@ratio-ui/core 0.2.0 → 0.3.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 +1091 -10
- package/dist/components/alert/Alert.d.ts +18 -0
- package/dist/components/alert/Alert.js +67 -0
- package/dist/components/alert/index.d.ts +2 -0
- package/dist/components/alert/index.js +1 -0
- package/dist/components/alert-dialog/AlertDialog.d.ts +48 -0
- package/dist/components/alert-dialog/AlertDialog.js +76 -0
- package/dist/components/alert-dialog/index.d.ts +2 -0
- package/dist/components/alert-dialog/index.js +1 -0
- package/dist/components/app-shell/AppShell.js +1 -1
- package/dist/components/calendar/Calendar.d.ts +20 -0
- package/dist/components/calendar/Calendar.js +119 -0
- package/dist/components/calendar/index.d.ts +2 -0
- package/dist/components/calendar/index.js +1 -0
- package/dist/components/checkbox-list/CheckboxList.d.ts +36 -0
- package/dist/components/checkbox-list/CheckboxList.js +93 -0
- package/dist/components/checkbox-list/index.d.ts +2 -0
- package/dist/components/checkbox-list/index.js +1 -0
- package/dist/components/collapsible/Collapsible.d.ts +24 -0
- package/dist/components/collapsible/Collapsible.js +58 -0
- package/dist/components/collapsible/index.d.ts +2 -0
- package/dist/components/collapsible/index.js +1 -0
- package/dist/components/data-table/DataTable.js +1 -1
- package/dist/components/date-picker/DatePicker.d.ts +33 -0
- package/dist/components/date-picker/DatePicker.js +104 -0
- package/dist/components/date-picker/index.d.ts +2 -0
- package/dist/components/date-picker/index.js +1 -0
- package/dist/components/date-range-picker/DateRangePicker.d.ts +39 -0
- package/dist/components/date-range-picker/DateRangePicker.js +142 -0
- package/dist/components/date-range-picker/index.d.ts +2 -0
- package/dist/components/date-range-picker/index.js +1 -0
- package/dist/components/field/Field.d.ts +35 -0
- package/dist/components/field/Field.js +67 -0
- package/dist/components/field/Label.d.ts +8 -0
- package/dist/components/field/Label.js +13 -0
- package/dist/components/field/index.d.ts +3 -0
- package/dist/components/field/index.js +3 -0
- package/dist/components/filter-tabs/FilterTabs.js +1 -1
- package/dist/components/global-search/GlobalSearch.js +1 -1
- package/dist/components/icon/registry.d.ts +2 -2
- package/dist/components/icon/registry.js +5 -1
- package/dist/components/input-group/InputGroup.js +1 -1
- package/dist/components/link/Link.d.ts +22 -0
- package/dist/components/link/Link.js +31 -0
- package/dist/components/link/index.d.ts +2 -0
- package/dist/components/link/index.js +1 -0
- package/dist/components/media-grid/MediaGrid.d.ts +49 -0
- package/dist/components/media-grid/MediaGrid.js +303 -0
- package/dist/components/media-grid/index.d.ts +2 -0
- package/dist/components/media-grid/index.js +1 -0
- package/dist/components/money-input/MoneyInput.d.ts +30 -0
- package/dist/components/money-input/MoneyInput.js +72 -0
- package/dist/components/money-input/index.d.ts +2 -0
- package/dist/components/money-input/index.js +1 -0
- package/dist/components/page-header/PageHeader.js +1 -1
- package/dist/components/save-bar/SaveBar.d.ts +27 -0
- package/dist/components/save-bar/SaveBar.js +37 -0
- package/dist/components/save-bar/index.d.ts +2 -0
- package/dist/components/save-bar/index.js +1 -0
- package/dist/components/search-field/SearchField.js +1 -1
- package/dist/components/select/Select.d.ts +40 -0
- package/dist/components/select/Select.js +84 -0
- package/dist/components/select/index.d.ts +2 -0
- package/dist/components/select/index.js +1 -0
- package/dist/components/side-nav/SideNav.js +3 -3
- package/dist/components/summary-chips/SummaryChips.d.ts +17 -0
- package/dist/components/summary-chips/SummaryChips.js +19 -0
- package/dist/components/summary-chips/index.d.ts +2 -0
- package/dist/components/summary-chips/index.js +1 -0
- package/dist/components/switch/Switch.d.ts +10 -0
- package/dist/components/switch/Switch.js +20 -0
- package/dist/components/switch/index.d.ts +2 -0
- package/dist/components/switch/index.js +1 -0
- package/dist/components/table/Table.js +1 -1
- package/dist/components/tag-input/TagInput.d.ts +44 -0
- package/dist/components/tag-input/TagInput.js +102 -0
- package/dist/components/tag-input/index.d.ts +2 -0
- package/dist/components/tag-input/index.js +1 -0
- package/dist/components/textarea/Textarea.d.ts +15 -0
- package/dist/components/textarea/Textarea.js +21 -0
- package/dist/components/textarea/index.d.ts +2 -0
- package/dist/components/textarea/index.js +1 -0
- package/dist/components/top-bar/StoreSwitcher.js +1 -1
- package/dist/components/top-bar/TopBar.js +2 -2
- package/dist/components/unit-input/UnitInput.d.ts +33 -0
- package/dist/components/unit-input/UnitInput.js +47 -0
- package/dist/components/unit-input/index.d.ts +2 -0
- package/dist/components/unit-input/index.js +1 -0
- package/dist/index.d.ts +43 -6
- package/dist/index.js +26 -7
- package/dist/lib/cn.js +7 -1
- package/dist/lib/format.d.ts +65 -4
- package/dist/lib/format.js +163 -6
- package/dist/styles.css +1 -1
- package/package.json +78 -1
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { CalendarProps } from "../calendar/Calendar.js";
|
|
2
|
+
import "../calendar/index.js";
|
|
3
|
+
import { InputGroupSize } from "../input-group/InputGroup.js";
|
|
4
|
+
import "../input-group/index.js";
|
|
5
|
+
import { InputHTMLAttributes, JSX, Ref } from "react";
|
|
6
|
+
/**
|
|
7
|
+
* The props of DatePicker: the HTML attributes of an input, except className, style, size and the ones the field sets itself,
|
|
8
|
+
* with the day as a Date in and out. Mark an invalid field with aria-invalid, which draws the error ring around the group.
|
|
9
|
+
*/
|
|
10
|
+
export type DatePickerProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'className' | 'style' | 'size' | 'type' | 'value' | 'defaultValue' | 'onChange' | 'min' | 'max'> & {
|
|
11
|
+
/** The day, or null for an empty field. Pass it to control the field. */
|
|
12
|
+
value?: Date | null;
|
|
13
|
+
/** The starting day when the field is not controlled. Defaults to empty. */
|
|
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. */
|
|
16
|
+
onValueChange?: (value: Date | null) => void;
|
|
17
|
+
/** The days that cannot be picked in the calendar, for example { before: new Date() }. */
|
|
18
|
+
disabledDays?: CalendarProps['disabled'];
|
|
19
|
+
/** The first month the calendar can show. */
|
|
20
|
+
startMonth?: Date;
|
|
21
|
+
/** The last month the calendar can show. */
|
|
22
|
+
endMonth?: Date;
|
|
23
|
+
/** The height in px: 36 for forms, 32 for fields inside a table. Defaults to 36. */
|
|
24
|
+
size?: InputGroupSize;
|
|
25
|
+
/** Receives the input element. */
|
|
26
|
+
ref?: Ref<HTMLInputElement>;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
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 is marked invalid when the field loses focus, and reports nothing.
|
|
31
|
+
* Under a name, a form submits the day as YYYY-MM-DD (empty when there is none), never the text on screen.
|
|
32
|
+
*/
|
|
33
|
+
export declare function DatePicker({ value, defaultValue, onValueChange, disabledDays, startMonth, endMonth, size, placeholder, readOnly, disabled, onBlur, ref, name, 'aria-invalid': ariaInvalid, ...rest }: DatePickerProps): JSX.Element;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { Icon } from "../icon/Icon.js";
|
|
2
|
+
import { Button } from "../button/Button.js";
|
|
3
|
+
import { FORMAT, formatDate, formatIsoDate, parseDate } from "../../lib/format.js";
|
|
4
|
+
import { Calendar } from "../calendar/Calendar.js";
|
|
5
|
+
import { InputGroup, InputGroupAddon, InputGroupInput } from "../input-group/InputGroup.js";
|
|
6
|
+
import { Popover, PopoverContent, PopoverTrigger } from "../popover/Popover.js";
|
|
7
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
import { useState } from "react";
|
|
9
|
+
// src/components/date-picker/DatePicker.tsx
|
|
10
|
+
/**
|
|
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 is marked invalid when the field loses focus, and reports nothing.
|
|
13
|
+
* Under a name, a form submits the day as YYYY-MM-DD (empty when there is none), never the text on screen.
|
|
14
|
+
*/
|
|
15
|
+
function DatePicker({ value, defaultValue = null, onValueChange, disabledDays, startMonth, endMonth, size = 36, placeholder = FORMAT.datePattern, readOnly, disabled, onBlur, ref, name, "aria-invalid": ariaInvalid, ...rest }) {
|
|
16
|
+
const controlled = value !== void 0;
|
|
17
|
+
const [inner, setInner] = useState(defaultValue);
|
|
18
|
+
const day = controlled ? value : inner;
|
|
19
|
+
const [text, setText] = useState(() => day === null ? "" : formatDate(day));
|
|
20
|
+
const [seen, setSeen] = useState(day);
|
|
21
|
+
const [malformed, setMalformed] = useState(false);
|
|
22
|
+
const [open, setOpen] = useState(false);
|
|
23
|
+
if ((seen?.getTime() ?? null) !== (day?.getTime() ?? null)) {
|
|
24
|
+
setSeen(day);
|
|
25
|
+
if ((day?.getTime() ?? null) !== (parseDate(text)?.getTime() ?? null)) setText(day === null ? "" : formatDate(day));
|
|
26
|
+
setMalformed(false);
|
|
27
|
+
}
|
|
28
|
+
const commit = (next) => {
|
|
29
|
+
setSeen(next);
|
|
30
|
+
if (!controlled) setInner(next);
|
|
31
|
+
onValueChange?.(next);
|
|
32
|
+
};
|
|
33
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(InputGroup, {
|
|
34
|
+
size,
|
|
35
|
+
children: [/* @__PURE__ */ jsx(InputGroupInput, {
|
|
36
|
+
...rest,
|
|
37
|
+
ref,
|
|
38
|
+
type: "text",
|
|
39
|
+
autoComplete: "off",
|
|
40
|
+
placeholder,
|
|
41
|
+
readOnly,
|
|
42
|
+
disabled,
|
|
43
|
+
"aria-invalid": ariaInvalid ?? (malformed ? true : void 0),
|
|
44
|
+
value: text,
|
|
45
|
+
onChange: (event) => {
|
|
46
|
+
const next = event.target.value;
|
|
47
|
+
setText(next);
|
|
48
|
+
setMalformed(false);
|
|
49
|
+
if (next.trim() === "") return commit(null);
|
|
50
|
+
const parsed = parseDate(next);
|
|
51
|
+
if (parsed !== null) commit(parsed);
|
|
52
|
+
},
|
|
53
|
+
onBlur: (event) => {
|
|
54
|
+
onBlur?.(event);
|
|
55
|
+
const parsed = parseDate(text);
|
|
56
|
+
if (text.trim() === "") setMalformed(false);
|
|
57
|
+
else if (parsed === null) setMalformed(true);
|
|
58
|
+
else setText(formatDate(parsed));
|
|
59
|
+
}
|
|
60
|
+
}), readOnly ? null : /* @__PURE__ */ jsx(InputGroupAddon, {
|
|
61
|
+
align: "end",
|
|
62
|
+
children: /* @__PURE__ */ jsxs(Popover, {
|
|
63
|
+
open,
|
|
64
|
+
onOpenChange: setOpen,
|
|
65
|
+
children: [/* @__PURE__ */ jsx(PopoverTrigger, {
|
|
66
|
+
disabled,
|
|
67
|
+
render: /* @__PURE__ */ jsx(Button, {
|
|
68
|
+
variant: "ghost",
|
|
69
|
+
iconOnly: true,
|
|
70
|
+
"aria-label": "Open calendar",
|
|
71
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
72
|
+
name: "calendar",
|
|
73
|
+
size: 12
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
}), /* @__PURE__ */ jsx(PopoverContent, {
|
|
77
|
+
align: "end",
|
|
78
|
+
children: /* @__PURE__ */ jsx(Calendar, {
|
|
79
|
+
mode: "single",
|
|
80
|
+
required: true,
|
|
81
|
+
selected: day ?? void 0,
|
|
82
|
+
defaultMonth: day ?? void 0,
|
|
83
|
+
onSelect: (picked) => {
|
|
84
|
+
setText(formatDate(picked));
|
|
85
|
+
setMalformed(false);
|
|
86
|
+
commit(picked);
|
|
87
|
+
setOpen(false);
|
|
88
|
+
},
|
|
89
|
+
disabled: disabledDays,
|
|
90
|
+
startMonth,
|
|
91
|
+
endMonth
|
|
92
|
+
})
|
|
93
|
+
})]
|
|
94
|
+
})
|
|
95
|
+
})]
|
|
96
|
+
}), name !== void 0 && /* @__PURE__ */ jsx("input", {
|
|
97
|
+
type: "hidden",
|
|
98
|
+
name,
|
|
99
|
+
form: rest.form,
|
|
100
|
+
value: day === null ? "" : formatIsoDate(day),
|
|
101
|
+
disabled
|
|
102
|
+
})] });
|
|
103
|
+
}
|
|
104
|
+
export { DatePicker };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { DatePicker } from "./DatePicker.js";
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { CalendarProps } from "../calendar/Calendar.js";
|
|
2
|
+
import "../calendar/index.js";
|
|
3
|
+
import { InputGroupSize } from "../input-group/InputGroup.js";
|
|
4
|
+
import "../input-group/index.js";
|
|
5
|
+
import { InputHTMLAttributes, JSX, Ref } from "react";
|
|
6
|
+
/** A complete range of days, the first day and the last day (the same day for a range of one). */
|
|
7
|
+
export type DateRangeValue = {
|
|
8
|
+
from: Date;
|
|
9
|
+
to: Date;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* The props of DateRangePicker: the HTML attributes of an input, except className, style, size and the ones the field sets
|
|
13
|
+
* itself, with the range as a first and a last day in and out. Mark an invalid field with aria-invalid.
|
|
14
|
+
*/
|
|
15
|
+
export type DateRangePickerProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'className' | 'style' | 'size' | 'type' | 'value' | 'defaultValue' | 'onChange' | 'min' | 'max'> & {
|
|
16
|
+
/** The range, or null for an empty field. Pass it to control the field. */
|
|
17
|
+
value?: DateRangeValue | null;
|
|
18
|
+
/** The starting range when the field is not controlled. Defaults to empty. */
|
|
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. */
|
|
21
|
+
onValueChange?: (value: DateRangeValue | null) => void;
|
|
22
|
+
/** The days that cannot be picked in the calendar, for example { after: new Date() } for an analytics period. */
|
|
23
|
+
disabledDays?: CalendarProps['disabled'];
|
|
24
|
+
/** The first month the calendar can show. */
|
|
25
|
+
startMonth?: Date;
|
|
26
|
+
/** The last month the calendar can show. */
|
|
27
|
+
endMonth?: Date;
|
|
28
|
+
/** The height in px: 36 for forms, 32 for fields inside a table. Defaults to 36. */
|
|
29
|
+
size?: InputGroupSize;
|
|
30
|
+
/** Receives the input element. */
|
|
31
|
+
ref?: Ref<HTMLInputElement>;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
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
|
+
* 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 marked invalid when the field loses focus, and reports nothing. Under a name, a form submits two fields,
|
|
37
|
+
* `name.from` and `name.to`, each as YYYY-MM-DD (empty when there is no range), never the text on screen.
|
|
38
|
+
*/
|
|
39
|
+
export declare function DateRangePicker({ value, defaultValue, onValueChange, disabledDays, startMonth, endMonth, size, placeholder, readOnly, disabled, onBlur, ref, name, 'aria-invalid': ariaInvalid, ...rest }: DateRangePickerProps): JSX.Element;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { Icon } from "../icon/Icon.js";
|
|
2
|
+
import { Button } from "../button/Button.js";
|
|
3
|
+
import { FORMAT, formatDate, formatIsoDate, parseDate } from "../../lib/format.js";
|
|
4
|
+
import { Calendar } from "../calendar/Calendar.js";
|
|
5
|
+
import { InputGroup, InputGroupAddon, InputGroupInput } from "../input-group/InputGroup.js";
|
|
6
|
+
import { Popover, PopoverContent, PopoverTrigger } from "../popover/Popover.js";
|
|
7
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
import { useState } from "react";
|
|
9
|
+
// src/components/date-range-picker/DateRangePicker.tsx
|
|
10
|
+
const SEPARATOR = " – ";
|
|
11
|
+
const SPLIT = /\s+[-–—]\s+|\s*[–—]\s*/;
|
|
12
|
+
const same = (a, b) => a === null || b === null ? a === b : a.from.getTime() === b.from.getTime() && a.to.getTime() === b.to.getTime();
|
|
13
|
+
const write = (range) => range === null ? "" : `${formatDate(range.from)}${SEPARATOR}${formatDate(range.to)}`;
|
|
14
|
+
/** Reads "28/10/2026 – 03/11/2026" (a dash, en dash or em dash between) as a range, or null when it is not a range in order. */
|
|
15
|
+
function parseRange(text) {
|
|
16
|
+
const parts = text.trim().split(SPLIT);
|
|
17
|
+
if (parts.length !== 2) return null;
|
|
18
|
+
const from = parseDate(parts[0] ?? "");
|
|
19
|
+
const to = parseDate(parts[1] ?? "");
|
|
20
|
+
return from === null || to === null || to < from ? null : {
|
|
21
|
+
from,
|
|
22
|
+
to
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
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
|
+
* 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 marked invalid when the field loses focus, and reports nothing. Under a name, a form submits two fields,
|
|
29
|
+
* `name.from` and `name.to`, each as YYYY-MM-DD (empty when there is no range), never the text on screen.
|
|
30
|
+
*/
|
|
31
|
+
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 }) {
|
|
32
|
+
const controlled = value !== void 0;
|
|
33
|
+
const [inner, setInner] = useState(defaultValue);
|
|
34
|
+
const range = controlled ? value : inner;
|
|
35
|
+
const [text, setText] = useState(() => write(range));
|
|
36
|
+
const [seen, setSeen] = useState(range);
|
|
37
|
+
const [malformed, setMalformed] = useState(false);
|
|
38
|
+
const [open, setOpen] = useState(false);
|
|
39
|
+
const [draft, setDraft] = useState();
|
|
40
|
+
if (!same(seen, range)) {
|
|
41
|
+
setSeen(range);
|
|
42
|
+
if (!same(range, parseRange(text))) setText(write(range));
|
|
43
|
+
setMalformed(false);
|
|
44
|
+
}
|
|
45
|
+
const commit = (next) => {
|
|
46
|
+
setSeen(next);
|
|
47
|
+
if (!controlled) setInner(next);
|
|
48
|
+
onValueChange?.(next);
|
|
49
|
+
};
|
|
50
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(InputGroup, {
|
|
51
|
+
size,
|
|
52
|
+
children: [/* @__PURE__ */ jsx(InputGroupInput, {
|
|
53
|
+
...rest,
|
|
54
|
+
ref,
|
|
55
|
+
type: "text",
|
|
56
|
+
autoComplete: "off",
|
|
57
|
+
placeholder,
|
|
58
|
+
readOnly,
|
|
59
|
+
disabled,
|
|
60
|
+
"aria-invalid": ariaInvalid ?? (malformed ? true : void 0),
|
|
61
|
+
value: text,
|
|
62
|
+
onChange: (event) => {
|
|
63
|
+
const next = event.target.value;
|
|
64
|
+
setText(next);
|
|
65
|
+
setMalformed(false);
|
|
66
|
+
if (next.trim() === "") return commit(null);
|
|
67
|
+
const parsed = parseRange(next);
|
|
68
|
+
if (parsed !== null) commit(parsed);
|
|
69
|
+
},
|
|
70
|
+
onBlur: (event) => {
|
|
71
|
+
onBlur?.(event);
|
|
72
|
+
const parsed = parseRange(text);
|
|
73
|
+
if (text.trim() === "") setMalformed(false);
|
|
74
|
+
else if (parsed === null) setMalformed(true);
|
|
75
|
+
else setText(write(parsed));
|
|
76
|
+
}
|
|
77
|
+
}), readOnly ? null : /* @__PURE__ */ jsx(InputGroupAddon, {
|
|
78
|
+
align: "end",
|
|
79
|
+
children: /* @__PURE__ */ jsxs(Popover, {
|
|
80
|
+
open,
|
|
81
|
+
onOpenChange: (next) => {
|
|
82
|
+
setDraft(void 0);
|
|
83
|
+
setOpen(next);
|
|
84
|
+
},
|
|
85
|
+
children: [/* @__PURE__ */ jsx(PopoverTrigger, {
|
|
86
|
+
disabled,
|
|
87
|
+
render: /* @__PURE__ */ jsx(Button, {
|
|
88
|
+
variant: "ghost",
|
|
89
|
+
iconOnly: true,
|
|
90
|
+
"aria-label": "Open calendar",
|
|
91
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
92
|
+
name: "calendar",
|
|
93
|
+
size: 12
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
}), /* @__PURE__ */ jsx(PopoverContent, {
|
|
97
|
+
align: "end",
|
|
98
|
+
children: /* @__PURE__ */ jsx(Calendar, {
|
|
99
|
+
mode: "range",
|
|
100
|
+
selected: draft ?? range ?? void 0,
|
|
101
|
+
defaultMonth: range?.from,
|
|
102
|
+
onSelect: (_picked, triggerDate) => {
|
|
103
|
+
if (draft?.from === void 0) {
|
|
104
|
+
setDraft({
|
|
105
|
+
from: triggerDate,
|
|
106
|
+
to: void 0
|
|
107
|
+
});
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const [from, to] = triggerDate < draft.from ? [triggerDate, draft.from] : [draft.from, triggerDate];
|
|
111
|
+
const next = {
|
|
112
|
+
from,
|
|
113
|
+
to
|
|
114
|
+
};
|
|
115
|
+
setText(write(next));
|
|
116
|
+
setMalformed(false);
|
|
117
|
+
commit(next);
|
|
118
|
+
setDraft(void 0);
|
|
119
|
+
setOpen(false);
|
|
120
|
+
},
|
|
121
|
+
disabled: disabledDays,
|
|
122
|
+
startMonth,
|
|
123
|
+
endMonth
|
|
124
|
+
})
|
|
125
|
+
})]
|
|
126
|
+
})
|
|
127
|
+
})]
|
|
128
|
+
}), name !== void 0 && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("input", {
|
|
129
|
+
type: "hidden",
|
|
130
|
+
name: `${name}.from`,
|
|
131
|
+
form: rest.form,
|
|
132
|
+
value: range === null ? "" : formatIsoDate(range.from),
|
|
133
|
+
disabled
|
|
134
|
+
}), /* @__PURE__ */ jsx("input", {
|
|
135
|
+
type: "hidden",
|
|
136
|
+
name: `${name}.to`,
|
|
137
|
+
form: rest.form,
|
|
138
|
+
value: range === null ? "" : formatIsoDate(range.to),
|
|
139
|
+
disabled
|
|
140
|
+
})] })] });
|
|
141
|
+
}
|
|
142
|
+
export { DateRangePicker };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { DateRangePicker } from "./DateRangePicker.js";
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { JSX, ReactElement, ReactNode } from "react";
|
|
2
|
+
/** The props of Field. */
|
|
3
|
+
export type FieldProps = {
|
|
4
|
+
/** The label text. */
|
|
5
|
+
label: ReactNode;
|
|
6
|
+
/** Help text under the control. An error replaces it. */
|
|
7
|
+
hint?: ReactNode;
|
|
8
|
+
/** The error message. It shows under the control beside the alert glyph and marks the control invalid. */
|
|
9
|
+
error?: ReactNode;
|
|
10
|
+
/** An explanation of the field, opened from a small info icon beside the label by keyboard, touch or hover. */
|
|
11
|
+
info?: ReactNode;
|
|
12
|
+
/** Shows a star after the label and marks the control aria-required. */
|
|
13
|
+
required?: boolean;
|
|
14
|
+
/** Dims the label and disables the control. */
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* The control: one element, such as Input, which gets an id, aria-describedby, aria-invalid, aria-required and disabled.
|
|
18
|
+
* When the control sits inside a wrapper such as InputGroup, pass a function instead and spread the props it receives
|
|
19
|
+
* onto the input itself.
|
|
20
|
+
*/
|
|
21
|
+
children: ReactElement<Record<string, unknown>> | ((props: FieldControlProps) => ReactNode);
|
|
22
|
+
};
|
|
23
|
+
/** The props Field hands to a function child, to spread onto the control. */
|
|
24
|
+
export type FieldControlProps = {
|
|
25
|
+
id: string;
|
|
26
|
+
'aria-describedby'?: string;
|
|
27
|
+
'aria-invalid'?: true;
|
|
28
|
+
'aria-required'?: true;
|
|
29
|
+
disabled?: true;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* A form row: label, control, hint and error. It wires the label to the control by id, links the hint and error with
|
|
33
|
+
* aria-describedby, and sets aria-invalid when there is an error. The control keeps an id it already has.
|
|
34
|
+
*/
|
|
35
|
+
export declare function Field({ label, hint, error, info, required, disabled, children }: FieldProps): JSX.Element;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Icon } from "../icon/Icon.js";
|
|
2
|
+
import { Definition } from "../definition/Definition.js";
|
|
3
|
+
import { Label } from "./Label.js";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { cloneElement, useId } from "react";
|
|
6
|
+
// src/components/field/Field.tsx
|
|
7
|
+
/**
|
|
8
|
+
* A form row: label, control, hint and error. It wires the label to the control by id, links the hint and error with
|
|
9
|
+
* aria-describedby, and sets aria-invalid when there is an error. The control keeps an id it already has.
|
|
10
|
+
*/
|
|
11
|
+
function Field({ label, hint, error, info, required, disabled, children }) {
|
|
12
|
+
const generatedId = useId();
|
|
13
|
+
const own = typeof children === "function" ? void 0 : children.props.id;
|
|
14
|
+
const id = typeof own === "string" && own !== "" ? own : generatedId;
|
|
15
|
+
const hasError = error !== void 0 && error !== null && error !== false && error !== "";
|
|
16
|
+
const hasHint = !hasError && hint !== void 0 && hint !== null && hint !== false && hint !== "";
|
|
17
|
+
const messageId = `${id}-message`;
|
|
18
|
+
const described = [typeof children === "function" ? void 0 : children.props["aria-describedby"], hasError || hasHint ? messageId : void 0].filter((part) => typeof part === "string" && part !== "").join(" ");
|
|
19
|
+
const wiring = {
|
|
20
|
+
id,
|
|
21
|
+
...described === "" ? {} : { "aria-describedby": described },
|
|
22
|
+
...hasError ? { "aria-invalid": true } : {},
|
|
23
|
+
...required === true ? { "aria-required": true } : {},
|
|
24
|
+
...disabled === true ? { disabled: true } : {}
|
|
25
|
+
};
|
|
26
|
+
const control = typeof children === "function" ? children(wiring) : cloneElement(children, { ...wiring });
|
|
27
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
28
|
+
"data-slot": "field",
|
|
29
|
+
className: "rt:flex rt:w-full rt:flex-col rt:gap-1.5",
|
|
30
|
+
children: [
|
|
31
|
+
/* @__PURE__ */ jsxs("div", {
|
|
32
|
+
className: `rt:flex rt:items-center rt:gap-1.5${disabled === true ? " rt:opacity-50" : ""}`,
|
|
33
|
+
children: [/* @__PURE__ */ jsxs(Label, {
|
|
34
|
+
htmlFor: id,
|
|
35
|
+
children: [label, required === true ? /* @__PURE__ */ jsx("span", {
|
|
36
|
+
"aria-hidden": "true",
|
|
37
|
+
className: "rt:text-foreground-subdued",
|
|
38
|
+
children: "*"
|
|
39
|
+
}) : null]
|
|
40
|
+
}), info === void 0 || info === null || info === false || info === "" ? null : /* @__PURE__ */ jsx(Definition, {
|
|
41
|
+
definition: info,
|
|
42
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
43
|
+
name: "info-circle",
|
|
44
|
+
tone: "subdued",
|
|
45
|
+
label: "More information"
|
|
46
|
+
})
|
|
47
|
+
})]
|
|
48
|
+
}),
|
|
49
|
+
control,
|
|
50
|
+
hasError ? /* @__PURE__ */ jsxs("p", {
|
|
51
|
+
id: messageId,
|
|
52
|
+
role: "alert",
|
|
53
|
+
className: "rt:flex rt:items-start rt:gap-1.5 rt:text-b1 rt:text-error",
|
|
54
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
55
|
+
className: "rt:mt-1 rt:text-error-icon",
|
|
56
|
+
children: /* @__PURE__ */ jsx(Icon, { name: "alert-triangle" })
|
|
57
|
+
}), /* @__PURE__ */ jsx("span", { children: error })]
|
|
58
|
+
}) : null,
|
|
59
|
+
hasHint ? /* @__PURE__ */ jsx("p", {
|
|
60
|
+
id: messageId,
|
|
61
|
+
className: "rt:text-b1 rt:text-foreground-subdued",
|
|
62
|
+
children: hint
|
|
63
|
+
}) : null
|
|
64
|
+
]
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
export { Field };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { JSX, LabelHTMLAttributes, Ref } from "react";
|
|
2
|
+
/** The props of Label: the HTML attributes of a label, except className and style. */
|
|
3
|
+
export type LabelProps = Omit<LabelHTMLAttributes<HTMLLabelElement>, 'className' | 'style'> & {
|
|
4
|
+
/** Receives the label element. */
|
|
5
|
+
ref?: Ref<HTMLLabelElement>;
|
|
6
|
+
};
|
|
7
|
+
/** The text that names a control, in the 12px medium label type. Point htmlFor at the control's id; Field does this for you. */
|
|
8
|
+
export declare function Label({ htmlFor, ref, ...rest }: LabelProps): JSX.Element;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
// src/components/field/Label.tsx
|
|
3
|
+
/** The text that names a control, in the 12px medium label type. Point htmlFor at the control's id; Field does this for you. */
|
|
4
|
+
function Label({ htmlFor, ref, ...rest }) {
|
|
5
|
+
return /* @__PURE__ */ jsx("label", {
|
|
6
|
+
"data-slot": "label",
|
|
7
|
+
htmlFor,
|
|
8
|
+
ref,
|
|
9
|
+
className: "rt:flex rt:w-fit rt:items-center rt:gap-1 rt:text-b2 rt:text-foreground rt:select-none",
|
|
10
|
+
...rest
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
export { Label };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { cn } from "../../lib/cn.js";
|
|
2
|
-
import { useState } from "react";
|
|
3
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from "react";
|
|
4
4
|
import { Toggle } from "@base-ui/react/toggle";
|
|
5
5
|
import { ToggleGroup } from "@base-ui/react/toggle-group";
|
|
6
6
|
// src/components/filter-tabs/FilterTabs.tsx
|
|
@@ -2,8 +2,8 @@ import { Icon } from "../icon/Icon.js";
|
|
|
2
2
|
import { formatCount } from "../../lib/format.js";
|
|
3
3
|
import { InputGroup, InputGroupAddon, InputGroupShortcut } from "../input-group/InputGroup.js";
|
|
4
4
|
import { Kbd, KbdGroup } from "../kbd/Kbd.js";
|
|
5
|
-
import { useEffect, useRef, useState } from "react";
|
|
6
5
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import { useEffect, useRef, useState } from "react";
|
|
7
7
|
import { Autocomplete } from "@base-ui/react/autocomplete";
|
|
8
8
|
// src/components/global-search/GlobalSearch.tsx
|
|
9
9
|
/**
|
|
@@ -2,6 +2,6 @@ import "@tabler/icons-react";
|
|
|
2
2
|
/**
|
|
3
3
|
* The name of an icon. The design system's own glyphs come first (plus, search, export, the two pagination chevrons and
|
|
4
4
|
* the five navigation glyphs); the rest are Tabler icons drawn with a 1.5px line: check, x, alert-triangle, info-circle,
|
|
5
|
-
* the dashboard's navigation glyphs (nav-orders to nav-settings), import, more and menu (the phone menu button).
|
|
5
|
+
* the dashboard's navigation glyphs (nav-orders to nav-settings), import, more and menu (the phone menu button) and calendar (the date pickers' open button).
|
|
6
6
|
*/
|
|
7
|
-
export type IconName = 'plus' | 'search' | 'export' | 'chevron-left' | 'chevron-right' | 'nav-home' | 'nav-shortcut' | 'nav-product' | 'nav-product-2' | 'nav-chevron' | 'nav-caret-down' | 'check' | 'x' | 'alert-triangle' | 'info-circle' | 'nav-orders' | 'nav-customers' | 'nav-growth' | 'nav-discounts' | 'nav-content' | 'nav-markets' | 'nav-analytics' | 'nav-sales-channels' | 'nav-apps' | 'nav-settings' | 'import' | 'more' | 'menu';
|
|
7
|
+
export type IconName = 'plus' | 'search' | 'export' | 'chevron-left' | 'chevron-right' | 'nav-home' | 'nav-shortcut' | 'nav-product' | 'nav-product-2' | 'nav-chevron' | 'nav-caret-down' | 'check' | 'x' | 'alert-triangle' | 'info-circle' | 'nav-orders' | 'nav-customers' | 'nav-growth' | 'nav-discounts' | 'nav-content' | 'nav-markets' | 'nav-analytics' | 'nav-sales-channels' | 'nav-apps' | 'nav-settings' | 'import' | 'more' | 'menu' | 'calendar';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CHEVRON_LEFT, CHEVRON_RIGHT, EXPORT, NAV_CHEVRON, NAV_HOME, NAV_PRODUCT, NAV_PRODUCT_2, NAV_SHORTCUT, PLUS, SEARCH } from "./glyphs.js";
|
|
2
|
-
import { IconAlertTriangle, IconApps, IconBuildingStore, IconChartBar, IconCheck, IconDiscount2, IconDots, IconFileImport, IconFileText, IconInfoCircle, IconMenu2, IconReceipt, IconSettings, IconTrendingUp, IconUsers, IconWorld, IconX } from "@tabler/icons-react";
|
|
2
|
+
import { IconAlertTriangle, IconApps, IconBuildingStore, IconCalendar, IconChartBar, IconCheck, IconDiscount2, IconDots, IconFileImport, IconFileText, IconInfoCircle, IconMenu2, IconReceipt, IconSettings, IconTrendingUp, IconUsers, IconWorld, IconX } from "@tabler/icons-react";
|
|
3
3
|
// src/components/icon/registry.ts
|
|
4
4
|
/**
|
|
5
5
|
* Every icon by name. nav-chevron and nav-caret-down are one glyph under two names, as in the design system, whose
|
|
@@ -117,6 +117,10 @@ const ICONS = {
|
|
|
117
117
|
menu: {
|
|
118
118
|
kind: "tabler",
|
|
119
119
|
component: IconMenu2
|
|
120
|
+
},
|
|
121
|
+
calendar: {
|
|
122
|
+
kind: "tabler",
|
|
123
|
+
component: IconCalendar
|
|
120
124
|
}
|
|
121
125
|
};
|
|
122
126
|
export { ICONS };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { cn } from "../../lib/cn.js";
|
|
2
|
-
import { useEffect, useRef } from "react";
|
|
3
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useRef } from "react";
|
|
4
4
|
import { cva } from "class-variance-authority";
|
|
5
5
|
import { Input } from "@base-ui/react/input";
|
|
6
6
|
// src/components/input-group/InputGroup.tsx
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { AnchorHTMLAttributes, JSX, ReactNode, Ref } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* The props of Link: the attributes of an anchor, except className and style, with the address and the two arrows.
|
|
4
|
+
*/
|
|
5
|
+
export type LinkProps = Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'className' | 'style' | 'href' | 'children'> & {
|
|
6
|
+
/** Where the link goes. */
|
|
7
|
+
href: string;
|
|
8
|
+
/** The link text. */
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
/** Shows a back arrow before the text, as in "← Products". Screen readers skip it. */
|
|
11
|
+
back?: boolean;
|
|
12
|
+
/** Shows a forward arrow after the text, as in "View all variants →". Screen readers skip it. */
|
|
13
|
+
forward?: boolean;
|
|
14
|
+
/** Receives the anchor element. */
|
|
15
|
+
ref?: Ref<HTMLAnchorElement>;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* A text link to another page, such as "← Products" or "View all variants →". It is drawn by the app's LinkProvider, so a
|
|
19
|
+
* router navigates without a reload; without one it is a plain <a href>. The arrows are decoration and are hidden from
|
|
20
|
+
* screen readers.
|
|
21
|
+
*/
|
|
22
|
+
export declare function Link({ href, children, back, forward, ref, ...rest }: LinkProps): JSX.Element;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { cn } from "../../lib/cn.js";
|
|
2
|
+
import { RatioLink } from "../link-provider/RatioLink.js";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
// src/components/link/Link.tsx
|
|
5
|
+
const LINK = "rt:inline-flex rt:items-center rt:gap-1 rt:rounded-sm rt:text-primary rt:transition-colors rt:hover:underline rt:focus-visible:focus-ring rt:forced-outline";
|
|
6
|
+
/**
|
|
7
|
+
* A text link to another page, such as "← Products" or "View all variants →". It is drawn by the app's LinkProvider, so a
|
|
8
|
+
* router navigates without a reload; without one it is a plain <a href>. The arrows are decoration and are hidden from
|
|
9
|
+
* screen readers.
|
|
10
|
+
*/
|
|
11
|
+
function Link({ href, children, back = false, forward = false, ref, ...rest }) {
|
|
12
|
+
return /* @__PURE__ */ jsxs(RatioLink, {
|
|
13
|
+
...rest,
|
|
14
|
+
ref,
|
|
15
|
+
href,
|
|
16
|
+
"data-slot": "link",
|
|
17
|
+
className: cn(LINK),
|
|
18
|
+
children: [
|
|
19
|
+
back && /* @__PURE__ */ jsx("span", {
|
|
20
|
+
"aria-hidden": "true",
|
|
21
|
+
children: "←"
|
|
22
|
+
}),
|
|
23
|
+
children,
|
|
24
|
+
forward && /* @__PURE__ */ jsx("span", {
|
|
25
|
+
"aria-hidden": "true",
|
|
26
|
+
children: "→"
|
|
27
|
+
})
|
|
28
|
+
]
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
export { Link };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Link } from "./Link.js";
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { JSX } from "react";
|
|
2
|
+
import { Accept } from "react-dropzone";
|
|
3
|
+
/** One image in a MediaGrid. */
|
|
4
|
+
export type MediaGridItem = {
|
|
5
|
+
/** A stable id, reported back by onReorder, onRemove and onMakeCover. */
|
|
6
|
+
id: string;
|
|
7
|
+
/** The image URL, or an object URL while the file uploads. */
|
|
8
|
+
src: string;
|
|
9
|
+
/** The text alternative. It also names the item in the action group and the announcements. */
|
|
10
|
+
alt: string;
|
|
11
|
+
/** Upload progress from 0 to 100. A thin bar shows on the thumb while it is below 100. */
|
|
12
|
+
progress?: number;
|
|
13
|
+
/** Why the upload failed, shown under the thumb beside the warning glyph; the thumb takes the error ring. */
|
|
14
|
+
error?: string;
|
|
15
|
+
};
|
|
16
|
+
/** The props of MediaGrid. The app uploads the files and owns the list; the grid reports what the person did. */
|
|
17
|
+
export type MediaGridProps = {
|
|
18
|
+
/** The images in order. The first one is the cover. */
|
|
19
|
+
items: MediaGridItem[];
|
|
20
|
+
/** Called with the files chosen in the add tile or dropped on the grid. */
|
|
21
|
+
onAdd: (files: File[]) => void;
|
|
22
|
+
/** Called with every id in the new order after a drag, Move left, Move right or Make cover. */
|
|
23
|
+
onReorder: (ids: string[]) => void;
|
|
24
|
+
/** Called with the id of the item to take off the grid. */
|
|
25
|
+
onRemove: (id: string) => void;
|
|
26
|
+
/** Called with the id after Make cover has moved it first through onReorder, for an app that saves the cover on its own. */
|
|
27
|
+
onMakeCover?: (id: string) => void;
|
|
28
|
+
/** The file types the add tile takes, as react-dropzone's accept map. Defaults to every image type. */
|
|
29
|
+
accept?: Accept;
|
|
30
|
+
/** Stops adding, dragging and every button. */
|
|
31
|
+
disabled?: boolean;
|
|
32
|
+
/** The accessible name of the add tile. Defaults to "Add media". */
|
|
33
|
+
addLabel?: string;
|
|
34
|
+
/** The id of the grid. */
|
|
35
|
+
id?: string;
|
|
36
|
+
/** The accessible name of the grid. Defaults to "Media" when there is no aria-labelledby. */
|
|
37
|
+
'aria-label'?: string;
|
|
38
|
+
/** The id of the visible label that names the grid, such as a Field label. */
|
|
39
|
+
'aria-labelledby'?: string;
|
|
40
|
+
/** The id of help text for the grid. */
|
|
41
|
+
'aria-describedby'?: string;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* The media of a product: a row of square thumbs with an add tile that opens the file picker and takes dropped files.
|
|
45
|
+
* Thumbs reorder by drag (pointer, touch or keyboard) and by the Move left, Move right and Make cover buttons, so no
|
|
46
|
+
* drag is needed. The first image is the cover and carries a badge. Uploads show a thin progress bar and failures an
|
|
47
|
+
* error under the thumb. Every move, cover change and removal is announced to screen readers.
|
|
48
|
+
*/
|
|
49
|
+
export declare function MediaGrid({ items, onAdd, onReorder, onRemove, onMakeCover, accept, disabled, addLabel, 'aria-label': ariaLabel, 'aria-labelledby': labelledBy, ...rest }: MediaGridProps): JSX.Element;
|