@lotics/ui 45.4.0 → 45.5.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.
- package/MIGRATION.md +26 -0
- package/docs/catalog.md +8 -2
- package/docs/data_entry.md +8 -1
- package/examples/tpl_task_board.tsx +12 -1
- package/package.json +1 -1
- package/src/inline_edit.tsx +21 -4
- package/src/inline_select.tsx +52 -10
- package/src/option_list.tsx +9 -1
package/MIGRATION.md
CHANGED
|
@@ -4,6 +4,32 @@ Breaking changes, newest first — normally per major, plus the rare minor that
|
|
|
4
4
|
anyway (recorded under its exact version). The current contract lives in `AGENTS.md` + `docs/`;
|
|
5
5
|
this file exists only to move an app from one release to the next.
|
|
6
6
|
|
|
7
|
+
## 45.5.0
|
|
8
|
+
|
|
9
|
+
**`InlineSelect` and `InlineMemberSelect` are a `combobox`, not a `button`.** They rest as a
|
|
10
|
+
`<div role="combobox">` carrying `aria-expanded`, where they rested as a `<button>`
|
|
11
|
+
(`InlineMemberSelect` is built on `InlineSelect`, so it follows). Other inline fields are
|
|
12
|
+
unchanged. Nothing to change in your code — but a test
|
|
13
|
+
that reached one with `getByRole("button")` now finds nothing, and wants `getByRole("combobox")`.
|
|
14
|
+
The old role was wrong twice over: it is not the pattern APG names for a control that holds a
|
|
15
|
+
value and opens a list, and a `<button>` may not contain one, which is what kept a tag field's
|
|
16
|
+
chips from carrying their own remove ✕.
|
|
17
|
+
|
|
18
|
+
**A multi `InlineSelect`'s menu rows no longer fall back to `renderSelected`.** Pass
|
|
19
|
+
`renderOptionContent` for custom rows; without it the list shows plain labels. `renderSelected`
|
|
20
|
+
now means the removable RESTING chip and takes `(option, { remove })`, so borrowing it for the
|
|
21
|
+
menu put a ✕ on every option in the list.
|
|
22
|
+
|
|
23
|
+
**Every option list's rows sit 4px apart.** `OptionList` declared that spacing all along, but
|
|
24
|
+
put it on the ScrollView's viewport rather than its content container, where it fell between
|
|
25
|
+
nothing — so rows rendered flush and two adjacent SELECTED rows merged into one tinted block. Every
|
|
26
|
+
selector that opens a list (`Select`, `Picker`, `InlineSelect`, the member pickers, menus) is now
|
|
27
|
+
slightly taller for the same number of rows, and a list capped at its 320px max shows one fewer.
|
|
28
|
+
|
|
29
|
+
**A multi `InlineSelect`'s chips follow the working set while its list is open** — the draft you
|
|
30
|
+
are building, not the stored value. Ticking a row or pressing a chip's ✕ now shows immediately
|
|
31
|
+
instead of at popover-close. The commit is unchanged: one write per visit, on close.
|
|
32
|
+
|
|
7
33
|
**`FileRow`'s `leading` slot is a SIBLING of the press door, not inside it.** No API change;
|
|
8
34
|
a row with `onPress` now has a slightly smaller press target, excluding the leading mark.
|
|
9
35
|
|
package/docs/catalog.md
CHANGED
|
@@ -1174,8 +1174,14 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1174
1174
|
`InlineSelect`/`InlineMemberSelect` render the resting value like its option —
|
|
1175
1175
|
`renderOptionContent` by default, `renderSelected` to override — a chip/badge at rest, not
|
|
1176
1176
|
just text. **`InlineSelect` is single OR multi** — pass `multi` for a tag SET (`value: T[]`,
|
|
1177
|
-
commits the new set on popover-CLOSE
|
|
1178
|
-
|
|
1177
|
+
commits the new set on popover-CLOSE), mirroring `Select`'s `multi` axis; there is NO separate
|
|
1178
|
+
tag component. In multi, `renderSelected(option, { remove })` is the resting CHIP BOX seam, the
|
|
1179
|
+
same shape `Select` gives: return `<Chip onDismiss={remove}>` for a removable tag, or a plain
|
|
1180
|
+
badge that ignores it — a dense grid column is the case for the badge, since the ✕ costs more
|
|
1181
|
+
width there than the reopen it saves. `remove` detaches that one tag whether the list is open or
|
|
1182
|
+
shut, and never opens the list — the innermost responder takes the gesture, so a ✕ inside the
|
|
1183
|
+
trigger needs nothing to hold the press back. Single has no `remove`: unsetting one value is `onClear` and its
|
|
1184
|
+
Clear row. Both modes take
|
|
1179
1185
|
`allowCustom` (a create-a-tag/option row) + `searchable` + **`customOptionPlacement`** (default
|
|
1180
1186
|
`"bottom"`, right for a tag field; pass `"top"` for a find-or-create REFERENCE picker over a long
|
|
1181
1187
|
registry, so the create row stays visible while the keyboard highlight stays on the first MATCH —
|
package/docs/data_entry.md
CHANGED
|
@@ -754,7 +754,14 @@ A tag field's resting state should be a tidy CHIP BOX — and that's just a mult
|
|
|
754
754
|
and no `display` mode: `renderSelected(item, { remove })` composes the anchor — render a `Chip`
|
|
755
755
|
with `remove` for the ✕, or a plain `OptionBadge`/`MemberChip`/custom pill that ignores it;
|
|
756
756
|
`renderOptionContent` renders the menu rows. `searchable` adds the filter field, `allowCustom` the
|
|
757
|
-
create row. Borderless for a grid cell? Pass a `style`, never a `variant`.
|
|
757
|
+
create row. Borderless for a grid cell? Pass a `style`, never a `variant`.
|
|
758
|
+
|
|
759
|
+
**On a record surface the same field is `InlineSelect multi`**, and it takes the identical seam —
|
|
760
|
+
`renderSelected(option, { remove })`. Reach for it wherever the neighbouring values are inline
|
|
761
|
+
editors, so the tag field wears the same frame and hover as the row it sits in; reach for `Select`
|
|
762
|
+
inside a form or dialog. The ✕ detaches its tag whether the list is open or shut and never opens
|
|
763
|
+
the list. Two densities, one seam: a record field earns the removable `Chip`, a narrow register
|
|
764
|
+
column keeps the plain badge, where the ✕ costs more width than the reopen it saves. `Combobox` is the
|
|
758
765
|
SINGLE-value sibling — for a search that emits one pick at a time and renders the selection
|
|
759
766
|
elsewhere, use `reflectSelection={false}` (see the [templates](./templates.md)).
|
|
760
767
|
|
|
@@ -5,6 +5,7 @@ import { colors, solid, asColorName } from "@lotics/ui/colors";
|
|
|
5
5
|
import { Icon } from "@lotics/ui/icon";
|
|
6
6
|
import { CheckCircle } from "@lotics/ui/check_circle";
|
|
7
7
|
import { OptionBadge, type OptionValue } from "@lotics/ui/option_badge";
|
|
8
|
+
import { Chip } from "@lotics/ui/chip";
|
|
8
9
|
import { MemberChip } from "@lotics/ui/member_chip";
|
|
9
10
|
import { Avatar } from "@lotics/ui/avatar";
|
|
10
11
|
import { SearchInput } from "@lotics/ui/search_input";
|
|
@@ -77,6 +78,16 @@ const TAG_OPTIONS: TagOption[] = Object.values(TAGS).map((t) => ({ value: String
|
|
|
77
78
|
const tagOf = (k: string): TagOption => ({ value: k, label: TAGS[k]?.label ?? k });
|
|
78
79
|
const tagBadge = (t: TagOption): OptionValue => TAGS[t.value] ?? { key: t.value, label: t.label, color: null };
|
|
79
80
|
const renderTagBadge = (o: { value: string; label?: string }) => <OptionBadge value={tagBadge({ value: o.value, label: o.label ?? o.value })} variant="dot" />;
|
|
81
|
+
// The same tag, removable. A tag field's resting state is a CHIP BOX — one ✕ per
|
|
82
|
+
// value beats reopening the list to untick one — and `renderSelected`'s `remove`
|
|
83
|
+
// is what a multi `InlineSelect` hands you for it. The column below deliberately
|
|
84
|
+
// does NOT use this: at 168px the ✕ costs more width than the reopen it saves, so
|
|
85
|
+
// a dense register keeps the plain badge. Same seam, two densities.
|
|
86
|
+
const renderRemovableTag = (o: { value: string; label?: string }, { remove }: { remove: () => void }) => (
|
|
87
|
+
<Chip onDismiss={remove}>
|
|
88
|
+
<OptionBadge value={tagBadge({ value: o.value, label: o.label ?? o.value })} variant="dot" />
|
|
89
|
+
</Chip>
|
|
90
|
+
);
|
|
80
91
|
|
|
81
92
|
// A per-row CONTEXTUAL action. Different tasks expose different actions, each
|
|
82
93
|
// wired (in a real app) to its OWN app-workflow — so ONE board drives many
|
|
@@ -580,7 +591,7 @@ function TaskAddRow({ preset, columns, onAdd }: { preset: Partial<Task>; columns
|
|
|
580
591
|
key === "assignee" ? <InlineMemberSelect variant="bare" members={MEMBERS} value={ownerId} onSave={setOwnerId} placeholder="Assignee" accessibilityLabel="Assignee" />
|
|
581
592
|
: key === "due" ? <InlineDatePicker variant="bare" value={due} optionalTime onSave={setDue} placeholder="No date" accessibilityLabel="Due date" />
|
|
582
593
|
: key === "status" ? <InlineSelect variant="bare" value={status} options={STATUS_OPTIONS} onSave={setStatus} renderSelected={renderStatusBadge} renderOptionContent={renderStatusBadge} accessibilityLabel="Status" />
|
|
583
|
-
: key === "tags" ? <InlineSelect multi searchable allowCustom variant="bare" value={tags.map((tag) => tag.value)} onSave={(next) => setTags(next.map(tagOf))} options={TAG_OPTIONS} renderSelected={
|
|
594
|
+
: key === "tags" ? <InlineSelect multi searchable allowCustom variant="bare" value={tags.map((tag) => tag.value)} onSave={(next) => setTags(next.map(tagOf))} options={TAG_OPTIONS} renderSelected={renderRemovableTag} renderOptionContent={renderTagBadge} placeholder="Add tags" accessibilityLabel="Tags" />
|
|
584
595
|
: null;
|
|
585
596
|
|
|
586
597
|
return (
|
package/package.json
CHANGED
package/src/inline_edit.tsx
CHANGED
|
@@ -385,8 +385,21 @@ interface InlineEditViewProps {
|
|
|
385
385
|
* active/open edge) is unchanged. */
|
|
386
386
|
actions?: ReactNode;
|
|
387
387
|
/** True while the field's popover (select/date) is open: wears the 2px active
|
|
388
|
-
* ring so a mouse-opened trigger reads like a focused input (no `:focus-visible`).
|
|
388
|
+
* ring so a mouse-opened trigger reads like a focused input (no `:focus-visible`).
|
|
389
|
+
* Under `role="combobox"` it is also what the field ANNOUNCES as aria-expanded. */
|
|
389
390
|
active?: boolean;
|
|
391
|
+
/** The APG pattern the resting field implements. Default "button" — a value that
|
|
392
|
+
* swaps to an input in place.
|
|
393
|
+
*
|
|
394
|
+
* Pass **"combobox"** when pressing the field opens a LIST that holds its value.
|
|
395
|
+
* Three things follow, and they are one decision rather than three:
|
|
396
|
+
* it is the role APG names for this control; `active` becomes aria-expanded, so
|
|
397
|
+
* the field can no longer claim to be a plain button while owning an open popup;
|
|
398
|
+
* and react-native-web renders a `<div>` instead of a `<button>`, whose content
|
|
399
|
+
* model forbids interactive descendants. That last one is load-bearing — a tag
|
|
400
|
+
* field's chips cannot carry their own remove ✕ inside a `<button>`, because a
|
|
401
|
+
* button may not contain a button. `Select` reached the same conclusion. */
|
|
402
|
+
role?: "button" | "combobox";
|
|
390
403
|
/** Raw focus on the resting view, any modality. The input-swap editors use it
|
|
391
404
|
* (via `InlineEditFrame`) to open edit mode on KEYBOARD focus; the popover
|
|
392
405
|
* editors leave it unset — focus never auto-opens an overlay (the WAI-ARIA
|
|
@@ -436,7 +449,11 @@ function multilineBox(numberOfLines: number | undefined): ViewStyle | null {
|
|
|
436
449
|
}
|
|
437
450
|
|
|
438
451
|
export function InlineEditView(props: InlineEditViewProps) {
|
|
439
|
-
const { display, placeholder, onPress, disabled, accessibilityLabel, trailing, actions, active, struck, variant = "framed", tone, onFocus, ref, anchorRef, numberOfLines } = props;
|
|
452
|
+
const { display, placeholder, onPress, disabled, accessibilityLabel, trailing, actions, active, struck, variant = "framed", tone, onFocus, ref, anchorRef, numberOfLines, role = "button" } = props;
|
|
453
|
+
// A combobox MUST expose its expanded state; a button must not. Deriving it from
|
|
454
|
+
// the `active` the popover callers already pass keeps the two from drifting apart
|
|
455
|
+
// — there is no way to take the role without the announcement.
|
|
456
|
+
const patternProps = role === "combobox" ? { accessibilityRole: "combobox" as const, "aria-expanded": active === true } : { accessibilityRole: "button" as const };
|
|
440
457
|
// Stop the press here so an inline editor nested in a pressable row (a task
|
|
441
458
|
// row that expands on press) edits the field instead of triggering the row.
|
|
442
459
|
const handlePress = onPress
|
|
@@ -465,7 +482,7 @@ export function InlineEditView(props: InlineEditViewProps) {
|
|
|
465
482
|
disabled={disabled}
|
|
466
483
|
onPress={handlePress}
|
|
467
484
|
onFocus={onFocus}
|
|
468
|
-
|
|
485
|
+
{...patternProps}
|
|
469
486
|
accessibilityLabel={accessibilityLabel}
|
|
470
487
|
style={[styles.viewInner, disabled === true && styles.viewInert, multilineBox(numberOfLines)]}
|
|
471
488
|
>
|
|
@@ -484,7 +501,7 @@ export function InlineEditView(props: InlineEditViewProps) {
|
|
|
484
501
|
disabled={disabled}
|
|
485
502
|
onPress={handlePress}
|
|
486
503
|
onFocus={onFocus}
|
|
487
|
-
|
|
504
|
+
{...patternProps}
|
|
488
505
|
accessibilityLabel={accessibilityLabel}
|
|
489
506
|
userSelect="none"
|
|
490
507
|
style={(state) => [surface(state.hovered ?? false), multilineBox(numberOfLines)]}
|
package/src/inline_select.tsx
CHANGED
|
@@ -31,10 +31,6 @@ interface InlineSelectBaseProps<T extends string, D = unknown> {
|
|
|
31
31
|
* rendered this two-line `MenuListItem`; this only forwards it, which is why
|
|
32
32
|
* callers were hand-rebuilding the anatomy through `renderOptionContent`. */
|
|
33
33
|
getOptionDescription?: (option: PickerOption<T, D>) => string | undefined;
|
|
34
|
-
/** Render ONE selected option as its resting chip — single: the value; multi:
|
|
35
|
-
* each tag. Falls back to `renderOptionContent`, then the plain label (single) /
|
|
36
|
-
* a zinc `Badge` (multi). The one seam for "how the selection looks". */
|
|
37
|
-
renderSelected?: (option: PickerOption<T, D>) => ReactNode;
|
|
38
34
|
/** Empty-state content: a string reads as muted placeholder text, a NODE renders
|
|
39
35
|
* as-is (e.g. an avatar "add" ghost for an `avatarOnly` member cell). */
|
|
40
36
|
placeholder?: string | ReactNode;
|
|
@@ -74,6 +70,11 @@ export type InlineSelectProps<T extends string, D = unknown> =
|
|
|
74
70
|
multi?: false;
|
|
75
71
|
value: T | null;
|
|
76
72
|
onSave: (next: T) => void | Promise<void>;
|
|
73
|
+
/** Render the selected option as the resting value. Falls back to
|
|
74
|
+
* `renderOptionContent`, then the plain label. No `remove`: unsetting a single
|
|
75
|
+
* field is `onClear` and its Clear row, and a second ✕ that silently did
|
|
76
|
+
* nothing whenever `onClear` was absent would be worse than no ✕ at all. */
|
|
77
|
+
renderSelected?: (option: PickerOption<T, D>) => ReactNode;
|
|
77
78
|
/** Unset the field. Provide it for a "Clear" row whenever there IS a value —
|
|
78
79
|
* kept separate from `onSave` (whose next is a non-null `T`). Single only. */
|
|
79
80
|
onClear?: () => void | Promise<void>;
|
|
@@ -82,6 +83,11 @@ export type InlineSelectProps<T extends string, D = unknown> =
|
|
|
82
83
|
multi: true;
|
|
83
84
|
value: T[];
|
|
84
85
|
onSave: (next: T[]) => void | Promise<void>;
|
|
86
|
+
/** Render ONE selected tag. Falls back to `renderOptionContent`, then a zinc
|
|
87
|
+
* `Badge`. `remove` detaches THAT tag — return `<Chip onDismiss={remove}>` for
|
|
88
|
+
* the removable chip box a tag field rests as, or a plain badge that ignores
|
|
89
|
+
* it. Mirrors `Select`; the ✕ removes without opening the list. */
|
|
90
|
+
renderSelected?: (option: PickerOption<T, D>, controls: { remove: () => void }) => ReactNode;
|
|
85
91
|
});
|
|
86
92
|
|
|
87
93
|
/** The shared shell: the resting view (a `Popover` trigger) that floats an
|
|
@@ -109,6 +115,10 @@ function InlineSelectShell(props: {
|
|
|
109
115
|
<PopoverTrigger>
|
|
110
116
|
<InlineEditView
|
|
111
117
|
anchorRef={anchorRef}
|
|
118
|
+
// Both modes hold a value and open a list, which is what the role names.
|
|
119
|
+
// It is also what lets a multi field's chips carry their own remove ✕:
|
|
120
|
+
// a <button> may not contain a <button>.
|
|
121
|
+
role="combobox"
|
|
112
122
|
actions={props.actions}
|
|
113
123
|
variant={props.variant}
|
|
114
124
|
display={props.display}
|
|
@@ -140,7 +150,7 @@ function InlineSelectShell(props: {
|
|
|
140
150
|
}
|
|
141
151
|
|
|
142
152
|
export function InlineSelect<T extends string, D = unknown>(props: InlineSelectProps<T, D>) {
|
|
143
|
-
const { options, renderOptionContent, getOptionDescription,
|
|
153
|
+
const { options, renderOptionContent, getOptionDescription, placeholder, disabled, accessibilityLabel, searchable = false, allowCustom = false, customOptionLabel, customOptionPlacement, variant, actions, autoFocus = false } = props;
|
|
144
154
|
const labels = useLoticsLocale().inline;
|
|
145
155
|
const [open, setOpen] = useState(autoFocus);
|
|
146
156
|
const [saving, setSaving] = useState(false);
|
|
@@ -162,15 +172,47 @@ export function InlineSelect<T extends string, D = unknown>(props: InlineSelectP
|
|
|
162
172
|
const searchMode = searchable || allowCustom ? "internal" : "none";
|
|
163
173
|
|
|
164
174
|
if (props.multi) {
|
|
165
|
-
const { value, onSave } = props;
|
|
175
|
+
const { value, onSave, renderSelected } = props;
|
|
176
|
+
// The chips show the WORKING set: the draft while the list is open, the stored
|
|
177
|
+
// value once it is shut. They used to show `value` throughout, which reads as a
|
|
178
|
+
// dead control the moment anything edits the draft — press a chip's ✕ with the
|
|
179
|
+
// list open and it just sits there until the popover closes. What you are
|
|
180
|
+
// building is what the field should show while you build it; ticking a row in
|
|
181
|
+
// the list lands in the same place, for the same reason.
|
|
182
|
+
//
|
|
166
183
|
// Resolve each value to its option IN SELECTION ORDER; an `allowCustom` value not in
|
|
167
184
|
// `options` (a just-created tag) renders from a {value,label} fallback so it still shows
|
|
168
185
|
// (mirrors `Select`). Without allowCustom, an unknown value is dropped (→ nothing), as before.
|
|
169
|
-
const selected = value
|
|
186
|
+
const selected = (open ? draft : value)
|
|
170
187
|
.map((v) => options.find((o) => o.value === v) ?? (allowCustom ? { value: v, label: v } : null))
|
|
171
188
|
.filter((o): o is PickerOption<T, D> => o != null);
|
|
189
|
+
/**
|
|
190
|
+
* Detach one tag.
|
|
191
|
+
*
|
|
192
|
+
* WHICH SET it edits depends on whether the list is open, and getting that wrong
|
|
193
|
+
* is silent: while open, the working set is `draft` and the close handler writes
|
|
194
|
+
* it back wholesale — so a ✕ that wrote straight to `value` would be UNDONE the
|
|
195
|
+
* moment the popover closed. Open → edit the draft and let the close commit it;
|
|
196
|
+
* closed → there is no draft, so write the new set immediately.
|
|
197
|
+
*
|
|
198
|
+
* Nothing here stops the press. The ✕ sits INSIDE the trigger, so opening the
|
|
199
|
+
* list on a removal looks like the obvious hazard — but react-native-web hands
|
|
200
|
+
* a gesture to one responder, and the innermost one wins. Verified against the
|
|
201
|
+
* gallery: with the stop removed the list still never opens.
|
|
202
|
+
*/
|
|
203
|
+
const removeValue = (v: T) => () => {
|
|
204
|
+
if (open) {
|
|
205
|
+
setDraft((current) => current.filter((d) => d !== v));
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
void runSave(() => onSave(value.filter((d) => d !== v)));
|
|
209
|
+
};
|
|
172
210
|
const chip = (o: PickerOption<T, D>) =>
|
|
173
|
-
renderSelected
|
|
211
|
+
renderSelected
|
|
212
|
+
? renderSelected(o, { remove: removeValue(o.value) })
|
|
213
|
+
: renderOptionContent
|
|
214
|
+
? renderOptionContent(o)
|
|
215
|
+
: <Badge label={o.label ?? String(o.value)} color="zinc" />;
|
|
174
216
|
const onOpenChange = (next: boolean) => {
|
|
175
217
|
if (next) {
|
|
176
218
|
setDraft(value);
|
|
@@ -209,13 +251,13 @@ export function InlineSelect<T extends string, D = unknown>(props: InlineSelectP
|
|
|
209
251
|
const created = raw.trim() as T;
|
|
210
252
|
if (created && !draft.includes(created)) setDraft([...draft, created]);
|
|
211
253
|
}}
|
|
212
|
-
renderOptionContent={renderOptionContent
|
|
254
|
+
renderOptionContent={renderOptionContent}
|
|
213
255
|
/>
|
|
214
256
|
</InlineSelectShell>
|
|
215
257
|
);
|
|
216
258
|
}
|
|
217
259
|
|
|
218
|
-
const { value, onSave, onClear } = props;
|
|
260
|
+
const { value, onSave, onClear, renderSelected } = props;
|
|
219
261
|
// An `allowCustom` value not in `options` renders from a fallback (like `Select`); a plain
|
|
220
262
|
// unknown value (a removed member/status) resolves to nothing → the placeholder shows.
|
|
221
263
|
const selected = options.find((o) => o.value === value) ?? (allowCustom && value != null ? { value, label: value } : undefined);
|
package/src/option_list.tsx
CHANGED
|
@@ -113,6 +113,12 @@ export function OptionList<T extends string, MULTI extends boolean = false, D =
|
|
|
113
113
|
<ScrollView
|
|
114
114
|
ref={list.scrollRef}
|
|
115
115
|
style={styles.optionsList}
|
|
116
|
+
// The GAP has to ride the content container, not the viewport. A ScrollView
|
|
117
|
+
// wraps its children in one content <div>, so a `gap` in `style` sits on a
|
|
118
|
+
// box with a single child and falls between nothing — rows rendered flush,
|
|
119
|
+
// and two adjacent SELECTED rows merged into one tinted block, which reads
|
|
120
|
+
// as a single wide row rather than two picked ones.
|
|
121
|
+
contentContainerStyle={styles.optionsContent}
|
|
116
122
|
nativeID={list.listboxId}
|
|
117
123
|
accessibilityLabel={accessibilityLabel}
|
|
118
124
|
keyboardShouldPersistTaps="handled"
|
|
@@ -235,9 +241,11 @@ const styles = StyleSheet.create({
|
|
|
235
241
|
},
|
|
236
242
|
optionsList: {
|
|
237
243
|
flex: 1,
|
|
238
|
-
gap: 4,
|
|
239
244
|
maxHeight: 320,
|
|
240
245
|
},
|
|
246
|
+
optionsContent: {
|
|
247
|
+
gap: 4,
|
|
248
|
+
},
|
|
241
249
|
selectAllContainer: {
|
|
242
250
|
flexDirection: "row",
|
|
243
251
|
alignItems: "center",
|