@nikala-ui/core 0.10.1 → 0.11.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 (55) hide show
  1. package/package.json +1 -1
  2. package/registry/bubble.json +18 -0
  3. package/registry/button-group.json +20 -0
  4. package/registry/create-chat-scroll.json +13 -0
  5. package/registry/create-drop-zone.json +13 -0
  6. package/registry/create-pagination.json +13 -0
  7. package/registry/dropzone.json +21 -0
  8. package/registry/footer.json +18 -0
  9. package/registry/forgot-password-01.json +28 -0
  10. package/registry/hero-01.json +22 -0
  11. package/registry/index.json +331 -0
  12. package/registry/login-01.json +28 -0
  13. package/registry/marker.json +17 -0
  14. package/registry/marquee.json +17 -0
  15. package/registry/message.json +17 -0
  16. package/registry/navbar.json +19 -0
  17. package/registry/navigation-menu.json +22 -0
  18. package/registry/otp-verification-01.json +26 -0
  19. package/registry/pagination.json +22 -0
  20. package/registry/rating.json +19 -0
  21. package/registry/register-01.json +31 -0
  22. package/registry/review-card.json +23 -0
  23. package/registry/scroll-area.json +1 -1
  24. package/registry/sidebar.json +24 -0
  25. package/registry/spinner.json +1 -1
  26. package/registry/stat.json +19 -0
  27. package/registry/table.json +17 -0
  28. package/registry/timeline.json +18 -0
  29. package/registry/toggle-group.json +21 -0
  30. package/src/registry/blocks/forgot-password-01.tsx +141 -0
  31. package/src/registry/blocks/hero-01.tsx +28 -0
  32. package/src/registry/blocks/login-01.tsx +231 -0
  33. package/src/registry/blocks/otp-verification-01.tsx +170 -0
  34. package/src/registry/blocks/register-01.tsx +318 -0
  35. package/src/registry/components/ui/bubble.tsx +142 -0
  36. package/src/registry/components/ui/button-group.tsx +42 -0
  37. package/src/registry/components/ui/dropzone.tsx +189 -0
  38. package/src/registry/components/ui/footer.tsx +247 -0
  39. package/src/registry/components/ui/marker.tsx +102 -0
  40. package/src/registry/components/ui/marquee.tsx +118 -0
  41. package/src/registry/components/ui/message.tsx +168 -0
  42. package/src/registry/components/ui/navbar.tsx +368 -0
  43. package/src/registry/components/ui/navigation-menu.tsx +358 -0
  44. package/src/registry/components/ui/pagination.tsx +258 -0
  45. package/src/registry/components/ui/rating.tsx +185 -0
  46. package/src/registry/components/ui/review-card.tsx +195 -0
  47. package/src/registry/components/ui/scroll-area.tsx +2 -2
  48. package/src/registry/components/ui/sidebar.tsx +692 -0
  49. package/src/registry/components/ui/spinner.tsx +1 -1
  50. package/src/registry/components/ui/stat.tsx +245 -0
  51. package/src/registry/components/ui/table.tsx +160 -0
  52. package/src/registry/components/ui/timeline.tsx +350 -0
  53. package/src/registry/components/ui/toggle-group.tsx +203 -0
  54. package/src/registry/index.ts +3 -3
  55. package/src/registry/metadata.ts +141 -0
@@ -0,0 +1,350 @@
1
+ import {
2
+ createContext,
3
+ useContext,
4
+ splitProps,
5
+ type Component,
6
+ type JSX,
7
+ type Accessor,
8
+ } from "solid-js";
9
+ import { cva, type VariantProps } from "class-variance-authority";
10
+ import { cn } from "@/lib/cn";
11
+
12
+ /* --- Timeline Context --- */
13
+ interface TimelineContextValue {
14
+ orientation: Accessor<"vertical" | "horizontal">;
15
+ align: Accessor<"left" | "right" | "alternate">;
16
+ size: Accessor<"sm" | "default" | "lg">;
17
+ }
18
+
19
+ const TimelineContext = createContext<TimelineContextValue>();
20
+
21
+ export interface TimelineProps extends JSX.HTMLAttributes<HTMLOListElement> {
22
+ /** Layout orientation of the timeline */
23
+ orientation?: "vertical" | "horizontal";
24
+ /** Alignment of content relative to the timeline line */
25
+ align?: "left" | "right" | "alternate";
26
+ /** Sizing of dots and spacing */
27
+ size?: "sm" | "default" | "lg";
28
+ class?: string;
29
+ children?: JSX.Element;
30
+ }
31
+
32
+ /**
33
+ * Root container for chronological timeline event sequences.
34
+ */
35
+ export const Timeline: Component<TimelineProps> = (props) => {
36
+ const [local, rest] = splitProps(props, [
37
+ "orientation",
38
+ "align",
39
+ "size",
40
+ "class",
41
+ "children",
42
+ ]);
43
+
44
+ const orientation = () => local.orientation ?? "vertical";
45
+ const align = () => local.align ?? "left";
46
+ const size = () => local.size ?? "default";
47
+
48
+ const contextValue: TimelineContextValue = {
49
+ orientation,
50
+ align,
51
+ size,
52
+ };
53
+
54
+ return (
55
+ <TimelineContext.Provider value={contextValue}>
56
+ <ol
57
+ role="list"
58
+ data-orientation={orientation()}
59
+ data-align={align()}
60
+ class={cn(
61
+ "relative flex",
62
+ orientation() === "vertical"
63
+ ? "flex-col w-full"
64
+ : "flex-row w-full items-start",
65
+ local.class
66
+ )}
67
+ {...rest}
68
+ >
69
+ {local.children}
70
+ </ol>
71
+ </TimelineContext.Provider>
72
+ );
73
+ };
74
+
75
+ /* --- Timeline Item --- */
76
+ export interface TimelineItemProps extends JSX.HTMLAttributes<HTMLLIElement> {
77
+ class?: string;
78
+ children?: JSX.Element;
79
+ }
80
+
81
+ /**
82
+ * Individual event row container in the timeline.
83
+ */
84
+ export const TimelineItem: Component<TimelineItemProps> = (props) => {
85
+ const context = useContext(TimelineContext);
86
+ const [local, rest] = splitProps(props, ["class", "children"]);
87
+
88
+ const isVertical = () => !context || context.orientation() === "vertical";
89
+ const align = () => context?.align() ?? "left";
90
+
91
+ return (
92
+ <li
93
+ class={cn(
94
+ "group relative flex",
95
+ isVertical()
96
+ ? "min-h-[3.5rem] w-full items-start"
97
+ : "flex-1 min-w-0 flex-col items-start",
98
+ isVertical() && align() === "right" && "flex-row-reverse",
99
+ isVertical() && align() === "alternate" && "[&:nth-child(even)]:flex-row-reverse",
100
+ local.class
101
+ )}
102
+ {...rest}
103
+ >
104
+ {local.children}
105
+ </li>
106
+ );
107
+ };
108
+
109
+ /* --- Timeline Separator (Dot + Connector wrapper) --- */
110
+ export interface TimelineSeparatorProps extends JSX.HTMLAttributes<HTMLDivElement> {
111
+ class?: string;
112
+ }
113
+
114
+ export const TimelineSeparator: Component<TimelineSeparatorProps> = (props) => {
115
+ const context = useContext(TimelineContext);
116
+ const [local, rest] = splitProps(props, ["class", "children"]);
117
+
118
+ const isVertical = () => !context || context.orientation() === "vertical";
119
+
120
+ return (
121
+ <div
122
+ aria-hidden="true"
123
+ class={cn(
124
+ "flex shrink-0 relative",
125
+ isVertical()
126
+ ? "flex-col items-center self-stretch"
127
+ : "flex-row items-center w-full",
128
+ local.class
129
+ )}
130
+ {...rest}
131
+ >
132
+ {local.children}
133
+ </div>
134
+ );
135
+ };
136
+
137
+ /* --- Timeline Point / Dot --- */
138
+ export const timelinePointVariants = cva(
139
+ "relative z-10 flex shrink-0 items-center justify-center rounded-lg font-medium transition-all shadow-xs",
140
+ {
141
+ variants: {
142
+ variant: {
143
+ default: "border-2 bg-background",
144
+ solid: "text-primary-foreground",
145
+ subtle: "bg-muted text-muted-foreground",
146
+ outline: "border-2 border-border bg-card text-foreground",
147
+ },
148
+ status: {
149
+ default: "border-border text-foreground bg-card",
150
+ primary: "border-primary bg-primary text-primary-foreground",
151
+ success: "border-emerald-500 bg-emerald-500 text-white dark:border-emerald-400 dark:bg-emerald-400",
152
+ warning: "border-amber-500 bg-amber-500 text-white dark:border-amber-400 dark:bg-amber-400",
153
+ destructive: "border-destructive bg-destructive text-destructive-foreground",
154
+ muted: "border-border/60 bg-muted text-muted-foreground",
155
+ },
156
+ size: {
157
+ sm: "size-5 text-[10px] [&_svg]:size-3",
158
+ default: "size-8 text-xs [&_svg]:size-4",
159
+ lg: "size-10 text-sm [&_svg]:size-5",
160
+ },
161
+ },
162
+ defaultVariants: {
163
+ variant: "default",
164
+ status: "default",
165
+ size: "default",
166
+ },
167
+ }
168
+ );
169
+
170
+ export interface TimelinePointProps
171
+ extends JSX.HTMLAttributes<HTMLDivElement>,
172
+ VariantProps<typeof timelinePointVariants> {
173
+ class?: string;
174
+ children?: JSX.Element;
175
+ }
176
+
177
+ /**
178
+ * Status indicator node or icon container in the timeline separator.
179
+ */
180
+ export const TimelinePoint: Component<TimelinePointProps> = (props) => {
181
+ const context = useContext(TimelineContext);
182
+ const [local, rest] = splitProps(props, [
183
+ "variant",
184
+ "status",
185
+ "size",
186
+ "class",
187
+ "children",
188
+ ]);
189
+
190
+ const size = () => local.size || context?.size() || "default";
191
+
192
+ return (
193
+ <div
194
+ class={cn(
195
+ timelinePointVariants({
196
+ variant: local.variant,
197
+ status: local.status,
198
+ size: size(),
199
+ }),
200
+ local.class
201
+ )}
202
+ {...rest}
203
+ >
204
+ {local.children}
205
+ </div>
206
+ );
207
+ };
208
+
209
+ /* --- Timeline Connector (Line track) --- */
210
+ export interface TimelineConnectorProps extends JSX.HTMLAttributes<HTMLDivElement> {
211
+ dashed?: boolean;
212
+ class?: string;
213
+ }
214
+
215
+ /**
216
+ * Line track connecting sequential timeline items.
217
+ */
218
+ export const TimelineConnector: Component<TimelineConnectorProps> = (props) => {
219
+ const context = useContext(TimelineContext);
220
+ const [local, rest] = splitProps(props, ["dashed", "class"]);
221
+
222
+ const isVertical = () => !context || context.orientation() === "vertical";
223
+
224
+ return (
225
+ <div
226
+ aria-hidden="true"
227
+ class={cn(
228
+ "transition-colors group-last:hidden",
229
+ isVertical()
230
+ ? "w-0.5 flex-1 min-h-6 my-1 bg-border"
231
+ : "h-0.5 flex-1 min-w-4 mx-2 bg-border",
232
+ local.dashed && (
233
+ isVertical()
234
+ ? "border-l-2 border-dashed border-border bg-transparent w-0"
235
+ : "border-t-2 border-dashed border-border bg-transparent h-0"
236
+ ),
237
+ local.class
238
+ )}
239
+ {...rest}
240
+ />
241
+ );
242
+ };
243
+
244
+ /* --- Timeline Content (Event Details) --- */
245
+ export interface TimelineContentProps extends JSX.HTMLAttributes<HTMLDivElement> {
246
+ class?: string;
247
+ }
248
+
249
+ /**
250
+ * Primary container holding the description, title, and body for a timeline event.
251
+ */
252
+ export const TimelineContent: Component<TimelineContentProps> = (props) => {
253
+ const context = useContext(TimelineContext);
254
+ const [local, rest] = splitProps(props, ["class"]);
255
+
256
+ const isVertical = () => !context || context.orientation() === "vertical";
257
+ const align = () => context?.align() ?? "left";
258
+
259
+ return (
260
+ <div
261
+ class={cn(
262
+ "flex flex-col",
263
+ isVertical() ? "flex-1 pb-6 pt-0.5" : "pt-2 pr-2 text-left",
264
+ isVertical() && align() === "left" && "text-left pl-3.5 pr-0",
265
+ isVertical() && align() === "right" && "text-right pr-3.5 pl-0",
266
+ isVertical() && align() === "alternate" && "text-left pl-3.5 group-even:text-right group-even:pr-3.5 group-even:pl-0",
267
+ local.class
268
+ )}
269
+ {...rest}
270
+ />
271
+ );
272
+ };
273
+
274
+ /* --- Timeline Opposite Content --- */
275
+ export interface TimelineOppositeContentProps extends JSX.HTMLAttributes<HTMLDivElement> {
276
+ class?: string;
277
+ }
278
+
279
+ /**
280
+ * Content placed on the opposite side of the timeline separator (e.g. timestamps in alternate layouts).
281
+ */
282
+ export const TimelineOppositeContent: Component<TimelineOppositeContentProps> = (props) => {
283
+ const context = useContext(TimelineContext);
284
+ const [local, rest] = splitProps(props, ["class"]);
285
+
286
+ const isVertical = () => !context || context.orientation() === "vertical";
287
+ const align = () => context?.align() ?? "left";
288
+
289
+ return (
290
+ <div
291
+ class={cn(
292
+ "flex flex-col text-xs text-muted-foreground",
293
+ align() !== "alternate" && "hidden",
294
+ isVertical()
295
+ ? "flex-1 pb-6 pt-1 text-right pr-3.5 group-even:text-left group-even:pl-3.5 group-even:pr-0"
296
+ : "pb-1 pr-2",
297
+ local.class
298
+ )}
299
+ {...rest}
300
+ />
301
+ );
302
+ };
303
+
304
+ /* --- Timeline Title --- */
305
+ export interface TimelineTitleProps extends JSX.HTMLAttributes<HTMLHeadingElement> {
306
+ class?: string;
307
+ }
308
+
309
+ export const TimelineTitle: Component<TimelineTitleProps> = (props) => {
310
+ const [local, rest] = splitProps(props, ["class"]);
311
+
312
+ return (
313
+ <h4
314
+ class={cn("text-sm font-semibold tracking-tight text-foreground", local.class)}
315
+ {...rest}
316
+ />
317
+ );
318
+ };
319
+
320
+ /* --- Timeline Description --- */
321
+ export interface TimelineDescriptionProps extends JSX.HTMLAttributes<HTMLParagraphElement> {
322
+ class?: string;
323
+ }
324
+
325
+ export const TimelineDescription: Component<TimelineDescriptionProps> = (props) => {
326
+ const [local, rest] = splitProps(props, ["class"]);
327
+
328
+ return (
329
+ <p
330
+ class={cn("mt-1 text-sm text-muted-foreground", local.class)}
331
+ {...rest}
332
+ />
333
+ );
334
+ };
335
+
336
+ /* --- Timeline Time --- */
337
+ export interface TimelineTimeProps extends JSX.HTMLAttributes<HTMLTimeElement> {
338
+ class?: string;
339
+ }
340
+
341
+ export const TimelineTime: Component<TimelineTimeProps> = (props) => {
342
+ const [local, rest] = splitProps(props, ["class"]);
343
+
344
+ return (
345
+ <time
346
+ class={cn("text-xs font-medium text-muted-foreground/80", local.class)}
347
+ {...rest}
348
+ />
349
+ );
350
+ };
@@ -0,0 +1,203 @@
1
+ import {
2
+ createContext,
3
+ useContext,
4
+ splitProps,
5
+ type Component,
6
+ type JSX,
7
+ type Accessor,
8
+ } from "solid-js";
9
+ import { cva, type VariantProps } from "class-variance-authority";
10
+ import { createControllableSignal } from "@nikala-ui/hooks";
11
+ import { cn } from "@/lib/cn";
12
+
13
+ export const toggleGroupItemVariants = cva(
14
+ "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",
15
+ {
16
+ variants: {
17
+ variant: {
18
+ default: "bg-transparent",
19
+ outline:
20
+ "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",
21
+ },
22
+ size: {
23
+ default: "h-9 px-3 min-w-9",
24
+ sm: "h-8 px-2 text-xs min-w-8",
25
+ lg: "h-10 px-3 min-w-10",
26
+ },
27
+ },
28
+ defaultVariants: {
29
+ variant: "default",
30
+ size: "default",
31
+ },
32
+ }
33
+ );
34
+
35
+ interface ToggleGroupContextValue {
36
+ type: "single" | "multiple";
37
+ value: Accessor<any>;
38
+ onItemSelect: (itemValue: string) => void;
39
+ variant: Accessor<"default" | "outline">;
40
+ size: Accessor<"default" | "sm" | "lg">;
41
+ disabled: Accessor<boolean>;
42
+ }
43
+
44
+ const ToggleGroupContext = createContext<ToggleGroupContextValue>();
45
+
46
+ export interface ToggleGroupProps<T extends "single" | "multiple" = "single">
47
+ extends Omit<JSX.HTMLAttributes<HTMLDivElement>, "onChange">,
48
+ VariantProps<typeof toggleGroupItemVariants> {
49
+ /** Mode of selection: single choice or multiple choices */
50
+ type?: T;
51
+ /** Controlled value: string for 'single', string[] for 'multiple' */
52
+ value?: T extends "multiple" ? string[] : string;
53
+ /** Uncontrolled default value */
54
+ defaultValue?: T extends "multiple" ? string[] : string;
55
+ /** Callback fired when selection changes */
56
+ onChange?: (value: T extends "multiple" ? string[] : string) => void;
57
+ /** Layout orientation */
58
+ orientation?: "horizontal" | "vertical";
59
+ /** Disables all buttons in the group */
60
+ disabled?: boolean;
61
+ class?: string;
62
+ children?: JSX.Element;
63
+ }
64
+
65
+ /**
66
+ * Root container for grouping connected toggle items with shared single/multiple selection state.
67
+ */
68
+ export const ToggleGroup = <T extends "single" | "multiple" = "single">(
69
+ props: ToggleGroupProps<T>
70
+ ) => {
71
+ const [local, rest] = splitProps(props, [
72
+ "type",
73
+ "value",
74
+ "defaultValue",
75
+ "onChange",
76
+ "orientation",
77
+ "variant",
78
+ "size",
79
+ "disabled",
80
+ "class",
81
+ "children",
82
+ ]);
83
+
84
+ const type = () => local.type ?? ("single" as T);
85
+ const variant = () => local.variant ?? "default";
86
+ const size = () => local.size ?? "default";
87
+ const disabled = () => local.disabled ?? false;
88
+ const orientation = () => local.orientation ?? "horizontal";
89
+
90
+ const [currentValue, setCurrentValue] = createControllableSignal<any>({
91
+ value: () => local.value,
92
+ defaultValue: local.defaultValue ?? (type() === "multiple" ? [] : undefined),
93
+ onChange: (val) => local.onChange?.(val),
94
+ });
95
+
96
+ const onItemSelect = (itemValue: string) => {
97
+ if (disabled()) return;
98
+
99
+ if (type() === "multiple") {
100
+ const currentList = Array.isArray(currentValue()) ? currentValue() : [];
101
+ if (currentList.includes(itemValue)) {
102
+ setCurrentValue(currentList.filter((v: string) => v !== itemValue));
103
+ } else {
104
+ setCurrentValue([...currentList, itemValue]);
105
+ }
106
+ } else {
107
+ const current = currentValue();
108
+ if (current === itemValue) {
109
+ setCurrentValue(undefined);
110
+ } else {
111
+ setCurrentValue(itemValue);
112
+ }
113
+ }
114
+ };
115
+
116
+ const contextValue: ToggleGroupContextValue = {
117
+ type: type(),
118
+ value: currentValue,
119
+ onItemSelect,
120
+ variant,
121
+ size,
122
+ disabled,
123
+ };
124
+
125
+ return (
126
+ <ToggleGroupContext.Provider value={contextValue}>
127
+ <div
128
+ role="group"
129
+ data-orientation={orientation()}
130
+ class={cn(
131
+ "flex items-center justify-center gap-1 rounded-lg",
132
+ orientation() === "vertical" ? "flex-col" : "flex-row",
133
+ local.class
134
+ )}
135
+ {...rest}
136
+ >
137
+ {local.children}
138
+ </div>
139
+ </ToggleGroupContext.Provider>
140
+ );
141
+ };
142
+
143
+ export interface ToggleGroupItemProps
144
+ extends JSX.ButtonHTMLAttributes<HTMLButtonElement>,
145
+ VariantProps<typeof toggleGroupItemVariants> {
146
+ /** Unique value representing this toggle item within the group */
147
+ value: string;
148
+ class?: string;
149
+ children?: JSX.Element;
150
+ }
151
+
152
+ /**
153
+ * Individual toggle button item within a ToggleGroup.
154
+ */
155
+ export const ToggleGroupItem: Component<ToggleGroupItemProps> = (props) => {
156
+ const context = useContext(ToggleGroupContext);
157
+
158
+ if (!context) {
159
+ throw new Error("ToggleGroupItem must be used within a ToggleGroup");
160
+ }
161
+
162
+ const [local, rest] = splitProps(props, [
163
+ "value",
164
+ "variant",
165
+ "size",
166
+ "disabled",
167
+ "class",
168
+ "children",
169
+ ]);
170
+
171
+ const isSelected = () => {
172
+ const groupValue = context.value();
173
+ if (context.type === "multiple") {
174
+ return Array.isArray(groupValue) && groupValue.includes(local.value);
175
+ }
176
+ return groupValue === local.value;
177
+ };
178
+
179
+ const isDisabled = () => local.disabled || context.disabled();
180
+ const itemVariant = () => local.variant || context.variant();
181
+ const itemSize = () => local.size || context.size();
182
+
183
+ return (
184
+ <button
185
+ type="button"
186
+ role={context.type === "single" ? "radio" : "checkbox"}
187
+ aria-checked={isSelected()}
188
+ data-state={isSelected() ? "on" : "off"}
189
+ disabled={isDisabled()}
190
+ onClick={() => context.onItemSelect(local.value)}
191
+ class={cn(
192
+ toggleGroupItemVariants({
193
+ variant: itemVariant(),
194
+ size: itemSize(),
195
+ }),
196
+ local.class
197
+ )}
198
+ {...rest}
199
+ >
200
+ {local.children}
201
+ </button>
202
+ );
203
+ };
@@ -9,7 +9,7 @@ export interface RegistryFile {
9
9
  /** The full raw text content of the file */
10
10
  content: string;
11
11
  /** The category type of the file */
12
- type: "registry:ui" | "registry:util" | "registry:hook";
12
+ type: "registry:ui" | "registry:util" | "registry:hook" | "registry:block";
13
13
  }
14
14
 
15
15
  /**
@@ -23,7 +23,7 @@ export interface RegistryItem {
23
23
  /** Short summary describing the component */
24
24
  description: string;
25
25
  /** Component category type */
26
- type: "registry:ui" | "registry:util" | "registry:hook";
26
+ type: "registry:ui" | "registry:util" | "registry:hook" | "registry:block";
27
27
  /** Required NPM dependencies to be installed automatically (e.g., ["clsx", "tailwind-merge"]) */
28
28
  dependencies?: string[];
29
29
  /** Internal Nikala UI component dependencies required by this component (e.g., ["button"]) */
@@ -39,7 +39,7 @@ export interface RegistryIndexItem {
39
39
  name: string;
40
40
  title: string;
41
41
  description: string;
42
- type: "registry:ui" | "registry:util" | "registry:hook";
42
+ type: "registry:ui" | "registry:util" | "registry:hook" | "registry:block";
43
43
  dependencies?: string[];
44
44
  registryDependencies?: string[];
45
45
  }