@milktop/react-ui 0.0.0-stage → 0.1.2
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/README.md +173 -2
- package/dist/accordion/Accordion.d.ts +65 -0
- package/dist/accordion/Accordion.js +61 -0
- package/dist/accordion/accordion.css +17 -0
- package/dist/action-bar/ActionBar.d.ts +33 -0
- package/dist/action-bar/ActionBar.js +48 -0
- package/dist/action-bar/action-bar.css +22 -0
- package/dist/alert/Alert.d.ts +28 -0
- package/dist/alert/Alert.js +12 -0
- package/dist/alert/alert.css +32 -0
- package/dist/app-shell/AppShell.d.ts +38 -0
- package/dist/app-shell/AppShell.js +92 -0
- package/dist/app-shell/Topbar.d.ts +20 -0
- package/dist/app-shell/Topbar.js +13 -0
- package/dist/app-shell/app-shell.css +41 -0
- package/dist/app-shell/shell.d.ts +9 -0
- package/dist/app-shell/shell.js +3 -0
- package/dist/attachments/Attachments.d.ts +84 -0
- package/dist/attachments/Attachments.js +235 -0
- package/dist/attachments/attachments.css +86 -0
- package/dist/avatar/Avatar.d.ts +31 -0
- package/dist/avatar/Avatar.js +41 -0
- package/dist/avatar/AvatarGroup.d.ts +41 -0
- package/dist/avatar/AvatarGroup.js +17 -0
- package/dist/avatar/avatar.css +73 -0
- package/dist/badge/Badge.d.ts +26 -0
- package/dist/badge/Badge.js +10 -0
- package/dist/badge/badge.css +24 -0
- package/dist/banner/Banner.d.ts +33 -0
- package/dist/banner/Banner.js +14 -0
- package/dist/banner/banner.css +25 -0
- package/dist/breadcrumbs/Breadcrumbs.d.ts +39 -0
- package/dist/breadcrumbs/Breadcrumbs.js +28 -0
- package/dist/breadcrumbs/breadcrumbs.css +18 -0
- package/dist/button/Button.d.ts +40 -0
- package/dist/button/Button.js +19 -0
- package/dist/button/button.css +63 -0
- package/dist/card/Card.d.ts +355 -0
- package/dist/card/Card.js +56 -0
- package/dist/card/card.css +43 -0
- package/dist/chart/AreaChart.d.ts +19 -0
- package/dist/chart/AreaChart.js +61 -0
- package/dist/chart/BarChart.d.ts +3 -0
- package/dist/chart/BarChart.js +60 -0
- package/dist/chart/ChartFrame.d.ts +25 -0
- package/dist/chart/ChartFrame.js +22 -0
- package/dist/chart/DonutChart.d.ts +20 -0
- package/dist/chart/DonutChart.js +41 -0
- package/dist/chart/Sparkline.d.ts +26 -0
- package/dist/chart/Sparkline.js +36 -0
- package/dist/chart/chart.css +51 -0
- package/dist/chart/chart.d.ts +23 -0
- package/dist/chart/chart.js +19 -0
- package/dist/checkbox/Checkbox.d.ts +50 -0
- package/dist/checkbox/Checkbox.js +19 -0
- package/dist/checkbox/CheckboxGroup.d.ts +44 -0
- package/dist/checkbox/CheckboxGroup.js +80 -0
- package/dist/checkbox/checkbox.css +49 -0
- package/dist/collapsible/Collapsible.d.ts +41 -0
- package/dist/collapsible/Collapsible.js +15 -0
- package/dist/collapsible/collapsible.css +20 -0
- package/dist/color-scheme.d.ts +18 -0
- package/dist/color-scheme.js +84 -0
- package/dist/command/Command.d.ts +64 -0
- package/dist/command/Command.js +162 -0
- package/dist/command/command.css +47 -0
- package/dist/confirm/confirm.css +5 -0
- package/dist/confirm/confirm.d.ts +42 -0
- package/dist/confirm/confirm.js +115 -0
- package/dist/copy-button/CopyButton.d.ts +30 -0
- package/dist/copy-button/CopyButton.js +23 -0
- package/dist/copy-button/copy-button.css +12 -0
- package/dist/css.d.ts +2 -0
- package/dist/data-list/DataList.d.ts +55 -0
- package/dist/data-list/DataList.js +43 -0
- package/dist/data-list/data-list.css +19 -0
- package/dist/date-picker/DatePicker.d.ts +95 -0
- package/dist/date-picker/DatePicker.js +237 -0
- package/dist/date-picker/date-picker.css +53 -0
- package/dist/dialog/Dialog.d.ts +98 -0
- package/dist/dialog/Dialog.js +92 -0
- package/dist/dialog/dialog.css +31 -0
- package/dist/divider/Divider.d.ts +27 -0
- package/dist/divider/Divider.js +8 -0
- package/dist/divider/divider.css +12 -0
- package/dist/editable/Editable.d.ts +47 -0
- package/dist/editable/Editable.js +51 -0
- package/dist/editable/editable.css +25 -0
- package/dist/editor/Editor.d.ts +59 -0
- package/dist/editor/Editor.js +220 -0
- package/dist/editor/editor.css +42 -0
- package/dist/editor/image.d.ts +1 -0
- package/dist/editor/image.js +23 -0
- package/dist/empty-state/EmptyState.d.ts +22 -0
- package/dist/empty-state/EmptyState.js +9 -0
- package/dist/empty-state/empty-state.css +14 -0
- package/dist/event-calendar/EventCalendar.d.ts +213 -0
- package/dist/event-calendar/EventCalendar.js +686 -0
- package/dist/event-calendar/dates.d.ts +8 -0
- package/dist/event-calendar/dates.js +21 -0
- package/dist/event-calendar/event-calendar.css +157 -0
- package/dist/field/Field.d.ts +66 -0
- package/dist/field/Field.js +23 -0
- package/dist/field/field.css +26 -0
- package/dist/file-upload/FileUpload.d.ts +60 -0
- package/dist/file-upload/FileUpload.js +102 -0
- package/dist/file-upload/file-upload.css +32 -0
- package/dist/format/FormatNumber.d.ts +42 -0
- package/dist/format/FormatNumber.js +59 -0
- package/dist/format/LocaleProvider.d.ts +33 -0
- package/dist/format/LocaleProvider.js +48 -0
- package/dist/format/format.d.ts +37 -0
- package/dist/format/format.js +74 -0
- package/dist/format/messages.d.ts +211 -0
- package/dist/format/messages.js +202 -0
- package/dist/highlight/Highlight.d.ts +11 -0
- package/dist/highlight/Highlight.js +14 -0
- package/dist/highlight/highlight.css +17 -0
- package/dist/hover-card/HoverCard.d.ts +47 -0
- package/dist/hover-card/HoverCard.js +18 -0
- package/dist/hover-card/hover-card.css +19 -0
- package/dist/index.d.ts +84 -0
- package/dist/index.js +85 -0
- package/dist/input/Input.d.ts +41 -0
- package/dist/input/Input.js +48 -0
- package/dist/input/input.css +25 -0
- package/dist/kbd/Kbd.d.ts +19 -0
- package/dist/kbd/Kbd.js +20 -0
- package/dist/kbd/kbd.css +8 -0
- package/dist/menu/Menu.d.ts +223 -0
- package/dist/menu/Menu.js +275 -0
- package/dist/menu/menu.css +38 -0
- package/dist/native-select/NativeSelect.d.ts +36 -0
- package/dist/native-select/NativeSelect.js +51 -0
- package/dist/native-select/native-select.css +16 -0
- package/dist/number-input/NumberInput.d.ts +56 -0
- package/dist/number-input/NumberInput.js +67 -0
- package/dist/number-input/number-input.css +21 -0
- package/dist/page/Page.d.ts +30 -0
- package/dist/page/Page.js +9 -0
- package/dist/page/page.css +17 -0
- package/dist/pagination/Pagination.d.ts +31 -0
- package/dist/pagination/Pagination.js +58 -0
- package/dist/pagination/pagination.css +15 -0
- package/dist/password-input/PasswordInput.d.ts +47 -0
- package/dist/password-input/PasswordInput.js +35 -0
- package/dist/password-input/password-input.css +18 -0
- package/dist/pin-input/PinInput.d.ts +45 -0
- package/dist/pin-input/PinInput.js +22 -0
- package/dist/pin-input/pin-input.css +13 -0
- package/dist/popover/Popover.d.ts +65 -0
- package/dist/popover/Popover.js +26 -0
- package/dist/popover/popover.css +16 -0
- package/dist/progress/Progress.d.ts +56 -0
- package/dist/progress/Progress.js +28 -0
- package/dist/progress/progress.css +24 -0
- package/dist/prose/Prose.d.ts +23 -0
- package/dist/prose/Prose.js +10 -0
- package/dist/prose/prose.css +29 -0
- package/dist/radio-group/RadioGroup.d.ts +59 -0
- package/dist/radio-group/RadioGroup.js +37 -0
- package/dist/radio-group/radio-group.css +30 -0
- package/dist/rolling-number/RollingNumber.d.ts +27 -0
- package/dist/rolling-number/RollingNumber.js +18 -0
- package/dist/rolling-number/rolling-number.css +10 -0
- package/dist/segment-group/Segmented.d.ts +68 -0
- package/dist/segment-group/Segmented.js +79 -0
- package/dist/segment-group/segment-group.css +25 -0
- package/dist/select/ListSelect.d.ts +5 -0
- package/dist/select/ListSelect.js +82 -0
- package/dist/select/SearchSelect.d.ts +43 -0
- package/dist/select/SearchSelect.js +219 -0
- package/dist/select/Select.d.ts +147 -0
- package/dist/select/Select.js +32 -0
- package/dist/select/items.d.ts +17 -0
- package/dist/select/items.js +14 -0
- package/dist/select/search-select.css +45 -0
- package/dist/select/select.css +52 -0
- package/dist/sheet/Sheet.d.ts +88 -0
- package/dist/sheet/Sheet.js +66 -0
- package/dist/sheet/sheet.css +44 -0
- package/dist/sidebar/NavGroup.d.ts +29 -0
- package/dist/sidebar/NavGroup.js +78 -0
- package/dist/sidebar/NavItem.d.ts +33 -0
- package/dist/sidebar/NavItem.js +50 -0
- package/dist/sidebar/NavSection.d.ts +20 -0
- package/dist/sidebar/NavSection.js +44 -0
- package/dist/sidebar/Sidebar.d.ts +65 -0
- package/dist/sidebar/Sidebar.js +59 -0
- package/dist/sidebar/SidebarUser.d.ts +31 -0
- package/dist/sidebar/SidebarUser.js +14 -0
- package/dist/sidebar/context.d.ts +13 -0
- package/dist/sidebar/context.js +6 -0
- package/dist/sidebar/sidebar.css +109 -0
- package/dist/skeleton/Skeleton.d.ts +25 -0
- package/dist/skeleton/Skeleton.js +8 -0
- package/dist/skeleton/skeleton.css +13 -0
- package/dist/slider/Slider.d.ts +70 -0
- package/dist/slider/Slider.js +65 -0
- package/dist/slider/slider.css +17 -0
- package/dist/sortable-list/SortableList.d.ts +46 -0
- package/dist/sortable-list/SortableList.js +85 -0
- package/dist/sortable-list/sortable-list.css +25 -0
- package/dist/spinner/Spinner.d.ts +18 -0
- package/dist/spinner/Spinner.js +11 -0
- package/dist/spinner/spinner.css +9 -0
- package/dist/stat/Stat.d.ts +42 -0
- package/dist/stat/Stat.js +21 -0
- package/dist/stat/Stats.d.ts +23 -0
- package/dist/stat/Stats.js +8 -0
- package/dist/stat/stat.css +34 -0
- package/dist/swatch-picker/SwatchPicker.d.ts +45 -0
- package/dist/swatch-picker/SwatchPicker.js +54 -0
- package/dist/swatch-picker/swatch-picker.css +38 -0
- package/dist/switch/Switch.d.ts +39 -0
- package/dist/switch/Switch.js +17 -0
- package/dist/switch/switch.css +13 -0
- package/dist/table/Table.d.ts +90 -0
- package/dist/table/Table.js +145 -0
- package/dist/table/table.css +44 -0
- package/dist/tabs/Tabs.d.ts +87 -0
- package/dist/tabs/Tabs.js +90 -0
- package/dist/tabs/tabs.css +53 -0
- package/dist/tags-input/TagsInput.d.ts +69 -0
- package/dist/tags-input/TagsInput.js +95 -0
- package/dist/tags-input/tags-input.css +37 -0
- package/dist/textarea/Textarea.d.ts +24 -0
- package/dist/textarea/Textarea.js +51 -0
- package/dist/textarea/textarea.css +14 -0
- package/dist/theme-toggle/ThemeToggle.d.ts +49 -0
- package/dist/theme-toggle/ThemeToggle.js +71 -0
- package/dist/theme-toggle/theme-toggle.css +23 -0
- package/dist/theme.css +121 -0
- package/dist/time-input/TimeInput.d.ts +25 -0
- package/dist/time-input/TimeInput.js +20 -0
- package/dist/timeline/Timeline.d.ts +50 -0
- package/dist/timeline/Timeline.js +15 -0
- package/dist/timeline/timeline.css +29 -0
- package/dist/toast/Toaster.d.ts +36 -0
- package/dist/toast/Toaster.js +25 -0
- package/dist/toast/toast.css +33 -0
- package/dist/tooltip/Tooltip.d.ts +55 -0
- package/dist/tooltip/Tooltip.js +48 -0
- package/dist/tooltip/tooltip.css +18 -0
- package/dist/tree-view/TreeView.d.ts +86 -0
- package/dist/tree-view/TreeView.js +74 -0
- package/dist/tree-view/tree-view.css +29 -0
- package/dist/utils.d.ts +19 -0
- package/dist/utils.js +59 -0
- package/package.json +99 -4
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A number input with step buttons; arrow keys step too (Shift for 10).
|
|
3
|
+
*
|
|
4
|
+
* <NumberInput label="Hourly rate" value={rate} onValueChange={setRate} min={0} step={5} prefix="£" />
|
|
5
|
+
*
|
|
6
|
+
* - `value`, `defaultValue`: a number, or null when empty
|
|
7
|
+
* - `onValueChange`: called with the new number (or null)
|
|
8
|
+
* - `min`, `max`, `step`
|
|
9
|
+
* - `prefix`, `suffix`: text before or after it ('£', 'kg'); `icon`: an Iconify name before it
|
|
10
|
+
* - `formatOptions`: Intl.NumberFormat options, e.g. { style: 'currency', currency: 'GBP' }, with `locale`
|
|
11
|
+
* - `allowMouseWheel`: scroll over the focused input to step
|
|
12
|
+
* - `steppers`: false to hide the buttons
|
|
13
|
+
* - `name`: for plain form posts
|
|
14
|
+
*
|
|
15
|
+
* By hand, with Ark's parts:
|
|
16
|
+
*
|
|
17
|
+
* <NumberInput.Root defaultValue="3" min={0}>
|
|
18
|
+
* <NumberInput.Label>Guests</NumberInput.Label>
|
|
19
|
+
* <NumberInput.Control>
|
|
20
|
+
* <NumberInput.DecrementTrigger>−</NumberInput.DecrementTrigger>
|
|
21
|
+
* <NumberInput.Input />
|
|
22
|
+
* <NumberInput.IncrementTrigger>+</NumberInput.IncrementTrigger>
|
|
23
|
+
* </NumberInput.Control>
|
|
24
|
+
* </NumberInput.Root>
|
|
25
|
+
*/
|
|
26
|
+
import { type ReactNode } from 'react';
|
|
27
|
+
import { NumberInput as Ark, type NumberInputValueChangeDetails } from '@ark-ui/react/number-input';
|
|
28
|
+
import 'iconify-icon';
|
|
29
|
+
import { type Classes } from '../utils.js';
|
|
30
|
+
import '../theme.css';
|
|
31
|
+
import './number-input.css';
|
|
32
|
+
export type NumberInputProps = Omit<Ark.RootProps, 'value' | 'defaultValue' | 'onValueChange' | 'label' | 'prefix'> & {
|
|
33
|
+
label?: ReactNode;
|
|
34
|
+
value?: number | null;
|
|
35
|
+
defaultValue?: number | null;
|
|
36
|
+
onValueChange?: (value: number | null, details: NumberInputValueChangeDetails) => void;
|
|
37
|
+
prefix?: ReactNode;
|
|
38
|
+
suffix?: ReactNode;
|
|
39
|
+
icon?: string | null;
|
|
40
|
+
steppers?: boolean;
|
|
41
|
+
placeholder?: string;
|
|
42
|
+
classes?: Classes<'root' | 'label' | 'control' | 'icon' | 'prefix' | 'input' | 'suffix' | 'decrementTrigger' | 'incrementTrigger'>;
|
|
43
|
+
};
|
|
44
|
+
declare function NumberInputShortcut({ label, value, defaultValue, onValueChange, prefix, suffix, icon, steppers, placeholder, className, classes, ...rest }: NumberInputProps): import("react").JSX.Element;
|
|
45
|
+
export declare const NumberInput: typeof NumberInputShortcut & {
|
|
46
|
+
Root: import("react").ForwardRefExoticComponent<Ark.RootProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
47
|
+
Label: import("react").ForwardRefExoticComponent<Ark.LabelProps & import("react").RefAttributes<HTMLLabelElement>>;
|
|
48
|
+
Control: import("react").ForwardRefExoticComponent<Ark.ControlProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
49
|
+
Input: import("react").ForwardRefExoticComponent<Ark.InputProps & import("react").RefAttributes<HTMLInputElement>>;
|
|
50
|
+
DecrementTrigger: import("react").ForwardRefExoticComponent<Ark.DecrementTriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
|
|
51
|
+
IncrementTrigger: import("react").ForwardRefExoticComponent<Ark.IncrementTriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
|
|
52
|
+
ValueText: import("react").ForwardRefExoticComponent<Ark.ValueTextProps & import("react").RefAttributes<HTMLSpanElement>>;
|
|
53
|
+
Scrubber: import("react").ForwardRefExoticComponent<Ark.ScrubberProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
54
|
+
Context: (props: Ark.ContextProps) => ReactNode;
|
|
55
|
+
};
|
|
56
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* A number input with step buttons; arrow keys step too (Shift for 10).
|
|
4
|
+
*
|
|
5
|
+
* <NumberInput label="Hourly rate" value={rate} onValueChange={setRate} min={0} step={5} prefix="£" />
|
|
6
|
+
*
|
|
7
|
+
* - `value`, `defaultValue`: a number, or null when empty
|
|
8
|
+
* - `onValueChange`: called with the new number (or null)
|
|
9
|
+
* - `min`, `max`, `step`
|
|
10
|
+
* - `prefix`, `suffix`: text before or after it ('£', 'kg'); `icon`: an Iconify name before it
|
|
11
|
+
* - `formatOptions`: Intl.NumberFormat options, e.g. { style: 'currency', currency: 'GBP' }, with `locale`
|
|
12
|
+
* - `allowMouseWheel`: scroll over the focused input to step
|
|
13
|
+
* - `steppers`: false to hide the buttons
|
|
14
|
+
* - `name`: for plain form posts
|
|
15
|
+
*
|
|
16
|
+
* By hand, with Ark's parts:
|
|
17
|
+
*
|
|
18
|
+
* <NumberInput.Root defaultValue="3" min={0}>
|
|
19
|
+
* <NumberInput.Label>Guests</NumberInput.Label>
|
|
20
|
+
* <NumberInput.Control>
|
|
21
|
+
* <NumberInput.DecrementTrigger>−</NumberInput.DecrementTrigger>
|
|
22
|
+
* <NumberInput.Input />
|
|
23
|
+
* <NumberInput.IncrementTrigger>+</NumberInput.IncrementTrigger>
|
|
24
|
+
* </NumberInput.Control>
|
|
25
|
+
* </NumberInput.Root>
|
|
26
|
+
*/
|
|
27
|
+
import { useState } from 'react';
|
|
28
|
+
import { NumberInput as Ark } from '@ark-ui/react/number-input';
|
|
29
|
+
import { useFieldContext } from '@ark-ui/react/field';
|
|
30
|
+
import 'iconify-icon';
|
|
31
|
+
import { cx, partsOf, withParts } from '../utils.js';
|
|
32
|
+
import { useMessages } from '../format/LocaleProvider.js';
|
|
33
|
+
import '../theme.css';
|
|
34
|
+
import './number-input.css';
|
|
35
|
+
const toText = (value) => (value == null ? '' : String(value));
|
|
36
|
+
function NumberInputShortcut({ label = null, value, defaultValue, onValueChange, prefix = null, suffix = null, icon = null, steppers = true, placeholder = '', className, classes, ...rest }) {
|
|
37
|
+
const t = useMessages();
|
|
38
|
+
const field = useFieldContext();
|
|
39
|
+
const part = partsOf('number-input', classes);
|
|
40
|
+
// Zag works on the text ('1.', '' while typing); `value` is its number.
|
|
41
|
+
// The text follows a controlled value that changes from outside.
|
|
42
|
+
const [text, setText] = useState(() => toText(value !== undefined ? value : defaultValue));
|
|
43
|
+
const [seen, setSeen] = useState(value);
|
|
44
|
+
if (value !== seen) {
|
|
45
|
+
setSeen(value);
|
|
46
|
+
const number = text === '' ? null : Number(text);
|
|
47
|
+
if (value !== number)
|
|
48
|
+
setText(toText(value));
|
|
49
|
+
}
|
|
50
|
+
function change(details) {
|
|
51
|
+
setText(details.value);
|
|
52
|
+
onValueChange?.(Number.isNaN(details.valueAsNumber) ? null : details.valueAsNumber, details);
|
|
53
|
+
}
|
|
54
|
+
return (_jsxs(Ark.Root, { ...(t.zag.numberInput && { translations: t.zag.numberInput }), ...rest, ...part('root'), className: cx(className, classes?.root), value: text, onValueChange: change, children: [label && !field && _jsx(Ark.Label, { ...part('label'), children: label }), _jsxs(Ark.Control, { ...part('control'), children: [icon ? _jsx("iconify-icon", { ...part('icon'), icon: icon, "aria-hidden": "true" })
|
|
55
|
+
: prefix != null && _jsx("span", { ...part('prefix'), children: prefix }), _jsx(Ark.Input, { ...part('input'), placeholder: placeholder }), suffix != null && _jsx("span", { ...part('suffix'), children: suffix }), steppers && _jsxs(_Fragment, { children: [_jsx(Ark.DecrementTrigger, { ...part('decrement-trigger'), children: _jsx("iconify-icon", { icon: "lucide:minus" }) }), _jsx(Ark.IncrementTrigger, { ...part('increment-trigger'), children: _jsx("iconify-icon", { icon: "lucide:plus" }) })] })] })] }));
|
|
56
|
+
}
|
|
57
|
+
export const NumberInput = withParts(NumberInputShortcut, {
|
|
58
|
+
Root: Ark.Root,
|
|
59
|
+
Label: Ark.Label,
|
|
60
|
+
Control: Ark.Control,
|
|
61
|
+
Input: Ark.Input,
|
|
62
|
+
DecrementTrigger: Ark.DecrementTrigger,
|
|
63
|
+
IncrementTrigger: Ark.IncrementTrigger,
|
|
64
|
+
ValueText: Ark.ValueText,
|
|
65
|
+
Scrubber: Ark.Scrubber,
|
|
66
|
+
Context: Ark.Context,
|
|
67
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
@layer ui {
|
|
2
|
+
[data-scope=number-input][data-part=root] { display: inline-block; min-width: 10rem; color: var(--ui-text); font-family: var(--ui-font); }
|
|
3
|
+
[data-scope=number-input][data-part=label] { display: block; font-size: 14px; font-weight: 500; margin-bottom: 0.375rem; }
|
|
4
|
+
[data-scope=number-input][data-part=control] { display: flex; align-items: center; gap: 0.5rem; height: var(--ui-control-height);
|
|
5
|
+
padding: 0 0.25rem 0 0.75rem; box-sizing: border-box; background: var(--ui-surface);
|
|
6
|
+
border: 1px solid var(--ui-border); border-radius: var(--ui-radius); font-size: 14px;
|
|
7
|
+
&:focus-within { border-color: var(--ui-ring); outline: 2px solid var(--ui-ring-soft); }
|
|
8
|
+
&[data-disabled] { opacity: 0.5; }
|
|
9
|
+
&[data-invalid], &:has([aria-invalid=true]) { border-color: var(--ui-danger); } }
|
|
10
|
+
[data-scope=number-input][data-part=input] { flex: 1; min-width: 0; padding: 0; border: none; background: transparent; outline: none;
|
|
11
|
+
color: inherit; font: inherit;
|
|
12
|
+
&::placeholder { color: var(--ui-muted); } }
|
|
13
|
+
[data-scope=number-input]:is([data-part=prefix], [data-part=suffix]) { display: flex; align-items: center; flex-shrink: 0;
|
|
14
|
+
color: var(--ui-muted); white-space: nowrap; }
|
|
15
|
+
[data-scope=number-input][data-part=icon] { display: block; width: 1em; height: 1em; font-size: 16px; color: var(--ui-muted); flex-shrink: 0; }
|
|
16
|
+
[data-scope=number-input]:is([data-part=decrement-trigger], [data-part=increment-trigger]) { display: grid; place-items: center;
|
|
17
|
+
flex-shrink: 0; width: 1.5rem; height: 1.75rem; border: none; background: transparent; border-radius: 4px;
|
|
18
|
+
color: var(--ui-muted); cursor: pointer;
|
|
19
|
+
&:hover:not([data-disabled]) { background: var(--ui-hover); color: var(--ui-text); }
|
|
20
|
+
&[data-disabled] { opacity: 0.4; cursor: not-allowed; } }
|
|
21
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A page: its header (heading, description, actions) and content, at a
|
|
3
|
+
* readable width.
|
|
4
|
+
*
|
|
5
|
+
* <Page heading="Students" description="12 students" actions={<Button variant="primary">Add student</Button>}>
|
|
6
|
+
* …content…
|
|
7
|
+
* </Page>
|
|
8
|
+
*
|
|
9
|
+
* - `heading`, `description`
|
|
10
|
+
* - `width`: 'narrow', 'default', 'wide' or 'full'
|
|
11
|
+
* - `align`: 'center' or 'start'
|
|
12
|
+
* - `actions`, `breadcrumbs`: shown in the header
|
|
13
|
+
*
|
|
14
|
+
* What's inside a Page isn't the shell's: a Sidebar shown in the page (as an
|
|
15
|
+
* example, say) doesn't collapse or turn into a drawer with the app's.
|
|
16
|
+
*/
|
|
17
|
+
import type { HTMLAttributes, ReactNode } from 'react';
|
|
18
|
+
import { type Classes } from '../utils.js';
|
|
19
|
+
import '../theme.css';
|
|
20
|
+
import './page.css';
|
|
21
|
+
export type PageProps = HTMLAttributes<HTMLDivElement> & {
|
|
22
|
+
heading?: ReactNode;
|
|
23
|
+
description?: ReactNode;
|
|
24
|
+
width?: 'narrow' | 'default' | 'wide' | 'full';
|
|
25
|
+
align?: 'center' | 'start';
|
|
26
|
+
actions?: ReactNode;
|
|
27
|
+
breadcrumbs?: ReactNode;
|
|
28
|
+
classes?: Classes<'root' | 'inner' | 'header' | 'breadcrumbs' | 'titles' | 'heading' | 'description' | 'actions' | 'content'>;
|
|
29
|
+
};
|
|
30
|
+
export declare function Page({ heading, description, width, align, actions, breadcrumbs, className, classes, children, ...rest }: PageProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cx, partsOf } from '../utils.js';
|
|
3
|
+
import { ShellContext } from '../app-shell/shell.js';
|
|
4
|
+
import '../theme.css';
|
|
5
|
+
import './page.css';
|
|
6
|
+
export function Page({ heading = null, description = null, width = 'default', align = 'center', actions = null, breadcrumbs = null, className, classes, children, ...rest }) {
|
|
7
|
+
const part = partsOf('page', classes);
|
|
8
|
+
return (_jsx("div", { ...rest, ...part('root'), className: cx(className, classes?.root), "data-width": width, "data-align": align, children: _jsxs("div", { ...part('inner'), children: [(heading || description || actions || breadcrumbs) && (_jsxs("header", { ...part('header'), children: [breadcrumbs && _jsx("div", { ...part('breadcrumbs'), children: breadcrumbs }), _jsxs("div", { ...part('titles'), children: [_jsxs("div", { children: [heading && _jsx("h1", { ...part('heading'), children: heading }), description && _jsx("p", { ...part('description'), children: description })] }), actions && _jsx("div", { ...part('actions'), children: actions })] })] })), _jsx("div", { ...part('content'), children: _jsx(ShellContext, { value: null, children: children }) })] }) }));
|
|
9
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/* A page holds other components, so its rules use > to stay off them. */
|
|
2
|
+
@layer ui {
|
|
3
|
+
[data-scope=page][data-part=root] { display: block; min-width: 0; box-sizing: border-box; padding: 1rem;
|
|
4
|
+
color: var(--ui-text); font-family: var(--ui-font);
|
|
5
|
+
@media (min-width: 768px) { padding: 2rem; } }
|
|
6
|
+
[data-scope=page][data-part=inner] { margin: 0 auto; max-width: 56rem;
|
|
7
|
+
[data-align=start] > & { margin-left: 0; }
|
|
8
|
+
[data-width=narrow] > & { max-width: 40rem; }
|
|
9
|
+
[data-width=wide] > & { max-width: 80rem; }
|
|
10
|
+
[data-width=full] > & { max-width: none; } }
|
|
11
|
+
[data-scope=page][data-part=header] { display: flex; flex-direction: column; gap: 0.5rem; margin-bottom: 1.5rem; }
|
|
12
|
+
[data-scope=page][data-part=breadcrumbs] { font-size: 14px; color: var(--ui-muted); }
|
|
13
|
+
[data-scope=page][data-part=titles] { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
|
|
14
|
+
[data-scope=page][data-part=heading] { margin: 0; font-size: 24px; font-weight: 700; line-height: 1.2; }
|
|
15
|
+
[data-scope=page][data-part=description] { margin: 0.25rem 0 0; color: var(--ui-muted); font-size: 14px; }
|
|
16
|
+
[data-scope=page][data-part=actions] { display: flex; gap: 0.5rem; flex-shrink: 0; }
|
|
17
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Pagination as Ark } from '@ark-ui/react/pagination';
|
|
2
|
+
import 'iconify-icon';
|
|
3
|
+
import { type Classes } from '../utils.js';
|
|
4
|
+
import '../theme.css';
|
|
5
|
+
import './pagination.css';
|
|
6
|
+
export type PaginationProps = Omit<Ark.RootProviderProps, 'value' | 'children'> & {
|
|
7
|
+
count?: number;
|
|
8
|
+
pageSize?: number;
|
|
9
|
+
page?: number;
|
|
10
|
+
defaultPage?: number;
|
|
11
|
+
onPageChange?: (page: number) => void;
|
|
12
|
+
siblingCount?: number;
|
|
13
|
+
summary?: boolean;
|
|
14
|
+
noun?: string;
|
|
15
|
+
firstLast?: boolean;
|
|
16
|
+
label?: string;
|
|
17
|
+
classes?: Classes<'root' | 'summary' | 'pages' | 'item' | 'ellipsis' | 'firstTrigger' | 'prevTrigger' | 'nextTrigger' | 'lastTrigger'>;
|
|
18
|
+
};
|
|
19
|
+
declare function PaginationShortcut({ count, pageSize, page: pageProp, defaultPage, onPageChange, siblingCount, summary, noun: nounProp, firstLast, label: labelProp, className, classes, ...rest }: PaginationProps): import("react").JSX.Element;
|
|
20
|
+
export declare const Pagination: typeof PaginationShortcut & {
|
|
21
|
+
Root: import("react").ForwardRefExoticComponent<Ark.RootProps & import("react").RefAttributes<HTMLElement>>;
|
|
22
|
+
RootProvider: import("react").ForwardRefExoticComponent<Ark.RootProviderProps & import("react").RefAttributes<HTMLElement>>;
|
|
23
|
+
Item: import("react").ForwardRefExoticComponent<Ark.ItemProps & import("react").RefAttributes<HTMLButtonElement>>;
|
|
24
|
+
Ellipsis: import("react").ForwardRefExoticComponent<Ark.EllipsisProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
25
|
+
PrevTrigger: import("react").ForwardRefExoticComponent<Ark.PrevTriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
|
|
26
|
+
NextTrigger: import("react").ForwardRefExoticComponent<Ark.NextTriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
|
|
27
|
+
FirstTrigger: import("react").ForwardRefExoticComponent<Ark.FirstTriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
|
|
28
|
+
LastTrigger: import("react").ForwardRefExoticComponent<Ark.LastTriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
|
|
29
|
+
Context: (props: Ark.ContextProps) => import("react").ReactNode;
|
|
30
|
+
};
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Previous and next buttons and page numbers, with ellipses for long runs.
|
|
4
|
+
*
|
|
5
|
+
* <Pagination count={students.length} pageSize={10} page={page} onPageChange={setPage} />
|
|
6
|
+
* {students.slice((page - 1) * 10, page * 10).map(…)}
|
|
7
|
+
*
|
|
8
|
+
* - `count`: the number of items; `pageSize` per page (10)
|
|
9
|
+
* - `page`, `defaultPage`: the current page, from 1; `onPageChange`: called
|
|
10
|
+
* with the new one. Past the last page (after filtering), it goes to the
|
|
11
|
+
* last one
|
|
12
|
+
* - `siblingCount`: pages shown either side of the current one (1)
|
|
13
|
+
* - `summary`: shows "Showing 11–20 of 95 items" alongside
|
|
14
|
+
* - `noun`: what the items are called in the summary ('items')
|
|
15
|
+
* - `firstLast`: buttons for the first and last page, shown when some pages
|
|
16
|
+
* are hidden behind an ellipsis (false to never show them)
|
|
17
|
+
* - `label`: the nav's accessible name
|
|
18
|
+
*
|
|
19
|
+
* By hand, Ark's parts are here too: Pagination.Root, PrevTrigger, Item,
|
|
20
|
+
* Ellipsis, NextTrigger, FirstTrigger, LastTrigger, Context.
|
|
21
|
+
*/
|
|
22
|
+
import { useEffect } from 'react';
|
|
23
|
+
import { Pagination as Ark, usePagination } from '@ark-ui/react/pagination';
|
|
24
|
+
import 'iconify-icon';
|
|
25
|
+
import { cx, partsOf, useControllable, withParts } from '../utils.js';
|
|
26
|
+
import { useLocale, useMessages } from '../format/LocaleProvider.js';
|
|
27
|
+
import { formatNumber } from '../format/format.js';
|
|
28
|
+
import '../theme.css';
|
|
29
|
+
import './pagination.css';
|
|
30
|
+
function PaginationShortcut({ count = 0, pageSize = 10, page: pageProp, defaultPage = 1, onPageChange, siblingCount = 1, summary = false, noun: nounProp, firstLast = true, label: labelProp, className, classes, ...rest }) {
|
|
31
|
+
const t = useMessages();
|
|
32
|
+
const locale = useLocale();
|
|
33
|
+
const noun = nounProp ?? t.pagination.noun;
|
|
34
|
+
const label = labelProp ?? t.pagination.label;
|
|
35
|
+
const fmt = (n) => formatNumber(n, { locale });
|
|
36
|
+
const part = partsOf('pagination', classes);
|
|
37
|
+
const [current, setPage] = useControllable(pageProp, defaultPage, onPageChange);
|
|
38
|
+
const totalPages = Math.max(1, Math.ceil(count / pageSize));
|
|
39
|
+
const page = Math.min(Math.max(1, current), totalPages);
|
|
40
|
+
// Past the last page (the list shrank): move to it.
|
|
41
|
+
useEffect(() => { if (current > totalPages)
|
|
42
|
+
setPage(totalPages); }, [current, totalPages, setPage]);
|
|
43
|
+
const api = usePagination({
|
|
44
|
+
count, pageSize, siblingCount, page, type: 'button', onPageChange: (details) => setPage(details.page),
|
|
45
|
+
...(t.zag.pagination && { translations: t.zag.pagination }),
|
|
46
|
+
});
|
|
47
|
+
const from = count ? (page - 1) * pageSize + 1 : 0;
|
|
48
|
+
const to = Math.min(page * pageSize, count);
|
|
49
|
+
// First and last only help when some page numbers are hidden.
|
|
50
|
+
const edges = firstLast && api.pages.some((item) => item.type === 'ellipsis');
|
|
51
|
+
return (_jsxs(Ark.RootProvider, { value: api, ...rest, ...part('root'), className: cx(className, classes?.root), "aria-label": label, children: [summary && _jsx("span", { ...part('summary'), children: t.pagination.showing(fmt(from), fmt(to), fmt(count), noun) }), _jsxs("div", { ...part('pages'), children: [edges && _jsx(Ark.FirstTrigger, { ...part('first-trigger'), children: _jsx("iconify-icon", { icon: "lucide:chevrons-left" }) }), _jsx(Ark.PrevTrigger, { ...part('prev-trigger'), children: _jsx("iconify-icon", { icon: "lucide:chevron-left" }) }), api.pages.map((item, i) => item.type === 'page'
|
|
52
|
+
? _jsx(Ark.Item, { ...item, ...part('item'), children: item.value }, i)
|
|
53
|
+
: _jsx(Ark.Ellipsis, { index: i, ...part('ellipsis'), children: "\u2026" }, i)), _jsx(Ark.NextTrigger, { ...part('next-trigger'), children: _jsx("iconify-icon", { icon: "lucide:chevron-right" }) }), edges && _jsx(Ark.LastTrigger, { ...part('last-trigger'), children: _jsx("iconify-icon", { icon: "lucide:chevrons-right" }) })] })] }));
|
|
54
|
+
}
|
|
55
|
+
export const Pagination = withParts(PaginationShortcut, {
|
|
56
|
+
Root: Ark.Root, RootProvider: Ark.RootProvider, Item: Ark.Item, Ellipsis: Ark.Ellipsis, PrevTrigger: Ark.PrevTrigger,
|
|
57
|
+
NextTrigger: Ark.NextTrigger, FirstTrigger: Ark.FirstTrigger, LastTrigger: Ark.LastTrigger, Context: Ark.Context,
|
|
58
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
@layer ui {
|
|
2
|
+
[data-scope=pagination][data-part=root] { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap;
|
|
3
|
+
color: var(--ui-text); font-family: var(--ui-font); font-size: 14px; }
|
|
4
|
+
[data-scope=pagination][data-part=summary] { color: var(--ui-muted); font-size: 12px; }
|
|
5
|
+
[data-scope=pagination][data-part=pages] { display: flex; align-items: center; gap: 0.25rem; margin-left: auto; }
|
|
6
|
+
[data-scope=pagination]:is([data-part=item], [data-part=first-trigger], [data-part=prev-trigger], [data-part=next-trigger], [data-part=last-trigger]) {
|
|
7
|
+
display: inline-flex; align-items: center; justify-content: center; min-width: 2rem; height: 2rem; padding: 0 0.5rem; box-sizing: border-box;
|
|
8
|
+
border: 1px solid transparent; border-radius: var(--ui-radius); background: transparent; color: inherit; font: inherit; cursor: pointer;
|
|
9
|
+
&:hover { background: var(--ui-hover); }
|
|
10
|
+
&:focus-visible { outline: 2px solid var(--ui-ring-soft); }
|
|
11
|
+
&:disabled { opacity: 0.4; cursor: not-allowed; background: transparent; }
|
|
12
|
+
}
|
|
13
|
+
[data-scope=pagination][data-part=item][data-selected] { border-color: var(--ui-border); background: var(--ui-surface); font-weight: 600; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); }
|
|
14
|
+
[data-scope=pagination][data-part=ellipsis] { display: inline-flex; align-items: center; justify-content: center; min-width: 2rem; height: 2rem; color: var(--ui-muted); }
|
|
15
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A password input with a button to show or hide it. Other attributes go on
|
|
3
|
+
* the <input>.
|
|
4
|
+
*
|
|
5
|
+
* <PasswordInput label="Password" value={password} onValueChange={setPassword} autoComplete="new-password" />
|
|
6
|
+
*
|
|
7
|
+
* - `value`, `defaultValue`; `onValueChange`: called with the new text
|
|
8
|
+
* - `icon`: an Iconify name before it
|
|
9
|
+
* - `visible`, `defaultVisible`; `onVisibilityChange`: whether the password shows
|
|
10
|
+
*
|
|
11
|
+
* By hand, with Ark's parts:
|
|
12
|
+
*
|
|
13
|
+
* <PasswordInput.Root>
|
|
14
|
+
* <PasswordInput.Label>Password</PasswordInput.Label>
|
|
15
|
+
* <PasswordInput.Control>
|
|
16
|
+
* <PasswordInput.Input />
|
|
17
|
+
* <PasswordInput.VisibilityTrigger><PasswordInput.Indicator fallback="Show">Hide</PasswordInput.Indicator></PasswordInput.VisibilityTrigger>
|
|
18
|
+
* </PasswordInput.Control>
|
|
19
|
+
* </PasswordInput.Root>
|
|
20
|
+
*/
|
|
21
|
+
import type { ComponentProps, ReactNode } from 'react';
|
|
22
|
+
import { PasswordInput as Ark, type PasswordInputVisibilityChangeDetails } from '@ark-ui/react/password-input';
|
|
23
|
+
import 'iconify-icon';
|
|
24
|
+
import { type Classes } from '../utils.js';
|
|
25
|
+
import '../theme.css';
|
|
26
|
+
import './password-input.css';
|
|
27
|
+
export type PasswordInputProps = Omit<ComponentProps<'input'>, 'type'> & {
|
|
28
|
+
label?: ReactNode;
|
|
29
|
+
onValueChange?: (value: string) => void;
|
|
30
|
+
icon?: string | null;
|
|
31
|
+
visible?: boolean;
|
|
32
|
+
defaultVisible?: boolean;
|
|
33
|
+
onVisibilityChange?: (visible: boolean, details: PasswordInputVisibilityChangeDetails) => void;
|
|
34
|
+
classes?: Classes<'root' | 'label' | 'control' | 'icon' | 'input' | 'visibilityTrigger'>;
|
|
35
|
+
};
|
|
36
|
+
declare function PasswordInputShortcut({ label, icon, visible, defaultVisible, onVisibilityChange, onValueChange, onChange, disabled, className, classes, ...rest }: PasswordInputProps): import("react").JSX.Element;
|
|
37
|
+
export declare const PasswordInput: typeof PasswordInputShortcut & {
|
|
38
|
+
Root: import("react").ForwardRefExoticComponent<Ark.RootProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
39
|
+
RootProvider: import("react").ForwardRefExoticComponent<Ark.RootProviderProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
40
|
+
Label: import("react").ForwardRefExoticComponent<Ark.LabelProps & import("react").RefAttributes<HTMLLabelElement>>;
|
|
41
|
+
Control: import("react").ForwardRefExoticComponent<Ark.ControlProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
42
|
+
Input: import("react").ForwardRefExoticComponent<Ark.InputProps & import("react").RefAttributes<HTMLInputElement>>;
|
|
43
|
+
VisibilityTrigger: import("react").ForwardRefExoticComponent<Ark.VisibilityTriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
|
|
44
|
+
Indicator: import("react").ForwardRefExoticComponent<Ark.IndicatorProps & import("react").RefAttributes<HTMLSpanElement>>;
|
|
45
|
+
Context: (props: Ark.ContextProps) => ReactNode;
|
|
46
|
+
};
|
|
47
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { PasswordInput as Ark, usePasswordInput } from '@ark-ui/react/password-input';
|
|
3
|
+
import { useFieldContext } from '@ark-ui/react/field';
|
|
4
|
+
import 'iconify-icon';
|
|
5
|
+
import { cx, partsOf, withParts } from '../utils.js';
|
|
6
|
+
import { useMessages } from '../format/LocaleProvider.js';
|
|
7
|
+
import '../theme.css';
|
|
8
|
+
import './password-input.css';
|
|
9
|
+
function PasswordInputShortcut({ label = null, icon = null, visible, defaultVisible, onVisibilityChange, onValueChange, onChange, disabled, className, classes, ...rest }) {
|
|
10
|
+
const t = useMessages();
|
|
11
|
+
const field = useFieldContext();
|
|
12
|
+
const part = partsOf('password-input', classes);
|
|
13
|
+
const password = usePasswordInput({
|
|
14
|
+
...(visible !== undefined && { visible }),
|
|
15
|
+
...(defaultVisible !== undefined && { defaultVisible }),
|
|
16
|
+
...(disabled !== undefined && { disabled }),
|
|
17
|
+
...(t.zag.passwordInput && { translations: t.zag.passwordInput }),
|
|
18
|
+
...(onVisibilityChange && { onVisibilityChange: (details) => onVisibilityChange(details.visible, details) }),
|
|
19
|
+
});
|
|
20
|
+
function change(e) {
|
|
21
|
+
onChange?.(e);
|
|
22
|
+
onValueChange?.(e.target.value);
|
|
23
|
+
}
|
|
24
|
+
return (_jsxs(Ark.RootProvider, { value: password, ...part('root'), className: cx(className, classes?.root), children: [label && !field && _jsx(Ark.Label, { ...part('label'), children: label }), _jsxs(Ark.Control, { ...part('control'), children: [icon && _jsx("iconify-icon", { ...part('icon'), icon: icon, "aria-hidden": "true" }), _jsx(Ark.Input, { ...rest, ...part('input'), onChange: change }), _jsx(Ark.VisibilityTrigger, { ...part('visibility-trigger'), children: _jsx("iconify-icon", { icon: password.visible ? 'lucide:eye-off' : 'lucide:eye', "aria-hidden": "true" }) })] })] }));
|
|
25
|
+
}
|
|
26
|
+
export const PasswordInput = withParts(PasswordInputShortcut, {
|
|
27
|
+
Root: Ark.Root,
|
|
28
|
+
RootProvider: Ark.RootProvider,
|
|
29
|
+
Label: Ark.Label,
|
|
30
|
+
Control: Ark.Control,
|
|
31
|
+
Input: Ark.Input,
|
|
32
|
+
VisibilityTrigger: Ark.VisibilityTrigger,
|
|
33
|
+
Indicator: Ark.Indicator,
|
|
34
|
+
Context: Ark.Context,
|
|
35
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
@layer ui {
|
|
2
|
+
[data-scope=password-input][data-part=root] { display: block; min-width: 0; color: var(--ui-text); font-family: var(--ui-font); }
|
|
3
|
+
[data-scope=password-input][data-part=label] { display: block; font-size: 14px; font-weight: 500; margin-bottom: 0.375rem; }
|
|
4
|
+
[data-scope=password-input][data-part=control] { display: flex; align-items: center; gap: 0.5rem; height: var(--ui-control-height);
|
|
5
|
+
padding: 0 0.25rem 0 0.75rem; box-sizing: border-box; background: var(--ui-surface);
|
|
6
|
+
border: 1px solid var(--ui-border); border-radius: var(--ui-radius); font-size: 14px;
|
|
7
|
+
&:focus-within { border-color: var(--ui-ring); outline: 2px solid var(--ui-ring-soft); }
|
|
8
|
+
&[data-invalid], &:has([aria-invalid=true]) { border-color: var(--ui-danger); }
|
|
9
|
+
&[data-disabled] { opacity: 0.5; } }
|
|
10
|
+
[data-scope=password-input][data-part=input] { flex: 1; min-width: 0; padding: 0; border: none; background: transparent; outline: none;
|
|
11
|
+
color: inherit; font: inherit;
|
|
12
|
+
&::placeholder { color: var(--ui-muted); } }
|
|
13
|
+
[data-scope=password-input][data-part=icon] { display: block; width: 1em; height: 1em; font-size: 16px; color: var(--ui-muted); flex-shrink: 0; }
|
|
14
|
+
[data-scope=password-input][data-part=visibility-trigger] { display: grid; place-items: center; flex-shrink: 0; width: 1.75rem; height: 1.75rem;
|
|
15
|
+
border: none; background: transparent; border-radius: 4px; color: var(--ui-muted); cursor: pointer;
|
|
16
|
+
&:hover { background: var(--ui-hover); color: var(--ui-text); }
|
|
17
|
+
&:focus-visible { outline: 2px solid var(--ui-ring-soft); } }
|
|
18
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A code input: one box per character, for verification codes and PINs.
|
|
3
|
+
*
|
|
4
|
+
* <PinInput label="Code" count={6} value={code} onValueChange={setCode} onComplete={verify} />
|
|
5
|
+
*
|
|
6
|
+
* - `value`, `defaultValue`: a string; `onValueChange`: called with it
|
|
7
|
+
* - `count`: how many boxes (4)
|
|
8
|
+
* - `type`: 'numeric', 'alphabetic' or 'alphanumeric'
|
|
9
|
+
* - `mask`: hide the characters, like a password
|
|
10
|
+
* - `placeholder`: shown in each empty box (none)
|
|
11
|
+
* - `otp`: offer one-time codes from the keyboard (autocomplete="one-time-code")
|
|
12
|
+
* - `onComplete`: called with the value once every box is filled
|
|
13
|
+
* - `name`: for plain form posts
|
|
14
|
+
*
|
|
15
|
+
* By hand, with Ark's parts:
|
|
16
|
+
*
|
|
17
|
+
* <PinInput.Root count={4}>
|
|
18
|
+
* <PinInput.Label>PIN</PinInput.Label>
|
|
19
|
+
* <PinInput.Control>{[0, 1, 2, 3].map((index) => <PinInput.Input key={index} index={index} />)}</PinInput.Control>
|
|
20
|
+
* <PinInput.HiddenInput />
|
|
21
|
+
* </PinInput.Root>
|
|
22
|
+
*/
|
|
23
|
+
import type { ReactNode } from 'react';
|
|
24
|
+
import { PinInput as Ark, type PinInputValueChangeDetails } from '@ark-ui/react/pin-input';
|
|
25
|
+
import { type Classes } from '../utils.js';
|
|
26
|
+
import '../theme.css';
|
|
27
|
+
import './pin-input.css';
|
|
28
|
+
export type PinInputProps = Omit<Ark.RootProps, 'value' | 'defaultValue' | 'onValueChange' | 'onValueComplete' | 'label'> & {
|
|
29
|
+
label?: ReactNode;
|
|
30
|
+
value?: string;
|
|
31
|
+
defaultValue?: string;
|
|
32
|
+
onValueChange?: (value: string, details: PinInputValueChangeDetails) => void;
|
|
33
|
+
onComplete?: (value: string, details: PinInputValueChangeDetails) => void;
|
|
34
|
+
classes?: Classes<'root' | 'label' | 'control' | 'input'>;
|
|
35
|
+
};
|
|
36
|
+
declare function PinInputShortcut({ label, count, type, placeholder, value, defaultValue, onValueChange, onComplete, className, classes, ...rest }: PinInputProps): import("react").JSX.Element;
|
|
37
|
+
export declare const PinInput: typeof PinInputShortcut & {
|
|
38
|
+
Root: import("react").ForwardRefExoticComponent<Ark.RootProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
39
|
+
Label: import("react").ForwardRefExoticComponent<Ark.LabelProps & import("react").RefAttributes<HTMLLabelElement>>;
|
|
40
|
+
Control: import("react").ForwardRefExoticComponent<Ark.ControlProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
41
|
+
Input: import("react").ForwardRefExoticComponent<Ark.InputProps & import("react").RefAttributes<HTMLInputElement>>;
|
|
42
|
+
HiddenInput: import("react").ForwardRefExoticComponent<Ark.HiddenInputProps & import("react").RefAttributes<HTMLInputElement>>;
|
|
43
|
+
Context: (props: Ark.ContextProps) => ReactNode;
|
|
44
|
+
};
|
|
45
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { PinInput as Ark } from '@ark-ui/react/pin-input';
|
|
3
|
+
import { useFieldContext } from '@ark-ui/react/field';
|
|
4
|
+
import { cx, partsOf, withParts } from '../utils.js';
|
|
5
|
+
import { useMessages } from '../format/LocaleProvider.js';
|
|
6
|
+
import '../theme.css';
|
|
7
|
+
import './pin-input.css';
|
|
8
|
+
function PinInputShortcut({ label = null, count = 4, type = 'numeric', placeholder = '', value, defaultValue, onValueChange, onComplete, className, classes, ...rest }) {
|
|
9
|
+
const t = useMessages();
|
|
10
|
+
const field = useFieldContext();
|
|
11
|
+
const part = partsOf('pin-input', classes);
|
|
12
|
+
const boxes = (text) => text.split('').slice(0, count);
|
|
13
|
+
return (_jsxs(Ark.Root, { ...(t.zag.pinInput && { translations: t.zag.pinInput }), ...rest, ...part('root'), className: cx(className, classes?.root), count: count, type: type, placeholder: placeholder, ...(value !== undefined && { value: boxes(value) }), ...(defaultValue !== undefined && { defaultValue: boxes(defaultValue) }), ...(onValueChange && { onValueChange: (details) => onValueChange(details.valueAsString, details) }), ...(onComplete && { onValueComplete: (details) => onComplete(details.valueAsString, details) }), children: [label && !field && _jsx(Ark.Label, { ...part('label'), children: label }), _jsx(Ark.Control, { ...part('control'), children: Array.from({ length: count }, (_, index) => _jsx(Ark.Input, { index: index, ...part('input') }, index)) }), _jsx(Ark.HiddenInput, {})] }));
|
|
14
|
+
}
|
|
15
|
+
export const PinInput = withParts(PinInputShortcut, {
|
|
16
|
+
Root: Ark.Root,
|
|
17
|
+
Label: Ark.Label,
|
|
18
|
+
Control: Ark.Control,
|
|
19
|
+
Input: Ark.Input,
|
|
20
|
+
HiddenInput: Ark.HiddenInput,
|
|
21
|
+
Context: Ark.Context,
|
|
22
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
@layer ui {
|
|
2
|
+
[data-scope=pin-input][data-part=root] { display: block; color: var(--ui-text); font-family: var(--ui-font); }
|
|
3
|
+
[data-scope=pin-input][data-part=label] { display: block; font-size: 14px; font-weight: 500; margin-bottom: 0.375rem; }
|
|
4
|
+
[data-scope=pin-input][data-part=control] { display: flex; gap: 0.5rem; }
|
|
5
|
+
[data-scope=pin-input][data-part=input] { width: var(--ui-control-height); height: var(--ui-control-height); padding: 0; box-sizing: border-box;
|
|
6
|
+
text-align: center; background: var(--ui-surface); border: 1px solid var(--ui-border); border-radius: var(--ui-radius);
|
|
7
|
+
color: inherit; font-family: inherit; font-size: 16px; font-weight: 500; outline: none;
|
|
8
|
+
&::placeholder { color: var(--ui-muted); }
|
|
9
|
+
&:focus { border-color: var(--ui-ring); outline: 2px solid var(--ui-ring-soft); }
|
|
10
|
+
&:is([data-invalid], [aria-invalid=true]) { border-color: var(--ui-danger); }
|
|
11
|
+
&[data-complete] { border-color: var(--ui-accent); }
|
|
12
|
+
&:disabled { opacity: 0.5; } }
|
|
13
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A popover: a panel that opens from a trigger, for settings or details.
|
|
3
|
+
*
|
|
4
|
+
* <Popover heading="Appearance" closable placement="bottom-end"
|
|
5
|
+
* trigger={<Button icon="lucide:palette" aria-label="Appearance" />}>
|
|
6
|
+
* …content…
|
|
7
|
+
* </Popover>
|
|
8
|
+
*
|
|
9
|
+
* - `trigger`: the element that opens it (it gets the trigger's props, so a
|
|
10
|
+
* component must pass them on, as Button does)
|
|
11
|
+
* - `heading`, `description`: a header for the panel
|
|
12
|
+
* - `closable`: a close button in the corner
|
|
13
|
+
* - `arrow`: points the panel at its trigger
|
|
14
|
+
* - `modal`: traps focus and blocks the page behind it
|
|
15
|
+
* - `open`, `defaultOpen`; `onOpenChange`
|
|
16
|
+
* - `placement`: where it opens ('bottom')
|
|
17
|
+
*
|
|
18
|
+
* By hand, with Ark's parts:
|
|
19
|
+
*
|
|
20
|
+
* <Popover.Root>
|
|
21
|
+
* <Popover.Trigger asChild><Button>Filters</Button></Popover.Trigger>
|
|
22
|
+
* <Popover.Positioner>
|
|
23
|
+
* <Popover.Content><Popover.Title>Filters</Popover.Title>…<Popover.CloseTrigger>×</Popover.CloseTrigger></Popover.Content>
|
|
24
|
+
* </Popover.Positioner>
|
|
25
|
+
* </Popover.Root>
|
|
26
|
+
*/
|
|
27
|
+
import type { ReactElement, ReactNode } from 'react';
|
|
28
|
+
import { Popover as Ark, type PopoverOpenChangeDetails } from '@ark-ui/react/popover';
|
|
29
|
+
import 'iconify-icon';
|
|
30
|
+
import { type Classes } from '../utils.js';
|
|
31
|
+
import '../theme.css';
|
|
32
|
+
import './popover.css';
|
|
33
|
+
export type PopoverProps = Omit<Ark.ContentProps, 'children'> & {
|
|
34
|
+
trigger?: ReactElement | null;
|
|
35
|
+
heading?: ReactNode;
|
|
36
|
+
description?: ReactNode;
|
|
37
|
+
closable?: boolean;
|
|
38
|
+
arrow?: boolean;
|
|
39
|
+
modal?: boolean;
|
|
40
|
+
open?: boolean;
|
|
41
|
+
defaultOpen?: boolean;
|
|
42
|
+
onOpenChange?: (open: boolean, details: PopoverOpenChangeDetails) => void;
|
|
43
|
+
placement?: NonNullable<Ark.RootProps['positioning']>['placement'];
|
|
44
|
+
classes?: Classes<'positioner' | 'content' | 'arrow' | 'arrowTip' | 'header' | 'title' | 'description' | 'closeTrigger'>;
|
|
45
|
+
children?: ReactNode;
|
|
46
|
+
};
|
|
47
|
+
declare function PopoverShortcut({ trigger, heading, description, closable, arrow, modal, open, defaultOpen, onOpenChange, placement, className, classes, children, ...rest }: PopoverProps): import("react").JSX.Element;
|
|
48
|
+
export declare const Popover: typeof PopoverShortcut & {
|
|
49
|
+
Root: (props: Ark.RootProps) => import("react").JSX.Element;
|
|
50
|
+
Trigger: import("react").ForwardRefExoticComponent<Ark.TriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
|
|
51
|
+
Anchor: import("react").ForwardRefExoticComponent<Ark.AnchorProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
52
|
+
Positioner: import("react").ForwardRefExoticComponent<Ark.PositionerProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
53
|
+
Content: import("react").ForwardRefExoticComponent<Ark.ContentProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
54
|
+
Title: import("react").ForwardRefExoticComponent<Ark.TitleProps & import("react").RefAttributes<HTMLHeadingElement>>;
|
|
55
|
+
Description: import("react").ForwardRefExoticComponent<Ark.DescriptionProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
56
|
+
CloseTrigger: import("react").ForwardRefExoticComponent<Ark.CloseTriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
|
|
57
|
+
Arrow: import("react").ForwardRefExoticComponent<Ark.ArrowProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
58
|
+
ArrowTip: import("react").ForwardRefExoticComponent<Ark.ArrowTipProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
59
|
+
Context: (props: Ark.ContextProps) => ReactNode;
|
|
60
|
+
Header: {
|
|
61
|
+
(props: import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>): ReactElement<any, string | import("react").JSXElementConstructor<any>>;
|
|
62
|
+
displayName: string;
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Popover as Ark } from '@ark-ui/react/popover';
|
|
3
|
+
import 'iconify-icon';
|
|
4
|
+
import { cx, on, partOf, partsOf, withParts } from '../utils.js';
|
|
5
|
+
import { useMessages } from '../format/LocaleProvider.js';
|
|
6
|
+
import '../theme.css';
|
|
7
|
+
import './popover.css';
|
|
8
|
+
function PopoverShortcut({ trigger = null, heading = null, description = null, closable = false, arrow = false, modal = false, open, defaultOpen, onOpenChange, placement = 'bottom', className, classes, children, ...rest }) {
|
|
9
|
+
const t = useMessages();
|
|
10
|
+
const part = partsOf('popover', classes);
|
|
11
|
+
return (_jsxs(Ark.Root, { modal: modal, positioning: { placement, strategy: 'fixed' }, ...(open !== undefined && { open }), ...(defaultOpen !== undefined && { defaultOpen }), ...on('onOpenChange', onOpenChange, (details) => details.open), children: [trigger && _jsx(Ark.Trigger, { asChild: true, children: trigger }), _jsx(Ark.Positioner, { ...part('positioner'), children: _jsxs(Ark.Content, { ...rest, ...part('content'), className: cx(className, classes?.content), children: [arrow && _jsx(Ark.Arrow, { ...part('arrow'), children: _jsx(Ark.ArrowTip, { ...part('arrow-tip') }) }), (heading || description) && (_jsxs("div", { ...part('header'), children: [heading && _jsx(Ark.Title, { ...part('title'), children: heading }), description && _jsx(Ark.Description, { ...part('description'), children: description })] })), children, closable && _jsx(Ark.CloseTrigger, { ...part('close-trigger'), "aria-label": t.common.close, children: _jsx("iconify-icon", { icon: "lucide:x" }) })] }) })] }));
|
|
12
|
+
}
|
|
13
|
+
export const Popover = withParts(PopoverShortcut, {
|
|
14
|
+
Root: Ark.Root,
|
|
15
|
+
Trigger: Ark.Trigger,
|
|
16
|
+
Anchor: Ark.Anchor,
|
|
17
|
+
Positioner: Ark.Positioner,
|
|
18
|
+
Content: Ark.Content,
|
|
19
|
+
Title: Ark.Title,
|
|
20
|
+
Description: Ark.Description,
|
|
21
|
+
CloseTrigger: Ark.CloseTrigger,
|
|
22
|
+
Arrow: Ark.Arrow,
|
|
23
|
+
ArrowTip: Ark.ArrowTip,
|
|
24
|
+
Context: Ark.Context,
|
|
25
|
+
Header: partOf('popover', 'header'),
|
|
26
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
@layer ui {
|
|
2
|
+
/* Zag copies the content's z-index onto its positioner. */
|
|
3
|
+
[data-scope=popover][data-part=content] { z-index: 50; position: relative; width: 18rem; max-width: calc(100vw - 32px);
|
|
4
|
+
padding: 1rem; box-sizing: border-box; outline: none; font-weight: 400;
|
|
5
|
+
background: var(--ui-surface); color: var(--ui-text); font-family: var(--ui-font); font-size: 14px;
|
|
6
|
+
border: 1px solid var(--ui-border); border-radius: calc(var(--ui-radius) + 2px); box-shadow: var(--ui-shadow);
|
|
7
|
+
--arrow-size: 12px; --arrow-background: var(--ui-surface); }
|
|
8
|
+
[data-scope=popover][data-part=arrow-tip] { border-top: 1px solid var(--ui-border); border-left: 1px solid var(--ui-border); }
|
|
9
|
+
[data-scope=popover][data-part=header] { padding-right: 1.5rem; margin-bottom: 0.75rem;
|
|
10
|
+
&:last-child { margin-bottom: 0; } }
|
|
11
|
+
[data-scope=popover][data-part=title] { font-weight: 600; }
|
|
12
|
+
[data-scope=popover][data-part=description] { margin: 0.25rem 0 0; color: var(--ui-muted); }
|
|
13
|
+
[data-scope=popover][data-part=close-trigger] { position: absolute; top: 0.5rem; right: 0.5rem; display: grid; place-items: center;
|
|
14
|
+
width: 1.75rem; height: 1.75rem; border: none; background: transparent; border-radius: 4px; color: var(--ui-muted); cursor: pointer;
|
|
15
|
+
&:hover { background: var(--ui-hover); color: var(--ui-text); } }
|
|
16
|
+
}
|