premium-ds 0.1.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/LICENSE +21 -0
- package/README.md +113 -0
- package/dist/alert.d.ts +31 -0
- package/dist/alert.js +6 -0
- package/dist/alert.js.map +1 -0
- package/dist/avatar-group.d.ts +13 -0
- package/dist/avatar-group.js +3 -0
- package/dist/avatar-group.js.map +1 -0
- package/dist/avatar.d.ts +25 -0
- package/dist/avatar.js +3 -0
- package/dist/avatar.js.map +1 -0
- package/dist/badge.d.ts +23 -0
- package/dist/badge.js +3 -0
- package/dist/badge.js.map +1 -0
- package/dist/button.d.ts +20 -0
- package/dist/button.js +3 -0
- package/dist/button.js.map +1 -0
- package/dist/checkbox.d.ts +25 -0
- package/dist/checkbox.js +3 -0
- package/dist/checkbox.js.map +1 -0
- package/dist/chunk-2OWHZ4JT.js +36 -0
- package/dist/chunk-2OWHZ4JT.js.map +1 -0
- package/dist/chunk-34SIXSYL.js +64 -0
- package/dist/chunk-34SIXSYL.js.map +1 -0
- package/dist/chunk-37O2ZXD6.js +55 -0
- package/dist/chunk-37O2ZXD6.js.map +1 -0
- package/dist/chunk-4AZL76UJ.js +89 -0
- package/dist/chunk-4AZL76UJ.js.map +1 -0
- package/dist/chunk-4HSCN5TZ.js +86 -0
- package/dist/chunk-4HSCN5TZ.js.map +1 -0
- package/dist/chunk-5DDOOT33.js +258 -0
- package/dist/chunk-5DDOOT33.js.map +1 -0
- package/dist/chunk-5FVHWIMY.js +117 -0
- package/dist/chunk-5FVHWIMY.js.map +1 -0
- package/dist/chunk-5K6KRJGX.js +147 -0
- package/dist/chunk-5K6KRJGX.js.map +1 -0
- package/dist/chunk-5PQMQBQC.js +74 -0
- package/dist/chunk-5PQMQBQC.js.map +1 -0
- package/dist/chunk-7OCTVQ7C.js +95 -0
- package/dist/chunk-7OCTVQ7C.js.map +1 -0
- package/dist/chunk-7OPMOET7.js +39 -0
- package/dist/chunk-7OPMOET7.js.map +1 -0
- package/dist/chunk-BXXS7YRC.js +270 -0
- package/dist/chunk-BXXS7YRC.js.map +1 -0
- package/dist/chunk-CV2Q4YXX.js +272 -0
- package/dist/chunk-CV2Q4YXX.js.map +1 -0
- package/dist/chunk-EIMMDWIW.js +282 -0
- package/dist/chunk-EIMMDWIW.js.map +1 -0
- package/dist/chunk-EZ2CWTBE.js +230 -0
- package/dist/chunk-EZ2CWTBE.js.map +1 -0
- package/dist/chunk-FGHDG3Y4.js +89 -0
- package/dist/chunk-FGHDG3Y4.js.map +1 -0
- package/dist/chunk-FPP2XLKX.js +127 -0
- package/dist/chunk-FPP2XLKX.js.map +1 -0
- package/dist/chunk-G6OY35DI.js +295 -0
- package/dist/chunk-G6OY35DI.js.map +1 -0
- package/dist/chunk-H6KWJNOE.js +65 -0
- package/dist/chunk-H6KWJNOE.js.map +1 -0
- package/dist/chunk-HGILYGY3.js +45 -0
- package/dist/chunk-HGILYGY3.js.map +1 -0
- package/dist/chunk-I3BCB4Z5.js +88 -0
- package/dist/chunk-I3BCB4Z5.js.map +1 -0
- package/dist/chunk-KBWNUUWM.js +582 -0
- package/dist/chunk-KBWNUUWM.js.map +1 -0
- package/dist/chunk-KN7JFAZ6.js +113 -0
- package/dist/chunk-KN7JFAZ6.js.map +1 -0
- package/dist/chunk-MEF7PI6U.js +16 -0
- package/dist/chunk-MEF7PI6U.js.map +1 -0
- package/dist/chunk-NKGMQL6I.js +310 -0
- package/dist/chunk-NKGMQL6I.js.map +1 -0
- package/dist/chunk-NMFQRGLL.js +127 -0
- package/dist/chunk-NMFQRGLL.js.map +1 -0
- package/dist/chunk-OUBWD6CX.js +433 -0
- package/dist/chunk-OUBWD6CX.js.map +1 -0
- package/dist/chunk-PFNXVBLU.js +96 -0
- package/dist/chunk-PFNXVBLU.js.map +1 -0
- package/dist/chunk-PUPZ4HME.js +165 -0
- package/dist/chunk-PUPZ4HME.js.map +1 -0
- package/dist/chunk-QFS52OK5.js +690 -0
- package/dist/chunk-QFS52OK5.js.map +1 -0
- package/dist/chunk-QNC6O3PG.js +45 -0
- package/dist/chunk-QNC6O3PG.js.map +1 -0
- package/dist/chunk-QUHOXWBK.js +82 -0
- package/dist/chunk-QUHOXWBK.js.map +1 -0
- package/dist/chunk-UIQGSTBJ.js +106 -0
- package/dist/chunk-UIQGSTBJ.js.map +1 -0
- package/dist/chunk-UJQKVP6V.js +193 -0
- package/dist/chunk-UJQKVP6V.js.map +1 -0
- package/dist/chunk-VVPGEAC6.js +11 -0
- package/dist/chunk-VVPGEAC6.js.map +1 -0
- package/dist/chunk-XA3T5KWA.js +58 -0
- package/dist/chunk-XA3T5KWA.js.map +1 -0
- package/dist/chunk-YSHJHSJM.js +19 -0
- package/dist/chunk-YSHJHSJM.js.map +1 -0
- package/dist/chunk-YVHOAVSM.js +182 -0
- package/dist/chunk-YVHOAVSM.js.map +1 -0
- package/dist/collapse.d.ts +16 -0
- package/dist/collapse.js +3 -0
- package/dist/collapse.js.map +1 -0
- package/dist/count-badge.d.ts +11 -0
- package/dist/count-badge.js +4 -0
- package/dist/count-badge.js.map +1 -0
- package/dist/date-field.d.ts +39 -0
- package/dist/date-field.js +8 -0
- package/dist/date-field.js.map +1 -0
- package/dist/date-range-field.d.ts +30 -0
- package/dist/date-range-field.js +8 -0
- package/dist/date-range-field.js.map +1 -0
- package/dist/datetime-field.d.ts +28 -0
- package/dist/datetime-field.js +10 -0
- package/dist/datetime-field.js.map +1 -0
- package/dist/dialog.d.ts +26 -0
- package/dist/dialog.js +7 -0
- package/dist/dialog.js.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -0
- package/dist/motion-tokens.d.ts +29 -0
- package/dist/motion-tokens.js +3 -0
- package/dist/motion-tokens.js.map +1 -0
- package/dist/multi-select.d.ts +25 -0
- package/dist/multi-select.js +7 -0
- package/dist/multi-select.js.map +1 -0
- package/dist/number-field.d.ts +24 -0
- package/dist/number-field.js +4 -0
- package/dist/number-field.js.map +1 -0
- package/dist/otp-field.d.ts +20 -0
- package/dist/otp-field.js +3 -0
- package/dist/otp-field.js.map +1 -0
- package/dist/overlay.d.ts +31 -0
- package/dist/overlay.js +4 -0
- package/dist/overlay.js.map +1 -0
- package/dist/pagination.d.ts +24 -0
- package/dist/pagination.js +5 -0
- package/dist/pagination.js.map +1 -0
- package/dist/radio-group.d.ts +46 -0
- package/dist/radio-group.js +6 -0
- package/dist/radio-group.js.map +1 -0
- package/dist/select-core-SAyS-8w0.d.ts +16 -0
- package/dist/select.d.ts +27 -0
- package/dist/select.js +7 -0
- package/dist/select.js.map +1 -0
- package/dist/status-badge.d.ts +17 -0
- package/dist/status-badge.js +5 -0
- package/dist/status-badge.js.map +1 -0
- package/dist/table.d.ts +65 -0
- package/dist/table.js +5 -0
- package/dist/table.js.map +1 -0
- package/dist/tabs.d.ts +44 -0
- package/dist/tabs.js +5 -0
- package/dist/tabs.js.map +1 -0
- package/dist/tag.d.ts +28 -0
- package/dist/tag.js +5 -0
- package/dist/tag.js.map +1 -0
- package/dist/text-field.d.ts +30 -0
- package/dist/text-field.js +6 -0
- package/dist/text-field.js.map +1 -0
- package/dist/textarea.d.ts +33 -0
- package/dist/textarea.js +5 -0
- package/dist/textarea.js.map +1 -0
- package/dist/time-field.d.ts +27 -0
- package/dist/time-field.js +6 -0
- package/dist/time-field.js.map +1 -0
- package/dist/toast-store.d.ts +75 -0
- package/dist/toast-store.js +3 -0
- package/dist/toast-store.js.map +1 -0
- package/dist/toast.d.ts +3 -0
- package/dist/toast.js +6 -0
- package/dist/toast.js.map +1 -0
- package/dist/toggle-tag.d.ts +24 -0
- package/dist/toggle-tag.js +4 -0
- package/dist/toggle-tag.js.map +1 -0
- package/dist/toggle.d.ts +21 -0
- package/dist/toggle.js +3 -0
- package/dist/toggle.js.map +1 -0
- package/dist/tooltip.d.ts +27 -0
- package/dist/tooltip.js +4 -0
- package/dist/tooltip.js.map +1 -0
- package/llms.txt +165 -0
- package/package.json +205 -0
- package/src/components/alert/Alert.tsx +118 -0
- package/src/components/alert/alert.css +136 -0
- package/src/components/avatar/Avatar.tsx +128 -0
- package/src/components/avatar/AvatarGroup.tsx +50 -0
- package/src/components/avatar/avatar.css +200 -0
- package/src/components/badge/Badge.tsx +66 -0
- package/src/components/badge/CountBadge.tsx +46 -0
- package/src/components/badge/StatusBadge.tsx +132 -0
- package/src/components/badge/badge.css +243 -0
- package/src/components/button/Button.tsx +68 -0
- package/src/components/button/button.css +222 -0
- package/src/components/checkbox/Checkbox.tsx +90 -0
- package/src/components/checkbox/checkbox.css +179 -0
- package/src/components/date-picker/DateField.tsx +362 -0
- package/src/components/date-picker/DateRangeField.tsx +533 -0
- package/src/components/date-picker/DateTimeField.tsx +177 -0
- package/src/components/date-picker/TimeField.tsx +100 -0
- package/src/components/date-picker/date-picker.css +591 -0
- package/src/components/date-picker/date-utils.ts +55 -0
- package/src/components/date-picker/field-shell.tsx +78 -0
- package/src/components/date-picker/glide-pill.tsx +81 -0
- package/src/components/date-picker/time-core.tsx +305 -0
- package/src/components/dialog/Dialog.tsx +181 -0
- package/src/components/dialog/dialog.css +170 -0
- package/src/components/glass/glass.css +100 -0
- package/src/components/icon/Icon.tsx +76 -0
- package/src/components/icon/IconSlot.tsx +11 -0
- package/src/components/icon/icon.css +33 -0
- package/src/components/input/NumberField.tsx +117 -0
- package/src/components/input/OtpField.tsx +118 -0
- package/src/components/input/TextField.tsx +123 -0
- package/src/components/input/input.css +335 -0
- package/src/components/motion/Collapse.tsx +33 -0
- package/src/components/motion/collapse.css +41 -0
- package/src/components/overlay/Overlay.tsx +239 -0
- package/src/components/overlay/overlay-core.tsx +565 -0
- package/src/components/overlay/overlay.css +119 -0
- package/src/components/overlay/sheet-drag.tsx +146 -0
- package/src/components/pagination/Pagination.tsx +140 -0
- package/src/components/pagination/pagination.css +48 -0
- package/src/components/radio-group/RadioGroup.tsx +182 -0
- package/src/components/radio-group/radio-group.css +277 -0
- package/src/components/select/MultiSelect.tsx +251 -0
- package/src/components/select/Select.tsx +235 -0
- package/src/components/select/select-core.tsx +417 -0
- package/src/components/select/select.css +386 -0
- package/src/components/table/Table.tsx +433 -0
- package/src/components/table/table.css +348 -0
- package/src/components/tabs/Tabs.tsx +371 -0
- package/src/components/tabs/tabs.css +228 -0
- package/src/components/tag/Tag.tsx +145 -0
- package/src/components/tag/ToggleTag.tsx +125 -0
- package/src/components/tag/tag.css +248 -0
- package/src/components/textarea/Textarea.tsx +197 -0
- package/src/components/textarea/textarea.css +219 -0
- package/src/components/toast/Toast.tsx +349 -0
- package/src/components/toast/toast-store.ts +266 -0
- package/src/components/toast/toast.css +233 -0
- package/src/components/toggle/Toggle.tsx +94 -0
- package/src/components/toggle/toggle.css +152 -0
- package/src/components/tooltip/Tooltip.tsx +365 -0
- package/src/components/tooltip/tooltip.css +86 -0
- package/src/index.ts +42 -0
- package/src/styles.css +39 -0
- package/src/tokens/avatar.css +20 -0
- package/src/tokens/color.css +56 -0
- package/src/tokens/elevation.css +20 -0
- package/src/tokens/fonts.css +3 -0
- package/src/tokens/glass.css +21 -0
- package/src/tokens/icons.css +7 -0
- package/src/tokens/layers.css +6 -0
- package/src/tokens/motion-tokens.ts +72 -0
- package/src/tokens/motion.css +49 -0
- package/src/tokens/radius.css +11 -0
- package/src/tokens/semantic.css +75 -0
- package/src/tokens/spacing.css +26 -0
- package/src/tokens/typography.css +54 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/* date-picker shared pure helpers - 'YYYY-MM-DD' civil-date math, month/weekday constants, tz-offset label. */
|
|
2
|
+
|
|
3
|
+
export const MONTHS: string[] = [
|
|
4
|
+
'January',
|
|
5
|
+
'February',
|
|
6
|
+
'March',
|
|
7
|
+
'April',
|
|
8
|
+
'May',
|
|
9
|
+
'June',
|
|
10
|
+
'July',
|
|
11
|
+
'August',
|
|
12
|
+
'September',
|
|
13
|
+
'October',
|
|
14
|
+
'November',
|
|
15
|
+
'December',
|
|
16
|
+
];
|
|
17
|
+
export const DOW: string[] = ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'];
|
|
18
|
+
|
|
19
|
+
export const pad = (n: number): string => String(n).padStart(2, '0');
|
|
20
|
+
export const key = (d: Date): string =>
|
|
21
|
+
d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate());
|
|
22
|
+
export function parse(k: string): Date {
|
|
23
|
+
const p = k.split('-').map(Number);
|
|
24
|
+
return new Date(p[0], p[1] - 1, p[2]);
|
|
25
|
+
}
|
|
26
|
+
export const today = (): string => key(new Date());
|
|
27
|
+
export const add = (k: string, days: number): string => {
|
|
28
|
+
const d = parse(k);
|
|
29
|
+
d.setDate(d.getDate() + days);
|
|
30
|
+
return key(d);
|
|
31
|
+
};
|
|
32
|
+
export const col = (d: Date): number => (d.getDay() + 6) % 7; // Monday-first column 0-6
|
|
33
|
+
|
|
34
|
+
/* 42 cells, Monday-first, covering the month of (y, m) */
|
|
35
|
+
export function grid(y: number, m: number): Date[] {
|
|
36
|
+
const lead = (new Date(y, m, 1).getDay() + 6) % 7;
|
|
37
|
+
const days: Date[] = [];
|
|
38
|
+
for (let i = 0; i < 42; i++) days.push(new Date(y, m, 1 - lead + i));
|
|
39
|
+
return days;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* 'Europe/Riga' - 'Europe/Riga - GMT+3'; dateKey gives the DST-correct offset for that date, else now. */
|
|
43
|
+
export function tzLabel(tz: string, dateKey?: string): string {
|
|
44
|
+
try {
|
|
45
|
+
const at = dateKey ? parse(dateKey) : new Date();
|
|
46
|
+
const parts = new Intl.DateTimeFormat('en-US', {
|
|
47
|
+
timeZone: tz,
|
|
48
|
+
timeZoneName: 'shortOffset',
|
|
49
|
+
}).formatToParts(at);
|
|
50
|
+
const name = parts.find((p) => p.type === 'timeZoneName');
|
|
51
|
+
return tz.replace(/_/g, ' ') + (name ? ' - ' + name.value : '');
|
|
52
|
+
} catch (_e) {
|
|
53
|
+
return tz;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/* FieldShell + useControllable - the variant-blind .fld chrome and a controlled/uncontrolled state hook. */
|
|
4
|
+
|
|
5
|
+
import * as React from 'react';
|
|
6
|
+
import type { ReactNode } from 'react';
|
|
7
|
+
import { Icon, type IconName } from '../icon/Icon';
|
|
8
|
+
|
|
9
|
+
const { useState, useCallback } = React;
|
|
10
|
+
|
|
11
|
+
export function useControllable<T>(
|
|
12
|
+
value: T | undefined,
|
|
13
|
+
defaultValue: T,
|
|
14
|
+
onChange?: (next: T) => void,
|
|
15
|
+
): [T, (next: T) => void] {
|
|
16
|
+
const controlled = value !== undefined;
|
|
17
|
+
const [inner, setInner] = useState<T>(defaultValue);
|
|
18
|
+
const val = controlled ? (value as T) : inner;
|
|
19
|
+
const commit = useCallback(
|
|
20
|
+
(next: T) => {
|
|
21
|
+
if (!controlled) setInner(next);
|
|
22
|
+
if (onChange) onChange(next);
|
|
23
|
+
},
|
|
24
|
+
[controlled, onChange],
|
|
25
|
+
);
|
|
26
|
+
return [val, commit];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface FieldShellProps {
|
|
30
|
+
variant: string;
|
|
31
|
+
label?: string;
|
|
32
|
+
required?: boolean;
|
|
33
|
+
invalid?: boolean;
|
|
34
|
+
message?: ReactNode;
|
|
35
|
+
icon: IconName;
|
|
36
|
+
className?: string;
|
|
37
|
+
children?: ReactNode;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function FieldShell({
|
|
41
|
+
variant,
|
|
42
|
+
label,
|
|
43
|
+
required,
|
|
44
|
+
invalid,
|
|
45
|
+
message,
|
|
46
|
+
icon,
|
|
47
|
+
className,
|
|
48
|
+
children,
|
|
49
|
+
}: FieldShellProps) {
|
|
50
|
+
const cls = ['fld', variant, 'fld--has-lead', invalid ? 'is-error' : '', className || '']
|
|
51
|
+
.filter(Boolean)
|
|
52
|
+
.join(' ');
|
|
53
|
+
return (
|
|
54
|
+
<div className={cls}>
|
|
55
|
+
{label ? (
|
|
56
|
+
<span className="fld__label">
|
|
57
|
+
{label}
|
|
58
|
+
{required ? (
|
|
59
|
+
<span className="fld__req" aria-hidden="true">
|
|
60
|
+
*
|
|
61
|
+
</span>
|
|
62
|
+
) : null}
|
|
63
|
+
</span>
|
|
64
|
+
) : null}
|
|
65
|
+
<div className="fld__control">
|
|
66
|
+
{children}
|
|
67
|
+
<span className="fld__icon fld__icon--lead" aria-hidden="true">
|
|
68
|
+
<Icon name={icon} size="md" />
|
|
69
|
+
</span>
|
|
70
|
+
</div>
|
|
71
|
+
{message ? (
|
|
72
|
+
<div className="fld__msg">
|
|
73
|
+
<span>{message}</span>
|
|
74
|
+
</div>
|
|
75
|
+
) : null}
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/* GlidePill + useGlide - one persistent pill that springs between hovered cells, never a per-cell remount. */
|
|
4
|
+
|
|
5
|
+
import * as React from 'react';
|
|
6
|
+
import type { RefObject } from 'react';
|
|
7
|
+
import { motion } from 'motion/react';
|
|
8
|
+
import { UIMotion } from '../../tokens/motion-tokens';
|
|
9
|
+
|
|
10
|
+
const SM = UIMotion;
|
|
11
|
+
const { useRef, useState, useEffect, useCallback } = React;
|
|
12
|
+
|
|
13
|
+
export interface GlideRect {
|
|
14
|
+
x: number;
|
|
15
|
+
y: number;
|
|
16
|
+
width: number;
|
|
17
|
+
height: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface GlideApi {
|
|
21
|
+
rect: GlideRect | null;
|
|
22
|
+
active: boolean;
|
|
23
|
+
enter: (el: HTMLElement | null) => void;
|
|
24
|
+
leave: () => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function useGlide<T extends HTMLElement = HTMLElement>(
|
|
28
|
+
containerRef: RefObject<T | null>,
|
|
29
|
+
): GlideApi {
|
|
30
|
+
const [rect, setRect] = useState<GlideRect | null>(null);
|
|
31
|
+
const [active, setActive] = useState(false);
|
|
32
|
+
const enter = useCallback(
|
|
33
|
+
(el: HTMLElement | null) => {
|
|
34
|
+
const c = containerRef.current;
|
|
35
|
+
if (!c || !el) return;
|
|
36
|
+
const cb = c.getBoundingClientRect();
|
|
37
|
+
const tb = el.getBoundingClientRect();
|
|
38
|
+
setRect({ x: tb.left - cb.left, y: tb.top - cb.top, width: tb.width, height: tb.height });
|
|
39
|
+
setActive(true);
|
|
40
|
+
},
|
|
41
|
+
[containerRef],
|
|
42
|
+
);
|
|
43
|
+
const leave = useCallback(() => setActive(false), []);
|
|
44
|
+
return { rect, active, enter, leave };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface GlidePillProps {
|
|
48
|
+
className?: string;
|
|
49
|
+
rect: GlideRect | null;
|
|
50
|
+
active: boolean;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function GlidePill({ className, rect, active }: GlidePillProps) {
|
|
54
|
+
/* `wasActive` trails one render: the first appearance lands in place, later moves travel on the spring. */
|
|
55
|
+
const wasActive = useRef(false);
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
wasActive.current = active;
|
|
58
|
+
});
|
|
59
|
+
const travel = wasActive.current && active ? SM.t.settle : { duration: 0 };
|
|
60
|
+
return (
|
|
61
|
+
<motion.span
|
|
62
|
+
className={className}
|
|
63
|
+
aria-hidden="true"
|
|
64
|
+
initial={false}
|
|
65
|
+
animate={{
|
|
66
|
+
x: rect ? rect.x : 0,
|
|
67
|
+
y: rect ? rect.y : 0,
|
|
68
|
+
width: rect ? rect.width : 0,
|
|
69
|
+
height: rect ? rect.height : 0,
|
|
70
|
+
opacity: active && rect ? 1 : 0,
|
|
71
|
+
}}
|
|
72
|
+
transition={{
|
|
73
|
+
x: travel,
|
|
74
|
+
y: travel,
|
|
75
|
+
width: travel,
|
|
76
|
+
height: travel,
|
|
77
|
+
opacity: { duration: SM.dur.fast, ease: active ? SM.ease.standard : SM.ease.exit },
|
|
78
|
+
}}
|
|
79
|
+
></motion.span>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/* TimeSegments - the segmented HH:MM machine: every keystroke interpreted (never inserted); commit is live and clamped (saturate, never error). */
|
|
4
|
+
|
|
5
|
+
import * as React from 'react';
|
|
6
|
+
|
|
7
|
+
const { useState, useRef, useEffect } = React;
|
|
8
|
+
|
|
9
|
+
type Meridiem = 'AM' | 'PM';
|
|
10
|
+
type Segment = 'h' | 'm';
|
|
11
|
+
interface Pending {
|
|
12
|
+
seg: Segment;
|
|
13
|
+
d: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface TimeSegmentsProps {
|
|
17
|
+
value?: string | null;
|
|
18
|
+
onCommit?: (value: string) => void;
|
|
19
|
+
format?: '24h' | '12h';
|
|
20
|
+
minuteStep?: number;
|
|
21
|
+
min?: string | null;
|
|
22
|
+
max?: string | null;
|
|
23
|
+
disabled?: boolean;
|
|
24
|
+
ariaLabel?: string;
|
|
25
|
+
className?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const tsgPad = (n: number): string => String(n).padStart(2, '0');
|
|
29
|
+
const tsgDisp12 = (h24: number): number => ((h24 + 11) % 12) + 1;
|
|
30
|
+
const tsgToH24 = (d12: number, mer: Meridiem): number => (d12 % 12) + (mer === 'PM' ? 12 : 0);
|
|
31
|
+
|
|
32
|
+
/* feed one digit into a bounded two-digit segment; done = saturated (can't take another). */
|
|
33
|
+
function tsgFeed(pend: number | null, d: number, hi: number): { val: number; done: boolean } {
|
|
34
|
+
if (pend != null) {
|
|
35
|
+
const n = pend * 10 + d;
|
|
36
|
+
if (n <= hi) return { val: n, done: true };
|
|
37
|
+
}
|
|
38
|
+
return { val: d, done: d * 10 > hi };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function TimeSegments({
|
|
42
|
+
value = null,
|
|
43
|
+
onCommit,
|
|
44
|
+
format = '24h',
|
|
45
|
+
minuteStep = 5,
|
|
46
|
+
min = null,
|
|
47
|
+
max = null,
|
|
48
|
+
disabled = false,
|
|
49
|
+
ariaLabel = 'Time',
|
|
50
|
+
className = '',
|
|
51
|
+
}: TimeSegmentsProps) {
|
|
52
|
+
const is12 = format === '12h';
|
|
53
|
+
const seed = value ? value.split(':').map(Number) : [null, null];
|
|
54
|
+
|
|
55
|
+
const [h, setHState] = useState<number | null>(seed[0]);
|
|
56
|
+
const [m, setMState] = useState<number | null>(seed[1]);
|
|
57
|
+
const [mer, setMer] = useState<Meridiem>(seed[0] != null && seed[0] >= 12 ? 'PM' : 'AM');
|
|
58
|
+
const [pend, setPendState] = useState<Pending | null>(null);
|
|
59
|
+
|
|
60
|
+
/* live mirrors - blur fires synchronously before re-render, so blur-commit reads refs, not stale closure state. */
|
|
61
|
+
const hLive = useRef<number | null>(seed[0]);
|
|
62
|
+
const mLive = useRef<number | null>(seed[1]);
|
|
63
|
+
const pendLive = useRef<Pending | null>(null);
|
|
64
|
+
const setH = (v: number | null) => {
|
|
65
|
+
hLive.current = v;
|
|
66
|
+
setHState(v);
|
|
67
|
+
};
|
|
68
|
+
const setM = (v: number | null) => {
|
|
69
|
+
mLive.current = v;
|
|
70
|
+
setMState(v);
|
|
71
|
+
};
|
|
72
|
+
const setPend = (v: Pending | null) => {
|
|
73
|
+
pendLive.current = v;
|
|
74
|
+
setPendState(v);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const hRef = useRef<HTMLSpanElement>(null);
|
|
78
|
+
const mRef = useRef<HTMLSpanElement>(null);
|
|
79
|
+
const merRef = useRef<HTMLSpanElement>(null);
|
|
80
|
+
const lastRef = useRef<string | null>(value || null);
|
|
81
|
+
|
|
82
|
+
/* external value changes re-seed; our own commits (matched via lastRef) are left alone, never clobbering in-flight typing. */
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
if ((value || null) === lastRef.current) return;
|
|
85
|
+
lastRef.current = value || null;
|
|
86
|
+
const p = value ? value.split(':').map(Number) : [null, null];
|
|
87
|
+
setH(p[0]);
|
|
88
|
+
setM(p[1]);
|
|
89
|
+
if (p[0] != null) setMer(p[0] >= 12 ? 'PM' : 'AM');
|
|
90
|
+
setPend(null);
|
|
91
|
+
}, [value]);
|
|
92
|
+
|
|
93
|
+
function tryCommit(nh: number | null, nm: number | null) {
|
|
94
|
+
if (nh == null || nm == null) return;
|
|
95
|
+
let t = tsgPad(nh) + ':' + tsgPad(nm);
|
|
96
|
+
if (min && t < min) t = min;
|
|
97
|
+
if (max && t > max) t = max;
|
|
98
|
+
const p = t.split(':').map(Number);
|
|
99
|
+
if (p[0] !== nh) {
|
|
100
|
+
setH(p[0]);
|
|
101
|
+
setMer(p[0] >= 12 ? 'PM' : 'AM');
|
|
102
|
+
setPend(null);
|
|
103
|
+
}
|
|
104
|
+
if (p[1] !== nm) setM(p[1]);
|
|
105
|
+
if (t === lastRef.current) return;
|
|
106
|
+
lastRef.current = t;
|
|
107
|
+
if (onCommit) onCommit(t);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const focusSeg = (r: React.RefObject<HTMLSpanElement | null>) => {
|
|
111
|
+
if (r.current) r.current.focus();
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
function onHKey(e: React.KeyboardEvent<HTMLSpanElement>) {
|
|
115
|
+
const k = e.key;
|
|
116
|
+
if (/^[0-9]$/.test(k)) {
|
|
117
|
+
const pd = pend && pend.seg === 'h' ? pend.d : null;
|
|
118
|
+
const r = tsgFeed(pd, +k, is12 ? 12 : 23);
|
|
119
|
+
const nh = is12 ? tsgToH24(r.val === 0 ? 12 : r.val, mer) : r.val;
|
|
120
|
+
setH(nh);
|
|
121
|
+
if (!is12) setMer(nh >= 12 ? 'PM' : 'AM');
|
|
122
|
+
setPend(r.done ? null : { seg: 'h', d: r.val });
|
|
123
|
+
if (r.done) {
|
|
124
|
+
focusSeg(mRef);
|
|
125
|
+
tryCommit(nh, mLive.current);
|
|
126
|
+
}
|
|
127
|
+
} else if (k === 'ArrowUp' || k === 'ArrowDown') {
|
|
128
|
+
const dir = k === 'ArrowUp' ? 1 : -1;
|
|
129
|
+
const nh = h == null ? new Date().getHours() : (h + dir + 24) % 24;
|
|
130
|
+
setH(nh);
|
|
131
|
+
setMer(nh >= 12 ? 'PM' : 'AM');
|
|
132
|
+
setPend(null);
|
|
133
|
+
tryCommit(nh, mLive.current);
|
|
134
|
+
} else if (k === 'Backspace' || k === 'Delete') {
|
|
135
|
+
setH(null);
|
|
136
|
+
setPend(null);
|
|
137
|
+
} else if (k === 'ArrowRight' || k === ':' || k === ';') {
|
|
138
|
+
focusSeg(mRef);
|
|
139
|
+
} else if (k === 'Tab') {
|
|
140
|
+
return;
|
|
141
|
+
} else if (k.length === 1 && !e.metaKey && !e.ctrlKey) {
|
|
142
|
+
/* swallow */
|
|
143
|
+
} else return;
|
|
144
|
+
e.preventDefault();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function onMKey(e: React.KeyboardEvent<HTMLSpanElement>) {
|
|
148
|
+
const k = e.key;
|
|
149
|
+
if (/^[0-9]$/.test(k)) {
|
|
150
|
+
const pd = pend && pend.seg === 'm' ? pend.d : null;
|
|
151
|
+
const r = tsgFeed(pd, +k, 59);
|
|
152
|
+
setM(r.val);
|
|
153
|
+
setPend(r.done ? null : { seg: 'm', d: r.val });
|
|
154
|
+
if (r.done) {
|
|
155
|
+
if (is12) focusSeg(merRef);
|
|
156
|
+
tryCommit(hLive.current, r.val);
|
|
157
|
+
}
|
|
158
|
+
} else if (k === 'ArrowUp' || k === 'ArrowDown') {
|
|
159
|
+
const dir = k === 'ArrowUp' ? 1 : -1;
|
|
160
|
+
let nm;
|
|
161
|
+
if (m == null) {
|
|
162
|
+
nm = (Math.round(new Date().getMinutes() / minuteStep) * minuteStep) % 60;
|
|
163
|
+
} else {
|
|
164
|
+
/* step to the NEXT grid point, so 07 + ↑(step 5) lands on 10 */
|
|
165
|
+
const next =
|
|
166
|
+
dir > 0
|
|
167
|
+
? (Math.floor(m / minuteStep) + 1) * minuteStep
|
|
168
|
+
: (Math.ceil(m / minuteStep) - 1) * minuteStep;
|
|
169
|
+
nm = ((next % 60) + 60) % 60;
|
|
170
|
+
}
|
|
171
|
+
setM(nm);
|
|
172
|
+
setPend(null);
|
|
173
|
+
tryCommit(hLive.current, nm);
|
|
174
|
+
} else if (k === 'Backspace' || k === 'Delete') {
|
|
175
|
+
if (m == null && !(pend && pend.seg === 'm')) {
|
|
176
|
+
focusSeg(hRef);
|
|
177
|
+
} else {
|
|
178
|
+
setM(null);
|
|
179
|
+
setPend(null);
|
|
180
|
+
}
|
|
181
|
+
} else if (k === 'ArrowLeft') {
|
|
182
|
+
focusSeg(hRef);
|
|
183
|
+
} else if (k === 'ArrowRight' && is12) {
|
|
184
|
+
focusSeg(merRef);
|
|
185
|
+
} else if (k === 'Tab') {
|
|
186
|
+
return;
|
|
187
|
+
} else if (k.length === 1 && !e.metaKey && !e.ctrlKey) {
|
|
188
|
+
/* swallow */
|
|
189
|
+
} else return;
|
|
190
|
+
e.preventDefault();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function setMeridiem(next: Meridiem) {
|
|
194
|
+
setMer(next);
|
|
195
|
+
if (h != null) {
|
|
196
|
+
const nh = tsgToH24(tsgDisp12(h), next);
|
|
197
|
+
setH(nh);
|
|
198
|
+
tryCommit(nh, mLive.current);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
function onMerKey(e: React.KeyboardEvent<HTMLSpanElement>) {
|
|
202
|
+
const k = e.key;
|
|
203
|
+
if (k === 'a' || k === 'A') setMeridiem('AM');
|
|
204
|
+
else if (k === 'p' || k === 'P') setMeridiem('PM');
|
|
205
|
+
else if (k === 'ArrowUp' || k === 'ArrowDown') setMeridiem(mer === 'AM' ? 'PM' : 'AM');
|
|
206
|
+
else if (k === 'ArrowLeft') focusSeg(mRef);
|
|
207
|
+
else if (k === 'Tab') {
|
|
208
|
+
return;
|
|
209
|
+
} else if (k.length === 1 && !e.metaKey && !e.ctrlKey) {
|
|
210
|
+
/* swallow */
|
|
211
|
+
} else return;
|
|
212
|
+
e.preventDefault();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function onPaste(e: React.ClipboardEvent<HTMLDivElement>) {
|
|
216
|
+
if (disabled) return;
|
|
217
|
+
const txt = (e.clipboardData.getData('text') || '').trim();
|
|
218
|
+
const mt =
|
|
219
|
+
txt.match(/^(\d{1,2})[:h.\s]?(\d{2})?\s*([ap])\.?m?\.?$/i) ||
|
|
220
|
+
txt.match(/^(\d{1,2})[:h.\s]?(\d{2})?$/);
|
|
221
|
+
if (!mt) return;
|
|
222
|
+
e.preventDefault();
|
|
223
|
+
let hh = +mt[1];
|
|
224
|
+
const mm = mt[2] != null ? +mt[2] : 0;
|
|
225
|
+
if (mt[3]) hh = tsgToH24(hh, /^p/i.test(mt[3]) ? 'PM' : 'AM');
|
|
226
|
+
if (hh > 23 || mm > 59) return;
|
|
227
|
+
setH(hh);
|
|
228
|
+
setM(mm);
|
|
229
|
+
setMer(hh >= 12 ? 'PM' : 'AM');
|
|
230
|
+
setPend(null);
|
|
231
|
+
tryCommit(hh, mm);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const onSegBlur = (seg: Segment) => () => {
|
|
235
|
+
if (pendLive.current && pendLive.current.seg === seg) {
|
|
236
|
+
setPend(null);
|
|
237
|
+
tryCommit(hLive.current, mLive.current);
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
/* pending digit overrides canonical display, so typing '0' in 12h shows '00', not a premature '12'. */
|
|
242
|
+
const hText =
|
|
243
|
+
pend && pend.seg === 'h' ? tsgPad(pend.d) : h == null ? '--' : tsgPad(is12 ? tsgDisp12(h) : h);
|
|
244
|
+
const mText = pend && pend.seg === 'm' ? tsgPad(pend.d) : m == null ? '--' : tsgPad(m);
|
|
245
|
+
|
|
246
|
+
const segCls = (empty: boolean): string => 'tsg__seg' + (empty ? ' is-empty' : '');
|
|
247
|
+
const tab = disabled ? -1 : 0;
|
|
248
|
+
|
|
249
|
+
return (
|
|
250
|
+
<div
|
|
251
|
+
className={('tsg' + (disabled ? ' is-disabled' : '') + ' ' + className).trim()}
|
|
252
|
+
role="group"
|
|
253
|
+
aria-label={ariaLabel}
|
|
254
|
+
aria-disabled={disabled || undefined}
|
|
255
|
+
onPaste={onPaste}
|
|
256
|
+
>
|
|
257
|
+
<span
|
|
258
|
+
ref={hRef}
|
|
259
|
+
className={segCls(h == null && !(pend && pend.seg === 'h'))}
|
|
260
|
+
role="spinbutton"
|
|
261
|
+
tabIndex={tab}
|
|
262
|
+
aria-label="Hours"
|
|
263
|
+
aria-valuemin={is12 ? 1 : 0}
|
|
264
|
+
aria-valuemax={is12 ? 12 : 23}
|
|
265
|
+
aria-valuenow={h == null ? undefined : is12 ? tsgDisp12(h) : h}
|
|
266
|
+
aria-valuetext={h == null ? 'Empty' : hText}
|
|
267
|
+
onKeyDown={disabled ? undefined : onHKey}
|
|
268
|
+
onBlur={onSegBlur('h')}
|
|
269
|
+
>
|
|
270
|
+
{hText}
|
|
271
|
+
</span>
|
|
272
|
+
<span className="tsg__sep" aria-hidden="true">
|
|
273
|
+
:
|
|
274
|
+
</span>
|
|
275
|
+
<span
|
|
276
|
+
ref={mRef}
|
|
277
|
+
className={segCls(m == null && !(pend && pend.seg === 'm'))}
|
|
278
|
+
role="spinbutton"
|
|
279
|
+
tabIndex={tab}
|
|
280
|
+
aria-label="Minutes"
|
|
281
|
+
aria-valuemin={0}
|
|
282
|
+
aria-valuemax={59}
|
|
283
|
+
aria-valuenow={m == null ? undefined : m}
|
|
284
|
+
aria-valuetext={m == null ? 'Empty' : mText}
|
|
285
|
+
onKeyDown={disabled ? undefined : onMKey}
|
|
286
|
+
onBlur={onSegBlur('m')}
|
|
287
|
+
>
|
|
288
|
+
{mText}
|
|
289
|
+
</span>
|
|
290
|
+
{is12 ? (
|
|
291
|
+
<span
|
|
292
|
+
ref={merRef}
|
|
293
|
+
className="tsg__seg tsg__seg--mer"
|
|
294
|
+
role="spinbutton"
|
|
295
|
+
tabIndex={tab}
|
|
296
|
+
aria-label="AM or PM"
|
|
297
|
+
aria-valuetext={mer}
|
|
298
|
+
onKeyDown={disabled ? undefined : onMerKey}
|
|
299
|
+
>
|
|
300
|
+
{mer}
|
|
301
|
+
</span>
|
|
302
|
+
) : null}
|
|
303
|
+
</div>
|
|
304
|
+
);
|
|
305
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/* Dialog - styled modal surface; a thin consumer of <Overlay mode="dialog">. */
|
|
4
|
+
import * as React from 'react';
|
|
5
|
+
import { Overlay } from '../overlay/Overlay';
|
|
6
|
+
import { Icon } from '../icon/Icon';
|
|
7
|
+
import { IconSlot } from '../icon/IconSlot';
|
|
8
|
+
|
|
9
|
+
const { useRef: dlgUseRef, useEffect: dlgUseEffect, useId: dlgUseId } = React;
|
|
10
|
+
|
|
11
|
+
export interface DialogProps {
|
|
12
|
+
/** Controlled open state. Omit for uncontrolled (use defaultOpen + trigger). */
|
|
13
|
+
open?: boolean;
|
|
14
|
+
defaultOpen?: boolean;
|
|
15
|
+
onOpenChange?: (open: boolean) => void;
|
|
16
|
+
/** Optional element cloned to open the dialog on click (uncontrolled ergonomics). */
|
|
17
|
+
trigger?: React.ReactElement | null;
|
|
18
|
+
title?: React.ReactNode;
|
|
19
|
+
description?: React.ReactNode;
|
|
20
|
+
size?: 'sm' | 'md' | 'lg';
|
|
21
|
+
/** 'danger' tints the header icon badge + is the convention for destructive confirms. */
|
|
22
|
+
tone?: 'default' | 'danger';
|
|
23
|
+
/** Your own icon node for the header badge (alert dialogs). */
|
|
24
|
+
icon?: React.ReactNode;
|
|
25
|
+
/** Close button + backdrop/Esc dismissal. Default true. */
|
|
26
|
+
dismissible?: boolean;
|
|
27
|
+
/** Action row - a node, or a render fn `(close) => node` so uncontrolled dialogs can dismiss. */
|
|
28
|
+
footer?: React.ReactNode | ((close: () => void) => React.ReactNode);
|
|
29
|
+
children?: React.ReactNode;
|
|
30
|
+
id?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* Flag the body's scroll edges (data-scroll-top/-bottom) so dialog.css :has() can earn the header lift + footer divider. */
|
|
34
|
+
function useScrollEdges(ref: React.RefObject<HTMLElement>) {
|
|
35
|
+
dlgUseEffect(() => {
|
|
36
|
+
const el = ref.current;
|
|
37
|
+
if (!el) return undefined;
|
|
38
|
+
const update = () => {
|
|
39
|
+
el.toggleAttribute('data-scroll-top', el.scrollTop > 1);
|
|
40
|
+
el.toggleAttribute(
|
|
41
|
+
'data-scroll-bottom',
|
|
42
|
+
el.scrollTop + el.clientHeight < el.scrollHeight - 1,
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
update();
|
|
46
|
+
el.addEventListener('scroll', update, { passive: true });
|
|
47
|
+
const ro = new ResizeObserver(update);
|
|
48
|
+
ro.observe(el);
|
|
49
|
+
return () => {
|
|
50
|
+
el.removeEventListener('scroll', update);
|
|
51
|
+
ro.disconnect();
|
|
52
|
+
};
|
|
53
|
+
}, [ref]);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface DialogSurfaceProps {
|
|
57
|
+
baseId: string;
|
|
58
|
+
size: 'sm' | 'md' | 'lg';
|
|
59
|
+
tone: 'default' | 'danger';
|
|
60
|
+
icon: React.ReactNode;
|
|
61
|
+
title: React.ReactNode;
|
|
62
|
+
description: React.ReactNode;
|
|
63
|
+
dismissible: boolean;
|
|
64
|
+
footerContent: React.ReactNode;
|
|
65
|
+
onRequestClose: () => void;
|
|
66
|
+
children?: React.ReactNode;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function DialogSurface({
|
|
70
|
+
baseId,
|
|
71
|
+
size,
|
|
72
|
+
tone,
|
|
73
|
+
icon,
|
|
74
|
+
title,
|
|
75
|
+
description,
|
|
76
|
+
dismissible,
|
|
77
|
+
footerContent,
|
|
78
|
+
onRequestClose,
|
|
79
|
+
children,
|
|
80
|
+
}: DialogSurfaceProps) {
|
|
81
|
+
const bodyRef = dlgUseRef<HTMLDivElement>(null);
|
|
82
|
+
const titleId = baseId + '-title';
|
|
83
|
+
const descId = baseId + '-desc';
|
|
84
|
+
|
|
85
|
+
useScrollEdges(bodyRef);
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<section
|
|
89
|
+
className="dialog"
|
|
90
|
+
role="dialog"
|
|
91
|
+
aria-modal="true"
|
|
92
|
+
data-size={size === 'md' ? undefined : size}
|
|
93
|
+
data-tone={tone === 'default' ? undefined : tone}
|
|
94
|
+
aria-labelledby={title ? titleId : undefined}
|
|
95
|
+
aria-describedby={description ? descId : undefined}
|
|
96
|
+
>
|
|
97
|
+
<header className="dialog__header">
|
|
98
|
+
{icon && (
|
|
99
|
+
<span className="dialog__icon">
|
|
100
|
+
<IconSlot size="md">{icon}</IconSlot>
|
|
101
|
+
</span>
|
|
102
|
+
)}
|
|
103
|
+
<div className="dialog__heading">
|
|
104
|
+
{title && (
|
|
105
|
+
<h2 className="dialog__title" id={titleId}>
|
|
106
|
+
{title}
|
|
107
|
+
</h2>
|
|
108
|
+
)}
|
|
109
|
+
{description && (
|
|
110
|
+
<p className="dialog__desc" id={descId}>
|
|
111
|
+
{description}
|
|
112
|
+
</p>
|
|
113
|
+
)}
|
|
114
|
+
</div>
|
|
115
|
+
{dismissible && (
|
|
116
|
+
<button
|
|
117
|
+
type="button"
|
|
118
|
+
className="dialog__close"
|
|
119
|
+
aria-label="Close dialog"
|
|
120
|
+
onClick={onRequestClose}
|
|
121
|
+
>
|
|
122
|
+
<Icon name="x" size="sm" weight="bold" />
|
|
123
|
+
</button>
|
|
124
|
+
)}
|
|
125
|
+
</header>
|
|
126
|
+
|
|
127
|
+
<div className="dialog__body" ref={bodyRef}>
|
|
128
|
+
{children}
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
{footerContent && <footer className="dialog__footer">{footerContent}</footer>}
|
|
132
|
+
</section>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function Dialog({
|
|
137
|
+
open,
|
|
138
|
+
defaultOpen = false,
|
|
139
|
+
onOpenChange,
|
|
140
|
+
trigger = null,
|
|
141
|
+
title,
|
|
142
|
+
description = null,
|
|
143
|
+
size = 'md',
|
|
144
|
+
tone = 'default',
|
|
145
|
+
icon = null,
|
|
146
|
+
dismissible = true,
|
|
147
|
+
footer = null,
|
|
148
|
+
children,
|
|
149
|
+
id,
|
|
150
|
+
}: DialogProps) {
|
|
151
|
+
const autoId = dlgUseId();
|
|
152
|
+
const baseId = id || 'dialog-' + autoId;
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<Overlay
|
|
156
|
+
mode="dialog"
|
|
157
|
+
id={baseId}
|
|
158
|
+
open={open}
|
|
159
|
+
defaultOpen={defaultOpen}
|
|
160
|
+
onOpenChange={onOpenChange}
|
|
161
|
+
trigger={trigger}
|
|
162
|
+
dismissible={dismissible}
|
|
163
|
+
>
|
|
164
|
+
{({ close }) => (
|
|
165
|
+
<DialogSurface
|
|
166
|
+
baseId={baseId}
|
|
167
|
+
size={size}
|
|
168
|
+
tone={tone}
|
|
169
|
+
icon={icon}
|
|
170
|
+
title={title}
|
|
171
|
+
description={description}
|
|
172
|
+
dismissible={dismissible}
|
|
173
|
+
footerContent={typeof footer === 'function' ? footer(close) : footer}
|
|
174
|
+
onRequestClose={close}
|
|
175
|
+
>
|
|
176
|
+
{children}
|
|
177
|
+
</DialogSurface>
|
|
178
|
+
)}
|
|
179
|
+
</Overlay>
|
|
180
|
+
);
|
|
181
|
+
}
|