@stacksjs/components 0.2.176 → 0.2.177

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/dist/{Accordion-pt8j917s.stx → Accordion-8tv78bh9.stx} +4 -4
  2. package/dist/{AccordionItem-hbhzx63p.stx → AccordionItem-7bd3ga36.stx} +3 -3
  3. package/dist/{Audio-hjywk3qc.stx → Audio-y0sgq50z.stx} +3 -3
  4. package/dist/{Avatar-h3jmwcq0.stx → Avatar-rjvwnskf.stx} +2 -2
  5. package/dist/{Badge-k8dy3d53.stx → Badge-k52qmxcm.stx} +1 -1
  6. package/dist/{Breadcrumb-7f7s4d02.stx → Breadcrumb-sy5taqsn.stx} +7 -7
  7. package/dist/{Button-1vknasy3.stx → Button-wrwmnfpf.stx} +2 -2
  8. package/dist/{Calendar-reny0et7.stx → Calendar-gjjhhfnt.stx} +8 -8
  9. package/dist/{Card-c1g1fd1w.stx → Card-473a02fy.stx} +3 -3
  10. package/dist/{Checkbox-aqmq4cqv.stx → Checkbox-2s2a7n4h.stx} +4 -4
  11. package/dist/{Checkout-agt0ngpw.stx → Checkout-22wh3s22.stx} +1 -1
  12. package/dist/{ComboboxOption-hmxt3gte.stx → ComboboxOption-6yh7xk7q.stx} +2 -2
  13. package/dist/{CommandPaletteItem-mvk6q7a2.stx → CommandPaletteItem-chdw0070.stx} +1 -1
  14. package/dist/{DefaultPaymentMethod-shhf6aap.stx → DefaultPaymentMethod-6qrem3sg.stx} +8 -8
  15. package/dist/{DialogDescription-76prd1m2.stx → DialogDescription-gth4c2kx.stx} +1 -1
  16. package/dist/{Drawer-hrz7qcyp.stx → Drawer-jgepqr9j.stx} +1 -1
  17. package/dist/{DropdownItem-c7rz08e5.stx → DropdownItem-d8ht2bp5.stx} +1 -1
  18. package/dist/{Form-bpnpbymb.stx → Form-vbag14t1.stx} +1 -1
  19. package/dist/{ListboxOption-e0dtv1gc.stx → ListboxOption-4j070101.stx} +2 -2
  20. package/dist/{Login-gepemsth.stx → Login-2cp65d41.stx} +40 -22
  21. package/dist/{Navigator-rk2a5wvk.stx → Navigator-smpa7my4.stx} +12 -12
  22. package/dist/{NumberInput-g41gmd2c.stx → NumberInput-j9p5bm5y.stx} +7 -7
  23. package/dist/{Pagination-wtgxmzey.stx → Pagination-jbcjrfcs.stx} +3 -3
  24. package/dist/{PasswordInput-4rsq1gwb.stx → PasswordInput-4qykq1dz.stx} +8 -8
  25. package/dist/{PaymentMethods-x4vrx9yb.stx → PaymentMethods-2xy66bwk.stx} +11 -11
  26. package/dist/{Progress-9dhakbh4.stx → Progress-4h7dytq5.stx} +9 -9
  27. package/dist/{Radio-djtb5dpc.stx → Radio-ccm07b2m.stx} +4 -4
  28. package/dist/RadioGroup-eerw9gq7.stx +138 -0
  29. package/dist/{RadioGroupDescription-yej78fak.stx → RadioGroupDescription-z34r9avr.stx} +1 -1
  30. package/dist/{RadioGroupOption-s1d6r221.stx → RadioGroupOption-7pq7rw84.stx} +1 -1
  31. package/dist/{Select-1vnd0brz.stx → Select-zt72gg1e.stx} +4 -4
  32. package/dist/{SidebarItem-2a6n0be3.stx → SidebarItem-5v8atp7p.stx} +2 -2
  33. package/dist/{SidebarPinned-tvj28bm0.stx → SidebarPinned-rq05nrtd.stx} +3 -3
  34. package/dist/{Signup-hxm5hwkr.stx → Signup-cbncrtvd.stx} +29 -19
  35. package/dist/{Skeleton-v1pwa8nr.stx → Skeleton-wtwb97xd.stx} +1 -1
  36. package/dist/{Spinner-7dahavkd.stx → Spinner-ypya5tt2.stx} +8 -8
  37. package/dist/{StepperStep-wxgembat.stx → StepperStep-3ahq4d9m.stx} +3 -3
  38. package/dist/{SubscriptionCheckout-zy2ycrmw.stx → SubscriptionCheckout-7fdkwf04.stx} +40 -34
  39. package/dist/{Switch-p1bcr9ks.stx → Switch-hdta55a8.stx} +1 -1
  40. package/dist/{Table-6cx8az9h.stx → Table-0mc3fdek.stx} +1 -1
  41. package/dist/{TableBody-wf6t13j9.stx → TableBody-drxyz5jh.stx} +1 -1
  42. package/dist/{TableCell-ntx0edbn.stx → TableCell-8swtcq8s.stx} +1 -1
  43. package/dist/{TableHead-3k4hnm2k.stx → TableHead-e9xvjx2j.stx} +1 -1
  44. package/dist/{TableHeader-0eyzbqpz.stx → TableHeader-sxc77b4d.stx} +2 -2
  45. package/dist/{TableRow-z09p2ebf.stx → TableRow-xd542nvb.stx} +1 -1
  46. package/dist/{Tabs-79brys99.stx → Tabs-b4xsba1w.stx} +6 -6
  47. package/dist/{TextInput-g0vaz0pe.stx → TextInput-cefyyprt.stx} +9 -9
  48. package/dist/{Textarea-96kawv5c.stx → Textarea-xcdym703.stx} +6 -6
  49. package/dist/{TwoFactorChallenge-y5m3nk79.stx → TwoFactorChallenge-y7n4yrk9.stx} +8 -8
  50. package/dist/{Video-84j4pv6h.stx → Video-a0aeyexv.stx} +1 -1
  51. package/dist/{VirtualTable-f9crbfjr.stx → VirtualTable-jvm1784g.stx} +6 -6
  52. package/dist/index.d.ts +2 -0
  53. package/dist/index.js +89 -55
  54. package/dist/theme/index.d.ts +92 -0
  55. package/package.json +3 -3
  56. package/src/index.ts +2 -0
  57. package/src/theme/index.ts +130 -0
  58. package/src/ui/accordion/Accordion.stx +4 -4
  59. package/src/ui/accordion/AccordionItem.stx +3 -3
  60. package/src/ui/audio/Audio.stx +3 -3
  61. package/src/ui/auth/Login.stx +40 -22
  62. package/src/ui/auth/Signup.stx +29 -19
  63. package/src/ui/auth/TwoFactorChallenge.stx +8 -8
  64. package/src/ui/avatar/Avatar.stx +2 -2
  65. package/src/ui/badge/Badge.stx +1 -1
  66. package/src/ui/breadcrumb/Breadcrumb.stx +7 -7
  67. package/src/ui/button/Button.stx +2 -2
  68. package/src/ui/calendar/Calendar.stx +8 -8
  69. package/src/ui/card/Card.stx +3 -3
  70. package/src/ui/checkbox/Checkbox.stx +4 -4
  71. package/src/ui/combobox/ComboboxOption.stx +2 -2
  72. package/src/ui/command-palette/CommandPaletteItem.stx +1 -1
  73. package/src/ui/dialog/DialogDescription.stx +1 -1
  74. package/src/ui/drawer/Drawer.stx +1 -1
  75. package/src/ui/dropdown/DropdownItem.stx +1 -1
  76. package/src/ui/form/Form.stx +1 -1
  77. package/src/ui/input/NumberInput.stx +7 -7
  78. package/src/ui/input/PasswordInput.stx +8 -8
  79. package/src/ui/input/TextInput.stx +9 -9
  80. package/src/ui/listbox/ListboxOption.stx +2 -2
  81. package/src/ui/navigator/Navigator.stx +12 -12
  82. package/src/ui/pagination/Pagination.stx +3 -3
  83. package/src/ui/payment/Checkout.stx +1 -1
  84. package/src/ui/payment/DefaultPaymentMethod.stx +8 -8
  85. package/src/ui/payment/PaymentMethods.stx +11 -11
  86. package/src/ui/payment/SubscriptionCheckout.stx +40 -34
  87. package/src/ui/progress/Progress.stx +9 -9
  88. package/src/ui/radio/Radio.stx +4 -4
  89. package/src/ui/radio-group/RadioGroup.stx +79 -1
  90. package/src/ui/radio-group/RadioGroupDescription.stx +1 -1
  91. package/src/ui/radio-group/RadioGroupOption.stx +1 -1
  92. package/src/ui/select/Select.stx +4 -4
  93. package/src/ui/sidebar/SidebarItem.stx +2 -2
  94. package/src/ui/sidebar/SidebarPinned.stx +3 -3
  95. package/src/ui/skeleton/Skeleton.stx +1 -1
  96. package/src/ui/spinner/Spinner.stx +8 -8
  97. package/src/ui/stepper/StepperStep.stx +3 -3
  98. package/src/ui/switch/Switch.stx +1 -1
  99. package/src/ui/table/Table.stx +1 -1
  100. package/src/ui/table/TableBody.stx +1 -1
  101. package/src/ui/table/TableCell.stx +1 -1
  102. package/src/ui/table/TableHead.stx +1 -1
  103. package/src/ui/table/TableHeader.stx +2 -2
  104. package/src/ui/table/TableRow.stx +1 -1
  105. package/src/ui/tabs/Tabs.stx +6 -6
  106. package/src/ui/textarea/Textarea.stx +6 -6
  107. package/src/ui/video/Video.stx +1 -1
  108. package/src/ui/virtual-table/VirtualTable.stx +6 -6
  109. package/dist/RadioGroup-qxmnm3xm.stx +0 -60
@@ -3,6 +3,30 @@ export const value = $props.value
3
3
  export const className = $props.className || ''
4
4
  export const as = $props.as || 'div'
5
5
 
6
+ /*
7
+ * Prop-array mode, alongside the slot API (stacksjs/stx#1930).
8
+ *
9
+ * `<RadioGroup :options="[{ label: 'A', value: 'a' }]" />` used to emit a
10
+ * hydration shell and nothing else: the labels reached the page only inside
11
+ * `data-stx-props`, so the choices were invisible without JavaScript and to a
12
+ * crawler. Every comparable component here — Tabs, Accordion, Select, Navigator,
13
+ * Breadcrumb — server-renders its content from a prop array, so this was the
14
+ * odd one out rather than a deliberate choice.
15
+ *
16
+ * The options are rendered here rather than delegated to <RadioGroupOption>
17
+ * because a loop variable cannot cross a component boundary as a `:` binding.
18
+ * Interpolating it would stringify the value — `1` and `"1"` would stop being
19
+ * distinguishable — and the group compares values by identity. Emitting the
20
+ * option markup directly keeps `data-option-value` exact JSON, which is what
21
+ * `syncOptionStates` already reads.
22
+ */
23
+ export const options = Array.isArray($props.options) ? $props.options : []
24
+ export const hasPropOptions = options.length > 0
25
+
26
+ const optionBaseClasses = 'relative flex cursor-pointer rounded-lg px-5 py-4 shadow-md focus:outline-none transition-all bg-white dark:bg-neutral-800 text-fg focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-60 focus-visible:ring-offset-2 focus-visible:ring-offset-blue-300 dark:focus-visible:ring-offset-blue-700'
27
+ export const optionClasses = optionBaseClasses
28
+ export const optionDisabledClasses = `${optionBaseClasses} opacity-50 cursor-not-allowed`
29
+
6
30
  const baseClasses = 'w-full'
7
31
  export const groupClasses = `${baseClasses} ${className}`.trim()
8
32
  export const radioGroupInstanceId = `stx-rg-${Math.random().toString(36).slice(2, 11)}`
@@ -40,11 +64,42 @@ function onSelect(event) {
40
64
  selectValue(event.detail)
41
65
  }
42
66
 
67
+ // Options rendered from the `options` prop have no <RadioGroupOption> behind
68
+ // them to dispatch stx:radio-select, so the group claims them by delegation.
69
+ // One listener on the root rather than a handler per option, which also means
70
+ // options added later are picked up with no re-binding.
71
+ function optionValueOf(el) {
72
+ try { return JSON.parse(el.dataset.optionValue ?? 'null') }
73
+ catch { return el.dataset.optionValue }
74
+ }
75
+
76
+ function onDelegatedActivate(event) {
77
+ const option = event.target.closest?.('[data-stx-radio-option]')
78
+ if (!option || !rootEl || !rootEl.contains(option)) return
79
+ if (option.getAttribute('aria-disabled') === 'true') return
80
+ // A <RadioGroupOption> dispatches its own event; claiming its click too would
81
+ // select twice and emit two change events.
82
+ if (option.dataset.stxRadioDelegated !== 'true') return
83
+ if (event.type === 'keydown' && event.key !== ' ' && event.key !== 'Enter') return
84
+ event.preventDefault()
85
+ selectValue(optionValueOf(option))
86
+ }
87
+
43
88
  onMount(() => {
44
89
  rootEl = document.querySelector(`[data-radio-group-id="${radioGroupInstanceId}"]`)
90
+ if (rootEl) {
91
+ rootEl.addEventListener('click', onDelegatedActivate)
92
+ rootEl.addEventListener('keydown', onDelegatedActivate)
93
+ }
45
94
  syncOptionStates()
46
95
  })
47
96
 
97
+ onDestroy(() => {
98
+ if (!rootEl) return
99
+ rootEl.removeEventListener('click', onDelegatedActivate)
100
+ rootEl.removeEventListener('keydown', onDelegatedActivate)
101
+ })
102
+
48
103
  defineExpose({ selected, selectValue })
49
104
  </script>
50
105
 
@@ -56,5 +111,28 @@ defineExpose({ selected, selectValue })
56
111
  role="radiogroup"
57
112
  @stx:radio-select="onSelect($event)"
58
113
  >
59
- <slot />
114
+ @if(hasPropOptions)
115
+ @foreach(option in options)
116
+ <div
117
+ data-stx-radio-option
118
+ data-stx-radio-delegated="true"
119
+ data-option-value="{{ JSON.stringify(option.value === undefined ? null : option.value) }}"
120
+ role="radio"
121
+ aria-checked="{{ option.value === value ? 'true' : 'false' }}"
122
+ data-checked="{{ option.value === value ? 'true' : 'false' }}"
123
+ aria-disabled="{{ option.disabled ? 'true' : 'false' }}"
124
+ tabindex="{{ option.disabled ? '-1' : '0' }}"
125
+ class="{{ option.disabled ? optionDisabledClasses : optionClasses }}"
126
+ >
127
+ <span class="flex flex-col">
128
+ <span class="text-sm font-medium">{{ option.label }}</span>
129
+ @if(option.description)
130
+ <span class="text-sm opacity-70">{{ option.description }}</span>
131
+ @endif
132
+ </span>
133
+ </div>
134
+ @endforeach
135
+ @else
136
+ <slot />
137
+ @endif
60
138
  </component>
@@ -2,7 +2,7 @@
2
2
  export const className = $props.className || ''
3
3
  export const as = $props.as || 'span'
4
4
 
5
- const baseClasses = 'inline text-sm text-neutral-500 dark:text-neutral-400'
5
+ const baseClasses = 'inline text-sm text-fg-soft'
6
6
  const descriptionClasses = `${baseClasses} ${className}`.trim()
7
7
  </script>
8
8
 
@@ -4,7 +4,7 @@ export const disabled = $props.disabled || false
4
4
  export const className = $props.className || ''
5
5
  export const as = $props.as || 'div'
6
6
 
7
- const baseClasses = 'relative flex cursor-pointer rounded-lg px-5 py-4 shadow-md focus:outline-none transition-all bg-white dark:bg-neutral-800 text-neutral-900 dark:text-neutral-100 focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-60 focus-visible:ring-offset-2 focus-visible:ring-offset-blue-300 dark:focus-visible:ring-offset-blue-700'
7
+ const baseClasses = 'relative flex cursor-pointer rounded-lg px-5 py-4 shadow-md focus:outline-none transition-all bg-white dark:bg-neutral-800 text-fg focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-60 focus-visible:ring-offset-2 focus-visible:ring-offset-blue-300 dark:focus-visible:ring-offset-blue-700'
8
8
  const disabledClasses = disabled ? 'opacity-50 cursor-not-allowed' : ''
9
9
  export const optionClasses = `${baseClasses} ${disabledClasses} ${className}`.trim()
10
10
  export const valueJson = JSON.stringify(value === undefined ? null : value)
@@ -14,14 +14,14 @@ const sizeClasses = { sm: 'px-3 py-1.5 text-sm h-8', md: 'px-4 py-2 text-base h-
14
14
  const baseClasses = 'block w-full border-0 rounded-md shadow-sm ring-1 ring-inset transition-colors duration-200 focus:ring-2 focus:ring-inset dark:bg-blue-gray-700 dark:text-gray-100'
15
15
  const stateClasses = error
16
16
  ? 'ring-red-300 dark:ring-red-600 text-red-900 dark:text-red-300 placeholder-red-300 dark:placeholder-red-500 focus:ring-red-500 dark:focus:ring-red-400'
17
- : 'ring-gray-300 dark:ring-gray-600 text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500 focus:ring-indigo-600 dark:focus:ring-indigo-400'
17
+ : 'ring-line-strong text-fg placeholder-fg-subtle focus:ring-accent'
18
18
  const disabledClasses = disabled
19
- ? 'opacity-50 cursor-not-allowed bg-gray-50 dark:bg-gray-800'
19
+ ? 'opacity-50 cursor-not-allowed bg-surface'
20
20
  : 'bg-white dark:bg-blue-gray-700 cursor-pointer hover:ring-gray-400 dark:hover:ring-gray-500'
21
21
 
22
22
  export const selectClasses = `${baseClasses} ${sizeClasses[size]} ${stateClasses} ${disabledClasses} ${className}`.trim()
23
- export const labelClasses = `block text-sm font-medium leading-6 ${error ? 'text-red-600 dark:text-red-400' : 'text-gray-900 dark:text-gray-100'}`.trim()
24
- export const helperClasses = `mt-2 text-sm ${error ? 'text-red-600 dark:text-red-400' : 'text-gray-500 dark:text-gray-400'}`.trim()
23
+ export const labelClasses = `block text-sm font-medium leading-6 ${error ? 'text-danger' : 'text-fg'}`.trim()
24
+ export const helperClasses = `mt-2 text-sm ${error ? 'text-danger' : 'text-fg-soft'}`.trim()
25
25
  export const selectId = `select-${value}`
26
26
  </script>
27
27
 
@@ -53,7 +53,7 @@ export const isLink = Boolean(href) && !disabled
53
53
  @if(indentStyle)style="{{ indentStyle }}"@endif
54
54
  >
55
55
  @if(isLink)
56
- <a href="{{ href }}" data-sidebar-item data-label="{{ label }}" class="{{ rowClasses }}" @if(active)data-active="true" aria-current="page"@endif>
56
+ <StxLink to="{{ href }}" data-sidebar-item data-label="{{ label }}" class="{{ rowClasses }}" :data-active="active ? 'true' : null" :aria-current="active ? 'page' : null">
57
57
  <span class="{{ disclosureClasses }}" @if(expandable)data-sidebar-disclosure role="button" aria-label="Toggle {{ label }}"@endif>
58
58
  @if(expandable)
59
59
  <span data-sidebar-chevron class="{{ chevronClasses }}" aria-hidden="true"></span>
@@ -68,7 +68,7 @@ export const isLink = Boolean(href) && !disabled
68
68
  @if(count !== '' && count !== null && count !== undefined)
69
69
  <span data-sidebar-count class="{{ countClasses }}">{{ count }}</span>
70
70
  @endif
71
- </a>
71
+ </StxLink>
72
72
  @else
73
73
  <button type="button" data-sidebar-item data-label="{{ label }}" class="{{ rowClasses }}" @if(active)data-active="true" aria-current="page"@endif @if(disabled)data-disabled disabled aria-disabled="true"@endif>
74
74
  <span class="{{ disclosureClasses }}" @if(expandable)data-sidebar-disclosure role="button" aria-label="Toggle {{ label }}"@endif>
@@ -51,8 +51,8 @@ function onPinnedClick(event) {
51
51
  >
52
52
  @foreach(items as item)
53
53
  @if(item.href)
54
- <a
55
- href="{{ item.href }}"
54
+ <StxLink
55
+ to="{{ item.href }}"
56
56
  data-space-pinned-item
57
57
  data-pinned-id="{{ item.id }}"
58
58
  class="{{ tileClasses }}"
@@ -65,7 +65,7 @@ function onPinnedClick(event) {
65
65
  @else
66
66
  <span class="{{ item.icon }} h-[20px] w-[20px]" @if(item.iconStyle)style="{{ item.iconStyle }}"@endif aria-hidden="true"></span>
67
67
  @endif
68
- </a>
68
+ </StxLink>
69
69
  @else
70
70
  <button
71
71
  type="button"
@@ -33,7 +33,7 @@ if (height) {
33
33
  styleString += `height: ${typeof height === 'number' ? height + 'px' : height};`
34
34
  }
35
35
 
36
- export const skeletonClasses = `bg-gray-200 dark:bg-gray-700 ${baseClasses} ${animationClasses} ${className}`.trim()
36
+ export const skeletonClasses = `bg-surface-sunken ${baseClasses} ${animationClasses} ${className}`.trim()
37
37
  export const containerClasses = count > 1 ? 'space-y-3' : ''
38
38
  </script>
39
39
 
@@ -16,19 +16,19 @@ const sizeClasses = {
16
16
 
17
17
  // Color classes
18
18
  const colorClasses = {
19
- primary: 'text-blue-600 dark:text-blue-500',
19
+ primary: 'text-info',
20
20
  secondary: 'text-purple-600 dark:text-purple-500',
21
- success: 'text-green-600 dark:text-green-500',
21
+ success: 'text-success',
22
22
  warning: 'text-yellow-600 dark:text-yellow-500',
23
- danger: 'text-red-600 dark:text-red-500',
23
+ danger: 'text-danger',
24
24
  info: 'text-cyan-600 dark:text-cyan-500',
25
25
  white: 'text-white',
26
- gray: 'text-gray-600 dark:text-gray-400',
26
+ gray: 'text-fg-muted',
27
27
  }
28
28
 
29
29
  export const containerClasses = `inline-flex flex-col items-center gap-2 ${className}`.trim()
30
30
  export const spinnerClasses = `${sizeClasses[size]} ${colorClasses[color]} animate-spin`.trim()
31
- export const labelClasses = `text-sm font-medium text-gray-700 dark:text-gray-300`.trim()
31
+ export const labelClasses = `text-sm font-medium text-fg-strong`.trim()
32
32
 
33
33
  // Dots size classes
34
34
  const dotsSizeClasses = {
@@ -40,11 +40,11 @@ const dotsSizeClasses = {
40
40
  }
41
41
 
42
42
  const dotsColorClasses = {
43
- primary: 'bg-blue-600 dark:bg-blue-500',
43
+ primary: 'bg-info-solid',
44
44
  secondary: 'bg-purple-600 dark:bg-purple-500',
45
- success: 'bg-green-600 dark:bg-green-500',
45
+ success: 'bg-success-solid',
46
46
  warning: 'bg-yellow-600 dark:bg-yellow-500',
47
- danger: 'bg-red-600 dark:bg-red-500',
47
+ danger: 'bg-danger-solid',
48
48
  info: 'bg-cyan-600 dark:bg-cyan-500',
49
49
  white: 'bg-white',
50
50
  gray: 'bg-gray-600 dark:bg-gray-400',
@@ -29,7 +29,7 @@ function onClick() {
29
29
  @click="onClick()"
30
30
  class="flex items-center gap-3 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 rounded-lg p-2"
31
31
  >
32
- <div class="{{ isComplete ? 'bg-blue-600 dark:bg-blue-500' : isCurrent ? 'bg-blue-600 dark:bg-blue-500 ring-4 ring-blue-200 dark:ring-blue-800' : 'bg-neutral-300 dark:bg-neutral-600' }} w-10 h-10 rounded-full flex items-center justify-center text-white font-semibold transition-all">
32
+ <div class="{{ isComplete ? 'bg-info-solid' : isCurrent ? 'bg-info-solid ring-4 ring-blue-200 dark:ring-blue-800' : 'bg-neutral-300 dark:bg-neutral-600' }} w-10 h-10 rounded-full flex items-center justify-center text-white font-semibold transition-all">
33
33
  @if(isComplete)
34
34
  <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
35
35
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
@@ -41,11 +41,11 @@ function onClick() {
41
41
 
42
42
  @if(label)
43
43
  <div class="text-left">
44
- <div class="text-sm font-medium {{ isCurrent ? 'text-blue-600 dark:text-blue-400' : 'text-neutral-900 dark:text-white' }}">
44
+ <div class="text-sm font-medium {{ isCurrent ? 'text-info' : 'text-neutral-900 dark:text-white' }}">
45
45
  {{ label }}
46
46
  </div>
47
47
  @if(description)
48
- <div class="text-xs text-neutral-500 dark:text-neutral-400">
48
+ <div class="text-xs text-fg-soft">
49
49
  {{ description }}
50
50
  </div>
51
51
  @endif
@@ -53,7 +53,7 @@ defineExpose({ isChecked, toggle })
53
53
 
54
54
  <label class="inline-flex items-center gap-3 cursor-pointer">
55
55
  @if(label)
56
- <span class="text-sm font-medium text-neutral-700 dark:text-neutral-300">
56
+ <span class="text-sm font-medium text-fg-strong">
57
57
  {{ label }}
58
58
  </span>
59
59
  @endif
@@ -3,7 +3,7 @@ export const className = $props.className || ''
3
3
  export const striped = $props.striped || false
4
4
  export const hoverable = $props.hoverable !== false
5
5
 
6
- const baseClasses = 'min-w-full divide-y divide-neutral-200 dark:divide-neutral-700'
6
+ const baseClasses = 'min-w-full divide-y divide-line'
7
7
  const tableClasses = `${baseClasses} ${className}`.trim()
8
8
  </script>
9
9
 
@@ -1,7 +1,7 @@
1
1
  <script server>
2
2
  export const className = $props.className || ''
3
3
 
4
- const baseClasses = 'bg-white divide-y divide-neutral-200 dark:bg-neutral-900 dark:divide-neutral-700'
4
+ const baseClasses = 'bg-white divide-y divide-line dark:bg-neutral-900'
5
5
  const bodyClasses = `${baseClasses} ${className}`.trim()
6
6
  </script>
7
7
 
@@ -1,7 +1,7 @@
1
1
  <script server>
2
2
  export const className = $props.className || ''
3
3
 
4
- const baseClasses = 'px-6 py-4 whitespace-nowrap text-sm text-neutral-900 dark:text-neutral-100'
4
+ const baseClasses = 'px-6 py-4 whitespace-nowrap text-sm text-fg'
5
5
  const cellClasses = `${baseClasses} ${className}`.trim()
6
6
  </script>
7
7
 
@@ -1,7 +1,7 @@
1
1
  <script server>
2
2
  export const className = $props.className || ''
3
3
 
4
- const baseClasses = 'bg-neutral-50 dark:bg-neutral-800'
4
+ const baseClasses = 'bg-surface'
5
5
  const headClasses = `${baseClasses} ${className}`.trim()
6
6
  </script>
7
7
 
@@ -2,8 +2,8 @@
2
2
  export const className = $props.className || ''
3
3
  export const sortable = $props.sortable || false
4
4
 
5
- const baseClasses = 'px-6 py-3 text-left text-xs font-medium text-neutral-500 uppercase tracking-wider dark:text-neutral-400'
6
- const sortableClasses = sortable ? 'cursor-pointer hover:text-neutral-700 dark:hover:text-neutral-200' : ''
5
+ const baseClasses = 'px-6 py-3 text-left text-xs font-medium text-fg-soft uppercase tracking-wider'
6
+ const sortableClasses = sortable ? 'cursor-pointer hover:text-fg-strong' : ''
7
7
  export const headerClasses = `${baseClasses} ${sortableClasses} ${className}`.trim()
8
8
  </script>
9
9
 
@@ -2,7 +2,7 @@
2
2
  export const className = $props.className || ''
3
3
  export const hoverable = $props.hoverable !== false
4
4
 
5
- const hoverClasses = hoverable ? 'hover:bg-neutral-50 dark:hover:bg-neutral-800' : ''
5
+ const hoverClasses = hoverable ? 'hover:bg-surface' : ''
6
6
  const rowClasses = `${hoverClasses} ${className}`.trim()
7
7
  </script>
8
8
 
@@ -24,9 +24,9 @@ const tabListOrientationClasses = {
24
24
  }
25
25
 
26
26
  const variantClasses = {
27
- line: 'border-gray-200 dark:border-gray-700',
28
- pills: 'gap-2 border-0 bg-gray-100 dark:bg-gray-800 p-1 rounded-lg',
29
- enclosed: 'border-gray-200 dark:border-gray-700',
27
+ line: 'border-line',
28
+ pills: 'gap-2 border-0 bg-surface-raised p-1 rounded-lg',
29
+ enclosed: 'border-line',
30
30
  }
31
31
 
32
32
  export const containerClasses = `flex ${orientationClasses[orientation]} ${className}`.trim()
@@ -97,14 +97,14 @@ function getTabClasses(index) {
97
97
  const base = 'px-4 py-2 font-medium text-sm transition-colors duration-150 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900'
98
98
  if (variant === 'line') {
99
99
  const border = orientation === 'horizontal' ? 'border-b-2' : 'border-r-2'
100
- return `${base} ${border} ${isActiveTab ? 'border-blue-500 text-blue-600 dark:text-blue-400' : 'border-transparent text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 hover:border-gray-300 dark:hover:border-gray-600'}`
100
+ return `${base} ${border} ${isActiveTab ? 'border-blue-500 text-info' : 'border-transparent text-fg-muted hover:text-fg hover:border-line-strong'}`
101
101
  }
102
102
  if (variant === 'pills') {
103
- return `${base} rounded-md ${isActiveTab ? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 shadow-sm' : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200'}`
103
+ return `${base} rounded-md ${isActiveTab ? 'bg-white dark:bg-gray-700 text-fg shadow-sm' : 'text-fg-muted hover:text-fg'}`
104
104
  }
105
105
  if (variant === 'enclosed') {
106
106
  const borders = orientation === 'horizontal' ? 'border-t border-l border-r rounded-t-md' : 'border-t border-l border-b rounded-l-md'
107
- return `${base} ${borders} ${isActiveTab ? 'bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700 -mb-px' : 'bg-gray-50 dark:bg-gray-900 border-transparent hover:border-gray-200 dark:hover:border-gray-700'}`
107
+ return `${base} ${borders} ${isActiveTab ? 'bg-white dark:bg-gray-800 border-line -mb-px' : 'bg-surface border-transparent hover:border-line'}`
108
108
  }
109
109
  return base
110
110
  }
@@ -19,16 +19,16 @@ const resizeClasses = { none: 'resize-none', vertical: 'resize-y', horizontal: '
19
19
  const baseClasses = 'block w-full border-0 rounded-md shadow-sm ring-1 ring-inset transition-colors duration-200 focus:ring-2 focus:ring-inset px-4 py-2 text-base dark:bg-blue-gray-700 dark:text-gray-100'
20
20
  const stateClasses = error
21
21
  ? 'ring-red-300 dark:ring-red-600 text-red-900 dark:text-red-300 placeholder-red-300 dark:placeholder-red-500 focus:ring-red-500 dark:focus:ring-red-400'
22
- : 'ring-gray-300 dark:ring-gray-600 text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500 focus:ring-indigo-600 dark:focus:ring-indigo-400'
22
+ : 'ring-line-strong text-fg placeholder-fg-subtle focus:ring-accent'
23
23
  const disabledClasses = disabled
24
- ? 'opacity-50 cursor-not-allowed bg-gray-50 dark:bg-gray-800'
24
+ ? 'opacity-50 cursor-not-allowed bg-surface'
25
25
  : readonly
26
- ? 'bg-gray-50 dark:bg-gray-800 cursor-default'
26
+ ? 'bg-surface cursor-default'
27
27
  : 'bg-white dark:bg-blue-gray-700'
28
28
 
29
29
  export const textareaClasses = `${baseClasses} ${stateClasses} ${disabledClasses} ${resizeClasses[resize]} ${className}`.trim()
30
- export const labelClasses = `block text-sm font-medium leading-6 mb-2 ${error ? 'text-red-600 dark:text-red-400' : 'text-gray-900 dark:text-gray-100'}`.trim()
31
- export const helperClasses = `mt-2 text-sm ${error ? 'text-red-600 dark:text-red-400' : 'text-gray-500 dark:text-gray-400'}`.trim()
30
+ export const labelClasses = `block text-sm font-medium leading-6 mb-2 ${error ? 'text-danger' : 'text-fg'}`.trim()
31
+ export const helperClasses = `mt-2 text-sm ${error ? 'text-danger' : 'text-fg-soft'}`.trim()
32
32
  </script>
33
33
 
34
34
  <script client>
@@ -101,7 +101,7 @@ defineExpose({ value: inputValue })
101
101
  @endif
102
102
 
103
103
  @if(showCount && maxLength)
104
- <p class="text-sm text-gray-500 dark:text-gray-400" x-text="charCount()"></p>
104
+ <p class="text-sm text-fg-soft" x-text="charCount()"></p>
105
105
  @endif
106
106
  </div>
107
107
  @endif
@@ -20,7 +20,7 @@ export const aspectRatioClass = aspectRatio === '16/9' ? 'aspect-video' : aspect
20
20
  <div class="{{ containerClasses }}">
21
21
  @if(title)
22
22
  <div class="mb-2 px-1">
23
- <h3 class="text-sm font-medium text-gray-900 dark:text-gray-100">
23
+ <h3 class="text-sm font-medium text-fg">
24
24
  {{ title }}
25
25
  </h3>
26
26
  </div>
@@ -10,12 +10,12 @@ export const hoverable = $props.hoverable ?? true
10
10
  export const className = $props.className || ''
11
11
 
12
12
  export const totalHeight = data.length * rowHeight
13
- export const containerClasses = `border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden ${className}`.trim()
14
- export const headerClasses = `bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 flex`.trim()
15
- export const headerCellClasses = `px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider`.trim()
13
+ export const containerClasses = `border border-line rounded-lg overflow-hidden ${className}`.trim()
14
+ export const headerClasses = `bg-surface border-b border-line flex`.trim()
15
+ export const headerCellClasses = `px-6 py-3 text-left text-xs font-medium text-fg-soft uppercase tracking-wider`.trim()
16
16
  export const bodyClasses = `overflow-auto bg-white dark:bg-gray-900`.trim()
17
- export const rowClasses = `absolute left-0 right-0 flex border-b border-gray-200 dark:border-gray-700 ${hoverable ? 'hover:bg-gray-50 dark:hover:bg-gray-800' : ''}`.trim()
18
- export const cellClasses = `px-6 py-4 text-sm text-gray-900 dark:text-gray-100`.trim()
17
+ export const rowClasses = `absolute left-0 right-0 flex border-b border-line ${hoverable ? 'hover:bg-surface' : ''}`.trim()
18
+ export const cellClasses = `px-6 py-4 text-sm text-fg`.trim()
19
19
  </script>
20
20
 
21
21
  <script client>
@@ -86,7 +86,7 @@ defineExpose({ scrollTop })
86
86
  <div
87
87
  :for="entry in visibleRows()"
88
88
  :key="entry.index"
89
- :class="entry.striped ? '{{ rowClasses }} bg-gray-50 dark:bg-gray-800' : '{{ rowClasses }}'"
89
+ :class="entry.striped ? '{{ rowClasses }} bg-surface' : '{{ rowClasses }}'"
90
90
  :style="`top: ${entry.top}px; height: {{ rowHeight }}px`"
91
91
  :data-index="entry.index"
92
92
  >
@@ -1,60 +0,0 @@
1
- <script server>
2
- export const value = $props.value
3
- export const className = $props.className || ''
4
- export const as = $props.as || 'div'
5
-
6
- const baseClasses = 'w-full'
7
- export const groupClasses = `${baseClasses} ${className}`.trim()
8
- export const radioGroupInstanceId = `stx-rg-${Math.random().toString(36).slice(2, 11)}`
9
- </script>
10
-
11
- <script client>
12
- const emit = defineEmits()
13
- const radioGroupInstanceId = {{ radioGroupInstanceId }}
14
- const initialValue = {{ value === undefined ? 'null' : value }}
15
- const selected = state(initialValue)
16
- let rootEl = null
17
-
18
- function selectValue(v) {
19
- selected.set(v)
20
- syncOptionStates()
21
- emit('change', v)
22
- }
23
-
24
- function syncOptionStates() {
25
- if (!rootEl) return
26
- const options = rootEl.querySelectorAll('[data-stx-radio-option]')
27
- for (const opt of options) {
28
- const raw = opt.dataset.optionValue
29
- let v
30
- try { v = JSON.parse(raw ?? 'null') }
31
- catch { v = raw }
32
- const checked = selected() === v
33
- opt.setAttribute('aria-checked', checked ? 'true' : 'false')
34
- opt.setAttribute('data-checked', checked ? 'true' : 'false')
35
- }
36
- }
37
-
38
- function onSelect(event) {
39
- event.stopPropagation()
40
- selectValue(event.detail)
41
- }
42
-
43
- onMount(() => {
44
- rootEl = document.querySelector(`[data-radio-group-id="${radioGroupInstanceId}"]`)
45
- syncOptionStates()
46
- })
47
-
48
- defineExpose({ selected, selectValue })
49
- </script>
50
-
51
- <component
52
- :is="{{ as }}"
53
- class="{{ groupClasses }}"
54
- data-stx-radio-group
55
- data-radio-group-id="{{ radioGroupInstanceId }}"
56
- role="radiogroup"
57
- @stx:radio-select="onSelect($event)"
58
- >
59
- <slot />
60
- </component>