@tensor_1/react 2.34.0 → 2.36.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 +164 -15
- package/dist/{SchedulingCalendar-DSLQ6AY5.d.ts → SchedulingCalendar-B6TakO2h.d.ts} +13 -1
- 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/List.d.ts +8 -1
- package/dist/components/List.js +3 -1
- package/dist/components/ListRoving.d.ts +12 -0
- package/dist/components/ListRoving.js +51 -0
- package/dist/components/Radio.d.ts +23 -2
- package/dist/components/Radio.js +18 -3
- package/dist/components/SchedulingAgenda.d.ts +1 -1
- package/dist/components/SchedulingAgenda.js +101 -4
- package/dist/components/SchedulingCalendar.d.ts +1 -1
- package/dist/components/SchedulingCalendar.js +89 -9
- package/dist/components/SchedulingTimeGrid.d.ts +1 -1
- package/dist/components/SchedulingTimeGrid.js +101 -4
- package/dist/components/SchedulingTimeline.d.ts +130 -15
- package/dist/components/SchedulingTimeline.js +765 -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/index.cjs +4179 -2056
- package/dist/index.d.cts +829 -264
- package/dist/index.d.ts +10 -7
- package/dist/index.js +40 -4
- package/dist/labels-context.d.ts +15 -2
- package/dist/roving.d.ts +15 -0
- package/dist/roving.js +17 -0
- 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
|
@@ -437,6 +437,29 @@ const spanLayout = layoutMonthSpans(spansInSalienceOrder, grid, { timeZone, week
|
|
|
437
437
|
- **A narrow month** (`monthDensity="counts"`): a cell is the date, the consumer's `days[date].count` and its markers.
|
|
438
438
|
No entry is drawn and none is counted: a row's "+N" is for windows over `windowLaneCap` only. The setting does not
|
|
439
439
|
touch the week.
|
|
440
|
+
- **Keyboard (HAR-1527).** The month grid, the time grid and the agenda are each **one tab stop**; no item is a
|
|
441
|
+
tab stop of its own.
|
|
442
|
+
- Month: arrows move between days, Home/End go to the first/last day shown. **Enter** on a day with items moves
|
|
443
|
+
into them (ArrowUp/ArrowDown, Home, End between them; Enter calls `onSelectEntry`; **Esc** returns to the day).
|
|
444
|
+
**Space** or a click on the day number calls `onSelectDate`; so does Enter on a day with no items.
|
|
445
|
+
- Week/Day time grid: the tab stop is a day head (ArrowLeft/ArrowRight between days). Enter goes into the
|
|
446
|
+
day's items and windows. On an item, Enter selects it, or confirms a pending move (`onProposeMove`); Esc drops
|
|
447
|
+
a pending move, otherwise returns to the day head.
|
|
448
|
+
- Agenda (grouped or flat): ArrowUp/ArrowDown, Home and End walk rows, window notes and "open day", also
|
|
449
|
+
through the rows a long agenda has not mounted yet.
|
|
450
|
+
- The keys act on the calendar's own items only: a field or link you render inside a cell or an item keeps its
|
|
451
|
+
keys. A row's "+N" is reached from the day it opens. An item reached with the pointer takes the tab stop to
|
|
452
|
+
its day. Esc on an item returns to its day and is not passed on; a second Esc reaches a surrounding dialog.
|
|
453
|
+
- A day button is named with the date and `days[date].label` (the count and the highest signal), through
|
|
454
|
+
`labels.dayName` (`'{date}, {label}'`). If your labels already say the date, set `dayName: '{label}'`.
|
|
455
|
+
- To put focus back on an item after closing your own panel: `querySelector('[data-item-id="…"]')?.focus()`.
|
|
456
|
+
- **Emphasis.** `entry.emphasis: 'highlight' | 'dim'` sets `data-highlight` / `data-dim` on that item in every
|
|
457
|
+
view. Highlight is a heavier edge in the text colour and a heavier weight, dim is quieter text;
|
|
458
|
+
neither changes a hue, the line style of the state, or the band's leading edge. With forced colours a
|
|
459
|
+
highlighted title is underlined and dimmed text is grey.
|
|
460
|
+
- **`<List roving>`**: the items are one tab stop (ArrowUp/ArrowDown, Home, End; Enter or Space activates the
|
|
461
|
+
focused item's `onClick`). A control inside an item keeps its own tab stop and keys. Items may be wrapped or
|
|
462
|
+
rendered later by a child component.
|
|
440
463
|
- **Colour.** Every calendar colour is named once at the top of `scheduling-calendar.css` (`--calendar-*`).
|
|
441
464
|
- **Deprecated, still working in 2.x:** `entry.state` / `SchedulingEntryState`, `overlay.kind` /
|
|
442
465
|
`SchedulingOverlayKind` and the `previous` / `next` labels. An entry with `state` is no longer tinted: the two
|
|
@@ -444,27 +467,43 @@ const spanLayout = layoutMonthSpans(spansInSalienceOrder, grid, { timeZone, week
|
|
|
444
467
|
|
|
445
468
|
### SchedulingTimeline
|
|
446
469
|
|
|
447
|
-
Lanes of bars on a time axis (HAR-1364
|
|
448
|
-
|
|
470
|
+
Lanes of bars on a time axis (HAR-1364, HAR-1521): schedules by service, rollout rows, renewal markers. It pairs
|
|
471
|
+
with `SchedulingCalendar`: the same item API (`band`, `status`, `markers`, `accessibleName`), the same explicit
|
|
472
|
+
time zone, the same proposal model for moves.
|
|
449
473
|
|
|
450
474
|
```tsx
|
|
451
475
|
<SchedulingTimeline
|
|
452
|
-
lanes={
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
476
|
+
lanes={services.map((s) => ({ id: s.id, label: s.name }))}
|
|
477
|
+
groups={[{ id: 'pay', label: 'Payments', laneIds: ['checkout', 'ledger'], collapsed, summary: { count: 7 } }]}
|
|
478
|
+
onToggleGroup={(id) => toggle(id)}
|
|
479
|
+
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 }))}
|
|
480
|
+
range={{ start: weekStart, end: weekEnd }} scale="day" subTicks={[6, 12, 18]} timeZone={tz} now={nowIso}
|
|
481
|
+
overlays={[{ id: 'q4', kindLabel: 'Hold', label: 'Quarter close', pattern: 'diagonal', laneIds: ['ledger'], start, end }]}
|
|
482
|
+
flagOverlaps={false}
|
|
483
|
+
onSelectItem={(item) => open(item.id)}
|
|
484
|
+
onProposeMove={(id, { start, end }) => askToMove(id, start, end)}
|
|
485
|
+
notice={truncated && 'Showing 500 of 1,240 items.'}
|
|
486
|
+
columnNotes={{ '2026-10-08': '12 more not shown' }}
|
|
459
487
|
/>
|
|
460
488
|
```
|
|
461
489
|
|
|
462
|
-
- **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.
|
|
463
|
-
- **
|
|
464
|
-
- **
|
|
465
|
-
- **
|
|
466
|
-
- **Windows and markers:** `
|
|
467
|
-
-
|
|
490
|
+
- **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.
|
|
491
|
+
- **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.
|
|
492
|
+
- **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.
|
|
493
|
+
- **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`.
|
|
494
|
+
- **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.
|
|
495
|
+
- **Moving:** three models, chosen by the callback.
|
|
496
|
+
- `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).
|
|
497
|
+
- `onMoveItem(id, { start, end })`: the 2.x model, one call for every key press and every drop.
|
|
498
|
+
- `onResizeItem(id, { start, end })`: Alt+Shift+←/→ moves the end of a bar. Without it there is no resize and no hint offers one.
|
|
499
|
+
Moves snap to `step` (15 min on an hour axis, 1 h on a day axis, 1 day otherwise). `movable: false` pins a bar.
|
|
500
|
+
- **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`).
|
|
501
|
+
- **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.
|
|
502
|
+
- **Notice and counts:** `notice` is shown above the axis; `columnNotes` puts a note on the column of a day (`YYYY-MM-DD` in `timeZone`).
|
|
503
|
+
- **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.
|
|
504
|
+
- **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.
|
|
505
|
+
- **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.
|
|
506
|
+
- Pure helpers are exported for tests and server code: `timelineTicks`, `timelineSubTicks`, `timelineRepeatedHourOffset`, `placeSpan`, `layoutLane`, `shiftSpan`, `snapToStep`, `pixelsToMs`, `defaultTimelineStep`, `timelineStepDelta`.
|
|
468
507
|
|
|
469
508
|
### Calendar model (zoned days, hour slots, lanes)
|
|
470
509
|
|
|
@@ -849,3 +888,113 @@ const v = useVirtualRows(rows, { rowHeight: 44, estimatedViewportHeight: 600, en
|
|
|
849
888
|
|
|
850
889
|
The density options no longer break a label inside a word at phone width ("Großzügig"); options
|
|
851
890
|
that do not fit one row wrap as whole options.
|
|
891
|
+
|
|
892
|
+
### Date fields, InlineEdit and other older gaps (2.36.0)
|
|
893
|
+
|
|
894
|
+
Older kit issues that TENSOR's triage still found blocking on 2026-10-08. Everything is additive.
|
|
895
|
+
|
|
896
|
+
#### DatePicker, DateTimePicker, DateRangePicker `mode="field"`
|
|
897
|
+
|
|
898
|
+
```tsx
|
|
899
|
+
<Field label={t('dueDate')} hint={t('workingDaysOnly')}>
|
|
900
|
+
<DatePicker value={due} onValueChange={setDue} name="due" locale={locale} weekStartsOn={1}
|
|
901
|
+
min="2026-11-02" max="2026-12-31" isUnavailable={isWeekend} />
|
|
902
|
+
</Field>
|
|
903
|
+
|
|
904
|
+
<Field label={t('starts')}>
|
|
905
|
+
<DateTimePicker value={starts} onValueChange={setStarts} timeZone="Europe/Vienna" minuteStep={15} />
|
|
906
|
+
</Field>
|
|
907
|
+
|
|
908
|
+
<DateRangePicker mode="field" value={period} onChange={setPeriod} label={t('period')} locale={locale} />
|
|
909
|
+
```
|
|
910
|
+
|
|
911
|
+
- **`DatePicker`** is a text input that shows the date and reads a typed one, with a button that
|
|
912
|
+
opens one month in a popover. The value is ISO `YYYY-MM-DD` or `null`; `name` submits it in a
|
|
913
|
+
hidden input whatever the field shows.
|
|
914
|
+
- **Typing:** read on Enter and when focus leaves. ISO is always accepted; otherwise three numbers
|
|
915
|
+
in the locale's order (`3.4.2026` is 3 April in German, `3/4/2026` is 4 March in en-US). Text
|
|
916
|
+
that is not a date, or a date outside `min` / `max` / `isUnavailable`, marks the field invalid,
|
|
917
|
+
says why, and leaves `value` alone (`onInputProblem` tells a form). Clearing the field sets
|
|
918
|
+
`null`.
|
|
919
|
+
- **Format:** the locale's numeric date by default. `formatDate` changes what the field writes;
|
|
920
|
+
pass `parseDate` with it when your format is not three numbers, and a `placeholder` so the
|
|
921
|
+
message can name the shape.
|
|
922
|
+
- **Calendar keys:** arrows by day and week, Home / End to the ends of the week, PageUp / PageDown
|
|
923
|
+
by month, with Shift by year. Focus never leaves `min` / `max`; a day refused by `isUnavailable`
|
|
924
|
+
can take focus and is read out, but cannot be chosen. ArrowDown in the field opens the
|
|
925
|
+
calendar; Escape closes it, returns focus to the field and does not close a drawer around it.
|
|
926
|
+
- **`DateTimePicker`** adds a time input. The value is `YYYY-MM-DDTHH:mm`, the shape
|
|
927
|
+
`datetime-local` holds, so it replaces that input without changing what a form submits. The
|
|
928
|
+
zone the wall clock is in is shown beside the time and is its description: `timeZone` shows
|
|
929
|
+
that zone's short name on the chosen day (it follows daylight saving), `timeZoneLabel` replaces
|
|
930
|
+
the text, and without either the viewer's own zone is shown. The value carries no zone.
|
|
931
|
+
- **`DateRangePicker mode="field"`** is a trigger that shows the range and opens the months in a
|
|
932
|
+
popover; it closes when the range has both ends. `DateRangePicker` also takes `weekStartsOn`.
|
|
933
|
+
- **Words:** `UixLabelsProvider` `datePicker`, `dateTimePicker` and the three new
|
|
934
|
+
`dateRangePicker` keys (`placeholder`, `fieldStart`, `fieldRange`).
|
|
935
|
+
- **Helpers:** `parseDateInput`, `formatDateKey`, `datePattern`, `dateOrder`, `isValidDateKey`,
|
|
936
|
+
`splitDateTime`, `joinDateTime`, `timeZoneName`.
|
|
937
|
+
|
|
938
|
+
#### InlineEdit
|
|
939
|
+
|
|
940
|
+
```tsx
|
|
941
|
+
<DescriptionItem term={t('title')}>
|
|
942
|
+
<InlineEdit label={t('title')} value={record.title} onSave={(next) => api.rename(record.id, next)}
|
|
943
|
+
validate={(next) => (next.trim() ? undefined : t('titleRequired'))} />
|
|
944
|
+
</DescriptionItem>
|
|
945
|
+
```
|
|
946
|
+
|
|
947
|
+
The value is a button named by the value and "Edit {label}". Activating it swaps in an editor with
|
|
948
|
+
Save and Cancel: Enter saves (Ctrl/⌘+Enter in `multiline`), Escape cancels without reaching a
|
|
949
|
+
drawer around it, and nothing is saved on blur. A promise from `onSave` shows the busy state; a
|
|
950
|
+
rejection keeps the draft and shows the reason. Afterwards focus is back on the value.
|
|
951
|
+
`renderView` and `renderEditor` replace either half (a `Select`, a `DatePicker`); `disabled`
|
|
952
|
+
renders plain text. Words: `labels` or `UixLabelsProvider` `inlineEdit`.
|
|
953
|
+
|
|
954
|
+
#### RadioCard
|
|
955
|
+
|
|
956
|
+
```tsx
|
|
957
|
+
<RadioGroup variant="card" label={t('rollout')}>
|
|
958
|
+
<RadioCard name="rollout" value="team" title={t('oneTeam')} description={t('oneTeamHint')}
|
|
959
|
+
checked={rollout === 'team'} onChange={() => setRollout('team')} />
|
|
960
|
+
…
|
|
961
|
+
</RadioGroup>
|
|
962
|
+
```
|
|
963
|
+
|
|
964
|
+
A native radio inside a card: one tab stop for the group, arrow keys choose, the whole card is
|
|
965
|
+
the target. The choice is a border and a check mark, not colour alone. `media` puts a preview
|
|
966
|
+
above the text. The grid wraps to one column on a phone.
|
|
967
|
+
|
|
968
|
+
#### Tree loads children on expand
|
|
969
|
+
|
|
970
|
+
```tsx
|
|
971
|
+
<Tree nodes={roots} loadChildren={(node) => api.org.children(node.id)} /> // roots: { id, label, hasChildren: true }
|
|
972
|
+
```
|
|
973
|
+
|
|
974
|
+
A node with `hasChildren` and no `children` is expandable. The first time it is expanded
|
|
975
|
+
`loadChildren` runs: the node is `aria-busy` and shows a loading row; a rejection shows an error
|
|
976
|
+
row whose Enter, Space or click retries. Focus follows the row that replaces the one it was on,
|
|
977
|
+
and lands on the node once its children are there. The tree keeps the loaded children; pass them
|
|
978
|
+
back in `nodes` when you own the data. Words: `labels` or `UixLabelsProvider` `tree`.
|
|
979
|
+
|
|
980
|
+
#### Avatar presence
|
|
981
|
+
|
|
982
|
+
```tsx
|
|
983
|
+
<Avatar presence="busy" src={user.photo} alt={user.name} /> <PresenceDot presence="away" />
|
|
984
|
+
```
|
|
985
|
+
|
|
986
|
+
`presence` is `online` (a filled dot), `busy` (a dot with a bar), `away` and `offline` (two
|
|
987
|
+
rings): a shape as well as a colour, and the state in visually hidden text (`presenceLabels`
|
|
988
|
+
translates it). `status` is still `presence="online"`. `PresenceDot` is the dot alone, named by
|
|
989
|
+
its state (`label={null}` makes it decorative). Fixed: the dot was clipped to a quarter by the avatar's round
|
|
990
|
+
mask; an avatar with a dot no longer clips it.
|
|
991
|
+
|
|
992
|
+
#### EntityPicker and CommandPalette words
|
|
993
|
+
|
|
994
|
+
- `EntityPicker onSearch` may resolve `{ options, hasMore: true }` when its list was cut off: the
|
|
995
|
+
list ends with a row that says more match (not an option; arrow keys skip it) and the polite
|
|
996
|
+
status says so too. Below `minQueryLength` the open list shows "Type at least {count}
|
|
997
|
+
characters." Both, and every other word, come from `labels` or `UixLabelsProvider`
|
|
998
|
+
`entityPicker`. `SearchSuggest` has the two slots these use: `idle` and `note`.
|
|
999
|
+
- `CommandPalette` announces its result count through `UixLabelsProvider` `commandPalette`
|
|
1000
|
+
(`resultsOne`, `resultsMany`, `{count}`) or a `resultsLabel(count)` prop; English is unchanged.
|
|
@@ -97,6 +97,12 @@ interface SchedulingCalendarEntry {
|
|
|
97
97
|
allDay?: boolean;
|
|
98
98
|
/** `false` keeps this entry where it is when the grid allows moves (`canMove`). */
|
|
99
99
|
movable?: boolean;
|
|
100
|
+
/**
|
|
101
|
+
* Draws the item with more or less weight than its neighbours, for example the partners of
|
|
102
|
+
* the item the user picked (`'highlight'`) against everything else (`'dim'`). It is a line
|
|
103
|
+
* weight and a quieter text colour, never a hue.
|
|
104
|
+
*/
|
|
105
|
+
emphasis?: 'highlight' | 'dim';
|
|
100
106
|
}
|
|
101
107
|
interface SchedulingCalendarOverlay {
|
|
102
108
|
id: string;
|
|
@@ -123,7 +129,11 @@ interface SchedulingCalendarDay {
|
|
|
123
129
|
count: number;
|
|
124
130
|
/** The "+N" of the cell. 0 shows none. */
|
|
125
131
|
overflowCount: number;
|
|
126
|
-
/**
|
|
132
|
+
/**
|
|
133
|
+
* The accessible text of the count, e.g. "7 items, 2 need review". The day's button is named
|
|
134
|
+
* with the date and this text (`labels.dayName`), so a screen reader hears the date, the count
|
|
135
|
+
* and the highest signal in one go.
|
|
136
|
+
*/
|
|
127
137
|
label?: string;
|
|
128
138
|
markers?: SchedulingMarker[];
|
|
129
139
|
}
|
|
@@ -193,6 +203,8 @@ interface SchedulingCalendarLabels {
|
|
|
193
203
|
moreSpans?: string;
|
|
194
204
|
/** Accessible name of that control. `{count}` and `{date}` (the first day with a hidden span). */
|
|
195
205
|
moreSpansLabel?: string;
|
|
206
|
+
/** The name of a day's button when `days[date].label` is given. `{date}` and `{label}` placeholders; use `'{label}'` if your labels already say the date. */
|
|
207
|
+
dayName?: string;
|
|
196
208
|
/** The accessible text of a day's count when `days[date].label` is not given. `{count}` placeholder. */
|
|
197
209
|
dayCount?: string;
|
|
198
210
|
states: Record<SchedulingEntryState, string>;
|
|
@@ -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 };
|