@ratio-ui/core 0.3.0 → 0.4.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/dist/catalogue.json +887 -27
- package/dist/components/accordion/Accordion.d.ts +19 -0
- package/dist/components/accordion/Accordion.js +52 -0
- package/dist/components/accordion/index.d.ts +2 -0
- package/dist/components/accordion/index.js +1 -0
- package/dist/components/breadcrumb/Breadcrumb.d.ts +19 -0
- package/dist/components/breadcrumb/Breadcrumb.js +50 -0
- package/dist/components/breadcrumb/index.d.ts +2 -0
- package/dist/components/breadcrumb/index.js +1 -0
- package/dist/components/chart/Chart.d.ts +137 -0
- package/dist/components/chart/Chart.js +238 -0
- package/dist/components/chart/index.d.ts +2 -0
- package/dist/components/chart/index.js +1 -0
- package/dist/components/combobox/Combobox.d.ts +52 -0
- package/dist/components/combobox/Combobox.js +92 -0
- package/dist/components/combobox/index.d.ts +2 -0
- package/dist/components/combobox/index.js +1 -0
- package/dist/components/data-table/DataTable.js +1 -1
- package/dist/components/date-picker/DatePicker.d.ts +3 -2
- package/dist/components/date-picker/DatePicker.js +7 -3
- package/dist/components/date-range-picker/DateRangePicker.d.ts +3 -2
- package/dist/components/date-range-picker/DateRangePicker.js +7 -3
- package/dist/components/drawer/Drawer.d.ts +52 -0
- package/dist/components/drawer/Drawer.js +120 -0
- package/dist/components/drawer/index.d.ts +2 -0
- package/dist/components/drawer/index.js +1 -0
- package/dist/components/field/Field.d.ts +8 -5
- package/dist/components/field/Field.js +9 -3
- package/dist/components/input-otp/InputOtp.d.ts +43 -0
- package/dist/components/input-otp/InputOtp.js +45 -0
- package/dist/components/input-otp/index.d.ts +2 -0
- package/dist/components/input-otp/index.js +1 -0
- package/dist/components/key-value-list/KeyValueList.d.ts +25 -0
- package/dist/components/key-value-list/KeyValueList.js +40 -0
- package/dist/components/key-value-list/index.d.ts +2 -0
- package/dist/components/key-value-list/index.js +1 -0
- package/dist/components/link/Link.js +1 -1
- package/dist/components/page-header/PageHeader.js +8 -8
- package/dist/components/progress/Progress.d.ts +25 -0
- package/dist/components/progress/Progress.js +38 -0
- package/dist/components/progress/index.d.ts +2 -0
- package/dist/components/progress/index.js +1 -0
- package/dist/components/radio-group/RadioGroup.d.ts +18 -0
- package/dist/components/radio-group/RadioGroup.js +32 -0
- package/dist/components/radio-group/index.d.ts +2 -0
- package/dist/components/radio-group/index.js +1 -0
- package/dist/components/scroll-area/ScrollArea.d.ts +12 -0
- package/dist/components/scroll-area/ScrollArea.js +34 -0
- package/dist/components/scroll-area/index.d.ts +2 -0
- package/dist/components/scroll-area/index.js +1 -0
- package/dist/components/separator/Separator.d.ts +15 -0
- package/dist/components/separator/Separator.js +25 -0
- package/dist/components/separator/index.d.ts +2 -0
- package/dist/components/separator/index.js +1 -0
- package/dist/components/sheet/Sheet.d.ts +52 -0
- package/dist/components/sheet/Sheet.js +89 -0
- package/dist/components/sheet/index.d.ts +2 -0
- package/dist/components/sheet/index.js +1 -0
- package/dist/components/summary-chips/SummaryChips.js +1 -1
- package/dist/components/tag-input/TagInput.js +1 -1
- package/dist/index.d.ts +28 -4
- package/dist/index.js +13 -1
- package/dist/lib/cn.js +2 -1
- package/dist/styles.css +1 -1
- package/package.json +52 -1
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Icon } from "../icon/Icon.js";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import { Combobox } from "@base-ui/react/combobox";
|
|
5
|
+
// src/components/combobox/Combobox.tsx
|
|
6
|
+
const toOption = (item) => typeof item === "string" ? {
|
|
7
|
+
value: item,
|
|
8
|
+
label: item
|
|
9
|
+
} : item;
|
|
10
|
+
const ROOT_CLASSES = {
|
|
11
|
+
field: "rt:flex rt:h-9 rt:w-full rt:min-w-0 rt:items-center rt:gap-0.5 rt:rounded-lg rt:bg-background rt:pr-1.5 rt:shadow-ring-subtle rt:transition-colors rt:forced-outline rt:focus-within:shadow-ring-focus rt:has-aria-invalid:shadow-ring-invalid rt:has-aria-invalid:focus-within:shadow-ring-invalid rt:has-disabled:opacity-50",
|
|
12
|
+
input: "rt:h-full rt:min-w-0 rt:flex-1 rt:bg-transparent rt:pl-3 rt:text-b3 rt:text-foreground rt:placeholder:text-muted-foreground rt:outline-none rt:disabled:cursor-not-allowed",
|
|
13
|
+
button: "rt:flex rt:size-6 rt:items-center rt:justify-center rt:rounded-md rt:text-muted-foreground rt:outline-none rt:hover:text-foreground rt:focus-visible:focus-ring rt:disabled:pointer-events-none",
|
|
14
|
+
popup: "rt:group/combobox-content rt:max-h-available rt:w-anchor rt:max-w-available rt:overflow-hidden rt:rounded-lg rt:border rt:border-border rt:bg-popover rt:text-b3 rt:text-popover-foreground rt:outline-hidden rt:transition-colors",
|
|
15
|
+
item: "rt:relative rt:flex rt:h-7 rt:w-full rt:cursor-default rt:items-center rt:gap-2 rt:pr-8 rt:pl-3 rt:text-b3 rt:text-foreground rt:outline-none rt:select-none rt:transition-colors rt:data-highlighted:bg-accent rt:data-highlighted:text-accent-foreground rt:data-disabled:pointer-events-none rt:data-disabled:opacity-50"
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* A field to choose one option from a list, with a search: type to narrow the list, pick with the mouse or Enter.
|
|
19
|
+
* The field looks like an Input and the list like a Popover panel. For several choices shown as chips, use TagInput.
|
|
20
|
+
* Mark it invalid with aria-invalid; give the list an empty text with emptyText. Inside a Field, the label, hint, error
|
|
21
|
+
* and required mark reach the input.
|
|
22
|
+
*/
|
|
23
|
+
function Combobox$1({ items, value, defaultValue, onValueChange, placeholder, emptyText = "No results found", "aria-invalid": ariaInvalid, invalid = false, clearable = true, disabled = false, name, id, ref, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "aria-required": ariaRequired }) {
|
|
24
|
+
const isInvalid = invalid || ariaInvalid === true || ariaInvalid === "true";
|
|
25
|
+
const options = items.map(toOption);
|
|
26
|
+
const key = JSON.stringify(options);
|
|
27
|
+
const labels = useMemo(() => new Map(JSON.parse(key).map((o) => [o.value, o.label])), [key]);
|
|
28
|
+
const root = {
|
|
29
|
+
items: useMemo(() => [...labels.keys()], [labels]),
|
|
30
|
+
disabled,
|
|
31
|
+
name,
|
|
32
|
+
itemToStringLabel: (v) => labels.get(v) ?? v,
|
|
33
|
+
onValueChange: (v, details) => onValueChange?.(v, details)
|
|
34
|
+
};
|
|
35
|
+
const controlled = value !== void 0 ? { value } : {};
|
|
36
|
+
const initial = defaultValue !== void 0 ? { defaultValue } : {};
|
|
37
|
+
return /* @__PURE__ */ jsxs(Combobox.Root, {
|
|
38
|
+
...root,
|
|
39
|
+
...controlled,
|
|
40
|
+
...initial,
|
|
41
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
42
|
+
"data-slot": "combobox",
|
|
43
|
+
className: ROOT_CLASSES.field,
|
|
44
|
+
children: [/* @__PURE__ */ jsx(Combobox.Input, {
|
|
45
|
+
ref,
|
|
46
|
+
id,
|
|
47
|
+
"aria-label": ariaLabel,
|
|
48
|
+
"aria-labelledby": ariaLabelledBy,
|
|
49
|
+
"aria-describedby": ariaDescribedBy,
|
|
50
|
+
"aria-required": ariaRequired,
|
|
51
|
+
"aria-invalid": isInvalid || void 0,
|
|
52
|
+
placeholder,
|
|
53
|
+
className: ROOT_CLASSES.input
|
|
54
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
55
|
+
className: "rt:flex rt:items-center rt:gap-0.5",
|
|
56
|
+
children: [clearable ? /* @__PURE__ */ jsx(Combobox.Clear, {
|
|
57
|
+
"aria-label": "Clear",
|
|
58
|
+
className: ROOT_CLASSES.button,
|
|
59
|
+
children: /* @__PURE__ */ jsx(Icon, { name: "x" })
|
|
60
|
+
}) : null, /* @__PURE__ */ jsx(Combobox.Trigger, {
|
|
61
|
+
"aria-label": "Show options",
|
|
62
|
+
className: ROOT_CLASSES.button,
|
|
63
|
+
children: /* @__PURE__ */ jsx(Icon, { name: "nav-caret-down" })
|
|
64
|
+
})]
|
|
65
|
+
})]
|
|
66
|
+
}), /* @__PURE__ */ jsx(Combobox.Portal, { children: /* @__PURE__ */ jsx(Combobox.Positioner, {
|
|
67
|
+
side: "bottom",
|
|
68
|
+
align: "start",
|
|
69
|
+
sideOffset: 4,
|
|
70
|
+
className: "rt:isolate rt:z-50",
|
|
71
|
+
children: /* @__PURE__ */ jsxs(Combobox.Popup, {
|
|
72
|
+
"data-slot": "combobox-content",
|
|
73
|
+
className: ROOT_CLASSES.popup,
|
|
74
|
+
children: [/* @__PURE__ */ jsx(Combobox.Empty, {
|
|
75
|
+
className: "rt:hidden rt:justify-center rt:py-3 rt:text-center rt:text-muted-foreground rt:group-data-empty/combobox-content:flex",
|
|
76
|
+
children: emptyText
|
|
77
|
+
}), /* @__PURE__ */ jsx(Combobox.List, {
|
|
78
|
+
className: "rt:max-h-72 rt:overflow-y-auto rt:overscroll-contain rt:py-1.5 rt:data-empty:py-0",
|
|
79
|
+
children: (v) => /* @__PURE__ */ jsxs(Combobox.Item, {
|
|
80
|
+
value: v,
|
|
81
|
+
className: ROOT_CLASSES.item,
|
|
82
|
+
children: [labels.get(v), /* @__PURE__ */ jsx(Combobox.ItemIndicator, {
|
|
83
|
+
className: "rt:pointer-events-none rt:absolute rt:right-3 rt:flex",
|
|
84
|
+
children: /* @__PURE__ */ jsx(Icon, { name: "check" })
|
|
85
|
+
})]
|
|
86
|
+
}, v)
|
|
87
|
+
})]
|
|
88
|
+
})
|
|
89
|
+
}) })]
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
export { Combobox$1 as Combobox };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Combobox } from "./Combobox.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Checkbox } from "../checkbox/Checkbox.js";
|
|
2
1
|
import { RatioLink } from "../link-provider/RatioLink.js";
|
|
2
|
+
import { Checkbox } from "../checkbox/Checkbox.js";
|
|
3
3
|
import { Pagination } from "../pagination/Pagination.js";
|
|
4
4
|
import { Table, TableBody, TableBox, TableCell, TableHead, TableHeader, TableRow } from "../table/Table.js";
|
|
5
5
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -12,7 +12,7 @@ export type DatePickerProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'class
|
|
|
12
12
|
value?: Date | null;
|
|
13
13
|
/** The starting day when the field is not controlled. Defaults to empty. */
|
|
14
14
|
defaultValue?: Date | null;
|
|
15
|
-
/** Called with the day when a typed date is complete or a day is picked, and with null when the text is cleared. */
|
|
15
|
+
/** Called with the day when a typed date is complete or a day is picked, and with null when the text is cleared or is not a date. */
|
|
16
16
|
onValueChange?: (value: Date | null) => void;
|
|
17
17
|
/** The days that cannot be picked in the calendar, for example { before: new Date() }. */
|
|
18
18
|
disabledDays?: CalendarProps['disabled'];
|
|
@@ -27,7 +27,8 @@ export type DatePickerProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'class
|
|
|
27
27
|
};
|
|
28
28
|
/**
|
|
29
29
|
* A field that shows a day as DD/MM/YYYY, reads a typed date through the format module and opens a calendar in a popover
|
|
30
|
-
* from the button at its end. Text that is not a date
|
|
30
|
+
* from the button at its end. Text that is not a date reports null (as soon as it is typed over a day, and when the field
|
|
31
|
+
* loses focus) and is marked invalid, aria-invalid, when the field loses focus; the app shows Field's error from the null.
|
|
31
32
|
* Under a name, a form submits the day as YYYY-MM-DD (empty when there is none), never the text on screen.
|
|
32
33
|
*/
|
|
33
34
|
export declare function DatePicker({ value, defaultValue, onValueChange, disabledDays, startMonth, endMonth, size, placeholder, readOnly, disabled, onBlur, ref, name, 'aria-invalid': ariaInvalid, ...rest }: DatePickerProps): JSX.Element;
|
|
@@ -9,7 +9,8 @@ import { useState } from "react";
|
|
|
9
9
|
// src/components/date-picker/DatePicker.tsx
|
|
10
10
|
/**
|
|
11
11
|
* A field that shows a day as DD/MM/YYYY, reads a typed date through the format module and opens a calendar in a popover
|
|
12
|
-
* from the button at its end. Text that is not a date
|
|
12
|
+
* from the button at its end. Text that is not a date reports null (as soon as it is typed over a day, and when the field
|
|
13
|
+
* loses focus) and is marked invalid, aria-invalid, when the field loses focus; the app shows Field's error from the null.
|
|
13
14
|
* Under a name, a form submits the day as YYYY-MM-DD (empty when there is none), never the text on screen.
|
|
14
15
|
*/
|
|
15
16
|
function DatePicker({ value, defaultValue = null, onValueChange, disabledDays, startMonth, endMonth, size = 36, placeholder = FORMAT.datePattern, readOnly, disabled, onBlur, ref, name, "aria-invalid": ariaInvalid, ...rest }) {
|
|
@@ -49,13 +50,16 @@ function DatePicker({ value, defaultValue = null, onValueChange, disabledDays, s
|
|
|
49
50
|
if (next.trim() === "") return commit(null);
|
|
50
51
|
const parsed = parseDate(next);
|
|
51
52
|
if (parsed !== null) commit(parsed);
|
|
53
|
+
else if (day !== null) commit(null);
|
|
52
54
|
},
|
|
53
55
|
onBlur: (event) => {
|
|
54
56
|
onBlur?.(event);
|
|
55
57
|
const parsed = parseDate(text);
|
|
56
58
|
if (text.trim() === "") setMalformed(false);
|
|
57
|
-
else if (parsed === null)
|
|
58
|
-
|
|
59
|
+
else if (parsed === null) {
|
|
60
|
+
setMalformed(true);
|
|
61
|
+
commit(null);
|
|
62
|
+
} else setText(formatDate(parsed));
|
|
59
63
|
}
|
|
60
64
|
}), readOnly ? null : /* @__PURE__ */ jsx(InputGroupAddon, {
|
|
61
65
|
align: "end",
|
|
@@ -17,7 +17,7 @@ export type DateRangePickerProps = Omit<InputHTMLAttributes<HTMLInputElement>, '
|
|
|
17
17
|
value?: DateRangeValue | null;
|
|
18
18
|
/** The starting range when the field is not controlled. Defaults to empty. */
|
|
19
19
|
defaultValue?: DateRangeValue | null;
|
|
20
|
-
/** Called with the range when a typed range is complete or two days are picked, and with null when the text is cleared. */
|
|
20
|
+
/** Called with the range when a typed range is complete or two days are picked, and with null when the text is cleared or is not a range. */
|
|
21
21
|
onValueChange?: (value: DateRangeValue | null) => void;
|
|
22
22
|
/** The days that cannot be picked in the calendar, for example { after: new Date() } for an analytics period. */
|
|
23
23
|
disabledDays?: CalendarProps['disabled'];
|
|
@@ -33,7 +33,8 @@ export type DateRangePickerProps = Omit<InputHTMLAttributes<HTMLInputElement>, '
|
|
|
33
33
|
/**
|
|
34
34
|
* A field that shows a range as two DD/MM/YYYY days, reads a typed range through the format module and opens a calendar in
|
|
35
35
|
* a popover from the button at its end, for analytics periods. Pick the first day, then the last. Text that is not a range
|
|
36
|
-
* in order is
|
|
36
|
+
* in order reports null (as soon as it is typed over a range, and when the field loses focus) and is marked invalid, aria-invalid,
|
|
37
|
+
* when the field loses focus. Under a name, a form submits two fields,
|
|
37
38
|
* `name.from` and `name.to`, each as YYYY-MM-DD (empty when there is no range), never the text on screen.
|
|
38
39
|
*/
|
|
39
40
|
export declare function DateRangePicker({ value, defaultValue, onValueChange, disabledDays, startMonth, endMonth, size, placeholder, readOnly, disabled, onBlur, ref, name, 'aria-invalid': ariaInvalid, ...rest }: DateRangePickerProps): JSX.Element;
|
|
@@ -25,7 +25,8 @@ function parseRange(text) {
|
|
|
25
25
|
/**
|
|
26
26
|
* A field that shows a range as two DD/MM/YYYY days, reads a typed range through the format module and opens a calendar in
|
|
27
27
|
* a popover from the button at its end, for analytics periods. Pick the first day, then the last. Text that is not a range
|
|
28
|
-
* in order is
|
|
28
|
+
* in order reports null (as soon as it is typed over a range, and when the field loses focus) and is marked invalid, aria-invalid,
|
|
29
|
+
* when the field loses focus. Under a name, a form submits two fields,
|
|
29
30
|
* `name.from` and `name.to`, each as YYYY-MM-DD (empty when there is no range), never the text on screen.
|
|
30
31
|
*/
|
|
31
32
|
function DateRangePicker({ value, defaultValue = null, onValueChange, disabledDays, startMonth, endMonth, size = 36, placeholder = `${FORMAT.datePattern}${SEPARATOR}${FORMAT.datePattern}`, readOnly, disabled, onBlur, ref, name, "aria-invalid": ariaInvalid, ...rest }) {
|
|
@@ -66,13 +67,16 @@ function DateRangePicker({ value, defaultValue = null, onValueChange, disabledDa
|
|
|
66
67
|
if (next.trim() === "") return commit(null);
|
|
67
68
|
const parsed = parseRange(next);
|
|
68
69
|
if (parsed !== null) commit(parsed);
|
|
70
|
+
else if (range !== null) commit(null);
|
|
69
71
|
},
|
|
70
72
|
onBlur: (event) => {
|
|
71
73
|
onBlur?.(event);
|
|
72
74
|
const parsed = parseRange(text);
|
|
73
75
|
if (text.trim() === "") setMalformed(false);
|
|
74
|
-
else if (parsed === null)
|
|
75
|
-
|
|
76
|
+
else if (parsed === null) {
|
|
77
|
+
setMalformed(true);
|
|
78
|
+
commit(null);
|
|
79
|
+
} else setText(write(parsed));
|
|
76
80
|
}
|
|
77
81
|
}), readOnly ? null : /* @__PURE__ */ jsx(InputGroupAddon, {
|
|
78
82
|
align: "end",
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { ComponentProps, JSX } from "react";
|
|
2
|
+
import { Drawer } from "@base-ui/react/drawer";
|
|
3
|
+
/** Props of Drawer. */
|
|
4
|
+
export interface DrawerProps extends Drawer.Root.Props {
|
|
5
|
+
/** Whether a short bar shows on the edge the drawer is swiped from, as a hint on phones. Defaults to false. */
|
|
6
|
+
showSwipeHandle?: boolean;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The root of a drawer: a panel people swipe away on a phone. It holds the open state and renders nothing itself.
|
|
10
|
+
* `swipeDirection` picks the edge it leaves by (down, the default, makes a bottom drawer). Pass `open` and
|
|
11
|
+
* `onOpenChange` to control it, or `defaultOpen`. The page behind is inert while it is open, with no scrim over it.
|
|
12
|
+
*/
|
|
13
|
+
declare function Drawer$1({ modal, showSwipeHandle, swipeDirection, ...props }: DrawerProps): JSX.Element;
|
|
14
|
+
/** Props of DrawerTrigger. */
|
|
15
|
+
export type DrawerTriggerProps = Omit<Drawer.Trigger.Props, 'className' | 'style'>;
|
|
16
|
+
/** The button that opens the drawer. */
|
|
17
|
+
export declare function DrawerTrigger(props: DrawerTriggerProps): JSX.Element;
|
|
18
|
+
/** Props of DrawerClose. */
|
|
19
|
+
export type DrawerCloseProps = Omit<Drawer.Close.Props, 'className' | 'style'>;
|
|
20
|
+
/** A button that closes the drawer, such as Done in the footer. */
|
|
21
|
+
export declare function DrawerClose(props: DrawerCloseProps): JSX.Element;
|
|
22
|
+
/** Props of DrawerContent. */
|
|
23
|
+
export interface DrawerContentProps extends Omit<Drawer.Popup.Props, 'className' | 'style'> {
|
|
24
|
+
/** Whether the corner close button shows. It is the way to close without swiping. Defaults to true. */
|
|
25
|
+
showCloseButton?: boolean;
|
|
26
|
+
/** The accessible name of the corner close button. Defaults to Close. */
|
|
27
|
+
closeLabel?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The panel, white with a hairline and 24px corners on its inner edge. A bottom drawer spans the screen and sizes to its
|
|
31
|
+
* content; a side drawer is full height and three quarters of a phone screen, 384px from the small breakpoint up. It
|
|
32
|
+
* follows the finger while it is swiped and otherwise does not move: no slide in or out, no scrim, blur or shadow.
|
|
33
|
+
* Put DrawerHeader, the content and DrawerFooter inside.
|
|
34
|
+
*/
|
|
35
|
+
export declare function DrawerContent({ children, showCloseButton, closeLabel, ...props }: DrawerContentProps): JSX.Element;
|
|
36
|
+
/** Props of DrawerHeader. */
|
|
37
|
+
export type DrawerHeaderProps = Omit<ComponentProps<'div'>, 'className' | 'style'>;
|
|
38
|
+
/** The title and description, stacked, with room on the right for the close button. */
|
|
39
|
+
export declare function DrawerHeader(props: DrawerHeaderProps): JSX.Element;
|
|
40
|
+
/** Props of DrawerFooter. */
|
|
41
|
+
export type DrawerFooterProps = Omit<ComponentProps<'div'>, 'className' | 'style'>;
|
|
42
|
+
/** The actions at the bottom of the drawer, stacked full width for a thumb. */
|
|
43
|
+
export declare function DrawerFooter(props: DrawerFooterProps): JSX.Element;
|
|
44
|
+
/** Props of DrawerTitle. */
|
|
45
|
+
export type DrawerTitleProps = Omit<Drawer.Title.Props, 'className' | 'style'>;
|
|
46
|
+
/** The drawer's name, in the b4 style. It names the drawer for assistive technology. */
|
|
47
|
+
export declare function DrawerTitle(props: DrawerTitleProps): JSX.Element;
|
|
48
|
+
/** Props of DrawerDescription. */
|
|
49
|
+
export type DrawerDescriptionProps = Omit<Drawer.Description.Props, 'className' | 'style'>;
|
|
50
|
+
/** One line that says what the drawer is for, in subdued body text. */
|
|
51
|
+
export declare function DrawerDescription(props: DrawerDescriptionProps): JSX.Element;
|
|
52
|
+
export { Drawer$1 as Drawer };
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { cn } from "../../lib/cn.js";
|
|
2
|
+
import { Icon } from "../icon/Icon.js";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { createContext, use, useMemo } from "react";
|
|
5
|
+
import { Drawer } from "@base-ui/react/drawer";
|
|
6
|
+
// src/components/drawer/Drawer.tsx
|
|
7
|
+
const DrawerContext = createContext({
|
|
8
|
+
modal: true,
|
|
9
|
+
showSwipeHandle: false
|
|
10
|
+
});
|
|
11
|
+
/**
|
|
12
|
+
* The root of a drawer: a panel people swipe away on a phone. It holds the open state and renders nothing itself.
|
|
13
|
+
* `swipeDirection` picks the edge it leaves by (down, the default, makes a bottom drawer). Pass `open` and
|
|
14
|
+
* `onOpenChange` to control it, or `defaultOpen`. The page behind is inert while it is open, with no scrim over it.
|
|
15
|
+
*/
|
|
16
|
+
function Drawer$1({ modal = true, showSwipeHandle = false, swipeDirection = "down", ...props }) {
|
|
17
|
+
const value = useMemo(() => ({
|
|
18
|
+
modal,
|
|
19
|
+
showSwipeHandle
|
|
20
|
+
}), [modal, showSwipeHandle]);
|
|
21
|
+
return /* @__PURE__ */ jsx(DrawerContext, {
|
|
22
|
+
value,
|
|
23
|
+
children: /* @__PURE__ */ jsx(Drawer.Root, {
|
|
24
|
+
modal,
|
|
25
|
+
swipeDirection,
|
|
26
|
+
...props
|
|
27
|
+
})
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/** The button that opens the drawer. */
|
|
31
|
+
function DrawerTrigger(props) {
|
|
32
|
+
return /* @__PURE__ */ jsx(Drawer.Trigger, {
|
|
33
|
+
"data-slot": "drawer-trigger",
|
|
34
|
+
...props
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/** A button that closes the drawer, such as Done in the footer. */
|
|
38
|
+
function DrawerClose(props) {
|
|
39
|
+
return /* @__PURE__ */ jsx(Drawer.Close, {
|
|
40
|
+
"data-slot": "drawer-close",
|
|
41
|
+
...props
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The panel, white with a hairline and 24px corners on its inner edge. A bottom drawer spans the screen and sizes to its
|
|
46
|
+
* content; a side drawer is full height and three quarters of a phone screen, 384px from the small breakpoint up. It
|
|
47
|
+
* follows the finger while it is swiped and otherwise does not move: no slide in or out, no scrim, blur or shadow.
|
|
48
|
+
* Put DrawerHeader, the content and DrawerFooter inside.
|
|
49
|
+
*/
|
|
50
|
+
function DrawerContent({ children, showCloseButton = true, closeLabel = "Close", ...props }) {
|
|
51
|
+
const { modal, showSwipeHandle } = use(DrawerContext);
|
|
52
|
+
return /* @__PURE__ */ jsx(Drawer.Portal, { children: /* @__PURE__ */ jsx(Drawer.Viewport, {
|
|
53
|
+
"data-slot": "drawer-viewport",
|
|
54
|
+
className: cn("rt:pointer-events-none rt:fixed rt:inset-0 rt:z-50", modal === true && "rt:pointer-events-auto"),
|
|
55
|
+
children: /* @__PURE__ */ jsxs(Drawer.Popup, {
|
|
56
|
+
"data-slot": "drawer-popup",
|
|
57
|
+
className: cn("rt:group/drawer-popup rt:pointer-events-auto rt:fixed rt:z-50 rt:flex rt:max-h-full rt:min-h-0 rt:flex-col rt:border-border rt:bg-popover rt:text-b3 rt:text-popover-foreground rt:select-none rt:drawer-swipe rt:focus-visible:focus-ring", "rt:swipe-y:inset-x-0 rt:swipe-x:inset-y-0 rt:swipe-x:w-3/4 rt:swipe-x:flex-row rt:swipe-x:sm:w-96", "rt:swipe-down:bottom-0 rt:swipe-down:rounded-t-3xl rt:swipe-down:border-x rt:swipe-down:border-t", "rt:swipe-up:top-0 rt:swipe-up:rounded-b-3xl rt:swipe-up:border-x rt:swipe-up:border-b", "rt:swipe-left:left-0 rt:swipe-left:rounded-r-3xl rt:swipe-left:border-y rt:swipe-left:border-r", "rt:swipe-right:right-0 rt:swipe-right:rounded-l-3xl rt:swipe-right:border-y rt:swipe-right:border-l"),
|
|
58
|
+
...props,
|
|
59
|
+
children: [
|
|
60
|
+
showSwipeHandle && /* @__PURE__ */ jsx(DrawerSwipeHandle, {}),
|
|
61
|
+
/* @__PURE__ */ jsx(Drawer.Content, {
|
|
62
|
+
"data-slot": "drawer-content",
|
|
63
|
+
className: "rt:flex rt:min-h-0 rt:flex-1 rt:flex-col rt:overflow-y-auto rt:overscroll-contain rt:select-text rt:group-data-swiping/drawer-popup:select-none",
|
|
64
|
+
children
|
|
65
|
+
}),
|
|
66
|
+
showCloseButton && /* @__PURE__ */ jsx(Drawer.Close, {
|
|
67
|
+
"data-slot": "drawer-close",
|
|
68
|
+
"aria-label": closeLabel,
|
|
69
|
+
className: "rt:absolute rt:top-5 rt:right-5 rt:flex rt:size-7 rt:items-center rt:justify-center rt:rounded-md rt:text-icon rt:transition-colors rt:hover:bg-primary-tint rt:focus-visible:focus-ring",
|
|
70
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
71
|
+
name: "x",
|
|
72
|
+
size: 12
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
]
|
|
76
|
+
})
|
|
77
|
+
}) });
|
|
78
|
+
}
|
|
79
|
+
/** A short bar on the edge the drawer is swiped from. It is decorative: the whole panel takes the swipe. */
|
|
80
|
+
function DrawerSwipeHandle() {
|
|
81
|
+
return /* @__PURE__ */ jsx("div", {
|
|
82
|
+
"data-slot": "drawer-swipe-handle",
|
|
83
|
+
"aria-hidden": "true",
|
|
84
|
+
className: "rt:flex rt:shrink-0 rt:items-center rt:justify-center rt:cursor-grab rt:group-swipe-y/drawer-popup:h-5 rt:group-swipe-x/drawer-popup:w-5 rt:group-swipe-up/drawer-popup:order-last rt:group-swipe-left/drawer-popup:order-last rt:active:cursor-grabbing",
|
|
85
|
+
children: /* @__PURE__ */ jsx("span", { className: "rt:block rt:rounded-sm rt:bg-muted rt:group-swipe-y/drawer-popup:h-1 rt:group-swipe-y/drawer-popup:w-12 rt:group-swipe-x/drawer-popup:h-12 rt:group-swipe-x/drawer-popup:w-1" })
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
/** The title and description, stacked, with room on the right for the close button. */
|
|
89
|
+
function DrawerHeader(props) {
|
|
90
|
+
return /* @__PURE__ */ jsx("div", {
|
|
91
|
+
"data-slot": "drawer-header",
|
|
92
|
+
className: "rt:flex rt:shrink-0 rt:flex-col rt:gap-1 rt:p-6 rt:pr-20",
|
|
93
|
+
...props
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/** The actions at the bottom of the drawer, stacked full width for a thumb. */
|
|
97
|
+
function DrawerFooter(props) {
|
|
98
|
+
return /* @__PURE__ */ jsx("div", {
|
|
99
|
+
"data-slot": "drawer-footer",
|
|
100
|
+
className: "rt:mt-auto rt:flex rt:shrink-0 rt:flex-col rt:gap-2 rt:px-6 rt:pb-6",
|
|
101
|
+
...props
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
/** The drawer's name, in the b4 style. It names the drawer for assistive technology. */
|
|
105
|
+
function DrawerTitle(props) {
|
|
106
|
+
return /* @__PURE__ */ jsx(Drawer.Title, {
|
|
107
|
+
"data-slot": "drawer-title",
|
|
108
|
+
className: "rt:text-b4 rt:text-foreground-strong",
|
|
109
|
+
...props
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/** One line that says what the drawer is for, in subdued body text. */
|
|
113
|
+
function DrawerDescription(props) {
|
|
114
|
+
return /* @__PURE__ */ jsx(Drawer.Description, {
|
|
115
|
+
"data-slot": "drawer-description",
|
|
116
|
+
className: "rt:text-b3 rt:text-foreground-subdued",
|
|
117
|
+
...props
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
export { Drawer$1 as Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, DrawerTrigger };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { Drawer, DrawerClose, DrawerCloseProps, DrawerContent, DrawerContentProps, DrawerDescription, DrawerDescriptionProps, DrawerFooter, DrawerFooterProps, DrawerHeader, DrawerHeaderProps, DrawerProps, DrawerTitle, DrawerTitleProps, DrawerTrigger, DrawerTriggerProps } from "./Drawer.js";
|
|
2
|
+
export { Drawer, DrawerClose, type DrawerCloseProps, DrawerContent, type DrawerContentProps, DrawerDescription, type DrawerDescriptionProps, DrawerFooter, type DrawerFooterProps, DrawerHeader, type DrawerHeaderProps, type DrawerProps, DrawerTitle, type DrawerTitleProps, DrawerTrigger, type DrawerTriggerProps };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, DrawerTrigger } from "./Drawer.js";
|
|
@@ -14,22 +14,25 @@ export type FieldProps = {
|
|
|
14
14
|
/** Dims the label and disables the control. */
|
|
15
15
|
disabled?: boolean;
|
|
16
16
|
/**
|
|
17
|
-
* The control: one element, such as Input, which gets an id, aria-
|
|
18
|
-
* When the control sits inside a wrapper such as InputGroup, pass a function instead and
|
|
19
|
-
* onto the input itself.
|
|
17
|
+
* The control: one element, such as Input, which gets an id, aria-labelledby, aria-describedby, aria-invalid,
|
|
18
|
+
* aria-required and disabled. When the control sits inside a wrapper such as InputGroup, pass a function instead and
|
|
19
|
+
* spread the props it receives onto the input itself.
|
|
20
20
|
*/
|
|
21
21
|
children: ReactElement<Record<string, unknown>> | ((props: FieldControlProps) => ReactNode);
|
|
22
22
|
};
|
|
23
23
|
/** The props Field hands to a function child, to spread onto the control. */
|
|
24
24
|
export type FieldControlProps = {
|
|
25
25
|
id: string;
|
|
26
|
+
/** The label's id, so a control that a label cannot point at with htmlFor, such as RadioGroup, still has a name. */
|
|
27
|
+
'aria-labelledby'?: string;
|
|
26
28
|
'aria-describedby'?: string;
|
|
27
29
|
'aria-invalid'?: true;
|
|
28
30
|
'aria-required'?: true;
|
|
29
31
|
disabled?: true;
|
|
30
32
|
};
|
|
31
33
|
/**
|
|
32
|
-
* A form row: label, control, hint and error. It wires the label to the control by id
|
|
33
|
-
* aria-describedby, and sets aria-invalid when there is
|
|
34
|
+
* A form row: label, control, hint and error. It wires the label to the control by id and aria-labelledby (which also
|
|
35
|
+
* names a group such as RadioGroup), links the hint and error with aria-describedby, and sets aria-invalid when there is
|
|
36
|
+
* an error. The control keeps an id it already has.
|
|
34
37
|
*/
|
|
35
38
|
export declare function Field({ label, hint, error, info, required, disabled, children }: FieldProps): JSX.Element;
|
|
@@ -5,8 +5,9 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
5
5
|
import { cloneElement, useId } from "react";
|
|
6
6
|
// src/components/field/Field.tsx
|
|
7
7
|
/**
|
|
8
|
-
* A form row: label, control, hint and error. It wires the label to the control by id
|
|
9
|
-
* aria-describedby, and sets aria-invalid when there is
|
|
8
|
+
* A form row: label, control, hint and error. It wires the label to the control by id and aria-labelledby (which also
|
|
9
|
+
* names a group such as RadioGroup), links the hint and error with aria-describedby, and sets aria-invalid when there is
|
|
10
|
+
* an error. The control keeps an id it already has.
|
|
10
11
|
*/
|
|
11
12
|
function Field({ label, hint, error, info, required, disabled, children }) {
|
|
12
13
|
const generatedId = useId();
|
|
@@ -15,9 +16,13 @@ function Field({ label, hint, error, info, required, disabled, children }) {
|
|
|
15
16
|
const hasError = error !== void 0 && error !== null && error !== false && error !== "";
|
|
16
17
|
const hasHint = !hasError && hint !== void 0 && hint !== null && hint !== false && hint !== "";
|
|
17
18
|
const messageId = `${id}-message`;
|
|
18
|
-
const
|
|
19
|
+
const labelId = `${id}-label`;
|
|
20
|
+
const ownProp = (name) => typeof children === "function" ? void 0 : children.props[name];
|
|
21
|
+
const ids = (...parts) => parts.filter((part) => typeof part === "string" && part !== "").join(" ");
|
|
22
|
+
const described = ids(ownProp("aria-describedby"), hasError || hasHint ? messageId : void 0);
|
|
19
23
|
const wiring = {
|
|
20
24
|
id,
|
|
25
|
+
"aria-labelledby": ids(labelId, ownProp("aria-labelledby")),
|
|
21
26
|
...described === "" ? {} : { "aria-describedby": described },
|
|
22
27
|
...hasError ? { "aria-invalid": true } : {},
|
|
23
28
|
...required === true ? { "aria-required": true } : {},
|
|
@@ -31,6 +36,7 @@ function Field({ label, hint, error, info, required, disabled, children }) {
|
|
|
31
36
|
/* @__PURE__ */ jsxs("div", {
|
|
32
37
|
className: `rt:flex rt:items-center rt:gap-1.5${disabled === true ? " rt:opacity-50" : ""}`,
|
|
33
38
|
children: [/* @__PURE__ */ jsxs(Label, {
|
|
39
|
+
id: labelId,
|
|
34
40
|
htmlFor: id,
|
|
35
41
|
children: [label, required === true ? /* @__PURE__ */ jsx("span", {
|
|
36
42
|
"aria-hidden": "true",
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { JSX, Ref } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* The props of InputOtp. Mark a wrong code with aria-invalid, which draws the error ring on every slot. Inside a Field,
|
|
4
|
+
* spread the props of its function child: they reach the hidden input.
|
|
5
|
+
*/
|
|
6
|
+
export type InputOtpProps = {
|
|
7
|
+
/** The number of digits. Defaults to 6. */
|
|
8
|
+
length?: number;
|
|
9
|
+
/** The code typed so far, for a controlled field. */
|
|
10
|
+
value?: string;
|
|
11
|
+
/** The code typed so far, for an uncontrolled field. */
|
|
12
|
+
defaultValue?: string;
|
|
13
|
+
/** Called with the whole code on every change, including a paste. */
|
|
14
|
+
onValueChange?: (value: string) => void;
|
|
15
|
+
/** Called once with the code when every slot is filled. */
|
|
16
|
+
onComplete?: (value: string) => void;
|
|
17
|
+
/** Stops editing and halves the opacity. */
|
|
18
|
+
disabled?: boolean;
|
|
19
|
+
/** Draws the error ring on every slot. */
|
|
20
|
+
'aria-invalid'?: boolean | 'true' | 'false';
|
|
21
|
+
/** The accessible name, for example "6-digit code". */
|
|
22
|
+
'aria-label'?: string;
|
|
23
|
+
/** The id of an element that names the field. */
|
|
24
|
+
'aria-labelledby'?: string;
|
|
25
|
+
/** The ids of the elements that describe the field, such as its hint or error. Field sets it. */
|
|
26
|
+
'aria-describedby'?: string;
|
|
27
|
+
/** Tells assistive technology that the code must be filled. Field sets it for a required field. */
|
|
28
|
+
'aria-required'?: boolean | 'true' | 'false';
|
|
29
|
+
/** The id of the hidden input, for a label's htmlFor. Field sets it. */
|
|
30
|
+
id?: string;
|
|
31
|
+
/** The form field name. */
|
|
32
|
+
name?: string;
|
|
33
|
+
/** Focuses the field on mount. */
|
|
34
|
+
autoFocus?: boolean;
|
|
35
|
+
/** Receives the hidden input element. */
|
|
36
|
+
ref?: Ref<HTMLInputElement>;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* A one-time-code field: six 44px digit slots (set `length` for another count) over one hidden input, so the browser
|
|
40
|
+
* offers the SMS code (autocomplete one-time-code), a pasted code fills every slot, and the numeric keypad opens on phones.
|
|
41
|
+
* Digits only.
|
|
42
|
+
*/
|
|
43
|
+
export declare function InputOtp({ length, ref, disabled, onValueChange, ...rest }: InputOtpProps): JSX.Element;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { use } from "react";
|
|
3
|
+
import { OTPInput, OTPInputContext, REGEXP_ONLY_DIGITS } from "input-otp";
|
|
4
|
+
// src/components/input-otp/InputOtp.tsx
|
|
5
|
+
const slotClass = "rt:relative rt:flex rt:size-11 rt:items-center rt:justify-center rt:rounded-lg rt:bg-background rt:text-otp rt:text-foreground rt:shadow-ring-subtle rt:transition-colors rt:forced-outline rt:data-active:shadow-ring-focus rt:aria-invalid:shadow-ring-invalid rt:aria-invalid:data-active:shadow-ring-invalid";
|
|
6
|
+
function Slot({ index, invalid }) {
|
|
7
|
+
const { char, hasFakeCaret, isActive } = use(OTPInputContext).slots[index] ?? {};
|
|
8
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
9
|
+
"data-slot": "input-otp-slot",
|
|
10
|
+
"data-active": isActive ? "" : void 0,
|
|
11
|
+
"aria-invalid": invalid || void 0,
|
|
12
|
+
className: slotClass,
|
|
13
|
+
children: [char, hasFakeCaret ? /* @__PURE__ */ jsx("div", {
|
|
14
|
+
className: "rt:pointer-events-none rt:absolute rt:inset-0 rt:flex rt:items-center rt:justify-center",
|
|
15
|
+
children: /* @__PURE__ */ jsx("div", { className: "rt:h-5 rt:w-px rt:bg-foreground" })
|
|
16
|
+
}) : null]
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* A one-time-code field: six 44px digit slots (set `length` for another count) over one hidden input, so the browser
|
|
21
|
+
* offers the SMS code (autocomplete one-time-code), a pasted code fills every slot, and the numeric keypad opens on phones.
|
|
22
|
+
* Digits only.
|
|
23
|
+
*/
|
|
24
|
+
function InputOtp({ length = 6, ref, disabled, onValueChange, ...rest }) {
|
|
25
|
+
return /* @__PURE__ */ jsx(OTPInput, {
|
|
26
|
+
"data-slot": "input-otp",
|
|
27
|
+
ref,
|
|
28
|
+
maxLength: length,
|
|
29
|
+
pattern: REGEXP_ONLY_DIGITS,
|
|
30
|
+
pasteTransformer: (pasted) => pasted.replace(/\D/g, ""),
|
|
31
|
+
inputMode: "numeric",
|
|
32
|
+
autoComplete: "one-time-code",
|
|
33
|
+
spellCheck: false,
|
|
34
|
+
disabled,
|
|
35
|
+
onChange: onValueChange,
|
|
36
|
+
containerClassName: "rt:flex rt:items-center rt:gap-2 rt:has-disabled:opacity-50",
|
|
37
|
+
className: "rt:disabled:cursor-not-allowed",
|
|
38
|
+
...rest,
|
|
39
|
+
children: Array.from({ length }, (_, index) => /* @__PURE__ */ jsx(Slot, {
|
|
40
|
+
index,
|
|
41
|
+
invalid: rest["aria-invalid"] === true || rest["aria-invalid"] === "true"
|
|
42
|
+
}, index))
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
export { InputOtp };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { InputOtp } from "./InputOtp.js";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { JSX, ReactNode } from "react";
|
|
2
|
+
/** One row of a KeyValueList. */
|
|
3
|
+
export type KeyValueItem = {
|
|
4
|
+
/** The label, shown on the left. It is the row key, so keep labels unique. */
|
|
5
|
+
label: string;
|
|
6
|
+
/** The value, shown on the right, already formatted (money and dates come from the format module). Empty shows the placeholder. */
|
|
7
|
+
value?: ReactNode;
|
|
8
|
+
/** An optional action, such as Edit, shown after the value. Its button is named with the action label and the row label. */
|
|
9
|
+
action?: {
|
|
10
|
+
label: string;
|
|
11
|
+
onClick: () => void;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
/** The props of KeyValueList. */
|
|
15
|
+
export type KeyValueListProps = {
|
|
16
|
+
/** The rows, in order. */
|
|
17
|
+
items: KeyValueItem[];
|
|
18
|
+
/** What an empty value shows. Defaults to "Not set". */
|
|
19
|
+
emptyValue?: string;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Settings rows in a hairline bordered panel: the label on the left, the value on the right and an optional action. It is
|
|
23
|
+
* a description list, so a screen reader announces each label with its value.
|
|
24
|
+
*/
|
|
25
|
+
export declare function KeyValueList({ items, emptyValue }: KeyValueListProps): JSX.Element;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Text } from "../text/Text.js";
|
|
2
|
+
import { Button } from "../button/Button.js";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
// src/components/key-value-list/KeyValueList.tsx
|
|
5
|
+
/**
|
|
6
|
+
* Settings rows in a hairline bordered panel: the label on the left, the value on the right and an optional action. It is
|
|
7
|
+
* a description list, so a screen reader announces each label with its value.
|
|
8
|
+
*/
|
|
9
|
+
function KeyValueList({ items, emptyValue = "Not set" }) {
|
|
10
|
+
return /* @__PURE__ */ jsx("dl", {
|
|
11
|
+
className: "rt:m-0 rt:overflow-hidden rt:rounded-lg rt:border rt:border-border rt:bg-background",
|
|
12
|
+
children: items.map((item) => {
|
|
13
|
+
const empty = item.value === void 0 || item.value === null || item.value === "";
|
|
14
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
15
|
+
className: "rt:flex rt:min-h-11 rt:items-center rt:justify-between rt:gap-3 rt:border-border rt:px-3 rt:py-2 rt:not-first:border-t",
|
|
16
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
17
|
+
as: "dt",
|
|
18
|
+
variant: "b3",
|
|
19
|
+
tone: "subdued",
|
|
20
|
+
children: item.label
|
|
21
|
+
}), /* @__PURE__ */ jsxs("dd", {
|
|
22
|
+
className: "rt:m-0 rt:flex rt:min-w-0 rt:items-center rt:justify-end rt:gap-3 rt:text-right",
|
|
23
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
24
|
+
variant: "b3",
|
|
25
|
+
tone: empty ? "muted" : "default",
|
|
26
|
+
children: empty ? emptyValue : item.value
|
|
27
|
+
}), item.action && /* @__PURE__ */ jsxs(Button, {
|
|
28
|
+
variant: "link",
|
|
29
|
+
onClick: item.action.onClick,
|
|
30
|
+
children: [item.action.label, /* @__PURE__ */ jsxs("span", {
|
|
31
|
+
className: "rt:sr-only",
|
|
32
|
+
children: [" ", item.label]
|
|
33
|
+
})]
|
|
34
|
+
})]
|
|
35
|
+
})]
|
|
36
|
+
}, item.label);
|
|
37
|
+
})
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
export { KeyValueList };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { KeyValueList } from "./KeyValueList.js";
|