@lotics/ui 7.12.0 → 7.12.2
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 +8 -4
- package/package.json +1 -1
- package/src/files_editor.tsx +13 -3
- package/src/pressable_row.tsx +9 -3
- package/src/table.tsx +24 -4
package/AGENTS.md
CHANGED
|
@@ -115,7 +115,7 @@ Pick by capability, not by name. (→ the source file for the API.)
|
|
|
115
115
|
`PageHeader` /
|
|
116
116
|
`PageContent`, `Stack`, `Spacer`, `Divider`, `Accordion`, `Tabs`, `SegmentedControl`, `Stepper`,
|
|
117
117
|
`DangerZone` (the destructive section — delete/archive — set apart at the bottom of a record/settings surface).
|
|
118
|
-
- **Rows & registers** — `PressableRow` (THE register row), `ListItem`, `MenuButton`,
|
|
118
|
+
- **Rows & registers** — `PressableRow` (THE register row; forwards its `ref`, so wrapping it in a `PopoverTrigger` anchors a row-triggered peek Popover — without the ref the trigger is unmeasurable and the popover renders off-screen), `ListItem`, `MenuButton`,
|
|
119
119
|
`MenuListItem`, `DetailRow` (label+value for drawers/peeks; optional `trailing` slot for a
|
|
120
120
|
right-side action/badge/unit), `ActionMenu` (⋯), `FloatingActionBar` (bulk-select bar).
|
|
121
121
|
- **Filters & view controls** — `SearchInput`, `ChipGroup`, `FilterChip` (+ `RangeSlider`,
|
|
@@ -302,7 +302,9 @@ marked + disabled), not a generic text menu. A composition (Badge + Button + Pop
|
|
|
302
302
|
**Default: `<FilesEditor files onAdd onRemove>`** — it bundles the grid + a toolbar (Upload · Select ·
|
|
303
303
|
Download all) + a batch SELECT mode + the gallery + confirmed-remove; the host only owns `files` and
|
|
304
304
|
wires `onAdd` (picked → its upload) / `onRemove`. The destructive per-tile ✕ shows only in SELECT mode
|
|
305
|
-
(the default view is a clean preview — no stray-tap deletes);
|
|
305
|
+
(the default view is a clean preview — no stray-tap deletes); pass `selectTileRemove={false}` to drop
|
|
306
|
+
that ✕ entirely so select mode deletes ONLY via Select → Menu → Delete (the batch gating flow — how the
|
|
307
|
+
cell popover's `cell_files_editor` does it). The built-in gallery is Download +
|
|
306
308
|
inline preview (no "open in new tab" — it's redundant once everything previews inline). In a
|
|
307
309
|
height-bounded container (a popover/drawer) pass `gridMaxHeight` so the grid SCROLLS and the toolbar
|
|
308
310
|
pins below it; omit it in free-flow layouts (a form field) where the grid grows. Reach for the
|
|
@@ -669,7 +671,9 @@ was only ever about the review surfaces' sparkles/severity glyphs, not functiona
|
|
|
669
671
|
+ cells align. The Divider BETWEEN rows is the resting separation. The COLUMNAR register is
|
|
670
672
|
`Table`/`TableRow`/`TableCell` (columns once → header + widths can't drift; ONE hairline under the
|
|
671
673
|
header; the rows below it Divider-separated; a sortable column shows its `SortHeader` sort glyph
|
|
672
|
-
ALWAYS so it reads as sortable).
|
|
674
|
+
ALWAYS so it reads as sortable). A `TableRow` with no `onPress` renders a STATIC read-only row (no
|
|
675
|
+
hover wash / pointer cursor) so `Table` also serves read-only tabular data (a fee breakdown, a spec
|
|
676
|
+
sheet), not just interactive registers. Make it SELECTABLE with the `Table` `leading` gutter + `selectAll`
|
|
673
677
|
slot — a `CheckboxInput` per `TableRow` (`leading`) + a select-all in the header band, the ticked
|
|
674
678
|
rows `marked`, paired with a `FloatingActionBar`; the selection state is the **`useSelection()`**
|
|
675
679
|
hook (`selected`/`count`/`toggle`/`setAll`/`allSelected`/`indeterminate`/`clear` — gating which
|
|
@@ -878,7 +882,7 @@ CalloutText · CalloutActions) · kpi_card · kpi_strip · summary_line (Summary
|
|
|
878
882
|
bar_chart · line_chart · pie_chart · ring_gauge · progress_bar · stacked_progress_bar · breakdown ·
|
|
879
883
|
funnel (Funnel — conversion funnel: narrowing bars + the step rate as a bold aligned headline row, count below; `orientation` vertical|horizontal, `onSelect`/`selectedKey` press-to-drill; the subset/drop-off sibling of stacked_progress_bar's whole-split) ·
|
|
880
884
|
status_grid (StatusGrid + StatusLegend) · heatmap (density cross-tab — colour-only, no numbers) · matrix (Matrix — the PIVOT cross-tab: band-compound `Matrix` root + `Matrix.Header` (corner + axis labels) + `Matrix.Grid` (`display` number|heat|both — the cells, pressable, the value IN the cell) + `Matrix.Totals` (row + column + grand) + `Matrix.Legend`; the data layer `matrixTotals`/`MatrixAxisItem`/`MatrixCellRef` import from `@lotics/ui/matrix_totals`. Pick over `Heatmap` when the NUMBER and totals matter, not just where it clusters) · legend_item · remainder_meter · allocation_row ·
|
|
881
|
-
scan_field · file_dropzone · files_editor (FilesEditor — THE all-in-one attachment field: FileGrid + a toolbar (Upload primary · Select · Download all) that swaps into a batch SELECT mode (Select all · a Menu of Download/Share/Delete · Done; the per-tile ✕ is select-mode-only, never in the default view) + built-in gallery (Download + inline preview; no "open in new tab") + Alert-confirmed remove; host wires `files` + `onAdd`/`onRemove` (+ optional `uploads`, `onShareSelected`, `readOnly`, `labels`, `galleryLabels`, `gridMaxHeight` — cap the grid height so it scrolls and the toolbar pins, for a popover/drawer); mirrors the frontend cell_files_editor. Use FileGrid/FileRows bare only when you own the chrome) · file_grid (FileGrid — the upload-aware grid: completed files + a live upload queue in one surface; FileUpload/PendingUpload types; the add-files default) · uploading_thumbnail (UploadingThumbnail — the single in-flight upload tile FileGrid renders; reach for it only when hand-rolling a non-grid upload layout) · file_thumbnail · file_thumbnail_grid · file_row · file_rows (FileRows — batteries-included file list: row press → built-in gallery + a ⋯ Download/Open-external/Remove menu; composes FileRow + ActionMenu + FileGalleryModal) · file_preview (FilePreview — the universal inline preview: image/PDF/video/audio + Word via `@lotics/docx` + Excel/CSV via `@lotics/xlsx`; the heavy engines are LAZY (dynamic-imported, ~free until a doc of that type is opened) so custom-code APPS can use it too — they just add `@lotics/docx` + `@lotics/xlsx` to their deps. Renders to canvas/DOM, never a nested iframe — works in the sandboxed app iframe) ·
|
|
885
|
+
scan_field · file_dropzone · files_editor (FilesEditor — THE all-in-one attachment field: FileGrid + a toolbar (Upload primary · Select · Download all) that swaps into a batch SELECT mode (Select all · a Menu of Download/Share/Delete · Done; the per-tile ✕ is select-mode-only, never in the default view; `selectTileRemove={false}` drops even that so delete is menu-only) + built-in gallery (Download + inline preview; no "open in new tab") + Alert-confirmed remove; host wires `files` + `onAdd`/`onRemove` (+ optional `uploads`, `onShareSelected`, `readOnly`, `selectTileRemove`, `labels`, `galleryLabels`, `gridMaxHeight` — cap the grid height so it scrolls and the toolbar pins, for a popover/drawer); mirrors the frontend cell_files_editor. Use FileGrid/FileRows bare only when you own the chrome) · file_grid (FileGrid — the upload-aware grid: completed files + a live upload queue in one surface; FileUpload/PendingUpload types; the add-files default) · uploading_thumbnail (UploadingThumbnail — the single in-flight upload tile FileGrid renders; reach for it only when hand-rolling a non-grid upload layout) · file_thumbnail · file_thumbnail_grid · file_row · file_rows (FileRows — batteries-included file list: row press → built-in gallery + a ⋯ Download/Open-external/Remove menu; composes FileRow + ActionMenu + FileGalleryModal) · file_preview (FilePreview — the universal inline preview: image/PDF/video/audio + Word via `@lotics/docx` + Excel/CSV via `@lotics/xlsx`; the heavy engines are LAZY (dynamic-imported, ~free until a doc of that type is opened) so custom-code APPS can use it too — they just add `@lotics/docx` + `@lotics/xlsx` to their deps. Renders to canvas/DOM, never a nested iframe — works in the sandboxed app iframe) ·
|
|
882
886
|
file_gallery_modal · image_gallery · use_selection (useSelection — always-on multi-select state for a register/list: the `selected` Set + `toggle`/`setAll`/`allSelected`/`indeterminate`/`count`/`clear`; selectability gating stays with the caller. The checkbox-always-visible counterpart to `use_selection_mode`) · use_selection_mode · share_or_download · rotate_image · avatar · skeleton · activity_indicator · loading · divider ·
|
|
883
887
|
spacer · stack · section_card · page_header · page_content · calendar (calendar/index.ts) · gantt ·
|
|
884
888
|
comments_thread · agent_run (live streaming work feed) · agent_progress (its compact floating
|
package/package.json
CHANGED
package/src/files_editor.tsx
CHANGED
|
@@ -64,6 +64,12 @@ export interface FilesEditorProps {
|
|
|
64
64
|
onDownloadZipSelected?: (files: DisplayFile[]) => void;
|
|
65
65
|
/** Remove a completed file. Omit (or `readOnly`) for a view-only surface. */
|
|
66
66
|
onRemove?: (id: string) => void;
|
|
67
|
+
/** Show the per-tile ✕ during SELECT mode (default `true`). The default
|
|
68
|
+
* (non-select) view NEVER shows a ✕ regardless — this governs select mode
|
|
69
|
+
* only. Set `false` when batch Select → Menu → Delete is the sole delete
|
|
70
|
+
* path (e.g. a cell popover), so a tile in select mode toggles selection and
|
|
71
|
+
* nothing else. */
|
|
72
|
+
selectTileRemove?: boolean;
|
|
67
73
|
/** Cancel an in-flight upload / retry a failed one (when using `uploads`). */
|
|
68
74
|
onUploadRemove?: (id: string) => void;
|
|
69
75
|
onRetry?: (id: string) => void;
|
|
@@ -112,6 +118,7 @@ export function FilesEditor(props: FilesEditorProps) {
|
|
|
112
118
|
onUpload,
|
|
113
119
|
onDownloadZipSelected,
|
|
114
120
|
onRemove,
|
|
121
|
+
selectTileRemove = true,
|
|
115
122
|
onUploadRemove,
|
|
116
123
|
onRetry,
|
|
117
124
|
onRetryAll,
|
|
@@ -163,9 +170,12 @@ export function FilesEditor(props: FilesEditorProps) {
|
|
|
163
170
|
selectedIds={selectMode ? selected : undefined}
|
|
164
171
|
onToggleSelect={selectMode ? toggle : undefined}
|
|
165
172
|
onFilePress={selectMode ? undefined : (f) => setGalleryIndex(files.findIndex((x) => x.id === f.id))}
|
|
166
|
-
// The per-tile ✕ is a SELECT-mode affordance
|
|
167
|
-
// preview surface so a stray tap can't drop a
|
|
168
|
-
|
|
173
|
+
// The per-tile ✕ is a SELECT-mode affordance (opt-out via `selectTileRemove`)
|
|
174
|
+
// — the default view stays a clean preview surface so a stray tap can't drop a
|
|
175
|
+
// file, and a `selectTileRemove={false}` surface deletes only via the menu.
|
|
176
|
+
onDisplayRemove={
|
|
177
|
+
!readOnly && selectMode && selectTileRemove && onRemove ? (id) => confirmRemove(() => onRemove(id)) : undefined
|
|
178
|
+
}
|
|
169
179
|
onUploadRemove={onUploadRemove}
|
|
170
180
|
onRetry={onRetry}
|
|
171
181
|
onRetryAll={onRetryAll}
|
package/src/pressable_row.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ReactNode, useState } from "react";
|
|
2
|
-
import { Pressable, StyleProp, StyleSheet, ViewStyle } from "react-native";
|
|
1
|
+
import { ReactNode, Ref, useState } from "react";
|
|
2
|
+
import { Pressable, StyleProp, StyleSheet, View, ViewStyle } from "react-native";
|
|
3
3
|
import { colors } from "./colors";
|
|
4
4
|
|
|
5
5
|
export interface PressableRowProps {
|
|
@@ -31,6 +31,11 @@ export interface PressableRowProps {
|
|
|
31
31
|
/** Layout only (gap, minHeight overrides). The surface owns its press
|
|
32
32
|
* states — never pass backgroundColor for hover/selected. */
|
|
33
33
|
style?: StyleProp<ViewStyle>;
|
|
34
|
+
/** Forwarded to the underlying Pressable's DOM node so a `Popover`/`Tooltip`
|
|
35
|
+
* can anchor to (measure) the row — RN-Web forwards it to the rendered element.
|
|
36
|
+
* Without it, wrapping this row in a `PopoverTrigger` yields an unmeasurable
|
|
37
|
+
* trigger and the popover renders unpositioned. */
|
|
38
|
+
ref?: Ref<View>;
|
|
34
39
|
}
|
|
35
40
|
|
|
36
41
|
/**
|
|
@@ -45,7 +50,7 @@ export interface PressableRowProps {
|
|
|
45
50
|
* `variant` picks bleed (registers) or inset (Accordion drill-down rows).
|
|
46
51
|
*/
|
|
47
52
|
export function PressableRow(props: PressableRowProps) {
|
|
48
|
-
const { onPress, selected = false, marked = false, variant = "register", children, style } = props;
|
|
53
|
+
const { onPress, selected = false, marked = false, variant = "register", children, style, ref } = props;
|
|
49
54
|
const [hovered, setHovered] = useState(false);
|
|
50
55
|
|
|
51
56
|
// RN's types don't declare mouse handlers; react-native-web forwards them
|
|
@@ -57,6 +62,7 @@ export function PressableRow(props: PressableRowProps) {
|
|
|
57
62
|
|
|
58
63
|
return (
|
|
59
64
|
<Pressable
|
|
65
|
+
ref={ref}
|
|
60
66
|
onPress={onPress}
|
|
61
67
|
// Non-focusable surface: tabIndex, NOT focusable — RN-Web's Pressable
|
|
62
68
|
// ignores `focusable` and writes its own tabIndex (the keyboard target is
|
package/src/table.tsx
CHANGED
|
@@ -144,11 +144,25 @@ export function TableRow(props: TableRowProps) {
|
|
|
144
144
|
);
|
|
145
145
|
const { focusVisible, focusProps } = useFocusRing();
|
|
146
146
|
|
|
147
|
+
// Read-only row (no `onPress`): a static, non-interactive row — no hover wash,
|
|
148
|
+
// no pointer cursor, no focusable door — so a `Table` can present read-only
|
|
149
|
+
// tabular data (a fee breakdown, a spec sheet) without implying the rows open
|
|
150
|
+
// something. An interactive row (with `onPress`) keeps the full register surface.
|
|
151
|
+
if (!onPress) {
|
|
152
|
+
return (
|
|
153
|
+
<View style={styles.staticRow}>
|
|
154
|
+
{ctx.leading > 0 ? <View style={{ width: ctx.leading }}>{leading}</View> : null}
|
|
155
|
+
<View style={[styles.door, { minHeight }]}>{cells}</View>
|
|
156
|
+
{ctx.trailing > 0 ? <View style={{ width: ctx.trailing, alignItems: "flex-end" }}>{trailing}</View> : null}
|
|
157
|
+
</View>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
147
161
|
return (
|
|
148
|
-
<PressableRow onPress={onPress
|
|
162
|
+
<PressableRow onPress={onPress} selected={selected} marked={marked} style={styles.row}>
|
|
149
163
|
{ctx.leading > 0 ? <View style={{ width: ctx.leading }}>{leading}</View> : null}
|
|
150
164
|
<Pressable
|
|
151
|
-
accessibilityRole=
|
|
165
|
+
accessibilityRole="button"
|
|
152
166
|
accessibilityLabel={accessibilityLabel}
|
|
153
167
|
onPress={onPress}
|
|
154
168
|
{...focusProps}
|
|
@@ -173,8 +187,6 @@ export function TableCell(props: TableCellProps) {
|
|
|
173
187
|
return <View style={_column ? colStyle(_column) : undefined}>{children}</View>;
|
|
174
188
|
}
|
|
175
189
|
|
|
176
|
-
const noop = () => {};
|
|
177
|
-
|
|
178
190
|
const styles = StyleSheet.create({
|
|
179
191
|
// A hairline under the column header anchors the columns; the rows below it are
|
|
180
192
|
// Divider-separated.
|
|
@@ -194,6 +206,14 @@ const styles = StyleSheet.create({
|
|
|
194
206
|
row: {
|
|
195
207
|
gap: 14,
|
|
196
208
|
},
|
|
209
|
+
// Read-only row (a `TableRow` with no `onPress`): the register row's gutter +
|
|
210
|
+
// layout so cells still align with the header, minus the hover/press surface.
|
|
211
|
+
staticRow: {
|
|
212
|
+
flexDirection: "row",
|
|
213
|
+
alignItems: "center",
|
|
214
|
+
gap: 14,
|
|
215
|
+
paddingHorizontal: 20,
|
|
216
|
+
},
|
|
197
217
|
door: {
|
|
198
218
|
flex: 1,
|
|
199
219
|
flexDirection: "row",
|