@inkra/react 0.2.0 → 0.4.3
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 +101 -7
- package/dist/Button.js +3 -2
- package/dist/PencilIcon.d.ts +10 -0
- package/dist/PencilIcon.js +83 -39
- package/dist/PencilLine.js +9 -1
- package/dist/PencilText.js +15 -9
- package/dist/accordion/Accordion.js +65 -5
- package/dist/alert-dialog/index.d.ts +1 -1
- package/dist/alert-dialog/index.js +18 -6
- package/dist/audio-player/index.d.ts +2 -0
- package/dist/audio-player/index.js +126 -31
- package/dist/calendar/index.d.ts +4 -1
- package/dist/calendar/index.js +31 -12
- package/dist/carousel/index.js +13 -6
- package/dist/choice-grid/index.d.ts +6 -1
- package/dist/choice-grid/index.js +61 -33
- package/dist/color-picker/GradientEditor.d.ts +39 -0
- package/dist/color-picker/GradientEditor.js +63 -0
- package/dist/color-picker/gradient.d.ts +25 -0
- package/dist/color-picker/gradient.js +60 -0
- package/dist/color-picker/index.d.ts +27 -7
- package/dist/color-picker/index.js +134 -32
- package/dist/color-swatch/index.d.ts +10 -0
- package/dist/color-swatch/index.js +9 -0
- package/dist/command/index.d.ts +24 -2
- package/dist/command/index.js +29 -6
- package/dist/content/index.d.ts +5 -0
- package/dist/content/index.js +12 -3
- package/dist/context-menu/index.d.ts +2 -2
- package/dist/context-menu/index.js +4 -3
- package/dist/data-table/index.d.ts +7 -1
- package/dist/data-table/index.js +64 -16
- package/dist/data-table/model.d.ts +2 -0
- package/dist/data-table/model.js +7 -0
- package/dist/date-picker/index.d.ts +2 -0
- package/dist/date-picker/index.js +10 -9
- package/dist/day-agenda/index.d.ts +6 -1
- package/dist/day-agenda/index.js +46 -21
- package/dist/day-agenda/layout.js +65 -9
- package/dist/display/ProgressRing.js +7 -3
- package/dist/display/index.d.ts +4 -1
- package/dist/display/index.js +3 -2
- package/dist/dockable-panel/index.js +5 -5
- package/dist/drawer/index.d.ts +1 -1
- package/dist/drawer/index.js +9 -6
- package/dist/dropdown-menu/index.d.ts +1 -1
- package/dist/dropdown-menu/index.js +3 -2
- package/dist/editable/index.d.ts +24 -2
- package/dist/editable/index.js +142 -37
- package/dist/empty/illustrations.d.ts +8 -0
- package/dist/empty/illustrations.js +78 -0
- package/dist/empty/index.d.ts +6 -1
- package/dist/empty/index.js +11 -3
- package/dist/flip/index.js +28 -8
- package/dist/form/EditablePencilText.js +34 -0
- package/dist/form/Field.js +11 -10
- package/dist/form/Input.js +1 -1
- package/dist/form/Textarea.d.ts +3 -1
- package/dist/form/Textarea.js +6 -4
- package/dist/hotkey/index.d.ts +1 -0
- package/dist/hotkey/index.js +6 -4
- package/dist/hover-card/index.js +1 -1
- package/dist/image-cropper/index.js +4 -2
- package/dist/image-viewer/VideoResource.d.ts +21 -0
- package/dist/image-viewer/VideoResource.js +194 -0
- package/dist/image-viewer/index.d.ts +14 -0
- package/dist/image-viewer/index.js +118 -65
- package/dist/image-viewer/lucideGeometry.d.ts +3 -0
- package/dist/image-viewer/lucideGeometry.js +19 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/input-number/index.d.ts +6 -0
- package/dist/input-number/index.js +131 -15
- package/dist/input-number/model.d.ts +13 -0
- package/dist/input-number/model.js +50 -0
- package/dist/internal/cache.d.ts +4 -0
- package/dist/internal/cache.js +18 -1
- package/dist/internal/iconGeometry.d.ts +2 -0
- package/dist/internal/iconGeometry.js +32 -0
- package/dist/internal/renderTokens.d.ts +4 -0
- package/dist/internal/renderTokens.js +4 -0
- package/dist/internal/stroke.d.ts +7 -0
- package/dist/internal/stroke.js +63 -7
- package/dist/internal/surfaceBudget.d.ts +20 -0
- package/dist/internal/surfaceBudget.js +69 -0
- package/dist/internal/surfaceRenderer.js +87 -28
- package/dist/internal/surfaceVisibility.d.ts +3 -0
- package/dist/internal/surfaceVisibility.js +60 -0
- package/dist/internal/textRenderer.d.ts +2 -0
- package/dist/internal/textRenderer.js +3 -3
- package/dist/internal/usePencilSurface.js +17 -1
- package/dist/internal/wash.js +4 -3
- package/dist/journey-map/terrain.js +10 -5
- package/dist/layout/index.js +1 -1
- package/dist/log/index.js +29 -22
- package/dist/markdown/CodeBlock.d.ts +19 -0
- package/dist/markdown/CodeBlock.js +86 -0
- package/dist/markdown/Deletion.d.ts +2 -0
- package/dist/markdown/Deletion.js +69 -0
- package/dist/markdown/icons.d.ts +14 -0
- package/dist/markdown/icons.js +16 -0
- package/dist/markdown/index.d.ts +17 -0
- package/dist/markdown/index.js +175 -0
- package/dist/node-graph/index.d.ts +2 -0
- package/dist/node-graph/index.js +49 -25
- package/dist/number-ticker/index.d.ts +11 -9
- package/dist/number-ticker/index.js +14 -11
- package/dist/number-ticker/model.d.ts +38 -5
- package/dist/number-ticker/model.js +139 -10
- package/dist/number-ticker/precision.d.ts +7 -0
- package/dist/number-ticker/precision.js +141 -0
- package/dist/overflow-list/index.js +5 -2
- package/dist/overlay/Overlay.d.ts +1 -1
- package/dist/overlay/Overlay.js +5 -3
- package/dist/overlay/Popover.d.ts +1 -1
- package/dist/overlay/Popover.js +6 -5
- package/dist/overlay/themeBridge.d.ts +12 -1
- package/dist/overlay/themeBridge.js +41 -3
- package/dist/pagination/index.d.ts +10 -1
- package/dist/pagination/index.js +6 -4
- package/dist/pan-zoom/index.d.ts +2 -0
- package/dist/pan-zoom/index.js +7 -6
- package/dist/pencil.d.ts +1 -0
- package/dist/pencil.js +4 -4
- package/dist/radar/index.d.ts +4 -0
- package/dist/radar/index.js +41 -21
- package/dist/rating/index.d.ts +4 -0
- package/dist/rating/index.js +128 -24
- package/dist/resizable/index.js +1 -1
- package/dist/scroll-area/index.d.ts +1 -1
- package/dist/scroll-area/index.js +5 -2
- package/dist/select/MultipleSelect.js +5 -4
- package/dist/select/SearchableSelect.js +61 -6
- package/dist/select/Select.d.ts +1 -0
- package/dist/select/Select.js +3 -1
- package/dist/select/StepperSelect.d.ts +29 -0
- package/dist/select/StepperSelect.js +119 -0
- package/dist/select/index.d.ts +2 -0
- package/dist/select/index.js +1 -0
- package/dist/select/stepperModel.d.ts +7 -0
- package/dist/select/stepperModel.js +29 -0
- package/dist/selection/Checkbox.js +4 -3
- package/dist/selection/RadioGroup.js +29 -13
- package/dist/selection/Switch.js +4 -3
- package/dist/selection/useNativeChecked.d.ts +2 -1
- package/dist/selection/useNativeChecked.js +26 -8
- package/dist/slider/index.js +50 -6
- package/dist/sortable/index.js +15 -6
- package/dist/sparkline/index.js +17 -2
- package/dist/sparkline/model.js +11 -4
- package/dist/styles/accordion.css +228 -0
- package/dist/styles/alert-dialog.css +139 -0
- package/dist/styles/audio-player.css +22 -0
- package/dist/styles/base.css +638 -0
- package/dist/styles/breadcrumb.css +7 -0
- package/dist/styles/calendar.css +62 -0
- package/dist/styles/carousel.css +173 -0
- package/dist/styles/choice-grid.css +226 -0
- package/dist/styles/color-picker.css +281 -0
- package/dist/styles/color-swatch.css +47 -0
- package/dist/styles/command.css +52 -0
- package/dist/styles/content.css +79 -0
- package/dist/styles/context-menu.css +35 -0
- package/dist/styles/data-table.css +31 -0
- package/dist/styles/date-picker.css +107 -0
- package/dist/styles/day-agenda.css +30 -0
- package/dist/styles/display.css +304 -0
- package/dist/styles/dockable-panel.css +110 -0
- package/dist/styles/drawer.css +133 -0
- package/dist/styles/dropdown-menu.css +68 -0
- package/dist/styles/editable.css +145 -0
- package/dist/styles/empty.css +108 -0
- package/dist/styles/flip.css +101 -0
- package/dist/styles/form.css +634 -0
- package/dist/styles/heatmap.css +256 -0
- package/dist/styles/hotkey.css +189 -0
- package/dist/styles/hover-card.css +27 -0
- package/dist/styles/image-cropper.css +205 -0
- package/dist/styles/image-viewer.css +422 -0
- package/dist/styles/input-number.css +21 -0
- package/dist/styles/journey-map.css +19 -0
- package/dist/styles/keypad.css +92 -0
- package/dist/styles/layout.css +72 -0
- package/dist/styles/leaflet.css +661 -0
- package/dist/styles/log.css +203 -0
- package/dist/styles/markdown.css +379 -0
- package/dist/styles/node-graph.css +142 -0
- package/dist/styles/number-ticker.css +83 -0
- package/dist/styles/overflow-list.css +51 -0
- package/dist/styles/overlay.css +284 -0
- package/dist/styles/pagination.css +21 -0
- package/dist/styles/pan-zoom.css +136 -0
- package/dist/styles/radar.css +1 -0
- package/dist/styles/rating.css +121 -0
- package/dist/styles/resizable.css +108 -0
- package/dist/styles/scroll-area.css +122 -0
- package/dist/styles/select.css +557 -0
- package/dist/styles/selection.css +264 -0
- package/dist/styles/slider.css +145 -0
- package/dist/styles/slot.css +109 -0
- package/dist/styles/sortable.css +159 -0
- package/dist/styles/sparkline.css +24 -0
- package/dist/styles/steps.css +27 -0
- package/dist/styles/tabs.css +79 -0
- package/dist/styles/tag-input.css +213 -0
- package/dist/styles/time-picker.css +66 -0
- package/dist/styles/timeline.css +24 -0
- package/dist/styles/toast.css +160 -0
- package/dist/styles/toggle-group.css +107 -0
- package/dist/styles/tooltip.css +78 -0
- package/dist/styles/transfer.css +148 -0
- package/dist/styles/tree-select.css +131 -0
- package/dist/styles/tree.css +59 -0
- package/dist/styles/typewriter.css +62 -0
- package/dist/styles/upload.css +60 -0
- package/dist/styles/virtual-list.css +112 -0
- package/dist/styles.css +2241 -443
- package/dist/tabs/index.d.ts +9 -0
- package/dist/tabs/index.js +66 -16
- package/dist/tabs/useOverflow.d.ts +20 -0
- package/dist/tabs/useOverflow.js +145 -0
- package/dist/tag-input/index.d.ts +7 -2
- package/dist/tag-input/index.js +70 -26
- package/dist/time-picker/index.js +38 -20
- package/dist/toast/index.d.ts +19 -11
- package/dist/toast/index.js +48 -8
- package/dist/toast/manager.d.ts +42 -0
- package/dist/toast/manager.js +85 -0
- package/dist/toast/useToastNavigation.d.ts +3 -0
- package/dist/toast/useToastNavigation.js +75 -0
- package/dist/tokens.css +4 -0
- package/dist/tooltip/Tooltip.d.ts +1 -1
- package/dist/tooltip/Tooltip.js +6 -5
- package/dist/transfer/index.d.ts +5 -0
- package/dist/transfer/index.js +93 -29
- package/dist/transfer/model.d.ts +4 -0
- package/dist/transfer/model.js +37 -11
- package/dist/tree/index.d.ts +5 -1
- package/dist/tree/index.js +5 -5
- package/dist/tree-select/index.js +67 -30
- package/dist/tree-select/model.d.ts +11 -5
- package/dist/tree-select/model.js +111 -83
- package/dist/typewriter/index.js +10 -10
- package/dist/upload/index.js +30 -16
- package/dist/virtual-list/index.js +10 -7
- package/package.json +416 -5
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { forwardRef, useEffect, useId, useImperativeHandle, useRef, useState } from 'react';
|
|
3
|
-
import { Button } from '../Button.js';
|
|
2
|
+
import { forwardRef, memo, useCallback, useEffect, useId, useImperativeHandle, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { Button, ButtonGroup } from '../Button.js';
|
|
4
4
|
import { useResolvedControlProps } from '../form/controlProps.js';
|
|
5
5
|
import { mergeAriaIds, useFieldContext } from '../form/context.js';
|
|
6
6
|
import { useBrowserLayoutEffect } from '../internal/useBrowserLayoutEffect.js';
|
|
@@ -19,15 +19,19 @@ const pad = (value) => {
|
|
|
19
19
|
return `${whole.padStart(2, '0')}${fraction ? `.${fraction}` : ''}`;
|
|
20
20
|
};
|
|
21
21
|
const times = (length, start = 0) => Array.from({ length }, (_, index) => index + start);
|
|
22
|
+
const hourOptions24 = times(24);
|
|
23
|
+
const hourOptions12 = times(12, 1);
|
|
24
|
+
const minuteSecondOptions = times(60);
|
|
25
|
+
const periodOptions = [0, 1];
|
|
22
26
|
function partsOf(value) {
|
|
23
27
|
if (!/^(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d{1,3})?)?$/.test(value))
|
|
24
28
|
return undefined;
|
|
25
29
|
const [hour, minute, second = 0] = value.split(':').map(Number);
|
|
26
30
|
return [hour, minute, second];
|
|
27
31
|
}
|
|
28
|
-
function TimeColumn({ name, label, value, options, onChange, format = pad }) {
|
|
32
|
+
const TimeColumn = memo(function TimeColumn({ name, label, value, options, onChange, format = pad }) {
|
|
29
33
|
return _jsx(RadioGroup, { name: name, label: label, value: String(value), variant: "segmented", className: "inkra-time-picker__column", onValueChange: next => onChange(Number(next)), children: options.map(option => _jsx(RadioGroupItem, { value: String(option), label: format(option) }, option)) });
|
|
30
|
-
}
|
|
34
|
+
});
|
|
31
35
|
export const TimePicker = forwardRef(function TimePicker(allProps, forwardedRef) {
|
|
32
36
|
const { value, defaultValue, onValueChange, onChange, onClear, clearable = true, clearButtonLabel, hourCycle = 'h24', locale, lang, showSeconds = false, step, min, max, name, form, id, disabled, readOnly, required, invalid, 'aria-describedby': ariaDescribedBy, 'aria-errormessage': ariaErrorMessage, 'aria-invalid': ariaInvalid, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, className, style, size = 'md', fullWidth = true, startIcon, endIcon, endAction, startAction, prefix, suffix, placeholder, seed, variant = 'outline', textAlign = 'start', loading, classNames, ...nativeProps } = allProps;
|
|
33
37
|
const generatedId = useId();
|
|
@@ -40,6 +44,8 @@ export const TimePicker = forwardRef(function TimePicker(allProps, forwardedRef)
|
|
|
40
44
|
const [internalValue, setInternalValue] = useState(() => String(defaultValue ?? ''));
|
|
41
45
|
const [open, setOpen] = useState(false);
|
|
42
46
|
const [draft, setDraft] = useState('00:00');
|
|
47
|
+
const draftRef = useRef(draft);
|
|
48
|
+
draftRef.current = draft;
|
|
43
49
|
const [draftValid, setDraftValid] = useState(true);
|
|
44
50
|
const [constraintInvalid, setConstraintInvalid] = useState(false);
|
|
45
51
|
useImperativeHandle(forwardedRef, () => inputRef.current);
|
|
@@ -59,16 +65,20 @@ export const TimePicker = forwardRef(function TimePicker(allProps, forwardedRef)
|
|
|
59
65
|
: { title: 'Choose time', hour: 'Hour', minute: 'Minute', second: 'Second', period: 'Period', am: 'AM', pm: 'PM',
|
|
60
66
|
done: 'Apply', cancel: 'Cancel', clear: 'Clear time', invalid: 'Choose a time within the allowed range and step.' };
|
|
61
67
|
const currentParts = partsOf(currentValue);
|
|
68
|
+
const hasCurrentParts = currentParts !== undefined;
|
|
69
|
+
const formatter = useMemo(() => hasCurrentParts ? new Intl.DateTimeFormat(resolvedLocale, {
|
|
70
|
+
hour: '2-digit', minute: '2-digit', ...(secondsVisible ? { second: '2-digit' } : {}),
|
|
71
|
+
hourCycle: hourCycle === 'h12' ? 'h12' : 'h23',
|
|
72
|
+
}) : null, [resolvedLocale, secondsVisible, hourCycle, hasCurrentParts]);
|
|
62
73
|
const displayValue = currentParts
|
|
63
|
-
? new
|
|
64
|
-
hour: '2-digit', minute: '2-digit', ...(secondsVisible ? { second: '2-digit' } : {}),
|
|
65
|
-
hourCycle: hourCycle === 'h12' ? 'h12' : 'h23',
|
|
66
|
-
}).format(new Date(2000, 0, 1, ...currentParts))
|
|
74
|
+
? formatter.format(new Date(2000, 0, 1, ...currentParts))
|
|
67
75
|
: placeholder ?? copy.title;
|
|
68
76
|
const canClear = clearable && Boolean(currentValue) && !state.disabled && !state.readOnly;
|
|
77
|
+
const buttonVariant = variant === 'soft' ? 'secondary' : variant === 'underline' ? 'outline' : variant;
|
|
78
|
+
const clearSize = size === 'sm' ? 'icon-sm' : size === 'lg' ? 'icon-lg' : 'icon';
|
|
69
79
|
const valueId = field ? `${state.id}-value` : undefined;
|
|
70
80
|
const [hour, minute, second] = partsOf(draft) ?? [0, 0, 0];
|
|
71
|
-
const validDraft = (candidate =
|
|
81
|
+
const validDraft = useCallback((candidate = draftRef.current) => {
|
|
72
82
|
// Native validation owns overnight ranges, fractional steps and step bases.
|
|
73
83
|
const input = document.createElement('input');
|
|
74
84
|
input.type = 'time';
|
|
@@ -81,7 +91,7 @@ export const TimePicker = forwardRef(function TimePicker(allProps, forwardedRef)
|
|
|
81
91
|
input.required = true;
|
|
82
92
|
input.value = candidate;
|
|
83
93
|
return input.validity.valid;
|
|
84
|
-
};
|
|
94
|
+
}, [min, max, resolvedStep, stepBase]);
|
|
85
95
|
const updateOpen = (next) => {
|
|
86
96
|
if (next && (state.disabled || state.readOnly))
|
|
87
97
|
return;
|
|
@@ -93,13 +103,21 @@ export const TimePicker = forwardRef(function TimePicker(allProps, forwardedRef)
|
|
|
93
103
|
}
|
|
94
104
|
setOpen(next);
|
|
95
105
|
};
|
|
96
|
-
const updatePart = (part, next) => {
|
|
97
|
-
const values = [
|
|
106
|
+
const updatePart = useCallback((part, next) => {
|
|
107
|
+
const values = partsOf(draftRef.current) ?? [0, 0, 0];
|
|
98
108
|
values[part] = next;
|
|
99
109
|
const nextDraft = values.slice(0, secondsVisible ? 3 : 2).map(pad).join(':');
|
|
110
|
+
draftRef.current = nextDraft;
|
|
100
111
|
setDraft(nextDraft);
|
|
101
112
|
setDraftValid(validDraft(nextDraft));
|
|
102
|
-
};
|
|
113
|
+
}, [secondsVisible, validDraft]);
|
|
114
|
+
const period = hour >= 12 ? 1 : 0;
|
|
115
|
+
const hourWithinPeriod = hour % 12;
|
|
116
|
+
const changeHour = useCallback((next) => updatePart(0, hourCycle === 'h12' ? next % 12 + period * 12 : next), [hourCycle, period, updatePart]);
|
|
117
|
+
const changeMinute = useCallback((next) => updatePart(1, next), [updatePart]);
|
|
118
|
+
const changeSecond = useCallback((next) => updatePart(2, next), [updatePart]);
|
|
119
|
+
const changePeriod = useCallback((next) => updatePart(0, hourWithinPeriod + next * 12), [hourWithinPeriod, updatePart]);
|
|
120
|
+
const formatPeriod = useCallback((next) => next ? copy.pm : copy.am, [copy.pm, copy.am]);
|
|
103
121
|
const commit = (next, reason) => {
|
|
104
122
|
if (!controlled)
|
|
105
123
|
setInternalValue(next);
|
|
@@ -107,7 +125,7 @@ export const TimePicker = forwardRef(function TimePicker(allProps, forwardedRef)
|
|
|
107
125
|
onValueChange?.(next, { reason });
|
|
108
126
|
setOpen(false);
|
|
109
127
|
};
|
|
110
|
-
useEffect(() => { setDraftValid(validDraft()); }, [draft,
|
|
128
|
+
useEffect(() => { setDraftValid(validDraft()); }, [draft, validDraft]);
|
|
111
129
|
useBrowserLayoutEffect(() => {
|
|
112
130
|
// React's controlled value attribute must not move the native step base on each edit.
|
|
113
131
|
if (inputRef.current)
|
|
@@ -134,11 +152,11 @@ export const TimePicker = forwardRef(function TimePicker(allProps, forwardedRef)
|
|
|
134
152
|
}, [controlled, currentValue, defaultValue, form]);
|
|
135
153
|
useEffect(() => { if (state.disabled || state.readOnly)
|
|
136
154
|
setOpen(false); }, [state.disabled, state.readOnly]);
|
|
137
|
-
return _jsxs(Popover.Root, { open: open, onOpenChange: updateOpen, children: [_jsxs("div", { className: ['inkra-time-picker', classNames?.root, className].filter(Boolean).join(' '), style: style, dir: nativeProps.dir, "data-full-width": fullWidth, "data-invalid": state.invalid || constraintInvalid || undefined, "data-readonly": state.readOnly || undefined, "data-disabled": state.disabled || undefined, children: [startAction,
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
155
|
+
return _jsxs(Popover.Root, { open: open, onOpenChange: updateOpen, children: [_jsxs("div", { className: ['inkra-time-picker', classNames?.root, className].filter(Boolean).join(' '), style: style, dir: nativeProps.dir, "data-full-width": fullWidth, "data-invalid": state.invalid || constraintInvalid || undefined, "data-readonly": state.readOnly || undefined, "data-disabled": state.disabled || undefined, "data-size": size, children: [startAction, _jsxs(ButtonGroup, { "aria-label": copy.title, className: ['inkra-time-picker__control', classNames?.control].filter(Boolean).join(' '), "data-size": size, fullWidth: fullWidth, size: size, variant: buttonVariant, children: [_jsx(Popover.Trigger, { asChild: true, children: _jsxs(Button, { className: "inkra-time-picker__trigger", id: state.id, ref: triggerRef, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": mergeAriaIds(state.describedBy, valueId), "aria-invalid": constraintInvalid || state.ariaInvalid, "aria-errormessage": state.errorMessage, "aria-required": state.required || undefined, "aria-readonly": state.readOnly || undefined, disabled: state.disabled, fullWidth: fullWidth, align: textAlign, seed: seed, loading: loading, autoFocus: nativeProps.autoFocus, startIcon: startIcon ?? _jsx(PencilIcon, { paths: clockPaths, sourceSize: 24, size: 22 }), endIcon: endIcon, children: [prefix, _jsx(PencilText, { children: displayValue }), suffix] }) }), clearable && _jsx(Button, { className: "inkra-time-picker__clear", "aria-label": clearButtonLabel ?? copy.clear, title: clearButtonLabel ?? copy.clear, "aria-hidden": !canClear || undefined, disabled: !canClear, tabIndex: canClear ? 0 : -1, variant: "ghost", size: clearSize, onPointerDown: event => event.preventDefault(), onClick: event => {
|
|
156
|
+
commit('', 'clear');
|
|
157
|
+
onClear?.(event);
|
|
158
|
+
triggerRef.current?.focus({ preventScroll: true });
|
|
159
|
+
}, children: _jsx(PencilIcon, { paths: clearPaths, sourceSize: 24, size: 22 }) })] }), valueId && _jsx("span", { className: "inkra-time-picker__accessible-value", id: valueId, children: displayValue }), endAction, _jsx("input", { ...nativeProps, ref: inputRef, type: "time", className: "inkra-time-picker__native", id: `${state.id}-native`, "aria-hidden": "true", tabIndex: -1, name: name, form: form, value: currentValue, min: min, max: max, step: resolvedStep, lang: lang ?? resolvedLocale, "data-hour-cycle": hourCycle, disabled: state.disabled, readOnly: state.readOnly, required: state.required, onChange: event => {
|
|
142
160
|
if (!controlled)
|
|
143
161
|
setInternalValue(event.currentTarget.value);
|
|
144
162
|
onChange?.(event);
|
|
@@ -161,7 +179,7 @@ export const TimePicker = forwardRef(function TimePicker(allProps, forwardedRef)
|
|
|
161
179
|
(content?.querySelector('input:checked')
|
|
162
180
|
?? content?.querySelector('input'))?.focus({ preventScroll: true });
|
|
163
181
|
});
|
|
164
|
-
}, children: [_jsx(Popover.Title, { children: copy.title }), _jsx(PencilLine, {}), _jsxs("div", { className: "inkra-time-picker__columns", children: [_jsx(TimeColumn, { name: `${generatedId}-hour`, label: copy.hour, value: hourCycle === 'h12' ? hour % 12 || 12 : hour, options: hourCycle === 'h12' ?
|
|
182
|
+
}, children: [_jsx(Popover.Title, { children: copy.title }), _jsx(PencilLine, {}), _jsxs("div", { className: "inkra-time-picker__columns", children: [_jsx(TimeColumn, { name: `${generatedId}-hour`, label: copy.hour, value: hourCycle === 'h12' ? hour % 12 || 12 : hour, options: hourCycle === 'h12' ? hourOptions12 : hourOptions24, onChange: changeHour }), _jsx(TimeColumn, { name: `${generatedId}-minute`, label: copy.minute, value: minute, options: minuteSecondOptions, onChange: changeMinute }), secondsVisible && _jsx(TimeColumn, { name: `${generatedId}-second`, label: copy.second, value: Math.floor(second), options: minuteSecondOptions, onChange: changeSecond }), hourCycle === 'h12' && _jsx(TimeColumn, { name: `${generatedId}-period`, label: copy.period, value: period, options: periodOptions, format: formatPeriod, onChange: changePeriod })] }), !draftValid && _jsx(PencilText, { as: "p", className: "inkra-time-picker__error", role: "status", children: copy.invalid }), _jsx(PencilLine, {}), _jsxs("div", { className: "inkra-time-picker__footer", children: [_jsx(Popover.Close, { asChild: true, children: _jsx(Button, { variant: "outline", children: copy.cancel }) }), _jsx(Button, { disabled: !draftValid || state.disabled || state.readOnly, onClick: () => {
|
|
165
183
|
if (validDraft())
|
|
166
184
|
commit(draft, 'input');
|
|
167
185
|
}, children: copy.done })] })] }) })] });
|
package/dist/toast/index.d.ts
CHANGED
|
@@ -1,27 +1,35 @@
|
|
|
1
|
-
import { Toast as BaseToast } from '@base-ui/react/toast';
|
|
2
|
-
import type { ToastManager as BaseToastManager } from '@base-ui/react/toast';
|
|
3
1
|
import type { ReactNode } from 'react';
|
|
4
|
-
|
|
2
|
+
import { createToastManager } from './manager.js';
|
|
3
|
+
import type { ToastManager, ToastObject, ToastPlacement, ToastVariant } from './manager.js';
|
|
4
|
+
export { createToastManager } from './manager.js';
|
|
5
|
+
export type { ToastManager, ToastObject, ToastPlacement, ToastVariant, ToastAddOptions, ToastUpdateOptions, ToastPromiseOptions } from './manager.js';
|
|
5
6
|
export type ToastTone = 'neutral' | 'success' | 'warning' | 'danger' | 'info';
|
|
6
7
|
export type ToastSwipeDirection = 'up' | 'down' | 'left' | 'right';
|
|
7
|
-
export type ToastManager<Data extends object = any> = BaseToastManager<Data>;
|
|
8
8
|
export interface ToastProviderProps {
|
|
9
9
|
children?: ReactNode;
|
|
10
10
|
timeout?: number;
|
|
11
11
|
limit?: number;
|
|
12
12
|
toastManager?: ToastManager;
|
|
13
13
|
placement?: ToastPlacement;
|
|
14
|
+
variant?: ToastVariant;
|
|
15
|
+
/** Per-position overrides; limit otherwise applies independently to every queue. */
|
|
16
|
+
limits?: Partial<Record<ToastPlacement, number>>;
|
|
14
17
|
swipeDirection?: ToastSwipeDirection | ToastSwipeDirection[];
|
|
15
18
|
closeLabel?: string;
|
|
16
|
-
viewportLabel?: string;
|
|
19
|
+
viewportLabel?: string | ((placement: ToastPlacement) => string);
|
|
17
20
|
viewportClassName?: string;
|
|
18
21
|
}
|
|
19
|
-
declare function Provider({ children, closeLabel, limit, placement, swipeDirection, timeout, toastManager, viewportClassName, viewportLabel, }: ToastProviderProps): import("react").JSX.Element;
|
|
20
|
-
export declare
|
|
21
|
-
|
|
22
|
+
declare function Provider({ children, closeLabel, limit, limits, placement, variant, swipeDirection, timeout, toastManager, viewportClassName, viewportLabel, }: ToastProviderProps): import("react").JSX.Element;
|
|
23
|
+
export declare function useToastManager<Data extends object = any>(): {
|
|
24
|
+
toasts: ToastObject<Data>[];
|
|
25
|
+
add: <T extends Data = Data>(options: import("./manager.js").ToastAddOptions<T>) => string;
|
|
26
|
+
update: <T extends Data = Data>(id: string, options: import("./manager.js").ToastUpdateOptions<T> | ((previous: ToastObject<T>) => import("./manager.js").ToastUpdateOptions<T>)) => void;
|
|
27
|
+
close: (id?: string) => void;
|
|
28
|
+
closeAll: (placement?: ToastPlacement) => void;
|
|
29
|
+
promise: <Value, T extends Data = Data>(value: Promise<Value>, options: import("./manager.js").ToastPromiseOptions<Value, T>) => Promise<Value>;
|
|
30
|
+
};
|
|
22
31
|
export declare const Toast: {
|
|
23
32
|
Provider: typeof Provider;
|
|
24
|
-
useToastManager: typeof
|
|
25
|
-
createToastManager: typeof
|
|
33
|
+
useToastManager: typeof useToastManager;
|
|
34
|
+
createToastManager: typeof createToastManager;
|
|
26
35
|
};
|
|
27
|
-
export {};
|
package/dist/toast/index.js
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Toast as BaseToast } from '@base-ui/react/toast';
|
|
3
|
+
import { createContext, useContext, useEffect, useMemo, useState, useSyncExternalStore } from 'react';
|
|
3
4
|
import { Button } from '../Button.js';
|
|
4
5
|
import { PencilIcon } from '../PencilIcon.js';
|
|
5
6
|
import { PencilSurface } from '../PencilSurface.js';
|
|
6
7
|
import { PencilText } from '../PencilText.js';
|
|
8
|
+
import { useBrowserLayoutEffect } from '../internal/useBrowserLayoutEffect.js';
|
|
7
9
|
import { PortalThemeBridge, PortalThemeProvider, usePortalTriggerRef, } from '../overlay/themeBridge.js';
|
|
10
|
+
import { createToastManager, getToastRouter, toastPlacements } from './manager.js';
|
|
11
|
+
import { useToastNavigation } from './useToastNavigation.js';
|
|
12
|
+
export { createToastManager } from './manager.js';
|
|
13
|
+
const ManagerContext = createContext(null);
|
|
14
|
+
const emptyToasts = [];
|
|
15
|
+
const getServerSnapshot = () => emptyToasts;
|
|
8
16
|
const closePaths = [
|
|
9
17
|
{ d: 'M6 6 18 18' },
|
|
10
18
|
{ d: 'M18 6 6 18' },
|
|
@@ -13,6 +21,8 @@ function text(content) {
|
|
|
13
21
|
return typeof content === 'string' ? _jsx(PencilText, { children: content }) : content;
|
|
14
22
|
}
|
|
15
23
|
function toneOf(type) {
|
|
24
|
+
if (type === 'error')
|
|
25
|
+
return 'danger';
|
|
16
26
|
return type === 'success' || type === 'warning' || type === 'danger' || type === 'info'
|
|
17
27
|
? type
|
|
18
28
|
: 'neutral';
|
|
@@ -21,16 +31,46 @@ function ToastThemeAnchor() {
|
|
|
21
31
|
const ref = usePortalTriggerRef(null);
|
|
22
32
|
return _jsx("span", { "aria-hidden": "true", className: "inkra-toast__theme-anchor", ref: ref });
|
|
23
33
|
}
|
|
24
|
-
function
|
|
25
|
-
const
|
|
26
|
-
|
|
34
|
+
function ToastDefaults({ manager, placement, variant }) {
|
|
35
|
+
const router = getToastRouter(manager);
|
|
36
|
+
useBrowserLayoutEffect(() => { router.defaults = { placement, variant }; }, [router, placement, variant]);
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
function ToastQueue({ closeLabel, placement, swipeDirection, viewportClassName, viewportLabel, manager, }) {
|
|
40
|
+
const controller = BaseToast.useToastManager();
|
|
41
|
+
const { toasts } = controller;
|
|
42
|
+
const router = getToastRouter(manager);
|
|
43
|
+
useBrowserLayoutEffect(() => { router.controllers.set(placement, controller); }, [router, placement, controller]);
|
|
44
|
+
useBrowserLayoutEffect(() => () => { router.controllers.delete(placement); }, [router, placement]);
|
|
45
|
+
useBrowserLayoutEffect(() => { router.report(placement, toasts); }, [router, placement, toasts]);
|
|
46
|
+
useEffect(() => () => { router.report(placement, emptyToasts); }, [router, placement]);
|
|
47
|
+
const directions = swipeDirection ?? (placement.startsWith('top') ? ['up', 'left', 'right'] : placement.startsWith('bottom') ? ['down', 'left', 'right'] : ['left', 'right']);
|
|
48
|
+
if (!toasts.length)
|
|
49
|
+
return null;
|
|
50
|
+
return (_jsx(BaseToast.Portal, { children: _jsx(PortalThemeBridge, { children: _jsx(BaseToast.Viewport, { ref: element => { if (element)
|
|
51
|
+
router.viewports.set(placement, element);
|
|
52
|
+
else
|
|
53
|
+
router.viewports.delete(placement); }, "aria-label": typeof viewportLabel === 'function' ? viewportLabel(placement) : viewportLabel, className: ['inkra-toast__viewport', viewportClassName].filter(Boolean).join(' '), "data-placement": placement, children: toasts.map((toastItem) => {
|
|
27
54
|
const tone = toneOf(toastItem.type);
|
|
28
|
-
|
|
55
|
+
const variant = !toastItem.variant || toastItem.variant === 'auto'
|
|
56
|
+
? (/^(top|bottom)-(start|end)$/.test(placement) ? 'card' : 'compact')
|
|
57
|
+
: toastItem.variant;
|
|
58
|
+
return (_jsxs(BaseToast.Root, { className: "inkra-toast__root", "data-tone": tone, "data-variant": variant, "data-toast-id": toastItem.id, swipeDirection: directions, toast: toastItem, children: [_jsx(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-toast__surface", seed: `toast-${toastItem.id}`, variant: variant === 'compact' ? 'full' : tone === 'neutral' ? 'paper' : 'quiet' }), variant === 'compact' && _jsx(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-toast__wash", seed: `toast-wash-${toastItem.id}`, variant: "selection" }), _jsx(PencilSurface, { "aria-hidden": "true", as: "span", className: "inkra-toast__focus", seed: `toast-focus-${toastItem.id}`, variant: "outline" }), _jsxs(BaseToast.Content, { className: "inkra-toast__content", children: [_jsxs("div", { className: "inkra-toast__copy", children: [toastItem.title != null && (_jsx(BaseToast.Title, { className: "inkra-toast__title", children: text(toastItem.title) })), toastItem.description != null && (_jsx(BaseToast.Description, { className: "inkra-toast__description", children: text(toastItem.description) }))] }), _jsxs("div", { className: "inkra-toast__actions", children: [toastItem.actionProps?.children != null && (_jsx(BaseToast.Action, { className: "inkra-toast__action", render: _jsx(Button, { size: "sm", variant: "outline" }) })), _jsx(BaseToast.Close, { "aria-label": closeLabel, className: "inkra-toast__close", render: _jsx(Button, { size: "icon-sm", variant: "ghost" }), children: _jsx(PencilIcon, { paths: closePaths, size: 20, sourceSize: 24, decorative: true }) })] })] })] }, toastItem.id));
|
|
29
59
|
}) }) }) }));
|
|
30
60
|
}
|
|
31
|
-
function Provider({ children, closeLabel = 'Close notification', limit, placement = 'bottom-end',
|
|
32
|
-
|
|
61
|
+
function Provider({ children, closeLabel = 'Close notification', limit, limits, placement = 'bottom-end', variant = 'auto', swipeDirection, timeout, toastManager, viewportClassName, viewportLabel = 'Notifications', }) {
|
|
62
|
+
const [localManager] = useState(() => createToastManager());
|
|
63
|
+
const manager = toastManager ?? localManager;
|
|
64
|
+
const router = getToastRouter(manager);
|
|
65
|
+
useToastNavigation(manager);
|
|
66
|
+
return (_jsx(PortalThemeProvider, { children: _jsxs(ManagerContext.Provider, { value: manager, children: [_jsx(ToastDefaults, { manager: manager, placement: placement, variant: variant }), _jsx(ToastThemeAnchor, {}), toastPlacements.map(position => (_jsx(BaseToast.Provider, { limit: limits?.[position] ?? limit, timeout: timeout, toastManager: router.queues[position], children: _jsx(ToastQueue, { manager: manager, closeLabel: closeLabel, placement: position, swipeDirection: swipeDirection, viewportClassName: viewportClassName, viewportLabel: viewportLabel }) }, position))), children] }) }));
|
|
67
|
+
}
|
|
68
|
+
export function useToastManager() {
|
|
69
|
+
const manager = useContext(ManagerContext);
|
|
70
|
+
if (!manager)
|
|
71
|
+
throw new Error('useToastManager must be used within Toast.Provider.');
|
|
72
|
+
const router = getToastRouter(manager);
|
|
73
|
+
const toasts = useSyncExternalStore(router.subscribe, router.getSnapshot, getServerSnapshot);
|
|
74
|
+
return useMemo(() => ({ ...manager, toasts }), [manager, toasts]);
|
|
33
75
|
}
|
|
34
|
-
export const useToastManager = BaseToast.useToastManager;
|
|
35
|
-
export const createToastManager = BaseToast.createToastManager;
|
|
36
76
|
export const Toast = { Provider, useToastManager, createToastManager };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ToastManagerAddOptions as BaseAddOptions, ToastManagerUpdateOptions as BaseUpdateOptions, ToastManagerPromiseOptions as BasePromiseOptions, ToastObject as BaseToastObject } from '@base-ui/react/toast';
|
|
2
|
+
export declare const toastPlacements: readonly ["top-start", "top-center", "top-end", "center-start", "center", "center-end", "bottom-start", "bottom-center", "bottom-end"];
|
|
3
|
+
export type ToastPlacement = typeof toastPlacements[number];
|
|
4
|
+
export type ToastVariant = 'auto' | 'card' | 'compact';
|
|
5
|
+
export type ToastObject<Data extends object = any> = BaseToastObject<Data> & {
|
|
6
|
+
placement?: ToastPlacement;
|
|
7
|
+
variant?: ToastVariant;
|
|
8
|
+
};
|
|
9
|
+
export type ToastAddOptions<Data extends object = any> = BaseAddOptions<Data> & {
|
|
10
|
+
placement?: ToastPlacement;
|
|
11
|
+
variant?: ToastVariant;
|
|
12
|
+
};
|
|
13
|
+
export type ToastUpdateOptions<Data extends object = any> = BaseUpdateOptions<Data> & {
|
|
14
|
+
variant?: ToastVariant;
|
|
15
|
+
};
|
|
16
|
+
export type ToastPromiseOptions<Value, Data extends object = any> = BasePromiseOptions<Value, Data> & {
|
|
17
|
+
placement?: ToastPlacement;
|
|
18
|
+
variant?: ToastVariant;
|
|
19
|
+
};
|
|
20
|
+
export interface ToastManager<Data extends object = any> {
|
|
21
|
+
add: <T extends Data = Data>(options: ToastAddOptions<T>) => string;
|
|
22
|
+
update: <T extends Data = Data>(id: string, options: ToastUpdateOptions<T> | ((previous: ToastObject<T>) => ToastUpdateOptions<T>)) => void;
|
|
23
|
+
close: (id?: string) => void;
|
|
24
|
+
closeAll: (placement?: ToastPlacement) => void;
|
|
25
|
+
promise: <Value, T extends Data = Data>(value: Promise<Value>, options: ToastPromiseOptions<Value, T>) => Promise<Value>;
|
|
26
|
+
}
|
|
27
|
+
export declare function getToastRouter(manager: ToastManager): {
|
|
28
|
+
queues: Record<"center" | "top-start" | "top-end" | "bottom-start" | "bottom-end" | "top-center" | "center-start" | "center-end" | "bottom-center", import("@base-ui/react/toast").ToastManager<object>>;
|
|
29
|
+
controllers: Map<"center" | "top-start" | "top-end" | "bottom-start" | "bottom-end" | "top-center" | "center-start" | "center-end" | "bottom-center", import("@base-ui/react/toast").UseToastManagerReturnValue<object>>;
|
|
30
|
+
engine: (placement: ToastPlacement) => import("@base-ui/react/toast").ToastManager<object> | import("@base-ui/react/toast").UseToastManagerReturnValue<object>;
|
|
31
|
+
viewports: Map<"center" | "top-start" | "top-end" | "bottom-start" | "bottom-end" | "top-center" | "center-start" | "center-end" | "bottom-center", HTMLElement>;
|
|
32
|
+
owners: Map<string, "center" | "top-start" | "top-end" | "bottom-start" | "bottom-end" | "top-center" | "center-start" | "center-end" | "bottom-center">;
|
|
33
|
+
defaults: {
|
|
34
|
+
placement: ToastPlacement;
|
|
35
|
+
variant: ToastVariant;
|
|
36
|
+
};
|
|
37
|
+
getSnapshot: () => ToastObject<any>[];
|
|
38
|
+
subscribe(listener: () => void): () => void;
|
|
39
|
+
report(placement: ToastPlacement, toasts: ToastObject[]): void;
|
|
40
|
+
};
|
|
41
|
+
/** Routes notifications; Base UI still owns each queue, timer and lifecycle. */
|
|
42
|
+
export declare function createToastManager<Data extends object = any>(): ToastManager<Data>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Toast as BaseToast } from '@base-ui/react/toast';
|
|
2
|
+
export const toastPlacements = [
|
|
3
|
+
'top-start', 'top-center', 'top-end',
|
|
4
|
+
'center-start', 'center', 'center-end',
|
|
5
|
+
'bottom-start', 'bottom-center', 'bottom-end',
|
|
6
|
+
];
|
|
7
|
+
function createRouter() {
|
|
8
|
+
const queues = Object.fromEntries(toastPlacements.map(placement => [placement, BaseToast.createToastManager()]));
|
|
9
|
+
const controllers = new Map();
|
|
10
|
+
const snapshots = new Map();
|
|
11
|
+
const owners = new Map();
|
|
12
|
+
const listeners = new Set();
|
|
13
|
+
let snapshot = [];
|
|
14
|
+
return {
|
|
15
|
+
queues,
|
|
16
|
+
controllers,
|
|
17
|
+
engine: (placement) => controllers.get(placement) ?? queues[placement],
|
|
18
|
+
viewports: new Map(),
|
|
19
|
+
owners,
|
|
20
|
+
defaults: { placement: 'bottom-end', variant: 'auto' },
|
|
21
|
+
getSnapshot: () => snapshot,
|
|
22
|
+
subscribe(listener) {
|
|
23
|
+
listeners.add(listener);
|
|
24
|
+
return () => { listeners.delete(listener); };
|
|
25
|
+
},
|
|
26
|
+
report(placement, toasts) {
|
|
27
|
+
if (snapshots.get(placement) === toasts)
|
|
28
|
+
return;
|
|
29
|
+
const currentIds = new Set(toasts.map(toast => toast.id));
|
|
30
|
+
for (const previous of snapshots.get(placement) ?? []) {
|
|
31
|
+
if (!currentIds.has(previous.id))
|
|
32
|
+
owners.delete(previous.id);
|
|
33
|
+
}
|
|
34
|
+
for (const toast of toasts)
|
|
35
|
+
owners.set(toast.id, placement);
|
|
36
|
+
snapshots.set(placement, toasts);
|
|
37
|
+
snapshot = toastPlacements.flatMap(position => snapshots.get(position) ?? []);
|
|
38
|
+
listeners.forEach(listener => listener());
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
const routers = new WeakMap();
|
|
43
|
+
export function getToastRouter(manager) {
|
|
44
|
+
const router = routers.get(manager);
|
|
45
|
+
if (!router)
|
|
46
|
+
throw new Error('Use createToastManager from @inkra/react with Toast.Provider.');
|
|
47
|
+
return router;
|
|
48
|
+
}
|
|
49
|
+
/** Routes notifications; Base UI still owns each queue, timer and lifecycle. */
|
|
50
|
+
export function createToastManager() {
|
|
51
|
+
const router = createRouter();
|
|
52
|
+
const manager = {
|
|
53
|
+
add(options) {
|
|
54
|
+
// An upsert stays in its original queue, even if the default position changed.
|
|
55
|
+
const placement = (options.id && router.owners.get(options.id)) || options.placement || router.defaults.placement;
|
|
56
|
+
const id = router.engine(placement).add({ ...options, placement, variant: options.variant ?? router.defaults.variant });
|
|
57
|
+
router.owners.set(id, placement);
|
|
58
|
+
return id;
|
|
59
|
+
},
|
|
60
|
+
update(id, options) {
|
|
61
|
+
for (const placement of toastPlacements)
|
|
62
|
+
router.engine(placement).update(id, options);
|
|
63
|
+
},
|
|
64
|
+
close(id) {
|
|
65
|
+
for (const placement of toastPlacements)
|
|
66
|
+
router.engine(placement).close(id);
|
|
67
|
+
},
|
|
68
|
+
closeAll(placement) {
|
|
69
|
+
if (placement)
|
|
70
|
+
router.engine(placement).close();
|
|
71
|
+
else
|
|
72
|
+
manager.close();
|
|
73
|
+
},
|
|
74
|
+
promise(value, options) {
|
|
75
|
+
const placement = options.placement ?? router.defaults.placement;
|
|
76
|
+
const loading = typeof options.loading === 'string' ? { title: options.loading } : options.loading;
|
|
77
|
+
return router.engine(placement).promise(value, {
|
|
78
|
+
...options,
|
|
79
|
+
loading: { ...loading, placement, variant: options.variant ?? router.defaults.variant },
|
|
80
|
+
});
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
routers.set(manager, router);
|
|
84
|
+
return manager;
|
|
85
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import { getToastRouter, toastPlacements } from './manager.js';
|
|
3
|
+
/** Coordinate F6 once: native per-queue listeners would otherwise compete for focus. */
|
|
4
|
+
export function useToastNavigation(manager) {
|
|
5
|
+
useEffect(() => {
|
|
6
|
+
const router = getToastRouter(manager);
|
|
7
|
+
let returnTarget = null;
|
|
8
|
+
let focusedToastElement = null;
|
|
9
|
+
const restore = () => {
|
|
10
|
+
if (returnTarget?.isConnected)
|
|
11
|
+
returnTarget.focus({ preventScroll: true });
|
|
12
|
+
returnTarget = null;
|
|
13
|
+
};
|
|
14
|
+
const focusin = (event) => {
|
|
15
|
+
const target = event.target;
|
|
16
|
+
if ([...router.viewports.values()].some(viewport => viewport.contains(target))) {
|
|
17
|
+
focusedToastElement = target;
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
focusedToastElement = null;
|
|
21
|
+
returnTarget = null;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
const unsubscribe = router.subscribe(() => {
|
|
25
|
+
if (returnTarget && focusedToastElement && !focusedToastElement.isConnected && document.activeElement === document.body)
|
|
26
|
+
restore();
|
|
27
|
+
});
|
|
28
|
+
const focusable = (viewport) => Array.from(viewport.querySelectorAll('button:not(:disabled), a[href], input:not(:disabled), select:not(:disabled), textarea:not(:disabled), [tabindex="0"]')).filter(element => element.getClientRects().length && !element.closest('[inert], [data-ending-style], [data-limited]'));
|
|
29
|
+
const keydown = (event) => {
|
|
30
|
+
const viewports = toastPlacements.map(placement => router.viewports.get(placement))
|
|
31
|
+
.filter((viewport) => !!viewport?.isConnected && focusable(viewport).length > 0);
|
|
32
|
+
if (!viewports.length)
|
|
33
|
+
return;
|
|
34
|
+
const active = document.activeElement;
|
|
35
|
+
const index = viewports.findIndex(viewport => viewport.contains(active));
|
|
36
|
+
if (event.key === 'F6' && !event.altKey && !event.ctrlKey && !event.metaKey) {
|
|
37
|
+
event.preventDefault();
|
|
38
|
+
event.stopImmediatePropagation();
|
|
39
|
+
if (index === -1)
|
|
40
|
+
returnTarget = active;
|
|
41
|
+
const next = index === -1 ? (event.shiftKey ? viewports.length - 1 : 0)
|
|
42
|
+
: (index + (event.shiftKey ? -1 : 1) + viewports.length) % viewports.length;
|
|
43
|
+
focusable(viewports[next])[0]?.focus({ preventScroll: true });
|
|
44
|
+
}
|
|
45
|
+
else if (index !== -1 && returnTarget && event.key === 'Tab') {
|
|
46
|
+
const items = focusable(viewports[index]);
|
|
47
|
+
if ((event.shiftKey && items[0] === active) || (!event.shiftKey && items[items.length - 1] === active)) {
|
|
48
|
+
event.preventDefault();
|
|
49
|
+
event.stopImmediatePropagation();
|
|
50
|
+
restore();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else if (index !== -1 && returnTarget && event.key === 'Escape') {
|
|
54
|
+
const toast = active?.closest('[data-toast-id]');
|
|
55
|
+
if (!toast?.dataset.toastId)
|
|
56
|
+
return;
|
|
57
|
+
event.preventDefault();
|
|
58
|
+
event.stopImmediatePropagation();
|
|
59
|
+
manager.close(toast.dataset.toastId);
|
|
60
|
+
const next = focusable(viewports[index]).find(element => !toast.contains(element));
|
|
61
|
+
if (next)
|
|
62
|
+
next.focus({ preventScroll: true });
|
|
63
|
+
else
|
|
64
|
+
restore();
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
window.addEventListener('keydown', keydown, true);
|
|
68
|
+
document.addEventListener('focusin', focusin, true);
|
|
69
|
+
return () => {
|
|
70
|
+
unsubscribe();
|
|
71
|
+
window.removeEventListener('keydown', keydown, true);
|
|
72
|
+
document.removeEventListener('focusin', focusin, true);
|
|
73
|
+
};
|
|
74
|
+
}, [manager]);
|
|
75
|
+
}
|
package/dist/tokens.css
CHANGED
|
@@ -166,6 +166,10 @@
|
|
|
166
166
|
--inkra-z-tooltip: 40;
|
|
167
167
|
|
|
168
168
|
--inkra-stroke-width: 2px;
|
|
169
|
+
--inkra-icon-stroke-width: 1.5px;
|
|
170
|
+
--inkra-icon-breaks: 0.18;
|
|
171
|
+
--inkra-icon-break-count: 1;
|
|
172
|
+
--inkra-icon-pressure-variation: 0.82;
|
|
169
173
|
--inkra-stroke-variation: 0.82;
|
|
170
174
|
--inkra-stroke-grain: 0.7;
|
|
171
175
|
--inkra-stroke-breaks: 0.2;
|
|
@@ -7,7 +7,7 @@ export declare function TooltipRoot(props: TooltipRootProps): import("react").JS
|
|
|
7
7
|
export type TooltipTriggerProps = TooltipPrimitive.TooltipTriggerProps;
|
|
8
8
|
export declare const TooltipTrigger: import("react").ForwardRefExoticComponent<TooltipPrimitive.TooltipTriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
|
|
9
9
|
export type TooltipPortalProps = TooltipPrimitive.TooltipPortalProps;
|
|
10
|
-
export declare function TooltipPortal({ children, ...props }: TooltipPortalProps): import("react").
|
|
10
|
+
export declare function TooltipPortal({ children, ...props }: TooltipPortalProps): import("react").ReactNode;
|
|
11
11
|
export interface TooltipContentProps extends TooltipPrimitive.TooltipContentProps {
|
|
12
12
|
seed?: string | number;
|
|
13
13
|
surfaceClassName?: string;
|
package/dist/tooltip/Tooltip.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
|
|
3
3
|
import { forwardRef, useId } from 'react';
|
|
4
|
-
import {
|
|
4
|
+
import { mapPortalChildren, PortalThemeProvider, usePortalThemeProps, usePortalTriggerRef } from '../overlay/themeBridge.js';
|
|
5
5
|
import { PencilSurface } from '../PencilSurface.js';
|
|
6
6
|
import { PencilText } from '../PencilText.js';
|
|
7
7
|
function classes(...values) {
|
|
@@ -16,14 +16,15 @@ export const TooltipTrigger = forwardRef(function TooltipTrigger(props, forwarde
|
|
|
16
16
|
return _jsx(TooltipPrimitive.Trigger, { ...props, ref: ref });
|
|
17
17
|
});
|
|
18
18
|
export function TooltipPortal({ children, ...props }) {
|
|
19
|
-
return (_jsx(TooltipPrimitive.Portal, { ...props, children:
|
|
19
|
+
return mapPortalChildren(children, child => _jsx(TooltipPrimitive.Portal, { ...props, children: child }));
|
|
20
20
|
}
|
|
21
|
-
export const TooltipContent = forwardRef(function TooltipContent({ children, className, seed, sideOffset = 6, surfaceClassName, surfaceRadius = 6, surfaceVariant = 'paper', ...props }, ref) {
|
|
21
|
+
export const TooltipContent = forwardRef(function TooltipContent({ children, className, collisionPadding = 8, seed, sideOffset = 6, surfaceClassName, surfaceRadius = 6, surfaceVariant = 'paper', ...props }, ref) {
|
|
22
22
|
const generatedId = useId();
|
|
23
|
+
props = usePortalThemeProps(props);
|
|
23
24
|
const resolvedSeed = seed ?? `inkra-tooltip-${generatedId}`;
|
|
24
|
-
return (_jsx(TooltipPrimitive.Content, { ...props, className: classes('inkra-tooltip__content', className), ref: ref, sideOffset: sideOffset, children: _jsx(PencilSurface, { className: classes('inkra-tooltip__surface', surfaceClassName), radius: surfaceRadius, seed: resolvedSeed, variant: surfaceVariant, children: _jsx("div", { className: "inkra-tooltip__content-body", children: typeof children === 'string' ? (_jsx(PencilText, { seed: `${resolvedSeed}-text`, children: children })) : children }) }) }));
|
|
25
|
+
return (_jsx(TooltipPrimitive.Content, { ...props, className: classes('inkra-tooltip__content', className), collisionPadding: collisionPadding, ref: ref, sideOffset: sideOffset, children: _jsx(PencilSurface, { className: classes('inkra-tooltip__surface', surfaceClassName), radius: surfaceRadius, seed: resolvedSeed, variant: surfaceVariant, children: _jsx("div", { className: "inkra-tooltip__content-body", children: typeof children === 'string' ? (_jsx(PencilText, { seed: `${resolvedSeed}-text`, children: children })) : children }) }) }));
|
|
25
26
|
});
|
|
26
|
-
export const Tooltip = Object.freeze({
|
|
27
|
+
export const Tooltip = /* @__PURE__ */ Object.freeze({
|
|
27
28
|
Provider: TooltipProvider,
|
|
28
29
|
Root: TooltipRoot,
|
|
29
30
|
Trigger: TooltipTrigger,
|
package/dist/transfer/index.d.ts
CHANGED
|
@@ -28,6 +28,8 @@ export interface TransferLabels {
|
|
|
28
28
|
moveSelectedToTarget?: string;
|
|
29
29
|
moveSelectedToSource?: string;
|
|
30
30
|
batchActions?: string;
|
|
31
|
+
previousPage?: string;
|
|
32
|
+
nextPage?: string;
|
|
31
33
|
}
|
|
32
34
|
export interface TransferCountDetails {
|
|
33
35
|
side: TransferSide;
|
|
@@ -58,6 +60,9 @@ export interface TransferProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onC
|
|
|
58
60
|
filterItem?: (item: TransferItem, query: string, side: TransferSide) => boolean;
|
|
59
61
|
renderItem?: (item: TransferItem, state: TransferItemRenderState) => ReactNode;
|
|
60
62
|
formatCount?: (details: TransferCountDetails) => ReactNode;
|
|
63
|
+
/** Opt-in panel pagination. All items remain searchable and movable. */
|
|
64
|
+
pageSize?: number;
|
|
65
|
+
pageLabel?: (page: number, pageCount: number) => ReactNode;
|
|
61
66
|
}
|
|
62
67
|
export declare const Transfer: import("react").ForwardRefExoticComponent<TransferProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
63
68
|
export { moveTransferKeys, normalizeTargetKeys, splitTransferItems, uniqueTransferItems } from './model.js';
|