@stacksjs/components 0.2.110 → 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.
- package/dist/{Sidebar-4hn7vw4n.stx → Sidebar-bg47fqmq.stx} +100 -3
- package/dist/{SidebarItem-gtrcw4ky.stx → SidebarItem-9rvbwhxr.stx} +11 -7
- package/dist/index.js +15 -12
- package/dist/ui/sidebar/themes.d.ts +26 -3
- package/package.json +4 -3
- package/src/components/CodeBlock.stx +54 -0
- package/src/components/Footer.stx +66 -0
- package/src/components/Hero.stx +25 -0
- package/src/components/Installation.stx +37 -0
- package/src/components/index.ts +4 -0
- package/src/composables/index.ts +4 -0
- package/src/composables/useCopyCode.ts +73 -0
- package/src/composables/useDarkMode.ts +100 -0
- package/src/composables/useSEO.ts +230 -0
- package/src/composables/utilities/index.ts +7 -0
- package/src/composables/utilities/useClickOutside.ts +72 -0
- package/src/composables/utilities/useFocusTrap.ts +159 -0
- package/src/composables/utilities/useKeyboard.ts +124 -0
- package/src/composables/utilities/useLocalStorage.ts +132 -0
- package/src/composables/utilities/useMediaQuery.ts +118 -0
- package/src/index.ts +99 -0
- package/src/stx.d.ts +4 -0
- package/src/ui/accordion/Accordion.stx +146 -0
- package/src/ui/accordion/AccordionItem.stx +37 -0
- package/src/ui/accordion/index.ts +47 -0
- package/src/ui/audio/Audio.stx +50 -0
- package/src/ui/audio/README.md +41 -0
- package/src/ui/audio/index.ts +13 -0
- package/src/ui/auth/Login.stx +161 -0
- package/src/ui/auth/README.md +142 -0
- package/src/ui/auth/Signup.stx +159 -0
- package/src/ui/auth/TwoFactorChallenge.stx +111 -0
- package/src/ui/auth/index.ts +32 -0
- package/src/ui/avatar/Avatar.stx +77 -0
- package/src/ui/avatar/index.ts +12 -0
- package/src/ui/badge/Badge.stx +62 -0
- package/src/ui/badge/index.ts +10 -0
- package/src/ui/breadcrumb/Breadcrumb.stx +79 -0
- package/src/ui/breadcrumb/index.ts +15 -0
- package/src/ui/button/Button.stx +74 -0
- package/src/ui/button/README.md +80 -0
- package/src/ui/button/index.ts +85 -0
- package/src/ui/calendar/Calendar.stx +189 -0
- package/src/ui/calendar/README.md +254 -0
- package/src/ui/calendar/index.ts +14 -0
- package/src/ui/card/Card.stx +68 -0
- package/src/ui/card/index.ts +13 -0
- package/src/ui/checkbox/Checkbox.stx +86 -0
- package/src/ui/checkbox/index.ts +16 -0
- package/src/ui/combobox/Combobox.stx +96 -0
- package/src/ui/combobox/ComboboxButton.stx +25 -0
- package/src/ui/combobox/ComboboxInput.stx +32 -0
- package/src/ui/combobox/ComboboxOption.stx +49 -0
- package/src/ui/combobox/ComboboxOptions.stx +16 -0
- package/src/ui/combobox/README.md +150 -0
- package/src/ui/combobox/index.ts +40 -0
- package/src/ui/command-palette/CommandPalette.stx +81 -0
- package/src/ui/command-palette/CommandPaletteItem.stx +27 -0
- package/src/ui/command-palette/README.md +42 -0
- package/src/ui/command-palette/index.ts +15 -0
- package/src/ui/dialog/Dialog.stx +41 -0
- package/src/ui/dialog/DialogBackdrop.stx +9 -0
- package/src/ui/dialog/DialogDescription.stx +11 -0
- package/src/ui/dialog/DialogPanel.stx +16 -0
- package/src/ui/dialog/DialogTitle.stx +11 -0
- package/src/ui/dialog/README.md +121 -0
- package/src/ui/dialog/index.ts +32 -0
- package/src/ui/drawer/Drawer.stx +120 -0
- package/src/ui/drawer/README.md +90 -0
- package/src/ui/drawer/index.ts +9 -0
- package/src/ui/dropdown/Dropdown.stx +82 -0
- package/src/ui/dropdown/DropdownButton.stx +29 -0
- package/src/ui/dropdown/DropdownItem.stx +35 -0
- package/src/ui/dropdown/DropdownItems.stx +18 -0
- package/src/ui/dropdown/README.md +114 -0
- package/src/ui/dropdown/index.ts +29 -0
- package/src/ui/form/Form.stx +95 -0
- package/src/ui/form/index.ts +67 -0
- package/src/ui/heatmap/Heatmap.stx +294 -0
- package/src/ui/image/Image.stx +165 -0
- package/src/ui/image/README.md +164 -0
- package/src/ui/image/index.ts +22 -0
- package/src/ui/input/EmailInput.stx +30 -0
- package/src/ui/input/NumberInput.stx +126 -0
- package/src/ui/input/PasswordInput.stx +143 -0
- package/src/ui/input/SearchInput.stx +44 -0
- package/src/ui/input/TextInput.stx +132 -0
- package/src/ui/input/index.ts +180 -0
- package/src/ui/listbox/Listbox.stx +101 -0
- package/src/ui/listbox/ListboxButton.stx +28 -0
- package/src/ui/listbox/ListboxLabel.stx +11 -0
- package/src/ui/listbox/ListboxOption.stx +49 -0
- package/src/ui/listbox/ListboxOptions.stx +16 -0
- package/src/ui/listbox/README.md +135 -0
- package/src/ui/listbox/index.ts +39 -0
- package/src/ui/navigator/Navigator.stx +113 -0
- package/src/ui/navigator/README.md +352 -0
- package/src/ui/navigator/index.ts +20 -0
- package/src/ui/notification/Notification.stx +117 -0
- package/src/ui/notification/README.md +114 -0
- package/src/ui/notification/index.ts +12 -0
- package/src/ui/pagination/Pagination.stx +150 -0
- package/src/ui/pagination/index.ts +11 -0
- package/src/ui/payment/Checkout.stx +31 -0
- package/src/ui/payment/DefaultPaymentMethod.stx +62 -0
- package/src/ui/payment/PaymentMethods.stx +104 -0
- package/src/ui/payment/README.md +345 -0
- package/src/ui/payment/SubscriptionCheckout.stx +323 -0
- package/src/ui/payment/index.ts +59 -0
- package/src/ui/popover/Popover.stx +60 -0
- package/src/ui/popover/PopoverButton.stx +29 -0
- package/src/ui/popover/PopoverPanel.stx +23 -0
- package/src/ui/popover/README.md +91 -0
- package/src/ui/popover/index.ts +22 -0
- package/src/ui/portal/Portal.stx +57 -0
- package/src/ui/portal/index.ts +6 -0
- package/src/ui/progress/Progress.stx +131 -0
- package/src/ui/progress/index.ts +12 -0
- package/src/ui/radio/Radio.stx +77 -0
- package/src/ui/radio/index.ts +15 -0
- package/src/ui/radio-group/README.md +139 -0
- package/src/ui/radio-group/RadioGroup.stx +60 -0
- package/src/ui/radio-group/RadioGroupDescription.stx +11 -0
- package/src/ui/radio-group/RadioGroupLabel.stx +11 -0
- package/src/ui/radio-group/RadioGroupOption.stx +58 -0
- package/src/ui/radio-group/index.ts +30 -0
- package/src/ui/select/README.md +180 -0
- package/src/ui/select/Select.stx +82 -0
- package/src/ui/select/index.ts +21 -0
- package/src/ui/sidebar/Sidebar.stx +573 -0
- package/src/ui/sidebar/SidebarFooter.stx +71 -0
- package/src/ui/sidebar/SidebarHeader.stx +103 -0
- package/src/ui/sidebar/SidebarItem.stx +90 -0
- package/src/ui/sidebar/SidebarSection.stx +83 -0
- package/src/ui/sidebar/index.ts +146 -0
- package/src/ui/sidebar/themes.ts +288 -0
- package/src/ui/skeleton/Skeleton.stx +48 -0
- package/src/ui/skeleton/index.ts +11 -0
- package/src/ui/spinner/Spinner.stx +109 -0
- package/src/ui/spinner/index.ts +9 -0
- package/src/ui/stepper/README.md +36 -0
- package/src/ui/stepper/Stepper.stx +17 -0
- package/src/ui/stepper/StepperStep.stx +57 -0
- package/src/ui/stepper/index.ts +19 -0
- package/src/ui/storage/README.md +193 -0
- package/src/ui/storage/index.ts +347 -0
- package/src/ui/switch/README.md +114 -0
- package/src/ui/switch/Switch.stx +72 -0
- package/src/ui/switch/index.ts +10 -0
- package/src/ui/table/README.md +39 -0
- package/src/ui/table/Table.stx +14 -0
- package/src/ui/table/TableBody.stx +10 -0
- package/src/ui/table/TableCell.stx +10 -0
- package/src/ui/table/TableHead.stx +10 -0
- package/src/ui/table/TableHeader.stx +20 -0
- package/src/ui/table/TableRow.stx +11 -0
- package/src/ui/table/index.ts +35 -0
- package/src/ui/tabs/TabPanel.stx +25 -0
- package/src/ui/tabs/Tabs.stx +202 -0
- package/src/ui/tabs/index.ts +50 -0
- package/src/ui/teleport/Teleport.stx +65 -0
- package/src/ui/teleport/index.ts +7 -0
- package/src/ui/textarea/Textarea.stx +108 -0
- package/src/ui/textarea/index.ts +20 -0
- package/src/ui/tooltip/Tooltip.stx +71 -0
- package/src/ui/tooltip/index.ts +10 -0
- package/src/ui/transition/README.md +30 -0
- package/src/ui/transition/Transition.stx +102 -0
- package/src/ui/transition/index.ts +25 -0
- package/src/ui/video/README.md +47 -0
- package/src/ui/video/Video.stx +45 -0
- package/src/ui/video/index.ts +16 -0
- package/src/ui/virtual-list/VirtualList.stx +67 -0
- package/src/ui/virtual-list/index.ts +10 -0
- package/src/ui/virtual-table/VirtualTable.stx +103 -0
- package/src/ui/virtual-table/index.ts +21 -0
- package/src/utils/PROP_VALIDATION.md +458 -0
- package/src/utils/THEME_SYSTEM.md +702 -0
- package/src/utils/accessibility.ts +841 -0
- package/src/utils/animation/easing.ts +368 -0
- package/src/utils/animation/helpers.ts +223 -0
- package/src/utils/animation/index.ts +5 -0
- package/src/utils/animation/keyframes.ts +230 -0
- package/src/utils/error-handling.ts +475 -0
- package/src/utils/highlighter.ts +93 -0
- package/src/utils/i18n.ts +634 -0
- package/src/utils/keep-alive.ts +819 -0
- package/src/utils/lazy-loading.ts +530 -0
- package/src/utils/prop-validation.ts +478 -0
- 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,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,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
|
+
}
|