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.
Files changed (71) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/bin/cli.js +0 -1
  3. package/dist/bundle.css +1 -1
  4. package/dist/bundle.d.ts +3 -1
  5. package/dist/bundle.js +10 -10
  6. package/dist/cjs/themes/_generator/utilities/color.d.ts +16 -0
  7. package/dist/cjs/themes/_generator/utilities/color.js +57 -7
  8. package/dist/cjs/themes/_generator/utilities/generateBackgroundColors.js +4 -0
  9. package/dist/cjs/themes/_generator/utilities/tests/color.test.js +73 -42
  10. package/dist/cjs/themes/index.d.ts +17 -0
  11. package/dist/cjs/themes/index.js +3 -0
  12. package/dist/cjs/types/config.d.ts +1 -0
  13. package/dist/components/Button/Button.module.css +1 -1
  14. package/dist/components/Button/tests/Button.stories.js +3 -1
  15. package/dist/components/DropdownMenu/DropdownMenu.module.css +1 -1
  16. package/dist/components/DropdownMenu/DropdownMenu.types.d.ts +1 -1
  17. package/dist/components/Modal/Modal.js +4 -3
  18. package/dist/components/Modal/tests/Modal.stories.d.ts +0 -1
  19. package/dist/components/Modal/tests/Modal.stories.js +0 -16
  20. package/dist/components/Overlay/Overlay.js +7 -7
  21. package/dist/components/Overlay/tests/Overlay.stories.js +3 -1
  22. package/dist/components/Popover/Popover.js +2 -2
  23. package/dist/components/Popover/Popover.types.d.ts +1 -1
  24. package/dist/components/Resizable/Resizable.d.ts +8 -0
  25. package/dist/components/Resizable/Resizable.js +149 -0
  26. package/dist/components/Resizable/Resizable.module.css +1 -0
  27. package/dist/components/Resizable/Resizable.types.d.ts +29 -0
  28. package/dist/components/Resizable/Resizable.types.js +1 -0
  29. package/dist/components/Resizable/index.d.ts +2 -0
  30. package/dist/components/Resizable/index.js +1 -0
  31. package/dist/components/Resizable/tests/Resizable.stories.d.ts +15 -0
  32. package/dist/components/Resizable/tests/Resizable.stories.js +58 -0
  33. package/dist/components/ScrollArea/ScrollArea.js +4 -4
  34. package/dist/components/Slider/Slider.types.d.ts +2 -2
  35. package/dist/components/Slider/Slider.utilities.js +4 -4
  36. package/dist/components/Slider/SliderControlled.js +11 -9
  37. package/dist/components/Slider/SliderThumb.js +1 -1
  38. package/dist/components/Slider/tests/Slider.stories.js +4 -0
  39. package/dist/components/Toast/Toast.types.d.ts +7 -6
  40. package/dist/components/Toast/index.d.ts +1 -1
  41. package/dist/components/Toast/useToast.d.ts +1 -1
  42. package/dist/components/Tooltip/tests/Tooltip.stories.js +31 -0
  43. package/dist/components/_private/Flyout/Flyout.context.d.ts +3 -1
  44. package/dist/components/_private/Flyout/Flyout.context.js +4 -1
  45. package/dist/components/_private/Flyout/Flyout.types.d.ts +1 -0
  46. package/dist/components/_private/Flyout/FlyoutContent.js +5 -7
  47. package/dist/components/_private/Flyout/FlyoutControlled.js +18 -12
  48. package/dist/components/_private/Flyout/FlyoutTrigger.js +3 -2
  49. package/dist/components/_private/Flyout/tests/Flyout.stories.d.ts +2 -7
  50. package/dist/components/_private/Flyout/tests/Flyout.stories.js +87 -38
  51. package/dist/components/_private/Portal/Portal.module.css +1 -1
  52. package/dist/hooks/_private/useOnClickOutside.js +5 -3
  53. package/dist/hooks/tests/useDrag.stories.d.ts +6 -0
  54. package/dist/hooks/tests/useDrag.stories.js +29 -0
  55. package/dist/hooks/useDrag.d.ts +17 -0
  56. package/dist/hooks/useDrag.js +116 -0
  57. package/dist/hooks/useHandlerRef.d.ts +8 -0
  58. package/dist/hooks/useHandlerRef.js +16 -0
  59. package/dist/hooks/useScrollLock.js +4 -3
  60. package/dist/hooks/useToggle.js +1 -1
  61. package/dist/index.d.ts +3 -1
  62. package/dist/index.js +1 -0
  63. package/dist/themes/_generator/tests/themes.stories.js +23 -0
  64. package/dist/themes/_generator/utilities/color.d.ts +16 -0
  65. package/dist/themes/_generator/utilities/color.js +54 -6
  66. package/dist/themes/_generator/utilities/generateBackgroundColors.js +4 -0
  67. package/dist/themes/index.d.ts +17 -0
  68. package/dist/themes/index.js +3 -0
  69. package/dist/types/config.d.ts +1 -0
  70. package/dist/types/global.d.ts +1 -1
  71. 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,2 @@
1
+ export { default } from "./Resizable";
2
+ export type { Props as ResizableProps, ItemProps as ResizableItemProps, HandleProps as ResizableHandleProps, } from "./Resizable.types";
@@ -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
- onThumbMove({ value: diff / total, type: "relative" });
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.pageY || event.screenY;
19
- return event.changedTouches[0].pageY;
18
+ return event.clientY;
19
+ return event.changedTouches[0].clientY;
20
20
  }
21
21
  if (event instanceof MouseEvent)
22
- return event.pageX || event.screenX;
23
- return event.changedTouches[0].pageX;
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 ? onChangeCommit : onChange;
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 ? onChangeCommit : onChange;
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 ? onChangeCommit : onChange;
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: Props & ShowOptions) => string;
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: Props & ShowOptions;
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";
@@ -1,5 +1,5 @@
1
1
  declare const useToast: () => {
2
- show: (toast: import("./Toast.types").Props & import("./Toast.types").ShowOptions) => string;
2
+ show: (toast: import("./Toast.types").ShowProps) => string;
3
3
  hide: (id: string) => void;
4
4
  id: string;
5
5
  };
@@ -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
- export { Provider, TriggerProvider, useFlyoutContext, useFlyoutTriggerContext };
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
- export { Provider, TriggerProvider, useFlyoutContext, useFlyoutTriggerContext };
11
+ const ContentProvider = FlyoutContentContext.Provider;
12
+ export { Provider, TriggerProvider, ContentProvider, useFlyoutContext, useFlyoutTriggerContext, useFlyoutContentContext, };
10
13
  export default FlyoutContext;
@@ -68,6 +68,7 @@ type BaseProps = {
68
68
  disabled?: boolean;
69
69
  disableHideAnimation?: boolean;
70
70
  disableContentHover?: boolean;
71
+ disableCloseOnOutsideClick?: boolean;
71
72
  children?: React.ReactNode;
72
73
  onOpen?: () => void;
73
74
  onClose?: () => void;
@@ -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
- // eslint-disable-next-line jsx-a11y/no-static-element-interactions
53
- _jsx("div", { className: contentClassNames, style: {
54
- ...styles,
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 || internalTriggerElRef;
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
- onOpen?.();
62
- // eslint-disable-next-line react-hooks/exhaustive-deps
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
- onClose?.();
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 === "content-menu",
184
+ includeTrigger: triggerType === "hover" && trapFocusMode !== "dialog" && !isSubmenu,
181
185
  onNavigateOutside: () => {
182
186
  handleClose();
183
187
  },
184
188
  });
185
- }, [status, triggerType, handleClose, trapFocusMode]);
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();