@lotics/ui 5.9.0 → 5.10.0

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 (97) hide show
  1. package/AGENTS.md +127 -44
  2. package/examples/tpl_approvals.tsx +3 -3
  3. package/examples/tpl_dossier.tsx +5 -4
  4. package/examples/tpl_pipeline.tsx +400 -0
  5. package/examples/tpl_record.tsx +5 -4
  6. package/examples/tpl_shifts.tsx +7 -5
  7. package/examples/tpl_task_board.tsx +473 -0
  8. package/examples/tpl_tasks.tsx +440 -0
  9. package/package.json +10 -5
  10. package/src/accordion.tsx +1 -1
  11. package/src/agent_progress.tsx +1 -1
  12. package/src/agent_run.tsx +1 -1
  13. package/src/breakdown.tsx +2 -2
  14. package/src/button.tsx +12 -3
  15. package/src/calendar/calendar_view.tsx +10 -10
  16. package/src/calendar/month_view.tsx +30 -11
  17. package/src/calendar/time_grid_view.tsx +25 -7
  18. package/src/card_select_item.tsx +11 -13
  19. package/src/check_circle.tsx +88 -0
  20. package/src/checkbox_input.tsx +5 -1
  21. package/src/chip.tsx +1 -1
  22. package/src/chip_group.tsx +1 -1
  23. package/src/column_filter.tsx +3 -2
  24. package/src/combobox.tsx +84 -219
  25. package/src/confidence.tsx +14 -3
  26. package/src/control_surface.ts +22 -7
  27. package/src/data_grid.tsx +159 -0
  28. package/src/date_calendar.tsx +46 -23
  29. package/src/date_field.tsx +10 -2
  30. package/src/date_filter.tsx +5 -9
  31. package/src/date_picker.tsx +78 -26
  32. package/src/date_picker_value.ts +11 -0
  33. package/src/date_range_filter_field.tsx +6 -4
  34. package/src/drawer.tsx +1 -1
  35. package/src/file_dropzone.tsx +5 -0
  36. package/src/file_gallery_modal.tsx +43 -42
  37. package/src/file_preview.web.tsx +25 -11
  38. package/src/file_preview_types.ts +9 -0
  39. package/src/file_row.tsx +5 -1
  40. package/src/file_rows.tsx +6 -2
  41. package/src/file_thumbnail.tsx +13 -4
  42. package/src/file_thumbnail_grid.tsx +5 -0
  43. package/src/files_editor.tsx +232 -0
  44. package/src/filter_chip.tsx +25 -12
  45. package/src/focus_ring_pressable.tsx +34 -0
  46. package/src/form_picker.tsx +3 -3
  47. package/src/gantt/gantt_view.tsx +25 -7
  48. package/src/heatmap.tsx +5 -1
  49. package/src/icon_button.tsx +2 -2
  50. package/src/image_gallery.tsx +8 -10
  51. package/src/index.css +8 -12
  52. package/src/inline_date_picker.tsx +11 -11
  53. package/src/inline_edit.tsx +22 -6
  54. package/src/inline_member_select.tsx +1 -1
  55. package/src/inline_select.tsx +9 -8
  56. package/src/inline_text_input.tsx +4 -1
  57. package/src/link.tsx +11 -1
  58. package/src/link_button.tsx +1 -1
  59. package/src/list_item.tsx +2 -2
  60. package/src/member_select.tsx +9 -4
  61. package/src/menu_button.tsx +2 -2
  62. package/src/menu_list_item.tsx +2 -2
  63. package/src/number_input.tsx +11 -4
  64. package/src/option_list.tsx +211 -0
  65. package/src/peek.tsx +1 -1
  66. package/src/picker.tsx +24 -213
  67. package/src/pressable_highlight.tsx +42 -23
  68. package/src/radio_picker.tsx +2 -2
  69. package/src/range_slider.tsx +35 -7
  70. package/src/react_native.d.ts +3 -0
  71. package/src/scroll_to_bottom.tsx +1 -1
  72. package/src/section.tsx +7 -2
  73. package/src/segmented_control.tsx +2 -2
  74. package/src/select.tsx +299 -0
  75. package/src/sort_header.tsx +1 -1
  76. package/src/sources.tsx +1 -1
  77. package/src/spreadsheet_view.tsx +43 -17
  78. package/src/status_grid.tsx +4 -3
  79. package/src/stepper.tsx +1 -1
  80. package/src/switch.tsx +5 -1
  81. package/src/switch_button.tsx +1 -1
  82. package/src/switcher.tsx +1 -1
  83. package/src/table.tsx +5 -1
  84. package/src/tabs.tsx +2 -2
  85. package/src/text_input_field.tsx +15 -4
  86. package/src/time_picker.tsx +11 -4
  87. package/src/timeline.tsx +1 -1
  88. package/src/use_focus_ring.ts +80 -0
  89. package/src/use_hover.ts +26 -0
  90. package/src/use_list_keyboard_nav.test.ts +71 -0
  91. package/src/use_list_keyboard_nav.ts +52 -5
  92. package/src/use_option_list.test.ts +193 -0
  93. package/src/use_option_list.ts +354 -0
  94. package/src/command_menu.tsx +0 -205
  95. package/src/picker_menu.tsx +0 -355
  96. package/src/tag_input.tsx +0 -203
  97. package/src/time_field.tsx +0 -297
@@ -1,355 +0,0 @@
1
- import { StyleSheet, View, ScrollView, TextInput } from "react-native";
2
- import { useState, useCallback, useMemo, useRef } from "react";
3
- import { colors } from "./colors";
4
- import { Text } from "./text";
5
- import { Icon } from "./icon";
6
- import { Checkbox } from "./checkbox";
7
- import { MenuButton } from "./menu_button";
8
- import { LinkButton } from "./link_button";
9
- import { ActivityIndicator } from "./activity_indicator";
10
- import { PickerOption, PickerValue, PickerOnValueChange, PickerOnClose } from "./picker";
11
- import { useScreenSize } from "./use_screen_size";
12
-
13
- export interface PickerMenuProps<T extends string = string, MULTI extends boolean = false> {
14
- testID?: string;
15
- options: (PickerOption<T> | undefined | false)[];
16
- multi?: MULTI;
17
- value?: PickerValue<T, MULTI> | null;
18
- onValueChange?: PickerOnValueChange<T, MULTI>;
19
- onClose?: PickerOnClose<T, MULTI>;
20
- onRequestClose?: () => void;
21
- renderOptionContent?: (option: PickerOption<T>) => React.ReactNode;
22
- enableSelectAll?: boolean;
23
- includeEmptyOption?: boolean;
24
- /** Show a loading indicator (e.g. while async results are in flight — the
25
- * parent owns fetching and passes the resulting `options`). */
26
- loading?: boolean;
27
- /** Shown when there are no options and not loading. Default: "No results". */
28
- emptyText?: string;
29
- /** Label for the "select all" link in multi-select mode. Default: "Select all" */
30
- selectAllLabel?: string;
31
- /** Label for the "deselect all" link in multi-select mode. Default: "Deselect all" */
32
- deselectAllLabel?: string;
33
- }
34
-
35
- /**
36
- * The option list for a Picker — a keyboard-navigable list with native-`<select>`
37
- * typeahead (type to jump focus). It has NO search box: filtering a long/async
38
- * list is the job of `Combobox`, whose input drives the search and passes the
39
- * already-filtered `options` to render. Use standalone inside a Popover, or via
40
- * the `Picker` component.
41
- */
42
- export function PickerMenu<T extends string, MULTI extends boolean = false>(
43
- props: PickerMenuProps<T, MULTI>,
44
- ) {
45
- const {
46
- testID,
47
- options,
48
- multi = false as MULTI,
49
- value,
50
- renderOptionContent,
51
- onValueChange,
52
- onClose,
53
- onRequestClose,
54
- enableSelectAll,
55
- includeEmptyOption,
56
- loading,
57
- emptyText = "No results",
58
- selectAllLabel = "Select all",
59
- deselectAllLabel = "Deselect all",
60
- } = props;
61
- const scrollViewRef = useRef<ScrollView>(null);
62
- const OPTION_HEIGHT = 40;
63
- const screenSize = useScreenSize();
64
-
65
- const { multiValue, singleValue } = useMemo((): {
66
- multiValue: T[];
67
- singleValue: T | undefined | null;
68
- } => {
69
- if (multi) {
70
- return {
71
- multiValue: Array.isArray(value) ? (value as T[]) : [],
72
- singleValue: undefined,
73
- };
74
- }
75
- return {
76
- multiValue: [],
77
- singleValue: Array.isArray(value) ? (value as T[])[0] : (value as T | undefined | null),
78
- };
79
- }, [multi, value]);
80
-
81
- const displayOptions = useMemo(() => {
82
- if (includeEmptyOption) {
83
- const emptyOption: PickerOption<T> = { label: "", value: "" as T };
84
- return [emptyOption, ...options];
85
- }
86
- return options;
87
- }, [options, includeEmptyOption]);
88
-
89
- const [focusedIndex, setFocusedIndex] = useState<number>(() => {
90
- const selectedIndex = displayOptions.findIndex((opt) => {
91
- if (!opt || opt.disabled) return false;
92
- if (multi && Array.isArray(multiValue)) {
93
- return multiValue.includes(opt.value);
94
- }
95
- return opt.value === singleValue;
96
- });
97
- if (selectedIndex >= 0) return selectedIndex;
98
- const firstValid = displayOptions.findIndex((opt) => opt && !opt.disabled);
99
- return firstValid >= 0 ? firstValid : 0;
100
- });
101
-
102
- const handleClose = useCallback(() => {
103
- onRequestClose?.();
104
- if (onClose) {
105
- if (multi) {
106
- (onClose as (value: T[]) => void)((multiValue ?? []) as T[]);
107
- } else {
108
- (onClose as (value: T) => void)((singleValue ?? "") as T);
109
- }
110
- }
111
- }, [multi, multiValue, singleValue, onClose, onRequestClose]);
112
-
113
- const handleMultiSelect = useCallback(
114
- (itemValue: T) => {
115
- if (!multi || !onValueChange) return;
116
- const currentValues = multiValue ?? [];
117
- const newValue = currentValues.includes(itemValue)
118
- ? currentValues.filter((v) => v !== itemValue)
119
- : [...currentValues, itemValue];
120
- (onValueChange as (value: T[]) => void)(newValue);
121
- },
122
- [multi, multiValue, onValueChange],
123
- );
124
-
125
- const handleSingleSelect = useCallback(
126
- (itemValue: T) => {
127
- if (multi) return;
128
- if (onValueChange) {
129
- (onValueChange as (value: T) => void)(itemValue);
130
- }
131
- handleClose();
132
- },
133
- [multi, onValueChange, handleClose],
134
- );
135
-
136
- const handleSelectAll = useCallback(() => {
137
- if (!multi || !onValueChange) return;
138
- const allValues = options
139
- .filter((opt): opt is PickerOption<T> => !!opt && !opt.disabled)
140
- .map((opt) => opt.value);
141
- (onValueChange as (value: T[]) => void)(allValues);
142
- }, [multi, onValueChange, options]);
143
-
144
- const handleDeselectAll = useCallback(() => {
145
- if (!multi || !onValueChange) return;
146
- (onValueChange as (value: T[]) => void)([]);
147
- }, [multi, onValueChange]);
148
-
149
- const scrollToIndex = useCallback((index: number) => {
150
- if (scrollViewRef.current && index >= 0) {
151
- const scrollY = index * OPTION_HEIGHT;
152
- scrollViewRef.current.scrollTo({ y: scrollY - 80, animated: false });
153
- }
154
- }, []);
155
-
156
- const handleKeyNavigation = useCallback(
157
- (key: string): boolean => {
158
- switch (key) {
159
- case "ArrowDown": {
160
- let next = focusedIndex + 1;
161
- while (next < displayOptions.length) {
162
- const opt = displayOptions[next];
163
- if (opt && !opt.disabled) {
164
- setFocusedIndex(next);
165
- scrollToIndex(next);
166
- break;
167
- }
168
- next++;
169
- }
170
- return true;
171
- }
172
- case "ArrowUp": {
173
- let next = focusedIndex - 1;
174
- while (next >= 0) {
175
- const opt = displayOptions[next];
176
- if (opt && !opt.disabled) {
177
- setFocusedIndex(next);
178
- scrollToIndex(next);
179
- break;
180
- }
181
- next--;
182
- }
183
- return true;
184
- }
185
- case "Enter":
186
- if (focusedIndex >= 0 && focusedIndex < displayOptions.length) {
187
- const focusedOption = displayOptions[focusedIndex];
188
- if (focusedOption && !focusedOption.disabled) {
189
- if (multi) handleMultiSelect(focusedOption.value);
190
- else handleSingleSelect(focusedOption.value);
191
- }
192
- }
193
- return true;
194
- case "Escape":
195
- handleClose();
196
- return true;
197
- case "Tab":
198
- handleClose();
199
- return false;
200
- default:
201
- return false;
202
- }
203
- },
204
- [focusedIndex, displayOptions, multi, handleMultiSelect, handleSingleSelect, handleClose, scrollToIndex],
205
- );
206
-
207
- // Native-select typeahead: accumulate printable keys into a short-lived buffer
208
- // and jump focus to the first option whose label starts with it.
209
- const typeaheadBufferRef = useRef("");
210
- const typeaheadTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
211
-
212
- const handleTypeahead = useCallback(
213
- (char: string) => {
214
- if (typeaheadTimerRef.current) clearTimeout(typeaheadTimerRef.current);
215
- typeaheadBufferRef.current += char.toLowerCase();
216
- const buffer = typeaheadBufferRef.current;
217
- const match = displayOptions.findIndex(
218
- (opt) => opt && !opt.disabled && (opt.label ?? "").toLowerCase().startsWith(buffer),
219
- );
220
- if (match >= 0) {
221
- setFocusedIndex(match);
222
- scrollToIndex(match);
223
- }
224
- typeaheadTimerRef.current = setTimeout(() => {
225
- typeaheadBufferRef.current = "";
226
- }, 600);
227
- },
228
- [displayOptions, scrollToIndex],
229
- );
230
-
231
- const handleKeyPress = useCallback(
232
- (e: { nativeEvent: { key: string }; preventDefault: () => void }) => {
233
- const key = e.nativeEvent.key;
234
- if (handleKeyNavigation(key)) {
235
- e.preventDefault();
236
- return;
237
- }
238
- if (key.length === 1) handleTypeahead(key);
239
- },
240
- [handleKeyNavigation, handleTypeahead],
241
- );
242
-
243
- const hasOptions = displayOptions.some((opt) => opt && !opt.disabled);
244
- const hasSelectedOptions = multi && Array.isArray(multiValue) && multiValue.length > 0;
245
- const showSelectAllLink = multi && hasOptions && enableSelectAll;
246
- const showDeselectAllLink = multi && hasOptions && hasSelectedOptions && enableSelectAll;
247
- const showLinks = showSelectAllLink || showDeselectAllLink;
248
-
249
- return (
250
- <View
251
- testID={testID}
252
- style={[styles.container, screenSize.small ? { flex: 1 } : { maxHeight: 480 }]}
253
- >
254
- {/* Visually hidden but focusable: captures typeahead + arrow/enter keys
255
- while the popover is open, giving native-<select> behavior. */}
256
- <TextInput
257
- autoFocus={!screenSize.small}
258
- value=""
259
- onChangeText={() => {}}
260
- onKeyPress={handleKeyPress}
261
- style={styles.typeaheadCapture}
262
- />
263
- <ScrollView ref={scrollViewRef} style={styles.optionsList}>
264
- {loading && (
265
- <View style={styles.statusRow}>
266
- <ActivityIndicator />
267
- </View>
268
- )}
269
- {!loading && displayOptions.length === 0 && (
270
- <View style={styles.statusRow}>
271
- <Text color="zinc-500" size="sm">
272
- {emptyText}
273
- </Text>
274
- </View>
275
- )}
276
- {displayOptions.map((item, index) => {
277
- if (!item) return null;
278
-
279
- const isSelected = multi
280
- ? Array.isArray(multiValue) && multiValue.includes(item.value)
281
- : item.value === singleValue;
282
- const isFocused = index === focusedIndex;
283
-
284
- return (
285
- <MenuButton
286
- key={item.value}
287
- testID={item.testID || `picker-option-${item.value}`}
288
- icon={multi ? <Checkbox checked={isSelected} /> : undefined}
289
- title={
290
- renderOptionContent ? (
291
- renderOptionContent(item)
292
- ) : (
293
- <Text userSelect="none" numberOfLines={1}>
294
- {item.label}
295
- </Text>
296
- )
297
- }
298
- right={
299
- !multi && isSelected ? (
300
- <Icon name="check" size={18} color={colors.zinc["950"]} />
301
- ) : undefined
302
- }
303
- focused={isFocused}
304
- disabled={item.disabled}
305
- onPress={() => (multi ? handleMultiSelect(item.value) : handleSingleSelect(item.value))}
306
- onHoverIn={() => setFocusedIndex(index)}
307
- style={styles.option}
308
- />
309
- );
310
- })}
311
- </ScrollView>
312
- {showLinks && (
313
- <View style={styles.selectAllContainer}>
314
- {showSelectAllLink && <LinkButton title={selectAllLabel} onPress={handleSelectAll} />}
315
- {showDeselectAllLink && <LinkButton title={deselectAllLabel} onPress={handleDeselectAll} />}
316
- </View>
317
- )}
318
- </View>
319
- );
320
- }
321
-
322
- const styles = StyleSheet.create({
323
- container: {
324
- gap: 4,
325
- },
326
- optionsList: {
327
- flex: 1,
328
- gap: 4,
329
- },
330
- selectAllContainer: {
331
- flexDirection: "row",
332
- alignItems: "center",
333
- gap: 16,
334
- paddingVertical: 4,
335
- // Matches a MenuButton option's 8px text inset, so the bleeding-wash
336
- // LinkButtons line up under the option labels above.
337
- paddingHorizontal: 8,
338
- borderTopWidth: 1,
339
- borderTopColor: colors.border,
340
- },
341
- option: {
342
- marginHorizontal: 0,
343
- },
344
- statusRow: {
345
- alignItems: "center",
346
- justifyContent: "center",
347
- paddingVertical: 16,
348
- },
349
- typeaheadCapture: {
350
- position: "absolute",
351
- width: 1,
352
- height: 1,
353
- opacity: 0,
354
- },
355
- });
package/src/tag_input.tsx DELETED
@@ -1,203 +0,0 @@
1
- import { useMemo, useRef, useState } from "react";
2
- import { Pressable, ScrollView, StyleSheet, View, type StyleProp, type ViewStyle } from "react-native";
3
- import { colors } from "./colors";
4
- import { CONTROL_RADIUS } from "./control_surface";
5
- import { Text } from "./text";
6
- import { Icon } from "./icon";
7
- import { TextInputField } from "./text_input_field";
8
- import { MenuButton } from "./menu_button";
9
- import { Popover, PopoverContent } from "./popover";
10
-
11
- export interface TagOption {
12
- value: string;
13
- label: string;
14
- }
15
-
16
- export interface TagInputProps {
17
- /** Selected tags. */
18
- value: TagOption[];
19
- /** The suggestible set. */
20
- options: TagOption[];
21
- onChange: (next: TagOption[]) => void;
22
- /** Offer a "Create …" row when the query matches no option. Default false. */
23
- allowCreate?: boolean;
24
- placeholder?: string;
25
- accessibilityLabel?: string;
26
- style?: StyleProp<ViewStyle>;
27
- }
28
-
29
- /**
30
- * Multi-select tag field. The CONTROL is a bordered box of removable chips plus
31
- * an Add affordance; the typing/searching/creating happens in a POPOVER (a
32
- * searchable, checkable list + an optional create row) — never an inline token
33
- * input squeezed among the chips. So the field always reads as a clean container
34
- * that matches its border, instead of a text box fighting the chips. Distinct
35
- * from `Combobox multi` (a search field whose selection renders as in-input chips):
36
- * reach for `TagInput` when the field's resting state should be a tidy chip box.
37
- */
38
- export function TagInput(props: TagInputProps) {
39
- const { value, options, onChange, allowCreate = false, placeholder = "Add tags", accessibilityLabel, style } = props;
40
- const [open, setOpen] = useState(false);
41
- const [query, setQuery] = useState("");
42
- const anchorRef = useRef<View>(null);
43
-
44
- const selected = useMemo(() => new Set(value.map((t) => t.value)), [value]);
45
-
46
- const filtered = useMemo(() => {
47
- const q = query.trim().toLowerCase();
48
- return q ? options.filter((o) => o.label.toLowerCase().includes(q)) : options;
49
- }, [options, query]);
50
-
51
- const exact = useMemo(() => {
52
- const q = query.trim().toLowerCase();
53
- return q.length > 0 && [...options, ...value].some((o) => o.label.toLowerCase() === q);
54
- }, [options, value, query]);
55
-
56
- const showCreate = allowCreate && query.trim().length > 0 && !exact;
57
-
58
- const toggle = (o: TagOption) =>
59
- onChange(selected.has(o.value) ? value.filter((t) => t.value !== o.value) : [...value, o]);
60
-
61
- const create = () => {
62
- const label = query.trim();
63
- if (!label) return;
64
- const v = label.toLowerCase().replaceAll(" ", "_");
65
- if (!selected.has(v)) onChange([...value, { value: v, label }]);
66
- setQuery("");
67
- };
68
-
69
- return (
70
- <>
71
- <View ref={anchorRef} style={[styles.box, style]} accessibilityLabel={accessibilityLabel}>
72
- {value.map((t) => (
73
- <View key={t.value} style={styles.chip}>
74
- <Text size="sm">{t.label}</Text>
75
- <Pressable
76
- onPress={() => onChange(value.filter((x) => x.value !== t.value))}
77
- accessibilityRole="button"
78
- accessibilityLabel={`Remove ${t.label}`}
79
- style={styles.chipRemove}
80
- hitSlop={6}
81
- >
82
- <Icon name="x" size={12} color={colors.zinc[500]} />
83
- </Pressable>
84
- </View>
85
- ))}
86
- <Pressable
87
- onPress={() => setOpen(true)}
88
- accessibilityRole="button"
89
- accessibilityLabel={accessibilityLabel ?? "Add tag"}
90
- style={styles.add}
91
- >
92
- <Icon name="plus" size={14} color={colors.zinc[500]} />
93
- <Text size="sm" color="muted">
94
- {value.length === 0 ? placeholder : "Add"}
95
- </Text>
96
- </Pressable>
97
- </View>
98
-
99
- <Popover
100
- open={open}
101
- onOpenChange={(o) => {
102
- setOpen(o);
103
- if (!o) setQuery("");
104
- }}
105
- triggerRef={anchorRef}
106
- side="bottom"
107
- align="start"
108
- offset={4}
109
- inheritTriggerWidth
110
- >
111
- <PopoverContent>
112
- <View style={styles.menu}>
113
- <TextInputField
114
- value={query}
115
- onChangeText={setQuery}
116
- icon="search"
117
- placeholder={allowCreate ? "Search or create…" : "Search…"}
118
- autoFocus
119
- autoCapitalize="none"
120
- autoCorrect={false}
121
- onKeyPress={(e: { nativeEvent: { key: string }; preventDefault: () => void }) => {
122
- if (e.nativeEvent.key === "Enter") {
123
- e.preventDefault();
124
- if (showCreate) create();
125
- else if (filtered.length === 1) toggle(filtered[0]);
126
- }
127
- }}
128
- />
129
- <ScrollView style={styles.list} keyboardShouldPersistTaps="handled">
130
- {filtered.map((o) => (
131
- <MenuButton
132
- key={o.value}
133
- title={o.label}
134
- right={selected.has(o.value) ? <Icon name="check" size={16} color={colors.zinc[900]} /> : undefined}
135
- onPress={() => toggle(o)}
136
- />
137
- ))}
138
- {showCreate ? <MenuButton title={`Create “${query.trim()}”`} icon="plus" onPress={create} /> : null}
139
- {filtered.length === 0 && !showCreate ? (
140
- <View style={styles.empty}>
141
- <Text size="sm" color="muted">
142
- No tags found
143
- </Text>
144
- </View>
145
- ) : null}
146
- </ScrollView>
147
- </View>
148
- </PopoverContent>
149
- </Popover>
150
- </>
151
- );
152
- }
153
-
154
- const styles = StyleSheet.create({
155
- box: {
156
- flexDirection: "row",
157
- flexWrap: "wrap",
158
- alignItems: "center",
159
- gap: 6,
160
- minHeight: 40,
161
- paddingHorizontal: 6,
162
- paddingVertical: 5,
163
- borderWidth: 1,
164
- borderColor: colors.border,
165
- borderRadius: CONTROL_RADIUS,
166
- backgroundColor: colors.background,
167
- },
168
- chip: {
169
- flexDirection: "row",
170
- alignItems: "center",
171
- gap: 4,
172
- paddingLeft: 8,
173
- paddingRight: 4,
174
- paddingVertical: 3,
175
- borderRadius: 6,
176
- backgroundColor: colors.zinc[100],
177
- },
178
- chipRemove: {
179
- padding: 2,
180
- borderRadius: 4,
181
- cursor: "pointer",
182
- },
183
- add: {
184
- flexDirection: "row",
185
- alignItems: "center",
186
- gap: 4,
187
- paddingHorizontal: 6,
188
- paddingVertical: 4,
189
- cursor: "pointer",
190
- },
191
- menu: {
192
- gap: 6,
193
- padding: 6,
194
- minWidth: 240,
195
- },
196
- list: {
197
- maxHeight: 240,
198
- },
199
- empty: {
200
- paddingVertical: 12,
201
- alignItems: "center",
202
- },
203
- });