From aaee20e2dd44886e7f6ad5910020339bce3e06e0 Mon Sep 17 00:00:00 2001 From: KaiUweCZE Date: Wed, 13 Aug 2025 06:55:54 +0200 Subject: [PATCH 01/44] use new name for spacing in components token --- libs/ui/src/molecules/select.tsx | 2 +- .../ui/src/tokens/components/atoms/_badge.css | 4 ++-- .../src/tokens/components/atoms/_button.css | 12 +++++------ .../ui/src/tokens/components/atoms/_input.css | 10 +++++----- .../src/tokens/components/atoms/_rating.css | 6 +++--- libs/ui/src/tokens/components/atoms/_tabs.css | 12 +++++------ .../src/tokens/components/atoms/_textarea.css | 6 +++--- .../src/tokens/components/atoms/_tooltip.css | 8 ++++---- .../components/molecules/_breadcrumb.css | 8 ++++---- .../tokens/components/molecules/_carousel.css | 8 ++++---- .../tokens/components/molecules/_checkbox.css | 2 +- .../tokens/components/molecules/_combobox.css | 10 +++++----- .../tokens/components/molecules/_dialog.css | 6 +++--- .../src/tokens/components/molecules/_menu.css | 14 ++++++------- .../components/molecules/_numeric-input.css | 12 +++++------ .../components/molecules/_pagination.css | 2 +- .../tokens/components/molecules/_popover.css | 12 +++++------ .../components/molecules/_product-card.css | 10 +++++----- .../components/molecules/_range-slider.css | 20 +++++++++---------- .../components/molecules/_search-form.css | 6 +++--- .../tokens/components/molecules/_select.css | 12 +++++------ .../tokens/components/molecules/_steps.css | 14 ++++++------- .../tokens/components/molecules/_switch.css | 8 ++++---- .../tokens/components/molecules/_toast.css | 6 +++--- .../components/molecules/_tree-view.css | 6 +++--- 25 files changed, 108 insertions(+), 108 deletions(-) diff --git a/libs/ui/src/molecules/select.tsx b/libs/ui/src/molecules/select.tsx index ceb65e9dae..00107bd4f6 100644 --- a/libs/ui/src/molecules/select.tsx +++ b/libs/ui/src/molecules/select.tsx @@ -45,7 +45,7 @@ const selectVariants = tv({ content: [ 'bg-select-content-bg border border-select-content-border', 'rounded-select shadow-select-content-shadow max-h-fit', - 'h-[calc(var(--available-height)-var(--spacing-lg))]', + 'h-[calc(var(--available-height)-var(--spacing-450))]', 'overflow-auto z-50', ], item: [ diff --git a/libs/ui/src/tokens/components/atoms/_badge.css b/libs/ui/src/tokens/components/atoms/_badge.css index 664f4c30b4..545457fc49 100644 --- a/libs/ui/src/tokens/components/atoms/_badge.css +++ b/libs/ui/src/tokens/components/atoms/_badge.css @@ -1,8 +1,8 @@ @import "../../_semantic.css"; @theme static { - --padding-badge-x: var(--spacing-3xs); - --padding-badge-y: var(--spacing-3xs); + --padding-badge-x: var(--spacing-100); + --padding-badge-y: var(--spacing-100); --text-badge-size: var(--text-xs); diff --git a/libs/ui/src/tokens/components/atoms/_button.css b/libs/ui/src/tokens/components/atoms/_button.css index 3e5287f39f..e23fc19286 100644 --- a/libs/ui/src/tokens/components/atoms/_button.css +++ b/libs/ui/src/tokens/components/atoms/_button.css @@ -200,14 +200,14 @@ /* === PADDING === */ - --padding-btn-sm: var(--spacing-2xs) var(--spacing-sm); - --padding-btn-md: var(--spacing-xs) var(--spacing-md); - --padding-btn-lg: var(--spacing-sm) var(--spacing-lg); + --padding-btn-sm: var(--spacing-150) var(--spacing-250); + --padding-btn-md: var(--spacing-200) var(--spacing-350); + --padding-btn-lg: var(--spacing-250) var(--spacing-450); /* Spacing between icon and text */ - --spacing-btn-sm: var(--spacing-2xs); - --spacing-btn-md: var(--spacing-xs); - --spacing-btn-lg: var(--spacing-sm); + --spacing-btn-sm: var(--spacing-150); + --spacing-btn-md: var(--spacing-200); + --spacing-btn-lg: var(--spacing-250); /* === TYPOGRAPHY === */ /* Text sizes */ diff --git a/libs/ui/src/tokens/components/atoms/_input.css b/libs/ui/src/tokens/components/atoms/_input.css index 698bcbbe87..b2be131378 100644 --- a/libs/ui/src/tokens/components/atoms/_input.css +++ b/libs/ui/src/tokens/components/atoms/_input.css @@ -81,7 +81,7 @@ --color-label-text: var(--color-input-text); --color-label-disabled: var(--color-input-text-disabled); --color-label-required: var(--color-input-danger); - --margin-label: var(--spacing-3xs); + --margin-label: var(--spacing-100); /* Error/Helper/Extra colors */ --color-error-text: var(--color-danger); @@ -103,10 +103,10 @@ ); /* Spacing - keeping your existing values */ - --padding-input-sm: var(--spacing-sm) var(--spacing-sm); - --padding-input-md: var(--spacing-md) var(--spacing-sm); - --padding-input-lg: var(--spacing-lg) var(--spacing-md); - --padding-with-button: var(--spacing-3xl); + --padding-input-sm: var(--spacing-250) var(--spacing-250); + --padding-input-md: var(--spacing-350) var(--spacing-250); + --padding-input-lg: var(--spacing-450) var(--spacing-350); + --padding-with-button: var(--spacing-950); /* Typography */ --text-input-sm: var(--text-sm); diff --git a/libs/ui/src/tokens/components/atoms/_rating.css b/libs/ui/src/tokens/components/atoms/_rating.css index ebde6242ef..74fa3f037c 100644 --- a/libs/ui/src/tokens/components/atoms/_rating.css +++ b/libs/ui/src/tokens/components/atoms/_rating.css @@ -9,9 +9,9 @@ ); /* Spacing */ - --spacing-rating-sm: var(--spacing-3xs); - --spacing-rating-md: var(--spacing-2xs); - --spacing-rating-lg: var(--spacing-xs); + --spacing-rating-sm: var(--spacing-100); + --spacing-rating-md: var(--spacing-150); + --spacing-rating-lg: var(--spacing-200); /*Size*/ --text-rating-sm: var(--text-icon-sm); diff --git a/libs/ui/src/tokens/components/atoms/_tabs.css b/libs/ui/src/tokens/components/atoms/_tabs.css index 591d7b3cdc..a2a2af3c89 100644 --- a/libs/ui/src/tokens/components/atoms/_tabs.css +++ b/libs/ui/src/tokens/components/atoms/_tabs.css @@ -26,14 +26,14 @@ /* === PADDING === */ /* Trigger padding */ - --padding-tabs-trigger-sm: var(--spacing-3xs) var(--spacing-2xs); - --padding-tabs-trigger-md: var(--spacing-2xs) var(--spacing-xs); - --padding-tabs-trigger-lg: var(--spacing-xs) var(--spacing-sm); + --padding-tabs-trigger-sm: var(--spacing-100) var(--spacing-150); + --padding-tabs-trigger-md: var(--spacing-150) var(--spacing-200); + --padding-tabs-trigger-lg: var(--spacing-200) var(--spacing-250); /* Content padding */ - --spacing-tabs-content-sm: var(--spacing-xs); - --spacing-tabs-content-md: var(--spacing-sm); - --spacing-tabs-content-lg: var(--spacing-md); + --spacing-tabs-content-sm: var(--spacing-200); + --spacing-tabs-content-md: var(--spacing-250); + --spacing-tabs-content-lg: var(--spacing-350); --spacing-tabs-content: var(--spacing-tabs-content-md); /* === TYPOGRAPHY === */ diff --git a/libs/ui/src/tokens/components/atoms/_textarea.css b/libs/ui/src/tokens/components/atoms/_textarea.css index 1c3a814a8a..d4b6d22686 100644 --- a/libs/ui/src/tokens/components/atoms/_textarea.css +++ b/libs/ui/src/tokens/components/atoms/_textarea.css @@ -76,9 +76,9 @@ ); /* Spacing */ - --spacing-textarea-sm: var(--spacing-3xs); - --spacing-textarea-md: var(--spacing-2xs); - --spacing-textarea-lg: var(--spacing-xs); + --spacing-textarea-sm: var(--spacing-100); + --spacing-textarea-md: var(--spacing-150); + --spacing-textarea-lg: var(--spacing-200); /* Typography */ --text-textarea-sm: var(--text-sm); diff --git a/libs/ui/src/tokens/components/atoms/_tooltip.css b/libs/ui/src/tokens/components/atoms/_tooltip.css index 922fc1bd90..7fc298e563 100644 --- a/libs/ui/src/tokens/components/atoms/_tooltip.css +++ b/libs/ui/src/tokens/components/atoms/_tooltip.css @@ -3,9 +3,9 @@ --color-tooltip-bg: var(--color-overlay); /* === SIZING & SPACING === */ - --padding-tooltip-sm: var(--spacing-2xs) var(--spacing-xs); - --padding-tooltip-md: var(--spacing-xs) var(--spacing-sm); - --padding-tooltip-lg: var(--spacing-sm) var(--spacing-md); + --padding-tooltip-sm: var(--spacing-150) var(--spacing-200); + --padding-tooltip-md: var(--spacing-200) var(--spacing-250); + --padding-tooltip-lg: var(--spacing-250) var(--spacing-350); --text-tooltip-sm: var(--text-xs); --text-tooltip-md: var(--text-sm); @@ -15,6 +15,6 @@ --shadow-tooltip: var(--shadow-md); /* Required for Zag.js */ - --tooltip-arrow-size: var(--spacing-2xs); + --tooltip-arrow-size: var(--spacing-150); --tooltip-arrow-background: var(--color-tooltip-bg); } diff --git a/libs/ui/src/tokens/components/molecules/_breadcrumb.css b/libs/ui/src/tokens/components/molecules/_breadcrumb.css index 47fe9ed06c..a25f8412b4 100644 --- a/libs/ui/src/tokens/components/molecules/_breadcrumb.css +++ b/libs/ui/src/tokens/components/molecules/_breadcrumb.css @@ -17,12 +17,12 @@ --text-breadcrumb-lg: var(--text-md); /* Gap between items */ - --gap-breadcrumb-sm: var(--spacing-3xs); - --gap-breadcrumb-md: var(--spacing-2xs); - --gap-breadcrumb-lg: var(--spacing-xs); + --gap-breadcrumb-sm: var(--spacing-100); + --gap-breadcrumb-md: var(--spacing-150); + --gap-breadcrumb-lg: var(--spacing-200); /* Padding */ - --spacing-breadcrumb: var(--spacing-2xs); + --spacing-breadcrumb: var(--spacing-150); /* Focus ring */ --color-breadcrumb-link-ring: var(--color-primary); diff --git a/libs/ui/src/tokens/components/molecules/_carousel.css b/libs/ui/src/tokens/components/molecules/_carousel.css index 0bf94e5f66..6d070cf87d 100644 --- a/libs/ui/src/tokens/components/molecules/_carousel.css +++ b/libs/ui/src/tokens/components/molecules/_carousel.css @@ -11,14 +11,14 @@ /* === CONTROL COLORS === */ --color-carousel-control-bg: var(--color-overlay); - --padding-carousel-control: var(--spacing-3xs); - --gap-carousel-control: var(--spacing-xs); + --padding-carousel-control: var(--spacing-100); + --gap-carousel-control: var(--spacing-200); /* === INDICATOR === */ - --gap-carousel-indicator: var(--spacing-3xs); + --gap-carousel-indicator: var(--spacing-100); --color-carousel-indicator-active: var(--color-primary); --aspect-carousel-indicator: var(--aspect-ratio-square); - --width-carousel-indicator: var(--spacing-xs); + --width-carousel-indicator: var(--spacing-200); /* === FOCUS STATES === */ --color-carousel-focus-ring: --alpha( diff --git a/libs/ui/src/tokens/components/molecules/_checkbox.css b/libs/ui/src/tokens/components/molecules/_checkbox.css index f0fd473137..5f67a74e47 100644 --- a/libs/ui/src/tokens/components/molecules/_checkbox.css +++ b/libs/ui/src/tokens/components/molecules/_checkbox.css @@ -15,7 +15,7 @@ --color-checkbox-offset: var(--color-base); --color-checkbox-border-error: var(--color-checkbox-error); - --spacing-checkbox: var(--spacing-xs); + --spacing-checkbox: var(--spacing-200); --color-icon-text: var(--color-fg-dark); --text-checkbox-icon-md: var(--text-xs); --radius-checkbox: var(--radius-sm); diff --git a/libs/ui/src/tokens/components/molecules/_combobox.css b/libs/ui/src/tokens/components/molecules/_combobox.css index cb840b13b4..39c2ec3b5b 100644 --- a/libs/ui/src/tokens/components/molecules/_combobox.css +++ b/libs/ui/src/tokens/components/molecules/_combobox.css @@ -57,8 +57,8 @@ from var(--color-combobox-trigger) min(l - 0.4, 1) c h ); --text-combobox-trigger-size: var(--text-lg); - --spacing-combobox-trigger: var(--spacing-xs); - --spacing-combobox-trigger-right: var(--spacing-lg); + --spacing-combobox-trigger: var(--spacing-200); + --spacing-combobox-trigger-right: var(--spacing-450); --color-combobox-trigger-bg: transparent; --color-combobox-trigger-bg-hover: var(--color-combobox-trigger-bg); --color-combobox-trigger-bg-active: var(--color-combobox-trigger-bg); @@ -70,11 +70,11 @@ --color-combobox-item-fg: var(--color-fg-primary); --color-combobox-item-selected: var(--color-highlight); --color-combobox-item-hover: var(--color-combobox-item-selected); - --spacing-combobox-item: var(--spacing-3xs); - --spacing-combobox-item-lg: var(--spacing-2xs); + --spacing-combobox-item: var(--spacing-100); + --spacing-combobox-item-lg: var(--spacing-150); /* Spacing */ - --spacing-combobox-root: var(--spacing-3xs); + --spacing-combobox-root: var(--spacing-100); } /* Semantic tokens for combobox icons */ diff --git a/libs/ui/src/tokens/components/molecules/_dialog.css b/libs/ui/src/tokens/components/molecules/_dialog.css index df86e1d2bd..49e5f0e85f 100644 --- a/libs/ui/src/tokens/components/molecules/_dialog.css +++ b/libs/ui/src/tokens/components/molecules/_dialog.css @@ -20,9 +20,9 @@ --radius-dialog-content: var(--radius-sm); --shadow-dialog-content: var(--shadow-xl); - --gap-dialog-content: var(--spacing-sm); + --gap-dialog-content: var(--spacing-250); --height-dialog-content: 80svh; - --padding-dialog-content: var(--spacing-lg); + --padding-dialog-content: var(--spacing-450); /* ===== DIALOG TITLE ===== */ --text-dialog-title-size: var(--text-xl); @@ -36,7 +36,7 @@ /* ===== DIALOG CLOSE TRIGGER (Button) ===== */ --color-dialog-close-trigger-fg: var(--color-dialog-fg); --radius-dialog-close-trigger: var(--radius-full); - --spacing-dialog-close-trigger-padding: var(--spacing-2xs); + --spacing-dialog-close-trigger-padding: var(--spacing-150); /* ===== FOCUS RING (General for focusable elements within dialog) ===== */ --color-dialog-ring: --alpha(var(--color-primary) / var(--opacity-ring)); diff --git a/libs/ui/src/tokens/components/molecules/_menu.css b/libs/ui/src/tokens/components/molecules/_menu.css index ba19ce06eb..c6f22d3069 100644 --- a/libs/ui/src/tokens/components/molecules/_menu.css +++ b/libs/ui/src/tokens/components/molecules/_menu.css @@ -5,16 +5,16 @@ --radius-menu: var(--radius-sm); --shadow-menu-content-shadow: var(--shadow-lg); - --padding-menu-content: var(--spacing-xs); + --padding-menu-content: var(--spacing-200); /* ===== MENU ITEM ===== */ --color-menu-item-fg: var(--color-fg-primary); --color-menu-item-hover: var(--color-surface-hover); --radius-menu-item: var(--radius-xs); - --padding-menu-item-x: var(--spacing-sm); - --padding-menu-item-y: var(--spacing-xs); - --gap-menu-item: var(--spacing-xs); + --padding-menu-item-x: var(--spacing-250); + --padding-menu-item-y: var(--spacing-200); + --gap-menu-item: var(--spacing-200); /* ===== MENU ITEM ICON ===== */ --text-menu-item-icon-size: var(--text-lg); @@ -23,12 +23,12 @@ /* ===== MENU SEPARATOR ===== */ --color-menu-separator-bg: var(--color-stroke-primary); --height-menu-separator: 1px; - --margin-menu-separator: var(--spacing-xs); + --margin-menu-separator: var(--spacing-200); /* ===== MENU SUBMENU INDICATOR ===== */ --color-menu-submenu-indicator-fg: var(--color-fg-secondary); - --spacing-menu-submenu-indicator: var(--spacing-sm); - --spacing-menu-submenu-offset: var(--spacing-xs); + --spacing-menu-submenu-indicator: var(--spacing-250); + --spacing-menu-submenu-offset: var(--spacing-200); /* ===== MENU DISABLED STATE ===== */ --opacity-menu-disabled: 50%; diff --git a/libs/ui/src/tokens/components/molecules/_numeric-input.css b/libs/ui/src/tokens/components/molecules/_numeric-input.css index ce47bd6971..b3945dc7be 100644 --- a/libs/ui/src/tokens/components/molecules/_numeric-input.css +++ b/libs/ui/src/tokens/components/molecules/_numeric-input.css @@ -9,9 +9,9 @@ --color-ni-border: var(--color-stroke-primary); --border-width-ni: var(--border-width-input); - --gap-ni-root-sm: var(--spacing-2xs); - --gap-ni-root-md: var(--spacing-xs); - --gap-ni-root-lg: var(--spacing-sm); + --gap-ni-root-sm: var(--spacing-150); + --gap-ni-root-md: var(--spacing-200); + --gap-ni-root-lg: var(--spacing-250); /* ==== TYPOGRAPHY ==== */ --text-ni-sm: var(--text-sm); @@ -22,12 +22,12 @@ --color-ni-input-bg: transparent; --color-ni-input-bg-hover: transparent; --color-ni-input-bg-active: transparent; - --padding-ni-input: 0 0 0 var(--spacing-2xs); + --padding-ni-input: 0 0 0 var(--spacing-150); /* === TRIGGER === */ --color-ni-trigger-container: var(--color-surface); - --padding-ni-trigger-x: var(--spacing-2xs); - --padding-ni-trigger-y: var(--spacing-3xs); + --padding-ni-trigger-x: var(--spacing-150); + --padding-ni-trigger-y: var(--spacing-100); /* === INCREMENT BUTTON === */ diff --git a/libs/ui/src/tokens/components/molecules/_pagination.css b/libs/ui/src/tokens/components/molecules/_pagination.css index d59180fe07..b732c9f5cc 100644 --- a/libs/ui/src/tokens/components/molecules/_pagination.css +++ b/libs/ui/src/tokens/components/molecules/_pagination.css @@ -35,7 +35,7 @@ --color-pagination-ring: --alpha(var(--color-primary) / var(--opacity-ring)); /* Spacing */ - --spacing-pagination: var(--spacing-xs); + --spacing-pagination: var(--spacing-200); --spacing-pagination-list: var(--spacing-pagination); /* height */ diff --git a/libs/ui/src/tokens/components/molecules/_popover.css b/libs/ui/src/tokens/components/molecules/_popover.css index 345ca1170e..de96122cd2 100644 --- a/libs/ui/src/tokens/components/molecules/_popover.css +++ b/libs/ui/src/tokens/components/molecules/_popover.css @@ -1,5 +1,5 @@ @theme static { - --arrow-size: var(--spacing-2xs); + --arrow-size: var(--spacing-150); /* Background & Text Colors */ --color-popover-bg: var(--color-overlay); @@ -9,19 +9,19 @@ --arrow-background: var(--color-popover-bg); /* Spacing */ - --spacing-popover-sm: var(--spacing-xs); - --spacing-popover-md: var(--spacing-sm); - --spacing-popover-lg: var(--spacing-md); + --spacing-popover-sm: var(--spacing-200); + --spacing-popover-md: var(--spacing-250); + --spacing-popover-lg: var(--spacing-350); /* Trigger */ - --padding-popover-trigger: var(--spacing-3xs) var(--spacing-2xs); + --padding-popover-trigger: var(--spacing-100) var(--spacing-150); /* Title */ --text-popover-title-sm: var(--text-md); --text-popover-title-md: var(--text-lg); --text-popover-title-lg: var(--text-xl); --font-weight-popover-title: var(--font-weight-semibold); - --spacing-popover-title-mb: var(--spacing-2xs); + --spacing-popover-title-mb: var(--spacing-150); /*description*/ --text-popover-description: var(--text-sm); diff --git a/libs/ui/src/tokens/components/molecules/_product-card.css b/libs/ui/src/tokens/components/molecules/_product-card.css index 50af989fc8..c54d5e3464 100644 --- a/libs/ui/src/tokens/components/molecules/_product-card.css +++ b/libs/ui/src/tokens/components/molecules/_product-card.css @@ -6,11 +6,11 @@ --radius-pc: var(--radius-md); /* Card spacing */ - --spacing-pc-row-layout: var(--spacing-sm); + --spacing-pc-row-layout: var(--spacing-250); --spacing-pc-max: 100%; /* temporary value */ - /* --spacing-pc-col-layout: var(--spacing-3xs);*/ - --spacing-pc-box: var(--spacing-2xs); - --spacing-pc-padding: var(--spacing-2xs); + /* --spacing-pc-col-layout: var(--spacing-100);*/ + --spacing-pc-box: var(--spacing-150); + --spacing-pc-padding: var(--spacing-150); --color-pc-name-fg: var(--color-fg-primary); --text-pc-name-size: var(--text-md); @@ -93,5 +93,5 @@ } @utility gap-pc-col-layout { - gap: var(--spacing-3xs); + gap: var(--spacing-100); } diff --git a/libs/ui/src/tokens/components/molecules/_range-slider.css b/libs/ui/src/tokens/components/molecules/_range-slider.css index c00344ffe8..5bbcdc6d8d 100644 --- a/libs/ui/src/tokens/components/molecules/_range-slider.css +++ b/libs/ui/src/tokens/components/molecules/_range-slider.css @@ -39,20 +39,20 @@ --color-slider-ring: --alpha(var(--color-primary) / var(--opacity-ring)); /* === SIZING === */ - --spacing-slider-thumb-sm: var(--spacing-xs); - --spacing-slider-thumb-md: var(--spacing-sm); - --spacing-slider-thumb-lg: var(--spacing-md); + --spacing-slider-thumb-sm: var(--spacing-200); + --spacing-slider-thumb-md: var(--spacing-250); + --spacing-slider-thumb-lg: var(--spacing-350); - --width-slider-track-sm: var(--spacing-3xs); - --width-slider-track-md: var(--spacing-2xs); - --width-slider-track-lg: var(--spacing-xs); + --width-slider-track-sm: var(--spacing-100); + --width-slider-track-md: var(--spacing-150); + --width-slider-track-lg: var(--spacing-200); - --height-slider-track-sm: var(--spacing-3xs); - --height-slider-track-md: var(--spacing-2xs); - --height-slider-track-lg: var(--spacing-xs); + --height-slider-track-sm: var(--spacing-100); + --height-slider-track-md: var(--spacing-150); + --height-slider-track-lg: var(--spacing-200); /* === SPACING === */ - --spacing-slider-root: var(--spacing-xs); + --spacing-slider-root: var(--spacing-200); /* === TYPOGRAPHY === */ --text-slider-value-size: var(--text-sm); diff --git a/libs/ui/src/tokens/components/molecules/_search-form.css b/libs/ui/src/tokens/components/molecules/_search-form.css index 59ef6dc41d..997fddd35b 100644 --- a/libs/ui/src/tokens/components/molecules/_search-form.css +++ b/libs/ui/src/tokens/components/molecules/_search-form.css @@ -1,8 +1,8 @@ @theme static { /* Spacing for stacked layout */ - --spacing-search-form-sm: var(--spacing-2xs); - --spacing-search-form-md: var(--spacing-xs); - --spacing-search-form-lg: var(--spacing-sm); + --spacing-search-form-sm: var(--spacing-150); + --spacing-search-form-md: var(--spacing-200); + --spacing-search-form-lg: var(--spacing-250); } @utility token-icon-search { diff --git a/libs/ui/src/tokens/components/molecules/_select.css b/libs/ui/src/tokens/components/molecules/_select.css index 931519600f..274463bce8 100644 --- a/libs/ui/src/tokens/components/molecules/_select.css +++ b/libs/ui/src/tokens/components/molecules/_select.css @@ -12,7 +12,7 @@ /* === ROOT === */ --color-select-root-bg: var(--color-select-bg); - --gap-select-root: var(--spacing-2xs); + --gap-select-root: var(--spacing-150); /* === LABEL === --color-label: var(--color-fg-primary);*/ @@ -22,13 +22,13 @@ --color-select-trigger-border: var(--color-select-border); --color-select-trigger-hover: var(--color-fill-hover); --color-select-trigger-focus: var(--color-primary); - --padding-select-trigger: var(--spacing-3xs); + --padding-select-trigger: var(--spacing-100); /* === CLEAR TRIGGER === */ --color-select-clear-trigger-fg: var(--color-select-fg); --color-select-clear-trigger-bg: transparent; - --padding-select-clear-trigger: var(--spacing-3xs); - --spacing-select-right: var(--spacing-lg); + --padding-select-clear-trigger: var(--spacing-100); + --spacing-select-right: var(--spacing-450); /* === DROPDOWN CONTENT === */ --color-select-content-bg: var(--color-surface); @@ -41,13 +41,13 @@ --color-select-item-hover: var(--color-fill-hover); --color-select-item-selected-bg: var(--color-overlay); --color-select-item-selected-fg: var(--color-primary); - --padding-select-item: var(--spacing-2xs) var(--spacing-sm); + --padding-select-item: var(--spacing-150) var(--spacing-250); /* === INDICATOR === */ --color-select-indicator: var(--color-primary); /* === PADDING === */ - --padding-select-trigger: var(--spacing-xs); + --padding-select-trigger: var(--spacing-200); /* === TYPOGRAPHY === */ --text-select-trigger: var(--text-md); diff --git a/libs/ui/src/tokens/components/molecules/_steps.css b/libs/ui/src/tokens/components/molecules/_steps.css index 02f575c2cd..ff3d96301d 100644 --- a/libs/ui/src/tokens/components/molecules/_steps.css +++ b/libs/ui/src/tokens/components/molecules/_steps.css @@ -10,29 +10,29 @@ /* === ROOT === */ --color-steps-root-bg: var(--color-steps); - --gap-root: var(--spacing-sm); + --gap-root: var(--spacing-250); /* === LIST === */ --color-steps-list-bg: var(--color-steps); - --gap-list: var(--spacing-xs); + --gap-list: var(--spacing-200); /* === ITEM === */ --color-steps-item-bg: transparent; - --gap-item: var(--spacing-2xs); + --gap-item: var(--spacing-150); /* === TRIGGERS === */ - --gap-trigger: var(--spacing-2xs); + --gap-trigger: var(--spacing-150); /* === CONTENT === */ --color-steps-content-bg: var(--color-steps-accent); - --padding-content: var(--spacing-sm); + --padding-content: var(--spacing-250); /* === INDICATOR === */ --color-steps-indicator-bg: var(--color-steps-accent); --color-steps-indicator-complete: var(--color-steps-active); --color-steps-indicator-current: var(--color-steps-active); - --height-steps-indicator: var(--spacing-md); + --height-steps-indicator: var(--spacing-350); /* === SEPARATOR === */ --color-steps-separator-bg: var(--color-steps-accent); @@ -41,7 +41,7 @@ --width-steps-separator: 2px; /* === BUTTON STEPS === */ - --padding-steps-btn: var(--spacing-3xs) var(--spacing-xs); + --padding-steps-btn: var(--spacing-100) var(--spacing-200); } /* Semantic tokens for steps icons */ diff --git a/libs/ui/src/tokens/components/molecules/_switch.css b/libs/ui/src/tokens/components/molecules/_switch.css index 5f506b7b6d..ee57c01667 100644 --- a/libs/ui/src/tokens/components/molecules/_switch.css +++ b/libs/ui/src/tokens/components/molecules/_switch.css @@ -44,10 +44,10 @@ ); /* === SPACING === */ - --spacing-root: var(--spacing-sm); - --spacing-control-padding: var(--spacing-3xs); - --spacing-track-width: var(--spacing-xl); - --spacing-track-height: var(--spacing-md); + --spacing-root: var(--spacing-250); + --spacing-control-padding: var(--spacing-100); + --spacing-track-width: var(--spacing-600); + --spacing-track-height: var(--spacing-350); /* Padding inside the control container */ --spacing-control-padding: 0.125rem; diff --git a/libs/ui/src/tokens/components/molecules/_toast.css b/libs/ui/src/tokens/components/molecules/_toast.css index e1529b9d52..0dc61380c8 100644 --- a/libs/ui/src/tokens/components/molecules/_toast.css +++ b/libs/ui/src/tokens/components/molecules/_toast.css @@ -52,9 +52,9 @@ /* === FOCUS RING === */ /* === SPACING === */ - --padding-toast-root: var(--spacing-sm); - --gap-toast-content: var(--spacing-xs); - --spacing-toast-description-gap: var(--spacing-3xs); + --padding-toast-root: var(--spacing-250); + --gap-toast-content: var(--spacing-200); + --spacing-toast-description-gap: var(--spacing-100); /* === TYPOGRAPHY === */ --font-weight-toast-title: var(--font-weight-medium); diff --git a/libs/ui/src/tokens/components/molecules/_tree-view.css b/libs/ui/src/tokens/components/molecules/_tree-view.css index 7373f9fc05..57e1a3da51 100644 --- a/libs/ui/src/tokens/components/molecules/_tree-view.css +++ b/libs/ui/src/tokens/components/molecules/_tree-view.css @@ -17,7 +17,7 @@ --color-tree-node-hover: var(--color-primary-hover); --color-tree-node-selected: var(--color-primary-active); --color-tree-node-focus: var(--color-ring-primary); - --padding-tree-node: var(--spacing-3xs); + --padding-tree-node: var(--spacing-100); /* Text colors */ --color-tree-fg-hover: var(--color-fg-reverse); @@ -30,8 +30,8 @@ --color-tree-icon-hover: var(--color-fg-primary); /* === SPACING & SIZING === */ - --tree-indent-per-level: var(--spacing-3xs); - --spacing-tree-icon: var(--spacing-2xs); + --tree-indent-per-level: var(--spacing-100); + --spacing-tree-icon: var(--spacing-150); /* Size variants */ --text-tree-size: var(--text-sm); From 67aafb3b081fa62dccba0cbd872ee9a8aceda95d Mon Sep 17 00:00:00 2001 From: KaiUweCZE Date: Wed, 13 Aug 2025 07:24:19 +0200 Subject: [PATCH 02/44] refactor(ui): rename text tokens to fg for consistency --- libs/ui/src/atoms/button.tsx | 42 +++++++------- libs/ui/src/atoms/error-text.tsx | 2 +- libs/ui/src/atoms/extra-text.tsx | 2 +- libs/ui/src/atoms/input.tsx | 4 +- libs/ui/src/atoms/label.tsx | 2 +- libs/ui/src/atoms/textarea.tsx | 6 +- libs/ui/src/molecules/checkbox.tsx | 4 +- libs/ui/src/tokens/_semantic.css | 2 +- libs/ui/src/tokens/_spacing.css | 3 +- .../src/tokens/components/atoms/_button.css | 56 +++++++++---------- .../ui/src/tokens/components/atoms/_input.css | 20 +++---- .../src/tokens/components/atoms/_textarea.css | 4 +- .../tokens/components/molecules/_checkbox.css | 2 +- 13 files changed, 74 insertions(+), 75 deletions(-) diff --git a/libs/ui/src/atoms/button.tsx b/libs/ui/src/atoms/button.tsx index 7251077796..1eb72968e7 100644 --- a/libs/ui/src/atoms/button.tsx +++ b/libs/ui/src/atoms/button.tsx @@ -11,7 +11,7 @@ export const buttonVariants = tv({ 'transition-all duration-200', 'focus:outline-none', 'focus-visible:ring-3 focus-visible:ring-offset-2', - 'disabled:text-btn-text-disabled disabled:pointer-events-none', + 'disabled:text-btn-fg-disabled disabled:pointer-events-none', ], variants: { variant: { @@ -49,7 +49,7 @@ export const buttonVariants = tv({ 'bg-btn-primary', 'hover:bg-btn-primary-hover', 'active:bg-btn-primary-active', - 'text-btn-text-primary', + 'text-btn-fg-primary', ], }, { @@ -59,7 +59,7 @@ export const buttonVariants = tv({ 'bg-btn-secondary', 'hover:bg-btn-secondary-hover', 'active:bg-btn-secondary-active', - 'text-btn-text-secondary', + 'text-btn-fg-secondary', ], }, { @@ -69,7 +69,7 @@ export const buttonVariants = tv({ 'bg-btn-tertiary', 'hover:bg-btn-tertiary-hover', 'active:bg-btn-tertiary-active', - 'text-btn-text-tertiary', + 'text-btn-fg-tertiary', ], }, { @@ -79,7 +79,7 @@ export const buttonVariants = tv({ 'bg-btn-warning', 'hover:bg-btn-warning-hover', 'active:bg-btn-warning-active', - 'text-btn-text-warning', + 'text-btn-fg-warning', ], }, { @@ -89,7 +89,7 @@ export const buttonVariants = tv({ 'bg-btn-danger', 'hover:bg-btn-danger-hover', 'active:bg-btn-danger-active', - 'text-btn-text-danger', + 'text-btn-fg-danger', ], }, { @@ -99,7 +99,7 @@ export const buttonVariants = tv({ 'bg-btn-primary-light', 'hover:bg-btn-primary-light-hover', 'active:bg-btn-primary-light-active', - 'text-btn-text-primary-light', + 'text-btn-fg-primary-light', ], }, { @@ -109,7 +109,7 @@ export const buttonVariants = tv({ 'bg-btn-secondary-light', 'hover:bg-btn-secondary-light-hover', 'active:bg-btn-secondary-light-active', - 'text-btn-text-secondary-light', + 'text-btn-fg-secondary-light', ], }, { @@ -119,7 +119,7 @@ export const buttonVariants = tv({ 'bg-btn-tertiary-light', 'hover:bg-btn-tertiary-light-hover', 'active:bg-btn-tertiary-light-active', - 'text-btn-text-tertiary-light', + 'text-btn-fg-tertiary-light', ], }, { @@ -129,7 +129,7 @@ export const buttonVariants = tv({ 'bg-btn-warning-light', 'hover:bg-btn-warning-light-hover', 'active:bg-btn-warning-light-active', - 'text-btn-text-warning-light', + 'text-btn-fg-warning-light', ], }, { @@ -139,7 +139,7 @@ export const buttonVariants = tv({ 'bg-btn-danger-light', 'hover:bg-btn-danger-light-hover', 'active:bg-btn-danger-light-active', - 'text-btn-text-danger-light', + 'text-btn-fg-danger-light', ], }, { @@ -149,7 +149,7 @@ export const buttonVariants = tv({ 'border-btn-border-primary', 'hover:bg-btn-outlined-primary-hover', 'active:bg-btn-outlined-primary-active', - 'text-btn-text-outlined-primary', + 'text-btn-fg-outlined-primary', ], }, { @@ -159,7 +159,7 @@ export const buttonVariants = tv({ 'border-btn-border-secondary', 'hover:bg-btn-outlined-secondary-hover', 'active:bg-btn-outlined-secondary-active', - 'text-btn-text-outlined-secondary', + 'text-btn-fg-outlined-secondary', ], }, { @@ -169,7 +169,7 @@ export const buttonVariants = tv({ 'border-btn-border-tertiary', 'hover:bg-btn-outlined-tertiary-hover', 'active:bg-btn-outlined-tertiary-active', - 'text-btn-text-outlined-tertiary', + 'text-btn-fg-outlined-tertiary', ], }, { @@ -179,7 +179,7 @@ export const buttonVariants = tv({ 'border-btn-border-warning', 'hover:bg-btn-outlined-warning-hover', 'active:bg-btn-outlined-warning-active', - 'text-btn-text-outlined-warning', + 'text-btn-fg-outlined-warning', ], }, { @@ -189,33 +189,33 @@ export const buttonVariants = tv({ 'border-btn-border-danger', 'hover:bg-btn-outlined-danger-hover', 'active:bg-btn-outlined-danger-active', - 'text-btn-text-outlined-danger', + 'text-btn-fg-outlined-danger', ], }, { variant: 'primary', theme: 'borderless', - className: ['text-btn-text-primary-borderless'], + className: ['text-btn-fg-primary-borderless'], }, { variant: 'secondary', theme: 'borderless', - className: ['text-btn-text-secondary-borderless'], + className: ['text-btn-fg-secondary-borderless'], }, { variant: 'tertiary', theme: 'borderless', - className: ['text-btn-text-tertiary-borderless'], + className: ['text-btn-fg-tertiary-borderless'], }, { variant: 'warning', theme: 'borderless', - className: ['text-btn-text-warning-borderless'], + className: ['text-btn-fg-warning-borderless'], }, { variant: 'danger', theme: 'borderless', - className: ['text-btn-text-danger-borderless'], + className: ['text-btn-fg-danger-borderless'], }, { theme: 'outlined', diff --git a/libs/ui/src/atoms/error-text.tsx b/libs/ui/src/atoms/error-text.tsx index c477efb769..52cad84693 100644 --- a/libs/ui/src/atoms/error-text.tsx +++ b/libs/ui/src/atoms/error-text.tsx @@ -4,7 +4,7 @@ import { tv } from '../utils' import { Icon } from './icon' const errorVariants = tv({ - base: ['text-error-text', 'flex items-center gap-1'], + base: ['text-error-fg', 'flex items-center gap-1'], variants: { size: { sm: 'text-error-sm', diff --git a/libs/ui/src/atoms/extra-text.tsx b/libs/ui/src/atoms/extra-text.tsx index b23cff47de..f861c1bfe7 100644 --- a/libs/ui/src/atoms/extra-text.tsx +++ b/libs/ui/src/atoms/extra-text.tsx @@ -3,7 +3,7 @@ import type { VariantProps } from 'tailwind-variants' import { tv } from '../utils' const extraTextVariants = tv({ - base: ['text-helper-text'], + base: ['text-helper-fg'], variants: { size: { sm: 'text-helper-sm', diff --git a/libs/ui/src/atoms/input.tsx b/libs/ui/src/atoms/input.tsx index 0292b3779f..2d4fe6a6f7 100644 --- a/libs/ui/src/atoms/input.tsx +++ b/libs/ui/src/atoms/input.tsx @@ -6,7 +6,7 @@ const inputVariants = tv({ base: [ 'block w-full', 'bg-input', - 'text-input-text', + 'text-input-fg', 'placeholder:text-input-placeholder', 'border border-input-border', 'rounded-input', @@ -58,7 +58,7 @@ const inputVariants = tv({ true: [ 'bg-input-disabled', 'border-input-border-disabled', - 'text-input-text-disabled', + 'text-input-fg-disabled', 'placeholder:text-input-placeholder-disabled', ], }, diff --git a/libs/ui/src/atoms/label.tsx b/libs/ui/src/atoms/label.tsx index 982b806861..5a4e3e957c 100644 --- a/libs/ui/src/atoms/label.tsx +++ b/libs/ui/src/atoms/label.tsx @@ -3,7 +3,7 @@ import type { VariantProps } from 'tailwind-variants' import { tv } from '../utils' const labelVariants = tv({ - base: ['block', 'text-label-text', 'font-label'], + base: ['block', 'text-label-fg', 'font-label'], variants: { size: { sm: 'text-label-sm', diff --git a/libs/ui/src/atoms/textarea.tsx b/libs/ui/src/atoms/textarea.tsx index 722433cc66..bdb9141962 100644 --- a/libs/ui/src/atoms/textarea.tsx +++ b/libs/ui/src/atoms/textarea.tsx @@ -6,7 +6,7 @@ const textareaVariants = tv({ base: [ 'block w-full', 'bg-textarea', - 'text-textarea-text', + 'text-textarea-fg', 'placeholder:text-textarea-placeholder', 'border-(length:--textarea-border-width) border-textarea-border', 'rounded-textarea', @@ -14,7 +14,7 @@ const textareaVariants = tv({ 'hover:bg-textarea-hover hover:border-textarea-border-hover', 'focus:outline-none focus:bg-textarea-focus focus:border-textarea-border-focus', 'focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-textarea-ring', - 'disabled:pointer-events-none disabled:bg-textarea-disabled disabled:border-textarea-border-disabled disabled:text-textarea-text-disabled', + 'disabled:pointer-events-none disabled:bg-textarea-disabled disabled:border-textarea-border-disabled disabled:text-textarea-fg-disabled', ], variants: { variant: { @@ -57,7 +57,7 @@ const textareaVariants = tv({ both: 'resize', }, readonly: { - true: 'bg-textarea-disabled cursor-default pointer-events-none opacity-90 text-textarea-text-disabled border-textarea-border-disabled', + true: 'bg-textarea-disabled cursor-default pointer-events-none opacity-90 text-textarea-fg-disabled border-textarea-border-disabled', }, }, defaultVariants: { diff --git a/libs/ui/src/molecules/checkbox.tsx b/libs/ui/src/molecules/checkbox.tsx index 4240ae0e1a..a3f808eeae 100644 --- a/libs/ui/src/molecules/checkbox.tsx +++ b/libs/ui/src/molecules/checkbox.tsx @@ -17,7 +17,7 @@ const checkboxVariants = tv({ 'bg-checkbox data-[state=checked]:bg-checkbox-checked border-checkbox', /* center icon, grid and place items do not work e.g. flex */ 'after:absolute after:left-1/2 after:top-1/2 after:-translate-x-1/2 after:-translate-y-1/2', - 'data-[state=checked]:after:token-icon-checkbox text-icon-text data-[state=checked]:text-checkbox-icon-md', + 'data-[state=checked]:after:token-icon-checkbox text-icon-fg data-[state=checked]:text-checkbox-icon-md', 'data-[disabled]:opacity-checkbox-disabled data-[disabled]:cursor-not-allowed data-[disabled]:grayscale', 'transition-all duration-200', 'data-[focus]:outline-none data-[focus]:ring-2 data-[focus]:ring-checkbox-ring data-[focus]:ring-offset-checkbox-offset data-[focus]:ring-offset-2', @@ -25,7 +25,7 @@ const checkboxVariants = tv({ 'data-[invalid]:border-checkbox-border-error', 'data-[state=indeterminate]:bg-checkbox-indeterminate', 'data-[state=indeterminate]:after:w-indeterminate data-[state=indeterminate]:after:h-indeterminate', - 'data-[state=indeterminate]:after:bg-icon-text', + 'data-[state=indeterminate]:after:bg-icon-fg', ], }, }) diff --git a/libs/ui/src/tokens/_semantic.css b/libs/ui/src/tokens/_semantic.css index 1b8ebc60a9..64a783b203 100644 --- a/libs/ui/src/tokens/_semantic.css +++ b/libs/ui/src/tokens/_semantic.css @@ -288,7 +288,7 @@ /*Disabled state - Disabled It is used to fill all kinds of forbidden elements in the interface, such as background, text, stroke, fill, etc*/ --color-disabled-bg: --alpha(var(--color-base-reverse) / 10%); - --color-disabled-text: --alpha( + --color-disabled-fg: --alpha( var(--color-fg-primary) / var(--opacity-disabled) ); diff --git a/libs/ui/src/tokens/_spacing.css b/libs/ui/src/tokens/_spacing.css index 0253cfa487..c44ab05de9 100644 --- a/libs/ui/src/tokens/_spacing.css +++ b/libs/ui/src/tokens/_spacing.css @@ -20,7 +20,6 @@ --spacing-900: clamp(6.1875rem, 5.9484rem + 1.1957vw, 6.875rem); --spacing-950: clamp(6.75rem, 6.4891rem + 1.3043vw, 7.5rem); - /* Legacy aliases for backward compatibility */ --spacing-3xs: var(--spacing-100); /* 4px */ --spacing-2xs: var(--spacing-150); /* 8px */ @@ -31,4 +30,4 @@ --spacing-xl: var(--spacing-600); /* 48px */ --spacing-2xl: var(--spacing-750); /* 64px */ --spacing-3xl: var(--spacing-950); /* 96px */ -} \ No newline at end of file +} diff --git a/libs/ui/src/tokens/components/atoms/_button.css b/libs/ui/src/tokens/components/atoms/_button.css index e23fc19286..a1d0d7a2f1 100644 --- a/libs/ui/src/tokens/components/atoms/_button.css +++ b/libs/ui/src/tokens/components/atoms/_button.css @@ -147,37 +147,37 @@ /* === UNIFIED DISABLED STATES === */ --color-btn-disabled: var(--color-disabled-bg); - --color-btn-text-disabled: var(--color-disabled-text); + --color-btn-fg-disabled: var(--color-disabled-fg); --color-btn-border-disabled: var(--color-disabled-border); /* === TEXT COLORS === */ - --color-btn-text: var(--color-fg-reverse); - - --color-btn-text-outlined: var(--color-fg-primary); - - --color-btn-text-primary: var(--color-btn-text); - --color-btn-text-secondary: var(--color-btn-text); - --color-btn-text-tertiary: var(--color-btn-text); - --color-btn-text-warning: var(--color-btn-text); - --color-btn-text-danger: var(--color-btn-text); - - --color-btn-text-primary-light: var(--color-btn-text-light); - --color-btn-text-secondary-light: var(--color-btn-text-light); - --color-btn-text-tertiary-light: var(--color-btn-text-light); - --color-btn-text-warning-light: var(--color-btn-text-light); - --color-btn-text-danger-light: var(--color-btn-text-light); - - --color-btn-text-outlined-primary: var(--color-btn-text-outlined); - --color-btn-text-outlined-secondary: var(--color-btn-text-outlined); - --color-btn-text-outlined-tertiary: var(--color-btn-text-outlined); - --color-btn-text-outlined-warning: var(--color-btn-text-outlined); - --color-btn-text-outlined-danger: var(--color-btn-text-outlined); - - --color-btn-text-primary-borderless: var(--color-btn-primary); - --color-btn-text-secondary-borderless: var(--color-btn-secondary); - --color-btn-text-tertiary-borderless: var(--color-btn-tertiary); - --color-btn-text-warning-borderless: var(--color-btn-warning); - --color-btn-text-danger-borderless: var(--color-btn-danger); + --color-btn-fg: var(--color-fg-reverse); + + --color-btn-fg-outlined: var(--color-fg-primary); + + --color-btn-fg-primary: var(--color-btn-fg); + --color-btn-fg-secondary: var(--color-btn-fg); + --color-btn-fg-tertiary: var(--color-btn-fg); + --color-btn-fg-warning: var(--color-btn-fg); + --color-btn-fg-danger: var(--color-btn-fg); + + --color-btn-fg-primary-light: var(--color-btn-fg-light); + --color-btn-fg-secondary-light: var(--color-btn-fg-light); + --color-btn-fg-tertiary-light: var(--color-btn-fg-light); + --color-btn-fg-warning-light: var(--color-btn-fg-light); + --color-btn-fg-danger-light: var(--color-btn-fg-light); + + --color-btn-fg-outlined-primary: var(--color-btn-fg-outlined); + --color-btn-fg-outlined-secondary: var(--color-btn-fg-outlined); + --color-btn-fg-outlined-tertiary: var(--color-btn-fg-outlined); + --color-btn-fg-outlined-warning: var(--color-btn-fg-outlined); + --color-btn-fg-outlined-danger: var(--color-btn-fg-outlined); + + --color-btn-fg-primary-borderless: var(--color-btn-primary); + --color-btn-fg-secondary-borderless: var(--color-btn-secondary); + --color-btn-fg-tertiary-borderless: var(--color-btn-tertiary); + --color-btn-fg-warning-borderless: var(--color-btn-warning); + --color-btn-fg-danger-borderless: var(--color-btn-danger); /* === FOCUS RINGS === */ --color-btn-ring-primary: --alpha( diff --git a/libs/ui/src/tokens/components/atoms/_input.css b/libs/ui/src/tokens/components/atoms/_input.css index b2be131378..c3493b7c66 100644 --- a/libs/ui/src/tokens/components/atoms/_input.css +++ b/libs/ui/src/tokens/components/atoms/_input.css @@ -3,7 +3,7 @@ /* Base input colors */ --color-input: var(--color-fill-base); --color-input-border: var(--color-stroke-primary); - --color-input-text: var(--color-fg-primary); + --color-input-fg: var(--color-fg-primary); --color-input-disabled: var(--color-disabled-bg); /* Validation states - primary colors */ @@ -63,29 +63,29 @@ ); /* Text colors */ - --color-input-text-disabled: --alpha( - var(--color-input-text) / + --color-input-fg-disabled: --alpha( + var(--color-input-fg) / var(--opacity-disabled) ); --color-input-placeholder: --alpha( - var(--color-input-text) / + var(--color-input-fg) / var(--opacity-placeholder) ); --color-input-placeholder-disabled: --alpha( - var(--color-input-text) / + var(--color-input-fg) / var(--opacity-disabled-placeholder) ); /* Label colors */ - --color-label-text: var(--color-input-text); - --color-label-disabled: var(--color-input-text-disabled); + --color-label-fg: var(--color-input-fg); + --color-label-disabled: var(--color-input-fg-disabled); --color-label-required: var(--color-input-danger); --margin-label: var(--spacing-100); /* Error/Helper/Extra colors */ - --color-error-text: var(--color-danger); - --color-helper-text: var(--color-input-text); + --color-error-fg: var(--color-danger); + --color-helper-fg: var(--color-input-fg); /* Focus ring colors - using consistent opacity */ --color-input-ring: --alpha(var(--color-primary) / var(--opacity-ring)); @@ -133,5 +133,5 @@ --text-helper-sm: var(--text-input-sm); --text-helper-md: var(--text-input-md); --text-helper-lg: var(--text-input-lg); - --color-helper-text: var(--color-fg-secondary); + --color-helper-fg: var(--color-fg-secondary); } diff --git a/libs/ui/src/tokens/components/atoms/_textarea.css b/libs/ui/src/tokens/components/atoms/_textarea.css index d4b6d22686..5d7b6bac7b 100644 --- a/libs/ui/src/tokens/components/atoms/_textarea.css +++ b/libs/ui/src/tokens/components/atoms/_textarea.css @@ -3,7 +3,7 @@ /* Primary variables mapped from semantic tokens */ --color-textarea: var(--color-fill-base); --color-textarea-border: var(--color-stroke-primary); - --color-textarea-text: var(--color-fg-primary); + --color-textarea-fg: var(--color-fg-primary); --color-textarea-placeholder: --alpha( var(--color-fg-primary) / var(--opacity-placeholder) @@ -58,7 +58,7 @@ --color-textarea-borderless: --alpha(var(--color-textarea) / 50%); /* Text states */ - --color-textarea-text-disabled: var(--color-disabled-text); + --color-textarea-fg-disabled: var(--color-disabled-fg); /* Focus rings */ --color-textarea-ring: --alpha(var(--color-primary) / var(--opacity-ring)); diff --git a/libs/ui/src/tokens/components/molecules/_checkbox.css b/libs/ui/src/tokens/components/molecules/_checkbox.css index 5f67a74e47..71bbe01eb7 100644 --- a/libs/ui/src/tokens/components/molecules/_checkbox.css +++ b/libs/ui/src/tokens/components/molecules/_checkbox.css @@ -16,7 +16,7 @@ --color-checkbox-border-error: var(--color-checkbox-error); --spacing-checkbox: var(--spacing-200); - --color-icon-text: var(--color-fg-dark); + --color-icon-fg: var(--color-fg-dark); --text-checkbox-icon-md: var(--text-xs); --radius-checkbox: var(--radius-sm); From 9786331885b4c6ed5686898560f79fd9e8077efb Mon Sep 17 00:00:00 2001 From: KaiUweCZE Date: Wed, 13 Aug 2025 11:23:04 +0200 Subject: [PATCH 03/44] refactor(ui): automate semantic color light variants with oklch calculations - Replace static light-dark() definitions with dynamic oklch derivation - Add configurable state variables for light variant customization - Create comprehensive color palette story for design system documentation - Ensure consistent light variant behavior across all theme modes --- libs/ui/src/tokens/_semantic.css | 44 ++- .../templates/color-palette.stories.tsx | 336 ++++++++++++++++++ .../templates/comprehensive-form.stories.tsx | 2 +- 3 files changed, 365 insertions(+), 17 deletions(-) create mode 100644 libs/ui/stories/templates/color-palette.stories.tsx diff --git a/libs/ui/src/tokens/_semantic.css b/libs/ui/src/tokens/_semantic.css index 64a783b203..882a4b6428 100644 --- a/libs/ui/src/tokens/_semantic.css +++ b/libs/ui/src/tokens/_semantic.css @@ -8,7 +8,9 @@ :root { --state-hover: -0.08; --state-active: -0.12; - --state-disabled-chroma: 0.05; + --state-disabled-chroma: 0.01; + --state-light-variant: 0.2; /* Always make colors lighter for -light variant */ + --state-light-chroma: 0.6; /* Reduce saturation for softer, pastel colors */ --opacity-ring: 80%; --opacity-disabled: 60%; --opacity-placeholder: 60%; @@ -78,7 +80,10 @@ --color-primary-disabled: oklch( from var(--color-primary) l var(--state-disabled-chroma) h ); - --color-primary-light: light-dark(var(--color-blue-300), var(--color-sky-200)); + --color-primary-light: oklch( + from var(--color-primary) clamp(0, calc(l + var(--state-light-variant)), 1) + calc(c * var(--state-light-chroma)) h + ); --color-primary-light-hover: oklch( from var(--color-primary-light) calc(l + var(--state-hover)) c h ); @@ -97,9 +102,10 @@ --color-secondary-disabled: oklch( from var(--color-secondary) l var(--state-disabled-chroma) h ); - --color-secondary-light: light-dark( - var(--color-orange-300), - var(--color-amber-200) + --color-secondary-light: oklch( + from var(--color-secondary) + clamp(0, calc(l + var(--state-light-variant)), 1) + calc(c * var(--state-light-chroma)) h ); --color-secondary-light-hover: oklch( from var(--color-secondary-light) calc(l + var(--state-hover)) c h @@ -119,9 +125,9 @@ --color-tertiary-disabled: oklch( from var(--color-tertiary) l var(--state-disabled-chroma) h ); - --color-tertiary-light: light-dark( - var(--color-pink-300), - var(--color-rose-200) + --color-tertiary-light: oklch( + from var(--color-tertiary) clamp(0, calc(l + var(--state-light-variant)), 1) + calc(c * var(--state-light-chroma)) h ); --color-tertiary-light-hover: oklch( from var(--color-tertiary-light) calc(l + var(--state-hover)) c h @@ -144,9 +150,9 @@ --color-success-disabled: oklch( from var(--color-success) l var(--state-disabled-chroma) h ); - --color-success-light: light-dark( - var(--color-emerald-100), - var(--color-emerald-100) + --color-success-light: oklch( + from var(--color-success) clamp(0, calc(l + var(--state-light-variant)), 1) + calc(c * var(--state-light-chroma)) h ); --color-success-light-hover: oklch( from var(--color-success-light) calc(l + var(--state-hover)) c h @@ -166,7 +172,10 @@ --color-info-disabled: oklch( from var(--color-info) l var(--state-disabled-chroma) h ); - --color-info-light: light-dark(var(--color-teal-300), var(--color-cyan-200)); + --color-info-light: oklch( + from var(--color-info) clamp(0, calc(l + var(--state-light-variant)), 1) + calc(c * var(--state-light-chroma)) h + ); --color-info-light-hover: oklch( from var(--color-info-light) calc(l + var(--state-hover)) c h ); @@ -185,9 +194,9 @@ --color-warning-disabled: oklch( from var(--color-warning) l var(--state-disabled-chroma) h ); - --color-warning-light: light-dark( - var(--color-yellow-300), - var(--color-yellow-200) + --color-warning-light: oklch( + from var(--color-warning) clamp(0, calc(l + var(--state-light-variant)), 1) + calc(c * var(--state-light-chroma)) h ); --color-warning-light-hover: oklch( from var(--color-warning-light) calc(l + var(--state-hover)) c h @@ -207,7 +216,10 @@ --color-danger-disabled: oklch( from var(--color-danger) l var(--state-disabled-chroma) h ); - --color-danger-light: light-dark(var(--color-red-300), var(--color-red-200)); + --color-danger-light: oklch( + from var(--color-danger) clamp(0, calc(l + var(--state-light-variant)), 1) + calc(c * var(--state-light-chroma)) h + ); --color-danger-light-hover: oklch( from var(--color-danger-light) calc(l + var(--state-hover)) c h ); diff --git a/libs/ui/stories/templates/color-palette.stories.tsx b/libs/ui/stories/templates/color-palette.stories.tsx new file mode 100644 index 0000000000..07d72ae5d8 --- /dev/null +++ b/libs/ui/stories/templates/color-palette.stories.tsx @@ -0,0 +1,336 @@ +import type { Meta, StoryObj } from '@storybook/react' +import { useState } from 'react' +import '../../src/tokens/_colors.css' +import '../../src/tokens/_semantic.css' + +const meta: Meta = { + title: 'Templates/Color Palette', + parameters: { + layout: 'fullscreen', + }, +} + +export default meta +type Story = StoryObj + +const semanticColors = [ + 'primary', + 'secondary', + 'tertiary', + 'info', + 'success', + 'warning', + 'danger', +] as const + +const colorVariants = [ + { suffix: '', label: 'Default' }, + { suffix: '-light', label: 'Light' }, +] as const + +const stateVariants = [ + { state: '', label: 'Default' }, + { state: 'hover', label: 'Hover' }, + { state: 'active', label: 'Active' }, + { state: 'disabled', label: 'Disabled' }, +] as const + +function ColorSwatch({ + color, + variant, + state, + showLabel = true +}: { + color: string + variant: string + state: string + showLabel?: boolean +}) { + const [copied, setCopied] = useState(false) + + const baseColorVar = `--color-${color}${variant}` + + // For disabled state, we use the predefined -disabled variant + // For other states, we calculate the color modification + const computedColor = state === 'disabled' + ? `var(--color-${color}${variant}-disabled)` + : state && state !== '' + ? `oklch(from var(${baseColorVar}) calc(l + var(--state-${state})) c h)` + : `var(${baseColorVar})` + + // Get the correct CSS variable name to copy + const colorVar = state === 'disabled' + ? `--color-${color}${variant}-disabled` + : baseColorVar + + const handleCopy = () => { + navigator.clipboard.writeText(colorVar) + setCopied(true) + setTimeout(() => setCopied(false), 2000) + } + + return ( +
+
+ {copied && ( +
+ Copied! +
+ )} +
+ {showLabel && ( +
+
+ {color}{variant} +
+ {state && ( +
+ {state} +
+ )} +
+ {colorVar} +
+
+ )} +
+ ) +} + +function ColorGroup({ color }: { color: string }) { + return ( +
+

+ {color} +

+ +
+ {colorVariants.map(({ suffix, label }) => ( +
+

+ {label} +

+
+ {stateVariants.map(({ state, label: stateLabel }) => ( + + ))} +
+
+ ))} +
+
+ ) +} + +function ColorPaletteGrid() { + const [filter, setFilter] = useState('all') + const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid') + + const filteredColors = filter === 'all' + ? semanticColors + : semanticColors.filter(c => c === filter) + + return ( +
+
+ {/* Header */} +
+

+ Color Palette +

+

+ Click any color swatch to copy its CSS variable name +

+
+ + {/* Controls */} +
+
+ + +
+ + +
+ + {/* Color Grid/List */} +
+ {filteredColors.map(color => ( + + ))} +
+ + {/* Quick Reference */} +
+

+ Quick Reference +

+ +
+
+

+ Color Variants +

+
+ {colorVariants.map(({ suffix, label }) => ( +
+ {label} + + {suffix || '(default)'} + +
+ ))} +
+
+ +
+

+ State Modifiers +

+
+ {stateVariants.map(({ state, label }) => ( +
+ {label} + + {state ? `--state-${state}` : '(default)'} + +
+ ))} +
+
+
+ +
+

+ Usage Example +

+ + background-color: var(--color-primary-light); + +
+
+
+
+ ) +} + +export const Default: Story = { + render: () => , +} + +// Compact view for all colors at once +function CompactColorGrid() { + return ( +
+
+

+ Color Palette - Compact View +

+ +
+
+ + + + + {colorVariants.map(({ label }) => ( + + ))} + + + + {colorVariants.map(({ label }) => ( + + {stateVariants.map(({ label: stateLabel }) => ( + + ))} + + ))} + + + + {semanticColors.map(color => ( + + + {colorVariants.map(({ suffix }) => ( + + {stateVariants.map(({ state }) => ( + + ))} + + ))} + + ))} + +
+ Color + + {label} +
+ {stateLabel} +
+ {color} + + +
+
+
+
+
+ ) +} + +export const Compact: Story = { + render: () => , +} \ No newline at end of file diff --git a/libs/ui/stories/templates/comprehensive-form.stories.tsx b/libs/ui/stories/templates/comprehensive-form.stories.tsx index 1a66917dbf..98b255cde2 100644 --- a/libs/ui/stories/templates/comprehensive-form.stories.tsx +++ b/libs/ui/stories/templates/comprehensive-form.stories.tsx @@ -8,7 +8,7 @@ import { Textarea } from '../../src/atoms/textarea' import { Combobox } from '../../src/molecules/combobox' import { FormCheckbox } from '../../src/molecules/form-checkbox' import { FormInput } from '../../src/molecules/form-input' -import { NumericInput } from '../../src/molecules/numeric-input' +import { NumericInput } from '../../src/atoms/numeric-input' import { Select } from '../../src/molecules/select' import { Switch } from '../../src/molecules/switch' From c7cc8094dd99bdaf147308359dbd0679c4c54988 Mon Sep 17 00:00:00 2001 From: KaiUweCZE Date: Wed, 13 Aug 2025 15:30:18 +0200 Subject: [PATCH 04/44] refactor(ui): migrate border tokens from hardcoded colors to alpha-based system - Replace --color-stroke-* with --color-border-* naming convention - minor refactor light variants to use min pattern intead of clamp --- libs/ui/src/tokens/_semantic.css | 36 ++++-- .../ui/src/tokens/components/atoms/_badge.css | 2 +- .../ui/src/tokens/components/atoms/_input.css | 2 +- .../src/tokens/components/atoms/_textarea.css | 2 +- .../components/molecules/_accordion.css | 2 +- .../tokens/components/molecules/_checkbox.css | 2 +- .../tokens/components/molecules/_combobox.css | 2 +- .../tokens/components/molecules/_dialog.css | 2 +- .../src/tokens/components/molecules/_menu.css | 4 +- .../components/molecules/_numeric-input.css | 2 +- .../components/molecules/_pagination.css | 2 +- .../components/molecules/_product-card.css | 2 +- .../components/molecules/_range-slider.css | 2 +- .../tokens/components/molecules/_select.css | 4 +- .../tokens/components/molecules/_switch.css | 2 +- .../components/molecules/_tree-view.css | 2 +- .../templates/color-palette.stories.tsx | 112 +++++++++++++++--- 17 files changed, 137 insertions(+), 45 deletions(-) diff --git a/libs/ui/src/tokens/_semantic.css b/libs/ui/src/tokens/_semantic.css index 882a4b6428..ca869f7ca6 100644 --- a/libs/ui/src/tokens/_semantic.css +++ b/libs/ui/src/tokens/_semantic.css @@ -9,8 +9,10 @@ --state-hover: -0.08; --state-active: -0.12; --state-disabled-chroma: 0.01; - --state-light-variant: 0.2; /* Always make colors lighter for -light variant */ + --state-light-variant: 0.1; /* Always make colors lighter for -light variant */ --state-light-chroma: 0.6; /* Reduce saturation for softer, pastel colors */ + --state-border: 15%; + --max-lightness: 0.97; --opacity-ring: 80%; --opacity-disabled: 60%; --opacity-placeholder: 60%; @@ -81,7 +83,8 @@ from var(--color-primary) l var(--state-disabled-chroma) h ); --color-primary-light: oklch( - from var(--color-primary) clamp(0, calc(l + var(--state-light-variant)), 1) + from var(--color-primary) + min(var(--max-lightness), calc(l + var(--state-light-variant))) calc(c * var(--state-light-chroma)) h ); --color-primary-light-hover: oklch( @@ -104,7 +107,7 @@ ); --color-secondary-light: oklch( from var(--color-secondary) - clamp(0, calc(l + var(--state-light-variant)), 1) + min(var(--max-lightness), calc(l + var(--state-light-variant))) calc(c * var(--state-light-chroma)) h ); --color-secondary-light-hover: oklch( @@ -126,7 +129,8 @@ from var(--color-tertiary) l var(--state-disabled-chroma) h ); --color-tertiary-light: oklch( - from var(--color-tertiary) clamp(0, calc(l + var(--state-light-variant)), 1) + from var(--color-tertiary) + min(var(--max-lightness), calc(l + var(--state-light-variant))) calc(c * var(--state-light-chroma)) h ); --color-tertiary-light-hover: oklch( @@ -151,7 +155,8 @@ from var(--color-success) l var(--state-disabled-chroma) h ); --color-success-light: oklch( - from var(--color-success) clamp(0, calc(l + var(--state-light-variant)), 1) + from var(--color-success) + min(var(--max-lightness), calc(l + var(--state-light-variant))) calc(c * var(--state-light-chroma)) h ); --color-success-light-hover: oklch( @@ -173,7 +178,8 @@ from var(--color-info) l var(--state-disabled-chroma) h ); --color-info-light: oklch( - from var(--color-info) clamp(0, calc(l + var(--state-light-variant)), 1) + from var(--color-info) + min(var(--max-lightness), calc(l + var(--state-light-variant))) calc(c * var(--state-light-chroma)) h ); --color-info-light-hover: oklch( @@ -195,7 +201,8 @@ from var(--color-warning) l var(--state-disabled-chroma) h ); --color-warning-light: oklch( - from var(--color-warning) clamp(0, calc(l + var(--state-light-variant)), 1) + from var(--color-warning) + min(var(--max-lightness), calc(l + var(--state-light-variant))) calc(c * var(--state-light-chroma)) h ); --color-warning-light-hover: oklch( @@ -217,7 +224,8 @@ from var(--color-danger) l var(--state-disabled-chroma) h ); --color-danger-light: oklch( - from var(--color-danger) clamp(0, calc(l + var(--state-light-variant)), 1) + from var(--color-danger) + min(var(--max-lightness), calc(l + var(--state-light-variant))) calc(c * var(--state-light-chroma)) h ); --color-danger-light-hover: oklch( @@ -266,8 +274,14 @@ ); /* Borders */ - --color-stroke-primary: var(--color-gray-300); - --color-stroke-secondary: var(--color-gray-600); + --color-border-primary: --alpha( + var(--color-base-reverse) / + var(--state-border) + ); + --color-border-secondary: --alpha( + var(--color-base-reverse) / + calc(var(--state-border) * 1.5) + ); --border-width-none: 0px; --border-width-sm: 1px; --border-width-md: 2px; @@ -305,7 +319,7 @@ var(--opacity-disabled) ); --color-disabled-border: --alpha( - var(--color-stroke-primary) / + var(--color-border-primary) / var(--opacity-disabled) ); --color-disabled-fill: --alpha( diff --git a/libs/ui/src/tokens/components/atoms/_badge.css b/libs/ui/src/tokens/components/atoms/_badge.css index 545457fc49..e66e6a912c 100644 --- a/libs/ui/src/tokens/components/atoms/_badge.css +++ b/libs/ui/src/tokens/components/atoms/_badge.css @@ -51,5 +51,5 @@ /* Outline variant */ --color-badge-outline-bg: transparent; --color-badge-outline-fg: var(--color-fg-secondary); - --color-badge-border-outline: var(--color-border-neutral); + --color-badge-border-outline: var(--color-border-primary); } diff --git a/libs/ui/src/tokens/components/atoms/_input.css b/libs/ui/src/tokens/components/atoms/_input.css index c3493b7c66..6085b0549e 100644 --- a/libs/ui/src/tokens/components/atoms/_input.css +++ b/libs/ui/src/tokens/components/atoms/_input.css @@ -2,7 +2,7 @@ /* BASE TOKENS - direct mapping from semantic tokens */ /* Base input colors */ --color-input: var(--color-fill-base); - --color-input-border: var(--color-stroke-primary); + --color-input-border: var(--color-border-primary); --color-input-fg: var(--color-fg-primary); --color-input-disabled: var(--color-disabled-bg); diff --git a/libs/ui/src/tokens/components/atoms/_textarea.css b/libs/ui/src/tokens/components/atoms/_textarea.css index 5d7b6bac7b..887e2ec867 100644 --- a/libs/ui/src/tokens/components/atoms/_textarea.css +++ b/libs/ui/src/tokens/components/atoms/_textarea.css @@ -2,7 +2,7 @@ /* === BASE TOKEN MAPPING === */ /* Primary variables mapped from semantic tokens */ --color-textarea: var(--color-fill-base); - --color-textarea-border: var(--color-stroke-primary); + --color-textarea-border: var(--color-border-primary); --color-textarea-fg: var(--color-fg-primary); --color-textarea-placeholder: --alpha( var(--color-fg-primary) / diff --git a/libs/ui/src/tokens/components/molecules/_accordion.css b/libs/ui/src/tokens/components/molecules/_accordion.css index 14a9ce6098..12a8009ed5 100644 --- a/libs/ui/src/tokens/components/molecules/_accordion.css +++ b/libs/ui/src/tokens/components/molecules/_accordion.css @@ -1,7 +1,7 @@ @theme static { /* === BASE TOKEN MAPPING === */ --color-accordion: var(--color-surface); - --color-accordion-border: var(--color-stroke-primary); + --color-accordion-border: var(--color-border-primary); /* === STATE VARIATIONS === */ --color-accordion-hover: oklch( diff --git a/libs/ui/src/tokens/components/molecules/_checkbox.css b/libs/ui/src/tokens/components/molecules/_checkbox.css index 71bbe01eb7..39f5997405 100644 --- a/libs/ui/src/tokens/components/molecules/_checkbox.css +++ b/libs/ui/src/tokens/components/molecules/_checkbox.css @@ -3,7 +3,7 @@ --color-checkbox: var(--color-fill-active); --color-checkbox-error: var(--color-danger); --color-checkbox-checked: var(--color-success); - --color-checkbox-border: var(--color-stroke-primary); + --color-checkbox-border: var(--color-border-primary); --opacity-checkbox-disabled: var(--opacity-disabled); --color-checkbox-indeterminate: var(--color-primary); diff --git a/libs/ui/src/tokens/components/molecules/_combobox.css b/libs/ui/src/tokens/components/molecules/_combobox.css index 39c2ec3b5b..8b505539f6 100644 --- a/libs/ui/src/tokens/components/molecules/_combobox.css +++ b/libs/ui/src/tokens/components/molecules/_combobox.css @@ -2,7 +2,7 @@ --z-combobox-content: 20; /* ===== CONTROL (Input Container) ===== */ --color-combobox: var(--color-fill-base); - --color-combobox-border: var(--color-stroke-primary); + --color-combobox-border: var(--color-border-primary); --color-combobox-hover: oklch( from var(--color-combobox) calc(l + var(--state-hover)) c h ); diff --git a/libs/ui/src/tokens/components/molecules/_dialog.css b/libs/ui/src/tokens/components/molecules/_dialog.css index 49e5f0e85f..8ac91ee13f 100644 --- a/libs/ui/src/tokens/components/molecules/_dialog.css +++ b/libs/ui/src/tokens/components/molecules/_dialog.css @@ -16,7 +16,7 @@ /* ===== DIALOG CONTENT PANEL ===== */ --color-dialog-content-bg: var(--color-dialog-bg); --color-dialog-content-fg: var(--color-dialog-fg); - --color-dialog-content-border: var(--color-stroke-primary); + --color-dialog-content-border: var(--color-border-primary); --radius-dialog-content: var(--radius-sm); --shadow-dialog-content: var(--shadow-xl); diff --git a/libs/ui/src/tokens/components/molecules/_menu.css b/libs/ui/src/tokens/components/molecules/_menu.css index c6f22d3069..41c8911675 100644 --- a/libs/ui/src/tokens/components/molecules/_menu.css +++ b/libs/ui/src/tokens/components/molecules/_menu.css @@ -1,7 +1,7 @@ @theme static { /* ===== MENU CONTENT PANEL ===== */ --color-menu-content-bg: var(--color-surface); - --color-menu-content-border: var(--color-stroke-primary); + --color-menu-content-border: var(--color-border-primary); --radius-menu: var(--radius-sm); --shadow-menu-content-shadow: var(--shadow-lg); @@ -21,7 +21,7 @@ --color-menu-item-icon-fg: var(--color-fg-secondary); /* ===== MENU SEPARATOR ===== */ - --color-menu-separator-bg: var(--color-stroke-primary); + --color-menu-separator-bg: var(--color-border-primary); --height-menu-separator: 1px; --margin-menu-separator: var(--spacing-200); diff --git a/libs/ui/src/tokens/components/molecules/_numeric-input.css b/libs/ui/src/tokens/components/molecules/_numeric-input.css index b3945dc7be..21cd3952e8 100644 --- a/libs/ui/src/tokens/components/molecules/_numeric-input.css +++ b/libs/ui/src/tokens/components/molecules/_numeric-input.css @@ -6,7 +6,7 @@ --color-ni-invalid-border: var(--color-danger); --color-ni-invalid-bg: --alpha(var(--color-danger) / 10%); - --color-ni-border: var(--color-stroke-primary); + --color-ni-border: var(--color-border-primary); --border-width-ni: var(--border-width-input); --gap-ni-root-sm: var(--spacing-150); diff --git a/libs/ui/src/tokens/components/molecules/_pagination.css b/libs/ui/src/tokens/components/molecules/_pagination.css index b732c9f5cc..8b7ad11c1d 100644 --- a/libs/ui/src/tokens/components/molecules/_pagination.css +++ b/libs/ui/src/tokens/components/molecules/_pagination.css @@ -20,7 +20,7 @@ ); /* Border colors */ - --color-pagination-border: var(--color-stroke-primary); + --color-pagination-border: var(--color-border-primary); --color-pagination-border-active: var(--color-primary); --color-pagination-border-hover: --alpha( var(--color-pagination-border-active) / diff --git a/libs/ui/src/tokens/components/molecules/_product-card.css b/libs/ui/src/tokens/components/molecules/_product-card.css index c54d5e3464..64e595653b 100644 --- a/libs/ui/src/tokens/components/molecules/_product-card.css +++ b/libs/ui/src/tokens/components/molecules/_product-card.css @@ -1,7 +1,7 @@ @theme static { --color-pc: var(--color-surface); - --border-pc-border: var(--color-stroke-primary); + --border-pc-border: var(--color-border-primary); --border-pc-width: var(--border-width-sm); --radius-pc: var(--radius-md); diff --git a/libs/ui/src/tokens/components/molecules/_range-slider.css b/libs/ui/src/tokens/components/molecules/_range-slider.css index 5bbcdc6d8d..db5c6c9b84 100644 --- a/libs/ui/src/tokens/components/molecules/_range-slider.css +++ b/libs/ui/src/tokens/components/molecules/_range-slider.css @@ -3,7 +3,7 @@ --color-slider-track: var(--color-fill-base); --color-slider-range: var(--color-primary); --color-slider-thumb: var(--color-base-light); - --color-slider-border: var(--color-stroke-primary); + --color-slider-border: var(--color-border-primary); --color-slider-error: var(--color-danger); --opacity-slider-disabled: var(--opacity-disabled); diff --git a/libs/ui/src/tokens/components/molecules/_select.css b/libs/ui/src/tokens/components/molecules/_select.css index 274463bce8..60e519329b 100644 --- a/libs/ui/src/tokens/components/molecules/_select.css +++ b/libs/ui/src/tokens/components/molecules/_select.css @@ -1,7 +1,7 @@ @theme static { /* === BASE TOKEN MAPPING === */ --color-select-bg: var(--color-base); - --color-select-border: var(--color-stroke-primary); + --color-select-border: var(--color-border-primary); --color-select-fg: var(--color-fg-primary); --color-select-placeholder: --alpha( var(--color-fg-secondary) / @@ -32,7 +32,7 @@ /* === DROPDOWN CONTENT === */ --color-select-content-bg: var(--color-surface); - --color-select-content-border: var(--color-stroke-primary); + --color-select-content-border: var(--color-border-primary); --color-select-content-shadow: var(--shadow-md); /* === OPTION ITEM === */ diff --git a/libs/ui/src/tokens/components/molecules/_switch.css b/libs/ui/src/tokens/components/molecules/_switch.css index ee57c01667..a88b11f24f 100644 --- a/libs/ui/src/tokens/components/molecules/_switch.css +++ b/libs/ui/src/tokens/components/molecules/_switch.css @@ -7,7 +7,7 @@ /* === BASE TOKEN MAPPING === */ --color-switch-bg: var(--color-overlay); --color-switch-checked: var(--color-success); - --color-switch-border: var(--color-stroke-primary); + --color-switch-border: var(--color-border-primary); --color-switch-thumb: var(--color-base-light); /* === STATE VARIATIONS === */ diff --git a/libs/ui/src/tokens/components/molecules/_tree-view.css b/libs/ui/src/tokens/components/molecules/_tree-view.css index 57e1a3da51..cdc611a0bb 100644 --- a/libs/ui/src/tokens/components/molecules/_tree-view.css +++ b/libs/ui/src/tokens/components/molecules/_tree-view.css @@ -3,7 +3,7 @@ /* === BASE TOKEN MAPPING === */ --color-tree-bg: transparent; --color-tree-fg: var(--color-fg-primary); - --color-tree-border: var(--color-stroke-primary); + --color-tree-border: var(--color-border-primary); /* Tree root */ --color-tree-root-bg: transparent; diff --git a/libs/ui/stories/templates/color-palette.stories.tsx b/libs/ui/stories/templates/color-palette.stories.tsx index 07d72ae5d8..0c6a006c1c 100644 --- a/libs/ui/stories/templates/color-palette.stories.tsx +++ b/libs/ui/stories/templates/color-palette.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from '@storybook/react' -import { useState } from 'react' +import React, { useState, useRef } from 'react' import '../../src/tokens/_colors.css' import '../../src/tokens/_semantic.css' @@ -39,14 +39,17 @@ function ColorSwatch({ color, variant, state, - showLabel = true + showLabel = true, + copyMode = 'variable' }: { color: string variant: string state: string showLabel?: boolean + copyMode?: 'variable' | 'oklch' }) { const [copied, setCopied] = useState(false) + const swatchRef = useRef(null) const baseColorVar = `--color-${color}${variant}` @@ -64,7 +67,18 @@ function ColorSwatch({ : baseColorVar const handleCopy = () => { - navigator.clipboard.writeText(colorVar) + let textToCopy: string + + if (copyMode === 'oklch' && swatchRef.current) { + // Get oklch value from computed styles + const oklch = window.getComputedStyle(swatchRef.current).backgroundColor + textToCopy = oklch + } else { + // Copy variable name + textToCopy = colorVar + } + + navigator.clipboard.writeText(textToCopy) setCopied(true) setTimeout(() => setCopied(false), 2000) } @@ -75,14 +89,18 @@ function ColorSwatch({ onClick={handleCopy} >
{copied && ( -
- Copied! +
+
Copied!
+
+ {copyMode === 'oklch' ? 'OKLCH' : 'VAR'} +
)}
@@ -97,7 +115,10 @@ function ColorSwatch({
)}
- {colorVar} + {copyMode === 'oklch' && swatchRef.current + ? window.getComputedStyle(swatchRef.current).backgroundColor + : colorVar + }
)} @@ -105,7 +126,7 @@ function ColorSwatch({ ) } -function ColorGroup({ color }: { color: string }) { +function ColorGroup({ color, copyMode }: { color: string; copyMode: 'variable' | 'oklch' }) { return (

@@ -125,6 +146,7 @@ function ColorGroup({ color }: { color: string }) { color={color} variant={suffix} state={state} + copyMode={copyMode} /> ))}

@@ -138,6 +160,7 @@ function ColorGroup({ color }: { color: string }) { function ColorPaletteGrid() { const [filter, setFilter] = useState('all') const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid') + const [copyMode, setCopyMode] = useState<'variable' | 'oklch'>('oklch') const filteredColors = filter === 'all' ? semanticColors @@ -152,7 +175,7 @@ function ColorPaletteGrid() { Color Palette

- Click any color swatch to copy its CSS variable name + Click any color swatch to copy its {copyMode === 'variable' ? 'CSS variable name' : 'OKLCH value'}

@@ -181,10 +204,34 @@ function ColorPaletteGrid() { +
+ Copy format: + + +
+ setFilter(details.value[0])} + options={[ + { label: 'All Colors', value: 'all' }, + ...semanticColors.map((color: string) => ({ + label: color.charAt(0).toUpperCase() + color.slice(1), + value: color, + })) + ]} + size="md" + /> - - - {/* Color Grid/List */} -
+
{filteredColors.map(color => ( - - ))} -
- - {/* Quick Reference */} -
-

- Quick Reference -

- -
-
-

- Color Variants -

-
- {colorVariants.map(({ suffix, label }) => ( -
- {label} - - {suffix || '(default)'} - -
- ))} -
-
- -
-

- State Modifiers -

-
- {stateVariants.map(({ state, label }) => ( -
- {label} - - {state ? `--state-${state}` : '(default)'} - +
+

+ {color} +

+ +
+ {colorVariants.map(({ suffix, label }) => ( +
+

+ {label} +

+
+ {stateVariants.map(({ state, label: stateLabel }) => { + const baseColorVar = `--color-${color}${suffix}` + const computedColor = state === 'disabled' + ? `var(--color-${color}${suffix}-disabled)` + : state !== '' + ? `oklch(from var(${baseColorVar}) calc(l + var(--state-${state})) c h)` + : `var(${baseColorVar})` + + const colorTokenName = state === 'disabled' + ? `--color-${color}${suffix}-disabled` + : state !== '' + ? `--color-${color}${suffix}-${state}` + : `--color-${color}${suffix}` + + return ( +
+
+ +
+
+
+ {state || 'Default'} +
+
+ {colorTokenName} +
+
- ))} -
+ ) + })}
- -
-

- Usage Example -

- - background-color: var(--color-primary-light); - -
-
+ ))} +
+
+ ))} +
) @@ -310,105 +138,3 @@ function ColorPaletteGrid() { export const Default: Story = { render: () => , } - -// Compact view for all colors at once -function CompactColorGrid() { - const [copyMode, setCopyMode] = useState<'variable' | 'oklch'>('variable') - - return ( -
-
-
-

- Color Palette - Compact View -

-

- Click any color swatch to copy its {copyMode === 'variable' ? 'CSS variable name' : 'OKLCH value'} -

-
- Copy format: - - -
-
- -
-
- - - - - {colorVariants.map(({ label }) => ( - - ))} - - - - {colorVariants.map(({ label }) => ( - - {stateVariants.map(({ label: stateLabel }) => ( - - ))} - - ))} - - - - {semanticColors.map(color => ( - - - {colorVariants.map(({ suffix }) => ( - - {stateVariants.map(({ state }) => ( - - ))} - - ))} - - ))} - -
- Color - - {label} -
- {stateLabel} -
- {color} - - -
-
-
-
-
- ) -} - -export const Compact: Story = { - render: () => , -} \ No newline at end of file From 93fac157c9dda894439708f5514898a71092d6cb Mon Sep 17 00:00:00 2001 From: KaiUweCZE Date: Sun, 31 Aug 2025 09:46:33 +0200 Subject: [PATCH 15/44] refactor(ui/tokens): simplify secondary text color system - Remove redundant fg-primary-light and fg-secondary-light tokens - Implement dynamic secondary text using OKLCH color space --- libs/ui/src/tokens/_semantic.css | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/libs/ui/src/tokens/_semantic.css b/libs/ui/src/tokens/_semantic.css index ca869f7ca6..5a140c225d 100644 --- a/libs/ui/src/tokens/_semantic.css +++ b/libs/ui/src/tokens/_semantic.css @@ -11,6 +11,7 @@ --state-disabled-chroma: 0.01; --state-light-variant: 0.1; /* Always make colors lighter for -light variant */ --state-light-chroma: 0.6; /* Reduce saturation for softer, pastel colors */ + --state-secondary-text: 0.15; --state-border: 15%; --max-lightness: 0.97; --opacity-ring: 80%; @@ -26,6 +27,7 @@ :is(.dark, .always-dark) { --state-hover: 0.08; --state-active: 0.12; + --state-secondary-text: -0.15; --surface-increment: -0.05; } @@ -34,6 +36,7 @@ :root:not(.light):not(.always-light) { --state-hover: 0.08; --state-active: 0.12; + --state-secondary-text: -0.15; --surface-increment: -0.05; } } @@ -42,12 +45,14 @@ :is(.light, .always-light) .reverse { --state-hover: 0.08; --state-active: 0.12; + --state-secondary-text: -0.15; --surface-increment: -0.05; } :is(.dark, .always-dark) .reverse { --state-hover: -0.08; --state-active: -0.12; + --state-secondary-text: 0.15; --surface-increment: 0.05; } @@ -56,6 +61,7 @@ .reverse:not(.dark):not(.always-dark):not(.light):not(.always-light) { --state-hover: 0.08; --state-active: 0.12; + --state-secondary-text: -0.15; --surface-increment: -0.05; } } @@ -64,6 +70,7 @@ .reverse:not(.dark):not(.always-dark):not(.light):not(.always-light) { --state-hover: -0.08; --state-active: -0.12; + --state-secondary-text: 0.15; --surface-increment: 0.05; } } @@ -240,14 +247,9 @@ --color-fg-light: var(--color-slate-800); /* Light mode */ --color-fg-dark: var(--color-white); /* Dark mode */ --color-fg-primary: light-dark(var(--color-fg-light), var(--color-fg-dark)); - --color-fg-primary-light: --alpha( - var(--color-fg-primary) / - var(--opacity-text-secondary) - ); - --color-fg-secondary: light-dark(var(--color-gray-900), var(--color-gray-200)); - --color-fg-secondary-light: --alpha( - var(--color-fg-secondary) / - var(--opacity-text-secondary) + + --color-fg-secondary: oklch( + from var(--color-fg-primary) calc(l + var(--state-secondary-text)) c h ); --color-fg-reverse: light-dark(var(--color-fg-dark), var(--color-fg-light)); From a29fa48efcb51a10125410fc15b0127ccd69b537 Mon Sep 17 00:00:00 2001 From: KaiUweCZE Date: Sun, 31 Aug 2025 16:25:51 +0200 Subject: [PATCH 16/44] chore(ui): upgrade tailwind-variants to v3.1.0 --- libs/ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/ui/package.json b/libs/ui/package.json index a9ea288ffe..f584698d77 100644 --- a/libs/ui/package.json +++ b/libs/ui/package.json @@ -68,7 +68,7 @@ "@zag-js/toast": "^1.15.0", "@zag-js/tooltip": "^1.15.0", "@zag-js/tree-view": "^1.15.0", - "tailwind-variants": "^1.0.0" + "tailwind-variants": "^3.1.0" }, "devDependencies": { "@eslint/js": "^9.0.0", From 5e333d6d92ec9ecdcd800c6ee12bbe6e8eef550a Mon Sep 17 00:00:00 2001 From: KaiUweCZE Date: Mon, 1 Sep 2025 05:18:55 +0200 Subject: [PATCH 17/44] "feat(ui/tokens): add layout primitive tokens for responsive design - Add new _layout.css with container widths, aspect ratios, breakpoints - Move aspect from semantic to layout - fix invalid naming: aspect-ratio-* => aspect-* --- libs/ui/src/tokens/_layout.css | 33 +++++++++++++++++++ libs/ui/src/tokens/_semantic.css | 6 ---- .../tokens/components/molecules/_carousel.css | 2 +- .../components/molecules/_product-card.css | 2 +- libs/ui/src/tokens/index.css | 1 + 5 files changed, 36 insertions(+), 8 deletions(-) create mode 100644 libs/ui/src/tokens/_layout.css diff --git a/libs/ui/src/tokens/_layout.css b/libs/ui/src/tokens/_layout.css new file mode 100644 index 0000000000..c1c1c1011f --- /dev/null +++ b/libs/ui/src/tokens/_layout.css @@ -0,0 +1,33 @@ +@theme static { + /* === BREAKPOINTS === */ + --breakpoint-xs: 30rem; + --breakpoint-sm: 40rem; + --breakpoint-md: 48rem; + --breakpoint-lg: 64rem; + --breakpoint-xl: 80rem; + --breakpoint-2xl: 96rem; + + /* === RESPONSIVE CONTAINER WIDTHS === */ + /* Main layout containers - responsive max-widths */ + --spacing-container-3xs: 20rem; + --spacing-container-2xs: 24rem; /* Small screens */ + --spacing-container-xs: 28rem; /* Small screens */ + --spacing-container-sm: 32rem; /* Medium screens */ + --spacing-container-md: 36rem; /* Large screens */ + --spacing-container-lg: 42rem; /* Extra large screens */ + --spacing-container-xl: 48rem; /* 2X large screens */ + --spacing-container-2xl: 56rem; /* Ultra wide screens */ + --spacing-container-3xl: 64rem; /* Ultra wide screens */ + --spacing-container-4xl: 72rem; + --spacing-container-max: 80rem; + --spacing-container-prose: 65ch; /* Optimal reading width */ + + /* === ASPECT RATIOS === */ + --aspect-square: 1; + --aspect-video: 16 / 9; + --aspect-portrait: 3 / 4; + --aspect-landscape: 4 / 3; + --aspect-wide: 21 / 9; + --aspect-card: 3 / 2; + --aspect-golden: 1.618; +} diff --git a/libs/ui/src/tokens/_semantic.css b/libs/ui/src/tokens/_semantic.css index 5a140c225d..76728fcbb8 100644 --- a/libs/ui/src/tokens/_semantic.css +++ b/libs/ui/src/tokens/_semantic.css @@ -334,10 +334,4 @@ /* Focus ring */ --color-ring-primary: --alpha(var(--color-primary) / var(--opacity-ring)); - - /* Aspect ratio */ - --aspect-ratio-square: 1 / 1; - --aspect-ratio-portrait: 3 / 4; - --aspect-ratio-landscape: 4 / 3; - --aspect-ratio-16-9: 16 / 9; } diff --git a/libs/ui/src/tokens/components/molecules/_carousel.css b/libs/ui/src/tokens/components/molecules/_carousel.css index 45d4ddbb89..9b846d56e7 100644 --- a/libs/ui/src/tokens/components/molecules/_carousel.css +++ b/libs/ui/src/tokens/components/molecules/_carousel.css @@ -17,7 +17,7 @@ /* === INDICATOR === */ --gap-carousel-indicator: var(--spacing-100); --color-carousel-indicator-active: var(--color-primary); - --aspect-carousel-indicator: var(--aspect-ratio-square); + --aspect-carousel-indicator: var(--aspect-square); --width-carousel-indicator: var(--spacing-200); /* === FOCUS STATES === */ diff --git a/libs/ui/src/tokens/components/molecules/_product-card.css b/libs/ui/src/tokens/components/molecules/_product-card.css index 64e595653b..389133876c 100644 --- a/libs/ui/src/tokens/components/molecules/_product-card.css +++ b/libs/ui/src/tokens/components/molecules/_product-card.css @@ -49,7 +49,7 @@ --color-btn-wishlist-fg: var(--color-btn-text); /* === PRODUCT CARD IMAGE === */ - --aspect-pc-image: var(--aspect-ratio-square); + --aspect-pc-image: var(--aspect-square); --radius-pc-image: var(--radius-pc); } diff --git a/libs/ui/src/tokens/index.css b/libs/ui/src/tokens/index.css index 4b1102291b..3a23727268 100644 --- a/libs/ui/src/tokens/index.css +++ b/libs/ui/src/tokens/index.css @@ -6,6 +6,7 @@ @import "./_colors.css"; @import "./_spacing.css"; @import "./_typography.css"; +@import "./_layout.css"; /* Semantic tokens (dependent on primitives) */ @import "./_semantic.css"; From e40b6938c23029071609c1da2bf11e438b155c98 Mon Sep 17 00:00:00 2001 From: KaiUweCZE Date: Mon, 1 Sep 2025 05:26:40 +0200 Subject: [PATCH 18/44] "refactor(ui/stories): simplify color palette story implementation - Remove interactive filter with Select component - Remove useState hook and filter logic - Convert to static color grid display - Simplify component to pure presentation without controls" --- .../templates/color-palette.stories.tsx | 45 ++----------------- 1 file changed, 4 insertions(+), 41 deletions(-) diff --git a/libs/ui/stories/templates/color-palette.stories.tsx b/libs/ui/stories/templates/color-palette.stories.tsx index d7f40a60bf..257a1809be 100644 --- a/libs/ui/stories/templates/color-palette.stories.tsx +++ b/libs/ui/stories/templates/color-palette.stories.tsx @@ -1,6 +1,4 @@ import type { Meta, StoryObj } from '@storybook/react' -import { useState } from 'react' -import { Select } from '../../src/molecules/select' import '../../src/tokens/_colors.css' import '../../src/tokens/_semantic.css' import { ColorSelect } from '../../src/atoms/color-select' @@ -38,45 +36,14 @@ const stateVariants = [ ] as const -function ColorPaletteGrid() { - const [filter, setFilter] = useState('all') - const filteredColors = filter === 'all' - ? semanticColors - : semanticColors.filter(c => c === filter) - - return ( +export const Default: Story = { + render: () => (
- {/* Header */} -
-

- Color Palette -

-
- - {/* Controls */} -
-
- +
- +
- +
), @@ -222,11 +235,11 @@ export const CustomTrigger: Story = {

This popover uses a custom trigger with an icon.

- + -

Click this custom area

+
+

Click this custom area

} triggerClassName="hover:border-primary focus:border-primary" @@ -256,13 +269,16 @@ export const Modal: Story = { trigger: 'Open Modal Popover', modal: true, title: 'Modal Popover', - description: 'This popover acts as a modal - it traps focus and blocks interactions outside.', + description: + 'This popover acts as a modal - it traps focus and blocks interactions outside.', showCloseButton: true, closeOnInteractOutside: false, children: (

Try clicking outside - it won't close!

-

Press Escape or use the close button to dismiss.

+

+ Press Escape or use the close button to dismiss. +

), }, @@ -272,18 +288,18 @@ export const AsyncContent: Story = { render: () => { const [loading, setLoading] = useState(false) const [data, setData] = useState(null) - + const loadData = async () => { setLoading(true) setData(null) - + // Simulate API call - await new Promise(resolve => setTimeout(resolve, 1500)) - + await new Promise((resolve) => setTimeout(resolve, 1500)) + setData('Data loaded successfully!') setLoading(false) } - + return ( -
+
{loading ? (
- + Loading...
) : data ? (
- +

{data}

) : ( @@ -315,14 +340,10 @@ export const AsyncContent: Story = { export const NestedPopovers: Story = { render: () => ( - +

This is the first level popover.

- +

This is nested inside the first popover.

- + +

This popover contains a lot of content that might need scrolling.

- + {Array.from({ length: 10 }, (_, i) => ( -
+

Section {i + 1}

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit. - Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. +

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do + eiusmod tempor incididunt ut labore et dolore magna aliqua.

))} - -

End of content

+ +

End of content

), }, @@ -381,10 +402,10 @@ export const NonPortalled: Story = { children: (

This popover is rendered in place, not in a portal.

-

+

Useful when you need the popover to be clipped by a parent container.

), }, -} \ No newline at end of file +} diff --git a/libs/ui/stories/molecules/steps.stories.tsx b/libs/ui/stories/molecules/steps.stories.tsx index 97e60c2c3f..6e70610ef3 100644 --- a/libs/ui/stories/molecules/steps.stories.tsx +++ b/libs/ui/stories/molecules/steps.stories.tsx @@ -1,8 +1,7 @@ import type { Meta, StoryObj } from '@storybook/react' import { useState } from 'react' -import { Steps } from '../../src/molecules/steps' import { Select } from '../../src/molecules/select' -import { Button } from '../../src/atoms/button' +import { Steps } from '../../src/molecules/steps' const meta: Meta = { title: 'Molecules/Steps', @@ -133,10 +132,10 @@ const stepsWithCustomContent = [ value: 0, title: 'Account', content: ( -
-

Create your account

+
+

Create your account

This is custom content for the account step.

-
@@ -151,11 +150,18 @@ const stepsWithCustomContent = [ value: 2, title: 'Settings', content: ( -
+

Custom Settings Form

-
), diff --git a/libs/ui/stories/molecules/toast.stories.tsx b/libs/ui/stories/molecules/toast.stories.tsx index ef649283da..db7402096d 100644 --- a/libs/ui/stories/molecules/toast.stories.tsx +++ b/libs/ui/stories/molecules/toast.stories.tsx @@ -288,19 +288,19 @@ export const PauseResumeExample: Story = { {toastId && ( + size="sm" + variant="secondary" + onClick={() => { + if (isPaused) { + toaster.resume(toastId) + } else { + toaster.pause(toastId) + } + setIsPaused(!isPaused) + }} + > + {isPaused ? 'Resume Timer' : 'Pause Timer'} + )}
diff --git a/libs/ui/stories/molecules/tree-view.stories.tsx b/libs/ui/stories/molecules/tree-view.stories.tsx index 7c73e8b65d..418779b62d 100644 --- a/libs/ui/stories/molecules/tree-view.stories.tsx +++ b/libs/ui/stories/molecules/tree-view.stories.tsx @@ -457,8 +457,7 @@ export const TypeaheadDebugDemo: Story = { { id: 'beta', name: 'Beta' }, { id: 'gamma', name: 'Gamma' }, ] - const handleKeyDown = (_e: React.KeyboardEvent) => { - } + const handleKeyDown = (_e: React.KeyboardEvent) => {} return (
diff --git a/libs/ui/stories/overview/color-palette.stories.tsx b/libs/ui/stories/overview/color-palette.stories.tsx index c7b736dac2..2c0eefeaa7 100644 --- a/libs/ui/stories/overview/color-palette.stories.tsx +++ b/libs/ui/stories/overview/color-palette.stories.tsx @@ -15,7 +15,7 @@ type Story = StoryObj const semanticColors = [ 'primary', - 'secondary', + 'secondary', 'tertiary', 'info', 'success', @@ -35,68 +35,70 @@ const stateVariants = [ { state: 'disabled', label: 'Disabled' }, ] as const - - export const Default: Story = { render: () => (
-
- -
- {semanticColors.map(color => ( -
-

- {color} -

- -
- {colorVariants.map(({ suffix, label }) => ( -
-

- {label} -

-
- {stateVariants.map(({ state, label: stateLabel }) => { - const baseColorVar = `--color-${color}${suffix}` - const computedColor = state === 'disabled' - ? `var(--color-${color}${suffix}-disabled)` - : state !== '' - ? `oklch(from var(${baseColorVar}) calc(l + var(--state-${state})) c h)` - : `var(${baseColorVar})` +
+
+ {semanticColors.map((color) => ( +
+

+ {color} +

- const colorTokenName = state === 'disabled' - ? `--color-${color}${suffix}-disabled` - : state !== '' - ? `--color-${color}${suffix}-${state}` - : `--color-${color}${suffix}` +
+ {colorVariants.map(({ suffix, label }) => ( +
+

+ {label} +

+
+ {stateVariants.map(({ state, label: stateLabel }) => { + const baseColorVar = `--color-${color}${suffix}` + const computedColor = + state === 'disabled' + ? `var(--color-${color}${suffix}-disabled)` + : state !== '' + ? `oklch(from var(${baseColorVar}) calc(l + var(--state-${state})) c h)` + : `var(${baseColorVar})` - return ( -
-
- -
-
-
- {state || 'Default'} -
-
- {colorTokenName} -
+ const colorTokenName = + state === 'disabled' + ? `--color-${color}${suffix}-disabled` + : state !== '' + ? `--color-${color}${suffix}-${state}` + : `--color-${color}${suffix}` + + return ( +
+
+ +
+
+
+ {state || 'Default'} +
+
+ {colorTokenName} +
+
+
+ ) + })}
- ) - })} + ))} +
-
- ))} -
-
))} -
+
), diff --git a/libs/ui/stories/templates/comprehensive-form.stories.tsx b/libs/ui/stories/templates/comprehensive-form.stories.tsx index 7f553dde73..e171d28694 100644 --- a/libs/ui/stories/templates/comprehensive-form.stories.tsx +++ b/libs/ui/stories/templates/comprehensive-form.stories.tsx @@ -4,14 +4,14 @@ import { VariantContainer, VariantGroup } from '../../.storybook/decorator' import { ErrorText } from '../../src/atoms/error-text' import { ExtraText } from '../../src/atoms/extra-text' import { Label } from '../../src/atoms/label' +import { NumericInput } from '../../src/atoms/numeric-input' import { Textarea } from '../../src/atoms/textarea' import { Combobox } from '../../src/molecules/combobox' import { FormCheckbox } from '../../src/molecules/form-checkbox' import { FormInput } from '../../src/molecules/form-input' -import { NumericInput } from '../../src/atoms/numeric-input' +import { RangeSlider } from '../../src/molecules/range-slider' import { Select } from '../../src/molecules/select' import { Switch } from '../../src/molecules/switch' -import {RangeSlider} from '../../src/molecules/range-slider' const meta: Meta = { title: 'Templates/Comprehensive Form', From 2b8fa55704df683a8c70a7f8a2fe29c792ba82d2 Mon Sep 17 00:00:00 2001 From: KaiUweCZE Date: Thu, 4 Sep 2025 04:02:34 +0200 Subject: [PATCH 23/44] refactor(ui/tokens): remove deprecated spacing aliases --- libs/ui/src/tokens/_spacing.css | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/libs/ui/src/tokens/_spacing.css b/libs/ui/src/tokens/_spacing.css index c44ab05de9..675580caee 100644 --- a/libs/ui/src/tokens/_spacing.css +++ b/libs/ui/src/tokens/_spacing.css @@ -19,15 +19,4 @@ --spacing-850: clamp(5.625rem, 5.4076rem + 1.087vw, 6.25rem); --spacing-900: clamp(6.1875rem, 5.9484rem + 1.1957vw, 6.875rem); --spacing-950: clamp(6.75rem, 6.4891rem + 1.3043vw, 7.5rem); - - /* Legacy aliases for backward compatibility */ - --spacing-3xs: var(--spacing-100); /* 4px */ - --spacing-2xs: var(--spacing-150); /* 8px */ - --spacing-xs: var(--spacing-200); /* 12px */ - --spacing-sm: var(--spacing-250); /* 16px */ - --spacing-md: var(--spacing-350); /* 24px */ - --spacing-lg: var(--spacing-450); /* 32px */ - --spacing-xl: var(--spacing-600); /* 48px */ - --spacing-2xl: var(--spacing-750); /* 64px */ - --spacing-3xl: var(--spacing-950); /* 96px */ } From d875d528a7bd6eb19c67bea0116532c2d8e89d8a Mon Sep 17 00:00:00 2001 From: KaiUweCZE Date: Thu, 4 Sep 2025 04:16:44 +0200 Subject: [PATCH 24/44] refactor: eliminate duplicates between CLAUDE.md and token-contribution.md --- libs/ui/CLAUDE.md | 130 ++++------------------------------ libs/ui/token-contribution.md | 124 ++++++++++++++++++++++++++------ 2 files changed, 113 insertions(+), 141 deletions(-) diff --git a/libs/ui/CLAUDE.md b/libs/ui/CLAUDE.md index a8ca16781b..36e2a8b74f 100644 --- a/libs/ui/CLAUDE.md +++ b/libs/ui/CLAUDE.md @@ -44,134 +44,28 @@ libs/ui/src/ 5. **Story Creation** covering all variants and states -## Token System Rules (MANDATORY) +## Token System Rules -### Token File Structure +See comprehensive token documentation in [`token-contribution.md`](./token-contribution.md) -**ALL component token files MUST follow this exact structure:** +### Key Points: +- **Two-layer token strategy**: Reference layer → Derived tokens +- **Strict naming conventions**: `--[prefix]-[component]-[part?]-[property]-[state?]` +- **Required suffixes**: All colors must have `-bg`, `-fg`, or `-border` +- **No abbreviations**: Use full names (`button` not `btn`) +- **Validation tools**: Run `pnpm validate:tokens` to check compliance -```css -/* Only for opacity values or calculations */ -:root { - --opacity-component-specific: 50%; -} - -@theme static { - /* === BASE COLOR MAPPING === */ - /* Reference layer - single source of truth for theming */ - --color-component-primary: var(--color-primary); - --color-component-secondary: var(--color-secondary); - --color-component-base: var(--color-surface); - - /* === DERIVED COLORS === */ - /* Background colors - using reference layer */ - --color-component-bg: var(--color-component-base); - --color-component-bg-primary: var(--color-component-primary); - - /* Foreground colors */ - --color-component-fg: var(--color-fg-primary); - --color-component-fg-primary: var(--color-fg-reverse); - - /* Border colors */ - --color-component-border: var(--color-border-primary); - --color-component-border-primary: var(--color-component-primary); - - /* === STATE VARIATIONS === */ - --color-component-bg-hover: oklch(from var(--color-component-bg) calc(l + var(--state-hover)) c h); - --color-component-bg-primary-hover: oklch(from var(--color-component-bg-primary) calc(l + var(--state-hover)) c h); - - /* === COMPONENT VARIANTS === */ - /* Light, outlined, borderless variants as needed */ - - /* === VALIDATION STATES === */ - --color-component-bg-danger: var(--color-danger); - --color-component-bg-success: var(--color-success); - - /* === DISABLED STATES === */ - --color-component-bg-disabled: var(--color-disabled-bg); - - /* === SPACING === */ - --spacing-component-padding: var(--spacing-250); - --spacing-component-gap: var(--spacing-200); - - /* === TYPOGRAPHY === */ - --text-component-sm: var(--text-sm); - --text-component-md: var(--text-md); - - /* === BORDERS & RADIUS === */ - --border-width-component: var(--border-width-sm); - --radius-component: var(--radius-md); - - /* === FOCUS RINGS === */ - --color-component-ring-primary: --alpha(var(--color-component-primary) / var(--opacity-ring)); -} -``` - -### Naming Convention Authority - -**Mandatory Pattern**: `--[prefix]-[component]-[part?]-[property]-[state?]` - -**Required Prefixes**: -- `--color-`: ALL colors (never without suffix) -- `--spacing-`: Spacing including width/height needing max/min variants -- `--text-`: Font sizes only -- `--border-`: Border properties -- `--radius-`: Border radius -- `--shadow-`: Box shadows -- `--opacity-`: Transparency values - -**Required Color Suffixes**: -- `-bg`: Background colors (MANDATORY - never `--color-button`) -- `-fg`: Foreground/text colors (never `-text`) -- `-border`: Border colors -- `-hover`: Hover states -- `-active`: Active states -- `-disabled`: Disabled states - -**Component Naming Rules**: -```css -/* ✅ CORRECT - Consistent with semantic tokens */ ---color-button-bg-primary /* Matches --color-bg-primary pattern */ ---color-accordion-fg-title /* Matches --color-fg-primary pattern */ ---color-product-card-bg /* Base background */ - -/* ❌ FORBIDDEN */ ---color-btn-primary /* Use 'button' not 'btn' */ ---color-pc-bg /* Use 'product-card' not 'pc' */ ---color-button /* Missing -bg suffix */ ---color-button-text /* Use -fg not -text */ ---color-button-primary-bg /* Wrong order - should be -bg-primary */ -``` - -### Two-Layer Token Strategy - -**ALWAYS** implement reference layer for theming: - -```css -/* === BASE COLOR MAPPING === */ -/* Reference layer - allows easy theming */ ---color-component-primary: var(--color-primary); ---color-component-secondary: var(--color-secondary); - -/* === DERIVED COLORS === */ -/* Actual usage tokens */ ---color-component-bg-primary: var(--color-component-primary); ---color-component-fg-primary: var(--color-fg-reverse); -``` - -**Benefits**: One-line component theme changes, consistent theming patterns. - -### Token Usage in Components +### Quick Token Usage in Components: ```typescript // ✅ CORRECT - Use Tailwind classes that reference your tokens 'bg-button-bg-primary' // Uses --color-button-bg-primary 'text-button-fg-primary' // Uses --color-button-fg-primary -'p-button-sm' // Uses --spacing-button-sm +'p-button-padding' // Uses --spacing-button-padding // ❌ WRONG - Never use arbitrary values -'bg-[var(--color-button-bg-primary)]' -'p-[var(--spacing-button-sm)]' +'bg-[var(--color-button-bg-primary)]' // Don't use arbitrary values +'p-[1rem]' // Use token classes instead ``` ## Component Implementation Patterns diff --git a/libs/ui/token-contribution.md b/libs/ui/token-contribution.md index 4ac45f49ff..a6a0d86253 100644 --- a/libs/ui/token-contribution.md +++ b/libs/ui/token-contribution.md @@ -1,3 +1,41 @@ +## 🚀 QUICK REFERENCE + +### Essential Rules +1. **All colors need suffixes**: `-bg`, `-fg`, `-border` (never just `--color-button`) +2. **Two-layer strategy**: Reference layer → Derived tokens +3. **No abbreviations**: `button` not `btn`, `product-card` not `pc` +4. **Follow the pattern**: `--[prefix]-[component]-[part?]-[property]-[state?]` + +### Validation +```bash +pnpm validate:tokens # Check token naming compliance +pnpm check:unused-tokens # Find unused tokens +``` + +### Real Example (Button) +```css +/* Optional: only if needed */ +:root { + --opacity-outlined-hover: 16%; +} + +@theme static { + /* Reference layer first */ + --color-button-primary: var(--color-primary); + --color-button-secondary: var(--color-secondary); + + /* Then derived tokens */ + --color-button-bg-primary: var(--color-button-primary); + --color-button-fg-primary: var(--color-fg-reverse); + + /* Spacing - separate single and composite */ + --spacing-button-sm: var(--spacing-150); + --padding-button-sm: var(--spacing-150) var(--spacing-250); +} +``` + +--- + 🎯 BASIC PRINCIPLES 1. Predictability Over Brevity @@ -67,11 +105,11 @@ Component Naming Rules /* === COMPONENT TOKEN FILE STRUCTURE === */ -/* 1. LOCAL VARIABLES */ -/* Only for opacity values or complex calculations */ +/* 1. LOCAL VARIABLES (OPTIONAL) */ +/* Only when needed for opacity values or complex calculations */ :root { - --opacity-component-specific: 50%; - --custom-calculation: calc(var(--spacing-base) * 2); + --opacity-outlined-hover: 16%; + --opacity-outlined-active: 12%; } /* 2. THEME TOKENS */ @@ -140,19 +178,22 @@ Derived Colors /* === DERIVED COLORS === */ /* Background colors - using reference layer */ ---color-component-bg: var(--color-component-base); ---color-component-bg-primary: var(--color-component-primary); ---color-component-bg-secondary: var(--color-component-secondary); +--color-button-bg-primary: var(--color-button-primary); +--color-button-bg-secondary: var(--color-button-secondary); +--color-button-bg-danger: var(--color-button-danger); -/* Foreground colors */ ---color-component-fg: var(--color-fg-primary); ---color-component-fg-primary: var(--color-fg-reverse); ---color-component-fg-secondary: var(--color-fg-reverse); +/* Foreground colors - can have shared base */ +--color-button-fg: var(--color-fg-reverse); /* Shared base */ +--color-button-fg-primary: var(--color-button-fg); +--color-button-fg-secondary: var(--color-button-fg); +--color-button-fg-danger: var(--color-button-fg); /* Border colors */ ---color-component-border: var(--color-border-primary); ---color-component-border-primary: var(--color-component-primary); ---color-component-border-secondary: var(--color-component-secondary); +--color-button-border-primary: var(--color-button-primary); +--color-button-border-secondary: var(--color-button-secondary); + +/* Aliases for special cases */ +--color-badge-bg-discount: var(--color-badge-danger); /* Alias */ State Calculations @@ -171,18 +212,21 @@ State Calculations --- 📏 SPACING & SIZING TOKENS -Spacing Naming +Spacing Patterns /* === SPACING === */ -/* Use semantic spacing tokens */ ---spacing-component-padding: var(--spacing-250); /* Generic padding */ ---spacing-component-padding-sm: var(--spacing-200); /* Small variant */ ---spacing-component-padding-lg: var(--spacing-350); /* Large variant */ ---spacing-component-gap: var(--spacing-200); /* Gap between elements */ ---spacing-component-margin: var(--spacing-150); /* External margin */ +/* Single spacing values for uniform padding */ +--spacing-button-sm: var(--spacing-150); /* Small size */ +--spacing-button-md: var(--spacing-200); /* Medium size */ +--spacing-button-lg: var(--spacing-250); /* Large size */ -/* Composite spacing (multiple values) */ ---spacing-component-padding-composite: var(--spacing-200) var(--spacing-350); +/* Composite padding (vertical horizontal) */ +--padding-button-sm: var(--spacing-150) var(--spacing-250); +--padding-button-md: var(--spacing-200) var(--spacing-350); +--padding-button-lg: var(--spacing-250) var(--spacing-450); + +/* Simple composite for badges */ +--padding-badge: var(--spacing-100) var(--spacing-100); Typography Tokens @@ -219,6 +263,40 @@ Component Variants --color-component-border-borderless: transparent; --color-component-bg-borderless-hover: var(--color-fill-hover); +--- +🔧 COMPONENT INTEGRATION + +Using Tokens in Components + +```typescript +// With tailwind-variants (tv) - Real button example +const buttonVariants = tv({ + base: 'transition-colors font-medium', + variants: { + variant: { + primary: 'bg-button-bg-primary text-button-fg-primary hover:bg-button-bg-primary-hover', + secondary: 'bg-button-bg-secondary text-button-fg-secondary hover:bg-button-bg-secondary-hover', + outlined: 'bg-transparent border-button-border-primary text-button-fg-outlined-primary' + }, + size: { + sm: 'p-button-sm text-button-sm rounded-button-sm', + md: 'p-button-md text-button-md rounded-button-md', + lg: 'p-button-lg text-button-lg rounded-button-lg' + } + } +}) +``` + +State-Based Styling + +```typescript +// Use data attributes for dynamic states +'data-[state=open]:bg-button-bg-open' +'data-[highlighted]:bg-button-bg-hover' +'data-[disabled]:opacity-button-disabled' +'data-[validation=error]:border-button-border-danger' +``` + --- 🚨 VALIDATION RULES From f20e35e4da27114d5a243392d430c48031823386 Mon Sep 17 00:00:00 2001 From: KaiUweCZE Date: Thu, 4 Sep 2025 04:17:20 +0200 Subject: [PATCH 25/44] remove unused font-weight --- libs/ui/src/tokens/_typography.css | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/ui/src/tokens/_typography.css b/libs/ui/src/tokens/_typography.css index 64adcd233c..b90147e7d8 100644 --- a/libs/ui/src/tokens/_typography.css +++ b/libs/ui/src/tokens/_typography.css @@ -10,7 +10,6 @@ --text-4xl: clamp(2.7994rem, 2.4462rem + 1.7658vw, 3.8147rem); /* 44.79px - 61.04px */ /* === TEXT WEIGHT === */ - --font-weight-light: 100; --font-weight-normal: 400; --font-weight-medium: 500; --font-weight-semibold: 600; From 5d80bae9fbb2bd6c502773474ddbb4aa46a5051c Mon Sep 17 00:00:00 2001 From: KaiUweCZE Date: Thu, 4 Sep 2025 09:00:40 +0200 Subject: [PATCH 26/44] small edits in layout.css --- libs/ui/src/tokens/_layout.css | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/libs/ui/src/tokens/_layout.css b/libs/ui/src/tokens/_layout.css index c1c1c1011f..888eb38055 100644 --- a/libs/ui/src/tokens/_layout.css +++ b/libs/ui/src/tokens/_layout.css @@ -7,25 +7,26 @@ --breakpoint-xl: 80rem; --breakpoint-2xl: 96rem; - /* === RESPONSIVE CONTAINER WIDTHS === */ - /* Main layout containers - responsive max-widths */ - --spacing-container-3xs: 20rem; - --spacing-container-2xs: 24rem; /* Small screens */ - --spacing-container-xs: 28rem; /* Small screens */ - --spacing-container-sm: 32rem; /* Medium screens */ - --spacing-container-md: 36rem; /* Large screens */ - --spacing-container-lg: 42rem; /* Extra large screens */ - --spacing-container-xl: 48rem; /* 2X large screens */ - --spacing-container-2xl: 56rem; /* Ultra wide screens */ - --spacing-container-3xl: 64rem; /* Ultra wide screens */ - --spacing-container-4xl: 72rem; - --spacing-container-max: 80rem; - --spacing-container-prose: 65ch; /* Optimal reading width */ + /* === CONTAINER SIZES === */ + --container-3xs: 16rem; + --container-2xs: 18rem; + --container-xs: 20rem; + --container-sm: 24rem; + --container-md: 28rem; + --container-lg: 32rem; + --container-xl: 36rem; + --container-2xl: 42rem; + --container-3xl: 48rem; + --container-4xl: 56rem; + --container-5xl: 64rem; + --container-6xl: 72rem; + --container-max: 80rem; + --container-prose: 65ch; /* === ASPECT RATIOS === */ --aspect-square: 1; --aspect-video: 16 / 9; - --aspect-portrait: 3 / 4; + --aspect-portrait: 9 / 16; --aspect-landscape: 4 / 3; --aspect-wide: 21 / 9; --aspect-card: 3 / 2; From c00cec24b44d47dfce3c6eb98ec2d08ef1d69673 Mon Sep 17 00:00:00 2001 From: Petr Glaser Date: Fri, 5 Sep 2025 21:49:05 +0200 Subject: [PATCH 27/44] fix: fix legacy tokens --- libs/ui/scripts/validate-token-definitions.js | 6 +++--- libs/ui/scripts/validate-token-usage.js | 6 +++--- libs/ui/src/molecules/dialog.tsx | 2 +- libs/ui/src/molecules/form-checkbox.tsx | 2 +- libs/ui/src/molecules/form-numeric-input.tsx | 2 +- libs/ui/src/molecules/search-form.tsx | 2 +- libs/ui/src/tokens/components/molecules/_pagination.css | 1 + 7 files changed, 11 insertions(+), 10 deletions(-) diff --git a/libs/ui/scripts/validate-token-definitions.js b/libs/ui/scripts/validate-token-definitions.js index e81915adbb..8fa0340edd 100755 --- a/libs/ui/scripts/validate-token-definitions.js +++ b/libs/ui/scripts/validate-token-definitions.js @@ -8,7 +8,7 @@ */ import fs from 'fs' -import { glob } from 'glob' +import { globSync } from 'glob' // Configuration for validation const CONFIG = { @@ -74,7 +74,7 @@ function parseTokensFromFile(filePath) { */ function loadAllTokens() { const allTokens = new Map() - const tokenFiles = glob.sync('src/tokens/components/**/*.css') + const tokenFiles = globSync('src/tokens/components/**/*.css') for (const file of tokenFiles) { const tokens = parseTokensFromFile(file) @@ -488,7 +488,7 @@ function validateTokenDefinitions() { // Cache component files content for reuse const componentFilesCache = new Map() - const componentFiles = glob.sync('src/**/*.{ts,tsx}', { + const componentFiles = globSync('src/**/*.{ts,tsx}', { ignore: CONFIG.excludeFiles, }) diff --git a/libs/ui/scripts/validate-token-usage.js b/libs/ui/scripts/validate-token-usage.js index 9be948b3e3..2029eea4b1 100755 --- a/libs/ui/scripts/validate-token-usage.js +++ b/libs/ui/scripts/validate-token-usage.js @@ -8,7 +8,7 @@ */ import fs from 'fs' -import { glob } from 'glob' +import { globSync } from 'glob' // Tailwind v4 namespace to utility prefix mappings const NAMESPACE_MAPPINGS = { @@ -290,7 +290,7 @@ function mapClassToPossibleTokens(className) { */ function loadDefinedTokens() { const tokens = new Set() - const tokenFiles = glob.sync('src/tokens/**/*.css') + const tokenFiles = globSync('src/tokens/**/*.css') for (const file of tokenFiles) { const content = fs.readFileSync(file, 'utf8') @@ -326,7 +326,7 @@ function validateTokenUsage() { const definedTokens = loadDefinedTokens() console.log(`📋 Found ${definedTokens.size} defined tokens`) - const componentFiles = glob.sync('src/**/*.{ts,tsx}', { + const componentFiles = globSync('src/**/*.{ts,tsx}', { ignore: ['**/*.stories.tsx', '**/*.test.tsx', '**/*.spec.tsx'], }) diff --git a/libs/ui/src/molecules/dialog.tsx b/libs/ui/src/molecules/dialog.tsx index 5023b9b400..eece2249eb 100644 --- a/libs/ui/src/molecules/dialog.tsx +++ b/libs/ui/src/molecules/dialog.tsx @@ -20,7 +20,7 @@ const dialogVariants = tv({ description: ['text-dialog-description-size text-dialog-description-fg'], trigger: [], closeTrigger: [ - 'absolute top-sm right-sm', + 'absolute top-250 right-250', 'flex items-center justify-center', 'p-dialog-close-trigger-padding rounded-dialog-close-trigger', 'text-dialog-close-trigger-fg', diff --git a/libs/ui/src/molecules/form-checkbox.tsx b/libs/ui/src/molecules/form-checkbox.tsx index c1c5385930..824c8857e6 100644 --- a/libs/ui/src/molecules/form-checkbox.tsx +++ b/libs/ui/src/molecules/form-checkbox.tsx @@ -29,7 +29,7 @@ export function FormCheckboxRaw({ const extraTextId = extraText ? `${id}-extra` : undefined return ( -
+
diff --git a/libs/ui/src/molecules/form-numeric-input.tsx b/libs/ui/src/molecules/form-numeric-input.tsx index ae08116648..9b3c4f7c67 100644 --- a/libs/ui/src/molecules/form-numeric-input.tsx +++ b/libs/ui/src/molecules/form-numeric-input.tsx @@ -28,7 +28,7 @@ export function FormNumericInputRaw({ const extraTextId = extraText ? `${id}-extra` : undefined return ( -
+
diff --git a/libs/ui/src/molecules/search-form.tsx b/libs/ui/src/molecules/search-form.tsx index b578e97e7e..6e42a24592 100644 --- a/libs/ui/src/molecules/search-form.tsx +++ b/libs/ui/src/molecules/search-form.tsx @@ -43,7 +43,7 @@ const buttonVariants = tv({ base: [ 'absolute', 'justify-self-end place-self-center', - 'h-full rounded-none px-xs py-xs', + 'h-full rounded-none p-200', ], }) diff --git a/libs/ui/src/tokens/components/molecules/_pagination.css b/libs/ui/src/tokens/components/molecules/_pagination.css index 8b7ad11c1d..13f0f6be2b 100644 --- a/libs/ui/src/tokens/components/molecules/_pagination.css +++ b/libs/ui/src/tokens/components/molecules/_pagination.css @@ -12,6 +12,7 @@ /* Text colors */ --color-pagination-fg: var(--color-fg-primary); + --color-pagination-fg-hover: var(--color-pagination-fg); --color-pagination-fg-active: var(--color-pagination-fg); --color-pagination-outlined-fg-active: var(--color-pagination-active); --color-pagination-fg-disabled: --alpha( From 60e11a089d241ff99929edc0797c5b9dba4c6136 Mon Sep 17 00:00:00 2001 From: Petr Glaser Date: Fri, 5 Sep 2025 22:32:50 +0200 Subject: [PATCH 28/44] fix: fix coderabbit recommendations --- libs/ui/scripts/validate-token-definitions.js | 4 +- libs/ui/scripts/validate-token-usage.js | 40 ++++++++++--------- libs/ui/src/atoms/numeric-input.tsx | 2 +- libs/ui/src/atoms/tooltip.tsx | 8 +++- .../tokens/components/molecules/_select.css | 7 +--- .../overview/color-palette.stories.tsx | 4 +- 6 files changed, 34 insertions(+), 31 deletions(-) diff --git a/libs/ui/scripts/validate-token-definitions.js b/libs/ui/scripts/validate-token-definitions.js index 8fa0340edd..46158a02f5 100755 --- a/libs/ui/scripts/validate-token-definitions.js +++ b/libs/ui/scripts/validate-token-definitions.js @@ -7,7 +7,7 @@ * Analyzes dependency graphs and reference chains to determine actual usage. */ -import fs from 'fs' +import fs from 'node:fs' import { globSync } from 'glob' // Configuration for validation @@ -20,7 +20,7 @@ const CONFIG = { /^--color-warning$/, /^--color-success$/, /^--color-info$/, - /^--spacing-(xs|sm|md|lg|xl)$/, + /^--spacing-\d{2,3}$/, /^--text-(xs|sm|md|lg|xl)$/, /^--radius-(sm|md|lg)$/, // Base system tokens diff --git a/libs/ui/scripts/validate-token-usage.js b/libs/ui/scripts/validate-token-usage.js index 2029eea4b1..05b92ecf27 100755 --- a/libs/ui/scripts/validate-token-usage.js +++ b/libs/ui/scripts/validate-token-usage.js @@ -7,7 +7,9 @@ * Follows Tailwind v4 theme variable namespace rules for precise mapping. */ -import fs from 'fs' +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' import { globSync } from 'glob' // Tailwind v4 namespace to utility prefix mappings @@ -117,9 +119,7 @@ function extractTailwindClasses(content, filePath) { const classes = new Set() // Match className props (string values) - const classNameMatches = content.matchAll( - /className\s*=\s*["'`]([^"'`]+)["'`]/g - ) + const classNameMatches = content.matchAll(/className\s*=\s*["']([^"']+)["']/g) for (const match of classNameMatches) { const classString = match[1] classString.split(/\s+/).forEach((cls) => cls && classes.add(cls.trim())) @@ -202,24 +202,25 @@ function extractTailwindClasses(content, filePath) { * Map Tailwind utility class to possible CSS custom properties */ function mapClassToPossibleTokens(className) { - // Remove state modifiers (hover:, focus:, data-[...]:, etc.) - const baseClass = className - .replace(/^[^:]*:/, '') - .replace(/^data-\[[^\]]+\]:/, '') + // Remove chained state/data prefixes (hover:, focus:, sm:, data-[...]:, etc.) + let baseClass = className + while (/^(?:[a-z-]+:|data-\[[^\]]+\]:)/i.test(baseClass)) { + baseClass = baseClass.replace(/^(?:[a-z-]+:|data-\[[^\]]+\]:)/i, '') + } // Parse utility class: prefix-value (non-greedy matching) // Match known prefixes first, then everything else as value const knownPrefixes = Object.values(NAMESPACE_MAPPINGS).flat() let prefix = null let value = null - + // Normalize negative utilities (-m-*, -mt-*, etc.) + const normalized = baseClass.startsWith('-') ? baseClass.slice(1) : baseClass // Try to match against known prefixes (longest first to handle cases like 'ring-offset') const sortedPrefixes = knownPrefixes.sort((a, b) => b.length - a.length) - for (const knownPrefix of sortedPrefixes) { - if (baseClass.startsWith(`${knownPrefix}-`)) { + if (normalized.startsWith(`${knownPrefix}-`)) { prefix = knownPrefix - value = baseClass.slice(knownPrefix.length + 1) // +1 for the dash + value = normalized.slice(knownPrefix.length + 1) // +1 for the dash break } } @@ -290,10 +291,11 @@ function mapClassToPossibleTokens(className) { */ function loadDefinedTokens() { const tokens = new Set() - const tokenFiles = globSync('src/tokens/**/*.css') + const ROOT = path.resolve(fileURLToPath(new URL('.', import.meta.url)), '..') + const tokenFiles = globSync('src/tokens/**/*.css', { cwd: ROOT }) for (const file of tokenFiles) { - const content = fs.readFileSync(file, 'utf8') + const content = fs.readFileSync(path.join(ROOT, file), 'utf8') // Match CSS custom properties const tokenMatches = content.matchAll(/--([a-z][a-z0-9-]*)\s*:/g) @@ -309,11 +311,11 @@ function loadDefinedTokens() { * Check if a class should be ignored */ function shouldIgnoreClass(className) { - // Remove state modifiers for checking - const baseClass = className - .replace(/^[^:]*:/, '') - .replace(/^data-\[[^\]]+\]:/, '') - + // Remove chained state/data prefixes for checking + let baseClass = className + while (/^(?:[a-z-]+:|data-\[[^\]]+\]:)/i.test(baseClass)) { + baseClass = baseClass.replace(/^(?:[a-z-]+:|data-\[[^\]]+\]:)/i, '') + } return IGNORE_PATTERNS.some((pattern) => pattern.test(baseClass)) } diff --git a/libs/ui/src/atoms/numeric-input.tsx b/libs/ui/src/atoms/numeric-input.tsx index 608580d75a..a15714988b 100644 --- a/libs/ui/src/atoms/numeric-input.tsx +++ b/libs/ui/src/atoms/numeric-input.tsx @@ -25,7 +25,7 @@ const numericInput = tv({ 'flex flex-col h-fit justify-center bg-ni-trigger-container', trigger: [ 'px-ni-trigger-x py-ni-trigger-y', - 'cursor-pointer focus:ring-increment-btn-ring', + 'cursor-pointer focus:ring-increment-btn-ring', ], scrubber: 'absolute inset-0 cursor-ew-resize', }, diff --git a/libs/ui/src/atoms/tooltip.tsx b/libs/ui/src/atoms/tooltip.tsx index 67cf510ff7..df0f0d2dc9 100644 --- a/libs/ui/src/atoms/tooltip.tsx +++ b/libs/ui/src/atoms/tooltip.tsx @@ -106,14 +106,18 @@ export function Tooltip({ }) const api = tooltip.connect(service as tooltip.Service, normalizeProps) - const { positioner, content: contentSlot } = tooltipVariants({ + const { + trigger, + positioner, + content: contentSlot, + } = tooltipVariants({ size, className, }) return ( <> - + {children} diff --git a/libs/ui/src/tokens/components/molecules/_select.css b/libs/ui/src/tokens/components/molecules/_select.css index 60e519329b..6494376c65 100644 --- a/libs/ui/src/tokens/components/molecules/_select.css +++ b/libs/ui/src/tokens/components/molecules/_select.css @@ -14,7 +14,7 @@ --color-select-root-bg: var(--color-select-bg); --gap-select-root: var(--spacing-150); - /* === LABEL === + /* === LABEL === --color-label: var(--color-fg-primary);*/ /* === TRIGGER === */ @@ -22,7 +22,7 @@ --color-select-trigger-border: var(--color-select-border); --color-select-trigger-hover: var(--color-fill-hover); --color-select-trigger-focus: var(--color-primary); - --padding-select-trigger: var(--spacing-100); + --padding-select-trigger: var(--spacing-200); /* === CLEAR TRIGGER === */ --color-select-clear-trigger-fg: var(--color-select-fg); @@ -46,9 +46,6 @@ /* === INDICATOR === */ --color-select-indicator: var(--color-primary); - /* === PADDING === */ - --padding-select-trigger: var(--spacing-200); - /* === TYPOGRAPHY === */ --text-select-trigger: var(--text-md); --text-select-item: var(--text-md); diff --git a/libs/ui/stories/overview/color-palette.stories.tsx b/libs/ui/stories/overview/color-palette.stories.tsx index 2c0eefeaa7..257d4e0359 100644 --- a/libs/ui/stories/overview/color-palette.stories.tsx +++ b/libs/ui/stories/overview/color-palette.stories.tsx @@ -1,7 +1,7 @@ import type { Meta, StoryObj } from '@storybook/react' import '../../src/tokens/_colors.css' import '../../src/tokens/_semantic.css' -import { ColorSelect } from '../../src/atoms/color-select' +import { ColorSelect } from '../../../../apps/frontend-demo/src/components/atoms/color-select' const meta: Meta = { title: 'Overview/Color Palette', @@ -41,7 +41,7 @@ export const Default: Story = {
{semanticColors.map((color) => ( -
+

{color}

From b7693964f6ba26835f5c47b707d6dcd015562ec7 Mon Sep 17 00:00:00 2001 From: Petr Glaser Date: Fri, 5 Sep 2025 22:41:03 +0200 Subject: [PATCH 29/44] feat: improve token usage validation to ignore TW default classes --- libs/ui/scripts/validate-token-usage.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/libs/ui/scripts/validate-token-usage.js b/libs/ui/scripts/validate-token-usage.js index 05b92ecf27..456523c3c8 100755 --- a/libs/ui/scripts/validate-token-usage.js +++ b/libs/ui/scripts/validate-token-usage.js @@ -67,6 +67,15 @@ const NAMESPACE_MAPPINGS = { // Standard Tailwind utilities to ignore (not custom tokens) const IGNORE_PATTERNS = [ + // CSS variables in parentheses - these use CSS variables directly, not design tokens + /^[a-z-]+\([^)]*--[^)]+\)$/, + + // Square bracket notation with CSS variables - ignore these as they use CSS vars directly + /^[a-z-]+\[var\(--[^)]+\)\]$/, + + // Standard positioning values + /^(top|right|bottom|left|inset)-(0|px|0\.5|1|1\.5|2|2\.5|3|3\.5|4|5|6|7|8|9|10|11|12|14|16|20|24|28|32|36|40|44|48|52|56|60|64|72|80|96|auto|full|screen|min|max|fit|end)$/, + // Layout & positioning /^(flex|grid|block|inline|hidden|absolute|relative|fixed|sticky)$/, /^(items|justify|content|self)-(start|end|center|stretch|between|around|evenly)$/, @@ -74,21 +83,24 @@ const IGNORE_PATTERNS = [ /^(order|col|row)-(start|end|\d+)$/, // Standard spacing (without custom tokens) - /^(p|m|gap|w|h|max-w|min-w|max-h|min-h|top|right|bottom|left|inset|space)-(0|px|0\.5|1|1\.5|2|2\.5|3|3\.5|4|5|6|7|8|9|10|11|12|14|16|20|24|28|32|36|40|44|48|52|56|60|64|72|80|96|auto|full|screen|min|max|fit)$/, + /^(p|m|gap|w|h|max-w|min-w|max-h|min-h|top|right|bottom|left|inset|space)-(0|px|0\.5|1|1\.5|2|2\.5|3|3\.5|4|5|6|7|8|9|10|11|12|14|16|20|24|28|32|36|40|44|48|52|56|60|64|72|80|96|auto|full|screen|min|max|fit|end)$/, + + // Margin/padding with directional prefixes + /^(ml|mr|mt|mb|mx|my|pl|pr|pt|pb|px|py)-(0|px|0\.5|1|1\.5|2|2\.5|3|3\.5|4|5|6|7|8|9|10|11|12|14|16|20|24|28|32|36|40|44|48|52|56|60|64|72|80|96|auto|full|screen|min|max|fit)$/, // Standard colors including transparent /^(bg|text|border)-(transparent|current|black|white|slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-?\d{0,3}$/, /^(bg|text|border)-(transparent|current|black|white|inherit)$/, // Standard typography - /^text-(xs|sm|base|lg|xl|2xl|3xl|4xl|5xl|6xl|7xl|8xl|9xl)$/, + /^text-(xs|sm|base|lg|xl|2xl|3xl|4xl|5xl|6xl|7xl|8xl|9xl|left|center|right|justify)$/, /^font-(thin|extralight|light|normal|medium|semibold|bold|extrabold|black)$/, /^font-(sans|serif|mono)$/, /^(leading|tracking)-(none|tight|snug|normal|relaxed|loose|wide|wider|widest)$/, // Standard borders & effects /^(border|rounded)-(none|sm|md|lg|xl|2xl|3xl|full)$/, - /^border-(0|2|4|8)$/, + /^border-(0|2|4|8|t|r|b|l|x|y|color)$/, /^shadow-(sm|md|lg|xl|2xl|inner|none)$/, /^opacity-(0|5|10|20|25|30|40|50|60|70|75|80|90|95|100)$/, From 50252de95e6a36663f71e52cb6569525005e112e Mon Sep 17 00:00:00 2001 From: Petr Glaser Date: Fri, 5 Sep 2025 23:29:36 +0200 Subject: [PATCH 30/44] fix: add unused tokens to components --- libs/ui/src/molecules/select.tsx | 8 +++++++- libs/ui/src/molecules/tree-view.tsx | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/libs/ui/src/molecules/select.tsx b/libs/ui/src/molecules/select.tsx index 00107bd4f6..33c194c769 100644 --- a/libs/ui/src/molecules/select.tsx +++ b/libs/ui/src/molecules/select.tsx @@ -20,7 +20,12 @@ export interface SelectOption { // === COMPONENT VARIANTS === const selectVariants = tv({ slots: { - root: ['relative', 'flex flex-col gap-select-root', 'w-full'], + root: [ + 'relative', + 'flex flex-col gap-select-root', + 'w-full', + 'bg-select-root-bg', + ], control: ['flex relative items-center justify-between', 'w-full'], positioner: ['w-(--reference-width)', 'isolate z-(--z-index)'], trigger: [ @@ -30,6 +35,7 @@ const selectVariants = tv({ 'border border-select-trigger-border rounded-select', 'text-select-trigger text-left', 'hover:bg-select-trigger-hover', + 'focus:bg-select-trigger-focus', 'focus:outline-none', 'data-[disabled]:cursor-not-allowed', 'data-[invalid]:border-select-danger data-[invalid]:ring-select-danger', diff --git a/libs/ui/src/molecules/tree-view.tsx b/libs/ui/src/molecules/tree-view.tsx index af189a9ec9..3f42b57ffd 100644 --- a/libs/ui/src/molecules/tree-view.tsx +++ b/libs/ui/src/molecules/tree-view.tsx @@ -55,7 +55,7 @@ const treeVariants = tv({ class: [ 'relative', // get --depth from zag-js api - 'ml-[calc(var(--depth)*theme(tree-indent-per-level))]', + 'ml-[calc(var(--depth)*var(--tree-indent-per-level))]', 'data-[depth=1]:ml-0', ], }, From 33204536bd7063b87f7716483dade87303386e61 Mon Sep 17 00:00:00 2001 From: Petr Glaser Date: Sat, 6 Sep 2025 00:18:47 +0200 Subject: [PATCH 31/44] fix: incorrectly undetected variables --- libs/ui/scripts/validate-token-definitions.js | 3 ++- libs/ui/src/molecules/accordion.tsx | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/libs/ui/scripts/validate-token-definitions.js b/libs/ui/scripts/validate-token-definitions.js index 46158a02f5..67cf924053 100755 --- a/libs/ui/scripts/validate-token-definitions.js +++ b/libs/ui/scripts/validate-token-definitions.js @@ -223,6 +223,8 @@ function tokenToUtilityClasses(tokenName) { '-end', // Layout 'gap', + 'gap-x', + 'gap-y', 'space-x', 'space-y', ], @@ -313,7 +315,6 @@ function tokenToUtilityClasses(tokenName) { 'opacity-bg', 'opacity-borderless', 'spacing-translate', - 'spacing-pc', 'spacing-menu-submenu', ] diff --git a/libs/ui/src/molecules/accordion.tsx b/libs/ui/src/molecules/accordion.tsx index 642fdb35fc..a1382507f0 100644 --- a/libs/ui/src/molecules/accordion.tsx +++ b/libs/ui/src/molecules/accordion.tsx @@ -55,7 +55,7 @@ const accordionVariants = tv({ subtitle: 'text-accordion-subtitle-sm', }, md: { - title: 'text-accordion-content-md py-accordion-primary-md', + title: 'text-accordion-title-md py-accordion-primary-md', content: 'text-accordion-content-md p-accordion-secondary-md', subtitle: 'text-accordion-subtitle-md', }, From 7b2b1873304043c2f4ba8ac777a123d20b4f77fa Mon Sep 17 00:00:00 2001 From: Petr Glaser Date: Sat, 6 Sep 2025 00:18:58 +0200 Subject: [PATCH 32/44] feat: use bun for faster processing --- libs/ui/package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libs/ui/package.json b/libs/ui/package.json index ac23b3487c..e2480542d7 100644 --- a/libs/ui/package.json +++ b/libs/ui/package.json @@ -40,10 +40,10 @@ "lint:tailwind": "eslint \"**/*.{js,jsx,ts,tsx}\" --config eslint.config.js", "lint:tailwind:fix": "eslint \"**/*.{js,jsx,ts,tsx}\" --config eslint.config.js --fix", "prepare": "pnpm build", - "check:unused-tokens": "node scripts/check-unused-tokens.js", - "validate:tokens": "node scripts/validate-token-usage.js && node scripts/validate-token-definitions.js", - "validate:token-usage": "node scripts/validate-token-usage.js", - "validate:token-definitions": "node scripts/validate-token-definitions.js" + "check:unused-tokens": "bun scripts/check-unused-tokens.js", + "validate:tokens": "bun scripts/validate-token-usage.js && bun scripts/validate-token-definitions.js", + "validate:token-usage": "bun scripts/validate-token-usage.js", + "validate:token-definitions": "bun scripts/validate-token-definitions.js" }, "dependencies": { "@zag-js/accordion": "^1.22.1", From 0089d7f0bb9f30dcfa98c2c5e61743d182147a70 Mon Sep 17 00:00:00 2001 From: Petr Glaser Date: Sat, 6 Sep 2025 01:19:58 +0200 Subject: [PATCH 33/44] refactor: improve performance of validate token script --- libs/ui/scripts/validate-token-definitions.js | 615 +++++++----------- 1 file changed, 247 insertions(+), 368 deletions(-) diff --git a/libs/ui/scripts/validate-token-definitions.js b/libs/ui/scripts/validate-token-definitions.js index 67cf924053..f48f3486e3 100755 --- a/libs/ui/scripts/validate-token-definitions.js +++ b/libs/ui/scripts/validate-token-definitions.js @@ -1,13 +1,16 @@ #!/usr/bin/env node /** - * Token Definition Validation Script + * Token Definition Validation Script (optimized) * - * Identifies unused tokens with smart exception handling. - * Analyzes dependency graphs and reference chains to determine actual usage. + * - Single-pass indexing of token CSS files + * - Single-pass scanning of component files + * - Dependency closure via forward BFS + * - Optional --profile timings */ -import fs from 'node:fs' +import fs from 'node:fs/promises' +import { existsSync } from 'node:fs' import { globSync } from 'glob' // Configuration for validation @@ -29,11 +32,7 @@ const CONFIG = { ], // Patterns to ignore completely - ignorePatterns: [ - /^--tw-/, // Tailwind internal variables - /_test$/, - /_debug$/, - ], + ignorePatterns: [/^--tw-/, /_test$/, /_debug$/], // File patterns to exclude from usage scanning excludeFiles: [ @@ -42,111 +41,48 @@ const CONFIG = { '**/*.spec.tsx', '**/node_modules/**', ], -} - -/** - * Parse CSS tokens from a file - */ -function parseTokensFromFile(filePath) { - const content = fs.readFileSync(filePath, 'utf8') - const tokens = new Map() - - // Match CSS custom properties with their values - const tokenRegex = /--([\w-]+)\s*:\s*([^;]+);/g - let match - - while ((match = tokenRegex.exec(content)) !== null) { - const [fullMatch, name, value] = match - const tokenName = `--${name}` - - tokens.set(tokenName, { - value: value.trim(), - file: filePath, - line: content.substring(0, match.index).split('\n').length, - }) - } - return tokens + // Token CSS glob + tokenCssGlob: 'src/tokens/components/**/*.css', } -/** - * Load all defined tokens from CSS files - */ -function loadAllTokens() { - const allTokens = new Map() - const tokenFiles = globSync('src/tokens/components/**/*.css') - - for (const file of tokenFiles) { - const tokens = parseTokensFromFile(file) - for (const [name, data] of tokens) { - allTokens.set(name, data) - } +// Simple stopwatch profiling +function profiled(enabled) { + const marks = new Map() + return { + mark(label) { + if (!enabled) return + marks.set(label, performance.now()) + }, + end(label) { + if (!enabled) return 0 + const start = marks.get(label) ?? performance.now() + const delta = performance.now() - start + marks.set(label, performance.now()) + return delta + }, } - - return allTokens } -/** - * Extract token references from CSS value - */ -function extractTokenReferences(value) { - const references = new Set() - - // Match var() functions - const varMatches = value.matchAll(/var\(\s*(--[\w-]+)/g) - for (const match of varMatches) { - references.add(match[1]) - } - - // Match oklch() from function references - const oklchMatches = value.matchAll(/oklch\([^)]*var\(\s*(--[\w-]+)/g) - for (const match of oklchMatches) { - references.add(match[1]) - } - - return references +function isWhitelisted(tokenName) { + return CONFIG.whitelistPatterns.some((p) => p.test(tokenName)) } -/** - * Build dependency graph of token references - */ -function buildTokenDependencyGraph(allTokens) { - const dependencyGraph = new Map() - const reverseDependencyGraph = new Map() - - for (const [tokenName, tokenData] of allTokens) { - const references = extractTokenReferences(tokenData.value) - dependencyGraph.set(tokenName, references) - - // Build reverse dependencies - for (const ref of references) { - if (!reverseDependencyGraph.has(ref)) { - reverseDependencyGraph.set(ref, new Set()) - } - reverseDependencyGraph.get(ref).add(tokenName) - } - } - - return { dependencyGraph, reverseDependencyGraph } +function shouldIgnoreToken(tokenName) { + return CONFIG.ignorePatterns.some((p) => p.test(tokenName)) } -/** - * Map CSS token to possible Tailwind utility classes - */ +// Map CSS token to possible Tailwind utility classes function tokenToUtilityClasses(tokenName) { const classes = new Set() - - // Simplified token parsing - const tokenParts = tokenName.slice(2).split('-') // Remove '--' and split + const tokenParts = tokenName.slice(2).split('-') if (tokenParts.length < 2) return classes const primaryNamespace = tokenParts[0] const subNamespace = tokenParts.length > 2 ? tokenParts[1] : null const key = tokenParts.slice(subNamespace ? 2 : 1).join('-') - // Complete namespace to utility prefix mappings per Tailwind v4 docs const mappings = { - // Color namespace - most extensive color: [ 'bg', 'text', @@ -163,10 +99,7 @@ function tokenToUtilityClasses(tokenName) { 'fill', 'stroke', ], - - // Spacing namespace - very extensive (general spacing) spacing: [ - // Padding 'p', 'px', 'py', @@ -176,7 +109,6 @@ function tokenToUtilityClasses(tokenName) { 'pl', 'ps', 'pe', - // Margin (including negative variants) 'm', 'mx', 'my', @@ -195,14 +127,12 @@ function tokenToUtilityClasses(tokenName) { '-ml', '-ms', '-me', - // Width & Height 'w', 'h', 'min-w', 'min-h', 'max-w', 'max-h', - // Position 'inset', 'inset-x', 'inset-y', @@ -221,15 +151,12 @@ function tokenToUtilityClasses(tokenName) { '-left', '-start', '-end', - // Layout 'gap', 'gap-x', 'gap-y', 'space-x', 'space-y', ], - - // Specific spacing namespaces (more specific than general spacing) padding: ['p', 'px', 'py', 'pt', 'pr', 'pb', 'pl', 'ps', 'pe'], margin: [ 'm', @@ -252,51 +179,32 @@ function tokenToUtilityClasses(tokenName) { '-me', ], gap: ['gap'], - - // Width namespace (when not using spacing) width: ['w', 'min-w', 'max-w'], - - // Height namespace (when not using spacing) height: ['h', 'min-h', 'max-h'], - - // Typography - text: ['text'], // Font size - font: ['font'], // Font family - tracking: ['tracking'], // Letter spacing - leading: ['leading'], // Line height - - // Border & Effects + text: ['text'], + font: ['font'], + tracking: ['tracking'], + leading: ['leading'], radius: ['rounded'], shadow: ['shadow'], 'inset-shadow': ['inset-shadow'], 'drop-shadow': ['drop-shadow'], blur: ['blur'], perspective: ['perspective'], - - // Layout aspect: ['aspect'], - - // Animation ease: ['ease'], animate: ['animate'], - - // Other opacity: ['opacity'], border: ['border'], - - // Special cases that don't map to utilities directly - z: [], // z-index values, handled differently - arrow: [], // Custom properties for arrows, not utilities - tree: [], // Custom component-specific properties - textarea: [], // Custom component properties - tooltip: [], // Custom component properties + z: [], + arrow: [], + tree: [], + textarea: [], + tooltip: [], } - // Simplified namespace and key handling let namespace = primaryNamespace let tokenKey = key - - // Handle special font-weight case if (primaryNamespace === 'font' && subNamespace === 'weight') { namespace = 'font-weight' } else if (subNamespace && mappings[`${primaryNamespace}-${subNamespace}`]) { @@ -305,7 +213,6 @@ function tokenToUtilityClasses(tokenName) { tokenKey = `${subNamespace}-${key}` } - // Skip custom properties that don't map to utilities const customPropertyPrefixes = [ 'arrow', 'tree', @@ -317,297 +224,269 @@ function tokenToUtilityClasses(tokenName) { 'spacing-translate', 'spacing-menu-submenu', ] - - if ( - customPropertyPrefixes.some((prefix) => tokenName.includes(`--${prefix}`)) - ) { + if (customPropertyPrefixes.some((prefix) => tokenName.includes(`--${prefix}`))) { return classes } const prefixes = mappings[namespace] || mappings[primaryNamespace] || [] - for (const prefix of prefixes) { if (namespace === 'font-weight') { - // Special case: --font-weight-bold → font-bold (strip "weight") classes.add(`font-${tokenKey}`) } else { classes.add(`${prefix}-${tokenKey}`) } } - return classes } -/** - * Search for token usage in component files (cached version) - */ -function findTokenUsageInComponentsCache(tokenName, componentFilesCache) { - const usage = new Set() - const possibleClasses = tokenToUtilityClasses(tokenName) +// Build indices from token CSS files in a single pass +async function buildTokenIndices() { + const files = globSync(CONFIG.tokenCssGlob) + const defs = new Map() // token -> { value, file, line } + const dependencyGraph = new Map() // token -> Set + const cssUsage = new Map() // token -> Set + + await Promise.all( + files.map(async (file) => { + if (!existsSync(file)) return + const content = await fs.readFile(file, 'utf8') + const lines = content.split('\n') - if (possibleClasses.size === 0) return usage + // 1) Parse token definitions and dependency edges + const tokenRegex = /--([\w-]+)\s*:\s*([^;]+);/g + let m + while ((m = tokenRegex.exec(content)) !== null) { + const name = `--${m[1]}` + const value = m[2].trim() + const line = content.substring(0, m.index).split('\n').length + defs.set(name, { value, file, line }) + + // Extract dependencies from value + const deps = new Set() + for (const vm of value.matchAll(/var\(\s*(--[\w-]+)/g)) { + deps.add(vm[1]) + } + for (const om of value.matchAll(/oklch\([^)]*var\(\s*(--[\w-]+)/g)) { + deps.add(om[1]) + } + dependencyGraph.set(name, deps) + } - for (const [file, content] of componentFilesCache) { - // Check for direct var() usage - if (content.includes(`var(${tokenName})`)) { - usage.add(`${file} (direct var() reference)`) - } + // 2) Index var() usage on non-definition lines as direct CSS usage + const defLine = new Set() + for (const [token, data] of defs) { + if (data.file === file) defLine.add(data.line) + } - // Check for utility class usage (simplified patterns) - for (const className of possibleClasses) { - const escapedClass = className.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') - - // Simplified patterns - covers most cases - const patterns = [ - // Matches: 'class', hover:class, data-[...]:class, sm:class - new RegExp( - `\\b(?:[a-z-]+:|data-\\[[^\\]]+\\]:|(?:sm|md|lg|xl|2xl):)?${escapedClass}\\b` - ), - // Matches inside quotes with optional spacing - new RegExp(`['"\`][^'"\`]*\\s${escapedClass}\\s[^'"\`]*['"\`]`), - ] - - for (const pattern of patterns) { - const match = content.search(pattern) - if (match !== -1) { - // Efficient line number calculation - const lineNumber = content.substring(0, match).split('\n').length - usage.add(`${file}:${lineNumber} (class: ${className})`) - break + for (let i = 0; i < lines.length; i++) { + const lineNo = i + 1 + if (defLine.has(lineNo)) continue + const line = lines[i] + if (!line.includes('var(')) continue + for (const vm of line.matchAll(/var\(\s*(--[\w-]+)/g)) { + const t = vm[1] + if (!cssUsage.has(t)) cssUsage.set(t, new Set()) + cssUsage.get(t).add(`${file}:${lineNo} (CSS property)`) // direct usage } } - } + }) + ) + + // Ensure every token key exists in maps + for (const token of defs.keys()) { + if (!dependencyGraph.has(token)) dependencyGraph.set(token, new Set()) + if (!cssUsage.has(token)) cssUsage.set(token, new Set()) } - return usage + return { defs, dependencyGraph, cssUsage } } -/** - * Search for token usage in CSS files (var() references) - */ -function findTokenUsageInCSS(tokenName, allTokens) { - const usage = new Set() - - // Check usage in other tokens (reuse already loaded token data) - for (const [otherTokenName, tokenData] of allTokens) { - if (otherTokenName === tokenName) continue - - if (tokenData.value.includes(`var(${tokenName})`)) { - usage.add( - `${tokenData.file}:${tokenData.line} (referenced by ${otherTokenName})` - ) - } +// Build component indices in a single pass +async function buildComponentIndices(classToTokens, knownTokens) { + const files = globSync('src/**/*.{ts,tsx}', { ignore: CONFIG.excludeFiles }) + const componentVarUsage = new Map() // token -> Set + const classUsageTokens = new Set() // tokens used via classes + + const normalizeClass = (raw) => { + // Strip variant prefixes like sm:, hover:, data-[...]: etc. + if (!raw) return '' + const parts = String(raw).split(':') + const core = parts[parts.length - 1] + return core } - // Check usage directly in CSS properties using already loaded files - const processedFiles = new Set() - - for (const [, tokenData] of allTokens) { - const file = tokenData.file - if (processedFiles.has(file)) continue - - processedFiles.add(file) - - try { - const content = fs.readFileSync(file, 'utf8') - const lines = content.split('\n') + await Promise.all( + files.map(async (file) => { + if (!existsSync(file)) return + try { + const content = await fs.readFile(file, 'utf8') + + // 1) Direct var(--token) usage in TSX + for (const m of content.matchAll(/var\(\s*(--[\w-]+)/g)) { + const t = m[1] + if (!componentVarUsage.has(t)) componentVarUsage.set(t, new Set()) + const lineNo = content.substring(0, m.index).split('\n').length + componentVarUsage + .get(t) + .add(`${file}:${lineNo} (component var() reference)`) + } - lines.forEach((line, index) => { - // Skip custom property definitions - if (line.trim().startsWith(`${tokenName}:`)) return + // 1b) Arbitrary utilities referencing tokens directly, e.g. border-(length:--token) + for (const m of content.matchAll(/--[a-z0-9-]+/gi)) { + const t = m[0] + if (knownTokens.has(t)) { + classUsageTokens.add(t) + } + } - // Look for var() usage in CSS properties - if ( - line.includes(`var(${tokenName})`) && - !line.includes(`${tokenName}:`) - ) { - usage.add(`${file}:${index + 1} (CSS property)`) + // 2) Class-based usage: scan whole file for class-like tokens + // Capture words with at least one hyphen, optionally with variant prefixes like sm:, hover:, data-[...]: + const classLike = /(^|[^A-Za-z0-9_-])([A-Za-z0-9_-]+(?::[A-Za-z0-9_\[\]-]+)*-[A-Za-z0-9_\[\]-]+)/g + for (const m of content.matchAll(classLike)) { + const raw = m[2] + const cls = normalizeClass(raw) + if (!cls) continue + const tokens = classToTokens.get(cls) + if (!tokens) continue + for (const t of tokens) classUsageTokens.add(t) } - }) - } catch (error) { - // Skip files that can't be read - } - } + } catch { + // ignore unreadable files + } + }) + ) - return usage + return { componentVarUsage, classUsageTokens } } -/** - * Check if token should be whitelisted - */ -function isWhitelisted(tokenName) { - return CONFIG.whitelistPatterns.some((pattern) => pattern.test(tokenName)) -} - -/** - * Check if token should be ignored - */ -function shouldIgnoreToken(tokenName) { - return CONFIG.ignorePatterns.some((pattern) => pattern.test(tokenName)) +function computeClassMaps(tokens) { + const tokenToClasses = new Map() + const classToTokens = new Map() + for (const token of tokens) { + const classes = tokenToUtilityClasses(token) + tokenToClasses.set(token, classes) + for (const c of classes) { + if (!classToTokens.has(c)) classToTokens.set(c, new Set()) + classToTokens.get(c).add(token) + } + } + return { tokenToClasses, classToTokens } } -/** - * Find all tokens that depend on a given token (recursive) - */ -function findDependentTokens( - tokenName, - reverseDependencyGraph, - visited = new Set() -) { - if (visited.has(tokenName)) return new Set() - - visited.add(tokenName) - const dependents = new Set() - - const directDependents = reverseDependencyGraph.get(tokenName) || new Set() - for (const dependent of directDependents) { - dependents.add(dependent) - const transitiveDependents = findDependentTokens( - dependent, - reverseDependencyGraph, - visited - ) - for (const transitive of transitiveDependents) { - dependents.add(transitive) +function propagateUsage(initialUsed, dependencyGraph) { + const used = new Set(initialUsed) + const queue = [...initialUsed] + while (queue.length) { + const cur = queue.shift() + const deps = dependencyGraph.get(cur) + if (!deps) continue + for (const d of deps) { + if (!used.has(d)) { + used.add(d) + queue.push(d) + } } } - - return dependents + return used } -/** - * Main validation function - */ -function validateTokenDefinitions() { +async function validateTokenDefinitions({ profile = false } = {}) { + const p = profiled(profile) + p.mark('total') console.log('🔍 Analyzing token definitions and usage...') - const allTokens = loadAllTokens() - console.log(`📋 Found ${allTokens.size} total tokens\n`) - - const { dependencyGraph, reverseDependencyGraph } = - buildTokenDependencyGraph(allTokens) - - // Cache component files content for reuse - const componentFilesCache = new Map() - const componentFiles = globSync('src/**/*.{ts,tsx}', { - ignore: CONFIG.excludeFiles, - }) - - for (const file of componentFiles) { - try { - componentFilesCache.set(file, fs.readFileSync(file, 'utf8')) - } catch (error) { - console.warn(`⚠️ Could not read ${file}: ${error.message}`) - } + // 1) Token indices + p.mark('tokens') + const { defs, dependencyGraph, cssUsage } = await buildTokenIndices() + const allTokens = Array.from(defs.keys()) + if (profile) console.log(`⏱️ tokens: ${p.end('tokens').toFixed(1)}ms`) + console.log(`📋 Found ${allTokens.length} total tokens\n`) + + // 2) Class maps from tokens + p.mark('classmaps') + const { classToTokens } = computeClassMaps(allTokens) + if (profile) + console.log(`⏱️ class maps: ${p.end('classmaps').toFixed(1)}ms`) + + // 3) Component indices + p.mark('components') + const { componentVarUsage, classUsageTokens } = await buildComponentIndices( + classToTokens, + new Set(allTokens) + ) + if (profile) + console.log(`⏱️ components: ${p.end('components').toFixed(1)}ms`) + + // 4) Seed used set + const usedDirect = new Set() + for (const t of allTokens) { + if (isWhitelisted(t)) usedDirect.add(t) } + // Direct CSS var() usage (outside token defs) + for (const [t, locs] of cssUsage) { + if (locs.size > 0) usedDirect.add(t) + } + // Component var() usage + for (const [t, locs] of componentVarUsage) { + if (locs.size > 0) usedDirect.add(t) + } + // Class usage + for (const t of classUsageTokens) usedDirect.add(t) - const unusedTokens = [] - const usedTokens = new Set() - let checkedCount = 0 - - for (const [tokenName, tokenData] of allTokens) { - checkedCount++ - - if (shouldIgnoreToken(tokenName)) { - continue - } - - if (isWhitelisted(tokenName)) { - usedTokens.add(tokenName) - continue - } - - // Check component usage with cached content - const componentUsage = findTokenUsageInComponentsCache( - tokenName, - componentFilesCache - ) - - // Check CSS usage - const cssUsage = findTokenUsageInCSS(tokenName, allTokens) - - const totalUsage = new Set([...componentUsage, ...cssUsage]) + // 5) Propagate through dependencies (forward) + p.mark('closure') + const usedTokens = propagateUsage(usedDirect, dependencyGraph) + if (profile) console.log(`⏱️ closure: ${p.end('closure').toFixed(1)}ms`) - if (totalUsage.size > 0) { - usedTokens.add(tokenName) - } else { - // Check if any dependent tokens are used - const dependents = findDependentTokens(tokenName, reverseDependencyGraph) - const usedDependents = Array.from(dependents).filter((dep) => - usedTokens.has(dep) - ) - - if (usedDependents.length > 0) { - usedTokens.add(tokenName) - } else { - unusedTokens.push({ - name: tokenName, - file: tokenData.file, - line: tokenData.line, - value: tokenData.value, - }) - } + // 6) Classify + const unusedTokens = [] + for (const t of allTokens) { + if (shouldIgnoreToken(t)) continue + if (!usedTokens.has(t)) { + const d = defs.get(t) + unusedTokens.push({ name: t, file: d.file, line: d.line, value: d.value }) } } // Report results console.log(`\n📊 Validation Summary:`) - console.log(` Total tokens: ${allTokens.size}`) - console.log(` Used tokens: ${usedTokens.size}`) + console.log(` Total tokens: ${allTokens.length}`) + console.log(` Used tokens: ${allTokens.length - unusedTokens.length}`) console.log(` Unused tokens: ${unusedTokens.length}`) if (unusedTokens.length === 0) { console.log('\n✅ All tokens are being used!') + if (profile) console.log(`⏱️ total: ${p.end('total').toFixed(1)}ms`) return true - } else { - console.log( - `\n⚠️ Found ${unusedTokens.length} potentially unused tokens:\n` - ) - - // Group by file - const tokensByFile = new Map() - for (const token of unusedTokens) { - if (!tokensByFile.has(token.file)) { - tokensByFile.set(token.file, []) - } - tokensByFile.get(token.file).push(token) - } - - for (const [file, tokens] of tokensByFile) { - console.log(`📄 ${file}:`) - for (const token of tokens) { - console.log(` Line ${token.line}: ${token.name} = ${token.value}`) - } - console.log() - } - - console.log( - '💡 Note: These tokens might be used in ways not detected by this script.' - ) - console.log( - ' Consider:\n - Dynamic class generation\n - External usage\n - Future planned usage\n' - ) + } - return false + console.log(`\n⚠️ Found ${unusedTokens.length} potentially unused tokens:\n`) + const byFile = new Map() + for (const tok of unusedTokens) { + if (!byFile.has(tok.file)) byFile.set(tok.file, []) + byFile.get(tok.file).push(tok) + } + for (const [file, list] of byFile) { + console.log(`📄 ${file}:`) + for (const t of list) console.log(` Line ${t.line}: ${t.name} = ${t.value}`) + console.log() } + console.log( + '💡 Note: Tokens might be used dynamically or externally and not detected.' + ) + if (profile) console.log(`⏱️ total: ${p.end('total').toFixed(1)}ms`) + return false } -/** - * Run validation - */ if (import.meta.url === `file://${process.argv[1]}`) { - try { - const success = validateTokenDefinitions() - process.exit(success ? 0 : 1) - } catch (error) { - console.error('💥 Validation failed:', error.message) - console.error(error.stack) - process.exit(1) - } + const profile = process.argv.includes('--profile') + validateTokenDefinitions({ profile }) + .then((ok) => process.exit(ok ? 0 : 1)) + .catch((err) => { + console.error('💥 Validation failed:', err?.message || err) + if (err?.stack) console.error(err.stack) + process.exit(1) + }) } -export { - validateTokenDefinitions, - buildTokenDependencyGraph, - findTokenUsageInComponentsCache, -} +export { validateTokenDefinitions } From cf44dccabd1c558fd38f4d8b4ba04acfeb996202 Mon Sep 17 00:00:00 2001 From: Petr Glaser Date: Sat, 6 Sep 2025 01:44:58 +0200 Subject: [PATCH 34/44] fix: improve tokens detection --- libs/ui/scripts/validate-token-usage.js | 135 ++++++++++++++++++++---- 1 file changed, 114 insertions(+), 21 deletions(-) diff --git a/libs/ui/scripts/validate-token-usage.js b/libs/ui/scripts/validate-token-usage.js index 456523c3c8..7ef6fd730c 100755 --- a/libs/ui/scripts/validate-token-usage.js +++ b/libs/ui/scripts/validate-token-usage.js @@ -22,6 +22,7 @@ const NAMESPACE_MAPPINGS = { 'stroke', 'outline', 'ring', + 'ring-offset', 'shadow', 'accent', 'caret', @@ -35,6 +36,8 @@ const NAMESPACE_MAPPINGS = { 'pr', 'pb', 'pl', + 'ps', + 'pe', 'm', 'mx', 'my', @@ -42,7 +45,13 @@ const NAMESPACE_MAPPINGS = { 'mr', 'mb', 'ml', + 'ms', + 'me', 'gap', + 'gap-x', + 'gap-y', + 'space-x', + 'space-y', 'w', 'h', 'max-w', @@ -54,12 +63,14 @@ const NAMESPACE_MAPPINGS = { 'bottom', 'left', 'inset', + 'inset-x', + 'inset-y', ], text: ['text'], 'font-weight': ['font'], font: ['font'], radius: ['rounded'], - shadow: ['shadow', 'drop-shadow'], + shadow: ['shadow', 'drop-shadow', 'inset-shadow'], blur: ['blur'], opacity: ['opacity'], border: ['border'], @@ -67,12 +78,6 @@ const NAMESPACE_MAPPINGS = { // Standard Tailwind utilities to ignore (not custom tokens) const IGNORE_PATTERNS = [ - // CSS variables in parentheses - these use CSS variables directly, not design tokens - /^[a-z-]+\([^)]*--[^)]+\)$/, - - // Square bracket notation with CSS variables - ignore these as they use CSS vars directly - /^[a-z-]+\[var\(--[^)]+\)\]$/, - // Standard positioning values /^(top|right|bottom|left|inset)-(0|px|0\.5|1|1\.5|2|2\.5|3|3\.5|4|5|6|7|8|9|10|11|12|14|16|20|24|28|32|36|40|44|48|52|56|60|64|72|80|96|auto|full|screen|min|max|fit|end)$/, @@ -124,6 +129,22 @@ const IGNORE_PATTERNS = [ /^(sr-only|not-sr-only|pointer-events|select|resize|appearance|cursor|outline|ring)-.+$/, ] +// Precompute prefix helpers for mapping +const KNOWN_PREFIXES = Object.values(NAMESPACE_MAPPINGS) + .flat() + .slice() + .sort((a, b) => b.length - a.length) +const PREFIX_TO_NAMESPACES = (() => { + const map = new Map() + for (const [ns, prefixes] of Object.entries(NAMESPACE_MAPPINGS)) { + for (const p of prefixes) { + if (!map.has(p)) map.set(p, []) + map.get(p).push(ns) + } + } + return map +})() + /** * Extract Tailwind classes from TypeScript/JSX content */ @@ -222,14 +243,12 @@ function mapClassToPossibleTokens(className) { // Parse utility class: prefix-value (non-greedy matching) // Match known prefixes first, then everything else as value - const knownPrefixes = Object.values(NAMESPACE_MAPPINGS).flat() let prefix = null let value = null // Normalize negative utilities (-m-*, -mt-*, etc.) const normalized = baseClass.startsWith('-') ? baseClass.slice(1) : baseClass // Try to match against known prefixes (longest first to handle cases like 'ring-offset') - const sortedPrefixes = knownPrefixes.sort((a, b) => b.length - a.length) - for (const knownPrefix of sortedPrefixes) { + for (const knownPrefix of KNOWN_PREFIXES) { if (normalized.startsWith(`${knownPrefix}-`)) { prefix = knownPrefix value = normalized.slice(knownPrefix.length + 1) // +1 for the dash @@ -242,20 +261,19 @@ function mapClassToPossibleTokens(className) { const possibleTokens = [] // Find which namespaces this prefix could belong to - for (const [namespace, prefixes] of Object.entries(NAMESPACE_MAPPINGS)) { - if (prefixes.includes(prefix)) { - // Special handling for font-weight - if (namespace === 'font-weight') { - possibleTokens.push(`--font-weight-${value}`) - } else { - possibleTokens.push(`--${namespace}-${value}`) - } + const namespaces = PREFIX_TO_NAMESPACES.get(prefix) || [] + for (const namespace of namespaces) { + if (namespace === 'font-weight') { + possibleTokens.push(`--font-weight-${value}`) + } else { + possibleTokens.push(`--${namespace}-${value}`) } } // Add specific namespace alternatives for spacing-related utilities if (['p', 'px', 'py', 'pt', 'pr', 'pb', 'pl', 'ps', 'pe'].includes(prefix)) { possibleTokens.push(`--padding-${value}`) + possibleTokens.push(`--spacing-${value}`) } if ( @@ -281,10 +299,12 @@ function mapClassToPossibleTokens(className) { ].includes(prefix) ) { possibleTokens.push(`--margin-${value}`) + possibleTokens.push(`--spacing-${value}`) } - if (['gap'].includes(prefix)) { + if (['gap', 'gap-x', 'gap-y'].includes(prefix)) { possibleTokens.push(`--gap-${value}`) + possibleTokens.push(`--spacing-${value}`) } if (['w', 'min-w', 'max-w'].includes(prefix)) { @@ -295,6 +315,20 @@ function mapClassToPossibleTokens(className) { possibleTokens.push(`--height-${value}`) } + if (['space-x', 'space-y'].includes(prefix)) { + possibleTokens.push(`--space-${value}`) + possibleTokens.push(`--spacing-${value}`) + } + + if ( + ['inset', 'inset-x', 'inset-y', 'top', 'right', 'bottom', 'left'].includes( + prefix + ) + ) { + possibleTokens.push(`--inset-${value}`) + possibleTokens.push(`--spacing-${value}`) + } + return possibleTokens } @@ -308,14 +342,23 @@ function loadDefinedTokens() { for (const file of tokenFiles) { const content = fs.readFileSync(path.join(ROOT, file), 'utf8') - - // Match CSS custom properties + // Match CSS custom properties defined in tokens const tokenMatches = content.matchAll(/--([a-z][a-z0-9-]*)\s*:/g) for (const match of tokenMatches) { tokens.add(`--${match[1]}`) } } + // Also treat inline style custom properties in components as defined + const componentFiles = globSync('src/**/*.{ts,tsx}', { cwd: ROOT, ignore: ['**/*.stories.tsx','**/*.test.tsx','**/*.spec.tsx'] }) + for (const file of componentFiles) { + const content = fs.readFileSync(path.join(ROOT, file), 'utf8') + // style={{ '--var': value }} or object entries '--var': value + for (const m of content.matchAll(/["'](--[a-z][a-z0-9-]*)["']\s*:/gi)) { + tokens.add(m[1]) + } + } + return tokens } @@ -331,6 +374,24 @@ function shouldIgnoreClass(className) { return IGNORE_PATTERNS.some((pattern) => pattern.test(baseClass)) } +// Extract tokens referenced directly inside arbitrary utility syntax +function extractTokensFromArbitraryUtility(className) { + const tokens = new Set() + // var(--token) + for (const m of className.matchAll(/var\(\s*(--[a-z][a-z0-9-]*)/gi)) { + tokens.add(m[1]) + } + // key:(--token) or key=--token forms inside parentheses/brackets + for (const m of className.matchAll(/[:=]\s*(--[a-z][a-z0-9-]*)/gi)) { + tokens.add(m[1]) + } + // Bare --token immediately after an opening parenthesis. Avoid matching icon names like mdi--play in brackets. + for (const m of className.matchAll(/\((--[a-z][a-z0-9-]*)/gi)) { + tokens.add(m[1]) + } + return Array.from(tokens) +} + /** * Main validation function */ @@ -353,8 +414,40 @@ function validateTokenUsage() { const fileErrors = [] for (const className of classes) { + // 1) Arbitrary utilities with direct token references + const arbitraryTokens = extractTokensFromArbitraryUtility(className) + if (arbitraryTokens.length > 0) { + // Allow some external CSS vars not governed by design tokens + const externalAllow = new Set([ + // Keep truly external/runtime-provided vars here; we now require + // --reference-width and --z-index to exist somewhere, per request. + '--available-height', + '--height', + '--border-width-badge-dynamic', + ]) + const tokensNeedingCheck = arbitraryTokens.filter( + (t) => !externalAllow.has(t) + ) + + const anyDefined = tokensNeedingCheck.some((t) => definedTokens.has(t)) + if (!anyDefined && tokensNeedingCheck.length > 0) { + fileErrors.push({ + className, + expectedTokens: tokensNeedingCheck, + line: + content + .split('\n') + .findIndex((line) => line.includes(className)) + 1, + }) + } + // If tokens are all external or any defined, consider it valid and continue + if (tokensNeedingCheck.length === 0 || anyDefined) continue + } + + // 2) Ignore standard classes if (shouldIgnoreClass(className)) continue + // 3) Map to possible tokens via namespace rules const possibleTokens = mapClassToPossibleTokens(className) if (possibleTokens.length === 0) continue From 728252d9a3a6bc317faff15a96b2f7619684bede Mon Sep 17 00:00:00 2001 From: Petr Glaser Date: Sat, 6 Sep 2025 01:55:05 +0200 Subject: [PATCH 35/44] chore: improve menu readability --- libs/ui/src/molecules/menu.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libs/ui/src/molecules/menu.tsx b/libs/ui/src/molecules/menu.tsx index 7c6616a432..23f5b6db4b 100644 --- a/libs/ui/src/molecules/menu.tsx +++ b/libs/ui/src/molecules/menu.tsx @@ -65,8 +65,7 @@ const menuVariants = tv({ 'bg-menu-content-bg border border-menu-content-border', 'rounded-menu shadow-menu-content-shadow', 'p-menu-content', - '', - ' overflow-auto', + 'overflow-auto', 'focus:outline-none', 'data-[state=open]:animate-in', 'data-[state=closed]:animate-out', From 541ebed3eca12ea43e5d9b42f3a667b25960992b Mon Sep 17 00:00:00 2001 From: Petr Glaser Date: Sat, 6 Sep 2025 02:12:03 +0200 Subject: [PATCH 36/44] feat: add eslint tailwind beta + fix menu build ! TODO: Fix any in menu.tsx --- libs/ui/eslint.config.js | 2 +- libs/ui/package.json | 2 +- libs/ui/src/molecules/menu.tsx | 14 +- pnpm-lock.yaml | 1044 ++++++++++++++++++++++---------- 4 files changed, 741 insertions(+), 321 deletions(-) diff --git a/libs/ui/eslint.config.js b/libs/ui/eslint.config.js index 655b1a092c..0b0f5c5955 100644 --- a/libs/ui/eslint.config.js +++ b/libs/ui/eslint.config.js @@ -17,7 +17,7 @@ export default [ settings: { tailwindcss: { // For TailwindCSS v4 - point to CSS config file - config: 'D:\\Work\\new-engine\\libs\\ui\\src\\tokens\\index.css', + config: '/Users/satan/work/new-engine/libs/ui/src/tokens/index.css', }, }, plugins: { diff --git a/libs/ui/package.json b/libs/ui/package.json index e2480542d7..cb4a3c083f 100644 --- a/libs/ui/package.json +++ b/libs/ui/package.json @@ -89,7 +89,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "eslint": "^9.0.0", - "eslint-plugin-tailwindcss": "npm:@hyoban/eslint-plugin-tailwindcss@4.0.0-alpha.12", + "eslint-plugin-tailwindcss": "beta", "glob": "^11.0.2", "next": "^15.5.0", "react": "^19.1.0", diff --git a/libs/ui/src/molecules/menu.tsx b/libs/ui/src/molecules/menu.tsx index 23f5b6db4b..a9ecd5eb1d 100644 --- a/libs/ui/src/molecules/menu.tsx +++ b/libs/ui/src/molecules/menu.tsx @@ -126,6 +126,7 @@ interface SubmenuItemProps { closeOnSelect?: boolean } +// ! TODO: Fix menu.machine typing, it should work without 'as any' function SubmenuItem({ item, parentApi, @@ -141,14 +142,11 @@ function SubmenuItem({ onSelect, }) - const submenuApi = menu.connect( - submenuService as menu.Service, - normalizeProps - ) + const submenuApi = menu.connect(submenuService as any, normalizeProps) useEffect(() => { // Setup parent-child relationship - parentApi.setChild(submenuService as menu.Service) + parentApi.setChild(submenuService as any) submenuApi.setParent(parentService) }, [parentApi, submenuApi, submenuService, parentService]) @@ -176,7 +174,7 @@ function SubmenuItem({ key={menuItem.value} item={menuItem} parentApi={submenuApi} - parentService={submenuService as menu.Service} + parentService={submenuService as any} size={size} onCheckedChange={onCheckedChange} onSelect={onSelect} @@ -346,7 +344,7 @@ export function Menu({ 'aria-label': ariaLabel, }) - const api = menu.connect(service as menu.Service, normalizeProps) + const api = menu.connect(service as any, normalizeProps) const { trigger, @@ -372,7 +370,7 @@ export function Menu({ key={item.value} item={item} parentApi={api} - parentService={service as menu.Service} + parentService={service as any} size={size} onCheckedChange={onCheckedChange} onSelect={onSelect} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index df7acbc2d5..fba8896fe1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -579,68 +579,68 @@ importers: libs/ui: dependencies: '@zag-js/accordion': - specifier: ^1.15.0 - version: 1.15.0 + specifier: ^1.22.1 + version: 1.22.1 '@zag-js/carousel': - specifier: ^1.15.0 - version: 1.15.0 + specifier: ^1.22.1 + version: 1.22.1 '@zag-js/checkbox': - specifier: ^1.15.0 - version: 1.15.0 + specifier: ^1.22.1 + version: 1.22.1 '@zag-js/combobox': - specifier: ^1.15.0 - version: 1.15.0 + specifier: ^1.22.1 + version: 1.22.1 '@zag-js/dialog': - specifier: ^1.15.0 - version: 1.15.0 + specifier: ^1.22.1 + version: 1.22.1 '@zag-js/i18n-utils': - specifier: ^1.15.0 - version: 1.15.0 + specifier: ^1.22.1 + version: 1.22.1 '@zag-js/menu': - specifier: ^1.15.0 - version: 1.18.2 + specifier: ^1.22.1 + version: 1.22.1 '@zag-js/number-input': - specifier: ^1.15.0 - version: 1.15.0 + specifier: ^1.22.1 + version: 1.22.1 '@zag-js/pagination': - specifier: ^1.15.0 - version: 1.15.0 + specifier: ^1.22.1 + version: 1.22.1 '@zag-js/popover': - specifier: ^1.15.0 - version: 1.15.0 + specifier: ^1.22.1 + version: 1.22.1 '@zag-js/rating-group': - specifier: ^1.15.0 - version: 1.15.0 + specifier: ^1.22.1 + version: 1.22.1 '@zag-js/react': - specifier: ^1.15.0 - version: 1.15.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + specifier: ^1.22.1 + version: 1.22.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@zag-js/select': - specifier: ^1.15.0 - version: 1.15.0 + specifier: ^1.22.1 + version: 1.22.1 '@zag-js/slider': - specifier: ^1.15.0 - version: 1.15.0 + specifier: ^1.22.1 + version: 1.22.1 '@zag-js/steps': - specifier: ^1.15.0 - version: 1.15.0 + specifier: ^1.22.1 + version: 1.22.1 '@zag-js/switch': - specifier: ^1.15.0 - version: 1.15.0 + specifier: ^1.22.1 + version: 1.22.1 '@zag-js/tabs': - specifier: ^1.15.0 - version: 1.15.0 + specifier: ^1.22.1 + version: 1.22.1 '@zag-js/toast': - specifier: ^1.15.0 - version: 1.15.0 + specifier: ^1.22.1 + version: 1.22.1 '@zag-js/tooltip': - specifier: ^1.15.0 - version: 1.15.0 + specifier: ^1.22.1 + version: 1.22.1 '@zag-js/tree-view': - specifier: ^1.15.0 - version: 1.15.0 + specifier: ^1.22.1 + version: 1.22.1 tailwind-variants: - specifier: ^1.0.0 - version: 1.0.0(tailwindcss@4.1.8) + specifier: ^3.1.0 + version: 3.1.0(tailwind-merge@3.3.0)(tailwindcss@4.1.8) devDependencies: '@eslint/js': specifier: ^9.0.0 @@ -703,11 +703,14 @@ importers: specifier: ^9.0.0 version: 9.26.0(jiti@2.4.2) eslint-plugin-tailwindcss: - specifier: npm:@hyoban/eslint-plugin-tailwindcss@4.0.0-alpha.12 - version: '@hyoban/eslint-plugin-tailwindcss@4.0.0-alpha.12(tailwindcss@4.1.8)' + specifier: beta + version: 4.0.0-beta.0(tailwindcss@4.1.8) glob: specifier: ^11.0.2 version: 11.0.3 + next: + specifier: ^15.5.0 + version: 15.5.2(@babel/core@7.27.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(babel-plugin-macros@3.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.84.0) react: specifier: ^19.1.0 version: 19.1.0 @@ -1829,6 +1832,9 @@ packages: '@emnapi/runtime@1.4.3': resolution: {integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==} + '@emnapi/runtime@1.5.0': + resolution: {integrity: sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==} + '@emnapi/wasi-threads@1.0.2': resolution: {integrity: sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==} @@ -2302,12 +2308,18 @@ packages: '@floating-ui/core@1.7.2': resolution: {integrity: sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==} + '@floating-ui/core@1.7.3': + resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} + '@floating-ui/dom@1.7.1': resolution: {integrity: sha512-cwsmW/zyw5ltYTUeeYJ60CnQuPqmGwuGVhG9w0PRaRKkAyi38BT5CKrpIbb+jtahSwUl04cWzSx9ZOIxeS6RsQ==} '@floating-ui/dom@1.7.2': resolution: {integrity: sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA==} + '@floating-ui/dom@1.7.4': + resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==} + '@floating-ui/react-dom@2.1.2': resolution: {integrity: sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==} peerDependencies: @@ -2448,12 +2460,6 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} - '@hyoban/eslint-plugin-tailwindcss@4.0.0-alpha.12': - resolution: {integrity: sha512-IKPK7PgoHoo9TEQLccNXLUXdlLxFHNrcr++NIEGtKMNfae99ygUdtLOPsq23zy6V7Xh3njzr/urADwdWYsql4Q==} - engines: {node: '>=18.12.0'} - peerDependencies: - tailwindcss: ^3.4.0 || ^4.0.0 - '@iconify/json@2.2.338': resolution: {integrity: sha512-wKUTEGEQwkgbY+P84c2njj/D9iEXoKr/Gv6Abwnw+tuElkT57JsGfMFAUpZpaKDL5fTD/+noRRA4L10mFOtIYw==} @@ -2474,110 +2480,232 @@ packages: cpu: [arm64] os: [darwin] + '@img/sharp-darwin-arm64@0.34.3': + resolution: {integrity: sha512-ryFMfvxxpQRsgZJqBd4wsttYQbCxsJksrv9Lw/v798JcQ8+w84mBWuXwl+TT0WJ/WrYOLaYpwQXi3sA9nTIaIg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + '@img/sharp-darwin-x64@0.34.1': resolution: {integrity: sha512-VfuYgG2r8BpYiOUN+BfYeFo69nP/MIwAtSJ7/Zpxc5QF3KS22z8Pvg3FkrSFJBPNQ7mmcUcYQFBmEQp7eu1F8Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [darwin] + '@img/sharp-darwin-x64@0.34.3': + resolution: {integrity: sha512-yHpJYynROAj12TA6qil58hmPmAwxKKC7reUqtGLzsOHfP7/rniNGTL8tjWX6L3CTV4+5P4ypcS7Pp+7OB+8ihA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + '@img/sharp-libvips-darwin-arm64@1.1.0': resolution: {integrity: sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA==} cpu: [arm64] os: [darwin] + '@img/sharp-libvips-darwin-arm64@1.2.0': + resolution: {integrity: sha512-sBZmpwmxqwlqG9ueWFXtockhsxefaV6O84BMOrhtg/YqbTaRdqDE7hxraVE3y6gVM4eExmfzW4a8el9ArLeEiQ==} + cpu: [arm64] + os: [darwin] + '@img/sharp-libvips-darwin-x64@1.1.0': resolution: {integrity: sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ==} cpu: [x64] os: [darwin] + '@img/sharp-libvips-darwin-x64@1.2.0': + resolution: {integrity: sha512-M64XVuL94OgiNHa5/m2YvEQI5q2cl9d/wk0qFTDVXcYzi43lxuiFTftMR1tOnFQovVXNZJ5TURSDK2pNe9Yzqg==} + cpu: [x64] + os: [darwin] + '@img/sharp-libvips-linux-arm64@1.1.0': resolution: {integrity: sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew==} cpu: [arm64] os: [linux] + '@img/sharp-libvips-linux-arm64@1.2.0': + resolution: {integrity: sha512-RXwd0CgG+uPRX5YYrkzKyalt2OJYRiJQ8ED/fi1tq9WQW2jsQIn0tqrlR5l5dr/rjqq6AHAxURhj2DVjyQWSOA==} + cpu: [arm64] + os: [linux] + '@img/sharp-libvips-linux-arm@1.1.0': resolution: {integrity: sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA==} cpu: [arm] os: [linux] + '@img/sharp-libvips-linux-arm@1.2.0': + resolution: {integrity: sha512-mWd2uWvDtL/nvIzThLq3fr2nnGfyr/XMXlq8ZJ9WMR6PXijHlC3ksp0IpuhK6bougvQrchUAfzRLnbsen0Cqvw==} + cpu: [arm] + os: [linux] + '@img/sharp-libvips-linux-ppc64@1.1.0': resolution: {integrity: sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ==} cpu: [ppc64] os: [linux] + '@img/sharp-libvips-linux-ppc64@1.2.0': + resolution: {integrity: sha512-Xod/7KaDDHkYu2phxxfeEPXfVXFKx70EAFZ0qyUdOjCcxbjqyJOEUpDe6RIyaunGxT34Anf9ue/wuWOqBW2WcQ==} + cpu: [ppc64] + os: [linux] + '@img/sharp-libvips-linux-s390x@1.1.0': resolution: {integrity: sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA==} cpu: [s390x] os: [linux] + '@img/sharp-libvips-linux-s390x@1.2.0': + resolution: {integrity: sha512-eMKfzDxLGT8mnmPJTNMcjfO33fLiTDsrMlUVcp6b96ETbnJmd4uvZxVJSKPQfS+odwfVaGifhsB07J1LynFehw==} + cpu: [s390x] + os: [linux] + '@img/sharp-libvips-linux-x64@1.1.0': resolution: {integrity: sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q==} cpu: [x64] os: [linux] + '@img/sharp-libvips-linux-x64@1.2.0': + resolution: {integrity: sha512-ZW3FPWIc7K1sH9E3nxIGB3y3dZkpJlMnkk7z5tu1nSkBoCgw2nSRTFHI5pB/3CQaJM0pdzMF3paf9ckKMSE9Tg==} + cpu: [x64] + os: [linux] + '@img/sharp-libvips-linuxmusl-arm64@1.1.0': resolution: {integrity: sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w==} cpu: [arm64] os: [linux] + '@img/sharp-libvips-linuxmusl-arm64@1.2.0': + resolution: {integrity: sha512-UG+LqQJbf5VJ8NWJ5Z3tdIe/HXjuIdo4JeVNADXBFuG7z9zjoegpzzGIyV5zQKi4zaJjnAd2+g2nna8TZvuW9Q==} + cpu: [arm64] + os: [linux] + '@img/sharp-libvips-linuxmusl-x64@1.1.0': resolution: {integrity: sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A==} cpu: [x64] os: [linux] + '@img/sharp-libvips-linuxmusl-x64@1.2.0': + resolution: {integrity: sha512-SRYOLR7CXPgNze8akZwjoGBoN1ThNZoqpOgfnOxmWsklTGVfJiGJoC/Lod7aNMGA1jSsKWM1+HRX43OP6p9+6Q==} + cpu: [x64] + os: [linux] + '@img/sharp-linux-arm64@0.34.1': resolution: {integrity: sha512-kX2c+vbvaXC6vly1RDf/IWNXxrlxLNpBVWkdpRq5Ka7OOKj6nr66etKy2IENf6FtOgklkg9ZdGpEu9kwdlcwOQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + '@img/sharp-linux-arm64@0.34.3': + resolution: {integrity: sha512-QdrKe3EvQrqwkDrtuTIjI0bu6YEJHTgEeqdzI3uWJOH6G1O8Nl1iEeVYRGdj1h5I21CqxSvQp1Yv7xeU3ZewbA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + '@img/sharp-linux-arm@0.34.1': resolution: {integrity: sha512-anKiszvACti2sGy9CirTlNyk7BjjZPiML1jt2ZkTdcvpLU1YH6CXwRAZCA2UmRXnhiIftXQ7+Oh62Ji25W72jA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] + '@img/sharp-linux-arm@0.34.3': + resolution: {integrity: sha512-oBK9l+h6KBN0i3dC8rYntLiVfW8D8wH+NPNT3O/WBHeW0OQWCjfWksLUaPidsrDKpJgXp3G3/hkmhptAW0I3+A==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + + '@img/sharp-linux-ppc64@0.34.3': + resolution: {integrity: sha512-GLtbLQMCNC5nxuImPR2+RgrviwKwVql28FWZIW1zWruy6zLgA5/x2ZXk3mxj58X/tszVF69KK0Is83V8YgWhLA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + '@img/sharp-linux-s390x@0.34.1': resolution: {integrity: sha512-7s0KX2tI9mZI2buRipKIw2X1ufdTeaRgwmRabt5bi9chYfhur+/C1OXg3TKg/eag1W+6CCWLVmSauV1owmRPxA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] + '@img/sharp-linux-s390x@0.34.3': + resolution: {integrity: sha512-3gahT+A6c4cdc2edhsLHmIOXMb17ltffJlxR0aC2VPZfwKoTGZec6u5GrFgdR7ciJSsHT27BD3TIuGcuRT0KmQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + '@img/sharp-linux-x64@0.34.1': resolution: {integrity: sha512-wExv7SH9nmoBW3Wr2gvQopX1k8q2g5V5Iag8Zk6AVENsjwd+3adjwxtp3Dcu2QhOXr8W9NusBU6XcQUohBZ5MA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + '@img/sharp-linux-x64@0.34.3': + resolution: {integrity: sha512-8kYso8d806ypnSq3/Ly0QEw90V5ZoHh10yH0HnrzOCr6DKAPI6QVHvwleqMkVQ0m+fc7EH8ah0BB0QPuWY6zJQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + '@img/sharp-linuxmusl-arm64@0.34.1': resolution: {integrity: sha512-DfvyxzHxw4WGdPiTF0SOHnm11Xv4aQexvqhRDAoD00MzHekAj9a/jADXeXYCDFH/DzYruwHbXU7uz+H+nWmSOQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + '@img/sharp-linuxmusl-arm64@0.34.3': + resolution: {integrity: sha512-vAjbHDlr4izEiXM1OTggpCcPg9tn4YriK5vAjowJsHwdBIdx0fYRsURkxLG2RLm9gyBq66gwtWI8Gx0/ov+JKQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + '@img/sharp-linuxmusl-x64@0.34.1': resolution: {integrity: sha512-pax/kTR407vNb9qaSIiWVnQplPcGU8LRIJpDT5o8PdAx5aAA7AS3X9PS8Isw1/WfqgQorPotjrZL3Pqh6C5EBg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + '@img/sharp-linuxmusl-x64@0.34.3': + resolution: {integrity: sha512-gCWUn9547K5bwvOn9l5XGAEjVTTRji4aPTqLzGXHvIr6bIDZKNTA34seMPgM0WmSf+RYBH411VavCejp3PkOeQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + '@img/sharp-wasm32@0.34.1': resolution: {integrity: sha512-YDybQnYrLQfEpzGOQe7OKcyLUCML4YOXl428gOOzBgN6Gw0rv8dpsJ7PqTHxBnXnwXr8S1mYFSLSa727tpz0xg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [wasm32] + '@img/sharp-wasm32@0.34.3': + resolution: {integrity: sha512-+CyRcpagHMGteySaWos8IbnXcHgfDn7pO2fiC2slJxvNq9gDipYBN42/RagzctVRKgxATmfqOSulgZv5e1RdMg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.3': + resolution: {integrity: sha512-MjnHPnbqMXNC2UgeLJtX4XqoVHHlZNd+nPt1kRPmj63wURegwBhZlApELdtxM2OIZDRv/DFtLcNhVbd1z8GYXQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + '@img/sharp-win32-ia32@0.34.1': resolution: {integrity: sha512-WKf/NAZITnonBf3U1LfdjoMgNO5JYRSlhovhRhMxXVdvWYveM4kM3L8m35onYIdh75cOMCo1BexgVQcCDzyoWw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ia32] os: [win32] + '@img/sharp-win32-ia32@0.34.3': + resolution: {integrity: sha512-xuCdhH44WxuXgOM714hn4amodJMZl3OEvf0GVTm0BEyMeA2to+8HEdRPShH0SLYptJY1uBw+SCFP9WVQi1Q/cw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + '@img/sharp-win32-x64@0.34.1': resolution: {integrity: sha512-hw1iIAHpNE8q3uMIRCgGOeDoz9KtFNarFLQclLxr/LK1VBkj8nby18RjFvr6aP7USRYAjTZW6yisnBWMX571Tw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [win32] + '@img/sharp-win32-x64@0.34.3': + resolution: {integrity: sha512-OWwz05d++TxzLEv4VnsTz5CmZ6mI6S05sfQGEMrNrQcOEERbX46332IvE7pO/EUiw7jUrrS40z/M7kPyjfl04g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + '@inquirer/checkbox@2.5.0': resolution: {integrity: sha512-sMgdETOfi2dUHT8r7TT1BTKOwNvdDGFDXYWtQ2J69SvlYNntk9I/gJe7r5yvMwwsuKnYbuRs3pNhx4tgNck5aA==} engines: {node: '>=18'} @@ -2611,8 +2739,8 @@ packages: '@internationalized/number@3.6.1': resolution: {integrity: sha512-UVsb4bCwbL944E0SX50CHFtWEeZ2uB5VozZ5yDXJdq6iPZsZO5p+bjVMZh2GxHf4Bs/7xtDCcPwEa2NU9DaG/g==} - '@internationalized/number@3.6.2': - resolution: {integrity: sha512-E5QTOlMg9wo5OrKdHD6edo1JJlIoOsylh0+mbf0evi1tHJwMZfJSaBpGtnJV9N7w3jeiioox9EG/EWRWPh82vg==} + '@internationalized/number@3.6.4': + resolution: {integrity: sha512-P+/h+RDaiX8EGt3shB9AYM1+QgkvHmJ5rKi4/59k4sg9g58k9rqsRW0WxRO7jCoHyvVbFRRFKmVTdFYdehrxHg==} '@internationalized/string@3.2.6': resolution: {integrity: sha512-LR2lnM4urJta5/wYJVV7m8qk5DrMZmLRTuFhbQO5b9/sKLHgty6unQy1Li4+Su2DWydmB4aZdS5uxBRXIq2aAw==} @@ -3525,6 +3653,9 @@ packages: '@next/env@15.3.2': resolution: {integrity: sha512-xURk++7P7qR9JG1jJtLzPzf0qEvqCN0A/T3DXf8IPMKo9/6FfjxtEffRJIIew/bIL4T3C2jLLqBor8B/zVlx6g==} + '@next/env@15.5.2': + resolution: {integrity: sha512-Qe06ew4zt12LeO6N7j8/nULSOe3fMXE4dM6xgpBQNvdzyK1sv5y4oAP3bq4LamrvGCZtmRYnW8URFCeX5nFgGg==} + '@next/eslint-plugin-next@15.2.1': resolution: {integrity: sha512-6ppeToFd02z38SllzWxayLxjjNfzvc7Wm07gQOKSLjyASvKcXjNStZrLXMHuaWkhjqxe+cnhb2uzfWXm1VEj/Q==} @@ -3543,6 +3674,12 @@ packages: cpu: [arm64] os: [darwin] + '@next/swc-darwin-arm64@15.5.2': + resolution: {integrity: sha512-8bGt577BXGSd4iqFygmzIfTYizHb0LGWqH+qgIF/2EDxS5JsSdERJKA8WgwDyNBZgTIIA4D8qUtoQHmxIIquoQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + '@next/swc-darwin-x64@15.3.0': resolution: {integrity: sha512-m+eO21yg80En8HJ5c49AOQpFDq+nP51nu88ZOMCorvw3g//8g1JSUsEiPSiFpJo1KCTQ+jm9H0hwXK49H/RmXg==} engines: {node: '>= 10'} @@ -3555,6 +3692,12 @@ packages: cpu: [x64] os: [darwin] + '@next/swc-darwin-x64@15.5.2': + resolution: {integrity: sha512-2DjnmR6JHK4X+dgTXt5/sOCu/7yPtqpYt8s8hLkHFK3MGkka2snTv3yRMdHvuRtJVkPwCGsvBSwmoQCHatauFQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + '@next/swc-linux-arm64-gnu@15.3.0': resolution: {integrity: sha512-H0Kk04ZNzb6Aq/G6e0un4B3HekPnyy6D+eUBYPJv9Abx8KDYgNMWzKt4Qhj57HXV3sTTjsfc1Trc1SxuhQB+Tg==} engines: {node: '>= 10'} @@ -3567,6 +3710,12 @@ packages: cpu: [arm64] os: [linux] + '@next/swc-linux-arm64-gnu@15.5.2': + resolution: {integrity: sha512-3j7SWDBS2Wov/L9q0mFJtEvQ5miIqfO4l7d2m9Mo06ddsgUK8gWfHGgbjdFlCp2Ek7MmMQZSxpGFqcC8zGh2AA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + '@next/swc-linux-arm64-musl@15.3.0': resolution: {integrity: sha512-k8GVkdMrh/+J9uIv/GpnHakzgDQhrprJ/FbGQvwWmstaeFG06nnAoZCJV+wO/bb603iKV1BXt4gHG+s2buJqZA==} engines: {node: '>= 10'} @@ -3579,6 +3728,12 @@ packages: cpu: [arm64] os: [linux] + '@next/swc-linux-arm64-musl@15.5.2': + resolution: {integrity: sha512-s6N8k8dF9YGc5T01UPQ08yxsK6fUow5gG1/axWc1HVVBYQBgOjca4oUZF7s4p+kwhkB1bDSGR8QznWrFZ/Rt5g==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + '@next/swc-linux-x64-gnu@15.3.0': resolution: {integrity: sha512-ZMQ9yzDEts/vkpFLRAqfYO1wSpIJGlQNK9gZ09PgyjBJUmg8F/bb8fw2EXKgEaHbCc4gmqMpDfh+T07qUphp9A==} engines: {node: '>= 10'} @@ -3591,6 +3746,12 @@ packages: cpu: [x64] os: [linux] + '@next/swc-linux-x64-gnu@15.5.2': + resolution: {integrity: sha512-o1RV/KOODQh6dM6ZRJGZbc+MOAHww33Vbs5JC9Mp1gDk8cpEO+cYC/l7rweiEalkSm5/1WGa4zY7xrNwObN4+Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + '@next/swc-linux-x64-musl@15.3.0': resolution: {integrity: sha512-RFwq5VKYTw9TMr4T3e5HRP6T4RiAzfDJ6XsxH8j/ZeYq2aLsBqCkFzwMI0FmnSsLaUbOb46Uov0VvN3UciHX5A==} engines: {node: '>= 10'} @@ -3603,6 +3764,12 @@ packages: cpu: [x64] os: [linux] + '@next/swc-linux-x64-musl@15.5.2': + resolution: {integrity: sha512-/VUnh7w8RElYZ0IV83nUcP/J4KJ6LLYliiBIri3p3aW2giF+PAVgZb6mk8jbQSB3WlTai8gEmCAr7kptFa1H6g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + '@next/swc-win32-arm64-msvc@15.3.0': resolution: {integrity: sha512-a7kUbqa/k09xPjfCl0RSVAvEjAkYBYxUzSVAzk2ptXiNEL+4bDBo9wNC43G/osLA/EOGzG4CuNRFnQyIHfkRgQ==} engines: {node: '>= 10'} @@ -3615,6 +3782,12 @@ packages: cpu: [arm64] os: [win32] + '@next/swc-win32-arm64-msvc@15.5.2': + resolution: {integrity: sha512-sMPyTvRcNKXseNQ/7qRfVRLa0VhR0esmQ29DD6pqvG71+JdVnESJaHPA8t7bc67KD5spP3+DOCNLhqlEI2ZgQg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + '@next/swc-win32-x64-msvc@15.3.0': resolution: {integrity: sha512-vHUQS4YVGJPmpjn7r5lEZuMhK5UQBNBRSB+iGDvJjaNk649pTIcRluDWNb9siunyLLiu/LDPHfvxBtNamyuLTw==} engines: {node: '>= 10'} @@ -3627,6 +3800,12 @@ packages: cpu: [x64] os: [win32] + '@next/swc-win32-x64-msvc@15.5.2': + resolution: {integrity: sha512-W5VvyZHnxG/2ukhZF/9Ikdra5fdNftxI6ybeVKYvBPDtyx7x4jPPSNduUkfH5fo3zG0JQ0bPxgy41af2JX5D4Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -7279,86 +7458,80 @@ packages: resolution: {integrity: sha512-/HcYgtUSiJiot/XWGLOlGxPYUG65+/31V8oqk17vZLW1xlCoR4PampyePljOxY2n8/3jz9+tIFzICsyGujJZoA==} engines: {node: '>=18.12.0'} - '@zag-js/accordion@1.15.0': - resolution: {integrity: sha512-EKNeuKx+lOQ/deCe/ApCjVPxpxpDwT2NXvMPL+YvqXmSv7hAnTLs9fDKjbDUQUMmsyx32BsBd8t6d17DL3rPXg==} + '@zag-js/accordion@1.22.1': + resolution: {integrity: sha512-P3jsauxnAGKBhuqs9gdivjEiSu7N7KnKRlgWlIpyti35askz8swHsqxsfkc2ASs9tcPKnPvuZDHIxXmJmZSLuQ==} '@zag-js/anatomy@1.15.0': resolution: {integrity: sha512-r0l5I7mSsF35HdwXm22TppNhfVftFuqvKfHvTUw+wQZhni4eUL93HypJD0Fl7mDhtP5zfVGfBwR048OzD0+tCw==} - '@zag-js/anatomy@1.18.2': - resolution: {integrity: sha512-GxwOUfSDrnwU4oROohKBy0TRKPlYjD0dhuFHo52ZJLSPDkr8H8DlE/y3rFlb6BaGVO/bHjCUeJlaZzZgIpFK0g==} + '@zag-js/anatomy@1.22.1': + resolution: {integrity: sha512-I5OvOuJBt6hEqbpqVkWCOEoDfGMnKuLx+S0h7Un5SyAwnif3F1dSqDYujU28bCy8FtKs36vsq/izxufXyiXSEg==} - '@zag-js/aria-hidden@1.15.0': - resolution: {integrity: sha512-3ogglAasycekTHI34ph16mqwM+VtHCOMtrFHWzPwB16itV5oDEeeMNdQXenHSSyQ/07nJ2QsRGFFjGhPm1kWNg==} + '@zag-js/aria-hidden@1.22.1': + resolution: {integrity: sha512-vPfAE35BfYPS1UbYRcNw8/kMl7uayE7LyRncK/gPMnoQMjmEKW0nXmD5WlCHFLdGX9WFGYTIde8k4U8ay+oqcg==} '@zag-js/carousel@1.15.0': resolution: {integrity: sha512-ZI9H34f2utdJ2Ek6GZa+iuRH4eC99GHD/VEOKLdGani8uadpT2v8M5kUwPGrlAJq9SiPbQ2UuXBmCkmurPQqdA==} - '@zag-js/checkbox@1.15.0': - resolution: {integrity: sha512-6lQvPQNJXt7R0xxdpOuh2qtmAkzdBdqSvFIH7fE6GJzJ/AWiRZh0X+9deLQ76CN4EDUdxizEe7MlQfTI3a56aw==} + '@zag-js/carousel@1.22.1': + resolution: {integrity: sha512-bFbCRe5xarBtD3NnozHmCmrGJ+nLRhqLQFq+RG13fl1hlhUJaJ5AsS7e8L1r2ZLdbVVrsB0lUuW/ocfJ/G4MSw==} + + '@zag-js/checkbox@1.22.1': + resolution: {integrity: sha512-A/cZb89Aeb2k/KGl3ITS2fuLBXwq6Rnq9aFirfKs/UHrY16fopRbRjfqOxF6wm8lWoFk3gqmRGgybo8qsIfxog==} - '@zag-js/collection@1.15.0': - resolution: {integrity: sha512-oC3i6c/oP/FuNPsfgoC1reSXbAvDBGXl0HU3CcvXiNLHbjg2ek8J7kbow6MNuXK6chiksiOHbzKxHl2Oo0Ox7A==} + '@zag-js/collection@1.22.1': + resolution: {integrity: sha512-jjeSKALTH3iK2vTI6uAh2NCtS9n+e2r1cGERKCfNkbt86U6VSp9xiXqalUsEI4ovNIPcgg0+/nzixoVwFO1Vgg==} - '@zag-js/combobox@1.15.0': - resolution: {integrity: sha512-HBck3wcEeIOa7IQMsUkUKbm9cAU7bjoklIyq2zFGn90k7DcDa++oXK9Z2pmcd4TPoBYiyVuuXucaCcjmLX8V/Q==} + '@zag-js/combobox@1.22.1': + resolution: {integrity: sha512-N4tGTmezfHGaKB0+aDB5yMuVzBv2ShgsAx1uizom6ElcvlYD2rsQTr3xLc4wyOR7fx0z6fFDo1+63/Dt3y0t4A==} '@zag-js/core@1.15.0': resolution: {integrity: sha512-P/8F3IXabMhpFnc6hC7GDg3rvUnvY27cuZU04hxjUqTH6+SfORIA/Uvqd4ekhC+dIprL9jicnFrmGgcyelyxfQ==} - '@zag-js/core@1.18.2': - resolution: {integrity: sha512-feKLPL8OMJIegwiGwQwoKI4iB9vA/Gf4d5IOZ+KH0X/5S4lCJ3dswmki+Jtu2Ce2PiyYc7oClvG3CM5mfywtfQ==} - - '@zag-js/dialog@1.15.0': - resolution: {integrity: sha512-Vlt5vySs4u8c8xBEh2JMUvRfPc+aaVEIIUtFVxpc2ORWhBXs9glijyp1yf3rNHJhjj8gqqhF5sEvs3yUTTAk+Q==} + '@zag-js/core@1.22.1': + resolution: {integrity: sha512-4BNrwO9Tadq2Z0d2xSSQs4O/o3OarEHzXM2FQqx46vrwSE57qUghnZex429ZQ51fuk8AL5Lowt26a9JxE9sVPg==} - '@zag-js/dismissable@1.15.0': - resolution: {integrity: sha512-yv575KWy8gA1p4aajOiY5l/nBQ3Xw+Mrjpungp1+wiGd/98eNAIKJ6/adldfbE1Ygd/Q4Dx2VQ7D1AmiTdwUSw==} + '@zag-js/dialog@1.22.1': + resolution: {integrity: sha512-b5KwMPYKc9RenZwxrAAHu6aHPz7tqPy4Mxa/YR5zo1pXBV4amA7u2xnqyncRaK65Z7y5QKmpmDuBp+0PnXxNIA==} - '@zag-js/dismissable@1.18.2': - resolution: {integrity: sha512-uv4FE62TuxWR/wSdr3wfQ9GRW2EHJYt4/HvhVH+mFno2JVRwm9/rSHDUc6QILabXrDVfnp/PdkPJ1rtsIzoOGA==} + '@zag-js/dismissable@1.22.1': + resolution: {integrity: sha512-0DzbykJu9QoXYw4Zcjte69Mtk6ThNRCXWxxCKBf930V8Bw3Ha7vfY5bgdb4RFT5K+BQP3E8vLT+PzIaDINn2Xw==} '@zag-js/dom-query@1.15.0': resolution: {integrity: sha512-z8H/j/Zs0eZEsGpbonScmlKSv0jEXKiAwUCrvQ9Mt6Gz9n0CQRM3MkFclSsM8aeiSv6qKLlhPfkzjl18OLkbgA==} - '@zag-js/dom-query@1.18.2': - resolution: {integrity: sha512-/yUfu4u527vL32mDYwoziEWfLLWfIBenwBo/v8JcDVJwrtBw/1OEPFU7lK9iDa7BAKaIBAGhY0pwsiFLT5UxzA==} + '@zag-js/dom-query@1.22.1': + resolution: {integrity: sha512-mtvGj2z3rkl40mkjd+QwoOHvxqpiOkY4mtVjzNzgzcbVtUN63Mz7giW8OZB+KLy37hwFX0B8JfiQncU8IOHNpw==} - '@zag-js/focus-trap@1.15.0': - resolution: {integrity: sha512-N8m/JpNe1gHUPJlr0hyGUdHg6pAuyJKkBaX0s38cyVntlo2CJhyAWZGuUdocpT2Q3HNPql666FNnH986rYPDKQ==} + '@zag-js/focus-trap@1.22.1': + resolution: {integrity: sha512-6W9cG0LEVICt0srVfWSpamKzsnRxXMdl3gV+GQ5HvkCCk1Sw6Io4tc3QvSSvaWcfyhM07feerOsa2ah7qiT/ig==} - '@zag-js/focus-visible@1.15.0': - resolution: {integrity: sha512-TPXBf47tj6L0hhZNl9AWhuLoVzfPaNPM+/Gw8t9l9Whvy6v9rk/rqUCidY5LsrQuPiKTi7s5WI5J+Wod8ib3gw==} + '@zag-js/focus-visible@1.22.1': + resolution: {integrity: sha512-TuBEux3UTivo9VXPPe79q9JfTwaP/uIshL1KPifg51ofGYesWjMGeE5S5MAuaSzUmH9+3CpnwP7h7f65s3D0kw==} - '@zag-js/i18n-utils@1.15.0': - resolution: {integrity: sha512-anxSbT8kLbJaFJFSb0Ork2j/Lp+XVfMNCIgiBR2BuqUlfX72k23TIJvRxAfwNIkUfs0L8ikaSgLss9OwS4mAnw==} + '@zag-js/i18n-utils@1.22.1': + resolution: {integrity: sha512-45KUYB9tu1br6NmgtaNW9NviozYCYUxJ8aZTI/Y6vKotXK/Pn3bIlaiOaq4Zel7TalGYT8gVnwgPe2E6H5sqTg==} - '@zag-js/interact-outside@1.15.0': - resolution: {integrity: sha512-OwBf/iesQGU9Oq3xe/tcK7gu7xipiGWsmwl2CcScr0fTp3BIMbQywHS928IgPk1DxA8KTHodY8wBjoY1dskfRA==} + '@zag-js/interact-outside@1.22.1': + resolution: {integrity: sha512-+iZ3xHC9+jVo2FCC4B9c9ntcXv19shVOqQGDr2cD30Hwmwtm9kCOdVydMqv3Lp3UhR8a105MXEVUAKg53WbCoA==} - '@zag-js/interact-outside@1.18.2': - resolution: {integrity: sha512-X2S3h/+MM5I83EnWihR2eHJYd1xbqfWeCO+Lz05V6+aWmJHpRPrniHGoVKyKocpSqmgQPrUMqY9ONmVuq4EPRA==} + '@zag-js/menu@1.22.1': + resolution: {integrity: sha512-a5pgQgcpVTVyY6JM8k1WGqelHVKSPwV2CwOv2oGjHWXIr2fpRCAKqZRtytE5PvUP/CZArk8bCjatmgOWe1RdPQ==} - '@zag-js/menu@1.18.2': - resolution: {integrity: sha512-jLxJY0Zg7ogMEJbo6VSDWWbxLGgU0LO1onhP3qqgl/aK1P+Rg5uZC5cq4e2OOjMz1DdMc6WLyjEvcD5pIt8gxQ==} + '@zag-js/number-input@1.22.1': + resolution: {integrity: sha512-E4DROYvSo5TFJMkSmnq+f75wSTL/N7SK6MR8ssNlA2oQp69iVWXhIlFLe4knekX02QJzK1MF97aVU332kAYTeQ==} - '@zag-js/number-input@1.15.0': - resolution: {integrity: sha512-+kK8kyXJhIAbEUnswoMDR+DSJUmvDNIOW0ffuZ9pbfukN3p6zaA3/dCp2Dtg3bQS7hGrFWgtrdejJ8l+mVvUAA==} + '@zag-js/pagination@1.22.1': + resolution: {integrity: sha512-Jeix+sXcfMPm5jer2W4PHSUCgu9a11aC/AOBk6dkxbX8XL23fYXJu5YyOVVq0iQIDWzX4Uij1N/vBha64ARmcA==} - '@zag-js/pagination@1.15.0': - resolution: {integrity: sha512-Z62Q41fQPWqk59QyJk+9J0Ad3H9DCqZ0zZutI6iH8DdzT0A0xxmT6zhup6DM/8C8h0OLlaHFTWQnj0RdRNrnXg==} + '@zag-js/popover@1.22.1': + resolution: {integrity: sha512-27VVkhaEOtiHJYj2j++AzYlAzpMcW0ED05TV9wIT1q0EYzASWxweSBajbnCiQf9TIYzCImDiNVDaCMl5D+TamQ==} - '@zag-js/popover@1.15.0': - resolution: {integrity: sha512-cdzEed3zcGbjSgPQnQnrsuXo2hVVslmSNwQbU5dHcNzG1uxxmtPCIMVeBUmGyJbAFF5XQpKCq/7mIr26dT73vw==} + '@zag-js/popper@1.22.1': + resolution: {integrity: sha512-vBI5WpvE/3ugsimjZaNisOwcECiYfzc+3LIJwaU8od62kInZ1XF6m096BvV7JGwP0FjkMPJrgjcv7weDtY2iDQ==} - '@zag-js/popper@1.15.0': - resolution: {integrity: sha512-Ra/0Ko423KN+8D4+mIFFkeTn9uaHfpxn6UUNIWwZKoiJQvED8DH4dPbLbmvGEoKp6qmisnRHAzi71NLgEhk0Mw==} - - '@zag-js/popper@1.18.2': - resolution: {integrity: sha512-RAhYpUhDyEjW40787d2FGcPHga8Z6t3vUmNLcoAS5FhIPlZcyPNmUo6PEPndo3Or8ZKrTNFzrU4qOQZ3Jvbf0w==} - - '@zag-js/rating-group@1.15.0': - resolution: {integrity: sha512-omGKN97FhplFwBX9J/Mj7BCZuwFXSXssSVTKU7Yp2d1Cmxhez4+Ju7KdSRNnIoWB4OxFCxwZyaAPTcg3E0Pjrg==} + '@zag-js/rating-group@1.22.1': + resolution: {integrity: sha512-QxBK+hpfkQ4yFHUr1YOSwEQ3LuTrdS32J9zV8UyHu8HbgwzfR7L8ZAa1PUUmG65tupzua2pbn1NioOkMvDmBOQ==} '@zag-js/react@1.15.0': resolution: {integrity: sha512-YSp9QBkdeBfZt4nVhJW+CUd5sNEEVAuwkmoZWDFUoDoWSAXwzSKuHCmTm5/8DaXg1IZD2bMrXgMNDqZv2x0hZw==} @@ -7366,53 +7539,65 @@ packages: react: '>=18.0.0' react-dom: '>=18.0.0' - '@zag-js/rect-utils@1.18.2': - resolution: {integrity: sha512-g26aaea2vWOG9Ahg1Y8HSRmOwMbgWjStvIo+KtP9ToKwoulQ7+xB3x13O3eDyGed2YBLnFjBj8lZB5Epwq39iQ==} + '@zag-js/react@1.22.1': + resolution: {integrity: sha512-TcIKkNo9EFel+d92nb7104voKJNDiMkqq9nn7Ozq/TE8A62JPf5zk8y8zqoxTbGDTTk+tDjW7Sm1IKb4r6rX4w==} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@zag-js/rect-utils@1.22.1': + resolution: {integrity: sha512-jtI03SR9kF0AcBffoFI/TKXn5KyhjNCtsGlqbWw0dKbhWTNy1v432FDC5opmmnH8W5LjjWebIzo4QtO5+632QQ==} - '@zag-js/remove-scroll@1.15.0': - resolution: {integrity: sha512-vdWSAdgY8wJ7s4YeaKwTMwmZiRMBxCehmdktSxBWvwtAjU1cM3UWvjmZ9E6INJrQXxH9vDpe/rpFSyv1guIQIw==} + '@zag-js/remove-scroll@1.22.1': + resolution: {integrity: sha512-2TrS8ljp8SADX5xRB/+KGBCBYbYTeH0k5IEalG2rt8ReNyNAW1JfCrm53KCVoCg9YmxKF3MrxPgPT83MNFsJhQ==} '@zag-js/scroll-snap@1.15.0': resolution: {integrity: sha512-/LfBlsjoR4tVL3Djus3k9jKLhwC2ApdHTACxEc72TAewoPe4M8icnSDLXmKHvwwOhzK0HlFz8wGm6ZncAbQbuA==} - '@zag-js/select@1.15.0': - resolution: {integrity: sha512-4urUBADzhrsGEO/UsqHdjsgmDdF15Zzeid3ejEbIMTrkt2/mMMcQ1CShuxtsWqm2EUBz/N1kOcZlE6Tq69n7Xg==} + '@zag-js/scroll-snap@1.22.1': + resolution: {integrity: sha512-kctqJiteALaavoHEpYBDSPgUErIdwAoY5jcrU4Mq5L8FjtI4tSNr8BWcXzSBK2UVqaKN+vDo+PDcj7XIXTUQJA==} + + '@zag-js/select@1.22.1': + resolution: {integrity: sha512-sWq0RqlJvmj0heJDpfS3OfM1ynSSCW+fYY5v3T/QyH4qneqB8OJjgh8EEBaHlOkbqv/oBsk855U8/o6jegfUxw==} - '@zag-js/slider@1.15.0': - resolution: {integrity: sha512-NYIsn3GKXIoPmvkDXsQmw9wdYg3QHbYHXnZ8Ewl2fVubN7S5mDlHSZs2iDVsBvX+a4RChWFRO6JHX8E1+BncOg==} + '@zag-js/slider@1.22.1': + resolution: {integrity: sha512-aricrX99r21RAS9TyPNTJL8gE8mNRSQMy7TIXTa9aoeRjN0Cf6+PSksKfmPdP9l249/nplGqvC25Ck7XUVJn6A==} - '@zag-js/steps@1.15.0': - resolution: {integrity: sha512-VoIDcDIEErZawmW2m0yTGlffqjfRuSwR37K9LdSRy8Q4Qzz3wV7jASaTjMhTya1hlreJ7tJg+Qbjqowvw9GndA==} + '@zag-js/steps@1.22.1': + resolution: {integrity: sha512-eJCHbHG9aGAbzb/IQCqpmk6fmwSmIfocAxNKVTljroD6OHkBtqgaZQVS3q4xyjz61nB/d/0ZlsvpCVjm1EhwBw==} '@zag-js/store@1.15.0': resolution: {integrity: sha512-ecqjcy3b1GsULpsT8RVJV9KDaikajRN0XRg48HMvaGkaPIvxI6esyrE6RKnShuqr2eVXIPghgBnCnrJUev4UlA==} - '@zag-js/switch@1.15.0': - resolution: {integrity: sha512-2CaAUTi7jM4lJjCYoSE1HWlFPCifI5GR+hufWOCYKpanf8VA/LM+t/a2Aq5QoBsWdcQv3B9mHxF/aVTDbnCKPQ==} + '@zag-js/store@1.22.1': + resolution: {integrity: sha512-KrMWi/Fa4cqOjx2zDSMIu6vztFYik+V3K6VPWRVONM4FkboLpTqAEayzwgTTNqMK9iYYZIYjhiPhAVLW9iLuBg==} - '@zag-js/tabs@1.15.0': - resolution: {integrity: sha512-voHWpibC1TKLmbAJfixOesxrCio7wK+gdLRvh7Xh5u+3VSsT2fP2wEw3ySkJbpw3MpEE7R2OWkInbCV/SwPcsA==} + '@zag-js/switch@1.22.1': + resolution: {integrity: sha512-ipmBHEqtcrPYr5WS5Juj5dt4GFIqr81NYVNe8RHMW8jIHgHhRCRj3TokGXVlZ7HdseCKTTNNrcvRFBr1sJBbOw==} - '@zag-js/toast@1.15.0': - resolution: {integrity: sha512-0RupMCXyGr7/La4Zlei7VqBF0VPNJelGd7zimLboe+IKZyy4Ypi/N2IX14rl8JZQDsDEgkLUl33xrSk/9RW2nQ==} + '@zag-js/tabs@1.22.1': + resolution: {integrity: sha512-B0WHW36uuR+pu/24X0yI4eyvSwo7WmqOc5C3ohZHOf03zkmMJdtMtVQSotKr7qhGMt5updCgs68MR7jAmmc1Lw==} - '@zag-js/tooltip@1.15.0': - resolution: {integrity: sha512-sOpVECyfdS4RZBx46mSV+RPc9C5k9JvYQYUfoOVWh0E5RLSEz5bQm5xxctKOHfCOv+vJNTfG5gP596B1r2+Fkw==} + '@zag-js/toast@1.22.1': + resolution: {integrity: sha512-cxcfbMftA//ggOAlxG3q04WZVL/mMVklvtQ2rSyj3oRmnwocJPYXtJzKIRazWBjji3u3BOA+ZeOI1AcGrfp/TQ==} - '@zag-js/tree-view@1.15.0': - resolution: {integrity: sha512-wqdd+hu1bDOCWtnZ8MarRFHqbZF2t8qKBM3kO42IBq7jTI/93LCkHSlceEPft9dgZ6Ea9km0YJMHhoTqCPZ/fw==} + '@zag-js/tooltip@1.22.1': + resolution: {integrity: sha512-0ub0p22CzYnaXv0prAnWNjqUBkdw4nO4yGk5qntaodajpLNQ4gSdq7Hj4afHzJqwbKAkwb3KzJFqcqIm9Y/dfw==} + + '@zag-js/tree-view@1.22.1': + resolution: {integrity: sha512-AQmOn1mB+nLJEaq0xdSVnTI8Vt3nB3OweqdB12jkbdIOcWI9eY0RfhiNHC0k0mgAw+dMjyn84op/gOd9VVdtmA==} '@zag-js/types@1.15.0': resolution: {integrity: sha512-lV2ov2M07BlmjDUCSwBeHxPApHI3oAiLytG94AqcYvQ0BtsCRo5T60yRQ0syFc6fHf0e9+kwt89uoIgfGFYfmw==} - '@zag-js/types@1.18.2': - resolution: {integrity: sha512-iyKwrhRLbMs+y22j8PdqdW7waIo98jbneNI4MmXOVbQUe2AgSfDnapL/JuO58hJ7vshdYrmkcoMzehwNSkYKXw==} + '@zag-js/types@1.22.1': + resolution: {integrity: sha512-lvpDSMR96e7H7TdwOiVpMzj6css5Ydix1nBi7BlmjME6v5OPR0KZwVDGD6h5UtTeVjPq8dPaqM8TJWw+QwbQSw==} '@zag-js/utils@1.15.0': resolution: {integrity: sha512-XctFny5H8C00BsougV40Yp0qVEj9M2d/NRme7B33mon9wG+3hscZwP6miJmF6BYI5Pgu6e2P0Sv45FddQU1Tkg==} - '@zag-js/utils@1.18.2': - resolution: {integrity: sha512-tGrG2Qnm5qf95VJEBHunrEDHO0OJZGU81FoZU2VNC+YkRmD4C1phcvyxVLklDFT569rNxIHmFn/6gr9D7HmPrQ==} + '@zag-js/utils@1.22.1': + resolution: {integrity: sha512-VXY4gjHaTENHW+wjnKKENZ2jcaW0vnG2a5lYEMuZR4dpNCKH217yFr/bCNrI44y2s1W3LWhWmpEjfZluP6udYg==} '@zkochan/js-yaml@0.0.7': resolution: {integrity: sha512-nrUSn7hzt7J6JWgWGz78ZYI8wj+gdIJdk0Ynjpp8l+trkn58Uqsf6RYrYkEK+3X18EX+TNdtJI0WxAtc+L84SQ==} @@ -8896,6 +9081,12 @@ packages: peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + eslint-plugin-tailwindcss@4.0.0-beta.0: + resolution: {integrity: sha512-WWCajZgQu38Sd67ZCl2W6i3MRzqB0d+H8s4qV9iB6lBJbsDOIpIlj6R1Fj2FXkoWErbo05pZnZYbCGIU9o/DsA==} + engines: {node: '>=18.12.0'} + peerDependencies: + tailwindcss: ^3.4.0 || ^4.0.0 + eslint-scope@5.1.1: resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} engines: {node: '>=8.0.0'} @@ -10720,6 +10911,27 @@ packages: sass: optional: true + next@15.5.2: + resolution: {integrity: sha512-H8Otr7abj1glFhbGnvUt3gz++0AF1+QoCXEBmd/6aKbfdFwrn0LpA836Ed5+00va/7HQSDD+mOoVhn3tNy3e/Q==} + engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.51.1 + babel-plugin-react-compiler: '*' + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + babel-plugin-react-compiler: + optional: true + sass: + optional: true + no-case@3.0.4: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} @@ -11929,6 +12141,10 @@ packages: resolution: {integrity: sha512-1j0w61+eVxu7DawFJtnfYcvSv6qPFvfTaqzTQ2BLknVhHTwGS8sc63ZBF4rzkWMBVKybo4S5OBtDdZahh2A1xg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + sharp@0.34.3: + resolution: {integrity: sha512-eX2IQ6nFohW4DbvHIOLRB3MHFpYqaqvXd3Tp5e/T/dSH83fxaNJQRvDMhASmkNTsNTVF2/OOopzRCt7xokgPfg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -12269,6 +12485,16 @@ packages: peerDependencies: tailwindcss: '*' + tailwind-variants@3.1.0: + resolution: {integrity: sha512-ieiYaEfUr+sNhw/k++dosmZfVA4VIG5bV+G1eGdJSC4FcflqQv0iSIlOLj/RbzRuTu/VrIiNSlwh1esBM3BXUg==} + engines: {node: '>=16.x', pnpm: '>=7.x'} + peerDependencies: + tailwind-merge: '>=3.0.0' + tailwindcss: '*' + peerDependenciesMeta: + tailwind-merge: + optional: true + tailwindcss-animate@1.0.7: resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} peerDependencies: @@ -14558,6 +14784,11 @@ snapshots: dependencies: tslib: 2.8.1 + '@emnapi/runtime@1.5.0': + dependencies: + tslib: 2.8.1 + optional: true + '@emnapi/wasi-threads@1.0.2': dependencies: tslib: 2.8.1 @@ -14827,6 +15058,10 @@ snapshots: dependencies: '@floating-ui/utils': 0.2.10 + '@floating-ui/core@1.7.3': + dependencies: + '@floating-ui/utils': 0.2.10 + '@floating-ui/dom@1.7.1': dependencies: '@floating-ui/core': 1.7.1 @@ -14837,6 +15072,11 @@ snapshots: '@floating-ui/core': 1.7.2 '@floating-ui/utils': 0.2.10 + '@floating-ui/dom@1.7.4': + dependencies: + '@floating-ui/core': 1.7.3 + '@floating-ui/utils': 0.2.10 + '@floating-ui/react-dom@2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@floating-ui/dom': 1.7.1 @@ -15017,14 +15257,6 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} - '@hyoban/eslint-plugin-tailwindcss@4.0.0-alpha.12(tailwindcss@4.1.8)': - dependencies: - fast-glob: 3.3.3 - postcss: 8.5.3 - synckit: 0.11.8 - tailwind-api-utils: 1.0.3(tailwindcss@4.1.8) - tailwindcss: 4.1.8 - '@iconify/json@2.2.338': dependencies: '@iconify/types': 2.0.0 @@ -15058,79 +15290,165 @@ snapshots: '@img/sharp-libvips-darwin-arm64': 1.1.0 optional: true + '@img/sharp-darwin-arm64@0.34.3': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.0 + optional: true + '@img/sharp-darwin-x64@0.34.1': optionalDependencies: '@img/sharp-libvips-darwin-x64': 1.1.0 optional: true + '@img/sharp-darwin-x64@0.34.3': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.0 + optional: true + '@img/sharp-libvips-darwin-arm64@1.1.0': optional: true + '@img/sharp-libvips-darwin-arm64@1.2.0': + optional: true + '@img/sharp-libvips-darwin-x64@1.1.0': optional: true + '@img/sharp-libvips-darwin-x64@1.2.0': + optional: true + '@img/sharp-libvips-linux-arm64@1.1.0': optional: true + '@img/sharp-libvips-linux-arm64@1.2.0': + optional: true + '@img/sharp-libvips-linux-arm@1.1.0': optional: true + '@img/sharp-libvips-linux-arm@1.2.0': + optional: true + '@img/sharp-libvips-linux-ppc64@1.1.0': optional: true + '@img/sharp-libvips-linux-ppc64@1.2.0': + optional: true + '@img/sharp-libvips-linux-s390x@1.1.0': optional: true + '@img/sharp-libvips-linux-s390x@1.2.0': + optional: true + '@img/sharp-libvips-linux-x64@1.1.0': optional: true + '@img/sharp-libvips-linux-x64@1.2.0': + optional: true + '@img/sharp-libvips-linuxmusl-arm64@1.1.0': optional: true + '@img/sharp-libvips-linuxmusl-arm64@1.2.0': + optional: true + '@img/sharp-libvips-linuxmusl-x64@1.1.0': optional: true + '@img/sharp-libvips-linuxmusl-x64@1.2.0': + optional: true + '@img/sharp-linux-arm64@0.34.1': optionalDependencies: '@img/sharp-libvips-linux-arm64': 1.1.0 optional: true + '@img/sharp-linux-arm64@0.34.3': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.0 + optional: true + '@img/sharp-linux-arm@0.34.1': optionalDependencies: '@img/sharp-libvips-linux-arm': 1.1.0 optional: true + '@img/sharp-linux-arm@0.34.3': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.0 + optional: true + + '@img/sharp-linux-ppc64@0.34.3': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.0 + optional: true + '@img/sharp-linux-s390x@0.34.1': optionalDependencies: '@img/sharp-libvips-linux-s390x': 1.1.0 optional: true + '@img/sharp-linux-s390x@0.34.3': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.0 + optional: true + '@img/sharp-linux-x64@0.34.1': optionalDependencies: '@img/sharp-libvips-linux-x64': 1.1.0 optional: true + '@img/sharp-linux-x64@0.34.3': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.0 + optional: true + '@img/sharp-linuxmusl-arm64@0.34.1': optionalDependencies: '@img/sharp-libvips-linuxmusl-arm64': 1.1.0 optional: true + '@img/sharp-linuxmusl-arm64@0.34.3': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.0 + optional: true + '@img/sharp-linuxmusl-x64@0.34.1': optionalDependencies: '@img/sharp-libvips-linuxmusl-x64': 1.1.0 optional: true + '@img/sharp-linuxmusl-x64@0.34.3': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.0 + optional: true + '@img/sharp-wasm32@0.34.1': dependencies: '@emnapi/runtime': 1.4.3 optional: true + '@img/sharp-wasm32@0.34.3': + dependencies: + '@emnapi/runtime': 1.5.0 + optional: true + + '@img/sharp-win32-arm64@0.34.3': + optional: true + '@img/sharp-win32-ia32@0.34.1': optional: true + '@img/sharp-win32-ia32@0.34.3': + optional: true + '@img/sharp-win32-x64@0.34.1': optional: true + '@img/sharp-win32-x64@0.34.3': + optional: true + '@inquirer/checkbox@2.5.0': dependencies: '@inquirer/core': 9.2.1 @@ -15182,7 +15500,7 @@ snapshots: dependencies: '@swc/helpers': 0.5.17 - '@internationalized/number@3.6.2': + '@internationalized/number@3.6.4': dependencies: '@swc/helpers': 0.5.17 @@ -16507,6 +16825,8 @@ snapshots: '@next/env@15.3.2': {} + '@next/env@15.5.2': {} + '@next/eslint-plugin-next@15.2.1': dependencies: fast-glob: 3.3.1 @@ -16521,48 +16841,72 @@ snapshots: '@next/swc-darwin-arm64@15.3.2': optional: true + '@next/swc-darwin-arm64@15.5.2': + optional: true + '@next/swc-darwin-x64@15.3.0': optional: true '@next/swc-darwin-x64@15.3.2': optional: true + '@next/swc-darwin-x64@15.5.2': + optional: true + '@next/swc-linux-arm64-gnu@15.3.0': optional: true '@next/swc-linux-arm64-gnu@15.3.2': optional: true + '@next/swc-linux-arm64-gnu@15.5.2': + optional: true + '@next/swc-linux-arm64-musl@15.3.0': optional: true '@next/swc-linux-arm64-musl@15.3.2': optional: true + '@next/swc-linux-arm64-musl@15.5.2': + optional: true + '@next/swc-linux-x64-gnu@15.3.0': optional: true '@next/swc-linux-x64-gnu@15.3.2': optional: true + '@next/swc-linux-x64-gnu@15.5.2': + optional: true + '@next/swc-linux-x64-musl@15.3.0': optional: true '@next/swc-linux-x64-musl@15.3.2': optional: true + '@next/swc-linux-x64-musl@15.5.2': + optional: true + '@next/swc-win32-arm64-msvc@15.3.0': optional: true '@next/swc-win32-arm64-msvc@15.3.2': optional: true + '@next/swc-win32-arm64-msvc@15.5.2': + optional: true + '@next/swc-win32-x64-msvc@15.3.0': optional: true '@next/swc-win32-x64-msvc@15.3.2': optional: true + '@next/swc-win32-x64-msvc@15.5.2': + optional: true + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -22659,19 +23003,19 @@ snapshots: js-yaml: 3.14.1 tslib: 2.8.1 - '@zag-js/accordion@1.15.0': + '@zag-js/accordion@1.22.1': dependencies: - '@zag-js/anatomy': 1.15.0 - '@zag-js/core': 1.15.0 - '@zag-js/dom-query': 1.15.0 - '@zag-js/types': 1.15.0 - '@zag-js/utils': 1.15.0 + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 '@zag-js/anatomy@1.15.0': {} - '@zag-js/anatomy@1.18.2': {} + '@zag-js/anatomy@1.22.1': {} - '@zag-js/aria-hidden@1.15.0': {} + '@zag-js/aria-hidden@1.22.1': {} '@zag-js/carousel@1.15.0': dependencies: @@ -22682,155 +23026,147 @@ snapshots: '@zag-js/types': 1.15.0 '@zag-js/utils': 1.15.0 - '@zag-js/checkbox@1.15.0': + '@zag-js/carousel@1.22.1': dependencies: - '@zag-js/anatomy': 1.15.0 - '@zag-js/core': 1.15.0 - '@zag-js/dom-query': 1.15.0 - '@zag-js/focus-visible': 1.15.0 - '@zag-js/types': 1.15.0 - '@zag-js/utils': 1.15.0 + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/scroll-snap': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 - '@zag-js/collection@1.15.0': + '@zag-js/checkbox@1.22.1': dependencies: - '@zag-js/utils': 1.15.0 + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/focus-visible': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 - '@zag-js/combobox@1.15.0': + '@zag-js/collection@1.22.1': dependencies: - '@zag-js/anatomy': 1.15.0 - '@zag-js/aria-hidden': 1.15.0 - '@zag-js/collection': 1.15.0 - '@zag-js/core': 1.15.0 - '@zag-js/dismissable': 1.15.0 - '@zag-js/dom-query': 1.15.0 - '@zag-js/popper': 1.15.0 - '@zag-js/types': 1.15.0 - '@zag-js/utils': 1.15.0 + '@zag-js/utils': 1.22.1 + + '@zag-js/combobox@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/aria-hidden': 1.22.1 + '@zag-js/collection': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dismissable': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/popper': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 '@zag-js/core@1.15.0': dependencies: '@zag-js/dom-query': 1.15.0 '@zag-js/utils': 1.15.0 - '@zag-js/core@1.18.2': + '@zag-js/core@1.22.1': dependencies: - '@zag-js/dom-query': 1.18.2 - '@zag-js/utils': 1.18.2 + '@zag-js/dom-query': 1.22.1 + '@zag-js/utils': 1.22.1 - '@zag-js/dialog@1.15.0': + '@zag-js/dialog@1.22.1': dependencies: - '@zag-js/anatomy': 1.15.0 - '@zag-js/aria-hidden': 1.15.0 - '@zag-js/core': 1.15.0 - '@zag-js/dismissable': 1.15.0 - '@zag-js/dom-query': 1.15.0 - '@zag-js/focus-trap': 1.15.0 - '@zag-js/remove-scroll': 1.15.0 - '@zag-js/types': 1.15.0 - '@zag-js/utils': 1.15.0 - - '@zag-js/dismissable@1.15.0': - dependencies: - '@zag-js/dom-query': 1.15.0 - '@zag-js/interact-outside': 1.15.0 - '@zag-js/utils': 1.15.0 + '@zag-js/anatomy': 1.22.1 + '@zag-js/aria-hidden': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dismissable': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/focus-trap': 1.22.1 + '@zag-js/remove-scroll': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 - '@zag-js/dismissable@1.18.2': + '@zag-js/dismissable@1.22.1': dependencies: - '@zag-js/dom-query': 1.18.2 - '@zag-js/interact-outside': 1.18.2 - '@zag-js/utils': 1.18.2 + '@zag-js/dom-query': 1.22.1 + '@zag-js/interact-outside': 1.22.1 + '@zag-js/utils': 1.22.1 '@zag-js/dom-query@1.15.0': dependencies: '@zag-js/types': 1.15.0 - '@zag-js/dom-query@1.18.2': + '@zag-js/dom-query@1.22.1': dependencies: - '@zag-js/types': 1.18.2 + '@zag-js/types': 1.22.1 - '@zag-js/focus-trap@1.15.0': + '@zag-js/focus-trap@1.22.1': dependencies: - '@zag-js/dom-query': 1.15.0 + '@zag-js/dom-query': 1.22.1 - '@zag-js/focus-visible@1.15.0': + '@zag-js/focus-visible@1.22.1': dependencies: - '@zag-js/dom-query': 1.15.0 + '@zag-js/dom-query': 1.22.1 - '@zag-js/i18n-utils@1.15.0': + '@zag-js/i18n-utils@1.22.1': dependencies: - '@zag-js/dom-query': 1.15.0 + '@zag-js/dom-query': 1.22.1 - '@zag-js/interact-outside@1.15.0': + '@zag-js/interact-outside@1.22.1': dependencies: - '@zag-js/dom-query': 1.15.0 - '@zag-js/utils': 1.15.0 + '@zag-js/dom-query': 1.22.1 + '@zag-js/utils': 1.22.1 - '@zag-js/interact-outside@1.18.2': + '@zag-js/menu@1.22.1': dependencies: - '@zag-js/dom-query': 1.18.2 - '@zag-js/utils': 1.18.2 + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dismissable': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/popper': 1.22.1 + '@zag-js/rect-utils': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 - '@zag-js/menu@1.18.2': + '@zag-js/number-input@1.22.1': dependencies: - '@zag-js/anatomy': 1.18.2 - '@zag-js/core': 1.18.2 - '@zag-js/dismissable': 1.18.2 - '@zag-js/dom-query': 1.18.2 - '@zag-js/popper': 1.18.2 - '@zag-js/rect-utils': 1.18.2 - '@zag-js/types': 1.18.2 - '@zag-js/utils': 1.18.2 + '@internationalized/number': 3.6.4 + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 - '@zag-js/number-input@1.15.0': + '@zag-js/pagination@1.22.1': dependencies: - '@internationalized/number': 3.6.2 - '@zag-js/anatomy': 1.15.0 - '@zag-js/core': 1.15.0 - '@zag-js/dom-query': 1.15.0 - '@zag-js/types': 1.15.0 - '@zag-js/utils': 1.15.0 + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 - '@zag-js/pagination@1.15.0': + '@zag-js/popover@1.22.1': dependencies: - '@zag-js/anatomy': 1.15.0 - '@zag-js/core': 1.15.0 - '@zag-js/dom-query': 1.15.0 - '@zag-js/types': 1.15.0 - '@zag-js/utils': 1.15.0 - - '@zag-js/popover@1.15.0': - dependencies: - '@zag-js/anatomy': 1.15.0 - '@zag-js/aria-hidden': 1.15.0 - '@zag-js/core': 1.15.0 - '@zag-js/dismissable': 1.15.0 - '@zag-js/dom-query': 1.15.0 - '@zag-js/focus-trap': 1.15.0 - '@zag-js/popper': 1.15.0 - '@zag-js/remove-scroll': 1.15.0 - '@zag-js/types': 1.15.0 - '@zag-js/utils': 1.15.0 - - '@zag-js/popper@1.15.0': - dependencies: - '@floating-ui/dom': 1.7.1 - '@zag-js/dom-query': 1.15.0 - '@zag-js/utils': 1.15.0 + '@zag-js/anatomy': 1.22.1 + '@zag-js/aria-hidden': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dismissable': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/focus-trap': 1.22.1 + '@zag-js/popper': 1.22.1 + '@zag-js/remove-scroll': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 - '@zag-js/popper@1.18.2': + '@zag-js/popper@1.22.1': dependencies: - '@floating-ui/dom': 1.7.2 - '@zag-js/dom-query': 1.18.2 - '@zag-js/utils': 1.18.2 + '@floating-ui/dom': 1.7.4 + '@zag-js/dom-query': 1.22.1 + '@zag-js/utils': 1.22.1 - '@zag-js/rating-group@1.15.0': + '@zag-js/rating-group@1.22.1': dependencies: - '@zag-js/anatomy': 1.15.0 - '@zag-js/core': 1.15.0 - '@zag-js/dom-query': 1.15.0 - '@zag-js/types': 1.15.0 - '@zag-js/utils': 1.15.0 + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 '@zag-js/react@1.15.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: @@ -22841,104 +23177,120 @@ snapshots: react: 19.1.0 react-dom: 19.1.0(react@19.1.0) - '@zag-js/rect-utils@1.18.2': {} + '@zag-js/react@1.22.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@zag-js/core': 1.22.1 + '@zag-js/store': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@zag-js/rect-utils@1.22.1': {} - '@zag-js/remove-scroll@1.15.0': + '@zag-js/remove-scroll@1.22.1': dependencies: - '@zag-js/dom-query': 1.15.0 + '@zag-js/dom-query': 1.22.1 '@zag-js/scroll-snap@1.15.0': dependencies: '@zag-js/dom-query': 1.15.0 - '@zag-js/select@1.15.0': + '@zag-js/scroll-snap@1.22.1': dependencies: - '@zag-js/anatomy': 1.15.0 - '@zag-js/collection': 1.15.0 - '@zag-js/core': 1.15.0 - '@zag-js/dismissable': 1.15.0 - '@zag-js/dom-query': 1.15.0 - '@zag-js/popper': 1.15.0 - '@zag-js/types': 1.15.0 - '@zag-js/utils': 1.15.0 + '@zag-js/dom-query': 1.22.1 - '@zag-js/slider@1.15.0': + '@zag-js/select@1.22.1': dependencies: - '@zag-js/anatomy': 1.15.0 - '@zag-js/core': 1.15.0 - '@zag-js/dom-query': 1.15.0 - '@zag-js/types': 1.15.0 - '@zag-js/utils': 1.15.0 + '@zag-js/anatomy': 1.22.1 + '@zag-js/collection': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dismissable': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/popper': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 - '@zag-js/steps@1.15.0': + '@zag-js/slider@1.22.1': dependencies: - '@zag-js/anatomy': 1.15.0 - '@zag-js/core': 1.15.0 - '@zag-js/dom-query': 1.15.0 - '@zag-js/types': 1.15.0 - '@zag-js/utils': 1.15.0 + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/steps@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 '@zag-js/store@1.15.0': dependencies: proxy-compare: 3.0.1 - '@zag-js/switch@1.15.0': + '@zag-js/store@1.22.1': dependencies: - '@zag-js/anatomy': 1.15.0 - '@zag-js/core': 1.15.0 - '@zag-js/dom-query': 1.15.0 - '@zag-js/focus-visible': 1.15.0 - '@zag-js/types': 1.15.0 - '@zag-js/utils': 1.15.0 + proxy-compare: 3.0.1 - '@zag-js/tabs@1.15.0': + '@zag-js/switch@1.22.1': dependencies: - '@zag-js/anatomy': 1.15.0 - '@zag-js/core': 1.15.0 - '@zag-js/dom-query': 1.15.0 - '@zag-js/types': 1.15.0 - '@zag-js/utils': 1.15.0 + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/focus-visible': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 - '@zag-js/toast@1.15.0': + '@zag-js/tabs@1.22.1': dependencies: - '@zag-js/anatomy': 1.15.0 - '@zag-js/core': 1.15.0 - '@zag-js/dismissable': 1.15.0 - '@zag-js/dom-query': 1.15.0 - '@zag-js/types': 1.15.0 - '@zag-js/utils': 1.15.0 + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 - '@zag-js/tooltip@1.15.0': + '@zag-js/toast@1.22.1': dependencies: - '@zag-js/anatomy': 1.15.0 - '@zag-js/core': 1.15.0 - '@zag-js/dom-query': 1.15.0 - '@zag-js/focus-visible': 1.15.0 - '@zag-js/popper': 1.15.0 - '@zag-js/store': 1.15.0 - '@zag-js/types': 1.15.0 - '@zag-js/utils': 1.15.0 + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dismissable': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 - '@zag-js/tree-view@1.15.0': + '@zag-js/tooltip@1.22.1': dependencies: - '@zag-js/anatomy': 1.15.0 - '@zag-js/collection': 1.15.0 - '@zag-js/core': 1.15.0 - '@zag-js/dom-query': 1.15.0 - '@zag-js/types': 1.15.0 - '@zag-js/utils': 1.15.0 + '@zag-js/anatomy': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/focus-visible': 1.22.1 + '@zag-js/popper': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 + + '@zag-js/tree-view@1.22.1': + dependencies: + '@zag-js/anatomy': 1.22.1 + '@zag-js/collection': 1.22.1 + '@zag-js/core': 1.22.1 + '@zag-js/dom-query': 1.22.1 + '@zag-js/types': 1.22.1 + '@zag-js/utils': 1.22.1 '@zag-js/types@1.15.0': dependencies: csstype: 3.1.3 - '@zag-js/types@1.18.2': + '@zag-js/types@1.22.1': dependencies: csstype: 3.1.3 '@zag-js/utils@1.15.0': {} - '@zag-js/utils@1.18.2': {} + '@zag-js/utils@1.22.1': {} '@zkochan/js-yaml@0.0.7': dependencies: @@ -24624,6 +24976,14 @@ snapshots: string.prototype.matchall: 4.0.12 string.prototype.repeat: 1.0.0 + eslint-plugin-tailwindcss@4.0.0-beta.0(tailwindcss@4.1.8): + dependencies: + fast-glob: 3.3.3 + postcss: 8.5.3 + synckit: 0.11.8 + tailwind-api-utils: 1.0.3(tailwindcss@4.1.8) + tailwindcss: 4.1.8 + eslint-scope@5.1.1: dependencies: esrecurse: 4.3.0 @@ -26785,6 +27145,32 @@ snapshots: - '@babel/core' - babel-plugin-macros + next@15.5.2(@babel/core@7.27.1)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(babel-plugin-macros@3.1.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.84.0): + dependencies: + '@next/env': 15.5.2 + '@swc/helpers': 0.5.15 + caniuse-lite: 1.0.30001718 + postcss: 8.4.31 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + styled-jsx: 5.1.6(@babel/core@7.27.1)(babel-plugin-macros@3.1.0)(react@19.1.0) + optionalDependencies: + '@next/swc-darwin-arm64': 15.5.2 + '@next/swc-darwin-x64': 15.5.2 + '@next/swc-linux-arm64-gnu': 15.5.2 + '@next/swc-linux-arm64-musl': 15.5.2 + '@next/swc-linux-x64-gnu': 15.5.2 + '@next/swc-linux-x64-musl': 15.5.2 + '@next/swc-win32-arm64-msvc': 15.5.2 + '@next/swc-win32-x64-msvc': 15.5.2 + '@opentelemetry/api': 1.9.0 + '@playwright/test': 1.52.0 + sass: 1.84.0 + sharp: 0.34.3 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + no-case@3.0.4: dependencies: lower-case: 2.0.2 @@ -28506,6 +28892,36 @@ snapshots: '@img/sharp-win32-x64': 0.34.1 optional: true + sharp@0.34.3: + dependencies: + color: 4.2.3 + detect-libc: 2.0.4 + semver: 7.7.2 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.3 + '@img/sharp-darwin-x64': 0.34.3 + '@img/sharp-libvips-darwin-arm64': 1.2.0 + '@img/sharp-libvips-darwin-x64': 1.2.0 + '@img/sharp-libvips-linux-arm': 1.2.0 + '@img/sharp-libvips-linux-arm64': 1.2.0 + '@img/sharp-libvips-linux-ppc64': 1.2.0 + '@img/sharp-libvips-linux-s390x': 1.2.0 + '@img/sharp-libvips-linux-x64': 1.2.0 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.0 + '@img/sharp-libvips-linuxmusl-x64': 1.2.0 + '@img/sharp-linux-arm': 0.34.3 + '@img/sharp-linux-arm64': 0.34.3 + '@img/sharp-linux-ppc64': 0.34.3 + '@img/sharp-linux-s390x': 0.34.3 + '@img/sharp-linux-x64': 0.34.3 + '@img/sharp-linuxmusl-arm64': 0.34.3 + '@img/sharp-linuxmusl-x64': 0.34.3 + '@img/sharp-wasm32': 0.34.3 + '@img/sharp-win32-arm64': 0.34.3 + '@img/sharp-win32-ia32': 0.34.3 + '@img/sharp-win32-x64': 0.34.3 + optional: true + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -28907,6 +29323,12 @@ snapshots: tailwind-merge: 3.0.2 tailwindcss: 4.1.8 + tailwind-variants@3.1.0(tailwind-merge@3.3.0)(tailwindcss@4.1.8): + dependencies: + tailwindcss: 4.1.8 + optionalDependencies: + tailwind-merge: 3.3.0 + tailwindcss-animate@1.0.7(tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.11.24(@swc/helpers@0.5.17))(@types/node@20.17.47)(typescript@5.8.3))): dependencies: tailwindcss: 3.4.17(ts-node@10.9.2(@swc/core@1.11.24(@swc/helpers@0.5.17))(@types/node@20.17.47)(typescript@5.8.3)) From 7f86e638693830c6d810e0044e98253723f73c54 Mon Sep 17 00:00:00 2001 From: KaiUweCZE Date: Sat, 6 Sep 2025 09:40:22 +0200 Subject: [PATCH 37/44] git commit -m "fix: replace stale stroke tokens with border tokens - Replace --color-stroke-primary with --color-border-primary - Replace --color-stroke-secondary with --color-border-secondary - Add TOKEN_CHANGELOG.md to document breaking changes - Update all references in frontend-demo token overrides Fixes #122" --- apps/frontend-demo/src/tokens/_overrides.css | 6 +++--- .../src/tokens/app-components/molecules/_cart-preview.css | 4 ++-- .../src/tokens/app-components/molecules/_navigation.css | 2 +- .../src/tokens/app-components/organisms/_header.css | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/frontend-demo/src/tokens/_overrides.css b/apps/frontend-demo/src/tokens/_overrides.css index 524dd5f576..317457221e 100644 --- a/apps/frontend-demo/src/tokens/_overrides.css +++ b/apps/frontend-demo/src/tokens/_overrides.css @@ -279,8 +279,8 @@ ); /* Borders */ - --color-stroke-primary: var(--color-slate-200); - --color-stroke-secondary: var(--color-slate-400); + --color-border-primary: var(--color-slate-200); + --color-border-secondary-alt: var(--color-slate-400); --color-border: light-dark(var(--color-slate-200), var(--color-slate-800)); --color-border-neutral: light-dark( var(--color-slate-300), @@ -331,7 +331,7 @@ var(--opacity-disabled) ); --color-disabled-border: --alpha( - var(--color-stroke-primary) / + var(--color-border-primary) / var(--opacity-disabled) ); --color-disabled-fill: --alpha( diff --git a/apps/frontend-demo/src/tokens/app-components/molecules/_cart-preview.css b/apps/frontend-demo/src/tokens/app-components/molecules/_cart-preview.css index bde2838dd5..e900c27e08 100644 --- a/apps/frontend-demo/src/tokens/app-components/molecules/_cart-preview.css +++ b/apps/frontend-demo/src/tokens/app-components/molecules/_cart-preview.css @@ -1,10 +1,10 @@ @theme static { /* === CART PREVIEW COLORS === */ - --color-cart-preview-item-border: var(--color-stroke-secondary); + --color-cart-preview-item-border: var(--color-border-secondary); --color-cart-preview-item-image-bg: var(--color-fill); --color-cart-preview-fg: var(--color-fg-primary); --color-cart-preview-fg-secondary: var(--color-fg-secondary); - --color-cart-preview-footer-border: var(--color-stroke-secondary); + --color-cart-preview-footer-border: var(--color-border-secondary); /* === SPACING === */ --spacing-cart-preview: var(--spacing-xs); diff --git a/apps/frontend-demo/src/tokens/app-components/molecules/_navigation.css b/apps/frontend-demo/src/tokens/app-components/molecules/_navigation.css index 9ad7f9f856..294fe67d33 100644 --- a/apps/frontend-demo/src/tokens/app-components/molecules/_navigation.css +++ b/apps/frontend-demo/src/tokens/app-components/molecules/_navigation.css @@ -12,7 +12,7 @@ /* Navigation submenu */ --color-nav-submenu-bg: var(--color-surface); --color-nav-submenu-item-hover: var(--color-surface-hover); - --color-nav-submenu-border: var(--color-stroke-primary); + --color-nav-submenu-border: var(--color-border-primary); --shadow-nav-submenu: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1); diff --git a/apps/frontend-demo/src/tokens/app-components/organisms/_header.css b/apps/frontend-demo/src/tokens/app-components/organisms/_header.css index e866f73416..6575c5a641 100644 --- a/apps/frontend-demo/src/tokens/app-components/organisms/_header.css +++ b/apps/frontend-demo/src/tokens/app-components/organisms/_header.css @@ -14,7 +14,7 @@ --color-header-mobile-menu-hover: var(--color-fill-hover); --color-header-mobile-menu-text: var(--color-fg-secondary); --color-header-mobile-menu-text-hover: var(--color-fg-primary); - --color-header-divider: var(--color-stroke-secondary); + --color-header-divider: var(--color-border-secondary); /* === HEADER SPACING === */ --spacing-header-container-x: var(--spacing-xs); From 4c34b7c8e71563dcc2a53001904ee6a1527f2d35 Mon Sep 17 00:00:00 2001 From: KaiUweCZE Date: Sat, 6 Sep 2025 10:37:37 +0200 Subject: [PATCH 38/44] feat: migrate to CSS logical properties for RTL support - Replace all physical properties (ml/mr/pl/pr) with logical (ms/me/ps/pe) --- libs/ui/src/atoms/input.tsx | 4 ++-- libs/ui/src/atoms/label.tsx | 2 +- libs/ui/src/molecules/menu.tsx | 6 +++--- libs/ui/src/molecules/toast.tsx | 2 +- libs/ui/src/molecules/tree-view.tsx | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/libs/ui/src/atoms/input.tsx b/libs/ui/src/atoms/input.tsx index 2d4fe6a6f7..296725cf16 100644 --- a/libs/ui/src/atoms/input.tsx +++ b/libs/ui/src/atoms/input.tsx @@ -48,8 +48,8 @@ const inputVariants = tv({ }, withButtonInside: { false: '', - right: 'pr-with-button', - left: 'pl-with-button', + right: 'pe-with-button', + left: 'ps-with-button', }, hideSearchClear: { true: '[&::-webkit-search-cancel-button]:hidden [&::-ms-clear]:hidden', diff --git a/libs/ui/src/atoms/label.tsx b/libs/ui/src/atoms/label.tsx index 5a4e3e957c..e04915ce6b 100644 --- a/libs/ui/src/atoms/label.tsx +++ b/libs/ui/src/atoms/label.tsx @@ -48,7 +48,7 @@ export function Label({ htmlFor={props.htmlFor} > {children} - {required && *} + {required && *} ) } diff --git a/libs/ui/src/molecules/menu.tsx b/libs/ui/src/molecules/menu.tsx index a9ecd5eb1d..805a3551cc 100644 --- a/libs/ui/src/molecules/menu.tsx +++ b/libs/ui/src/molecules/menu.tsx @@ -91,7 +91,7 @@ const menuVariants = tv({ itemText: ['flex-grow'], itemIcon: ['text-menu-item-icon-size text-menu-item-icon-fg'], submenuIndicator: [ - 'ml-menu-submenu-indicator text-menu-submenu-indicator-fg', + 'ms-menu-submenu-indicator text-menu-submenu-indicator-fg', ], }, variants: { @@ -433,10 +433,10 @@ export function Menu({ ) : ( diff --git a/libs/ui/src/molecules/toast.tsx b/libs/ui/src/molecules/toast.tsx index b9e323163a..45c32e79bc 100644 --- a/libs/ui/src/molecules/toast.tsx +++ b/libs/ui/src/molecules/toast.tsx @@ -44,7 +44,7 @@ const toastVariants = tv({ 'text-toast-description-size text-toast-description-fg mt-toast-description-gap', ], closeButton: [ - 'grid place-items-center flex-shrink-0 ml-auto py-0 px-0', + 'grid place-items-center flex-shrink-0 ms-auto py-0 px-0', 'cursor-pointer', 'text-toast-close hover:text-toast-close-hover', ], diff --git a/libs/ui/src/molecules/tree-view.tsx b/libs/ui/src/molecules/tree-view.tsx index 3f42b57ffd..931ecadcd4 100644 --- a/libs/ui/src/molecules/tree-view.tsx +++ b/libs/ui/src/molecules/tree-view.tsx @@ -55,8 +55,8 @@ const treeVariants = tv({ class: [ 'relative', // get --depth from zag-js api - 'ml-[calc(var(--depth)*var(--tree-indent-per-level))]', - 'data-[depth=1]:ml-0', + 'ms-[calc(var(--depth)*var(--tree-indent-per-level))]', + 'data-[depth=1]:ms-0', ], }, { From e789b3517aeff7170968dd138630d7e6903ee77c Mon Sep 17 00:00:00 2001 From: KaiUweCZE Date: Sat, 6 Sep 2025 10:54:22 +0200 Subject: [PATCH 39/44] fix: define missing shadow-xl token for component usage Fixes #119 --- libs/ui/src/tokens/_base.css | 1 + libs/ui/src/tokens/components/molecules/_accordion.css | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/ui/src/tokens/_base.css b/libs/ui/src/tokens/_base.css index 0c4e522208..96cd8e2c02 100644 --- a/libs/ui/src/tokens/_base.css +++ b/libs/ui/src/tokens/_base.css @@ -21,4 +21,5 @@ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05); --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1); --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1); + --shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.15), 0 10px 10px rgba(0, 0, 0, 0.04); } diff --git a/libs/ui/src/tokens/components/molecules/_accordion.css b/libs/ui/src/tokens/components/molecules/_accordion.css index 585b40003e..9ceaa380b4 100644 --- a/libs/ui/src/tokens/components/molecules/_accordion.css +++ b/libs/ui/src/tokens/components/molecules/_accordion.css @@ -59,7 +59,7 @@ /* === SHADOWS === */ --shadow-accordion-root-sm: var(--shadow-primary); - --shadow-accordion-root-md: var(--shadow-xl); + --shadow-accordion-root-md: var(--shadow-md); --shadow-accordion-content-sm: var(--shadow-sm); --shadow-accordion-content-md: var(--shadow-primary); } From fc5309ec1055d3b905390abce5d1e04abbbf9211 Mon Sep 17 00:00:00 2001 From: KaiUweCZE Date: Sat, 6 Sep 2025 11:08:06 +0200 Subject: [PATCH 40/44] fix: migrate from btn to button token naming convention MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename CSS tokens: --color-btn-* → --color-button-* - Update Tailwind classes: bg-btn-*, text-btn-* → bg-button-*, text-button-* - Fix token references: var(--color-btn-text) → var(--color-button-fg) Fixes #120 --- .../src/tokens/_overrides-components.css | 22 ++++++++--------- .../molecules/_product-card.css | 24 +++++++++---------- libs/ui/src/molecules/product-card.tsx | 6 ++--- .../components/molecules/_product-card.css | 24 +++++++++---------- 4 files changed, 38 insertions(+), 38 deletions(-) diff --git a/apps/frontend-demo/src/tokens/_overrides-components.css b/apps/frontend-demo/src/tokens/_overrides-components.css index ea4ce35070..4368da43ec 100644 --- a/apps/frontend-demo/src/tokens/_overrides-components.css +++ b/apps/frontend-demo/src/tokens/_overrides-components.css @@ -15,27 +15,27 @@ /* === BUTTON === */ /* Change from --alpha-ring to --opacity-ring for all focus rings */ - --color-btn-ring-primary: --alpha( - var(--color-btn-primary) / + --color-button-ring-primary: --alpha( + var(--color-button-primary) / var(--opacity-ring) ); - --color-btn-ring-secondary: --alpha( - var(--color-btn-secondary) / + --color-button-ring-secondary: --alpha( + var(--color-button-secondary) / var(--opacity-ring) ); - --color-btn-ring-tertiary: --alpha( - var(--color-btn-tertiary) / + --color-button-ring-tertiary: --alpha( + var(--color-button-tertiary) / var(--opacity-ring) ); - --color-btn-ring-warning: --alpha( - var(--color-btn-warning) / + --color-button-ring-warning: --alpha( + var(--color-button-warning) / var(--opacity-ring) ); - --color-btn-ring-danger: --alpha( - var(--color-btn-danger) / + --color-button-ring-danger: --alpha( + var(--color-button-danger) / var(--opacity-ring) ); - --color-btn-text-secondary: var(--color-fg-dark); + --color-button-text-secondary: var(--color-fg-dark); /* === INPUT === */ /* Different padding for small inputs */ diff --git a/apps/frontend-demo/src/tokens/app-components/molecules/_product-card.css b/apps/frontend-demo/src/tokens/app-components/molecules/_product-card.css index 6c8b955913..d95e302afc 100644 --- a/apps/frontend-demo/src/tokens/app-components/molecules/_product-card.css +++ b/apps/frontend-demo/src/tokens/app-components/molecules/_product-card.css @@ -28,28 +28,28 @@ /* === PRODUCT CARD BUTTONS ===*/ /*Add to cart button*/ - --color-btn-cart: var(--color-btn-primary); - --color-btn-cart-hover: oklch( - from var(--color-btn-cart) calc(l + var(--state-hover)) c h + --color-button-cart: var(--color-button-primary); + --color-button-cart-hover: oklch( + from var(--color-button-cart) calc(l + var(--state-hover)) c h ); - --color-btn-cart-fg: var(--color-btn-text); + --color-button-cart-fg: var(--color-button-fg); /* Detail button */ - --color-btn-detail: var(--color-btn-secondary); - --color-btn-detail-hover: oklch( - from var(--color-btn-detail) calc(l + var(--state-hover)) c h + --color-button-detail: var(--color-button-secondary); + --color-button-detail-hover: oklch( + from var(--color-button-detail) calc(l + var(--state-hover)) c h ); - --color-btn-detail-fg: var(--color-btn-text); + --color-button-detail-fg: var(--color-button-fg); /* wishlist button */ - --color-btn-wishlist: var(--color-btn-tertiary); - --color-btn-wishlist-hover: oklch( - from var(--color-btn-wishlist) calc(l + var(--state-hover)) c h + --color-button-wishlist: var(--color-button-tertiary); + --color-button-wishlist-hover: oklch( + from var(--color-button-wishlist) calc(l + var(--state-hover)) c h ); - --color-btn-wishlist-fg: var(--color-btn-text); + --color-button-wishlist-fg: var(--color-button-fg); /* === PRODUCT CARD IMAGE === */ --aspect-pc-image: var(--aspect-ratio-square); diff --git a/libs/ui/src/molecules/product-card.tsx b/libs/ui/src/molecules/product-card.tsx index eed04b59f5..479da4b211 100644 --- a/libs/ui/src/molecules/product-card.tsx +++ b/libs/ui/src/molecules/product-card.tsx @@ -22,11 +22,11 @@ const productCard = tv({ badgesSlot: 'flex flex-wrap gap-pc-box', ratingSlot: 'flex items-center', buttonsSlot: 'flex flex-wrap w-fit', - cartButton: 'bg-btn-cart hover:bg-btn-cart-hover text-btn-cart-fg w-max', + cartButton: 'bg-button-cart hover:bg-button-cart-hover text-button-cart-fg w-max', detailButton: - 'bg-btn-detail hover:bg-btn-detail-hover text-btn-detail-fg w-max', + 'bg-button-detail hover:bg-button-detail-hover text-button-detail-fg w-max', wishlistButton: - 'bg-btn-wishlist hover:bg-btn-wishlist-hover text-btn-wishlist-fg w-max', + 'bg-button-wishlist hover:bg-button-wishlist-hover text-button-wishlist-fg w-max', }, variants: { // variant for layout of the card diff --git a/libs/ui/src/tokens/components/molecules/_product-card.css b/libs/ui/src/tokens/components/molecules/_product-card.css index 389133876c..27a5d67294 100644 --- a/libs/ui/src/tokens/components/molecules/_product-card.css +++ b/libs/ui/src/tokens/components/molecules/_product-card.css @@ -25,28 +25,28 @@ /* === PRODUCT CARD BUTTONS ===*/ /*Add to cart button*/ - --color-btn-cart: var(--color-btn-primary); - --color-btn-cart-hover: oklch( - from var(--color-btn-cart) calc(l + var(--state-hover)) c h + --color-button-cart: var(--color-button-primary); + --color-button-cart-hover: oklch( + from var(--color-button-cart) calc(l + var(--state-hover)) c h ); - --color-btn-cart-fg: var(--color-btn-text); + --color-button-cart-fg: var(--color-button-fg); /* Detail button */ - --color-btn-detail: var(--color-btn-secondary); - --color-btn-detail-hover: oklch( - from var(--color-btn-detail) calc(l + var(--state-hover)) c h + --color-button-detail: var(--color-button-secondary); + --color-button-detail-hover: oklch( + from var(--color-button-detail) calc(l + var(--state-hover)) c h ); - --color-btn-detail-fg: var(--color-btn-text); + --color-button-detail-fg: var(--color-button-fg); /* wishlist button */ - --color-btn-wishlist: var(--color-btn-tertiary); - --color-btn-wishlist-hover: oklch( - from var(--color-btn-wishlist) calc(l + var(--state-hover)) c h + --color-button-wishlist: var(--color-button-tertiary); + --color-button-wishlist-hover: oklch( + from var(--color-button-wishlist) calc(l + var(--state-hover)) c h ); - --color-btn-wishlist-fg: var(--color-btn-text); + --color-button-wishlist-fg: var(--color-button-fg); /* === PRODUCT CARD IMAGE === */ --aspect-pc-image: var(--aspect-square); From 6caddaa7d96e0c7228687a01bacbb03f42880197 Mon Sep 17 00:00:00 2001 From: KaiUweCZE Date: Sat, 6 Sep 2025 11:57:16 +0200 Subject: [PATCH 41/44] fix(ui): add missing -light-disabled tokens for semantic colors Closes #121 --- libs/ui/src/tokens/_semantic.css | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/libs/ui/src/tokens/_semantic.css b/libs/ui/src/tokens/_semantic.css index 76728fcbb8..d58e32c967 100644 --- a/libs/ui/src/tokens/_semantic.css +++ b/libs/ui/src/tokens/_semantic.css @@ -100,6 +100,9 @@ --color-primary-light-active: oklch( from var(--color-primary-light) calc(l + var(--state-active)) c h ); + --color-primary-light-disabled: oklch( + from var(--color-primary-light) l var(--state-disabled-chroma) h + ); /*Secondary colors. Less important than primary colors, but still serve as accents.*/ --color-secondary: light-dark(var(--color-orange-500), var(--color-amber-400)); @@ -123,6 +126,9 @@ --color-secondary-light-active: oklch( from var(--color-secondary-light) calc(l + var(--state-active)) c h ); + --color-secondary-light-disabled: oklch( + from var(--color-secondary-light) l var(--state-disabled-chroma) h + ); /* In the user interface, non emphasis color and interactive color are usually used for general and non emphasis function operation buttons */ --color-tertiary: light-dark(var(--color-pink-500), var(--color-rose-400)); @@ -146,6 +152,9 @@ --color-tertiary-light-active: oklch( from var(--color-tertiary-light) calc(l + var(--state-active)) c h ); + --color-tertiary-light-disabled: oklch( + from var(--color-tertiary-light) l var(--state-disabled-chroma) h + ); /* It is usually used to express the success, completion and opening status, and is used in scenarios with the above semantics */ --color-success: light-dark( @@ -172,6 +181,9 @@ --color-success-light-active: oklch( from var(--color-success-light) calc(l + var(--state-active)) c h ); + --color-success-light-disabled: oklch( + from var(--color-success-light) l var(--state-disabled-chroma) h + ); /* It is usually used to express objective and neutral information in the context with the above semantics */ --color-info: light-dark(var(--color-teal-500), var(--color-cyan-300)); @@ -195,6 +207,9 @@ --color-info-light-active: oklch( from var(--color-info-light) calc(l + var(--state-active)) c h ); + --color-info-light-disabled: oklch( + from var(--color-info-light) l var(--state-disabled-chroma) h + ); /* It is usually used to express warning and unsafe state, and is used in scenarios with the above semantics */ --color-warning: light-dark(var(--color-yellow-500), var(--color-yellow-400)); @@ -218,6 +233,9 @@ --color-warning-light-active: oklch( from var(--color-warning-light) calc(l + var(--state-active)) c h ); + --color-warning-light-disabled: oklch( + from var(--color-warning-light) l var(--state-disabled-chroma) h + ); /* It is usually used to express the dangerous state, and is used in the scene with the above semantics */ --color-danger: light-dark(var(--color-red-500), var(--color-red-400)); @@ -241,6 +259,9 @@ --color-danger-light-active: oklch( from var(--color-danger-light) calc(l + var(--state-active)) c h ); + --color-danger-light-disabled: oklch( + from var(--color-danger-light) l var(--state-disabled-chroma) h + ); /* Text colors and Icon colors*/ /* Four different levels of text / icon colors represent the most important, secondary, minor and minor contents in the product interface */ From 24d00a1a1dfd96297229a53b911ebcaf5065fd97 Mon Sep 17 00:00:00 2001 From: Petr Glaser Date: Sun, 7 Sep 2025 11:26:55 +0200 Subject: [PATCH 42/44] fix: implement code review suggestions --- apps/frontend-demo/package.json | 4 +- libs/ui/eslint.config.js | 9 +- libs/ui/scripts/validate-token-definitions.js | 107 ++++++++++-------- libs/ui/scripts/validate-token-usage.js | 47 ++++---- pnpm-lock.yaml | 81 +------------ 5 files changed, 102 insertions(+), 146 deletions(-) diff --git a/apps/frontend-demo/package.json b/apps/frontend-demo/package.json index 2739802b28..8d5fa5f439 100644 --- a/apps/frontend-demo/package.json +++ b/apps/frontend-demo/package.json @@ -21,8 +21,8 @@ "@react-email/components": "^0.3.2", "@tanstack/react-query": "^5.76.1", "@tanstack/react-store": "^0.7.1", - "@zag-js/carousel": "^1.14.0", - "@zag-js/react": "^1.14.0", + "@zag-js/carousel": "^1.22.1", + "@zag-js/react": "^1.22.1", "clsx": "^2.1.1", "dompurify": "^3.2.6", "next": "^15.3.2", diff --git a/libs/ui/eslint.config.js b/libs/ui/eslint.config.js index 0b0f5c5955..de276d610f 100644 --- a/libs/ui/eslint.config.js +++ b/libs/ui/eslint.config.js @@ -1,6 +1,13 @@ +import path from 'node:path' +import { fileURLToPath } from 'node:url' import tsParser from '@typescript-eslint/parser' import tailwind from 'eslint-plugin-tailwindcss' +const tokensCssAbsolutePath = path.resolve( + path.dirname(fileURLToPath(import.meta.url)), + 'src/tokens/index.css' +) + export default [ { files: ['**/*.{js,jsx,ts,tsx}'], @@ -17,7 +24,7 @@ export default [ settings: { tailwindcss: { // For TailwindCSS v4 - point to CSS config file - config: '/Users/satan/work/new-engine/libs/ui/src/tokens/index.css', + config: tokensCssAbsolutePath, }, }, plugins: { diff --git a/libs/ui/scripts/validate-token-definitions.js b/libs/ui/scripts/validate-token-definitions.js index f48f3486e3..b5f7d57bab 100755 --- a/libs/ui/scripts/validate-token-definitions.js +++ b/libs/ui/scripts/validate-token-definitions.js @@ -9,10 +9,15 @@ * - Optional --profile timings */ -import fs from 'node:fs/promises' import { existsSync } from 'node:fs' +import fs from 'node:fs/promises' +import path from 'node:path' +import { performance } from 'node:perf_hooks' +import { fileURLToPath } from 'node:url' import { globSync } from 'glob' +const ROOT = path.resolve(fileURLToPath(new URL('.', import.meta.url)), '..') + // Configuration for validation const CONFIG = { // Tokens to always consider "used" (whitelist) @@ -224,7 +229,9 @@ function tokenToUtilityClasses(tokenName) { 'spacing-translate', 'spacing-menu-submenu', ] - if (customPropertyPrefixes.some((prefix) => tokenName.includes(`--${prefix}`))) { + if ( + customPropertyPrefixes.some((prefix) => tokenName.includes(`--${prefix}`)) + ) { return classes } @@ -248,46 +255,51 @@ async function buildTokenIndices() { await Promise.all( files.map(async (file) => { - if (!existsSync(file)) return - const content = await fs.readFile(file, 'utf8') - const lines = content.split('\n') - - // 1) Parse token definitions and dependency edges - const tokenRegex = /--([\w-]+)\s*:\s*([^;]+);/g - let m - while ((m = tokenRegex.exec(content)) !== null) { - const name = `--${m[1]}` - const value = m[2].trim() - const line = content.substring(0, m.index).split('\n').length - defs.set(name, { value, file, line }) - - // Extract dependencies from value - const deps = new Set() - for (const vm of value.matchAll(/var\(\s*(--[\w-]+)/g)) { - deps.add(vm[1]) - } - for (const om of value.matchAll(/oklch\([^)]*var\(\s*(--[\w-]+)/g)) { - deps.add(om[1]) + const abs = path.join(ROOT, file) + if (!existsSync(abs)) return + try { + const content = await fs.readFile(abs, 'utf8') + const lines = content.split('\n') + + // 1) Parse token definitions and dependency edges + const tokenRegex = /--([\w-]+)\s*:\s*([^;]+);/g + let m + while ((m = tokenRegex.exec(content)) !== null) { + const name = `--${m[1]}` + const value = m[2].trim() + const line = content.substring(0, m.index).split('\n').length + defs.set(name, { value, file, line }) + + // Extract dependencies from value + const deps = new Set() + for (const vm of value.matchAll(/var\(\s*(--[\w-]+)/g)) { + deps.add(vm[1]) + } + for (const om of value.matchAll(/oklch\([^)]*var\(\s*(--[\w-]+)/g)) { + deps.add(om[1]) + } + dependencyGraph.set(name, deps) } - dependencyGraph.set(name, deps) - } - // 2) Index var() usage on non-definition lines as direct CSS usage - const defLine = new Set() - for (const [token, data] of defs) { - if (data.file === file) defLine.add(data.line) - } + // 2) Index var() usage on non-definition lines as direct CSS usage + const defLine = new Set() + for (const [token, data] of defs) { + if (data.file === file) defLine.add(data.line) + } - for (let i = 0; i < lines.length; i++) { - const lineNo = i + 1 - if (defLine.has(lineNo)) continue - const line = lines[i] - if (!line.includes('var(')) continue - for (const vm of line.matchAll(/var\(\s*(--[\w-]+)/g)) { - const t = vm[1] - if (!cssUsage.has(t)) cssUsage.set(t, new Set()) - cssUsage.get(t).add(`${file}:${lineNo} (CSS property)`) // direct usage + for (let i = 0; i < lines.length; i++) { + const lineNo = i + 1 + if (defLine.has(lineNo)) continue + const line = lines[i] + if (!line.includes('var(')) continue + for (const vm of line.matchAll(/var\(\s*(--[\w-]+)/g)) { + const t = vm[1] + if (!cssUsage.has(t)) cssUsage.set(t, new Set()) + cssUsage.get(t).add(`${file}:${lineNo} (CSS property)`) // direct usage + } } + } catch (err) { + console.error(`💥 Failed to process ${file}:`, err?.message || err) } }) ) @@ -303,7 +315,10 @@ async function buildTokenIndices() { // Build component indices in a single pass async function buildComponentIndices(classToTokens, knownTokens) { - const files = globSync('src/**/*.{ts,tsx}', { ignore: CONFIG.excludeFiles }) + const files = globSync('src/**/*.{ts,tsx}', { + cwd: ROOT, + ignore: CONFIG.excludeFiles, + }) const componentVarUsage = new Map() // token -> Set const classUsageTokens = new Set() // tokens used via classes @@ -328,7 +343,7 @@ async function buildComponentIndices(classToTokens, knownTokens) { const lineNo = content.substring(0, m.index).split('\n').length componentVarUsage .get(t) - .add(`${file}:${lineNo} (component var() reference)`) + .add(`${file}:${lineNo} (component var() reference)`) } // 1b) Arbitrary utilities referencing tokens directly, e.g. border-(length:--token) @@ -341,7 +356,8 @@ async function buildComponentIndices(classToTokens, knownTokens) { // 2) Class-based usage: scan whole file for class-like tokens // Capture words with at least one hyphen, optionally with variant prefixes like sm:, hover:, data-[...]: - const classLike = /(^|[^A-Za-z0-9_-])([A-Za-z0-9_-]+(?::[A-Za-z0-9_\[\]-]+)*-[A-Za-z0-9_\[\]-]+)/g + const classLike = + /(^|[^A-Za-z0-9_-])([A-Za-z0-9_-]+(?::[A-Za-z0-9_[\]-]+)*-[A-Za-z0-9_[\]-]+)/g for (const m of content.matchAll(classLike)) { const raw = m[2] const cls = normalizeClass(raw) @@ -405,8 +421,7 @@ async function validateTokenDefinitions({ profile = false } = {}) { // 2) Class maps from tokens p.mark('classmaps') const { classToTokens } = computeClassMaps(allTokens) - if (profile) - console.log(`⏱️ class maps: ${p.end('classmaps').toFixed(1)}ms`) + if (profile) console.log(`⏱️ class maps: ${p.end('classmaps').toFixed(1)}ms`) // 3) Component indices p.mark('components') @@ -414,8 +429,7 @@ async function validateTokenDefinitions({ profile = false } = {}) { classToTokens, new Set(allTokens) ) - if (profile) - console.log(`⏱️ components: ${p.end('components').toFixed(1)}ms`) + if (profile) console.log(`⏱️ components: ${p.end('components').toFixed(1)}ms`) // 4) Seed used set const usedDirect = new Set() @@ -468,7 +482,8 @@ async function validateTokenDefinitions({ profile = false } = {}) { } for (const [file, list] of byFile) { console.log(`📄 ${file}:`) - for (const t of list) console.log(` Line ${t.line}: ${t.name} = ${t.value}`) + for (const t of list) + console.log(` Line ${t.line}: ${t.name} = ${t.value}`) console.log() } console.log( diff --git a/libs/ui/scripts/validate-token-usage.js b/libs/ui/scripts/validate-token-usage.js index 7ef6fd730c..718006ec8a 100755 --- a/libs/ui/scripts/validate-token-usage.js +++ b/libs/ui/scripts/validate-token-usage.js @@ -9,9 +9,11 @@ import fs from 'node:fs' import path from 'node:path' -import { fileURLToPath } from 'node:url' +import { fileURLToPath, pathToFileURL } from 'node:url' import { globSync } from 'glob' +const ROOT = path.resolve(fileURLToPath(new URL('.', import.meta.url)), '..') + // Tailwind v4 namespace to utility prefix mappings const NAMESPACE_MAPPINGS = { color: [ @@ -148,7 +150,7 @@ const PREFIX_TO_NAMESPACES = (() => { /** * Extract Tailwind classes from TypeScript/JSX content */ -function extractTailwindClasses(content, filePath) { +function extractTailwindClasses(content) { const classes = new Set() // Match className props (string values) @@ -337,7 +339,6 @@ function mapClassToPossibleTokens(className) { */ function loadDefinedTokens() { const tokens = new Set() - const ROOT = path.resolve(fileURLToPath(new URL('.', import.meta.url)), '..') const tokenFiles = globSync('src/tokens/**/*.css', { cwd: ROOT }) for (const file of tokenFiles) { @@ -350,7 +351,10 @@ function loadDefinedTokens() { } // Also treat inline style custom properties in components as defined - const componentFiles = globSync('src/**/*.{ts,tsx}', { cwd: ROOT, ignore: ['**/*.stories.tsx','**/*.test.tsx','**/*.spec.tsx'] }) + const componentFiles = globSync('src/**/*.{ts,tsx}', { + cwd: ROOT, + ignore: ['**/*.stories.tsx', '**/*.test.tsx', '**/*.spec.tsx'], + }) for (const file of componentFiles) { const content = fs.readFileSync(path.join(ROOT, file), 'utf8') // style={{ '--var': value }} or object entries '--var': value @@ -402,6 +406,7 @@ function validateTokenUsage() { console.log(`📋 Found ${definedTokens.size} defined tokens`) const componentFiles = globSync('src/**/*.{ts,tsx}', { + cwd: ROOT, ignore: ['**/*.stories.tsx', '**/*.test.tsx', '**/*.spec.tsx'], }) @@ -409,7 +414,7 @@ function validateTokenUsage() { const errorsByFile = new Map() for (const file of componentFiles) { - const content = fs.readFileSync(file, 'utf8') + const content = fs.readFileSync(path.join(ROOT, file), 'utf8') const classes = extractTailwindClasses(content, file) const fileErrors = [] @@ -479,28 +484,30 @@ function validateTokenUsage() { '✅ All component classes have corresponding token definitions!' ) return true - } else { - console.log(`❌ Found ${totalErrors} missing token definitions:\n`) - - for (const [file, errors] of errorsByFile) { - console.log(`📄 ${file}:`) - for (const error of errors) { - const tokenList = error.expectedTokens.join(' OR ') - console.log( - ` Line ${error.line}: ${error.className} → Missing token: ${tokenList}` - ) - } - console.log() + } + console.log(`❌ Found ${totalErrors} missing token definitions:\n`) + + for (const [file, errors] of errorsByFile) { + console.log(`📄 ${file}:`) + for (const error of errors) { + const tokenList = error.expectedTokens.join(' OR ') + console.log( + ` Line ${error.line}: ${error.className} → Missing token: ${tokenList}` + ) } - - return false + console.log() } + + return false } /** * Run validation */ -if (import.meta.url === `file://${process.argv[1]}`) { +if ( + process.argv[1] && + pathToFileURL(path.resolve(process.argv[1])).href === import.meta.url +) { try { const success = validateTokenUsage() process.exit(success ? 0 : 1) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fba8896fe1..17690c92b3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -79,11 +79,11 @@ importers: specifier: ^0.7.1 version: 0.7.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@zag-js/carousel': - specifier: ^1.14.0 - version: 1.15.0 + specifier: ^1.22.1 + version: 1.22.1 '@zag-js/react': - specifier: ^1.14.0 - version: 1.15.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + specifier: ^1.22.1 + version: 1.22.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) clsx: specifier: ^2.1.1 version: 2.1.1 @@ -7461,18 +7461,12 @@ packages: '@zag-js/accordion@1.22.1': resolution: {integrity: sha512-P3jsauxnAGKBhuqs9gdivjEiSu7N7KnKRlgWlIpyti35askz8swHsqxsfkc2ASs9tcPKnPvuZDHIxXmJmZSLuQ==} - '@zag-js/anatomy@1.15.0': - resolution: {integrity: sha512-r0l5I7mSsF35HdwXm22TppNhfVftFuqvKfHvTUw+wQZhni4eUL93HypJD0Fl7mDhtP5zfVGfBwR048OzD0+tCw==} - '@zag-js/anatomy@1.22.1': resolution: {integrity: sha512-I5OvOuJBt6hEqbpqVkWCOEoDfGMnKuLx+S0h7Un5SyAwnif3F1dSqDYujU28bCy8FtKs36vsq/izxufXyiXSEg==} '@zag-js/aria-hidden@1.22.1': resolution: {integrity: sha512-vPfAE35BfYPS1UbYRcNw8/kMl7uayE7LyRncK/gPMnoQMjmEKW0nXmD5WlCHFLdGX9WFGYTIde8k4U8ay+oqcg==} - '@zag-js/carousel@1.15.0': - resolution: {integrity: sha512-ZI9H34f2utdJ2Ek6GZa+iuRH4eC99GHD/VEOKLdGani8uadpT2v8M5kUwPGrlAJq9SiPbQ2UuXBmCkmurPQqdA==} - '@zag-js/carousel@1.22.1': resolution: {integrity: sha512-bFbCRe5xarBtD3NnozHmCmrGJ+nLRhqLQFq+RG13fl1hlhUJaJ5AsS7e8L1r2ZLdbVVrsB0lUuW/ocfJ/G4MSw==} @@ -7485,9 +7479,6 @@ packages: '@zag-js/combobox@1.22.1': resolution: {integrity: sha512-N4tGTmezfHGaKB0+aDB5yMuVzBv2ShgsAx1uizom6ElcvlYD2rsQTr3xLc4wyOR7fx0z6fFDo1+63/Dt3y0t4A==} - '@zag-js/core@1.15.0': - resolution: {integrity: sha512-P/8F3IXabMhpFnc6hC7GDg3rvUnvY27cuZU04hxjUqTH6+SfORIA/Uvqd4ekhC+dIprL9jicnFrmGgcyelyxfQ==} - '@zag-js/core@1.22.1': resolution: {integrity: sha512-4BNrwO9Tadq2Z0d2xSSQs4O/o3OarEHzXM2FQqx46vrwSE57qUghnZex429ZQ51fuk8AL5Lowt26a9JxE9sVPg==} @@ -7497,9 +7488,6 @@ packages: '@zag-js/dismissable@1.22.1': resolution: {integrity: sha512-0DzbykJu9QoXYw4Zcjte69Mtk6ThNRCXWxxCKBf930V8Bw3Ha7vfY5bgdb4RFT5K+BQP3E8vLT+PzIaDINn2Xw==} - '@zag-js/dom-query@1.15.0': - resolution: {integrity: sha512-z8H/j/Zs0eZEsGpbonScmlKSv0jEXKiAwUCrvQ9Mt6Gz9n0CQRM3MkFclSsM8aeiSv6qKLlhPfkzjl18OLkbgA==} - '@zag-js/dom-query@1.22.1': resolution: {integrity: sha512-mtvGj2z3rkl40mkjd+QwoOHvxqpiOkY4mtVjzNzgzcbVtUN63Mz7giW8OZB+KLy37hwFX0B8JfiQncU8IOHNpw==} @@ -7533,12 +7521,6 @@ packages: '@zag-js/rating-group@1.22.1': resolution: {integrity: sha512-QxBK+hpfkQ4yFHUr1YOSwEQ3LuTrdS32J9zV8UyHu8HbgwzfR7L8ZAa1PUUmG65tupzua2pbn1NioOkMvDmBOQ==} - '@zag-js/react@1.15.0': - resolution: {integrity: sha512-YSp9QBkdeBfZt4nVhJW+CUd5sNEEVAuwkmoZWDFUoDoWSAXwzSKuHCmTm5/8DaXg1IZD2bMrXgMNDqZv2x0hZw==} - peerDependencies: - react: '>=18.0.0' - react-dom: '>=18.0.0' - '@zag-js/react@1.22.1': resolution: {integrity: sha512-TcIKkNo9EFel+d92nb7104voKJNDiMkqq9nn7Ozq/TE8A62JPf5zk8y8zqoxTbGDTTk+tDjW7Sm1IKb4r6rX4w==} peerDependencies: @@ -7551,9 +7533,6 @@ packages: '@zag-js/remove-scroll@1.22.1': resolution: {integrity: sha512-2TrS8ljp8SADX5xRB/+KGBCBYbYTeH0k5IEalG2rt8ReNyNAW1JfCrm53KCVoCg9YmxKF3MrxPgPT83MNFsJhQ==} - '@zag-js/scroll-snap@1.15.0': - resolution: {integrity: sha512-/LfBlsjoR4tVL3Djus3k9jKLhwC2ApdHTACxEc72TAewoPe4M8icnSDLXmKHvwwOhzK0HlFz8wGm6ZncAbQbuA==} - '@zag-js/scroll-snap@1.22.1': resolution: {integrity: sha512-kctqJiteALaavoHEpYBDSPgUErIdwAoY5jcrU4Mq5L8FjtI4tSNr8BWcXzSBK2UVqaKN+vDo+PDcj7XIXTUQJA==} @@ -7566,9 +7545,6 @@ packages: '@zag-js/steps@1.22.1': resolution: {integrity: sha512-eJCHbHG9aGAbzb/IQCqpmk6fmwSmIfocAxNKVTljroD6OHkBtqgaZQVS3q4xyjz61nB/d/0ZlsvpCVjm1EhwBw==} - '@zag-js/store@1.15.0': - resolution: {integrity: sha512-ecqjcy3b1GsULpsT8RVJV9KDaikajRN0XRg48HMvaGkaPIvxI6esyrE6RKnShuqr2eVXIPghgBnCnrJUev4UlA==} - '@zag-js/store@1.22.1': resolution: {integrity: sha512-KrMWi/Fa4cqOjx2zDSMIu6vztFYik+V3K6VPWRVONM4FkboLpTqAEayzwgTTNqMK9iYYZIYjhiPhAVLW9iLuBg==} @@ -7587,15 +7563,9 @@ packages: '@zag-js/tree-view@1.22.1': resolution: {integrity: sha512-AQmOn1mB+nLJEaq0xdSVnTI8Vt3nB3OweqdB12jkbdIOcWI9eY0RfhiNHC0k0mgAw+dMjyn84op/gOd9VVdtmA==} - '@zag-js/types@1.15.0': - resolution: {integrity: sha512-lV2ov2M07BlmjDUCSwBeHxPApHI3oAiLytG94AqcYvQ0BtsCRo5T60yRQ0syFc6fHf0e9+kwt89uoIgfGFYfmw==} - '@zag-js/types@1.22.1': resolution: {integrity: sha512-lvpDSMR96e7H7TdwOiVpMzj6css5Ydix1nBi7BlmjME6v5OPR0KZwVDGD6h5UtTeVjPq8dPaqM8TJWw+QwbQSw==} - '@zag-js/utils@1.15.0': - resolution: {integrity: sha512-XctFny5H8C00BsougV40Yp0qVEj9M2d/NRme7B33mon9wG+3hscZwP6miJmF6BYI5Pgu6e2P0Sv45FddQU1Tkg==} - '@zag-js/utils@1.22.1': resolution: {integrity: sha512-VXY4gjHaTENHW+wjnKKENZ2jcaW0vnG2a5lYEMuZR4dpNCKH217yFr/bCNrI44y2s1W3LWhWmpEjfZluP6udYg==} @@ -23011,21 +22981,10 @@ snapshots: '@zag-js/types': 1.22.1 '@zag-js/utils': 1.22.1 - '@zag-js/anatomy@1.15.0': {} - '@zag-js/anatomy@1.22.1': {} '@zag-js/aria-hidden@1.22.1': {} - '@zag-js/carousel@1.15.0': - dependencies: - '@zag-js/anatomy': 1.15.0 - '@zag-js/core': 1.15.0 - '@zag-js/dom-query': 1.15.0 - '@zag-js/scroll-snap': 1.15.0 - '@zag-js/types': 1.15.0 - '@zag-js/utils': 1.15.0 - '@zag-js/carousel@1.22.1': dependencies: '@zag-js/anatomy': 1.22.1 @@ -23060,11 +23019,6 @@ snapshots: '@zag-js/types': 1.22.1 '@zag-js/utils': 1.22.1 - '@zag-js/core@1.15.0': - dependencies: - '@zag-js/dom-query': 1.15.0 - '@zag-js/utils': 1.15.0 - '@zag-js/core@1.22.1': dependencies: '@zag-js/dom-query': 1.22.1 @@ -23088,10 +23042,6 @@ snapshots: '@zag-js/interact-outside': 1.22.1 '@zag-js/utils': 1.22.1 - '@zag-js/dom-query@1.15.0': - dependencies: - '@zag-js/types': 1.15.0 - '@zag-js/dom-query@1.22.1': dependencies: '@zag-js/types': 1.22.1 @@ -23168,15 +23118,6 @@ snapshots: '@zag-js/types': 1.22.1 '@zag-js/utils': 1.22.1 - '@zag-js/react@1.15.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': - dependencies: - '@zag-js/core': 1.15.0 - '@zag-js/store': 1.15.0 - '@zag-js/types': 1.15.0 - '@zag-js/utils': 1.15.0 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - '@zag-js/react@1.22.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@zag-js/core': 1.22.1 @@ -23192,10 +23133,6 @@ snapshots: dependencies: '@zag-js/dom-query': 1.22.1 - '@zag-js/scroll-snap@1.15.0': - dependencies: - '@zag-js/dom-query': 1.15.0 - '@zag-js/scroll-snap@1.22.1': dependencies: '@zag-js/dom-query': 1.22.1 @@ -23227,10 +23164,6 @@ snapshots: '@zag-js/types': 1.22.1 '@zag-js/utils': 1.22.1 - '@zag-js/store@1.15.0': - dependencies: - proxy-compare: 3.0.1 - '@zag-js/store@1.22.1': dependencies: proxy-compare: 3.0.1 @@ -23280,16 +23213,10 @@ snapshots: '@zag-js/types': 1.22.1 '@zag-js/utils': 1.22.1 - '@zag-js/types@1.15.0': - dependencies: - csstype: 3.1.3 - '@zag-js/types@1.22.1': dependencies: csstype: 3.1.3 - '@zag-js/utils@1.15.0': {} - '@zag-js/utils@1.22.1': {} '@zkochan/js-yaml@0.0.7': From 8f5e45f323f5bb1b678557385e04cf1019b6ebad Mon Sep 17 00:00:00 2001 From: Petr Glaser Date: Sun, 7 Sep 2025 11:43:54 +0200 Subject: [PATCH 43/44] fix: allow transparency and colour variants --- libs/ui/scripts/validate-token-usage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/ui/scripts/validate-token-usage.js b/libs/ui/scripts/validate-token-usage.js index 718006ec8a..dc91f52b35 100755 --- a/libs/ui/scripts/validate-token-usage.js +++ b/libs/ui/scripts/validate-token-usage.js @@ -96,7 +96,7 @@ const IGNORE_PATTERNS = [ /^(ml|mr|mt|mb|mx|my|pl|pr|pt|pb|px|py)-(0|px|0\.5|1|1\.5|2|2\.5|3|3\.5|4|5|6|7|8|9|10|11|12|14|16|20|24|28|32|36|40|44|48|52|56|60|64|72|80|96|auto|full|screen|min|max|fit)$/, // Standard colors including transparent - /^(bg|text|border)-(transparent|current|black|white|slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-?\d{0,3}$/, + /^(bg|text|border)-(transparent|current|black|white|slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)(?:-\d{1,3})?(?:\/\d{1,3})?$/, /^(bg|text|border)-(transparent|current|black|white|inherit)$/, // Standard typography From e4cef8a4270653a471c38dadb722200222835950 Mon Sep 17 00:00:00 2001 From: Petr Glaser Date: Sun, 7 Sep 2025 11:54:33 +0200 Subject: [PATCH 44/44] fix: simplify matching --- libs/ui/scripts/validate-token-usage.js | 34 ++++++++++++++++++------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/libs/ui/scripts/validate-token-usage.js b/libs/ui/scripts/validate-token-usage.js index dc91f52b35..e9abd1e43d 100755 --- a/libs/ui/scripts/validate-token-usage.js +++ b/libs/ui/scripts/validate-token-usage.js @@ -147,6 +147,8 @@ const PREFIX_TO_NAMESPACES = (() => { return map })() +const CLASS_STRING_REGEX = /\S+/g + /** * Extract Tailwind classes from TypeScript/JSX content */ @@ -157,7 +159,9 @@ function extractTailwindClasses(content) { const classNameMatches = content.matchAll(/className\s*=\s*["']([^"']+)["']/g) for (const match of classNameMatches) { const classString = match[1] - classString.split(/\s+/).forEach((cls) => cls && classes.add(cls.trim())) + for (const cls of classString.match(CLASS_STRING_REGEX) || []) { + classes.add(cls.trim()) + } } // Match className arrays: className: ['class1', 'class2'] or className={['class1', 'class2']} @@ -170,7 +174,9 @@ function extractTailwindClasses(content) { const stringMatches = arrayContent.matchAll(/['"`]([^'"`]+)['"`]/g) for (const stringMatch of stringMatches) { const classString = stringMatch[1] - classString.split(/\s+/).forEach((cls) => cls && classes.add(cls.trim())) + for (const cls of classString.match(CLASS_STRING_REGEX) || []) { + classes.add(cls.trim()) + } } } @@ -180,9 +186,11 @@ function extractTailwindClasses(content) { const classString = match[1] // Extract static classes, ignore interpolations const staticParts = classString.split(/\$\{[^}]+\}/) - staticParts.forEach((part) => { - part.split(/\s+/).forEach((cls) => cls && classes.add(cls.trim())) - }) + for (const part of staticParts) { + for (const cls of part.match(CLASS_STRING_REGEX) || []) { + classes.add(cls.trim()) + } + } } // Match tailwind-variants tv() configurations @@ -194,7 +202,9 @@ function extractTailwindClasses(content) { const slotMatches = tvConfig.matchAll(/\[\s*['"`]([^'"`]+)['"`]/g) for (const slotMatch of slotMatches) { const classString = slotMatch[1] - classString.split(/\s+/).forEach((cls) => cls && classes.add(cls.trim())) + for (const cls of classString.match(CLASS_STRING_REGEX) || []) { + classes.add(cls.trim()) + } } // Extract from variant values @@ -203,7 +213,9 @@ function extractTailwindClasses(content) { ) for (const variantMatch of variantMatches) { const classString = variantMatch[1] - classString.split(/\s+/).forEach((cls) => cls && classes.add(cls.trim())) + for (const cls of classString.match(CLASS_STRING_REGEX) || []) { + classes.add(cls.trim()) + } } } @@ -214,7 +226,9 @@ function extractTailwindClasses(content) { const stringMatches = args.matchAll(/['"`]([^'"`]+)['"`]/g) for (const stringMatch of stringMatches) { const classString = stringMatch[1] - classString.split(/\s+/).forEach((cls) => cls && classes.add(cls.trim())) + for (const cls of classString.match(CLASS_STRING_REGEX) || []) { + classes.add(cls.trim()) + } } } @@ -226,7 +240,9 @@ function extractTailwindClasses(content) { const classString = match[1] // Only extract if it looks like CSS classes if (/^[a-z-\s:[\]()]+$/i.test(classString)) { - classString.split(/\s+/).forEach((cls) => cls && classes.add(cls.trim())) + for (const cls of classString.match(CLASS_STRING_REGEX) || []) { + classes.add(cls.trim()) + } } }