@nikala-ui/core 0.6.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.
Files changed (62) hide show
  1. package/README.md +42 -0
  2. package/package.json +25 -0
  3. package/registry/accordion.json +18 -0
  4. package/registry/alert.json +18 -0
  5. package/registry/avatar.json +17 -0
  6. package/registry/badge.json +18 -0
  7. package/registry/banner.json +19 -0
  8. package/registry/breadcrumb.json +17 -0
  9. package/registry/button.json +18 -0
  10. package/registry/card.json +17 -0
  11. package/registry/checkbox.json +17 -0
  12. package/registry/command.json +25 -0
  13. package/registry/dialog.json +18 -0
  14. package/registry/dropdown-menu.json +18 -0
  15. package/registry/index.json +297 -0
  16. package/registry/input-group.json +21 -0
  17. package/registry/input.json +17 -0
  18. package/registry/kbd.json +18 -0
  19. package/registry/label.json +18 -0
  20. package/registry/list.json +20 -0
  21. package/registry/radio-group.json +18 -0
  22. package/registry/select.json +18 -0
  23. package/registry/separator.json +17 -0
  24. package/registry/sheet.json +19 -0
  25. package/registry/skeleton.json +17 -0
  26. package/registry/switch.json +17 -0
  27. package/registry/tabs.json +17 -0
  28. package/registry/textarea.json +17 -0
  29. package/registry/theme-manager.json +38 -0
  30. package/src/index.css +11 -0
  31. package/src/lib/cn.ts +9 -0
  32. package/src/registry/components/ui/accordion.tsx +128 -0
  33. package/src/registry/components/ui/alert.tsx +155 -0
  34. package/src/registry/components/ui/avatar.tsx +114 -0
  35. package/src/registry/components/ui/badge.tsx +50 -0
  36. package/src/registry/components/ui/banner.tsx +189 -0
  37. package/src/registry/components/ui/breadcrumb.tsx +136 -0
  38. package/src/registry/components/ui/button.tsx +63 -0
  39. package/src/registry/components/ui/card.tsx +113 -0
  40. package/src/registry/components/ui/checkbox.tsx +85 -0
  41. package/src/registry/components/ui/command.tsx +286 -0
  42. package/src/registry/components/ui/dialog.tsx +195 -0
  43. package/src/registry/components/ui/dropdown-menu.tsx +250 -0
  44. package/src/registry/components/ui/input-group.tsx +80 -0
  45. package/src/registry/components/ui/input.tsx +28 -0
  46. package/src/registry/components/ui/kbd.tsx +73 -0
  47. package/src/registry/components/ui/label.tsx +30 -0
  48. package/src/registry/components/ui/list.tsx +222 -0
  49. package/src/registry/components/ui/radio-group.tsx +95 -0
  50. package/src/registry/components/ui/select.tsx +138 -0
  51. package/src/registry/components/ui/separator.tsx +37 -0
  52. package/src/registry/components/ui/sheet.tsx +216 -0
  53. package/src/registry/components/ui/skeleton.tsx +24 -0
  54. package/src/registry/components/ui/switch.tsx +79 -0
  55. package/src/registry/components/ui/tabs.tsx +212 -0
  56. package/src/registry/components/ui/textarea.tsx +82 -0
  57. package/src/registry/components/ui/theme-toggle.tsx +195 -0
  58. package/src/registry/index.ts +50 -0
  59. package/src/registry/metadata.ts +152 -0
  60. package/src/registry/providers/theme-provider.tsx +208 -0
  61. package/src/registry/providers/theme-script.tsx +58 -0
  62. package/src/registry/providers/theme-transitions.ts +108 -0
package/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # @nikala-ui/core
2
+
3
+ Core design system components, providers, and registry manifests for **Nikala UI**.
4
+
5
+ Honoring the iconic Georgian painter **Niko Pirosmani (Nikala)**.
6
+
7
+ Official Documentation & Interactive Demos: [nikala.magradze.dev](https://nikala.magradze.dev)
8
+
9
+ ---
10
+
11
+ ## Overview
12
+
13
+ This package serves as the primary component registry and source code repository for Nikala UI. Components are fetched on demand via `@nikala-ui/cli` and written directly into your application's `src/components/ui` workspace directory.
14
+
15
+ ---
16
+
17
+ ## Core Features
18
+
19
+ - **26 Core UI Components** — Button, Input, Card, Badge, Avatar, Separator, Textarea, Label, Skeleton, Switch, Checkbox, Radio Group, Select, Tabs, Accordion, Breadcrumb, Alert, Dialog, Sheet, Dropdown Menu, Theme Manager, Banner, List, Kbd, InputGroup, Command.
20
+ - **Native SolidJS Reactivity** — Fine-grained signals with `splitProps` and context accessors.
21
+ - **100% WAI-ARIA Accessible** — Powered by Kobalte and Corvu headless primitives.
22
+ - **Tailwind CSS v4 Native** — Driven by `@theme inline` semantic design tokens.
23
+
24
+ ---
25
+
26
+ ## Usage
27
+
28
+ Use the official CLI client to add components to your SolidJS or SolidStart project:
29
+
30
+ ```bash
31
+ npx @nikala-ui/cli add
32
+ ```
33
+
34
+ ## Documentation & Links
35
+
36
+ - Repository: [github.com/magradze/nikala-ui](https://github.com/magradze/nikala-ui)
37
+ - Component Authoring Guide: [docs/COMPONENT_GUIDE.md](https://github.com/magradze/nikala-ui/blob/main/docs/COMPONENT_GUIDE.md)
38
+ - Theming System Guide: [docs/THEMING.md](https://github.com/magradze/nikala-ui/blob/main/docs/THEMING.md)
39
+
40
+ ## License
41
+
42
+ [MIT](https://github.com/magradze/nikala-ui/blob/main/LICENSE)
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@nikala-ui/core",
3
+ "version": "0.6.0",
4
+ "description": "Core component definitions, design tokens, and registry for Nikala UI",
5
+ "type": "module",
6
+ "private": false,
7
+ "files": [
8
+ "registry",
9
+ "src"
10
+ ],
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "scripts": {
15
+ "build:registry": "bun scripts/build-registry.ts"
16
+ },
17
+ "devDependencies": {
18
+ "@kobalte/core": "^0.13.12",
19
+ "class-variance-authority": "^0.7.1",
20
+ "clsx": "^2.1.1",
21
+ "corvu": "^0.7.2",
22
+ "solid-js": "^1.9.14",
23
+ "tailwind-merge": "^3.6.0"
24
+ }
25
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "accordion",
3
+ "title": "Accordion",
4
+ "description": "A vertically stacked set of interactive headings 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/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};",
15
+ "type": "registry:ui"
16
+ }
17
+ ]
18
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "alert",
3
+ "title": "Alert",
4
+ "description": "Displays a callout banner for user feedback with variants, dismiss button, and timer.",
5
+ "type": "registry:ui",
6
+ "dependencies": [
7
+ "clsx",
8
+ "tailwind-merge",
9
+ "class-variance-authority"
10
+ ],
11
+ "files": [
12
+ {
13
+ "path": "ui/alert.tsx",
14
+ "content": "import {\n createSignal,\n splitProps,\n onMount,\n onCleanup,\n Show,\n type Component,\n type JSX,\n} from \"solid-js\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"@/lib/cn\";\n\n/**\n * CVA variants for Alert status styles and color themes.\n */\nexport const alertVariants = cva(\n \"relative w-full rounded-lg border p-4 transition-all duration-200 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground\",\n {\n variants: {\n variant: {\n default:\n \"bg-background text-foreground border-border\",\n info:\n \"bg-blue-50 text-blue-900 border-blue-200 dark:bg-blue-950/50 dark:text-blue-200 dark:border-blue-900/50\",\n success:\n \"bg-emerald-50 text-emerald-900 border-emerald-200 dark:bg-emerald-950/50 dark:text-emerald-200 dark:border-emerald-900/50\",\n warning:\n \"bg-amber-50 text-amber-900 border-amber-200 dark:bg-amber-950/50 dark:text-amber-200 dark:border-amber-950/50\",\n destructive:\n \"bg-destructive/15 text-destructive border-destructive/30\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n },\n }\n);\n\nexport interface AlertProps\n extends JSX.HTMLAttributes<HTMLDivElement>,\n VariantProps<typeof alertVariants> {\n /** Display an interactive close (X) button */\n closable?: boolean;\n /** Auto-dismiss timer duration in milliseconds */\n duration?: number;\n /** Callback fired when the alert is dismissed */\n onClose?: () => void;\n class?: string;\n}\n\n/**\n * Nikala UI Alert notification banner with auto-dismiss timer and theme variants.\n */\nexport const Alert: Component<AlertProps> = (props) => {\n const [local, rest] = splitProps(props, [\n \"variant\",\n \"closable\",\n \"duration\",\n \"onClose\",\n \"class\",\n \"children\",\n ]);\n\n const [isVisible, setIsVisible] = createSignal(true);\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const handleClose = () => {\n setIsVisible(false);\n if (typeof local.onClose === \"function\") {\n local.onClose();\n }\n };\n\n onMount(() => {\n if (local.duration && local.duration > 0) {\n timer = setTimeout(() => {\n handleClose();\n }, local.duration);\n }\n });\n\n onCleanup(() => {\n if (timer) clearTimeout(timer);\n });\n\n return (\n <Show when={isVisible()}>\n <div\n role=\"alert\"\n class={cn(\n alertVariants({ variant: local.variant }),\n \"animate-in fade-in-0 duration-200\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n <Show when={local.closable || typeof local.onClose === \"function\"}>\n <button\n type=\"button\"\n aria-label=\"Close alert\"\n onClick={handleClose}\n class=\"absolute right-3 top-3 rounded-md p-1 text-zinc-500 hover:bg-zinc-100 hover:text-zinc-900 dark:text-zinc-400 dark:hover:bg-zinc-800 dark:hover:text-zinc-50 transition-colors cursor-pointer\"\n >\n <svg\n class=\"h-4 w-4\"\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=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </Show>\n </div>\n </Show>\n );\n};\n\nexport interface AlertTitleProps extends JSX.HTMLAttributes<HTMLHeadingElement> {\n class?: string;\n}\n\n/**\n * Header title element for the Alert banner.\n */\nexport const AlertTitle: Component<AlertTitleProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"]);\n\n return (\n <h5\n class={cn(\"mb-1 font-medium leading-none tracking-tight text-foreground\", local.class)}\n {...rest}\n />\n );\n};\n\nexport interface AlertDescriptionProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\n/**\n * Body text content for the Alert banner.\n */\nexport const AlertDescription: Component<AlertDescriptionProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"]);\n\n return (\n <div\n class={cn(\"text-sm [&_p]:leading-relaxed text-muted-foreground\", local.class)}\n {...rest}\n />\n );\n};",
15
+ "type": "registry:ui"
16
+ }
17
+ ]
18
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "avatar",
3
+ "title": "Avatar",
4
+ "description": "An image element with fallback representation for representing users.",
5
+ "type": "registry:ui",
6
+ "dependencies": [
7
+ "clsx",
8
+ "tailwind-merge"
9
+ ],
10
+ "files": [
11
+ {
12
+ "path": "ui/avatar.tsx",
13
+ "content": "import {\n createSignal,\n createEffect,\n Show,\n splitProps,\n type Component,\n type JSX,\n} from \"solid-js\";\nimport { cn } from \"@/lib/cn\";\n\nexport interface AvatarProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\n/**\n * Root Avatar container component.\n */\nexport const Avatar: Component<AvatarProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"]);\n\n return (\n <div\n class={cn(\n \"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full border border-border bg-muted\",\n local.class\n )}\n {...rest}\n />\n );\n};\n\nexport interface AvatarImageProps\n extends JSX.ImgHTMLAttributes<HTMLImageElement> {\n class?: string;\n}\n\n/**\n * Image element for the Avatar component with background status loader.\n */\nexport const AvatarImage: Component<AvatarImageProps> = (props) => {\n const [local, rest] = splitProps(props, [\n \"class\",\n \"src\",\n \"alt\",\n \"onLoad\",\n \"onError\",\n ]);\n const [status, setStatus] = createSignal<\"loading\" | \"loaded\" | \"error\">(\n \"loading\"\n );\n\n /* Pre-test image loading in background JS to prevent native broken icon flashes */\n createEffect(() => {\n const src = local.src;\n if (!src) {\n setStatus(\"error\");\n return;\n }\n\n const img = new Image();\n img.src = src;\n img.onload = () => setStatus(\"loaded\");\n img.onerror = () => setStatus(\"error\");\n });\n\n const handleLoad: JSX.EventHandlerUnion<HTMLImageElement, Event> = (e) => {\n setStatus(\"loaded\");\n if (typeof local.onLoad === \"function\") {\n (local.onLoad as (e: Event) => void)(e);\n }\n };\n\n const handleError: JSX.EventHandlerUnion<HTMLImageElement, Event> = (e) => {\n setStatus(\"error\");\n if (typeof local.onError === \"function\") {\n (local.onError as (e: Event) => void)(e);\n }\n };\n\n return (\n <Show when={status() === \"loaded\"}>\n <img\n src={local.src}\n alt={local.alt}\n class={cn(\"aspect-square h-full w-full object-cover\", local.class)}\n onLoad={handleLoad}\n onError={handleError}\n {...rest}\n />\n </Show>\n );\n};\n\nexport interface AvatarFallbackProps\n extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\n/**\n * Fallback container for rendering initials or icons when avatar image is missing/broken.\n */\nexport const AvatarFallback: Component<AvatarFallbackProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"]);\n\n return (\n <div\n class={cn(\n \"flex h-full w-full items-center justify-center bg-muted text-sm font-medium text-muted-foreground select-none\",\n local.class\n )}\n {...rest}\n />\n );\n};",
14
+ "type": "registry:ui"
15
+ }
16
+ ]
17
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "badge",
3
+ "title": "Badge",
4
+ "description": "A small badge component for status indicators and tags.",
5
+ "type": "registry:ui",
6
+ "dependencies": [
7
+ "clsx",
8
+ "tailwind-merge",
9
+ "class-variance-authority"
10
+ ],
11
+ "files": [
12
+ {
13
+ "path": "ui/badge.tsx",
14
+ "content": "import { splitProps, type Component, type JSX } from \"solid-js\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"@/lib/cn\";\n\n/**\n * Class variance authority configuration for badge styling variants.\n */\nexport const badgeVariants = cva(\n \"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2\",\n {\n variants: {\n variant: {\n default:\n \"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80\",\n secondary:\n \"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n destructive:\n \"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80\",\n outline:\n \"text-foreground border-border\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n },\n }\n);\n\n/**\n * Props interface for the Badge component extending standard HTML div attributes.\n */\nexport interface BadgeProps\n extends JSX.HTMLAttributes<HTMLDivElement>,\n VariantProps<typeof badgeVariants> {\n class?: string;\n}\n\n/**\n * Nikala UI Badge component built for SolidJS with Tailwind CSS v4 styling.\n */\nexport const Badge: Component<BadgeProps> = (props) => {\n // Use splitProps to preserve SolidJS reactivity\n const [local, rest] = splitProps(props, [\"variant\", \"class\", \"children\"]);\n\n return (\n <div class={cn(badgeVariants({ variant: local.variant }), local.class)} {...rest}>\n {local.children}\n </div>\n );\n};",
15
+ "type": "registry:ui"
16
+ }
17
+ ]
18
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "banner",
3
+ "title": "Banner",
4
+ "description": "An announcement banner with sticky positioning, dismissal persistence, auto-hide timer, Lucide icons, and variant styles.",
5
+ "type": "registry:ui",
6
+ "dependencies": [
7
+ "clsx",
8
+ "tailwind-merge",
9
+ "class-variance-authority",
10
+ "lucide-solid"
11
+ ],
12
+ "files": [
13
+ {
14
+ "path": "ui/banner.tsx",
15
+ "content": "// src/components/ui/banner.tsx\nimport {\n createSignal,\n onMount,\n onCleanup,\n Show,\n splitProps,\n type Component,\n type JSX,\n} from \"solid-js\";\nimport { Dynamic } from \"solid-js/web\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { Info, AlertTriangle, CheckCircle, AlertCircle, X } from \"lucide-solid\";\nimport { cn } from \"@/lib/cn\";\n\nexport const bannerVariants = cva(\n \"relative flex w-full items-center justify-between gap-3 px-4 py-2.5 text-sm transition-all duration-300 ease-in-out\",\n {\n variants: {\n variant: {\n default:\n \"bg-zinc-900 text-zinc-50 dark:bg-zinc-100 dark:text-zinc-900\",\n warning:\n \"bg-amber-500/15 text-amber-900 border-b border-amber-500/20 dark:text-amber-200\",\n info:\n \"bg-sky-500/15 text-sky-900 border-b border-sky-500/20 dark:text-sky-200\",\n success:\n \"bg-emerald-500/15 text-emerald-900 border-b border-emerald-500/20 dark:text-emerald-200\",\n destructive:\n \"bg-rose-500/15 text-rose-900 border-b border-rose-500/20 dark:text-rose-200\",\n pirosmani:\n \"bg-[#722f37] text-white border-b border-[#8a3943] shadow-sm\",\n },\n sticky: {\n true: \"sticky top-0 z-50 backdrop-blur-sm\",\n false: \"relative\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n sticky: false,\n },\n }\n);\n\nexport interface BannerProps\n extends JSX.HTMLAttributes<HTMLDivElement>,\n VariantProps<typeof bannerVariants> {\n dismissible?: boolean;\n autoHideDelay?: number;\n storageKey?: string;\n showIcon?: boolean;\n icon?: Component<{ class?: string }>;\n link?: string;\n linkText?: string;\n linkTarget?: string;\n onDismiss?: () => void;\n class?: string;\n}\n\n/**\n * Nikala UI Banner Component.\n */\nexport const Banner: Component<BannerProps> = (props) => {\n const [local, rest] = splitProps(props, [\n \"variant\",\n \"sticky\",\n \"dismissible\",\n \"autoHideDelay\",\n \"storageKey\",\n \"showIcon\",\n \"icon\",\n \"link\",\n \"linkText\",\n \"linkTarget\",\n \"onDismiss\",\n \"class\",\n \"children\",\n ]);\n\n const [visible, setVisible] = createSignal(true);\n let timerId: ReturnType<typeof setTimeout> | undefined;\n\n onMount(() => {\n if (local.storageKey) {\n try {\n const isDismissed = localStorage.getItem(local.storageKey);\n if (isDismissed === \"true\") {\n setVisible(false);\n return;\n }\n } catch (e) { }\n }\n\n if (local.autoHideDelay && local.autoHideDelay > 0) {\n timerId = setTimeout(() => {\n handleDismiss();\n }, local.autoHideDelay);\n }\n });\n\n onCleanup(() => {\n if (timerId) clearTimeout(timerId);\n });\n\n const handleDismiss = () => {\n setVisible(false);\n\n if (local.storageKey) {\n try {\n localStorage.setItem(local.storageKey, \"true\");\n } catch (e) {}\n }\n\n if (typeof local.onDismiss === \"function\") {\n local.onDismiss();\n }\n };\n\n /* Default icon selector based on banner variant */\n const getDefaultIcon = () => {\n switch (local.variant) {\n case \"warning\":\n return AlertTriangle;\n case \"success\":\n return CheckCircle;\n case \"destructive\":\n return AlertCircle;\n default:\n return Info;\n }\n };\n\n const activeIcon = () => local.icon || getDefaultIcon();\n const shouldShowIcon = () => local.showIcon !== false;\n const shouldBeDismissible = () => local.dismissible !== false;\n\n return (\n <Show when={visible()}>\n <div\n class={cn(\n bannerVariants({ variant: local.variant, sticky: local.sticky }),\n local.class\n )}\n {...rest}\n >\n <div class=\"flex flex-1 items-center justify-center gap-2 text-center sm:text-left\">\n {/* Dynamic Lucide Icon */}\n <Show when={shouldShowIcon()}>\n <span class=\"inline-flex shrink-0 items-center justify-center\">\n <Dynamic component={activeIcon()} class=\"h-4 w-4 opacity-80\" />\n </span>\n </Show>\n\n {/* Text Content */}\n <div class=\"flex-1 text-xs font-medium sm:text-sm\">\n {local.children}\n </div>\n\n {/* Action Link */}\n <Show when={local.link}>\n <div class=\"shrink-0\">\n <a\n href={local.link}\n target={local.linkTarget || \"_blank\"}\n rel=\"noreferrer\"\n class=\"underline font-semibold hover:opacity-80 transition-opacity\"\n >\n {local.linkText || \"Learn more\"}\n </a>\n </div>\n </Show>\n </div>\n\n {/* Close Button */}\n <Show when={shouldBeDismissible()}>\n <button\n type=\"button\"\n onClick={handleDismiss}\n class=\"shrink-0 rounded-md p-1 opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring\"\n aria-label=\"Dismiss banner\"\n >\n <X class=\"h-4 w-4\" />\n </button>\n </Show>\n </div>\n </Show>\n );\n};",
16
+ "type": "registry:ui"
17
+ }
18
+ ]
19
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "breadcrumb",
3
+ "title": "Breadcrumb",
4
+ "description": "Displays the path to the current resource using a hierarchy of links.",
5
+ "type": "registry:ui",
6
+ "dependencies": [
7
+ "clsx",
8
+ "tailwind-merge"
9
+ ],
10
+ "files": [
11
+ {
12
+ "path": "ui/breadcrumb.tsx",
13
+ "content": "import { splitProps, type Component, type JSX } from \"solid-js\";\nimport { cn } from \"@/lib/cn\";\n\nexport interface BreadcrumbProps extends JSX.HTMLAttributes<HTMLElement> {\n class?: string;\n}\n\n/**\n * Root navigation container for the Nikala UI Breadcrumb hierarchy.\n */\nexport const Breadcrumb: Component<BreadcrumbProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"]);\n return <nav aria-label=\"breadcrumb\" class={cn(\"relative w-full\", local.class)} {...rest} />;\n};\n\nexport interface BreadcrumbListProps extends JSX.HTMLAttributes<HTMLOListElement> {\n class?: string;\n}\n\n/**\n * Ordered list container housing all breadcrumb path items.\n */\nexport const BreadcrumbList: Component<BreadcrumbListProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"]);\n return (\n <ol\n class={cn(\n \"flex flex-wrap items-center gap-1.5 text-sm text-muted-foreground break-words sm:gap-2.5\",\n local.class\n )}\n {...rest}\n />\n );\n};\n\nexport interface BreadcrumbItemProps extends JSX.HTMLAttributes<HTMLLIElement> {\n class?: string;\n}\n\n/**\n * Individual list item element wrapper in the breadcrumb hierarchy.\n */\nexport const BreadcrumbItem: Component<BreadcrumbItemProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"]);\n return <li class={cn(\"inline-flex items-center gap-1.5\", local.class)} {...rest} />;\n};\n\nexport interface BreadcrumbLinkProps extends JSX.AnchorHTMLAttributes<HTMLAnchorElement> {\n class?: string;\n}\n\n/**\n * Clickable breadcrumb trail link element.\n */\nexport const BreadcrumbLink: Component<BreadcrumbLinkProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"]);\n return (\n <a\n class={cn(\n \"transition-colors hover:text-foreground underline-offset-4 hover:underline\",\n local.class\n )}\n {...rest}\n />\n );\n};\n\nexport interface BreadcrumbPageProps extends JSX.HTMLAttributes<HTMLSpanElement> {\n class?: string;\n}\n\n/**\n * Non-clickable active page indicator in breadcrumb hierarchy.\n */\nexport const BreadcrumbPage: Component<BreadcrumbPageProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"]);\n return (\n <span\n role=\"link\"\n aria-disabled=\"true\"\n aria-current=\"page\"\n class={cn(\"font-normal text-foreground\", local.class)}\n {...rest}\n />\n );\n};\n\nexport interface BreadcrumbSeparatorProps extends JSX.HTMLAttributes<HTMLLIElement> {\n class?: string;\n}\n\n/**\n * Visual separator icon rendered between breadcrumb path links.\n */\nexport const BreadcrumbSeparator: Component<BreadcrumbSeparatorProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n return (\n <li\n role=\"presentation\"\n aria-hidden=\"true\"\n class={cn(\"[&>svg]:size-3.5 text-muted-foreground\", local.class)}\n {...rest}\n >\n {local.children || (\n <svg 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 )}\n </li>\n );\n};\n\nexport interface BreadcrumbEllipsisProps extends JSX.HTMLAttributes<HTMLSpanElement> {\n class?: string;\n}\n\n/**\n * Ellipsis element representing truncated intermediate breadcrumb items.\n */\nexport const BreadcrumbEllipsis: Component<BreadcrumbEllipsisProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"]);\n return (\n <span\n role=\"presentation\"\n aria-hidden=\"true\"\n class={cn(\"flex h-9 w-9 items-center justify-center text-muted-foreground\", local.class)}\n {...rest}\n >\n <svg class=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <circle cx=\"12\" cy=\"12\" r=\"1\" />\n <circle cx=\"19\" cy=\"12\" r=\"1\" />\n <circle cx=\"5\" cy=\"12\" r=\"1\" />\n </svg>\n </span>\n );\n};",
14
+ "type": "registry:ui"
15
+ }
16
+ ]
17
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "button",
3
+ "title": "Button",
4
+ "description": "An interactive button component with variant and size options.",
5
+ "type": "registry:ui",
6
+ "dependencies": [
7
+ "clsx",
8
+ "tailwind-merge",
9
+ "class-variance-authority"
10
+ ],
11
+ "files": [
12
+ {
13
+ "path": "ui/button.tsx",
14
+ "content": "import { splitProps, type Component, type JSX } from \"solid-js\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"@/lib/cn\";\n\n/**\n * Class variance authority configuration for button styling variants and sizes.\n */\nexport const buttonVariants = cva(\n \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 cursor-pointer\",\n {\n variants: {\n variant: {\n default:\n \"bg-primary text-primary-foreground shadow hover:bg-primary/90\",\n destructive:\n \"bg-red-600 text-zinc-50 shadow-sm hover:bg-red-600/90 dark:bg-red-900 dark:text-zinc-50 dark:hover:bg-red-900/90\",\n outline:\n \"border border-input bg-background hover:bg-accent hover:text-accent-foreground\",\n secondary:\n \"bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n ghost:\n \"hover:bg-accent hover:text-accent-foreground\",\n link: \"text-primary underline-offset-4 hover:underline\",\n },\n size: {\n default: \"h-9 px-4 py-2\",\n sm: \"h-8 rounded-md px-3 text-xs\",\n lg: \"h-10 rounded-md px-8\",\n icon: \"h-9 w-9\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n }\n);\n\n/**\n * Props interface for the Button component extending standard HTML button attributes.\n */\nexport interface ButtonProps\n extends JSX.ButtonHTMLAttributes<HTMLButtonElement>,\n VariantProps<typeof buttonVariants> {\n class?: string;\n}\n\n/**\n * Nikala UI Button component built for SolidJS with Tailwind CSS v4 styling.\n */\nexport const Button: Component<ButtonProps> = (props) => {\n // Use splitProps to preserve SolidJS reactivity for destructured props\n const [local, rest] = splitProps(props, [\"variant\", \"size\", \"class\", \"children\"]);\n\n return (\n <button\n class={cn(buttonVariants({ variant: local.variant, size: local.size }), local.class)}\n {...rest}\n >\n {local.children}\n </button>\n );\n};",
15
+ "type": "registry:ui"
16
+ }
17
+ ]
18
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "card",
3
+ "title": "Card",
4
+ "description": "A versatile container component with header, content, and footer sections.",
5
+ "type": "registry:ui",
6
+ "dependencies": [
7
+ "clsx",
8
+ "tailwind-merge"
9
+ ],
10
+ "files": [
11
+ {
12
+ "path": "ui/card.tsx",
13
+ "content": "import { splitProps, type Component, type JSX } from \"solid-js\";\nimport { cn } from \"@/lib/cn\";\n\nexport interface CardProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\n/**\n * Root Card container component.\n */\nexport const Card: Component<CardProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"]);\n\n return (\n <div\n class={cn(\n \"rounded-lg border border-border bg-card text-card-foreground shadow-sm\",\n local.class\n )}\n {...rest}\n />\n );\n};\n\nexport interface CardHeaderProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\n/**\n * Header wrapper for the Card component.\n */\nexport const CardHeader: Component<CardHeaderProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"]);\n\n return (\n <div\n class={cn(\"flex flex-col space-y-1.5 p-6\", local.class)}\n {...rest}\n />\n );\n};\n\nexport interface CardTitleProps extends JSX.HTMLAttributes<HTMLHeadingElement> {\n class?: string;\n}\n\n/**\n * Main title heading for the Card header.\n */\nexport const CardTitle: Component<CardTitleProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"]);\n\n return (\n <h3\n class={cn(\"font-semibold leading-none tracking-tight text-lg\", local.class)}\n {...rest}\n />\n );\n};\n\nexport interface CardDescriptionProps extends JSX.HTMLAttributes<HTMLParagraphElement> {\n class?: string;\n}\n\n/**\n * Subtitle description text for the Card header.\n */\nexport const CardDescription: Component<CardDescriptionProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"]);\n\n return (\n <p\n class={cn(\"text-sm text-muted-foreground\", local.class)}\n {...rest}\n />\n );\n};\n\nexport interface CardContentProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\n/**\n * Main body content area for the Card.\n */\nexport const CardContent: Component<CardContentProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"]);\n\n return (\n <div\n class={cn(\"p-6 pt-0\", local.class)}\n {...rest}\n />\n );\n};\n\nexport interface CardFooterProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\n/**\n * Footer action area for the Card.\n */\nexport const CardFooter: Component<CardFooterProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"]);\n\n return (\n <div\n class={cn(\"flex items-center p-6 pt-0\", local.class)}\n {...rest}\n />\n );\n};",
14
+ "type": "registry:ui"
15
+ }
16
+ ]
17
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "checkbox",
3
+ "title": "Checkbox",
4
+ "description": "A control that allows the user to toggle between checked and unchecked options.",
5
+ "type": "registry:ui",
6
+ "dependencies": [
7
+ "clsx",
8
+ "tailwind-merge"
9
+ ],
10
+ "files": [
11
+ {
12
+ "path": "ui/checkbox.tsx",
13
+ "content": "import { createSignal, splitProps, Show, type Component, type JSX } from \"solid-js\";\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 // Use splitProps to preserve SolidJS reactivity\n const [local, rest] = splitProps(props, [\n \"checked\",\n \"defaultChecked\",\n \"onChange\",\n \"disabled\",\n \"class\",\n \"onClick\",\n ]);\n\n // Internal state for uncontrolled mode\n const [internalChecked, setInternalChecked] = createSignal(\n local.defaultChecked ?? false\n );\n\n // Computed checked state\n const isChecked = () =>\n local.checked !== undefined ? local.checked : internalChecked();\n\n const toggle = (\n e: MouseEvent & { currentTarget: HTMLButtonElement; target: Element }\n ) => {\n if (local.disabled) return;\n\n const nextState = !isChecked();\n if (local.checked === undefined) {\n setInternalChecked(nextState);\n }\n\n if (typeof local.onChange === \"function\") {\n local.onChange(nextState);\n }\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={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
+ "type": "registry:ui"
15
+ }
16
+ ]
17
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "command",
3
+ "title": "Command / Command Palette",
4
+ "description": "Fast, accessible command palette and search modal built on Kobalte Dialog primitives with auto-filtering.",
5
+ "type": "registry:ui",
6
+ "dependencies": [
7
+ "clsx",
8
+ "tailwind-merge",
9
+ "class-variance-authority",
10
+ "lucide-solid",
11
+ "@kobalte/core"
12
+ ],
13
+ "registryDependencies": [
14
+ "kbd",
15
+ "input-group",
16
+ "list"
17
+ ],
18
+ "files": [
19
+ {
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-xl 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-xl 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
+ "type": "registry:ui"
23
+ }
24
+ ]
25
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "dialog",
3
+ "title": "Dialog",
4
+ "description": "A modal window overlaying the main content, built on Kobalte primitives with blur and outside-click options.",
5
+ "type": "registry:ui",
6
+ "dependencies": [
7
+ "clsx",
8
+ "tailwind-merge",
9
+ "@kobalte/core"
10
+ ],
11
+ "files": [
12
+ {
13
+ "path": "ui/dialog.tsx",
14
+ "content": "import { splitProps, type Component, type JSX, type ValidComponent, Show } from \"solid-js\";\nimport * as DialogPrimitive from \"@kobalte/core/dialog\";\nimport type { PolymorphicProps } from \"@kobalte/core/polymorphic\";\nimport { cn } from \"@/lib/cn\";\n\nexport type DialogRootProps = DialogPrimitive.DialogRootProps;\n\n/**\n * Root Dialog component managing modal open state.\n */\nexport const Dialog: Component<DialogRootProps> = (props) => {\n return <DialogPrimitive.Root {...props} />;\n};\n\nexport const DialogTrigger = DialogPrimitive.Trigger;\n\nexport type DialogOverlayProps<T extends ValidComponent = \"div\"> =\n DialogPrimitive.DialogOverlayProps<T> & {\n /** Whether to apply background blur effect (default: true) */\n blur?: boolean;\n class?: string;\n };\n\n/**\n * Backdrop overlay wrapper with optional backdrop blur styling.\n */\nexport const DialogOverlay = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, DialogOverlayProps<T>>\n) => {\n const [local, rest] = splitProps(props as DialogOverlayProps, [\"class\", \"blur\"]);\n\n return (\n <DialogPrimitive.Overlay\n class={cn(\n \"fixed inset-0 z-50 bg-black/80 data-[expanded]:animate-in data-[closed]:animate-out data-[closed]:fade-out-0 data-[expanded]:fade-in-0\",\n local.blur !== false && \"backdrop-blur-sm\",\n local.class\n )}\n {...(rest as any)}\n />\n );\n};\n\nexport type DialogContentProps<T extends ValidComponent = \"div\"> =\n DialogPrimitive.DialogContentProps<T> & {\n /** Whether to show the top-right close (X) button (default: true) */\n showCloseButton?: boolean;\n /** Whether clicking outside closes the dialog (default: true) */\n closeOnOutsideClick?: boolean;\n /** Whether to apply background backdrop blur (default: true) */\n blur?: boolean;\n class?: string;\n children?: JSX.Element;\n };\n\n/**\n * Main dialog popup window housing header, content, and footer.\n */\nexport const DialogContent = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, DialogContentProps<T>>\n) => {\n const [local, rest] = splitProps(props as DialogContentProps, [\n \"class\",\n \"children\",\n \"showCloseButton\",\n \"closeOnOutsideClick\",\n \"blur\",\n \"onPointerDownOutside\",\n \"onInteractOutside\",\n ]);\n\n const handlePointerDownOutside = (e: Event) => {\n if (local.closeOnOutsideClick === false) {\n e.preventDefault();\n }\n if (typeof local.onPointerDownOutside === \"function\") {\n local.onPointerDownOutside(e as any);\n }\n };\n\n const handleInteractOutside = (e: Event) => {\n if (local.closeOnOutsideClick === false) {\n e.preventDefault();\n }\n if (typeof local.onInteractOutside === \"function\") {\n local.onInteractOutside(e as any);\n }\n };\n\n return (\n <DialogPrimitive.Portal>\n {/* Forward blur prop to DialogOverlay */}\n <DialogOverlay blur={local.blur} />\n <DialogPrimitive.Content\n onPointerDownOutside={handlePointerDownOutside}\n onInteractOutside={handleInteractOutside}\n class={cn(\n \"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-border bg-card p-6 shadow-lg duration-200 data-[expanded]:animate-in data-[closed]:animate-out data-[closed]:fade-out-0 data-[expanded]:fade-in-0 data-[closed]:zoom-out-95 data-[expanded]:zoom-in-95 data-[closed]:slide-out-to-left-1/2 data-[closed]:slide-out-to-top-[48%] data-[expanded]:slide-in-from-left-1/2 data-[expanded]:slide-in-from-top-[48%] sm:rounded-lg text-card-foreground\",\n local.class\n )}\n {...(rest as any)}\n >\n {local.children}\n <Show when={local.showCloseButton !== false}>\n <DialogPrimitive.CloseButton class=\"absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[expanded]:bg-accent data-[expanded]:text-muted-foreground cursor-pointer\">\n <svg class=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n <span class=\"sr-only\">Close</span>\n </DialogPrimitive.CloseButton>\n </Show>\n </DialogPrimitive.Content>\n </DialogPrimitive.Portal>\n );\n};\n\nexport interface DialogHeaderProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\n/**\n * Dialog top header container.\n */\nexport const DialogHeader: Component<DialogHeaderProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"]);\n\n return (\n <div\n class={cn(\"flex flex-col space-y-1.5 text-center sm:text-left\", local.class)}\n {...rest}\n />\n );\n};\n\nexport interface DialogFooterProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\n/**\n * Dialog bottom action buttons area.\n */\nexport const DialogFooter: Component<DialogFooterProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"]);\n\n return (\n <div\n class={cn(\"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2\", local.class)}\n {...rest}\n />\n );\n};\n\nexport type DialogTitleProps<T extends ValidComponent = \"h2\"> =\n DialogPrimitive.DialogTitleProps<T> & {\n class?: string;\n };\n\n/**\n * Dialog title heading.\n */\nexport const DialogTitle = <T extends ValidComponent = \"h2\">(\n props: PolymorphicProps<T, DialogTitleProps<T>>\n) => {\n const [local, rest] = splitProps(props as DialogTitleProps, [\"class\"]);\n\n return (\n <DialogPrimitive.Title\n class={cn(\"text-lg font-semibold leading-none tracking-tight text-foreground\", local.class)}\n {...(rest as any)}\n />\n );\n};\n\nexport type DialogDescriptionProps<T extends ValidComponent = \"p\"> =\n DialogPrimitive.DialogDescriptionProps<T> & {\n class?: string;\n };\n\n/**\n * Dialog subtitle or description text.\n */\nexport const DialogDescription = <T extends ValidComponent = \"p\">(\n props: PolymorphicProps<T, DialogDescriptionProps<T>>\n) => {\n const [local, rest] = splitProps(props as DialogDescriptionProps, [\"class\"]);\n\n return (\n <DialogPrimitive.Description\n class={cn(\"text-sm text-muted-foreground\", local.class)}\n {...(rest as any)}\n />\n );\n};\n\nexport const DialogClose = DialogPrimitive.CloseButton;",
15
+ "type": "registry:ui"
16
+ }
17
+ ]
18
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "dropdown-menu",
3
+ "title": "Dropdown Menu",
4
+ "description": "Displays a menu to the user—such as a set of actions or functions—triggered by a button or avatar.",
5
+ "type": "registry:ui",
6
+ "dependencies": [
7
+ "clsx",
8
+ "tailwind-merge",
9
+ "@kobalte/core"
10
+ ],
11
+ "files": [
12
+ {
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};",
15
+ "type": "registry:ui"
16
+ }
17
+ ]
18
+ }