@tensor_1/react 2.35.0 → 2.37.0
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 +154 -15
- package/dist/components/Avatar.d.ts +26 -4
- package/dist/components/Avatar.js +26 -3
- package/dist/components/CommandPalette.d.ts +6 -1
- package/dist/components/CommandPalette.js +9 -3
- package/dist/components/DateFieldParts.d.ts +61 -0
- package/dist/components/DateFieldParts.js +195 -0
- package/dist/components/DatePicker.d.ts +129 -0
- package/dist/components/DatePicker.js +340 -0
- package/dist/components/DateRangePicker.d.ts +34 -3
- package/dist/components/DateRangePicker.js +120 -17
- package/dist/components/EntityPicker.d.ts +29 -4
- package/dist/components/EntityPicker.js +20 -8
- package/dist/components/InlineEdit.d.ts +74 -0
- package/dist/components/InlineEdit.js +176 -0
- package/dist/components/Radio.d.ts +23 -2
- package/dist/components/Radio.js +18 -3
- package/dist/components/SchedulingAgenda.js +3 -1
- package/dist/components/SchedulingTimeGrid.js +6 -4
- package/dist/components/SchedulingTimeline.d.ts +130 -15
- package/dist/components/SchedulingTimeline.js +768 -141
- package/dist/components/SearchSuggest.d.ts +12 -1
- package/dist/components/SearchSuggest.js +7 -2
- package/dist/components/Tree.d.ts +30 -2
- package/dist/components/Tree.js +135 -10
- package/dist/date-field-model.d.ts +40 -0
- package/dist/date-field-model.js +79 -0
- package/dist/hooks/usePrinting.d.ts +15 -0
- package/dist/hooks/usePrinting.js +29 -0
- package/dist/index.cjs +3890 -2070
- package/dist/index.d.cts +808 -262
- package/dist/index.d.ts +9 -6
- package/dist/index.js +40 -4
- package/dist/labels-context.d.ts +14 -1
- package/dist/scheduling-timeline-model.d.ts +61 -11
- package/dist/scheduling-timeline-model.js +85 -26
- package/dist/scheduling-timeline-rows.d.ts +40 -0
- package/dist/scheduling-timeline-rows.js +42 -0
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -460,6 +460,18 @@ const spanLayout = layoutMonthSpans(spansInSalienceOrder, grid, { timeZone, week
|
|
|
460
460
|
- **`<List roving>`**: the items are one tab stop (ArrowUp/ArrowDown, Home, End; Enter or Space activates the
|
|
461
461
|
focused item's `onClick`). A control inside an item keeps its own tab stop and keys. Items may be wrapped or
|
|
462
462
|
rendered later by a child component.
|
|
463
|
+
- **Print (HAR-1541).** The stylesheet has `@media print` rules; add no print CSS of your own. On paper the same
|
|
464
|
+
markup has no scroller, fits the page width, and keeps a week row or an agenda row on one page. The colours are
|
|
465
|
+
the `--uix-print-*` tokens (light, whatever theme the screen has). Nothing depends on a background: the `high`
|
|
466
|
+
band is a heavy edge on a paper surface (on paper `--calendar-band-high-fill` is the paper's and the edge reads
|
|
467
|
+
the new `--calendar-band-high-edge`; no screen rule reads it), each window pattern has an edge style of its own (`solid`, `diagonal` dashed, `cross`
|
|
468
|
+
double, `dotted` dotted), and the hour lines are drawn as lines. Month chips wrap to show the whole title, and
|
|
469
|
+
a time-grid item is at least as tall as its time and grows to hold its words (one too short for a line on a
|
|
470
|
+
screen prints them too). The header's buttons, the "now" line and a pending move are not printed; the period
|
|
471
|
+
title, the zone label and the legend are. A long agenda mounts every row while the page prints (`beforeprint`,
|
|
472
|
+
or print media) and is a window of rows again afterwards. One limit: `window.print()` called from inside a
|
|
473
|
+
React effect, on mount or later, runs before those rows are there. Such a page calls it from an event handler
|
|
474
|
+
or a timer, or passes `virtualizeAbove={Infinity}`.
|
|
463
475
|
- **Colour.** Every calendar colour is named once at the top of `scheduling-calendar.css` (`--calendar-*`).
|
|
464
476
|
- **Deprecated, still working in 2.x:** `entry.state` / `SchedulingEntryState`, `overlay.kind` /
|
|
465
477
|
`SchedulingOverlayKind` and the `previous` / `next` labels. An entry with `state` is no longer tinted: the two
|
|
@@ -467,27 +479,44 @@ const spanLayout = layoutMonthSpans(spansInSalienceOrder, grid, { timeZone, week
|
|
|
467
479
|
|
|
468
480
|
### SchedulingTimeline
|
|
469
481
|
|
|
470
|
-
Lanes of bars on a time axis (HAR-1364
|
|
471
|
-
|
|
482
|
+
Lanes of bars on a time axis (HAR-1364, HAR-1521): schedules by service, rollout rows, renewal markers. It pairs
|
|
483
|
+
with `SchedulingCalendar`: the same item API (`band`, `status`, `markers`, `accessibleName`), the same explicit
|
|
484
|
+
time zone, the same proposal model for moves.
|
|
472
485
|
|
|
473
486
|
```tsx
|
|
474
487
|
<SchedulingTimeline
|
|
475
|
-
lanes={
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
488
|
+
lanes={services.map((s) => ({ id: s.id, label: s.name }))}
|
|
489
|
+
groups={[{ id: 'pay', label: 'Payments', laneIds: ['checkout', 'ledger'], collapsed, summary: { count: 7 } }]}
|
|
490
|
+
onToggleGroup={(id) => toggle(id)}
|
|
491
|
+
items={rows.map((r) => ({ id: r.id, laneId: r.serviceId, title: r.title, start: r.start, end: r.end, band: r.band, status: r.status, markers: r.markers }))}
|
|
492
|
+
range={{ start: weekStart, end: weekEnd }} scale="day" subTicks={[6, 12, 18]} timeZone={tz} now={nowIso}
|
|
493
|
+
overlays={[{ id: 'q4', kindLabel: 'Hold', label: 'Quarter close', pattern: 'diagonal', laneIds: ['ledger'], start, end }]}
|
|
494
|
+
flagOverlaps={false}
|
|
495
|
+
onSelectItem={(item) => open(item.id)}
|
|
496
|
+
onProposeMove={(id, { start, end }) => askToMove(id, start, end)}
|
|
497
|
+
notice={truncated && 'Showing 500 of 1,240 items.'}
|
|
498
|
+
columnNotes={{ '2026-10-08': '12 more not shown' }}
|
|
482
499
|
/>
|
|
483
500
|
```
|
|
484
501
|
|
|
485
|
-
- **Axis:** `scale` is `hour`, `day` (default), `week` or `month`. Ticks fall on wall-clock boundaries in `timeZone` (DST and half-hour zones included). `tickWidth` sets the width of one unit; the axis scrolls inside the component and the lane column stays put.
|
|
486
|
-
- **
|
|
487
|
-
- **
|
|
488
|
-
- **
|
|
489
|
-
- **Windows and markers:** `
|
|
490
|
-
-
|
|
502
|
+
- **Axis:** `scale` is `hour`, `day` (default), `week` or `month`. Ticks fall on wall-clock boundaries in `timeZone` (DST and half-hour zones included). `tickWidth` sets the width of one unit; the axis scrolls inside the component and the lane column stays put. On a `day` axis `subTicks` (local hours) draws minor ticks inside each day; on an `hour` axis an hour that occurs twice (the clocks went back) shows its UTC offset.
|
|
503
|
+
- **Groups:** `groups` puts lanes into collapsible blocks, in the order given; a lane no group names is drawn after them. A collapsed group is one row with `summary.count` and `summary.markers`, which the consumer supplies: the timeline derives no count, and the `groupSummary` label words it (`Items: {count}` by default). That row stands for the lanes it hides: when a window covers one of them (or every lane), the row gets a strip under its head, one sub-row tall, and the window is drawn there, where the group label cannot lie over it. With `onToggleGroup` the consumer owns `collapsed`; without it the group opens and closes itself.
|
|
504
|
+
- **Bars:** bars that share time in a lane stack in sub-rows, packed by `packLanes`. A bar is drawn at least 24 px wide (`--timeline-bar-min`) and is packed as that wide, so two bars never cover each other and each is a 24 × 24 px target with nothing over it. The price is height: where bars are closer together than 24 px of axis, a lane takes more sub-rows than it has bars running at once. On a month axis at the default `tickWidth` 24 px is about six days, so a lane of daily bars is six sub-rows tall; a wider `tickWidth` brings it back down. The packing follows the width of the track (it is measured), and when it changes the first row in view is kept where it was. Only `band="high"` is filled; `status` is a line style; `markers` are shapes or icons with text. `renderItem` replaces the bar text, which is cut at a word: a first word that does not fit is not shown, so no part of a word or of a time is. The full title is in the name and the `title` tooltip.
|
|
505
|
+
- **Stacking flag:** by default a bar that shares time with another bar of its lane is hatched (`data-conflict`) and its name says so (`labels.conflict`). With `flagOverlaps={false}` the stacking stays and the hatch and the words go: the consumer says what clashes, with a reason, through `markers`.
|
|
506
|
+
- **Windows and markers:** an overlay is one neutral element with a `pattern` and its `kindLabel`, name and `scopeLabel` as text, however many rows there are. `laneIds` limits it to the rows of those lanes (unset: every row). With `onSelectOverlay` each window is a named button; without, windows are listed for screen readers. `markers` (a point in time, optionally with `laneIds`) and `now` are one line each. The now-line is neutral.
|
|
507
|
+
- **Moving:** three models, chosen by the callback.
|
|
508
|
+
- `onProposeMove(id, { start, end, adjusted })`: drag a bar, or press Shift+←/→ to build a pending move, Enter to send it and Escape to drop it. One call per finished gesture. The timeline never moves the bar: an item whose props do not change is back where it was, so a refused move snaps back. Return a promise to keep the outline until it settles. On a week or month axis a step is a calendar day (the wall-clock time is kept across a clock change).
|
|
509
|
+
- `onMoveItem(id, { start, end })`: the 2.x model, one call for every key press and every drop.
|
|
510
|
+
- `onResizeItem(id, { start, end })`: Alt+Shift+←/→ moves the end of a bar. Without it there is no resize and no hint offers one.
|
|
511
|
+
Moves snap to `step` (15 min on an hour axis, 1 h on a day axis, 1 day otherwise). `movable: false` pins a bar.
|
|
512
|
+
- **Keyboard:** the bars are one tab stop. ←/→ go to the previous or next bar in a lane, ↑/↓ to the nearest bar in the next lane (over group heads), Home/End to the ends of the lane, and Enter selects, or confirms a pending move, never both (a click or Space does nothing while a move is pending). A move that was sent is announced (`moveSent`).
|
|
513
|
+
- **Long timelines:** above `virtualizeAbove` rows (default 150) only the rows near the viewport are mounted, and an arrow key that goes to a bar that is not mounted mounts it, scrolls to it and focuses it. The rows counted are the ones passed, `lanes` plus `groups`: the answer does not move with the width, with what is collapsed or with how the bars pack. What is mounted is what the viewport shows plus overscan (under 150 row elements up to a viewport of about 7,000 px). The row of the focused control is never unmounted. `maxHeight` makes the lanes scroll inside the timeline under the axis; without it the page scrolls.
|
|
514
|
+
- **Notice and counts:** `notice` is shown above the axis; `columnNotes` puts a note on the column of a day (`YYYY-MM-DD` in `timeZone`).
|
|
515
|
+
- **Row heights:** with `groups`, `laneIds` or a virtual window every row has a fixed height (a lane is its sub-rows, a group head one line) and a lane label that does not fit is cut with an ellipsis. A timeline that uses none of them keeps rows that grow with their label.
|
|
516
|
+
- **Colour:** every colour is named once at the top of `scheduling-timeline.css` (`--timeline-*`). Forced colours are handled in `forced-colors.css`, as for the calendar.
|
|
517
|
+
- **Print (HAR-1541).** The stylesheet has `@media print` rules; add no print CSS of your own. On paper the timeline has no scroller and nothing sticks: the axis is fitted to the page width and the bars follow (every position is a share of the axis), and a lane stays on one page. The colours are the `--uix-print-*` tokens (light, whatever theme the screen has). Nothing depends on a background: the `high` band is a heavy edge on a paper surface (on paper `--timeline-band-high-fill` is the paper's and the edge reads the new `--timeline-band-high-edge`; no screen rule reads it), each window pattern has an edge style of its own, and the grid lines are drawn as lines. A note on the column of a day is not cut at its column (notes on neighbouring days can meet on a narrow page). The "now" line and a pending move are not printed. A timeline over `virtualizeAbove` rows mounts every row and bar while the page prints and windows again afterwards; a page that calls `window.print()` from inside a React effect calls it from an event handler or a timer, or passes `virtualizeAbove={Infinity}`. Limits: a bar keeps its share of the axis, so on a narrow page a short bar shows its time and not its title (the title is still the bar's name); and the bars are packed into sub-rows for the width the timeline had on the screen, so print from a window about as wide as the page when bars are short and close together.
|
|
518
|
+
- **Deprecated, still working in 2.x:** `item.state` / `SchedulingEntryState`, `overlay.kind` / `SchedulingTimelineOverlayKind` and the `states` / `overlays` labels. A bar with `state` is no longer tinted: the two problem states show a marker with the word for that state, and the kinds are told apart by pattern.
|
|
519
|
+
- Pure helpers are exported for tests and server code: `timelineTicks`, `timelineSubTicks`, `timelineRepeatedHourOffset`, `placeSpan`, `layoutLane`, `shiftSpan`, `snapToStep`, `pixelsToMs`, `defaultTimelineStep`, `timelineStepDelta`.
|
|
491
520
|
|
|
492
521
|
### Calendar model (zoned days, hour slots, lanes)
|
|
493
522
|
|
|
@@ -872,3 +901,113 @@ const v = useVirtualRows(rows, { rowHeight: 44, estimatedViewportHeight: 600, en
|
|
|
872
901
|
|
|
873
902
|
The density options no longer break a label inside a word at phone width ("Großzügig"); options
|
|
874
903
|
that do not fit one row wrap as whole options.
|
|
904
|
+
|
|
905
|
+
### Date fields, InlineEdit and other older gaps (2.36.0)
|
|
906
|
+
|
|
907
|
+
Older kit issues that TENSOR's triage still found blocking on 2026-10-08. Everything is additive.
|
|
908
|
+
|
|
909
|
+
#### DatePicker, DateTimePicker, DateRangePicker `mode="field"`
|
|
910
|
+
|
|
911
|
+
```tsx
|
|
912
|
+
<Field label={t('dueDate')} hint={t('workingDaysOnly')}>
|
|
913
|
+
<DatePicker value={due} onValueChange={setDue} name="due" locale={locale} weekStartsOn={1}
|
|
914
|
+
min="2026-11-02" max="2026-12-31" isUnavailable={isWeekend} />
|
|
915
|
+
</Field>
|
|
916
|
+
|
|
917
|
+
<Field label={t('starts')}>
|
|
918
|
+
<DateTimePicker value={starts} onValueChange={setStarts} timeZone="Europe/Vienna" minuteStep={15} />
|
|
919
|
+
</Field>
|
|
920
|
+
|
|
921
|
+
<DateRangePicker mode="field" value={period} onChange={setPeriod} label={t('period')} locale={locale} />
|
|
922
|
+
```
|
|
923
|
+
|
|
924
|
+
- **`DatePicker`** is a text input that shows the date and reads a typed one, with a button that
|
|
925
|
+
opens one month in a popover. The value is ISO `YYYY-MM-DD` or `null`; `name` submits it in a
|
|
926
|
+
hidden input whatever the field shows.
|
|
927
|
+
- **Typing:** read on Enter and when focus leaves. ISO is always accepted; otherwise three numbers
|
|
928
|
+
in the locale's order (`3.4.2026` is 3 April in German, `3/4/2026` is 4 March in en-US). Text
|
|
929
|
+
that is not a date, or a date outside `min` / `max` / `isUnavailable`, marks the field invalid,
|
|
930
|
+
says why, and leaves `value` alone (`onInputProblem` tells a form). Clearing the field sets
|
|
931
|
+
`null`.
|
|
932
|
+
- **Format:** the locale's numeric date by default. `formatDate` changes what the field writes;
|
|
933
|
+
pass `parseDate` with it when your format is not three numbers, and a `placeholder` so the
|
|
934
|
+
message can name the shape.
|
|
935
|
+
- **Calendar keys:** arrows by day and week, Home / End to the ends of the week, PageUp / PageDown
|
|
936
|
+
by month, with Shift by year. Focus never leaves `min` / `max`; a day refused by `isUnavailable`
|
|
937
|
+
can take focus and is read out, but cannot be chosen. ArrowDown in the field opens the
|
|
938
|
+
calendar; Escape closes it, returns focus to the field and does not close a drawer around it.
|
|
939
|
+
- **`DateTimePicker`** adds a time input. The value is `YYYY-MM-DDTHH:mm`, the shape
|
|
940
|
+
`datetime-local` holds, so it replaces that input without changing what a form submits. The
|
|
941
|
+
zone the wall clock is in is shown beside the time and is its description: `timeZone` shows
|
|
942
|
+
that zone's short name on the chosen day (it follows daylight saving), `timeZoneLabel` replaces
|
|
943
|
+
the text, and without either the viewer's own zone is shown. The value carries no zone.
|
|
944
|
+
- **`DateRangePicker mode="field"`** is a trigger that shows the range and opens the months in a
|
|
945
|
+
popover; it closes when the range has both ends. `DateRangePicker` also takes `weekStartsOn`.
|
|
946
|
+
- **Words:** `UixLabelsProvider` `datePicker`, `dateTimePicker` and the three new
|
|
947
|
+
`dateRangePicker` keys (`placeholder`, `fieldStart`, `fieldRange`).
|
|
948
|
+
- **Helpers:** `parseDateInput`, `formatDateKey`, `datePattern`, `dateOrder`, `isValidDateKey`,
|
|
949
|
+
`splitDateTime`, `joinDateTime`, `timeZoneName`.
|
|
950
|
+
|
|
951
|
+
#### InlineEdit
|
|
952
|
+
|
|
953
|
+
```tsx
|
|
954
|
+
<DescriptionItem term={t('title')}>
|
|
955
|
+
<InlineEdit label={t('title')} value={record.title} onSave={(next) => api.rename(record.id, next)}
|
|
956
|
+
validate={(next) => (next.trim() ? undefined : t('titleRequired'))} />
|
|
957
|
+
</DescriptionItem>
|
|
958
|
+
```
|
|
959
|
+
|
|
960
|
+
The value is a button named by the value and "Edit {label}". Activating it swaps in an editor with
|
|
961
|
+
Save and Cancel: Enter saves (Ctrl/⌘+Enter in `multiline`), Escape cancels without reaching a
|
|
962
|
+
drawer around it, and nothing is saved on blur. A promise from `onSave` shows the busy state; a
|
|
963
|
+
rejection keeps the draft and shows the reason. Afterwards focus is back on the value.
|
|
964
|
+
`renderView` and `renderEditor` replace either half (a `Select`, a `DatePicker`); `disabled`
|
|
965
|
+
renders plain text. Words: `labels` or `UixLabelsProvider` `inlineEdit`.
|
|
966
|
+
|
|
967
|
+
#### RadioCard
|
|
968
|
+
|
|
969
|
+
```tsx
|
|
970
|
+
<RadioGroup variant="card" label={t('rollout')}>
|
|
971
|
+
<RadioCard name="rollout" value="team" title={t('oneTeam')} description={t('oneTeamHint')}
|
|
972
|
+
checked={rollout === 'team'} onChange={() => setRollout('team')} />
|
|
973
|
+
…
|
|
974
|
+
</RadioGroup>
|
|
975
|
+
```
|
|
976
|
+
|
|
977
|
+
A native radio inside a card: one tab stop for the group, arrow keys choose, the whole card is
|
|
978
|
+
the target. The choice is a border and a check mark, not colour alone. `media` puts a preview
|
|
979
|
+
above the text. The grid wraps to one column on a phone.
|
|
980
|
+
|
|
981
|
+
#### Tree loads children on expand
|
|
982
|
+
|
|
983
|
+
```tsx
|
|
984
|
+
<Tree nodes={roots} loadChildren={(node) => api.org.children(node.id)} /> // roots: { id, label, hasChildren: true }
|
|
985
|
+
```
|
|
986
|
+
|
|
987
|
+
A node with `hasChildren` and no `children` is expandable. The first time it is expanded
|
|
988
|
+
`loadChildren` runs: the node is `aria-busy` and shows a loading row; a rejection shows an error
|
|
989
|
+
row whose Enter, Space or click retries. Focus follows the row that replaces the one it was on,
|
|
990
|
+
and lands on the node once its children are there. The tree keeps the loaded children; pass them
|
|
991
|
+
back in `nodes` when you own the data. Words: `labels` or `UixLabelsProvider` `tree`.
|
|
992
|
+
|
|
993
|
+
#### Avatar presence
|
|
994
|
+
|
|
995
|
+
```tsx
|
|
996
|
+
<Avatar presence="busy" src={user.photo} alt={user.name} /> <PresenceDot presence="away" />
|
|
997
|
+
```
|
|
998
|
+
|
|
999
|
+
`presence` is `online` (a filled dot), `busy` (a dot with a bar), `away` and `offline` (two
|
|
1000
|
+
rings): a shape as well as a colour, and the state in visually hidden text (`presenceLabels`
|
|
1001
|
+
translates it). `status` is still `presence="online"`. `PresenceDot` is the dot alone, named by
|
|
1002
|
+
its state (`label={null}` makes it decorative). Fixed: the dot was clipped to a quarter by the avatar's round
|
|
1003
|
+
mask; an avatar with a dot no longer clips it.
|
|
1004
|
+
|
|
1005
|
+
#### EntityPicker and CommandPalette words
|
|
1006
|
+
|
|
1007
|
+
- `EntityPicker onSearch` may resolve `{ options, hasMore: true }` when its list was cut off: the
|
|
1008
|
+
list ends with a row that says more match (not an option; arrow keys skip it) and the polite
|
|
1009
|
+
status says so too. Below `minQueryLength` the open list shows "Type at least {count}
|
|
1010
|
+
characters." Both, and every other word, come from `labels` or `UixLabelsProvider`
|
|
1011
|
+
`entityPicker`. `SearchSuggest` has the two slots these use: `idle` and `note`.
|
|
1012
|
+
- `CommandPalette` announces its result count through `UixLabelsProvider` `commandPalette`
|
|
1013
|
+
(`resultsOne`, `resultsMany`, `{count}`) or a `resultsLabel(count)` prop; English is unchanged.
|
|
@@ -1,19 +1,41 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { HTMLAttributes, ReactNode } from 'react';
|
|
3
3
|
|
|
4
|
+
/** Presence of a person (HAR-1374). Each state has its own shape as well as its own colour. */
|
|
5
|
+
type AvatarPresence = 'online' | 'busy' | 'away' | 'offline';
|
|
6
|
+
/** The words read for each presence state. English; pass translations as `presenceLabels`. */
|
|
7
|
+
declare const DEFAULT_PRESENCE_LABELS: Record<AvatarPresence, string>;
|
|
4
8
|
interface AvatarProps extends HTMLAttributes<HTMLSpanElement> {
|
|
5
|
-
/** TENSOR RX-125 (UIX-04): translatable; English default. */
|
|
9
|
+
/** TENSOR RX-125 (UIX-04): translatable; English default. Still honoured for `online`. */
|
|
6
10
|
onlineLabel?: string;
|
|
7
11
|
size?: 'sm' | 'md' | 'lg' | 'xl' | '2xl';
|
|
8
12
|
src?: string;
|
|
9
13
|
alt?: string;
|
|
10
14
|
/** Initials / fallback shown when there is no `src`. */
|
|
11
15
|
children?: ReactNode;
|
|
12
|
-
/** Online status dot. */
|
|
16
|
+
/** Online status dot. The same as `presence="online"`; kept for existing callers. */
|
|
13
17
|
status?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* A presence dot: `online` (filled green), `busy` (red with a bar), `away` (an amber ring),
|
|
20
|
+
* `offline` (a grey ring). The state is also in visually hidden text, so it is never colour
|
|
21
|
+
* alone.
|
|
22
|
+
*/
|
|
23
|
+
presence?: AvatarPresence;
|
|
24
|
+
/** Translations of the presence words. */
|
|
25
|
+
presenceLabels?: Partial<Record<AvatarPresence, string>>;
|
|
14
26
|
}
|
|
15
27
|
/** Avatar over `.uix-avatar`. Renders an image when `src` is set, else children (initials). */
|
|
16
|
-
declare function Avatar({ size, src, alt, children, status, onlineLabel, className, ...props }: AvatarProps): react.JSX.Element;
|
|
28
|
+
declare function Avatar({ size, src, alt, children, status, presence, presenceLabels, onlineLabel, className, ...props }: AvatarProps): react.JSX.Element;
|
|
29
|
+
interface PresenceDotProps extends Omit<HTMLAttributes<HTMLSpanElement>, 'children'> {
|
|
30
|
+
presence: AvatarPresence;
|
|
31
|
+
/**
|
|
32
|
+
* The state in words for assistive technology. Default: the English word. Pass `null` when
|
|
33
|
+
* the state is already written next to the dot, to make the dot decorative.
|
|
34
|
+
*/
|
|
35
|
+
label?: string | null;
|
|
36
|
+
}
|
|
37
|
+
/** The presence dot on its own, for a row or a menu that shows no avatar (HAR-1374). */
|
|
38
|
+
declare function PresenceDot({ presence, label, className, ...props }: PresenceDotProps): react.JSX.Element;
|
|
17
39
|
interface AvatarGroupProps extends HTMLAttributes<HTMLSpanElement> {
|
|
18
40
|
children?: ReactNode;
|
|
19
41
|
}
|
|
@@ -29,4 +51,4 @@ interface UserChipProps extends HTMLAttributes<HTMLSpanElement> {
|
|
|
29
51
|
/** Avatar + name (+ optional sub-line) over `.uix-user-chip`. */
|
|
30
52
|
declare function UserChip({ avatar, name, sub, className, children, ...props }: UserChipProps): react.JSX.Element;
|
|
31
53
|
|
|
32
|
-
export { Avatar, AvatarGroup, type AvatarGroupProps, type AvatarProps, UserChip, type UserChipProps };
|
|
54
|
+
export { Avatar, AvatarGroup, type AvatarGroupProps, type AvatarPresence, type AvatarProps, DEFAULT_PRESENCE_LABELS, PresenceDot, type PresenceDotProps, UserChip, type UserChipProps };
|
|
@@ -1,12 +1,33 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { cx } from "../cx.js";
|
|
3
|
-
|
|
3
|
+
const DEFAULT_PRESENCE_LABELS = {
|
|
4
|
+
online: "online",
|
|
5
|
+
busy: "busy",
|
|
6
|
+
away: "away",
|
|
7
|
+
offline: "offline"
|
|
8
|
+
};
|
|
9
|
+
function Avatar({ size = "md", src, alt, children, status, presence, presenceLabels, onlineLabel, className, ...props }) {
|
|
10
|
+
const state = presence ?? (status ? "online" : void 0);
|
|
11
|
+
const word = state && (presenceLabels?.[state] ?? (state === "online" ? onlineLabel : void 0) ?? DEFAULT_PRESENCE_LABELS[state]);
|
|
4
12
|
return /* @__PURE__ */ jsxs("span", { className: cx("uix-avatar", size !== "md" && `uix-avatar--${size}`, className), ...props, children: [
|
|
5
13
|
src ? /* @__PURE__ */ jsx("img", { src, alt: alt ?? "" }) : children,
|
|
6
|
-
|
|
7
|
-
|
|
14
|
+
state && /* @__PURE__ */ jsx("span", { className: "uix-avatar__status", "data-presence": state === "online" ? void 0 : state, "aria-hidden": "true" }),
|
|
15
|
+
state && /* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", children: word })
|
|
8
16
|
] });
|
|
9
17
|
}
|
|
18
|
+
function PresenceDot({ presence, label, className, ...props }) {
|
|
19
|
+
const decorative = label === null;
|
|
20
|
+
return /* @__PURE__ */ jsx(
|
|
21
|
+
"span",
|
|
22
|
+
{
|
|
23
|
+
className: cx("uix-presence", `uix-presence--${presence}`, className),
|
|
24
|
+
role: decorative ? void 0 : "img",
|
|
25
|
+
"aria-label": decorative ? void 0 : label ?? DEFAULT_PRESENCE_LABELS[presence],
|
|
26
|
+
"aria-hidden": decorative ? true : void 0,
|
|
27
|
+
...props
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
}
|
|
10
31
|
function AvatarGroup({ children, className, ...props }) {
|
|
11
32
|
return /* @__PURE__ */ jsx("span", { className: cx("uix-avatar-group", className), ...props, children });
|
|
12
33
|
}
|
|
@@ -23,5 +44,7 @@ function UserChip({ avatar, name, sub, className, children, ...props }) {
|
|
|
23
44
|
export {
|
|
24
45
|
Avatar,
|
|
25
46
|
AvatarGroup,
|
|
47
|
+
DEFAULT_PRESENCE_LABELS,
|
|
48
|
+
PresenceDot,
|
|
26
49
|
UserChip
|
|
27
50
|
};
|
|
@@ -4,6 +4,11 @@ import { ReactNode, HTMLAttributes, InputHTMLAttributes } from 'react';
|
|
|
4
4
|
interface CommandPaletteProps extends HTMLAttributes<HTMLDivElement> {
|
|
5
5
|
/** TENSOR RX-125 (UIX-04): translatable; English default. */
|
|
6
6
|
inputLabel?: string;
|
|
7
|
+
/**
|
|
8
|
+
* The polite result count for this palette (HAR-1647). Default: `UixLabelsProvider`
|
|
9
|
+
* `commandPalette.resultsOne` / `resultsMany` (`{count}`), then "1 result" / "3 results".
|
|
10
|
+
*/
|
|
11
|
+
resultsLabel?: (count: number) => string;
|
|
7
12
|
/** Props for the search input (value/onChange/placeholder wired by the consumer). */
|
|
8
13
|
inputProps?: InputHTMLAttributes<HTMLInputElement>;
|
|
9
14
|
children?: ReactNode;
|
|
@@ -14,7 +19,7 @@ interface CommandPaletteProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
14
19
|
* a combobox over the listbox of `CommandItem`s; spreads override every default, so
|
|
15
20
|
* consumers already passing their own roles/labels keep working.
|
|
16
21
|
*/
|
|
17
|
-
declare function CommandPalette({ inputProps, children, inputLabel: inputLabelProp, className, ...props }: CommandPaletteProps): react.JSX.Element;
|
|
22
|
+
declare function CommandPalette({ inputProps, children, inputLabel: inputLabelProp, resultsLabel, className, ...props }: CommandPaletteProps): react.JSX.Element;
|
|
18
23
|
interface CommandGroupProps {
|
|
19
24
|
label?: ReactNode;
|
|
20
25
|
children?: ReactNode;
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { createContext, useCallback, useContext, useEffect, useId, useMemo, useState } from "react";
|
|
3
|
+
import { createContext, useCallback, useContext, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
4
4
|
import { cx } from "../cx.js";
|
|
5
|
+
import { fillLabel } from "../fill-label.js";
|
|
5
6
|
import { useUixLabels } from "../labels-context.js";
|
|
6
7
|
const CmdkContext = createContext(null);
|
|
7
|
-
function CommandPalette({ inputProps, children, inputLabel: inputLabelProp, className, ...props }) {
|
|
8
|
+
function CommandPalette({ inputProps, children, inputLabel: inputLabelProp, resultsLabel, className, ...props }) {
|
|
8
9
|
const uixLabels = useUixLabels();
|
|
9
10
|
const inputLabel = inputLabelProp ?? uixLabels.commandPalette?.input ?? "Command palette";
|
|
11
|
+
const resultsOne = uixLabels.commandPalette?.resultsOne ?? "{count} result";
|
|
12
|
+
const resultsMany = uixLabels.commandPalette?.resultsMany ?? "{count} results";
|
|
13
|
+
const resultsText = (count2) => resultsLabel?.(count2) ?? fillLabel(count2 === 1 ? resultsOne : resultsMany, { count: count2 });
|
|
14
|
+
const resultsTextRef = useRef(resultsText);
|
|
15
|
+
resultsTextRef.current = resultsText;
|
|
10
16
|
const listId = useId();
|
|
11
17
|
const [activeId, setActiveId] = useState(null);
|
|
12
18
|
const [count, setCount] = useState(0);
|
|
@@ -19,7 +25,7 @@ function CommandPalette({ inputProps, children, inputLabel: inputLabelProp, clas
|
|
|
19
25
|
return () => setCount((c) => c - 1);
|
|
20
26
|
}, []);
|
|
21
27
|
useEffect(() => {
|
|
22
|
-
const t = window.setTimeout(() => setAnnounced(
|
|
28
|
+
const t = window.setTimeout(() => setAnnounced(resultsTextRef.current(count)), 150);
|
|
23
29
|
return () => clearTimeout(t);
|
|
24
30
|
}, [count]);
|
|
25
31
|
const ctx = useMemo(() => ({ listId, setActive, registerItem }), [listId, setActive, registerItem]);
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { MouseEvent } from 'react';
|
|
3
|
+
import { CalendarWeekday } from '../calendar-model.js';
|
|
4
|
+
|
|
5
|
+
/** The viewer's local calendar date as `YYYY-MM-DD` (not UTC: "today" is a local idea). */
|
|
6
|
+
declare const localDateKey: (now?: Date) => string;
|
|
7
|
+
declare const monthLabel: (date: string, locale?: string) => string;
|
|
8
|
+
declare const fullDateLabel: (date: string, locale?: string) => string;
|
|
9
|
+
/**
|
|
10
|
+
* Short weekday names in `locale`, starting on `weekStartsOn`. 2024-01-07 is a Sunday;
|
|
11
|
+
* formatting in UTC keeps each reference day on its own date.
|
|
12
|
+
*/
|
|
13
|
+
declare const weekdayNames: (locale: string | undefined, weekStartsOn?: CalendarWeekday) => string[];
|
|
14
|
+
/**
|
|
15
|
+
* Open state and trigger wiring for a field whose calendar lives in a kit `Popover`
|
|
16
|
+
* (`popover="auto"`, so a press outside or Escape closes it). `restoreFocus` is called when it
|
|
17
|
+
* closes while focus is inside it or nowhere: the field takes focus back.
|
|
18
|
+
*/
|
|
19
|
+
declare function useFieldPopover(popoverId: string, restoreFocus: () => void, onOpenChange?: (open: boolean) => void): {
|
|
20
|
+
open: boolean;
|
|
21
|
+
show: () => void;
|
|
22
|
+
hide: (restore?: boolean) => void;
|
|
23
|
+
/** Spread on the button that opens the calendar. */
|
|
24
|
+
triggerProps: {
|
|
25
|
+
'aria-haspopup': "dialog";
|
|
26
|
+
'aria-expanded': boolean;
|
|
27
|
+
'aria-controls': string;
|
|
28
|
+
onPointerDown: () => void;
|
|
29
|
+
onPointerLeave: () => void;
|
|
30
|
+
onPointerCancel: () => void;
|
|
31
|
+
onClick: (event: MouseEvent<HTMLElement>) => void;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
interface MonthCalendarProps {
|
|
35
|
+
/** The selected day, or `null`. */
|
|
36
|
+
value: string | null;
|
|
37
|
+
onSelect: (date: string) => void;
|
|
38
|
+
/** The day that has focus when the calendar opens. */
|
|
39
|
+
initialDate: string;
|
|
40
|
+
min?: string;
|
|
41
|
+
max?: string;
|
|
42
|
+
isUnavailable?: (date: string) => boolean;
|
|
43
|
+
locale?: string;
|
|
44
|
+
weekStartsOn?: CalendarWeekday;
|
|
45
|
+
today: string | null;
|
|
46
|
+
labels: {
|
|
47
|
+
previousMonth: string;
|
|
48
|
+
nextMonth: string;
|
|
49
|
+
};
|
|
50
|
+
/** Move focus to the active day when the calendar mounts. */
|
|
51
|
+
autoFocus?: boolean;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* One month of day buttons with the full calendar key path: arrows by day and week, Home / End
|
|
55
|
+
* to the ends of the week, PageUp / PageDown by month, with Shift by year. Days outside
|
|
56
|
+
* `min` / `max` are never reached; unavailable days inside them can be reached and read out,
|
|
57
|
+
* but not chosen. Shares the day-cell look of the range picker.
|
|
58
|
+
*/
|
|
59
|
+
declare function MonthCalendar({ value, onSelect, initialDate, min, max, isUnavailable, locale, weekStartsOn, today, labels, autoFocus, }: MonthCalendarProps): react.JSX.Element;
|
|
60
|
+
|
|
61
|
+
export { MonthCalendar, type MonthCalendarProps, fullDateLabel, localDateKey, monthLabel, useFieldPopover, weekdayNames };
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { addCalendarDays, addCalendarMonths, buildMonthGrid, isDateUnavailable, startOfMonth } from "../calendar-model.js";
|
|
5
|
+
import { ChevronLeftIcon, ChevronRightIcon } from "../icons/components.js";
|
|
6
|
+
const localDateKey = (now = /* @__PURE__ */ new Date()) => `${now.getFullYear().toString().padStart(4, "0")}-${(now.getMonth() + 1).toString().padStart(2, "0")}-${now.getDate().toString().padStart(2, "0")}`;
|
|
7
|
+
const utc = (date) => /* @__PURE__ */ new Date(`${date}T00:00:00Z`);
|
|
8
|
+
const monthLabel = (date, locale) => new Intl.DateTimeFormat(locale, { month: "long", year: "numeric", timeZone: "UTC" }).format(utc(date));
|
|
9
|
+
const fullDateLabel = (date, locale) => new Intl.DateTimeFormat(locale, { dateStyle: "full", timeZone: "UTC" }).format(utc(date));
|
|
10
|
+
const weekdayNames = (locale, weekStartsOn = 1) => {
|
|
11
|
+
const fmt = new Intl.DateTimeFormat(locale, { weekday: "short", timeZone: "UTC" });
|
|
12
|
+
return Array.from({ length: 7 }, (_, i) => fmt.format(new Date(Date.UTC(2024, 0, 7 + weekStartsOn + i))));
|
|
13
|
+
};
|
|
14
|
+
const isOpen = (el) => {
|
|
15
|
+
try {
|
|
16
|
+
return !!el?.matches(":popover-open");
|
|
17
|
+
} catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
function useFieldPopover(popoverId, restoreFocus, onOpenChange) {
|
|
22
|
+
const [open, setOpen] = useState(false);
|
|
23
|
+
const pointerWasOpen = useRef(null);
|
|
24
|
+
const restoreRef = useRef(restoreFocus);
|
|
25
|
+
restoreRef.current = restoreFocus;
|
|
26
|
+
const onOpenChangeRef = useRef(onOpenChange);
|
|
27
|
+
onOpenChangeRef.current = onOpenChange;
|
|
28
|
+
const skipRestore = useRef(false);
|
|
29
|
+
const popoverEl = () => typeof document === "undefined" ? null : document.getElementById(popoverId);
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
const el = popoverEl();
|
|
32
|
+
if (!el) return;
|
|
33
|
+
const onToggle = (event) => {
|
|
34
|
+
const { newState } = event;
|
|
35
|
+
const nowOpen = newState ? newState === "open" : isOpen(el);
|
|
36
|
+
setOpen(nowOpen);
|
|
37
|
+
onOpenChangeRef.current?.(nowOpen);
|
|
38
|
+
if (nowOpen) return;
|
|
39
|
+
const active = el.ownerDocument.activeElement;
|
|
40
|
+
if (skipRestore.current) skipRestore.current = false;
|
|
41
|
+
else if (el.contains(active) || active === el.ownerDocument.body) restoreRef.current();
|
|
42
|
+
};
|
|
43
|
+
el.addEventListener("toggle", onToggle);
|
|
44
|
+
return () => el.removeEventListener("toggle", onToggle);
|
|
45
|
+
}, [popoverId]);
|
|
46
|
+
const show = () => {
|
|
47
|
+
const el = popoverEl();
|
|
48
|
+
if (el && !isOpen(el)) {
|
|
49
|
+
try {
|
|
50
|
+
el.showPopover();
|
|
51
|
+
} catch {
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
const hide = (restore = true) => {
|
|
56
|
+
const el = popoverEl();
|
|
57
|
+
if (!el || !isOpen(el)) return;
|
|
58
|
+
skipRestore.current = !restore;
|
|
59
|
+
try {
|
|
60
|
+
el.hidePopover();
|
|
61
|
+
} catch {
|
|
62
|
+
skipRestore.current = false;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
return {
|
|
66
|
+
open,
|
|
67
|
+
show,
|
|
68
|
+
hide,
|
|
69
|
+
/** Spread on the button that opens the calendar. */
|
|
70
|
+
triggerProps: {
|
|
71
|
+
"aria-haspopup": "dialog",
|
|
72
|
+
"aria-expanded": open,
|
|
73
|
+
"aria-controls": popoverId,
|
|
74
|
+
// A pointer press outside an open auto popover already closed it (light dismiss); the
|
|
75
|
+
// click that follows must not open it again.
|
|
76
|
+
onPointerDown: () => {
|
|
77
|
+
pointerWasOpen.current = isOpen(popoverEl());
|
|
78
|
+
},
|
|
79
|
+
// A press that never became a click (dragged off, cancelled) must not decide the next one.
|
|
80
|
+
onPointerLeave: () => {
|
|
81
|
+
pointerWasOpen.current = null;
|
|
82
|
+
},
|
|
83
|
+
onPointerCancel: () => {
|
|
84
|
+
pointerWasOpen.current = null;
|
|
85
|
+
},
|
|
86
|
+
onClick: (event) => {
|
|
87
|
+
if (event.defaultPrevented) return;
|
|
88
|
+
const was = pointerWasOpen.current;
|
|
89
|
+
pointerWasOpen.current = null;
|
|
90
|
+
if (was === true) {
|
|
91
|
+
hide();
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (isOpen(popoverEl())) hide();
|
|
95
|
+
else show();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function MonthCalendar({
|
|
101
|
+
value,
|
|
102
|
+
onSelect,
|
|
103
|
+
initialDate,
|
|
104
|
+
min,
|
|
105
|
+
max,
|
|
106
|
+
isUnavailable,
|
|
107
|
+
locale,
|
|
108
|
+
weekStartsOn = 1,
|
|
109
|
+
today,
|
|
110
|
+
labels,
|
|
111
|
+
autoFocus = false
|
|
112
|
+
}) {
|
|
113
|
+
const clamp = (date) => min && date < min ? min : max && date > max ? max : date;
|
|
114
|
+
const [active, setActive] = useState(() => clamp(initialDate));
|
|
115
|
+
const gridRef = useRef(null);
|
|
116
|
+
const pendingFocus = useRef(autoFocus);
|
|
117
|
+
const month = startOfMonth(active);
|
|
118
|
+
const days = useMemo(() => buildMonthGrid(month, weekStartsOn).filter((day) => day.inMonth), [month, weekStartsOn]);
|
|
119
|
+
const blanks = (utc(month).getUTCDay() - weekStartsOn + 7) % 7;
|
|
120
|
+
const weekdays = useMemo(() => weekdayNames(locale, weekStartsOn), [locale, weekStartsOn]);
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
if (!pendingFocus.current) return;
|
|
123
|
+
pendingFocus.current = false;
|
|
124
|
+
gridRef.current?.querySelector(`[data-date="${active}"]`)?.focus({ preventScroll: true });
|
|
125
|
+
}, [active]);
|
|
126
|
+
const focusDate = (date) => {
|
|
127
|
+
pendingFocus.current = true;
|
|
128
|
+
setActive(clamp(date));
|
|
129
|
+
};
|
|
130
|
+
const showMonth = (amount) => setActive((current) => clamp(addCalendarMonths(current, amount)));
|
|
131
|
+
const unavailable = (date) => isDateUnavailable(date, { min, max, isDisabled: isUnavailable });
|
|
132
|
+
const onDayKeyDown = (event, date) => {
|
|
133
|
+
const rtl = typeof getComputedStyle === "function" && getComputedStyle(event.currentTarget).direction === "rtl";
|
|
134
|
+
const offsets = { ArrowLeft: rtl ? 1 : -1, ArrowRight: rtl ? -1 : 1, ArrowUp: -7, ArrowDown: 7 };
|
|
135
|
+
if (event.key in offsets) {
|
|
136
|
+
event.preventDefault();
|
|
137
|
+
focusDate(addCalendarDays(date, offsets[event.key]));
|
|
138
|
+
} else if (event.key === "Home" || event.key === "End") {
|
|
139
|
+
event.preventDefault();
|
|
140
|
+
const index = (utc(date).getUTCDay() - weekStartsOn + 7) % 7;
|
|
141
|
+
focusDate(addCalendarDays(date, event.key === "Home" ? -index : 6 - index));
|
|
142
|
+
} else if (event.key === "PageUp" || event.key === "PageDown") {
|
|
143
|
+
event.preventDefault();
|
|
144
|
+
focusDate(addCalendarMonths(date, (event.key === "PageUp" ? -1 : 1) * (event.shiftKey ? 12 : 1)));
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
const atMin = !!min && month <= startOfMonth(min);
|
|
148
|
+
const atMax = !!max && month >= startOfMonth(max);
|
|
149
|
+
return /* @__PURE__ */ jsxs("div", { className: "uix-date-picker__calendar", children: [
|
|
150
|
+
/* @__PURE__ */ jsxs("div", { className: "uix-date-picker__nav", children: [
|
|
151
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "uix-date-picker__nav-btn", "aria-label": labels.previousMonth, "aria-disabled": atMin || void 0, onClick: () => {
|
|
152
|
+
if (!atMin) showMonth(-1);
|
|
153
|
+
}, children: /* @__PURE__ */ jsx(ChevronLeftIcon, { size: "sm" }) }),
|
|
154
|
+
/* @__PURE__ */ jsx("span", { className: "uix-date-picker__month", "aria-live": "polite", children: monthLabel(month, locale) }),
|
|
155
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "uix-date-picker__nav-btn", "aria-label": labels.nextMonth, "aria-disabled": atMax || void 0, onClick: () => {
|
|
156
|
+
if (!atMax) showMonth(1);
|
|
157
|
+
}, children: /* @__PURE__ */ jsx(ChevronRightIcon, { size: "sm" }) })
|
|
158
|
+
] }),
|
|
159
|
+
/* @__PURE__ */ jsx("div", { className: "uix-date-range-picker__weekdays", "aria-hidden": "true", children: weekdays.map((day, index) => /* @__PURE__ */ jsx("span", { children: day }, index)) }),
|
|
160
|
+
/* @__PURE__ */ jsxs("div", { ref: gridRef, className: "uix-date-range-picker__grid uix-date-picker__grid", role: "group", "aria-label": monthLabel(month, locale), children: [
|
|
161
|
+
Array.from({ length: blanks }, (_, index) => /* @__PURE__ */ jsx("span", { "aria-hidden": "true" }, `blank-${index}`)),
|
|
162
|
+
days.map((day) => {
|
|
163
|
+
const off = unavailable(day.date);
|
|
164
|
+
return /* @__PURE__ */ jsx(
|
|
165
|
+
"button",
|
|
166
|
+
{
|
|
167
|
+
type: "button",
|
|
168
|
+
"data-date": day.date,
|
|
169
|
+
"data-selected": day.date === value || void 0,
|
|
170
|
+
"aria-pressed": day.date === value,
|
|
171
|
+
"aria-label": fullDateLabel(day.date, locale),
|
|
172
|
+
"aria-current": day.date === today ? "date" : void 0,
|
|
173
|
+
"aria-disabled": off || void 0,
|
|
174
|
+
tabIndex: active === day.date ? 0 : -1,
|
|
175
|
+
onFocus: () => setActive(day.date),
|
|
176
|
+
onKeyDown: (event) => onDayKeyDown(event, day.date),
|
|
177
|
+
onClick: () => {
|
|
178
|
+
if (!off) onSelect(day.date);
|
|
179
|
+
},
|
|
180
|
+
children: day.day
|
|
181
|
+
},
|
|
182
|
+
day.date
|
|
183
|
+
);
|
|
184
|
+
})
|
|
185
|
+
] })
|
|
186
|
+
] });
|
|
187
|
+
}
|
|
188
|
+
export {
|
|
189
|
+
MonthCalendar,
|
|
190
|
+
fullDateLabel,
|
|
191
|
+
localDateKey,
|
|
192
|
+
monthLabel,
|
|
193
|
+
useFieldPopover,
|
|
194
|
+
weekdayNames
|
|
195
|
+
};
|