@antadesign/anta 0.3.18 → 0.3.20

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.
Files changed (92) hide show
  1. package/README.md +38 -242
  2. package/dist/anta_helpers.d.ts +2 -2
  3. package/dist/components/Banner.d.ts +3 -3
  4. package/dist/components/Button.d.ts +1 -1
  5. package/dist/components/Calendar.d.ts +11 -12
  6. package/dist/components/Card.d.ts +1 -1
  7. package/dist/components/Card.js +1 -1
  8. package/dist/components/Checkbox.d.ts +8 -9
  9. package/dist/components/Dialog.d.ts +1 -1
  10. package/dist/components/Expander.d.ts +5 -6
  11. package/dist/components/Input.d.ts +6 -3
  12. package/dist/components/Input.js +3 -1
  13. package/dist/components/InputAutocomplete.d.ts +8 -8
  14. package/dist/components/InputDate.css +1 -0
  15. package/dist/components/InputDate.d.ts +1 -0
  16. package/dist/components/InputDate.js +5 -5
  17. package/dist/components/InputTime.d.ts +3 -3
  18. package/dist/components/InputTime.js +1 -1
  19. package/dist/components/Menu.d.ts +7 -3
  20. package/dist/components/Menu.js +3 -2
  21. package/dist/components/MenuItem.d.ts +1 -1
  22. package/dist/components/MenuItem.js +1 -1
  23. package/dist/components/Progress.d.ts +1 -1
  24. package/dist/components/RadioGroup.d.ts +12 -13
  25. package/dist/components/Select.d.ts +32 -38
  26. package/dist/components/Select.js +9 -13
  27. package/dist/components/SelectFaceted.d.ts +28 -33
  28. package/dist/components/SelectFaceted.js +29 -23
  29. package/dist/components/Slider.d.ts +5 -5
  30. package/dist/components/Switch.d.ts +4 -4
  31. package/dist/components/Tabs.d.ts +7 -7
  32. package/dist/components/Title.d.ts +1 -1
  33. package/dist/components/Toaster.d.ts +1 -1
  34. package/dist/components/Tooltip.d.ts +1 -1
  35. package/dist/components/select-parts.css +1 -0
  36. package/dist/elements/a-card.css +1 -1
  37. package/dist/elements/a-dialog.css +1 -1
  38. package/dist/elements/a-expander.css +1 -1
  39. package/dist/elements/a-expander.d.ts +1 -0
  40. package/dist/elements/a-expander.js +12 -2
  41. package/dist/elements/a-input.css +1 -1
  42. package/dist/elements/a-menu.js +12 -0
  43. package/dist/elements/a-progress.css +1 -1
  44. package/dist/elements/a-slider.css +1 -1
  45. package/dist/elements/a-tabs.css +1 -1
  46. package/dist/elements/a-tabs.d.ts +3 -0
  47. package/dist/elements/a-tabs.js +40 -1
  48. package/dist/elements/a-tooltip.css +1 -1
  49. package/dist/elements/a-tooltip.d.ts +1 -1
  50. package/dist/elements/a-tooltip.js +1 -1
  51. package/dist/elements/index.d.ts +1 -1
  52. package/dist/general_types.d.ts +24 -22
  53. package/dist/index.d.ts +3 -3
  54. package/dist/jsx-runtime.d.ts +9 -0
  55. package/docs/accessibility.md +34 -0
  56. package/docs/changelog.md +561 -0
  57. package/docs/colors.md +42 -0
  58. package/docs/comparison.md +28 -0
  59. package/docs/components/banner.md +267 -0
  60. package/docs/components/button.md +603 -0
  61. package/docs/components/card.md +321 -0
  62. package/docs/components/checkbox.md +296 -0
  63. package/docs/components/dialog.md +314 -0
  64. package/docs/components/expander.md +526 -0
  65. package/docs/components/icon.md +155 -0
  66. package/docs/components/input-autocomplete.md +100 -0
  67. package/docs/components/input-date.md +401 -0
  68. package/docs/components/input-time.md +174 -0
  69. package/docs/components/input.md +610 -0
  70. package/docs/components/loader.md +139 -0
  71. package/docs/components/menu.md +511 -0
  72. package/docs/components/progress.md +182 -0
  73. package/docs/components/radio.md +368 -0
  74. package/docs/components/select-faceted.md +451 -0
  75. package/docs/components/select.md +926 -0
  76. package/docs/components/slider.md +147 -0
  77. package/docs/components/switch.md +198 -0
  78. package/docs/components/tabs.md +471 -0
  79. package/docs/components/tag.md +185 -0
  80. package/docs/components/text.md +148 -0
  81. package/docs/components/title.md +146 -0
  82. package/docs/components/toaster.md +313 -0
  83. package/docs/components/tooltip.md +177 -0
  84. package/docs/credits.md +18 -0
  85. package/docs/index.md +52 -0
  86. package/docs/install-config.md +240 -0
  87. package/docs/normalization.md +102 -0
  88. package/docs/overview.md +10 -0
  89. package/docs/packages/stickers.md +187 -0
  90. package/docs/packages/table.md +153 -0
  91. package/docs/theming.md +13 -0
  92. package/package.json +10 -6
@@ -0,0 +1,100 @@
1
+ # InputAutocomplete
2
+
3
+ `InputAutocomplete` is a text field with a suggestion list. Its value remains a
4
+ free-form string: text that is not in the list stays in the field, and selecting
5
+ a suggestion fills the field. Use [Select](./select.md) with `filter` when the
6
+ value must be one of the options.
7
+
8
+ InputAutocomplete uses an [Input](./input.md) for the field and a [Menu](./menu.md)
9
+ for suggestions. **`onValueChange`** reports field text. `onSelect` runs only
10
+ after someone chooses a suggestion. Use `value` to control it, or `defaultValue`
11
+ for uncontrolled use.
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`: a custom matcher, called for each 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 uncontrolled use. |
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` uses a custom matcher; `false` shows `suggestions` unchanged for
52
+ application-provided async or remote filtering. |
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 color — a named tone or any CSS color. |
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 browser renders the suggestions popup. Use `InputAutocomplete` for custom
80
+ option content, popup styling, or consistent behavior across browsers.
81
+
82
+ ```html
83
+ <input data-anta type="text" list="people" placeholder="Search people">
84
+
85
+ <datalist id="people">
86
+ <option value="Maya Chen">
87
+ <option value="Noah Williams">
88
+ <option value="Olivia Martinez">
89
+ <option value="Liam Johnson">
90
+ </datalist>
91
+ ```
92
+
93
+ **The field** is styled through Input's props (`size`, `status`, `tone`, `round`,
94
+ `leading` / `trailing`) and its `::part`s; set its width with `style` / `className`
95
+ (forwarded to the field). **The suggestion popover** takes Menu's `::part(menu)`.
96
+
97
+ ```tsx
98
+ // Give the field a width; tint it with a custom tone.
99
+ <InputAutocomplete tone="info" style={{ width: '260px' }} suggestions={frameworks} />
100
+ ```
@@ -0,0 +1,401 @@
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` uses Anta components in light DOM; it has no hidden shadow-root UI.
98
+ The day cells and previous/next chevrons are `<Button>`s inside a
99
+ form-associated `<a-calendar>` grid. Date calculations use the
100
+ [Temporal API](https://tc39.es/proposal-temporal/docs/) through
101
+ `temporal-polyfill`.
102
+
103
+ For a raw grid, import `buildMonth` from `@antadesign/anta`. It returns
104
+ localized weekday headers and day records with ISO values, accessible labels,
105
+ and selected, disabled, today, and outside-month state. It does not manage
106
+ navigation, roving focus, selection, or rendering. The value is an ISO
107
+ `YYYY-MM-DD` string, never a `Date`. Use `value` with `onStateChange` to
108
+ control it, or `defaultValue` for uncontrolled use. It participates in forms
109
+ under `name`.
110
+
111
+ **Not SSR-safe.** `Calendar` (and `InputDate`, which embeds it) reads the current
112
+ date (`Temporal.Now`) and locale (`navigator.language`) at render, so a server
113
+ render disagrees with the client on the highlighted "today" and on localized
114
+ labels, and the page hydrates with a mismatch. Render it client-side only: Astro
115
+ `client:only`, or a dynamic import inside `useEffect`. Every demo on this page uses
116
+ `client:only` for that reason.
117
+
118
+ ## Selecting a date
119
+
120
+ Click a day, or focus the grid and press Enter / Space. The
121
+ selected day takes the tertiary-button selected look toned brand — a brand-tinted
122
+ filled cell with a ring. Today shows as a secondary Button, a resting fill.
123
+
124
+ ```tsx
125
+ <Calendar defaultValue="2026-06-12" />
126
+ ```
127
+
128
+ ## Size
129
+
130
+ `size` scales the whole calendar: the day cells, the prev/next chevrons, and the
131
+ weekday and month-heading type. It follows Button's `small` / `medium` (default) /
132
+ `large` scale, with day-cell heights of 24 / 28 / 32px.
133
+
134
+ ```tsx
135
+ <Calendar size="small" defaultValue="2026-06-28" />
136
+ <Calendar defaultValue="2026-06-28" /> {/* medium (default) */}
137
+ <Calendar size="large" defaultValue="2026-06-28" />
138
+ ```
139
+
140
+ ## Range
141
+
142
+ `min` and `max` (ISO strings) bound the selectable range. Days outside it render
143
+ disabled and skip keyboard focus, and the prev/next arrows stop at the range edge.
144
+ The jump menu adapts to how far the range reaches.
145
+
146
+ No `min` / `max`: the jump menu lists today ±3 years, each a submenu of its months.
147
+
148
+ ```tsx
149
+ <Calendar size="small" defaultValue="2026-06-15" />
150
+ ```
151
+
152
+ A range across years lists each year (here 2026 and 2027) as a submenu of months.
153
+ June 2026 disables days 1–7, and the back arrow stops at the range's start.
154
+
155
+ ```tsx
156
+ <Calendar size="small" defaultValue="2026-06-15" min="2026-06-08" max="2027-06-24" />
157
+ ```
158
+
159
+ A range inside one year skips the year level and lists months directly, March
160
+ through November enabled and the rest disabled.
161
+
162
+ ```tsx
163
+ <Calendar size="small" defaultValue="2026-06-15" min="2026-03-01" max="2026-11-30" />
164
+ ```
165
+
166
+ A range inside one month leaves nowhere to jump. Both arrows and the month/year
167
+ button are disabled, and only the in-range days stay selectable.
168
+
169
+ ```tsx
170
+ <Calendar size="small" defaultValue="2026-06-15" min="2026-06-08" max="2026-06-24" />
171
+ ```
172
+
173
+ ## Locale
174
+
175
+ `Calendar` respects the browser locale by default (`navigator.language`). Pass a
176
+ BCP-47 `locale` to override it: the first day of week comes from the locale's week
177
+ info (never hard-coded to Sunday), and the weekday and month names are localized.
178
+
179
+ ```tsx
180
+ <Calendar defaultValue="2026-06-28" locale="en-US" /> {/* week starts Sunday */}
181
+ <Calendar defaultValue="2026-06-28" locale="fr-FR" /> {/* week starts Monday */}
182
+ <Calendar defaultValue="2026-06-28" locale="ja-JP" />
183
+ ```
184
+
185
+ ## Controlled
186
+
187
+ Uncontrolled by default: pass `defaultValue` and read the date from `onValueChange`
188
+ or at form submit. Make it controlled with `value` + `onStateChange` to drive it
189
+ from your store. The grid then follows `value`, and a pick only *requests* a change:
190
+ answer by updating `value`, reject by doing nothing.
191
+
192
+ ```tsx
193
+ const [date, setDate] = useState('2026-06-28')
194
+
195
+ <Calendar
196
+ value={date}
197
+ onStateChange={(_e, { next }) => setDate(next ?? '')}
198
+ />
199
+ ```
200
+
201
+ `onStateChange` fires before the element applies, with `(event, { next, prev, reason })`
202
+ where `next` / `prev` are ISO strings. For a `'user'` pick, `event.preventDefault()`
203
+ vetoes it (uncontrolled). A controlled calendar with no `onStateChange` is read-only.
204
+
205
+ ## Disabled
206
+
207
+ `disabled` dims the whole calendar and removes it from interaction.
208
+
209
+ ```tsx
210
+ <Calendar defaultValue="2026-06-28" disabled />
211
+ ```
212
+
213
+ ## Forms
214
+
215
+ `Calendar` is form-associated. Give it a `name` and it submits the selected ISO
216
+ date under that key, resets to `defaultValue` on form reset, and reports through
217
+ the standard constraint-validation pipeline.
218
+
219
+ ```tsx
220
+ <form>
221
+ <Calendar name="due-date" defaultValue="2026-06-28" />
222
+ </form>
223
+ ```
224
+
225
+ ## Accessibility
226
+
227
+ The calendar is a labelled **group** of day buttons, a flat grid rather than a
228
+ nested `grid`/`row`/`gridcell` tree. The group is named by the visible month
229
+ (`aria-labelledby`). Each day is a `<button>` whose `aria-label` is its full date
230
+ including weekday ("Sunday, June 28, 2026"), with `aria-pressed` on the selected day
231
+ and `aria-current="date"` on today. The decorative weekday headers are `aria-hidden`,
232
+ since each day already names its weekday. A roving `tabindex` keeps a single tab
233
+ stop, and an `aria-live` region announces the month when it changes.
234
+
235
+ | Key | Moves focus |
236
+ |---|---|
237
+ | ← / → | by one day |
238
+ | ↑ / ↓ | by one week |
239
+ | Home / End | to the start / end of the week |
240
+ | PageUp / PageDown | by one month |
241
+ | Shift + PageUp / PageDown | by one year |
242
+ | Enter / Space | select the focused day |
243
+
244
+ ## Customization
245
+
246
+ `InputDate` uses Input, Menu, and Calendar. Calendar uses an `<a-calendar>`
247
+ element for the day grid and a JSX wrapper for navigation. The sections below
248
+ describe which layer provides each behavior and how to recreate the interface
249
+ without React.
250
+
251
+ ### Build it from elements
252
+
253
+ `InputDate` has no `a-inputdate` element. Its wrapper renders an editable
254
+ `<Input>` and a `<Menu>` containing a `<Calendar>`. When `time` is set, the
255
+ hour/minute fields and AM/PM `Tabs` appear below the grid. The wrapper manages
256
+ draft text, parsing on commit, and the ISO value.
257
+
258
+ Clicking the field opens the menu while keeping focus in the field. Pressing
259
+ ↓ opens the menu and moves focus to the grid. The wrapper passes a
260
+ `focusSignal` to Calendar, which renders `data-focus`; `<a-calendar>` then
261
+ focuses the active day. Calendar and Tabs emit their own `statechange` events,
262
+ but Menu responds only to changes in its own open state.
263
+
264
+ The `<a-calendar>` element provides grid behavior. It connects the supplied day
265
+ `<button>`s to click and keyboard navigation, including arrows, Home,
266
+ End, PageUp, and PageDown. It stores the form
267
+ value through `ElementInternals` and emits `statechange` / `change` on selection
268
+ and `navigate` when keyboard navigation leaves the displayed month. It is
269
+ framework-agnostic and does not track a current month, `min`, or `max`.
270
+
271
+ The JSX wrapper renders the controls above the grid as siblings of
272
+ `<a-calendar>`. The previous/next arrows are `<Button>`s. The month/year picker
273
+ is a `<Button>` that opens a `<Menu>` of year `MenuItem`s, each with a month
274
+ submenu. The wrapper stores the displayed-month cursor in state and derives day
275
+ cells and the arrows' `disabled` states from `min` and `max`. A click on an
276
+ arrow, a menu selection, or a `navigate` event updates the cursor and re-renders
277
+ the grid. The wrapper enforces the range. The element only ignores a day that
278
+ was rendered with `disabled`.
279
+
280
+ The element has no `min` or `max` because the wrapper provides the surrounding
281
+ controls. Without React, render the grid with `buildMonth` and add the
282
+ navigation behavior shown below:
283
+
284
+ ```html
285
+ <!-- `.calendar` scopes this example. Use an application selector instead. -->
286
+ <div class="calendar" style="display: inline-flex; flex-direction: column; gap: 6px">
287
+ <div style="display: flex; align-items: center">
288
+ <a-button priority="tertiary" data-prev aria-label="Previous month"><a-icon shape="chevron-left"></a-icon></a-button>
289
+ <a-button priority="tertiary" data-heading style="flex: 1; justify-content: center" aria-haspopup="menu"></a-button>
290
+ <a-button priority="tertiary" data-next aria-label="Next month"><a-icon shape="chevron-right"></a-icon></a-button>
291
+ </div>
292
+ <!-- The element provides selection, keyboard behavior, form value, and events. -->
293
+ <a-calendar role="group" name="date"></a-calendar>
294
+ </div>
295
+ <script type="module">
296
+ import '@antadesign/anta/elements'
297
+ import { buildMonth, parseISODate } from '@antadesign/anta'
298
+ import { Temporal } from 'temporal-polyfill'
299
+
300
+ const root = document.querySelector('.calendar')
301
+ const grid = root.querySelector('a-calendar')
302
+ const heading = root.querySelector('[data-heading]')
303
+ let cursor = Temporal.Now.plainDateISO()
304
+
305
+ function render() {
306
+ const m = buildMonth({ anchor: cursor, locale: navigator.language })
307
+ heading.textContent = m.heading
308
+ grid.replaceChildren()
309
+ for (const w of m.weekdays) {
310
+ const s = Object.assign(document.createElement('span'), { textContent: w.narrow })
311
+ s.setAttribute('data-part', 'weekday')
312
+ s.setAttribute('aria-hidden', 'true')
313
+ grid.append(s)
314
+ }
315
+ for (const d of m.weeks.flat()) {
316
+ const b = Object.assign(document.createElement('a-button'), { textContent: String(d.day) })
317
+ b.setAttribute('priority', 'tertiary')
318
+ b.setAttribute('data-part', 'day-cell')
319
+ b.setAttribute('data-date', d.iso) // the element keys off this
320
+ b.tabIndex = d.iso === cursor.toString() ? 0 : -1
321
+ if (d.selected) b.setAttribute('selected', '')
322
+ if (d.disabled) b.setAttribute('disabled', '')
323
+ grid.append(b)
324
+ }
325
+ }
326
+
327
+ root.querySelector('[data-prev]').addEventListener('click', () => { cursor = cursor.subtract({ months: 1 }); render() })
328
+ root.querySelector('[data-next]').addEventListener('click', () => { cursor = cursor.add({ months: 1 }); render() })
329
+ // Keyboard paging past the rendered grid: the element asks for a month flip.
330
+ grid.addEventListener('navigate', (e) => { cursor = parseISODate(e.detail.date) ?? cursor; render() })
331
+ render()
332
+ </script>
333
+ ```
334
+
335
+ Tracking the cursor, painting the grid from `buildMonth`, and re-rendering on
336
+ `navigate` is what the React `Calendar` packages, plus the year/month jump menu,
337
+ which uses the same `Menu` composition shown on the [Menu](./menu.md) page.
338
+
339
+ `InputDate` has no host element. Rebuilding its text parsing, dropdown calendar,
340
+ localized day grid, month navigation, keyboard navigation, and focus management
341
+ in vanilla JavaScript is significant work, even with Anta's `buildMonth` and
342
+ `parseISODate` helpers. Use the `InputDate` wrapper when React or Preact is an
343
+ option. Otherwise, a native HTML date input is the practical baseline.
344
+
345
+ ### Native HTML date input
346
+
347
+ Add `data-anta` to a `date`, `datetime-local`, `month`, `time`, or `week` input.
348
+ Nothing else is required: Anta styles the field while the browser keeps its picker,
349
+ icon, validation, and keyboard model. The picker itself and its date/time
350
+ formatting remain browser- and OS-owned.
351
+
352
+ `data-anta-size`, `round`, and a custom-color `tone` use the matching Input
353
+ field treatments.
354
+
355
+ ```html
356
+ <!-- `data-anta` styles the field, not the native picker. -->
357
+ <input data-anta type="date" name="due-date" value="2026-06-15">
358
+ <input data-anta type="datetime-local" name="starts-at" value="2026-06-15T09:00">
359
+ <input data-anta type="month" name="billing-month" value="2026-06">
360
+ <input data-anta type="time" name="reminder-at" value="09:00">
361
+ <input data-anta type="week" name="sprint-week" value="2026-W25">
362
+ ```
363
+
364
+ The calendar is **light DOM**, so everything is plain CSS, with no shadow parts or
365
+ hidden internals. The days and chevrons are real `<a-button>`s, so their colors,
366
+ hover, focus ring, and selected look come from the Button styles and theme
367
+ themselves; they sit inside the `<a-calendar>` grid, and the switcher header sits in
368
+ the `.anta-calendar` shell. An un-layered consumer rule beats `@layer anta` without
369
+ `!important`. The class below is for the demo.
370
+
371
+ ```css
372
+ /* Bigger, circular day cells. `className` lands on the outer .anta-calendar
373
+ shell; `--calendar-cell-width` / `--calendar-cell-height` drive the grid track
374
+ and the cell box (inherited down), and you target the day Buttons directly. */
375
+ .anta-calendar.roomy { --calendar-cell-width: 40px; --calendar-cell-height: 40px; }
376
+ .anta-calendar.roomy a-button[data-part="day-cell"] { border-radius: 50%; }
377
+ ```
378
+
379
+ Each day is a `<a-button data-part="day-cell" data-date="YYYY-MM-DD">`, so a
380
+ specific date is addressable by its `data-date`: tint it, or hang an event marker
381
+ off a `::after` (give the cell `position: relative` first). Below, three days carry a
382
+ brand dot and one is flagged in the critical color.
383
+
384
+ ```css
385
+ /* Day cells are `<a-button data-part="day-cell" data-date="YYYY-MM-DD">`. Give them a
386
+ positioning context, dot event days with a `::after`, and tint a date's text. */
387
+ .anta-calendar.events a-button[data-part="day-cell"] { position: relative; }
388
+ .anta-calendar.events a-button[data-part="day-cell"]:is(
389
+ [data-date="2026-06-05"], [data-date="2026-06-12"], [data-date="2026-06-20"]
390
+ )::after {
391
+ content: ""; position: absolute; inset-block-end: 3px; inset-inline-start: 50%;
392
+ inline-size: 4px; block-size: 4px; transform: translateX(-50%);
393
+ border-radius: 999px; background: var(--text-2-brand);
394
+ }
395
+ .anta-calendar.events a-button[data-part="day-cell"][data-date="2026-06-25"] {
396
+ color: var(--text-2-critical); font-weight: 600;
397
+ }
398
+ ```
399
+
400
+ The engine (`buildMonth`, `firstDayOfWeek`, …) is exported, so you can render a
401
+ fully custom grid and keep only the `<a-calendar>` form plumbing.
@@ -0,0 +1,174 @@
1
+ # InputTime
2
+
3
+ **`InputTime`** is a segmented wall-clock field: one boxed input, like the others,
4
+ holding separate **hour** and **minute** sections — plus an **AM/PM** section in
5
+ 12-hour locales — that behave as a single control. Each section is a native text
6
+ input:
7
+ ↑/↓ steps its value (wrapping 59 → 00), ←/→
8
+ moves between sections at the caret boundary, and typing a digit fills the section
9
+ and advances to the next once it can't hold more. AM/PM accepts its locale text or
10
+ `a` / `p`.
11
+
12
+ A leading clock icon marks the affordance (change it with `icon`). Unlike a text
13
+ `Input` (which fills its container), the field **sizes to its content** — a time
14
+ is a small, fixed control — so set a `width` (e.g. `style={{ width: '100%' }}`) if
15
+ you want it to fill. The value is a 24-hour `"HH:mm"` string (`''` until both hour
16
+ and minute are set), never a `Date`. It works controlled (`value` + `onValueChange`)
17
+ or uncontrolled (`defaultValue`), and submits under `name` via the native form APIs.
18
+
19
+ ## The field
20
+
21
+ Focus a section and edit it with the keyboard: ↑/↓ to step (it
22
+ wraps), PageUp/PageDown for a larger jump on minutes,
23
+ or enter the digits. Home/End, selection, deletion, and
24
+ paste behave as they do in a native text input. Typing
25
+ `9` then `3` `0` lands `09:30` and moves across the sections on its own; an
26
+ out-of-range digit (a `6` in the minutes) commits and advances immediately.
27
+ Backspace on an empty section selects the previous one. The whole thing
28
+ reads as one field while preserving native caret behavior. In a
29
+ 12-hour field, typing a 24-hour hour converts it and flips AM/PM — `18` becomes
30
+ `6 PM`, so pasted 24-hour times work.
31
+
32
+ ```tsx
33
+ <InputTime label="Start time" defaultValue="09:30" onValueChange={(_, { value }) => save(value)} />
34
+ ```
35
+
36
+ ## Locale
37
+
38
+ The clock follows the locale: `en-US` shows 12-hour with an AM/PM section, most
39
+ others show 24-hour with none. It's derived from `Intl` (the resolved `hourCycle`),
40
+ and the section **order**, the **separator**, and the **AM/PM text** all come from
41
+ the locale too — Japanese renders the period first (`午後2:05`), Finnish separates
42
+ with a dot (`14.05`), and 24-hour locales drop the AM/PM section entirely. Set
43
+ `hour12` to force one clock regardless of locale.
44
+
45
+ ```tsx
46
+ <InputTime label="US English" locale="en-US" defaultValue="14:05" /> {/* 12-hour: 02 : 05 PM */}
47
+ <InputTime label="German" locale="de-DE" defaultValue="14:05" /> {/* 24-hour: 14 : 05 */}
48
+ <InputTime label="Japanese" locale="ja-JP" hour12 defaultValue="14:05" /> {/* 午後 02:05 — period first */}
49
+ <InputTime label="Force 24h" hour12={false} defaultValue="14:05" />
50
+ ```
51
+
52
+ ## Size, status, and clearing
53
+
54
+ `size` (`small` / `medium` / `large`) matches the other inputs' scale; `status`
55
+ recolors the box and hint (only `critical` marks it invalid); `clearable` adds a
56
+ clear button once a value is set — clearing empties every section back to the
57
+ placeholder dashes. `disabled` behaves as it does on `Input`.
58
+
59
+ `min` and `max` (24-hour `"HH:mm"`) bound the value: a complete time outside the
60
+ range is clamped to the nearest bound as you step or leave the field, and flags
61
+ `rangeUnderflow` / `rangeOverflow` for form validity in the meantime.
62
+
63
+ ```tsx
64
+ <InputTime label="Meeting" size="small" defaultValue="10:00" clearable />
65
+ <InputTime label="Deadline" status="critical" hint="Pick a time in the future." />
66
+ <InputTime label="Office hours" size="large" min="09:00" max="17:00" defaultValue="09:30" />
67
+ ```
68
+
69
+ | Prop | Type | Default | Description |
70
+ |------|------|---------|-------------|
71
+ | `children?` | ReactNode | — | Extra content rendered under the field, above the hint (a no-box child like
72
+ a `<Tooltip>` takes no space and anchors to the field). |
73
+ | `clearable?` | boolean | — | Show a clear button once the field has a value. |
74
+ | `defaultValue?` | string | — | Initial value for the uncontrolled case (24-hour `"HH:mm"`). |
75
+ | `dimActions?` | boolean | — | Dim the trailing adornments at rest; they brighten on hover / focus. |
76
+ | `disabled?` | boolean | — | Disable the field. |
77
+ | `hint?` | ReactNode | — | Message below the field. `status` recolors it and prefixes a glyph. |
78
+ | `hour12?` | boolean | — | Force the clock: `true` = 12-hour (AM/PM), `false` = 24-hour. Omit to follow
79
+ the locale. |
80
+ | `icon?` | false \| IconShape | clock | Leading icon at the start of the field — the clock affordance. Pass another
81
+ shape to change it, or `false` to drop it. |
82
+ | `label?` | ReactNode | — | Field label, shown above the control and used as the segment group's
83
+ accessible name. |
84
+ | `locale?` | string | navigator.language | BCP-47 locale driving the clock (12h vs 24h), segment order, separator, and
85
+ the AM/PM text. |
86
+ | `max?` | string | — | Latest allowed time, 24-hour `"HH:mm"`. A complete value above it is clamped
87
+ down (on step / blur) and flagged `rangeOverflow`. |
88
+ | `min?` | string | — | Earliest allowed time, 24-hour `"HH:mm"`. A complete value below it is
89
+ clamped up (on step / blur) and flagged `rangeUnderflow` for form validity. |
90
+ | `name?` | string | — | Form field name — the 24-hour value submits under this key. |
91
+ | `onBlur?` | (e) => void | — | Fires when the field loses focus. |
92
+ | `onClearInput?` | (e) => void | — | Fires after the built-in clear button has cleared the field. |
93
+ | `onFocus?` | (e) => void | — | Fires when the field gains focus. |
94
+ | `onValueChange?` | (event, attrs) => void | — | Fires on every edit (`input`), with the native event + an `attrs` snapshot
95
+ (`value`, `name`, `empty`, `valid`, `validationMessage`). Also fires on
96
+ `change` (blur) and on clear. |
97
+ | `required?` | boolean | — | Mark the field required (drives native validity). |
98
+ | `round?` | boolean \| number \| string | — | Fully-round the field, or a custom radius (`number` px / CSS length). |
99
+ | `size?` | 'small' \| 'medium' \| 'large' | medium | Size variant. small=24px, medium=28px, large=32px tall. |
100
+ | `status?` | 'neutral' \| 'brand' \| 'info' \| 'success' \| 'warning' \| 'critical' | — | Validation / feedback tone. Only `critical` marks the field invalid; the
101
+ others are advisory. Omit (or `neutral`) for a plain field. |
102
+ | `statusIcon?` | (string & {}) \| false \| IconShape | — | Glyph before the `hint` when `status` is set (per-status default; pass a
103
+ shape to override, or `false` to drop it). |
104
+ | `tone?` | string | — | Custom accent color — any literal CSS color tints the resting + hover
105
+ border (focus ring stays `--focus-ring`); `status` overrides for validation. |
106
+ | `trailing?` | ReactNode | — | Content pinned to the end of the field (after the clear button). |
107
+ | `value?` | string | — | Controlled value — 24-hour `"HH:mm"`. Pair with `onValueChange`. |
108
+
109
+ Use the web component directly when you are not using React or Preact and a native control does not fit.
110
+
111
+ Slots supply the field label and leading icon.
112
+
113
+ ```html
114
+ <a-input-time name="start-time" defaultvalue="09:30">
115
+ <span slot="label">Start time</span>
116
+ <a-icon slot="leading" shape="clock" aria-hidden="true"></a-icon>
117
+ </a-input-time>
118
+ ```
119
+
120
+ ### Native HTML time input
121
+
122
+ For a form that can use the browser's time picker, add `data-anta` to a regular
123
+ time input. It gives the field Anta's resting chrome while the browser keeps its
124
+ own picker, validation, and keyboard behavior.
125
+
126
+ `data-anta-size`, `round`, and a custom-color `tone` use the matching Input
127
+ field treatments.
128
+
129
+ ```html
130
+ <input data-anta type="time" name="reminder-at" value="09:00">
131
+ ```
132
+
133
+ ## Styling
134
+
135
+ `InputTime` renders an `<a-input-time>` whose field chrome mirrors `<a-input>`.
136
+ Route feedback color through `status`; everything else is reachable as plain CSS on
137
+ the host or through `::part()`. `segment` targets every editable input. `hour`,
138
+ `minute`, and `period` target one segment type regardless of locale order. `literal`
139
+ targets the localized separator. The element also exposes `field`, `segments`,
140
+ `leading`, `label`, `hint`, `clear`, and `trailing`. Don't override the internal
141
+ `--input-time-*` values directly; they are defaults, not a styling API.
142
+
143
+ ```css
144
+ /* Make one field fill its container (the default is content-width). Style all
145
+ editable segments, then keep AM/PM and the locale separator quieter. */
146
+ .my-time { width: 100%; }
147
+ .my-time::part(segments) { color: #e5484d; }
148
+ .my-time::part(period) { color: var(--text-3); }
149
+ .my-time::part(literal) { color: var(--text-4); }
150
+ ```
151
+
152
+ Drop the clock icon with the `icon={false}` prop, and point the `segments` part at
153
+ a monospaced family for fixed-width digits — a small `padding-top` re-centers the
154
+ tighter `1em` line:
155
+
156
+ ```tsx
157
+ <InputTime icon={false} className="plain-time" defaultValue="09:30" />
158
+
159
+ <style>{`
160
+ .plain-time::part(segments) {
161
+ font-family: var(--monospace, ui-monospace, monospace);
162
+ line-height: 1em;
163
+ padding-top: 2px;
164
+ }
165
+ `}</style>
166
+ ```
167
+
168
+ For a one-off accent, `tone` takes **any literal CSS color** (e.g. `#7c3aed`) and
169
+ tints the resting + hover border — the focus ring stays the global ring. It's a
170
+ color, not a named tone; the named feedback tones live on `status`.
171
+
172
+ ```tsx
173
+ <InputTime label="Accent" tone="#7c3aed" defaultValue="09:30" />
174
+ ```