@passui/react 0.1.45 → 0.1.46

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 (140) hide show
  1. package/README.md +57 -46
  2. package/dist/types/src/components/accordion/accordion.d.ts +1 -1
  3. package/dist/types/src/components/breadcrumb/breadcrumb.d.ts +1 -1
  4. package/dist/types/src/components/inline-disclosure/inline-disclosure.d.ts +1 -1
  5. package/dist/types/src/components/popover/anchored-surface.d.ts +5 -1
  6. package/dist/types/src/components/popover/index.d.ts +41 -0
  7. package/dist/types/src/components/popover/popover-content.d.ts +32 -0
  8. package/dist/types/src/components/popover/popover-root.d.ts +22 -0
  9. package/dist/types/src/components/popover/popover-trigger.d.ts +23 -0
  10. package/dist/types/src/components/stepper/rolling-digit.d.ts +1 -1
  11. package/dist/types/src/components/table/table.d.ts +1 -1
  12. package/dist/types/src/components/view-controller/view-controller.d.ts +2 -2
  13. package/dist/types/src/index.d.ts +1 -1
  14. package/dist/types/src/machines/popover.machine.d.ts +30 -0
  15. package/package.json +307 -307
  16. package/src/appearance/index.ts +2 -2
  17. package/src/appearance/platform.tsx +76 -60
  18. package/src/appearance/theme.tsx +409 -377
  19. package/src/components/accordion/accordion.tsx +58 -49
  20. package/src/components/ambient/ambient.tsx +25 -25
  21. package/src/components/ambient/index.ts +1 -1
  22. package/src/components/aspect-ratio/aspect-ratio.tsx +21 -16
  23. package/src/components/breadcrumb/breadcrumb.tsx +63 -57
  24. package/src/components/button/button-styles.ts +39 -32
  25. package/src/components/button/button.tsx +91 -84
  26. package/src/components/button/icon-button.tsx +27 -25
  27. package/src/components/button/pending-spinner.tsx +8 -8
  28. package/src/components/button/use-pending-indicator.ts +11 -11
  29. package/src/components/chip/chip.tsx +35 -30
  30. package/src/components/code-block/code-block.tsx +40 -39
  31. package/src/components/combo-box/combo-box.tsx +5 -5
  32. package/src/components/command/command.tsx +124 -94
  33. package/src/components/compact-date-picker/calendar-month.ts +75 -53
  34. package/src/components/compact-date-picker/compact-date-picker.tsx +308 -289
  35. package/src/components/compact-time-picker/compact-time-picker.tsx +288 -281
  36. package/src/components/compact-time-picker/time-value.ts +32 -29
  37. package/src/components/content-unavailable-view/content-unavailable-view.tsx +64 -48
  38. package/src/components/content-view/content-view.tsx +168 -159
  39. package/src/components/content-view/index.ts +1 -1
  40. package/src/components/context-menu/conceal-menu-source.ts +25 -25
  41. package/src/components/context-menu/context-menu-host.tsx +439 -402
  42. package/src/components/context-menu/context-menu-store.ts +125 -123
  43. package/src/components/context-menu/menu-navigation.ts +25 -18
  44. package/src/components/context-menu/menu-position.ts +33 -29
  45. package/src/components/context-menu/menu-reveal.ts +32 -32
  46. package/src/components/context-menu/use-context-menu.ts +63 -56
  47. package/src/components/conversation/conversation.tsx +265 -263
  48. package/src/components/conversation/index.ts +1 -1
  49. package/src/components/date-picker/date-picker.tsx +95 -94
  50. package/src/components/date-picker/date-value.ts +20 -10
  51. package/src/components/dialog/dialog-host.tsx +105 -103
  52. package/src/components/dialog/dialog-store.ts +37 -36
  53. package/src/components/dialog/dialog.tsx +192 -179
  54. package/src/components/dialog/index.ts +1 -1
  55. package/src/components/disclosure-group/disclosure-group.tsx +93 -86
  56. package/src/components/divider/divider.tsx +24 -22
  57. package/src/components/inline-disclosure/inline-disclosure.tsx +40 -36
  58. package/src/components/keyboard-shortcut/keyboard-shortcut.tsx +23 -19
  59. package/src/components/labeled-content/labeled-content.tsx +68 -68
  60. package/src/components/locale-switcher/index.ts +1 -1
  61. package/src/components/locale-switcher/locale-switcher.tsx +279 -251
  62. package/src/components/menu/menu.tsx +75 -58
  63. package/src/components/morph/index.ts +2 -2
  64. package/src/components/morph/morph.tsx +227 -201
  65. package/src/components/morph/use-morph-disclosure.ts +19 -19
  66. package/src/components/one-time-code/one-time-code.tsx +216 -200
  67. package/src/components/picker/picker-trigger.ts +16 -13
  68. package/src/components/picker/picker.tsx +371 -345
  69. package/src/components/popover/anchored-surface.tsx +253 -229
  70. package/src/components/popover/index.ts +50 -0
  71. package/src/components/popover/popover-content.tsx +334 -0
  72. package/src/components/popover/popover-fit.ts +58 -53
  73. package/src/components/popover/popover-root.tsx +97 -0
  74. package/src/components/popover/popover-trigger.tsx +71 -0
  75. package/src/components/profile-image/profile-image.tsx +80 -64
  76. package/src/components/progress-view/progress-view.tsx +61 -59
  77. package/src/components/progress-view/use-liquid-progress.ts +38 -38
  78. package/src/components/radio-group/radio-group.tsx +71 -68
  79. package/src/components/search-field/expanding-search-field.tsx +191 -177
  80. package/src/components/search-field/search-field.tsx +59 -50
  81. package/src/components/search-list/search-list.tsx +246 -236
  82. package/src/components/section/section.tsx +58 -58
  83. package/src/components/secure-field/secure-field.tsx +3 -3
  84. package/src/components/segmented-control/segmented-control.tsx +136 -136
  85. package/src/components/sheet/modal-environment.ts +71 -64
  86. package/src/components/sheet/sheet-portal.tsx +58 -53
  87. package/src/components/sheet/sheet.tsx +146 -141
  88. package/src/components/sheet/use-sheet-focus.ts +97 -82
  89. package/src/components/sidebar/sidebar.tsx +78 -78
  90. package/src/components/skeleton/skeleton.tsx +30 -21
  91. package/src/components/slider/slider.tsx +113 -104
  92. package/src/components/slosh-slider/liquid-step.ts +32 -31
  93. package/src/components/slosh-slider/slosh-slider.tsx +265 -228
  94. package/src/components/stat-tile/index.ts +1 -1
  95. package/src/components/stat-tile/stat-tile.tsx +79 -68
  96. package/src/components/status-indicator/status-indicator.tsx +40 -37
  97. package/src/components/stepper/rolling-digit.tsx +82 -68
  98. package/src/components/stepper/stepper.tsx +122 -113
  99. package/src/components/swatch-picker/swatch-picker.tsx +120 -120
  100. package/src/components/tab-bar/tab-bar.tsx +149 -148
  101. package/src/components/tab-view/tab-view.tsx +140 -137
  102. package/src/components/table/table.tsx +117 -98
  103. package/src/components/text-editor/text-editor.tsx +34 -29
  104. package/src/components/text-field/text-field.tsx +182 -180
  105. package/src/components/toast/toast-store.ts +127 -115
  106. package/src/components/toast/toaster.tsx +361 -347
  107. package/src/components/toggle/toggle.tsx +297 -282
  108. package/src/components/tooltip/tooltip-host.tsx +157 -150
  109. package/src/components/view-controller/index.ts +1 -1
  110. package/src/components/view-controller/view-controller.tsx +64 -53
  111. package/src/components/wake-slider/wake-slider.tsx +220 -205
  112. package/src/hooks/scroll/minimize-decision.ts +11 -7
  113. package/src/hooks/use-anchored-disclosure.ts +19 -19
  114. package/src/hooks/use-anchored-placement.ts +29 -25
  115. package/src/hooks/use-dismiss.ts +33 -29
  116. package/src/hooks/use-floating-dismiss.ts +24 -22
  117. package/src/hooks/use-media-query.ts +67 -60
  118. package/src/hooks/use-minimize-on-scroll.ts +69 -60
  119. package/src/hooks/use-visual-viewport.ts +63 -63
  120. package/src/hooks/viewport/types.ts +16 -16
  121. package/src/hooks/viewport/viewport-metrics.ts +16 -10
  122. package/src/icons/icons.tsx +440 -328
  123. package/src/index.ts +91 -91
  124. package/src/lib/anchored-placement.ts +70 -65
  125. package/src/lib/class-names.ts +14 -13
  126. package/src/lib/clipboard.ts +8 -7
  127. package/src/lib/date.ts +61 -60
  128. package/src/lib/floating-layers.ts +39 -24
  129. package/src/lib/haptics.ts +10 -10
  130. package/src/lib/language.ts +9 -9
  131. package/src/lib/range-value.ts +27 -21
  132. package/src/lib/screen-edges.ts +33 -30
  133. package/src/lib/shapes.ts +38 -34
  134. package/src/machines/popover.machine.ts +51 -0
  135. package/src/motion/transitions.ts +46 -43
  136. package/src/styles/code-block.css +69 -43
  137. package/src/styles/motion-controls.css +393 -191
  138. package/src/tokens.ts +1 -1
  139. package/dist/types/src/components/popover/popover.d.ts +0 -21
  140. package/src/components/popover/popover.tsx +0 -334
@@ -1,58 +1,70 @@
1
- import { useAnchoredDisclosure } from "../../hooks/use-anchored-disclosure"
2
- import { AnchoredSurface } from "../popover/anchored-surface"
3
- import { type KeyboardEvent, useEffect, useId, useMemo, useRef, useState } from "react"
4
- import { createPortal } from "react-dom"
5
- import { CalendarIcon, ChevronLeftIcon, ChevronRightIcon } from "../../icons/icons"
6
- import { cn } from "../../lib/class-names"
7
- import { haptic } from "../../lib/haptics"
8
- import { IconButton } from "../button/icon-button"
9
- import { pickerTriggerStyle, type PickerSize } from "../picker/picker-trigger"
1
+ import { useAnchoredDisclosure } from "../../hooks/use-anchored-disclosure";
2
+ import { AnchoredSurface } from "../popover/anchored-surface";
10
3
  import {
11
- addDays,
12
- addMonths,
13
- type CalendarDay,
14
- firstWeekday,
15
- formatDay,
16
- monthGrid,
17
- parseDay,
18
- withinBounds,
19
- } from "./calendar-month"
4
+ type KeyboardEvent,
5
+ useEffect,
6
+ useId,
7
+ useMemo,
8
+ useRef,
9
+ useState,
10
+ } from "react";
11
+ import { createPortal } from "react-dom";
12
+ import {
13
+ CalendarIcon,
14
+ ChevronLeftIcon,
15
+ ChevronRightIcon,
16
+ } from "../../icons/icons";
17
+ import { cn } from "../../lib/class-names";
18
+ import { haptic } from "../../lib/haptics";
19
+ import { IconButton } from "../button/icon-button";
20
+ import { pickerTriggerStyle, type PickerSize } from "../picker/picker-trigger";
21
+ import {
22
+ addDays,
23
+ addMonths,
24
+ type CalendarDay,
25
+ firstWeekday,
26
+ formatDay,
27
+ monthGrid,
28
+ parseDay,
29
+ withinBounds,
30
+ } from "./calendar-month";
20
31
 
21
32
  export interface CompactDatePickerProps {
22
- /** `YYYY-MM-DD`, or "" when no date is chosen. */
23
- value: string
24
- onChange: (value: string) => void
25
- /** Names the button and the calendar for assistive technology. */
26
- label: string
27
- locale: string
28
- min?: string
29
- max?: string
30
- /** Shown when no date is chosen. */
31
- placeholder?: string
32
- labels: {
33
- previousMonth: string
34
- nextMonth: string
35
- /** Offers to remove the date, for optional dates. */
36
- clear?: string
37
- }
38
- className?: string
39
- /** Prevents opening the date panel. @default false */
40
- disabled?: boolean
41
- /** Trigger button density; matches Picker's sm | md | lg. @default "md" */
42
- size?: PickerSize
33
+ /** `YYYY-MM-DD`, or "" when no date is chosen. */
34
+ value: string;
35
+ onChange: (value: string) => void;
36
+ /** Names the button and the calendar for assistive technology. */
37
+ label: string;
38
+ locale: string;
39
+ min?: string;
40
+ max?: string;
41
+ /** Shown when no date is chosen. */
42
+ placeholder?: string;
43
+ labels: {
44
+ previousMonth: string;
45
+ nextMonth: string;
46
+ /** Offers to remove the date, for optional dates. */
47
+ clear?: string;
48
+ };
49
+ className?: string;
50
+ /** Prevents opening the date panel. @default false */
51
+ disabled?: boolean;
52
+ /** Trigger button density; matches Picker's sm | md | lg. @default "md" */
53
+ size?: PickerSize;
43
54
  }
44
55
 
45
- const WIDTH = 308
56
+ const WIDTH = 308;
46
57
 
47
- const toDate = ({ year, month, day }: CalendarDay) => new Date(Date.UTC(year, month - 1, day))
58
+ const toDate = ({ year, month, day }: CalendarDay) =>
59
+ new Date(Date.UTC(year, month - 1, day));
48
60
 
49
61
  function todayDay(): CalendarDay {
50
- const now = new Date()
51
- return {
52
- year: now.getFullYear(),
53
- month: now.getMonth() + 1,
54
- day: now.getDate(),
55
- }
62
+ const now = new Date();
63
+ return {
64
+ year: now.getFullYear(),
65
+ month: now.getMonth() + 1,
66
+ day: now.getDate(),
67
+ };
56
68
  }
57
69
 
58
70
  /**
@@ -61,258 +73,265 @@ function todayDay(): CalendarDay {
61
73
  * invoice. For dates people know by heart, like a birthday, use DatePicker.
62
74
  */
63
75
  export function CompactDatePicker({
64
- value,
65
- onChange,
66
- label,
67
- locale,
68
- min,
69
- max,
70
- placeholder,
71
- labels,
72
- className,
73
- disabled = false,
74
- size = "md",
76
+ value,
77
+ onChange,
78
+ label,
79
+ locale,
80
+ min,
81
+ max,
82
+ placeholder,
83
+ labels,
84
+ className,
85
+ disabled = false,
86
+ size = "md",
75
87
  }: CompactDatePickerProps) {
76
- const panel = useAnchoredDisclosure(disabled)
77
- const { open } = panel
78
- const trigger = useRef<HTMLButtonElement>(null)
79
- const calendarId = useId()
80
- const chosen = parseDay(value)
81
- const shown = chosen
82
- ? new Intl.DateTimeFormat(locale, {
83
- dateStyle: "medium",
84
- timeZone: "UTC",
85
- }).format(toDate(chosen))
86
- : (placeholder ?? "")
88
+ const panel = useAnchoredDisclosure(disabled);
89
+ const { open } = panel;
90
+ const trigger = useRef<HTMLButtonElement>(null);
91
+ const calendarId = useId();
92
+ const chosen = parseDay(value);
93
+ const shown = chosen
94
+ ? new Intl.DateTimeFormat(locale, {
95
+ dateStyle: "medium",
96
+ timeZone: "UTC",
97
+ }).format(toDate(chosen))
98
+ : (placeholder ?? "");
87
99
 
88
- const close = (restoreFocus: boolean) => {
89
- panel.close()
90
- if (restoreFocus) trigger.current?.focus({ preventScroll: true })
91
- }
100
+ const close = (restoreFocus: boolean) => {
101
+ panel.close();
102
+ if (restoreFocus) trigger.current?.focus({ preventScroll: true });
103
+ };
92
104
 
93
- return (
94
- <>
95
- <button
96
- ref={trigger}
97
- type="button"
98
- aria-label={chosen ? `${label}, ${shown}` : label}
99
- aria-haspopup="dialog"
100
- aria-expanded={open && !disabled}
101
- aria-controls={open && !disabled ? calendarId : undefined}
102
- disabled={disabled}
103
- onClick={() => panel.toggle()}
104
- className={pickerTriggerStyle(className, size)}
105
- >
106
- <span
107
- className={cn(
108
- "min-w-0 grow truncate tabular-nums",
109
- !chosen && "text-label-tertiary"
110
- )}
111
- >
112
- {shown}
113
- </span>
114
- <CalendarIcon size={16} className="shrink-0 text-label-tertiary" />
115
- </button>
116
- {typeof document !== "undefined" &&
117
- createPortal(
118
- panel.mounted && trigger.current ? (
119
- <MonthCalendar
120
- open={open}
121
- onExited={panel.onExited}
122
- id={calendarId}
123
- anchor={trigger.current}
124
- label={label}
125
- locale={locale}
126
- value={value}
127
- min={min}
128
- max={max}
129
- labels={labels}
130
- onChoose={(next) => {
131
- haptic("selection")
132
- onChange(next)
133
- close(true)
134
- }}
135
- onClose={close}
136
- />
137
- ) : null,
138
- document.body
139
- )}
140
- </>
141
- )
105
+ return (
106
+ <>
107
+ <button
108
+ ref={trigger}
109
+ type="button"
110
+ aria-label={chosen ? `${label}, ${shown}` : label}
111
+ aria-haspopup="dialog"
112
+ aria-expanded={open && !disabled}
113
+ aria-controls={open && !disabled ? calendarId : undefined}
114
+ disabled={disabled}
115
+ onClick={() => panel.toggle()}
116
+ className={pickerTriggerStyle(className, size)}
117
+ >
118
+ <span
119
+ className={cn(
120
+ "min-w-0 grow truncate tabular-nums",
121
+ !chosen && "text-label-tertiary",
122
+ )}
123
+ >
124
+ {shown}
125
+ </span>
126
+ <CalendarIcon size={16} className="text-label-tertiary shrink-0" />
127
+ </button>
128
+ {typeof document !== "undefined" &&
129
+ createPortal(
130
+ panel.mounted && trigger.current ? (
131
+ <MonthCalendar
132
+ open={open}
133
+ onExited={panel.onExited}
134
+ id={calendarId}
135
+ anchor={trigger.current}
136
+ label={label}
137
+ locale={locale}
138
+ value={value}
139
+ min={min}
140
+ max={max}
141
+ labels={labels}
142
+ onChoose={(next) => {
143
+ haptic("selection");
144
+ onChange(next);
145
+ close(true);
146
+ }}
147
+ onClose={close}
148
+ />
149
+ ) : null,
150
+ document.body,
151
+ )}
152
+ </>
153
+ );
142
154
  }
143
155
 
144
156
  function MonthCalendar({
145
- open,
146
- onExited,
147
- id,
148
- anchor,
149
- label,
150
- locale,
151
- value,
152
- min,
153
- max,
154
- labels,
155
- onChoose,
156
- onClose,
157
+ open,
158
+ onExited,
159
+ id,
160
+ anchor,
161
+ label,
162
+ locale,
163
+ value,
164
+ min,
165
+ max,
166
+ labels,
167
+ onChoose,
168
+ onClose,
157
169
  }: {
158
- open: boolean
159
- onExited: () => void
160
- id: string
161
- anchor: HTMLElement
162
- label: string
163
- locale: string
164
- value: string
165
- min?: string
166
- max?: string
167
- labels: CompactDatePickerProps["labels"]
168
- onChoose: (value: string) => void
169
- onClose: (restoreFocus: boolean) => void
170
+ open: boolean;
171
+ onExited: () => void;
172
+ id: string;
173
+ anchor: HTMLElement;
174
+ label: string;
175
+ locale: string;
176
+ value: string;
177
+ min?: string;
178
+ max?: string;
179
+ labels: CompactDatePickerProps["labels"];
180
+ onChoose: (value: string) => void;
181
+ onClose: (restoreFocus: boolean) => void;
170
182
  }) {
171
- const root = useRef<HTMLDivElement>(null)
172
- const today = todayDay()
173
- const [focused, setFocused] = useState<CalendarDay>(() => parseDay(value) ?? today)
174
- const firstDay = useMemo(() => firstWeekday(locale), [locale])
175
- const days = monthGrid(focused.year, focused.month, firstDay)
176
- const title = new Intl.DateTimeFormat(locale, {
177
- month: "long",
178
- year: "numeric",
179
- timeZone: "UTC",
180
- }).format(toDate({ ...focused, day: 1 }))
181
- const weekdays = days.slice(0, 7).map((day) =>
182
- new Intl.DateTimeFormat(locale, {
183
- weekday: "narrow",
184
- timeZone: "UTC",
185
- }).format(toDate(day))
186
- )
187
- const fullDate = new Intl.DateTimeFormat(locale, {
188
- dateStyle: "full",
189
- timeZone: "UTC",
190
- })
183
+ const root = useRef<HTMLDivElement>(null);
184
+ const today = todayDay();
185
+ const [focused, setFocused] = useState<CalendarDay>(
186
+ () => parseDay(value) ?? today,
187
+ );
188
+ const firstDay = useMemo(() => firstWeekday(locale), [locale]);
189
+ const days = monthGrid(focused.year, focused.month, firstDay);
190
+ const title = new Intl.DateTimeFormat(locale, {
191
+ month: "long",
192
+ year: "numeric",
193
+ timeZone: "UTC",
194
+ }).format(toDate({ ...focused, day: 1 }));
195
+ const weekdays = days.slice(0, 7).map((day) =>
196
+ new Intl.DateTimeFormat(locale, {
197
+ weekday: "narrow",
198
+ timeZone: "UTC",
199
+ }).format(toDate(day)),
200
+ );
201
+ const fullDate = new Intl.DateTimeFormat(locale, {
202
+ dateStyle: "full",
203
+ timeZone: "UTC",
204
+ });
191
205
 
192
- // The focused day takes keyboard focus as it moves.
193
- useEffect(() => {
194
- root.current
195
- ?.querySelector<HTMLButtonElement>(`[data-day="${formatDay(focused)}"]`)
196
- ?.focus()
197
- }, [focused])
206
+ // The focused day takes keyboard focus as it moves.
207
+ useEffect(() => {
208
+ root.current
209
+ ?.querySelector<HTMLButtonElement>(`[data-day="${formatDay(focused)}"]`)
210
+ ?.focus();
211
+ }, [focused]);
198
212
 
199
- const move = (next: CalendarDay) => setFocused(next)
213
+ const move = (next: CalendarDay) => setFocused(next);
200
214
 
201
- const onKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
202
- const rtl = getComputedStyle(event.currentTarget).direction === "rtl" ? -1 : 1
203
- const keys: Record<string, () => void> = {
204
- ArrowLeft: () => move(addDays(focused, -rtl)),
205
- ArrowRight: () => move(addDays(focused, rtl)),
206
- ArrowUp: () => move(addDays(focused, -7)),
207
- ArrowDown: () => move(addDays(focused, 7)),
208
- PageUp: () => move(addMonths(focused, event.shiftKey ? -12 : -1)),
209
- PageDown: () => move(addMonths(focused, event.shiftKey ? 12 : 1)),
210
- Escape: () => onClose(true),
211
- }
212
- const action = keys[event.key]
213
- if (!action) return
214
- event.preventDefault()
215
- event.stopPropagation()
216
- action()
217
- }
215
+ const onKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
216
+ const rtl =
217
+ getComputedStyle(event.currentTarget).direction === "rtl" ? -1 : 1;
218
+ const keys: Record<string, () => void> = {
219
+ ArrowLeft: () => move(addDays(focused, -rtl)),
220
+ ArrowRight: () => move(addDays(focused, rtl)),
221
+ ArrowUp: () => move(addDays(focused, -7)),
222
+ ArrowDown: () => move(addDays(focused, 7)),
223
+ PageUp: () => move(addMonths(focused, event.shiftKey ? -12 : -1)),
224
+ PageDown: () => move(addMonths(focused, event.shiftKey ? 12 : 1)),
225
+ Escape: () => onClose(true),
226
+ };
227
+ const action = keys[event.key];
228
+ if (!action) return;
229
+ event.preventDefault();
230
+ event.stopPropagation();
231
+ action();
232
+ };
218
233
 
219
- return (
220
- <AnchoredSurface
221
- open={open}
222
- onExited={onExited}
223
- ref={root}
224
- anchor={anchor}
225
- width={WIDTH}
226
- needed={360}
227
- onClose={onClose}
228
- id={id}
229
- role="dialog"
230
- aria-label={label}
231
- contentClassName="gap-2 p-3"
232
- onKeyDown={onKeyDown}
233
- >
234
- <div className="flex items-center justify-between gap-2 ps-2">
235
- <p className="text-headline font-semibold" aria-live="polite">
236
- {title}
237
- </p>
238
- <div className="flex items-center rtl:flex-row-reverse">
239
- <IconButton
240
- variant="ghost"
241
- size="icon-sm"
242
- label={labels.previousMonth}
243
- onClick={() => move(addMonths(focused, -1))}
244
- className="text-accent-text"
245
- >
246
- <ChevronLeftIcon size={20} />
247
- </IconButton>
248
- <IconButton
249
- variant="ghost"
250
- size="icon-sm"
251
- label={labels.nextMonth}
252
- onClick={() => move(addMonths(focused, 1))}
253
- className="text-accent-text"
254
- >
255
- <ChevronRightIcon size={20} />
256
- </IconButton>
257
- </div>
258
- </div>
259
- <div role="grid" aria-label={title} className="grid grid-cols-7 gap-y-0.5">
260
- <div role="row" className="contents">
261
- {weekdays.map((weekday, index) => (
262
- <span
263
- key={index}
264
- role="columnheader"
265
- className="pb-1 text-center text-caption2 font-semibold text-label-tertiary"
266
- >
267
- {weekday}
268
- </span>
269
- ))}
270
- </div>
271
- {days.map((day) => {
272
- const key = formatDay(day)
273
- const selected = key === value
274
- const isToday = key === formatDay(today)
275
- const isFocused = key === formatDay(focused)
276
- const allowed = withinBounds(key, min, max)
277
- return (
278
- <button
279
- key={key}
280
- type="button"
281
- role="gridcell"
282
- data-day={key}
283
- tabIndex={isFocused ? 0 : -1}
284
- disabled={!allowed}
285
- aria-selected={selected}
286
- aria-current={isToday ? "date" : undefined}
287
- aria-label={fullDate.format(toDate(day))}
288
- onClick={() => onChoose(key)}
289
- className={cn(
290
- "mx-auto flex size-10 items-center justify-center rounded-full text-body tabular-nums transition-colors",
291
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/70",
292
- selected
293
- ? "bg-accent font-semibold text-on-accent"
294
- : isToday
295
- ? "font-semibold text-accent-text hover:bg-fill/80"
296
- : day.inMonth
297
- ? "text-label hover:bg-fill/80"
298
- : "text-label-tertiary hover:bg-fill/60",
299
- !allowed && "opacity-30"
300
- )}
301
- >
302
- {day.day}
303
- </button>
304
- )
305
- })}
306
- </div>
307
- {labels.clear && value && (
308
- <button
309
- type="button"
310
- onClick={() => onChoose("")}
311
- className="self-center rounded-full px-3 py-1.5 text-subheadline text-accent-text hover:bg-fill/80"
312
- >
313
- {labels.clear}
314
- </button>
315
- )}
316
- </AnchoredSurface>
317
- )
234
+ return (
235
+ <AnchoredSurface
236
+ open={open}
237
+ onExited={onExited}
238
+ ref={root}
239
+ anchor={anchor}
240
+ width={WIDTH}
241
+ needed={360}
242
+ onClose={onClose}
243
+ id={id}
244
+ role="dialog"
245
+ aria-label={label}
246
+ contentClassName="gap-2 p-3"
247
+ onKeyDown={onKeyDown}
248
+ >
249
+ <div className="flex items-center justify-between gap-2 ps-2">
250
+ <p className="text-headline font-semibold" aria-live="polite">
251
+ {title}
252
+ </p>
253
+ <div className="flex items-center rtl:flex-row-reverse">
254
+ <IconButton
255
+ variant="ghost"
256
+ size="icon-sm"
257
+ label={labels.previousMonth}
258
+ onClick={() => move(addMonths(focused, -1))}
259
+ className="text-accent-text"
260
+ >
261
+ <ChevronLeftIcon size={20} />
262
+ </IconButton>
263
+ <IconButton
264
+ variant="ghost"
265
+ size="icon-sm"
266
+ label={labels.nextMonth}
267
+ onClick={() => move(addMonths(focused, 1))}
268
+ className="text-accent-text"
269
+ >
270
+ <ChevronRightIcon size={20} />
271
+ </IconButton>
272
+ </div>
273
+ </div>
274
+ <div
275
+ role="grid"
276
+ aria-label={title}
277
+ className="grid grid-cols-7 gap-y-0.5"
278
+ >
279
+ <div role="row" className="contents">
280
+ {weekdays.map((weekday, index) => (
281
+ <span
282
+ key={days[index].toString()}
283
+ role="columnheader"
284
+ className="text-caption2 text-label-tertiary pb-1 text-center font-semibold"
285
+ >
286
+ {weekday}
287
+ </span>
288
+ ))}
289
+ </div>
290
+ {days.map((day) => {
291
+ const key = formatDay(day);
292
+ const selected = key === value;
293
+ const isToday = key === formatDay(today);
294
+ const isFocused = key === formatDay(focused);
295
+ const allowed = withinBounds(key, min, max);
296
+ return (
297
+ <button
298
+ key={key}
299
+ type="button"
300
+ role="gridcell"
301
+ data-day={key}
302
+ tabIndex={isFocused ? 0 : -1}
303
+ disabled={!allowed}
304
+ aria-selected={selected}
305
+ aria-current={isToday ? "date" : undefined}
306
+ aria-label={fullDate.format(toDate(day))}
307
+ onClick={() => onChoose(key)}
308
+ className={cn(
309
+ "text-body mx-auto flex size-10 items-center justify-center rounded-full tabular-nums transition-colors",
310
+ "focus-visible:ring-accent/70 focus-visible:ring-2 focus-visible:outline-none",
311
+ selected
312
+ ? "bg-accent text-on-accent font-semibold"
313
+ : isToday
314
+ ? "text-accent-text hover:bg-fill/80 font-semibold"
315
+ : day.inMonth
316
+ ? "text-label hover:bg-fill/80"
317
+ : "text-label-tertiary hover:bg-fill/60",
318
+ !allowed && "opacity-30",
319
+ )}
320
+ >
321
+ {day.day}
322
+ </button>
323
+ );
324
+ })}
325
+ </div>
326
+ {labels.clear && value && (
327
+ <button
328
+ type="button"
329
+ onClick={() => onChoose("")}
330
+ className="text-subheadline text-accent-text hover:bg-fill/80 self-center rounded-full px-3 py-1.5"
331
+ >
332
+ {labels.clear}
333
+ </button>
334
+ )}
335
+ </AnchoredSurface>
336
+ );
318
337
  }