@lotics/ui 7.17.0 → 7.19.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/AGENTS.md +12 -5
- package/package.json +1 -1
- package/src/file_gallery_modal.tsx +3 -2
- package/src/file_preview.tsx +3 -2
- package/src/file_preview.web.tsx +3 -2
- package/src/file_preview_types.ts +5 -34
- package/src/file_rows.tsx +3 -2
- package/src/locale.tsx +41 -0
- package/src/pressable_row.tsx +4 -1
- package/src/table.tsx +51 -17
package/AGENTS.md
CHANGED
|
@@ -21,9 +21,11 @@ typeahead, async search, virtualization, and a11y the primitive already ships.
|
|
|
21
21
|
app stays English. Wired so far: `Pagination`, `SortHeader`/`Table.sortLabels`, `OptionList`
|
|
22
22
|
(+ `Select`/`Combobox` built on it), `FilterChip`, `Drawer`, `Confidence`, `RemainderMeter`,
|
|
23
23
|
`ChangeReview`/`ChangeReviewActions`, `DateRangeFilterField`/`DateFilter` (presets + footer +
|
|
24
|
-
time-segment a11y, one `dateRange` slice),
|
|
24
|
+
time-segment a11y, one `dateRange` slice), `FormField` (the `optional` marker), and
|
|
25
|
+
`FileRows`/`FileGalleryModal`/`FilePreview` (one `gallery` slice: the ⋯ menu incl. remove+confirm,
|
|
26
|
+
the gallery chrome, and the preview captions). Adding a string to a wired component's `*Labels` forces
|
|
25
27
|
both packs in `locale.tsx` to fill it (compile error) — that's how the kit avoids a silent English
|
|
26
|
-
leak. Not yet on the provider (pass `labels` for now): calendar/gantt, the
|
|
28
|
+
leak. Not yet on the provider (pass `labels` for now): calendar/gantt, the comment labels.
|
|
27
29
|
|
|
28
30
|
---
|
|
29
31
|
|
|
@@ -682,7 +684,8 @@ was only ever about the review surfaces' sparkles/severity glyphs, not functiona
|
|
|
682
684
|
header; the rows below it Divider-separated; a sortable column shows its `SortHeader` sort glyph
|
|
683
685
|
ALWAYS so it reads as sortable). A `TableRow` with no `onPress` renders a STATIC read-only row (no
|
|
684
686
|
hover wash / pointer cursor) so `Table` also serves read-only tabular data (a fee breakdown, a spec
|
|
685
|
-
sheet), not just interactive registers.
|
|
687
|
+
sheet), not just interactive registers. A pressable `TableRow` REQUIRES `accessibilityLabel`
|
|
688
|
+
("Open …") — its keyboard door is an empty overlay with no content to derive a name from. Make it SELECTABLE with the `Table` `leading` gutter + `selectAll`
|
|
686
689
|
slot — a `CheckboxInput` per `TableRow` (`leading`) + a select-all in the header band, the ticked
|
|
687
690
|
rows `marked`, paired with a `FloatingActionBar`; the selection state is the **`useSelection()`**
|
|
688
691
|
hook (`selected`/`count`/`toggle`/`setAll`/`allSelected`/`indeterminate`/`clear` — gating which
|
|
@@ -696,9 +699,13 @@ was only ever about the review surfaces' sparkles/severity glyphs, not functiona
|
|
|
696
699
|
a fixed `width`, so amount/status columns don't jitter).
|
|
697
700
|
- **Row actions are ALWAYS-VISIBLE siblings, never hover-revealed, never nested.** A pressable row =
|
|
698
701
|
a role-less surface (the hover/press wash, via DOM `mouseenter` so it covers the whole row) + an
|
|
699
|
-
accessible "Open …" door (
|
|
702
|
+
accessible "Open …" door (a button) + trailing actions (⋯, remove ✕, edit) as SIBLINGS of
|
|
700
703
|
the door — a button must not contain another button, so the action is independently pressable and
|
|
701
|
-
the ✕ isn't swallowed by the row press (`FileRow` and `PressableRow` are the references).
|
|
704
|
+
the ✕ isn't swallowed by the row press (`FileRow` and `PressableRow` are the references). The door
|
|
705
|
+
wraps the row body ONLY when that body is non-interactive by construction (`FileRow`'s name/meta);
|
|
706
|
+
where the body is arbitrary app content — `TableRow`'s cells, which legitimately carry a `Link` or
|
|
707
|
+
a popover trigger — the door is an EMPTY absolutely-positioned sibling under the cells (tab stop +
|
|
708
|
+
name + focus ring; mouse rides the surface), so a control in a cell never nests inside it. Actions
|
|
702
709
|
stay **visible** — a hover-only action is invisible to keyboard and touch users. The ONLY exception
|
|
703
710
|
is a DENSE tree/register where a persistent per-row action would clutter (e.g. the DB sidebar):
|
|
704
711
|
reveal on hover **OR focus-within**, keeping the action in the DOM + tab order and gating only its
|
package/package.json
CHANGED
|
@@ -18,7 +18,8 @@ import type { DisplayFile } from "./file_thumbnail";
|
|
|
18
18
|
import { FilePreview } from "./file_preview";
|
|
19
19
|
import { isImageMimeType } from "./mime";
|
|
20
20
|
import { useImageRotation, type ImageRotation } from "./use_image_rotation";
|
|
21
|
-
import {
|
|
21
|
+
import { type GalleryLabels } from "./file_preview_types";
|
|
22
|
+
import { useLoticsLocale } from "./locale";
|
|
22
23
|
import { downloadFileFromUrl } from "./download";
|
|
23
24
|
import { ActionMenu, type ActionMenuItem } from "./action_menu";
|
|
24
25
|
import { useScreenSize } from "./use_screen_size";
|
|
@@ -153,7 +154,7 @@ export function FileGalleryModal(props: FileGalleryModalProps) {
|
|
|
153
154
|
const file = files[activeIndex];
|
|
154
155
|
if (!file) return null;
|
|
155
156
|
|
|
156
|
-
const l =
|
|
157
|
+
const l = { ...useLoticsLocale().gallery, ...labels };
|
|
157
158
|
const isImage = isImageMimeType(file.mimeType);
|
|
158
159
|
const total = files.length;
|
|
159
160
|
const rotated = rot.rotationFor(file.id) !== 0;
|
package/src/file_preview.tsx
CHANGED
|
@@ -3,7 +3,8 @@ import { Text } from "./text";
|
|
|
3
3
|
import { DocumentCard } from "./file_thumbnail";
|
|
4
4
|
import { isImageMimeType } from "./mime";
|
|
5
5
|
import { RotatableImage } from "./rotatable_image";
|
|
6
|
-
import { type FilePreviewProps
|
|
6
|
+
import { type FilePreviewProps } from "./file_preview_types";
|
|
7
|
+
import { useLoticsLocale } from "./locale";
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Native (mobile) fallback: images render full-bleed; everything else shows a
|
|
@@ -11,7 +12,7 @@ import { type FilePreviewProps, resolveLabels } from "./file_preview_types";
|
|
|
11
12
|
* the `.web.tsx` variant carries it; native consumers download instead.
|
|
12
13
|
*/
|
|
13
14
|
export function FilePreview({ file, labels, rotation }: FilePreviewProps) {
|
|
14
|
-
const l =
|
|
15
|
+
const l = { ...useLoticsLocale().gallery, ...labels };
|
|
15
16
|
if (isImageMimeType(file.mimeType)) {
|
|
16
17
|
return <RotatableImage uri={file.url} alt={file.filename} rotation={rotation} />;
|
|
17
18
|
}
|
package/src/file_preview.web.tsx
CHANGED
|
@@ -13,7 +13,8 @@ import {
|
|
|
13
13
|
isAudioMimeType,
|
|
14
14
|
isPreviewableMimeType,
|
|
15
15
|
} from "./mime";
|
|
16
|
-
import { type FilePreviewProps,
|
|
16
|
+
import { type FilePreviewProps, type PreviewLabels } from "./file_preview_types";
|
|
17
|
+
import { useLoticsLocale } from "./locale";
|
|
17
18
|
import { SpreadsheetView } from "./spreadsheet_view";
|
|
18
19
|
import { RotatableImage } from "./rotatable_image";
|
|
19
20
|
import { downloadFileFromUrl } from "./download";
|
|
@@ -36,7 +37,7 @@ import { colors } from "./colors";
|
|
|
36
37
|
* (purity test). Lifted from the frontend gallery so both share one renderer.
|
|
37
38
|
*/
|
|
38
39
|
export function FilePreview({ file, labels, onError, rotation, credentials }: FilePreviewProps) {
|
|
39
|
-
const l =
|
|
40
|
+
const l = { ...useLoticsLocale().gallery, ...labels };
|
|
40
41
|
|
|
41
42
|
if (isImageMimeType(file.mimeType)) {
|
|
42
43
|
return (
|
|
@@ -2,8 +2,9 @@ import type { DisplayFile } from "./file_thumbnail";
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Caption/error strings shown inside the preview. `@lotics/ui` is i18n-free
|
|
5
|
-
* (purity test forbids lingui)
|
|
6
|
-
*
|
|
5
|
+
* (purity test forbids lingui): the strings resolve from the `LoticsLocale`
|
|
6
|
+
* `gallery` slice (`LoticsLocaleProvider`, default English) with a per-instance
|
|
7
|
+
* `labels` prop as the one-off override.
|
|
7
8
|
*/
|
|
8
9
|
export interface PreviewLabels {
|
|
9
10
|
notAvailable: string;
|
|
@@ -12,13 +13,6 @@ export interface PreviewLabels {
|
|
|
12
13
|
passwordProtected: string;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
export const defaultPreviewLabels: PreviewLabels = {
|
|
16
|
-
notAvailable: "Preview not available",
|
|
17
|
-
loadFailed: "Failed to load preview",
|
|
18
|
-
download: "Download file",
|
|
19
|
-
passwordProtected: "This file is password-protected and cannot be previewed",
|
|
20
|
-
};
|
|
21
|
-
|
|
22
16
|
export interface FilePreviewProps {
|
|
23
17
|
file: DisplayFile;
|
|
24
18
|
/** Override any subset of the English default labels. */
|
|
@@ -40,14 +34,10 @@ export interface FilePreviewProps {
|
|
|
40
34
|
credentials?: RequestCredentials;
|
|
41
35
|
}
|
|
42
36
|
|
|
43
|
-
export function resolveLabels(labels: Partial<PreviewLabels> | undefined): PreviewLabels {
|
|
44
|
-
return labels ? { ...defaultPreviewLabels, ...labels } : defaultPreviewLabels;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
37
|
/**
|
|
48
38
|
* Strings for the full-screen `FileGalleryModal` chrome (toolbar + nav), on top
|
|
49
|
-
* of the inner `PreviewLabels`. Same
|
|
50
|
-
*
|
|
39
|
+
* of the inner `PreviewLabels`. Same contract: the `LoticsLocale` `gallery`
|
|
40
|
+
* slice supplies them; a per-instance `labels` prop overrides.
|
|
51
41
|
*/
|
|
52
42
|
export interface GalleryLabels extends PreviewLabels {
|
|
53
43
|
close: string;
|
|
@@ -65,22 +55,3 @@ export interface GalleryLabels extends PreviewLabels {
|
|
|
65
55
|
saveRotation: string;
|
|
66
56
|
}
|
|
67
57
|
|
|
68
|
-
export const defaultGalleryLabels: GalleryLabels = {
|
|
69
|
-
...defaultPreviewLabels,
|
|
70
|
-
close: "Close",
|
|
71
|
-
previous: "Previous",
|
|
72
|
-
next: "Next",
|
|
73
|
-
actions: "Actions",
|
|
74
|
-
openExternal: "Open in new tab",
|
|
75
|
-
remove: "Remove",
|
|
76
|
-
removeConfirmTitle: "Remove file",
|
|
77
|
-
removeConfirmMessage: (filename) => `Remove "${filename}"?`,
|
|
78
|
-
cancel: "Cancel",
|
|
79
|
-
rotateLeft: "Rotate left",
|
|
80
|
-
rotateRight: "Rotate right",
|
|
81
|
-
saveRotation: "Save rotation",
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
export function resolveGalleryLabels(labels: Partial<GalleryLabels> | undefined): GalleryLabels {
|
|
85
|
-
return labels ? { ...defaultGalleryLabels, ...labels } : defaultGalleryLabels;
|
|
86
|
-
}
|
package/src/file_rows.tsx
CHANGED
|
@@ -12,7 +12,8 @@ import { ActionMenu, type ActionMenuItem } from "./action_menu";
|
|
|
12
12
|
import { FileGalleryModal } from "./file_gallery_modal";
|
|
13
13
|
import { resolveMime } from "./file_badge";
|
|
14
14
|
import { downloadFileFromUrl } from "./download";
|
|
15
|
-
import {
|
|
15
|
+
import { type GalleryLabels } from "./file_preview_types";
|
|
16
|
+
import { useLoticsLocale } from "./locale";
|
|
16
17
|
import type { DisplayFile } from "./file_thumbnail";
|
|
17
18
|
|
|
18
19
|
export interface FileRowsProps {
|
|
@@ -47,7 +48,7 @@ export interface FileRowsProps {
|
|
|
47
48
|
*/
|
|
48
49
|
export function FileRows({ files, meta, onRemove, onOpenExternal, onDownload, onError, labels, gap = 8, credentials }: FileRowsProps) {
|
|
49
50
|
const [activeIndex, setActiveIndex] = useState<number | null>(null);
|
|
50
|
-
const l =
|
|
51
|
+
const l = { ...useLoticsLocale().gallery, ...labels };
|
|
51
52
|
|
|
52
53
|
if (files.length === 0) return null;
|
|
53
54
|
|
package/src/locale.tsx
CHANGED
|
@@ -5,6 +5,7 @@ import { type ConfidenceLabels } from "./confidence";
|
|
|
5
5
|
import { type FindingLabels } from "./finding";
|
|
6
6
|
import { type RemainderMeterLabels } from "./remainder_meter";
|
|
7
7
|
import { type DateRangeFilterFieldLabels } from "./date_range_filter_field";
|
|
8
|
+
import { type GalleryLabels } from "./file_preview_types";
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* The kit's localizable strings, one slice per string-bearing component. A
|
|
@@ -61,6 +62,10 @@ export interface LoticsLocale {
|
|
|
61
62
|
* from/to, the footer Clear/Done, the trigger placeholder, and the time-field
|
|
62
63
|
* segment a11y names. */
|
|
63
64
|
dateRange: DateRangeFilterFieldLabels;
|
|
65
|
+
/** `FileRows` / `FileGalleryModal` / `FilePreview`: the per-row ⋯ menu
|
|
66
|
+
* (open/download/remove + the confirm), the gallery chrome (close, prev/next,
|
|
67
|
+
* rotate), and the preview captions (not-available / load-failed / password). */
|
|
68
|
+
gallery: GalleryLabels;
|
|
64
69
|
}
|
|
65
70
|
|
|
66
71
|
/** The platform default — English. Every component's hardcoded default lives
|
|
@@ -112,6 +117,24 @@ export const en: LoticsLocale = {
|
|
|
112
117
|
selectDateRange: "Select date range", selectDate: "Select date",
|
|
113
118
|
clear: "Clear", done: "Done", placeholder: "All time",
|
|
114
119
|
},
|
|
120
|
+
gallery: {
|
|
121
|
+
close: "Close",
|
|
122
|
+
previous: "Previous",
|
|
123
|
+
next: "Next",
|
|
124
|
+
actions: "Actions",
|
|
125
|
+
openExternal: "Open in new tab",
|
|
126
|
+
remove: "Remove",
|
|
127
|
+
removeConfirmTitle: "Remove file",
|
|
128
|
+
removeConfirmMessage: (filename) => `Remove "${filename}"?`,
|
|
129
|
+
cancel: "Cancel",
|
|
130
|
+
rotateLeft: "Rotate left",
|
|
131
|
+
rotateRight: "Rotate right",
|
|
132
|
+
saveRotation: "Save rotation",
|
|
133
|
+
notAvailable: "Preview not available",
|
|
134
|
+
loadFailed: "Failed to load preview",
|
|
135
|
+
download: "Download file",
|
|
136
|
+
passwordProtected: "This file is password-protected and cannot be previewed",
|
|
137
|
+
},
|
|
115
138
|
};
|
|
116
139
|
|
|
117
140
|
/** Vietnamese. Maintained once here so every app (and the frontend) shares one
|
|
@@ -163,6 +186,24 @@ export const vi: LoticsLocale = {
|
|
|
163
186
|
selectDateRange: "Chọn khoảng ngày", selectDate: "Chọn ngày",
|
|
164
187
|
clear: "Xóa", done: "Xong", placeholder: "Tất cả thời gian",
|
|
165
188
|
},
|
|
189
|
+
gallery: {
|
|
190
|
+
close: "Đóng",
|
|
191
|
+
previous: "Trước",
|
|
192
|
+
next: "Sau",
|
|
193
|
+
actions: "Thao tác",
|
|
194
|
+
openExternal: "Mở trong thẻ mới",
|
|
195
|
+
remove: "Gỡ tệp",
|
|
196
|
+
removeConfirmTitle: "Gỡ tệp",
|
|
197
|
+
removeConfirmMessage: (filename) => `Gỡ "${filename}"?`,
|
|
198
|
+
cancel: "Hủy",
|
|
199
|
+
rotateLeft: "Xoay trái",
|
|
200
|
+
rotateRight: "Xoay phải",
|
|
201
|
+
saveRotation: "Lưu ảnh đã xoay",
|
|
202
|
+
notAvailable: "Không xem trước được định dạng này",
|
|
203
|
+
loadFailed: "Không tải được bản xem trước",
|
|
204
|
+
download: "Tải xuống",
|
|
205
|
+
passwordProtected: "Tệp có mật khẩu — không xem trước được",
|
|
206
|
+
},
|
|
166
207
|
};
|
|
167
208
|
|
|
168
209
|
const LoticsLocaleContext = createContext<LoticsLocale>(en);
|
package/src/pressable_row.tsx
CHANGED
|
@@ -26,7 +26,10 @@ export interface PressableRowProps {
|
|
|
26
26
|
variant?: "register" | "bleed" | "inset";
|
|
27
27
|
/** Cells + trailing controls. Nested pressables (CTAs, checkboxes, ⋯)
|
|
28
28
|
* claim their own presses; put the accessible door inside — a plain
|
|
29
|
-
* `Pressable` with role="button" + "Open …" label
|
|
29
|
+
* `Pressable` with role="button" + "Open …" label. Wrap the row body with it
|
|
30
|
+
* only when the body is non-interactive by construction; a body that can
|
|
31
|
+
* carry its own controls gets an EMPTY absolutely-positioned door as their
|
|
32
|
+
* sibling instead (a button must not contain a button — see `TableRow`). */
|
|
30
33
|
children: ReactNode;
|
|
31
34
|
/** Layout only (gap, minHeight overrides). The surface owns its press
|
|
32
35
|
* states — never pass backgroundColor for hover/selected. */
|
package/src/table.tsx
CHANGED
|
@@ -114,14 +114,10 @@ export function Table(props: TableProps) {
|
|
|
114
114
|
);
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
export
|
|
118
|
-
/** Opens the record (the drawer). The door + the full-row surface both call it. */
|
|
119
|
-
onPress?: () => void;
|
|
117
|
+
export type TableRowProps = {
|
|
120
118
|
selected?: boolean;
|
|
121
119
|
/** Part of a multi-select set — a resting blue tint (the open/hover wash overrides it). */
|
|
122
120
|
marked?: boolean;
|
|
123
|
-
/** Accessible name for the door (the keyboard-focusable open target). */
|
|
124
|
-
accessibilityLabel?: string;
|
|
125
121
|
/** Outside the door, BEFORE the cells (a selection checkbox) — width = Table `leading`. */
|
|
126
122
|
leading?: ReactNode;
|
|
127
123
|
/** Outside the door, AFTER the cells (a ⋯ menu / a button) — width = Table `trailing`. */
|
|
@@ -130,14 +126,31 @@ export interface TableRowProps {
|
|
|
130
126
|
minHeight?: number;
|
|
131
127
|
/** The `TableCell`s, one per column, in column order. */
|
|
132
128
|
children: ReactNode;
|
|
133
|
-
}
|
|
129
|
+
} & (
|
|
130
|
+
| {
|
|
131
|
+
/** Opens the record (the drawer). The door + the full-row surface both call it. */
|
|
132
|
+
onPress: () => void;
|
|
133
|
+
/** Accessible name for the door (the keyboard-focusable open target). REQUIRED
|
|
134
|
+
* on a pressable row: the door is an empty overlay (cells are its siblings, so
|
|
135
|
+
* a cell control never nests in a button), which means it has no text content
|
|
136
|
+
* to derive a name from — without a label it's a nameless button. */
|
|
137
|
+
accessibilityLabel: string;
|
|
138
|
+
}
|
|
139
|
+
| { onPress?: undefined; accessibilityLabel?: undefined }
|
|
140
|
+
);
|
|
134
141
|
|
|
135
142
|
/**
|
|
136
143
|
* A register row: a full-bleed `PressableRow` (its hover wash spans the whole row,
|
|
137
|
-
* incl. the leading/trailing controls)
|
|
138
|
-
* `role="button"`
|
|
139
|
-
*
|
|
140
|
-
*
|
|
144
|
+
* incl. the leading/trailing controls) holding the cells, with the door — the
|
|
145
|
+
* keyboard-accessible `role="button"` open target — as an EMPTY absolutely-
|
|
146
|
+
* positioned SIBLING beneath them, never their ancestor. A button must not
|
|
147
|
+
* contain interactive descendants, and cells legitimately carry their own
|
|
148
|
+
* controls (a popover trigger, a `Link`); wrapping the cells in the door nests
|
|
149
|
+
* `<button>` in `<button>` (invalid HTML — React hydration warning). The door
|
|
150
|
+
* carries the tab stop, the accessible name, and the focus ring (spanning the
|
|
151
|
+
* row, rounded to the register wash); mouse presses ride the `PressableRow`
|
|
152
|
+
* surface, and nested controls claim their own. `leading`/`trailing` slots stay
|
|
153
|
+
* independently pressable. Cells map to `Table`'s columns by position.
|
|
141
154
|
*/
|
|
142
155
|
export function TableRow(props: TableRowProps) {
|
|
143
156
|
const { onPress, selected, marked, accessibilityLabel, leading, trailing, minHeight = 52, children } = props;
|
|
@@ -157,7 +170,7 @@ export function TableRow(props: TableRowProps) {
|
|
|
157
170
|
return (
|
|
158
171
|
<View style={styles.staticRow}>
|
|
159
172
|
{ctx.leading > 0 ? <View style={{ width: ctx.leading }}>{leading}</View> : null}
|
|
160
|
-
<View style={[styles.
|
|
173
|
+
<View style={[styles.cells, { minHeight }]}>{cells}</View>
|
|
161
174
|
{ctx.trailing > 0 ? <View style={{ width: ctx.trailing, alignItems: "flex-end" }}>{trailing}</View> : null}
|
|
162
175
|
</View>
|
|
163
176
|
);
|
|
@@ -165,17 +178,21 @@ export function TableRow(props: TableRowProps) {
|
|
|
165
178
|
|
|
166
179
|
return (
|
|
167
180
|
<PressableRow onPress={onPress} selected={selected} marked={marked} style={styles.row}>
|
|
168
|
-
{ctx.leading > 0 ? <View style={{ width: ctx.leading }}>{leading}</View> : null}
|
|
181
|
+
{ctx.leading > 0 ? <View style={[styles.slot, { width: ctx.leading }]}>{leading}</View> : null}
|
|
182
|
+
{/* An absolutely-positioned sibling paints — and hit-tests — above in-flow
|
|
183
|
+
content, which would swallow every cell control's press; the slots lift
|
|
184
|
+
above it via zIndex 1 (z-index applies to flex items), so the door gets
|
|
185
|
+
only the keyboard. Negative z on the door instead would escape the row
|
|
186
|
+
(no stacking context) and bury the focus ring under the Card background. */}
|
|
169
187
|
<Pressable
|
|
170
188
|
accessibilityRole="button"
|
|
171
189
|
accessibilityLabel={accessibilityLabel}
|
|
172
190
|
onPress={onPress}
|
|
173
191
|
{...focusProps}
|
|
174
|
-
style={[styles.door,
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
</
|
|
178
|
-
{ctx.trailing > 0 ? <View style={{ width: ctx.trailing, alignItems: "flex-end" }}>{trailing}</View> : null}
|
|
192
|
+
style={[styles.door, focusVisible && { boxShadow: FOCUS_RING }]}
|
|
193
|
+
/>
|
|
194
|
+
<View style={[styles.cells, { minHeight }]}>{cells}</View>
|
|
195
|
+
{ctx.trailing > 0 ? <View style={[styles.slot, { width: ctx.trailing, alignItems: "flex-end" }]}>{trailing}</View> : null}
|
|
179
196
|
</PressableRow>
|
|
180
197
|
);
|
|
181
198
|
}
|
|
@@ -219,10 +236,27 @@ const styles = StyleSheet.create({
|
|
|
219
236
|
gap: 14,
|
|
220
237
|
paddingHorizontal: 20,
|
|
221
238
|
},
|
|
239
|
+
// The keyboard door: an empty overlay spanning the row — the tab stop + focus
|
|
240
|
+
// ring live here, the cells are its SIBLINGS (see TableRow's doc). Radius
|
|
241
|
+
// matches the register wash.
|
|
222
242
|
door: {
|
|
243
|
+
position: "absolute",
|
|
244
|
+
top: 0,
|
|
245
|
+
right: 0,
|
|
246
|
+
bottom: 0,
|
|
247
|
+
left: 0,
|
|
248
|
+
borderRadius: 10,
|
|
249
|
+
},
|
|
250
|
+
cells: {
|
|
223
251
|
flex: 1,
|
|
224
252
|
flexDirection: "row",
|
|
225
253
|
alignItems: "center",
|
|
226
254
|
gap: 14,
|
|
255
|
+
zIndex: 1,
|
|
256
|
+
},
|
|
257
|
+
// Leading/trailing gutters carry their own controls — lifted above the door
|
|
258
|
+
// like the cells.
|
|
259
|
+
slot: {
|
|
260
|
+
zIndex: 1,
|
|
227
261
|
},
|
|
228
262
|
});
|