@recursica/mantine-adapter 0.10.0 → 0.11.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 (73) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/mantine-adapter.cjs +1 -1
  3. package/dist/mantine-adapter.cjs.map +1 -1
  4. package/dist/mantine-adapter.css +1 -1
  5. package/dist/mantine-adapter.js +1482 -1316
  6. package/dist/mantine-adapter.js.map +1 -1
  7. package/dist/src/components/Checkbox/Checkbox.d.ts +3 -1
  8. package/dist/src/components/FormControlLayout/FormControlLayout.d.ts +21 -0
  9. package/dist/src/components/FormControlWrapper/FormControlWrapper.d.ts +2 -0
  10. package/dist/src/components/Label/Label.d.ts +1 -3
  11. package/dist/src/components/Radio/Radio.d.ts +3 -1
  12. package/dist/src/components/ReadOnlyField/ReadOnlyBooleanField.d.ts +5 -0
  13. package/dist/src/components/ReadOnlyField/ReadOnlyField.d.ts +6 -6
  14. package/dist/src/components/ReadOnlyField/ReadOnlySwitchField.d.ts +5 -0
  15. package/dist/src/components/ReadOnlyField/WithReadOnlyWrapper.d.ts +7 -1
  16. package/dist/src/components/ReadOnlyField/index.d.ts +2 -0
  17. package/dist/src/components/Switch/Switch.d.ts +3 -1
  18. package/dist/src/components/index.d.ts +1 -0
  19. package/dist/src/utils/RequireAccessibleLabel.d.ts +18 -0
  20. package/package.json +1 -1
  21. package/src/components/AssistiveElement/AssistiveElement.stories.tsx +6 -4
  22. package/src/components/Avatar/Avatar.stories.tsx +10 -5
  23. package/src/components/Badge/Badge.stories.tsx +2 -1
  24. package/src/components/Button/Button.stories.tsx +2 -4
  25. package/src/components/Checkbox/Checkbox.module.css +1 -1
  26. package/src/components/Checkbox/Checkbox.stories.tsx +24 -3
  27. package/src/components/Checkbox/Checkbox.tsx +72 -15
  28. package/src/components/Checkbox/CheckboxGroup.stories.tsx +26 -25
  29. package/src/components/Checkbox/CheckboxGroup.tsx +38 -58
  30. package/src/components/Container/Container.stories.tsx +6 -3
  31. package/src/components/DatePicker/DatePicker.stories.tsx +1 -5
  32. package/src/components/Dropdown/Dropdown.stories.tsx +0 -3
  33. package/src/components/Dropdown/Dropdown.tsx +6 -5
  34. package/src/components/FileInput/FileInput.stories.tsx +1 -5
  35. package/src/components/FileUpload/FileUpload.stories.tsx +1 -5
  36. package/src/components/Flex/Flex.stories.tsx +6 -3
  37. package/src/components/FormControlLayout/FormControlLayout.module.css +80 -0
  38. package/src/components/FormControlLayout/FormControlLayout.stories.tsx +82 -0
  39. package/src/components/FormControlLayout/FormControlLayout.tsx +78 -0
  40. package/src/components/FormControlWrapper/FormControlWrapper.module.css +3 -41
  41. package/src/components/FormControlWrapper/FormControlWrapper.stories.tsx +18 -5
  42. package/src/components/FormControlWrapper/FormControlWrapper.tsx +28 -41
  43. package/src/components/Group/Group.stories.tsx +6 -3
  44. package/src/components/Label/Label.module.css +0 -37
  45. package/src/components/Label/Label.stories.tsx +8 -9
  46. package/src/components/Label/Label.tsx +2 -7
  47. package/src/components/Loader/Loader.stories.tsx +10 -5
  48. package/src/components/NumberInput/NumberInput.stories.tsx +1 -5
  49. package/src/components/Radio/Radio.stories.tsx +22 -8
  50. package/src/components/Radio/Radio.tsx +134 -75
  51. package/src/components/Radio/RadioGroup.stories.tsx +26 -7
  52. package/src/components/Radio/RadioGroup.tsx +37 -57
  53. package/src/components/ReadOnlyField/ReadOnlyBooleanField.tsx +21 -0
  54. package/src/components/ReadOnlyField/ReadOnlyField.stories.tsx +43 -0
  55. package/src/components/ReadOnlyField/ReadOnlyField.tsx +30 -3
  56. package/src/components/ReadOnlyField/ReadOnlySwitchField.tsx +21 -0
  57. package/src/components/ReadOnlyField/ReadOnlyTextField.tsx +3 -1
  58. package/src/components/ReadOnlyField/WithReadOnlyWrapper.tsx +10 -2
  59. package/src/components/ReadOnlyField/index.ts +2 -0
  60. package/src/components/SegmentedControl/SegmentedControl.stories.tsx +1 -5
  61. package/src/components/Slider/Slider.stories.tsx +1 -5
  62. package/src/components/Stack/Stack.stories.tsx +6 -3
  63. package/src/components/Switch/Switch.module.css +2 -10
  64. package/src/components/Switch/Switch.stories.tsx +42 -27
  65. package/src/components/Switch/Switch.tsx +138 -89
  66. package/src/components/Switch/SwitchGroup.stories.tsx +28 -8
  67. package/src/components/Switch/SwitchGroup.tsx +37 -57
  68. package/src/components/TextArea/TextArea.stories.tsx +0 -3
  69. package/src/components/TextArea/TextArea.tsx +6 -5
  70. package/src/components/TextField/TextField.tsx +6 -5
  71. package/src/components/TimePicker/TimePicker.stories.tsx +1 -5
  72. package/src/components/index.ts +1 -0
  73. package/src/utils/RequireAccessibleLabel.ts +12 -0
@@ -1,14 +1,17 @@
1
1
  import React, { forwardRef, useId } from "react";
2
- import { type InputWrapperProps, Box } from "@mantine/core";
2
+ import { type InputWrapperProps } from "@mantine/core";
3
3
  import {
4
4
  filterStylingProps,
5
5
  type RecursicaOverStyled,
6
6
  } from "../../utils/filterStylingProps";
7
7
  import { Label, type RecursicaLabelProps } from "../Label/Label";
8
+ import { FormControlLayout } from "../FormControlLayout/FormControlLayout";
8
9
  import { AssistiveElement } from "../AssistiveElement/AssistiveElement";
9
10
  import styles from "./FormControlWrapper.module.css";
10
11
 
11
12
  export interface RecursicaFormControlWrapperProps extends RecursicaLabelProps {
13
+ /** Overall structural flow mapping the Form Control natively cascading down to Label and Input logic. */
14
+ formLayout?: "stacked" | "side-by-side";
12
15
  /** Securely replaces standard Mantine descriptions safely providing standard Assistive properties. */
13
16
  assistiveText?: React.ReactNode;
14
17
  /** Explicit toggle to suppress the Info icon rendering natively alongside the assistiveText. Defaults to true. */
@@ -93,51 +96,35 @@ export const FormControlWrapper = forwardRef<
93
96
  )
94
97
  : children;
95
98
 
99
+ const labelNode = label ? (
100
+ <Label
101
+ id={id} // Directly binds ARIA references down
102
+ labelAlignment={labelAlignment}
103
+ labelOptionalText={labelOptionalText}
104
+ labelWithEditIcon={labelWithEditIcon}
105
+ labelActionArea={labelActionArea}
106
+ onLabelEditClick={onLabelEditClick}
107
+ required={withAsterisk ?? required}
108
+ // Manually propagate relevant structural logic if underlying groups dictate Label to act as generic wrapper
109
+ {...(labelElement === "div" ? { as: "div" } : {})}
110
+ >
111
+ {label}
112
+ </Label>
113
+ ) : undefined;
114
+
96
115
  return (
97
- <Box
116
+ <FormControlLayout
98
117
  ref={ref}
99
118
  className={finalClass}
100
- data-form-layout={formLayout || "stacked"}
101
- data-form-alignment={labelAlignment || "left"}
102
- style={
103
- {
104
- ...((restRecord.style as React.CSSProperties) || {}),
105
- ...(controlMaxWidth
106
- ? { "--form-control-max-width": controlMaxWidth }
107
- : {}),
108
- ...(controlMinWidth
109
- ? { "--form-control-min-width": controlMinWidth }
110
- : {}),
111
- } as React.CSSProperties
112
- }
119
+ formLayout={formLayout}
120
+ labelSize={labelSize}
121
+ controlMaxWidth={controlMaxWidth}
122
+ controlMinWidth={controlMinWidth}
123
+ leftSection={labelNode}
113
124
  {...restRecord}
114
125
  >
115
- {/*
116
- Section 1: The Native Custom Label
117
- Bypasses Mantine's built-in Input.Wrapper Label entirely in favor of explicit Recursica formatting.
118
- */}
119
- {label && (
120
- <div className={styles.labelSection}>
121
- <Label
122
- id={id} // Directly binds ARIA references down
123
- formLayout={formLayout}
124
- labelSize={labelSize}
125
- labelAlignment={labelAlignment}
126
- labelOptionalText={labelOptionalText}
127
- labelWithEditIcon={labelWithEditIcon}
128
- labelActionArea={labelActionArea}
129
- onLabelEditClick={onLabelEditClick}
130
- required={withAsterisk ?? required}
131
- // Manually propagate relevant structural logic if underlying groups dictate Label to act as generic wrapper
132
- {...(labelElement === "div" ? { as: "div" } : {})}
133
- >
134
- {label}
135
- </Label>
136
- </div>
137
- )}
138
-
139
126
  {/*
140
- Section 2: The Core Input Wrapper & Controls
127
+ The Core Input Wrapper & Controls
141
128
  Nakedly inject the actual field elements alongside natively bridged Assistive components mapped dynamically.
142
129
  */}
143
130
  <div className={styles.inputSection}>
@@ -163,7 +150,7 @@ export const FormControlWrapper = forwardRef<
163
150
  </AssistiveElement>
164
151
  )}
165
152
  </div>
166
- </Box>
153
+ </FormControlLayout>
167
154
  );
168
155
  });
169
156
 
@@ -81,7 +81,8 @@ export default meta;
81
81
  type Story = StoryObj<GroupStoryProps>;
82
82
 
83
83
  export const Default: Story = {
84
- render: (args) => (
84
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
85
+ render: ({ withLayer, layer, ...args }: any) => (
85
86
  <Group {...args}>
86
87
  <Button variant="solid">Primary</Button>
87
88
  <Button variant="outline">Secondary</Button>
@@ -94,7 +95,8 @@ export const StaticGapSmall: Story = {
94
95
  args: {
95
96
  gap: "rec-sm",
96
97
  },
97
- render: (args) => (
98
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
99
+ render: ({ withLayer, layer, ...args }: any) => (
98
100
  <Group {...args}>
99
101
  <Button variant="solid">Item 1</Button>
100
102
  <Button variant="solid">Item 2</Button>
@@ -107,7 +109,8 @@ export const StaticGapLarge: Story = {
107
109
  args: {
108
110
  gap: "rec-xl",
109
111
  },
110
- render: (args) => (
112
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
113
+ render: ({ withLayer, layer, ...args }: any) => (
111
114
  <Group {...args}>
112
115
  <Button variant="solid">Item 1</Button>
113
116
  <Button variant="solid">Item 2</Button>
@@ -23,43 +23,6 @@
23
23
  text-align: right;
24
24
  }
25
25
 
26
- .root[data-layout="stacked"] {
27
- padding-bottom: var(
28
- --recursica_ui-kit_components_label_variants_layouts_stacked_properties_bottom-padding
29
- );
30
- min-height: var(
31
- --recursica_ui-kit_components_label_variants_layouts_stacked_properties_min-height
32
- );
33
- }
34
-
35
- .root[data-layout="side-by-side"] {
36
- padding-top: var(
37
- --recursica_ui-kit_components_label_variants_layouts_side-by-side_properties_vertical-padding
38
- );
39
- padding-bottom: var(
40
- --recursica_ui-kit_components_label_variants_layouts_side-by-side_properties_vertical-padding
41
- );
42
- min-height: var(
43
- --recursica_ui-kit_components_label_variants_layouts_side-by-side_properties_min-height
44
- );
45
- /* In side-by-side, the label exists to the left of its input, so we use the gutter gap here. */
46
- margin-right: var(
47
- --recursica_ui-kit_components_label_variants_layouts_side-by-side_properties_gutter
48
- );
49
- }
50
-
51
- .root[data-layout="side-by-side"][data-size="default"] {
52
- width: var(
53
- --recursica_ui-kit_components_label_variants_layouts_side-by-side_variants_sizes_default_properties_width
54
- );
55
- }
56
-
57
- .root[data-layout="side-by-side"][data-size="small"] {
58
- width: var(
59
- --recursica_ui-kit_components_label_variants_layouts_side-by-side_variants_sizes_small_properties_width
60
- );
61
- }
62
-
63
26
  .labelText {
64
27
  order: 1;
65
28
  font-family: var(
@@ -40,7 +40,7 @@ const renderWithTextField = ({ children, ...args }: LabelStoryProps) => (
40
40
  export const Default: Story = {
41
41
  args: {
42
42
  children: "Dynamic Label (Controls)",
43
- formLayout: "stacked",
43
+
44
44
  labelSize: "default",
45
45
  labelAlignment: "left",
46
46
  required: false,
@@ -53,7 +53,6 @@ export const Default: Story = {
53
53
  export const StackedDefault: Story = {
54
54
  args: {
55
55
  children: "Email Address",
56
- formLayout: "stacked",
57
56
  },
58
57
  render: renderWithTextField,
59
58
  };
@@ -61,7 +60,7 @@ export const StackedDefault: Story = {
61
60
  export const StackedRequired: Story = {
62
61
  args: {
63
62
  children: "Primary Network Node",
64
- formLayout: "stacked",
63
+
65
64
  required: true,
66
65
  },
67
66
  render: renderWithTextField,
@@ -70,7 +69,7 @@ export const StackedRequired: Story = {
70
69
  export const StackedWithEditIcon: Story = {
71
70
  args: {
72
71
  children: "Environment Variables",
73
- formLayout: "stacked",
72
+
74
73
  labelWithEditIcon: true,
75
74
  },
76
75
  render: renderWithTextField,
@@ -79,7 +78,7 @@ export const StackedWithEditIcon: Story = {
79
78
  export const SideBySideDefault: Story = {
80
79
  args: {
81
80
  children: "Status",
82
- formLayout: "side-by-side",
81
+
83
82
  labelSize: "default",
84
83
  },
85
84
  render: renderWithTextField,
@@ -88,7 +87,7 @@ export const SideBySideDefault: Story = {
88
87
  export const RequiredSuppressesOptionalText: Story = {
89
88
  args: {
90
89
  children: "Full Name",
91
- formLayout: "stacked",
90
+
92
91
  required: true,
93
92
  labelOptionalText: "This should not render",
94
93
  },
@@ -98,7 +97,7 @@ export const RequiredSuppressesOptionalText: Story = {
98
97
  export const BooleanOptionalText: Story = {
99
98
  args: {
100
99
  children: "Middle Initial",
101
- formLayout: "side-by-side",
100
+
102
101
  labelOptionalText: true,
103
102
  },
104
103
  render: renderWithTextField,
@@ -107,7 +106,7 @@ export const BooleanOptionalText: Story = {
107
106
  export const WithEditIcon: Story = {
108
107
  args: {
109
108
  children: "Shipping Address",
110
- formLayout: "side-by-side",
109
+
111
110
  labelWithEditIcon: true,
112
111
  },
113
112
  render: renderWithTextField,
@@ -116,7 +115,7 @@ export const WithEditIcon: Story = {
116
115
  export const LayerOneSideBySide: Story = {
117
116
  args: {
118
117
  children: "Configuration",
119
- formLayout: "side-by-side",
118
+
120
119
  labelWithEditIcon: true,
121
120
  },
122
121
  render: renderWithTextField,
@@ -7,10 +7,8 @@ import {
7
7
  import styles from "./Label.module.css";
8
8
 
9
9
  export interface RecursicaLabelProps {
10
- /** Overall structural flow mapping the Form Control natively cascading down to Label and Input logic. */
11
- formLayout?: "stacked" | "side-by-side";
12
10
  /** Specifies the sizing metrics natively mapping the Label boundaries. */
13
- labelSize?: "default" | "small";
11
+ labelSize?: "default" | "small" | "md";
14
12
  /** Overall alignment directive for the label strings natively forcing Left/Right justification. */
15
13
  labelAlignment?: "left" | "right";
16
14
  /** Injects an indicator text block alongside the label. Can be boolean (`true` maps to '(Optional)') or custom React nodes. */
@@ -29,7 +27,6 @@ export type LabelProps = RecursicaOverStyled<
29
27
 
30
28
  export const Label = forwardRef<HTMLLabelElement, LabelProps>(function Label(
31
29
  {
32
- formLayout = "stacked",
33
30
  labelSize = "default",
34
31
  labelAlignment,
35
32
  required = false,
@@ -43,8 +40,7 @@ export const Label = forwardRef<HTMLLabelElement, LabelProps>(function Label(
43
40
  },
44
41
  ref,
45
42
  ) {
46
- const resolvedAlignment =
47
- labelAlignment || (formLayout === "side-by-side" ? "right" : "left");
43
+ const resolvedAlignment = labelAlignment || "left";
48
44
 
49
45
  let resolvedOptionalText: React.ReactNode | undefined;
50
46
  if (!required) {
@@ -85,7 +81,6 @@ export const Label = forwardRef<HTMLLabelElement, LabelProps>(function Label(
85
81
  ref={ref}
86
82
  className={finalClass}
87
83
  classNames={mergedClassNames}
88
- data-layout={formLayout}
89
84
  data-size={labelSize}
90
85
  data-alignment={resolvedAlignment}
91
86
  required={required && !labelWithEditIcon}
@@ -54,7 +54,8 @@ export const Default: Story = {
54
54
  size: "default",
55
55
  layer: 0,
56
56
  },
57
- render: ({ layer, ...args }) => (
57
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
58
+ render: ({ withLayer, layer, ...args }: any) => (
58
59
  <Layer layer={layer ?? 0} style={{ padding: "24px" }}>
59
60
  <Loader {...args} />
60
61
  </Layer>
@@ -66,7 +67,8 @@ export const StaticOvalDefault: Story = {
66
67
  variant: "oval",
67
68
  size: "default",
68
69
  },
69
- render: (args) => <Loader {...args} />,
70
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
71
+ render: ({ withLayer, layer, ...args }: any) => <Loader {...args} />,
70
72
  };
71
73
 
72
74
  export const StaticBarsLarge: Story = {
@@ -74,7 +76,8 @@ export const StaticBarsLarge: Story = {
74
76
  variant: "bars",
75
77
  size: "large",
76
78
  },
77
- render: (args) => <Loader {...args} />,
79
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
80
+ render: ({ withLayer, layer, ...args }: any) => <Loader {...args} />,
78
81
  };
79
82
 
80
83
  export const StaticDotsSmall: Story = {
@@ -82,7 +85,8 @@ export const StaticDotsSmall: Story = {
82
85
  variant: "dots",
83
86
  size: "sm",
84
87
  },
85
- render: (args) => <Loader {...args} />,
88
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
89
+ render: ({ withLayer, layer, ...args }: any) => <Loader {...args} />,
86
90
  };
87
91
 
88
92
  export const LayerTwoOval: Story = {
@@ -90,7 +94,8 @@ export const LayerTwoOval: Story = {
90
94
  variant: "oval",
91
95
  size: "default",
92
96
  },
93
- render: (args) => (
97
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
98
+ render: ({ withLayer, layer, ...args }: any) => (
94
99
  <Layer layer={2} style={{ padding: "24px" }}>
95
100
  <Loader {...args} />
96
101
  </Layer>
@@ -6,11 +6,7 @@ const meta: Meta<typeof NumberInput> = {
6
6
  title: "UI-Kit/🚧 NumberInput",
7
7
  component: NumberInput,
8
8
  tags: ["autodocs"],
9
- argTypes: {
10
- disabled: {
11
- control: "boolean",
12
- },
13
- },
9
+ argTypes: {},
14
10
  };
15
11
 
16
12
  export default meta;
@@ -18,9 +18,6 @@ The \`Radio\` component represents a fundamental standalone boolean selection no
18
18
  },
19
19
  },
20
20
  argTypes: {
21
- disabled: {
22
- control: "boolean",
23
- },
24
21
  checked: {
25
22
  control: "boolean",
26
23
  },
@@ -33,6 +30,8 @@ The \`Radio\` component represents a fundamental standalone boolean selection no
33
30
  defaultChecked: {
34
31
  control: "boolean",
35
32
  },
33
+ controlMaxWidth: { table: { disable: true } },
34
+ controlMinWidth: { table: { disable: true } },
36
35
  },
37
36
  };
38
37
 
@@ -46,6 +45,13 @@ export const Default: Story = {
46
45
  },
47
46
  };
48
47
 
48
+ export const SideBySideLayout: Story = {
49
+ args: {
50
+ label: "Opt-in form alignment",
51
+ formLayout: "side-by-side",
52
+ },
53
+ };
54
+
49
55
  export const CheckedState: Story = {
50
56
  args: {
51
57
  ...Default.args,
@@ -68,9 +74,17 @@ export const DisabledChecked: Story = {
68
74
  },
69
75
  };
70
76
 
71
- export const ReadOnlyStandalone: Story = {
72
- args: {
73
- readOnly: true,
74
- readOnlyComponent: <span>Locked Native Value</span>,
75
- },
77
+ export const ReadOnly: Story = {
78
+ args: {},
79
+ render: () => (
80
+ <div
81
+ style={{
82
+ display: "flex",
83
+ flexDirection: "column",
84
+ gap: "24px",
85
+ }}
86
+ >
87
+ <Radio label="Account Type" defaultChecked readOnly />
88
+ </div>
89
+ ),
76
90
  };
@@ -9,6 +9,12 @@ import {
9
9
  filterStylingProps,
10
10
  type RecursicaOverStyled,
11
11
  } from "../../utils/filterStylingProps";
12
+ import { type RequireAccessibleLabel } from "../../utils/RequireAccessibleLabel";
13
+ import {
14
+ FormControlLayout,
15
+ type FormControlLayoutProps,
16
+ } from "../FormControlLayout/FormControlLayout";
17
+
12
18
  import styles from "./Radio.module.css";
13
19
 
14
20
  const RadioIcon: React.FC<{
@@ -26,11 +32,17 @@ const RadioIcon: React.FC<{
26
32
  </svg>
27
33
  );
28
34
 
29
- export type RecursicaRadioProps = Omit<
30
- MantineRadioProps,
31
- "size" | "color" | "radius" | "iconColor" | "variant"
32
- > &
33
- ReadOnlyControlProps;
35
+ export type RecursicaRadioProps = RequireAccessibleLabel<
36
+ Omit<
37
+ MantineRadioProps,
38
+ "size" | "color" | "radius" | "iconColor" | "variant"
39
+ > &
40
+ ReadOnlyControlProps &
41
+ Pick<
42
+ FormControlLayoutProps,
43
+ "formLayout" | "labelSize" | "controlMaxWidth" | "controlMinWidth"
44
+ >
45
+ >;
34
46
 
35
47
  export type RadioProps = RecursicaOverStyled<RecursicaRadioProps>;
36
48
 
@@ -40,76 +52,123 @@ type RadioComponent = React.ForwardRefExoticComponent<
40
52
  Group: typeof RadioGroup;
41
53
  };
42
54
 
43
- export const Radio = forwardRef<HTMLInputElement, RadioProps>(function Radio(
44
- { overStyled = false, readOnly, readOnlyComponent, disabled, ...rest },
45
- ref,
46
- ) {
47
- const sanitizedProps = filterStylingProps(rest, overStyled);
48
- const restRecord = sanitizedProps as Record<string, unknown>;
49
-
50
- // Actively delete dimension bindings that bypass the abstraction
51
- delete restRecord["size"];
52
- delete restRecord["color"];
53
- delete restRecord["radius"];
54
- delete restRecord["variant"];
55
- delete restRecord["iconColor"];
56
-
57
- if (readOnly && readOnlyComponent) {
58
- // Safely fallback explicitly returning mapped elements natively without macro wrappers
59
- return <>{readOnlyComponent}</>;
60
- }
61
-
62
- const mergedClassNames: Partial<Record<string, string>> = {
63
- root: styles.root,
64
- body: styles.body,
65
- inner: styles.inner,
66
- radio: styles.radio,
67
- icon: styles.icon,
68
- labelWrapper: styles.labelWrapper,
69
- label: styles.label,
70
- };
71
-
72
- const classNamesProp = restRecord.classNames;
73
- if (
74
- classNamesProp &&
75
- typeof classNamesProp === "object" &&
76
- !Array.isArray(classNamesProp)
77
- ) {
78
- const o = classNamesProp as Partial<Record<string, string>>;
79
- mergedClassNames.root = o.root ? `${styles.root} ${o.root}` : styles.root;
80
- mergedClassNames.body = o.body ? `${styles.body} ${o.body}` : styles.body;
81
- mergedClassNames.inner = o.inner
82
- ? `${styles.inner} ${o.inner}`
83
- : styles.inner;
84
- mergedClassNames.radio = o.radio
85
- ? `${styles.radio} ${o.radio}`
86
- : styles.radio;
87
- mergedClassNames.icon = o.icon ? `${styles.icon} ${o.icon}` : styles.icon;
88
- mergedClassNames.labelWrapper = o.labelWrapper
89
- ? `${styles.labelWrapper} ${o.labelWrapper}`
90
- : styles.labelWrapper;
91
- mergedClassNames.label = o.label
92
- ? `${styles.label} ${o.label}`
93
- : styles.label;
94
- }
95
-
96
- const classNameProp = restRecord.className as string | undefined;
97
- const finalClass = classNameProp
98
- ? `${styles.root} ${classNameProp}`
99
- : styles.root;
100
-
101
- // We omit Mantine's sizing/coloring so we rely strictly on variables from Radio.module.css
102
- return (
103
- <MantineRadio
104
- ref={ref}
105
- icon={RadioIcon}
106
- className={finalClass}
107
- classNames={mergedClassNames}
108
- disabled={readOnly || disabled}
109
- {...(sanitizedProps as unknown as MantineRadioProps)}
110
- />
111
- );
112
- }) as RadioComponent;
55
+ export const Radio = forwardRef<HTMLInputElement, RadioProps>(
56
+ function Radio(props, ref) {
57
+ const {
58
+ overStyled = false,
59
+ readOnly,
60
+ readOnlyComponent,
61
+ disabled,
62
+ formLayout,
63
+ labelSize,
64
+ controlMaxWidth,
65
+ controlMinWidth,
66
+ ...rest
67
+ } = props;
68
+ const sanitizedProps = filterStylingProps(rest, overStyled);
69
+ const restRecord = sanitizedProps as Record<string, unknown>;
70
+
71
+ // Actively delete dimension bindings that bypass the abstraction
72
+ delete restRecord["size"];
73
+ delete restRecord["color"];
74
+ delete restRecord["radius"];
75
+ delete restRecord["variant"];
76
+ delete restRecord["iconColor"];
77
+
78
+ const mergedClassNames: Partial<Record<string, string>> = {
79
+ root: styles.root,
80
+ body: styles.body,
81
+ inner: styles.inner,
82
+ radio: styles.radio,
83
+ icon: styles.icon,
84
+ labelWrapper: styles.labelWrapper,
85
+ label: styles.label,
86
+ };
87
+
88
+ const classNamesProp = restRecord.classNames;
89
+ if (
90
+ classNamesProp &&
91
+ typeof classNamesProp === "object" &&
92
+ !Array.isArray(classNamesProp)
93
+ ) {
94
+ const o = classNamesProp as Partial<Record<string, string>>;
95
+ mergedClassNames.root = o.root ? `${styles.root} ${o.root}` : styles.root;
96
+ mergedClassNames.body = o.body ? `${styles.body} ${o.body}` : styles.body;
97
+ mergedClassNames.inner = o.inner
98
+ ? `${styles.inner} ${o.inner}`
99
+ : styles.inner;
100
+ mergedClassNames.radio = o.radio
101
+ ? `${styles.radio} ${o.radio}`
102
+ : styles.radio;
103
+ mergedClassNames.icon = o.icon ? `${styles.icon} ${o.icon}` : styles.icon;
104
+ mergedClassNames.labelWrapper = o.labelWrapper
105
+ ? `${styles.labelWrapper} ${o.labelWrapper}`
106
+ : styles.labelWrapper;
107
+ mergedClassNames.label = o.label
108
+ ? `${styles.label} ${o.label}`
109
+ : styles.label;
110
+ }
111
+
112
+ const classNameProp = restRecord.className as string | undefined;
113
+ const finalClass = classNameProp
114
+ ? `${styles.root} ${classNameProp}`
115
+ : styles.root;
116
+
117
+ if (readOnly && !!readOnlyComponent) {
118
+ const isChecked = !!(restRecord.checked ?? restRecord.defaultChecked);
119
+ const ReadOnlyComp = readOnlyComponent;
120
+ const roNode = (
121
+ <ReadOnlyComp
122
+ {...props}
123
+ checked={isChecked}
124
+ label={restRecord.label as React.ReactNode}
125
+ />
126
+ );
127
+
128
+ if (formLayout) {
129
+ return (
130
+ <FormControlLayout
131
+ formLayout={formLayout}
132
+ labelSize={labelSize}
133
+ controlMaxWidth={controlMaxWidth}
134
+ controlMinWidth={controlMinWidth}
135
+ >
136
+ {roNode}
137
+ </FormControlLayout>
138
+ );
139
+ }
140
+
141
+ return <>{roNode}</>;
142
+ }
143
+
144
+ // We omit Mantine's sizing/coloring so we rely strictly on variables from Radio.module.css
145
+ const radioNode = (
146
+ <MantineRadio
147
+ ref={ref}
148
+ icon={RadioIcon}
149
+ className={finalClass}
150
+ classNames={mergedClassNames}
151
+ disabled={readOnly || disabled}
152
+ {...(sanitizedProps as unknown as MantineRadioProps)}
153
+ />
154
+ );
155
+
156
+ if (formLayout) {
157
+ return (
158
+ <FormControlLayout
159
+ formLayout={formLayout}
160
+ labelSize={labelSize}
161
+ controlMaxWidth={controlMaxWidth}
162
+ controlMinWidth={controlMinWidth}
163
+ >
164
+ {radioNode}
165
+ </FormControlLayout>
166
+ );
167
+ }
168
+
169
+ return radioNode;
170
+ },
171
+ ) as RadioComponent;
113
172
 
114
173
  Radio.displayName = "Radio";
115
174
  Radio.Group = RadioGroup;