@stacksjs/components 0.2.3
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/README.md +477 -0
- package/dist/Accordion-x2gjedap.stx +108 -0
- package/dist/Audio-0fk6p9tn.stx +50 -0
- package/dist/Avatar-5m0v0s4x.stx +88 -0
- package/dist/Badge-ynes82vv.stx +70 -0
- package/dist/Breadcrumb-h9n427sk.stx +79 -0
- package/dist/Button-7xtrzn14.stx +89 -0
- package/dist/Calendar-egvx1pma.stx +232 -0
- package/dist/Card-r9w7vy1a.stx +74 -0
- package/dist/Checkbox-m8v46pmx.stx +92 -0
- package/dist/Checkout-vcnnr176.stx +39 -0
- package/dist/CodeBlock-mwex82cm.stx +57 -0
- package/dist/Combobox-npb08vkp.stx +13 -0
- package/dist/ComboboxButton-vcdyq4zw.stx +13 -0
- package/dist/ComboboxInput-c9d2p02m.stx +25 -0
- package/dist/ComboboxOption-7d0h9ash.stx +52 -0
- package/dist/ComboboxOptions-4dtqc0qa.stx +12 -0
- package/dist/CommandPalette-ah00bf8v.stx +39 -0
- package/dist/CommandPaletteItem-2yx0gvtj.stx +32 -0
- package/dist/DefaultPaymentMethod-emw5fg8s.stx +62 -0
- package/dist/Dialog-1w4rz7x0.stx +19 -0
- package/dist/DialogBackdrop-qbm3y5ab.stx +9 -0
- package/dist/DialogDescription-01w5j74b.stx +11 -0
- package/dist/DialogPanel-brsszrrs.stx +16 -0
- package/dist/DialogTitle-3rxhwg21.stx +11 -0
- package/dist/Drawer-yfaz8q7f.stx +137 -0
- package/dist/Dropdown-ng7nx556.stx +24 -0
- package/dist/DropdownButton-vc4zpyrb.stx +23 -0
- package/dist/DropdownItem-z491y23c.stx +43 -0
- package/dist/DropdownItems-7ykf29sj.stx +18 -0
- package/dist/EmailInput-bfshpj92.stx +41 -0
- package/dist/Footer-xdaf02nr.stx +66 -0
- package/dist/Form-z4f7w6r1.stx +209 -0
- package/dist/Hero-9vvp7pxf.stx +25 -0
- package/dist/Image-ktk0451c.stx +197 -0
- package/dist/Installation-33bj4rm7.stx +39 -0
- package/dist/Listbox-xcejcmyc.stx +14 -0
- package/dist/ListboxButton-nx5kmw19.stx +17 -0
- package/dist/ListboxLabel-aky8a73n.stx +11 -0
- package/dist/ListboxOption-6h4hsdz5.stx +57 -0
- package/dist/ListboxOptions-2sacdtm5.stx +16 -0
- package/dist/Login-s2jkvd9y.stx +163 -0
- package/dist/Navigator-tqqdgdkw.stx +138 -0
- package/dist/Notification-sry8egea.stx +71 -0
- package/dist/NumberInput-zm93ddfw.stx +138 -0
- package/dist/Pagination-yza9v4qw.stx +183 -0
- package/dist/PasswordInput-4xnndp5y.stx +155 -0
- package/dist/PaymentMethods-jxba6kek.stx +113 -0
- package/dist/Popover-scbqatjq.stx +24 -0
- package/dist/PopoverButton-rbxhzj4k.stx +23 -0
- package/dist/PopoverPanel-9ng1rm6e.stx +23 -0
- package/dist/Portal-j111yvqp.stx +74 -0
- package/dist/Progress-gbmw3akr.stx +142 -0
- package/dist/Radio-1n9qamv1.stx +81 -0
- package/dist/RadioGroup-12dw2vhk.stx +13 -0
- package/dist/RadioGroupDescription-bjm3mdht.stx +11 -0
- package/dist/RadioGroupLabel-aky8a73n.stx +11 -0
- package/dist/RadioGroupOption-9pcxgwmd.stx +51 -0
- package/dist/SearchInput-g240e2nq.stx +63 -0
- package/dist/Select-p9f94b20.stx +89 -0
- package/dist/Sidebar-gwxw8q38.stx +288 -0
- package/dist/SidebarFooter-887kxret.stx +95 -0
- package/dist/SidebarHeader-qjeyx0hv.stx +100 -0
- package/dist/SidebarItem-4dpwtmb4.stx +118 -0
- package/dist/SidebarSection-kh92fk4m.stx +108 -0
- package/dist/Signup-3nxq5zag.stx +161 -0
- package/dist/Skeleton-g0nx1njm.stx +48 -0
- package/dist/Spinner-w4zj58xp.stx +109 -0
- package/dist/Stepper-rwc5actp.stx +17 -0
- package/dist/StepperStep-h0k657nm.stx +53 -0
- package/dist/SubscriptionCheckout-4cbxdg7w.stx +353 -0
- package/dist/Switch-kjq8qd2p.stx +74 -0
- package/dist/Table-59j9qwp0.stx +14 -0
- package/dist/TableBody-reka2gwv.stx +10 -0
- package/dist/TableCell-j7nh4sgr.stx +10 -0
- package/dist/TableHead-ecbrwpqm.stx +10 -0
- package/dist/TableHeader-jn2shwsv.stx +13 -0
- package/dist/TableRow-xz8rrxhc.stx +11 -0
- package/dist/Tabs-kb6ex6m7.stx +145 -0
- package/dist/Teleport-819cg9zm.stx +101 -0
- package/dist/TextInput-zx9kgr79.stx +146 -0
- package/dist/Textarea-5xvmrb0z.stx +119 -0
- package/dist/Tooltip-5ehqsy49.stx +76 -0
- package/dist/Transition-2j39s044.stx +174 -0
- package/dist/TwoFactorChallenge-2q4gg89m.stx +125 -0
- package/dist/Video-6aq4shh4.stx +45 -0
- package/dist/VirtualList-weh5qtm1.stx +83 -0
- package/dist/VirtualTable-59a1eskc.stx +113 -0
- package/dist/components/index.d.ts +4 -0
- package/dist/composables/index.d.ts +4 -0
- package/dist/composables/index.js +3 -0
- package/dist/composables/useCopyCode.d.ts +19 -0
- package/dist/composables/useDarkMode.d.ts +25 -0
- package/dist/composables/useSEO.d.ts +36 -0
- package/dist/composables/utilities/index.d.ts +5 -0
- package/dist/composables/utilities/useClickOutside.d.ts +28 -0
- package/dist/composables/utilities/useFocusTrap.d.ts +34 -0
- package/dist/composables/utilities/useKeyboard.d.ts +55 -0
- package/dist/composables/utilities/useLocalStorage.d.ts +33 -0
- package/dist/composables/utilities/useMediaQuery.d.ts +64 -0
- package/dist/index.d.ts +86 -0
- package/dist/index.js +19 -0
- package/dist/ui/accordion/index.d.ts +12 -0
- package/dist/ui/audio/index.d.ts +12 -0
- package/dist/ui/auth/index.d.ts +29 -0
- package/dist/ui/avatar/index.d.ts +11 -0
- package/dist/ui/badge/index.d.ts +9 -0
- package/dist/ui/breadcrumb/index.d.ts +13 -0
- package/dist/ui/button/index.d.ts +45 -0
- package/dist/ui/calendar/index.d.ts +13 -0
- package/dist/ui/card/index.d.ts +12 -0
- package/dist/ui/checkbox/index.d.ts +15 -0
- package/dist/ui/combobox/index.d.ts +35 -0
- package/dist/ui/command-palette/index.d.ts +13 -0
- package/dist/ui/dialog/index.d.ts +27 -0
- package/dist/ui/drawer/index.d.ts +8 -0
- package/dist/ui/dropdown/index.d.ts +25 -0
- package/dist/ui/form/index.d.ts +48 -0
- package/dist/ui/image/index.d.ts +21 -0
- package/dist/ui/input/index.d.ts +73 -0
- package/dist/ui/listbox/index.d.ts +34 -0
- package/dist/ui/navigator/index.d.ts +18 -0
- package/dist/ui/notification/index.d.ts +11 -0
- package/dist/ui/pagination/index.d.ts +10 -0
- package/dist/ui/payment/index.d.ts +53 -0
- package/dist/ui/popover/index.d.ts +19 -0
- package/dist/ui/portal/index.d.ts +5 -0
- package/dist/ui/progress/index.d.ts +11 -0
- package/dist/ui/radio/index.d.ts +14 -0
- package/dist/ui/radio-group/index.d.ts +26 -0
- package/dist/ui/select/index.d.ts +19 -0
- package/dist/ui/sidebar/index.d.ts +129 -0
- package/dist/ui/skeleton/index.d.ts +10 -0
- package/dist/ui/spinner/index.d.ts +8 -0
- package/dist/ui/stepper/index.d.ts +17 -0
- package/dist/ui/storage/index.d.ts +40 -0
- package/dist/ui/switch/index.d.ts +9 -0
- package/dist/ui/table/index.d.ts +29 -0
- package/dist/ui/tabs/index.d.ts +14 -0
- package/dist/ui/teleport/index.d.ts +6 -0
- package/dist/ui/textarea/index.d.ts +19 -0
- package/dist/ui/tooltip/index.d.ts +9 -0
- package/dist/ui/transition/index.d.ts +24 -0
- package/dist/ui/video/index.d.ts +15 -0
- package/dist/ui/virtual-list/index.d.ts +9 -0
- package/dist/ui/virtual-table/index.d.ts +19 -0
- package/dist/utils/accessibility.d.ts +285 -0
- package/dist/utils/animation/easing.d.ts +112 -0
- package/dist/utils/animation/helpers.d.ts +119 -0
- package/dist/utils/animation/index.d.ts +3 -0
- package/dist/utils/animation/keyframes.d.ts +334 -0
- package/dist/utils/error-handling.d.ts +182 -0
- package/dist/utils/highlighter.d.ts +27 -0
- package/dist/utils/highlighter.js +2 -0
- package/dist/utils/i18n.d.ts +245 -0
- package/dist/utils/keep-alive.d.ts +171 -0
- package/dist/utils/lazy-loading.d.ts +174 -0
- package/dist/utils/prop-validation.d.ts +106 -0
- package/dist/utils/theme.d.ts +234 -0
- package/package.json +69 -0
- package/src/components/CodeBlock.stx +57 -0
- package/src/components/Footer.stx +66 -0
- package/src/components/Hero.stx +25 -0
- package/src/components/Installation.stx +39 -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 +229 -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 +108 -0
- package/src/ui/accordion/index.ts +14 -0
- package/src/ui/audio/Audio.stx +50 -0
- package/src/ui/audio/README.md +42 -0
- package/src/ui/audio/index.ts +13 -0
- package/src/ui/auth/Login.stx +163 -0
- package/src/ui/auth/README.md +145 -0
- package/src/ui/auth/Signup.stx +161 -0
- package/src/ui/auth/TwoFactorChallenge.stx +125 -0
- package/src/ui/auth/index.ts +32 -0
- package/src/ui/avatar/Avatar.stx +88 -0
- package/src/ui/avatar/index.ts +12 -0
- package/src/ui/badge/Badge.stx +70 -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 +89 -0
- package/src/ui/button/README.md +100 -0
- package/src/ui/button/index.ts +85 -0
- package/src/ui/calendar/Calendar.stx +232 -0
- package/src/ui/calendar/README.md +254 -0
- package/src/ui/calendar/index.ts +14 -0
- package/src/ui/card/Card.stx +74 -0
- package/src/ui/card/index.ts +13 -0
- package/src/ui/checkbox/Checkbox.stx +92 -0
- package/src/ui/checkbox/index.ts +16 -0
- package/src/ui/combobox/Combobox.stx +13 -0
- package/src/ui/combobox/ComboboxButton.stx +13 -0
- package/src/ui/combobox/ComboboxInput.stx +25 -0
- package/src/ui/combobox/ComboboxOption.stx +52 -0
- package/src/ui/combobox/ComboboxOptions.stx +12 -0
- package/src/ui/combobox/README.md +154 -0
- package/src/ui/combobox/index.ts +40 -0
- package/src/ui/command-palette/CommandPalette.stx +39 -0
- package/src/ui/command-palette/CommandPaletteItem.stx +32 -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 +19 -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 +133 -0
- package/src/ui/dialog/index.ts +32 -0
- package/src/ui/drawer/Drawer.stx +137 -0
- package/src/ui/drawer/README.md +110 -0
- package/src/ui/drawer/index.ts +9 -0
- package/src/ui/dropdown/Dropdown.stx +24 -0
- package/src/ui/dropdown/DropdownButton.stx +23 -0
- package/src/ui/dropdown/DropdownItem.stx +43 -0
- package/src/ui/dropdown/DropdownItems.stx +18 -0
- package/src/ui/dropdown/README.md +142 -0
- package/src/ui/dropdown/index.ts +29 -0
- package/src/ui/form/Form.stx +209 -0
- package/src/ui/form/index.ts +67 -0
- package/src/ui/heatmap/Heatmap.stx +315 -0
- package/src/ui/image/Image.stx +197 -0
- package/src/ui/image/README.md +171 -0
- package/src/ui/image/index.ts +22 -0
- package/src/ui/input/EmailInput.stx +41 -0
- package/src/ui/input/NumberInput.stx +138 -0
- package/src/ui/input/PasswordInput.stx +155 -0
- package/src/ui/input/SearchInput.stx +63 -0
- package/src/ui/input/TextInput.stx +146 -0
- package/src/ui/input/index.ts +180 -0
- package/src/ui/listbox/Listbox.stx +14 -0
- package/src/ui/listbox/ListboxButton.stx +17 -0
- package/src/ui/listbox/ListboxLabel.stx +11 -0
- package/src/ui/listbox/ListboxOption.stx +57 -0
- package/src/ui/listbox/ListboxOptions.stx +16 -0
- package/src/ui/listbox/README.md +143 -0
- package/src/ui/listbox/index.ts +39 -0
- package/src/ui/navigator/Navigator.stx +138 -0
- package/src/ui/navigator/README.md +352 -0
- package/src/ui/navigator/index.ts +20 -0
- package/src/ui/notification/Notification.stx +71 -0
- package/src/ui/notification/README.md +118 -0
- package/src/ui/notification/index.ts +12 -0
- package/src/ui/pagination/Pagination.stx +183 -0
- package/src/ui/pagination/index.ts +11 -0
- package/src/ui/payment/Checkout.stx +39 -0
- package/src/ui/payment/DefaultPaymentMethod.stx +62 -0
- package/src/ui/payment/PaymentMethods.stx +113 -0
- package/src/ui/payment/README.md +427 -0
- package/src/ui/payment/SubscriptionCheckout.stx +353 -0
- package/src/ui/payment/index.ts +59 -0
- package/src/ui/popover/Popover.stx +24 -0
- package/src/ui/popover/PopoverButton.stx +23 -0
- package/src/ui/popover/PopoverPanel.stx +23 -0
- package/src/ui/popover/README.md +95 -0
- package/src/ui/popover/index.ts +22 -0
- package/src/ui/portal/Portal.stx +74 -0
- package/src/ui/portal/index.ts +6 -0
- package/src/ui/progress/Progress.stx +142 -0
- package/src/ui/progress/index.ts +12 -0
- package/src/ui/radio/Radio.stx +81 -0
- package/src/ui/radio/index.ts +15 -0
- package/src/ui/radio-group/README.md +145 -0
- package/src/ui/radio-group/RadioGroup.stx +13 -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 +51 -0
- package/src/ui/radio-group/index.ts +30 -0
- package/src/ui/select/README.md +204 -0
- package/src/ui/select/Select.stx +89 -0
- package/src/ui/select/index.ts +21 -0
- package/src/ui/sidebar/Sidebar.stx +288 -0
- package/src/ui/sidebar/SidebarFooter.stx +95 -0
- package/src/ui/sidebar/SidebarHeader.stx +100 -0
- package/src/ui/sidebar/SidebarItem.stx +118 -0
- package/src/ui/sidebar/SidebarSection.stx +108 -0
- package/src/ui/sidebar/index.ts +175 -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 +53 -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 +133 -0
- package/src/ui/switch/Switch.stx +74 -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 +13 -0
- package/src/ui/table/TableRow.stx +11 -0
- package/src/ui/table/index.ts +35 -0
- package/src/ui/tabs/Tabs.stx +145 -0
- package/src/ui/tabs/index.ts +16 -0
- package/src/ui/teleport/Teleport.stx +101 -0
- package/src/ui/teleport/index.ts +7 -0
- package/src/ui/textarea/Textarea.stx +119 -0
- package/src/ui/textarea/index.ts +20 -0
- package/src/ui/tooltip/Tooltip.stx +76 -0
- package/src/ui/tooltip/index.ts +10 -0
- package/src/ui/transition/README.md +30 -0
- package/src/ui/transition/Transition.stx +174 -0
- package/src/ui/transition/index.ts +25 -0
- package/src/ui/video/README.md +48 -0
- package/src/ui/video/Video.stx +45 -0
- package/src/ui/video/index.ts +16 -0
- package/src/ui/virtual-list/VirtualList.stx +83 -0
- package/src/ui/virtual-list/index.ts +10 -0
- package/src/ui/virtual-table/VirtualTable.stx +113 -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 +473 -0
- package/src/utils/highlighter.ts +93 -0
- package/src/utils/i18n.ts +634 -0
- package/src/utils/keep-alive.ts +818 -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,113 @@
|
|
|
1
|
+
<script>
|
|
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
|
+
// Calculate visible range
|
|
13
|
+
let scrollTop = 0
|
|
14
|
+
const visibleCount = Math.ceil((height - headerHeight) / rowHeight)
|
|
15
|
+
const totalHeight = data.length * rowHeight
|
|
16
|
+
|
|
17
|
+
function getVisibleRange() {
|
|
18
|
+
const startIndex = Math.max(0, Math.floor(scrollTop / rowHeight) - overscan)
|
|
19
|
+
const endIndex = Math.min(data.length - 1, Math.floor((scrollTop + height - headerHeight) / rowHeight) + overscan)
|
|
20
|
+
return { startIndex, endIndex }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const { startIndex, endIndex } = getVisibleRange()
|
|
24
|
+
const visibleRows = data.slice(startIndex, endIndex + 1)
|
|
25
|
+
const offsetY = startIndex * rowHeight
|
|
26
|
+
|
|
27
|
+
function handleScroll(event) {
|
|
28
|
+
const target = event.target
|
|
29
|
+
scrollTop = target.scrollTop
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Get cell value
|
|
33
|
+
function getCellValue(row, column) {
|
|
34
|
+
if (column.render) {
|
|
35
|
+
return column.render(row[column.key], row)
|
|
36
|
+
}
|
|
37
|
+
return row[column.key]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const containerClasses = `border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden ${className}`.trim()
|
|
41
|
+
export const headerClasses = `bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 flex`.trim()
|
|
42
|
+
export const headerCellClasses = `px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider`.trim()
|
|
43
|
+
export const bodyClasses = `overflow-auto bg-white dark:bg-gray-900`.trim()
|
|
44
|
+
export const innerClasses = `relative`.trim()
|
|
45
|
+
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()
|
|
46
|
+
export const cellClasses = `px-6 py-4 text-sm text-gray-900 dark:text-gray-100`.trim()
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<div class="{{ containerClasses }}">
|
|
50
|
+
<!-- Header -->
|
|
51
|
+
<div class="{{ headerClasses }}" style="height: {{ headerHeight }}px">
|
|
52
|
+
@foreach(column in columns)
|
|
53
|
+
<div
|
|
54
|
+
class="{{ headerCellClasses }}"
|
|
55
|
+
style="flex: {{ column.width || 1 }}; min-width: {{ column.minWidth || 'auto' }}"
|
|
56
|
+
>
|
|
57
|
+
{{ column.label || column.key }}
|
|
58
|
+
</div>
|
|
59
|
+
@endforeach
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<!-- Body -->
|
|
63
|
+
<div
|
|
64
|
+
class="{{ bodyClasses }}"
|
|
65
|
+
style="height: {{ height - headerHeight }}px"
|
|
66
|
+
onscroll="handleScroll(event)"
|
|
67
|
+
>
|
|
68
|
+
<div
|
|
69
|
+
class="{{ innerClasses }}"
|
|
70
|
+
style="height: {{ totalHeight }}px"
|
|
71
|
+
>
|
|
72
|
+
@foreach(row in visibleRows)
|
|
73
|
+
<div
|
|
74
|
+
class="{{ rowClasses }} {{ striped && ($loop.index % 2 === 1) ? 'bg-gray-50 dark:bg-gray-800' : '' }}"
|
|
75
|
+
style="top: {{ (startIndex + $loop.index) * rowHeight }}px; height: {{ rowHeight }}px"
|
|
76
|
+
data-index="{{ startIndex + $loop.index }}"
|
|
77
|
+
>
|
|
78
|
+
@foreach(column in columns)
|
|
79
|
+
<div
|
|
80
|
+
class="{{ cellClasses }}"
|
|
81
|
+
style="flex: {{ column.width || 1 }}; min-width: {{ column.minWidth || 'auto' }}"
|
|
82
|
+
>
|
|
83
|
+
{!! getCellValue(row, column) !!}
|
|
84
|
+
</div>
|
|
85
|
+
@endforeach
|
|
86
|
+
</div>
|
|
87
|
+
@endforeach
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<script>
|
|
93
|
+
// Re-render on scroll (simplified reactivity)
|
|
94
|
+
if (typeof window !== 'undefined') {
|
|
95
|
+
let ticking = false
|
|
96
|
+
|
|
97
|
+
function updateVisibleRows() {
|
|
98
|
+
const range = getVisibleRange()
|
|
99
|
+
// Trigger re-render
|
|
100
|
+
ticking = false
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const bodyContainer = document.querySelector('.overflow-auto')
|
|
104
|
+
if (bodyContainer) {
|
|
105
|
+
bodyContainer.addEventListener('scroll', () => {
|
|
106
|
+
if (!ticking) {
|
|
107
|
+
window.requestAnimationFrame(updateVisibleRows)
|
|
108
|
+
ticking = true
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
</script>
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
# Prop Validation System
|
|
2
|
+
|
|
3
|
+
A comprehensive runtime prop validation system for component development with clear, actionable error messages.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Development-only warnings** - Zero runtime cost in production
|
|
8
|
+
- **TypeScript integration** - Works alongside TypeScript for compile-time checks
|
|
9
|
+
- **Clear error messages** - Actionable warnings with specific values and expected types
|
|
10
|
+
- **Extensive validators** - 20+ built-in validators for common use cases
|
|
11
|
+
- **Composable schemas** - Build complex validation schemas from simple validators
|
|
12
|
+
- **Custom validators** - Easy to create custom validation logic
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
The prop validation utilities are included in `@stacksjs/components`:
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { validateProps, PropTypes } from '@stacksjs/components'
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Basic Usage
|
|
23
|
+
|
|
24
|
+
### Define a Schema
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { PropTypes } from '@stacksjs/components'
|
|
28
|
+
|
|
29
|
+
const buttonPropSchema = {
|
|
30
|
+
variant: PropTypes.oneOf(['primary', 'secondary', 'outline']),
|
|
31
|
+
size: PropTypes.oneOf(['sm', 'md', 'lg']),
|
|
32
|
+
disabled: PropTypes.boolean,
|
|
33
|
+
onClick: PropTypes.func.required, // Mark as required
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Validate Props
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { validateProps } from '@stacksjs/components'
|
|
41
|
+
|
|
42
|
+
function Button(props) {
|
|
43
|
+
// Validate props (only warns in development)
|
|
44
|
+
validateProps('Button', props, buttonPropSchema)
|
|
45
|
+
|
|
46
|
+
// ... rest of component
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Using createPropValidator
|
|
51
|
+
|
|
52
|
+
For reusable validators:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import { createPropValidator, PropTypes } from '@stacksjs/components'
|
|
56
|
+
|
|
57
|
+
const validateButtonProps = createPropValidator('Button', {
|
|
58
|
+
variant: PropTypes.oneOf(['primary', 'secondary', 'outline']),
|
|
59
|
+
size: PropTypes.oneOf(['sm', 'md', 'lg']),
|
|
60
|
+
disabled: PropTypes.boolean,
|
|
61
|
+
onClick: PropTypes.func.required,
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
// In component
|
|
65
|
+
function Button(props) {
|
|
66
|
+
validateButtonProps(props)
|
|
67
|
+
// ... rest of component
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Available Validators
|
|
72
|
+
|
|
73
|
+
### Primitive Types
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
PropTypes.string // String type
|
|
77
|
+
PropTypes.number // Number type (excludes NaN)
|
|
78
|
+
PropTypes.boolean // Boolean type
|
|
79
|
+
PropTypes.func // Function type
|
|
80
|
+
PropTypes.object // Object type (excludes arrays and null)
|
|
81
|
+
PropTypes.array // Array type
|
|
82
|
+
PropTypes.any // Any type (always passes)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Enum Validators
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
// Value must be one of the specified options
|
|
89
|
+
PropTypes.oneOf(['sm', 'md', 'lg'])
|
|
90
|
+
PropTypes.oneOf([1, 2, 3])
|
|
91
|
+
|
|
92
|
+
// Value must match one of the specified types
|
|
93
|
+
PropTypes.oneOfType([
|
|
94
|
+
PropTypes.string,
|
|
95
|
+
PropTypes.number
|
|
96
|
+
])
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Array Validators
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
// Array of specific type
|
|
103
|
+
PropTypes.arrayOf(PropTypes.string)
|
|
104
|
+
PropTypes.arrayOf(PropTypes.number)
|
|
105
|
+
|
|
106
|
+
// Array of objects with specific shape
|
|
107
|
+
PropTypes.arrayOf(
|
|
108
|
+
PropTypes.shape({
|
|
109
|
+
id: PropTypes.number.required,
|
|
110
|
+
name: PropTypes.string.required
|
|
111
|
+
})
|
|
112
|
+
)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Object Validators
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
// Object with specific shape
|
|
119
|
+
PropTypes.shape({
|
|
120
|
+
name: PropTypes.string.required,
|
|
121
|
+
age: PropTypes.number,
|
|
122
|
+
email: PropTypes.email,
|
|
123
|
+
address: PropTypes.shape({
|
|
124
|
+
street: PropTypes.string,
|
|
125
|
+
city: PropTypes.string.required
|
|
126
|
+
})
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
// Instance of a class
|
|
130
|
+
PropTypes.instanceOf(Date)
|
|
131
|
+
PropTypes.instanceOf(HTMLElement)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Number Validators
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
// Minimum value
|
|
138
|
+
PropTypes.min(0)
|
|
139
|
+
|
|
140
|
+
// Maximum value
|
|
141
|
+
PropTypes.max(100)
|
|
142
|
+
|
|
143
|
+
// Range
|
|
144
|
+
PropTypes.range(0, 100)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### String Validators
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
// Pattern matching
|
|
151
|
+
PropTypes.pattern(/^[A-Z]{3}$/)
|
|
152
|
+
|
|
153
|
+
// Email validation
|
|
154
|
+
PropTypes.email
|
|
155
|
+
|
|
156
|
+
// URL validation
|
|
157
|
+
PropTypes.url
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Custom Validators
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
// Custom validation function
|
|
164
|
+
PropTypes.custom(
|
|
165
|
+
(value) => value > 0 && value < 100,
|
|
166
|
+
'Value must be between 0 and 100'
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
// Custom with dynamic error message
|
|
170
|
+
PropTypes.custom(
|
|
171
|
+
(value, propName, componentName) => typeof value === 'string' && value.length > 5,
|
|
172
|
+
(value, propName, componentName) =>
|
|
173
|
+
`Prop \`${propName}\` in \`${componentName}\` must be a string longer than 5 characters`
|
|
174
|
+
)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Required Props
|
|
178
|
+
|
|
179
|
+
Mark any validator as required:
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
const schema = {
|
|
183
|
+
name: PropTypes.string.required,
|
|
184
|
+
age: PropTypes.number.required,
|
|
185
|
+
email: PropTypes.email.required,
|
|
186
|
+
onClick: PropTypes.func.required
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Complex Examples
|
|
191
|
+
|
|
192
|
+
### Form Input Validation
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
const inputPropSchema = {
|
|
196
|
+
value: PropTypes.string,
|
|
197
|
+
onChange: PropTypes.func.required,
|
|
198
|
+
placeholder: PropTypes.string,
|
|
199
|
+
disabled: PropTypes.boolean,
|
|
200
|
+
size: PropTypes.oneOf(['sm', 'md', 'lg']),
|
|
201
|
+
error: PropTypes.boolean,
|
|
202
|
+
label: PropTypes.string,
|
|
203
|
+
helperText: PropTypes.string,
|
|
204
|
+
required: PropTypes.boolean,
|
|
205
|
+
maxLength: PropTypes.range(1, 1000),
|
|
206
|
+
pattern: PropTypes.instanceOf(RegExp)
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### User Object Validation
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
const userPropSchema = {
|
|
214
|
+
user: PropTypes.shape({
|
|
215
|
+
id: PropTypes.number.required,
|
|
216
|
+
name: PropTypes.string.required,
|
|
217
|
+
email: PropTypes.email.required,
|
|
218
|
+
age: PropTypes.range(0, 150),
|
|
219
|
+
address: PropTypes.shape({
|
|
220
|
+
street: PropTypes.string,
|
|
221
|
+
city: PropTypes.string.required,
|
|
222
|
+
zipCode: PropTypes.pattern(/^\d{5}$/)
|
|
223
|
+
}),
|
|
224
|
+
roles: PropTypes.arrayOf(
|
|
225
|
+
PropTypes.oneOf(['admin', 'user', 'guest'])
|
|
226
|
+
)
|
|
227
|
+
}).required
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Complex Component Props
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
const dataPropSchema = {
|
|
235
|
+
items: PropTypes.arrayOf(
|
|
236
|
+
PropTypes.shape({
|
|
237
|
+
id: PropTypes.oneOfType([
|
|
238
|
+
PropTypes.string,
|
|
239
|
+
PropTypes.number
|
|
240
|
+
]).required,
|
|
241
|
+
label: PropTypes.string.required,
|
|
242
|
+
value: PropTypes.any,
|
|
243
|
+
disabled: PropTypes.boolean,
|
|
244
|
+
metadata: PropTypes.object
|
|
245
|
+
})
|
|
246
|
+
).required,
|
|
247
|
+
selectedId: PropTypes.oneOfType([
|
|
248
|
+
PropTypes.string,
|
|
249
|
+
PropTypes.number
|
|
250
|
+
]),
|
|
251
|
+
onSelect: PropTypes.func.required,
|
|
252
|
+
multiSelect: PropTypes.boolean,
|
|
253
|
+
maxSelections: PropTypes.custom(
|
|
254
|
+
(value) => typeof value === 'number' && value > 0,
|
|
255
|
+
'maxSelections must be a positive number'
|
|
256
|
+
)
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## Validation Options
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
validateProps('Component', props, schema, {
|
|
264
|
+
// Throw error instead of warning (default: false)
|
|
265
|
+
throwOnError: true,
|
|
266
|
+
|
|
267
|
+
// Disable console warnings (default: true in dev)
|
|
268
|
+
logWarnings: false
|
|
269
|
+
})
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Helper Functions
|
|
273
|
+
|
|
274
|
+
### assertProp
|
|
275
|
+
|
|
276
|
+
Assert a value matches a validator (throws on failure):
|
|
277
|
+
|
|
278
|
+
```ts
|
|
279
|
+
import { assertProp, PropTypes } from '@stacksjs/components'
|
|
280
|
+
|
|
281
|
+
function processUser(user: unknown) {
|
|
282
|
+
// Throws if user doesn't match shape
|
|
283
|
+
assertProp(user, PropTypes.shape({
|
|
284
|
+
id: PropTypes.number.required,
|
|
285
|
+
name: PropTypes.string.required
|
|
286
|
+
}), 'user')
|
|
287
|
+
|
|
288
|
+
// TypeScript now knows user has the correct shape
|
|
289
|
+
console.log(user.id, user.name)
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### warnProp
|
|
294
|
+
|
|
295
|
+
Warn if prop doesn't match validator (non-throwing):
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
import { warnProp, PropTypes } from '@stacksjs/components'
|
|
299
|
+
|
|
300
|
+
function logValue(value: unknown) {
|
|
301
|
+
// Only warns in development, doesn't throw
|
|
302
|
+
warnProp(value, PropTypes.string, 'value')
|
|
303
|
+
|
|
304
|
+
console.log(value)
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## Error Messages
|
|
309
|
+
|
|
310
|
+
The validation system provides clear, actionable error messages:
|
|
311
|
+
|
|
312
|
+
```
|
|
313
|
+
⚠️ Prop validation failed for `Button`
|
|
314
|
+
Invalid prop `variant` of value `large` supplied to `Button`, expected one of ["primary", "secondary", "outline", "ghost", "danger"].
|
|
315
|
+
Required prop `onClick` was not specified in `Button`.
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
## Production Behavior
|
|
319
|
+
|
|
320
|
+
- All validation is **completely skipped in production** builds
|
|
321
|
+
- Zero runtime overhead
|
|
322
|
+
- No console warnings
|
|
323
|
+
- `validateProps()`, `assertProp()`, and `warnProp()` become no-ops
|
|
324
|
+
|
|
325
|
+
## TypeScript Integration
|
|
326
|
+
|
|
327
|
+
Prop validation works alongside TypeScript:
|
|
328
|
+
|
|
329
|
+
```ts
|
|
330
|
+
interface ButtonProps {
|
|
331
|
+
variant?: 'primary' | 'secondary' | 'outline'
|
|
332
|
+
size?: 'sm' | 'md' | 'lg'
|
|
333
|
+
disabled?: boolean
|
|
334
|
+
onClick: (event: Event) => void
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const buttonPropSchema = {
|
|
338
|
+
variant: PropTypes.oneOf(['primary', 'secondary', 'outline']),
|
|
339
|
+
size: PropTypes.oneOf(['sm', 'md', 'lg']),
|
|
340
|
+
disabled: PropTypes.boolean,
|
|
341
|
+
onClick: PropTypes.func.required
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function Button(props: ButtonProps) {
|
|
345
|
+
// TypeScript provides compile-time checking
|
|
346
|
+
// PropTypes provides runtime checking in development
|
|
347
|
+
validateProps('Button', props, buttonPropSchema)
|
|
348
|
+
|
|
349
|
+
// ...
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
## Best Practices
|
|
354
|
+
|
|
355
|
+
### 1. Define schemas separately
|
|
356
|
+
|
|
357
|
+
```ts
|
|
358
|
+
// schemas/button.ts
|
|
359
|
+
export const buttonPropSchema = {
|
|
360
|
+
variant: PropTypes.oneOf(['primary', 'secondary']),
|
|
361
|
+
size: PropTypes.oneOf(['sm', 'md', 'lg']),
|
|
362
|
+
onClick: PropTypes.func.required
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// components/Button.ts
|
|
366
|
+
import { buttonPropSchema } from '../schemas/button'
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### 2. Reuse base schemas
|
|
370
|
+
|
|
371
|
+
```ts
|
|
372
|
+
const baseInputSchema = {
|
|
373
|
+
value: PropTypes.string,
|
|
374
|
+
onChange: PropTypes.func,
|
|
375
|
+
disabled: PropTypes.boolean,
|
|
376
|
+
size: PropTypes.oneOf(['sm', 'md', 'lg'])
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const textInputSchema = {
|
|
380
|
+
...baseInputSchema,
|
|
381
|
+
maxLength: PropTypes.number,
|
|
382
|
+
placeholder: PropTypes.string
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const numberInputSchema = {
|
|
386
|
+
...baseInputSchema,
|
|
387
|
+
min: PropTypes.number,
|
|
388
|
+
max: PropTypes.number,
|
|
389
|
+
step: PropTypes.number
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
### 3. Create component-specific validators
|
|
394
|
+
|
|
395
|
+
```ts
|
|
396
|
+
// ui/button/index.ts
|
|
397
|
+
import { createPropValidator, PropTypes } from '@stacksjs/components'
|
|
398
|
+
|
|
399
|
+
export const validateButtonProps = createPropValidator('Button', {
|
|
400
|
+
variant: PropTypes.oneOf(['primary', 'secondary', 'outline']),
|
|
401
|
+
size: PropTypes.oneOf(['sm', 'md', 'lg']),
|
|
402
|
+
onClick: PropTypes.func.required
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
// Export for use in other files
|
|
406
|
+
export { validateButtonProps }
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### 4. Use custom validators for complex logic
|
|
410
|
+
|
|
411
|
+
```ts
|
|
412
|
+
const isPowerOfTwo = PropTypes.custom(
|
|
413
|
+
(value) => typeof value === 'number' && (value & (value - 1)) === 0,
|
|
414
|
+
'Value must be a power of 2'
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
const isEven = PropTypes.custom(
|
|
418
|
+
(value) => typeof value === 'number' && value % 2 === 0,
|
|
419
|
+
'Value must be an even number'
|
|
420
|
+
)
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
## Migration from React PropTypes
|
|
424
|
+
|
|
425
|
+
If you're familiar with React's PropTypes, this system follows a similar API:
|
|
426
|
+
|
|
427
|
+
```ts
|
|
428
|
+
// React PropTypes
|
|
429
|
+
Component.propTypes = {
|
|
430
|
+
name: PropTypes.string.isRequired,
|
|
431
|
+
age: PropTypes.number,
|
|
432
|
+
onClick: PropTypes.func.isRequired
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// @stacksjs/components
|
|
436
|
+
const componentPropSchema = {
|
|
437
|
+
name: PropTypes.string.required,
|
|
438
|
+
age: PropTypes.number,
|
|
439
|
+
onClick: PropTypes.func.required
|
|
440
|
+
}
|
|
441
|
+
validateProps('Component', props, componentPropSchema)
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
Key differences:
|
|
445
|
+
- Use `.required` instead of `.isRequired`
|
|
446
|
+
- Validation is a separate function call, not a static property
|
|
447
|
+
- Validation only runs in development mode
|
|
448
|
+
|
|
449
|
+
## Examples in the Library
|
|
450
|
+
|
|
451
|
+
Several components in the library already use prop validation:
|
|
452
|
+
|
|
453
|
+
- **Button** - `src/ui/button/index.ts`
|
|
454
|
+
- **TextInput** - `src/ui/input/index.ts`
|
|
455
|
+
- **NumberInput** - `src/ui/input/index.ts`
|
|
456
|
+
- **Form** - `src/ui/form/index.ts`
|
|
457
|
+
|
|
458
|
+
Check these files for real-world examples of prop validation in action.
|