@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
@@ -2,7 +2,9 @@ import { CheckboxProps as MantineCheckboxProps } from '@mantine/core';
2
2
  import { ReadOnlyControlProps } from '@recursica/adapter-common';
3
3
  import { CheckboxGroup } from './CheckboxGroup';
4
4
  import { RecursicaOverStyled } from '../../utils/filterStylingProps';
5
- export type RecursicaCheckboxProps = Omit<MantineCheckboxProps, "size" | "color" | "radius" | "iconColor" | "variant"> & ReadOnlyControlProps;
5
+ import { RequireAccessibleLabel } from '../../utils/RequireAccessibleLabel';
6
+ import { FormControlLayoutProps } from '../FormControlLayout/FormControlLayout';
7
+ export type RecursicaCheckboxProps = RequireAccessibleLabel<Omit<MantineCheckboxProps, "size" | "color" | "radius" | "iconColor" | "variant"> & ReadOnlyControlProps & Pick<FormControlLayoutProps, "formLayout" | "labelSize" | "controlMaxWidth" | "controlMinWidth">>;
6
8
  export type CheckboxProps = RecursicaOverStyled<RecursicaCheckboxProps>;
7
9
  type CheckboxComponent = React.ForwardRefExoticComponent<CheckboxProps & React.RefAttributes<HTMLInputElement>> & {
8
10
  Group: typeof CheckboxGroup;
@@ -0,0 +1,21 @@
1
+ import { default as React } from 'react';
2
+ export interface FormControlLayoutProps extends React.HTMLAttributes<HTMLDivElement> {
3
+ formLayout?: "stacked" | "side-by-side";
4
+ labelSize?: "default" | "small" | "md";
5
+ controlMaxWidth?: string;
6
+ controlMinWidth?: string;
7
+ leftSection?: React.ReactNode;
8
+ children?: React.ReactNode;
9
+ }
10
+ /**
11
+ * A layout component used to correctly position form inputs alongside their labels.
12
+ *
13
+ * **When to use this:**
14
+ * Typically, you should use `FormControlWrapper` instead, which uses this component
15
+ * under the hood to handle layout automatically.
16
+ *
17
+ * However, this component is useful when you need to align standalone inputs
18
+ * (like a `Switch` or `Checkbox` without a label) so they perfectly match the spacing
19
+ * and alignment of your other form fields in a `side-by-side` layout.
20
+ */
21
+ export declare const FormControlLayout: React.ForwardRefExoticComponent<FormControlLayoutProps & React.RefAttributes<HTMLDivElement>>;
@@ -3,6 +3,8 @@ import { InputWrapperProps } from '@mantine/core';
3
3
  import { RecursicaOverStyled } from '../../utils/filterStylingProps';
4
4
  import { RecursicaLabelProps } from '../Label/Label';
5
5
  export interface RecursicaFormControlWrapperProps extends RecursicaLabelProps {
6
+ /** Overall structural flow mapping the Form Control natively cascading down to Label and Input logic. */
7
+ formLayout?: "stacked" | "side-by-side";
6
8
  /** Securely replaces standard Mantine descriptions safely providing standard Assistive properties. */
7
9
  assistiveText?: React.ReactNode;
8
10
  /** Explicit toggle to suppress the Info icon rendering natively alongside the assistiveText. Defaults to true. */
@@ -2,10 +2,8 @@ import { default as React } from 'react';
2
2
  import { InputLabelProps } from '@mantine/core';
3
3
  import { RecursicaOverStyled } from '../../utils/filterStylingProps';
4
4
  export interface RecursicaLabelProps {
5
- /** Overall structural flow mapping the Form Control natively cascading down to Label and Input logic. */
6
- formLayout?: "stacked" | "side-by-side";
7
5
  /** Specifies the sizing metrics natively mapping the Label boundaries. */
8
- labelSize?: "default" | "small";
6
+ labelSize?: "default" | "small" | "md";
9
7
  /** Overall alignment directive for the label strings natively forcing Left/Right justification. */
10
8
  labelAlignment?: "left" | "right";
11
9
  /** Injects an indicator text block alongside the label. Can be boolean (`true` maps to '(Optional)') or custom React nodes. */
@@ -2,7 +2,9 @@ import { RadioProps as MantineRadioProps } from '@mantine/core';
2
2
  import { ReadOnlyControlProps } from '@recursica/adapter-common';
3
3
  import { RadioGroup } from './RadioGroup';
4
4
  import { RecursicaOverStyled } from '../../utils/filterStylingProps';
5
- export type RecursicaRadioProps = Omit<MantineRadioProps, "size" | "color" | "radius" | "iconColor" | "variant"> & ReadOnlyControlProps;
5
+ import { RequireAccessibleLabel } from '../../utils/RequireAccessibleLabel';
6
+ import { FormControlLayoutProps } from '../FormControlLayout/FormControlLayout';
7
+ export type RecursicaRadioProps = RequireAccessibleLabel<Omit<MantineRadioProps, "size" | "color" | "radius" | "iconColor" | "variant"> & ReadOnlyControlProps & Pick<FormControlLayoutProps, "formLayout" | "labelSize" | "controlMaxWidth" | "controlMinWidth">>;
6
8
  export type RadioProps = RecursicaOverStyled<RecursicaRadioProps>;
7
9
  type RadioComponent = React.ForwardRefExoticComponent<RadioProps & React.RefAttributes<HTMLInputElement>> & {
8
10
  Group: typeof RadioGroup;
@@ -0,0 +1,5 @@
1
+ import { default as React } from 'react';
2
+ export declare const ReadOnlyBooleanField: React.FC<{
3
+ checked: boolean;
4
+ label?: React.ReactNode;
5
+ }>;
@@ -1,16 +1,16 @@
1
1
  import { default as React } from 'react';
2
- import { ReadOnlyFieldType } from '@recursica/adapter-common';
2
+ import { ReadOnlyFieldType, ReadOnlyControlProps } from '@recursica/adapter-common';
3
3
  import { RecursicaOverStyled } from '../../utils/filterStylingProps';
4
4
  import { FormControlWrapperProps } from '../FormControlWrapper/FormControlWrapper';
5
- export interface RecursicaReadOnlyFieldProps extends Omit<FormControlWrapperProps, "children" | "overStyled"> {
5
+ export interface RecursicaReadOnlyFieldProps extends Omit<FormControlWrapperProps, "children" | "overStyled" | "controlMaxWidth" | "controlMinWidth">, Pick<ReadOnlyControlProps, "emptyValueComponent"> {
6
6
  /** The specific value to be rendered in read-only mode explicitly matching the original field input */
7
7
  value?: any;
8
8
  /** The data type formatting rules bounding how the string is presented to the user */
9
9
  type?: ReadOnlyFieldType;
10
- /** Custom renderer explicitly responsible for formatting missing/empty value mappings (overriding default 'N/A') */
11
- emptyValueComponent?: React.ElementType<{
12
- value?: any;
13
- }>;
10
+ /** Pass the native maximum width design variable dynamically bounding the specific wrapper width exclusively. */
11
+ controlMaxWidth?: string | undefined;
12
+ /** Pass the native minimum width design variable dynamically bounding the specific wrapper width exclusively. */
13
+ controlMinWidth?: string | undefined;
14
14
  }
15
15
  export type ReadOnlyFieldProps = RecursicaOverStyled<RecursicaReadOnlyFieldProps>;
16
16
  /**
@@ -0,0 +1,5 @@
1
+ import { default as React } from 'react';
2
+ export declare const ReadOnlySwitchField: React.FC<{
3
+ checked: boolean;
4
+ label?: React.ReactNode;
5
+ }>;
@@ -5,7 +5,13 @@ export interface WithReadOnlyWrapperProps extends Omit<FormControlWrapperProps,
5
5
  /** Injects whether the field defaults to reading mode natively */
6
6
  readOnly?: boolean;
7
7
  /** Explicit React component directly overtaking the baseline text renderer when read-only mode is active */
8
- readOnlyComponent?: React.ReactNode;
8
+ readOnlyComponent?: React.ElementType<any>;
9
+ /** Custom renderer explicitly responsible for formatting missing/empty value mappings (overriding default 'N/A') */
10
+ emptyValueComponent?: React.ElementType<{
11
+ value?: any;
12
+ }>;
13
+ /** The full native props dictionary securely forwarded into the custom readOnlyComponent if active */
14
+ readOnlyNativeProps?: any;
9
15
  /** Instructs the underlying generic ReadOnlyField which raw visual structure to bridge automatically */
10
16
  readOnlyType?: ReadOnlyFieldType;
11
17
  /** The isolated raw data value intercepted explicitly into the ReadOnly formatters */
@@ -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';
@@ -1,8 +1,10 @@
1
1
  import { SwitchProps as MantineSwitchProps } from '@mantine/core';
2
2
  import { ReadOnlyControlProps } from '@recursica/adapter-common';
3
3
  import { RecursicaOverStyled } from '../../utils/filterStylingProps';
4
+ import { RequireAccessibleLabel } from '../../utils/RequireAccessibleLabel';
5
+ import { FormControlLayoutProps } from '../FormControlLayout/FormControlLayout';
4
6
  import { SwitchGroup } from './SwitchGroup';
5
- export type RecursicaSwitchProps = Omit<MantineSwitchProps, "size" | "color" | "radius" | "variant"> & ReadOnlyControlProps;
7
+ export type RecursicaSwitchProps = RequireAccessibleLabel<Omit<MantineSwitchProps, "size" | "color" | "radius" | "variant"> & ReadOnlyControlProps & Pick<FormControlLayoutProps, "formLayout" | "labelSize" | "controlMaxWidth" | "controlMinWidth">>;
6
8
  export type SwitchProps = RecursicaOverStyled<RecursicaSwitchProps>;
7
9
  type SwitchComponent = React.ForwardRefExoticComponent<SwitchProps & React.RefAttributes<HTMLInputElement>> & {
8
10
  Group: typeof SwitchGroup;
@@ -13,6 +13,7 @@ export * from './Dropdown/Dropdown';
13
13
  export * from './FileInput/FileInput';
14
14
  export * from './FileUpload/FileUpload';
15
15
  export * from './Flex/Flex';
16
+ export * from './FormControlLayout/FormControlLayout';
16
17
  export * from './Group/Group';
17
18
  export * from './HoverCard/HoverCard';
18
19
  export * from './Link/Link';
@@ -0,0 +1,18 @@
1
+ import { ReactNode } from 'react';
2
+ /**
3
+ * Enforces accessibility by strictly requiring at least one form of labeling:
4
+ * either a visual \`label\`, an \`aria-label\`, or an \`aria-labelledby\`.
5
+ */
6
+ export type RequireAccessibleLabel<T> = T & ({
7
+ label: ReactNode;
8
+ "aria-label"?: string;
9
+ "aria-labelledby"?: string;
10
+ } | {
11
+ label?: ReactNode;
12
+ "aria-label": string;
13
+ "aria-labelledby"?: string;
14
+ } | {
15
+ label?: ReactNode;
16
+ "aria-label"?: string;
17
+ "aria-labelledby": string;
18
+ });
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "type": "git",
12
12
  "url": "git+https://github.com/borderux/recursica.git"
13
13
  },
14
- "version": "0.10.0",
14
+ "version": "0.11.0",
15
15
  "type": "module",
16
16
  "main": "./dist/mantine-adapter.cjs",
17
17
  "module": "./dist/mantine-adapter.js",
@@ -1,7 +1,6 @@
1
1
  import type { Meta, StoryObj } from "@storybook/react";
2
2
  import { AssistiveElement } from "./AssistiveElement";
3
3
  import { Layer } from "@recursica/adapter-common";
4
- import React from "react";
5
4
 
6
5
  const meta: Meta<typeof AssistiveElement> = {
7
6
  title: "UI-Kit/AssistiveElement",
@@ -37,7 +36,8 @@ export const DefaultHelp: Story = {
37
36
  assistiveVariant: "help",
38
37
  assistiveWithIcon: true,
39
38
  },
40
- render: (args) => (
39
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
40
+ render: ({ withLayer, layer, ...args }: any) => (
41
41
  <Layer layer={0} style={{ padding: "48px" }}>
42
42
  <AssistiveElement {...args} />
43
43
  </Layer>
@@ -51,7 +51,8 @@ export const ErrorState: Story = {
51
51
  assistiveVariant: "error",
52
52
  assistiveWithIcon: true,
53
53
  },
54
- render: (args) => (
54
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
55
+ render: ({ withLayer, layer, ...args }: any) => (
55
56
  <Layer layer={0} style={{ padding: "48px" }}>
56
57
  <AssistiveElement {...args} />
57
58
  </Layer>
@@ -65,7 +66,8 @@ export const NoIconHelp: Story = {
65
66
  assistiveVariant: "help",
66
67
  assistiveWithIcon: false,
67
68
  },
68
- render: (args) => (
69
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
70
+ render: ({ withLayer, layer, ...args }: any) => (
69
71
  <Layer layer={0} style={{ padding: "48px" }}>
70
72
  <AssistiveElement {...args} />
71
73
  </Layer>
@@ -42,7 +42,8 @@ export const Default: Story = {
42
42
  size: "default",
43
43
  variant: "solid",
44
44
  },
45
- render: (args) => {
45
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
46
+ render: ({ withLayer, layer, ...args }: any) => {
46
47
  return <Avatar {...args} />;
47
48
  },
48
49
  };
@@ -53,7 +54,8 @@ export const TextSolidDefault: Story = {
53
54
  variant: "solid",
54
55
  size: "default",
55
56
  },
56
- render: (args) => <Avatar {...args} />,
57
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
58
+ render: ({ withLayer, layer, ...args }: any) => <Avatar {...args} />,
57
59
  };
58
60
 
59
61
  export const ImageLarge: Story = {
@@ -61,7 +63,8 @@ export const ImageLarge: Story = {
61
63
  src: "https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=250&q=80",
62
64
  size: "large",
63
65
  },
64
- render: (args) => <Avatar {...args} />,
66
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
67
+ render: ({ withLayer, layer, ...args }: any) => <Avatar {...args} />,
65
68
  };
66
69
 
67
70
  export const IconSmallGhost: Story = {
@@ -85,7 +88,8 @@ export const IconSmallGhost: Story = {
85
88
  </svg>
86
89
  ),
87
90
  },
88
- render: (args) => <Avatar {...args} />,
91
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
92
+ render: ({ withLayer, layer, ...args }: any) => <Avatar {...args} />,
89
93
  };
90
94
 
91
95
  export const LayerOneOutline: Story = {
@@ -94,7 +98,8 @@ export const LayerOneOutline: Story = {
94
98
  variant: "outline",
95
99
  size: "default",
96
100
  },
97
- render: (args) => (
101
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
102
+ render: ({ withLayer, layer, ...args }: any) => (
98
103
  <Layer layer={1} style={{ padding: "24px", display: "inline-block" }}>
99
104
  <Avatar {...args} />
100
105
  </Layer>
@@ -27,7 +27,8 @@ export const Default: Story = {
27
27
  children: "Badge Label",
28
28
  variant: "primary-color",
29
29
  },
30
- render: (args) => {
30
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
31
+ render: ({ withLayer, layer, ...args }: any) => {
31
32
  return <Badge {...args} />;
32
33
  },
33
34
  };
@@ -20,9 +20,6 @@ const meta: Meta<ButtonStoryProps> = {
20
20
  options: ["default", "small"],
21
21
  description: "The size of the button",
22
22
  },
23
- disabled: {
24
- control: "boolean",
25
- },
26
23
  },
27
24
  };
28
25
 
@@ -37,7 +34,8 @@ export const Default: Story = {
37
34
  size: "default",
38
35
  disabled: false,
39
36
  },
40
- render: (args) => {
37
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
38
+ render: ({ withLayer, layer, ...args }: any) => {
41
39
  return <Button {...args} />;
42
40
  },
43
41
  };
@@ -161,7 +161,7 @@
161
161
  --recursica_ui-kit_components_checkbox_properties_colors_icon-color
162
162
  ) !important;
163
163
  }
164
- .input:disabled + .iconWrapper .icon {
164
+ .input:disabled + .icon {
165
165
  color: var(
166
166
  --recursica_ui-kit_components_checkbox_properties_colors_disabled-icon
167
167
  ) !important;
@@ -24,14 +24,13 @@ To render a solitary component natively:
24
24
  },
25
25
  },
26
26
  argTypes: {
27
- disabled: {
28
- control: "boolean",
29
- },
30
27
  readOnly: {
31
28
  control: "boolean",
32
29
  description:
33
30
  "Toggles structural read-only data presentation bypassing interaction boundaries completely.",
34
31
  },
32
+ controlMaxWidth: { table: { disable: true } },
33
+ controlMinWidth: { table: { disable: true } },
35
34
  },
36
35
  };
37
36
 
@@ -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 LongLabelWrap: Story = {
50
56
  args: {
51
57
  label:
@@ -71,3 +77,18 @@ export const StaticVariations: Story = {
71
77
  </div>
72
78
  ),
73
79
  };
80
+
81
+ export const ReadOnly: Story = {
82
+ args: {},
83
+ render: () => (
84
+ <div
85
+ style={{
86
+ display: "flex",
87
+ flexDirection: "column",
88
+ gap: "24px",
89
+ }}
90
+ >
91
+ <Checkbox label="Accept Terms & Conditions" defaultChecked readOnly />
92
+ </div>
93
+ ),
94
+ };
@@ -9,13 +9,25 @@ 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 "./Checkbox.module.css";
13
19
 
14
- export type RecursicaCheckboxProps = Omit<
15
- MantineCheckboxProps,
16
- "size" | "color" | "radius" | "iconColor" | "variant"
17
- > &
18
- ReadOnlyControlProps;
20
+ export type RecursicaCheckboxProps = RequireAccessibleLabel<
21
+ Omit<
22
+ MantineCheckboxProps,
23
+ "size" | "color" | "radius" | "iconColor" | "variant"
24
+ > &
25
+ ReadOnlyControlProps &
26
+ Pick<
27
+ FormControlLayoutProps,
28
+ "formLayout" | "labelSize" | "controlMaxWidth" | "controlMinWidth"
29
+ >
30
+ >;
19
31
 
20
32
  export type CheckboxProps = RecursicaOverStyled<RecursicaCheckboxProps>;
21
33
 
@@ -26,10 +38,18 @@ type CheckboxComponent = React.ForwardRefExoticComponent<
26
38
  };
27
39
 
28
40
  export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
29
- function Checkbox(
30
- { overStyled = false, readOnly, readOnlyComponent, disabled, ...rest },
31
- ref,
32
- ) {
41
+ function Checkbox(props, ref) {
42
+ const {
43
+ overStyled = false,
44
+ readOnly,
45
+ readOnlyComponent,
46
+ disabled,
47
+ formLayout,
48
+ labelSize,
49
+ controlMaxWidth,
50
+ controlMinWidth,
51
+ ...rest
52
+ } = props;
33
53
  // Checkbox doesn't use Label onLabelEditClick natively since it isn't mapped inside FormControlWrapper intrinsically.
34
54
  const sanitizedProps = filterStylingProps(rest, overStyled);
35
55
  const restRecord = sanitizedProps as Record<string, unknown>;
@@ -41,11 +61,6 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
41
61
  delete restRecord["variant"];
42
62
  delete restRecord["iconColor"];
43
63
 
44
- if (readOnly && readOnlyComponent) {
45
- // Safely fallback explicitly returning mapped elements natively without macro wrappers
46
- return <>{readOnlyComponent}</>;
47
- }
48
-
49
64
  const mergedClassNames: Partial<Record<string, string>> = {
50
65
  root: styles.root,
51
66
  body: styles.body,
@@ -85,8 +100,35 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
85
100
  ? `${styles.root} ${classNameProp}`
86
101
  : styles.root;
87
102
 
103
+ if (readOnly && !!readOnlyComponent) {
104
+ const isChecked = !!(restRecord.checked ?? restRecord.defaultChecked);
105
+ const ReadOnlyComp = readOnlyComponent;
106
+ const roNode = (
107
+ <ReadOnlyComp
108
+ {...props}
109
+ checked={isChecked}
110
+ label={restRecord.label as React.ReactNode}
111
+ />
112
+ );
113
+
114
+ if (formLayout) {
115
+ return (
116
+ <FormControlLayout
117
+ formLayout={formLayout}
118
+ labelSize={labelSize}
119
+ controlMaxWidth={controlMaxWidth}
120
+ controlMinWidth={controlMinWidth}
121
+ >
122
+ {roNode}
123
+ </FormControlLayout>
124
+ );
125
+ }
126
+
127
+ return <>{roNode}</>;
128
+ }
129
+
88
130
  // We omit Mantine's sizing/coloring so we rely strictly on variables from Checkbox.module.css
89
- return (
131
+ const checkboxNode = (
90
132
  <MantineCheckbox
91
133
  ref={ref}
92
134
  className={finalClass}
@@ -95,6 +137,21 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
95
137
  {...(sanitizedProps as unknown as MantineCheckboxProps)}
96
138
  />
97
139
  );
140
+
141
+ if (formLayout) {
142
+ return (
143
+ <FormControlLayout
144
+ formLayout={formLayout}
145
+ labelSize={labelSize}
146
+ controlMaxWidth={controlMaxWidth}
147
+ controlMinWidth={controlMinWidth}
148
+ >
149
+ {checkboxNode}
150
+ </FormControlLayout>
151
+ );
152
+ }
153
+
154
+ return checkboxNode;
98
155
  },
99
156
  ) as CheckboxComponent;
100
157
 
@@ -46,7 +46,24 @@ export default meta;
46
46
 
47
47
  type Story = StoryObj<typeof CheckboxGroup>;
48
48
 
49
- export const GroupSideBySide: Story = {
49
+ export const Default: Story = {
50
+ args: {
51
+ formLayout: "stacked",
52
+ label: "Standard Group",
53
+ },
54
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
55
+ render: function StoryRender({ withLayer, layer, ...args }: any) {
56
+ const [value, setValue] = useState<string[]>([]);
57
+ return (
58
+ <Checkbox.Group {...args} value={value} onChange={setValue}>
59
+ <Checkbox value="1" label="Option 1" />
60
+ <Checkbox value="2" label="Option 2" />
61
+ </Checkbox.Group>
62
+ );
63
+ },
64
+ };
65
+
66
+ export const SideBySideLayout: Story = {
50
67
  args: {
51
68
  formLayout: "side-by-side",
52
69
  labelOptionalText: "Recommended",
@@ -55,7 +72,8 @@ export const GroupSideBySide: Story = {
55
72
  assistiveText:
56
73
  "Select all libraries currently in use for this specific workspace configuration.",
57
74
  },
58
- render: function GroupSideBySideRender(args) {
75
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
76
+ render: function StoryRender({ withLayer, layer, ...args }: any) {
59
77
  const [value, setValue] = useState<string[]>(["react"]);
60
78
  return (
61
79
  <Checkbox.Group {...args} value={value} onChange={setValue}>
@@ -70,14 +88,15 @@ export const GroupSideBySide: Story = {
70
88
  },
71
89
  };
72
90
 
73
- export const GroupStacked: Story = {
91
+ export const StackedLayout: Story = {
74
92
  args: {
75
93
  formLayout: "stacked",
76
94
  required: true,
77
95
  label: "Execution Targets",
78
96
  error: "You must select at least one deployment target to compile.",
79
97
  },
80
- render: function GroupStackedRender(args) {
98
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
99
+ render: function StoryRender({ withLayer, layer, ...args }: any) {
81
100
  const [value, setValue] = useState<string[]>(["react"]);
82
101
  return (
83
102
  <Checkbox.Group {...args} value={value} onChange={setValue}>
@@ -92,7 +111,7 @@ export const GroupStacked: Story = {
92
111
  },
93
112
  };
94
113
 
95
- export const StaticReadOnlyGroup: Story = {
114
+ export const ReadOnly: Story = {
96
115
  args: {
97
116
  readOnly: true,
98
117
  formLayout: "stacked",
@@ -101,7 +120,8 @@ export const StaticReadOnlyGroup: Story = {
101
120
  assistiveText:
102
121
  "This structure explicitly validates native component-level DOM preservation natively mapping lock bounds safely over interaction.",
103
122
  },
104
- render: function StaticReadOnlyGroupRender(args) {
123
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
124
+ render: function StoryRender({ withLayer, layer, ...args }: any) {
105
125
  const [value, setValue] = useState<string[]>(["disabledNode"]);
106
126
  return (
107
127
  <Checkbox.Group {...args} value={value} onChange={setValue}>
@@ -117,22 +137,3 @@ export const StaticReadOnlyGroup: Story = {
117
137
  );
118
138
  },
119
139
  };
120
-
121
- export const EditableReadOnlyGroup: Story = {
122
- args: {
123
- readOnly: true,
124
- formLayout: "side-by-side",
125
- labelWithEditIcon: true,
126
- label: "Interactively Editable ReadOnly Group",
127
- assistiveText: "Users explicitly unlock DOM structures dynamically.",
128
- },
129
- render: function EditableReadOnlyGroupRender(args) {
130
- const [value, setValue] = useState<string[]>(["activeState"]);
131
- return (
132
- <Checkbox.Group {...args} value={value} onChange={setValue}>
133
- <Checkbox value="activeState" label="Locked active state" />
134
- <Checkbox value="unlockedNode" label="Locked inactive map" />
135
- </Checkbox.Group>
136
- );
137
- },
138
- };