@simplybusiness/mobius 5.3.1 → 5.5.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 (83) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/cjs/components/Checkbox/Checkbox.js +16 -22
  3. package/dist/cjs/components/Checkbox/Checkbox.js.map +1 -1
  4. package/dist/cjs/components/Checkbox/CheckboxGroup.js +29 -11
  5. package/dist/cjs/components/Checkbox/CheckboxGroup.js.map +1 -1
  6. package/dist/cjs/components/Combobox/Combobox.js +129 -0
  7. package/dist/cjs/components/Combobox/Combobox.js.map +1 -0
  8. package/dist/cjs/components/Combobox/fixtures.js +244 -0
  9. package/dist/cjs/components/Combobox/fixtures.js.map +1 -0
  10. package/dist/cjs/components/Combobox/index.js +21 -0
  11. package/dist/cjs/components/Combobox/index.js.map +1 -0
  12. package/dist/cjs/components/Combobox/types.js +6 -0
  13. package/dist/cjs/components/Combobox/types.js.map +1 -0
  14. package/dist/cjs/components/index.js +1 -0
  15. package/dist/cjs/components/index.js.map +1 -1
  16. package/dist/cjs/hooks/index.js +1 -0
  17. package/dist/cjs/hooks/index.js.map +1 -1
  18. package/dist/cjs/hooks/useRenderCount/index.js +20 -0
  19. package/dist/cjs/hooks/useRenderCount/index.js.map +1 -0
  20. package/dist/cjs/hooks/useRenderCount/useRenderCount.js +20 -0
  21. package/dist/cjs/hooks/useRenderCount/useRenderCount.js.map +1 -0
  22. package/dist/cjs/hooks/useTextField/useTextField.js +1 -0
  23. package/dist/cjs/hooks/useTextField/useTextField.js.map +1 -1
  24. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  25. package/dist/esm/components/Checkbox/Checkbox.js +17 -23
  26. package/dist/esm/components/Checkbox/Checkbox.js.map +1 -1
  27. package/dist/esm/components/Checkbox/CheckboxGroup.js +29 -11
  28. package/dist/esm/components/Checkbox/CheckboxGroup.js.map +1 -1
  29. package/dist/esm/components/Checkbox/types.js.map +1 -1
  30. package/dist/esm/components/Combobox/Combobox.js +114 -0
  31. package/dist/esm/components/Combobox/Combobox.js.map +1 -0
  32. package/dist/esm/components/Combobox/fixtures.js +226 -0
  33. package/dist/esm/components/Combobox/fixtures.js.map +1 -0
  34. package/dist/esm/components/Combobox/index.js +4 -0
  35. package/dist/esm/components/Combobox/index.js.map +1 -0
  36. package/dist/esm/components/Combobox/types.js +3 -0
  37. package/dist/esm/components/Combobox/types.js.map +1 -0
  38. package/dist/esm/components/index.js +1 -0
  39. package/dist/esm/components/index.js.map +1 -1
  40. package/dist/esm/hooks/index.js +1 -0
  41. package/dist/esm/hooks/index.js.map +1 -1
  42. package/dist/esm/hooks/useRenderCount/index.js +3 -0
  43. package/dist/esm/hooks/useRenderCount/index.js.map +1 -0
  44. package/dist/esm/hooks/useRenderCount/useRenderCount.js +10 -0
  45. package/dist/esm/hooks/useRenderCount/useRenderCount.js.map +1 -0
  46. package/dist/esm/hooks/useTextField/types.js.map +1 -1
  47. package/dist/esm/hooks/useTextField/useTextField.js +1 -0
  48. package/dist/esm/hooks/useTextField/useTextField.js.map +1 -1
  49. package/dist/types/components/Checkbox/CheckboxGroup.stories.d.ts +1 -0
  50. package/dist/types/components/Checkbox/types.d.ts +10 -6
  51. package/dist/types/components/Combobox/Combobox.d.ts +3 -0
  52. package/dist/types/components/Combobox/Combobox.stories.d.ts +7 -0
  53. package/dist/types/components/Combobox/Combobox.test.d.ts +1 -0
  54. package/dist/types/components/Combobox/fixtures.d.ts +5 -0
  55. package/dist/types/components/Combobox/index.d.ts +2 -0
  56. package/dist/types/components/Combobox/types.d.ts +16 -0
  57. package/dist/types/components/index.d.ts +1 -0
  58. package/dist/types/hooks/index.d.ts +1 -0
  59. package/dist/types/hooks/useRenderCount/index.d.ts +1 -0
  60. package/dist/types/hooks/useRenderCount/useRenderCount.d.ts +1 -0
  61. package/dist/types/hooks/useRenderCount/useRenderCount.test.d.ts +1 -0
  62. package/dist/types/hooks/useTextField/types.d.ts +3 -2
  63. package/package.json +17 -17
  64. package/src/components/Checkbox/Checkbox.tsx +18 -28
  65. package/src/components/Checkbox/CheckboxGroup.stories.tsx +15 -0
  66. package/src/components/Checkbox/CheckboxGroup.test.tsx +107 -1
  67. package/src/components/Checkbox/CheckboxGroup.tsx +45 -15
  68. package/src/components/Checkbox/types.ts +13 -6
  69. package/src/components/Combobox/Combobox.css +30 -0
  70. package/src/components/Combobox/Combobox.stories.tsx +26 -0
  71. package/src/components/Combobox/Combobox.test.tsx +578 -0
  72. package/src/components/Combobox/Combobox.tsx +154 -0
  73. package/src/components/Combobox/fixtures.tsx +93 -0
  74. package/src/components/Combobox/index.tsx +2 -0
  75. package/src/components/Combobox/types.tsx +20 -0
  76. package/src/components/index.tsx +1 -0
  77. package/src/hooks/index.tsx +1 -0
  78. package/src/hooks/useRenderCount/index.ts +1 -0
  79. package/src/hooks/useRenderCount/useRenderCount.test.ts +26 -0
  80. package/src/hooks/useRenderCount/useRenderCount.ts +9 -0
  81. package/src/hooks/useTextField/types.tsx +7 -1
  82. package/src/hooks/useTextField/useTextField.test.tsx +1 -0
  83. package/src/hooks/useTextField/useTextField.tsx +1 -0
@@ -1,28 +1,29 @@
1
1
  "use client";
2
2
 
3
+ import classNames from "classnames/dedupe";
3
4
  import {
5
+ type ChangeEvent,
6
+ type ReactElement,
4
7
  Children,
5
- forwardRef,
6
- ReactElement,
7
8
  cloneElement,
9
+ forwardRef,
8
10
  isValidElement,
11
+ useEffect,
9
12
  useId,
10
- ChangeEvent,
11
13
  useState,
12
- useEffect,
13
14
  } from "react";
14
- import classNames from "classnames/dedupe";
15
+ import { useRenderCount, useValidationClasses } from "../../hooks";
15
16
  import { ForwardedRefComponent } from "../../types/components";
17
+ import { spaceDelimitedList } from "../../utils/spaceDelimitedList";
18
+ import { ErrorMessage } from "../ErrorMessage";
19
+ import { Label } from "../Label";
20
+ import { Checkbox } from "./Checkbox";
16
21
  import {
17
22
  CheckboxElementType,
18
23
  CheckboxGroupElementType,
19
24
  CheckboxGroupProps,
20
25
  CheckboxGroupRef,
21
26
  } from "./types";
22
- import { Label } from "../Label";
23
- import { ErrorMessage } from "../ErrorMessage";
24
- import { spaceDelimitedList } from "../../utils/spaceDelimitedList";
25
- import { useValidationClasses } from "../../hooks";
26
27
 
27
28
  export const CheckboxGroup: ForwardedRefComponent<
28
29
  CheckboxGroupProps,
@@ -42,6 +43,7 @@ export const CheckboxGroup: ForwardedRefComponent<
42
43
  defaultValue = [],
43
44
  isReadOnly,
44
45
  itemsPerRow,
46
+ lastItemDisables = false,
45
47
  ...rest
46
48
  } = props;
47
49
  const [selected, setSelected] = useState<string[]>(defaultValue);
@@ -74,7 +76,10 @@ export const CheckboxGroup: ForwardedRefComponent<
74
76
  ]);
75
77
  const labelId = useId();
76
78
 
77
- const handleChange = (event: ChangeEvent<CheckboxElementType>) => {
79
+ const handleChange = (
80
+ event: ChangeEvent<CheckboxElementType>,
81
+ isLastItem = false,
82
+ ) => {
78
83
  const {
79
84
  target: { value, checked },
80
85
  } = event;
@@ -83,16 +88,34 @@ export const CheckboxGroup: ForwardedRefComponent<
83
88
  setSelected(selected.filter(item => item !== value));
84
89
  }
85
90
 
91
+ if (checked && lastItemDisables && isLastItem) {
92
+ setSelected([value]);
93
+ return;
94
+ }
95
+
86
96
  if (checked) {
87
97
  setSelected([...selected, value]);
88
98
  }
89
99
  };
90
100
 
101
+ // HACK: This is a workaround to ensure that the onChange event is not
102
+ // fired on the initial render.
103
+ const renderCount = useRenderCount();
91
104
  useEffect(() => {
92
- if (onChange) {
105
+ if (onChange && renderCount > 1) {
93
106
  onChange(selected);
94
107
  }
95
- }, [selected, onChange]);
108
+ }, [selected, onChange, renderCount]);
109
+
110
+ const childrenArray = Children.toArray(children);
111
+ const lastCheckbox = childrenArray
112
+ .filter(
113
+ child =>
114
+ isValidElement(child) && (child as ReactElement).type === Checkbox,
115
+ )
116
+ .pop() as ReactElement<CheckboxElementType> | undefined;
117
+ const lastCheckboxIsChecked =
118
+ lastCheckbox && selected.includes(lastCheckbox.props.value);
96
119
 
97
120
  return (
98
121
  <div
@@ -113,14 +136,21 @@ export const CheckboxGroup: ForwardedRefComponent<
113
136
  </Label>
114
137
  )}
115
138
  <div className="mobius-checkbox-group__wrapper">
116
- {Children.map(children, child => {
139
+ {childrenArray.map(child => {
117
140
  if (isValidElement(child)) {
141
+ // lastItemDisables support
142
+ const isLastItem = child === lastCheckbox;
143
+ const isChildDisabled =
144
+ isDisabled ||
145
+ (lastItemDisables && lastCheckboxIsChecked && !isLastItem);
146
+
118
147
  return cloneElement(child as ReactElement, {
119
- isDisabled,
148
+ isDisabled: isChildDisabled,
120
149
  isRequired,
121
150
  isReadOnly,
122
151
  isInvalid,
123
- defaultSelected: selected.includes(child.props.value),
152
+ isLastItem,
153
+ selected: selected.includes(child.props.value),
124
154
  onChange: handleChange,
125
155
  "aria-describedby": describedBy,
126
156
  });
@@ -19,7 +19,10 @@ export interface CheckboxProps
19
19
  value?: string;
20
20
  // Whether the input is disabled.
21
21
  isDisabled?: boolean;
22
- onChange?: (event: ChangeEvent<CheckboxElementType>) => void;
22
+ onChange?: (
23
+ event: ChangeEvent<CheckboxElementType>,
24
+ isLastItem?: boolean,
25
+ ) => void;
23
26
  // The default value (uncontrolled).
24
27
  defaultSelected?: boolean;
25
28
  // Whether the input can be selected but not changed by the user.
@@ -33,17 +36,17 @@ export interface CheckboxProps
33
36
  */
34
37
  "aria-describedby"?: string;
35
38
  /**
36
- * **Internal:** Do not use
39
+ * Whether the checkbox is the last item of a group.
37
40
  */
38
- groupDisabled?: boolean;
41
+ isLastItem?: boolean;
39
42
  /**
40
43
  * **Internal:** Do not use
41
44
  */
42
- selected?: string;
45
+ groupDisabled?: boolean;
43
46
  /**
44
- * **Internal:** Do not use
47
+ * Whether the checkbox is selected.
45
48
  */
46
- setSelected?: React.Dispatch<React.SetStateAction<string>>;
49
+ selected?: boolean;
47
50
  }
48
51
 
49
52
  export type CheckboxGroupElementType = HTMLDivElement;
@@ -81,6 +84,10 @@ interface CheckboxGroupPropsInternal
81
84
  * See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/radio#Value).
82
85
  */
83
86
  value?: string;
87
+ /**
88
+ * This determines if the last item in the group should disable the other items when selected.
89
+ */
90
+ lastItemDisables?: boolean;
84
91
  }
85
92
 
86
93
  interface HorizontalCheckboxGroupProps extends CheckboxGroupPropsInternal {
@@ -0,0 +1,30 @@
1
+ .mobius-combobox {
2
+ position: relative;
3
+ }
4
+
5
+ .mobius-combobox__list {
6
+ position: absolute;
7
+ margin: 0;
8
+ padding: 0;
9
+ top: 100%;
10
+ left: 0;
11
+ right: 0;
12
+ z-index: 1000;
13
+ background-color: #fff;
14
+ border: 1px solid #ccc;
15
+ border-top: 0;
16
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
17
+ max-height: 200px;
18
+ overflow-y: auto;
19
+ }
20
+
21
+ .mobius-combobox__option {
22
+ padding: 0.5rem 1rem;
23
+ cursor: pointer;
24
+ }
25
+
26
+ .mobius-combobox__option:hover,
27
+ .mobius-combobox__option--is-highlighted {
28
+ background-color: var(--color-primary);
29
+ color: var(--color-neutral-100);
30
+ }
@@ -0,0 +1,26 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Combobox, type ComboboxProps } from ".";
3
+ import { FRUITS, FRUITS_OBJECTS } from "./fixtures";
4
+
5
+ type StoryType = StoryObj<typeof Combobox>;
6
+
7
+ const meta: Meta<typeof Combobox> = {
8
+ title: "Forms/Combobox",
9
+ component: Combobox,
10
+ };
11
+
12
+ export const Default: StoryType = {
13
+ render: (args: ComboboxProps) => <Combobox {...args} />,
14
+ args: {
15
+ options: FRUITS,
16
+ },
17
+ };
18
+
19
+ export const ObjectOptions: StoryType = {
20
+ render: (args: ComboboxProps) => <Combobox {...args} />,
21
+ args: {
22
+ options: FRUITS_OBJECTS,
23
+ },
24
+ };
25
+
26
+ export default meta;