@nikala-ui/docs 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -0
- package/dist/cli/commands/build.d.ts +5 -0
- package/dist/cli/commands/build.d.ts.map +1 -0
- package/dist/cli/commands/build.js +27 -0
- package/dist/cli/commands/build.js.map +1 -0
- package/dist/cli/commands/dev.d.ts +7 -0
- package/dist/cli/commands/dev.d.ts.map +1 -0
- package/dist/cli/commands/dev.js +32 -0
- package/dist/cli/commands/dev.js.map +1 -0
- package/dist/cli/commands/init.d.ts +2 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +308 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/preview.d.ts +7 -0
- package/dist/cli/commands/preview.d.ts.map +1 -0
- package/dist/cli/commands/preview.js +24 -0
- package/dist/cli/commands/preview.js.map +1 -0
- package/dist/cli/commands/serve.d.ts +7 -0
- package/dist/cli/commands/serve.d.ts.map +1 -0
- package/dist/cli/commands/serve.js +33 -0
- package/dist/cli/commands/serve.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +53 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/client/App.d.ts +5 -0
- package/dist/client/App.d.ts.map +1 -0
- package/dist/client/App.jsx +31 -0
- package/dist/client/App.jsx.map +1 -0
- package/dist/client/app-types.d.ts +14 -0
- package/dist/client/app-types.d.ts.map +1 -0
- package/dist/client/app-types.js +2 -0
- package/dist/client/app-types.js.map +1 -0
- package/dist/client/components/DocsLayoutShell.d.ts +21 -0
- package/dist/client/components/DocsLayoutShell.d.ts.map +1 -0
- package/dist/client/components/DocsLayoutShell.jsx +14 -0
- package/dist/client/components/DocsLayoutShell.jsx.map +1 -0
- package/dist/client/components/DocsPageContent.d.ts +9 -0
- package/dist/client/components/DocsPageContent.d.ts.map +1 -0
- package/dist/client/components/DocsPageContent.jsx +10 -0
- package/dist/client/components/DocsPageContent.jsx.map +1 -0
- package/dist/client/entry.d.ts +2 -0
- package/dist/client/entry.d.ts.map +1 -0
- package/dist/client/entry.jsx +28 -0
- package/dist/client/entry.jsx.map +1 -0
- package/dist/client/index.html +21 -0
- package/dist/client/navigation/page-navigation.d.ts +9 -0
- package/dist/client/navigation/page-navigation.d.ts.map +1 -0
- package/dist/client/navigation/page-navigation.js +51 -0
- package/dist/client/navigation/page-navigation.js.map +1 -0
- package/dist/client/routing/use-docs-router.d.ts +17 -0
- package/dist/client/routing/use-docs-router.d.ts.map +1 -0
- package/dist/client/routing/use-docs-router.js +71 -0
- package/dist/client/routing/use-docs-router.js.map +1 -0
- package/dist/client/ssr-entry.d.ts +3 -0
- package/dist/client/ssr-entry.d.ts.map +1 -0
- package/dist/client/ssr-entry.jsx +13 -0
- package/dist/client/ssr-entry.jsx.map +1 -0
- package/dist/client/style.css +183 -0
- package/dist/client/utils/sidebar.d.ts +2 -0
- package/dist/client/utils/sidebar.d.ts.map +1 -0
- package/dist/client/utils/sidebar.js +2 -0
- package/dist/client/utils/sidebar.js.map +1 -0
- package/dist/components/mdx-components.d.ts +34 -0
- package/dist/components/mdx-components.d.ts.map +1 -0
- package/dist/components/mdx-components.jsx +131 -0
- package/dist/components/mdx-components.jsx.map +1 -0
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +105 -0
- package/dist/config.js.map +1 -0
- package/dist/core/content-scanner.d.ts +10 -0
- package/dist/core/content-scanner.d.ts.map +1 -0
- package/dist/core/content-scanner.js +153 -0
- package/dist/core/content-scanner.js.map +1 -0
- package/dist/core/route-tree.d.ts +19 -0
- package/dist/core/route-tree.d.ts.map +1 -0
- package/dist/core/route-tree.js +176 -0
- package/dist/core/route-tree.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/cn.d.ts +6 -0
- package/dist/lib/cn.d.ts.map +1 -0
- package/dist/lib/cn.js +9 -0
- package/dist/lib/cn.js.map +1 -0
- package/dist/mdx/compiler.d.ts +13 -0
- package/dist/mdx/compiler.d.ts.map +1 -0
- package/dist/mdx/compiler.js +38 -0
- package/dist/mdx/compiler.js.map +1 -0
- package/dist/mdx/highlighter.d.ts +22 -0
- package/dist/mdx/highlighter.d.ts.map +1 -0
- package/dist/mdx/highlighter.js +139 -0
- package/dist/mdx/highlighter.js.map +1 -0
- package/dist/mdx/rehype-shiki.d.ts +3 -0
- package/dist/mdx/rehype-shiki.d.ts.map +1 -0
- package/dist/mdx/rehype-shiki.js +83 -0
- package/dist/mdx/rehype-shiki.js.map +1 -0
- package/dist/navigation/index.d.ts +2 -0
- package/dist/navigation/index.d.ts.map +1 -0
- package/dist/navigation/index.js +2 -0
- package/dist/navigation/index.js.map +1 -0
- package/dist/navigation/sidebar-state.d.ts +6 -0
- package/dist/navigation/sidebar-state.d.ts.map +1 -0
- package/dist/navigation/sidebar-state.js +20 -0
- package/dist/navigation/sidebar-state.js.map +1 -0
- package/dist/navigation/sidebar-tree.d.ts +7 -0
- package/dist/navigation/sidebar-tree.d.ts.map +1 -0
- package/dist/navigation/sidebar-tree.js +11 -0
- package/dist/navigation/sidebar-tree.js.map +1 -0
- package/dist/registry/accordion.json +18 -0
- package/dist/registry/alert.json +18 -0
- package/dist/registry/api-table.json +21 -0
- package/dist/registry/aspect-ratio.json +17 -0
- package/dist/registry/avatar.json +17 -0
- package/dist/registry/badge.json +18 -0
- package/dist/registry/banner.json +19 -0
- package/dist/registry/breadcrumb.json +17 -0
- package/dist/registry/bubble.json +18 -0
- package/dist/registry/button-group.json +20 -0
- package/dist/registry/button.json +21 -0
- package/dist/registry/callout.json +19 -0
- package/dist/registry/card.json +17 -0
- package/dist/registry/checkbox.json +20 -0
- package/dist/registry/code-block.json +26 -0
- package/dist/registry/code-group.json +20 -0
- package/dist/registry/collapsible.json +18 -0
- package/dist/registry/combobox.json +21 -0
- package/dist/registry/command.json +27 -0
- package/dist/registry/component-viewer.json +25 -0
- package/dist/registry/container.json +18 -0
- package/dist/registry/context-menu.json +24 -0
- package/dist/registry/create-active-element.json +13 -0
- package/dist/registry/create-app-updater.json +13 -0
- package/dist/registry/create-audio.json +7 -0
- package/dist/registry/create-battery.json +13 -0
- package/dist/registry/create-chat-scroll.json +13 -0
- package/dist/registry/create-click-outside.json +13 -0
- package/dist/registry/create-clipboard.json +13 -0
- package/dist/registry/create-color-mode.json +13 -0
- package/dist/registry/create-controllable-signal.json +13 -0
- package/dist/registry/create-debounce.json +13 -0
- package/dist/registry/create-disclosure.json +13 -0
- package/dist/registry/create-document-tabs.json +13 -0
- package/dist/registry/create-document-title.json +13 -0
- package/dist/registry/create-drop-zone.json +13 -0
- package/dist/registry/create-event-source.json +13 -0
- package/dist/registry/create-favicon.json +13 -0
- package/dist/registry/create-fetch.json +13 -0
- package/dist/registry/create-focus-trap.json +13 -0
- package/dist/registry/create-form.json +13 -0
- package/dist/registry/create-fullscreen.json +13 -0
- package/dist/registry/create-geolocation.json +13 -0
- package/dist/registry/create-global-shortcut.json +13 -0
- package/dist/registry/create-hover.json +13 -0
- package/dist/registry/create-idle.json +13 -0
- package/dist/registry/create-infinite-scroll.json +13 -0
- package/dist/registry/create-input-mask.json +13 -0
- package/dist/registry/create-intersection-observer.json +13 -0
- package/dist/registry/create-keybindings.json +13 -0
- package/dist/registry/create-lock-scroll.json +13 -0
- package/dist/registry/create-long-press.json +13 -0
- package/dist/registry/create-media-query.json +13 -0
- package/dist/registry/create-mouse-position.json +13 -0
- package/dist/registry/create-network-status.json +13 -0
- package/dist/registry/create-orientation.json +13 -0
- package/dist/registry/create-pagination.json +13 -0
- package/dist/registry/create-permission.json +13 -0
- package/dist/registry/create-previous.json +13 -0
- package/dist/registry/create-resize-observer.json +13 -0
- package/dist/registry/create-scroll-into-view.json +13 -0
- package/dist/registry/create-scroll-position.json +13 -0
- package/dist/registry/create-storage.json +13 -0
- package/dist/registry/create-tauri-window.json +13 -0
- package/dist/registry/create-timer.json +13 -0
- package/dist/registry/create-tiptap-editor.json +34 -0
- package/dist/registry/create-undo-redo.json +13 -0
- package/dist/registry/create-web-notification.json +13 -0
- package/dist/registry/create-websocket.json +13 -0
- package/dist/registry/create-window-size.json +13 -0
- package/dist/registry/dialog.json +21 -0
- package/dist/registry/dropdown-menu.json +22 -0
- package/dist/registry/dropzone.json +21 -0
- package/dist/registry/empty.json +17 -0
- package/dist/registry/field.json +20 -0
- package/dist/registry/file-tree.json +21 -0
- package/dist/registry/footer.json +18 -0
- package/dist/registry/forgot-password-01.json +28 -0
- package/dist/registry/form-message.json +17 -0
- package/dist/registry/form.json +17 -0
- package/dist/registry/hero-01.json +22 -0
- package/dist/registry/hover-card.json +18 -0
- package/dist/registry/icon-button.json +20 -0
- package/dist/registry/index.json +1489 -0
- package/dist/registry/input-group.json +21 -0
- package/dist/registry/input.json +17 -0
- package/dist/registry/kbd.json +18 -0
- package/dist/registry/label.json +18 -0
- package/dist/registry/list.json +20 -0
- package/dist/registry/login-01.json +28 -0
- package/dist/registry/logo.json +17 -0
- package/dist/registry/marker.json +17 -0
- package/dist/registry/marquee.json +17 -0
- package/dist/registry/message.json +17 -0
- package/dist/registry/navbar.json +19 -0
- package/dist/registry/navigation-menu.json +22 -0
- package/dist/registry/number-input.json +24 -0
- package/dist/registry/otp-verification-01.json +26 -0
- package/dist/registry/package-manager-tabs.json +24 -0
- package/dist/registry/pager.json +21 -0
- package/dist/registry/pagination.json +22 -0
- package/dist/registry/pin-input.json +17 -0
- package/dist/registry/popover.json +22 -0
- package/dist/registry/progress.json +18 -0
- package/dist/registry/radio-group.json +18 -0
- package/dist/registry/rating.json +19 -0
- package/dist/registry/register-01.json +31 -0
- package/dist/registry/resizable.json +21 -0
- package/dist/registry/review-card.json +23 -0
- package/dist/registry/rich-text-editor.json +95 -0
- package/dist/registry/scroll-area.json +21 -0
- package/dist/registry/section-heading.json +21 -0
- package/dist/registry/select.json +22 -0
- package/dist/registry/separator.json +17 -0
- package/dist/registry/sheet.json +22 -0
- package/dist/registry/sidebar.json +24 -0
- package/dist/registry/skeleton.json +17 -0
- package/dist/registry/slider.json +18 -0
- package/dist/registry/spinner.json +18 -0
- package/dist/registry/stat.json +19 -0
- package/dist/registry/status.json +18 -0
- package/dist/registry/steps.json +20 -0
- package/dist/registry/switch.json +20 -0
- package/dist/registry/table-of-contents.json +23 -0
- package/dist/registry/table.json +17 -0
- package/dist/registry/tabs.json +20 -0
- package/dist/registry/textarea.json +17 -0
- package/dist/registry/theme-manager.json +39 -0
- package/dist/registry/timeline.json +18 -0
- package/dist/registry/titlebar-tabs.json +24 -0
- package/dist/registry/titlebar.json +26 -0
- package/dist/registry/toast.json +20 -0
- package/dist/registry/toggle-group.json +21 -0
- package/dist/registry/toggle.json +18 -0
- package/dist/registry/tooltip.json +18 -0
- package/dist/registry/updater-modal.json +26 -0
- package/dist/server/index.d.ts +18 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +526 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/plugin.d.ts +10 -0
- package/dist/server/plugin.d.ts.map +1 -0
- package/dist/server/plugin.js +271 -0
- package/dist/server/plugin.js.map +1 -0
- package/dist/themes/default/content/breadcrumbs.d.ts +4 -0
- package/dist/themes/default/content/breadcrumbs.d.ts.map +1 -0
- package/dist/themes/default/content/breadcrumbs.jsx +31 -0
- package/dist/themes/default/content/breadcrumbs.jsx.map +1 -0
- package/dist/themes/default/content/pager.d.ts +5 -0
- package/dist/themes/default/content/pager.d.ts.map +1 -0
- package/dist/themes/default/content/pager.jsx +9 -0
- package/dist/themes/default/content/pager.jsx.map +1 -0
- package/dist/themes/default/content/table-of-contents.d.ts +4 -0
- package/dist/themes/default/content/table-of-contents.d.ts.map +1 -0
- package/dist/themes/default/content/table-of-contents.jsx +19 -0
- package/dist/themes/default/content/table-of-contents.jsx.map +1 -0
- package/dist/themes/default/index.d.ts +11 -0
- package/dist/themes/default/index.d.ts.map +1 -0
- package/dist/themes/default/index.js +30 -0
- package/dist/themes/default/index.js.map +1 -0
- package/dist/themes/default/layout.d.ts +4 -0
- package/dist/themes/default/layout.d.ts.map +1 -0
- package/dist/themes/default/layout.jsx +75 -0
- package/dist/themes/default/layout.jsx.map +1 -0
- package/dist/themes/default/navbar.d.ts +4 -0
- package/dist/themes/default/navbar.d.ts.map +1 -0
- package/dist/themes/default/navbar.jsx +74 -0
- package/dist/themes/default/navbar.jsx.map +1 -0
- package/dist/themes/default/navigation/mobile-table-of-contents.d.ts +4 -0
- package/dist/themes/default/navigation/mobile-table-of-contents.d.ts.map +1 -0
- package/dist/themes/default/navigation/mobile-table-of-contents.jsx +22 -0
- package/dist/themes/default/navigation/mobile-table-of-contents.jsx.map +1 -0
- package/dist/themes/default/navigation/sidebar-tree.d.ts +8 -0
- package/dist/themes/default/navigation/sidebar-tree.d.ts.map +1 -0
- package/dist/themes/default/navigation/sidebar-tree.jsx +100 -0
- package/dist/themes/default/navigation/sidebar-tree.jsx.map +1 -0
- package/dist/themes/default/overlays/search-dialog.d.ts +4 -0
- package/dist/themes/default/overlays/search-dialog.d.ts.map +1 -0
- package/dist/themes/default/overlays/search-dialog.jsx +34 -0
- package/dist/themes/default/overlays/search-dialog.jsx.map +1 -0
- package/dist/themes/default/sidebar.d.ts +4 -0
- package/dist/themes/default/sidebar.d.ts.map +1 -0
- package/dist/themes/default/sidebar.jsx +60 -0
- package/dist/themes/default/sidebar.jsx.map +1 -0
- package/dist/themes/index.d.ts +4 -0
- package/dist/themes/index.d.ts.map +1 -0
- package/dist/themes/index.js +5 -0
- package/dist/themes/index.js.map +1 -0
- package/dist/themes/types.d.ts +83 -0
- package/dist/themes/types.d.ts.map +1 -0
- package/dist/themes/types.js +2 -0
- package/dist/themes/types.js.map +1 -0
- package/dist/types.d.ts +97 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/vendor/core-src/index.css +11 -0
- package/dist/vendor/core-src/index.ts +48 -0
- package/dist/vendor/core-src/lib/cn.ts +9 -0
- package/dist/vendor/core-src/registry/blocks/forgot-password-01.tsx +141 -0
- package/dist/vendor/core-src/registry/blocks/hero-01.tsx +28 -0
- package/dist/vendor/core-src/registry/blocks/login-01.tsx +231 -0
- package/dist/vendor/core-src/registry/blocks/otp-verification-01.tsx +170 -0
- package/dist/vendor/core-src/registry/blocks/register-01.tsx +318 -0
- package/dist/vendor/core-src/registry/components/ui/accordion.tsx +128 -0
- package/dist/vendor/core-src/registry/components/ui/alert.tsx +155 -0
- package/dist/vendor/core-src/registry/components/ui/api-table.tsx +117 -0
- package/dist/vendor/core-src/registry/components/ui/aspect-ratio.tsx +31 -0
- package/dist/vendor/core-src/registry/components/ui/avatar.tsx +114 -0
- package/dist/vendor/core-src/registry/components/ui/badge.tsx +56 -0
- package/dist/vendor/core-src/registry/components/ui/banner.tsx +187 -0
- package/dist/vendor/core-src/registry/components/ui/breadcrumb.tsx +136 -0
- package/dist/vendor/core-src/registry/components/ui/bubble.tsx +142 -0
- package/dist/vendor/core-src/registry/components/ui/button-group.tsx +42 -0
- package/dist/vendor/core-src/registry/components/ui/button.tsx +82 -0
- package/dist/vendor/core-src/registry/components/ui/callout.tsx +137 -0
- package/dist/vendor/core-src/registry/components/ui/card.tsx +113 -0
- package/dist/vendor/core-src/registry/components/ui/checkbox.tsx +74 -0
- package/dist/vendor/core-src/registry/components/ui/code-block.tsx +326 -0
- package/dist/vendor/core-src/registry/components/ui/code-group.tsx +105 -0
- package/dist/vendor/core-src/registry/components/ui/collapsible.tsx +73 -0
- package/dist/vendor/core-src/registry/components/ui/combobox.tsx +299 -0
- package/dist/vendor/core-src/registry/components/ui/command.tsx +378 -0
- package/dist/vendor/core-src/registry/components/ui/component-viewer.tsx +363 -0
- package/dist/vendor/core-src/registry/components/ui/container.tsx +45 -0
- package/dist/vendor/core-src/registry/components/ui/context-menu.tsx +237 -0
- package/dist/vendor/core-src/registry/components/ui/dialog.tsx +200 -0
- package/dist/vendor/core-src/registry/components/ui/dropdown-menu.tsx +298 -0
- package/dist/vendor/core-src/registry/components/ui/dropzone.tsx +189 -0
- package/dist/vendor/core-src/registry/components/ui/empty.tsx +83 -0
- package/dist/vendor/core-src/registry/components/ui/field.tsx +71 -0
- package/dist/vendor/core-src/registry/components/ui/file-tree.tsx +181 -0
- package/dist/vendor/core-src/registry/components/ui/footer.tsx +247 -0
- package/dist/vendor/core-src/registry/components/ui/form-message.tsx +36 -0
- package/dist/vendor/core-src/registry/components/ui/form.tsx +21 -0
- package/dist/vendor/core-src/registry/components/ui/hover-card.tsx +105 -0
- package/dist/vendor/core-src/registry/components/ui/icon-button.tsx +34 -0
- package/dist/vendor/core-src/registry/components/ui/input-group.tsx +80 -0
- package/dist/vendor/core-src/registry/components/ui/input.tsx +28 -0
- package/dist/vendor/core-src/registry/components/ui/kbd.tsx +73 -0
- package/dist/vendor/core-src/registry/components/ui/label.tsx +30 -0
- package/dist/vendor/core-src/registry/components/ui/list.tsx +222 -0
- package/dist/vendor/core-src/registry/components/ui/logo.tsx +68 -0
- package/dist/vendor/core-src/registry/components/ui/marker.tsx +102 -0
- package/dist/vendor/core-src/registry/components/ui/marquee.tsx +118 -0
- package/dist/vendor/core-src/registry/components/ui/message.tsx +168 -0
- package/dist/vendor/core-src/registry/components/ui/navbar.tsx +375 -0
- package/dist/vendor/core-src/registry/components/ui/navigation-menu.tsx +358 -0
- package/dist/vendor/core-src/registry/components/ui/number-input.tsx +150 -0
- package/dist/vendor/core-src/registry/components/ui/package-manager-tabs.tsx +226 -0
- package/dist/vendor/core-src/registry/components/ui/pager.tsx +102 -0
- package/dist/vendor/core-src/registry/components/ui/pagination.tsx +258 -0
- package/dist/vendor/core-src/registry/components/ui/pin-input.tsx +198 -0
- package/dist/vendor/core-src/registry/components/ui/popover.tsx +90 -0
- package/dist/vendor/core-src/registry/components/ui/progress.tsx +66 -0
- package/dist/vendor/core-src/registry/components/ui/radio-group.tsx +95 -0
- package/dist/vendor/core-src/registry/components/ui/rating.tsx +185 -0
- package/dist/vendor/core-src/registry/components/ui/resizable.tsx +195 -0
- package/dist/vendor/core-src/registry/components/ui/review-card.tsx +195 -0
- package/dist/vendor/core-src/registry/components/ui/rich-text-editor/bubble-menu.tsx +236 -0
- package/dist/vendor/core-src/registry/components/ui/rich-text-editor/editor.tsx +308 -0
- package/dist/vendor/core-src/registry/components/ui/rich-text-editor/extensions.ts +116 -0
- package/dist/vendor/core-src/registry/components/ui/rich-text-editor/footer.tsx +36 -0
- package/dist/vendor/core-src/registry/components/ui/rich-text-editor/image-dialog.tsx +75 -0
- package/dist/vendor/core-src/registry/components/ui/rich-text-editor/index.ts +11 -0
- package/dist/vendor/core-src/registry/components/ui/rich-text-editor/link-dialog.tsx +48 -0
- package/dist/vendor/core-src/registry/components/ui/rich-text-editor/markdown.ts +170 -0
- package/dist/vendor/core-src/registry/components/ui/rich-text-editor/slash-command.tsx +301 -0
- package/dist/vendor/core-src/registry/components/ui/rich-text-editor/table-controls.tsx +68 -0
- package/dist/vendor/core-src/registry/components/ui/rich-text-editor/toolbar.tsx +465 -0
- package/dist/vendor/core-src/registry/components/ui/rich-text-editor/use-editor.ts +231 -0
- package/dist/vendor/core-src/registry/components/ui/scroll-area.tsx +228 -0
- package/dist/vendor/core-src/registry/components/ui/section-heading.tsx +108 -0
- package/dist/vendor/core-src/registry/components/ui/select.tsx +156 -0
- package/dist/vendor/core-src/registry/components/ui/separator.tsx +37 -0
- package/dist/vendor/core-src/registry/components/ui/sheet.tsx +220 -0
- package/dist/vendor/core-src/registry/components/ui/sidebar.tsx +692 -0
- package/dist/vendor/core-src/registry/components/ui/skeleton.tsx +24 -0
- package/dist/vendor/core-src/registry/components/ui/slider.tsx +153 -0
- package/dist/vendor/core-src/registry/components/ui/spinner.tsx +41 -0
- package/dist/vendor/core-src/registry/components/ui/stat.tsx +245 -0
- package/dist/vendor/core-src/registry/components/ui/status.tsx +121 -0
- package/dist/vendor/core-src/registry/components/ui/steps.tsx +198 -0
- package/dist/vendor/core-src/registry/components/ui/switch.tsx +68 -0
- package/dist/vendor/core-src/registry/components/ui/table-of-contents.tsx +131 -0
- package/dist/vendor/core-src/registry/components/ui/table.tsx +160 -0
- package/dist/vendor/core-src/registry/components/ui/tabs.tsx +204 -0
- package/dist/vendor/core-src/registry/components/ui/textarea.tsx +82 -0
- package/dist/vendor/core-src/registry/components/ui/theme-toggle.tsx +238 -0
- package/dist/vendor/core-src/registry/components/ui/timeline.tsx +350 -0
- package/dist/vendor/core-src/registry/components/ui/titlebar-tabs.tsx +773 -0
- package/dist/vendor/core-src/registry/components/ui/titlebar.tsx +468 -0
- package/dist/vendor/core-src/registry/components/ui/toast.tsx +124 -0
- package/dist/vendor/core-src/registry/components/ui/toggle-group.tsx +203 -0
- package/dist/vendor/core-src/registry/components/ui/toggle.tsx +101 -0
- package/dist/vendor/core-src/registry/components/ui/tooltip.tsx +41 -0
- package/dist/vendor/core-src/registry/components/ui/updater-modal.tsx +254 -0
- package/dist/vendor/core-src/registry/index.ts +50 -0
- package/dist/vendor/core-src/registry/metadata.ts +742 -0
- package/dist/vendor/core-src/registry/providers/theme-provider.tsx +216 -0
- package/dist/vendor/core-src/registry/providers/theme-script.tsx +78 -0
- package/dist/vendor/core-src/registry/providers/theme-transitions.ts +139 -0
- package/dist/vendor/hooks-src/create-active-element.ts +51 -0
- package/dist/vendor/hooks-src/create-app-updater.ts +272 -0
- package/dist/vendor/hooks-src/create-audio-video.ts +444 -0
- package/dist/vendor/hooks-src/create-battery.ts +78 -0
- package/dist/vendor/hooks-src/create-chat-scroll.ts +113 -0
- package/dist/vendor/hooks-src/create-click-outside.ts +82 -0
- package/dist/vendor/hooks-src/create-clipboard.ts +69 -0
- package/dist/vendor/hooks-src/create-color-mode.ts +115 -0
- package/dist/vendor/hooks-src/create-controllable-signal.ts +56 -0
- package/dist/vendor/hooks-src/create-debounce.ts +90 -0
- package/dist/vendor/hooks-src/create-disclosure.ts +82 -0
- package/dist/vendor/hooks-src/create-document-tabs.ts +267 -0
- package/dist/vendor/hooks-src/create-document-title.ts +33 -0
- package/dist/vendor/hooks-src/create-drop-zone.ts +317 -0
- package/dist/vendor/hooks-src/create-event-source.ts +126 -0
- package/dist/vendor/hooks-src/create-favicon.ts +52 -0
- package/dist/vendor/hooks-src/create-fetch.ts +93 -0
- package/dist/vendor/hooks-src/create-focus-trap.ts +94 -0
- package/dist/vendor/hooks-src/create-form.ts +200 -0
- package/dist/vendor/hooks-src/create-fullscreen.ts +105 -0
- package/dist/vendor/hooks-src/create-geolocation.ts +140 -0
- package/dist/vendor/hooks-src/create-global-shortcut.ts +140 -0
- package/dist/vendor/hooks-src/create-hover.ts +88 -0
- package/dist/vendor/hooks-src/create-idle.ts +109 -0
- package/dist/vendor/hooks-src/create-infinite-scroll.ts +90 -0
- package/dist/vendor/hooks-src/create-input-mask.ts +109 -0
- package/dist/vendor/hooks-src/create-intersection-observer.ts +82 -0
- package/dist/vendor/hooks-src/create-keybindings.ts +148 -0
- package/dist/vendor/hooks-src/create-lock-scroll.ts +97 -0
- package/dist/vendor/hooks-src/create-long-press.ts +85 -0
- package/dist/vendor/hooks-src/create-media-query.ts +94 -0
- package/dist/vendor/hooks-src/create-mouse-position.ts +87 -0
- package/dist/vendor/hooks-src/create-network-status.ts +100 -0
- package/dist/vendor/hooks-src/create-orientation.ts +129 -0
- package/dist/vendor/hooks-src/create-pagination.ts +208 -0
- package/dist/vendor/hooks-src/create-permission.ts +91 -0
- package/dist/vendor/hooks-src/create-previous.ts +21 -0
- package/dist/vendor/hooks-src/create-resize-observer.ts +89 -0
- package/dist/vendor/hooks-src/create-scroll-into-view.ts +57 -0
- package/dist/vendor/hooks-src/create-scroll-position.ts +129 -0
- package/dist/vendor/hooks-src/create-storage.ts +106 -0
- package/dist/vendor/hooks-src/create-tauri-window.ts +628 -0
- package/dist/vendor/hooks-src/create-timer.ts +150 -0
- package/dist/vendor/hooks-src/create-tiptap-editor.ts +326 -0
- package/dist/vendor/hooks-src/create-undo-redo.ts +100 -0
- package/dist/vendor/hooks-src/create-web-notification.ts +111 -0
- package/dist/vendor/hooks-src/create-websocket.ts +179 -0
- package/dist/vendor/hooks-src/create-window-size.ts +41 -0
- package/dist/vendor/hooks-src/index.ts +48 -0
- package/package.json +87 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "command",
|
|
3
|
+
"title": "Command / Command Palette",
|
|
4
|
+
"description": "Fast, accessible command palette and search modal built on Kobalte Dialog primitives with auto-filtering.",
|
|
5
|
+
"type": "registry:ui",
|
|
6
|
+
"dependencies": [
|
|
7
|
+
"clsx",
|
|
8
|
+
"tailwind-merge",
|
|
9
|
+
"class-variance-authority",
|
|
10
|
+
"lucide-solid",
|
|
11
|
+
"@kobalte/core"
|
|
12
|
+
],
|
|
13
|
+
"registryDependencies": [
|
|
14
|
+
"kbd",
|
|
15
|
+
"input-group",
|
|
16
|
+
"list",
|
|
17
|
+
"scroll-area",
|
|
18
|
+
"create-keybindings"
|
|
19
|
+
],
|
|
20
|
+
"files": [
|
|
21
|
+
{
|
|
22
|
+
"path": "ui/command.tsx",
|
|
23
|
+
"content": "import {\n createContext,\n useContext,\n createSignal,\n onCleanup,\n Show,\n splitProps,\n type Component,\n type JSX,\n type Accessor,\n} from \"solid-js\";\nimport { createKeybindings } from \"@/hooks/create-keybindings\";\nimport { Dialog } from \"@kobalte/core/dialog\";\nimport { Search, ArrowUp, ArrowDown, CornerDownLeft } from \"lucide-solid\";\nimport { cn } from \"@/lib/cn\";\nimport { Kbd, KbdGroup } from \"../ui/kbd\";\nimport { InputGroup, InputGroupInput, InputGroupAddon } from \"../ui/input-group\";\nimport { List, ListGroup, ListHeader, ListItem, type ListItemProps } from \"../ui/list\";\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\n\n/* --- Command Context State --- */\ninterface CommandContextValue {\n search: Accessor<string>;\n setSearch: (value: string) => void;\n activeIndex: Accessor<number>;\n setActiveIndex: (fn: (prev: number) => number) => void;\n registerItemIndex: (element: HTMLElement) => number;\n onItemSelect: (index: number, action?: () => void) => void;\n}\n\nconst CommandContext = createContext<CommandContextValue>();\n\nexport const useCommand = () => {\n const ctx = useContext(CommandContext);\n if (!ctx) {\n throw new Error(\"useCommand must be used within a <Command />\");\n }\n return ctx;\n};\n\n/* --- Root Command Container --- */\nexport interface CommandProps extends Omit<JSX.HTMLAttributes<HTMLDivElement>, \"children\"> {\n class?: string;\n children?: JSX.Element | ((ctx: CommandContextValue) => JSX.Element);\n}\n\nexport const Command: Component<CommandProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n const [search, setSearch] = createSignal(\"\");\n const [activeIndex, setActiveIndex] = createSignal(0);\n\n let containerRef: HTMLDivElement | undefined;\n let itemsList: HTMLElement[] = [];\n\n const registerItemIndex = (element: HTMLElement) => {\n if (!itemsList.includes(element)) {\n itemsList.push(element);\n }\n return itemsList.indexOf(element);\n };\n\n const onItemSelect = (index: number, action?: () => void) => {\n setActiveIndex(index);\n if (action) action();\n };\n\n const getVisibleItems = () => {\n if (!containerRef) return [];\n return Array.from(containerRef.querySelectorAll<HTMLElement>(\"[data-command-item]:not(.hidden)\"));\n };\n\n const updateActiveAttribute = (index: number) => {\n const visible = getVisibleItems();\n visible.forEach((el, idx) => {\n if (idx === index) {\n el.setAttribute(\"aria-selected\", \"true\");\n el.scrollIntoView({ block: \"nearest\" });\n } else {\n el.removeAttribute(\"aria-selected\");\n }\n });\n };\n\n const handleKeyDown = (e: KeyboardEvent) => {\n const visible = getVisibleItems();\n if (visible.length === 0) return;\n\n if (e.key === \"ArrowDown\") {\n e.preventDefault();\n const next = (activeIndex() + 1) % visible.length;\n setActiveIndex(next);\n updateActiveAttribute(next);\n } else if (e.key === \"ArrowUp\") {\n e.preventDefault();\n const prev = (activeIndex() - 1 + visible.length) % visible.length;\n setActiveIndex(prev);\n updateActiveAttribute(prev);\n } else if (e.key === \"Enter\") {\n e.preventDefault();\n const current = visible[activeIndex()];\n if (current) {\n current.click();\n }\n }\n };\n\n const ctx: CommandContextValue = {\n search,\n setSearch: (val) => {\n setSearch(val);\n setActiveIndex(0);\n },\n activeIndex,\n setActiveIndex: (fn) => setActiveIndex(fn),\n registerItemIndex,\n onItemSelect,\n };\n\n return (\n <CommandContext.Provider value={ctx}>\n <div\n ref={containerRef}\n onKeyDown={handleKeyDown}\n class={cn(\n \"flex flex-col w-full h-full rounded-lg bg-popover text-popover-foreground overflow-hidden border border-border shadow-md outline-none\",\n local.class\n )}\n tabIndex={0}\n {...rest}\n >\n {typeof local.children === \"function\" ? (local.children as any)(ctx) : local.children}\n </div>\n </CommandContext.Provider>\n );\n};\n\n/* --- Command Dialog (Modal) --- */\nexport interface CommandDialogProps {\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n enableHotkey?: boolean;\n children?: JSX.Element | ((ctx: CommandContextValue) => JSX.Element);\n class?: string;\n}\n\nexport const CommandDialog: Component<CommandDialogProps> = (props) => {\n const [internalOpen, setInternalOpen] = createSignal(false);\n\n const isOpen = () => (props.open !== undefined ? props.open : internalOpen());\n const setOpen = (val: boolean) => {\n if (props.open === undefined) setInternalOpen(val);\n if (typeof props.onOpenChange === \"function\") props.onOpenChange(val);\n };\n\n /* Listen for global Ctrl+K / Cmd+K hotkeys */\n createKeybindings(\n [\n {\n key: [\"meta+k\", \"ctrl+k\"],\n handler: () => setOpen(!isOpen()),\n preventDefault: true,\n },\n ],\n {\n enabled: () => props.enableHotkey !== false,\n }\n );\n\n return (\n <Dialog open={isOpen()} onOpenChange={setOpen}>\n <Dialog.Portal>\n <Dialog.Overlay class=\"fixed inset-0 z-50 bg-black/60 backdrop-blur-xs data-expanded:animate-in data-closed:animate-out data-[closed]:fade-out-0 data-[expanded]:fade-in-0\" />\n <div class=\"fixed inset-0 z-50 flex items-start justify-center pt-16 sm:pt-24 px-4\">\n <Dialog.Content class=\"w-full max-w-xl rounded-lg border border-border bg-popover text-popover-foreground shadow-2xl outline-none data-expanded:animate-in data-closed:animate-out data-[closed]:fade-out-0 data-[expanded]:fade-in-0 data-closed:zoom-out-95 data-expanded:zoom-in-95\">\n <Command class={props.class}>{props.children}</Command>\n </Dialog.Content>\n </div>\n </Dialog.Portal>\n </Dialog>\n );\n};\n\n/* --- Command Input --- */\nexport interface CommandInputProps\n extends JSX.InputHTMLAttributes<HTMLInputElement> {\n class?: string;\n}\n\nexport const CommandInput: Component<CommandInputProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"value\", \"onInput\"]);\n const { search, setSearch } = useCommand();\n\n return (\n <InputGroup class=\"border-0 border-b border-border rounded-none bg-transparent px-3 py-1 shadow-none focus-within:ring-0 focus-within:border-border\">\n <InputGroupAddon align=\"inline-start\">\n <Search class=\"w-4 h-4 text-muted-foreground\" />\n </InputGroupAddon>\n\n <InputGroupInput\n value={search()}\n onInput={(e) => {\n setSearch(e.currentTarget.value);\n if (typeof local.onInput === \"function\") {\n local.onInput(e);\n }\n }}\n placeholder=\"Type a command or search...\"\n class=\"h-11 text-base sm:text-sm font-medium\"\n {...rest}\n />\n </InputGroup>\n );\n};\n\n/* --- Command List Container --- */\nexport interface CommandListProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const CommandList: Component<CommandListProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <ScrollArea\n class={cn(\"max-h-82.5 p-1.5\", local.class)}\n {...rest}\n >\n <List>{local.children}</List>\n </ScrollArea>\n );\n};\n\n/* --- Command Empty Block --- */\nexport interface CommandEmptyProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const CommandEmpty: Component<CommandEmptyProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n const { search } = useCommand();\n\n return (\n <Show when={search().trim().length > 0}>\n <div\n class={cn(\n \"py-8 text-center text-sm text-muted-foreground font-medium select-none\",\n local.class\n )}\n {...rest}\n >\n {local.children || `No results found for \"${search()}\".`}\n </div>\n </Show>\n );\n};\n\n/* --- Command Group --- */\nexport interface CommandGroupProps {\n heading: string;\n children?: JSX.Element;\n class?: string;\n}\n\nexport const CommandGroup: Component<CommandGroupProps> = (props) => {\n return (\n <ListGroup class={props.class}>\n <ListHeader title={props.heading} />\n {props.children}\n </ListGroup>\n );\n};\n\n/* --- Command Item --- */\nexport interface CommandItemProps extends ListItemProps {\n keywords?: string[];\n description?: string;\n onSelect?: () => void;\n shouldFilter?: boolean;\n}\n\nexport const CommandItem: Component<CommandItemProps> = (props) => {\n let itemRef: HTMLDivElement | undefined;\n const [local, rest] = splitProps(props, [\n \"title\",\n \"subtitle\",\n \"description\",\n \"keywords\",\n \"onSelect\",\n \"onClick\",\n \"shouldFilter\",\n \"class\",\n ]);\n const { search } = useCommand();\n\n /* Auto filter item based on search query */\n const matchesSearch = () => {\n if (local.shouldFilter === false) return true;\n const query = search().toLowerCase().trim();\n if (!query) return true;\n\n const titleMatch = local.title?.toLowerCase().includes(query);\n const subtitleMatch = local.subtitle?.toLowerCase().includes(query);\n const descMatch = local.description?.toLowerCase().includes(query);\n const keywordMatch = local.keywords?.some((k) =>\n k.toLowerCase().includes(query)\n );\n\n return Boolean(titleMatch || subtitleMatch || descMatch || keywordMatch);\n };\n\n const handleClick = (e: MouseEvent) => {\n if (typeof local.onSelect === \"function\") {\n local.onSelect();\n }\n if (typeof local.onClick === \"function\") {\n local.onClick(e as any);\n }\n };\n\n return (\n <Show when={matchesSearch()}>\n <ListItem\n ref={itemRef}\n data-command-item=\"true\"\n title={local.title}\n subtitle={local.subtitle || local.description}\n onClick={handleClick}\n class={cn(\n \"aria-selected:bg-accent aria-selected:text-accent-foreground cursor-pointer transition-colors hover:bg-accent hover:text-accent-foreground\",\n local.class\n )}\n {...rest}\n />\n </Show>\n );\n};\n\n/* --- Command Footer --- */\nexport interface CommandFooterProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n children?: JSX.Element;\n}\n\nexport const CommandFooter: Component<CommandFooterProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <div\n class={cn(\n \"flex items-center justify-between border-t border-border bg-muted/30 px-3 py-2 text-xs text-muted-foreground select-none\",\n local.class\n )}\n {...rest}\n >\n <div class=\"flex items-center gap-3\">\n <span class=\"flex items-center gap-1\">\n <KbdGroup>\n <Kbd size=\"sm\"><ArrowUp class=\"w-2.5 h-2.5\" /></Kbd>\n <Kbd size=\"sm\"><ArrowDown class=\"w-2.5 h-2.5\" /></Kbd>\n </KbdGroup>\n <span>Navigate</span>\n </span>\n\n <span class=\"flex items-center gap-1\">\n <Kbd size=\"sm\"><CornerDownLeft class=\"w-2.5 h-2.5\" /></Kbd>\n <span>Select</span>\n </span>\n\n <span class=\"flex items-center gap-1\">\n <Kbd size=\"sm\">Esc</Kbd>\n <span>Close</span>\n </span>\n </div>\n\n {local.children}\n </div>\n );\n};",
|
|
24
|
+
"type": "registry:ui"
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "component-viewer",
|
|
3
|
+
"title": "Component Viewer",
|
|
4
|
+
"description": "An interactive preview canvas and code viewer container with responsive viewports, canvas grid, and AI prompts.",
|
|
5
|
+
"type": "registry:ui",
|
|
6
|
+
"dependencies": [
|
|
7
|
+
"clsx",
|
|
8
|
+
"tailwind-merge",
|
|
9
|
+
"lucide-solid"
|
|
10
|
+
],
|
|
11
|
+
"registryDependencies": [
|
|
12
|
+
"tabs",
|
|
13
|
+
"button",
|
|
14
|
+
"tooltip",
|
|
15
|
+
"code-block",
|
|
16
|
+
"create-clipboard"
|
|
17
|
+
],
|
|
18
|
+
"files": [
|
|
19
|
+
{
|
|
20
|
+
"path": "ui/component-viewer.tsx",
|
|
21
|
+
"content": "import {\n createSignal,\n splitProps,\n Show,\n type JSX,\n type ParentComponent,\n} from \"solid-js\";\nimport { Portal } from \"solid-js/web\";\nimport {\n Monitor,\n Tablet,\n Smartphone,\n Sparkles,\n RotateCcw,\n Terminal,\n Check,\n Maximize2,\n Minimize2,\n Grid,\n} from \"lucide-solid\";\nimport { Tabs, TabsList, TabsTrigger, TabsContent } from \"@/components/ui/tabs\";\nimport { Button } from \"@/components/ui/button\";\nimport { Tooltip, TooltipTrigger, TooltipContent } from \"@/components/ui/tooltip\";\nimport { CodeBlock } from \"@/components/ui/code-block\";\nimport { createClipboard } from \"@/hooks/create-clipboard\";\nimport { cn } from \"@/lib/cn\";\n\nexport type ComponentViewerViewport = \"desktop\" | \"tablet\" | \"mobile\";\n\nexport interface ComponentViewerProps extends JSX.HTMLAttributes<HTMLDivElement> {\n /** Name of the component or UI primitive (e.g. \"button\", \"dialog\") */\n name?: string;\n /** Title label displayed in the viewer header */\n title?: string;\n /** Raw TSX/JSX source code to display in the Code tab and copy */\n code: string;\n /** Programming language for syntax highlighting (defaults to \"tsx\") */\n lang?: string;\n /** Alignment of the preview canvas: \"center\" (default), \"start\", or \"end\" */\n align?: \"center\" | \"start\" | \"end\";\n /** Optional CLI command to install this component (e.g. \"bunx @nikala-ui/cli add button\") */\n command?: string;\n /** Initial responsive viewport (default: \"desktop\") */\n defaultViewport?: ComponentViewerViewport;\n /** Whether to allow overflowing elements in the canvas (e.g. popovers, dropdowns) */\n allowOverflow?: boolean;\n /** Enables responsive screen size toggle controls (Desktop, Tablet, Mobile) */\n responsive?: boolean;\n /** Enables canvas grid pattern toggle */\n showGridToggle?: boolean;\n /** Enables a reset button to re-mount the preview canvas */\n reRenderable?: boolean;\n class?: string;\n}\n\n/**\n * Advanced component inspection and preview container with responsive viewport emulation,\n * background grid patterns, live re-mounting, AI context generation, and syntax-highlighted code.\n */\nexport const ComponentViewer: ParentComponent<ComponentViewerProps> = (props) => {\n const [local, rest] = splitProps(props, [\n \"name\",\n \"title\",\n \"code\",\n \"lang\",\n \"align\",\n \"command\",\n \"defaultViewport\",\n \"allowOverflow\",\n \"responsive\",\n \"showGridToggle\",\n \"reRenderable\",\n \"class\",\n \"children\",\n ]);\n\n const [viewport, setViewport] = createSignal<ComponentViewerViewport>(\n local.defaultViewport || \"desktop\"\n );\n const [showGrid, setShowGrid] = createSignal(true);\n const [isFullscreen, setIsFullscreen] = createSignal(false);\n const aiClipboard = createClipboard();\n const cmdClipboard = createClipboard();\n const [mounted, setMounted] = createSignal(true);\n\n const cliCommand = () => local.command;\n\n const handleCopyAi = async () => {\n const cmd = cliCommand();\n const prompt = `// ${local.title || local.name || \"Component\"}\n${cmd ? `// Installation: ${cmd}\\n` : \"\"}// Usage Code:\n${local.code}`;\n await aiClipboard.copy(prompt);\n };\n\n const handleCopyCmd = async () => {\n const cmd = cliCommand();\n if (!cmd) return;\n await cmdClipboard.copy(cmd);\n };\n\n const handleReset = () => {\n setMounted(false);\n setTimeout(() => setMounted(true), 20);\n };\n\n const alignmentClass = () => {\n if (local.align === \"start\") return \"items-start justify-start\";\n if (local.align === \"end\") return \"items-end justify-end\";\n return \"items-center justify-center\";\n };\n\n const viewportWidthClass = () => {\n switch (viewport()) {\n case \"mobile\":\n return \"max-w-[375px]\";\n case \"tablet\":\n return \"max-w-[768px]\";\n default:\n return \"max-w-full\";\n }\n };\n\n const renderViewerContent = (isModal = false) => (\n <div\n class={cn(\n \"group relative flex flex-col rounded-lg border border-border bg-card/60 shadow-2xs transition-all w-full\",\n isModal && \"h-full flex-1 bg-background shadow-2xl\",\n !isModal && \"my-6\",\n local.class\n )}\n {...rest}\n >\n <Tabs defaultValue=\"preview\" class=\"relative w-full flex flex-col h-full flex-1 gap-0\">\n {/* Top Header & Toolbar */}\n <div class=\"flex flex-wrap items-center justify-between border-b border-border/70 px-3 py-2 gap-2 bg-muted/30 shrink-0\">\n <div class=\"flex items-center gap-3\">\n <TabsList class=\"h-8 rounded-md bg-muted/60 p-0.5 gap-1\">\n <TabsTrigger value=\"preview\" class=\"h-7 px-3 text-xs cursor-pointer data-[state=active]:bg-background data-[state=active]:shadow-2xs\">\n Preview\n </TabsTrigger>\n <TabsTrigger value=\"code\" class=\"h-7 px-3 text-xs cursor-pointer data-[state=active]:bg-background data-[state=active]:shadow-2xs\">\n Code\n </TabsTrigger>\n </TabsList>\n\n <Show when={local.title}>\n <span class=\"text-xs font-semibold text-foreground tracking-tight hidden sm:inline-block\">\n {local.title}\n </span>\n </Show>\n </div>\n\n {/* Right Action Tools */}\n <div class=\"flex flex-wrap items-center gap-1.5 ml-auto\">\n {/* Responsive Viewport Switcher */}\n <Show when={local.responsive ?? true}>\n <div class=\"flex items-center rounded-md border border-border/60 bg-muted/40 p-0.5 gap-0.5 mr-1\">\n <Tooltip>\n <TooltipTrigger\n as={Button}\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => setViewport(\"desktop\")}\n aria-label=\"Desktop viewport\"\n class={cn(\n \"size-6.5 p-0 rounded-sm cursor-pointer text-muted-foreground\",\n viewport() === \"desktop\" && \"bg-background text-foreground shadow-2xs font-semibold\"\n )}\n >\n <Monitor class=\"size-3.5\" />\n </TooltipTrigger>\n <TooltipContent class=\"text-[10px] py-1 px-2\">Desktop (100%)</TooltipContent>\n </Tooltip>\n\n <Tooltip>\n <TooltipTrigger\n as={Button}\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => setViewport(\"tablet\")}\n aria-label=\"Tablet viewport\"\n class={cn(\n \"size-6.5 p-0 rounded-sm cursor-pointer text-muted-foreground\",\n viewport() === \"tablet\" && \"bg-background text-foreground shadow-2xs font-semibold\"\n )}\n >\n <Tablet class=\"size-3.5\" />\n </TooltipTrigger>\n <TooltipContent class=\"text-[10px] py-1 px-2\">Tablet (768px)</TooltipContent>\n </Tooltip>\n\n <Tooltip>\n <TooltipTrigger\n as={Button}\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => setViewport(\"mobile\")}\n aria-label=\"Mobile viewport\"\n class={cn(\n \"size-6.5 p-0 rounded-sm cursor-pointer text-muted-foreground\",\n viewport() === \"mobile\" && \"bg-background text-foreground shadow-2xs font-semibold\"\n )}\n >\n <Smartphone class=\"size-3.5\" />\n </TooltipTrigger>\n <TooltipContent class=\"text-[10px] py-1 px-2\">Mobile (375px)</TooltipContent>\n </Tooltip>\n </div>\n </Show>\n\n {/* Grid Pattern Toggle */}\n <Show when={local.showGridToggle ?? true}>\n <Tooltip>\n <TooltipTrigger\n as={Button}\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => setShowGrid(!showGrid())}\n aria-label=\"Toggle background grid\"\n class={cn(\n \"size-7 rounded-md border border-border/50 text-muted-foreground hover:text-foreground cursor-pointer\",\n showGrid() && \"text-foreground bg-muted/60\"\n )}\n >\n <Grid class=\"size-3.5\" />\n </TooltipTrigger>\n <TooltipContent class=\"text-[10px] py-1 px-2\">Toggle Canvas Grid</TooltipContent>\n </Tooltip>\n </Show>\n\n {/* Re-render Reset Canvas */}\n <Show when={local.reRenderable ?? true}>\n <Tooltip>\n <TooltipTrigger\n as={Button}\n variant=\"ghost\"\n size=\"icon\"\n onClick={handleReset}\n aria-label=\"Reset preview canvas\"\n class=\"size-7 rounded-md border border-border/50 text-muted-foreground hover:text-foreground cursor-pointer\"\n >\n <RotateCcw class=\"size-3.5\" />\n </TooltipTrigger>\n <TooltipContent class=\"text-[10px] py-1 px-2\">Reset canvas</TooltipContent>\n </Tooltip>\n </Show>\n\n {/* Copy for AI */}\n <Tooltip>\n <TooltipTrigger\n as={Button}\n variant=\"ghost\"\n size=\"sm\"\n onClick={handleCopyAi}\n aria-label=\"Copy context formatted for AI assistants\"\n class=\"h-7 px-2 text-xs text-muted-foreground hover:text-foreground border border-border/50 rounded-md cursor-pointer flex items-center gap-1.5\"\n >\n <Show when={aiClipboard.copied()} fallback={<Sparkles class=\"size-3\" />}>\n <Check class=\"size-3 text-emerald-500\" />\n </Show>\n <span class=\"hidden sm:inline\">{aiClipboard.copied() ? \"Copied!\" : \"Prompt\"}</span>\n </TooltipTrigger>\n <TooltipContent class=\"text-[10px] py-1 px-2\">Copy code formatted for AI</TooltipContent>\n </Tooltip>\n\n {/* Copy CLI Installation Command (only when command prop is provided) */}\n <Show when={cliCommand()}>\n <Tooltip>\n <TooltipTrigger\n as={Button}\n variant=\"ghost\"\n size=\"sm\"\n onClick={handleCopyCmd}\n aria-label=\"Copy CLI installation command\"\n class=\"h-7 px-2 text-xs font-mono text-muted-foreground hover:text-foreground border border-border/50 rounded-md cursor-pointer flex items-center gap-1.5\"\n >\n <Show when={cmdClipboard.copied()} fallback={<Terminal class=\"size-3\" />}>\n <Check class=\"size-3 text-emerald-500\" />\n </Show>\n <span class=\"hidden md:inline\">{cmdClipboard.copied() ? \"Copied!\" : cliCommand()}</span>\n </TooltipTrigger>\n <TooltipContent class=\"text-[10px] py-1 px-2\">Copy CLI command</TooltipContent>\n </Tooltip>\n </Show>\n\n {/* Fullscreen Expand Button */}\n <Tooltip>\n <TooltipTrigger\n as={Button}\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => setIsFullscreen(!isFullscreen())}\n aria-label={isFullscreen() ? \"Exit fullscreen\" : \"Expand to fullscreen\"}\n class=\"size-7 rounded-md border border-border/50 text-muted-foreground hover:text-foreground cursor-pointer\"\n >\n <Show when={isFullscreen()} fallback={<Maximize2 class=\"size-3.5\" />}>\n <Minimize2 class=\"size-3.5 text-primary\" />\n </Show>\n </TooltipTrigger>\n <TooltipContent class=\"text-[10px] py-1 px-2\">\n {isFullscreen() ? \"Exit Fullscreen\" : \"Fullscreen Preview\"}\n </TooltipContent>\n </Tooltip>\n </div>\n </div>\n\n {/* Live Preview Canvas Content */}\n <TabsContent\n value=\"preview\"\n class={cn(\n \"relative flex-1 p-4 sm:p-6 md:p-10 flex flex-col items-center justify-center transition-all\",\n isModal ? \"min-h-[450px] flex-1\" : \"min-h-[280px]\",\n showGrid() &&\n \"bg-[radial-gradient(#e5e7eb_1px,transparent_1px)] dark:bg-[radial-gradient(#27272a_1px,transparent_1px)] [background-size:16px_16px]\",\n local.allowOverflow ? \"overflow-visible\" : \"overflow-x-auto\"\n )}\n >\n <div\n class={cn(\n \"w-full transition-all duration-300 flex\",\n viewportWidthClass(),\n alignmentClass(),\n viewport() !== \"desktop\" &&\n \"rounded-lg border border-dashed border-border/80 p-4 bg-background/80 shadow-xs\"\n )}\n >\n <Show when={mounted()}>\n {local.children}\n </Show>\n </div>\n </TabsContent>\n\n {/* Source Code View Content */}\n <TabsContent value=\"code\" class={cn(\"p-0 m-0\", isModal && \"flex-1 overflow-auto\")}>\n <CodeBlock\n code={local.code}\n lang={local.lang || \"tsx\"}\n filename={local.name ? `${local.name}.tsx` : undefined}\n class=\"my-0 border-0 rounded-t-none\"\n />\n </TabsContent>\n </Tabs>\n </div>\n );\n\n return (\n <>\n <Show when={!isFullscreen()}>\n {renderViewerContent(false)}\n </Show>\n\n {/* Fullscreen Portal Modal */}\n <Show when={isFullscreen()}>\n <Portal mount={typeof document !== \"undefined\" ? document.body : undefined}>\n <div class=\"fixed inset-0 z-[9999] flex flex-col bg-background/95 backdrop-blur-md p-4 sm:p-8 animate-in fade-in duration-200\">\n {renderViewerContent(true)}\n </div>\n </Portal>\n </Show>\n </>\n );\n};\n",
|
|
22
|
+
"type": "registry:ui"
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "container",
|
|
3
|
+
"title": "Container",
|
|
4
|
+
"description": "A responsive layout container constraining maximum width with semantic padding tokens.",
|
|
5
|
+
"type": "registry:ui",
|
|
6
|
+
"dependencies": [
|
|
7
|
+
"clsx",
|
|
8
|
+
"tailwind-merge",
|
|
9
|
+
"class-variance-authority"
|
|
10
|
+
],
|
|
11
|
+
"files": [
|
|
12
|
+
{
|
|
13
|
+
"path": "ui/container.tsx",
|
|
14
|
+
"content": "import { splitProps, type JSX, type ValidComponent } from \"solid-js\";\nimport { Dynamic } from \"solid-js/web\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"@/lib/cn\";\n\nexport const containerVariants = cva(\"w-full mx-auto px-4 sm:px-6 lg:px-8\", {\n variants: {\n size: {\n sm: \"max-w-screen-sm\",\n md: \"max-w-screen-md\",\n lg: \"max-w-screen-lg\",\n xl: \"max-w-screen-xl\",\n \"2xl\": \"max-w-screen-2xl\",\n full: \"max-w-full\",\n },\n },\n defaultVariants: {\n size: \"2xl\",\n },\n});\n\nexport interface ContainerProps<T extends ValidComponent = \"div\">\n extends JSX.HTMLAttributes<HTMLDivElement>,\n VariantProps<typeof containerVariants> {\n /** Underlying HTML element or Solid component tag (defaults to \"div\") */\n as?: T;\n class?: string;\n}\n\n/**\n * Responsive container primitive constraining max-width with semantic padding tokens.\n */\nexport const Container = <T extends ValidComponent = \"div\">(props: ContainerProps<T>) => {\n const [local, rest] = splitProps(props as ContainerProps, [\"as\", \"size\", \"class\", \"children\"]);\n\n return (\n <Dynamic\n component={local.as || \"div\"}\n class={cn(containerVariants({ size: local.size }), local.class)}\n {...rest}\n >\n {local.children}\n </Dynamic>\n );\n};\n",
|
|
15
|
+
"type": "registry:ui"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "context-menu",
|
|
3
|
+
"title": "Context Menu",
|
|
4
|
+
"description": "Displays a contextual popup menu triggered by right-clicking target areas, built on Kobalte primitives.",
|
|
5
|
+
"type": "registry:ui",
|
|
6
|
+
"dependencies": [
|
|
7
|
+
"clsx",
|
|
8
|
+
"tailwind-merge",
|
|
9
|
+
"@kobalte/core"
|
|
10
|
+
],
|
|
11
|
+
"registryDependencies": [
|
|
12
|
+
"separator",
|
|
13
|
+
"kbd",
|
|
14
|
+
"create-click-outside",
|
|
15
|
+
"scroll-area"
|
|
16
|
+
],
|
|
17
|
+
"files": [
|
|
18
|
+
{
|
|
19
|
+
"path": "ui/context-menu.tsx",
|
|
20
|
+
"content": "import { splitProps, type Component, type JSX, type ValidComponent } from \"solid-js\";\nimport * as ContextMenuPrimitive from \"@kobalte/core/context-menu\";\nimport type { PolymorphicProps } from \"@kobalte/core/polymorphic\";\nimport { createClickOutside } from \"@/hooks/create-click-outside\";\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { Kbd } from \"@/components/ui/kbd\";\nimport { cn } from \"@/lib/cn\";\n\nexport const ContextMenu = ContextMenuPrimitive.Root;\nexport const ContextMenuTrigger = ContextMenuPrimitive.Trigger;\nexport const ContextMenuGroup = ContextMenuPrimitive.Group;\nexport const ContextMenuPortal = ContextMenuPrimitive.Portal;\nexport const ContextMenuSub = ContextMenuPrimitive.Sub;\nexport const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup;\n\nexport type ContextMenuSubTriggerProps<T extends ValidComponent = \"div\"> =\n ContextMenuPrimitive.ContextMenuSubTriggerProps<T> & {\n class?: string;\n children?: JSX.Element;\n inset?: boolean;\n };\n\nexport const ContextMenuSubTrigger = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, ContextMenuSubTriggerProps<T>>\n) => {\n const [local, rest] = splitProps(props as ContextMenuSubTriggerProps, [\"class\", \"children\", \"inset\"]);\n\n return (\n <ContextMenuPrimitive.SubTrigger\n class={cn(\n \"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent text-foreground\",\n local.inset && \"pl-8\",\n local.class\n )}\n {...(rest as any)}\n >\n {local.children}\n <svg class=\"ml-auto h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5l7 7-7 7\" />\n </svg>\n </ContextMenuPrimitive.SubTrigger>\n );\n};\n\nexport type ContextMenuSubContentProps<T extends ValidComponent = \"div\"> =\n ContextMenuPrimitive.ContextMenuSubContentProps<T> & {\n class?: string;\n };\n\nexport const ContextMenuSubContent = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, ContextMenuSubContentProps<T>>\n) => {\n const [local, rest] = splitProps(props as ContextMenuSubContentProps, [\"class\"]);\n\n return (\n <ContextMenuPrimitive.Portal>\n <ContextMenuPrimitive.SubContent\n class={cn(\n \"z-50 min-w-[8rem] overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md transition-all animate-in fade-in-80 slide-in-from-top-1\",\n local.class\n )}\n {...(rest as any)}\n />\n </ContextMenuPrimitive.Portal>\n );\n};\n\nexport type ContextMenuContentProps<T extends ValidComponent = \"div\"> =\n ContextMenuPrimitive.ContextMenuContentProps<T> & {\n class?: string;\n ref?: (el: HTMLElement) => void;\n children?: JSX.Element;\n };\n\nexport const ContextMenuContent = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, ContextMenuContentProps<T>>\n) => {\n const [local, rest] = splitProps(props as ContextMenuContentProps, [\"class\", \"ref\", \"children\"]);\n let menuRef: HTMLElement | undefined;\n\n createClickOutside({\n target: () => menuRef,\n onInteractOutside: () => {\n // Automatic portal dismiss on outside click\n },\n });\n\n return (\n <ContextMenuPrimitive.Portal>\n <ContextMenuPrimitive.Content\n ref={(el) => {\n menuRef = el;\n if (typeof local.ref === \"function\") local.ref(el);\n }}\n class={cn(\n \"z-50 min-w-[8rem] rounded-md border border-border bg-popover text-popover-foreground shadow-md transition-all animate-in fade-in-80 slide-in-from-top-1 max-h-72 flex flex-col\",\n local.class\n )}\n {...(rest as any)}\n >\n <ScrollArea class=\"max-h-72 w-full rounded-[inherit]\">\n <div class=\"p-1\">\n {local.children}\n </div>\n </ScrollArea>\n </ContextMenuPrimitive.Content>\n </ContextMenuPrimitive.Portal>\n );\n};\n\nexport type ContextMenuItemProps<T extends ValidComponent = \"div\"> =\n ContextMenuPrimitive.ContextMenuItemProps<T> & {\n class?: string;\n inset?: boolean;\n };\n\nexport const ContextMenuItem = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, ContextMenuItemProps<T>>\n) => {\n const [local, rest] = splitProps(props as ContextMenuItemProps, [\"class\", \"inset\"]);\n\n return (\n <ContextMenuPrimitive.Item\n class={cn(\n \"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n local.inset && \"pl-8\",\n local.class\n )}\n {...(rest as any)}\n />\n );\n};\n\nexport type ContextMenuCheckboxItemProps<T extends ValidComponent = \"div\"> =\n ContextMenuPrimitive.ContextMenuCheckboxItemProps<T> & {\n class?: string;\n children?: JSX.Element;\n checked?: boolean;\n };\n\nexport const ContextMenuCheckboxItem = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, ContextMenuCheckboxItemProps<T>>\n) => {\n const [local, rest] = splitProps(props as ContextMenuCheckboxItemProps, [\"class\", \"children\", \"checked\"]);\n\n return (\n <ContextMenuPrimitive.CheckboxItem\n checked={local.checked}\n class={cn(\n \"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n local.class\n )}\n {...(rest as any)}\n >\n <span class=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n <ContextMenuPrimitive.ItemIndicator>\n <svg class=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M5 13l4 4L19 7\" />\n </svg>\n </ContextMenuPrimitive.ItemIndicator>\n </span>\n {local.children}\n </ContextMenuPrimitive.CheckboxItem>\n );\n};\n\nexport type ContextMenuRadioItemProps<T extends ValidComponent = \"div\"> =\n ContextMenuPrimitive.ContextMenuRadioItemProps<T> & {\n class?: string;\n children?: JSX.Element;\n };\n\nexport const ContextMenuRadioItem = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, ContextMenuRadioItemProps<T>>\n) => {\n const [local, rest] = splitProps(props as ContextMenuRadioItemProps, [\"class\", \"children\"]);\n\n return (\n <ContextMenuPrimitive.RadioItem\n class={cn(\n \"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n local.class\n )}\n {...(rest as any)}\n >\n <span class=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n <ContextMenuPrimitive.ItemIndicator>\n <svg class=\"h-2 w-2 fill-current\" viewBox=\"0 0 8 8\">\n <circle cx=\"4\" cy=\"4\" r=\"3\" />\n </svg>\n </ContextMenuPrimitive.ItemIndicator>\n </span>\n {local.children}\n </ContextMenuPrimitive.RadioItem>\n );\n};\n\nexport interface ContextMenuLabelProps {\n class?: string;\n inset?: boolean;\n children?: JSX.Element;\n}\n\nexport const ContextMenuLabel: Component<ContextMenuLabelProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"inset\", \"children\"]);\n\n return (\n <ContextMenuPrimitive.GroupLabel\n class={cn(\"px-2 py-1.5 text-sm font-semibold text-foreground\", local.inset && \"pl-8\", local.class)}\n {...rest}\n >\n {local.children}\n </ContextMenuPrimitive.GroupLabel>\n );\n};\n\nexport const ContextMenuSeparator: Component<{ class?: string }> = (props) => {\n return <Separator class={cn(\"-mx-1 my-1\", props.class)} />;\n};\n\nexport interface ContextMenuShortcutProps {\n class?: string;\n children?: JSX.Element;\n}\n\nexport const ContextMenuShortcut: Component<ContextMenuShortcutProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <span class={cn(\"ml-auto pl-4 text-xs tracking-widest text-muted-foreground\", local.class)} {...rest}>\n <Kbd size=\"sm\" variant=\"outline\">\n {local.children}\n </Kbd>\n </span>\n );\n};\n",
|
|
21
|
+
"type": "registry:ui"
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-active-element",
|
|
3
|
+
"title": "createActiveElement",
|
|
4
|
+
"description": "SolidJS reactive primitive for tracking the currently focused DOM element",
|
|
5
|
+
"type": "registry:hook",
|
|
6
|
+
"files": [
|
|
7
|
+
{
|
|
8
|
+
"path": "hooks/create-active-element.ts",
|
|
9
|
+
"content": "import { createSignal, onMount, onCleanup, type Accessor } from \"solid-js\";\nimport { isServer } from \"solid-js/web\";\n\nexport interface CreateActiveElementReturn {\n /** Accessor returning the currently focused DOM element, or null */\n activeElement: Accessor<Element | null>;\n /** Accessor returning true if any element (non-body) is focused */\n hasFocus: Accessor<boolean>;\n}\n\n/**\n * SolidJS reactive primitive for tracking the currently focused (active) DOM element.\n * Listens to focus/blur events on the document and reactively updates `activeElement` and `hasFocus`.\n */\nexport function createActiveElement(): CreateActiveElementReturn {\n // SSR: return static defaults\n if (isServer) {\n const [activeElement] = createSignal<Element | null>(null);\n const [hasFocus] = createSignal(false);\n return { activeElement, hasFocus };\n }\n\n const [activeElement, setActiveElement] = createSignal<Element | null>(null);\n\n const hasFocus = () => {\n const el = activeElement();\n return el !== null && el !== document.body;\n };\n\n const handleFocusChange = () => {\n setActiveElement(document.activeElement);\n };\n\n onMount(() => {\n // Set initial value\n setActiveElement(document.activeElement);\n\n document.addEventListener(\"focusin\", handleFocusChange, true);\n document.addEventListener(\"focusout\", handleFocusChange, true);\n\n onCleanup(() => {\n document.removeEventListener(\"focusin\", handleFocusChange, true);\n document.removeEventListener(\"focusout\", handleFocusChange, true);\n });\n });\n\n return {\n activeElement,\n hasFocus,\n };\n}\n",
|
|
10
|
+
"type": "registry:hook"
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-app-updater",
|
|
3
|
+
"title": "createAppUpdater",
|
|
4
|
+
"description": "SolidJS reactive primitive for controlling and observing Tauri v2 application updates",
|
|
5
|
+
"type": "registry:hook",
|
|
6
|
+
"files": [
|
|
7
|
+
{
|
|
8
|
+
"path": "hooks/create-app-updater.ts",
|
|
9
|
+
"content": "import { createSignal, onMount, type Accessor } from \"solid-js\";\nimport { isTauriEnvironment } from \"./create-tauri-window\";\n\nexport type UpdaterStatus =\n | \"idle\"\n | \"checking\"\n | \"available\"\n | \"up-to-date\"\n | \"downloading\"\n | \"downloaded\"\n | \"error\";\n\nexport interface UpdateManifestInfo {\n version: string;\n currentVersion?: string;\n date?: string;\n body?: string;\n}\n\nexport interface UpdateProgressInfo {\n downloaded: number;\n total: number;\n percentage: number;\n}\n\nexport interface CreateAppUpdaterOptions {\n /** Automatically check for updates on mount. Defaults to false. */\n autoCheck?: boolean;\n /** Current application version fallback if not detected. */\n currentVersion?: string;\n /** Callback fired when an update is available. */\n onUpdateAvailable?: (info: UpdateManifestInfo) => void;\n /** Callback fired when download finishes. */\n onDownloadFinished?: () => void;\n /** Callback fired on update check or download error. */\n onError?: (error: Error | string) => void;\n}\n\nexport interface CreateAppUpdaterReturn {\n /** Current lifecycle status of the updater. */\n status: Accessor<UpdaterStatus>;\n /** Update metadata (new version, release notes, release date). */\n updateInfo: Accessor<UpdateManifestInfo | null>;\n /** Download progress (bytes downloaded, total bytes, percentage). */\n progress: Accessor<UpdateProgressInfo>;\n /** Error message if an operation failed. */\n error: Accessor<string | null>;\n /** Whether an update is currently being checked or downloaded. */\n isLoading: Accessor<boolean>;\n /** Check update endpoint for a new release. */\n checkForUpdates: () => Promise<boolean>;\n /** Download and install the available update. */\n downloadAndInstall: () => Promise<void>;\n /** Restart application to apply the downloaded update. */\n relaunch: () => Promise<void>;\n /** Dismiss current update prompt or clear error state. */\n dismiss: () => void;\n /** Simulate an update cycle in web / playground preview environments. */\n simulateUpdate: (mockInfo?: Partial<UpdateManifestInfo>) => void;\n}\n\n/**\n * SolidJS reactive primitive for controlling and observing Tauri v2 application updates.\n * Integrates with @tauri-apps/plugin-updater with full web simulation support.\n */\nexport function createAppUpdater(\n options: CreateAppUpdaterOptions = {}\n): CreateAppUpdaterReturn {\n const [status, setStatus] = createSignal<UpdaterStatus>(\"idle\");\n const [updateInfo, setUpdateInfo] = createSignal<UpdateManifestInfo | null>(null);\n const [progress, setProgress] = createSignal<UpdateProgressInfo>({\n downloaded: 0,\n total: 0,\n percentage: 0,\n });\n const [error, setError] = createSignal<string | null>(null);\n\n let activeTauriUpdate: any = null;\n let simulatedTimer: any = null;\n\n const getTauriUpdaterPlugin = async () => {\n if (typeof window === \"undefined\") return null;\n try {\n if ((window as any).__TAURI__?.updater) {\n return (window as any).__TAURI__.updater;\n }\n const moduleName = \"@tauri-apps/plugin-updater\";\n // @ts-ignore - Optional runtime dependency in Tauri apps\n const plugin = await import(/* @vite-ignore */ moduleName).catch(() => null);\n return plugin;\n } catch {\n return null;\n }\n };\n\n const getTauriProcessPlugin = async () => {\n if (typeof window === \"undefined\") return null;\n try {\n if ((window as any).__TAURI__?.process) {\n return (window as any).__TAURI__.process;\n }\n const moduleName = \"@tauri-apps/plugin-process\";\n // @ts-ignore - Optional runtime dependency in Tauri apps\n const plugin = await import(/* @vite-ignore */ moduleName).catch(() => null);\n return plugin;\n } catch {\n return null;\n }\n };\n\n const checkForUpdates = async (): Promise<boolean> => {\n setStatus(\"checking\");\n setError(null);\n\n if (isTauriEnvironment()) {\n try {\n const updaterPlugin = await getTauriUpdaterPlugin();\n if (updaterPlugin?.check) {\n const update = await updaterPlugin.check();\n if (update?.available) {\n activeTauriUpdate = update;\n const info: UpdateManifestInfo = {\n version: update.version,\n currentVersion: update.currentVersion || options.currentVersion || \"v1.0.0\",\n date: update.date,\n body: update.body || \"Bug fixes and performance enhancements.\",\n };\n setUpdateInfo(info);\n setStatus(\"available\");\n options.onUpdateAvailable?.(info);\n return true;\n } else {\n setStatus(\"up-to-date\");\n return false;\n }\n }\n } catch (err: any) {\n const errMsg = err?.message || String(err);\n setError(errMsg);\n setStatus(\"error\");\n options.onError?.(errMsg);\n return false;\n }\n }\n\n // In web preview / non-tauri mode, simulate checking\n await new Promise((resolve) => setTimeout(resolve, 800));\n setStatus(\"up-to-date\");\n return false;\n };\n\n const downloadAndInstall = async (): Promise<void> => {\n if (status() !== \"available\" && status() !== \"error\") return;\n setStatus(\"downloading\");\n setError(null);\n setProgress({ downloaded: 0, total: 100, percentage: 0 });\n\n if (isTauriEnvironment() && activeTauriUpdate?.downloadAndInstall) {\n try {\n let downloadedBytes = 0;\n let contentLength = 0;\n\n await activeTauriUpdate.downloadAndInstall((event: any) => {\n if (event.event === \"Started\") {\n contentLength = event.data?.contentLength || 100;\n } else if (event.event === \"Progress\") {\n downloadedBytes += event.data?.chunkLength || 0;\n const pct = contentLength > 0 ? Math.min(100, Math.round((downloadedBytes / contentLength) * 100)) : 50;\n setProgress({\n downloaded: downloadedBytes,\n total: contentLength,\n percentage: pct,\n });\n } else if (event.event === \"Finished\") {\n setProgress({\n downloaded: contentLength,\n total: contentLength,\n percentage: 100,\n });\n }\n });\n\n setStatus(\"downloaded\");\n options.onDownloadFinished?.();\n return;\n } catch (err: any) {\n const errMsg = err?.message || String(err);\n setError(errMsg);\n setStatus(\"error\");\n options.onError?.(errMsg);\n return;\n }\n }\n\n // Web simulation mode with smooth progress step\n let currentPct = 0;\n simulatedTimer = setInterval(() => {\n currentPct += 15;\n if (currentPct >= 100) {\n clearInterval(simulatedTimer);\n setProgress({ downloaded: 45.8 * 1024 * 1024, total: 45.8 * 1024 * 1024, percentage: 100 });\n setStatus(\"downloaded\");\n options.onDownloadFinished?.();\n } else {\n const downloaded = (45.8 * 1024 * 1024 * currentPct) / 100;\n setProgress({ downloaded, total: 45.8 * 1024 * 1024, percentage: currentPct });\n }\n }, 250);\n };\n\n const relaunch = async (): Promise<void> => {\n if (isTauriEnvironment()) {\n try {\n const processPlugin = await getTauriProcessPlugin();\n if (processPlugin?.relaunch) {\n await processPlugin.relaunch();\n return;\n }\n } catch {\n // Fallback\n }\n }\n\n if (typeof window !== \"undefined\") {\n window.location.reload();\n }\n };\n\n const dismiss = (): void => {\n if (simulatedTimer) clearInterval(simulatedTimer);\n setStatus(\"idle\");\n setError(null);\n };\n\n const simulateUpdate = (mockInfo?: Partial<UpdateManifestInfo>): void => {\n if (simulatedTimer) clearInterval(simulatedTimer);\n const info: UpdateManifestInfo = {\n version: mockInfo?.version || \"v1.2.0\",\n currentVersion: mockInfo?.currentVersion || options.currentVersion || \"v1.0.0\",\n date: mockInfo?.date || new Date().toISOString().split(\"T\")[0],\n body:\n mockInfo?.body ||\n \"### What's New in v1.2.0\\n- Added native Window Titlebar tabs\\n- Enhanced Tauri v2 capability security\\n- Optimized SolidJS signal reactivity\\n- Fixed titlebar drag region jitter on Linux\",\n };\n setUpdateInfo(info);\n setStatus(\"available\");\n setError(null);\n setProgress({ downloaded: 0, total: 100, percentage: 0 });\n options.onUpdateAvailable?.(info);\n };\n\n onMount(() => {\n if (options.autoCheck) {\n checkForUpdates();\n }\n });\n\n const isLoading = () => status() === \"checking\" || status() === \"downloading\";\n\n return {\n status,\n updateInfo,\n progress,\n error,\n isLoading,\n checkForUpdates,\n downloadAndInstall,\n relaunch,\n dismiss,\n simulateUpdate,\n };\n}\n",
|
|
10
|
+
"type": "registry:hook"
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-battery",
|
|
3
|
+
"title": "createBattery",
|
|
4
|
+
"description": "SolidJS reactive primitive for observing device battery status, charge level, and charging metrics",
|
|
5
|
+
"type": "registry:hook",
|
|
6
|
+
"files": [
|
|
7
|
+
{
|
|
8
|
+
"path": "hooks/create-battery.ts",
|
|
9
|
+
"content": "import { createSignal, createEffect, onCleanup, type Accessor } from \"solid-js\";\n\nexport interface BatteryState {\n /** Battery charge level ratio from 0.0 (empty) to 1.0 (full). */\n level: number;\n /** Whether the device battery is currently charging. */\n charging: boolean;\n /** Seconds remaining until fully charged (0 if already full or unknown). */\n chargingTime: number;\n /** Seconds remaining until fully discharged. */\n dischargingTime: number;\n}\n\nexport interface CreateBatteryReturn {\n /** Signal accessor containing battery state metrics. */\n battery: Accessor<BatteryState>;\n /** Signal accessor indicating whether Battery Status API is supported in browser environment. */\n isSupported: Accessor<boolean>;\n}\n\nconst initialBatteryState: BatteryState = {\n level: 1,\n charging: true,\n chargingTime: 0,\n dischargingTime: Infinity,\n};\n\n/**\n * SolidJS reactive primitive for observing device battery status, charge level, and charging metrics.\n */\nexport function createBattery(): CreateBatteryReturn {\n const [battery, setBattery] = createSignal<BatteryState>(initialBatteryState);\n\n const isSupported = (): boolean =>\n typeof window !== \"undefined\" &&\n typeof navigator !== \"undefined\" &&\n \"getBattery\" in navigator;\n\n let batteryManager: any = null;\n\n const updateBatteryStatus = (): void => {\n if (!batteryManager) return;\n setBattery({\n level: batteryManager.level ?? 1,\n charging: Boolean(batteryManager.charging),\n chargingTime: batteryManager.chargingTime ?? 0,\n dischargingTime: batteryManager.dischargingTime ?? Infinity,\n });\n };\n\n createEffect(() => {\n if (!isSupported()) return;\n\n (navigator as any).getBattery().then((manager: any) => {\n batteryManager = manager;\n updateBatteryStatus();\n\n manager.addEventListener(\"levelchange\", updateBatteryStatus);\n manager.addEventListener(\"chargingchange\", updateBatteryStatus);\n manager.addEventListener(\"chargingtimechange\", updateBatteryStatus);\n manager.addEventListener(\"dischargingtimechange\", updateBatteryStatus);\n }).catch(() => {});\n\n onCleanup(() => {\n if (batteryManager) {\n batteryManager.removeEventListener(\"levelchange\", updateBatteryStatus);\n batteryManager.removeEventListener(\"chargingchange\", updateBatteryStatus);\n batteryManager.removeEventListener(\"chargingtimechange\", updateBatteryStatus);\n batteryManager.removeEventListener(\"dischargingtimechange\", updateBatteryStatus);\n }\n });\n });\n\n return {\n battery,\n isSupported,\n };\n}\n",
|
|
10
|
+
"type": "registry:hook"
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-chat-scroll",
|
|
3
|
+
"title": "createChatScroll",
|
|
4
|
+
"description": "SolidJS reactive primitive for automated chat container scrolling with manual scroll-up detection",
|
|
5
|
+
"type": "registry:hook",
|
|
6
|
+
"files": [
|
|
7
|
+
{
|
|
8
|
+
"path": "hooks/create-chat-scroll.ts",
|
|
9
|
+
"content": "import { createSignal, createEffect, onCleanup, onMount, type Accessor } from \"solid-js\";\n\nexport interface CreateChatScrollOptions {\n /** Target scrollable container element or accessor */\n target: HTMLElement | Accessor<HTMLElement | undefined>;\n /** Dependency accessor (e.g. messages length or content signal) that triggers auto-scroll when changed */\n trigger?: Accessor<any>;\n /** Threshold in pixels from bottom to consider the container \"at bottom\". Defaults to 40. */\n threshold?: number;\n /** Whether auto-scroll is enabled. Defaults to true. */\n enabled?: boolean | Accessor<boolean>;\n /** Scroll behavior: \"smooth\" or \"auto\". Defaults to \"smooth\". */\n behavior?: ScrollBehavior;\n}\n\nexport interface CreateChatScrollReturn {\n /** Accessor indicating whether the container is currently scrolled to the bottom */\n isAtBottom: Accessor<boolean>;\n /** Accessor indicating whether user has manually scrolled up away from bottom */\n isScrolledUp: Accessor<boolean>;\n /** Programmatically scroll container directly to the bottom */\n scrollToBottom: (options?: { smooth?: boolean }) => void;\n}\n\n/**\n * SolidJS reactive primitive for chat and streaming message auto-scrolling with user scroll detection.\n *\n * @param options Chat scroll configuration options.\n */\nexport function createChatScroll(options: CreateChatScrollOptions): CreateChatScrollReturn {\n const [isAtBottom, setIsAtBottom] = createSignal<boolean>(true);\n const isScrolledUp = () => !isAtBottom();\n\n const getElement = (): HTMLElement | undefined => {\n if (typeof options.target === \"function\") {\n return (options.target as Accessor<HTMLElement | undefined>)();\n }\n return options.target;\n };\n\n const isEnabled = () => {\n if (typeof options.enabled === \"function\") {\n return (options.enabled as Accessor<boolean>)();\n }\n return options.enabled ?? true;\n };\n\n const threshold = options.threshold ?? 40;\n\n const checkIfAtBottom = () => {\n const el = getElement();\n if (!el) return true;\n const distanceToBottom = el.scrollHeight - el.scrollTop - el.clientHeight;\n return distanceToBottom <= threshold;\n };\n\n const scrollToBottom = (opts?: { smooth?: boolean }) => {\n const el = getElement();\n if (!el) return;\n\n const useSmooth = opts?.smooth ?? (options.behavior === \"smooth\" || options.behavior === undefined);\n\n el.scrollTo({\n top: el.scrollHeight,\n behavior: useSmooth ? \"smooth\" : \"auto\",\n });\n setIsAtBottom(true);\n };\n\n const handleScroll = () => {\n const atBottom = checkIfAtBottom();\n setIsAtBottom(atBottom);\n };\n\n onMount(() => {\n if (typeof window === \"undefined\") return;\n\n const el = getElement();\n if (el) {\n el.addEventListener(\"scroll\", handleScroll, { passive: true });\n setIsAtBottom(checkIfAtBottom());\n }\n });\n\n onCleanup(() => {\n if (typeof window === \"undefined\") return;\n const el = getElement();\n if (el) {\n el.removeEventListener(\"scroll\", handleScroll);\n }\n });\n\n // Watch trigger dependencies (e.g. messages length or stream tokens)\n if (options.trigger) {\n createEffect(() => {\n // Track trigger dependency\n options.trigger!();\n\n if (isEnabled() && isAtBottom()) {\n // Run after microtask/DOM paint\n setTimeout(() => {\n scrollToBottom({ smooth: true });\n }, 10);\n }\n });\n }\n\n return {\n isAtBottom,\n isScrolledUp,\n scrollToBottom,\n };\n}\n",
|
|
10
|
+
"type": "registry:hook"
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-click-outside",
|
|
3
|
+
"title": "createClickOutside",
|
|
4
|
+
"description": "SolidJS reactive primitive for detecting click and pointer interactions outside target elements",
|
|
5
|
+
"type": "registry:hook",
|
|
6
|
+
"files": [
|
|
7
|
+
{
|
|
8
|
+
"path": "hooks/create-click-outside.ts",
|
|
9
|
+
"content": "import { onMount, onCleanup, type Accessor } from \"solid-js\";\n\nexport interface CreateClickOutsideOptions {\n /** Target HTML element or accessor returning the container element */\n target:\n | HTMLElement\n | Accessor<HTMLElement | undefined>\n | (HTMLElement | Accessor<HTMLElement | undefined>)[];\n /** Callback fired when a click/pointer event occurs outside the target element(s) */\n onInteractOutside: (event: MouseEvent | PointerEvent | TouchEvent) => void;\n /** Whether the listener is active. Defaults to true. */\n enabled?: boolean | Accessor<boolean>;\n /** Optional element or accessor to ignore when checking outside clicks (e.g. trigger button) */\n ignore?:\n | HTMLElement\n | Accessor<HTMLElement | undefined>\n | (HTMLElement | Accessor<HTMLElement | undefined>)[];\n}\n\n/**\n * SolidJS reactive primitive for detecting user interactions outside specified element(s).\n *\n * @param options Configuration options including target element(s), callback, and optional ignore elements.\n */\nexport function createClickOutside(options: CreateClickOutsideOptions): void {\n const isEnabled = () => {\n if (typeof options.enabled === \"function\") {\n return options.enabled();\n }\n return options.enabled ?? true;\n };\n\n const getElement = (\n el: HTMLElement | Accessor<HTMLElement | undefined> | undefined\n ): HTMLElement | undefined => {\n if (!el) return undefined;\n if (typeof el === \"function\") {\n return (el as Accessor<HTMLElement | undefined>)();\n }\n return el;\n };\n\n const getElements = (\n targets:\n | HTMLElement\n | Accessor<HTMLElement | undefined>\n | (HTMLElement | Accessor<HTMLElement | undefined>)[]\n | undefined\n ): HTMLElement[] => {\n if (!targets) return [];\n const list = Array.isArray(targets) ? targets : [targets];\n return list.map(getElement).filter((e): e is HTMLElement => e !== undefined);\n };\n\n const handlePointerDown = (event: MouseEvent | PointerEvent | TouchEvent) => {\n if (!isEnabled()) return;\n\n const targetNode = event.target as Node | null;\n if (!targetNode) return;\n\n const mainElements = getElements(options.target);\n if (mainElements.length === 0) return;\n\n const isInsideTarget = mainElements.some((el) => el.contains(targetNode));\n if (isInsideTarget) return;\n\n const ignoreElements = getElements(options.ignore);\n const isInsideIgnore = ignoreElements.some((el) => el.contains(targetNode));\n if (isInsideIgnore) return;\n\n options.onInteractOutside(event);\n };\n\n onMount(() => {\n if (typeof window === \"undefined\") return;\n\n window.addEventListener(\"pointerdown\", handlePointerDown, true);\n onCleanup(() => {\n window.removeEventListener(\"pointerdown\", handlePointerDown, true);\n });\n });\n}\n",
|
|
10
|
+
"type": "registry:hook"
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-clipboard",
|
|
3
|
+
"title": "createClipboard",
|
|
4
|
+
"description": "SolidJS reactive primitive for copying text to clipboard with automatic status reset",
|
|
5
|
+
"type": "registry:hook",
|
|
6
|
+
"files": [
|
|
7
|
+
{
|
|
8
|
+
"path": "hooks/create-clipboard.ts",
|
|
9
|
+
"content": "import { createSignal, onCleanup, type Accessor } from \"solid-js\";\n\nexport interface CreateClipboardOptions {\n /** Time in milliseconds to maintain the copied state before resetting. Defaults to 2000ms. */\n timeout?: number;\n}\n\nexport interface CreateClipboardReturn {\n /** Signal accessor indicating if content was recently copied */\n copied: Accessor<boolean>;\n /** Function to copy text to clipboard */\n copy: (text: string) => Promise<boolean>;\n /** Error state if clipboard write fails */\n error: Accessor<Error | undefined>;\n}\n\n/**\n * SolidJS reactive primitive for copying text to clipboard with automatic reset state.\n *\n * @param options Configuration options including copied status reset timeout duration.\n */\nexport function createClipboard(options: CreateClipboardOptions = {}): CreateClipboardReturn {\n const timeoutDuration = options.timeout ?? 2000;\n const [copied, setCopied] = createSignal(false);\n const [error, setError] = createSignal<Error | undefined>(undefined);\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const clearTimer = () => {\n if (timer) {\n clearTimeout(timer);\n timer = undefined;\n }\n };\n\n const copy = async (text: string): Promise<boolean> => {\n clearTimer();\n setError(undefined);\n\n try {\n if (typeof window !== \"undefined\" && navigator.clipboard && navigator.clipboard.writeText) {\n await navigator.clipboard.writeText(text);\n setCopied(true);\n\n timer = setTimeout(() => {\n setCopied(false);\n }, timeoutDuration);\n\n return true;\n }\n\n throw new Error(\"Clipboard API not supported\");\n } catch (err) {\n const copyError = err instanceof Error ? err : new Error(String(err));\n setError(copyError);\n setCopied(false);\n return false;\n }\n };\n\n onCleanup(() => {\n clearTimer();\n });\n\n return {\n copied,\n copy,\n error,\n };\n}\n",
|
|
10
|
+
"type": "registry:hook"
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-color-mode",
|
|
3
|
+
"title": "createColorMode",
|
|
4
|
+
"description": "SolidJS reactive primitive for managing dark/light themes and system preferences",
|
|
5
|
+
"type": "registry:hook",
|
|
6
|
+
"files": [
|
|
7
|
+
{
|
|
8
|
+
"path": "hooks/create-color-mode.ts",
|
|
9
|
+
"content": "import { createEffect, createSignal, onCleanup, onMount, type Accessor } from \"solid-js\";\n\nexport type ColorMode = \"light\" | \"dark\" | \"system\";\n\nexport interface CreateColorModeOptions {\n /** Initial color mode fallback. Defaults to 'system'. */\n initialValue?: ColorMode;\n /** LocalStorage key for persisting color mode state. Defaults to 'nikala-color-mode'. */\n storageKey?: string;\n /** HTML attribute to apply dark mode class on document.documentElement. Defaults to 'class'. */\n attribute?: string;\n}\n\nexport interface CreateColorModeReturn {\n /** Accessor for current active color mode ('light', 'dark', 'system') */\n mode: Accessor<ColorMode>;\n /** Function to update current color mode */\n setMode: (mode: ColorMode) => void;\n /** Function to toggle color mode between 'light' and 'dark' */\n toggleColorMode: () => void;\n /** Accessor indicating if current resolved mode is dark */\n isDark: Accessor<boolean>;\n}\n\n/**\n * SolidJS reactive primitive for managing dark/light color mode themes and system preferences.\n *\n * @param options Configuration options including storage key and initial mode.\n */\nexport function createColorMode(\n options: CreateColorModeOptions = {}\n): CreateColorModeReturn {\n const initialMode = options.initialValue ?? \"system\";\n const storageKey = options.storageKey ?? \"nikala-color-mode\";\n const attribute = options.attribute ?? \"class\";\n\n const [mode, setModeSignal] = createSignal<ColorMode>(initialMode);\n const [systemDark, setSystemDark] = createSignal(false);\n\n const isDark = () => {\n const currentMode = mode();\n if (currentMode === \"system\") return systemDark();\n return currentMode === \"dark\";\n };\n\n const applyTheme = (dark: boolean) => {\n if (typeof document === \"undefined\") return;\n const root = document.documentElement;\n if (attribute === \"class\") {\n if (dark) root.classList.add(\"dark\");\n else root.classList.remove(\"dark\");\n } else {\n root.setAttribute(attribute, dark ? \"dark\" : \"light\");\n }\n };\n\n const setMode = (newMode: ColorMode) => {\n setModeSignal(newMode);\n if (typeof window !== \"undefined\") {\n try {\n localStorage.setItem(storageKey, newMode);\n } catch (e) {\n console.warn(`[nikala-ui/hooks] Error setting color mode storage:`, e);\n }\n }\n };\n\n const toggleColorMode = () => {\n const next = isDark() ? \"light\" : \"dark\";\n setMode(next);\n };\n\n createEffect(() => {\n applyTheme(isDark());\n });\n\n onMount(() => {\n if (typeof window === \"undefined\") return;\n\n // Check system preference\n const mediaQuery = window.matchMedia(\"(prefers-color-scheme: dark)\");\n setSystemDark(mediaQuery.matches);\n\n const handleMediaChange = (e: MediaQueryListEvent) => {\n setSystemDark(e.matches);\n };\n\n if (mediaQuery.addEventListener) {\n mediaQuery.addEventListener(\"change\", handleMediaChange);\n }\n\n // Check localStorage\n try {\n const stored = localStorage.getItem(storageKey) as ColorMode | null;\n if (stored && [\"light\", \"dark\", \"system\"].includes(stored)) {\n setModeSignal(stored);\n }\n } catch (e) {\n console.warn(`[nikala-ui/hooks] Error reading color mode storage:`, e);\n }\n\n onCleanup(() => {\n if (mediaQuery.removeEventListener) {\n mediaQuery.removeEventListener(\"change\", handleMediaChange);\n }\n });\n });\n\n return {\n mode,\n setMode,\n toggleColorMode,\n isDark,\n };\n}\n",
|
|
10
|
+
"type": "registry:hook"
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-controllable-signal",
|
|
3
|
+
"title": "createControllableSignal",
|
|
4
|
+
"description": "SolidJS reactive primitive supporting both controlled and uncontrolled state management",
|
|
5
|
+
"type": "registry:hook",
|
|
6
|
+
"files": [
|
|
7
|
+
{
|
|
8
|
+
"path": "hooks/create-controllable-signal.ts",
|
|
9
|
+
"content": "import { createSignal, type Accessor } from \"solid-js\";\n\nexport interface CreateControllableSignalOptions<T> {\n /** Controlled value accessor or raw value */\n value?: Accessor<T | undefined> | T;\n /** Uncontrolled default initial value */\n defaultValue?: T;\n /** Callback fired whenever the value changes */\n onChange?: (value: T) => void;\n}\n\n/**\n * SolidJS reactive primitive for managing state supporting both controlled and uncontrolled modes.\n *\n * @param options Configuration options for value, defaultValue, and onChange callback.\n * @returns A tuple containing [valueAccessor, setValueFunction].\n */\nexport function createControllableSignal<T>(\n options: CreateControllableSignalOptions<T>\n): [Accessor<T | undefined>, (nextValue: T | ((prev: T | undefined) => T)) => void] {\n const [internalValue, setInternalValue] = createSignal<T | undefined>(\n options.defaultValue\n );\n\n const getValue = (): T | undefined => {\n if (typeof options.value === \"function\") {\n return (options.value as Accessor<T | undefined>)();\n }\n return options.value;\n };\n\n const isControlled = () => getValue() !== undefined;\n\n const value = () => {\n const controlledVal = getValue();\n return controlledVal !== undefined ? controlledVal : internalValue();\n };\n\n const setValue = (nextValue: T | ((prev: T | undefined) => T)) => {\n const current = value();\n const resolvedNext =\n typeof nextValue === \"function\"\n ? (nextValue as (prev: T | undefined) => T)(current)\n : nextValue;\n\n if (!isControlled()) {\n setInternalValue(resolvedNext as any);\n }\n\n if (typeof options.onChange === \"function\") {\n options.onChange(resolvedNext);\n }\n };\n\n return [value, setValue];\n}",
|
|
10
|
+
"type": "registry:hook"
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-debounce",
|
|
3
|
+
"title": "createDebounce",
|
|
4
|
+
"description": "SolidJS reactive primitives for debouncing and throttling rate-limited function execution",
|
|
5
|
+
"type": "registry:hook",
|
|
6
|
+
"files": [
|
|
7
|
+
{
|
|
8
|
+
"path": "hooks/create-debounce.ts",
|
|
9
|
+
"content": "import { onCleanup, type Accessor } from \"solid-js\";\n\nexport interface DebounceThrottleReturn<T extends (...args: any[]) => any> {\n (...args: Parameters<T>): void;\n clear: () => void;\n}\n\n/**\n * SolidJS reactive primitive for debouncing function execution.\n *\n * @param fn Function to debounce.\n * @param delay Delay in milliseconds (number or accessor).\n */\nexport function createDebounce<T extends (...args: any[]) => any>(\n fn: T,\n delay: number | Accessor<number>\n): DebounceThrottleReturn<T> {\n const getDelay = (): number => (typeof delay === \"function\" ? delay() : delay);\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const clear = () => {\n if (timer) {\n clearTimeout(timer);\n timer = undefined;\n }\n };\n\n const debounced = (...args: Parameters<T>) => {\n clear();\n timer = setTimeout(() => {\n fn(...args);\n }, getDelay());\n };\n\n debounced.clear = clear;\n\n onCleanup(() => {\n clear();\n });\n\n return debounced;\n}\n\n/**\n * SolidJS reactive primitive for throttling function execution.\n *\n * @param fn Function to throttle.\n * @param delay Interval in milliseconds (number or accessor).\n */\nexport function createThrottle<T extends (...args: any[]) => any>(\n fn: T,\n delay: number | Accessor<number>\n): DebounceThrottleReturn<T> {\n const getDelay = (): number => (typeof delay === \"function\" ? delay() : delay);\n let lastCall = 0;\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const clear = () => {\n if (timer) {\n clearTimeout(timer);\n timer = undefined;\n }\n };\n\n const throttled = (...args: Parameters<T>) => {\n const now = Date.now();\n const interval = getDelay();\n const remaining = interval - (now - lastCall);\n\n if (remaining <= 0) {\n clear();\n lastCall = now;\n fn(...args);\n } else if (!timer) {\n timer = setTimeout(() => {\n lastCall = Date.now();\n timer = undefined;\n fn(...args);\n }, remaining);\n }\n };\n\n throttled.clear = clear;\n\n onCleanup(() => {\n clear();\n });\n\n return throttled;\n}\n",
|
|
10
|
+
"type": "registry:hook"
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-disclosure",
|
|
3
|
+
"title": "createDisclosure",
|
|
4
|
+
"description": "SolidJS reactive primitive for managing boolean open/close disclosure state with helper controls",
|
|
5
|
+
"type": "registry:hook",
|
|
6
|
+
"files": [
|
|
7
|
+
{
|
|
8
|
+
"path": "hooks/create-disclosure.ts",
|
|
9
|
+
"content": "import { createSignal, type Accessor } from \"solid-js\";\n\nexport interface CreateDisclosureOptions {\n /** Uncontrolled initial open state. Defaults to false. */\n defaultIsOpen?: boolean;\n /** Controlled open state accessor */\n isOpen?: boolean | Accessor<boolean | undefined>;\n /** Callback triggered when state transitions to open */\n onOpen?: () => void;\n /** Callback triggered when state transitions to closed */\n onClose?: () => void;\n /** Callback triggered whenever open state changes */\n onChange?: (isOpen: boolean) => void;\n}\n\nexport interface CreateDisclosureReturn {\n /** Signal accessor indicating if disclosure is open */\n isOpen: Accessor<boolean>;\n /** Function to open the disclosure */\n open: () => void;\n /** Function to close the disclosure */\n close: () => void;\n /** Function to toggle the disclosure open state */\n toggle: () => void;\n /** Setter function to programmatically set open state */\n setOpen: (open: boolean) => void;\n}\n\n/**\n * SolidJS reactive primitive for managing boolean disclosure (open/close) state with controlled and uncontrolled support.\n *\n * @param options Configuration options including initial state and callbacks.\n */\nexport function createDisclosure(options: CreateDisclosureOptions = {}): CreateDisclosureReturn {\n const [internalOpen, setInternalOpen] = createSignal<boolean>(\n options.defaultIsOpen ?? false\n );\n\n const isControlled = () => {\n if (typeof options.isOpen === \"function\") {\n return options.isOpen() !== undefined;\n }\n return options.isOpen !== undefined;\n };\n\n const isOpen = (): boolean => {\n if (typeof options.isOpen === \"function\") {\n const val = options.isOpen();\n return val !== undefined ? val : internalOpen();\n }\n return options.isOpen !== undefined ? options.isOpen : internalOpen();\n };\n\n const setOpenState = (nextState: boolean) => {\n const currentState = isOpen();\n if (currentState === nextState) return;\n\n if (!isControlled()) {\n setInternalOpen(nextState);\n }\n\n options.onChange?.(nextState);\n\n if (nextState) {\n options.onOpen?.();\n } else {\n options.onClose?.();\n }\n };\n\n const open = () => setOpenState(true);\n const close = () => setOpenState(false);\n const toggle = () => setOpenState(!isOpen());\n\n return {\n isOpen,\n open,\n close,\n toggle,\n setOpen: setOpenState,\n };\n}\n",
|
|
10
|
+
"type": "registry:hook"
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-document-tabs",
|
|
3
|
+
"title": "createDocumentTabs",
|
|
4
|
+
"description": "SolidJS reactive primitive for managing multi-document tabs, editor buffers, and browser tab stacks with adjacent activation and pinned state",
|
|
5
|
+
"type": "registry:hook",
|
|
6
|
+
"files": [
|
|
7
|
+
{
|
|
8
|
+
"path": "hooks/create-document-tabs.ts",
|
|
9
|
+
"content": "import {\n createSignal,\n createMemo,\n onMount,\n onCleanup,\n type Accessor,\n} from \"solid-js\";\n\nexport interface TabItem<T = any> {\n id: string;\n title: string;\n icon?: any;\n isDirty?: boolean;\n isPinned?: boolean;\n closable?: boolean;\n data?: T;\n}\n\nexport interface CreateDocumentTabsOptions<T = any> {\n /** Initial array of tab items. */\n initialTabs?: TabItem<T>[];\n /** Default active tab id. Defaults to first tab if available. */\n defaultActiveId?: string;\n /** Whether to enable keyboard shortcuts (Ctrl/Cmd+W to close active, Ctrl/Cmd+T for new tab). */\n enableKeybindings?: boolean;\n /** Callback triggered when the active tab changes. */\n onTabChange?: (tab: TabItem<T> | undefined) => void;\n /** Callback triggered before or when a tab closes. Return false to prevent closing. */\n onTabClose?: (tab: TabItem<T>) => boolean | void;\n /** Callback triggered when a new tab is created. */\n onTabAdd?: (tab: TabItem<T>) => void;\n}\n\nexport interface CreateDocumentTabsReturn<T = any> {\n /** All tabs list. */\n tabs: Accessor<TabItem<T>[]>;\n /** Currently active tab ID. */\n activeTabId: Accessor<string>;\n /** Currently active tab object. */\n activeTab: Accessor<TabItem<T> | undefined>;\n /** Pinned tabs only. */\n pinnedTabs: Accessor<TabItem<T>[]>;\n /** Standard unpinned tabs. */\n unpinnedTabs: Accessor<TabItem<T>[]>;\n /** Whether any open tab has unsaved changes (isDirty). */\n hasDirtyTabs: Accessor<boolean>;\n /** Count of total open tabs. */\n count: Accessor<number>;\n /** Add a new tab to the list. */\n addTab: (tab: TabItem<T>, activate?: boolean) => void;\n /** Close a tab by ID with auto-switching to adjacent tab. */\n closeTab: (id: string) => boolean;\n /** Set the active tab. */\n setActiveTab: (id: string) => void;\n /** Mark or unmark a tab as dirty (unsaved changes). */\n markDirty: (id: string, isDirty?: boolean) => void;\n /** Toggle pinned state of a tab. */\n togglePin: (id: string) => void;\n /** Close all tabs except the specified one. */\n closeOthers: (id: string) => void;\n /** Close all open unpinned tabs. */\n closeAll: (includePinned?: boolean) => void;\n /** Switch to the next tab in sequence. */\n nextTab: () => void;\n /** Switch to the previous tab in sequence. */\n prevTab: () => void;\n /** Reorder tabs from one position to another. */\n reorderTabs: (fromIndex: number, toIndex: number) => void;\n}\n\nfunction findLastPinnedIndex<T>(items: TabItem<T>[]): number {\n for (let i = items.length - 1; i >= 0; i--) {\n if (items[i].isPinned) return i;\n }\n return -1;\n}\n\n/**\n * SolidJS reactive primitive for managing multi-document tabs, editor buffers, and browser tab states.\n */\nexport function createDocumentTabs<T = any>(\n options: CreateDocumentTabsOptions<T> = {}\n): CreateDocumentTabsReturn<T> {\n const initial = options.initialTabs || [];\n const [tabs, setTabs] = createSignal<TabItem<T>[]>(initial);\n const [activeTabId, setActiveTabId] = createSignal<string>(\n options.defaultActiveId || (initial.length > 0 ? initial[0].id : \"\")\n );\n\n const activeTab = createMemo(() => tabs().find((t) => t.id === activeTabId()));\n const pinnedTabs = createMemo(() => tabs().filter((t) => t.isPinned));\n const unpinnedTabs = createMemo(() => tabs().filter((t) => !t.isPinned));\n const hasDirtyTabs = createMemo(() => tabs().some((t) => t.isDirty));\n const count = createMemo(() => tabs().length);\n\n const setActive = (id: string) => {\n const target = tabs().find((t) => t.id === id);\n if (target) {\n setActiveTabId(id);\n options.onTabChange?.(target);\n }\n };\n\n const addTab = (tab: TabItem<T>, activate = true) => {\n const existing = tabs().find((t) => t.id === tab.id);\n if (existing) {\n if (activate) setActive(tab.id);\n return;\n }\n\n setTabs((prev) => {\n if (tab.isPinned) {\n const lastPinnedIdx = findLastPinnedIndex(prev);\n if (lastPinnedIdx === -1) return [tab, ...prev];\n const next = [...prev];\n next.splice(lastPinnedIdx + 1, 0, tab);\n return next;\n }\n return [...prev, tab];\n });\n\n if (activate) {\n setActiveTabId(tab.id);\n options.onTabChange?.(tab);\n }\n options.onTabAdd?.(tab);\n };\n\n const closeTab = (id: string): boolean => {\n const currentTabs = tabs();\n const tabToClose = currentTabs.find((t) => t.id === id);\n if (!tabToClose) return false;\n\n if (options.onTabClose && options.onTabClose(tabToClose) === false) {\n return false;\n }\n\n const closeIdx = currentTabs.findIndex((t) => t.id === id);\n const nextTabs = currentTabs.filter((t) => t.id !== id);\n setTabs(nextTabs);\n\n if (activeTabId() === id) {\n if (nextTabs.length === 0) {\n setActiveTabId(\"\");\n options.onTabChange?.(undefined);\n } else {\n const nextIdx = Math.min(closeIdx, nextTabs.length - 1);\n const nextActive = nextTabs[nextIdx];\n setActiveTabId(nextActive.id);\n options.onTabChange?.(nextActive);\n }\n }\n return true;\n };\n\n const markDirty = (id: string, isDirty = true) => {\n setTabs((prev) =>\n prev.map((t) => (t.id === id ? { ...t, isDirty } : t))\n );\n };\n\n const togglePin = (id: string) => {\n setTabs((prev) => {\n const target = prev.find((t) => t.id === id);\n if (!target) return prev;\n const updated = { ...target, isPinned: !target.isPinned };\n const remaining = prev.filter((t) => t.id !== id);\n\n if (updated.isPinned) {\n const lastPinnedIdx = findLastPinnedIndex(remaining);\n if (lastPinnedIdx === -1) return [updated, ...remaining];\n remaining.splice(lastPinnedIdx + 1, 0, updated);\n return remaining;\n }\n return [...remaining, updated];\n });\n };\n\n const closeOthers = (id: string) => {\n setTabs((prev) => prev.filter((t) => t.id === id || t.isPinned));\n setActive(id);\n };\n\n const closeAll = (includePinned = false) => {\n if (includePinned) {\n setTabs([]);\n setActiveTabId(\"\");\n options.onTabChange?.(undefined);\n } else {\n const pinned = tabs().filter((t) => t.isPinned);\n setTabs(pinned);\n if (pinned.length > 0) {\n setActiveTabId(pinned[0].id);\n options.onTabChange?.(pinned[0]);\n } else {\n setActiveTabId(\"\");\n options.onTabChange?.(undefined);\n }\n }\n };\n\n const nextTab = () => {\n const list = tabs();\n if (list.length <= 1) return;\n const currentIdx = list.findIndex((t) => t.id === activeTabId());\n const nextIdx = (currentIdx + 1) % list.length;\n setActive(list[nextIdx].id);\n };\n\n const prevTab = () => {\n const list = tabs();\n if (list.length <= 1) return;\n const currentIdx = list.findIndex((t) => t.id === activeTabId());\n const prevIdx = (currentIdx - 1 + list.length) % list.length;\n setActive(list[prevIdx].id);\n };\n\n const reorderTabs = (fromIndex: number, toIndex: number) => {\n setTabs((prev) => {\n if (fromIndex < 0 || fromIndex >= prev.length || toIndex < 0 || toIndex >= prev.length) {\n return prev;\n }\n const clone = [...prev];\n const [moved] = clone.splice(fromIndex, 1);\n clone.splice(toIndex, 0, moved);\n return clone;\n });\n };\n\n onMount(() => {\n if (!options.enableKeybindings || typeof window === \"undefined\") return;\n\n const handleKeyDown = (e: KeyboardEvent) => {\n const isCmdOrCtrl = e.metaKey || e.ctrlKey;\n if (isCmdOrCtrl && (e.key === \"w\" || e.key === \"W\")) {\n const active = activeTabId();\n if (active) {\n e.preventDefault();\n closeTab(active);\n }\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n onCleanup(() => window.removeEventListener(\"keydown\", handleKeyDown));\n });\n\n return {\n tabs,\n activeTabId,\n activeTab,\n pinnedTabs,\n unpinnedTabs,\n hasDirtyTabs,\n count,\n addTab,\n closeTab,\n setActiveTab: setActive,\n markDirty,\n togglePin,\n closeOthers,\n closeAll,\n nextTab,\n prevTab,\n reorderTabs,\n };\n}\n",
|
|
10
|
+
"type": "registry:hook"
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-document-title",
|
|
3
|
+
"title": "createDocumentTitle",
|
|
4
|
+
"description": "SolidJS reactive primitive for managing document title dynamically",
|
|
5
|
+
"type": "registry:hook",
|
|
6
|
+
"files": [
|
|
7
|
+
{
|
|
8
|
+
"path": "hooks/create-document-title.ts",
|
|
9
|
+
"content": "import { createEffect, onCleanup, type Accessor } from \"solid-js\";\n\nexport interface CreateDocumentTitleOptions {\n /** Whether to restore original title on component unmount. Defaults to true. */\n restoreOnUnmount?: boolean;\n}\n\n/**\n * SolidJS reactive primitive for managing document title dynamically.\n */\nexport function createDocumentTitle(\n title: string | Accessor<string>,\n options: CreateDocumentTitleOptions = {}\n): void {\n const getTitle = (): string => (typeof title === \"function\" ? title() : title);\n\n createEffect(() => {\n if (typeof document === \"undefined\") return;\n\n const originalTitle = document.title;\n const newTitle = getTitle();\n\n if (newTitle) {\n document.title = newTitle;\n }\n\n onCleanup(() => {\n if (typeof document !== \"undefined\" && (options.restoreOnUnmount ?? true)) {\n document.title = originalTitle;\n }\n });\n });\n}\n",
|
|
10
|
+
"type": "registry:hook"
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-drop-zone",
|
|
3
|
+
"title": "createDropZone",
|
|
4
|
+
"description": "SolidJS reactive primitive for file drag & drop operations, validation, and file chooser dialogs",
|
|
5
|
+
"type": "registry:hook",
|
|
6
|
+
"files": [
|
|
7
|
+
{
|
|
8
|
+
"path": "hooks/create-drop-zone.ts",
|
|
9
|
+
"content": "import { createSignal, onCleanup, onMount, type Accessor } from \"solid-js\";\n\nexport interface FileRejection {\n file: File;\n errors: Array<{\n code: \"file-invalid-type\" | \"file-too-large\" | \"file-too-small\" | \"too-many-files\";\n message: string;\n }>;\n}\n\nexport interface CreateDropZoneOptions {\n /** Accepted file types: MIME types (e.g. \"image/*\", \"application/pdf\") or extensions (e.g. \".png\", \".jpg\") */\n accept?: string | string[];\n /** Maximum number of files allowed */\n maxFiles?: number;\n /** Maximum file size in bytes */\n maxSize?: number;\n /** Minimum file size in bytes */\n minSize?: number;\n /** Whether multiple files are allowed. Defaults to true */\n multiple?: boolean;\n /** Whether the drop zone is disabled */\n disabled?: boolean | Accessor<boolean>;\n /** Prevents browser default behavior of opening files dropped outside dropzone. Defaults to true */\n preventDropOnDocument?: boolean;\n /** Callback fired when valid files are dropped */\n onDrop?: (files: File[], event: DragEvent) => void;\n /** Callback fired when some or all files fail validation */\n onDropRejected?: (rejectedFiles: FileRejection[], event: DragEvent) => void;\n /** Callback fired when drag enters the dropzone */\n onDragEnter?: (event: DragEvent) => void;\n /** Callback fired when drag leaves the dropzone */\n onDragLeave?: (event: DragEvent) => void;\n /** Callback fired when dragging over the dropzone */\n onDragOver?: (event: DragEvent) => void;\n /** Callback fired whenever accepted files list changes */\n onFilesChanged?: (files: File[]) => void;\n}\n\nexport interface CreateDropZoneReturn {\n /** Whether drag operation is currently active over the target drop zone */\n isOver: Accessor<boolean>;\n /** Whether files are currently being dragged anywhere on the window */\n isDragging: Accessor<boolean>;\n /** Currently accepted dropped files */\n files: Accessor<File[]>;\n /** Currently rejected files with error details */\n rejectedFiles: Accessor<FileRejection[]>;\n /** Clear all accepted and rejected files */\n clear: () => void;\n /** Programmatically set files (e.g. from an <input type=\"file\" /> change event) */\n setFiles: (files: File[]) => void;\n /** Programmatically open the native browser file selector dialog */\n openFileDialog: () => void;\n /** Ref callback to attach to target DOM element */\n ref: (el: HTMLElement) => void;\n /** Event handler props to spread directly onto target JSX element */\n props: {\n onDragEnter: (e: DragEvent) => void;\n onDragLeave: (e: DragEvent) => void;\n onDragOver: (e: DragEvent) => void;\n onDrop: (e: DragEvent) => void;\n };\n}\n\nfunction matchesAccept(file: File, acceptList: string[]): boolean {\n if (acceptList.length === 0) return true;\n const fileName = file.name.toLowerCase();\n const fileType = file.type.toLowerCase();\n\n return acceptList.some((pattern) => {\n const p = pattern.trim().toLowerCase();\n if (p.startsWith(\".\")) {\n return fileName.endsWith(p);\n }\n if (p.endsWith(\"/*\")) {\n const typePrefix = p.slice(0, -2);\n return fileType.startsWith(typePrefix + \"/\");\n }\n return fileType === p;\n });\n}\n\nfunction validateFiles(\n incomingFiles: File[],\n options: CreateDropZoneOptions\n): { accepted: File[]; rejected: FileRejection[] } {\n const acceptList = options.accept\n ? (Array.isArray(options.accept) ? options.accept : options.accept.split(\",\"))\n .map((s) => s.trim())\n .filter(Boolean)\n : [];\n\n const maxFiles = options.maxFiles ?? (options.multiple === false ? 1 : Infinity);\n const maxSize = options.maxSize;\n const minSize = options.minSize;\n\n const accepted: File[] = [];\n const rejected: FileRejection[] = [];\n\n incomingFiles.forEach((file, index) => {\n const errors: FileRejection[\"errors\"] = [];\n\n if (index >= maxFiles) {\n errors.push({\n code: \"too-many-files\",\n message: `Maximum allowed files is ${maxFiles}.`,\n });\n }\n\n if (acceptList.length > 0 && !matchesAccept(file, acceptList)) {\n errors.push({\n code: \"file-invalid-type\",\n message: `File type \"${file.type || file.name.split(\".\").pop()}\" is not allowed.`,\n });\n }\n\n if (maxSize !== undefined && file.size > maxSize) {\n errors.push({\n code: \"file-too-large\",\n message: `File size exceeds ${(maxSize / (1024 * 1024)).toFixed(1)}MB limit.`,\n });\n }\n\n if (minSize !== undefined && file.size < minSize) {\n errors.push({\n code: \"file-too-small\",\n message: `File size is below ${(minSize / 1024).toFixed(1)}KB limit.`,\n });\n }\n\n if (errors.length > 0) {\n rejected.push({ file, errors });\n } else {\n accepted.push(file);\n }\n });\n\n return { accepted, rejected };\n}\n\n/**\n * SolidJS reactive primitive for managing file drag & drop zones with validation and file dialog support.\n *\n * @param options Configuration options for file acceptance, size limits, and callbacks.\n */\nexport function createDropZone(options: CreateDropZoneOptions = {}): CreateDropZoneReturn {\n const [isOver, setIsOver] = createSignal(false);\n const [isDragging, setIsDragging] = createSignal(false);\n const [files, setFilesInternal] = createSignal<File[]>([]);\n const [rejectedFiles, setRejectedFilesInternal] = createSignal<FileRejection[]>([]);\n\n let dragCounter = 0;\n let windowDragCounter = 0;\n let targetElement: HTMLElement | null = null;\n\n const isDisabled = () => {\n if (typeof options.disabled === \"function\") {\n return (options.disabled as Accessor<boolean>)();\n }\n return options.disabled ?? false;\n };\n\n const processFiles = (incomingFiles: File[], event: DragEvent) => {\n const { accepted, rejected } = validateFiles(incomingFiles, options);\n\n setFilesInternal(accepted);\n setRejectedFilesInternal(rejected);\n\n if (accepted.length > 0) {\n options.onDrop?.(accepted, event);\n options.onFilesChanged?.(accepted);\n }\n\n if (rejected.length > 0) {\n options.onDropRejected?.(rejected, event);\n }\n };\n\n const onDragEnter = (e: DragEvent) => {\n if (isDisabled()) return;\n e.preventDefault();\n dragCounter++;\n if (dragCounter === 1) {\n setIsOver(true);\n options.onDragEnter?.(e);\n }\n };\n\n const onDragOver = (e: DragEvent) => {\n if (isDisabled()) return;\n e.preventDefault();\n if (e.dataTransfer) {\n e.dataTransfer.dropEffect = \"copy\";\n }\n options.onDragOver?.(e);\n };\n\n const onDragLeave = (e: DragEvent) => {\n if (isDisabled()) return;\n e.preventDefault();\n dragCounter--;\n if (dragCounter <= 0) {\n dragCounter = 0;\n setIsOver(false);\n options.onDragLeave?.(e);\n }\n };\n\n const onDrop = (e: DragEvent) => {\n if (isDisabled()) return;\n e.preventDefault();\n dragCounter = 0;\n setIsOver(false);\n\n if (e.dataTransfer?.files && e.dataTransfer.files.length > 0) {\n processFiles(Array.from(e.dataTransfer.files), e);\n }\n };\n\n const clear = () => {\n setFilesInternal([]);\n setRejectedFilesInternal([]);\n options.onFilesChanged?.([]);\n };\n\n const setFiles = (newFiles: File[]) => {\n const dummyEvent = new Event(\"drop\") as unknown as DragEvent;\n processFiles(newFiles, dummyEvent);\n };\n\n const openFileDialog = () => {\n if (typeof document === \"undefined\" || isDisabled()) return;\n const input = document.createElement(\"input\");\n input.type = \"file\";\n if (options.multiple !== false && (options.maxFiles === undefined || options.maxFiles > 1)) {\n input.multiple = true;\n }\n if (options.accept) {\n input.accept = Array.isArray(options.accept) ? options.accept.join(\",\") : options.accept;\n }\n input.onchange = (e) => {\n const target = e.target as HTMLInputElement;\n if (target.files && target.files.length > 0) {\n processFiles(Array.from(target.files), e as unknown as DragEvent);\n }\n };\n input.click();\n };\n\n const ref = (el: HTMLElement) => {\n targetElement = el;\n };\n\n // Window-level drag detection and document drop prevention\n onMount(() => {\n if (typeof window === \"undefined\") return;\n\n const handleWindowDragEnter = (e: DragEvent) => {\n windowDragCounter++;\n if (windowDragCounter === 1) {\n setIsDragging(true);\n }\n };\n\n const handleWindowDragLeave = (e: DragEvent) => {\n windowDragCounter--;\n if (windowDragCounter <= 0) {\n windowDragCounter = 0;\n setIsDragging(false);\n }\n };\n\n const handleWindowDrop = (e: DragEvent) => {\n windowDragCounter = 0;\n setIsDragging(false);\n if (options.preventDropOnDocument !== false) {\n e.preventDefault();\n }\n };\n\n const handleWindowDragOver = (e: DragEvent) => {\n if (options.preventDropOnDocument !== false) {\n e.preventDefault();\n }\n };\n\n window.addEventListener(\"dragenter\", handleWindowDragEnter);\n window.addEventListener(\"dragleave\", handleWindowDragLeave);\n window.addEventListener(\"dragover\", handleWindowDragOver);\n window.addEventListener(\"drop\", handleWindowDrop);\n\n onCleanup(() => {\n window.removeEventListener(\"dragenter\", handleWindowDragEnter);\n window.removeEventListener(\"dragleave\", handleWindowDragLeave);\n window.removeEventListener(\"dragover\", handleWindowDragOver);\n window.removeEventListener(\"drop\", handleWindowDrop);\n });\n });\n\n return {\n isOver,\n isDragging,\n files,\n rejectedFiles,\n clear,\n setFiles,\n openFileDialog,\n ref,\n props: {\n onDragEnter,\n onDragLeave,\n onDragOver,\n onDrop,\n },\n };\n}\n",
|
|
10
|
+
"type": "registry:hook"
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-event-source",
|
|
3
|
+
"title": "createEventSource",
|
|
4
|
+
"description": "SolidJS reactive primitive for subscribing to Server-Sent Events (SSE) streams",
|
|
5
|
+
"type": "registry:hook",
|
|
6
|
+
"files": [
|
|
7
|
+
{
|
|
8
|
+
"path": "hooks/create-event-source.ts",
|
|
9
|
+
"content": "import { createSignal, createEffect, onCleanup, type Accessor } from \"solid-js\";\n\nexport type EventSourceStatus = \"CONNECTING\" | \"OPEN\" | \"CLOSED\";\n\nexport interface CreateEventSourceOptions {\n /** Event names to listen to on the EventSource. Defaults to ['message']. */\n events?: string[];\n /** Include credentials in CORS requests. */\n withCredentials?: boolean;\n /** Whether to open connection immediately. Defaults to true. */\n immediate?: boolean;\n /** Callback fired when connection is opened. */\n onOpen?: (event: Event) => void;\n /** Callback fired when message event is received. */\n onMessage?: (event: MessageEvent) => void;\n /** Callback fired when error occurs. */\n onError?: (event: Event) => void;\n}\n\nexport interface CreateEventSourceReturn<T = unknown> {\n /** Signal accessor containing latest received SSE event data (parsed if JSON). */\n data: Accessor<T | null>;\n /** Signal accessor containing current EventSource status. */\n status: Accessor<EventSourceStatus>;\n /** Signal accessor containing last raw MessageEvent. */\n event: Accessor<MessageEvent | null>;\n /** Open or reconnect EventSource stream. */\n open: () => void;\n /** Close active EventSource stream. */\n close: () => void;\n /** Signal accessor indicating whether EventSource is supported in browser environment. */\n isSupported: Accessor<boolean>;\n}\n\n/**\n * SolidJS reactive primitive for subscribing to Server-Sent Events (SSE) streams.\n */\nexport function createEventSource<T = unknown>(\n url: string | Accessor<string>,\n options: CreateEventSourceOptions = {}\n): CreateEventSourceReturn<T> {\n const [data, setData] = createSignal<T | null>(null);\n const [event, setEvent] = createSignal<MessageEvent | null>(null);\n const [status, setStatus] = createSignal<EventSourceStatus>(\"CLOSED\");\n\n const getUrl = (): string => (typeof url === \"function\" ? url() : url);\n\n const isSupported = (): boolean =>\n typeof window !== \"undefined\" && \"EventSource\" in window;\n\n let es: EventSource | null = null;\n\n const close = (): void => {\n if (es) {\n es.close();\n es = null;\n setStatus(\"CLOSED\");\n }\n };\n\n const open = (): void => {\n if (!isSupported()) return;\n\n close();\n\n setStatus(\"CONNECTING\");\n\n try {\n const source = new EventSource(getUrl(), {\n withCredentials: options.withCredentials,\n });\n es = source;\n\n source.onopen = (e) => {\n setStatus(\"OPEN\");\n options.onOpen?.(e);\n };\n\n source.onerror = (e) => {\n if (source.readyState === EventSource.CLOSED) {\n setStatus(\"CLOSED\");\n } else if (source.readyState === EventSource.CONNECTING) {\n setStatus(\"CONNECTING\");\n }\n options.onError?.(e);\n };\n\n const eventList = options.events ?? [\"message\"];\n eventList.forEach((eventName) => {\n source.addEventListener(eventName, (e) => {\n const msgEvt = e as MessageEvent;\n setEvent(() => msgEvt);\n try {\n const parsed = JSON.parse(msgEvt.data);\n setData(() => parsed);\n } catch {\n setData(() => msgEvt.data as unknown as T);\n }\n options.onMessage?.(msgEvt);\n });\n });\n } catch {\n setStatus(\"CLOSED\");\n es = null;\n }\n };\n\n createEffect(() => {\n if (options.immediate ?? true) {\n open();\n }\n\n onCleanup(() => {\n close();\n });\n });\n\n return {\n data,\n status,\n event,\n open,\n close,\n isSupported,\n };\n}\n",
|
|
10
|
+
"type": "registry:hook"
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-favicon",
|
|
3
|
+
"title": "createFavicon",
|
|
4
|
+
"description": "SolidJS reactive primitive for dynamically updating browser favicon element",
|
|
5
|
+
"type": "registry:hook",
|
|
6
|
+
"files": [
|
|
7
|
+
{
|
|
8
|
+
"path": "hooks/create-favicon.ts",
|
|
9
|
+
"content": "import { createEffect, onCleanup, type Accessor } from \"solid-js\";\n\nexport interface CreateFaviconOptions {\n /** Favicon rel attribute value. Defaults to 'icon'. */\n rel?: string;\n /** Favicon image mime-type format (e.g., 'image/x-icon', 'image/svg+xml', 'image/png'). */\n type?: string;\n /** Whether to restore original favicon on component unmount. Defaults to true. */\n restoreOnUnmount?: boolean;\n}\n\n/**\n * SolidJS reactive primitive for dynamically updating browser favicon element.\n */\nexport function createFavicon(\n href: string | Accessor<string>,\n options: CreateFaviconOptions = {}\n): void {\n const getHref = (): string => (typeof href === \"function\" ? href() : href);\n\n createEffect(() => {\n if (typeof document === \"undefined\") return;\n\n const rel = options.rel ?? \"icon\";\n let linkElement: HTMLLinkElement | null = document.querySelector(\n `link[rel*=\"${rel}\"]`\n );\n\n const originalHref = linkElement ? linkElement.href : \"\";\n\n if (!linkElement) {\n linkElement = document.createElement(\"link\");\n linkElement.rel = rel;\n if (options.type) linkElement.type = options.type;\n document.head.appendChild(linkElement);\n }\n\n const newHref = getHref();\n if (newHref) {\n linkElement.href = newHref;\n if (options.type) linkElement.type = options.type;\n }\n\n onCleanup(() => {\n if (typeof document !== \"undefined\" && (options.restoreOnUnmount ?? true) && linkElement) {\n if (originalHref) {\n linkElement.href = originalHref;\n }\n }\n });\n });\n}\n",
|
|
10
|
+
"type": "registry:hook"
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-fetch",
|
|
3
|
+
"title": "createFetch",
|
|
4
|
+
"description": "SolidJS reactive primitive for HTTP REST API fetching, request loading states, error handling, and refetching",
|
|
5
|
+
"type": "registry:hook",
|
|
6
|
+
"files": [
|
|
7
|
+
{
|
|
8
|
+
"path": "hooks/create-fetch.ts",
|
|
9
|
+
"content": "import { createSignal, createEffect, type Accessor } from \"solid-js\";\n\nexport interface CreateFetchOptions<T> extends RequestInit {\n /** Whether the request should be refetched automatically on window focus. Defaults to false. */\n refetchOnFocus?: boolean;\n /** Custom transform function to process raw JSON / text response into required shape. */\n transform?: (data: unknown) => T;\n /** Whether the initial request should execute immediately. Defaults to true. */\n immediate?: boolean;\n}\n\nexport interface CreateFetchReturn<T> {\n /** Signal containing fetched data. */\n data: Accessor<T | null>;\n /** Signal indicating whether request is loading. */\n isLoading: Accessor<boolean>;\n /** Signal containing request error if fetch failed. */\n error: Accessor<Error | null>;\n /** Imperative function to refetch data manually. */\n refetch: () => Promise<void>;\n /** Abort ongoing HTTP request. */\n abort: () => void;\n}\n\n/**\n * SolidJS reactive primitive for handling HTTP fetch requests, loading states, errors, and manual refetching.\n */\nexport function createFetch<T = unknown>(\n url: string | Accessor<string>,\n options: CreateFetchOptions<T> = {}\n): CreateFetchReturn<T> {\n const [data, setData] = createSignal<T | null>(null);\n const [isLoading, setIsLoading] = createSignal(options.immediate ?? true);\n const [error, setError] = createSignal<Error | null>(null);\n\n let controller: AbortController | null = null;\n\n const getUrl = (): string => {\n return typeof url === \"function\" ? url() : url;\n };\n\n const abort = (): void => {\n if (controller) {\n controller.abort();\n controller = null;\n }\n };\n\n const executeFetch = async (): Promise<void> => {\n if (typeof window === \"undefined\") return;\n\n abort();\n controller = new AbortController();\n\n setIsLoading(true);\n setError(null);\n\n try {\n const response = await fetch(getUrl(), {\n ...options,\n signal: controller.signal,\n });\n\n if (!response.ok) {\n throw new Error(`HTTP error! status: ${response.status} ${response.statusText}`);\n }\n\n const raw = await response.json();\n const result = options.transform ? options.transform(raw) : (raw as T);\n\n setData(() => result);\n } catch (err) {\n if (err instanceof Error && err.name === \"AbortError\") return;\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n setIsLoading(false);\n }\n };\n\n createEffect(() => {\n if (options.immediate ?? true) {\n executeFetch();\n }\n });\n\n return {\n data,\n isLoading,\n error,\n refetch: executeFetch,\n abort,\n };\n}\n",
|
|
10
|
+
"type": "registry:hook"
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-focus-trap",
|
|
3
|
+
"title": "createFocusTrap",
|
|
4
|
+
"description": "SolidJS reactive primitive for trapping keyboard focus inside target container element for accessibility",
|
|
5
|
+
"type": "registry:hook",
|
|
6
|
+
"files": [
|
|
7
|
+
{
|
|
8
|
+
"path": "hooks/create-focus-trap.ts",
|
|
9
|
+
"content": "import { createEffect, onCleanup, type Accessor } from \"solid-js\";\n\nexport interface CreateFocusTrapOptions {\n /** Whether focus trap is actively enabled. Defaults to true. */\n enabled?: boolean | Accessor<boolean>;\n /** Whether to return focus to previously focused element on cleanup. Defaults to true. */\n returnFocusOnDeactivate?: boolean;\n}\n\nconst FOCUSABLE_SELECTOR =\n 'a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, [tabindex]:not([tabindex=\"-1\"]), [contenteditable]';\n\n/**\n * SolidJS reactive primitive for trapping keyboard focus inside target container element.\n *\n * @param target Target element or accessor returning HTML element.\n * @param options Configuration options for focus trap behavior.\n */\nexport function createFocusTrap(\n target: HTMLElement | Accessor<HTMLElement | undefined>,\n options: CreateFocusTrapOptions = {}\n): void {\n const getTarget = (): HTMLElement | undefined => {\n if (typeof target === \"function\") {\n return (target as Accessor<HTMLElement | undefined>)();\n }\n return target;\n };\n\n const isEnabled = (): boolean => {\n if (typeof options.enabled === \"function\") {\n return options.enabled();\n }\n return options.enabled ?? true;\n };\n\n createEffect(() => {\n if (typeof window === \"undefined\" || !isEnabled()) return;\n\n const container = getTarget();\n if (!container) return;\n\n const previousActiveElement = document.activeElement as HTMLElement | null;\n\n const getFocusableElements = (): HTMLElement[] => {\n return Array.from(container.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)).filter(\n (el) => el.offsetWidth > 0 || el.offsetHeight > 0 || el.getClientRects().length > 0\n );\n };\n\n const focusable = getFocusableElements();\n if (focusable.length > 0) {\n focusable[0].focus();\n } else {\n container.focus();\n }\n\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key !== \"Tab\") return;\n\n const elements = getFocusableElements();\n if (elements.length === 0) {\n event.preventDefault();\n return;\n }\n\n const firstElement = elements[0];\n const lastElement = elements[elements.length - 1];\n const activeElement = document.activeElement;\n\n if (event.shiftKey) {\n if (activeElement === firstElement || !container.contains(activeElement)) {\n event.preventDefault();\n lastElement.focus();\n }\n } else {\n if (activeElement === lastElement || !container.contains(activeElement)) {\n event.preventDefault();\n firstElement.focus();\n }\n }\n };\n\n document.addEventListener(\"keydown\", handleKeyDown);\n\n onCleanup(() => {\n document.removeEventListener(\"keydown\", handleKeyDown);\n\n if (options.returnFocusOnDeactivate !== false && previousActiveElement) {\n previousActiveElement.focus?.();\n }\n });\n });\n}\n",
|
|
10
|
+
"type": "registry:hook"
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|