@nikala-ui/core 0.9.11 → 0.9.12
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/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/dialog.json +4 -1
- package/registry/dropdown-menu.json +4 -1
- package/registry/index.json +111 -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/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/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/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/theme-toggle.tsx +6 -4
- package/src/registry/components/ui/toggle.tsx +101 -0
- package/src/registry/metadata.ts +45 -1
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
|
+
}
|
|
@@ -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
|
+
}
|
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
|
]
|
package/registry/index.json
CHANGED
|
@@ -21,6 +21,16 @@
|
|
|
21
21
|
"class-variance-authority"
|
|
22
22
|
]
|
|
23
23
|
},
|
|
24
|
+
{
|
|
25
|
+
"name": "aspect-ratio",
|
|
26
|
+
"title": "Aspect Ratio",
|
|
27
|
+
"description": "Displays content within a specific aspect ratio using CSS aspect-ratio while preventing layout shifts.",
|
|
28
|
+
"type": "registry:ui",
|
|
29
|
+
"dependencies": [
|
|
30
|
+
"clsx",
|
|
31
|
+
"tailwind-merge"
|
|
32
|
+
]
|
|
33
|
+
},
|
|
24
34
|
{
|
|
25
35
|
"name": "avatar",
|
|
26
36
|
"title": "Avatar",
|
|
@@ -95,6 +105,17 @@
|
|
|
95
105
|
"tailwind-merge"
|
|
96
106
|
]
|
|
97
107
|
},
|
|
108
|
+
{
|
|
109
|
+
"name": "collapsible",
|
|
110
|
+
"title": "Collapsible",
|
|
111
|
+
"description": "An interactive component that expands and collapses content panels with smooth height animations, built on Kobalte primitives.",
|
|
112
|
+
"type": "registry:ui",
|
|
113
|
+
"dependencies": [
|
|
114
|
+
"clsx",
|
|
115
|
+
"tailwind-merge",
|
|
116
|
+
"@kobalte/core"
|
|
117
|
+
]
|
|
118
|
+
},
|
|
98
119
|
{
|
|
99
120
|
"name": "combobox",
|
|
100
121
|
"title": "Combobox",
|
|
@@ -104,6 +125,9 @@
|
|
|
104
125
|
"clsx",
|
|
105
126
|
"tailwind-merge",
|
|
106
127
|
"@kobalte/core"
|
|
128
|
+
],
|
|
129
|
+
"registryDependencies": [
|
|
130
|
+
"scroll-area"
|
|
107
131
|
]
|
|
108
132
|
},
|
|
109
133
|
{
|
|
@@ -121,7 +145,25 @@
|
|
|
121
145
|
"registryDependencies": [
|
|
122
146
|
"kbd",
|
|
123
147
|
"input-group",
|
|
124
|
-
"list"
|
|
148
|
+
"list",
|
|
149
|
+
"scroll-area"
|
|
150
|
+
]
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
"name": "context-menu",
|
|
154
|
+
"title": "Context Menu",
|
|
155
|
+
"description": "Displays a contextual popup menu triggered by right-clicking target areas, built on Kobalte primitives.",
|
|
156
|
+
"type": "registry:ui",
|
|
157
|
+
"dependencies": [
|
|
158
|
+
"clsx",
|
|
159
|
+
"tailwind-merge",
|
|
160
|
+
"@kobalte/core"
|
|
161
|
+
],
|
|
162
|
+
"registryDependencies": [
|
|
163
|
+
"separator",
|
|
164
|
+
"kbd",
|
|
165
|
+
"create-click-outside",
|
|
166
|
+
"scroll-area"
|
|
125
167
|
]
|
|
126
168
|
},
|
|
127
169
|
{
|
|
@@ -133,6 +175,9 @@
|
|
|
133
175
|
"clsx",
|
|
134
176
|
"tailwind-merge",
|
|
135
177
|
"@kobalte/core"
|
|
178
|
+
],
|
|
179
|
+
"registryDependencies": [
|
|
180
|
+
"scroll-area"
|
|
136
181
|
]
|
|
137
182
|
},
|
|
138
183
|
{
|
|
@@ -144,6 +189,9 @@
|
|
|
144
189
|
"clsx",
|
|
145
190
|
"tailwind-merge",
|
|
146
191
|
"@kobalte/core"
|
|
192
|
+
],
|
|
193
|
+
"registryDependencies": [
|
|
194
|
+
"scroll-area"
|
|
147
195
|
]
|
|
148
196
|
},
|
|
149
197
|
{
|
|
@@ -226,6 +274,23 @@
|
|
|
226
274
|
"tailwind-merge"
|
|
227
275
|
]
|
|
228
276
|
},
|
|
277
|
+
{
|
|
278
|
+
"name": "number-input",
|
|
279
|
+
"title": "Number Input",
|
|
280
|
+
"description": "A numeric stepper input component supporting min, max, step, negative values, and long-press auto-repeat, built on Kobalte primitives.",
|
|
281
|
+
"type": "registry:ui",
|
|
282
|
+
"dependencies": [
|
|
283
|
+
"clsx",
|
|
284
|
+
"tailwind-merge",
|
|
285
|
+
"@kobalte/core",
|
|
286
|
+
"lucide-solid"
|
|
287
|
+
],
|
|
288
|
+
"registryDependencies": [
|
|
289
|
+
"input",
|
|
290
|
+
"button",
|
|
291
|
+
"create-long-press"
|
|
292
|
+
]
|
|
293
|
+
},
|
|
229
294
|
{
|
|
230
295
|
"name": "pin-input",
|
|
231
296
|
"title": "Pin Input",
|
|
@@ -270,6 +335,34 @@
|
|
|
270
335
|
"@kobalte/core"
|
|
271
336
|
]
|
|
272
337
|
},
|
|
338
|
+
{
|
|
339
|
+
"name": "resizable",
|
|
340
|
+
"title": "Resizable",
|
|
341
|
+
"description": "Accessible resizable panel layout component supporting drag-to-resize handles.",
|
|
342
|
+
"type": "registry:ui",
|
|
343
|
+
"dependencies": [
|
|
344
|
+
"clsx",
|
|
345
|
+
"tailwind-merge",
|
|
346
|
+
"lucide-solid"
|
|
347
|
+
],
|
|
348
|
+
"registryDependencies": [
|
|
349
|
+
"create-resize-observer"
|
|
350
|
+
]
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
"name": "scroll-area",
|
|
354
|
+
"title": "Scroll Area",
|
|
355
|
+
"description": "Augments native scroll functionality with custom styled scrollbars and reactive scroll tracking.",
|
|
356
|
+
"type": "registry:ui",
|
|
357
|
+
"dependencies": [
|
|
358
|
+
"clsx",
|
|
359
|
+
"tailwind-merge"
|
|
360
|
+
],
|
|
361
|
+
"registryDependencies": [
|
|
362
|
+
"create-scroll-position",
|
|
363
|
+
"create-resize-observer"
|
|
364
|
+
]
|
|
365
|
+
},
|
|
273
366
|
{
|
|
274
367
|
"name": "select",
|
|
275
368
|
"title": "Select",
|
|
@@ -279,6 +372,9 @@
|
|
|
279
372
|
"clsx",
|
|
280
373
|
"tailwind-merge",
|
|
281
374
|
"@kobalte/core"
|
|
375
|
+
],
|
|
376
|
+
"registryDependencies": [
|
|
377
|
+
"scroll-area"
|
|
282
378
|
]
|
|
283
379
|
},
|
|
284
380
|
{
|
|
@@ -301,6 +397,9 @@
|
|
|
301
397
|
"tailwind-merge",
|
|
302
398
|
"class-variance-authority",
|
|
303
399
|
"@kobalte/core"
|
|
400
|
+
],
|
|
401
|
+
"registryDependencies": [
|
|
402
|
+
"scroll-area"
|
|
304
403
|
]
|
|
305
404
|
},
|
|
306
405
|
{
|
|
@@ -383,6 +482,17 @@
|
|
|
383
482
|
"@kobalte/core"
|
|
384
483
|
]
|
|
385
484
|
},
|
|
485
|
+
{
|
|
486
|
+
"name": "toggle",
|
|
487
|
+
"title": "Toggle",
|
|
488
|
+
"description": "A two-state interactive button component built on Kobalte primitives.",
|
|
489
|
+
"type": "registry:ui",
|
|
490
|
+
"dependencies": [
|
|
491
|
+
"clsx",
|
|
492
|
+
"tailwind-merge",
|
|
493
|
+
"@kobalte/core"
|
|
494
|
+
]
|
|
495
|
+
},
|
|
386
496
|
{
|
|
387
497
|
"name": "tooltip",
|
|
388
498
|
"title": "Tooltip",
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "number-input",
|
|
3
|
+
"title": "Number Input",
|
|
4
|
+
"description": "A numeric stepper input component supporting min, max, step, negative values, and long-press auto-repeat, built on Kobalte primitives.",
|
|
5
|
+
"type": "registry:ui",
|
|
6
|
+
"dependencies": [
|
|
7
|
+
"clsx",
|
|
8
|
+
"tailwind-merge",
|
|
9
|
+
"@kobalte/core",
|
|
10
|
+
"lucide-solid"
|
|
11
|
+
],
|
|
12
|
+
"registryDependencies": [
|
|
13
|
+
"input",
|
|
14
|
+
"button",
|
|
15
|
+
"create-long-press"
|
|
16
|
+
],
|
|
17
|
+
"files": [
|
|
18
|
+
{
|
|
19
|
+
"path": "ui/number-input.tsx",
|
|
20
|
+
"content": "import { createSignal, onCleanup, splitProps, type Component, type JSX } from \"solid-js\";\nimport { NumberField as KobalteNumberField } from \"@kobalte/core/number-field\";\nimport { Plus, Minus } from \"lucide-solid\";\nimport { Input } from \"./input\";\nimport { Button } from \"./button\";\nimport { createLongPress } from \"@nikala-ui/hooks\";\nimport { cn } from \"@/lib/cn\";\n\nexport interface NumberInputProps {\n value?: number;\n defaultValue?: number;\n onValueChange?: (value: number) => void;\n minValue?: number;\n maxValue?: number;\n step?: number;\n allowNegative?: boolean;\n disabled?: boolean;\n readOnly?: boolean;\n class?: string;\n id?: string;\n}\n\nexport const NumberInput: Component<NumberInputProps> = (props) => {\n const [local, rest] = splitProps(props, [\n \"value\",\n \"defaultValue\",\n \"onValueChange\",\n \"minValue\",\n \"maxValue\",\n \"step\",\n \"allowNegative\",\n \"disabled\",\n \"readOnly\",\n \"class\",\n \"id\",\n ]);\n\n const [internalVal, setInternalVal] = createSignal<number>(\n local.value !== undefined\n ? local.value\n : local.defaultValue !== undefined\n ? local.defaultValue\n : 0\n );\n\n let autoRepeatInterval: ReturnType<typeof setInterval> | undefined;\n\n const currentVal = () => (local.value !== undefined ? local.value : internalVal());\n\n const effectiveMin = () => {\n if (local.minValue !== undefined) return local.minValue;\n return local.allowNegative ? -Infinity : 0;\n };\n\n const effectiveMax = () => {\n if (local.maxValue !== undefined) return local.maxValue;\n return Infinity;\n };\n\n const handleValueChange = (val: number) => {\n if (isNaN(val)) return;\n const clamped = Math.max(effectiveMin(), Math.min(effectiveMax(), val));\n setInternalVal(clamped);\n local.onValueChange?.(clamped);\n };\n\n const stopAutoRepeat = () => {\n if (autoRepeatInterval) {\n clearInterval(autoRepeatInterval);\n autoRepeatInterval = undefined;\n }\n };\n\n const startAutoRepeat = (direction: 1 | -1) => {\n if (local.disabled || local.readOnly) return;\n stopAutoRepeat();\n const stepAmount = local.step || 1;\n autoRepeatInterval = setInterval(() => {\n handleValueChange(currentVal() + direction * stepAmount);\n }, 75);\n };\n\n /* Nikala UI createLongPress hook for Increment Trigger */\n const incrementLongPress = createLongPress(\n () => {\n startAutoRepeat(1);\n },\n {\n threshold: 300,\n onCancel: stopAutoRepeat,\n }\n );\n\n /* Nikala UI createLongPress hook for Decrement Trigger */\n const decrementLongPress = createLongPress(\n () => {\n startAutoRepeat(-1);\n },\n {\n threshold: 300,\n onCancel: stopAutoRepeat,\n }\n );\n\n onCleanup(() => stopAutoRepeat());\n\n return (\n <KobalteNumberField\n value={currentVal()}\n onRawValueChange={handleValueChange}\n minValue={effectiveMin()}\n maxValue={effectiveMax()}\n step={local.step || 1}\n disabled={local.disabled}\n readOnly={local.readOnly}\n id={local.id}\n class={cn(\"relative flex items-center max-w-[160px]\", local.class)}\n {...rest}\n >\n <KobalteNumberField.Input\n as={Input}\n class=\"pr-16 text-center font-mono focus-visible:ring-1\"\n />\n\n <div class=\"absolute right-1 flex items-center gap-0.5\">\n <KobalteNumberField.DecrementTrigger\n as={Button}\n variant=\"ghost\"\n size=\"icon\"\n class=\"h-7 w-7 rounded-sm p-0 text-muted-foreground hover:text-foreground select-none\"\n aria-label=\"Decrement value\"\n {...decrementLongPress.props}\n >\n <Minus class=\"h-3.5 w-3.5\" />\n </KobalteNumberField.DecrementTrigger>\n\n <KobalteNumberField.IncrementTrigger\n as={Button}\n variant=\"ghost\"\n size=\"icon\"\n class=\"h-7 w-7 rounded-sm p-0 text-muted-foreground hover:text-foreground select-none\"\n aria-label=\"Increment value\"\n {...incrementLongPress.props}\n >\n <Plus class=\"h-3.5 w-3.5\" />\n </KobalteNumberField.IncrementTrigger>\n </div>\n </KobalteNumberField>\n );\n};\n",
|
|
21
|
+
"type": "registry:ui"
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "resizable",
|
|
3
|
+
"title": "Resizable",
|
|
4
|
+
"description": "Accessible resizable panel layout component supporting drag-to-resize handles.",
|
|
5
|
+
"type": "registry:ui",
|
|
6
|
+
"dependencies": [
|
|
7
|
+
"clsx",
|
|
8
|
+
"tailwind-merge",
|
|
9
|
+
"lucide-solid"
|
|
10
|
+
],
|
|
11
|
+
"registryDependencies": [
|
|
12
|
+
"create-resize-observer"
|
|
13
|
+
],
|
|
14
|
+
"files": [
|
|
15
|
+
{
|
|
16
|
+
"path": "ui/resizable.tsx",
|
|
17
|
+
"content": "import {\n createSignal,\n createContext,\n useContext,\n splitProps,\n type Component,\n type JSX,\n type Accessor,\n} from \"solid-js\";\nimport { createElementSize } from \"@nikala-ui/hooks\";\nimport { GripVertical, GripHorizontal } from \"lucide-solid\";\nimport { cn } from \"@/lib/cn\";\n\nexport interface ResizableContextValue {\n orientation: Accessor<\"horizontal\" | \"vertical\">;\n registerPanel: (id: string, initialSizes: number) => void;\n sizes: Accessor<Record<string, number>>;\n startDragging: (handleIndex: number, event: PointerEvent) => void;\n containerRef: () => HTMLDivElement | undefined;\n}\n\nconst ResizableContext = createContext<ResizableContextValue>();\n\nexport interface ResizableGroupProps extends JSX.HTMLAttributes<HTMLDivElement> {\n orientation?: \"horizontal\" | \"vertical\";\n class?: string;\n children?: JSX.Element;\n}\n\nexport const ResizableGroup: Component<ResizableGroupProps> = (props) => {\n const [local, rest] = splitProps(props, [\"orientation\", \"class\", \"children\"]);\n const orientation = () => local.orientation || \"horizontal\";\n let containerEl: HTMLDivElement | undefined;\n\n const [panelOrder, setPanelOrder] = createSignal<string[]>([]);\n const [sizes, setSizes] = createSignal<Record<string, number>>({});\n\n const registerPanel = (id: string, initialSize: number) => {\n setPanelOrder((prev) => (prev.includes(id) ? prev : [...prev, id]));\n setSizes((prev) => (prev[id] !== undefined ? prev : { ...prev, [id]: initialSize }));\n };\n\n const startDragging = (handleIndex: number, event: PointerEvent) => {\n if (!containerEl) return;\n event.preventDefault();\n\n const order = panelOrder();\n if (handleIndex < 0 || handleIndex >= order.length - 1) return;\n\n const leftId = order[handleIndex];\n const rightId = order[handleIndex + 1];\n\n const isHoriz = orientation() === \"horizontal\";\n const startPos = isHoriz ? event.clientX : event.clientY;\n const rect = containerEl.getBoundingClientRect();\n const totalPx = isHoriz ? rect.width : rect.height;\n\n const startLeftPct = sizes()[leftId] ?? 50;\n const startRightPct = sizes()[rightId] ?? 50;\n\n const onPointerMove = (e: PointerEvent) => {\n const currentPos = isHoriz ? e.clientX : e.clientY;\n const deltaPx = currentPos - startPos;\n const deltaPct = (deltaPx / totalPx) * 100;\n\n let newLeft = startLeftPct + deltaPct;\n let newRight = startRightPct - deltaPct;\n\n if (newLeft < 10) {\n newLeft = 10;\n newRight = startLeftPct + startRightPct - 10;\n } else if (newRight < 10) {\n newRight = 10;\n newLeft = startLeftPct + startRightPct - 10;\n }\n\n setSizes((prev) => ({\n ...prev,\n [leftId]: newLeft,\n [rightId]: newRight,\n }));\n };\n\n const onPointerUp = () => {\n window.removeEventListener(\"pointermove\", onPointerMove);\n window.removeEventListener(\"pointerup\", onPointerUp);\n };\n\n window.addEventListener(\"pointermove\", onPointerMove);\n window.addEventListener(\"pointerup\", onPointerUp);\n };\n\n return (\n <ResizableContext.Provider\n value={{\n orientation,\n registerPanel,\n sizes,\n startDragging,\n containerRef: () => containerEl,\n }}\n >\n <div\n ref={containerEl}\n class={cn(\n \"flex h-full w-full overflow-hidden rounded-lg border border-border bg-background\",\n orientation() === \"vertical\" ? \"flex-col\" : \"flex-row\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n </div>\n </ResizableContext.Provider>\n );\n};\n\nexport interface ResizablePanelProps extends JSX.HTMLAttributes<HTMLDivElement> {\n id: string;\n initialSize?: number;\n class?: string;\n children?: JSX.Element;\n}\n\nexport const ResizablePanel: Component<ResizablePanelProps> = (props) => {\n const [local, rest] = splitProps(props, [\"id\", \"initialSize\", \"class\", \"children\"]);\n const ctx = useContext(ResizableContext);\n\n if (!ctx) {\n throw new Error(\"ResizablePanel must be used within a ResizableGroup\");\n }\n\n ctx.registerPanel(local.id, local.initialSize ?? 50);\n\n const currentPct = () => ctx.sizes()[local.id] ?? local.initialSize ?? 50;\n\n const containerSize = createElementSize(() => ctx.containerRef());\n\n return (\n <div\n class={cn(\"overflow-auto transition-[flex-basis] duration-75\", local.class)}\n style={{\n \"flex-basis\": `${currentPct()}%`,\n \"flex-grow\": 0,\n \"flex-shrink\": 0,\n }}\n {...rest}\n >\n {local.children}\n </div>\n );\n};\n\nexport interface ResizableHandleProps extends JSX.HTMLAttributes<HTMLDivElement> {\n handleIndex: number;\n withHandle?: boolean;\n class?: string;\n}\n\nexport const ResizableHandle: Component<ResizableHandleProps> = (props) => {\n const [local, rest] = splitProps(props, [\"handleIndex\", \"withHandle\", \"class\"]);\n const ctx = useContext(ResizableContext);\n\n if (!ctx) {\n throw new Error(\"ResizableHandle must be used within a ResizableGroup\");\n }\n\n const isHoriz = () => ctx.orientation() === \"horizontal\";\n\n return (\n <div\n role=\"separator\"\n tabIndex={0}\n class={cn(\n \"relative flex select-none items-center justify-center bg-border transition-colors hover:bg-primary/50 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring cursor-col-resize\",\n isHoriz() ? \"h-full w-1.5 cursor-col-resize\" : \"h-1.5 w-full cursor-row-resize\",\n local.class\n )}\n onPointerDown={(e) => ctx.startDragging(local.handleIndex, e)}\n {...rest}\n >\n {local.withHandle && (\n <div class=\"z-10 flex h-4 w-3 items-center justify-center rounded-xs border border-border bg-muted shadow-2xs\">\n {isHoriz() ? (\n <GripVertical class=\"h-2.5 w-2.5 text-muted-foreground\" />\n ) : (\n <GripHorizontal class=\"h-2.5 w-2.5 text-muted-foreground\" />\n )}\n </div>\n )}\n </div>\n );\n};\n",
|
|
18
|
+
"type": "registry:ui"
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|