@antadesign/anta 0.3.25 → 0.3.26
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/dist/anta_helpers.js +3 -3
- package/dist/box-types.d.ts +161 -0
- package/dist/box-types.js +0 -0
- package/dist/bundle.css +1 -1
- package/dist/bundle.js +25 -25
- package/dist/components/Box.d.ts +37 -0
- package/dist/components/Box.js +63 -0
- package/dist/elements/a-avatar.css +1 -1
- package/dist/elements/a-banner.css +1 -1
- package/dist/elements/a-banner.js +4 -4
- package/dist/elements/a-box.css +1 -0
- package/dist/elements/a-box.d.ts +41 -0
- package/dist/elements/a-box.js +576 -0
- package/dist/elements/a-button.css +1 -1
- package/dist/elements/a-calendar.js +2 -2
- package/dist/elements/a-card.css +1 -1
- package/dist/elements/a-checkbox.css +1 -1
- package/dist/elements/a-dialog.css +1 -1
- package/dist/elements/a-expander.css +1 -1
- package/dist/elements/a-expander.js +2 -2
- package/dist/elements/a-input-time.css +1 -1
- package/dist/elements/a-input-time.js +3 -3
- package/dist/elements/a-input.css +1 -1
- package/dist/elements/a-input.js +6 -6
- package/dist/elements/a-menu-item.css +1 -1
- package/dist/elements/a-menu-item.js +3 -3
- package/dist/elements/a-menu.css +1 -1
- package/dist/elements/a-menu.js +2 -2
- package/dist/elements/a-radio-group.js +3 -3
- package/dist/elements/a-radio.css +1 -1
- package/dist/elements/a-slider.css +1 -1
- package/dist/elements/a-switch.css +1 -1
- package/dist/elements/a-switch.js +2 -2
- package/dist/elements/a-tabpanel.js +2 -2
- package/dist/elements/a-tabs.css +1 -1
- package/dist/elements/a-tabs.js +6 -6
- package/dist/elements/a-tag.css +1 -1
- package/dist/elements/a-text.css +1 -1
- package/dist/elements/a-title.css +1 -1
- package/dist/elements/a-toast.js +2 -2
- package/dist/elements/a-tooltip.css +1 -1
- package/dist/elements/a-tooltip.d.ts +4 -3
- package/dist/elements/a-tooltip.js +9 -5
- package/dist/elements/index.d.ts +1 -0
- package/dist/elements/index.js +3 -0
- package/dist/general_types.d.ts +36 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/jsx-runtime.d.ts +2 -1
- package/docs/changelog.md +23 -3
- package/docs/components/avatar.md +368 -0
- package/docs/components/box.md +282 -0
- package/docs/index.md +4 -3
- package/package.json +1 -1
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
export const initialsOnly = { headRadiusTop: { mode: 'off' }, headRadiusBottom: { mode: 'off' }, bodyBorderRadius: { mode: 'off' } }
|
|
2
|
+
|
|
3
|
+
export const brandRange = { bgColor: { mode: 'range', l: [0.5, 0.62], c: [0.09, 0.15], h: [255, 285] }, harmony: true }
|
|
4
|
+
|
|
5
|
+
export const palette = { bgColor: { mode: 'list', values: ['#6c5ce7', '#00b894', '#e17055', '#0984e3', '#fdcb6e'] }, headRadiusTop: { mode: 'off' }, headRadiusBottom: { mode: 'off' }, bodyBorderRadius: { mode: 'off' } }
|
|
6
|
+
|
|
7
|
+
# Avatar
|
|
8
|
+
|
|
9
|
+
A userpic container. `Avatar` shows one of three things, in priority order: a
|
|
10
|
+
supplied image, a seed-generated placeholder figure, or initials. The generated
|
|
11
|
+
figure imitates a framed portrait — a head and shoulders over a colored
|
|
12
|
+
background — with no face or gender indicators, so an app can fill an empty slot
|
|
13
|
+
with a distinct, on-brand picture per user without an uploaded photo.
|
|
14
|
+
|
|
15
|
+
An alphanumeric `seed` drives the result deterministically: the same seed always
|
|
16
|
+
renders the same avatar, and different seeds are distinguishable. A `generator`
|
|
17
|
+
config caps each dimension to your brand.
|
|
18
|
+
|
|
19
|
+
## Generated userpic
|
|
20
|
+
|
|
21
|
+
Pass a `seed` (a stable user id) and a `name`. With no `generator`, each seed
|
|
22
|
+
draws a varied figure: background, head, and body colors, the figure's scale, the
|
|
23
|
+
head and shoulder angles, and the corner radius of each shape all move with the
|
|
24
|
+
seed, while the head and body hues stay coordinated with the background.
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
<Avatar seed="user-42" name="Vlad Korobov" />
|
|
28
|
+
<Avatar seed="lena-r" name="Lena Rossi" />
|
|
29
|
+
<Avatar seed="8f21ac" name="Sam Okafor" />
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Initials
|
|
33
|
+
|
|
34
|
+
Turn the shape dimensions off in the `generator` and the avatar falls back to
|
|
35
|
+
initials — the first letter of each of the first three words of `name` — over the
|
|
36
|
+
generated background. The background color still varies with the seed.
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
const initials = {
|
|
40
|
+
headRadiusTop: { mode: 'off' },
|
|
41
|
+
headRadiusBottom: { mode: 'off' },
|
|
42
|
+
bodyBorderRadius: { mode: 'off' },
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
<Avatar name="Vlad Korobov" generator={initials} />
|
|
46
|
+
<Avatar name="Marie Skłodowska Curie" generator={initials} />
|
|
47
|
+
<Avatar name="Madonna" generator={initials} />
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Image and badge
|
|
51
|
+
|
|
52
|
+
Set `src` to show an uploaded photo instead of a generated figure, cropped to
|
|
53
|
+
fill the container.
|
|
54
|
+
|
|
55
|
+
`badge` adds a corner badge, colored by tone, on any avatar — image or generated.
|
|
56
|
+
It takes any named tone, so the application decides what each one means:
|
|
57
|
+
`success` for online, `critical` for busy, `warning` for idle, `neutral` for
|
|
58
|
+
offline. Named tones track light and dark mode automatically.
|
|
59
|
+
|
|
60
|
+
The badge sits in a hole masked out of the picture rather than behind a painted
|
|
61
|
+
ring, so whatever is behind the avatar shows through the gap and no ring color
|
|
62
|
+
has to track the surface.
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
<Avatar round src="/anta-logo.svg" name="Anta" badge="success" />
|
|
66
|
+
<Avatar seed="lena-r" name="Lena Rossi" badge="critical" />
|
|
67
|
+
<Avatar seed="8f21ac" name="Sam Okafor" badge="warning" />
|
|
68
|
+
<Avatar seed="k-tanaka" name="Kenji Tanaka" badge="info" />
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`badge` also accepts any literal CSS color for a one-off badge, derived in oklch
|
|
72
|
+
the same way a named tone is.
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
<Avatar seed="a-lee" name="Ann Lee" badge="#ff1493" />
|
|
76
|
+
<Avatar seed="b-qi" name="Bo Qi" badge="rebeccapurple" />
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Size
|
|
80
|
+
|
|
81
|
+
`size` is `small` (32px), `medium` (44px, the default), or `large` (64px). Pass a
|
|
82
|
+
number for a pixel size.
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
<Avatar seed="user-42" name="Vlad Korobov" size="small" />
|
|
86
|
+
<Avatar seed="user-42" name="Vlad Korobov" size="large" />
|
|
87
|
+
<Avatar seed="user-42" name="Vlad Korobov" size={96} />
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Round
|
|
91
|
+
|
|
92
|
+
`round` makes the frame a circle. Pass a number (px) or a CSS length for a custom
|
|
93
|
+
radius instead. The badge hugs the circle's edge on a round frame.
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
<Avatar round seed="user-42" name="Vlad Korobov" />
|
|
97
|
+
<Avatar round seed="lena-r" name="Lena Rossi" badge="success" />
|
|
98
|
+
<Avatar seed="k-tanaka" name="Kenji Tanaka" round={10} />
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Brand constraints
|
|
102
|
+
|
|
103
|
+
The `generator` config caps the space so every avatar sits on your brand. Each
|
|
104
|
+
dimension takes one of four modes: `off` (excluded, a fixed default), `any` (its
|
|
105
|
+
full natural range), `range` (an explicit range), or `list` (a set of values the
|
|
106
|
+
seed picks from). Edit the config in the playground's **Code** tab to try them.
|
|
107
|
+
|
|
108
|
+
**Color ranges** cap OKLCH per channel — lightness, chroma, and hue. Narrow the
|
|
109
|
+
hue to your brand's, keep chroma modest, and every generated color stays in band
|
|
110
|
+
(chroma is clamped into sRGB, so it never clips). `harmony` ties the head and
|
|
111
|
+
body hue to the background.
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
const brand = {
|
|
115
|
+
bgColor: { mode: 'range', l: [0.5, 0.62], c: [0.09, 0.15], h: [255, 285] },
|
|
116
|
+
harmony: true,
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
<Avatar seed={user.id} name={user.name} generator={brand} />
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**Color lists** pass an explicit palette — a scheme the seed picks from at
|
|
123
|
+
random. Combine with `off` shapes for a classic initials avatar restricted to
|
|
124
|
+
your palette.
|
|
125
|
+
|
|
126
|
+
```tsx
|
|
127
|
+
const palette = {
|
|
128
|
+
bgColor: { mode: 'list', values: ['#6c5ce7', '#00b894', '#e17055', '#0984e3', '#fdcb6e'] },
|
|
129
|
+
headRadiusTop: { mode: 'off' },
|
|
130
|
+
headRadiusBottom: { mode: 'off' },
|
|
131
|
+
bodyBorderRadius: { mode: 'off' },
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
<Avatar name={user.name} generator={palette} />
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### The dimensions
|
|
138
|
+
|
|
139
|
+
| Dimension | Range |
|
|
140
|
+
| --- | --- |
|
|
141
|
+
| `bgColor`, `headColor`, `bodyColor` | OKLCH `l` / `c` / `h` caps, or a palette via `list` |
|
|
142
|
+
| `figureScale` | Relative size of the whole figure |
|
|
143
|
+
| `figureTranslate` | X and Y shift from the centered position |
|
|
144
|
+
| `figureAngle` | Rotation of the whole figure |
|
|
145
|
+
| `headAngle`, `bodyAngle` | Rotation of each part within the figure |
|
|
146
|
+
| `figureGap` | Space between head and body, `0` to `1` of head height |
|
|
147
|
+
| `headRadiusTop`, `headRadiusBottom` | `0` square, `1` fully round, above `1` elongated |
|
|
148
|
+
| `bodyBorderRadius` | `0` square to `1` fully round shoulders |
|
|
149
|
+
|
|
150
|
+
Corner radius is normalized: `0` is a square corner and `1` is fully round, so a
|
|
151
|
+
head with both radii at `1` is a circle. **Above `1` the head elongates** into an
|
|
152
|
+
oval, spending the excess on that half's height — `1.3` on both radii is a tall
|
|
153
|
+
oval, and more at the bottom than the top is an egg.
|
|
154
|
+
|
|
155
|
+
Two rules keep the head reading as a head. The bottom is never rounded less than
|
|
156
|
+
the top, so a value below the top radius is raised to match it: a rounder crown
|
|
157
|
+
over a squarer jaw reads mechanical, and the reverse reads natural. And under
|
|
158
|
+
`any` both radii average near or above `1`, so heads read round to oval unless
|
|
159
|
+
you narrow them yourself.
|
|
160
|
+
|
|
161
|
+
`generator` takes an `AvatarGenConfig`. Every field is optional — anything you
|
|
162
|
+
leave out keeps its default from the table above.
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
interface AvatarGenConfig {
|
|
166
|
+
bgColor?: ColorDim
|
|
167
|
+
headColor?: ColorDim
|
|
168
|
+
bodyColor?: ColorDim
|
|
169
|
+
figureScale?: ScalarDim
|
|
170
|
+
figureTranslate?: Vec2Dim
|
|
171
|
+
figureAngle?: ScalarDim
|
|
172
|
+
headAngle?: ScalarDim
|
|
173
|
+
bodyAngle?: ScalarDim
|
|
174
|
+
figureGap?: ScalarDim
|
|
175
|
+
headRadiusTop?: ScalarDim
|
|
176
|
+
headRadiusBottom?: ScalarDim
|
|
177
|
+
bodyBorderRadius?: ScalarDim
|
|
178
|
+
harmony?: boolean
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
type DimMode = 'off' | 'any' | 'range' | 'list'
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Modes
|
|
185
|
+
|
|
186
|
+
Every dimension carries a `mode`. It decides which of the dimension's other
|
|
187
|
+
fields are read; the rest are ignored.
|
|
188
|
+
|
|
189
|
+
| `mode` | Behavior | Fields read |
|
|
190
|
+
| --- | --- | --- |
|
|
191
|
+
| `off` | Excluded. The dimension holds a fixed neutral default. | none |
|
|
192
|
+
| `any` | Varies across the dimension's full natural range (the default). | none |
|
|
193
|
+
| `range` | Varies within an explicit range. | `min` / `max`, or `l` / `c` / `h`, or `x` / `y` |
|
|
194
|
+
| `list` | Picks one value at random from an explicit set. | `values` |
|
|
195
|
+
|
|
196
|
+
### Dimension types
|
|
197
|
+
|
|
198
|
+
**`ScalarDim`** — a single number: scale, an angle, the gap, a corner radius.
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
interface ScalarDim {
|
|
202
|
+
mode?: DimMode
|
|
203
|
+
min?: number // range: lower bound
|
|
204
|
+
max?: number // range: upper bound
|
|
205
|
+
values?: number[] // list: the allowed values
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
**`ColorDim`** — a color. `range` caps OKLCH per channel as `[min, max]` pairs,
|
|
210
|
+
with chroma clamped into sRGB so a value can never clip. `list` passes a palette
|
|
211
|
+
of any CSS colors.
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
interface ColorDim {
|
|
215
|
+
mode?: DimMode
|
|
216
|
+
l?: [number, number] // range: lightness, 0 to 1
|
|
217
|
+
c?: [number, number] // range: chroma, 0 to ~0.4
|
|
218
|
+
h?: [number, number] // range: hue, 0 to 360
|
|
219
|
+
values?: string[] // list: the allowed CSS colors
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
**`Vec2Dim`** — the figure's offset from center, used only by `figureTranslate`.
|
|
224
|
+
Each axis runs from −50 to 50 in the frame's coordinates, where 0 centers the
|
|
225
|
+
head.
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
interface Vec2Dim {
|
|
229
|
+
mode?: DimMode // 'list' is not used here
|
|
230
|
+
x?: [number, number] // range: horizontal bounds
|
|
231
|
+
y?: [number, number] // range: vertical bounds
|
|
232
|
+
values?: [number, number][] // list: allowed [x, y] points
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
`harmony` is a plain boolean rather than a dimension: when true, `headColor` and
|
|
237
|
+
`bodyColor` take their hue from the background so the parts stay coordinated.
|
|
238
|
+
|
|
239
|
+
### Defaults per dimension
|
|
240
|
+
|
|
241
|
+
What `off` pins each dimension to, and what `any` samples. Corner radii are
|
|
242
|
+
sampled toward the top of their range, so heads average round to oval.
|
|
243
|
+
|
|
244
|
+
| Dimension | `off` | `any` samples |
|
|
245
|
+
| --- | --- | --- |
|
|
246
|
+
| `bgColor` | mid grey | `l` 0.5–0.82, `c` 0.02–0.12, any hue |
|
|
247
|
+
| `headColor` | light grey | `l` 0.78–0.96, `c` 0.015–0.075, any hue |
|
|
248
|
+
| `bodyColor` | light grey | `l` 0.68–0.9, `c` 0.015–0.09, any hue |
|
|
249
|
+
| `figureScale` | `1` | 0.76–1.2 |
|
|
250
|
+
| `figureTranslate` | `[0, 0]` | X −15 to 15; Y −14 to 4 |
|
|
251
|
+
| `figureAngle` | `0` | −15° to 15° |
|
|
252
|
+
| `headAngle` | `0` | −20° to 20° |
|
|
253
|
+
| `bodyAngle` | `0` | −16° to 16° |
|
|
254
|
+
| `figureGap` | `0.25` | 0.12–0.42 |
|
|
255
|
+
| `headRadiusTop` | `1` | 0.35–1.25 |
|
|
256
|
+
| `headRadiusBottom` | `1` | 0.45–1.5 |
|
|
257
|
+
| `bodyBorderRadius` | `1` | 0.25–1 |
|
|
258
|
+
| `harmony` | — | `true` |
|
|
259
|
+
|
|
260
|
+
With every shape dimension (`headRadiusTop`, `headRadiusBottom`,
|
|
261
|
+
`bodyBorderRadius`) set to `off`, no figure shape is configured and the avatar
|
|
262
|
+
renders initials instead.
|
|
263
|
+
|
|
264
|
+
The generation helpers are exported too, for a consumer that needs the result
|
|
265
|
+
outside a component: `resolveAvatar(config, seed)` returns the resolved colors
|
|
266
|
+
and geometry, `avatarToSvg(resolved)` renders it, and `getInitials(name)` derives
|
|
267
|
+
the fallback letters.
|
|
268
|
+
|
|
269
|
+
### Props
|
|
270
|
+
|
|
271
|
+
| Prop | Type | Default | Description |
|
|
272
|
+
|------|------|---------|-------------|
|
|
273
|
+
| `badge?` | 'neutral' \| 'brand' \| 'info' \| 'success' \| 'warning' \| 'critical' \| (string & {}) | — | Corner badge, colored by tone. Pass a named tone or any literal CSS color;
|
|
274
|
+
the application decides what each tone means (`success` for online,
|
|
275
|
+
`critical` for busy, `neutral` for offline). Omit for no badge. |
|
|
276
|
+
| `generator?` | AvatarGenConfig | — | Brand generation constraints — each dimension is OFF / ANY / RANGE / LIST.
|
|
277
|
+
Colors cap OKLCH ranges or pass an explicit palette. Omit for the default
|
|
278
|
+
varied figure. Define one config for the app and reuse it across avatars. |
|
|
279
|
+
| `name?` | string | — | Person or entity name. Supplies the initials fallback (up to 3 letters from
|
|
280
|
+
the first 3 words) and the accessible name, and seeds generation when `seed`
|
|
281
|
+
is absent. |
|
|
282
|
+
| `round?` | boolean \| number \| string | — | Fully-round (circular) frame. Pass a `number` (px) or a CSS length string
|
|
283
|
+
for a custom radius instead. |
|
|
284
|
+
| `seed?` | string | — | Alphanumeric seed that deterministically drives the generated userpic. The
|
|
285
|
+
same seed always renders the same avatar. Falls back to `name` when omitted,
|
|
286
|
+
so a stable user id or name is a good value. |
|
|
287
|
+
| `size?` | 'small' \| 'medium' \| 'large' \| number | medium | Size of the square container. A number is a pixel size. |
|
|
288
|
+
| `src?` | string | — | Image URL. When set, the image is shown instead of a generated userpic,
|
|
289
|
+
cropped to fill the container. |
|
|
290
|
+
|
|
291
|
+
Use the web component directly when you are not using React or Preact. The
|
|
292
|
+
generation config rides the `config` attribute as a JSON string, so the element
|
|
293
|
+
generates from plain markup with no JavaScript of your own.
|
|
294
|
+
|
|
295
|
+
```html
|
|
296
|
+
<a-avatar seed="user-42" name="Vlad Korobov"></a-avatar>
|
|
297
|
+
|
|
298
|
+
<a-avatar
|
|
299
|
+
name="Vlad Korobov"
|
|
300
|
+
config='{"headRadiusTop":{"mode":"off"},"headRadiusBottom":{"mode":"off"},"bodyBorderRadius":{"mode":"off"}}'
|
|
301
|
+
></a-avatar>
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Reach for the props first: **`size`** sets the dimensions and **`generator`** the
|
|
305
|
+
generated look. Everything else is `--avatar-*` custom properties on the host and
|
|
306
|
+
`::part()` on the shadow content.
|
|
307
|
+
|
|
308
|
+
The container is a rounded square by default; `round` makes it a circle and takes
|
|
309
|
+
a length for a custom radius, so reach for the prop before the custom property.
|
|
310
|
+
The badge is sized and placed with `--avatar-badge-ratio` (its size as a fraction
|
|
311
|
+
of the frame), `--avatar-badge-inset`, and `--avatar-badge-gap` (the width of the
|
|
312
|
+
cutout around it). Its fill comes from `badge`, so route color through the prop
|
|
313
|
+
rather than overriding `--avatar-badge-color`.
|
|
314
|
+
|
|
315
|
+
```css
|
|
316
|
+
a-avatar.badge-dot {
|
|
317
|
+
--avatar-badge-ratio: 0.34; /* a larger badge */
|
|
318
|
+
--avatar-badge-gap: 4px; /* in a wider cutout */
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
The shadow content exposes `::part(frame)` (the picture — the generated figure or
|
|
323
|
+
the image, whichever is showing), `::part(image)` (only the `src` image), and
|
|
324
|
+
`::part(badge)` (the corner badge) for anything the custom properties do not
|
|
325
|
+
cover.
|
|
326
|
+
|
|
327
|
+
## Example
|
|
328
|
+
|
|
329
|
+
```tsx
|
|
330
|
+
import { Avatar, type AvatarGenConfig } from '@antadesign/anta'
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* @play props Generator
|
|
334
|
+
* The avatar's JSON-like generation settings. Nested values become editable
|
|
335
|
+
* fields in the Props panel.
|
|
336
|
+
*/
|
|
337
|
+
const generator = {
|
|
338
|
+
// Every dimension takes one mode: 'off' | 'any' | 'range' | 'list'.
|
|
339
|
+
bgColor: { mode: 'range', l: [0.55, 0.72], c: [0.05, 0.12], h: [0, 360] },
|
|
340
|
+
headColor: { mode: 'any' },
|
|
341
|
+
bodyColor: { mode: 'any' },
|
|
342
|
+
// Corner radius: 0 square, 1 fully round, above 1 elongated into an oval.
|
|
343
|
+
// The bottom is never rounded less than the top, so an egg leans jaw-down.
|
|
344
|
+
headRadiusTop: { mode: 'range', min: 0.5, max: 1.15 },
|
|
345
|
+
headRadiusBottom: { mode: 'range', min: 0.6, max: 1.35 },
|
|
346
|
+
bodyBorderRadius: { mode: 'any' },
|
|
347
|
+
// Space between head and body, as a fraction of head height (0 to 1).
|
|
348
|
+
figureGap: { mode: 'range', min: 0, max: 0.3 },
|
|
349
|
+
figureScale: { mode: 'any' },
|
|
350
|
+
headAngle: { mode: 'any' },
|
|
351
|
+
bodyAngle: { mode: 'any' },
|
|
352
|
+
harmony: true,
|
|
353
|
+
} satisfies AvatarGenConfig
|
|
354
|
+
|
|
355
|
+
function Demo() {
|
|
356
|
+
return (
|
|
357
|
+
/** @play props Avatar */
|
|
358
|
+
<Avatar
|
|
359
|
+
seed="user-42"
|
|
360
|
+
name="Vlad Korobov"
|
|
361
|
+
size={96}
|
|
362
|
+
badge="success"
|
|
363
|
+
round
|
|
364
|
+
generator={generator}
|
|
365
|
+
/>
|
|
366
|
+
)
|
|
367
|
+
}
|
|
368
|
+
```
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# Box
|
|
2
|
+
|
|
3
|
+
`Box` is a DOM container that adds layout props, overflow states, and browser
|
|
4
|
+
context events.
|
|
5
|
+
|
|
6
|
+
## Display
|
|
7
|
+
|
|
8
|
+
`display` and `gap` set the matching CSS properties. A numeric `gap` uses
|
|
9
|
+
pixels. A string accepts any CSS length.
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
<Box round={8}><span /></Box>
|
|
13
|
+
<Box display="flex" round={8} gap={6}><span /></Box>
|
|
14
|
+
<Box display="grid" round={8} gap="0.5rem" style={{ gridTemplateColumns: '1fr 1fr' }}><span /></Box>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`round` accepts the same values. Pass `round` for fully rounded corners. Omit
|
|
18
|
+
it for square corners.
|
|
19
|
+
|
|
20
|
+
`display`, `gap`, and `round` use the `anta.components` layer. Your
|
|
21
|
+
`className` and `style` override them.
|
|
22
|
+
|
|
23
|
+
## Overflow
|
|
24
|
+
|
|
25
|
+
Box reports when content exceeds its bounds (`overflowX` and `overflowY`), when
|
|
26
|
+
CSS clips it (`clippedX` and `clippedY`), and when it can scroll (`scrollableX`
|
|
27
|
+
and `scrollableY`). It exposes each measurement as a CSS state, so CSS can react
|
|
28
|
+
without JSX state or a measurement ref.
|
|
29
|
+
|
|
30
|
+
A Box measures for `fade`, `onMeasureChange`, or `observe="size"`. Use
|
|
31
|
+
`observe="size"` when CSS reads its states. A Box with none of these attaches no
|
|
32
|
+
observers. Measurement pauses off screen, except `fade`, which measures on
|
|
33
|
+
connection so it can paint its mask.
|
|
34
|
+
|
|
35
|
+
`observe` accepts `"size"`, `"context"`, or `"all"`. A bare `observe` means
|
|
36
|
+
`"all"`. The JSX `Box` derives the needed value from its handlers. When you use
|
|
37
|
+
`a-box` directly, set it yourself; `addEventListener` does not start reporting:
|
|
38
|
+
|
|
39
|
+
```html
|
|
40
|
+
<a-box observe="size">Content</a-box>
|
|
41
|
+
<a-box observe="all">Content</a-box>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The attribute works with listeners added before upgrade, removed with `once` or
|
|
45
|
+
`AbortSignal`, and replaced during React renders.
|
|
46
|
+
|
|
47
|
+
`.edge` is a demo class name. Use your own selector.
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
<Box observe="size" round={8} className="edge" style={{ width: 150 }}>Content that fits.</Box>
|
|
51
|
+
|
|
52
|
+
<Box observe="size" round={8} className="edge" style={{ width: 150, overflow: 'hidden', whiteSpace: 'nowrap' }}>
|
|
53
|
+
A label too long for this box.
|
|
54
|
+
</Box>
|
|
55
|
+
|
|
56
|
+
<Box observe="size" round={8} className="edge" style={{ width: 150, height: 56, overflowY: 'auto' }}>
|
|
57
|
+
One. Two. Three. Four. Five. Six. Seven. Eight. Nine. Ten.
|
|
58
|
+
</Box>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```css
|
|
62
|
+
.edge:state(clipped-x) { border-color: var(--border-3-warning); }
|
|
63
|
+
.edge:state(scrollable-y) { border-color: var(--border-3-info); }
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Fading a clipped edge
|
|
67
|
+
|
|
68
|
+
`fade` masks each edge that hides content. Scrolling moves the mask to the edge
|
|
69
|
+
that still hides content and removes it at the end. `fadeSize` sets its depth.
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
<Box fade fadeSize={32} round display="flex" gap={6} style={{ overflowX: 'auto' }}>
|
|
73
|
+
{TAGS.map((t) => <Tag key={t} size="small" label={t} />)}
|
|
74
|
+
</Box>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The mask clips to the padding box. It preserves the Box border, shadows, and
|
|
78
|
+
focus ring when no edge is hidden.
|
|
79
|
+
|
|
80
|
+
The four edge states are `hidden-start-x`, `hidden-end-x`, `hidden-start-y`, and
|
|
81
|
+
`hidden-end-y`. Use `hiddenStartX` and its siblings in JSX. To style the states
|
|
82
|
+
without `fade`, add `observe="size"`.
|
|
83
|
+
|
|
84
|
+
```css
|
|
85
|
+
/* <Box observe="size" className="my-box"> */
|
|
86
|
+
.my-box:state(hidden-end-x) {
|
|
87
|
+
mask-image: linear-gradient(to right, black calc(100% - 2rem), transparent);
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Tooltip on clipped content
|
|
92
|
+
|
|
93
|
+
`Tooltip truncatedOnly` uses Box's `isTruncated` getter. It shows when the Box
|
|
94
|
+
clips on either axis, including wrapped children.
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
const TAGS = ['frontend', 'design-system', 'a11y', 'performance']
|
|
98
|
+
|
|
99
|
+
<Box display="flex" round={8} gap={6} className="tag-box" style={{ height: 34 }}>
|
|
100
|
+
{TAGS.map((t) => <Tag key={t} size="small" label={t} />)}
|
|
101
|
+
<Tooltip truncatedOnly>{TAGS.join(' · ')}</Tooltip>
|
|
102
|
+
</Box>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```css
|
|
106
|
+
.tag-box { flex-wrap: wrap; padding: 6px; overflow: hidden; }
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Measurements
|
|
110
|
+
|
|
111
|
+
`measurechange` fires one frame after observation starts and after the Box or its
|
|
112
|
+
content changes. `changed` contains the moved fields, and `current` is the full
|
|
113
|
+
snapshot. The event pauses off screen and reports again when the Box returns.
|
|
114
|
+
|
|
115
|
+
```tsx title="measurechange"
|
|
116
|
+
<Box
|
|
117
|
+
style={{ maxWidth: 210, overflow: 'hidden', whiteSpace: 'nowrap' }}
|
|
118
|
+
onMeasureChange={(_, { changed, current }) => {
|
|
119
|
+
// changed → { clippedX: true, scrollWidth: 412 }
|
|
120
|
+
if (changed.clippedX) setShowOverflowHint(current.clippedX)
|
|
121
|
+
}}
|
|
122
|
+
>
|
|
123
|
+
Long account name
|
|
124
|
+
</Box>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
The preview updates as you resize or scroll the Box. Tags mark `true` values.
|
|
128
|
+
|
|
129
|
+
[`BoxMeasurement`](#boxmeasurement) lists every field. Box maps each overflow
|
|
130
|
+
field to a kebab-case CSS state, such as `overflow-x`, `clipped-y`,
|
|
131
|
+
`scrollable-x`, and `hidden-end-y`. These states are not host attributes. They
|
|
132
|
+
remain current only while `fade`, `observe="size"`, or `onMeasureChange` enables
|
|
133
|
+
measurement.
|
|
134
|
+
|
|
135
|
+
## Context
|
|
136
|
+
|
|
137
|
+
`contextchange` uses the same `{ changed, current }` shape. Boxes share one
|
|
138
|
+
context observer per window, which stops when the last context reader
|
|
139
|
+
disconnects.
|
|
140
|
+
|
|
141
|
+
[`BoxContext`](#boxcontext) lists every field. `contextchange` sets no CSS
|
|
142
|
+
states. Use `:focus-within`, mode classes, and media queries when CSS is the
|
|
143
|
+
reader.
|
|
144
|
+
|
|
145
|
+
Every value in the following preview is live. Switch the site theme, resize the
|
|
146
|
+
window, zoom the page, or use a touch device. Treat `osVersion` and
|
|
147
|
+
`browserVersion` as hints because browsers freeze parts of these values. Use
|
|
148
|
+
`pointer`, `hover`, or a feature test to gate behavior. The second Box is in a
|
|
149
|
+
`.light` scope, so its `mode` remains `light` on a dark page while `globalMode`
|
|
150
|
+
follows the document.
|
|
151
|
+
|
|
152
|
+
`context.font` is the Box's resolved text style, `context.inset` measures from
|
|
153
|
+
the border edge to the content edge, and `devicePixelRatio` provides the canvas
|
|
154
|
+
scale. Use them to align canvas text with the DOM.
|
|
155
|
+
|
|
156
|
+
`measurement` cannot derive the inset: `width` is the border box, and
|
|
157
|
+
`clientWidth` is the padding box.
|
|
158
|
+
|
|
159
|
+
Set `ctx.font` from `font.shorthand` first. Chromium and Firefox reset
|
|
160
|
+
`fontStretch`, `fontVariantCaps`, `fontKerning`, and `textRendering` when
|
|
161
|
+
`ctx.font` changes. `stretch` and `variantCaps` stay out of the shorthand because
|
|
162
|
+
a percentage `font-stretch` invalidates it.
|
|
163
|
+
|
|
164
|
+
```tsx title="canvas"
|
|
165
|
+
<Box
|
|
166
|
+
onContextChange={(_, { current }) => {
|
|
167
|
+
const canvas = canvasRef.current
|
|
168
|
+
const ctx = canvas?.getContext('2d')
|
|
169
|
+
if (!ctx) return
|
|
170
|
+
|
|
171
|
+
const { font, devicePixelRatio: dpr } = current
|
|
172
|
+
canvas.width = 300 * dpr
|
|
173
|
+
canvas.height = 80 * dpr
|
|
174
|
+
ctx.scale(dpr, dpr)
|
|
175
|
+
|
|
176
|
+
ctx.font = font.shorthand
|
|
177
|
+
ctx.fontStretch = font.stretch
|
|
178
|
+
ctx.fontVariantCaps = font.variantCaps
|
|
179
|
+
ctx.fontKerning = font.kerning
|
|
180
|
+
ctx.textRendering = font.textRendering
|
|
181
|
+
ctx.letterSpacing = font.letterSpacing
|
|
182
|
+
ctx.wordSpacing = font.wordSpacing
|
|
183
|
+
ctx.direction = font.direction
|
|
184
|
+
ctx.fillStyle = font.color
|
|
185
|
+
|
|
186
|
+
const x = current.inset.borderLeft + current.inset.paddingLeft
|
|
187
|
+
const y = current.inset.borderTop + current.inset.paddingTop
|
|
188
|
+
ctx.fillText('Matches the DOM', x, y + (font.lineHeight ?? font.size))
|
|
189
|
+
}}
|
|
190
|
+
>
|
|
191
|
+
<canvas ref={canvasRef} />
|
|
192
|
+
</Box>
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
`getComputedStyle(element).font` is empty in Chromium, Firefox, and WebKit, so
|
|
196
|
+
Box assembles the shorthand.
|
|
197
|
+
|
|
198
|
+
```tsx title="contextchange"
|
|
199
|
+
<Box onContextChange={(_, { changed, current }) => {
|
|
200
|
+
if (changed.mode) updatePreviewTheme(current.mode)
|
|
201
|
+
if (changed.focusWithin) announceFocus(current.focusWithin)
|
|
202
|
+
}}>
|
|
203
|
+
<span>Preview content</span>
|
|
204
|
+
</Box>
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Props
|
|
208
|
+
|
|
209
|
+
| Prop | Type | Default | Description |
|
|
210
|
+
|------|------|---------|-------------|
|
|
211
|
+
| `display?` | BoxDisplay | block | Layout model for the host. All other layout, sizing, mask, and shadow
|
|
212
|
+
properties stay ordinary `className` / `style` CSS on the Box itself. |
|
|
213
|
+
| `fade?` | boolean | — | Fades out every edge that currently hides clipped content, and drops the
|
|
214
|
+
fade from an edge once the reader scrolls to it. |
|
|
215
|
+
| `fadeSize?` | number \| string | 24 | Depth of the `fade` gradient. A `number` is pixels; a string is any CSS
|
|
216
|
+
length. |
|
|
217
|
+
| `gap?` | number \| string | — | Gap between children, matching the CSS `gap` property. A `number` is
|
|
218
|
+
pixels; a string is any CSS length or two-value gap (`'1rem'`,
|
|
219
|
+
`'8px 16px'`). Applies while the Box is a flex or grid container. |
|
|
220
|
+
| `observe?` | 'size' \| 'context' \| 'all' | — | What the Box watches, when a handler is not what turns it on. `'size'`
|
|
221
|
+
keeps the overflow CSS states (`:state(clipped-x)`, `:state(scrollable-y)`,
|
|
222
|
+
…) current — reach for it when your own CSS is the only reader. `'context'`
|
|
223
|
+
and `'all'` are there for symmetry; passing `onMeasureChange` or
|
|
224
|
+
`onContextChange` already turns the matching half on. |
|
|
225
|
+
| `onContextChange?` | (event, detail) => void | — | Fired after Box's browser and local rendering context changes. `detail`
|
|
226
|
+
contains the changed fields and a full current snapshot. |
|
|
227
|
+
| `onMeasureChange?` | (event, detail) => void | — | Fired after Box geometry or its content-overflow state changes. `detail`
|
|
228
|
+
contains the changed fields and a full current snapshot. |
|
|
229
|
+
| `round?` | boolean \| number \| string | — | Fully-round corners (`border-radius: 999px`, clamped to the box). Pass a
|
|
230
|
+
`number` (px) or a CSS length string (`'1rem'`) for a custom radius. Omit
|
|
231
|
+
for square corners. |
|
|
232
|
+
|
|
233
|
+
### BoxMeasurement
|
|
234
|
+
|
|
235
|
+
The `measurechange` payload, as `changed` (only what moved) and `current` (the
|
|
236
|
+
whole snapshot).
|
|
237
|
+
|
|
238
|
+
### BoxContext
|
|
239
|
+
|
|
240
|
+
The `contextchange` payload, in the same two shapes.
|
|
241
|
+
|
|
242
|
+
### BoxFont
|
|
243
|
+
|
|
244
|
+
`context.font`, the resolved text style.
|
|
245
|
+
|
|
246
|
+
### BoxInset
|
|
247
|
+
|
|
248
|
+
`context.inset`, the distance from the border edge to the content edge.
|
|
249
|
+
|
|
250
|
+
Use `<a-box>` when you assemble DOM without a JSX wrapper. Both events are
|
|
251
|
+
ordinary, non-bubbling `CustomEvent`s carrying the same detail object.
|
|
252
|
+
|
|
253
|
+
```html title="a-box"
|
|
254
|
+
<a-box display="grid" gap="8px" round="12px" observe="all" id="summary"
|
|
255
|
+
style="overflow: auto; max-height: 16rem">
|
|
256
|
+
<p>Summary content</p>
|
|
257
|
+
</a-box>
|
|
258
|
+
|
|
259
|
+
<script type="module">
|
|
260
|
+
import '@antadesign/anta/elements/a-box'
|
|
261
|
+
|
|
262
|
+
const box = document.querySelector('#summary')
|
|
263
|
+
box.addEventListener('measurechange', ({ detail }) => {
|
|
264
|
+
console.log(detail.changed, detail.current.scrollableY)
|
|
265
|
+
})
|
|
266
|
+
box.addEventListener('contextchange', ({ detail }) => {
|
|
267
|
+
console.log(detail.changed, detail.current.mode)
|
|
268
|
+
})
|
|
269
|
+
</script>
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
`gap`, `round`, and `fade-size` read their length through typed `attr()`, which
|
|
273
|
+
today only Chrome supports. The matching custom properties in the host's inline
|
|
274
|
+
style work everywhere and take precedence, so that is what the JSX wrapper sets:
|
|
275
|
+
|
|
276
|
+
```html
|
|
277
|
+
<a-box gap round fade
|
|
278
|
+
style="--box-gap: 8px; --box-round: 12px; --box-fade-size: 32px">Content</a-box>
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
`box.measurement`, `box.context`, and `box.isTruncated` read the same values
|
|
282
|
+
synchronously.
|
package/docs/index.md
CHANGED
|
@@ -21,6 +21,7 @@ Anta is designed for a clean DOM. Prefer the props and attributes exposed by eac
|
|
|
21
21
|
- [Tag](./components/tag.md)
|
|
22
22
|
- [Tooltip](./components/tooltip.md)
|
|
23
23
|
- [Icon](./components/icon.md)
|
|
24
|
+
- [Avatar](./components/avatar.md)
|
|
24
25
|
- [Loader](./components/loader.md)
|
|
25
26
|
- [Progress](./components/progress.md)
|
|
26
27
|
|
|
@@ -33,9 +34,6 @@ Anta is designed for a clean DOM. Prefer the props and attributes exposed by eac
|
|
|
33
34
|
- [Tabs](./components/tabs.md)
|
|
34
35
|
- [Steps](./components/steps.md)
|
|
35
36
|
|
|
36
|
-
- [Expander](./components/expander.md)
|
|
37
|
-
- [Menu](./components/menu.md)
|
|
38
|
-
|
|
39
37
|
- [Input](./components/input.md)
|
|
40
38
|
- [InputAutocomplete](./components/input-autocomplete.md)
|
|
41
39
|
- [InputDate](./components/input-date.md)
|
|
@@ -43,10 +41,13 @@ Anta is designed for a clean DOM. Prefer the props and attributes exposed by eac
|
|
|
43
41
|
- [Select](./components/select.md)
|
|
44
42
|
- [SelectFaceted](./components/select-faceted.md)
|
|
45
43
|
|
|
44
|
+
- [Box](./components/box.md)
|
|
46
45
|
- [Banner](./components/banner.md)
|
|
47
46
|
- [Card](./components/card.md)
|
|
48
47
|
- [Dialog](./components/dialog.md)
|
|
49
48
|
- [Toaster](./components/toaster.md)
|
|
49
|
+
- [Expander](./components/expander.md)
|
|
50
|
+
- [Menu](./components/menu.md)
|
|
50
51
|
|
|
51
52
|
## Packages
|
|
52
53
|
|