@nikala-ui/core 0.9.11 → 0.9.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "scroll-area",
3
+ "title": "Scroll Area",
4
+ "description": "Augments native scroll functionality with custom styled scrollbars and reactive scroll tracking.",
5
+ "type": "registry:ui",
6
+ "dependencies": [
7
+ "clsx",
8
+ "tailwind-merge"
9
+ ],
10
+ "registryDependencies": [
11
+ "create-scroll-position",
12
+ "create-resize-observer"
13
+ ],
14
+ "files": [
15
+ {
16
+ "path": "ui/scroll-area.tsx",
17
+ "content": "import {\n createSignal,\n createEffect,\n onCleanup,\n splitProps,\n type Component,\n type JSX,\n type Accessor,\n} from \"solid-js\";\nimport { createScrollPosition, createElementSize } from \"@nikala-ui/hooks\";\nimport { cn } from \"@/lib/cn\";\n\nexport interface ScrollAreaProps extends JSX.HTMLAttributes<HTMLDivElement> {\n orientation?: \"vertical\" | \"horizontal\" | \"both\";\n scrollHideDelay?: number;\n class?: string;\n children?: JSX.Element;\n}\n\nexport const ScrollArea: Component<ScrollAreaProps> = (props) => {\n const [local, rest] = splitProps(props, [\n \"orientation\",\n \"scrollHideDelay\",\n \"class\",\n \"children\",\n ]);\n\n const orientation = () => local.orientation || \"vertical\";\n let viewportRef: HTMLDivElement | undefined;\n let verticalTrackRef: HTMLDivElement | undefined;\n let horizontalTrackRef: HTMLDivElement | undefined;\n\n const scrollPos = createScrollPosition({\n target: () => viewportRef,\n });\n\n const viewportSize = createElementSize(() => viewportRef);\n\n const [thumbHeight, setThumbHeight] = createSignal(0);\n const [thumbTop, setThumbTop] = createSignal(0);\n const [thumbWidth, setThumbWidth] = createSignal(0);\n const [thumbLeft, setThumbLeft] = createSignal(0);\n const [isDragging, setIsDragging] = createSignal(false);\n\n const updateThumbMetrics = () => {\n if (!viewportRef) return;\n\n const scrollHeight = viewportRef.scrollHeight;\n const clientHeight = viewportRef.clientHeight;\n const scrollWidth = viewportRef.scrollWidth;\n const clientWidth = viewportRef.clientWidth;\n\n const trackHeight = verticalTrackRef ? verticalTrackRef.clientHeight - 4 : clientHeight - 4;\n const trackWidth = horizontalTrackRef ? horizontalTrackRef.clientWidth - 4 : clientWidth - 4;\n\n if (scrollHeight > clientHeight && clientHeight > 0) {\n const vRatio = clientHeight / scrollHeight;\n const calculatedHeight = Math.max(vRatio * trackHeight, 20);\n const maxTop = trackHeight - calculatedHeight;\n const topPct = scrollPos.y() / (scrollHeight - clientHeight);\n setThumbHeight(calculatedHeight);\n setThumbTop(topPct * maxTop);\n } else {\n setThumbHeight(0);\n }\n\n if (scrollWidth > clientWidth && clientWidth > 0) {\n const hRatio = clientWidth / scrollWidth;\n const calculatedWidth = Math.max(hRatio * trackWidth, 20);\n const maxLeft = trackWidth - calculatedWidth;\n const leftPct = scrollPos.x() / (scrollWidth - clientWidth);\n setThumbWidth(calculatedWidth);\n setThumbLeft(leftPct * maxLeft);\n } else {\n setThumbWidth(0);\n }\n };\n\n createEffect(() => {\n viewportSize.width();\n viewportSize.height();\n scrollPos.x();\n scrollPos.y();\n updateThumbMetrics();\n });\n\n const handleVerticalThumbPointerDown = (e: PointerEvent) => {\n if (!viewportRef || !verticalTrackRef) return;\n e.preventDefault();\n e.stopPropagation();\n\n setIsDragging(true);\n const startY = e.clientY;\n const startScrollTop = viewportRef.scrollTop;\n const scrollHeight = viewportRef.scrollHeight;\n const clientHeight = viewportRef.clientHeight;\n const trackHeight = verticalTrackRef.clientHeight - 4;\n\n const maxScrollTop = scrollHeight - clientHeight;\n const maxThumbTop = trackHeight - thumbHeight();\n const ratio = maxThumbTop > 0 ? maxScrollTop / maxThumbTop : 0;\n\n const onPointerMove = (moveEvent: PointerEvent) => {\n const deltaY = moveEvent.clientY - startY;\n viewportRef!.scrollTop = Math.max(0, Math.min(maxScrollTop, startScrollTop + deltaY * ratio));\n };\n\n const onPointerUp = () => {\n setIsDragging(false);\n window.removeEventListener(\"pointermove\", onPointerMove);\n window.removeEventListener(\"pointerup\", onPointerUp);\n };\n\n window.addEventListener(\"pointermove\", onPointerMove);\n window.addEventListener(\"pointerup\", onPointerUp);\n };\n\n const handleHorizontalThumbPointerDown = (e: PointerEvent) => {\n if (!viewportRef || !horizontalTrackRef) return;\n e.preventDefault();\n e.stopPropagation();\n\n setIsDragging(true);\n const startX = e.clientX;\n const startScrollLeft = viewportRef.scrollLeft;\n const scrollWidth = viewportRef.scrollWidth;\n const clientWidth = viewportRef.clientWidth;\n const trackWidth = horizontalTrackRef.clientWidth - 4;\n\n const maxScrollLeft = scrollWidth - clientWidth;\n const maxThumbLeft = trackWidth - thumbWidth();\n const ratio = maxThumbLeft > 0 ? maxScrollLeft / maxThumbLeft : 0;\n\n const onPointerMove = (moveEvent: PointerEvent) => {\n const deltaX = moveEvent.clientX - startX;\n viewportRef!.scrollLeft = Math.max(0, Math.min(maxScrollLeft, startScrollLeft + deltaX * ratio));\n };\n\n const onPointerUp = () => {\n setIsDragging(false);\n window.removeEventListener(\"pointermove\", onPointerMove);\n window.removeEventListener(\"pointerup\", onPointerUp);\n };\n\n window.addEventListener(\"pointermove\", onPointerMove);\n window.addEventListener(\"pointerup\", onPointerUp);\n };\n\n const handleWheel = (e: WheelEvent) => {\n if (orientation() === \"horizontal\" && viewportRef) {\n if (Math.abs(e.deltaY) > Math.abs(e.deltaX)) {\n e.preventDefault();\n viewportRef.scrollLeft += e.deltaY;\n }\n }\n };\n\n return (\n <div\n class={cn(\"relative overflow-hidden group/scroll-area\", local.class)}\n onWheel={handleWheel}\n {...rest}\n >\n <div\n ref={viewportRef}\n class=\"h-full w-full overflow-auto scrollbar-none rounded-[inherit]\"\n style={{\n \"scrollbar-width\": \"none\",\n \"-ms-overflow-style\": \"none\",\n }}\n >\n {local.children}\n </div>\n\n {(orientation() === \"vertical\" || orientation() === \"both\") && thumbHeight() > 0 && (\n <div\n ref={verticalTrackRef}\n class={cn(\n \"absolute right-0 top-0 bottom-0 w-2.5 p-0.5 select-none transition-opacity duration-300 pointer-events-none\",\n scrollPos.isScrolling() || isDragging()\n ? \"opacity-100\"\n : \"opacity-0 group-hover/scroll-area:opacity-100\"\n )}\n >\n <div\n class=\"w-1.5 rounded-full bg-border hover:bg-muted-foreground/50 transition-colors cursor-pointer pointer-events-auto\"\n style={{\n height: `${thumbHeight()}px`,\n transform: `translateY(${thumbTop()}px)`,\n }}\n onPointerDown={handleVerticalThumbPointerDown}\n />\n </div>\n )}\n\n {(orientation() === \"horizontal\" || orientation() === \"both\") && thumbWidth() > 0 && (\n <div\n ref={horizontalTrackRef}\n class={cn(\n \"absolute bottom-0 left-0 right-0 h-2.5 p-0.5 select-none transition-opacity duration-300 pointer-events-none\",\n scrollPos.isScrolling() || isDragging()\n ? \"opacity-100\"\n : \"opacity-0 group-hover/scroll-area:opacity-100\"\n )}\n >\n <div\n class=\"h-1.5 rounded-full bg-border hover:bg-muted-foreground/50 transition-colors cursor-pointer pointer-events-auto\"\n style={{\n width: `${thumbWidth()}px`,\n transform: `translateX(${thumbLeft()}px)`,\n }}\n onPointerDown={handleHorizontalThumbPointerDown}\n />\n </div>\n )}\n </div>\n );\n};\n",
18
+ "type": "registry:ui"
19
+ }
20
+ ]
21
+ }
@@ -8,10 +8,13 @@
8
8
  "tailwind-merge",
9
9
  "@kobalte/core"
10
10
  ],
11
+ "registryDependencies": [
12
+ "scroll-area"
13
+ ],
11
14
  "files": [
12
15
  {
13
16
  "path": "ui/select.tsx",
14
- "content": "import { splitProps, type JSX, type ValidComponent } from \"solid-js\";\nimport * as SelectPrimitive from \"@kobalte/core/select\";\nimport type { PolymorphicProps } from \"@kobalte/core/polymorphic\";\nimport { createClickOutside } from \"@nikala-ui/hooks\";\nimport { cn } from \"@/lib/cn\";\n\nexport type SelectRootProps<Option = any, OptGroup = any, T extends ValidComponent = \"div\"> =\n SelectPrimitive.SelectRootProps<Option, OptGroup, T> & {\n class?: string;\n };\n\n/**\n * Root Select component built on top of Kobalte headless primitives.\n */\nexport const Select = <Option = any, OptGroup = any, T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, SelectRootProps<Option, OptGroup, T>>\n) => {\n const [local, rest] = splitProps(props as SelectRootProps, [\"class\"]);\n\n return <SelectPrimitive.Root class={cn(\"relative w-full\", local.class)} {...(rest as any)} />;\n};\n\nexport type SelectTriggerProps<T extends ValidComponent = \"button\"> =\n SelectPrimitive.SelectTriggerProps<T> & {\n class?: string;\n children?: JSX.Element;\n };\n\n/**\n * Trigger button opening the Select options list.\n */\nexport const SelectTrigger = <T extends ValidComponent = \"button\">(\n props: PolymorphicProps<T, SelectTriggerProps<T>>\n) => {\n const [local, rest] = splitProps(props as SelectTriggerProps, [\"class\", \"children\"]);\n\n return (\n <SelectPrimitive.Trigger\n class={cn(\n \"flex h-9 w-full items-center justify-between rounded-md border border-input bg-muted px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-primary disabled:cursor-not-allowed disabled:opacity-50 cursor-pointer text-foreground\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n <SelectPrimitive.Icon\n as=\"svg\"\n class=\"h-4 w-4 opacity-50 transition-transform\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M19 9l-7 7-7-7\" />\n </SelectPrimitive.Icon>\n </SelectPrimitive.Trigger>\n );\n};\n\nexport type SelectValueProps<Option, T extends ValidComponent = \"span\"> =\n SelectPrimitive.SelectValueProps<Option, T> & {\n class?: string;\n };\n\n/**\n * Renders the currently selected option text or placeholder.\n */\nexport const SelectValue = <Option = any, T extends ValidComponent = \"span\">(\n props: PolymorphicProps<T, SelectValueProps<Option, T>>\n) => {\n const [local, rest] = splitProps(props as SelectValueProps<Option>, [\"class\"]);\n\n return (\n <SelectPrimitive.Value\n class={cn(\"block truncate\", local.class)}\n {...(rest as any)}\n />\n );\n};\n\nexport type SelectContentProps<T extends ValidComponent = \"div\"> =\n SelectPrimitive.SelectContentProps<T> & {\n class?: string;\n };\n\n/**\n * Portaled overlay container rendering the listbox options.\n */\nexport const SelectContent = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, SelectContentProps<T>>\n) => {\n const [local, rest] = splitProps(props as SelectContentProps, [\"class\"]);\n let contentRef: HTMLElement | undefined;\n\n createClickOutside({\n target: () => contentRef,\n onInteractOutside: (e) => {\n if (typeof (props as any).onInteractOutside === \"function\") {\n (props as any).onInteractOutside(e);\n }\n },\n });\n\n return (\n <SelectPrimitive.Portal>\n <SelectPrimitive.Content\n ref={(el) => {\n contentRef = el;\n if (typeof (props as any).ref === \"function\") (props as any).ref(el);\n }}\n class={cn(\n \"relative z-50 min-w-8rem overflow-hidden rounded-md border border-border bg-popover text-popover-foreground shadow-md animate-in fade-in-80\",\n local.class\n )}\n {...rest}\n >\n <SelectPrimitive.Listbox class=\"p-1 outline-none\" />\n </SelectPrimitive.Content>\n </SelectPrimitive.Portal>\n );\n};\n\nexport type SelectItemProps<T extends ValidComponent = \"li\"> =\n SelectPrimitive.SelectItemProps<T> & {\n class?: string;\n children?: JSX.Element;\n };\n\n/**\n * Individual option item choice inside SelectContent.\n */\nexport const SelectItem = <T extends ValidComponent = \"li\">(\n props: PolymorphicProps<T, SelectItemProps<T>>\n) => {\n const [local, rest] = splitProps(props as SelectItemProps, [\"class\", \"children\"]);\n\n return (\n <SelectPrimitive.Item\n class={cn(\n \"relative flex w-full cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground\",\n local.class\n )}\n {...rest}\n >\n <SelectPrimitive.ItemIndicator class=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n <svg class=\"h-4 w-4 fill-none stroke-current stroke-2\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M5 13l4 4L19 7\" />\n </svg>\n </SelectPrimitive.ItemIndicator>\n <SelectPrimitive.ItemLabel>{local.children}</SelectPrimitive.ItemLabel>\n </SelectPrimitive.Item>\n );\n};",
17
+ "content": "import { splitProps, type Component, type JSX, type ValidComponent } from \"solid-js\";\nimport * as SelectPrimitive from \"@kobalte/core/select\";\nimport type { PolymorphicProps } from \"@kobalte/core/polymorphic\";\nimport { createClickOutside } from \"@nikala-ui/hooks\";\nimport { ScrollArea } from \"./scroll-area\";\nimport { cn } from \"@/lib/cn\";\n\nexport type SelectRootProps<Option = any, OptGroup = any, T extends ValidComponent = \"div\"> =\n SelectPrimitive.SelectRootProps<Option, OptGroup, T>;\n\n/**\n * Root Select component wrapper built on Kobalte primitives.\n */\nexport const Select = <Option = any, OptGroup = any, T extends ValidComponent = \"div\">(\n props: SelectRootProps<Option, OptGroup, T>\n) => {\n return <SelectPrimitive.Root {...props} />;\n};\n\nexport type SelectTriggerProps<T extends ValidComponent = \"button\"> =\n SelectPrimitive.SelectTriggerProps<T> & {\n class?: string;\n children?: JSX.Element;\n };\n\n/**\n * Trigger button opening the Select options list.\n */\nexport const SelectTrigger = <T extends ValidComponent = \"button\">(\n props: PolymorphicProps<T, SelectTriggerProps<T>>\n) => {\n const [local, rest] = splitProps(props as SelectTriggerProps, [\"class\", \"children\"]);\n\n return (\n <SelectPrimitive.Trigger\n class={cn(\n \"flex h-9 w-full items-center justify-between rounded-md border border-input bg-muted px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-primary disabled:cursor-not-allowed disabled:opacity-50 cursor-pointer text-foreground\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n <SelectPrimitive.Icon\n as=\"svg\"\n class=\"h-4 w-4 opacity-50 transition-transform\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M19 9l-7 7-7-7\" />\n </SelectPrimitive.Icon>\n </SelectPrimitive.Trigger>\n );\n};\n\nexport type SelectValueProps<Option, T extends ValidComponent = \"span\"> =\n SelectPrimitive.SelectValueProps<Option, T> & {\n class?: string;\n };\n\n/**\n * Renders the currently selected option text or placeholder.\n */\nexport const SelectValue = <Option = any, T extends ValidComponent = \"span\">(\n props: PolymorphicProps<T, SelectValueProps<Option, T>>\n) => {\n const [local, rest] = splitProps(props as SelectValueProps<Option>, [\"class\"]);\n\n return (\n <SelectPrimitive.Value\n class={cn(\"block truncate\", local.class)}\n {...(rest as any)}\n />\n );\n};\n\nexport type SelectContentProps<T extends ValidComponent = \"div\"> =\n SelectPrimitive.SelectContentProps<T> & {\n class?: string;\n };\n\n/**\n * Portaled overlay container rendering the listbox options.\n */\nexport const SelectContent = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, SelectContentProps<T>>\n) => {\n const [local, rest] = splitProps(props as SelectContentProps, [\"class\"]);\n let contentRef: HTMLElement | undefined;\n\n createClickOutside({\n target: () => contentRef,\n onInteractOutside: (e) => {\n if (typeof (props as any).onInteractOutside === \"function\") {\n (props as any).onInteractOutside(e);\n }\n },\n });\n\n return (\n <SelectPrimitive.Portal>\n <SelectPrimitive.Content\n ref={(el) => {\n contentRef = el;\n if (typeof (props as any).ref === \"function\") (props as any).ref(el);\n }}\n class={cn(\n \"relative z-50 min-w-8rem overflow-hidden rounded-md border border-border bg-popover text-popover-foreground shadow-md animate-in fade-in-80 max-h-60\",\n local.class\n )}\n {...rest}\n >\n <ScrollArea class=\"max-h-60 w-full\">\n <SelectPrimitive.Listbox class=\"p-1 outline-none\" />\n </ScrollArea>\n </SelectPrimitive.Content>\n </SelectPrimitive.Portal>\n );\n};\n\nexport type SelectItemProps<T extends ValidComponent = \"li\"> =\n SelectPrimitive.SelectItemProps<T> & {\n class?: string;\n children?: JSX.Element;\n };\n\n/**\n * Individual option item choice inside SelectContent.\n */\nexport const SelectItem = <T extends ValidComponent = \"li\">(\n props: PolymorphicProps<T, SelectItemProps<T>>\n) => {\n const [local, rest] = splitProps(props as SelectItemProps, [\"class\", \"children\"]);\n\n return (\n <SelectPrimitive.Item\n class={cn(\n \"relative flex w-full cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50 text-foreground\",\n local.class\n )}\n {...rest}\n >\n <span class=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n <SelectPrimitive.ItemIndicator\n as=\"svg\"\n class=\"h-4 w-4\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M5 13l4 4L19 7\" />\n </SelectPrimitive.ItemIndicator>\n </span>\n <SelectPrimitive.ItemLabel>{local.children}</SelectPrimitive.ItemLabel>\n </SelectPrimitive.Item>\n );\n};",
15
18
  "type": "registry:ui"
16
19
  }
17
20
  ]
@@ -9,10 +9,13 @@
9
9
  "class-variance-authority",
10
10
  "@kobalte/core"
11
11
  ],
12
+ "registryDependencies": [
13
+ "scroll-area"
14
+ ],
12
15
  "files": [
13
16
  {
14
17
  "path": "ui/sheet.tsx",
15
- "content": "import { splitProps, type Component, type JSX, type ValidComponent, Show } from \"solid-js\";\nimport * as DialogPrimitive from \"@kobalte/core/dialog\";\nimport type { PolymorphicProps } from \"@kobalte/core/polymorphic\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"@/lib/cn\";\n\n// Global CSS Keyframe Animations specifically designed for Kobalte animationend DOM events\nconst sheetStyles = `\n@keyframes sheet-slide-in-left { from { transform: translateX(-100%); } to { transform: translateX(0); } }\n@keyframes sheet-slide-out-left { from { transform: translateX(0); } to { transform: translateX(-100%); } }\n@keyframes sheet-slide-in-right { from { transform: translateX(100%); } to { transform: translateX(0); } }\n@keyframes sheet-slide-out-right { from { transform: translateX(0); } to { transform: translateX(100%); } }\n@keyframes sheet-slide-in-top { from { transform: translateY(-100%); } to { transform: translateY(0); } }\n@keyframes sheet-slide-out-top { from { transform: translateY(0); } to { transform: translateY(-100%); } }\n@keyframes sheet-slide-in-bottom { from { transform: translateY(100%); } to { transform: translateY(0); } }\n@keyframes sheet-slide-out-bottom { from { transform: translateY(0); } to { transform: translateY(100%); } }\n@keyframes sheet-fade-in { from { opacity: 0; } to { opacity: 1; } }\n@keyframes sheet-fade-out { from { opacity: 1; } to { opacity: 0; } }\n`;\n\n/**\n * CVA variants for CSS keyframe slide animations across 4 edges.\n */\nexport const sheetVariants = cva(\n \"fixed z-50 gap-4 bg-card p-6 text-card-foreground shadow-lg\",\n {\n variants: {\n side: {\n left: \"fixed top-0 bottom-0 left-0 w-3/4 border-r border-border sm:max-w-sm data-[expanded]:animate-[sheet-slide-in-left_300ms_ease-in-out] data-[closed]:animate-[sheet-slide-out-left_300ms_ease-in-out]\",\n right: \"fixed top-0 bottom-0 right-0 w-3/4 border-l border-border sm:max-w-sm data-[expanded]:animate-[sheet-slide-in-right_300ms_ease-in-out] data-[closed]:animate-[sheet-slide-out-right_300ms_ease-in-out]\",\n top: \"fixed top-0 left-0 right-0 border-b border-border data-[expanded]:animate-[sheet-slide-in-top_300ms_ease-in-out] data-[closed]:animate-[sheet-slide-out-top_300ms_ease-in-out]\",\n bottom: \"fixed bottom-0 left-0 right-0 border-t border-border data-[expanded]:animate-[sheet-slide-in-bottom_300ms_ease-in-out] data-[closed]:animate-[sheet-slide-out-bottom_300ms_ease-in-out]\",\n },\n },\n defaultVariants: {\n side: \"right\",\n },\n }\n);\n\nexport type SheetRootProps = DialogPrimitive.DialogRootProps;\n\nexport const Sheet: Component<SheetRootProps> = (props) => {\n return <DialogPrimitive.Root {...props} />;\n};\n\nexport const SheetTrigger = DialogPrimitive.Trigger;\nexport const SheetClose = DialogPrimitive.CloseButton;\n\nexport interface SheetOverlayProps<T extends ValidComponent = \"div\"> {\n /** Whether to apply background blur effect or keep transparent (default: true) */\n blur?: boolean;\n class?: string;\n}\n\n/**\n * Backdrop overlay wrapper with fade-in and fade-out animations.\n */\nexport const SheetOverlay = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, SheetOverlayProps<T>>\n) => {\n const [local, rest] = splitProps(props as SheetOverlayProps, [\"class\", \"blur\"]);\n\n return (\n <DialogPrimitive.Overlay\n class={cn(\n \"fixed inset-0 z-50 data-[expanded]:animate-[sheet-fade-in_300ms_ease-in-out] data-[closed]:animate-[sheet-fade-out_300ms_ease-in-out]\",\n local.blur !== false ? \"bg-black/80 backdrop-blur-sm\" : \"bg-transparent\",\n local.class\n )}\n {...(rest as any)}\n />\n );\n};\n\nexport type SheetContentProps<T extends ValidComponent = \"div\"> =\n DialogPrimitive.DialogContentProps<T> &\n VariantProps<typeof sheetVariants> & {\n /** Direction from which the sheet slides out: top, bottom, left, right */\n side?: \"top\" | \"bottom\" | \"left\" | \"right\";\n /** Whether to display the top-right close (X) button (default: true) */\n showCloseButton?: boolean;\n /** Whether clicking outside closes the sheet (default: true) */\n closeOnOutsideClick?: boolean;\n /** Whether to apply background backdrop blur (default: true) */\n blur?: boolean;\n class?: string;\n children?: JSX.Element;\n };\n\n/**\n * Main sheet container with smooth CSS keyframe slide animations.\n */\nexport const SheetContent = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, SheetContentProps<T>>\n) => {\n const [local, rest] = splitProps(props as SheetContentProps, [\n \"side\",\n \"showCloseButton\",\n \"closeOnOutsideClick\",\n \"blur\",\n \"class\",\n \"children\",\n \"onPointerDownOutside\",\n \"onInteractOutside\",\n ]);\n\n const side = () => local.side || \"right\";\n\n const handlePointerDownOutside = (e: Event) => {\n if (local.closeOnOutsideClick === false) {\n e.preventDefault();\n }\n if (typeof local.onPointerDownOutside === \"function\") {\n local.onPointerDownOutside(e as any);\n }\n };\n\n const handleInteractOutside = (e: Event) => {\n if (local.closeOnOutsideClick === false) {\n e.preventDefault();\n }\n if (typeof local.onInteractOutside === \"function\") {\n local.onInteractOutside(e as any);\n }\n };\n\n return (\n <DialogPrimitive.Portal>\n <style>{sheetStyles}</style>\n <SheetOverlay blur={local.blur} />\n <DialogPrimitive.Content\n onPointerDownOutside={handlePointerDownOutside}\n onInteractOutside={handleInteractOutside}\n class={cn(sheetVariants({ side: side() }), local.class)}\n {...(rest as any)}\n >\n {local.children}\n <Show when={local.showCloseButton !== false}>\n <DialogPrimitive.CloseButton class=\"absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none cursor-pointer\">\n <svg class=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n <span class=\"sr-only\">Close</span>\n </DialogPrimitive.CloseButton>\n </Show>\n </DialogPrimitive.Content>\n </DialogPrimitive.Portal>\n );\n};\n\nexport interface SheetHeaderProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const SheetHeader: Component<SheetHeaderProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"]);\n\n return (\n <div\n class={cn(\"flex flex-col space-y-2 text-center sm:text-left\", local.class)}\n {...rest}\n />\n );\n};\n\nexport interface SheetFooterProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const SheetFooter: Component<SheetFooterProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"]);\n\n return (\n <div\n class={cn(\"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2\", local.class)}\n {...rest}\n />\n );\n};\n\nexport type SheetTitleProps<T extends ValidComponent = \"h2\"> =\n DialogPrimitive.DialogTitleProps<T> & {\n class?: string;\n };\n\nexport const SheetTitle = <T extends ValidComponent = \"h2\">(\n props: PolymorphicProps<T, SheetTitleProps<T>>\n) => {\n const [local, rest] = splitProps(props as SheetTitleProps, [\"class\"]);\n\n return (\n <DialogPrimitive.Title\n class={cn(\"text-lg font-semibold text-foreground\", local.class)}\n {...(rest as any)}\n />\n );\n};\n\nexport type SheetDescriptionProps<T extends ValidComponent = \"p\"> =\n DialogPrimitive.DialogDescriptionProps<T> & {\n class?: string;\n };\n\nexport const SheetDescription = <T extends ValidComponent = \"p\">(\n props: PolymorphicProps<T, SheetDescriptionProps<T>>\n) => {\n const [local, rest] = splitProps(props as SheetDescriptionProps, [\"class\"]);\n\n return (\n <DialogPrimitive.Description\n class={cn(\"text-sm text-muted-foreground\", local.class)}\n {...(rest as any)}\n />\n );\n};",
18
+ "content": "import { splitProps, type Component, type JSX, type ValidComponent, Show } from \"solid-js\";\nimport * as DialogPrimitive from \"@kobalte/core/dialog\";\nimport type { PolymorphicProps } from \"@kobalte/core/polymorphic\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { X } from \"lucide-solid\";\nimport { ScrollArea } from \"./scroll-area\";\nimport { cn } from \"@/lib/cn\";\n\n// Global CSS Keyframe Animations specifically designed for Kobalte animationend DOM events\nconst sheetStyles = `\n@keyframes sheet-slide-in-left { from { transform: translateX(-100%); } to { transform: translateX(0); } }\n@keyframes sheet-slide-out-left { from { transform: translateX(0); } to { transform: translateX(-100%); } }\n@keyframes sheet-slide-in-right { from { transform: translateX(100%); } to { transform: translateX(0); } }\n@keyframes sheet-slide-out-right { from { transform: translateX(0); } to { transform: translateX(100%); } }\n@keyframes sheet-slide-in-top { from { transform: translateY(-100%); } to { transform: translateY(0); } }\n@keyframes sheet-slide-out-top { from { transform: translateY(0); } to { transform: translateY(-100%); } }\n@keyframes sheet-slide-in-bottom { from { transform: translateY(100%); } to { transform: translateY(0); } }\n@keyframes sheet-slide-out-bottom { from { transform: translateY(0); } to { transform: translateY(-100%); } }\n@keyframes sheet-fade-in { from { opacity: 0; } to { opacity: 1; } }\n@keyframes sheet-fade-out { from { opacity: 1; } to { opacity: 0; } }\n`;\n\n/**\n * CVA variants for CSS keyframe slide animations across 4 edges.\n */\nexport const sheetVariants = cva(\n \"fixed z-50 gap-4 bg-card p-6 text-card-foreground shadow-lg\",\n {\n variants: {\n side: {\n top: \"inset-x-0 top-0 border-b data-[expanded]:animate-[sheet-slide-in-top_300ms_ease-in-out] data-[closed]:animate-[sheet-slide-out-top_300ms_ease-in-out]\",\n bottom:\n \"inset-x-0 bottom-0 border-t data-[expanded]:animate-[sheet-slide-in-bottom_300ms_ease-in-out] data-[closed]:animate-[sheet-slide-out-bottom_300ms_ease-in-out]\",\n left: \"inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm data-[expanded]:animate-[sheet-slide-in-left_300ms_ease-in-out] data-[closed]:animate-[sheet-slide-out-left_300ms_ease-in-out]\",\n right:\n \"inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm data-[expanded]:animate-[sheet-slide-in-right_300ms_ease-in-out] data-[closed]:animate-[sheet-slide-out-right_300ms_ease-in-out]\",\n },\n },\n defaultVariants: {\n side: \"right\",\n },\n }\n);\n\nexport const Sheet = DialogPrimitive.Root;\nexport const SheetTrigger = DialogPrimitive.Trigger;\nexport const SheetClose = DialogPrimitive.CloseButton;\nexport const SheetPortal = DialogPrimitive.Portal;\n\nexport type SheetOverlayProps<T extends ValidComponent = \"div\"> =\n DialogPrimitive.DialogOverlayProps<T> & {\n /** Whether to apply backdrop blur (default: true) */\n blur?: boolean;\n class?: string;\n };\n\n/**\n * Fullscreen dark backdrop with fade keyframe animation.\n */\nexport const SheetOverlay = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, SheetOverlayProps<T>>\n) => {\n const [local, rest] = splitProps(props as SheetOverlayProps, [\"class\", \"blur\"]);\n\n return (\n <DialogPrimitive.Overlay\n class={cn(\n \"fixed inset-0 z-50 transition-all duration-200 data-[expanded]:animate-[sheet-fade-in_300ms_ease-in-out] data-[closed]:animate-[sheet-fade-out_300ms_ease-in-out]\",\n local.blur !== false ? \"bg-black/80 backdrop-blur-sm\" : \"bg-black/80\",\n local.class\n )}\n {...(rest as any)}\n />\n );\n};\n\nexport type SheetContentProps<T extends ValidComponent = \"div\"> =\n DialogPrimitive.DialogContentProps<T> &\n VariantProps<typeof sheetVariants> & {\n side?: \"top\" | \"bottom\" | \"left\" | \"right\";\n showCloseButton?: boolean;\n closeOnOutsideClick?: boolean;\n blur?: boolean;\n class?: string;\n children?: JSX.Element;\n };\n\n/**\n * Slide-out panel container with ScrollArea and animation support.\n */\nexport const SheetContent = <T extends ValidComponent = \"div\">(\n props: PolymorphicProps<T, SheetContentProps<T>>\n) => {\n const [local, rest] = splitProps(props as SheetContentProps, [\n \"class\",\n \"children\",\n \"side\",\n \"showCloseButton\",\n \"closeOnOutsideClick\",\n \"blur\",\n \"onPointerDownOutside\",\n \"onInteractOutside\",\n ]);\n\n const side = () => local.side || \"right\";\n\n const handlePointerDownOutside = (e: Event) => {\n if (local.closeOnOutsideClick === false) {\n e.preventDefault();\n }\n if (typeof local.onPointerDownOutside === \"function\") {\n local.onPointerDownOutside(e as any);\n }\n };\n\n const handleInteractOutside = (e: Event) => {\n if (local.closeOnOutsideClick === false) {\n e.preventDefault();\n }\n if (typeof local.onInteractOutside === \"function\") {\n local.onInteractOutside(e as any);\n }\n };\n\n return (\n <SheetPortal>\n <style>{sheetStyles}</style>\n <SheetOverlay blur={local.blur} />\n <DialogPrimitive.Content\n onPointerDownOutside={handlePointerDownOutside}\n onInteractOutside={handleInteractOutside}\n class={cn(sheetVariants({ side: side() }), \"p-0\", local.class)}\n {...(rest as any)}\n >\n <ScrollArea class=\"h-full w-full\">\n <div class=\"p-6 space-y-4\">\n {local.children}\n </div>\n </ScrollArea>\n <Show when={local.showCloseButton !== false}>\n <DialogPrimitive.CloseButton class=\"absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none cursor-pointer z-50\">\n <X class=\"h-4 w-4\" />\n <span class=\"sr-only\">Close</span>\n </DialogPrimitive.CloseButton>\n </Show>\n </DialogPrimitive.Content>\n </SheetPortal>\n );\n};\n\nexport interface SheetHeaderProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const SheetHeader: Component<SheetHeaderProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"]);\n\n return (\n <div\n class={cn(\"flex flex-col space-y-2 text-center sm:text-left\", local.class)}\n {...rest}\n />\n );\n};\n\nexport interface SheetFooterProps extends JSX.HTMLAttributes<HTMLDivElement> {\n class?: string;\n}\n\nexport const SheetFooter: Component<SheetFooterProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\"]);\n\n return (\n <div\n class={cn(\"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2 pt-4\", local.class)}\n {...rest}\n />\n );\n};\n\nexport interface SheetTitleProps {\n class?: string;\n children?: JSX.Element;\n}\n\nexport const SheetTitle: Component<SheetTitleProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <DialogPrimitive.Title\n class={cn(\"text-lg font-semibold text-foreground\", local.class)}\n {...rest}\n >\n {local.children}\n </DialogPrimitive.Title>\n );\n};\n\nexport interface SheetDescriptionProps {\n class?: string;\n children?: JSX.Element;\n}\n\nexport const SheetDescription: Component<SheetDescriptionProps> = (props) => {\n const [local, rest] = splitProps(props, [\"class\", \"children\"]);\n\n return (\n <DialogPrimitive.Description\n class={cn(\"text-sm text-muted-foreground\", local.class)}\n {...rest}\n >\n {local.children}\n </DialogPrimitive.Description>\n );\n};",
16
19
  "type": "registry:ui"
17
20
  }
18
21
  ]
@@ -31,7 +31,7 @@
31
31
  },
32
32
  {
33
33
  "path": "ui/theme-toggle.tsx",
34
- "content": "import { splitProps, type Component, For, Show } from \"solid-js\";\nimport { Sun, Moon, Monitor } from \"lucide-solid\";\nimport {\n useTheme,\n type AccentColor,\n type Radius,\n type Theme,\n} from \"../../providers/theme-provider\";\nimport {\n runThemeTransition,\n type ThemeEffect,\n} from \"../../providers/theme-transitions\";\nimport { Button } from \"./button\";\nimport {\n DropdownMenu,\n DropdownMenuTrigger,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n} from \"./dropdown-menu\";\nimport { cn } from \"@/lib/cn\";\n\nimport { createColorMode } from \"@nikala-ui/hooks\";\n\nexport interface ThemeToggleProps {\n /** Display mode: \"mini\" for compact dropdown, \"max\" for full customizer panel (default: \"mini\") */\n mode?: \"mini\" | \"max\";\n /** Transition animation effect when changing themes (\"none\", \"circular\", \"fade\") */\n effect?: ThemeEffect;\n class?: string;\n}\n\nconst ACCENT_OPTIONS: { name: AccentColor; label: string; color: string }[] = [\n { name: \"wine\", label: \"Wine\", color: \"bg-[#722f37]\" },\n { name: \"violet\", label: \"Violet\", color: \"bg-[#7c3aed]\" },\n { name: \"sky\", label: \"Sky\", color: \"bg-[#0284c7]\" },\n { name: \"emerald\", label: \"Emerald\", color: \"bg-[#059669]\" },\n { name: \"rose\", label: \"Rose\", color: \"bg-[#e11d48]\" },\n { name: \"amber\", label: \"Amber\", color: \"bg-[#d97706]\" },\n { name: \"zinc\", label: \"Zinc\", color: \"bg-[#18181b]\" },\n];\n\nconst RADIUS_OPTIONS: { value: Radius; label: string }[] = [\n { value: \"0\", label: \"0\" },\n { value: \"0.3\", label: \"0.3\" },\n { value: \"0.5\", label: \"0.5\" },\n { value: \"0.75\", label: \"0.75\" },\n { value: \"1.0\", label: \"1.0\" },\n];\n\n/**\n * Interactive UI theme switcher supporting mini/max modes and View Transition animations.\n */\nexport const ThemeToggle: Component<ThemeToggleProps> = (props) => {\n const [local] = splitProps(props, [\"mode\", \"effect\", \"class\"]);\n const { theme, setTheme, accent, setAccent, radius, setRadius } = useTheme();\n\n const colorMode = createColorMode({\n initialValue: theme(),\n storageKey: \"nikala-theme-mode\",\n });\n\n const mode = () => local.mode || \"mini\";\n const effect = () => local.effect || \"none\";\n\n /* Reactive accessor determining whether dark mode is active via createColorMode hook */\n const isDarkMode = () => {\n const currentTheme = theme();\n if (currentTheme === \"dark\") return true;\n if (currentTheme === \"light\") return false;\n return colorMode.isDark();\n };\n\n const changeThemeWithEffect = (newTheme: Theme, e: MouseEvent) => {\n runThemeTransition(effect(), e, () => {\n setTheme(newTheme);\n colorMode.setMode(newTheme);\n });\n };\n\n return (\n <DropdownMenu placement=\"bottom-end\">\n <DropdownMenuTrigger\n as={Button}\n variant=\"ghost\"\n size=\"icon\"\n class={cn(\"relative h-9 w-9 cursor-pointer\", local.class)}\n >\n {/* Reactive Sun / Moon Icon Toggle */}\n <Show\n when={isDarkMode()}\n fallback={<Sun class=\"h-4 w-4 text-foreground transition-transform\" />}\n >\n <Moon class=\"h-4 w-4 text-foreground transition-transform\" />\n </Show>\n\n <span class=\"sr-only\">Toggle theme</span>\n </DropdownMenuTrigger>\n\n <Show\n when={mode() === \"max\"}\n fallback={\n /* Mini Mode: Compact Dropdown */\n <DropdownMenuContent>\n <DropdownMenuItem onClick={(e: MouseEvent) => changeThemeWithEffect(\"light\", e)}>\n <Sun class=\"mr-2 h-4 w-4 text-muted-foreground\" />\n Light\n </DropdownMenuItem>\n\n <DropdownMenuItem onClick={(e: MouseEvent) => changeThemeWithEffect(\"dark\", e)}>\n <Moon class=\"mr-2 h-4 w-4 text-muted-foreground\" />\n Dark\n </DropdownMenuItem>\n\n <DropdownMenuItem onClick={(e: MouseEvent) => changeThemeWithEffect(\"system\", e)}>\n <Monitor class=\"mr-2 h-4 w-4 text-muted-foreground\" />\n System\n </DropdownMenuItem>\n </DropdownMenuContent>\n }\n >\n {/* Max Mode: Full Theme Customizer Panel */}\n <DropdownMenuContent class=\"w-64 p-3\">\n <DropdownMenuLabel class=\"px-0 pt-0 text-xs font-semibold uppercase tracking-wider text-muted-foreground\">\n Theme Mode\n </DropdownMenuLabel>\n <div class=\"grid grid-cols-3 gap-1 my-1.5\">\n <Button\n variant={theme() === \"light\" ? \"default\" : \"outline\"}\n size=\"sm\"\n onClick={(e: MouseEvent) => changeThemeWithEffect(\"light\", e)}\n class=\"h-8 text-xs cursor-pointer\"\n >\n Light\n </Button>\n <Button\n variant={theme() === \"dark\" ? \"default\" : \"outline\"}\n size=\"sm\"\n onClick={(e: MouseEvent) => changeThemeWithEffect(\"dark\", e)}\n class=\"h-8 text-xs cursor-pointer\"\n >\n Dark\n </Button>\n <Button\n variant={theme() === \"system\" ? \"default\" : \"outline\"}\n size=\"sm\"\n onClick={(e: MouseEvent) => changeThemeWithEffect(\"system\", e)}\n class=\"h-8 text-xs cursor-pointer\"\n >\n System\n </Button>\n </div>\n\n <DropdownMenuSeparator class=\"my-2\" />\n\n <DropdownMenuLabel class=\"px-0 text-xs font-semibold uppercase tracking-wider text-muted-foreground\">\n Brand Accent Color\n </DropdownMenuLabel>\n <div class=\"flex flex-wrap gap-1.5 my-1.5\">\n <For each={ACCENT_OPTIONS}>\n {(opt) => (\n <button\n type=\"button\"\n title={opt.label}\n onClick={() => setAccent(opt.name)}\n class={cn(\n \"h-6 w-6 rounded-md transition-all cursor-pointer border border-border flex items-center justify-center\",\n opt.color,\n accent() === opt.name ? \"ring-2 ring-primary ring-offset-2 ring-offset-background scale-110\" : \"hover:scale-105\"\n )}\n />\n )}\n </For>\n </div>\n\n <DropdownMenuSeparator class=\"my-2\" />\n\n <DropdownMenuLabel class=\"px-0 text-xs font-semibold uppercase tracking-wider text-muted-foreground\">\n Border Radius\n </DropdownMenuLabel>\n <div class=\"grid grid-cols-5 gap-1 my-1.5\">\n <For each={RADIUS_OPTIONS}>\n {(r) => (\n <Button\n variant={radius() === r.value ? \"default\" : \"outline\"}\n size=\"sm\"\n onClick={() => setRadius(r.value)}\n class=\"h-7 text-xs px-1 cursor-pointer\"\n >\n {r.label}\n </Button>\n )}\n </For>\n </div>\n </DropdownMenuContent>\n </Show>\n </DropdownMenu>\n );\n};",
34
+ "content": "import { splitProps, type Component, For, Show } from \"solid-js\";\nimport { Sun, Moon, Monitor } from \"lucide-solid\";\nimport {\n useTheme,\n type AccentColor,\n type Radius,\n type Theme,\n} from \"../../providers/theme-provider\";\nimport {\n runThemeTransition,\n type ThemeEffect,\n} from \"../../providers/theme-transitions\";\nimport { Button } from \"./button\";\nimport {\n DropdownMenu,\n DropdownMenuTrigger,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n} from \"./dropdown-menu\";\nimport { cn } from \"@/lib/cn\";\n\nimport { createColorMode } from \"@nikala-ui/hooks\";\n\nexport interface ThemeToggleProps {\n /** Display mode: \"mini\" for compact dropdown, \"max\" for full customizer panel (default: \"mini\") */\n mode?: \"mini\" | \"max\";\n /** Transition animation effect when changing themes (\"none\", \"circular\", \"fade\") */\n effect?: ThemeEffect;\n class?: string;\n}\n\nconst ACCENT_OPTIONS: { name: AccentColor; label: string; color: string }[] = [\n { name: \"wine\", label: \"Wine\", color: \"bg-[#722f37]\" },\n { name: \"violet\", label: \"Violet\", color: \"bg-[#7c3aed]\" },\n { name: \"sky\", label: \"Sky\", color: \"bg-[#0284c7]\" },\n { name: \"emerald\", label: \"Emerald\", color: \"bg-[#059669]\" },\n { name: \"rose\", label: \"Rose\", color: \"bg-[#e11d48]\" },\n { name: \"amber\", label: \"Amber\", color: \"bg-[#d97706]\" },\n { name: \"zinc\", label: \"Zinc\", color: \"bg-[#18181b]\" },\n];\n\nconst RADIUS_OPTIONS: { value: Radius; label: string }[] = [\n { value: \"0\", label: \"0\" },\n { value: \"0.3\", label: \"0.3\" },\n { value: \"0.5\", label: \"0.5\" },\n { value: \"0.75\", label: \"0.75\" },\n { value: \"1.0\", label: \"1.0\" },\n];\n\n/**\n * Interactive UI theme switcher supporting mini/max modes and View Transition animations.\n */\nexport const ThemeToggle: Component<ThemeToggleProps> = (props) => {\n const [local] = splitProps(props, [\"mode\", \"effect\", \"class\"]);\n const { theme, setTheme, accent, setAccent, radius, setRadius } = useTheme();\n\n const colorMode = createColorMode({\n initialValue: theme(),\n storageKey: \"nikala-theme-mode\",\n });\n\n const mode = () => local.mode || \"mini\";\n const effect = () => local.effect || \"none\";\n\n /* Reactive accessor determining whether dark mode is active via createColorMode hook */\n const isDarkMode = () => {\n const currentTheme = theme();\n if (currentTheme === \"dark\") return true;\n if (currentTheme === \"light\") return false;\n return colorMode.isDark();\n };\n\n const changeThemeWithEffect = (newTheme: Theme, e: MouseEvent) => {\n runThemeTransition(effect(), e, () => {\n setTheme(newTheme);\n colorMode.setMode(newTheme);\n });\n };\n\n return (\n <DropdownMenu placement=\"bottom-end\">\n <DropdownMenuTrigger\n as={Button}\n variant=\"ghost\"\n size=\"icon\"\n class={cn(\"relative h-9 w-9 cursor-pointer\", local.class)}\n >\n {/* Reactive Sun / Moon Icon Toggle */}\n <Show\n when={isDarkMode()}\n fallback={<Sun class=\"h-4 w-4 text-foreground transition-transform\" />}\n >\n <Moon class=\"h-4 w-4 text-foreground transition-transform\" />\n </Show>\n\n <span class=\"sr-only\">Toggle theme</span>\n </DropdownMenuTrigger>\n\n <Show\n when={mode() === \"max\"}\n fallback={\n /* Mini Mode: Compact Dropdown */\n <DropdownMenuContent>\n <DropdownMenuItem onClick={(e: MouseEvent) => changeThemeWithEffect(\"light\", e)}>\n <Sun class=\"mr-2 h-4 w-4 text-muted-foreground\" />\n Light\n </DropdownMenuItem>\n\n <DropdownMenuItem onClick={(e: MouseEvent) => changeThemeWithEffect(\"dark\", e)}>\n <Moon class=\"mr-2 h-4 w-4 text-muted-foreground\" />\n Dark\n </DropdownMenuItem>\n\n <DropdownMenuItem onClick={(e: MouseEvent) => changeThemeWithEffect(\"system\", e)}>\n <Monitor class=\"mr-2 h-4 w-4 text-muted-foreground\" />\n System\n </DropdownMenuItem>\n </DropdownMenuContent>\n }\n >\n {/* Max Mode: Full Theme Customizer Panel */}\n <DropdownMenuContent class=\"w-64\">\n <div class=\"p-2 space-y-2\">\n <DropdownMenuLabel class=\"px-0 pt-0 text-xs font-semibold uppercase tracking-wider text-muted-foreground\">\n Theme Mode\n </DropdownMenuLabel>\n <div class=\"grid grid-cols-3 gap-1 my-1.5\">\n <Button\n variant={theme() === \"light\" ? \"default\" : \"outline\"}\n size=\"sm\"\n onClick={(e: MouseEvent) => changeThemeWithEffect(\"light\", e)}\n class=\"h-8 text-xs cursor-pointer\"\n >\n Light\n </Button>\n <Button\n variant={theme() === \"dark\" ? \"default\" : \"outline\"}\n size=\"sm\"\n onClick={(e: MouseEvent) => changeThemeWithEffect(\"dark\", e)}\n class=\"h-8 text-xs cursor-pointer\"\n >\n Dark\n </Button>\n <Button\n variant={theme() === \"system\" ? \"default\" : \"outline\"}\n size=\"sm\"\n onClick={(e: MouseEvent) => changeThemeWithEffect(\"system\", e)}\n class=\"h-8 text-xs cursor-pointer\"\n >\n System\n </Button>\n </div>\n\n <DropdownMenuSeparator class=\"my-2\" />\n\n <DropdownMenuLabel class=\"px-0 text-xs font-semibold uppercase tracking-wider text-muted-foreground\">\n Brand Accent Color\n </DropdownMenuLabel>\n <div class=\"flex flex-wrap gap-1.5 my-1.5\">\n <For each={ACCENT_OPTIONS}>\n {(opt) => (\n <button\n type=\"button\"\n title={opt.label}\n onClick={() => setAccent(opt.name)}\n class={cn(\n \"h-6 w-6 rounded-md transition-all cursor-pointer border border-border flex items-center justify-center\",\n opt.color,\n accent() === opt.name ? \"ring-2 ring-primary ring-offset-2 ring-offset-background scale-110\" : \"hover:scale-105\"\n )}\n />\n )}\n </For>\n </div>\n\n <DropdownMenuSeparator class=\"my-2\" />\n\n <DropdownMenuLabel class=\"px-0 text-xs font-semibold uppercase tracking-wider text-muted-foreground\">\n Border Radius\n </DropdownMenuLabel>\n <div class=\"grid grid-cols-5 gap-1 my-1.5\">\n <For each={RADIUS_OPTIONS}>\n {(r) => (\n <Button\n variant={radius() === r.value ? \"default\" : \"outline\"}\n size=\"sm\"\n onClick={() => setRadius(r.value)}\n class=\"h-7 text-xs px-1 cursor-pointer\"\n >\n {r.label}\n </Button>\n )}\n </For>\n </div>\n </div>\n </DropdownMenuContent>\n </Show>\n </DropdownMenu>\n );\n};",
35
35
  "type": "registry:ui"
36
36
  }
37
37
  ]
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "toggle",
3
+ "title": "Toggle",
4
+ "description": "A two-state interactive button component built on Kobalte primitives.",
5
+ "type": "registry:ui",
6
+ "dependencies": [
7
+ "clsx",
8
+ "tailwind-merge",
9
+ "@kobalte/core"
10
+ ],
11
+ "files": [
12
+ {
13
+ "path": "ui/toggle.tsx",
14
+ "content": "import { splitProps, type Component, type JSX } from \"solid-js\";\nimport { ToggleButton as KobalteToggle } from \"@kobalte/core/toggle-button\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"@/lib/cn\";\n\nexport const toggleVariants = 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-[pressed]:bg-accent data-[pressed]: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-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground data-[pressed]:bg-accent data-[pressed]: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\nexport interface ToggleProps\n extends JSX.ButtonHTMLAttributes<HTMLButtonElement>,\n VariantProps<typeof toggleVariants> {\n pressed?: boolean;\n defaultPressed?: boolean;\n onPressedChange?: (pressed: boolean) => void;\n disabled?: boolean;\n class?: string;\n children?: JSX.Element;\n}\n\nexport const Toggle: Component<ToggleProps> = (props) => {\n const [local, rest] = splitProps(props, [\n \"variant\",\n \"size\",\n \"class\",\n \"children\",\n \"pressed\",\n \"defaultPressed\",\n \"onPressedChange\",\n \"disabled\",\n \"onChange\",\n ]);\n\n return (\n <KobalteToggle\n isPressed={local.pressed}\n defaultIsPressed={local.defaultPressed}\n onChange={local.onPressedChange}\n disabled={local.disabled}\n class={cn(\n toggleVariants({ variant: local.variant, size: local.size }),\n local.class\n )}\n {...rest}\n >\n {local.children}\n </KobalteToggle>\n );\n};\n\nexport interface ToggleGroupProps extends JSX.HTMLAttributes<HTMLDivElement> {\n type?: \"single\" | \"multiple\";\n value?: string | string[];\n defaultValue?: string | string[];\n onChange?: (value: any) => void;\n disabled?: boolean;\n class?: string;\n children?: JSX.Element;\n}\n\nexport const ToggleGroup: Component<ToggleGroupProps> = (props) => {\n const [local, rest] = splitProps(props, [\n \"type\",\n \"value\",\n \"defaultValue\",\n \"onChange\",\n \"disabled\",\n \"class\",\n \"children\",\n ]);\n\n return (\n <div\n class={cn(\n \"flex items-center justify-center gap-1 rounded-md border border-border/40 p-1 bg-background\",\n local.class\n )}\n {...rest}\n >\n {local.children}\n </div>\n );\n};\n",
15
+ "type": "registry:ui"
16
+ }
17
+ ]
18
+ }
@@ -0,0 +1,31 @@
1
+ import { splitProps, type Component, type JSX } from "solid-js";
2
+ import { cn } from "@/lib/cn";
3
+
4
+ export interface AspectRatioProps extends JSX.HTMLAttributes<HTMLDivElement> {
5
+ ratio?: number;
6
+ class?: string;
7
+ children?: JSX.Element;
8
+ }
9
+
10
+ /**
11
+ * Nikala UI AspectRatio Component.
12
+ * Displays content within a specific aspect ratio (e.g. 16/9, 4/3, 1/1) using CSS aspect-ratio.
13
+ */
14
+ export const AspectRatio: Component<AspectRatioProps> = (props) => {
15
+ const [local, rest] = splitProps(props, ["ratio", "class", "children", "style"]);
16
+
17
+ const computedRatio = () => (local.ratio !== undefined ? local.ratio : 16 / 9);
18
+
19
+ return (
20
+ <div
21
+ class={cn("relative w-full overflow-hidden", local.class)}
22
+ style={{
23
+ "aspect-ratio": `${computedRatio()}`,
24
+ ...(typeof local.style === "object" ? local.style : {}),
25
+ }}
26
+ {...rest}
27
+ >
28
+ {local.children}
29
+ </div>
30
+ );
31
+ };
@@ -0,0 +1,73 @@
1
+ import { splitProps, type JSX, type ValidComponent } from "solid-js";
2
+ import * as CollapsiblePrimitive from "@kobalte/core/collapsible";
3
+ import type { PolymorphicProps } from "@kobalte/core/polymorphic";
4
+ import { cn } from "@/lib/cn";
5
+
6
+ export type CollapsibleRootProps<T extends ValidComponent = "div"> =
7
+ CollapsiblePrimitive.CollapsibleRootProps<T> & {
8
+ class?: string;
9
+ };
10
+
11
+ /**
12
+ * Root Collapsible component built on Kobalte primitives.
13
+ */
14
+ export const Collapsible = <T extends ValidComponent = "div">(
15
+ props: PolymorphicProps<T, CollapsibleRootProps<T>>
16
+ ) => {
17
+ const [local, rest] = splitProps(props as CollapsibleRootProps, ["class"]);
18
+ return (
19
+ <CollapsiblePrimitive.Root
20
+ class={cn("w-full", local.class)}
21
+ {...(rest as any)}
22
+ />
23
+ );
24
+ };
25
+
26
+ export type CollapsibleTriggerProps<T extends ValidComponent = "button"> =
27
+ CollapsiblePrimitive.CollapsibleTriggerProps<T> & {
28
+ class?: string;
29
+ children?: JSX.Element;
30
+ };
31
+
32
+ /**
33
+ * Trigger element that toggles the Collapsible open/closed state.
34
+ */
35
+ export const CollapsibleTrigger = <T extends ValidComponent = "button">(
36
+ props: PolymorphicProps<T, CollapsibleTriggerProps<T>>
37
+ ) => {
38
+ const [local, rest] = splitProps(props as CollapsibleTriggerProps, ["class", "children"]);
39
+ return (
40
+ <CollapsiblePrimitive.Trigger
41
+ class={cn("flex w-full items-center justify-between cursor-pointer", local.class)}
42
+ {...rest}
43
+ >
44
+ {local.children}
45
+ </CollapsiblePrimitive.Trigger>
46
+ );
47
+ };
48
+
49
+ export type CollapsibleContentProps<T extends ValidComponent = "div"> =
50
+ CollapsiblePrimitive.CollapsibleContentProps<T> & {
51
+ class?: string;
52
+ children?: JSX.Element;
53
+ };
54
+
55
+ /**
56
+ * Collapsible content panel revealed when opened.
57
+ */
58
+ export const CollapsibleContent = <T extends ValidComponent = "div">(
59
+ props: PolymorphicProps<T, CollapsibleContentProps<T>>
60
+ ) => {
61
+ const [local, rest] = splitProps(props as CollapsibleContentProps, ["class", "children"]);
62
+ return (
63
+ <CollapsiblePrimitive.Content
64
+ class={cn(
65
+ "overflow-hidden transition-all data-expanded:animate-collapsible-down data-closed:animate-collapsible-up",
66
+ local.class
67
+ )}
68
+ {...rest}
69
+ >
70
+ {local.children}
71
+ </CollapsiblePrimitive.Content>
72
+ );
73
+ };
@@ -1,86 +1,44 @@
1
1
  import { splitProps, type JSX, type ValidComponent } from "solid-js";
2
+ import { Check, ChevronDown, X } from "lucide-solid";
3
+ import { ScrollArea } from "./scroll-area";
2
4
  import * as ComboboxPrimitive from "@kobalte/core/combobox";
3
5
  import { cn } from "@/lib/cn";
4
6
 
5
7
  export type ComboboxRootProps<Option = any, OptGroup = any, T extends ValidComponent = "div"> =
6
- ComboboxPrimitive.ComboboxRootProps<Option, OptGroup, T> & {
7
- class?: string;
8
- children?: JSX.Element;
9
- triggerMode?: "input" | "focus" | "both" | "manual";
10
- };
8
+ ComboboxPrimitive.ComboboxRootProps<Option, OptGroup, T>;
11
9
 
12
10
  /**
13
- * Root Combobox component providing search, single/multi-selection, and group support.
14
- * `triggerMode="focus"` or `triggerMode="both"` enables opening dropdown on input click/focus.
11
+ * Root Combobox primitive component wrapper.
15
12
  */
16
13
  export const Combobox = <Option = any, OptGroup = any, T extends ValidComponent = "div">(
17
14
  props: ComboboxRootProps<Option, OptGroup, T>
18
15
  ) => {
19
- const [local, rest] = splitProps(props as ComboboxRootProps, ["class", "children", "triggerMode"]);
20
-
21
- return (
22
- <ComboboxPrimitive.Root
23
- triggerMode={local.triggerMode ?? "input"}
24
- class={cn("relative w-full", local.class)}
25
- {...(rest as any)}
26
- >
27
- {local.children}
28
- </ComboboxPrimitive.Root>
29
- );
16
+ return <ComboboxPrimitive.Root {...props} />;
30
17
  };
31
18
 
32
19
  export type ComboboxControlProps<Option = any, T extends ValidComponent = "div"> =
33
20
  ComboboxPrimitive.ComboboxControlProps<Option, T> & {
34
21
  class?: string;
35
22
  children?: JSX.Element;
36
- clearable?: boolean;
37
- onClear?: () => void;
38
23
  };
39
24
 
40
25
  /**
41
- * Input container for Combobox supporting search input, selected tags, and clear button.
26
+ * Input container box supporting single or multi-select tokens.
42
27
  */
43
28
  export const ComboboxControl = <Option = any, T extends ValidComponent = "div">(
44
29
  props: ComboboxControlProps<Option, T>
45
30
  ) => {
46
- const [local, rest] = splitProps(props as ComboboxControlProps, [
47
- "class",
48
- "children",
49
- "clearable",
50
- "onClear",
51
- ]);
31
+ const [local, rest] = splitProps(props as ComboboxControlProps, ["class", "children"]);
52
32
 
53
33
  return (
54
34
  <ComboboxPrimitive.Control
55
35
  class={cn(
56
- "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",
36
+ "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",
57
37
  local.class
58
38
  )}
59
39
  {...(rest as any)}
60
40
  >
61
- <div class="flex flex-wrap items-center gap-1.5 flex-1 min-w-0">
62
- {local.children}
63
- </div>
64
-
65
- <div class="flex items-center gap-1 shrink-0 self-center">
66
- {local.clearable && (
67
- <button
68
- type="button"
69
- tabIndex={-1}
70
- onClick={(e) => {
71
- e.stopPropagation();
72
- if (local.onClear) local.onClear();
73
- }}
74
- class="rounded-sm p-0.5 opacity-60 hover:opacity-100 hover:bg-accent text-foreground transition-opacity focus:outline-none cursor-pointer"
75
- aria-label="Clear selection"
76
- >
77
- <svg class="h-3.5 w-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
78
- <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
79
- </svg>
80
- </button>
81
- )}
82
- <ComboboxTrigger />
83
- </div>
41
+ {local.children}
84
42
  </ComboboxPrimitive.Control>
85
43
  );
86
44
  };
@@ -92,8 +50,7 @@ export type ComboboxInputProps<T extends ValidComponent = "input"> =
92
50
  };
93
51
 
94
52
  /**
95
- * Search input field embedded within ComboboxControl.
96
- * Supports `openOnFocus` to automatically trigger the dropdown when focused or clicked.
53
+ * Filter search input field.
97
54
  */
98
55
  export const ComboboxInput = <T extends ValidComponent = "input">(
99
56
  props: ComboboxInputProps<T>
@@ -178,12 +135,10 @@ export const ComboboxToken = <Option = any>(props: ComboboxTokenProps<Option>) =
178
135
  e.stopPropagation();
179
136
  if (local.onRemove) local.onRemove();
180
137
  }}
181
- class="rounded-xs p-0.5 hover:bg-muted-foreground/20 text-muted-foreground hover:text-foreground transition-colors focus:outline-none cursor-pointer"
182
- aria-label="Remove tag"
138
+ class="rounded-xs opacity-70 hover:opacity-100 focus:outline-none cursor-pointer text-muted-foreground hover:text-foreground"
183
139
  >
184
- <svg class="h-3 w-3" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
185
- <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
186
- </svg>
140
+ <X class="h-3 w-3" />
141
+ <span class="sr-only">Remove</span>
187
142
  </button>
188
143
  </span>
189
144
  );
@@ -206,12 +161,14 @@ export const ComboboxContent = <T extends ValidComponent = "div">(
206
161
  <ComboboxPrimitive.Portal>
207
162
  <ComboboxPrimitive.Content
208
163
  class={cn(
209
- "relative z-50 min-w-8rem overflow-hidden rounded-md border border-border bg-popover text-popover-foreground shadow-md animate-in fade-in-80 data-[expanded]:animate-in data-[closed]:animate-out data-[closed]:fade-out-0 data-[closed]:fade-out-0 data-[expanded]:fade-in-0 data-[closed]:zoom-out-95 data-[expanded]:zoom-in-95",
164
+ "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",
210
165
  local.class
211
166
  )}
212
167
  {...(rest as any)}
213
168
  >
214
- <ComboboxPrimitive.Listbox class="max-h-60 overflow-y-auto p-1 outline-none" />
169
+ <ScrollArea class="max-h-60 w-full">
170
+ <ComboboxPrimitive.Listbox class="p-1 outline-none" />
171
+ </ScrollArea>
215
172
  </ComboboxPrimitive.Content>
216
173
  </ComboboxPrimitive.Portal>
217
174
  );
@@ -234,19 +191,17 @@ export const ComboboxItem = <T extends ValidComponent = "li">(
234
191
  return (
235
192
  <ComboboxPrimitive.Item
236
193
  class={cn(
237
- "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",
194
+ "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",
238
195
  local.class
239
196
  )}
240
- {...rest}
197
+ {...(rest as any)}
241
198
  >
242
- <ComboboxPrimitive.ItemIndicator class="absolute left-2 flex h-4 w-4 items-center justify-center text-primary">
243
- <svg class="h-4 w-4 fill-none stroke-current stroke-2" viewBox="0 0 24 24">
244
- <path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
245
- </svg>
246
- </ComboboxPrimitive.ItemIndicator>
247
- <ComboboxPrimitive.ItemLabel class="flex items-center gap-2 w-full truncate">
199
+ <ComboboxPrimitive.ItemLabel class="flex-1 truncate">
248
200
  {local.children}
249
201
  </ComboboxPrimitive.ItemLabel>
202
+ <ComboboxPrimitive.ItemIndicator class="ml-2 flex h-4 w-4 items-center justify-center text-primary">
203
+ <Check class="h-4 w-4 stroke-2" />
204
+ </ComboboxPrimitive.ItemIndicator>
250
205
  </ComboboxPrimitive.Item>
251
206
  );
252
207
  };
@@ -277,19 +232,3 @@ export const ComboboxGroup = <T extends ValidComponent = "li">(
277
232
  </ComboboxPrimitive.Section>
278
233
  );
279
234
  };
280
-
281
- /**
282
- * Empty state notice when no matching search items exist.
283
- */
284
- export const ComboboxEmpty = (props: { class?: string; children?: JSX.Element }) => {
285
- return (
286
- <div class={cn("py-6 text-center text-sm text-muted-foreground", props.class)}>
287
- {props.children || "No matching items found."}
288
- </div>
289
- );
290
- };
291
-
292
- /**
293
- * Hidden native select element for form integrations.
294
- */
295
- export const ComboboxHiddenSelect = ComboboxPrimitive.HiddenSelect;