@recursica/mui-adapter 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/ARCHITECTURE.md +15 -0
  2. package/CHANGELOG.md +6 -0
  3. package/SETUP.md +77 -0
  4. package/USAGE.md +55 -0
  5. package/dist/mui-adapter.cjs +26 -26
  6. package/dist/mui-adapter.cjs.map +1 -1
  7. package/dist/mui-adapter.css +1 -1
  8. package/dist/mui-adapter.js +3721 -3511
  9. package/dist/mui-adapter.js.map +1 -1
  10. package/dist/src/components/Accordion/Accordion.d.ts +9 -29
  11. package/dist/src/components/AssistiveElement/AssistiveElement.d.ts +2 -5
  12. package/dist/src/components/Autocomplete/Autocomplete.d.ts +4 -8
  13. package/dist/src/components/Avatar/Avatar.d.ts +1 -6
  14. package/dist/src/components/Badge/Badge.d.ts +1 -4
  15. package/dist/src/components/Breadcrumb/Breadcrumb.d.ts +2 -3
  16. package/dist/src/components/Button/Button.d.ts +2 -17
  17. package/dist/src/components/Card/Card.d.ts +2 -4
  18. package/dist/src/components/Checkbox/Checkbox.d.ts +4 -6
  19. package/dist/src/components/Checkbox/CheckboxGroup.d.ts +8 -6
  20. package/dist/src/components/Chip/Chip.d.ts +2 -11
  21. package/dist/src/components/Container/Container.d.ts +1 -5
  22. package/dist/src/components/DatePicker/DatePicker.d.ts +2 -1
  23. package/dist/src/components/Dropdown/Dropdown.d.ts +4 -14
  24. package/dist/src/components/Flex/Flex.d.ts +2 -22
  25. package/dist/src/components/FormControlLayout/FormControlLayout.d.ts +2 -7
  26. package/dist/src/components/FormControlWrapper/FormControlWrapper.d.ts +3 -18
  27. package/dist/src/components/Group/Group.d.ts +2 -7
  28. package/dist/src/components/HoverCard/HoverCard.d.ts +13 -39
  29. package/dist/src/components/Label/Label.d.ts +2 -9
  30. package/dist/src/components/Link/Link.d.ts +2 -6
  31. package/dist/src/components/Loader/Loader.d.ts +2 -7
  32. package/dist/src/components/Menu/Menu.d.ts +24 -98
  33. package/dist/src/components/Modal/Modal.d.ts +32 -31
  34. package/dist/src/components/NumberInput/NumberInput.d.ts +3 -5
  35. package/dist/src/components/Pagination/Pagination.d.ts +8 -41
  36. package/dist/src/components/Panel/Panel.d.ts +5 -18
  37. package/dist/src/components/Radio/Radio.d.ts +4 -4
  38. package/dist/src/components/Radio/RadioGroup.d.ts +3 -7
  39. package/dist/src/components/ReadOnlyField/ReadOnlyField.d.ts +2 -5
  40. package/dist/src/components/SegmentedControl/SegmentedControl.d.ts +5 -15
  41. package/dist/src/components/Slider/Slider.d.ts +4 -14
  42. package/dist/src/components/Stack/Stack.d.ts +2 -5
  43. package/dist/src/components/Stepper/Stepper.d.ts +36 -16
  44. package/dist/src/components/Switch/Switch.d.ts +4 -4
  45. package/dist/src/components/Switch/SwitchGroup.d.ts +4 -4
  46. package/dist/src/components/Table/Table.d.ts +2 -1
  47. package/dist/src/components/Tabs/Tabs.d.ts +11 -26
  48. package/dist/src/components/Text/Text.d.ts +2 -16
  49. package/dist/src/components/TextArea/TextArea.d.ts +3 -9
  50. package/dist/src/components/TextField/TextField.d.ts +3 -7
  51. package/dist/src/components/TimePicker/TimePicker.d.ts +2 -1
  52. package/dist/src/components/Timeline/Timeline.d.ts +2 -1
  53. package/dist/src/components/Timeline/TimelineItem.d.ts +2 -13
  54. package/dist/src/components/Title/Title.d.ts +2 -15
  55. package/dist/src/components/Toast/Toast.d.ts +11 -16
  56. package/dist/src/components/Tooltip/Tooltip.d.ts +7 -21
  57. package/dist/src/components/TransferList/TransferList.d.ts +2 -1
  58. package/dist/src/components/Typography/Typography.d.ts +1 -1
  59. package/dist/src/utils/RequireAccessibleLabel.d.ts +1 -18
  60. package/dist/src/utils/filterStylingProps.d.ts +1 -11
  61. package/llms.txt +16 -0
  62. package/package.json +6 -2
  63. package/src/components/Accordion/Accordion.module.css +1 -2
  64. package/src/components/Accordion/Accordion.stories.tsx +0 -18
  65. package/src/components/Accordion/Accordion.tsx +19 -23
  66. package/src/components/AssistiveElement/AssistiveElement.module.css +32 -29
  67. package/src/components/AssistiveElement/AssistiveElement.stories.tsx +10 -27
  68. package/src/components/AssistiveElement/AssistiveElement.tsx +11 -15
  69. package/src/components/Autocomplete/Autocomplete.module.css +17 -18
  70. package/src/components/Autocomplete/Autocomplete.stories.tsx +7 -16
  71. package/src/components/Autocomplete/Autocomplete.tsx +27 -45
  72. package/src/components/Avatar/Avatar.tsx +2 -13
  73. package/src/components/Badge/Badge.tsx +2 -5
  74. package/src/components/Breadcrumb/Breadcrumb.tsx +1 -4
  75. package/src/components/Button/Button.module.css +19 -61
  76. package/src/components/Button/Button.stories.tsx +24 -23
  77. package/src/components/Button/Button.tsx +7 -18
  78. package/src/components/Card/Card.module.css +36 -0
  79. package/src/components/Card/Card.tsx +6 -4
  80. package/src/components/Checkbox/Checkbox.module.css +27 -10
  81. package/src/components/Checkbox/Checkbox.tsx +124 -24
  82. package/src/components/Checkbox/CheckboxGroup.stories.tsx +139 -0
  83. package/src/components/Checkbox/CheckboxGroup.tsx +43 -23
  84. package/src/components/Chip/Chip.module.css +52 -34
  85. package/src/components/Chip/Chip.tsx +45 -47
  86. package/src/components/Container/Container.stories.tsx +103 -20
  87. package/src/components/Container/Container.tsx +1 -13
  88. package/src/components/DatePicker/DatePicker.stories.tsx +125 -19
  89. package/src/components/DatePicker/DatePicker.tsx +3 -1
  90. package/src/components/Dropdown/Dropdown.tsx +7 -9
  91. package/src/components/FileInput/FileInput.stories.tsx +2 -19
  92. package/src/components/FileUpload/FileUpload.stories.tsx +2 -19
  93. package/src/components/Flex/Flex.stories.tsx +113 -20
  94. package/src/components/Flex/Flex.tsx +16 -26
  95. package/src/components/FormControlLayout/FormControlLayout.stories.tsx +0 -8
  96. package/src/components/FormControlLayout/FormControlLayout.tsx +18 -7
  97. package/src/components/FormControlWrapper/FormControlWrapper.module.css +1 -10
  98. package/src/components/FormControlWrapper/FormControlWrapper.stories.tsx +12 -21
  99. package/src/components/FormControlWrapper/FormControlWrapper.tsx +11 -30
  100. package/src/components/Group/Group.stories.tsx +6 -30
  101. package/src/components/Group/Group.tsx +14 -9
  102. package/src/components/HoverCard/HoverCard.tsx +72 -75
  103. package/src/components/Label/Label.module.css +4 -0
  104. package/src/components/Label/Label.stories.tsx +18 -12
  105. package/src/components/Label/Label.tsx +3 -9
  106. package/src/components/Link/Link.module.css +4 -3
  107. package/src/components/Link/Link.stories.tsx +2 -8
  108. package/src/components/Link/Link.tsx +1 -5
  109. package/src/components/Loader/Loader.module.css +1 -2
  110. package/src/components/Loader/Loader.tsx +1 -7
  111. package/src/components/Menu/IMPLEMENTATION_NOTES.md +4 -0
  112. package/src/components/Menu/Menu.stories.tsx +106 -188
  113. package/src/components/Menu/Menu.tsx +34 -280
  114. package/src/components/Modal/Modal.tsx +158 -160
  115. package/src/components/NumberInput/NumberInput.tsx +6 -18
  116. package/src/components/Pagination/IMPLEMENTATION_NOTES.md +3 -0
  117. package/src/components/Pagination/Pagination.module.css +12 -24
  118. package/src/components/Pagination/Pagination.stories.tsx +0 -8
  119. package/src/components/Pagination/Pagination.tsx +20 -140
  120. package/src/components/Panel/Panel.tsx +7 -30
  121. package/src/components/Radio/Radio.module.css +25 -10
  122. package/src/components/Radio/Radio.tsx +51 -19
  123. package/src/components/Radio/RadioGroup.stories.tsx +117 -0
  124. package/src/components/Radio/RadioGroup.tsx +26 -12
  125. package/src/components/ReadOnlyField/ReadOnlyField.module.css +6 -6
  126. package/src/components/ReadOnlyField/ReadOnlyField.stories.tsx +5 -36
  127. package/src/components/ReadOnlyField/ReadOnlyField.tsx +9 -13
  128. package/src/components/SegmentedControl/SegmentedControl.module.css +23 -47
  129. package/src/components/SegmentedControl/SegmentedControl.tsx +44 -14
  130. package/src/components/Slider/Slider.module.css +1 -4
  131. package/src/components/Slider/Slider.stories.tsx +3 -3
  132. package/src/components/Slider/Slider.tsx +26 -25
  133. package/src/components/Stack/Stack.stories.tsx +4 -25
  134. package/src/components/Stack/Stack.tsx +1 -5
  135. package/src/components/Stepper/IMPLEMENTATION_NOTES.md +4 -0
  136. package/src/components/Stepper/Stepper.stories.tsx +56 -43
  137. package/src/components/Stepper/Stepper.tsx +89 -87
  138. package/src/components/Switch/Switch.module.css +21 -0
  139. package/src/components/Switch/Switch.tsx +63 -16
  140. package/src/components/Switch/SwitchGroup.stories.tsx +132 -0
  141. package/src/components/Switch/SwitchGroup.tsx +27 -18
  142. package/src/components/Table/Table.stories.tsx +1 -19
  143. package/src/components/Table/Table.tsx +3 -1
  144. package/src/components/Tabs/IMPLEMENTATION_NOTES.md +4 -0
  145. package/src/components/Tabs/Tabs.stories.tsx +41 -24
  146. package/src/components/Tabs/Tabs.tsx +49 -66
  147. package/src/components/Text/Text.stories.tsx +5 -17
  148. package/src/components/Text/Text.tsx +1 -15
  149. package/src/components/TextArea/TextArea.tsx +7 -17
  150. package/src/components/TextField/TextField.stories.tsx +1 -27
  151. package/src/components/TextField/TextField.tsx +4 -6
  152. package/src/components/TimePicker/TimePicker.stories.tsx +2 -19
  153. package/src/components/TimePicker/TimePicker.tsx +3 -1
  154. package/src/components/Timeline/Timeline.tsx +4 -1
  155. package/src/components/Timeline/TimelineItem.tsx +1 -12
  156. package/src/components/Title/Title.stories.tsx +0 -18
  157. package/src/components/Title/Title.tsx +1 -7
  158. package/src/components/Toast/Toast.stories.tsx +1 -1
  159. package/src/components/Toast/Toast.tsx +62 -28
  160. package/src/components/Tooltip/Tooltip.tsx +11 -37
  161. package/src/components/TransferList/TransferList.stories.tsx +1 -19
  162. package/src/components/TransferList/TransferList.tsx +3 -1
  163. package/src/utils/RequireAccessibleLabel.ts +1 -12
  164. package/src/utils/filterStylingProps.ts +7 -20
@@ -1,6 +1,6 @@
1
1
  import React, { useState } from "react";
2
2
  import type { Meta, StoryObj } from "@storybook/react";
3
- import { Stepper } from "./Stepper";
3
+ import { Stepper, Step, StepLabel } from "./Stepper";
4
4
  import { Button } from "../Button/Button";
5
5
  import { Group } from "../Group/Group";
6
6
  import { Flex } from "../Flex/Flex";
@@ -11,19 +11,6 @@ const meta: Meta<typeof Stepper> = {
11
11
  tags: ["autodocs"],
12
12
  parameters: {
13
13
  layout: "centered",
14
- docs: {
15
- description: {
16
- component: `
17
- The \`Stepper\` component provides a visual progression interface strictly mapped to Recursica's UI-Kit.
18
- This component wraps Mantine's \`Stepper\` and natively enforces Figma tokens for sizes, colors, gaps, and states (\`completed\`, \`current\`, \`upcoming\`).
19
-
20
- ### Layout & Orientation
21
- Orientation dictates how the steps flow:
22
- - **horizontal**: Steps render side-by-side.
23
- - **vertical**: Steps stack vertically, mapping seamlessly to the vertical gap tokens.
24
- `,
25
- },
26
- },
27
14
  },
28
15
  argTypes: {
29
16
  size: {
@@ -34,9 +21,6 @@ Orientation dictates how the steps flow:
34
21
  control: "radio",
35
22
  options: ["horizontal", "vertical"],
36
23
  },
37
- defaultChecked: {
38
- table: { disable: true },
39
- },
40
24
  },
41
25
  };
42
26
 
@@ -50,23 +34,39 @@ const InteractiveStepper = (args: React.ComponentProps<typeof Stepper>) => {
50
34
  const prevStep = () =>
51
35
  setActive((current) => (current > 0 ? current - 1 : current));
52
36
 
37
+ const steps = [
38
+ {
39
+ label: "First step",
40
+ description: "Create an account and set up your billing profile",
41
+ },
42
+ {
43
+ label: "Second step",
44
+ description:
45
+ "Verify email and ensure all notification preferences are correct",
46
+ },
47
+ { label: "Final step", description: "Get full access" },
48
+ ];
49
+
53
50
  return (
54
51
  <Flex direction="column" w={600}>
55
- <Stepper {...args} active={active} onStepClick={setActive}>
56
- <Stepper.Step
57
- label="First step"
58
- description="Create an account and set up your billing profile"
59
- />
60
- <Stepper.Step
61
- label="Second step"
62
- description="Verify email and ensure all notification preferences are correct"
63
- />
64
- <Stepper.Step label="Final step" description="Get full access" />
65
- <Stepper.Completed>
66
- Completed, click back button to get to previous step
67
- </Stepper.Completed>
52
+ <Stepper {...args} activeStep={active}>
53
+ {steps.map((step, index) => (
54
+ <Step key={index} completed={active > index}>
55
+ <StepLabel description={step.description}>{step.label}</StepLabel>
56
+ </Step>
57
+ ))}
68
58
  </Stepper>
69
59
 
60
+ {active === steps.length ? (
61
+ <div style={{ marginTop: 24, textAlign: "center" }}>
62
+ Completed, click back button to get to previous step
63
+ </div>
64
+ ) : (
65
+ <div style={{ marginTop: 24, textAlign: "center" }}>
66
+ Step {active + 1} content
67
+ </div>
68
+ )}
69
+
70
70
  <Group mt={24} justify="center" gap={8}>
71
71
  <Button variant="outline" onClick={prevStep} disabled={active === 0}>
72
72
  Previous step
@@ -110,23 +110,36 @@ const StressTestStepper = (args: React.ComponentProps<typeof Stepper>) => {
110
110
  const prevStep = () =>
111
111
  setActive((current) => (current > 0 ? current - 1 : current));
112
112
 
113
+ const steps = [
114
+ {
115
+ label:
116
+ "This is an extremely long step title designed to test how the layout handles multiline text wrapping and constraints",
117
+ description: "Create an account and set up your billing profile",
118
+ },
119
+ {
120
+ label: "Second step",
121
+ description:
122
+ "Verify email and ensure all notification preferences are correct",
123
+ },
124
+ { label: "Final step", description: undefined },
125
+ ];
126
+
113
127
  return (
114
128
  <Flex direction="column" w={600}>
115
- <Stepper {...args} active={active} onStepClick={setActive}>
116
- <Stepper.Step
117
- label="This is an extremely long step title designed to test how the layout handles multiline text wrapping and constraints"
118
- description="Create an account and set up your billing profile"
119
- />
120
- <Stepper.Step
121
- label="Second step"
122
- description="Verify email and ensure all notification preferences are correct"
123
- />
124
- <Stepper.Step label="Final step" />
125
- <Stepper.Completed>
126
- Completed, click back button to get to previous step
127
- </Stepper.Completed>
129
+ <Stepper {...args} activeStep={active}>
130
+ {steps.map((step, index) => (
131
+ <Step key={index} completed={active > index}>
132
+ <StepLabel description={step.description}>{step.label}</StepLabel>
133
+ </Step>
134
+ ))}
128
135
  </Stepper>
129
136
 
137
+ {active === steps.length && (
138
+ <div style={{ marginTop: 24, textAlign: "center" }}>
139
+ Completed, click back button to get to previous step
140
+ </div>
141
+ )}
142
+
130
143
  <Group mt={24} justify="center" gap={8}>
131
144
  <Button variant="outline" onClick={prevStep} disabled={active === 0}>
132
145
  Previous step
@@ -1,4 +1,4 @@
1
- import React, { forwardRef, isValidElement } from "react";
1
+ import React, { forwardRef } from "react";
2
2
  import {
3
3
  Stepper as MuiStepper,
4
4
  Step as MuiStep,
@@ -6,6 +6,10 @@ import {
6
6
  StepButton as MuiStepButton,
7
7
  StepConnector as MuiStepConnector,
8
8
  type StepperProps as MuiStepperProps,
9
+ type StepProps as MuiStepProps,
10
+ type StepLabelProps as MuiStepLabelProps,
11
+ type StepButtonProps as MuiStepButtonProps,
12
+ type StepConnectorProps as MuiStepConnectorProps,
9
13
  } from "@mui/material";
10
14
  import {
11
15
  filterStylingProps,
@@ -13,23 +17,11 @@ import {
13
17
  } from "../../utils/filterStylingProps";
14
18
  import styles from "./Stepper.module.css";
15
19
 
16
- export interface RecursicaStepperProps
17
- extends Omit<
18
- MuiStepperProps,
19
- "size" | "color" | "radius" | "iconSize" | "contentPadding" | "activeStep"
20
- > {
21
- size?: "small" | "large";
22
- active?: number;
23
- onStepClick?: (stepIndex: number) => void;
24
- orientation?: "horizontal" | "vertical";
25
- }
20
+ import { type RecursicaStepperProps } from "@recursica/adapter-common";
26
21
 
27
22
  export type StepperProps = RecursicaOverStyled<RecursicaStepperProps>;
28
23
 
29
- export const StepperStep: React.FC<any> = () => null;
30
- export const StepperCompleted: React.FC<any> = () => null;
31
-
32
- const _Stepper = forwardRef<HTMLDivElement, StepperProps>(
24
+ export const Stepper = forwardRef<HTMLDivElement, StepperProps>(
33
25
  function Stepper(props, ref) {
34
26
  const {
35
27
  overStyled = false,
@@ -37,27 +29,11 @@ const _Stepper = forwardRef<HTMLDivElement, StepperProps>(
37
29
  orientation = "horizontal",
38
30
  className,
39
31
  style,
40
- active = 0,
41
- onStepClick,
42
- children,
43
32
  ...rest
44
33
  } = props;
45
34
 
46
35
  const sanitizedProps = filterStylingProps(rest, overStyled);
47
36
 
48
- const steps = React.Children.toArray(children).filter(
49
- (child: any) =>
50
- isValidElement(child) &&
51
- (child.type === StepperStep ||
52
- (child.type as any).displayName === "StepperStep"),
53
- );
54
- const completed = React.Children.toArray(children).find(
55
- (child: any) =>
56
- isValidElement(child) &&
57
- (child.type === StepperCompleted ||
58
- (child.type as any).displayName === "StepperCompleted"),
59
- );
60
-
61
37
  return (
62
38
  <div
63
39
  className={`${styles.root} ${orientation === "horizontal" ? styles.horizontal : styles.vertical} ${size === "large" ? styles.large : styles.small} ${className || ""}`}
@@ -68,7 +44,6 @@ const _Stepper = forwardRef<HTMLDivElement, StepperProps>(
68
44
  <MuiStepper
69
45
  ref={ref as any}
70
46
  orientation={orientation}
71
- activeStep={active}
72
47
  connector={
73
48
  <MuiStepConnector
74
49
  classes={{
@@ -83,65 +58,92 @@ const _Stepper = forwardRef<HTMLDivElement, StepperProps>(
83
58
  root: styles.steps,
84
59
  }}
85
60
  {...(sanitizedProps as MuiStepperProps)}
86
- >
87
- {steps.map((step: any, index: number) => {
88
- const stepProps = step.props;
89
- const label = stepProps.label;
90
- const description = stepProps.description;
91
-
92
- return (
93
- <MuiStep
94
- key={index}
95
- classes={{ root: styles.step }}
96
- completed={active > index}
97
- >
98
- <MuiStepButton onClick={() => onStepClick?.(index)}>
99
- <MuiStepLabel
100
- optional={
101
- description ? (
102
- <div className={styles.stepDescription}>
103
- {description}
104
- </div>
105
- ) : null
106
- }
107
- classes={{
108
- label: styles.stepLabel,
109
- iconContainer: styles.stepIcon,
110
- }}
111
- >
112
- {label}
113
- </MuiStepLabel>
114
- </MuiStepButton>
115
- </MuiStep>
116
- );
117
- })}
118
- </MuiStepper>
119
- {active >= steps.length && completed && (
120
- <div className={styles.content}>
121
- {(completed as React.ReactElement).props.children}
122
- </div>
123
- )}
124
- {active < steps.length &&
125
- steps[active] &&
126
- (steps[active] as React.ReactElement).props.children && (
127
- <div className={styles.content}>
128
- {(steps[active] as React.ReactElement).props.children}
129
- </div>
130
- )}
61
+ />
131
62
  </div>
132
63
  );
133
64
  },
134
65
  );
135
66
 
136
- StepperStep.displayName = "StepperStep";
137
- StepperCompleted.displayName = "StepperCompleted";
67
+ Stepper.displayName = "Stepper";
68
+
69
+ export type StepProps = RecursicaOverStyled<MuiStepProps>;
70
+
71
+ export const Step = forwardRef<HTMLDivElement, StepProps>(
72
+ function Step(props, ref) {
73
+ const { overStyled = false, className, ...rest } = props;
74
+ return (
75
+ <MuiStep
76
+ ref={ref}
77
+ classes={{ root: styles.step }}
78
+ className={className || ""}
79
+ {...(filterStylingProps(rest, overStyled) as MuiStepProps)}
80
+ />
81
+ );
82
+ },
83
+ );
84
+
85
+ Step.displayName = "Step";
86
+
87
+ export type StepButtonProps = RecursicaOverStyled<MuiStepButtonProps>;
88
+
89
+ export const StepButton = forwardRef<HTMLButtonElement, StepButtonProps>(
90
+ function StepButton(props, ref) {
91
+ const { overStyled = false, className, ...rest } = props;
92
+ return (
93
+ <MuiStepButton
94
+ ref={ref}
95
+ className={className || ""}
96
+ {...(filterStylingProps(rest, overStyled) as MuiStepButtonProps)}
97
+ />
98
+ );
99
+ },
100
+ );
101
+
102
+ StepButton.displayName = "StepButton";
103
+
104
+ export type StepLabelProps = RecursicaOverStyled<
105
+ MuiStepLabelProps & { description?: React.ReactNode }
106
+ >;
138
107
 
139
- export const Stepper: typeof _Stepper & {
140
- Step: typeof StepperStep;
141
- Completed: typeof StepperCompleted;
142
- } = Object.assign(_Stepper, {
143
- Step: StepperStep,
144
- Completed: StepperCompleted,
145
- });
108
+ export const StepLabel = forwardRef<HTMLDivElement, StepLabelProps>(
109
+ function StepLabel(props, ref) {
110
+ const { overStyled = false, className, description, ...rest } = props;
111
+ return (
112
+ <MuiStepLabel
113
+ ref={ref}
114
+ className={className || ""}
115
+ classes={{
116
+ label: styles.stepLabel,
117
+ iconContainer: styles.stepIcon,
118
+ }}
119
+ optional={
120
+ description ? (
121
+ <div className={styles.stepDescription}>{description}</div>
122
+ ) : (
123
+ rest.optional
124
+ )
125
+ }
126
+ {...(filterStylingProps(rest, overStyled) as MuiStepLabelProps)}
127
+ />
128
+ );
129
+ },
130
+ );
131
+
132
+ StepLabel.displayName = "StepLabel";
133
+
134
+ export type StepConnectorProps = RecursicaOverStyled<MuiStepConnectorProps>;
135
+
136
+ export const StepConnector = forwardRef<HTMLDivElement, StepConnectorProps>(
137
+ function StepConnector(props, ref) {
138
+ const { overStyled = false, className, ...rest } = props;
139
+ return (
140
+ <MuiStepConnector
141
+ ref={ref}
142
+ className={className || ""}
143
+ {...(filterStylingProps(rest, overStyled) as MuiStepConnectorProps)}
144
+ />
145
+ );
146
+ },
147
+ );
146
148
 
147
- _Stepper.displayName = "Stepper";
149
+ StepConnector.displayName = "StepConnector";
@@ -150,6 +150,27 @@
150
150
  );
151
151
  }
152
152
 
153
+ .description {
154
+ margin-top: 5px; /* HARDCODE: mantine default */
155
+ color: #868e96; /* HARDCODE: mantine default dimmed */
156
+ font-size: 12px; /* HARDCODE: mantine default xs */
157
+ line-height: 1.2;
158
+ }
159
+
160
+ .error {
161
+ margin-top: 5px; /* HARDCODE: mantine default */
162
+ color: #fa5252; /* HARDCODE: mantine default error */
163
+ font-size: 12px; /* HARDCODE: mantine default xs */
164
+ line-height: 1.2;
165
+ }
166
+
167
+ .description[data-disabled="true"],
168
+ .error[data-disabled="true"] {
169
+ opacity: var(
170
+ --recursica_ui-kit_components_switch-item_properties_disabled-opacity
171
+ );
172
+ }
173
+
153
174
  .thumbIconWrapper {
154
175
  display: flex;
155
176
  align-items: center;
@@ -9,25 +9,33 @@ import {
9
9
  filterStylingProps,
10
10
  type RecursicaOverStyled,
11
11
  } from "../../utils/filterStylingProps";
12
- import { type RequireAccessibleLabel } from "../../utils/RequireAccessibleLabel";
12
+ import {
13
+ type RequireAccessibleLabel,
14
+ type RecursicaSwitchProps,
15
+ } from "@recursica/adapter-common";
13
16
  import {
14
17
  FormControlLayout,
15
18
  type FormControlLayoutProps,
16
19
  } from "../FormControlLayout/FormControlLayout";
17
20
  import styles from "./Switch.module.css";
18
21
 
19
- import { SwitchGroup } from "./SwitchGroup";
22
+ // Removed unused SwitchGroup import
23
+
24
+ export type SwitchWrapperProps = Omit<
25
+ MuiSwitchProps,
26
+ "size" | "color" | "radius" | "variant"
27
+ > &
28
+ RecursicaSwitchProps &
29
+ ReadOnlyControlProps &
30
+ Pick<
31
+ FormControlLayoutProps,
32
+ "formLayout" | "labelSize" | "controlMaxWidth" | "controlMinWidth"
33
+ >;
20
34
 
21
- export type RecursicaSwitchProps = RequireAccessibleLabel<
22
- Omit<MuiSwitchProps, "size" | "color" | "radius" | "variant"> &
23
- ReadOnlyControlProps &
24
- Pick<
25
- FormControlLayoutProps,
26
- "formLayout" | "labelSize" | "controlMaxWidth" | "controlMinWidth"
27
- >
28
- >;
35
+ export type RecursicaSwitchPropsAlias =
36
+ RequireAccessibleLabel<SwitchWrapperProps>;
29
37
 
30
- export type SwitchProps = RecursicaOverStyled<RecursicaSwitchProps>;
38
+ export type SwitchProps = RecursicaOverStyled<RecursicaSwitchPropsAlias>;
31
39
 
32
40
  type SwitchComponent = any;
33
41
 
@@ -38,11 +46,15 @@ export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
38
46
  readOnly,
39
47
  readOnlyComponent,
40
48
  disabled,
41
- thumbIcon,
49
+ // Removed thumbIcon from destructured props since it's not supported
42
50
  formLayout,
43
51
  labelSize,
44
52
  controlMaxWidth,
45
53
  controlMinWidth,
54
+ label,
55
+ description,
56
+ error,
57
+ style,
46
58
  ...rest
47
59
  } = props;
48
60
  const sanitizedProps = filterStylingProps(rest, overStyled);
@@ -125,8 +137,8 @@ export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
125
137
  // We omit Mui's sizing/coloring so we rely strictly on variables from Switch.module.css
126
138
  const switchNode = (
127
139
  <MuiSwitch
128
- ref={ref}
129
- className={finalClass}
140
+ ref={ref as any}
141
+ className={!label ? `${finalClass} ${styles.inner}` : styles.inner}
130
142
  classes={mergedClassNames}
131
143
  disabled={readOnly || disabled}
132
144
  data-disabled={readOnly || disabled || undefined}
@@ -135,6 +147,41 @@ export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
135
147
  />
136
148
  );
137
149
 
150
+ const finalNode = label ? (
151
+ <div className={finalClass} style={style as React.CSSProperties}>
152
+ <div className={styles.body}>
153
+ {switchNode}
154
+ <div className={styles.labelWrapper}>
155
+ <label
156
+ className={styles.label}
157
+ htmlFor={restRecord.id as string}
158
+ data-disabled={readOnly || disabled ? true : undefined}
159
+ >
160
+ {label as React.ReactNode}
161
+ </label>
162
+ {description && (
163
+ <div
164
+ className={styles.description}
165
+ data-disabled={readOnly || disabled ? true : undefined}
166
+ >
167
+ {description}
168
+ </div>
169
+ )}
170
+ {error && (
171
+ <div
172
+ className={styles.error}
173
+ data-disabled={readOnly || disabled ? true : undefined}
174
+ >
175
+ {error}
176
+ </div>
177
+ )}
178
+ </div>
179
+ </div>
180
+ </div>
181
+ ) : (
182
+ switchNode
183
+ );
184
+
138
185
  if (formLayout) {
139
186
  return (
140
187
  <FormControlLayout
@@ -143,12 +190,12 @@ export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
143
190
  controlMaxWidth={controlMaxWidth}
144
191
  controlMinWidth={controlMinWidth}
145
192
  >
146
- {switchNode}
193
+ {finalNode}
147
194
  </FormControlLayout>
148
195
  );
149
196
  }
150
197
 
151
- return switchNode;
198
+ return finalNode;
152
199
  },
153
200
  ) as SwitchComponent;
154
201
 
@@ -0,0 +1,132 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Switch } from "./Switch";
3
+ import { SwitchGroup } from "./SwitchGroup";
4
+ import { useState } from "react";
5
+ import { formControlArgTypes } from "../../../.storybook/commonArgTypes";
6
+
7
+ const meta: Meta<typeof SwitchGroup> = {
8
+ title: "UI-Kit/SwitchGroup",
9
+ component: SwitchGroup,
10
+ tags: ["autodocs"],
11
+ parameters: {
12
+ docs: {
13
+ description: {
14
+ component: `
15
+ The \`SwitchGroup\` component is the mandatory organizational wrapper for orchestrating \`Switch\` primitives into macro form layouts. It inherently leverages the \`FormControlWrapper\`, granting native access to structural alignments, assistive descriptions, and strict flex arrays.
16
+
17
+ ### Supported Layout Vectors
18
+ - **\`formLayout="stacked"\`**: Top-to-bottom layout cascading the Label bounding box down vertically into a standard stacked switch array.
19
+ - **\`formLayout="side-by-side"\`**: Flow architecture pulling the grouping Label dynamically to the left while structurally organizing the internal switches cleanly alongside it horizontally.
20
+
21
+ > [!IMPORTANT]
22
+ > If you require a solitary \`Switch\` field to natively accept an overarching form label, assistive text, or align with other side-by-side components in a form column, you must wrap it inside a \`Switch.Group\` to trigger the \`FormControlWrapper\` mapping.
23
+ `,
24
+ },
25
+ },
26
+ },
27
+ argTypes: {
28
+ ...formControlArgTypes,
29
+ readOnly: {
30
+ control: "boolean",
31
+ description:
32
+ "Passes read-only lock natively down mapping inner switches into explicitly disabled states.",
33
+ },
34
+ },
35
+ };
36
+
37
+ export default meta;
38
+
39
+ type Story = StoryObj<typeof SwitchGroup>;
40
+
41
+ export const Default: Story = {
42
+ args: {
43
+ formLayout: "stacked",
44
+ label: "Standard Group",
45
+ },
46
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
47
+ render: function StoryRender({ withLayer, layer, ...args }: any) {
48
+ const [value, setValue] = useState<string[]>([]);
49
+ return (
50
+ <SwitchGroup {...args} value={value} onChange={setValue}>
51
+ <Switch value="1" label="Option 1" />
52
+ <Switch value="2" label="Option 2" />
53
+ </SwitchGroup>
54
+ );
55
+ },
56
+ };
57
+
58
+ export const StackedLayout: Story = {
59
+ args: {
60
+ formLayout: "stacked",
61
+ label: "Notification Settings",
62
+ description: "Manage your preferences.",
63
+ assistiveText: "We recommend turning these on.",
64
+ error: "",
65
+ required: true,
66
+ },
67
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
68
+ render: function StoryRender({ withLayer, layer, ...args }: any) {
69
+ const [value, setValue] = useState<string[]>(["email"]);
70
+ return (
71
+ <SwitchGroup {...args} value={value} onChange={setValue}>
72
+ <Switch value="email" label="Email Alerts" />
73
+ <Switch value="push" label="Push Notifications" />
74
+ <Switch value="sms" label="SMS Messages" />
75
+ </SwitchGroup>
76
+ );
77
+ },
78
+ };
79
+
80
+ export const SideBySideLayout: Story = {
81
+ args: {
82
+ ...StackedLayout.args,
83
+ formLayout: "side-by-side",
84
+ labelSize: "default",
85
+ labelAlignment: "left",
86
+ },
87
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
88
+ render: function StoryRender({ withLayer, layer, ...args }: any) {
89
+ const [value, setValue] = useState<string[]>([]);
90
+ return (
91
+ <SwitchGroup {...args} value={value} onChange={setValue}>
92
+ <Switch value="weekly" label="Weekly Digest" />
93
+ <Switch value="marketing" label="Marketing Emails" />
94
+ </SwitchGroup>
95
+ );
96
+ },
97
+ };
98
+
99
+ export const SolitaryFormControl: Story = {
100
+ args: {
101
+ formLayout: "side-by-side",
102
+ label: "Enable Backups",
103
+ description: "Automatically back up your data nightly.",
104
+ },
105
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
106
+ render: function StoryRender({ withLayer, layer, ...args }: any) {
107
+ const [value, setValue] = useState<string[]>(["auto"]);
108
+ return (
109
+ <SwitchGroup {...args} value={value} onChange={setValue}>
110
+ <Switch value="auto" label="Auto-backup" />
111
+ </SwitchGroup>
112
+ );
113
+ },
114
+ };
115
+
116
+ export const ReadOnly: Story = {
117
+ args: {
118
+ ...StackedLayout.args,
119
+ readOnly: true,
120
+ },
121
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
122
+ render: function StoryRender({ withLayer, layer, ...args }: any) {
123
+ const [value, setValue] = useState<string[]>(["email", "sms"]);
124
+ return (
125
+ <SwitchGroup {...args} value={value} onChange={setValue}>
126
+ <Switch value="email" label="Email Alerts" />
127
+ <Switch value="push" label="Push Notifications" />
128
+ <Switch value="sms" label="SMS Messages" />
129
+ </SwitchGroup>
130
+ );
131
+ },
132
+ };