@nikala-ui/core 0.10.1 → 0.11.0-nightly.b01fa01
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/package.json +1 -1
- package/registry/banner.json +1 -1
- package/registry/bubble.json +18 -0
- package/registry/button-group.json +20 -0
- package/registry/button.json +1 -1
- package/registry/checkbox.json +4 -1
- package/registry/combobox.json +1 -1
- package/registry/command.json +3 -2
- package/registry/context-menu.json +1 -1
- package/registry/create-app-updater.json +13 -0
- package/registry/create-chat-scroll.json +13 -0
- package/registry/create-document-tabs.json +13 -0
- package/registry/create-drop-zone.json +13 -0
- package/registry/create-global-shortcut.json +13 -0
- package/registry/create-pagination.json +13 -0
- package/registry/create-tauri-window.json +13 -0
- package/registry/dialog.json +1 -1
- package/registry/dropdown-menu.json +3 -2
- package/registry/dropzone.json +21 -0
- package/registry/field.json +1 -1
- package/registry/footer.json +18 -0
- package/registry/forgot-password-01.json +28 -0
- package/registry/form-message.json +3 -2
- package/registry/hero-01.json +22 -0
- package/registry/icon-button.json +1 -1
- package/registry/index.json +432 -5
- package/registry/login-01.json +28 -0
- package/registry/marker.json +17 -0
- package/registry/marquee.json +17 -0
- package/registry/message.json +17 -0
- package/registry/navbar.json +19 -0
- package/registry/navigation-menu.json +22 -0
- package/registry/number-input.json +1 -1
- package/registry/otp-verification-01.json +26 -0
- package/registry/pagination.json +22 -0
- package/registry/popover.json +4 -1
- package/registry/rating.json +19 -0
- package/registry/register-01.json +31 -0
- package/registry/resizable.json +1 -1
- package/registry/review-card.json +23 -0
- package/registry/scroll-area.json +1 -1
- package/registry/select.json +3 -2
- package/registry/sheet.json +1 -1
- package/registry/sidebar.json +24 -0
- package/registry/spinner.json +1 -1
- package/registry/stat.json +19 -0
- package/registry/status.json +1 -1
- package/registry/switch.json +4 -1
- package/registry/table.json +17 -0
- package/registry/tabs.json +4 -1
- package/registry/theme-manager.json +4 -3
- package/registry/timeline.json +18 -0
- package/registry/titlebar-tabs.json +24 -0
- package/registry/titlebar.json +26 -0
- package/registry/toggle-group.json +21 -0
- package/registry/updater-modal.json +26 -0
- package/src/registry/blocks/forgot-password-01.tsx +141 -0
- package/src/registry/blocks/hero-01.tsx +28 -0
- package/src/registry/blocks/login-01.tsx +231 -0
- package/src/registry/blocks/otp-verification-01.tsx +170 -0
- package/src/registry/blocks/register-01.tsx +318 -0
- package/src/registry/components/ui/banner.tsx +0 -2
- package/src/registry/components/ui/bubble.tsx +142 -0
- package/src/registry/components/ui/button-group.tsx +42 -0
- package/src/registry/components/ui/button.tsx +1 -1
- package/src/registry/components/ui/checkbox.tsx +1 -1
- package/src/registry/components/ui/combobox.tsx +83 -18
- package/src/registry/components/ui/command.tsx +101 -105
- package/src/registry/components/ui/context-menu.tsx +67 -22
- package/src/registry/components/ui/dialog.tsx +42 -32
- package/src/registry/components/ui/dropdown-menu.tsx +69 -31
- package/src/registry/components/ui/dropzone.tsx +189 -0
- package/src/registry/components/ui/field.tsx +7 -3
- package/src/registry/components/ui/footer.tsx +247 -0
- package/src/registry/components/ui/form-message.tsx +2 -2
- package/src/registry/components/ui/icon-button.tsx +1 -1
- package/src/registry/components/ui/marker.tsx +102 -0
- package/src/registry/components/ui/marquee.tsx +118 -0
- package/src/registry/components/ui/message.tsx +168 -0
- package/src/registry/components/ui/navbar.tsx +375 -0
- package/src/registry/components/ui/navigation-menu.tsx +358 -0
- package/src/registry/components/ui/number-input.tsx +3 -3
- package/src/registry/components/ui/pagination.tsx +258 -0
- package/src/registry/components/ui/popover.tsx +1 -1
- package/src/registry/components/ui/rating.tsx +185 -0
- package/src/registry/components/ui/resizable.tsx +3 -1
- package/src/registry/components/ui/review-card.tsx +195 -0
- package/src/registry/components/ui/scroll-area.tsx +13 -3
- package/src/registry/components/ui/select.tsx +16 -19
- package/src/registry/components/ui/sheet.tsx +58 -53
- package/src/registry/components/ui/sidebar.tsx +692 -0
- package/src/registry/components/ui/spinner.tsx +1 -1
- package/src/registry/components/ui/stat.tsx +245 -0
- package/src/registry/components/ui/status.tsx +7 -5
- package/src/registry/components/ui/switch.tsx +1 -1
- package/src/registry/components/ui/table.tsx +160 -0
- package/src/registry/components/ui/tabs.tsx +1 -1
- package/src/registry/components/ui/theme-toggle.tsx +80 -79
- package/src/registry/components/ui/timeline.tsx +350 -0
- package/src/registry/components/ui/titlebar-tabs.tsx +412 -0
- package/src/registry/components/ui/titlebar.tsx +399 -0
- package/src/registry/components/ui/toggle-group.tsx +203 -0
- package/src/registry/components/ui/updater-modal.tsx +254 -0
- package/src/registry/index.ts +3 -3
- package/src/registry/metadata.ts +184 -5
- package/src/registry/providers/theme-transitions.ts +81 -50
|
@@ -1,44 +1,88 @@
|
|
|
1
1
|
import { splitProps, type JSX, type ValidComponent } from "solid-js";
|
|
2
2
|
import { Check, ChevronDown, X } from "lucide-solid";
|
|
3
|
-
import { ScrollArea } from "
|
|
3
|
+
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
4
4
|
import * as ComboboxPrimitive from "@kobalte/core/combobox";
|
|
5
5
|
import { cn } from "@/lib/cn";
|
|
6
6
|
|
|
7
7
|
export type ComboboxRootProps<Option = any, OptGroup = any, T extends ValidComponent = "div"> =
|
|
8
|
-
ComboboxPrimitive.ComboboxRootProps<Option, OptGroup, T
|
|
8
|
+
ComboboxPrimitive.ComboboxRootProps<Option, OptGroup, T> & {
|
|
9
|
+
class?: string;
|
|
10
|
+
children?: JSX.Element;
|
|
11
|
+
triggerMode?: "input" | "focus" | "both" | "manual";
|
|
12
|
+
};
|
|
9
13
|
|
|
10
14
|
/**
|
|
11
|
-
* Root Combobox
|
|
15
|
+
* Root Combobox component providing search, single/multi-selection, and group support.
|
|
16
|
+
* `triggerMode="focus"` or `triggerMode="both"` enables opening dropdown on input click/focus.
|
|
12
17
|
*/
|
|
13
18
|
export const Combobox = <Option = any, OptGroup = any, T extends ValidComponent = "div">(
|
|
14
19
|
props: ComboboxRootProps<Option, OptGroup, T>
|
|
15
20
|
) => {
|
|
16
|
-
|
|
21
|
+
const [local, rest] = splitProps(props as ComboboxRootProps, ["class", "children", "triggerMode"]);
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<ComboboxPrimitive.Root
|
|
25
|
+
triggerMode={local.triggerMode ?? "input"}
|
|
26
|
+
class={cn("relative w-full", local.class)}
|
|
27
|
+
{...(rest as any)}
|
|
28
|
+
>
|
|
29
|
+
{local.children}
|
|
30
|
+
</ComboboxPrimitive.Root>
|
|
31
|
+
);
|
|
17
32
|
};
|
|
18
33
|
|
|
19
34
|
export type ComboboxControlProps<Option = any, T extends ValidComponent = "div"> =
|
|
20
35
|
ComboboxPrimitive.ComboboxControlProps<Option, T> & {
|
|
21
36
|
class?: string;
|
|
22
37
|
children?: JSX.Element;
|
|
38
|
+
clearable?: boolean;
|
|
39
|
+
onClear?: () => void;
|
|
23
40
|
};
|
|
24
41
|
|
|
25
42
|
/**
|
|
26
|
-
* Input container
|
|
43
|
+
* Input container for Combobox supporting search input, selected tags, and clear button.
|
|
27
44
|
*/
|
|
28
45
|
export const ComboboxControl = <Option = any, T extends ValidComponent = "div">(
|
|
29
46
|
props: ComboboxControlProps<Option, T>
|
|
30
47
|
) => {
|
|
31
|
-
const [local, rest] = splitProps(props as ComboboxControlProps, [
|
|
48
|
+
const [local, rest] = splitProps(props as ComboboxControlProps, [
|
|
49
|
+
"class",
|
|
50
|
+
"children",
|
|
51
|
+
"clearable",
|
|
52
|
+
"onClear",
|
|
53
|
+
]);
|
|
32
54
|
|
|
33
55
|
return (
|
|
34
56
|
<ComboboxPrimitive.Control
|
|
35
57
|
class={cn(
|
|
36
|
-
"flex min-h-9 w-full flex-wrap items-center
|
|
58
|
+
"flex min-h-9 w-full flex-wrap items-center justify-between rounded-md border border-input bg-muted px-3 py-1 text-sm shadow-sm transition-colors focus-within:ring-1 focus-within:ring-primary focus-within:border-primary disabled:cursor-not-allowed disabled:opacity-50 gap-1.5 text-foreground",
|
|
37
59
|
local.class
|
|
38
60
|
)}
|
|
39
61
|
{...(rest as any)}
|
|
40
62
|
>
|
|
41
|
-
|
|
63
|
+
<div class="flex flex-wrap items-center gap-1.5 flex-1 min-w-0">
|
|
64
|
+
{local.children}
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<div class="flex items-center gap-1 shrink-0 self-center">
|
|
68
|
+
{local.clearable && (
|
|
69
|
+
<button
|
|
70
|
+
type="button"
|
|
71
|
+
tabIndex={-1}
|
|
72
|
+
onClick={(e) => {
|
|
73
|
+
e.stopPropagation();
|
|
74
|
+
if (local.onClear) local.onClear();
|
|
75
|
+
}}
|
|
76
|
+
class="rounded-sm p-0.5 opacity-60 hover:opacity-100 hover:bg-accent text-foreground transition-opacity focus:outline-none cursor-pointer"
|
|
77
|
+
aria-label="Clear selection"
|
|
78
|
+
>
|
|
79
|
+
<svg class="h-3.5 w-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
80
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
|
81
|
+
</svg>
|
|
82
|
+
</button>
|
|
83
|
+
)}
|
|
84
|
+
<ComboboxTrigger />
|
|
85
|
+
</div>
|
|
42
86
|
</ComboboxPrimitive.Control>
|
|
43
87
|
);
|
|
44
88
|
};
|
|
@@ -50,7 +94,8 @@ export type ComboboxInputProps<T extends ValidComponent = "input"> =
|
|
|
50
94
|
};
|
|
51
95
|
|
|
52
96
|
/**
|
|
53
|
-
*
|
|
97
|
+
* Search input field embedded within ComboboxControl.
|
|
98
|
+
* Supports `openOnFocus` to automatically trigger the dropdown when focused or clicked.
|
|
54
99
|
*/
|
|
55
100
|
export const ComboboxInput = <T extends ValidComponent = "input">(
|
|
56
101
|
props: ComboboxInputProps<T>
|
|
@@ -135,10 +180,12 @@ export const ComboboxToken = <Option = any>(props: ComboboxTokenProps<Option>) =
|
|
|
135
180
|
e.stopPropagation();
|
|
136
181
|
if (local.onRemove) local.onRemove();
|
|
137
182
|
}}
|
|
138
|
-
class="rounded-xs
|
|
183
|
+
class="rounded-xs p-0.5 hover:bg-muted-foreground/20 text-muted-foreground hover:text-foreground transition-colors focus:outline-none cursor-pointer"
|
|
184
|
+
aria-label="Remove tag"
|
|
139
185
|
>
|
|
140
|
-
<
|
|
141
|
-
|
|
186
|
+
<svg class="h-3 w-3" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
187
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
|
188
|
+
</svg>
|
|
142
189
|
</button>
|
|
143
190
|
</span>
|
|
144
191
|
);
|
|
@@ -191,17 +238,19 @@ export const ComboboxItem = <T extends ValidComponent = "li">(
|
|
|
191
238
|
return (
|
|
192
239
|
<ComboboxPrimitive.Item
|
|
193
240
|
class={cn(
|
|
194
|
-
"relative flex w-full cursor-pointer select-none items-center
|
|
241
|
+
"relative flex w-full cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-disabled:pointer-events-none data-disabled:opacity-50 data-highlighted:bg-accent data-highlighted:text-accent-foreground text-popover-foreground transition-colors",
|
|
195
242
|
local.class
|
|
196
243
|
)}
|
|
197
|
-
{...
|
|
244
|
+
{...rest}
|
|
198
245
|
>
|
|
199
|
-
<ComboboxPrimitive.
|
|
246
|
+
<ComboboxPrimitive.ItemIndicator class="absolute left-2 flex h-4 w-4 items-center justify-center text-primary">
|
|
247
|
+
<svg class="h-4 w-4 fill-none stroke-current stroke-2" viewBox="0 0 24 24">
|
|
248
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
|
|
249
|
+
</svg>
|
|
250
|
+
</ComboboxPrimitive.ItemIndicator>
|
|
251
|
+
<ComboboxPrimitive.ItemLabel class="flex items-center gap-2 w-full truncate">
|
|
200
252
|
{local.children}
|
|
201
253
|
</ComboboxPrimitive.ItemLabel>
|
|
202
|
-
<ComboboxPrimitive.ItemIndicator class="ml-2 flex h-4 w-4 items-center justify-center text-primary">
|
|
203
|
-
<Check class="h-4 w-4 stroke-2" />
|
|
204
|
-
</ComboboxPrimitive.ItemIndicator>
|
|
205
254
|
</ComboboxPrimitive.Item>
|
|
206
255
|
);
|
|
207
256
|
};
|
|
@@ -232,3 +281,19 @@ export const ComboboxGroup = <T extends ValidComponent = "li">(
|
|
|
232
281
|
</ComboboxPrimitive.Section>
|
|
233
282
|
);
|
|
234
283
|
};
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Empty state notice when no matching search items exist.
|
|
287
|
+
*/
|
|
288
|
+
export const ComboboxEmpty = (props: { class?: string; children?: JSX.Element }) => {
|
|
289
|
+
return (
|
|
290
|
+
<div class={cn("py-6 text-center text-sm text-muted-foreground", props.class)}>
|
|
291
|
+
{props.children || "No matching items found."}
|
|
292
|
+
</div>
|
|
293
|
+
);
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Hidden native select element for form integrations.
|
|
298
|
+
*/
|
|
299
|
+
export const ComboboxHiddenSelect = ComboboxPrimitive.HiddenSelect;
|
|
@@ -9,14 +9,14 @@ import {
|
|
|
9
9
|
type JSX,
|
|
10
10
|
type Accessor,
|
|
11
11
|
} from "solid-js";
|
|
12
|
-
import { createKeybindings } from "
|
|
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 "
|
|
16
|
-
import { InputGroup, InputGroupInput, InputGroupAddon } from "
|
|
17
|
-
import { List, ListGroup, ListHeader, ListItem, type ListItemProps } from "
|
|
18
|
-
import {
|
|
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: (
|
|
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 />
|
|
36
|
+
throw new Error("useCommand must be used within a <Command />");
|
|
37
37
|
}
|
|
38
38
|
return ctx;
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
-
/* --- Command
|
|
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
|
-
|
|
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
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return idx;
|
|
62
|
+
const onItemSelect = (index: number, action?: () => void) => {
|
|
63
|
+
setActiveIndex(index);
|
|
64
|
+
if (action) action();
|
|
59
65
|
};
|
|
60
66
|
|
|
61
|
-
const
|
|
62
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
<
|
|
148
|
-
|
|
149
|
-
<
|
|
150
|
-
<
|
|
151
|
-
|
|
152
|
-
|
|
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
|
};
|
|
@@ -257,118 +283,88 @@ export const CommandItem: Component<CommandItemProps> = (props) => {
|
|
|
257
283
|
"title",
|
|
258
284
|
"subtitle",
|
|
259
285
|
"keywords",
|
|
260
|
-
"disabled",
|
|
261
286
|
"onSelect",
|
|
287
|
+
"onClick",
|
|
262
288
|
"class",
|
|
263
|
-
"children",
|
|
264
289
|
]);
|
|
290
|
+
const { search, activeIndex } = useCommand();
|
|
265
291
|
|
|
266
|
-
|
|
267
|
-
const
|
|
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();
|
|
292
|
+
/* Auto fuzzy-filter item based on search query */
|
|
293
|
+
const matchesSearch = () => {
|
|
294
|
+
const query = search().toLowerCase().trim();
|
|
280
295
|
if (!query) return true;
|
|
281
296
|
|
|
282
|
-
const
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
if (local.keywords) {
|
|
288
|
-
return local.keywords.some((k) => k.toLowerCase().includes(query));
|
|
289
|
-
}
|
|
297
|
+
const titleMatch = local.title?.toLowerCase().includes(query);
|
|
298
|
+
const subtitleMatch = local.subtitle?.toLowerCase().includes(query);
|
|
299
|
+
const keywordMatch = local.keywords?.some((k) =>
|
|
300
|
+
k.toLowerCase().includes(query)
|
|
301
|
+
);
|
|
290
302
|
|
|
291
|
-
return
|
|
303
|
+
return Boolean(titleMatch || subtitleMatch || keywordMatch);
|
|
292
304
|
};
|
|
293
305
|
|
|
294
|
-
const
|
|
295
|
-
if (local.
|
|
296
|
-
|
|
306
|
+
const handleClick = (e: MouseEvent) => {
|
|
307
|
+
if (typeof local.onSelect === "function") {
|
|
308
|
+
local.onSelect();
|
|
309
|
+
}
|
|
310
|
+
if (typeof local.onClick === "function") {
|
|
311
|
+
local.onClick(e as any);
|
|
312
|
+
}
|
|
297
313
|
};
|
|
298
314
|
|
|
299
315
|
return (
|
|
300
|
-
<Show when={
|
|
316
|
+
<Show when={matchesSearch()}>
|
|
301
317
|
<ListItem
|
|
302
|
-
ref={
|
|
303
|
-
|
|
304
|
-
scrollIntoViewIfNeeded();
|
|
305
|
-
}}
|
|
318
|
+
ref={itemRef}
|
|
319
|
+
data-command-item="true"
|
|
306
320
|
title={local.title}
|
|
307
321
|
subtitle={local.subtitle}
|
|
308
|
-
|
|
322
|
+
onClick={handleClick}
|
|
309
323
|
class={cn(
|
|
310
|
-
"
|
|
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",
|
|
324
|
+
"aria-selected:bg-accent aria-selected:text-accent-foreground cursor-pointer transition-colors hover:bg-accent hover:text-accent-foreground",
|
|
315
325
|
local.class
|
|
316
326
|
)}
|
|
317
|
-
onClick={handleSelect}
|
|
318
|
-
onMouseEnter={() => ctx.setActiveIndex(() => index)}
|
|
319
327
|
{...rest}
|
|
320
|
-
|
|
321
|
-
{local.children}
|
|
322
|
-
</ListItem>
|
|
328
|
+
/>
|
|
323
329
|
</Show>
|
|
324
330
|
);
|
|
325
331
|
};
|
|
326
332
|
|
|
327
|
-
/* --- Command Footer
|
|
333
|
+
/* --- Command Footer --- */
|
|
328
334
|
export interface CommandFooterProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
|
329
335
|
class?: string;
|
|
330
336
|
}
|
|
331
337
|
|
|
332
338
|
export const CommandFooter: Component<CommandFooterProps> = (props) => {
|
|
333
|
-
const [local, rest] = splitProps(props, ["class"
|
|
339
|
+
const [local, rest] = splitProps(props, ["class"]);
|
|
334
340
|
|
|
335
341
|
return (
|
|
336
342
|
<div
|
|
337
343
|
class={cn(
|
|
338
|
-
"flex items-center justify-between border-t border-border px-3 py-2 text-xs text-muted-foreground
|
|
344
|
+
"flex items-center justify-between border-t border-border bg-muted/30 px-3 py-2 text-xs text-muted-foreground select-none",
|
|
339
345
|
local.class
|
|
340
346
|
)}
|
|
341
347
|
{...rest}
|
|
342
348
|
>
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
<
|
|
346
|
-
<
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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
|
-
)}
|
|
349
|
+
<div class="flex items-center gap-3">
|
|
350
|
+
<span class="flex items-center gap-1">
|
|
351
|
+
<KbdGroup>
|
|
352
|
+
<Kbd size="sm"><ArrowUp class="w-2.5 h-2.5" /></Kbd>
|
|
353
|
+
<Kbd size="sm"><ArrowDown class="w-2.5 h-2.5" /></Kbd>
|
|
354
|
+
</KbdGroup>
|
|
355
|
+
<span>Navigate</span>
|
|
356
|
+
</span>
|
|
357
|
+
|
|
358
|
+
<span class="flex items-center gap-1">
|
|
359
|
+
<Kbd size="sm"><CornerDownLeft class="w-2.5 h-2.5" /></Kbd>
|
|
360
|
+
<span>Select</span>
|
|
361
|
+
</span>
|
|
362
|
+
</div>
|
|
363
|
+
|
|
364
|
+
<span class="flex items-center gap-1">
|
|
365
|
+
<Kbd size="sm">Esc</Kbd>
|
|
366
|
+
<span>Close</span>
|
|
367
|
+
</span>
|
|
372
368
|
</div>
|
|
373
369
|
);
|
|
374
370
|
};
|
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
import { splitProps, type Component, type JSX, type ValidComponent } from "solid-js";
|
|
2
2
|
import * as ContextMenuPrimitive from "@kobalte/core/context-menu";
|
|
3
3
|
import type { PolymorphicProps } from "@kobalte/core/polymorphic";
|
|
4
|
-
import { createClickOutside } from "
|
|
5
|
-
import { ScrollArea } from "
|
|
6
|
-
import { Separator } from "
|
|
7
|
-
import { Kbd } from "
|
|
4
|
+
import { createClickOutside } from "@/hooks/create-click-outside";
|
|
5
|
+
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
6
|
+
import { Separator } from "@/components/ui/separator";
|
|
7
|
+
import { Kbd } from "@/components/ui/kbd";
|
|
8
8
|
import { cn } from "@/lib/cn";
|
|
9
9
|
|
|
10
|
-
export
|
|
11
|
-
|
|
12
|
-
export const ContextMenu: Component<ContextMenuRootProps> = (props) => {
|
|
13
|
-
return <ContextMenuPrimitive.Root {...props} />;
|
|
14
|
-
};
|
|
15
|
-
|
|
10
|
+
export const ContextMenu = ContextMenuPrimitive.Root;
|
|
16
11
|
export const ContextMenuTrigger = ContextMenuPrimitive.Trigger;
|
|
17
12
|
export const ContextMenuGroup = ContextMenuPrimitive.Group;
|
|
13
|
+
export const ContextMenuPortal = ContextMenuPrimitive.Portal;
|
|
18
14
|
export const ContextMenuSub = ContextMenuPrimitive.Sub;
|
|
15
|
+
export const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup;
|
|
19
16
|
|
|
20
17
|
export type ContextMenuSubTriggerProps<T extends ValidComponent = "div"> =
|
|
21
18
|
ContextMenuPrimitive.ContextMenuSubTriggerProps<T> & {
|
|
@@ -32,7 +29,7 @@ export const ContextMenuSubTrigger = <T extends ValidComponent = "div">(
|
|
|
32
29
|
return (
|
|
33
30
|
<ContextMenuPrimitive.SubTrigger
|
|
34
31
|
class={cn(
|
|
35
|
-
"flex cursor-
|
|
32
|
+
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent text-foreground",
|
|
36
33
|
local.inset && "pl-8",
|
|
37
34
|
local.class
|
|
38
35
|
)}
|
|
@@ -126,7 +123,7 @@ export const ContextMenuItem = <T extends ValidComponent = "div">(
|
|
|
126
123
|
return (
|
|
127
124
|
<ContextMenuPrimitive.Item
|
|
128
125
|
class={cn(
|
|
129
|
-
"relative flex cursor-
|
|
126
|
+
"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",
|
|
130
127
|
local.inset && "pl-8",
|
|
131
128
|
local.class
|
|
132
129
|
)}
|
|
@@ -151,7 +148,7 @@ export const ContextMenuCheckboxItem = <T extends ValidComponent = "div">(
|
|
|
151
148
|
<ContextMenuPrimitive.CheckboxItem
|
|
152
149
|
checked={local.checked}
|
|
153
150
|
class={cn(
|
|
154
|
-
"relative flex cursor-
|
|
151
|
+
"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",
|
|
155
152
|
local.class
|
|
156
153
|
)}
|
|
157
154
|
{...(rest as any)}
|
|
@@ -168,25 +165,73 @@ export const ContextMenuCheckboxItem = <T extends ValidComponent = "div">(
|
|
|
168
165
|
);
|
|
169
166
|
};
|
|
170
167
|
|
|
171
|
-
export
|
|
168
|
+
export type ContextMenuRadioItemProps<T extends ValidComponent = "div"> =
|
|
169
|
+
ContextMenuPrimitive.ContextMenuRadioItemProps<T> & {
|
|
170
|
+
class?: string;
|
|
171
|
+
children?: JSX.Element;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
export const ContextMenuRadioItem = <T extends ValidComponent = "div">(
|
|
175
|
+
props: PolymorphicProps<T, ContextMenuRadioItemProps<T>>
|
|
176
|
+
) => {
|
|
177
|
+
const [local, rest] = splitProps(props as ContextMenuRadioItemProps, ["class", "children"]);
|
|
178
|
+
|
|
179
|
+
return (
|
|
180
|
+
<ContextMenuPrimitive.RadioItem
|
|
181
|
+
class={cn(
|
|
182
|
+
"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",
|
|
183
|
+
local.class
|
|
184
|
+
)}
|
|
185
|
+
{...(rest as any)}
|
|
186
|
+
>
|
|
187
|
+
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
188
|
+
<ContextMenuPrimitive.ItemIndicator>
|
|
189
|
+
<svg class="h-2 w-2 fill-current" viewBox="0 0 8 8">
|
|
190
|
+
<circle cx="4" cy="4" r="3" />
|
|
191
|
+
</svg>
|
|
192
|
+
</ContextMenuPrimitive.ItemIndicator>
|
|
193
|
+
</span>
|
|
194
|
+
{local.children}
|
|
195
|
+
</ContextMenuPrimitive.RadioItem>
|
|
196
|
+
);
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
export interface ContextMenuLabelProps {
|
|
172
200
|
class?: string;
|
|
201
|
+
inset?: boolean;
|
|
173
202
|
children?: JSX.Element;
|
|
174
203
|
}
|
|
175
204
|
|
|
176
|
-
export const
|
|
177
|
-
const [local, rest] = splitProps(props, ["class", "children"]);
|
|
205
|
+
export const ContextMenuLabel: Component<ContextMenuLabelProps> = (props) => {
|
|
206
|
+
const [local, rest] = splitProps(props, ["class", "inset", "children"]);
|
|
178
207
|
|
|
179
208
|
return (
|
|
180
|
-
<
|
|
181
|
-
|
|
182
|
-
|
|
209
|
+
<ContextMenuPrimitive.GroupLabel
|
|
210
|
+
class={cn("px-2 py-1.5 text-sm font-semibold text-foreground", local.inset && "pl-8", local.class)}
|
|
211
|
+
{...rest}
|
|
212
|
+
>
|
|
213
|
+
{local.children}
|
|
214
|
+
</ContextMenuPrimitive.GroupLabel>
|
|
183
215
|
);
|
|
184
216
|
};
|
|
185
217
|
|
|
186
|
-
export
|
|
218
|
+
export const ContextMenuSeparator: Component<{ class?: string }> = (props) => {
|
|
219
|
+
return <Separator class={cn("-mx-1 my-1", props.class)} />;
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
export interface ContextMenuShortcutProps {
|
|
187
223
|
class?: string;
|
|
224
|
+
children?: JSX.Element;
|
|
188
225
|
}
|
|
189
226
|
|
|
190
|
-
export const
|
|
191
|
-
|
|
227
|
+
export const ContextMenuShortcut: Component<ContextMenuShortcutProps> = (props) => {
|
|
228
|
+
const [local, rest] = splitProps(props, ["class", "children"]);
|
|
229
|
+
|
|
230
|
+
return (
|
|
231
|
+
<span class={cn("ml-auto pl-4 text-xs tracking-widest text-muted-foreground", local.class)} {...rest}>
|
|
232
|
+
<Kbd size="sm" variant="outline">
|
|
233
|
+
{local.children}
|
|
234
|
+
</Kbd>
|
|
235
|
+
</span>
|
|
236
|
+
);
|
|
192
237
|
};
|