@nikala-ui/core 0.11.0 → 0.12.1

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 (109) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +97 -20
  3. package/package.json +43 -2
  4. package/registry/api-table.json +21 -0
  5. package/registry/banner.json +1 -1
  6. package/registry/button.json +1 -1
  7. package/registry/callout.json +19 -0
  8. package/registry/checkbox.json +4 -1
  9. package/registry/code-block.json +26 -0
  10. package/registry/code-group.json +20 -0
  11. package/registry/combobox.json +1 -1
  12. package/registry/command.json +3 -2
  13. package/registry/component-viewer.json +25 -0
  14. package/registry/container.json +18 -0
  15. package/registry/context-menu.json +1 -1
  16. package/registry/create-app-updater.json +13 -0
  17. package/registry/create-document-tabs.json +13 -0
  18. package/registry/create-global-shortcut.json +13 -0
  19. package/registry/create-tauri-window.json +13 -0
  20. package/registry/create-tiptap-editor.json +34 -0
  21. package/registry/dialog.json +1 -1
  22. package/registry/dropdown-menu.json +3 -2
  23. package/registry/field.json +1 -1
  24. package/registry/file-tree.json +21 -0
  25. package/registry/footer.json +1 -1
  26. package/registry/form-message.json +3 -2
  27. package/registry/icon-button.json +1 -1
  28. package/registry/index.json +336 -5
  29. package/registry/navbar.json +1 -1
  30. package/registry/navigation-menu.json +1 -1
  31. package/registry/number-input.json +1 -1
  32. package/registry/package-manager-tabs.json +24 -0
  33. package/registry/pager.json +21 -0
  34. package/registry/popover.json +4 -1
  35. package/registry/resizable.json +1 -1
  36. package/registry/rich-text-editor.json +95 -0
  37. package/registry/scroll-area.json +1 -1
  38. package/registry/section-heading.json +21 -0
  39. package/registry/select.json +3 -2
  40. package/registry/sheet.json +1 -1
  41. package/registry/sidebar.json +1 -1
  42. package/registry/status.json +1 -1
  43. package/registry/steps.json +20 -0
  44. package/registry/switch.json +4 -1
  45. package/registry/table-of-contents.json +23 -0
  46. package/registry/tabs.json +4 -1
  47. package/registry/theme-manager.json +6 -5
  48. package/registry/titlebar-tabs.json +24 -0
  49. package/registry/titlebar.json +26 -0
  50. package/registry/toggle-group.json +1 -1
  51. package/registry/updater-modal.json +26 -0
  52. package/src/index.ts +48 -0
  53. package/src/registry/components/ui/api-table.tsx +117 -0
  54. package/src/registry/components/ui/banner.tsx +0 -2
  55. package/src/registry/components/ui/button.tsx +1 -1
  56. package/src/registry/components/ui/callout.tsx +137 -0
  57. package/src/registry/components/ui/checkbox.tsx +1 -1
  58. package/src/registry/components/ui/code-block.tsx +326 -0
  59. package/src/registry/components/ui/code-group.tsx +105 -0
  60. package/src/registry/components/ui/combobox.tsx +83 -18
  61. package/src/registry/components/ui/command.tsx +110 -106
  62. package/src/registry/components/ui/component-viewer.tsx +363 -0
  63. package/src/registry/components/ui/container.tsx +45 -0
  64. package/src/registry/components/ui/context-menu.tsx +67 -22
  65. package/src/registry/components/ui/dialog.tsx +42 -32
  66. package/src/registry/components/ui/dropdown-menu.tsx +69 -31
  67. package/src/registry/components/ui/field.tsx +7 -3
  68. package/src/registry/components/ui/file-tree.tsx +181 -0
  69. package/src/registry/components/ui/footer.tsx +1 -1
  70. package/src/registry/components/ui/form-message.tsx +2 -2
  71. package/src/registry/components/ui/icon-button.tsx +1 -1
  72. package/src/registry/components/ui/navbar.tsx +18 -11
  73. package/src/registry/components/ui/navigation-menu.tsx +2 -2
  74. package/src/registry/components/ui/number-input.tsx +3 -3
  75. package/src/registry/components/ui/package-manager-tabs.tsx +226 -0
  76. package/src/registry/components/ui/pager.tsx +102 -0
  77. package/src/registry/components/ui/popover.tsx +1 -1
  78. package/src/registry/components/ui/resizable.tsx +3 -1
  79. package/src/registry/components/ui/rich-text-editor/bubble-menu.tsx +236 -0
  80. package/src/registry/components/ui/rich-text-editor/editor.tsx +308 -0
  81. package/src/registry/components/ui/rich-text-editor/extensions.ts +116 -0
  82. package/src/registry/components/ui/rich-text-editor/footer.tsx +36 -0
  83. package/src/registry/components/ui/rich-text-editor/image-dialog.tsx +75 -0
  84. package/src/registry/components/ui/rich-text-editor/index.ts +11 -0
  85. package/src/registry/components/ui/rich-text-editor/link-dialog.tsx +48 -0
  86. package/src/registry/components/ui/rich-text-editor/markdown.ts +170 -0
  87. package/src/registry/components/ui/rich-text-editor/slash-command.tsx +301 -0
  88. package/src/registry/components/ui/rich-text-editor/table-controls.tsx +68 -0
  89. package/src/registry/components/ui/rich-text-editor/toolbar.tsx +465 -0
  90. package/src/registry/components/ui/rich-text-editor/use-editor.ts +231 -0
  91. package/src/registry/components/ui/scroll-area.tsx +12 -2
  92. package/src/registry/components/ui/section-heading.tsx +108 -0
  93. package/src/registry/components/ui/select.tsx +16 -19
  94. package/src/registry/components/ui/sheet.tsx +58 -53
  95. package/src/registry/components/ui/sidebar.tsx +4 -4
  96. package/src/registry/components/ui/status.tsx +7 -5
  97. package/src/registry/components/ui/steps.tsx +198 -0
  98. package/src/registry/components/ui/switch.tsx +1 -1
  99. package/src/registry/components/ui/table-of-contents.tsx +131 -0
  100. package/src/registry/components/ui/tabs.tsx +1 -1
  101. package/src/registry/components/ui/theme-toggle.tsx +175 -139
  102. package/src/registry/components/ui/titlebar-tabs.tsx +773 -0
  103. package/src/registry/components/ui/titlebar.tsx +468 -0
  104. package/src/registry/components/ui/toggle-group.tsx +1 -1
  105. package/src/registry/components/ui/updater-modal.tsx +254 -0
  106. package/src/registry/metadata.ts +167 -6
  107. package/src/registry/providers/theme-provider.tsx +16 -8
  108. package/src/registry/providers/theme-script.tsx +29 -9
  109. package/src/registry/providers/theme-transitions.ts +81 -50
@@ -1,30 +1,29 @@
1
1
  import { splitProps, type Component, type JSX, type ValidComponent, Show } from "solid-js";
2
2
  import * as DialogPrimitive from "@kobalte/core/dialog";
3
3
  import type { PolymorphicProps } from "@kobalte/core/polymorphic";
4
- import { ScrollArea } from "./scroll-area";
4
+ import { ScrollArea } from "@/components/ui/scroll-area";
5
5
  import { cn } from "@/lib/cn";
6
6
 
7
7
  export type DialogRootProps = DialogPrimitive.DialogRootProps;
8
8
 
9
9
  /**
10
- * Root Dialog container managing state and context.
10
+ * Root Dialog component managing modal open state.
11
11
  */
12
12
  export const Dialog: Component<DialogRootProps> = (props) => {
13
13
  return <DialogPrimitive.Root {...props} />;
14
14
  };
15
15
 
16
16
  export const DialogTrigger = DialogPrimitive.Trigger;
17
- export const DialogClose = DialogPrimitive.CloseButton;
18
17
 
19
18
  export type DialogOverlayProps<T extends ValidComponent = "div"> =
20
19
  DialogPrimitive.DialogOverlayProps<T> & {
21
- /** Whether to apply background backdrop blur (default: true) */
20
+ /** Whether to apply background blur effect (default: true) */
22
21
  blur?: boolean;
23
22
  class?: string;
24
23
  };
25
24
 
26
25
  /**
27
- * Darkened background overlay behind open dialog.
26
+ * Backdrop overlay wrapper with optional backdrop blur styling.
28
27
  */
29
28
  export const DialogOverlay = <T extends ValidComponent = "div">(
30
29
  props: PolymorphicProps<T, DialogOverlayProps<T>>
@@ -34,8 +33,8 @@ export const DialogOverlay = <T extends ValidComponent = "div">(
34
33
  return (
35
34
  <DialogPrimitive.Overlay
36
35
  class={cn(
37
- "fixed inset-0 z-50 transition-all duration-200 data-expanded:animate-in data-closed:animate-out data-[closed]:fade-out-0 data-[expanded]:fade-in-0",
38
- local.blur !== false ? "bg-black/80 backdrop-blur-sm" : "bg-black/80",
36
+ "fixed inset-0 z-50 bg-black/80 data-expanded:animate-in data-closed:animate-out data-[closed]:fade-out-0 data-[expanded]:fade-in-0",
37
+ local.blur !== false && "backdrop-blur-sm",
39
38
  local.class
40
39
  )}
41
40
  {...(rest as any)}
@@ -124,6 +123,9 @@ export interface DialogHeaderProps extends JSX.HTMLAttributes<HTMLDivElement> {
124
123
  class?: string;
125
124
  }
126
125
 
126
+ /**
127
+ * Dialog top header container.
128
+ */
127
129
  export const DialogHeader: Component<DialogHeaderProps> = (props) => {
128
130
  const [local, rest] = splitProps(props, ["class"]);
129
131
 
@@ -139,52 +141,60 @@ export interface DialogFooterProps extends JSX.HTMLAttributes<HTMLDivElement> {
139
141
  class?: string;
140
142
  }
141
143
 
144
+ /**
145
+ * Dialog bottom action buttons area.
146
+ */
142
147
  export const DialogFooter: Component<DialogFooterProps> = (props) => {
143
148
  const [local, rest] = splitProps(props, ["class"]);
144
149
 
145
150
  return (
146
151
  <div
147
- class={cn(
148
- "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2 pt-4",
149
- local.class
150
- )}
152
+ class={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", local.class)}
151
153
  {...rest}
152
154
  />
153
155
  );
154
156
  };
155
157
 
156
- export interface DialogTitleProps {
157
- class?: string;
158
- children?: JSX.Element;
159
- }
158
+ export type DialogTitleProps<T extends ValidComponent = "h2"> =
159
+ DialogPrimitive.DialogTitleProps<T> & {
160
+ class?: string;
161
+ };
160
162
 
161
- export const DialogTitle: Component<DialogTitleProps> = (props) => {
162
- const [local, rest] = splitProps(props, ["class", "children"]);
163
+ /**
164
+ * Dialog title heading.
165
+ */
166
+ export const DialogTitle = <T extends ValidComponent = "h2">(
167
+ props: PolymorphicProps<T, DialogTitleProps<T>>
168
+ ) => {
169
+ const [local, rest] = splitProps(props as DialogTitleProps, ["class"]);
163
170
 
164
171
  return (
165
172
  <DialogPrimitive.Title
166
173
  class={cn("text-lg font-semibold leading-none tracking-tight text-foreground", local.class)}
167
- {...rest}
168
- >
169
- {local.children}
170
- </DialogPrimitive.Title>
174
+ {...(rest as any)}
175
+ />
171
176
  );
172
177
  };
173
178
 
174
- export interface DialogDescriptionProps {
175
- class?: string;
176
- children?: JSX.Element;
177
- }
179
+ export type DialogDescriptionProps<T extends ValidComponent = "p"> =
180
+ DialogPrimitive.DialogDescriptionProps<T> & {
181
+ class?: string;
182
+ };
178
183
 
179
- export const DialogDescription: Component<DialogDescriptionProps> = (props) => {
180
- const [local, rest] = splitProps(props, ["class", "children"]);
184
+ /**
185
+ * Dialog subtitle or description text.
186
+ */
187
+ export const DialogDescription = <T extends ValidComponent = "p">(
188
+ props: PolymorphicProps<T, DialogDescriptionProps<T>>
189
+ ) => {
190
+ const [local, rest] = splitProps(props as DialogDescriptionProps, ["class"]);
181
191
 
182
192
  return (
183
193
  <DialogPrimitive.Description
184
194
  class={cn("text-sm text-muted-foreground", local.class)}
185
- {...rest}
186
- >
187
- {local.children}
188
- </DialogPrimitive.Description>
195
+ {...(rest as any)}
196
+ />
189
197
  );
190
- };
198
+ };
199
+
200
+ export const DialogClose = DialogPrimitive.CloseButton;
@@ -1,10 +1,12 @@
1
- import { splitProps, type Component, type JSX, type ValidComponent } from "solid-js";
1
+ import { splitProps, type Component, type JSX, type ValidComponent, createContext, useContext } from "solid-js";
2
2
  import * as DropdownMenuPrimitive from "@kobalte/core/dropdown-menu";
3
3
  import type { PolymorphicProps } from "@kobalte/core/polymorphic";
4
- import { createClickOutside } from "@nikala-ui/hooks";
5
- import { ScrollArea } from "./scroll-area";
4
+ import { createClickOutside } from "@/hooks/create-click-outside";
5
+ import { ScrollArea } from "@/components/ui/scroll-area";
6
6
  import { cn } from "@/lib/cn";
7
7
 
8
+ const DropdownGroupContext = createContext<boolean>(false);
9
+
8
10
  export type DropdownMenuRootProps = Omit<
9
11
  DropdownMenuPrimitive.DropdownMenuRootProps,
10
12
  "placement"
@@ -29,7 +31,15 @@ export const DropdownMenu: Component<DropdownMenuRootProps> = (props) => {
29
31
  };
30
32
 
31
33
  export const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
32
- export const DropdownMenuGroup = DropdownMenuPrimitive.Group;
34
+
35
+ export const DropdownMenuGroup: Component<DropdownMenuPrimitive.DropdownMenuGroupProps> = (props) => {
36
+ return (
37
+ <DropdownGroupContext.Provider value={true}>
38
+ <DropdownMenuPrimitive.Group {...props} />
39
+ </DropdownGroupContext.Provider>
40
+ );
41
+ };
42
+
33
43
  export const DropdownMenuSub = DropdownMenuPrimitive.Sub;
34
44
  export const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
35
45
 
@@ -120,7 +130,7 @@ export const DropdownMenuContent = <T extends ValidComponent = "div">(
120
130
  local.class
121
131
  )}
122
132
  >
123
- <ScrollArea class="max-h-72 w-full rounded-[inherit]">
133
+ <ScrollArea class="max-h-72 w-full">
124
134
  <div class="p-1">
125
135
  {local.children}
126
136
  </div>
@@ -134,18 +144,25 @@ export type DropdownMenuItemProps<T extends ValidComponent = "div"> =
134
144
  DropdownMenuPrimitive.DropdownMenuItemProps<T> & {
135
145
  class?: string;
136
146
  inset?: boolean;
147
+ variant?: "default" | "destructive";
137
148
  };
138
149
 
139
150
  export const DropdownMenuItem = <T extends ValidComponent = "div">(
140
151
  props: PolymorphicProps<T, DropdownMenuItemProps<T>>
141
152
  ) => {
142
- const [local, rest] = splitProps(props as DropdownMenuItemProps, ["class", "inset"]);
153
+ const [local, rest] = splitProps(props as DropdownMenuItemProps, [
154
+ "class",
155
+ "inset",
156
+ "variant",
157
+ ]);
143
158
 
144
159
  return (
145
160
  <DropdownMenuPrimitive.Item
146
161
  class={cn(
147
- "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 text-foreground",
162
+ "relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
148
163
  local.inset && "pl-8",
164
+ local.variant === "destructive" &&
165
+ "text-destructive focus:bg-destructive/15 focus:text-destructive",
149
166
  local.class
150
167
  )}
151
168
  {...(rest as any)}
@@ -157,26 +174,24 @@ export type DropdownMenuCheckboxItemProps<T extends ValidComponent = "div"> =
157
174
  DropdownMenuPrimitive.DropdownMenuCheckboxItemProps<T> & {
158
175
  class?: string;
159
176
  children?: JSX.Element;
160
- checked?: boolean;
161
177
  };
162
178
 
163
179
  export const DropdownMenuCheckboxItem = <T extends ValidComponent = "div">(
164
180
  props: PolymorphicProps<T, DropdownMenuCheckboxItemProps<T>>
165
181
  ) => {
166
- const [local, rest] = splitProps(props as DropdownMenuCheckboxItemProps, ["class", "children", "checked"]);
182
+ const [local, rest] = splitProps(props as DropdownMenuCheckboxItemProps, ["class", "children"]);
167
183
 
168
184
  return (
169
185
  <DropdownMenuPrimitive.CheckboxItem
170
- checked={local.checked}
171
186
  class={cn(
172
- "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 text-foreground",
187
+ "relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
173
188
  local.class
174
189
  )}
175
190
  {...(rest as any)}
176
191
  >
177
192
  <span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
178
193
  <DropdownMenuPrimitive.ItemIndicator>
179
- <svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
194
+ <svg class="h-4 w-4 fill-none stroke-current stroke-2" viewBox="0 0 24 24">
180
195
  <path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
181
196
  </svg>
182
197
  </DropdownMenuPrimitive.ItemIndicator>
@@ -200,16 +215,14 @@ export const DropdownMenuRadioItem = <T extends ValidComponent = "div">(
200
215
  return (
201
216
  <DropdownMenuPrimitive.RadioItem
202
217
  class={cn(
203
- "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 text-foreground",
218
+ "relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
204
219
  local.class
205
220
  )}
206
221
  {...(rest as any)}
207
222
  >
208
223
  <span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
209
224
  <DropdownMenuPrimitive.ItemIndicator>
210
- <svg class="h-2 w-2 fill-current" viewBox="0 0 8 8">
211
- <circle cx="4" cy="4" r="3" />
212
- </svg>
225
+ <span class="h-2 w-2 rounded-lg bg-current" />
213
226
  </DropdownMenuPrimitive.ItemIndicator>
214
227
  </span>
215
228
  {local.children}
@@ -217,7 +230,9 @@ export const DropdownMenuRadioItem = <T extends ValidComponent = "div">(
217
230
  );
218
231
  };
219
232
 
220
- export interface DropdownMenuLabelProps {
233
+ export const DropdownMenuGroupLabel = DropdownMenuPrimitive.GroupLabel;
234
+
235
+ export interface DropdownMenuLabelProps extends JSX.HTMLAttributes<HTMLDivElement> {
221
236
  class?: string;
222
237
  inset?: boolean;
223
238
  children?: JSX.Element;
@@ -225,36 +240,59 @@ export interface DropdownMenuLabelProps {
225
240
 
226
241
  export const DropdownMenuLabel: Component<DropdownMenuLabelProps> = (props) => {
227
242
  const [local, rest] = splitProps(props, ["class", "inset", "children"]);
243
+ const isInsideGroup = useContext(DropdownGroupContext);
244
+
245
+ const classes = () =>
246
+ cn(
247
+ "px-2 py-1.5 text-sm font-semibold text-foreground",
248
+ local.inset && "pl-8",
249
+ local.class
250
+ );
228
251
 
229
252
  return (
230
- <DropdownMenuPrimitive.GroupLabel
231
- class={cn("px-2 py-1.5 text-sm font-semibold text-foreground", local.inset && "pl-8", local.class)}
232
- {...rest}
233
- >
234
- {local.children}
235
- </DropdownMenuPrimitive.GroupLabel>
253
+ <>
254
+ {isInsideGroup ? (
255
+ <DropdownMenuPrimitive.GroupLabel class={classes()} {...(rest as any)}>
256
+ {local.children}
257
+ </DropdownMenuPrimitive.GroupLabel>
258
+ ) : (
259
+ <div class={classes()} {...rest}>
260
+ {local.children}
261
+ </div>
262
+ )}
263
+ </>
236
264
  );
237
265
  };
238
266
 
239
- export const DropdownMenuSeparator: Component<{ class?: string }> = (props) => {
267
+ export type DropdownMenuSeparatorProps<T extends ValidComponent = "hr"> =
268
+ DropdownMenuPrimitive.DropdownMenuSeparatorProps<T> & {
269
+ class?: string;
270
+ };
271
+
272
+ export const DropdownMenuSeparator = <T extends ValidComponent = "hr">(
273
+ props: PolymorphicProps<T, DropdownMenuSeparatorProps<T>>
274
+ ) => {
275
+ const [local, rest] = splitProps(props as DropdownMenuSeparatorProps, ["class"]);
276
+
240
277
  return (
241
278
  <DropdownMenuPrimitive.Separator
242
- class={cn("-mx-1 my-1 h-px bg-border", props.class)}
279
+ class={cn("-mx-1 my-1 h-px bg-border", local.class)}
280
+ {...(rest as any)}
243
281
  />
244
282
  );
245
283
  };
246
284
 
247
- export interface DropdownMenuShortcutProps {
285
+ export interface DropdownMenuShortcutProps extends JSX.HTMLAttributes<HTMLSpanElement> {
248
286
  class?: string;
249
- children?: JSX.Element;
250
287
  }
251
288
 
252
289
  export const DropdownMenuShortcut: Component<DropdownMenuShortcutProps> = (props) => {
253
- const [local, rest] = splitProps(props, ["class", "children"]);
290
+ const [local, rest] = splitProps(props, ["class"]);
254
291
 
255
292
  return (
256
- <span class={cn("ml-auto text-xs tracking-widest text-muted-foreground", local.class)} {...rest}>
257
- {local.children}
258
- </span>
293
+ <span
294
+ class={cn("ml-auto text-xs tracking-widest text-muted-foreground", local.class)}
295
+ {...rest}
296
+ />
259
297
  );
260
298
  };
@@ -1,6 +1,6 @@
1
1
  import { splitProps, type Component, type JSX } from "solid-js";
2
2
  import { cn } from "@/lib/cn";
3
- import { Label, type LabelProps } from "./label";
3
+ import { Label, type LabelProps } from "@/components/ui/label";
4
4
 
5
5
  export interface FieldProps extends JSX.HTMLAttributes<HTMLDivElement> {
6
6
  class?: string;
@@ -17,8 +17,9 @@ export const Field: Component<FieldProps> = (props) => {
17
17
  );
18
18
  };
19
19
 
20
- export interface FieldLabelProps extends LabelProps {
20
+ export interface FieldLabelProps extends JSX.LabelHTMLAttributes<HTMLLabelElement>, LabelProps {
21
21
  class?: string;
22
+ for?: string;
22
23
  children?: JSX.Element;
23
24
  }
24
25
 
@@ -41,7 +42,10 @@ export const FieldDescription: Component<FieldDescriptionProps> = (props) => {
41
42
  const [local, rest] = splitProps(props, ["class", "children"]);
42
43
 
43
44
  return (
44
- <p class={cn("text-sm text-muted-foreground", local.class)} {...rest}>
45
+ <p
46
+ class={cn("text-sm text-muted-foreground", local.class)}
47
+ {...rest}
48
+ >
45
49
  {local.children}
46
50
  </p>
47
51
  );
@@ -0,0 +1,181 @@
1
+ import {
2
+ createSignal,
3
+ splitProps,
4
+ Show,
5
+ type JSX,
6
+ type ParentComponent,
7
+ } from "solid-js";
8
+ import {
9
+ Folder,
10
+ FolderOpen,
11
+ File,
12
+ FileCode,
13
+ FileJson,
14
+ FileText,
15
+ ChevronRight,
16
+ } from "lucide-solid";
17
+ import {
18
+ Collapsible,
19
+ CollapsibleTrigger,
20
+ CollapsibleContent,
21
+ } from "@/components/ui/collapsible";
22
+ import { cn } from "@/lib/cn";
23
+
24
+ export interface FileTreeProps extends JSX.HTMLAttributes<HTMLDivElement> {
25
+ class?: string;
26
+ }
27
+
28
+ /**
29
+ * Root FileTree container for displaying project directory hierarchies.
30
+ */
31
+ export const FileTree: ParentComponent<FileTreeProps> = (props) => {
32
+ const [local, rest] = splitProps(props, ["class", "children"]);
33
+ return (
34
+ <div
35
+ class={cn(
36
+ "my-4 w-full rounded-lg border border-border bg-card/60 p-3 font-mono text-sm shadow-2xs select-none",
37
+ local.class
38
+ )}
39
+ {...rest}
40
+ >
41
+ <div class="space-y-0.5">{local.children}</div>
42
+ </div>
43
+ );
44
+ };
45
+
46
+ export interface FileTreeFolderProps {
47
+ /** Folder name */
48
+ name: string;
49
+ /** Initial open state of the folder (default: true) */
50
+ defaultOpen?: boolean;
51
+ /** Controlled open state */
52
+ open?: boolean;
53
+ /** Callback fired when open state changes */
54
+ onOpenChange?: (open: boolean) => void;
55
+ /** Custom folder icon override */
56
+ icon?: JSX.Element;
57
+ class?: string;
58
+ children?: JSX.Element;
59
+ }
60
+
61
+ /**
62
+ * Collapsible folder node in the FileTree.
63
+ */
64
+ export const FileTreeFolder: ParentComponent<FileTreeFolderProps> = (props) => {
65
+ const [local, rest] = splitProps(props, [
66
+ "name",
67
+ "defaultOpen",
68
+ "open",
69
+ "onOpenChange",
70
+ "icon",
71
+ "class",
72
+ "children",
73
+ ]);
74
+
75
+ const [isOpen, setIsOpen] = createSignal(local.defaultOpen ?? true);
76
+
77
+ const openState = () => (local.open !== undefined ? local.open : isOpen());
78
+
79
+ const handleToggle = (next: boolean) => {
80
+ if (local.open === undefined) {
81
+ setIsOpen(next);
82
+ }
83
+ local.onOpenChange?.(next);
84
+ };
85
+
86
+ return (
87
+ <Collapsible
88
+ open={openState()}
89
+ onOpenChange={handleToggle}
90
+ class={cn("w-full", local.class)}
91
+ {...rest}
92
+ >
93
+ <CollapsibleTrigger class="group flex w-full items-center justify-start gap-1.5 rounded-md px-1.5 py-1 text-xs text-foreground transition-colors hover:bg-muted/70 cursor-pointer">
94
+ <ChevronRight
95
+ class={cn(
96
+ "size-3.5 shrink-0 text-muted-foreground transition-transform duration-200",
97
+ openState() && "rotate-90 text-foreground"
98
+ )}
99
+ />
100
+ <Show
101
+ when={local.icon}
102
+ fallback={
103
+ <Show
104
+ when={openState()}
105
+ fallback={<Folder class="size-3.5 shrink-0 text-amber-500/90 dark:text-amber-400" />}
106
+ >
107
+ <FolderOpen class="size-3.5 shrink-0 text-amber-500 dark:text-amber-400" />
108
+ </Show>
109
+ }
110
+ >
111
+ {local.icon}
112
+ </Show>
113
+ <span class="font-medium text-foreground tracking-tight">{local.name}</span>
114
+ </CollapsibleTrigger>
115
+
116
+ <CollapsibleContent class="border-l border-border/60 ml-3.5 pl-3 pt-0.5 space-y-0.5">
117
+ {local.children}
118
+ </CollapsibleContent>
119
+ </Collapsible>
120
+ );
121
+ };
122
+
123
+ export interface FileTreeFileProps extends JSX.HTMLAttributes<HTMLDivElement> {
124
+ /** File name */
125
+ name: string;
126
+ /** Custom file icon override */
127
+ icon?: JSX.Element;
128
+ class?: string;
129
+ }
130
+
131
+ /**
132
+ * Returns a smart default Lucide icon based on file extension.
133
+ */
134
+ function getFileIcon(filename: string): JSX.Element {
135
+ const ext = filename.split(".").pop()?.toLowerCase();
136
+ switch (ext) {
137
+ case "tsx":
138
+ case "jsx":
139
+ case "ts":
140
+ case "js":
141
+ case "mjs":
142
+ case "rs":
143
+ case "py":
144
+ case "go":
145
+ return <FileCode class="size-3.5 shrink-0 text-sky-500 dark:text-sky-400" />;
146
+ case "json":
147
+ return <FileJson class="size-3.5 shrink-0 text-amber-500 dark:text-amber-400" />;
148
+ case "md":
149
+ case "mdx":
150
+ case "txt":
151
+ return <FileText class="size-3.5 shrink-0 text-emerald-500 dark:text-emerald-400" />;
152
+ default:
153
+ return <File class="size-3.5 shrink-0 text-muted-foreground" />;
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Leaf file item inside a FileTree.
159
+ */
160
+ export const FileTreeFile: ParentComponent<FileTreeFileProps> = (props) => {
161
+ const [local, rest] = splitProps(props, ["name", "icon", "class", "children"]);
162
+
163
+ return (
164
+ <div
165
+ class={cn(
166
+ "flex w-full items-center justify-start gap-1.5 rounded-md px-1.5 py-1 text-xs text-muted-foreground transition-colors hover:bg-muted/50 hover:text-foreground cursor-default",
167
+ local.class
168
+ )}
169
+ {...rest}
170
+ >
171
+ <span class="size-3.5 shrink-0" />
172
+ <Show when={local.icon} fallback={getFileIcon(local.name)}>
173
+ {local.icon}
174
+ </Show>
175
+ <span class="tracking-tight">{local.name}</span>
176
+ <Show when={local.children}>
177
+ <div class="ml-auto">{local.children}</div>
178
+ </Show>
179
+ </div>
180
+ );
181
+ };
@@ -1,4 +1,4 @@
1
- import { splitProps, type JSX, type ParentComponent } from "solid-js";
1
+ import { splitProps, type JSX, type ParentComponent, type Component } from "solid-js";
2
2
  import { cva, type VariantProps } from "class-variance-authority";
3
3
  import { cn } from "@/lib/cn";
4
4
 
@@ -1,6 +1,6 @@
1
1
  import { Show, splitProps, type JSX } from "solid-js";
2
- import type { CreateFormReturn } from "@nikala-ui/hooks";
3
- import { FieldError, type FieldErrorProps } from "./field";
2
+ import type { CreateFormReturn } from "@/hooks/create-form";
3
+ import { FieldError, type FieldErrorProps } from "@/components/ui/field";
4
4
 
5
5
  type FormState<T extends Record<string, any>> = Pick<
6
6
  CreateFormReturn<T>,
@@ -1,5 +1,5 @@
1
1
  import { splitProps, type Component, type JSX } from "solid-js";
2
- import { Button, type ButtonProps } from "./button";
2
+ import { Button, type ButtonProps } from "@/components/ui/button";
3
3
  import { cn } from "@/lib/cn";
4
4
 
5
5
  export interface IconButtonProps
@@ -8,7 +8,9 @@ import {
8
8
  type ParentComponent,
9
9
  type Component,
10
10
  type Accessor,
11
+ type ValidComponent,
11
12
  } from "solid-js";
13
+ import { Dynamic } from "solid-js/web";
12
14
  import { cva, type VariantProps } from "class-variance-authority";
13
15
  import { Menu, X } from "lucide-solid";
14
16
  import { cn } from "@/lib/cn";
@@ -145,15 +147,18 @@ export const NavbarContainer: ParentComponent<NavbarContainerProps> = (props) =>
145
147
  };
146
148
 
147
149
  /* --- Navbar Brand / Logo Container --- */
148
- export interface NavbarBrandProps extends JSX.AnchorHTMLAttributes<HTMLAnchorElement> {
150
+ export interface NavbarBrandProps<T extends ValidComponent = "a"> extends JSX.AnchorHTMLAttributes<HTMLAnchorElement> {
149
151
  class?: string;
152
+ as?: T;
153
+ href?: string;
150
154
  }
151
155
 
152
- export const NavbarBrand: ParentComponent<NavbarBrandProps> = (props) => {
153
- const [local, rest] = splitProps(props, ["class", "children"]);
156
+ export const NavbarBrand = <T extends ValidComponent = "a">(props: NavbarBrandProps<T>) => {
157
+ const [local, rest] = splitProps(props as NavbarBrandProps, ["class", "as", "children"]);
154
158
 
155
159
  return (
156
- <a
160
+ <Dynamic
161
+ component={local.as || "a"}
157
162
  class={cn(
158
163
  "flex items-center gap-2 font-bold text-foreground transition-opacity hover:opacity-90 cursor-pointer shrink-0",
159
164
  local.class
@@ -161,7 +166,7 @@ export const NavbarBrand: ParentComponent<NavbarBrandProps> = (props) => {
161
166
  {...rest}
162
167
  >
163
168
  {local.children}
164
- </a>
169
+ </Dynamic>
165
170
  );
166
171
  };
167
172
 
@@ -193,7 +198,6 @@ export const NavbarContent: ParentComponent<NavbarContentProps> = (props) => {
193
198
  <div
194
199
  class={cn(
195
200
  navbarContentVariants({ justify: local.justify }),
196
- local.hideOnMobile !== false && "hidden md:flex",
197
201
  local.class
198
202
  )}
199
203
  {...rest}
@@ -248,18 +252,21 @@ export const navbarLinkVariants = cva(
248
252
  }
249
253
  );
250
254
 
251
- export interface NavbarLinkProps
255
+ export interface NavbarLinkProps<T extends ValidComponent = "a">
252
256
  extends JSX.AnchorHTMLAttributes<HTMLAnchorElement>,
253
257
  VariantProps<typeof navbarLinkVariants> {
254
258
  class?: string;
255
259
  isActive?: boolean;
260
+ as?: T;
261
+ href?: string;
256
262
  }
257
263
 
258
- export const NavbarLink: ParentComponent<NavbarLinkProps> = (props) => {
259
- const [local, rest] = splitProps(props, ["class", "variant", "isActive", "children"]);
264
+ export const NavbarLink = <T extends ValidComponent = "a">(props: NavbarLinkProps<T>) => {
265
+ const [local, rest] = splitProps(props as NavbarLinkProps, ["class", "variant", "isActive", "as", "children"]);
260
266
 
261
267
  return (
262
- <a
268
+ <Dynamic
269
+ component={local.as || "a"}
263
270
  class={cn(
264
271
  navbarLinkVariants({
265
272
  variant: local.isActive ? "active" : local.variant,
@@ -269,7 +276,7 @@ export const NavbarLink: ParentComponent<NavbarLinkProps> = (props) => {
269
276
  {...rest}
270
277
  >
271
278
  {local.children}
272
- </a>
279
+ </Dynamic>
273
280
  );
274
281
  };
275
282