@pihanga2/shadcn 0.1.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 +114 -0
- package/cards/badge/badge.component.d.ts +14 -0
- package/cards/badge/badge.component.js +18 -0
- package/cards/badge/badge.component.js.map +1 -0
- package/cards/badge/badge.types.d.ts +37 -0
- package/cards/badge/badge.types.js +7 -0
- package/cards/badge/badge.types.js.map +1 -0
- package/cards/badge/index.d.ts +1 -0
- package/cards/badge/index.js +12 -0
- package/cards/badge/index.js.map +1 -0
- package/cards/box/box.component.d.ts +4 -0
- package/cards/box/box.component.js +24 -0
- package/cards/box/box.component.js.map +1 -0
- package/cards/box/box.types.d.ts +71 -0
- package/cards/box/box.types.js +7 -0
- package/cards/box/box.types.js.map +1 -0
- package/cards/box/index.d.ts +1 -0
- package/cards/box/index.js +12 -0
- package/cards/box/index.js.map +1 -0
- package/cards/button/button.component.d.ts +4 -0
- package/cards/button/button.component.js +71 -0
- package/cards/button/button.component.js.map +1 -0
- package/cards/button/button.types.d.ts +106 -0
- package/cards/button/button.types.js +7 -0
- package/cards/button/button.types.js.map +1 -0
- package/cards/button/index.d.ts +1 -0
- package/cards/button/index.js +13 -0
- package/cards/button/index.js.map +1 -0
- package/cards/checkbox/checkbox.component.d.ts +4 -0
- package/cards/checkbox/checkbox.component.js +36 -0
- package/cards/checkbox/checkbox.component.js.map +1 -0
- package/cards/checkbox/checkbox.types.d.ts +37 -0
- package/cards/checkbox/checkbox.types.js +7 -0
- package/cards/checkbox/checkbox.types.js.map +1 -0
- package/cards/checkbox/index.d.ts +1 -0
- package/cards/checkbox/index.js +13 -0
- package/cards/checkbox/index.js.map +1 -0
- package/cards/conditional/conditional.component.d.ts +11 -0
- package/cards/conditional/conditional.component.js +15 -0
- package/cards/conditional/conditional.component.js.map +1 -0
- package/cards/conditional/conditional.types.d.ts +29 -0
- package/cards/conditional/conditional.types.js +7 -0
- package/cards/conditional/conditional.types.js.map +1 -0
- package/cards/conditional/index.d.ts +1 -0
- package/cards/conditional/index.js +12 -0
- package/cards/conditional/index.js.map +1 -0
- package/cards/core-index.d.ts +30 -0
- package/cards/core-index.js +61 -0
- package/cards/dataTable/dataTable.component.d.ts +4 -0
- package/cards/dataTable/dataTable.component.js +209 -0
- package/cards/dataTable/dataTable.component.js.map +1 -0
- package/cards/dataTable/dataTable.types.d.ts +196 -0
- package/cards/dataTable/dataTable.types.js +13 -0
- package/cards/dataTable/dataTable.types.js.map +1 -0
- package/cards/dataTable/dataTableRowDetail.component.d.ts +26 -0
- package/cards/dataTable/dataTableRowDetail.component.js +47 -0
- package/cards/dataTable/dataTableRowDetail.component.js.map +1 -0
- package/cards/dataTable/index.d.ts +1 -0
- package/cards/dataTable/index.js +16 -0
- package/cards/dataTable/index.js.map +1 -0
- package/cards/dialog/dialog.component.d.ts +4 -0
- package/cards/dialog/dialog.component.js +89 -0
- package/cards/dialog/dialog.component.js.map +1 -0
- package/cards/dialog/dialog.types.d.ts +115 -0
- package/cards/dialog/dialog.types.js +11 -0
- package/cards/dialog/dialog.types.js.map +1 -0
- package/cards/dialog/index.d.ts +1 -0
- package/cards/dialog/index.js +13 -0
- package/cards/dialog/index.js.map +1 -0
- package/cards/dropDownMenu/drop-down.component.d.ts +4 -0
- package/cards/dropDownMenu/drop-down.component.js +205 -0
- package/cards/dropDownMenu/drop-down.component.js.map +1 -0
- package/cards/dropDownMenu/drop-down.types.d.ts +131 -0
- package/cards/dropDownMenu/drop-down.types.js +7 -0
- package/cards/dropDownMenu/drop-down.types.js.map +1 -0
- package/cards/dropDownMenu/dropdown-context.d.ts +7 -0
- package/cards/dropDownMenu/dropdown-context.js +7 -0
- package/cards/dropDownMenu/dropdown-context.js.map +1 -0
- package/cards/dropDownMenu/dropdown-menu.ui.d.ts +42 -0
- package/cards/dropDownMenu/dropdown-menu.ui.js +111 -0
- package/cards/dropDownMenu/dropdown-menu.ui.js.map +1 -0
- package/cards/dropDownMenu/index.d.ts +1 -0
- package/cards/dropDownMenu/index.js +13 -0
- package/cards/dropDownMenu/index.js.map +1 -0
- package/cards/emptyCard.d.ts +7 -0
- package/cards/field/field.component.d.ts +4 -0
- package/cards/field/field.component.js +38 -0
- package/cards/field/field.component.js.map +1 -0
- package/cards/field/field.types.d.ts +30 -0
- package/cards/field/field.types.js +7 -0
- package/cards/field/field.types.js.map +1 -0
- package/cards/field/index.d.ts +1 -0
- package/cards/field/index.js +12 -0
- package/cards/field/index.js.map +1 -0
- package/cards/flexGrid/flexGrid.component.d.ts +4 -0
- package/cards/flexGrid/flexGrid.component.js +40 -0
- package/cards/flexGrid/flexGrid.component.js.map +1 -0
- package/cards/flexGrid/flexGrid.types.d.ts +23 -0
- package/cards/flexGrid/flexGrid.types.js +7 -0
- package/cards/flexGrid/flexGrid.types.js.map +1 -0
- package/cards/flexGrid/index.d.ts +1 -0
- package/cards/flexGrid/index.js +12 -0
- package/cards/flexGrid/index.js.map +1 -0
- package/cards/form/form.component.d.ts +4 -0
- package/cards/form/form.component.js +64 -0
- package/cards/form/form.component.js.map +1 -0
- package/cards/form/form.context.d.ts +23 -0
- package/cards/form/form.context.js +13 -0
- package/cards/form/form.context.js.map +1 -0
- package/cards/form/form.types.d.ts +43 -0
- package/cards/form/form.types.js +7 -0
- package/cards/form/form.types.js.map +1 -0
- package/cards/form/index.d.ts +2 -0
- package/cards/form/index.js +14 -0
- package/cards/form/index.js.map +1 -0
- package/cards/framework/framework.component.d.ts +3 -0
- package/cards/framework/framework.component.js +21 -0
- package/cards/framework/framework.component.js.map +1 -0
- package/cards/framework/index.d.ts +2 -0
- package/cards/framework/index.js +12 -0
- package/cards/framework/index.js.map +1 -0
- package/cards/graphin/graphin.component.d.ts +4 -0
- package/cards/graphin/graphin.types.d.ts +18 -0
- package/cards/graphin/index.d.ts +2 -0
- package/cards/graphin/tooltip.component.d.ts +10 -0
- package/cards/icons.d.ts +15 -0
- package/cards/icons.js +15 -0
- package/cards/icons.js.map +1 -0
- package/cards/input/index.d.ts +1 -0
- package/cards/input/index.js +13 -0
- package/cards/input/index.js.map +1 -0
- package/cards/input/input.component.d.ts +4 -0
- package/cards/input/input.component.js +69 -0
- package/cards/input/input.component.js.map +1 -0
- package/cards/input/input.types.d.ts +80 -0
- package/cards/input/input.types.js +7 -0
- package/cards/input/input.types.js.map +1 -0
- package/cards/jsonViewer/index.d.ts +1 -0
- package/cards/jsonViewer/jsonViewer.component.d.ts +4 -0
- package/cards/jsonViewer/jsonViewer.types.d.ts +76 -0
- package/cards/list/index.d.ts +1 -0
- package/cards/list/index.js +13 -0
- package/cards/list/index.js.map +1 -0
- package/cards/list/list.component.d.ts +4 -0
- package/cards/list/list.component.js +102 -0
- package/cards/list/list.component.js.map +1 -0
- package/cards/list/list.types.d.ts +65 -0
- package/cards/list/list.types.js +7 -0
- package/cards/list/list.types.js.map +1 -0
- package/cards/loadingOverlay/index.d.ts +1 -0
- package/cards/loadingOverlay/index.js +12 -0
- package/cards/loadingOverlay/index.js.map +1 -0
- package/cards/loadingOverlay/loadingOverlay.component.d.ts +4 -0
- package/cards/loadingOverlay/loadingOverlay.component.js +31 -0
- package/cards/loadingOverlay/loadingOverlay.component.js.map +1 -0
- package/cards/loadingOverlay/loadingOverlay.types.d.ts +13 -0
- package/cards/loadingOverlay/loadingOverlay.types.js +7 -0
- package/cards/loadingOverlay/loadingOverlay.types.js.map +1 -0
- package/cards/loadingSkeleton/index.d.ts +1 -0
- package/cards/loadingSkeleton/index.js +12 -0
- package/cards/loadingSkeleton/index.js.map +1 -0
- package/cards/loadingSkeleton/loading-skeleton.component.d.ts +19 -0
- package/cards/loadingSkeleton/loading-skeleton.component.js +33 -0
- package/cards/loadingSkeleton/loading-skeleton.component.js.map +1 -0
- package/cards/loadingSkeleton/loading-skeleton.types.d.ts +94 -0
- package/cards/loadingSkeleton/loading-skeleton.types.js +7 -0
- package/cards/loadingSkeleton/loading-skeleton.types.js.map +1 -0
- package/cards/markdownViewer/index.d.ts +1 -0
- package/cards/markdownViewer/markdownViewer.component.d.ts +4 -0
- package/cards/markdownViewer/markdownViewer.types.d.ts +28 -0
- package/cards/menu/index.d.ts +1 -0
- package/cards/menu/index.js +13 -0
- package/cards/menu/index.js.map +1 -0
- package/cards/menu/menu.component.d.ts +4 -0
- package/cards/menu/menu.component.js +23 -0
- package/cards/menu/menu.component.js.map +1 -0
- package/cards/menu/menu.types.d.ts +53 -0
- package/cards/menu/menu.types.js +7 -0
- package/cards/menu/menu.types.js.map +1 -0
- package/cards/modeToggle/index.d.ts +1 -0
- package/cards/modeToggle/index.js +13 -0
- package/cards/modeToggle/index.js.map +1 -0
- package/cards/modeToggle/mode-toggle.component.d.ts +4 -0
- package/cards/modeToggle/mode-toggle.component.js +32 -0
- package/cards/modeToggle/mode-toggle.component.js.map +1 -0
- package/cards/modeToggle/mode-toggle.types.d.ts +20 -0
- package/cards/modeToggle/mode-toggle.types.js +7 -0
- package/cards/modeToggle/mode-toggle.types.js.map +1 -0
- package/cards/navbarSearch/index.d.ts +1 -0
- package/cards/navbarSearch/index.js +13 -0
- package/cards/navbarSearch/index.js.map +1 -0
- package/cards/navbarSearch/navbarSearch.component.d.ts +3 -0
- package/cards/navbarSearch/navbarSearch.component.js +88 -0
- package/cards/navbarSearch/navbarSearch.component.js.map +1 -0
- package/cards/navbarSearch/navbarSearch.type.d.ts +32 -0
- package/cards/navbarSearch/navbarSearch.type.js +11 -0
- package/cards/navbarSearch/navbarSearch.type.js.map +1 -0
- package/cards/pageWithNavbar/index.d.ts +2 -0
- package/cards/pageWithNavbar/index.js +14 -0
- package/cards/pageWithNavbar/index.js.map +1 -0
- package/cards/pageWithNavbar/pageWithNavbar.component.d.ts +12 -0
- package/cards/pageWithNavbar/pageWithNavbar.component.js +153 -0
- package/cards/pageWithNavbar/pageWithNavbar.component.js.map +1 -0
- package/cards/pageWithNavbar/pageWithNavbar.type.d.ts +81 -0
- package/cards/pageWithNavbar/pageWithNavbar.type.js +7 -0
- package/cards/pageWithNavbar/pageWithNavbar.type.js.map +1 -0
- package/cards/pasteTarget/index.d.ts +1 -0
- package/cards/pasteTarget/index.js +13 -0
- package/cards/pasteTarget/index.js.map +1 -0
- package/cards/pasteTarget/pasteTarget.component.d.ts +4 -0
- package/cards/pasteTarget/pasteTarget.component.js +158 -0
- package/cards/pasteTarget/pasteTarget.component.js.map +1 -0
- package/cards/pasteTarget/pasteTarget.types.d.ts +46 -0
- package/cards/pasteTarget/pasteTarget.types.js +11 -0
- package/cards/pasteTarget/pasteTarget.types.js.map +1 -0
- package/cards/resizable/index.d.ts +1 -0
- package/cards/resizable/resizable.component.d.ts +4 -0
- package/cards/resizable/resizable.types.d.ts +49 -0
- package/cards/select/index.d.ts +1 -0
- package/cards/select/index.js +13 -0
- package/cards/select/index.js.map +1 -0
- package/cards/select/select.component.d.ts +4 -0
- package/cards/select/select.component.js +41 -0
- package/cards/select/select.component.js.map +1 -0
- package/cards/select/select.types.d.ts +73 -0
- package/cards/select/select.types.js +11 -0
- package/cards/select/select.types.js.map +1 -0
- package/cards/stack/index.d.ts +1 -0
- package/cards/stack/index.js +12 -0
- package/cards/stack/index.js.map +1 -0
- package/cards/stack/stack.component.d.ts +7 -0
- package/cards/stack/stack.component.js +83 -0
- package/cards/stack/stack.component.js.map +1 -0
- package/cards/stack/stack.types.d.ts +47 -0
- package/cards/stack/stack.types.js +7 -0
- package/cards/stack/stack.types.js.map +1 -0
- package/cards/stepper/index.d.ts +1 -0
- package/cards/stepper/index.js +13 -0
- package/cards/stepper/index.js.map +1 -0
- package/cards/stepper/stepper.component.d.ts +4 -0
- package/cards/stepper/stepper.component.js +48 -0
- package/cards/stepper/stepper.component.js.map +1 -0
- package/cards/stepper/stepper.types.d.ts +96 -0
- package/cards/stepper/stepper.types.js +7 -0
- package/cards/stepper/stepper.types.js.map +1 -0
- package/cards/switch/index.d.ts +1 -0
- package/cards/switch/index.js +13 -0
- package/cards/switch/index.js.map +1 -0
- package/cards/switch/switch.component.d.ts +4 -0
- package/cards/switch/switch.component.js +34 -0
- package/cards/switch/switch.component.js.map +1 -0
- package/cards/switch/switch.types.d.ts +39 -0
- package/cards/switch/switch.types.js +7 -0
- package/cards/switch/switch.types.js.map +1 -0
- package/cards/tabs/index.d.ts +1 -0
- package/cards/tabs/index.js +13 -0
- package/cards/tabs/index.js.map +1 -0
- package/cards/tabs/tabs.component.d.ts +4 -0
- package/cards/tabs/tabs.component.js +42 -0
- package/cards/tabs/tabs.component.js.map +1 -0
- package/cards/tabs/tabs.types.d.ts +72 -0
- package/cards/tabs/tabs.types.js +7 -0
- package/cards/tabs/tabs.types.js.map +1 -0
- package/cards/textField/index.d.ts +1 -0
- package/cards/textField/index.js +13 -0
- package/cards/textField/index.js.map +1 -0
- package/cards/textField/textField.component.d.ts +4 -0
- package/cards/textField/textField.component.js +29 -0
- package/cards/textField/textField.component.js.map +1 -0
- package/cards/textField/textField.types.d.ts +36 -0
- package/cards/textField/textField.types.js +7 -0
- package/cards/textField/textField.types.js.map +1 -0
- package/cards/toast/index.d.ts +1 -0
- package/cards/toast/index.js +13 -0
- package/cards/toast/index.js.map +1 -0
- package/cards/toast/toast.component.d.ts +4 -0
- package/cards/toast/toast.component.js +76 -0
- package/cards/toast/toast.component.js.map +1 -0
- package/cards/toast/toast.types.d.ts +100 -0
- package/cards/toast/toast.types.js +12 -0
- package/cards/toast/toast.types.js.map +1 -0
- package/cards/toggleGroup/index.d.ts +1 -0
- package/cards/toggleGroup/index.js +13 -0
- package/cards/toggleGroup/index.js.map +1 -0
- package/cards/toggleGroup/toggleGroup.component.d.ts +4 -0
- package/cards/toggleGroup/toggleGroup.component.js +55 -0
- package/cards/toggleGroup/toggleGroup.component.js.map +1 -0
- package/cards/toggleGroup/toggleGroup.types.d.ts +96 -0
- package/cards/toggleGroup/toggleGroup.types.js +7 -0
- package/cards/toggleGroup/toggleGroup.types.js.map +1 -0
- package/cards/types.d.ts +72 -0
- package/cards/types.js +22 -0
- package/cards/types.js.map +1 -0
- package/cards/typography/index.d.ts +1 -0
- package/cards/typography/index.js +12 -0
- package/cards/typography/index.js.map +1 -0
- package/cards/typography/typography.component.d.ts +4 -0
- package/cards/typography/typography.component.js +44 -0
- package/cards/typography/typography.component.js.map +1 -0
- package/cards/typography/typography.types.d.ts +23 -0
- package/cards/typography/typography.types.js +7 -0
- package/cards/typography/typography.types.js.map +1 -0
- package/components/hooks/use-is-touch-device.d.ts +1 -0
- package/components/lib/utils.js +10 -0
- package/components/lib/utils.js.map +1 -0
- package/components/theme-provider/index.d.ts +4 -0
- package/components/theme-provider/index.js +12 -0
- package/components/theme-provider/index.js.map +1 -0
- package/components/theme-provider/theme-provider.component.d.ts +2 -0
- package/components/theme-provider/theme-provider.component.js +31 -0
- package/components/theme-provider/theme-provider.component.js.map +1 -0
- package/components/theme-provider/theme-provider.types.d.ts +12 -0
- package/components/theme-provider/theme-provider.types.js +9 -0
- package/components/theme-provider/theme-provider.types.js.map +1 -0
- package/components/ui/avatar.js +30 -0
- package/components/ui/avatar.js.map +1 -0
- package/components/ui/badge.js +26 -0
- package/components/ui/badge.js.map +1 -0
- package/components/ui/button.js +98 -0
- package/components/ui/button.js.map +1 -0
- package/components/ui/dialog.js +109 -0
- package/components/ui/dialog.js.map +1 -0
- package/components/ui/field.js +68 -0
- package/components/ui/field.js.map +1 -0
- package/components/ui/input.js +16 -0
- package/components/ui/input.js.map +1 -0
- package/components/ui/label.js +16 -0
- package/components/ui/label.js.map +1 -0
- package/components/ui/menubar.js +62 -0
- package/components/ui/menubar.js.map +1 -0
- package/components/ui/select.js +84 -0
- package/components/ui/select.js.map +1 -0
- package/components/ui/separator.js +5 -0
- package/components/ui/sheet.js +49 -0
- package/components/ui/sheet.js.map +1 -0
- package/components/ui/spinner.js +35 -0
- package/components/ui/spinner.js.map +1 -0
- package/components/ui/stepper.js +145 -0
- package/components/ui/stepper.js.map +1 -0
- package/components/ui/switch.js +20 -0
- package/components/ui/switch.js.map +1 -0
- package/components/ui/table.js +61 -0
- package/components/ui/table.js.map +1 -0
- package/components/ui/tabs.js +38 -0
- package/components/ui/tabs.js.map +1 -0
- package/components/ui/toggle-group.js +50 -0
- package/components/ui/toggle-group.js.map +1 -0
- package/components/ui/toggle.js +27 -0
- package/components/ui/toggle.js.map +1 -0
- package/components/ui/tooltip.js +38 -0
- package/components/ui/tooltip.js.map +1 -0
- package/lib/utils.d.ts +2 -0
- package/lib/utils.js +10 -0
- package/lib/utils.js.map +1 -0
- package/mountain-snow.svg +4 -0
- package/package.json +203 -0
- package/pihanga-shadcn.css +2 -0
- package/r/badge.json +35 -0
- package/r/box.json +26 -0
- package/r/button.json +39 -0
- package/r/checkbox.json +31 -0
- package/r/conditional.json +30 -0
- package/r/dataTable.json +43 -0
- package/r/dialog.json +36 -0
- package/r/dropDownMenu.json +47 -0
- package/r/field.json +35 -0
- package/r/flexGrid.json +32 -0
- package/r/form.json +37 -0
- package/r/framework.json +28 -0
- package/r/graphin.json +42 -0
- package/r/input.json +35 -0
- package/r/jsonViewer.json +40 -0
- package/r/list.json +39 -0
- package/r/loadingOverlay.json +39 -0
- package/r/loadingSkeleton.json +30 -0
- package/r/markdownViewer.json +51 -0
- package/r/menu.json +34 -0
- package/r/modeToggle.json +36 -0
- package/r/navbarSearch.json +31 -0
- package/r/pageWithNavbar.json +46 -0
- package/r/pasteTarget.json +41 -0
- package/r/pihanga-base.json +11 -0
- package/r/pihanga-cards-icons.json +16 -0
- package/r/pihanga-cards-types.json +16 -0
- package/r/pihanga-hook-use-is-touch-device.json +15 -0
- package/r/pihanga-lib-utils.json +18 -0
- package/r/pihanga-theme-provider.json +25 -0
- package/r/pihanga-ui-extras.json +68 -0
- package/r/registry.json +171 -0
- package/r/resizable.json +35 -0
- package/r/select.json +35 -0
- package/r/stack.json +27 -0
- package/r/stepper.json +39 -0
- package/r/switch.json +36 -0
- package/r/tabs.json +33 -0
- package/r/textField.json +32 -0
- package/r/toast.json +32 -0
- package/r/toggleGroup.json +35 -0
- package/r/typography.json +31 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "pihanga-hook-use-is-touch-device",
|
|
4
|
+
"type": "registry:hook",
|
|
5
|
+
"description": "Pihanga shared hook: use-is-touch-device",
|
|
6
|
+
"dependencies": [],
|
|
7
|
+
"registryDependencies": [],
|
|
8
|
+
"files": [
|
|
9
|
+
{
|
|
10
|
+
"path": "hooks/use-is-touch-device.ts",
|
|
11
|
+
"content": "'use client';\n\nimport * as React from 'react';\n\nexport function useIsTouchDevice() {\n const [isTouchDevice, setIsTouchDevice] = React.useState(false);\n\n React.useEffect(() => {\n function onResize() {\n setIsTouchDevice(\n 'ontouchstart' in window ||\n navigator.maxTouchPoints > 0 ||\n navigator.maxTouchPoints > 0\n );\n }\n\n window.addEventListener('resize', onResize);\n onResize();\n\n return () => {\n window.removeEventListener('resize', onResize);\n };\n }, []);\n\n return isTouchDevice;\n}\n",
|
|
12
|
+
"type": "registry:hook"
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "pihanga-lib-utils",
|
|
4
|
+
"type": "registry:lib",
|
|
5
|
+
"description": "Tailwind CSS utility helper — the cn() function",
|
|
6
|
+
"dependencies": [
|
|
7
|
+
"clsx@^2.1.1",
|
|
8
|
+
"tailwind-merge@^3.3.0"
|
|
9
|
+
],
|
|
10
|
+
"registryDependencies": [],
|
|
11
|
+
"files": [
|
|
12
|
+
{
|
|
13
|
+
"path": "lib/utils.ts",
|
|
14
|
+
"content": "import {clsx, type ClassValue} from \"clsx\";\nimport {twMerge} from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n",
|
|
15
|
+
"type": "registry:lib"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "pihanga-theme-provider",
|
|
4
|
+
"type": "registry:component",
|
|
5
|
+
"description": "Pihanga theme provider — light / dark mode support",
|
|
6
|
+
"dependencies": [],
|
|
7
|
+
"registryDependencies": [],
|
|
8
|
+
"files": [
|
|
9
|
+
{
|
|
10
|
+
"path": "components/theme-provider/index.ts",
|
|
11
|
+
"content": "import {useContext} from \"react\";\nimport {ThemeProviderContext} from \"./theme-provider.types\";\n\nexport const useTheme = () => {\n const context = useContext(ThemeProviderContext);\n\n if (context === undefined)\n throw new Error(\"useTheme must be used within a ThemeProvider\");\n\n return context;\n};\n",
|
|
12
|
+
"type": "registry:component"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"path": "components/theme-provider/theme-provider.component.tsx",
|
|
16
|
+
"content": "import {useEffect, useState} from \"react\";\nimport {\n ThemeProviderContext,\n type Theme,\n type ThemeProviderProps,\n} from \"./theme-provider.types\";\n\nexport function ThemeProvider({\n children,\n defaultTheme = \"system\",\n storageKey = \"vite-ui-theme\",\n ...props\n}: ThemeProviderProps) {\n const [theme, setTheme] = useState<Theme>(\n () => (localStorage.getItem(storageKey) as Theme) || defaultTheme\n );\n\n useEffect(() => {\n const root = window.document.documentElement;\n\n root.classList.remove(\"light\", \"dark\");\n\n if (theme === \"system\") {\n const systemTheme = window.matchMedia(\"(prefers-color-scheme: dark)\")\n .matches\n ? \"dark\"\n : \"light\";\n\n root.classList.add(systemTheme);\n return;\n }\n\n root.classList.add(theme);\n }, [theme]);\n\n const value = {\n theme,\n setTheme: (theme: Theme) => {\n localStorage.setItem(storageKey, theme);\n setTheme(theme);\n },\n };\n\n return (\n <ThemeProviderContext.Provider {...props} value={value}>\n {children}\n </ThemeProviderContext.Provider>\n );\n}\n",
|
|
17
|
+
"type": "registry:component"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"path": "components/theme-provider/theme-provider.types.ts",
|
|
21
|
+
"content": "import {createContext} from \"react\";\n\nexport type Theme = \"dark\" | \"light\" | \"system\";\n\nexport type ThemeProviderProps = {\n children: React.ReactNode;\n defaultTheme?: Theme;\n storageKey?: string;\n};\n\ntype ThemeProviderState = {\n theme: Theme;\n setTheme: (theme: Theme) => void;\n};\n\nconst initialState: ThemeProviderState = {\n theme: \"system\",\n setTheme: () => null,\n};\n\nexport const ThemeProviderContext =\n createContext<ThemeProviderState>(initialState);\n",
|
|
22
|
+
"type": "registry:component"
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "pihanga-ui-extras",
|
|
4
|
+
"type": "registry:ui",
|
|
5
|
+
"description": "Pihanga-custom UI primitives not available in the official shadcn/ui registry (button with LinkButton, customised dialog with DialogBody, spinner, field, stepper, toolbar, variant tables).",
|
|
6
|
+
"dependencies": [
|
|
7
|
+
"class-variance-authority@^0.7.1",
|
|
8
|
+
"radix-ui@^1.4.3",
|
|
9
|
+
"@radix-ui/react-dialog@^1.1.15",
|
|
10
|
+
"lucide-react@^0.513.0",
|
|
11
|
+
"@radix-ui/react-toolbar@^1.1.11",
|
|
12
|
+
"@radix-ui/react-tooltip@^1.2.8"
|
|
13
|
+
],
|
|
14
|
+
"registryDependencies": [
|
|
15
|
+
"https://ivcap-works.github.io/pihanga-shadcn/r/pihanga-lib-utils.json",
|
|
16
|
+
"label",
|
|
17
|
+
"separator",
|
|
18
|
+
"dropdown-menu",
|
|
19
|
+
"tooltip"
|
|
20
|
+
],
|
|
21
|
+
"files": [
|
|
22
|
+
{
|
|
23
|
+
"path": "components/ui/badge-variants.ts",
|
|
24
|
+
"content": "import { cva } from \"class-variance-authority\"\n\nexport const badgeVariants = cva(\n \"inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden\",\n {\n variants: {\n variant: {\n default:\n \"border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90\",\n secondary:\n \"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90\",\n destructive:\n \"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60\",\n outline:\n \"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n },\n }\n)\n",
|
|
25
|
+
"type": "registry:ui"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"path": "components/ui/button-variants.ts",
|
|
29
|
+
"content": "import { cva } from \"class-variance-authority\"\n\nexport const buttonVariants = cva(\n \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive\",\n {\n variants: {\n variant: {\n default: \"bg-primary text-primary-foreground hover:bg-primary/90\",\n destructive:\n \"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60\",\n outline:\n \"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50\",\n secondary:\n \"bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n ghost:\n \"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50\",\n link: \"text-primary underline-offset-4 hover:underline\",\n },\n size: {\n default: \"h-9 px-4 py-2 has-[>svg]:px-3\",\n sm: \"h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5\",\n lg: \"h-10 rounded-md px-6 has-[>svg]:px-4\",\n icon: \"size-9\",\n \"icon-sm\": \"size-8\",\n \"icon-lg\": \"size-10\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n }\n)\n",
|
|
30
|
+
"type": "registry:ui"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"path": "components/ui/button.tsx",
|
|
34
|
+
"content": "import * as React from \"react\";\nimport {cva, type VariantProps} from \"class-variance-authority\";\nimport {Slot} from \"radix-ui\";\n\nimport {cn} from \"@/lib/utils\";\n\nconst buttonVariants = cva(\n \"inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n {\n variants: {\n variant: {\n default: \"bg-primary text-primary-foreground hover:bg-primary/90\",\n destructive:\n \"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40\",\n outline:\n \"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50\",\n secondary:\n \"bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n ghost:\n \"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50\",\n link: \"text-primary underline-offset-4 hover:underline\",\n // Extended variants used by pi/button\n ghost2:\n \"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50\",\n ghost3:\n \"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50\",\n ghostActive: \"bg-accent text-accent-foreground\",\n brand: \"bg-primary text-primary-foreground hover:bg-primary/90\",\n nav: \"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50\",\n navAction:\n \"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50\",\n menuAction:\n \"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50\",\n blockAction:\n \"bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n blockActionSecondary:\n \"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground\",\n primaryOutline:\n \"border border-primary text-primary hover:bg-primary hover:text-primary-foreground\",\n radio:\n \"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground\",\n none: \"\",\n },\n size: {\n default: \"h-9 px-4 py-2 has-[>svg]:px-3\",\n xs: \"h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3\",\n sm: \"h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5\",\n md: \"h-9 px-4 py-2 has-[>svg]:px-3\",\n lg: \"h-10 rounded-md px-6 has-[>svg]:px-4\",\n icon: \"size-9\",\n iconSm: \"size-8\",\n \"icon-xs\": \"size-6 rounded-md [&_svg:not([class*='size-'])]:size-3\",\n \"icon-sm\": \"size-8\",\n \"icon-lg\": \"size-10\",\n navAction: \"h-9 px-3\",\n menuAction: \"h-8 px-3 text-sm\",\n blockAction: \"h-10 px-4\",\n none: \"\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n },\n);\n\n/** Extra props accepted by both Button and LinkButton beyond standard HTML/CVA props */\ntype ButtonExtendedProps = {\n /** Accessible label; maps to aria-label */\n label?: string;\n active?: boolean;\n focused?: boolean;\n isMenu?: boolean;\n truncate?: boolean;\n loading?: boolean;\n isPending?: boolean;\n loadingClassName?: string;\n};\n\nfunction Button({\n className,\n variant = \"default\",\n size = \"default\",\n asChild = false,\n label,\n active,\n focused,\n isMenu,\n truncate,\n loading,\n isPending,\n loadingClassName: _loadingClassName,\n ...props\n}: React.ComponentProps<\"button\"> &\n VariantProps<typeof buttonVariants> & {\n asChild?: boolean;\n } & ButtonExtendedProps) {\n const Comp = asChild ? Slot.Root : \"button\";\n\n return (\n <Comp\n data-slot=\"button\"\n data-variant={variant}\n data-size={size}\n aria-label={label}\n data-active={active || undefined}\n data-focused={focused || undefined}\n data-is-menu={isMenu || undefined}\n data-loading={loading || undefined}\n data-pending={isPending || undefined}\n className={cn(\n buttonVariants({variant, size, className}),\n truncate && \"truncate\",\n )}\n {...props}\n />\n );\n}\n\n/** Button rendered as an anchor (`<a>`) element */\nfunction LinkButton({\n className,\n variant = \"default\",\n size = \"default\",\n href,\n target,\n label,\n active,\n focused,\n isMenu,\n truncate,\n loading,\n isPending,\n loadingClassName: _loadingClassName,\n onClick,\n children,\n ...props\n}: Omit<React.ComponentProps<\"a\">, \"onClick\"> &\n VariantProps<typeof buttonVariants> &\n ButtonExtendedProps & {\n href?: string;\n target?: string;\n onClick?: () => void;\n }) {\n return (\n <a\n data-slot=\"button\"\n data-variant={variant}\n data-size={size}\n href={href}\n target={target}\n aria-label={label}\n data-active={active || undefined}\n data-focused={focused || undefined}\n data-is-menu={isMenu || undefined}\n data-loading={loading || undefined}\n data-pending={isPending || undefined}\n className={cn(\n buttonVariants({variant, size, className}),\n truncate && \"truncate\",\n )}\n onClick={\n onClick\n ? (e) => {\n e.preventDefault();\n onClick();\n }\n : undefined\n }\n {...props}\n >\n {children}\n </a>\n );\n}\n\nexport {Button, buttonVariants, LinkButton};\n",
|
|
35
|
+
"type": "registry:ui"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"path": "components/ui/dialog.tsx",
|
|
39
|
+
"content": "import * as React from \"react\";\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\";\nimport {X} from \"lucide-react\";\n\nimport {cn} from \"@/lib/utils\";\n\nconst sizeClasses: Record<string, string> = {\n xs: \"max-w-xs\",\n sm: \"max-w-sm\",\n md: \"max-w-md\",\n lg: \"max-w-lg\",\n xl: \"max-w-xl\",\n \"2xl\": \"max-w-2xl\",\n \"3xl\": \"max-w-3xl\",\n \"4xl\": \"max-w-4xl\",\n};\n\ntype DialogProps = React.ComponentProps<typeof DialogPrimitive.Root> & {\n desktopVariant?: \"modal\" | \"drawer\" | \"full\";\n mobileVariant?: \"modal\" | \"drawer\" | \"full\";\n};\n\nfunction Dialog({\n desktopVariant: _desktopVariant,\n mobileVariant: _mobileVariant,\n ...props\n}: DialogProps) {\n return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />;\n}\n\nfunction DialogTrigger({\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {\n return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />;\n}\n\nfunction DialogPortal({\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Portal>) {\n return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n}\n\nfunction DialogClose({\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Close>) {\n return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />;\n}\n\nfunction DialogOverlay({\n className,\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {\n return (\n <DialogPrimitive.Overlay\n data-slot=\"dialog-overlay\"\n className={cn(\n \"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50\",\n className,\n )}\n {...props}\n />\n );\n}\n\ntype DialogContentProps = React.ComponentProps<\n typeof DialogPrimitive.Content\n> & {\n size?: \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\" | \"2xl\" | \"3xl\" | \"4xl\";\n variant?: \"modal\" | \"full\";\n dismissible?: boolean;\n hideClose?: boolean;\n fixed?: boolean;\n};\n\nfunction DialogContent({\n className,\n children,\n size = \"md\",\n variant,\n dismissible = true,\n hideClose = false,\n fixed,\n ...props\n}: DialogContentProps) {\n return (\n <DialogPortal>\n <DialogOverlay />\n <DialogPrimitive.Content\n data-slot=\"dialog-content\"\n onInteractOutside={\n dismissible === false ? (e) => e.preventDefault() : undefined\n }\n onEscapeKeyDown={\n dismissible === false ? (e) => e.preventDefault() : undefined\n }\n className={cn(\n \"bg-card text-card-foreground border border-border data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-1/2 left-1/2 z-50 w-full -translate-x-1/2 -translate-y-1/2 rounded-lg shadow-xl\",\n sizeClasses[size],\n variant === \"full\" && \"max-w-full h-full rounded-none\",\n fixed && \"flex flex-col max-h-[90vh]\",\n className,\n )}\n {...props}\n >\n {!hideClose && (\n <DialogPrimitive.Close className=\"absolute top-4 right-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none\">\n <X className=\"h-4 w-4\" />\n <span className=\"sr-only\">Close</span>\n </DialogPrimitive.Close>\n )}\n {children}\n </DialogPrimitive.Content>\n </DialogPortal>\n );\n}\n\nfunction DialogHeader({className, ...props}: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"dialog-header\"\n className={cn(\n \"flex shrink-0 flex-col gap-2 px-6 pt-6 text-center sm:text-left\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction DialogBody({className, ...props}: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"dialog-body\"\n className={cn(\"flex-1 overflow-auto px-6 py-4\", className)}\n {...props}\n />\n );\n}\n\nfunction DialogFooter({className, ...props}: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"dialog-footer\"\n className={cn(\n \"flex shrink-0 flex-col-reverse gap-2 px-6 pb-6 sm:flex-row sm:justify-end\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction DialogTitle({\n className,\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Title>) {\n return (\n <DialogPrimitive.Title\n data-slot=\"dialog-title\"\n className={cn(\"text-lg font-semibold leading-none\", className)}\n {...props}\n />\n );\n}\n\nfunction DialogDescription({\n className,\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Description>) {\n return (\n <DialogPrimitive.Description\n data-slot=\"dialog-description\"\n className={cn(\"text-muted-foreground text-sm\", className)}\n {...props}\n />\n );\n}\n\nexport {\n Dialog,\n DialogBody,\n DialogClose,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogOverlay,\n DialogPortal,\n DialogTitle,\n DialogTrigger,\n};\n",
|
|
40
|
+
"type": "registry:ui"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"path": "components/ui/editor-static-variants.ts",
|
|
44
|
+
"content": "import { cva } from \"class-variance-authority\"\n\nimport { cn } from \"@/components/lib/utils\"\n\nexport const editorVariants = cva(\n cn(\n \"group/editor\",\n \"relative w-full cursor-text overflow-x-hidden break-words whitespace-pre-wrap select-text\",\n \"rounded-md ring-offset-background focus-visible:outline-none\",\n \"placeholder:text-muted-foreground/80 **:data-slate-placeholder:top-[auto_!important] **:data-slate-placeholder:text-muted-foreground/80 **:data-slate-placeholder:opacity-100!\",\n \"[&_strong]:font-bold\"\n ),\n {\n defaultVariants: {\n variant: \"none\",\n },\n variants: {\n disabled: {\n true: \"cursor-not-allowed opacity-50\",\n },\n focused: {\n true: \"ring-2 ring-ring ring-offset-2\",\n },\n variant: {\n ai: \"w-full px-0 text-base md:text-sm\",\n aiChat:\n \"max-h-[min(70vh,320px)] w-full max-w-[700px] overflow-y-auto px-5 py-3 text-base md:text-sm\",\n default:\n \"size-full px-16 pt-4 pb-72 text-base sm:px-[max(64px,calc(50%-350px))]\",\n demo: \"size-full px-16 pt-4 pb-72 text-base sm:px-[max(64px,calc(50%-350px))]\",\n fullWidth: \"size-full px-16 pt-4 pb-72 text-base sm:px-24\",\n none: \"\",\n select: \"px-3 py-2 text-base data-readonly:w-fit\",\n },\n },\n }\n)\n",
|
|
45
|
+
"type": "registry:ui"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"path": "components/ui/field.tsx",
|
|
49
|
+
"content": "import { useMemo } from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Label } from \"@/components/ui/label\"\nimport { Separator } from \"@/components/ui/separator\"\n\nfunction FieldSet({ className, ...props }: React.ComponentProps<\"fieldset\">) {\n return (\n <fieldset\n data-slot=\"field-set\"\n className={cn(\n \"flex flex-col gap-6\",\n \"has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction FieldLegend({\n className,\n variant = \"legend\",\n ...props\n}: React.ComponentProps<\"legend\"> & { variant?: \"legend\" | \"label\" }) {\n return (\n <legend\n data-slot=\"field-legend\"\n data-variant={variant}\n className={cn(\n \"mb-3 font-medium\",\n \"data-[variant=legend]:text-base\",\n \"data-[variant=label]:text-sm\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction FieldGroup({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"field-group\"\n className={cn(\n \"group/field-group @container/field-group flex w-full flex-col gap-7 data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4\",\n className\n )}\n {...props}\n />\n )\n}\n\nconst fieldVariants = cva(\n \"group/field flex w-full gap-3 data-[invalid=true]:text-destructive\",\n {\n variants: {\n orientation: {\n vertical: [\"flex-col [&>*]:w-full [&>.sr-only]:w-auto\"],\n horizontal: [\n \"flex-row items-center\",\n \"[&>[data-slot=field-label]]:flex-auto\",\n \"has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px\",\n ],\n responsive: [\n \"flex-col @md/field-group:flex-row @md/field-group:items-center [&>*]:w-full @md/field-group:[&>*]:w-auto [&>.sr-only]:w-auto\",\n \"@md/field-group:[&>[data-slot=field-label]]:flex-auto\",\n \"@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px\",\n ],\n },\n },\n defaultVariants: {\n orientation: \"vertical\",\n },\n }\n)\n\nfunction Field({\n className,\n orientation = \"vertical\",\n ...props\n}: React.ComponentProps<\"div\"> & VariantProps<typeof fieldVariants>) {\n return (\n <div\n role=\"group\"\n data-slot=\"field\"\n data-orientation={orientation}\n className={cn(fieldVariants({ orientation }), className)}\n {...props}\n />\n )\n}\n\nfunction FieldContent({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"field-content\"\n className={cn(\n \"group/field-content flex flex-1 flex-col gap-1.5 leading-snug\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction FieldLabel({\n className,\n ...props\n}: React.ComponentProps<typeof Label>) {\n return (\n <Label\n data-slot=\"field-label\"\n className={cn(\n \"group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50\",\n \"has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border [&>*]:data-[slot=field]:p-4\",\n \"has-data-[state=checked]:border-primary has-data-[state=checked]:bg-primary/5 dark:has-data-[state=checked]:bg-primary/10\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction FieldTitle({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"field-label\"\n className={cn(\n \"flex w-fit items-center gap-2 text-sm leading-snug font-medium group-data-[disabled=true]/field:opacity-50\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction FieldDescription({ className, ...props }: React.ComponentProps<\"p\">) {\n return (\n <p\n data-slot=\"field-description\"\n className={cn(\n \"text-sm leading-normal font-normal text-muted-foreground group-has-[[data-orientation=horizontal]]/field:text-balance\",\n \"last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5\",\n \"[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction FieldSeparator({\n children,\n className,\n ...props\n}: React.ComponentProps<\"div\"> & {\n children?: React.ReactNode\n}) {\n return (\n <div\n data-slot=\"field-separator\"\n data-content={!!children}\n className={cn(\n \"relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2\",\n className\n )}\n {...props}\n >\n <Separator className=\"absolute inset-0 top-1/2\" />\n {children && (\n <span\n className=\"relative mx-auto block w-fit bg-background px-2 text-muted-foreground\"\n data-slot=\"field-separator-content\"\n >\n {children}\n </span>\n )}\n </div>\n )\n}\n\nfunction FieldError({\n className,\n children,\n errors,\n ...props\n}: React.ComponentProps<\"div\"> & {\n errors?: Array<{ message?: string } | undefined>\n}) {\n const content = useMemo(() => {\n if (children) {\n return children\n }\n\n if (!errors?.length) {\n return null\n }\n\n const uniqueErrors = [\n ...new Map(errors.map((error) => [error?.message, error])).values(),\n ]\n\n if (uniqueErrors?.length == 1) {\n return uniqueErrors[0]?.message\n }\n\n return (\n <ul className=\"ml-4 flex list-disc flex-col gap-1\">\n {uniqueErrors.map(\n (error, index) =>\n error?.message && <li key={index}>{error.message}</li>\n )}\n </ul>\n )\n }, [children, errors])\n\n if (!content) {\n return null\n }\n\n return (\n <div\n role=\"alert\"\n data-slot=\"field-error\"\n className={cn(\"text-sm font-normal text-destructive\", className)}\n {...props}\n >\n {content}\n </div>\n )\n}\n\nexport {\n Field,\n FieldLabel,\n FieldDescription,\n FieldError,\n FieldGroup,\n FieldLegend,\n FieldSeparator,\n FieldSet,\n FieldContent,\n FieldTitle,\n}\n",
|
|
50
|
+
"type": "registry:ui"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"path": "components/ui/spinner.tsx",
|
|
54
|
+
"content": "import {cn} from \"@/lib/utils\";\n\ntype SpinnerProps = {\n className?: string;\n size?: \"default\" | \"sm\" | \"lg\" | \"icon\";\n};\n\nconst sizeClasses: Record<string, string> = {\n default: \"size-6\",\n sm: \"size-4\",\n lg: \"size-8\",\n icon: \"size-9\",\n};\n\nfunction Spinner({className, size = \"default\"}: SpinnerProps) {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n role=\"status\"\n aria-label=\"Loading\"\n className={cn(\"animate-spin\", sizeClasses[size], className)}\n >\n <circle\n className=\"opacity-25\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n strokeWidth=\"4\"\n />\n <path\n className=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n />\n </svg>\n );\n}\n\nexport {Spinner};\n",
|
|
55
|
+
"type": "registry:ui"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"path": "components/ui/stepper.tsx",
|
|
59
|
+
"content": "/**\n * Stepper UI primitive — shadcn/Tailwind-based, inspired by MUI Joy UI's Stepper.\n *\n * Architecture\n * ───────────\n * • Tailwind classes handle structural / layout / dimension concerns that are\n * difficult to override with plain CSS (flex layout, width/height, spacing).\n * • BEM class names (`pi-stepper`, `pi-stepper__*`, `pi-stepper__*--state`)\n * are applied to every element so consumers can override visual styles\n * (colours, borders, transitions) in `stepper.css` or any downstream sheet.\n * • Default visual styles live in `src/cards/shadcn/stepper/stepper.css`\n * (imported by the Pihanga card component).\n */\nimport * as React from \"react\";\nimport {Check} from \"lucide-react\";\nimport {cn} from \"@/lib/utils\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type StepState = \"completed\" | \"active\" | \"upcoming\";\n\nexport interface StepData {\n id: string;\n title: string;\n description?: string;\n optional?: boolean;\n /**\n * Optional pre-rendered React node to replace the default title/description\n * label. Produced by the Pihanga card component when `labelCard` is set on\n * a `StepItem`; rendered via `<Card cardName={step.labelCard} />` there.\n */\n labelNode?: React.ReactNode;\n}\n\nexport interface StepperUIProps {\n steps: StepData[];\n /** 0-based index of the currently active step */\n activeStep: number;\n orientation?: \"horizontal\" | \"vertical\";\n size?: \"sm\" | \"md\" | \"lg\";\n className?: string;\n /** If provided, steps become clickable */\n onStepClick?: (stepIndex: number, stepId: string) => void;\n /** Optional content rendered below the label in vertical-mode active steps */\n renderStepContent?: (stepIndex: number, step: StepData) => React.ReactNode;\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction getStepState(stepIndex: number, activeStep: number): StepState {\n if (stepIndex < activeStep) return \"completed\";\n if (stepIndex === activeStep) return \"active\";\n return \"upcoming\";\n}\n\n// Tailwind dimension classes for the step-indicator button.\n// Kept here so layout always works even without the CSS file loaded.\nconst INDICATOR_SIZE = {\n sm: {btn: \"h-6 w-6 text-xs\", icon: \"h-3 w-3\"},\n md: {btn: \"h-8 w-8 text-sm\", icon: \"h-4 w-4\"},\n lg: {btn: \"h-10 w-10 text-base\", icon: \"h-5 w-5\"},\n} as const;\n\n// Tailwind label font classes — fallback when CSS file is absent.\nconst LABEL_SIZE = {\n sm: \"text-xs\",\n md: \"text-sm\",\n lg: \"text-base\",\n} as const;\n\n// ─── Indicator button ─────────────────────────────────────────────────────────\n\nfunction IndicatorButton({\n state,\n stepIndex,\n size,\n onClick,\n}: {\n state: StepState;\n stepIndex: number;\n size: \"sm\" | \"md\" | \"lg\";\n onClick?: () => void;\n}) {\n const s = INDICATOR_SIZE[size];\n return (\n <button\n type=\"button\"\n onClick={onClick}\n disabled={!onClick}\n aria-current={state === \"active\" ? \"step\" : undefined}\n className={cn(\n // ── BEM — visual override target ──\n \"pi-stepper__indicator\",\n `pi-stepper__indicator--${state}`,\n onClick\n ? \"pi-stepper__indicator--clickable\"\n : \"pi-stepper__indicator--static\",\n // ── Tailwind — structural defaults (always applied) ──\n \"flex shrink-0 items-center justify-center rounded-full font-semibold\",\n s.btn,\n // Default visual appearance via Tailwind (overridden by stepper.css rules)\n state === \"completed\" && \"bg-primary text-primary-foreground\",\n state === \"active\" &&\n \"bg-primary text-primary-foreground ring-2 ring-primary ring-offset-2\",\n state === \"upcoming\" &&\n \"border-2 border-muted-foreground/40 bg-background text-muted-foreground\",\n onClick && \"cursor-pointer hover:opacity-80\",\n !onClick && \"cursor-default\",\n )}\n >\n {state === \"completed\" ? (\n <Check\n className={cn(\"pi-stepper__indicator-icon\", s.icon)}\n strokeWidth={2.5}\n />\n ) : (\n <span className=\"pi-stepper__indicator-label\">{stepIndex + 1}</span>\n )}\n </button>\n );\n}\n\n// ─── Label ────────────────────────────────────────────────────────────────────\n\nfunction StepLabel({\n step,\n state,\n size,\n vertical = false,\n}: {\n step: StepData;\n state: StepState;\n size: \"sm\" | \"md\" | \"lg\";\n vertical?: boolean;\n}) {\n // Custom card label takes precedence over the default text label.\n if (step.labelNode != null) {\n return (\n <div\n className={cn(\n \"pi-stepper__label-card\",\n `pi-stepper__label-card--${state}`,\n vertical && \"pi-stepper__label-card--vertical\",\n // Tailwind defaults\n vertical ? \"ml-4\" : \"mt-2 text-center\",\n )}\n >\n {step.labelNode}\n </div>\n );\n }\n\n return (\n <div\n className={cn(\n \"pi-stepper__label\",\n `pi-stepper__label--${state}`,\n vertical && \"pi-stepper__label--vertical\",\n // Tailwind defaults\n vertical ? \"ml-4\" : \"mt-2 text-center\",\n !vertical && \"max-w-[96px]\",\n )}\n >\n <p\n className={cn(\n \"pi-stepper__label-title\",\n `pi-stepper__label-title--${state}`,\n // Tailwind defaults\n LABEL_SIZE[size],\n \"font-medium leading-tight\",\n state === \"upcoming\" ? \"text-muted-foreground\" : \"text-foreground\",\n vertical && \"leading-none\",\n )}\n >\n {step.title}\n {vertical && step.optional && (\n <span className=\"pi-stepper__label-optional ml-1 text-xs font-normal text-muted-foreground\">\n (Optional)\n </span>\n )}\n </p>\n\n {!vertical && step.optional && (\n <p className=\"pi-stepper__label-optional text-xs font-normal text-muted-foreground\">\n Optional\n </p>\n )}\n\n {step.description && (\n <p className=\"pi-stepper__label-description mt-0.5 text-xs text-muted-foreground\">\n {step.description}\n </p>\n )}\n </div>\n );\n}\n\n// ─── Horizontal layout ────────────────────────────────────────────────────────\n\nfunction HorizontalStepper({\n steps,\n activeStep,\n size = \"md\",\n className,\n onStepClick,\n}: StepperUIProps) {\n return (\n <ol\n aria-label=\"Steps\"\n className={cn(\n \"pi-stepper pi-stepper--horizontal\",\n `pi-stepper--${size}`,\n \"flex w-full items-start\",\n className,\n )}\n >\n {steps.map((step, index) => {\n const state = getStepState(index, activeStep);\n const isFirst = index === 0;\n const isLast = index === steps.length - 1;\n\n const leftCompleted =\n !isFirst && getStepState(index - 1, activeStep) === \"completed\";\n const rightCompleted = !isLast && state === \"completed\";\n\n return (\n <li\n key={step.id}\n className={cn(\n \"pi-stepper__step\",\n `pi-stepper__step--${state}`,\n isFirst && \"pi-stepper__step--first\",\n isLast && \"pi-stepper__step--last\",\n \"flex flex-1 flex-col items-center\",\n )}\n >\n {/* ── [left-half] [indicator] [right-half] centred row ── */}\n <div className=\"flex w-full items-center\">\n {isFirst ? (\n <div className=\"flex-1\" aria-hidden=\"true\" />\n ) : (\n <div\n className={cn(\n \"pi-stepper__connector pi-stepper__connector--left\",\n leftCompleted\n ? \"pi-stepper__connector--completed\"\n : \"pi-stepper__connector--upcoming\",\n // Tailwind defaults\n \"h-0.5 flex-1 transition-colors duration-300\",\n leftCompleted ? \"bg-primary\" : \"bg-border\",\n )}\n />\n )}\n\n <IndicatorButton\n state={state}\n stepIndex={index}\n size={size}\n onClick={\n onStepClick ? () => onStepClick(index, step.id) : undefined\n }\n />\n\n {isLast ? (\n <div className=\"flex-1\" aria-hidden=\"true\" />\n ) : (\n <div\n className={cn(\n \"pi-stepper__connector pi-stepper__connector--right\",\n rightCompleted\n ? \"pi-stepper__connector--completed\"\n : \"pi-stepper__connector--upcoming\",\n // Tailwind defaults\n \"h-0.5 flex-1 transition-colors duration-300\",\n rightCompleted ? \"bg-primary\" : \"bg-border\",\n )}\n />\n )}\n </div>\n\n <StepLabel step={step} state={state} size={size} vertical={false} />\n </li>\n );\n })}\n </ol>\n );\n}\n\n// ─── Vertical layout ──────────────────────────────────────────────────────────\n\nfunction VerticalStepper({\n steps,\n activeStep,\n size = \"md\",\n className,\n onStepClick,\n renderStepContent,\n}: StepperUIProps) {\n return (\n <ol\n className={cn(\n \"pi-stepper pi-stepper--vertical\",\n `pi-stepper--${size}`,\n \"flex flex-col\",\n className,\n )}\n >\n {steps.map((step, index) => {\n const state = getStepState(index, activeStep);\n const isFirst = index === 0;\n const isLast = index === steps.length - 1;\n const content =\n state === \"active\" && renderStepContent\n ? renderStepContent(index, step)\n : null;\n\n return (\n <li\n key={step.id}\n className={cn(\n \"pi-stepper__step\",\n `pi-stepper__step--${state}`,\n isFirst && \"pi-stepper__step--first\",\n isLast && \"pi-stepper__step--last\",\n \"flex\",\n )}\n >\n {/* Left col: indicator + connector */}\n <div className=\"flex flex-col items-center\">\n <IndicatorButton\n state={state}\n stepIndex={index}\n size={size}\n onClick={\n onStepClick ? () => onStepClick(index, step.id) : undefined\n }\n />\n {!isLast && (\n <div\n className={cn(\n \"pi-stepper__connector pi-stepper__connector--vertical\",\n state === \"completed\"\n ? \"pi-stepper__connector--completed\"\n : \"pi-stepper__connector--upcoming\",\n // Tailwind defaults\n \"mb-1 mt-1 w-0.5 flex-1 transition-colors duration-300\",\n state === \"completed\" ? \"bg-primary\" : \"bg-border\",\n )}\n style={{minHeight: \"24px\"}}\n />\n )}\n </div>\n\n {/* Right col: label + content */}\n <div className={cn(\"flex-1\", !isLast && \"pb-6\")}>\n <StepLabel\n step={step}\n state={state}\n size={size}\n vertical={true}\n />\n {content && (\n <div className=\"pi-stepper__step-content ml-4 mt-3\">\n {content}\n </div>\n )}\n </div>\n </li>\n );\n })}\n </ol>\n );\n}\n\n// ─── Public export ────────────────────────────────────────────────────────────\n\nexport function StepperUI(props: StepperUIProps) {\n return props.orientation === \"vertical\" ? (\n <VerticalStepper {...props} />\n ) : (\n <HorizontalStepper {...props} />\n );\n}\n",
|
|
60
|
+
"type": "registry:ui"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"path": "components/ui/toolbar.tsx",
|
|
64
|
+
"content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport * as ToolbarPrimitive from \"@radix-ui/react-toolbar\";\nimport * as TooltipPrimitive from \"@radix-ui/react-tooltip\";\nimport {type VariantProps, cva} from \"class-variance-authority\";\nimport {ChevronDown} from \"lucide-react\";\n\nimport {\n DropdownMenuLabel,\n DropdownMenuRadioGroup,\n DropdownMenuSeparator,\n} from \"@/components/ui/dropdown-menu\";\nimport {Separator} from \"@/components/ui/separator\";\nimport {Tooltip, TooltipTrigger} from \"@/components/ui/tooltip\";\nimport {cn} from \"@/components/lib/utils\";\n\nexport function Toolbar({\n className,\n ...props\n}: React.ComponentProps<typeof ToolbarPrimitive.Root>) {\n return (\n <ToolbarPrimitive.Root\n className={cn(\"relative flex items-center select-none\", className)}\n {...props}\n />\n );\n}\n\nexport function ToolbarToggleGroup({\n className,\n ...props\n}: React.ComponentProps<typeof ToolbarPrimitive.ToolbarToggleGroup>) {\n return (\n <ToolbarPrimitive.ToolbarToggleGroup\n className={cn(\"flex items-center\", className)}\n {...props}\n />\n );\n}\n\nexport function ToolbarLink({\n className,\n ...props\n}: React.ComponentProps<typeof ToolbarPrimitive.Link>) {\n return (\n <ToolbarPrimitive.Link\n className={cn(\"font-medium underline underline-offset-4\", className)}\n {...props}\n />\n );\n}\n\nexport function ToolbarSeparator({\n className,\n ...props\n}: React.ComponentProps<typeof ToolbarPrimitive.Separator>) {\n return (\n <ToolbarPrimitive.Separator\n className={cn(\"mx-2 my-1 w-px shrink-0 bg-border\", className)}\n {...props}\n />\n );\n}\n\n// From toggleVariants\nconst toolbarButtonVariants = cva(\n \"inline-flex cursor-pointer items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,box-shadow] outline-none hover:bg-muted hover:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-checked:bg-accent aria-checked:text-accent-foreground aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n {\n defaultVariants: {\n size: \"default\",\n variant: \"default\",\n },\n variants: {\n size: {\n default: \"h-9 min-w-9 px-2\",\n lg: \"h-10 min-w-10 px-2.5\",\n sm: \"h-8 min-w-8 px-1.5\",\n },\n variant: {\n default: \"bg-transparent\",\n outline:\n \"border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground\",\n },\n },\n }\n);\n\nconst dropdownArrowVariants = cva(\n cn(\n \"inline-flex items-center justify-center rounded-r-md text-sm font-medium text-foreground transition-colors disabled:pointer-events-none disabled:opacity-50\"\n ),\n {\n defaultVariants: {\n size: \"sm\",\n variant: \"default\",\n },\n variants: {\n size: {\n default: \"h-9 w-6\",\n lg: \"h-10 w-8\",\n sm: \"h-8 w-4\",\n },\n variant: {\n default:\n \"bg-transparent hover:bg-muted hover:text-muted-foreground aria-checked:bg-accent aria-checked:text-accent-foreground\",\n outline:\n \"border border-l-0 border-input bg-transparent hover:bg-accent hover:text-accent-foreground\",\n },\n },\n }\n);\n\ntype ToolbarButtonProps = {\n isDropdown?: boolean;\n pressed?: boolean;\n} & Omit<\n React.ComponentPropsWithoutRef<typeof ToolbarToggleItem>,\n \"asChild\" | \"value\"\n> &\n VariantProps<typeof toolbarButtonVariants>;\n\nexport const ToolbarButton = withTooltip(function ToolbarButton({\n children,\n className,\n isDropdown,\n pressed,\n size = \"sm\",\n variant,\n ...props\n}: ToolbarButtonProps) {\n return typeof pressed === \"boolean\" ? (\n <ToolbarToggleGroup disabled={props.disabled} value=\"single\" type=\"single\">\n <ToolbarToggleItem\n className={cn(\n toolbarButtonVariants({\n size,\n variant,\n }),\n isDropdown && \"justify-between gap-1 pr-1\",\n className\n )}\n value={pressed ? \"single\" : \"\"}\n {...props}\n >\n {isDropdown ? (\n <>\n <div className=\"flex flex-1 items-center gap-2 whitespace-nowrap\">\n {children}\n </div>\n <div>\n <ChevronDown\n className=\"size-3.5 text-muted-foreground\"\n data-icon\n />\n </div>\n </>\n ) : (\n children\n )}\n </ToolbarToggleItem>\n </ToolbarToggleGroup>\n ) : (\n <ToolbarPrimitive.Button\n className={cn(\n toolbarButtonVariants({\n size,\n variant,\n }),\n isDropdown && \"pr-1\",\n className\n )}\n {...props}\n >\n {children}\n </ToolbarPrimitive.Button>\n );\n});\n\nexport function ToolbarSplitButton({\n className,\n ...props\n}: React.ComponentPropsWithoutRef<typeof ToolbarButton>) {\n return (\n <ToolbarButton\n className={cn(\"group flex gap-0 px-0 hover:bg-transparent\", className)}\n {...props}\n />\n );\n}\n\ntype ToolbarSplitButtonPrimaryProps = Omit<\n React.ComponentPropsWithoutRef<typeof ToolbarToggleItem>,\n \"value\"\n> &\n VariantProps<typeof toolbarButtonVariants>;\n\nexport function ToolbarSplitButtonPrimary({\n children,\n className,\n size = \"sm\",\n variant,\n ...props\n}: ToolbarSplitButtonPrimaryProps) {\n return (\n <span\n className={cn(\n toolbarButtonVariants({\n size,\n variant,\n }),\n \"rounded-r-none\",\n \"group-data-[pressed=true]:bg-accent group-data-[pressed=true]:text-accent-foreground\",\n className\n )}\n {...props}\n >\n {children}\n </span>\n );\n}\n\nexport function ToolbarSplitButtonSecondary({\n className,\n size,\n variant,\n ...props\n}: React.ComponentPropsWithoutRef<\"span\"> &\n VariantProps<typeof dropdownArrowVariants>) {\n return (\n <span\n className={cn(\n dropdownArrowVariants({\n size,\n variant,\n }),\n \"group-data-[pressed=true]:bg-accent group-data-[pressed=true]:text-accent-foreground\",\n className\n )}\n onClick={(e) => e.stopPropagation()}\n role=\"button\"\n {...props}\n >\n <ChevronDown className=\"size-3.5 text-muted-foreground\" data-icon />\n </span>\n );\n}\n\nexport function ToolbarToggleItem({\n className,\n size = \"sm\",\n variant,\n ...props\n}: React.ComponentProps<typeof ToolbarPrimitive.ToggleItem> &\n VariantProps<typeof toolbarButtonVariants>) {\n return (\n <ToolbarPrimitive.ToggleItem\n className={cn(toolbarButtonVariants({size, variant}), className)}\n {...props}\n />\n );\n}\n\nexport function ToolbarGroup({\n children,\n className,\n}: React.ComponentProps<\"div\">) {\n return (\n <div\n className={cn(\n \"group/toolbar-group\",\n \"relative hidden has-[button]:flex\",\n className\n )}\n >\n <div className=\"flex items-center\">{children}</div>\n\n <div className=\"mx-1.5 py-0.5 group-last/toolbar-group:hidden!\">\n <Separator orientation=\"vertical\" />\n </div>\n </div>\n );\n}\n\ntype TooltipProps<T extends React.ElementType> = {\n tooltip?: React.ReactNode;\n tooltipContentProps?: Omit<\n React.ComponentPropsWithoutRef<typeof TooltipContent>,\n \"children\"\n >;\n tooltipProps?: Omit<\n React.ComponentPropsWithoutRef<typeof Tooltip>,\n \"children\"\n >;\n tooltipTriggerProps?: React.ComponentPropsWithoutRef<typeof TooltipTrigger>;\n} & React.ComponentProps<T>;\n\nfunction withTooltip<T extends React.ElementType>(Component: T) {\n return function ExtendComponent({\n tooltip,\n tooltipContentProps,\n tooltipProps,\n tooltipTriggerProps,\n ...props\n }: TooltipProps<T>) {\n const [mounted, setMounted] = React.useState(false);\n\n React.useEffect(() => {\n setMounted(true);\n }, []);\n\n const component = <Component {...(props as React.ComponentProps<T>)} />;\n\n if (tooltip && mounted) {\n return (\n <Tooltip {...tooltipProps}>\n <TooltipTrigger asChild {...tooltipTriggerProps}>\n {component}\n </TooltipTrigger>\n\n <TooltipContent {...tooltipContentProps}>{tooltip}</TooltipContent>\n </Tooltip>\n );\n }\n\n return component;\n };\n}\n\nfunction TooltipContent({\n children,\n className,\n // CHANGE\n sideOffset = 4,\n ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Content>) {\n return (\n <TooltipPrimitive.Portal>\n <TooltipPrimitive.Content\n className={cn(\n \"z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md bg-primary px-3 py-1.5 text-xs text-balance text-primary-foreground\",\n className\n )}\n data-slot=\"tooltip-content\"\n sideOffset={sideOffset}\n {...props}\n >\n {children}\n {/* CHANGE */}\n {/* <TooltipPrimitive.Arrow className=\"z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-primary fill-primary\" /> */}\n </TooltipPrimitive.Content>\n </TooltipPrimitive.Portal>\n );\n}\n\nexport function ToolbarMenuGroup({\n children,\n className,\n label,\n ...props\n}: React.ComponentProps<typeof DropdownMenuRadioGroup> & {label?: string}) {\n return (\n <>\n <DropdownMenuSeparator\n className={cn(\n \"hidden\",\n \"mb-0 shrink-0 peer-has-[[role=menuitem]]/menu-group:block peer-has-[[role=menuitemradio]]/menu-group:block peer-has-[[role=option]]/menu-group:block\"\n )}\n />\n\n <DropdownMenuRadioGroup\n {...props}\n className={cn(\n \"hidden\",\n \"peer/menu-group group/menu-group my-1.5 has-[[role=menuitem]]:block has-[[role=menuitemradio]]:block has-[[role=option]]:block\",\n className\n )}\n >\n {label && (\n <DropdownMenuLabel className=\"text-xs font-semibold text-muted-foreground select-none\">\n {label}\n </DropdownMenuLabel>\n )}\n {children}\n </DropdownMenuRadioGroup>\n </>\n );\n}\n",
|
|
65
|
+
"type": "registry:ui"
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
}
|
package/r/registry.json
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry.json",
|
|
3
|
+
"name": "pihanga-shadcn",
|
|
4
|
+
"homepage": "https://ivcap-works.github.io/pihanga-shadcn",
|
|
5
|
+
"items": [
|
|
6
|
+
{
|
|
7
|
+
"name": "pihanga-base",
|
|
8
|
+
"type": "registry:component"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"name": "pihanga-lib-utils",
|
|
12
|
+
"type": "registry:lib"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"name": "pihanga-cards-icons",
|
|
16
|
+
"type": "registry:component"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"name": "pihanga-cards-types",
|
|
20
|
+
"type": "registry:component"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"name": "pihanga-theme-provider",
|
|
24
|
+
"type": "registry:component"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"name": "pihanga-hook-use-is-touch-device",
|
|
28
|
+
"type": "registry:hook"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"name": "pihanga-ui-extras",
|
|
32
|
+
"type": "registry:ui"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"name": "badge",
|
|
36
|
+
"type": "registry:component"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"name": "box",
|
|
40
|
+
"type": "registry:component"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"name": "button",
|
|
44
|
+
"type": "registry:component"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"name": "checkbox",
|
|
48
|
+
"type": "registry:component"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"name": "conditional",
|
|
52
|
+
"type": "registry:component"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"name": "dataTable",
|
|
56
|
+
"type": "registry:component"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"name": "dialog",
|
|
60
|
+
"type": "registry:component"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"name": "dropDownMenu",
|
|
64
|
+
"type": "registry:component"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"name": "field",
|
|
68
|
+
"type": "registry:component"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"name": "flexGrid",
|
|
72
|
+
"type": "registry:component"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"name": "form",
|
|
76
|
+
"type": "registry:component"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"name": "framework",
|
|
80
|
+
"type": "registry:component"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"name": "graphin",
|
|
84
|
+
"type": "registry:component"
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"name": "input",
|
|
88
|
+
"type": "registry:component"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"name": "jsonViewer",
|
|
92
|
+
"type": "registry:component"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"name": "list",
|
|
96
|
+
"type": "registry:component"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"name": "loadingOverlay",
|
|
100
|
+
"type": "registry:component"
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"name": "loadingSkeleton",
|
|
104
|
+
"type": "registry:component"
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"name": "markdownViewer",
|
|
108
|
+
"type": "registry:component"
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"name": "menu",
|
|
112
|
+
"type": "registry:component"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"name": "modeToggle",
|
|
116
|
+
"type": "registry:component"
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"name": "navbarSearch",
|
|
120
|
+
"type": "registry:component"
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"name": "pageWithNavbar",
|
|
124
|
+
"type": "registry:component"
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"name": "pasteTarget",
|
|
128
|
+
"type": "registry:component"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"name": "resizable",
|
|
132
|
+
"type": "registry:component"
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"name": "select",
|
|
136
|
+
"type": "registry:component"
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"name": "stack",
|
|
140
|
+
"type": "registry:component"
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"name": "stepper",
|
|
144
|
+
"type": "registry:component"
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"name": "switch",
|
|
148
|
+
"type": "registry:component"
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"name": "tabs",
|
|
152
|
+
"type": "registry:component"
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
"name": "textField",
|
|
156
|
+
"type": "registry:component"
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"name": "toast",
|
|
160
|
+
"type": "registry:component"
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
"name": "toggleGroup",
|
|
164
|
+
"type": "registry:component"
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
"name": "typography",
|
|
168
|
+
"type": "registry:component"
|
|
169
|
+
}
|
|
170
|
+
]
|
|
171
|
+
}
|
package/r/resizable.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "resizable",
|
|
4
|
+
"type": "registry:component",
|
|
5
|
+
"description": "Pihanga resizable card component",
|
|
6
|
+
"dependencies": [
|
|
7
|
+
"clsx@^2.1.1",
|
|
8
|
+
"lucide-react@^0.513.0",
|
|
9
|
+
"react-resizable-panels@^2.0.0"
|
|
10
|
+
],
|
|
11
|
+
"registryDependencies": [
|
|
12
|
+
"https://ivcap-works.github.io/pihanga-shadcn/r/pihanga-base.json",
|
|
13
|
+
"resizable"
|
|
14
|
+
],
|
|
15
|
+
"files": [
|
|
16
|
+
{
|
|
17
|
+
"path": "cards/resizable/index.ts",
|
|
18
|
+
"content": "import {registerCardComponent} from \"@pihanga2/core\";\n\nimport {ResizableComponent} from \"./resizable.component\";\nimport {RESIZABLE_CARD} from \"./resizable.types\";\n\nexport * from \"./resizable.types\";\n\nregisterCardComponent({\n name: RESIZABLE_CARD,\n component: ResizableComponent,\n});\n",
|
|
19
|
+
"type": "registry:component",
|
|
20
|
+
"target": "src/cards/resizable/index.ts"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"path": "cards/resizable/resizable.component.tsx",
|
|
24
|
+
"content": "import React, {Fragment} from \"react\";\nimport {Card, type PiCardProps} from \"@pihanga2/core\";\nimport {clsx} from \"clsx\";\nimport {\n ResizableHandle,\n ResizablePanel,\n ResizablePanelGroup,\n} from \"@/components/ui/resizable\";\nimport {\n DefResizableHandle,\n type PiResizableHandle,\n type PiResizablePanel,\n type ResizableProps,\n} from \"./resizable.types\";\n\nexport const ResizableComponent = (\n props: PiCardProps<ResizableProps>,\n): React.ReactNode => {\n const {content, handles, direction, className, style, cardName} = props;\n\n // `style` may carry Shadcn-specific class overrides in a `shad` sub-key.\n const shadStyle = style as\n | {shad?: {root?: string; panel?: string; handle?: string}}\n | undefined;\n const sy = shadStyle?.shad ?? {};\n\n const _handles: PiResizableHandle[] = Array.isArray(handles)\n ? handles\n : Array(content.length - 1).fill(handles ?? DefResizableHandle);\n\n function renderPanel(el: PiResizablePanel, idx: number) {\n const name = el.name ?? `panel${idx}`;\n const panelProps: {\n defaultSize: number;\n collapsible?: boolean;\n maxSize?: number;\n minSize?: number;\n } = {\n defaultSize: el.defaultSize ?? 50,\n collapsible: el.collapsible,\n };\n if (el.maxSize !== undefined) panelProps.maxSize = el.maxSize;\n if (el.minSize !== undefined) panelProps.minSize = el.minSize;\n\n return (\n <Fragment key={idx}>\n {renderHandle(idx)}\n <ResizablePanel\n {...panelProps}\n className={clsx(sy.panel, className && `${className}-${name}`)}\n >\n <Card cardName={el.content} parentCard={cardName} />\n </ResizablePanel>\n </Fragment>\n );\n }\n\n function renderHandle(idx: number) {\n if (idx === 0) return null;\n const h = _handles[idx - 1];\n return <ResizableHandle {...h} className={clsx(sy.handle)} />;\n }\n\n return (\n <ResizablePanelGroup\n direction={direction ?? \"horizontal\"}\n className={clsx(className, sy.root)}\n data-pihanga={cardName}\n >\n {content.map(renderPanel)}\n </ResizablePanelGroup>\n );\n};\n",
|
|
25
|
+
"type": "registry:component",
|
|
26
|
+
"target": "src/cards/resizable/resizable.component.tsx"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"path": "cards/resizable/resizable.types.ts",
|
|
30
|
+
"content": "import {type PiCardRef, createCardDeclaration} from \"@pihanga2/core\";\n\nexport const RESIZABLE_CARD = \"shad/resizable\";\n\nexport const SdResizable =\n createCardDeclaration<ResizableProps>(RESIZABLE_CARD);\n\n/** Convenience alias — same as {@link SdResizable}. */\nexport const Resizable = SdResizable;\n\nexport type ResizableProps<S = unknown> = {\n /** Ordered list of panels to render inside the resizable group. */\n content: PiResizablePanel[];\n /** Layout direction of the panel group. Defaults to `\"horizontal\"`. */\n direction?: \"horizontal\" | \"vertical\";\n /**\n * Handle configuration(s) between panels.\n *\n * - A single `PiResizableHandle` is used for every handle in the group.\n * - An array must have exactly `content.length - 1` entries.\n *\n * Defaults to {@link DefResizableHandle} (visible drag handle).\n */\n handles?: PiResizableHandle | PiResizableHandle[];\n className?: string;\n /**\n * Optional Shadcn-style style overrides.\n *\n * When provided as `{ shad?: { root?: string; panel?: string; handle?: string } }`,\n * the nested class names are applied to the corresponding elements in\n * addition to any `className` already set.\n */\n style?: S;\n};\n\nexport type PiResizablePanel = {\n /** Used for CSS class suffixes and stable ordering. Defaults to `\"panel{idx}\"`. */\n name?: string;\n /** The card to render inside this panel. */\n content: PiCardRef;\n /** Initial size as a percentage of the group. Defaults to `50`. */\n defaultSize?: number;\n minSize?: number;\n maxSize?: number;\n /** When `true`, the panel can be collapsed to zero size. */\n collapsible?: boolean;\n};\n\nexport type PiResizableHandle = {\n /** When `true`, renders a visible drag-handle grip indicator. */\n withHandle?: boolean;\n /** When `true`, the handle is decorative only (not draggable). */\n disabled?: boolean;\n};\n\n/** Default handle configuration: visible grip, draggable. */\nexport const DefResizableHandle: PiResizableHandle = {\n withHandle: true,\n disabled: false,\n};\n",
|
|
31
|
+
"type": "registry:component",
|
|
32
|
+
"target": "src/cards/resizable/resizable.types.ts"
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
}
|
package/r/select.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "select",
|
|
4
|
+
"type": "registry:component",
|
|
5
|
+
"description": "Pihanga select card component",
|
|
6
|
+
"dependencies": [
|
|
7
|
+
"@radix-ui/react-select@^2.2.6",
|
|
8
|
+
"lucide-react@^0.513.0"
|
|
9
|
+
],
|
|
10
|
+
"registryDependencies": [
|
|
11
|
+
"https://ivcap-works.github.io/pihanga-shadcn/r/pihanga-base.json",
|
|
12
|
+
"select",
|
|
13
|
+
"https://ivcap-works.github.io/pihanga-shadcn/r/form.json"
|
|
14
|
+
],
|
|
15
|
+
"files": [
|
|
16
|
+
{
|
|
17
|
+
"path": "cards/select/index.ts",
|
|
18
|
+
"content": "import {actionTypesToEvents, registerCardComponent} from \"@pihanga2/core\";\n\nimport {SelectComponent} from \"./select.component\";\nimport {PI_SELECT_ACTION, PI_SELECT_CARD} from \"./select.types\";\n\nexport * from \"./select.types\";\n\nregisterCardComponent({\n name: PI_SELECT_CARD,\n component: SelectComponent,\n events: actionTypesToEvents(PI_SELECT_ACTION),\n});\n",
|
|
19
|
+
"type": "registry:component",
|
|
20
|
+
"target": "src/cards/select/index.ts"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"path": "cards/select/select.component.tsx",
|
|
24
|
+
"content": "import React, {useState} from \"react\";\nimport {type PiCardProps} from \"@pihanga2/core\";\nimport {\n Select as RadixSelect,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/components/ui/select\";\nimport {useFormContext} from \"@/cards/form/form.context\";\nimport type {PiSelectEvents, PiSelectProps} from \"./select.types\";\n\nexport const SelectComponent = (\n props: PiCardProps<PiSelectProps, PiSelectEvents>,\n): React.ReactNode => {\n const {\n name,\n value: propValue = \"\",\n defaultValue,\n selfManaged = false,\n options,\n placeholder,\n required,\n disabled,\n ariaLabel,\n cardName,\n onChanged,\n onOpened,\n onClosed,\n } = props;\n\n // `id` and `invalid` may be injected by a parent pi/field card via\n // Pihanga's extra-prop forwarding. They are not in the official type so\n // we read them through an escape hatch.\n const injectedId = (props as {id?: string}).id;\n const invalid = Boolean((props as {invalid?: boolean}).invalid);\n\n // Detect if we are inside a pi/form card via React context.\n const form = useFormContext();\n const useFormData = form.isInForm && Boolean(name);\n\n // Internal state used only when selfManaged=true and not bound to a form.\n const [managedValue, setManagedValue] = useState<string>(\n defaultValue ?? propValue,\n );\n\n const value = useFormData\n ? ((form.formData[name!] as string | undefined) ?? \"\")\n : selfManaged\n ? managedValue\n : propValue;\n\n function handleChange(newValue: string) {\n if (useFormData) {\n form.handleChange(name!, newValue);\n } else {\n if (selfManaged) {\n setManagedValue(newValue);\n }\n onChanged({name, value: newValue});\n }\n }\n\n function handleOpenChange(open: boolean) {\n if (open) {\n onOpened({});\n } else {\n onClosed({});\n }\n }\n\n // Use the id injected by pi/field; fall back to a local id when standalone.\n const selfId = `${cardName}-${name ?? \"select\"}`;\n const fieldId = injectedId ?? selfId;\n\n return (\n <RadixSelect\n value={value}\n defaultValue={defaultValue}\n onValueChange={handleChange}\n onOpenChange={handleOpenChange}\n disabled={disabled}\n required={required}\n aria-label={ariaLabel}\n >\n <SelectTrigger\n id={fieldId}\n aria-invalid={invalid || undefined}\n data-pihanga={cardName}\n >\n <SelectValue placeholder={placeholder ?? \"Select…\"} />\n </SelectTrigger>\n <SelectContent>\n <SelectGroup>\n {options.map((opt) => (\n <SelectItem\n key={opt.value}\n value={opt.value}\n disabled={opt.disabled}\n >\n {opt.label}\n </SelectItem>\n ))}\n </SelectGroup>\n </SelectContent>\n </RadixSelect>\n );\n};\n",
|
|
25
|
+
"type": "registry:component",
|
|
26
|
+
"target": "src/cards/select/select.component.tsx"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"path": "cards/select/select.types.ts",
|
|
30
|
+
"content": "import {\n createCardDeclaration,\n createOnAction,\n registerActions,\n} from \"@pihanga2/core\";\n\nexport const PI_SELECT_CARD = \"pi/select\";\n\nexport const Select = createCardDeclaration<PiSelectProps, PiSelectEvents>(\n PI_SELECT_CARD,\n);\n\nexport const PI_SELECT_ACTION = registerActions(PI_SELECT_CARD, [\n \"changed\",\n \"opened\",\n \"closed\",\n]);\n\nexport const onPiSelectChanged = createOnAction<PiSelectChangedEvent>(\n PI_SELECT_ACTION.CHANGED,\n);\n\nexport const onPiSelectOpened = createOnAction<PiSelectOpenedEvent>(\n PI_SELECT_ACTION.OPENED,\n);\n\nexport const onPiSelectClosed = createOnAction<PiSelectClosedEvent>(\n PI_SELECT_ACTION.CLOSED,\n);\n\nexport type PiSelectOption = {\n /** The value stored in form state / dispatched in events. */\n value: string;\n /** Human-readable label shown in the dropdown. */\n label: string;\n /** When true, the option is shown but cannot be selected. */\n disabled?: boolean;\n};\n\nexport type PiSelectProps = {\n /**\n * Field name used to bind to FormContext when inside a pi/form card.\n * When provided the component reads its selected value from form data and\n * writes back via form.handleChange.\n */\n name?: string;\n\n /**\n * Controlled value used in standalone mode (outside a Form).\n * Ignored when `name` is set and the component is inside a pi/form.\n */\n value?: string;\n\n /**\n * When true the component manages its own selected-value state internally.\n * Selecting an item will still dispatch the normal `onChanged` action, but\n * the UI will immediately reflect the new selection without waiting for the\n * host application to update `value` via a reducer.\n *\n * Has no effect when the component is bound to a pi/form via `name`.\n */\n selfManaged?: boolean;\n\n /**\n * Default (uncontrolled) value. Mirrors Radix `defaultValue`.\n */\n defaultValue?: string;\n\n /** Available options to display in the dropdown. */\n options: PiSelectOption[];\n\n /** Placeholder text shown when no option is selected. */\n placeholder?: string;\n\n /** When true, the select is required (HTML form attribute). */\n required?: boolean;\n\n /** When true, the select is disabled and non-interactive. */\n disabled?: boolean;\n\n /** Accessible label for screen readers. */\n ariaLabel?: string;\n};\n\nexport type PiSelectChangedEvent = {\n /** Field name, mirrors the `name` prop if provided. */\n name?: string;\n /** The value of the newly selected option. */\n value: string;\n};\n\nexport type PiSelectOpenedEvent = Record<string, never>;\n\nexport type PiSelectClosedEvent = Record<string, never>;\n\nexport type PiSelectEvents = {\n onChanged: PiSelectChangedEvent;\n onOpened: PiSelectOpenedEvent;\n onClosed: PiSelectClosedEvent;\n};\n",
|
|
31
|
+
"type": "registry:component",
|
|
32
|
+
"target": "src/cards/select/select.types.ts"
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
}
|
package/r/stack.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "stack",
|
|
4
|
+
"type": "registry:component",
|
|
5
|
+
"description": "Pihanga stack card component",
|
|
6
|
+
"dependencies": [
|
|
7
|
+
"@pihanga2/cards@^0.3.15",
|
|
8
|
+
"clsx@^2.1.1"
|
|
9
|
+
],
|
|
10
|
+
"registryDependencies": [
|
|
11
|
+
"https://ivcap-works.github.io/pihanga-shadcn/r/pihanga-base.json"
|
|
12
|
+
],
|
|
13
|
+
"files": [
|
|
14
|
+
{
|
|
15
|
+
"path": "cards/stack/index.ts",
|
|
16
|
+
"content": "import {createCardDeclaration, registerCardComponent} from \"@pihanga2/core\";\nimport type {StackProps} from \"@pihanga2/cards\";\n\nimport {Component} from \"./stack.component\";\n\nexport const CARD_TYPE = \"shad/stack\";\nexport const Stack = createCardDeclaration<StackProps>(CARD_TYPE);\n\nregisterCardComponent({\n name: CARD_TYPE,\n component: Component,\n});\n",
|
|
17
|
+
"type": "registry:component",
|
|
18
|
+
"target": "src/cards/stack/index.ts"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"path": "cards/stack/stack.component.tsx",
|
|
22
|
+
"content": "import * as React from \"react\";\nimport type {StackProps} from \"@pihanga2/cards\";\n\nimport {Card, type PiCardProps} from \"@pihanga2/core\";\nimport clsx from \"clsx\";\nimport {Fragment} from \"react\";\n\nexport type SelectSD = {\n root?: string;\n};\n\nexport const Component = (props: PiCardProps<StackProps>): React.ReactNode => {\n const {\n content,\n direction,\n divider,\n spacing,\n justifyContent,\n alignItems,\n className,\n style,\n cardName,\n } = props;\n const sd: SelectSD = style?.shad || {};\n\n const cn = [className, sd.root, \"flex\"];\n if (direction) {\n switch (direction) {\n // \"column-reverse\" | \"column\" | \"row-reverse\" | \"row\"\n case \"row\":\n cn.push(\"flex-row\");\n break;\n case \"row-reverse\":\n cn.push(\"flex-row-reverse\");\n break;\n case \"column\":\n cn.push(\"flex-col\");\n break;\n case \"column-reverse\":\n cn.push(\"flex-col-reverse\");\n break;\n }\n }\n if (justifyContent) {\n switch (justifyContent) {\n // \"flex-start\" | \"center\" | \"flex-end\" | \"space-between\" | \"space-around\" | \"space-evenly\"\n case \"flex-start\":\n cn.push(\"justify-start\");\n break;\n case \"center\":\n cn.push(\"justify-center\");\n break;\n case \"flex-end\":\n cn.push(\"justify-end\");\n break;\n case \"space-between\":\n cn.push(\"justify-between\");\n break;\n case \"space-around\":\n cn.push(\"justify-around\");\n break;\n case \"space-evenly\":\n cn.push(\"justify-evenly\");\n break;\n }\n }\n if (alignItems) {\n switch (alignItems) {\n // \"flex-start\" | \"center\" | \"flex-end\" | \"normal\" | \"stretch\" | \"start\" | \"end\" | \"baseline\" | \"initial\" | \"inherit\"\n case \"flex-start\":\n cn.push(\"items-start\");\n break;\n case \"center\":\n cn.push(\"items-center\");\n break;\n case \"stretch\":\n cn.push(\"items-stretch\");\n break;\n case \"flex-end\":\n cn.push(\"items-end\");\n break;\n case \"baseline\":\n cn.push(\"items-baseline\");\n break;\n default:\n console.warn(\"stack: unsupported 'alignItems' property\", alignItems);\n }\n }\n if (spacing && spacing > 0) {\n if (\n direction === undefined ||\n direction === \"row\" ||\n direction === \"row-reverse\"\n ) {\n cn.push(`gap-${spacing}`);\n } else {\n cn.push(`gap-${spacing}`);\n }\n }\n\n // const sx = style?.joy\n // const p = {\n // direction,\n // spacing,\n // justifyContent,\n // alignItems,\n // divider: divider ? <Card cardName={divider} parentCard={cardName} /> : null,\n // sx,\n // className,\n // }\n\n function renderContent() {\n if (!content) return null;\n\n return content.map((cn, i) => (\n <Fragment key={i}>\n <Card cardName={cn} parentCard={cardName} key={`item-${i}`} />\n {divider && i < content.length - 1 && (\n <Card cardName={divider} parentCard={cardName} key={`divider-${i}`} />\n )}\n </Fragment>\n ));\n }\n\n return (\n <div className={clsx(cn)} data-pihanga={cardName}>\n {renderContent()}\n {props.children}\n </div>\n );\n};\n",
|
|
23
|
+
"type": "registry:component",
|
|
24
|
+
"target": "src/cards/stack/stack.component.tsx"
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
package/r/stepper.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "stepper",
|
|
4
|
+
"type": "registry:component",
|
|
5
|
+
"description": "Pihanga stepper card component",
|
|
6
|
+
"dependencies": [
|
|
7
|
+
"lucide-react@^0.513.0"
|
|
8
|
+
],
|
|
9
|
+
"registryDependencies": [
|
|
10
|
+
"https://ivcap-works.github.io/pihanga-shadcn/r/pihanga-base.json",
|
|
11
|
+
"https://ivcap-works.github.io/pihanga-shadcn/r/pihanga-ui-extras.json"
|
|
12
|
+
],
|
|
13
|
+
"files": [
|
|
14
|
+
{
|
|
15
|
+
"path": "cards/stepper/index.ts",
|
|
16
|
+
"content": "import {registerCardComponent} from \"@pihanga2/core\";\n\nimport {StepperComponent} from \"./stepper.component\";\nimport {STEPPER_ACTION, STEPPER_CARD} from \"./stepper.types\";\n\nexport * from \"./stepper.types\";\n\nregisterCardComponent({\n name: STEPPER_CARD,\n component: StepperComponent,\n // Explicit mapping avoids CamelCase issues: STEPCLICKED → onStepClicked\n // (actionTypesToEvents would produce onStepclicked with lowercase 'c').\n events: {\n onStepClicked: STEPPER_ACTION.STEPCLICKED,\n },\n});\n",
|
|
17
|
+
"type": "registry:component",
|
|
18
|
+
"target": "src/cards/stepper/index.ts"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"path": "cards/stepper/stepper.component.tsx",
|
|
22
|
+
"content": "import React, {useState} from \"react\";\nimport {Card, type PiCardProps} from \"@pihanga2/core\";\nimport {StepperUI, type StepData} from \"@/components/ui/stepper\";\nimport type {StepperEvents, StepperProps} from \"./stepper.types\";\n// Default visual styles — import before component renders so BEM classes are styled.\n// Override by targeting the same class names in a stylesheet loaded after this one.\nimport \"./stepper.css\";\n\nexport const StepperComponent = (\n props: PiCardProps<StepperProps, StepperEvents>,\n): React.ReactNode => {\n const {\n steps,\n activeStep: propActiveStep,\n defaultActiveStep = 0,\n selfManaged = false,\n orientation = \"horizontal\",\n size = \"md\",\n showContent = false,\n className,\n cardName,\n onStepClicked,\n } = props;\n\n // Internal state used only when selfManaged=true.\n // Initialised from propActiveStep (if given) or defaultActiveStep.\n const [managedStep, setManagedStep] = useState<number>(\n propActiveStep ?? defaultActiveStep,\n );\n\n // Resolve the effective active step:\n // - selfManaged → use internal state\n // - controlled → use the prop (fall back to 0 if not set)\n const activeStep = selfManaged ? managedStep : (propActiveStep ?? 0);\n\n // Map StepItem[] → StepData[] (UI primitive only needs serialisable fields\n // plus an optional pre-rendered React node for custom labels).\n const stepData: StepData[] = steps.map((step) => ({\n id: step.id,\n title: step.title,\n description: step.description,\n optional: step.optional,\n // If a labelCard is specified, render it here and pass as `labelNode`.\n // The UI primitive will display it instead of the default title/description.\n labelNode: step.labelCard ? (\n <Card cardName={step.labelCard} parentCard={cardName} />\n ) : undefined,\n }));\n\n function handleStepClick(stepIndex: number, stepId: string) {\n if (selfManaged) {\n setManagedStep(stepIndex);\n }\n // Dispatch Pihanga event when the callback is wired (it may be absent for\n // inline / anonymous card declarations that have no registered reducer).\n if (typeof onStepClicked === \"function\") {\n onStepClicked({stepIndex, stepId});\n }\n }\n\n /**\n * Render a card ref for the active step's content (vertical mode only).\n * We look up the original `steps` array to get the `contentCard` reference.\n */\n function renderStepContent(stepIndex: number): React.ReactNode {\n if (!showContent || orientation !== \"vertical\") return null;\n const step = steps[stepIndex];\n if (!step?.contentCard) return null;\n return <Card cardName={step.contentCard} parentCard={cardName} />;\n }\n\n return (\n <div data-pihanga={cardName}>\n <StepperUI\n steps={stepData}\n activeStep={activeStep}\n orientation={orientation}\n size={size}\n className={className}\n onStepClick={handleStepClick}\n renderStepContent={renderStepContent}\n />\n </div>\n );\n};\n",
|
|
23
|
+
"type": "registry:component",
|
|
24
|
+
"target": "src/cards/stepper/stepper.component.tsx"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"path": "cards/stepper/stepper.css",
|
|
28
|
+
"content": "/* ============================================================\n pi-stepper — default visual styles\n src/cards/shadcn/stepper/stepper.css\n\n All visual properties live here so consumers can override\n individual rules by targeting the same BEM class names in\n a stylesheet loaded after this one (or by replacing this\n file entirely).\n\n Structural / layout Tailwind classes stay in the JSX and\n are not repeated here.\n\n Color tokens: the project uses Tailwind v4 with oklch values.\n Variables like --primary, --border etc. are full oklch() color\n values — use them directly with var(--primary), NOT with\n hsl(var(--primary)) which would be invalid in this setup.\n ============================================================ */\n\n/* ── CSS size variables (override per stepper or globally) ─── */\n.pi-stepper {\n --pi-stepper-indicator-size-sm: 1.5rem;\n /* 24 px */\n --pi-stepper-indicator-size-md: 2rem;\n /* 32 px */\n --pi-stepper-indicator-size-lg: 2.5rem;\n /* 40 px */\n --pi-stepper-indicator-size: var(--pi-stepper-indicator-size-md);\n --pi-stepper-connector-thickness: 2px;\n --pi-stepper-connector-min-height: 1.5rem;\n /* vertical mode */\n --pi-stepper-label-max-width: 6rem;\n /* horizontal mode */\n}\n\n/* ── Size modifiers ─────────────────────────────────────────── */\n.pi-stepper--sm {\n --pi-stepper-indicator-size: var(--pi-stepper-indicator-size-sm);\n}\n\n.pi-stepper--md {\n --pi-stepper-indicator-size: var(--pi-stepper-indicator-size-md);\n}\n\n.pi-stepper--lg {\n --pi-stepper-indicator-size: var(--pi-stepper-indicator-size-lg);\n}\n\n/* ──────────────────────────────────────────────────────────────\n Indicator (step icon / circle)\n\n Tailwind classes in stepper.tsx provide WORKING defaults:\n bg-primary / text-primary-foreground / ring-* / border-*\n Override them here by raising specificity one level (e.g. add\n the parent class) or by using !important if needed.\n ────────────────────────────────────────────────────────────── */\n\n/* Base indicator — transitions only (no colors; Tailwind handles defaults) */\n.pi-stepper__indicator {\n transition:\n background-color 200ms ease,\n border-color 200ms ease,\n color 200ms ease,\n outline-color 200ms ease;\n}\n\n/* To override the active indicator colour, use e.g.:\n .pi-stepper__indicator--active { background-color: var(--destructive); }\n The var() tokens are direct oklch values in this project. */\n\n.pi-stepper__indicator--clickable {\n cursor: pointer;\n}\n\n.pi-stepper__indicator--clickable:hover {\n opacity: 0.8;\n}\n\n.pi-stepper__indicator--static {\n cursor: default;\n}\n\n/* ── Connector (line between steps) ─────────────────────────── */\n.pi-stepper__connector {\n transition: background-color 300ms ease;\n}\n\n/* Vertical connector segment */\n.pi-stepper__connector--vertical {\n min-height: var(--pi-stepper-connector-min-height);\n}\n\n/* ── Label title ─────────────────────────────────────────────── */\n.pi-stepper__label-title {\n transition: color 200ms ease;\n}\n\n/* ── Vertical step content slot ──────────────────────────────── */\n.pi-stepper__step-content {\n /* no special styles needed — spacing set via Tailwind in JSX */\n}",
|
|
29
|
+
"type": "registry:component",
|
|
30
|
+
"target": "src/cards/stepper/stepper.css"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"path": "cards/stepper/stepper.types.ts",
|
|
34
|
+
"content": "import {\n createCardDeclaration,\n createOnAction,\n type PiCardRef,\n registerActions,\n} from \"@pihanga2/core\";\n\nexport const STEPPER_CARD = \"shad/stepper\";\n\nexport const Stepper = createCardDeclaration<StepperProps, StepperEvents>(\n STEPPER_CARD,\n);\n\nexport const STEPPER_ACTION = registerActions(STEPPER_CARD, [\"stepClicked\"]);\n\nexport const onStepperStepClicked = createOnAction<StepperStepClickedEvent>(\n STEPPER_ACTION.STEPCLICKED,\n);\n\n// ─── Step definition ─────────────────────────────────────────────────────────\n\n/**\n * A single step in the stepper.\n */\nexport type StepItem = {\n /** Unique identifier used in events. */\n id: string;\n\n /** Short label shown below/beside the step indicator. */\n title: string;\n\n /** Optional secondary text shown under the title. */\n description?: string;\n\n /**\n * If true, an \"(Optional)\" suffix is appended to the title.\n * Mirrors the MUI Joy UI `optional` prop.\n */\n optional?: boolean;\n\n /**\n * Optional Pihanga card rendered **instead of** the default title/description\n * text label. Useful for rich labels (icons, custom markup, etc.).\n * The card receives no extra props — use `memo` in its own declaration if\n * it needs to read from state.\n */\n labelCard?: PiCardRef;\n\n /**\n * A card ref rendered as the body of this step when it is active\n * and the stepper is in `\"vertical\"` orientation with `showContent: true`.\n */\n contentCard?: PiCardRef;\n};\n\n// ─── Card props ───────────────────────────────────────────────────────────────\n\nexport type StepperProps = {\n /** Ordered list of step definitions. */\n steps: StepItem[];\n\n /**\n * 0-based index of the currently active step (controlled mode).\n * Steps before this index are rendered as \"completed\";\n * steps after are \"upcoming\".\n *\n * Required in controlled mode (`selfManaged: false`).\n * Used as the initial value in self-managed mode when provided\n * (otherwise defaults to `defaultActiveStep ?? 0`).\n */\n activeStep?: number;\n\n /**\n * Initial active step used only in self-managed mode (`selfManaged: true`).\n * Ignored in controlled mode.\n * @default 0\n */\n defaultActiveStep?: number;\n\n /**\n * When `true`, the component manages its own `activeStep` state internally.\n * Clicking a step immediately advances the indicator without waiting for the\n * host application to update the `activeStep` prop via a reducer.\n * The `onStepClicked` event is still dispatched on each click.\n *\n * When `false` (default), `activeStep` fully controls the display.\n * @default false\n */\n selfManaged?: boolean;\n\n /**\n * Layout orientation.\n * - `\"horizontal\"` (default): steps flow left-to-right with labels below.\n * - `\"vertical\"`: steps stack top-to-bottom with labels/content to the right.\n */\n orientation?: \"horizontal\" | \"vertical\";\n\n /**\n * Visual size of the step indicators.\n * @default \"md\"\n */\n size?: \"sm\" | \"md\" | \"lg\";\n\n /**\n * When `true` and `orientation === \"vertical\"`, the active step's\n * `contentCard` is rendered inline beneath its label.\n * @default false\n */\n showContent?: boolean;\n\n /** Additional Tailwind classes applied to the root element. */\n className?: string;\n};\n\n// ─── Events ───────────────────────────────────────────────────────────────────\n\nexport type StepperStepClickedEvent = {\n /** 0-based index of the clicked step. */\n stepIndex: number;\n /** The `id` of the clicked step definition. */\n stepId: string;\n};\n\nexport type StepperEvents = {\n /** Fired when the user clicks on a step indicator. */\n onStepClicked: StepperStepClickedEvent;\n};\n",
|
|
35
|
+
"type": "registry:component",
|
|
36
|
+
"target": "src/cards/stepper/stepper.types.ts"
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|