@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.
Files changed (107) hide show
  1. package/package.json +43 -2
  2. package/registry/api-table.json +21 -0
  3. package/registry/banner.json +1 -1
  4. package/registry/button.json +1 -1
  5. package/registry/callout.json +19 -0
  6. package/registry/checkbox.json +4 -1
  7. package/registry/code-block.json +26 -0
  8. package/registry/code-group.json +20 -0
  9. package/registry/combobox.json +1 -1
  10. package/registry/command.json +3 -2
  11. package/registry/component-viewer.json +25 -0
  12. package/registry/container.json +18 -0
  13. package/registry/context-menu.json +1 -1
  14. package/registry/create-app-updater.json +13 -0
  15. package/registry/create-document-tabs.json +13 -0
  16. package/registry/create-global-shortcut.json +13 -0
  17. package/registry/create-tauri-window.json +13 -0
  18. package/registry/create-tiptap-editor.json +34 -0
  19. package/registry/dialog.json +1 -1
  20. package/registry/dropdown-menu.json +3 -2
  21. package/registry/field.json +1 -1
  22. package/registry/file-tree.json +21 -0
  23. package/registry/footer.json +1 -1
  24. package/registry/form-message.json +3 -2
  25. package/registry/icon-button.json +1 -1
  26. package/registry/index.json +336 -5
  27. package/registry/navbar.json +1 -1
  28. package/registry/navigation-menu.json +1 -1
  29. package/registry/number-input.json +1 -1
  30. package/registry/package-manager-tabs.json +24 -0
  31. package/registry/pager.json +21 -0
  32. package/registry/popover.json +4 -1
  33. package/registry/resizable.json +1 -1
  34. package/registry/rich-text-editor.json +95 -0
  35. package/registry/scroll-area.json +1 -1
  36. package/registry/section-heading.json +21 -0
  37. package/registry/select.json +3 -2
  38. package/registry/sheet.json +1 -1
  39. package/registry/sidebar.json +1 -1
  40. package/registry/status.json +1 -1
  41. package/registry/steps.json +20 -0
  42. package/registry/switch.json +4 -1
  43. package/registry/table-of-contents.json +23 -0
  44. package/registry/tabs.json +4 -1
  45. package/registry/theme-manager.json +6 -5
  46. package/registry/titlebar-tabs.json +24 -0
  47. package/registry/titlebar.json +26 -0
  48. package/registry/toggle-group.json +1 -1
  49. package/registry/updater-modal.json +26 -0
  50. package/src/index.ts +48 -0
  51. package/src/registry/components/ui/api-table.tsx +117 -0
  52. package/src/registry/components/ui/banner.tsx +0 -2
  53. package/src/registry/components/ui/button.tsx +1 -1
  54. package/src/registry/components/ui/callout.tsx +137 -0
  55. package/src/registry/components/ui/checkbox.tsx +1 -1
  56. package/src/registry/components/ui/code-block.tsx +326 -0
  57. package/src/registry/components/ui/code-group.tsx +105 -0
  58. package/src/registry/components/ui/combobox.tsx +83 -18
  59. package/src/registry/components/ui/command.tsx +110 -106
  60. package/src/registry/components/ui/component-viewer.tsx +363 -0
  61. package/src/registry/components/ui/container.tsx +45 -0
  62. package/src/registry/components/ui/context-menu.tsx +67 -22
  63. package/src/registry/components/ui/dialog.tsx +42 -32
  64. package/src/registry/components/ui/dropdown-menu.tsx +69 -31
  65. package/src/registry/components/ui/field.tsx +7 -3
  66. package/src/registry/components/ui/file-tree.tsx +181 -0
  67. package/src/registry/components/ui/footer.tsx +1 -1
  68. package/src/registry/components/ui/form-message.tsx +2 -2
  69. package/src/registry/components/ui/icon-button.tsx +1 -1
  70. package/src/registry/components/ui/navbar.tsx +18 -11
  71. package/src/registry/components/ui/navigation-menu.tsx +2 -2
  72. package/src/registry/components/ui/number-input.tsx +3 -3
  73. package/src/registry/components/ui/package-manager-tabs.tsx +226 -0
  74. package/src/registry/components/ui/pager.tsx +102 -0
  75. package/src/registry/components/ui/popover.tsx +1 -1
  76. package/src/registry/components/ui/resizable.tsx +3 -1
  77. package/src/registry/components/ui/rich-text-editor/bubble-menu.tsx +236 -0
  78. package/src/registry/components/ui/rich-text-editor/editor.tsx +308 -0
  79. package/src/registry/components/ui/rich-text-editor/extensions.ts +116 -0
  80. package/src/registry/components/ui/rich-text-editor/footer.tsx +36 -0
  81. package/src/registry/components/ui/rich-text-editor/image-dialog.tsx +75 -0
  82. package/src/registry/components/ui/rich-text-editor/index.ts +11 -0
  83. package/src/registry/components/ui/rich-text-editor/link-dialog.tsx +48 -0
  84. package/src/registry/components/ui/rich-text-editor/markdown.ts +170 -0
  85. package/src/registry/components/ui/rich-text-editor/slash-command.tsx +301 -0
  86. package/src/registry/components/ui/rich-text-editor/table-controls.tsx +68 -0
  87. package/src/registry/components/ui/rich-text-editor/toolbar.tsx +465 -0
  88. package/src/registry/components/ui/rich-text-editor/use-editor.ts +231 -0
  89. package/src/registry/components/ui/scroll-area.tsx +12 -2
  90. package/src/registry/components/ui/section-heading.tsx +108 -0
  91. package/src/registry/components/ui/select.tsx +16 -19
  92. package/src/registry/components/ui/sheet.tsx +58 -53
  93. package/src/registry/components/ui/sidebar.tsx +4 -4
  94. package/src/registry/components/ui/status.tsx +7 -5
  95. package/src/registry/components/ui/steps.tsx +198 -0
  96. package/src/registry/components/ui/switch.tsx +1 -1
  97. package/src/registry/components/ui/table-of-contents.tsx +131 -0
  98. package/src/registry/components/ui/tabs.tsx +1 -1
  99. package/src/registry/components/ui/theme-toggle.tsx +175 -139
  100. package/src/registry/components/ui/titlebar-tabs.tsx +773 -0
  101. package/src/registry/components/ui/titlebar.tsx +468 -0
  102. package/src/registry/components/ui/toggle-group.tsx +1 -1
  103. package/src/registry/components/ui/updater-modal.tsx +254 -0
  104. package/src/registry/metadata.ts +167 -6
  105. package/src/registry/providers/theme-provider.tsx +16 -8
  106. package/src/registry/providers/theme-script.tsx +29 -9
  107. package/src/registry/providers/theme-transitions.ts +81 -50
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "titlebar",
3
+ "title": "Titlebar",
4
+ "description": "A native-feeling custom titlebar for frameless desktop windows supporting macOS Traffic Lights and Windows 11 controls.",
5
+ "type": "registry:ui",
6
+ "dependencies": [
7
+ "clsx",
8
+ "tailwind-merge",
9
+ "class-variance-authority",
10
+ "lucide-solid"
11
+ ],
12
+ "registryDependencies": [
13
+ "titlebar-tabs",
14
+ "button",
15
+ "tooltip",
16
+ "create-tauri-window",
17
+ "create-document-tabs"
18
+ ],
19
+ "files": [
20
+ {
21
+ "path": "ui/titlebar.tsx",
22
+ "content": "import {\n createContext,\n useContext,\n splitProps,\n createMemo,\n Show,\n type Component,\n type JSX,\n type ParentComponent,\n type Accessor,\n} from \"solid-js\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { createTauriWindow, type TauriPlatform, detectPlatform } from \"@/hooks/create-tauri-window\";\nimport { cn } from \"@/lib/cn\";\n\n/* --- 1. Context & Types --- */\nexport type TitlebarPlatform = \"macos\" | \"windows\" | \"auto\";\n\nexport interface TitlebarContextValue {\n platform: Accessor<\"macos\" | \"windows\">;\n isMaximized: Accessor<boolean>;\n minimize: () => Promise<void> | void;\n toggleMaximize: () => Promise<void> | void;\n close: () => Promise<void> | void;\n destroy?: () => Promise<void> | void;\n}\n\nconst TitlebarContext = createContext<TitlebarContextValue>();\n\nexport function useTitlebar() {\n const context = useContext(TitlebarContext);\n if (!context) {\n throw new Error(\"useTitlebar must be used within a <Titlebar />\");\n }\n return context;\n}\n\n/* --- 2. Titlebar Variants --- */\nexport const titlebarVariants = cva(\n \"relative flex w-full select-none items-center justify-between border-b transition-colors shrink-0 sticky top-0 z-50\",\n {\n variants: {\n variant: {\n default: \"border-border/50 bg-background text-foreground\",\n translucent: \"border-border/40 bg-background/80 backdrop-blur-md text-foreground\",\n floating: \"m-2 rounded-lg border border-border bg-card shadow-sm text-card-foreground\",\n transparent: \"border-transparent bg-transparent text-foreground\",\n },\n size: {\n sm: \"h-8 text-xs px-2.5\",\n default: \"h-9 text-xs px-3\",\n lg: \"h-11 text-sm px-4\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n }\n);\n\n/* --- 3. Root Titlebar Component --- */\nexport interface TitlebarProps\n extends JSX.HTMLAttributes<HTMLElement>,\n VariantProps<typeof titlebarVariants> {\n /** Explicit platform style, or 'auto' to detect host OS. */\n platform?: TitlebarPlatform;\n /** Optional controlled maximize state override. */\n isMaximized?: boolean;\n /** Optional minimize callback override. */\n onMinimize?: () => void | Promise<void>;\n /** Optional maximize callback override. */\n onToggleMaximize?: () => void | Promise<void>;\n /** Optional close callback override. */\n onClose?: () => void | Promise<void>;\n /** Whether double-clicking the titlebar toggles window maximize state. Defaults to true. */\n doubleClickToMaximize?: boolean;\n}\n\nexport const Titlebar: ParentComponent<TitlebarProps> = (props) => {\n const [local, rest] = splitProps(props, [\n \"platform\",\n \"isMaximized\",\n \"onMinimize\",\n \"onToggleMaximize\",\n \"onClose\",\n \"variant\",\n \"size\",\n \"class\",\n \"children\",\n \"doubleClickToMaximize\",\n \"onMouseDown\",\n \"onDblClick\",\n ]);\n\n const windowCtrl = createTauriWindow();\n\n const resolvedPlatform = createMemo<\"macos\" | \"windows\">(() => {\n if (local.platform && local.platform !== \"auto\") {\n return local.platform;\n }\n const detected = detectPlatform();\n return detected === \"macos\" ? \"macos\" : \"windows\";\n });\n\n const isMaximized = () =>\n local.isMaximized !== undefined ? local.isMaximized : windowCtrl.isMaximized();\n\n const minimize = () => (local.onMinimize ? local.onMinimize() : windowCtrl.minimize());\n const toggleMaximize = () =>\n local.onToggleMaximize ? local.onToggleMaximize() : windowCtrl.toggleMaximize();\n const close = () => (local.onClose ? local.onClose() : windowCtrl.close());\n const destroy = () => windowCtrl.destroy();\n\n const handleMouseDown: JSX.EventHandlerUnion<HTMLElement, MouseEvent> = (e) => {\n if (e.button === 0) {\n const target = e.target as HTMLElement;\n if (!target.closest(\"button, a, input, select, textarea, [data-no-drag], [data-tauri-drag-region='false']\")) {\n if (e.detail === 2 && local.doubleClickToMaximize !== false) {\n // Double-click detected on mousedown detail - toggles maximize immediately without initiating a drag\n toggleMaximize();\n } else if (e.detail === 1) {\n // Single-click: initiate window drag\n windowCtrl.startDragging(e);\n }\n }\n }\n if (typeof local.onMouseDown === \"function\") {\n (local.onMouseDown as any)(e);\n }\n };\n\n let lastDblClickTime = 0;\n const handleDoubleClick: JSX.EventHandlerUnion<HTMLElement, MouseEvent> = (e) => {\n const now = Date.now();\n if (now - lastDblClickTime < 350) {\n return; // Deduplicate rapid triggers\n }\n lastDblClickTime = now;\n\n if (local.doubleClickToMaximize !== false) {\n // Don't maximize if user double-clicked an interactive control\n const target = e.target as HTMLElement;\n if (!target.closest(\"button, a, input, select, textarea, [data-no-drag], [data-tauri-drag-region='false']\")) {\n toggleMaximize();\n }\n }\n if (typeof local.onDblClick === \"function\") {\n (local.onDblClick as any)(e);\n }\n };\n\n const contextValue: TitlebarContextValue = {\n platform: resolvedPlatform,\n isMaximized,\n minimize,\n toggleMaximize,\n close,\n destroy,\n };\n\n return (\n <TitlebarContext.Provider value={contextValue}>\n <header\n onMouseDown={handleMouseDown}\n onDblClick={handleDoubleClick}\n class={cn(\n titlebarVariants({ variant: local.variant, size: local.size }),\n \"select-none cursor-default\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n </header>\n </TitlebarContext.Provider>\n );\n};\n\n/* --- 4. Subcomponents --- */\n\n/**\n * Traffic light buttons for macOS or Fluent control buttons for Windows.\n */\nexport interface TitlebarControlsProps extends JSX.HTMLAttributes<HTMLDivElement> {\n /** Optional override platform style. Defaults to Titlebar context or detected OS. */\n platform?: \"macos\" | \"windows\";\n /** Optional controlled maximize state override. */\n isMaximized?: boolean;\n /** Optional minimize callback override. */\n onMinimize?: () => void | Promise<void>;\n /** Optional maximize callback override. */\n onToggleMaximize?: () => void | Promise<void>;\n /** Optional close callback override. */\n onClose?: () => void | Promise<void>;\n}\n\nexport const TitlebarControls: Component<TitlebarControlsProps> = (props) => {\n const [local, rest] = splitProps(props, [\n \"platform\",\n \"class\",\n \"style\",\n \"isMaximized\",\n \"onMinimize\",\n \"onToggleMaximize\",\n \"onClose\",\n ]);\n const ctx = useContext(TitlebarContext);\n let fallbackCtrl: ReturnType<typeof createTauriWindow> | null = null;\n if (!ctx) {\n fallbackCtrl = createTauriWindow();\n }\n\n const activePlatform = createMemo<\"macos\" | \"windows\">(() => {\n if (local.platform) return local.platform;\n if (ctx?.platform) return ctx.platform();\n const detected = detectPlatform();\n return detected === \"macos\" ? \"macos\" : \"windows\";\n });\n\n const isMaximized = () => {\n if (local.isMaximized !== undefined) return local.isMaximized;\n if (ctx?.isMaximized) return ctx.isMaximized();\n return fallbackCtrl ? fallbackCtrl.isMaximized() : false;\n };\n\n const handleMinimize = (e: MouseEvent) => {\n e.stopPropagation();\n if (local.onMinimize) return local.onMinimize();\n if (ctx?.minimize) return ctx.minimize();\n return fallbackCtrl?.minimize();\n };\n\n const handleToggleMaximize = (e: MouseEvent) => {\n e.stopPropagation();\n if (local.onToggleMaximize) return local.onToggleMaximize();\n if (ctx?.toggleMaximize) return ctx.toggleMaximize();\n return fallbackCtrl?.toggleMaximize();\n };\n\n const handleClose = (e: MouseEvent) => {\n e.stopPropagation();\n if (local.onClose) return local.onClose();\n if (ctx?.close) return ctx.close();\n return fallbackCtrl?.close();\n };\n\n return (\n <div\n data-no-drag\n data-tauri-drag-region=\"false\"\n style={(\"-webkit-app-region: no-drag; app-region: no-drag;\" + (typeof local.style === \"string\" ? ` ${local.style}` : \"\")) as any}\n class={cn(\n \"flex items-center z-10 self-stretch shrink-0 pointer-events-auto [app-region:no-drag] [-webkit-app-region:no-drag]\",\n activePlatform() === \"macos\"\n ? \"gap-2 px-1 order-first\"\n : \"gap-0 -mr-3 h-full order-last ml-auto\",\n local.class\n )}\n {...rest}\n >\n <Show\n when={activePlatform() === \"macos\"}\n fallback={\n /* Windows 11 Fluent Window Controls */\n <div class=\"flex items-center h-full self-stretch\">\n {/* Minimize */}\n <button\n type=\"button\"\n onClick={handleMinimize}\n class=\"inline-flex h-full w-11 items-center justify-center text-muted-foreground hover:bg-muted hover:text-foreground transition-colors cursor-pointer\"\n aria-label=\"Minimize Window\"\n >\n <svg class=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 12 12\">\n <path stroke=\"currentColor\" stroke-width=\"1\" d=\"M1.5 6h9\" />\n </svg>\n </button>\n\n {/* Maximize / Restore */}\n <button\n type=\"button\"\n onClick={handleToggleMaximize}\n class=\"inline-flex h-full w-11 items-center justify-center text-muted-foreground hover:bg-muted hover:text-foreground transition-colors cursor-pointer\"\n aria-label={isMaximized() ? \"Restore Window\" : \"Maximize Window\"}\n >\n <Show\n when={isMaximized()}\n fallback={\n <svg class=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 12 12\">\n <rect width=\"8\" height=\"8\" x=\"2\" y=\"2\" stroke=\"currentColor\" stroke-width=\"1\" rx=\"0.5\" />\n </svg>\n }\n >\n <svg class=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 12 12\">\n <path stroke=\"currentColor\" stroke-width=\"1\" d=\"M3.5 3.5v-1.5h6v6h-1.5M2 4.5h6v6h-6z\" />\n </svg>\n </Show>\n </button>\n\n {/* Close */}\n <button\n type=\"button\"\n onClick={handleClose}\n class=\"inline-flex h-full w-11 items-center justify-center text-muted-foreground hover:bg-destructive hover:text-destructive-foreground transition-colors cursor-pointer\"\n aria-label=\"Close Window\"\n >\n <svg class=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 12 12\">\n <path stroke=\"currentColor\" stroke-width=\"1\" d=\"M2.5 2.5l7 7M9.5 2.5l-7 7\" />\n </svg>\n </button>\n </div>\n }\n >\n {/* macOS Traffic Lights */}\n <div class=\"group/traffic flex items-center gap-2\">\n {/* Close */}\n <button\n type=\"button\"\n onClick={handleClose}\n class=\"flex size-3 items-center justify-center rouded-lg bg-[#ff5f57] border border-[#e0443e] cursor-pointer\"\n aria-label=\"Close Window\"\n >\n <svg\n class=\"size-1.5 text-[#4c0000] opacity-0 group-hover/traffic:opacity-100 transition-opacity\"\n viewBox=\"0 0 6 6\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.2\"\n >\n <path d=\"M1 1l4 4M5 1L1 5\" />\n </svg>\n </button>\n\n {/* Minimize */}\n <button\n type=\"button\"\n onClick={handleMinimize}\n class=\"flex size-3 items-center justify-center rouded-lg bg-[#febc2e] border border-[#d89e24] cursor-pointer\"\n aria-label=\"Minimize Window\"\n >\n <svg\n class=\"size-1.5 text-[#5c3e00] opacity-0 group-hover/traffic:opacity-100 transition-opacity\"\n viewBox=\"0 0 6 6\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.2\"\n >\n <path d=\"M1 3h4\" />\n </svg>\n </button>\n\n {/* Maximize */}\n <button\n type=\"button\"\n onClick={handleToggleMaximize}\n class=\"flex size-3 items-center justify-center rouded-lg bg-[#28c840] border border-[#1aab29] cursor-pointer\"\n aria-label=\"Maximize Window\"\n >\n <svg\n class=\"size-1.5 text-[#003d07] opacity-0 group-hover/traffic:opacity-100 transition-opacity\"\n viewBox=\"0 0 6 6\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.2\"\n >\n <path d=\"M1 4.5l3.5-3.5M4.5 1h-3M4.5 1v3\" />\n </svg>\n </button>\n </div>\n </Show>\n </div>\n );\n};\n\n/**\n * Title text container. Placed naturally in JSX flow by default, or centered with align=\"center\".\n */\nexport interface TitlebarTitleProps extends JSX.HTMLAttributes<HTMLDivElement> {\n align?: \"center\" | \"start\";\n}\n\nexport const TitlebarTitle: ParentComponent<TitlebarTitleProps> = (props) => {\n const [local, rest] = splitProps(props, [\"align\", \"class\", \"children\"]);\n\n const isCentered = () => local.align === \"center\";\n\n return (\n <div\n class={cn(\n \"flex items-center gap-2 font-medium truncate text-xs text-foreground\",\n isCentered()\n ? \"absolute left-1/2 -translate-x-1/2 pointer-events-none text-center justify-center max-w-[calc(100%-160px)]\"\n : \"shrink-0\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n </div>\n );\n};\n\n/**\n * App icon slot within Titlebar.\n */\nexport const TitlebarIcon: ParentComponent<JSX.HTMLAttributes<HTMLDivElement>> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n return (\n <div\n data-no-drag\n data-tauri-drag-region=\"false\"\n class={cn(\n \"flex shrink-0 items-center justify-center size-4 text-muted-foreground pointer-events-auto [app-region:no-drag] [-webkit-app-region:no-drag]\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n </div>\n );\n};\n\n/**\n * Action buttons container (Search, Settings, ThemeToggle, Navigation) placed in the titlebar.\n * Supports start, end, center, or natural flow alignment.\n */\nexport interface TitlebarActionsProps extends JSX.HTMLAttributes<HTMLDivElement> {\n /** Alignment of the actions container within the titlebar. Defaults to 'end'. */\n align?: \"start\" | \"end\" | \"center\" | \"none\";\n}\n\nexport const TitlebarActions: ParentComponent<TitlebarActionsProps> = (props) => {\n const [local, rest] = splitProps(props, [\"align\", \"class\", \"children\", \"style\"]);\n\n const alignClass = () => {\n switch (local.align) {\n case \"start\":\n return \"mr-auto\";\n case \"center\":\n return \"mx-auto\";\n case \"none\":\n return \"\";\n case \"end\":\n default:\n return \"ml-auto\";\n }\n };\n\n return (\n <div\n data-no-drag\n data-tauri-drag-region=\"false\"\n style={(\"-webkit-app-region: no-drag; app-region: no-drag;\" + (typeof local.style === \"string\" ? ` ${local.style}` : \"\")) as any}\n class={cn(\n \"flex items-center gap-1 shrink-0 z-10 px-1 pointer-events-auto [app-region:no-drag] [-webkit-app-region:no-drag]\",\n alignClass(),\n local.class\n )}\n {...rest}\n >\n {local.children}\n </div>\n );\n};\n\n/* --- 6. Re-export TitlebarTabs Suite --- */\nexport * from \"./titlebar-tabs\";\n\n",
23
+ "type": "registry:ui"
24
+ }
25
+ ]
26
+ }
@@ -14,7 +14,7 @@
14
14
  "files": [
15
15
  {
16
16
  "path": "ui/toggle-group.tsx",
17
- "content": "import {\n createContext,\n useContext,\n splitProps,\n type Component,\n type JSX,\n type Accessor,\n} from \"solid-js\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { createControllableSignal } from \"@nikala-ui/hooks\";\nimport { cn } from \"@/lib/cn\";\n\nexport const toggleGroupItemVariants = cva(\n \"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 cursor-pointer\",\n {\n variants: {\n variant: {\n default: \"bg-transparent\",\n outline:\n \"border border-border bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground data-[state=on]:bg-accent data-[state=on]:text-accent-foreground\",\n },\n size: {\n default: \"h-9 px-3 min-w-9\",\n sm: \"h-8 px-2 text-xs min-w-8\",\n lg: \"h-10 px-3 min-w-10\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n }\n);\n\ninterface ToggleGroupContextValue {\n type: \"single\" | \"multiple\";\n value: Accessor<any>;\n onItemSelect: (itemValue: string) => void;\n variant: Accessor<\"default\" | \"outline\">;\n size: Accessor<\"default\" | \"sm\" | \"lg\">;\n disabled: Accessor<boolean>;\n}\n\nconst ToggleGroupContext = createContext<ToggleGroupContextValue>();\n\nexport interface ToggleGroupProps<T extends \"single\" | \"multiple\" = \"single\">\n extends Omit<JSX.HTMLAttributes<HTMLDivElement>, \"onChange\">,\n VariantProps<typeof toggleGroupItemVariants> {\n /** Mode of selection: single choice or multiple choices */\n type?: T;\n /** Controlled value: string for 'single', string[] for 'multiple' */\n value?: T extends \"multiple\" ? string[] : string;\n /** Uncontrolled default value */\n defaultValue?: T extends \"multiple\" ? string[] : string;\n /** Callback fired when selection changes */\n onChange?: (value: T extends \"multiple\" ? string[] : string) => void;\n /** Layout orientation */\n orientation?: \"horizontal\" | \"vertical\";\n /** Disables all buttons in the group */\n disabled?: boolean;\n class?: string;\n children?: JSX.Element;\n}\n\n/**\n * Root container for grouping connected toggle items with shared single/multiple selection state.\n */\nexport const ToggleGroup = <T extends \"single\" | \"multiple\" = \"single\">(\n props: ToggleGroupProps<T>\n) => {\n const [local, rest] = splitProps(props, [\n \"type\",\n \"value\",\n \"defaultValue\",\n \"onChange\",\n \"orientation\",\n \"variant\",\n \"size\",\n \"disabled\",\n \"class\",\n \"children\",\n ]);\n\n const type = () => local.type ?? (\"single\" as T);\n const variant = () => local.variant ?? \"default\";\n const size = () => local.size ?? \"default\";\n const disabled = () => local.disabled ?? false;\n const orientation = () => local.orientation ?? \"horizontal\";\n\n const [currentValue, setCurrentValue] = createControllableSignal<any>({\n value: () => local.value,\n defaultValue: local.defaultValue ?? (type() === \"multiple\" ? [] : undefined),\n onChange: (val) => local.onChange?.(val),\n });\n\n const onItemSelect = (itemValue: string) => {\n if (disabled()) return;\n\n if (type() === \"multiple\") {\n const currentList = Array.isArray(currentValue()) ? currentValue() : [];\n if (currentList.includes(itemValue)) {\n setCurrentValue(currentList.filter((v: string) => v !== itemValue));\n } else {\n setCurrentValue([...currentList, itemValue]);\n }\n } else {\n const current = currentValue();\n if (current === itemValue) {\n setCurrentValue(undefined);\n } else {\n setCurrentValue(itemValue);\n }\n }\n };\n\n const contextValue: ToggleGroupContextValue = {\n type: type(),\n value: currentValue,\n onItemSelect,\n variant,\n size,\n disabled,\n };\n\n return (\n <ToggleGroupContext.Provider value={contextValue}>\n <div\n role=\"group\"\n data-orientation={orientation()}\n class={cn(\n \"flex items-center justify-center gap-1 rounded-lg\",\n orientation() === \"vertical\" ? \"flex-col\" : \"flex-row\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n </div>\n </ToggleGroupContext.Provider>\n );\n};\n\nexport interface ToggleGroupItemProps\n extends JSX.ButtonHTMLAttributes<HTMLButtonElement>,\n VariantProps<typeof toggleGroupItemVariants> {\n /** Unique value representing this toggle item within the group */\n value: string;\n class?: string;\n children?: JSX.Element;\n}\n\n/**\n * Individual toggle button item within a ToggleGroup.\n */\nexport const ToggleGroupItem: Component<ToggleGroupItemProps> = (props) => {\n const context = useContext(ToggleGroupContext);\n\n if (!context) {\n throw new Error(\"ToggleGroupItem must be used within a ToggleGroup\");\n }\n\n const [local, rest] = splitProps(props, [\n \"value\",\n \"variant\",\n \"size\",\n \"disabled\",\n \"class\",\n \"children\",\n ]);\n\n const isSelected = () => {\n const groupValue = context.value();\n if (context.type === \"multiple\") {\n return Array.isArray(groupValue) && groupValue.includes(local.value);\n }\n return groupValue === local.value;\n };\n\n const isDisabled = () => local.disabled || context.disabled();\n const itemVariant = () => local.variant || context.variant();\n const itemSize = () => local.size || context.size();\n\n return (\n <button\n type=\"button\"\n role={context.type === \"single\" ? \"radio\" : \"checkbox\"}\n aria-checked={isSelected()}\n data-state={isSelected() ? \"on\" : \"off\"}\n disabled={isDisabled()}\n onClick={() => context.onItemSelect(local.value)}\n class={cn(\n toggleGroupItemVariants({\n variant: itemVariant(),\n size: itemSize(),\n }),\n local.class\n )}\n {...rest}\n >\n {local.children}\n </button>\n );\n};\n",
17
+ "content": "import {\n createContext,\n useContext,\n splitProps,\n type Component,\n type JSX,\n type Accessor,\n} from \"solid-js\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { createControllableSignal } from \"@/hooks/create-controllable-signal\";\nimport { cn } from \"@/lib/cn\";\n\nexport const toggleGroupItemVariants = cva(\n \"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 cursor-pointer\",\n {\n variants: {\n variant: {\n default: \"bg-transparent\",\n outline:\n \"border border-border bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground data-[state=on]:bg-accent data-[state=on]:text-accent-foreground\",\n },\n size: {\n default: \"h-9 px-3 min-w-9\",\n sm: \"h-8 px-2 text-xs min-w-8\",\n lg: \"h-10 px-3 min-w-10\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n }\n);\n\ninterface ToggleGroupContextValue {\n type: \"single\" | \"multiple\";\n value: Accessor<any>;\n onItemSelect: (itemValue: string) => void;\n variant: Accessor<\"default\" | \"outline\">;\n size: Accessor<\"default\" | \"sm\" | \"lg\">;\n disabled: Accessor<boolean>;\n}\n\nconst ToggleGroupContext = createContext<ToggleGroupContextValue>();\n\nexport interface ToggleGroupProps<T extends \"single\" | \"multiple\" = \"single\">\n extends Omit<JSX.HTMLAttributes<HTMLDivElement>, \"onChange\">,\n VariantProps<typeof toggleGroupItemVariants> {\n /** Mode of selection: single choice or multiple choices */\n type?: T;\n /** Controlled value: string for 'single', string[] for 'multiple' */\n value?: T extends \"multiple\" ? string[] : string;\n /** Uncontrolled default value */\n defaultValue?: T extends \"multiple\" ? string[] : string;\n /** Callback fired when selection changes */\n onChange?: (value: T extends \"multiple\" ? string[] : string) => void;\n /** Layout orientation */\n orientation?: \"horizontal\" | \"vertical\";\n /** Disables all buttons in the group */\n disabled?: boolean;\n class?: string;\n children?: JSX.Element;\n}\n\n/**\n * Root container for grouping connected toggle items with shared single/multiple selection state.\n */\nexport const ToggleGroup = <T extends \"single\" | \"multiple\" = \"single\">(\n props: ToggleGroupProps<T>\n) => {\n const [local, rest] = splitProps(props, [\n \"type\",\n \"value\",\n \"defaultValue\",\n \"onChange\",\n \"orientation\",\n \"variant\",\n \"size\",\n \"disabled\",\n \"class\",\n \"children\",\n ]);\n\n const type = () => local.type ?? (\"single\" as T);\n const variant = () => local.variant ?? \"default\";\n const size = () => local.size ?? \"default\";\n const disabled = () => local.disabled ?? false;\n const orientation = () => local.orientation ?? \"horizontal\";\n\n const [currentValue, setCurrentValue] = createControllableSignal<any>({\n value: () => local.value,\n defaultValue: local.defaultValue ?? (type() === \"multiple\" ? [] : undefined),\n onChange: (val) => local.onChange?.(val),\n });\n\n const onItemSelect = (itemValue: string) => {\n if (disabled()) return;\n\n if (type() === \"multiple\") {\n const currentList = Array.isArray(currentValue()) ? currentValue() : [];\n if (currentList.includes(itemValue)) {\n setCurrentValue(currentList.filter((v: string) => v !== itemValue));\n } else {\n setCurrentValue([...currentList, itemValue]);\n }\n } else {\n const current = currentValue();\n if (current === itemValue) {\n setCurrentValue(undefined);\n } else {\n setCurrentValue(itemValue);\n }\n }\n };\n\n const contextValue: ToggleGroupContextValue = {\n type: type(),\n value: currentValue,\n onItemSelect,\n variant,\n size,\n disabled,\n };\n\n return (\n <ToggleGroupContext.Provider value={contextValue}>\n <div\n role=\"group\"\n data-orientation={orientation()}\n class={cn(\n \"flex items-center justify-center gap-1 rounded-lg\",\n orientation() === \"vertical\" ? \"flex-col\" : \"flex-row\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n </div>\n </ToggleGroupContext.Provider>\n );\n};\n\nexport interface ToggleGroupItemProps\n extends JSX.ButtonHTMLAttributes<HTMLButtonElement>,\n VariantProps<typeof toggleGroupItemVariants> {\n /** Unique value representing this toggle item within the group */\n value: string;\n class?: string;\n children?: JSX.Element;\n}\n\n/**\n * Individual toggle button item within a ToggleGroup.\n */\nexport const ToggleGroupItem: Component<ToggleGroupItemProps> = (props) => {\n const context = useContext(ToggleGroupContext);\n\n if (!context) {\n throw new Error(\"ToggleGroupItem must be used within a ToggleGroup\");\n }\n\n const [local, rest] = splitProps(props, [\n \"value\",\n \"variant\",\n \"size\",\n \"disabled\",\n \"class\",\n \"children\",\n ]);\n\n const isSelected = () => {\n const groupValue = context.value();\n if (context.type === \"multiple\") {\n return Array.isArray(groupValue) && groupValue.includes(local.value);\n }\n return groupValue === local.value;\n };\n\n const isDisabled = () => local.disabled || context.disabled();\n const itemVariant = () => local.variant || context.variant();\n const itemSize = () => local.size || context.size();\n\n return (\n <button\n type=\"button\"\n role={context.type === \"single\" ? \"radio\" : \"checkbox\"}\n aria-checked={isSelected()}\n data-state={isSelected() ? \"on\" : \"off\"}\n disabled={isDisabled()}\n onClick={() => context.onItemSelect(local.value)}\n class={cn(\n toggleGroupItemVariants({\n variant: itemVariant(),\n size: itemSize(),\n }),\n local.class\n )}\n {...rest}\n >\n {local.children}\n </button>\n );\n};\n",
18
18
  "type": "registry:ui"
19
19
  }
20
20
  ]
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "updater-modal",
3
+ "title": "Updater Modal",
4
+ "description": "An automated application auto-updater dialog for Tauri v2 with release notes preview, download progress bar, and relaunch actions.",
5
+ "type": "registry:ui",
6
+ "dependencies": [
7
+ "clsx",
8
+ "tailwind-merge",
9
+ "lucide-solid"
10
+ ],
11
+ "registryDependencies": [
12
+ "dialog",
13
+ "button",
14
+ "badge",
15
+ "progress",
16
+ "alert",
17
+ "create-app-updater"
18
+ ],
19
+ "files": [
20
+ {
21
+ "path": "ui/updater-modal.tsx",
22
+ "content": "import {\n Show,\n splitProps,\n type Component,\n type ParentComponent,\n type JSX,\n} from \"solid-js\";\nimport {\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n DialogDescription,\n DialogFooter,\n} from \"@/components/ui/dialog\";\nimport { Button } from \"@/components/ui/button\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Progress } from \"@/components/ui/progress\";\nimport { Alert, AlertTitle, AlertDescription } from \"@/components/ui/alert\";\nimport type {\n CreateAppUpdaterReturn,\n UpdateManifestInfo,\n UpdateProgressInfo,\n} from \"@/hooks/create-app-updater\";\nimport {\n Download,\n RotateCcw,\n CheckCircle2,\n AlertTriangle,\n ArrowRight,\n Package,\n} from \"lucide-solid\";\nimport { cn } from \"@/lib/cn\";\n\nfunction formatBytes(bytes: number): string {\n if (!bytes || bytes === 0) return \"0 MB\";\n const mb = bytes / (1024 * 1024);\n return `${mb.toFixed(1)} MB`;\n}\n\n/* --- 1. Sub-components for Clean Modularity & Zero Repetition --- */\n\nexport interface UpdaterHeaderProps {\n appName: string;\n status: string;\n info?: UpdateManifestInfo | null;\n}\n\nexport const UpdaterHeader: Component<UpdaterHeaderProps> = (props) => (\n <DialogHeader class=\"space-y-1.5 text-left\">\n <div class=\"flex items-center justify-between\">\n <div class=\"size-8 rounded-lg bg-primary/10 text-primary flex items-center justify-center\">\n <Package class=\"size-4\" />\n </div>\n <Show when={props.info?.version}>\n <div class=\"flex items-center gap-1.5 text-xs font-mono\">\n <span class=\"text-muted-foreground\">{props.info?.currentVersion || \"v1.0.0\"}</span>\n <ArrowRight class=\"size-3 text-muted-foreground\" />\n <Badge variant=\"default\" class=\"text-xs px-1.5 py-0 bg-primary text-primary-foreground font-semibold\">\n {props.info?.version}\n </Badge>\n </div>\n </Show>\n </div>\n\n <DialogTitle class=\"text-base font-bold tracking-tight text-foreground\">\n {props.status === \"downloaded\"\n ? \"Update Ready to Install\"\n : props.status === \"downloading\"\n ? \"Downloading Update...\"\n : props.status === \"up-to-date\"\n ? \"Up to Date\"\n : `New Update Available for ${props.appName}`}\n </DialogTitle>\n\n <DialogDescription class=\"text-xs text-muted-foreground leading-relaxed\">\n {props.status === \"downloaded\"\n ? \"The update has been downloaded and verified. Restart to apply changes.\"\n : props.status === \"downloading\"\n ? \"Please wait while update packages are being downloaded.\"\n : props.status === \"up-to-date\"\n ? `${props.appName} is running on the latest version.`\n : \"A new version with features and bug fixes is ready to install.\"}\n </DialogDescription>\n </DialogHeader>\n);\n\nexport interface UpdaterReleaseNotesProps {\n body?: string;\n date?: string;\n}\n\nexport const UpdaterReleaseNotes: Component<UpdaterReleaseNotesProps> = (props) => (\n <div class=\"space-y-1\">\n <div class=\"flex items-center justify-between text-xs text-muted-foreground font-medium\">\n <span>Release Notes</span>\n <Show when={props.date}>\n <span class=\"text-[11px] font-mono\">{props.date}</span>\n </Show>\n </div>\n <pre class=\"max-h-28 overflow-y-auto rounded-md border border-border bg-black/5 dark:bg-black/50 p-2.5 text-xs text-foreground font-mono whitespace-pre-wrap leading-relaxed select-text shadow-inner\">\n {props.body}\n </pre>\n </div>\n);\n\nexport interface UpdaterProgressBarProps {\n progress: UpdateProgressInfo;\n}\n\nexport const UpdaterProgressBar: Component<UpdaterProgressBarProps> = (props) => (\n <div class=\"space-y-1.5 py-1\">\n <div class=\"flex items-center justify-between text-xs font-mono\">\n <span class=\"text-muted-foreground font-sans\">Progress</span>\n <span class=\"font-semibold text-primary\">{props.progress.percentage}%</span>\n </div>\n <Progress value={props.progress.percentage} class=\"h-1.5\" />\n <div class=\"flex items-center justify-between text-[11px] font-mono text-muted-foreground\">\n <span>{formatBytes(props.progress.downloaded)}</span>\n <span>{formatBytes(props.progress.total)}</span>\n </div>\n </div>\n);\n\n/* --- 2. Main UpdaterModal Compound Component --- */\n\nexport interface UpdaterModalProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n updater: CreateAppUpdaterReturn;\n appName?: string;\n class?: string;\n}\n\nexport const UpdaterModal: Component<UpdaterModalProps> = (props) => {\n const [local, rest] = splitProps(props, [\n \"open\",\n \"onOpenChange\",\n \"updater\",\n \"appName\",\n \"class\",\n ]);\n\n const appName = () => local.appName || \"Nikala Desktop\";\n const status = () => local.updater.status();\n const info = () => local.updater.updateInfo();\n const progress = () => local.updater.progress();\n const error = () => local.updater.error();\n\n return (\n <Dialog open={local.open} onOpenChange={local.onOpenChange}>\n <DialogContent class={cn(\"sm:max-w-sm\", local.class)}>\n {/* Header */}\n <UpdaterHeader\n appName={appName()}\n status={status()}\n info={info()}\n />\n\n {/* Error Alert */}\n <Show when={error()}>\n <Alert variant=\"destructive\" class=\"py-2 text-xs\">\n <AlertTriangle class=\"size-3.5\" />\n <AlertTitle class=\"font-semibold text-xs\">Update Failed</AlertTitle>\n <AlertDescription class=\"text-[11px]\">{error()}</AlertDescription>\n </Alert>\n </Show>\n\n {/* Release Notes */}\n <Show when={info()?.body && (status() === \"available\" || status() === \"downloading\")}>\n <UpdaterReleaseNotes body={info()?.body} date={info()?.date} />\n </Show>\n\n {/* Progress Bar */}\n <Show when={status() === \"downloading\"}>\n <UpdaterProgressBar progress={progress()} />\n </Show>\n\n {/* Success Callout */}\n <Show when={status() === \"downloaded\"}>\n <div class=\"flex items-center gap-2.5 p-2.5 rounded-md border border-primary/30 bg-primary/5 text-xs text-foreground\">\n <CheckCircle2 class=\"size-4 text-primary shrink-0\" />\n <p class=\"font-medium text-foreground text-xs\">Package downloaded & verified.</p>\n </div>\n </Show>\n\n {/* Actions Footer */}\n <DialogFooter class=\"flex-row items-center justify-end gap-1.5 pt-1\">\n <Show when={status() === \"available\" || status() === \"error\"}>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => local.onOpenChange(false)}\n class=\"text-xs h-7.5 px-3 cursor-pointer\"\n >\n Later\n </Button>\n <Button\n variant=\"default\"\n size=\"sm\"\n onClick={() => local.updater.downloadAndInstall()}\n class=\"text-xs h-7.5 px-3 gap-1.5 cursor-pointer font-medium\"\n >\n <Download class=\"size-3.5\" />\n <span>Update Now</span>\n </Button>\n </Show>\n\n <Show when={status() === \"downloading\"}>\n <Button\n variant=\"outline\"\n size=\"sm\"\n disabled\n class=\"text-xs h-7.5 w-full opacity-70 justify-center\"\n >\n Downloading ({progress().percentage}%)\n </Button>\n </Show>\n\n <Show when={status() === \"downloaded\"}>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => local.onOpenChange(false)}\n class=\"text-xs h-7.5 px-3 cursor-pointer\"\n >\n Later\n </Button>\n <Button\n variant=\"default\"\n size=\"sm\"\n onClick={() => local.updater.relaunch()}\n class=\"text-xs h-7.5 px-3 gap-1.5 cursor-pointer font-semibold shadow-xs\"\n >\n <RotateCcw class=\"size-3.5\" />\n <span>Relaunch</span>\n </Button>\n </Show>\n\n <Show when={status() === \"up-to-date\" || status() === \"idle\"}>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => local.onOpenChange(false)}\n class=\"text-xs h-7.5 px-3 cursor-pointer\"\n >\n Close\n </Button>\n </Show>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n};\n",
23
+ "type": "registry:ui"
24
+ }
25
+ ]
26
+ }
package/src/index.ts ADDED
@@ -0,0 +1,48 @@
1
+ // packages/core/src/index.ts
2
+ export * from "./registry/index.js";
3
+ export * from "./lib/cn.js";
4
+
5
+ // Providers & Theme
6
+ export * from "./registry/providers/theme-provider.jsx";
7
+ export * from "./registry/providers/theme-transitions.js";
8
+ export * from "./registry/providers/theme-script.jsx";
9
+
10
+ // UI Primitives & Components
11
+ export * from "./registry/components/ui/accordion.jsx";
12
+ export * from "./registry/components/ui/alert.jsx";
13
+ export * from "./registry/components/ui/api-table.jsx";
14
+ export * from "./registry/components/ui/aspect-ratio.jsx";
15
+ export * from "./registry/components/ui/avatar.jsx";
16
+ export * from "./registry/components/ui/badge.jsx";
17
+ export * from "./registry/components/ui/banner.jsx";
18
+ export * from "./registry/components/ui/breadcrumb.jsx";
19
+ export * from "./registry/components/ui/bubble.jsx";
20
+ export * from "./registry/components/ui/button.jsx";
21
+ export * from "./registry/components/ui/callout.jsx";
22
+ export * from "./registry/components/ui/card.jsx";
23
+ export * from "./registry/components/ui/code-block.jsx";
24
+ export * from "./registry/components/ui/code-group.jsx";
25
+ export * from "./registry/components/ui/component-viewer.jsx";
26
+ export * from "./registry/components/ui/collapsible.jsx";
27
+ export * from "./registry/components/ui/command.jsx";
28
+ export * from "./registry/components/ui/container.jsx";
29
+ export * from "./registry/components/ui/dialog.jsx";
30
+ export * from "./registry/components/ui/file-tree.jsx";
31
+ export * from "./registry/components/ui/kbd.jsx";
32
+ export * from "./registry/components/ui/list.jsx";
33
+ export * from "./registry/components/ui/navbar.jsx";
34
+ export * from "./registry/components/ui/pager.jsx";
35
+ export * from "./registry/components/ui/pagination.jsx";
36
+ export * from "./registry/components/ui/package-manager-tabs.jsx";
37
+ export * from "./registry/components/ui/scroll-area.jsx";
38
+ export * from "./registry/components/ui/section-heading.jsx";
39
+ export * from "./registry/components/ui/separator.jsx";
40
+ export * from "./registry/components/ui/sheet.jsx";
41
+ export * from "./registry/components/ui/sidebar.jsx";
42
+ export * from "./registry/components/ui/spinner.jsx";
43
+ export * from "./registry/components/ui/steps.jsx";
44
+ export * from "./registry/components/ui/table-of-contents.jsx";
45
+ export * from "./registry/components/ui/table.jsx";
46
+ export * from "./registry/components/ui/tabs.jsx";
47
+ export * from "./registry/components/ui/theme-toggle.jsx";
48
+ export * from "./registry/components/ui/tooltip.jsx";
@@ -0,0 +1,117 @@
1
+ import {
2
+ splitProps,
3
+ For,
4
+ Show,
5
+ type JSX,
6
+ type Component,
7
+ } from "solid-js";
8
+ import { SectionHeading } from "@/components/ui/section-heading";
9
+ import {
10
+ Table,
11
+ TableHeader,
12
+ TableBody,
13
+ TableRow,
14
+ TableHead,
15
+ TableCell,
16
+ } from "@/components/ui/table";
17
+ import { cn } from "@/lib/cn";
18
+
19
+ export interface ApiTableItem {
20
+ /** Property, attribute, or method name */
21
+ prop: string;
22
+ /** TypeScript type definition string */
23
+ type: string;
24
+ /** Default value if optional */
25
+ default?: string;
26
+ /** Detailed description of purpose and usage */
27
+ description: string;
28
+ /** Whether the property is strictly required */
29
+ required?: boolean;
30
+ }
31
+
32
+ export interface ApiTableProps extends JSX.HTMLAttributes<HTMLDivElement> {
33
+ /** Title header for this API section (e.g. component or interface name) */
34
+ title?: string;
35
+ /** Subtitle or explanatory note */
36
+ description?: string;
37
+ /** Badge label next to the title (defaults to "Props") */
38
+ badge?: string;
39
+ /** List of API properties to render in the table */
40
+ items: ApiTableItem[];
41
+ class?: string;
42
+ }
43
+
44
+ /**
45
+ * Clean, structured API reference table component composed from Table and Badge primitives.
46
+ */
47
+ export const ApiTable: Component<ApiTableProps> = (props) => {
48
+ const [local, rest] = splitProps(props, [
49
+ "title",
50
+ "description",
51
+ "badge",
52
+ "items",
53
+ "class",
54
+ ]);
55
+
56
+ return (
57
+ <div class={cn("my-6 space-y-3", local.class)} {...rest}>
58
+ {/* Header */}
59
+ <Show when={local.title}>
60
+ <SectionHeading
61
+ variant="compact"
62
+ title={local.title!}
63
+ badge={local.badge || "Props"}
64
+ description={local.description}
65
+ />
66
+ </Show>
67
+
68
+ {/* Responsive Table Container Composed from Nikala Table Primitives */}
69
+ <div class="rounded-lg border border-border bg-card/50 shadow-2xs overflow-hidden">
70
+ <Table class="text-xs">
71
+ <TableHeader class="bg-muted/40 font-mono text-[11px] uppercase tracking-wider text-muted-foreground select-none">
72
+ <TableRow class="hover:bg-transparent">
73
+ <TableHead class="h-9 px-4 font-semibold">Prop</TableHead>
74
+ <TableHead class="h-9 px-4 font-semibold">Type</TableHead>
75
+ <TableHead class="h-9 px-4 font-semibold">Default</TableHead>
76
+ <TableHead class="h-9 px-4 font-semibold">Description</TableHead>
77
+ </TableRow>
78
+ </TableHeader>
79
+ <TableBody class="font-mono text-xs">
80
+ <For each={local.items}>
81
+ {(item) => (
82
+ <TableRow class="transition-colors hover:bg-muted/20">
83
+ <TableCell class="px-4 py-3 font-semibold text-primary">
84
+ <span class="inline-flex items-center gap-1">
85
+ {item.prop}
86
+ <Show when={item.required}>
87
+ <span class="text-rose-500 font-bold" title="Required">*</span>
88
+ </Show>
89
+ </span>
90
+ </TableCell>
91
+ <TableCell class="px-4 py-3 text-muted-foreground">
92
+ <code class="rounded-md bg-muted px-1.5 py-0.5 font-mono text-[11px] text-foreground">
93
+ {item.type}
94
+ </code>
95
+ </TableCell>
96
+ <TableCell class="px-4 py-3 text-muted-foreground">
97
+ <Show
98
+ when={item.default}
99
+ fallback={<span class="text-muted-foreground/40 font-mono">-</span>}
100
+ >
101
+ <code class="rounded-md bg-muted/60 px-1 py-0.5 font-mono text-[11px]">
102
+ {item.default}
103
+ </code>
104
+ </Show>
105
+ </TableCell>
106
+ <TableCell class="px-4 py-3 font-sans font-normal leading-relaxed text-muted-foreground">
107
+ {item.description}
108
+ </TableCell>
109
+ </TableRow>
110
+ )}
111
+ </For>
112
+ </TableBody>
113
+ </Table>
114
+ </div>
115
+ </div>
116
+ );
117
+ };
@@ -28,8 +28,6 @@ export const bannerVariants = cva(
28
28
  "bg-emerald-500/15 text-emerald-900 border-b border-emerald-500/20 dark:text-emerald-200",
29
29
  destructive:
30
30
  "bg-rose-500/15 text-rose-900 border-b border-rose-500/20 dark:text-rose-200",
31
- pirosmani:
32
- "bg-[#722f37] text-white border-b border-[#8a3943] shadow-sm",
33
31
  },
34
32
  sticky: {
35
33
  true: "sticky top-0 z-50 backdrop-blur-sm",
@@ -1,7 +1,7 @@
1
1
  import { Show, splitProps, type Component, type JSX } from "solid-js";
2
2
  import { cva, type VariantProps } from "class-variance-authority";
3
3
  import { cn } from "@/lib/cn";
4
- import { Spinner } from "./spinner";
4
+ import { Spinner } from "@/components/ui/spinner";
5
5
 
6
6
  /**
7
7
  * Class variance authority configuration for button styling variants and sizes.
@@ -0,0 +1,137 @@
1
+ import {
2
+ splitProps,
3
+ Show,
4
+ type Component,
5
+ type JSX,
6
+ type ParentComponent,
7
+ } from "solid-js";
8
+ import { Dynamic } from "solid-js/web";
9
+ import { cva, type VariantProps } from "class-variance-authority";
10
+ import {
11
+ Info,
12
+ Lightbulb,
13
+ AlertTriangle,
14
+ AlertCircle,
15
+ CheckCircle2,
16
+ Bookmark,
17
+ } from "lucide-solid";
18
+ import { cn } from "@/lib/cn";
19
+
20
+ export const calloutVariants = cva(
21
+ "relative w-full rounded-lg border p-4 text-sm leading-relaxed transition-colors",
22
+ {
23
+ variants: {
24
+ variant: {
25
+ note: "border-border/80 bg-muted/50 text-foreground [&>svg]:text-foreground",
26
+ 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",
27
+ 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",
28
+ 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",
29
+ danger: "border-destructive/30 bg-destructive/10 text-destructive dark:text-red-200 [&>svg]:text-destructive",
30
+ 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",
31
+ },
32
+ },
33
+ defaultVariants: {
34
+ variant: "note",
35
+ },
36
+ }
37
+ );
38
+
39
+ const defaultIcons = {
40
+ note: Bookmark,
41
+ info: Info,
42
+ tip: Lightbulb,
43
+ warning: AlertTriangle,
44
+ danger: AlertCircle,
45
+ success: CheckCircle2,
46
+ };
47
+
48
+ export interface CalloutProps
49
+ extends JSX.HTMLAttributes<HTMLDivElement>,
50
+ VariantProps<typeof calloutVariants> {
51
+ title?: string;
52
+ type?: VariantProps<typeof calloutVariants>["variant"];
53
+ icon?: Component<{ class?: string }> | false;
54
+ class?: string;
55
+ }
56
+
57
+ export const Callout: ParentComponent<CalloutProps> = (props) => {
58
+ const [local, rest] = splitProps(props, [
59
+ "variant",
60
+ "type",
61
+ "title",
62
+ "icon",
63
+ "class",
64
+ "children",
65
+ ]);
66
+
67
+ const variant = () => local.variant || local.type || "note";
68
+
69
+ const IconComponent = () => {
70
+ if (local.icon === false) return null;
71
+ if (local.icon) return local.icon;
72
+ return defaultIcons[variant()] || Info;
73
+ };
74
+
75
+ return (
76
+ <div
77
+ role="region"
78
+ aria-label={local.title || `${variant()} callout`}
79
+ class={cn(
80
+ calloutVariants({ variant: variant() }),
81
+ "flex gap-3.5 items-start",
82
+ local.class
83
+ )}
84
+ {...rest}
85
+ >
86
+ <Show when={IconComponent()}>
87
+ {(Icon) => (
88
+ <span class="inline-flex shrink-0 items-center justify-center mt-0.5 select-none">
89
+ <Dynamic component={Icon()} class="size-4.5 shrink-0" />
90
+ </span>
91
+ )}
92
+ </Show>
93
+ <div class="flex-1 space-y-1 min-w-0">
94
+ <Show when={local.title}>
95
+ <h5 class="font-semibold text-sm leading-none tracking-tight">
96
+ {local.title}
97
+ </h5>
98
+ </Show>
99
+ <div class="text-sm opacity-90 leading-relaxed break-words">
100
+ {local.children}
101
+ </div>
102
+ </div>
103
+ </div>
104
+ );
105
+ };
106
+
107
+ export interface CalloutTitleProps extends JSX.HTMLAttributes<HTMLHeadingElement> {
108
+ class?: string;
109
+ }
110
+
111
+ export const CalloutTitle: ParentComponent<CalloutTitleProps> = (props) => {
112
+ const [local, rest] = splitProps(props, ["class", "children"]);
113
+ return (
114
+ <h5
115
+ class={cn("font-semibold text-sm leading-none tracking-tight mb-1", local.class)}
116
+ {...rest}
117
+ >
118
+ {local.children}
119
+ </h5>
120
+ );
121
+ };
122
+
123
+ export interface CalloutDescriptionProps extends JSX.HTMLAttributes<HTMLParagraphElement> {
124
+ class?: string;
125
+ }
126
+
127
+ export const CalloutDescription: ParentComponent<CalloutDescriptionProps> = (props) => {
128
+ const [local, rest] = splitProps(props, ["class", "children"]);
129
+ return (
130
+ <div
131
+ class={cn("text-sm opacity-90 leading-relaxed", local.class)}
132
+ {...rest}
133
+ >
134
+ {local.children}
135
+ </div>
136
+ );
137
+ };
@@ -1,5 +1,5 @@
1
1
  import { splitProps, Show, type Component, type JSX } from "solid-js";
2
- import { createControllableSignal } from "@nikala-ui/hooks";
2
+ import { createControllableSignal } from "@/hooks/create-controllable-signal";
3
3
  import { cn } from "@/lib/cn";
4
4
 
5
5
  export interface CheckboxProps