@stacksjs/components 0.2.111 → 0.2.112

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-bg47fqmq.stx} +100 -3
  2. package/dist/{SidebarItem-gtrcw4ky.stx → SidebarItem-9rvbwhxr.stx} +11 -7
  3. package/dist/index.js +15 -12
  4. package/dist/ui/sidebar/themes.d.ts +26 -3
  5. package/package.json +4 -3
  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 +573 -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 +288 -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,108 @@
1
+ <script server>
2
+ export const value = $props.value || ''
3
+ export const placeholder = $props.placeholder || ''
4
+ export const disabled = $props.disabled || false
5
+ export const readonly = $props.readonly || false
6
+ export const rows = $props.rows || 4
7
+ export const autoResize = $props.autoResize || false
8
+ export const maxRows = $props.maxRows || 10
9
+ export const error = $props.error || false
10
+ export const label = $props.label || ''
11
+ export const helperText = $props.helperText || ''
12
+ export const required = $props.required || false
13
+ export const maxLength = $props.maxLength
14
+ export const showCount = $props.showCount || false
15
+ export const resize = $props.resize || 'vertical'
16
+ export const className = $props.className || ''
17
+
18
+ const resizeClasses = { none: 'resize-none', vertical: 'resize-y', horizontal: 'resize-x', both: 'resize' }
19
+ const baseClasses = 'block w-full border-0 rounded-md shadow-sm ring-1 ring-inset transition-colors duration-200 focus:ring-2 focus:ring-inset px-4 py-2 text-base dark:bg-blue-gray-700 dark:text-gray-100'
20
+ const stateClasses = error
21
+ ? 'ring-red-300 dark:ring-red-600 text-red-900 dark:text-red-300 placeholder-red-300 dark:placeholder-red-500 focus:ring-red-500 dark:focus:ring-red-400'
22
+ : 'ring-gray-300 dark:ring-gray-600 text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500 focus:ring-indigo-600 dark:focus:ring-indigo-400'
23
+ const disabledClasses = disabled
24
+ ? 'opacity-50 cursor-not-allowed bg-gray-50 dark:bg-gray-800'
25
+ : readonly
26
+ ? 'bg-gray-50 dark:bg-gray-800 cursor-default'
27
+ : 'bg-white dark:bg-blue-gray-700'
28
+
29
+ export const textareaClasses = `${baseClasses} ${stateClasses} ${disabledClasses} ${resizeClasses[resize]} ${className}`.trim()
30
+ export const labelClasses = `block text-sm font-medium leading-6 mb-2 ${error ? 'text-red-600 dark:text-red-400' : 'text-gray-900 dark:text-gray-100'}`.trim()
31
+ export const helperClasses = `mt-2 text-sm ${error ? 'text-red-600 dark:text-red-400' : 'text-gray-500 dark:text-gray-400'}`.trim()
32
+ </script>
33
+
34
+ <script client>
35
+ const emit = defineEmits()
36
+ const autoResize = {{ autoResize }}
37
+ const maxRows = {{ maxRows }}
38
+ // Reactive prop binding — parent can drive `value` via signals. See #1704.
39
+ const inputValue = useReactiveProp('value', {{ value }}, { parse: (v) => v == null ? '' : String(v) })
40
+
41
+ function resizeTextarea(el) {
42
+ if (!autoResize) return
43
+ el.style.height = 'auto'
44
+ const scrollHeight = el.scrollHeight
45
+ const lineHeight = Number.parseInt(window.getComputedStyle(el).lineHeight) || 20
46
+ const maxHeight = lineHeight * maxRows
47
+ el.style.height = `${Math.min(scrollHeight, maxHeight)}px`
48
+ el.style.overflowY = scrollHeight > maxHeight ? 'auto' : 'hidden'
49
+ }
50
+
51
+ function onInput(event) {
52
+ inputValue.set(event.target.value)
53
+ resizeTextarea(event.target)
54
+ emit('input', inputValue())
55
+ }
56
+
57
+ onMount(() => {
58
+ if (autoResize) {
59
+ const el = document.getElementById('textarea-input')
60
+ if (el) resizeTextarea(el)
61
+ }
62
+ })
63
+
64
+ const charCount = derived(() => `${inputValue().length} / {{ maxLength || 0 }}`)
65
+
66
+ defineExpose({ value: inputValue })
67
+ </script>
68
+
69
+ <div class="w-full">
70
+ @if(label)
71
+ <label for="textarea-input" class="{{ labelClasses }}">
72
+ {{ label }}
73
+ @if(required)
74
+ <span class="text-red-500 ml-1">*</span>
75
+ @endif
76
+ </label>
77
+ @endif
78
+
79
+ <textarea
80
+ id="textarea-input"
81
+ class="{{ textareaClasses }}"
82
+ rows="{{ rows }}"
83
+ placeholder="{{ placeholder }}"
84
+ @if(disabled)disabled@endif
85
+ @if(readonly)readonly@endif
86
+ @if(required)required@endif
87
+ @if(maxLength)maxlength="{{ maxLength }}"@endif
88
+ @input="onInput($event)"
89
+ :aria-invalid="{{ error ? 'true' : 'false' }}"
90
+ @if(helperText)aria-describedby="helper-text"@endif
91
+ >{{ value }}</textarea>
92
+
93
+ @if(helperText || (showCount && maxLength))
94
+ <div class="flex items-center justify-between mt-2">
95
+ @if(helperText)
96
+ <p id="helper-text" class="{{ helperClasses }}">
97
+ {{ helperText }}
98
+ </p>
99
+ @else
100
+ <span></span>
101
+ @endif
102
+
103
+ @if(showCount && maxLength)
104
+ <p class="text-sm text-gray-500 dark:text-gray-400" x-text="charCount()"></p>
105
+ @endif
106
+ </div>
107
+ @endif
108
+ </div>
@@ -0,0 +1,20 @@
1
+ export { default as Textarea } from './Textarea.stx'
2
+
3
+ export interface TextareaProps {
4
+ value?: string
5
+ onChange?: (value: string) => void
6
+ placeholder?: string
7
+ disabled?: boolean
8
+ readonly?: boolean
9
+ rows?: number
10
+ autoResize?: boolean
11
+ maxRows?: number
12
+ error?: boolean
13
+ label?: string
14
+ helperText?: string
15
+ required?: boolean
16
+ maxLength?: number
17
+ showCount?: boolean
18
+ resize?: 'none' | 'vertical' | 'horizontal' | 'both'
19
+ className?: string
20
+ }
@@ -0,0 +1,71 @@
1
+ <script server>
2
+ export const content = $props.content || ''
3
+ export const position = $props.position || 'top'
4
+ export const delay = $props.delay || 0
5
+ export const className = $props.className || ''
6
+ export const show = $props.show || false
7
+ export const disabled = $props.disabled || false
8
+
9
+ const positionClasses = {
10
+ top: 'bottom-full left-1/2 -translate-x-1/2 mb-2',
11
+ bottom: 'top-full left-1/2 -translate-x-1/2 mt-2',
12
+ left: 'right-full top-1/2 -translate-y-1/2 mr-2',
13
+ right: 'left-full top-1/2 -translate-y-1/2 ml-2',
14
+ }
15
+
16
+ const arrowClasses = {
17
+ top: 'top-full left-1/2 -translate-x-1/2 border-t-gray-900 dark:border-t-gray-700',
18
+ bottom: 'bottom-full left-1/2 -translate-x-1/2 border-b-gray-900 dark:border-b-gray-700',
19
+ left: 'left-full top-1/2 -translate-y-1/2 border-l-gray-900 dark:border-l-gray-700',
20
+ right: 'right-full top-1/2 -translate-y-1/2 border-r-gray-900 dark:border-r-gray-700',
21
+ }
22
+
23
+ export const tooltipClasses = `absolute z-50 px-3 py-2 text-sm font-medium text-white bg-gray-900 dark:bg-gray-700 rounded-lg shadow-lg whitespace-nowrap pointer-events-none transition-opacity duration-200 ${positionClasses[position]} ${className}`.trim()
24
+ export const arrowClass = `absolute w-0 h-0 border-4 border-transparent ${arrowClasses[position]}`.trim()
25
+ </script>
26
+
27
+ <script client>
28
+ const delay = {{ delay }}
29
+ const isDisabled = {{ disabled }}
30
+ // Reactive prop binding — parent can drive `show` via signals. See #1704.
31
+ const isVisible = useReactiveProp('show', {{ show }})
32
+ let timeoutId = null
33
+
34
+ function showTooltip() {
35
+ if (isDisabled) return
36
+ if (delay > 0) {
37
+ timeoutId = setTimeout(() => isVisible.set(true), delay)
38
+ }
39
+ else {
40
+ isVisible.set(true)
41
+ }
42
+ }
43
+
44
+ function hideTooltip() {
45
+ if (timeoutId) {
46
+ clearTimeout(timeoutId)
47
+ timeoutId = null
48
+ }
49
+ isVisible.set(false)
50
+ }
51
+ </script>
52
+
53
+ <div class="relative inline-block {{ disabled ? 'cursor-not-allowed' : '' }}">
54
+ <div
55
+ @mouseenter="showTooltip()"
56
+ @mouseleave="hideTooltip()"
57
+ @focusin="showTooltip()"
58
+ @focusout="hideTooltip()"
59
+ >
60
+ <slot />
61
+ </div>
62
+
63
+ <div
64
+ :show="isVisible() && !{{ disabled }}"
65
+ class="{{ tooltipClasses }}"
66
+ role="tooltip"
67
+ >
68
+ {{ content }}
69
+ <div class="{{ arrowClass }}"></div>
70
+ </div>
71
+ </div>
@@ -0,0 +1,10 @@
1
+ export { default as Tooltip } from './Tooltip.stx'
2
+
3
+ export interface TooltipProps {
4
+ content: string
5
+ position?: 'top' | 'bottom' | 'left' | 'right'
6
+ delay?: number
7
+ show?: boolean
8
+ disabled?: boolean
9
+ className?: string
10
+ }
@@ -0,0 +1,30 @@
1
+ # Transition Component
2
+
3
+ Smooth enter/leave transitions for showing and hiding content.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ bun add @stacksjs/components
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```stx
14
+ <script server>
15
+ let isOpen = true
16
+ </script>
17
+
18
+ <Transition :show="isOpen">
19
+ <div class="p-4 bg-white rounded-lg shadow-lg">
20
+ Content with fade transition
21
+ </div>
22
+ </Transition>
23
+ ```
24
+
25
+ ## Features
26
+
27
+ - Enter/leave animations
28
+ - Customizable transition classes
29
+ - Dark mode support
30
+ - Headwind utility classes
@@ -0,0 +1,102 @@
1
+ <script server>
2
+ export const show = $props.show !== false
3
+ export const enter = $props.enter || 'transition-opacity duration-300'
4
+ export const enterFrom = $props.enterFrom || 'opacity-0'
5
+ export const enterTo = $props.enterTo || 'opacity-100'
6
+ export const leave = $props.leave || 'transition-opacity duration-200'
7
+ export const leaveFrom = $props.leaveFrom || 'opacity-100'
8
+ export const leaveTo = $props.leaveTo || 'opacity-0'
9
+ export const appear = $props.appear || false
10
+ export const duration = $props.duration || 300
11
+ export const delay = $props.delay || 0
12
+ export const preset = $props.preset || ''
13
+ export const className = $props.className || ''
14
+
15
+ const presets = {
16
+ fade: { enter: 'transition-opacity', enterFrom: 'opacity-0', enterTo: 'opacity-100', leave: 'transition-opacity', leaveFrom: 'opacity-100', leaveTo: 'opacity-0' },
17
+ slide: { enter: 'transition-transform', enterFrom: 'translate-y-2', enterTo: 'translate-y-0', leave: 'transition-transform', leaveFrom: 'translate-y-0', leaveTo: 'translate-y-2' },
18
+ slideLeft: { enter: 'transition-transform', enterFrom: 'translate-x-full', enterTo: 'translate-x-0', leave: 'transition-transform', leaveFrom: 'translate-x-0', leaveTo: '-translate-x-full' },
19
+ slideRight: { enter: 'transition-transform', enterFrom: '-translate-x-full', enterTo: 'translate-x-0', leave: 'transition-transform', leaveFrom: 'translate-x-0', leaveTo: 'translate-x-full' },
20
+ scale: { enter: 'transition-transform', enterFrom: 'scale-95', enterTo: 'scale-100', leave: 'transition-transform', leaveFrom: 'scale-100', leaveTo: 'scale-95' },
21
+ rotate: { enter: 'transition-transform', enterFrom: 'rotate-12 scale-95', enterTo: 'rotate-0 scale-100', leave: 'transition-transform', leaveFrom: 'rotate-0 scale-100', leaveTo: '-rotate-12 scale-95' },
22
+ zoom: { enter: 'transition-all', enterFrom: 'scale-0 opacity-0', enterTo: 'scale-100 opacity-100', leave: 'transition-all', leaveFrom: 'scale-100 opacity-100', leaveTo: 'scale-0 opacity-0' },
23
+ }
24
+
25
+ const p = preset && presets[preset] ? presets[preset] : {}
26
+ export const enterClass = p.enter || enter
27
+ export const enterFromClass = p.enterFrom || enterFrom
28
+ export const enterToClass = p.enterTo || enterTo
29
+ export const leaveClass = p.leave || leave
30
+ export const leaveFromClass = p.leaveFrom || leaveFrom
31
+ export const leaveToClass = p.leaveTo || leaveTo
32
+
33
+ export const durationStyle = `transition-duration: ${duration}ms; transition-delay: ${delay}ms;`
34
+ export const extraClass = className
35
+ </script>
36
+
37
+ <script client>
38
+ const emit = defineEmits()
39
+ const showProp = {{ show }}
40
+ const appear = {{ appear }}
41
+ const duration = {{ duration }}
42
+ const delay = {{ delay }}
43
+ const enterClass = {{ enterClass }}
44
+ const enterFromClass = {{ enterFromClass }}
45
+ const enterToClass = {{ enterToClass }}
46
+ const leaveClass = {{ leaveClass }}
47
+ const leaveFromClass = {{ leaveFromClass }}
48
+ const leaveToClass = {{ leaveToClass }}
49
+ const extraClass = {{ extraClass }}
50
+
51
+ const visible = state(showProp)
52
+ const isEntering = state(appear && showProp)
53
+ const isLeaving = state(false)
54
+ const shouldRender = state(showProp)
55
+
56
+ const transitionClass = derived(() => {
57
+ if (visible()) {
58
+ return isEntering() ? `${enterClass} ${enterFromClass}` : enterToClass
59
+ }
60
+ return isLeaving() ? `${leaveClass} ${leaveFromClass}` : leaveToClass
61
+ })
62
+
63
+ const componentClass = derived(() => `${transitionClass()} ${extraClass}`.trim())
64
+
65
+ function setShow(next) {
66
+ if (next === visible()) return
67
+ if (next) {
68
+ emit('beforeEnter')
69
+ shouldRender.set(true)
70
+ visible.set(true)
71
+ isEntering.set(true)
72
+ setTimeout(() => emit('enter'), delay)
73
+ setTimeout(() => {
74
+ isEntering.set(false)
75
+ emit('afterEnter')
76
+ }, duration + delay)
77
+ }
78
+ else {
79
+ emit('beforeLeave')
80
+ visible.set(false)
81
+ isLeaving.set(true)
82
+ setTimeout(() => emit('leave'), delay)
83
+ setTimeout(() => {
84
+ isLeaving.set(false)
85
+ shouldRender.set(false)
86
+ emit('afterLeave')
87
+ }, duration + delay)
88
+ }
89
+ }
90
+
91
+ onMount(() => {
92
+ if (appear && showProp) {
93
+ setTimeout(() => isEntering.set(false), duration + delay)
94
+ }
95
+ })
96
+
97
+ defineExpose({ visible, setShow })
98
+ </script>
99
+
100
+ <div :show="shouldRender()" :class="componentClass()" style="{{ durationStyle }}">
101
+ <slot />
102
+ </div>
@@ -0,0 +1,25 @@
1
+ export { default as Transition } from './Transition.stx'
2
+
3
+ export interface TransitionProps {
4
+ show?: boolean
5
+ enter?: string
6
+ enterFrom?: string
7
+ enterTo?: string
8
+ leave?: string
9
+ leaveFrom?: string
10
+ leaveTo?: string
11
+ appear?: boolean
12
+ appearFrom?: string
13
+ appearTo?: string
14
+ mode?: 'in-out' | 'out-in' | 'default'
15
+ duration?: number
16
+ delay?: number
17
+ preset?: 'fade' | 'slide' | 'slideLeft' | 'slideRight' | 'scale' | 'rotate' | 'zoom' | ''
18
+ onBeforeEnter?: () => void
19
+ onEnter?: () => void
20
+ onAfterEnter?: () => void
21
+ onBeforeLeave?: () => void
22
+ onLeave?: () => void
23
+ onAfterLeave?: () => void
24
+ className?: string
25
+ }
@@ -0,0 +1,47 @@
1
+ # Video Component
2
+
3
+ A responsive video player with aspect ratio support and poster images.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ bun add @stacksjs/components
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```stx
14
+ <Video
15
+ src="/videos/demo.mp4"
16
+ poster="/images/video-thumbnail.jpg"
17
+ title="Product Demo"
18
+ aspectRatio="16/9"
19
+ controls
20
+ />
21
+ ```
22
+
23
+ ## Props
24
+
25
+ | Prop | Type | Default | Description |
26
+ |------|------|---------|-------------|
27
+ | `src` | `string` | - | Video file URL (required) |
28
+ | `poster` | `string` | `''` | Poster image URL |
29
+ | `autoplay` | `boolean` | `false` | Auto-play on load |
30
+ | `loop` | `boolean` | `false` | Loop playback |
31
+ | `muted` | `boolean` | `false` | Start muted |
32
+ | `controls` | `boolean` | `true` | Show native controls |
33
+ | `preload` | `'none' \| 'metadata' \| 'auto'` | `'metadata'` | Preload strategy |
34
+ | `width` | `number \| string` | - | Video width |
35
+ | `height` | `number \| string` | - | Video height |
36
+ | `aspectRatio` | `'16/9' \| '4/3' \| '1/1' \| string` | `'16/9'` | Aspect ratio |
37
+ | `className` | `string` | `''` | Additional CSS classes |
38
+ | `title` | `string` | `''` | Optional title above player |
39
+
40
+ ## Features
41
+
42
+ - **Aspect ratios** - 16:9, 4:3, 1:1 built-in
43
+ - **Poster images** - Show thumbnail before play
44
+ - **Native controls** - Uses browser's native video controls
45
+ - **Dark mode** - Styled for dark mode
46
+ - **Responsive** - Full-width with aspect ratio
47
+ - **Accessible** - Proper fallback text
@@ -0,0 +1,45 @@
1
+ <script server>
2
+ export const src = $props.src || ''
3
+ export const poster = $props.poster || ''
4
+ export const autoplay = $props.autoplay || false
5
+ export const loop = $props.loop || false
6
+ export const muted = $props.muted || false
7
+ export const controls = $props.controls ?? true
8
+ export const preload = $props.preload || 'metadata'
9
+ export const width = $props.width
10
+ export const height = $props.height
11
+ export const aspectRatio = $props.aspectRatio || '16/9'
12
+ export const className = $props.className || ''
13
+ export const title = $props.title || ''
14
+
15
+ export const containerClasses = `relative w-full overflow-hidden rounded-lg ${className}`.trim()
16
+ export const videoClasses = 'w-full h-full object-cover bg-gray-900 dark:bg-gray-950'
17
+ export const aspectRatioClass = aspectRatio === '16/9' ? 'aspect-video' : aspectRatio === '4/3' ? 'aspect-[4/3]' : aspectRatio === '1/1' ? 'aspect-square' : ''
18
+ </script>
19
+
20
+ <div class="{{ containerClasses }}">
21
+ @if(title)
22
+ <div class="mb-2 px-1">
23
+ <h3 class="text-sm font-medium text-gray-900 dark:text-gray-100">
24
+ {{ title }}
25
+ </h3>
26
+ </div>
27
+ @endif
28
+
29
+ <div class="{{ aspectRatioClass }}">
30
+ <video
31
+ class="{{ videoClasses }}"
32
+ src="{{ src }}"
33
+ poster="{{ poster }}"
34
+ @if(controls)controls@endif
35
+ @if(autoplay)autoplay@endif
36
+ @if(loop)loop@endif
37
+ @if(muted)muted@endif
38
+ preload="{{ preload }}"
39
+ width="{{ width }}"
40
+ height="{{ height }}"
41
+ >
42
+ Your browser does not support the video element.
43
+ </video>
44
+ </div>
45
+ </div>
@@ -0,0 +1,16 @@
1
+ export { default as Video } from './Video.stx'
2
+
3
+ export interface VideoProps {
4
+ src: string
5
+ poster?: string
6
+ autoplay?: boolean
7
+ loop?: boolean
8
+ muted?: boolean
9
+ controls?: boolean
10
+ preload?: 'none' | 'metadata' | 'auto'
11
+ width?: number | string
12
+ height?: number | string
13
+ aspectRatio?: '16/9' | '4/3' | '1/1' | string
14
+ className?: string
15
+ title?: string
16
+ }
@@ -0,0 +1,67 @@
1
+ <script server>
2
+ export const items = $props.items || []
3
+ export const itemHeight = $props.itemHeight || 50
4
+ export const height = $props.height || 400
5
+ export const overscan = $props.overscan || 3
6
+ export const className = $props.className || ''
7
+
8
+ export const totalHeight = items.length * itemHeight
9
+ export const containerClasses = `overflow-auto ${className}`.trim()
10
+ </script>
11
+
12
+ <script client>
13
+ const items = {{ items }}
14
+ const itemHeight = {{ itemHeight }}
15
+ const height = {{ height }}
16
+ const overscan = {{ overscan }}
17
+
18
+ const scrollTop = state(0)
19
+
20
+ const visibleRange = derived(() => {
21
+ const top = scrollTop()
22
+ const startIndex = Math.max(0, Math.floor(top / itemHeight) - overscan)
23
+ const endIndex = Math.min(items.length - 1, Math.floor((top + height) / itemHeight) + overscan)
24
+ return { startIndex, endIndex }
25
+ })
26
+
27
+ const visibleItems = derived(() => {
28
+ const { startIndex, endIndex } = visibleRange()
29
+ return items.slice(startIndex, endIndex + 1).map((item, i) => ({
30
+ item,
31
+ index: startIndex + i,
32
+ top: (startIndex + i) * itemHeight,
33
+ }))
34
+ })
35
+
36
+ let ticking = false
37
+ function onScroll(event) {
38
+ const target = event.target
39
+ if (!ticking) {
40
+ requestAnimationFrame(() => {
41
+ scrollTop.set(target.scrollTop)
42
+ ticking = false
43
+ })
44
+ ticking = true
45
+ }
46
+ }
47
+
48
+ defineExpose({ scrollTop, scrollTo: (top) => scrollTop.set(top) })
49
+ </script>
50
+
51
+ <div
52
+ class="{{ containerClasses }}"
53
+ style="height: {{ height }}px"
54
+ @scroll="onScroll($event)"
55
+ >
56
+ <div class="relative" style="height: {{ totalHeight }}px">
57
+ <div
58
+ :for="entry in visibleItems()"
59
+ :key="entry.index"
60
+ class="absolute left-0 right-0"
61
+ :style="`top: ${entry.top}px; height: {{ itemHeight }}px`"
62
+ :data-index="entry.index"
63
+ >
64
+ <slot :item="entry.item" :index="entry.index" />
65
+ </div>
66
+ </div>
67
+ </div>
@@ -0,0 +1,10 @@
1
+ export { default as VirtualList } from './VirtualList.stx'
2
+
3
+ export interface VirtualListProps<T = any> {
4
+ items: T[]
5
+ itemHeight: number
6
+ height: number
7
+ overscan?: number
8
+ renderItem?: (item: T, index: number) => string
9
+ className?: string
10
+ }
@@ -0,0 +1,103 @@
1
+ <script server>
2
+ export const columns = $props.columns || []
3
+ export const data = $props.data || []
4
+ export const rowHeight = $props.rowHeight || 48
5
+ export const height = $props.height || 600
6
+ export const headerHeight = $props.headerHeight || 56
7
+ export const overscan = $props.overscan || 5
8
+ export const striped = $props.striped || false
9
+ export const hoverable = $props.hoverable ?? true
10
+ export const className = $props.className || ''
11
+
12
+ export const totalHeight = data.length * rowHeight
13
+ export const containerClasses = `border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden ${className}`.trim()
14
+ export const headerClasses = `bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 flex`.trim()
15
+ export const headerCellClasses = `px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider`.trim()
16
+ export const bodyClasses = `overflow-auto bg-white dark:bg-gray-900`.trim()
17
+ export const rowClasses = `absolute left-0 right-0 flex border-b border-gray-200 dark:border-gray-700 ${hoverable ? 'hover:bg-gray-50 dark:hover:bg-gray-800' : ''}`.trim()
18
+ export const cellClasses = `px-6 py-4 text-sm text-gray-900 dark:text-gray-100`.trim()
19
+ </script>
20
+
21
+ <script client>
22
+ const data = {{ data }}
23
+ const columns = {{ columns }}
24
+ const rowHeight = {{ rowHeight }}
25
+ const height = {{ height }}
26
+ const headerHeight = {{ headerHeight }}
27
+ const overscan = {{ overscan }}
28
+ const striped = {{ striped }}
29
+
30
+ const scrollTop = state(0)
31
+
32
+ const visibleRows = derived(() => {
33
+ const top = scrollTop()
34
+ const startIndex = Math.max(0, Math.floor(top / rowHeight) - overscan)
35
+ const endIndex = Math.min(data.length - 1, Math.floor((top + height - headerHeight) / rowHeight) + overscan)
36
+ const out = []
37
+ for (let i = startIndex; i <= endIndex; i++) {
38
+ out.push({
39
+ row: data[i],
40
+ index: i,
41
+ top: i * rowHeight,
42
+ striped: striped && (i % 2 === 1),
43
+ })
44
+ }
45
+ return out
46
+ })
47
+
48
+ function getCellValue(row, column) {
49
+ if (column.render) return column.render(row[column.key], row)
50
+ return row[column.key]
51
+ }
52
+
53
+ let ticking = false
54
+ function onScroll(event) {
55
+ const target = event.target
56
+ if (!ticking) {
57
+ requestAnimationFrame(() => {
58
+ scrollTop.set(target.scrollTop)
59
+ ticking = false
60
+ })
61
+ ticking = true
62
+ }
63
+ }
64
+
65
+ defineExpose({ scrollTop })
66
+ </script>
67
+
68
+ <div class="{{ containerClasses }}">
69
+ <div class="{{ headerClasses }}" style="height: {{ headerHeight }}px">
70
+ @foreach(column in columns)
71
+ <div
72
+ class="{{ headerCellClasses }}"
73
+ style="flex: {{ column.width || 1 }}; min-width: {{ column.minWidth || 'auto' }}"
74
+ >
75
+ {{ column.label || column.key }}
76
+ </div>
77
+ @endforeach
78
+ </div>
79
+
80
+ <div
81
+ class="{{ bodyClasses }}"
82
+ style="height: {{ height - headerHeight }}px"
83
+ @scroll="onScroll($event)"
84
+ >
85
+ <div class="relative" style="height: {{ totalHeight }}px">
86
+ <div
87
+ :for="entry in visibleRows()"
88
+ :key="entry.index"
89
+ :class="entry.striped ? '{{ rowClasses }} bg-gray-50 dark:bg-gray-800' : '{{ rowClasses }}'"
90
+ :style="`top: ${entry.top}px; height: {{ rowHeight }}px`"
91
+ :data-index="entry.index"
92
+ >
93
+ <div
94
+ :for="column in columns"
95
+ :key="column.key"
96
+ class="{{ cellClasses }}"
97
+ :style="`flex: ${column.width || 1}; min-width: ${column.minWidth || 'auto'}`"
98
+ x-html="getCellValue(entry.row, column)"
99
+ ></div>
100
+ </div>
101
+ </div>
102
+ </div>
103
+ </div>
@@ -0,0 +1,21 @@
1
+ export { default as VirtualTable } from './VirtualTable.stx'
2
+
3
+ export interface VirtualTableColumn<T = any> {
4
+ key: string
5
+ label?: string
6
+ width?: number
7
+ minWidth?: string
8
+ render?: (value: any, row: T) => string
9
+ }
10
+
11
+ export interface VirtualTableProps<T = any> {
12
+ columns: VirtualTableColumn<T>[]
13
+ data: T[]
14
+ rowHeight?: number
15
+ height?: number
16
+ headerHeight?: number
17
+ overscan?: number
18
+ striped?: boolean
19
+ hoverable?: boolean
20
+ className?: string
21
+ }