@dxos/react-ui 0.9.1-main.c7dcc2e112 → 0.9.1-staging.ee54ba693a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/{chunk-HFJYTGHU.mjs → chunk-CMHHVHX3.mjs} +4 -1
- package/dist/lib/browser/chunk-CMHHVHX3.mjs.map +7 -0
- package/dist/lib/browser/{chunk-F3PLXTDA.mjs → chunk-GTHV4WYK.mjs} +123 -232
- package/dist/lib/browser/chunk-GTHV4WYK.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +1069 -976
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +5 -5
- package/dist/lib/browser/testing/index.mjs.map +2 -2
- package/dist/lib/browser/translations.mjs +1 -1
- package/dist/lib/node-esm/{chunk-LLVUYLAM.mjs → chunk-3VSKWNCQ.mjs} +123 -232
- package/dist/lib/node-esm/chunk-3VSKWNCQ.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-OJYUPUCY.mjs → chunk-P4KFQGJI.mjs} +4 -1
- package/dist/lib/node-esm/chunk-P4KFQGJI.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +1069 -976
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +5 -5
- package/dist/lib/node-esm/testing/index.mjs.map +2 -2
- package/dist/lib/node-esm/translations.mjs +1 -1
- package/dist/types/src/components/Avatars/Avatar.d.ts +1 -1
- package/dist/types/src/components/Avatars/Avatar.d.ts.map +1 -1
- package/dist/types/src/components/Breadcrumb/Breadcrumb.d.ts +3 -3
- package/dist/types/src/components/Breadcrumb/Breadcrumb.d.ts.map +1 -1
- package/dist/types/src/components/Button/Button.d.ts +2 -2
- package/dist/types/src/components/Button/Button.d.ts.map +1 -1
- package/dist/types/src/components/Button/IconButton.stories.d.ts +3 -0
- package/dist/types/src/components/Button/IconButton.stories.d.ts.map +1 -1
- package/dist/types/src/components/Button/MicButton.d.ts +24 -0
- package/dist/types/src/components/Button/MicButton.d.ts.map +1 -0
- package/dist/types/src/components/Button/MicButton.stories.d.ts +57 -0
- package/dist/types/src/components/Button/MicButton.stories.d.ts.map +1 -0
- package/dist/types/src/components/Button/SystemIconButton.d.ts +15 -2
- package/dist/types/src/components/Button/SystemIconButton.d.ts.map +1 -1
- package/dist/types/src/components/Button/SystemIconButton.stories.d.ts +51 -0
- package/dist/types/src/components/Button/SystemIconButton.stories.d.ts.map +1 -0
- package/dist/types/src/components/Button/ToggleGroup.d.ts +2 -2
- package/dist/types/src/components/Button/ToggleGroup.d.ts.map +1 -1
- package/dist/types/src/components/Button/index.d.ts +1 -0
- package/dist/types/src/components/Button/index.d.ts.map +1 -1
- package/dist/types/src/components/Calendar/Calendar.d.ts.map +1 -1
- package/dist/types/src/components/Calendar/Calendar.theme.d.ts.map +1 -1
- package/dist/types/src/components/Card/Card.d.ts +8 -8
- package/dist/types/src/components/Card/Card.d.ts.map +1 -1
- package/dist/types/src/components/Card/Card.stories.d.ts +2 -2
- package/dist/types/src/components/Card/Card.stories.d.ts.map +1 -1
- package/dist/types/src/components/Carousel/Carousel.d.ts +29 -6
- package/dist/types/src/components/Carousel/Carousel.d.ts.map +1 -1
- package/dist/types/src/components/Carousel/Carousel.stories.d.ts +48 -2
- package/dist/types/src/components/Carousel/Carousel.stories.d.ts.map +1 -1
- package/dist/types/src/components/Column/Column.d.ts +2 -2
- package/dist/types/src/components/Column/Column.d.ts.map +1 -1
- package/dist/types/src/components/Dialog/AlertDialog.d.ts +2 -2
- package/dist/types/src/components/Dialog/AlertDialog.d.ts.map +1 -1
- package/dist/types/src/components/Dialog/Dialog.d.ts +1 -1
- package/dist/types/src/components/Dialog/Dialog.d.ts.map +1 -1
- package/dist/types/src/components/Dialog/Dialog.stories.d.ts +2 -2
- package/dist/types/src/components/Dialog/Dialog.stories.d.ts.map +1 -1
- package/dist/types/src/components/Icon/Icon.stories.d.ts +5 -0
- package/dist/types/src/components/Icon/Icon.stories.d.ts.map +1 -1
- package/dist/types/src/components/Input/Input.d.ts +4 -1
- package/dist/types/src/components/Input/Input.d.ts.map +1 -1
- package/dist/types/src/components/Input/Input.stories.d.ts +3 -3
- package/dist/types/src/components/Input/Input.stories.d.ts.map +1 -1
- package/dist/types/src/components/Input/Input.theme.d.ts +10 -10
- package/dist/types/src/components/Input/Input.theme.d.ts.map +1 -1
- package/dist/types/src/components/Input/SegmentedInput.d.ts +8 -8
- package/dist/types/src/components/Input/SegmentedInput.d.ts.map +1 -1
- package/dist/types/src/components/Input/index.d.ts +0 -1
- package/dist/types/src/components/Input/index.d.ts.map +1 -1
- package/dist/types/src/components/Main/Main.d.ts +3 -3
- package/dist/types/src/components/Main/Main.d.ts.map +1 -1
- package/dist/types/src/components/Menu/ContextMenu.d.ts +1 -1
- package/dist/types/src/components/Menu/ContextMenu.d.ts.map +1 -1
- package/dist/types/src/components/Menu/DropdownMenu.d.ts +2 -2
- package/dist/types/src/components/Menu/DropdownMenu.d.ts.map +1 -1
- package/dist/types/src/components/Message/Message.d.ts +1 -1
- package/dist/types/src/components/Message/Message.d.ts.map +1 -1
- package/dist/types/src/components/Message/Message.stories.d.ts +2 -2
- package/dist/types/src/components/Message/Message.stories.d.ts.map +1 -1
- package/dist/types/src/components/Panel/Panel.d.ts +1 -1
- package/dist/types/src/components/Panel/Panel.d.ts.map +1 -1
- package/dist/types/src/components/Panel/Panel.theme.d.ts.map +1 -1
- package/dist/types/src/components/Popover/Popover.d.ts +2 -2
- package/dist/types/src/components/Popover/Popover.d.ts.map +1 -1
- package/dist/types/src/components/ScrollArea/ScrollArea.d.ts +2 -0
- package/dist/types/src/components/ScrollArea/ScrollArea.d.ts.map +1 -1
- package/dist/types/src/components/ScrollArea/ScrollArea.stories.d.ts +1 -0
- package/dist/types/src/components/ScrollArea/ScrollArea.stories.d.ts.map +1 -1
- package/dist/types/src/components/ScrollContainer/ScrollContainer.d.ts +1 -1
- package/dist/types/src/components/ScrollContainer/ScrollContainer.d.ts.map +1 -1
- package/dist/types/src/components/ScrollContainer/ScrollContainer.stories.d.ts +2 -2
- package/dist/types/src/components/ScrollContainer/ScrollContainer.stories.d.ts.map +1 -1
- package/dist/types/src/components/Select/Select.d.ts +1 -1
- package/dist/types/src/components/Select/Select.d.ts.map +1 -1
- package/dist/types/src/components/Select/Select.stories.d.ts +2 -2
- package/dist/types/src/components/Select/Select.stories.d.ts.map +1 -1
- package/dist/types/src/components/Separator/Separator.d.ts.map +1 -1
- package/dist/types/src/components/Splitter/Splitter.d.ts +30 -16
- package/dist/types/src/components/Splitter/Splitter.d.ts.map +1 -1
- package/dist/types/src/components/Splitter/Splitter.stories.d.ts +6 -1
- package/dist/types/src/components/Splitter/Splitter.stories.d.ts.map +1 -1
- package/dist/types/src/components/Splitter/Splitter.theme.d.ts +3 -1
- package/dist/types/src/components/Splitter/Splitter.theme.d.ts.map +1 -1
- package/dist/types/src/components/Tag/Tag.d.ts +1 -1
- package/dist/types/src/components/Tag/Tag.d.ts.map +1 -1
- package/dist/types/src/components/Tag/Tag.theme.d.ts +1 -1
- package/dist/types/src/components/Tag/Tag.theme.d.ts.map +1 -1
- package/dist/types/src/components/Toast/Toast.d.ts +1 -1
- package/dist/types/src/components/Toast/Toast.d.ts.map +1 -1
- package/dist/types/src/components/Toast/Toast.theme.d.ts.map +1 -1
- package/dist/types/src/components/Toolbar/Toolbar.d.ts +1 -1
- package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
- package/dist/types/src/components/Tooltip/Tooltip.d.ts +3 -3
- package/dist/types/src/components/Tooltip/Tooltip.d.ts.map +1 -1
- package/dist/types/src/components/Tooltip/Tooltip.stories.d.ts +2 -2
- package/dist/types/src/components/Tooltip/Tooltip.stories.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +0 -1
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/primitives/ThemeProvider/TranslationsProvider.d.ts +1 -8
- package/dist/types/src/primitives/ThemeProvider/TranslationsProvider.d.ts.map +1 -1
- package/dist/types/src/theme/defaultTheme.d.ts.map +1 -1
- package/dist/types/src/theme/index.d.ts +0 -2
- package/dist/types/src/theme/index.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +3 -0
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/util/slots.d.ts +1 -1
- package/dist/types/src/util/slots.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +22 -21
- package/src/components/Avatars/Avatar.stories.tsx +2 -2
- package/src/components/Avatars/Avatar.theme.ts +1 -1
- package/src/components/Avatars/Avatar.tsx +3 -3
- package/src/components/Breadcrumb/Breadcrumb.tsx +6 -6
- package/src/components/Button/Button.tsx +2 -2
- package/src/components/Button/MicButton.stories.tsx +60 -0
- package/src/components/Button/MicButton.tsx +117 -0
- package/src/components/Button/SystemIconButton.stories.tsx +91 -0
- package/src/components/Button/SystemIconButton.tsx +113 -2
- package/src/components/Button/ToggleGroup.tsx +2 -2
- package/src/components/Button/index.ts +1 -0
- package/src/components/Calendar/Calendar.theme.ts +2 -0
- package/src/components/Calendar/Calendar.tsx +3 -3
- package/src/components/Card/Card.stories.tsx +2 -2
- package/src/components/Card/Card.tsx +11 -11
- package/src/components/Carousel/Carousel.stories.tsx +42 -18
- package/src/components/Carousel/Carousel.tsx +169 -33
- package/src/components/Column/Column.tsx +4 -7
- package/src/components/Dialog/AlertDialog.stories.tsx +2 -2
- package/src/components/Dialog/AlertDialog.tsx +13 -13
- package/src/components/Dialog/Dialog.stories.tsx +3 -4
- package/src/components/Dialog/Dialog.theme.ts +1 -1
- package/src/components/Dialog/Dialog.tsx +12 -12
- package/src/components/Icon/Icon.stories.tsx +23 -0
- package/src/components/Icon/Icon.theme.ts +1 -1
- package/src/components/Input/Input.stories.tsx +13 -5
- package/src/components/Input/Input.theme.ts +17 -39
- package/src/components/Input/Input.tsx +30 -13
- package/src/components/Input/SegmentedInput.tsx +50 -41
- package/src/components/Input/index.ts +0 -1
- package/src/components/Main/Main.tsx +3 -3
- package/src/components/MediaPlayer/MediaPlayer.tsx +4 -4
- package/src/components/Menu/ContextMenu.tsx +8 -8
- package/src/components/Menu/DropdownMenu.tsx +12 -13
- package/src/components/Message/Message.stories.tsx +2 -2
- package/src/components/Message/Message.theme.ts +1 -1
- package/src/components/Message/Message.tsx +2 -2
- package/src/components/Panel/Panel.theme.ts +2 -1
- package/src/components/Panel/Panel.tsx +1 -1
- package/src/components/Popover/Popover.tsx +9 -9
- package/src/components/ScrollArea/ScrollArea.tsx +7 -4
- package/src/components/ScrollContainer/ScrollContainer.stories.tsx +2 -2
- package/src/components/ScrollContainer/ScrollContainer.tsx +2 -2
- package/src/components/Select/Select.stories.tsx +2 -2
- package/src/components/Select/Select.theme.ts +1 -1
- package/src/components/Select/Select.tsx +14 -14
- package/src/components/Separator/Separator.tsx +1 -0
- package/src/components/Splitter/Splitter.stories.tsx +84 -20
- package/src/components/Splitter/Splitter.theme.ts +20 -3
- package/src/components/Splitter/Splitter.tsx +320 -47
- package/src/components/Tag/Tag.stories.tsx +1 -1
- package/src/components/Tag/Tag.theme.ts +1 -1
- package/src/components/Tag/Tag.tsx +3 -5
- package/src/components/Toast/Toast.theme.ts +3 -2
- package/src/components/Toast/Toast.tsx +5 -5
- package/src/components/Toolbar/Toolbar.tsx +10 -10
- package/src/components/Tooltip/Tooltip.stories.tsx +2 -2
- package/src/components/Tooltip/Tooltip.tsx +3 -3
- package/src/components/index.ts +0 -1
- package/src/exemplars/tabster.stories.tsx +1 -1
- package/src/index.ts +1 -1
- package/src/primitives/ThemeProvider/TranslationsProvider.tsx +10 -30
- package/src/testing/decorators/withLayout.tsx +4 -4
- package/src/theme/defaultTheme.ts +0 -4
- package/src/theme/index.ts +0 -2
- package/src/translations.ts +8 -0
- package/src/util/slots.ts +5 -5
- package/dist/lib/browser/chunk-F3PLXTDA.mjs.map +0 -7
- package/dist/lib/browser/chunk-HFJYTGHU.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-LLVUYLAM.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-OJYUPUCY.mjs.map +0 -7
- package/dist/types/src/components/Input/constants.d.ts +0 -5
- package/dist/types/src/components/Input/constants.d.ts.map +0 -1
- package/dist/types/src/components/List/List.d.ts +0 -29
- package/dist/types/src/components/List/List.d.ts.map +0 -1
- package/dist/types/src/components/List/List.stories.d.ts +0 -16
- package/dist/types/src/components/List/List.stories.d.ts.map +0 -1
- package/dist/types/src/components/List/List.theme.d.ts +0 -7
- package/dist/types/src/components/List/List.theme.d.ts.map +0 -1
- package/dist/types/src/components/List/ListDropIndicator.d.ts +0 -13
- package/dist/types/src/components/List/ListDropIndicator.d.ts.map +0 -1
- package/dist/types/src/components/List/Tree.d.ts +0 -23
- package/dist/types/src/components/List/Tree.d.ts.map +0 -1
- package/dist/types/src/components/List/Tree.stories.d.ts +0 -15
- package/dist/types/src/components/List/Tree.stories.d.ts.map +0 -1
- package/dist/types/src/components/List/TreeDropIndicator.d.ts +0 -8
- package/dist/types/src/components/List/TreeDropIndicator.d.ts.map +0 -1
- package/dist/types/src/components/List/Treegrid.d.ts +0 -33
- package/dist/types/src/components/List/Treegrid.d.ts.map +0 -1
- package/dist/types/src/components/List/Treegrid.stories.d.ts +0 -12
- package/dist/types/src/components/List/Treegrid.stories.d.ts.map +0 -1
- package/dist/types/src/components/List/Treegrid.theme.d.ts +0 -7
- package/dist/types/src/components/List/Treegrid.theme.d.ts.map +0 -1
- package/dist/types/src/components/List/index.d.ts +0 -4
- package/dist/types/src/components/List/index.d.ts.map +0 -1
- package/src/components/Input/constants.ts +0 -8
- package/src/components/List/List.stories.tsx +0 -234
- package/src/components/List/List.theme.ts +0 -47
- package/src/components/List/List.tsx +0 -171
- package/src/components/List/ListDropIndicator.tsx +0 -69
- package/src/components/List/Tree.stories.tsx +0 -87
- package/src/components/List/Tree.tsx +0 -74
- package/src/components/List/TreeDropIndicator.tsx +0 -70
- package/src/components/List/Treegrid.stories.tsx +0 -156
- package/src/components/List/Treegrid.theme.ts +0 -35
- package/src/components/List/Treegrid.tsx +0 -187
- package/src/components/List/index.ts +0 -7
|
@@ -2,31 +2,125 @@
|
|
|
2
2
|
// Copyright 2026 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { useComposedRefs } from '@radix-ui/react-compose-refs';
|
|
6
|
+
import { createContext } from '@radix-ui/react-context';
|
|
6
7
|
import { Primitive } from '@radix-ui/react-primitive';
|
|
7
8
|
import { Slot } from '@radix-ui/react-slot';
|
|
8
|
-
import
|
|
9
|
+
import { useControllableState } from '@radix-ui/react-use-controllable-state';
|
|
10
|
+
import React, {
|
|
11
|
+
type CSSProperties,
|
|
12
|
+
type KeyboardEvent,
|
|
13
|
+
type PointerEvent,
|
|
14
|
+
type RefObject,
|
|
15
|
+
useCallback,
|
|
16
|
+
useEffect,
|
|
17
|
+
useRef,
|
|
18
|
+
useState,
|
|
19
|
+
} from 'react';
|
|
20
|
+
|
|
21
|
+
import { type SlottableProps } from '@dxos/ui-types';
|
|
9
22
|
|
|
10
23
|
import { useThemeContext } from '../../hooks';
|
|
11
24
|
import { composableProps, slottable } from '../../util';
|
|
12
|
-
import { ThemedClassName } from '../../util';
|
|
13
25
|
|
|
14
|
-
type
|
|
26
|
+
type SplitterOrientation = 'horizontal' | 'vertical';
|
|
27
|
+
|
|
28
|
+
// Animated panel visibility: collapse to the start panel, the end panel, or show both split at `size`.
|
|
29
|
+
type SplitterMode = 'start' | 'end' | 'split';
|
|
30
|
+
|
|
31
|
+
type Position = 'start' | 'end';
|
|
15
32
|
|
|
16
|
-
//
|
|
17
|
-
|
|
33
|
+
//
|
|
34
|
+
// Context
|
|
35
|
+
//
|
|
36
|
+
|
|
37
|
+
const SPLITTER_NAME = 'Splitter';
|
|
18
38
|
|
|
19
39
|
type SplitterContextValue = {
|
|
20
|
-
|
|
21
|
-
|
|
40
|
+
orientation: SplitterOrientation;
|
|
41
|
+
mode: SplitterMode;
|
|
42
|
+
/** Which panel `size` measures; the other panel fills the remainder. */
|
|
43
|
+
anchor: Position;
|
|
44
|
+
/** The anchored panel's extent in rem, or `undefined` for an even (50/50) split. */
|
|
45
|
+
size: number | undefined;
|
|
22
46
|
transition: number;
|
|
47
|
+
resizable: boolean;
|
|
48
|
+
/** Lower bound (rem) applied to both panels. */
|
|
49
|
+
minSize: number;
|
|
50
|
+
dragging: boolean;
|
|
51
|
+
/** True only briefly after a `mode` change, so the collapse animates but layout reflows (resize) do not. */
|
|
52
|
+
animating: boolean;
|
|
53
|
+
rootRef: RefObject<HTMLDivElement | null>;
|
|
54
|
+
setSize: (size: number) => void;
|
|
55
|
+
setDragging: (dragging: boolean) => void;
|
|
23
56
|
};
|
|
24
57
|
|
|
25
|
-
const
|
|
58
|
+
const [SplitterProvider, useSplitterContext] = createContext<SplitterContextValue>(SPLITTER_NAME);
|
|
59
|
+
|
|
60
|
+
const getRem = (): number => parseFloat(getComputedStyle(document.documentElement).fontSize) || 16;
|
|
26
61
|
|
|
27
|
-
|
|
62
|
+
// Lower-bound clamp applied to both panels: the start panel can't drop below `minSize` nor exceed the
|
|
63
|
+
// container minus `minSize` (reserving room for the end panel, which also carries the min).
|
|
64
|
+
const clampStyle = (minSize: number, orientation: SplitterOrientation): CSSProperties =>
|
|
65
|
+
orientation === 'horizontal'
|
|
66
|
+
? { minInlineSize: `${minSize}rem`, maxInlineSize: `calc(100% - ${minSize}rem)` }
|
|
67
|
+
: { minBlockSize: `${minSize}rem`, maxBlockSize: `calc(100% - ${minSize}rem)` };
|
|
28
68
|
|
|
29
|
-
const
|
|
69
|
+
const endMinStyle = (minSize: number, orientation: SplitterOrientation): CSSProperties =>
|
|
70
|
+
orientation === 'horizontal' ? { minInlineSize: `${minSize}rem` } : { minBlockSize: `${minSize}rem` };
|
|
71
|
+
|
|
72
|
+
// In `split` mode the anchored panel is fixed at `size` rem (clamped) and the other fills the rest; when
|
|
73
|
+
// `size` is undefined both panels grow equally (50/50). Otherwise one panel fills (flex-grow) and the
|
|
74
|
+
// other collapses to zero (content stays mounted, clipped).
|
|
75
|
+
const panelStyle = (
|
|
76
|
+
position: Position,
|
|
77
|
+
mode: SplitterMode,
|
|
78
|
+
anchor: Position,
|
|
79
|
+
size: number | undefined,
|
|
80
|
+
minSize: number,
|
|
81
|
+
orientation: SplitterOrientation,
|
|
82
|
+
): CSSProperties => {
|
|
83
|
+
if (mode !== 'split') {
|
|
84
|
+
const fills = (mode === 'start' && position === 'start') || (mode === 'end' && position === 'end');
|
|
85
|
+
return { flexGrow: fills ? 1 : 0, flexShrink: 1, flexBasis: 0 };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// No explicit size: split evenly.
|
|
89
|
+
if (size === undefined) {
|
|
90
|
+
return { flexGrow: 1, flexShrink: 1, flexBasis: 0, ...endMinStyle(minSize, orientation) };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return position === anchor
|
|
94
|
+
? { flexGrow: 0, flexShrink: 1, flexBasis: `${size}rem`, ...clampStyle(minSize, orientation) }
|
|
95
|
+
: { flexGrow: 1, flexShrink: 1, flexBasis: 0, ...endMinStyle(minSize, orientation) };
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// Absolute position for the handle, centered on the split point (`size` rem from the anchored edge, or
|
|
99
|
+
// the middle for an even split). The offset is clamped to the same bounds as the anchored panel
|
|
100
|
+
// (`minSize` .. `100% - minSize`) so a sudden container shrink (e.g. opening devtools) that caps the
|
|
101
|
+
// panel keeps the handle pinned to the panel edge instead of floating into the other panel.
|
|
102
|
+
const handlePosition = (
|
|
103
|
+
orientation: SplitterOrientation,
|
|
104
|
+
anchor: Position,
|
|
105
|
+
size: number | undefined,
|
|
106
|
+
minSize: number,
|
|
107
|
+
): CSSProperties => {
|
|
108
|
+
if (size === undefined) {
|
|
109
|
+
return orientation === 'horizontal'
|
|
110
|
+
? { insetBlock: 0, insetInlineStart: '50%', transform: 'translateX(-50%)' }
|
|
111
|
+
: { insetInline: 0, insetBlockStart: '50%', transform: 'translateY(-50%)' };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const offset = `clamp(${minSize}rem, ${size}rem, calc(100% - ${minSize}rem))`;
|
|
115
|
+
if (orientation === 'horizontal') {
|
|
116
|
+
return anchor === 'end'
|
|
117
|
+
? { insetBlock: 0, insetInlineEnd: offset, transform: 'translateX(50%)' }
|
|
118
|
+
: { insetBlock: 0, insetInlineStart: offset, transform: 'translateX(-50%)' };
|
|
119
|
+
}
|
|
120
|
+
return anchor === 'end'
|
|
121
|
+
? { insetInline: 0, insetBlockEnd: offset, transform: 'translateY(50%)' }
|
|
122
|
+
: { insetInline: 0, insetBlockStart: offset, transform: 'translateY(-50%)' };
|
|
123
|
+
};
|
|
30
124
|
|
|
31
125
|
//
|
|
32
126
|
// Root
|
|
@@ -34,18 +128,88 @@ const [SplitterProvider, useSplitterContext] = createSplitterContext<SplitterCon
|
|
|
34
128
|
|
|
35
129
|
const ROOT_NAME = 'Splitter.Root';
|
|
36
130
|
|
|
37
|
-
type
|
|
131
|
+
type SplitterRootElementProps = {
|
|
132
|
+
orientation?: SplitterOrientation;
|
|
133
|
+
mode?: SplitterMode;
|
|
134
|
+
/** Which panel `size` measures (defaults to `start`); the other panel fills the remainder. */
|
|
135
|
+
anchor?: Position;
|
|
136
|
+
/** The anchored panel's extent in rem (controlled). */
|
|
137
|
+
size?: number;
|
|
138
|
+
defaultSize?: number;
|
|
139
|
+
onSizeChange?: (size: number) => void;
|
|
140
|
+
transition?: number;
|
|
141
|
+
resizable?: boolean;
|
|
142
|
+
/** Lower bound (rem) applied to both panels. */
|
|
143
|
+
minSize?: number;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
type SplitterRootProps = SlottableProps<SplitterRootElementProps>;
|
|
38
147
|
|
|
39
|
-
const SplitterRoot = slottable<HTMLDivElement,
|
|
40
|
-
(
|
|
148
|
+
const SplitterRoot = slottable<HTMLDivElement, SplitterRootElementProps>(
|
|
149
|
+
(
|
|
150
|
+
{
|
|
151
|
+
asChild,
|
|
152
|
+
children,
|
|
153
|
+
orientation = 'vertical',
|
|
154
|
+
mode = 'split',
|
|
155
|
+
anchor = 'start',
|
|
156
|
+
size: sizeProp,
|
|
157
|
+
defaultSize,
|
|
158
|
+
onSizeChange,
|
|
159
|
+
transition = 250,
|
|
160
|
+
resizable = false,
|
|
161
|
+
minSize = 0,
|
|
162
|
+
...props
|
|
163
|
+
},
|
|
164
|
+
forwardedRef,
|
|
165
|
+
) => {
|
|
41
166
|
const { tx } = useThemeContext();
|
|
42
|
-
const
|
|
43
|
-
const
|
|
167
|
+
const rootRef = useRef<HTMLDivElement>(null);
|
|
168
|
+
const composedRef = useComposedRefs(forwardedRef, rootRef);
|
|
169
|
+
const [size, setSize] = useControllableState({
|
|
170
|
+
prop: sizeProp,
|
|
171
|
+
defaultProp: defaultSize,
|
|
172
|
+
onChange: onSizeChange,
|
|
173
|
+
});
|
|
174
|
+
const [dragging, setDragging] = useState(false);
|
|
175
|
+
|
|
176
|
+
// Animate ONLY for a brief window right after a `mode` change (the collapse). The rest of the time the
|
|
177
|
+
// transition is off, so layout reflows from a container/window resize never animate (no jitter) — this
|
|
178
|
+
// avoids relying on observing/throttling resize events at all.
|
|
179
|
+
const [animating, setAnimating] = useState(false);
|
|
180
|
+
const previousMode = useRef(mode);
|
|
181
|
+
useEffect(() => {
|
|
182
|
+
if (previousMode.current === mode) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
previousMode.current = mode;
|
|
186
|
+
if (transition <= 0) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
setAnimating(true);
|
|
190
|
+
const timer = setTimeout(() => setAnimating(false), transition);
|
|
191
|
+
return () => clearTimeout(timer);
|
|
192
|
+
}, [mode, transition]);
|
|
193
|
+
|
|
194
|
+
const { className, ...rest } = composableProps(props);
|
|
44
195
|
const Comp = asChild ? Slot : Primitive.div;
|
|
45
196
|
|
|
46
197
|
return (
|
|
47
|
-
<SplitterProvider
|
|
48
|
-
|
|
198
|
+
<SplitterProvider
|
|
199
|
+
orientation={orientation}
|
|
200
|
+
mode={mode}
|
|
201
|
+
anchor={anchor}
|
|
202
|
+
size={size}
|
|
203
|
+
transition={transition}
|
|
204
|
+
resizable={resizable}
|
|
205
|
+
minSize={minSize}
|
|
206
|
+
animating={animating}
|
|
207
|
+
dragging={dragging}
|
|
208
|
+
rootRef={rootRef}
|
|
209
|
+
setSize={setSize}
|
|
210
|
+
setDragging={setDragging}
|
|
211
|
+
>
|
|
212
|
+
<Comp {...rest} ref={composedRef} className={tx('splitter.root', { orientation }, className)}>
|
|
49
213
|
{children}
|
|
50
214
|
</Comp>
|
|
51
215
|
</SplitterProvider>
|
|
@@ -61,42 +225,28 @@ SplitterRoot.displayName = ROOT_NAME;
|
|
|
61
225
|
|
|
62
226
|
const PANEL_NAME = 'Splitter.Panel';
|
|
63
227
|
|
|
64
|
-
type SplitterPanelProps =
|
|
65
|
-
position: 'top' | 'bottom';
|
|
66
|
-
}>;
|
|
228
|
+
type SplitterPanelProps = SlottableProps<{ position: Position }>;
|
|
67
229
|
|
|
68
|
-
const SplitterPanel = slottable<HTMLDivElement,
|
|
69
|
-
({
|
|
230
|
+
const SplitterPanel = slottable<HTMLDivElement, { position: Position }>(
|
|
231
|
+
({ asChild, children, position, ...props }, forwardedRef) => {
|
|
70
232
|
const { tx } = useThemeContext();
|
|
71
|
-
const {
|
|
233
|
+
const { orientation, mode, anchor, size, minSize, transition, dragging, animating } =
|
|
234
|
+
useSplitterContext(PANEL_NAME);
|
|
235
|
+
const { className, style, ...rest } = composableProps(props);
|
|
72
236
|
const Comp = asChild ? Slot : Primitive.div;
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const isTopPanel = position === 'top';
|
|
78
|
-
const topOffset = isTopPanel ? '0%' : mode === 'top' ? '100%' : mode === 'bottom' ? '0%' : `${ratio * 100}%`;
|
|
79
|
-
const height = isTopPanel
|
|
80
|
-
? mode === 'top'
|
|
81
|
-
? '100%'
|
|
82
|
-
: mode === 'bottom'
|
|
83
|
-
? '0%'
|
|
84
|
-
: `${ratio * 100}%`
|
|
85
|
-
: mode === 'bottom'
|
|
86
|
-
? '100%'
|
|
87
|
-
: mode === 'top'
|
|
88
|
-
? '0%'
|
|
89
|
-
: `${(1 - ratio) * 100}%`;
|
|
237
|
+
|
|
238
|
+
// Only animate during the brief post-mode-change window (collapse), never while dragging or on a plain
|
|
239
|
+
// container/window resize — so the panels track layout reflows instantly without jitter.
|
|
240
|
+
const animate = transition > 0 && animating && !dragging;
|
|
90
241
|
|
|
91
242
|
return (
|
|
92
243
|
<Comp
|
|
93
|
-
{...
|
|
244
|
+
{...rest}
|
|
94
245
|
ref={forwardedRef}
|
|
95
246
|
className={tx('splitter.panel', {}, className)}
|
|
96
247
|
style={{
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
transition: `top ${transition}ms, height ${transition}ms ease-out`,
|
|
248
|
+
...panelStyle(position, mode, anchor, size, minSize, orientation),
|
|
249
|
+
transition: animate ? `flex-grow ${transition}ms ease-out, flex-basis ${transition}ms ease-out` : undefined,
|
|
100
250
|
...style,
|
|
101
251
|
}}
|
|
102
252
|
>
|
|
@@ -108,6 +258,128 @@ const SplitterPanel = slottable<HTMLDivElement, SplitterPanelProps>(
|
|
|
108
258
|
|
|
109
259
|
SplitterPanel.displayName = PANEL_NAME;
|
|
110
260
|
|
|
261
|
+
//
|
|
262
|
+
// Handle
|
|
263
|
+
//
|
|
264
|
+
|
|
265
|
+
const HANDLE_NAME = 'Splitter.Handle';
|
|
266
|
+
|
|
267
|
+
type SplitterHandleProps = SlottableProps;
|
|
268
|
+
|
|
269
|
+
const SplitterHandle = slottable<HTMLDivElement>(({ asChild, children, ...props }, forwardedRef) => {
|
|
270
|
+
const { tx } = useThemeContext();
|
|
271
|
+
const { orientation, anchor, size, resizable, minSize, rootRef, setSize, setDragging } =
|
|
272
|
+
useSplitterContext(HANDLE_NAME);
|
|
273
|
+
const { className, ...rest } = composableProps(props);
|
|
274
|
+
const Comp = asChild ? Slot : Primitive.div;
|
|
275
|
+
|
|
276
|
+
// Container extent (rem) along the split axis; the upper bound is `extent - minSize`.
|
|
277
|
+
const extentRem = useCallback((): number => {
|
|
278
|
+
const rect = rootRef.current?.getBoundingClientRect();
|
|
279
|
+
if (!rect) {
|
|
280
|
+
return (size ?? minSize) + minSize;
|
|
281
|
+
}
|
|
282
|
+
return (orientation === 'horizontal' ? rect.width : rect.height) / getRem();
|
|
283
|
+
}, [orientation, rootRef, size, minSize]);
|
|
284
|
+
|
|
285
|
+
const handlePointerDown = useCallback(
|
|
286
|
+
(event: PointerEvent<HTMLDivElement>) => {
|
|
287
|
+
if (event.button !== 0) {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
event.preventDefault();
|
|
291
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
292
|
+
setDragging(true);
|
|
293
|
+
},
|
|
294
|
+
[setDragging],
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
// Map the pointer position within the Root to the anchored panel's extent (rem), clamped so neither
|
|
298
|
+
// panel drops below `minSize`. For an `end` anchor the extent is measured from the far edge.
|
|
299
|
+
const handlePointerMove = useCallback(
|
|
300
|
+
(event: PointerEvent<HTMLDivElement>) => {
|
|
301
|
+
if (!event.currentTarget.hasPointerCapture(event.pointerId)) {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
const root = rootRef.current;
|
|
305
|
+
if (!root) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const rect = root.getBoundingClientRect();
|
|
309
|
+
const rem = getRem();
|
|
310
|
+
const offset = (orientation === 'horizontal' ? event.clientX - rect.left : event.clientY - rect.top) / rem;
|
|
311
|
+
const extent = (orientation === 'horizontal' ? rect.width : rect.height) / rem;
|
|
312
|
+
const next = anchor === 'end' ? extent - offset : offset;
|
|
313
|
+
setSize(Math.min(Math.max(next, minSize), Math.max(minSize, extent - minSize)));
|
|
314
|
+
},
|
|
315
|
+
[orientation, anchor, minSize, rootRef, setSize],
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
const handlePointerUp = useCallback(
|
|
319
|
+
(event: PointerEvent<HTMLDivElement>) => {
|
|
320
|
+
if (event.currentTarget.hasPointerCapture(event.pointerId)) {
|
|
321
|
+
event.currentTarget.releasePointerCapture(event.pointerId);
|
|
322
|
+
}
|
|
323
|
+
setDragging(false);
|
|
324
|
+
},
|
|
325
|
+
[setDragging],
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
// Arrow keys nudge the start panel's size (rem); Home/End jump to the bounds.
|
|
329
|
+
// Keeps the resize affordance usable without a pointer.
|
|
330
|
+
const handleKeyDown = useCallback(
|
|
331
|
+
(event: KeyboardEvent<HTMLDivElement>) => {
|
|
332
|
+
const horizontal = orientation === 'horizontal';
|
|
333
|
+
const step = event.shiftKey ? 4 : 1;
|
|
334
|
+
// Start from the current size, or the midpoint when the split is even (unsized).
|
|
335
|
+
const base = size ?? extentRem() / 2;
|
|
336
|
+
let next: number | undefined;
|
|
337
|
+
if (event.key === (horizontal ? 'ArrowRight' : 'ArrowDown')) {
|
|
338
|
+
next = base + step;
|
|
339
|
+
} else if (event.key === (horizontal ? 'ArrowLeft' : 'ArrowUp')) {
|
|
340
|
+
next = base - step;
|
|
341
|
+
} else if (event.key === 'Home') {
|
|
342
|
+
next = minSize;
|
|
343
|
+
} else if (event.key === 'End') {
|
|
344
|
+
next = extentRem() - minSize;
|
|
345
|
+
}
|
|
346
|
+
if (next === undefined) {
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
event.preventDefault();
|
|
350
|
+
setSize(Math.min(Math.max(next, minSize), Math.max(minSize, extentRem() - minSize)));
|
|
351
|
+
},
|
|
352
|
+
[orientation, size, minSize, extentRem, setSize],
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
if (!resizable) {
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return (
|
|
360
|
+
<Comp
|
|
361
|
+
{...rest}
|
|
362
|
+
ref={forwardedRef}
|
|
363
|
+
role='separator'
|
|
364
|
+
tabIndex={0}
|
|
365
|
+
aria-orientation={orientation === 'horizontal' ? 'vertical' : 'horizontal'}
|
|
366
|
+
aria-valuemin={Math.round(minSize)}
|
|
367
|
+
aria-valuenow={size !== undefined ? Math.round(size) : undefined}
|
|
368
|
+
className={tx('splitter.handle', { orientation }, className)}
|
|
369
|
+
style={handlePosition(orientation, anchor, size, minSize)}
|
|
370
|
+
onPointerDown={handlePointerDown}
|
|
371
|
+
onPointerMove={handlePointerMove}
|
|
372
|
+
onPointerUp={handlePointerUp}
|
|
373
|
+
onPointerCancel={handlePointerUp}
|
|
374
|
+
onKeyDown={handleKeyDown}
|
|
375
|
+
>
|
|
376
|
+
{children}
|
|
377
|
+
</Comp>
|
|
378
|
+
);
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
SplitterHandle.displayName = HANDLE_NAME;
|
|
382
|
+
|
|
111
383
|
//
|
|
112
384
|
// Splitter
|
|
113
385
|
//
|
|
@@ -115,8 +387,9 @@ SplitterPanel.displayName = PANEL_NAME;
|
|
|
115
387
|
const Splitter = {
|
|
116
388
|
Root: SplitterRoot,
|
|
117
389
|
Panel: SplitterPanel,
|
|
390
|
+
Handle: SplitterHandle,
|
|
118
391
|
};
|
|
119
392
|
|
|
120
|
-
export { Splitter
|
|
393
|
+
export { Splitter };
|
|
121
394
|
|
|
122
|
-
export type {
|
|
395
|
+
export type { SplitterHandleProps, SplitterMode, SplitterOrientation, SplitterPanelProps, SplitterRootProps };
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
} from '@dxos/ui-types';
|
|
13
13
|
|
|
14
14
|
export type TagStyleProps = {
|
|
15
|
-
|
|
15
|
+
hue?: ChromaticPalette | NeutralPalette | MessageValence;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
const root: ComponentFunction<TagStyleProps> = (_, ...etc) => mx('dx-tag', ...etc);
|
|
@@ -12,16 +12,14 @@ import { useThemeContext } from '../../hooks';
|
|
|
12
12
|
import { type ThemedClassName } from '../../util';
|
|
13
13
|
|
|
14
14
|
export type TagProps = ThemedClassName<ComponentPropsWithRef<typeof Primitive.span>> & {
|
|
15
|
-
palette?: NeutralPalette | ChromaticPalette | MessageValence;
|
|
16
15
|
asChild?: boolean;
|
|
16
|
+
hue?: NeutralPalette | ChromaticPalette | MessageValence;
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
export const Tag = forwardRef<HTMLSpanElement, TagProps>(
|
|
20
|
-
({ asChild,
|
|
20
|
+
({ asChild, hue = 'neutral', classNames, ...props }, forwardedRef) => {
|
|
21
21
|
const { tx } = useThemeContext();
|
|
22
22
|
const Comp = asChild ? Slot : Primitive.span;
|
|
23
|
-
return (
|
|
24
|
-
<Comp {...props} className={tx('tag.root', { palette }, classNames)} data-hue={palette} ref={forwardedRef} />
|
|
25
|
-
);
|
|
23
|
+
return <Comp {...props} className={tx('tag.root', { hue }, classNames)} data-hue={hue} ref={forwardedRef} />;
|
|
26
24
|
},
|
|
27
25
|
);
|
|
@@ -9,8 +9,9 @@ export type ToastStyleProps = {};
|
|
|
9
9
|
|
|
10
10
|
const viewport: ComponentFunction<ToastStyleProps> = (_props, ...etc) =>
|
|
11
11
|
mx(
|
|
12
|
-
|
|
13
|
-
'
|
|
12
|
+
'z-40 fixed bottom-[calc(env(safe-area-inset-bottom)+1rem)]',
|
|
13
|
+
'inset-start-[calc(env(safe-area-inset-left)+1rem)] inset-end-[calc(env(safe-area-inset-right)+1rem)]',
|
|
14
|
+
'w-auto md:end-[calc(env(safe-area-inset-right)+4rem)] md:left-auto md:w-full md:max-w-sm',
|
|
14
15
|
'rounded-md flex flex-col gap-2',
|
|
15
16
|
...etc,
|
|
16
17
|
);
|
|
@@ -162,12 +162,12 @@ export const Toast = {
|
|
|
162
162
|
};
|
|
163
163
|
|
|
164
164
|
export type {
|
|
165
|
+
ToastActionProps,
|
|
166
|
+
ToastActionsProps,
|
|
167
|
+
ToastCloseProps,
|
|
168
|
+
ToastDescriptionProps,
|
|
165
169
|
ToastProviderProps,
|
|
166
|
-
ToastViewportProps,
|
|
167
170
|
ToastRootProps,
|
|
168
171
|
ToastTitleProps,
|
|
169
|
-
|
|
170
|
-
ToastActionsProps,
|
|
171
|
-
ToastActionProps,
|
|
172
|
-
ToastCloseProps,
|
|
172
|
+
ToastViewportProps,
|
|
173
173
|
};
|
|
@@ -366,19 +366,19 @@ export const Toolbar = {
|
|
|
366
366
|
};
|
|
367
367
|
|
|
368
368
|
export type {
|
|
369
|
-
|
|
370
|
-
|
|
369
|
+
ToolbarActionIconButtonAction,
|
|
370
|
+
ToolbarActionIconButtonProps,
|
|
371
371
|
ToolbarButtonProps,
|
|
372
|
+
ToolbarDragHandleProps,
|
|
372
373
|
ToolbarIconButtonProps,
|
|
373
374
|
ToolbarLinkProps,
|
|
374
|
-
ToolbarToggleProps,
|
|
375
|
-
ToolbarToggleGroupProps,
|
|
376
|
-
ToolbarToggleGroupItemProps,
|
|
377
|
-
ToolbarToggleGroupIconItemProps,
|
|
378
|
-
ToolbarSeparatorProps,
|
|
379
|
-
ToolbarDragHandleProps,
|
|
380
|
-
ToolbarActionIconButtonAction,
|
|
381
|
-
ToolbarActionIconButtonProps,
|
|
382
375
|
ToolbarMenuItem,
|
|
383
376
|
ToolbarMenuProps,
|
|
377
|
+
ToolbarRootProps,
|
|
378
|
+
ToolbarSeparatorProps,
|
|
379
|
+
ToolbarTextProps,
|
|
380
|
+
ToolbarToggleGroupIconItemProps,
|
|
381
|
+
ToolbarToggleGroupItemProps,
|
|
382
|
+
ToolbarToggleGroupProps,
|
|
383
|
+
ToolbarToggleProps,
|
|
384
384
|
};
|
|
@@ -11,12 +11,12 @@ import { withTheme } from '../../testing';
|
|
|
11
11
|
import { Button } from '../Button';
|
|
12
12
|
import { Tooltip } from './Tooltip';
|
|
13
13
|
|
|
14
|
-
type
|
|
14
|
+
type StoryArgs = {
|
|
15
15
|
tooltips: { label: string; content: string }[];
|
|
16
16
|
defaultOpen?: boolean;
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
const DefaultStory = ({ tooltips, defaultOpen }:
|
|
19
|
+
const DefaultStory = ({ tooltips, defaultOpen }: StoryArgs) => {
|
|
20
20
|
return (
|
|
21
21
|
<Tooltip.Provider defaultOpen={defaultOpen}>
|
|
22
22
|
<div className='w-32'>
|
|
@@ -493,12 +493,12 @@ type TooltipContentImplProps = Omit<PopperContentProps, 'onPlaced'> & {
|
|
|
493
493
|
* Event handler called when the escape key is down.
|
|
494
494
|
* Can be prevented.
|
|
495
495
|
*/
|
|
496
|
-
onEscapeKeyDown?: DismissableLayerProps['onEscapeKeyDown'];
|
|
496
|
+
'onEscapeKeyDown'?: DismissableLayerProps['onEscapeKeyDown'];
|
|
497
497
|
/**
|
|
498
498
|
* Event handler called when the a `pointerdown` event happens outside of the `Tooltip`.
|
|
499
499
|
* Can be prevented.
|
|
500
500
|
*/
|
|
501
|
-
onPointerDownOutside?: DismissableLayerProps['onPointerDownOutside'];
|
|
501
|
+
'onPointerDownOutside'?: DismissableLayerProps['onPointerDownOutside'];
|
|
502
502
|
};
|
|
503
503
|
|
|
504
504
|
const TooltipContentImpl = forwardRef<TooltipContentImplElement, TooltipContentImplProps>(
|
|
@@ -765,4 +765,4 @@ export const Tooltip = {
|
|
|
765
765
|
|
|
766
766
|
export { createTooltipScope, useTooltipContext };
|
|
767
767
|
|
|
768
|
-
export type { TooltipProviderProps,
|
|
768
|
+
export type { TooltipProviderProps, TooltipScopedProps, TooltipSide, TooltipTriggerProps };
|
package/src/components/index.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -3,31 +3,14 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { type Locale, enUS as dtLocaleEnUs } from 'date-fns/locale';
|
|
6
|
-
import i18Next, { type Resource } from 'i18next';
|
|
7
6
|
import React, { type ReactNode, Suspense, createContext, useContext, useEffect, useState } from 'react';
|
|
8
|
-
import {
|
|
7
|
+
import { I18nextProvider, useTranslation as useI18NextTranslation } from 'react-i18next';
|
|
8
|
+
|
|
9
|
+
import { type Resource, addResources, i18n } from '@dxos/i18n';
|
|
9
10
|
|
|
10
|
-
const initialLng = 'en-US';
|
|
11
11
|
const initialNs = 'dxos-common';
|
|
12
12
|
const initialDtLocale = dtLocaleEnUs;
|
|
13
13
|
|
|
14
|
-
export const resources = {
|
|
15
|
-
[initialLng]: {
|
|
16
|
-
[initialNs]: {
|
|
17
|
-
'loading translations': 'Loading translations…',
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
} as const satisfies Resource;
|
|
21
|
-
|
|
22
|
-
void i18Next.use(initReactI18next).init({
|
|
23
|
-
resources,
|
|
24
|
-
lng: initialLng,
|
|
25
|
-
defaultNS: initialNs,
|
|
26
|
-
interpolation: {
|
|
27
|
-
escapeValue: false,
|
|
28
|
-
},
|
|
29
|
-
});
|
|
30
|
-
|
|
31
14
|
export interface TranslationsProviderProps {
|
|
32
15
|
children?: ReactNode;
|
|
33
16
|
// TODO(wittjosiah): Rename to `placeholder` to match ClientProvider?
|
|
@@ -59,14 +42,9 @@ export const TranslationsProvider = ({
|
|
|
59
42
|
const [loaded, setLoaded] = useState(false);
|
|
60
43
|
useEffect(() => {
|
|
61
44
|
setLoaded(false);
|
|
45
|
+
// Convenience for standalone/storybook callers; the i18next instance lifecycle is owned by `@dxos/i18n`.
|
|
62
46
|
if (resourceExtensions && resourceExtensions.length) {
|
|
63
|
-
resourceExtensions
|
|
64
|
-
Object.keys(resource).forEach((language) => {
|
|
65
|
-
Object.keys(resource[language]).forEach((ns) => {
|
|
66
|
-
i18Next.addResourceBundle(language, ns, resource[language][ns]);
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
});
|
|
47
|
+
addResources(resourceExtensions);
|
|
70
48
|
}
|
|
71
49
|
|
|
72
50
|
setLoaded(true);
|
|
@@ -75,8 +53,10 @@ export const TranslationsProvider = ({
|
|
|
75
53
|
// TODO(thure): This is not ideal, but i18next was causing `Suspense` to not render the fallback even when the child was asking for namespaces yet to be added.
|
|
76
54
|
// TODO(burdon): Fallbacks should only appear after a short delay, and if the displayed then be visible for 500mx to avoid startup flickering.
|
|
77
55
|
return (
|
|
78
|
-
<
|
|
79
|
-
<
|
|
80
|
-
|
|
56
|
+
<I18nextProvider i18n={i18n}>
|
|
57
|
+
<TranslationsContext.Provider value={{ appNs: appNs ?? initialNs, dtLocale: dtLocale ?? initialDtLocale }}>
|
|
58
|
+
<Suspense fallback={fallback}>{loaded ? children : fallback}</Suspense>
|
|
59
|
+
</TranslationsContext.Provider>
|
|
60
|
+
</I18nextProvider>
|
|
81
61
|
);
|
|
82
62
|
};
|