@antadesign/anta 0.3.18 → 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/Menu.d.ts +5 -1
- package/dist/components/Menu.js +3 -2
- 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-input.css +1 -1
- package/dist/elements/a-menu.js +12 -0
- 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/general_types.d.ts +2 -0
- package/dist/jsx-runtime.d.ts +9 -0
- 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,101 @@
|
|
|
1
|
+
# InputAutocomplete
|
|
2
|
+
|
|
3
|
+
`InputAutocomplete` — a text field whose value is a free string, with a suggestion list that assists
|
|
4
|
+
without constraining. Type anything: a value that isn't in the list stays as you
|
|
5
|
+
typed it, and picking a suggestion fills the field. For a value that must be one of
|
|
6
|
+
the options, use [Select](./select.md) with `filter`.
|
|
7
|
+
|
|
8
|
+
It's a **composed component**, an [Input](./input.md) stitched to a [Menu](./menu.md) of
|
|
9
|
+
suggestions. The field text arrives through **`onValueChange`**; `onSelect` fires
|
|
10
|
+
only when a suggestion is chosen. Controlled via `value`, uncontrolled via
|
|
11
|
+
`defaultValue`.
|
|
12
|
+
|
|
13
|
+
## Free text is the point
|
|
14
|
+
|
|
15
|
+
The value is always the text in the field. That's what separates this from a
|
|
16
|
+
[Select](./select.md): a Select constrains the value to one of its options, so a Select
|
|
17
|
+
with `filter` already covers "search a fixed list". `InputAutocomplete` is for when
|
|
18
|
+
the list only *suggests* and the user may commit something you didn't list.
|
|
19
|
+
|
|
20
|
+
The keyboard follows that intent. No suggestion is highlighted as you type, so
|
|
21
|
+
Enter commits the typed text and closes the list. ↓ highlights
|
|
22
|
+
the first suggestion (then ↑/↓ move through them), and
|
|
23
|
+
Enter on a highlighted row picks it. Esc or an outside click
|
|
24
|
+
closes the list; clicking the field to place the caret keeps it open.
|
|
25
|
+
|
|
26
|
+
## Filtering
|
|
27
|
+
|
|
28
|
+
`filter` decides how suggestions match the text:
|
|
29
|
+
|
|
30
|
+
- **`true`** (default): built-in case-insensitive substring match on each option's
|
|
31
|
+
`value` / `label` / `hint`.
|
|
32
|
+
- **a function** `(option, query) => boolean`: your own matcher, called per option.
|
|
33
|
+
- **`false`**: show `suggestions` as-is, doing no local filtering. Use this when you
|
|
34
|
+
fetch or filter results yourself (async / remote) and feed the current set through
|
|
35
|
+
`suggestions`.
|
|
36
|
+
|
|
37
|
+
Suggestions are bare strings or `SelectOption`s, the same option shape
|
|
38
|
+
[Select](./select.md) uses: `value`, `label`, `hint`, `icon`, `tone`, `disabled`.
|
|
39
|
+
|
|
40
|
+
### Props
|
|
41
|
+
|
|
42
|
+
| Prop | Type | Default | Description |
|
|
43
|
+
|------|------|---------|-------------|
|
|
44
|
+
| `suggestions` | string \| SelectOption[] | — | The suggestions: bare strings or `SelectOption`s (`value` / `label` / `hint` /
|
|
45
|
+
`icon` / `tone` / `disabled`). A flat list; picking one fills the field. |
|
|
46
|
+
| `clearable?` | boolean | — | Show a clear button while the field has text. |
|
|
47
|
+
| `defaultValue?` | string | — | Initial value for the uncontrolled case (the wrapper then owns it). |
|
|
48
|
+
| `disabled?` | boolean | — | Disable the field. |
|
|
49
|
+
| `filter?` | boolean \| (option, query) => boolean | true | How suggestions match the text. `true` uses the built-in case-insensitive
|
|
50
|
+
substring match on `value` / `label` / `hint`; a function `(option, query) =>
|
|
51
|
+
boolean` does custom matching; `false` shows `suggestions` verbatim so you
|
|
52
|
+
can filter them yourself (async / remote). |
|
|
53
|
+
| `hint?` | ReactNode | — | Hint under the field. |
|
|
54
|
+
| `label?` | ReactNode | — | Field label. |
|
|
55
|
+
| `leading?` | ReactNode | — | Leading adornment inside the field. |
|
|
56
|
+
| `name?` | string | — | Form field name. |
|
|
57
|
+
| `onSelect?` | (option) => void | — | Fires only when a suggestion is chosen, with that option. |
|
|
58
|
+
| `onValueChange?` | (value) => void | — | Fires as the text changes (typing, picking a suggestion, or clearing) with
|
|
59
|
+
the new field text. |
|
|
60
|
+
| `placeholder?` | string | — | Placeholder text. |
|
|
61
|
+
| `size?` | 'small' \| 'medium' \| 'large' | medium | Field size. |
|
|
62
|
+
| `status?` | 'neutral' \| 'brand' \| 'info' \| 'success' \| 'warning' \| 'critical' | — | Validation status. |
|
|
63
|
+
| `statusIcon?` | (string & {}) \| false \| IconShape | — | Status icon override, or `false` to drop it. |
|
|
64
|
+
| `tone?` | string | — | Custom accent colour — a named tone or any CSS colour. |
|
|
65
|
+
| `trailing?` | ReactNode | — | Trailing adornment inside the field. |
|
|
66
|
+
| `value?` | string | — | Controlled value: the field's text, a **free** string not constrained to a
|
|
67
|
+
suggestion. Leave undefined for uncontrolled. |
|
|
68
|
+
|
|
69
|
+
`InputAutocomplete` combines an input with a Menu. Reproducing its filtering,
|
|
70
|
+
selection, focus, and popup coordination without the React or Preact wrapper is
|
|
71
|
+
a substantial amount of code.
|
|
72
|
+
|
|
73
|
+
### Native HTML input with autocomplete
|
|
74
|
+
|
|
75
|
+
For browser-native suggestions, pair a native input with a `<datalist>`. It
|
|
76
|
+
keeps free-form entry and lets the browser filter and fill matching options,
|
|
77
|
+
while `data-anta` supplies the field appearance.
|
|
78
|
+
|
|
79
|
+
The suggestions popup is owned by the browser, so use `InputAutocomplete` when
|
|
80
|
+
you need custom option content, popup styling, or consistent interaction across
|
|
81
|
+
browsers.
|
|
82
|
+
|
|
83
|
+
```html
|
|
84
|
+
<input data-anta type="text" list="people" placeholder="Search people">
|
|
85
|
+
|
|
86
|
+
<datalist id="people">
|
|
87
|
+
<option value="Maya Chen">
|
|
88
|
+
<option value="Noah Williams">
|
|
89
|
+
<option value="Olivia Martinez">
|
|
90
|
+
<option value="Liam Johnson">
|
|
91
|
+
</datalist>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**The field** is styled through Input's props (`size`, `status`, `tone`, `round`,
|
|
95
|
+
`leading` / `trailing`) and its `::part`s; set its width with `style` / `className`
|
|
96
|
+
(forwarded to the field). **The suggestion popover** takes Menu's `::part(menu)`.
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
// Give the field a width; tint it with a custom tone.
|
|
100
|
+
<InputAutocomplete tone="info" style={{ width: '260px' }} suggestions={frameworks} />
|
|
101
|
+
```
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
# InputDate
|
|
2
|
+
|
|
3
|
+
**`InputDate`** is a date field that replaces the native `type="date"` (and, with
|
|
4
|
+
`time`, `type="datetime-local"`): a text field you can type into, backed by a
|
|
5
|
+
`Calendar` that opens when you click the field (or press ↓), marked by a
|
|
6
|
+
leading calendar icon. `Calendar`, the month grid it builds on, is documented lower
|
|
7
|
+
down.
|
|
8
|
+
|
|
9
|
+
The field accepts free text and resolves it on commit (blur / Enter): a lenient
|
|
10
|
+
parser reads the locale's order (`06/07/2026` is June 7 in en-US, July 6 in de-DE),
|
|
11
|
+
month names, and run-together digits, then rewrites the entry to the canonical
|
|
12
|
+
format. An unrecognized entry marks the field until you fix it. The value is an ISO
|
|
13
|
+
`YYYY-MM-DD` string, never a `Date`; it works controlled (`value` + `onValueChange`)
|
|
14
|
+
or uncontrolled (`defaultValue`), and submits under `name`.
|
|
15
|
+
|
|
16
|
+
## The date field
|
|
17
|
+
|
|
18
|
+
Type a date and it resolves on blur or Enter, or click the field to open
|
|
19
|
+
the calendar and pick a day. Either way the field settles on the locale's canonical
|
|
20
|
+
format and the value is ISO `YYYY-MM-DD`. A click keeps focus in the field, so you
|
|
21
|
+
can keep typing or click a day; ↓ opens the calendar and moves focus into
|
|
22
|
+
the grid, and Esc closes it back to the field. While you type, the calendar
|
|
23
|
+
previews the entry — it jumps to that month and highlights the day — and commits it on
|
|
24
|
+
Enter (which also closes the calendar); an unrecognized entry keeps the
|
|
25
|
+
calendar open and marks the field.
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
<InputDate label="Due date" defaultValue="2026-06-15" onValueChange={(v) => save(v)} />
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The placeholder shows the locale's format, and parsing follows the same order, so
|
|
32
|
+
`de-DE` reads and writes `TT.MM.JJJJ` while `en-US` uses `MM/DD/YYYY`. `min` / `max`
|
|
33
|
+
bound both the calendar and what a typed date will accept.
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
<InputDate label="US English" locale="en-US" defaultValue="2026-06-15" />
|
|
37
|
+
<InputDate label="German" locale="de-DE" defaultValue="2026-06-15" />
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`time` turns it into a date-time field (value `YYYY-MM-DDTHH:mm`). The menu grows a
|
|
41
|
+
time row under the calendar: an [`InputTime`](./input-time.md) (segmented hour / minute,
|
|
42
|
+
plus AM/PM in 12-hour locales) and a confirm (✓) button. Picking a day keeps the menu
|
|
43
|
+
open so you can set the time. The clock follows the locale (`en-US` is 12-hour, most
|
|
44
|
+
others 24), overridable with `hour12`. The field parses a trailing time after a space
|
|
45
|
+
(`06/15/2026 2:30pm` or `… 14:30`), and typing a 24-hour hour into the 12-hour time
|
|
46
|
+
field converts it (18 → 6 PM).
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
{/* `size` scales the calendar, the time inputs, the AM/PM toggle, and the buttons together. */}
|
|
50
|
+
<InputDate size="small" label="Small" time defaultValue="2026-06-15T09:00" />
|
|
51
|
+
<InputDate size="medium" label="Medium" time defaultValue="2026-06-15T09:00" />
|
|
52
|
+
<InputDate size="large" label="Large" time defaultValue="2026-06-15T09:00" />
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
| Prop | Type | Default | Description |
|
|
56
|
+
|------|------|---------|-------------|
|
|
57
|
+
| `clearable?` | boolean | — | Show a clear button once the field has a value. |
|
|
58
|
+
| `defaultValue?` | string | — | Initial value for the uncontrolled case. |
|
|
59
|
+
| `disabled?` | boolean | — | Disable the field and the calendar. |
|
|
60
|
+
| `hint?` | ReactNode | — | Helper text under the field. Replaced by a format hint while the entry is
|
|
61
|
+
unrecognized. |
|
|
62
|
+
| `hour12?` | boolean | — | Force the time cycle when `time` is on: `true` for 12-hour (AM/PM), `false`
|
|
63
|
+
for 24-hour. Omit to follow the locale (`en-US` → 12-hour, most others → 24). |
|
|
64
|
+
| `icon?` | false \| IconShape | calendar-days | Leading icon at the start of the field — the calendar affordance. Pass
|
|
65
|
+
another shape to change it, or `false` to drop it. |
|
|
66
|
+
| `label?` | ReactNode | — | Field label, above the control. |
|
|
67
|
+
| `locale?` | string | navigator.language | BCP-47 locale driving the display format, the placeholder mask, parsing order,
|
|
68
|
+
and the calendar. |
|
|
69
|
+
| `max?` | string | — | Latest selectable date (ISO). |
|
|
70
|
+
| `min?` | string | — | Earliest selectable date (ISO). Earlier days disable, and a typed date before
|
|
71
|
+
it stays uncommitted. |
|
|
72
|
+
| `name?` | string | — | Form field name — the ISO value submits under this key. |
|
|
73
|
+
| `offset?` | number | 4 | Gap in pixels between the field and the calendar menu. |
|
|
74
|
+
| `onValueChange?` | (value, attrs) => void | — | Fired after the value resolves (a recognized entry, a calendar pick, a time
|
|
75
|
+
change, or a clear), with the new ISO value (`''` when cleared) and a
|
|
76
|
+
`{ value, name }` snapshot. An unrecognized entry does not fire it. |
|
|
77
|
+
| `placeholder?` | string | the locale's format mask (e.g. `MM/DD/YYYY`, `MM/DD/YYYY HH:MM` with `time`) | Placeholder shown when empty. |
|
|
78
|
+
| `placement?` | 'left' \| 'right' \| 'bottom' \| 'top' \| 'bottom-start' \| 'bottom-end' \| 'top-start' \| 'top-end' \| 'right-start' \| 'right-end' \| 'left-start' \| 'left-end' | bottom-start | Preferred placement of the calendar menu relative to the field. Same values as
|
|
79
|
+
`Menu`; it auto-flips and clamps when space runs out. |
|
|
80
|
+
| `round?` | boolean \| number \| string | — | Round the field corners — `true` for fully round, or a number / CSS length. |
|
|
81
|
+
| `size?` | 'small' \| 'medium' \| 'large' | medium | Field size. Also sizes the calendar and the time row. |
|
|
82
|
+
| `status?` | 'neutral' \| 'brand' \| 'info' \| 'success' \| 'warning' \| 'critical' | neutral | Validation/feedback tone. An unrecognized entry forces `critical` until fixed. |
|
|
83
|
+
| `time?` | boolean | — | Include a time. The value becomes ISO `YYYY-MM-DDTHH:mm`, the field parses a
|
|
84
|
+
trailing time after a space (`06/15/2026 14:30`, `… 2:30pm`), and the menu
|
|
85
|
+
shows a time row (hours : minutes, an AM/PM toggle in 12-hour locales, then a
|
|
86
|
+
Done button) under the calendar. Picking a day keeps the menu open so you can
|
|
87
|
+
set the time. |
|
|
88
|
+
| `value?` | string | — | Controlled value — ISO `YYYY-MM-DD` (or `YYYY-MM-DDTHH:mm` with `time`), `''`
|
|
89
|
+
for empty. Pair with `onValueChange`; the field and calendar follow it and a
|
|
90
|
+
pick only *requests* a change. |
|
|
91
|
+
|
|
92
|
+
## Calendar
|
|
93
|
+
|
|
94
|
+
The sections below document `Calendar`, the month grid `InputDate` opens. Reach for
|
|
95
|
+
it directly when you want the grid inline, without a field.
|
|
96
|
+
|
|
97
|
+
`Calendar` is **composed from Anta components in light DOM**, with nothing hidden in
|
|
98
|
+
a shadow root. The days and prev/next chevrons are `<Button>`s (a selected day is a
|
|
99
|
+
`tertiary` Button in its selected state, toned `brand`), inside a form-associated `<a-calendar>`
|
|
100
|
+
grid. Date math runs on the [Temporal API](https://tc39.es/proposal-temporal/docs/),
|
|
101
|
+
bundled via `temporal-polyfill`. For a raw grid, import `buildMonth` from
|
|
102
|
+
`@antadesign/anta`: it returns localized weekday headers and every day record you
|
|
103
|
+
need to render — ISO value, accessible label, and selected, disabled, today, and
|
|
104
|
+
outside-month state. It does not coordinate navigation, roving focus, selection, or
|
|
105
|
+
re-renders for you. The value is an ISO `YYYY-MM-DD` string, never a `Date`. It works
|
|
106
|
+
controlled (`value` + `onStateChange`) or uncontrolled (`defaultValue`), and
|
|
107
|
+
participates in forms under `name`.
|
|
108
|
+
|
|
109
|
+
**Not SSR-safe.** `Calendar` (and `InputDate`, which embeds it) reads the current
|
|
110
|
+
date (`Temporal.Now`) and locale (`navigator.language`) at render, so a server
|
|
111
|
+
render disagrees with the client on the highlighted "today" and on localized
|
|
112
|
+
labels, and the page hydrates with a mismatch. Render it client-side only: Astro
|
|
113
|
+
`client:only`, or a dynamic import inside `useEffect`. Every demo on this page uses
|
|
114
|
+
`client:only` for that reason.
|
|
115
|
+
|
|
116
|
+
## Selecting a date
|
|
117
|
+
|
|
118
|
+
Click a day, or focus the grid and press Enter / Space. The
|
|
119
|
+
selected day takes the tertiary-button selected look toned brand — a brand-tinted
|
|
120
|
+
filled cell with a ring. Today shows as a secondary Button, a resting fill.
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
<Calendar defaultValue="2026-06-12" />
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Size
|
|
127
|
+
|
|
128
|
+
`size` scales the whole calendar: the day cells, the prev/next chevrons, and the
|
|
129
|
+
weekday and month-heading type. It follows Button's `small` / `medium` (default) /
|
|
130
|
+
`large` scale, with day-cell heights of 24 / 28 / 32px.
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
<Calendar size="small" defaultValue="2026-06-28" />
|
|
134
|
+
<Calendar defaultValue="2026-06-28" /> {/* medium (default) */}
|
|
135
|
+
<Calendar size="large" defaultValue="2026-06-28" />
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Range
|
|
139
|
+
|
|
140
|
+
`min` and `max` (ISO strings) bound the selectable range. Days outside it render
|
|
141
|
+
disabled and skip keyboard focus, and the prev/next arrows stop at the range edge.
|
|
142
|
+
The jump menu adapts to how far the range reaches.
|
|
143
|
+
|
|
144
|
+
No `min` / `max`: the jump menu lists today ±3 years, each a submenu of its months.
|
|
145
|
+
|
|
146
|
+
```tsx
|
|
147
|
+
<Calendar size="small" defaultValue="2026-06-15" />
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
A range across years lists each year (here 2026 and 2027) as a submenu of months.
|
|
151
|
+
June 2026 disables days 1–7, and the back arrow stops at the range's start.
|
|
152
|
+
|
|
153
|
+
```tsx
|
|
154
|
+
<Calendar size="small" defaultValue="2026-06-15" min="2026-06-08" max="2027-06-24" />
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
A range inside one year skips the year level and lists months directly, March
|
|
158
|
+
through November enabled and the rest disabled.
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
<Calendar size="small" defaultValue="2026-06-15" min="2026-03-01" max="2026-11-30" />
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
A range inside one month leaves nowhere to jump. Both arrows and the month/year
|
|
165
|
+
button are disabled, and only the in-range days stay selectable.
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
<Calendar size="small" defaultValue="2026-06-15" min="2026-06-08" max="2026-06-24" />
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Locale
|
|
172
|
+
|
|
173
|
+
`Calendar` respects the browser locale by default (`navigator.language`). Pass a
|
|
174
|
+
BCP-47 `locale` to override it: the first day of week comes from the locale's week
|
|
175
|
+
info (never hard-coded to Sunday), and the weekday and month names are localized.
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
<Calendar defaultValue="2026-06-28" locale="en-US" /> {/* week starts Sunday */}
|
|
179
|
+
<Calendar defaultValue="2026-06-28" locale="fr-FR" /> {/* week starts Monday */}
|
|
180
|
+
<Calendar defaultValue="2026-06-28" locale="ja-JP" />
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Controlled
|
|
184
|
+
|
|
185
|
+
Uncontrolled by default: pass `defaultValue` and read the date from `onValueChange`
|
|
186
|
+
or at form submit. Make it controlled with `value` + `onStateChange` to drive it
|
|
187
|
+
from your store. The grid then follows `value`, and a pick only *requests* a change:
|
|
188
|
+
answer by updating `value`, reject by doing nothing.
|
|
189
|
+
|
|
190
|
+
```tsx
|
|
191
|
+
const [date, setDate] = useState('2026-06-28')
|
|
192
|
+
|
|
193
|
+
<Calendar
|
|
194
|
+
value={date}
|
|
195
|
+
onStateChange={(_e, { next }) => setDate(next ?? '')}
|
|
196
|
+
/>
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
`onStateChange` fires before the element applies, with `(event, { next, prev, reason })`
|
|
200
|
+
where `next` / `prev` are ISO strings. For a `'user'` pick, `event.preventDefault()`
|
|
201
|
+
vetoes it (uncontrolled). A controlled calendar with no `onStateChange` is read-only.
|
|
202
|
+
|
|
203
|
+
## Disabled
|
|
204
|
+
|
|
205
|
+
`disabled` dims the whole calendar and removes it from interaction.
|
|
206
|
+
|
|
207
|
+
```tsx
|
|
208
|
+
<Calendar defaultValue="2026-06-28" disabled />
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Forms
|
|
212
|
+
|
|
213
|
+
`Calendar` is form-associated. Give it a `name` and it submits the selected ISO
|
|
214
|
+
date under that key, resets to `defaultValue` on form reset, and reports through
|
|
215
|
+
the standard constraint-validation pipeline.
|
|
216
|
+
|
|
217
|
+
```tsx
|
|
218
|
+
<form>
|
|
219
|
+
<Calendar name="due-date" defaultValue="2026-06-28" />
|
|
220
|
+
</form>
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Accessibility
|
|
224
|
+
|
|
225
|
+
The calendar is a labelled **group** of day buttons, a flat grid rather than a
|
|
226
|
+
nested `grid`/`row`/`gridcell` tree. The group is named by the visible month
|
|
227
|
+
(`aria-labelledby`). Each day is a `<button>` whose `aria-label` is its full date
|
|
228
|
+
including weekday ("Sunday, June 28, 2026"), with `aria-pressed` on the selected day
|
|
229
|
+
and `aria-current="date"` on today. The decorative weekday headers are `aria-hidden`,
|
|
230
|
+
since each day already names its weekday. A roving `tabindex` keeps a single tab
|
|
231
|
+
stop, and an `aria-live` region announces the month when it changes.
|
|
232
|
+
|
|
233
|
+
| Key | Moves focus |
|
|
234
|
+
|---|---|
|
|
235
|
+
| ← / → | by one day |
|
|
236
|
+
| ↑ / ↓ | by one week |
|
|
237
|
+
| Home / End | to the start / end of the week |
|
|
238
|
+
| PageUp / PageDown | by one month |
|
|
239
|
+
| Shift + PageUp / PageDown | by one year |
|
|
240
|
+
| Enter / Space | select the focused day |
|
|
241
|
+
|
|
242
|
+
## Customization
|
|
243
|
+
|
|
244
|
+
The family stacks in layers: `InputDate` composes `Input` + `Menu` + `Calendar`,
|
|
245
|
+
and `Calendar` itself splits into the `<a-calendar>` element (the day grid) and a
|
|
246
|
+
JSX wrapper (the navigation chrome). Knowing which layer owns what tells you where to
|
|
247
|
+
reach when you extend it, and lets a non-React consumer rebuild the chrome from stock
|
|
248
|
+
elements.
|
|
249
|
+
|
|
250
|
+
### Components composition
|
|
251
|
+
|
|
252
|
+
`InputDate` has no `a-inputdate` element; the wrapper is the coordinator, the role
|
|
253
|
+
`Select` plays over `Input` + `Menu`. It renders an editable `<Input>` trigger and a
|
|
254
|
+
`<Menu>` holding a `<Calendar>` (with `time`, the hour/minute row and AM/PM `Tabs`
|
|
255
|
+
sit under the grid), and owns the draft text, the lenient parse on commit, and the
|
|
256
|
+
ISO value. The menu anchors to the field and opens on click (via the `Menu`'s own
|
|
257
|
+
anchor-click trigger, like `Select`) — a mouse open leaves focus in the field so you
|
|
258
|
+
can keep typing, while ↓ opens it and moves focus into the grid. That
|
|
259
|
+
focus move is declarative: `InputDate` bumps `Calendar`'s `focusSignal`, which
|
|
260
|
+
renders a `data-focus` attribute the `<a-calendar>` element reads to focus the active
|
|
261
|
+
day — the wrapper never touches the DOM. Nesting a stateful component in a menu has
|
|
262
|
+
one wrinkle: the `Calendar` (and `Tabs`) dispatch their own
|
|
263
|
+
`statechange`, so the `Menu` acts only on its own `open` / `closed`, and a bubbled
|
|
264
|
+
pick isn't mistaken for a dismiss.
|
|
265
|
+
|
|
266
|
+
The `<a-calendar>` element owns the grid and nothing else. It wires the day
|
|
267
|
+
`<button>`s you render into it (click and keyboard: arrows, Home/End,
|
|
268
|
+
PageUp/PageDown), holds the submitted value via `ElementInternals`,
|
|
269
|
+
and dispatches the events: `statechange` / `change` on a pick, and `navigate` when a
|
|
270
|
+
key pages past the rendered month. It's framework-agnostic and works on its own, with
|
|
271
|
+
no notion of a "current month" and no sight of `min` / `max`.
|
|
272
|
+
|
|
273
|
+
Everything above the grid is the JSX wrapper, rendered as **siblings outside**
|
|
274
|
+
`<a-calendar>` from ordinary components: the prev/next arrows are `<Button>`s, and the
|
|
275
|
+
month/year picker is a `<Button>` opening a `<Menu>` of year `MenuItem`s, each a
|
|
276
|
+
submenu of months. The wrapper holds the displayed-month cursor in state, derives the
|
|
277
|
+
day cells and the arrows' `disabled` state from `min` / `max`, and turns a click on an
|
|
278
|
+
arrow, a pick in the menu, or a `navigate` event into a new cursor, then re-renders
|
|
279
|
+
the grid. So range enforcement (disabled days, capped arrows, a disabled picker for a
|
|
280
|
+
single-month range) lives entirely in the wrapper; the element only refuses to
|
|
281
|
+
activate a day you've rendered `disabled`.
|
|
282
|
+
|
|
283
|
+
That seam is why the element has no `min` / `max` and the switcher is a sibling: the
|
|
284
|
+
grid stays a small, portable authority, and the chrome (Anta `Button`s and a `Menu`)
|
|
285
|
+
is composition you can replace or rebuild. **Without React**, render the grid from the
|
|
286
|
+
exported engine (`buildMonth`) and drive it with the same glue the wrapper packages:
|
|
287
|
+
|
|
288
|
+
```html
|
|
289
|
+
<!-- `.calendar` is your own wrapper; the switcher is plain Buttons + a Menu. -->
|
|
290
|
+
<div class="calendar" style="display: inline-flex; flex-direction: column; gap: 6px">
|
|
291
|
+
<div style="display: flex; align-items: center">
|
|
292
|
+
<a-button priority="tertiary" data-prev aria-label="Previous month"><a-icon shape="chevron-left"></a-icon></a-button>
|
|
293
|
+
<a-button priority="tertiary" data-heading style="flex: 1; justify-content: center" aria-haspopup="menu"></a-button>
|
|
294
|
+
<a-button priority="tertiary" data-next aria-label="Next month"><a-icon shape="chevron-right"></a-icon></a-button>
|
|
295
|
+
</div>
|
|
296
|
+
<!-- The element wires whatever day cells you render; it owns selection,
|
|
297
|
+
keyboard, the form value, and the events. -->
|
|
298
|
+
<a-calendar role="group" name="date"></a-calendar>
|
|
299
|
+
</div>
|
|
300
|
+
<script type="module">
|
|
301
|
+
import '@antadesign/anta/elements'
|
|
302
|
+
import { buildMonth, parseISODate } from '@antadesign/anta'
|
|
303
|
+
import { Temporal } from 'temporal-polyfill'
|
|
304
|
+
|
|
305
|
+
const root = document.querySelector('.calendar')
|
|
306
|
+
const grid = root.querySelector('a-calendar')
|
|
307
|
+
const heading = root.querySelector('[data-heading]')
|
|
308
|
+
let cursor = Temporal.Now.plainDateISO()
|
|
309
|
+
|
|
310
|
+
function render() {
|
|
311
|
+
const m = buildMonth({ anchor: cursor, locale: navigator.language })
|
|
312
|
+
heading.textContent = m.heading
|
|
313
|
+
grid.replaceChildren()
|
|
314
|
+
for (const w of m.weekdays) {
|
|
315
|
+
const s = Object.assign(document.createElement('span'), { textContent: w.narrow })
|
|
316
|
+
s.setAttribute('data-part', 'weekday')
|
|
317
|
+
s.setAttribute('aria-hidden', 'true')
|
|
318
|
+
grid.append(s)
|
|
319
|
+
}
|
|
320
|
+
for (const d of m.weeks.flat()) {
|
|
321
|
+
const b = Object.assign(document.createElement('a-button'), { textContent: String(d.day) })
|
|
322
|
+
b.setAttribute('priority', 'tertiary')
|
|
323
|
+
b.setAttribute('data-part', 'day-cell')
|
|
324
|
+
b.setAttribute('data-date', d.iso) // the element keys off this
|
|
325
|
+
b.tabIndex = d.iso === cursor.toString() ? 0 : -1
|
|
326
|
+
if (d.selected) b.setAttribute('selected', '')
|
|
327
|
+
if (d.disabled) b.setAttribute('disabled', '')
|
|
328
|
+
grid.append(b)
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
root.querySelector('[data-prev]').addEventListener('click', () => { cursor = cursor.subtract({ months: 1 }); render() })
|
|
333
|
+
root.querySelector('[data-next]').addEventListener('click', () => { cursor = cursor.add({ months: 1 }); render() })
|
|
334
|
+
// Keyboard paging past the rendered grid: the element asks for a month flip.
|
|
335
|
+
grid.addEventListener('navigate', (e) => { cursor = parseISODate(e.detail.date) ?? cursor; render() })
|
|
336
|
+
render()
|
|
337
|
+
</script>
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Tracking the cursor, painting the grid from `buildMonth`, and re-rendering on
|
|
341
|
+
`navigate` is what the React `Calendar` packages, plus the year/month jump menu,
|
|
342
|
+
which uses the same `Menu` composition shown on the [Menu](./menu.md) page.
|
|
343
|
+
|
|
344
|
+
`InputDate` has no host element. Rebuilding its text parsing, dropdown calendar,
|
|
345
|
+
localized day grid, month navigation, keyboard navigation, and focus management
|
|
346
|
+
in vanilla JavaScript is significant work, even with Anta's `buildMonth` and
|
|
347
|
+
`parseISODate` helpers. Use the `InputDate` wrapper when React or Preact is an
|
|
348
|
+
option. Otherwise, a native HTML date input is the practical baseline.
|
|
349
|
+
|
|
350
|
+
### Native HTML date input
|
|
351
|
+
|
|
352
|
+
Add `data-anta` to a `date`, `datetime-local`, `month`, `time`, or `week` input.
|
|
353
|
+
Nothing else is required: Anta styles the field while the browser keeps its picker,
|
|
354
|
+
icon, validation, and keyboard model. The picker itself and its date/time
|
|
355
|
+
formatting remain browser- and OS-owned.
|
|
356
|
+
|
|
357
|
+
`data-anta-size`, `round`, and a custom-color `tone` use the matching Input
|
|
358
|
+
field treatments.
|
|
359
|
+
|
|
360
|
+
```html
|
|
361
|
+
<!-- `data-anta` styles the field, not the native picker. -->
|
|
362
|
+
<input data-anta type="date" name="due-date" value="2026-06-15">
|
|
363
|
+
<input data-anta type="datetime-local" name="starts-at" value="2026-06-15T09:00">
|
|
364
|
+
<input data-anta type="month" name="billing-month" value="2026-06">
|
|
365
|
+
<input data-anta type="time" name="reminder-at" value="09:00">
|
|
366
|
+
<input data-anta type="week" name="sprint-week" value="2026-W25">
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
The calendar is **light DOM**, so everything is plain CSS, with no shadow parts or
|
|
370
|
+
hidden internals. The days and chevrons are real `<a-button>`s, so their colours,
|
|
371
|
+
hover, focus ring, and selected look come from the Button styles and theme
|
|
372
|
+
themselves; they sit inside the `<a-calendar>` grid, and the switcher header sits in
|
|
373
|
+
the `.anta-calendar` shell. An un-layered consumer rule beats `@layer anta` without
|
|
374
|
+
`!important`. The class below is for the demo.
|
|
375
|
+
|
|
376
|
+
```css
|
|
377
|
+
/* Bigger, circular day cells. `className` lands on the outer .anta-calendar
|
|
378
|
+
shell; `--calendar-cell-width` / `--calendar-cell-height` drive the grid track
|
|
379
|
+
and the cell box (inherited down), and you target the day Buttons directly. */
|
|
380
|
+
.anta-calendar.roomy { --calendar-cell-width: 40px; --calendar-cell-height: 40px; }
|
|
381
|
+
.anta-calendar.roomy a-button[data-part="day-cell"] { border-radius: 50%; }
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
Each day is a `<a-button data-part="day-cell" data-date="YYYY-MM-DD">`, so a
|
|
385
|
+
specific date is addressable by its `data-date`: tint it, or hang an event marker
|
|
386
|
+
off a `::after` (give the cell `position: relative` first). Below, three days carry a
|
|
387
|
+
brand dot and one is flagged in the critical colour.
|
|
388
|
+
|
|
389
|
+
```css
|
|
390
|
+
/* Day cells are `<a-button data-part="day-cell" data-date="YYYY-MM-DD">`. Give them a
|
|
391
|
+
positioning context, dot event days with a `::after`, and tint a date's text. */
|
|
392
|
+
.anta-calendar.events a-button[data-part="day-cell"] { position: relative; }
|
|
393
|
+
.anta-calendar.events a-button[data-part="day-cell"]:is(
|
|
394
|
+
[data-date="2026-06-05"], [data-date="2026-06-12"], [data-date="2026-06-20"]
|
|
395
|
+
)::after {
|
|
396
|
+
content: ""; position: absolute; inset-block-end: 3px; inset-inline-start: 50%;
|
|
397
|
+
inline-size: 4px; block-size: 4px; transform: translateX(-50%);
|
|
398
|
+
border-radius: 999px; background: var(--text-2-brand);
|
|
399
|
+
}
|
|
400
|
+
.anta-calendar.events a-button[data-part="day-cell"][data-date="2026-06-25"] {
|
|
401
|
+
color: var(--text-2-critical); font-weight: 600;
|
|
402
|
+
}
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
The engine (`buildMonth`, `firstDayOfWeek`, …) is exported, so you can render a
|
|
406
|
+
fully custom grid and keep only the `<a-calendar>` form plumbing.
|