@lotics/ui 7.13.0 → 7.15.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 CHANGED
@@ -50,7 +50,9 @@ Pick by capability, not by name. (→ the source file for the API.)
50
50
  `useOptionList` engine; `Picker` is the only one that's a native dropdown.
51
51
  - **Pick member(s)** — `MemberSelect` (a `Select` that renders each option as a `MemberChip`,
52
52
  single or multi) — the ready member picker; pass it the roster (`members={useMembers().members}`).
53
- Don't re-wire `Select` + `renderOptionContent` + a directory by hand. To edit a `select_member`
53
+ Don't re-wire `Select` + `renderOptionContent` + a directory by hand. For an assignee FILTER that
54
+ also matches unassigned records, pass `unassignedLabel` → it prepends a muted "unassigned" option
55
+ (value `MEMBER_UNASSIGNED`), so you never hand-roll a mixed member+none `Select`. To edit a `select_member`
54
56
  field IN PLACE (chip at rest → picker on click) use its inline-edit twin `InlineMemberSelect`.
55
57
  - **A person / member (display)** — `MemberChip` (avatar + name + optional secondary line) — the
56
58
  ONE way to show a member inline: a picker option, an assignee, a `select_member` value. Pure:
@@ -888,7 +890,7 @@ CalloutText · CalloutActions) · kpi_card · kpi_strip · summary_line (Summary
888
890
  bar_chart · line_chart · pie_chart · ring_gauge · progress_bar · stacked_progress_bar · breakdown ·
889
891
  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) ·
890
892
  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 ·
891
- 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) ·
893
+ 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 (pdf.js · `@lotics/docx` · `@lotics/xlsx`) are LAZY (dynamic-imported, ~free until a doc of that type is opened) and SHIP AS `@lotics/ui` deps (7.14.0+)custom-code apps get PDF/Word/Excel preview with ZERO extra install. Renders to canvas/DOM, never a nested iframe — works in the sandboxed app iframe) ·
892
894
  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 ·
893
895
  spacer · stack · section_card · page_header · page_content · calendar (calendar/index.ts) · gantt ·
894
896
  comments_thread · agent_run (live streaming work feed) · agent_progress (its compact floating
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/ui",
3
- "version": "7.13.0",
3
+ "version": "7.15.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./tokens": "./src/tokens.ts",
@@ -240,17 +240,17 @@
240
240
  },
241
241
  "license": "MIT",
242
242
  "dependencies": {
243
+ "@lotics/docx": "^0.1.0",
244
+ "@lotics/xlsx": "^0.1.0",
245
+ "pdfjs-dist": "^6.0.227",
243
246
  "react-markdown": "^10.0.0",
244
247
  "remark-gfm": "^4.0.0"
245
248
  },
246
249
  "peerDependencies": {
247
- "@lotics/docx": "^0.1.0",
248
- "@lotics/xlsx": "^0.1.0",
249
250
  "@react-native-picker/picker": ">=2.0.0",
250
251
  "expo-image": ">=3.0.0",
251
252
  "lucide-react": ">=0.460.0",
252
253
  "lucide-react-native": ">=0.460.0",
253
- "pdfjs-dist": ">=6.0.0",
254
254
  "react": "^19.2.0",
255
255
  "react-dom": "^19.2.0",
256
256
  "react-native": ">=0.85.0",
@@ -258,15 +258,6 @@
258
258
  "react-native-web": ">=0.20.0"
259
259
  },
260
260
  "peerDependenciesMeta": {
261
- "@lotics/docx": {
262
- "optional": true
263
- },
264
- "@lotics/xlsx": {
265
- "optional": true
266
- },
267
- "pdfjs-dist": {
268
- "optional": true
269
- },
270
261
  "expo-image": {
271
262
  "optional": true
272
263
  },
@@ -287,12 +278,9 @@
287
278
  "test": "vitest run"
288
279
  },
289
280
  "devDependencies": {
290
- "@lotics/docx": "^0.1.0",
291
- "@lotics/xlsx": "^0.1.0",
292
281
  "@types/react-dom": "~19.2.2",
293
282
  "@vitejs/plugin-react": "^4.3.4",
294
283
  "lucide-react": "^0.562.0",
295
- "pdfjs-dist": "^6.0.227",
296
284
  "react-dom": "^19.2.0",
297
285
  "react-native-web": "^0.21.0",
298
286
  "vite": "^7.2.4"
@@ -8,6 +8,12 @@ import {
8
8
  type PickerOnClose,
9
9
  } from "./picker";
10
10
  import { MemberChip } from "./member_chip";
11
+ import { Text } from "./text";
12
+
13
+ /** The `value` of {@link MemberSelect}'s "unassigned" option (present only when
14
+ * `unassignedLabel` is set). Check a `MemberSelect` value against it to detect
15
+ * "no member assigned" — e.g. `value.includes(MEMBER_UNASSIGNED)` in a filter. */
16
+ export const MEMBER_UNASSIGNED = "__unassigned__";
11
17
 
12
18
  /** A candidate member for {@link MemberSelect}. Shaped to accept a `useMembers`
13
19
  * row from `@lotics/app-sdk` (or any roster) directly. */
@@ -35,6 +41,13 @@ interface MemberSelectProps<MULTI extends boolean = false> {
35
41
  /** Show an in-menu search box to filter the roster; default false. */
36
42
  searchable?: boolean;
37
43
  includeEmptyOption?: boolean;
44
+ /**
45
+ * When set, prepend a selectable "unassigned" option (value {@link MEMBER_UNASSIGNED}),
46
+ * rendered as muted text — for an assignee FILTER that also matches "no one assigned".
47
+ * Distinct from `includeEmptyOption` (a single-select CLEAR row that unsets the value):
48
+ * this is a real, filterable value the consumer detects in `value`.
49
+ */
50
+ unassignedLabel?: string;
38
51
  enableSelectAll?: boolean;
39
52
  /** Label for the "select all" link in multi-select mode. Pass a translated string. */
40
53
  selectAllLabel?: string;
@@ -61,16 +74,20 @@ interface MemberSelectProps<MULTI extends boolean = false> {
61
74
  * ```
62
75
  */
63
76
  export function MemberSelect<MULTI extends boolean = false>(props: MemberSelectProps<MULTI>) {
64
- const { members, ...picker } = props;
77
+ const { members, unassignedLabel, ...picker } = props;
65
78
 
66
79
  const byId = useMemo(() => new Map(members.map((m) => [m.id, m])), [members]);
67
80
  const options = useMemo<PickerOption<string>[]>(
68
- () => members.map((m) => ({ value: m.id, label: m.name || m.email || m.id })),
69
- [members],
81
+ () => [
82
+ ...(unassignedLabel ? [{ value: MEMBER_UNASSIGNED, label: unassignedLabel }] : []),
83
+ ...members.map((m) => ({ value: m.id, label: m.name || m.email || m.id })),
84
+ ],
85
+ [members, unassignedLabel],
70
86
  );
71
87
 
72
88
  const renderOptionContent = useCallback(
73
89
  (option: PickerOption<string>) => {
90
+ if (option.value === MEMBER_UNASSIGNED) return <Text size="sm" color="muted">{option.label}</Text>;
74
91
  const member = byId.get(option.value);
75
92
  if (!member) return null;
76
93
  return <MemberChip name={member.name || member.email} image={member.image} />;