@inkra/react 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +316 -2
- package/THIRD_PARTY_NOTICES.md +39 -0
- package/Yozai-OFL.txt +94 -0
- package/dist/Button.d.ts +36 -0
- package/dist/Button.js +89 -0
- package/dist/PencilIcon.d.ts +22 -0
- package/dist/PencilIcon.js +210 -0
- package/dist/PencilLine.d.ts +10 -0
- package/dist/PencilLine.js +91 -0
- package/dist/PencilSurface.d.ts +19 -0
- package/dist/PencilSurface.js +19 -0
- package/dist/PencilText.d.ts +16 -0
- package/dist/PencilText.js +338 -0
- package/dist/accordion/Accordion.d.ts +27 -0
- package/dist/accordion/Accordion.js +40 -0
- package/dist/accordion/index.d.ts +2 -0
- package/dist/accordion/index.js +1 -0
- package/dist/alert-dialog/index.d.ts +64 -0
- package/dist/alert-dialog/index.js +109 -0
- package/dist/assets/ui-hand-yozai.woff2 +0 -0
- package/dist/audio-player/index.d.ts +60 -0
- package/dist/audio-player/index.js +101 -0
- package/dist/breadcrumb/index.d.ts +16 -0
- package/dist/breadcrumb/index.js +25 -0
- package/dist/calendar/index.d.ts +21 -0
- package/dist/calendar/index.js +41 -0
- package/dist/carousel/index.d.ts +51 -0
- package/dist/carousel/index.js +290 -0
- package/dist/choice-grid/index.d.ts +38 -0
- package/dist/choice-grid/index.js +85 -0
- package/dist/color-picker/color.d.ts +15 -0
- package/dist/color-picker/color.js +55 -0
- package/dist/color-picker/index.d.ts +31 -0
- package/dist/color-picker/index.js +69 -0
- package/dist/command/index.d.ts +23 -0
- package/dist/command/index.js +39 -0
- package/dist/content/index.d.ts +106 -0
- package/dist/content/index.js +80 -0
- package/dist/context-menu/index.d.ts +76 -0
- package/dist/context-menu/index.js +240 -0
- package/dist/data-table/index.d.ts +47 -0
- package/dist/data-table/index.js +91 -0
- package/dist/date-picker/index.d.ts +50 -0
- package/dist/date-picker/index.js +180 -0
- package/dist/day-agenda/index.d.ts +43 -0
- package/dist/day-agenda/index.js +47 -0
- package/dist/day-agenda/layout.d.ts +20 -0
- package/dist/day-agenda/layout.js +60 -0
- package/dist/display/ProgressRing.d.ts +6 -0
- package/dist/display/ProgressRing.js +146 -0
- package/dist/display/index.d.ts +59 -0
- package/dist/display/index.js +75 -0
- package/dist/dockable-panel/index.d.ts +41 -0
- package/dist/dockable-panel/index.js +253 -0
- package/dist/dockable-panel/model.d.ts +21 -0
- package/dist/dockable-panel/model.js +61 -0
- package/dist/drawer/index.d.ts +63 -0
- package/dist/drawer/index.js +86 -0
- package/dist/dropdown-menu/index.d.ts +34 -0
- package/dist/dropdown-menu/index.js +40 -0
- package/dist/editable/index.d.ts +53 -0
- package/dist/editable/index.js +162 -0
- package/dist/empty/index.d.ts +21 -0
- package/dist/empty/index.js +23 -0
- package/dist/flip/index.d.ts +49 -0
- package/dist/flip/index.js +138 -0
- package/dist/form/EditablePencilText.d.ts +13 -0
- package/dist/form/EditablePencilText.js +323 -0
- package/dist/form/Field.d.ts +13 -0
- package/dist/form/Field.js +26 -0
- package/dist/form/Input.d.ts +27 -0
- package/dist/form/Input.js +97 -0
- package/dist/form/InputAction.d.ts +7 -0
- package/dist/form/InputAction.js +24 -0
- package/dist/form/Textarea.d.ts +17 -0
- package/dist/form/Textarea.js +102 -0
- package/dist/form/context.d.ts +12 -0
- package/dist/form/context.js +10 -0
- package/dist/form/controlProps.d.ts +23 -0
- package/dist/form/controlProps.js +20 -0
- package/dist/form/index.d.ts +7 -0
- package/dist/form/index.js +3 -0
- package/dist/form/types.d.ts +6 -0
- package/dist/form/types.js +1 -0
- package/dist/heatmap/index.d.ts +59 -0
- package/dist/heatmap/index.js +248 -0
- package/dist/hotkey/index.d.ts +37 -0
- package/dist/hotkey/index.js +130 -0
- package/dist/hotkey/model.d.ts +27 -0
- package/dist/hotkey/model.js +97 -0
- package/dist/hover-card/index.d.ts +33 -0
- package/dist/hover-card/index.js +141 -0
- package/dist/image-cropper/index.d.ts +82 -0
- package/dist/image-cropper/index.js +261 -0
- package/dist/image-cropper/model.d.ts +42 -0
- package/dist/image-cropper/model.js +94 -0
- package/dist/image-viewer/index.d.ts +79 -0
- package/dist/image-viewer/index.js +220 -0
- package/dist/image-viewer/model.d.ts +9 -0
- package/dist/image-viewer/model.js +18 -0
- package/dist/images/layers-2x.png +0 -0
- package/dist/images/layers.png +0 -0
- package/dist/images/marker-icon.png +0 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.js +69 -0
- package/dist/input-number/index.d.ts +16 -0
- package/dist/input-number/index.js +77 -0
- package/dist/internal/InputActionContext.d.ts +1 -0
- package/dist/internal/InputActionContext.js +3 -0
- package/dist/internal/cache.d.ts +14 -0
- package/dist/internal/cache.js +54 -0
- package/dist/internal/line.d.ts +4 -0
- package/dist/internal/line.js +21 -0
- package/dist/internal/paper.d.ts +1 -0
- package/dist/internal/paper.js +65 -0
- package/dist/internal/random.d.ts +3 -0
- package/dist/internal/random.js +30 -0
- package/dist/internal/recovery.d.ts +1 -0
- package/dist/internal/recovery.js +52 -0
- package/dist/internal/renderTokens.d.ts +10 -0
- package/dist/internal/renderTokens.js +13 -0
- package/dist/internal/stroke.d.ts +24 -0
- package/dist/internal/stroke.js +144 -0
- package/dist/internal/surfaceRenderer.d.ts +41 -0
- package/dist/internal/surfaceRenderer.js +178 -0
- package/dist/internal/textQueue.d.ts +1 -0
- package/dist/internal/textQueue.js +37 -0
- package/dist/internal/textRenderer.d.ts +28 -0
- package/dist/internal/textRenderer.js +239 -0
- package/dist/internal/travelIcons.d.ts +24 -0
- package/dist/internal/travelIcons.js +14 -0
- package/dist/internal/useBrowserLayoutEffect.d.ts +2 -0
- package/dist/internal/useBrowserLayoutEffect.js +3 -0
- package/dist/internal/usePencilSurface.d.ts +3 -0
- package/dist/internal/usePencilSurface.js +116 -0
- package/dist/internal/useSelectionDrawing.d.ts +2 -0
- package/dist/internal/useSelectionDrawing.js +90 -0
- package/dist/internal/wash.d.ts +16 -0
- package/dist/internal/wash.js +149 -0
- package/dist/journey-map/MapScale.d.ts +4 -0
- package/dist/journey-map/MapScale.js +54 -0
- package/dist/journey-map/index.d.ts +39 -0
- package/dist/journey-map/index.js +197 -0
- package/dist/journey-map/terrain.d.ts +8 -0
- package/dist/journey-map/terrain.js +127 -0
- package/dist/keypad/index.d.ts +23 -0
- package/dist/keypad/index.js +63 -0
- package/dist/keypad/model.d.ts +20 -0
- package/dist/keypad/model.js +33 -0
- package/dist/layout/index.d.ts +63 -0
- package/dist/layout/index.js +62 -0
- package/dist/log/index.d.ts +45 -0
- package/dist/log/index.js +159 -0
- package/dist/log/model.d.ts +16 -0
- package/dist/log/model.js +13 -0
- package/dist/node-graph/index.d.ts +123 -0
- package/dist/node-graph/index.js +304 -0
- package/dist/number-ticker/index.d.ts +29 -0
- package/dist/number-ticker/index.js +132 -0
- package/dist/number-ticker/model.d.ts +8 -0
- package/dist/number-ticker/model.js +34 -0
- package/dist/overflow-list/index.d.ts +39 -0
- package/dist/overflow-list/index.js +230 -0
- package/dist/overflow-list/layout.d.ts +19 -0
- package/dist/overflow-list/layout.js +44 -0
- package/dist/overlay/Overlay.d.ts +65 -0
- package/dist/overlay/Overlay.js +77 -0
- package/dist/overlay/Popover.d.ts +43 -0
- package/dist/overlay/Popover.js +45 -0
- package/dist/overlay/index.d.ts +4 -0
- package/dist/overlay/index.js +2 -0
- package/dist/overlay/themeBridge.d.ts +8 -0
- package/dist/overlay/themeBridge.js +98 -0
- package/dist/pagination/index.d.ts +23 -0
- package/dist/pagination/index.js +41 -0
- package/dist/pagination/model.d.ts +4 -0
- package/dist/pagination/model.js +42 -0
- package/dist/pan-zoom/index.d.ts +71 -0
- package/dist/pan-zoom/index.js +175 -0
- package/dist/pencil.d.ts +10 -0
- package/dist/pencil.js +39 -0
- package/dist/radar/index.d.ts +54 -0
- package/dist/radar/index.js +220 -0
- package/dist/range-slider/index.d.ts +16 -0
- package/dist/range-slider/index.js +22 -0
- package/dist/rating/index.d.ts +28 -0
- package/dist/rating/index.js +127 -0
- package/dist/resizable/index.d.ts +32 -0
- package/dist/resizable/index.js +50 -0
- package/dist/scroll-area/index.d.ts +36 -0
- package/dist/scroll-area/index.js +44 -0
- package/dist/segmented-progress/SegmentedProgress.d.ts +17 -0
- package/dist/segmented-progress/SegmentedProgress.js +42 -0
- package/dist/select/MultipleSelect.d.ts +44 -0
- package/dist/select/MultipleSelect.js +124 -0
- package/dist/select/SearchableSelect.d.ts +45 -0
- package/dist/select/SearchableSelect.js +50 -0
- package/dist/select/Select.d.ts +109 -0
- package/dist/select/Select.js +205 -0
- package/dist/select/index.d.ts +4 -0
- package/dist/select/index.js +2 -0
- package/dist/selection/Checkbox.d.ts +9 -0
- package/dist/selection/Checkbox.js +37 -0
- package/dist/selection/RadioGroup.d.ts +23 -0
- package/dist/selection/RadioGroup.js +82 -0
- package/dist/selection/Switch.d.ts +8 -0
- package/dist/selection/Switch.js +36 -0
- package/dist/selection/index.d.ts +6 -0
- package/dist/selection/index.js +3 -0
- package/dist/selection/useNativeChecked.d.ts +13 -0
- package/dist/selection/useNativeChecked.js +27 -0
- package/dist/slider/index.d.ts +11 -0
- package/dist/slider/index.js +45 -0
- package/dist/slot/index.d.ts +24 -0
- package/dist/slot/index.js +29 -0
- package/dist/sortable/index.d.ts +50 -0
- package/dist/sortable/index.js +278 -0
- package/dist/sortable/model.d.ts +30 -0
- package/dist/sortable/model.js +64 -0
- package/dist/sparkline/index.d.ts +19 -0
- package/dist/sparkline/index.js +240 -0
- package/dist/sparkline/model.d.ts +16 -0
- package/dist/sparkline/model.js +28 -0
- package/dist/steps/index.d.ts +16 -0
- package/dist/steps/index.js +37 -0
- package/dist/styles.css +8226 -0
- package/dist/tabs/index.d.ts +22 -0
- package/dist/tabs/index.js +42 -0
- package/dist/tag-input/index.d.ts +69 -0
- package/dist/tag-input/index.js +325 -0
- package/dist/tag-input/model.d.ts +14 -0
- package/dist/tag-input/model.js +38 -0
- package/dist/time-picker/index.d.ts +15 -0
- package/dist/time-picker/index.js +168 -0
- package/dist/timeline/index.d.ts +14 -0
- package/dist/timeline/index.js +16 -0
- package/dist/toast/index.d.ts +27 -0
- package/dist/toast/index.js +36 -0
- package/dist/toggle-group/index.d.ts +17 -0
- package/dist/toggle-group/index.js +26 -0
- package/dist/tokens.css +356 -0
- package/dist/tooltip/Tooltip.d.ts +24 -0
- package/dist/tooltip/Tooltip.js +32 -0
- package/dist/tooltip/index.d.ts +2 -0
- package/dist/tooltip/index.js +1 -0
- package/dist/transfer/index.d.ts +64 -0
- package/dist/transfer/index.js +174 -0
- package/dist/transfer/model.d.ts +12 -0
- package/dist/transfer/model.js +33 -0
- package/dist/tree/index.d.ts +39 -0
- package/dist/tree/index.js +36 -0
- package/dist/tree-select/index.d.ts +59 -0
- package/dist/tree-select/index.js +142 -0
- package/dist/tree-select/model.d.ts +31 -0
- package/dist/tree-select/model.js +135 -0
- package/dist/typewriter/index.d.ts +21 -0
- package/dist/typewriter/index.js +101 -0
- package/dist/typewriter/model.d.ts +7 -0
- package/dist/typewriter/model.js +45 -0
- package/dist/upload/index.d.ts +59 -0
- package/dist/upload/index.js +230 -0
- package/dist/virtual-list/index.d.ts +44 -0
- package/dist/virtual-list/index.js +128 -0
- package/dist/virtual-list/model.d.ts +7 -0
- package/dist/virtual-list/model.js +27 -0
- package/package.json +119 -4
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useCallback, useEffect, useId, useImperativeHandle, useRef, useState } from 'react';
|
|
3
|
+
import { Button } from '../Button.js';
|
|
4
|
+
import { PencilIcon } from '../PencilIcon.js';
|
|
5
|
+
import { PencilSurface } from '../PencilSurface.js';
|
|
6
|
+
import { PencilText } from '../PencilText.js';
|
|
7
|
+
import { Select } from '../select/index.js';
|
|
8
|
+
import { Slider } from '../slider/index.js';
|
|
9
|
+
const pausePaths = [
|
|
10
|
+
{ d: 'M 15 3 H 18 A 1 1 0 0 1 19 4 V 20 A 1 1 0 0 1 18 21 H 15 A 1 1 0 0 1 14 20 V 4 A 1 1 0 0 1 15 3 Z' },
|
|
11
|
+
{ d: 'M 6 3 H 9 A 1 1 0 0 1 10 4 V 20 A 1 1 0 0 1 9 21 H 6 A 1 1 0 0 1 5 20 V 4 A 1 1 0 0 1 6 3 Z' },
|
|
12
|
+
];
|
|
13
|
+
const playPaths = [{ d: 'M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z' }];
|
|
14
|
+
const volumePaths = [
|
|
15
|
+
{ d: 'M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z' },
|
|
16
|
+
{ d: 'M16 9a5 5 0 0 1 0 6' },
|
|
17
|
+
{ d: 'M19.364 18.364a9 9 0 0 0 0-12.728' },
|
|
18
|
+
];
|
|
19
|
+
const mutePaths = [
|
|
20
|
+
{ d: 'M11 4.702a.7.7 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.7.7 0 0 0 11 19.298z' },
|
|
21
|
+
{ d: 'm16.5 14.5 5-5' },
|
|
22
|
+
{ d: 'm16.5 9.5 5 5' },
|
|
23
|
+
];
|
|
24
|
+
const DEFAULT_LABELS = {
|
|
25
|
+
play: 'Play', pause: 'Pause', mute: 'Mute', unmute: 'Unmute', seek: 'Seek', volume: 'Volume', rate: 'Playback speed', loading: 'Loading audio', blocked: 'Playback was blocked. Press play to try again.', error: 'Audio failed to load.',
|
|
26
|
+
};
|
|
27
|
+
const rates = [0.75, 1, 1.25, 1.5, 2];
|
|
28
|
+
function clamp(value, min, max) { return Number.isFinite(value) ? Math.min(max, Math.max(min, value)) : min; }
|
|
29
|
+
function formatTime(value) { if (!Number.isFinite(value) || value < 0)
|
|
30
|
+
return '0:00'; const seconds = Math.floor(value); return `${Math.floor(seconds / 60)}:${String(seconds % 60).padStart(2, '0')}`; }
|
|
31
|
+
export const AudioPlayer = forwardRef(function AudioPlayer({ src, preload = 'metadata', autoPlay = false, crossOrigin, className, defaultMuted = false, defaultPlaybackRate = 1, defaultVolume = 1, volume, muted, playbackRate, onVolumeChange, onMutedChange, onPlaybackRateChange, onTimeChange, onStateChange, onError, labels, renderControls, audioProps, ...props }, forwardedRef) {
|
|
32
|
+
const id = useId();
|
|
33
|
+
const audioRef = useRef(null);
|
|
34
|
+
const rootRef = useRef(null);
|
|
35
|
+
const [state, setState] = useState({ status: src ? 'loading' : 'idle', currentTime: 0, duration: 0, buffered: 0, volume: clamp(volume ?? defaultVolume, 0, 1), muted: muted ?? defaultMuted, playbackRate: clamp(playbackRate ?? defaultPlaybackRate, 0.25, 4), canPlay: false, error: null });
|
|
36
|
+
const stateRef = useRef(state);
|
|
37
|
+
stateRef.current = state;
|
|
38
|
+
const mergedLabels = { ...DEFAULT_LABELS, ...labels };
|
|
39
|
+
const update = useCallback((patch) => {
|
|
40
|
+
const next = { ...stateRef.current, ...patch };
|
|
41
|
+
stateRef.current = next;
|
|
42
|
+
setState(next);
|
|
43
|
+
onStateChange?.(next);
|
|
44
|
+
}, [onStateChange]);
|
|
45
|
+
const sync = useCallback(() => {
|
|
46
|
+
const audio = audioRef.current;
|
|
47
|
+
if (!audio)
|
|
48
|
+
return;
|
|
49
|
+
const buffered = audio.duration > 0 && audio.buffered.length ? audio.buffered.end(audio.buffered.length - 1) / audio.duration : 0;
|
|
50
|
+
update({ currentTime: Number.isFinite(audio.currentTime) ? audio.currentTime : 0, duration: Number.isFinite(audio.duration) ? audio.duration : 0, buffered: clamp(buffered, 0, 1), volume: audio.volume, muted: audio.muted, playbackRate: audio.playbackRate, canPlay: audio.readyState >= 3 });
|
|
51
|
+
}, [update]);
|
|
52
|
+
const play = useCallback(async () => { const audio = audioRef.current; if (!audio)
|
|
53
|
+
return; try {
|
|
54
|
+
await audio.play();
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
update({ status: 'blocked' });
|
|
58
|
+
onError?.(error instanceof DOMException ? error : null);
|
|
59
|
+
} }, [onError, update]);
|
|
60
|
+
const pause = useCallback(() => { audioRef.current?.pause(); }, []);
|
|
61
|
+
const togglePlay = useCallback(async () => { const audio = audioRef.current; if (!audio)
|
|
62
|
+
return; if (audio.paused)
|
|
63
|
+
await play();
|
|
64
|
+
else
|
|
65
|
+
pause(); }, [pause, play]);
|
|
66
|
+
const seek = useCallback((time) => { const audio = audioRef.current; if (!audio || !Number.isFinite(time))
|
|
67
|
+
return; audio.currentTime = clamp(time, 0, Number.isFinite(audio.duration) ? audio.duration : Math.max(0, time)); sync(); onTimeChange?.(audio.currentTime); }, [onTimeChange, sync]);
|
|
68
|
+
const setVolume = useCallback((next) => { const audio = audioRef.current; if (!audio)
|
|
69
|
+
return; audio.volume = clamp(next, 0, 1); onVolumeChange?.(audio.volume); sync(); }, [onVolumeChange, sync]);
|
|
70
|
+
const setMuted = useCallback((next) => { const audio = audioRef.current; if (!audio)
|
|
71
|
+
return; audio.muted = next; onMutedChange?.(next); sync(); }, [onMutedChange, sync]);
|
|
72
|
+
const setPlaybackRate = useCallback((next) => { const audio = audioRef.current; if (!audio)
|
|
73
|
+
return; audio.playbackRate = clamp(next, 0.25, 4); onPlaybackRateChange?.(audio.playbackRate); sync(); }, [onPlaybackRateChange, sync]);
|
|
74
|
+
const focus = useCallback(() => { rootRef.current?.querySelector('[data-audio-player-control]')?.focus(); }, []);
|
|
75
|
+
const controls = { audio: audioRef.current, state, play, pause, togglePlay, seek, setVolume, setMuted, setPlaybackRate, focus };
|
|
76
|
+
useImperativeHandle(forwardedRef, () => controls, [state, play, pause, togglePlay, seek, setVolume, setMuted, setPlaybackRate, focus]);
|
|
77
|
+
useEffect(() => { const audio = audioRef.current; if (!audio)
|
|
78
|
+
return; audio.volume = clamp(volume ?? defaultVolume, 0, 1); audio.muted = muted ?? defaultMuted; audio.playbackRate = clamp(playbackRate ?? defaultPlaybackRate, 0.25, 4); sync(); }, [defaultMuted, defaultPlaybackRate, defaultVolume, muted, playbackRate, src, volume, sync]);
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
const audio = audioRef.current;
|
|
81
|
+
if (!audio || !src)
|
|
82
|
+
return;
|
|
83
|
+
audio.pause();
|
|
84
|
+
audio.currentTime = 0;
|
|
85
|
+
update({ status: 'loading', currentTime: 0, duration: 0, buffered: 0, canPlay: false, error: null });
|
|
86
|
+
audio.load();
|
|
87
|
+
}, [src, update]);
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
const audio = audioRef.current;
|
|
90
|
+
if (!audio)
|
|
91
|
+
return;
|
|
92
|
+
const handlers = {
|
|
93
|
+
loadstart: () => update({ status: 'loading', error: null, canPlay: false }), loadedmetadata: () => { sync(); update({ status: 'ready' }); }, canplay: () => { sync(); update({ canPlay: true, status: audio.paused ? 'paused' : 'playing' }); }, play: () => update({ status: 'playing' }), pause: () => update({ status: audio.ended ? 'ended' : 'paused' }), playing: () => update({ status: 'playing', canPlay: true }), waiting: () => update({ status: 'buffering' }), progress: sync, timeupdate: () => { sync(); onTimeChange?.(audio.currentTime); }, ratechange: sync, volumechange: () => { sync(); onVolumeChange?.(audio.volume); onMutedChange?.(audio.muted); }, ended: () => update({ status: 'ended', currentTime: audio.duration || audio.currentTime }), error: () => { const error = audio.error; update({ status: 'error', error }); onError?.(error); }, abort: () => update({ status: 'idle' }),
|
|
94
|
+
};
|
|
95
|
+
Object.entries(handlers).forEach(([name, handler]) => audio.addEventListener(name, handler));
|
|
96
|
+
return () => Object.entries(handlers).forEach(([name, handler]) => audio.removeEventListener(name, handler));
|
|
97
|
+
}, [onError, onMutedChange, onTimeChange, onVolumeChange, sync, src, update]);
|
|
98
|
+
const playLabel = state.status === 'playing' ? mergedLabels.pause : mergedLabels.play;
|
|
99
|
+
const defaultControls = _jsxs("div", { className: "inkra-audio-player__controls", role: "group", "aria-label": "Audio controls", children: [_jsx(Button, { "data-audio-player-control": true, "data-tooltip": playLabel, type: "button", size: "icon-sm", variant: "outline", "aria-label": playLabel, onClick: () => void togglePlay(), children: _jsx(PencilIcon, { paths: state.status === 'playing' ? pausePaths : playPaths, size: 20, sourceSize: 24 }) }), _jsx("div", { className: "inkra-audio-player__seek", children: _jsx(Slider, { "data-audio-player-control": true, min: 0, max: state.duration || 0, step: 0.01, value: [state.currentTime], onValueChange: values => seek(values[0] ?? 0), disabled: !state.duration, "aria-label": mergedLabels.seek, getValueLabel: value => labels?.time?.(value, state.duration) ?? `${formatTime(value)} / ${formatTime(state.duration)}`, size: "sm", variant: "inset" }) }), _jsx(PencilText, { as: "span", className: "inkra-audio-player__time", "aria-live": "off", children: labels?.time?.(state.currentTime, state.duration) ?? `${formatTime(state.currentTime)} / ${formatTime(state.duration)}` }), _jsxs("div", { className: "inkra-audio-player__settings", children: [_jsx(Button, { "data-audio-player-control": true, "data-tooltip": state.muted ? mergedLabels.unmute : mergedLabels.mute, type: "button", size: "icon-sm", variant: "ghost", "aria-label": state.muted ? mergedLabels.unmute : mergedLabels.mute, onClick: () => setMuted(!state.muted), children: _jsx(PencilIcon, { paths: state.muted ? mutePaths : volumePaths, size: 20, sourceSize: 24 }) }), _jsx("div", { className: "inkra-audio-player__volume", children: _jsx(Slider, { "data-audio-player-control": true, min: 0, max: 1, step: 0.01, value: [state.muted ? 0 : state.volume], onValueChange: values => { setMuted(false); setVolume(values[0] ?? 0); }, "aria-label": mergedLabels.volume, getValueLabel: value => `${Math.round(value * 100)}%`, size: "sm", variant: "inset" }) }), _jsxs(Select.Root, { value: String(state.playbackRate), onValueChange: value => setPlaybackRate(Number(value)), children: [_jsx(Select.Trigger, { "data-audio-player-control": true, "aria-label": mergedLabels.rate, fullWidth: false, size: "sm", children: _jsx(Select.Value, {}) }), _jsx(Select.Portal, { children: _jsx(Select.Content, { position: "popper", sideOffset: 4, children: _jsx(Select.Viewport, { children: rates.map(rate => _jsxs(Select.Item, { value: String(rate), children: [_jsx(Select.ItemText, { children: `${rate}x` }), _jsx(Select.ItemIndicator, {})] }, rate)) }) }) })] })] })] });
|
|
100
|
+
return _jsxs(PencilSurface, { ref: rootRef, ...props, className: ['inkra-audio-player', className].filter(Boolean).join(' '), variant: "paper", radius: 8, "data-status": state.status, "data-audio-player-id": id, children: [_jsx("audio", { ...audioProps, ref: audioRef, "aria-label": audioProps?.['aria-label'] ?? 'Audio', autoPlay: autoPlay, crossOrigin: crossOrigin, muted: state.muted, preload: preload, src: src }), state.status === 'blocked' && _jsx(PencilText, { as: "p", className: "inkra-audio-player__message", role: "status", children: mergedLabels.blocked }), state.status === 'error' && _jsx(PencilText, { as: "p", className: "inkra-audio-player__message", role: "alert", children: mergedLabels.error }), renderControls ? renderControls(controls) : defaultControls] });
|
|
101
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type ComponentPropsWithoutRef } from 'react';
|
|
2
|
+
import { type ButtonLinkProps } from '../Button.js';
|
|
3
|
+
export type BreadcrumbProps = ComponentPropsWithoutRef<'nav'>;
|
|
4
|
+
export type BreadcrumbListProps = ComponentPropsWithoutRef<'ol'>;
|
|
5
|
+
export type BreadcrumbItemProps = ComponentPropsWithoutRef<'li'>;
|
|
6
|
+
export type BreadcrumbLinkProps = ButtonLinkProps;
|
|
7
|
+
export type BreadcrumbPageProps = ComponentPropsWithoutRef<'span'>;
|
|
8
|
+
export type BreadcrumbSeparatorProps = ComponentPropsWithoutRef<'li'>;
|
|
9
|
+
export declare const Breadcrumb: {
|
|
10
|
+
Root: import("react").ForwardRefExoticComponent<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLElement>, HTMLElement>, "ref"> & import("react").RefAttributes<HTMLElement>>;
|
|
11
|
+
List: import("react").ForwardRefExoticComponent<Omit<import("react").DetailedHTMLProps<import("react").OlHTMLAttributes<HTMLOListElement>, HTMLOListElement>, "ref"> & import("react").RefAttributes<HTMLOListElement>>;
|
|
12
|
+
Item: import("react").ForwardRefExoticComponent<Omit<import("react").DetailedHTMLProps<import("react").LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>, "ref"> & import("react").RefAttributes<HTMLLIElement>>;
|
|
13
|
+
Link: import("react").ForwardRefExoticComponent<ButtonLinkProps & import("react").RefAttributes<HTMLAnchorElement>>;
|
|
14
|
+
Page: import("react").ForwardRefExoticComponent<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "ref"> & import("react").RefAttributes<HTMLSpanElement>>;
|
|
15
|
+
Separator: import("react").ForwardRefExoticComponent<Omit<import("react").DetailedHTMLProps<import("react").LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>, "ref"> & import("react").RefAttributes<HTMLLIElement>>;
|
|
16
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef } from 'react';
|
|
3
|
+
import { ButtonLink } from '../Button.js';
|
|
4
|
+
import { PencilText } from '../PencilText.js';
|
|
5
|
+
const classes = (...values) => values.filter(Boolean).join(' ');
|
|
6
|
+
const copy = (children) => typeof children === 'string' ? _jsx(PencilText, { children: children }) : children;
|
|
7
|
+
const Root = forwardRef(function BreadcrumbRoot({ className, 'aria-label': label = 'Breadcrumb', ...props }, ref) {
|
|
8
|
+
return _jsx("nav", { ...props, ref: ref, "aria-label": label, className: classes('inkra-breadcrumb', className) });
|
|
9
|
+
});
|
|
10
|
+
const List = forwardRef(function BreadcrumbList({ className, ...props }, ref) {
|
|
11
|
+
return _jsx("ol", { ...props, ref: ref, className: classes('inkra-breadcrumb__list', className) });
|
|
12
|
+
});
|
|
13
|
+
const Item = forwardRef(function BreadcrumbItem({ className, ...props }, ref) {
|
|
14
|
+
return _jsx("li", { ...props, ref: ref, className: classes('inkra-breadcrumb__item', className) });
|
|
15
|
+
});
|
|
16
|
+
const Link = forwardRef(function BreadcrumbLink({ className, variant = 'ghost', ...props }, ref) {
|
|
17
|
+
return _jsx(ButtonLink, { ...props, ref: ref, variant: variant, className: classes('inkra-breadcrumb__link', className) });
|
|
18
|
+
});
|
|
19
|
+
const Page = forwardRef(function BreadcrumbPage({ className, children, ...props }, ref) {
|
|
20
|
+
return _jsx("span", { ...props, ref: ref, "aria-current": "page", className: classes('inkra-breadcrumb__page', className), children: copy(children) });
|
|
21
|
+
});
|
|
22
|
+
const Separator = forwardRef(function BreadcrumbSeparator({ className, children = '/', ...props }, ref) {
|
|
23
|
+
return _jsx("li", { ...props, ref: ref, role: "presentation", "aria-hidden": "true", className: classes('inkra-breadcrumb__separator', className), children: copy(children) });
|
|
24
|
+
});
|
|
25
|
+
export const Breadcrumb = { Root, List, Item, Link, Page, Separator };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type DayPickerProps } from 'react-day-picker';
|
|
3
|
+
export interface CalendarEvent {
|
|
4
|
+
date: Date;
|
|
5
|
+
label: string;
|
|
6
|
+
id?: string;
|
|
7
|
+
color?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
}
|
|
10
|
+
export type CalendarProps = DayPickerProps & {
|
|
11
|
+
events?: readonly CalendarEvent[];
|
|
12
|
+
eventIndicator?: 'count' | 'dot' | 'none';
|
|
13
|
+
appearance?: 'itinerary' | 'picker';
|
|
14
|
+
onEventSelect?: (event: CalendarEvent) => void;
|
|
15
|
+
renderDayAccessory?: (date: Date) => ReactNode;
|
|
16
|
+
displayRange?: {
|
|
17
|
+
from: Date;
|
|
18
|
+
to: Date;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
export declare function Calendar({ events, eventIndicator, appearance, onEventSelect, renderDayAccessory, displayRange, className, components, showOutsideDays, ...props }: CalendarProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext } from 'react';
|
|
3
|
+
import { CaptionLabel, Day, DayButton, DayPicker, Weekday } from 'react-day-picker';
|
|
4
|
+
import { Button } from '../Button.js';
|
|
5
|
+
import { PencilIcon } from '../PencilIcon.js';
|
|
6
|
+
import { PencilSurface } from '../PencilSurface.js';
|
|
7
|
+
import { PencilText } from '../PencilText.js';
|
|
8
|
+
const EventContext = createContext({ events: [], indicator: 'count', appearance: 'itinerary' });
|
|
9
|
+
const matches = (event, date) => event.date.getFullYear() === date.getFullYear()
|
|
10
|
+
&& event.date.getMonth() === date.getMonth() && event.date.getDate() === date.getDate();
|
|
11
|
+
function CalendarCaption({ children, ...props }) {
|
|
12
|
+
return _jsx(CaptionLabel, { ...props, children: typeof children === 'string' ? _jsx(PencilText, { children: children }) : children });
|
|
13
|
+
}
|
|
14
|
+
function CalendarWeekday({ children, ...props }) {
|
|
15
|
+
return _jsx(Weekday, { ...props, children: typeof children === 'string' ? _jsx(PencilText, { children: children }) : children });
|
|
16
|
+
}
|
|
17
|
+
function CalendarMonthButton({ disabled, ...props }) {
|
|
18
|
+
return _jsx(Button, { ...props, size: "icon", variant: "ghost", disabled: disabled || props['aria-disabled'] === true || props['aria-disabled'] === 'true' });
|
|
19
|
+
}
|
|
20
|
+
// Lucide chevrons retain their geometry and use the shared pencil renderer.
|
|
21
|
+
const chevrons = { left: 'm15 18-6-6 6-6', right: 'm9 18 6-6-6-6', up: 'm18 15-6-6-6 6', down: 'm6 9 6 6 6-6' };
|
|
22
|
+
function CalendarChevron({ className, orientation = 'left', size = 28 }) {
|
|
23
|
+
return _jsx(PencilIcon, { className: className, paths: [{ d: chevrons[orientation] }], sourceSize: 24, size: size });
|
|
24
|
+
}
|
|
25
|
+
// The trip calendar uses a full-cell date target with sibling event buttons.
|
|
26
|
+
function CalendarCell({ children, day, modifiers, ...props }) {
|
|
27
|
+
const { events, appearance, onEventSelect, renderDayAccessory } = useContext(EventContext);
|
|
28
|
+
if (appearance === 'picker')
|
|
29
|
+
return _jsx(Day, { ...props, day: day, modifiers: modifiers, children: children });
|
|
30
|
+
const entries = events.filter(event => matches(event, day.date));
|
|
31
|
+
return _jsx(Day, { ...props, day: day, modifiers: modifiers, children: _jsxs("div", { className: "inkra-calendar__day-card", children: [_jsx(PencilSurface, { variant: modifiers.selected ? 'action' : 'quiet', radius: 8, className: "inkra-calendar__day-paper", "aria-hidden": "true", seed: `day-${day.date.toISOString()}` }), children, renderDayAccessory && _jsx("div", { className: "inkra-calendar__accessory", children: renderDayAccessory(day.date) }), _jsx("div", { className: "inkra-calendar__events", children: entries.map((event, index) => _jsx(PencilSurface, { as: "button", variant: "quiet", radius: 3, className: "inkra-calendar__event-card", disabled: modifiers.disabled || !onEventSelect, style: event.color ? { '--inkra-accent-soft': event.color } : undefined, "aria-label": `${event.label}${event.description ? `, ${event.description}` : ''}`, title: event.description ?? event.label, onClick: click => { click.stopPropagation(); onEventSelect?.(event); }, children: _jsx(PencilText, { ellipsis: true, children: event.label }) }, event.id ?? index)) })] }) });
|
|
32
|
+
}
|
|
33
|
+
function CalendarDay({ children, modifiers, day, ...props }) {
|
|
34
|
+
const { events: allEvents, indicator, appearance } = useContext(EventContext);
|
|
35
|
+
const events = allEvents.filter(event => matches(event, day.date));
|
|
36
|
+
return _jsxs(DayButton, { ...props, day: day, modifiers: modifiers, "aria-label": [props['aria-label'], ...events.map(event => event.label)].filter(Boolean).join(', '), children: [modifiers.selected && appearance === 'picker' && _jsx(PencilSurface, { variant: "selection", className: "inkra-calendar__selection", "aria-hidden": "true" }), _jsx(PencilText, { children: String(children) }), appearance === 'picker' && events.length > 0 && indicator !== 'none' && _jsx("span", { className: "inkra-calendar__event", "data-indicator": indicator, "aria-hidden": "true", children: indicator === 'count' ? events.length : '\u2022' })] });
|
|
37
|
+
}
|
|
38
|
+
export function Calendar({ events = [], eventIndicator = 'count', appearance = 'itinerary', onEventSelect, renderDayAccessory, displayRange, className, components, showOutsideDays = true, ...props }) {
|
|
39
|
+
const hidden = displayRange ? [...(Array.isArray(props.hidden) ? props.hidden : props.hidden ? [props.hidden] : []), { before: displayRange.from }, { after: displayRange.to }] : props.hidden;
|
|
40
|
+
return _jsx(EventContext.Provider, { value: { events, indicator: eventIndicator, appearance, onEventSelect, renderDayAccessory }, children: _jsx("div", { className: "inkra-calendar-scroll", "data-appearance": appearance, children: _jsx(DayPicker, { ...props, hidden: hidden, "data-appearance": appearance, "data-week-numbers": props.showWeekNumber || undefined, showOutsideDays: showOutsideDays, className: ['inkra-calendar', className].filter(Boolean).join(' '), components: { CaptionLabel: CalendarCaption, Weekday: CalendarWeekday, Chevron: CalendarChevron, PreviousMonthButton: CalendarMonthButton, NextMonthButton: CalendarMonthButton, Day: CalendarCell, DayButton: CalendarDay, ...components } }) }) });
|
|
41
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
export interface CarouselVisibleItems {
|
|
3
|
+
base?: number;
|
|
4
|
+
sm?: number;
|
|
5
|
+
md?: number;
|
|
6
|
+
lg?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface CarouselAutoplayOptions {
|
|
9
|
+
delay?: number;
|
|
10
|
+
pauseOnHover?: boolean;
|
|
11
|
+
pauseOnFocus?: boolean;
|
|
12
|
+
stopOnInteraction?: boolean;
|
|
13
|
+
respectReducedMotion?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface CarouselLabels {
|
|
16
|
+
previous?: string;
|
|
17
|
+
next?: string;
|
|
18
|
+
pause?: string;
|
|
19
|
+
play?: string;
|
|
20
|
+
pages?: string;
|
|
21
|
+
empty?: string;
|
|
22
|
+
slide?: (index: number, count: number) => string;
|
|
23
|
+
page?: (index: number, count: number) => string;
|
|
24
|
+
status?: (index: number, count: number) => string;
|
|
25
|
+
}
|
|
26
|
+
export interface CarouselProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'onChange'> {
|
|
27
|
+
children?: ReactNode;
|
|
28
|
+
currentIndex?: number;
|
|
29
|
+
defaultIndex?: number;
|
|
30
|
+
onCurrentIndexChange?: (index: number) => void;
|
|
31
|
+
visibleItems?: number | CarouselVisibleItems;
|
|
32
|
+
gap?: number;
|
|
33
|
+
align?: 'start' | 'center' | 'end';
|
|
34
|
+
loop?: boolean;
|
|
35
|
+
dragFree?: boolean;
|
|
36
|
+
disabled?: boolean;
|
|
37
|
+
controls?: boolean;
|
|
38
|
+
pagination?: boolean;
|
|
39
|
+
autoplay?: boolean | CarouselAutoplayOptions;
|
|
40
|
+
emptyState?: ReactNode;
|
|
41
|
+
labels?: CarouselLabels;
|
|
42
|
+
}
|
|
43
|
+
export interface CarouselRef {
|
|
44
|
+
root: HTMLDivElement | null;
|
|
45
|
+
previous: () => void;
|
|
46
|
+
next: () => void;
|
|
47
|
+
scrollTo: (index: number) => void;
|
|
48
|
+
play: () => void;
|
|
49
|
+
pause: () => void;
|
|
50
|
+
}
|
|
51
|
+
export declare const Carousel: import("react").ForwardRefExoticComponent<CarouselProps & import("react").RefAttributes<CarouselRef>>;
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import Autoplay from 'embla-carousel-autoplay';
|
|
3
|
+
import useEmblaCarousel from 'embla-carousel-react';
|
|
4
|
+
import { Children, forwardRef, useCallback, useEffect, useId, useImperativeHandle, useMemo, useRef, useState, } from 'react';
|
|
5
|
+
import { Button } from '../Button.js';
|
|
6
|
+
import { PencilIcon } from '../PencilIcon.js';
|
|
7
|
+
import { PencilSurface } from '../PencilSurface.js';
|
|
8
|
+
const previousPaths = [{ d: 'm15 18-6-6 6-6' }];
|
|
9
|
+
const nextPaths = [{ d: 'm9 18 6-6-6-6' }];
|
|
10
|
+
const pausePaths = [{ d: 'M8 5v14M16 5v14' }];
|
|
11
|
+
const playPaths = [{ d: 'm7 4 12 8-12 8Z' }];
|
|
12
|
+
function finiteInteger(value, fallback, minimum = 1, maximum = 12) {
|
|
13
|
+
return Number.isFinite(value) ? Math.min(maximum, Math.max(minimum, Math.round(value))) : fallback;
|
|
14
|
+
}
|
|
15
|
+
function clampIndex(value, count) {
|
|
16
|
+
if (count === 0)
|
|
17
|
+
return 0;
|
|
18
|
+
const index = Number.isFinite(value) ? Math.round(value) : 0;
|
|
19
|
+
return Math.min(count - 1, Math.max(0, index));
|
|
20
|
+
}
|
|
21
|
+
function visibleItemValues(value) {
|
|
22
|
+
if (typeof value === 'number') {
|
|
23
|
+
const fixed = finiteInteger(value, 1);
|
|
24
|
+
return { base: fixed, sm: fixed, md: fixed, lg: fixed };
|
|
25
|
+
}
|
|
26
|
+
const base = finiteInteger(value?.base, 1);
|
|
27
|
+
const sm = finiteInteger(value?.sm, base);
|
|
28
|
+
const md = finiteInteger(value?.md, sm);
|
|
29
|
+
const lg = finiteInteger(value?.lg, md);
|
|
30
|
+
return { base, sm, md, lg };
|
|
31
|
+
}
|
|
32
|
+
function useReducedMotion() {
|
|
33
|
+
const [reduced, setReduced] = useState(false);
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
const query = window.matchMedia('(prefers-reduced-motion: reduce)');
|
|
36
|
+
const update = () => setReduced(query.matches);
|
|
37
|
+
update();
|
|
38
|
+
query.addEventListener('change', update);
|
|
39
|
+
return () => query.removeEventListener('change', update);
|
|
40
|
+
}, []);
|
|
41
|
+
return reduced;
|
|
42
|
+
}
|
|
43
|
+
export const Carousel = forwardRef(function Carousel({ 'aria-label': ariaLabel = 'Carousel', align = 'start', autoplay = false, children, className, controls = true, currentIndex, defaultIndex = 0, dir, disabled = false, dragFree = false, emptyState, gap = 16, labels, loop = false, onBlurCapture, onCurrentIndexChange, onFocusCapture, onKeyDown, onPointerEnter, onPointerLeave, pagination = true, role = 'region', style, tabIndex, visibleItems = { base: 1, sm: 2, md: 3 }, ...props }, forwardedRef) {
|
|
44
|
+
const generatedId = useId();
|
|
45
|
+
const rootRef = useRef(null);
|
|
46
|
+
const slideItems = Children.toArray(children);
|
|
47
|
+
const count = slideItems.length;
|
|
48
|
+
const controlled = currentIndex !== undefined;
|
|
49
|
+
const initialIndex = clampIndex(controlled ? currentIndex : defaultIndex, count);
|
|
50
|
+
const [uncontrolledIndex, setUncontrolledIndex] = useState(initialIndex);
|
|
51
|
+
const [selectedIndex, setSelectedIndex] = useState(initialIndex);
|
|
52
|
+
const [scrollSnaps, setScrollSnaps] = useState([]);
|
|
53
|
+
const [canScrollPrevious, setCanScrollPrevious] = useState(false);
|
|
54
|
+
const [canScrollNext, setCanScrollNext] = useState(false);
|
|
55
|
+
const [inferredDir, setInferredDir] = useState('ltr');
|
|
56
|
+
const [playing, setPlaying] = useState(false);
|
|
57
|
+
const hoveredRef = useRef(false);
|
|
58
|
+
const focusedRef = useRef(false);
|
|
59
|
+
const userPausedRef = useRef(false);
|
|
60
|
+
const autoplayControlPointerRef = useRef(false);
|
|
61
|
+
const reducedMotion = useReducedMotion();
|
|
62
|
+
const selectionCount = scrollSnaps.length || count;
|
|
63
|
+
const activeIndex = clampIndex(controlled ? currentIndex : uncontrolledIndex, selectionCount);
|
|
64
|
+
const resolvedDir = dir === 'rtl' ? 'rtl' : dir === 'ltr' ? 'ltr' : inferredDir;
|
|
65
|
+
const autoplayOptions = typeof autoplay === 'object' ? autoplay : {};
|
|
66
|
+
const stopOnInteraction = autoplayOptions.stopOnInteraction ?? true;
|
|
67
|
+
const autoplayEnabled = Boolean(autoplay) && count > 1 && !disabled;
|
|
68
|
+
const autoplayAllowed = autoplayEnabled && !(autoplayOptions.respectReducedMotion !== false && reducedMotion);
|
|
69
|
+
const requestedVisible = visibleItemValues(visibleItems);
|
|
70
|
+
const visible = count > 0 ? {
|
|
71
|
+
base: Math.min(count, requestedVisible.base),
|
|
72
|
+
sm: Math.min(count, requestedVisible.sm),
|
|
73
|
+
md: Math.min(count, requestedVisible.md),
|
|
74
|
+
lg: Math.min(count, requestedVisible.lg),
|
|
75
|
+
} : requestedVisible;
|
|
76
|
+
const resolvedGap = Number.isFinite(gap) ? Math.max(0, gap) : 16;
|
|
77
|
+
const autoplayActive = Boolean(autoplay);
|
|
78
|
+
const autoplayPlugin = useMemo(() => Autoplay({
|
|
79
|
+
active: autoplayActive,
|
|
80
|
+
delay: Number.isFinite(autoplayOptions.delay) ? Math.max(1000, autoplayOptions.delay) : 5000,
|
|
81
|
+
playOnInit: false,
|
|
82
|
+
stopOnFocusIn: false,
|
|
83
|
+
stopOnInteraction,
|
|
84
|
+
stopOnMouseEnter: false,
|
|
85
|
+
}), [autoplayActive, autoplayOptions.delay, stopOnInteraction]);
|
|
86
|
+
const plugins = useMemo(() => autoplayActive ? [autoplayPlugin] : [], [autoplayActive, autoplayPlugin]);
|
|
87
|
+
const [viewportRef, emblaApi] = useEmblaCarousel({
|
|
88
|
+
active: !disabled && count > 0,
|
|
89
|
+
align,
|
|
90
|
+
containScroll: 'trimSnaps',
|
|
91
|
+
direction: resolvedDir,
|
|
92
|
+
dragFree,
|
|
93
|
+
loop: loop && count > 1,
|
|
94
|
+
slidesToScroll: 1,
|
|
95
|
+
startIndex: initialIndex,
|
|
96
|
+
watchResize: true,
|
|
97
|
+
watchSlides: true,
|
|
98
|
+
}, plugins);
|
|
99
|
+
const refresh = useCallback(() => {
|
|
100
|
+
if (!emblaApi)
|
|
101
|
+
return;
|
|
102
|
+
const nextIndex = clampIndex(emblaApi.selectedScrollSnap(), count);
|
|
103
|
+
setSelectedIndex(nextIndex);
|
|
104
|
+
setScrollSnaps(emblaApi.scrollSnapList());
|
|
105
|
+
setCanScrollPrevious(emblaApi.canScrollPrev());
|
|
106
|
+
setCanScrollNext(emblaApi.canScrollNext());
|
|
107
|
+
}, [count, emblaApi]);
|
|
108
|
+
const select = useCallback(() => {
|
|
109
|
+
if (!emblaApi)
|
|
110
|
+
return;
|
|
111
|
+
const nextIndex = clampIndex(emblaApi.selectedScrollSnap(), count);
|
|
112
|
+
setSelectedIndex(nextIndex);
|
|
113
|
+
setCanScrollPrevious(emblaApi.canScrollPrev());
|
|
114
|
+
setCanScrollNext(emblaApi.canScrollNext());
|
|
115
|
+
if (!controlled)
|
|
116
|
+
setUncontrolledIndex(nextIndex);
|
|
117
|
+
if (nextIndex !== activeIndex)
|
|
118
|
+
onCurrentIndexChange?.(nextIndex);
|
|
119
|
+
}, [activeIndex, controlled, count, emblaApi, onCurrentIndexChange]);
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
if (!emblaApi)
|
|
122
|
+
return undefined;
|
|
123
|
+
refresh();
|
|
124
|
+
emblaApi.on('reInit', refresh);
|
|
125
|
+
emblaApi.on('select', select);
|
|
126
|
+
return () => {
|
|
127
|
+
emblaApi.off('reInit', refresh);
|
|
128
|
+
emblaApi.off('select', select);
|
|
129
|
+
};
|
|
130
|
+
}, [emblaApi, refresh, select]);
|
|
131
|
+
useEffect(() => {
|
|
132
|
+
if (controlled || count === 0)
|
|
133
|
+
return;
|
|
134
|
+
setUncontrolledIndex(index => clampIndex(index, count));
|
|
135
|
+
}, [controlled, count]);
|
|
136
|
+
useEffect(() => {
|
|
137
|
+
if (!emblaApi || count === 0 || selectedIndex === activeIndex)
|
|
138
|
+
return;
|
|
139
|
+
emblaApi.scrollTo(activeIndex, reducedMotion);
|
|
140
|
+
}, [activeIndex, count, emblaApi, reducedMotion, selectedIndex]);
|
|
141
|
+
useEffect(() => {
|
|
142
|
+
const root = rootRef.current;
|
|
143
|
+
if (dir || !root)
|
|
144
|
+
return undefined;
|
|
145
|
+
const update = () => setInferredDir(getComputedStyle(root).direction === 'rtl' ? 'rtl' : 'ltr');
|
|
146
|
+
update();
|
|
147
|
+
const observer = new MutationObserver(update);
|
|
148
|
+
for (let node = root; node; node = node.parentElement) {
|
|
149
|
+
observer.observe(node, { attributes: true, attributeFilter: ['class', 'dir', 'style'] });
|
|
150
|
+
}
|
|
151
|
+
return () => observer.disconnect();
|
|
152
|
+
}, [dir]);
|
|
153
|
+
const pause = useCallback(() => {
|
|
154
|
+
autoplayPlugin.stop();
|
|
155
|
+
setPlaying(false);
|
|
156
|
+
}, [autoplayPlugin]);
|
|
157
|
+
const play = useCallback((explicit = false) => {
|
|
158
|
+
const blockedByHover = autoplayOptions.pauseOnHover !== false && hoveredRef.current;
|
|
159
|
+
const blockedByFocus = autoplayOptions.pauseOnFocus !== false && focusedRef.current;
|
|
160
|
+
if (!autoplayAllowed || (!explicit && (blockedByHover || blockedByFocus)))
|
|
161
|
+
return;
|
|
162
|
+
autoplayPlugin.play();
|
|
163
|
+
setPlaying(true);
|
|
164
|
+
}, [autoplayAllowed, autoplayOptions.pauseOnFocus, autoplayOptions.pauseOnHover, autoplayPlugin]);
|
|
165
|
+
useEffect(() => {
|
|
166
|
+
if (!emblaApi || !autoplayEnabled) {
|
|
167
|
+
autoplayPlugin.stop();
|
|
168
|
+
setPlaying(false);
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
const updatePlaying = () => setPlaying(autoplayPlugin.isPlaying());
|
|
172
|
+
const stopForInteraction = () => {
|
|
173
|
+
if (!stopOnInteraction)
|
|
174
|
+
return;
|
|
175
|
+
userPausedRef.current = true;
|
|
176
|
+
pause();
|
|
177
|
+
};
|
|
178
|
+
emblaApi.on('autoplay:play', updatePlaying);
|
|
179
|
+
emblaApi.on('autoplay:stop', updatePlaying);
|
|
180
|
+
emblaApi.on('pointerDown', stopForInteraction);
|
|
181
|
+
if (autoplayAllowed && !userPausedRef.current)
|
|
182
|
+
play();
|
|
183
|
+
else
|
|
184
|
+
pause();
|
|
185
|
+
return () => {
|
|
186
|
+
emblaApi.off('autoplay:play', updatePlaying);
|
|
187
|
+
emblaApi.off('autoplay:stop', updatePlaying);
|
|
188
|
+
emblaApi.off('pointerDown', stopForInteraction);
|
|
189
|
+
autoplayPlugin.stop();
|
|
190
|
+
};
|
|
191
|
+
}, [autoplayAllowed, autoplayEnabled, autoplayPlugin, emblaApi, pause, play, stopOnInteraction]);
|
|
192
|
+
const stopForControlInteraction = useCallback(() => {
|
|
193
|
+
if (!autoplayEnabled || !stopOnInteraction)
|
|
194
|
+
return;
|
|
195
|
+
userPausedRef.current = true;
|
|
196
|
+
pause();
|
|
197
|
+
}, [autoplayEnabled, pause, stopOnInteraction]);
|
|
198
|
+
const previous = useCallback(() => {
|
|
199
|
+
stopForControlInteraction();
|
|
200
|
+
emblaApi?.scrollPrev(reducedMotion);
|
|
201
|
+
}, [emblaApi, reducedMotion, stopForControlInteraction]);
|
|
202
|
+
const next = useCallback(() => {
|
|
203
|
+
stopForControlInteraction();
|
|
204
|
+
emblaApi?.scrollNext(reducedMotion);
|
|
205
|
+
}, [emblaApi, reducedMotion, stopForControlInteraction]);
|
|
206
|
+
const scrollTo = useCallback((index) => {
|
|
207
|
+
stopForControlInteraction();
|
|
208
|
+
emblaApi?.scrollTo(clampIndex(index, selectionCount), reducedMotion);
|
|
209
|
+
}, [emblaApi, reducedMotion, selectionCount, stopForControlInteraction]);
|
|
210
|
+
useImperativeHandle(forwardedRef, () => ({ root: rootRef.current, previous, next, scrollTo, play: () => play(true), pause }), [next, pause, play, previous, scrollTo]);
|
|
211
|
+
const resumeAfterTransientPause = () => {
|
|
212
|
+
const blockedByHover = autoplayOptions.pauseOnHover !== false && hoveredRef.current;
|
|
213
|
+
const blockedByFocus = autoplayOptions.pauseOnFocus !== false && focusedRef.current;
|
|
214
|
+
if (!userPausedRef.current && autoplayAllowed && !blockedByHover && !blockedByFocus)
|
|
215
|
+
play();
|
|
216
|
+
};
|
|
217
|
+
const handleKeyDown = (event) => {
|
|
218
|
+
onKeyDown?.(event);
|
|
219
|
+
if (event.defaultPrevented || event.target !== event.currentTarget || disabled || count < 2)
|
|
220
|
+
return;
|
|
221
|
+
if (event.key === 'ArrowLeft') {
|
|
222
|
+
event.preventDefault();
|
|
223
|
+
resolvedDir === 'rtl' ? next() : previous();
|
|
224
|
+
}
|
|
225
|
+
else if (event.key === 'ArrowRight') {
|
|
226
|
+
event.preventDefault();
|
|
227
|
+
resolvedDir === 'rtl' ? previous() : next();
|
|
228
|
+
}
|
|
229
|
+
else if (event.key === 'Home') {
|
|
230
|
+
event.preventDefault();
|
|
231
|
+
scrollTo(0);
|
|
232
|
+
}
|
|
233
|
+
else if (event.key === 'End') {
|
|
234
|
+
event.preventDefault();
|
|
235
|
+
scrollTo(count - 1);
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
const handlePointerEnter = (event) => {
|
|
239
|
+
onPointerEnter?.(event);
|
|
240
|
+
hoveredRef.current = true;
|
|
241
|
+
if (autoplayOptions.pauseOnHover !== false)
|
|
242
|
+
pause();
|
|
243
|
+
};
|
|
244
|
+
const handlePointerLeave = (event) => {
|
|
245
|
+
onPointerLeave?.(event);
|
|
246
|
+
hoveredRef.current = false;
|
|
247
|
+
resumeAfterTransientPause();
|
|
248
|
+
};
|
|
249
|
+
const handleFocus = (event) => {
|
|
250
|
+
onFocusCapture?.(event);
|
|
251
|
+
focusedRef.current = true;
|
|
252
|
+
if (autoplayControlPointerRef.current) {
|
|
253
|
+
autoplayControlPointerRef.current = false;
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (autoplayOptions.pauseOnFocus !== false)
|
|
257
|
+
pause();
|
|
258
|
+
};
|
|
259
|
+
const handleBlur = (event) => {
|
|
260
|
+
onBlurCapture?.(event);
|
|
261
|
+
if (event.currentTarget.contains(event.relatedTarget))
|
|
262
|
+
return;
|
|
263
|
+
focusedRef.current = false;
|
|
264
|
+
resumeAfterTransientPause();
|
|
265
|
+
};
|
|
266
|
+
const toggleAutoplay = () => {
|
|
267
|
+
if (playing) {
|
|
268
|
+
userPausedRef.current = true;
|
|
269
|
+
pause();
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
userPausedRef.current = false;
|
|
273
|
+
play(true);
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
const rootStyle = {
|
|
277
|
+
...style,
|
|
278
|
+
'--inkra-carousel-gap': `${resolvedGap}px`,
|
|
279
|
+
'--inkra-carousel-visible-base': visible.base,
|
|
280
|
+
'--inkra-carousel-visible-sm': visible.sm,
|
|
281
|
+
'--inkra-carousel-visible-md': visible.md,
|
|
282
|
+
'--inkra-carousel-visible-lg': visible.lg,
|
|
283
|
+
};
|
|
284
|
+
const slideLabel = labels?.slide ?? ((index, total) => `Slide ${index} of ${total}`);
|
|
285
|
+
const pageLabel = labels?.page ?? ((index, total) => `Go to page ${index} of ${total}`);
|
|
286
|
+
const statusLabel = labels?.status ?? ((index, total) => `Slide ${index} of ${total}`);
|
|
287
|
+
const showNavigation = controls && count > 1;
|
|
288
|
+
const showPagination = pagination && scrollSnaps.length > 1;
|
|
289
|
+
return _jsxs("div", { ...props, "aria-label": ariaLabel, "aria-roledescription": "carousel", className: ['inkra-carousel', className].filter(Boolean).join(' '), "data-autoplay": autoplayEnabled || undefined, "data-disabled": disabled || undefined, "data-playing": playing || undefined, dir: resolvedDir, onBlurCapture: handleBlur, onFocusCapture: handleFocus, onKeyDown: handleKeyDown, onPointerEnter: handlePointerEnter, onPointerLeave: handlePointerLeave, ref: rootRef, role: role, style: rootStyle, tabIndex: !disabled && count > 1 ? (tabIndex ?? 0) : tabIndex, children: [count === 0 ? _jsx("div", { className: "inkra-carousel__empty", children: emptyState ?? labels?.empty ?? 'No items' }) : _jsxs(_Fragment, { children: [_jsx("div", { className: "inkra-carousel__viewport", ref: viewportRef, children: _jsx("div", { className: "inkra-carousel__container", children: slideItems.map((item, index) => _jsx("div", { "aria-label": slideLabel(index + 1, count), "aria-roledescription": "slide", className: "inkra-carousel__slide", "data-current": index === activeIndex || undefined, role: "group", children: item }, item.key ?? index)) }) }), (showNavigation || showPagination || autoplayEnabled) && _jsxs("div", { className: "inkra-carousel__toolbar", children: [showNavigation && _jsxs("div", { className: "inkra-carousel__navigation", children: [_jsx(Button, { "aria-label": labels?.previous ?? 'Previous slide', disabled: disabled || !canScrollPrevious, onClick: previous, size: "icon", variant: "outline", children: _jsx(PencilIcon, { decorative: true, paths: resolvedDir === 'rtl' ? nextPaths : previousPaths, seed: `${generatedId}-previous`, size: 20, sourceSize: 24 }) }), _jsx(Button, { "aria-label": labels?.next ?? 'Next slide', disabled: disabled || !canScrollNext, onClick: next, size: "icon", variant: "outline", children: _jsx(PencilIcon, { decorative: true, paths: resolvedDir === 'rtl' ? previousPaths : nextPaths, seed: `${generatedId}-next`, size: 20, sourceSize: 24 }) })] }), showPagination && _jsx("div", { "aria-label": labels?.pages ?? 'Carousel pages', className: "inkra-carousel__pagination", role: "group", children: scrollSnaps.map((_, index) => _jsx("button", { "aria-current": index === selectedIndex ? 'true' : undefined, "aria-label": pageLabel(index + 1, scrollSnaps.length), className: "inkra-carousel__page", disabled: disabled, onClick: () => scrollTo(index), type: "button", children: _jsx(PencilSurface, { as: "span", "aria-hidden": "true", className: "inkra-carousel__page-mark", radius: 6, seed: `${generatedId}-page-${index}`, variant: index === selectedIndex ? 'selection' : 'outline' }) }, index)) }), autoplayEnabled && _jsx(Button, { "aria-label": playing ? (labels?.pause ?? 'Pause autoplay') : (labels?.play ?? 'Play autoplay'), disabled: !autoplayAllowed, onClick: toggleAutoplay, onPointerDown: () => { autoplayControlPointerRef.current = true; }, size: "icon", variant: "ghost", children: _jsx(PencilIcon, { decorative: true, paths: playing ? pausePaths : playPaths, seed: `${generatedId}-autoplay`, size: 20, sourceSize: 24 }) })] })] }), count > 0 && _jsx("span", { "aria-atomic": "true", "aria-live": playing ? 'off' : 'polite', className: "inkra-carousel__status", children: statusLabel(activeIndex + 1, count) })] });
|
|
290
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { FieldsetHTMLAttributes, ImgHTMLAttributes, InputHTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
export interface ChoiceGridImage extends Omit<ImgHTMLAttributes<HTMLImageElement>, 'alt' | 'children'> {
|
|
3
|
+
/** Choice images are decorative; the option label supplies the accessible name. */
|
|
4
|
+
alt?: '';
|
|
5
|
+
}
|
|
6
|
+
export type ChoiceGridColumns = 1 | 2 | 3 | 4;
|
|
7
|
+
interface ChoiceGridCommonProps extends Omit<FieldsetHTMLAttributes<HTMLFieldSetElement>, 'defaultValue' | 'onChange'> {
|
|
8
|
+
name: string;
|
|
9
|
+
label: ReactNode;
|
|
10
|
+
description?: ReactNode;
|
|
11
|
+
error?: ReactNode;
|
|
12
|
+
columns?: ChoiceGridColumns;
|
|
13
|
+
minItemWidth?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface SingleChoiceGridProps extends ChoiceGridCommonProps {
|
|
16
|
+
type?: 'single';
|
|
17
|
+
value?: string;
|
|
18
|
+
defaultValue?: string;
|
|
19
|
+
onValueChange?: (value: string) => void;
|
|
20
|
+
}
|
|
21
|
+
export interface MultipleChoiceGridProps extends ChoiceGridCommonProps {
|
|
22
|
+
type: 'multiple';
|
|
23
|
+
values?: readonly string[];
|
|
24
|
+
defaultValues?: readonly string[];
|
|
25
|
+
onValuesChange?: (values: string[]) => void;
|
|
26
|
+
}
|
|
27
|
+
export type ChoiceGridProps = SingleChoiceGridProps | MultipleChoiceGridProps;
|
|
28
|
+
export interface ChoiceGridOptionProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'checked' | 'children' | 'defaultChecked' | 'name' | 'type'> {
|
|
29
|
+
value: string;
|
|
30
|
+
label: ReactNode;
|
|
31
|
+
description?: ReactNode;
|
|
32
|
+
icon?: ReactNode;
|
|
33
|
+
image?: ChoiceGridImage;
|
|
34
|
+
seed?: string | number;
|
|
35
|
+
}
|
|
36
|
+
export declare const ChoiceGrid: import("react").ForwardRefExoticComponent<ChoiceGridProps & import("react").RefAttributes<HTMLFieldSetElement>>;
|
|
37
|
+
export declare const ChoiceGridOption: import("react").ForwardRefExoticComponent<ChoiceGridOptionProps & import("react").RefAttributes<HTMLInputElement>>;
|
|
38
|
+
export {};
|