@stacksjs/components 0.2.92 → 0.2.94

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 (188) hide show
  1. package/dist/{Sidebar-yb115a2w.stx → Sidebar-4hn7vw4n.stx} +18 -0
  2. package/dist/index.js +5 -5
  3. package/package.json +5 -6
  4. package/src/components/CodeBlock.stx +0 -54
  5. package/src/components/Footer.stx +0 -66
  6. package/src/components/Hero.stx +0 -25
  7. package/src/components/Installation.stx +0 -37
  8. package/src/components/index.ts +0 -4
  9. package/src/composables/index.ts +0 -4
  10. package/src/composables/useCopyCode.ts +0 -73
  11. package/src/composables/useDarkMode.ts +0 -100
  12. package/src/composables/useSEO.ts +0 -230
  13. package/src/composables/utilities/index.ts +0 -7
  14. package/src/composables/utilities/useClickOutside.ts +0 -72
  15. package/src/composables/utilities/useFocusTrap.ts +0 -159
  16. package/src/composables/utilities/useKeyboard.ts +0 -124
  17. package/src/composables/utilities/useLocalStorage.ts +0 -132
  18. package/src/composables/utilities/useMediaQuery.ts +0 -118
  19. package/src/index.ts +0 -99
  20. package/src/stx.d.ts +0 -4
  21. package/src/ui/accordion/Accordion.stx +0 -146
  22. package/src/ui/accordion/AccordionItem.stx +0 -37
  23. package/src/ui/accordion/index.ts +0 -47
  24. package/src/ui/audio/Audio.stx +0 -50
  25. package/src/ui/audio/README.md +0 -41
  26. package/src/ui/audio/index.ts +0 -13
  27. package/src/ui/auth/Login.stx +0 -161
  28. package/src/ui/auth/README.md +0 -142
  29. package/src/ui/auth/Signup.stx +0 -159
  30. package/src/ui/auth/TwoFactorChallenge.stx +0 -111
  31. package/src/ui/auth/index.ts +0 -32
  32. package/src/ui/avatar/Avatar.stx +0 -77
  33. package/src/ui/avatar/index.ts +0 -12
  34. package/src/ui/badge/Badge.stx +0 -62
  35. package/src/ui/badge/index.ts +0 -10
  36. package/src/ui/breadcrumb/Breadcrumb.stx +0 -79
  37. package/src/ui/breadcrumb/index.ts +0 -15
  38. package/src/ui/button/Button.stx +0 -74
  39. package/src/ui/button/README.md +0 -80
  40. package/src/ui/button/index.ts +0 -85
  41. package/src/ui/calendar/Calendar.stx +0 -189
  42. package/src/ui/calendar/README.md +0 -254
  43. package/src/ui/calendar/index.ts +0 -14
  44. package/src/ui/card/Card.stx +0 -68
  45. package/src/ui/card/index.ts +0 -13
  46. package/src/ui/checkbox/Checkbox.stx +0 -86
  47. package/src/ui/checkbox/index.ts +0 -16
  48. package/src/ui/combobox/Combobox.stx +0 -96
  49. package/src/ui/combobox/ComboboxButton.stx +0 -25
  50. package/src/ui/combobox/ComboboxInput.stx +0 -32
  51. package/src/ui/combobox/ComboboxOption.stx +0 -49
  52. package/src/ui/combobox/ComboboxOptions.stx +0 -16
  53. package/src/ui/combobox/README.md +0 -150
  54. package/src/ui/combobox/index.ts +0 -40
  55. package/src/ui/command-palette/CommandPalette.stx +0 -81
  56. package/src/ui/command-palette/CommandPaletteItem.stx +0 -27
  57. package/src/ui/command-palette/README.md +0 -42
  58. package/src/ui/command-palette/index.ts +0 -15
  59. package/src/ui/dialog/Dialog.stx +0 -41
  60. package/src/ui/dialog/DialogBackdrop.stx +0 -9
  61. package/src/ui/dialog/DialogDescription.stx +0 -11
  62. package/src/ui/dialog/DialogPanel.stx +0 -16
  63. package/src/ui/dialog/DialogTitle.stx +0 -11
  64. package/src/ui/dialog/README.md +0 -121
  65. package/src/ui/dialog/index.ts +0 -32
  66. package/src/ui/drawer/Drawer.stx +0 -120
  67. package/src/ui/drawer/README.md +0 -90
  68. package/src/ui/drawer/index.ts +0 -9
  69. package/src/ui/dropdown/Dropdown.stx +0 -82
  70. package/src/ui/dropdown/DropdownButton.stx +0 -29
  71. package/src/ui/dropdown/DropdownItem.stx +0 -35
  72. package/src/ui/dropdown/DropdownItems.stx +0 -18
  73. package/src/ui/dropdown/README.md +0 -114
  74. package/src/ui/dropdown/index.ts +0 -29
  75. package/src/ui/form/Form.stx +0 -95
  76. package/src/ui/form/index.ts +0 -67
  77. package/src/ui/heatmap/Heatmap.stx +0 -294
  78. package/src/ui/image/Image.stx +0 -165
  79. package/src/ui/image/README.md +0 -164
  80. package/src/ui/image/index.ts +0 -22
  81. package/src/ui/input/EmailInput.stx +0 -30
  82. package/src/ui/input/NumberInput.stx +0 -126
  83. package/src/ui/input/PasswordInput.stx +0 -143
  84. package/src/ui/input/SearchInput.stx +0 -44
  85. package/src/ui/input/TextInput.stx +0 -132
  86. package/src/ui/input/index.ts +0 -180
  87. package/src/ui/listbox/Listbox.stx +0 -101
  88. package/src/ui/listbox/ListboxButton.stx +0 -28
  89. package/src/ui/listbox/ListboxLabel.stx +0 -11
  90. package/src/ui/listbox/ListboxOption.stx +0 -49
  91. package/src/ui/listbox/ListboxOptions.stx +0 -16
  92. package/src/ui/listbox/README.md +0 -135
  93. package/src/ui/listbox/index.ts +0 -39
  94. package/src/ui/navigator/Navigator.stx +0 -113
  95. package/src/ui/navigator/README.md +0 -352
  96. package/src/ui/navigator/index.ts +0 -20
  97. package/src/ui/notification/Notification.stx +0 -117
  98. package/src/ui/notification/README.md +0 -114
  99. package/src/ui/notification/index.ts +0 -12
  100. package/src/ui/pagination/Pagination.stx +0 -150
  101. package/src/ui/pagination/index.ts +0 -11
  102. package/src/ui/payment/Checkout.stx +0 -31
  103. package/src/ui/payment/DefaultPaymentMethod.stx +0 -62
  104. package/src/ui/payment/PaymentMethods.stx +0 -104
  105. package/src/ui/payment/README.md +0 -345
  106. package/src/ui/payment/SubscriptionCheckout.stx +0 -323
  107. package/src/ui/payment/index.ts +0 -59
  108. package/src/ui/popover/Popover.stx +0 -60
  109. package/src/ui/popover/PopoverButton.stx +0 -29
  110. package/src/ui/popover/PopoverPanel.stx +0 -23
  111. package/src/ui/popover/README.md +0 -91
  112. package/src/ui/popover/index.ts +0 -22
  113. package/src/ui/portal/Portal.stx +0 -57
  114. package/src/ui/portal/index.ts +0 -6
  115. package/src/ui/progress/Progress.stx +0 -131
  116. package/src/ui/progress/index.ts +0 -12
  117. package/src/ui/radio/Radio.stx +0 -77
  118. package/src/ui/radio/index.ts +0 -15
  119. package/src/ui/radio-group/README.md +0 -139
  120. package/src/ui/radio-group/RadioGroup.stx +0 -60
  121. package/src/ui/radio-group/RadioGroupDescription.stx +0 -11
  122. package/src/ui/radio-group/RadioGroupLabel.stx +0 -11
  123. package/src/ui/radio-group/RadioGroupOption.stx +0 -58
  124. package/src/ui/radio-group/index.ts +0 -30
  125. package/src/ui/select/README.md +0 -180
  126. package/src/ui/select/Select.stx +0 -82
  127. package/src/ui/select/index.ts +0 -21
  128. package/src/ui/sidebar/Sidebar.stx +0 -458
  129. package/src/ui/sidebar/SidebarFooter.stx +0 -71
  130. package/src/ui/sidebar/SidebarHeader.stx +0 -103
  131. package/src/ui/sidebar/SidebarItem.stx +0 -86
  132. package/src/ui/sidebar/SidebarSection.stx +0 -83
  133. package/src/ui/sidebar/index.ts +0 -153
  134. package/src/ui/sidebar/themes.ts +0 -240
  135. package/src/ui/skeleton/Skeleton.stx +0 -48
  136. package/src/ui/skeleton/index.ts +0 -11
  137. package/src/ui/spinner/Spinner.stx +0 -109
  138. package/src/ui/spinner/index.ts +0 -9
  139. package/src/ui/stepper/README.md +0 -36
  140. package/src/ui/stepper/Stepper.stx +0 -17
  141. package/src/ui/stepper/StepperStep.stx +0 -57
  142. package/src/ui/stepper/index.ts +0 -19
  143. package/src/ui/storage/README.md +0 -193
  144. package/src/ui/storage/index.ts +0 -347
  145. package/src/ui/switch/README.md +0 -114
  146. package/src/ui/switch/Switch.stx +0 -72
  147. package/src/ui/switch/index.ts +0 -10
  148. package/src/ui/table/README.md +0 -39
  149. package/src/ui/table/Table.stx +0 -14
  150. package/src/ui/table/TableBody.stx +0 -10
  151. package/src/ui/table/TableCell.stx +0 -10
  152. package/src/ui/table/TableHead.stx +0 -10
  153. package/src/ui/table/TableHeader.stx +0 -20
  154. package/src/ui/table/TableRow.stx +0 -11
  155. package/src/ui/table/index.ts +0 -35
  156. package/src/ui/tabs/TabPanel.stx +0 -25
  157. package/src/ui/tabs/Tabs.stx +0 -202
  158. package/src/ui/tabs/index.ts +0 -50
  159. package/src/ui/teleport/Teleport.stx +0 -65
  160. package/src/ui/teleport/index.ts +0 -7
  161. package/src/ui/textarea/Textarea.stx +0 -108
  162. package/src/ui/textarea/index.ts +0 -20
  163. package/src/ui/tooltip/Tooltip.stx +0 -71
  164. package/src/ui/tooltip/index.ts +0 -10
  165. package/src/ui/transition/README.md +0 -30
  166. package/src/ui/transition/Transition.stx +0 -102
  167. package/src/ui/transition/index.ts +0 -25
  168. package/src/ui/video/README.md +0 -47
  169. package/src/ui/video/Video.stx +0 -45
  170. package/src/ui/video/index.ts +0 -16
  171. package/src/ui/virtual-list/VirtualList.stx +0 -67
  172. package/src/ui/virtual-list/index.ts +0 -10
  173. package/src/ui/virtual-table/VirtualTable.stx +0 -103
  174. package/src/ui/virtual-table/index.ts +0 -21
  175. package/src/utils/PROP_VALIDATION.md +0 -458
  176. package/src/utils/THEME_SYSTEM.md +0 -702
  177. package/src/utils/accessibility.ts +0 -841
  178. package/src/utils/animation/easing.ts +0 -368
  179. package/src/utils/animation/helpers.ts +0 -223
  180. package/src/utils/animation/index.ts +0 -5
  181. package/src/utils/animation/keyframes.ts +0 -230
  182. package/src/utils/error-handling.ts +0 -475
  183. package/src/utils/highlighter.ts +0 -93
  184. package/src/utils/i18n.ts +0 -634
  185. package/src/utils/keep-alive.ts +0 -819
  186. package/src/utils/lazy-loading.ts +0 -530
  187. package/src/utils/prop-validation.ts +0 -478
  188. package/src/utils/theme.ts +0 -626
@@ -1,132 +0,0 @@
1
- /**
2
- * Reactive localStorage hook with cross-tab synchronization
3
- *
4
- * @example
5
- * ```ts
6
- * const { value, setValue, removeValue } = useLocalStorage('user', { name: 'John' })
7
- *
8
- * setValue({ name: 'Jane' })
9
- * setValue(prev => ({ ...prev, age: 30 }))
10
- * removeValue()
11
- * ```
12
- */
13
-
14
- /**
15
- * Options for useLocalStorage hook
16
- */
17
- export interface UseLocalStorageOptions<T> {
18
- /** Custom serializer function - defaults to JSON.stringify */
19
- serializer?: (value: T) => string
20
- /** Custom deserializer function - defaults to JSON.parse */
21
- deserializer?: (value: string) => T
22
- /** Error handler function */
23
- onError?: (error: Error) => void
24
- }
25
-
26
- /**
27
- * Hook for reactive localStorage with cross-tab sync
28
- *
29
- * @param key - Storage key
30
- * @param initialValue - Initial value if key doesn't exist
31
- * @param options - Configuration options
32
- * @returns Object with value, setValue, and removeValue
33
- */
34
- export function useLocalStorage<T>(
35
- key: string,
36
- initialValue: T,
37
- options: UseLocalStorageOptions<T> = {},
38
- ): {
39
- value: T
40
- setValue: (newValue: T | ((_prev: T) => T)) => void
41
- removeValue: () => void
42
- } {
43
- const {
44
- serializer = JSON.stringify,
45
- deserializer = JSON.parse,
46
- onError = console.error,
47
- } = options
48
-
49
- // Get initial value from storage
50
- let storedValue: T = initialValue
51
-
52
- try {
53
- if (typeof window !== 'undefined') {
54
- const item = window.localStorage.getItem(key)
55
- if (item !== null) {
56
- storedValue = deserializer(item)
57
- }
58
- }
59
- }
60
- catch (error) {
61
- onError(error as Error)
62
- }
63
-
64
- let value = storedValue
65
- const listeners = new Set<(newValue: T) => void>()
66
-
67
- // Set value
68
- function setValue(newValue: T | ((prev: T) => T)) {
69
- try {
70
- // Allow value to be a function for same API as useState
71
- const valueToStore = typeof newValue === 'function' ? (newValue as (_prev: T) => T)(value) : newValue
72
-
73
- value = valueToStore
74
-
75
- // Save to localStorage
76
- if (typeof window !== 'undefined') {
77
- window.localStorage.setItem(key, serializer(valueToStore))
78
- }
79
-
80
- // Notify listeners
81
- listeners.forEach(listener => listener(valueToStore))
82
- }
83
- catch (error) {
84
- onError(error as Error)
85
- }
86
- }
87
-
88
- // Remove value
89
- function removeValue() {
90
- try {
91
- value = initialValue
92
-
93
- if (typeof window !== 'undefined') {
94
- window.localStorage.removeItem(key)
95
- }
96
-
97
- // Notify listeners
98
- listeners.forEach(listener => listener(initialValue))
99
- }
100
- catch (error) {
101
- onError(error as Error)
102
- }
103
- }
104
-
105
- // Listen for storage events (changes from other tabs/windows)
106
- if (typeof window !== 'undefined') {
107
- const handleStorageChange = (event: StorageEvent) => {
108
- if (event.key === key && event.newValue !== null) {
109
- try {
110
- const newValue = deserializer(event.newValue)
111
- value = newValue
112
- listeners.forEach(listener => listener(newValue))
113
- }
114
- catch (error) {
115
- onError(error as Error)
116
- }
117
- }
118
- else if (event.key === key && event.newValue === null) {
119
- value = initialValue
120
- listeners.forEach(listener => listener(initialValue))
121
- }
122
- }
123
-
124
- window.addEventListener('storage', handleStorageChange)
125
- }
126
-
127
- return {
128
- value,
129
- setValue,
130
- removeValue,
131
- }
132
- }
@@ -1,118 +0,0 @@
1
- /**
2
- * Hook for responsive media queries
3
- *
4
- * @example
5
- * ```ts
6
- * const { matches } = useMediaQuery('(min-width: 768px)')
7
- * const { matches: isDark } = useIsDarkMode()
8
- * const { matches: isMobile } = useIsMobile()
9
- * ```
10
- */
11
-
12
- /**
13
- * Options for useMediaQuery hook
14
- */
15
- export interface UseMediaQueryOptions {
16
- /** Default value when window.matchMedia is not available */
17
- defaultValue?: boolean
18
- }
19
-
20
- /**
21
- * Hook for responsive media queries
22
- *
23
- * @param query - Media query string
24
- * @param options - Configuration options
25
- * @returns Object with matches boolean and updateMatches function
26
- */
27
- export function useMediaQuery(
28
- query: string,
29
- options: UseMediaQueryOptions = {},
30
- ): { matches: boolean, updateMatches: () => void } {
31
- const { defaultValue = false } = options
32
-
33
- let matches = defaultValue
34
- let mediaQueryList: MediaQueryList | null = null
35
-
36
- // Initialize
37
- if (typeof window !== 'undefined' && 'matchMedia' in window) {
38
- mediaQueryList = window.matchMedia(query)
39
- matches = mediaQueryList.matches
40
-
41
- // Listen for changes
42
- const handleChange = (event: MediaQueryListEvent) => {
43
- matches = event.matches
44
- }
45
-
46
- if (mediaQueryList.addEventListener) {
47
- mediaQueryList.addEventListener('change', handleChange)
48
- }
49
- else {
50
- // Fallback for older browsers
51
- mediaQueryList.addListener(handleChange)
52
- }
53
- }
54
-
55
- function updateMatches() {
56
- if (mediaQueryList) {
57
- matches = mediaQueryList.matches
58
- }
59
- }
60
-
61
- return { matches, updateMatches }
62
- }
63
-
64
- /**
65
- * Common responsive breakpoints
66
- */
67
- export const Breakpoints = {
68
- 'xs': '(min-width: 320px)',
69
- 'sm': '(min-width: 640px)',
70
- 'md': '(min-width: 768px)',
71
- 'lg': '(min-width: 1024px)',
72
- 'xl': '(min-width: 1280px)',
73
- '2xl': '(min-width: 1536px)',
74
- 'mobile': '(max-width: 767px)',
75
- 'tablet': '(min-width: 768px) and (max-width: 1023px)',
76
- 'desktop': '(min-width: 1024px)',
77
- 'dark': '(prefers-color-scheme: dark)',
78
- 'light': '(prefers-color-scheme: light)',
79
- 'reducedMotion': '(prefers-reduced-motion: reduce)',
80
- 'portrait': '(orientation: portrait)',
81
- 'landscape': '(orientation: landscape)',
82
- } as const
83
-
84
- /**
85
- * Use a predefined breakpoint
86
- * @param breakpoint - Breakpoint name from Breakpoints constant
87
- */
88
- export function useBreakpoint(breakpoint: keyof typeof Breakpoints): { matches: boolean, updateMatches: () => void } {
89
- return useMediaQuery(Breakpoints[breakpoint])
90
- }
91
-
92
- /**
93
- * Check if viewport is mobile (max-width: 767px)
94
- */
95
- export function useIsMobile(): { matches: boolean, updateMatches: () => void } {
96
- return useMediaQuery(Breakpoints.mobile)
97
- }
98
-
99
- /**
100
- * Check if viewport is desktop (min-width: 1024px)
101
- */
102
- export function useIsDesktop(): { matches: boolean, updateMatches: () => void } {
103
- return useMediaQuery(Breakpoints.desktop)
104
- }
105
-
106
- /**
107
- * Check if user prefers dark color scheme
108
- */
109
- export function useIsDarkMode(): { matches: boolean, updateMatches: () => void } {
110
- return useMediaQuery(Breakpoints.dark)
111
- }
112
-
113
- /**
114
- * Check if user prefers reduced motion
115
- */
116
- export function usePrefersReducedMotion(): { matches: boolean, updateMatches: () => void } {
117
- return useMediaQuery(Breakpoints.reducedMotion)
118
- }
package/src/index.ts DELETED
@@ -1,99 +0,0 @@
1
- // Export shared components (these will be imported as .stx files)
2
- export { default as CodeBlock } from './components/CodeBlock.stx'
3
-
4
- export { default as Footer } from './components/Footer.stx'
5
- export { default as Hero } from './components/Hero.stx'
6
- export { default as Installation } from './components/Installation.stx'
7
- // Export composables
8
- export * from './composables'
9
- // Export UI components
10
- export * from './ui/accordion'
11
- export * from './ui/audio'
12
- export * from './ui/auth'
13
- export * from './ui/avatar'
14
- export * from './ui/badge'
15
-
16
- export * from './ui/breadcrumb'
17
- export * from './ui/button'
18
- export * from './ui/calendar'
19
- export * from './ui/card'
20
-
21
- export * from './ui/checkbox'
22
- export * from './ui/combobox'
23
- export * from './ui/command-palette'
24
- export * from './ui/dialog'
25
- export * from './ui/drawer'
26
- export * from './ui/dropdown'
27
- export * from './ui/form'
28
- export * from './ui/image'
29
- export * from './ui/input'
30
- export * from './ui/listbox'
31
- export * from './ui/navigator'
32
- export * from './ui/notification'
33
- export * from './ui/pagination'
34
- export * from './ui/payment'
35
- export * from './ui/popover'
36
- export * from './ui/portal'
37
- export * from './ui/progress'
38
- export * from './ui/radio'
39
- export * from './ui/radio-group'
40
- export * from './ui/select'
41
- export * from './ui/sidebar'
42
- export * from './ui/skeleton'
43
- export * from './ui/spinner'
44
- export * from './ui/stepper'
45
- export * from './ui/storage'
46
- export * from './ui/switch'
47
- export * from './ui/table'
48
- export * from './ui/tabs'
49
- export * from './ui/teleport'
50
- export * from './ui/textarea'
51
- export * from './ui/tooltip'
52
- export * from './ui/transition'
53
- export * from './ui/video'
54
- export * from './ui/virtual-list'
55
- export * from './ui/virtual-table'
56
- export * from './utils/accessibility'
57
- export * from './utils/animation'
58
- export * from './utils/error-handling'
59
- // Export utilities
60
- export * from './utils/highlighter'
61
- export * from './utils/i18n'
62
- export * from './utils/keep-alive'
63
- export * from './utils/lazy-loading'
64
- export * from './utils/prop-validation'
65
- export * from './utils/theme'
66
-
67
- // Types
68
- export interface ComponentProps {
69
- className?: string
70
- [key: string]: any
71
- }
72
-
73
- export interface CodeBlockProps extends ComponentProps {
74
- code: string
75
- language?: string
76
- theme?: 'light' | 'dark' | 'auto'
77
- lineNumbers?: boolean
78
- copyable?: boolean
79
- }
80
-
81
- export interface HeroProps extends ComponentProps {
82
- title?: string
83
- description?: string
84
- githubUrl?: string
85
- }
86
-
87
- export interface FooterProps extends ComponentProps {
88
- author?: string
89
- authorUrl?: string
90
- project?: string
91
- projectUrl?: string
92
- twitterUrl?: string
93
- sponsorUrl?: string
94
- coffeeUrl?: string
95
- }
96
-
97
- export interface InstallationProps extends ComponentProps {
98
- packageName?: string
99
- }
package/src/stx.d.ts DELETED
@@ -1,4 +0,0 @@
1
- declare module '*.stx' {
2
- const component: any
3
- export default component
4
- }
@@ -1,146 +0,0 @@
1
- <script server>
2
- // Two modes (see stacksjs/stx#1703):
3
- // 1. Legacy prop API — pass `items={[{title, content}, …]}` for simple
4
- // string-content accordions (kept for backward compatibility with
5
- // existing visual snapshots and consumers).
6
- // 2. Slot API — drop `<AccordionItem title="…">` children for arbitrary
7
- // content. Each AccordionItem renders its own header + content panel;
8
- // this parent attaches click handlers and toggles content visibility.
9
- export const items = $props.items || []
10
- export const allowMultiple = $props.allowMultiple || false
11
- export const defaultOpen = $props.defaultOpen || []
12
- export const className = $props.className || ''
13
-
14
- export const accordionClasses = `divide-y divide-gray-200 dark:divide-gray-700 border border-gray-200 dark:border-gray-700 rounded-lg ${className}`.trim()
15
- export const headerClasses = `w-full flex items-center justify-between px-4 py-3 text-left font-medium text-gray-900 dark:text-gray-100 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors duration-150 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900`.trim()
16
- export const iconClasses = `w-5 h-5 text-gray-500 dark:text-gray-400 transition-transform duration-200`.trim()
17
- export const contentClasses = `px-4 py-3 text-gray-700 dark:text-gray-300`.trim()
18
- export const hasPropItems = Array.isArray(items) && items.length > 0
19
- </script>
20
-
21
- <script client>
22
- const emit = defineEmits()
23
- const allowMultiple = {{ allowMultiple }}
24
- const hasPropItems = {{ hasPropItems }}
25
- const containerRef = useRef()
26
- const openItems = state({{ defaultOpen }})
27
-
28
- function findOwnItems() {
29
- const root = containerRef.value
30
- if (!root) return []
31
- return Array.from(root.querySelectorAll('[data-stx-accordion-item]'))
32
- .filter(item => item.closest('[data-stx-accordion]') === root)
33
- }
34
-
35
- onMount(() => {
36
- if (hasPropItems) return // legacy mode: server-rendered, no wiring needed
37
- const items = findOwnItems()
38
- items.forEach((item, idx) => {
39
- const header = item.querySelector(':scope > [data-stx-accordion-header]')
40
- if (header && !header.__stx_wired) {
41
- header.__stx_wired = true
42
- header.addEventListener('click', () => toggleItem(idx))
43
- header.addEventListener('keydown', (e) => onHeaderKey(e, idx))
44
- }
45
- })
46
- })
47
-
48
- // Sync the open state into the DOM: show/hide content panels, update
49
- // aria-expanded on headers, rotate chevrons.
50
- effect(() => {
51
- if (hasPropItems) return
52
- const items = findOwnItems()
53
- const open = openItems()
54
- items.forEach((item, idx) => {
55
- const isOpenItem = open.includes(idx)
56
- const header = item.querySelector(':scope > [data-stx-accordion-header]')
57
- const content = item.querySelector(':scope > [data-stx-accordion-content]')
58
- const chevron = item.querySelector(':scope > [data-stx-accordion-header] [data-stx-accordion-chevron]')
59
- if (header) header.setAttribute('aria-expanded', isOpenItem ? 'true' : 'false')
60
- if (content) {
61
- if (isOpenItem) content.removeAttribute('hidden')
62
- else content.setAttribute('hidden', '')
63
- }
64
- if (chevron) chevron.style.transform = `rotate(${isOpenItem ? 180 : 0}deg)`
65
- })
66
- })
67
-
68
- function toggleItem(index) {
69
- const cur = openItems()
70
- if (allowMultiple) {
71
- openItems.set(cur.includes(index) ? cur.filter(i => i !== index) : [...cur, index])
72
- }
73
- else {
74
- openItems.set(cur.includes(index) ? [] : [index])
75
- }
76
- emit('change', openItems())
77
- }
78
-
79
- function isOpen(index) {
80
- return openItems().includes(index)
81
- }
82
-
83
- function onHeaderKey(event, index) {
84
- const root = containerRef.value
85
- if (!root) return
86
- const buttons = root.querySelectorAll('button[data-stx-accordion-header], button[data-accordion-header]')
87
- if (event.key === 'ArrowDown') {
88
- event.preventDefault()
89
- buttons[index + 1]?.focus()
90
- }
91
- else if (event.key === 'ArrowUp') {
92
- event.preventDefault()
93
- buttons[index - 1]?.focus()
94
- }
95
- else if (event.key === 'Home') {
96
- event.preventDefault()
97
- buttons[0]?.focus()
98
- }
99
- else if (event.key === 'End') {
100
- event.preventDefault()
101
- buttons[buttons.length - 1]?.focus()
102
- }
103
- }
104
-
105
- defineExpose({ openItems, toggleItem })
106
- </script>
107
-
108
- <div x-ref="containerRef" data-stx-accordion class="{{ accordionClasses }}" role="region">
109
- @if(hasPropItems)
110
- @foreach(item in items)
111
- <div>
112
- <button
113
- type="button"
114
- data-accordion-header
115
- class="{{ headerClasses }}"
116
- @click="toggleItem({{ $loop.index }})"
117
- @keydown="onHeaderKey($event, {{ $loop.index }})"
118
- :aria-expanded="isOpen({{ $loop.index }}) ? 'true' : 'false'"
119
- aria-controls="accordion-content-{{ $loop.index }}"
120
- >
121
- <span>{{ item.title }}</span>
122
- <svg
123
- class="{{ iconClasses }}"
124
- :style="`transform: rotate(${isOpen({{ $loop.index }}) ? 180 : 0}deg)`"
125
- fill="none"
126
- viewBox="0 0 24 24"
127
- stroke="currentColor"
128
- >
129
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
130
- </svg>
131
- </button>
132
-
133
- <div
134
- :show="isOpen({{ $loop.index }})"
135
- id="accordion-content-{{ $loop.index }}"
136
- class="{{ contentClasses }}"
137
- role="region"
138
- >
139
- {!! item.content !!}
140
- </div>
141
- </div>
142
- @endforeach
143
- @else
144
- <slot />
145
- @endif
146
- </div>
@@ -1,37 +0,0 @@
1
- <script server>
2
- // Slot wrapper that the parent <Accordion> discovers and wires up on mount.
3
- // Renders its own header button + content panel; the parent attaches click
4
- // handlers and toggles the content panel's `hidden` attribute as openItems
5
- // changes. Hard-coded styling here matches the legacy prop-mode markup so
6
- // snapshots compare cleanly in both modes. See stacksjs/stx#1703.
7
- export const title = $props.title || ''
8
- export const className = $props.className || ''
9
-
10
- const headerClasses = 'w-full flex items-center justify-between px-4 py-3 text-left font-medium text-gray-900 dark:text-gray-100 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors duration-150 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900'
11
- const iconClasses = 'w-5 h-5 text-gray-500 dark:text-gray-400 transition-transform duration-200'
12
- const contentClasses = 'px-4 py-3 text-gray-700 dark:text-gray-300'
13
- </script>
14
-
15
- <div data-stx-accordion-item data-title="{{ title }}" class="{{ className }}">
16
- <button
17
- type="button"
18
- data-stx-accordion-header
19
- data-accordion-header
20
- class="{{ headerClasses }}"
21
- aria-expanded="false"
22
- >
23
- <span>{{ title }}</span>
24
- <svg
25
- data-stx-accordion-chevron
26
- class="{{ iconClasses }}"
27
- fill="none"
28
- viewBox="0 0 24 24"
29
- stroke="currentColor"
30
- >
31
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
32
- </svg>
33
- </button>
34
- <div data-stx-accordion-content class="{{ contentClasses }}" role="region" hidden>
35
- <slot />
36
- </div>
37
- </div>
@@ -1,47 +0,0 @@
1
- export { default as Accordion } from './Accordion.stx'
2
- export { default as AccordionItem } from './AccordionItem.stx'
3
-
4
- /**
5
- * Legacy prop API — pass `items` as an array of `{ title, content }` where
6
- * `content` is a HTML string. Kept for backward compatibility; prefer the
7
- * slot API below for any accordion whose content needs to be a component tree.
8
- *
9
- * See stacksjs/stx#1703.
10
- */
11
- export interface AccordionLegacyItem {
12
- title: string
13
- content: string
14
- }
15
-
16
- export type AccordionItemDefinition = AccordionLegacyItem
17
-
18
- export interface AccordionProps {
19
- /** Legacy: array of item definitions with string content. */
20
- items?: AccordionItemDefinition[]
21
- allowMultiple?: boolean
22
- defaultOpen?: number[]
23
- onChange?: (openItems: number[]) => void
24
- className?: string
25
- }
26
-
27
- /**
28
- * Slot API — wrap each section's content in `<AccordionItem title="…">`.
29
- * Each `<AccordionItem>` renders its own header + content panel; the parent
30
- * `<Accordion>` attaches click handlers and toggles the content visibility.
31
- *
32
- * @example
33
- * ```html
34
- * <Accordion allowMultiple>
35
- * <AccordionItem title="Profile">
36
- * <ProfileEditor :user="user()" />
37
- * </AccordionItem>
38
- * <AccordionItem title="Zones">
39
- * <ZonesEditor />
40
- * </AccordionItem>
41
- * </Accordion>
42
- * ```
43
- */
44
- export interface AccordionItemProps {
45
- title: string
46
- className?: string
47
- }
@@ -1,50 +0,0 @@
1
- <script server>
2
- export const src = $props.src || ''
3
- export const autoplay = $props.autoplay || false
4
- export const loop = $props.loop || false
5
- export const muted = $props.muted || false
6
- export const controls = $props.controls ?? true
7
- export const preload = $props.preload || 'metadata'
8
- export const className = $props.className || ''
9
- export const showWaveform = $props.showWaveform || false
10
- export const title = $props.title || ''
11
-
12
- export const containerClasses = `relative w-full ${className}`.trim()
13
- export const audioClasses = 'w-full rounded-md bg-gray-100 dark:bg-gray-800'
14
- </script>
15
-
16
- <div class="{{ containerClasses }}">
17
- @if(title)
18
- <div class="mb-2 px-1">
19
- <h3 class="text-sm font-medium text-gray-900 dark:text-gray-100">
20
- {{ title }}
21
- </h3>
22
- </div>
23
- @endif
24
-
25
- <audio
26
- class="{{ audioClasses }}"
27
- src="{{ src }}"
28
- @if(controls)controls@endif
29
- @if(autoplay)autoplay@endif
30
- @if(loop)loop@endif
31
- @if(muted)muted@endif
32
- preload="{{ preload }}"
33
- >
34
- Your browser does not support the audio element.
35
- </audio>
36
-
37
- @if(showWaveform)
38
- <div class="mt-2 h-16 bg-gray-100 dark:bg-gray-800 rounded-md flex items-center justify-center">
39
- <svg class="w-full h-full px-4" viewBox="0 0 100 40" preserveAspectRatio="none">
40
- <path
41
- d="M 0,20 L 10,15 L 20,10 L 30,5 L 40,15 L 50,20 L 60,25 L 70,30 L 80,20 L 90,15 L 100,20"
42
- fill="none"
43
- stroke="currentColor"
44
- stroke-width="2"
45
- class="text-indigo-500 dark:text-indigo-400"
46
- />
47
- </svg>
48
- </div>
49
- @endif
50
- </div>
@@ -1,41 +0,0 @@
1
- # Audio Component
2
-
3
- A styled audio player with optional waveform visualization.
4
-
5
- ## Installation
6
-
7
- ```bash
8
- bun add @stacksjs/components
9
- ```
10
-
11
- ## Usage
12
-
13
- ```stx
14
- <Audio
15
- src="/audio/podcast.mp3"
16
- title="Episode 42: Building with STX"
17
- controls
18
- />
19
- ```
20
-
21
- ## Props
22
-
23
- | Prop | Type | Default | Description |
24
- |------|------|---------|-------------|
25
- | `src` | `string` | - | Audio file URL (required) |
26
- | `autoplay` | `boolean` | `false` | Auto-play on load |
27
- | `loop` | `boolean` | `false` | Loop playback |
28
- | `muted` | `boolean` | `false` | Start muted |
29
- | `controls` | `boolean` | `true` | Show native controls |
30
- | `preload` | `'none' \| 'metadata' \| 'auto'` | `'metadata'` | Preload strategy |
31
- | `className` | `string` | `''` | Additional CSS classes |
32
- | `showWaveform` | `boolean` | `false` | Show waveform visualization |
33
- | `title` | `string` | `''` | Optional title above player |
34
-
35
- ## Features
36
-
37
- - **Native controls** - Uses browser's native audio controls
38
- - **Dark mode** - Styled for dark mode
39
- - **Waveform** - Optional visual waveform display
40
- - **Accessible** - Proper fallback text
41
- - **Responsive** - Full-width design
@@ -1,13 +0,0 @@
1
- export { default as Audio } from './Audio.stx'
2
-
3
- export interface AudioProps {
4
- src: string
5
- autoplay?: boolean
6
- loop?: boolean
7
- muted?: boolean
8
- controls?: boolean
9
- preload?: 'none' | 'metadata' | 'auto'
10
- className?: string
11
- showWaveform?: boolean
12
- title?: string
13
- }