@nikala-ui/core 0.9.10 → 0.9.11-nightly.78f0a94
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +2 -2
- package/registry/aspect-ratio.json +17 -0
- package/registry/collapsible.json +18 -0
- package/registry/combobox.json +18 -0
- package/registry/command.json +1 -1
- package/registry/hover-card.json +18 -0
- package/registry/index.json +64 -0
- package/registry/pin-input.json +17 -0
- package/registry/slider.json +18 -0
- package/src/registry/components/ui/aspect-ratio.tsx +31 -0
- package/src/registry/components/ui/collapsible.tsx +73 -0
- package/src/registry/components/ui/combobox.tsx +295 -0
- package/src/registry/components/ui/command.tsx +90 -7
- package/src/registry/components/ui/hover-card.tsx +105 -0
- package/src/registry/components/ui/pin-input.tsx +198 -0
- package/src/registry/components/ui/slider.tsx +153 -0
- package/src/registry/metadata.ts +30 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { splitProps, type JSX, type ValidComponent } from "solid-js";
|
|
2
|
+
import * as ComboboxPrimitive from "@kobalte/core/combobox";
|
|
3
|
+
import { cn } from "@/lib/cn";
|
|
4
|
+
|
|
5
|
+
export type ComboboxRootProps<Option = any, OptGroup = any, T extends ValidComponent = "div"> =
|
|
6
|
+
ComboboxPrimitive.ComboboxRootProps<Option, OptGroup, T> & {
|
|
7
|
+
class?: string;
|
|
8
|
+
children?: JSX.Element;
|
|
9
|
+
triggerMode?: "input" | "focus" | "both" | "manual";
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Root Combobox component providing search, single/multi-selection, and group support.
|
|
14
|
+
* `triggerMode="focus"` or `triggerMode="both"` enables opening dropdown on input click/focus.
|
|
15
|
+
*/
|
|
16
|
+
export const Combobox = <Option = any, OptGroup = any, T extends ValidComponent = "div">(
|
|
17
|
+
props: ComboboxRootProps<Option, OptGroup, T>
|
|
18
|
+
) => {
|
|
19
|
+
const [local, rest] = splitProps(props as ComboboxRootProps, ["class", "children", "triggerMode"]);
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<ComboboxPrimitive.Root
|
|
23
|
+
triggerMode={local.triggerMode ?? "input"}
|
|
24
|
+
class={cn("relative w-full", local.class)}
|
|
25
|
+
{...(rest as any)}
|
|
26
|
+
>
|
|
27
|
+
{local.children}
|
|
28
|
+
</ComboboxPrimitive.Root>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type ComboboxControlProps<Option = any, T extends ValidComponent = "div"> =
|
|
33
|
+
ComboboxPrimitive.ComboboxControlProps<Option, T> & {
|
|
34
|
+
class?: string;
|
|
35
|
+
children?: JSX.Element;
|
|
36
|
+
clearable?: boolean;
|
|
37
|
+
onClear?: () => void;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Input container for Combobox supporting search input, selected tags, and clear button.
|
|
42
|
+
*/
|
|
43
|
+
export const ComboboxControl = <Option = any, T extends ValidComponent = "div">(
|
|
44
|
+
props: ComboboxControlProps<Option, T>
|
|
45
|
+
) => {
|
|
46
|
+
const [local, rest] = splitProps(props as ComboboxControlProps, [
|
|
47
|
+
"class",
|
|
48
|
+
"children",
|
|
49
|
+
"clearable",
|
|
50
|
+
"onClear",
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<ComboboxPrimitive.Control
|
|
55
|
+
class={cn(
|
|
56
|
+
"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",
|
|
57
|
+
local.class
|
|
58
|
+
)}
|
|
59
|
+
{...(rest as any)}
|
|
60
|
+
>
|
|
61
|
+
<div class="flex flex-wrap items-center gap-1.5 flex-1 min-w-0">
|
|
62
|
+
{local.children}
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<div class="flex items-center gap-1 shrink-0 self-center">
|
|
66
|
+
{local.clearable && (
|
|
67
|
+
<button
|
|
68
|
+
type="button"
|
|
69
|
+
tabIndex={-1}
|
|
70
|
+
onClick={(e) => {
|
|
71
|
+
e.stopPropagation();
|
|
72
|
+
if (local.onClear) local.onClear();
|
|
73
|
+
}}
|
|
74
|
+
class="rounded-sm p-0.5 opacity-60 hover:opacity-100 hover:bg-accent text-foreground transition-opacity focus:outline-none cursor-pointer"
|
|
75
|
+
aria-label="Clear selection"
|
|
76
|
+
>
|
|
77
|
+
<svg class="h-3.5 w-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
78
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
|
79
|
+
</svg>
|
|
80
|
+
</button>
|
|
81
|
+
)}
|
|
82
|
+
<ComboboxTrigger />
|
|
83
|
+
</div>
|
|
84
|
+
</ComboboxPrimitive.Control>
|
|
85
|
+
);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export type ComboboxInputProps<T extends ValidComponent = "input"> =
|
|
89
|
+
ComboboxPrimitive.ComboboxInputProps<T> & {
|
|
90
|
+
class?: string;
|
|
91
|
+
openOnFocus?: boolean;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Search input field embedded within ComboboxControl.
|
|
96
|
+
* Supports `openOnFocus` to automatically trigger the dropdown when focused or clicked.
|
|
97
|
+
*/
|
|
98
|
+
export const ComboboxInput = <T extends ValidComponent = "input">(
|
|
99
|
+
props: ComboboxInputProps<T>
|
|
100
|
+
) => {
|
|
101
|
+
const [local, rest] = splitProps(props as ComboboxInputProps, ["class", "openOnFocus", "onFocus", "onClick"]);
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<ComboboxPrimitive.Input
|
|
105
|
+
class={cn(
|
|
106
|
+
"flex-1 bg-transparent py-1 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed text-foreground min-w-16",
|
|
107
|
+
local.class
|
|
108
|
+
)}
|
|
109
|
+
onFocus={(e: FocusEvent) => {
|
|
110
|
+
if (typeof local.onFocus === "function") local.onFocus(e as any);
|
|
111
|
+
}}
|
|
112
|
+
onClick={(e: MouseEvent) => {
|
|
113
|
+
if (typeof local.onClick === "function") local.onClick(e as any);
|
|
114
|
+
}}
|
|
115
|
+
{...(rest as any)}
|
|
116
|
+
/>
|
|
117
|
+
);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export type ComboboxTriggerProps<T extends ValidComponent = "button"> =
|
|
121
|
+
ComboboxPrimitive.ComboboxTriggerProps<T> & {
|
|
122
|
+
class?: string;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Dropdown chevron trigger icon.
|
|
127
|
+
*/
|
|
128
|
+
export const ComboboxTrigger = <T extends ValidComponent = "button">(
|
|
129
|
+
props: ComboboxTriggerProps<T>
|
|
130
|
+
) => {
|
|
131
|
+
const [local, rest] = splitProps(props as ComboboxTriggerProps, ["class"]);
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<ComboboxPrimitive.Trigger
|
|
135
|
+
class={cn(
|
|
136
|
+
"flex h-4 w-4 items-center justify-center opacity-50 hover:opacity-100 transition-opacity focus:outline-none cursor-pointer",
|
|
137
|
+
local.class
|
|
138
|
+
)}
|
|
139
|
+
{...(rest as any)}
|
|
140
|
+
>
|
|
141
|
+
<ComboboxPrimitive.Icon
|
|
142
|
+
as="svg"
|
|
143
|
+
class="h-4 w-4 stroke-current stroke-2 fill-none"
|
|
144
|
+
viewBox="0 0 24 24"
|
|
145
|
+
>
|
|
146
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7" />
|
|
147
|
+
</ComboboxPrimitive.Icon>
|
|
148
|
+
</ComboboxPrimitive.Trigger>
|
|
149
|
+
);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
export type ComboboxTokenProps<Option = any> = {
|
|
153
|
+
class?: string;
|
|
154
|
+
children?: JSX.Element;
|
|
155
|
+
item?: Option;
|
|
156
|
+
onRemove?: () => void;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Selected item tag token pill rendered in multi-select mode.
|
|
161
|
+
*/
|
|
162
|
+
export const ComboboxToken = <Option = any>(props: ComboboxTokenProps<Option>) => {
|
|
163
|
+
const [local, rest] = splitProps(props, ["class", "children", "onRemove"]);
|
|
164
|
+
|
|
165
|
+
return (
|
|
166
|
+
<span
|
|
167
|
+
class={cn(
|
|
168
|
+
"inline-flex items-center gap-1.5 rounded-md bg-accent px-2 py-0.5 text-xs font-medium text-accent-foreground border border-border shadow-2xs animate-in fade-in-50",
|
|
169
|
+
local.class
|
|
170
|
+
)}
|
|
171
|
+
{...rest}
|
|
172
|
+
>
|
|
173
|
+
<span class="truncate">{local.children}</span>
|
|
174
|
+
<button
|
|
175
|
+
type="button"
|
|
176
|
+
tabIndex={-1}
|
|
177
|
+
onClick={(e) => {
|
|
178
|
+
e.stopPropagation();
|
|
179
|
+
if (local.onRemove) local.onRemove();
|
|
180
|
+
}}
|
|
181
|
+
class="rounded-xs p-0.5 hover:bg-muted-foreground/20 text-muted-foreground hover:text-foreground transition-colors focus:outline-none cursor-pointer"
|
|
182
|
+
aria-label="Remove tag"
|
|
183
|
+
>
|
|
184
|
+
<svg class="h-3 w-3" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
185
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
|
186
|
+
</svg>
|
|
187
|
+
</button>
|
|
188
|
+
</span>
|
|
189
|
+
);
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
export type ComboboxContentProps<T extends ValidComponent = "div"> =
|
|
193
|
+
ComboboxPrimitive.ComboboxContentProps<T> & {
|
|
194
|
+
class?: string;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Portaled popover content listbox container.
|
|
199
|
+
*/
|
|
200
|
+
export const ComboboxContent = <T extends ValidComponent = "div">(
|
|
201
|
+
props: ComboboxContentProps<T>
|
|
202
|
+
) => {
|
|
203
|
+
const [local, rest] = splitProps(props as ComboboxContentProps, ["class"]);
|
|
204
|
+
|
|
205
|
+
return (
|
|
206
|
+
<ComboboxPrimitive.Portal>
|
|
207
|
+
<ComboboxPrimitive.Content
|
|
208
|
+
class={cn(
|
|
209
|
+
"relative z-50 min-w-8rem overflow-hidden rounded-md border border-border bg-popover text-popover-foreground shadow-md animate-in fade-in-80 data-[expanded]:animate-in data-[closed]:animate-out data-[closed]:fade-out-0 data-[closed]:fade-out-0 data-[expanded]:fade-in-0 data-[closed]:zoom-out-95 data-[expanded]:zoom-in-95",
|
|
210
|
+
local.class
|
|
211
|
+
)}
|
|
212
|
+
{...(rest as any)}
|
|
213
|
+
>
|
|
214
|
+
<ComboboxPrimitive.Listbox class="max-h-60 overflow-y-auto p-1 outline-none" />
|
|
215
|
+
</ComboboxPrimitive.Content>
|
|
216
|
+
</ComboboxPrimitive.Portal>
|
|
217
|
+
);
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
export type ComboboxItemProps<T extends ValidComponent = "li"> =
|
|
221
|
+
ComboboxPrimitive.ComboboxItemProps<T> & {
|
|
222
|
+
class?: string;
|
|
223
|
+
children?: JSX.Element;
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Individual option item inside listbox supporting custom avatars, icons, and titles.
|
|
228
|
+
*/
|
|
229
|
+
export const ComboboxItem = <T extends ValidComponent = "li">(
|
|
230
|
+
props: ComboboxItemProps<T>
|
|
231
|
+
) => {
|
|
232
|
+
const [local, rest] = splitProps(props as ComboboxItemProps, ["class", "children"]);
|
|
233
|
+
|
|
234
|
+
return (
|
|
235
|
+
<ComboboxPrimitive.Item
|
|
236
|
+
class={cn(
|
|
237
|
+
"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",
|
|
238
|
+
local.class
|
|
239
|
+
)}
|
|
240
|
+
{...rest}
|
|
241
|
+
>
|
|
242
|
+
<ComboboxPrimitive.ItemIndicator class="absolute left-2 flex h-4 w-4 items-center justify-center text-primary">
|
|
243
|
+
<svg class="h-4 w-4 fill-none stroke-current stroke-2" viewBox="0 0 24 24">
|
|
244
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
|
|
245
|
+
</svg>
|
|
246
|
+
</ComboboxPrimitive.ItemIndicator>
|
|
247
|
+
<ComboboxPrimitive.ItemLabel class="flex items-center gap-2 w-full truncate">
|
|
248
|
+
{local.children}
|
|
249
|
+
</ComboboxPrimitive.ItemLabel>
|
|
250
|
+
</ComboboxPrimitive.Item>
|
|
251
|
+
);
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
export type ComboboxGroupProps<T extends ValidComponent = "li"> =
|
|
255
|
+
ComboboxPrimitive.ComboboxSectionProps<T> & {
|
|
256
|
+
class?: string;
|
|
257
|
+
children?: JSX.Element;
|
|
258
|
+
label?: JSX.Element;
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Group container for organizing related options.
|
|
263
|
+
*/
|
|
264
|
+
export const ComboboxGroup = <T extends ValidComponent = "li">(
|
|
265
|
+
props: ComboboxGroupProps<T>
|
|
266
|
+
) => {
|
|
267
|
+
const [local, rest] = splitProps(props as ComboboxGroupProps, ["class", "children", "label"]);
|
|
268
|
+
|
|
269
|
+
return (
|
|
270
|
+
<ComboboxPrimitive.Section class={cn("px-1 py-1", local.class)} {...(rest as any)}>
|
|
271
|
+
{local.label && (
|
|
272
|
+
<span class="px-2 py-1.5 text-xs font-semibold text-muted-foreground uppercase tracking-wider block">
|
|
273
|
+
{local.label}
|
|
274
|
+
</span>
|
|
275
|
+
)}
|
|
276
|
+
{local.children}
|
|
277
|
+
</ComboboxPrimitive.Section>
|
|
278
|
+
);
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Empty state notice when no matching search items exist.
|
|
283
|
+
*/
|
|
284
|
+
export const ComboboxEmpty = (props: { class?: string; children?: JSX.Element }) => {
|
|
285
|
+
return (
|
|
286
|
+
<div class={cn("py-6 text-center text-sm text-muted-foreground", props.class)}>
|
|
287
|
+
{props.children || "No matching items found."}
|
|
288
|
+
</div>
|
|
289
|
+
);
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Hidden native select element for form integrations.
|
|
294
|
+
*/
|
|
295
|
+
export const ComboboxHiddenSelect = ComboboxPrimitive.HiddenSelect;
|
|
@@ -21,6 +21,10 @@ import { List, ListGroup, ListHeader, ListItem, type ListItemProps } from "../ui
|
|
|
21
21
|
interface CommandContextValue {
|
|
22
22
|
search: Accessor<string>;
|
|
23
23
|
setSearch: (value: string) => void;
|
|
24
|
+
activeIndex: Accessor<number>;
|
|
25
|
+
setActiveIndex: (fn: (prev: number) => number) => void;
|
|
26
|
+
registerItemIndex: (element: HTMLElement) => number;
|
|
27
|
+
onItemSelect: (index: number, action?: () => void) => void;
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
const CommandContext = createContext<CommandContextValue>();
|
|
@@ -34,24 +38,96 @@ export const useCommand = () => {
|
|
|
34
38
|
};
|
|
35
39
|
|
|
36
40
|
/* --- Root Command Container --- */
|
|
37
|
-
export interface CommandProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
|
41
|
+
export interface CommandProps extends Omit<JSX.HTMLAttributes<HTMLDivElement>, "children"> {
|
|
38
42
|
class?: string;
|
|
43
|
+
children?: JSX.Element | ((ctx: CommandContextValue) => JSX.Element);
|
|
39
44
|
}
|
|
40
45
|
|
|
41
46
|
export const Command: Component<CommandProps> = (props) => {
|
|
42
47
|
const [local, rest] = splitProps(props, ["class", "children"]);
|
|
43
48
|
const [search, setSearch] = createSignal("");
|
|
49
|
+
const [activeIndex, setActiveIndex] = createSignal(0);
|
|
50
|
+
|
|
51
|
+
let containerRef: HTMLDivElement | undefined;
|
|
52
|
+
let itemsList: HTMLElement[] = [];
|
|
53
|
+
|
|
54
|
+
const registerItemIndex = (element: HTMLElement) => {
|
|
55
|
+
if (!itemsList.includes(element)) {
|
|
56
|
+
itemsList.push(element);
|
|
57
|
+
}
|
|
58
|
+
return itemsList.indexOf(element);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const onItemSelect = (index: number, action?: () => void) => {
|
|
62
|
+
setActiveIndex(index);
|
|
63
|
+
if (action) action();
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const getVisibleItems = () => {
|
|
67
|
+
if (!containerRef) return [];
|
|
68
|
+
return Array.from(containerRef.querySelectorAll<HTMLElement>("[data-command-item]:not(.hidden)"));
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const updateActiveAttribute = (index: number) => {
|
|
72
|
+
const visible = getVisibleItems();
|
|
73
|
+
visible.forEach((el, idx) => {
|
|
74
|
+
if (idx === index) {
|
|
75
|
+
el.setAttribute("aria-selected", "true");
|
|
76
|
+
el.scrollIntoView({ block: "nearest" });
|
|
77
|
+
} else {
|
|
78
|
+
el.removeAttribute("aria-selected");
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
84
|
+
const visible = getVisibleItems();
|
|
85
|
+
if (visible.length === 0) return;
|
|
86
|
+
|
|
87
|
+
if (e.key === "ArrowDown") {
|
|
88
|
+
e.preventDefault();
|
|
89
|
+
const next = (activeIndex() + 1) % visible.length;
|
|
90
|
+
setActiveIndex(next);
|
|
91
|
+
updateActiveAttribute(next);
|
|
92
|
+
} else if (e.key === "ArrowUp") {
|
|
93
|
+
e.preventDefault();
|
|
94
|
+
const prev = (activeIndex() - 1 + visible.length) % visible.length;
|
|
95
|
+
setActiveIndex(prev);
|
|
96
|
+
updateActiveAttribute(prev);
|
|
97
|
+
} else if (e.key === "Enter") {
|
|
98
|
+
e.preventDefault();
|
|
99
|
+
const current = visible[activeIndex()];
|
|
100
|
+
if (current) {
|
|
101
|
+
current.click();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const ctx: CommandContextValue = {
|
|
107
|
+
search,
|
|
108
|
+
setSearch: (val) => {
|
|
109
|
+
setSearch(val);
|
|
110
|
+
setActiveIndex(0);
|
|
111
|
+
},
|
|
112
|
+
activeIndex,
|
|
113
|
+
setActiveIndex: (fn) => setActiveIndex(fn),
|
|
114
|
+
registerItemIndex,
|
|
115
|
+
onItemSelect,
|
|
116
|
+
};
|
|
44
117
|
|
|
45
118
|
return (
|
|
46
|
-
<CommandContext.Provider value={
|
|
119
|
+
<CommandContext.Provider value={ctx}>
|
|
47
120
|
<div
|
|
121
|
+
ref={containerRef}
|
|
122
|
+
onKeyDown={handleKeyDown}
|
|
48
123
|
class={cn(
|
|
49
|
-
"flex flex-col w-full h-full rounded-lg bg-popover text-popover-foreground overflow-hidden border border-border shadow-md",
|
|
124
|
+
"flex flex-col w-full h-full rounded-lg bg-popover text-popover-foreground overflow-hidden border border-border shadow-md outline-none",
|
|
50
125
|
local.class
|
|
51
126
|
)}
|
|
127
|
+
tabIndex={0}
|
|
52
128
|
{...rest}
|
|
53
129
|
>
|
|
54
|
-
{local.children}
|
|
130
|
+
{typeof local.children === "function" ? (local.children as any)(ctx) : local.children}
|
|
55
131
|
</div>
|
|
56
132
|
</CommandContext.Provider>
|
|
57
133
|
);
|
|
@@ -62,7 +138,7 @@ export interface CommandDialogProps {
|
|
|
62
138
|
open?: boolean;
|
|
63
139
|
onOpenChange?: (open: boolean) => void;
|
|
64
140
|
enableHotkey?: boolean;
|
|
65
|
-
children?: JSX.Element;
|
|
141
|
+
children?: JSX.Element | ((ctx: CommandContextValue) => JSX.Element);
|
|
66
142
|
class?: string;
|
|
67
143
|
}
|
|
68
144
|
|
|
@@ -120,6 +196,7 @@ export const CommandInput: Component<CommandInputProps> = (props) => {
|
|
|
120
196
|
</InputGroupAddon>
|
|
121
197
|
|
|
122
198
|
<InputGroupInput
|
|
199
|
+
ref={(el) => setTimeout(() => el.focus(), 50)}
|
|
123
200
|
value={search()}
|
|
124
201
|
onInput={(e) => {
|
|
125
202
|
setSearch(e.currentTarget.value);
|
|
@@ -200,6 +277,7 @@ export interface CommandItemProps extends ListItemProps {
|
|
|
200
277
|
}
|
|
201
278
|
|
|
202
279
|
export const CommandItem: Component<CommandItemProps> = (props) => {
|
|
280
|
+
let itemRef: HTMLDivElement | undefined;
|
|
203
281
|
const [local, rest] = splitProps(props, [
|
|
204
282
|
"title",
|
|
205
283
|
"subtitle",
|
|
@@ -208,7 +286,7 @@ export const CommandItem: Component<CommandItemProps> = (props) => {
|
|
|
208
286
|
"onClick",
|
|
209
287
|
"class",
|
|
210
288
|
]);
|
|
211
|
-
const { search } = useCommand();
|
|
289
|
+
const { search, activeIndex } = useCommand();
|
|
212
290
|
|
|
213
291
|
/* Auto fuzzy-filter item based on search query */
|
|
214
292
|
const matchesSearch = () => {
|
|
@@ -236,10 +314,15 @@ export const CommandItem: Component<CommandItemProps> = (props) => {
|
|
|
236
314
|
return (
|
|
237
315
|
<Show when={matchesSearch()}>
|
|
238
316
|
<ListItem
|
|
317
|
+
ref={itemRef}
|
|
318
|
+
data-command-item="true"
|
|
239
319
|
title={local.title}
|
|
240
320
|
subtitle={local.subtitle}
|
|
241
321
|
onClick={handleClick}
|
|
242
|
-
class={
|
|
322
|
+
class={cn(
|
|
323
|
+
"aria-selected:bg-accent aria-selected:text-accent-foreground cursor-pointer transition-colors hover:bg-accent hover:text-accent-foreground",
|
|
324
|
+
local.class
|
|
325
|
+
)}
|
|
243
326
|
{...rest}
|
|
244
327
|
/>
|
|
245
328
|
</Show>
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { splitProps, type JSX, type ValidComponent } from "solid-js";
|
|
2
|
+
import * as HoverCardPrimitive from "@kobalte/core/hover-card";
|
|
3
|
+
import { cn } from "@/lib/cn";
|
|
4
|
+
|
|
5
|
+
export type HoverCardProps = HoverCardPrimitive.HoverCardRootProps;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Root HoverCard component built on Kobalte primitives.
|
|
9
|
+
* Controls hover open/close delays (default: 200ms / 150ms).
|
|
10
|
+
*/
|
|
11
|
+
export const HoverCard = (props: HoverCardProps) => {
|
|
12
|
+
return (
|
|
13
|
+
<HoverCardPrimitive.Root
|
|
14
|
+
openDelay={200}
|
|
15
|
+
closeDelay={150}
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type HoverCardTriggerProps<T extends ValidComponent = "a"> =
|
|
22
|
+
HoverCardPrimitive.HoverCardTriggerProps<T> &
|
|
23
|
+
JSX.AnchorHTMLAttributes<HTMLAnchorElement> & {
|
|
24
|
+
class?: string;
|
|
25
|
+
children?: JSX.Element;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* HoverCard trigger handle (link, avatar, or text).
|
|
30
|
+
*/
|
|
31
|
+
export const HoverCardTrigger = <T extends ValidComponent = "a">(
|
|
32
|
+
props: HoverCardTriggerProps<T>
|
|
33
|
+
) => {
|
|
34
|
+
const [local, rest] = splitProps(props as HoverCardTriggerProps, [
|
|
35
|
+
"class",
|
|
36
|
+
"children",
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<HoverCardPrimitive.Trigger
|
|
41
|
+
class={cn(
|
|
42
|
+
"inline-flex items-center text-sm font-medium text-foreground underline-offset-4 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2",
|
|
43
|
+
local.class
|
|
44
|
+
)}
|
|
45
|
+
{...(rest as any)}
|
|
46
|
+
>
|
|
47
|
+
{local.children}
|
|
48
|
+
</HoverCardPrimitive.Trigger>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export type HoverCardContentProps<T extends ValidComponent = "div"> =
|
|
53
|
+
HoverCardPrimitive.HoverCardContentProps<T> & {
|
|
54
|
+
class?: string;
|
|
55
|
+
children?: JSX.Element;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Floating card preview container rendered in a portal layer.
|
|
60
|
+
* Includes offset spacing from trigger and support for optional HoverCardArrow.
|
|
61
|
+
*/
|
|
62
|
+
export const HoverCardContent = <T extends ValidComponent = "div">(
|
|
63
|
+
props: HoverCardContentProps<T>
|
|
64
|
+
) => {
|
|
65
|
+
const [local, rest] = splitProps(props as HoverCardContentProps, [
|
|
66
|
+
"class",
|
|
67
|
+
"children",
|
|
68
|
+
]);
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<HoverCardPrimitive.Portal>
|
|
72
|
+
<HoverCardPrimitive.Content
|
|
73
|
+
sideOffset={8}
|
|
74
|
+
class={cn(
|
|
75
|
+
"z-50 w-80 rounded-lg border border-border bg-popover p-4 text-popover-foreground shadow-xl outline-none transition-all 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 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
76
|
+
local.class
|
|
77
|
+
)}
|
|
78
|
+
{...(rest as any)}
|
|
79
|
+
>
|
|
80
|
+
{local.children}
|
|
81
|
+
</HoverCardPrimitive.Content>
|
|
82
|
+
</HoverCardPrimitive.Portal>
|
|
83
|
+
);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export type HoverCardArrowProps<T extends ValidComponent = "div"> =
|
|
87
|
+
HoverCardPrimitive.HoverCardArrowProps<T> & {
|
|
88
|
+
class?: string;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Optional arrow pointer pointing to the trigger.
|
|
93
|
+
*/
|
|
94
|
+
export const HoverCardArrow = <T extends ValidComponent = "div">(
|
|
95
|
+
props: HoverCardArrowProps<T>
|
|
96
|
+
) => {
|
|
97
|
+
const [local, rest] = splitProps(props as HoverCardArrowProps, ["class"]);
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<HoverCardPrimitive.Arrow
|
|
101
|
+
class={cn("fill-popover stroke-border stroke-1", local.class)}
|
|
102
|
+
{...(rest as any)}
|
|
103
|
+
/>
|
|
104
|
+
);
|
|
105
|
+
};
|