@noya-app/noya-designsystem 0.1.41 → 0.1.42
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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +10 -0
- package/dist/index.css +1 -1
- package/dist/index.d.mts +217 -121
- package/dist/index.d.ts +217 -121
- package/dist/index.js +7435 -6796
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +5257 -4660
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -5
- package/src/__tests__/combobox.test.ts +578 -0
- package/src/components/ActivityIndicator.tsx +4 -4
- package/src/components/AnimatePresence.tsx +5 -5
- package/src/components/Avatar.tsx +5 -2
- package/src/components/BaseToolbar.tsx +61 -0
- package/src/components/Button.tsx +1 -1
- package/src/components/Checkbox.tsx +6 -5
- package/src/components/Combobox.tsx +220 -324
- package/src/components/ComboboxMenu.tsx +88 -0
- package/src/components/Dialog.tsx +10 -6
- package/src/components/DimensionInput.tsx +4 -4
- package/src/components/FillPreviewBackground.tsx +51 -44
- package/src/components/FloatingWindow.tsx +5 -2
- package/src/components/GradientPicker.tsx +14 -14
- package/src/components/IconButton.tsx +6 -12
- package/src/components/Icons.tsx +3 -3
- package/src/components/InputField.tsx +145 -160
- package/src/components/Label.tsx +4 -4
- package/src/components/LabeledElementView.tsx +35 -41
- package/src/components/ListView.tsx +49 -39
- package/src/components/Message.tsx +75 -0
- package/src/components/Progress.tsx +2 -2
- package/src/components/ScrollArea.tsx +7 -5
- package/src/components/SegmentedControl.tsx +171 -0
- package/src/components/SelectMenu.tsx +61 -10
- package/src/components/Slider.tsx +5 -2
- package/src/components/Sortable.tsx +17 -27
- package/src/components/Spacer.tsx +28 -9
- package/src/components/Switch.tsx +8 -8
- package/src/components/TextArea.tsx +23 -13
- package/src/components/Toolbar.tsx +134 -0
- package/src/components/Tooltip.tsx +10 -7
- package/src/components/internal/Menu.tsx +5 -4
- package/src/contexts/DesignSystemConfiguration.tsx +15 -24
- package/src/contexts/DialogContext.tsx +137 -49
- package/src/contexts/ImageDataContext.tsx +6 -12
- package/src/index.css +1 -3
- package/src/index.tsx +12 -3
- package/src/utils/combobox.ts +369 -0
- package/tailwind.config.ts +1 -2
- package/src/components/RadioGroup.tsx +0 -100
- package/src/utils/completions.ts +0 -21
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import { chunkBy, partition } from "@noya-app/noya-utils";
|
|
2
|
+
import { ReactNode, useSyncExternalStore } from "react";
|
|
3
|
+
import { fuzzyFilter, IScoredItem } from "./fuzzyScorer";
|
|
4
|
+
|
|
5
|
+
export type ComboboxOption = {
|
|
6
|
+
type?: undefined;
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
icon?: ReactNode;
|
|
10
|
+
alwaysInclude?: boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type ComboboxSectionHeader = {
|
|
14
|
+
type: "sectionHeader";
|
|
15
|
+
id: string;
|
|
16
|
+
name: string;
|
|
17
|
+
maxVisibleItems?: number;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type ComboboxItem = ComboboxOption | ComboboxSectionHeader;
|
|
21
|
+
|
|
22
|
+
export type InternalComboboxItem =
|
|
23
|
+
| (ComboboxOption & IScoredItem)
|
|
24
|
+
| ComboboxSectionHeader;
|
|
25
|
+
|
|
26
|
+
export type ComboboxStateSnapshot = {
|
|
27
|
+
filter: string;
|
|
28
|
+
selectedIndex: number;
|
|
29
|
+
filteredItems: InternalComboboxItem[];
|
|
30
|
+
lastSubmittedValue: { filter: string; itemName: string };
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export class ComboboxState {
|
|
34
|
+
private filter: string;
|
|
35
|
+
private selectedIndex: number;
|
|
36
|
+
private items: ComboboxItem[];
|
|
37
|
+
private lastSubmittedValue: { filter: string; itemName: string };
|
|
38
|
+
private subscribers: (() => void)[] = [];
|
|
39
|
+
private version = 0;
|
|
40
|
+
private _cachedSnapshot: [number, ComboboxStateSnapshot] | undefined;
|
|
41
|
+
|
|
42
|
+
constructor(
|
|
43
|
+
items: ComboboxItem[],
|
|
44
|
+
initialValue: string,
|
|
45
|
+
allowArbitraryValues: boolean
|
|
46
|
+
) {
|
|
47
|
+
this.items = items;
|
|
48
|
+
this.lastSubmittedValue = {
|
|
49
|
+
filter: initialValue,
|
|
50
|
+
itemName: initialValue,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Initialize with the initial value
|
|
54
|
+
this.filter = initialValue;
|
|
55
|
+
this.selectedIndex = 0;
|
|
56
|
+
|
|
57
|
+
// Create initial snapshot
|
|
58
|
+
this._cachedSnapshot = [
|
|
59
|
+
0,
|
|
60
|
+
{
|
|
61
|
+
filter: initialValue,
|
|
62
|
+
selectedIndex: 0,
|
|
63
|
+
filteredItems: items.map((item, index) => {
|
|
64
|
+
if (item.type === "sectionHeader") return item;
|
|
65
|
+
return { ...item, index, score: 0 };
|
|
66
|
+
}),
|
|
67
|
+
lastSubmittedValue: this.lastSubmittedValue,
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
// If there's an initial value, validate it
|
|
72
|
+
if (initialValue) {
|
|
73
|
+
// Check if the initial value exists in items
|
|
74
|
+
const hasMatch = items.some(
|
|
75
|
+
(item) =>
|
|
76
|
+
item.type !== "sectionHeader" &&
|
|
77
|
+
item.name.toLowerCase() === initialValue.toLowerCase()
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
// Get filtered items to check for fuzzy matches
|
|
81
|
+
const filteredItems = this.getFilteredItems();
|
|
82
|
+
|
|
83
|
+
// If no matches and we don't want arbitrary values, reset to empty
|
|
84
|
+
if (!hasMatch && !filteredItems.length && !allowArbitraryValues) {
|
|
85
|
+
this.filter = "";
|
|
86
|
+
this.selectedIndex = 0;
|
|
87
|
+
this._cachedSnapshot = [
|
|
88
|
+
0,
|
|
89
|
+
{
|
|
90
|
+
filter: "",
|
|
91
|
+
selectedIndex: 0,
|
|
92
|
+
filteredItems: items.map((item, index) => {
|
|
93
|
+
if (item.type === "sectionHeader") return item;
|
|
94
|
+
return { ...item, index, score: 0 };
|
|
95
|
+
}),
|
|
96
|
+
lastSubmittedValue: this.lastSubmittedValue,
|
|
97
|
+
},
|
|
98
|
+
];
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
subscribe = (listener: () => void): (() => void) => {
|
|
104
|
+
this.subscribers.push(listener);
|
|
105
|
+
return () => {
|
|
106
|
+
this.subscribers = this.subscribers.filter((l) => l !== listener);
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
private notifyChange() {
|
|
111
|
+
this.version++;
|
|
112
|
+
this._cachedSnapshot = undefined;
|
|
113
|
+
this.subscribers.forEach((listener) => listener());
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private getFilteredItems(): InternalComboboxItem[] {
|
|
117
|
+
return filterWithGroupedSections(this.items, this.filter);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
getSnapshot = (): ComboboxStateSnapshot => {
|
|
121
|
+
if (this._cachedSnapshot && this._cachedSnapshot[0] === this.version) {
|
|
122
|
+
return this._cachedSnapshot[1];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const snapshot = {
|
|
126
|
+
filter: this.filter,
|
|
127
|
+
selectedIndex: this.selectedIndex,
|
|
128
|
+
filteredItems: this.getFilteredItems(),
|
|
129
|
+
lastSubmittedValue: this.lastSubmittedValue,
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
this._cachedSnapshot = [this.version, snapshot];
|
|
133
|
+
return snapshot;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
setItems(items: ComboboxItem[]) {
|
|
137
|
+
this.items = items;
|
|
138
|
+
this.notifyChange();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
setFilter(filter: string) {
|
|
142
|
+
this.filter = filter;
|
|
143
|
+
const filteredItems = this.getFilteredItems();
|
|
144
|
+
|
|
145
|
+
// Find exact match if it exists (case insensitive)
|
|
146
|
+
const exactMatchIndex = filteredItems.findIndex(
|
|
147
|
+
(item) =>
|
|
148
|
+
item.type !== "sectionHeader" &&
|
|
149
|
+
item.name.toLowerCase().startsWith(filter.toLowerCase())
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
// Find the first non-header item
|
|
153
|
+
const firstSelectableIndex = filteredItems.findIndex(
|
|
154
|
+
(item) => item.type !== "sectionHeader"
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
this.selectedIndex =
|
|
158
|
+
exactMatchIndex >= 0 ? exactMatchIndex : firstSelectableIndex;
|
|
159
|
+
|
|
160
|
+
this.notifyChange();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
moveSelection(direction: "up" | "down") {
|
|
164
|
+
const filteredItems = this.getFilteredItems();
|
|
165
|
+
this.selectedIndex = getNextIndex(
|
|
166
|
+
filteredItems,
|
|
167
|
+
this.selectedIndex,
|
|
168
|
+
direction === "down" ? "next" : "previous",
|
|
169
|
+
(item) => item.type === "sectionHeader"
|
|
170
|
+
);
|
|
171
|
+
this.notifyChange();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
selectCurrentItem(
|
|
175
|
+
item?: InternalComboboxItem
|
|
176
|
+
): InternalComboboxItem | undefined {
|
|
177
|
+
const itemToSelect = item || this.getFilteredItems()[this.selectedIndex];
|
|
178
|
+
if (!itemToSelect || itemToSelect.type === "sectionHeader") {
|
|
179
|
+
// If no valid item to select and arbitrary values aren't allowed,
|
|
180
|
+
// restore the last valid value
|
|
181
|
+
this.restoreLastSubmittedValue();
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
this.lastSubmittedValue = {
|
|
186
|
+
filter: itemToSelect.name,
|
|
187
|
+
itemName: itemToSelect.name,
|
|
188
|
+
};
|
|
189
|
+
this.filter = itemToSelect.name;
|
|
190
|
+
this.notifyChange();
|
|
191
|
+
return itemToSelect;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
restoreLastSubmittedValue() {
|
|
195
|
+
this.filter = this.lastSubmittedValue.itemName;
|
|
196
|
+
this.notifyChange();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
reset(newFilter = "") {
|
|
200
|
+
this.filter = newFilter;
|
|
201
|
+
this.selectedIndex = 0;
|
|
202
|
+
this.notifyChange();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
getSelectedItem(): InternalComboboxItem | undefined {
|
|
206
|
+
const filteredItems = this.getFilteredItems();
|
|
207
|
+
return filteredItems[this.selectedIndex];
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
getTypeaheadValue(): string | undefined {
|
|
211
|
+
const selectedItem = this.getSelectedItem();
|
|
212
|
+
if (!selectedItem || selectedItem.type === "sectionHeader") return;
|
|
213
|
+
return selectedItem.name;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
setSelectedIndex(index: number) {
|
|
217
|
+
const filteredItems = this.getFilteredItems();
|
|
218
|
+
|
|
219
|
+
// If trying to select a header, find the next selectable item
|
|
220
|
+
if (filteredItems[index]?.type === "sectionHeader") {
|
|
221
|
+
// Look for the next non-header item
|
|
222
|
+
for (let i = index + 1; i < filteredItems.length; i++) {
|
|
223
|
+
if (filteredItems[i]?.type !== "sectionHeader") {
|
|
224
|
+
this.selectedIndex = i;
|
|
225
|
+
this.notifyChange();
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
// If no next item, look backwards
|
|
230
|
+
for (let i = index - 1; i >= 0; i--) {
|
|
231
|
+
if (filteredItems[i]?.type !== "sectionHeader") {
|
|
232
|
+
this.selectedIndex = i;
|
|
233
|
+
this.notifyChange();
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
// If no selectable items found, don't change selection
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
this.selectedIndex = index;
|
|
242
|
+
this.notifyChange();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
submitArbitraryFilter(filter: string) {
|
|
246
|
+
this.lastSubmittedValue = {
|
|
247
|
+
filter,
|
|
248
|
+
itemName: filter,
|
|
249
|
+
};
|
|
250
|
+
this.filter = filter;
|
|
251
|
+
this.notifyChange();
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function filterWithGroupedSections(
|
|
256
|
+
items: ComboboxItem[],
|
|
257
|
+
query: string
|
|
258
|
+
): InternalComboboxItem[] {
|
|
259
|
+
const sections = chunkBy(items, (_a, b) => b.type !== "sectionHeader");
|
|
260
|
+
let result: InternalComboboxItem[] = [];
|
|
261
|
+
let currentIndex = 0;
|
|
262
|
+
|
|
263
|
+
const createExtraItem = (
|
|
264
|
+
item: ComboboxOption,
|
|
265
|
+
index: number
|
|
266
|
+
): InternalComboboxItem => ({
|
|
267
|
+
...item,
|
|
268
|
+
index: currentIndex + index,
|
|
269
|
+
score: 0,
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
const createScoredItem = (
|
|
273
|
+
item: IScoredItem,
|
|
274
|
+
regular: ComboboxOption[]
|
|
275
|
+
): InternalComboboxItem => ({
|
|
276
|
+
...item,
|
|
277
|
+
...regular[item.index],
|
|
278
|
+
index: currentIndex + item.index,
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
for (const section of sections) {
|
|
282
|
+
const [headers, regular] = partition(
|
|
283
|
+
section,
|
|
284
|
+
(item): item is ComboboxSectionHeader => item.type === "sectionHeader"
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
let newItems: InternalComboboxItem[];
|
|
288
|
+
|
|
289
|
+
if (!query.trim()) {
|
|
290
|
+
// For empty query, include all items with score 0
|
|
291
|
+
newItems = regular.map((item, index) => createExtraItem(item, index));
|
|
292
|
+
} else {
|
|
293
|
+
const scoredItems = fuzzyFilter({
|
|
294
|
+
items: regular.map((item) => item.name),
|
|
295
|
+
query,
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
const usedIndexes = new Set(scoredItems.map((item) => item.index));
|
|
299
|
+
const extraItems = regular.flatMap(
|
|
300
|
+
(item, index): InternalComboboxItem[] =>
|
|
301
|
+
item.alwaysInclude && !usedIndexes.has(index)
|
|
302
|
+
? [createExtraItem(item, index)]
|
|
303
|
+
: []
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
newItems = scoredItems
|
|
307
|
+
.map((item) => createScoredItem(item, regular))
|
|
308
|
+
.concat(extraItems);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// If we have items or this is an empty query
|
|
312
|
+
if (newItems.length > 0 || !query.trim()) {
|
|
313
|
+
const maxVisibleItems =
|
|
314
|
+
headers.length > 0 ? headers[0].maxVisibleItems : undefined;
|
|
315
|
+
|
|
316
|
+
if (maxVisibleItems !== undefined) {
|
|
317
|
+
newItems = newItems.slice(0, maxVisibleItems);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
result.push(...headers, ...newItems);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
currentIndex += regular.length;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return result;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function getNextIndex<T>(
|
|
330
|
+
items: T[],
|
|
331
|
+
currentIndex: number,
|
|
332
|
+
direction: "next" | "previous",
|
|
333
|
+
isDisabled: (item: T) => boolean
|
|
334
|
+
): number {
|
|
335
|
+
// Make sure the current index is within bounds
|
|
336
|
+
currentIndex =
|
|
337
|
+
currentIndex < 0
|
|
338
|
+
? 0
|
|
339
|
+
: currentIndex >= items.length
|
|
340
|
+
? items.length - 1
|
|
341
|
+
: currentIndex;
|
|
342
|
+
|
|
343
|
+
// If there are no valid items in the array, return -1
|
|
344
|
+
if (items.every(isDisabled)) return -1;
|
|
345
|
+
|
|
346
|
+
let nextIndex = currentIndex;
|
|
347
|
+
|
|
348
|
+
do {
|
|
349
|
+
// Move to the next or previous index, wrapping around if necessary
|
|
350
|
+
if (direction === "next") {
|
|
351
|
+
nextIndex = (nextIndex + 1) % items.length;
|
|
352
|
+
} else {
|
|
353
|
+
nextIndex = (nextIndex - 1 + items.length) % items.length;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// If we've looped all the way around without finding a valid item, return the current index
|
|
357
|
+
if (nextIndex === currentIndex) return currentIndex;
|
|
358
|
+
} while (isDisabled(items[nextIndex]));
|
|
359
|
+
|
|
360
|
+
return nextIndex;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export function useComboboxState(state: ComboboxState) {
|
|
364
|
+
return useSyncExternalStore(
|
|
365
|
+
state.subscribe,
|
|
366
|
+
state.getSnapshot,
|
|
367
|
+
state.getSnapshot
|
|
368
|
+
);
|
|
369
|
+
}
|
package/tailwind.config.ts
CHANGED
|
@@ -46,7 +46,7 @@ const config = {
|
|
|
46
46
|
"listview-editing-background": "var(--listview-editing-background)",
|
|
47
47
|
"slider-thumb-background": "var(--slider-thumb-background)",
|
|
48
48
|
"slider-border": "var(--slider-border)",
|
|
49
|
-
"
|
|
49
|
+
"segmented-control-item": "var(--segmented-control-item)",
|
|
50
50
|
mask: "var(--mask)",
|
|
51
51
|
"transparent-checker": "var(--transparent-checker)",
|
|
52
52
|
scrollbar: "var(--scrollbar)",
|
|
@@ -62,7 +62,6 @@ const config = {
|
|
|
62
62
|
icon: "var(--icon)",
|
|
63
63
|
"icon-selected": "var(--icon-selected)",
|
|
64
64
|
warning: "var(--warning)",
|
|
65
|
-
"radio-group-item": "var(--radio-group-item)",
|
|
66
65
|
dot: "var(--dot)",
|
|
67
66
|
"row-highlight": "var(--row-highlight)",
|
|
68
67
|
"table-row-background": "var(--table-row-background)",
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
|
|
2
|
-
import React, {
|
|
3
|
-
createContext,
|
|
4
|
-
forwardRef,
|
|
5
|
-
memo,
|
|
6
|
-
ReactNode,
|
|
7
|
-
useCallback,
|
|
8
|
-
useContext,
|
|
9
|
-
useMemo,
|
|
10
|
-
} from "react";
|
|
11
|
-
import { Tooltip } from "./Tooltip";
|
|
12
|
-
|
|
13
|
-
type RadioGroupColorScheme = "primary" | "secondary";
|
|
14
|
-
interface ItemProps {
|
|
15
|
-
value: string;
|
|
16
|
-
tooltip?: ReactNode;
|
|
17
|
-
children: ReactNode;
|
|
18
|
-
disabled?: boolean;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const ToggleGroupItem = forwardRef(function ToggleGroupItem(
|
|
22
|
-
{ value, tooltip, children, disabled = false }: ItemProps,
|
|
23
|
-
forwardedRef: React.ForwardedRef<HTMLButtonElement>
|
|
24
|
-
) {
|
|
25
|
-
const { colorScheme } = useContext(RadioGroupContext);
|
|
26
|
-
|
|
27
|
-
const itemElement = (
|
|
28
|
-
<ToggleGroupPrimitive.Item
|
|
29
|
-
ref={forwardedRef}
|
|
30
|
-
value={value}
|
|
31
|
-
disabled={disabled}
|
|
32
|
-
className={`font-sans text-heading5 font-normal relative flex-1 appearance-none border-none bg-none text-radio-group-item p-0 m-0 rounded inline-flex items-center justify-center align-middle focus:outline-none focus:shadow-[0_0_0_1px_var(--sidebar-background),0_0_0_3px_${colorScheme === "secondary" ? "var(--secondary)" : "var(--primary)"}] aria-checked:bg-${colorScheme ? colorScheme : "radio-group-background"} aria-checked:text-${colorScheme ? "radio-group-background" : "text"} aria-checked:shadow-${colorScheme ? "none" : "0_1px_1px_rgba(0,0,0,0.1)"} focus:z-interactable`}
|
|
33
|
-
>
|
|
34
|
-
{children}
|
|
35
|
-
</ToggleGroupPrimitive.Item>
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
return tooltip ? (
|
|
39
|
-
<Tooltip content={tooltip}>{itemElement}</Tooltip>
|
|
40
|
-
) : (
|
|
41
|
-
itemElement
|
|
42
|
-
);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
type RadioGroupContextValue = {
|
|
46
|
-
/** @default primary */
|
|
47
|
-
colorScheme?: RadioGroupColorScheme;
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
const RadioGroupContext = createContext<RadioGroupContextValue>({
|
|
51
|
-
colorScheme: "primary",
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
interface Props {
|
|
55
|
-
id?: string;
|
|
56
|
-
value?: string;
|
|
57
|
-
onValueChange?: (value: string) => void;
|
|
58
|
-
/** @default primary */
|
|
59
|
-
colorScheme?: RadioGroupColorScheme;
|
|
60
|
-
allowEmpty?: boolean;
|
|
61
|
-
children: ReactNode;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function ToggleGroupRoot({
|
|
65
|
-
id,
|
|
66
|
-
value,
|
|
67
|
-
onValueChange,
|
|
68
|
-
colorScheme,
|
|
69
|
-
allowEmpty,
|
|
70
|
-
children,
|
|
71
|
-
}: Props) {
|
|
72
|
-
const contextValue = useMemo(() => ({ colorScheme }), [colorScheme]);
|
|
73
|
-
|
|
74
|
-
const handleValueChange = useCallback(
|
|
75
|
-
(value: string) => {
|
|
76
|
-
if (!allowEmpty && !value) return;
|
|
77
|
-
onValueChange?.(value);
|
|
78
|
-
},
|
|
79
|
-
[allowEmpty, onValueChange]
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
return (
|
|
83
|
-
<RadioGroupContext.Provider value={contextValue}>
|
|
84
|
-
<ToggleGroupPrimitive.Root
|
|
85
|
-
id={id}
|
|
86
|
-
type="single"
|
|
87
|
-
value={value}
|
|
88
|
-
onValueChange={handleValueChange}
|
|
89
|
-
className={`appearance-none w-0 flex-1 relative border-0 outline-none min-w-0 min-h-[27px] text-left self-stretch rounded bg-input-background items-stretch focus:shadow-[0_0_0_1px_var(--sidebar-background),0_0_0_3px_${colorScheme === "secondary" ? "var(--secondary)" : "var(--primary)"}] ${colorScheme ? "p-0" : "p-0.5"}`}
|
|
90
|
-
>
|
|
91
|
-
{children}
|
|
92
|
-
</ToggleGroupPrimitive.Root>
|
|
93
|
-
</RadioGroupContext.Provider>
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export namespace RadioGroup {
|
|
98
|
-
export const Root = memo(ToggleGroupRoot);
|
|
99
|
-
export const Item = memo(ToggleGroupItem);
|
|
100
|
-
}
|
package/src/utils/completions.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { ReactNode } from 'react';
|
|
2
|
-
import { IScoredItem } from './fuzzyScorer';
|
|
3
|
-
|
|
4
|
-
export type CompletionItem = {
|
|
5
|
-
type?: undefined;
|
|
6
|
-
id: string;
|
|
7
|
-
name: string;
|
|
8
|
-
icon?: ReactNode;
|
|
9
|
-
alwaysInclude?: boolean;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export type CompletionSectionHeader = {
|
|
13
|
-
type: 'sectionHeader';
|
|
14
|
-
id: string;
|
|
15
|
-
name: string;
|
|
16
|
-
maxVisibleItems?: number;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export type CompletionListItem =
|
|
20
|
-
| (CompletionItem & IScoredItem)
|
|
21
|
-
| CompletionSectionHeader;
|