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
@@ -3,7 +3,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { useFlyoutContext, TriggerProvider } from "./Flyout.context.js";
4
4
  const FlyoutTrigger = (props) => {
5
5
  const { children } = props;
6
- const { id, triggerElRef, triggerType, flyout, handleFocus, handleBlur, handleMouseEnter, handleMouseLeave, handleClick, trapFocusMode, } = useFlyoutContext();
6
+ const { id, triggerElRef, triggerType, flyout, handleFocus, handleBlur, handleMouseEnter, handleMouseLeave, handleClick, trapFocusMode, isSubmenu, } = useFlyoutContext();
7
7
  let childrenAttributes = {
8
8
  onBlur: handleBlur,
9
9
  ref: triggerElRef,
@@ -15,7 +15,8 @@ const FlyoutTrigger = (props) => {
15
15
  childrenAttributes.onMouseEnter = handleMouseEnter;
16
16
  childrenAttributes.onMouseLeave = handleMouseLeave;
17
17
  }
18
- if ((triggerType === "hover" && trapFocusMode !== "action-menu") || triggerType === "focus") {
18
+ // Submenus open on keypress instead of hover
19
+ if ((triggerType === "hover" && !isSubmenu) || triggerType === "focus") {
19
20
  childrenAttributes.onFocus = handleFocus;
20
21
  childrenAttributes["aria-describedby"] = id;
21
22
  }
@@ -5,13 +5,8 @@ declare const _default: {
5
5
  export default _default;
6
6
  export declare const positions: () => React.JSX.Element;
7
7
  export declare const dynamicPosition: () => React.JSX.Element;
8
- export declare const modeDialogClick: () => React.JSX.Element;
9
- export declare const modeActionMenuClick: () => React.JSX.Element;
10
- export declare const modeContentMenuClick: () => React.JSX.Element;
11
- export declare const modeDialogHover: () => React.JSX.Element;
12
- export declare const modeActionMenuHover: () => React.JSX.Element;
13
- export declare const modeContentMenuHover: () => React.JSX.Element;
14
- export declare const disableContentHover: () => React.JSX.Element;
8
+ export declare const modes: () => React.JSX.Element;
9
+ export declare const disableFlags: () => React.JSX.Element;
15
10
  export declare const customPortalTarget: () => React.JSX.Element;
16
11
  export declare const testWidthOverflowOnMobile: () => React.JSX.Element;
17
12
  export declare const testInsideScrollArea: () => React.JSX.Element;
@@ -50,39 +50,70 @@ export const positions = () => (<div style={{ paddingTop: 200 }}>
50
50
  export const dynamicPosition = () => (<div style={{ position: "absolute", top: 0, left: "50%" }}>
51
51
  <Demo position="top"/>
52
52
  </div>);
53
- export const modeDialogClick = () => (<Demo position="bottom-start" trapFocusMode="dialog">
54
- <button type="button">Item 1</button>
55
- <button type="button">Item 2</button>
56
- <button type="button">Close</button>
57
- </Demo>);
58
- export const modeActionMenuClick = () => (<Demo position="bottom-start" trapFocusMode="action-menu">
59
- <button type="button">Item 1</button>
60
- <button type="button">Item 2</button>
61
- <button type="button">Close</button>
62
- </Demo>);
63
- export const modeContentMenuClick = () => (<Demo position="bottom-start" trapFocusMode="content-menu">
64
- <button type="button">Item 1</button>
65
- <button type="button">Item 2</button>
66
- <button type="button">Close</button>
67
- </Demo>);
68
- export const modeDialogHover = () => (<Demo position="bottom-start" trapFocusMode="dialog" triggerType="hover">
69
- <button type="button">Item 1</button>
70
- <button type="button">Item 2</button>
71
- <button type="button">Close</button>
72
- </Demo>);
73
- export const modeActionMenuHover = () => (<Demo position="bottom-start" trapFocusMode="action-menu" triggerType="hover">
74
- <button type="button">Item 1</button>
75
- <button type="button">Item 2</button>
76
- <button type="button">Close</button>
77
- </Demo>);
78
- export const modeContentMenuHover = () => (<Demo position="bottom-start" trapFocusMode="content-menu" triggerType="hover">
79
- <button type="button">Item 1</button>
80
- <button type="button">Item 2</button>
81
- <button type="button">Close</button>
82
- </Demo>);
83
- export const disableContentHover = () => (<Demo triggerType="hover" disableContentHover>
84
- Content
85
- </Demo>);
53
+ export const modes = () => (<Example>
54
+ <Example.Item title="dialog click">
55
+ <Demo position="bottom-start" trapFocusMode="dialog">
56
+ <button type="button">Item 1</button>
57
+ <button type="button">Item 2</button>
58
+ <button type="button">Close</button>
59
+ </Demo>
60
+ </Example.Item>
61
+
62
+ <Example.Item title="action-menu click">
63
+ <Demo position="bottom-start" trapFocusMode="action-menu">
64
+ <button type="button">Item 1</button>
65
+ <button type="button">Item 2</button>
66
+ <button type="button">Close</button>
67
+ </Demo>
68
+ </Example.Item>
69
+
70
+ <Example.Item title="content-menu click">
71
+ <Demo position="bottom-start" trapFocusMode="content-menu">
72
+ <button type="button">Item 1</button>
73
+ <button type="button">Item 2</button>
74
+ <button type="button">Close</button>
75
+ </Demo>
76
+ </Example.Item>
77
+
78
+ <Example.Item title="dialog hover">
79
+ <Demo position="bottom-start" trapFocusMode="dialog" triggerType="hover">
80
+ <button type="button">Item 1</button>
81
+ <button type="button">Item 2</button>
82
+ <button type="button">Close</button>
83
+ </Demo>
84
+ </Example.Item>
85
+
86
+ <Example.Item title="action-menu hover">
87
+ <Demo position="bottom-start" trapFocusMode="action-menu" triggerType="hover">
88
+ <button type="button">Item 1</button>
89
+ <button type="button">Item 2</button>
90
+ <button type="button">Close</button>
91
+ </Demo>
92
+ </Example.Item>
93
+
94
+ <Example.Item title="content-menu hover">
95
+ <Demo position="bottom-start" trapFocusMode="content-menu" triggerType="hover">
96
+ <button type="button">Item 1</button>
97
+ <button type="button">Item 2</button>
98
+ <button type="button">Close</button>
99
+ </Demo>
100
+ </Example.Item>
101
+ </Example>);
102
+ export const disableFlags = () => (<Example>
103
+ <Example.Item title="disableContentHover">
104
+ <Demo triggerType="hover" disableContentHover>
105
+ Content
106
+ </Demo>
107
+ </Example.Item>
108
+
109
+ <Example.Item title="disableCloseOnOutsideClick">
110
+ <Demo disableCloseOnOutsideClick>Content</Demo>
111
+ </Example.Item>
112
+
113
+ <Example.Item title="disableHideAnimation">
114
+ <Demo disableHideAnimation>Content</Demo>
115
+ </Example.Item>
116
+ </Example>);
86
117
  class CustomElement extends window.HTMLElement {
87
118
  constructor() {
88
119
  super();
@@ -99,7 +130,9 @@ class CustomElement extends window.HTMLElement {
99
130
  root.render(node);
100
131
  }
101
132
  }
102
- window.customElements.define("custom-element", CustomElement);
133
+ if (!window.customElements.get("custom-element")) {
134
+ window.customElements.define("custom-element", CustomElement);
135
+ }
103
136
  export const customPortalTarget = () => {
104
137
  const portalRef = React.useRef(null);
105
138
  return (<Example>
@@ -186,16 +219,32 @@ export const testInsideFixed = () => (<Example>
186
219
  export const testDynamicBounds = () => {
187
220
  const [left, setLeft] = React.useState("50%");
188
221
  const [size, setSize] = React.useState("medium");
222
+ const flyoutRef = React.useRef();
223
+ React.useEffect(() => {
224
+ flyoutRef.current?.updatePosition();
225
+ }, [left]);
189
226
  return (<View gap={4}>
190
227
  <View direction="row" gap={2}>
191
- <Button onClick={() => setLeft("0%")}>Left</Button>
192
- <Button onClick={() => setLeft("50%")}>Center</Button>
193
- <Button onClick={() => setLeft("100%")}>Right</Button>
228
+ <Button onClick={() => {
229
+ setLeft("20%");
230
+ }}>
231
+ Left
232
+ </Button>
233
+ <Button onClick={() => {
234
+ setLeft("50%");
235
+ }}>
236
+ Center
237
+ </Button>
238
+ <Button onClick={() => {
239
+ setLeft("70%");
240
+ }}>
241
+ Right
242
+ </Button>
194
243
  <Button onClick={() => setSize("large")}>Large button</Button>
195
244
  <Button onClick={() => setSize("medium")}>Small button</Button>
196
245
  </View>
197
246
  <View height={100}>
198
- <Flyout position="bottom" active>
247
+ <Flyout position="bottom" active instanceRef={flyoutRef}>
199
248
  <Flyout.Trigger>
200
249
  {(attributes) => (<div style={{ position: "absolute", left, top: "50%" }}>
201
250
  <Button color="primary" attributes={attributes} size={size}>
@@ -1 +1 @@
1
- .root{display:contents}
1
+ .root{display:none}
@@ -1,7 +1,9 @@
1
+ import useHandlerRef from "../useHandlerRef.js";
1
2
  import React from "react";
2
3
  const useOnClickOutside = (refs, handler) => {
4
+ const handlerRef = useHandlerRef(handler);
3
5
  React.useEffect(() => {
4
- if (!handler)
6
+ if (!handlerRef.current)
5
7
  return;
6
8
  const handleClick = (event) => {
7
9
  let isInside = false;
@@ -14,7 +16,7 @@ const useOnClickOutside = (refs, handler) => {
14
16
  });
15
17
  if (isInside)
16
18
  return;
17
- handler(event);
19
+ handlerRef.current?.(event);
18
20
  };
19
21
  // Using events that happen before click to handle cases when element is hidden on click
20
22
  document.addEventListener("mousedown", handleClick);
@@ -24,6 +26,6 @@ const useOnClickOutside = (refs, handler) => {
24
26
  document.removeEventListener("touchstart", handleClick);
25
27
  };
26
28
  // eslint-disable-next-line react-hooks/exhaustive-deps
27
- }, [handler, ...refs]);
29
+ }, [handlerRef, ...refs]);
28
30
  };
29
31
  export default useOnClickOutside;
@@ -0,0 +1,6 @@
1
+ import React from "react";
2
+ declare const _default: {
3
+ title: string;
4
+ };
5
+ export default _default;
6
+ export declare const state: () => React.JSX.Element;
@@ -0,0 +1,29 @@
1
+ import React from "react";
2
+ import View from "../../components/View/index.js";
3
+ import useDrag from "../useDrag.js";
4
+ import useToggle from "../useToggle.js";
5
+ import Button from "../../components/Button/index.js";
6
+ export default { title: "Hooks/useDrag" };
7
+ function Example() {
8
+ const [state, setState] = React.useState({ x: 0, y: 0 });
9
+ const disabledToggle = useToggle();
10
+ const { ref, containerRef, active } = useDrag((args) => {
11
+ setState(args);
12
+ }, {
13
+ disabled: disabledToggle.active,
14
+ });
15
+ return (<View direction="row" gap={4}>
16
+ <View backgroundColor="neutral-faded" borderRadius="medium" width="200px" height="200px" attributes={{ ref: containerRef }}>
17
+ <View height={8} width={8} borderRadius="small" animated backgroundColor={active ? "primary" : "neutral"} attributes={{
18
+ role: "button",
19
+ tabIndex: 0,
20
+ ref,
21
+ style: { translate: `${state.x}px ${state.y}px`, cursor: active ? "grabbing" : "grab" },
22
+ }}/>
23
+ </View>
24
+ <Button onClick={disabledToggle.toggle}>
25
+ {disabledToggle.active ? "Enable" : "Disable"}
26
+ </Button>
27
+ </View>);
28
+ }
29
+ export const state = () => <Example />;
@@ -0,0 +1,17 @@
1
+ import React from "react";
2
+ export type UseDragCallbackArgs = {
3
+ x: number;
4
+ y: number;
5
+ triggerX: number;
6
+ triggerY: number;
7
+ };
8
+ declare const useDrag: <TriggerElement extends HTMLElement = HTMLButtonElement, ContainerElement extends HTMLElement = HTMLDivElement>(cb: (args: UseDragCallbackArgs) => void, options?: {
9
+ disabled?: boolean;
10
+ containerRef?: React.RefObject<ContainerElement>;
11
+ orientation?: "horizontal" | "vertical" | "all";
12
+ }) => {
13
+ ref: React.MutableRefObject<TriggerElement | null>;
14
+ containerRef: React.RefObject<ContainerElement>;
15
+ active: boolean;
16
+ };
17
+ export default useDrag;
@@ -0,0 +1,116 @@
1
+ "use client";
2
+ import React from "react";
3
+ import { disableUserSelect, enableUserSelect, disableScroll, enableScroll } from "../utilities/dom.js";
4
+ import useToggle from "./useToggle.js";
5
+ import useHotkeys from "./useHotkeys.js";
6
+ import * as keys from "../constants/keys.js";
7
+ import useHandlerRef from "./useHandlerRef.js";
8
+ const useDrag = (cb, options) => {
9
+ const { disabled, containerRef: passedContainerRef, orientation = "all" } = options || {};
10
+ const cbRef = useHandlerRef(cb);
11
+ const toggle = useToggle();
12
+ const triggerRef = React.useRef(null);
13
+ const internalContainerRef = React.useRef(null);
14
+ const containerRef = passedContainerRef || internalContainerRef;
15
+ const triggerCompensationRef = React.useRef({ x: 0, y: 0 });
16
+ const isVertical = orientation === "vertical" || orientation === "all";
17
+ const isHorizontal = orientation === "horizontal" || orientation === "all";
18
+ const handleKeyboard = (x, y) => {
19
+ const triggerEl = triggerRef.current;
20
+ if (!triggerEl)
21
+ return;
22
+ const container = containerRef.current ?? document.body;
23
+ const containerRect = container.getBoundingClientRect();
24
+ const triggerRect = triggerEl?.getBoundingClientRect();
25
+ const nextArgs = { x: 0, y: 0, triggerX: 0, triggerY: 0 };
26
+ if (isVertical) {
27
+ const relativeY = Math.round(triggerRect.y) - containerRect.y + y;
28
+ nextArgs.y = Math.max(0, Math.min(relativeY, containerRect.height - triggerRect.height));
29
+ nextArgs.triggerY = triggerRect.y - containerRect.y;
30
+ }
31
+ if (isHorizontal) {
32
+ const relativeX = Math.round(triggerRect.x) - containerRect.x + x;
33
+ nextArgs.x = Math.max(0, Math.min(relativeX, containerRect.width - triggerRect.width));
34
+ nextArgs.triggerX = triggerRect.x - containerRect.x;
35
+ }
36
+ cb(nextArgs);
37
+ };
38
+ useHotkeys({
39
+ [keys.LEFT]: () => isHorizontal && handleKeyboard(-20, 0),
40
+ [keys.RIGHT]: () => isHorizontal && handleKeyboard(20, 0),
41
+ [keys.UP]: () => isVertical && handleKeyboard(0, -20),
42
+ [keys.DOWN]: () => isVertical && handleKeyboard(0, 20),
43
+ }, [], {
44
+ ref: triggerRef,
45
+ disabled,
46
+ });
47
+ React.useEffect(() => {
48
+ const triggerEl = triggerRef.current;
49
+ if (!triggerEl)
50
+ return;
51
+ if (!toggle.active)
52
+ return;
53
+ const handleDrag = (event) => {
54
+ const resolvedEvent = event instanceof MouseEvent ? event : event.changedTouches[0];
55
+ const container = containerRef.current ?? document.body;
56
+ const containerRect = container.getBoundingClientRect();
57
+ const triggerRect = triggerEl.getBoundingClientRect();
58
+ const triggerX = resolvedEvent.clientX - containerRect.x;
59
+ const triggerY = resolvedEvent.clientY - containerRect.y;
60
+ // Calculate position relative to the container
61
+ const relativeX = triggerX - triggerCompensationRef.current.x;
62
+ const relativeY = triggerY - triggerCompensationRef.current.y;
63
+ cbRef.current?.({
64
+ x: isHorizontal
65
+ ? Math.max(0, Math.min(relativeX, containerRect.width - triggerRect.width))
66
+ : 0,
67
+ y: isVertical
68
+ ? Math.max(0, Math.min(relativeY, containerRect.height - triggerRect.height))
69
+ : 0,
70
+ triggerX: triggerRect.x - containerRect.x,
71
+ triggerY: triggerRect.y - containerRect.y,
72
+ });
73
+ };
74
+ const handleDragEnd = () => {
75
+ triggerCompensationRef.current = { x: 0, y: 0 };
76
+ toggle.deactivate();
77
+ enableUserSelect();
78
+ enableScroll();
79
+ };
80
+ document.addEventListener("touchmove", handleDrag, { passive: true });
81
+ document.addEventListener("touchend", handleDragEnd, { passive: true });
82
+ document.addEventListener("mousemove", handleDrag, { passive: true });
83
+ document.addEventListener("mouseup", handleDragEnd, { passive: true });
84
+ return () => {
85
+ document.removeEventListener("touchmove", handleDrag);
86
+ document.removeEventListener("touchend", handleDragEnd);
87
+ document.removeEventListener("mousemove", handleDrag);
88
+ document.removeEventListener("mouseup", handleDragEnd);
89
+ };
90
+ }, [toggle, isHorizontal, isVertical, containerRef, cbRef]);
91
+ React.useEffect(() => {
92
+ const triggerEl = triggerRef.current;
93
+ if (!triggerEl || disabled)
94
+ return;
95
+ const handleStart = (event) => {
96
+ const resolvedEvent = event instanceof MouseEvent ? event : event.changedTouches[0];
97
+ // Find the coordinate of the event inside the trigger
98
+ const triggerRect = triggerEl.getBoundingClientRect();
99
+ triggerCompensationRef.current = {
100
+ x: resolvedEvent.clientX - triggerRect.x,
101
+ y: resolvedEvent.clientY - triggerRect.y,
102
+ };
103
+ toggle.activate();
104
+ disableUserSelect();
105
+ disableScroll();
106
+ };
107
+ triggerEl.addEventListener("touchstart", handleStart, { passive: true });
108
+ triggerEl.addEventListener("mousedown", handleStart, { passive: true });
109
+ return () => {
110
+ triggerEl.removeEventListener("touchstart", handleStart);
111
+ triggerEl.removeEventListener("mousedown", handleStart);
112
+ };
113
+ }, [toggle, disabled]);
114
+ return { ref: triggerRef, containerRef, active: toggle.active };
115
+ };
116
+ export default useDrag;
@@ -0,0 +1,8 @@
1
+ import React from "react";
2
+ /**
3
+ * Hook for wrapping event handlers passed as props with a ref
4
+ * This way we can keep the instance of the ref the same and pass this ref to the effects dependency array
5
+ * While also making sure that function implementation stays up-to-date
6
+ */
7
+ declare const useHandlerRef: <T>(cb: T) => React.RefObject<T>;
8
+ export default useHandlerRef;
@@ -0,0 +1,16 @@
1
+ import React from "react";
2
+ import useIsomorphicLayoutEffect from "./useIsomorphicLayoutEffect.js";
3
+ /**
4
+ * Hook for wrapping event handlers passed as props with a ref
5
+ * This way we can keep the instance of the ref the same and pass this ref to the effects dependency array
6
+ * While also making sure that function implementation stays up-to-date
7
+ */
8
+ const useHandlerRef = (cb) => {
9
+ const ref = React.useRef(cb);
10
+ // Update the callback on every render, keeping the ref instance the same
11
+ useIsomorphicLayoutEffect(() => {
12
+ ref.current = cb;
13
+ });
14
+ return ref;
15
+ };
16
+ export default useHandlerRef;
@@ -21,8 +21,8 @@ const useScrollLock = () => {
21
21
  const [locked, setLocked] = React.useState(false);
22
22
  const overflowStyleRef = React.useRef();
23
23
  const isOverflowingRef = React.useRef(false);
24
- const targetEl = document.body;
25
24
  const lockScroll = React.useCallback(() => {
25
+ const targetEl = document.body;
26
26
  const rect = targetEl.getBoundingClientRect();
27
27
  isOverflowingRef.current = rect.left + rect.right < window.innerWidth;
28
28
  overflowStyleRef.current = targetEl.style.overflow;
@@ -32,13 +32,14 @@ const useScrollLock = () => {
32
32
  targetEl.style.paddingRight = `${scrollBarWidth}px`;
33
33
  }
34
34
  setLocked(true);
35
- }, [setLocked, isOverflowingRef, overflowStyleRef, targetEl]);
35
+ }, [setLocked, isOverflowingRef, overflowStyleRef]);
36
36
  const unlockScroll = React.useCallback(() => {
37
+ const targetEl = document.body;
37
38
  targetEl.style.overflow = overflowStyleRef.current || "";
38
39
  if (isOverflowingRef.current)
39
40
  targetEl.style.paddingRight = "";
40
41
  setLocked(false);
41
- }, [setLocked, isOverflowingRef, overflowStyleRef, targetEl]);
42
+ }, [setLocked, isOverflowingRef, overflowStyleRef]);
42
43
  return { scrollLocked: locked, lockScroll, unlockScroll };
43
44
  };
44
45
  export default useScrollLock;
@@ -11,6 +11,6 @@ const useToggle = (defaultValue) => {
11
11
  const toggle = React.useCallback(() => {
12
12
  setActive((active) => !active);
13
13
  }, []);
14
- return { active, activate, deactivate, toggle };
14
+ return React.useMemo(() => ({ active, activate, deactivate, toggle }), [activate, deactivate, toggle, active]);
15
15
  };
16
16
  export default useToggle;
package/dist/index.d.ts CHANGED
@@ -75,6 +75,8 @@ export { default as RadioGroup } from "./components/RadioGroup";
75
75
  export type { RadioGroupProps } from "./components/RadioGroup";
76
76
  export { default as Reshaped } from "./components/Reshaped";
77
77
  export type { ReshapedProps } from "./components/Reshaped";
78
+ export { default as Resizable } from "./components/Resizable";
79
+ export type { ResizableProps, ResizableItemProps, ResizableHandleProps, } from "./components/Resizable";
78
80
  export { default as Scrim } from "./components/Scrim";
79
81
  export type { ScrimProps } from "./components/Scrim";
80
82
  export { default as ScrollArea } from "./components/ScrollArea";
@@ -102,7 +104,7 @@ export type { TextFieldProps } from "./components/TextField";
102
104
  export { default as Timeline } from "./components/Timeline";
103
105
  export type { TimelineProps, TimelineItemProps } from "./components/Timeline";
104
106
  export { useToast, ToastProvider } from "./components/Toast";
105
- export type { ToastProps, ToastProviderProps } from "./components/Toast";
107
+ export type { ToastProps, ToastProviderProps, ToastShowProps } from "./components/Toast";
106
108
  export { default as Tooltip } from "./components/Tooltip";
107
109
  export type { TooltipProps } from "./components/Tooltip";
108
110
  export { default as View } from "./components/View";
package/dist/index.js CHANGED
@@ -38,6 +38,7 @@ export { default as Progress } from "./components/Progress/index.js";
38
38
  export { default as Radio } from "./components/Radio/index.js";
39
39
  export { default as RadioGroup } from "./components/RadioGroup/index.js";
40
40
  export { default as Reshaped } from "./components/Reshaped/index.js";
41
+ export { default as Resizable } from "./components/Resizable/index.js";
41
42
  export { default as Scrim } from "./components/Scrim/index.js";
42
43
  export { default as ScrollArea } from "./components/ScrollArea/index.js";
43
44
  export { default as Select } from "./components/Select/index.js";
@@ -235,6 +235,20 @@ const onColorsCss = getThemeCSS("on-color", {
235
235
  },
236
236
  },
237
237
  });
238
+ const onColorsCssApca = getThemeCSS("on-color-apca", {
239
+ color: {
240
+ backgroundPrimary: { hex: "#1abc9c", hexDark: "#16a085" },
241
+ backgroundPrimaryHighlighted: { hex: "#16a085", hexDark: "#1abc9c" },
242
+ },
243
+ }, {
244
+ colorContrastAlgorithm: "apca",
245
+ onColorValues: {
246
+ primary: {
247
+ hexLight: "#d1fae5",
248
+ hexDark: "#022c22",
249
+ },
250
+ },
251
+ });
238
252
  export const onColors = () => (<Example>
239
253
  <Example.Item title="custom on color values">
240
254
  <style>{onColorsCss}</style>
@@ -245,4 +259,13 @@ export const onColors = () => (<Example>
245
259
  </View>
246
260
  </Theme>
247
261
  </Example.Item>
262
+ <Example.Item title="custom on color values, apca">
263
+ <style>{onColorsCssApca}</style>
264
+ <Theme name="on-color-apca">
265
+ <View gap={2} direction="row">
266
+ <Button color="primary">Primary button</Button>
267
+ <Button color="critical">Critical button</Button>
268
+ </View>
269
+ </Theme>
270
+ </Example.Item>
248
271
  </Example>);
@@ -81,9 +81,25 @@ export declare const getDarkModeColor: (hsl: HslColor) => {
81
81
  };
82
82
  export declare const getLuminanceDelta: (luminance: number) => number;
83
83
  export declare function getRgbLuminance({ r, g, b }: RgbColor): number;
84
+ export declare const getOnColorWCAG: (args: {
85
+ bgHexColor: string;
86
+ lightHexColor?: string;
87
+ darkHexColor?: string;
88
+ }) => string;
89
+ export declare const getOnColorAPCA: (args: {
90
+ bgHexColor: string;
91
+ mode: "light" | "dark";
92
+ lightHexColor?: string;
93
+ darkHexColor?: string;
94
+ }) => string;
95
+ /**
96
+ * On color resolver
97
+ */
84
98
  export declare const getOnColor: (args: {
85
99
  bgHexColor: string;
86
100
  lightHexColor?: string;
87
101
  darkHexColor?: string;
102
+ mode: "light" | "dark";
103
+ algorithm?: "wcag" | "apca";
88
104
  }) => string;
89
105
  export {};
@@ -301,29 +301,77 @@ export function getRgbLuminance({ r, g, b }) {
301
301
  return Math.pow(y, 1 / 3) * 116 - 16;
302
302
  }
303
303
  }
304
+ /**
305
+ * WCAG contrast
306
+ */
304
307
  const RED = 0.2126;
305
308
  const GREEN = 0.7152;
306
309
  const BLUE = 0.0722;
307
310
  const GAMMA = 2.4;
308
- function luminance(r, g, b) {
311
+ function luminanceWCAG(r, g, b) {
309
312
  var a = [r, g, b].map((v) => {
310
313
  v /= 255;
311
314
  return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, GAMMA);
312
315
  });
313
316
  return a[0] * RED + a[1] * GREEN + a[2] * BLUE;
314
317
  }
315
- function contrast(rgb1, rgb2) {
316
- var lum1 = luminance(...rgb1);
317
- var lum2 = luminance(...rgb2);
318
+ function contrastWCAG(rgb1, rgb2) {
319
+ var lum1 = luminanceWCAG(...rgb1);
320
+ var lum2 = luminanceWCAG(...rgb2);
318
321
  var brightest = Math.max(lum1, lum2);
319
322
  var darkest = Math.min(lum1, lum2);
320
323
  return (brightest + 0.05) / (darkest + 0.05);
321
324
  }
322
- export const getOnColor = (args) => {
325
+ export const getOnColorWCAG = (args) => {
323
326
  const { bgHexColor, lightHexColor = "#ffffff", darkHexColor = "#000000" } = args;
324
327
  const bgRgb = hexToRgb(bgHexColor);
325
328
  const lightRgb = hexToRgb(lightHexColor);
326
- return contrast([bgRgb.r, bgRgb.g, bgRgb.b], [lightRgb.r, lightRgb.g, lightRgb.b]) > 4.5
329
+ return contrastWCAG([bgRgb.r, bgRgb.g, bgRgb.b], [lightRgb.r, lightRgb.g, lightRgb.b]) > 4.5
327
330
  ? lightHexColor
328
331
  : darkHexColor;
329
332
  };
333
+ /**
334
+ * APCA contrast
335
+ */
336
+ function luminanceAPCA({ r, g, b }) {
337
+ return (0.2126 * Math.pow(r / 255, 2.2) +
338
+ 0.7152 * Math.pow(g / 255, 2.2) +
339
+ 0.0722 * Math.pow(b / 255, 2.2));
340
+ }
341
+ function contrastAPCA(backgroundLuminance, textLuminance) {
342
+ // Calculate the contrast based on APCA
343
+ let Lc = textLuminance - backgroundLuminance;
344
+ return Math.abs(Lc); // Return the absolute value of contrast
345
+ }
346
+ export const getOnColorAPCA = (args) => {
347
+ const { bgHexColor, mode, lightHexColor = "#ffffff", darkHexColor = "#000000" } = args;
348
+ const bgHexAlpha = bgHexColor.slice(7);
349
+ const bgAlpha = bgHexAlpha ? Number((parseInt(bgHexAlpha, 16) / 255).toFixed(2)) : 1;
350
+ const bgColor = hexToRgb(bgHexColor.slice(0, 7));
351
+ const baseColor = mode === "light" ? { r: 255, g: 255, b: 255 } : { r: 0, g: 0, b: 0 };
352
+ const { r, g, b } = {
353
+ r: (1 - bgAlpha) * baseColor.r + bgAlpha * bgColor.r,
354
+ g: (1 - bgAlpha) * baseColor.g + bgAlpha * bgColor.g,
355
+ b: (1 - bgAlpha) * baseColor.b + bgAlpha * bgColor.b,
356
+ };
357
+ // Calculate luminance for background and for white & black
358
+ let backgroundLuminance = luminanceAPCA({ r, g, b });
359
+ let whiteLuminance = luminanceAPCA({ r: 255, g: 255, b: 255 });
360
+ let blackLuminance = luminanceAPCA({ r: 0, g: 0, b: 0 });
361
+ // Calculate contrast
362
+ let contrastWithWhite = contrastAPCA(backgroundLuminance, whiteLuminance);
363
+ let contrastWithBlack = contrastAPCA(backgroundLuminance, blackLuminance);
364
+ // Choose the color with higher contrast
365
+ return contrastWithWhite > contrastWithBlack ? lightHexColor : darkHexColor;
366
+ };
367
+ /**
368
+ * On color resolver
369
+ */
370
+ export const getOnColor = (args) => {
371
+ if (args.algorithm === "apca") {
372
+ return getOnColorAPCA(args);
373
+ }
374
+ else {
375
+ return getOnColorWCAG(args);
376
+ }
377
+ };
@@ -29,10 +29,14 @@ const generateBackgroundColors = (definition, themeOptions) => {
29
29
  };
30
30
  const hex = getOnColor({
31
31
  bgHexColor: bgToken.hex,
32
+ mode: "light",
33
+ algorithm: themeOptions?.colorContrastAlgorithm,
32
34
  ...onColorHexMap,
33
35
  });
34
36
  const hexDark = getOnColor({
35
37
  bgHexColor: bgToken.hexDark || bgToken.hex,
38
+ mode: "dark",
39
+ algorithm: themeOptions?.colorContrastAlgorithm,
36
40
  ...onColorHexMap,
37
41
  });
38
42
  // eslint-disable-next-line no-param-reassign