@nikala-ui/core 0.9.11 → 0.10.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/package.json +1 -1
- package/registry/aspect-ratio.json +17 -0
- package/registry/button.json +4 -1
- package/registry/collapsible.json +18 -0
- package/registry/combobox.json +4 -1
- package/registry/command.json +3 -2
- package/registry/context-menu.json +24 -0
- package/registry/create-form.json +1 -1
- package/registry/dialog.json +4 -1
- package/registry/dropdown-menu.json +4 -1
- package/registry/empty.json +17 -0
- package/registry/field.json +20 -0
- package/registry/form-message.json +16 -0
- package/registry/form.json +17 -0
- package/registry/icon-button.json +20 -0
- package/registry/index.json +191 -1
- package/registry/number-input.json +24 -0
- package/registry/resizable.json +21 -0
- package/registry/scroll-area.json +21 -0
- package/registry/select.json +4 -1
- package/registry/sheet.json +4 -1
- package/registry/spinner.json +18 -0
- package/registry/status.json +18 -0
- package/registry/theme-manager.json +1 -1
- package/registry/toggle.json +18 -0
- package/src/registry/components/ui/aspect-ratio.tsx +31 -0
- package/src/registry/components/ui/button.tsx +18 -3
- package/src/registry/components/ui/collapsible.tsx +73 -0
- package/src/registry/components/ui/combobox.tsx +23 -84
- package/src/registry/components/ui/command.tsx +154 -66
- package/src/registry/components/ui/context-menu.tsx +192 -0
- package/src/registry/components/ui/dialog.tsx +39 -44
- package/src/registry/components/ui/dropdown-menu.tsx +40 -45
- package/src/registry/components/ui/empty.tsx +83 -0
- package/src/registry/components/ui/field.tsx +67 -0
- package/src/registry/components/ui/form-message.tsx +36 -0
- package/src/registry/components/ui/form.tsx +21 -0
- package/src/registry/components/ui/icon-button.tsx +34 -0
- package/src/registry/components/ui/number-input.tsx +150 -0
- package/src/registry/components/ui/resizable.tsx +193 -0
- package/src/registry/components/ui/scroll-area.tsx +218 -0
- package/src/registry/components/ui/select.tsx +22 -16
- package/src/registry/components/ui/sheet.tsx +62 -63
- package/src/registry/components/ui/spinner.tsx +41 -0
- package/src/registry/components/ui/status.tsx +119 -0
- package/src/registry/components/ui/theme-toggle.tsx +6 -4
- package/src/registry/components/ui/toggle.tsx +101 -0
- package/src/registry/metadata.ts +84 -2
package/package.json
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "aspect-ratio",
|
|
3
|
+
"title": "Aspect Ratio",
|
|
4
|
+
"description": "Displays content within a specific aspect ratio using CSS aspect-ratio while preventing layout shifts.",
|
|
5
|
+
"type": "registry:ui",
|
|
6
|
+
"dependencies": [
|
|
7
|
+
"clsx",
|
|
8
|
+
"tailwind-merge"
|
|
9
|
+
],
|
|
10
|
+
"files": [
|
|
11
|
+
{
|
|
12
|
+
"path": "ui/aspect-ratio.tsx",
|
|
13
|
+
"content": "import { splitProps, type Component, type JSX } from \"solid-js\";\nimport { cn } from \"@/lib/cn\";\n\nexport interface AspectRatioProps extends JSX.HTMLAttributes<HTMLDivElement> {\n ratio?: number;\n class?: string;\n children?: JSX.Element;\n}\n\n/**\n * Nikala UI AspectRatio Component.\n * Displays content within a specific aspect ratio (e.g. 16/9, 4/3, 1/1) using CSS aspect-ratio.\n */\nexport const AspectRatio: Component<AspectRatioProps> = (props) => {\n const [local, rest] = splitProps(props, [\"ratio\", \"class\", \"children\", \"style\"]);\n\n const computedRatio = () => (local.ratio !== undefined ? local.ratio : 16 / 9);\n\n return (\n <div\n class={cn(\"relative w-full overflow-hidden\", local.class)}\n style={{\n \"aspect-ratio\": `${computedRatio()}`,\n ...(typeof local.style === \"object\" ? local.style : {}),\n }}\n {...rest}\n >\n {local.children}\n </div>\n );\n};\n",
|
|
14
|
+
"type": "registry:ui"
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|
package/registry/button.json
CHANGED
|
@@ -8,10 +8,13 @@
|
|
|
8
8
|
"tailwind-merge",
|
|
9
9
|
"class-variance-authority"
|
|
10
10
|
],
|
|
11
|
+
"registryDependencies": [
|
|
12
|
+
"spinner"
|
|
13
|
+
],
|
|
11
14
|
"files": [
|
|
12
15
|
{
|
|
13
16
|
"path": "ui/button.tsx",
|
|
14
|
-
"content": "import { splitProps, type Component, type JSX } from \"solid-js\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"@/lib/cn\";\n\n/**\n * Class variance authority configuration for button styling variants and sizes.\n */\nexport const buttonVariants = cva(\n \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 cursor-pointer\",\n {\n variants: {\n variant: {\n default:\n \"bg-primary text-primary-foreground shadow hover:bg-primary/90 border border-primary/50\",\n destructive:\n \"bg-red-600 text-zinc-50 shadow-sm hover:bg-red-600/90 dark:bg-red-900 dark:text-zinc-50 dark:hover:bg-red-900/90\",\n outline:\n \"border border-input bg-background hover:bg-accent hover:text-accent-foreground\",\n secondary:\n \"bg-secondary text-secondary-foreground hover:bg-secondary/80 border border-border\",\n ghost:\n \"hover:bg-accent hover:text-accent-foreground\",\n link: \"text-primary underline-offset-4 hover:underline\",\n success: \"*:bg-green-600 text-zinc-50 shadow-sm hover:bg-green-600/90 dark:bg-green-900 dark:text-zinc-50 dark:hover:bg-green-900/90\",\n warning: \"*:bg-yellow-600 text-zinc-50 shadow-sm hover:bg-yellow-600/90 dark:bg-yellow-900 dark:text-zinc-50 dark:hover:bg-yellow-900/90\",\n info: \"*:bg-blue-600 text-zinc-50 shadow-sm hover:bg-blue-600/90 dark:bg-blue-900 dark:text-zinc-50 dark:hover:bg-blue-900/90\",\n },\n size: {\n xs: \"h-6 rounded-md px-2 text-xs\",\n default: \"h-9 px-4 py-2\",\n sm: \"h-8 rounded-md px-3 text-xs\",\n lg: \"h-10 rounded-md px-8\",\n icon: \"h-9 w-9\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n }\n);\n\n/**\n * Props interface for the Button component extending standard HTML button attributes.\n */\nexport interface ButtonProps\n extends JSX.ButtonHTMLAttributes<HTMLButtonElement>,\n VariantProps<typeof buttonVariants> {\n class?: string;\n}\n\n/**\n * Nikala UI Button component built for SolidJS with Tailwind CSS v4 styling.\n */\nexport const Button: Component<ButtonProps> = (props) => {\n // Use splitProps to preserve SolidJS reactivity for destructured props\n const [local, rest] = splitProps(props, [\"variant\"
|
|
17
|
+
"content": "import { Show, splitProps, type Component, type JSX } from \"solid-js\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"@/lib/cn\";\nimport { Spinner } from \"./spinner\";\n\n/**\n * Class variance authority configuration for button styling variants and sizes.\n */\nexport const buttonVariants = cva(\n \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 cursor-pointer\",\n {\n variants: {\n variant: {\n default:\n \"bg-primary text-primary-foreground shadow hover:bg-primary/90 border border-primary/50\",\n destructive:\n \"bg-red-600 text-zinc-50 shadow-sm hover:bg-red-600/90 dark:bg-red-900 dark:text-zinc-50 dark:hover:bg-red-900/90\",\n outline:\n \"border border-input bg-background hover:bg-accent hover:text-accent-foreground\",\n secondary:\n \"bg-secondary text-secondary-foreground hover:bg-secondary/80 border border-border\",\n ghost:\n \"hover:bg-accent hover:text-accent-foreground\",\n link: \"text-primary underline-offset-4 hover:underline\",\n success: \"*:bg-green-600 text-zinc-50 shadow-sm hover:bg-green-600/90 dark:bg-green-900 dark:text-zinc-50 dark:hover:bg-green-900/90\",\n warning: \"*:bg-yellow-600 text-zinc-50 shadow-sm hover:bg-yellow-600/90 dark:bg-yellow-900 dark:text-zinc-50 dark:hover:bg-yellow-900/90\",\n info: \"*:bg-blue-600 text-zinc-50 shadow-sm hover:bg-blue-600/90 dark:bg-blue-900 dark:text-zinc-50 dark:hover:bg-blue-900/90\",\n },\n size: {\n xs: \"h-6 rounded-md px-2 text-xs\",\n default: \"h-9 px-4 py-2\",\n sm: \"h-8 rounded-md px-3 text-xs\",\n lg: \"h-10 rounded-md px-8\",\n icon: \"h-9 w-9\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n }\n);\n\n/**\n * Props interface for the Button component extending standard HTML button attributes.\n */\nexport interface ButtonProps\n extends JSX.ButtonHTMLAttributes<HTMLButtonElement>,\n VariantProps<typeof buttonVariants> {\n /** Shows a loading spinner and prevents repeated clicks while active. */\n loading?: boolean;\n class?: string;\n}\n\n/**\n * Nikala UI Button component built for SolidJS with Tailwind CSS v4 styling.\n */\nexport const Button: Component<ButtonProps> = (props) => {\n // Use splitProps to preserve SolidJS reactivity for destructured props\n const [local, rest] = splitProps(props, [\n \"variant\",\n \"size\",\n \"class\",\n \"children\",\n \"loading\",\n \"disabled\",\n ]);\n\n return (\n <button\n class={cn(buttonVariants({ variant: local.variant, size: local.size }), local.class)}\n disabled={local.disabled || local.loading}\n aria-busy={local.loading ? \"true\" : undefined}\n {...rest}\n >\n <Show when={local.loading}>\n <Spinner size=\"sm\" class=\"text-current\" />\n </Show>\n {local.children}\n </button>\n );\n};\n",
|
|
15
18
|
"type": "registry:ui"
|
|
16
19
|
}
|
|
17
20
|
]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "collapsible",
|
|
3
|
+
"title": "Collapsible",
|
|
4
|
+
"description": "An interactive component that expands and collapses content panels with smooth height animations, built on Kobalte primitives.",
|
|
5
|
+
"type": "registry:ui",
|
|
6
|
+
"dependencies": [
|
|
7
|
+
"clsx",
|
|
8
|
+
"tailwind-merge",
|
|
9
|
+
"@kobalte/core"
|
|
10
|
+
],
|
|
11
|
+
"files": [
|
|
12
|
+
{
|
|
13
|
+
"path": "ui/collapsible.tsx",
|
|
14
|
+
"content": "import { splitProps, type JSX, type ValidComponent } from \"solid-js\";\nimport * as CollapsiblePrimitive from \"@kobalte/core/collapsible\";\nimport type { PolymorphicProps } from \"@kobalte/core/polymorphic\";\nimport { cn } from \"@/lib/cn\";\n\nexport type CollapsibleRootProps<T extends ValidComponent = \"div\"> =\n CollapsiblePrimitive.CollapsibleRootProps<T> & {\n class?: string;\n };\n\n/**\n * Root Collapsible component built on Kobalte primitives.\n */\nexport const Collapsible = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, CollapsibleRootProps<T>>\n) => {\n const [local, rest] = splitProps(props as CollapsibleRootProps, [\"class\"]);\n return (\n <CollapsiblePrimitive.Root\n class={cn(\"w-full\", local.class)}\n {...(rest as any)}\n />\n );\n};\n\nexport type CollapsibleTriggerProps<T extends ValidComponent = \"button\"> =\n CollapsiblePrimitive.CollapsibleTriggerProps<T> & {\n class?: string;\n children?: JSX.Element;\n };\n\n/**\n * Trigger element that toggles the Collapsible open/closed state.\n */\nexport const CollapsibleTrigger = <T extends ValidComponent = \"button\">(\n props: PolymorphicProps<T, CollapsibleTriggerProps<T>>\n) => {\n const [local, rest] = splitProps(props as CollapsibleTriggerProps, [\"class\", \"children\"]);\n return (\n <CollapsiblePrimitive.Trigger\n class={cn(\"flex w-full items-center justify-between cursor-pointer\", local.class)}\n {...rest}\n >\n {local.children}\n </CollapsiblePrimitive.Trigger>\n );\n};\n\nexport type CollapsibleContentProps<T extends ValidComponent = \"div\"> =\n CollapsiblePrimitive.CollapsibleContentProps<T> & {\n class?: string;\n children?: JSX.Element;\n };\n\n/**\n * Collapsible content panel revealed when opened.\n */\nexport const CollapsibleContent = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, CollapsibleContentProps<T>>\n) => {\n const [local, rest] = splitProps(props as CollapsibleContentProps, [\"class\", \"children\"]);\n return (\n <CollapsiblePrimitive.Content\n class={cn(\n \"overflow-hidden transition-all data-expanded:animate-collapsible-down data-closed:animate-collapsible-up\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n </CollapsiblePrimitive.Content>\n );\n};\n",
|
|
15
|
+
"type": "registry:ui"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
package/registry/combobox.json
CHANGED
|
@@ -8,10 +8,13 @@
|
|
|
8
8
|
"tailwind-merge",
|
|
9
9
|
"@kobalte/core"
|
|
10
10
|
],
|
|
11
|
+
"registryDependencies": [
|
|
12
|
+
"scroll-area"
|
|
13
|
+
],
|
|
11
14
|
"files": [
|
|
12
15
|
{
|
|
13
16
|
"path": "ui/combobox.tsx",
|
|
14
|
-
"content": "import { splitProps, type JSX, type ValidComponent } from \"solid-js\";\nimport * as ComboboxPrimitive from \"@kobalte/core/combobox\";\nimport { cn } from \"@/lib/cn\";\n\nexport type ComboboxRootProps<Option = any, OptGroup = any, T extends ValidComponent = \"div\"> =\n ComboboxPrimitive.ComboboxRootProps<Option, OptGroup, T> & {\n class?: string;\n children?: JSX.Element;\n triggerMode?: \"input\" | \"focus\" | \"both\" | \"manual\";\n };\n\n/**\n * Root Combobox component providing search, single/multi-selection, and group support.\n * `triggerMode=\"focus\"` or `triggerMode=\"both\"` enables opening dropdown on input click/focus.\n */\nexport const Combobox = <Option = any, OptGroup = any, T extends ValidComponent = \"div\">(\n props: ComboboxRootProps<Option, OptGroup, T>\n) => {\n const [local, rest] = splitProps(props as ComboboxRootProps, [\"class\", \"children\", \"triggerMode\"]);\n\n return (\n <ComboboxPrimitive.Root\n triggerMode={local.triggerMode ?? \"input\"}\n class={cn(\"relative w-full\", local.class)}\n {...(rest as any)}\n >\n {local.children}\n </ComboboxPrimitive.Root>\n );\n};\n\nexport type ComboboxControlProps<Option = any, T extends ValidComponent = \"div\"> =\n ComboboxPrimitive.ComboboxControlProps<Option, T> & {\n class?: string;\n children?: JSX.Element;\n clearable?: boolean;\n onClear?: () => void;\n };\n\n/**\n * Input container for Combobox supporting search input, selected tags, and clear button.\n */\nexport const ComboboxControl = <Option = any, T extends ValidComponent = \"div\">(\n props: ComboboxControlProps<Option, T>\n) => {\n const [local, rest] = splitProps(props as ComboboxControlProps, [\n \"class\",\n \"children\",\n \"clearable\",\n \"onClear\",\n ]);\n\n return (\n <ComboboxPrimitive.Control\n class={cn(\n \"flex min-h-9 w-full flex-wrap items-center justify-between rounded-md border border-input bg-muted px-3 py-1 text-sm shadow-sm transition-colors focus-within:ring-1 focus-within:ring-primary focus-within:border-primary disabled:cursor-not-allowed disabled:opacity-50 gap-1.5 text-foreground\",\n local.class\n )}\n {...(rest as any)}\n >\n <div class=\"flex flex-wrap items-center gap-1.5 flex-1 min-w-0\">\n {local.children}\n </div>\n\n <div class=\"flex items-center gap-1 shrink-0 self-center\">\n {local.clearable && (\n <button\n type=\"button\"\n tabIndex={-1}\n onClick={(e) => {\n e.stopPropagation();\n if (local.onClear) local.onClear();\n }}\n class=\"rounded-sm p-0.5 opacity-60 hover:opacity-100 hover:bg-accent text-foreground transition-opacity focus:outline-none cursor-pointer\"\n aria-label=\"Clear selection\"\n >\n <svg class=\"h-3.5 w-3.5\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n )}\n <ComboboxTrigger />\n </div>\n </ComboboxPrimitive.Control>\n );\n};\n\nexport type ComboboxInputProps<T extends ValidComponent = \"input\"> =\n ComboboxPrimitive.ComboboxInputProps<T> & {\n class?: string;\n openOnFocus?: boolean;\n };\n\n/**\n * Search input field embedded within ComboboxControl.\n * Supports `openOnFocus` to automatically trigger the dropdown when focused or clicked.\n */\nexport const ComboboxInput = <T extends ValidComponent = \"input\">(\n props: ComboboxInputProps<T>\n) => {\n const [local, rest] = splitProps(props as ComboboxInputProps, [\"class\", \"openOnFocus\", \"onFocus\", \"onClick\"]);\n\n return (\n <ComboboxPrimitive.Input\n class={cn(\n \"flex-1 bg-transparent py-1 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed text-foreground min-w-16\",\n local.class\n )}\n onFocus={(e: FocusEvent) => {\n if (typeof local.onFocus === \"function\") local.onFocus(e as any);\n }}\n onClick={(e: MouseEvent) => {\n if (typeof local.onClick === \"function\") local.onClick(e as any);\n }}\n {...(rest as any)}\n />\n );\n};\n\nexport type ComboboxTriggerProps<T extends ValidComponent = \"button\"> =\n ComboboxPrimitive.ComboboxTriggerProps<T> & {\n class?: string;\n };\n\n/**\n * Dropdown chevron trigger icon.\n */\nexport const ComboboxTrigger = <T extends ValidComponent = \"button\">(\n props: ComboboxTriggerProps<T>\n) => {\n const [local, rest] = splitProps(props as ComboboxTriggerProps, [\"class\"]);\n\n return (\n <ComboboxPrimitive.Trigger\n class={cn(\n \"flex h-4 w-4 items-center justify-center opacity-50 hover:opacity-100 transition-opacity focus:outline-none cursor-pointer\",\n local.class\n )}\n {...(rest as any)}\n >\n <ComboboxPrimitive.Icon\n as=\"svg\"\n class=\"h-4 w-4 stroke-current stroke-2 fill-none\"\n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M19 9l-7 7-7-7\" />\n </ComboboxPrimitive.Icon>\n </ComboboxPrimitive.Trigger>\n );\n};\n\nexport type ComboboxTokenProps<Option = any> = {\n class?: string;\n children?: JSX.Element;\n item?: Option;\n onRemove?: () => void;\n};\n\n/**\n * Selected item tag token pill rendered in multi-select mode.\n */\nexport const ComboboxToken = <Option = any>(props: ComboboxTokenProps<Option>) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\", \"onRemove\"]);\n\n return (\n <span\n class={cn(\n \"inline-flex items-center gap-1.5 rounded-md bg-accent px-2 py-0.5 text-xs font-medium text-accent-foreground border border-border shadow-2xs animate-in fade-in-50\",\n local.class\n )}\n {...rest}\n >\n <span class=\"truncate\">{local.children}</span>\n <button\n type=\"button\"\n tabIndex={-1}\n onClick={(e) => {\n e.stopPropagation();\n if (local.onRemove) local.onRemove();\n }}\n class=\"rounded-xs p-0.5 hover:bg-muted-foreground/20 text-muted-foreground hover:text-foreground transition-colors focus:outline-none cursor-pointer\"\n aria-label=\"Remove tag\"\n >\n <svg class=\"h-3 w-3\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </span>\n );\n};\n\nexport type ComboboxContentProps<T extends ValidComponent = \"div\"> =\n ComboboxPrimitive.ComboboxContentProps<T> & {\n class?: string;\n };\n\n/**\n * Portaled popover content listbox container.\n */\nexport const ComboboxContent = <T extends ValidComponent = \"div\">(\n props: ComboboxContentProps<T>\n) => {\n const [local, rest] = splitProps(props as ComboboxContentProps, [\"class\"]);\n\n return (\n <ComboboxPrimitive.Portal>\n <ComboboxPrimitive.Content\n class={cn(\n \"relative z-50 min-w-8rem overflow-hidden rounded-md border border-border bg-popover text-popover-foreground shadow-md animate-in fade-in-80 data-[expanded]:animate-in data-[closed]:animate-out data-[closed]:fade-out-0 data-[closed]:fade-out-0 data-[expanded]:fade-in-0 data-[closed]:zoom-out-95 data-[expanded]:zoom-in-95\",\n local.class\n )}\n {...(rest as any)}\n >\n <ComboboxPrimitive.Listbox class=\"max-h-60 overflow-y-auto p-1 outline-none\" />\n </ComboboxPrimitive.Content>\n </ComboboxPrimitive.Portal>\n );\n};\n\nexport type ComboboxItemProps<T extends ValidComponent = \"li\"> =\n ComboboxPrimitive.ComboboxItemProps<T> & {\n class?: string;\n children?: JSX.Element;\n };\n\n/**\n * Individual option item inside listbox supporting custom avatars, icons, and titles.\n */\nexport const ComboboxItem = <T extends ValidComponent = \"li\">(\n props: ComboboxItemProps<T>\n) => {\n const [local, rest] = splitProps(props as ComboboxItemProps, [\"class\", \"children\"]);\n\n return (\n <ComboboxPrimitive.Item\n class={cn(\n \"relative flex w-full cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-disabled:pointer-events-none data-disabled:opacity-50 data-highlighted:bg-accent data-highlighted:text-accent-foreground text-popover-foreground transition-colors\",\n local.class\n )}\n {...rest}\n >\n <ComboboxPrimitive.ItemIndicator class=\"absolute left-2 flex h-4 w-4 items-center justify-center text-primary\">\n <svg class=\"h-4 w-4 fill-none stroke-current stroke-2\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M5 13l4 4L19 7\" />\n </svg>\n </ComboboxPrimitive.ItemIndicator>\n <ComboboxPrimitive.ItemLabel class=\"flex items-center gap-2 w-full truncate\">\n {local.children}\n </ComboboxPrimitive.ItemLabel>\n </ComboboxPrimitive.Item>\n );\n};\n\nexport type ComboboxGroupProps<T extends ValidComponent = \"li\"> =\n ComboboxPrimitive.ComboboxSectionProps<T> & {\n class?: string;\n children?: JSX.Element;\n label?: JSX.Element;\n };\n\n/**\n * Group container for organizing related options.\n */\nexport const ComboboxGroup = <T extends ValidComponent = \"li\">(\n props: ComboboxGroupProps<T>\n) => {\n const [local, rest] = splitProps(props as ComboboxGroupProps, [\"class\", \"children\", \"label\"]);\n\n return (\n <ComboboxPrimitive.Section class={cn(\"px-1 py-1\", local.class)} {...(rest as any)}>\n {local.label && (\n <span class=\"px-2 py-1.5 text-xs font-semibold text-muted-foreground uppercase tracking-wider block\">\n {local.label}\n </span>\n )}\n {local.children}\n </ComboboxPrimitive.Section>\n );\n};\n\n/**\n * Empty state notice when no matching search items exist.\n */\nexport const ComboboxEmpty = (props: { class?: string; children?: JSX.Element }) => {\n return (\n <div class={cn(\"py-6 text-center text-sm text-muted-foreground\", props.class)}>\n {props.children || \"No matching items found.\"}\n </div>\n );\n};\n\n/**\n * Hidden native select element for form integrations.\n */\nexport const ComboboxHiddenSelect = ComboboxPrimitive.HiddenSelect;\n",
|
|
17
|
+
"content": "import { splitProps, type JSX, type ValidComponent } from \"solid-js\";\nimport { Check, ChevronDown, X } from \"lucide-solid\";\nimport { ScrollArea } from \"./scroll-area\";\nimport * as ComboboxPrimitive from \"@kobalte/core/combobox\";\nimport { cn } from \"@/lib/cn\";\n\nexport type ComboboxRootProps<Option = any, OptGroup = any, T extends ValidComponent = \"div\"> =\n ComboboxPrimitive.ComboboxRootProps<Option, OptGroup, T>;\n\n/**\n * Root Combobox primitive component wrapper.\n */\nexport const Combobox = <Option = any, OptGroup = any, T extends ValidComponent = \"div\">(\n props: ComboboxRootProps<Option, OptGroup, T>\n) => {\n return <ComboboxPrimitive.Root {...props} />;\n};\n\nexport type ComboboxControlProps<Option = any, T extends ValidComponent = \"div\"> =\n ComboboxPrimitive.ComboboxControlProps<Option, T> & {\n class?: string;\n children?: JSX.Element;\n };\n\n/**\n * Input container box supporting single or multi-select tokens.\n */\nexport const ComboboxControl = <Option = any, T extends ValidComponent = \"div\">(\n props: ComboboxControlProps<Option, T>\n) => {\n const [local, rest] = splitProps(props as ComboboxControlProps, [\"class\", \"children\"]);\n\n return (\n <ComboboxPrimitive.Control\n class={cn(\n \"flex min-h-9 w-full flex-wrap items-center gap-1.5 rounded-md border border-input bg-muted px-3 py-1.5 text-sm shadow-2xs ring-offset-background focus-within:ring-1 focus-within:ring-primary focus-within:border-primary disabled:cursor-not-allowed disabled:opacity-50 text-foreground cursor-text transition-colors\",\n local.class\n )}\n {...(rest as any)}\n >\n {local.children}\n </ComboboxPrimitive.Control>\n );\n};\n\nexport type ComboboxInputProps<T extends ValidComponent = \"input\"> =\n ComboboxPrimitive.ComboboxInputProps<T> & {\n class?: string;\n openOnFocus?: boolean;\n };\n\n/**\n * Filter search input field.\n */\nexport const ComboboxInput = <T extends ValidComponent = \"input\">(\n props: ComboboxInputProps<T>\n) => {\n const [local, rest] = splitProps(props as ComboboxInputProps, [\"class\", \"openOnFocus\", \"onFocus\", \"onClick\"]);\n\n return (\n <ComboboxPrimitive.Input\n class={cn(\n \"flex-1 bg-transparent py-1 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed text-foreground min-w-16\",\n local.class\n )}\n onFocus={(e: FocusEvent) => {\n if (typeof local.onFocus === \"function\") local.onFocus(e as any);\n }}\n onClick={(e: MouseEvent) => {\n if (typeof local.onClick === \"function\") local.onClick(e as any);\n }}\n {...(rest as any)}\n />\n );\n};\n\nexport type ComboboxTriggerProps<T extends ValidComponent = \"button\"> =\n ComboboxPrimitive.ComboboxTriggerProps<T> & {\n class?: string;\n };\n\n/**\n * Dropdown chevron trigger icon.\n */\nexport const ComboboxTrigger = <T extends ValidComponent = \"button\">(\n props: ComboboxTriggerProps<T>\n) => {\n const [local, rest] = splitProps(props as ComboboxTriggerProps, [\"class\"]);\n\n return (\n <ComboboxPrimitive.Trigger\n class={cn(\n \"flex h-4 w-4 items-center justify-center opacity-50 hover:opacity-100 transition-opacity focus:outline-none cursor-pointer\",\n local.class\n )}\n {...(rest as any)}\n >\n <ComboboxPrimitive.Icon\n as=\"svg\"\n class=\"h-4 w-4 stroke-current stroke-2 fill-none\"\n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M19 9l-7 7-7-7\" />\n </ComboboxPrimitive.Icon>\n </ComboboxPrimitive.Trigger>\n );\n};\n\nexport type ComboboxTokenProps<Option = any> = {\n class?: string;\n children?: JSX.Element;\n item?: Option;\n onRemove?: () => void;\n};\n\n/**\n * Selected item tag token pill rendered in multi-select mode.\n */\nexport const ComboboxToken = <Option = any>(props: ComboboxTokenProps<Option>) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\", \"onRemove\"]);\n\n return (\n <span\n class={cn(\n \"inline-flex items-center gap-1.5 rounded-md bg-accent px-2 py-0.5 text-xs font-medium text-accent-foreground border border-border shadow-2xs animate-in fade-in-50\",\n local.class\n )}\n {...rest}\n >\n <span class=\"truncate\">{local.children}</span>\n <button\n type=\"button\"\n tabIndex={-1}\n onClick={(e) => {\n e.stopPropagation();\n if (local.onRemove) local.onRemove();\n }}\n class=\"rounded-xs opacity-70 hover:opacity-100 focus:outline-none cursor-pointer text-muted-foreground hover:text-foreground\"\n >\n <X class=\"h-3 w-3\" />\n <span class=\"sr-only\">Remove</span>\n </button>\n </span>\n );\n};\n\nexport type ComboboxContentProps<T extends ValidComponent = \"div\"> =\n ComboboxPrimitive.ComboboxContentProps<T> & {\n class?: string;\n };\n\n/**\n * Portaled popover content listbox container.\n */\nexport const ComboboxContent = <T extends ValidComponent = \"div\">(\n props: ComboboxContentProps<T>\n) => {\n const [local, rest] = splitProps(props as ComboboxContentProps, [\"class\"]);\n\n return (\n <ComboboxPrimitive.Portal>\n <ComboboxPrimitive.Content\n class={cn(\n \"relative z-50 min-w-8rem overflow-hidden rounded-md border border-border bg-popover text-popover-foreground shadow-md animate-in fade-in-80 data-expanded:animate-in data-closed:animate-out data-[closed]:fade-out-0 data-[expanded]:fade-in-0 data-closed:zoom-out-95 data-expanded:zoom-in-95 max-h-60\",\n local.class\n )}\n {...(rest as any)}\n >\n <ScrollArea class=\"max-h-60 w-full\">\n <ComboboxPrimitive.Listbox class=\"p-1 outline-none\" />\n </ScrollArea>\n </ComboboxPrimitive.Content>\n </ComboboxPrimitive.Portal>\n );\n};\n\nexport type ComboboxItemProps<T extends ValidComponent = \"li\"> =\n ComboboxPrimitive.ComboboxItemProps<T> & {\n class?: string;\n children?: JSX.Element;\n };\n\n/**\n * Individual option item inside listbox supporting custom avatars, icons, and titles.\n */\nexport const ComboboxItem = <T extends ValidComponent = \"li\">(\n props: ComboboxItemProps<T>\n) => {\n const [local, rest] = splitProps(props as ComboboxItemProps, [\"class\", \"children\"]);\n\n return (\n <ComboboxPrimitive.Item\n class={cn(\n \"relative flex w-full cursor-pointer select-none items-center justify-between rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50 text-foreground transition-colors\",\n local.class\n )}\n {...(rest as any)}\n >\n <ComboboxPrimitive.ItemLabel class=\"flex-1 truncate\">\n {local.children}\n </ComboboxPrimitive.ItemLabel>\n <ComboboxPrimitive.ItemIndicator class=\"ml-2 flex h-4 w-4 items-center justify-center text-primary\">\n <Check class=\"h-4 w-4 stroke-2\" />\n </ComboboxPrimitive.ItemIndicator>\n </ComboboxPrimitive.Item>\n );\n};\n\nexport type ComboboxGroupProps<T extends ValidComponent = \"li\"> =\n ComboboxPrimitive.ComboboxSectionProps<T> & {\n class?: string;\n children?: JSX.Element;\n label?: JSX.Element;\n };\n\n/**\n * Group container for organizing related options.\n */\nexport const ComboboxGroup = <T extends ValidComponent = \"li\">(\n props: ComboboxGroupProps<T>\n) => {\n const [local, rest] = splitProps(props as ComboboxGroupProps, [\"class\", \"children\", \"label\"]);\n\n return (\n <ComboboxPrimitive.Section class={cn(\"px-1 py-1\", local.class)} {...(rest as any)}>\n {local.label && (\n <span class=\"px-2 py-1.5 text-xs font-semibold text-muted-foreground uppercase tracking-wider block\">\n {local.label}\n </span>\n )}\n {local.children}\n </ComboboxPrimitive.Section>\n );\n};\n",
|
|
15
18
|
"type": "registry:ui"
|
|
16
19
|
}
|
|
17
20
|
]
|
package/registry/command.json
CHANGED
|
@@ -13,12 +13,13 @@
|
|
|
13
13
|
"registryDependencies": [
|
|
14
14
|
"kbd",
|
|
15
15
|
"input-group",
|
|
16
|
-
"list"
|
|
16
|
+
"list",
|
|
17
|
+
"scroll-area"
|
|
17
18
|
],
|
|
18
19
|
"files": [
|
|
19
20
|
{
|
|
20
21
|
"path": "ui/command.tsx",
|
|
21
|
-
"content": "import {\n createContext,\n useContext,\n createSignal,\n onCleanup,\n Show,\n splitProps,\n type Component,\n type JSX,\n type Accessor,\n} from \"solid-js\";\nimport { createKeybindings } from \"@nikala-ui/hooks\";\nimport { Dialog } from \"@kobalte/core/dialog\";\nimport { Search, ArrowUp, ArrowDown, CornerDownLeft } from \"lucide-solid\";\nimport { cn } from \"@/lib/cn\";\nimport { Kbd, KbdGroup } from \"../ui/kbd\";\nimport { InputGroup, InputGroupInput, InputGroupAddon } from \"../ui/input-group\";\nimport { List, ListGroup, ListHeader, ListItem, type ListItemProps } from \"../ui/list\";\n\n/* --- Command Context State --- */\ninterface CommandContextValue {\n search: Accessor<string>;\n setSearch: (value: string) => void;\n}\n\nconst CommandContext = createContext<CommandContextValue>();\n\nexport const useCommand = () => {\n const ctx = useContext(CommandContext);\n if (!ctx) {\n throw new Error(\"useCommand must be used within a <Command />\");\n }\n return ctx;\n};\n\n/* --- Root Command Container --- */\nexport interface CommandProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const Command: Component<CommandProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n const [search, setSearch] = createSignal(\"\");\n\n return (\n <CommandContext.Provider value={{ search, setSearch }}>\n <div\n class={cn(\n \"flex flex-col w-full h-full rounded-lg bg-popover text-popover-foreground overflow-hidden border border-border shadow-md\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n </div>\n </CommandContext.Provider>\n );\n};\n\n/* --- Command Dialog (Modal) --- */\nexport interface CommandDialogProps {\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n enableHotkey?: boolean;\n children?: JSX.Element;\n class?: string;\n}\n\nexport const CommandDialog: Component<CommandDialogProps> = (props) => {\n const [internalOpen, setInternalOpen] = createSignal(false);\n\n const isOpen = () => (props.open !== undefined ? props.open : internalOpen());\n const setOpen = (val: boolean) => {\n if (props.open === undefined) setInternalOpen(val);\n if (typeof props.onOpenChange === \"function\") props.onOpenChange(val);\n };\n\n /* Listen for global Ctrl+K / Cmd+K hotkeys */\n createKeybindings(\n [\n {\n key: [\"meta+k\", \"ctrl+k\"],\n handler: () => setOpen(!isOpen()),\n preventDefault: true,\n },\n ],\n {\n enabled: () => props.enableHotkey !== false,\n }\n );\n\n return (\n <Dialog open={isOpen()} onOpenChange={setOpen}>\n <Dialog.Portal>\n <Dialog.Overlay class=\"fixed inset-0 z-50 bg-black/60 backdrop-blur-xs data-expanded:animate-in data-closed:animate-out data-[closed]:fade-out-0 data-[expanded]:fade-in-0\" />\n <div class=\"fixed inset-0 z-50 flex items-start justify-center pt-16 sm:pt-24 px-4\">\n <Dialog.Content class=\"w-full max-w-xl rounded-lg border border-border bg-popover text-popover-foreground shadow-2xl outline-none data-expanded:animate-in data-closed:animate-out data-[closed]:fade-out-0 data-[expanded]:fade-in-0 data-closed:zoom-out-95 data-expanded:zoom-in-95\">\n <Command class={props.class}>{props.children}</Command>\n </Dialog.Content>\n </div>\n </Dialog.Portal>\n </Dialog>\n );\n};\n\n/* --- Command Input --- */\nexport interface CommandInputProps\n extends JSX.InputHTMLAttributes<HTMLInputElement> {\n class?: string;\n}\n\nexport const CommandInput: Component<CommandInputProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"value\", \"onInput\"]);\n const { search, setSearch } = useCommand();\n\n return (\n <InputGroup class=\"border-0 border-b border-border rounded-none bg-transparent px-3 py-1 shadow-none focus-within:ring-0 focus-within:border-border\">\n <InputGroupAddon align=\"inline-start\">\n <Search class=\"w-4 h-4 text-muted-foreground\" />\n </InputGroupAddon>\n\n <InputGroupInput\n value={search()}\n onInput={(e) => {\n setSearch(e.currentTarget.value);\n if (typeof local.onInput === \"function\") {\n local.onInput(e);\n }\n }}\n placeholder=\"Type a command or search...\"\n class=\"h-11 text-base sm:text-sm font-medium\"\n {...rest}\n />\n </InputGroup>\n );\n};\n\n/* --- Command List Container --- */\nexport interface CommandListProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const CommandList: Component<CommandListProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <div\n class={cn(\"max-h-82.5 overflow-y-auto p-1.5 scrollbar-thin\", local.class)}\n {...rest}\n >\n <List>{local.children}</List>\n </div>\n );\n};\n\n/* --- Command Empty Block --- */\nexport interface CommandEmptyProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const CommandEmpty: Component<CommandEmptyProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n const { search } = useCommand();\n\n return (\n <Show when={search().trim().length > 0}>\n <div\n class={cn(\n \"py-8 text-center text-sm text-muted-foreground font-medium select-none\",\n local.class\n )}\n {...rest}\n >\n {local.children || `No results found for \"${search()}\".`}\n </div>\n </Show>\n );\n};\n\n/* --- Command Group --- */\nexport interface CommandGroupProps {\n heading: string;\n children?: JSX.Element;\n class?: string;\n}\n\nexport const CommandGroup: Component<CommandGroupProps> = (props) => {\n return (\n <ListGroup class={props.class}>\n <ListHeader title={props.heading} />\n {props.children}\n </ListGroup>\n );\n};\n\n/* --- Command Item --- */\nexport interface CommandItemProps extends ListItemProps {\n keywords?: string[];\n onSelect?: () => void;\n}\n\nexport const CommandItem: Component<CommandItemProps> = (props) => {\n const [local, rest] = splitProps(props, [\n \"title\",\n \"subtitle\",\n \"keywords\",\n \"onSelect\",\n \"onClick\",\n \"class\",\n ]);\n const { search } = useCommand();\n\n /* Auto fuzzy-filter item based on search query */\n const matchesSearch = () => {\n const query = search().toLowerCase().trim();\n if (!query) return true;\n\n const titleMatch = local.title?.toLowerCase().includes(query);\n const subtitleMatch = local.subtitle?.toLowerCase().includes(query);\n const keywordMatch = local.keywords?.some((k) =>\n k.toLowerCase().includes(query)\n );\n\n return Boolean(titleMatch || subtitleMatch || keywordMatch);\n };\n\n const handleClick = (e: MouseEvent) => {\n if (typeof local.onSelect === \"function\") {\n local.onSelect();\n }\n if (typeof local.onClick === \"function\") {\n local.onClick(e as any);\n }\n };\n\n return (\n <Show when={matchesSearch()}>\n <ListItem\n title={local.title}\n subtitle={local.subtitle}\n onClick={handleClick}\n class={local.class}\n {...rest}\n />\n </Show>\n );\n};\n\n/* --- Command Footer --- */\nexport interface CommandFooterProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const CommandFooter: Component<CommandFooterProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"]);\n\n return (\n <div\n class={cn(\n \"flex items-center justify-between border-t border-border bg-muted/30 px-3 py-2 text-xs text-muted-foreground select-none\",\n local.class\n )}\n {...rest}\n >\n <div class=\"flex items-center gap-3\">\n <span class=\"flex items-center gap-1\">\n <KbdGroup>\n <Kbd size=\"sm\"><ArrowUp class=\"w-2.5 h-2.5\" /></Kbd>\n <Kbd size=\"sm\"><ArrowDown class=\"w-2.5 h-2.5\" /></Kbd>\n </KbdGroup>\n <span>Navigate</span>\n </span>\n\n <span class=\"flex items-center gap-1\">\n <Kbd size=\"sm\"><CornerDownLeft class=\"w-2.5 h-2.5\" /></Kbd>\n <span>Select</span>\n </span>\n </div>\n\n <span class=\"flex items-center gap-1\">\n <Kbd size=\"sm\">Esc</Kbd>\n <span>Close</span>\n </span>\n </div>\n );\n};",
|
|
22
|
+
"content": "import {\n createContext,\n useContext,\n createSignal,\n onCleanup,\n Show,\n splitProps,\n type Component,\n type JSX,\n type Accessor,\n} from \"solid-js\";\nimport { createKeybindings } from \"@nikala-ui/hooks\";\nimport { Search, ArrowUp, ArrowDown, CornerDownLeft } from \"lucide-solid\";\nimport { cn } from \"@/lib/cn\";\nimport { Kbd, KbdGroup } from \"./kbd\";\nimport { InputGroup, InputGroupInput, InputGroupAddon } from \"./input-group\";\nimport { List, ListGroup, ListHeader, ListItem, type ListItemProps } from \"./list\";\nimport { Dialog, DialogOverlay, DialogContent } from \"./dialog\";\nimport { ScrollArea } from \"./scroll-area\";\n\n/* --- Command Context State --- */\ninterface CommandContextValue {\n search: Accessor<string>;\n setSearch: (value: string) => void;\n activeIndex: Accessor<number>;\n setActiveIndex: (fn: (prev: number) => number) => void;\n registerItemIndex: () => number;\n onItemSelect: (fn: () => void) => void;\n}\n\nconst CommandContext = createContext<CommandContextValue>();\n\nexport const useCommand = () => {\n const ctx = useContext(CommandContext);\n if (!ctx) {\n throw new Error(\"useCommand must be used within a <Command /> root component.\");\n }\n return ctx;\n};\n\n/* --- Command Root --- */\nexport interface CommandProps extends Omit<JSX.HTMLAttributes<HTMLDivElement>, \"children\"> {\n class?: string;\n children?: JSX.Element | ((ctx: CommandContextValue) => JSX.Element);\n}\n\nexport const Command: Component<CommandProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n const [search, setSearch] = createSignal(\"\");\n const [activeIndex, setActiveIndex] = createSignal(0);\n let itemCounter = 0;\n const itemCallbacks: Record<number, () => void> = {};\n let containerRef: HTMLDivElement | undefined;\n\n const registerItemIndex = () => {\n const idx = itemCounter;\n itemCounter += 1;\n return idx;\n };\n\n const onItemSelect = (fn: () => void) => {\n itemCallbacks[activeIndex()] = fn;\n };\n\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"ArrowDown\") {\n e.preventDefault();\n setActiveIndex((prev) => Math.min(prev + 1, Math.max(0, itemCounter - 1)));\n } else if (e.key === \"ArrowUp\") {\n e.preventDefault();\n setActiveIndex((prev) => Math.max(prev - 1, 0));\n } else if (e.key === \"Enter\") {\n e.preventDefault();\n const fn = itemCallbacks[activeIndex()];\n if (fn) fn();\n const current = containerRef?.querySelector('[data-command-active=\"true\"]') as HTMLElement;\n if (current) {\n current.click();\n }\n }\n };\n\n const ctx: CommandContextValue = {\n search,\n setSearch: (val) => {\n setSearch(val);\n setActiveIndex(0);\n },\n activeIndex,\n setActiveIndex: (fn) => setActiveIndex(fn),\n registerItemIndex,\n onItemSelect,\n };\n\n return (\n <CommandContext.Provider value={ctx}>\n <div\n ref={containerRef}\n onKeyDown={handleKeyDown}\n class={cn(\n \"flex flex-col w-full h-full rounded-lg bg-popover text-popover-foreground overflow-hidden border border-border shadow-md outline-none\",\n local.class\n )}\n tabIndex={0}\n {...rest}\n >\n {typeof local.children === \"function\" ? (local.children as any)(ctx) : local.children}\n </div>\n </CommandContext.Provider>\n );\n};\n\n/* --- Command Dialog (Modal) --- */\nexport interface CommandDialogProps {\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n enableHotkey?: boolean;\n children?: JSX.Element | ((ctx: CommandContextValue) => JSX.Element);\n class?: string;\n}\n\nexport const CommandDialog: Component<CommandDialogProps> = (props) => {\n const [internalOpen, setInternalOpen] = createSignal(false);\n\n const isOpen = () => (props.open !== undefined ? props.open : internalOpen());\n const setOpen = (val: boolean) => {\n if (props.open === undefined) setInternalOpen(val);\n if (typeof props.onOpenChange === \"function\") props.onOpenChange(val);\n };\n\n /* Listen for global Ctrl+K / Cmd+K hotkeys */\n createKeybindings(\n [\n {\n key: [\"meta+k\", \"ctrl+k\"],\n handler: () => setOpen(!isOpen()),\n preventDefault: true,\n },\n ],\n {\n enabled: () => props.enableHotkey !== false,\n }\n );\n\n return (\n <Dialog open={isOpen()} onOpenChange={setOpen}>\n <DialogOverlay class=\"fixed inset-0 z-50 bg-black/60 backdrop-blur-xs data-expanded:animate-in data-closed:animate-out data-[closed]:fade-out-0 data-[expanded]:fade-in-0\" />\n <div class=\"fixed inset-0 z-50 flex items-start justify-center pt-16 sm:pt-24 px-4\">\n <DialogContent class=\"w-full max-w-xl rounded-lg border border-border bg-popover text-popover-foreground shadow-2xl outline-none data-expanded:animate-in data-closed:animate-out data-[closed]:fade-out-0 data-[expanded]:fade-in-0 data-closed:zoom-out-95 data-expanded:zoom-in-95 p-0\">\n <Command class={props.class}>{props.children}</Command>\n </DialogContent>\n </div>\n </Dialog>\n );\n};\n\n/* --- Command Input --- */\nexport interface CommandInputProps\n extends JSX.InputHTMLAttributes<HTMLInputElement> {\n class?: string;\n}\n\nexport const CommandInput: Component<CommandInputProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"value\", \"onInput\"]);\n const { search, setSearch } = useCommand();\n\n return (\n <InputGroup class=\"border-0 border-b border-border rounded-none bg-transparent px-3 py-1 shadow-none focus-within:ring-0 focus-within:border-border\">\n <InputGroupAddon align=\"inline-start\">\n <Search class=\"w-4 h-4 text-muted-foreground\" />\n </InputGroupAddon>\n\n <InputGroupInput\n ref={(el) => setTimeout(() => el.focus(), 50)}\n value={search()}\n onInput={(e) => {\n setSearch(e.currentTarget.value);\n if (typeof local.onInput === \"function\") {\n local.onInput(e);\n }\n }}\n placeholder=\"Type a command or search...\"\n class=\"h-11 text-base sm:text-sm font-medium\"\n {...rest}\n />\n </InputGroup>\n );\n};\n\n/* --- Command List Container --- */\nexport interface CommandListProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const CommandList: Component<CommandListProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <ScrollArea\n class={cn(\"max-h-82.5 p-1.5\", local.class)}\n {...rest}\n >\n <List>{local.children}</List>\n </ScrollArea>\n );\n};\n\n/* --- Command Empty Block --- */\nexport interface CommandEmptyProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const CommandEmpty: Component<CommandEmptyProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n const { search } = useCommand();\n\n return (\n <Show when={search().trim().length > 0}>\n <div\n class={cn(\n \"py-8 text-center text-sm text-muted-foreground font-medium select-none\",\n local.class\n )}\n {...rest}\n >\n {local.children || `No results found for \"${search()}\".`}\n </div>\n </Show>\n );\n};\n\n/* --- Command Group --- */\nexport interface CommandGroupProps {\n heading: string;\n children?: JSX.Element;\n class?: string;\n}\n\nexport const CommandGroup: Component<CommandGroupProps> = (props) => {\n return (\n <ListGroup class={props.class}>\n <ListHeader title={props.heading} />\n {props.children}\n </ListGroup>\n );\n};\n\n/* --- Command Item --- */\nexport interface CommandItemProps extends ListItemProps {\n keywords?: string[];\n onSelect?: () => void;\n}\n\nexport const CommandItem: Component<CommandItemProps> = (props) => {\n let itemRef: HTMLDivElement | undefined;\n const [local, rest] = splitProps(props, [\n \"title\",\n \"subtitle\",\n \"keywords\",\n \"disabled\",\n \"onSelect\",\n \"class\",\n \"children\",\n ]);\n\n const ctx = useCommand();\n const index = ctx.registerItemIndex();\n\n const isActive = () => ctx.activeIndex() === index;\n\n /* Automatic scroll into view when navigating with Arrow keys */\n const scrollIntoViewIfNeeded = () => {\n if (isActive() && itemRef) {\n itemRef.scrollIntoView({ block: \"nearest\", behavior: \"smooth\" });\n }\n };\n\n const isVisible = () => {\n const query = ctx.search().toLowerCase().trim();\n if (!query) return true;\n\n const titleStr = typeof local.title === \"string\" ? local.title.toLowerCase() : \"\";\n const subStr = typeof local.subtitle === \"string\" ? local.subtitle.toLowerCase() : \"\";\n\n if (titleStr.includes(query) || subStr.includes(query)) return true;\n\n if (local.keywords) {\n return local.keywords.some((k) => k.toLowerCase().includes(query));\n }\n\n return false;\n };\n\n const handleSelect = () => {\n if (local.disabled) return;\n if (local.onSelect) local.onSelect();\n };\n\n return (\n <Show when={isVisible()}>\n <ListItem\n ref={(el) => {\n itemRef = el;\n scrollIntoViewIfNeeded();\n }}\n title={local.title}\n subtitle={local.subtitle}\n data-command-active={isActive() ? \"true\" : \"false\"}\n class={cn(\n \"relative flex cursor-pointer select-none items-center rounded-md px-2 py-1.5 text-sm outline-none transition-colors\",\n isActive()\n ? \"bg-accent text-accent-foreground font-medium\"\n : \"text-popover-foreground hover:bg-muted/50\",\n local.disabled && \"pointer-events-none opacity-50\",\n local.class\n )}\n onClick={handleSelect}\n onMouseEnter={() => ctx.setActiveIndex(() => index)}\n {...rest}\n >\n {local.children}\n </ListItem>\n </Show>\n );\n};\n\n/* --- Command Footer Indicator Bar --- */\nexport interface CommandFooterProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const CommandFooter: Component<CommandFooterProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <div\n class={cn(\n \"flex items-center justify-between border-t border-border px-3 py-2 text-xs text-muted-foreground bg-muted/40\",\n local.class\n )}\n {...rest}\n >\n {local.children || (\n <>\n <div class=\"flex items-center gap-2\">\n <span class=\"inline-flex items-center gap-1\">\n <KbdGroup>\n <Kbd size=\"sm\">\n <ArrowUp class=\"w-2.5 h-2.5\" />\n </Kbd>\n <Kbd size=\"sm\">\n <ArrowDown class=\"w-2.5 h-2.5\" />\n </Kbd>\n </KbdGroup>\n <span>Navigate</span>\n </span>\n\n <span class=\"inline-flex items-center gap-1\">\n <Kbd size=\"sm\">\n <CornerDownLeft class=\"w-2.5 h-2.5\" />\n </Kbd>\n <span>Select</span>\n </span>\n </div>\n\n <span class=\"inline-flex items-center gap-1\">\n <Kbd size=\"sm\">ESC</Kbd>\n <span>Close</span>\n </span>\n </>\n )}\n </div>\n );\n};",
|
|
22
23
|
"type": "registry:ui"
|
|
23
24
|
}
|
|
24
25
|
]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "context-menu",
|
|
3
|
+
"title": "Context Menu",
|
|
4
|
+
"description": "Displays a contextual popup menu triggered by right-clicking target areas, built on Kobalte primitives.",
|
|
5
|
+
"type": "registry:ui",
|
|
6
|
+
"dependencies": [
|
|
7
|
+
"clsx",
|
|
8
|
+
"tailwind-merge",
|
|
9
|
+
"@kobalte/core"
|
|
10
|
+
],
|
|
11
|
+
"registryDependencies": [
|
|
12
|
+
"separator",
|
|
13
|
+
"kbd",
|
|
14
|
+
"create-click-outside",
|
|
15
|
+
"scroll-area"
|
|
16
|
+
],
|
|
17
|
+
"files": [
|
|
18
|
+
{
|
|
19
|
+
"path": "ui/context-menu.tsx",
|
|
20
|
+
"content": "import { splitProps, type Component, type JSX, type ValidComponent } from \"solid-js\";\nimport * as ContextMenuPrimitive from \"@kobalte/core/context-menu\";\nimport type { PolymorphicProps } from \"@kobalte/core/polymorphic\";\nimport { createClickOutside } from \"@nikala-ui/hooks\";\nimport { ScrollArea } from \"./scroll-area\";\nimport { Separator } from \"./separator\";\nimport { Kbd } from \"./kbd\";\nimport { cn } from \"@/lib/cn\";\n\nexport type ContextMenuRootProps = ContextMenuPrimitive.ContextMenuRootProps;\n\nexport const ContextMenu: Component<ContextMenuRootProps> = (props) => {\n return <ContextMenuPrimitive.Root {...props} />;\n};\n\nexport const ContextMenuTrigger = ContextMenuPrimitive.Trigger;\nexport const ContextMenuGroup = ContextMenuPrimitive.Group;\nexport const ContextMenuSub = ContextMenuPrimitive.Sub;\n\nexport type ContextMenuSubTriggerProps<T extends ValidComponent = \"div\"> =\n ContextMenuPrimitive.ContextMenuSubTriggerProps<T> & {\n class?: string;\n children?: JSX.Element;\n inset?: boolean;\n };\n\nexport const ContextMenuSubTrigger = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, ContextMenuSubTriggerProps<T>>\n) => {\n const [local, rest] = splitProps(props as ContextMenuSubTriggerProps, [\"class\", \"children\", \"inset\"]);\n\n return (\n <ContextMenuPrimitive.SubTrigger\n class={cn(\n \"flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground text-foreground\",\n local.inset && \"pl-8\",\n local.class\n )}\n {...(rest as any)}\n >\n {local.children}\n <svg class=\"ml-auto h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5l7 7-7 7\" />\n </svg>\n </ContextMenuPrimitive.SubTrigger>\n );\n};\n\nexport type ContextMenuSubContentProps<T extends ValidComponent = \"div\"> =\n ContextMenuPrimitive.ContextMenuSubContentProps<T> & {\n class?: string;\n };\n\nexport const ContextMenuSubContent = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, ContextMenuSubContentProps<T>>\n) => {\n const [local, rest] = splitProps(props as ContextMenuSubContentProps, [\"class\"]);\n\n return (\n <ContextMenuPrimitive.Portal>\n <ContextMenuPrimitive.SubContent\n class={cn(\n \"z-50 min-w-[8rem] overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md transition-all animate-in fade-in-80 slide-in-from-top-1\",\n local.class\n )}\n {...(rest as any)}\n />\n </ContextMenuPrimitive.Portal>\n );\n};\n\nexport type ContextMenuContentProps<T extends ValidComponent = \"div\"> =\n ContextMenuPrimitive.ContextMenuContentProps<T> & {\n class?: string;\n ref?: (el: HTMLElement) => void;\n children?: JSX.Element;\n };\n\nexport const ContextMenuContent = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, ContextMenuContentProps<T>>\n) => {\n const [local, rest] = splitProps(props as ContextMenuContentProps, [\"class\", \"ref\", \"children\"]);\n let menuRef: HTMLElement | undefined;\n\n createClickOutside({\n target: () => menuRef,\n onInteractOutside: () => {\n // Automatic portal dismiss on outside click\n },\n });\n\n return (\n <ContextMenuPrimitive.Portal>\n <ContextMenuPrimitive.Content\n ref={(el) => {\n menuRef = el;\n if (typeof local.ref === \"function\") local.ref(el);\n }}\n class={cn(\n \"z-50 min-w-[8rem] rounded-md border border-border bg-popover text-popover-foreground shadow-md transition-all animate-in fade-in-80 slide-in-from-top-1 max-h-72 flex flex-col\",\n local.class\n )}\n {...(rest as any)}\n >\n <ScrollArea class=\"max-h-72 w-full rounded-[inherit]\">\n <div class=\"p-1\">\n {local.children}\n </div>\n </ScrollArea>\n </ContextMenuPrimitive.Content>\n </ContextMenuPrimitive.Portal>\n );\n};\n\nexport type ContextMenuItemProps<T extends ValidComponent = \"div\"> =\n ContextMenuPrimitive.ContextMenuItemProps<T> & {\n class?: string;\n inset?: boolean;\n };\n\nexport const ContextMenuItem = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, ContextMenuItemProps<T>>\n) => {\n const [local, rest] = splitProps(props as ContextMenuItemProps, [\"class\", \"inset\"]);\n\n return (\n <ContextMenuPrimitive.Item\n class={cn(\n \"relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 text-foreground\",\n local.inset && \"pl-8\",\n local.class\n )}\n {...(rest as any)}\n />\n );\n};\n\nexport type ContextMenuCheckboxItemProps<T extends ValidComponent = \"div\"> =\n ContextMenuPrimitive.ContextMenuCheckboxItemProps<T> & {\n class?: string;\n children?: JSX.Element;\n checked?: boolean;\n };\n\nexport const ContextMenuCheckboxItem = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, ContextMenuCheckboxItemProps<T>>\n) => {\n const [local, rest] = splitProps(props as ContextMenuCheckboxItemProps, [\"class\", \"children\", \"checked\"]);\n\n return (\n <ContextMenuPrimitive.CheckboxItem\n checked={local.checked}\n class={cn(\n \"relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 text-foreground\",\n local.class\n )}\n {...(rest as any)}\n >\n <span class=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n <ContextMenuPrimitive.ItemIndicator>\n <svg class=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M5 13l4 4L19 7\" />\n </svg>\n </ContextMenuPrimitive.ItemIndicator>\n </span>\n {local.children}\n </ContextMenuPrimitive.CheckboxItem>\n );\n};\n\nexport interface ContextMenuShortcutProps extends JSX.HTMLAttributes<HTMLSpanElement> {\n class?: string;\n children?: JSX.Element;\n}\n\nexport const ContextMenuShortcut: Component<ContextMenuShortcutProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <span class={cn(\"ml-auto text-xs tracking-widest text-muted-foreground\", local.class)} {...rest}>\n <Kbd size=\"sm\">{local.children}</Kbd>\n </span>\n );\n};\n\nexport interface ContextMenuSeparatorProps {\n class?: string;\n}\n\nexport const ContextMenuSeparator: Component<ContextMenuSeparatorProps> = (props) => {\n return <Separator class={cn(\"-mx-1 my-1 h-px bg-border\", props.class)} />;\n};\n",
|
|
21
|
+
"type": "registry:ui"
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"files": [
|
|
7
7
|
{
|
|
8
8
|
"path": "hooks/create-form.ts",
|
|
9
|
-
"content": "import { createSignal, createMemo, type Accessor } from \"solid-js\";\n\nexport type FormErrors<T> = Partial<Record<keyof T, string>>;\nexport type FormTouched<T> = Partial<Record<keyof T, boolean>>;\n\nexport interface CreateFormOptions<T extends Record<string, any>> {\n /** Initial form field values object */\n initialValues: T;\n /** Custom validation function returning error messages object */\n validate?: (values: T) => FormErrors<T> | Promise<FormErrors<T>>;\n /** Submit handler callback invoked when validation succeeds */\n onSubmit?: (values: T) => void | Promise<void>;\n}\n\nexport interface CreateFormReturn<T extends Record<string, any>> {\n /** Accessor for current form field values */\n values: Accessor<T>;\n /** Accessor for form field validation error messages */\n errors: Accessor<FormErrors<T>>;\n /** Accessor for form field touched states */\n touched: Accessor<FormTouched<T>>;\n /** Accessor indicating if form is currently submitting */\n isSubmitting: Accessor<boolean>;\n /** Accessor indicating if form has zero validation errors */\n isValid: Accessor<boolean>;\n /** Accessor indicating if form values differ from initial values */\n isDirty: Accessor<boolean>;\n /** Update specific form field value */\n setFieldValue: <K extends keyof T>(field: K, value: T[K]) => void;\n /** Set specific form field validation error */\n setFieldError: <K extends keyof T>(field: K, error: string | undefined) => void;\n /** Set specific form field touched state */\n setFieldTouched: <K extends keyof T>(field: K, isTouched?: boolean) => void;\n /** Input change event listener helper factory function */\n handleChange: <K extends keyof T>(field: K) => (e:
|
|
9
|
+
"content": "import { createSignal, createMemo, type Accessor } from \"solid-js\";\n\nexport type FormErrors<T> = Partial<Record<keyof T, string>>;\nexport type FormTouched<T> = Partial<Record<keyof T, boolean>>;\nexport type ValidateOn = \"change\" | \"blur\" | \"submit\";\n\nexport type FormInputEvent = Event & {\n currentTarget:\n | HTMLInputElement\n | HTMLTextAreaElement\n | HTMLSelectElement;\n};\n\nexport interface CreateFormOptions<T extends Record<string, any>> {\n /** Initial form field values object */\n initialValues: T;\n /** Custom validation function returning error messages object */\n validate?: (values: T) => FormErrors<T> | Promise<FormErrors<T>>;\n /** Event that triggers validation. Defaults to \"change\". */\n validateOn?: ValidateOn;\n /** Submit handler callback invoked when validation succeeds */\n onSubmit?: (values: T) => void | Promise<void>;\n}\n\nexport interface CreateFormReturn<T extends Record<string, any>> {\n /** Accessor for current form field values */\n values: Accessor<T>;\n /** Accessor for form field validation error messages */\n errors: Accessor<FormErrors<T>>;\n /** Accessor for form field touched states */\n touched: Accessor<FormTouched<T>>;\n /** Accessor indicating if form is currently submitting */\n isSubmitting: Accessor<boolean>;\n /** Accessor containing an exception thrown by onSubmit, if any */\n submitError: Accessor<unknown>;\n /** Accessor indicating if form has zero validation errors */\n isValid: Accessor<boolean>;\n /** Accessor indicating if form values differ from initial values */\n isDirty: Accessor<boolean>;\n /** Update specific form field value */\n setFieldValue: <K extends keyof T>(field: K, value: T[K]) => void;\n /** Set specific form field validation error */\n setFieldError: <K extends keyof T>(field: K, error: string | undefined) => void;\n /** Set specific form field touched state */\n setFieldTouched: <K extends keyof T>(field: K, isTouched?: boolean) => void;\n /** Input change event listener helper factory function */\n handleChange: <K extends keyof T>(field: K) => (e: FormInputEvent) => void;\n /** Input blur event listener helper factory function */\n handleBlur: <K extends keyof T>(field: K) => () => void;\n /** Form onSubmit event handler */\n handleSubmit: (e?: Event) => Promise<void>;\n /** Reset form values, errors, and touched states to initial values */\n resetForm: () => void;\n}\n\n/**\n * SolidJS reactive primitive for managing form field state, validation, errors, and submission.\n *\n * @param options Form configuration options including initialValues and validate function.\n */\nexport function createForm<T extends Record<string, any>>(\n options: CreateFormOptions<T>\n): CreateFormReturn<T> {\n const initialValues = { ...options.initialValues };\n\n const [values, setValues] = createSignal<T>({ ...initialValues });\n const [errors, setErrors] = createSignal<FormErrors<T>>({});\n const [touched, setTouched] = createSignal<FormTouched<T>>({});\n const [isSubmitting, setIsSubmitting] = createSignal(false);\n const [submitError, setSubmitError] = createSignal<unknown>();\n const validateOn = options.validateOn ?? \"change\";\n let validationSequence = 0;\n\n const isDirty = createMemo(() => {\n const current = values();\n return Object.keys(initialValues).some((key) => current[key] !== initialValues[key]);\n });\n\n const isValid = createMemo(() => {\n const errs = errors();\n return Object.keys(errs).length === 0;\n });\n\n const runValidation = async (currentValues: T): Promise<FormErrors<T>> => {\n const sequence = ++validationSequence;\n if (!options.validate) {\n setErrors(() => ({}));\n return {};\n }\n\n const result = await options.validate(currentValues);\n const newErrors = result || {};\n // Ignore stale async validations that finished after a newer request.\n if (sequence === validationSequence) {\n setErrors(() => newErrors);\n }\n return newErrors;\n };\n\n const setFieldValue = <K extends keyof T>(field: K, value: T[K]) => {\n const next = { ...values(), [field]: value };\n setValues(() => next);\n if (validateOn === \"change\") {\n void runValidation(next);\n }\n };\n\n const setFieldError = <K extends keyof T>(field: K, error: string | undefined) => {\n setErrors((prev) => {\n const next = { ...prev };\n if (error) next[field] = error;\n else delete next[field];\n return next;\n });\n };\n\n const setFieldTouched = <K extends keyof T>(field: K, isTouched = true) => {\n setTouched((prev) => ({ ...prev, [field]: isTouched }));\n };\n\n const handleChange = <K extends keyof T>(field: K) => {\n return (e: FormInputEvent) => {\n const target = e.currentTarget;\n let value: unknown = target.value;\n\n if (\n \"checked\" in target &&\n ((target as HTMLInputElement).type === \"checkbox\" ||\n (target as HTMLInputElement).type === \"radio\")\n ) {\n value = (target as HTMLInputElement).checked;\n } else if (\"selectedOptions\" in target && target.multiple) {\n value = Array.from(target.selectedOptions, (option) => option.value);\n }\n\n setFieldValue(field, value as T[K]);\n };\n };\n\n const handleBlur = <K extends keyof T>(field: K) => {\n return () => {\n setFieldTouched(field, true);\n if (validateOn === \"blur\") {\n void runValidation(values());\n }\n };\n };\n\n const handleSubmit = async (e?: Event) => {\n e?.preventDefault();\n setIsSubmitting(true);\n setSubmitError(undefined);\n\n // Mark all fields as touched on submit\n const allTouched = Object.keys(values()).reduce((acc, key) => {\n acc[key as keyof T] = true;\n return acc;\n }, {} as FormTouched<T>);\n setTouched(() => allTouched);\n\n try {\n const validationErrors = await runValidation(values());\n if (Object.keys(validationErrors).length === 0 && options.onSubmit) {\n try {\n await options.onSubmit(values());\n } catch (error) {\n setSubmitError(() => error);\n }\n }\n } finally {\n setIsSubmitting(false);\n }\n };\n\n const resetForm = () => {\n setValues(() => ({ ...initialValues }));\n setErrors(() => ({}));\n setTouched(() => ({}));\n setIsSubmitting(false);\n setSubmitError(undefined);\n validationSequence += 1;\n };\n\n return {\n values,\n errors,\n touched,\n isSubmitting,\n submitError,\n isValid,\n isDirty,\n setFieldValue,\n setFieldError,\n setFieldTouched,\n handleChange,\n handleBlur,\n handleSubmit,\n resetForm,\n };\n}\n",
|
|
10
10
|
"type": "registry:hook"
|
|
11
11
|
}
|
|
12
12
|
]
|
package/registry/dialog.json
CHANGED
|
@@ -8,10 +8,13 @@
|
|
|
8
8
|
"tailwind-merge",
|
|
9
9
|
"@kobalte/core"
|
|
10
10
|
],
|
|
11
|
+
"registryDependencies": [
|
|
12
|
+
"scroll-area"
|
|
13
|
+
],
|
|
11
14
|
"files": [
|
|
12
15
|
{
|
|
13
16
|
"path": "ui/dialog.tsx",
|
|
14
|
-
"content": "import { splitProps, type Component, type JSX, type ValidComponent, Show } from \"solid-js\";\nimport * as DialogPrimitive from \"@kobalte/core/dialog\";\nimport type { PolymorphicProps } from \"@kobalte/core/polymorphic\";\nimport { cn } from \"@/lib/cn\";\n\nexport type DialogRootProps = DialogPrimitive.DialogRootProps;\n\n/**\n * Root Dialog
|
|
17
|
+
"content": "import { splitProps, type Component, type JSX, type ValidComponent, Show } from \"solid-js\";\nimport * as DialogPrimitive from \"@kobalte/core/dialog\";\nimport type { PolymorphicProps } from \"@kobalte/core/polymorphic\";\nimport { ScrollArea } from \"./scroll-area\";\nimport { cn } from \"@/lib/cn\";\n\nexport type DialogRootProps = DialogPrimitive.DialogRootProps;\n\n/**\n * Root Dialog container managing state and context.\n */\nexport const Dialog: Component<DialogRootProps> = (props) => {\n return <DialogPrimitive.Root {...props} />;\n};\n\nexport const DialogTrigger = DialogPrimitive.Trigger;\nexport const DialogClose = DialogPrimitive.CloseButton;\n\nexport type DialogOverlayProps<T extends ValidComponent = \"div\"> =\n DialogPrimitive.DialogOverlayProps<T> & {\n /** Whether to apply background backdrop blur (default: true) */\n blur?: boolean;\n class?: string;\n };\n\n/**\n * Darkened background overlay behind open dialog.\n */\nexport const DialogOverlay = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, DialogOverlayProps<T>>\n) => {\n const [local, rest] = splitProps(props as DialogOverlayProps, [\"class\", \"blur\"]);\n\n return (\n <DialogPrimitive.Overlay\n class={cn(\n \"fixed inset-0 z-50 transition-all duration-200 data-expanded:animate-in data-closed:animate-out data-[closed]:fade-out-0 data-[expanded]:fade-in-0\",\n local.blur !== false ? \"bg-black/80 backdrop-blur-sm\" : \"bg-black/80\",\n local.class\n )}\n {...(rest as any)}\n />\n );\n};\n\nexport type DialogContentProps<T extends ValidComponent = \"div\"> =\n DialogPrimitive.DialogContentProps<T> & {\n /** Whether to show the top-right close (X) button (default: true) */\n showCloseButton?: boolean;\n /** Whether clicking outside closes the dialog (default: true) */\n closeOnOutsideClick?: boolean;\n /** Whether to apply background backdrop blur (default: true) */\n blur?: boolean;\n class?: string;\n children?: JSX.Element;\n };\n\n/**\n * Main dialog popup window housing header, content, and footer.\n */\nexport const DialogContent = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, DialogContentProps<T>>\n) => {\n const [local, rest] = splitProps(props as DialogContentProps, [\n \"class\",\n \"children\",\n \"showCloseButton\",\n \"closeOnOutsideClick\",\n \"blur\",\n \"onPointerDownOutside\",\n \"onInteractOutside\",\n ]);\n\n const handlePointerDownOutside = (e: Event) => {\n if (local.closeOnOutsideClick === false) {\n e.preventDefault();\n }\n if (typeof local.onPointerDownOutside === \"function\") {\n local.onPointerDownOutside(e as any);\n }\n };\n\n const handleInteractOutside = (e: Event) => {\n if (local.closeOnOutsideClick === false) {\n e.preventDefault();\n }\n if (typeof local.onInteractOutside === \"function\") {\n local.onInteractOutside(e as any);\n }\n };\n\n return (\n <DialogPrimitive.Portal>\n {/* Forward blur prop to DialogOverlay */}\n <DialogOverlay blur={local.blur} />\n <DialogPrimitive.Content\n onPointerDownOutside={handlePointerDownOutside}\n onInteractOutside={handleInteractOutside}\n class={cn(\n \"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] border border-border bg-popover shadow-lg duration-200 data-expanded:animate-in data-closed:animate-out data-[closed]:fade-out-0 data-[expanded]:fade-in-0 data-closed:zoom-out-95 data-expanded:zoom-in-95 data-[closed]:slide-out-to-left-1/2 data-[closed]:slide-out-to-top-[48%] data-[expanded]:slide-in-from-left-1/2 data-[expanded]:slide-in-from-top-[48%] sm:rounded-lg text-card-foreground overflow-hidden max-h-[80vh]\",\n local.class\n )}\n {...(rest as any)}\n >\n <ScrollArea class=\"max-h-[80vh] w-full\">\n <div class=\"p-6 space-y-4\">\n {local.children}\n </div>\n </ScrollArea>\n <Show when={local.showCloseButton !== false}>\n <DialogPrimitive.CloseButton class=\"absolute right-4 top-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 data-expanded:bg-accent data-expanded:text-muted-foreground cursor-pointer z-50\">\n <svg class=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n <span class=\"sr-only\">Close</span>\n </DialogPrimitive.CloseButton>\n </Show>\n </DialogPrimitive.Content>\n </DialogPrimitive.Portal>\n );\n};\n\nexport interface DialogHeaderProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const DialogHeader: Component<DialogHeaderProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"]);\n\n return (\n <div\n class={cn(\"flex flex-col space-y-1.5 text-center sm:text-left\", local.class)}\n {...rest}\n />\n );\n};\n\nexport interface DialogFooterProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const DialogFooter: Component<DialogFooterProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"]);\n\n return (\n <div\n class={cn(\n \"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2 pt-4\",\n local.class\n )}\n {...rest}\n />\n );\n};\n\nexport interface DialogTitleProps {\n class?: string;\n children?: JSX.Element;\n}\n\nexport const DialogTitle: Component<DialogTitleProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <DialogPrimitive.Title\n class={cn(\"text-lg font-semibold leading-none tracking-tight text-foreground\", local.class)}\n {...rest}\n >\n {local.children}\n </DialogPrimitive.Title>\n );\n};\n\nexport interface DialogDescriptionProps {\n class?: string;\n children?: JSX.Element;\n}\n\nexport const DialogDescription: Component<DialogDescriptionProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <DialogPrimitive.Description\n class={cn(\"text-sm text-muted-foreground\", local.class)}\n {...rest}\n >\n {local.children}\n </DialogPrimitive.Description>\n );\n};",
|
|
15
18
|
"type": "registry:ui"
|
|
16
19
|
}
|
|
17
20
|
]
|
|
@@ -8,10 +8,13 @@
|
|
|
8
8
|
"tailwind-merge",
|
|
9
9
|
"@kobalte/core"
|
|
10
10
|
],
|
|
11
|
+
"registryDependencies": [
|
|
12
|
+
"scroll-area"
|
|
13
|
+
],
|
|
11
14
|
"files": [
|
|
12
15
|
{
|
|
13
16
|
"path": "ui/dropdown-menu.tsx",
|
|
14
|
-
"content": "import { splitProps, type Component, type JSX, type ValidComponent } from \"solid-js\";\nimport * as DropdownMenuPrimitive from \"@kobalte/core/dropdown-menu\";\nimport type { PolymorphicProps } from \"@kobalte/core/polymorphic\";\nimport { createClickOutside } from \"@nikala-ui/hooks\";\nimport { cn } from \"@/lib/cn\";\n\nexport type DropdownMenuRootProps = Omit<\n DropdownMenuPrimitive.DropdownMenuRootProps,\n \"placement\"\n> & {\n placement?:\n | \"top\"\n | \"top-start\"\n | \"top-end\"\n | \"right\"\n | \"right-start\"\n | \"right-end\"\n | \"bottom\"\n | \"bottom-start\"\n | \"bottom-end\"\n | \"left\"\n | \"left-start\"\n | \"left-end\";\n};\n\nexport const DropdownMenu: Component<DropdownMenuRootProps> = (props) => {\n return <DropdownMenuPrimitive.Root {...props} />;\n};\n\nexport const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;\nexport const DropdownMenuGroup = DropdownMenuPrimitive.Group;\nexport const DropdownMenuSub = DropdownMenuPrimitive.Sub;\nexport const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;\n\nexport type DropdownMenuSubTriggerProps<T extends ValidComponent = \"div\"> =\n DropdownMenuPrimitive.DropdownMenuSubTriggerProps<T> & {\n class?: string;\n children?: JSX.Element;\n inset?: boolean;\n };\n\nexport const DropdownMenuSubTrigger = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, DropdownMenuSubTriggerProps<T>>\n) => {\n const [local, rest] = splitProps(props as DropdownMenuSubTriggerProps, [\"class\", \"children\", \"inset\"]);\n\n return (\n <DropdownMenuPrimitive.SubTrigger\n class={cn(\n \"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent text-foreground\",\n local.inset && \"pl-8\",\n local.class\n )}\n {...(rest as any)}\n >\n {local.children}\n <svg class=\"ml-auto h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5l7 7-7 7\" />\n </svg>\n </DropdownMenuPrimitive.SubTrigger>\n );\n};\n\nexport type DropdownMenuSubContentProps<T extends ValidComponent = \"div\"> =\n DropdownMenuPrimitive.DropdownMenuSubContentProps<T> & {\n class?: string;\n };\n\nexport const DropdownMenuSubContent = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, DropdownMenuSubContentProps<T>>\n) => {\n const [local, rest] = splitProps(props as DropdownMenuSubContentProps, [\"class\"]);\n\n return (\n <DropdownMenuPrimitive.Portal>\n <DropdownMenuPrimitive.SubContent\n class={cn(\n \"z-50 min-w-32 overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-lg data-expanded:animate-in data-closed:animate-out data-[closed]:fade-out-0 data-[expanded]:fade-in-0\",\n local.class\n )}\n {...(rest as any)}\n />\n </DropdownMenuPrimitive.Portal>\n );\n};\n\nexport type DropdownMenuContentProps<T extends ValidComponent = \"div\"> =\n DropdownMenuPrimitive.DropdownMenuContentProps<T> & {\n class?: string;\n };\n\nexport const DropdownMenuContent = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, DropdownMenuContentProps<T>>\n) => {\n const [local, rest] = splitProps(props as DropdownMenuContentProps, [\"class\"]);\n let contentRef: HTMLElement | undefined;\n\n createClickOutside({\n target: () => contentRef,\n onInteractOutside: (e) => {\n if (typeof (props as any).onInteractOutside === \"function\") {\n (props as any).onInteractOutside(e);\n }\n },\n });\n\n return (\n <DropdownMenuPrimitive.Portal>\n <DropdownMenuPrimitive.Content\n ref={(el) => {\n contentRef = el;\n if (typeof (props as any).ref === \"function\") (props as any).ref(el);\n }}\n class={cn(\n \"z-50 min-w-32
|
|
17
|
+
"content": "import { splitProps, type Component, type JSX, type ValidComponent } from \"solid-js\";\nimport * as DropdownMenuPrimitive from \"@kobalte/core/dropdown-menu\";\nimport type { PolymorphicProps } from \"@kobalte/core/polymorphic\";\nimport { createClickOutside } from \"@nikala-ui/hooks\";\nimport { ScrollArea } from \"./scroll-area\";\nimport { cn } from \"@/lib/cn\";\n\nexport type DropdownMenuRootProps = Omit<\n DropdownMenuPrimitive.DropdownMenuRootProps,\n \"placement\"\n> & {\n placement?:\n | \"top\"\n | \"top-start\"\n | \"top-end\"\n | \"right\"\n | \"right-start\"\n | \"right-end\"\n | \"bottom\"\n | \"bottom-start\"\n | \"bottom-end\"\n | \"left\"\n | \"left-start\"\n | \"left-end\";\n};\n\nexport const DropdownMenu: Component<DropdownMenuRootProps> = (props) => {\n return <DropdownMenuPrimitive.Root {...props} />;\n};\n\nexport const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;\nexport const DropdownMenuGroup = DropdownMenuPrimitive.Group;\nexport const DropdownMenuSub = DropdownMenuPrimitive.Sub;\nexport const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;\n\nexport type DropdownMenuSubTriggerProps<T extends ValidComponent = \"div\"> =\n DropdownMenuPrimitive.DropdownMenuSubTriggerProps<T> & {\n class?: string;\n children?: JSX.Element;\n inset?: boolean;\n };\n\nexport const DropdownMenuSubTrigger = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, DropdownMenuSubTriggerProps<T>>\n) => {\n const [local, rest] = splitProps(props as DropdownMenuSubTriggerProps, [\"class\", \"children\", \"inset\"]);\n\n return (\n <DropdownMenuPrimitive.SubTrigger\n class={cn(\n \"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent text-foreground\",\n local.inset && \"pl-8\",\n local.class\n )}\n {...(rest as any)}\n >\n {local.children}\n <svg class=\"ml-auto h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5l7 7-7 7\" />\n </svg>\n </DropdownMenuPrimitive.SubTrigger>\n );\n};\n\nexport type DropdownMenuSubContentProps<T extends ValidComponent = \"div\"> =\n DropdownMenuPrimitive.DropdownMenuSubContentProps<T> & {\n class?: string;\n };\n\nexport const DropdownMenuSubContent = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, DropdownMenuSubContentProps<T>>\n) => {\n const [local, rest] = splitProps(props as DropdownMenuSubContentProps, [\"class\"]);\n\n return (\n <DropdownMenuPrimitive.Portal>\n <DropdownMenuPrimitive.SubContent\n class={cn(\n \"z-50 min-w-32 overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-lg data-expanded:animate-in data-closed:animate-out data-[closed]:fade-out-0 data-[expanded]:fade-in-0\",\n local.class\n )}\n {...(rest as any)}\n />\n </DropdownMenuPrimitive.Portal>\n );\n};\n\nexport type DropdownMenuContentProps<T extends ValidComponent = \"div\"> =\n DropdownMenuPrimitive.DropdownMenuContentProps<T> & {\n class?: string;\n children?: JSX.Element;\n };\n\nexport const DropdownMenuContent = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, DropdownMenuContentProps<T>>\n) => {\n const [local, rest] = splitProps(props as DropdownMenuContentProps, [\"class\", \"children\"]);\n let contentRef: HTMLElement | undefined;\n\n createClickOutside({\n target: () => contentRef,\n onInteractOutside: (e) => {\n if (typeof (props as any).onInteractOutside === \"function\") {\n (props as any).onInteractOutside(e);\n }\n },\n });\n\n return (\n <DropdownMenuPrimitive.Portal>\n <DropdownMenuPrimitive.Content\n ref={(el) => {\n contentRef = el;\n if (typeof (props as any).ref === \"function\") (props as any).ref(el);\n }}\n {...(rest as any)}\n sideOffset={(rest as any).sideOffset ?? 8}\n class={cn(\n \"z-50 min-w-32 overflow-hidden rounded-md border border-border bg-popover text-popover-foreground shadow-md animate-in fade-in-80 max-h-72 flex flex-col mt-1\",\n local.class\n )}\n >\n <ScrollArea class=\"max-h-72 w-full rounded-[inherit]\">\n <div class=\"p-1\">\n {local.children}\n </div>\n </ScrollArea>\n </DropdownMenuPrimitive.Content>\n </DropdownMenuPrimitive.Portal>\n );\n};\n\nexport type DropdownMenuItemProps<T extends ValidComponent = \"div\"> =\n DropdownMenuPrimitive.DropdownMenuItemProps<T> & {\n class?: string;\n inset?: boolean;\n };\n\nexport const DropdownMenuItem = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, DropdownMenuItemProps<T>>\n) => {\n const [local, rest] = splitProps(props as DropdownMenuItemProps, [\"class\", \"inset\"]);\n\n return (\n <DropdownMenuPrimitive.Item\n class={cn(\n \"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 text-foreground\",\n local.inset && \"pl-8\",\n local.class\n )}\n {...(rest as any)}\n />\n );\n};\n\nexport type DropdownMenuCheckboxItemProps<T extends ValidComponent = \"div\"> =\n DropdownMenuPrimitive.DropdownMenuCheckboxItemProps<T> & {\n class?: string;\n children?: JSX.Element;\n checked?: boolean;\n };\n\nexport const DropdownMenuCheckboxItem = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, DropdownMenuCheckboxItemProps<T>>\n) => {\n const [local, rest] = splitProps(props as DropdownMenuCheckboxItemProps, [\"class\", \"children\", \"checked\"]);\n\n return (\n <DropdownMenuPrimitive.CheckboxItem\n checked={local.checked}\n class={cn(\n \"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 text-foreground\",\n local.class\n )}\n {...(rest as any)}\n >\n <span class=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n <DropdownMenuPrimitive.ItemIndicator>\n <svg class=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M5 13l4 4L19 7\" />\n </svg>\n </DropdownMenuPrimitive.ItemIndicator>\n </span>\n {local.children}\n </DropdownMenuPrimitive.CheckboxItem>\n );\n};\n\nexport type DropdownMenuRadioItemProps<T extends ValidComponent = \"div\"> =\n DropdownMenuPrimitive.DropdownMenuRadioItemProps<T> & {\n class?: string;\n children?: JSX.Element;\n };\n\nexport const DropdownMenuRadioItem = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, DropdownMenuRadioItemProps<T>>\n) => {\n const [local, rest] = splitProps(props as DropdownMenuRadioItemProps, [\"class\", \"children\"]);\n\n return (\n <DropdownMenuPrimitive.RadioItem\n class={cn(\n \"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 text-foreground\",\n local.class\n )}\n {...(rest as any)}\n >\n <span class=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n <DropdownMenuPrimitive.ItemIndicator>\n <svg class=\"h-2 w-2 fill-current\" viewBox=\"0 0 8 8\">\n <circle cx=\"4\" cy=\"4\" r=\"3\" />\n </svg>\n </DropdownMenuPrimitive.ItemIndicator>\n </span>\n {local.children}\n </DropdownMenuPrimitive.RadioItem>\n );\n};\n\nexport interface DropdownMenuLabelProps {\n class?: string;\n inset?: boolean;\n children?: JSX.Element;\n}\n\nexport const DropdownMenuLabel: Component<DropdownMenuLabelProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"inset\", \"children\"]);\n\n return (\n <DropdownMenuPrimitive.GroupLabel\n class={cn(\"px-2 py-1.5 text-sm font-semibold text-foreground\", local.inset && \"pl-8\", local.class)}\n {...rest}\n >\n {local.children}\n </DropdownMenuPrimitive.GroupLabel>\n );\n};\n\nexport const DropdownMenuSeparator: Component<{ class?: string }> = (props) => {\n return (\n <DropdownMenuPrimitive.Separator\n class={cn(\"-mx-1 my-1 h-px bg-border\", props.class)}\n />\n );\n};\n\nexport interface DropdownMenuShortcutProps {\n class?: string;\n children?: JSX.Element;\n}\n\nexport const DropdownMenuShortcut: Component<DropdownMenuShortcutProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <span class={cn(\"ml-auto text-xs tracking-widest text-muted-foreground\", local.class)} {...rest}>\n {local.children}\n </span>\n );\n};",
|
|
15
18
|
"type": "registry:ui"
|
|
16
19
|
}
|
|
17
20
|
]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "empty",
|
|
3
|
+
"title": "Empty",
|
|
4
|
+
"description": "A compound empty-state layout for collections, search results, and initial application states.",
|
|
5
|
+
"type": "registry:ui",
|
|
6
|
+
"dependencies": [
|
|
7
|
+
"clsx",
|
|
8
|
+
"tailwind-merge"
|
|
9
|
+
],
|
|
10
|
+
"files": [
|
|
11
|
+
{
|
|
12
|
+
"path": "ui/empty.tsx",
|
|
13
|
+
"content": "import { splitProps, type Component, type JSX } from \"solid-js\";\nimport { cn } from \"@/lib/cn\";\n\nexport interface EmptyProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\n/** A centered layout for empty collections, search results, and initial states. */\nexport const Empty: Component<EmptyProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"]);\n\n return (\n <div\n class={cn(\n \"flex min-h-64 w-full flex-col items-center justify-center rounded-lg border border-dashed border-border bg-card/30 p-6 text-center\",\n local.class\n )}\n {...rest}\n />\n );\n};\n\nexport interface EmptyIconProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const EmptyIcon: Component<EmptyIconProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"]);\n\n return (\n <div\n aria-hidden=\"true\"\n class={cn(\n \"mb-4 flex size-12 items-center justify-center rounded-lg bg-muted text-muted-foreground [&_svg]:size-6\",\n local.class\n )}\n {...rest}\n />\n );\n};\n\nexport interface EmptyTitleProps extends JSX.HTMLAttributes<HTMLHeadingElement> {\n class?: string;\n}\n\nexport const EmptyTitle: Component<EmptyTitleProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"]);\n\n return (\n <h3\n class={cn(\"text-lg font-semibold tracking-tight text-foreground\", local.class)}\n {...rest}\n />\n );\n};\n\nexport interface EmptyDescriptionProps\n extends JSX.HTMLAttributes<HTMLParagraphElement> {\n class?: string;\n}\n\nexport const EmptyDescription: Component<EmptyDescriptionProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"]);\n\n return (\n <p\n class={cn(\"mt-1 max-w-sm text-sm text-muted-foreground\", local.class)}\n {...rest}\n />\n );\n};\n\nexport interface EmptyActionProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const EmptyAction: Component<EmptyActionProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"]);\n\n return (\n <div class={cn(\"mt-5 flex items-center gap-2\", local.class)} {...rest} />\n );\n};\n",
|
|
14
|
+
"type": "registry:ui"
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "field",
|
|
3
|
+
"title": "Field",
|
|
4
|
+
"description": "A consistent form field layout for labels, descriptions, and validation errors.",
|
|
5
|
+
"type": "registry:ui",
|
|
6
|
+
"dependencies": [
|
|
7
|
+
"clsx",
|
|
8
|
+
"tailwind-merge"
|
|
9
|
+
],
|
|
10
|
+
"registryDependencies": [
|
|
11
|
+
"label"
|
|
12
|
+
],
|
|
13
|
+
"files": [
|
|
14
|
+
{
|
|
15
|
+
"path": "ui/field.tsx",
|
|
16
|
+
"content": "import { splitProps, type Component, type JSX } from \"solid-js\";\nimport { cn } from \"@/lib/cn\";\nimport { Label, type LabelProps } from \"./label\";\n\nexport interface FieldProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\n/** A consistent layout wrapper for labels, controls, descriptions, and errors. */\nexport const Field: Component<FieldProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <div class={cn(\"grid w-full gap-1.5\", local.class)} {...rest}>\n {local.children}\n </div>\n );\n};\n\nexport interface FieldLabelProps extends LabelProps {\n class?: string;\n children?: JSX.Element;\n}\n\nexport const FieldLabel: Component<FieldLabelProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <Label class={cn(\"text-sm font-medium\", local.class)} {...rest}>\n {local.children}\n </Label>\n );\n};\n\nexport interface FieldDescriptionProps\n extends JSX.HTMLAttributes<HTMLParagraphElement> {\n class?: string;\n}\n\nexport const FieldDescription: Component<FieldDescriptionProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <p class={cn(\"text-sm text-muted-foreground\", local.class)} {...rest}>\n {local.children}\n </p>\n );\n};\n\nexport interface FieldErrorProps\n extends JSX.HTMLAttributes<HTMLParagraphElement> {\n class?: string;\n}\n\nexport const FieldError: Component<FieldErrorProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <p\n role=\"alert\"\n class={cn(\"text-sm font-medium text-destructive\", local.class)}\n {...rest}\n >\n {local.children}\n </p>\n );\n};\n",
|
|
17
|
+
"type": "registry:ui"
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "form-message",
|
|
3
|
+
"title": "Form Message",
|
|
4
|
+
"description": "Validation message helper connected to createForm errors and touched state.",
|
|
5
|
+
"type": "registry:ui",
|
|
6
|
+
"registryDependencies": [
|
|
7
|
+
"field"
|
|
8
|
+
],
|
|
9
|
+
"files": [
|
|
10
|
+
{
|
|
11
|
+
"path": "ui/form-message.tsx",
|
|
12
|
+
"content": "import { Show, splitProps, type JSX } from \"solid-js\";\nimport type { CreateFormReturn } from \"@nikala-ui/hooks\";\nimport { FieldError, type FieldErrorProps } from \"./field\";\n\ntype FormState<T extends Record<string, any>> = Pick<\n CreateFormReturn<T>,\n \"errors\" | \"touched\"\n>;\n\nexport interface FormMessageProps<\n T extends Record<string, any>,\n K extends keyof T,\n> extends Omit<FieldErrorProps, \"children\"> {\n form: FormState<T>;\n name: K;\n /** Show the message even before the field has been touched. */\n showUntouched?: boolean;\n}\n\n/** Displays a field's validation error from createForm when it should be visible. */\nexport function FormMessage<\n T extends Record<string, any>,\n K extends keyof T,\n>(props: FormMessageProps<T, K>): JSX.Element {\n const [local, rest] = splitProps(props, [\"form\", \"name\", \"showUntouched\", \"class\"]);\n const error = () => local.form.errors()[local.name] as string | undefined;\n const touched = () => local.form.touched()[local.name] === true;\n\n return (\n <Show when={error() && (local.showUntouched || touched())}>\n <FieldError class={local.class} {...rest}>\n {error()}\n </FieldError>\n </Show>\n );\n}\n",
|
|
13
|
+
"type": "registry:ui"
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "form",
|
|
3
|
+
"title": "Form",
|
|
4
|
+
"description": "A semantic form layout wrapper designed to work with the createForm hook.",
|
|
5
|
+
"type": "registry:ui",
|
|
6
|
+
"dependencies": [
|
|
7
|
+
"clsx",
|
|
8
|
+
"tailwind-merge"
|
|
9
|
+
],
|
|
10
|
+
"files": [
|
|
11
|
+
{
|
|
12
|
+
"path": "ui/form.tsx",
|
|
13
|
+
"content": "import { splitProps, type Component, type JSX } from \"solid-js\";\nimport { cn } from \"@/lib/cn\";\n\nexport interface FormProps extends JSX.FormHTMLAttributes<HTMLFormElement> {\n /** Indicates that the form is currently processing a submission. */\n loading?: boolean;\n class?: string;\n}\n\n/** A semantic form layout wrapper designed to work with the createForm hook. */\nexport const Form: Component<FormProps> = (props) => {\n const [local, rest] = splitProps(props, [\"loading\", \"class\"]);\n\n return (\n <form\n aria-busy={local.loading ? \"true\" : undefined}\n class={cn(\"w-full space-y-5\", local.class)}\n {...rest}\n />\n );\n};\n",
|
|
14
|
+
"type": "registry:ui"
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "icon-button",
|
|
3
|
+
"title": "Icon Button",
|
|
4
|
+
"description": "An accessible square button intended for a single icon action.",
|
|
5
|
+
"type": "registry:ui",
|
|
6
|
+
"dependencies": [
|
|
7
|
+
"clsx",
|
|
8
|
+
"tailwind-merge"
|
|
9
|
+
],
|
|
10
|
+
"registryDependencies": [
|
|
11
|
+
"button"
|
|
12
|
+
],
|
|
13
|
+
"files": [
|
|
14
|
+
{
|
|
15
|
+
"path": "ui/icon-button.tsx",
|
|
16
|
+
"content": "import { splitProps, type Component, type JSX } from \"solid-js\";\nimport { Button, type ButtonProps } from \"./button\";\nimport { cn } from \"@/lib/cn\";\n\nexport interface IconButtonProps\n extends Omit<ButtonProps, \"size\" | \"children\"> {\n /** Accessible label for the icon-only button. */\n label: string;\n size?: \"sm\" | \"default\" | \"lg\";\n children?: JSX.Element;\n}\n\n/** An accessible square button intended for a single icon action. */\nexport const IconButton: Component<IconButtonProps> = (props) => {\n const [local, rest] = splitProps(props, [\"label\", \"size\", \"class\", \"children\"]);\n\n return (\n <Button\n size=\"icon\"\n aria-label={local.label}\n class={cn(\n {\n \"size-8\": local.size === \"sm\",\n \"size-9\": !local.size || local.size === \"default\",\n \"size-10\": local.size === \"lg\",\n },\n local.class\n )}\n {...rest}\n >\n {local.children}\n </Button>\n );\n};\n",
|
|
17
|
+
"type": "registry:ui"
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|