@simplybusiness/mobius 8.0.1 → 9.0.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 +45 -0
- package/dist/cjs/index.js +4773 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/cjs/meta.json +4948 -0
- package/dist/esm/index.js +542 -694
- package/dist/esm/index.js.map +4 -4
- package/dist/esm/meta.json +300 -532
- package/dist/esm/tsconfig.build.tsbuildinfo +1 -1
- package/dist/types/src/hooks/index.d.ts +0 -7
- package/dist/types/src/utils/htmlDialogPolyfill.d.ts +1 -0
- package/dist/types/src/utils/index.d.ts +0 -1
- package/dist/types/src/utils/mockMatchMedia.d.ts +1 -0
- package/dist/types/vitest.config.d.ts +2 -0
- package/package.json +14 -18
- package/src/components/Accordion/Accordion.stories.tsx +1 -1
- package/src/components/Accordion/Accordion.test.tsx +12 -12
- package/src/components/Accordion/Accordion.tsx +1 -1
- package/src/components/Accordion/AccordionList.stories.tsx +1 -1
- package/src/components/Accordion/AccordionList.test.tsx +6 -6
- package/src/components/AddressLookup/AddressLookup.stories.tsx +1 -1
- package/src/components/AddressLookup/AddressLookup.test.tsx +19 -20
- package/src/components/AddressLookup/LoqateAddressLookupService.test.tsx +7 -6
- package/src/components/Alert/Alert.stories.tsx +1 -1
- package/src/components/Box/Box.stories.tsx +1 -1
- package/src/components/Breadcrumbs/Breadcrumbs.stories.tsx +1 -1
- package/src/components/Button/Button.stories.tsx +3 -4
- package/src/components/Button/Button.test.tsx +4 -4
- package/src/components/Checkbox/Checkbox.stories.tsx +1 -1
- package/src/components/Checkbox/Checkbox.test.tsx +2 -2
- package/src/components/Checkbox/CheckboxGroup.stories.tsx +1 -1
- package/src/components/Checkbox/CheckboxGroup.test.tsx +5 -5
- package/src/components/Combobox/Combobox.stories.tsx +1 -1
- package/src/components/Combobox/Combobox.test.tsx +67 -78
- package/src/components/Combobox/Combobox.tsx +2 -1
- package/src/components/Combobox/useComboboxOptions.test.ts +30 -30
- package/src/components/Combobox/useComboboxOptions.ts +1 -1
- package/src/components/Container/Container.stories.tsx +1 -1
- package/src/components/DateField/DateField.stories.tsx +1 -1
- package/src/components/DateField/DateField.test.tsx +1 -1
- package/src/components/Divider/Divider.stories.tsx +1 -1
- package/src/components/Drawer/Drawer.stories.tsx +1 -1
- package/src/components/Drawer/Drawer.test.tsx +6 -6
- package/src/components/DropdownMenu/DropdownMenu.stories.tsx +8 -10
- package/src/components/DropdownMenu/DropdownMenu.test.tsx +1 -1
- package/src/components/ErrorMessage/ErrorMessage.stories.tsx +1 -1
- package/src/components/ExpandableText/ExpandableText.test.tsx +14 -14
- package/src/components/Fieldset/Fieldset.stories.tsx +1 -1
- package/src/components/Flex/Flex.stories.tsx +1 -1
- package/src/components/Grid/Grid.stories.tsx +4 -7
- package/src/components/Icon/Icon.stories.tsx +1 -1
- package/src/components/Image/Image.stories.tsx +1 -1
- package/src/components/Label/Label.stories.tsx +1 -1
- package/src/components/Link/Link.stories.tsx +1 -1
- package/src/components/Link/Link.test.tsx +1 -1
- package/src/components/LinkButton/LinkButton.stories.tsx +1 -1
- package/src/components/LinkButton/LinkButton.test.tsx +2 -2
- package/src/components/List/List.stories.tsx +1 -1
- package/src/components/LoadingIndicator/LoadingIndicator.stories.tsx +1 -1
- package/src/components/Logo/Logo.stories.tsx +1 -1
- package/src/components/Modal/Modal.stories.tsx +1 -1
- package/src/components/Modal/Modal.test.tsx +6 -6
- package/src/components/NumberField/NumberField.stories.tsx +1 -1
- package/src/components/NumberField/NumberField.test.tsx +5 -5
- package/src/components/PasswordField/PasswordField.stories.tsx +1 -1
- package/src/components/Popover/Popover.stories.tsx +4 -8
- package/src/components/Popover/Popover.test.tsx +4 -4
- package/src/components/Popover/Popover.tsx +1 -1
- package/src/components/Progress/Progress.stories.tsx +1 -1
- package/src/components/Radio/Radio.stories.tsx +1 -1
- package/src/components/Radio/Radio.test.tsx +9 -9
- package/src/components/SVG/SVG.stories.tsx +1 -1
- package/src/components/Segment/Segment.stories.tsx +1 -1
- package/src/components/Select/Select.stories.tsx +1 -1
- package/src/components/Select/Select.test.tsx +1 -1
- package/src/components/Slider/Slider.stories.tsx +1 -1
- package/src/components/Slider/Slider.test.tsx +6 -6
- package/src/components/Slider/helpers.test.ts +1 -1
- package/src/components/Stack/Stack.stories.tsx +1 -1
- package/src/components/Switch/Switch.stories.tsx +1 -1
- package/src/components/Switch/Switch.test.tsx +1 -1
- package/src/components/Table/Table.stories.tsx +1 -1
- package/src/components/Text/Text.stories.tsx +1 -1
- package/src/components/TextArea/TextArea.stories.tsx +1 -1
- package/src/components/TextArea/TextArea.test.tsx +3 -3
- package/src/components/TextField/TextField.stories.tsx +1 -1
- package/src/components/TextOrHTML/TextOrHTML.stories.tsx +1 -1
- package/src/components/Title/Title.stories.tsx +1 -1
- package/src/components/Toast/Toast.stories.tsx +1 -1
- package/src/components/Toast/Toast.test.tsx +6 -6
- package/src/components/Trust/Trust.stories.tsx +1 -1
- package/src/components/VisuallyHidden/VisuallyHidden.stories.tsx +1 -1
- package/src/hooks/index.tsx +0 -7
- package/src/hooks/useBreakpoint/useBreakpoint.ssr.test.tsx +18 -0
- package/src/hooks/useBreakpoint/useBreakpoint.stories.tsx +1 -1
- package/src/hooks/useBreakpoint/useBreakpoint.test.tsx +65 -5
- package/src/hooks/useBreakpoint/useBreakpoint.tsx +25 -39
- package/src/hooks/useButton/useButton.test.tsx +4 -4
- package/src/hooks/useDialog/useDialog.ts +1 -1
- package/src/hooks/useLabel/useLabel.test.tsx +1 -1
- package/src/hooks/useTextField/useTextField.test.tsx +4 -4
- package/src/public-whitelist.test.ts +1 -0
- package/src/utils/delay.test.ts +4 -4
- package/src/utils/{jestHTMLDialogPolyfill.ts → htmlDialogPolyfill.ts} +5 -5
- package/src/utils/index.ts +0 -1
- package/src/utils/mockMatchMedia.ts +16 -0
- package/dist/types/src/hooks/useBodyScrollLock/index.d.ts +0 -1
- package/dist/types/src/hooks/useBodyScrollLock/useBodyScrollLock.d.ts +0 -3
- package/dist/types/src/hooks/useDebouncedValue/index.d.ts +0 -1
- package/dist/types/src/hooks/useDebouncedValue/useDebouncedValue.d.ts +0 -1
- package/dist/types/src/hooks/useOnClickOutside/index.d.ts +0 -1
- package/dist/types/src/hooks/useOnClickOutside/useOnClickOutside.d.ts +0 -2
- package/dist/types/src/hooks/useOnUnmount/index.d.ts +0 -1
- package/dist/types/src/hooks/useOnUnmount/useOnUnmount.d.ts +0 -1
- package/dist/types/src/hooks/usePrefersReducedMotion/index.d.ts +0 -1
- package/dist/types/src/hooks/usePrefersReducedMotion/usePrefersReducedMotion.d.ts +0 -1
- package/dist/types/src/hooks/useRenderCount/index.d.ts +0 -1
- package/dist/types/src/hooks/useRenderCount/useRenderCount.d.ts +0 -1
- package/dist/types/src/hooks/useWindowEvent/index.d.ts +0 -1
- package/dist/types/src/hooks/useWindowEvent/useWindowEvent.d.ts +0 -1
- package/dist/types/src/utils/jestHTMLDialogPolyfill.d.ts +0 -1
- package/dist/types/src/utils/jestMockMatchMedia.d.ts +0 -1
- package/src/hooks/useBodyScrollLock/index.ts +0 -1
- package/src/hooks/useBodyScrollLock/useBodyScrollLock.test.ts +0 -34
- package/src/hooks/useBodyScrollLock/useBodyScrollLock.ts +0 -30
- package/src/hooks/useDebouncedValue/index.tsx +0 -1
- package/src/hooks/useDebouncedValue/useDebouncedValue.test.tsx +0 -62
- package/src/hooks/useDebouncedValue/useDebouncedValue.tsx +0 -25
- package/src/hooks/useOnClickOutside/index.tsx +0 -1
- package/src/hooks/useOnClickOutside/useOnClickOutside.test.tsx +0 -189
- package/src/hooks/useOnClickOutside/useOnClickOutside.tsx +0 -44
- package/src/hooks/useOnUnmount/index.tsx +0 -1
- package/src/hooks/useOnUnmount/useOnUnmount.test.tsx +0 -37
- package/src/hooks/useOnUnmount/useOnUnmount.tsx +0 -8
- package/src/hooks/usePrefersReducedMotion/index.tsx +0 -1
- package/src/hooks/usePrefersReducedMotion/usePrefersReducedMotion.test.tsx +0 -48
- package/src/hooks/usePrefersReducedMotion/usePrefersReducedMotion.tsx +0 -22
- package/src/hooks/useRenderCount/index.ts +0 -1
- package/src/hooks/useRenderCount/useRenderCount.test.ts +0 -26
- package/src/hooks/useRenderCount/useRenderCount.ts +0 -9
- package/src/hooks/useWindowEvent/index.tsx +0 -1
- package/src/hooks/useWindowEvent/useWindowEvent.test.tsx +0 -188
- package/src/hooks/useWindowEvent/useWindowEvent.tsx +0 -41
- package/src/utils/jestMockMatchMedia.ts +0 -16
|
@@ -59,7 +59,7 @@ describe("Slider helpers", () => {
|
|
|
59
59
|
|
|
60
60
|
describe("useUnwrappedHandler", () => {
|
|
61
61
|
it("should call the handler with the unwrapped value", () => {
|
|
62
|
-
const handler =
|
|
62
|
+
const handler = vi.fn();
|
|
63
63
|
const { result } = renderHook(() => useUnwrappedHandler(handler));
|
|
64
64
|
result.current([1]);
|
|
65
65
|
expect(handler).toHaveBeenCalledWith(1);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useEffect, useState } from "react";
|
|
2
|
-
import type { Meta, StoryObj } from "@storybook/react
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
3
3
|
import { Switch as Control, type SwitchProps } from ".";
|
|
4
4
|
import { excludeControls } from "../../utils";
|
|
5
5
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from "@storybook/react
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
2
|
import { excludeControls } from "../../utils";
|
|
3
3
|
import { StoryContainer } from "../../utils/StoryContainer";
|
|
4
4
|
import type { TextAreaProps } from "./TextArea";
|
|
@@ -11,7 +11,7 @@ describe("TextArea", () => {
|
|
|
11
11
|
});
|
|
12
12
|
|
|
13
13
|
it("should call onChange with the updated value when the user types", async () => {
|
|
14
|
-
const callback =
|
|
14
|
+
const callback = vi.fn();
|
|
15
15
|
const { getByLabelText } = render(
|
|
16
16
|
<TextArea label="First name" onChange={callback} />,
|
|
17
17
|
);
|
|
@@ -24,8 +24,8 @@ describe("TextArea", () => {
|
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
it("focuses correctly via keyboard", async () => {
|
|
27
|
-
const callbackFirstName =
|
|
28
|
-
const callbackLastName =
|
|
27
|
+
const callbackFirstName = vi.fn();
|
|
28
|
+
const callbackLastName = vi.fn();
|
|
29
29
|
render(
|
|
30
30
|
<>
|
|
31
31
|
<TextArea label="First name" onChange={callbackFirstName} />
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { search } from "@simplybusiness/icons";
|
|
2
|
-
import type { Meta, StoryObj } from "@storybook/react
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
3
3
|
import { useRef } from "react";
|
|
4
4
|
import { excludeControls } from "../../utils";
|
|
5
5
|
import { StoryContainer } from "../../utils/StoryContainer";
|
|
@@ -4,8 +4,8 @@ import { Toaster, toast } from ".";
|
|
|
4
4
|
|
|
5
5
|
// Mock setPointerCapture which JSDOM doesn't support (used by Sonner)
|
|
6
6
|
beforeAll(() => {
|
|
7
|
-
Element.prototype.setPointerCapture =
|
|
8
|
-
Element.prototype.releasePointerCapture =
|
|
7
|
+
Element.prototype.setPointerCapture = vi.fn();
|
|
8
|
+
Element.prototype.releasePointerCapture = vi.fn();
|
|
9
9
|
});
|
|
10
10
|
|
|
11
11
|
describe("Toast", () => {
|
|
@@ -86,7 +86,7 @@ describe("Toast", () => {
|
|
|
86
86
|
render(<Toaster />);
|
|
87
87
|
|
|
88
88
|
toast.info("Action toast", {
|
|
89
|
-
action: { label: "Undo", onClick:
|
|
89
|
+
action: { label: "Undo", onClick: vi.fn() },
|
|
90
90
|
});
|
|
91
91
|
|
|
92
92
|
await waitFor(() => {
|
|
@@ -108,7 +108,7 @@ describe("Toast", () => {
|
|
|
108
108
|
|
|
109
109
|
it("calls action onClick and dismisses toast when action button is clicked", async () => {
|
|
110
110
|
const user = userEvent.setup();
|
|
111
|
-
const onClickMock =
|
|
111
|
+
const onClickMock = vi.fn();
|
|
112
112
|
render(<Toaster />);
|
|
113
113
|
|
|
114
114
|
toast.info("Action toast", {
|
|
@@ -130,7 +130,7 @@ describe("Toast", () => {
|
|
|
130
130
|
|
|
131
131
|
it("calls cancel onClick and dismisses toast when cancel button is clicked", async () => {
|
|
132
132
|
const user = userEvent.setup();
|
|
133
|
-
const onClickMock =
|
|
133
|
+
const onClickMock = vi.fn();
|
|
134
134
|
render(<Toaster />);
|
|
135
135
|
|
|
136
136
|
toast.info("Cancel toast", {
|
|
@@ -168,7 +168,7 @@ describe("Toast", () => {
|
|
|
168
168
|
|
|
169
169
|
it("calls onDismiss callback when toast is dismissed via close button", async () => {
|
|
170
170
|
const user = userEvent.setup();
|
|
171
|
-
const onDismissMock =
|
|
171
|
+
const onDismissMock = vi.fn();
|
|
172
172
|
render(<Toaster closeButton />);
|
|
173
173
|
|
|
174
174
|
toast.info("Dismissable toast", { onDismiss: onDismissMock });
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from "@storybook/react
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
2
|
import { Trust, type TrustProps } from "..";
|
|
3
3
|
import { excludeControls } from "../../utils";
|
|
4
4
|
import { TrustpilotProvider } from "./TrustpilotProvider";
|
package/src/hooks/index.tsx
CHANGED
|
@@ -1,14 +1,7 @@
|
|
|
1
|
-
export * from "./useBodyScrollLock";
|
|
2
1
|
export * from "./useBreakpoint";
|
|
3
2
|
export * from "./useButton";
|
|
4
|
-
export * from "./useDebouncedValue";
|
|
5
3
|
export * from "./useDialog";
|
|
6
4
|
export * from "./useDialogPolyfill";
|
|
7
5
|
export * from "./useLabel";
|
|
8
|
-
export * from "./useOnClickOutside";
|
|
9
|
-
export * from "./useOnUnmount";
|
|
10
|
-
export * from "./usePrefersReducedMotion";
|
|
11
|
-
export * from "./useRenderCount";
|
|
12
6
|
export * from "./useTextField";
|
|
13
7
|
export * from "./useValidationClasses";
|
|
14
|
-
export * from "./useWindowEvent";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vitest-environment node
|
|
3
|
+
*/
|
|
4
|
+
import { renderToString } from "react-dom/server";
|
|
5
|
+
import { DEFAULT_BREAKPOINTS, useBreakpoint } from ".";
|
|
6
|
+
|
|
7
|
+
function TestComponent() {
|
|
8
|
+
const { breakpoint } = useBreakpoint();
|
|
9
|
+
return <div data-testid="breakpoint">{breakpoint.size}</div>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
describe("useBreakpoint SSR", () => {
|
|
13
|
+
it("renders the default breakpoint on the server", () => {
|
|
14
|
+
const view = renderToString(<TestComponent />);
|
|
15
|
+
|
|
16
|
+
expect(view).toContain(DEFAULT_BREAKPOINTS[0].size);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { render, renderHook, screen } from "@testing-library/react";
|
|
1
|
+
import { act, render, renderHook, screen } from "@testing-library/react";
|
|
2
|
+
import { hydrateRoot } from "react-dom/client";
|
|
3
|
+
import { renderToString } from "react-dom/server";
|
|
2
4
|
import type { BreakpointsType } from ".";
|
|
3
5
|
import { DEFAULT_BREAKPOINTS, useBreakpoint } from ".";
|
|
4
6
|
|
|
@@ -38,6 +40,7 @@ describe("useBreakpoint", () => {
|
|
|
38
40
|
|
|
39
41
|
describe("given no breakpoints are provided through a Context", () => {
|
|
40
42
|
it("returns default breakpoint", () => {
|
|
43
|
+
setWindowWidth(300);
|
|
41
44
|
render(<TestComponent />);
|
|
42
45
|
|
|
43
46
|
expect(screen.getByText(DEFAULT_BREAKPOINTS[0].size)).toBeInTheDocument();
|
|
@@ -133,7 +136,7 @@ describe("useBreakpoint", () => {
|
|
|
133
136
|
] as const;
|
|
134
137
|
|
|
135
138
|
describe.each(table)("given window width %s", (value, expected) => {
|
|
136
|
-
it.each(expected)(
|
|
139
|
+
it.each([...expected])(
|
|
137
140
|
"it returns %s for breakpoint %s",
|
|
138
141
|
(expectedResult, breakpoint) => {
|
|
139
142
|
setWindowWidth(value as number);
|
|
@@ -216,7 +219,7 @@ describe("useBreakpoint", () => {
|
|
|
216
219
|
] as const;
|
|
217
220
|
|
|
218
221
|
describe.each(table)("given window width %s", (value, expected) => {
|
|
219
|
-
it.each(expected)(
|
|
222
|
+
it.each([...expected])(
|
|
220
223
|
"it returns %s for breakpoint %s",
|
|
221
224
|
(expectedResult, breakpoint) => {
|
|
222
225
|
setWindowWidth(value as number);
|
|
@@ -334,7 +337,7 @@ describe("useBreakpoint", () => {
|
|
|
334
337
|
] as const;
|
|
335
338
|
|
|
336
339
|
describe.each(table)("given window width %s", (value, expected) => {
|
|
337
|
-
it.each(expected)(
|
|
340
|
+
it.each([...expected])(
|
|
338
341
|
"it returns %s for breakpoint %s",
|
|
339
342
|
(expectedResult, breakpoint) => {
|
|
340
343
|
setWindowWidth(value as number);
|
|
@@ -413,7 +416,7 @@ describe("useBreakpoint", () => {
|
|
|
413
416
|
] as const;
|
|
414
417
|
|
|
415
418
|
describe.each(table)("given window width %s", (value, expected) => {
|
|
416
|
-
it.each(expected)(
|
|
419
|
+
it.each([...expected])(
|
|
417
420
|
"it returns %s for breakpoint %s",
|
|
418
421
|
(expectedResult, breakpoint) => {
|
|
419
422
|
setWindowWidth(value as number);
|
|
@@ -425,4 +428,61 @@ describe("useBreakpoint", () => {
|
|
|
425
428
|
});
|
|
426
429
|
});
|
|
427
430
|
});
|
|
431
|
+
|
|
432
|
+
describe("resize after mount", () => {
|
|
433
|
+
it("updates the breakpoint after the debounce delay", () => {
|
|
434
|
+
vi.useFakeTimers();
|
|
435
|
+
setWindowWidth(300);
|
|
436
|
+
|
|
437
|
+
const { result } = renderHook(() => useBreakpoint());
|
|
438
|
+
expect(result.current.breakpoint.size).toBe("xs");
|
|
439
|
+
|
|
440
|
+
act(() => {
|
|
441
|
+
Reflect.set(window, "innerWidth", 1400);
|
|
442
|
+
window.dispatchEvent(new Event("resize"));
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
// Before debounce settles, still xs
|
|
446
|
+
expect(result.current.breakpoint.size).toBe("xs");
|
|
447
|
+
|
|
448
|
+
act(() => {
|
|
449
|
+
vi.advanceTimersByTime(200);
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
expect(result.current.breakpoint.size).toBe("xxl");
|
|
453
|
+
vi.useRealTimers();
|
|
454
|
+
});
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
describe("hydration", () => {
|
|
458
|
+
it("does not produce hydration mismatches", () => {
|
|
459
|
+
// Window is xxl — deliberately different from default xs breakpoint
|
|
460
|
+
setWindowWidth(1400);
|
|
461
|
+
|
|
462
|
+
const view = renderToString(<TestComponent />);
|
|
463
|
+
// Server should render the default (xs) regardless of window width
|
|
464
|
+
expect(view).toContain("xs");
|
|
465
|
+
|
|
466
|
+
const container = document.createElement("div");
|
|
467
|
+
const parsed = new DOMParser().parseFromString(view, "text/html");
|
|
468
|
+
container.append(...parsed.body.childNodes);
|
|
469
|
+
document.body.appendChild(container);
|
|
470
|
+
|
|
471
|
+
const errorSpy = vi.spyOn(console, "error");
|
|
472
|
+
|
|
473
|
+
act(() => {
|
|
474
|
+
hydrateRoot(container, <TestComponent />);
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
const hydrationErrors = errorSpy.mock.calls.filter(
|
|
478
|
+
([firstArg]) =>
|
|
479
|
+
typeof firstArg === "string" &&
|
|
480
|
+
firstArg.toLowerCase().includes("hydrat"),
|
|
481
|
+
);
|
|
482
|
+
expect(hydrationErrors).toHaveLength(0);
|
|
483
|
+
|
|
484
|
+
errorSpy.mockRestore();
|
|
485
|
+
document.body.removeChild(container);
|
|
486
|
+
});
|
|
487
|
+
});
|
|
428
488
|
});
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
useDebouncedValue,
|
|
5
|
+
useIsClient,
|
|
6
|
+
useWindowEvent,
|
|
7
|
+
} from "@simplybusiness/mobius-hooks";
|
|
8
|
+
import { useCallback, useMemo, useState } from "react";
|
|
5
9
|
import type { SizeType } from "../../types";
|
|
6
10
|
|
|
7
11
|
export type Breakpoint = {
|
|
@@ -25,6 +29,8 @@ export const DEFAULT_BREAKPOINTS = [
|
|
|
25
29
|
{ size: "xxl", value: 1320 },
|
|
26
30
|
] as BreakpointsType;
|
|
27
31
|
|
|
32
|
+
const PASSIVE: AddEventListenerOptions = { passive: true };
|
|
33
|
+
|
|
28
34
|
const getBreakpoint = (breakpoints: BreakpointsType, windowWidth: number) => {
|
|
29
35
|
// When breakpoint size and windowWidth are a match
|
|
30
36
|
// The addition of 1px ensures the right breakpoint
|
|
@@ -44,46 +50,30 @@ const getBreakpoint = (breakpoints: BreakpointsType, windowWidth: number) => {
|
|
|
44
50
|
const useBreakpoint = (
|
|
45
51
|
customBreakpoints?: BreakpointsType,
|
|
46
52
|
): UseBreakpointType => {
|
|
47
|
-
const
|
|
48
|
-
const isClientSide = typeof window !== "undefined";
|
|
53
|
+
const isClientSide = useIsClient();
|
|
49
54
|
const breakpoints = customBreakpoints || DEFAULT_BREAKPOINTS;
|
|
50
55
|
const defaultBreakpoint = breakpoints[0];
|
|
56
|
+
|
|
51
57
|
const [windowWidth, setWindowWidth] = useState<number>(
|
|
52
|
-
|
|
58
|
+
typeof globalThis?.window !== "undefined"
|
|
59
|
+
? window.innerWidth
|
|
60
|
+
: defaultBreakpoint.value,
|
|
53
61
|
);
|
|
54
|
-
const [currentBreakpoint, setCurrentBreakpoint] =
|
|
55
|
-
useState<Breakpoint>(defaultBreakpoint);
|
|
56
|
-
const handleResize = () => {
|
|
57
|
-
setWindowWidth(window.innerWidth);
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
useEffect(() => {
|
|
61
|
-
if (!windowWidth || breakpoints.length === 0) {
|
|
62
|
-
setCurrentBreakpoint(breakpoints[0]);
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
62
|
|
|
66
|
-
|
|
63
|
+
useWindowEvent("resize", () => setWindowWidth(window.innerWidth), PASSIVE);
|
|
67
64
|
|
|
68
|
-
|
|
69
|
-
if (currentBreakpoint.size === newBreakpoint?.size) return;
|
|
65
|
+
const debouncedWidth = useDebouncedValue(windowWidth, 200);
|
|
70
66
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
81
|
-
}, [window]);
|
|
67
|
+
// Gate on isClientSide so up()/down() also return default-based values
|
|
68
|
+
// during SSR and hydration, matching the server render.
|
|
69
|
+
const currentBreakpoint = useMemo(
|
|
70
|
+
() =>
|
|
71
|
+
!isClientSide || breakpoints.length === 0
|
|
72
|
+
? defaultBreakpoint
|
|
73
|
+
: getBreakpoint(breakpoints, debouncedWidth),
|
|
74
|
+
[isClientSide, breakpoints, defaultBreakpoint, debouncedWidth],
|
|
75
|
+
);
|
|
82
76
|
|
|
83
|
-
/**
|
|
84
|
-
* up(breakpointSize: string) => boolean
|
|
85
|
-
* Returns true if the current screen width >= breakpoint width
|
|
86
|
-
*/
|
|
87
77
|
const up = useCallback(
|
|
88
78
|
(size: SizeType) => {
|
|
89
79
|
const sizeIndex = breakpoints.findIndex(item => item.size === size);
|
|
@@ -99,10 +89,6 @@ const useBreakpoint = (
|
|
|
99
89
|
[currentBreakpoint, breakpoints],
|
|
100
90
|
);
|
|
101
91
|
|
|
102
|
-
/**
|
|
103
|
-
* down(breakpointSize: string) => boolean
|
|
104
|
-
* Returns true if the current screen width <= breakpoint width
|
|
105
|
-
*/
|
|
106
92
|
const down = useCallback(
|
|
107
93
|
(size: SizeType) => {
|
|
108
94
|
const sizeIndex = breakpoints.findIndex(item => item.size === size);
|
|
@@ -119,7 +105,7 @@ const useBreakpoint = (
|
|
|
119
105
|
);
|
|
120
106
|
|
|
121
107
|
return {
|
|
122
|
-
breakpoint:
|
|
108
|
+
breakpoint: currentBreakpoint,
|
|
123
109
|
up,
|
|
124
110
|
down,
|
|
125
111
|
};
|
|
@@ -144,14 +144,14 @@ describe("useButton", () => {
|
|
|
144
144
|
|
|
145
145
|
describe("onClick", () => {
|
|
146
146
|
it("should call onClick when buttonProps onClick is called", () => {
|
|
147
|
-
const onClick =
|
|
147
|
+
const onClick = vi.fn();
|
|
148
148
|
const { result } = renderHook(() => useButton({ onClick }));
|
|
149
149
|
result.current.buttonProps.onClick(mockClickEvent);
|
|
150
150
|
expect(onClick).toHaveBeenCalled();
|
|
151
151
|
});
|
|
152
152
|
|
|
153
153
|
it("should not call onClick when buttonProps onClick is called and isDisabled is true", () => {
|
|
154
|
-
const onClick =
|
|
154
|
+
const onClick = vi.fn();
|
|
155
155
|
const { result } = renderHook(() =>
|
|
156
156
|
useButton({ onClick, isDisabled: true }),
|
|
157
157
|
);
|
|
@@ -162,14 +162,14 @@ describe("useButton", () => {
|
|
|
162
162
|
|
|
163
163
|
describe("onPress", () => {
|
|
164
164
|
it("should call onPress when buttonProps onClick is called", () => {
|
|
165
|
-
const onPress =
|
|
165
|
+
const onPress = vi.fn();
|
|
166
166
|
const { result } = renderHook(() => useButton({ onPress }));
|
|
167
167
|
result.current.buttonProps.onClick(mockClickEvent);
|
|
168
168
|
expect(onPress).toHaveBeenCalled();
|
|
169
169
|
});
|
|
170
170
|
|
|
171
171
|
it("should not call onPress when buttonProps onClick is called and isDisabled is true", () => {
|
|
172
|
-
const onPress =
|
|
172
|
+
const onPress = vi.fn();
|
|
173
173
|
const { result } = renderHook(() =>
|
|
174
174
|
useButton({ onPress, isDisabled: true }),
|
|
175
175
|
);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { MutableRefObject, SyntheticEvent } from "react";
|
|
2
2
|
import { useCallback, useEffect, useState } from "react";
|
|
3
3
|
import { supportsDialog } from "../../utils";
|
|
4
|
-
import { useBodyScrollLock } from "
|
|
4
|
+
import { useBodyScrollLock } from "@simplybusiness/mobius-hooks";
|
|
5
5
|
import { useDialogPolyfill } from "../useDialogPolyfill";
|
|
6
6
|
|
|
7
7
|
export type TransitionProps = {
|
|
@@ -7,7 +7,7 @@ describe("useLabel", () => {
|
|
|
7
7
|
});
|
|
8
8
|
|
|
9
9
|
it("should warn if no label or aria-label or aria-labelledby is provided", () => {
|
|
10
|
-
|
|
10
|
+
vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
11
11
|
renderHook(() => useLabel({}));
|
|
12
12
|
expect(console.warn).toHaveBeenCalled();
|
|
13
13
|
});
|
|
@@ -45,7 +45,7 @@ describe("useTextField", () => {
|
|
|
45
45
|
|
|
46
46
|
describe("controlled value", () => {
|
|
47
47
|
it("should set the value to the value prop", () => {
|
|
48
|
-
const handleChange =
|
|
48
|
+
const handleChange = vi.fn();
|
|
49
49
|
render(
|
|
50
50
|
<WrapperComponent
|
|
51
51
|
label={TEST_LABEL}
|
|
@@ -57,7 +57,7 @@ describe("useTextField", () => {
|
|
|
57
57
|
});
|
|
58
58
|
|
|
59
59
|
it("should call onChange with event", async () => {
|
|
60
|
-
const handleChange =
|
|
60
|
+
const handleChange = vi.fn();
|
|
61
61
|
render(
|
|
62
62
|
<WrapperComponent
|
|
63
63
|
label={TEST_LABEL}
|
|
@@ -83,7 +83,7 @@ describe("useTextField", () => {
|
|
|
83
83
|
});
|
|
84
84
|
|
|
85
85
|
it("should not call onChange when isDisabled is true", async () => {
|
|
86
|
-
const handleChange =
|
|
86
|
+
const handleChange = vi.fn();
|
|
87
87
|
render(
|
|
88
88
|
<WrapperComponent
|
|
89
89
|
label={TEST_LABEL}
|
|
@@ -105,7 +105,7 @@ describe("useTextField", () => {
|
|
|
105
105
|
});
|
|
106
106
|
|
|
107
107
|
it("should not call onChange", async () => {
|
|
108
|
-
const handleChange =
|
|
108
|
+
const handleChange = vi.fn();
|
|
109
109
|
render(
|
|
110
110
|
<WrapperComponent
|
|
111
111
|
label={TEST_LABEL}
|
|
@@ -8,6 +8,7 @@ const exec = promisify(_exec);
|
|
|
8
8
|
|
|
9
9
|
// Publishing packages to the public registry requires approval from appsec
|
|
10
10
|
const PUBLIC_PACKAGE_WHITELIST = [
|
|
11
|
+
"@simplybusiness/mobius-hooks",
|
|
11
12
|
"@simplybusiness/icons",
|
|
12
13
|
"@simplybusiness/mobius-datepicker",
|
|
13
14
|
"@simplybusiness/mobius",
|
package/src/utils/delay.test.ts
CHANGED
|
@@ -2,16 +2,16 @@ import { delay } from "./delay";
|
|
|
2
2
|
|
|
3
3
|
describe("delay", () => {
|
|
4
4
|
beforeEach(() => {
|
|
5
|
-
|
|
5
|
+
vi.useFakeTimers();
|
|
6
6
|
});
|
|
7
7
|
|
|
8
8
|
afterEach(() => {
|
|
9
|
-
|
|
9
|
+
vi.useRealTimers();
|
|
10
10
|
});
|
|
11
11
|
|
|
12
12
|
it("should resolve after specified delay", async () => {
|
|
13
13
|
const promise = delay(1000);
|
|
14
|
-
|
|
14
|
+
vi.advanceTimersByTime(1000);
|
|
15
15
|
await promise;
|
|
16
16
|
});
|
|
17
17
|
|
|
@@ -27,7 +27,7 @@ describe("delay", () => {
|
|
|
27
27
|
|
|
28
28
|
it("should not resolve before the specified time", async () => {
|
|
29
29
|
const promise = delay(1000);
|
|
30
|
-
|
|
30
|
+
vi.advanceTimersByTime(500);
|
|
31
31
|
|
|
32
32
|
const resolved = await Promise.race([promise, Promise.resolve("early")]);
|
|
33
33
|
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
// Workaround for lack of `<dialog>` support in jsdom
|
|
2
2
|
// Workaround from: https://github.com/jsdom/jsdom/issues/3294#issuecomment-1268330372
|
|
3
3
|
// Fix: https://github.com/jsdom/jsdom/pull/3403
|
|
4
|
-
// This can be removed once `<dialog>` support is introduced in
|
|
5
|
-
export const
|
|
6
|
-
HTMLDialogElement.prototype.show =
|
|
4
|
+
// This can be removed once `<dialog>` support is introduced in jsdom
|
|
5
|
+
export const htmlDialogPolyfill = () => {
|
|
6
|
+
HTMLDialogElement.prototype.show = vi.fn(function mock(
|
|
7
7
|
this: HTMLDialogElement,
|
|
8
8
|
) {
|
|
9
9
|
this.open = true;
|
|
10
10
|
});
|
|
11
11
|
|
|
12
|
-
HTMLDialogElement.prototype.showModal =
|
|
12
|
+
HTMLDialogElement.prototype.showModal = vi.fn(function mock(
|
|
13
13
|
this: HTMLDialogElement,
|
|
14
14
|
) {
|
|
15
15
|
this.open = true;
|
|
16
16
|
});
|
|
17
17
|
|
|
18
|
-
HTMLDialogElement.prototype.close =
|
|
18
|
+
HTMLDialogElement.prototype.close = vi.fn(function mock(
|
|
19
19
|
this: HTMLDialogElement,
|
|
20
20
|
) {
|
|
21
21
|
this.open = false;
|
package/src/utils/index.ts
CHANGED
|
@@ -3,7 +3,6 @@ export * from "./delay";
|
|
|
3
3
|
export * from "./excludeControls";
|
|
4
4
|
export * from "./filterUndefinedProps";
|
|
5
5
|
export * from "./getSpacingValue";
|
|
6
|
-
export * from "./jestHTMLDialogPolyfill";
|
|
7
6
|
export * from "./mergeRefs";
|
|
8
7
|
export * from "./polyfill-tests";
|
|
9
8
|
export * from "./sizeClasses";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const mockMatchMedia = (matches: boolean) => {
|
|
2
|
+
Object.defineProperty(window, "matchMedia", {
|
|
3
|
+
writable: true,
|
|
4
|
+
configurable: true,
|
|
5
|
+
value: vi.fn().mockImplementation(query => ({
|
|
6
|
+
matches,
|
|
7
|
+
media: query,
|
|
8
|
+
onchange: null,
|
|
9
|
+
addListener: vi.fn(),
|
|
10
|
+
removeListener: vi.fn(),
|
|
11
|
+
addEventListener: vi.fn(),
|
|
12
|
+
removeEventListener: vi.fn(),
|
|
13
|
+
dispatchEvent: vi.fn(),
|
|
14
|
+
})),
|
|
15
|
+
});
|
|
16
|
+
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./useBodyScrollLock";
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./useDebouncedValue";
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function useDebouncedValue<T>(value: T, delay?: number): T;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./useOnClickOutside";
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./useOnUnmount";
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function useOnUnmount(callback: () => void): void;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./usePrefersReducedMotion";
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function usePrefersReducedMotion(): boolean;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./useRenderCount";
|