@nikala-ui/core 0.7.1 → 0.9.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 +5 -1
- package/registry/accordion.json +1 -1
- package/registry/checkbox.json +1 -1
- package/registry/command.json +1 -1
- package/registry/dropdown-menu.json +1 -1
- package/registry/index.json +23 -0
- package/registry/popover.json +19 -0
- package/registry/progress.json +18 -0
- package/registry/select.json +1 -1
- package/registry/switch.json +1 -1
- package/registry/tabs.json +1 -1
- package/registry/theme-manager.json +1 -1
- package/registry/tooltip.json +1 -1
- package/src/registry/components/ui/accordion.tsx +4 -0
- package/src/registry/components/ui/checkbox.tsx +9 -20
- package/src/registry/components/ui/command.tsx +2 -2
- package/src/registry/components/ui/dropdown-menu.tsx +15 -0
- package/src/registry/components/ui/popover.tsx +90 -0
- package/src/registry/components/ui/progress.tsx +66 -0
- package/src/registry/components/ui/select.tsx +15 -0
- package/src/registry/components/ui/switch.tsx +9 -20
- package/src/registry/components/ui/tabs.tsx +7 -15
- package/src/registry/components/ui/theme-toggle.tsx +10 -5
- package/src/registry/components/ui/tooltip.tsx +18 -6
- package/src/registry/metadata.ts +10 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nikala-ui/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Core component definitions, design tokens, and registry for Nikala UI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -29,5 +29,9 @@
|
|
|
29
29
|
"picocolors": "^1.1.1",
|
|
30
30
|
"solid-js": "^1.9.14",
|
|
31
31
|
"tailwind-merge": "^3.6.0"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@nikala-ui/hooks": "workspace:*",
|
|
35
|
+
"lucide-solid": "^1.28.0"
|
|
32
36
|
}
|
|
33
37
|
}
|
package/registry/accordion.json
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"files": [
|
|
12
12
|
{
|
|
13
13
|
"path": "ui/accordion.tsx",
|
|
14
|
-
"content": "import { splitProps, type JSX, type ValidComponent } from \"solid-js\";\nimport * as AccordionPrimitive from \"@kobalte/core/accordion\";\nimport type { PolymorphicProps } from \"@kobalte/core/polymorphic\";\nimport { cn } from \"@/lib/cn\";\n\nexport type AccordionRootProps<T extends ValidComponent = \"div\"> = Omit<\n AccordionPrimitive.AccordionRootProps<T>,\n \"value\" | \"defaultValue\"\n> & {\n /** Accordion mode: \"single\" allows one open item, \"multiple\" allows many */\n type?: \"single\" | \"multiple\";\n /** Controlled value (single string or array of strings) */\n value?: string | string[];\n /** Default initial value (single string or array of strings) */\n defaultValue?: string | string[];\n class?: string;\n};\n\n/**\n * Root Accordion component built on top of Kobalte headless primitives.\n */\nexport const Accordion = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, AccordionRootProps<T>>\n) => {\n const [local, rest] = splitProps(props as AccordionRootProps, [\n \"class\",\n \"type\",\n \"multiple\",\n ]);\n\n // Support both `type=\"multiple\"` and `multiple={true}`\n const isMultiple = () => local.multiple ?? local.type === \"multiple\";\n\n return (\n <AccordionPrimitive.Root\n multiple={isMultiple()}\n class={cn(\"w-full divide-y divide-border\", local.class)}\n {...(rest as any)}\n />\n );\n};\n\nexport type AccordionItemProps<T extends ValidComponent = \"div\"> =\n AccordionPrimitive.AccordionItemProps<T> & {\n class?: string;\n value: string;\n };\n\n/**\n * Individual Accordion section item wrapper.\n */\nexport const AccordionItem = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, AccordionItemProps<T>>\n) => {\n const [local, rest] = splitProps(props as AccordionItemProps, [\"class\"]);\n\n return (\n <AccordionPrimitive.Item\n class={cn(\"border-b border-border\", local.class)}\n {...rest}\n />\n );\n};\n\nexport type AccordionTriggerProps<T extends ValidComponent = \"button\"> =\n AccordionPrimitive.AccordionTriggerProps<T> & {\n class?: string;\n children?: JSX.Element;\n };\n\n/**\n * Header trigger button toggling the expansion of an AccordionItem.\n */\nexport const AccordionTrigger = <T extends ValidComponent = \"button\">(\n props: PolymorphicProps<T, AccordionTriggerProps<T>>\n) => {\n const [local, rest] = splitProps(props as AccordionTriggerProps, [\"class\", \"children\"]);\n\n return (\n <AccordionPrimitive.Header class=\"flex\">\n <AccordionPrimitive.Trigger\n class={cn(\n \"flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline [&[data-expanded]>svg]:rotate-180 cursor-pointer text-foreground\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n <svg\n class=\"h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M19 9l-7 7-7-7\" />\n </svg>\n </AccordionPrimitive.Trigger>\n </AccordionPrimitive.Header>\n );\n};\n\nexport type AccordionContentProps<T extends ValidComponent = \"div\"> =\n AccordionPrimitive.AccordionContentProps<T> & {\n class?: string;\n children?: JSX.Element;\n };\n\n/**\n * Collapsible content panel revealed when the associated AccordionItem is open.\n */\nexport const AccordionContent = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, AccordionContentProps<T>>\n) => {\n const [local, rest] = splitProps(props as AccordionContentProps, [\"class\", \"children\"]);\n\n return (\n <AccordionPrimitive.Content\n class={cn(\n \"overflow-hidden text-sm text-muted-foreground transition-all\",\n local.class\n )}\n {...rest}\n >\n <div class=\"pb-4 pt-0\">{local.children}</div>\n </AccordionPrimitive.Content>\n );\n};",
|
|
14
|
+
"content": "import { splitProps, type JSX, type ValidComponent } from \"solid-js\";\nimport * as AccordionPrimitive from \"@kobalte/core/accordion\";\nimport type { PolymorphicProps } from \"@kobalte/core/polymorphic\";\nimport { cn } from \"@/lib/cn\";\n\nexport type AccordionRootProps<T extends ValidComponent = \"div\"> = Omit<\n AccordionPrimitive.AccordionRootProps<T>,\n \"value\" | \"defaultValue\"\n> & {\n /** Accordion mode: \"single\" allows one open item, \"multiple\" allows many */\n type?: \"single\" | \"multiple\";\n /** Controlled value (single string or array of strings) */\n value?: string | string[];\n /** Default initial value (single string or array of strings) */\n defaultValue?: string | string[];\n class?: string;\n};\n\n/**\n * Root Accordion component built on top of Kobalte headless primitives.\n */\nexport const Accordion = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, AccordionRootProps<T>>\n) => {\n const [local, rest] = splitProps(props as AccordionRootProps, [\n \"class\",\n \"type\",\n \"multiple\",\n \"value\",\n \"defaultValue\",\n ]);\n\n // Support both `type=\"multiple\"` and `multiple={true}`\n const isMultiple = () => local.multiple ?? local.type === \"multiple\";\n\n return (\n <AccordionPrimitive.Root\n multiple={isMultiple()}\n value={local.value}\n defaultValue={local.defaultValue}\n class={cn(\"w-full divide-y divide-border\", local.class)}\n {...(rest as any)}\n />\n );\n};\n\nexport type AccordionItemProps<T extends ValidComponent = \"div\"> =\n AccordionPrimitive.AccordionItemProps<T> & {\n class?: string;\n value: string;\n };\n\n/**\n * Individual Accordion section item wrapper.\n */\nexport const AccordionItem = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, AccordionItemProps<T>>\n) => {\n const [local, rest] = splitProps(props as AccordionItemProps, [\"class\"]);\n\n return (\n <AccordionPrimitive.Item\n class={cn(\"border-b border-border\", local.class)}\n {...rest}\n />\n );\n};\n\nexport type AccordionTriggerProps<T extends ValidComponent = \"button\"> =\n AccordionPrimitive.AccordionTriggerProps<T> & {\n class?: string;\n children?: JSX.Element;\n };\n\n/**\n * Header trigger button toggling the expansion of an AccordionItem.\n */\nexport const AccordionTrigger = <T extends ValidComponent = \"button\">(\n props: PolymorphicProps<T, AccordionTriggerProps<T>>\n) => {\n const [local, rest] = splitProps(props as AccordionTriggerProps, [\"class\", \"children\"]);\n\n return (\n <AccordionPrimitive.Header class=\"flex\">\n <AccordionPrimitive.Trigger\n class={cn(\n \"flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline [&[data-expanded]>svg]:rotate-180 cursor-pointer text-foreground\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n <svg\n class=\"h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M19 9l-7 7-7-7\" />\n </svg>\n </AccordionPrimitive.Trigger>\n </AccordionPrimitive.Header>\n );\n};\n\nexport type AccordionContentProps<T extends ValidComponent = \"div\"> =\n AccordionPrimitive.AccordionContentProps<T> & {\n class?: string;\n children?: JSX.Element;\n };\n\n/**\n * Collapsible content panel revealed when the associated AccordionItem is open.\n */\nexport const AccordionContent = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, AccordionContentProps<T>>\n) => {\n const [local, rest] = splitProps(props as AccordionContentProps, [\"class\", \"children\"]);\n\n return (\n <AccordionPrimitive.Content\n class={cn(\n \"overflow-hidden text-sm text-muted-foreground transition-all\",\n local.class\n )}\n {...rest}\n >\n <div class=\"pb-4 pt-0\">{local.children}</div>\n </AccordionPrimitive.Content>\n );\n};",
|
|
15
15
|
"type": "registry:ui"
|
|
16
16
|
}
|
|
17
17
|
]
|
package/registry/checkbox.json
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
{
|
|
12
12
|
"path": "ui/checkbox.tsx",
|
|
13
|
-
"content": "import {
|
|
13
|
+
"content": "import { splitProps, Show, type Component, type JSX } from \"solid-js\";\nimport { createControllableSignal } from \"@nikala-ui/hooks\";\nimport { cn } from \"@/lib/cn\";\n\nexport interface CheckboxProps\n extends Omit<JSX.ButtonHTMLAttributes<HTMLButtonElement>, \"onChange\"> {\n /** Controlled checked state */\n checked?: boolean;\n /** Uncontrolled default checked state */\n defaultChecked?: boolean;\n /** Event handler triggered when checked state changes */\n onChange?: (checked: boolean) => void;\n class?: string;\n}\n\n/**\n * Nikala UI Checkbox component built for SolidJS with Tailwind CSS v4 styling.\n */\nexport const Checkbox: Component<CheckboxProps> = (props) => {\n const [local, rest] = splitProps(props, [\n \"checked\",\n \"defaultChecked\",\n \"onChange\",\n \"disabled\",\n \"class\",\n \"onClick\",\n ]);\n\n const [isChecked, setIsChecked] = createControllableSignal({\n value: () => local.checked,\n defaultValue: local.defaultChecked ?? false,\n onChange: (val) => local.onChange?.(val),\n });\n\n const toggle = (\n e: MouseEvent & { currentTarget: HTMLButtonElement; target: Element }\n ) => {\n if (local.disabled) return;\n setIsChecked(!isChecked());\n\n if (typeof local.onClick === \"function\") {\n local.onClick(e);\n }\n };\n\n return (\n <button\n type=\"button\"\n role=\"checkbox\"\n aria-checked={Boolean(isChecked())}\n data-state={isChecked() ? \"checked\" : \"unchecked\"}\n disabled={local.disabled}\n onClick={toggle}\n class={cn(\n \"peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground flex items-center justify-center transition-colors\",\n local.class\n )}\n {...rest}\n >\n <Show when={isChecked()}>\n <svg\n class=\"h-3.5 w-3.5 fill-none stroke-current stroke-[3]\"\n viewBox=\"0 0 24 24\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n d=\"M5 13l4 4L19 7\"\n />\n </svg>\n </Show>\n </button>\n );\n};",
|
|
14
14
|
"type": "registry:ui"
|
|
15
15
|
}
|
|
16
16
|
]
|
package/registry/command.json
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"files": [
|
|
19
19
|
{
|
|
20
20
|
"path": "ui/command.tsx",
|
|
21
|
-
"content": "import {\n createContext,\n useContext,\n createSignal,\n onMount,\n onCleanup,\n Show,\n splitProps,\n type Component,\n type JSX,\n type Accessor,\n} from \"solid-js\";\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-
|
|
21
|
+
"content": "import {\n createContext,\n useContext,\n createSignal,\n onMount,\n onCleanup,\n Show,\n splitProps,\n type Component,\n type JSX,\n type Accessor,\n} from \"solid-js\";\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 onMount(() => {\n if (props.enableHotkey !== false) {\n const handleKeyDown = (e: KeyboardEvent) => {\n if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === \"k\") {\n e.preventDefault();\n setOpen(!isOpen());\n }\n };\n window.addEventListener(\"keydown\", handleKeyDown);\n onCleanup(() => window.removeEventListener(\"keydown\", handleKeyDown));\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-[330px] 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
22
|
"type": "registry:ui"
|
|
23
23
|
}
|
|
24
24
|
]
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"files": [
|
|
12
12
|
{
|
|
13
13
|
"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 { 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-[8rem] 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\n return (\n <DropdownMenuPrimitive.Portal>\n <DropdownMenuPrimitive.Content\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 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 DropdownMenuItemProps<T extends ValidComponent = \"div\"> =\n DropdownMenuPrimitive.DropdownMenuItemProps<T> & {\n class?: string;\n inset?: boolean;\n variant?: \"default\" | \"destructive\";\n };\n\nexport const DropdownMenuItem = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, DropdownMenuItemProps<T>>\n) => {\n const [local, rest] = splitProps(props as DropdownMenuItemProps, [\n \"class\",\n \"inset\",\n \"variant\",\n ]);\n\n return (\n <DropdownMenuPrimitive.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\",\n local.inset && \"pl-8\",\n local.variant === \"destructive\" &&\n \"text-destructive focus:bg-destructive/15 focus:text-destructive\",\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 };\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\"]);\n\n return (\n <DropdownMenuPrimitive.CheckboxItem\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\",\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 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 </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-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\",\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 <span class=\"h-2 w-2 rounded-full bg-current\" />\n </DropdownMenuPrimitive.ItemIndicator>\n </span>\n {local.children}\n </DropdownMenuPrimitive.RadioItem>\n );\n};\n\nexport interface DropdownMenuLabelProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n inset?: boolean;\n}\n\nexport const DropdownMenuLabel: Component<DropdownMenuLabelProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"inset\"]);\n\n return (\n <div\n class={cn(\n \"px-2 py-1.5 text-sm font-semibold text-foreground\",\n local.inset && \"pl-8\",\n local.class\n )}\n {...rest}\n />\n );\n};\n\nexport type DropdownMenuSeparatorProps<T extends ValidComponent = \"hr\"> =\n DropdownMenuPrimitive.DropdownMenuSeparatorProps<T> & {\n class?: string;\n };\n\nexport const DropdownMenuSeparator = <T extends ValidComponent = \"hr\">(\n props: PolymorphicProps<T, DropdownMenuSeparatorProps<T>>\n) => {\n const [local, rest] = splitProps(props as DropdownMenuSeparatorProps, [\"class\"]);\n\n return (\n <DropdownMenuPrimitive.Separator\n class={cn(\"-mx-1 my-1 h-px bg-border\", local.class)}\n {...(rest as any)}\n />\n );\n};\n\nexport interface DropdownMenuShortcutProps extends JSX.HTMLAttributes<HTMLSpanElement> {\n class?: string;\n}\n\nexport const DropdownMenuShortcut: Component<DropdownMenuShortcutProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"]);\n\n return (\n <span\n class={cn(\"ml-auto text-xs tracking-widest text-muted-foreground\", local.class)}\n {...rest}\n />\n );\n};",
|
|
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-[8rem] 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-[8rem] overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md 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 DropdownMenuItemProps<T extends ValidComponent = \"div\"> =\n DropdownMenuPrimitive.DropdownMenuItemProps<T> & {\n class?: string;\n inset?: boolean;\n variant?: \"default\" | \"destructive\";\n };\n\nexport const DropdownMenuItem = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, DropdownMenuItemProps<T>>\n) => {\n const [local, rest] = splitProps(props as DropdownMenuItemProps, [\n \"class\",\n \"inset\",\n \"variant\",\n ]);\n\n return (\n <DropdownMenuPrimitive.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\",\n local.inset && \"pl-8\",\n local.variant === \"destructive\" &&\n \"text-destructive focus:bg-destructive/15 focus:text-destructive\",\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 };\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\"]);\n\n return (\n <DropdownMenuPrimitive.CheckboxItem\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\",\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 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 </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-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\",\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 <span class=\"h-2 w-2 rounded-full bg-current\" />\n </DropdownMenuPrimitive.ItemIndicator>\n </span>\n {local.children}\n </DropdownMenuPrimitive.RadioItem>\n );\n};\n\nexport interface DropdownMenuLabelProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n inset?: boolean;\n}\n\nexport const DropdownMenuLabel: Component<DropdownMenuLabelProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"inset\"]);\n\n return (\n <div\n class={cn(\n \"px-2 py-1.5 text-sm font-semibold text-foreground\",\n local.inset && \"pl-8\",\n local.class\n )}\n {...rest}\n />\n );\n};\n\nexport type DropdownMenuSeparatorProps<T extends ValidComponent = \"hr\"> =\n DropdownMenuPrimitive.DropdownMenuSeparatorProps<T> & {\n class?: string;\n };\n\nexport const DropdownMenuSeparator = <T extends ValidComponent = \"hr\">(\n props: PolymorphicProps<T, DropdownMenuSeparatorProps<T>>\n) => {\n const [local, rest] = splitProps(props as DropdownMenuSeparatorProps, [\"class\"]);\n\n return (\n <DropdownMenuPrimitive.Separator\n class={cn(\"-mx-1 my-1 h-px bg-border\", local.class)}\n {...(rest as any)}\n />\n );\n};\n\nexport interface DropdownMenuShortcutProps extends JSX.HTMLAttributes<HTMLSpanElement> {\n class?: string;\n}\n\nexport const DropdownMenuShortcut: Component<DropdownMenuShortcutProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"]);\n\n return (\n <span\n class={cn(\"ml-auto text-xs tracking-widest text-muted-foreground\", local.class)}\n {...rest}\n />\n );\n};",
|
|
15
15
|
"type": "registry:ui"
|
|
16
16
|
}
|
|
17
17
|
]
|
package/registry/index.json
CHANGED
|
@@ -194,6 +194,29 @@
|
|
|
194
194
|
"@kobalte/core"
|
|
195
195
|
]
|
|
196
196
|
},
|
|
197
|
+
{
|
|
198
|
+
"name": "popover",
|
|
199
|
+
"title": "Popover",
|
|
200
|
+
"description": "Displays rich content in a portal layer triggered by a button, built on Kobalte primitives.",
|
|
201
|
+
"type": "registry:ui",
|
|
202
|
+
"dependencies": [
|
|
203
|
+
"clsx",
|
|
204
|
+
"tailwind-merge",
|
|
205
|
+
"@kobalte/core",
|
|
206
|
+
"lucide-solid"
|
|
207
|
+
]
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"name": "progress",
|
|
211
|
+
"title": "Progress",
|
|
212
|
+
"description": "Displays an indicator showing the completion progress of a task or media playback, built on Kobalte primitives.",
|
|
213
|
+
"type": "registry:ui",
|
|
214
|
+
"dependencies": [
|
|
215
|
+
"clsx",
|
|
216
|
+
"tailwind-merge",
|
|
217
|
+
"@kobalte/core"
|
|
218
|
+
]
|
|
219
|
+
},
|
|
197
220
|
{
|
|
198
221
|
"name": "radio-group",
|
|
199
222
|
"title": "Radio Group",
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "popover",
|
|
3
|
+
"title": "Popover",
|
|
4
|
+
"description": "Displays rich content in a portal layer triggered by a button, built on Kobalte primitives.",
|
|
5
|
+
"type": "registry:ui",
|
|
6
|
+
"dependencies": [
|
|
7
|
+
"clsx",
|
|
8
|
+
"tailwind-merge",
|
|
9
|
+
"@kobalte/core",
|
|
10
|
+
"lucide-solid"
|
|
11
|
+
],
|
|
12
|
+
"files": [
|
|
13
|
+
{
|
|
14
|
+
"path": "ui/popover.tsx",
|
|
15
|
+
"content": "import { splitProps, type Component, type ComponentProps } from \"solid-js\";\nimport { Popover as KobaltePopover } from \"@kobalte/core/popover\";\nimport { createClickOutside } from \"@nikala-ui/hooks\";\nimport { X } from \"lucide-solid\";\nimport { cn } from \"@/lib/cn\";\n\nexport const Popover = KobaltePopover;\n\nexport const PopoverTrigger = KobaltePopover.Trigger;\n\nexport const PopoverArrow: Component<ComponentProps<typeof KobaltePopover.Arrow>> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"]);\n\n return (\n <KobaltePopover.Arrow\n class={cn(\"fill-popover stroke-border\", local.class)}\n {...rest}\n />\n );\n};\n\nexport const PopoverContent: Component<ComponentProps<typeof KobaltePopover.Content>> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\", \"onInteractOutside\"]);\n let contentRef: HTMLElement | undefined;\n\n createClickOutside({\n target: () => contentRef,\n onInteractOutside: (e) => {\n if (typeof local.onInteractOutside === \"function\") {\n local.onInteractOutside(e as any);\n }\n },\n });\n\n return (\n <KobaltePopover.Portal>\n <KobaltePopover.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 w-72 rounded-lg border border-border bg-popover p-4 text-popover-foreground shadow-md 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 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n </KobaltePopover.Content>\n </KobaltePopover.Portal>\n );\n};\n\nexport const PopoverTitle: Component<ComponentProps<typeof KobaltePopover.Title>> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"]);\n\n return (\n <KobaltePopover.Title\n class={cn(\"text-sm font-semibold text-foreground\", local.class)}\n {...rest}\n />\n );\n};\n\nexport const PopoverDescription: Component<ComponentProps<typeof KobaltePopover.Description>> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"]);\n\n return (\n <KobaltePopover.Description\n class={cn(\"text-sm text-muted-foreground\", local.class)}\n {...rest}\n />\n );\n};\n\nexport const PopoverCloseButton: Component<ComponentProps<typeof KobaltePopover.CloseButton>> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <KobaltePopover.CloseButton\n class={cn(\n \"absolute right-2 top-2 rounded-md p-1 opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none\",\n local.class\n )}\n {...rest}\n >\n {local.children || <X class=\"h-4 w-4 text-muted-foreground\" />}\n </KobaltePopover.CloseButton>\n );\n};",
|
|
16
|
+
"type": "registry:ui"
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "progress",
|
|
3
|
+
"title": "Progress",
|
|
4
|
+
"description": "Displays an indicator showing the completion progress of a task or media playback, 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/progress.tsx",
|
|
14
|
+
"content": "import { splitProps, Show, type Component, type JSX } from \"solid-js\";\nimport { Progress as KobalteProgress } from \"@kobalte/core/progress\";\nimport { cn } from \"@/lib/cn\";\n\nexport interface ProgressProps\n extends Omit<\n JSX.HTMLAttributes<HTMLDivElement>,\n \"value\" | \"aria-valuenow\" | \"aria-valuemin\" | \"aria-valuemax\"\n > {\n /** The current numeric progress value. */\n value?: number;\n /** Minimum progress value. Defaults to 0. */\n minValue?: number;\n /** Maximum progress value. Defaults to 100. */\n maxValue?: number;\n /** Label text for accessibility and screen readers. */\n getValueLabel?: (params: { value: number; max: number }) => string;\n /** Custom label element to display above or beside progress bar. */\n label?: JSX.Element;\n /** Optional custom class for root container. */\n class?: string;\n /** Optional custom class for indicator fill bar. */\n indicatorClass?: string;\n}\n\n/**\n * Nikala UI Progress component for showing task completion status or media timeline positions.\n */\nexport const Progress: Component<ProgressProps> = (props) => {\n const [local, rest] = splitProps(props, [\n \"value\",\n \"minValue\",\n \"maxValue\",\n \"getValueLabel\",\n \"label\",\n \"class\",\n \"indicatorClass\",\n ]);\n\n return (\n <KobalteProgress\n value={local.value ?? 0}\n minValue={local.minValue ?? 0}\n maxValue={local.maxValue ?? 100}\n getValueLabel={local.getValueLabel}\n class={cn(\"flex w-full flex-col gap-1.5\", local.class)}\n {...rest}\n >\n <Show when={local.label}>\n <div class=\"flex justify-between text-xs font-medium text-muted-foreground\">\n <KobalteProgress.Label>{local.label}</KobalteProgress.Label>\n <KobalteProgress.ValueLabel class=\"font-mono text-foreground\" />\n </div>\n </Show>\n\n <KobalteProgress.Track class=\"relative h-2 w-full overflow-hidden rounded-full bg-primary/20\">\n <KobalteProgress.Fill\n class={cn(\n \"h-full w-[var(--kb-progress-fill-width)] bg-primary transition-all duration-300 ease-in-out\",\n local.indicatorClass\n )}\n />\n </KobalteProgress.Track>\n </KobalteProgress>\n );\n};\n",
|
|
15
|
+
"type": "registry:ui"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
package/registry/select.json
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"files": [
|
|
12
12
|
{
|
|
13
13
|
"path": "ui/select.tsx",
|
|
14
|
-
"content": "import { splitProps, type JSX, type ValidComponent } from \"solid-js\";\nimport * as SelectPrimitive from \"@kobalte/core/select\";\nimport type { PolymorphicProps } from \"@kobalte/core/polymorphic\";\nimport { cn } from \"@/lib/cn\";\n\nexport type SelectRootProps<Option = any, OptGroup = any, T extends ValidComponent = \"div\"> =\n SelectPrimitive.SelectRootProps<Option, OptGroup, T> & {\n class?: string;\n };\n\n/**\n * Root Select component built on top of Kobalte headless primitives.\n */\nexport const Select = <Option = any, OptGroup = any, T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, SelectRootProps<Option, OptGroup, T>>\n) => {\n const [local, rest] = splitProps(props as SelectRootProps, [\"class\"]);\n\n return <SelectPrimitive.Root class={cn(\"relative w-full\", local.class)} {...(rest as any)} />;\n};\n\nexport type SelectTriggerProps<T extends ValidComponent = \"button\"> =\n SelectPrimitive.SelectTriggerProps<T> & {\n class?: string;\n children?: JSX.Element;\n };\n\n/**\n * Trigger button opening the Select options list.\n */\nexport const SelectTrigger = <T extends ValidComponent = \"button\">(\n props: PolymorphicProps<T, SelectTriggerProps<T>>\n) => {\n const [local, rest] = splitProps(props as SelectTriggerProps, [\"class\", \"children\"]);\n\n return (\n <SelectPrimitive.Trigger\n class={cn(\n \"flex h-9 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 cursor-pointer text-foreground\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n <SelectPrimitive.Icon\n as=\"svg\"\n class=\"h-4 w-4 opacity-50 transition-transform\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M19 9l-7 7-7-7\" />\n </SelectPrimitive.Icon>\n </SelectPrimitive.Trigger>\n );\n};\n\nexport type SelectValueProps<Option, T extends ValidComponent = \"span\"> =\n SelectPrimitive.SelectValueProps<Option, T> & {\n class?: string;\n };\n\n/**\n * Renders the currently selected option text or placeholder.\n */\nexport const SelectValue = <Option = any, T extends ValidComponent = \"span\">(\n props: PolymorphicProps<T, SelectValueProps<Option, T>>\n) => {\n const [local, rest] = splitProps(props as SelectValueProps<Option>, [\"class\"]);\n\n return (\n <SelectPrimitive.Value\n class={cn(\"block truncate\", local.class)}\n {...(rest as any)}\n />\n );\n};\n\nexport type SelectContentProps<T extends ValidComponent = \"div\"> =\n SelectPrimitive.SelectContentProps<T> & {\n class?: string;\n };\n\n/**\n * Portaled overlay container rendering the listbox options.\n */\nexport const SelectContent = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, SelectContentProps<T>>\n) => {\n const [local, rest] = splitProps(props as SelectContentProps, [\"class\"]);\n\n return (\n <SelectPrimitive.Portal>\n <SelectPrimitive.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\",\n local.class\n )}\n {...rest}\n >\n <SelectPrimitive.Listbox class=\"p-1 outline-none\" />\n </SelectPrimitive.Content>\n </SelectPrimitive.Portal>\n );\n};\n\nexport type SelectItemProps<T extends ValidComponent = \"li\"> =\n SelectPrimitive.SelectItemProps<T> & {\n class?: string;\n children?: JSX.Element;\n };\n\n/**\n * Individual option item choice inside SelectContent.\n */\nexport const SelectItem = <T extends ValidComponent = \"li\">(\n props: PolymorphicProps<T, SelectItemProps<T>>\n) => {\n const [local, rest] = splitProps(props as SelectItemProps, [\"class\", \"children\"]);\n\n return (\n <SelectPrimitive.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-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground\",\n local.class\n )}\n {...rest}\n >\n <SelectPrimitive.ItemIndicator class=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\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 </SelectPrimitive.ItemIndicator>\n <SelectPrimitive.ItemLabel>{local.children}</SelectPrimitive.ItemLabel>\n </SelectPrimitive.Item>\n );\n};",
|
|
14
|
+
"content": "import { splitProps, type JSX, type ValidComponent } from \"solid-js\";\nimport * as SelectPrimitive from \"@kobalte/core/select\";\nimport type { PolymorphicProps } from \"@kobalte/core/polymorphic\";\nimport { createClickOutside } from \"@nikala-ui/hooks\";\nimport { cn } from \"@/lib/cn\";\n\nexport type SelectRootProps<Option = any, OptGroup = any, T extends ValidComponent = \"div\"> =\n SelectPrimitive.SelectRootProps<Option, OptGroup, T> & {\n class?: string;\n };\n\n/**\n * Root Select component built on top of Kobalte headless primitives.\n */\nexport const Select = <Option = any, OptGroup = any, T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, SelectRootProps<Option, OptGroup, T>>\n) => {\n const [local, rest] = splitProps(props as SelectRootProps, [\"class\"]);\n\n return <SelectPrimitive.Root class={cn(\"relative w-full\", local.class)} {...(rest as any)} />;\n};\n\nexport type SelectTriggerProps<T extends ValidComponent = \"button\"> =\n SelectPrimitive.SelectTriggerProps<T> & {\n class?: string;\n children?: JSX.Element;\n };\n\n/**\n * Trigger button opening the Select options list.\n */\nexport const SelectTrigger = <T extends ValidComponent = \"button\">(\n props: PolymorphicProps<T, SelectTriggerProps<T>>\n) => {\n const [local, rest] = splitProps(props as SelectTriggerProps, [\"class\", \"children\"]);\n\n return (\n <SelectPrimitive.Trigger\n class={cn(\n \"flex h-9 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 cursor-pointer text-foreground\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n <SelectPrimitive.Icon\n as=\"svg\"\n class=\"h-4 w-4 opacity-50 transition-transform\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M19 9l-7 7-7-7\" />\n </SelectPrimitive.Icon>\n </SelectPrimitive.Trigger>\n );\n};\n\nexport type SelectValueProps<Option, T extends ValidComponent = \"span\"> =\n SelectPrimitive.SelectValueProps<Option, T> & {\n class?: string;\n };\n\n/**\n * Renders the currently selected option text or placeholder.\n */\nexport const SelectValue = <Option = any, T extends ValidComponent = \"span\">(\n props: PolymorphicProps<T, SelectValueProps<Option, T>>\n) => {\n const [local, rest] = splitProps(props as SelectValueProps<Option>, [\"class\"]);\n\n return (\n <SelectPrimitive.Value\n class={cn(\"block truncate\", local.class)}\n {...(rest as any)}\n />\n );\n};\n\nexport type SelectContentProps<T extends ValidComponent = \"div\"> =\n SelectPrimitive.SelectContentProps<T> & {\n class?: string;\n };\n\n/**\n * Portaled overlay container rendering the listbox options.\n */\nexport const SelectContent = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, SelectContentProps<T>>\n) => {\n const [local, rest] = splitProps(props as SelectContentProps, [\"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 <SelectPrimitive.Portal>\n <SelectPrimitive.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 \"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\",\n local.class\n )}\n {...rest}\n >\n <SelectPrimitive.Listbox class=\"p-1 outline-none\" />\n </SelectPrimitive.Content>\n </SelectPrimitive.Portal>\n );\n};\n\nexport type SelectItemProps<T extends ValidComponent = \"li\"> =\n SelectPrimitive.SelectItemProps<T> & {\n class?: string;\n children?: JSX.Element;\n };\n\n/**\n * Individual option item choice inside SelectContent.\n */\nexport const SelectItem = <T extends ValidComponent = \"li\">(\n props: PolymorphicProps<T, SelectItemProps<T>>\n) => {\n const [local, rest] = splitProps(props as SelectItemProps, [\"class\", \"children\"]);\n\n return (\n <SelectPrimitive.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-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground\",\n local.class\n )}\n {...rest}\n >\n <SelectPrimitive.ItemIndicator class=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\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 </SelectPrimitive.ItemIndicator>\n <SelectPrimitive.ItemLabel>{local.children}</SelectPrimitive.ItemLabel>\n </SelectPrimitive.Item>\n );\n};",
|
|
15
15
|
"type": "registry:ui"
|
|
16
16
|
}
|
|
17
17
|
]
|
package/registry/switch.json
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
{
|
|
12
12
|
"path": "ui/switch.tsx",
|
|
13
|
-
"content": "import {
|
|
13
|
+
"content": "import { splitProps, type Component, type JSX } from \"solid-js\";\nimport { createControllableSignal } from \"@nikala-ui/hooks\";\nimport { cn } from \"@/lib/cn\";\n\nexport interface SwitchProps\n extends Omit<JSX.ButtonHTMLAttributes<HTMLButtonElement>, \"onChange\"> {\n /** Controlled checked state */\n checked?: boolean;\n /** Uncontrolled default checked state */\n defaultChecked?: boolean;\n /** Event handler triggered when toggle state changes */\n onChange?: (checked: boolean) => void;\n class?: string;\n}\n\n/**\n * Nikala UI Switch component for boolean toggle inputs built for SolidJS with Tailwind CSS v4 styling.\n */\nexport const Switch: Component<SwitchProps> = (props) => {\n const [local, rest] = splitProps(props, [\n \"checked\",\n \"defaultChecked\",\n \"onChange\",\n \"disabled\",\n \"class\",\n \"onClick\",\n ]);\n\n const [isChecked, setIsChecked] = createControllableSignal({\n value: () => local.checked,\n defaultValue: local.defaultChecked ?? false,\n onChange: (val) => local.onChange?.(val),\n });\n\n const toggle = (\n e: MouseEvent & { currentTarget: HTMLButtonElement; target: Element }\n ) => {\n if (local.disabled) return;\n setIsChecked(!isChecked());\n\n if (typeof local.onClick === \"function\") {\n local.onClick(e);\n }\n };\n\n return (\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={Boolean(isChecked())}\n data-state={isChecked() ? \"checked\" : \"unchecked\"}\n disabled={local.disabled}\n onClick={toggle}\n class={cn(\n \"peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-lg border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input\",\n local.class\n )}\n {...rest}\n >\n <span\n data-state={isChecked() ? \"checked\" : \"unchecked\"}\n class={cn(\n \"pointer-events-none block h-4 w-4 rounded-sm bg-primary-foreground shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0\"\n )}\n />\n </button>\n );\n};",
|
|
14
14
|
"type": "registry:ui"
|
|
15
15
|
}
|
|
16
16
|
]
|
package/registry/tabs.json
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
{
|
|
12
12
|
"path": "ui/tabs.tsx",
|
|
13
|
-
"content": "import {\n createContext,\n useContext,\n
|
|
13
|
+
"content": "import {\n createContext,\n useContext,\n splitProps,\n Show,\n type Component,\n type JSX,\n type Accessor,\n} from \"solid-js\";\nimport { createControllableSignal } from \"@nikala-ui/hooks\";\nimport { cn } from \"@/lib/cn\";\n\ninterface TabsContextValue {\n value: Accessor<string | undefined>;\n setValue: (value: string) => void;\n orientation: Accessor<\"horizontal\" | \"vertical\">;\n}\n\nconst TabsContext = createContext<TabsContextValue>();\n\nexport interface TabsProps\n extends Omit<JSX.HTMLAttributes<HTMLDivElement>, \"onChange\"> {\n /** Controlled active tab value */\n value?: string;\n /** Uncontrolled default active tab value */\n defaultValue?: string;\n /** Callback fired when active tab changes */\n onChange?: (value: string) => void;\n /** Layout orientation of tab triggers and content */\n orientation?: \"horizontal\" | \"vertical\";\n class?: string;\n}\n\n/**\n * Root Tabs container component managing active tab context state and orientation.\n */\nexport const Tabs: Component<TabsProps> = (props) => {\n const [local, rest] = splitProps(props, [\n \"value\",\n \"defaultValue\",\n \"onChange\",\n \"orientation\",\n \"class\",\n \"children\",\n ]);\n\n const [currentValue, setCurrentValue] = createControllableSignal<string>({\n value: () => local.value,\n defaultValue: local.defaultValue,\n onChange: (val) => local.onChange?.(val),\n });\n\n const orientation = () => local.orientation || \"horizontal\";\n\n const contextValue: TabsContextValue = {\n value: currentValue,\n setValue: (val: string) => setCurrentValue(val),\n orientation,\n };\n\n return (\n <TabsContext.Provider value={contextValue}>\n <div\n data-orientation={orientation()}\n class={cn(\n \"w-full\",\n orientation() === \"vertical\" ? \"flex flex-row gap-4\" : \"flex flex-col gap-2\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n </div>\n </TabsContext.Provider>\n );\n};\n\nexport interface TabsListProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\n/**\n * Container wrapper for Tab triggers.\n */\nexport const TabsList: Component<TabsListProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n const context = useContext(TabsContext);\n\n const isVertical = () => context?.orientation() === \"vertical\";\n\n return (\n <div\n role=\"tablist\"\n aria-orientation={context?.orientation() || \"horizontal\"}\n class={cn(\n \"inline-flex rounded-lg bg-muted p-1 text-muted-foreground\",\n isVertical()\n ? \"flex-col h-auto w-auto items-stretch justify-start\"\n : \"h-9 items-center justify-center\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n </div>\n );\n};\n\nexport interface TabsTriggerProps\n extends Omit<JSX.ButtonHTMLAttributes<HTMLButtonElement>, \"onChange\"> {\n /** Unique value identifier for this tab */\n value: string;\n class?: string;\n}\n\n/**\n * Tab button trigger to activate a specific tab panel.\n */\nexport const TabsTrigger: Component<TabsTriggerProps> = (props) => {\n const [local, rest] = splitProps(props, [\n \"value\",\n \"disabled\",\n \"class\",\n \"children\",\n \"onClick\",\n ]);\n const context = useContext(TabsContext);\n\n if (!context) {\n throw new Error(\"TabsTrigger must be used within a Tabs component\");\n }\n\n const isSelected = () => context.value() === local.value;\n const isVertical = () => context.orientation() === \"vertical\";\n\n const handleClick = (\n e: MouseEvent & { currentTarget: HTMLButtonElement; target: Element }\n ) => {\n if (local.disabled) return;\n context.setValue(local.value);\n if (typeof local.onClick === \"function\") {\n local.onClick(e);\n }\n };\n\n return (\n <button\n type=\"button\"\n role=\"tab\"\n aria-selected={isSelected()}\n data-state={isSelected() ? \"active\" : \"inactive\"}\n data-orientation={context.orientation()}\n disabled={local.disabled}\n onClick={handleClick}\n class={cn(\n \"inline-flex items-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm cursor-pointer\",\n isVertical() ? \"justify-start py-1.5\" : \"justify-center\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n </button>\n );\n};\n\nexport interface TabsContentProps extends JSX.HTMLAttributes<HTMLDivElement> {\n /** Value matching the corresponding tab trigger */\n value: string;\n class?: string;\n}\n\n/**\n * Content panel revealed when the associated tab is active.\n */\nexport const TabsContent: Component<TabsContentProps> = (props) => {\n const [local, rest] = splitProps(props, [\"value\", \"class\", \"children\"]);\n const context = useContext(TabsContext);\n\n if (!context) {\n throw new Error(\"TabsContent must be used within a Tabs component\");\n }\n\n const isSelected = () => context.value() === local.value;\n const isVertical = () => context.orientation() === \"vertical\";\n\n return (\n <Show when={isSelected()}>\n <div\n role=\"tabpanel\"\n data-state={isSelected() ? \"active\" : \"inactive\"}\n data-orientation={context.orientation()}\n class={cn(\n \"ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n isVertical() ? \"flex-1 mt-0\" : \"mt-2\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n </div>\n </Show>\n );\n};",
|
|
14
14
|
"type": "registry:ui"
|
|
15
15
|
}
|
|
16
16
|
]
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
33
|
"path": "ui/theme-toggle.tsx",
|
|
34
|
-
"content": "import { splitProps, type Component, For, Show } from \"solid-js\";\nimport { Sun, Moon, Monitor } from \"lucide-solid\";\nimport {\n useTheme,\n type AccentColor,\n type Radius,\n type Theme,\n} from \"../../providers/theme-provider\";\nimport {\n runThemeTransition,\n type ThemeEffect,\n} from \"../../providers/theme-transitions\";\nimport { Button } from \"./button\";\nimport {\n DropdownMenu,\n DropdownMenuTrigger,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n} from \"./dropdown-menu\";\nimport { cn } from \"@/lib/cn\";\n\nexport interface ThemeToggleProps {\n /** Display mode: \"mini\" for compact dropdown, \"max\" for full customizer panel (default: \"mini\") */\n mode?: \"mini\" | \"max\";\n /** Transition animation effect when changing themes (\"none\", \"circular\", \"fade\") */\n effect?: ThemeEffect;\n class?: string;\n}\n\nconst ACCENT_OPTIONS: { name: AccentColor; label: string; color: string }[] = [\n { name: \"wine\", label: \"Wine\", color: \"bg-[#722f37]\" },\n { name: \"violet\", label: \"Violet\", color: \"bg-[#7c3aed]\" },\n { name: \"sky\", label: \"Sky\", color: \"bg-[#0284c7]\" },\n { name: \"emerald\", label: \"Emerald\", color: \"bg-[#059669]\" },\n { name: \"rose\", label: \"Rose\", color: \"bg-[#e11d48]\" },\n { name: \"amber\", label: \"Amber\", color: \"bg-[#d97706]\" },\n { name: \"zinc\", label: \"Zinc\", color: \"bg-[#18181b]\" },\n];\n\nconst RADIUS_OPTIONS: { value: Radius; label: string }[] = [\n { value: \"0\", label: \"0\" },\n { value: \"0.3\", label: \"0.3\" },\n { value: \"0.5\", label: \"0.5\" },\n { value: \"0.75\", label: \"0.75\" },\n { value: \"1.0\", label: \"1.0\" },\n];\n\n/**\n * Interactive UI theme switcher supporting mini/max modes and View Transition animations.\n */\nexport const ThemeToggle: Component<ThemeToggleProps> = (props) => {\n const [local] = splitProps(props, [\"mode\", \"effect\", \"class\"]);\n const { theme, setTheme, accent, setAccent, radius, setRadius } = useTheme();\n\n const mode = () => local.mode || \"mini\";\n const effect = () => local.effect || \"none\";\n\n /* Reactive accessor determining whether dark mode is active */\n const isDarkMode = () => {\n const currentTheme = theme();\n if (currentTheme === \"dark\") return true;\n if (currentTheme === \"light\") return false;\n return
|
|
34
|
+
"content": "import { splitProps, type Component, For, Show } from \"solid-js\";\nimport { Sun, Moon, Monitor } from \"lucide-solid\";\nimport {\n useTheme,\n type AccentColor,\n type Radius,\n type Theme,\n} from \"../../providers/theme-provider\";\nimport {\n runThemeTransition,\n type ThemeEffect,\n} from \"../../providers/theme-transitions\";\nimport { Button } from \"./button\";\nimport {\n DropdownMenu,\n DropdownMenuTrigger,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n} from \"./dropdown-menu\";\nimport { cn } from \"@/lib/cn\";\n\nimport { createColorMode } from \"@nikala-ui/hooks\";\n\nexport interface ThemeToggleProps {\n /** Display mode: \"mini\" for compact dropdown, \"max\" for full customizer panel (default: \"mini\") */\n mode?: \"mini\" | \"max\";\n /** Transition animation effect when changing themes (\"none\", \"circular\", \"fade\") */\n effect?: ThemeEffect;\n class?: string;\n}\n\nconst ACCENT_OPTIONS: { name: AccentColor; label: string; color: string }[] = [\n { name: \"wine\", label: \"Wine\", color: \"bg-[#722f37]\" },\n { name: \"violet\", label: \"Violet\", color: \"bg-[#7c3aed]\" },\n { name: \"sky\", label: \"Sky\", color: \"bg-[#0284c7]\" },\n { name: \"emerald\", label: \"Emerald\", color: \"bg-[#059669]\" },\n { name: \"rose\", label: \"Rose\", color: \"bg-[#e11d48]\" },\n { name: \"amber\", label: \"Amber\", color: \"bg-[#d97706]\" },\n { name: \"zinc\", label: \"Zinc\", color: \"bg-[#18181b]\" },\n];\n\nconst RADIUS_OPTIONS: { value: Radius; label: string }[] = [\n { value: \"0\", label: \"0\" },\n { value: \"0.3\", label: \"0.3\" },\n { value: \"0.5\", label: \"0.5\" },\n { value: \"0.75\", label: \"0.75\" },\n { value: \"1.0\", label: \"1.0\" },\n];\n\n/**\n * Interactive UI theme switcher supporting mini/max modes and View Transition animations.\n */\nexport const ThemeToggle: Component<ThemeToggleProps> = (props) => {\n const [local] = splitProps(props, [\"mode\", \"effect\", \"class\"]);\n const { theme, setTheme, accent, setAccent, radius, setRadius } = useTheme();\n\n const colorMode = createColorMode({\n initialValue: theme(),\n storageKey: \"nikala-theme-mode\",\n });\n\n const mode = () => local.mode || \"mini\";\n const effect = () => local.effect || \"none\";\n\n /* Reactive accessor determining whether dark mode is active via createColorMode hook */\n const isDarkMode = () => {\n const currentTheme = theme();\n if (currentTheme === \"dark\") return true;\n if (currentTheme === \"light\") return false;\n return colorMode.isDark();\n };\n\n const changeThemeWithEffect = (newTheme: Theme, e: MouseEvent) => {\n runThemeTransition(effect(), e, () => {\n setTheme(newTheme);\n colorMode.setMode(newTheme);\n });\n };\n\n return (\n <DropdownMenu placement=\"bottom-end\">\n <DropdownMenuTrigger\n as={Button}\n variant=\"outline\"\n size=\"icon\"\n class={cn(\"relative h-9 w-9 cursor-pointer\", local.class)}\n >\n {/* Reactive Sun / Moon Icon Toggle */}\n <Show\n when={isDarkMode()}\n fallback={<Sun class=\"h-4 w-4 text-foreground transition-transform\" />}\n >\n <Moon class=\"h-4 w-4 text-foreground transition-transform\" />\n </Show>\n\n <span class=\"sr-only\">Toggle theme</span>\n </DropdownMenuTrigger>\n\n <Show\n when={mode() === \"max\"}\n fallback={\n /* Mini Mode: Compact Dropdown */\n <DropdownMenuContent>\n <DropdownMenuItem onClick={(e: MouseEvent) => changeThemeWithEffect(\"light\", e)}>\n <Sun class=\"mr-2 h-4 w-4 text-muted-foreground\" />\n Light\n </DropdownMenuItem>\n\n <DropdownMenuItem onClick={(e: MouseEvent) => changeThemeWithEffect(\"dark\", e)}>\n <Moon class=\"mr-2 h-4 w-4 text-muted-foreground\" />\n Dark\n </DropdownMenuItem>\n\n <DropdownMenuItem onClick={(e: MouseEvent) => changeThemeWithEffect(\"system\", e)}>\n <Monitor class=\"mr-2 h-4 w-4 text-muted-foreground\" />\n System\n </DropdownMenuItem>\n </DropdownMenuContent>\n }\n >\n {/* Max Mode: Full Theme Customizer Panel */}\n <DropdownMenuContent class=\"w-64 p-3\">\n <DropdownMenuLabel class=\"px-0 pt-0 text-xs font-semibold uppercase tracking-wider text-muted-foreground\">\n Theme Mode\n </DropdownMenuLabel>\n <div class=\"grid grid-cols-3 gap-1 my-1.5\">\n <Button\n variant={theme() === \"light\" ? \"default\" : \"outline\"}\n size=\"sm\"\n onClick={(e: MouseEvent) => changeThemeWithEffect(\"light\", e)}\n class=\"h-8 text-xs cursor-pointer\"\n >\n Light\n </Button>\n <Button\n variant={theme() === \"dark\" ? \"default\" : \"outline\"}\n size=\"sm\"\n onClick={(e: MouseEvent) => changeThemeWithEffect(\"dark\", e)}\n class=\"h-8 text-xs cursor-pointer\"\n >\n Dark\n </Button>\n <Button\n variant={theme() === \"system\" ? \"default\" : \"outline\"}\n size=\"sm\"\n onClick={(e: MouseEvent) => changeThemeWithEffect(\"system\", e)}\n class=\"h-8 text-xs cursor-pointer\"\n >\n System\n </Button>\n </div>\n\n <DropdownMenuSeparator class=\"my-2\" />\n\n <DropdownMenuLabel class=\"px-0 text-xs font-semibold uppercase tracking-wider text-muted-foreground\">\n Brand Accent Color\n </DropdownMenuLabel>\n <div class=\"flex flex-wrap gap-1.5 my-1.5\">\n <For each={ACCENT_OPTIONS}>\n {(opt) => (\n <button\n type=\"button\"\n title={opt.label}\n onClick={() => setAccent(opt.name)}\n class={cn(\n \"h-6 w-6 rounded-md transition-all cursor-pointer border border-border flex items-center justify-center\",\n opt.color,\n accent() === opt.name ? \"ring-2 ring-primary ring-offset-2 ring-offset-background scale-110\" : \"hover:scale-105\"\n )}\n />\n )}\n </For>\n </div>\n\n <DropdownMenuSeparator class=\"my-2\" />\n\n <DropdownMenuLabel class=\"px-0 text-xs font-semibold uppercase tracking-wider text-muted-foreground\">\n Border Radius\n </DropdownMenuLabel>\n <div class=\"grid grid-cols-5 gap-1 my-1.5\">\n <For each={RADIUS_OPTIONS}>\n {(r) => (\n <Button\n variant={radius() === r.value ? \"default\" : \"outline\"}\n size=\"sm\"\n onClick={() => setRadius(r.value)}\n class=\"h-7 text-xs px-1 cursor-pointer\"\n >\n {r.label}\n </Button>\n )}\n </For>\n </div>\n </DropdownMenuContent>\n </Show>\n </DropdownMenu>\n );\n};",
|
|
35
35
|
"type": "registry:ui"
|
|
36
36
|
}
|
|
37
37
|
]
|
package/registry/tooltip.json
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"files": [
|
|
12
12
|
{
|
|
13
13
|
"path": "ui/tooltip.tsx",
|
|
14
|
-
"content": "import { splitProps, type Component, type ComponentProps } from \"solid-js\";\nimport { Tooltip as KobalteTooltip } from \"@kobalte/core/tooltip\";\nimport { cn } from \"@/lib/cn\";\n\nexport const Tooltip: Component<ComponentProps<typeof KobalteTooltip>> = (props) => {\n return <KobalteTooltip
|
|
14
|
+
"content": "import { splitProps, type Component, type ComponentProps } from \"solid-js\";\nimport { Tooltip as KobalteTooltip } from \"@kobalte/core/tooltip\";\nimport { cn } from \"@/lib/cn\";\n\nexport const Tooltip = KobalteTooltip;\n\nexport const TooltipTrigger = KobalteTooltip.Trigger;\n\nexport const TooltipArrow: Component<ComponentProps<typeof KobalteTooltip.Arrow>> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"]);\n\n return (\n <KobalteTooltip.Arrow\n size={8}\n class={cn(\"fill-popover stroke-border\", local.class)}\n {...rest}\n />\n );\n};\n\nexport interface TooltipContentProps extends ComponentProps<typeof KobalteTooltip.Content> {\n class?: string;\n}\n\nexport const TooltipContent: Component<TooltipContentProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <KobalteTooltip.Portal>\n <KobalteTooltip.Content\n class={cn(\n \"z-50 rounded-md border border-border bg-popover px-3 py-1.5 text-xs text-popover-foreground shadow-md transition-all animate-in fade-in-0 zoom-in-95 data-closed:animate-out data-[closed]:fade-out-0 data-closed:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n </KobalteTooltip.Content>\n </KobalteTooltip.Portal>\n );\n};",
|
|
15
15
|
"type": "registry:ui"
|
|
16
16
|
}
|
|
17
17
|
]
|
|
@@ -26,6 +26,8 @@ export const Accordion = <T extends ValidComponent = "div">(
|
|
|
26
26
|
"class",
|
|
27
27
|
"type",
|
|
28
28
|
"multiple",
|
|
29
|
+
"value",
|
|
30
|
+
"defaultValue",
|
|
29
31
|
]);
|
|
30
32
|
|
|
31
33
|
// Support both `type="multiple"` and `multiple={true}`
|
|
@@ -34,6 +36,8 @@ export const Accordion = <T extends ValidComponent = "div">(
|
|
|
34
36
|
return (
|
|
35
37
|
<AccordionPrimitive.Root
|
|
36
38
|
multiple={isMultiple()}
|
|
39
|
+
value={local.value}
|
|
40
|
+
defaultValue={local.defaultValue}
|
|
37
41
|
class={cn("w-full divide-y divide-border", local.class)}
|
|
38
42
|
{...(rest as any)}
|
|
39
43
|
/>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { splitProps, Show, type Component, type JSX } from "solid-js";
|
|
2
|
+
import { createControllableSignal } from "@nikala-ui/hooks";
|
|
2
3
|
import { cn } from "@/lib/cn";
|
|
3
4
|
|
|
4
5
|
export interface CheckboxProps
|
|
@@ -16,7 +17,6 @@ export interface CheckboxProps
|
|
|
16
17
|
* Nikala UI Checkbox component built for SolidJS with Tailwind CSS v4 styling.
|
|
17
18
|
*/
|
|
18
19
|
export const Checkbox: Component<CheckboxProps> = (props) => {
|
|
19
|
-
// Use splitProps to preserve SolidJS reactivity
|
|
20
20
|
const [local, rest] = splitProps(props, [
|
|
21
21
|
"checked",
|
|
22
22
|
"defaultChecked",
|
|
@@ -26,28 +26,17 @@ export const Checkbox: Component<CheckboxProps> = (props) => {
|
|
|
26
26
|
"onClick",
|
|
27
27
|
]);
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
local.defaultChecked ?? false
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
// Computed checked state
|
|
35
|
-
const isChecked = () =>
|
|
36
|
-
local.checked !== undefined ? local.checked : internalChecked();
|
|
29
|
+
const [isChecked, setIsChecked] = createControllableSignal({
|
|
30
|
+
value: () => local.checked,
|
|
31
|
+
defaultValue: local.defaultChecked ?? false,
|
|
32
|
+
onChange: (val) => local.onChange?.(val),
|
|
33
|
+
});
|
|
37
34
|
|
|
38
35
|
const toggle = (
|
|
39
36
|
e: MouseEvent & { currentTarget: HTMLButtonElement; target: Element }
|
|
40
37
|
) => {
|
|
41
38
|
if (local.disabled) return;
|
|
42
|
-
|
|
43
|
-
const nextState = !isChecked();
|
|
44
|
-
if (local.checked === undefined) {
|
|
45
|
-
setInternalChecked(nextState);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (typeof local.onChange === "function") {
|
|
49
|
-
local.onChange(nextState);
|
|
50
|
-
}
|
|
39
|
+
setIsChecked(!isChecked());
|
|
51
40
|
|
|
52
41
|
if (typeof local.onClick === "function") {
|
|
53
42
|
local.onClick(e);
|
|
@@ -58,7 +47,7 @@ export const Checkbox: Component<CheckboxProps> = (props) => {
|
|
|
58
47
|
<button
|
|
59
48
|
type="button"
|
|
60
49
|
role="checkbox"
|
|
61
|
-
aria-checked={isChecked()}
|
|
50
|
+
aria-checked={Boolean(isChecked())}
|
|
62
51
|
data-state={isChecked() ? "checked" : "unchecked"}
|
|
63
52
|
disabled={local.disabled}
|
|
64
53
|
onClick={toggle}
|
|
@@ -46,7 +46,7 @@ export const Command: Component<CommandProps> = (props) => {
|
|
|
46
46
|
<CommandContext.Provider value={{ search, setSearch }}>
|
|
47
47
|
<div
|
|
48
48
|
class={cn(
|
|
49
|
-
"flex flex-col w-full h-full rounded-
|
|
49
|
+
"flex flex-col w-full h-full rounded-lg bg-popover text-popover-foreground overflow-hidden border border-border shadow-md",
|
|
50
50
|
local.class
|
|
51
51
|
)}
|
|
52
52
|
{...rest}
|
|
@@ -94,7 +94,7 @@ export const CommandDialog: Component<CommandDialogProps> = (props) => {
|
|
|
94
94
|
<Dialog.Portal>
|
|
95
95
|
<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" />
|
|
96
96
|
<div class="fixed inset-0 z-50 flex items-start justify-center pt-16 sm:pt-24 px-4">
|
|
97
|
-
<Dialog.Content class="w-full max-w-xl rounded-
|
|
97
|
+
<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">
|
|
98
98
|
<Command class={props.class}>{props.children}</Command>
|
|
99
99
|
</Dialog.Content>
|
|
100
100
|
</div>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { splitProps, type Component, type JSX, type ValidComponent } from "solid-js";
|
|
2
2
|
import * as DropdownMenuPrimitive from "@kobalte/core/dropdown-menu";
|
|
3
3
|
import type { PolymorphicProps } from "@kobalte/core/polymorphic";
|
|
4
|
+
import { createClickOutside } from "@nikala-ui/hooks";
|
|
4
5
|
import { cn } from "@/lib/cn";
|
|
5
6
|
|
|
6
7
|
export type DropdownMenuRootProps = Omit<
|
|
@@ -92,10 +93,24 @@ export const DropdownMenuContent = <T extends ValidComponent = "div">(
|
|
|
92
93
|
props: PolymorphicProps<T, DropdownMenuContentProps<T>>
|
|
93
94
|
) => {
|
|
94
95
|
const [local, rest] = splitProps(props as DropdownMenuContentProps, ["class"]);
|
|
96
|
+
let contentRef: HTMLElement | undefined;
|
|
97
|
+
|
|
98
|
+
createClickOutside({
|
|
99
|
+
target: () => contentRef,
|
|
100
|
+
onInteractOutside: (e) => {
|
|
101
|
+
if (typeof (props as any).onInteractOutside === "function") {
|
|
102
|
+
(props as any).onInteractOutside(e);
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
});
|
|
95
106
|
|
|
96
107
|
return (
|
|
97
108
|
<DropdownMenuPrimitive.Portal>
|
|
98
109
|
<DropdownMenuPrimitive.Content
|
|
110
|
+
ref={(el) => {
|
|
111
|
+
contentRef = el;
|
|
112
|
+
if (typeof (props as any).ref === "function") (props as any).ref(el);
|
|
113
|
+
}}
|
|
99
114
|
class={cn(
|
|
100
115
|
"z-50 min-w-[8rem] overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md data-[expanded]:animate-in data-[closed]:animate-out data-[closed]:fade-out-0 data-[expanded]:fade-in-0",
|
|
101
116
|
local.class
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { splitProps, type Component, type ComponentProps } from "solid-js";
|
|
2
|
+
import { Popover as KobaltePopover } from "@kobalte/core/popover";
|
|
3
|
+
import { createClickOutside } from "@nikala-ui/hooks";
|
|
4
|
+
import { X } from "lucide-solid";
|
|
5
|
+
import { cn } from "@/lib/cn";
|
|
6
|
+
|
|
7
|
+
export const Popover = KobaltePopover;
|
|
8
|
+
|
|
9
|
+
export const PopoverTrigger = KobaltePopover.Trigger;
|
|
10
|
+
|
|
11
|
+
export const PopoverArrow: Component<ComponentProps<typeof KobaltePopover.Arrow>> = (props) => {
|
|
12
|
+
const [local, rest] = splitProps(props, ["class"]);
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<KobaltePopover.Arrow
|
|
16
|
+
class={cn("fill-popover stroke-border", local.class)}
|
|
17
|
+
{...rest}
|
|
18
|
+
/>
|
|
19
|
+
);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const PopoverContent: Component<ComponentProps<typeof KobaltePopover.Content>> = (props) => {
|
|
23
|
+
const [local, rest] = splitProps(props, ["class", "children", "onInteractOutside"]);
|
|
24
|
+
let contentRef: HTMLElement | undefined;
|
|
25
|
+
|
|
26
|
+
createClickOutside({
|
|
27
|
+
target: () => contentRef,
|
|
28
|
+
onInteractOutside: (e) => {
|
|
29
|
+
if (typeof local.onInteractOutside === "function") {
|
|
30
|
+
local.onInteractOutside(e as any);
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<KobaltePopover.Portal>
|
|
37
|
+
<KobaltePopover.Content
|
|
38
|
+
ref={(el) => {
|
|
39
|
+
contentRef = el;
|
|
40
|
+
if (typeof (props as any).ref === "function") (props as any).ref(el);
|
|
41
|
+
}}
|
|
42
|
+
class={cn(
|
|
43
|
+
"z-50 w-72 rounded-lg border border-border bg-popover p-4 text-popover-foreground shadow-md 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 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
44
|
+
local.class
|
|
45
|
+
)}
|
|
46
|
+
{...rest}
|
|
47
|
+
>
|
|
48
|
+
{local.children}
|
|
49
|
+
</KobaltePopover.Content>
|
|
50
|
+
</KobaltePopover.Portal>
|
|
51
|
+
);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const PopoverTitle: Component<ComponentProps<typeof KobaltePopover.Title>> = (props) => {
|
|
55
|
+
const [local, rest] = splitProps(props, ["class"]);
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<KobaltePopover.Title
|
|
59
|
+
class={cn("text-sm font-semibold text-foreground", local.class)}
|
|
60
|
+
{...rest}
|
|
61
|
+
/>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const PopoverDescription: Component<ComponentProps<typeof KobaltePopover.Description>> = (props) => {
|
|
66
|
+
const [local, rest] = splitProps(props, ["class"]);
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<KobaltePopover.Description
|
|
70
|
+
class={cn("text-sm text-muted-foreground", local.class)}
|
|
71
|
+
{...rest}
|
|
72
|
+
/>
|
|
73
|
+
);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export const PopoverCloseButton: Component<ComponentProps<typeof KobaltePopover.CloseButton>> = (props) => {
|
|
77
|
+
const [local, rest] = splitProps(props, ["class", "children"]);
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<KobaltePopover.CloseButton
|
|
81
|
+
class={cn(
|
|
82
|
+
"absolute right-2 top-2 rounded-md p-1 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",
|
|
83
|
+
local.class
|
|
84
|
+
)}
|
|
85
|
+
{...rest}
|
|
86
|
+
>
|
|
87
|
+
{local.children || <X class="h-4 w-4 text-muted-foreground" />}
|
|
88
|
+
</KobaltePopover.CloseButton>
|
|
89
|
+
);
|
|
90
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { splitProps, Show, type Component, type JSX } from "solid-js";
|
|
2
|
+
import { Progress as KobalteProgress } from "@kobalte/core/progress";
|
|
3
|
+
import { cn } from "@/lib/cn";
|
|
4
|
+
|
|
5
|
+
export interface ProgressProps
|
|
6
|
+
extends Omit<
|
|
7
|
+
JSX.HTMLAttributes<HTMLDivElement>,
|
|
8
|
+
"value" | "aria-valuenow" | "aria-valuemin" | "aria-valuemax"
|
|
9
|
+
> {
|
|
10
|
+
/** The current numeric progress value. */
|
|
11
|
+
value?: number;
|
|
12
|
+
/** Minimum progress value. Defaults to 0. */
|
|
13
|
+
minValue?: number;
|
|
14
|
+
/** Maximum progress value. Defaults to 100. */
|
|
15
|
+
maxValue?: number;
|
|
16
|
+
/** Label text for accessibility and screen readers. */
|
|
17
|
+
getValueLabel?: (params: { value: number; max: number }) => string;
|
|
18
|
+
/** Custom label element to display above or beside progress bar. */
|
|
19
|
+
label?: JSX.Element;
|
|
20
|
+
/** Optional custom class for root container. */
|
|
21
|
+
class?: string;
|
|
22
|
+
/** Optional custom class for indicator fill bar. */
|
|
23
|
+
indicatorClass?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Nikala UI Progress component for showing task completion status or media timeline positions.
|
|
28
|
+
*/
|
|
29
|
+
export const Progress: Component<ProgressProps> = (props) => {
|
|
30
|
+
const [local, rest] = splitProps(props, [
|
|
31
|
+
"value",
|
|
32
|
+
"minValue",
|
|
33
|
+
"maxValue",
|
|
34
|
+
"getValueLabel",
|
|
35
|
+
"label",
|
|
36
|
+
"class",
|
|
37
|
+
"indicatorClass",
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<KobalteProgress
|
|
42
|
+
value={local.value ?? 0}
|
|
43
|
+
minValue={local.minValue ?? 0}
|
|
44
|
+
maxValue={local.maxValue ?? 100}
|
|
45
|
+
getValueLabel={local.getValueLabel}
|
|
46
|
+
class={cn("flex w-full flex-col gap-1.5", local.class)}
|
|
47
|
+
{...rest}
|
|
48
|
+
>
|
|
49
|
+
<Show when={local.label}>
|
|
50
|
+
<div class="flex justify-between text-xs font-medium text-muted-foreground">
|
|
51
|
+
<KobalteProgress.Label>{local.label}</KobalteProgress.Label>
|
|
52
|
+
<KobalteProgress.ValueLabel class="font-mono text-foreground" />
|
|
53
|
+
</div>
|
|
54
|
+
</Show>
|
|
55
|
+
|
|
56
|
+
<KobalteProgress.Track class="relative h-2 w-full overflow-hidden rounded-full bg-primary/20">
|
|
57
|
+
<KobalteProgress.Fill
|
|
58
|
+
class={cn(
|
|
59
|
+
"h-full w-[var(--kb-progress-fill-width)] bg-primary transition-all duration-300 ease-in-out",
|
|
60
|
+
local.indicatorClass
|
|
61
|
+
)}
|
|
62
|
+
/>
|
|
63
|
+
</KobalteProgress.Track>
|
|
64
|
+
</KobalteProgress>
|
|
65
|
+
);
|
|
66
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { splitProps, type JSX, type ValidComponent } from "solid-js";
|
|
2
2
|
import * as SelectPrimitive from "@kobalte/core/select";
|
|
3
3
|
import type { PolymorphicProps } from "@kobalte/core/polymorphic";
|
|
4
|
+
import { createClickOutside } from "@nikala-ui/hooks";
|
|
4
5
|
import { cn } from "@/lib/cn";
|
|
5
6
|
|
|
6
7
|
export type SelectRootProps<Option = any, OptGroup = any, T extends ValidComponent = "div"> =
|
|
@@ -89,10 +90,24 @@ export const SelectContent = <T extends ValidComponent = "div">(
|
|
|
89
90
|
props: PolymorphicProps<T, SelectContentProps<T>>
|
|
90
91
|
) => {
|
|
91
92
|
const [local, rest] = splitProps(props as SelectContentProps, ["class"]);
|
|
93
|
+
let contentRef: HTMLElement | undefined;
|
|
94
|
+
|
|
95
|
+
createClickOutside({
|
|
96
|
+
target: () => contentRef,
|
|
97
|
+
onInteractOutside: (e) => {
|
|
98
|
+
if (typeof (props as any).onInteractOutside === "function") {
|
|
99
|
+
(props as any).onInteractOutside(e);
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
});
|
|
92
103
|
|
|
93
104
|
return (
|
|
94
105
|
<SelectPrimitive.Portal>
|
|
95
106
|
<SelectPrimitive.Content
|
|
107
|
+
ref={(el) => {
|
|
108
|
+
contentRef = el;
|
|
109
|
+
if (typeof (props as any).ref === "function") (props as any).ref(el);
|
|
110
|
+
}}
|
|
96
111
|
class={cn(
|
|
97
112
|
"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",
|
|
98
113
|
local.class
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { splitProps, type Component, type JSX } from "solid-js";
|
|
2
|
+
import { createControllableSignal } from "@nikala-ui/hooks";
|
|
2
3
|
import { cn } from "@/lib/cn";
|
|
3
4
|
|
|
4
5
|
export interface SwitchProps
|
|
@@ -16,7 +17,6 @@ export interface SwitchProps
|
|
|
16
17
|
* Nikala UI Switch component for boolean toggle inputs built for SolidJS with Tailwind CSS v4 styling.
|
|
17
18
|
*/
|
|
18
19
|
export const Switch: Component<SwitchProps> = (props) => {
|
|
19
|
-
// Use splitProps to preserve SolidJS reactivity
|
|
20
20
|
const [local, rest] = splitProps(props, [
|
|
21
21
|
"checked",
|
|
22
22
|
"defaultChecked",
|
|
@@ -26,28 +26,17 @@ export const Switch: Component<SwitchProps> = (props) => {
|
|
|
26
26
|
"onClick",
|
|
27
27
|
]);
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
local.defaultChecked ?? false
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
// Computed checked state supporting both controlled and uncontrolled modes
|
|
35
|
-
const isChecked = () =>
|
|
36
|
-
local.checked !== undefined ? local.checked : internalChecked();
|
|
29
|
+
const [isChecked, setIsChecked] = createControllableSignal({
|
|
30
|
+
value: () => local.checked,
|
|
31
|
+
defaultValue: local.defaultChecked ?? false,
|
|
32
|
+
onChange: (val) => local.onChange?.(val),
|
|
33
|
+
});
|
|
37
34
|
|
|
38
35
|
const toggle = (
|
|
39
36
|
e: MouseEvent & { currentTarget: HTMLButtonElement; target: Element }
|
|
40
37
|
) => {
|
|
41
38
|
if (local.disabled) return;
|
|
42
|
-
|
|
43
|
-
const nextState = !isChecked();
|
|
44
|
-
if (local.checked === undefined) {
|
|
45
|
-
setInternalChecked(nextState);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (typeof local.onChange === "function") {
|
|
49
|
-
local.onChange(nextState);
|
|
50
|
-
}
|
|
39
|
+
setIsChecked(!isChecked());
|
|
51
40
|
|
|
52
41
|
if (typeof local.onClick === "function") {
|
|
53
42
|
local.onClick(e);
|
|
@@ -58,7 +47,7 @@ export const Switch: Component<SwitchProps> = (props) => {
|
|
|
58
47
|
<button
|
|
59
48
|
type="button"
|
|
60
49
|
role="switch"
|
|
61
|
-
aria-checked={isChecked()}
|
|
50
|
+
aria-checked={Boolean(isChecked())}
|
|
62
51
|
data-state={isChecked() ? "checked" : "unchecked"}
|
|
63
52
|
disabled={local.disabled}
|
|
64
53
|
onClick={toggle}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createContext,
|
|
3
3
|
useContext,
|
|
4
|
-
createSignal,
|
|
5
4
|
splitProps,
|
|
6
5
|
Show,
|
|
7
6
|
type Component,
|
|
8
7
|
type JSX,
|
|
9
8
|
type Accessor,
|
|
10
9
|
} from "solid-js";
|
|
10
|
+
import { createControllableSignal } from "@nikala-ui/hooks";
|
|
11
11
|
import { cn } from "@/lib/cn";
|
|
12
12
|
|
|
13
13
|
interface TabsContextValue {
|
|
@@ -44,25 +44,17 @@ export const Tabs: Component<TabsProps> = (props) => {
|
|
|
44
44
|
"children",
|
|
45
45
|
]);
|
|
46
46
|
|
|
47
|
-
const [
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
const [currentValue, setCurrentValue] = createControllableSignal<string>({
|
|
48
|
+
value: () => local.value,
|
|
49
|
+
defaultValue: local.defaultValue,
|
|
50
|
+
onChange: (val) => local.onChange?.(val),
|
|
51
|
+
});
|
|
51
52
|
|
|
52
53
|
const orientation = () => local.orientation || "horizontal";
|
|
53
54
|
|
|
54
|
-
const handleSelect = (val: string) => {
|
|
55
|
-
if (local.value === undefined) {
|
|
56
|
-
setInternalValue(val);
|
|
57
|
-
}
|
|
58
|
-
if (typeof local.onChange === "function") {
|
|
59
|
-
local.onChange(val);
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
|
|
63
55
|
const contextValue: TabsContextValue = {
|
|
64
56
|
value: currentValue,
|
|
65
|
-
setValue:
|
|
57
|
+
setValue: (val: string) => setCurrentValue(val),
|
|
66
58
|
orientation,
|
|
67
59
|
};
|
|
68
60
|
|
|
@@ -21,6 +21,8 @@ import {
|
|
|
21
21
|
} from "./dropdown-menu";
|
|
22
22
|
import { cn } from "@/lib/cn";
|
|
23
23
|
|
|
24
|
+
import { createColorMode } from "@nikala-ui/hooks";
|
|
25
|
+
|
|
24
26
|
export interface ThemeToggleProps {
|
|
25
27
|
/** Display mode: "mini" for compact dropdown, "max" for full customizer panel (default: "mini") */
|
|
26
28
|
mode?: "mini" | "max";
|
|
@@ -54,23 +56,26 @@ export const ThemeToggle: Component<ThemeToggleProps> = (props) => {
|
|
|
54
56
|
const [local] = splitProps(props, ["mode", "effect", "class"]);
|
|
55
57
|
const { theme, setTheme, accent, setAccent, radius, setRadius } = useTheme();
|
|
56
58
|
|
|
59
|
+
const colorMode = createColorMode({
|
|
60
|
+
initialValue: theme(),
|
|
61
|
+
storageKey: "nikala-theme-mode",
|
|
62
|
+
});
|
|
63
|
+
|
|
57
64
|
const mode = () => local.mode || "mini";
|
|
58
65
|
const effect = () => local.effect || "none";
|
|
59
66
|
|
|
60
|
-
/* Reactive accessor determining whether dark mode is active */
|
|
67
|
+
/* Reactive accessor determining whether dark mode is active via createColorMode hook */
|
|
61
68
|
const isDarkMode = () => {
|
|
62
69
|
const currentTheme = theme();
|
|
63
70
|
if (currentTheme === "dark") return true;
|
|
64
71
|
if (currentTheme === "light") return false;
|
|
65
|
-
return (
|
|
66
|
-
typeof window !== "undefined" &&
|
|
67
|
-
window.matchMedia("(prefers-color-scheme: dark)").matches
|
|
68
|
-
);
|
|
72
|
+
return colorMode.isDark();
|
|
69
73
|
};
|
|
70
74
|
|
|
71
75
|
const changeThemeWithEffect = (newTheme: Theme, e: MouseEvent) => {
|
|
72
76
|
runThemeTransition(effect(), e, () => {
|
|
73
77
|
setTheme(newTheme);
|
|
78
|
+
colorMode.setMode(newTheme);
|
|
74
79
|
});
|
|
75
80
|
};
|
|
76
81
|
|
|
@@ -2,28 +2,40 @@ import { splitProps, type Component, type ComponentProps } from "solid-js";
|
|
|
2
2
|
import { Tooltip as KobalteTooltip } from "@kobalte/core/tooltip";
|
|
3
3
|
import { cn } from "@/lib/cn";
|
|
4
4
|
|
|
5
|
-
export const Tooltip
|
|
6
|
-
return <KobalteTooltip gutter={4} {...props} />;
|
|
7
|
-
};
|
|
5
|
+
export const Tooltip = KobalteTooltip;
|
|
8
6
|
|
|
9
7
|
export const TooltipTrigger = KobalteTooltip.Trigger;
|
|
10
8
|
|
|
9
|
+
export const TooltipArrow: Component<ComponentProps<typeof KobalteTooltip.Arrow>> = (props) => {
|
|
10
|
+
const [local, rest] = splitProps(props, ["class"]);
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<KobalteTooltip.Arrow
|
|
14
|
+
size={8}
|
|
15
|
+
class={cn("fill-popover stroke-border", local.class)}
|
|
16
|
+
{...rest}
|
|
17
|
+
/>
|
|
18
|
+
);
|
|
19
|
+
};
|
|
20
|
+
|
|
11
21
|
export interface TooltipContentProps extends ComponentProps<typeof KobalteTooltip.Content> {
|
|
12
22
|
class?: string;
|
|
13
23
|
}
|
|
14
24
|
|
|
15
25
|
export const TooltipContent: Component<TooltipContentProps> = (props) => {
|
|
16
|
-
const [local, rest] = splitProps(props, ["class"]);
|
|
26
|
+
const [local, rest] = splitProps(props, ["class", "children"]);
|
|
17
27
|
|
|
18
28
|
return (
|
|
19
29
|
<KobalteTooltip.Portal>
|
|
20
30
|
<KobalteTooltip.Content
|
|
21
31
|
class={cn(
|
|
22
|
-
"z-50
|
|
32
|
+
"z-50 rounded-md border border-border bg-popover px-3 py-1.5 text-xs text-popover-foreground shadow-md transition-all animate-in fade-in-0 zoom-in-95 data-closed:animate-out data-[closed]:fade-out-0 data-closed:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
23
33
|
local.class
|
|
24
34
|
)}
|
|
25
35
|
{...rest}
|
|
26
|
-
|
|
36
|
+
>
|
|
37
|
+
{local.children}
|
|
38
|
+
</KobalteTooltip.Content>
|
|
27
39
|
</KobalteTooltip.Portal>
|
|
28
40
|
);
|
|
29
41
|
};
|
package/src/registry/metadata.ts
CHANGED
|
@@ -159,4 +159,14 @@ export const COMPONENT_METADATA: Record<string, ComponentMeta> = {
|
|
|
159
159
|
description: "A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it, built on Kobalte primitives.",
|
|
160
160
|
dependencies: ["clsx", "tailwind-merge", "@kobalte/core"],
|
|
161
161
|
},
|
|
162
|
+
popover: {
|
|
163
|
+
title: "Popover",
|
|
164
|
+
description: "Displays rich content in a portal layer triggered by a button, built on Kobalte primitives.",
|
|
165
|
+
dependencies: ["clsx", "tailwind-merge", "@kobalte/core", "lucide-solid"],
|
|
166
|
+
},
|
|
167
|
+
progress: {
|
|
168
|
+
title: "Progress",
|
|
169
|
+
description: "Displays an indicator showing the completion progress of a task or media playback, built on Kobalte primitives.",
|
|
170
|
+
dependencies: ["clsx", "tailwind-merge", "@kobalte/core"],
|
|
171
|
+
},
|
|
162
172
|
};
|