reshaped 3.3.12 → 3.4.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/bundle.css +1 -1
  3. package/dist/bundle.d.ts +4 -1
  4. package/dist/bundle.js +11 -11
  5. package/dist/components/Autocomplete/Autocomplete.js +34 -24
  6. package/dist/components/Autocomplete/Autocomplete.types.d.ts +1 -1
  7. package/dist/components/Autocomplete/tests/Autocomplete.stories.js +8 -1
  8. package/dist/components/Carousel/Carousel.js +53 -4
  9. package/dist/components/Carousel/Carousel.types.d.ts +5 -0
  10. package/dist/components/Carousel/tests/Carousel.stories.d.ts +20 -4
  11. package/dist/components/Carousel/tests/Carousel.stories.js +195 -102
  12. package/dist/components/Container/Container.module.css +1 -1
  13. package/dist/components/ContextMenu/ContextMenu.js +2 -2
  14. package/dist/components/ContextMenu/tests/ContextMenu.test.stories.js +1 -1
  15. package/dist/components/DropdownMenu/DropdownMenu.js +6 -1
  16. package/dist/components/DropdownMenu/tests/DropdownMenu.test.stories.js +2 -2
  17. package/dist/components/Popover/Popover.js +1 -1
  18. package/dist/components/Popover/tests/Popover.test.stories.js +4 -4
  19. package/dist/components/ProgressIndicator/ProgressIndicator.d.ts +3 -0
  20. package/dist/components/ProgressIndicator/ProgressIndicator.js +101 -0
  21. package/dist/components/ProgressIndicator/ProgressIndicator.module.css +1 -0
  22. package/dist/components/ProgressIndicator/ProgressIndicator.types.d.ts +9 -0
  23. package/dist/components/ProgressIndicator/ProgressIndicator.types.js +1 -0
  24. package/dist/components/ProgressIndicator/index.d.ts +2 -0
  25. package/dist/components/ProgressIndicator/index.js +1 -0
  26. package/dist/components/ProgressIndicator/tests/ProgressIndicator.stories.d.ts +19 -0
  27. package/dist/components/ProgressIndicator/tests/ProgressIndicator.stories.js +85 -0
  28. package/dist/components/Reshaped/Reshaped.js +4 -5
  29. package/dist/components/Table/index.d.ts +1 -1
  30. package/dist/components/Table/tests/Table.stories.d.ts +5 -5
  31. package/dist/components/Table/tests/Table.test.stories.d.ts +5 -5
  32. package/dist/components/TextField/TextField.module.css +1 -1
  33. package/dist/components/Toast/tests/Toast.stories.js +22 -7
  34. package/dist/components/Tooltip/tests/Tooltip.test.stories.js +1 -1
  35. package/dist/components/_private/Flyout/Flyout.module.css +1 -1
  36. package/dist/components/_private/Flyout/Flyout.types.d.ts +14 -3
  37. package/dist/components/_private/Flyout/FlyoutContent.js +37 -7
  38. package/dist/components/_private/Flyout/FlyoutControlled.js +12 -11
  39. package/dist/components/_private/Flyout/FlyoutUncontrolled.js +3 -5
  40. package/dist/components/_private/Flyout/index.d.ts +1 -1
  41. package/dist/components/_private/Flyout/tests/Flyout.stories.d.ts +79 -13
  42. package/dist/components/_private/Flyout/tests/Flyout.stories.js +526 -280
  43. package/dist/components/_private/Flyout/useFlyout.js +9 -3
  44. package/dist/components/_private/Flyout/utilities/isFullyVisible.d.ts +3 -1
  45. package/dist/components/_private/Flyout/utilities/isFullyVisible.js +2 -2
  46. package/dist/hooks/_private/usePrevious.d.ts +2 -0
  47. package/dist/hooks/_private/usePrevious.js +17 -0
  48. package/dist/hooks/_private/useSingletonEnvironment.d.ts +1 -1
  49. package/dist/hooks/_private/useSingletonEnvironment.js +1 -1
  50. package/dist/hooks/_private/useSingletonKeyboardMode.d.ts +13 -2
  51. package/dist/hooks/_private/useSingletonKeyboardMode.js +48 -15
  52. package/dist/hooks/tests/useKeyboardMode.stories.d.ts +6 -0
  53. package/dist/hooks/tests/useKeyboardMode.stories.js +37 -0
  54. package/dist/hooks/useKeyboardMode.d.ts +7 -0
  55. package/dist/hooks/useKeyboardMode.js +13 -0
  56. package/dist/index.d.ts +4 -1
  57. package/dist/index.js +2 -0
  58. package/dist/utilities/a11y/index.d.ts +1 -1
  59. package/dist/utilities/a11y/index.js +1 -1
  60. package/dist/utilities/a11y/keyboardMode.d.ts +2 -2
  61. package/dist/utilities/a11y/keyboardMode.js +2 -2
  62. package/dist/utilities/dom/find.d.ts +4 -1
  63. package/dist/utilities/dom/find.js +4 -4
  64. package/dist/utilities/scroll/lockStandard.js +1 -1
  65. package/package.json +34 -35
  66. package/dist/components/Carousel/tests/Carousel.test.stories.d.ts +0 -17
  67. package/dist/components/Carousel/tests/Carousel.test.stories.js +0 -52
  68. package/dist/components/_private/Flyout/tests/Flyout.test.stories.d.ts +0 -28
  69. package/dist/components/_private/Flyout/tests/Flyout.test.stories.js +0 -205
@@ -9,22 +9,47 @@ import useHotkeys from "../../hooks/useHotkeys.js";
9
9
  import useHandlerRef from "../../hooks/useHandlerRef.js";
10
10
  const AutocompleteContext = React.createContext({});
11
11
  const Autocomplete = (props) => {
12
- const { children, onChange, onInput, onItemSelect, name, containerRef, instanceRef, onBackspace, ...textFieldProps } = props;
12
+ const { children, onChange, onInput, onItemSelect, name, containerRef, instanceRef, onBackspace, active, onOpen, onClose, ...textFieldProps } = props;
13
13
  const onBackspaceRef = useHandlerRef(onBackspace);
14
14
  const internalInputRef = React.useRef(null);
15
15
  const inputAttributesRef = textFieldProps.inputAttributes?.ref;
16
16
  const inputRef = inputAttributesRef && typeof inputAttributesRef !== "string" && "current" in inputAttributesRef
17
17
  ? inputAttributesRef
18
18
  : internalInputRef;
19
- const [active, setActive] = React.useState(false);
19
+ const [internalActive, setInternalActive] = React.useState(false);
20
20
  const hasChildren = !!React.Children.toArray(children).filter(Boolean).length;
21
21
  const lockedRef = React.useRef(false);
22
+ const onOpenRef = useHandlerRef(onOpen);
23
+ const onCloseRef = useHandlerRef(onClose);
24
+ const isDropdownActive = hasChildren && (active ?? internalActive);
22
25
  const handleOpen = React.useCallback(() => {
23
26
  if (lockedRef.current)
24
27
  return;
25
- setActive(true);
26
- }, []);
27
- const handleClose = () => setActive(false);
28
+ setInternalActive(true);
29
+ onOpenRef.current?.();
30
+ }, [onOpenRef]);
31
+ const handleClose = (args) => {
32
+ setInternalActive(false);
33
+ onCloseRef.current?.(args);
34
+ };
35
+ const handleItemClick = (args) => {
36
+ onChange?.({ value: args.value, name });
37
+ onItemSelect?.(args);
38
+ // Prevent dropdown from re-opening when clicked on item with mouse
39
+ // and focus moves to the item and back to the input
40
+ lockedRef.current = true;
41
+ setTimeout(() => {
42
+ lockedRef.current = false;
43
+ }, 100);
44
+ };
45
+ const handleChange = (args) => {
46
+ onChange?.(args);
47
+ handleOpen();
48
+ };
49
+ const handleInput = (e) => {
50
+ onInput?.({ value: e.currentTarget.value, name, event: e });
51
+ textFieldProps.inputAttributes?.onInput?.(e);
52
+ };
28
53
  useHotkeys({
29
54
  [keys.BACKSPACE]: () => {
30
55
  onBackspaceRef.current?.();
@@ -42,26 +67,9 @@ const Autocomplete = (props) => {
42
67
  el?.click();
43
68
  },
44
69
  }, [handleOpen], { ref: inputRef, preventDefault: true });
45
- const handleChange = (args) => {
46
- onChange?.(args);
47
- handleOpen();
48
- };
49
- const handleItemClick = (args) => {
50
- onChange?.({ value: args.value, name });
51
- onItemSelect?.(args);
52
- // Prevent dropdown from re-opening when clicked on item with mouse
53
- // and focus moves to the item and back to the input
54
- lockedRef.current = true;
55
- setTimeout(() => (lockedRef.current = false), 100);
56
- };
57
- const handleInput = (e) => {
58
- onInput?.({ value: e.currentTarget.value, name, event: e });
59
- textFieldProps.inputAttributes?.onInput?.(e);
60
- };
61
- return (_jsx(AutocompleteContext.Provider, { value: { onItemClick: handleItemClick }, children: _jsxs(DropdownMenu, { position: "bottom", width: "trigger", triggerType: "focus", trapFocusMode: "selection-menu", active: hasChildren && active, onClose: handleClose, onOpen: handleOpen, containerRef: containerRef, disableHideAnimation: true, instanceRef: instanceRef, children: [_jsx(DropdownMenu.Trigger, { children: ({ ref, ...attributes }) => (_jsx(TextField, { ...textFieldProps, name: name, onChange: handleChange, focused: hasChildren && active,
62
- // Ignoring the type check since TS can't infer the correct html element type
63
- attributes: {
70
+ return (_jsx(AutocompleteContext.Provider, { value: { onItemClick: handleItemClick }, children: _jsxs(DropdownMenu, { position: "bottom", width: "trigger", triggerType: "focus", trapFocusMode: "selection-menu", active: isDropdownActive, onClose: handleClose, onOpen: handleOpen, containerRef: containerRef, disableHideAnimation: true, instanceRef: instanceRef, children: [_jsx(DropdownMenu.Trigger, { children: ({ ref, ...attributes }) => (_jsx(TextField, { ...textFieldProps, name: name, onChange: handleChange, focused: isDropdownActive, attributes: {
64
71
  ...textFieldProps.attributes,
72
+ // Ignoring the type check since TS can't infer the correct html element type
65
73
  ref: ref,
66
74
  onClick: attributes.onFocus,
67
75
  }, inputAttributes: {
@@ -70,6 +78,8 @@ const Autocomplete = (props) => {
70
78
  onFocus: (e) => {
71
79
  attributes.onFocus?.();
72
80
  textFieldProps.onFocus?.(e);
81
+ if (!lockedRef.current)
82
+ inputRef.current?.select();
73
83
  },
74
84
  onInput: handleInput,
75
85
  onClick: attributes.onFocus,
@@ -5,7 +5,7 @@ type SelectArgs = {
5
5
  value: string;
6
6
  data?: unknown;
7
7
  };
8
- export type Props = TextFieldProps & Pick<DropdownMenuProps, "containerRef" | "instanceRef"> & {
8
+ export type Props = TextFieldProps & Pick<DropdownMenuProps, "containerRef" | "instanceRef" | "active" | "onOpen" | "onClose"> & {
9
9
  onInput?: TextFieldProps["onChange"];
10
10
  onItemSelect?: (args: SelectArgs) => void;
11
11
  onBackspace?: () => void;
@@ -44,6 +44,7 @@ export const multiselect = {
44
44
  name: "multiselect",
45
45
  render: () => {
46
46
  const inputRef = React.useRef(null);
47
+ const [active, setActive] = React.useState(false);
47
48
  const [values, setValues] = React.useState(["foo", "bar"]);
48
49
  const [query, setQuery] = React.useState("");
49
50
  const [customValueQuery, setCustomValueQuery] = React.useState("");
@@ -70,7 +71,13 @@ export const multiselect = {
70
71
  <Autocomplete name="fruit" value={query} placeholder="Pick your food" startSlot={valuesNode} inputAttributes={{ ref: inputRef }} onBackspace={() => {
71
72
  if (!query.length)
72
73
  handleDismiss(values[values.length - 1]);
73
- }} multiline onChange={(args) => setQuery(args.value)} onItemSelect={(args) => {
74
+ }} onOpen={() => {
75
+ setActive(true);
76
+ }} onClose={(args) => {
77
+ if (args.reason === "item-selection")
78
+ return;
79
+ setActive(false);
80
+ }} active={active} multiline onChange={(args) => setQuery(args.value)} onItemSelect={(args) => {
74
81
  setCustomValueQuery(query);
75
82
  setQuery("");
76
83
  if (args.value === "_custom") {
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import React from "react";
4
- import { classNames, responsiveVariables, responsiveClassNames, debounceHandler, } from "../../utilities/helpers.js";
4
+ import { classNames, responsiveVariables, responsiveClassNames, throttleHandler, } from "../../utilities/helpers.js";
5
5
  import View from "../View/index.js";
6
6
  import useRTL from "../../hooks/useRTL.js";
7
7
  import useIsomorphicLayoutEffect from "../../hooks/useIsomorphicLayoutEffect.js";
@@ -9,7 +9,9 @@ import CarouselControl from "./CarouselControl.js";
9
9
  import * as T from "./Carousel.types.js";
10
10
  import s from "./Carousel.module.css";
11
11
  const Carousel = (props) => {
12
- const { children, gap = 3, visibleItems, bleed, navigationDisplay, instanceRef, className, attributes, } = props;
12
+ const { children, gap = 3, visibleItems, bleed, navigationDisplay, onChange, onScroll, instanceRef, className, attributes, } = props;
13
+ const currentIndexRef = React.useRef(0);
14
+ const itemRefs = React.useRef([]);
13
15
  const [mounted, setMounted] = React.useState(false);
14
16
  const [scrollPosition, setScrollPosition] = React.useState(0);
15
17
  const [isRTL] = useRTL();
@@ -21,9 +23,21 @@ const Carousel = (props) => {
21
23
  });
22
24
  }
23
25
  const rootClassNames = classNames(s.root, className, ...responsiveClassNames(s, "--bleed", typeof bleed === "number" ? true : bleedClassNames));
24
- const handleScroll = debounceHandler((event) => {
26
+ const handleItemRef = (el, index) => {
27
+ itemRefs.current[index] = el;
28
+ // TODO: Enable in React v19 since it introduced refs cleanup
29
+ // return () => {
30
+ // itemRefs.current[index] = null;
31
+ // };
32
+ };
33
+ const handleScroll = throttleHandler((event) => {
25
34
  const el = event.target;
35
+ const firstVisibleIndex = getFirstVisibleIndex();
26
36
  setScrollPosition(el.scrollLeft);
37
+ onScroll?.(event);
38
+ if (currentIndexRef.current !== firstVisibleIndex)
39
+ onChange?.({ index: firstVisibleIndex });
40
+ currentIndexRef.current = firstVisibleIndex;
27
41
  }, 16);
28
42
  const getItemsGap = () => {
29
43
  const style = getComputedStyle(scrollElRef.current);
@@ -31,6 +45,40 @@ const Carousel = (props) => {
31
45
  const xGap = style.gap.split(" ")[0];
32
46
  return Number(xGap.replace("px", ""));
33
47
  };
48
+ const getFirstVisibleIndex = () => {
49
+ let resultIndex = 0;
50
+ let sizeCalc = 0;
51
+ const scrollEl = scrollElRef.current;
52
+ if (!scrollEl)
53
+ return resultIndex;
54
+ const scrollValue = isRTL ? -scrollEl.scrollLeft : scrollEl.scrollLeft;
55
+ const gap = getItemsGap();
56
+ itemRefs.current.some((el, index) => {
57
+ if (!el)
58
+ return false;
59
+ const visible = sizeCalc + el.clientWidth / 2 >= scrollValue;
60
+ if (visible) {
61
+ resultIndex = index;
62
+ return true;
63
+ }
64
+ sizeCalc += el?.clientWidth + gap;
65
+ return false;
66
+ });
67
+ return resultIndex;
68
+ };
69
+ const navigateTo = (index) => {
70
+ const scrollEl = scrollElRef.current;
71
+ const el = itemRefs.current[index];
72
+ if (!el)
73
+ return;
74
+ scrollEl.scrollTo({
75
+ // Browsers mirror offsetLeft value but we need to also keep the target element on the other side of the container
76
+ // so adding addition calculations for the width of the content outside the target el
77
+ left: isRTL ? el.offsetLeft - (scrollEl.clientWidth - el.clientWidth) : el.offsetLeft,
78
+ top: 0,
79
+ behavior: "smooth",
80
+ });
81
+ };
34
82
  const navigateRight = () => {
35
83
  const scrollEl = scrollElRef.current;
36
84
  scrollEl.scrollBy({
@@ -52,6 +100,7 @@ const Carousel = (props) => {
52
100
  React.useImperativeHandle(instanceRef, () => ({
53
101
  navigateBack,
54
102
  navigateForward,
103
+ navigateTo,
55
104
  }));
56
105
  /**
57
106
  * Changing flag here since scroll ref changing won't rerender the controls and show them after SSR
@@ -63,6 +112,6 @@ const Carousel = (props) => {
63
112
  ...responsiveVariables("--rs-carousel-items", visibleItems),
64
113
  ...responsiveVariables("--rs-carousel-bleed", bleed),
65
114
  ...attributes?.style,
66
- }, children: [navigationDisplay !== "hidden" && (_jsxs(_Fragment, { children: [_jsx(CarouselControl, { isRTL: isRTL, type: T.ControlType.back, scrollElRef: scrollElRef, scrollPosition: scrollPosition, onClick: navigateBack, mounted: mounted }), _jsx(CarouselControl, { isRTL: isRTL, type: T.ControlType.forward, scrollElRef: scrollElRef, scrollPosition: scrollPosition, onClick: navigateForward, mounted: mounted })] })), _jsx(View, { as: "ul", direction: "row", wrap: false, gap: gap, className: s.scroll, attributes: { ref: scrollElRef, onScroll: handleScroll }, children: React.Children.map(children, (child) => (_jsx(View.Item, { className: s.item, as: "li", children: child }))) })] }));
115
+ }, children: [navigationDisplay !== "hidden" && (_jsxs(_Fragment, { children: [_jsx(CarouselControl, { isRTL: isRTL, type: T.ControlType.back, scrollElRef: scrollElRef, scrollPosition: scrollPosition, onClick: navigateBack, mounted: mounted }), _jsx(CarouselControl, { isRTL: isRTL, type: T.ControlType.forward, scrollElRef: scrollElRef, scrollPosition: scrollPosition, onClick: navigateForward, mounted: mounted })] })), _jsx(View, { as: "ul", direction: "row", wrap: false, gap: gap, className: s.scroll, attributes: { ref: scrollElRef, onScroll: handleScroll }, children: React.Children.map(children, (child, index) => (_jsx(View.Item, { className: s.item, as: "li", attributes: { ref: (el) => handleItemRef(el, index) }, children: child }))) })] }));
67
116
  };
68
117
  export default Carousel;
@@ -3,6 +3,7 @@ import type * as G from "../../types/global";
3
3
  export type Instance = {
4
4
  navigateBack: () => void;
5
5
  navigateForward: () => void;
6
+ navigateTo: (index: number) => void;
6
7
  } | undefined;
7
8
  export declare enum ControlType {
8
9
  back = "back",
@@ -23,6 +24,10 @@ export type Props = {
23
24
  bleed?: G.Responsive<number>;
24
25
  navigationDisplay?: "hidden";
25
26
  instanceRef?: React.Ref<Instance>;
27
+ onChange?: (args: {
28
+ index: number;
29
+ }) => void;
30
+ onScroll?: (e: React.UIEvent<HTMLUListElement>) => void;
26
31
  className?: G.ClassName;
27
32
  attributes?: G.Attributes<"div">;
28
33
  };
@@ -1,4 +1,6 @@
1
1
  import React from "react";
2
+ import { StoryObj } from "@storybook/react";
3
+ import { fn } from "@storybook/test";
2
4
  declare const _default: {
3
5
  title: string;
4
6
  component: (props: import("./..").CarouselProps) => React.JSX.Element;
@@ -12,7 +14,21 @@ declare const _default: {
12
14
  };
13
15
  };
14
16
  export default _default;
15
- export declare const visibleItems: () => React.JSX.Element;
16
- export declare const gap: () => React.JSX.Element;
17
- export declare const bleed: () => React.JSX.Element;
18
- export declare const navigation: () => React.JSX.Element;
17
+ export declare const base: StoryObj;
18
+ export declare const visibleItems: {
19
+ name: string;
20
+ render: () => React.JSX.Element;
21
+ };
22
+ export declare const gap: {
23
+ name: string;
24
+ render: () => React.JSX.Element;
25
+ };
26
+ export declare const bleed: {
27
+ name: string;
28
+ render: () => React.JSX.Element;
29
+ };
30
+ export declare const navigationDisplay: StoryObj;
31
+ export declare const instanceRef: StoryObj<{
32
+ handleChange: ReturnType<typeof fn>;
33
+ }>;
34
+ export declare const className: StoryObj;
@@ -1,4 +1,5 @@
1
1
  import React from "react";
2
+ import { expect, fn, userEvent, waitFor } from "@storybook/test";
2
3
  import { Example, Placeholder } from "../../../utilities/storybook/index.js";
3
4
  import Carousel from "../index.js";
4
5
  import Button from "../../Button/index.js";
@@ -16,116 +17,208 @@ export default {
16
17
  },
17
18
  },
18
19
  };
19
- export const visibleItems = () => (<Example>
20
- <Example.Item title="visibleItems: 3">
21
- <Carousel visibleItems={3}>
22
- <Placeholder h={100}/>
23
- <Placeholder h={100}/>
24
- <Placeholder h={100}/>
25
- <Placeholder h={100}/>
26
- <Placeholder h={100}/>
27
- <Placeholder h={100}/>
28
- </Carousel>
29
- </Example.Item>
20
+ export const base = {
21
+ name: "base",
22
+ render: () => (<Carousel attributes={{ "data-testid": "test-id" }} visibleItems={3}>
23
+ <Placeholder h={100}>Content</Placeholder>
24
+ <Placeholder h={100}>Content</Placeholder>
25
+ <Placeholder h={100}>Content</Placeholder>
26
+ <Placeholder h={100}>Content</Placeholder>
27
+ <Placeholder h={100}>Content</Placeholder>
28
+ <Placeholder h={100}>Content</Placeholder>
29
+ </Carousel>),
30
+ play: async ({ canvas }) => {
31
+ const root = canvas.getByTestId("test-id");
32
+ const items = canvas.getAllByText("Content");
33
+ const buttons = root.querySelectorAll("button");
34
+ expect(root).toBeInTheDocument();
35
+ expect(root.tagName).toBe("SECTION");
36
+ expect(items).toHaveLength(6);
37
+ expect(buttons).toHaveLength(2);
38
+ },
39
+ };
40
+ export const visibleItems = {
41
+ name: "visibleItems",
42
+ render: () => (<Example>
43
+ <Example.Item title="visibleItems: 3">
44
+ <Carousel visibleItems={3}>
45
+ <Placeholder h={100}/>
46
+ <Placeholder h={100}/>
47
+ <Placeholder h={100}/>
48
+ <Placeholder h={100}/>
49
+ <Placeholder h={100}/>
50
+ <Placeholder h={100}/>
51
+ </Carousel>
52
+ </Example.Item>
30
53
 
31
- <Example.Item title={["responsive visibleItems", "[s] 2", "[m+] 3"]}>
32
- <Carousel visibleItems={{ s: 2, m: 3 }}>
33
- <Placeholder h={100}/>
34
- <Placeholder h={100}/>
35
- <Placeholder h={100}/>
36
- <Placeholder h={100}/>
37
- <Placeholder h={100}/>
38
- <Placeholder h={100}/>
39
- </Carousel>
40
- </Example.Item>
54
+ <Example.Item title={["responsive visibleItems", "s: 2, m+ 3"]}>
55
+ <Carousel visibleItems={{ s: 2, m: 3 }}>
56
+ <Placeholder h={100}/>
57
+ <Placeholder h={100}/>
58
+ <Placeholder h={100}/>
59
+ <Placeholder h={100}/>
60
+ <Placeholder h={100}/>
61
+ <Placeholder h={100}/>
62
+ </Carousel>
63
+ </Example.Item>
41
64
 
42
- <Example.Item title="visibleItems: auto">
43
- <Carousel>
44
- <Placeholder h={100}/>
45
- <Placeholder h={100} w={200}/>
46
- <Placeholder h={100} w={300}/>
47
- <Placeholder h={100}/>
48
- <Placeholder h={100}/>
49
- <Placeholder h={100}/>
50
- </Carousel>
51
- </Example.Item>
52
- </Example>);
53
- export const gap = () => (<Example>
54
- <Example.Item title="gap: 2, visibleItems 3">
55
- <Carousel visibleItems={3} gap={2}>
56
- <Placeholder h={100}/>
57
- <Placeholder h={100}/>
58
- <Placeholder h={100}/>
59
- <Placeholder h={100}/>
60
- <Placeholder h={100}/>
61
- <Placeholder h={100}/>
62
- </Carousel>
63
- </Example.Item>
65
+ <Example.Item title="visibleItems: auto">
66
+ <Carousel>
67
+ <Placeholder h={100}/>
68
+ <Placeholder h={100} w={200}/>
69
+ <Placeholder h={100} w={300}/>
70
+ <Placeholder h={100}/>
71
+ <Placeholder h={100}/>
72
+ <Placeholder h={100}/>
73
+ </Carousel>
74
+ </Example.Item>
75
+ </Example>),
76
+ };
77
+ export const gap = {
78
+ name: "gap",
79
+ render: () => (<Example>
80
+ <Example.Item title="gap: 2, visibleItems 3">
81
+ <Carousel visibleItems={3} gap={2}>
82
+ <Placeholder h={100}/>
83
+ <Placeholder h={100}/>
84
+ <Placeholder h={100}/>
85
+ <Placeholder h={100}/>
86
+ <Placeholder h={100}/>
87
+ <Placeholder h={100}/>
88
+ </Carousel>
89
+ </Example.Item>
64
90
 
65
- <Example.Item title={["responsive gap, visibleItems: 3", "[s] 2", "[l] 8"]}>
66
- <Carousel visibleItems={3} gap={{ s: 2, l: 8 }}>
67
- <Placeholder h={100}/>
68
- <Placeholder h={100}/>
69
- <Placeholder h={100}/>
70
- <Placeholder h={100}/>
71
- <Placeholder h={100}/>
72
- <Placeholder h={100}/>
73
- </Carousel>
74
- </Example.Item>
75
- </Example>);
76
- export const bleed = () => (<Example>
77
- <Example.Item title="bleed: 4, visibleItems: 3">
78
- <Carousel visibleItems={3} bleed={4}>
79
- <Placeholder h={100}/>
80
- <Placeholder h={100}/>
81
- <Placeholder h={100}/>
82
- <Placeholder h={100}/>
83
- <Placeholder h={100}/>
84
- <Placeholder h={100}/>
85
- </Carousel>
86
- </Example.Item>
91
+ <Example.Item title={["gap: responsive, visibleItems: 3", "s: 2, l: 8"]}>
92
+ <Carousel visibleItems={3} gap={{ s: 2, l: 8 }}>
93
+ <Placeholder h={100}/>
94
+ <Placeholder h={100}/>
95
+ <Placeholder h={100}/>
96
+ <Placeholder h={100}/>
97
+ <Placeholder h={100}/>
98
+ <Placeholder h={100}/>
99
+ </Carousel>
100
+ </Example.Item>
101
+ </Example>),
102
+ };
103
+ export const bleed = {
104
+ name: "bleed",
105
+ render: () => (<Example>
106
+ <Example.Item title="bleed: 4, visibleItems: 3">
107
+ <Carousel visibleItems={3} bleed={4}>
108
+ <Placeholder h={100}/>
109
+ <Placeholder h={100}/>
110
+ <Placeholder h={100}/>
111
+ <Placeholder h={100}/>
112
+ <Placeholder h={100}/>
113
+ <Placeholder h={100}/>
114
+ </Carousel>
115
+ </Example.Item>
87
116
 
88
- <Example.Item title={["responsive bleed, visibleItems: 3", "[s] 4, [l+] 0"]}>
89
- <Carousel visibleItems={3} bleed={{ s: 4, l: 0 }}>
90
- <Placeholder h={100}/>
91
- <Placeholder h={100}/>
92
- <Placeholder h={100}/>
93
- <Placeholder h={100}/>
94
- <Placeholder h={100}/>
95
- <Placeholder h={100}/>
96
- </Carousel>
97
- </Example.Item>
98
- </Example>);
99
- const RefDemo = () => {
100
- const carouselRef = React.useRef(null);
101
- return (<View gap={3}>
102
- <View gap={3} direction="row">
103
- <Button onClick={() => carouselRef.current?.navigateBack()}>Back</Button>
104
- <Button onClick={() => carouselRef.current?.navigateForward()}>Forward</Button>
105
- </View>
106
- <Carousel visibleItems={2} instanceRef={carouselRef} navigationDisplay="hidden">
117
+ <Example.Item title={["responsive bleed, visibleItems: 3", "[s] 4, [l+] 0"]}>
118
+ <Carousel visibleItems={3} bleed={{ s: 4, l: 0 }}>
119
+ <Placeholder h={100}/>
120
+ <Placeholder h={100}/>
121
+ <Placeholder h={100}/>
122
+ <Placeholder h={100}/>
123
+ <Placeholder h={100}/>
124
+ <Placeholder h={100}/>
125
+ </Carousel>
126
+ </Example.Item>
127
+ </Example>),
128
+ };
129
+ export const navigationDisplay = {
130
+ name: "navigationDisplay",
131
+ render: () => (<Example>
132
+ <Example.Item title="navigationDisplay: hidden">
133
+ <Carousel visibleItems={3} navigationDisplay="hidden" attributes={{ "data-testid": "test-id" }}>
134
+ <Placeholder h={100}/>
135
+ <Placeholder h={100}/>
136
+ <Placeholder h={100}/>
137
+ <Placeholder h={100}/>
138
+ <Placeholder h={100}/>
139
+ <Placeholder h={100}/>
140
+ </Carousel>
141
+ </Example.Item>
142
+ </Example>),
143
+ play: async ({ canvas }) => {
144
+ const root = canvas.getByTestId("test-id");
145
+ const buttons = root.querySelectorAll("button");
146
+ expect(buttons).toHaveLength(0);
147
+ },
148
+ };
149
+ export const instanceRef = {
150
+ name: "instanceRef, onChange",
151
+ args: {
152
+ handleChange: fn(),
153
+ },
154
+ render: (args) => {
155
+ const carouselRef = React.useRef(null);
156
+ const [index, setIndex] = React.useState(0);
157
+ return (<Example>
158
+ <Example.Item title="instanceRef, onChange">
159
+ <View gap={3}>
160
+ <View gap={3} direction="row" align="center">
161
+ <Button onClick={() => carouselRef.current?.navigateBack()}>Back</Button>
162
+ <Button onClick={() => carouselRef.current?.navigateForward()}>Forward</Button>
163
+ <Button onClick={() => carouselRef.current?.navigateTo(3)}>To 3</Button>
164
+ <View.Item>Index: {index}</View.Item>
165
+ </View>
166
+ <Carousel visibleItems={2} instanceRef={carouselRef} navigationDisplay="hidden" onChange={(changeArgs) => {
167
+ args.handleChange(changeArgs);
168
+ setIndex(changeArgs.index);
169
+ }}>
170
+ <Placeholder h={100}>Item 0</Placeholder>
171
+ <Placeholder h={100}>Item 1</Placeholder>
172
+ <Placeholder h={100}>Item 2</Placeholder>
173
+ <Placeholder h={100}>Item 3</Placeholder>
174
+ <Placeholder h={100}>Item 4</Placeholder>
175
+ <Placeholder h={100}>Item 5</Placeholder>
176
+ </Carousel>
177
+ </View>
178
+ </Example.Item>
179
+ </Example>);
180
+ },
181
+ play: async ({ canvas, args }) => {
182
+ const buttons = canvas.getAllByRole("button");
183
+ const backButton = buttons[0];
184
+ const forwardButton = buttons[1];
185
+ const toButton = buttons[2];
186
+ await userEvent.click(forwardButton);
187
+ await waitFor(() => {
188
+ expect(args.handleChange).toHaveBeenCalledWith({ index: 1 });
189
+ expect(args.handleChange).toHaveBeenCalledWith({ index: 2 });
190
+ expect(args.handleChange).not.toHaveBeenCalledWith({ index: 3 });
191
+ });
192
+ args.handleChange.mockClear();
193
+ await userEvent.click(backButton);
194
+ await waitFor(() => {
195
+ expect(args.handleChange).toHaveBeenCalledWith({ index: 0 });
196
+ expect(args.handleChange).toHaveBeenCalledWith({ index: 1 });
197
+ });
198
+ args.handleChange.mockClear();
199
+ await userEvent.click(toButton);
200
+ await waitFor(() => {
201
+ expect(args.handleChange).toHaveBeenCalledWith({ index: 1 });
202
+ expect(args.handleChange).toHaveBeenCalledWith({ index: 2 });
203
+ expect(args.handleChange).toHaveBeenCalledWith({ index: 3 });
204
+ });
205
+ },
206
+ };
207
+ export const className = {
208
+ name: "className, attributes",
209
+ render: () => (<div data-testid="root">
210
+ <Carousel visibleItems={2} className="test-classname" attributes={{ id: "test-id" }}>
211
+ <Placeholder h={100}>Item 0</Placeholder>
107
212
  <Placeholder h={100}>Item 1</Placeholder>
108
213
  <Placeholder h={100}>Item 2</Placeholder>
109
214
  <Placeholder h={100}>Item 3</Placeholder>
110
215
  <Placeholder h={100}>Item 4</Placeholder>
111
216
  <Placeholder h={100}>Item 5</Placeholder>
112
- <Placeholder h={100}>Item 6</Placeholder>
113
217
  </Carousel>
114
- </View>);
218
+ </div>),
219
+ play: async ({ canvas }) => {
220
+ const root = canvas.getByTestId("root").firstChild;
221
+ expect(root).toHaveClass("test-classname");
222
+ expect(root).toHaveAttribute("id", "test-id");
223
+ },
115
224
  };
116
- export const navigation = () => (<Example>
117
- <Example.Item title="navigation: hidden">
118
- <Carousel visibleItems={3} navigationDisplay="hidden">
119
- <Placeholder h={100}/>
120
- <Placeholder h={100}/>
121
- <Placeholder h={100}/>
122
- <Placeholder h={100}/>
123
- <Placeholder h={100}/>
124
- <Placeholder h={100}/>
125
- </Carousel>
126
- </Example.Item>
127
-
128
- <Example.Item title="navigation: external">
129
- <RefDemo />
130
- </Example.Item>
131
- </Example>);
@@ -1 +1 @@
1
- .root{box-sizing:content-box;margin:0 auto}
1
+ .root{margin:0 auto}
@@ -27,10 +27,10 @@ const ContextMenu = (props) => {
27
27
  React.useEffect(() => {
28
28
  return () => unlockScroll();
29
29
  }, [unlockScroll]);
30
- return (_jsx("div", { className: s.root, ref: originRef, children: _jsx(DropdownMenu, { ...dropdownMenuProps, position: position, originCoordinates: coordinates, active: !!coordinates, onClose: () => {
30
+ return (_jsx("div", { className: s.root, ref: originRef, children: _jsx(DropdownMenu, { ...dropdownMenuProps, position: position, originCoordinates: coordinates, active: !!coordinates, onClose: (args) => {
31
31
  setCoordinates(undefined);
32
32
  unlockScroll();
33
- onClose?.();
33
+ onClose?.(args);
34
34
  } }) }));
35
35
  };
36
36
  ContextMenu.Content = DropdownMenu.Content;
@@ -42,7 +42,7 @@ export const handlers = {
42
42
  await sleep(500);
43
43
  await userEvent.click(root);
44
44
  expect(args.handleClose).toHaveBeenCalledTimes(1);
45
- expect(args.handleClose).toHaveBeenCalledWith();
45
+ expect(args.handleClose).toHaveBeenCalledWith({ reason: "outside-click" });
46
46
  await waitFor(() => {
47
47
  expect(item).not.toBeInTheDocument();
48
48
  expect(scroll).not.toHaveStyle("overflow: hidden");
@@ -40,8 +40,13 @@ const DropdownMenuItem = (props) => {
40
40
  const { onClick } = props;
41
41
  const { handleClose } = useFlyoutContext();
42
42
  const handleClick = (e) => {
43
+ /**
44
+ * Stop event propagation to make sure outside click doesn't get triggered
45
+ * after the content is closed
46
+ */
47
+ e.stopPropagation();
43
48
  if (handleClose)
44
- handleClose({ closeParents: true });
49
+ handleClose({ closeParents: true, reason: "item-selection" });
45
50
  if (onClick)
46
51
  onClick(e);
47
52
  };
@@ -34,7 +34,7 @@ export const defaultActive = {
34
34
  await userEvent.click(document.body);
35
35
  await waitFor(() => {
36
36
  expect(args.handleClose).toHaveBeenCalledTimes(1);
37
- expect(args.handleClose).toHaveBeenCalledWith();
37
+ expect(args.handleClose).toHaveBeenCalledWith({ reason: "outside-click" });
38
38
  expect(item).not.toBeInTheDocument();
39
39
  });
40
40
  await userEvent.click(trigger);
@@ -66,7 +66,7 @@ export const active = {
66
66
  await userEvent.click(document.body);
67
67
  await waitFor(() => {
68
68
  expect(args.handleClose).toHaveBeenCalledTimes(1);
69
- expect(args.handleClose).toHaveBeenCalledWith();
69
+ expect(args.handleClose).toHaveBeenCalledWith({ reason: "outside-click" });
70
70
  });
71
71
  expect(item).toBeInTheDocument();
72
72
  },