@simplybusiness/mobius 4.2.8 → 4.3.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 (68) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/cjs/components/Drawer/Drawer.js +1 -1
  3. package/dist/cjs/components/Drawer/Drawer.js.map +1 -1
  4. package/dist/cjs/components/Modal/Content.js +3 -13
  5. package/dist/cjs/components/Modal/Content.js.map +1 -1
  6. package/dist/cjs/components/Modal/Header.js +4 -14
  7. package/dist/cjs/components/Modal/Header.js.map +1 -1
  8. package/dist/cjs/components/Modal/Modal.js +35 -22
  9. package/dist/cjs/components/Modal/Modal.js.map +1 -1
  10. package/dist/cjs/components/Modal/index.js.map +1 -1
  11. package/dist/cjs/components/Modal/types.js +6 -0
  12. package/dist/cjs/components/Modal/types.js.map +1 -0
  13. package/dist/cjs/hooks/useTextField/useTextField.js +1 -1
  14. package/dist/cjs/hooks/useTextField/useTextField.js.map +1 -1
  15. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  16. package/dist/cjs/utils/index.js +0 -1
  17. package/dist/cjs/utils/index.js.map +1 -1
  18. package/dist/cjs/utils/jestMockMatchMedia.js +28 -0
  19. package/dist/cjs/utils/jestMockMatchMedia.js.map +1 -0
  20. package/dist/esm/components/Drawer/Drawer.js +1 -1
  21. package/dist/esm/components/Drawer/Drawer.js.map +1 -1
  22. package/dist/esm/components/Modal/Content.js +3 -8
  23. package/dist/esm/components/Modal/Content.js.map +1 -1
  24. package/dist/esm/components/Modal/Header.js +4 -9
  25. package/dist/esm/components/Modal/Header.js.map +1 -1
  26. package/dist/esm/components/Modal/Modal.js +36 -24
  27. package/dist/esm/components/Modal/Modal.js.map +1 -1
  28. package/dist/esm/components/Modal/index.js.map +1 -1
  29. package/dist/esm/components/Modal/types.js +3 -0
  30. package/dist/esm/components/Modal/types.js.map +1 -0
  31. package/dist/esm/hooks/useTextField/useTextField.js +1 -1
  32. package/dist/esm/hooks/useTextField/useTextField.js.map +1 -1
  33. package/dist/esm/utils/index.js +0 -1
  34. package/dist/esm/utils/index.js.map +1 -1
  35. package/dist/esm/utils/jestMockMatchMedia.js +18 -0
  36. package/dist/esm/utils/jestMockMatchMedia.js.map +1 -0
  37. package/package.json +1 -1
  38. package/src/components/Accordion/Accordion.test.tsx +3 -19
  39. package/src/components/Checkbox/Checkbox.stories.tsx +2 -1
  40. package/src/components/Checkbox/CheckboxGroup.stories.tsx +3 -2
  41. package/src/components/Divider/Divider.stories.tsx +1 -1
  42. package/src/components/Drawer/Drawer.test.tsx +13 -47
  43. package/src/components/Drawer/Drawer.tsx +1 -1
  44. package/src/components/DropdownMenu/DropdownMenu.stories.tsx +4 -3
  45. package/src/components/Fieldset/Fieldset.stories.tsx +3 -2
  46. package/src/components/Modal/Content.tsx +18 -22
  47. package/src/components/Modal/Header.tsx +16 -22
  48. package/src/components/Modal/Modal.stories.tsx +6 -28
  49. package/src/components/Modal/Modal.test.tsx +82 -101
  50. package/src/components/Modal/Modal.tsx +62 -75
  51. package/src/components/Modal/index.tsx +2 -1
  52. package/src/components/Modal/types.ts +32 -0
  53. package/src/components/NumberField/NumberField.stories.tsx +2 -1
  54. package/src/components/PasswordField/PasswordField.stories.tsx +2 -1
  55. package/src/components/Progress/Progress.stories.tsx +2 -1
  56. package/src/components/Radio/Radio.stories.tsx +4 -3
  57. package/src/components/Radio/RadioButton.stories.tsx +4 -3
  58. package/src/components/Select/Select.stories.tsx +2 -2
  59. package/src/components/Slider/Slider.stories.tsx +2 -1
  60. package/src/components/TextArea/TextArea.stories.tsx +2 -1
  61. package/src/components/TextField/TextField.stories.tsx +6 -5
  62. package/src/hooks/useTextField/useTextField.tsx +3 -3
  63. package/src/utils/index.ts +0 -1
  64. package/src/utils/jestMockMatchMedia.ts +16 -0
  65. package/dist/cjs/utils/StoryContainer.js +0 -39
  66. package/dist/cjs/utils/StoryContainer.js.map +0 -1
  67. package/dist/esm/utils/StoryContainer.js +0 -29
  68. package/dist/esm/utils/StoryContainer.js.map +0 -1
@@ -1,14 +1,12 @@
1
- /* eslint-disable no-console */
2
-
3
1
  "use client";
4
2
 
5
- // eslint-disable-next-line import/no-extraneous-dependencies
6
3
  import classNames from "classnames/dedupe";
7
4
  import {
8
5
  Children,
9
- ReactEventHandler,
10
- ReactNode,
6
+ Ref,
7
+ SyntheticEvent,
11
8
  cloneElement,
9
+ forwardRef,
12
10
  isValidElement,
13
11
  useCallback,
14
12
  useEffect,
@@ -16,52 +14,29 @@ import {
16
14
  } from "react";
17
15
  import { useBodyScrollLock } from "../../hooks/useBodyScrollLock";
18
16
  import { supportsDialog } from "../../utils/polyfill-tests";
19
-
20
- export interface ModalProps {
21
- isOpen: boolean;
22
- onClose?: () => void;
23
- children?: ReactNode;
24
- className?: string;
25
- closeLabel?: string;
26
- animation?: "slideUp" | "fade";
27
- isFullScreen?: boolean;
28
- /**
29
- * **Deprecated:** No longer needed
30
- */
31
- size?: string;
32
- /**
33
- * **Deprecated:** No longer needed
34
- */
35
- appElement?: string;
36
- /**
37
- * **Deprecated:** No longer needed
38
- */
39
- preventCloseOnEsc?: boolean;
40
- /**
41
- * **Deprecated:** No longer needed
42
- */
43
- shouldFocusAfterRender?: boolean;
44
- /**
45
- * **Deprecated:** No longer needed
46
- */
47
- parentSelector?: () => HTMLElement;
48
- }
49
-
50
- const Modal = ({
51
- isOpen,
52
- onClose,
53
- children,
54
- className,
55
- closeLabel,
56
- isFullScreen,
57
- animation,
58
- // Deprecated props below
59
- size,
60
- appElement,
61
- preventCloseOnEsc,
62
- shouldFocusAfterRender,
63
- parentSelector,
64
- }: ModalProps) => {
17
+ import { ModalProps } from "./types";
18
+ import { mergeRefs } from "../../utils";
19
+
20
+ export type ModalElementType = HTMLDialogElement;
21
+ export type ModalRef = Ref<ModalElementType>;
22
+
23
+ const Modal = forwardRef((props: ModalProps, ref: ModalRef) => {
24
+ const {
25
+ isOpen,
26
+ onClose,
27
+ onOpen,
28
+ children,
29
+ className,
30
+ closeLabel,
31
+ isFullScreen,
32
+ animation,
33
+ // Deprecated props below
34
+ size,
35
+ appElement,
36
+ preventCloseOnEsc,
37
+ shouldFocusAfterRender,
38
+ parentSelector,
39
+ } = props;
65
40
  const hasWarnedAboutMissingLabels = useRef<boolean>(false);
66
41
  // Handle deprecated props
67
42
  if (!hasWarnedAboutMissingLabels.current) {
@@ -79,39 +54,46 @@ const Modal = ({
79
54
  }
80
55
  }
81
56
 
57
+ const hasOpened = useRef<boolean>(false);
82
58
  const modalRef = useRef<HTMLDialogElement>(null);
83
59
  const dialog = modalRef.current as HTMLDialogElement;
84
60
  const noPreference = window?.matchMedia(
85
61
  "(prefers-reduced-motion: no-preference)",
86
62
  );
87
63
 
64
+ // Fire onOpen once
65
+ if (onOpen && !hasOpened.current) {
66
+ onOpen();
67
+ hasOpened.current = true;
68
+ }
69
+
88
70
  useBodyScrollLock({ enabled: isOpen });
89
71
 
90
72
  // Add close handler, to enable closing animations
91
- const handleClose: ReactEventHandler<HTMLDialogElement> = useCallback(
92
- event => {
73
+ const handleClose = useCallback(
74
+ (event?: SyntheticEvent<HTMLElement, Event>) => {
93
75
  if (event) {
94
76
  event.preventDefault();
95
77
  event.stopPropagation();
78
+ }
96
79
 
97
- const doClose = () => {
98
- dialog.close();
99
- onClose?.();
100
- };
101
-
102
- if (animation) {
103
- dialog.classList.add("close");
104
- dialog.addEventListener(
105
- "animationend",
106
- () => {
107
- dialog.classList.remove("close");
108
- doClose();
109
- },
110
- { once: true },
111
- );
112
- } else {
113
- doClose();
114
- }
80
+ const doClose = () => {
81
+ dialog?.close();
82
+ onClose?.();
83
+ };
84
+
85
+ if (animation) {
86
+ dialog?.classList.add("close");
87
+ dialog?.addEventListener(
88
+ "animationend",
89
+ () => {
90
+ dialog.classList.remove("close");
91
+ doClose();
92
+ },
93
+ { once: true },
94
+ );
95
+ } else {
96
+ doClose();
115
97
  }
116
98
  },
117
99
  [animation, dialog, onClose],
@@ -141,16 +123,21 @@ const Modal = ({
141
123
 
142
124
  if (isOpen) {
143
125
  modalRef.current?.showModal();
126
+ onOpen?.();
144
127
  } else if (modalRef.current?.open) {
145
- modalRef.current?.close();
128
+ handleClose();
146
129
  }
147
130
  }
148
131
 
149
132
  toggleModal();
150
- }, [isOpen]);
133
+ }, [isOpen, onOpen, handleClose]);
151
134
 
152
135
  return (
153
- <dialog ref={modalRef} onCancel={handleClose} className={modalClasses}>
136
+ <dialog
137
+ ref={mergeRefs([modalRef, ref])}
138
+ onCancel={handleClose}
139
+ className={modalClasses}
140
+ >
154
141
  {Children.map(children, child => {
155
142
  if (isValidElement(child)) {
156
143
  return cloneElement(child, {
@@ -163,7 +150,7 @@ const Modal = ({
163
150
  })}
164
151
  </dialog>
165
152
  );
166
- };
153
+ });
167
154
 
168
155
  Modal.displayName = "Modal";
169
156
  export { Modal };
@@ -8,7 +8,8 @@ import {
8
8
  HeaderElementType as ModalHeaderElementType,
9
9
  HeaderProps as ModalHeaderProps,
10
10
  } from "./Header";
11
- import { Modal as ModalComponent, ModalProps } from "./Modal";
11
+ import { Modal as ModalComponent } from "./Modal";
12
+ import { ModalProps } from "./types";
12
13
 
13
14
  const Modal = Object.assign(ModalComponent, {
14
15
  Header,
@@ -0,0 +1,32 @@
1
+ import { ReactNode } from "react";
2
+
3
+ export interface ModalProps {
4
+ isOpen: boolean;
5
+ onOpen?: () => void;
6
+ onClose?: () => void;
7
+ children?: ReactNode;
8
+ className?: string;
9
+ closeLabel?: string;
10
+ animation?: "slideUp" | "fade";
11
+ isFullScreen?: boolean;
12
+ /**
13
+ * **Deprecated:** No longer needed
14
+ */
15
+ size?: string;
16
+ /**
17
+ * **Deprecated:** No longer needed
18
+ */
19
+ appElement?: string;
20
+ /**
21
+ * **Deprecated:** No longer needed
22
+ */
23
+ preventCloseOnEsc?: boolean;
24
+ /**
25
+ * **Deprecated:** No longer needed
26
+ */
27
+ shouldFocusAfterRender?: boolean;
28
+ /**
29
+ * **Deprecated:** No longer needed
30
+ */
31
+ parentSelector?: () => HTMLElement;
32
+ }
@@ -1,6 +1,7 @@
1
1
  import type { Meta, StoryObj } from "@storybook/react";
2
+ import { excludeControls } from "../../utils";
3
+ import { StoryContainer } from "../../utils/StoryContainer";
2
4
  import { NumberField, NumberFieldProps } from "./NumberField";
3
- import { StoryContainer, excludeControls } from "../../utils";
4
5
 
5
6
  type StoryType = StoryObj<typeof NumberField>;
6
7
 
@@ -1,6 +1,7 @@
1
1
  import type { Meta, StoryObj } from "@storybook/react";
2
+ import { excludeControls } from "../../utils";
3
+ import { StoryContainer } from "../../utils/StoryContainer";
2
4
  import { PasswordField, PasswordFieldProps } from "./PasswordField";
3
- import { StoryContainer, excludeControls } from "../../utils";
4
5
 
5
6
  type StoryType = StoryObj<typeof PasswordField>;
6
7
 
@@ -1,6 +1,7 @@
1
1
  import type { Meta, StoryObj } from "@storybook/react";
2
+ import { excludeControls } from "../../utils";
3
+ import { StoryContainer } from "../../utils/StoryContainer";
2
4
  import { Progress, ProgressProps } from "./Progress";
3
- import { StoryContainer, excludeControls } from "../../utils";
4
5
 
5
6
  type StoryType = StoryObj<typeof Progress>;
6
7
 
@@ -1,9 +1,10 @@
1
1
  import type { Meta, StoryObj } from "@storybook/react";
2
+ import { excludeControls } from "../../utils";
3
+ import { StoryContainer } from "../../utils/StoryContainer";
4
+ import { Divider } from "../Divider";
5
+ import { Flex } from "../Flex";
2
6
  import { Radio } from "./Radio";
3
7
  import { RadioGroup, RadioGroupProps } from "./RadioGroup";
4
- import { Flex } from "../Flex";
5
- import { Divider } from "../Divider";
6
- import { StoryContainer, excludeControls } from "../../utils";
7
8
 
8
9
  type StoryType = StoryObj<typeof RadioGroup>;
9
10
 
@@ -1,9 +1,10 @@
1
- import type { Meta, StoryObj } from "@storybook/react";
2
1
  import { menu, star, user } from "@simplybusiness/icons";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+ import { excludeControls } from "../../utils";
4
+ import { StoryContainer } from "../../utils/StoryContainer";
5
+ import { Icon } from "../Icon";
3
6
  import { RadioButton } from "./RadioButton";
4
7
  import { RadioGroup, RadioGroupProps } from "./RadioGroup";
5
- import { Icon } from "../Icon";
6
- import { StoryContainer, excludeControls } from "../../utils";
7
8
 
8
9
  type StoryType = StoryObj<typeof RadioGroup>;
9
10
 
@@ -1,7 +1,7 @@
1
1
  import type { Meta, StoryObj } from "@storybook/react";
2
- import { Select, SelectProps } from "./Select";
2
+ import { StoryContainer } from "../../utils/StoryContainer";
3
3
  import { Option } from "../Option";
4
- import { StoryContainer } from "../../utils";
4
+ import { Select, SelectProps } from "./Select";
5
5
 
6
6
  type StoryType = StoryObj<typeof Select>;
7
7
 
@@ -1,5 +1,6 @@
1
1
  import type { Meta, StoryObj } from "@storybook/react";
2
- import { StoryContainer, excludeControls } from "../../utils";
2
+ import { excludeControls } from "../../utils";
3
+ import { StoryContainer } from "../../utils/StoryContainer";
3
4
  import { Slider, SliderProps } from "./Slider";
4
5
 
5
6
  type StoryType = StoryObj<typeof Slider>;
@@ -1,6 +1,7 @@
1
1
  import type { Meta, StoryObj } from "@storybook/react";
2
+ import { excludeControls } from "../../utils";
3
+ import { StoryContainer } from "../../utils/StoryContainer";
2
4
  import { TextArea, TextAreaProps } from "./TextArea";
3
- import { StoryContainer, excludeControls } from "../../utils";
4
5
 
5
6
  type StoryType = StoryObj<typeof TextArea>;
6
7
 
@@ -1,12 +1,13 @@
1
+ import { search } from "@simplybusiness/icons";
1
2
  import type { Meta, StoryObj } from "@storybook/react";
2
3
  import { useRef } from "react";
3
- import { search } from "@simplybusiness/icons";
4
- import { TextField, TextFieldProps } from "./TextField";
5
- import { StoryContainer, excludeControls } from "../../utils";
6
- import { Segment, SegmentGroup } from "../Segment";
7
- import { Icon } from "../Icon";
4
+ import { excludeControls } from "../../utils";
5
+ import { StoryContainer } from "../../utils/StoryContainer";
8
6
  import { Button } from "../Button";
7
+ import { Icon } from "../Icon";
8
+ import { Segment, SegmentGroup } from "../Segment";
9
9
  import { Text } from "../Text/Text";
10
+ import { TextField, TextFieldProps } from "./TextField";
10
11
 
11
12
  type StoryType = StoryObj<typeof TextField>;
12
13
 
@@ -1,7 +1,7 @@
1
1
  import { useId } from "react";
2
+ import { spaceDelimitedList } from "../../utils/spaceDelimitedList";
2
3
  import { useLabel } from "../useLabel/useLabel";
3
4
  import { UseTextFieldProps, UseTextFieldReturn } from "./types";
4
- import { spaceDelimitedList } from "../../utils/spaceDelimitedList";
5
5
 
6
6
  export function useTextField(props: UseTextFieldProps): UseTextFieldReturn {
7
7
  const {
@@ -39,8 +39,8 @@ export function useTextField(props: UseTextFieldProps): UseTextFieldReturn {
39
39
  props.validationState === "invalid"
40
40
  ? true
41
41
  : props.validationState === "valid"
42
- ? false
43
- : undefined,
42
+ ? false
43
+ : undefined,
44
44
  "aria-describedby": ariaDescribedBy,
45
45
  "aria-errormessage": props["aria-errormessage"],
46
46
 
@@ -5,4 +5,3 @@ export * from "./mergeRefs";
5
5
  export * from "./polyfill-tests";
6
6
  export * from "./sizeClasses";
7
7
  export * from "./spaceDelimitedList";
8
- export * from "./StoryContainer";
@@ -0,0 +1,16 @@
1
+ export const jestMockMatchMedia = (matches: boolean) => {
2
+ Object.defineProperty(window, "matchMedia", {
3
+ writable: true,
4
+ configurable: true,
5
+ value: jest.fn().mockImplementation(query => ({
6
+ matches,
7
+ media: query,
8
+ onchange: null,
9
+ addListener: jest.fn(),
10
+ removeListener: jest.fn(),
11
+ addEventListener: jest.fn(),
12
+ removeEventListener: jest.fn(),
13
+ dispatchEvent: jest.fn(),
14
+ })),
15
+ });
16
+ };
@@ -1,39 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", {
3
- value: true
4
- });
5
- Object.defineProperty(exports, "StoryContainer", {
6
- enumerable: true,
7
- get: function() {
8
- return StoryContainer;
9
- }
10
- });
11
- const _jsxruntime = require("react/jsx-runtime");
12
- const css = `
13
- :root {
14
- --story-container-width: auto;
15
- }
16
-
17
- @media (min-width: 641px) {
18
- :root {
19
- --story-container-width: var(--size-inner-container);
20
- }
21
- }
22
-
23
- .story-container {
24
- width: var(--story-container-width);
25
- }
26
- `;
27
- const StoryContainer = (props)=>/*#__PURE__*/ (0, _jsxruntime.jsxs)(_jsxruntime.Fragment, {
28
- children: [
29
- /*#__PURE__*/ (0, _jsxruntime.jsx)("style", {
30
- children: css
31
- }),
32
- /*#__PURE__*/ (0, _jsxruntime.jsx)("div", {
33
- className: "story-container",
34
- ...props
35
- })
36
- ]
37
- });
38
-
39
- //# sourceMappingURL=StoryContainer.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../src/utils/StoryContainer.tsx"],"sourcesContent":["const css = `\n :root {\n --story-container-width: auto;\n }\n\n @media (min-width: 641px) {\n :root {\n --story-container-width: var(--size-inner-container);\n }\n }\n\n .story-container {\n width: var(--story-container-width);\n }\n`;\n\nexport const StoryContainer = (props: Record<string, unknown>) => (\n <>\n <style>{css}</style>\n <div className=\"story-container\" {...props} />\n </>\n);\n"],"names":["StoryContainer","css","props","style","div","className"],"mappings":";;;;+BAgBaA;;;eAAAA;;;;AAhBb,MAAMC,MAAM,CAAC;;;;;;;;;;;;;;AAcb,CAAC;AAEM,MAAMD,iBAAiB,CAACE,sBAC7B;;0BACE,qBAACC;0BAAOF;;0BACR,qBAACG;gBAAIC,WAAU;gBAAmB,GAAGH,KAAK"}
@@ -1,29 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- const css = `
3
- :root {
4
- --story-container-width: auto;
5
- }
6
-
7
- @media (min-width: 641px) {
8
- :root {
9
- --story-container-width: var(--size-inner-container);
10
- }
11
- }
12
-
13
- .story-container {
14
- width: var(--story-container-width);
15
- }
16
- `;
17
- export const StoryContainer = (props)=>/*#__PURE__*/ _jsxs(_Fragment, {
18
- children: [
19
- /*#__PURE__*/ _jsx("style", {
20
- children: css
21
- }),
22
- /*#__PURE__*/ _jsx("div", {
23
- className: "story-container",
24
- ...props
25
- })
26
- ]
27
- });
28
-
29
- //# sourceMappingURL=StoryContainer.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../src/utils/StoryContainer.tsx"],"sourcesContent":["const css = `\n :root {\n --story-container-width: auto;\n }\n\n @media (min-width: 641px) {\n :root {\n --story-container-width: var(--size-inner-container);\n }\n }\n\n .story-container {\n width: var(--story-container-width);\n }\n`;\n\nexport const StoryContainer = (props: Record<string, unknown>) => (\n <>\n <style>{css}</style>\n <div className=\"story-container\" {...props} />\n </>\n);\n"],"names":["css","StoryContainer","props","style","div","className"],"mappings":";AAAA,MAAMA,MAAM,CAAC;;;;;;;;;;;;;;AAcb,CAAC;AAED,OAAO,MAAMC,iBAAiB,CAACC,sBAC7B;;0BACE,KAACC;0BAAOH;;0BACR,KAACI;gBAAIC,WAAU;gBAAmB,GAAGH,KAAK;;;OAE5C"}