@nikala-ui/core 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -0
- package/package.json +25 -0
- package/registry/accordion.json +18 -0
- package/registry/alert.json +18 -0
- package/registry/avatar.json +17 -0
- package/registry/badge.json +18 -0
- package/registry/banner.json +19 -0
- package/registry/breadcrumb.json +17 -0
- package/registry/button.json +18 -0
- package/registry/card.json +17 -0
- package/registry/checkbox.json +17 -0
- package/registry/command.json +25 -0
- package/registry/dialog.json +18 -0
- package/registry/dropdown-menu.json +18 -0
- package/registry/index.json +297 -0
- package/registry/input-group.json +21 -0
- package/registry/input.json +17 -0
- package/registry/kbd.json +18 -0
- package/registry/label.json +18 -0
- package/registry/list.json +20 -0
- package/registry/radio-group.json +18 -0
- package/registry/select.json +18 -0
- package/registry/separator.json +17 -0
- package/registry/sheet.json +19 -0
- package/registry/skeleton.json +17 -0
- package/registry/switch.json +17 -0
- package/registry/tabs.json +17 -0
- package/registry/textarea.json +17 -0
- package/registry/theme-manager.json +38 -0
- package/src/index.css +11 -0
- package/src/lib/cn.ts +9 -0
- package/src/registry/components/ui/accordion.tsx +128 -0
- package/src/registry/components/ui/alert.tsx +155 -0
- package/src/registry/components/ui/avatar.tsx +114 -0
- package/src/registry/components/ui/badge.tsx +50 -0
- package/src/registry/components/ui/banner.tsx +189 -0
- package/src/registry/components/ui/breadcrumb.tsx +136 -0
- package/src/registry/components/ui/button.tsx +63 -0
- package/src/registry/components/ui/card.tsx +113 -0
- package/src/registry/components/ui/checkbox.tsx +85 -0
- package/src/registry/components/ui/command.tsx +286 -0
- package/src/registry/components/ui/dialog.tsx +195 -0
- package/src/registry/components/ui/dropdown-menu.tsx +250 -0
- package/src/registry/components/ui/input-group.tsx +80 -0
- package/src/registry/components/ui/input.tsx +28 -0
- package/src/registry/components/ui/kbd.tsx +73 -0
- package/src/registry/components/ui/label.tsx +30 -0
- package/src/registry/components/ui/list.tsx +222 -0
- package/src/registry/components/ui/radio-group.tsx +95 -0
- package/src/registry/components/ui/select.tsx +138 -0
- package/src/registry/components/ui/separator.tsx +37 -0
- package/src/registry/components/ui/sheet.tsx +216 -0
- package/src/registry/components/ui/skeleton.tsx +24 -0
- package/src/registry/components/ui/switch.tsx +79 -0
- package/src/registry/components/ui/tabs.tsx +212 -0
- package/src/registry/components/ui/textarea.tsx +82 -0
- package/src/registry/components/ui/theme-toggle.tsx +195 -0
- package/src/registry/index.ts +50 -0
- package/src/registry/metadata.ts +152 -0
- package/src/registry/providers/theme-provider.tsx +208 -0
- package/src/registry/providers/theme-script.tsx +58 -0
- package/src/registry/providers/theme-transitions.ts +108 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { splitProps, type JSX, type ValidComponent } from "solid-js";
|
|
2
|
+
import * as RadioGroupPrimitive from "@kobalte/core/radio-group";
|
|
3
|
+
import type { PolymorphicProps } from "@kobalte/core/polymorphic";
|
|
4
|
+
import { cn } from "@/lib/cn";
|
|
5
|
+
|
|
6
|
+
export type RadioGroupRootProps<T extends ValidComponent = "div"> =
|
|
7
|
+
RadioGroupPrimitive.RadioGroupRootProps<T> & {
|
|
8
|
+
class?: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Root RadioGroup component built on top of Kobalte headless primitives.
|
|
13
|
+
* Supports both "vertical" (default) and "horizontal" layout orientations.
|
|
14
|
+
*/
|
|
15
|
+
export const RadioGroup = <T extends ValidComponent = "div">(
|
|
16
|
+
props: PolymorphicProps<T, RadioGroupRootProps<T>>
|
|
17
|
+
) => {
|
|
18
|
+
const [local, rest] = splitProps(props as RadioGroupRootProps, ["class", "orientation"]);
|
|
19
|
+
|
|
20
|
+
const isHorizontal = () => local.orientation === "horizontal";
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<RadioGroupPrimitive.Root
|
|
24
|
+
orientation={local.orientation || "vertical"}
|
|
25
|
+
class={cn(
|
|
26
|
+
isHorizontal()
|
|
27
|
+
? "flex flex-row items-center gap-4"
|
|
28
|
+
: "grid gap-2",
|
|
29
|
+
local.class
|
|
30
|
+
)}
|
|
31
|
+
{...(rest as any)}
|
|
32
|
+
/>
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type RadioGroupItemProps<T extends ValidComponent = "div"> =
|
|
37
|
+
RadioGroupPrimitive.RadioGroupItemProps<T> & {
|
|
38
|
+
class?: string;
|
|
39
|
+
children?: JSX.Element;
|
|
40
|
+
id?: string;
|
|
41
|
+
value: string;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Accessible RadioGroup item choice wrapping Kobalte primitives.
|
|
46
|
+
*/
|
|
47
|
+
export const RadioGroupItem = <T extends ValidComponent = "div">(
|
|
48
|
+
props: PolymorphicProps<T, RadioGroupItemProps<T>>
|
|
49
|
+
) => {
|
|
50
|
+
const [local, rest] = splitProps(props as RadioGroupItemProps, ["class", "children"]);
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<RadioGroupPrimitive.Item
|
|
54
|
+
class={cn("flex items-center space-x-2 cursor-pointer", local.class)}
|
|
55
|
+
{...rest}
|
|
56
|
+
>
|
|
57
|
+
<RadioGroupPrimitive.ItemInput />
|
|
58
|
+
<RadioGroupPrimitive.ItemControl
|
|
59
|
+
class={cn(
|
|
60
|
+
"aspect-square h-4 w-4 rounded-lg border border-primary text-primary shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 flex items-center justify-center transition-colors data-[checked]:bg-primary data-[checked]:text-primary-foreground"
|
|
61
|
+
)}
|
|
62
|
+
>
|
|
63
|
+
<RadioGroupPrimitive.ItemIndicator class="flex items-center justify-center">
|
|
64
|
+
<span class="h-2 w-2 rounded-lg bg-primary-foreground" />
|
|
65
|
+
</RadioGroupPrimitive.ItemIndicator>
|
|
66
|
+
</RadioGroupPrimitive.ItemControl>
|
|
67
|
+
{local.children}
|
|
68
|
+
</RadioGroupPrimitive.Item>
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export type RadioGroupItemLabelProps<T extends ValidComponent = "label"> =
|
|
73
|
+
RadioGroupPrimitive.RadioGroupItemLabelProps<T> & {
|
|
74
|
+
class?: string;
|
|
75
|
+
children?: JSX.Element;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Accessible text label for a RadioGroup item.
|
|
80
|
+
*/
|
|
81
|
+
export const RadioGroupItemLabel = <T extends ValidComponent = "label">(
|
|
82
|
+
props: PolymorphicProps<T, RadioGroupItemLabelProps<T>>
|
|
83
|
+
) => {
|
|
84
|
+
const [local, rest] = splitProps(props as RadioGroupItemLabelProps, ["class"]);
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<RadioGroupPrimitive.ItemLabel
|
|
88
|
+
class={cn(
|
|
89
|
+
"text-sm font-medium leading-none select-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-foreground",
|
|
90
|
+
local.class
|
|
91
|
+
)}
|
|
92
|
+
{...rest}
|
|
93
|
+
/>
|
|
94
|
+
);
|
|
95
|
+
};
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { splitProps, type JSX, type ValidComponent } from "solid-js";
|
|
2
|
+
import * as SelectPrimitive from "@kobalte/core/select";
|
|
3
|
+
import type { PolymorphicProps } from "@kobalte/core/polymorphic";
|
|
4
|
+
import { cn } from "@/lib/cn";
|
|
5
|
+
|
|
6
|
+
export type SelectRootProps<Option = any, OptGroup = any, T extends ValidComponent = "div"> =
|
|
7
|
+
SelectPrimitive.SelectRootProps<Option, OptGroup, T> & {
|
|
8
|
+
class?: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Root Select component built on top of Kobalte headless primitives.
|
|
13
|
+
*/
|
|
14
|
+
export const Select = <Option = any, OptGroup = any, T extends ValidComponent = "div">(
|
|
15
|
+
props: PolymorphicProps<T, SelectRootProps<Option, OptGroup, T>>
|
|
16
|
+
) => {
|
|
17
|
+
const [local, rest] = splitProps(props as SelectRootProps, ["class"]);
|
|
18
|
+
|
|
19
|
+
return <SelectPrimitive.Root class={cn("relative w-full", local.class)} {...(rest as any)} />;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type SelectTriggerProps<T extends ValidComponent = "button"> =
|
|
23
|
+
SelectPrimitive.SelectTriggerProps<T> & {
|
|
24
|
+
class?: string;
|
|
25
|
+
children?: JSX.Element;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Trigger button opening the Select options list.
|
|
30
|
+
*/
|
|
31
|
+
export const SelectTrigger = <T extends ValidComponent = "button">(
|
|
32
|
+
props: PolymorphicProps<T, SelectTriggerProps<T>>
|
|
33
|
+
) => {
|
|
34
|
+
const [local, rest] = splitProps(props as SelectTriggerProps, ["class", "children"]);
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<SelectPrimitive.Trigger
|
|
38
|
+
class={cn(
|
|
39
|
+
"flex h-9 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 cursor-pointer text-foreground",
|
|
40
|
+
local.class
|
|
41
|
+
)}
|
|
42
|
+
{...rest}
|
|
43
|
+
>
|
|
44
|
+
{local.children}
|
|
45
|
+
<SelectPrimitive.Icon
|
|
46
|
+
as="svg"
|
|
47
|
+
class="h-4 w-4 opacity-50 transition-transform"
|
|
48
|
+
viewBox="0 0 24 24"
|
|
49
|
+
fill="none"
|
|
50
|
+
stroke="currentColor"
|
|
51
|
+
stroke-width="2"
|
|
52
|
+
>
|
|
53
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7" />
|
|
54
|
+
</SelectPrimitive.Icon>
|
|
55
|
+
</SelectPrimitive.Trigger>
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export type SelectValueProps<Option, T extends ValidComponent = "span"> =
|
|
60
|
+
SelectPrimitive.SelectValueProps<Option, T> & {
|
|
61
|
+
class?: string;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Renders the currently selected option text or placeholder.
|
|
66
|
+
*/
|
|
67
|
+
export const SelectValue = <Option = any, T extends ValidComponent = "span">(
|
|
68
|
+
props: PolymorphicProps<T, SelectValueProps<Option, T>>
|
|
69
|
+
) => {
|
|
70
|
+
const [local, rest] = splitProps(props as SelectValueProps<Option>, ["class"]);
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<SelectPrimitive.Value
|
|
74
|
+
class={cn("block truncate", local.class)}
|
|
75
|
+
{...(rest as any)}
|
|
76
|
+
/>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export type SelectContentProps<T extends ValidComponent = "div"> =
|
|
81
|
+
SelectPrimitive.SelectContentProps<T> & {
|
|
82
|
+
class?: string;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Portaled overlay container rendering the listbox options.
|
|
87
|
+
*/
|
|
88
|
+
export const SelectContent = <T extends ValidComponent = "div">(
|
|
89
|
+
props: PolymorphicProps<T, SelectContentProps<T>>
|
|
90
|
+
) => {
|
|
91
|
+
const [local, rest] = splitProps(props as SelectContentProps, ["class"]);
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<SelectPrimitive.Portal>
|
|
95
|
+
<SelectPrimitive.Content
|
|
96
|
+
class={cn(
|
|
97
|
+
"relative z-50 min-w-[8rem] overflow-hidden rounded-md border border-border bg-popover text-popover-foreground shadow-md animate-in fade-in-80",
|
|
98
|
+
local.class
|
|
99
|
+
)}
|
|
100
|
+
{...rest}
|
|
101
|
+
>
|
|
102
|
+
<SelectPrimitive.Listbox class="p-1 outline-none" />
|
|
103
|
+
</SelectPrimitive.Content>
|
|
104
|
+
</SelectPrimitive.Portal>
|
|
105
|
+
);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export type SelectItemProps<T extends ValidComponent = "li"> =
|
|
109
|
+
SelectPrimitive.SelectItemProps<T> & {
|
|
110
|
+
class?: string;
|
|
111
|
+
children?: JSX.Element;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Individual option item choice inside SelectContent.
|
|
116
|
+
*/
|
|
117
|
+
export const SelectItem = <T extends ValidComponent = "li">(
|
|
118
|
+
props: PolymorphicProps<T, SelectItemProps<T>>
|
|
119
|
+
) => {
|
|
120
|
+
const [local, rest] = splitProps(props as SelectItemProps, ["class", "children"]);
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<SelectPrimitive.Item
|
|
124
|
+
class={cn(
|
|
125
|
+
"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",
|
|
126
|
+
local.class
|
|
127
|
+
)}
|
|
128
|
+
{...rest}
|
|
129
|
+
>
|
|
130
|
+
<SelectPrimitive.ItemIndicator class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
131
|
+
<svg class="h-4 w-4 fill-none stroke-current stroke-2" viewBox="0 0 24 24">
|
|
132
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
|
|
133
|
+
</svg>
|
|
134
|
+
</SelectPrimitive.ItemIndicator>
|
|
135
|
+
<SelectPrimitive.ItemLabel>{local.children}</SelectPrimitive.ItemLabel>
|
|
136
|
+
</SelectPrimitive.Item>
|
|
137
|
+
);
|
|
138
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { splitProps, type Component, type JSX } from "solid-js";
|
|
2
|
+
import { cn } from "@/lib/cn";
|
|
3
|
+
|
|
4
|
+
export interface SeparatorProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
/** Orientation of the separator line */
|
|
6
|
+
orientation?: "horizontal" | "vertical";
|
|
7
|
+
/** Whether the element is purely visual or semantic for screen readers */
|
|
8
|
+
decorative?: boolean;
|
|
9
|
+
class?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Nikala UI Separator component used to divide content visually or semantically.
|
|
14
|
+
*/
|
|
15
|
+
export const Separator: Component<SeparatorProps> = (props) => {
|
|
16
|
+
const [local, rest] = splitProps(props, [
|
|
17
|
+
"orientation",
|
|
18
|
+
"decorative",
|
|
19
|
+
"class",
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
const orientation = () => local.orientation || "horizontal";
|
|
23
|
+
const isDecorative = () => local.decorative ?? true;
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div
|
|
27
|
+
role={isDecorative() ? "none" : "separator"}
|
|
28
|
+
aria-orientation={isDecorative() ? undefined : orientation()}
|
|
29
|
+
class={cn(
|
|
30
|
+
"shrink-0 bg-border",
|
|
31
|
+
orientation() === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
|
|
32
|
+
local.class
|
|
33
|
+
)}
|
|
34
|
+
{...rest}
|
|
35
|
+
/>
|
|
36
|
+
);
|
|
37
|
+
};
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { splitProps, type Component, type JSX, type ValidComponent, Show } from "solid-js";
|
|
2
|
+
import * as DialogPrimitive from "@kobalte/core/dialog";
|
|
3
|
+
import type { PolymorphicProps } from "@kobalte/core/polymorphic";
|
|
4
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
5
|
+
import { cn } from "@/lib/cn";
|
|
6
|
+
|
|
7
|
+
// Global CSS Keyframe Animations specifically designed for Kobalte animationend DOM events
|
|
8
|
+
const sheetStyles = `
|
|
9
|
+
@keyframes sheet-slide-in-left { from { transform: translateX(-100%); } to { transform: translateX(0); } }
|
|
10
|
+
@keyframes sheet-slide-out-left { from { transform: translateX(0); } to { transform: translateX(-100%); } }
|
|
11
|
+
@keyframes sheet-slide-in-right { from { transform: translateX(100%); } to { transform: translateX(0); } }
|
|
12
|
+
@keyframes sheet-slide-out-right { from { transform: translateX(0); } to { transform: translateX(100%); } }
|
|
13
|
+
@keyframes sheet-slide-in-top { from { transform: translateY(-100%); } to { transform: translateY(0); } }
|
|
14
|
+
@keyframes sheet-slide-out-top { from { transform: translateY(0); } to { transform: translateY(-100%); } }
|
|
15
|
+
@keyframes sheet-slide-in-bottom { from { transform: translateY(100%); } to { transform: translateY(0); } }
|
|
16
|
+
@keyframes sheet-slide-out-bottom { from { transform: translateY(0); } to { transform: translateY(100%); } }
|
|
17
|
+
@keyframes sheet-fade-in { from { opacity: 0; } to { opacity: 1; } }
|
|
18
|
+
@keyframes sheet-fade-out { from { opacity: 1; } to { opacity: 0; } }
|
|
19
|
+
`;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* CVA variants for CSS keyframe slide animations across 4 edges.
|
|
23
|
+
*/
|
|
24
|
+
export const sheetVariants = cva(
|
|
25
|
+
"fixed z-50 gap-4 bg-card p-6 text-card-foreground shadow-lg",
|
|
26
|
+
{
|
|
27
|
+
variants: {
|
|
28
|
+
side: {
|
|
29
|
+
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]",
|
|
30
|
+
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]",
|
|
31
|
+
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]",
|
|
32
|
+
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]",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
defaultVariants: {
|
|
36
|
+
side: "right",
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
export type SheetRootProps = DialogPrimitive.DialogRootProps;
|
|
42
|
+
|
|
43
|
+
export const Sheet: Component<SheetRootProps> = (props) => {
|
|
44
|
+
return <DialogPrimitive.Root {...props} />;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const SheetTrigger = DialogPrimitive.Trigger;
|
|
48
|
+
export const SheetClose = DialogPrimitive.CloseButton;
|
|
49
|
+
|
|
50
|
+
export interface SheetOverlayProps<T extends ValidComponent = "div"> {
|
|
51
|
+
/** Whether to apply background blur effect or keep transparent (default: true) */
|
|
52
|
+
blur?: boolean;
|
|
53
|
+
class?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Backdrop overlay wrapper with fade-in and fade-out animations.
|
|
58
|
+
*/
|
|
59
|
+
export const SheetOverlay = <T extends ValidComponent = "div">(
|
|
60
|
+
props: PolymorphicProps<T, SheetOverlayProps<T>>
|
|
61
|
+
) => {
|
|
62
|
+
const [local, rest] = splitProps(props as SheetOverlayProps, ["class", "blur"]);
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<DialogPrimitive.Overlay
|
|
66
|
+
class={cn(
|
|
67
|
+
"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]",
|
|
68
|
+
local.blur !== false ? "bg-black/80 backdrop-blur-sm" : "bg-transparent",
|
|
69
|
+
local.class
|
|
70
|
+
)}
|
|
71
|
+
{...(rest as any)}
|
|
72
|
+
/>
|
|
73
|
+
);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export type SheetContentProps<T extends ValidComponent = "div"> =
|
|
77
|
+
DialogPrimitive.DialogContentProps<T> &
|
|
78
|
+
VariantProps<typeof sheetVariants> & {
|
|
79
|
+
/** Direction from which the sheet slides out: top, bottom, left, right */
|
|
80
|
+
side?: "top" | "bottom" | "left" | "right";
|
|
81
|
+
/** Whether to display the top-right close (X) button (default: true) */
|
|
82
|
+
showCloseButton?: boolean;
|
|
83
|
+
/** Whether clicking outside closes the sheet (default: true) */
|
|
84
|
+
closeOnOutsideClick?: boolean;
|
|
85
|
+
/** Whether to apply background backdrop blur (default: true) */
|
|
86
|
+
blur?: boolean;
|
|
87
|
+
class?: string;
|
|
88
|
+
children?: JSX.Element;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Main sheet container with smooth CSS keyframe slide animations.
|
|
93
|
+
*/
|
|
94
|
+
export const SheetContent = <T extends ValidComponent = "div">(
|
|
95
|
+
props: PolymorphicProps<T, SheetContentProps<T>>
|
|
96
|
+
) => {
|
|
97
|
+
const [local, rest] = splitProps(props as SheetContentProps, [
|
|
98
|
+
"side",
|
|
99
|
+
"showCloseButton",
|
|
100
|
+
"closeOnOutsideClick",
|
|
101
|
+
"blur",
|
|
102
|
+
"class",
|
|
103
|
+
"children",
|
|
104
|
+
"onPointerDownOutside",
|
|
105
|
+
"onInteractOutside",
|
|
106
|
+
]);
|
|
107
|
+
|
|
108
|
+
const side = () => local.side || "right";
|
|
109
|
+
|
|
110
|
+
const handlePointerDownOutside = (e: Event) => {
|
|
111
|
+
if (local.closeOnOutsideClick === false) {
|
|
112
|
+
e.preventDefault();
|
|
113
|
+
}
|
|
114
|
+
if (typeof local.onPointerDownOutside === "function") {
|
|
115
|
+
local.onPointerDownOutside(e as any);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const handleInteractOutside = (e: Event) => {
|
|
120
|
+
if (local.closeOnOutsideClick === false) {
|
|
121
|
+
e.preventDefault();
|
|
122
|
+
}
|
|
123
|
+
if (typeof local.onInteractOutside === "function") {
|
|
124
|
+
local.onInteractOutside(e as any);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<DialogPrimitive.Portal>
|
|
130
|
+
<style>{sheetStyles}</style>
|
|
131
|
+
<SheetOverlay blur={local.blur} />
|
|
132
|
+
<DialogPrimitive.Content
|
|
133
|
+
onPointerDownOutside={handlePointerDownOutside}
|
|
134
|
+
onInteractOutside={handleInteractOutside}
|
|
135
|
+
class={cn(sheetVariants({ side: side() }), local.class)}
|
|
136
|
+
{...(rest as any)}
|
|
137
|
+
>
|
|
138
|
+
{local.children}
|
|
139
|
+
<Show when={local.showCloseButton !== false}>
|
|
140
|
+
<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">
|
|
141
|
+
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
142
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
|
143
|
+
</svg>
|
|
144
|
+
<span class="sr-only">Close</span>
|
|
145
|
+
</DialogPrimitive.CloseButton>
|
|
146
|
+
</Show>
|
|
147
|
+
</DialogPrimitive.Content>
|
|
148
|
+
</DialogPrimitive.Portal>
|
|
149
|
+
);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
export interface SheetHeaderProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
|
153
|
+
class?: string;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export const SheetHeader: Component<SheetHeaderProps> = (props) => {
|
|
157
|
+
const [local, rest] = splitProps(props, ["class"]);
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<div
|
|
161
|
+
class={cn("flex flex-col space-y-2 text-center sm:text-left", local.class)}
|
|
162
|
+
{...rest}
|
|
163
|
+
/>
|
|
164
|
+
);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
export interface SheetFooterProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
|
168
|
+
class?: string;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export const SheetFooter: Component<SheetFooterProps> = (props) => {
|
|
172
|
+
const [local, rest] = splitProps(props, ["class"]);
|
|
173
|
+
|
|
174
|
+
return (
|
|
175
|
+
<div
|
|
176
|
+
class={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", local.class)}
|
|
177
|
+
{...rest}
|
|
178
|
+
/>
|
|
179
|
+
);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
export type SheetTitleProps<T extends ValidComponent = "h2"> =
|
|
183
|
+
DialogPrimitive.DialogTitleProps<T> & {
|
|
184
|
+
class?: string;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
export const SheetTitle = <T extends ValidComponent = "h2">(
|
|
188
|
+
props: PolymorphicProps<T, SheetTitleProps<T>>
|
|
189
|
+
) => {
|
|
190
|
+
const [local, rest] = splitProps(props as SheetTitleProps, ["class"]);
|
|
191
|
+
|
|
192
|
+
return (
|
|
193
|
+
<DialogPrimitive.Title
|
|
194
|
+
class={cn("text-lg font-semibold text-foreground", local.class)}
|
|
195
|
+
{...(rest as any)}
|
|
196
|
+
/>
|
|
197
|
+
);
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
export type SheetDescriptionProps<T extends ValidComponent = "p"> =
|
|
201
|
+
DialogPrimitive.DialogDescriptionProps<T> & {
|
|
202
|
+
class?: string;
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
export const SheetDescription = <T extends ValidComponent = "p">(
|
|
206
|
+
props: PolymorphicProps<T, SheetDescriptionProps<T>>
|
|
207
|
+
) => {
|
|
208
|
+
const [local, rest] = splitProps(props as SheetDescriptionProps, ["class"]);
|
|
209
|
+
|
|
210
|
+
return (
|
|
211
|
+
<DialogPrimitive.Description
|
|
212
|
+
class={cn("text-sm text-muted-foreground", local.class)}
|
|
213
|
+
{...(rest as any)}
|
|
214
|
+
/>
|
|
215
|
+
);
|
|
216
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { splitProps, type Component, type JSX } from "solid-js";
|
|
2
|
+
import { cn } from "@/lib/cn";
|
|
3
|
+
|
|
4
|
+
export interface SkeletonProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
class?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Nikala UI Skeleton component for rendering animated pulse loading placeholders.
|
|
10
|
+
*/
|
|
11
|
+
export const Skeleton: Component<SkeletonProps> = (props) => {
|
|
12
|
+
// Use splitProps to preserve SolidJS reactivity
|
|
13
|
+
const [local, rest] = splitProps(props, ["class"]);
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div
|
|
17
|
+
class={cn(
|
|
18
|
+
"animate-pulse rounded-md bg-muted",
|
|
19
|
+
local.class
|
|
20
|
+
)}
|
|
21
|
+
{...rest}
|
|
22
|
+
/>
|
|
23
|
+
);
|
|
24
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { createSignal, splitProps, type Component, type JSX } from "solid-js";
|
|
2
|
+
import { cn } from "@/lib/cn";
|
|
3
|
+
|
|
4
|
+
export interface SwitchProps
|
|
5
|
+
extends Omit<JSX.ButtonHTMLAttributes<HTMLButtonElement>, "onChange"> {
|
|
6
|
+
/** Controlled checked state */
|
|
7
|
+
checked?: boolean;
|
|
8
|
+
/** Uncontrolled default checked state */
|
|
9
|
+
defaultChecked?: boolean;
|
|
10
|
+
/** Event handler triggered when toggle state changes */
|
|
11
|
+
onChange?: (checked: boolean) => void;
|
|
12
|
+
class?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Nikala UI Switch component for boolean toggle inputs built for SolidJS with Tailwind CSS v4 styling.
|
|
17
|
+
*/
|
|
18
|
+
export const Switch: Component<SwitchProps> = (props) => {
|
|
19
|
+
// Use splitProps to preserve SolidJS reactivity
|
|
20
|
+
const [local, rest] = splitProps(props, [
|
|
21
|
+
"checked",
|
|
22
|
+
"defaultChecked",
|
|
23
|
+
"onChange",
|
|
24
|
+
"disabled",
|
|
25
|
+
"class",
|
|
26
|
+
"onClick",
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
// Internal state for uncontrolled mode
|
|
30
|
+
const [internalChecked, setInternalChecked] = createSignal(
|
|
31
|
+
local.defaultChecked ?? false
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
// Computed checked state supporting both controlled and uncontrolled modes
|
|
35
|
+
const isChecked = () =>
|
|
36
|
+
local.checked !== undefined ? local.checked : internalChecked();
|
|
37
|
+
|
|
38
|
+
const toggle = (
|
|
39
|
+
e: MouseEvent & { currentTarget: HTMLButtonElement; target: Element }
|
|
40
|
+
) => {
|
|
41
|
+
if (local.disabled) return;
|
|
42
|
+
|
|
43
|
+
const nextState = !isChecked();
|
|
44
|
+
if (local.checked === undefined) {
|
|
45
|
+
setInternalChecked(nextState);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (typeof local.onChange === "function") {
|
|
49
|
+
local.onChange(nextState);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (typeof local.onClick === "function") {
|
|
53
|
+
local.onClick(e);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<button
|
|
59
|
+
type="button"
|
|
60
|
+
role="switch"
|
|
61
|
+
aria-checked={isChecked()}
|
|
62
|
+
data-state={isChecked() ? "checked" : "unchecked"}
|
|
63
|
+
disabled={local.disabled}
|
|
64
|
+
onClick={toggle}
|
|
65
|
+
class={cn(
|
|
66
|
+
"peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-lg border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
|
|
67
|
+
local.class
|
|
68
|
+
)}
|
|
69
|
+
{...rest}
|
|
70
|
+
>
|
|
71
|
+
<span
|
|
72
|
+
data-state={isChecked() ? "checked" : "unchecked"}
|
|
73
|
+
class={cn(
|
|
74
|
+
"pointer-events-none block h-4 w-4 rounded-sm bg-primary-foreground shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0"
|
|
75
|
+
)}
|
|
76
|
+
/>
|
|
77
|
+
</button>
|
|
78
|
+
);
|
|
79
|
+
};
|