reshaped 3.0.9 → 3.0.10
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/CHANGELOG.md +6 -0
- package/bin/cli.js +0 -1
- package/dist/bundle.css +1 -1
- package/dist/bundle.d.ts +3 -1
- package/dist/bundle.js +10 -10
- package/dist/cjs/themes/_generator/utilities/color.d.ts +16 -0
- package/dist/cjs/themes/_generator/utilities/color.js +57 -7
- package/dist/cjs/themes/_generator/utilities/generateBackgroundColors.js +4 -0
- package/dist/cjs/themes/_generator/utilities/tests/color.test.js +73 -42
- package/dist/cjs/themes/index.d.ts +17 -0
- package/dist/cjs/themes/index.js +3 -0
- package/dist/cjs/types/config.d.ts +1 -0
- package/dist/components/Button/Button.module.css +1 -1
- package/dist/components/Button/tests/Button.stories.js +3 -1
- package/dist/components/DropdownMenu/DropdownMenu.module.css +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.types.d.ts +1 -1
- package/dist/components/Modal/Modal.js +4 -3
- package/dist/components/Modal/tests/Modal.stories.d.ts +0 -1
- package/dist/components/Modal/tests/Modal.stories.js +0 -16
- package/dist/components/Overlay/Overlay.js +7 -7
- package/dist/components/Overlay/tests/Overlay.stories.js +3 -1
- package/dist/components/Popover/Popover.js +2 -2
- package/dist/components/Popover/Popover.types.d.ts +1 -1
- package/dist/components/Resizable/Resizable.d.ts +8 -0
- package/dist/components/Resizable/Resizable.js +149 -0
- package/dist/components/Resizable/Resizable.module.css +1 -0
- package/dist/components/Resizable/Resizable.types.d.ts +29 -0
- package/dist/components/Resizable/Resizable.types.js +1 -0
- package/dist/components/Resizable/index.d.ts +2 -0
- package/dist/components/Resizable/index.js +1 -0
- package/dist/components/Resizable/tests/Resizable.stories.d.ts +15 -0
- package/dist/components/Resizable/tests/Resizable.stories.js +58 -0
- package/dist/components/ScrollArea/ScrollArea.js +4 -4
- package/dist/components/Slider/Slider.types.d.ts +2 -2
- package/dist/components/Slider/Slider.utilities.js +4 -4
- package/dist/components/Slider/SliderControlled.js +11 -9
- package/dist/components/Slider/SliderThumb.js +1 -1
- package/dist/components/Slider/tests/Slider.stories.js +4 -0
- package/dist/components/Toast/Toast.types.d.ts +7 -6
- package/dist/components/Toast/index.d.ts +1 -1
- package/dist/components/Toast/useToast.d.ts +1 -1
- package/dist/components/Tooltip/tests/Tooltip.stories.js +31 -0
- package/dist/components/_private/Flyout/Flyout.context.d.ts +3 -1
- package/dist/components/_private/Flyout/Flyout.context.js +4 -1
- package/dist/components/_private/Flyout/Flyout.types.d.ts +1 -0
- package/dist/components/_private/Flyout/FlyoutContent.js +5 -7
- package/dist/components/_private/Flyout/FlyoutControlled.js +18 -12
- package/dist/components/_private/Flyout/FlyoutTrigger.js +3 -2
- package/dist/components/_private/Flyout/tests/Flyout.stories.d.ts +2 -7
- package/dist/components/_private/Flyout/tests/Flyout.stories.js +87 -38
- package/dist/components/_private/Portal/Portal.module.css +1 -1
- package/dist/hooks/_private/useOnClickOutside.js +5 -3
- package/dist/hooks/tests/useDrag.stories.d.ts +6 -0
- package/dist/hooks/tests/useDrag.stories.js +29 -0
- package/dist/hooks/useDrag.d.ts +17 -0
- package/dist/hooks/useDrag.js +116 -0
- package/dist/hooks/useHandlerRef.d.ts +8 -0
- package/dist/hooks/useHandlerRef.js +16 -0
- package/dist/hooks/useScrollLock.js +4 -3
- package/dist/hooks/useToggle.js +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -0
- package/dist/themes/_generator/tests/themes.stories.js +23 -0
- package/dist/themes/_generator/utilities/color.d.ts +16 -0
- package/dist/themes/_generator/utilities/color.js +54 -6
- package/dist/themes/_generator/utilities/generateBackgroundColors.js +4 -0
- package/dist/themes/index.d.ts +17 -0
- package/dist/themes/index.js +3 -0
- package/dist/types/config.d.ts +1 -0
- package/dist/types/global.d.ts +1 -1
- package/package.json +1 -1
@@ -0,0 +1,149 @@
|
|
1
|
+
"use client";
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
3
|
+
import React from "react";
|
4
|
+
import { classNames } from "../../utilities/helpers.js";
|
5
|
+
import useDrag from "../../hooks/useDrag.js";
|
6
|
+
import View from "../View/index.js";
|
7
|
+
import s from "./Resizable.module.css";
|
8
|
+
const PrivateResizableHandle = (props) => {
|
9
|
+
const { containerRef, onDrag, index, direction, children } = props;
|
10
|
+
const { ref, active } = useDrag((args) => {
|
11
|
+
onDrag({ ...args, index });
|
12
|
+
}, {
|
13
|
+
containerRef,
|
14
|
+
orientation: direction === "row" ? "horizontal" : "vertical",
|
15
|
+
});
|
16
|
+
const handleClassNames = classNames(s.handle, active && s["handle--dragging"]);
|
17
|
+
if (children)
|
18
|
+
return _jsx(View.Item, { children: children({ ref }) });
|
19
|
+
return (_jsx(View.Item, { className: handleClassNames, attributes: {
|
20
|
+
role: "button",
|
21
|
+
tabIndex: 0,
|
22
|
+
ref: (el) => {
|
23
|
+
// @ts-ignore
|
24
|
+
ref.current = el;
|
25
|
+
},
|
26
|
+
} }));
|
27
|
+
};
|
28
|
+
const PrivateResizableItem = React.forwardRef((props, ref) => {
|
29
|
+
const { children, defaultSize, minSize, maxSize } = props;
|
30
|
+
const itemRef = React.useRef(null);
|
31
|
+
return (_jsx(View.Item, { grow: true, className: s.item, attributes: {
|
32
|
+
ref: (el) => {
|
33
|
+
if (typeof ref === "function")
|
34
|
+
ref(el);
|
35
|
+
itemRef.current = el;
|
36
|
+
},
|
37
|
+
style: {
|
38
|
+
"--rs-resizable-default-size": defaultSize,
|
39
|
+
"--rs-resizable-min-size": minSize,
|
40
|
+
"--rs-resizable-max-size": maxSize,
|
41
|
+
},
|
42
|
+
}, children: children }));
|
43
|
+
});
|
44
|
+
const Resizable = (props) => {
|
45
|
+
const { children, height, direction = "row", gap = 2, className, attributes } = props;
|
46
|
+
const rootClassNames = classNames(s.root, s[`--direction-${direction}`], className);
|
47
|
+
const containerRef = React.useRef(null);
|
48
|
+
const itemsRef = React.useRef([]);
|
49
|
+
const horizontal = direction === "row";
|
50
|
+
let currentHandleIndex = 0;
|
51
|
+
let currentItemIndex = 0;
|
52
|
+
itemsRef.current = [];
|
53
|
+
const checkedCrossedBoundaries = (args) => {
|
54
|
+
const { item, grow, itemsSize, itemsCount } = args;
|
55
|
+
const { minSize, maxSize } = item.props;
|
56
|
+
const nextPx = (grow / itemsCount / 100) * itemsSize;
|
57
|
+
const minPx = minSize && Number(minSize.replace("px", ""));
|
58
|
+
const maxPx = maxSize && Number(maxSize?.replace("px", ""));
|
59
|
+
if (minPx && minPx > nextPx)
|
60
|
+
return true;
|
61
|
+
if (maxPx && maxPx < nextPx)
|
62
|
+
return true;
|
63
|
+
return false;
|
64
|
+
};
|
65
|
+
const onDrag = (args) => {
|
66
|
+
const { index, x, y, triggerX, triggerY } = args;
|
67
|
+
const startItem = itemsRef.current[index];
|
68
|
+
const endItem = itemsRef.current[index + 1];
|
69
|
+
if (!startItem.el || !endItem.el)
|
70
|
+
return;
|
71
|
+
const itemsCount = itemsRef.current.length;
|
72
|
+
// Each item has a flex-grow of 1 as default and these values get updated while dragging for the items around the handle
|
73
|
+
// Grow value of all items besides currently updating ones
|
74
|
+
let currentItemsGrow = itemsCount * 100;
|
75
|
+
let itemsSize = 0;
|
76
|
+
itemsRef.current.forEach((item, i) => {
|
77
|
+
if (!item.el)
|
78
|
+
return;
|
79
|
+
itemsSize += horizontal ? item.el.clientWidth : item.el.clientHeight;
|
80
|
+
if (i === index || i === index + 1)
|
81
|
+
return;
|
82
|
+
currentItemsGrow -= Number(item.el.style.flexGrow || 100);
|
83
|
+
}, 0);
|
84
|
+
const startSize = horizontal ? startItem.el.clientWidth : startItem.el.clientHeight;
|
85
|
+
const startOffset = horizontal ? startItem.el.offsetLeft : startItem.el.offsetTop;
|
86
|
+
const endSize = horizontal ? endItem.el.clientWidth : endItem.el.clientHeight;
|
87
|
+
// Handles containing triggers are located lower based on the gap and padding inside the handle
|
88
|
+
const gapCompensation = (horizontal ? triggerX : triggerY) - startSize - startOffset;
|
89
|
+
const dragCoord = (horizontal ? x : y) - gapCompensation;
|
90
|
+
// Total size of the dragging area
|
91
|
+
const currentItemsSize = startSize + endSize;
|
92
|
+
// x is calculated based on container but we're changing grow based on current items
|
93
|
+
const percent = Math.min(1, Math.max(0, (dragCoord - startOffset) / currentItemsSize));
|
94
|
+
const nextStartGrow = Math.floor(percent * currentItemsGrow);
|
95
|
+
const nextEndGrow = Math.floor(currentItemsGrow - nextStartGrow);
|
96
|
+
// Validate that next grow values won't break the min/max size values
|
97
|
+
if (checkedCrossedBoundaries({ item: startItem, itemsSize, grow: nextStartGrow, itemsCount })) {
|
98
|
+
return;
|
99
|
+
}
|
100
|
+
if (checkedCrossedBoundaries({ item: endItem, itemsSize, grow: nextEndGrow, itemsCount })) {
|
101
|
+
return;
|
102
|
+
}
|
103
|
+
startItem.el.style.flexGrow = nextStartGrow.toString();
|
104
|
+
endItem.el.style.flexGrow = nextEndGrow.toString();
|
105
|
+
};
|
106
|
+
/**
|
107
|
+
* When passing sizes, items first get rendered with css
|
108
|
+
* and then have to be hydrated with flexGrow to enable correct resizing
|
109
|
+
*/
|
110
|
+
React.useEffect(() => {
|
111
|
+
const growValues = [];
|
112
|
+
// Calculate total size of items excluding gaps
|
113
|
+
let totalItemsSize = 0;
|
114
|
+
itemsRef.current.forEach((item) => {
|
115
|
+
if (!item.el)
|
116
|
+
return;
|
117
|
+
totalItemsSize += horizontal ? item.el.clientWidth : item.el.clientHeight;
|
118
|
+
});
|
119
|
+
// Calculate flex grow values of all items rendered by css originally
|
120
|
+
itemsRef.current.forEach((item, i) => {
|
121
|
+
if (!item.el)
|
122
|
+
return;
|
123
|
+
const itemSizePercent = (horizontal ? item.el.clientWidth : item.el.clientHeight) / totalItemsSize;
|
124
|
+
growValues[i] = itemsRef.current.length * itemSizePercent * 100;
|
125
|
+
});
|
126
|
+
// Apply flex grow after calculation is done to avoid layout shifts during the calculation
|
127
|
+
itemsRef.current.forEach((item, i) => {
|
128
|
+
if (!item.el || !growValues[i])
|
129
|
+
return;
|
130
|
+
item.el.style.flexGrow = growValues[i].toString();
|
131
|
+
item.el.setAttribute("data-rs-resizable-item-mounted", "");
|
132
|
+
});
|
133
|
+
}, [horizontal]);
|
134
|
+
const output = React.Children.map(children, (child) => {
|
135
|
+
const isComponent = React.isValidElement(child);
|
136
|
+
if (isComponent && child.type === Resizable.Handle && child.props) {
|
137
|
+
return (_jsx(PrivateResizableHandle, { ...child.props, containerRef: containerRef, index: currentHandleIndex++, onDrag: onDrag, direction: direction }));
|
138
|
+
}
|
139
|
+
if (isComponent && child.type === Resizable.Item && child.props) {
|
140
|
+
const index = currentHandleIndex;
|
141
|
+
return (_jsx(PrivateResizableItem, { ...child.props, index: currentItemIndex++, ref: (el) => (itemsRef.current[index] = { el, props: child.props }) }));
|
142
|
+
}
|
143
|
+
return null;
|
144
|
+
});
|
145
|
+
return (_jsx(View, { attributes: { ...attributes, ref: containerRef }, className: rootClassNames, height: height, direction: direction, align: "stretch", gap: gap, children: output }));
|
146
|
+
};
|
147
|
+
Resizable.Item = (() => null);
|
148
|
+
Resizable.Handle = (() => null);
|
149
|
+
export default Resizable;
|
@@ -0,0 +1 @@
|
|
1
|
+
.item{--rs-resizable-default-size:none;--rs-resizable-min-size:0;--rs-resizable-max-size:100%;flex-grow:100;max-width:var(--rs-resizable-default-size);min-width:var(--rs-resizable-default-size);overflow:hidden}.handle{flex-shrink:0;position:relative}.handle:after,.handle:before{border-radius:999px;content:"";position:absolute}.handle:after{background:var(--rs-color-border-neutral);opacity:0;transition:opacity var(--rs-duration-fast) var(--rs-easing-standard)}.handle--dragging:after,.handle:focus-visible:after,.handle:hover:after{opacity:.6}body:has(.handle--dragging) .handle:not(.handle--dragging){opacity:0}body:has(.--direction-row>.handle--dragging){cursor:ew-resize}body:has(.--direction-column>.handle--dragging){cursor:ns-resize}.--direction-row>.handle{cursor:ew-resize;height:100%}.--direction-row>.handle:after,.--direction-row>.handle:before{inset-block:0;inset-inline-start:50%;transform:translateX(-50%)}.--direction-row>.handle:before{width:var(--rs-unit-x4)}.--direction-row>.handle:after{width:var(--rs-unit-x1)}.--direction-row>.item[data-rs-resizable-item-mounted]{max-width:var(--rs-resizable-max-size);min-width:var(--rs-resizable-min-size)}.--direction-column>.handle{cursor:ns-resize;width:100%}.--direction-column>.handle:after,.--direction-column>.handle:before{inset-block-start:50%;inset-inline:0;transform:translateY(-50%)}.--direction-column>.handle:before{height:var(--rs-unit-x4)}.--direction-column>.handle:after{height:var(--rs-unit-x1)}.--direction-column>.item[data-rs-resizable-item-mounted]{max-height:var(--rs-resizable-max-size);min-height:var(--rs-resizable-min-size)}
|
@@ -0,0 +1,29 @@
|
|
1
|
+
import type React from "react";
|
2
|
+
import type { ViewProps } from "../View";
|
3
|
+
import type { UseDragCallbackArgs } from "../../hooks/useDrag";
|
4
|
+
export type Props = Pick<ViewProps, "children" | "className" | "attributes" | "height" | "direction" | "gap">;
|
5
|
+
export type ItemProps = {
|
6
|
+
children: React.ReactNode;
|
7
|
+
minSize?: `${number}px`;
|
8
|
+
maxSize?: `${number}px`;
|
9
|
+
defaultSize?: `${number}px`;
|
10
|
+
};
|
11
|
+
export type PrivateItemProps = ItemProps & {
|
12
|
+
index: number;
|
13
|
+
};
|
14
|
+
export type HandleProps = {
|
15
|
+
children?: (attributes: {
|
16
|
+
ref: React.RefObject<HTMLButtonElement>;
|
17
|
+
}) => React.ReactNode;
|
18
|
+
};
|
19
|
+
export type PrivateHandleProps = HandleProps & {
|
20
|
+
containerRef: React.RefObject<HTMLDivElement>;
|
21
|
+
index: number;
|
22
|
+
onDrag: (args: UseDragCallbackArgs & {
|
23
|
+
index: number;
|
24
|
+
}) => void;
|
25
|
+
} & Pick<Props, "direction">;
|
26
|
+
export type ItemsRefProps = {
|
27
|
+
el: HTMLDivElement | null;
|
28
|
+
props: ItemProps;
|
29
|
+
}[];
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -0,0 +1 @@
|
|
1
|
+
export { default } from "./Resizable.js";
|
@@ -0,0 +1,15 @@
|
|
1
|
+
declare const _default: {
|
2
|
+
title: string;
|
3
|
+
component: {
|
4
|
+
(props: import("./..").ResizableProps): import("react").JSX.Element;
|
5
|
+
Item: import("react").FC<import("./..").ResizableItemProps>;
|
6
|
+
Handle: import("react").FC<import("./..").ResizableHandleProps>;
|
7
|
+
};
|
8
|
+
parameters: {
|
9
|
+
iframe: {
|
10
|
+
url: string;
|
11
|
+
};
|
12
|
+
};
|
13
|
+
};
|
14
|
+
export default _default;
|
15
|
+
export declare const base: () => import("react").JSX.Element;
|
@@ -0,0 +1,58 @@
|
|
1
|
+
import { Example } from "../../../utilities/storybook/index.js";
|
2
|
+
import Resizable from "../index.js";
|
3
|
+
import View from "../../View/index.js";
|
4
|
+
import Button from "../../Button/index.js";
|
5
|
+
export default {
|
6
|
+
title: "Components/Resizable",
|
7
|
+
component: Resizable,
|
8
|
+
parameters: {
|
9
|
+
iframe: {
|
10
|
+
url: "https://reshaped.so/docs/utilities/Resizable",
|
11
|
+
},
|
12
|
+
},
|
13
|
+
};
|
14
|
+
export const base = () => (<Example>
|
15
|
+
<Example.Item>
|
16
|
+
<Resizable height="300px" gap={4}>
|
17
|
+
<Resizable.Item minSize="100px" defaultSize="200px">
|
18
|
+
<View backgroundColor="neutral-faded" borderRadius="medium" align="center" justify="center" height="100%">
|
19
|
+
Panel
|
20
|
+
</View>
|
21
|
+
</Resizable.Item>
|
22
|
+
<Resizable.Handle />
|
23
|
+
<Resizable.Item>
|
24
|
+
<View backgroundColor="neutral-faded" borderRadius="medium" align="center" justify="center" height="100%">
|
25
|
+
Panel
|
26
|
+
</View>
|
27
|
+
</Resizable.Item>
|
28
|
+
<Resizable.Handle />
|
29
|
+
<Resizable.Item>
|
30
|
+
<Resizable height="100%" direction="column">
|
31
|
+
<Resizable.Item minSize="50px" maxSize="100px">
|
32
|
+
<View backgroundColor="neutral-faded" borderRadius="medium" align="center" justify="center" height="100%">
|
33
|
+
Panel
|
34
|
+
</View>
|
35
|
+
</Resizable.Item>
|
36
|
+
<Resizable.Handle />
|
37
|
+
<Resizable.Item>
|
38
|
+
<View backgroundColor="neutral-faded" borderRadius="medium" align="center" justify="center" height="100%">
|
39
|
+
Panel
|
40
|
+
</View>
|
41
|
+
</Resizable.Item>
|
42
|
+
<Resizable.Handle>
|
43
|
+
{(attributes) => (<View backgroundColor="primary-faded" padding={1} align="center" borderRadius="small">
|
44
|
+
<Button attributes={attributes} type="button">
|
45
|
+
Drag me
|
46
|
+
</Button>
|
47
|
+
</View>)}
|
48
|
+
</Resizable.Handle>
|
49
|
+
<Resizable.Item>
|
50
|
+
<View backgroundColor="neutral-faded" borderRadius="medium" align="center" justify="center" height="100%">
|
51
|
+
Panel
|
52
|
+
</View>
|
53
|
+
</Resizable.Item>
|
54
|
+
</Resizable>
|
55
|
+
</Resizable.Item>
|
56
|
+
</Resizable>
|
57
|
+
</Example.Item>
|
58
|
+
</Example>);
|
@@ -7,8 +7,10 @@ import getHeightStyles from "../../styles/height/index.js";
|
|
7
7
|
import getMaxHeightStyles from "../../styles/maxHeight/index.js";
|
8
8
|
import useIsomorphicLayoutEffect from "../../hooks/useIsomorphicLayoutEffect.js";
|
9
9
|
import s from "./ScrollArea.module.css";
|
10
|
+
import useHandlerRef from "../../hooks/useHandlerRef.js";
|
10
11
|
const ScrollAreaBar = (props) => {
|
11
12
|
const { ratio, position, vertical, onThumbMove } = props;
|
13
|
+
const onThumbMoveRef = useHandlerRef(onThumbMove);
|
12
14
|
const [dragging, setDragging] = React.useState(false);
|
13
15
|
const dragStartPositionRef = React.useRef(0);
|
14
16
|
const barRef = React.useRef(null);
|
@@ -38,10 +40,8 @@ const ScrollAreaBar = (props) => {
|
|
38
40
|
return;
|
39
41
|
const diff = vertical ? e.movementY : e.movementX;
|
40
42
|
const total = vertical ? elBar.scrollHeight : elBar.scrollWidth;
|
41
|
-
|
42
|
-
},
|
43
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
44
|
-
[ratio, vertical, dragging]);
|
43
|
+
onThumbMoveRef.current?.({ value: diff / total, type: "relative" });
|
44
|
+
}, [vertical, dragging, onThumbMoveRef]);
|
45
45
|
const handleDragEnd = React.useCallback(() => {
|
46
46
|
setDragging(false);
|
47
47
|
enableUserSelect();
|
@@ -48,9 +48,9 @@ type BaseProps = {
|
|
48
48
|
min?: number;
|
49
49
|
max?: number;
|
50
50
|
orientation?: "vertical" | "horizontal";
|
51
|
-
renderValue?: (args: {
|
51
|
+
renderValue?: ((args: {
|
52
52
|
value: number;
|
53
|
-
}) => string;
|
53
|
+
}) => string) | false;
|
54
54
|
className?: G.ClassName;
|
55
55
|
attributes?: G.Attributes<"div">;
|
56
56
|
};
|
@@ -15,10 +15,10 @@ export const applyStepToValue = (value, step) => {
|
|
15
15
|
export const getDragCoord = ({ event, vertical, }) => {
|
16
16
|
if (vertical) {
|
17
17
|
if (event instanceof MouseEvent)
|
18
|
-
return event.
|
19
|
-
return event.changedTouches[0].
|
18
|
+
return event.clientY;
|
19
|
+
return event.changedTouches[0].clientY;
|
20
20
|
}
|
21
21
|
if (event instanceof MouseEvent)
|
22
|
-
return event.
|
23
|
-
return event.changedTouches[0].
|
22
|
+
return event.clientX;
|
23
|
+
return event.changedTouches[0].clientX;
|
24
24
|
};
|
@@ -9,9 +9,12 @@ import { useFormControl } from "../FormControl/index.js";
|
|
9
9
|
import SliderThumb from "./SliderThumb.js";
|
10
10
|
import { applyStepToValue, getDragCoord } from "./Slider.utilities.js";
|
11
11
|
import s from "./Slider.module.css";
|
12
|
+
import useHandlerRef from "../../hooks/useHandlerRef.js";
|
12
13
|
const THUMB_SIZE = 16;
|
13
14
|
const SliderControlled = (props) => {
|
14
15
|
const { name, range, max, min, step = 1, onChange, onChangeCommit, renderValue, className, attributes, orientation = "horizontal", } = props;
|
16
|
+
const onChangeRef = useHandlerRef(onChange);
|
17
|
+
const onChangeCommitRef = useHandlerRef(onChangeCommit);
|
15
18
|
const vertical = orientation === "vertical";
|
16
19
|
const minValue = range && props.minValue !== undefined ? applyStepToValue(props.minValue, step) : undefined;
|
17
20
|
const maxValue = applyStepToValue(range ? props.maxValue : props.value, step);
|
@@ -79,22 +82,21 @@ const SliderControlled = (props) => {
|
|
79
82
|
const handleMinChange = React.useCallback((value, options) => {
|
80
83
|
if (!range)
|
81
84
|
return;
|
82
|
-
const method = options?.commit ?
|
85
|
+
const method = options?.commit ? onChangeCommitRef.current : onChangeRef.current;
|
86
|
+
// @ts-ignore - creating refs out of handler props loses connection to the range flag
|
83
87
|
method?.({ minValue: value, maxValue, name });
|
84
|
-
},
|
85
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
86
|
-
[maxValue, name, range]);
|
88
|
+
}, [maxValue, name, range, onChangeCommitRef, onChangeRef]);
|
87
89
|
const handleMaxChange = React.useCallback((value, options) => {
|
88
90
|
if (range) {
|
89
|
-
const method = options?.commit ?
|
91
|
+
const method = options?.commit ? onChangeCommitRef.current : onChangeRef.current;
|
92
|
+
// @ts-ignore - creating refs out of handler props loses connection to the range flag
|
90
93
|
method?.({ minValue: minValue, maxValue: value, name });
|
91
94
|
return;
|
92
95
|
}
|
93
|
-
const method = options?.commit ?
|
96
|
+
const method = options?.commit ? onChangeCommitRef.current : onChangeRef.current;
|
97
|
+
// @ts-ignore - creating refs out of handler props loses connection to the range flag
|
94
98
|
method?.({ value, name });
|
95
|
-
},
|
96
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
97
|
-
[minValue, name, range]);
|
99
|
+
}, [minValue, name, range, onChangeRef, onChangeCommitRef]);
|
98
100
|
const handleMouseDown = ({ nativeEvent }) => {
|
99
101
|
if (disabled)
|
100
102
|
return;
|
@@ -15,6 +15,6 @@ const SliderThumb = (props, ref) => {
|
|
15
15
|
const handleChange = (e) => {
|
16
16
|
onChange(+e.target.value);
|
17
17
|
};
|
18
|
-
return (_jsxs(_Fragment, { children: [_jsx("input", { className: s.input, type: "range", name: name, value: value, onChange: handleChange, disabled: disabled, max: max, min: min, step: step, "aria-labelledby": id, "aria-orientation": orientation }), _jsx("div", { ref: ref, className: thumbClassNames, onMouseDown: onDragStart, onTouchStart: onDragStart, style: { "--ts-slider-thumb-position": `${position}%` }, id: id, "aria-hidden": "true", children: _jsx(Theme, { colorMode: "inverted", children: _jsx(Text, { variant: "caption-1", weight: "medium", className: s.tooltip, attributes: { ref: tooltipRef }, children: tooltipValue }) }) })] }));
|
18
|
+
return (_jsxs(_Fragment, { children: [_jsx("input", { className: s.input, type: "range", name: name, value: value, onChange: handleChange, disabled: disabled, max: max, min: min, step: step, "aria-labelledby": id, "aria-orientation": orientation }), _jsx("div", { ref: ref, className: thumbClassNames, onMouseDown: onDragStart, onTouchStart: onDragStart, style: { "--ts-slider-thumb-position": `${position}%` }, id: id, "aria-hidden": "true", children: renderValue !== false && (_jsx(Theme, { colorMode: "inverted", children: _jsx(Text, { variant: "caption-1", weight: "medium", className: s.tooltip, attributes: { ref: tooltipRef }, children: tooltipValue }) })) })] }));
|
19
19
|
};
|
20
20
|
export default React.forwardRef(SliderThumb);
|
@@ -22,6 +22,7 @@ export const direction = () => (<Example>
|
|
22
22
|
<View height="200px">
|
23
23
|
<Slider range name="slider" defaultMinValue={30} defaultMaxValue={100} orientation="vertical"/>
|
24
24
|
</View>
|
25
|
+
<View height="2000px"/>
|
25
26
|
</Example.Item>
|
26
27
|
</Example>);
|
27
28
|
export const step = () => (<Example>
|
@@ -49,6 +50,9 @@ export const customRender = () => (<Example>
|
|
49
50
|
<Example.Item title="custom render">
|
50
51
|
<Slider name="slider" defaultValue={30} renderValue={(args) => `$${args.value}`}/>
|
51
52
|
</Example.Item>
|
53
|
+
<Example.Item title="no tooltip">
|
54
|
+
<Slider name="slider" defaultValue={30} renderValue={false}/>
|
55
|
+
</Example.Item>
|
52
56
|
</Example>);
|
53
57
|
export const formControl = () => (<Example>
|
54
58
|
<Example.Item title="form control, disabled">
|
@@ -37,23 +37,24 @@ export type ContainerProps = {
|
|
37
37
|
status?: "entering" | "entered" | "exiting";
|
38
38
|
inspected: boolean;
|
39
39
|
};
|
40
|
+
export type ShowOptions = {
|
41
|
+
timeout?: Timeout;
|
42
|
+
position?: Position;
|
43
|
+
};
|
44
|
+
export type ShowProps = Props & ShowOptions;
|
40
45
|
export type Context = {
|
41
46
|
options?: ProviderProps["options"];
|
42
47
|
queues: Record<RegionProps["position"], Array<ContainerProps>>;
|
43
|
-
add: (toast:
|
48
|
+
add: (toast: ShowProps) => string;
|
44
49
|
show: (id: string) => void;
|
45
50
|
hide: (id: string) => void;
|
46
51
|
remove: (id: string) => void;
|
47
52
|
id: string;
|
48
53
|
};
|
49
|
-
export type ShowOptions = {
|
50
|
-
timeout?: Timeout;
|
51
|
-
position?: Position;
|
52
|
-
};
|
53
54
|
type AddAction = {
|
54
55
|
type: "add";
|
55
56
|
payload: {
|
56
|
-
toastProps:
|
57
|
+
toastProps: ShowProps;
|
57
58
|
id: string;
|
58
59
|
};
|
59
60
|
};
|
@@ -1,3 +1,3 @@
|
|
1
1
|
export { default as useToast } from "./useToast";
|
2
2
|
export { default as ToastProvider } from "./ToastProvider";
|
3
|
-
export type { Props as ToastProps, ProviderProps as ToastProviderProps } from "./Toast.types";
|
3
|
+
export type { Props as ToastProps, ProviderProps as ToastProviderProps, ShowProps as ToastShowProps, } from "./Toast.types";
|
@@ -121,4 +121,35 @@ export const edgeCases = () => (<Example>
|
|
121
121
|
</Actionable>)}
|
122
122
|
</Tooltip>
|
123
123
|
</Example.Item>
|
124
|
+
|
125
|
+
<Example.Item title="nested popovers inside a tooltip">
|
126
|
+
<Tooltip position="top" text="Hello">
|
127
|
+
{(tooltipAttributes) => (<Popover position="bottom">
|
128
|
+
<Popover.Trigger>
|
129
|
+
{(attributes) => (<Button color="primary" attributes={{
|
130
|
+
...tooltipAttributes,
|
131
|
+
...attributes,
|
132
|
+
}}>
|
133
|
+
Open
|
134
|
+
</Button>)}
|
135
|
+
</Popover.Trigger>
|
136
|
+
<Popover.Content>
|
137
|
+
<View gap={2} align="start">
|
138
|
+
Popover content
|
139
|
+
<Popover position="bottom">
|
140
|
+
<Popover.Trigger>
|
141
|
+
{(attributes) => <Button attributes={attributes}>Open</Button>}
|
142
|
+
</Popover.Trigger>
|
143
|
+
<Popover.Content>
|
144
|
+
<View gap={2} align="start">
|
145
|
+
Popover content
|
146
|
+
<Button onClick={() => { }}>Button</Button>
|
147
|
+
</View>
|
148
|
+
</Popover.Content>
|
149
|
+
</Popover>
|
150
|
+
</View>
|
151
|
+
</Popover.Content>
|
152
|
+
</Popover>)}
|
153
|
+
</Tooltip>
|
154
|
+
</Example.Item>
|
124
155
|
</Example>);
|
@@ -3,7 +3,9 @@ import type * as T from "./Flyout.types";
|
|
3
3
|
declare const FlyoutContext: React.Context<T.ContextProps>;
|
4
4
|
declare const useFlyoutContext: () => T.ContextProps;
|
5
5
|
declare const useFlyoutTriggerContext: () => T.TriggerContextProps;
|
6
|
+
declare const useFlyoutContentContext: () => boolean;
|
6
7
|
declare const Provider: React.Provider<T.ContextProps>;
|
7
8
|
declare const TriggerProvider: React.Provider<T.TriggerContextProps>;
|
8
|
-
|
9
|
+
declare const ContentProvider: React.Provider<boolean>;
|
10
|
+
export { Provider, TriggerProvider, ContentProvider, useFlyoutContext, useFlyoutTriggerContext, useFlyoutContentContext, };
|
9
11
|
export default FlyoutContext;
|
@@ -2,9 +2,12 @@
|
|
2
2
|
import React from "react";
|
3
3
|
const FlyoutContext = React.createContext({});
|
4
4
|
const FlyoutTriggerContext = React.createContext({});
|
5
|
+
const FlyoutContentContext = React.createContext(false);
|
5
6
|
const useFlyoutContext = () => React.useContext(FlyoutContext);
|
6
7
|
const useFlyoutTriggerContext = () => React.useContext(FlyoutTriggerContext);
|
8
|
+
const useFlyoutContentContext = () => React.useContext(FlyoutContentContext);
|
7
9
|
const Provider = FlyoutContext.Provider;
|
8
10
|
const TriggerProvider = FlyoutTriggerContext.Provider;
|
9
|
-
|
11
|
+
const ContentProvider = FlyoutContentContext.Provider;
|
12
|
+
export { Provider, TriggerProvider, ContentProvider, useFlyoutContext, useFlyoutTriggerContext, useFlyoutContentContext, };
|
10
13
|
export default FlyoutContext;
|
@@ -6,7 +6,7 @@ import useIsomorphicLayoutEffect from "../../../hooks/useIsomorphicLayoutEffect.
|
|
6
6
|
import Portal from "../Portal/index.js";
|
7
7
|
import { getClosestFlyoutTarget } from "../../../utilities/dom.js";
|
8
8
|
import cooldown from "./utilities/cooldown.js";
|
9
|
-
import { useFlyoutContext } from "./Flyout.context.js";
|
9
|
+
import { useFlyoutContext, ContentProvider } from "./Flyout.context.js";
|
10
10
|
import s from "./Flyout.module.css";
|
11
11
|
const FlyoutContent = (props) => {
|
12
12
|
const { children, className, attributes } = props;
|
@@ -48,12 +48,10 @@ const FlyoutContent = (props) => {
|
|
48
48
|
else if (trapFocusMode === "action-menu") {
|
49
49
|
role = "menu";
|
50
50
|
}
|
51
|
-
const content = (
|
52
|
-
|
53
|
-
|
54
|
-
...
|
55
|
-
"--rs-flyout-gap": contentGap,
|
56
|
-
}, ref: flyoutElRef, onTransitionEnd: handleTransitionEnd, onMouseEnter: triggerType === "hover" ? handleMouseEnter : undefined, onMouseLeave: triggerType === "hover" ? handleMouseLeave : undefined, onMouseDown: handleContentMouseDown, onTouchStart: handleContentMouseDown, onMouseUp: handleContentMouseUp, onTouchEnd: handleContentMouseUp, children: _jsx("div", { role: role, ...attributes, id: id, "aria-modal": triggerType === "click", style: contentAttributes?.style, className: innerClassNames, children: children }) }));
|
51
|
+
const content = (_jsx(ContentProvider, { value: true, children: _jsx("div", { className: contentClassNames, style: {
|
52
|
+
...styles,
|
53
|
+
"--rs-flyout-gap": contentGap,
|
54
|
+
}, ref: flyoutElRef, onTransitionEnd: handleTransitionEnd, onMouseEnter: triggerType === "hover" ? handleMouseEnter : undefined, onMouseLeave: triggerType === "hover" ? handleMouseLeave : undefined, onMouseDown: handleContentMouseDown, onTouchStart: handleContentMouseDown, onMouseUp: handleContentMouseUp, onTouchEnd: handleContentMouseUp, children: _jsx("div", { role: role, ...attributes, id: id, "aria-modal": triggerType === "click", style: contentAttributes?.style, className: innerClassNames, children: children }) }) }));
|
57
55
|
const closestScrollable = getClosestFlyoutTarget(triggerElRef.current);
|
58
56
|
const scrollableRef = closestScrollable === document.body ? undefined : { current: closestScrollable };
|
59
57
|
return _jsx(Portal, { targetRef: containerRef || scrollableRef, children: content });
|
@@ -13,12 +13,16 @@ import { checkKeyboardMode } from "../../../utilities/a11y/keyboardMode.js";
|
|
13
13
|
import useFlyout from "./useFlyout.js";
|
14
14
|
import * as timeouts from "./Flyout.constants.js";
|
15
15
|
import cooldown from "./utilities/cooldown.js";
|
16
|
-
import { Provider, useFlyoutTriggerContext, useFlyoutContext } from "./Flyout.context.js";
|
16
|
+
import { Provider, useFlyoutTriggerContext, useFlyoutContext, useFlyoutContentContext, } from "./Flyout.context.js";
|
17
|
+
import useHandlerRef from "../../../hooks/useHandlerRef.js";
|
17
18
|
const FlyoutRoot = (props) => {
|
18
|
-
const { triggerType = "click", onOpen, onClose, children, disabled, forcePosition, trapFocusMode, width, disableHideAnimation, disableContentHover, contentGap, contentClassName, contentAttributes, position: passedPosition, active: passedActive, id: passedId, instanceRef, containerRef, } = props;
|
19
|
+
const { triggerType = "click", onOpen, onClose, children, disabled, forcePosition, trapFocusMode, width, disableHideAnimation, disableContentHover, disableCloseOnOutsideClick, contentGap, contentClassName, contentAttributes, position: passedPosition, active: passedActive, id: passedId, instanceRef, containerRef, } = props;
|
20
|
+
const onOpenRef = useHandlerRef(onOpen);
|
21
|
+
const onCloseRef = useHandlerRef(onClose);
|
19
22
|
const resolvedActive = disabled === true ? false : passedActive;
|
20
23
|
const parentFlyoutContext = useFlyoutContext();
|
21
24
|
const parentFlyoutTriggerContext = useFlyoutTriggerContext();
|
25
|
+
const parentFlyoutContentContext = useFlyoutContentContext();
|
22
26
|
const isSubmenu = parentFlyoutContext.trapFocusMode === "action-menu" ||
|
23
27
|
parentFlyoutContext.trapFocusMode === "content-menu";
|
24
28
|
const [isRTL] = useRTL();
|
@@ -27,7 +31,8 @@ const FlyoutRoot = (props) => {
|
|
27
31
|
* Reuse the parent trigger ref in case we render nested triggers
|
28
32
|
* For example, when we apply tooltip and popover to the same button
|
29
33
|
*/
|
30
|
-
const triggerElRef = parentFlyoutTriggerContext?.triggerElRef ||
|
34
|
+
const triggerElRef = (!parentFlyoutContentContext && parentFlyoutTriggerContext?.triggerElRef) ||
|
35
|
+
internalTriggerElRef;
|
31
36
|
const flyoutElRef = React.useRef(null);
|
32
37
|
const id = useElementId(passedId);
|
33
38
|
const timerRef = React.useRef();
|
@@ -58,20 +63,17 @@ const FlyoutRoot = (props) => {
|
|
58
63
|
const canOpen = !lockedRef.current && status === "idle";
|
59
64
|
if (!canOpen)
|
60
65
|
return;
|
61
|
-
|
62
|
-
|
63
|
-
}, [status]);
|
66
|
+
onOpenRef.current?.();
|
67
|
+
}, [status, onOpenRef]);
|
64
68
|
const handleClose = React.useCallback((options) => {
|
65
69
|
const isLocked = triggerType === "click" && !isDismissible();
|
66
70
|
const canClose = !isLocked && (status !== "idle" || disabled);
|
67
71
|
if (!canClose)
|
68
72
|
return;
|
69
|
-
|
73
|
+
onCloseRef.current?.();
|
70
74
|
if (options?.closeParents)
|
71
75
|
parentFlyoutContext?.handleClose?.();
|
72
|
-
},
|
73
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
74
|
-
[status, isDismissible, triggerType]);
|
76
|
+
}, [status, isDismissible, triggerType, onCloseRef, disabled, parentFlyoutContext]);
|
75
77
|
/**
|
76
78
|
* Trigger event handlers
|
77
79
|
*/
|
@@ -174,15 +176,17 @@ const FlyoutRoot = (props) => {
|
|
174
176
|
useIsomorphicLayoutEffect(() => {
|
175
177
|
if (status !== "visible" || !flyoutElRef.current)
|
176
178
|
return;
|
179
|
+
if (trapFocusRef.current?.trapped)
|
180
|
+
return;
|
177
181
|
trapFocusRef.current = new TrapFocus(flyoutElRef.current);
|
178
182
|
trapFocusRef.current.trap({
|
179
183
|
mode: trapFocusMode,
|
180
|
-
includeTrigger: triggerType === "hover" && trapFocusMode
|
184
|
+
includeTrigger: triggerType === "hover" && trapFocusMode !== "dialog" && !isSubmenu,
|
181
185
|
onNavigateOutside: () => {
|
182
186
|
handleClose();
|
183
187
|
},
|
184
188
|
});
|
185
|
-
}, [status, triggerType,
|
189
|
+
}, [status, triggerType, trapFocusMode]);
|
186
190
|
React.useEffect(() => {
|
187
191
|
if (!disableHideAnimation && status !== "hidden")
|
188
192
|
return;
|
@@ -229,6 +233,8 @@ const FlyoutRoot = (props) => {
|
|
229
233
|
}), [handleOpen, handleClose, updatePosition]);
|
230
234
|
useHotkeys({ Escape: () => handleClose() }, [handleClose]);
|
231
235
|
useOnClickOutside([flyoutElRef, triggerElRef], () => {
|
236
|
+
if (disableCloseOnOutsideClick)
|
237
|
+
return;
|
232
238
|
// Clicking outside changes focused element so we don't need to set it back ourselves
|
233
239
|
shouldReturnFocusRef.current = false;
|
234
240
|
handleClose();
|