@stacksjs/components 0.2.111 → 0.2.113

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 (190) hide show
  1. package/dist/{Sidebar-4hn7vw4n.stx → Sidebar-gbd5ctkp.stx} +84 -3
  2. package/dist/{SidebarItem-gtrcw4ky.stx → SidebarItem-9rvbwhxr.stx} +11 -7
  3. package/dist/index.js +14 -11
  4. package/dist/ui/sidebar/themes.d.ts +25 -3
  5. package/package.json +5 -4
  6. package/src/components/CodeBlock.stx +54 -0
  7. package/src/components/Footer.stx +66 -0
  8. package/src/components/Hero.stx +25 -0
  9. package/src/components/Installation.stx +37 -0
  10. package/src/components/index.ts +4 -0
  11. package/src/composables/index.ts +4 -0
  12. package/src/composables/useCopyCode.ts +73 -0
  13. package/src/composables/useDarkMode.ts +100 -0
  14. package/src/composables/useSEO.ts +230 -0
  15. package/src/composables/utilities/index.ts +7 -0
  16. package/src/composables/utilities/useClickOutside.ts +72 -0
  17. package/src/composables/utilities/useFocusTrap.ts +159 -0
  18. package/src/composables/utilities/useKeyboard.ts +124 -0
  19. package/src/composables/utilities/useLocalStorage.ts +132 -0
  20. package/src/composables/utilities/useMediaQuery.ts +118 -0
  21. package/src/index.ts +99 -0
  22. package/src/stx.d.ts +4 -0
  23. package/src/ui/accordion/Accordion.stx +146 -0
  24. package/src/ui/accordion/AccordionItem.stx +37 -0
  25. package/src/ui/accordion/index.ts +47 -0
  26. package/src/ui/audio/Audio.stx +50 -0
  27. package/src/ui/audio/README.md +41 -0
  28. package/src/ui/audio/index.ts +13 -0
  29. package/src/ui/auth/Login.stx +161 -0
  30. package/src/ui/auth/README.md +142 -0
  31. package/src/ui/auth/Signup.stx +159 -0
  32. package/src/ui/auth/TwoFactorChallenge.stx +111 -0
  33. package/src/ui/auth/index.ts +32 -0
  34. package/src/ui/avatar/Avatar.stx +77 -0
  35. package/src/ui/avatar/index.ts +12 -0
  36. package/src/ui/badge/Badge.stx +62 -0
  37. package/src/ui/badge/index.ts +10 -0
  38. package/src/ui/breadcrumb/Breadcrumb.stx +79 -0
  39. package/src/ui/breadcrumb/index.ts +15 -0
  40. package/src/ui/button/Button.stx +74 -0
  41. package/src/ui/button/README.md +80 -0
  42. package/src/ui/button/index.ts +85 -0
  43. package/src/ui/calendar/Calendar.stx +189 -0
  44. package/src/ui/calendar/README.md +254 -0
  45. package/src/ui/calendar/index.ts +14 -0
  46. package/src/ui/card/Card.stx +68 -0
  47. package/src/ui/card/index.ts +13 -0
  48. package/src/ui/checkbox/Checkbox.stx +86 -0
  49. package/src/ui/checkbox/index.ts +16 -0
  50. package/src/ui/combobox/Combobox.stx +96 -0
  51. package/src/ui/combobox/ComboboxButton.stx +25 -0
  52. package/src/ui/combobox/ComboboxInput.stx +32 -0
  53. package/src/ui/combobox/ComboboxOption.stx +49 -0
  54. package/src/ui/combobox/ComboboxOptions.stx +16 -0
  55. package/src/ui/combobox/README.md +150 -0
  56. package/src/ui/combobox/index.ts +40 -0
  57. package/src/ui/command-palette/CommandPalette.stx +81 -0
  58. package/src/ui/command-palette/CommandPaletteItem.stx +27 -0
  59. package/src/ui/command-palette/README.md +42 -0
  60. package/src/ui/command-palette/index.ts +15 -0
  61. package/src/ui/dialog/Dialog.stx +41 -0
  62. package/src/ui/dialog/DialogBackdrop.stx +9 -0
  63. package/src/ui/dialog/DialogDescription.stx +11 -0
  64. package/src/ui/dialog/DialogPanel.stx +16 -0
  65. package/src/ui/dialog/DialogTitle.stx +11 -0
  66. package/src/ui/dialog/README.md +121 -0
  67. package/src/ui/dialog/index.ts +32 -0
  68. package/src/ui/drawer/Drawer.stx +120 -0
  69. package/src/ui/drawer/README.md +90 -0
  70. package/src/ui/drawer/index.ts +9 -0
  71. package/src/ui/dropdown/Dropdown.stx +82 -0
  72. package/src/ui/dropdown/DropdownButton.stx +29 -0
  73. package/src/ui/dropdown/DropdownItem.stx +35 -0
  74. package/src/ui/dropdown/DropdownItems.stx +18 -0
  75. package/src/ui/dropdown/README.md +114 -0
  76. package/src/ui/dropdown/index.ts +29 -0
  77. package/src/ui/form/Form.stx +95 -0
  78. package/src/ui/form/index.ts +67 -0
  79. package/src/ui/heatmap/Heatmap.stx +294 -0
  80. package/src/ui/image/Image.stx +165 -0
  81. package/src/ui/image/README.md +164 -0
  82. package/src/ui/image/index.ts +22 -0
  83. package/src/ui/input/EmailInput.stx +30 -0
  84. package/src/ui/input/NumberInput.stx +126 -0
  85. package/src/ui/input/PasswordInput.stx +143 -0
  86. package/src/ui/input/SearchInput.stx +44 -0
  87. package/src/ui/input/TextInput.stx +132 -0
  88. package/src/ui/input/index.ts +180 -0
  89. package/src/ui/listbox/Listbox.stx +101 -0
  90. package/src/ui/listbox/ListboxButton.stx +28 -0
  91. package/src/ui/listbox/ListboxLabel.stx +11 -0
  92. package/src/ui/listbox/ListboxOption.stx +49 -0
  93. package/src/ui/listbox/ListboxOptions.stx +16 -0
  94. package/src/ui/listbox/README.md +135 -0
  95. package/src/ui/listbox/index.ts +39 -0
  96. package/src/ui/navigator/Navigator.stx +113 -0
  97. package/src/ui/navigator/README.md +352 -0
  98. package/src/ui/navigator/index.ts +20 -0
  99. package/src/ui/notification/Notification.stx +117 -0
  100. package/src/ui/notification/README.md +114 -0
  101. package/src/ui/notification/index.ts +12 -0
  102. package/src/ui/pagination/Pagination.stx +150 -0
  103. package/src/ui/pagination/index.ts +11 -0
  104. package/src/ui/payment/Checkout.stx +31 -0
  105. package/src/ui/payment/DefaultPaymentMethod.stx +62 -0
  106. package/src/ui/payment/PaymentMethods.stx +104 -0
  107. package/src/ui/payment/README.md +345 -0
  108. package/src/ui/payment/SubscriptionCheckout.stx +323 -0
  109. package/src/ui/payment/index.ts +59 -0
  110. package/src/ui/popover/Popover.stx +60 -0
  111. package/src/ui/popover/PopoverButton.stx +29 -0
  112. package/src/ui/popover/PopoverPanel.stx +23 -0
  113. package/src/ui/popover/README.md +91 -0
  114. package/src/ui/popover/index.ts +22 -0
  115. package/src/ui/portal/Portal.stx +57 -0
  116. package/src/ui/portal/index.ts +6 -0
  117. package/src/ui/progress/Progress.stx +131 -0
  118. package/src/ui/progress/index.ts +12 -0
  119. package/src/ui/radio/Radio.stx +77 -0
  120. package/src/ui/radio/index.ts +15 -0
  121. package/src/ui/radio-group/README.md +139 -0
  122. package/src/ui/radio-group/RadioGroup.stx +60 -0
  123. package/src/ui/radio-group/RadioGroupDescription.stx +11 -0
  124. package/src/ui/radio-group/RadioGroupLabel.stx +11 -0
  125. package/src/ui/radio-group/RadioGroupOption.stx +58 -0
  126. package/src/ui/radio-group/index.ts +30 -0
  127. package/src/ui/select/README.md +180 -0
  128. package/src/ui/select/Select.stx +82 -0
  129. package/src/ui/select/index.ts +21 -0
  130. package/src/ui/sidebar/Sidebar.stx +557 -0
  131. package/src/ui/sidebar/SidebarFooter.stx +71 -0
  132. package/src/ui/sidebar/SidebarHeader.stx +103 -0
  133. package/src/ui/sidebar/SidebarItem.stx +90 -0
  134. package/src/ui/sidebar/SidebarSection.stx +83 -0
  135. package/src/ui/sidebar/index.ts +146 -0
  136. package/src/ui/sidebar/themes.ts +277 -0
  137. package/src/ui/skeleton/Skeleton.stx +48 -0
  138. package/src/ui/skeleton/index.ts +11 -0
  139. package/src/ui/spinner/Spinner.stx +109 -0
  140. package/src/ui/spinner/index.ts +9 -0
  141. package/src/ui/stepper/README.md +36 -0
  142. package/src/ui/stepper/Stepper.stx +17 -0
  143. package/src/ui/stepper/StepperStep.stx +57 -0
  144. package/src/ui/stepper/index.ts +19 -0
  145. package/src/ui/storage/README.md +193 -0
  146. package/src/ui/storage/index.ts +347 -0
  147. package/src/ui/switch/README.md +114 -0
  148. package/src/ui/switch/Switch.stx +72 -0
  149. package/src/ui/switch/index.ts +10 -0
  150. package/src/ui/table/README.md +39 -0
  151. package/src/ui/table/Table.stx +14 -0
  152. package/src/ui/table/TableBody.stx +10 -0
  153. package/src/ui/table/TableCell.stx +10 -0
  154. package/src/ui/table/TableHead.stx +10 -0
  155. package/src/ui/table/TableHeader.stx +20 -0
  156. package/src/ui/table/TableRow.stx +11 -0
  157. package/src/ui/table/index.ts +35 -0
  158. package/src/ui/tabs/TabPanel.stx +25 -0
  159. package/src/ui/tabs/Tabs.stx +202 -0
  160. package/src/ui/tabs/index.ts +50 -0
  161. package/src/ui/teleport/Teleport.stx +65 -0
  162. package/src/ui/teleport/index.ts +7 -0
  163. package/src/ui/textarea/Textarea.stx +108 -0
  164. package/src/ui/textarea/index.ts +20 -0
  165. package/src/ui/tooltip/Tooltip.stx +71 -0
  166. package/src/ui/tooltip/index.ts +10 -0
  167. package/src/ui/transition/README.md +30 -0
  168. package/src/ui/transition/Transition.stx +102 -0
  169. package/src/ui/transition/index.ts +25 -0
  170. package/src/ui/video/README.md +47 -0
  171. package/src/ui/video/Video.stx +45 -0
  172. package/src/ui/video/index.ts +16 -0
  173. package/src/ui/virtual-list/VirtualList.stx +67 -0
  174. package/src/ui/virtual-list/index.ts +10 -0
  175. package/src/ui/virtual-table/VirtualTable.stx +103 -0
  176. package/src/ui/virtual-table/index.ts +21 -0
  177. package/src/utils/PROP_VALIDATION.md +458 -0
  178. package/src/utils/THEME_SYSTEM.md +702 -0
  179. package/src/utils/accessibility.ts +841 -0
  180. package/src/utils/animation/easing.ts +368 -0
  181. package/src/utils/animation/helpers.ts +223 -0
  182. package/src/utils/animation/index.ts +5 -0
  183. package/src/utils/animation/keyframes.ts +230 -0
  184. package/src/utils/error-handling.ts +475 -0
  185. package/src/utils/highlighter.ts +93 -0
  186. package/src/utils/i18n.ts +634 -0
  187. package/src/utils/keep-alive.ts +819 -0
  188. package/src/utils/lazy-loading.ts +530 -0
  189. package/src/utils/prop-validation.ts +478 -0
  190. package/src/utils/theme.ts +626 -0
@@ -0,0 +1,277 @@
1
+ /**
2
+ * Sidebar theme registry
3
+ *
4
+ * Every visual decision the sidebar makes lives here, expressed as utility
5
+ * classes, so a theme can be read top-to-bottom like a spec sheet.
6
+ *
7
+ * The flagship theme is `macos` — a faithful recreation of the sidebar in
8
+ * the latest macOS (Tahoe, macOS 26/27 "Liquid Glass"). Its metrics were
9
+ * measured pixel-by-pixel from Mail.app's source list at @2x:
10
+ *
11
+ * row pitch 32px, and the highlight fills the whole 32px — AppKit leaves
12
+ * no gap between rows, so consecutive selected/hovered rows touch
13
+ * highlight radius 8px
14
+ * label 13px · count 13px secondary gray · section header 11px semibold,
15
+ * Title Case (NOT uppercase — uppercasing section headers is the single
16
+ * most common tell of a web sidebar imitating macOS)
17
+ * icon 17px in a fixed 22px slot · disclosure gutter 18px · child indent 16px
18
+ * pane 234px wide, #f1f2f3 in light appearance, ~90% opaque so the
19
+ * backdrop bleeds through faintly rather than tinting the pane
20
+ *
21
+ * Selection follows AppKit's two-state model (`selection` below): in a key
22
+ * window the row takes the system accent with a white label, icon and count;
23
+ * when the window resigns key it falls back to a neutral fill with the label
24
+ * left dark. Icons desaturate to gray in the same non-key state.
25
+ *
26
+ * Legacy themes (`workspace`, `desktop`, `solid`, `transparent`, `vibrancy`)
27
+ * are preserved verbatim from the previous variant maps. `tahoe` now aliases
28
+ * `macos` — it always meant "look like macOS", and now it actually does.
29
+ */
30
+
31
+ /** macOS system colors (light appearance) for tinting sidebar icons. */
32
+ export const macosColors = {
33
+ blue: '#0088ff',
34
+ red: '#ff383c',
35
+ pink: '#ff2d55',
36
+ orange: '#ff8d28',
37
+ yellow: '#ffcc00',
38
+ green: '#34c759',
39
+ teal: '#00c3d0',
40
+ cyan: '#00c0e8',
41
+ indigo: '#6155f5',
42
+ purple: '#cb30e0',
43
+ brown: '#ac7f5e',
44
+ gray: '#8e8e93',
45
+ } as const
46
+
47
+ export type MacosColor = keyof typeof macosColors
48
+
49
+ /**
50
+ * How a theme paints the selected row.
51
+ *
52
+ * `accent` reproduces AppKit: the pane owns CSS custom properties for the
53
+ * key/non-key fills, and scoped rules recolor the row's label, icon, count
54
+ * and chevron together. `classes` is the older utility-class model, where
55
+ * `item.active` is simply added to the row element and children are left
56
+ * alone. Legacy themes stay on `classes` so they render exactly as before.
57
+ */
58
+ export type SidebarSelectionModel = 'accent' | 'classes'
59
+
60
+ /** Class groups a sidebar theme provides. All values are utility classes. */
61
+ export interface SidebarTheme {
62
+ /** The <aside> pane itself: material, text color, borders. */
63
+ pane: string
64
+ /** Which selection model the Sidebar controller and scoped CSS apply. */
65
+ selection: SidebarSelectionModel
66
+ /** Extra overlay layers rendered inside the pane (visual only). */
67
+ layers: {
68
+ /** Liquid Glass edge highlight — a bright rim that subtly shimmers. */
69
+ shimmerRim?: boolean
70
+ /** Soft tint gradient blended over the material. */
71
+ tint?: string
72
+ }
73
+ /** Scrollable content area between header and footer. */
74
+ scrollArea: string
75
+ /** Section header row (e.g. "Favorites", "iCloud"). */
76
+ sectionHeader: string
77
+ /** Chevron inside the section header (revealed on hover). */
78
+ sectionChevron: string
79
+ /** Wrapper around a section's items. */
80
+ sectionGroup: string
81
+ /** One navigation row, layout + typography. State styles live below. */
82
+ item: {
83
+ base: string
84
+ hover: string
85
+ active: string
86
+ pressed: string
87
+ disabled: string
88
+ /** Fixed-width leading slot the disclosure chevron sits in. */
89
+ disclosure: string
90
+ /** Chevron glyph itself (rotates 90° when expanded). */
91
+ chevron: string
92
+ /** Fixed-width slot that centers the 17px icon. */
93
+ iconSlot: string
94
+ icon: string
95
+ /** Thumbnail images (album art, avatars) rendered instead of an icon. */
96
+ image: string
97
+ label: string
98
+ /** Right-aligned unread/item count — plain gray text on macOS. */
99
+ count: string
100
+ /** Indentation added per nesting depth. */
101
+ indentPerLevel: number
102
+ }
103
+ }
104
+
105
+ const macos: SidebarTheme = {
106
+ pane: [
107
+ // Frosted sidebar material. In a Craft window with `webSidebarMaterial`
108
+ // the native vibrancy shows through; in a browser the backdrop blur
109
+ // approximates it over whatever sits behind the pane.
110
+ //
111
+ // The opacity is deliberately high. Sampling Mail.app over a saturated
112
+ // blue wallpaper, the pane moved by one or two 8-bit steps (#f1f2f2 to
113
+ // #f1f2f3) — AppKit's sidebar material is nearly opaque and only lets a
114
+ // hint of the backdrop through. A half-transparent pane reads as a web
115
+ // imitation, and over a dark page it inverts into a dark bar entirely.
116
+ 'bg-[#f2f2f4]/90 dark:bg-[#1c1c1e]/88',
117
+ 'backdrop-blur-[50px] backdrop-saturate-[180%]',
118
+ 'text-black dark:text-white',
119
+ 'select-none',
120
+ ].join(' '),
121
+ selection: 'accent',
122
+ layers: {
123
+ shimmerRim: true,
124
+ },
125
+ scrollArea: 'flex-1 overflow-y-auto overflow-x-hidden px-[10px] pb-[10px]',
126
+ sectionHeader: [
127
+ 'group/section flex w-full items-center',
128
+ 'pl-[8px] pr-[6px] pt-[16px] pb-[5px]',
129
+ 'text-[11px] font-semibold leading-[13px]',
130
+ 'text-[#3c3c43]/60 dark:text-[#ebebf5]/60',
131
+ ].join(' '),
132
+ sectionChevron: [
133
+ 'i-f7-chevron-down h-[11px] w-[11px] ml-auto',
134
+ 'text-[#3c3c43]/45 dark:text-[#ebebf5]/45',
135
+ 'opacity-0 group-hover/section:opacity-100 transition-opacity duration-150',
136
+ 'transition-transform duration-200',
137
+ ].join(' '),
138
+ // No gap: AppKit rows are contiguous, the 32px pitch IS the row.
139
+ sectionGroup: 'flex flex-col',
140
+ item: {
141
+ base: [
142
+ 'flex w-full items-center',
143
+ 'h-[32px] rounded-[8px] pl-[4px] pr-[8px]',
144
+ 'text-[13px] leading-[16px] font-normal',
145
+ 'text-black dark:text-white',
146
+ 'transition-colors duration-150 ease-out',
147
+ 'cursor-default',
148
+ ].join(' '),
149
+ hover: 'hover:bg-black/4 dark:hover:bg-white/6',
150
+ // Selection is painted by the `accent` model in Sidebar.stx's scoped CSS,
151
+ // which has to recolor the label, icon and count together. Leaving this
152
+ // empty keeps the controller from also stacking a conflicting background.
153
+ active: '',
154
+ pressed: 'active:bg-black/10 dark:active:bg-white/18',
155
+ disabled: 'opacity-40 pointer-events-none',
156
+ disclosure: 'flex h-[16px] w-[18px] shrink-0 items-center justify-center',
157
+ chevron: [
158
+ 'i-f7-chevron-right h-[11px] w-[11px]',
159
+ 'text-[#3c3c43]/55 dark:text-[#ebebf5]/55',
160
+ 'transition-transform duration-200 ease-out',
161
+ ].join(' '),
162
+ iconSlot: 'flex h-[20px] w-[22px] shrink-0 items-center justify-center mr-[7px]',
163
+ icon: 'h-[17px] w-[17px]',
164
+ image: 'h-[20px] w-[20px] rounded-[4px] object-cover shadow-sm',
165
+ label: 'flex-1 truncate text-left',
166
+ count: [
167
+ 'ml-[8px] shrink-0 tabular-nums',
168
+ 'text-[13px] leading-[16px]',
169
+ 'text-[#3c3c43]/60 dark:text-[#ebebf5]/60',
170
+ ].join(' '),
171
+ indentPerLevel: 16,
172
+ },
173
+ }
174
+
175
+ /**
176
+ * Legacy themes carried over from the old per-component variant maps so
177
+ * existing apps keep rendering identically. New work should use `macos`.
178
+ */
179
+ const workspace: SidebarTheme = {
180
+ selection: 'classes',
181
+ pane: 'bg-[#f4f4f3] dark:bg-neutral-950 text-zinc-950 dark:text-white select-none',
182
+ layers: {},
183
+ scrollArea: 'flex-1 overflow-y-auto overflow-x-hidden px-5 py-5',
184
+ sectionHeader: 'group/section flex w-full items-center gap-2.5 px-4 pb-2 pt-5 text-[13px] font-semibold uppercase tracking-[0.12em] text-zinc-400 dark:text-zinc-500',
185
+ sectionChevron: 'i-f7-chevron-down h-3 w-3 ml-auto opacity-0 group-hover/section:opacity-100 transition-opacity duration-150 transition-transform duration-200',
186
+ sectionGroup: 'flex flex-col space-y-1',
187
+ item: {
188
+ base: 'flex w-full items-center gap-3 rounded-[16px] px-3.5 py-2 text-[14px] leading-tight transition-colors duration-150',
189
+ hover: 'hover:bg-[#ececea] hover:text-zinc-950 dark:hover:bg-white/10',
190
+ active: 'bg-[#e7e7e5] text-zinc-950 dark:bg-white/12 dark:text-white',
191
+ pressed: 'active:bg-[#e2e2e0] dark:active:bg-white/15',
192
+ disabled: 'opacity-50 pointer-events-none',
193
+ disclosure: 'flex h-4 w-4 shrink-0 items-center justify-center',
194
+ chevron: 'i-f7-chevron-right h-3 w-3 opacity-50 transition-transform duration-200',
195
+ iconSlot: 'flex h-5 w-5 shrink-0 items-center justify-center',
196
+ icon: 'h-[18px] w-[18px] text-zinc-500 dark:text-zinc-500',
197
+ image: 'h-5 w-5 rounded object-cover',
198
+ label: 'flex-1 truncate text-left text-zinc-700 dark:text-zinc-300',
199
+ count: 'ml-auto grid h-5 min-w-5 place-items-center rounded-full bg-white/70 px-1.5 text-[11px] font-medium text-zinc-500 shadow-sm dark:bg-white/10 dark:text-zinc-400',
200
+ indentPerLevel: 12,
201
+ },
202
+ }
203
+
204
+ const desktop: SidebarTheme = {
205
+ selection: 'classes',
206
+ pane: 'bg-[#f3f3f1]/48 dark:bg-neutral-950/32 backdrop-blur-3xl backdrop-saturate-150 text-zinc-950 dark:text-white select-none',
207
+ layers: {
208
+ tint: 'bg-[linear-gradient(112deg,rgba(255,255,255,0.20)_0%,rgba(255,255,255,0.04)_45%,rgba(226,226,222,0.16)_100%)] dark:bg-[linear-gradient(112deg,rgba(255,255,255,0.08)_0%,rgba(255,255,255,0.02)_46%,rgba(255,255,255,0.06)_100%)]',
209
+ },
210
+ scrollArea: 'flex-1 overflow-y-auto overflow-x-hidden px-5 py-2',
211
+ sectionHeader: 'group/section flex w-full items-center gap-2.5 px-3 pb-1.5 pt-3 rounded-[14px] text-[12px] font-semibold uppercase tracking-[0.10em] text-zinc-500/80 dark:text-zinc-400/80',
212
+ sectionChevron: 'i-f7-chevron-down h-3 w-3 ml-auto opacity-0 group-hover/section:opacity-100 transition-opacity duration-150 transition-transform duration-200',
213
+ sectionGroup: 'flex flex-col space-y-1',
214
+ item: {
215
+ base: 'flex w-full items-center gap-3 rounded-[14px] px-3 py-1.5 text-[13.5px] leading-tight transition-colors duration-150',
216
+ hover: 'hover:bg-white/38 hover:text-zinc-950 dark:hover:bg-white/10',
217
+ active: 'bg-[#e8e8e6]/90 text-zinc-950 dark:bg-white/12 dark:text-white',
218
+ pressed: 'active:bg-white/50 dark:active:bg-white/15',
219
+ disabled: 'opacity-50 pointer-events-none',
220
+ disclosure: 'flex h-4 w-4 shrink-0 items-center justify-center',
221
+ chevron: 'i-f7-chevron-right h-3 w-3 opacity-50 transition-transform duration-200',
222
+ iconSlot: 'flex h-5 w-5 shrink-0 items-center justify-center',
223
+ icon: 'h-[18px] w-[18px] text-zinc-600 dark:text-zinc-400',
224
+ image: 'h-5 w-5 rounded object-cover',
225
+ label: 'flex-1 truncate text-left text-zinc-700 dark:text-zinc-300',
226
+ count: 'ml-auto grid h-5 min-w-5 place-items-center rounded-full bg-white/72 px-1.5 text-[11px] font-medium text-zinc-500 shadow-sm dark:bg-white/10 dark:text-zinc-400',
227
+ indentPerLevel: 12,
228
+ },
229
+ }
230
+
231
+ const solid: SidebarTheme = {
232
+ ...macos,
233
+ pane: 'bg-stone-100 dark:bg-neutral-900 text-black dark:text-white select-none',
234
+ layers: {},
235
+ }
236
+
237
+ const transparent: SidebarTheme = {
238
+ ...macos,
239
+ pane: 'bg-transparent text-black dark:text-white select-none',
240
+ layers: {},
241
+ }
242
+
243
+ const vibrancy: SidebarTheme = {
244
+ ...macos,
245
+ pane: 'bg-white/50 dark:bg-black/40 backdrop-blur-3xl backdrop-saturate-200 text-black dark:text-white select-none',
246
+ layers: {},
247
+ }
248
+
249
+ export const sidebarThemes: Record<string, SidebarTheme> = {
250
+ macos,
251
+ // Historical names people may still pass — all render the macOS look.
252
+ 'tahoe': macos,
253
+ 'macos-tahoe': macos,
254
+ 'macos-latest': macos,
255
+ workspace,
256
+ desktop,
257
+ solid,
258
+ transparent,
259
+ vibrancy,
260
+ }
261
+
262
+ export type SidebarThemeName = keyof typeof sidebarThemes
263
+
264
+ export function resolveSidebarTheme(name?: string): SidebarTheme {
265
+ return sidebarThemes[name || 'macos'] || sidebarThemes.macos
266
+ }
267
+
268
+ /**
269
+ * Resolve an item's icon tint. Accepts a macOS system color name
270
+ * (`"blue"`, `"red"`, …) or any CSS color, and falls back to the
271
+ * theme's default icon color when unset.
272
+ */
273
+ export function resolveIconColor(color?: string): string | undefined {
274
+ if (!color)
275
+ return undefined
276
+ return macosColors[color as MacosColor] || color
277
+ }
@@ -0,0 +1,48 @@
1
+ <script server>
2
+ export const variant = $props.variant || 'text'
3
+ export const count = $props.count || 1
4
+ export const width = $props.width || ''
5
+ export const height = $props.height || ''
6
+ export const circle = $props.circle || false
7
+ export const className = $props.className || ''
8
+ export const animate = $props.animate ?? true
9
+
10
+ // Variant classes
11
+ const variantClasses = {
12
+ text: 'h-4 rounded',
13
+ title: 'h-8 rounded',
14
+ avatar: 'w-10 h-10 rounded-full',
15
+ thumbnail: 'w-24 h-24 rounded-md',
16
+ button: 'h-10 w-32 rounded-md',
17
+ card: 'h-64 rounded-lg',
18
+ rect: 'rounded-md',
19
+ }
20
+
21
+ // Animation classes
22
+ const animationClasses = animate ? 'animate-pulse' : ''
23
+
24
+ // Build base classes
25
+ const baseClasses = circle ? 'rounded-full' : variantClasses[variant]
26
+
27
+ // Build style object
28
+ let styleString = ''
29
+ if (width) {
30
+ styleString += `width: ${typeof width === 'number' ? width + 'px' : width};`
31
+ }
32
+ if (height) {
33
+ styleString += `height: ${typeof height === 'number' ? height + 'px' : height};`
34
+ }
35
+
36
+ export const skeletonClasses = `bg-gray-200 dark:bg-gray-700 ${baseClasses} ${animationClasses} ${className}`.trim()
37
+ export const containerClasses = count > 1 ? 'space-y-3' : ''
38
+ </script>
39
+
40
+ @if(count > 1)
41
+ <div class="{{ containerClasses }}">
42
+ @for(i = 0; i < count; i++)
43
+ <div class="{{ skeletonClasses }}" style="{{ styleString }}" aria-busy="true" aria-live="polite"></div>
44
+ @endfor
45
+ </div>
46
+ @else
47
+ <div class="{{ skeletonClasses }}" style="{{ styleString }}" aria-busy="true" aria-live="polite"></div>
48
+ @endif
@@ -0,0 +1,11 @@
1
+ export { default as Skeleton } from './Skeleton.stx'
2
+
3
+ export interface SkeletonProps {
4
+ variant?: 'text' | 'title' | 'avatar' | 'thumbnail' | 'button' | 'card' | 'rect'
5
+ count?: number
6
+ width?: string | number
7
+ height?: string | number
8
+ circle?: boolean
9
+ animate?: boolean
10
+ className?: string
11
+ }
@@ -0,0 +1,109 @@
1
+ <script server>
2
+ export const variant = $props.variant || 'circle'
3
+ export const size = $props.size || 'md'
4
+ export const color = $props.color || 'primary'
5
+ export const label = $props.label || ''
6
+ export const className = $props.className || ''
7
+
8
+ // Size classes
9
+ const sizeClasses = {
10
+ xs: 'w-3 h-3',
11
+ sm: 'w-4 h-4',
12
+ md: 'w-6 h-6',
13
+ lg: 'w-8 h-8',
14
+ xl: 'w-12 h-12',
15
+ }
16
+
17
+ // Color classes
18
+ const colorClasses = {
19
+ primary: 'text-blue-600 dark:text-blue-500',
20
+ secondary: 'text-purple-600 dark:text-purple-500',
21
+ success: 'text-green-600 dark:text-green-500',
22
+ warning: 'text-yellow-600 dark:text-yellow-500',
23
+ danger: 'text-red-600 dark:text-red-500',
24
+ info: 'text-cyan-600 dark:text-cyan-500',
25
+ white: 'text-white',
26
+ gray: 'text-gray-600 dark:text-gray-400',
27
+ }
28
+
29
+ export const containerClasses = `inline-flex flex-col items-center gap-2 ${className}`.trim()
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()
32
+
33
+ // Dots size classes
34
+ const dotsSizeClasses = {
35
+ xs: 'w-1 h-1',
36
+ sm: 'w-1.5 h-1.5',
37
+ md: 'w-2 h-2',
38
+ lg: 'w-2.5 h-2.5',
39
+ xl: 'w-3 h-3',
40
+ }
41
+
42
+ const dotsColorClasses = {
43
+ primary: 'bg-blue-600 dark:bg-blue-500',
44
+ secondary: 'bg-purple-600 dark:bg-purple-500',
45
+ success: 'bg-green-600 dark:bg-green-500',
46
+ warning: 'bg-yellow-600 dark:bg-yellow-500',
47
+ danger: 'bg-red-600 dark:bg-red-500',
48
+ info: 'bg-cyan-600 dark:bg-cyan-500',
49
+ white: 'bg-white',
50
+ gray: 'bg-gray-600 dark:bg-gray-400',
51
+ }
52
+
53
+ export const dotClasses = `${dotsSizeClasses[size]} ${dotsColorClasses[color]} rounded-full`.trim()
54
+ </script>
55
+
56
+ <div class="{{ containerClasses }}" role="status" aria-live="polite" aria-busy="true">
57
+ @if(variant === 'circle')
58
+ <!-- Circle spinner (default) -->
59
+ <svg class="{{ spinnerClasses }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
60
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
61
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
62
+ </svg>
63
+ @elseif(variant === 'dots')
64
+ <!-- Dots spinner -->
65
+ <div class="flex items-center gap-1">
66
+ <div class="{{ dotClasses }}" style="animation: bounce 1.4s infinite ease-in-out both; animation-delay: -0.32s"></div>
67
+ <div class="{{ dotClasses }}" style="animation: bounce 1.4s infinite ease-in-out both; animation-delay: -0.16s"></div>
68
+ <div class="{{ dotClasses }}" style="animation: bounce 1.4s infinite ease-in-out both"></div>
69
+ </div>
70
+ @elseif(variant === 'bars')
71
+ <!-- Bars spinner -->
72
+ <div class="flex items-center gap-1">
73
+ <div class="w-1 bg-current rounded-full" style="height: {{ size === 'xs' ? '8px' : size === 'sm' ? '12px' : size === 'md' ? '16px' : size === 'lg' ? '20px' : '24px' }}; animation: bars 1.2s infinite ease-in-out; animation-delay: -0.24s"></div>
74
+ <div class="w-1 bg-current rounded-full" style="height: {{ size === 'xs' ? '8px' : size === 'sm' ? '12px' : size === 'md' ? '16px' : size === 'lg' ? '20px' : '24px' }}; animation: bars 1.2s infinite ease-in-out; animation-delay: -0.12s"></div>
75
+ <div class="w-1 bg-current rounded-full" style="height: {{ size === 'xs' ? '8px' : size === 'sm' ? '12px' : size === 'md' ? '16px' : size === 'lg' ? '20px' : '24px' }}; animation: bars 1.2s infinite ease-in-out"></div>
76
+ </div>
77
+ @elseif(variant === 'ring')
78
+ <!-- Ring spinner -->
79
+ <div class="{{ spinnerClasses }}" style="border: 2px solid currentColor; border-top-color: transparent; border-radius: 50%;"></div>
80
+ @endif
81
+
82
+ @if(label)
83
+ <span class="{{ labelClasses }}">{{ label }}</span>
84
+ @endif
85
+
86
+ <span class="sr-only">{{ label || 'Loading...' }}</span>
87
+ </div>
88
+
89
+ <style>
90
+ @keyframes bounce {
91
+ 0%, 80%, 100% {
92
+ transform: scale(0);
93
+ opacity: 0.5;
94
+ }
95
+ 40% {
96
+ transform: scale(1);
97
+ opacity: 1;
98
+ }
99
+ }
100
+
101
+ @keyframes bars {
102
+ 0%, 40%, 100% {
103
+ transform: scaleY(0.4);
104
+ }
105
+ 20% {
106
+ transform: scaleY(1);
107
+ }
108
+ }
109
+ </style>
@@ -0,0 +1,9 @@
1
+ export { default as Spinner } from './Spinner.stx'
2
+
3
+ export interface SpinnerProps {
4
+ variant?: 'circle' | 'dots' | 'bars' | 'ring'
5
+ size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
6
+ color?: 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'white' | 'gray'
7
+ label?: string
8
+ className?: string
9
+ }
@@ -0,0 +1,36 @@
1
+ # Stepper Component
2
+
3
+ A step-by-step progress indicator for multi-step processes.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ bun add @stacksjs/components
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```stx
14
+ <script server>
15
+ let currentStep = 0
16
+
17
+ function handleStepClick(step) {
18
+ currentStep = step
19
+ }
20
+ </script>
21
+
22
+ <Stepper :currentStep="currentStep">
23
+ <StepperStep :stepNumber="0" :currentStep="currentStep" label="Account" :onClick="handleStepClick" />
24
+ <StepperStep :stepNumber="1" :currentStep="currentStep" label="Profile" :onClick="handleStepClick" />
25
+ <StepperStep :stepNumber="2" :currentStep="currentStep" label="Confirmation" :onClick="handleStepClick" />
26
+ </Stepper>
27
+ ```
28
+
29
+ ## Features
30
+
31
+ - Horizontal and vertical orientation
32
+ - Clickable steps
33
+ - Complete/current/upcoming states
34
+ - Dark mode support
35
+ - Accessible
36
+ - Customizable styling
@@ -0,0 +1,17 @@
1
+ <script server>
2
+ export const currentStep = $props.currentStep || 0
3
+ export const orientation = $props.orientation || 'horizontal'
4
+ export const className = $props.className || ''
5
+ export const as = $props.as || 'div'
6
+
7
+ const orientationClasses = orientation === 'horizontal'
8
+ ? 'flex flex-row'
9
+ : 'flex flex-col'
10
+
11
+ const baseClasses = 'w-full'
12
+ const stepperClasses = `${baseClasses} ${orientationClasses} ${className}`.trim()
13
+ </script>
14
+
15
+ <component :is="{{ as }}" class="{{ stepperClasses }}">
16
+ <slot :currentStep="currentStep" />
17
+ </component>
@@ -0,0 +1,57 @@
1
+ <script server>
2
+ export const stepNumber = $props.stepNumber
3
+ export const currentStep = $props.currentStep || 0
4
+ export const label = $props.label || ''
5
+ export const description = $props.description || ''
6
+ export const className = $props.className || ''
7
+ export const as = $props.as || 'div'
8
+
9
+ export const isComplete = stepNumber < currentStep
10
+ export const isCurrent = stepNumber === currentStep
11
+ export const isUpcoming = stepNumber > currentStep
12
+
13
+ const baseClasses = 'flex items-center'
14
+ export const stepClasses = `${baseClasses} ${className}`.trim()
15
+ </script>
16
+
17
+ <script client>
18
+ const emit = defineEmits()
19
+ const stepNumber = {{ stepNumber }}
20
+
21
+ function onClick() {
22
+ emit('click', stepNumber)
23
+ }
24
+ </script>
25
+
26
+ <component :is="{{ as }}" class="{{ stepClasses }}">
27
+ <button
28
+ type="button"
29
+ @click="onClick()"
30
+ class="flex items-center gap-3 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 rounded-lg p-2"
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">
33
+ @if(isComplete)
34
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
35
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
36
+ </svg>
37
+ @else
38
+ {{ stepNumber + 1 }}
39
+ @endif
40
+ </div>
41
+
42
+ @if(label)
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' }}">
45
+ {{ label }}
46
+ </div>
47
+ @if(description)
48
+ <div class="text-xs text-neutral-500 dark:text-neutral-400">
49
+ {{ description }}
50
+ </div>
51
+ @endif
52
+ </div>
53
+ @endif
54
+ </button>
55
+
56
+ <slot :isComplete="isComplete" :isCurrent="isCurrent" :isUpcoming="isUpcoming" />
57
+ </component>
@@ -0,0 +1,19 @@
1
+ export { default as Stepper } from './Stepper.stx'
2
+ export { default as StepperStep } from './StepperStep.stx'
3
+
4
+ export interface StepperProps {
5
+ currentStep?: number
6
+ orientation?: 'horizontal' | 'vertical'
7
+ className?: string
8
+ as?: string
9
+ }
10
+
11
+ export interface StepperStepProps {
12
+ stepNumber: number
13
+ currentStep?: number
14
+ label?: string
15
+ description?: string
16
+ className?: string
17
+ as?: string
18
+ onClick?: (step: number) => void
19
+ }