refactor(ui): replace Tailwind utilities with local styles

This commit is contained in:
2026-08-09 13:58:13 +05:30
parent ff62918dbb
commit 5c2c6a0a8f
11 changed files with 473 additions and 184 deletions
+7 -7
View File
@@ -46,16 +46,16 @@
"packages/ui/components/HeroActions.wrn": "c5720efe2ce7f6c114eb2ea4ccc3e8102a1099078ba23fd13770eddeb6aec640", "packages/ui/components/HeroActions.wrn": "c5720efe2ce7f6c114eb2ea4ccc3e8102a1099078ba23fd13770eddeb6aec640",
"packages/ui/components/Image.wrn": "8113441bf90ab0d1d8618d477125ad339021f39e853db89d656c72c492e7a20b", "packages/ui/components/Image.wrn": "8113441bf90ab0d1d8618d477125ad339021f39e853db89d656c72c492e7a20b",
"packages/ui/components/InputGroup.wrn": "7347865ff9b90dde690cf0753adc753a91d2ae599e92405f47e5b35806f58e06", "packages/ui/components/InputGroup.wrn": "7347865ff9b90dde690cf0753adc753a91d2ae599e92405f47e5b35806f58e06",
"packages/ui/components/InputNumber.wrn": "3850f1cbcd314367e17244c644f1115e5ecddc7dc8248aa33fee88936c2981db", "packages/ui/components/InputNumber.wrn": "0ec020084f3e556d438f8264d61f836cfab4951c52d4df60e4944fb734fb38a9",
"packages/ui/components/Kbd.wrn": "5afaabbb15902bd8fb82cb4155cf6cbfff55600927b54d564d27e2dd8b752386", "packages/ui/components/Kbd.wrn": "5afaabbb15902bd8fb82cb4155cf6cbfff55600927b54d564d27e2dd8b752386",
"packages/ui/components/LayoutSplitter.wrn": "6bd6812e364420ed5d3439862116c58d0b011b298679005795bdccdbb7455066", "packages/ui/components/LayoutSplitter.wrn": "6bd6812e364420ed5d3439862116c58d0b011b298679005795bdccdbb7455066",
"packages/ui/components/LegendIndicator.wrn": "6a7607b27a17196f899132eb952203a1073e4077365a94935f485e700cbac665", "packages/ui/components/LegendIndicator.wrn": "6a7607b27a17196f899132eb952203a1073e4077365a94935f485e700cbac665",
"packages/ui/components/Link.wrn": "b986abcc66bc0b1c6296f3ff4e560812e35d89cc680d5251d7a3883e68c1d053", "packages/ui/components/Link.wrn": "b986abcc66bc0b1c6296f3ff4e560812e35d89cc680d5251d7a3883e68c1d053",
"packages/ui/components/List.wrn": "11e07bbf25956c9b0116ac100c2deda0455d5e1679bfc3395450ea3a8bf23f22", "packages/ui/components/List.wrn": "9bfa9613f388e5b126fc27d505e8165d7aa4a058e72132b755585464e116490e",
"packages/ui/components/ListGroup.wrn": "37e2ec61ceab20ed020a834da03208c493702b41abcf586816e29f2e50d8ee11", "packages/ui/components/ListGroup.wrn": "37e2ec61ceab20ed020a834da03208c493702b41abcf586816e29f2e50d8ee11",
"packages/ui/components/Map.wrn": "2e2335a8ef3da51519478ccbc380adeb37c97ae84336c4e94d4d0ceb857f391e", "packages/ui/components/Map.wrn": "9023a7a188480b24222a3910d26f6926918470369a1ab610f841c31a6120b9aa",
"packages/ui/components/MarketingSectionHeader.wrn": "7d6ffcc01a9331474475ea57ca58598be757abd9428b66bdfaf6f3603c5b145b", "packages/ui/components/MarketingSectionHeader.wrn": "7d6ffcc01a9331474475ea57ca58598be757abd9428b66bdfaf6f3603c5b145b",
"packages/ui/components/Marquee.wrn": "59c6b30bfa4d3a2084a7454c856dcd2f24d20d67c559cb2434f466f418c6cd87", "packages/ui/components/Marquee.wrn": "2a88c8bd8378469add26b0d937adccac4aa9f2c2005393010298e8e4fa89693e",
"packages/ui/components/MegaMenu.wrn": "8a9a9b348ca1c5e182914ff55d981a91f47a49c52ef5e5efd8e4cb6e56361a43", "packages/ui/components/MegaMenu.wrn": "8a9a9b348ca1c5e182914ff55d981a91f47a49c52ef5e5efd8e4cb6e56361a43",
"packages/ui/components/MetricCard.wrn": "6451182739298691908f68258c0250cce2a78b0dc27c97115579ece30d7d9f92", "packages/ui/components/MetricCard.wrn": "6451182739298691908f68258c0250cce2a78b0dc27c97115579ece30d7d9f92",
"packages/ui/components/MetricGrid.wrn": "6018a98c10628ed240ed236ed916c0ff60994d192c3aadafd10bc876be0f9364", "packages/ui/components/MetricGrid.wrn": "6018a98c10628ed240ed236ed916c0ff60994d192c3aadafd10bc876be0f9364",
@@ -72,7 +72,7 @@
"packages/ui/components/RangeSlider.wrn": "90ef35ceb94899dfd6e07ef3f3a88f2fb49c8dfe4596def67fe39fd9911d73c7", "packages/ui/components/RangeSlider.wrn": "90ef35ceb94899dfd6e07ef3f3a88f2fb49c8dfe4596def67fe39fd9911d73c7",
"packages/ui/components/Rating.wrn": "a2d74dc748fc7d98892684c760518b87fa65411b1102e3611252b87245b3ffcb", "packages/ui/components/Rating.wrn": "a2d74dc748fc7d98892684c760518b87fa65411b1102e3611252b87245b3ffcb",
"packages/ui/components/Scrollspy.wrn": "76d1c17580c8d460f1222ce97585bdcac9779141f2f30d5460c84ff9f75413d8", "packages/ui/components/Scrollspy.wrn": "76d1c17580c8d460f1222ce97585bdcac9779141f2f30d5460c84ff9f75413d8",
"packages/ui/components/SearchBox.wrn": "ab6a78b88aef494b85a87470d4392303d9d1937a5327f1c8aa8d910f1b1829c7", "packages/ui/components/SearchBox.wrn": "717f7647fabd762f8ed60a7c3306eac3ddb83c0da292a53c76d91886c2b8f94f",
"packages/ui/components/Section.wrn": "33eb8a82a2102229c2b1f64cec497e9e8e70b67adab8dbe396be860fccbb699c", "packages/ui/components/Section.wrn": "33eb8a82a2102229c2b1f64cec497e9e8e70b67adab8dbe396be860fccbb699c",
"packages/ui/components/SectionHeader.wrn": "8eecf2ea7a93139512260e3d46ca2a1afeb341b8bd46b7c30baa6e2c75e25d10", "packages/ui/components/SectionHeader.wrn": "8eecf2ea7a93139512260e3d46ca2a1afeb341b8bd46b7c30baa6e2c75e25d10",
"packages/ui/components/Sidebar.wrn": "03f1bbce75ca6d50ed940e62df39c539610e89eadd2064fe24abc7d5470f98bf", "packages/ui/components/Sidebar.wrn": "03f1bbce75ca6d50ed940e62df39c539610e89eadd2064fe24abc7d5470f98bf",
@@ -82,9 +82,9 @@
"packages/ui/components/StrongPassword.wrn": "9559756cacd10d613aea7a4ab9845a011f7f2f4613f82f83c87ab5fd2bad2ea9", "packages/ui/components/StrongPassword.wrn": "9559756cacd10d613aea7a4ab9845a011f7f2f4613f82f83c87ab5fd2bad2ea9",
"packages/ui/components/StyledIcon.wrn": "4a4504e357dee9dd0418edbe85fc90b824ccdb3752123a2125da95b77bf0b336", "packages/ui/components/StyledIcon.wrn": "4a4504e357dee9dd0418edbe85fc90b824ccdb3752123a2125da95b77bf0b336",
"packages/ui/components/Tabs.wrn": "e38336d28876caa4e97da32a569749e3803b030284a85ce9f35fa788e51cc5b0", "packages/ui/components/Tabs.wrn": "e38336d28876caa4e97da32a569749e3803b030284a85ce9f35fa788e51cc5b0",
"packages/ui/components/TextLink.wrn": "30782039293eb36d63b7b3a4f32a71a47177a3a68c7e90184be7cf7b4385eb19", "packages/ui/components/TextLink.wrn": "6e18906337cd3d30c2a0122593d249bde8f7cedc697b90ae388e2bd0e8eff3e1",
"packages/ui/components/TimePicker.wrn": "b3f24767448cb04ddd56e06540b52e5cc32ea21ed4acd2a5eb4f7e46f8617aba", "packages/ui/components/TimePicker.wrn": "b3f24767448cb04ddd56e06540b52e5cc32ea21ed4acd2a5eb4f7e46f8617aba",
"packages/ui/components/Timeline.wrn": "eaa6764b9e62c28cb6919ac1e86d3ebe9abc3cd8036091cdb703540dbfc33873", "packages/ui/components/Timeline.wrn": "686a37ebc62732842846d004491336970db94945bca942f226097d863e159d1c",
"packages/ui/components/Toaster.wrn": "288b2b370f65bf823e8d2a6f8c8cf66972332b8174303d33ebe8546d5e1d52c4", "packages/ui/components/Toaster.wrn": "288b2b370f65bf823e8d2a6f8c8cf66972332b8174303d33ebe8546d5e1d52c4",
"packages/ui/components/ToggleCount.wrn": "ee706b191fcfc01dd7bde0ddba1940af400ed2d01b639016134699963b1000ab", "packages/ui/components/ToggleCount.wrn": "ee706b191fcfc01dd7bde0ddba1940af400ed2d01b639016134699963b1000ab",
"packages/ui/components/TogglePassword.wrn": "c644436c3435b8a9b39c6b7a9f75671b76ca74f273952ce5c032ca169b0470e5", "packages/ui/components/TogglePassword.wrn": "c644436c3435b8a9b39c6b7a9f75671b76ca74f273952ce5c032ca169b0470e5",
+17
View File
@@ -2098,6 +2098,23 @@ const MIGRATIONS: Migration[] = [
// start painting where they previously rendered as nothing. // start painting where they previously rendered as nothing.
}, },
}, },
{
version: "0.8.7",
id: "0.8.7-ui-component-depth",
description:
"Removes superseded UI scaffolds and moves the remaining utility-styled components to self-contained wire-* BEM styles.",
apply() {
// AdvancedDatePicker -> DatePicker; AdvancedRangeSlider -> RangeSlider;
// FileUpload -> FileInput + FileUploadProgress (or @wrnexus/uploader);
// Toast and ToastNotifications -> Toaster.
//
// List, InputNumber, Marquee, TextLink, Map, SearchBox and Timeline no
// longer render Tailwind utility class names. Application CSS selecting
// those internal utilities must move to the component's wire-* BEM
// classes or its data-size/data-variant attributes. List now emits
// select for non-link items as well as navigation items.
},
},
]; ];
/** Release tooling uses this to require an explicit migration entry per version. */ /** Release tooling uses this to require an explicit migration entry per version. */
+5 -9
View File
@@ -118,16 +118,12 @@ are `.wrn` files rendered server-side.
The framework serves this stylesheet once at `/__wrnexus/ui.css`, so pages get all The framework serves this stylesheet once at `/__wrnexus/ui.css`, so pages get all
component styles from a single request. component styles from a single request.
### Tailwind and motion ### Component styles and motion
Components use static Tailwind utility classes alongside the shared `.wire-*` Components own their BEM styles in local `style {}` blocks and do not require a
layer. If the package is consumed by a separate Tailwind build, include its Tailwind scan. `ui.css` supplies only global tokens, resets, and shared
component sources so every utility is generated: primitives. Applications can still use Tailwind independently in their own
source files.
```css
@import "tailwindcss";
@source "../node_modules/@wrnexus/ui/components/*.wrn";
```
The shared stylesheet gives all component boundaries consistent, GPU-friendly The shared stylesheet gives all component boundaries consistent, GPU-friendly
entry and interaction motion. Override `--wire-motion-fast`, entry and interaction motion. Override `--wire-motion-fast`,
+82 -26
View File
@@ -418,19 +418,20 @@ component InputNumber {
data-value='{currentValue}' data-value='{currentValue}'
data-invalid='{isInvalid() ? "true" : "false"}' data-invalid='{isInvalid() ? "true" : "false"}'
data-disabled='{disabled ? "true" : "false"}' data-disabled='{disabled ? "true" : "false"}'
data-full-width='{fullWidth ? "true" : "false"}'
style='--input-number-accent: {componentColor()};' style='--input-number-accent: {componentColor()};'
class='relative flex flex-col gap-1.5 text-[var(--wire-color-text)] {fullWidth ? "w-full" : variant === "compact" ? "w-fit max-w-full" : "w-full max-w-sm"} {disabled ? "opacity-60" : ""} {class}' class='wire-input-number {class}'
> >
{#if label !== "" && variant !== "labeled" && variant !== "seat"} {#if label !== "" && variant !== "labeled" && variant !== "seat"}
<label <label
for='{inputId()}' for='{inputId()}'
class='inline-flex items-center gap-1 text-sm font-semibold leading-5 text-[var(--wire-color-text)]' class='wire-input-number__label'
> >
<span>{label}</span> <span>{label}</span>
{#if required} {#if required}
<span <span
aria-hidden="true" aria-hidden="true"
class='text-[var(--wire-color-danger)]' class='wire-input-number__required'
>*</span> >*</span>
{/if} {/if}
</label> </label>
@@ -439,14 +440,14 @@ component InputNumber {
{#if description !== "" && variant !== "labeled" && variant !== "seat"} {#if description !== "" && variant !== "labeled" && variant !== "seat"}
<p <p
id='{descriptionId()}' id='{descriptionId()}'
class='m-0 text-xs leading-5 text-[var(--wire-color-muted)]' class='wire-input-number__description'
> >
{description} {description}
</p> </p>
{/if} {/if}
<div <div
class='group flex min-w-0 overflow-hidden border bg-[var(--wire-color-surface)] text-[var(--wire-color-text)] shadow-[var(--wire-shadow-1)] transition-[border-color,box-shadow,background-color] duration-[var(--wire-motion-base)] ease-[var(--wire-ease-standard)] focus-within:border-[var(--input-number-accent)] focus-within:shadow-[0_0_0_3px_color-mix(in_srgb,var(--input-number-accent)_18%,transparent)] {variant === "compact" ? "rounded-full" : "rounded-[var(--wire-radius-sm)]"} {size === "xs" ? "min-h-8 text-xs" : size === "sm" ? "min-h-9 text-sm" : size === "lg" ? "min-h-12 text-base" : size === "xl" ? "min-h-14 text-lg" : "min-h-10 text-sm"} {isInvalid() ? "border-[var(--wire-color-danger)] focus-within:border-[var(--wire-color-danger)] focus-within:shadow-[0_0_0_3px_color-mix(in_srgb,var(--wire-color-danger)_18%,transparent)]" : "border-[var(--wire-color-border)]"} {disabled ? "cursor-not-allowed bg-[var(--wire-color-surface-2)]" : ""}' class='wire-input-number__control'
> >
{#if variant === "horizontal" && showButtons} {#if variant === "horizontal" && showButtons}
<button <button
@@ -455,30 +456,30 @@ component InputNumber {
aria-controls='{inputId()}' aria-controls='{inputId()}'
disabled='{decrementDisabled()}' disabled='{decrementDisabled()}'
@click='decrementValue(event)' @click='decrementValue(event)'
class='inline-flex shrink-0 items-center justify-center border-r border-[var(--wire-color-border)] bg-transparent text-[var(--wire-color-muted)] transition-[color,background-color] duration-[var(--wire-motion-fast)] hover:bg-[var(--wire-color-surface-2)] hover:text-[var(--input-number-accent)] focus-visible:z-10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--input-number-accent)] disabled:cursor-not-allowed disabled:opacity-40 {size === "xs" ? "w-8" : size === "sm" ? "w-9" : size === "lg" ? "w-12" : size === "xl" ? "w-14" : "w-10"}' class='wire-input-number__button wire-input-number__button--start'
> >
<span <span
aria-hidden="true" aria-hidden="true"
class='icon-[lucide--minus] size-4' class='icon-[lucide--minus] wire-input-number__icon'
></span> ></span>
</button> </button>
{/if} {/if}
<div <div
class='flex min-w-0 flex-1 {variant === "labeled" ? "flex-col items-stretch justify-center gap-0.5" : variant === "seat" ? "items-center justify-between gap-3" : "items-center"} {size === "xs" ? "px-2" : size === "sm" ? "px-2.5" : size === "lg" ? "px-4" : size === "xl" ? "px-5" : "px-3"}' class='wire-input-number__content'
> >
{#if variant === "labeled" || variant === "seat"} {#if variant === "labeled" || variant === "seat"}
<div class='min-w-0 flex-1'> <div class='wire-input-number__copy'>
{#if label !== ""} {#if label !== ""}
<label <label
for='{inputId()}' for='{inputId()}'
class='block truncate font-semibold leading-4 text-[var(--wire-color-text)] {variant === "labeled" ? "text-xs font-medium text-[var(--wire-color-muted)]" : "text-sm"}' class='wire-input-number__inner-label'
> >
{label} {label}
{#if required} {#if required}
<span <span
aria-hidden="true" aria-hidden="true"
class='ml-0.5 text-[var(--wire-color-danger)]' class='wire-input-number__required'
>*</span> >*</span>
{/if} {/if}
</label> </label>
@@ -487,7 +488,7 @@ component InputNumber {
{#if description !== ""} {#if description !== ""}
<span <span
id='{descriptionId()}' id='{descriptionId()}'
class='block truncate text-xs leading-4 text-[var(--wire-color-muted)]' class='wire-input-number__inner-description'
> >
{description} {description}
</span> </span>
@@ -496,12 +497,12 @@ component InputNumber {
{/if} {/if}
<div <div
class='flex min-w-0 items-center {variant === "seat" ? "w-auto shrink-0" : "w-full"}' class='wire-input-number__field'
> >
{#if prefix !== ""} {#if prefix !== ""}
<span <span
aria-hidden="true" aria-hidden="true"
class='shrink-0 pr-1.5 text-[var(--wire-color-muted)]' class='wire-input-number__prefix'
> >
{prefix} {prefix}
</span> </span>
@@ -531,13 +532,13 @@ component InputNumber {
@change='handleChange(event)' @change='handleChange(event)'
@keydown='handleKeydown(event)' @keydown='handleKeydown(event)'
@wheel='handleWheel(event)' @wheel='handleWheel(event)'
class='min-w-0 flex-1 appearance-none border-0 bg-transparent p-0 font-medium leading-none text-[var(--wire-color-text)] outline-none placeholder:text-[var(--wire-color-muted)] read-only:cursor-default disabled:cursor-not-allowed [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none {variant === "horizontal" || variant === "compact" || variant === "seat" ? "text-center" : "text-left"} {variant === "seat" ? "w-10 flex-none" : "w-full"}' class='wire-input-number__input'
/> />
{#if suffix !== ""} {#if suffix !== ""}
<span <span
aria-hidden="true" aria-hidden="true"
class='shrink-0 pl-1.5 text-[var(--wire-color-muted)]' class='wire-input-number__suffix'
> >
{suffix} {suffix}
</span> </span>
@@ -552,11 +553,11 @@ component InputNumber {
aria-controls='{inputId()}' aria-controls='{inputId()}'
disabled='{incrementDisabled()}' disabled='{incrementDisabled()}'
@click='incrementValue(event)' @click='incrementValue(event)'
class='inline-flex shrink-0 items-center justify-center border-l border-[var(--wire-color-border)] bg-transparent text-[var(--wire-color-muted)] transition-[color,background-color] duration-[var(--wire-motion-fast)] hover:bg-[var(--wire-color-surface-2)] hover:text-[var(--input-number-accent)] focus-visible:z-10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--input-number-accent)] disabled:cursor-not-allowed disabled:opacity-40 {size === "xs" ? "w-8" : size === "sm" ? "w-9" : size === "lg" ? "w-12" : size === "xl" ? "w-14" : "w-10"}' class='wire-input-number__button wire-input-number__button--end'
> >
<span <span
aria-hidden="true" aria-hidden="true"
class='icon-[lucide--plus] size-4' class='icon-[lucide--plus] wire-input-number__icon'
></span> ></span>
</button> </button>
{:else} {:else}
@@ -564,7 +565,7 @@ component InputNumber {
<div <div
role="group" role="group"
aria-label='{controlsLabel}' aria-label='{controlsLabel}'
class='flex shrink-0 border-l border-[var(--wire-color-border)] {variant === "vertical" ? "flex-col" : "flex-row"}' class='wire-input-number__buttons'
> >
<button <button
type="button" type="button"
@@ -572,11 +573,11 @@ component InputNumber {
aria-controls='{inputId()}' aria-controls='{inputId()}'
disabled='{decrementDisabled()}' disabled='{decrementDisabled()}'
@click='decrementValue(event)' @click='decrementValue(event)'
class='inline-flex items-center justify-center bg-transparent text-[var(--wire-color-muted)] transition-[color,background-color] duration-[var(--wire-motion-fast)] hover:bg-[var(--wire-color-surface-2)] hover:text-[var(--input-number-accent)] focus-visible:z-10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--input-number-accent)] disabled:cursor-not-allowed disabled:opacity-40 {variant === "vertical" ? "flex-1 border-b border-[var(--wire-color-border)]" : "border-r border-[var(--wire-color-border)]"} {size === "xs" ? "w-8" : size === "sm" ? "w-9" : size === "lg" ? "w-12" : size === "xl" ? "w-14" : "w-10"}' class='wire-input-number__button wire-input-number__button--decrement'
> >
<span <span
aria-hidden="true" aria-hidden="true"
class='icon-[lucide--minus] size-4' class='icon-[lucide--minus] wire-input-number__icon'
></span> ></span>
</button> </button>
@@ -586,11 +587,11 @@ component InputNumber {
aria-controls='{inputId()}' aria-controls='{inputId()}'
disabled='{incrementDisabled()}' disabled='{incrementDisabled()}'
@click='incrementValue(event)' @click='incrementValue(event)'
class='inline-flex items-center justify-center bg-transparent text-[var(--wire-color-muted)] transition-[color,background-color] duration-[var(--wire-motion-fast)] hover:bg-[var(--wire-color-surface-2)] hover:text-[var(--input-number-accent)] focus-visible:z-10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--input-number-accent)] disabled:cursor-not-allowed disabled:opacity-40 {variant === "vertical" ? "flex-1" : ""} {size === "xs" ? "w-8" : size === "sm" ? "w-9" : size === "lg" ? "w-12" : size === "xl" ? "w-14" : "w-10"}' class='wire-input-number__button wire-input-number__button--increment'
> >
<span <span
aria-hidden="true" aria-hidden="true"
class='icon-[lucide--plus] size-4' class='icon-[lucide--plus] wire-input-number__icon'
></span> ></span>
</button> </button>
</div> </div>
@@ -603,11 +604,11 @@ component InputNumber {
id='{messageId()}' id='{messageId()}'
role="alert" role="alert"
aria-live="polite" aria-live="polite"
class='m-0 flex items-center gap-1.5 text-xs leading-5 text-[var(--wire-color-danger)]' class='wire-input-number__message wire-input-number__message--error'
> >
<span <span
aria-hidden="true" aria-hidden="true"
class='icon-[lucide--circle-alert] size-3.5 shrink-0' class='icon-[lucide--circle-alert] wire-input-number__error-icon'
></span> ></span>
<span>{validationMessage()}</span> <span>{validationMessage()}</span>
</p> </p>
@@ -616,11 +617,66 @@ component InputNumber {
{#if (!showValidationMessage || !isInvalid() || validationMessage() === "") && helpText !== ""} {#if (!showValidationMessage || !isInvalid() || validationMessage() === "") && helpText !== ""}
<p <p
id='{messageId()}' id='{messageId()}'
class='m-0 text-xs leading-5 text-[var(--wire-color-muted)]' class='wire-input-number__message'
> >
{helpText} {helpText}
</p> </p>
{/if} {/if}
</div> </div>
} }
style {
.wire-input-number { position: relative; display: flex; width: 100%; max-width: 24rem; flex-direction: column; gap: 0.375rem; color: var(--wire-color-text); }
.wire-input-number[data-full-width="true"] { max-width: none; }
.wire-input-number[data-variant="compact"] { width: fit-content; max-width: 100%; }
.wire-input-number[data-disabled="true"] { opacity: 0.6; }
.wire-input-number__label { display: inline-flex; align-items: center; gap: 0.25rem; color: var(--wire-color-text); font-size: 0.875rem; font-weight: 600; line-height: 1.25rem; }
.wire-input-number__required { margin-left: 0.125rem; color: var(--wire-color-danger); }
.wire-input-number__description, .wire-input-number__message { margin: 0; color: var(--wire-color-muted); font-size: 0.75rem; line-height: 1.25rem; }
.wire-input-number__control { display: flex; min-width: 0; min-height: 2.5rem; overflow: hidden; border: 1px solid var(--wire-color-border); border-radius: var(--wire-radius-sm); color: var(--wire-color-text); background: var(--wire-color-surface); box-shadow: var(--wire-shadow-1); transition: border-color var(--wire-motion-base), box-shadow var(--wire-motion-base), background var(--wire-motion-base); }
.wire-input-number__control:focus-within { border-color: var(--input-number-accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--input-number-accent) 18%, transparent); }
.wire-input-number[data-invalid="true"] .wire-input-number__control { border-color: var(--wire-color-danger); }
.wire-input-number[data-invalid="true"] .wire-input-number__control:focus-within { box-shadow: 0 0 0 3px color-mix(in srgb, var(--wire-color-danger) 18%, transparent); }
.wire-input-number[data-disabled="true"] .wire-input-number__control { cursor: not-allowed; background: var(--wire-color-surface-2); }
.wire-input-number[data-variant="compact"] .wire-input-number__control { border-radius: 999px; }
.wire-input-number[data-size="xs"] .wire-input-number__control { min-height: 2rem; font-size: 0.75rem; }
.wire-input-number[data-size="sm"] .wire-input-number__control { min-height: 2.25rem; font-size: 0.875rem; }
.wire-input-number[data-size="lg"] .wire-input-number__control { min-height: 3rem; font-size: 1rem; }
.wire-input-number[data-size="xl"] .wire-input-number__control { min-height: 3.5rem; font-size: 1.125rem; }
.wire-input-number__content { display: flex; min-width: 0; flex: 1; align-items: center; padding-inline: 0.75rem; }
.wire-input-number[data-size="xs"] .wire-input-number__content { padding-inline: 0.5rem; }
.wire-input-number[data-size="sm"] .wire-input-number__content { padding-inline: 0.625rem; }
.wire-input-number[data-size="lg"] .wire-input-number__content { padding-inline: 1rem; }
.wire-input-number[data-size="xl"] .wire-input-number__content { padding-inline: 1.25rem; }
.wire-input-number[data-variant="labeled"] .wire-input-number__content { flex-direction: column; align-items: stretch; justify-content: center; gap: 0.125rem; }
.wire-input-number[data-variant="seat"] .wire-input-number__content { justify-content: space-between; gap: 0.75rem; }
.wire-input-number__copy { min-width: 0; flex: 1; }
.wire-input-number__inner-label, .wire-input-number__inner-description { display: block; overflow: hidden; color: var(--wire-color-text); font-size: 0.875rem; font-weight: 600; line-height: 1rem; text-overflow: ellipsis; white-space: nowrap; }
.wire-input-number__inner-description { color: var(--wire-color-muted); font-size: 0.75rem; font-weight: 400; }
.wire-input-number[data-variant="labeled"] .wire-input-number__inner-label { color: var(--wire-color-muted); font-size: 0.75rem; font-weight: 500; }
.wire-input-number__field { display: flex; width: 100%; min-width: 0; align-items: center; }
.wire-input-number[data-variant="seat"] .wire-input-number__field { width: auto; flex: none; }
.wire-input-number__prefix, .wire-input-number__suffix { flex: none; color: var(--wire-color-muted); }
.wire-input-number__prefix { padding-right: 0.375rem; } .wire-input-number__suffix { padding-left: 0.375rem; }
.wire-input-number__input { width: 100%; min-width: 0; flex: 1; padding: 0; border: 0; outline: none; color: var(--wire-color-text); background: transparent; font: inherit; font-weight: 500; line-height: 1; appearance: textfield; }
.wire-input-number__input::-webkit-inner-spin-button, .wire-input-number__input::-webkit-outer-spin-button { appearance: none; }
.wire-input-number__input::placeholder { color: var(--wire-color-muted); }
.wire-input-number__input:read-only { cursor: default; }
.wire-input-number__input:disabled { cursor: not-allowed; }
.wire-input-number:is([data-variant="horizontal"], [data-variant="compact"], [data-variant="seat"]) .wire-input-number__input { text-align: center; }
.wire-input-number[data-variant="seat"] .wire-input-number__input { width: 2.5rem; flex: none; }
.wire-input-number__buttons { display: flex; flex: none; border-left: 1px solid var(--wire-color-border); }
.wire-input-number[data-variant="vertical"] .wire-input-number__buttons { flex-direction: column; }
.wire-input-number__button { display: inline-flex; width: 2.5rem; flex: none; align-items: center; justify-content: center; padding: 0; border: 0; color: var(--wire-color-muted); background: transparent; transition: color var(--wire-motion-fast), background var(--wire-motion-fast); }
.wire-input-number[data-size="xs"] .wire-input-number__button { width: 2rem; } .wire-input-number[data-size="sm"] .wire-input-number__button { width: 2.25rem; } .wire-input-number[data-size="lg"] .wire-input-number__button { width: 3rem; } .wire-input-number[data-size="xl"] .wire-input-number__button { width: 3.5rem; }
.wire-input-number__button--start { border-right: 1px solid var(--wire-color-border); } .wire-input-number__button--end { border-left: 1px solid var(--wire-color-border); }
.wire-input-number__button--decrement { border-right: 1px solid var(--wire-color-border); }
.wire-input-number[data-variant="vertical"] .wire-input-number__button--decrement { border-right: 0; border-bottom: 1px solid var(--wire-color-border); }
.wire-input-number__button:hover { color: var(--input-number-accent); background: var(--wire-color-surface-2); }
.wire-input-number__button:focus-visible { z-index: 1; outline: 2px solid var(--input-number-accent); outline-offset: -2px; }
.wire-input-number__button:disabled { cursor: not-allowed; opacity: 0.4; }
.wire-input-number__icon { width: 1rem; height: 1rem; }
.wire-input-number__message--error { display: flex; align-items: center; gap: 0.375rem; color: var(--wire-color-danger); }
.wire-input-number__error-icon { width: 0.875rem; height: 0.875rem; flex: none; }
}
} }
+56 -39
View File
@@ -17,89 +17,76 @@ component List {
<section <section
data-ui-component="List" data-ui-component="List"
aria-label='{title}' aria-label='{title}'
class='w-full {class}' class='wire-list {class}'
> >
{#if title || description} {#if title || description}
<header class="mb-4"> <header class="wire-list__header">
{#if title} {#if title}
<h3 class="text-lg font-bold text-[var(--wire-color-text)]">{title}</h3> <h3 class="wire-list__title">{title}</h3>
{/if} {/if}
{#if description} {#if description}
<p class="mt-1 text-sm leading-6 text-[var(--wire-color-text-muted)]">{description}</p> <p class="wire-list__description">{description}</p>
{/if} {/if}
</header> </header>
{/if} {/if}
<ul <ul
class="overflow-hidden" class="wire-list__items"
class:divide-y='variant === "default" || variant === "divided"' data-variant='{variant}'
class:divide-[var(--wire-color-border)]='variant === "default" || variant === "divided"'
class:rounded-2xl='variant === "card"'
class:border='variant === "card"'
class:border-[var(--wire-color-border)]='variant === "card"'
class:bg-[var(--wire-color-surface-raised)]='variant === "card"'
class:space-y-3='variant === "separated"'
> >
{#each items as item, index} {#each items as item, index}
<li <li
class="group min-w-0" class="wire-list__item"
class:rounded-xl='variant === "separated"' data-variant='{variant}'
class:border='variant === "separated"' @click='if (!item.href) { output.select({ item: item, index: index }) }'
class:border-[var(--wire-color-border)]='variant === "separated"'
class:bg-[var(--wire-color-surface-raised)]='variant === "separated"'
> >
{#if item.href} {#if item.href}
<a <a
href='{item.href}' href='{item.href}'
class="flex min-w-0 items-start gap-3 px-4 py-3 outline-none transition hover:bg-[var(--wire-color-surface-soft)] focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--wire-color-focus)]" class="wire-list__row"
class:px-3='size === "sm"' data-size='{size}'
class:py-2.5='size === "sm"'
class:px-5='size === "lg"'
class:py-4='size === "lg"'
@click='output.select({ item: item, index: index })' @click='output.select({ item: item, index: index })'
> >
{#if item.icon} {#if item.icon}
<span class="flex size-10 shrink-0 items-center justify-center rounded-xl bg-[var(--wire-color-primary-soft)] text-[var(--wire-color-primary)]"> <span class="wire-list__leading-icon"><span class='{item.icon}' aria-hidden="true"></span>
<span class='{item.icon + " size-5"}' aria-hidden="true"></span>
</span> </span>
{/if} {/if}
{#if item.imageSrc} {#if item.imageSrc}
<img src='{item.imageSrc}' alt='{item.imageAlt || ""}' class="size-12 shrink-0 rounded-xl object-cover" loading="lazy" /> <img src='{item.imageSrc}' alt='{item.imageAlt || ""}' class="wire-list__image" loading="lazy" />
{/if} {/if}
<div class="min-w-0 flex-1"> <div class="wire-list__body">
<div class="flex items-start justify-between gap-3"> <div class="wire-list__heading">
<p class="truncate font-semibold text-[var(--wire-color-text)]">{item.title || item.label}</p> <p class="wire-list__item-title">{item.title || item.label}</p>
{#if item.meta} {#if item.meta}
<span class="shrink-0 text-xs text-[var(--wire-color-text-muted)]">{item.meta}</span> <span class="wire-list__meta">{item.meta}</span>
{/if} {/if}
</div> </div>
{#if item.description} {#if item.description}
<p class="mt-1 line-clamp-2 text-sm leading-5 text-[var(--wire-color-text-muted)]">{item.description}</p> <p class="wire-list__item-description">{item.description}</p>
{/if} {/if}
{#if item.badge} {#if item.badge}
<span class="mt-2 inline-flex rounded-full bg-[var(--wire-color-primary-soft)] px-2.5 py-1 text-xs font-semibold text-[var(--wire-color-primary)]">{item.badge}</span> <span class="wire-list__badge">{item.badge}</span>
{/if} {/if}
</div> </div>
<span class="icon-[lucide--chevron-right] mt-1 size-4 shrink-0 text-[var(--wire-color-text-muted)] transition group-hover:translate-x-0.5 group-hover:text-[var(--wire-color-primary)]" aria-hidden="true"></span> <span class="icon-[lucide--chevron-right] wire-list__chevron" aria-hidden="true"></span>
</a> </a>
{:else} {:else}
<div class="flex min-w-0 items-start gap-3 px-4 py-3"> <div class="wire-list__row" data-size='{size}'>
{#if item.icon} {#if item.icon}
<span class="flex size-10 shrink-0 items-center justify-center rounded-xl bg-[var(--wire-color-primary-soft)] text-[var(--wire-color-primary)]"> <span class="wire-list__leading-icon"><span class='{item.icon}' aria-hidden="true"></span>
<span class='{item.icon + " size-5"}' aria-hidden="true"></span>
</span> </span>
{/if} {/if}
<div class="min-w-0 flex-1"> <div class="wire-list__body">
<p class="font-semibold text-[var(--wire-color-text)]">{item.title || item.label}</p> <p class="wire-list__item-title">{item.title || item.label}</p>
{#if item.description} {#if item.description}
<p class="mt-1 text-sm leading-5 text-[var(--wire-color-text-muted)]">{item.description}</p> <p class="wire-list__item-description">{item.description}</p>
{/if} {/if}
</div> </div>
</div> </div>
{/if} {/if}
</li> </li>
{:empty} {:empty}
<li class="rounded-xl border border-dashed border-[var(--wire-color-border)] p-6 text-center text-sm text-[var(--wire-color-text-muted)]"> <li class="wire-list__empty">
No items available. No items available.
</li> </li>
{/each} {/each}
@@ -108,4 +95,34 @@ component List {
<slot></slot> <slot></slot>
</section> </section>
} }
style {
.wire-list { width: 100%; }
.wire-list__header { margin-bottom: 1rem; }
.wire-list__title { color: var(--wire-color-text); font-size: 1.125rem; font-weight: 700; }
.wire-list__description { margin-top: 0.25rem; color: var(--wire-color-text-muted); font-size: 0.875rem; line-height: 1.5rem; }
.wire-list__items { overflow: hidden; }
.wire-list__items:is([data-variant="default"], [data-variant="divided"]) > .wire-list__item + .wire-list__item { border-top: 1px solid var(--wire-color-border); }
.wire-list__items[data-variant="card"] { border: 1px solid var(--wire-color-border); border-radius: calc(var(--wire-radius) * 1.5); background: var(--wire-color-surface-raised); }
.wire-list__items[data-variant="separated"] { display: grid; gap: 0.75rem; }
.wire-list__item { min-width: 0; }
.wire-list__item[data-variant="separated"] { border: 1px solid var(--wire-color-border); border-radius: var(--wire-radius); background: var(--wire-color-surface-raised); }
.wire-list__row { display: flex; min-width: 0; align-items: flex-start; gap: 0.75rem; padding: 0.75rem 1rem; outline: none; transition: background var(--wire-motion-base); }
a.wire-list__row:hover { background: var(--wire-color-surface-soft); }
.wire-list__row:focus-visible { box-shadow: inset 0 0 0 2px var(--wire-color-focus); }
.wire-list__row[data-size="sm"] { padding: 0.625rem 0.75rem; }
.wire-list__row[data-size="lg"] { padding: 1rem 1.25rem; }
.wire-list__leading-icon { display: flex; width: 2.5rem; height: 2.5rem; flex: none; align-items: center; justify-content: center; border-radius: var(--wire-radius); color: var(--wire-color-primary); background: var(--wire-color-primary-soft); }
.wire-list__leading-icon > span { width: 1.25rem; height: 1.25rem; }
.wire-list__image { width: 3rem; height: 3rem; flex: none; border-radius: var(--wire-radius); object-fit: cover; }
.wire-list__body { min-width: 0; flex: 1; }
.wire-list__heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 0.75rem; }
.wire-list__item-title { overflow: hidden; color: var(--wire-color-text); font-weight: 600; text-overflow: ellipsis; white-space: nowrap; }
.wire-list__meta { flex: none; color: var(--wire-color-text-muted); font-size: 0.75rem; }
.wire-list__item-description { display: -webkit-box; margin-top: 0.25rem; overflow: hidden; color: var(--wire-color-text-muted); font-size: 0.875rem; line-height: 1.25rem; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }
.wire-list__badge { display: inline-flex; margin-top: 0.5rem; padding: 0.25rem 0.625rem; border-radius: 999px; color: var(--wire-color-primary); background: var(--wire-color-primary-soft); font-size: 0.75rem; font-weight: 600; }
.wire-list__chevron { width: 1rem; height: 1rem; flex: none; margin-top: 0.25rem; color: var(--wire-color-text-muted); transition: color var(--wire-motion-base), transform var(--wire-motion-base); }
.wire-list__row:hover .wire-list__chevron { color: var(--wire-color-primary); transform: translateX(0.125rem); }
.wire-list__empty { padding: 1.5rem; border: 1px dashed var(--wire-color-border); border-radius: var(--wire-radius); color: var(--wire-color-text-muted); font-size: 0.875rem; text-align: center; }
}
} }
+36 -18
View File
@@ -21,37 +21,34 @@ component Map {
<section <section
data-ui-component="Map" data-ui-component="Map"
aria-label='{title}' aria-label='{title}'
class='overflow-hidden rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-raised)] shadow-sm {class}' class='wire-map {class}'
> >
{#if title || description} {#if title || description}
<header class="flex items-start justify-between gap-4 border-b border-[var(--wire-color-border)] p-5"> <header class="wire-map__header">
<div> <div>
{#if title} {#if title}
<h3 class="text-lg font-bold text-[var(--wire-color-text)]">{title}</h3> <h3 class="wire-map__title">{title}</h3>
{/if} {/if}
{#if description} {#if description}
<p class="mt-1 text-sm leading-6 text-[var(--wire-color-text-muted)]">{description}</p> <p class="wire-map__description">{description}</p>
{/if} {/if}
</div> </div>
<span class="flex size-10 shrink-0 items-center justify-center rounded-xl bg-[var(--wire-color-primary-soft)] text-[var(--wire-color-primary)]"> <span class="wire-map__header-icon"><span class="icon-[lucide--map]" aria-hidden="true"></span>
<span class="icon-[lucide--map] size-5" aria-hidden="true"></span>
</span> </span>
</header> </header>
{/if} {/if}
<div <div
class="relative isolate overflow-hidden bg-[var(--wire-color-surface-soft)]" class="wire-map__viewport"
class:h-64='size === "sm"' data-size='{size}'
class:h-80='size === "default" || size === "md"'
class:h-[28rem]='size === "lg"'
> >
<div <div
class="pointer-events-none absolute inset-0 opacity-50" class="wire-map__grid"
style="background-image: linear-gradient(var(--wire-color-border) 1px, transparent 1px), linear-gradient(90deg, var(--wire-color-border) 1px, transparent 1px); background-size: 32px 32px;" style="background-image: linear-gradient(var(--wire-color-border) 1px, transparent 1px), linear-gradient(90deg, var(--wire-color-border) 1px, transparent 1px); background-size: 32px 32px;"
aria-hidden="true" aria-hidden="true"
></div> ></div>
<div class="absolute inset-0 flex items-center justify-center p-6"> <div class="wire-map__content">
<slot></slot> <slot></slot>
</div> </div>
@@ -60,7 +57,7 @@ component Map {
<button <button
type="button" type="button"
aria-label='{item.label || item.title || "Map marker"}' aria-label='{item.label || item.title || "Map marker"}'
class="absolute inline-flex size-10 items-center justify-center rounded-full border-4 border-[var(--wire-color-surface-raised)] bg-[var(--wire-color-primary)] text-[var(--wire-color-on-primary)] shadow-lg transition hover:scale-110 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--wire-color-focus)]" class="wire-map__marker"
style='left: {item.x || (20 + index * 12)}%; top: {item.y || (30 + (index % 3) * 18)}%;' style='left: {item.x || (20 + index * 12)}%; top: {item.y || (30 + (index % 3) * 18)}%;'
@click='output.markerClick(item); output.select(item)' @click='output.markerClick(item); output.select(item)'
> >
@@ -69,25 +66,46 @@ component Map {
{/each} {/each}
{/if} {/if}
<div class="absolute bottom-4 right-4 flex flex-col gap-2"> <div class="wire-map__controls">
<button <button
type="button" type="button"
aria-label="Zoom in" aria-label="Zoom in"
class="inline-flex size-10 items-center justify-center rounded-xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-raised)] text-[var(--wire-color-text)] shadow-sm transition hover:bg-[var(--wire-color-surface-soft)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--wire-color-focus)]" class="wire-map__control"
@click='output.zoom({ direction: "in" })' @click='output.zoom({ direction: "in" })'
> >
<span class="icon-[lucide--plus] size-4" aria-hidden="true"></span> <span class="icon-[lucide--plus] wire-map__control-icon" aria-hidden="true"></span>
</button> </button>
<button <button
type="button" type="button"
aria-label="Zoom out" aria-label="Zoom out"
class="inline-flex size-10 items-center justify-center rounded-xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-raised)] text-[var(--wire-color-text)] shadow-sm transition hover:bg-[var(--wire-color-surface-soft)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--wire-color-focus)]" class="wire-map__control"
@click='output.zoom({ direction: "out" })' @click='output.zoom({ direction: "out" })'
> >
<span class="icon-[lucide--minus] size-4" aria-hidden="true"></span> <span class="icon-[lucide--minus] wire-map__control-icon" aria-hidden="true"></span>
</button> </button>
</div> </div>
</div> </div>
</section> </section>
} }
style {
.wire-map { overflow: hidden; border: 1px solid var(--wire-color-border); border-radius: calc(var(--wire-radius) * 1.5); background: var(--wire-color-surface-raised); box-shadow: var(--wire-shadow-1); }
.wire-map__header { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; padding: 1.25rem; border-bottom: 1px solid var(--wire-color-border); }
.wire-map__title { color: var(--wire-color-text); font-size: 1.125rem; font-weight: 700; }
.wire-map__description { margin-top: 0.25rem; color: var(--wire-color-text-muted); font-size: 0.875rem; line-height: 1.5rem; }
.wire-map__header-icon { display: flex; width: 2.5rem; height: 2.5rem; flex: none; align-items: center; justify-content: center; border-radius: var(--wire-radius); color: var(--wire-color-primary); background: var(--wire-color-primary-soft); }
.wire-map__header-icon > span { width: 1.25rem; height: 1.25rem; }
.wire-map__viewport { position: relative; isolation: isolate; height: 20rem; overflow: hidden; background: var(--wire-color-surface-soft); }
.wire-map__viewport[data-size="sm"] { height: 16rem; }
.wire-map__viewport[data-size="lg"] { height: 28rem; }
.wire-map__grid { position: absolute; inset: 0; opacity: 0.5; pointer-events: none; }
.wire-map__content { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; padding: 1.5rem; }
.wire-map__marker { position: absolute; display: inline-flex; width: 2.5rem; height: 2.5rem; align-items: center; justify-content: center; padding: 0; border: 4px solid var(--wire-color-surface-raised); border-radius: 50%; color: var(--wire-color-on-primary); background: var(--wire-color-primary); box-shadow: var(--wire-shadow-3); transition: transform var(--wire-motion-base) var(--wire-ease-standard); }
.wire-map__marker:hover { transform: scale(1.1); }
.wire-map__controls { position: absolute; right: 1rem; bottom: 1rem; display: flex; flex-direction: column; gap: 0.5rem; }
.wire-map__control { display: inline-flex; width: 2.5rem; height: 2.5rem; align-items: center; justify-content: center; padding: 0; border: 1px solid var(--wire-color-border); border-radius: var(--wire-radius); color: var(--wire-color-text); background: var(--wire-color-surface-raised); box-shadow: var(--wire-shadow-1); transition: background var(--wire-motion-base); }
.wire-map__control:hover { background: var(--wire-color-surface-soft); }
.wire-map__marker:focus-visible, .wire-map__control:focus-visible { outline: 2px solid var(--wire-color-focus); outline-offset: 2px; }
.wire-map__control-icon { width: 1rem; height: 1rem; }
}
} }
+38 -21
View File
@@ -21,46 +21,44 @@ component Marquee {
<section <section
data-ui-component="Marquee" data-ui-component="Marquee"
aria-label='{title}' aria-label='{title}'
class='overflow-hidden border-y border-[var(--wire-color-border)] bg-[var(--wire-color-surface-raised)] {class}' class='wire-marquee {class}'
> >
<div class="flex items-stretch"> <div class="wire-marquee__layout">
{#if title} {#if title}
<div class="relative z-10 flex shrink-0 items-center gap-2 border-r border-[var(--wire-color-border)] bg-[var(--wire-color-primary)] px-4 py-3 text-[var(--wire-color-on-primary)] sm:px-5"> <div class="wire-marquee__title">
<span class="icon-[lucide--megaphone] size-4" aria-hidden="true"></span> <span class="icon-[lucide--megaphone] wire-marquee__icon" aria-hidden="true"></span>
<span class="text-sm font-bold">{title}</span> <span>{title}</span>
</div> </div>
{/if} {/if}
<div <div
class="group relative min-w-0 flex-1 overflow-hidden" class="wire-marquee__window"
@mouseenter='paused = true; output.pause({})' @mouseenter='paused = true; output.pause({})'
@mouseleave='paused = false; output.resume({})' @mouseleave='paused = false; output.resume({})'
@focusin='paused = true' @focusin='paused = true'
@focusout='paused = false' @focusout='paused = false'
> >
<div <div
class="wire-marquee-track flex w-max min-w-full items-center" class="wire-marquee__track"
class:wire-marquee-paused='paused' class:wire-marquee-paused='paused'
> >
{#each items as item} {#each items as item}
<a <a
href='{item.href || "#"}' href='{item.href || "#"}'
class="flex shrink-0 items-center gap-3 px-5 py-3 text-sm font-medium text-[var(--wire-color-text)] transition hover:bg-[var(--wire-color-primary-soft)] hover:text-[var(--wire-color-primary)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--wire-color-focus)]" class="wire-marquee__item"
> >
<span <span
class="size-2 shrink-0 rounded-full bg-[var(--wire-color-primary)]" class="wire-marquee__dot"
class:bg-[var(--wire-color-danger)]='item.color === "danger"' data-color='{item.color || "primary"}'
class:bg-[var(--wire-color-warning)]='item.color === "warning"'
class:bg-[var(--wire-color-success)]='item.color === "success"'
aria-hidden="true" aria-hidden="true"
></span> ></span>
<span>{item.label || item.title}</span> <span>{item.label || item.title}</span>
{#if item.meta} {#if item.meta}
<span class="text-xs text-[var(--wire-color-text-muted)]">{item.meta}</span> <span class="wire-marquee__meta">{item.meta}</span>
{/if} {/if}
</a> </a>
{:empty} {:empty}
<p class="px-5 py-3 text-sm text-[var(--wire-color-text-muted)]">{description || "No announcements available."}</p> <p class="wire-marquee__empty">{description || "No announcements available."}</p>
{/each} {/each}
{#if items.length > 0} {#if items.length > 0}
@@ -69,9 +67,9 @@ component Marquee {
href='{item.href || "#"}' href='{item.href || "#"}'
aria-hidden="true" aria-hidden="true"
tabindex="-1" tabindex="-1"
class="flex shrink-0 items-center gap-3 px-5 py-3 text-sm font-medium text-[var(--wire-color-text)]" class="wire-marquee__item"
> >
<span class="size-2 shrink-0 rounded-full bg-[var(--wire-color-primary)]" aria-hidden="true"></span> <span class="wire-marquee__dot" aria-hidden="true"></span>
<span>{item.label || item.title}</span> <span>{item.label || item.title}</span>
</a> </a>
{/each} {/each}
@@ -82,11 +80,11 @@ component Marquee {
<button <button
type="button" type="button"
aria-label='{paused ? "Resume announcements" : "Pause announcements"}' aria-label='{paused ? "Resume announcements" : "Pause announcements"}'
class="flex shrink-0 items-center justify-center border-l border-[var(--wire-color-border)] px-4 text-[var(--wire-color-text-muted)] transition hover:bg-[var(--wire-color-surface-soft)] hover:text-[var(--wire-color-text)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--wire-color-focus)]" class="wire-marquee__toggle"
@click='paused = !paused; paused ? output.pause({}) : output.resume({})' @click='paused = !paused; paused ? output.pause({}) : output.resume({})'
> >
<span class="icon-[lucide--pause] size-4" data-show='!paused' aria-hidden="true"></span> <span class="icon-[lucide--pause] wire-marquee__icon" data-show='!paused' aria-hidden="true"></span>
<span class="icon-[lucide--play] size-4" data-show='paused' aria-hidden="true"></span> <span class="icon-[lucide--play] wire-marquee__icon" data-show='paused' aria-hidden="true"></span>
</button> </button>
</div> </div>
@@ -95,9 +93,28 @@ component Marquee {
} }
style { style {
.wire-marquee-track { .wire-marquee { overflow: hidden; border-block: 1px solid var(--wire-color-border); background: var(--wire-color-surface-raised); }
.wire-marquee__layout { display: flex; align-items: stretch; }
.wire-marquee__title { position: relative; z-index: 1; display: flex; flex: none; align-items: center; gap: 0.5rem; padding: 0.75rem 1rem; border-right: 1px solid var(--wire-color-border); color: var(--wire-color-on-primary); background: var(--wire-color-primary); font-size: 0.875rem; font-weight: 700; }
.wire-marquee__window { position: relative; min-width: 0; flex: 1; overflow: hidden; }
.wire-marquee__track {
display: flex; width: max-content; min-width: 100%; align-items: center;
animation: wire-marquee 32s linear infinite; animation: wire-marquee 32s linear infinite;
} }
.wire-marquee-paused { animation-play-state: paused; }
.wire-marquee__item { display: flex; flex: none; align-items: center; gap: 0.75rem; padding: 0.75rem 1.25rem; color: var(--wire-color-text); font-size: 0.875rem; font-weight: 500; transition: color var(--wire-motion-base), background var(--wire-motion-base); }
.wire-marquee__item:hover { color: var(--wire-color-primary); background: var(--wire-color-primary-soft); }
.wire-marquee__item:focus-visible, .wire-marquee__toggle:focus-visible { outline: 2px solid var(--wire-color-focus); outline-offset: -2px; }
.wire-marquee__dot { width: 0.5rem; height: 0.5rem; flex: none; border-radius: 50%; background: var(--wire-color-primary); }
.wire-marquee__dot[data-color="danger"] { background: var(--wire-color-danger); }
.wire-marquee__dot[data-color="warning"] { background: var(--wire-color-warning); }
.wire-marquee__dot[data-color="success"] { background: var(--wire-color-success); }
.wire-marquee__meta { color: var(--wire-color-text-muted); font-size: 0.75rem; }
.wire-marquee__empty { padding: 0.75rem 1.25rem; color: var(--wire-color-text-muted); font-size: 0.875rem; }
.wire-marquee__toggle { display: flex; flex: none; align-items: center; justify-content: center; padding-inline: 1rem; border: 0; border-left: 1px solid var(--wire-color-border); color: var(--wire-color-text-muted); background: transparent; transition: color var(--wire-motion-base), background var(--wire-motion-base); }
.wire-marquee__toggle:hover { color: var(--wire-color-text); background: var(--wire-color-surface-soft); }
.wire-marquee__icon { width: 1rem; height: 1rem; }
@media (min-width: 40rem) { .wire-marquee__title { padding-inline: 1.25rem; } }
.wire-marquee-paused { .wire-marquee-paused {
animation-play-state: paused; animation-play-state: paused;
@@ -109,7 +126,7 @@ component Marquee {
} }
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
.wire-marquee-track { .wire-marquee__track {
animation: none; animation: none;
} }
} }
+78 -13
View File
@@ -26,19 +26,19 @@ component SearchBox {
<form <form
data-ui-component="SearchBox" data-ui-component="SearchBox"
role="search" role="search"
class='w-full {class}' class='wire-search-box {class}'
@submit='event.preventDefault(); output.search({ value: query, name: name })' @submit='event.preventDefault(); output.search({ value: query, name: name })'
> >
<label <label
class="mb-2 block text-sm font-semibold text-[var(--wire-color-text)]" class="wire-search-box__label"
class:sr-only='label === ""' data-hidden='{label === "" ? "true" : "false"}'
> >
{label} {label}
</label> </label>
<div class="relative flex items-center"> <div class="wire-search-box__control">
<span <span
class="icon-[lucide--search] pointer-events-none absolute left-4 size-5 text-[var(--wire-color-input-placeholder)]" class="icon-[lucide--search] wire-search-box__search-icon"
aria-hidden="true" aria-hidden="true"
></span> ></span>
@@ -53,10 +53,8 @@ component SearchBox {
disabled='{disabled}' disabled='{disabled}'
required='{required}' required='{required}'
autocomplete="off" autocomplete="off"
class="w-full rounded-xl border border-[var(--wire-color-input-border)] bg-[var(--wire-color-input-background)] pl-12 pr-24 text-[var(--wire-color-input-text)] outline-none transition placeholder:text-[var(--wire-color-input-placeholder)] hover:border-[var(--wire-color-input-border-hover)] focus:border-[var(--wire-color-input-border-focus)] focus:ring-2 focus:ring-[var(--wire-color-focus)] disabled:cursor-not-allowed disabled:opacity-60" class="wire-search-box__input"
class:h-10='size === "sm"' data-size='{size}'
class:h-12='size === "default" || size === "md"'
class:h-14='size === "lg"'
@input='query = event.target.value' @input='query = event.target.value'
@change='query = event.target.value' @change='query = event.target.value'
/> />
@@ -65,21 +63,88 @@ component SearchBox {
type="button" type="button"
aria-label="Clear search" aria-label="Clear search"
data-show='query.length > 0 && !disabled' data-show='query.length > 0 && !disabled'
class="absolute right-12 inline-flex size-8 items-center justify-center rounded-lg text-[var(--wire-color-text-muted)] transition hover:bg-[var(--wire-color-surface-soft)] hover:text-[var(--wire-color-text)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--wire-color-focus)]" class="wire-search-box__button wire-search-box__clear"
@click='query = ""; event.currentTarget.parentElement.querySelector("input")?.focus(); output.clear({ value: "", name: name })' @click='query = ""; event.currentTarget.parentElement.querySelector("input")?.focus(); output.clear({ value: "", name: name })'
> >
<span class="icon-[lucide--x] size-4" aria-hidden="true"></span> <span class="icon-[lucide--x] wire-search-box__button-icon" aria-hidden="true"></span>
</button> </button>
<button <button
type="submit" type="submit"
aria-label='{label || "Search"}' aria-label='{label || "Search"}'
disabled='{disabled}' disabled='{disabled}'
class="absolute right-2 inline-flex size-9 items-center justify-center rounded-lg bg-[var(--wire-color-primary)] text-[var(--wire-color-on-primary)] transition hover:bg-[var(--wire-color-primary-hover)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--wire-color-focus)] disabled:cursor-not-allowed disabled:opacity-60" class="wire-search-box__button wire-search-box__submit"
> >
<span class="icon-[lucide--arrow-right] size-4" aria-hidden="true"></span> <span class="icon-[lucide--arrow-right] wire-search-box__button-icon" aria-hidden="true"></span>
</button> </button>
</div> </div>
</form> </form>
} }
style {
.wire-search-box { width: 100%; }
.wire-search-box__label {
display: block;
margin-bottom: 0.5rem;
color: var(--wire-color-text);
font-size: 0.875rem;
font-weight: 600;
}
.wire-search-box__label[data-hidden="true"] {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.wire-search-box__control { position: relative; display: flex; align-items: center; }
.wire-search-box__search-icon {
position: absolute;
left: 1rem;
width: 1.25rem;
height: 1.25rem;
color: var(--wire-color-input-placeholder);
pointer-events: none;
}
.wire-search-box__input {
width: 100%;
height: 3rem;
padding: 0 6rem 0 3rem;
border: 1px solid var(--wire-color-input-border);
border-radius: var(--wire-radius);
outline: none;
color: var(--wire-color-input-text);
background: var(--wire-color-input-background);
transition: border-color var(--wire-motion-base) var(--wire-ease-standard), box-shadow var(--wire-motion-base) var(--wire-ease-standard);
}
.wire-search-box__input[data-size="sm"] { height: 2.5rem; }
.wire-search-box__input[data-size="lg"] { height: 3.5rem; }
.wire-search-box__input::placeholder { color: var(--wire-color-input-placeholder); }
.wire-search-box__input:hover { border-color: var(--wire-color-input-border-hover); }
.wire-search-box__input:focus {
border-color: var(--wire-color-input-border-focus);
box-shadow: 0 0 0 2px var(--wire-color-focus);
}
.wire-search-box__input:disabled, .wire-search-box__button:disabled { cursor: not-allowed; opacity: 0.6; }
.wire-search-box__button {
position: absolute;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0;
border: 0;
border-radius: var(--wire-radius-sm);
transition: color var(--wire-motion-base), background var(--wire-motion-base);
}
.wire-search-box__clear { right: 3rem; width: 2rem; height: 2rem; color: var(--wire-color-text-muted); background: transparent; }
.wire-search-box__clear:hover { color: var(--wire-color-text); background: var(--wire-color-surface-soft); }
.wire-search-box__submit { right: 0.5rem; width: 2.25rem; height: 2.25rem; color: var(--wire-color-on-primary); background: var(--wire-color-primary); }
.wire-search-box__submit:hover { background: var(--wire-color-primary-hover); }
.wire-search-box__button:focus-visible { outline: 2px solid var(--wire-color-focus); outline-offset: 2px; }
.wire-search-box__button-icon { width: 1rem; height: 1rem; }
}
} }
+66 -28
View File
@@ -29,18 +29,16 @@ label: string = "Learn more"
data-size='{size}' data-size='{size}'
data-color='{color}' data-color='{color}'
data-variant='{variant}' data-variant='{variant}'
class='inline-flex min-w-0 {class}' class='wire-text-link {class}'
> >
{#if disabled} {#if disabled}
<span <span
aria-disabled="true" aria-disabled="true"
class="inline-flex cursor-not-allowed items-center gap-2 font-semibold opacity-50" class="wire-text-link__control"
class:text-sm='size === "sm" || size === "default"' data-size='{size}'
class:text-base='size === "md"'
class:text-lg='size === "lg"'
> >
{#if icon && iconPosition === "start"} {#if icon && iconPosition === "start"}
<span class='{icon + " size-4 shrink-0"}' aria-hidden="true"></span> <span class='{icon + " wire-text-link__icon"}' aria-hidden="true"></span>
{/if} {/if}
<span>{label}</span> <span>{label}</span>
</span> </span>
@@ -49,42 +47,82 @@ label: string = "Learn more"
href='{href}' href='{href}'
target='{target}' target='{target}'
rel='{external ? (rel || "noopener noreferrer") : rel}' rel='{external ? (rel || "noopener noreferrer") : rel}'
class="group inline-flex items-center gap-2 rounded-md font-semibold outline-none transition focus-visible:ring-2 focus-visible:ring-[var(--wire-color-focus)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--wire-color-background)]" class="wire-text-link__control"
class:text-sm='size === "sm" || size === "default"' data-size='{size}'
class:text-base='size === "md"' data-color='{color}'
class:text-lg='size === "lg"' data-variant='{variant}'
class:text-[var(--wire-color-primary)]='color === "primary"' data-underline='{underline ? "true" : "false"}'
class:hover:text-[var(--wire-color-primary-hover)]='color === "primary"'
class:text-[var(--wire-color-secondary)]='color === "secondary"'
class:text-[var(--wire-color-success)]='color === "success"'
class:text-[var(--wire-color-warning-text)]='color === "warning"'
class:text-[var(--wire-color-danger)]='color === "danger"'
class:text-[var(--wire-color-info)]='color === "info"'
class:text-[var(--wire-color-text)]='color === "neutral"'
class:underline='underline'
class:underline-offset-4='underline'
class:rounded-lg='variant === "button"'
class:bg-[var(--wire-color-primary-soft)]='variant === "button" && color === "primary"'
class:px-3='variant === "button"'
class:py-2='variant === "button"'
> >
{#if icon && iconPosition === "start"} {#if icon && iconPosition === "start"}
<span class='{icon + " size-4 shrink-0"}' aria-hidden="true"></span> <span class='{icon + " wire-text-link__icon"}' aria-hidden="true"></span>
{/if} {/if}
<span>{label}</span> <span>{label}</span>
{#if icon && iconPosition === "end"} {#if icon && iconPosition === "end"}
<span class='{icon + " size-4 shrink-0"}' aria-hidden="true"></span> <span class='{icon + " wire-text-link__icon"}' aria-hidden="true"></span>
{/if} {/if}
{#if external} {#if external}
<span class="icon-[lucide--external-link] size-4 shrink-0" aria-hidden="true"></span> <span class="icon-[lucide--external-link] wire-text-link__icon" aria-hidden="true"></span>
{:else if showArrow} {:else if showArrow}
<span class="icon-[lucide--arrow-right] size-4 shrink-0 transition-transform group-hover:translate-x-1" aria-hidden="true"></span> <span class="icon-[lucide--arrow-right] wire-text-link__icon wire-text-link__arrow" aria-hidden="true"></span>
{/if} {/if}
</a> </a>
{/if} {/if}
</span> </span>
} }
style {
.wire-text-link {
display: inline-flex;
min-width: 0;
}
.wire-text-link__control {
display: inline-flex;
align-items: center;
gap: 0.5rem;
border-radius: var(--wire-radius-sm);
color: var(--wire-color-primary);
font-weight: 600;
outline: none;
transition: color var(--wire-motion-base) var(--wire-ease-standard);
}
span.wire-text-link__control {
cursor: not-allowed;
opacity: 0.5;
}
.wire-text-link__control[data-size="sm"],
.wire-text-link__control[data-size="default"] { font-size: 0.875rem; }
.wire-text-link__control[data-size="md"] { font-size: 1rem; }
.wire-text-link__control[data-size="lg"] { font-size: 1.125rem; }
.wire-text-link__control[data-color="secondary"] { color: var(--wire-color-secondary); }
.wire-text-link__control[data-color="success"] { color: var(--wire-color-success); }
.wire-text-link__control[data-color="warning"] { color: var(--wire-color-warning-text); }
.wire-text-link__control[data-color="danger"] { color: var(--wire-color-danger); }
.wire-text-link__control[data-color="info"] { color: var(--wire-color-info); }
.wire-text-link__control[data-color="neutral"] { color: var(--wire-color-text); }
.wire-text-link__control[data-color="primary"]:hover { color: var(--wire-color-primary-hover); }
.wire-text-link__control[data-underline="true"] {
text-decoration: underline;
text-underline-offset: 0.25rem;
}
.wire-text-link__control[data-variant="button"] {
padding: 0.5rem 0.75rem;
border-radius: var(--wire-radius);
}
.wire-text-link__control[data-variant="button"][data-color="primary"] {
background: var(--wire-color-primary-soft);
}
.wire-text-link__control:focus-visible {
outline: 2px solid var(--wire-color-focus);
outline-offset: 2px;
}
.wire-text-link__icon { width: 1rem; height: 1rem; flex: none; }
.wire-text-link__arrow { transition: transform var(--wire-motion-base) var(--wire-ease-standard); }
.wire-text-link__control:hover .wire-text-link__arrow { transform: translateX(0.25rem); }
}
} }
+50 -23
View File
@@ -17,71 +17,98 @@ component Timeline {
<section <section
data-ui-component="Timeline" data-ui-component="Timeline"
aria-label='{title}' aria-label='{title}'
class='w-full {class}' class='wire-timeline {class}'
> >
{#if title || description} {#if title || description}
<header class="mb-6"> <header class="wire-timeline__header">
{#if title} {#if title}
<h3 class="text-xl font-bold text-[var(--wire-color-text)]">{title}</h3> <h3 class="wire-timeline__title">{title}</h3>
{/if} {/if}
{#if description} {#if description}
<p class="mt-2 max-w-2xl leading-7 text-[var(--wire-color-text-muted)]">{description}</p> <p class="wire-timeline__description">{description}</p>
{/if} {/if}
</header> </header>
{/if} {/if}
<ol class="relative ml-4 border-l border-[var(--wire-color-border)]"> <ol class="wire-timeline__list">
{#each items as item, index} {#each items as item, index}
<li <li
class="relative pb-8 pl-8 last:pb-0" class="wire-timeline__item"
@click='output.select({ item: item, index: index })' @click='output.select({ item: item, index: index })'
> >
<span <span
class="absolute -left-[1.05rem] top-0 flex size-8 items-center justify-center rounded-full border-4 border-[var(--wire-color-background)] bg-[var(--wire-color-primary)] text-[var(--wire-color-on-primary)] shadow-sm" class="wire-timeline__marker"
class:bg-[var(--wire-color-success)]='item.status === "complete" || item.status === "success"' data-status='{item.status || "default"}'
class:bg-[var(--wire-color-warning)]='item.status === "warning" || item.status === "pending"'
class:bg-[var(--wire-color-danger)]='item.status === "danger" || item.status === "failed"'
> >
<span class='{item.icon || "icon-[lucide--circle]"}' aria-hidden="true"></span> <span class='{item.icon || "icon-[lucide--circle]"}' aria-hidden="true"></span>
</span> </span>
<article <article
class="rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-raised)] p-5 shadow-sm" class="wire-timeline__card"
class:border-transparent='variant === "minimal"' data-variant='{variant}'
class:bg-transparent='variant === "minimal"'
class:p-0='variant === "minimal"'
class:shadow-none='variant === "minimal"'
> >
<div class="flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between"> <div class="wire-timeline__card-header">
<div> <div>
<h4 class="font-bold text-[var(--wire-color-text)]">{item.title || item.label}</h4> <h4 class="wire-timeline__item-title">{item.title || item.label}</h4>
{#if item.subtitle} {#if item.subtitle}
<p class="mt-1 text-sm font-medium text-[var(--wire-color-primary)]">{item.subtitle}</p> <p class="wire-timeline__subtitle">{item.subtitle}</p>
{/if} {/if}
</div> </div>
{#if item.date || item.meta} {#if item.date || item.meta}
<time class="shrink-0 text-sm text-[var(--wire-color-text-muted)]">{item.date || item.meta}</time> <time class="wire-timeline__time">{item.date || item.meta}</time>
{/if} {/if}
</div> </div>
{#if item.description} {#if item.description}
<p class="mt-3 text-sm leading-6 text-[var(--wire-color-text-muted)]">{item.description}</p> <p class="wire-timeline__copy">{item.description}</p>
{/if} {/if}
{#if item.href} {#if item.href}
<a href='{item.href}' class="mt-4 inline-flex items-center gap-2 text-sm font-semibold text-[var(--wire-color-primary)] hover:text-[var(--wire-color-primary-hover)]"> <a href='{item.href}' class="wire-timeline__action">
<span>{item.actionLabel || "View details"}</span> <span>{item.actionLabel || "View details"}</span>
<span class="icon-[lucide--arrow-right] size-4" aria-hidden="true"></span> <span class="icon-[lucide--arrow-right] wire-timeline__action-icon" aria-hidden="true"></span>
</a> </a>
{/if} {/if}
</article> </article>
</li> </li>
{:empty} {:empty}
<li class="pl-8 text-sm text-[var(--wire-color-text-muted)]">No timeline items available.</li> <li class="wire-timeline__empty">No timeline items available.</li>
{/each} {/each}
</ol> </ol>
<slot></slot> <slot></slot>
</section> </section>
} }
style {
.wire-timeline { width: 100%; }
.wire-timeline__header { margin-bottom: 1.5rem; }
.wire-timeline__title { color: var(--wire-color-text); font-size: 1.25rem; font-weight: 700; }
.wire-timeline__description { max-width: 42rem; margin-top: 0.5rem; color: var(--wire-color-text-muted); line-height: 1.75rem; }
.wire-timeline__list { position: relative; margin-left: 1rem; border-left: 1px solid var(--wire-color-border); }
.wire-timeline__item { position: relative; padding: 0 0 2rem 2rem; cursor: pointer; }
.wire-timeline__item:last-child { padding-bottom: 0; }
.wire-timeline__marker {
position: absolute; top: 0; left: -1.05rem; display: flex; width: 2rem; height: 2rem;
align-items: center; justify-content: center; border: 4px solid var(--wire-color-background);
border-radius: 50%; color: var(--wire-color-on-primary); background: var(--wire-color-primary); box-shadow: var(--wire-shadow-1);
}
.wire-timeline__marker:is([data-status="complete"], [data-status="success"]) { background: var(--wire-color-success); }
.wire-timeline__marker:is([data-status="warning"], [data-status="pending"]) { background: var(--wire-color-warning); }
.wire-timeline__marker:is([data-status="danger"], [data-status="failed"]) { background: var(--wire-color-danger); }
.wire-timeline__card { padding: 1.25rem; border: 1px solid var(--wire-color-border); border-radius: calc(var(--wire-radius) * 1.5); background: var(--wire-color-surface-raised); box-shadow: var(--wire-shadow-1); }
.wire-timeline__card[data-variant="minimal"] { padding: 0; border-color: transparent; background: transparent; box-shadow: none; }
.wire-timeline__card-header { display: flex; flex-direction: column; gap: 0.5rem; }
.wire-timeline__item-title { color: var(--wire-color-text); font-weight: 700; }
.wire-timeline__subtitle { margin-top: 0.25rem; color: var(--wire-color-primary); font-size: 0.875rem; font-weight: 500; }
.wire-timeline__time { flex: none; color: var(--wire-color-text-muted); font-size: 0.875rem; }
.wire-timeline__copy { margin-top: 0.75rem; color: var(--wire-color-text-muted); font-size: 0.875rem; line-height: 1.5rem; }
.wire-timeline__action { display: inline-flex; align-items: center; gap: 0.5rem; margin-top: 1rem; color: var(--wire-color-primary); font-size: 0.875rem; font-weight: 600; }
.wire-timeline__action:hover { color: var(--wire-color-primary-hover); }
.wire-timeline__action-icon { width: 1rem; height: 1rem; }
.wire-timeline__empty { padding-left: 2rem; color: var(--wire-color-text-muted); font-size: 0.875rem; }
@media (min-width: 40rem) {
.wire-timeline__card-header { flex-direction: row; align-items: flex-start; justify-content: space-between; }
}
}
} }
+38
View File
@@ -3080,6 +3080,25 @@ test("layout components ship their own styles instead of Tailwind utilities", ()
} }
}); });
test("remaining utility-styled components use local BEM styles", () => {
const migrated = ["List", "InputNumber", "Marquee", "TextLink", "Map", "SearchBox", "Timeline"];
const utilityMarkup =
/class(?::[^=\s]+)?=['"][^'"]*\b(?:flex|grid|w-full|items-center|justify-between|gap-[0-9]|p[xytrbl]?-[0-9]|m[xytrbl]?-[0-9]|text-(?:xs|sm|lg|xl)|rounded-(?:md|lg|xl|2xl|full)|border-[lrtbxy]?|bg-\[|shadow-(?:sm|lg)|size-[0-9])\b/;
for (const name of migrated) {
const source = readFileSync(uiComponentPath(name), "utf8");
const markup = source.split(/^\s{2,4}style \{/m)[0] ?? source;
expect({ name, hasStyleBlock: /^\s{2,4}style \{/m.test(source) }).toEqual({
name,
hasStyleBlock: true,
});
expect({ name, utilityMarkup: utilityMarkup.test(markup) }).toEqual({
name,
utilityMarkup: false,
});
}
});
test("every wire color token a component references is defined by the theme", () => { test("every wire color token a component references is defined by the theme", () => {
/* /*
* Ten tokens were referenced by components and defined by nothing: * Ten tokens were referenced by components and defined by nothing:
@@ -3179,6 +3198,25 @@ test("no component gains an output that nothing ever emits", () => {
expect(offenders).not.toContain("CustomScrollbar.scroll"); expect(offenders).not.toContain("CustomScrollbar.scroll");
}); });
test("list selects items without hrefs as well as navigation items", async () => {
const source = readFileSync(uiComponentPath("List"), "utf8");
const dom = mountHtml(
await renderComponent(source, {
title: "Actions",
items: [{ label: "Run report" }],
}),
);
const root = dom.querySelector('[data-ui-component="List"]') as HTMLElement;
const events: Array<{ item: { label: string }; index: number }> = [];
root.addEventListener("select", (event) => {
events.push((event as CustomEvent).detail);
});
(root.querySelector("li") as HTMLElement).click();
expect(events).toEqual([{ item: { label: "Run report" }, index: 0 }]);
});
test("a declared output is never emitted as a hand-built CustomEvent", () => { test("a declared output is never emitted as a hand-built CustomEvent", () => {
/* /*
* The failure this prevents is silent in both directions: the component * The failure this prevents is silent in both directions: the component