@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 progress indicator, as a bar or a circle.
|
|
3
|
+
*
|
|
4
|
+
* <Progress label="Course" value={64} showValue />
|
|
5
|
+
* <Progress variant="circle" value={80} showValue />
|
|
6
|
+
*
|
|
7
|
+
* - `value`: from `min` to `max` (0 to 100); null means indeterminate, for
|
|
8
|
+
* work of unknown length
|
|
9
|
+
* - `label`, `showValue`: a label and the formatted value (a percentage by
|
|
10
|
+
* default; see `formatOptions`, `locale`)
|
|
11
|
+
* - `translations.value`: writes the value text, ({ value, max, percent }) => '3 of 4'
|
|
12
|
+
* - `variant`: 'linear' (default) or 'circle'; `size` and `thickness` set the
|
|
13
|
+
* circle's diameter and stroke in px
|
|
14
|
+
*
|
|
15
|
+
* By hand, with Ark's parts:
|
|
16
|
+
*
|
|
17
|
+
* <Progress.Root value={40}>
|
|
18
|
+
* <Progress.Label>Upload</Progress.Label> <Progress.ValueText />
|
|
19
|
+
* <Progress.Track><Progress.Range /></Progress.Track>
|
|
20
|
+
* </Progress.Root>
|
|
21
|
+
*/
|
|
22
|
+
import type { ReactNode } from 'react';
|
|
23
|
+
import { Progress as Ark } from '@ark-ui/react/progress';
|
|
24
|
+
import { type Classes } from '../utils.js';
|
|
25
|
+
import '../theme.css';
|
|
26
|
+
import './progress.css';
|
|
27
|
+
export type ProgressProps = Omit<Ark.RootProps, 'value' | 'defaultValue' | 'label'> & {
|
|
28
|
+
value?: number | null;
|
|
29
|
+
label?: ReactNode;
|
|
30
|
+
showValue?: boolean;
|
|
31
|
+
variant?: 'linear' | 'circle';
|
|
32
|
+
size?: number;
|
|
33
|
+
thickness?: number;
|
|
34
|
+
classes?: Classes<'root' | 'header' | 'label' | 'valueText' | 'track' | 'range' | 'circleWrap' | 'circle' | 'circleTrack' | 'circleRange'>;
|
|
35
|
+
};
|
|
36
|
+
declare function ProgressShortcut({ value, min, max, label, showValue, variant, size, thickness, locale: localeProp, className, classes, ...rest }: ProgressProps): import("react").JSX.Element;
|
|
37
|
+
export declare const Progress: typeof ProgressShortcut & {
|
|
38
|
+
Root: import("react").ForwardRefExoticComponent<Ark.RootProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
39
|
+
Label: import("react").ForwardRefExoticComponent<Ark.LabelProps & import("react").RefAttributes<HTMLSpanElement>>;
|
|
40
|
+
ValueText: import("react").ForwardRefExoticComponent<Ark.ValueTextProps & import("react").RefAttributes<HTMLSpanElement>>;
|
|
41
|
+
Track: import("react").ForwardRefExoticComponent<Ark.TrackProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
42
|
+
Range: import("react").ForwardRefExoticComponent<Ark.RangeProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
43
|
+
Circle: import("react").ForwardRefExoticComponent<Ark.CircleProps & import("react").RefAttributes<SVGSVGElement>>;
|
|
44
|
+
CircleTrack: import("react").ForwardRefExoticComponent<Ark.CircleTrackProps & import("react").RefAttributes<SVGCircleElement>>;
|
|
45
|
+
CircleRange: import("react").ForwardRefExoticComponent<Ark.CircleRangeProps & import("react").RefAttributes<SVGCircleElement>>;
|
|
46
|
+
Context: (props: Ark.ContextProps) => ReactNode;
|
|
47
|
+
Header: {
|
|
48
|
+
(props: import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>): import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
|
|
49
|
+
displayName: string;
|
|
50
|
+
};
|
|
51
|
+
CircleWrap: {
|
|
52
|
+
(props: import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>): import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
|
|
53
|
+
displayName: string;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Progress as Ark } from '@ark-ui/react/progress';
|
|
3
|
+
import { useLocale } from '../format/LocaleProvider.js';
|
|
4
|
+
import { cx, partOf, partsOf, withParts } from '../utils.js';
|
|
5
|
+
import '../theme.css';
|
|
6
|
+
import './progress.css';
|
|
7
|
+
// The value as Zag writes it (formatOptions, translations.value); Ark's
|
|
8
|
+
// ValueText would show the percentage whatever they say.
|
|
9
|
+
const ValueText = (props) => _jsx(Ark.Context, { children: (api) => _jsx(Ark.ValueText, { ...props, children: api.valueAsString }) });
|
|
10
|
+
function ProgressShortcut({ value = null, min = 0, max = 100, label = null, showValue = false, variant = 'linear', size = 48, thickness = 5, locale: localeProp, className, classes, ...rest }) {
|
|
11
|
+
const app = useLocale();
|
|
12
|
+
const locale = localeProp ?? app ?? 'en-GB';
|
|
13
|
+
const part = partsOf('progress', classes);
|
|
14
|
+
return (_jsxs(Ark.Root, { ...rest, ...part('root'), className: cx(className, classes?.root), "data-variant": variant, value: value, min: min, max: max, locale: locale, children: [(label || (showValue && variant === 'linear')) && (_jsxs("div", { ...part('header'), children: [label && _jsx(Ark.Label, { ...part('label'), children: label }), showValue && variant === 'linear' && _jsx(ValueText, { ...part('value-text') })] })), variant === 'circle' ? (_jsxs("div", { ...part('circle-wrap'), style: { '--size': `${size}px`, '--thickness': `${thickness}px` }, children: [_jsxs(Ark.Circle, { ...part('circle'), children: [_jsx(Ark.CircleTrack, { ...part('circle-track') }), _jsx(Ark.CircleRange, { ...part('circle-range') })] }), showValue && _jsx(ValueText, { ...part('value-text') })] })) : (_jsx(Ark.Track, { ...part('track'), children: _jsx(Ark.Range, { ...part('range') }) }))] }));
|
|
15
|
+
}
|
|
16
|
+
export const Progress = withParts(ProgressShortcut, {
|
|
17
|
+
Root: Ark.Root,
|
|
18
|
+
Label: Ark.Label,
|
|
19
|
+
ValueText: Ark.ValueText,
|
|
20
|
+
Track: Ark.Track,
|
|
21
|
+
Range: Ark.Range,
|
|
22
|
+
Circle: Ark.Circle,
|
|
23
|
+
CircleTrack: Ark.CircleTrack,
|
|
24
|
+
CircleRange: Ark.CircleRange,
|
|
25
|
+
Context: Ark.Context,
|
|
26
|
+
Header: partOf('progress', 'header'),
|
|
27
|
+
CircleWrap: partOf('progress', 'circle-wrap'),
|
|
28
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
@layer ui {
|
|
2
|
+
[data-scope=progress][data-part=root] {
|
|
3
|
+
display: block; width: 100%; min-width: 0; color: var(--ui-text); font-family: var(--ui-font); font-size: 14px;
|
|
4
|
+
&[data-variant=circle] { display: inline-block; width: auto; }
|
|
5
|
+
}
|
|
6
|
+
[data-scope=progress][data-part=header] { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem; margin-bottom: 0.375rem; }
|
|
7
|
+
[data-scope=progress][data-part=label] { font-weight: 500; }
|
|
8
|
+
/* Zag calls both value texts value-text; their places tell them apart. */
|
|
9
|
+
[data-scope=progress][data-part=header] > [data-part=value-text] { margin-left: auto; color: var(--ui-muted); font-variant-numeric: tabular-nums; }
|
|
10
|
+
[data-scope=progress][data-part=track] { position: relative; height: 0.5rem; overflow: hidden; background: var(--ui-hover); border-radius: 9999px; }
|
|
11
|
+
[data-scope=progress][data-part=range] { height: 100%; width: var(--percent, 0); background: var(--ui-accent); border-radius: 9999px; transition: width 300ms ease;
|
|
12
|
+
[data-state=indeterminate] & { width: 40%; animation: ui-progress-slide 1.2s ease-in-out infinite; } }
|
|
13
|
+
[data-scope=progress][data-part=circle-wrap] { position: relative; display: inline-grid; place-items: center; }
|
|
14
|
+
/* Zag rotates the range to start at the top and sets its dash offset inline. */
|
|
15
|
+
[data-scope=progress][data-part=circle] { display: block;
|
|
16
|
+
[data-state=indeterminate] & { animation: ui-spin 1s linear infinite; } }
|
|
17
|
+
[data-scope=progress][data-part=circle-track] { stroke: var(--ui-hover); fill: none; }
|
|
18
|
+
[data-scope=progress][data-part=circle-range] { stroke: var(--ui-accent); fill: none; stroke-linecap: round;
|
|
19
|
+
stroke-dasharray: var(--circumference); stroke-dashoffset: var(--offset); transition: stroke-dashoffset 300ms ease;
|
|
20
|
+
/* Indeterminate: a 30% dash with a full gap leaves an arc to spin. */
|
|
21
|
+
[data-state=indeterminate] & { stroke-dasharray: calc(var(--circumference) * 0.3) var(--circumference); } }
|
|
22
|
+
[data-scope=progress][data-part=circle-wrap] > [data-part=value-text] { position: absolute; font-size: 12px; font-weight: 500; font-variant-numeric: tabular-nums; }
|
|
23
|
+
@keyframes ui-progress-slide { from { translate: -100%; } to { translate: 250%; } }
|
|
24
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Styles rich text: headings, lists, quotes, links, code, images and tables,
|
|
3
|
+
* e.g. the HTML an Editor saved, shown on a page.
|
|
4
|
+
*
|
|
5
|
+
* <Prose html={lesson.notes} />
|
|
6
|
+
* <Prose size="sm"><h2>Homework</h2><p>Worksheet 4…</p></Prose>
|
|
7
|
+
*
|
|
8
|
+
* Its look matches the Editor's, so what was written reads the same.
|
|
9
|
+
*
|
|
10
|
+
* - `html`: an HTML string to show (only from a source you trust, such as
|
|
11
|
+
* your own Editor's output saved by your app); or give it children
|
|
12
|
+
* - `size`: 'sm' (13px), 'md' (14px, default) or 'lg' (16px)
|
|
13
|
+
*/
|
|
14
|
+
import type { HTMLAttributes } from 'react';
|
|
15
|
+
import { type Classes } from '../utils.js';
|
|
16
|
+
import '../theme.css';
|
|
17
|
+
import './prose.css';
|
|
18
|
+
export type ProseProps = HTMLAttributes<HTMLDivElement> & {
|
|
19
|
+
html?: string | null;
|
|
20
|
+
size?: 'sm' | 'md' | 'lg';
|
|
21
|
+
classes?: Classes<'root'>;
|
|
22
|
+
};
|
|
23
|
+
export declare function Prose({ html, size, className, classes, children, ...rest }: ProseProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cx, partsOf } from '../utils.js';
|
|
3
|
+
import '../theme.css';
|
|
4
|
+
import './prose.css';
|
|
5
|
+
export function Prose({ html = null, size = 'md', className, classes, children, ...rest }) {
|
|
6
|
+
const part = partsOf('prose', classes);
|
|
7
|
+
return html != null
|
|
8
|
+
? _jsx("div", { ...rest, ...part('root'), className: cx(className, classes?.root), "data-size": size, dangerouslySetInnerHTML: { __html: html } })
|
|
9
|
+
: _jsx("div", { ...rest, ...part('root'), className: cx(className, classes?.root), "data-size": size, children: children });
|
|
10
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/* The Editor's content look, for showing what it saved. */
|
|
2
|
+
@layer ui {
|
|
3
|
+
[data-scope=prose][data-part=root] { color: var(--ui-text); font-family: var(--ui-font); font-size: 14px; line-height: 1.6; overflow-wrap: anywhere;
|
|
4
|
+
&[data-size=sm] { font-size: 13px; }
|
|
5
|
+
&[data-size=lg] { font-size: 16px; }
|
|
6
|
+
& > :first-child { margin-top: 0; }
|
|
7
|
+
& > :last-child { margin-bottom: 0; }
|
|
8
|
+
& :is(p, ul, ol, blockquote, pre, table, figure) { margin: 0 0 0.75em; }
|
|
9
|
+
& h1 { margin: 1em 0 0.5em; font-size: 1.6em; font-weight: 700; line-height: 1.25; }
|
|
10
|
+
& h2 { margin: 1em 0 0.5em; font-size: 1.3em; font-weight: 600; line-height: 1.3; }
|
|
11
|
+
& h3 { margin: 0.9em 0 0.4em; font-size: 1.1em; font-weight: 600; line-height: 1.3; }
|
|
12
|
+
& h4 { margin: 0.9em 0 0.4em; font-size: 1em; font-weight: 600; }
|
|
13
|
+
& :is(ul, ol) { padding-left: 1.25em; }
|
|
14
|
+
& ul { list-style: disc; }
|
|
15
|
+
& ol { list-style: decimal; }
|
|
16
|
+
& li > p { margin: 0; }
|
|
17
|
+
& li + li { margin-top: 0.25em; }
|
|
18
|
+
& blockquote { padding-left: 0.75em; border-left: 3px solid var(--ui-border); color: var(--ui-muted); }
|
|
19
|
+
& code { padding: 0.125em 0.3em; border-radius: 4px; background: var(--ui-hover); font-family: ui-monospace, monospace; font-size: 0.9em; }
|
|
20
|
+
& pre { padding: 0.75em 1em; border-radius: var(--ui-radius); background: var(--ui-hover); overflow-x: auto; font-size: 0.9em;
|
|
21
|
+
& code { padding: 0; background: none; } }
|
|
22
|
+
& a { color: var(--ui-accent); text-decoration: underline; text-underline-offset: 2px; }
|
|
23
|
+
& img { display: block; max-width: 100%; height: auto; margin: 0.75em 0; border-radius: var(--ui-radius); }
|
|
24
|
+
& hr { margin: 1.5em 0; border: none; border-top: 1px solid var(--ui-border); }
|
|
25
|
+
& strong { font-weight: 600; }
|
|
26
|
+
& table { width: 100%; border-collapse: collapse; font-size: 0.95em; }
|
|
27
|
+
& :is(th, td) { padding: 0.4em 0.6em; border-bottom: 1px solid var(--ui-border); text-align: left; }
|
|
28
|
+
& th { font-weight: 600; } }
|
|
29
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A group of radio buttons: pick one.
|
|
3
|
+
*
|
|
4
|
+
* <RadioGroup label="Lesson length" items={['30 min', '45 min', '60 min']} value={length} onValueChange={setLength} />
|
|
5
|
+
*
|
|
6
|
+
* - `items`: strings, or objects (see `labelKey`, `valueKey`, `disabledKey`; `description`)
|
|
7
|
+
* - `value`, `defaultValue`: the item's own value; `onValueChange`: called with it
|
|
8
|
+
* - `label`: a label above it (skipped inside a Field)
|
|
9
|
+
* - `orientation`: 'vertical' or 'horizontal'
|
|
10
|
+
* - `variant`: 'list' (default), or 'cards': each option a bordered card
|
|
11
|
+
* (with its `description`, and an `icon` from the item), chosen one outlined
|
|
12
|
+
* - `name`: for plain form posts
|
|
13
|
+
*
|
|
14
|
+
* By hand, with Ark's parts:
|
|
15
|
+
*
|
|
16
|
+
* <RadioGroup.Root defaultValue="45">
|
|
17
|
+
* <RadioGroup.Label>Length</RadioGroup.Label>
|
|
18
|
+
* {['30', '45'].map((value) => (
|
|
19
|
+
* <RadioGroup.Item key={value} value={value}>
|
|
20
|
+
* <RadioGroup.ItemControl /><RadioGroup.ItemText>{value} min</RadioGroup.ItemText><RadioGroup.ItemHiddenInput />
|
|
21
|
+
* </RadioGroup.Item>
|
|
22
|
+
* ))}
|
|
23
|
+
* </RadioGroup.Root>
|
|
24
|
+
*/
|
|
25
|
+
import type { ReactNode } from 'react';
|
|
26
|
+
import { RadioGroup as Ark, type RadioGroupValueChangeDetails } from '@ark-ui/react/radio-group';
|
|
27
|
+
import 'iconify-icon';
|
|
28
|
+
import { type Classes } from '../utils.js';
|
|
29
|
+
import '../theme.css';
|
|
30
|
+
import './radio-group.css';
|
|
31
|
+
type Value<T, K extends string> = T extends string | number ? T : K extends keyof T ? T[K] : unknown;
|
|
32
|
+
export type RadioGroupProps<T, K extends string = 'value'> = Omit<Ark.RootProps, 'value' | 'defaultValue' | 'onValueChange' | 'label'> & {
|
|
33
|
+
items: readonly T[];
|
|
34
|
+
value?: NoInfer<Value<T, K>> | null;
|
|
35
|
+
defaultValue?: NoInfer<Value<T, K>> | null;
|
|
36
|
+
onValueChange?: (value: NoInfer<Value<T, K>> | null, details: RadioGroupValueChangeDetails) => void;
|
|
37
|
+
label?: ReactNode;
|
|
38
|
+
labelKey?: string;
|
|
39
|
+
valueKey?: K;
|
|
40
|
+
disabledKey?: string;
|
|
41
|
+
variant?: 'list' | 'cards';
|
|
42
|
+
classes?: Classes<'root' | 'label' | 'items' | 'item' | 'itemControl' | 'icon' | 'text' | 'itemText' | 'description'>;
|
|
43
|
+
};
|
|
44
|
+
declare function RadioGroupShortcut<T, K extends string = 'value'>({ items, value, defaultValue, onValueChange, label, orientation, variant, labelKey, valueKey, disabledKey, className, classes, ...rest }: RadioGroupProps<T, K>): import("react").JSX.Element;
|
|
45
|
+
export declare const RadioGroup: typeof RadioGroupShortcut & {
|
|
46
|
+
Root: import("react").ForwardRefExoticComponent<Ark.RootProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
47
|
+
Label: import("react").ForwardRefExoticComponent<Ark.LabelProps & import("react").RefAttributes<HTMLSpanElement>>;
|
|
48
|
+
Item: import("react").ForwardRefExoticComponent<Ark.ItemProps & import("react").RefAttributes<HTMLLabelElement>>;
|
|
49
|
+
ItemControl: import("react").ForwardRefExoticComponent<Ark.ItemControlProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
50
|
+
ItemText: import("react").ForwardRefExoticComponent<Ark.ItemTextProps & import("react").RefAttributes<HTMLSpanElement>>;
|
|
51
|
+
ItemHiddenInput: import("react").ForwardRefExoticComponent<Ark.ItemHiddenInputProps & import("react").RefAttributes<HTMLInputElement>>;
|
|
52
|
+
Indicator: import("react").ForwardRefExoticComponent<Ark.IndicatorProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
53
|
+
Context: (props: import("@ark-ui/react/radio-group").RadioGroupContextProps) => ReactNode;
|
|
54
|
+
Items: {
|
|
55
|
+
(props: import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>): import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
|
|
56
|
+
displayName: string;
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { RadioGroup as Ark } from '@ark-ui/react/radio-group';
|
|
3
|
+
import { useFieldContext } from '@ark-ui/react/field';
|
|
4
|
+
import 'iconify-icon';
|
|
5
|
+
import { cx, partOf, partsOf, useControllable, withParts } from '../utils.js';
|
|
6
|
+
import '../theme.css';
|
|
7
|
+
import './radio-group.css';
|
|
8
|
+
function RadioGroupShortcut({ items, value, defaultValue, onValueChange, label = null, orientation = 'vertical', variant = 'list', labelKey = 'label', valueKey = 'value', disabledKey = 'disabled', className, classes, ...rest }) {
|
|
9
|
+
const field = useFieldContext();
|
|
10
|
+
const part = partsOf('radio-group', classes);
|
|
11
|
+
const [current, setCurrent] = useControllable(value, defaultValue ?? null, undefined);
|
|
12
|
+
const key = (item, k) => item[k];
|
|
13
|
+
const isObject = (item) => typeof item === 'object' && item !== null;
|
|
14
|
+
const labelOf = (item) => String(isObject(item) ? key(item, labelKey) : item);
|
|
15
|
+
const valueOf = (item) => (isObject(item) ? key(item, valueKey) : item);
|
|
16
|
+
const descriptionOf = (item) => (isObject(item) ? key(item, 'description') : null);
|
|
17
|
+
const iconOf = (item) => (isObject(item) ? key(item, 'icon') : undefined);
|
|
18
|
+
// Zag's values are strings; map them back to the items' own.
|
|
19
|
+
function change(details) {
|
|
20
|
+
const item = items.find((item) => String(valueOf(item)) === details.value);
|
|
21
|
+
const next = item === undefined ? null : valueOf(item);
|
|
22
|
+
setCurrent(next);
|
|
23
|
+
onValueChange?.(next, details);
|
|
24
|
+
}
|
|
25
|
+
return (_jsxs(Ark.Root, { ...rest, ...part('root'), className: cx(className, classes?.root), orientation: orientation, "data-variant": variant, value: current == null ? null : String(current), onValueChange: change, "aria-describedby": field?.ariaDescribedby, ...(field && { ids: { label: field.ids.label }, invalid: field.invalid, disabled: field.disabled }), children: [label && !field && _jsx(Ark.Label, { ...part('label'), children: label }), _jsx("div", { ...part('items'), children: items.map((item) => (_jsxs(Ark.Item, { value: String(valueOf(item)), disabled: isObject(item) && !!key(item, disabledKey), ...part('item'), children: [_jsx(Ark.ItemControl, { ...part('item-control') }), iconOf(item) && _jsx("iconify-icon", { ...part('icon'), icon: iconOf(item), "aria-hidden": "true" }), _jsxs("span", { ...part('text'), children: [_jsx(Ark.ItemText, { ...part('item-text'), children: labelOf(item) }), descriptionOf(item) && _jsx("span", { ...part('description'), children: descriptionOf(item) })] }), _jsx(Ark.ItemHiddenInput, {})] }, String(valueOf(item))))) })] }));
|
|
26
|
+
}
|
|
27
|
+
export const RadioGroup = withParts(RadioGroupShortcut, {
|
|
28
|
+
Root: Ark.Root,
|
|
29
|
+
Label: Ark.Label,
|
|
30
|
+
Item: Ark.Item,
|
|
31
|
+
ItemControl: Ark.ItemControl,
|
|
32
|
+
ItemText: Ark.ItemText,
|
|
33
|
+
ItemHiddenInput: Ark.ItemHiddenInput,
|
|
34
|
+
Indicator: Ark.Indicator,
|
|
35
|
+
Context: Ark.Context,
|
|
36
|
+
Items: partOf('radio-group', 'items'),
|
|
37
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/* Ark's root (the radiogroup) holds the label and the items; built by hand,
|
|
2
|
+
the items can sit straight in it. */
|
|
3
|
+
@layer ui {
|
|
4
|
+
[data-scope=radio-group][data-part=root] { display: block; color: var(--ui-text); font-family: var(--ui-font);
|
|
5
|
+
> [data-part=item] + [data-part=item] { margin-top: 0.5rem; } }
|
|
6
|
+
[data-scope=radio-group][data-part=label] { display: block; font-size: 14px; font-weight: 500; margin-bottom: 0.375rem; }
|
|
7
|
+
[data-scope=radio-group][data-part=items] { display: flex; flex-direction: column; gap: 0.5rem;
|
|
8
|
+
[data-orientation=horizontal] > & { flex-direction: row; flex-wrap: wrap; gap: 0.5rem 1rem; } }
|
|
9
|
+
[data-scope=radio-group][data-part=item] { display: flex; align-items: flex-start; gap: 0.5rem; font-size: 14px; cursor: pointer;
|
|
10
|
+
&[data-disabled] { opacity: 0.5; cursor: not-allowed; } }
|
|
11
|
+
[data-scope=radio-group][data-part=item-control] { display: grid; place-items: center; flex-shrink: 0; width: 1rem; height: 1rem;
|
|
12
|
+
margin-top: 0.125rem; box-sizing: border-box; background: var(--ui-surface); border: 1px solid var(--ui-border); border-radius: 9999px;
|
|
13
|
+
&[data-state=checked] { border-color: var(--ui-accent); border-width: 5px; }
|
|
14
|
+
&[data-focus-visible] { outline: 2px solid var(--ui-ring-soft); border-color: var(--ui-ring); }
|
|
15
|
+
&[data-invalid] { border-color: var(--ui-danger); } }
|
|
16
|
+
[data-scope=radio-group][data-part=text] { display: flex; flex-direction: column; gap: 0.125rem; }
|
|
17
|
+
[data-scope=radio-group][data-part=description] { color: var(--ui-muted); font-size: 12px; }
|
|
18
|
+
/* Cards: each option a bordered card, the chosen one outlined in the accent. */
|
|
19
|
+
[data-scope=radio-group][data-part=icon] { flex-shrink: 0; margin-top: 0.0625rem; color: var(--ui-muted); font-size: 18px; }
|
|
20
|
+
[data-scope=radio-group][data-variant=cards] {
|
|
21
|
+
& > [data-part=items] { display: grid; grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr)); gap: 0.5rem; }
|
|
22
|
+
& [data-part=item] { gap: 0.625rem; padding: 0.75rem; border: 1px solid var(--ui-border); border-radius: calc(var(--ui-radius) + 2px);
|
|
23
|
+
background: var(--ui-surface); transition: border-color 120ms, background 120ms;
|
|
24
|
+
&:hover { border-color: var(--ui-muted); }
|
|
25
|
+
&[data-state=checked] { border-color: var(--ui-accent); background: color-mix(in srgb, var(--ui-accent-soft) 45%, var(--ui-surface));
|
|
26
|
+
box-shadow: 0 0 0 1px var(--ui-accent); }
|
|
27
|
+
&[data-focus-visible] { outline: 2px solid var(--ui-ring-soft); }
|
|
28
|
+
&[data-state=checked] [data-part=icon] { color: var(--ui-accent); } }
|
|
29
|
+
& [data-part=item-text] { font-weight: 500; } }
|
|
30
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A number whose digits roll to their new value when it changes, e.g. a live
|
|
3
|
+
* count on a dashboard. Formatted like FormatNumber; still while the system
|
|
4
|
+
* asks for reduced motion.
|
|
5
|
+
*
|
|
6
|
+
* <RollingNumber value={appointments.length} />
|
|
7
|
+
* <RollingNumber value={revenue} format={{ style: 'currency', currency: 'GBP', maximumFractionDigits: 0 }} />
|
|
8
|
+
*
|
|
9
|
+
* - `value`: the number
|
|
10
|
+
* - `format`: Intl.NumberFormat's options (grouping by default)
|
|
11
|
+
* - `duration`: how long a roll takes, in ms (600)
|
|
12
|
+
* - `locale`: the LocaleProvider's (or the browser's) by default
|
|
13
|
+
*
|
|
14
|
+
* Assistive tech reads the whole number, not the digits.
|
|
15
|
+
*/
|
|
16
|
+
import type { HTMLAttributes } from 'react';
|
|
17
|
+
import { type Classes } from '../utils.js';
|
|
18
|
+
import '../theme.css';
|
|
19
|
+
import './rolling-number.css';
|
|
20
|
+
export type RollingNumberProps = Omit<HTMLAttributes<HTMLSpanElement>, 'children'> & {
|
|
21
|
+
value: number;
|
|
22
|
+
format?: Intl.NumberFormatOptions;
|
|
23
|
+
duration?: number;
|
|
24
|
+
locale?: string;
|
|
25
|
+
classes?: Classes<'root' | 'digit' | 'strip' | 'char'>;
|
|
26
|
+
};
|
|
27
|
+
export declare function RollingNumber({ value, format, duration, locale, className, classes, style, ...rest }: RollingNumberProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { formatNumber } from '../format/format.js';
|
|
3
|
+
import { useLocale } from '../format/LocaleProvider.js';
|
|
4
|
+
import { cx, partsOf } from '../utils.js';
|
|
5
|
+
import '../theme.css';
|
|
6
|
+
import './rolling-number.css';
|
|
7
|
+
const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
|
|
8
|
+
export function RollingNumber({ value, format = {}, duration = 600, locale, className, classes, style, ...rest }) {
|
|
9
|
+
const app = useLocale();
|
|
10
|
+
const part = partsOf('rolling-number', classes);
|
|
11
|
+
const text = formatNumber(value, { ...format, locale: locale ?? app });
|
|
12
|
+
// Keyed from the right, so the ones stay the ones as the number grows.
|
|
13
|
+
const chars = [...text].map((char, i, all) => ({ char, key: all.length - i }));
|
|
14
|
+
return (_jsx("span", { ...rest, ...part('root'), className: cx(className, classes?.root), role: "img", "aria-label": text, style: { ...style, '--duration': `${duration}ms` }, children: chars.map(({ char, key }) => /\d/.test(char)
|
|
15
|
+
// A column of 0–9, moved so the digit shows.
|
|
16
|
+
? _jsxs("span", { ...part('digit'), "aria-hidden": "true", children: [_jsx("span", { ...part('strip'), style: { '--digit': Number(char) }, children: digits.map((d) => _jsx("span", { children: d }, d)) }), _jsx("span", { ...part('sizer'), children: "0" })] }, key)
|
|
17
|
+
: _jsx("span", { ...part('char'), "aria-hidden": "true", children: char }, key)) }));
|
|
18
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
@layer ui {
|
|
2
|
+
[data-scope=rolling-number][data-part=root] { display: inline-flex; align-items: baseline; font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
3
|
+
/* One digit: a window a line tall over a column of 0–9. */
|
|
4
|
+
[data-scope=rolling-number][data-part=digit] { position: relative; display: inline-block; height: 1lh; overflow: hidden; }
|
|
5
|
+
[data-scope=rolling-number][data-part=sizer] { visibility: hidden; }
|
|
6
|
+
[data-scope=rolling-number][data-part=strip] { position: absolute; left: 0; top: 0; display: flex; flex-direction: column;
|
|
7
|
+
translate: 0 calc(var(--digit) * -1lh); transition: translate var(--duration) cubic-bezier(0.2, 0.7, 0.2, 1);
|
|
8
|
+
& > span { height: 1lh; }
|
|
9
|
+
@media (prefers-reduced-motion: reduce) { transition: none; } }
|
|
10
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A segmented control: a row of options, one picked, with a sliding indicator.
|
|
3
|
+
*
|
|
4
|
+
* <Segmented items={['Day', 'Week', 'Month']} value={view} onValueChange={setView} />
|
|
5
|
+
* <Segmented items={[{ value: 'grid', label: 'Grid', icon: 'lucide:grid-2x2' }]} iconOnly />
|
|
6
|
+
*
|
|
7
|
+
* - `items`: strings, or objects (see `labelKey`, `valueKey`, `disabledKey`; `icon`, `tooltip`)
|
|
8
|
+
* - `value`, `defaultValue`: the item's own value; `onValueChange`: called with it
|
|
9
|
+
* - `label`: a label above it (skipped inside a Field)
|
|
10
|
+
* - `iconOnly`: just the icons (each keeps its label for assistive tech)
|
|
11
|
+
* - `tooltips`: each item's label (or its own `tooltip`) in a tooltip on
|
|
12
|
+
* hover and keyboard focus; on by default with `iconOnly`
|
|
13
|
+
* - `size`: 'sm', 'md' or 'lg'
|
|
14
|
+
* - `renderItem`: an option's content, (item, { selected }) => node (a count
|
|
15
|
+
* beside the label, say); the icon still comes first
|
|
16
|
+
* - `name`: for plain form posts
|
|
17
|
+
*
|
|
18
|
+
* By hand, with Ark's SegmentGroup parts (and Group, the track):
|
|
19
|
+
*
|
|
20
|
+
* <Segmented.Root defaultValue="week" orientation="horizontal">
|
|
21
|
+
* <Segmented.Group>
|
|
22
|
+
* <Segmented.Indicator />
|
|
23
|
+
* {['day', 'week'].map((value) => (
|
|
24
|
+
* <Segmented.Item key={value} value={value}><Segmented.ItemText>{value}</Segmented.ItemText><Segmented.ItemHiddenInput /></Segmented.Item>
|
|
25
|
+
* ))}
|
|
26
|
+
* </Segmented.Group>
|
|
27
|
+
* </Segmented.Root>
|
|
28
|
+
*/
|
|
29
|
+
import { type ReactNode } from 'react';
|
|
30
|
+
import { SegmentGroup as Ark, type SegmentGroupValueChangeDetails } from '@ark-ui/react/segment-group';
|
|
31
|
+
import 'iconify-icon';
|
|
32
|
+
import { type Classes } from '../utils.js';
|
|
33
|
+
import '../theme.css';
|
|
34
|
+
import './segment-group.css';
|
|
35
|
+
type Value<T, K extends string> = T extends string | number ? T : K extends keyof T ? T[K] : unknown;
|
|
36
|
+
export type SegmentedProps<T, K extends string = 'value'> = Omit<Ark.RootProps, 'value' | 'defaultValue' | 'onValueChange' | 'label'> & {
|
|
37
|
+
items: readonly T[];
|
|
38
|
+
value?: NoInfer<Value<T, K>> | null;
|
|
39
|
+
defaultValue?: NoInfer<Value<T, K>> | null;
|
|
40
|
+
onValueChange?: (value: NoInfer<Value<T, K>> | null, details: SegmentGroupValueChangeDetails) => void;
|
|
41
|
+
label?: ReactNode;
|
|
42
|
+
labelKey?: string;
|
|
43
|
+
valueKey?: K;
|
|
44
|
+
disabledKey?: string;
|
|
45
|
+
iconOnly?: boolean;
|
|
46
|
+
tooltips?: boolean | null;
|
|
47
|
+
size?: 'sm' | 'md' | 'lg';
|
|
48
|
+
renderItem?: (item: T, state: {
|
|
49
|
+
selected: boolean;
|
|
50
|
+
}) => ReactNode;
|
|
51
|
+
classes?: Classes<'root' | 'label' | 'group' | 'indicator' | 'item' | 'itemText' | 'itemIcon' | 'tooltipTarget'>;
|
|
52
|
+
};
|
|
53
|
+
declare function SegmentedShortcut<T, K extends string = 'value'>({ items, value, defaultValue, onValueChange, label, labelKey, valueKey, disabledKey, iconOnly, tooltips, size, renderItem, className, classes, ...rest }: SegmentedProps<T, K>): import("react").JSX.Element;
|
|
54
|
+
export declare const Segmented: typeof SegmentedShortcut & {
|
|
55
|
+
Root: import("react").ForwardRefExoticComponent<Ark.RootProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
56
|
+
Label: import("react").ForwardRefExoticComponent<Ark.LabelProps & import("react").RefAttributes<HTMLSpanElement>>;
|
|
57
|
+
Indicator: import("react").ForwardRefExoticComponent<Ark.IndicatorProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
58
|
+
Item: import("react").ForwardRefExoticComponent<Ark.ItemProps & import("react").RefAttributes<HTMLLabelElement>>;
|
|
59
|
+
ItemText: import("react").ForwardRefExoticComponent<Ark.ItemTextProps & import("react").RefAttributes<HTMLSpanElement>>;
|
|
60
|
+
ItemControl: import("react").ForwardRefExoticComponent<Ark.ItemControlProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
61
|
+
ItemHiddenInput: import("react").ForwardRefExoticComponent<Ark.ItemHiddenInputProps & import("react").RefAttributes<HTMLInputElement>>;
|
|
62
|
+
Context: (props: Ark.ContextProps) => ReactNode;
|
|
63
|
+
Group: {
|
|
64
|
+
(props: import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>): import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
|
|
65
|
+
displayName: string;
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
export {};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* A segmented control: a row of options, one picked, with a sliding indicator.
|
|
4
|
+
*
|
|
5
|
+
* <Segmented items={['Day', 'Week', 'Month']} value={view} onValueChange={setView} />
|
|
6
|
+
* <Segmented items={[{ value: 'grid', label: 'Grid', icon: 'lucide:grid-2x2' }]} iconOnly />
|
|
7
|
+
*
|
|
8
|
+
* - `items`: strings, or objects (see `labelKey`, `valueKey`, `disabledKey`; `icon`, `tooltip`)
|
|
9
|
+
* - `value`, `defaultValue`: the item's own value; `onValueChange`: called with it
|
|
10
|
+
* - `label`: a label above it (skipped inside a Field)
|
|
11
|
+
* - `iconOnly`: just the icons (each keeps its label for assistive tech)
|
|
12
|
+
* - `tooltips`: each item's label (or its own `tooltip`) in a tooltip on
|
|
13
|
+
* hover and keyboard focus; on by default with `iconOnly`
|
|
14
|
+
* - `size`: 'sm', 'md' or 'lg'
|
|
15
|
+
* - `renderItem`: an option's content, (item, { selected }) => node (a count
|
|
16
|
+
* beside the label, say); the icon still comes first
|
|
17
|
+
* - `name`: for plain form posts
|
|
18
|
+
*
|
|
19
|
+
* By hand, with Ark's SegmentGroup parts (and Group, the track):
|
|
20
|
+
*
|
|
21
|
+
* <Segmented.Root defaultValue="week" orientation="horizontal">
|
|
22
|
+
* <Segmented.Group>
|
|
23
|
+
* <Segmented.Indicator />
|
|
24
|
+
* {['day', 'week'].map((value) => (
|
|
25
|
+
* <Segmented.Item key={value} value={value}><Segmented.ItemText>{value}</Segmented.ItemText><Segmented.ItemHiddenInput /></Segmented.Item>
|
|
26
|
+
* ))}
|
|
27
|
+
* </Segmented.Group>
|
|
28
|
+
* </Segmented.Root>
|
|
29
|
+
*/
|
|
30
|
+
import { useState } from 'react';
|
|
31
|
+
import { SegmentGroup as Ark } from '@ark-ui/react/segment-group';
|
|
32
|
+
import { useFieldContext } from '@ark-ui/react/field';
|
|
33
|
+
import 'iconify-icon';
|
|
34
|
+
import { Tooltip } from '../tooltip/Tooltip.js';
|
|
35
|
+
import { cx, partOf, partsOf, useControllable, withParts } from '../utils.js';
|
|
36
|
+
import '../theme.css';
|
|
37
|
+
import './segment-group.css';
|
|
38
|
+
function SegmentedShortcut({ items, value, defaultValue, onValueChange, label = null, labelKey = 'label', valueKey = 'value', disabledKey = 'disabled', iconOnly = false, tooltips = null, size = 'md', renderItem, className, classes, ...rest }) {
|
|
39
|
+
const field = useFieldContext();
|
|
40
|
+
const part = partsOf('segment-group', classes);
|
|
41
|
+
const [current, setCurrent] = useControllable(value, defaultValue ?? null, undefined);
|
|
42
|
+
const key = (item, k) => item[k];
|
|
43
|
+
const isObject = (item) => typeof item === 'object' && item !== null;
|
|
44
|
+
const labelOf = (item) => String(isObject(item) ? key(item, labelKey) : item);
|
|
45
|
+
const valueOf = (item) => (isObject(item) ? key(item, valueKey) : item);
|
|
46
|
+
const iconOf = (item) => (isObject(item) ? key(item, 'icon') : undefined);
|
|
47
|
+
const tipFor = (item) => (isObject(item) && key(item, 'tooltip')) || labelOf(item);
|
|
48
|
+
// Zag's values are strings; map them back to the items' own.
|
|
49
|
+
function change(details) {
|
|
50
|
+
const item = items.find((item) => String(valueOf(item)) === details.value);
|
|
51
|
+
const next = item === undefined ? null : valueOf(item);
|
|
52
|
+
setCurrent(next);
|
|
53
|
+
onValueChange?.(next, details);
|
|
54
|
+
}
|
|
55
|
+
const withTooltips = tooltips ?? iconOnly;
|
|
56
|
+
// The focusable element is the hidden radio, so keyboard focus opens its
|
|
57
|
+
// tooltip by hand (pointer focus doesn't).
|
|
58
|
+
const [tipOpen, setTipOpen] = useState(null);
|
|
59
|
+
const focus = (key) => (e) => {
|
|
60
|
+
if (withTooltips && e.target.matches(':focus-visible'))
|
|
61
|
+
setTipOpen(key);
|
|
62
|
+
};
|
|
63
|
+
return (_jsxs(Ark.Root, { ...rest, ...part('root'), className: cx(className, classes?.root), "data-size": size, orientation: "horizontal", value: current == null ? null : String(current), onValueChange: change, "aria-describedby": field?.ariaDescribedby, ...(field && { ids: { label: field.ids.label }, invalid: field.invalid, disabled: field.disabled }), children: [label && !field && _jsx(Ark.Label, { ...part('label'), children: label }), _jsxs("div", { ...part('group'), children: [_jsx(Ark.Indicator, { ...part('indicator') }), items.map((item) => {
|
|
64
|
+
const itemKey = String(valueOf(item));
|
|
65
|
+
return (_jsxs(Ark.Item, { value: itemKey, disabled: isObject(item) && !!key(item, disabledKey), ...part('item'), "data-icon-only": iconOnly || undefined, children: [_jsxs(Ark.ItemText, { ...part('item-text'), children: [iconOf(item) && _jsx("iconify-icon", { ...part('item-icon'), icon: iconOf(item), "aria-hidden": "true" }), iconOnly ? _jsx("span", { ...part('sr-only'), children: labelOf(item) })
|
|
66
|
+
: renderItem ? renderItem(item, { selected: String(current) === itemKey }) : labelOf(item)] }), withTooltips && (_jsx(Tooltip, { content: tipFor(item), open: tipOpen === itemKey, onOpenChange: (open) => setTipOpen((tip) => (open ? itemKey : tip === itemKey ? null : tip)), children: _jsx("span", { ...part('tooltip-target') }) })), _jsx(Ark.ItemHiddenInput, { onFocus: focus(itemKey), onBlur: () => setTipOpen(null) })] }, itemKey));
|
|
67
|
+
})] })] }));
|
|
68
|
+
}
|
|
69
|
+
export const Segmented = withParts(SegmentedShortcut, {
|
|
70
|
+
Root: Ark.Root,
|
|
71
|
+
Label: Ark.Label,
|
|
72
|
+
Indicator: Ark.Indicator,
|
|
73
|
+
Item: Ark.Item,
|
|
74
|
+
ItemText: Ark.ItemText,
|
|
75
|
+
ItemControl: Ark.ItemControl,
|
|
76
|
+
ItemHiddenInput: Ark.ItemHiddenInput,
|
|
77
|
+
Context: Ark.Context,
|
|
78
|
+
Group: partOf('segment-group', 'group'),
|
|
79
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
@layer ui {
|
|
2
|
+
[data-scope=segment-group][data-part=root] { display: inline-block; max-width: 100%; color: var(--ui-text); font-family: var(--ui-font);
|
|
3
|
+
--seg-h: var(--ui-control-height); --seg-px: 0.75rem; --seg-px-icon: 0.5rem; --seg-icon: 15px; --seg-fs: 14px;
|
|
4
|
+
&[data-size=sm] { --seg-h: var(--ui-control-height-sm); --seg-px: 0.625rem; --seg-px-icon: 0.375rem; --seg-icon: 14px; --seg-fs: 12px; }
|
|
5
|
+
&[data-size=lg] { --seg-h: var(--ui-control-height-lg); --seg-px: 1rem; --seg-px-icon: 0.625rem; --seg-icon: 17px; --seg-fs: 16px; } }
|
|
6
|
+
[data-scope=segment-group][data-part=label] { display: block; font-size: 14px; font-weight: 500; margin-bottom: 0.375rem; }
|
|
7
|
+
[data-scope=segment-group][data-part=group] { position: relative; display: inline-flex; max-width: 100%; gap: 0.25rem; padding: 0.25rem;
|
|
8
|
+
box-sizing: border-box; background: var(--ui-hover); border-radius: var(--ui-radius);
|
|
9
|
+
&[data-disabled] { opacity: 0.5; }
|
|
10
|
+
&[data-invalid], &[aria-invalid=true] { outline: 1px solid var(--ui-danger); } }
|
|
11
|
+
[data-scope=segment-group][data-part=indicator] { position: absolute; left: var(--left); top: var(--top); width: var(--width); height: var(--height);
|
|
12
|
+
background: var(--ui-surface); border-radius: calc(var(--ui-radius) - 2px); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
|
|
13
|
+
transition-property: var(--transition-property); transition-duration: 150ms; }
|
|
14
|
+
[data-scope=segment-group][data-part=item] { position: relative; z-index: 1; display: flex; align-items: center; justify-content: center;
|
|
15
|
+
flex: 1 1 auto; padding: 0 var(--seg-px); height: calc(var(--seg-h) - 8px); border-radius: calc(var(--ui-radius) - 2px);
|
|
16
|
+
font-size: var(--seg-fs); font-weight: 500; color: var(--ui-muted); white-space: nowrap; cursor: pointer;
|
|
17
|
+
&[data-state=checked] { color: var(--ui-text); }
|
|
18
|
+
&[data-focus-visible] { outline: 2px solid var(--ui-ring-soft); }
|
|
19
|
+
&[data-disabled] { opacity: 0.4; cursor: not-allowed; }
|
|
20
|
+
&[data-icon-only] { padding: 0 var(--seg-px-icon); } }
|
|
21
|
+
[data-scope=segment-group][data-part=tooltip-target] { position: absolute; inset: 0; }
|
|
22
|
+
[data-scope=segment-group][data-part=item-text] { display: inline-flex; align-items: center; gap: 0.375rem; }
|
|
23
|
+
[data-scope=segment-group][data-part=item-icon] { display: block; font-size: var(--seg-icon); }
|
|
24
|
+
[data-scope=segment-group][data-part=sr-only] { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
|
|
25
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import 'iconify-icon';
|
|
2
|
+
import type { InnerProps } from './Select.js';
|
|
3
|
+
import '../theme.css';
|
|
4
|
+
import './select.css';
|
|
5
|
+
export declare function ListSelect<T>({ label, items, placeholder: placeholderProp, labelKey, valueKey, disabledKey, multiple, tags, variant, hideSelected, deselectable, closeOnSelect, clearable, emptyText: emptyTextProp, empty, name, size, placement, disabled, value, defaultValue, onValueChange, renderItem, renderValue, start, searchable: _searchable, load: _load, debounce: _debounce, loadingText: _loadingText, onCreate: _onCreate, renderCreate: _renderCreate, className, classes, ...rest }: InnerProps<T>): import("react").JSX.Element;
|