@antadesign/anta 0.3.17 → 0.3.19
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 +38 -242
- package/dist/components/Input.d.ts +4 -1
- package/dist/components/Input.js +2 -0
- package/dist/components/InputDate.css +1 -0
- package/dist/components/InputDate.d.ts +1 -0
- package/dist/components/InputDate.js +5 -5
- package/dist/components/Loader.d.ts +39 -0
- package/dist/components/Loader.js +33 -0
- package/dist/components/Menu.d.ts +5 -1
- package/dist/components/Menu.js +3 -2
- package/dist/components/Progress.d.ts +17 -8
- package/dist/components/Progress.js +12 -9
- package/dist/components/Select.d.ts +1 -0
- package/dist/components/Select.js +9 -13
- package/dist/components/SelectFaceted.d.ts +1 -0
- package/dist/components/SelectFaceted.js +29 -23
- package/dist/components/select-parts.css +1 -0
- package/dist/elements/a-button.css +1 -1
- package/dist/elements/a-icon.d.ts +1 -0
- package/dist/elements/a-icon.js +1 -0
- package/dist/elements/a-input.css +1 -1
- package/dist/elements/a-loader.css +1 -0
- package/dist/elements/a-loader.d.ts +12 -0
- package/dist/elements/a-loader.js +15 -0
- package/dist/elements/a-menu.js +12 -0
- package/dist/elements/a-progress.css +1 -1
- package/dist/elements/a-progress.js +32 -2
- package/dist/elements/a-slider.css +1 -1
- package/dist/elements/a-tabs.css +1 -1
- package/dist/elements/a-tabs.d.ts +3 -0
- package/dist/elements/a-tabs.js +40 -1
- package/dist/elements/index.d.ts +1 -0
- package/dist/elements/index.js +3 -0
- package/dist/general_types.d.ts +27 -2
- package/dist/index.d.ts +6 -1
- package/dist/index.js +2 -0
- package/dist/jsx-runtime.d.ts +11 -1
- package/dist/reset.css +1 -1
- package/dist/theme-anta.css +1 -1
- package/docs/accessibility.md +34 -0
- package/docs/changelog.md +551 -0
- package/docs/colors.md +42 -0
- package/docs/comparison.md +28 -0
- package/docs/components/banner.md +267 -0
- package/docs/components/button.md +603 -0
- package/docs/components/card.md +322 -0
- package/docs/components/checkbox.md +297 -0
- package/docs/components/dialog.md +314 -0
- package/docs/components/expander.md +529 -0
- package/docs/components/icon.md +155 -0
- package/docs/components/input-autocomplete.md +101 -0
- package/docs/components/input-date.md +406 -0
- package/docs/components/input-time.md +174 -0
- package/docs/components/input.md +610 -0
- package/docs/components/loader.md +139 -0
- package/docs/components/menu.md +447 -0
- package/docs/components/progress.md +182 -0
- package/docs/components/radio.md +369 -0
- package/docs/components/select-faceted.md +456 -0
- package/docs/components/select.md +931 -0
- package/docs/components/slider.md +142 -0
- package/docs/components/switch.md +198 -0
- package/docs/components/tabs.md +471 -0
- package/docs/components/tag.md +185 -0
- package/docs/components/text.md +148 -0
- package/docs/components/title.md +146 -0
- package/docs/components/toaster.md +313 -0
- package/docs/components/tooltip.md +177 -0
- package/docs/credits.md +22 -0
- package/docs/index.md +52 -0
- package/docs/install-config.md +240 -0
- package/docs/normalization.md +105 -0
- package/docs/overview.md +10 -0
- package/docs/packages/stickers.md +187 -0
- package/docs/packages/table.md +153 -0
- package/docs/theming.md +13 -0
- package/package.json +10 -6
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Loader
|
|
2
|
+
|
|
3
|
+
`Loader` is an animated, icon-sized indicator for active work. It rotates while
|
|
4
|
+
the duration is unknown. Pass `value` when the current proportion is known.
|
|
5
|
+
|
|
6
|
+
## Indeterminate and determinate work
|
|
7
|
+
|
|
8
|
+
```tsx
|
|
9
|
+
// No value: the gradient communicates active work.
|
|
10
|
+
<Loader label="Loading results" />
|
|
11
|
+
|
|
12
|
+
// A value fills the circle through the current proportion.
|
|
13
|
+
<Loader value={42} label="Uploading files" />
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Without `value`, the faded gradient rotates. With `value`, the filled section
|
|
17
|
+
starts at 12 o’clock, has a sharp edge at the current proportion, and stays
|
|
18
|
+
still. The final preview is a slow, docs-only 0–100% cycle. `max` defaults to
|
|
19
|
+
`100`.
|
|
20
|
+
|
|
21
|
+
## Size and speed
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
<Loader size={14} speed={1} label="Slow loading" />
|
|
25
|
+
<Loader label="Loading" /> // 16px, 0.75 seconds
|
|
26
|
+
<Loader size={32} label="Loading" />
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`size` is a pixel value, matching `Icon`. `speed` is seconds per rotation. All
|
|
30
|
+
loaders with the same speed share a rotation phase.
|
|
31
|
+
|
|
32
|
+
## Icon-shape alias
|
|
33
|
+
|
|
34
|
+
Use `loader` where an Anta component accepts an `IconShape` string. The alias
|
|
35
|
+
is indeterminate and follows the surrounding icon size and color.
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
<Button tone="brand" icon="loader" label="Saving" />
|
|
39
|
+
<Tag tone="info" icon="loader" label="Syncing" />
|
|
40
|
+
<Input
|
|
41
|
+
label="Workspace"
|
|
42
|
+
defaultValue="Anta"
|
|
43
|
+
hint="Checking availability"
|
|
44
|
+
dimActions
|
|
45
|
+
trailing={<Icon shape="loader" aria-hidden="true" />}
|
|
46
|
+
/>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The alias uses the same conic field as an alpha mask, keeping the icon slot's
|
|
50
|
+
own color. It is decorative; the surrounding control or field provides its
|
|
51
|
+
label, state, and semantics.
|
|
52
|
+
|
|
53
|
+
Use `<Loader>` when you need a value, speed, or accessible loading status.
|
|
54
|
+
|
|
55
|
+
### Props
|
|
56
|
+
|
|
57
|
+
| Prop | Type | Default | Description |
|
|
58
|
+
|------|------|---------|-------------|
|
|
59
|
+
| `label?` | string | — | Accessible name for a standalone loader. Without a label, the loader is
|
|
60
|
+
decorative so nearby status text remains the single announcement. |
|
|
61
|
+
| `max?` | number | 100 | Upper bound of the range. |
|
|
62
|
+
| `size?` | number | 16 | Width and height in pixels. |
|
|
63
|
+
| `speed?` | number | 0.75 | Seconds per rotation. Pass a positive number. |
|
|
64
|
+
| `value?` | number \| false | — | Current progress value. Omit this prop, or pass `false`, for an
|
|
65
|
+
indeterminate rotating gradient. |
|
|
66
|
+
|
|
67
|
+
Use a raw `<a-loader>` when you are not using the JSX wrapper. Set its size,
|
|
68
|
+
speed, and determinate percentage with CSS custom properties. `value` selects
|
|
69
|
+
the static ring; `--loader-value` is the percentage it shows.
|
|
70
|
+
|
|
71
|
+
```html
|
|
72
|
+
<!-- Indeterminate -->
|
|
73
|
+
<a-loader
|
|
74
|
+
role="progressbar"
|
|
75
|
+
aria-label="Loading files"
|
|
76
|
+
style="--loader-size: 24px; --loader-speed: 1s"
|
|
77
|
+
></a-loader>
|
|
78
|
+
|
|
79
|
+
<!-- Determinate -->
|
|
80
|
+
<a-loader
|
|
81
|
+
value="42"
|
|
82
|
+
role="progressbar"
|
|
83
|
+
aria-label="Uploading files"
|
|
84
|
+
aria-valuenow="42"
|
|
85
|
+
aria-valuemin="0"
|
|
86
|
+
aria-valuemax="100"
|
|
87
|
+
style="--loader-size: 24px; --loader-value: 42%"
|
|
88
|
+
></a-loader>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
When the value is unknown, omit `value` and give a standalone loader an
|
|
92
|
+
accessible name. Mark a loader beside visible status text with `aria-hidden`.
|
|
93
|
+
|
|
94
|
+
The standalone Loader uses the quiet `--text-3` color by default. Set `color`
|
|
95
|
+
directly when it needs a deliberate semantic or one-off color; the `loader`
|
|
96
|
+
icon alias inherits its parent control’s color.
|
|
97
|
+
|
|
98
|
+
```css
|
|
99
|
+
.loader-demo { color: mediumaquamarine; }
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The `.loader-demo` class is only for this preview. Use a color on a parent or
|
|
103
|
+
your own selector in an application stylesheet.
|
|
104
|
+
|
|
105
|
+
```css
|
|
106
|
+
a-loader.rainbow-loader:not([value]) {
|
|
107
|
+
background: conic-gradient(
|
|
108
|
+
from 0deg,
|
|
109
|
+
color-mix(in oklch, #ff5d8f 8%, transparent),
|
|
110
|
+
#ff5d8f 25%,
|
|
111
|
+
#9b6cf5 50%,
|
|
112
|
+
#55b8e8 75%,
|
|
113
|
+
#45ca9f
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Override `background` to tune an indeterminate gradient field. Keep the
|
|
119
|
+
opening stop faint so the rotating hand still has a readable leading edge.
|
|
120
|
+
|
|
121
|
+
```css
|
|
122
|
+
a-loader.ring-loader {
|
|
123
|
+
position: relative;
|
|
124
|
+
color: var(--anta-seed-brand);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
a-loader.ring-loader::after {
|
|
128
|
+
content: "";
|
|
129
|
+
position: absolute;
|
|
130
|
+
inset: 5px;
|
|
131
|
+
border-radius: 100%;
|
|
132
|
+
background: var(--bg-2); /* the known surface behind the Loader */
|
|
133
|
+
pointer-events: none;
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**Known surface.** This paints a `--bg-2` circle over the Loader, matching
|
|
138
|
+
this preview’s explicit background and making a visual hole. Use the same
|
|
139
|
+
approach only when your Loader sits on a known opaque surface.
|
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
# Menu
|
|
2
|
+
|
|
3
|
+
A dropdown or context menu that anchors to any target and just works. Place
|
|
4
|
+
`<Menu>` immediately after the trigger — a button, an icon, a whole region —
|
|
5
|
+
and it opens on click, positions itself with room-aware flipping, and dismisses
|
|
6
|
+
on outside-click, `Escape`, or scroll. Rows are dedicated **`MenuItem`s** (not
|
|
7
|
+
buttons), with optional leading icons, trailing keyboard hints, separators,
|
|
8
|
+
grouped sections, and nested submenus.
|
|
9
|
+
|
|
10
|
+
Open state is uncontrolled by default — pass **`onStateChange`** (event-first:
|
|
11
|
+
`(event, { next, prev })`, or listen for the raw **`statechange`** event) to
|
|
12
|
+
observe it, or pass **`open`** to control it, applying `detail.next` to `open`
|
|
13
|
+
from your `onStateChange` handler (submenus stay uncontrolled). `statechange` is
|
|
14
|
+
**cancelable** and fires before the menu moves, so uncontrolled you can keep it
|
|
15
|
+
open with `event.preventDefault()`. You can also grab a `ref` and call
|
|
16
|
+
`.open()` / `.close()` / `.toggle()`.
|
|
17
|
+
|
|
18
|
+
## Dropdown from a button
|
|
19
|
+
|
|
20
|
+
Render `<Menu>` right after the trigger element. A `MenuItem` takes a leading
|
|
21
|
+
`icon`, a `label` (or `children`), and a trailing `kbd` hint. Selecting an item
|
|
22
|
+
closes the menu and fires its `onSelect(event, { value, label })`; `Escape` or an
|
|
23
|
+
outside click dismisses without selecting. Give items a `value` to route a single
|
|
24
|
+
handler — `onSelect` reports the chosen item's `value` and `label`, and never
|
|
25
|
+
fires for a submenu parent (that opens the flyout) or a selection bubbling up
|
|
26
|
+
from a nested submenu.
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
// One handler, keyed by each item's `value`.
|
|
30
|
+
const onSelect = (e, { value }) => act(value)
|
|
31
|
+
|
|
32
|
+
<Button>Actions</Button>
|
|
33
|
+
<Menu>
|
|
34
|
+
<MenuItem icon="edit" label="Edit" kbd="⌘E" value="edit" onSelect={onSelect} />
|
|
35
|
+
<MenuItem icon="copy" label="Duplicate" kbd="⌘D" value="duplicate" onSelect={onSelect} />
|
|
36
|
+
<MenuItem icon="download" label="Export" value="export" onSelect={onSelect} />
|
|
37
|
+
<MenuSeparator />
|
|
38
|
+
<MenuItem tone="critical" icon="trash" label="Delete" kbd="⌘⌫" value="delete" onSelect={onSelect} />
|
|
39
|
+
</Menu>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Tones
|
|
43
|
+
|
|
44
|
+
`tone` colors an item's label, icon, and hover/selected tint (and its
|
|
45
|
+
`selectionIndicator`) with a semantic palette — `brand`, `info`, `success`,
|
|
46
|
+
`warning`, or `critical` — tracking light and dark mode automatically. Omit it (or
|
|
47
|
+
pass `neutral`) for the standard gray. `critical` is the conventional destructive
|
|
48
|
+
action. Pass **any CSS color** for a one-off custom tone — its hue and chroma are
|
|
49
|
+
kept while the lightness is pinned to match the brand text (resolved through
|
|
50
|
+
`--menu-item-tone-source`).
|
|
51
|
+
|
|
52
|
+
`toneSelected` applies the tone the same way, but **only while the row is `selected`**
|
|
53
|
+
— an unselected row stays neutral. It's the checkable-menu counterpart to `tone`
|
|
54
|
+
(`Select` uses it to tone the chosen row).
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
<Menu>
|
|
58
|
+
<MenuItem label="Neutral" />
|
|
59
|
+
<MenuItem tone="brand" icon="star" label="Brand" />
|
|
60
|
+
<MenuItem tone="info" icon="info" label="Info" />
|
|
61
|
+
<MenuItem tone="success" icon="circle-check" label="Success" />
|
|
62
|
+
<MenuItem tone="warning" icon="warning-triangle" label="Warning" />
|
|
63
|
+
<MenuItem tone="critical" icon="trash" label="Critical" />
|
|
64
|
+
</Menu>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Placement
|
|
68
|
+
|
|
69
|
+
`placement` sets the preferred side and edge alignment: `bottom-start` (the
|
|
70
|
+
default), `bottom-end`, `top-start`, or `top-end`. The `-start` / `-end` suffix
|
|
71
|
+
aligns the menu's left or right edge to the trigger. The menu auto-flips
|
|
72
|
+
vertically and clamps horizontally when there isn't room, so the placement is a
|
|
73
|
+
preference, not a constraint.
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
<Button>bottom-start</Button>
|
|
77
|
+
<Menu placement="bottom-start">…</Menu>
|
|
78
|
+
|
|
79
|
+
<Button>bottom-end</Button>
|
|
80
|
+
<Menu placement="bottom-end">…</Menu>
|
|
81
|
+
|
|
82
|
+
<Button>top-start</Button>
|
|
83
|
+
<Menu placement="top-start">…</Menu>
|
|
84
|
+
|
|
85
|
+
<Button>top-end</Button>
|
|
86
|
+
<Menu placement="top-end">…</Menu>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The menu positions against the trigger's box. A trigger can narrow that: if it
|
|
90
|
+
implements **`getAnchorRect(): DOMRect`**, the menu positions against *that* rect
|
|
91
|
+
instead of its full border box. Anta's `Input` does this — it reports its field
|
|
92
|
+
box — so a `Menu` placed after an `<Input>` (as in `Select`) lines up with the
|
|
93
|
+
field, not the whole component (whose box also spans the label and hint).
|
|
94
|
+
|
|
95
|
+
## Right-click & at-cursor
|
|
96
|
+
|
|
97
|
+
Pass `context` to open on right-click (the `contextmenu` event) of the region
|
|
98
|
+
the menu follows, positioned at the pointer — ideal for content surfaces like a
|
|
99
|
+
log viewer or canvas where secondary actions shouldn't clutter the UI. `coord`
|
|
100
|
+
on its own opens a normal left-click menu at the cursor instead of aligned to
|
|
101
|
+
the trigger box.
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
<div class="canvas">Right-click anywhere here</div>
|
|
105
|
+
<Menu context>
|
|
106
|
+
<MenuItem icon="link" label="Share log" onSelect={share} />
|
|
107
|
+
<MenuItem icon="copy" label="Copy moment" onSelect={copy} />
|
|
108
|
+
</Menu>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Submenus
|
|
112
|
+
|
|
113
|
+
Mark a `MenuItem` with `submenu` and nest a `<Menu>` inside it to create a
|
|
114
|
+
flyout (the nested menu detects it's a submenu from being inside the item). The
|
|
115
|
+
parent item gets a chevron (override it per item with `iconTrailing`),
|
|
116
|
+
`aria-haspopup="menu"`, and an `aria-expanded` that
|
|
117
|
+
tracks the flyout's open state; the submenu opens
|
|
118
|
+
to the side and flips when it nears the edge. Submenus open on **hover**
|
|
119
|
+
(with intent timing, so a quick pass-through doesn't trigger them) as well as on
|
|
120
|
+
click; add `nohover` to a submenu to make it click-only. Hover-intent is
|
|
121
|
+
mouse-only regardless: on touch a submenu opens on tap and stays open until you
|
|
122
|
+
dismiss it or open a sibling.
|
|
123
|
+
|
|
124
|
+
For a top-level dropdown, add `aria-haspopup="menu"` to your own trigger element
|
|
125
|
+
— it's a control you render, so the accessible name and popup hint live there.
|
|
126
|
+
|
|
127
|
+
Submenus nest arbitrarily deep — here `View → Columns → Visible columns` is three
|
|
128
|
+
flyouts in. The deepest one isn't `MenuItem`s but **custom content**: a checkbox
|
|
129
|
+
list whose region carries `data-menu-open`, so ticking a box (or the **Select all**
|
|
130
|
+
toggle, or a row's **Only** button — which leaves just that column on) updates state
|
|
131
|
+
**without closing the menu**.
|
|
132
|
+
|
|
133
|
+
```tsx
|
|
134
|
+
const COLUMNS = ['Name', 'Status', 'Owner', 'Created', 'Modified']
|
|
135
|
+
const [on, setOn] = useState(() => Object.fromEntries(COLUMNS.map((c) => [c, true])))
|
|
136
|
+
const allOn = COLUMNS.every((c) => on[c])
|
|
137
|
+
const someOn = COLUMNS.some((c) => on[c])
|
|
138
|
+
const only = (c) => setOn(Object.fromEntries(COLUMNS.map((k) => [k, k === c])))
|
|
139
|
+
// …toggle / toggleAll omitted
|
|
140
|
+
|
|
141
|
+
<Button>More</Button>
|
|
142
|
+
<Menu>
|
|
143
|
+
<MenuItem icon="edit" label="Rename" />
|
|
144
|
+
<MenuItem label="Move to" submenu>
|
|
145
|
+
<Menu>
|
|
146
|
+
<MenuItem icon="folder-open" label="Projects" />
|
|
147
|
+
<MenuItem icon="folder-open" label="Archive" />
|
|
148
|
+
</Menu>
|
|
149
|
+
</MenuItem>
|
|
150
|
+
<MenuItem label="View" submenu>
|
|
151
|
+
<Menu>
|
|
152
|
+
<MenuItem label="Columns" submenu>
|
|
153
|
+
<Menu>
|
|
154
|
+
<MenuItem label="Visible columns" submenu>
|
|
155
|
+
<Menu>
|
|
156
|
+
{/* custom content — never auto-closes thanks to data-menu-open */}
|
|
157
|
+
<div class="menu-check-row" data-menu-open>
|
|
158
|
+
{/* Checkbox takes 'indeterminate' directly — no ref poking */}
|
|
159
|
+
<Checkbox
|
|
160
|
+
className="menu-check"
|
|
161
|
+
label="Select all"
|
|
162
|
+
checked={allOn ? true : someOn ? 'indeterminate' : false}
|
|
163
|
+
onStateChange={toggleAll}
|
|
164
|
+
/>
|
|
165
|
+
</div>
|
|
166
|
+
<MenuSeparator />
|
|
167
|
+
{COLUMNS.map((c) => (
|
|
168
|
+
<div class="menu-check-row" data-menu-open key={c}>
|
|
169
|
+
<Checkbox className="menu-check" label={c} checked={on[c]} onStateChange={() => toggle(c)} />
|
|
170
|
+
<Button priority="tertiary" size="small" onClick={() => only(c)}>Only</Button>
|
|
171
|
+
</div>
|
|
172
|
+
))}
|
|
173
|
+
</Menu>
|
|
174
|
+
</MenuItem>
|
|
175
|
+
</Menu>
|
|
176
|
+
</MenuItem>
|
|
177
|
+
</Menu>
|
|
178
|
+
</MenuItem>
|
|
179
|
+
</Menu>
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Groups
|
|
183
|
+
|
|
184
|
+
Wrap related items in a `MenuGroup` with a `label` to add a titled section.
|
|
185
|
+
Headings are skipped during keyboard navigation, which flattens all items into a
|
|
186
|
+
single up/down sequence across group boundaries.
|
|
187
|
+
|
|
188
|
+
A `MenuSeparator` is a plain hairline when empty, but given text
|
|
189
|
+
(`<MenuSeparator>No results</MenuSeparator>`) it renders a small muted caption and
|
|
190
|
+
becomes an `aria-live="polite"` status region — the styled home for a status or
|
|
191
|
+
empty-state message (what `Select`'s `renderEmpty` uses).
|
|
192
|
+
|
|
193
|
+
```tsx
|
|
194
|
+
<Menu>
|
|
195
|
+
<MenuGroup label="Sort by">
|
|
196
|
+
<MenuItem icon="check" label="Name" />
|
|
197
|
+
<MenuItem label="Date modified" />
|
|
198
|
+
</MenuGroup>
|
|
199
|
+
<MenuSeparator />
|
|
200
|
+
<MenuGroup label="Show">
|
|
201
|
+
<MenuItem label="Hidden files" data-menu-open />
|
|
202
|
+
</MenuGroup>
|
|
203
|
+
</Menu>
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Pinned header & footer
|
|
207
|
+
|
|
208
|
+
`a-menu-header` and `a-menu-footer` name fixed regions around the scrolling
|
|
209
|
+
items. Put `a-select-header` or `a-select-footer` in the same slots when a
|
|
210
|
+
composed Select needs its own DOM anatomy. A `MenuSeparator` slotted into the
|
|
211
|
+
footer stays the menu's divider.
|
|
212
|
+
|
|
213
|
+
```tsx
|
|
214
|
+
<Button>Filter</Button>
|
|
215
|
+
<Menu inset={8}>
|
|
216
|
+
<a-menu-header slot="header" data-menu-open>
|
|
217
|
+
<Input size="small" placeholder="Filter…" />
|
|
218
|
+
</a-menu-header>
|
|
219
|
+
<MenuItem label="Design" />
|
|
220
|
+
<MenuItem label="Engineering" />
|
|
221
|
+
<MenuSeparator slot="footer" />
|
|
222
|
+
<a-menu-footer slot="footer">
|
|
223
|
+
<MenuItem icon="x" label="Clear filters" data-menu-open />
|
|
224
|
+
</a-menu-footer>
|
|
225
|
+
</Menu>
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Custom content & the close contract
|
|
229
|
+
|
|
230
|
+
The close decision is read straight from the DOM on click — never from whether a
|
|
231
|
+
handler called `preventDefault` — so it's safe in runtimes where event handlers
|
|
232
|
+
run off the UI thread. Walking out from the click, the nearest marker wins:
|
|
233
|
+
|
|
234
|
+
- A **`MenuItem`** is a choice → selecting it **closes** the menu.
|
|
235
|
+
- **Arbitrary content you inject never closes it** — drop a slider, an input, or
|
|
236
|
+
any control straight into a `<Menu>` and interacting leaves the menu open.
|
|
237
|
+
- **`data-menu-open`** keeps the menu open past a click —
|
|
238
|
+
put it on an item, a `MenuGroup`, or any wrapping element (toggles, multi-select).
|
|
239
|
+
- **`data-menu-close`** opts arbitrary content *into* closing — so a custom
|
|
240
|
+
element (or a "Done" button inside a `data-menu-open` region) can dismiss the
|
|
241
|
+
menu without being a `MenuItem`.
|
|
242
|
+
|
|
243
|
+
For programmatic open/close from app state, drive the `open` prop (the `state`
|
|
244
|
+
attribute, `"open"` / `"closed"`) and react via `onStateChange`, or grab a `ref`
|
|
245
|
+
and call `.open()` / `.close()` / `.toggle()`.
|
|
246
|
+
|
|
247
|
+
```tsx
|
|
248
|
+
<Menu>
|
|
249
|
+
<MenuItem label="Profile" onSelect={openProfile} />
|
|
250
|
+
{/* data-menu-open: a toggle that doesn't dismiss */}
|
|
251
|
+
<MenuItem label="Notifications" data-menu-open onSelect={toggleNotifs} />
|
|
252
|
+
<MenuSeparator />
|
|
253
|
+
{/* data-menu-open region: interacting with the slider keeps the menu open;
|
|
254
|
+
the Done button opts back into closing with data-menu-close */}
|
|
255
|
+
<div data-menu-open class="zoom">
|
|
256
|
+
<label>Zoom</label>
|
|
257
|
+
<input type="range" />
|
|
258
|
+
{/* a plain button, not a MenuItem — data-menu-close lets it dismiss */}
|
|
259
|
+
<button data-menu-close>Done</button>
|
|
260
|
+
</div>
|
|
261
|
+
</Menu>
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## Links
|
|
265
|
+
|
|
266
|
+
Give a `MenuItem` an **`href`** and it renders a native `<a>` instead of the
|
|
267
|
+
custom element — a real link. It navigates on click, opens in a new tab on
|
|
268
|
+
⌘ / middle-click, offers "copy link address", and takes the anchor attributes:
|
|
269
|
+
`target`, `rel`, `ping`, and **`download`** (`true` for the resource's default
|
|
270
|
+
filename, a string to rename it). The parent `Menu` treats it as a first-class
|
|
271
|
+
row — arrow-key navigation, Enter / Space activation, and close-on-select — mixed
|
|
272
|
+
freely with regular items. `onSelect` fires alongside the navigation; `disabled`
|
|
273
|
+
drops the `href` so it can't navigate; `selected` marks a current link
|
|
274
|
+
(`aria-current`).
|
|
275
|
+
|
|
276
|
+
Under the hood it's `<a role="menuitem" data-anta-menu-item href>`, the same
|
|
277
|
+
opt-in-marker pattern as [`Button`'s link mode](./button.md#link-mode).
|
|
278
|
+
|
|
279
|
+
```tsx
|
|
280
|
+
<Menu>
|
|
281
|
+
{/* Real links — open in a new tab, or download */}
|
|
282
|
+
<MenuItem icon="file" label="Documentation" href="/docs" target="_blank" iconTrailing="external-link" />
|
|
283
|
+
<MenuItem icon="download" label="Download report" href="/report.pdf" download="report.pdf" />
|
|
284
|
+
<MenuSeparator />
|
|
285
|
+
{/* A regular action row, mixed in */}
|
|
286
|
+
<MenuItem icon="settings" label="Preferences" onSelect={openPrefs} />
|
|
287
|
+
</Menu>
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## Selected
|
|
291
|
+
|
|
292
|
+
`selected` gives a row a persistent background tint — the resting fill a pressed
|
|
293
|
+
row shows — marking it as chosen. It touches only the background, so nothing
|
|
294
|
+
shifts and no gutter is reserved. It's the building block for select-style
|
|
295
|
+
menus: mark one row for single-select, several for multi-select (pair with
|
|
296
|
+
`data-menu-open` to keep the menu open as choices toggle). The tint tracks
|
|
297
|
+
`tone`, so a toned row stays in its own color.
|
|
298
|
+
|
|
299
|
+
For a real checkable row, add **`selectionIndicator`** (`'checkbox'` or `'radio'`).
|
|
300
|
+
It renders a passive checkbox/radio at the leading edge (reusing the
|
|
301
|
+
[Checkbox](./checkbox.md) / [Radio](./radio.md) element visuals),
|
|
302
|
+
flips the row to `role="menuitemcheckbox"` / `"menuitemradio"`, and pairs
|
|
303
|
+
`aria-checked` — the row stays the control, the indicator is decorative. This is
|
|
304
|
+
what [Select](./select.md)'s `selection` modes render; use it directly for
|
|
305
|
+
a checkable menu.
|
|
306
|
+
|
|
307
|
+
```tsx
|
|
308
|
+
{/* Plain tint (no indicator) */}
|
|
309
|
+
<MenuItem label="Relevance" selected />
|
|
310
|
+
|
|
311
|
+
{/* Checkable rows — the row is the control; the mark is decorative */}
|
|
312
|
+
<MenuItem selectionIndicator="radio" label="Relevance" selected />
|
|
313
|
+
<MenuItem selectionIndicator="checkbox" label="Show archived" selected data-menu-open />
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
## Badges, counters & hints
|
|
317
|
+
|
|
318
|
+
Drop a `<Tag>` (or any element) as a **child** of a `MenuItem` for a trailing
|
|
319
|
+
counter or badge. Children render after the label but before the `kbd` hint and
|
|
320
|
+
trailing icon, and the label's `flex: 1` right-aligns them — so a small tag sits
|
|
321
|
+
at the row's edge, just left of a shortcut or submenu chevron. Use `size="small"`
|
|
322
|
+
so it sits comfortably in the row.
|
|
323
|
+
|
|
324
|
+
Add `hint` for secondary text under the label — muted, and it tracks the row's
|
|
325
|
+
`tone`. It stacks in a column beneath the label while the icon, badge, and
|
|
326
|
+
chevron stay centered on the row, so it composes with a trailing counter.
|
|
327
|
+
|
|
328
|
+
```tsx
|
|
329
|
+
<Menu>
|
|
330
|
+
<MenuItem icon="folder-open" label="Active" hint="12 open items"><Tag size="small" tone="info">12</Tag></MenuItem>
|
|
331
|
+
<MenuItem icon="folder-close" label="Archived" hint="Read-only" />
|
|
332
|
+
<MenuItem icon="trash" label="Trash"><Tag size="small">3</Tag></MenuItem>
|
|
333
|
+
</Menu>
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
`MenuItemCopy` is a row that copies to the clipboard when chosen — `copy` for a
|
|
337
|
+
literal string, `copyNode` for a DOM region. It composes a `<MenuItem>` with a
|
|
338
|
+
slotted `<a-copy>` element that performs the write, and keeps the menu open on
|
|
339
|
+
select so the check / retone feedback shows. `onCopied(ok)` fires after each
|
|
340
|
+
attempt.
|
|
341
|
+
|
|
342
|
+
Copy sits alongside normal items, so a "Share" menu can mix a copy-link row with
|
|
343
|
+
navigating links.
|
|
344
|
+
|
|
345
|
+
```tsx
|
|
346
|
+
<Button icon="share" iconTrailing="chevron-down" label="Share" />
|
|
347
|
+
<Menu>
|
|
348
|
+
<MenuItemCopy copy={pageUrl} label="Copy link" kbd="⌘C" />
|
|
349
|
+
<MenuItemCopy copy={embedHtml} icon="braces" label="Copy embed" />
|
|
350
|
+
<MenuSeparator />
|
|
351
|
+
<MenuItem icon="external-link" label="Open in new tab" href="/menu" target="_blank" />
|
|
352
|
+
</Menu>
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### Props
|
|
356
|
+
|
|
357
|
+
| Prop | Type | Default | Description |
|
|
358
|
+
|------|------|---------|-------------|
|
|
359
|
+
| `copy?` | string | — | Text copied to the clipboard on activation. |
|
|
360
|
+
| `copyNode?` | boolean \| string | — | Copy a DOM node as rich text (`text/html`) + plain text. `true` copies
|
|
361
|
+
the nearest ancestor marked `data-copy-source`; a string is a CSS
|
|
362
|
+
selector for an ancestor region (`closest`). The copy control is stripped
|
|
363
|
+
from the copied output. |
|
|
364
|
+
| `copyUrl?` | true | — | Copy the current page URL (`location.href`). |
|
|
365
|
+
| `copyWithUrl?` | boolean | — | Prefix the copied text with `// URL: <current page URL>`. |
|
|
366
|
+
| `onCopied?` | (ok) => void | — | Fires after the copy attempt with whether it succeeded. |
|
|
367
|
+
| `onCopyRequest?` | () => void | — | Compute the copy content lazily. Fires on pointerdown / keydown; update
|
|
368
|
+
`copy` (a state change) here and the activation copies the latest value.
|
|
369
|
+
The gap lets the update land even off the UI thread — only the
|
|
370
|
+
serializable `copy` string crosses. |
|
|
371
|
+
|
|
372
|
+
## Keyboard
|
|
373
|
+
|
|
374
|
+
While open, the menu keeps Tab focus contained. Open the trigger with `Enter` /
|
|
375
|
+
`Space` to focus the first item.
|
|
376
|
+
|
|
377
|
+
- **↑ / ↓** — move between items (wraps; skips separators, headings, and disabled items)
|
|
378
|
+
- **Home / End** — first / last item
|
|
379
|
+
- **Type-ahead** — type to jump to the next item whose label matches
|
|
380
|
+
- **→ / Enter** — open a submenu (and focus its first item); **←** — close it, back to the parent
|
|
381
|
+
- **Enter / Space** — activate the focused item
|
|
382
|
+
- **Tab / Shift+Tab** — cycle through the focusables *inside* the menu — items **and** any nested controls (a slider, an input, a button) — wrapping at the ends so focus stays in the open menu. Arrow keys hand off to a nested control once it's focused
|
|
383
|
+
- **Esc** — close the topmost menu and return focus to its parent item or trigger
|
|
384
|
+
|
|
385
|
+
## Controlled vs Uncontrolled
|
|
386
|
+
|
|
387
|
+
Open state follows Anta's shared **state contract**. By default the menu is
|
|
388
|
+
*uncontrolled* — its triggers (click / right-click / `.open()`) own open/close,
|
|
389
|
+
so there's no prop to set. Pass `open` to *control* it: visibility follows the
|
|
390
|
+
prop, and a user dismiss only *requests* a change. The `<Menu>` wrapper takes a
|
|
391
|
+
boolean; the `<a-menu>` element takes a `state` string enum:
|
|
392
|
+
|
|
393
|
+
| | `<Menu>` (JSX) | `<a-menu>` (element) |
|
|
394
|
+
|---|---|---|
|
|
395
|
+
| **Uncontrolled** (default) — triggers own it | *(no prop)* | *(no `state` attribute)* |
|
|
396
|
+
| **Controlled** — you own it | `open` + `onStateChange` | `state="open" \| "closed"` + `statechange` |
|
|
397
|
+
| **Change event** | `onStateChange(event, { next, prev })` — `next` / `prev` are **booleans** | `statechange`, a `CustomEvent<{ next, prev }>` — `'open'` / `'closed'` (plus `coord` / `originEvent`) |
|
|
398
|
+
|
|
399
|
+
`statechange` is **cancelable** and fires *before* the menu moves. Uncontrolled,
|
|
400
|
+
call `event.preventDefault()` to keep the menu as-is (e.g. block a dismiss).
|
|
401
|
+
Controlled, the menu never self-moves — apply `detail.next` to `open` to accept,
|
|
402
|
+
or do nothing to reject. (Submenus are always uncontrolled, regardless of `open`.)
|
|
403
|
+
|
|
404
|
+
Use this when you are not using the React or Preact wrapper and a native HTML control does not fit: construct the equivalent Anta web component from the elements below.
|
|
405
|
+
|
|
406
|
+
Put a focusable trigger immediately before `<a-menu>` and omit `state` for an
|
|
407
|
+
uncontrolled menu. Its light-DOM `<a-menu-item>` rows need their own `tabindex`
|
|
408
|
+
values.
|
|
409
|
+
|
|
410
|
+
```html
|
|
411
|
+
<a-button role="button" tabindex="0" aria-haspopup="menu">
|
|
412
|
+
<a-button-label>Actions</a-button-label>
|
|
413
|
+
</a-button>
|
|
414
|
+
<a-menu role="menu">
|
|
415
|
+
<a-menu-item role="menuitem" tabindex="0" value="settings"><a-menu-item-label>Settings</a-menu-item-label></a-menu-item>
|
|
416
|
+
<a-menu-item role="menuitem" tabindex="0" value="members"><a-menu-item-label>Members</a-menu-item-label></a-menu-item>
|
|
417
|
+
</a-menu>
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
The popover surface lives in shadow DOM and is exposed as a **part** — style its
|
|
421
|
+
chrome (background, frost, border, radius, shadow, padding, min-width) with
|
|
422
|
+
`::part(menu)`. Menu items are light DOM, so style them (and their
|
|
423
|
+
`a-icon` / `kbd` / `a-menu-item-label` children) directly. Match **both** item
|
|
424
|
+
shapes — the `a-menu-item` custom element and the `a[data-anta-menu-item]` link —
|
|
425
|
+
with `:is(a-menu-item, a[data-anta-menu-item])`, or a link row skips your item
|
|
426
|
+
rule. **Click the trigger** to open the styled menu; the `.fancy` class is just
|
|
427
|
+
for the demo:
|
|
428
|
+
|
|
429
|
+
```css
|
|
430
|
+
/* a "liquid glass" menu — more transparent + a stronger blur, big radius, fully
|
|
431
|
+
rounded items with roomier padding and bigger icons / type. */
|
|
432
|
+
a-menu.fancy::part(menu) {
|
|
433
|
+
border-radius: 22px;
|
|
434
|
+
min-width: 240px;
|
|
435
|
+
padding: 8px;
|
|
436
|
+
background: light-dark(rgba(255,255,255,0.55), rgba(28,26,32,0.5));
|
|
437
|
+
backdrop-filter: blur(28px) saturate(180%);
|
|
438
|
+
}
|
|
439
|
+
/* Items are light DOM — style them directly. `:is()` catches both an
|
|
440
|
+
<a-menu-item> and a link row (<a data-anta-menu-item>). */
|
|
441
|
+
a-menu.fancy :is(a-menu-item, a[data-anta-menu-item]) {
|
|
442
|
+
border-radius: 999px;
|
|
443
|
+
padding: 10px 16px;
|
|
444
|
+
font-size: 15px;
|
|
445
|
+
--icon-size: 18px; /* inherited by the item's <a-icon> */
|
|
446
|
+
}
|
|
447
|
+
```
|