@jsenv/navi 0.29.418 → 0.29.420
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/README.md +1 -1
- package/dist/dev/jsenv_navi.js +584 -196
- package/dist/dev/jsenv_navi.js.map +35 -35
- package/dist/jsenv_navi.js +563 -187
- package/dist/jsenv_navi.js.map +35 -35
- package/docs/AI_INSTRUCTIONS.md +177 -261
- package/docs/actions.md +164 -251
- package/docs/autofocus.md +66 -83
- package/docs/control_object.md +117 -214
- package/docs/control_value.md +121 -185
- package/docs/create_and_edit.md +169 -254
- package/docs/css_architecture.md +196 -260
- package/docs/data_states.md +39 -43
- package/docs/dialog_shape.md +91 -134
- package/docs/drag_interactions.md +115 -174
- package/docs/drag_to_travel.md +10 -9
- package/docs/dynamic_import.md +119 -136
- package/docs/error_handling.md +75 -127
- package/docs/field_validation.md +49 -89
- package/docs/form_changed.md +66 -81
- package/docs/group.md +141 -0
- package/docs/i18n.md +31 -76
- package/docs/interactions.md +6 -4
- package/docs/list_action.md +36 -51
- package/docs/list_refresh.md +73 -115
- package/docs/mobile_touch.md +56 -62
- package/docs/navigation.md +377 -723
- package/docs/network_policy.md +61 -75
- package/docs/pan_zoom.md +62 -103
- package/docs/popup_backdrop.md +128 -168
- package/docs/popup_lift.md +179 -268
- package/docs/popup_open.md +380 -651
- package/docs/resource.md +228 -408
- package/docs/route_transitions.md +333 -482
- package/docs/safe_area.md +102 -89
- package/docs/scroll.md +175 -335
- package/docs/state_binding.md +2 -1
- package/docs/testid.md +18 -23
- package/docs/typography.md +111 -141
- package/docs/view_transitions.md +106 -104
- package/docs/z_index.md +61 -109
- package/package.json +2 -2
- package/docs/badge_list.md +0 -83
- package/docs/control_group.md +0 -168
- package/docs/mobile_layout_pitfalls.md +0 -145
package/docs/state_binding.md
CHANGED
|
@@ -113,7 +113,8 @@ const styleSignal = useSignalSync(user.share_image_style);
|
|
|
113
113
|
```
|
|
114
114
|
|
|
115
115
|
One write at a time shows nothing: the answer carries the value the signal
|
|
116
|
-
already holds. Two in flight — two notches in quick succession
|
|
116
|
+
already holds. Two in flight — two notches in quick succession, nothing
|
|
117
|
+
reordered, only an answer landing after a newer write was sent — and every
|
|
117
118
|
answer is to a request the user has already moved past: the container walks to
|
|
118
119
|
it, the callback saves it, and that write's answer starts the next turn. The
|
|
119
120
|
loop never closes.
|
package/docs/testid.md
CHANGED
|
@@ -71,12 +71,10 @@ on. A testid on the wrapper would answer a click with a hit on the padding.
|
|
|
71
71
|
</span>
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
`
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
so a `data-test-id` or a `data-qa` of your own would name the wrapper, which is
|
|
79
|
-
the second reason to keep the standard spelling.
|
|
74
|
+
Navi hands the host the props it knows belong there (`id`, `name`, `type`,
|
|
75
|
+
`value`, `tabIndex`, `data-testid`, …); any prop it does not know lands on the
|
|
76
|
+
root box instead — so a `data-test-id` or a `data-qa` of your own would name
|
|
77
|
+
the wrapper, which is the second reason to keep the standard spelling.
|
|
80
78
|
|
|
81
79
|
`aria-label` and `aria-labelledby` take the same road, and for the reason the
|
|
82
80
|
section above gives: the name has to be where the role is. The root box has no
|
|
@@ -124,16 +122,16 @@ error message, a section, a row — put the testid on the surrounding component
|
|
|
124
122
|
(`<Field data-testid="email-field">`, `<Box data-testid="cart-row">`); anything
|
|
125
123
|
built on `Box` forwards it to its own element.
|
|
126
124
|
|
|
127
|
-
A selectable `List.Item` is a control too, and the rule holds there
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
waits for it to show, targets what the row draws: a testid on its content.
|
|
125
|
+
A selectable `List.Item` is a control too, and the rule holds there: its host is
|
|
126
|
+
the real checkbox or radio the row selects with, an input navi draws invisible
|
|
127
|
+
and lays under the row's content. A `data-testid` on the item names that input —
|
|
128
|
+
what `toBeChecked()` reads, not what the eye sees. A test that presses the row,
|
|
129
|
+
or waits for it to show, targets what the row draws: a testid on its content.
|
|
133
130
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
131
|
+
What navi builds itself is named through props. A `data-testid` on a `<Picker>`
|
|
132
|
+
names the trigger; `popupTestId` names the popup it builds (the callout, under
|
|
133
|
+
`mode="callout"`), and `confirmTestId` / `cancelTestId` the yes and no buttons
|
|
134
|
+
of a `type="confirm"` picker's default body:
|
|
137
135
|
|
|
138
136
|
```jsx
|
|
139
137
|
<Picker data-testid="tie-break" popupTestId="tie-break-sheet">
|
|
@@ -141,13 +139,11 @@ nothing names the popup. `popupTestId` does:
|
|
|
141
139
|
</Picker>
|
|
142
140
|
```
|
|
143
141
|
|
|
144
|
-
Most tests do not need
|
|
142
|
+
Most tests do not need `popupTestId`: a testid on what the popup holds (`<Box
|
|
145
143
|
data-testid="place-pick">` among the children) names the screen, not the frame,
|
|
146
144
|
and is the better name for a test that reads or clicks the content. Reach for
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
every mode, including `mode="callout"`, where the frame is a callout rather
|
|
150
|
-
than a sheet.
|
|
145
|
+
it when the frame IS what the test looks at — a screenshot of the popup's
|
|
146
|
+
surface, its position, its size, its backdrop.
|
|
151
147
|
|
|
152
148
|
## A callout is a role first
|
|
153
149
|
|
|
@@ -168,9 +164,8 @@ for it is what a live region is for, and the role is what makes a screen reader
|
|
|
168
164
|
read it out. A test targeting it asserts that.
|
|
169
165
|
|
|
170
166
|
When the role is not enough — two callouts up at once, a screenshot of one
|
|
171
|
-
particular surface — `openCallout` takes a `testId
|
|
172
|
-
|
|
173
|
-
to replace: it is a navi class like any other.
|
|
167
|
+
particular surface — `openCallout` takes a `testId`. Waiting on `.navi_callout`
|
|
168
|
+
is the thing to replace: it is a navi class like any other.
|
|
174
169
|
|
|
175
170
|
`<ControlSwap.Side>` answers the same situation the other way: the cap at the
|
|
176
171
|
end of the row is drawn by navi, and every prop the side does not use for
|
package/docs/typography.md
CHANGED
|
@@ -38,11 +38,11 @@ same names — `size="l"` is a font size, `padding="l"` is a gap.
|
|
|
38
38
|
|
|
39
39
|
Four of the five are formulas on `currentColor`, so they follow whatever ink
|
|
40
40
|
their container writes in — a dark card sets `color` and nothing else. Never
|
|
41
|
-
|
|
42
|
-
name, and an invented one (`var(--navi-text-color-discrete)`) resolves to
|
|
43
|
-
nothing and silently inherits.
|
|
44
|
-
why a surface re-declares
|
|
45
|
-
[css_architecture.md](./css_architecture.md#ink-ratio-paper-the-color-keywords).
|
|
41
|
+
read the `--navi-color-*` variable behind a keyword with `var()`: the keyword is
|
|
42
|
+
the name, and an invented one (`var(--navi-text-color-discrete)`) resolves to
|
|
43
|
+
nothing and silently inherits. Declare one only to pin a container's paper —
|
|
44
|
+
what the ratios are, how a theme changes them and why a surface re-declares
|
|
45
|
+
them: [css_architecture.md](./css_architecture.md#ink-ratio-paper-the-color-keywords).
|
|
46
46
|
|
|
47
47
|
Live examples: `src/text/demos/*_demo.html` — one page per concern
|
|
48
48
|
(`text_overflow_demo.html`, `text_spacing_demo.html`, `text_loading_demo.html`,
|
|
@@ -61,59 +61,19 @@ implements them with two unrelated mechanisms:
|
|
|
61
61
|
```
|
|
62
62
|
|
|
63
63
|
Do **not** write `lineClamp={1}`. `lineClamp` and `overflowEllipsis` are raw
|
|
64
|
-
`Box` style props
|
|
65
|
-
`
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
`
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
### Who takes `maxLines`
|
|
78
|
-
|
|
79
|
-
The prop is the same everywhere: a number of lines, cut with an ellipsis.
|
|
80
|
-
|
|
81
|
-
- **`Text`** and everything built on it — `Title`, `Paragraph`, `Caption`,
|
|
82
|
-
`Link`, `Time`, `Quantity`… — the prop flows through. `Badge` is `maxLines={1}`
|
|
83
|
-
by construction.
|
|
84
|
-
- **`Picker`** — its value, `maxLines={1}` by default; `variant="text"` and
|
|
85
|
-
`variant="bare"` draw the caller's own thing and are not clamped, a `Text` in
|
|
86
|
-
their `ui` cuts itself with its own `maxLines`.
|
|
87
|
-
- **`Spin`** (`DaySpin`, `NumberSpin`…) — its value; left out, a long value
|
|
88
|
-
wraps and the box grows.
|
|
89
|
-
- **`Binder`** — its tab labels, `maxLines={1}` by default, `false` lets them
|
|
90
|
-
wrap; overridable per item.
|
|
91
|
-
- **`BadgeList`** — its rows, measured (a wrapped flex row is not a line box):
|
|
92
|
-
the badges that fit and a "+N" badge for the rest. Inside a `Picker` it
|
|
93
|
-
inherits the picker's count, see [badge_list.md](./badge_list.md).
|
|
94
|
-
|
|
95
|
-
On a plain `Box`, `maxLines` is the raw CSS mapping described above: it clamps
|
|
96
|
-
whatever the box holds, and states nothing else.
|
|
97
|
-
|
|
98
|
-
### Truncation only happens if something says "you may shrink"
|
|
99
|
-
|
|
100
|
-
A flex or grid item refuses to become narrower than its content unless it is
|
|
101
|
-
told it may. `maxLines` sets `min-width: 0` on the `Text` itself, so the leaf is
|
|
102
|
-
covered — but **every `Box` between it and the element that actually has a
|
|
103
|
-
width must say it too**, or the whole chain grows instead of truncating:
|
|
104
|
-
|
|
105
|
-
```jsx
|
|
106
|
-
<Box flex width="300" spacing="s">
|
|
107
|
-
{/* without minWidth the row just grows */}
|
|
108
|
-
<Box flex expandX minWidth="0">
|
|
109
|
-
<Text maxLines={1}>…</Text>
|
|
110
|
-
</Box>
|
|
111
|
-
</Box>
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
Symmetrically, whatever must keep its full size says so with `shrink={false}`.
|
|
115
|
-
A row where the text gives way and nothing else does is the normal shape — see
|
|
116
|
-
the recipes below.
|
|
64
|
+
`Box` style props, mapped one-to-one to CSS for an element that is not a `Text`.
|
|
65
|
+
`maxLines` also states the `white-space` its line count needs — one line does
|
|
66
|
+
not wrap, n lines may — and that is what makes it hold anywhere: `white-space`
|
|
67
|
+
is inherited, and a raw clamp put under a single-line ancestor — a `Picker`'s
|
|
68
|
+
value, a `Time` — inherits `nowrap`, gets one line to cut, and lets the text run
|
|
69
|
+
past its box instead. An explicit `noWrap`/`pre`/`preLine` beside `maxLines`
|
|
70
|
+
still wins.
|
|
71
|
+
|
|
72
|
+
The prop is the same wherever it is taken — a `Picker`'s value (1 by default),
|
|
73
|
+
a `Spin`'s value, `Binder`'s tab labels, a `BadgeList`'s rows (see
|
|
74
|
+
[BadgeList](#badgelist)); `Badge` is `maxLines={1}` by construction. On a plain
|
|
75
|
+
`Box`, `maxLines` is the raw CSS mapping: it clamps whatever the box holds, and
|
|
76
|
+
states nothing else.
|
|
117
77
|
|
|
118
78
|
## A row: icon, text, icon
|
|
119
79
|
|
|
@@ -145,22 +105,34 @@ The three parts of it, and each is load-bearing: `shrink={false}` on the icons
|
|
|
145
105
|
(they are the fixed part), `expandX` on the text (it is the part that gives),
|
|
146
106
|
`maxLines={1}` (what giving way means for text). Drop any one and the row fails
|
|
147
107
|
in a different way — icons squashed, ellipsis never appearing, or the row
|
|
148
|
-
overflowing its container.
|
|
108
|
+
overflowing its container. Same shape for a count, a badge or a status kept to
|
|
109
|
+
the right of the ellipsis: a sibling with `shrink={false}`.
|
|
110
|
+
|
|
111
|
+
A flex or grid item refuses to become narrower than its content unless it is
|
|
112
|
+
told it may. `maxLines` sets `min-width: 0` on the `Text` itself, but **every
|
|
113
|
+
`Box` between it and the element that actually has a width must say it too**,
|
|
114
|
+
or the whole chain grows instead of truncating:
|
|
149
115
|
|
|
150
|
-
|
|
151
|
-
|
|
116
|
+
```jsx
|
|
117
|
+
<Box flex width="300" spacing="s">
|
|
118
|
+
{/* without minWidth the row just grows */}
|
|
119
|
+
<Box flex expandX minWidth="0">
|
|
120
|
+
<Text maxLines={1}>…</Text>
|
|
121
|
+
</Box>
|
|
122
|
+
</Box>
|
|
123
|
+
```
|
|
152
124
|
|
|
153
125
|
### The text wraps — the end icon must not be left alone
|
|
154
126
|
|
|
155
127
|
No truncation here: the text is allowed to take several lines. The trap is the
|
|
156
128
|
last child. An icon (or a unit, or an arrow) is an atomic inline, so the browser
|
|
157
|
-
is free to break the line right before it — and no character can prevent that;
|
|
158
|
-
word joiner does not suppress a break before an atomic inline. On the wrong
|
|
129
|
+
is free to break the line right before it — and no character can prevent that;
|
|
130
|
+
a word joiner does not suppress a break before an atomic inline. On the wrong
|
|
159
131
|
container width, the icon ends up alone on a line under the label.
|
|
160
132
|
|
|
161
133
|
`attachLastChild` fixes it: the last child and the **last word** before it are
|
|
162
|
-
put in one `white-space: nowrap` box
|
|
163
|
-
|
|
134
|
+
put in one `white-space: nowrap` box — only the last word, since wrapping the
|
|
135
|
+
whole preceding text would stop a long label from wrapping at all.
|
|
164
136
|
|
|
165
137
|
```jsx
|
|
166
138
|
<Text attachLastChild>
|
|
@@ -171,114 +143,112 @@ place where breaking is always wrong.
|
|
|
171
143
|
</Text>
|
|
172
144
|
```
|
|
173
145
|
|
|
174
|
-
Only the last word travels with the child — wrapping the whole preceding text
|
|
175
|
-
would stop a long label from wrapping at all.
|
|
176
|
-
|
|
177
146
|
`Link` does this on its own whenever it renders an end icon (`endIcon`,
|
|
178
|
-
`anchorIcon`, the external-target one)
|
|
179
|
-
|
|
180
|
-
yourself when you build such a pair outside `Link`.
|
|
147
|
+
`anchorIcon`, the external-target one). Write `attachLastChild` yourself when
|
|
148
|
+
you build such a pair outside `Link`.
|
|
181
149
|
|
|
182
150
|
## Spacing between children
|
|
183
151
|
|
|
184
152
|
`Text` injects a separator between its children whenever at least one side is an
|
|
185
|
-
element —
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
- `markAsOutsideTextFlow(Component)` — for something rendered inside a `Text`
|
|
193
|
-
that takes no room in the line (an absolutely positioned indicator, an
|
|
194
|
-
overlay). Without it, a separator is injected next to something invisible and
|
|
195
|
-
leaves a stray gap.
|
|
196
|
-
- `preventSpaceUnderlines` — inside an `<a>`, browsers draw the underline under
|
|
197
|
-
the space characters too. This replaces them with padding-based spaces, so the
|
|
198
|
-
underline stops at the text. `Link` sets it.
|
|
153
|
+
element — an icon and a label are spaced without a manual `{" "}` — and
|
|
154
|
+
`spacing` changes it. Two things opt out of that flow:
|
|
155
|
+
`markAsOutsideTextFlow(Component)`, for something rendered inside a `Text` that
|
|
156
|
+
takes no room in the line (an absolutely positioned indicator, an overlay),
|
|
157
|
+
where a separator would leave a stray gap next to something invisible; and
|
|
158
|
+
`preventSpaceUnderlines`, which `Link` sets, so an underline stops at the text
|
|
159
|
+
instead of running under the spaces.
|
|
199
160
|
|
|
200
161
|
## Emoji
|
|
201
162
|
|
|
202
163
|
**An emoji is not a character the layout can absorb.** The system emoji fonts
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
lines up with its siblings. Tighten the line to get the rows even again and the
|
|
209
|
-
glyph is clipped instead — its box is taller than a letter's, and what sticks
|
|
210
|
-
out is simply cut.
|
|
164
|
+
have a taller ascent/descent than any text font, and under `line-height: normal`
|
|
165
|
+
a line box takes the height of the tallest font it holds. So the moment an emoji
|
|
166
|
+
sits in a line, that line is taller than the ones around it: a row shifts down
|
|
167
|
+
next to its neighbours, a paragraph's lines are unevenly spaced. Tighten the
|
|
168
|
+
line to get the rows even again and the glyph is clipped instead.
|
|
211
169
|
|
|
212
170
|
**One number answers both: `--navi-line-height`, 1.25.** Tall enough to contain
|
|
213
171
|
an emoji's own box, so nothing is clipped; tight enough that a line carrying one
|
|
214
172
|
is exactly as tall as a line of plain text, so nothing moves. 1 cuts the top off
|
|
215
173
|
the glyph, 1.5 spaces the rows out more than reading them asks for. **An app
|
|
216
174
|
that displays what people typed cannot go below 1.25** — that is the floor this
|
|
217
|
-
token encodes
|
|
218
|
-
every component.
|
|
175
|
+
token encodes.
|
|
219
176
|
|
|
220
177
|
**Everything is written on that line, controls included.** The document sets it
|
|
221
|
-
on `:root`, and the
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
in
|
|
227
|
-
of pixels tall. That is what makes a
|
|
228
|
-
value keep its line, and its emoji its size, as it passes from the field it was
|
|
229
|
-
typed in to whatever displays it afterwards — an emoji typed in an input is not
|
|
230
|
-
clipped, and the same emoji displayed in a row, a bubble or a button does not
|
|
231
|
-
push anything around. `text_emoji_demo.html` shows it by swapping the field and
|
|
232
|
-
the rendering in place.
|
|
178
|
+
on `:root`, and the controls that would otherwise start from the browser's
|
|
179
|
+
`normal` — the one value the emoji breaks — are handed it by name: `Button`,
|
|
180
|
+
`Input`, `Textarea`, `Select`, and `Picker` and selectable `List` items, which
|
|
181
|
+
stand in a row with them. A form control inherits nothing from the page on its
|
|
182
|
+
own. So a value keeps its line, and its emoji its size, from the field it was
|
|
183
|
+
typed in to whatever displays it afterwards (`text_emoji_demo.html`).
|
|
233
184
|
|
|
234
185
|
A control takes it snapped to the pixel (`--navi-control-line-height`,
|
|
235
|
-
`round(
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
looks too high. A whole number of pixels has no remainder to put anywhere, and
|
|
240
|
-
a `Textarea` sized in `lh` lands on the same grid as the placeholder it
|
|
241
|
-
measures. The page's text keeps the plain number: a length would stop
|
|
242
|
-
following the font size the moment it is inherited.
|
|
186
|
+
`round(calc(var(--navi-line-height) * 1em), 1px)`): a fractional line puts its
|
|
187
|
+
remainder under the glyph, which then sits a pixel above the middle of its
|
|
188
|
+
field. The page's text keeps the plain number, which follows the font size when
|
|
189
|
+
inherited.
|
|
243
190
|
|
|
244
191
|
Change it on `:root` for a whole app. Do not unset it on a component, do not let
|
|
245
192
|
one fall back to `normal`, and do not raise a local `lineHeight` because of an
|
|
246
193
|
emoji: a `lineHeight` is a typographic choice for the text itself — a paragraph
|
|
247
194
|
that wants air — never a workaround for a glyph that grew too tall.
|
|
248
195
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
somebody's real name — a name field keeps to the rules that are about what can
|
|
254
|
-
be drawn at all (`data-displayable`: marks stacked into zalgo, a value that
|
|
255
|
-
shows nothing, blank lines in series).
|
|
196
|
+
Where an emoji belongs is a separate question, and not a layout one: a field
|
|
197
|
+
refuses one with `noEmoji`, and a name field — people are called what they are
|
|
198
|
+
called — keeps to what can be drawn at all (`displayable`) rather than a
|
|
199
|
+
whitelist. See [field_validation.md](./field_validation.md#constraints).
|
|
256
200
|
|
|
257
201
|
## Text that must not move when its style changes
|
|
258
202
|
|
|
259
203
|
A label that becomes bold when its row is selected reflows everything around it.
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
glyphs. Works on several lines, handles weight only.
|
|
267
|
-
|
|
268
|
-
And `shrinkWrap` for the reverse problem: an element whose box is wider than the
|
|
269
|
-
longest line it actually renders (a wrapped paragraph inside a flex or grid
|
|
270
|
-
container). It measures and pins the width to that longest line.
|
|
204
|
+
`holdSpaceForStyle={{ fontWeight: "bold" }}` reserves the space with an
|
|
205
|
+
invisible copy in the target style — any style change, single-line only (best
|
|
206
|
+
with `noWrap`); `boldStable` handles weight only, on several lines. And
|
|
207
|
+
`shrinkWrap` for the reverse problem: an element wider than the longest line it
|
|
208
|
+
renders (a wrapped paragraph inside a flex or grid container) is pinned to that
|
|
209
|
+
line.
|
|
271
210
|
|
|
272
211
|
## Loading
|
|
273
212
|
|
|
274
213
|
`loading` renders a shimmering skeleton in place of the content; `skeleton` is
|
|
275
214
|
the same bar without the animation. The children stay in the DOM (hidden), so
|
|
276
|
-
the block keeps the size the real text will have — pass the eventual text,
|
|
277
|
-
placeholder
|
|
278
|
-
|
|
279
|
-
The bar
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
over; navi declares neither, so both can be set on the element or on any
|
|
215
|
+
the block keeps the size the real text will have — **pass the eventual text,
|
|
216
|
+
not a placeholder**, whenever it is known.
|
|
217
|
+
|
|
218
|
+
The bar is painted with the text's own ink, so it is seen on whatever paper it
|
|
219
|
+
lands on without being told what that paper is. Where the surrounding ink is the
|
|
220
|
+
wrong cue, `--skeleton-color` and `--skeleton-shimmer-color` (the moving band)
|
|
221
|
+
take it over; navi declares neither, so both can be set on the element or on any
|
|
284
222
|
container above it.
|
|
223
|
+
|
|
224
|
+
## BadgeList
|
|
225
|
+
|
|
226
|
+
A row of badges that wraps. A plain one costs nothing — one element holding its
|
|
227
|
+
children as they are; only `max`, `fallback`, `maxLines` and `shrinkWrap` count
|
|
228
|
+
or measure. Once the list has something to decide (`max`, `fallback`,
|
|
229
|
+
`maxLines`), a `Badge` inside it does not render itself: it hands its props to
|
|
230
|
+
the list, which draws it. Two consequences:
|
|
231
|
+
|
|
232
|
+
- a `Badge` must be a **direct** child of the list — `<div><Badge /></div>`
|
|
233
|
+
takes the badge out of its wrapper;
|
|
234
|
+
- a badge's own `key` does not reach the badge the list draws, which is keyed
|
|
235
|
+
by position: reordering recreates the nodes rather than moving them.
|
|
236
|
+
|
|
237
|
+
Inside a `Picker`, the rows are capped by the picker's `maxLines` (the picker
|
|
238
|
+
turns its own clamp off: line-clamp never sees a wrapped flex row), and `max`
|
|
239
|
+
composes with it — the `+N` badge takes one of the `max` slots. A picker given
|
|
240
|
+
a `ui` draws that and only that, never its `placeholder`, so an empty
|
|
241
|
+
`BadgeList` there is a blank picker: pass the placeholder text as the list's
|
|
242
|
+
`fallback`.
|
|
243
|
+
|
|
244
|
+
```jsx
|
|
245
|
+
<BadgeList fallback="Select skills…">
|
|
246
|
+
{selected.map((skill) => (
|
|
247
|
+
<Badge key={skill}>{skill}</Badge>
|
|
248
|
+
))}
|
|
249
|
+
</BadgeList>
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Plain text is the right shape for it: it reads at the picker's own size, and the
|
|
253
|
+
box stays the same height empty and filled. It is drawn in the normal text
|
|
254
|
+
color, not the placeholder's — a caller's `ui` is never greyed as a placeholder.
|