@nsaa/core 0.1.0-alpha.5 → 0.1.0-alpha.6

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.
@@ -5,9 +5,9 @@ import { Locale, Matcher } from "react-day-picker";
5
5
  export type DateRangePickerLabels = {
6
6
  /** Trigger text when there is no range. */
7
7
  placeholder: string;
8
- /** The presetsOnly option that opens the calendar. */
8
+ /** The radio for a range that matches no preset; in presetsOnly it opens the calendar. */
9
9
  custom: string;
10
- /** The presets listbox's name. */
10
+ /** The presets radio group's name. */
11
11
  presets: string;
12
12
  /** The panel's name (Popover) and the Drawer's title. */
13
13
  title: string;
@@ -10,11 +10,13 @@ import { localeWeekStart, matchPreset, presets, todayIn } from "./presets.js";
10
10
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
11
11
  import * as React from "react";
12
12
  import { useReducedMotion, useReducedMotionConfig } from "motion/react";
13
+ import { Radio } from "@base-ui/react/radio";
14
+ import { RadioGroup } from "@base-ui/react/radio-group";
13
15
  // src/date-range-picker/date-range-picker.tsx
14
16
  const DEFAULT_LABELS = {
15
17
  placeholder: "Pick a date range",
16
18
  custom: "Custom…",
17
- presets: "Presets",
19
+ presets: "Period",
18
20
  title: "Date range"
19
21
  };
20
22
  const DEFAULT_PRESETS = Object.values(presets);
@@ -90,55 +92,28 @@ function useMedia(query) {
90
92
  return () => list.removeEventListener("change", onChange);
91
93
  }, () => window.matchMedia(query).matches, () => false);
92
94
  }
93
- /** Presets as a listbox: roving focus, arrows (along the orientation, RTL-aware), Home, End, Enter. */
94
- function PresetList({ options, selectedId, orientation, label, onPick }) {
95
- const baseId = React.useId();
96
- const refs = React.useRef([]);
97
- const selected = options.findIndex((o) => o.id === selectedId);
98
- const [active, setActive] = React.useState(Math.max(selected, 0));
99
- const move = (index) => {
100
- setActive(index);
101
- refs.current[index]?.focus();
102
- };
103
- const onKeyDown = (event) => {
104
- const rtl = event.currentTarget.closest("[dir]")?.getAttribute("dir") === "rtl";
105
- const vertical = orientation === "vertical";
106
- const next = vertical ? "ArrowDown" : rtl ? "ArrowLeft" : "ArrowRight";
107
- const prev = vertical ? "ArrowUp" : rtl ? "ArrowRight" : "ArrowLeft";
108
- const last = options.length - 1;
109
- const option = options[active];
110
- if (event.key === next) move(Math.min(active + 1, last));
111
- else if (event.key === prev) move(Math.max(active - 1, 0));
112
- else if (event.key === "Home") move(0);
113
- else if (event.key === "End") move(last);
114
- else if (event.key === "Enter" || event.key === " ") {
115
- if (option && !option.disabled) onPick(option.id);
116
- } else return;
117
- event.preventDefault();
118
- };
119
- return /* @__PURE__ */ jsx("div", {
120
- role: "listbox",
95
+ /**
96
+ * Presets on Base UI Radio Group (#159): one tab stop, arrows move the checked radio. The checked
97
+ * radio is only a draft (the calendar previews it); a click, Space (Base UI turns it into a click)
98
+ * or Enter applies it.
99
+ */
100
+ function PresetList({ options, value, orientation, label, onDraft, onPick }) {
101
+ return /* @__PURE__ */ jsx(RadioGroup, {
102
+ value: value ?? null,
103
+ onValueChange: (next) => onDraft(next),
121
104
  "aria-label": label,
122
- "aria-orientation": orientation,
123
105
  "data-slot": "date-range-picker-presets",
124
106
  "data-orientation": orientation,
125
107
  className: cn("flex gap-1 p-2", orientation === "vertical" ? "w-40 shrink-0 flex-col" : "overflow-x-auto border-b border-border"),
126
- children: options.map((option, index) => /* @__PURE__ */ jsx("div", {
127
- ref: (el) => {
128
- refs.current[index] = el;
129
- },
130
- id: `${baseId}-${option.id}`,
131
- role: "option",
132
- tabIndex: index === active ? 0 : -1,
133
- "aria-selected": option.id === selectedId,
134
- "aria-disabled": option.disabled || void 0,
108
+ children: options.map((option) => /* @__PURE__ */ jsx(Radio.Root, {
109
+ value: option.id,
110
+ disabled: option.disabled,
135
111
  "data-slot": "date-range-picker-preset",
136
- onFocus: () => setActive(index),
137
- onKeyDown,
138
- onClick: () => {
139
- if (!option.disabled) onPick(option.id);
112
+ onClick: () => onPick(option.id),
113
+ onKeyDown: (event) => {
114
+ if (event.key === "Enter") onPick(option.id);
140
115
  },
141
- className: cn("flex cursor-default items-center rounded-md text-sm whitespace-nowrap text-foreground outline-hidden select-none hover:bg-muted focus-visible:ring-3 focus-visible:ring-ring/50 aria-disabled:pointer-events-none aria-disabled:opacity-50 aria-selected:bg-muted aria-selected:font-medium", orientation === "vertical" ? "min-h-8 px-2 pointer-coarse:min-h-11" : "h-7 shrink-0 border border-border px-2.5 pointer-coarse:h-11"),
116
+ className: cn("flex cursor-default items-center rounded-md text-start text-sm whitespace-nowrap text-foreground outline-hidden select-none hover:bg-muted focus-visible:ring-3 focus-visible:ring-ring/50 data-checked:bg-muted data-checked:font-medium data-disabled:pointer-events-none data-disabled:opacity-50", orientation === "vertical" ? "min-h-8 px-2 pointer-coarse:min-h-11" : "h-7 shrink-0 border border-border px-2.5 pointer-coarse:h-11"),
142
117
  children: option.label
143
118
  }, option.id))
144
119
  });
@@ -154,6 +129,7 @@ function DateRangePicker({ value: valueProp, defaultValue = null, onValueChange,
154
129
  const [open, setOpen] = React.useState(false);
155
130
  const [view, setView] = React.useState("calendar");
156
131
  const [draft, setDraft] = React.useState(null);
132
+ const [presetDraft, setPresetDraft] = React.useState();
157
133
  const [axes, setAxes] = React.useState({});
158
134
  const [announcement, setAnnouncement] = React.useState("");
159
135
  const wide = useContainerWide(rootRef);
@@ -197,6 +173,7 @@ function DateRangePicker({ value: valueProp, defaultValue = null, onValueChange,
197
173
  setView(presetsOnly ? "list" : "calendar");
198
174
  }
199
175
  setDraft(null);
176
+ setPresetDraft(next ? matched?.id ?? CUSTOM : void 0);
200
177
  setOpen(next);
201
178
  };
202
179
  const apply = (range, picked) => {
@@ -214,6 +191,7 @@ function DateRangePicker({ value: valueProp, defaultValue = null, onValueChange,
214
191
  };
215
192
  const onDay = (_range, day) => {
216
193
  clearTimeout(timer.current);
194
+ setPresetDraft(CUSTOM);
217
195
  if (!draft || draft.to) {
218
196
  setDraft({ from: day });
219
197
  return;
@@ -239,10 +217,20 @@ function DateRangePicker({ value: valueProp, defaultValue = null, onValueChange,
239
217
  };
240
218
  });
241
219
  const pickPreset = (presetId) => {
242
- if (presetId === CUSTOM) return setView("calendar");
220
+ if (presetId === CUSTOM) {
221
+ if (presetsOnly) return setView("calendar");
222
+ panelRef.current?.querySelector("button[data-day][tabindex=\"0\"]")?.focus();
223
+ return;
224
+ }
243
225
  const preset = presetList.find((p) => p.id === presetId);
244
226
  if (preset) apply(preset.range(today, options), preset);
245
227
  };
228
+ const presetChoices = [...presetOptions, {
229
+ id: CUSTOM,
230
+ label: labels.custom
231
+ }];
232
+ const previewPreset = presetList.find((p) => p.id === presetDraft);
233
+ const previewed = previewPreset ? previewPreset.range(today, options) : void 0;
246
234
  const matchers = [
247
235
  ...min ? [{ before: min }] : [],
248
236
  ...max ? [{ after: max }] : [],
@@ -252,13 +240,11 @@ function DateRangePicker({ value: valueProp, defaultValue = null, onValueChange,
252
240
  const defaultMonth = months === 2 ? new Date(anchor.getFullYear(), anchor.getMonth() - 1, 1) : anchor;
253
241
  const narrow = months === 1;
254
242
  const panel = view === "list" ? /* @__PURE__ */ jsx(PresetList, {
255
- options: [...presetOptions, {
256
- id: CUSTOM,
257
- label: labels.custom
258
- }],
259
- selectedId: matched?.id ?? (value ? CUSTOM : void 0),
243
+ options: presetChoices,
244
+ value: presetDraft,
260
245
  orientation: "vertical",
261
246
  label: labels.presets,
247
+ onDraft: setPresetDraft,
262
248
  onPick: pickPreset
263
249
  }) : /* @__PURE__ */ jsxs("div", {
264
250
  ref: panelRef,
@@ -267,16 +253,17 @@ function DateRangePicker({ value: valueProp, defaultValue = null, onValueChange,
267
253
  children: [!presetsOnly && presetOptions.length > 0 && /* @__PURE__ */ jsx("div", {
268
254
  className: cn(!narrow && "border-e border-border"),
269
255
  children: /* @__PURE__ */ jsx(PresetList, {
270
- options: presetOptions,
271
- selectedId: matched?.id,
256
+ options: presetChoices,
257
+ value: presetDraft,
272
258
  orientation: narrow ? "horizontal" : "vertical",
273
259
  label: labels.presets,
260
+ onDraft: setPresetDraft,
274
261
  onPick: pickPreset
275
262
  })
276
263
  }), /* @__PURE__ */ jsx(Calendar, {
277
264
  mode: "range",
278
265
  locale: calendarLocale,
279
- selected: draft ?? value ?? void 0,
266
+ selected: draft ?? previewed ?? value ?? void 0,
280
267
  onSelect: onDay,
281
268
  numberOfMonths: months,
282
269
  showOutsideDays: months === 1,
@@ -22,7 +22,7 @@ function ProgressTrack({ className, ...props }) {
22
22
  function ProgressIndicator({ className, ...props }) {
23
23
  return /* @__PURE__ */ jsx(Progress.Indicator, {
24
24
  "data-slot": "progress-indicator",
25
- className: cn("h-full bg-primary transition-all", className),
25
+ className: cn("h-full bg-(--nsaa-color-brand-foreground) transition-all", className),
26
26
  ...props
27
27
  });
28
28
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nsaa/core",
3
- "version": "0.1.0-alpha.5",
3
+ "version": "0.1.0-alpha.6",
4
4
  "description": "Nsaa core components: accessible, token-driven, animated to the Nsaa feel spec.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -53,7 +53,7 @@
53
53
  "tailwind-merge": "^3.7.0",
54
54
  "tailwind-variants": "^3.3.1",
55
55
  "uisfx": "0.4.0",
56
- "@nsaa/tokens": "^0.1.0-alpha.5"
56
+ "@nsaa/tokens": "^0.1.0-alpha.6"
57
57
  },
58
58
  "devDependencies": {
59
59
  "@testing-library/jest-dom": "^7.0.1",