@lotics/ui 38.0.0 → 39.0.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
@@ -118,9 +118,10 @@ CURRENT major only — upgrading an app across majors is `MIGRATION.md`.
118
118
  anyway. Use `FileRow` / `FileRows`, where the full name is the row and a second line can
119
119
  carry a date, a size, a status, a diff. Photos, scans and drawings are identified by their
120
120
  CONTENT — the filename is `IMG_4471.jpg` and tells nobody anything — so use
121
- `FileThumbnailGrid`, where the picture is the identity and many fit on one screen. A mixed
122
- set follows its majority; an app whose attachments are overwhelmingly paperwork should not
123
- pay a grid's wasted space for the occasional photo.
121
+ `FileThumbnailGrid`, where the picture is the identity and many fit on one screen. **A mixed
122
+ set follows its MAJORITY, and that one surface holds the whole set — never split a pile by
123
+ file type** the cost lands on the minority by design, not on the reader, who would otherwise
124
+ get two places to look for one thing.
124
125
  → [catalog.md](./docs/catalog.md).
125
126
  - **A reference to another record is a FIELD** — it wears the inline editor's own resting
126
127
  surface and PEEKS its facts on press; EVERY verb lives in the peek's footer as a `Button`,
package/MIGRATION.md CHANGED
@@ -4,6 +4,31 @@ 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
+ ## 39.0.0 — `PageHeader.leading` is `trailing`, and it renders AFTER the title
8
+
9
+ 38.0.0 shipped `leading`, a slot before the title. It is now **`trailing`**, in the same
10
+ place in the props but rendered on the other side of the title text.
11
+
12
+ ```tsx
13
+ // 38.0.0
14
+ <PageHeader title="Members" leading={<IconButton …/>} actions={…} />
15
+ // 39.0.0
16
+ <PageHeader title="Members" trailing={<IconButton …/>} actions={…} />
17
+ ```
18
+
19
+ **Why the side changed.** The title is the page's first word. A control ahead of it makes
20
+ the eye parse chrome before it learns where it is, and on a page whose heading is the only
21
+ thing telling you which section you are in, that is the wrong order. After the title the
22
+ control still reads as part of the heading — which is the whole point of not putting it
23
+ with `actions` — without standing in front of it.
24
+
25
+ **Why a rename rather than a second slot.** A slot with no consumer is surface that has to
26
+ be understood by everyone who reads the component and used by no one; the kit's cull rule
27
+ says cut it. One slot for "a control belonging to the title" is the whole contract.
28
+
29
+ The layout law is unchanged and now covers both: the TITLE gives way and wraps, while
30
+ `trailing` and `actions` keep their width.
31
+
7
32
  ## 38.0.0 — the `lg` avatar is a control (40), not a size (48)
8
33
 
9
34
  `AVATAR_PX.lg` was 48 and `AVATAR_TEXT.lg` was `lg` type. They are now **40** and `md`
package/docs/catalog.md CHANGED
@@ -286,6 +286,21 @@ says the read succeeded and found nothing, a failed read knows neither.
286
286
 
287
287
  ### Files
288
288
 
289
+ **Pick the surface FIRST, and pick exactly one per set.** Documents get a LIST (`FileRows`),
290
+ images get a GRID (`FileThumbnailGrid`) — the discriminator is what identifies the file to the
291
+ person hunting for it: a PDF's thumbnail is a grey page identical to every other grey page, so
292
+ its NAME is the identity; a photo's filename is `IMG_4471.jpg` and tells nobody anything, so its
293
+ CONTENT is. **A mixed pile follows its majority and stays whole.** Sorting one pile by mime type
294
+ into a list *and* a grid is the mistake — it gives the reader two places to look for the same
295
+ thing. The minority is not stranded by this: a non-image tile in a grid renders a `DocumentCard`
296
+ printing its filename, and a photo in a list keeps its full name plus a meta line and is ONE tap
297
+ from the picture, because a `FileRows` row press opens the built-in gallery. (The badge is
298
+ MIME-driven, so that row shows a generic image mark, not the shot itself — which is the cost the
299
+ majority already decided to pay.) A screen showing BOTH surfaces (`tpl_record`) does
300
+ so because it holds two distinct SETS the reader asks different questions of — the paperwork
301
+ that arrived on the record, and the hand-over photos taken against it — not because one set was
302
+ split by type.
303
+
289
304
  `FilesEditor` (THE attachment surface: an upload-aware grid whose bar you COMPOSE — the root
290
305
  owns selection + gallery + confirmed remove, the bar pieces and any HOST verb go below it via
291
306
  `useFilesEditorSelection`),
@@ -521,13 +536,15 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
521
536
  - **`container`** — `Container`: centers content at a max width (`ContainerSize` sm|md|lg,
522
537
  `CONTAINER_SIZES`).
523
538
  - **`page_header`** — `PageHeader`: the page's title band. `actions` puts page-level CTAs on
524
- the title row (right-aligned) and `leading` puts a control immediately BEFORE the title;
525
- `left`/`right` form a separate nav row above. Split `leading` from `actions` by what the
539
+ the title row (right-aligned) and `trailing` puts a control immediately AFTER the title;
540
+ `left`/`right` form a separate nav row above. Split `trailing` from `actions` by what the
526
541
  control acts on: `actions` do something to the page's CONTENT (create, sort, export),
527
- `leading` changes what is AROUND it (a side-panel toggle, a back arrow). A panel toggle
528
- filed under `actions` reads as a peer of "create one of these". Under a title too long
529
- for the row the TITLE gives way and wraps while `actions` keep their width — a wrapped
530
- title is merely taller, a CTA pushed off the row is unreachable.
542
+ `trailing` changes what is AROUND it (a side-panel toggle, a view switch). A panel toggle
543
+ filed under `actions` reads as a peer of "create one of these". It sits AFTER the title
544
+ because the title is the page's first word and nothing should come between the reader
545
+ and it. Under a title too long for the row the TITLE gives way and wraps while `trailing`
546
+ and `actions` keep their width — a wrapped title is merely taller, a control squeezed
547
+ below its own icon is broken.
531
548
  - **`page_content`** — `PageContent` + `PAGE_SIZES`: the page's padded, width-capped content
532
549
  region — a centred column with optional `title`/`titleRight`/`description`, `header`/`footer`
533
550
  slots and `fullscreen`. **Reach for it before hand-rolling a screen shell**: a scroller, a
@@ -2683,7 +2683,7 @@ export function TplRecord({ chrome = "page", code = "RC-2026-0418", openSection
2683
2683
  </Section>
2684
2684
  </View>
2685
2685
 
2686
- {/* PHOTOS — the same collection, the other SURFACE.
2686
+ {/* PHOTOS — a second SET, and therefore the other SURFACE.
2687
2687
 
2688
2688
  Files above is a row LIST because a shipping document is identified by
2689
2689
  its NAME: a PDF thumbnail is a grey page identical to every other grey
@@ -2693,6 +2693,17 @@ export function TplRecord({ chrome = "page", code = "RC-2026-0418", openSection
2693
2693
  same verbs, different surface, decided by what the reader recognises.
2694
2694
  (AGENTS.md: documents get a LIST, images get a GRID.)
2695
2695
 
2696
+ READ THIS BEFORE COPYING THE SHAPE. Two sections here does NOT mean
2697
+ "sort one pile by mime type into a list and a grid". This record holds
2698
+ two distinct SETS — the paperwork that arrived on it, and the hand-over
2699
+ photos taken against it — which the reader asks different questions of.
2700
+ A SINGLE pile picks ONE surface by its majority and keeps it: the odd
2701
+ .docx among forty phone photos stays in the grid, where it renders a
2702
+ `DocumentCard` printing its filename, and the odd photo among forty
2703
+ PDFs stays in the list, where its row carries the full name and a press
2704
+ opens the gallery. Splitting one set by type would give the reader two
2705
+ places to look for the same thing.
2706
+
2696
2707
  The ADD rides the heading row, right edge, exactly as it does on Files
2697
2708
  and Fees — a section's add sits in the same spot whether the section
2698
2709
  holds nought or forty. Note it is a plain `Button` + `pickFiles`, not
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/ui",
3
- "version": "38.0.0",
3
+ "version": "39.0.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./vite": {
@@ -10,32 +10,36 @@ interface PageHeaderProps {
10
10
  /** The nav row ABOVE the title, right-aligned. */
11
11
  right?: ReactNode;
12
12
  /**
13
- * A control that belongs to the TITLE, rendered immediately before it on the
14
- * title row — a panel toggle, a back arrow into the page you came from.
13
+ * A control that belongs to the TITLE, rendered immediately AFTER it on the
14
+ * title row — a panel toggle, a view switch, anything that reframes the page
15
+ * rather than acting on it.
15
16
  *
16
17
  * The distinction from `actions` is what the control acts ON, not where it
17
18
  * looks best. `actions` are things you do to the page's CONTENT (create, sort,
18
- * export) and collect at the right, where a scanning eye reaches them last;
19
- * `leading` changes what is AROUND the content, and belongs against the title
20
- * it frames. Putting a panel toggle in `actions` files it with the CTAs and
21
- * makes the reader parse "open a side panel" as a peer of "create one of
22
- * these".
19
+ * export) and collect at the far right, where a scanning eye reaches them
20
+ * last; `trailing` changes what is AROUND the content and stays with the title
21
+ * it qualifies, so it is read as part of the heading rather than as one more
22
+ * thing you can do to your list.
23
+ *
24
+ * It sits AFTER the title, not before, because the title is the page's first
25
+ * word and nothing should come between the reader and it — a control ahead of
26
+ * the heading makes the eye parse chrome before it learns where it is.
23
27
  */
24
- leading?: ReactNode;
28
+ trailing?: ReactNode;
25
29
  /** Page-level actions (CTAs), rendered on the title row, right-aligned —
26
30
  * distinct from `left`/`right`, which form a separate nav row above. */
27
31
  actions?: ReactNode;
28
32
  }
29
33
 
30
34
  export function PageHeader(props: PageHeaderProps) {
31
- const { title, description, left, right, leading, actions } = props;
35
+ const { title, description, left, right, trailing, actions } = props;
32
36
 
33
37
  const hasNav = !!left || !!right;
34
38
 
35
- // ONE shape, whichever slots are filled. Branching the row on `leading` gave
36
- // the title two different behaviours under a long string — shrinking when a
37
- // panel toggle happened to be present and overflowing when it was not — which
38
- // is a layout law decided by an unrelated prop.
39
+ // ONE shape, whichever slots are filled. Branching the row on the title's own
40
+ // slot gave the title two different behaviours under a long string — shrinking
41
+ // when a panel toggle happened to be present and overflowing when it was not —
42
+ // which is a layout law decided by an unrelated prop.
39
43
  //
40
44
  // The title is what gives way; `actions` keep their width, because a CTA
41
45
  // pushed off the row is unreachable while a wrapped title is merely taller.
@@ -62,10 +66,12 @@ export function PageHeader(props: PageHeaderProps) {
62
66
  minWidth: 0,
63
67
  }}
64
68
  >
65
- {leading}
66
69
  <Text size="xxl" weight="semibold" style={{ flexShrink: 1, minWidth: 0 }}>
67
70
  {title}
68
71
  </Text>
72
+ {/* Never shrinks: the title is what gives way, and a control squeezed
73
+ below its own icon is not a smaller control, it is a broken one. */}
74
+ {trailing !== undefined && <View style={{ flexShrink: 0 }}>{trailing}</View>}
69
75
  </View>
70
76
  {actions}
71
77
  </View>