solid-tom-ui 1.0.8 → 1.0.11
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/README.md +246 -0
- package/dist/components/avatar/index.d.ts +3 -0
- package/{components → dist/components}/avatar/index.d.ts.map +1 -1
- package/dist/components/button/index.d.ts +3 -0
- package/{components → dist/components}/button/index.d.ts.map +1 -1
- package/dist/components/collapse/index.d.ts +3 -0
- package/{components → dist/components}/collapse/index.d.ts.map +1 -1
- package/dist/components/float-button/index.d.ts +3 -0
- package/{components → dist/components}/float-button/index.d.ts.map +1 -1
- package/dist/components/input/index.d.ts +3 -0
- package/dist/components/input/index.d.ts.map +1 -0
- package/dist/components/menu/index.d.ts +4 -0
- package/{components → dist/components}/menu/index.d.ts.map +1 -1
- package/{components → dist/components}/menu/menu.types.d.ts +9 -10
- package/{components → dist/components}/menu/menu.types.d.ts.map +1 -1
- package/dist/components/rating/index.d.ts +3 -0
- package/{components → dist/components}/rating/index.d.ts.map +1 -1
- package/dist/components/slider/index.d.ts +3 -0
- package/{components → dist/components}/slider/index.d.ts.map +1 -1
- package/{components → dist/components}/tour/tour.d.ts.map +1 -1
- package/{components → dist/components}/tour/tour.js +1 -1
- package/{components → dist/components}/tour/tour.js.map +1 -1
- package/{components → dist/components}/z-index/z-index.types.js.map +1 -1
- package/dist/package.json +45 -0
- package/dist/skill/avatar.skill.md.txt +255 -0
- package/dist/skill/badge.skill.md.txt +223 -0
- package/dist/skill/blank.skill.md.txt +0 -0
- package/dist/skill/breadcrumb.skill.md.txt +177 -0
- package/dist/skill/button.skill.md.txt +198 -0
- package/dist/skill/carousel.skill.md.txt +406 -0
- package/dist/skill/chat-bubble.skill.md.txt +342 -0
- package/dist/skill/checkbox.skill.md.txt +326 -0
- package/dist/skill/code-preview.skill.md.txt +240 -0
- package/dist/skill/collapse.skill.md.txt +329 -0
- package/dist/skill/context-menu.skill.md.txt +233 -0
- package/dist/skill/diff.skill.md.txt +244 -0
- package/dist/skill/divider.skill.md.txt +151 -0
- package/dist/skill/doc.skill.md.txt +191 -0
- package/dist/skill/drawer.skill.md.txt +157 -0
- package/dist/skill/dropdown.skill.md.txt +198 -0
- package/dist/skill/float-button.skill.md.txt +315 -0
- package/dist/skill/hover-3d-image.skill.md.txt +120 -0
- package/dist/skill/iframe.skill.md.txt +114 -0
- package/dist/skill/image-preview.skill.md.txt +162 -0
- package/dist/skill/indicator.skill.md.txt +60 -0
- package/dist/skill/input.skill.md.txt +489 -0
- package/dist/skill/loading.skill.md.txt +127 -0
- package/dist/skill/mansory.skill.md.txt +0 -0
- package/dist/skill/menu.skill.md.txt +476 -0
- package/dist/skill/modal.skill.md.txt +359 -0
- package/dist/skill/pagination.skill.md.txt +405 -0
- package/dist/skill/progress-bar.skill.md.txt +207 -0
- package/dist/skill/qr-code.skill.md.txt +136 -0
- package/dist/skill/rating.skill.md.txt +167 -0
- package/dist/skill/select-zone.skill.md.txt +93 -0
- package/dist/skill/select.skill.md.txt +663 -0
- package/dist/skill/skeleton.skill.md.txt +192 -0
- package/dist/skill/slider.skill.md.txt +404 -0
- package/dist/skill/splitter.skill.md.txt +411 -0
- package/dist/skill/steps.skill.md.txt +264 -0
- package/dist/skill/swap.skill.md.txt +139 -0
- package/dist/skill/switch.skill.md.txt +191 -0
- package/dist/skill/tab.skill.md.txt +484 -0
- package/dist/skill/table.example.header.md.txt +667 -0
- package/dist/skill/table.skill.md.txt +1407 -0
- package/dist/skill/text-rotate.skill.md.txt +186 -0
- package/dist/skill/timeline.skill.md.txt +247 -0
- package/dist/skill/toast.skill.md.txt +531 -0
- package/dist/skill/tooltip.skill.md.txt +222 -0
- package/dist/skill/tour.skill.md.txt +156 -0
- package/dist/skill/upload.skill.md.txt +358 -0
- package/dist/skill/z-index.skill.md.txt +0 -0
- package/{solid-ui.css → dist/solid-ui.css} +2 -2
- package/package.json +97 -9
- package/components/avatar/index.d.ts +0 -2
- package/components/button/index.d.ts +0 -2
- package/components/collapse/index.d.ts +0 -2
- package/components/float-button/index.d.ts +0 -2
- package/components/input/index.d.ts +0 -2
- package/components/input/index.d.ts.map +0 -1
- package/components/menu/index.d.ts +0 -4
- package/components/rating/index.d.ts +0 -2
- package/components/slider/index.d.ts +0 -2
- package/macos.code-workspace +0 -8
- /package/{components → dist/components}/avatar/avatar.d.ts +0 -0
- /package/{components → dist/components}/avatar/avatar.d.ts.map +0 -0
- /package/{components → dist/components}/avatar/avatar.js +0 -0
- /package/{components → dist/components}/avatar/avatar.js.map +0 -0
- /package/{components → dist/components}/avatar/avatar.types.d.ts +0 -0
- /package/{components → dist/components}/avatar/avatar.types.d.ts.map +0 -0
- /package/{components → dist/components}/badge/badge.d.ts +0 -0
- /package/{components → dist/components}/badge/badge.d.ts.map +0 -0
- /package/{components → dist/components}/badge/badge.js +0 -0
- /package/{components → dist/components}/badge/badge.js.map +0 -0
- /package/{components → dist/components}/badge/badge.types.d.ts +0 -0
- /package/{components → dist/components}/badge/badge.types.d.ts.map +0 -0
- /package/{components → dist/components}/badge/index.d.ts +0 -0
- /package/{components → dist/components}/badge/index.d.ts.map +0 -0
- /package/{components → dist/components}/blank/blank.d.ts +0 -0
- /package/{components → dist/components}/blank/blank.d.ts.map +0 -0
- /package/{components → dist/components}/blank/index.d.ts +0 -0
- /package/{components → dist/components}/blank/index.d.ts.map +0 -0
- /package/{components → dist/components}/breadcrumb/breadcrumb.d.ts +0 -0
- /package/{components → dist/components}/breadcrumb/breadcrumb.d.ts.map +0 -0
- /package/{components → dist/components}/breadcrumb/breadcrumb.js +0 -0
- /package/{components → dist/components}/breadcrumb/breadcrumb.js.map +0 -0
- /package/{components → dist/components}/breadcrumb/index.d.ts +0 -0
- /package/{components → dist/components}/breadcrumb/index.d.ts.map +0 -0
- /package/{components → dist/components}/button/button.d.ts +0 -0
- /package/{components → dist/components}/button/button.d.ts.map +0 -0
- /package/{components → dist/components}/button/button.js +0 -0
- /package/{components → dist/components}/button/button.js.map +0 -0
- /package/{components → dist/components}/button/button.types.d.ts +0 -0
- /package/{components → dist/components}/button/button.types.d.ts.map +0 -0
- /package/{components → dist/components}/carousel/carousel.d.ts +0 -0
- /package/{components → dist/components}/carousel/carousel.d.ts.map +0 -0
- /package/{components → dist/components}/carousel/carousel.js +0 -0
- /package/{components → dist/components}/carousel/carousel.js.map +0 -0
- /package/{components → dist/components}/carousel/carousel.types.d.ts +0 -0
- /package/{components → dist/components}/carousel/carousel.types.d.ts.map +0 -0
- /package/{components → dist/components}/carousel/index.d.ts +0 -0
- /package/{components → dist/components}/carousel/index.d.ts.map +0 -0
- /package/{components → dist/components}/chat-bubble/chatBubble.d.ts +0 -0
- /package/{components → dist/components}/chat-bubble/chatBubble.d.ts.map +0 -0
- /package/{components → dist/components}/chat-bubble/chatBubble.js +0 -0
- /package/{components → dist/components}/chat-bubble/chatBubble.js.map +0 -0
- /package/{components → dist/components}/chat-bubble/chatBubble.type.d.ts +0 -0
- /package/{components → dist/components}/chat-bubble/chatBubble.type.d.ts.map +0 -0
- /package/{components → dist/components}/chat-bubble/index.d.ts +0 -0
- /package/{components → dist/components}/chat-bubble/index.d.ts.map +0 -0
- /package/{components → dist/components}/checkbox/checkbox.d.ts +0 -0
- /package/{components → dist/components}/checkbox/checkbox.d.ts.map +0 -0
- /package/{components → dist/components}/checkbox/checkbox.js +0 -0
- /package/{components → dist/components}/checkbox/checkbox.js.map +0 -0
- /package/{components → dist/components}/checkbox/index.d.ts +0 -0
- /package/{components → dist/components}/checkbox/index.d.ts.map +0 -0
- /package/{components → dist/components}/collapse/collapse.d.ts +0 -0
- /package/{components → dist/components}/collapse/collapse.d.ts.map +0 -0
- /package/{components → dist/components}/collapse/collapse.js +0 -0
- /package/{components → dist/components}/collapse/collapse.js.map +0 -0
- /package/{components → dist/components}/collapse/collapse.types.d.ts +0 -0
- /package/{components → dist/components}/collapse/collapse.types.d.ts.map +0 -0
- /package/{components → dist/components}/context-menu/context-menu.d.ts +0 -0
- /package/{components → dist/components}/context-menu/context-menu.d.ts.map +0 -0
- /package/{components → dist/components}/context-menu/context-menu.js +0 -0
- /package/{components → dist/components}/context-menu/context-menu.js.map +0 -0
- /package/{components → dist/components}/context-menu/context-menu.store.d.ts +0 -0
- /package/{components → dist/components}/context-menu/context-menu.store.d.ts.map +0 -0
- /package/{components → dist/components}/context-menu/context-menu.store.js +0 -0
- /package/{components → dist/components}/context-menu/context-menu.store.js.map +0 -0
- /package/{components → dist/components}/context-menu/context-menu.types.d.ts +0 -0
- /package/{components → dist/components}/context-menu/context-menu.types.d.ts.map +0 -0
- /package/{components → dist/components}/context-menu/index.d.ts +0 -0
- /package/{components → dist/components}/context-menu/index.d.ts.map +0 -0
- /package/{components → dist/components}/diff/diff.d.ts +0 -0
- /package/{components → dist/components}/diff/diff.d.ts.map +0 -0
- /package/{components → dist/components}/diff/diff.js +0 -0
- /package/{components → dist/components}/diff/diff.js.map +0 -0
- /package/{components → dist/components}/diff/index.d.ts +0 -0
- /package/{components → dist/components}/diff/index.d.ts.map +0 -0
- /package/{components → dist/components}/divider/divider.d.ts +0 -0
- /package/{components → dist/components}/divider/divider.d.ts.map +0 -0
- /package/{components → dist/components}/divider/divider.js +0 -0
- /package/{components → dist/components}/divider/divider.js.map +0 -0
- /package/{components → dist/components}/divider/divider.types.d.ts +0 -0
- /package/{components → dist/components}/divider/divider.types.d.ts.map +0 -0
- /package/{components → dist/components}/divider/index.d.ts +0 -0
- /package/{components → dist/components}/divider/index.d.ts.map +0 -0
- /package/{components → dist/components}/drawer/drawer.d.ts +0 -0
- /package/{components → dist/components}/drawer/drawer.d.ts.map +0 -0
- /package/{components → dist/components}/drawer/drawer.js +0 -0
- /package/{components → dist/components}/drawer/drawer.js.map +0 -0
- /package/{components → dist/components}/drawer/drawer.types.d.ts +0 -0
- /package/{components → dist/components}/drawer/drawer.types.d.ts.map +0 -0
- /package/{components → dist/components}/drawer/index.d.ts +0 -0
- /package/{components → dist/components}/drawer/index.d.ts.map +0 -0
- /package/{components → dist/components}/dropdown/dropdown.d.ts +0 -0
- /package/{components → dist/components}/dropdown/dropdown.d.ts.map +0 -0
- /package/{components → dist/components}/dropdown/dropdown.js +0 -0
- /package/{components → dist/components}/dropdown/dropdown.js.map +0 -0
- /package/{components → dist/components}/dropdown/dropdown.store.d.ts +0 -0
- /package/{components → dist/components}/dropdown/dropdown.store.d.ts.map +0 -0
- /package/{components → dist/components}/dropdown/dropdown.store.js +0 -0
- /package/{components → dist/components}/dropdown/dropdown.store.js.map +0 -0
- /package/{components → dist/components}/dropdown/dropdown.types.d.ts +0 -0
- /package/{components → dist/components}/dropdown/dropdown.types.d.ts.map +0 -0
- /package/{components → dist/components}/dropdown/index.d.ts +0 -0
- /package/{components → dist/components}/dropdown/index.d.ts.map +0 -0
- /package/{components → dist/components}/float-button/float-button.d.ts +0 -0
- /package/{components → dist/components}/float-button/float-button.d.ts.map +0 -0
- /package/{components → dist/components}/float-button/float-button.js +0 -0
- /package/{components → dist/components}/float-button/float-button.js.map +0 -0
- /package/{components → dist/components}/float-button/float-button.types.d.ts +0 -0
- /package/{components → dist/components}/float-button/float-button.types.d.ts.map +0 -0
- /package/{components → dist/components}/hover-3d-image/hover-3d-example.d.ts +0 -0
- /package/{components → dist/components}/hover-3d-image/hover-3d-example.d.ts.map +0 -0
- /package/{components → dist/components}/hover-3d-image/hover-3d-image.d.ts +0 -0
- /package/{components → dist/components}/hover-3d-image/hover-3d-image.d.ts.map +0 -0
- /package/{components → dist/components}/hover-3d-image/hover-3d-image.js +0 -0
- /package/{components → dist/components}/hover-3d-image/hover-3d-image.js.map +0 -0
- /package/{components → dist/components}/hover-3d-image/index.d.ts +0 -0
- /package/{components → dist/components}/hover-3d-image/index.d.ts.map +0 -0
- /package/{components → dist/components}/image-preview/image-preview.d.ts +0 -0
- /package/{components → dist/components}/image-preview/image-preview.d.ts.map +0 -0
- /package/{components → dist/components}/image-preview/image-preview.js +0 -0
- /package/{components → dist/components}/image-preview/image-preview.js.map +0 -0
- /package/{components → dist/components}/image-preview/index.d.ts +0 -0
- /package/{components → dist/components}/image-preview/index.d.ts.map +0 -0
- /package/{components → dist/components}/indicator/index.d.ts +0 -0
- /package/{components → dist/components}/indicator/index.d.ts.map +0 -0
- /package/{components → dist/components}/indicator/indicator.d.ts +0 -0
- /package/{components → dist/components}/indicator/indicator.d.ts.map +0 -0
- /package/{components → dist/components}/indicator/indicator.js +0 -0
- /package/{components → dist/components}/indicator/indicator.js.map +0 -0
- /package/{components → dist/components}/indicator/indicator.types.d.ts +0 -0
- /package/{components → dist/components}/indicator/indicator.types.d.ts.map +0 -0
- /package/{components → dist/components}/input/input.d.ts +0 -0
- /package/{components → dist/components}/input/input.d.ts.map +0 -0
- /package/{components → dist/components}/input/input.js +0 -0
- /package/{components → dist/components}/input/input.js.map +0 -0
- /package/{components → dist/components}/input/input.types.d.ts +0 -0
- /package/{components → dist/components}/input/input.types.d.ts.map +0 -0
- /package/{components → dist/components}/input/input.utils.d.ts +0 -0
- /package/{components → dist/components}/input/input.utils.d.ts.map +0 -0
- /package/{components → dist/components}/input/input.utils.js +0 -0
- /package/{components → dist/components}/input/input.utils.js.map +0 -0
- /package/{components → dist/components}/input/variants/input-color.d.ts +0 -0
- /package/{components → dist/components}/input/variants/input-color.d.ts.map +0 -0
- /package/{components → dist/components}/input/variants/input-color.js +0 -0
- /package/{components → dist/components}/input/variants/input-color.js.map +0 -0
- /package/{components → dist/components}/input/variants/input-date.d.ts +0 -0
- /package/{components → dist/components}/input/variants/input-date.d.ts.map +0 -0
- /package/{components → dist/components}/input/variants/input-date.js +0 -0
- /package/{components → dist/components}/input/variants/input-date.js.map +0 -0
- /package/{components → dist/components}/input/variants/input-number.d.ts +0 -0
- /package/{components → dist/components}/input/variants/input-number.d.ts.map +0 -0
- /package/{components → dist/components}/input/variants/input-number.js +0 -0
- /package/{components → dist/components}/input/variants/input-number.js.map +0 -0
- /package/{components → dist/components}/input/variants/input-otp.d.ts +0 -0
- /package/{components → dist/components}/input/variants/input-otp.d.ts.map +0 -0
- /package/{components → dist/components}/input/variants/input-otp.js +0 -0
- /package/{components → dist/components}/input/variants/input-otp.js.map +0 -0
- /package/{components → dist/components}/input/variants/input-password.d.ts +0 -0
- /package/{components → dist/components}/input/variants/input-password.d.ts.map +0 -0
- /package/{components → dist/components}/input/variants/input-password.js +0 -0
- /package/{components → dist/components}/input/variants/input-password.js.map +0 -0
- /package/{components → dist/components}/input/variants/input-radio.d.ts +0 -0
- /package/{components → dist/components}/input/variants/input-radio.d.ts.map +0 -0
- /package/{components → dist/components}/input/variants/input-radio.js +0 -0
- /package/{components → dist/components}/input/variants/input-radio.js.map +0 -0
- /package/{components → dist/components}/input/variants/input-range.d.ts +0 -0
- /package/{components → dist/components}/input/variants/input-range.d.ts.map +0 -0
- /package/{components → dist/components}/input/variants/input-range.js +0 -0
- /package/{components → dist/components}/input/variants/input-range.js.map +0 -0
- /package/{components → dist/components}/input/variants/input-text.d.ts +0 -0
- /package/{components → dist/components}/input/variants/input-text.d.ts.map +0 -0
- /package/{components → dist/components}/input/variants/input-text.js +0 -0
- /package/{components → dist/components}/input/variants/input-text.js.map +0 -0
- /package/{components → dist/components}/input/variants/input-textarea.d.ts +0 -0
- /package/{components → dist/components}/input/variants/input-textarea.d.ts.map +0 -0
- /package/{components → dist/components}/input/variants/input-textarea.js +0 -0
- /package/{components → dist/components}/input/variants/input-textarea.js.map +0 -0
- /package/{components → dist/components}/loading/index.d.ts +0 -0
- /package/{components → dist/components}/loading/index.d.ts.map +0 -0
- /package/{components → dist/components}/loading/loading.d.ts +0 -0
- /package/{components → dist/components}/loading/loading.d.ts.map +0 -0
- /package/{components → dist/components}/loading/loading.js +0 -0
- /package/{components → dist/components}/loading/loading.js.map +0 -0
- /package/{components → dist/components}/mansory/index.d.ts +0 -0
- /package/{components → dist/components}/mansory/index.d.ts.map +0 -0
- /package/{components → dist/components}/mansory/mansory.d.ts +0 -0
- /package/{components → dist/components}/mansory/mansory.d.ts.map +0 -0
- /package/{components → dist/components}/mansory/mansory.js +0 -0
- /package/{components → dist/components}/mansory/mansory.js.map +0 -0
- /package/{components → dist/components}/mansory/mansory.types.d.ts +0 -0
- /package/{components → dist/components}/mansory/mansory.types.d.ts.map +0 -0
- /package/{components → dist/components}/menu/menu.d.ts +0 -0
- /package/{components → dist/components}/menu/menu.d.ts.map +0 -0
- /package/{components → dist/components}/menu/menu.data-example.d.ts +0 -0
- /package/{components → dist/components}/menu/menu.data-example.d.ts.map +0 -0
- /package/{components → dist/components}/menu/menu.js +0 -0
- /package/{components → dist/components}/menu/menu.js.map +0 -0
- /package/{components → dist/components}/modal/index.d.ts +0 -0
- /package/{components → dist/components}/modal/index.d.ts.map +0 -0
- /package/{components → dist/components}/modal/modal.d.ts +0 -0
- /package/{components → dist/components}/modal/modal.d.ts.map +0 -0
- /package/{components → dist/components}/modal/modal.js +0 -0
- /package/{components → dist/components}/modal/modal.js.map +0 -0
- /package/{components → dist/components}/modal/modalContext.d.ts +0 -0
- /package/{components → dist/components}/modal/modalContext.d.ts.map +0 -0
- /package/{components → dist/components}/modal/modalContext.js +0 -0
- /package/{components → dist/components}/modal/modalContext.js.map +0 -0
- /package/{components → dist/components}/pagination/index.d.ts +0 -0
- /package/{components → dist/components}/pagination/index.d.ts.map +0 -0
- /package/{components → dist/components}/pagination/pagination.d.ts +0 -0
- /package/{components → dist/components}/pagination/pagination.d.ts.map +0 -0
- /package/{components → dist/components}/pagination/pagination.js +0 -0
- /package/{components → dist/components}/pagination/pagination.js.map +0 -0
- /package/{components → dist/components}/pagination/pagination.types.d.ts +0 -0
- /package/{components → dist/components}/pagination/pagination.types.d.ts.map +0 -0
- /package/{components → dist/components}/progress-bar/index.d.ts +0 -0
- /package/{components → dist/components}/progress-bar/index.d.ts.map +0 -0
- /package/{components → dist/components}/progress-bar/progress-bar.d.ts +0 -0
- /package/{components → dist/components}/progress-bar/progress-bar.d.ts.map +0 -0
- /package/{components → dist/components}/progress-bar/progress-bar.js +0 -0
- /package/{components → dist/components}/progress-bar/progress-bar.js.map +0 -0
- /package/{components → dist/components}/progress-bar/progress-bar.types.d.ts +0 -0
- /package/{components → dist/components}/progress-bar/progress-bar.types.d.ts.map +0 -0
- /package/{components → dist/components}/qr-code/index.d.ts +0 -0
- /package/{components → dist/components}/qr-code/index.d.ts.map +0 -0
- /package/{components → dist/components}/qr-code/qr-code.d.ts +0 -0
- /package/{components → dist/components}/qr-code/qr-code.d.ts.map +0 -0
- /package/{components → dist/components}/qr-code/qr-code.js +0 -0
- /package/{components → dist/components}/qr-code/qr-code.js.map +0 -0
- /package/{components → dist/components}/qr-code/qr-code.types.d.ts +0 -0
- /package/{components → dist/components}/qr-code/qr-code.types.d.ts.map +0 -0
- /package/{components → dist/components}/rating/rating.d.ts +0 -0
- /package/{components → dist/components}/rating/rating.d.ts.map +0 -0
- /package/{components → dist/components}/rating/rating.js +0 -0
- /package/{components → dist/components}/rating/rating.js.map +0 -0
- /package/{components → dist/components}/rating/rating.types.d.ts +0 -0
- /package/{components → dist/components}/rating/rating.types.d.ts.map +0 -0
- /package/{components → dist/components}/select/index.d.ts +0 -0
- /package/{components → dist/components}/select/index.d.ts.map +0 -0
- /package/{components → dist/components}/select/select.d.ts +0 -0
- /package/{components → dist/components}/select/select.d.ts.map +0 -0
- /package/{components → dist/components}/select/select.js +0 -0
- /package/{components → dist/components}/select/select.js.map +0 -0
- /package/{components → dist/components}/select/select.types.d.ts +0 -0
- /package/{components → dist/components}/select/select.types.d.ts.map +0 -0
- /package/{components → dist/components}/select-zone/index.d.ts +0 -0
- /package/{components → dist/components}/select-zone/index.d.ts.map +0 -0
- /package/{components → dist/components}/select-zone/select-zone.d.ts +0 -0
- /package/{components → dist/components}/select-zone/select-zone.d.ts.map +0 -0
- /package/{components → dist/components}/select-zone/select-zone.js +0 -0
- /package/{components → dist/components}/select-zone/select-zone.js.map +0 -0
- /package/{components → dist/components}/select-zone/select-zone.types.d.ts +0 -0
- /package/{components → dist/components}/select-zone/select-zone.types.d.ts.map +0 -0
- /package/{components → dist/components}/skeleton/index.d.ts +0 -0
- /package/{components → dist/components}/skeleton/index.d.ts.map +0 -0
- /package/{components → dist/components}/skeleton/skeleton.d.ts +0 -0
- /package/{components → dist/components}/skeleton/skeleton.d.ts.map +0 -0
- /package/{components → dist/components}/skeleton/skeleton.js +0 -0
- /package/{components → dist/components}/skeleton/skeleton.js.map +0 -0
- /package/{components → dist/components}/slider/slider.d.ts +0 -0
- /package/{components → dist/components}/slider/slider.d.ts.map +0 -0
- /package/{components → dist/components}/slider/slider.js +0 -0
- /package/{components → dist/components}/slider/slider.js.map +0 -0
- /package/{components → dist/components}/slider/slider.types.d.ts +0 -0
- /package/{components → dist/components}/slider/slider.types.d.ts.map +0 -0
- /package/{components → dist/components}/splitter/index.d.ts +0 -0
- /package/{components → dist/components}/splitter/index.d.ts.map +0 -0
- /package/{components → dist/components}/splitter/splitter.d.ts +0 -0
- /package/{components → dist/components}/splitter/splitter.d.ts.map +0 -0
- /package/{components → dist/components}/splitter/splitter.js +0 -0
- /package/{components → dist/components}/splitter/splitter.js.map +0 -0
- /package/{components → dist/components}/splitter/splitter.types.d.ts +0 -0
- /package/{components → dist/components}/splitter/splitter.types.d.ts.map +0 -0
- /package/{components → dist/components}/steps/index.d.ts +0 -0
- /package/{components → dist/components}/steps/index.d.ts.map +0 -0
- /package/{components → dist/components}/steps/steps.d.ts +0 -0
- /package/{components → dist/components}/steps/steps.d.ts.map +0 -0
- /package/{components → dist/components}/steps/steps.js +0 -0
- /package/{components → dist/components}/steps/steps.js.map +0 -0
- /package/{components → dist/components}/swap/index.d.ts +0 -0
- /package/{components → dist/components}/swap/index.d.ts.map +0 -0
- /package/{components → dist/components}/swap/swap.d.ts +0 -0
- /package/{components → dist/components}/swap/swap.d.ts.map +0 -0
- /package/{components → dist/components}/swap/swap.js +0 -0
- /package/{components → dist/components}/swap/swap.js.map +0 -0
- /package/{components → dist/components}/switch/index.d.ts +0 -0
- /package/{components → dist/components}/switch/index.d.ts.map +0 -0
- /package/{components → dist/components}/switch/switch.d.ts +0 -0
- /package/{components → dist/components}/switch/switch.d.ts.map +0 -0
- /package/{components → dist/components}/switch/switch.js +0 -0
- /package/{components → dist/components}/switch/switch.js.map +0 -0
- /package/{components → dist/components}/switch/switch.types.d.ts +0 -0
- /package/{components → dist/components}/switch/switch.types.d.ts.map +0 -0
- /package/{components → dist/components}/tab/index.d.ts +0 -0
- /package/{components → dist/components}/tab/index.d.ts.map +0 -0
- /package/{components → dist/components}/tab/tab.d.ts +0 -0
- /package/{components → dist/components}/tab/tab.d.ts.map +0 -0
- /package/{components → dist/components}/tab/tab.js +0 -0
- /package/{components → dist/components}/tab/tab.js.map +0 -0
- /package/{components → dist/components}/tab/tab.types.d.ts +0 -0
- /package/{components → dist/components}/tab/tab.types.d.ts.map +0 -0
- /package/{components → dist/components}/table/index.d.ts +0 -0
- /package/{components → dist/components}/table/index.d.ts.map +0 -0
- /package/{components → dist/components}/table/index.js +0 -0
- /package/{components → dist/components}/table/table.d.ts +0 -0
- /package/{components → dist/components}/table/table.d.ts.map +0 -0
- /package/{components → dist/components}/table/table.js +0 -0
- /package/{components → dist/components}/table/table.js.map +0 -0
- /package/{components → dist/components}/table/table.types.d.ts +0 -0
- /package/{components → dist/components}/table/table.types.d.ts.map +0 -0
- /package/{components → dist/components}/text-rotate/index.d.ts +0 -0
- /package/{components → dist/components}/text-rotate/index.d.ts.map +0 -0
- /package/{components → dist/components}/text-rotate/text-rotate.d.ts +0 -0
- /package/{components → dist/components}/text-rotate/text-rotate.d.ts.map +0 -0
- /package/{components → dist/components}/text-rotate/text-rotate.js +0 -0
- /package/{components → dist/components}/text-rotate/text-rotate.js.map +0 -0
- /package/{components → dist/components}/timeline/index.d.ts +0 -0
- /package/{components → dist/components}/timeline/index.d.ts.map +0 -0
- /package/{components → dist/components}/timeline/timeline.d.ts +0 -0
- /package/{components → dist/components}/timeline/timeline.d.ts.map +0 -0
- /package/{components → dist/components}/timeline/timeline.js +0 -0
- /package/{components → dist/components}/timeline/timeline.js.map +0 -0
- /package/{components → dist/components}/timeline/timeline.types.d.ts +0 -0
- /package/{components → dist/components}/timeline/timeline.types.d.ts.map +0 -0
- /package/{components → dist/components}/toast/icons/ErrorIcon.d.ts +0 -0
- /package/{components → dist/components}/toast/icons/ErrorIcon.d.ts.map +0 -0
- /package/{components → dist/components}/toast/icons/ErrorIcon.js +0 -0
- /package/{components → dist/components}/toast/icons/ErrorIcon.js.map +0 -0
- /package/{components → dist/components}/toast/icons/IconCircle.d.ts +0 -0
- /package/{components → dist/components}/toast/icons/IconCircle.d.ts.map +0 -0
- /package/{components → dist/components}/toast/icons/IconCircle.js +0 -0
- /package/{components → dist/components}/toast/icons/IconCircle.js.map +0 -0
- /package/{components → dist/components}/toast/icons/InfoIcon.d.ts +0 -0
- /package/{components → dist/components}/toast/icons/InfoIcon.d.ts.map +0 -0
- /package/{components → dist/components}/toast/icons/InfoIcon.js +0 -0
- /package/{components → dist/components}/toast/icons/InfoIcon.js.map +0 -0
- /package/{components → dist/components}/toast/icons/LoaderIcon.d.ts +0 -0
- /package/{components → dist/components}/toast/icons/LoaderIcon.d.ts.map +0 -0
- /package/{components → dist/components}/toast/icons/LoaderIcon.js +0 -0
- /package/{components → dist/components}/toast/icons/LoaderIcon.js.map +0 -0
- /package/{components → dist/components}/toast/icons/SuccessIcon.d.ts +0 -0
- /package/{components → dist/components}/toast/icons/SuccessIcon.d.ts.map +0 -0
- /package/{components → dist/components}/toast/icons/SuccessIcon.js +0 -0
- /package/{components → dist/components}/toast/icons/SuccessIcon.js.map +0 -0
- /package/{components → dist/components}/toast/icons/WarningIcon.d.ts +0 -0
- /package/{components → dist/components}/toast/icons/WarningIcon.d.ts.map +0 -0
- /package/{components → dist/components}/toast/icons/WarningIcon.js +0 -0
- /package/{components → dist/components}/toast/icons/WarningIcon.js.map +0 -0
- /package/{components → dist/components}/toast/icons/index.d.ts +0 -0
- /package/{components → dist/components}/toast/icons/index.d.ts.map +0 -0
- /package/{components → dist/components}/toast/index.d.ts +0 -0
- /package/{components → dist/components}/toast/index.d.ts.map +0 -0
- /package/{components → dist/components}/toast/toast.d.ts +0 -0
- /package/{components → dist/components}/toast/toast.d.ts.map +0 -0
- /package/{components → dist/components}/toast/toast.js +0 -0
- /package/{components → dist/components}/toast/toast.js.map +0 -0
- /package/{components → dist/components}/toast/toast.store.d.ts +0 -0
- /package/{components → dist/components}/toast/toast.store.d.ts.map +0 -0
- /package/{components → dist/components}/toast/toast.store.js +0 -0
- /package/{components → dist/components}/toast/toast.store.js.map +0 -0
- /package/{components → dist/components}/toast/toast.type.d.ts +0 -0
- /package/{components → dist/components}/toast/toast.type.d.ts.map +0 -0
- /package/{components → dist/components}/tooltip/index.d.ts +0 -0
- /package/{components → dist/components}/tooltip/index.d.ts.map +0 -0
- /package/{components → dist/components}/tooltip/tooltip.d.ts +0 -0
- /package/{components → dist/components}/tooltip/tooltip.d.ts.map +0 -0
- /package/{components → dist/components}/tooltip/tooltip.js +0 -0
- /package/{components → dist/components}/tooltip/tooltip.js.map +0 -0
- /package/{components → dist/components}/tooltip/tooltip.types.d.ts +0 -0
- /package/{components → dist/components}/tooltip/tooltip.types.d.ts.map +0 -0
- /package/{components → dist/components}/tour/index.d.ts +0 -0
- /package/{components → dist/components}/tour/index.d.ts.map +0 -0
- /package/{components → dist/components}/tour/tour.d.ts +0 -0
- /package/{components → dist/components}/tour/tour.types.d.ts +0 -0
- /package/{components → dist/components}/tour/tour.types.d.ts.map +0 -0
- /package/{components → dist/components}/upload/index.d.ts +0 -0
- /package/{components → dist/components}/upload/index.d.ts.map +0 -0
- /package/{components → dist/components}/upload/upload.d.ts +0 -0
- /package/{components → dist/components}/upload/upload.d.ts.map +0 -0
- /package/{components → dist/components}/upload/upload.js +0 -0
- /package/{components → dist/components}/upload/upload.js.map +0 -0
- /package/{components → dist/components}/upload/upload.types.d.ts +0 -0
- /package/{components → dist/components}/upload/upload.types.d.ts.map +0 -0
- /package/{components → dist/components}/z-index/index.d.ts +0 -0
- /package/{components → dist/components}/z-index/index.d.ts.map +0 -0
- /package/{components → dist/components}/z-index/z-index.context.d.ts +0 -0
- /package/{components → dist/components}/z-index/z-index.context.d.ts.map +0 -0
- /package/{components → dist/components}/z-index/z-index.context.js +0 -0
- /package/{components → dist/components}/z-index/z-index.context.js.map +0 -0
- /package/{components → dist/components}/z-index/z-index.d.ts +0 -0
- /package/{components → dist/components}/z-index/z-index.d.ts.map +0 -0
- /package/{components → dist/components}/z-index/z-index.js +0 -0
- /package/{components → dist/components}/z-index/z-index.js.map +0 -0
- /package/{components → dist/components}/z-index/z-index.store.d.ts +0 -0
- /package/{components → dist/components}/z-index/z-index.store.d.ts.map +0 -0
- /package/{components → dist/components}/z-index/z-index.store.js +0 -0
- /package/{components → dist/components}/z-index/z-index.store.js.map +0 -0
- /package/{components → dist/components}/z-index/z-index.types.d.ts +0 -0
- /package/{components → dist/components}/z-index/z-index.types.d.ts.map +0 -0
- /package/{components → dist/components}/z-index/z-index.types.js +0 -0
- /package/{lib.d.ts → dist/lib.d.ts} +0 -0
- /package/{lib.d.ts.map → dist/lib.d.ts.map} +0 -0
- /package/{lib.js → dist/lib.js} +0 -0
- /package/{type.d.ts → dist/type.d.ts} +0 -0
- /package/{type.d.ts.map → dist/type.d.ts.map} +0 -0
- /package/{utils → dist/utils}/cn.d.ts +0 -0
- /package/{utils → dist/utils}/cn.d.ts.map +0 -0
- /package/{utils → dist/utils}/cn.js +0 -0
- /package/{utils → dist/utils}/cn.js.map +0 -0
- /package/{utils → dist/utils}/element-tracker.js +0 -0
- /package/{utils → dist/utils}/element-tracker.js.map +0 -0
- /package/{utils → dist/utils}/helper.d.ts +0 -0
- /package/{utils → dist/utils}/helper.d.ts.map +0 -0
- /package/{utils → dist/utils}/helper.js +0 -0
- /package/{utils → dist/utils}/helper.js.map +0 -0
- /package/{utils → dist/utils}/hoc.js +0 -0
- /package/{utils → dist/utils}/hoc.js.map +0 -0
|
@@ -0,0 +1,1407 @@
|
|
|
1
|
+
## COMPONENT IDENTITY
|
|
2
|
+
- **Import**: `import { Table, createColumnHelper } from 'solid-tom-ui';`
|
|
3
|
+
- **Exports**: `Table` (named export), `createColumnHelper` (re-exported from TanStack), all table type exports (type exports)
|
|
4
|
+
- **Framework**: SolidJS
|
|
5
|
+
- **Underlying library**: `@tanstack/solid-table` v8 + `@tanstack/solid-virtual` (optional virtual scrolling)
|
|
6
|
+
- **Purpose**: Feature-rich data table — virtual scrolling, expandable rows, column pinning, zebra striping, sort/filter, pagination, and row selection
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## STATIC FACTORY METHODS
|
|
11
|
+
|
|
12
|
+
`Table` has two static factory methods that generate `ColumnDef` objects:
|
|
13
|
+
|
|
14
|
+
### `Table.Selection<TData>(options?)`
|
|
15
|
+
|
|
16
|
+
Generates a **row selection column** (checkbox or radio). Place it anywhere in the `columns` array — only the first one is rendered (duplicates silently ignored).
|
|
17
|
+
|
|
18
|
+
**`TableSelectionOptions<TData>`**:
|
|
19
|
+
|
|
20
|
+
| Option | Type | Default | Description |
|
|
21
|
+
|---|---|---|---|
|
|
22
|
+
| `type` | `'checkbox' \| 'radio'` | `'checkbox'` | Multi-select or single-select |
|
|
23
|
+
| `title` | `JSXElement` | — | Custom header content (overrides auto select-all checkbox) |
|
|
24
|
+
| `width` | `string \| number` | `48` | Column width (px) |
|
|
25
|
+
| `hideSelectAll` | `boolean` | `false` | Hide select-all checkbox in header (checkbox type only) |
|
|
26
|
+
| `renderCell` | `(checked, record, index) => JSXElement` | — | Custom cell renderer; clicking the result toggles the row |
|
|
27
|
+
|
|
28
|
+
**Behavior**:
|
|
29
|
+
- `type='checkbox'` → renders `<input type="checkbox">` per cell; header renders select-all checkbox (unless `hideSelectAll` or `title` set)
|
|
30
|
+
- `type='radio'` → renders `<input type="radio">` per cell; forces `enableMultiRowSelection=false` internally; header is empty
|
|
31
|
+
- Presence of `Table.Selection()` in columns **auto-enables** `enableRowSelection` (no need to pass the prop separately)
|
|
32
|
+
- Column id: `__selection__` (internal, do not rely on it for external logic)
|
|
33
|
+
|
|
34
|
+
### `Table.Expanded<TData>(options)`
|
|
35
|
+
|
|
36
|
+
Generates a **row expand toggle column**. Place it anywhere in the `columns` array. Required option: `expandedRowRender`.
|
|
37
|
+
|
|
38
|
+
**`TableExpandedOptions<TData>`**:
|
|
39
|
+
|
|
40
|
+
| Option | Type | Default | Description |
|
|
41
|
+
|---|---|---|---|
|
|
42
|
+
| `expandedRowRender` | `(record, index, indent, expanded) => JSX.Element \| null` | **required** | Render the expanded row content |
|
|
43
|
+
| `rowExpandable` | `(record, index) => boolean` | — | When omitted, all rows are expandable |
|
|
44
|
+
| `expandIcon` | `(expanded: boolean) => JSX.Element` | — | Custom icon; receives current expanded state. When omitted, uses a rotating `chevron-right` |
|
|
45
|
+
| `width` | `number` | `48` | Column width (px) |
|
|
46
|
+
|
|
47
|
+
**Behavior**:
|
|
48
|
+
- Column id: `__expanded__` (internal)
|
|
49
|
+
- Clicking the expand cell (or the icon) toggles the row
|
|
50
|
+
- Expanded content renders in a separate `<tr>` spanning all columns, below the data row
|
|
51
|
+
- `rowExpandable` returning `false` hides the icon and prevents toggling for that row
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## PROP REFERENCE
|
|
56
|
+
|
|
57
|
+
### Core data props
|
|
58
|
+
|
|
59
|
+
| Prop | Type | Default | Description |
|
|
60
|
+
|---|---|---|---|
|
|
61
|
+
| `columns` | `ColumnDef<TData, any>[]` | **required** | Column definitions. Use `createColumnHelper` for type safety. |
|
|
62
|
+
| `data` | `TData[]` | **required** | Table rows data. |
|
|
63
|
+
| `options` | `Partial<Omit<TableOptions<TData>, 'data' \| 'columns' \| 'getCoreRowModel'>>` | — | Pass-through for any TanStack table option not exposed as a prop (e.g. custom row models, meta). |
|
|
64
|
+
| `onTableReady` | `(table: TableInstance<TData>) => void` | — | Receives the TanStack `table` instance after creation. Use to access full TanStack API. |
|
|
65
|
+
| `getRowId` | `(row, index, parent?) => string` | — | Custom row ID. Required when using selection/expand with non-index IDs (e.g. `row => String(row.id)`). |
|
|
66
|
+
|
|
67
|
+
### Style props
|
|
68
|
+
|
|
69
|
+
| Prop | Type | Default | Description |
|
|
70
|
+
|---|---|---|---|
|
|
71
|
+
| `size` | `'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl'` | `'md'` | Cell padding + font size variant |
|
|
72
|
+
| `zebra` | `boolean \| { rows?: boolean; cols?: boolean }` | `{ rows: true }` | Zebra striping. `true` → `{ rows: true, cols: false }` (rows only). `false` or omit → disabled. |
|
|
73
|
+
| `border` | `boolean \| { outer?: boolean; horizontal?: boolean; vertical?: boolean }` | — | Border control. `true` → all borders. `false` → no borders. Object for granular control. |
|
|
74
|
+
| `rounded` | `string` | `'rounded-sm'` | Tailwind rounded class applied to root. Also sets `overflow-hidden`. Pass `''` to disable. |
|
|
75
|
+
| `resizable` | `boolean` | `true` | Enable drag-to-resize columns via overlay handles (data columns only, not selection/expand columns). |
|
|
76
|
+
| `showHeader` | `boolean` | `true` | Show or hide the table header (`<thead>`). Set to `false` to render the table without any column header row. |
|
|
77
|
+
| `color` | `BaseColorProps` | `'info'` | Theme color token used for hover/highlight/sort icon colors via CSS custom properties. |
|
|
78
|
+
| `scroll` | `{ x?: string \| number \| true; y?: string \| number }` | — | Scroll config. See scroll section below. |
|
|
79
|
+
| `virtual` | `boolean \| TableVirtualOptions` | — | Enable virtual scrolling for large datasets. Requires `scroll.y`. See virtual section below. |
|
|
80
|
+
|
|
81
|
+
### Slot / render props
|
|
82
|
+
|
|
83
|
+
| Prop | Type | Description |
|
|
84
|
+
|---|---|---|
|
|
85
|
+
| `headerPanel` | `() => JSXElement` | Renders above the table (toolbar, title, filters, etc.). Has its own panel div with optional border. |
|
|
86
|
+
| `footerPanel` | `() => JSXElement` | Renders below the table (pagination, summary, etc.). Has its own panel div. |
|
|
87
|
+
| `emptyContent` | `JSXElement` | Content shown when `data` is empty. Default: `"No data"`. |
|
|
88
|
+
| `class` | `TableClassNames` | Custom class names per semantic part. See `TableClassNames` below. |
|
|
89
|
+
| `onRowClick` | `(row: Row<TData>, e: MouseEvent) => void` | Fired on body row click. Does NOT fire when clicking selection or expand cells. |
|
|
90
|
+
| `onCell` | `(cell: Cell<TData, unknown>, rowIndex: number) => TableCellSpanResult` | Table-level callback to control `colSpan`/`rowSpan` for body cells. Per-column `meta.onCell` takes priority. Only applies in non-virtual mode. |
|
|
91
|
+
|
|
92
|
+
### Row selection props
|
|
93
|
+
|
|
94
|
+
| Prop | Type | Description |
|
|
95
|
+
|---|---|---|
|
|
96
|
+
| `enableRowSelection` | `boolean \| ((row) => boolean)` | Enable selection. Auto-set to `true` when `Table.Selection()` column is present. |
|
|
97
|
+
| `rowSelection` | `RowSelectionState` | Controlled selection state. Shape: `{ [rowId: string]: boolean }` |
|
|
98
|
+
| `onRowSelectionChange` | `OnChangeFn<RowSelectionState>` | Called when selection changes (TanStack updater function). |
|
|
99
|
+
|
|
100
|
+
### Sorting props
|
|
101
|
+
|
|
102
|
+
| Prop | Type | Default | Description |
|
|
103
|
+
|---|---|---|---|
|
|
104
|
+
| `enableSorting` | `boolean` | `false` | Enable column sorting. Must be `true` for any sort to work. |
|
|
105
|
+
| `sorting` | `SortingState` | — | Controlled sort state. Shape: `[{ id: string; desc: boolean }]` |
|
|
106
|
+
| `onSortingChange` | `OnChangeFn<SortingState>` | — | Called when sort changes. |
|
|
107
|
+
|
|
108
|
+
**Multi-sort behavior**: Every header click accumulates sort columns (equivalent to always holding Shift). Clicking a sorted column cycles through: unsorted → asc → desc → unsorted. Multiple columns can be sorted simultaneously. The priority badge (1, 2, 3...) is shown on each sorted column when more than one column is sorted.
|
|
109
|
+
|
|
110
|
+
### Filtering props
|
|
111
|
+
|
|
112
|
+
| Prop | Type | Default | Description |
|
|
113
|
+
|---|---|---|---|
|
|
114
|
+
| `enableColumnFilters` | `boolean` | `false` | Enable column-level filtering. |
|
|
115
|
+
| `columnFilters` | `ColumnFiltersState` | — | Controlled column filter state. |
|
|
116
|
+
| `onColumnFiltersChange` | `OnChangeFn<ColumnFiltersState>` | — | Called when column filters change. |
|
|
117
|
+
| `globalFilter` | `string` | — | Global filter string. |
|
|
118
|
+
| `onGlobalFilterChange` | `OnChangeFn<string>` | — | Called when global filter changes. |
|
|
119
|
+
|
|
120
|
+
### Visibility props
|
|
121
|
+
|
|
122
|
+
| Prop | Type | Description |
|
|
123
|
+
|---|---|---|
|
|
124
|
+
| `columnVisibility` | `VisibilityState` | Controlled column visibility. Shape: `{ [colId: string]: boolean }` |
|
|
125
|
+
| `onColumnVisibilityChange` | `OnChangeFn<VisibilityState>` | Called when visibility changes. |
|
|
126
|
+
|
|
127
|
+
### Pagination props
|
|
128
|
+
|
|
129
|
+
| Prop | Type | Default | Description |
|
|
130
|
+
|---|---|---|---|
|
|
131
|
+
| `pagination` | `PaginationState` | — | Initial/controlled pagination state. Shape: `{ pageIndex: number; pageSize: number }`. Used to seed or override the table's internal page state. |
|
|
132
|
+
| `paginationProps` | `PaginationComponentProps` | — | When provided, enables client-side pagination and renders a `<Pagination>` component below the table. See details below. |
|
|
133
|
+
|
|
134
|
+
**`paginationProps`** is `Omit<PaginationProps, 'current' | 'pageSize' | 'defaultCurrent' | 'defaultPageSize'> & { total: number; position?: 'start' | 'end' }`:
|
|
135
|
+
|
|
136
|
+
| Option | Type | Description |
|
|
137
|
+
|---|---|---|
|
|
138
|
+
| `total` | `number` | **required** — Total number of data items (used to compute page count). |
|
|
139
|
+
| `position` | `'start' \| 'end'` | Position of the Pagination component relative to the table. `'start'` = above table, `'end'` = below table. Default: `'end'`. |
|
|
140
|
+
| `onChange` | `(page, size) => void` | Called after the table's internal page/size state has already been updated. Use for server-side pagination to fetch the new page. |
|
|
141
|
+
| `showSizeChanger` | `boolean` | Show page size selector. |
|
|
142
|
+
| `showQuickJumper` | `boolean` | Show "go to page" input. |
|
|
143
|
+
| `size` | `PaginationSize` | Size variant for the Pagination component. |
|
|
144
|
+
| `disabled` | `boolean` | Disable pagination controls. |
|
|
145
|
+
| `...` | `PaginationProps` | Any other `PaginationProps` except `current`, `pageSize`, `defaultCurrent`, `defaultPageSize` (wired automatically). |
|
|
146
|
+
|
|
147
|
+
**Notes**:
|
|
148
|
+
- `current` and `pageSize` in the `<Pagination>` component are wired automatically from table state — do not pass them
|
|
149
|
+
- For **client-side pagination**: pass all `data` rows; the table slices them automatically
|
|
150
|
+
- For **server-side pagination**: pass only the current page's rows as `data`, and use `paginationProps.onChange` to fetch the new page
|
|
151
|
+
- The `<Pagination>` component renders outside the table border (below `sui-table-root`)
|
|
152
|
+
|
|
153
|
+
### Expanded rows props (state management)
|
|
154
|
+
|
|
155
|
+
| Prop | Type | Description |
|
|
156
|
+
|---|---|---|
|
|
157
|
+
| `defaultExpandAllRows` | `boolean` | Expand all rows on first mount (uncontrolled; applied once). |
|
|
158
|
+
| `defaultExpandedRowKeys` | `string[]` | Initial expanded row keys (uncontrolled). |
|
|
159
|
+
| `expandedRowKeys` | `string[]` | Controlled list of expanded row keys. |
|
|
160
|
+
| `onExpandedRowsChange` | `(expandedKeys: string[]) => void` | Called when the set of expanded keys changes. |
|
|
161
|
+
| `onExpand` | `(expanded: boolean, record: TData) => void` | Called when a single row's expand icon is clicked. |
|
|
162
|
+
|
|
163
|
+
### Infinite scroll props (virtual mode only)
|
|
164
|
+
|
|
165
|
+
| Prop | Type | Description |
|
|
166
|
+
|---|---|---|
|
|
167
|
+
| `getVirtualData` | `() => Promise<TData[]>` | Called when the user scrolls near the end of the virtual list (auto mode) or clicks "Load more" (manual mode). Must return a Promise resolving with the next page of data. The resolved rows are automatically appended to the internal data. Only fires when `virtual` is set. |
|
|
168
|
+
| `loadingMore` | `'none' \| 'auto' \| 'manual'` | Controls behavior and loading indicator at the end of the virtual list. `'none'`: no indicator, no action (default). `'auto'`: shows a spinner; `getVirtualData` is called automatically when scrolling near the end. `'manual'`: shows a "Load more" button; `getVirtualData` is only called when the user clicks the button. Only applies in virtual mode. |
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## `TableClassNames` — per-part class customization
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
interface TableClassNames {
|
|
176
|
+
root?: string; // root <div> wrapping everything
|
|
177
|
+
headerPanel?: string; // headerPanel slot wrapper
|
|
178
|
+
table?: string; // <table> element
|
|
179
|
+
thead?: string; // <thead>
|
|
180
|
+
tbody?: string; // <tbody>
|
|
181
|
+
tfoot?: string; // footerPanel wrapper (reuses tfoot key)
|
|
182
|
+
tr?: string; // every <tr>
|
|
183
|
+
th?: string; // every <th>
|
|
184
|
+
td?: string; // every <td>
|
|
185
|
+
headerRow?: string; // <tr> in thead only
|
|
186
|
+
bodyRow?: string; // <tr> in tbody only
|
|
187
|
+
footerRow?: string; // (reserved, not used for tfoot tr)
|
|
188
|
+
empty?: string; // empty state <td>
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## SCROLL CONFIGURATION
|
|
195
|
+
|
|
196
|
+
`scroll.x` — horizontal scrolling:
|
|
197
|
+
|
|
198
|
+
| Value | Behavior |
|
|
199
|
+
|---|---|
|
|
200
|
+
| `number` | Wrapper `max-width` = `${n}px`; table scrolls inside |
|
|
201
|
+
| `string` (e.g. `'600px'`) | Wrapper `max-width` = value |
|
|
202
|
+
| `true` | Wrapper inherits parent width; table `min-width` = sum of all column sizes |
|
|
203
|
+
| `'max-content'` | Table `width` = `max-content`; wrapper scrolls |
|
|
204
|
+
| omitted | No horizontal scroll constraint |
|
|
205
|
+
|
|
206
|
+
`scroll.y` — vertical scrolling:
|
|
207
|
+
|
|
208
|
+
| Value | Behavior |
|
|
209
|
+
|---|---|
|
|
210
|
+
| `number` | Wrapper `max-height` = `${n}px`; `<thead>` becomes sticky (fixed header) |
|
|
211
|
+
| `string` | Wrapper `max-height` = value |
|
|
212
|
+
| omitted | No vertical scroll |
|
|
213
|
+
|
|
214
|
+
**Note**: When `scroll.y` is set OR any column has `meta.fixed`, `thead tr` becomes `position: sticky; top: 0`.
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## VIRTUAL SCROLLING
|
|
219
|
+
|
|
220
|
+
Virtual scrolling renders only the rows visible in the viewport instead of all rows, keeping DOM size and rendering cost constant regardless of dataset size. Powered by `@tanstack/solid-virtual`.
|
|
221
|
+
|
|
222
|
+
### Requirements
|
|
223
|
+
|
|
224
|
+
- `scroll.y` **must** be set to define the fixed-height viewport
|
|
225
|
+
- `virtual` prop must be set to `true` or a `TableVirtualOptions` object
|
|
226
|
+
|
|
227
|
+
### `TableVirtualOptions`
|
|
228
|
+
|
|
229
|
+
| Option | Type | Default | Description |
|
|
230
|
+
|---|---|---|---|
|
|
231
|
+
| `itemSize` | `number` | `40` | Estimated row height in pixels. Match to your `size` variant. |
|
|
232
|
+
| `overscan` | `number` | `5` | Extra rows rendered beyond the visible area (above + below). |
|
|
233
|
+
|
|
234
|
+
**Approximate row heights by size variant**:
|
|
235
|
+
|
|
236
|
+
| Size | Estimated height |
|
|
237
|
+
|---|---|
|
|
238
|
+
| `xs` | `26px` |
|
|
239
|
+
| `sm` | `33px` |
|
|
240
|
+
| `md` | `40px` (default) |
|
|
241
|
+
| `lg` | `48px` |
|
|
242
|
+
| `xl` | `58px` |
|
|
243
|
+
|
|
244
|
+
### How it works
|
|
245
|
+
|
|
246
|
+
- The scroll wrapper (`sui-table-wrapper`) becomes the virtualizer's scroll container
|
|
247
|
+
- Only visible rows (+ `overscan` buffer) are rendered in the DOM
|
|
248
|
+
- Top and bottom spacer `<tr>` elements fill the virtual space so the scrollbar reflects total height
|
|
249
|
+
- `measureElement` is called on each rendered row `<tr>` so the virtualizer can refine height estimates dynamically
|
|
250
|
+
|
|
251
|
+
### Virtual sort loading indicator
|
|
252
|
+
|
|
253
|
+
When `enableSorting` is set and the user clicks a sortable column header in virtual mode, sorting is executed asynchronously:
|
|
254
|
+
|
|
255
|
+
1. A full-height loading overlay (spinner + "Sorting" text) replaces the tbody rows immediately
|
|
256
|
+
2. A microtask yields to let the loading state render
|
|
257
|
+
3. The actual sort executes
|
|
258
|
+
4. The loading overlay is shown for at least 500ms total, then removed
|
|
259
|
+
5. The scroll position is reset to the top so the virtualizer renders the newly sorted rows
|
|
260
|
+
|
|
261
|
+
This prevents jank on large datasets where synchronous sorting would block the UI thread. Clicking a column header while sorting is in progress is a no-op.
|
|
262
|
+
|
|
263
|
+
### Infinite scroll
|
|
264
|
+
|
|
265
|
+
Use `getVirtualData` + `loadingMore` together to implement lazy-loading / infinite scroll on top of virtual scrolling.
|
|
266
|
+
|
|
267
|
+
**`loadingMore` values**:
|
|
268
|
+
- `'none'` (default) — no indicator shown; `getVirtualData` is never called automatically
|
|
269
|
+
- `'auto'` — renders a spinner row below the last virtual row when fetching; `getVirtualData` is triggered automatically when scrolling near the end or when content doesn't fill the viewport
|
|
270
|
+
- `'manual'` — renders a "Load more" button row; `getVirtualData` is only called when the user clicks the button (NOT on scroll)
|
|
271
|
+
|
|
272
|
+
**How `getVirtualData` works:**
|
|
273
|
+
- `getVirtualData` returns a `Promise<TData[]>` — the component calls it and automatically appends the resolved rows to the internal data
|
|
274
|
+
- The component tracks a loading state internally (`isFetchingMore`) to prevent concurrent calls
|
|
275
|
+
- The `data` prop serves as the **initial data**; subsequent pages are appended internally
|
|
276
|
+
- When the `data` prop reference changes, the internal data resets to the new prop value
|
|
277
|
+
|
|
278
|
+
**Auto-fill behavior**: When content is too short to fill the viewport (no scrollbar appears), the component automatically calls `getVirtualData` in a promise chain until enough rows are loaded to create a scrollbar. This only runs in `'auto'` mode (not `'manual'`).
|
|
279
|
+
|
|
280
|
+
**How scroll detection works internally:**
|
|
281
|
+
- A scroll listener is attached to the `wrapperEl` (scroll container) on mount, only when `virtual` is set
|
|
282
|
+
- In `'auto'` mode: fires `getVirtualData` when `scrollTop + clientHeight ≥ scrollHeight − threshold`, where `threshold = itemSize × overscan` (default: `40 × 5 = 200px`)
|
|
283
|
+
- In `'manual'` mode: no scroll listener fires `getVirtualData`; only the button click does
|
|
284
|
+
- In `'none'` mode: no action on scroll
|
|
285
|
+
|
|
286
|
+
**Implementation pattern (`'auto'` mode):**
|
|
287
|
+
```tsx
|
|
288
|
+
const initialData = fetchFirstPage(); // stable reference — do not recreate on each render
|
|
289
|
+
const [loadedCount, setLoadedCount] = createSignal(initialData.length);
|
|
290
|
+
const hasMore = () => loadedCount() < TOTAL;
|
|
291
|
+
|
|
292
|
+
const getVirtualData = () =>
|
|
293
|
+
fetchNextPage(loadedCount()).then(newRows => {
|
|
294
|
+
setLoadedCount(prev => prev + newRows.length);
|
|
295
|
+
return newRows; // component appends automatically
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
<Table
|
|
299
|
+
data={initialData}
|
|
300
|
+
getVirtualData={hasMore() ? getVirtualData : undefined}
|
|
301
|
+
loadingMore={hasMore() ? 'auto' : 'none'}
|
|
302
|
+
// ...
|
|
303
|
+
/>
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
**Implementation pattern (`'manual'` mode):**
|
|
307
|
+
```tsx
|
|
308
|
+
<Table
|
|
309
|
+
data={initialData}
|
|
310
|
+
getVirtualData={hasMore() ? getVirtualData : undefined}
|
|
311
|
+
loadingMore={hasMore() ? 'manual' : 'none'}
|
|
312
|
+
// ...
|
|
313
|
+
/>
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
**Rules:**
|
|
317
|
+
- Keep the `data` prop as a **stable reference** (e.g. a constant or a signal that only changes on explicit reset). The component resets internal data whenever `data` changes reference.
|
|
318
|
+
- `getRowId` is required to give each row a stable unique ID across pages
|
|
319
|
+
- Set `loadingMore='none'` (or omit `getVirtualData`) when all data is exhausted to hide the indicator
|
|
320
|
+
- The component prevents concurrent calls internally — no extra guard needed in the caller
|
|
321
|
+
|
|
322
|
+
### Limitations
|
|
323
|
+
|
|
324
|
+
- **Expanded rows are not supported** with virtual scrolling (variable heights break virtualizer accuracy)
|
|
325
|
+
- **`scroll.y` is required** — without a fixed viewport height, no scrolling occurs and virtual mode has no effect
|
|
326
|
+
- **`resizable={false}` is recommended** — column resizing and virtual mode can be used together but may cause minor visual glitches
|
|
327
|
+
|
|
328
|
+
### Example
|
|
329
|
+
|
|
330
|
+
```tsx
|
|
331
|
+
// Basic — 10,000 rows with defaults
|
|
332
|
+
<Table
|
|
333
|
+
columns={columns}
|
|
334
|
+
data={largeData}
|
|
335
|
+
scroll={{ y: 400 }}
|
|
336
|
+
virtual
|
|
337
|
+
border
|
|
338
|
+
size="md"
|
|
339
|
+
resizable={false}
|
|
340
|
+
getRowId={row => String(row.id)}
|
|
341
|
+
/>
|
|
342
|
+
|
|
343
|
+
// Custom options (lg size rows)
|
|
344
|
+
<Table
|
|
345
|
+
columns={columns}
|
|
346
|
+
data={largeData}
|
|
347
|
+
scroll={{ y: 400 }}
|
|
348
|
+
virtual={{ itemSize: 48, overscan: 10 }}
|
|
349
|
+
size="lg"
|
|
350
|
+
resizable={false}
|
|
351
|
+
/>
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
## COLUMN META EXTENSIONS
|
|
357
|
+
|
|
358
|
+
`ColumnDef.meta` supports several custom fields beyond TanStack's defaults:
|
|
359
|
+
|
|
360
|
+
### `meta.fixed` — sticky columns
|
|
361
|
+
|
|
362
|
+
```ts
|
|
363
|
+
meta: { fixed: 'start' } // sticky left
|
|
364
|
+
meta: { fixed: 'end' } // sticky right
|
|
365
|
+
meta: { fixed: true } // alias for 'start'
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
See [FIXED (STICKY) COLUMNS](#fixed-sticky-columns) below.
|
|
369
|
+
|
|
370
|
+
### `meta.align` — cell alignment
|
|
371
|
+
|
|
372
|
+
Horizontal alignment applied to **both** the header and body cells. Default: `'start'`.
|
|
373
|
+
|
|
374
|
+
```ts
|
|
375
|
+
meta: { align: 'start' | 'center' | 'end' }
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
```ts
|
|
379
|
+
col.accessor('score', {
|
|
380
|
+
header: 'Score',
|
|
381
|
+
meta: { align: 'end' }, // right-align header + cells
|
|
382
|
+
})
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
### `meta.tooltip` — cell tooltip
|
|
386
|
+
|
|
387
|
+
Wraps every body cell in a `Tooltip` component. Table automatically injects `usePortal=true` and `containerId` (the `<tbody>` element ID), so `position` defaults to `'top'` if not specified.
|
|
388
|
+
|
|
389
|
+
Type: `TableCellTooltipOptions` — same as `TooltipProps` but omits `children`, `usePortal`, `containerId`. The `content` field can be a **static string** or a **function** receiving the cell value.
|
|
390
|
+
|
|
391
|
+
```ts
|
|
392
|
+
meta: {
|
|
393
|
+
tooltip?: TableCellTooltipOptions
|
|
394
|
+
}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
```ts
|
|
398
|
+
col.accessor('email', {
|
|
399
|
+
header: 'Email',
|
|
400
|
+
meta: {
|
|
401
|
+
tooltip: {
|
|
402
|
+
content: (value) => `Send email to: ${value}`,
|
|
403
|
+
position: 'bottom',
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
})
|
|
407
|
+
|
|
408
|
+
// Static content:
|
|
409
|
+
col.accessor('notes', {
|
|
410
|
+
header: 'Notes',
|
|
411
|
+
meta: { tooltip: { content: 'Click to edit' } },
|
|
412
|
+
})
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### `meta.onCell` — per-column cell spanning
|
|
416
|
+
|
|
417
|
+
Controls `colSpan` and `rowSpan` for body cells in this column. **Takes priority** over the table-level `onCell` prop.
|
|
418
|
+
|
|
419
|
+
Return `{ colSpan: 0 }` or `{ rowSpan: 0 }` to hide a cell that is covered by a spanning neighbour.
|
|
420
|
+
|
|
421
|
+
```ts
|
|
422
|
+
meta: {
|
|
423
|
+
onCell?: (cell: Cell<TData, unknown>, rowIndex: number) => TableCellSpanResult
|
|
424
|
+
}
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
```ts
|
|
428
|
+
col.accessor('product', {
|
|
429
|
+
header: 'Product',
|
|
430
|
+
meta: {
|
|
431
|
+
onCell: (cell, rowIndex) => {
|
|
432
|
+
if (cell.getValue() === 'Widget B') return { colSpan: 3 };
|
|
433
|
+
return {};
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
})
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
### `meta.onHeaderCell` — per-column header spanning
|
|
440
|
+
|
|
441
|
+
Controls `colSpan` and `rowSpan` for the header cell (`<th>`) of this column. Overrides TanStack's auto-computed values.
|
|
442
|
+
|
|
443
|
+
```ts
|
|
444
|
+
meta: {
|
|
445
|
+
onHeaderCell?: (header: Header<TData, unknown>) => TableCellSpanResult
|
|
446
|
+
}
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
```ts
|
|
450
|
+
col.accessor('name', {
|
|
451
|
+
header: 'Full Name',
|
|
452
|
+
meta: {
|
|
453
|
+
onHeaderCell: (header) => ({ colSpan: 2 }),
|
|
454
|
+
},
|
|
455
|
+
})
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
---
|
|
459
|
+
|
|
460
|
+
## FIXED (STICKY) COLUMNS
|
|
461
|
+
|
|
462
|
+
Set `meta.fixed` on any `ColumnDef`:
|
|
463
|
+
|
|
464
|
+
```ts
|
|
465
|
+
meta: { fixed: 'start' } // sticky left
|
|
466
|
+
meta: { fixed: 'end' } // sticky right
|
|
467
|
+
meta: { fixed: true } // alias for 'start'
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
**Rules**:
|
|
471
|
+
- Offsets are computed automatically by measuring DOM widths via `ResizeObserver`
|
|
472
|
+
- The rightmost `'start'` column gets a drop-shadow on its right edge
|
|
473
|
+
- The leftmost `'end'` column gets a drop-shadow on its left edge
|
|
474
|
+
- Fixed columns require `scroll.x` to be set, otherwise they overlap content
|
|
475
|
+
- `resizable` should be `false` when using fixed columns (set `resizable={false}`)
|
|
476
|
+
|
|
477
|
+
---
|
|
478
|
+
|
|
479
|
+
## COLUMN RESIZING
|
|
480
|
+
|
|
481
|
+
- Enabled by default (`resizable={true}`)
|
|
482
|
+
- Overlay `<div>` handles are positioned at each column's right border
|
|
483
|
+
- Drag starts `mousemove` listener on `document`; drag end cleans up
|
|
484
|
+
- Minimum column width: `40px`
|
|
485
|
+
- Only data columns are resizable (`__selection__` and `__expanded__` are excluded)
|
|
486
|
+
- When any column has been resized, `table-fixed` CSS layout is activated and `<colgroup>` drives widths
|
|
487
|
+
- The last data column always takes remaining space (no explicit width applied)
|
|
488
|
+
|
|
489
|
+
---
|
|
490
|
+
|
|
491
|
+
## EVENT DELEGATION
|
|
492
|
+
|
|
493
|
+
The component uses a **single delegated `onClick`** on `<table>` instead of per-cell handlers:
|
|
494
|
+
|
|
495
|
+
1. Click on `th[data-column-id]` → triggers sorting toggle for that column
|
|
496
|
+
2. Click on `td.sui-table-selection-cell` → toggles row selection
|
|
497
|
+
3. Click on `td.sui-table-expand-cell` → toggles row expansion
|
|
498
|
+
4. Click on `tr[data-row-id]` (body) → fires `onRowClick` prop
|
|
499
|
+
5. Priority: selection cell > expand cell > body row (each returns early)
|
|
500
|
+
|
|
501
|
+
---
|
|
502
|
+
|
|
503
|
+
## BEHAVIORAL RULES
|
|
504
|
+
|
|
505
|
+
### Controlled vs Uncontrolled for sorting/selection/pagination
|
|
506
|
+
- All TanStack features follow the same pattern: pass `state.*` + `on*Change` for controlled, or omit both for uncontrolled (TanStack manages internally)
|
|
507
|
+
- The component passes controlled state via reactive getters to TanStack's `createSolidTable` (not snapshots), so signal changes propagate correctly
|
|
508
|
+
|
|
509
|
+
### Multi-sort always enabled
|
|
510
|
+
- Every column header click is treated as a multi-sort event (`isMultiSortEvent: () => true` is hardcoded)
|
|
511
|
+
- Clicking a column adds it to the sort stack; clicking again cycles it (asc → desc → unsorted)
|
|
512
|
+
- There is no way to sort by a single column exclusively — use `options={{ isMultiSortEvent: () => false }}` to override if needed
|
|
513
|
+
|
|
514
|
+
### Expandable rows — controlled vs uncontrolled
|
|
515
|
+
- **Uncontrolled**: omit `expandedRowKeys`. Use `defaultExpandedRowKeys` for initial state. `defaultExpandAllRows` expands all rows once on mount.
|
|
516
|
+
- **Controlled**: pass `expandedRowKeys` + `onExpandedRowsChange`. You must update the signal yourself.
|
|
517
|
+
- Row IDs used for expansion are TanStack row IDs (auto-indexed `"0"`, `"1"`, ... unless `getRowId` is provided)
|
|
518
|
+
|
|
519
|
+
### Selection column auto-wiring
|
|
520
|
+
- If `Table.Selection()` is present in `columns`, `enableRowSelection` is automatically set to `true`
|
|
521
|
+
- For `type='radio'`, `enableMultiRowSelection` is automatically set to `false`
|
|
522
|
+
- Only the **first** `Table.Selection()` in the array is rendered; additional ones are silently dropped
|
|
523
|
+
|
|
524
|
+
### Column `size` and `table-fixed` layout
|
|
525
|
+
- If any `ColumnDef` has an explicit `size` field, `table-fixed` layout + `<colgroup>` is activated
|
|
526
|
+
- Without explicit sizes, columns share width equally (browser default)
|
|
527
|
+
- Sized body cells render content inside `<div class="sui-table-sized-cell">` (truncates overflow with ellipsis)
|
|
528
|
+
|
|
529
|
+
### Pagination with `paginationProps`
|
|
530
|
+
- Providing `paginationProps` automatically enables `getPaginationRowModel` (client-side pagination)
|
|
531
|
+
- The `<Pagination>` component renders outside `sui-table-root`, inside `sui-table-outer`
|
|
532
|
+
- `current` and `pageSize` are wired from internal table state; do not pass them in `paginationProps`
|
|
533
|
+
- `paginationProps.onChange(page, size)` is called **after** the table's internal state has already been updated
|
|
534
|
+
|
|
535
|
+
---
|
|
536
|
+
|
|
537
|
+
## USAGE PATTERNS
|
|
538
|
+
|
|
539
|
+
### 1. Minimal table
|
|
540
|
+
|
|
541
|
+
```tsx
|
|
542
|
+
import { Table, createColumnHelper } from 'solid-tom-ui';
|
|
543
|
+
|
|
544
|
+
type Person = { name: string; age: number };
|
|
545
|
+
const col = createColumnHelper<Person>();
|
|
546
|
+
|
|
547
|
+
const columns = [
|
|
548
|
+
col.accessor('name', { header: 'Name' }),
|
|
549
|
+
col.accessor('age', { header: 'Age' }),
|
|
550
|
+
];
|
|
551
|
+
|
|
552
|
+
const data: Person[] = [{ name: 'Alice', age: 30 }];
|
|
553
|
+
|
|
554
|
+
<Table columns={columns} data={data} />
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
### 2. Sortable table (controlled)
|
|
558
|
+
|
|
559
|
+
```tsx
|
|
560
|
+
const [sorting, setSorting] = createSignal<SortingState>([]);
|
|
561
|
+
|
|
562
|
+
<Table
|
|
563
|
+
columns={columns}
|
|
564
|
+
data={data}
|
|
565
|
+
enableSorting
|
|
566
|
+
sorting={sorting()}
|
|
567
|
+
onSortingChange={setSorting}
|
|
568
|
+
/>
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
Per column, disable sorting:
|
|
572
|
+
```ts
|
|
573
|
+
col.accessor('id', { header: '#', enableSorting: false })
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
Note: Every sort click is multi-sort by default. To disable multi-sort, use:
|
|
577
|
+
```tsx
|
|
578
|
+
<Table options={{ isMultiSortEvent: () => false }} ... />
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
### 3. Row selection — checkbox (controlled)
|
|
582
|
+
|
|
583
|
+
```tsx
|
|
584
|
+
const [rowSelection, setRowSelection] = createSignal<RowSelectionState>({});
|
|
585
|
+
|
|
586
|
+
const columns = [
|
|
587
|
+
Table.Selection({ type: 'checkbox' }),
|
|
588
|
+
col.accessor('name', { header: 'Name' }),
|
|
589
|
+
];
|
|
590
|
+
|
|
591
|
+
<Table
|
|
592
|
+
columns={columns}
|
|
593
|
+
data={data}
|
|
594
|
+
rowSelection={rowSelection()}
|
|
595
|
+
onRowSelectionChange={setRowSelection}
|
|
596
|
+
getRowId={row => String(row.id)} // use stable IDs
|
|
597
|
+
/>
|
|
598
|
+
|
|
599
|
+
// Read selected IDs:
|
|
600
|
+
const selectedIds = () => Object.keys(rowSelection()).filter(k => rowSelection()[k]);
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
### 4. Row selection — radio (single-select)
|
|
604
|
+
|
|
605
|
+
```tsx
|
|
606
|
+
const columns = [
|
|
607
|
+
Table.Selection({ type: 'radio' }),
|
|
608
|
+
col.accessor('name', { header: 'Name' }),
|
|
609
|
+
];
|
|
610
|
+
|
|
611
|
+
<Table
|
|
612
|
+
columns={columns}
|
|
613
|
+
data={data}
|
|
614
|
+
rowSelection={rowSelection()}
|
|
615
|
+
onRowSelectionChange={setRowSelection}
|
|
616
|
+
getRowId={row => String(row.id)}
|
|
617
|
+
/>
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
### 5. Row selection — hide select-all
|
|
621
|
+
|
|
622
|
+
```tsx
|
|
623
|
+
Table.Selection({ type: 'checkbox', hideSelectAll: true })
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
### 6. Row selection — custom cell renderer
|
|
627
|
+
|
|
628
|
+
```tsx
|
|
629
|
+
Table.Selection<Person>({
|
|
630
|
+
type: 'checkbox',
|
|
631
|
+
title: 'Select',
|
|
632
|
+
width: 80,
|
|
633
|
+
renderCell: (checked, record) => (
|
|
634
|
+
<button class={checked ? 'btn-primary' : 'btn-ghost'}>
|
|
635
|
+
{checked ? 'Selected' : 'Select'}
|
|
636
|
+
</button>
|
|
637
|
+
),
|
|
638
|
+
})
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
### 7. Expandable rows — basic (uncontrolled)
|
|
642
|
+
|
|
643
|
+
```tsx
|
|
644
|
+
const columns = [
|
|
645
|
+
col.accessor('name', { header: 'Name' }),
|
|
646
|
+
Table.Expanded({
|
|
647
|
+
expandedRowRender: (record) => (
|
|
648
|
+
<div>Details: {record.email}</div>
|
|
649
|
+
),
|
|
650
|
+
}),
|
|
651
|
+
col.accessor('job', { header: 'Job' }),
|
|
652
|
+
];
|
|
653
|
+
|
|
654
|
+
<Table columns={columns} data={data} border />
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
### 8. Expandable rows — custom icon
|
|
658
|
+
|
|
659
|
+
```tsx
|
|
660
|
+
Table.Expanded({
|
|
661
|
+
expandIcon: (expanded) => (
|
|
662
|
+
<DynamicIcon name={expanded ? 'square-minus' : 'square-plus'} size={16} />
|
|
663
|
+
),
|
|
664
|
+
expandedRowRender: (record) => <div>{record.email}</div>,
|
|
665
|
+
})
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
### 9. Expandable rows — conditional (rowExpandable)
|
|
669
|
+
|
|
670
|
+
```tsx
|
|
671
|
+
Table.Expanded<Person>({
|
|
672
|
+
rowExpandable: (record) => record.hasDetails === true,
|
|
673
|
+
expandedRowRender: (record) => <div>{record.details}</div>,
|
|
674
|
+
})
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
### 10. Expandable rows — controlled
|
|
678
|
+
|
|
679
|
+
```tsx
|
|
680
|
+
const [expandedKeys, setExpandedKeys] = createSignal<string[]>([]);
|
|
681
|
+
|
|
682
|
+
<Table
|
|
683
|
+
columns={columns}
|
|
684
|
+
data={data}
|
|
685
|
+
expandedRowKeys={expandedKeys()}
|
|
686
|
+
onExpandedRowsChange={setExpandedKeys}
|
|
687
|
+
onExpand={(expanded, record) => console.log(expanded, record)}
|
|
688
|
+
/>
|
|
689
|
+
|
|
690
|
+
// Expand all programmatically (row IDs are 0-indexed strings by default):
|
|
691
|
+
setExpandedKeys(data.map((_, i) => String(i)));
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
### 11. Expandable rows — expand all on mount
|
|
695
|
+
|
|
696
|
+
```tsx
|
|
697
|
+
<Table columns={columns} data={data} defaultExpandAllRows />
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
### 12. Expandable rows — nested table
|
|
701
|
+
|
|
702
|
+
```tsx
|
|
703
|
+
Table.Expanded<Person>({
|
|
704
|
+
rowExpandable: record => orders[record.id]?.length > 0,
|
|
705
|
+
expandedRowRender: record => (
|
|
706
|
+
<div class="ml-10">
|
|
707
|
+
<Table columns={orderColumns} data={orders[record.id]} size="xs" border resizable={false} />
|
|
708
|
+
</div>
|
|
709
|
+
),
|
|
710
|
+
})
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
### 13. Scroll — vertical (fixed header)
|
|
714
|
+
|
|
715
|
+
```tsx
|
|
716
|
+
<Table columns={columns} data={data} scroll={{ y: 400 }} border />
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
### 14. Scroll — horizontal
|
|
720
|
+
|
|
721
|
+
```tsx
|
|
722
|
+
<Table columns={columns} data={data} scroll={{ x: 800 }} />
|
|
723
|
+
// or: auto min-width from column sizes
|
|
724
|
+
<Table columns={columns} data={data} scroll={{ x: true }} />
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
### 15. Fixed (sticky) columns
|
|
728
|
+
|
|
729
|
+
```tsx
|
|
730
|
+
const columns = [
|
|
731
|
+
col.accessor('id', {
|
|
732
|
+
header: '#',
|
|
733
|
+
size: 60,
|
|
734
|
+
meta: { fixed: 'start' }, // sticky left
|
|
735
|
+
}),
|
|
736
|
+
col.accessor('name', { header: 'Name', size: 150 }),
|
|
737
|
+
col.accessor('action', {
|
|
738
|
+
header: 'Action',
|
|
739
|
+
size: 100,
|
|
740
|
+
meta: { fixed: 'end' }, // sticky right
|
|
741
|
+
}),
|
|
742
|
+
];
|
|
743
|
+
|
|
744
|
+
<Table
|
|
745
|
+
columns={columns}
|
|
746
|
+
data={data}
|
|
747
|
+
scroll={{ x: true, y: 400 }}
|
|
748
|
+
resizable={false} // recommended: disable resizing with fixed columns
|
|
749
|
+
/>
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
### 16. Borders
|
|
753
|
+
|
|
754
|
+
```tsx
|
|
755
|
+
<Table columns={columns} data={data} border /> // all borders
|
|
756
|
+
<Table columns={columns} data={data} border={false} /> // no borders
|
|
757
|
+
<Table columns={columns} data={data} border={{ outer: true, horizontal: true }} />
|
|
758
|
+
<Table columns={columns} data={data} border={{ vertical: true }} />
|
|
759
|
+
```
|
|
760
|
+
|
|
761
|
+
### 17. Zebra striping
|
|
762
|
+
|
|
763
|
+
```tsx
|
|
764
|
+
<Table columns={columns} data={data} zebra /> // rows only (default)
|
|
765
|
+
<Table columns={columns} data={data} zebra={{ rows: true }} /> // rows only
|
|
766
|
+
<Table columns={columns} data={data} zebra={{ cols: true }} /> // cols only
|
|
767
|
+
<Table columns={columns} data={data} zebra={{ rows: true, cols: true }} /> // both
|
|
768
|
+
<Table columns={columns} data={data} zebra={false} /> // disabled
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
Note: `zebra={true}` is equivalent to `zebra={{ rows: true }}` — it only enables row striping, NOT column striping.
|
|
772
|
+
|
|
773
|
+
### 18. Size variants
|
|
774
|
+
|
|
775
|
+
```tsx
|
|
776
|
+
<Table columns={columns} data={data} size="xs" />
|
|
777
|
+
<Table columns={columns} data={data} size="sm" />
|
|
778
|
+
<Table columns={columns} data={data} size="md" /> // default
|
|
779
|
+
<Table columns={columns} data={data} size="lg" />
|
|
780
|
+
<Table columns={columns} data={data} size="xl" />
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
### 19. Header and footer panels
|
|
784
|
+
|
|
785
|
+
```tsx
|
|
786
|
+
<Table
|
|
787
|
+
columns={columns}
|
|
788
|
+
data={data}
|
|
789
|
+
border
|
|
790
|
+
rounded="rounded-sm"
|
|
791
|
+
headerPanel={() => (
|
|
792
|
+
<div class="flex items-center justify-between">
|
|
793
|
+
<span class="font-semibold">Employee List</span>
|
|
794
|
+
<span class="text-sm opacity-60">{data.length} records</span>
|
|
795
|
+
</div>
|
|
796
|
+
)}
|
|
797
|
+
footerPanel={() => (
|
|
798
|
+
<div class="flex justify-end gap-2">
|
|
799
|
+
<button onClick={() => setPage(p => p - 1)}>Prev</button>
|
|
800
|
+
<button onClick={() => setPage(p => p + 1)}>Next</button>
|
|
801
|
+
</div>
|
|
802
|
+
)}
|
|
803
|
+
/>
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
### 20. Empty state
|
|
807
|
+
|
|
808
|
+
```tsx
|
|
809
|
+
<Table
|
|
810
|
+
columns={columns}
|
|
811
|
+
data={[]}
|
|
812
|
+
emptyContent={<span class="text-gray-400 italic">No records found</span>}
|
|
813
|
+
/>
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
### 21. Row click handler
|
|
817
|
+
|
|
818
|
+
```tsx
|
|
819
|
+
<Table
|
|
820
|
+
columns={columns}
|
|
821
|
+
data={data}
|
|
822
|
+
onRowClick={(row, e) => {
|
|
823
|
+
console.log('clicked row:', row.original);
|
|
824
|
+
}}
|
|
825
|
+
/>
|
|
826
|
+
```
|
|
827
|
+
|
|
828
|
+
### 22. Access TanStack table instance
|
|
829
|
+
|
|
830
|
+
```tsx
|
|
831
|
+
let tableInstance: TableInstance<Person>;
|
|
832
|
+
|
|
833
|
+
<Table
|
|
834
|
+
columns={columns}
|
|
835
|
+
data={data}
|
|
836
|
+
onTableReady={(table) => { tableInstance = table; }}
|
|
837
|
+
/>
|
|
838
|
+
|
|
839
|
+
// Later: tableInstance.getSelectedRowModel(), etc.
|
|
840
|
+
```
|
|
841
|
+
|
|
842
|
+
### 23. Client-side pagination (with Pagination component)
|
|
843
|
+
|
|
844
|
+
```tsx
|
|
845
|
+
// paginationProps wires the <Pagination> component automatically.
|
|
846
|
+
// Pass all data rows; the table slices them by page internally.
|
|
847
|
+
<Table
|
|
848
|
+
columns={columns}
|
|
849
|
+
data={allData} // all rows — table slices by page
|
|
850
|
+
border
|
|
851
|
+
paginationProps={{
|
|
852
|
+
total: allData.length, // required
|
|
853
|
+
showSizeChanger: true,
|
|
854
|
+
}}
|
|
855
|
+
/>
|
|
856
|
+
```
|
|
857
|
+
|
|
858
|
+
### 24. Server-side pagination
|
|
859
|
+
|
|
860
|
+
```tsx
|
|
861
|
+
const [pagination, setPagination] = createSignal<PaginationState>({ pageIndex: 0, pageSize: 10 });
|
|
862
|
+
const [serverData, setServerData] = createSignal<Person[]>([]);
|
|
863
|
+
|
|
864
|
+
// Fetch initial page
|
|
865
|
+
onMount(() => fetchPage(0, 10).then(setServerData));
|
|
866
|
+
|
|
867
|
+
<Table
|
|
868
|
+
columns={columns}
|
|
869
|
+
data={serverData()} // only current page's rows
|
|
870
|
+
pagination={pagination()} // seed/control internal page state
|
|
871
|
+
paginationProps={{
|
|
872
|
+
total: totalCount(), // required for correct page count
|
|
873
|
+
onChange: (page, size) => {
|
|
874
|
+
// Called AFTER table state is updated
|
|
875
|
+
setPagination({ pageIndex: page - 1, pageSize: size });
|
|
876
|
+
fetchPage(page - 1, size).then(setServerData);
|
|
877
|
+
},
|
|
878
|
+
}}
|
|
879
|
+
/>
|
|
880
|
+
```
|
|
881
|
+
|
|
882
|
+
### 25. Column visibility
|
|
883
|
+
|
|
884
|
+
```tsx
|
|
885
|
+
const [visibility, setVisibility] = createSignal<VisibilityState>({ email: false });
|
|
886
|
+
|
|
887
|
+
<Table
|
|
888
|
+
columns={columns}
|
|
889
|
+
data={data}
|
|
890
|
+
columnVisibility={visibility()}
|
|
891
|
+
onColumnVisibilityChange={setVisibility}
|
|
892
|
+
/>
|
|
893
|
+
```
|
|
894
|
+
|
|
895
|
+
### 26. Pass-through TanStack options
|
|
896
|
+
|
|
897
|
+
```tsx
|
|
898
|
+
<Table
|
|
899
|
+
columns={columns}
|
|
900
|
+
data={data}
|
|
901
|
+
options={{
|
|
902
|
+
autoResetPageIndex: false,
|
|
903
|
+
meta: { myCustomMeta: 'value' },
|
|
904
|
+
isMultiSortEvent: () => false, // disable multi-sort
|
|
905
|
+
}}
|
|
906
|
+
/>
|
|
907
|
+
```
|
|
908
|
+
|
|
909
|
+
### 27. Virtual scrolling — basic (10,000+ rows)
|
|
910
|
+
|
|
911
|
+
```tsx
|
|
912
|
+
<Table
|
|
913
|
+
columns={columns}
|
|
914
|
+
data={largeData} // 10,000+ rows
|
|
915
|
+
scroll={{ y: 400 }}
|
|
916
|
+
virtual // true → defaults: itemSize=40, overscan=5
|
|
917
|
+
border
|
|
918
|
+
size="md"
|
|
919
|
+
resizable={false}
|
|
920
|
+
getRowId={row => String(row.id)}
|
|
921
|
+
/>
|
|
922
|
+
```
|
|
923
|
+
|
|
924
|
+
### 28. Virtual scrolling — custom options
|
|
925
|
+
|
|
926
|
+
```tsx
|
|
927
|
+
// Tune itemSize to match actual row height (size="lg" ≈ 48px)
|
|
928
|
+
<Table
|
|
929
|
+
columns={columns}
|
|
930
|
+
data={largeData}
|
|
931
|
+
scroll={{ y: 420 }}
|
|
932
|
+
virtual={{ itemSize: 48, overscan: 10 }}
|
|
933
|
+
size="lg"
|
|
934
|
+
resizable={false}
|
|
935
|
+
/>
|
|
936
|
+
```
|
|
937
|
+
|
|
938
|
+
### 29. Virtual scrolling — with row selection
|
|
939
|
+
|
|
940
|
+
```tsx
|
|
941
|
+
const [rowSelection, setRowSelection] = createSignal<RowSelectionState>({});
|
|
942
|
+
|
|
943
|
+
const columns = [
|
|
944
|
+
Table.Selection({ type: 'checkbox' }),
|
|
945
|
+
col.accessor('name', { header: 'Name' }),
|
|
946
|
+
// ...
|
|
947
|
+
];
|
|
948
|
+
|
|
949
|
+
<Table
|
|
950
|
+
columns={columns}
|
|
951
|
+
data={largeData}
|
|
952
|
+
scroll={{ y: 400 }}
|
|
953
|
+
virtual
|
|
954
|
+
rowSelection={rowSelection()}
|
|
955
|
+
onRowSelectionChange={setRowSelection}
|
|
956
|
+
getRowId={row => String(row.id)}
|
|
957
|
+
/>
|
|
958
|
+
```
|
|
959
|
+
|
|
960
|
+
### 30. Virtual scrolling — with sorting
|
|
961
|
+
|
|
962
|
+
```tsx
|
|
963
|
+
<Table
|
|
964
|
+
columns={columns}
|
|
965
|
+
data={largeData}
|
|
966
|
+
scroll={{ y: 400 }}
|
|
967
|
+
virtual
|
|
968
|
+
enableSorting
|
|
969
|
+
resizable={false}
|
|
970
|
+
getRowId={row => String(row.id)}
|
|
971
|
+
/>
|
|
972
|
+
// Sorting in virtual mode shows a loading indicator while the sort executes,
|
|
973
|
+
// then scrolls to the top automatically.
|
|
974
|
+
```
|
|
975
|
+
|
|
976
|
+
### 31. Virtual scrolling — infinite scroll auto mode
|
|
977
|
+
|
|
978
|
+
```tsx
|
|
979
|
+
const TOTAL = 200;
|
|
980
|
+
|
|
981
|
+
const initialRows = fetchPage(0); // stable reference — do NOT recreate on each render
|
|
982
|
+
const [loadedCount, setLoadedCount] = createSignal(initialRows.length);
|
|
983
|
+
const hasMore = () => loadedCount() < TOTAL;
|
|
984
|
+
|
|
985
|
+
const getVirtualData = () =>
|
|
986
|
+
fetchPage(loadedCount()).then(newRows => {
|
|
987
|
+
setLoadedCount(prev => prev + newRows.length);
|
|
988
|
+
return newRows; // component appends automatically
|
|
989
|
+
});
|
|
990
|
+
|
|
991
|
+
<Table
|
|
992
|
+
columns={columns}
|
|
993
|
+
data={initialRows} // stable initial data
|
|
994
|
+
scroll={{ y: 420 }}
|
|
995
|
+
virtual
|
|
996
|
+
border
|
|
997
|
+
size="sm"
|
|
998
|
+
resizable={false}
|
|
999
|
+
getRowId={row => String(row.id)} // required for stable row identity
|
|
1000
|
+
getVirtualData={hasMore() ? getVirtualData : undefined}
|
|
1001
|
+
loadingMore={hasMore() ? 'auto' : 'none'} // 'none' when all data is loaded
|
|
1002
|
+
/>
|
|
1003
|
+
// 'auto' mode: getVirtualData is called automatically on scroll near end + auto-fill when viewport not full.
|
|
1004
|
+
// Component appends resolved rows internally — no manual setRows needed.
|
|
1005
|
+
// Concurrent calls are prevented internally — no extra guard needed.
|
|
1006
|
+
```
|
|
1007
|
+
|
|
1008
|
+
### 32. Virtual scrolling — infinite scroll manual mode (Load more button)
|
|
1009
|
+
|
|
1010
|
+
```tsx
|
|
1011
|
+
<Table
|
|
1012
|
+
columns={columns}
|
|
1013
|
+
data={initialRows}
|
|
1014
|
+
scroll={{ y: 420 }}
|
|
1015
|
+
virtual
|
|
1016
|
+
getRowId={row => String(row.id)}
|
|
1017
|
+
getVirtualData={hasMore() ? getVirtualData : undefined}
|
|
1018
|
+
loadingMore={hasMore() ? 'manual' : 'none'} // 'none' hides the button when all loaded
|
|
1019
|
+
/>
|
|
1020
|
+
// 'manual' mode: getVirtualData fires ONLY on "Load more" button click, NOT on scroll.
|
|
1021
|
+
```
|
|
1022
|
+
|
|
1023
|
+
### 33. Hide table header
|
|
1024
|
+
|
|
1025
|
+
```tsx
|
|
1026
|
+
<Table columns={columns} data={data} showHeader={false} />
|
|
1027
|
+
```
|
|
1028
|
+
|
|
1029
|
+
### 34. Sorting — uncontrolled (simplest)
|
|
1030
|
+
|
|
1031
|
+
```tsx
|
|
1032
|
+
// No state management needed — table handles sort state internally.
|
|
1033
|
+
// Click any column header to toggle: asc → desc → none.
|
|
1034
|
+
<Table columns={columns} data={data} enableSorting border />
|
|
1035
|
+
```
|
|
1036
|
+
|
|
1037
|
+
### 35. Sorting — pre-sorted on mount
|
|
1038
|
+
|
|
1039
|
+
```tsx
|
|
1040
|
+
// Initialize the sorting signal with a value to sort the table on first render.
|
|
1041
|
+
const [sorting, setSorting] = createSignal<SortingState>([{ id: 'salary', desc: true }]);
|
|
1042
|
+
|
|
1043
|
+
<Table
|
|
1044
|
+
columns={columns}
|
|
1045
|
+
data={data}
|
|
1046
|
+
enableSorting
|
|
1047
|
+
sorting={sorting()}
|
|
1048
|
+
onSortingChange={setSorting}
|
|
1049
|
+
/>
|
|
1050
|
+
```
|
|
1051
|
+
|
|
1052
|
+
### 36. Sorting — disable sort on specific columns
|
|
1053
|
+
|
|
1054
|
+
```tsx
|
|
1055
|
+
const columns = [
|
|
1056
|
+
col.accessor('id', { header: '#', size: 60, enableSorting: false }), // not sortable
|
|
1057
|
+
col.accessor('name', { header: 'Name', size: 160 }), // sortable
|
|
1058
|
+
col.accessor('job', { header: 'Job', enableSorting: false }), // not sortable
|
|
1059
|
+
];
|
|
1060
|
+
|
|
1061
|
+
<Table columns={columns} data={data} enableSorting />
|
|
1062
|
+
```
|
|
1063
|
+
|
|
1064
|
+
### 37. Sorting — programmatic multi-sort presets
|
|
1065
|
+
|
|
1066
|
+
```tsx
|
|
1067
|
+
const [sorting, setSorting] = createSignal<SortingState>([]);
|
|
1068
|
+
|
|
1069
|
+
// Set multiple sort columns at once via external controls:
|
|
1070
|
+
const applyPreset = () =>
|
|
1071
|
+
setSorting([
|
|
1072
|
+
{ id: 'department', desc: false },
|
|
1073
|
+
{ id: 'salary', desc: true },
|
|
1074
|
+
]);
|
|
1075
|
+
|
|
1076
|
+
<Table
|
|
1077
|
+
columns={columns}
|
|
1078
|
+
data={data}
|
|
1079
|
+
enableSorting
|
|
1080
|
+
sorting={sorting()}
|
|
1081
|
+
onSortingChange={setSorting}
|
|
1082
|
+
/>
|
|
1083
|
+
// Priority badges (1, 2…) appear on each sorted column automatically.
|
|
1084
|
+
```
|
|
1085
|
+
|
|
1086
|
+
### 38. Sorting — custom sortingFn
|
|
1087
|
+
|
|
1088
|
+
```tsx
|
|
1089
|
+
const columns: ColumnDef<Person, any>[] = [
|
|
1090
|
+
col.accessor('joinedAt', {
|
|
1091
|
+
header: 'Joined',
|
|
1092
|
+
// Compare as Date objects (ISO strings sort lexicographically but this shows the pattern)
|
|
1093
|
+
sortingFn: (rowA, rowB, columnId) => {
|
|
1094
|
+
const a = new Date(rowA.getValue<string>(columnId)).getTime();
|
|
1095
|
+
const b = new Date(rowB.getValue<string>(columnId)).getTime();
|
|
1096
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
1097
|
+
},
|
|
1098
|
+
}),
|
|
1099
|
+
col.accessor('score', {
|
|
1100
|
+
header: 'Score',
|
|
1101
|
+
sortingFn: 'alphanumeric', // TanStack built-in: handles numbers + mixed strings
|
|
1102
|
+
}),
|
|
1103
|
+
];
|
|
1104
|
+
|
|
1105
|
+
<Table columns={columns} data={data} enableSorting sorting={sorting()} onSortingChange={setSorting} />
|
|
1106
|
+
```
|
|
1107
|
+
|
|
1108
|
+
### 39. Sorting — server-side
|
|
1109
|
+
|
|
1110
|
+
```tsx
|
|
1111
|
+
const [sorting, setSorting] = createSignal<SortingState>([]);
|
|
1112
|
+
const [serverData, setServerData] = createSignal<Person[]>(initialData);
|
|
1113
|
+
|
|
1114
|
+
const handleSortingChange = (updater: SortingState | ((prev: SortingState) => SortingState)) => {
|
|
1115
|
+
const next = typeof updater === 'function' ? updater(sorting()) : updater;
|
|
1116
|
+
setSorting(next);
|
|
1117
|
+
fetchSortedData(next).then(setServerData); // call your API with the new sort params
|
|
1118
|
+
};
|
|
1119
|
+
|
|
1120
|
+
<Table
|
|
1121
|
+
columns={columns}
|
|
1122
|
+
data={serverData()}
|
|
1123
|
+
enableSorting
|
|
1124
|
+
sorting={sorting()}
|
|
1125
|
+
onSortingChange={handleSortingChange}
|
|
1126
|
+
options={{ manualSorting: true }} // disables client-side row sorting
|
|
1127
|
+
/>
|
|
1128
|
+
```
|
|
1129
|
+
|
|
1130
|
+
### 40. Column alignment
|
|
1131
|
+
|
|
1132
|
+
```tsx
|
|
1133
|
+
const columns = [
|
|
1134
|
+
col.accessor('id', {
|
|
1135
|
+
header: '#',
|
|
1136
|
+
size: 60,
|
|
1137
|
+
meta: { align: 'end' }, // right-align header + cells
|
|
1138
|
+
}),
|
|
1139
|
+
col.accessor('name', {
|
|
1140
|
+
header: 'Name',
|
|
1141
|
+
size: 150,
|
|
1142
|
+
meta: { align: 'start' }, // default
|
|
1143
|
+
}),
|
|
1144
|
+
col.accessor('score', {
|
|
1145
|
+
header: 'Score',
|
|
1146
|
+
meta: { align: 'center' }, // center-align
|
|
1147
|
+
}),
|
|
1148
|
+
];
|
|
1149
|
+
```
|
|
1150
|
+
|
|
1151
|
+
### 41. Cell tooltip
|
|
1152
|
+
|
|
1153
|
+
```tsx
|
|
1154
|
+
const columns = [
|
|
1155
|
+
col.accessor('email', {
|
|
1156
|
+
header: 'Email',
|
|
1157
|
+
meta: {
|
|
1158
|
+
tooltip: {
|
|
1159
|
+
content: (value) => `Send to: ${value as string}`,
|
|
1160
|
+
position: 'top',
|
|
1161
|
+
},
|
|
1162
|
+
},
|
|
1163
|
+
}),
|
|
1164
|
+
col.accessor('notes', {
|
|
1165
|
+
header: 'Notes',
|
|
1166
|
+
meta: {
|
|
1167
|
+
tooltip: { content: 'Click cell to edit' },
|
|
1168
|
+
},
|
|
1169
|
+
}),
|
|
1170
|
+
];
|
|
1171
|
+
|
|
1172
|
+
<Table columns={columns} data={data} />
|
|
1173
|
+
// Hovering any cell in the tooltip-enabled column shows the tooltip automatically.
|
|
1174
|
+
```
|
|
1175
|
+
|
|
1176
|
+
### 42. Cell spanning (colSpan / rowSpan)
|
|
1177
|
+
|
|
1178
|
+
```tsx
|
|
1179
|
+
// Table-level: applies to all columns unless overridden by meta.onCell
|
|
1180
|
+
<Table
|
|
1181
|
+
columns={columns}
|
|
1182
|
+
data={data}
|
|
1183
|
+
onCell={(cell, rowIndex) => {
|
|
1184
|
+
if (cell.column.id === 'category' && rowIndex === 0) return { colSpan: 2 };
|
|
1185
|
+
return {};
|
|
1186
|
+
}}
|
|
1187
|
+
/>
|
|
1188
|
+
```
|
|
1189
|
+
|
|
1190
|
+
```tsx
|
|
1191
|
+
// Column-level (higher priority than table-level onCell):
|
|
1192
|
+
const columns = [
|
|
1193
|
+
col.accessor('product', {
|
|
1194
|
+
header: 'Product',
|
|
1195
|
+
meta: {
|
|
1196
|
+
onCell: (cell, rowIndex) => {
|
|
1197
|
+
if (cell.getValue() === 'Total') return { colSpan: 3 };
|
|
1198
|
+
return {};
|
|
1199
|
+
},
|
|
1200
|
+
},
|
|
1201
|
+
}),
|
|
1202
|
+
col.accessor('qty', { header: 'Qty' }),
|
|
1203
|
+
col.accessor('price', { header: 'Price' }),
|
|
1204
|
+
];
|
|
1205
|
+
// Return { colSpan: 0 } or { rowSpan: 0 } to hide cells covered by a spanning neighbour.
|
|
1206
|
+
```
|
|
1207
|
+
|
|
1208
|
+
### 43. Header spanning (via meta.onHeaderCell)
|
|
1209
|
+
|
|
1210
|
+
```tsx
|
|
1211
|
+
const columns = [
|
|
1212
|
+
col.accessor('firstName', {
|
|
1213
|
+
header: 'First Name',
|
|
1214
|
+
meta: {
|
|
1215
|
+
onHeaderCell: () => ({ colSpan: 2 }), // spans 2 header columns
|
|
1216
|
+
},
|
|
1217
|
+
}),
|
|
1218
|
+
col.accessor('lastName', {
|
|
1219
|
+
header: 'Last Name',
|
|
1220
|
+
// this th will be covered — return colSpan: 0 on the header to hide it
|
|
1221
|
+
meta: {
|
|
1222
|
+
onHeaderCell: () => ({ colSpan: 0 }),
|
|
1223
|
+
},
|
|
1224
|
+
}),
|
|
1225
|
+
];
|
|
1226
|
+
```
|
|
1227
|
+
|
|
1228
|
+
### 44. Pagination above the table
|
|
1229
|
+
|
|
1230
|
+
```tsx
|
|
1231
|
+
<Table
|
|
1232
|
+
columns={columns}
|
|
1233
|
+
data={allData}
|
|
1234
|
+
paginationProps={{
|
|
1235
|
+
total: allData.length,
|
|
1236
|
+
position: 'start', // renders Pagination above the table
|
|
1237
|
+
}}
|
|
1238
|
+
/>
|
|
1239
|
+
```
|
|
1240
|
+
|
|
1241
|
+
### 46. Sorting — imperative via onTableReady
|
|
1242
|
+
|
|
1243
|
+
```tsx
|
|
1244
|
+
let tableInstance: TableInstance<Person> | undefined;
|
|
1245
|
+
|
|
1246
|
+
<Table
|
|
1247
|
+
columns={columns}
|
|
1248
|
+
data={data}
|
|
1249
|
+
enableSorting
|
|
1250
|
+
onTableReady={t => (tableInstance = t)}
|
|
1251
|
+
/>
|
|
1252
|
+
|
|
1253
|
+
// Later — sort imperatively without external signal:
|
|
1254
|
+
tableInstance?.setSorting([{ id: 'name', desc: false }]);
|
|
1255
|
+
tableInstance?.setSorting([{ id: 'age', desc: false }, { id: 'score', desc: true }]);
|
|
1256
|
+
tableInstance?.setSorting([]); // clear all sorting
|
|
1257
|
+
```
|
|
1258
|
+
|
|
1259
|
+
---
|
|
1260
|
+
|
|
1261
|
+
## CSS CLASSES (public API)
|
|
1262
|
+
|
|
1263
|
+
| Class | Applied to |
|
|
1264
|
+
|---|---|
|
|
1265
|
+
| `sui-table-outer` | Outermost wrapper `<div>` (contains root + pagination component) |
|
|
1266
|
+
| `sui-table-has-pagination` | `sui-table-outer` when `paginationProps` is set |
|
|
1267
|
+
| `sui-table-root` | Root wrapper `<div>` inside `sui-table-outer` |
|
|
1268
|
+
| `sui-table-border-outer` | Root when `border.outer=true` |
|
|
1269
|
+
| `sui-table-resizable` | Root when `resizable=true` |
|
|
1270
|
+
| `sui-table-resizing` | Root while a column is being dragged |
|
|
1271
|
+
| `sui-table-fixed-header` | Root when `scroll.y` set or any column has `fixed` |
|
|
1272
|
+
| `sui-table-wrapper` | Scroll container `<div>` |
|
|
1273
|
+
| `sui-table-scroll` | Scroll wrapper when scroll is active |
|
|
1274
|
+
| `sui-table` | `<table>` element |
|
|
1275
|
+
| `sui-table-xs/sm/md/lg/xl` | Applied to `<table>` via wrapper class |
|
|
1276
|
+
| `sui-table-zebra-rows` | `<table>` when `zebra.rows=true` |
|
|
1277
|
+
| `sui-table-zebra-cols` | `<table>` when `zebra.cols=true` |
|
|
1278
|
+
| `sui-table-border-horizontal` | `<table>` when `border.horizontal=true` |
|
|
1279
|
+
| `sui-table-border-vertical` | `<table>` when `border.vertical=true` |
|
|
1280
|
+
| `sui-table-th-sortable` | `<th>` when column `getCanSort()` is true |
|
|
1281
|
+
| `sui-table-header-cell` | Inner `<div>` in `<th>` |
|
|
1282
|
+
| `sui-table-header-cell-text` | `<span>` wrapping header text |
|
|
1283
|
+
| `sui-table-sized-header-cell` | Header cell inner div when column has explicit size |
|
|
1284
|
+
| `sui-table-sort-icon` | Sort icon `<span>` in `<th>` |
|
|
1285
|
+
| `sui-table-sort-priority` | Sort priority badge `<span>` (shown when 2+ columns sorted) |
|
|
1286
|
+
| `sui-table-row-selected` | `<tr>` when row is selected |
|
|
1287
|
+
| `sui-table-row-expanded` | `<tr>` when row is expanded |
|
|
1288
|
+
| `sui-table-row-odd` | `<tr>` at even index (0, 2, 4...) — note: class name is "odd" but index is even |
|
|
1289
|
+
| `sui-table-row-even` | `<tr>` at odd index (1, 3, 5...) |
|
|
1290
|
+
| `sui-table-sized-cell` | Inner `<div>` in body `<td>` when column has explicit size (overflow ellipsis) |
|
|
1291
|
+
| `sui-table-empty` | `<td>` for empty state |
|
|
1292
|
+
| `sui-table-panel` | Header/footer panel `<div>` |
|
|
1293
|
+
| `sui-table-panel-bordered` | Header panel when `border.outer=true` |
|
|
1294
|
+
| `sui-table-selection-cell` | `<th>` and `<td>` for selection column |
|
|
1295
|
+
| `sui-table-expand-cell` | `<th>` and `<td>` for expand column |
|
|
1296
|
+
| `sui-table-expand-icon` | Icon wrapper `<span>` in expand cell |
|
|
1297
|
+
| `sui-table-expand-icon-svg` | Default chevron `<svg>` |
|
|
1298
|
+
| `sui-table-expand-icon-expanded` | Added to SVG when row is expanded (rotates 90°) |
|
|
1299
|
+
| `sui-table-expanded-row` | `<tr>` containing expanded content |
|
|
1300
|
+
| `sui-table-expanded-content` | `<td>` inside expanded row |
|
|
1301
|
+
| `sui-table-cell-fixed` | `<th>` / `<td>` for sticky columns |
|
|
1302
|
+
| `sui-table-cell-fixed-start-last` | Last sticky-start column (renders right shadow) |
|
|
1303
|
+
| `sui-table-cell-fixed-end-first` | First sticky-end column (renders left shadow) |
|
|
1304
|
+
| `sui-table-resize-handle` | Resize handle overlay `<div>` |
|
|
1305
|
+
| `sui-table-resize-handle-active` | Handle currently being dragged |
|
|
1306
|
+
| `tbe-tt-cell` | Class applied to the Tooltip root wrapping a cell with `meta.tooltip` |
|
|
1307
|
+
|
|
1308
|
+
---
|
|
1309
|
+
|
|
1310
|
+
## CSS CUSTOM PROPERTIES
|
|
1311
|
+
|
|
1312
|
+
Resolved from `color` prop (via `getColor()` utility):
|
|
1313
|
+
|
|
1314
|
+
| Property | Usage |
|
|
1315
|
+
|---|---|
|
|
1316
|
+
| `--color` | Primary accent: sort icons, hover border, resize handle active |
|
|
1317
|
+
| `--hover-bg-color` | `color-mix(color 10%, base-100)` — cell hover background |
|
|
1318
|
+
| `--row-highlight-bg-color` | `color-mix(color 6%, base-100)` — zebra rows, expanded content |
|
|
1319
|
+
| `--row-highlight-border` | `color-mix(color 15%, base-100)` — hover inset border |
|
|
1320
|
+
| `--header-bg-color` | `color-mix(color 20%, base-100)` — thead background |
|
|
1321
|
+
|
|
1322
|
+
---
|
|
1323
|
+
|
|
1324
|
+
## INTERNAL COLUMN IDs
|
|
1325
|
+
|
|
1326
|
+
| ID | Created by | Notes |
|
|
1327
|
+
|---|---|---|
|
|
1328
|
+
| `__selection__` | `Table.Selection()` | Do not reference in `columnVisibility` or other state by this ID |
|
|
1329
|
+
| `__expanded__` | `Table.Expanded()` | Do not reference in `columnVisibility` or other state by this ID |
|
|
1330
|
+
|
|
1331
|
+
---
|
|
1332
|
+
|
|
1333
|
+
## CONSTRAINTS & EDGE CASES
|
|
1334
|
+
|
|
1335
|
+
- **`getRowId` is required** for selection and controlled expansion to work correctly when `data` has a non-index stable ID. Without it, row IDs are `"0"`, `"1"`, etc. (position-based, breaks on re-order/filter).
|
|
1336
|
+
- **Controlled expanded keys use TanStack row IDs** (not raw data IDs). If `getRowId` returns `String(row.id)`, expanded keys must also use that same format.
|
|
1337
|
+
- **`defaultExpandAllRows` applies once** — subsequent data changes won't re-expand. Use controlled mode for dynamic expand-all.
|
|
1338
|
+
- **`simple` mode does not exist** — unlike Pagination, Table has no simple mode.
|
|
1339
|
+
- **`enableSorting` must be `true` at the table level** even if individual columns have `enableSorting: true` on their `ColumnDef`.
|
|
1340
|
+
- **Multi-sort is always on** — every header click adds to the sort stack. Override with `options={{ isMultiSortEvent: () => false }}`.
|
|
1341
|
+
- **`resizable={false}` is recommended** when using `meta.fixed` columns, as resizing computes widths separately from fixed offset computation (they can diverge).
|
|
1342
|
+
- **Zebra row classes**: `sui-table-row-odd` is applied to rows at even indexes (0, 2, 4...) and `sui-table-row-even` to odd indexes — the naming matches visual appearance, not the index parity.
|
|
1343
|
+
- **`zebra={true}`** enables row striping only (`{ rows: true, cols: false }`), not column striping. Pass `{ rows: true, cols: true }` explicitly for both.
|
|
1344
|
+
- **Column `size` vs resize**: TanStack's `size` on `ColumnDef` only activates `table-fixed` layout if the component detects it at initialization (`colsWithExplicitSize` Set). Columns added after mount via reactive `columns` signal may not update this set — define columns outside the component or in a `createMemo` that runs before first render.
|
|
1345
|
+
- **`options.state` is merged** — `p.options?.state` is spread into the table's `state` object, so you can extend state (e.g. custom meta state) without losing the controlled props.
|
|
1346
|
+
- **`footerPanel` class key**: The `class.tfoot` key is used for the footer panel wrapper div (not a `<tfoot>` element — the actual `<tfoot>` element is not rendered).
|
|
1347
|
+
- **Empty state colspan**: `table.getAllColumns().length` is used (includes hidden columns) — always renders correctly.
|
|
1348
|
+
- **`paginationProps` vs `pagination`**: `paginationProps` enables pagination AND renders the `<Pagination>` component. `pagination` is just a state seed/override. Both can be used together for server-side pagination.
|
|
1349
|
+
- **`loadingMore='manual'` blocks auto-fill**: In `'manual'` mode, the auto-fill behavior (filling viewport when content is too short) is suppressed — the user must click the button.
|
|
1350
|
+
- **`TableExample` / `TableSortingExample` etc.** are demo components only — do not use in production (not exported from `solid-tom-ui`).
|
|
1351
|
+
- **`showHeader=false`** removes the entire `<thead>` from the DOM. Column resizing and fixed-column offset measurement rely on `<thead>` cells, so combining `showHeader={false}` with `resizable` or `meta.fixed` columns may produce incorrect behavior.
|
|
1352
|
+
- **`onCell` is ignored in virtual mode** — cell spanning (`colSpan`/`rowSpan`) only works in non-virtual table rendering. Use it only when `virtual` is not set.
|
|
1353
|
+
- **`meta.onCell` vs `onCell`**: Column-level `meta.onCell` takes priority over the table-level `onCell` prop for the same column.
|
|
1354
|
+
- **`meta.tooltip`**: `usePortal` and `containerId` are injected automatically by the Table — do not set them manually. The `position` prop defaults to `'top'` if omitted.
|
|
1355
|
+
- **`meta.align`**: Affects alignment of both the header cell inner div and the body cell text alignment (`text-align`). Setting `meta.align='end'` right-aligns numbers automatically.
|
|
1356
|
+
- **`paginationProps.position`**: `'start'` renders Pagination above the table inside `sui-table-outer`; `'end'` (default) renders it below.
|
|
1357
|
+
|
|
1358
|
+
---
|
|
1359
|
+
|
|
1360
|
+
## DO / DON'T
|
|
1361
|
+
|
|
1362
|
+
**DO**
|
|
1363
|
+
- Import `createColumnHelper` from `solid-tom-ui` (re-exported for convenience)
|
|
1364
|
+
- Always provide `getRowId` when using row selection or controlled expand with business IDs
|
|
1365
|
+
- Use `Table.Selection()` and `Table.Expanded()` as `ColumnDef` entries, not as components
|
|
1366
|
+
- Set `resizable={false}` when using fixed columns
|
|
1367
|
+
- Set `scroll={{ x: true, y: 400 }}` together when you need both fixed header and horizontal scroll with fixed columns
|
|
1368
|
+
- Use `onTableReady` to access the full TanStack table API for operations not covered by props
|
|
1369
|
+
- Use `options` prop to pass TanStack options not exposed directly (e.g. `autoResetPageIndex`, custom row models, `isMultiSortEvent`)
|
|
1370
|
+
- Always set `scroll.y` when using `virtual` — without a fixed viewport height, virtual mode has no effect
|
|
1371
|
+
- Tune `virtual.itemSize` to match your `size` variant row height for smoother scrolling (xs≈26, sm≈33, md≈40, lg≈48, xl≈58)
|
|
1372
|
+
- Set `resizable={false}` when using virtual scrolling
|
|
1373
|
+
- Use `getVirtualData` + `loadingMore` together for infinite scroll — `'auto'` for spinner, `'manual'` for button
|
|
1374
|
+
- The component handles concurrent call prevention internally — no extra guard needed in the `getVirtualData` callback
|
|
1375
|
+
- Always **append** new rows to existing data (`[...prev, ...newRows]`) — replacing the array causes a scroll jump
|
|
1376
|
+
- Provide `getRowId` when using infinite scroll so rows across pages have stable unique IDs
|
|
1377
|
+
- Use `paginationProps` to add a pagination UI below the table; it also enables client-side pagination automatically
|
|
1378
|
+
- Use `meta.align` to right-align numeric columns (`'end'`) or center-align status columns (`'center'`)
|
|
1379
|
+
- Use `meta.tooltip` for per-column cell tooltips — Table handles portal and container injection automatically
|
|
1380
|
+
- Use `meta.onCell` (column-level) for column-specific cell spanning; use table-level `onCell` for cross-column logic
|
|
1381
|
+
- Return `{ colSpan: 0 }` or `{ rowSpan: 0 }` from `onCell`/`meta.onCell` to hide cells covered by a spanning neighbour
|
|
1382
|
+
|
|
1383
|
+
**DON'T**
|
|
1384
|
+
- Don't place `Table.Selection()` or `Table.Expanded()` more than once in the same `columns` array (duplicates are silently dropped)
|
|
1385
|
+
- Don't use `enableSorting` on individual `ColumnDef` only — you must also set `enableSorting` prop on `<Table>`
|
|
1386
|
+
- Don't pass `current`, `pageSize`, `defaultCurrent`, or `defaultPageSize` inside `paginationProps` — they are wired automatically
|
|
1387
|
+
- Don't rely on `__selection__` or `__expanded__` column IDs in external logic
|
|
1388
|
+
- Don't use `defaultExpandAllRows` when you need reactive expand-all — use controlled `expandedRowKeys` instead
|
|
1389
|
+
- Don't pass `color` as a hex string — it must be a design system token (`'info'`, `'primary'`, `'success'`, etc.)
|
|
1390
|
+
- Don't combine `virtual` with row expansion (`Table.Expanded()`) — expanded rows have variable heights that break virtualizer accuracy
|
|
1391
|
+
- Don't use `virtual` without `scroll.y` — the prop has no effect without a bounded scroll viewport
|
|
1392
|
+
- Don't use `getVirtualData` or `loadingMore` outside virtual mode — both props are no-ops without `virtual`
|
|
1393
|
+
- Don't replace the entire `data` array on each page load (infinite scroll) — always append to preserve scroll position
|
|
1394
|
+
- Don't forget to hide `loadingMore` when all data is loaded — leaving it as `'auto'` causes repeated empty fetches; leaving it as `'manual'` keeps showing the button forever
|
|
1395
|
+
- Don't pass `loadingMore='auto'` while not actually fetching — it will suppress the auto-fill trigger
|
|
1396
|
+
- Don't use `onCell` in virtual mode — cell spanning is ignored in virtual rendering
|
|
1397
|
+
- Don't set `usePortal` or `containerId` in `meta.tooltip` — the Table component injects these automatically
|
|
1398
|
+
- Don't use `meta.onCell` and table-level `onCell` for the same column expecting them to merge — column-level wins entirely
|
|
1399
|
+
- Don't assume `zebra={true}` enables column striping — it only enables row striping
|
|
1400
|
+
- Don't combine `showHeader={false}` with `resizable` or fixed columns — header cells are needed for measurement and resize handle positioning
|
|
1401
|
+
---
|
|
1402
|
+
|
|
1403
|
+
## Component Conventions
|
|
1404
|
+
|
|
1405
|
+
> **CSS encoding**: internal CSS classes use short encoded names (e.g. `tl01`, `tl02`) per project convention.
|
|
1406
|
+
|
|
1407
|
+
> **Unique IDs**: if this component needs to generate HTML `id` attributes, always use `createUniqueId()` from `solid-js` — never `Math.random()` or `Date.now()`.
|