@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
@@ -9,14 +9,14 @@ import {
9
9
  type JSX,
10
10
  type Accessor,
11
11
  } from "solid-js";
12
- import { createKeybindings } from "@nikala-ui/hooks";
12
+ import { createKeybindings } from "@/hooks/create-keybindings";
13
+ import { Dialog } from "@kobalte/core/dialog";
13
14
  import { Search, ArrowUp, ArrowDown, CornerDownLeft } from "lucide-solid";
14
15
  import { cn } from "@/lib/cn";
15
- import { Kbd, KbdGroup } from "./kbd";
16
- import { InputGroup, InputGroupInput, InputGroupAddon } from "./input-group";
17
- import { List, ListGroup, ListHeader, ListItem, type ListItemProps } from "./list";
18
- import { Dialog, DialogOverlay, DialogContent } from "./dialog";
19
- import { ScrollArea } from "./scroll-area";
16
+ import { Kbd, KbdGroup } from "../ui/kbd";
17
+ import { InputGroup, InputGroupInput, InputGroupAddon } from "../ui/input-group";
18
+ import { List, ListGroup, ListHeader, ListItem, type ListItemProps } from "../ui/list";
19
+ import { ScrollArea } from "@/components/ui/scroll-area";
20
20
 
21
21
  /* --- Command Context State --- */
22
22
  interface CommandContextValue {
@@ -24,8 +24,8 @@ interface CommandContextValue {
24
24
  setSearch: (value: string) => void;
25
25
  activeIndex: Accessor<number>;
26
26
  setActiveIndex: (fn: (prev: number) => number) => void;
27
- registerItemIndex: () => number;
28
- onItemSelect: (fn: () => void) => void;
27
+ registerItemIndex: (element: HTMLElement) => number;
28
+ onItemSelect: (index: number, action?: () => void) => void;
29
29
  }
30
30
 
31
31
  const CommandContext = createContext<CommandContextValue>();
@@ -33,12 +33,12 @@ const CommandContext = createContext<CommandContextValue>();
33
33
  export const useCommand = () => {
34
34
  const ctx = useContext(CommandContext);
35
35
  if (!ctx) {
36
- throw new Error("useCommand must be used within a <Command /> root component.");
36
+ throw new Error("useCommand must be used within a <Command />");
37
37
  }
38
38
  return ctx;
39
39
  };
40
40
 
41
- /* --- Command Root --- */
41
+ /* --- Root Command Container --- */
42
42
  export interface CommandProps extends Omit<JSX.HTMLAttributes<HTMLDivElement>, "children"> {
43
43
  class?: string;
44
44
  children?: JSX.Element | ((ctx: CommandContextValue) => JSX.Element);
@@ -48,32 +48,56 @@ export const Command: Component<CommandProps> = (props) => {
48
48
  const [local, rest] = splitProps(props, ["class", "children"]);
49
49
  const [search, setSearch] = createSignal("");
50
50
  const [activeIndex, setActiveIndex] = createSignal(0);
51
- let itemCounter = 0;
52
- const itemCallbacks: Record<number, () => void> = {};
51
+
53
52
  let containerRef: HTMLDivElement | undefined;
53
+ let itemsList: HTMLElement[] = [];
54
+
55
+ const registerItemIndex = (element: HTMLElement) => {
56
+ if (!itemsList.includes(element)) {
57
+ itemsList.push(element);
58
+ }
59
+ return itemsList.indexOf(element);
60
+ };
54
61
 
55
- const registerItemIndex = () => {
56
- const idx = itemCounter;
57
- itemCounter += 1;
58
- return idx;
62
+ const onItemSelect = (index: number, action?: () => void) => {
63
+ setActiveIndex(index);
64
+ if (action) action();
59
65
  };
60
66
 
61
- const onItemSelect = (fn: () => void) => {
62
- itemCallbacks[activeIndex()] = fn;
67
+ const getVisibleItems = () => {
68
+ if (!containerRef) return [];
69
+ return Array.from(containerRef.querySelectorAll<HTMLElement>("[data-command-item]:not(.hidden)"));
70
+ };
71
+
72
+ const updateActiveAttribute = (index: number) => {
73
+ const visible = getVisibleItems();
74
+ visible.forEach((el, idx) => {
75
+ if (idx === index) {
76
+ el.setAttribute("aria-selected", "true");
77
+ el.scrollIntoView({ block: "nearest" });
78
+ } else {
79
+ el.removeAttribute("aria-selected");
80
+ }
81
+ });
63
82
  };
64
83
 
65
84
  const handleKeyDown = (e: KeyboardEvent) => {
85
+ const visible = getVisibleItems();
86
+ if (visible.length === 0) return;
87
+
66
88
  if (e.key === "ArrowDown") {
67
89
  e.preventDefault();
68
- setActiveIndex((prev) => Math.min(prev + 1, Math.max(0, itemCounter - 1)));
90
+ const next = (activeIndex() + 1) % visible.length;
91
+ setActiveIndex(next);
92
+ updateActiveAttribute(next);
69
93
  } else if (e.key === "ArrowUp") {
70
94
  e.preventDefault();
71
- setActiveIndex((prev) => Math.max(prev - 1, 0));
95
+ const prev = (activeIndex() - 1 + visible.length) % visible.length;
96
+ setActiveIndex(prev);
97
+ updateActiveAttribute(prev);
72
98
  } else if (e.key === "Enter") {
73
99
  e.preventDefault();
74
- const fn = itemCallbacks[activeIndex()];
75
- if (fn) fn();
76
- const current = containerRef?.querySelector('[data-command-active="true"]') as HTMLElement;
100
+ const current = visible[activeIndex()];
77
101
  if (current) {
78
102
  current.click();
79
103
  }
@@ -144,12 +168,14 @@ export const CommandDialog: Component<CommandDialogProps> = (props) => {
144
168
 
145
169
  return (
146
170
  <Dialog open={isOpen()} onOpenChange={setOpen}>
147
- <DialogOverlay class="fixed inset-0 z-50 bg-black/60 backdrop-blur-xs data-expanded:animate-in data-closed:animate-out data-[closed]:fade-out-0 data-[expanded]:fade-in-0" />
148
- <div class="fixed inset-0 z-50 flex items-start justify-center pt-16 sm:pt-24 px-4">
149
- <DialogContent class="w-full max-w-xl rounded-lg border border-border bg-popover text-popover-foreground shadow-2xl outline-none 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 p-0">
150
- <Command class={props.class}>{props.children}</Command>
151
- </DialogContent>
152
- </div>
171
+ <Dialog.Portal>
172
+ <Dialog.Overlay class="fixed inset-0 z-50 bg-black/60 backdrop-blur-xs data-expanded:animate-in data-closed:animate-out data-[closed]:fade-out-0 data-[expanded]:fade-in-0" />
173
+ <div class="fixed inset-0 z-50 flex items-start justify-center pt-16 sm:pt-24 px-4">
174
+ <Dialog.Content class="w-full max-w-xl rounded-lg border border-border bg-popover text-popover-foreground shadow-2xl outline-none 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">
175
+ <Command class={props.class}>{props.children}</Command>
176
+ </Dialog.Content>
177
+ </div>
178
+ </Dialog.Portal>
153
179
  </Dialog>
154
180
  );
155
181
  };
@@ -171,7 +197,6 @@ export const CommandInput: Component<CommandInputProps> = (props) => {
171
197
  </InputGroupAddon>
172
198
 
173
199
  <InputGroupInput
174
- ref={(el) => setTimeout(() => el.focus(), 50)}
175
200
  value={search()}
176
201
  onInput={(e) => {
177
202
  setSearch(e.currentTarget.value);
@@ -248,7 +273,9 @@ export const CommandGroup: Component<CommandGroupProps> = (props) => {
248
273
  /* --- Command Item --- */
249
274
  export interface CommandItemProps extends ListItemProps {
250
275
  keywords?: string[];
276
+ description?: string;
251
277
  onSelect?: () => void;
278
+ shouldFilter?: boolean;
252
279
  }
253
280
 
254
281
  export const CommandItem: Component<CommandItemProps> = (props) => {
@@ -256,77 +283,62 @@ export const CommandItem: Component<CommandItemProps> = (props) => {
256
283
  const [local, rest] = splitProps(props, [
257
284
  "title",
258
285
  "subtitle",
286
+ "description",
259
287
  "keywords",
260
- "disabled",
261
288
  "onSelect",
289
+ "onClick",
290
+ "shouldFilter",
262
291
  "class",
263
- "children",
264
292
  ]);
293
+ const { search } = useCommand();
265
294
 
266
- const ctx = useCommand();
267
- const index = ctx.registerItemIndex();
268
-
269
- const isActive = () => ctx.activeIndex() === index;
270
-
271
- /* Automatic scroll into view when navigating with Arrow keys */
272
- const scrollIntoViewIfNeeded = () => {
273
- if (isActive() && itemRef) {
274
- itemRef.scrollIntoView({ block: "nearest", behavior: "smooth" });
275
- }
276
- };
277
-
278
- const isVisible = () => {
279
- const query = ctx.search().toLowerCase().trim();
295
+ /* Auto filter item based on search query */
296
+ const matchesSearch = () => {
297
+ if (local.shouldFilter === false) return true;
298
+ const query = search().toLowerCase().trim();
280
299
  if (!query) return true;
281
300
 
282
- const titleStr = typeof local.title === "string" ? local.title.toLowerCase() : "";
283
- const subStr = typeof local.subtitle === "string" ? local.subtitle.toLowerCase() : "";
284
-
285
- if (titleStr.includes(query) || subStr.includes(query)) return true;
286
-
287
- if (local.keywords) {
288
- return local.keywords.some((k) => k.toLowerCase().includes(query));
289
- }
301
+ const titleMatch = local.title?.toLowerCase().includes(query);
302
+ const subtitleMatch = local.subtitle?.toLowerCase().includes(query);
303
+ const descMatch = local.description?.toLowerCase().includes(query);
304
+ const keywordMatch = local.keywords?.some((k) =>
305
+ k.toLowerCase().includes(query)
306
+ );
290
307
 
291
- return false;
308
+ return Boolean(titleMatch || subtitleMatch || descMatch || keywordMatch);
292
309
  };
293
310
 
294
- const handleSelect = () => {
295
- if (local.disabled) return;
296
- if (local.onSelect) local.onSelect();
311
+ const handleClick = (e: MouseEvent) => {
312
+ if (typeof local.onSelect === "function") {
313
+ local.onSelect();
314
+ }
315
+ if (typeof local.onClick === "function") {
316
+ local.onClick(e as any);
317
+ }
297
318
  };
298
319
 
299
320
  return (
300
- <Show when={isVisible()}>
321
+ <Show when={matchesSearch()}>
301
322
  <ListItem
302
- ref={(el) => {
303
- itemRef = el;
304
- scrollIntoViewIfNeeded();
305
- }}
323
+ ref={itemRef}
324
+ data-command-item="true"
306
325
  title={local.title}
307
- subtitle={local.subtitle}
308
- data-command-active={isActive() ? "true" : "false"}
326
+ subtitle={local.subtitle || local.description}
327
+ onClick={handleClick}
309
328
  class={cn(
310
- "relative flex cursor-pointer select-none items-center rounded-md px-2 py-1.5 text-sm outline-none transition-colors",
311
- isActive()
312
- ? "bg-accent text-accent-foreground font-medium"
313
- : "text-popover-foreground hover:bg-muted/50",
314
- local.disabled && "pointer-events-none opacity-50",
329
+ "aria-selected:bg-accent aria-selected:text-accent-foreground cursor-pointer transition-colors hover:bg-accent hover:text-accent-foreground",
315
330
  local.class
316
331
  )}
317
- onClick={handleSelect}
318
- onMouseEnter={() => ctx.setActiveIndex(() => index)}
319
332
  {...rest}
320
- >
321
- {local.children}
322
- </ListItem>
333
+ />
323
334
  </Show>
324
335
  );
325
336
  };
326
337
 
327
- /* --- Command Footer Indicator Bar --- */
338
+ /* --- Command Footer --- */
328
339
  export interface CommandFooterProps extends JSX.HTMLAttributes<HTMLDivElement> {
329
340
  class?: string;
341
+ children?: JSX.Element;
330
342
  }
331
343
 
332
344
  export const CommandFooter: Component<CommandFooterProps> = (props) => {
@@ -335,40 +347,32 @@ export const CommandFooter: Component<CommandFooterProps> = (props) => {
335
347
  return (
336
348
  <div
337
349
  class={cn(
338
- "flex items-center justify-between border-t border-border px-3 py-2 text-xs text-muted-foreground bg-muted/40",
350
+ "flex items-center justify-between border-t border-border bg-muted/30 px-3 py-2 text-xs text-muted-foreground select-none",
339
351
  local.class
340
352
  )}
341
353
  {...rest}
342
354
  >
343
- {local.children || (
344
- <>
345
- <div class="flex items-center gap-2">
346
- <span class="inline-flex items-center gap-1">
347
- <KbdGroup>
348
- <Kbd size="sm">
349
- <ArrowUp class="w-2.5 h-2.5" />
350
- </Kbd>
351
- <Kbd size="sm">
352
- <ArrowDown class="w-2.5 h-2.5" />
353
- </Kbd>
354
- </KbdGroup>
355
- <span>Navigate</span>
356
- </span>
357
-
358
- <span class="inline-flex items-center gap-1">
359
- <Kbd size="sm">
360
- <CornerDownLeft class="w-2.5 h-2.5" />
361
- </Kbd>
362
- <span>Select</span>
363
- </span>
364
- </div>
365
-
366
- <span class="inline-flex items-center gap-1">
367
- <Kbd size="sm">ESC</Kbd>
368
- <span>Close</span>
369
- </span>
370
- </>
371
- )}
355
+ <div class="flex items-center gap-3">
356
+ <span class="flex items-center gap-1">
357
+ <KbdGroup>
358
+ <Kbd size="sm"><ArrowUp class="w-2.5 h-2.5" /></Kbd>
359
+ <Kbd size="sm"><ArrowDown class="w-2.5 h-2.5" /></Kbd>
360
+ </KbdGroup>
361
+ <span>Navigate</span>
362
+ </span>
363
+
364
+ <span class="flex items-center gap-1">
365
+ <Kbd size="sm"><CornerDownLeft class="w-2.5 h-2.5" /></Kbd>
366
+ <span>Select</span>
367
+ </span>
368
+
369
+ <span class="flex items-center gap-1">
370
+ <Kbd size="sm">Esc</Kbd>
371
+ <span>Close</span>
372
+ </span>
373
+ </div>
374
+
375
+ {local.children}
372
376
  </div>
373
377
  );
374
378
  };