@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
@@ -29,6 +29,7 @@ export const className = $props.className || ''
29
29
  export const sidebarUid = $props.id || `stx-sidebar-${Math.random().toString(36).slice(2, 8)}`
30
30
 
31
31
  const themeStyle = resolveSidebarTheme(theme)
32
+ export const selectionModel = themeStyle.selection
32
33
  export const hasShimmerRim = themeStyle.layers.shimmerRim === true
33
34
  export const tintClasses = themeStyle.layers.tint || ''
34
35
  export const scrollAreaClasses = themeStyle.scrollArea
@@ -277,6 +278,24 @@ function toggleCollapse() {
277
278
  // root element unless the app manages its own theme (data-theme attribute).
278
279
  // ---------------------------------------------------------------------------
279
280
 
281
+ // ---------------------------------------------------------------------------
282
+ // Key-window tracking — AppKit dims a source-list selection from the accent
283
+ // to a neutral fill the moment the window resigns key. `data-window-key` on
284
+ // the pane drives the two selection rule sets in the scoped CSS above.
285
+ // ---------------------------------------------------------------------------
286
+
287
+ function trackKeyWindow() {
288
+ // Driven by real focus/blur events only — never by an initial
289
+ // `document.hasFocus()` reading. A freshly loaded page, a background tab
290
+ // that has not been clicked yet, and an embedded webview that does not
291
+ // report focus at all all answer `false`, which would open the sidebar in
292
+ // the dimmed non-key state and leave it there. Starting from the key state
293
+ // and reacting to an actual blur keeps the default correct everywhere and
294
+ // still dims exactly when AppKit would.
295
+ window.addEventListener('focus', () => pane.setAttribute('data-window-key', 'true'))
296
+ window.addEventListener('blur', () => pane.setAttribute('data-window-key', 'false'))
297
+ }
298
+
280
299
  function followSystemAppearance() {
281
300
  const media = window.matchMedia('(prefers-color-scheme: dark)')
282
301
  function sync() {
@@ -292,6 +311,7 @@ onMount(() => {
292
311
  if (!pane) return
293
312
 
294
313
  followSystemAppearance()
314
+ trackKeyWindow()
295
315
 
296
316
  if (persistKeyProp) {
297
317
  try {
@@ -321,6 +341,8 @@ defineExpose({ collapsed, toggle: toggleCollapse })
321
341
  id="{{ sidebarUid }}"
322
342
  data-stx-sidebar
323
343
  data-sidebar-theme="{{ theme }}"
344
+ data-sidebar-selection="{{ selectionModel }}"
345
+ data-window-key="true"
324
346
  data-active-class="{{ activeClasses }}"
325
347
  class="{{ paneClasses }}"
326
348
  :style="`width: ${sidebarWidth()}`"
@@ -430,10 +452,69 @@ defineExpose({ collapsed, toggle: toggleCollapse })
430
452
  .stx-sidebar-rim::after { animation: none; }
431
453
  }
432
454
 
433
- /* Rows collapse like AppKit outlines: fixed row height animates to zero. */
455
+ /* ---------------------------------------------------------------------------
456
+ * Selection, `accent` model (macOS and its derivatives).
457
+ *
458
+ * AppKit paints a source-list selection in two states. In a key window the
459
+ * row takes the system accent and its label, icon, count and disclosure
460
+ * chevron all turn white; the label also goes semibold. When the window
461
+ * resigns key the fill drops to a neutral gray and the label stays dark.
462
+ * Measured from Mail.app: key fill is the accent, non-key fill sits about
463
+ * 5.5% black over the pane, and the icons desaturate to secondary gray.
464
+ *
465
+ * These are attribute rules rather than utility classes because the row's
466
+ * children have to recolor together, and because the controller toggles
467
+ * `data-active` on the client — CSS keyed on the attribute keeps the
468
+ * previously selected row's own icon tint intact when selection moves.
469
+ * ------------------------------------------------------------------------- */
470
+ [data-sidebar-selection="accent"] {
471
+ --stx-sidebar-accent: #007aff;
472
+ --stx-sidebar-selection-idle: rgba(0, 0, 0, 0.055);
473
+ --stx-sidebar-on-selection: #fff;
474
+ --stx-sidebar-icon-idle: #8e8e93;
475
+ }
476
+ @media (prefers-color-scheme: dark) {
477
+ [data-sidebar-selection="accent"] {
478
+ --stx-sidebar-accent: #0a84ff;
479
+ --stx-sidebar-selection-idle: rgba(255, 255, 255, 0.11);
480
+ --stx-sidebar-icon-idle: #98989d;
481
+ }
482
+ }
483
+
484
+ /* Per-item tint, published by SidebarItem as a custom property. */
485
+ [data-sidebar-selection="accent"] [data-sidebar-icon] {
486
+ color: var(--stx-sidebar-icon, currentColor);
487
+ }
488
+
489
+ [data-sidebar-selection="accent"][data-window-key="true"] [data-sidebar-item][data-active="true"] {
490
+ background: var(--stx-sidebar-accent);
491
+ color: var(--stx-sidebar-on-selection);
492
+ font-weight: 590;
493
+ }
494
+ [data-sidebar-selection="accent"][data-window-key="true"] [data-sidebar-item][data-active="true"] [data-sidebar-icon],
495
+ [data-sidebar-selection="accent"][data-window-key="true"] [data-sidebar-item][data-active="true"] [data-sidebar-count],
496
+ [data-sidebar-selection="accent"][data-window-key="true"] [data-sidebar-item][data-active="true"] [data-sidebar-chevron] {
497
+ color: var(--stx-sidebar-on-selection);
498
+ }
499
+ /* Hover must not lighten the selected row out from under the accent. */
500
+ [data-sidebar-selection="accent"][data-window-key="true"] [data-sidebar-item][data-active="true"]:hover {
501
+ background: var(--stx-sidebar-accent);
502
+ }
503
+
504
+ /* Window resigned key: neutral fill, label stays dark, icons desaturate. */
505
+ [data-sidebar-selection="accent"][data-window-key="false"] [data-sidebar-item][data-active="true"] {
506
+ background: var(--stx-sidebar-selection-idle);
507
+ font-weight: 590;
508
+ }
509
+ [data-sidebar-selection="accent"][data-window-key="false"] [data-sidebar-icon] {
510
+ color: var(--stx-sidebar-icon-idle);
511
+ }
512
+
513
+ /* Rows collapse like AppKit outlines: fixed row height animates to zero.
514
+ The 32px pitch is the row itself — AppKit leaves no gap, so a run of
515
+ selected or hovered rows reads as one continuous block. */
434
516
  [data-sidebar-row] {
435
- height: 32px; /* 30px row + 2px gap */
436
- padding-bottom: 2px;
517
+ height: 32px;
437
518
  transition: height 0.22s ease, opacity 0.16s ease;
438
519
  }
439
520
  [data-sidebar-row][data-collapsed] {
@@ -36,8 +36,12 @@ export const countClasses = themeStyle.count
36
36
  // Nested rows are indented; the icon tint accepts macOS system color names
37
37
  // ("blue", "yellow", …) or any CSS color.
38
38
  export const indentStyle = depth > 0 ? `padding-left: ${depth * themeStyle.indentPerLevel}px` : ''
39
+ // The tint is published as a custom property rather than `color:` directly.
40
+ // An inline `color` would outrank any stylesheet rule, so a selected row
41
+ // could never turn its icon white the way AppKit does, and toggling
42
+ // selection on the client could not restore the original tint afterwards.
39
43
  const tint = resolveIconColor(iconColor)
40
- export const iconStyle = tint ? `color: ${tint}` : ''
44
+ export const iconStyle = tint ? `--stx-sidebar-icon: ${tint}` : ''
41
45
  export const isLink = Boolean(href) && !disabled
42
46
  </script>
43
47
 
@@ -52,34 +56,34 @@ export const isLink = Boolean(href) && !disabled
52
56
  <a href="{{ href }}" data-sidebar-item data-label="{{ label }}" class="{{ rowClasses }}" @if(active)data-active="true" aria-current="page"@endif>
53
57
  <span class="{{ disclosureClasses }}" @if(expandable)data-sidebar-disclosure role="button" aria-label="Toggle {{ label }}"@endif>
54
58
  @if(expandable)
55
- <span class="{{ chevronClasses }}" aria-hidden="true"></span>
59
+ <span data-sidebar-chevron class="{{ chevronClasses }}" aria-hidden="true"></span>
56
60
  @endif
57
61
  </span>
58
62
  @if(image)
59
63
  <span class="{{ iconSlotClasses }}"><img src="{{ image }}" alt="" class="{{ imageClasses }}" /></span>
60
64
  @elseif(icon)
61
- <span class="{{ iconSlotClasses }}"><span class="{{ iconClasses }}" @if(iconStyle)style="{{ iconStyle }}"@endif aria-hidden="true"></span></span>
65
+ <span class="{{ iconSlotClasses }}"><span data-sidebar-icon class="{{ iconClasses }}" @if(iconStyle)style="{{ iconStyle }}"@endif aria-hidden="true"></span></span>
62
66
  @endif
63
67
  <span class="{{ labelClasses }}">{{ label }}</span>
64
68
  @if(count !== '' && count !== null && count !== undefined)
65
- <span class="{{ countClasses }}">{{ count }}</span>
69
+ <span data-sidebar-count class="{{ countClasses }}">{{ count }}</span>
66
70
  @endif
67
71
  </a>
68
72
  @else
69
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>
70
74
  <span class="{{ disclosureClasses }}" @if(expandable)data-sidebar-disclosure role="button" aria-label="Toggle {{ label }}"@endif>
71
75
  @if(expandable)
72
- <span class="{{ chevronClasses }}" aria-hidden="true"></span>
76
+ <span data-sidebar-chevron class="{{ chevronClasses }}" aria-hidden="true"></span>
73
77
  @endif
74
78
  </span>
75
79
  @if(image)
76
80
  <span class="{{ iconSlotClasses }}"><img src="{{ image }}" alt="" class="{{ imageClasses }}" /></span>
77
81
  @elseif(icon)
78
- <span class="{{ iconSlotClasses }}"><span class="{{ iconClasses }}" @if(iconStyle)style="{{ iconStyle }}"@endif aria-hidden="true"></span></span>
82
+ <span class="{{ iconSlotClasses }}"><span data-sidebar-icon class="{{ iconClasses }}" @if(iconStyle)style="{{ iconStyle }}"@endif aria-hidden="true"></span></span>
79
83
  @endif
80
84
  <span class="{{ labelClasses }}">{{ label }}</span>
81
85
  @if(count !== '' && count !== null && count !== undefined)
82
- <span class="{{ countClasses }}">{{ count }}</span>
86
+ <span data-sidebar-count class="{{ countClasses }}">{{ count }}</span>
83
87
  @endif
84
88
  </button>
85
89
  @endif
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  // @bun
2
- // ../../../../../../../tmp/stx-components-build-C0GkHf/stubs/stub-0.js
2
+ // ../../../../../../../tmp/stx-components-build-5MMjxD/stubs/stub-0.js
3
3
  var stub_0_default = { __stx: true, src: "./components/CodeBlock.stx" };
4
- // ../../../../../../../tmp/stx-components-build-C0GkHf/stubs/stub-1.js
4
+ // ../../../../../../../tmp/stx-components-build-5MMjxD/stubs/stub-1.js
5
5
  var stub_1_default = { __stx: true, src: "./components/Footer.stx" };
6
- // ../../../../../../../tmp/stx-components-build-C0GkHf/stubs/stub-2.js
6
+ // ../../../../../../../tmp/stx-components-build-5MMjxD/stubs/stub-2.js
7
7
  var stub_2_default = { __stx: true, src: "./components/Hero.stx" };
8
- // ../../../../../../../tmp/stx-components-build-C0GkHf/stubs/stub-3.js
8
+ // ../../../../../../../tmp/stx-components-build-5MMjxD/stubs/stub-3.js
9
9
  var stub_3_default = { __stx: true, src: "./components/Installation.stx" };
10
10
  // src/composables/useCopyCode.ts
11
11
  function useCopyCode(options) {
@@ -1132,13 +1132,13 @@ var RadioGroupOption_default = "./RadioGroupOption-s1d6r221.stx";
1132
1132
  // src/ui/select/Select.stx
1133
1133
  var Select_default = "./Select-1vnd0brz.stx";
1134
1134
  // src/ui/sidebar/Sidebar.stx
1135
- var Sidebar_default = "./Sidebar-4hn7vw4n.stx";
1135
+ var Sidebar_default = "./Sidebar-gbd5ctkp.stx";
1136
1136
  // src/ui/sidebar/SidebarFooter.stx
1137
1137
  var SidebarFooter_default = "./SidebarFooter-0aegr99p.stx";
1138
1138
  // src/ui/sidebar/SidebarHeader.stx
1139
1139
  var SidebarHeader_default = "./SidebarHeader-ed5wb13t.stx";
1140
1140
  // src/ui/sidebar/SidebarItem.stx
1141
- var SidebarItem_default = "./SidebarItem-gtrcw4ky.stx";
1141
+ var SidebarItem_default = "./SidebarItem-9rvbwhxr.stx";
1142
1142
  // src/ui/sidebar/SidebarSection.stx
1143
1143
  var SidebarSection_default = "./SidebarSection-qf8fhy55.stx";
1144
1144
  // src/ui/sidebar/themes.ts
@@ -1158,11 +1158,12 @@ var macosColors = {
1158
1158
  };
1159
1159
  var macos = {
1160
1160
  pane: [
1161
- "bg-white/55 dark:bg-[#1e1e23]/55",
1162
- "backdrop-blur-[60px] backdrop-saturate-[1.8]",
1161
+ "bg-[#f2f2f4]/90 dark:bg-[#1c1c1e]/88",
1162
+ "backdrop-blur-[50px] backdrop-saturate-[180%]",
1163
1163
  "text-black dark:text-white",
1164
1164
  "select-none"
1165
1165
  ].join(" "),
1166
+ selection: "accent",
1166
1167
  layers: {
1167
1168
  shimmerRim: true
1168
1169
  },
@@ -1179,18 +1180,18 @@ var macos = {
1179
1180
  "opacity-0 group-hover/section:opacity-100 transition-opacity duration-150",
1180
1181
  "transition-transform duration-200"
1181
1182
  ].join(" "),
1182
- sectionGroup: "flex flex-col gap-[2px]",
1183
+ sectionGroup: "flex flex-col",
1183
1184
  item: {
1184
1185
  base: [
1185
1186
  "flex w-full items-center",
1186
- "h-[30px] rounded-[9px] pl-[4px] pr-[8px]",
1187
+ "h-[32px] rounded-[8px] pl-[4px] pr-[8px]",
1187
1188
  "text-[13px] leading-[16px] font-normal",
1188
1189
  "text-black dark:text-white",
1189
1190
  "transition-colors duration-150 ease-out",
1190
1191
  "cursor-default"
1191
1192
  ].join(" "),
1192
1193
  hover: "hover:bg-black/4 dark:hover:bg-white/6",
1193
- active: "bg-black/8 dark:bg-white/14",
1194
+ active: "",
1194
1195
  pressed: "active:bg-black/10 dark:active:bg-white/18",
1195
1196
  disabled: "opacity-40 pointer-events-none",
1196
1197
  disclosure: "flex h-[16px] w-[18px] shrink-0 items-center justify-center",
@@ -1212,6 +1213,7 @@ var macos = {
1212
1213
  }
1213
1214
  };
1214
1215
  var workspace = {
1216
+ selection: "classes",
1215
1217
  pane: "bg-[#f4f4f3] dark:bg-neutral-950 text-zinc-950 dark:text-white select-none",
1216
1218
  layers: {},
1217
1219
  scrollArea: "flex-1 overflow-y-auto overflow-x-hidden px-5 py-5",
@@ -1235,6 +1237,7 @@ var workspace = {
1235
1237
  }
1236
1238
  };
1237
1239
  var desktop = {
1240
+ selection: "classes",
1238
1241
  pane: "bg-[#f3f3f1]/48 dark:bg-neutral-950/32 backdrop-blur-3xl backdrop-saturate-150 text-zinc-950 dark:text-white select-none",
1239
1242
  layers: {
1240
1243
  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%)]"
@@ -13,11 +13,22 @@ export declare function resolveIconColor(color?: string): string | undefined;
13
13
  *
14
14
  * The flagship theme is `macos` — a faithful recreation of the sidebar in
15
15
  * the latest macOS (Tahoe, macOS 26/27 "Liquid Glass"). Its metrics were
16
- * measured from native apps (Mail, Music, Notes) at @2x:
16
+ * measured pixel-by-pixel from Mail.app's source list at @2x:
17
17
  *
18
- * row pitch 32px = 30px row + 2px gap · highlight radius 9px
19
- * label 13px · count 13px secondary gray · section header 11px semibold
18
+ * row pitch 32px, and the highlight fills the whole 32px — AppKit leaves
19
+ * no gap between rows, so consecutive selected/hovered rows touch
20
+ * highlight radius 8px
21
+ * label 13px · count 13px secondary gray · section header 11px semibold,
22
+ * Title Case (NOT uppercase — uppercasing section headers is the single
23
+ * most common tell of a web sidebar imitating macOS)
20
24
  * icon 17px in a fixed 22px slot · disclosure gutter 18px · child indent 16px
25
+ * pane 234px wide, #f1f2f3 in light appearance, ~90% opaque so the
26
+ * backdrop bleeds through faintly rather than tinting the pane
27
+ *
28
+ * Selection follows AppKit's two-state model (`selection` below): in a key
29
+ * window the row takes the system accent with a white label, icon and count;
30
+ * when the window resigns key it falls back to a neutral fill with the label
31
+ * left dark. Icons desaturate to gray in the same non-key state.
21
32
  *
22
33
  * Legacy themes (`workspace`, `desktop`, `solid`, `transparent`, `vibrancy`)
23
34
  * are preserved verbatim from the previous variant maps. `tahoe` now aliases
@@ -46,6 +57,7 @@ export declare const sidebarThemes: {
46
57
  /** Class groups a sidebar theme provides. All values are utility classes. */
47
58
  export declare interface SidebarTheme {
48
59
  pane: string
60
+ selection: SidebarSelectionModel
49
61
  layers: {
50
62
  shimmerRim?: boolean
51
63
  tint?: string
@@ -71,4 +83,14 @@ export declare interface SidebarTheme {
71
83
  }
72
84
  }
73
85
  export type MacosColor = keyof typeof macosColors;
86
+ /**
87
+ * How a theme paints the selected row.
88
+ *
89
+ * `accent` reproduces AppKit: the pane owns CSS custom properties for the
90
+ * key/non-key fills, and scoped rules recolor the row's label, icon, count
91
+ * and chevron together. `classes` is the older utility-class model, where
92
+ * `item.active` is simply added to the row element and children are left
93
+ * alone. Legacy themes stay on `classes` so they render exactly as before.
94
+ */
95
+ export type SidebarSelectionModel = 'accent' | 'classes';
74
96
  export type SidebarThemeName = keyof typeof sidebarThemes;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/components",
3
3
  "type": "module",
4
- "version": "0.2.111",
4
+ "version": "0.2.113",
5
5
  "description": "A modern component library built with STX, featuring headwind utility classes and TypeScript syntax highlighting.",
6
6
  "author": "Chris Breuer <chris@stacksjs.org>",
7
7
  "license": "MIT",
@@ -47,6 +47,7 @@
47
47
  "files": [
48
48
  "README.md",
49
49
  "dist",
50
+ "src",
50
51
  "stx-plugin.ts"
51
52
  ],
52
53
  "scripts": {
@@ -61,9 +62,9 @@
61
62
  "prepublishOnly": "bun run build"
62
63
  },
63
64
  "dependencies": {
64
- "@cwcss/crosswind": "^0.2.6",
65
- "@stacksjs/stx": "0.2.111",
66
- "bun-plugin-stx": "0.2.111",
65
+ "@cwcss/crosswind": "^0.2.12",
66
+ "@stacksjs/stx": "0.2.113",
67
+ "bun-plugin-stx": "0.2.113",
67
68
  "ts-syntax-highlighter": "^0.2.1"
68
69
  },
69
70
  "devDependencies": {
@@ -0,0 +1,54 @@
1
+ <script server>
2
+ import { applyHeadwindClasses, highlight } from '../utils/highlighter'
3
+
4
+ export const code = $props.code || ''
5
+ export const language = $props.language || 'typescript'
6
+ export const theme = $props.theme || 'auto'
7
+ export const lineNumbers = $props.lineNumbers !== false
8
+ export const copyable = $props.copyable !== false
9
+ export const className = $props.className || ''
10
+
11
+ const result = await highlight(code, { language, theme, lineNumbers })
12
+ export const highlightedCode = applyHeadwindClasses(result.html, className)
13
+ </script>
14
+
15
+ <script client>
16
+ const code = {{ code }}
17
+ const copied = state(false)
18
+
19
+ async function copyToClipboard() {
20
+ try {
21
+ await navigator.clipboard.writeText(code)
22
+ copied.set(true)
23
+ setTimeout(() => copied.set(false), 2000)
24
+ }
25
+ catch (err) {
26
+ console.error('Failed to copy code:', err)
27
+ }
28
+ }
29
+ </script>
30
+
31
+ <div class="relative group">
32
+ {!! highlightedCode !!}
33
+
34
+ @if(copyable)
35
+ <button
36
+ @click="copyToClipboard()"
37
+ class="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 px-3 py-1.5 bg-neutral-800 hover:bg-neutral-700 text-white text-xs rounded-md font-medium"
38
+ aria-label="Copy code"
39
+ >
40
+ <span :show="copied()" class="flex items-center gap-1.5">
41
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
42
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
43
+ </svg>
44
+ Copied!
45
+ </span>
46
+ <span :show="!copied()" class="flex items-center gap-1.5">
47
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
48
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
49
+ </svg>
50
+ Copy
51
+ </span>
52
+ </button>
53
+ @endif
54
+ </div>
@@ -0,0 +1,66 @@
1
+ <script server>
2
+ export const author = $props.author || '@chrisbbreuer'
3
+ export const authorUrl = $props.authorUrl || 'https://github.com/chrisbbreuer'
4
+ export const project = $props.project || '@stacksjs'
5
+ export const projectUrl = $props.projectUrl || 'https://github.com/stacksjs/stacks'
6
+ export const twitterUrl = $props.twitterUrl || 'https://twitter.com/chrisbbreuer'
7
+ export const sponsorUrl = $props.sponsorUrl || 'https://github.com/sponsors/chrisbbreuer'
8
+ export const coffeeUrl = $props.coffeeUrl || 'https://www.buymeacoffee.com/xlbd'
9
+
10
+ export const currentYear = new Date().getFullYear()
11
+ </script>
12
+
13
+ <footer class="flex items-center justify-center mt-16 w-full pb-4 text-neutral-600 dark:text-neutral-400 opacity-60">
14
+ <div class="flex flex-col items-center justify-center">
15
+ <p class="text-sm">
16
+ With 💙 by
17
+ <a href="{{ authorUrl }}" class="text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300 transition-colors">
18
+ {{ author }}
19
+ </a>
20
+ &
21
+ <a href="{{ projectUrl }}" class="text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300 transition-colors">
22
+ {{ project }}
23
+ </a>
24
+ <span> © {{ currentYear }}</span>
25
+ </p>
26
+
27
+ <div class="mt-2 flex flex-col items-center sm:flex-row space-x-1 space-y-2 sm:space-y-0">
28
+ <div class="flex items-center space-x-1">
29
+ <svg class="w-4 h-4 text-blue-500" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
30
+ <path d="M8.29 20.251c7.547 0 11.675-6.253 11.675-11.675 0-.178 0-.355-.012-.53A8.348 8.348 0 0022 5.92a8.19 8.19 0 01-2.357.646 4.118 4.118 0 001.804-2.27 8.224 8.224 0 01-2.605.996 4.107 4.107 0 00-6.993 3.743 11.65 11.65 0 01-8.457-4.287 4.106 4.106 0 001.27 5.477A4.072 4.072 0 012.8 9.713v.052a4.105 4.105 0 003.292 4.022 4.095 4.095 0 01-1.853.07 4.108 4.108 0 003.834 2.85A8.233 8.233 0 012 18.407a11.616 11.616 0 006.29 1.84" />
31
+ </svg>
32
+ <span class="text-sm">
33
+ <a href="{{ twitterUrl }}" target="_blank" rel="noopener noreferrer" class="text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300 transition-colors">
34
+ Follow me on Twitter
35
+ </a>
36
+ </span>
37
+ </div>
38
+
39
+ <span class="hidden px-2 text-neutral-400 sm:block">|</span>
40
+
41
+ <div class="flex items-center space-x-1">
42
+ <svg class="w-4 h-4 text-blue-500" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
43
+ <path d="M20.216 6.415l-.132-.666c-.119-.598-.388-1.163-1.001-1.379-.197-.069-.42-.098-.57-.241-.152-.143-.196-.366-.231-.572-.065-.378-.125-.756-.192-1.133-.057-.325-.102-.69-.25-.987-.195-.4-.597-.634-.996-.788a5.723 5.723 0 00-.626-.194c-1-.263-2.05-.36-3.077-.416a25.834 25.834 0 00-3.7.062c-.915.083-1.88.184-2.75.5-.318.116-.646.256-.888.501-.297.302-.393.77-.177 1.146.154.267.415.456.692.58.36.162.737.284 1.123.366 1.075.238 2.189.331 3.287.37 1.218.05 2.437.01 3.65-.118.299-.033.598-.073.896-.119.352-.054.578-.513.474-.834-.124-.383-.457-.531-.834-.473-.466.074-.96.108-1.382.146-1.177.08-2.358.082-3.536.006a22.228 22.228 0 01-1.157-.107c-.086-.01-.18-.025-.258-.036-.243-.036-.484-.08-.724-.13-.111-.027-.111-.185 0-.212h.005c.277-.06.557-.108.838-.147h.002c.131-.009.263-.032.394-.048a25.076 25.076 0 013.426-.12c.674.019 1.347.067 2.017.144l.228.031c.267.04.533.088.798.145.392.085.895.113 1.07.542.055.137.08.288.111.431l.319 1.484a.237.237 0 01-.199.284h-.003c-.037.006-.075.01-.112.015a36.704 36.704 0 01-4.743.295 37.059 37.059 0 01-4.699-.304c-.14-.017-.293-.042-.417-.06-.326-.048-.649-.108-.973-.161-.393-.065-.768-.032-1.123.161-.29.16-.527.404-.675.701-.154.316-.199.66-.267 1-.069.34-.176.707-.135 1.056.087.753.613 1.365 1.37 1.502a39.69 39.69 0 0011.343.376.483.483 0 01.535.53l-.071.697-1.018 9.907c-.041.41-.047.832-.125 1.237-.122.637-.553 1.028-1.182 1.171-.577.131-1.165.2-1.756.205-.656.004-1.31-.025-1.966-.022-.699.004-1.556-.06-2.095-.58-.475-.458-.54-1.174-.605-1.793l-.731-7.013-.322-3.094c-.037-.351-.286-.695-.678-.678-.336.015-.718.3-.678.679l.228 2.185.949 9.112c.147 1.344 1.174 2.068 2.446 2.272.742.12 1.503.144 2.257.156.966.016 1.942.053 2.892-.122 1.408-.258 2.465-1.198 2.616-2.657.34-3.332.683-6.663 1.024-9.995l.215-2.087a.484.484 0 01.39-.426c.402-.078.787-.212 1.074-.518.455-.488.546-1.124.385-1.766zm-1.478.772c-.145.137-.363.201-.578.233-2.416.359-4.866.54-7.308.46-1.748-.06-3.477-.254-5.207-.498-.17-.024-.353-.055-.47-.18-.22-.236-.111-.71-.054-.995.052-.26.152-.609.463-.646.484-.057 1.046.148 1.526.22.577.088 1.156.16 1.737.212 2.48.226 5.002.19 7.472-.14.45-.06.899-.13 1.345-.21.399-.072.84-.206 1.08.206.166.281.188.657.162.974a.544.544 0 01-.169.364zm-6.159 3.9c-.862.37-1.84.788-3.109.788a5.884 5.884 0 01-1.569-.217l.877 9.004c.065.78.717 1.38 1.5 1.38 0 0 1.243.065 1.658.065.447 0 1.786-.065 1.786-.065.783 0 1.434-.6 1.499-1.38l.94-9.95a3.996 3.996 0 00-1.322-.238c-.826 0-1.491.284-2.26.613z" />
44
+ </svg>
45
+ <span class="text-sm">
46
+ <a href="{{ coffeeUrl }}" target="_blank" rel="noopener noreferrer" class="text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300 transition-colors">
47
+ Buy me a coffee
48
+ </a>
49
+ </span>
50
+ </div>
51
+
52
+ <span class="hidden px-2 text-neutral-400 sm:block">|</span>
53
+
54
+ <div class="flex items-center space-x-1">
55
+ <svg class="w-4 h-4 text-blue-500" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
56
+ <path d="m11.645 20.91-.007-.003-.022-.012a15.247 15.247 0 0 1-.383-.218 25.18 25.18 0 0 1-4.244-3.17C4.688 15.36 2.25 12.174 2.25 8.25 2.25 5.322 4.714 3 7.688 3A5.5 5.5 0 0 1 12 5.052 5.5 5.5 0 0 1 16.313 3c2.973 0 5.437 2.322 5.437 5.25 0 3.925-2.438 7.111-4.739 9.256a25.175 25.175 0 0 1-4.244 3.17 15.247 15.247 0 0 1-.383.219l-.022.012-.007.004-.003.001a.752.752 0 0 1-.704 0l-.003-.001Z" />
57
+ </svg>
58
+ <span class="text-sm">
59
+ <a href="{{ sponsorUrl }}" target="_blank" rel="noopener noreferrer" class="text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300 transition-colors">
60
+ Sponsor me on GitHub
61
+ </a>
62
+ </span>
63
+ </div>
64
+ </div>
65
+ </div>
66
+ </footer>
@@ -0,0 +1,25 @@
1
+ <script server>
2
+ export const title = $props.title || 'Component'
3
+ export const description = $props.description || 'A modern component built with STX'
4
+ export const githubUrl = $props.githubUrl || '#'
5
+ </script>
6
+
7
+ <div class="flex flex-col items-center gap-3">
8
+ <h1 class="text-5xl font-bold mb-3 -mt-5 bg-gradient-to-r from-blue-500 to-purple-600 bg-clip-text text-transparent">
9
+ {{ title }}
10
+ </h1>
11
+ <p class="mb-3 mt-0 text-lg text-neutral-600 dark:text-neutral-400">
12
+ {{ description }}
13
+ </p>
14
+ <div class="flex gap-2">
15
+ <slot name="demo" />
16
+ <a
17
+ href="{{ githubUrl }}"
18
+ target="_blank"
19
+ rel="noopener noreferrer"
20
+ class="inline-flex items-center justify-center h-10 px-8 rounded-md text-sm font-semibold bg-gradient-to-b from-white to-neutral-100 dark:from-neutral-800 dark:to-neutral-900 text-neutral-900 dark:text-white shadow-md hover:shadow-lg transition-all duration-200"
21
+ >
22
+ GitHub
23
+ </a>
24
+ </div>
25
+ </div>
@@ -0,0 +1,37 @@
1
+ <script server>
2
+ export const packageName = $props.packageName || '@stacksjs/component'
3
+ export const installCode = `bun add ${packageName}`
4
+ export const npmCode = `npm install ${packageName}`
5
+ export const pnpmCode = `pnpm add ${packageName}`
6
+ export const yarnCode = `yarn add ${packageName}`
7
+ </script>
8
+
9
+ <div class="installation">
10
+ <h2 class="my-2 text-lg font-semibold">Installation</h2>
11
+
12
+ <p class="my-3 text-base">
13
+ Install the component using your preferred package manager:
14
+ </p>
15
+
16
+ <div class="space-y-4">
17
+ <div>
18
+ <h3 class="text-sm font-medium mb-2 text-neutral-700 dark:text-neutral-300">Bun (recommended)</h3>
19
+ <CodeBlock :code="installCode" language="bash" />
20
+ </div>
21
+
22
+ <div>
23
+ <h3 class="text-sm font-medium mb-2 text-neutral-700 dark:text-neutral-300">npm</h3>
24
+ <CodeBlock :code="npmCode" language="bash" />
25
+ </div>
26
+
27
+ <div>
28
+ <h3 class="text-sm font-medium mb-2 text-neutral-700 dark:text-neutral-300">pnpm</h3>
29
+ <CodeBlock :code="pnpmCode" language="bash" />
30
+ </div>
31
+
32
+ <div>
33
+ <h3 class="text-sm font-medium mb-2 text-neutral-700 dark:text-neutral-300">Yarn</h3>
34
+ <CodeBlock :code="yarnCode" language="bash" />
35
+ </div>
36
+ </div>
37
+ </div>
@@ -0,0 +1,4 @@
1
+ export { default as CodeBlock } from './CodeBlock.stx'
2
+ export { default as Footer } from './Footer.stx'
3
+ export { default as Hero } from './Hero.stx'
4
+ export { default as Installation } from './Installation.stx'
@@ -0,0 +1,4 @@
1
+ export * from './useCopyCode'
2
+ export * from './useDarkMode'
3
+ export * from './useSEO'
4
+ export * from './utilities'
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Copy code to clipboard with visual feedback
3
+ */
4
+ export interface CopyCodeOptions {
5
+ code: string
6
+ onSuccess?: () => void
7
+ onError?: (error: Error) => void
8
+ timeout?: number
9
+ }
10
+
11
+ export interface CopyCodeResult {
12
+ copied: boolean
13
+ copy: () => Promise<void>
14
+ reset: () => void
15
+ }
16
+
17
+ export function useCopyCode(options: CopyCodeOptions): CopyCodeResult {
18
+ const { code, onSuccess, onError, timeout = 2000 } = options
19
+
20
+ let copied = false
21
+ let timeoutId: ReturnType<typeof setTimeout> | null = null
22
+
23
+ const reset = () => {
24
+ copied = false
25
+ if (timeoutId) {
26
+ clearTimeout(timeoutId)
27
+ timeoutId = null
28
+ }
29
+ }
30
+
31
+ const copy = async () => {
32
+ try {
33
+ if (!navigator.clipboard) {
34
+ throw new Error('Clipboard API not available')
35
+ }
36
+
37
+ await navigator.clipboard.writeText(code)
38
+ copied = true
39
+ onSuccess?.()
40
+
41
+ // Auto-reset after timeout
42
+ timeoutId = setTimeout(() => {
43
+ copied = false
44
+ timeoutId = null
45
+ }, timeout)
46
+ }
47
+ catch (error) {
48
+ const err = error instanceof Error ? error : new Error('Failed to copy code')
49
+ onError?.(err)
50
+ console.error('Failed to copy code:', err)
51
+ }
52
+ }
53
+
54
+ return {
55
+ copied,
56
+ copy,
57
+ reset,
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Simple synchronous copy function for direct use
63
+ */
64
+ export async function copyToClipboard(text: string): Promise<boolean> {
65
+ try {
66
+ await navigator.clipboard.writeText(text)
67
+ return true
68
+ }
69
+ catch (error) {
70
+ console.error('Failed to copy to clipboard:', error)
71
+ return false
72
+ }
73
+ }