@nikala-ui/core 0.11.0 → 0.12.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 +43 -2
- package/registry/api-table.json +21 -0
- package/registry/banner.json +1 -1
- package/registry/button.json +1 -1
- package/registry/callout.json +19 -0
- package/registry/checkbox.json +4 -1
- package/registry/code-block.json +26 -0
- package/registry/code-group.json +20 -0
- package/registry/combobox.json +1 -1
- package/registry/command.json +3 -2
- package/registry/component-viewer.json +25 -0
- package/registry/container.json +18 -0
- package/registry/context-menu.json +1 -1
- package/registry/create-app-updater.json +13 -0
- package/registry/create-document-tabs.json +13 -0
- package/registry/create-global-shortcut.json +13 -0
- package/registry/create-tauri-window.json +13 -0
- package/registry/create-tiptap-editor.json +34 -0
- package/registry/dialog.json +1 -1
- package/registry/dropdown-menu.json +3 -2
- package/registry/field.json +1 -1
- package/registry/file-tree.json +21 -0
- package/registry/footer.json +1 -1
- package/registry/form-message.json +3 -2
- package/registry/icon-button.json +1 -1
- package/registry/index.json +336 -5
- package/registry/navbar.json +1 -1
- package/registry/navigation-menu.json +1 -1
- package/registry/number-input.json +1 -1
- package/registry/package-manager-tabs.json +24 -0
- package/registry/pager.json +21 -0
- package/registry/popover.json +4 -1
- package/registry/resizable.json +1 -1
- package/registry/rich-text-editor.json +95 -0
- package/registry/scroll-area.json +1 -1
- package/registry/section-heading.json +21 -0
- package/registry/select.json +3 -2
- package/registry/sheet.json +1 -1
- package/registry/sidebar.json +1 -1
- package/registry/status.json +1 -1
- package/registry/steps.json +20 -0
- package/registry/switch.json +4 -1
- package/registry/table-of-contents.json +23 -0
- package/registry/tabs.json +4 -1
- package/registry/theme-manager.json +6 -5
- package/registry/titlebar-tabs.json +24 -0
- package/registry/titlebar.json +26 -0
- package/registry/toggle-group.json +1 -1
- package/registry/updater-modal.json +26 -0
- package/src/index.ts +48 -0
- package/src/registry/components/ui/api-table.tsx +117 -0
- package/src/registry/components/ui/banner.tsx +0 -2
- package/src/registry/components/ui/button.tsx +1 -1
- package/src/registry/components/ui/callout.tsx +137 -0
- package/src/registry/components/ui/checkbox.tsx +1 -1
- package/src/registry/components/ui/code-block.tsx +326 -0
- package/src/registry/components/ui/code-group.tsx +105 -0
- package/src/registry/components/ui/combobox.tsx +83 -18
- package/src/registry/components/ui/command.tsx +110 -106
- package/src/registry/components/ui/component-viewer.tsx +363 -0
- package/src/registry/components/ui/container.tsx +45 -0
- package/src/registry/components/ui/context-menu.tsx +67 -22
- package/src/registry/components/ui/dialog.tsx +42 -32
- package/src/registry/components/ui/dropdown-menu.tsx +69 -31
- package/src/registry/components/ui/field.tsx +7 -3
- package/src/registry/components/ui/file-tree.tsx +181 -0
- package/src/registry/components/ui/footer.tsx +1 -1
- package/src/registry/components/ui/form-message.tsx +2 -2
- package/src/registry/components/ui/icon-button.tsx +1 -1
- package/src/registry/components/ui/navbar.tsx +18 -11
- package/src/registry/components/ui/navigation-menu.tsx +2 -2
- package/src/registry/components/ui/number-input.tsx +3 -3
- package/src/registry/components/ui/package-manager-tabs.tsx +226 -0
- package/src/registry/components/ui/pager.tsx +102 -0
- package/src/registry/components/ui/popover.tsx +1 -1
- package/src/registry/components/ui/resizable.tsx +3 -1
- package/src/registry/components/ui/rich-text-editor/bubble-menu.tsx +236 -0
- package/src/registry/components/ui/rich-text-editor/editor.tsx +308 -0
- package/src/registry/components/ui/rich-text-editor/extensions.ts +116 -0
- package/src/registry/components/ui/rich-text-editor/footer.tsx +36 -0
- package/src/registry/components/ui/rich-text-editor/image-dialog.tsx +75 -0
- package/src/registry/components/ui/rich-text-editor/index.ts +11 -0
- package/src/registry/components/ui/rich-text-editor/link-dialog.tsx +48 -0
- package/src/registry/components/ui/rich-text-editor/markdown.ts +170 -0
- package/src/registry/components/ui/rich-text-editor/slash-command.tsx +301 -0
- package/src/registry/components/ui/rich-text-editor/table-controls.tsx +68 -0
- package/src/registry/components/ui/rich-text-editor/toolbar.tsx +465 -0
- package/src/registry/components/ui/rich-text-editor/use-editor.ts +231 -0
- package/src/registry/components/ui/scroll-area.tsx +12 -2
- package/src/registry/components/ui/section-heading.tsx +108 -0
- package/src/registry/components/ui/select.tsx +16 -19
- package/src/registry/components/ui/sheet.tsx +58 -53
- package/src/registry/components/ui/sidebar.tsx +4 -4
- package/src/registry/components/ui/status.tsx +7 -5
- package/src/registry/components/ui/steps.tsx +198 -0
- package/src/registry/components/ui/switch.tsx +1 -1
- package/src/registry/components/ui/table-of-contents.tsx +131 -0
- package/src/registry/components/ui/tabs.tsx +1 -1
- package/src/registry/components/ui/theme-toggle.tsx +175 -139
- package/src/registry/components/ui/titlebar-tabs.tsx +773 -0
- package/src/registry/components/ui/titlebar.tsx +468 -0
- package/src/registry/components/ui/toggle-group.tsx +1 -1
- package/src/registry/components/ui/updater-modal.tsx +254 -0
- package/src/registry/metadata.ts +167 -6
- package/src/registry/providers/theme-provider.tsx +16 -8
- package/src/registry/providers/theme-script.tsx +29 -9
- package/src/registry/providers/theme-transitions.ts +81 -50
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nikala-ui/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Core component definitions, design tokens, and registry for Nikala UI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -8,6 +8,27 @@
|
|
|
8
8
|
"registry",
|
|
9
9
|
"src"
|
|
10
10
|
],
|
|
11
|
+
"main": "./src/index.ts",
|
|
12
|
+
"module": "./src/index.ts",
|
|
13
|
+
"types": "./src/index.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./src/index.ts",
|
|
17
|
+
"import": "./src/index.ts"
|
|
18
|
+
},
|
|
19
|
+
"./ui/*": {
|
|
20
|
+
"types": "./src/registry/components/ui/*.tsx",
|
|
21
|
+
"import": "./src/registry/components/ui/*.tsx"
|
|
22
|
+
},
|
|
23
|
+
"./lib/*": {
|
|
24
|
+
"types": "./src/lib/*.ts",
|
|
25
|
+
"import": "./src/lib/*.ts"
|
|
26
|
+
},
|
|
27
|
+
"./providers/*": {
|
|
28
|
+
"types": "./src/registry/providers/*.tsx",
|
|
29
|
+
"import": "./src/registry/providers/*.tsx"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
11
32
|
"publishConfig": {
|
|
12
33
|
"access": "public"
|
|
13
34
|
},
|
|
@@ -32,6 +53,26 @@
|
|
|
32
53
|
},
|
|
33
54
|
"dependencies": {
|
|
34
55
|
"@nikala-ui/hooks": "workspace:*",
|
|
35
|
-
"
|
|
56
|
+
"@tiptap/core": "^3.31.0",
|
|
57
|
+
"@tiptap/extension-character-count": "^3.31.0",
|
|
58
|
+
"@tiptap/extension-highlight": "^3.31.0",
|
|
59
|
+
"@tiptap/extension-image": "^3.31.0",
|
|
60
|
+
"@tiptap/extension-link": "^3.31.0",
|
|
61
|
+
"@tiptap/extension-placeholder": "^3.31.0",
|
|
62
|
+
"@tiptap/extension-subscript": "^3.31.0",
|
|
63
|
+
"@tiptap/extension-superscript": "^3.31.0",
|
|
64
|
+
"@tiptap/extension-table": "^3.31.0",
|
|
65
|
+
"@tiptap/extension-table-cell": "^3.31.0",
|
|
66
|
+
"@tiptap/extension-table-header": "^3.31.0",
|
|
67
|
+
"@tiptap/extension-table-row": "^3.31.0",
|
|
68
|
+
"@tiptap/extension-task-item": "^3.31.0",
|
|
69
|
+
"@tiptap/extension-task-list": "^3.31.0",
|
|
70
|
+
"@tiptap/extension-text-align": "^3.31.0",
|
|
71
|
+
"@tiptap/extension-typography": "^3.31.0",
|
|
72
|
+
"@tiptap/extension-underline": "^3.31.0",
|
|
73
|
+
"@tiptap/starter-kit": "^3.31.0",
|
|
74
|
+
"lucide-solid": "^1.28.0",
|
|
75
|
+
"shiki": "^4.4.3",
|
|
76
|
+
"tiptap-markdown": "^0.9.0"
|
|
36
77
|
}
|
|
37
78
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "api-table",
|
|
3
|
+
"title": "API Table",
|
|
4
|
+
"description": "A structured, clean API reference table component for documenting props, options, and events.",
|
|
5
|
+
"type": "registry:ui",
|
|
6
|
+
"dependencies": [
|
|
7
|
+
"clsx",
|
|
8
|
+
"tailwind-merge"
|
|
9
|
+
],
|
|
10
|
+
"registryDependencies": [
|
|
11
|
+
"table",
|
|
12
|
+
"section-heading"
|
|
13
|
+
],
|
|
14
|
+
"files": [
|
|
15
|
+
{
|
|
16
|
+
"path": "ui/api-table.tsx",
|
|
17
|
+
"content": "import {\n splitProps,\n For,\n Show,\n type JSX,\n type Component,\n} from \"solid-js\";\nimport { SectionHeading } from \"@/components/ui/section-heading\";\nimport {\n Table,\n TableHeader,\n TableBody,\n TableRow,\n TableHead,\n TableCell,\n} from \"@/components/ui/table\";\nimport { cn } from \"@/lib/cn\";\n\nexport interface ApiTableItem {\n /** Property, attribute, or method name */\n prop: string;\n /** TypeScript type definition string */\n type: string;\n /** Default value if optional */\n default?: string;\n /** Detailed description of purpose and usage */\n description: string;\n /** Whether the property is strictly required */\n required?: boolean;\n}\n\nexport interface ApiTableProps extends JSX.HTMLAttributes<HTMLDivElement> {\n /** Title header for this API section (e.g. component or interface name) */\n title?: string;\n /** Subtitle or explanatory note */\n description?: string;\n /** Badge label next to the title (defaults to \"Props\") */\n badge?: string;\n /** List of API properties to render in the table */\n items: ApiTableItem[];\n class?: string;\n}\n\n/**\n * Clean, structured API reference table component composed from Table and Badge primitives.\n */\nexport const ApiTable: Component<ApiTableProps> = (props) => {\n const [local, rest] = splitProps(props, [\n \"title\",\n \"description\",\n \"badge\",\n \"items\",\n \"class\",\n ]);\n\n return (\n <div class={cn(\"my-6 space-y-3\", local.class)} {...rest}>\n {/* Header */}\n <Show when={local.title}>\n <SectionHeading\n variant=\"compact\"\n title={local.title!}\n badge={local.badge || \"Props\"}\n description={local.description}\n />\n </Show>\n\n {/* Responsive Table Container Composed from Nikala Table Primitives */}\n <div class=\"rounded-lg border border-border bg-card/50 shadow-2xs overflow-hidden\">\n <Table class=\"text-xs\">\n <TableHeader class=\"bg-muted/40 font-mono text-[11px] uppercase tracking-wider text-muted-foreground select-none\">\n <TableRow class=\"hover:bg-transparent\">\n <TableHead class=\"h-9 px-4 font-semibold\">Prop</TableHead>\n <TableHead class=\"h-9 px-4 font-semibold\">Type</TableHead>\n <TableHead class=\"h-9 px-4 font-semibold\">Default</TableHead>\n <TableHead class=\"h-9 px-4 font-semibold\">Description</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody class=\"font-mono text-xs\">\n <For each={local.items}>\n {(item) => (\n <TableRow class=\"transition-colors hover:bg-muted/20\">\n <TableCell class=\"px-4 py-3 font-semibold text-primary\">\n <span class=\"inline-flex items-center gap-1\">\n {item.prop}\n <Show when={item.required}>\n <span class=\"text-rose-500 font-bold\" title=\"Required\">*</span>\n </Show>\n </span>\n </TableCell>\n <TableCell class=\"px-4 py-3 text-muted-foreground\">\n <code class=\"rounded-md bg-muted px-1.5 py-0.5 font-mono text-[11px] text-foreground\">\n {item.type}\n </code>\n </TableCell>\n <TableCell class=\"px-4 py-3 text-muted-foreground\">\n <Show\n when={item.default}\n fallback={<span class=\"text-muted-foreground/40 font-mono\">-</span>}\n >\n <code class=\"rounded-md bg-muted/60 px-1 py-0.5 font-mono text-[11px]\">\n {item.default}\n </code>\n </Show>\n </TableCell>\n <TableCell class=\"px-4 py-3 font-sans font-normal leading-relaxed text-muted-foreground\">\n {item.description}\n </TableCell>\n </TableRow>\n )}\n </For>\n </TableBody>\n </Table>\n </div>\n </div>\n );\n};\n",
|
|
18
|
+
"type": "registry:ui"
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|
package/registry/banner.json
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"files": [
|
|
13
13
|
{
|
|
14
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
|
|
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 },\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
16
|
"type": "registry:ui"
|
|
17
17
|
}
|
|
18
18
|
]
|
package/registry/button.json
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"files": [
|
|
15
15
|
{
|
|
16
16
|
"path": "ui/button.tsx",
|
|
17
|
-
"content": "import { Show, splitProps, type Component, type JSX } from \"solid-js\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"@/lib/cn\";\nimport { Spinner } from \"
|
|
17
|
+
"content": "import { Show, splitProps, type Component, type JSX } from \"solid-js\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"@/lib/cn\";\nimport { Spinner } from \"@/components/ui/spinner\";\n\n/**\n * Class variance authority configuration for button styling variants and sizes.\n */\nexport const buttonVariants = cva(\n \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 cursor-pointer\",\n {\n variants: {\n variant: {\n default:\n \"bg-primary text-primary-foreground shadow hover:bg-primary/90 border border-primary/50\",\n destructive:\n \"bg-red-600 text-zinc-50 shadow-sm hover:bg-red-600/90 dark:bg-red-900 dark:text-zinc-50 dark:hover:bg-red-900/90\",\n outline:\n \"border border-input bg-background hover:bg-accent hover:text-accent-foreground\",\n secondary:\n \"bg-secondary text-secondary-foreground hover:bg-secondary/80 border border-border\",\n ghost:\n \"hover:bg-accent hover:text-accent-foreground\",\n link: \"text-primary underline-offset-4 hover:underline\",\n success: \"*:bg-green-600 text-zinc-50 shadow-sm hover:bg-green-600/90 dark:bg-green-900 dark:text-zinc-50 dark:hover:bg-green-900/90\",\n warning: \"*:bg-yellow-600 text-zinc-50 shadow-sm hover:bg-yellow-600/90 dark:bg-yellow-900 dark:text-zinc-50 dark:hover:bg-yellow-900/90\",\n info: \"*:bg-blue-600 text-zinc-50 shadow-sm hover:bg-blue-600/90 dark:bg-blue-900 dark:text-zinc-50 dark:hover:bg-blue-900/90\",\n },\n size: {\n xs: \"h-6 rounded-md px-2 text-xs\",\n default: \"h-9 px-4 py-2\",\n sm: \"h-8 rounded-md px-3 text-xs\",\n lg: \"h-10 rounded-md px-8\",\n icon: \"h-9 w-9\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n }\n);\n\n/**\n * Props interface for the Button component extending standard HTML button attributes.\n */\nexport interface ButtonProps\n extends JSX.ButtonHTMLAttributes<HTMLButtonElement>,\n VariantProps<typeof buttonVariants> {\n /** Shows a loading spinner and prevents repeated clicks while active. */\n loading?: boolean;\n class?: string;\n}\n\n/**\n * Nikala UI Button component built for SolidJS with Tailwind CSS v4 styling.\n */\nexport const Button: Component<ButtonProps> = (props) => {\n // Use splitProps to preserve SolidJS reactivity for destructured props\n const [local, rest] = splitProps(props, [\n \"variant\",\n \"size\",\n \"class\",\n \"children\",\n \"loading\",\n \"disabled\",\n ]);\n\n return (\n <button\n class={cn(buttonVariants({ variant: local.variant, size: local.size }), local.class)}\n disabled={local.disabled || local.loading}\n aria-busy={local.loading ? \"true\" : undefined}\n {...rest}\n >\n <Show when={local.loading}>\n <Spinner size=\"sm\" class=\"text-current\" />\n </Show>\n {local.children}\n </button>\n );\n};\n",
|
|
18
18
|
"type": "registry:ui"
|
|
19
19
|
}
|
|
20
20
|
]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "callout",
|
|
3
|
+
"title": "Callout",
|
|
4
|
+
"description": "A semantic callout and alert block with status variants, icons, and titles for documentation and notices.",
|
|
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/callout.tsx",
|
|
15
|
+
"content": "import {\n splitProps,\n Show,\n type Component,\n type JSX,\n type ParentComponent,\n} from \"solid-js\";\nimport { Dynamic } from \"solid-js/web\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport {\n Info,\n Lightbulb,\n AlertTriangle,\n AlertCircle,\n CheckCircle2,\n Bookmark,\n} from \"lucide-solid\";\nimport { cn } from \"@/lib/cn\";\n\nexport const calloutVariants = cva(\n \"relative w-full rounded-lg border p-4 text-sm leading-relaxed transition-colors\",\n {\n variants: {\n variant: {\n note: \"border-border/80 bg-muted/50 text-foreground [&>svg]:text-foreground\",\n info: \"border-sky-500/30 bg-sky-500/10 text-sky-950 dark:text-sky-100 [&>svg]:text-sky-600 dark:[&>svg]:text-sky-400\",\n tip: \"border-emerald-500/30 bg-emerald-500/10 text-emerald-950 dark:text-emerald-100 [&>svg]:text-emerald-600 dark:[&>svg]:text-emerald-400\",\n warning: \"border-amber-500/30 bg-amber-500/10 text-amber-950 dark:text-amber-100 [&>svg]:text-amber-600 dark:[&>svg]:text-amber-400\",\n danger: \"border-destructive/30 bg-destructive/10 text-destructive dark:text-red-200 [&>svg]:text-destructive\",\n success: \"border-emerald-500/30 bg-emerald-500/10 text-emerald-950 dark:text-emerald-100 [&>svg]:text-emerald-600 dark:[&>svg]:text-emerald-400\",\n },\n },\n defaultVariants: {\n variant: \"note\",\n },\n }\n);\n\nconst defaultIcons = {\n note: Bookmark,\n info: Info,\n tip: Lightbulb,\n warning: AlertTriangle,\n danger: AlertCircle,\n success: CheckCircle2,\n};\n\nexport interface CalloutProps\n extends JSX.HTMLAttributes<HTMLDivElement>,\n VariantProps<typeof calloutVariants> {\n title?: string;\n type?: VariantProps<typeof calloutVariants>[\"variant\"];\n icon?: Component<{ class?: string }> | false;\n class?: string;\n}\n\nexport const Callout: ParentComponent<CalloutProps> = (props) => {\n const [local, rest] = splitProps(props, [\n \"variant\",\n \"type\",\n \"title\",\n \"icon\",\n \"class\",\n \"children\",\n ]);\n\n const variant = () => local.variant || local.type || \"note\";\n\n const IconComponent = () => {\n if (local.icon === false) return null;\n if (local.icon) return local.icon;\n return defaultIcons[variant()] || Info;\n };\n\n return (\n <div\n role=\"region\"\n aria-label={local.title || `${variant()} callout`}\n class={cn(\n calloutVariants({ variant: variant() }),\n \"flex gap-3.5 items-start\",\n local.class\n )}\n {...rest}\n >\n <Show when={IconComponent()}>\n {(Icon) => (\n <span class=\"inline-flex shrink-0 items-center justify-center mt-0.5 select-none\">\n <Dynamic component={Icon()} class=\"size-4.5 shrink-0\" />\n </span>\n )}\n </Show>\n <div class=\"flex-1 space-y-1 min-w-0\">\n <Show when={local.title}>\n <h5 class=\"font-semibold text-sm leading-none tracking-tight\">\n {local.title}\n </h5>\n </Show>\n <div class=\"text-sm opacity-90 leading-relaxed break-words\">\n {local.children}\n </div>\n </div>\n </div>\n );\n};\n\nexport interface CalloutTitleProps extends JSX.HTMLAttributes<HTMLHeadingElement> {\n class?: string;\n}\n\nexport const CalloutTitle: ParentComponent<CalloutTitleProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n return (\n <h5\n class={cn(\"font-semibold text-sm leading-none tracking-tight mb-1\", local.class)}\n {...rest}\n >\n {local.children}\n </h5>\n );\n};\n\nexport interface CalloutDescriptionProps extends JSX.HTMLAttributes<HTMLParagraphElement> {\n class?: string;\n}\n\nexport const CalloutDescription: ParentComponent<CalloutDescriptionProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n return (\n <div\n class={cn(\"text-sm opacity-90 leading-relaxed\", local.class)}\n {...rest}\n >\n {local.children}\n </div>\n );\n};\n",
|
|
16
|
+
"type": "registry:ui"
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
}
|
package/registry/checkbox.json
CHANGED
|
@@ -7,10 +7,13 @@
|
|
|
7
7
|
"clsx",
|
|
8
8
|
"tailwind-merge"
|
|
9
9
|
],
|
|
10
|
+
"registryDependencies": [
|
|
11
|
+
"create-controllable-signal"
|
|
12
|
+
],
|
|
10
13
|
"files": [
|
|
11
14
|
{
|
|
12
15
|
"path": "ui/checkbox.tsx",
|
|
13
|
-
"content": "import { splitProps, Show, type Component, type JSX } from \"solid-js\";\nimport { createControllableSignal } from \"
|
|
16
|
+
"content": "import { splitProps, Show, type Component, type JSX } from \"solid-js\";\nimport { createControllableSignal } from \"@/hooks/create-controllable-signal\";\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={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
17
|
"type": "registry:ui"
|
|
15
18
|
}
|
|
16
19
|
]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "code-block",
|
|
3
|
+
"title": "Code Block",
|
|
4
|
+
"description": "A code block container with filename header, language indicator, and interactive copy to clipboard button.",
|
|
5
|
+
"type": "registry:ui",
|
|
6
|
+
"dependencies": [
|
|
7
|
+
"clsx",
|
|
8
|
+
"tailwind-merge",
|
|
9
|
+
"lucide-solid",
|
|
10
|
+
"shiki"
|
|
11
|
+
],
|
|
12
|
+
"registryDependencies": [
|
|
13
|
+
"button",
|
|
14
|
+
"badge",
|
|
15
|
+
"tooltip",
|
|
16
|
+
"tabs",
|
|
17
|
+
"create-clipboard"
|
|
18
|
+
],
|
|
19
|
+
"files": [
|
|
20
|
+
{
|
|
21
|
+
"path": "ui/code-block.tsx",
|
|
22
|
+
"content": "import {\n createSignal,\n createEffect,\n createMemo,\n splitProps,\n For,\n Show,\n type JSX,\n type ParentComponent,\n} from \"solid-js\";\nimport { Check, Copy } from \"lucide-solid\";\nimport { Button } from \"@/components/ui/button\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Tabs, TabsList, TabsTrigger } from \"@/components/ui/tabs\";\nimport { Tooltip, TooltipTrigger, TooltipContent } from \"@/components/ui/tooltip\";\nimport { createClipboard } from \"@/hooks/create-clipboard\";\nimport { cn } from \"@/lib/cn\";\n\nexport type CodeBlockPackageManager = \"bunx\" | \"npx\" | \"pnpm\" | \"yarn\";\n\nexport interface CodeBlockProps extends JSX.HTMLAttributes<HTMLDivElement> {\n /** The raw source code text to display and copy */\n code?: string;\n /** File name to display in the header bar (e.g. \"App.tsx\", \"main.rs\") */\n filename?: string;\n /** Programming language badge (e.g. \"tsx\", \"rust\", \"bash\") */\n language?: string;\n /** Alias for language */\n lang?: string;\n /** Whether the code snippet is an executable CLI command */\n isCli?: boolean;\n /** Shows the interactive package manager runner tabs (bunx, npx, pnpm, yarn) */\n showPmSwitcher?: boolean;\n /** List of package manager runners to show */\n managers?: CodeBlockPackageManager[];\n /** Whether the copy button is enabled. Defaults to true. */\n copyable?: boolean;\n class?: string;\n}\n\nlet shikiHighlighterPromise: Promise<any> | null = null;\n\nasync function getShikiHighlighter() {\n if (typeof window === \"undefined\") return null;\n if (!shikiHighlighterPromise) {\n try {\n const { createHighlighter } = await import(\"shiki\");\n shikiHighlighterPromise = createHighlighter({\n themes: [\"github-dark\", \"github-light\"],\n langs: [\n \"typescript\",\n \"javascript\",\n \"tsx\",\n \"jsx\",\n \"bash\",\n \"json\",\n \"css\",\n \"html\",\n \"rust\",\n ],\n });\n } catch (e) {\n console.warn(\"Failed to load Shiki highlighter\", e);\n return null;\n }\n }\n return shikiHighlighterPromise;\n}\n\nfunction highlightCliCommand(code: string): string {\n const trimmed = code.trim();\n const parts = trimmed.split(/\\s+/);\n if (parts.length === 0) return code;\n\n const isRunner = [\"bunx\", \"npx\", \"pnpm\", \"yarn\", \"bun\", \"npm\", \"cargo\", \"deno\"].includes(parts[0]);\n if (!isRunner) return `<span class=\"text-foreground\">${escapeHtml(trimmed)}</span>`;\n\n return parts\n .map((part, index) => {\n if (index === 0) {\n return `<span class=\"text-amber-500 dark:text-amber-400 font-bold\">${escapeHtml(part)}</span>`;\n }\n if (part.startsWith(\"-\")) {\n return `<span class=\"text-purple-400 dark:text-purple-300\">${escapeHtml(part)}</span>`;\n }\n if (part.startsWith(\"@\")) {\n return `<span class=\"text-emerald-400 dark:text-emerald-300 font-medium\">${escapeHtml(part)}</span>`;\n }\n if (index === 1 && [\"add\", \"install\", \"init\", \"create\", \"run\"].includes(part)) {\n return `<span class=\"text-sky-500 dark:text-sky-400 font-medium\">${escapeHtml(part)}</span>`;\n }\n return `<span class=\"text-foreground/90\">${escapeHtml(part)}</span>`;\n })\n .join(\" \");\n}\n\nfunction transformCommandForPm(cmd: string, pm: CodeBlockPackageManager): string {\n const trimmed = cmd.trim();\n const runners = [\"bunx\", \"npx\", \"pnpm dlx\", \"pnpm\", \"yarn dlx\", \"yarn\", \"bun\", \"npm\"];\n\n let base = trimmed;\n for (const r of runners) {\n if (trimmed.startsWith(r)) {\n base = trimmed.slice(r.length).trim();\n break;\n }\n }\n\n switch (pm) {\n case \"bunx\":\n return `bunx ${base}`;\n case \"npx\":\n return `npx ${base}`;\n case \"pnpm\":\n return `pnpm dlx ${base}`;\n case \"yarn\":\n return `yarn dlx ${base}`;\n default:\n return `${pm} ${base}`;\n }\n}\n\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n\nexport const CodeBlock: ParentComponent<CodeBlockProps> = (props) => {\n const [local, rest] = splitProps(props, [\n \"id\",\n \"code\",\n \"filename\",\n \"language\",\n \"lang\",\n \"isCli\",\n \"showPmSwitcher\",\n \"managers\",\n \"copyable\",\n \"class\",\n \"children\",\n ]);\n\n const clipboard = createClipboard();\n const [activePm, setActivePm] = createSignal<CodeBlockPackageManager>(\"bunx\");\n const [highlightedHtml, setHighlightedHtml] = createSignal(\"\");\n\n const effectiveLang = () => (local.lang || local.language || \"tsx\").toLowerCase();\n const copyable = () => local.copyable ?? true;\n const defaultManagers: CodeBlockPackageManager[] = [\"bunx\", \"npx\", \"pnpm\", \"yarn\"];\n const managersList = () => local.managers || defaultManagers;\n\n const isBashCli = () => {\n const l = effectiveLang();\n return l === \"bash\" || l === \"sh\" || l === \"shell\" || Boolean(local.isCli);\n };\n\n const cleanCode = createMemo(() => {\n const raw = (local.code || \"\").trim();\n if (local.isCli || local.showPmSwitcher) {\n return transformCommandForPm(raw, activePm());\n }\n return raw;\n });\n\n createEffect(() => {\n const codeStr = cleanCode();\n const lang = effectiveLang();\n\n if (isBashCli()) {\n setHighlightedHtml(highlightCliCommand(codeStr));\n return;\n }\n\n if (!codeStr) {\n setHighlightedHtml(\"\");\n return;\n }\n\n // Attempt Shiki highlighting\n getShikiHighlighter().then((highlighter) => {\n if (highlighter) {\n try {\n const html = highlighter.codeToHtml(codeStr, {\n lang: lang === \"js\" ? \"javascript\" : lang === \"ts\" ? \"typescript\" : lang,\n themes: {\n light: \"github-light\",\n dark: \"github-dark\",\n },\n });\n setHighlightedHtml(html);\n return;\n } catch (err) {\n console.warn(`Shiki failed for language: ${lang}`, err);\n }\n }\n // Fallback\n setHighlightedHtml(`<pre class=\"text-foreground/90\"><code>${escapeHtml(codeStr)}</code></pre>`);\n });\n });\n\n const handleCopy = async () => {\n let textToCopy = cleanCode();\n if (!textToCopy && typeof window !== \"undefined\") {\n const target = document.querySelector(`[data-code-block-id=\"${local.id}\"]`);\n if (target) textToCopy = target.textContent || \"\";\n }\n\n if (!textToCopy) return;\n await clipboard.copy(textToCopy);\n };\n\n const shouldShowSwitcher = () => Boolean(local.showPmSwitcher || local.isCli);\n const hasHeader = () => Boolean(shouldShowSwitcher() || local.filename || effectiveLang());\n\n return (\n <div\n class={cn(\n \"group relative my-4 w-full overflow-hidden rounded-lg border border-border bg-muted/40 font-mono text-sm shadow-2xs\",\n local.class\n )}\n {...rest}\n >\n {/* Code Header Bar */}\n <Show when={hasHeader()}>\n <div class=\"flex h-9 items-center justify-between border-b border-border/70 bg-muted/70 px-3.5 text-xs text-muted-foreground select-none\">\n <div class=\"flex items-center gap-2\">\n <Show when={shouldShowSwitcher()}>\n <Tabs\n value={activePm()}\n onChange={(val) => setActivePm(val as CodeBlockPackageManager)}\n class=\"w-auto flex-none\"\n >\n <TabsList class=\"w-auto inline-flex h-auto bg-background/60 p-0.5 border border-border/50 gap-0.5 rounded-md font-mono\">\n <For each={managersList()}>\n {(pm) => (\n <TabsTrigger\n value={pm}\n class=\"rounded-xs px-2 py-0.5 text-[11px] font-mono transition-colors cursor-pointer data-[state=active]:bg-primary data-[state=active]:text-primary-foreground data-[state=active]:font-semibold data-[state=active]:shadow-2xs\"\n >\n {pm}\n </TabsTrigger>\n )}\n </For>\n </TabsList>\n </Tabs>\n </Show>\n\n <Show when={local.filename}>\n <span class=\"font-medium text-foreground tracking-tight\">{local.filename}</span>\n </Show>\n\n <Show when={effectiveLang() && !shouldShowSwitcher() && !local.filename}>\n <Badge variant=\"outline\" class=\"uppercase text-[10px] px-1.5 py-0 h-4 font-mono font-semibold\">\n {effectiveLang()}\n </Badge>\n </Show>\n </div>\n\n <Show when={copyable() && (local.code || cleanCode())}>\n <Tooltip>\n <TooltipTrigger\n as={Button}\n variant=\"ghost\"\n size=\"icon\"\n onClick={handleCopy}\n aria-label={clipboard.copied() ? \"Copied code\" : \"Copy code\"}\n class=\"size-6 p-0 rounded-xs text-muted-foreground hover:bg-background hover:text-foreground cursor-pointer\"\n >\n <Show when={clipboard.copied()} fallback={<Copy class=\"size-3.5\" />}>\n <Check class=\"size-3.5 text-emerald-500 dark:text-emerald-400\" />\n </Show>\n </TooltipTrigger>\n <TooltipContent class=\"text-[10px] py-1 px-2\">\n {clipboard.copied() ? \"Copied!\" : \"Copy code\"}\n </TooltipContent>\n </Tooltip>\n </Show>\n </div>\n </Show>\n\n {/* Floating Copy Button (if no header bar is present) */}\n <Show when={!hasHeader() && copyable() && (local.code || cleanCode())}>\n <div class=\"absolute right-2.5 top-2.5 z-10 opacity-0 transition-opacity group-hover:opacity-100 focus-within:opacity-100\">\n <Tooltip>\n <TooltipTrigger\n as={Button}\n variant=\"outline\"\n size=\"icon\"\n onClick={handleCopy}\n aria-label={clipboard.copied() ? \"Copied code\" : \"Copy code\"}\n class=\"size-7 rounded-md border-border/80 bg-background/90 text-muted-foreground backdrop-blur-xs hover:bg-muted hover:text-foreground cursor-pointer shadow-2xs\"\n >\n <Show when={clipboard.copied()} fallback={<Copy class=\"size-3.5\" />}>\n <Check class=\"size-3.5 text-emerald-500 dark:text-emerald-400\" />\n </Show>\n </TooltipTrigger>\n <TooltipContent class=\"text-[10px] py-1 px-2\">\n {clipboard.copied() ? \"Copied!\" : \"Copy code\"}\n </TooltipContent>\n </Tooltip>\n </div>\n </Show>\n\n {/* Code Container */}\n <div class=\"overflow-x-auto p-4 text-[13px] leading-relaxed [scrollbar-width:thin]\">\n <Show\n when={local.children}\n fallback={\n <div\n class=\"font-mono text-sm leading-relaxed overflow-x-auto [&>pre]:!bg-transparent [&>pre]:!p-0 [&>pre]:!m-0 [&>pre]:!border-none\"\n innerHTML={highlightedHtml() || `<pre class=\"text-foreground/90\"><code>${escapeHtml(cleanCode())}</code></pre>`}\n />\n }\n >\n <pre class=\"shiki nikala-code-block m-0 overflow-x-auto bg-transparent p-0 font-mono text-xs\">\n {local.children}\n </pre>\n </Show>\n </div>\n </div>\n );\n};\n",
|
|
23
|
+
"type": "registry:ui"
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "code-group",
|
|
3
|
+
"title": "Code Group",
|
|
4
|
+
"description": "A compound tabbed code container for organizing multiple files, languages, and snippets built on Tabs.",
|
|
5
|
+
"type": "registry:ui",
|
|
6
|
+
"dependencies": [
|
|
7
|
+
"clsx",
|
|
8
|
+
"tailwind-merge"
|
|
9
|
+
],
|
|
10
|
+
"registryDependencies": [
|
|
11
|
+
"tabs"
|
|
12
|
+
],
|
|
13
|
+
"files": [
|
|
14
|
+
{
|
|
15
|
+
"path": "ui/code-group.tsx",
|
|
16
|
+
"content": "import {\n splitProps,\n type JSX,\n type ParentComponent,\n} from \"solid-js\";\nimport {\n Tabs,\n TabsList,\n TabsTrigger,\n TabsContent,\n} from \"@/components/ui/tabs\";\nimport { cn } from \"@/lib/cn\";\n\nexport interface CodeGroupProps\n extends Omit<JSX.HTMLAttributes<HTMLDivElement>, \"onChange\"> {\n /** Controlled active tab value */\n value?: string;\n /** Default active tab value */\n defaultValue?: string;\n /** Callback fired when active tab changes */\n onChange?: (value: string) => void;\n class?: string;\n}\n\n/**\n * Tabbed code container composed directly from Nikala UI Tabs primitives.\n */\nexport const CodeGroup: ParentComponent<CodeGroupProps> = (props) => {\n const [local, rest] = splitProps(props, [\n \"value\",\n \"defaultValue\",\n \"onChange\",\n \"class\",\n \"children\",\n ]);\n\n return (\n <Tabs\n value={local.value}\n defaultValue={local.defaultValue}\n onChange={local.onChange}\n class={cn(\n \"my-4 w-full gap-0 overflow-hidden rounded-lg border border-border bg-muted/40 font-mono text-sm shadow-2xs\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n </Tabs>\n );\n};\n\nexport interface CodeGroupListProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const CodeGroupList: ParentComponent<CodeGroupListProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n return (\n <div class={cn(\"flex h-9 items-center justify-between border-b border-border/70 bg-muted/70 px-3 select-none\", local.class)}>\n <TabsList class=\"h-auto bg-background/60 p-0.5 border border-border/50 gap-0.5 rounded-md\" {...rest}>\n {local.children}\n </TabsList>\n </div>\n );\n};\n\nexport interface CodeGroupTriggerProps extends JSX.ButtonHTMLAttributes<HTMLButtonElement> {\n value: string;\n class?: string;\n}\n\nexport const CodeGroupTrigger: ParentComponent<CodeGroupTriggerProps> = (props) => {\n const [local, rest] = splitProps(props, [\"value\", \"class\", \"children\"]);\n return (\n <TabsTrigger\n value={local.value}\n class={cn(\n \"rounded-xs px-2 py-0.5 text-[11px] font-mono transition-colors cursor-pointer data-[state=active]:bg-primary data-[state=active]:text-primary-foreground data-[state=active]:font-semibold data-[state=active]:shadow-2xs\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n </TabsTrigger>\n );\n};\n\nexport interface CodeGroupContentProps extends JSX.HTMLAttributes<HTMLDivElement> {\n value: string;\n class?: string;\n}\n\nexport const CodeGroupContent: ParentComponent<CodeGroupContentProps> = (props) => {\n const [local, rest] = splitProps(props, [\"value\", \"class\", \"children\"]);\n return (\n <TabsContent\n value={local.value}\n class={cn(\"p-4 text-[13px] leading-relaxed overflow-x-auto m-0 focus-visible:outline-none\", local.class)}\n {...rest}\n >\n {local.children}\n </TabsContent>\n );\n};\n",
|
|
17
|
+
"type": "registry:ui"
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
package/registry/combobox.json
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"files": [
|
|
15
15
|
{
|
|
16
16
|
"path": "ui/combobox.tsx",
|
|
17
|
-
"content": "import { splitProps, type JSX, type ValidComponent } from \"solid-js\";\nimport { Check, ChevronDown, X } from \"lucide-solid\";\nimport { ScrollArea } from \"./scroll-area\";\nimport * as ComboboxPrimitive from \"@kobalte/core/combobox\";\nimport { cn } from \"@/lib/cn\";\n\nexport type ComboboxRootProps<Option = any, OptGroup = any, T extends ValidComponent = \"div\"> =\n ComboboxPrimitive.ComboboxRootProps<Option, OptGroup, T>;\n\n/**\n * Root Combobox primitive component wrapper.\n */\nexport const Combobox = <Option = any, OptGroup = any, T extends ValidComponent = \"div\">(\n props: ComboboxRootProps<Option, OptGroup, T>\n) => {\n return <ComboboxPrimitive.Root {...props} />;\n};\n\nexport type ComboboxControlProps<Option = any, T extends ValidComponent = \"div\"> =\n ComboboxPrimitive.ComboboxControlProps<Option, T> & {\n class?: string;\n children?: JSX.Element;\n };\n\n/**\n * Input container box supporting single or multi-select tokens.\n */\nexport const ComboboxControl = <Option = any, T extends ValidComponent = \"div\">(\n props: ComboboxControlProps<Option, T>\n) => {\n const [local, rest] = splitProps(props as ComboboxControlProps, [\"class\", \"children\"]);\n\n return (\n <ComboboxPrimitive.Control\n class={cn(\n \"flex min-h-9 w-full flex-wrap items-center gap-1.5 rounded-md border border-input bg-muted px-3 py-1.5 text-sm shadow-2xs ring-offset-background focus-within:ring-1 focus-within:ring-primary focus-within:border-primary disabled:cursor-not-allowed disabled:opacity-50 text-foreground cursor-text transition-colors\",\n local.class\n )}\n {...(rest as any)}\n >\n {local.children}\n </ComboboxPrimitive.Control>\n );\n};\n\nexport type ComboboxInputProps<T extends ValidComponent = \"input\"> =\n ComboboxPrimitive.ComboboxInputProps<T> & {\n class?: string;\n openOnFocus?: boolean;\n };\n\n/**\n * Filter search input field.\n */\nexport const ComboboxInput = <T extends ValidComponent = \"input\">(\n props: ComboboxInputProps<T>\n) => {\n const [local, rest] = splitProps(props as ComboboxInputProps, [\"class\", \"openOnFocus\", \"onFocus\", \"onClick\"]);\n\n return (\n <ComboboxPrimitive.Input\n class={cn(\n \"flex-1 bg-transparent py-1 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed text-foreground min-w-16\",\n local.class\n )}\n onFocus={(e: FocusEvent) => {\n if (typeof local.onFocus === \"function\") local.onFocus(e as any);\n }}\n onClick={(e: MouseEvent) => {\n if (typeof local.onClick === \"function\") local.onClick(e as any);\n }}\n {...(rest as any)}\n />\n );\n};\n\nexport type ComboboxTriggerProps<T extends ValidComponent = \"button\"> =\n ComboboxPrimitive.ComboboxTriggerProps<T> & {\n class?: string;\n };\n\n/**\n * Dropdown chevron trigger icon.\n */\nexport const ComboboxTrigger = <T extends ValidComponent = \"button\">(\n props: ComboboxTriggerProps<T>\n) => {\n const [local, rest] = splitProps(props as ComboboxTriggerProps, [\"class\"]);\n\n return (\n <ComboboxPrimitive.Trigger\n class={cn(\n \"flex h-4 w-4 items-center justify-center opacity-50 hover:opacity-100 transition-opacity focus:outline-none cursor-pointer\",\n local.class\n )}\n {...(rest as any)}\n >\n <ComboboxPrimitive.Icon\n as=\"svg\"\n class=\"h-4 w-4 stroke-current stroke-2 fill-none\"\n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M19 9l-7 7-7-7\" />\n </ComboboxPrimitive.Icon>\n </ComboboxPrimitive.Trigger>\n );\n};\n\nexport type ComboboxTokenProps<Option = any> = {\n class?: string;\n children?: JSX.Element;\n item?: Option;\n onRemove?: () => void;\n};\n\n/**\n * Selected item tag token pill rendered in multi-select mode.\n */\nexport const ComboboxToken = <Option = any>(props: ComboboxTokenProps<Option>) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\", \"onRemove\"]);\n\n return (\n <span\n class={cn(\n \"inline-flex items-center gap-1.5 rounded-md bg-accent px-2 py-0.5 text-xs font-medium text-accent-foreground border border-border shadow-2xs animate-in fade-in-50\",\n local.class\n )}\n {...rest}\n >\n <span class=\"truncate\">{local.children}</span>\n <button\n type=\"button\"\n tabIndex={-1}\n onClick={(e) => {\n e.stopPropagation();\n if (local.onRemove) local.onRemove();\n }}\n class=\"rounded-xs opacity-70 hover:opacity-100 focus:outline-none cursor-pointer text-muted-foreground hover:text-foreground\"\n >\n <X class=\"h-3 w-3\" />\n <span class=\"sr-only\">Remove</span>\n </button>\n </span>\n );\n};\n\nexport type ComboboxContentProps<T extends ValidComponent = \"div\"> =\n ComboboxPrimitive.ComboboxContentProps<T> & {\n class?: string;\n };\n\n/**\n * Portaled popover content listbox container.\n */\nexport const ComboboxContent = <T extends ValidComponent = \"div\">(\n props: ComboboxContentProps<T>\n) => {\n const [local, rest] = splitProps(props as ComboboxContentProps, [\"class\"]);\n\n return (\n <ComboboxPrimitive.Portal>\n <ComboboxPrimitive.Content\n class={cn(\n \"relative z-50 min-w-8rem overflow-hidden rounded-md border border-border bg-popover text-popover-foreground shadow-md animate-in fade-in-80 data-expanded:animate-in data-closed:animate-out data-[closed]:fade-out-0 data-[expanded]:fade-in-0 data-closed:zoom-out-95 data-expanded:zoom-in-95 max-h-60\",\n local.class\n )}\n {...(rest as any)}\n >\n <ScrollArea class=\"max-h-60 w-full\">\n <ComboboxPrimitive.Listbox class=\"p-1 outline-none\" />\n </ScrollArea>\n </ComboboxPrimitive.Content>\n </ComboboxPrimitive.Portal>\n );\n};\n\nexport type ComboboxItemProps<T extends ValidComponent = \"li\"> =\n ComboboxPrimitive.ComboboxItemProps<T> & {\n class?: string;\n children?: JSX.Element;\n };\n\n/**\n * Individual option item inside listbox supporting custom avatars, icons, and titles.\n */\nexport const ComboboxItem = <T extends ValidComponent = \"li\">(\n props: ComboboxItemProps<T>\n) => {\n const [local, rest] = splitProps(props as ComboboxItemProps, [\"class\", \"children\"]);\n\n return (\n <ComboboxPrimitive.Item\n class={cn(\n \"relative flex w-full cursor-pointer select-none items-center justify-between rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50 text-foreground transition-colors\",\n local.class\n )}\n {...(rest as any)}\n >\n <ComboboxPrimitive.ItemLabel class=\"flex-1 truncate\">\n {local.children}\n </ComboboxPrimitive.ItemLabel>\n <ComboboxPrimitive.ItemIndicator class=\"ml-2 flex h-4 w-4 items-center justify-center text-primary\">\n <Check class=\"h-4 w-4 stroke-2\" />\n </ComboboxPrimitive.ItemIndicator>\n </ComboboxPrimitive.Item>\n );\n};\n\nexport type ComboboxGroupProps<T extends ValidComponent = \"li\"> =\n ComboboxPrimitive.ComboboxSectionProps<T> & {\n class?: string;\n children?: JSX.Element;\n label?: JSX.Element;\n };\n\n/**\n * Group container for organizing related options.\n */\nexport const ComboboxGroup = <T extends ValidComponent = \"li\">(\n props: ComboboxGroupProps<T>\n) => {\n const [local, rest] = splitProps(props as ComboboxGroupProps, [\"class\", \"children\", \"label\"]);\n\n return (\n <ComboboxPrimitive.Section class={cn(\"px-1 py-1\", local.class)} {...(rest as any)}>\n {local.label && (\n <span class=\"px-2 py-1.5 text-xs font-semibold text-muted-foreground uppercase tracking-wider block\">\n {local.label}\n </span>\n )}\n {local.children}\n </ComboboxPrimitive.Section>\n );\n};\n",
|
|
17
|
+
"content": "import { splitProps, type JSX, type ValidComponent } from \"solid-js\";\nimport { Check, ChevronDown, X } from \"lucide-solid\";\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\nimport * as ComboboxPrimitive from \"@kobalte/core/combobox\";\nimport { cn } from \"@/lib/cn\";\n\nexport type ComboboxRootProps<Option = any, OptGroup = any, T extends ValidComponent = \"div\"> =\n ComboboxPrimitive.ComboboxRootProps<Option, OptGroup, T> & {\n class?: string;\n children?: JSX.Element;\n triggerMode?: \"input\" | \"focus\" | \"both\" | \"manual\";\n };\n\n/**\n * Root Combobox component providing search, single/multi-selection, and group support.\n * `triggerMode=\"focus\"` or `triggerMode=\"both\"` enables opening dropdown on input click/focus.\n */\nexport const Combobox = <Option = any, OptGroup = any, T extends ValidComponent = \"div\">(\n props: ComboboxRootProps<Option, OptGroup, T>\n) => {\n const [local, rest] = splitProps(props as ComboboxRootProps, [\"class\", \"children\", \"triggerMode\"]);\n\n return (\n <ComboboxPrimitive.Root\n triggerMode={local.triggerMode ?? \"input\"}\n class={cn(\"relative w-full\", local.class)}\n {...(rest as any)}\n >\n {local.children}\n </ComboboxPrimitive.Root>\n );\n};\n\nexport type ComboboxControlProps<Option = any, T extends ValidComponent = \"div\"> =\n ComboboxPrimitive.ComboboxControlProps<Option, T> & {\n class?: string;\n children?: JSX.Element;\n clearable?: boolean;\n onClear?: () => void;\n };\n\n/**\n * Input container for Combobox supporting search input, selected tags, and clear button.\n */\nexport const ComboboxControl = <Option = any, T extends ValidComponent = \"div\">(\n props: ComboboxControlProps<Option, T>\n) => {\n const [local, rest] = splitProps(props as ComboboxControlProps, [\n \"class\",\n \"children\",\n \"clearable\",\n \"onClear\",\n ]);\n\n return (\n <ComboboxPrimitive.Control\n class={cn(\n \"flex min-h-9 w-full flex-wrap items-center justify-between rounded-md border border-input bg-muted px-3 py-1 text-sm shadow-sm transition-colors focus-within:ring-1 focus-within:ring-primary focus-within:border-primary disabled:cursor-not-allowed disabled:opacity-50 gap-1.5 text-foreground\",\n local.class\n )}\n {...(rest as any)}\n >\n <div class=\"flex flex-wrap items-center gap-1.5 flex-1 min-w-0\">\n {local.children}\n </div>\n\n <div class=\"flex items-center gap-1 shrink-0 self-center\">\n {local.clearable && (\n <button\n type=\"button\"\n tabIndex={-1}\n onClick={(e) => {\n e.stopPropagation();\n if (local.onClear) local.onClear();\n }}\n class=\"rounded-sm p-0.5 opacity-60 hover:opacity-100 hover:bg-accent text-foreground transition-opacity focus:outline-none cursor-pointer\"\n aria-label=\"Clear selection\"\n >\n <svg class=\"h-3.5 w-3.5\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n )}\n <ComboboxTrigger />\n </div>\n </ComboboxPrimitive.Control>\n );\n};\n\nexport type ComboboxInputProps<T extends ValidComponent = \"input\"> =\n ComboboxPrimitive.ComboboxInputProps<T> & {\n class?: string;\n openOnFocus?: boolean;\n };\n\n/**\n * Search input field embedded within ComboboxControl.\n * Supports `openOnFocus` to automatically trigger the dropdown when focused or clicked.\n */\nexport const ComboboxInput = <T extends ValidComponent = \"input\">(\n props: ComboboxInputProps<T>\n) => {\n const [local, rest] = splitProps(props as ComboboxInputProps, [\"class\", \"openOnFocus\", \"onFocus\", \"onClick\"]);\n\n return (\n <ComboboxPrimitive.Input\n class={cn(\n \"flex-1 bg-transparent py-1 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed text-foreground min-w-16\",\n local.class\n )}\n onFocus={(e: FocusEvent) => {\n if (typeof local.onFocus === \"function\") local.onFocus(e as any);\n }}\n onClick={(e: MouseEvent) => {\n if (typeof local.onClick === \"function\") local.onClick(e as any);\n }}\n {...(rest as any)}\n />\n );\n};\n\nexport type ComboboxTriggerProps<T extends ValidComponent = \"button\"> =\n ComboboxPrimitive.ComboboxTriggerProps<T> & {\n class?: string;\n };\n\n/**\n * Dropdown chevron trigger icon.\n */\nexport const ComboboxTrigger = <T extends ValidComponent = \"button\">(\n props: ComboboxTriggerProps<T>\n) => {\n const [local, rest] = splitProps(props as ComboboxTriggerProps, [\"class\"]);\n\n return (\n <ComboboxPrimitive.Trigger\n class={cn(\n \"flex h-4 w-4 items-center justify-center opacity-50 hover:opacity-100 transition-opacity focus:outline-none cursor-pointer\",\n local.class\n )}\n {...(rest as any)}\n >\n <ComboboxPrimitive.Icon\n as=\"svg\"\n class=\"h-4 w-4 stroke-current stroke-2 fill-none\"\n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M19 9l-7 7-7-7\" />\n </ComboboxPrimitive.Icon>\n </ComboboxPrimitive.Trigger>\n );\n};\n\nexport type ComboboxTokenProps<Option = any> = {\n class?: string;\n children?: JSX.Element;\n item?: Option;\n onRemove?: () => void;\n};\n\n/**\n * Selected item tag token pill rendered in multi-select mode.\n */\nexport const ComboboxToken = <Option = any>(props: ComboboxTokenProps<Option>) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\", \"onRemove\"]);\n\n return (\n <span\n class={cn(\n \"inline-flex items-center gap-1.5 rounded-md bg-accent px-2 py-0.5 text-xs font-medium text-accent-foreground border border-border shadow-2xs animate-in fade-in-50\",\n local.class\n )}\n {...rest}\n >\n <span class=\"truncate\">{local.children}</span>\n <button\n type=\"button\"\n tabIndex={-1}\n onClick={(e) => {\n e.stopPropagation();\n if (local.onRemove) local.onRemove();\n }}\n class=\"rounded-xs p-0.5 hover:bg-muted-foreground/20 text-muted-foreground hover:text-foreground transition-colors focus:outline-none cursor-pointer\"\n aria-label=\"Remove tag\"\n >\n <svg class=\"h-3 w-3\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </span>\n );\n};\n\nexport type ComboboxContentProps<T extends ValidComponent = \"div\"> =\n ComboboxPrimitive.ComboboxContentProps<T> & {\n class?: string;\n };\n\n/**\n * Portaled popover content listbox container.\n */\nexport const ComboboxContent = <T extends ValidComponent = \"div\">(\n props: ComboboxContentProps<T>\n) => {\n const [local, rest] = splitProps(props as ComboboxContentProps, [\"class\"]);\n\n return (\n <ComboboxPrimitive.Portal>\n <ComboboxPrimitive.Content\n class={cn(\n \"relative z-50 min-w-8rem overflow-hidden rounded-md border border-border bg-popover text-popover-foreground shadow-md animate-in fade-in-80 data-expanded:animate-in data-closed:animate-out data-[closed]:fade-out-0 data-[expanded]:fade-in-0 data-closed:zoom-out-95 data-expanded:zoom-in-95 max-h-60\",\n local.class\n )}\n {...(rest as any)}\n >\n <ScrollArea class=\"max-h-60 w-full\">\n <ComboboxPrimitive.Listbox class=\"p-1 outline-none\" />\n </ScrollArea>\n </ComboboxPrimitive.Content>\n </ComboboxPrimitive.Portal>\n );\n};\n\nexport type ComboboxItemProps<T extends ValidComponent = \"li\"> =\n ComboboxPrimitive.ComboboxItemProps<T> & {\n class?: string;\n children?: JSX.Element;\n };\n\n/**\n * Individual option item inside listbox supporting custom avatars, icons, and titles.\n */\nexport const ComboboxItem = <T extends ValidComponent = \"li\">(\n props: ComboboxItemProps<T>\n) => {\n const [local, rest] = splitProps(props as ComboboxItemProps, [\"class\", \"children\"]);\n\n return (\n <ComboboxPrimitive.Item\n class={cn(\n \"relative flex w-full cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-disabled:pointer-events-none data-disabled:opacity-50 data-highlighted:bg-accent data-highlighted:text-accent-foreground text-popover-foreground transition-colors\",\n local.class\n )}\n {...rest}\n >\n <ComboboxPrimitive.ItemIndicator class=\"absolute left-2 flex h-4 w-4 items-center justify-center text-primary\">\n <svg class=\"h-4 w-4 fill-none stroke-current stroke-2\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M5 13l4 4L19 7\" />\n </svg>\n </ComboboxPrimitive.ItemIndicator>\n <ComboboxPrimitive.ItemLabel class=\"flex items-center gap-2 w-full truncate\">\n {local.children}\n </ComboboxPrimitive.ItemLabel>\n </ComboboxPrimitive.Item>\n );\n};\n\nexport type ComboboxGroupProps<T extends ValidComponent = \"li\"> =\n ComboboxPrimitive.ComboboxSectionProps<T> & {\n class?: string;\n children?: JSX.Element;\n label?: JSX.Element;\n };\n\n/**\n * Group container for organizing related options.\n */\nexport const ComboboxGroup = <T extends ValidComponent = \"li\">(\n props: ComboboxGroupProps<T>\n) => {\n const [local, rest] = splitProps(props as ComboboxGroupProps, [\"class\", \"children\", \"label\"]);\n\n return (\n <ComboboxPrimitive.Section class={cn(\"px-1 py-1\", local.class)} {...(rest as any)}>\n {local.label && (\n <span class=\"px-2 py-1.5 text-xs font-semibold text-muted-foreground uppercase tracking-wider block\">\n {local.label}\n </span>\n )}\n {local.children}\n </ComboboxPrimitive.Section>\n );\n};\n\n/**\n * Empty state notice when no matching search items exist.\n */\nexport const ComboboxEmpty = (props: { class?: string; children?: JSX.Element }) => {\n return (\n <div class={cn(\"py-6 text-center text-sm text-muted-foreground\", props.class)}>\n {props.children || \"No matching items found.\"}\n </div>\n );\n};\n\n/**\n * Hidden native select element for form integrations.\n */\nexport const ComboboxHiddenSelect = ComboboxPrimitive.HiddenSelect;\n",
|
|
18
18
|
"type": "registry:ui"
|
|
19
19
|
}
|
|
20
20
|
]
|
package/registry/command.json
CHANGED
|
@@ -14,12 +14,13 @@
|
|
|
14
14
|
"kbd",
|
|
15
15
|
"input-group",
|
|
16
16
|
"list",
|
|
17
|
-
"scroll-area"
|
|
17
|
+
"scroll-area",
|
|
18
|
+
"create-keybindings"
|
|
18
19
|
],
|
|
19
20
|
"files": [
|
|
20
21
|
{
|
|
21
22
|
"path": "ui/command.tsx",
|
|
22
|
-
"content": "import {\n createContext,\n useContext,\n createSignal,\n onCleanup,\n Show,\n splitProps,\n type Component,\n type JSX,\n type Accessor,\n} from \"solid-js\";\nimport { createKeybindings } from \"@nikala-ui/hooks\";\nimport { Search, ArrowUp, ArrowDown, CornerDownLeft } from \"lucide-solid\";\nimport { cn } from \"@/lib/cn\";\nimport { Kbd, KbdGroup } from \"./kbd\";\nimport { InputGroup, InputGroupInput, InputGroupAddon } from \"./input-group\";\nimport { List, ListGroup, ListHeader, ListItem, type ListItemProps } from \"./list\";\nimport { Dialog, DialogOverlay, DialogContent } from \"./dialog\";\nimport { ScrollArea } from \"./scroll-area\";\n\n/* --- Command Context State --- */\ninterface CommandContextValue {\n search: Accessor<string>;\n setSearch: (value: string) => void;\n activeIndex: Accessor<number>;\n setActiveIndex: (fn: (prev: number) => number) => void;\n registerItemIndex: () => number;\n onItemSelect: (fn: () => void) => void;\n}\n\nconst CommandContext = createContext<CommandContextValue>();\n\nexport const useCommand = () => {\n const ctx = useContext(CommandContext);\n if (!ctx) {\n throw new Error(\"useCommand must be used within a <Command /> root component.\");\n }\n return ctx;\n};\n\n/* --- Command Root --- */\nexport interface CommandProps extends Omit<JSX.HTMLAttributes<HTMLDivElement>, \"children\"> {\n class?: string;\n children?: JSX.Element | ((ctx: CommandContextValue) => JSX.Element);\n}\n\nexport const Command: Component<CommandProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n const [search, setSearch] = createSignal(\"\");\n const [activeIndex, setActiveIndex] = createSignal(0);\n let itemCounter = 0;\n const itemCallbacks: Record<number, () => void> = {};\n let containerRef: HTMLDivElement | undefined;\n\n const registerItemIndex = () => {\n const idx = itemCounter;\n itemCounter += 1;\n return idx;\n };\n\n const onItemSelect = (fn: () => void) => {\n itemCallbacks[activeIndex()] = fn;\n };\n\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"ArrowDown\") {\n e.preventDefault();\n setActiveIndex((prev) => Math.min(prev + 1, Math.max(0, itemCounter - 1)));\n } else if (e.key === \"ArrowUp\") {\n e.preventDefault();\n setActiveIndex((prev) => Math.max(prev - 1, 0));\n } else if (e.key === \"Enter\") {\n e.preventDefault();\n const fn = itemCallbacks[activeIndex()];\n if (fn) fn();\n const current = containerRef?.querySelector('[data-command-active=\"true\"]') as HTMLElement;\n if (current) {\n current.click();\n }\n }\n };\n\n const ctx: CommandContextValue = {\n search,\n setSearch: (val) => {\n setSearch(val);\n setActiveIndex(0);\n },\n activeIndex,\n setActiveIndex: (fn) => setActiveIndex(fn),\n registerItemIndex,\n onItemSelect,\n };\n\n return (\n <CommandContext.Provider value={ctx}>\n <div\n ref={containerRef}\n onKeyDown={handleKeyDown}\n class={cn(\n \"flex flex-col w-full h-full rounded-lg bg-popover text-popover-foreground overflow-hidden border border-border shadow-md outline-none\",\n local.class\n )}\n tabIndex={0}\n {...rest}\n >\n {typeof local.children === \"function\" ? (local.children as any)(ctx) : local.children}\n </div>\n </CommandContext.Provider>\n );\n};\n\n/* --- Command Dialog (Modal) --- */\nexport interface CommandDialogProps {\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n enableHotkey?: boolean;\n children?: JSX.Element | ((ctx: CommandContextValue) => JSX.Element);\n class?: string;\n}\n\nexport const CommandDialog: Component<CommandDialogProps> = (props) => {\n const [internalOpen, setInternalOpen] = createSignal(false);\n\n const isOpen = () => (props.open !== undefined ? props.open : internalOpen());\n const setOpen = (val: boolean) => {\n if (props.open === undefined) setInternalOpen(val);\n if (typeof props.onOpenChange === \"function\") props.onOpenChange(val);\n };\n\n /* Listen for global Ctrl+K / Cmd+K hotkeys */\n createKeybindings(\n [\n {\n key: [\"meta+k\", \"ctrl+k\"],\n handler: () => setOpen(!isOpen()),\n preventDefault: true,\n },\n ],\n {\n enabled: () => props.enableHotkey !== false,\n }\n );\n\n return (\n <Dialog open={isOpen()} onOpenChange={setOpen}>\n <DialogOverlay class=\"fixed inset-0 z-50 bg-black/60 backdrop-blur-xs data-expanded:animate-in data-closed:animate-out data-[closed]:fade-out-0 data-[expanded]:fade-in-0\" />\n <div class=\"fixed inset-0 z-50 flex items-start justify-center pt-16 sm:pt-24 px-4\">\n <DialogContent class=\"w-full max-w-xl rounded-lg border border-border bg-popover text-popover-foreground shadow-2xl outline-none data-expanded:animate-in data-closed:animate-out data-[closed]:fade-out-0 data-[expanded]:fade-in-0 data-closed:zoom-out-95 data-expanded:zoom-in-95 p-0\">\n <Command class={props.class}>{props.children}</Command>\n </DialogContent>\n </div>\n </Dialog>\n );\n};\n\n/* --- Command Input --- */\nexport interface CommandInputProps\n extends JSX.InputHTMLAttributes<HTMLInputElement> {\n class?: string;\n}\n\nexport const CommandInput: Component<CommandInputProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"value\", \"onInput\"]);\n const { search, setSearch } = useCommand();\n\n return (\n <InputGroup class=\"border-0 border-b border-border rounded-none bg-transparent px-3 py-1 shadow-none focus-within:ring-0 focus-within:border-border\">\n <InputGroupAddon align=\"inline-start\">\n <Search class=\"w-4 h-4 text-muted-foreground\" />\n </InputGroupAddon>\n\n <InputGroupInput\n ref={(el) => setTimeout(() => el.focus(), 50)}\n value={search()}\n onInput={(e) => {\n setSearch(e.currentTarget.value);\n if (typeof local.onInput === \"function\") {\n local.onInput(e);\n }\n }}\n placeholder=\"Type a command or search...\"\n class=\"h-11 text-base sm:text-sm font-medium\"\n {...rest}\n />\n </InputGroup>\n );\n};\n\n/* --- Command List Container --- */\nexport interface CommandListProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const CommandList: Component<CommandListProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <ScrollArea\n class={cn(\"max-h-82.5 p-1.5\", local.class)}\n {...rest}\n >\n <List>{local.children}</List>\n </ScrollArea>\n );\n};\n\n/* --- Command Empty Block --- */\nexport interface CommandEmptyProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const CommandEmpty: Component<CommandEmptyProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n const { search } = useCommand();\n\n return (\n <Show when={search().trim().length > 0}>\n <div\n class={cn(\n \"py-8 text-center text-sm text-muted-foreground font-medium select-none\",\n local.class\n )}\n {...rest}\n >\n {local.children || `No results found for \"${search()}\".`}\n </div>\n </Show>\n );\n};\n\n/* --- Command Group --- */\nexport interface CommandGroupProps {\n heading: string;\n children?: JSX.Element;\n class?: string;\n}\n\nexport const CommandGroup: Component<CommandGroupProps> = (props) => {\n return (\n <ListGroup class={props.class}>\n <ListHeader title={props.heading} />\n {props.children}\n </ListGroup>\n );\n};\n\n/* --- Command Item --- */\nexport interface CommandItemProps extends ListItemProps {\n keywords?: string[];\n onSelect?: () => void;\n}\n\nexport const CommandItem: Component<CommandItemProps> = (props) => {\n let itemRef: HTMLDivElement | undefined;\n const [local, rest] = splitProps(props, [\n \"title\",\n \"subtitle\",\n \"keywords\",\n \"disabled\",\n \"onSelect\",\n \"class\",\n \"children\",\n ]);\n\n const ctx = useCommand();\n const index = ctx.registerItemIndex();\n\n const isActive = () => ctx.activeIndex() === index;\n\n /* Automatic scroll into view when navigating with Arrow keys */\n const scrollIntoViewIfNeeded = () => {\n if (isActive() && itemRef) {\n itemRef.scrollIntoView({ block: \"nearest\", behavior: \"smooth\" });\n }\n };\n\n const isVisible = () => {\n const query = ctx.search().toLowerCase().trim();\n if (!query) return true;\n\n const titleStr = typeof local.title === \"string\" ? local.title.toLowerCase() : \"\";\n const subStr = typeof local.subtitle === \"string\" ? local.subtitle.toLowerCase() : \"\";\n\n if (titleStr.includes(query) || subStr.includes(query)) return true;\n\n if (local.keywords) {\n return local.keywords.some((k) => k.toLowerCase().includes(query));\n }\n\n return false;\n };\n\n const handleSelect = () => {\n if (local.disabled) return;\n if (local.onSelect) local.onSelect();\n };\n\n return (\n <Show when={isVisible()}>\n <ListItem\n ref={(el) => {\n itemRef = el;\n scrollIntoViewIfNeeded();\n }}\n title={local.title}\n subtitle={local.subtitle}\n data-command-active={isActive() ? \"true\" : \"false\"}\n class={cn(\n \"relative flex cursor-pointer select-none items-center rounded-md px-2 py-1.5 text-sm outline-none transition-colors\",\n isActive()\n ? \"bg-accent text-accent-foreground font-medium\"\n : \"text-popover-foreground hover:bg-muted/50\",\n local.disabled && \"pointer-events-none opacity-50\",\n local.class\n )}\n onClick={handleSelect}\n onMouseEnter={() => ctx.setActiveIndex(() => index)}\n {...rest}\n >\n {local.children}\n </ListItem>\n </Show>\n );\n};\n\n/* --- Command Footer Indicator Bar --- */\nexport interface CommandFooterProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const CommandFooter: Component<CommandFooterProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <div\n class={cn(\n \"flex items-center justify-between border-t border-border px-3 py-2 text-xs text-muted-foreground bg-muted/40\",\n local.class\n )}\n {...rest}\n >\n {local.children || (\n <>\n <div class=\"flex items-center gap-2\">\n <span class=\"inline-flex items-center gap-1\">\n <KbdGroup>\n <Kbd size=\"sm\">\n <ArrowUp class=\"w-2.5 h-2.5\" />\n </Kbd>\n <Kbd size=\"sm\">\n <ArrowDown class=\"w-2.5 h-2.5\" />\n </Kbd>\n </KbdGroup>\n <span>Navigate</span>\n </span>\n\n <span class=\"inline-flex items-center gap-1\">\n <Kbd size=\"sm\">\n <CornerDownLeft class=\"w-2.5 h-2.5\" />\n </Kbd>\n <span>Select</span>\n </span>\n </div>\n\n <span class=\"inline-flex items-center gap-1\">\n <Kbd size=\"sm\">ESC</Kbd>\n <span>Close</span>\n </span>\n </>\n )}\n </div>\n );\n};",
|
|
23
|
+
"content": "import {\n createContext,\n useContext,\n createSignal,\n onCleanup,\n Show,\n splitProps,\n type Component,\n type JSX,\n type Accessor,\n} from \"solid-js\";\nimport { createKeybindings } from \"@/hooks/create-keybindings\";\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\";\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\n\n/* --- Command Context State --- */\ninterface CommandContextValue {\n search: Accessor<string>;\n setSearch: (value: string) => void;\n activeIndex: Accessor<number>;\n setActiveIndex: (fn: (prev: number) => number) => void;\n registerItemIndex: (element: HTMLElement) => number;\n onItemSelect: (index: number, action?: () => void) => void;\n}\n\nconst CommandContext = createContext<CommandContextValue>();\n\nexport const useCommand = () => {\n const ctx = useContext(CommandContext);\n if (!ctx) {\n throw new Error(\"useCommand must be used within a <Command />\");\n }\n return ctx;\n};\n\n/* --- Root Command Container --- */\nexport interface CommandProps extends Omit<JSX.HTMLAttributes<HTMLDivElement>, \"children\"> {\n class?: string;\n children?: JSX.Element | ((ctx: CommandContextValue) => JSX.Element);\n}\n\nexport const Command: Component<CommandProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n const [search, setSearch] = createSignal(\"\");\n const [activeIndex, setActiveIndex] = createSignal(0);\n\n let containerRef: HTMLDivElement | undefined;\n let itemsList: HTMLElement[] = [];\n\n const registerItemIndex = (element: HTMLElement) => {\n if (!itemsList.includes(element)) {\n itemsList.push(element);\n }\n return itemsList.indexOf(element);\n };\n\n const onItemSelect = (index: number, action?: () => void) => {\n setActiveIndex(index);\n if (action) action();\n };\n\n const getVisibleItems = () => {\n if (!containerRef) return [];\n return Array.from(containerRef.querySelectorAll<HTMLElement>(\"[data-command-item]:not(.hidden)\"));\n };\n\n const updateActiveAttribute = (index: number) => {\n const visible = getVisibleItems();\n visible.forEach((el, idx) => {\n if (idx === index) {\n el.setAttribute(\"aria-selected\", \"true\");\n el.scrollIntoView({ block: \"nearest\" });\n } else {\n el.removeAttribute(\"aria-selected\");\n }\n });\n };\n\n const handleKeyDown = (e: KeyboardEvent) => {\n const visible = getVisibleItems();\n if (visible.length === 0) return;\n\n if (e.key === \"ArrowDown\") {\n e.preventDefault();\n const next = (activeIndex() + 1) % visible.length;\n setActiveIndex(next);\n updateActiveAttribute(next);\n } else if (e.key === \"ArrowUp\") {\n e.preventDefault();\n const prev = (activeIndex() - 1 + visible.length) % visible.length;\n setActiveIndex(prev);\n updateActiveAttribute(prev);\n } else if (e.key === \"Enter\") {\n e.preventDefault();\n const current = visible[activeIndex()];\n if (current) {\n current.click();\n }\n }\n };\n\n const ctx: CommandContextValue = {\n search,\n setSearch: (val) => {\n setSearch(val);\n setActiveIndex(0);\n },\n activeIndex,\n setActiveIndex: (fn) => setActiveIndex(fn),\n registerItemIndex,\n onItemSelect,\n };\n\n return (\n <CommandContext.Provider value={ctx}>\n <div\n ref={containerRef}\n onKeyDown={handleKeyDown}\n class={cn(\n \"flex flex-col w-full h-full rounded-lg bg-popover text-popover-foreground overflow-hidden border border-border shadow-md outline-none\",\n local.class\n )}\n tabIndex={0}\n {...rest}\n >\n {typeof local.children === \"function\" ? (local.children as any)(ctx) : local.children}\n </div>\n </CommandContext.Provider>\n );\n};\n\n/* --- Command Dialog (Modal) --- */\nexport interface CommandDialogProps {\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n enableHotkey?: boolean;\n children?: JSX.Element | ((ctx: CommandContextValue) => JSX.Element);\n class?: string;\n}\n\nexport const CommandDialog: Component<CommandDialogProps> = (props) => {\n const [internalOpen, setInternalOpen] = createSignal(false);\n\n const isOpen = () => (props.open !== undefined ? props.open : internalOpen());\n const setOpen = (val: boolean) => {\n if (props.open === undefined) setInternalOpen(val);\n if (typeof props.onOpenChange === \"function\") props.onOpenChange(val);\n };\n\n /* Listen for global Ctrl+K / Cmd+K hotkeys */\n createKeybindings(\n [\n {\n key: [\"meta+k\", \"ctrl+k\"],\n handler: () => setOpen(!isOpen()),\n preventDefault: true,\n },\n ],\n {\n enabled: () => props.enableHotkey !== false,\n }\n );\n\n return (\n <Dialog open={isOpen()} onOpenChange={setOpen}>\n <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 <ScrollArea\n class={cn(\"max-h-82.5 p-1.5\", local.class)}\n {...rest}\n >\n <List>{local.children}</List>\n </ScrollArea>\n );\n};\n\n/* --- Command Empty Block --- */\nexport interface CommandEmptyProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const CommandEmpty: Component<CommandEmptyProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n const { search } = useCommand();\n\n return (\n <Show when={search().trim().length > 0}>\n <div\n class={cn(\n \"py-8 text-center text-sm text-muted-foreground font-medium select-none\",\n local.class\n )}\n {...rest}\n >\n {local.children || `No results found for \"${search()}\".`}\n </div>\n </Show>\n );\n};\n\n/* --- Command Group --- */\nexport interface CommandGroupProps {\n heading: string;\n children?: JSX.Element;\n class?: string;\n}\n\nexport const CommandGroup: Component<CommandGroupProps> = (props) => {\n return (\n <ListGroup class={props.class}>\n <ListHeader title={props.heading} />\n {props.children}\n </ListGroup>\n );\n};\n\n/* --- Command Item --- */\nexport interface CommandItemProps extends ListItemProps {\n keywords?: string[];\n description?: string;\n onSelect?: () => void;\n shouldFilter?: boolean;\n}\n\nexport const CommandItem: Component<CommandItemProps> = (props) => {\n let itemRef: HTMLDivElement | undefined;\n const [local, rest] = splitProps(props, [\n \"title\",\n \"subtitle\",\n \"description\",\n \"keywords\",\n \"onSelect\",\n \"onClick\",\n \"shouldFilter\",\n \"class\",\n ]);\n const { search } = useCommand();\n\n /* Auto filter item based on search query */\n const matchesSearch = () => {\n if (local.shouldFilter === false) return true;\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 descMatch = local.description?.toLowerCase().includes(query);\n const keywordMatch = local.keywords?.some((k) =>\n k.toLowerCase().includes(query)\n );\n\n return Boolean(titleMatch || subtitleMatch || descMatch || 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 ref={itemRef}\n data-command-item=\"true\"\n title={local.title}\n subtitle={local.subtitle || local.description}\n onClick={handleClick}\n class={cn(\n \"aria-selected:bg-accent aria-selected:text-accent-foreground cursor-pointer transition-colors hover:bg-accent hover:text-accent-foreground\",\n local.class\n )}\n {...rest}\n />\n </Show>\n );\n};\n\n/* --- Command Footer --- */\nexport interface CommandFooterProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n children?: JSX.Element;\n}\n\nexport const CommandFooter: Component<CommandFooterProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <div\n class={cn(\n \"flex items-center justify-between border-t border-border 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\n <span class=\"flex items-center gap-1\">\n <Kbd size=\"sm\">Esc</Kbd>\n <span>Close</span>\n </span>\n </div>\n\n {local.children}\n </div>\n );\n};",
|
|
23
24
|
"type": "registry:ui"
|
|
24
25
|
}
|
|
25
26
|
]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "component-viewer",
|
|
3
|
+
"title": "Component Viewer",
|
|
4
|
+
"description": "An interactive preview canvas and code viewer container with responsive viewports, canvas grid, and AI prompts.",
|
|
5
|
+
"type": "registry:ui",
|
|
6
|
+
"dependencies": [
|
|
7
|
+
"clsx",
|
|
8
|
+
"tailwind-merge",
|
|
9
|
+
"lucide-solid"
|
|
10
|
+
],
|
|
11
|
+
"registryDependencies": [
|
|
12
|
+
"tabs",
|
|
13
|
+
"button",
|
|
14
|
+
"tooltip",
|
|
15
|
+
"code-block",
|
|
16
|
+
"create-clipboard"
|
|
17
|
+
],
|
|
18
|
+
"files": [
|
|
19
|
+
{
|
|
20
|
+
"path": "ui/component-viewer.tsx",
|
|
21
|
+
"content": "import {\n createSignal,\n splitProps,\n Show,\n type JSX,\n type ParentComponent,\n} from \"solid-js\";\nimport { Portal } from \"solid-js/web\";\nimport {\n Monitor,\n Tablet,\n Smartphone,\n Sparkles,\n RotateCcw,\n Terminal,\n Check,\n Maximize2,\n Minimize2,\n Grid,\n} from \"lucide-solid\";\nimport { Tabs, TabsList, TabsTrigger, TabsContent } from \"@/components/ui/tabs\";\nimport { Button } from \"@/components/ui/button\";\nimport { Tooltip, TooltipTrigger, TooltipContent } from \"@/components/ui/tooltip\";\nimport { CodeBlock } from \"@/components/ui/code-block\";\nimport { createClipboard } from \"@/hooks/create-clipboard\";\nimport { cn } from \"@/lib/cn\";\n\nexport type ComponentViewerViewport = \"desktop\" | \"tablet\" | \"mobile\";\n\nexport interface ComponentViewerProps extends JSX.HTMLAttributes<HTMLDivElement> {\n /** Name of the component or UI primitive (e.g. \"button\", \"dialog\") */\n name?: string;\n /** Title label displayed in the viewer header */\n title?: string;\n /** Raw TSX/JSX source code to display in the Code tab and copy */\n code: string;\n /** Programming language for syntax highlighting (defaults to \"tsx\") */\n lang?: string;\n /** Alignment of the preview canvas: \"center\" (default), \"start\", or \"end\" */\n align?: \"center\" | \"start\" | \"end\";\n /** Optional CLI command to install this component (e.g. \"bunx @nikala-ui/cli add button\") */\n command?: string;\n /** Initial responsive viewport (default: \"desktop\") */\n defaultViewport?: ComponentViewerViewport;\n /** Whether to allow overflowing elements in the canvas (e.g. popovers, dropdowns) */\n allowOverflow?: boolean;\n /** Enables responsive screen size toggle controls (Desktop, Tablet, Mobile) */\n responsive?: boolean;\n /** Enables canvas grid pattern toggle */\n showGridToggle?: boolean;\n /** Enables a reset button to re-mount the preview canvas */\n reRenderable?: boolean;\n class?: string;\n}\n\n/**\n * Advanced component inspection and preview container with responsive viewport emulation,\n * background grid patterns, live re-mounting, AI context generation, and syntax-highlighted code.\n */\nexport const ComponentViewer: ParentComponent<ComponentViewerProps> = (props) => {\n const [local, rest] = splitProps(props, [\n \"name\",\n \"title\",\n \"code\",\n \"lang\",\n \"align\",\n \"command\",\n \"defaultViewport\",\n \"allowOverflow\",\n \"responsive\",\n \"showGridToggle\",\n \"reRenderable\",\n \"class\",\n \"children\",\n ]);\n\n const [viewport, setViewport] = createSignal<ComponentViewerViewport>(\n local.defaultViewport || \"desktop\"\n );\n const [showGrid, setShowGrid] = createSignal(true);\n const [isFullscreen, setIsFullscreen] = createSignal(false);\n const aiClipboard = createClipboard();\n const cmdClipboard = createClipboard();\n const [mounted, setMounted] = createSignal(true);\n\n const cliCommand = () => local.command;\n\n const handleCopyAi = async () => {\n const cmd = cliCommand();\n const prompt = `// ${local.title || local.name || \"Component\"}\n${cmd ? `// Installation: ${cmd}\\n` : \"\"}// Usage Code:\n${local.code}`;\n await aiClipboard.copy(prompt);\n };\n\n const handleCopyCmd = async () => {\n const cmd = cliCommand();\n if (!cmd) return;\n await cmdClipboard.copy(cmd);\n };\n\n const handleReset = () => {\n setMounted(false);\n setTimeout(() => setMounted(true), 20);\n };\n\n const alignmentClass = () => {\n if (local.align === \"start\") return \"items-start justify-start\";\n if (local.align === \"end\") return \"items-end justify-end\";\n return \"items-center justify-center\";\n };\n\n const viewportWidthClass = () => {\n switch (viewport()) {\n case \"mobile\":\n return \"max-w-[375px]\";\n case \"tablet\":\n return \"max-w-[768px]\";\n default:\n return \"max-w-full\";\n }\n };\n\n const renderViewerContent = (isModal = false) => (\n <div\n class={cn(\n \"group relative flex flex-col rounded-lg border border-border bg-card/60 shadow-2xs transition-all w-full\",\n isModal && \"h-full flex-1 bg-background shadow-2xl\",\n !isModal && \"my-6\",\n local.class\n )}\n {...rest}\n >\n <Tabs defaultValue=\"preview\" class=\"relative w-full flex flex-col h-full flex-1 gap-0\">\n {/* Top Header & Toolbar */}\n <div class=\"flex flex-wrap items-center justify-between border-b border-border/70 px-3 py-2 gap-2 bg-muted/30 shrink-0\">\n <div class=\"flex items-center gap-3\">\n <TabsList class=\"h-8 rounded-md bg-muted/60 p-0.5 gap-1\">\n <TabsTrigger value=\"preview\" class=\"h-7 px-3 text-xs cursor-pointer data-[state=active]:bg-background data-[state=active]:shadow-2xs\">\n Preview\n </TabsTrigger>\n <TabsTrigger value=\"code\" class=\"h-7 px-3 text-xs cursor-pointer data-[state=active]:bg-background data-[state=active]:shadow-2xs\">\n Code\n </TabsTrigger>\n </TabsList>\n\n <Show when={local.title}>\n <span class=\"text-xs font-semibold text-foreground tracking-tight hidden sm:inline-block\">\n {local.title}\n </span>\n </Show>\n </div>\n\n {/* Right Action Tools */}\n <div class=\"flex flex-wrap items-center gap-1.5 ml-auto\">\n {/* Responsive Viewport Switcher */}\n <Show when={local.responsive ?? true}>\n <div class=\"flex items-center rounded-md border border-border/60 bg-muted/40 p-0.5 gap-0.5 mr-1\">\n <Tooltip>\n <TooltipTrigger\n as={Button}\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => setViewport(\"desktop\")}\n aria-label=\"Desktop viewport\"\n class={cn(\n \"size-6.5 p-0 rounded-sm cursor-pointer text-muted-foreground\",\n viewport() === \"desktop\" && \"bg-background text-foreground shadow-2xs font-semibold\"\n )}\n >\n <Monitor class=\"size-3.5\" />\n </TooltipTrigger>\n <TooltipContent class=\"text-[10px] py-1 px-2\">Desktop (100%)</TooltipContent>\n </Tooltip>\n\n <Tooltip>\n <TooltipTrigger\n as={Button}\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => setViewport(\"tablet\")}\n aria-label=\"Tablet viewport\"\n class={cn(\n \"size-6.5 p-0 rounded-sm cursor-pointer text-muted-foreground\",\n viewport() === \"tablet\" && \"bg-background text-foreground shadow-2xs font-semibold\"\n )}\n >\n <Tablet class=\"size-3.5\" />\n </TooltipTrigger>\n <TooltipContent class=\"text-[10px] py-1 px-2\">Tablet (768px)</TooltipContent>\n </Tooltip>\n\n <Tooltip>\n <TooltipTrigger\n as={Button}\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => setViewport(\"mobile\")}\n aria-label=\"Mobile viewport\"\n class={cn(\n \"size-6.5 p-0 rounded-sm cursor-pointer text-muted-foreground\",\n viewport() === \"mobile\" && \"bg-background text-foreground shadow-2xs font-semibold\"\n )}\n >\n <Smartphone class=\"size-3.5\" />\n </TooltipTrigger>\n <TooltipContent class=\"text-[10px] py-1 px-2\">Mobile (375px)</TooltipContent>\n </Tooltip>\n </div>\n </Show>\n\n {/* Grid Pattern Toggle */}\n <Show when={local.showGridToggle ?? true}>\n <Tooltip>\n <TooltipTrigger\n as={Button}\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => setShowGrid(!showGrid())}\n aria-label=\"Toggle background grid\"\n class={cn(\n \"size-7 rounded-md border border-border/50 text-muted-foreground hover:text-foreground cursor-pointer\",\n showGrid() && \"text-foreground bg-muted/60\"\n )}\n >\n <Grid class=\"size-3.5\" />\n </TooltipTrigger>\n <TooltipContent class=\"text-[10px] py-1 px-2\">Toggle Canvas Grid</TooltipContent>\n </Tooltip>\n </Show>\n\n {/* Re-render Reset Canvas */}\n <Show when={local.reRenderable ?? true}>\n <Tooltip>\n <TooltipTrigger\n as={Button}\n variant=\"ghost\"\n size=\"icon\"\n onClick={handleReset}\n aria-label=\"Reset preview canvas\"\n class=\"size-7 rounded-md border border-border/50 text-muted-foreground hover:text-foreground cursor-pointer\"\n >\n <RotateCcw class=\"size-3.5\" />\n </TooltipTrigger>\n <TooltipContent class=\"text-[10px] py-1 px-2\">Reset canvas</TooltipContent>\n </Tooltip>\n </Show>\n\n {/* Copy for AI */}\n <Tooltip>\n <TooltipTrigger\n as={Button}\n variant=\"ghost\"\n size=\"sm\"\n onClick={handleCopyAi}\n aria-label=\"Copy context formatted for AI assistants\"\n class=\"h-7 px-2 text-xs text-muted-foreground hover:text-foreground border border-border/50 rounded-md cursor-pointer flex items-center gap-1.5\"\n >\n <Show when={aiClipboard.copied()} fallback={<Sparkles class=\"size-3\" />}>\n <Check class=\"size-3 text-emerald-500\" />\n </Show>\n <span class=\"hidden sm:inline\">{aiClipboard.copied() ? \"Copied!\" : \"Prompt\"}</span>\n </TooltipTrigger>\n <TooltipContent class=\"text-[10px] py-1 px-2\">Copy code formatted for AI</TooltipContent>\n </Tooltip>\n\n {/* Copy CLI Installation Command (only when command prop is provided) */}\n <Show when={cliCommand()}>\n <Tooltip>\n <TooltipTrigger\n as={Button}\n variant=\"ghost\"\n size=\"sm\"\n onClick={handleCopyCmd}\n aria-label=\"Copy CLI installation command\"\n class=\"h-7 px-2 text-xs font-mono text-muted-foreground hover:text-foreground border border-border/50 rounded-md cursor-pointer flex items-center gap-1.5\"\n >\n <Show when={cmdClipboard.copied()} fallback={<Terminal class=\"size-3\" />}>\n <Check class=\"size-3 text-emerald-500\" />\n </Show>\n <span class=\"hidden md:inline\">{cmdClipboard.copied() ? \"Copied!\" : cliCommand()}</span>\n </TooltipTrigger>\n <TooltipContent class=\"text-[10px] py-1 px-2\">Copy CLI command</TooltipContent>\n </Tooltip>\n </Show>\n\n {/* Fullscreen Expand Button */}\n <Tooltip>\n <TooltipTrigger\n as={Button}\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => setIsFullscreen(!isFullscreen())}\n aria-label={isFullscreen() ? \"Exit fullscreen\" : \"Expand to fullscreen\"}\n class=\"size-7 rounded-md border border-border/50 text-muted-foreground hover:text-foreground cursor-pointer\"\n >\n <Show when={isFullscreen()} fallback={<Maximize2 class=\"size-3.5\" />}>\n <Minimize2 class=\"size-3.5 text-primary\" />\n </Show>\n </TooltipTrigger>\n <TooltipContent class=\"text-[10px] py-1 px-2\">\n {isFullscreen() ? \"Exit Fullscreen\" : \"Fullscreen Preview\"}\n </TooltipContent>\n </Tooltip>\n </div>\n </div>\n\n {/* Live Preview Canvas Content */}\n <TabsContent\n value=\"preview\"\n class={cn(\n \"relative flex-1 p-4 sm:p-6 md:p-10 flex flex-col items-center justify-center transition-all\",\n isModal ? \"min-h-[450px] flex-1\" : \"min-h-[280px]\",\n showGrid() &&\n \"bg-[radial-gradient(#e5e7eb_1px,transparent_1px)] dark:bg-[radial-gradient(#27272a_1px,transparent_1px)] [background-size:16px_16px]\",\n local.allowOverflow ? \"overflow-visible\" : \"overflow-x-auto\"\n )}\n >\n <div\n class={cn(\n \"w-full transition-all duration-300 flex\",\n viewportWidthClass(),\n alignmentClass(),\n viewport() !== \"desktop\" &&\n \"rounded-lg border border-dashed border-border/80 p-4 bg-background/80 shadow-xs\"\n )}\n >\n <Show when={mounted()}>\n {local.children}\n </Show>\n </div>\n </TabsContent>\n\n {/* Source Code View Content */}\n <TabsContent value=\"code\" class={cn(\"p-0 m-0\", isModal && \"flex-1 overflow-auto\")}>\n <CodeBlock\n code={local.code}\n lang={local.lang || \"tsx\"}\n filename={local.name ? `${local.name}.tsx` : undefined}\n class=\"my-0 border-0 rounded-t-none\"\n />\n </TabsContent>\n </Tabs>\n </div>\n );\n\n return (\n <>\n <Show when={!isFullscreen()}>\n {renderViewerContent(false)}\n </Show>\n\n {/* Fullscreen Portal Modal */}\n <Show when={isFullscreen()}>\n <Portal mount={typeof document !== \"undefined\" ? document.body : undefined}>\n <div class=\"fixed inset-0 z-[9999] flex flex-col bg-background/95 backdrop-blur-md p-4 sm:p-8 animate-in fade-in duration-200\">\n {renderViewerContent(true)}\n </div>\n </Portal>\n </Show>\n </>\n );\n};\n",
|
|
22
|
+
"type": "registry:ui"
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "container",
|
|
3
|
+
"title": "Container",
|
|
4
|
+
"description": "A responsive layout container constraining maximum width with semantic padding tokens.",
|
|
5
|
+
"type": "registry:ui",
|
|
6
|
+
"dependencies": [
|
|
7
|
+
"clsx",
|
|
8
|
+
"tailwind-merge",
|
|
9
|
+
"class-variance-authority"
|
|
10
|
+
],
|
|
11
|
+
"files": [
|
|
12
|
+
{
|
|
13
|
+
"path": "ui/container.tsx",
|
|
14
|
+
"content": "import { splitProps, type JSX, type ValidComponent } from \"solid-js\";\nimport { Dynamic } from \"solid-js/web\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"@/lib/cn\";\n\nexport const containerVariants = cva(\"w-full mx-auto px-4 sm:px-6 lg:px-8\", {\n variants: {\n size: {\n sm: \"max-w-screen-sm\",\n md: \"max-w-screen-md\",\n lg: \"max-w-screen-lg\",\n xl: \"max-w-screen-xl\",\n \"2xl\": \"max-w-screen-2xl\",\n full: \"max-w-full\",\n },\n },\n defaultVariants: {\n size: \"2xl\",\n },\n});\n\nexport interface ContainerProps<T extends ValidComponent = \"div\">\n extends JSX.HTMLAttributes<HTMLDivElement>,\n VariantProps<typeof containerVariants> {\n /** Underlying HTML element or Solid component tag (defaults to \"div\") */\n as?: T;\n class?: string;\n}\n\n/**\n * Responsive container primitive constraining max-width with semantic padding tokens.\n */\nexport const Container = <T extends ValidComponent = \"div\">(props: ContainerProps<T>) => {\n const [local, rest] = splitProps(props as ContainerProps, [\"as\", \"size\", \"class\", \"children\"]);\n\n return (\n <Dynamic\n component={local.as || \"div\"}\n class={cn(containerVariants({ size: local.size }), local.class)}\n {...rest}\n >\n {local.children}\n </Dynamic>\n );\n};\n",
|
|
15
|
+
"type": "registry:ui"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"files": [
|
|
18
18
|
{
|
|
19
19
|
"path": "ui/context-menu.tsx",
|
|
20
|
-
"content": "import { splitProps, type Component, type JSX, type ValidComponent } from \"solid-js\";\nimport * as ContextMenuPrimitive from \"@kobalte/core/context-menu\";\nimport type { PolymorphicProps } from \"@kobalte/core/polymorphic\";\nimport { createClickOutside } from \"
|
|
20
|
+
"content": "import { splitProps, type Component, type JSX, type ValidComponent } from \"solid-js\";\nimport * as ContextMenuPrimitive from \"@kobalte/core/context-menu\";\nimport type { PolymorphicProps } from \"@kobalte/core/polymorphic\";\nimport { createClickOutside } from \"@/hooks/create-click-outside\";\nimport { ScrollArea } from \"@/components/ui/scroll-area\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { Kbd } from \"@/components/ui/kbd\";\nimport { cn } from \"@/lib/cn\";\n\nexport const ContextMenu = ContextMenuPrimitive.Root;\nexport const ContextMenuTrigger = ContextMenuPrimitive.Trigger;\nexport const ContextMenuGroup = ContextMenuPrimitive.Group;\nexport const ContextMenuPortal = ContextMenuPrimitive.Portal;\nexport const ContextMenuSub = ContextMenuPrimitive.Sub;\nexport const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup;\n\nexport type ContextMenuSubTriggerProps<T extends ValidComponent = \"div\"> =\n ContextMenuPrimitive.ContextMenuSubTriggerProps<T> & {\n class?: string;\n children?: JSX.Element;\n inset?: boolean;\n };\n\nexport const ContextMenuSubTrigger = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, ContextMenuSubTriggerProps<T>>\n) => {\n const [local, rest] = splitProps(props as ContextMenuSubTriggerProps, [\"class\", \"children\", \"inset\"]);\n\n return (\n <ContextMenuPrimitive.SubTrigger\n class={cn(\n \"flex cursor-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 </ContextMenuPrimitive.SubTrigger>\n );\n};\n\nexport type ContextMenuSubContentProps<T extends ValidComponent = \"div\"> =\n ContextMenuPrimitive.ContextMenuSubContentProps<T> & {\n class?: string;\n };\n\nexport const ContextMenuSubContent = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, ContextMenuSubContentProps<T>>\n) => {\n const [local, rest] = splitProps(props as ContextMenuSubContentProps, [\"class\"]);\n\n return (\n <ContextMenuPrimitive.Portal>\n <ContextMenuPrimitive.SubContent\n class={cn(\n \"z-50 min-w-[8rem] overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md transition-all animate-in fade-in-80 slide-in-from-top-1\",\n local.class\n )}\n {...(rest as any)}\n />\n </ContextMenuPrimitive.Portal>\n );\n};\n\nexport type ContextMenuContentProps<T extends ValidComponent = \"div\"> =\n ContextMenuPrimitive.ContextMenuContentProps<T> & {\n class?: string;\n ref?: (el: HTMLElement) => void;\n children?: JSX.Element;\n };\n\nexport const ContextMenuContent = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, ContextMenuContentProps<T>>\n) => {\n const [local, rest] = splitProps(props as ContextMenuContentProps, [\"class\", \"ref\", \"children\"]);\n let menuRef: HTMLElement | undefined;\n\n createClickOutside({\n target: () => menuRef,\n onInteractOutside: () => {\n // Automatic portal dismiss on outside click\n },\n });\n\n return (\n <ContextMenuPrimitive.Portal>\n <ContextMenuPrimitive.Content\n ref={(el) => {\n menuRef = el;\n if (typeof local.ref === \"function\") local.ref(el);\n }}\n class={cn(\n \"z-50 min-w-[8rem] rounded-md border border-border bg-popover text-popover-foreground shadow-md transition-all animate-in fade-in-80 slide-in-from-top-1 max-h-72 flex flex-col\",\n local.class\n )}\n {...(rest as any)}\n >\n <ScrollArea class=\"max-h-72 w-full rounded-[inherit]\">\n <div class=\"p-1\">\n {local.children}\n </div>\n </ScrollArea>\n </ContextMenuPrimitive.Content>\n </ContextMenuPrimitive.Portal>\n );\n};\n\nexport type ContextMenuItemProps<T extends ValidComponent = \"div\"> =\n ContextMenuPrimitive.ContextMenuItemProps<T> & {\n class?: string;\n inset?: boolean;\n };\n\nexport const ContextMenuItem = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, ContextMenuItemProps<T>>\n) => {\n const [local, rest] = splitProps(props as ContextMenuItemProps, [\"class\", \"inset\"]);\n\n return (\n <ContextMenuPrimitive.Item\n class={cn(\n \"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n local.inset && \"pl-8\",\n local.class\n )}\n {...(rest as any)}\n />\n );\n};\n\nexport type ContextMenuCheckboxItemProps<T extends ValidComponent = \"div\"> =\n ContextMenuPrimitive.ContextMenuCheckboxItemProps<T> & {\n class?: string;\n children?: JSX.Element;\n checked?: boolean;\n };\n\nexport const ContextMenuCheckboxItem = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, ContextMenuCheckboxItemProps<T>>\n) => {\n const [local, rest] = splitProps(props as ContextMenuCheckboxItemProps, [\"class\", \"children\", \"checked\"]);\n\n return (\n <ContextMenuPrimitive.CheckboxItem\n checked={local.checked}\n class={cn(\n \"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n local.class\n )}\n {...(rest as any)}\n >\n <span class=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n <ContextMenuPrimitive.ItemIndicator>\n <svg class=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M5 13l4 4L19 7\" />\n </svg>\n </ContextMenuPrimitive.ItemIndicator>\n </span>\n {local.children}\n </ContextMenuPrimitive.CheckboxItem>\n );\n};\n\nexport type ContextMenuRadioItemProps<T extends ValidComponent = \"div\"> =\n ContextMenuPrimitive.ContextMenuRadioItemProps<T> & {\n class?: string;\n children?: JSX.Element;\n };\n\nexport const ContextMenuRadioItem = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, ContextMenuRadioItemProps<T>>\n) => {\n const [local, rest] = splitProps(props as ContextMenuRadioItemProps, [\"class\", \"children\"]);\n\n return (\n <ContextMenuPrimitive.RadioItem\n class={cn(\n \"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n local.class\n )}\n {...(rest as any)}\n >\n <span class=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n <ContextMenuPrimitive.ItemIndicator>\n <svg class=\"h-2 w-2 fill-current\" viewBox=\"0 0 8 8\">\n <circle cx=\"4\" cy=\"4\" r=\"3\" />\n </svg>\n </ContextMenuPrimitive.ItemIndicator>\n </span>\n {local.children}\n </ContextMenuPrimitive.RadioItem>\n );\n};\n\nexport interface ContextMenuLabelProps {\n class?: string;\n inset?: boolean;\n children?: JSX.Element;\n}\n\nexport const ContextMenuLabel: Component<ContextMenuLabelProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"inset\", \"children\"]);\n\n return (\n <ContextMenuPrimitive.GroupLabel\n class={cn(\"px-2 py-1.5 text-sm font-semibold text-foreground\", local.inset && \"pl-8\", local.class)}\n {...rest}\n >\n {local.children}\n </ContextMenuPrimitive.GroupLabel>\n );\n};\n\nexport const ContextMenuSeparator: Component<{ class?: string }> = (props) => {\n return <Separator class={cn(\"-mx-1 my-1\", props.class)} />;\n};\n\nexport interface ContextMenuShortcutProps {\n class?: string;\n children?: JSX.Element;\n}\n\nexport const ContextMenuShortcut: Component<ContextMenuShortcutProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <span class={cn(\"ml-auto pl-4 text-xs tracking-widest text-muted-foreground\", local.class)} {...rest}>\n <Kbd size=\"sm\" variant=\"outline\">\n {local.children}\n </Kbd>\n </span>\n );\n};\n",
|
|
21
21
|
"type": "registry:ui"
|
|
22
22
|
}
|
|
23
23
|
]
|