@recursica/mui-adapter 0.6.0 → 0.7.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 (164) hide show
  1. package/ARCHITECTURE.md +15 -0
  2. package/CHANGELOG.md +6 -0
  3. package/SETUP.md +77 -0
  4. package/USAGE.md +55 -0
  5. package/dist/mui-adapter.cjs +26 -26
  6. package/dist/mui-adapter.cjs.map +1 -1
  7. package/dist/mui-adapter.css +1 -1
  8. package/dist/mui-adapter.js +3721 -3511
  9. package/dist/mui-adapter.js.map +1 -1
  10. package/dist/src/components/Accordion/Accordion.d.ts +9 -29
  11. package/dist/src/components/AssistiveElement/AssistiveElement.d.ts +2 -5
  12. package/dist/src/components/Autocomplete/Autocomplete.d.ts +4 -8
  13. package/dist/src/components/Avatar/Avatar.d.ts +1 -6
  14. package/dist/src/components/Badge/Badge.d.ts +1 -4
  15. package/dist/src/components/Breadcrumb/Breadcrumb.d.ts +2 -3
  16. package/dist/src/components/Button/Button.d.ts +2 -17
  17. package/dist/src/components/Card/Card.d.ts +2 -4
  18. package/dist/src/components/Checkbox/Checkbox.d.ts +4 -6
  19. package/dist/src/components/Checkbox/CheckboxGroup.d.ts +8 -6
  20. package/dist/src/components/Chip/Chip.d.ts +2 -11
  21. package/dist/src/components/Container/Container.d.ts +1 -5
  22. package/dist/src/components/DatePicker/DatePicker.d.ts +2 -1
  23. package/dist/src/components/Dropdown/Dropdown.d.ts +4 -14
  24. package/dist/src/components/Flex/Flex.d.ts +2 -22
  25. package/dist/src/components/FormControlLayout/FormControlLayout.d.ts +2 -7
  26. package/dist/src/components/FormControlWrapper/FormControlWrapper.d.ts +3 -18
  27. package/dist/src/components/Group/Group.d.ts +2 -7
  28. package/dist/src/components/HoverCard/HoverCard.d.ts +13 -39
  29. package/dist/src/components/Label/Label.d.ts +2 -9
  30. package/dist/src/components/Link/Link.d.ts +2 -6
  31. package/dist/src/components/Loader/Loader.d.ts +2 -7
  32. package/dist/src/components/Menu/Menu.d.ts +24 -98
  33. package/dist/src/components/Modal/Modal.d.ts +32 -31
  34. package/dist/src/components/NumberInput/NumberInput.d.ts +3 -5
  35. package/dist/src/components/Pagination/Pagination.d.ts +8 -41
  36. package/dist/src/components/Panel/Panel.d.ts +5 -18
  37. package/dist/src/components/Radio/Radio.d.ts +4 -4
  38. package/dist/src/components/Radio/RadioGroup.d.ts +3 -7
  39. package/dist/src/components/ReadOnlyField/ReadOnlyField.d.ts +2 -5
  40. package/dist/src/components/SegmentedControl/SegmentedControl.d.ts +5 -15
  41. package/dist/src/components/Slider/Slider.d.ts +4 -14
  42. package/dist/src/components/Stack/Stack.d.ts +2 -5
  43. package/dist/src/components/Stepper/Stepper.d.ts +36 -16
  44. package/dist/src/components/Switch/Switch.d.ts +4 -4
  45. package/dist/src/components/Switch/SwitchGroup.d.ts +4 -4
  46. package/dist/src/components/Table/Table.d.ts +2 -1
  47. package/dist/src/components/Tabs/Tabs.d.ts +11 -26
  48. package/dist/src/components/Text/Text.d.ts +2 -16
  49. package/dist/src/components/TextArea/TextArea.d.ts +3 -9
  50. package/dist/src/components/TextField/TextField.d.ts +3 -7
  51. package/dist/src/components/TimePicker/TimePicker.d.ts +2 -1
  52. package/dist/src/components/Timeline/Timeline.d.ts +2 -1
  53. package/dist/src/components/Timeline/TimelineItem.d.ts +2 -13
  54. package/dist/src/components/Title/Title.d.ts +2 -15
  55. package/dist/src/components/Toast/Toast.d.ts +11 -16
  56. package/dist/src/components/Tooltip/Tooltip.d.ts +7 -21
  57. package/dist/src/components/TransferList/TransferList.d.ts +2 -1
  58. package/dist/src/components/Typography/Typography.d.ts +1 -1
  59. package/dist/src/utils/RequireAccessibleLabel.d.ts +1 -18
  60. package/dist/src/utils/filterStylingProps.d.ts +1 -11
  61. package/llms.txt +16 -0
  62. package/package.json +6 -2
  63. package/src/components/Accordion/Accordion.module.css +1 -2
  64. package/src/components/Accordion/Accordion.stories.tsx +0 -18
  65. package/src/components/Accordion/Accordion.tsx +19 -23
  66. package/src/components/AssistiveElement/AssistiveElement.module.css +32 -29
  67. package/src/components/AssistiveElement/AssistiveElement.stories.tsx +10 -27
  68. package/src/components/AssistiveElement/AssistiveElement.tsx +11 -15
  69. package/src/components/Autocomplete/Autocomplete.module.css +17 -18
  70. package/src/components/Autocomplete/Autocomplete.stories.tsx +7 -16
  71. package/src/components/Autocomplete/Autocomplete.tsx +27 -45
  72. package/src/components/Avatar/Avatar.tsx +2 -13
  73. package/src/components/Badge/Badge.tsx +2 -5
  74. package/src/components/Breadcrumb/Breadcrumb.tsx +1 -4
  75. package/src/components/Button/Button.module.css +19 -61
  76. package/src/components/Button/Button.stories.tsx +24 -23
  77. package/src/components/Button/Button.tsx +7 -18
  78. package/src/components/Card/Card.module.css +36 -0
  79. package/src/components/Card/Card.tsx +6 -4
  80. package/src/components/Checkbox/Checkbox.module.css +27 -10
  81. package/src/components/Checkbox/Checkbox.tsx +124 -24
  82. package/src/components/Checkbox/CheckboxGroup.stories.tsx +139 -0
  83. package/src/components/Checkbox/CheckboxGroup.tsx +43 -23
  84. package/src/components/Chip/Chip.module.css +52 -34
  85. package/src/components/Chip/Chip.tsx +45 -47
  86. package/src/components/Container/Container.stories.tsx +103 -20
  87. package/src/components/Container/Container.tsx +1 -13
  88. package/src/components/DatePicker/DatePicker.stories.tsx +125 -19
  89. package/src/components/DatePicker/DatePicker.tsx +3 -1
  90. package/src/components/Dropdown/Dropdown.tsx +7 -9
  91. package/src/components/FileInput/FileInput.stories.tsx +2 -19
  92. package/src/components/FileUpload/FileUpload.stories.tsx +2 -19
  93. package/src/components/Flex/Flex.stories.tsx +113 -20
  94. package/src/components/Flex/Flex.tsx +16 -26
  95. package/src/components/FormControlLayout/FormControlLayout.stories.tsx +0 -8
  96. package/src/components/FormControlLayout/FormControlLayout.tsx +18 -7
  97. package/src/components/FormControlWrapper/FormControlWrapper.module.css +1 -10
  98. package/src/components/FormControlWrapper/FormControlWrapper.stories.tsx +12 -21
  99. package/src/components/FormControlWrapper/FormControlWrapper.tsx +11 -30
  100. package/src/components/Group/Group.stories.tsx +6 -30
  101. package/src/components/Group/Group.tsx +14 -9
  102. package/src/components/HoverCard/HoverCard.tsx +72 -75
  103. package/src/components/Label/Label.module.css +4 -0
  104. package/src/components/Label/Label.stories.tsx +18 -12
  105. package/src/components/Label/Label.tsx +3 -9
  106. package/src/components/Link/Link.module.css +4 -3
  107. package/src/components/Link/Link.stories.tsx +2 -8
  108. package/src/components/Link/Link.tsx +1 -5
  109. package/src/components/Loader/Loader.module.css +1 -2
  110. package/src/components/Loader/Loader.tsx +1 -7
  111. package/src/components/Menu/IMPLEMENTATION_NOTES.md +4 -0
  112. package/src/components/Menu/Menu.stories.tsx +106 -188
  113. package/src/components/Menu/Menu.tsx +34 -280
  114. package/src/components/Modal/Modal.tsx +158 -160
  115. package/src/components/NumberInput/NumberInput.tsx +6 -18
  116. package/src/components/Pagination/IMPLEMENTATION_NOTES.md +3 -0
  117. package/src/components/Pagination/Pagination.module.css +12 -24
  118. package/src/components/Pagination/Pagination.stories.tsx +0 -8
  119. package/src/components/Pagination/Pagination.tsx +20 -140
  120. package/src/components/Panel/Panel.tsx +7 -30
  121. package/src/components/Radio/Radio.module.css +25 -10
  122. package/src/components/Radio/Radio.tsx +51 -19
  123. package/src/components/Radio/RadioGroup.stories.tsx +117 -0
  124. package/src/components/Radio/RadioGroup.tsx +26 -12
  125. package/src/components/ReadOnlyField/ReadOnlyField.module.css +6 -6
  126. package/src/components/ReadOnlyField/ReadOnlyField.stories.tsx +5 -36
  127. package/src/components/ReadOnlyField/ReadOnlyField.tsx +9 -13
  128. package/src/components/SegmentedControl/SegmentedControl.module.css +23 -47
  129. package/src/components/SegmentedControl/SegmentedControl.tsx +44 -14
  130. package/src/components/Slider/Slider.module.css +1 -4
  131. package/src/components/Slider/Slider.stories.tsx +3 -3
  132. package/src/components/Slider/Slider.tsx +26 -25
  133. package/src/components/Stack/Stack.stories.tsx +4 -25
  134. package/src/components/Stack/Stack.tsx +1 -5
  135. package/src/components/Stepper/IMPLEMENTATION_NOTES.md +4 -0
  136. package/src/components/Stepper/Stepper.stories.tsx +56 -43
  137. package/src/components/Stepper/Stepper.tsx +89 -87
  138. package/src/components/Switch/Switch.module.css +21 -0
  139. package/src/components/Switch/Switch.tsx +63 -16
  140. package/src/components/Switch/SwitchGroup.stories.tsx +132 -0
  141. package/src/components/Switch/SwitchGroup.tsx +27 -18
  142. package/src/components/Table/Table.stories.tsx +1 -19
  143. package/src/components/Table/Table.tsx +3 -1
  144. package/src/components/Tabs/IMPLEMENTATION_NOTES.md +4 -0
  145. package/src/components/Tabs/Tabs.stories.tsx +41 -24
  146. package/src/components/Tabs/Tabs.tsx +49 -66
  147. package/src/components/Text/Text.stories.tsx +5 -17
  148. package/src/components/Text/Text.tsx +1 -15
  149. package/src/components/TextArea/TextArea.tsx +7 -17
  150. package/src/components/TextField/TextField.stories.tsx +1 -27
  151. package/src/components/TextField/TextField.tsx +4 -6
  152. package/src/components/TimePicker/TimePicker.stories.tsx +2 -19
  153. package/src/components/TimePicker/TimePicker.tsx +3 -1
  154. package/src/components/Timeline/Timeline.tsx +4 -1
  155. package/src/components/Timeline/TimelineItem.tsx +1 -12
  156. package/src/components/Title/Title.stories.tsx +0 -18
  157. package/src/components/Title/Title.tsx +1 -7
  158. package/src/components/Toast/Toast.stories.tsx +1 -1
  159. package/src/components/Toast/Toast.tsx +62 -28
  160. package/src/components/Tooltip/Tooltip.tsx +11 -37
  161. package/src/components/TransferList/TransferList.stories.tsx +1 -19
  162. package/src/components/TransferList/TransferList.tsx +3 -1
  163. package/src/utils/RequireAccessibleLabel.ts +1 -12
  164. package/src/utils/filterStylingProps.ts +7 -20
@@ -1,6 +1,5 @@
1
1
  import React, { forwardRef } from "react";
2
2
  import { Loader } from "../Loader/Loader";
3
- import type { RecursicaLoaderProps } from "../Loader/Loader";
4
3
  import {
5
4
  Button as MuiButton,
6
5
  type ButtonProps as MuiButtonProps,
@@ -11,21 +10,7 @@ import {
11
10
  } from "../../utils/filterStylingProps";
12
11
  import styles from "./Button.module.css";
13
12
 
14
- export interface RecursicaButtonProps {
15
- variant?: "solid" | "outline" | "text";
16
- size?: "default" | "small";
17
- icon?: React.ReactNode;
18
- /**
19
- * For polymorphic support (e.g., `component="a"`), native to MUI.
20
- */
21
- component?: React.ElementType;
22
- /** Which Recursica Loader variant to use */
23
- loaderVariant?: RecursicaLoaderProps["variant"];
24
- /** The size variant for the loader */
25
- loaderSize?: RecursicaLoaderProps["size"];
26
- /** Whether to use the Recursica loader or fallback to the Mantine loader */
27
- useRecursicaLoader?: boolean;
28
- }
13
+ import { type RecursicaButtonProps } from "@recursica/adapter-common";
29
14
 
30
15
  export type ButtonProps = RecursicaOverStyled<
31
16
  Omit<MuiButtonProps, "variant" | "size" | "color" | "fullWidth"> &
@@ -73,7 +58,6 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
73
58
  const restRecord = sanitizedProps as Record<string, unknown>;
74
59
 
75
60
  // Explicitly delete blocked semantic expansion dimension props that MUI uses natively
76
- delete restRecord["fullWidth"];
77
61
  delete restRecord["color"]; // Recursica handles colors internally via tokens
78
62
 
79
63
  const hasStartIcon = !!icon || !!restRecord["startIcon"];
@@ -127,8 +111,13 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
127
111
  className={finalClass}
128
112
  loading={!!restRecord.loading}
129
113
  loadingIndicator={loadingIndicator}
114
+ fullWidth={!!restRecord.fullWidth}
130
115
  startIcon={
131
- icon != null ? (
116
+ restRecord.loading ? (
117
+ <span className={styles.iconWrapper} aria-hidden>
118
+ {loadingIndicator}
119
+ </span>
120
+ ) : icon != null ? (
132
121
  <span className={styles.iconWrapper} aria-hidden>
133
122
  {icon}
134
123
  </span>
@@ -65,6 +65,18 @@
65
65
  text-decoration: var(
66
66
  --recursica_ui-kit_components_card_properties_header-style_textDecoration
67
67
  );
68
+
69
+ /* Edge-to-edge behavior */
70
+ margin-left: calc(var(--card-padding) * -1) !important;
71
+ margin-right: calc(var(--card-padding) * -1) !important;
72
+ }
73
+
74
+ .header:first-child {
75
+ margin-top: calc(var(--card-padding) * -1) !important;
76
+ }
77
+
78
+ .header:last-child {
79
+ margin-bottom: calc(var(--card-padding) * -1) !important;
68
80
  }
69
81
 
70
82
  .footer {
@@ -75,6 +87,18 @@
75
87
  border-top: var(--recursica_ui-kit_components_card_properties_divider-size)
76
88
  solid
77
89
  var(--recursica_ui-kit_components_card_properties_colors_divider-color);
90
+
91
+ /* Edge-to-edge behavior */
92
+ margin-left: calc(var(--card-padding) * -1) !important;
93
+ margin-right: calc(var(--card-padding) * -1) !important;
94
+ }
95
+
96
+ .footer:first-child {
97
+ margin-top: calc(var(--card-padding) * -1) !important;
98
+ }
99
+
100
+ .footer:last-child {
101
+ margin-bottom: calc(var(--card-padding) * -1) !important;
78
102
  }
79
103
 
80
104
  .section {
@@ -82,6 +106,18 @@
82
106
  margin-top: var(
83
107
  --recursica_ui-kit_components_card_properties_vertical-gutter
84
108
  );
109
+
110
+ /* Edge-to-edge behavior */
111
+ margin-left: calc(var(--card-padding) * -1) !important;
112
+ margin-right: calc(var(--card-padding) * -1) !important;
113
+ }
114
+
115
+ .section:first-child {
116
+ margin-top: calc(var(--card-padding) * -1) !important;
117
+ }
118
+
119
+ .section:last-child {
120
+ margin-bottom: calc(var(--card-padding) * -1) !important;
85
121
  }
86
122
 
87
123
  .content {
@@ -6,8 +6,13 @@ import {
6
6
  } from "../../utils/filterStylingProps";
7
7
  import styles from "./Card.module.css";
8
8
 
9
+ import {
10
+ type RecursicaCardProps,
11
+ type RecursicaCardSectionProps,
12
+ } from "@recursica/adapter-common";
13
+
9
14
  // ==== CARD CONTAINER ====
10
- export type CardProps = RecursicaOverStyled<MuiCardProps>;
15
+ export type CardProps = RecursicaOverStyled<MuiCardProps & RecursicaCardProps>;
11
16
 
12
17
  /**
13
18
  * The root Card elevation box. It establishes the global background color, border radius, nested component gap, and outer shadow governed by the current `Layer` context.
@@ -53,9 +58,6 @@ const CardBase = forwardRef<HTMLDivElement, CardProps>(function Card(
53
58
  CardBase.displayName = "Card";
54
59
 
55
60
  // ==== NATIVE MANTINE CARD.SECTION ====
56
- export type RecursicaCardSectionProps = React.HTMLAttributes<HTMLDivElement> & {
57
- children?: React.ReactNode;
58
- };
59
61
  export type CardSectionProps = RecursicaOverStyled<RecursicaCardSectionProps>;
60
62
 
61
63
  /**
@@ -11,17 +11,11 @@
11
11
 
12
12
  /* --- GROUP STYLES --- */
13
13
  .groupRoot {
14
- padding: var(
15
- --recursica_ui-kit_components_checkbox-group_properties_padding,
16
- 0
17
- );
14
+ padding: var(--recursica_ui-kit_components_checkbox-group_properties_padding);
18
15
  margin: 0; /* HARDCODE: structural reset; consider token if layout needs change */
19
16
  display: flex;
20
17
  flex-direction: column;
21
- gap: var(
22
- --recursica_ui-kit_components_checkbox-group_properties_item-gap,
23
- 16px
24
- );
18
+ gap: var(--recursica_ui-kit_components_checkbox-group_properties_item-gap);
25
19
  }
26
20
 
27
21
  /* SideBySide Variant */
@@ -118,7 +112,8 @@
118
112
  }
119
113
 
120
114
  /* Checked/Indeterminate State */
121
- .input:checked {
115
+ .input:checked,
116
+ .inputChecked {
122
117
  background-color: var(
123
118
  --recursica_ui-kit_components_checkbox_properties_colors_background-checked
124
119
  );
@@ -126,7 +121,8 @@
126
121
  --recursica_ui-kit_components_checkbox_properties_colors_border-checked
127
122
  );
128
123
  }
129
- .input[data-indeterminate] {
124
+ .input[data-indeterminate],
125
+ .inputIndeterminate {
130
126
  background-color: var(
131
127
  --recursica_ui-kit_components_checkbox_properties_colors_background-indeterminate
132
128
  );
@@ -213,6 +209,27 @@
213
209
  );
214
210
  }
215
211
 
212
+ .description {
213
+ margin-top: 5px; /* HARDCODE: mantine default */
214
+ color: #868e96; /* HARDCODE: mantine default dimmed */
215
+ font-size: 12px; /* HARDCODE: mantine default xs */
216
+ line-height: 1.2;
217
+ }
218
+
219
+ .error {
220
+ margin-top: 5px; /* HARDCODE: mantine default */
221
+ color: #fa5252; /* HARDCODE: mantine default error */
222
+ font-size: 12px; /* HARDCODE: mantine default xs */
223
+ line-height: 1.2;
224
+ }
225
+
226
+ .description[data-disabled="true"],
227
+ .error[data-disabled="true"] {
228
+ opacity: var(
229
+ --recursica_ui-kit_components_checkbox-item_properties_disabled-opacity
230
+ );
231
+ }
232
+
216
233
  /* CheckboxGroup Layouts */
217
234
  .groupRoot {
218
235
  display: flex;
@@ -2,15 +2,17 @@ import React, { forwardRef } from "react";
2
2
  import {
3
3
  Checkbox as MuiCheckbox,
4
4
  type CheckboxProps as MuiCheckboxProps,
5
- FormControlLabel,
6
5
  } from "@mui/material";
7
6
  import { type ReadOnlyControlProps } from "@recursica/adapter-common";
8
- import { CheckboxGroup } from "./CheckboxGroup";
7
+ import { CheckboxGroup, CheckboxGroupContext } from "./CheckboxGroup";
9
8
  import {
10
9
  filterStylingProps,
11
10
  type RecursicaOverStyled,
12
11
  } from "../../utils/filterStylingProps";
13
- import { type RequireAccessibleLabel } from "../../utils/RequireAccessibleLabel";
12
+ import {
13
+ type RequireAccessibleLabel,
14
+ type RecursicaCheckboxProps,
15
+ } from "@recursica/adapter-common";
14
16
  import {
15
17
  FormControlLayout,
16
18
  type FormControlLayoutProps,
@@ -18,17 +20,18 @@ import {
18
20
 
19
21
  import styles from "./Checkbox.module.css";
20
22
 
21
- export type RecursicaCheckboxProps = RequireAccessibleLabel<
22
- Omit<MuiCheckboxProps, "size" | "color"> & {
23
- label?: React.ReactNode;
24
- } & ReadOnlyControlProps &
25
- Pick<
26
- FormControlLayoutProps,
27
- "formLayout" | "labelSize" | "controlMaxWidth" | "controlMinWidth"
28
- >
29
- >;
23
+ export type CheckboxWrapperProps = Omit<MuiCheckboxProps, "size" | "color"> &
24
+ RecursicaCheckboxProps &
25
+ ReadOnlyControlProps &
26
+ Pick<
27
+ FormControlLayoutProps,
28
+ "formLayout" | "labelSize" | "controlMaxWidth" | "controlMinWidth"
29
+ >;
30
+
31
+ export type RecursicaCheckboxPropsAlias =
32
+ RequireAccessibleLabel<CheckboxWrapperProps>;
30
33
 
31
- export type CheckboxProps = RecursicaOverStyled<RecursicaCheckboxProps>;
34
+ export type CheckboxProps = RecursicaOverStyled<RecursicaCheckboxPropsAlias>;
32
35
 
33
36
  type CheckboxComponent = React.ForwardRefExoticComponent<
34
37
  CheckboxProps & React.RefAttributes<HTMLButtonElement>
@@ -48,6 +51,9 @@ export const Checkbox = forwardRef<HTMLButtonElement, CheckboxProps>(
48
51
  controlMaxWidth,
49
52
  controlMinWidth,
50
53
  label,
54
+ description,
55
+ error,
56
+ style,
51
57
  ...rest
52
58
  } = props;
53
59
 
@@ -84,11 +90,21 @@ export const Checkbox = forwardRef<HTMLButtonElement, CheckboxProps>(
84
90
  ? `${styles.root} ${classNameProp}`
85
91
  : styles.root;
86
92
 
93
+ const groupContext = React.useContext(CheckboxGroupContext);
94
+ const isGrouped = groupContext !== null;
95
+ const isGroupReadOnly = isGrouped ? groupContext.readOnly : false;
96
+ const isChecked = isGrouped
97
+ ? (groupContext.value || []).includes(restRecord.value)
98
+ : !!(restRecord.checked ?? restRecord.defaultChecked);
99
+
87
100
  if (readOnly && !!readOnlyComponent) {
88
- const isChecked = !!(restRecord.checked ?? restRecord.defaultChecked);
89
101
  const ReadOnlyComp = readOnlyComponent;
90
102
  const roNode = (
91
- <ReadOnlyComp {...props} checked={isChecked} label={label} />
103
+ <ReadOnlyComp
104
+ {...props}
105
+ checked={isChecked}
106
+ label={label as React.ReactNode}
107
+ />
92
108
  );
93
109
 
94
110
  if (formLayout) {
@@ -107,25 +123,109 @@ export const Checkbox = forwardRef<HTMLButtonElement, CheckboxProps>(
107
123
  return <>{roNode}</>;
108
124
  }
109
125
 
126
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
127
+ if (isGrouped) {
128
+ const newValue = e.target.checked
129
+ ? [...(groupContext.value || []), restRecord.value]
130
+ : (groupContext.value || []).filter((v) => v !== restRecord.value);
131
+ if (groupContext.onChange) {
132
+ groupContext.onChange(e, newValue);
133
+ }
134
+ } else {
135
+ if (restRecord.onChange) {
136
+ (restRecord.onChange as any)(e, e.target.checked);
137
+ }
138
+ }
139
+ };
140
+
141
+ const CheckIcon = (props: React.ComponentProps<"svg">) => (
142
+ <svg
143
+ viewBox="0 0 10 7"
144
+ fill="none"
145
+ xmlns="http://www.w3.org/2000/svg"
146
+ {...props}
147
+ >
148
+ <path
149
+ d="M4 4.586L1.707 2.293A1 1 0 1 0 .293 3.707l3 3a.997.997 0 0 0 1.414 0l5-5A1 1 0 1 0 8.293.293L4 4.586z"
150
+ fill="currentColor"
151
+ fillRule="evenodd"
152
+ clipRule="evenodd"
153
+ />
154
+ </svg>
155
+ );
156
+
110
157
  const checkboxNode = (
111
158
  <MuiCheckbox
112
159
  ref={ref}
113
- className={finalClass}
160
+ className={!label ? `${finalClass} ${styles.inner}` : styles.inner}
114
161
  classes={mergedClassNames}
115
- disabled={readOnly || disabled}
162
+ disabled={readOnly || disabled || isGroupReadOnly}
116
163
  {...(sanitizedProps as unknown as MuiCheckboxProps)}
164
+ checked={isChecked as boolean}
165
+ onChange={handleChange}
166
+ sx={label ? { padding: 0 } : undefined}
167
+ icon={<div className={styles.input} />}
168
+ checkedIcon={
169
+ <div className={`${styles.input} ${styles.inputChecked}`}>
170
+ <CheckIcon className={styles.icon} />
171
+ </div>
172
+ }
173
+ indeterminateIcon={
174
+ <div className={`${styles.input} ${styles.inputIndeterminate}`}>
175
+ <svg
176
+ className={styles.icon}
177
+ xmlns="http://www.w3.org/2000/svg"
178
+ fill="none"
179
+ viewBox="0 0 32 32"
180
+ >
181
+ <rect x="6" y="14" width="20" height="4" fill="currentColor" />
182
+ </svg>
183
+ </div>
184
+ }
117
185
  />
118
186
  );
119
187
 
120
188
  const finalNode = label ? (
121
- <FormControlLabel
122
- control={checkboxNode}
123
- label={label}
124
- className={styles.labelWrapper}
125
- classes={{ label: styles.label }}
126
- />
189
+ <div className={finalClass} style={style as React.CSSProperties}>
190
+ <div className={styles.body}>
191
+ {checkboxNode}
192
+ <div className={styles.labelWrapper}>
193
+ <label
194
+ className={styles.label}
195
+ htmlFor={restRecord.id as string}
196
+ data-disabled={
197
+ readOnly || disabled || isGroupReadOnly ? true : undefined
198
+ }
199
+ >
200
+ {label as React.ReactNode}
201
+ </label>
202
+ {description && (
203
+ <div
204
+ className={styles.description}
205
+ data-disabled={
206
+ readOnly || disabled || isGroupReadOnly ? true : undefined
207
+ }
208
+ >
209
+ {description}
210
+ </div>
211
+ )}
212
+ {error && (
213
+ <div
214
+ className={styles.error}
215
+ data-disabled={
216
+ readOnly || disabled || isGroupReadOnly ? true : undefined
217
+ }
218
+ >
219
+ {error}
220
+ </div>
221
+ )}
222
+ </div>
223
+ </div>
224
+ </div>
127
225
  ) : (
128
- checkboxNode
226
+ <div className={finalClass} style={style as React.CSSProperties}>
227
+ {checkboxNode}
228
+ </div>
129
229
  );
130
230
 
131
231
  if (formLayout) {
@@ -0,0 +1,139 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Checkbox } from "./Checkbox";
3
+ import { CheckboxGroup } from "./CheckboxGroup";
4
+ import { useState } from "react";
5
+ import { formControlArgTypes } from "../../../.storybook/commonArgTypes";
6
+
7
+ const meta: Meta<typeof CheckboxGroup> = {
8
+ title: "UI-Kit/CheckboxGroup",
9
+ component: CheckboxGroup,
10
+ tags: ["autodocs"],
11
+ parameters: {
12
+ docs: {
13
+ description: {
14
+ component: `
15
+ The \`CheckboxGroup\` component is the mandatory organizational wrapper aggregating multiple \`Checkbox\` primitives into structurally bound unified arrays. It inherently leverages the \`FormControlWrapper\` granting native access to macroscopic layout structuring, assistive descriptions, and strict flex arrays.
16
+
17
+ We exclusively utilize the \`formLayout\` parameter to control macro-level form flow:
18
+ - **\`formLayout="stacked"\`**: Top-to-bottom layout cascading the Label bounding box down vertically into a standard stacked checkbox column array.
19
+ - **\`formLayout="side-by-side"\`**: Flow architecture pulling the grouping Label dynamically to the left while structurally organizing the internal checkboxes cleanly alongside it horizontally.
20
+
21
+ \`\`\`tsx
22
+ <Checkbox.Group
23
+ label="Execution Targets"
24
+ assistiveText="Strictly mapped structural grouping dynamically applied natively."
25
+ formLayout="stacked"
26
+ >
27
+ <Checkbox value="react" label="Browser Execution Context" />
28
+ <Checkbox value="svelte" label="Edge Nodes" />
29
+ </Checkbox.Group>
30
+ \`\`\`
31
+ `,
32
+ },
33
+ },
34
+ },
35
+ argTypes: {
36
+ ...formControlArgTypes,
37
+ readOnly: {
38
+ control: "boolean",
39
+ description:
40
+ "Toggles structural read-only data presentation bypassing interaction boundaries completely.",
41
+ },
42
+ },
43
+ };
44
+
45
+ export default meta;
46
+
47
+ type Story = StoryObj<typeof CheckboxGroup>;
48
+
49
+ export const Default: Story = {
50
+ args: {
51
+ formLayout: "stacked",
52
+ label: "Standard Group",
53
+ },
54
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
55
+ render: function StoryRender({ withLayer, layer, ...args }: any) {
56
+ const [value, setValue] = useState<string[]>([]);
57
+ return (
58
+ <Checkbox.Group {...args} value={value} onChange={setValue}>
59
+ <Checkbox value="1" label="Option 1" />
60
+ <Checkbox value="2" label="Option 2" />
61
+ </Checkbox.Group>
62
+ );
63
+ },
64
+ };
65
+
66
+ export const SideBySideLayout: Story = {
67
+ args: {
68
+ formLayout: "side-by-side",
69
+ labelOptionalText: "Recommended",
70
+ labelWithEditIcon: true,
71
+ label: "Frontend Frameworks",
72
+ assistiveText:
73
+ "Select all libraries currently in use for this specific workspace configuration.",
74
+ },
75
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
76
+ render: function StoryRender({ withLayer, layer, ...args }: any) {
77
+ const [value, setValue] = useState<string[]>(["react"]);
78
+ return (
79
+ <Checkbox.Group {...args} value={value} onChange={setValue}>
80
+ <Checkbox value="react" label="React (Standard Build)" />
81
+ <Checkbox
82
+ value="svelte"
83
+ label="The Svelte architecture which provides a highly optimized, completely compiler-driven framework avoiding virtual DOM boundaries. This massively extended text explicitly guarantees accurate wrapper constraint checking and multi-line flex alignment."
84
+ />
85
+ <Checkbox value="vue" label="Vue Configuration Map" />
86
+ </Checkbox.Group>
87
+ );
88
+ },
89
+ };
90
+
91
+ export const StackedLayout: Story = {
92
+ args: {
93
+ formLayout: "stacked",
94
+ required: true,
95
+ label: "Execution Targets",
96
+ error: "You must select at least one deployment target to compile.",
97
+ },
98
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
99
+ render: function StoryRender({ withLayer, layer, ...args }: any) {
100
+ const [value, setValue] = useState<string[]>(["react"]);
101
+ return (
102
+ <Checkbox.Group {...args} value={value} onChange={setValue}>
103
+ <Checkbox value="react" label="Browser Execution Context" />
104
+ <Checkbox
105
+ value="svelte"
106
+ label="Highly distributed Edge computing environments seamlessly bridging local runtime boundaries."
107
+ />
108
+ <Checkbox value="vue" label="Serverless Cloud Providers" />
109
+ </Checkbox.Group>
110
+ );
111
+ },
112
+ };
113
+
114
+ export const ReadOnly: Story = {
115
+ args: {
116
+ readOnly: true,
117
+ formLayout: "stacked",
118
+ required: true,
119
+ label: "Static ReadOnly Execution Locks",
120
+ assistiveText:
121
+ "This structure explicitly validates native component-level DOM preservation natively mapping lock bounds safely over interaction.",
122
+ },
123
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
124
+ render: function StoryRender({ withLayer, layer, ...args }: any) {
125
+ const [value, setValue] = useState<string[]>(["disabledNode"]);
126
+ return (
127
+ <Checkbox.Group {...args} value={value} onChange={setValue}>
128
+ <Checkbox
129
+ value="disabledNode"
130
+ label="Structurally Checked Node natively"
131
+ />
132
+ <Checkbox
133
+ value="disabledNodeEmpty"
134
+ label="Unchecked Configuration Limit"
135
+ />
136
+ </Checkbox.Group>
137
+ );
138
+ },
139
+ };
@@ -1,6 +1,5 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
  import React, { forwardRef } from "react";
3
- import { FormGroup, type FormGroupProps } from "@mui/material";
4
3
  import { type ReadOnlyControlProps } from "@recursica/adapter-common";
5
4
  import {
6
5
  filterStylingProps,
@@ -10,17 +9,29 @@ import { type RecursicaFormControlWrapperProps } from "../FormControlWrapper/For
10
9
  import { WithReadOnlyWrapper } from "../ReadOnlyField/WithReadOnlyWrapper";
11
10
  import styles from "./Checkbox.module.css";
12
11
 
12
+ import { FormGroup as MuiFormGroup } from "@mui/material";
13
+
14
+ export const CheckboxGroupContext = React.createContext<{
15
+ value?: any[];
16
+ onChange?: (event: React.SyntheticEvent, value: any) => void;
17
+ name?: string;
18
+ readOnly?: boolean;
19
+ } | null>(null);
20
+
21
+ import { type RecursicaCheckboxGroupProps as BaseRecursicaCheckboxGroupProps } from "@recursica/adapter-common";
22
+
13
23
  export interface RecursicaCheckboxGroupProps
14
- extends Omit<FormGroupProps, "size" | "onChange">,
24
+ extends Omit<React.HTMLAttributes<HTMLDivElement>, "onChange">,
15
25
  Omit<
16
26
  RecursicaFormControlWrapperProps,
17
- "controlMaxWidth" | "controlMinWidth"
27
+ | "controlMaxWidth"
28
+ | "controlMinWidth"
29
+ | "onChange"
30
+ | "classes"
31
+ | "withAsterisk"
18
32
  >,
19
- ReadOnlyControlProps {
20
- value?: any[];
21
- defaultValue?: any[];
22
- onChange?: (value: any[]) => void;
23
- }
33
+ ReadOnlyControlProps,
34
+ BaseRecursicaCheckboxGroupProps {}
24
35
 
25
36
  export type CheckboxGroupProps =
26
37
  RecursicaOverStyled<RecursicaCheckboxGroupProps>;
@@ -45,7 +56,7 @@ export const CheckboxGroup = forwardRef<HTMLDivElement, CheckboxGroupProps>(
45
56
  assistiveWithIcon,
46
57
  error,
47
58
  required,
48
- withAsterisk,
59
+ // removed withAsterisk
49
60
  id,
50
61
  className,
51
62
  style,
@@ -56,6 +67,7 @@ export const CheckboxGroup = forwardRef<HTMLDivElement, CheckboxGroupProps>(
56
67
  value,
57
68
  defaultValue,
58
69
  onChange,
70
+ row,
59
71
  ...rest
60
72
  } = props;
61
73
  const sanitizedProps = filterStylingProps(rest, overStyled);
@@ -64,11 +76,10 @@ export const CheckboxGroup = forwardRef<HTMLDivElement, CheckboxGroupProps>(
64
76
  // Delete prohibited sizing hooks
65
77
  delete restRecord["size"];
66
78
 
67
- const handleChange = (event: React.SyntheticEvent, childValue: any) => {
68
- // In MUI, FormGroup doesn't handle value arrays automatically like Mantine Checkbox.Group does.
69
- // A common pattern is that each child Checkbox handles its own onChange,
70
- // but to match Mantine API we might need context or cloning.
71
- // For now, we pass down props or rely on the user to handle state.
79
+ const handleChange = (_event: React.SyntheticEvent, childValue: any) => {
80
+ if (onChange) {
81
+ onChange(childValue);
82
+ }
72
83
  };
73
84
 
74
85
  return (
@@ -78,7 +89,7 @@ export const CheckboxGroup = forwardRef<HTMLDivElement, CheckboxGroupProps>(
78
89
  controlMaxWidth="var(--recursica_ui-kit_components_checkbox-item_properties_max-width)"
79
90
  controlMinWidth={undefined}
80
91
  overStyled={overStyled as true}
81
- labelElement="div"
92
+ // strictly override
82
93
  formLayout={formLayout}
83
94
  labelSize={labelSize}
84
95
  labelAlignment={labelAlignment}
@@ -91,7 +102,6 @@ export const CheckboxGroup = forwardRef<HTMLDivElement, CheckboxGroupProps>(
91
102
  assistiveWithIcon={assistiveWithIcon}
92
103
  error={error}
93
104
  required={required}
94
- withAsterisk={withAsterisk}
95
105
  id={id}
96
106
  readOnly={readOnly && !!readOnlyComponent}
97
107
  readOnlyComponent={readOnlyComponent}
@@ -100,14 +110,24 @@ export const CheckboxGroup = forwardRef<HTMLDivElement, CheckboxGroupProps>(
100
110
  readOnlyValue={value !== undefined ? value : defaultValue}
101
111
  readOnlyNativeProps={props}
102
112
  activeComponent={
103
- <FormGroup
104
- ref={ref}
105
- {...(sanitizedProps as unknown as FormGroupProps)}
106
- className={`${styles.groupRoot} ${(sanitizedProps as any).className || ""}`}
107
- data-layout={formLayout}
113
+ <CheckboxGroupContext.Provider
114
+ value={{
115
+ value: value !== undefined ? value : defaultValue,
116
+ onChange: handleChange,
117
+ name: restRecord.name as string | undefined,
118
+ readOnly: readOnly || !!(restRecord as any).disabled,
119
+ }}
108
120
  >
109
- {children}
110
- </FormGroup>
121
+ <MuiFormGroup
122
+ ref={ref}
123
+ row={row}
124
+ {...(sanitizedProps as any)}
125
+ className={`${styles.groupRoot} ${(sanitizedProps as any).className || ""}`.trim()}
126
+ data-layout={formLayout}
127
+ >
128
+ {children}
129
+ </MuiFormGroup>
130
+ </CheckboxGroupContext.Provider>
111
131
  }
112
132
  />
113
133
  );