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.
- package/CHANGELOG.md +13 -0
- package/dist/bundle.css +1 -1
- package/dist/bundle.d.ts +4 -1
- package/dist/bundle.js +11 -11
- package/dist/components/Autocomplete/Autocomplete.js +34 -24
- package/dist/components/Autocomplete/Autocomplete.types.d.ts +1 -1
- package/dist/components/Autocomplete/tests/Autocomplete.stories.js +8 -1
- package/dist/components/Carousel/Carousel.js +53 -4
- package/dist/components/Carousel/Carousel.types.d.ts +5 -0
- package/dist/components/Carousel/tests/Carousel.stories.d.ts +20 -4
- package/dist/components/Carousel/tests/Carousel.stories.js +195 -102
- package/dist/components/Container/Container.module.css +1 -1
- package/dist/components/ContextMenu/ContextMenu.js +2 -2
- package/dist/components/ContextMenu/tests/ContextMenu.test.stories.js +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.js +6 -1
- package/dist/components/DropdownMenu/tests/DropdownMenu.test.stories.js +2 -2
- package/dist/components/Popover/Popover.js +1 -1
- package/dist/components/Popover/tests/Popover.test.stories.js +4 -4
- package/dist/components/ProgressIndicator/ProgressIndicator.d.ts +3 -0
- package/dist/components/ProgressIndicator/ProgressIndicator.js +101 -0
- package/dist/components/ProgressIndicator/ProgressIndicator.module.css +1 -0
- package/dist/components/ProgressIndicator/ProgressIndicator.types.d.ts +9 -0
- package/dist/components/ProgressIndicator/ProgressIndicator.types.js +1 -0
- package/dist/components/ProgressIndicator/index.d.ts +2 -0
- package/dist/components/ProgressIndicator/index.js +1 -0
- package/dist/components/ProgressIndicator/tests/ProgressIndicator.stories.d.ts +19 -0
- package/dist/components/ProgressIndicator/tests/ProgressIndicator.stories.js +85 -0
- package/dist/components/Reshaped/Reshaped.js +4 -5
- package/dist/components/Table/index.d.ts +1 -1
- package/dist/components/Table/tests/Table.stories.d.ts +5 -5
- package/dist/components/Table/tests/Table.test.stories.d.ts +5 -5
- package/dist/components/TextField/TextField.module.css +1 -1
- package/dist/components/Toast/tests/Toast.stories.js +22 -7
- package/dist/components/Tooltip/tests/Tooltip.test.stories.js +1 -1
- package/dist/components/_private/Flyout/Flyout.module.css +1 -1
- package/dist/components/_private/Flyout/Flyout.types.d.ts +14 -3
- package/dist/components/_private/Flyout/FlyoutContent.js +37 -7
- package/dist/components/_private/Flyout/FlyoutControlled.js +12 -11
- package/dist/components/_private/Flyout/FlyoutUncontrolled.js +3 -5
- package/dist/components/_private/Flyout/index.d.ts +1 -1
- package/dist/components/_private/Flyout/tests/Flyout.stories.d.ts +79 -13
- package/dist/components/_private/Flyout/tests/Flyout.stories.js +526 -280
- package/dist/components/_private/Flyout/useFlyout.js +9 -3
- package/dist/components/_private/Flyout/utilities/isFullyVisible.d.ts +3 -1
- package/dist/components/_private/Flyout/utilities/isFullyVisible.js +2 -2
- package/dist/hooks/_private/usePrevious.d.ts +2 -0
- package/dist/hooks/_private/usePrevious.js +17 -0
- package/dist/hooks/_private/useSingletonEnvironment.d.ts +1 -1
- package/dist/hooks/_private/useSingletonEnvironment.js +1 -1
- package/dist/hooks/_private/useSingletonKeyboardMode.d.ts +13 -2
- package/dist/hooks/_private/useSingletonKeyboardMode.js +48 -15
- package/dist/hooks/tests/useKeyboardMode.stories.d.ts +6 -0
- package/dist/hooks/tests/useKeyboardMode.stories.js +37 -0
- package/dist/hooks/useKeyboardMode.d.ts +7 -0
- package/dist/hooks/useKeyboardMode.js +13 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +2 -0
- package/dist/utilities/a11y/index.d.ts +1 -1
- package/dist/utilities/a11y/index.js +1 -1
- package/dist/utilities/a11y/keyboardMode.d.ts +2 -2
- package/dist/utilities/a11y/keyboardMode.js +2 -2
- package/dist/utilities/dom/find.d.ts +4 -1
- package/dist/utilities/dom/find.js +4 -4
- package/dist/utilities/scroll/lockStandard.js +1 -1
- package/package.json +34 -35
- package/dist/components/Carousel/tests/Carousel.test.stories.d.ts +0 -17
- package/dist/components/Carousel/tests/Carousel.test.stories.js +0 -52
- package/dist/components/_private/Flyout/tests/Flyout.test.stories.d.ts +0 -28
- 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 [
|
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
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
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
|
-
}}
|
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,
|
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
|
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
|
16
|
-
export declare const
|
17
|
-
|
18
|
-
|
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
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
<
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
<
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
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
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
<
|
105
|
-
|
106
|
-
|
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
|
-
</
|
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{
|
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
|
},
|