@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
@@ -13,6 +13,8 @@ import { type RecursicaFormControlWrapperProps } from "../FormControlWrapper/For
13
13
  import { WithReadOnlyWrapper } from "../ReadOnlyField/WithReadOnlyWrapper";
14
14
  import styles from "./Dropdown.module.css";
15
15
 
16
+ import { type RecursicaDropdownProps as BaseRecursicaDropdownProps } from "@recursica/adapter-common";
17
+
16
18
  export interface RecursicaDropdownProps
17
19
  extends Omit<
18
20
  MuiSelectProps,
@@ -29,14 +31,8 @@ export interface RecursicaDropdownProps
29
31
  | "labelWithEditIcon"
30
32
  | "onLabelEditClick"
31
33
  >,
32
- ReadOnlyControlProps {
33
- /** Internal hook for Selects to override raw layout width properties when necessary */
34
- containerWidth?: React.CSSProperties["width"];
35
- data?: (string | { value: string; label: ReactNode; disabled?: boolean })[];
36
- searchable?: boolean;
37
- clearable?: boolean;
38
- withAsterisk?: boolean;
39
- }
34
+ ReadOnlyControlProps,
35
+ BaseRecursicaDropdownProps {}
40
36
 
41
37
  export type DropdownProps = RecursicaOverStyled<RecursicaDropdownProps>;
42
38
 
@@ -59,6 +55,7 @@ export const Dropdown = forwardRef<HTMLInputElement, DropdownProps>(
59
55
  assistiveWithIcon,
60
56
  error,
61
57
  required,
58
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
62
59
  withAsterisk,
63
60
  id,
64
61
  className,
@@ -70,7 +67,9 @@ export const Dropdown = forwardRef<HTMLInputElement, DropdownProps>(
70
67
  value,
71
68
  defaultValue,
72
69
  data,
70
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
73
71
  searchable, // Not natively supported by basic MUI Select, stubbed
72
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
74
73
  clearable, // Not natively supported by basic MUI Select, stubbed
75
74
  ...rest
76
75
  } = props;
@@ -135,7 +134,6 @@ export const Dropdown = forwardRef<HTMLInputElement, DropdownProps>(
135
134
  assistiveWithIcon={assistiveWithIcon}
136
135
  error={!!error}
137
136
  required={required}
138
- withAsterisk={withAsterisk}
139
137
  id={id}
140
138
  readOnly={readOnly}
141
139
  readOnlyComponent={readOnlyComponent}
@@ -6,28 +6,11 @@ const meta: Meta<typeof FileInput> = {
6
6
  title: "UI-Kit/🚧 FileInput",
7
7
  component: FileInput,
8
8
  tags: ["autodocs"],
9
- parameters: {
10
- controls: {
11
- include: [
12
- "layer",
13
- "withLayer",
14
- "children",
15
- "component",
16
- "variant",
17
- "size",
18
- "icon",
19
- "disabled",
20
- "href",
21
- "onClick",
22
- "onChange",
23
- "value",
24
- "checked",
25
- ],
26
- },
27
- },
9
+ argTypes: {},
28
10
  };
29
11
 
30
12
  export default meta;
13
+
31
14
  type Story = StoryObj<typeof FileInput>;
32
15
 
33
16
  export const Default: Story = {
@@ -6,28 +6,11 @@ const meta: Meta<typeof FileUpload> = {
6
6
  title: "UI-Kit/🚧 FileUpload",
7
7
  component: FileUpload,
8
8
  tags: ["autodocs"],
9
- parameters: {
10
- controls: {
11
- include: [
12
- "layer",
13
- "withLayer",
14
- "children",
15
- "component",
16
- "variant",
17
- "size",
18
- "icon",
19
- "disabled",
20
- "href",
21
- "onClick",
22
- "onChange",
23
- "value",
24
- "checked",
25
- ],
26
- },
27
- },
9
+ argTypes: {},
28
10
  };
29
11
 
30
12
  export default meta;
13
+
31
14
  type Story = StoryObj<typeof FileUpload>;
32
15
 
33
16
  export const Default: Story = {
@@ -1,35 +1,128 @@
1
+ import React from "react";
1
2
  import type { Meta, StoryObj } from "@storybook/react";
2
3
  import { Flex } from "./Flex";
3
- import { ComingSoon } from "@recursica/storybook-template";
4
+ import { Button } from "../Button";
5
+ import { Text } from "../Text/Text";
4
6
 
5
- const meta: Meta<typeof Flex> = {
6
- title: "UI-Kit/🚧 Flex",
7
+ type FlexStoryProps = React.ComponentProps<typeof Flex>;
8
+
9
+ const meta: Meta<FlexStoryProps> = {
10
+ title: "UI-Kit/Flex",
7
11
  component: Flex,
8
12
  tags: ["autodocs"],
9
13
  parameters: {
10
- controls: {
11
- include: [
12
- "layer",
13
- "withLayer",
14
- "children",
15
- "component",
16
- "variant",
17
- "size",
18
- "icon",
19
- "disabled",
20
- "href",
21
- "onClick",
22
- "onChange",
23
- "value",
24
- "checked",
14
+ docs: {
15
+ description: {
16
+ component:
17
+ "Flex is a bare-metal flex container that maps directly to Mantine's Flex component, providing unopinionated control over direction, alignment, and wrapping.",
18
+ },
19
+ },
20
+ },
21
+ args: {
22
+ gap: "rec-default",
23
+ align: "center",
24
+ justify: "flex-start",
25
+ direction: "row",
26
+ wrap: "wrap",
27
+ },
28
+ argTypes: {
29
+ gap: {
30
+ control: "select",
31
+ options: [
32
+ "rec-none",
33
+ "rec-sm",
34
+ "rec-default",
35
+ "rec-md",
36
+ "rec-lg",
37
+ "rec-xl",
38
+ "rec-2xl",
39
+ "xs",
40
+ "sm",
41
+ "md",
42
+ "lg",
43
+ "xl",
44
+ ],
45
+ description: "Gap between elements",
46
+ },
47
+ direction: {
48
+ control: "select",
49
+ options: ["row", "column", "row-reverse", "column-reverse"],
50
+ description: "Flex-direction property",
51
+ },
52
+ align: {
53
+ control: "select",
54
+ options: ["flex-start", "center", "flex-end", "stretch"],
55
+ description: "Align-items property",
56
+ },
57
+ justify: {
58
+ control: "select",
59
+ options: [
60
+ "flex-start",
61
+ "center",
62
+ "flex-end",
63
+ "space-between",
64
+ "space-around",
25
65
  ],
66
+ description: "Justify-content property",
67
+ },
68
+ wrap: {
69
+ control: "select",
70
+ options: ["wrap", "nowrap", "wrap-reverse"],
71
+ description: "Flex-wrap property",
72
+ },
73
+ defaultChecked: {
74
+ table: { disable: true },
75
+ },
76
+ rowGap: {
77
+ table: { disable: true },
78
+ },
79
+ columnGap: {
80
+ table: { disable: true },
26
81
  },
27
82
  },
28
83
  };
29
84
 
30
85
  export default meta;
31
- type Story = StoryObj<typeof Flex>;
86
+
87
+ type Story = StoryObj<FlexStoryProps>;
32
88
 
33
89
  export const Default: Story = {
34
- render: () => <ComingSoon componentName="Flex" />,
90
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
91
+ render: ({ withLayer, layer, ...args }: any) => (
92
+ <Flex {...args}>
93
+ <Button variant="solid">Block A</Button>
94
+ <Button variant="outline">Block B</Button>
95
+ <Text>Text inside Flex</Text>
96
+ </Flex>
97
+ ),
98
+ };
99
+
100
+ export const StaticGapSmallColumn: Story = {
101
+ args: {
102
+ gap: "rec-sm",
103
+ direction: "column",
104
+ },
105
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
106
+ render: ({ withLayer, layer, ...args }: any) => (
107
+ <Flex {...args}>
108
+ <Button variant="solid">Item 1</Button>
109
+ <Button variant="solid">Item 2</Button>
110
+ <Button variant="solid">Item 3</Button>
111
+ </Flex>
112
+ ),
113
+ };
114
+
115
+ export const StaticGapLargeRow: Story = {
116
+ args: {
117
+ gap: "rec-xl",
118
+ direction: "row",
119
+ },
120
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
121
+ render: ({ withLayer, layer, ...args }: any) => (
122
+ <Flex {...args}>
123
+ <Button variant="solid">Item 1</Button>
124
+ <Button variant="solid">Item 2</Button>
125
+ <Button variant="solid">Item 3</Button>
126
+ </Flex>
127
+ ),
35
128
  };
@@ -11,41 +11,27 @@
11
11
  import React, { forwardRef } from "react";
12
12
  import { Box as MUIBox, type BoxProps as MUIBoxProps } from "@mui/material";
13
13
  import {
14
- type RecursicaSpacing,
15
14
  SPACING_MAP,
16
15
  type OmitSx,
17
16
  filterSxProp,
18
17
  } from "../../utils/filterStylingProps";
19
18
 
20
- export interface RecursicaFlexProps {
21
- /**
22
- * Content to render inside the flex container.
23
- */
24
- children?: React.ReactNode;
25
-
26
- /**
27
- * The gap between flex items. Accepts standard MUI spacing or Recursica gap tokens
28
- * (e.g. "rec-md").
29
- */
30
- gap?: string | number | RecursicaSpacing;
31
-
32
- /**
33
- * The vertical gap between flex items when wrapping. Accepts standard MUI spacing
34
- * or Recursica gap tokens.
35
- */
36
- rowGap?: string | number | RecursicaSpacing;
37
-
38
- /**
39
- * The horizontal gap between flex items when wrapping. Accepts standard MUI spacing
40
- * or Recursica gap tokens.
41
- */
42
- columnGap?: string | number | RecursicaSpacing;
43
- }
19
+ import { type RecursicaFlexProps } from "@recursica/adapter-common";
44
20
 
45
21
  export type FlexProps = OmitSx<MUIBoxProps & RecursicaFlexProps>;
46
22
 
47
23
  export const Flex = forwardRef<HTMLDivElement, FlexProps>(function Flex(
48
- { children, gap = "rec-default", rowGap, columnGap, ...rest },
24
+ {
25
+ children,
26
+ gap = "rec-default",
27
+ rowGap,
28
+ columnGap,
29
+ direction,
30
+ align,
31
+ justify,
32
+ wrap,
33
+ ...rest
34
+ },
49
35
  ref,
50
36
  ) {
51
37
  const safeProps = filterSxProp(rest as Record<string, unknown>);
@@ -70,6 +56,10 @@ export const Flex = forwardRef<HTMLDivElement, FlexProps>(function Flex(
70
56
  gap={resolvedGap}
71
57
  rowGap={resolvedRowGap}
72
58
  columnGap={resolvedColumnGap}
59
+ flexDirection={direction}
60
+ alignItems={align}
61
+ justifyContent={justify}
62
+ flexWrap={wrap}
73
63
  >
74
64
  {children}
75
65
  </MUIBox>
@@ -1,5 +1,3 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import React from "react";
3
1
  import type { Meta, StoryObj } from "@storybook/react";
4
2
  import { FormControlLayout } from "./FormControlLayout";
5
3
  import { Switch } from "../Switch/Switch";
@@ -15,9 +13,6 @@ const meta: Meta<typeof FormControlLayout> = {
15
13
  "A layout component used to correctly position form inputs alongside their labels.\n\n**When to use this:**\nTypically, you should use `FormControlWrapper` instead, which uses this component under the hood to handle layout automatically.\n\nHowever, this component is useful when you need to align standalone inputs (like a `Switch` or `Checkbox` without a label) so they perfectly match the spacing and alignment of your other form fields in a `side-by-side` layout.",
16
14
  },
17
15
  },
18
- controls: {
19
- include: ["formLayout", "labelSize"],
20
- },
21
16
  },
22
17
  tags: ["autodocs"],
23
18
  argTypes: {
@@ -59,7 +54,6 @@ export const Default: Story = {
59
54
  ),
60
55
  children: <Switch label="Input area content" />,
61
56
  },
62
- render: ({ ...args }: any) => <FormControlLayout {...args} />,
63
57
  };
64
58
 
65
59
  /**
@@ -72,7 +66,6 @@ export const StackedLayout: Story = {
72
66
  labelSize: "default",
73
67
  children: <Switch label="Flush stacked switch" />,
74
68
  },
75
- render: ({ ...args }: any) => <FormControlLayout {...args} />,
76
69
  };
77
70
 
78
71
  /**
@@ -86,5 +79,4 @@ export const SideBySideLayout: Story = {
86
79
  labelSize: "default",
87
80
  children: <Switch label="Offset switch aligning with grid" />,
88
81
  },
89
- render: ({ ...args }: any) => <FormControlLayout {...args} />,
90
82
  };
@@ -2,15 +2,12 @@ import React from "react";
2
2
  import { filterStylingProps } from "../../utils/filterStylingProps";
3
3
  import styles from "./FormControlLayout.module.css";
4
4
 
5
+ import { type RecursicaFormControlLayoutProps } from "@recursica/adapter-common";
6
+
5
7
  export interface FormControlLayoutProps
6
- extends React.HTMLAttributes<HTMLDivElement> {
8
+ extends React.HTMLAttributes<HTMLDivElement>,
9
+ RecursicaFormControlLayoutProps {
7
10
  overStyled?: boolean;
8
- formLayout?: "stacked" | "side-by-side";
9
- labelSize?: "small" | "default" | "md";
10
- controlMaxWidth?: string;
11
- controlMinWidth?: string;
12
- leftSection?: React.ReactNode;
13
- children: React.ReactNode;
14
11
  }
15
12
 
16
13
  export const FormControlLayout = React.forwardRef<
@@ -21,9 +18,12 @@ export const FormControlLayout = React.forwardRef<
21
18
  overStyled = false,
22
19
  formLayout = "stacked",
23
20
  labelSize = "default",
21
+ controlMaxWidth,
22
+ controlMinWidth,
24
23
  leftSection,
25
24
  className,
26
25
  children,
26
+ style,
27
27
  ...rest
28
28
  } = props;
29
29
 
@@ -34,6 +34,17 @@ export const FormControlLayout = React.forwardRef<
34
34
  ref={ref}
35
35
  className={className ? `${styles.root} ${className}` : styles.root}
36
36
  data-form-layout={formLayout}
37
+ style={
38
+ {
39
+ ...style,
40
+ ...(controlMaxWidth
41
+ ? { "--form-control-max-width": controlMaxWidth }
42
+ : {}),
43
+ ...(controlMinWidth
44
+ ? { "--form-control-min-width": controlMinWidth }
45
+ : {}),
46
+ } as React.CSSProperties
47
+ }
37
48
  {...sanitizedProps}
38
49
  >
39
50
  {/*
@@ -74,21 +74,12 @@
74
74
  color: var(--form-field-text-valued);
75
75
  }
76
76
 
77
- .inputContainer {
77
+ .inputSection {
78
78
  display: flex;
79
79
  flex-direction: column;
80
80
  flex: 1;
81
81
  min-width: 0;
82
-
83
- /* Constrain the input natively to custom properties */
84
- max-width: var(--form-control-max-width);
85
- min-width: var(--form-control-min-width);
86
-
87
- /* Hook to allow child components to add margin below themselves dynamically
88
- via CSS variables (e.g. padding beneath TextField before AssistiveText renders) */
89
- margin-bottom: var(--form-control-margin-bottom);
90
82
  }
91
-
92
83
  .assistive {
93
84
  margin-top: var(
94
85
  --recursica_ui-kit_globals_form_properties_label-field-gap-vertical
@@ -1,5 +1,3 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import React from "react";
3
1
  import type { Meta, StoryObj } from "@storybook/react";
4
2
  import {
5
3
  FormControlWrapper,
@@ -18,23 +16,9 @@ const meta: Meta<WrapperStoryProps> = {
18
16
  docs: {
19
17
  description: {
20
18
  component:
21
- "The `FormControlWrapper` is the ultimate structural replacement for built-in wrapper layouts. By centralizing label tracking, error rendering, ARIA generation, and grid layouts natively inside this single component, we achieve pixel-perfect layout compliance while retaining MUI's internal `useFormControl` Context natively.\n\n### Usage with Naked Primitives\nThis component wraps 'naked' elements directly. The demonstration stories below utilize `<TextField>` as a native display vehicle, since `<TextField>` natively pipes all its properties structurally back into this wrapper.",
19
+ "The `FormControlWrapper` is the ultimate structural replacement for Mantine's built-in `Input.Wrapper`. By abandoning Mantine's opinionated wrappers entirely, we centralize all label tracking, error rendering, ARIA generation, and grid layouts natively inside this single component.\n\n### Usage with Naked Primitives\nThis component wraps 'naked' elements like `<Input>` directly. The demonstration stories below utilize `<TextField>` as a native display vehicle, since `<TextField>` natively pipes all its properties structurally back into this wrapper.",
22
20
  },
23
21
  },
24
- controls: {
25
- include: [
26
- "error",
27
- "assistiveText",
28
- "assistiveWithIcon",
29
- "required",
30
- "formLayout",
31
- "labelSize",
32
- "labelAlignment",
33
- "labelOptionalText",
34
- "labelWithEditIcon",
35
- "labelActionArea",
36
- ],
37
- },
38
22
  },
39
23
  argTypes: {
40
24
  ...formControlArgTypes,
@@ -61,10 +45,15 @@ export default meta;
61
45
 
62
46
  type Story = StoryObj<WrapperStoryProps>;
63
47
 
64
- const renderWithTextField = ({ ...args }: any) => (
65
- // We explicitly cast args to 'any' here because MUI wrapper defines Wrapper anatomy,
48
+ const renderWithTextField = (args: WrapperStoryProps) => (
49
+ // We explicitly cast args to 'any' here because Mantine enforces strict anatomy types for
50
+ // 'classNames', 'styles', and 'attributes'. WrapperStoryProps defines these for the Wrapper anatomy,
66
51
  // but TextField expects them for the full TextInput anatomy, causing a TS intersection mismatch.
67
- <TextField placeholder="Form Control primitive mapped..." {...args} />
52
+ <TextField
53
+ placeholder="Form Control primitive mapped..."
54
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
55
+ {...(args as any)}
56
+ />
68
57
  );
69
58
 
70
59
  export const Default: Story = {
@@ -119,7 +108,8 @@ export const NativeChildrenDirectly: Story = {
119
108
  formLayout: "side-by-side",
120
109
  assistiveText: "This wraps a raw HTML input tag mapping correctly.",
121
110
  },
122
- render: ({ ...args }: any) => (
111
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
112
+ render: ({ withLayer, layer, ...args }: any) => (
123
113
  <div
124
114
  style={{
125
115
  display: "flex",
@@ -127,6 +117,7 @@ export const NativeChildrenDirectly: Story = {
127
117
  alignItems: "center",
128
118
  }}
129
119
  >
120
+ {/* We can cleanly wrap even un-styled HTML primitives! */}
130
121
  <FormControlWrapper {...args}>
131
122
  <input
132
123
  type="checkbox"
@@ -6,29 +6,14 @@ import { AssistiveElement } from "../AssistiveElement/AssistiveElement";
6
6
  import { FormControlLayout } from "../FormControlLayout/FormControlLayout";
7
7
  import styles from "./FormControlWrapper.module.css";
8
8
 
9
+ import { type RecursicaFormControlWrapperProps as BaseRecursicaFormControlWrapperProps } from "@recursica/adapter-common";
10
+
9
11
  export interface RecursicaFormControlWrapperProps
10
- extends Omit<FormControlProps, "margin" | "variant" | "size" | "color"> {
12
+ extends Omit<FormControlProps, "margin" | "variant" | "size" | "color">,
13
+ BaseRecursicaFormControlWrapperProps {
11
14
  overStyled?: boolean;
12
15
  label?: React.ReactNode;
13
- /** Primary assistive description bound natively to the component footer. Maps to MUI's 'helperText' dynamically. */
14
- assistiveText?: React.ReactNode;
15
- /** Secondary parameter fallback identical to assistiveText to match MUI / Mantine native APIs safely. */
16
- description?: React.ReactNode;
17
- /** Fallback mapping for MUI's native helperText */
18
- helperText?: React.ReactNode;
19
- assistiveWithIcon?: boolean;
20
-
21
- formLayout?: "stacked" | "side-by-side";
22
- labelSize?: "small" | "default" | "md";
23
- labelAlignment?: "left" | "right"; // Currently functionally maps to left automatically natively
24
- labelOptionalText?: React.ReactNode | true;
25
- labelWithEditIcon?: boolean;
26
- onLabelEditClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
27
- labelActionArea?: React.ReactNode;
28
-
29
- /** Allows layout mapping to limit the input's bounding box without impacting the label */
30
- controlMaxWidth?: string;
31
- controlMinWidth?: string;
16
+ focused?: boolean;
32
17
  }
33
18
 
34
19
  export type FormControlWrapperProps = RecursicaFormControlWrapperProps;
@@ -95,11 +80,16 @@ export const FormControlWrapper = React.forwardRef<
95
80
  focused={focused}
96
81
  className={className ? `${styles.root} ${className}` : styles.root}
97
82
  style={style}
83
+ data-error={error ? "true" : undefined}
84
+ data-disabled={disabled ? "true" : undefined}
85
+ data-focused={focused ? "true" : undefined}
98
86
  {...(sanitizedProps as FormControlProps)}
99
87
  >
100
88
  <FormControlLayout
101
89
  formLayout={formLayout}
102
90
  labelSize={labelSize}
91
+ controlMaxWidth={controlMaxWidth}
92
+ controlMinWidth={controlMinWidth}
103
93
  leftSection={
104
94
  label && (
105
95
  <Label
@@ -116,15 +106,7 @@ export const FormControlWrapper = React.forwardRef<
116
106
  )
117
107
  }
118
108
  >
119
- <div
120
- className={styles.inputContainer}
121
- style={
122
- {
123
- "--form-control-max-width": controlMaxWidth,
124
- "--form-control-min-width": controlMinWidth,
125
- } as React.CSSProperties
126
- }
127
- >
109
+ <div className={styles.inputSection}>
128
110
  {/* Natively wrap children into flex box */}
129
111
  {mappedChildren}
130
112
 
@@ -134,7 +116,6 @@ export const FormControlWrapper = React.forwardRef<
134
116
  id={descriptionId}
135
117
  assistiveVariant={error ? "error" : "help"}
136
118
  assistiveWithIcon={assistiveWithIcon}
137
- className={styles.assistive}
138
119
  >
139
120
  {error && typeof error === "string" ? error : finalAssistiveText}
140
121
  </AssistiveElement>
@@ -17,36 +17,12 @@ const meta: Meta<GroupStoryProps> = {
17
17
  "Group is a flex horizontal layout container that maps directly to Mantine's Group component allowing safe layout property passing.",
18
18
  },
19
19
  },
20
- controls: {
21
- include: [
22
- "layer",
23
- "withLayer",
24
- "gap",
25
- "alignItems",
26
- "justifyContent",
27
- "flexWrap",
28
- "defaultChecked",
29
- "rowGap",
30
- "columnGap",
31
- "children",
32
- "component",
33
- "variant",
34
- "size",
35
- "icon",
36
- "disabled",
37
- "href",
38
- "onClick",
39
- "onChange",
40
- "value",
41
- "checked",
42
- ],
43
- },
44
20
  },
45
21
  args: {
46
22
  gap: "rec-default",
47
- alignItems: "center",
48
- justifyContent: "flex-start",
49
- flexWrap: "wrap",
23
+ align: "center",
24
+ justify: "flex-start",
25
+ wrap: "wrap",
50
26
  },
51
27
  argTypes: {
52
28
  gap: {
@@ -67,12 +43,12 @@ const meta: Meta<GroupStoryProps> = {
67
43
  ],
68
44
  description: "Gap between elements",
69
45
  },
70
- alignItems: {
46
+ align: {
71
47
  control: "select",
72
48
  options: ["flex-start", "center", "flex-end", "stretch"],
73
49
  description: "Align-items property",
74
50
  },
75
- justifyContent: {
51
+ justify: {
76
52
  control: "select",
77
53
  options: [
78
54
  "flex-start",
@@ -83,7 +59,7 @@ const meta: Meta<GroupStoryProps> = {
83
59
  ],
84
60
  description: "Justify-content property",
85
61
  },
86
- flexWrap: {
62
+ wrap: {
87
63
  control: "select",
88
64
  options: ["wrap", "nowrap", "wrap-reverse"],
89
65
  description: "Flex-wrap property",