@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,36 +1,19 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import React, { forwardRef } from "react";
1
+ import { forwardRef } from "react";
3
2
  import {
4
3
  Pagination as MuiPagination,
5
4
  type PaginationProps as MuiPaginationProps,
6
- type PaginationRootProps as MuiPaginationRootProps,
7
5
  } from "@mui/material";
8
6
  import {
9
7
  filterStylingProps,
10
8
  type RecursicaOverStyled,
11
9
  } from "../../utils/filterStylingProps";
12
10
  import styles from "./Pagination.module.css";
13
- import {
14
- NextWithLabel,
15
- PrevWithLabel,
16
- FirstWithLabel,
17
- LastWithLabel,
18
- PaginationIcon,
19
- } from "./Pagination.icons";
20
-
21
- export type PaginationProps = RecursicaOverStyled<MuiPaginationProps> & {
22
- /** If set to true, displays text labels alongside the icons for next/previous/first/last edges. */
23
- withLabels?: boolean;
24
- };
25
11
 
26
- export type PaginationRootProps = RecursicaOverStyled<MuiPaginationRootProps>;
12
+ import { type RecursicaPaginationProps } from "@recursica/adapter-common";
27
13
 
28
- export type PaginationEdgeProps<T extends React.ElementType> =
29
- React.ComponentProps<T> & {
30
- /** If set to true, displays text labels alongside the icon. */
31
- withLabel?: boolean;
32
- icon?: any;
33
- };
14
+ export type PaginationProps = RecursicaOverStyled<
15
+ MuiPaginationProps & RecursicaPaginationProps
16
+ >;
34
17
 
35
18
  function usePaginationClassNames(restRecord: Record<string, unknown>): {
36
19
  className: string;
@@ -38,8 +21,7 @@ function usePaginationClassNames(restRecord: Record<string, unknown>): {
38
21
  } {
39
22
  const mergedClassNames: Partial<Record<string, string>> = {
40
23
  root: styles.root,
41
- control: styles.control,
42
- dots: styles.dots,
24
+ ul: styles.ul,
43
25
  };
44
26
 
45
27
  const classNamesProp = restRecord.classNames;
@@ -50,10 +32,7 @@ function usePaginationClassNames(restRecord: Record<string, unknown>): {
50
32
  ) {
51
33
  const o = classNamesProp as Partial<Record<string, string>>;
52
34
  mergedClassNames.root = o.root ? `${styles.root} ${o.root}` : styles.root;
53
- mergedClassNames.control = o.control
54
- ? `${styles.control} ${o.control}`
55
- : styles.control;
56
- mergedClassNames.dots = o.dots ? `${styles.dots} ${o.dots}` : styles.dots;
35
+ mergedClassNames.ul = o.ul ? `${styles.ul} ${o.ul}` : styles.ul;
57
36
  }
58
37
 
59
38
  const classNameProp = restRecord.className as string | undefined;
@@ -64,72 +43,14 @@ function usePaginationClassNames(restRecord: Record<string, unknown>): {
64
43
  return { className: finalClass, classNames: mergedClassNames };
65
44
  }
66
45
 
67
- const _PaginationRoot = forwardRef<HTMLDivElement, PaginationRootProps>(
68
- function PaginationRoot({ overStyled = false, ...rest }, ref) {
69
- const sanitizedProps = filterStylingProps(rest, overStyled);
70
- const stylingParams = usePaginationClassNames(
71
- sanitizedProps as Record<string, unknown>,
72
- );
73
-
74
- return (
75
- <div
76
- ref={ref}
77
- className={stylingParams.className}
78
- classes={stylingParams.classNames}
79
- {...(sanitizedProps as MuiPaginationRootProps)}
80
- />
81
- );
82
- },
83
- );
84
- _PaginationRoot.displayName = "Pagination.Root";
85
-
86
- const _PaginationNext = forwardRef<
87
- HTMLButtonElement,
88
- PaginationEdgeProps<typeof MuiPagination.Next>
89
- >(function PaginationNext({ withLabel, icon, ...props }, ref) {
90
- const renderIcon = icon || (withLabel ? NextWithLabel : undefined);
91
- return (
92
- <div ref={ref} data-variant="text" icon={renderIcon as any} {...props} />
93
- );
94
- });
95
- _PaginationNext.displayName = "Pagination.Next";
96
-
97
- const _PaginationPrevious = forwardRef<
98
- HTMLButtonElement,
99
- PaginationEdgeProps<typeof MuiPagination.Previous>
100
- >(function PaginationPrevious({ withLabel, icon, ...props }, ref) {
101
- const renderIcon = icon || (withLabel ? PrevWithLabel : undefined);
102
- return (
103
- <div ref={ref} data-variant="text" icon={renderIcon as any} {...props} />
104
- );
105
- });
106
- _PaginationPrevious.displayName = "Pagination.Previous";
107
-
108
- const _PaginationFirst = forwardRef<
109
- HTMLButtonElement,
110
- PaginationEdgeProps<typeof MuiPagination.First>
111
- >(function PaginationFirst({ withLabel, icon, ...props }, ref) {
112
- const renderIcon = icon || (withLabel ? FirstWithLabel : undefined);
113
- return (
114
- <div ref={ref} data-variant="text" icon={renderIcon as any} {...props} />
115
- );
116
- });
117
- _PaginationFirst.displayName = "Pagination.First";
118
-
119
- const _PaginationLast = forwardRef<
120
- HTMLButtonElement,
121
- PaginationEdgeProps<typeof MuiPagination.Last>
122
- >(function PaginationLast({ withLabel, icon, ...props }, ref) {
123
- const renderIcon = icon || (withLabel ? LastWithLabel : undefined);
124
- return (
125
- <div ref={ref} data-variant="text" icon={renderIcon as any} {...props} />
126
- );
127
- });
128
- _PaginationLast.displayName = "Pagination.Last";
129
-
130
46
  const _Pagination = forwardRef<HTMLDivElement, PaginationProps>(
131
47
  function Pagination(
132
- { overStyled = false, getControlProps, withLabels, ...rest },
48
+ {
49
+ overStyled = false,
50
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
51
+ withLabels,
52
+ ...rest
53
+ },
133
54
  ref,
134
55
  ) {
135
56
  const sanitizedProps = filterStylingProps(rest, overStyled);
@@ -137,34 +58,13 @@ const _Pagination = forwardRef<HTMLDivElement, PaginationProps>(
137
58
  sanitizedProps as Record<string, unknown>,
138
59
  );
139
60
 
140
- const mergedGetControlProps = (
141
- control: "first" | "previous" | "last" | "next",
142
- ) => {
143
- const baseProps = { "data-variant": "text" };
144
- if (getControlProps) {
145
- return { ...baseProps, ...getControlProps(control) };
146
- }
147
- return baseProps;
148
- };
149
-
150
- const labelsIcons = withLabels
151
- ? {
152
- nextIcon: NextWithLabel,
153
- previousIcon: PrevWithLabel,
154
- firstIcon: FirstWithLabel,
155
- lastIcon: LastWithLabel,
156
- }
157
- : {};
158
-
159
61
  return (
160
- <MuiPagination
161
- ref={ref}
162
- className={stylingParams.className}
163
- classes={stylingParams.classNames}
164
- getControlProps={mergedGetControlProps}
165
- {...labelsIcons}
166
- {...(sanitizedProps as MuiPaginationProps)}
167
- />
62
+ <div ref={ref} className={stylingParams.className}>
63
+ <MuiPagination
64
+ classes={stylingParams.classNames}
65
+ {...(sanitizedProps as MuiPaginationProps)}
66
+ />
67
+ </div>
168
68
  );
169
69
  },
170
70
  );
@@ -173,24 +73,4 @@ _Pagination.displayName = "Pagination";
173
73
  /**
174
74
  * Recursica Pagination component wrapping Mui's Pagination.
175
75
  */
176
- export const Pagination = _Pagination as typeof _Pagination & {
177
- Root: typeof _PaginationRoot;
178
- Items: typeof MuiPagination.Items;
179
- Control: typeof MuiPagination.Control;
180
- Dots: typeof MuiPagination.Dots;
181
- Next: typeof _PaginationNext;
182
- Previous: typeof _PaginationPrevious;
183
- First: typeof _PaginationFirst;
184
- Last: typeof _PaginationLast;
185
- Icon: typeof PaginationIcon;
186
- };
187
-
188
- Pagination.Root = _PaginationRoot;
189
- Pagination.Items = MuiPagination.Items;
190
- Pagination.Control = MuiPagination.Control;
191
- Pagination.Dots = MuiPagination.Dots;
192
- Pagination.Next = _PaginationNext;
193
- Pagination.Previous = _PaginationPrevious;
194
- Pagination.First = _PaginationFirst;
195
- Pagination.Last = _PaginationLast;
196
- Pagination.Icon = PaginationIcon;
76
+ export const Pagination = _Pagination;
@@ -13,14 +13,7 @@ import styles from "./Panel.module.css";
13
13
  // PANEL (Drawer)
14
14
  // ============================================================
15
15
 
16
- export interface RecursicaPanelProps extends MuiDrawerProps {
17
- /**
18
- * If true, forces the header text to a single line and truncates with an ellipsis.
19
- * Note: While the prop is named `wrapHeaderText` for backward compatibility,
20
- * setting it to `true` actually PREVENTS wrapping (it forces truncation).
21
- */
22
- wrapHeaderText?: boolean;
23
- }
16
+ import { type RecursicaPanelProps } from "@recursica/adapter-common";
24
17
 
25
18
  /**
26
19
  * Recursica Panel root props. Extends Mui Drawer.
@@ -59,7 +52,10 @@ const PanelBase = function Panel({
59
52
  keepMounted = true,
60
53
  wrapHeaderText = false,
61
54
  ...rest
62
- }: PanelProps) {
55
+ }: PanelProps & {
56
+ position?: "left" | "right" | "top" | "bottom";
57
+ opened?: boolean;
58
+ }) {
63
59
  const sanitizedProps = filterStylingProps(rest, overStyled);
64
60
 
65
61
  // Bind CSS module classes to Mui's internal classNames API
@@ -89,9 +85,9 @@ const PanelBase = function Panel({
89
85
 
90
86
  return (
91
87
  <MuiDrawer
92
- position={position} /* Recursica default: right; Mui default: left */
88
+ anchor={position} /* Recursica default: right; Mui default: left */
93
89
  keepMounted={keepMounted}
94
- closeOnClickOutside={rest.closeOnClickOutside ?? Boolean(rest.opened)}
90
+ open={Boolean(rest.opened)}
95
91
  {...(sanitizedProps as unknown as MuiDrawerProps)}
96
92
  classes={mergedClassNames}
97
93
  />
@@ -129,28 +125,9 @@ export const PanelFooter = forwardRef<HTMLDivElement, PanelFooterProps>(
129
125
  PanelFooter.displayName = "PanelFooter";
130
126
 
131
127
  // ============================================================
132
- // DOT NOTATION EXPORT
133
- // ============================================================
134
-
135
128
  type PanelComponent = typeof PanelBase & {
136
- Root: typeof MuiDrawer.Root;
137
- Overlay: typeof MuiDrawer.Overlay;
138
- Content: typeof MuiDrawer.Content;
139
- Header: typeof MuiDrawer.Header;
140
- Title: typeof MuiDrawer.Title;
141
- CloseButton: typeof MuiDrawer.CloseButton;
142
- Body: typeof MuiDrawer.Body;
143
- Stack: typeof MuiDrawer.Stack;
144
129
  Footer: typeof PanelFooter;
145
130
  };
146
131
 
147
132
  export const Panel = PanelBase as PanelComponent;
148
- Panel.Root = MuiDrawer.Root;
149
- Panel.Overlay = MuiDrawer.Overlay;
150
- Panel.Content = MuiDrawer.Content;
151
- Panel.Header = MuiDrawer.Header;
152
- Panel.Title = MuiDrawer.Title;
153
- Panel.CloseButton = MuiDrawer.CloseButton;
154
- Panel.Body = MuiDrawer.Body;
155
- Panel.Stack = MuiDrawer.Stack;
156
133
  Panel.Footer = PanelFooter;
@@ -12,15 +12,13 @@
12
12
  /* --- GROUP STYLES --- */
13
13
  .groupRoot {
14
14
  padding: var(
15
- --recursica_ui-kit_components_radio-button-group_properties_padding,
16
- 0
15
+ --recursica_ui-kit_components_radio-button-group_properties_padding
17
16
  );
18
17
  margin: 0; /* HARDCODE: structural reset; consider token if layout needs change */
19
18
  display: flex;
20
19
  flex-direction: column;
21
20
  gap: var(
22
- --recursica_ui-kit_components_radio-button-group_properties_item-gap,
23
- 16px
21
+ --recursica_ui-kit_components_radio-button-group_properties_item-gap
24
22
  );
25
23
  }
26
24
 
@@ -49,10 +47,7 @@
49
47
  .groupRoot[data-layout="stacked"] {
50
48
  flex-direction: column;
51
49
  margin-top: var(
52
- --recursica_ui-kit_components_radio-button-group_variants_layouts_stacked_properties_label-field-gap,
53
- var(
54
- --recursica_ui-kit_components_radio-button-group_variants_layouts_stacked_properties_top-bottom-margin
55
- )
50
+ --recursica_ui-kit_components_radio-button-group_variants_layouts_stacked_properties_label-field-gap
56
51
  );
57
52
  margin-bottom: var(
58
53
  --recursica_ui-kit_components_radio-button-group_variants_layouts_stacked_properties_top-bottom-margin
@@ -214,7 +209,27 @@
214
209
  --recursica_ui-kit_components_radio-button-item_properties_colors_disabled-text
215
210
  );
216
211
  opacity: var(
217
- --recursica_ui-kit_components_radio-button-item_properties_disabled-opacity,
218
- var(--recursica_ui-kit_components_radio-button_properties_disabled-opacity)
212
+ --recursica_ui-kit_components_radio-button-item_properties_disabled-opacity
213
+ );
214
+ }
215
+
216
+ .root .description {
217
+ margin-top: 5px; /* HARDCODE: mantine default */
218
+ color: #868e96; /* HARDCODE: mantine default dimmed */
219
+ font-size: 12px; /* HARDCODE: mantine default xs */
220
+ line-height: 1.2;
221
+ }
222
+
223
+ .root .error {
224
+ margin-top: 5px; /* HARDCODE: mantine default */
225
+ color: #fa5252; /* HARDCODE: mantine default error */
226
+ font-size: 12px; /* HARDCODE: mantine default xs */
227
+ line-height: 1.2;
228
+ }
229
+
230
+ .root .description[data-disabled="true"],
231
+ .root .error[data-disabled="true"] {
232
+ opacity: var(
233
+ --recursica_ui-kit_components_radio-button-item_properties_disabled-opacity
219
234
  );
220
235
  }
@@ -2,7 +2,6 @@ import { forwardRef } from "react";
2
2
  import {
3
3
  Radio as MuiRadio,
4
4
  type RadioProps as MuiRadioProps,
5
- FormControlLabel,
6
5
  } from "@mui/material";
7
6
  import { type ReadOnlyControlProps } from "@recursica/adapter-common";
8
7
  import { RadioGroup } from "./RadioGroup";
@@ -10,7 +9,10 @@ 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 RecursicaRadioProps,
15
+ } from "@recursica/adapter-common";
14
16
  import {
15
17
  FormControlLayout,
16
18
  type FormControlLayoutProps,
@@ -33,16 +35,18 @@ const RadioIcon: React.FC<{
33
35
  </svg>
34
36
  );
35
37
 
36
- export type RecursicaRadioProps = RequireAccessibleLabel<
37
- Omit<MuiRadioProps, "size" | "color"> &
38
- ReadOnlyControlProps &
39
- Pick<
40
- FormControlLayoutProps,
41
- "formLayout" | "labelSize" | "controlMaxWidth" | "controlMinWidth"
42
- >
43
- >;
38
+ export type RadioWrapperProps = Omit<MuiRadioProps, "size" | "color"> &
39
+ RecursicaRadioProps &
40
+ ReadOnlyControlProps &
41
+ Pick<
42
+ FormControlLayoutProps,
43
+ "formLayout" | "labelSize" | "controlMaxWidth" | "controlMinWidth"
44
+ >;
45
+
46
+ export type RecursicaRadioPropsAlias =
47
+ RequireAccessibleLabel<RadioWrapperProps>;
44
48
 
45
- export type RadioProps = RecursicaOverStyled<RecursicaRadioProps>;
49
+ export type RadioProps = RecursicaOverStyled<RecursicaRadioPropsAlias>;
46
50
 
47
51
  type RadioComponent = React.ForwardRefExoticComponent<
48
52
  RadioProps & React.RefAttributes<HTMLInputElement>
@@ -61,6 +65,10 @@ export const Radio = forwardRef<HTMLInputElement, RadioProps>(
61
65
  labelSize,
62
66
  controlMaxWidth,
63
67
  controlMinWidth,
68
+ label,
69
+ description,
70
+ error,
71
+ style,
64
72
  ...rest
65
73
  } = props;
66
74
  const sanitizedProps = filterStylingProps(rest, overStyled);
@@ -139,7 +147,7 @@ export const Radio = forwardRef<HTMLInputElement, RadioProps>(
139
147
  // We omit Mui's sizing/coloring so we rely strictly on variables from Radio.module.css
140
148
  const radioNode = (
141
149
  <MuiRadio
142
- ref={ref}
150
+ ref={ref as any}
143
151
  icon={<RadioIcon />}
144
152
  checkedIcon={<RadioIcon className={styles.checked} />}
145
153
  className={finalClass}
@@ -149,13 +157,37 @@ export const Radio = forwardRef<HTMLInputElement, RadioProps>(
149
157
  />
150
158
  );
151
159
 
152
- const finalNode = restRecord.label ? (
153
- <FormControlLabel
154
- control={radioNode}
155
- label={restRecord.label as React.ReactNode}
156
- className={styles.labelWrapper}
157
- classes={{ label: styles.label }}
158
- />
160
+ const finalNode = label ? (
161
+ <div className={finalClass} style={style as React.CSSProperties}>
162
+ <div className={styles.body}>
163
+ {radioNode}
164
+ <div className={styles.labelWrapper}>
165
+ <label
166
+ className={styles.label}
167
+ htmlFor={restRecord.id as string}
168
+ data-disabled={readOnly || disabled ? true : undefined}
169
+ >
170
+ {label as React.ReactNode}
171
+ </label>
172
+ {description && (
173
+ <div
174
+ className={styles.description}
175
+ data-disabled={readOnly || disabled ? true : undefined}
176
+ >
177
+ {description}
178
+ </div>
179
+ )}
180
+ {error && (
181
+ <div
182
+ className={styles.error}
183
+ data-disabled={readOnly || disabled ? true : undefined}
184
+ >
185
+ {error}
186
+ </div>
187
+ )}
188
+ </div>
189
+ </div>
190
+ </div>
159
191
  ) : (
160
192
  radioNode
161
193
  );
@@ -0,0 +1,117 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Radio } from "./Radio";
3
+ import { RadioGroup } from "./RadioGroup";
4
+ import { useState } from "react";
5
+ import { formControlArgTypes } from "../../../.storybook/commonArgTypes";
6
+
7
+ const meta: Meta<typeof RadioGroup> = {
8
+ title: "UI-Kit/RadioGroup",
9
+ component: RadioGroup,
10
+ tags: ["autodocs"],
11
+ parameters: {
12
+ docs: {
13
+ description: {
14
+ component: `
15
+ The \`RadioGroup\` component is the mandatory organizational wrapper aggregating multiple \`Radio\` primitives into structurally bound unified selection 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 radio column array.
19
+ - **\`formLayout="side-by-side"\`**: Flow architecture pulling the grouping Label dynamically to the left while structurally organizing the internal radios cleanly alongside it horizontally.
20
+ `,
21
+ },
22
+ },
23
+ },
24
+ argTypes: {
25
+ ...formControlArgTypes,
26
+ readOnly: {
27
+ control: "boolean",
28
+ description:
29
+ "Toggles structural read-only data presentation bypassing interaction boundaries completely.",
30
+ },
31
+ },
32
+ };
33
+
34
+ export default meta;
35
+
36
+ type Story = StoryObj<typeof RadioGroup>;
37
+
38
+ export const Default: Story = {
39
+ args: {
40
+ formLayout: "stacked",
41
+ label: "Standard Group",
42
+ },
43
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
44
+ render: function StoryRender({ withLayer, layer, ...args }: any) {
45
+ const [value, setValue] = useState<string>("");
46
+ return (
47
+ <RadioGroup {...args} value={value} onChange={setValue}>
48
+ <Radio value="1" label="Option 1" />
49
+ <Radio value="2" label="Option 2" />
50
+ </RadioGroup>
51
+ );
52
+ },
53
+ };
54
+
55
+ export const StackedLayout: Story = {
56
+ args: {
57
+ formLayout: "stacked",
58
+ required: true,
59
+ label: "Hosting Provider",
60
+ error: "You must select a deployment provider.",
61
+ },
62
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
63
+ render: function StoryRender({ withLayer, layer, ...args }: any) {
64
+ const [value, setValue] = useState<string>("aws");
65
+ return (
66
+ <RadioGroup {...args} value={value} onChange={setValue}>
67
+ <Radio value="aws" label="Amazon Web Services" />
68
+ <Radio
69
+ value="gcp"
70
+ label="Google Cloud Platform (with completely distributed edge computing environments bridging local runtime boundaries seamlessly.)"
71
+ />
72
+ <Radio value="azure" label="Microsoft Azure" />
73
+ </RadioGroup>
74
+ );
75
+ },
76
+ };
77
+
78
+ export const SideBySideLayout: Story = {
79
+ args: {
80
+ formLayout: "side-by-side",
81
+ labelOptionalText: "Recommended",
82
+ labelWithEditIcon: true,
83
+ label: "Deployment Region",
84
+ assistiveText: "Select the data center closest to your user base.",
85
+ },
86
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
87
+ render: function StoryRender({ withLayer, layer, ...args }: any) {
88
+ const [value, setValue] = useState<string>("us-east");
89
+ return (
90
+ <RadioGroup {...args} value={value} onChange={setValue}>
91
+ <Radio value="us-east" label="US East (N. Virginia)" />
92
+ <Radio value="us-west" label="US West (Oregon)" />
93
+ <Radio value="eu-central" label="EU Central (Frankfurt)" />
94
+ </RadioGroup>
95
+ );
96
+ },
97
+ };
98
+
99
+ export const ReadOnly: Story = {
100
+ args: {
101
+ readOnly: true,
102
+ formLayout: "stacked",
103
+ required: true,
104
+ label: "Selected Framework",
105
+ assistiveText: "This selection cannot be changed after initialization.",
106
+ },
107
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
108
+ render: function StoryRender({ withLayer, layer, ...args }: any) {
109
+ const [value, setValue] = useState<string>("react");
110
+ return (
111
+ <RadioGroup {...args} value={value} onChange={setValue}>
112
+ <Radio value="react" label="React" />
113
+ <Radio value="vue" label="Vue" />
114
+ </RadioGroup>
115
+ );
116
+ },
117
+ };
@@ -1,7 +1,6 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
1
  import React, { forwardRef } from "react";
3
2
  import {
4
- Radio as MuiRadio,
3
+ // Removed unused Radio import
5
4
  RadioGroup as MuiRadioGroup,
6
5
  type RadioGroupProps as MuiRadioGroupProps,
7
6
  } from "@mui/material";
@@ -14,13 +13,29 @@ import { type RecursicaFormControlWrapperProps } from "../FormControlWrapper/For
14
13
  import { WithReadOnlyWrapper } from "../ReadOnlyField/WithReadOnlyWrapper";
15
14
  import styles from "./Radio.module.css";
16
15
 
16
+ import { type RecursicaRadioGroupProps as BaseRecursicaRadioGroupProps } from "@recursica/adapter-common";
17
+
17
18
  export interface RecursicaRadioGroupProps
18
- extends Omit<MuiRadioGroupProps, "size" | "labelProps">,
19
+ extends Omit<
20
+ MuiRadioGroupProps,
21
+ | "size"
22
+ | "labelProps"
23
+ | "classes"
24
+ | "ref"
25
+ | "onChange"
26
+ | "value"
27
+ | "defaultValue"
28
+ >,
19
29
  Omit<
20
30
  RecursicaFormControlWrapperProps,
21
- "controlMaxWidth" | "controlMinWidth"
31
+ | "controlMaxWidth"
32
+ | "controlMinWidth"
33
+ | "classes"
34
+ | "onChange"
35
+ | keyof MuiRadioGroupProps
22
36
  >,
23
- ReadOnlyControlProps {}
37
+ ReadOnlyControlProps,
38
+ BaseRecursicaRadioGroupProps {}
24
39
 
25
40
  export type RadioGroupProps = RecursicaOverStyled<RecursicaRadioGroupProps>;
26
41
 
@@ -43,8 +58,9 @@ export const RadioGroup = forwardRef<HTMLDivElement, RadioGroupProps>(
43
58
  assistiveText,
44
59
  assistiveWithIcon,
45
60
  error,
61
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
46
62
  required,
47
- withAsterisk,
63
+ // Removed withAsterisk as it is not supported natively in this interface
48
64
  id,
49
65
  className,
50
66
  style,
@@ -69,7 +85,7 @@ export const RadioGroup = forwardRef<HTMLDivElement, RadioGroupProps>(
69
85
  controlMaxWidth="var(--recursica_ui-kit_components_radio-button-item_properties_max-width)"
70
86
  controlMinWidth={undefined}
71
87
  overStyled={overStyled as true}
72
- labelElement="div" // Strictly override. ARIA grouping prohibits interactive radios nested natively inside <label>.
88
+ // Strictly override. ARIA grouping prohibits interactive radios nested natively inside <label>.
73
89
  formLayout={formLayout}
74
90
  labelSize={labelSize}
75
91
  labelAlignment={labelAlignment}
@@ -81,8 +97,6 @@ export const RadioGroup = forwardRef<HTMLDivElement, RadioGroupProps>(
81
97
  assistiveText={assistiveText}
82
98
  assistiveWithIcon={assistiveWithIcon}
83
99
  error={error}
84
- required={required}
85
- withAsterisk={withAsterisk}
86
100
  id={id}
87
101
  readOnly={readOnly && !!readOnlyComponent}
88
102
  readOnlyComponent={readOnlyComponent}
@@ -97,10 +111,10 @@ export const RadioGroup = forwardRef<HTMLDivElement, RadioGroupProps>(
97
111
  {...(sanitizedProps as unknown as MuiRadioGroupProps)}
98
112
  value={value}
99
113
  defaultValue={defaultValue}
114
+ className={styles.groupRoot}
115
+ data-layout={formLayout}
100
116
  >
101
- <div className={styles.groupRoot} data-layout={formLayout}>
102
- {children}
103
- </div>
117
+ {children}
104
118
  </MuiRadioGroup>
105
119
  }
106
120
  />
@@ -3,21 +3,22 @@
3
3
  .root {
4
4
  width: 100%;
5
5
  --form-control-margin-bottom: var(
6
- --recursica_ui-kit_components_read-only-field_variants_layouts_stacked_properties_top-bottom-margin,
7
- 0px
6
+ --recursica_ui-kit_components_read-only-field_variants_layouts_stacked_properties_top-bottom-margin
8
7
  );
9
8
  }
10
9
 
11
10
  .root[data-form-layout="side-by-side"] {
12
11
  --form-control-margin-bottom: var(
13
- --recursica_ui-kit_components_read-only-field_variants_layouts_side-by-side_properties_top-bottom-margin,
14
- 0px
12
+ --recursica_ui-kit_components_read-only-field_variants_layouts_side-by-side_properties_top-bottom-margin
15
13
  );
16
14
  }
17
15
 
18
16
  .text {
17
+ margin: 0;
18
+ padding: 0;
19
19
  display: flex;
20
20
  align-items: center;
21
+ word-break: break-word;
21
22
 
22
23
  font-family: var(
23
24
  --recursica_ui-kit_components_read-only-field_properties_text_font-family
@@ -48,7 +49,6 @@
48
49
  );
49
50
 
50
51
  min-height: var(
51
- --recursica_ui-kit_components_read-only-field_properties_min-height,
52
- 40px
52
+ --recursica_ui-kit_components_read-only-field_properties_min-height
53
53
  );
54
54
  }