@recursica/mantine-adapter 0.10.0 → 0.12.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 (79) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +6 -28
  3. package/dist/mantine-adapter.cjs +1 -1
  4. package/dist/mantine-adapter.cjs.map +1 -1
  5. package/dist/mantine-adapter.css +1 -1
  6. package/dist/mantine-adapter.js +1650 -1344
  7. package/dist/mantine-adapter.js.map +1 -1
  8. package/dist/src/components/Checkbox/Checkbox.d.ts +3 -1
  9. package/dist/src/components/FormControlLayout/FormControlLayout.d.ts +21 -0
  10. package/dist/src/components/FormControlWrapper/FormControlWrapper.d.ts +2 -0
  11. package/dist/src/components/Label/Label.d.ts +1 -3
  12. package/dist/src/components/Menu/Menu.d.ts +85 -3
  13. package/dist/src/components/Radio/Radio.d.ts +3 -1
  14. package/dist/src/components/ReadOnlyField/ReadOnlyBooleanField.d.ts +5 -0
  15. package/dist/src/components/ReadOnlyField/ReadOnlyField.d.ts +6 -6
  16. package/dist/src/components/ReadOnlyField/ReadOnlySwitchField.d.ts +5 -0
  17. package/dist/src/components/ReadOnlyField/WithReadOnlyWrapper.d.ts +7 -1
  18. package/dist/src/components/ReadOnlyField/index.d.ts +2 -0
  19. package/dist/src/components/Switch/Switch.d.ts +3 -1
  20. package/dist/src/components/index.d.ts +1 -0
  21. package/dist/src/utils/RequireAccessibleLabel.d.ts +18 -0
  22. package/package.json +1 -1
  23. package/src/components/AssistiveElement/AssistiveElement.stories.tsx +6 -4
  24. package/src/components/Avatar/Avatar.stories.tsx +10 -5
  25. package/src/components/Badge/Badge.stories.tsx +2 -1
  26. package/src/components/Button/Button.stories.tsx +2 -4
  27. package/src/components/Checkbox/Checkbox.module.css +1 -1
  28. package/src/components/Checkbox/Checkbox.stories.tsx +24 -3
  29. package/src/components/Checkbox/Checkbox.tsx +72 -15
  30. package/src/components/Checkbox/CheckboxGroup.stories.tsx +26 -25
  31. package/src/components/Checkbox/CheckboxGroup.tsx +38 -58
  32. package/src/components/Container/Container.stories.tsx +6 -3
  33. package/src/components/DatePicker/DatePicker.stories.tsx +1 -5
  34. package/src/components/Dropdown/Dropdown.stories.tsx +0 -3
  35. package/src/components/Dropdown/Dropdown.tsx +6 -5
  36. package/src/components/FileInput/FileInput.stories.tsx +1 -5
  37. package/src/components/FileUpload/FileUpload.stories.tsx +1 -5
  38. package/src/components/Flex/Flex.stories.tsx +6 -3
  39. package/src/components/FormControlLayout/FormControlLayout.module.css +80 -0
  40. package/src/components/FormControlLayout/FormControlLayout.stories.tsx +82 -0
  41. package/src/components/FormControlLayout/FormControlLayout.tsx +78 -0
  42. package/src/components/FormControlWrapper/FormControlWrapper.module.css +3 -41
  43. package/src/components/FormControlWrapper/FormControlWrapper.stories.tsx +18 -5
  44. package/src/components/FormControlWrapper/FormControlWrapper.tsx +28 -41
  45. package/src/components/Group/Group.stories.tsx +6 -3
  46. package/src/components/Label/Label.module.css +0 -37
  47. package/src/components/Label/Label.stories.tsx +8 -9
  48. package/src/components/Label/Label.tsx +2 -7
  49. package/src/components/Loader/Loader.stories.tsx +10 -5
  50. package/src/components/Menu/MENU_IMPLEMENTATION_NOTES.md +78 -0
  51. package/src/components/Menu/Menu.module.css +244 -0
  52. package/src/components/Menu/Menu.stories.tsx +309 -5
  53. package/src/components/Menu/Menu.tsx +317 -4
  54. package/src/components/NumberInput/NumberInput.stories.tsx +1 -5
  55. package/src/components/Radio/Radio.stories.tsx +22 -8
  56. package/src/components/Radio/Radio.tsx +134 -75
  57. package/src/components/Radio/RadioGroup.stories.tsx +26 -7
  58. package/src/components/Radio/RadioGroup.tsx +37 -57
  59. package/src/components/ReadOnlyField/ReadOnlyBooleanField.tsx +21 -0
  60. package/src/components/ReadOnlyField/ReadOnlyField.stories.tsx +43 -0
  61. package/src/components/ReadOnlyField/ReadOnlyField.tsx +30 -3
  62. package/src/components/ReadOnlyField/ReadOnlySwitchField.tsx +21 -0
  63. package/src/components/ReadOnlyField/ReadOnlyTextField.tsx +3 -1
  64. package/src/components/ReadOnlyField/WithReadOnlyWrapper.tsx +10 -2
  65. package/src/components/ReadOnlyField/index.ts +2 -0
  66. package/src/components/SegmentedControl/SegmentedControl.stories.tsx +1 -5
  67. package/src/components/Slider/Slider.stories.tsx +1 -5
  68. package/src/components/Stack/Stack.stories.tsx +6 -3
  69. package/src/components/Switch/Switch.module.css +2 -10
  70. package/src/components/Switch/Switch.stories.tsx +42 -27
  71. package/src/components/Switch/Switch.tsx +138 -89
  72. package/src/components/Switch/SwitchGroup.stories.tsx +28 -8
  73. package/src/components/Switch/SwitchGroup.tsx +37 -57
  74. package/src/components/TextArea/TextArea.stories.tsx +0 -3
  75. package/src/components/TextArea/TextArea.tsx +6 -5
  76. package/src/components/TextField/TextField.tsx +6 -5
  77. package/src/components/TimePicker/TimePicker.stories.tsx +1 -5
  78. package/src/components/index.ts +1 -0
  79. package/src/utils/RequireAccessibleLabel.ts +12 -0
@@ -35,14 +35,32 @@ export default meta;
35
35
 
36
36
  type Story = StoryObj<typeof RadioGroup>;
37
37
 
38
- export const GroupStacked: Story = {
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 = {
39
56
  args: {
40
57
  formLayout: "stacked",
41
58
  required: true,
42
59
  label: "Hosting Provider",
43
60
  error: "You must select a deployment provider.",
44
61
  },
45
- render: function GroupStackedRender(args) {
62
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
63
+ render: function StoryRender({ withLayer, layer, ...args }: any) {
46
64
  const [value, setValue] = useState<string>("aws");
47
65
  return (
48
66
  <RadioGroup {...args} value={value} onChange={setValue}>
@@ -57,7 +75,7 @@ export const GroupStacked: Story = {
57
75
  },
58
76
  };
59
77
 
60
- export const GroupSideBySide: Story = {
78
+ export const SideBySideLayout: Story = {
61
79
  args: {
62
80
  formLayout: "side-by-side",
63
81
  labelOptionalText: "Recommended",
@@ -65,7 +83,8 @@ export const GroupSideBySide: Story = {
65
83
  label: "Deployment Region",
66
84
  assistiveText: "Select the data center closest to your user base.",
67
85
  },
68
- render: function GroupSideBySideRender(args) {
86
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
87
+ render: function StoryRender({ withLayer, layer, ...args }: any) {
69
88
  const [value, setValue] = useState<string>("us-east");
70
89
  return (
71
90
  <RadioGroup {...args} value={value} onChange={setValue}>
@@ -77,16 +96,16 @@ export const GroupSideBySide: Story = {
77
96
  },
78
97
  };
79
98
 
80
- export const StaticReadOnlyGroup: Story = {
99
+ export const ReadOnly: Story = {
81
100
  args: {
82
101
  readOnly: true,
83
102
  formLayout: "stacked",
84
103
  required: true,
85
104
  label: "Selected Framework",
86
105
  assistiveText: "This selection cannot be changed after initialization.",
87
- readOnlyComponent: <span>React (Standard Build)</span>,
88
106
  },
89
- render: function StaticReadOnlyGroupRender(args) {
107
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
108
+ render: function StoryRender({ withLayer, layer, ...args }: any) {
90
109
  const [value, setValue] = useState<string>("react");
91
110
  return (
92
111
  <RadioGroup {...args} value={value} onChange={setValue}>
@@ -9,10 +9,8 @@ import {
9
9
  filterStylingProps,
10
10
  type RecursicaOverStyled,
11
11
  } from "../../utils/filterStylingProps";
12
- import {
13
- FormControlWrapper,
14
- type RecursicaFormControlWrapperProps,
15
- } from "../FormControlWrapper/FormControlWrapper";
12
+ import { type RecursicaFormControlWrapperProps } from "../FormControlWrapper/FormControlWrapper";
13
+ import { WithReadOnlyWrapper } from "../ReadOnlyField/WithReadOnlyWrapper";
16
14
  import styles from "./Radio.module.css";
17
15
 
18
16
  export interface RecursicaRadioGroupProps
@@ -26,8 +24,8 @@ export interface RecursicaRadioGroupProps
26
24
  export type RadioGroupProps = RecursicaOverStyled<RecursicaRadioGroupProps>;
27
25
 
28
26
  export const RadioGroup = forwardRef<HTMLDivElement, RadioGroupProps>(
29
- function RadioGroup(
30
- {
27
+ function RadioGroup(props, ref) {
28
+ const {
31
29
  overStyled = false,
32
30
  formLayout = "stacked",
33
31
 
@@ -52,47 +50,19 @@ export const RadioGroup = forwardRef<HTMLDivElement, RadioGroupProps>(
52
50
  children,
53
51
  readOnly,
54
52
  readOnlyComponent,
53
+ emptyValueComponent,
54
+ value,
55
+ defaultValue,
55
56
  ...rest
56
- },
57
- ref,
58
- ) {
57
+ } = props;
59
58
  const sanitizedProps = filterStylingProps(rest, overStyled);
60
59
  const restRecord = sanitizedProps as Record<string, unknown>;
61
60
 
62
61
  // Delete prohibited sizing hooks from bypassing the variables
63
62
  delete restRecord["size"];
64
63
 
65
- if (readOnly && readOnlyComponent) {
66
- return (
67
- <FormControlWrapper
68
- className={className}
69
- style={style as React.CSSProperties}
70
- controlMaxWidth="var(--recursica_ui-kit_components_radio-button-item_properties_max-width)"
71
- controlMinWidth={undefined}
72
- overStyled={overStyled as true}
73
- labelElement="div"
74
- formLayout={formLayout}
75
- labelSize={labelSize}
76
- labelAlignment={labelAlignment}
77
- labelOptionalText={labelOptionalText}
78
- labelWithEditIcon={labelWithEditIcon}
79
- onLabelEditClick={onLabelEditClick}
80
- label={label}
81
- description={description}
82
- assistiveText={assistiveText}
83
- assistiveWithIcon={assistiveWithIcon}
84
- error={error}
85
- required={required}
86
- withAsterisk={withAsterisk}
87
- id={id}
88
- >
89
- {readOnlyComponent}
90
- </FormControlWrapper>
91
- );
92
- }
93
-
94
64
  return (
95
- <FormControlWrapper
65
+ <WithReadOnlyWrapper
96
66
  className={className}
97
67
  style={style as React.CSSProperties}
98
68
  controlMaxWidth="var(--recursica_ui-kit_components_radio-button-item_properties_max-width)"
@@ -113,24 +83,34 @@ export const RadioGroup = forwardRef<HTMLDivElement, RadioGroupProps>(
113
83
  required={required}
114
84
  withAsterisk={withAsterisk}
115
85
  id={id}
116
- >
117
- <MantineRadio.Group
118
- ref={ref}
119
- /* Natively bind local disabled lock dynamically */
120
- {...(sanitizedProps as unknown as MantineRadioGroupProps)}
121
- >
122
- <div className={styles.groupRoot} data-layout={formLayout}>
123
- {React.Children.map(children, (child) => {
124
- if (React.isValidElement(child)) {
125
- return React.cloneElement(child as React.ReactElement<any>, {
126
- disabled: readOnly || (child.props as any).disabled,
127
- });
128
- }
129
- return child;
130
- })}
131
- </div>
132
- </MantineRadio.Group>
133
- </FormControlWrapper>
86
+ readOnly={readOnly && !!readOnlyComponent}
87
+ readOnlyComponent={readOnlyComponent}
88
+ emptyValueComponent={emptyValueComponent}
89
+ readOnlyType="text"
90
+ readOnlyValue={value !== undefined ? value : defaultValue}
91
+ readOnlyNativeProps={props}
92
+ activeComponent={
93
+ <MantineRadio.Group
94
+ ref={ref}
95
+ /* Natively bind local disabled lock dynamically */
96
+ {...(sanitizedProps as unknown as MantineRadioGroupProps)}
97
+ disabled={readOnly || (restRecord as any).disabled}
98
+ value={value}
99
+ defaultValue={defaultValue}
100
+ >
101
+ <div className={styles.groupRoot} data-layout={formLayout}>
102
+ {React.Children.map(children, (child) => {
103
+ if (React.isValidElement(child)) {
104
+ return React.cloneElement(child as React.ReactElement<any>, {
105
+ disabled: readOnly || (child.props as any).disabled,
106
+ });
107
+ }
108
+ return child;
109
+ })}
110
+ </div>
111
+ </MantineRadio.Group>
112
+ }
113
+ />
134
114
  );
135
115
  },
136
116
  );
@@ -0,0 +1,21 @@
1
+ import React from "react";
2
+ import { Box } from "@mantine/core";
3
+ import styles from "./ReadOnlyField.module.css";
4
+
5
+ export const ReadOnlyBooleanField: React.FC<{
6
+ checked: boolean;
7
+ label?: React.ReactNode;
8
+ }> = ({ checked, label }) => {
9
+ return (
10
+ <Box
11
+ component="span"
12
+ className={styles.textField}
13
+ style={{ display: "inline-flex", gap: "8px", alignItems: "baseline" }}
14
+ >
15
+ {label && <span style={{ fontWeight: 500 }}>{label}:</span>}
16
+ <span>{checked ? "True" : "False"}</span>
17
+ </Box>
18
+ );
19
+ };
20
+
21
+ ReadOnlyBooleanField.displayName = "ReadOnlyBooleanField";
@@ -118,3 +118,46 @@ export const WithEditIcon: Story = {
118
118
  value: "Editable fixed structure",
119
119
  },
120
120
  };
121
+
122
+ /**
123
+ * Demonstrates how the generic ReadOnlyField handles mapping different internal data types.
124
+ * Currently, all types safely default to string extraction mapping into the text renderer natively.
125
+ */
126
+ export const DataTypes: Story = {
127
+ args: {},
128
+ render: () => (
129
+ <div style={{ display: "flex", flexDirection: "column", gap: "24px" }}>
130
+ <ReadOnlyField
131
+ label="Text Mapping"
132
+ type="text"
133
+ value="Standard string output"
134
+ />
135
+ <ReadOnlyField label="Number Mapping" type="number" value={1234567.89} />
136
+ <ReadOnlyField
137
+ label="Date Mapping"
138
+ type="date"
139
+ value={new Date("2026-04-28T12:00:00Z").toLocaleDateString()}
140
+ />
141
+ <ReadOnlyField
142
+ label="Boolean Mapping (True)"
143
+ type="boolean"
144
+ value={true}
145
+ />
146
+ <ReadOnlyField
147
+ label="Boolean Mapping (False)"
148
+ type="boolean"
149
+ value={false}
150
+ />
151
+ <ReadOnlyField
152
+ label="Switch Mapping (True -> On)"
153
+ type="switch"
154
+ value={true}
155
+ />
156
+ <ReadOnlyField
157
+ label="Switch Mapping (False -> Off)"
158
+ type="switch"
159
+ value={false}
160
+ />
161
+ </div>
162
+ ),
163
+ };
@@ -3,6 +3,7 @@ import React, { forwardRef } from "react";
3
3
  import {
4
4
  ReadOnlyFieldType,
5
5
  EmptyValueRenderer,
6
+ type ReadOnlyControlProps,
6
7
  } from "@recursica/adapter-common";
7
8
  import {
8
9
  filterStylingProps,
@@ -15,13 +16,19 @@ import {
15
16
  import { ReadOnlyTextField } from "./ReadOnlyTextField";
16
17
 
17
18
  export interface RecursicaReadOnlyFieldProps
18
- extends Omit<FormControlWrapperProps, "children" | "overStyled"> {
19
+ extends Omit<
20
+ FormControlWrapperProps,
21
+ "children" | "overStyled" | "controlMaxWidth" | "controlMinWidth"
22
+ >,
23
+ Pick<ReadOnlyControlProps, "emptyValueComponent"> {
19
24
  /** The specific value to be rendered in read-only mode explicitly matching the original field input */
20
25
  value?: any;
21
26
  /** The data type formatting rules bounding how the string is presented to the user */
22
27
  type?: ReadOnlyFieldType;
23
- /** Custom renderer explicitly responsible for formatting missing/empty value mappings (overriding default 'N/A') */
24
- emptyValueComponent?: React.ElementType<{ value?: any }>;
28
+ /** Pass the native maximum width design variable dynamically bounding the specific wrapper width exclusively. */
29
+ controlMaxWidth?: string | undefined;
30
+ /** Pass the native minimum width design variable dynamically bounding the specific wrapper width exclusively. */
31
+ controlMinWidth?: string | undefined;
25
32
  }
26
33
 
27
34
  export type ReadOnlyFieldProps =
@@ -49,6 +56,26 @@ export const ReadOnlyField = forwardRef<HTMLDivElement, ReadOnlyFieldProps>(
49
56
  const displayValue = isValueEmpty ? <Renderer value={value} /> : value;
50
57
 
51
58
  switch (type) {
59
+ case "boolean":
60
+ content = (
61
+ <ReadOnlyTextField
62
+ value={
63
+ value === true ? "True" : value === false ? "False" : displayValue
64
+ }
65
+ overStyled={overStyled as true}
66
+ />
67
+ );
68
+ break;
69
+ case "switch":
70
+ content = (
71
+ <ReadOnlyTextField
72
+ value={
73
+ value === true ? "On" : value === false ? "Off" : displayValue
74
+ }
75
+ overStyled={overStyled as true}
76
+ />
77
+ );
78
+ break;
52
79
  case "text":
53
80
  default:
54
81
  content = (
@@ -0,0 +1,21 @@
1
+ import React from "react";
2
+ import { Box } from "@mantine/core";
3
+ import styles from "./ReadOnlyField.module.css";
4
+
5
+ export const ReadOnlySwitchField: React.FC<{
6
+ checked: boolean;
7
+ label?: React.ReactNode;
8
+ }> = ({ checked, label }) => {
9
+ return (
10
+ <Box
11
+ component="span"
12
+ className={styles.textField}
13
+ style={{ display: "inline-flex", gap: "8px", alignItems: "baseline" }}
14
+ >
15
+ {label && <span style={{ fontWeight: 500 }}>{label}:</span>}
16
+ <span>{checked ? "On" : "Off"}</span>
17
+ </Box>
18
+ );
19
+ };
20
+
21
+ ReadOnlySwitchField.displayName = "ReadOnlySwitchField";
@@ -37,7 +37,9 @@ export const ReadOnlyTextField: React.FC<ReadOnlyTextFieldProps> = ({
37
37
  {value != null
38
38
  ? React.isValidElement(value)
39
39
  ? value
40
- : String(value)
40
+ : Array.isArray(value)
41
+ ? value.join(", ")
42
+ : String(value)
41
43
  : ""}
42
44
  </Box>
43
45
  );
@@ -12,7 +12,11 @@ export interface WithReadOnlyWrapperProps
12
12
  /** Injects whether the field defaults to reading mode natively */
13
13
  readOnly?: boolean;
14
14
  /** Explicit React component directly overtaking the baseline text renderer when read-only mode is active */
15
- readOnlyComponent?: React.ReactNode;
15
+ readOnlyComponent?: React.ElementType<any>;
16
+ /** Custom renderer explicitly responsible for formatting missing/empty value mappings (overriding default 'N/A') */
17
+ emptyValueComponent?: React.ElementType<{ value?: any }>;
18
+ /** The full native props dictionary securely forwarded into the custom readOnlyComponent if active */
19
+ readOnlyNativeProps?: any;
16
20
  /** Instructs the underlying generic ReadOnlyField which raw visual structure to bridge automatically */
17
21
  readOnlyType?: ReadOnlyFieldType;
18
22
  /** The isolated raw data value intercepted explicitly into the ReadOnly formatters */
@@ -36,6 +40,8 @@ export const WithReadOnlyWrapper = forwardRef<
36
40
  readOnlyComponent,
37
41
  readOnlyType = "text",
38
42
  readOnlyValue,
43
+ readOnlyNativeProps,
44
+ emptyValueComponent,
39
45
  activeComponent,
40
46
  overStyled,
41
47
  onLabelEditClick,
@@ -45,6 +51,7 @@ export const WithReadOnlyWrapper = forwardRef<
45
51
  ) {
46
52
  if (readOnly) {
47
53
  if (readOnlyComponent) {
54
+ const ReadOnlyComponent = readOnlyComponent;
48
55
  return (
49
56
  <FormControlWrapper
50
57
  ref={ref}
@@ -52,7 +59,7 @@ export const WithReadOnlyWrapper = forwardRef<
52
59
  onLabelEditClick={onLabelEditClick}
53
60
  overStyled={overStyled as true}
54
61
  >
55
- {readOnlyComponent}
62
+ <ReadOnlyComponent {...readOnlyNativeProps} />
56
63
  </FormControlWrapper>
57
64
  );
58
65
  }
@@ -62,6 +69,7 @@ export const WithReadOnlyWrapper = forwardRef<
62
69
  ref={ref}
63
70
  type={readOnlyType}
64
71
  value={readOnlyValue}
72
+ emptyValueComponent={emptyValueComponent}
65
73
  onLabelEditClick={onLabelEditClick}
66
74
  overStyled={overStyled as true}
67
75
  {...wrapperProps}
@@ -1,3 +1,5 @@
1
1
  export * from "./ReadOnlyField";
2
2
  export * from "./WithReadOnlyWrapper";
3
3
  export * from "./ReadOnlyTextField";
4
+ export * from "./ReadOnlySwitchField";
5
+ export * from "./ReadOnlyBooleanField";
@@ -6,11 +6,7 @@ const meta: Meta<typeof SegmentedControl> = {
6
6
  title: "UI-Kit/🚧 SegmentedControl",
7
7
  component: SegmentedControl,
8
8
  tags: ["autodocs"],
9
- argTypes: {
10
- disabled: {
11
- control: "boolean",
12
- },
13
- },
9
+ argTypes: {},
14
10
  };
15
11
 
16
12
  export default meta;
@@ -6,11 +6,7 @@ const meta: Meta<typeof Slider> = {
6
6
  title: "UI-Kit/🚧 Slider",
7
7
  component: Slider,
8
8
  tags: ["autodocs"],
9
- argTypes: {
10
- disabled: {
11
- control: "boolean",
12
- },
13
- },
9
+ argTypes: {},
14
10
  };
15
11
 
16
12
  export default meta;
@@ -69,7 +69,8 @@ export default meta;
69
69
  type Story = StoryObj<StackStoryProps>;
70
70
 
71
71
  export const Default: Story = {
72
- render: (args) => (
72
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
73
+ render: ({ withLayer, layer, ...args }: any) => (
73
74
  <Stack {...args}>
74
75
  <Button variant="solid">Primary Block</Button>
75
76
  <Button variant="outline">Secondary Block</Button>
@@ -82,7 +83,8 @@ export const StaticGapSmall: Story = {
82
83
  args: {
83
84
  gap: "rec-sm",
84
85
  },
85
- render: (args) => (
86
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
87
+ render: ({ withLayer, layer, ...args }: any) => (
86
88
  <Stack {...args}>
87
89
  <Button variant="solid">Item 1</Button>
88
90
  <Button variant="solid">Item 2</Button>
@@ -95,7 +97,8 @@ export const StaticGapLarge: Story = {
95
97
  args: {
96
98
  gap: "rec-xl",
97
99
  },
98
- render: (args) => (
100
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
101
+ render: ({ withLayer, layer, ...args }: any) => (
99
102
  <Stack {...args}>
100
103
  <Button variant="solid">Item 1</Button>
101
104
  <Button variant="solid">Item 2</Button>
@@ -190,18 +190,10 @@
190
190
 
191
191
  .groupRoot {
192
192
  display: flex;
193
+ flex-direction: column;
194
+ align-items: flex-start;
193
195
  gap: var(
194
196
  --recursica_ui-kit_components_switch_properties_label-switch-gap,
195
197
  16px
196
198
  );
197
199
  }
198
-
199
- .groupRoot[data-layout="stacked"] {
200
- flex-direction: column;
201
- align-items: flex-start;
202
- }
203
-
204
- .groupRoot[data-layout="side-by-side"] {
205
- flex-direction: row;
206
- align-items: center;
207
- }
@@ -1,5 +1,6 @@
1
1
  import type { Meta, StoryObj } from "@storybook/react";
2
2
  import { Switch } from "./Switch";
3
+ import { Stack } from "../Stack/Stack";
3
4
 
4
5
  const meta: Meta<typeof Switch> = {
5
6
  title: "UI-Kit/Switch",
@@ -24,9 +25,6 @@ To render a standard switch:
24
25
  },
25
26
  },
26
27
  argTypes: {
27
- disabled: {
28
- control: "boolean",
29
- },
30
28
  checked: {
31
29
  control: "boolean",
32
30
  },
@@ -44,6 +42,9 @@ To render a standard switch:
44
42
  description:
45
43
  "Toggles structural read-only data presentation bypassing interaction boundaries completely.",
46
44
  },
45
+ controlMaxWidth: { table: { disable: true } },
46
+ controlMinWidth: { table: { disable: true } },
47
+ emptyValueComponent: { table: { disable: true } },
47
48
  },
48
49
  };
49
50
 
@@ -56,41 +57,55 @@ export const Default: Story = {
56
57
  disabled: false,
57
58
  label: "Standard Switch",
58
59
  },
60
+ // We explicitly destructure global Storybook injected arguments so they do not leak into the DOM natively.
61
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
62
+ render: ({ withLayer, layer, ...args }: any) => <Switch {...args} />,
63
+ };
64
+
65
+ export const SideBySideLayout: Story = {
66
+ args: {
67
+ label: "Opt-in form alignment",
68
+ formLayout: "side-by-side",
69
+ },
70
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
71
+ render: ({ withLayer, layer, ...args }: any) => <Switch {...args} />,
59
72
  };
60
73
 
61
74
  export const StaticVariations: Story = {
62
75
  args: {},
63
76
  render: () => (
64
- <div
65
- style={{
66
- display: "flex",
67
- flexDirection: "column",
68
- gap: "24px",
69
- }}
70
- >
77
+ <Stack gap="xl">
71
78
  <Switch label="Default Unchecked State" />
72
79
  <Switch label="Checked State" defaultChecked />
73
80
  <Switch label="Disabled Unchecked" disabled />
74
81
  <Switch label="Disabled Checked" defaultChecked disabled />
75
- </div>
82
+ </Stack>
76
83
  ),
77
84
  };
78
85
 
79
- export const StaticReadOnly: Story = {
80
- args: {},
81
- render: () => (
82
- <div
83
- style={{
84
- display: "flex",
85
- flexDirection: "column",
86
- gap: "24px",
87
- }}
88
- >
89
- <Switch
90
- label="Read Only Switch"
91
- readOnly
92
- readOnlyComponent={<span>Enabled</span>}
93
- />
94
- </div>
86
+ export const ReadOnly: Story = {
87
+ args: {
88
+ ...Default.args,
89
+ readOnly: true,
90
+ },
91
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
92
+ render: ({ withLayer, layer, ...args }: any) => <Switch {...args} />,
93
+ };
94
+
95
+ export const CustomReadOnly: Story = {
96
+ args: {
97
+ ...Default.args,
98
+ readOnly: true,
99
+ },
100
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
101
+ render: ({ withLayer, layer, ...args }: any) => (
102
+ <Switch
103
+ {...args}
104
+ readOnlyComponent={({ checked, label }) => (
105
+ <span style={{ fontWeight: "bold", color: checked ? "green" : "red" }}>
106
+ {label} {checked ? "ENABLED" : "DISABLED"}
107
+ </span>
108
+ )}
109
+ />
95
110
  ),
96
111
  };