@nexub/react 6.1.0 → 6.2.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.
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "6.1.0",
2
+ "version": "6.2.0",
3
3
  "standard": "WCAG 2.2 Level AA",
4
4
  "method": {
5
5
  "automated": "axe-core rules tagged wcag2a, wcag2aa, wcag21a, wcag21aa and wcag22aa, run on every Storybook story in light and dark, and on open popups (menus, listboxes, dialogs), on every pull request; a new violation fails the build.",
@@ -342,6 +342,15 @@
342
342
  "keyboard": [],
343
343
  "screenReader": []
344
344
  },
345
+ {
346
+ "slug": "date-time-picker",
347
+ "name": "Date Time Picker",
348
+ "package": "@nexub/react",
349
+ "automated": "pass",
350
+ "exceptions": [],
351
+ "keyboard": [],
352
+ "screenReader": []
353
+ },
345
354
  {
346
355
  "slug": "dialog",
347
356
  "name": "Dialog",
@@ -401,6 +410,15 @@
401
410
  "keyboard": [],
402
411
  "screenReader": []
403
412
  },
413
+ {
414
+ "slug": "event-calendar",
415
+ "name": "Event Calendar",
416
+ "package": "@nexub/react",
417
+ "automated": "pass",
418
+ "exceptions": [],
419
+ "keyboard": [],
420
+ "screenReader": []
421
+ },
404
422
  {
405
423
  "slug": "eyebrow-chip",
406
424
  "name": "Eyebrow Chip",
@@ -1127,6 +1145,18 @@
1127
1145
  "Render it only while waiting and remove it when tokens start, so it is not announced again."
1128
1146
  ]
1129
1147
  },
1148
+ {
1149
+ "slug": "time-picker",
1150
+ "name": "Time Picker",
1151
+ "package": "@nexub/react",
1152
+ "automated": "pass",
1153
+ "exceptions": [],
1154
+ "keyboard": [
1155
+ "ArrowUp / ArrowDown: Changes the focused segment (hour, minute, AM/PM).",
1156
+ "ArrowLeft / ArrowRight: Moves between segments."
1157
+ ],
1158
+ "screenReader": []
1159
+ },
1130
1160
  {
1131
1161
  "slug": "toast",
1132
1162
  "name": "Toast",
@@ -0,0 +1,20 @@
1
+ import * as React from 'react';
2
+ import type { Locale } from 'date-fns';
3
+ export interface DateTimePickerProps {
4
+ /** Date and time in the reader's local time. */
5
+ value?: Date;
6
+ onChange?: (value: Date | undefined) => void;
7
+ /** Time used when a date is picked first. @default '09:00' */
8
+ defaultTime?: string;
9
+ minuteStep?: number;
10
+ minDate?: Date;
11
+ maxDate?: Date;
12
+ disabled?: boolean;
13
+ /** IANA zone to name next to the fields (label only; the value stays local). */
14
+ timeZone?: string;
15
+ 'aria-label'?: string;
16
+ className?: string;
17
+ locale?: Locale;
18
+ }
19
+ /** A DatePicker and a TimePicker that edit one `Date` together. */
20
+ export declare function DateTimePicker({ value, onChange, defaultTime, minuteStep, minDate, maxDate, disabled, timeZone, 'aria-label': ariaLabel, className, locale, }: DateTimePickerProps): React.JSX.Element;
@@ -0,0 +1,67 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { cn } from "../../utils/cn.js";
5
+ import { DatePicker } from "../DatePicker/DatePicker.js";
6
+ import { TimePicker } from "../TimePicker/TimePicker.js";
7
+ import { useIntlLocale, useNexubMessages } from "../NexubProvider/NexubProvider.js";
8
+ const pad = (n) => String(n).padStart(2, "0");
9
+ const timeOf = (d) => `${pad(d.getHours())}:${pad(d.getMinutes())}`;
10
+ const withTime = (d, time) => {
11
+ const [h = 0, m = 0] = time.split(":").map(Number);
12
+ const next = new Date(d);
13
+ next.setHours(h, m, 0, 0);
14
+ return next;
15
+ };
16
+ function DateTimePicker({
17
+ value,
18
+ onChange,
19
+ defaultTime = "09:00",
20
+ minuteStep,
21
+ minDate,
22
+ maxDate,
23
+ disabled,
24
+ timeZone,
25
+ "aria-label": ariaLabel,
26
+ className,
27
+ locale
28
+ }) {
29
+ const messages = useNexubMessages();
30
+ const intlLocale = useIntlLocale();
31
+ const [pendingTime, setPendingTime] = React.useState(defaultTime);
32
+ const time = value ? timeOf(value) : pendingTime;
33
+ const zoneName = timeZone ? new Intl.DateTimeFormat(intlLocale, { timeZone, timeZoneName: "shortOffset" }).formatToParts(value ?? /* @__PURE__ */ new Date()).find((p) => p.type === "timeZoneName")?.value ?? timeZone : void 0;
34
+ return /* @__PURE__ */ jsxs("div", { role: "group", "aria-label": ariaLabel, className: cn("flex flex-wrap items-center gap-2", className), children: [
35
+ /* @__PURE__ */ jsx("div", { className: "min-w-40 flex-1", children: /* @__PURE__ */ jsx(
36
+ DatePicker,
37
+ {
38
+ "aria-label": messages.dateLabel,
39
+ value,
40
+ onChange: (date) => onChange?.(date ? withTime(date, time) : void 0),
41
+ minDate,
42
+ maxDate,
43
+ disabled,
44
+ locale
45
+ }
46
+ ) }),
47
+ /* @__PURE__ */ jsx(
48
+ TimePicker,
49
+ {
50
+ "aria-label": messages.timeLabel,
51
+ wrapperClassName: "w-36",
52
+ value: time,
53
+ minuteStep,
54
+ disabled,
55
+ onValueChange: (next) => {
56
+ if (!next) return;
57
+ if (value) onChange?.(withTime(value, next));
58
+ else setPendingTime(next);
59
+ }
60
+ }
61
+ ),
62
+ zoneName && /* @__PURE__ */ jsx("span", { className: "text-body-sm text-[var(--color-on-surface-muted)]", children: messages.timeZoneLabel(`${zoneName} (${timeZone})`) })
63
+ ] });
64
+ }
65
+ export {
66
+ DateTimePicker
67
+ };
@@ -0,0 +1,2 @@
1
+ export { DateTimePicker } from './DateTimePicker.js';
2
+ export type { DateTimePickerProps } from './DateTimePicker.js';
@@ -0,0 +1,4 @@
1
+ import { DateTimePicker } from "./DateTimePicker.js";
2
+ export {
3
+ DateTimePicker
4
+ };
@@ -0,0 +1,38 @@
1
+ import * as React from 'react';
2
+ import { type Day } from 'date-fns';
3
+ export type EventCalendarView = 'month' | 'week';
4
+ export type EventTone = 'primary' | 'success' | 'warning' | 'error' | 'info';
5
+ export interface CalendarEvent {
6
+ id: string;
7
+ title: string;
8
+ start: Date;
9
+ /** Inclusive end; events spanning days show on each of them. */
10
+ end?: Date;
11
+ /** Hides the start time. */
12
+ allDay?: boolean;
13
+ /** @default 'primary' */
14
+ tone?: EventTone;
15
+ }
16
+ export interface EventCalendarProps {
17
+ events: CalendarEvent[];
18
+ /** Any day inside the shown month or week. */
19
+ date?: Date;
20
+ defaultDate?: Date;
21
+ onDateChange?: (date: Date) => void;
22
+ view?: EventCalendarView;
23
+ defaultView?: EventCalendarView;
24
+ onViewChange?: (view: EventCalendarView) => void;
25
+ onEventClick?: (event: CalendarEvent) => void;
26
+ /** A day's number, or its "N more" link in month view. */
27
+ onDayClick?: (date: Date) => void;
28
+ /** Events shown per day in month view before "N more". @default 3 */
29
+ maxEventsPerDay?: number;
30
+ /** 0 = Sunday. Defaults to the locale's first day of the week. */
31
+ weekStartsOn?: Day;
32
+ className?: string;
33
+ }
34
+ /**
35
+ * A month or week grid of events. Display only: your app owns the events and
36
+ * decides what a click on an event or a day does.
37
+ */
38
+ export declare function EventCalendar({ events, date: dateProp, defaultDate, onDateChange, view: viewProp, defaultView, onViewChange, onEventClick, onDayClick, maxEventsPerDay, weekStartsOn: weekStartsOnProp, className, }: EventCalendarProps): React.JSX.Element;
@@ -0,0 +1,204 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import {
5
+ addDays,
6
+ addMonths,
7
+ addWeeks,
8
+ endOfDay,
9
+ endOfMonth,
10
+ endOfWeek,
11
+ format,
12
+ isSameDay,
13
+ isSameMonth,
14
+ startOfDay,
15
+ startOfMonth,
16
+ startOfWeek
17
+ } from "date-fns";
18
+ import { ChevronLeft, ChevronRight } from "@nexub/icons";
19
+ import { cn } from "../../utils/cn.js";
20
+ import { Button } from "../Button/Button.js";
21
+ import { IconButton } from "../IconButton/IconButton.js";
22
+ import { SegmentedControl } from "../SegmentedControl/SegmentedControl.js";
23
+ import { useControllableState } from "../../hooks/useControllableState.js";
24
+ import { useIntlLocale, useLocale, useNexubMessages } from "../NexubProvider/NexubProvider.js";
25
+ const tones = {
26
+ primary: "bg-[var(--color-primary-container)] text-[var(--color-on-primary-container)]",
27
+ success: "bg-[var(--color-success-container)] text-[var(--color-on-success-container)]",
28
+ warning: "bg-[var(--color-warning-container)] text-[var(--color-on-warning-container)]",
29
+ error: "bg-[var(--color-error-container)] text-[var(--color-on-error-container)]",
30
+ info: "bg-[var(--color-info-container)] text-[var(--color-on-info-container)]"
31
+ };
32
+ function localeWeekStart(locale) {
33
+ try {
34
+ const loc = new Intl.Locale(locale ?? new Intl.DateTimeFormat().resolvedOptions().locale);
35
+ const firstDay = loc.getWeekInfo?.().firstDay ?? loc.weekInfo?.firstDay;
36
+ return firstDay ? firstDay % 7 : 0;
37
+ } catch {
38
+ return 0;
39
+ }
40
+ }
41
+ function EventCalendar({
42
+ events,
43
+ date: dateProp,
44
+ defaultDate,
45
+ onDateChange,
46
+ view: viewProp,
47
+ defaultView = "month",
48
+ onViewChange,
49
+ onEventClick,
50
+ onDayClick,
51
+ maxEventsPerDay = 3,
52
+ weekStartsOn: weekStartsOnProp,
53
+ className
54
+ }) {
55
+ const t = useNexubMessages();
56
+ const { dir } = useLocale();
57
+ const intlLocale = useIntlLocale();
58
+ const [date = /* @__PURE__ */ new Date(), setDate] = useControllableState({
59
+ value: dateProp,
60
+ defaultValue: defaultDate ?? /* @__PURE__ */ new Date(),
61
+ onChange: onDateChange
62
+ });
63
+ const [view = "month", setView] = useControllableState({
64
+ value: viewProp,
65
+ defaultValue: defaultView,
66
+ onChange: onViewChange
67
+ });
68
+ const weekStartsOn = weekStartsOnProp ?? localeWeekStart(intlLocale);
69
+ const opts = { weekStartsOn };
70
+ const month = view === "month";
71
+ const first = startOfWeek(month ? startOfMonth(date) : date, opts);
72
+ const last = endOfWeek(month ? endOfMonth(date) : date, opts);
73
+ const days = [];
74
+ for (let d = first; d <= last; d = addDays(d, 1)) days.push(d);
75
+ const weeks = [];
76
+ for (let i = 0; i < days.length; i += 7) weeks.push(days.slice(i, i + 7));
77
+ const sorted = React.useMemo(
78
+ () => [...events].sort((a, b) => a.start.getTime() - b.start.getTime()),
79
+ [events]
80
+ );
81
+ const eventsOn = (day) => sorted.filter((e) => e.start <= endOfDay(day) && (e.end ?? e.start) >= startOfDay(day));
82
+ const fmt = (o) => new Intl.DateTimeFormat(intlLocale, o);
83
+ const title = month ? fmt({ month: "long", year: "numeric" }).format(date) : fmt({ month: "short", day: "numeric", year: "numeric" }).formatRange(days[0], days[6]);
84
+ const timeFmt = fmt({ timeStyle: "short" });
85
+ const fullFmt = fmt({ dateStyle: "full" });
86
+ const today = /* @__PURE__ */ new Date();
87
+ const step = (n) => setDate(month ? addMonths(date, n) : addWeeks(date, n));
88
+ const [Back, Forward] = dir === "rtl" ? [ChevronRight, ChevronLeft] : [ChevronLeft, ChevronRight];
89
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-3", className), children: [
90
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
91
+ /* @__PURE__ */ jsx("h2", { "aria-live": "polite", className: "me-auto text-title-lg font-semibold text-[var(--color-on-surface)]", children: title }),
92
+ /* @__PURE__ */ jsx(Button, { variant: "outlined", size: "sm", onClick: () => setDate(/* @__PURE__ */ new Date()), children: t.today }),
93
+ /* @__PURE__ */ jsx(IconButton, { variant: "ghost", size: "sm", label: month ? t.previousMonth : t.previousWeek, onClick: () => step(-1), children: /* @__PURE__ */ jsx(Back, { size: "sm", "aria-hidden": "true" }) }),
94
+ /* @__PURE__ */ jsx(IconButton, { variant: "ghost", size: "sm", label: month ? t.nextMonth : t.nextWeek, onClick: () => step(1), children: /* @__PURE__ */ jsx(Forward, { size: "sm", "aria-hidden": "true" }) }),
95
+ /* @__PURE__ */ jsxs(
96
+ SegmentedControl.Root,
97
+ {
98
+ size: "sm",
99
+ "aria-label": t.calendarView,
100
+ value: view,
101
+ onValueChange: (v) => v && setView(v),
102
+ children: [
103
+ /* @__PURE__ */ jsx(SegmentedControl.Item, { value: "month", children: t.monthView }),
104
+ /* @__PURE__ */ jsx(SegmentedControl.Item, { value: "week", children: t.weekView })
105
+ ]
106
+ }
107
+ )
108
+ ] }),
109
+ /* @__PURE__ */ jsxs("table", { className: "w-full table-fixed border-collapse text-body-sm", children: [
110
+ /* @__PURE__ */ jsx("caption", { className: "sr-only", children: title }),
111
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: weeks[0].map((d) => /* @__PURE__ */ jsx(
112
+ "th",
113
+ {
114
+ scope: "col",
115
+ abbr: fmt({ weekday: "long" }).format(d),
116
+ className: "pb-2 text-label-md font-medium text-[var(--color-on-surface-muted)]",
117
+ children: fmt({ weekday: "short" }).format(d)
118
+ },
119
+ d.getDay()
120
+ )) }) }),
121
+ /* @__PURE__ */ jsx("tbody", { children: weeks.map((week) => /* @__PURE__ */ jsx("tr", { children: week.map((day) => {
122
+ const list = eventsOn(day);
123
+ const shown = month ? list.slice(0, maxEventsPerDay) : list;
124
+ const hidden = list.length - shown.length;
125
+ const isToday = isSameDay(day, today);
126
+ const outside = month && !isSameMonth(day, date);
127
+ return /* @__PURE__ */ jsxs(
128
+ "td",
129
+ {
130
+ "aria-current": isToday ? "date" : void 0,
131
+ className: cn(
132
+ "border border-[var(--color-outline-variant)] p-1 align-top",
133
+ month ? "h-28" : "h-80",
134
+ outside && "bg-[var(--color-surface-container-low)]"
135
+ ),
136
+ children: [
137
+ /* @__PURE__ */ jsx(
138
+ DayNumber,
139
+ {
140
+ day,
141
+ label: fullFmt.format(day),
142
+ isToday,
143
+ muted: outside,
144
+ onClick: onDayClick
145
+ }
146
+ ),
147
+ /* @__PURE__ */ jsx("ul", { className: "mt-1 flex flex-col gap-0.5", children: shown.map((e) => {
148
+ const time = e.allDay || !isSameDay(e.start, day) ? "" : timeFmt.format(e.start);
149
+ const body = /* @__PURE__ */ jsxs(Fragment, { children: [
150
+ time && /* @__PURE__ */ jsx("span", { className: "me-1 tabular-nums opacity-80", children: time }),
151
+ e.title
152
+ ] });
153
+ const cls = cn(
154
+ "block w-full truncate rounded-shape-min px-1.5 py-0.5 text-start text-label-md",
155
+ tones[e.tone ?? "primary"]
156
+ );
157
+ return /* @__PURE__ */ jsx("li", { children: onEventClick ? /* @__PURE__ */ jsx("button", { type: "button", className: cn(cls, "hover:opacity-90"), onClick: () => onEventClick(e), children: body }) : /* @__PURE__ */ jsx("span", { className: cls, children: body }) }, e.id);
158
+ }) }),
159
+ hidden > 0 && (onDayClick ? /* @__PURE__ */ jsx(
160
+ "button",
161
+ {
162
+ type: "button",
163
+ className: "min-h-6 px-1.5 text-label-md text-[var(--color-on-surface-variant)] hover:underline",
164
+ onClick: () => onDayClick(day),
165
+ children: t.moreCount(hidden)
166
+ }
167
+ ) : /* @__PURE__ */ jsx("span", { className: "mt-0.5 block px-1.5 text-label-md text-[var(--color-on-surface-variant)]", children: t.moreCount(hidden) }))
168
+ ]
169
+ },
170
+ day.toISOString()
171
+ );
172
+ }) }, week[0].toISOString())) })
173
+ ] })
174
+ ] });
175
+ }
176
+ function DayNumber({
177
+ day,
178
+ label,
179
+ isToday,
180
+ muted,
181
+ onClick
182
+ }) {
183
+ const cls = cn(
184
+ "inline-flex size-7 items-center justify-center rounded-full text-label-md tabular-nums",
185
+ isToday ? "bg-[var(--color-primary)] text-[var(--color-on-primary)]" : muted ? "text-[var(--color-on-surface-muted)]" : "text-[var(--color-on-surface)]"
186
+ );
187
+ const n = day.getDate();
188
+ return onClick ? /* @__PURE__ */ jsx(
189
+ "button",
190
+ {
191
+ type: "button",
192
+ "aria-label": label,
193
+ className: cn(cls, !isToday && "hover:bg-[var(--color-surface-container-high)]"),
194
+ onClick: () => onClick(day),
195
+ children: n
196
+ }
197
+ ) : /* @__PURE__ */ jsxs("time", { dateTime: format(day, "yyyy-MM-dd"), className: cls, children: [
198
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: n }),
199
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: label })
200
+ ] });
201
+ }
202
+ export {
203
+ EventCalendar
204
+ };
@@ -0,0 +1,2 @@
1
+ export { EventCalendar } from './EventCalendar.js';
2
+ export type { EventCalendarProps, EventCalendarView, CalendarEvent, EventTone } from './EventCalendar.js';
@@ -0,0 +1,4 @@
1
+ import { EventCalendar } from "./EventCalendar.js";
2
+ export {
3
+ EventCalendar
4
+ };
@@ -0,0 +1,15 @@
1
+ import * as React from 'react';
2
+ import { type InputProps } from '../Input/Input.js';
3
+ export interface TimePickerProps extends Omit<InputProps, 'type' | 'value' | 'defaultValue' | 'onChange' | 'step'> {
4
+ /** 24-hour `"HH:mm"`; the field shows 12 or 24 hours from the reader's locale. */
5
+ value?: string;
6
+ defaultValue?: string;
7
+ onValueChange?: (value: string) => void;
8
+ /** Minutes between steps for the arrow keys and the picker. @default 1 */
9
+ minuteStep?: number;
10
+ }
11
+ /**
12
+ * A time field built on the native `<input type="time">`: locale-aware 12/24h,
13
+ * keyboard segments, the platform's picker on phones, and form submission.
14
+ */
15
+ export declare const TimePicker: React.ForwardRefExoticComponent<TimePickerProps & React.RefAttributes<HTMLInputElement>>;
@@ -0,0 +1,26 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { Input } from "../Input/Input.js";
5
+ import { useControllableState } from "../../hooks/useControllableState.js";
6
+ const TimePicker = React.forwardRef(function TimePicker2({ value: valueProp, defaultValue, onValueChange, minuteStep = 1, ...props }, ref) {
7
+ const [value = "", setValue] = useControllableState({
8
+ value: valueProp,
9
+ defaultValue: defaultValue ?? "",
10
+ onChange: onValueChange
11
+ });
12
+ return /* @__PURE__ */ jsx(
13
+ Input,
14
+ {
15
+ ref,
16
+ type: "time",
17
+ step: minuteStep * 60,
18
+ value,
19
+ onChange: (e) => setValue(e.target.value),
20
+ ...props
21
+ }
22
+ );
23
+ });
24
+ export {
25
+ TimePicker
26
+ };
@@ -0,0 +1,2 @@
1
+ export { TimePicker } from './TimePicker.js';
2
+ export type { TimePickerProps } from './TimePicker.js';
@@ -0,0 +1,4 @@
1
+ import { TimePicker } from "./TimePicker.js";
2
+ export {
3
+ TimePicker
4
+ };
package/dist/index.d.ts CHANGED
@@ -220,6 +220,12 @@ export { Fieldset } from './components/Fieldset/index.js';
220
220
  export type { FieldsetProps } from './components/Fieldset/index.js';
221
221
  export { InlineEdit } from './components/InlineEdit/index.js';
222
222
  export type { InlineEditProps } from './components/InlineEdit/index.js';
223
+ export { TimePicker } from './components/TimePicker/index.js';
224
+ export type { TimePickerProps } from './components/TimePicker/index.js';
225
+ export { DateTimePicker } from './components/DateTimePicker/index.js';
226
+ export type { DateTimePickerProps } from './components/DateTimePicker/index.js';
227
+ export { EventCalendar } from './components/EventCalendar/index.js';
228
+ export type { EventCalendarProps, EventCalendarView, CalendarEvent, EventTone } from './components/EventCalendar/index.js';
223
229
  export type { OTPInputProps } from './components/OTPInput/index.js';
224
230
  export { DatePicker } from './components/DatePicker/index.js';
225
231
  export type { DatePickerProps } from './components/DatePicker/index.js';
package/dist/index.js CHANGED
@@ -124,6 +124,9 @@ import { OTPInput } from "./components/OTPInput/index.js";
124
124
  import { Form, useForm } from "./components/Form/index.js";
125
125
  import { Fieldset } from "./components/Fieldset/index.js";
126
126
  import { InlineEdit } from "./components/InlineEdit/index.js";
127
+ import { TimePicker } from "./components/TimePicker/index.js";
128
+ import { DateTimePicker } from "./components/DateTimePicker/index.js";
129
+ import { EventCalendar } from "./components/EventCalendar/index.js";
127
130
  import { DatePicker } from "./components/DatePicker/index.js";
128
131
  import { DateRangePicker } from "./components/DateRangePicker/index.js";
129
132
  import { todayIn } from "./components/DateRangePicker/index.js";
@@ -169,6 +172,7 @@ export {
169
172
  DataTable,
170
173
  DatePicker,
171
174
  DateRangePicker,
175
+ DateTimePicker,
172
176
  Dialog,
173
177
  DiffView,
174
178
  DonutChart,
@@ -177,6 +181,7 @@ export {
177
181
  EmptyState,
178
182
  EmptyStatePreset,
179
183
  ErrorState,
184
+ EventCalendar,
180
185
  EyebrowChip,
181
186
  Fade,
182
187
  Fieldset,
@@ -266,6 +271,7 @@ export {
266
271
  ThemeProvider,
267
272
  ThemeScript,
268
273
  ThinkingIndicator,
274
+ TimePicker,
269
275
  Toast,
270
276
  Toaster,
271
277
  Toggle,