@lotics/ui 28.1.0 → 28.2.1
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 +20 -0
- package/docs/catalog.md +7 -3
- package/docs/composition.md +9 -3
- package/examples/tpl_record.tsx +13 -10
- package/package.json +1 -1
- package/src/section_heading.tsx +28 -6
package/MIGRATION.md
CHANGED
|
@@ -4,6 +4,26 @@ 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
|
+
## 28.2.0 — `SubsectionHeadingTitle` takes a `description`
|
|
8
|
+
|
|
9
|
+
Additive; nothing to change. The heading ramp's three rungs are meant to carry the SAME slots
|
|
10
|
+
and differ only in type size — `description` was missing from the middle one, so a group that
|
|
11
|
+
needed a muted line under its title had to borrow the section rung above it or hand-roll a
|
|
12
|
+
`Text` off the ramp.
|
|
13
|
+
|
|
14
|
+
It matters most where a GROUP receives something. A `FileDropTarget`-wrapped region shows no
|
|
15
|
+
dropzone, so `AGENTS.md` requires the heading to name the drag and paste paths — and a surface
|
|
16
|
+
with several PEER file regions (per-kind photo buckets, each its own target, because nothing in
|
|
17
|
+
a dropped file says which bucket it belongs to) carries those regions at subsection rung:
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
<SubsectionHeading>
|
|
21
|
+
<SubsectionHeadingTitle description="Kéo, dán hoặc bấm để thêm ảnh">Ảnh hạ</SubsectionHeadingTitle>
|
|
22
|
+
</SubsectionHeading>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The rung is unchanged (`###`, lg semibold, level 3) — only the slot is new.
|
|
26
|
+
|
|
7
27
|
## 28.1.0 — `ChecklistItem` can announce more than its title
|
|
8
28
|
|
|
9
29
|
Additive; nothing to change. `accessibilityLabel` overrides the row's accessible name, which
|
package/docs/catalog.md
CHANGED
|
@@ -451,13 +451,17 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
451
451
|
(16, fixed), no body component. `SectionHeadingTitle` is ALWAYS `##` (xl semibold;
|
|
452
452
|
`weight="medium"` opt-down only) + `info` for an ⓘ provenance popover after the title,
|
|
453
453
|
same as `CardHeaderTitle.info`. `SubsectionHeadingTitle` is the `###` lg-semibold level-3
|
|
454
|
-
title of a named group inside a section (same `info` ⓘ
|
|
455
|
-
heading-row siblings ride its right edge.
|
|
454
|
+
title of a named group inside a section (same `info` ⓘ AND `description` slots as the section
|
|
455
|
+
title) — heading-row siblings ride its right edge. Its `description` is what a files
|
|
456
|
+
SUBSECTION uses to name its drag/paste paths: a `FileDropTarget`-wrapped region shows no
|
|
457
|
+
dropzone, and a surface with several peer file regions (per-kind photo buckets, each its own
|
|
458
|
+
target) carries that line at THIS rung, not the section above it. **A section's ADD is one of those siblings** — a
|
|
456
459
|
`primary` `Button` beside the title, rendered whether the collection is empty or full, never
|
|
457
460
|
under the rows it extends and never repeated in the `EmptyState`
|
|
458
461
|
(composition.md § The add-placement law). `DialogSectionHeadingTitle` is the `####`
|
|
459
462
|
md-semibold rung with the SAME `icon`/`description`/`info` slots as the section title, so a
|
|
460
|
-
dialog surface loses only the type size, never an affordance.
|
|
463
|
+
dialog surface loses only the type size, never an affordance. **`description` is on every
|
|
464
|
+
rung** — the ramp's slots do not vary by level; only the type size does. The heading ramp is FIXED:
|
|
461
465
|
`#` xxl / `##` xl / `###` lg / `####` md, no size props.
|
|
462
466
|
- **`section_stack`** — `SectionStack`, `SubsectionStack` — stacks that own the
|
|
463
467
|
between-block law, skipping null children: `SectionStack` = a fixed 56px beat + a hairline
|
package/docs/composition.md
CHANGED
|
@@ -46,7 +46,10 @@ restyle a heading level per-page.
|
|
|
46
46
|
(`##` — xl semibold, optional muted `description` and `info` popover), everything left-aligned
|
|
47
47
|
at the column edge; `SectionHeadingMeta`, any VIEW control, and the section's own ADD ride the
|
|
48
48
|
heading row's right edge (the title's `flex: 1` pushes them) — see the add-placement law
|
|
49
|
-
below.
|
|
49
|
+
below. A COUNT earns that slot only when what it counts is not fully on screen — collapsed,
|
|
50
|
+
paged, or long enough that the reader would have to scan to know. Over a list or thread
|
|
51
|
+
rendered whole directly beneath its own heading it restates what is already in view, and the
|
|
52
|
+
heading reads busier for it. The page column is a **`SectionStack`** — it owns the
|
|
50
53
|
between-section law (a fixed 56px beat + a bare hairline separating one section from the NEXT;
|
|
51
54
|
null/false children are skipped so a conditional section never leaves a stray hairline). The
|
|
52
55
|
`Divider` NEVER goes directly under a heading — that orphans the title from its own content.
|
|
@@ -64,8 +67,11 @@ restyle a heading level per-page.
|
|
|
64
67
|
stays ABSOLUTE — this is one more fixed size, never a `size`/`compact` knob on a rung above.
|
|
65
68
|
- **Subsection title** — the level BELOW a section on a long record surface: `Subsection` ›
|
|
66
69
|
`SubsectionHeading` › `SubsectionHeadingTitle` (`###` — lg semibold, with the same `info` ⓘ
|
|
67
|
-
|
|
68
|
-
`SectionHeadingMeta`, an action — ride the heading row's right edge).
|
|
70
|
+
and `description` slots as `SectionHeadingTitle`; siblings — a `Badge`, a
|
|
71
|
+
`SectionHeadingMeta`, an action — ride the heading row's right edge). Reach for its
|
|
72
|
+
`description` when the GROUP is what receives something — a files subsection wrapped in its
|
|
73
|
+
own `FileDropTarget` has to name drag and paste itself, since a wrapped region shows no zone
|
|
74
|
+
and the section above it may hold several such groups. Sibling subsections stack
|
|
69
75
|
in a **`SubsectionStack`** (a fixed 32px beat; no margins, no hand-rolled dividers).
|
|
70
76
|
It is SPACE-ONLY while the groups are SHORT — the titles carry the grouping and hairlines
|
|
71
77
|
belong to the SECTION level, one rule per altitude — and takes `divided` once the groups run
|
package/examples/tpl_record.tsx
CHANGED
|
@@ -12,7 +12,7 @@ import type { PickerOption } from "@lotics/ui/picker";
|
|
|
12
12
|
import { Combobox, ComboboxInput, ComboboxContent } from "@lotics/ui/combobox";
|
|
13
13
|
import { DetailRow, DetailTable } from "@lotics/ui/detail_row";
|
|
14
14
|
import { Callout, CalloutText } from "@lotics/ui/callout";
|
|
15
|
-
import { Section, SectionHeading,
|
|
15
|
+
import { Section, SectionHeading, SectionHeadingTitle, Subsection, SubsectionHeading, SubsectionHeadingTitle } from "@lotics/ui/section_heading";
|
|
16
16
|
import { SectionStack, SubsectionStack } from "@lotics/ui/section_stack";
|
|
17
17
|
import { Checklist, ChecklistActions, ChecklistGroup, ChecklistItem, ChecklistNote } from "@lotics/ui/checklist";
|
|
18
18
|
import { DateStamp } from "@lotics/ui/date_stamp";
|
|
@@ -2381,9 +2381,13 @@ export function TplRecord({ chrome = "page", code = "RC-2026-0418", openSection
|
|
|
2381
2381
|
{!wide ? (
|
|
2382
2382
|
<View onLayout={nav.register("comments")}>
|
|
2383
2383
|
<Section>
|
|
2384
|
+
{/* No count. The thread is RIGHT THERE — the number restates what
|
|
2385
|
+
the reader is already looking at, and it is the kind of meta that
|
|
2386
|
+
earns its place only when the thing it counts is collapsed, or
|
|
2387
|
+
off-screen, or expensive to scan. None of those hold for a
|
|
2388
|
+
discussion rendered in full directly beneath its own heading. */}
|
|
2384
2389
|
<SectionHeading>
|
|
2385
2390
|
<SectionHeadingTitle description="Notes on this record — everyone with access sees them.">Comments</SectionHeadingTitle>
|
|
2386
|
-
<SectionHeadingMeta>{`${comments.length} ${comments.length === 1 ? "comment" : "comments"}`}</SectionHeadingMeta>
|
|
2387
2391
|
</SectionHeading>
|
|
2388
2392
|
{commentsThread}
|
|
2389
2393
|
</Section>
|
|
@@ -2626,9 +2630,9 @@ export function TplRecord({ chrome = "page", code = "RC-2026-0418", openSection
|
|
|
2626
2630
|
the verb is off-screen entirely, and on an empty list there is no
|
|
2627
2631
|
last row to sit under, so it had to be a SECOND button inside the
|
|
2628
2632
|
`EmptyState`. One verb, two renderings, neither findable without
|
|
2629
|
-
scanning. The heading row is the section's control line —
|
|
2630
|
-
|
|
2631
|
-
|
|
2633
|
+
scanning. The heading row is the section's control line — where a
|
|
2634
|
+
section's meta and verbs sit, with the title growing to push its
|
|
2635
|
+
siblings right — so the add belongs on it, in the same spot
|
|
2632
2636
|
whether the list holds nought or forty. */}
|
|
2633
2637
|
<Button title="Add fee" color="primary" onPress={addFee} />
|
|
2634
2638
|
</SectionHeading>
|
|
@@ -3632,13 +3636,12 @@ export function TplRecord({ chrome = "page", code = "RC-2026-0418", openSection
|
|
|
3632
3636
|
{/* The panel is a PEER of the sections beside it, not a widget, so its
|
|
3633
3637
|
title takes the ramp's section rung rather than a hand-picked size —
|
|
3634
3638
|
and it takes the WHOLE construct. `SectionHeadingTitle` carries
|
|
3635
|
-
`flex: 1` so it can push
|
|
3636
|
-
straight into this column it grows VERTICALLY instead
|
|
3637
|
-
thread beneath it. The row wrapper is what makes that
|
|
3638
|
-
it was written to mean. */}
|
|
3639
|
+
`flex: 1` so it can push a heading row's trailing content to the right
|
|
3640
|
+
edge; dropped straight into this column it grows VERTICALLY instead
|
|
3641
|
+
and starves the thread beneath it. The row wrapper is what makes that
|
|
3642
|
+
flex mean what it was written to mean. */}
|
|
3639
3643
|
<SectionHeading>
|
|
3640
3644
|
<SectionHeadingTitle>Comments</SectionHeadingTitle>
|
|
3641
|
-
<SectionHeadingMeta>{`${comments.length} ${comments.length === 1 ? "comment" : "comments"}`}</SectionHeadingMeta>
|
|
3642
3645
|
</SectionHeading>
|
|
3643
3646
|
{/* The panel's OWN scroller, so a long thread scrolls inside itself and
|
|
3644
3647
|
never lengthens the record. The thread inside it is the same one the
|
package/package.json
CHANGED
package/src/section_heading.tsx
CHANGED
|
@@ -87,6 +87,17 @@ export function SubsectionHeading(props: SubsectionHeadingProps) {
|
|
|
87
87
|
|
|
88
88
|
export interface SubsectionHeadingTitleProps {
|
|
89
89
|
children: React.ReactNode;
|
|
90
|
+
/**
|
|
91
|
+
* Muted line under the title, as on the rungs either side of this one.
|
|
92
|
+
*
|
|
93
|
+
* The ramp carries the same slots at every rung, and this was the one gap in
|
|
94
|
+
* it. It matters most for a group that RECEIVES something: a files region
|
|
95
|
+
* wrapped in a `FileDropTarget` shows no dropzone, so drag and paste are
|
|
96
|
+
* invisible unless the heading names them — and a surface with several peer
|
|
97
|
+
* file regions (per-kind photo buckets, each its own drop target) puts them at
|
|
98
|
+
* THIS rung, where the prescribed line previously had nowhere to sit.
|
|
99
|
+
*/
|
|
100
|
+
description?: string;
|
|
90
101
|
/** Heading rank. Defaults to 3 — one level under `SectionHeadingTitle`'s 2. */
|
|
91
102
|
level?: HeadingLevel;
|
|
92
103
|
/** An ⓘ popover after the title — a short "what this is / where it came from"
|
|
@@ -98,14 +109,25 @@ export interface SubsectionHeadingTitleProps {
|
|
|
98
109
|
* xl section title and above the md/sm body. Grows to push siblings (a
|
|
99
110
|
* `SectionHeadingMeta`, a `Badge`, an action) to the right edge. */
|
|
100
111
|
export function SubsectionHeadingTitle(props: SubsectionHeadingTitleProps) {
|
|
101
|
-
const { children, level = 3, info } = props;
|
|
112
|
+
const { children, description, level = 3, info } = props;
|
|
102
113
|
const words = useLoticsLocale();
|
|
114
|
+
// Same anatomy as `SectionHeadingTitle` one rung down: the title row keeps its
|
|
115
|
+
// own `flexDirection: "row"` so `info` sits beside the text, and the column
|
|
116
|
+
// wrapper carries the `flex: 1` that lets a heading row push meta to its right
|
|
117
|
+
// edge. Without the wrapper the description would land on the title's LINE.
|
|
103
118
|
return (
|
|
104
|
-
<View style={{ flex: 1,
|
|
105
|
-
<
|
|
106
|
-
{
|
|
107
|
-
|
|
108
|
-
|
|
119
|
+
<View style={{ flex: 1, gap: 2 }}>
|
|
120
|
+
<View style={{ flexDirection: "row", alignItems: "center", gap: 6 }}>
|
|
121
|
+
<Text level={level} size="lg" weight="semibold">
|
|
122
|
+
{children}
|
|
123
|
+
</Text>
|
|
124
|
+
{info ? <InfoPopover text={info} accessibilityLabel={words.sectionHeading.info} /> : null}
|
|
125
|
+
</View>
|
|
126
|
+
{description ? (
|
|
127
|
+
<Text color="zinc-500" size="sm">
|
|
128
|
+
{description}
|
|
129
|
+
</Text>
|
|
130
|
+
) : null}
|
|
109
131
|
</View>
|
|
110
132
|
);
|
|
111
133
|
}
|