@recursica/mantine-adapter 0.5.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 (160) hide show
  1. package/.storybook/commonArgTypes.ts +148 -0
  2. package/CHANGELOG.md +69 -0
  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 +1076 -266
  7. package/dist/mantine-adapter.js.map +1 -1
  8. package/dist/src/components/Accordion/Accordion.d.ts +13 -28
  9. package/dist/src/components/AssistiveElement/AssistiveElement.d.ts +10 -0
  10. package/dist/src/components/Avatar/Avatar.d.ts +1 -1
  11. package/dist/src/components/Breadcrumb/Breadcrumb.d.ts +6 -3
  12. package/dist/src/components/Button/Button.d.ts +1 -1
  13. package/dist/src/components/Card/Card.d.ts +38 -3
  14. package/dist/src/components/Checkbox/Checkbox.d.ts +11 -3
  15. package/dist/src/components/Checkbox/CheckboxGroup.d.ts +9 -0
  16. package/dist/src/components/Chip/Chip.d.ts +14 -2
  17. package/dist/src/components/FormControlWrapper/FormControlWrapper.d.ts +18 -0
  18. package/dist/src/components/Label/Label.d.ts +21 -0
  19. package/dist/src/components/Label/index.d.ts +1 -0
  20. package/dist/src/components/ReadOnlyField/ReadOnlyField.d.ts +19 -2
  21. package/dist/src/components/ReadOnlyField/ReadOnlyTextField.d.ts +8 -0
  22. package/dist/src/components/ReadOnlyField/WithReadOnlyWrapper.d.ts +22 -0
  23. package/dist/src/components/ReadOnlyField/index.d.ts +3 -0
  24. package/dist/src/components/Text/Text.d.ts +17 -0
  25. package/dist/src/components/TextField/TextField.d.ts +8 -2
  26. package/dist/src/components/Title/Title.d.ts +13 -0
  27. package/dist/src/components/index.d.ts +3 -1
  28. package/dist/src/utils/ColorSchemeWrapper.d.ts +3 -0
  29. package/dist/src/utils/filterStylingProps.d.ts +6 -2
  30. package/package.json +9 -5
  31. package/src/OverStyling.stories.tsx +174 -0
  32. package/src/Version.stories.tsx +56 -0
  33. package/src/components/Accordion/ACCORDION_IMPLEMENTATION_NOTES.md +48 -0
  34. package/src/components/Accordion/Accordion.module.css +261 -0
  35. package/src/components/Accordion/Accordion.stories.tsx +145 -0
  36. package/src/components/Accordion/Accordion.tsx +200 -0
  37. package/src/components/Accordion/index.ts +1 -0
  38. package/src/components/AssistiveElement/AssistiveElement.module.css +111 -0
  39. package/src/components/AssistiveElement/AssistiveElement.stories.tsx +73 -0
  40. package/src/components/AssistiveElement/AssistiveElement.tsx +91 -0
  41. package/src/components/Avatar/AVATAR_IMPLEMENTATION_NOTES.md +24 -0
  42. package/src/components/Avatar/Avatar.module.css +340 -0
  43. package/src/components/Avatar/Avatar.stories.tsx +102 -0
  44. package/src/components/Avatar/Avatar.tsx +100 -0
  45. package/src/components/Avatar/index.ts +1 -0
  46. package/src/components/Badge/BADGE_IMPLEMENTATION_NOTES.md +18 -0
  47. package/src/components/Badge/Badge.module.css +124 -0
  48. package/src/components/Badge/Badge.stories.tsx +77 -0
  49. package/src/components/Badge/Badge.tsx +66 -0
  50. package/src/components/Breadcrumb/BREADCRUMB_IMPLEMENTATION_NOTES.md +14 -0
  51. package/src/components/Breadcrumb/Breadcrumb.module.css +26 -0
  52. package/src/components/Breadcrumb/Breadcrumb.stories.tsx +77 -0
  53. package/src/components/Breadcrumb/Breadcrumb.tsx +64 -0
  54. package/src/components/Button/Button.module.css +275 -0
  55. package/src/components/Button/Button.stories.tsx +105 -0
  56. package/src/components/Button/Button.tsx +122 -0
  57. package/src/components/Button/IMPLEMENTATION_NOTES.md +45 -0
  58. package/src/components/Button/index.ts +1 -0
  59. package/src/components/Card/CARD_IMPLEMENTATION_NOTES.md +10 -0
  60. package/src/components/Card/Card.module.css +60 -0
  61. package/src/components/Card/Card.stories.tsx +141 -0
  62. package/src/components/Card/Card.tsx +176 -0
  63. package/src/components/Checkbox/CHECKBOX_IMPLEMENTATION_NOTES.md +25 -0
  64. package/src/components/Checkbox/Checkbox.module.css +204 -0
  65. package/src/components/Checkbox/Checkbox.stories.tsx +73 -0
  66. package/src/components/Checkbox/Checkbox.tsx +111 -0
  67. package/src/components/Checkbox/CheckboxGroup.stories.tsx +138 -0
  68. package/src/components/Checkbox/CheckboxGroup.tsx +140 -0
  69. package/src/components/Chip/CHIP_IMPLEMENTATION_NOTES.md +35 -0
  70. package/src/components/Chip/Chip.module.css +218 -0
  71. package/src/components/Chip/Chip.stories.tsx +115 -0
  72. package/src/components/Chip/Chip.tsx +143 -0
  73. package/src/components/DatePicker/DatePicker.stories.tsx +22 -0
  74. package/src/components/DatePicker/DatePicker.tsx +7 -0
  75. package/src/components/Dropdown/Dropdown.stories.tsx +22 -0
  76. package/src/components/Dropdown/Dropdown.tsx +7 -0
  77. package/src/components/FileInput/FileInput.stories.tsx +22 -0
  78. package/src/components/FileInput/FileInput.tsx +7 -0
  79. package/src/components/FileUpload/FileUpload.stories.tsx +22 -0
  80. package/src/components/FileUpload/FileUpload.tsx +7 -0
  81. package/src/components/FormControlWrapper/FORMCONTROLWRAPPER_IMPLEMENTATION_NOTES.md +32 -0
  82. package/src/components/FormControlWrapper/FormControlWrapper.module.css +46 -0
  83. package/src/components/FormControlWrapper/FormControlWrapper.stories.tsx +116 -0
  84. package/src/components/FormControlWrapper/FormControlWrapper.tsx +170 -0
  85. package/src/components/HoverCard/HoverCard.stories.tsx +17 -0
  86. package/src/components/HoverCard/HoverCard.tsx +7 -0
  87. package/src/components/Label/LABEL_IMPLEMENTATION_NOTES.md +48 -0
  88. package/src/components/Label/Label.module.css +177 -0
  89. package/src/components/Label/Label.stories.tsx +123 -0
  90. package/src/components/Label/Label.tsx +132 -0
  91. package/src/components/Label/index.ts +1 -0
  92. package/src/components/Link/Link.stories.tsx +17 -0
  93. package/src/components/Link/Link.tsx +7 -0
  94. package/src/components/Loader/Loader.stories.tsx +17 -0
  95. package/src/components/Loader/Loader.tsx +7 -0
  96. package/src/components/Menu/Menu.stories.tsx +17 -0
  97. package/src/components/Menu/Menu.tsx +7 -0
  98. package/src/components/Modal/Modal.stories.tsx +17 -0
  99. package/src/components/Modal/Modal.tsx +7 -0
  100. package/src/components/NumberInput/NumberInput.stories.tsx +22 -0
  101. package/src/components/NumberInput/NumberInput.tsx +7 -0
  102. package/src/components/Pagination/Pagination.stories.tsx +17 -0
  103. package/src/components/Pagination/Pagination.tsx +7 -0
  104. package/src/components/Panel/Panel.stories.tsx +17 -0
  105. package/src/components/Panel/Panel.tsx +7 -0
  106. package/src/components/Popover/Popover.stories.tsx +17 -0
  107. package/src/components/Popover/Popover.tsx +7 -0
  108. package/src/components/Radio/Radio.stories.tsx +22 -0
  109. package/src/components/Radio/Radio.tsx +7 -0
  110. package/src/components/ReadOnlyField/READ_ONLY_FIELD_IMPLEMENTATION_NOTES.md +15 -0
  111. package/src/components/ReadOnlyField/ReadOnlyField.module.css +49 -0
  112. package/src/components/ReadOnlyField/ReadOnlyField.stories.tsx +120 -0
  113. package/src/components/ReadOnlyField/ReadOnlyField.tsx +75 -0
  114. package/src/components/ReadOnlyField/ReadOnlyTextField.tsx +46 -0
  115. package/src/components/ReadOnlyField/WithReadOnlyWrapper.tsx +85 -0
  116. package/src/components/ReadOnlyField/index.ts +3 -0
  117. package/src/components/Search/Search.stories.tsx +17 -0
  118. package/src/components/Search/Search.tsx +7 -0
  119. package/src/components/SegmentedControl/SegmentedControl.stories.tsx +22 -0
  120. package/src/components/SegmentedControl/SegmentedControl.tsx +7 -0
  121. package/src/components/Slider/Slider.stories.tsx +22 -0
  122. package/src/components/Slider/Slider.tsx +7 -0
  123. package/src/components/Stepper/Stepper.stories.tsx +17 -0
  124. package/src/components/Stepper/Stepper.tsx +7 -0
  125. package/src/components/Switch/Switch.stories.tsx +22 -0
  126. package/src/components/Switch/Switch.tsx +7 -0
  127. package/src/components/Table/Table.stories.tsx +17 -0
  128. package/src/components/Table/Table.tsx +7 -0
  129. package/src/components/Tabs/Tabs.stories.tsx +17 -0
  130. package/src/components/Tabs/Tabs.tsx +7 -0
  131. package/src/components/Text/Text.stories.tsx +88 -0
  132. package/src/components/Text/Text.tsx +54 -0
  133. package/src/components/TextArea/TextArea.stories.tsx +22 -0
  134. package/src/components/TextArea/TextArea.tsx +7 -0
  135. package/src/components/TextField/TEXTFIELD_IMPLEMENTATION_NOTES.md +19 -0
  136. package/src/components/TextField/TextField.module.css +225 -0
  137. package/src/components/TextField/TextField.stories.tsx +162 -0
  138. package/src/components/TextField/TextField.tsx +138 -0
  139. package/src/components/TimePicker/TimePicker.stories.tsx +22 -0
  140. package/src/components/TimePicker/TimePicker.tsx +7 -0
  141. package/src/components/Timeline/Timeline.stories.tsx +17 -0
  142. package/src/components/Timeline/Timeline.tsx +7 -0
  143. package/src/components/Title/Title.stories.tsx +63 -0
  144. package/src/components/Title/Title.tsx +45 -0
  145. package/src/components/Toast/Toast.stories.tsx +17 -0
  146. package/src/components/Toast/Toast.tsx +7 -0
  147. package/src/components/Tooltip/Tooltip.stories.tsx +17 -0
  148. package/src/components/Tooltip/Tooltip.tsx +7 -0
  149. package/src/components/TransferList/TransferList.stories.tsx +17 -0
  150. package/src/components/TransferList/TransferList.tsx +7 -0
  151. package/src/components/index.ts +39 -0
  152. package/src/env.d.ts +7 -0
  153. package/src/index.ts +3 -0
  154. package/src/types/index.ts +9 -0
  155. package/src/types/mantine.d.ts +7 -0
  156. package/src/types/scss-modules.d.ts +9 -0
  157. package/src/utils/ColorSchemeWrapper.tsx +27 -0
  158. package/src/utils/copyToClipboard.ts +36 -0
  159. package/src/utils/filterStylingProps.ts +139 -0
  160. package/src/utils/index.ts +1 -0
@@ -0,0 +1,115 @@
1
+ import React from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+ import { Chip } from "./Chip";
4
+
5
+ type ChipStoryProps = React.ComponentProps<typeof Chip>;
6
+
7
+ const meta: Meta<ChipStoryProps> = {
8
+ title: "UI-Kit/Chip",
9
+ component: Chip,
10
+ tags: ["autodocs"],
11
+ parameters: {
12
+ docs: {
13
+ description: {
14
+ component:
15
+ "The Chip component is used to represent interactive selections, descriptive tags, or dynamic filters natively bounded to Recursica variables. It can be used as a toggleable input, can render a custom leading `icon`, and handles close constraints automatically via the `onRemove` property.",
16
+ },
17
+ },
18
+ },
19
+ argTypes: {
20
+ error: {
21
+ control: "boolean",
22
+ description: "Applies the error state styling dynamically.",
23
+ },
24
+ disabled: {
25
+ control: "boolean",
26
+ description: "Applies disabled token states.",
27
+ },
28
+ checked: {
29
+ control: "boolean",
30
+ description: "Forces the visual selected state.",
31
+ },
32
+ },
33
+ };
34
+
35
+ export default meta;
36
+
37
+ type Story = StoryObj<ChipStoryProps>;
38
+
39
+ // The Default story exposing native properties for the playground.
40
+ export const Default: Story = {
41
+ args: {
42
+ children: "Default Chip",
43
+ error: false,
44
+ disabled: false,
45
+ checked: false,
46
+ },
47
+ render: (args: ChipStoryProps) => <Chip {...args} />,
48
+ };
49
+
50
+ // Static Stories for Regression Snapshots
51
+ export const Unselected: Story = {
52
+ args: {
53
+ children: "Unselected",
54
+ checked: false,
55
+ },
56
+ render: (args: ChipStoryProps) => <Chip {...args} />,
57
+ };
58
+
59
+ export const Selected: Story = {
60
+ args: {
61
+ children: "Selected",
62
+ checked: true,
63
+ },
64
+ render: (args: ChipStoryProps) => <Chip {...args} onChange={() => {}} />,
65
+ };
66
+
67
+ export const ErrorState: Story = {
68
+ args: {
69
+ children: "Error",
70
+ error: true,
71
+ checked: false,
72
+ },
73
+ render: (args: ChipStoryProps) => <Chip {...args} />,
74
+ };
75
+
76
+ export const ErrorSelected: Story = {
77
+ args: {
78
+ children: "Error Selected",
79
+ error: true,
80
+ checked: true,
81
+ },
82
+ render: (args: ChipStoryProps) => <Chip {...args} onChange={() => {}} />,
83
+ };
84
+
85
+ export const Removable: Story = {
86
+ args: {
87
+ children: "Dismissible",
88
+ checked: false,
89
+ onRemove: () => console.log("Removal Action Triggered"),
90
+ },
91
+ render: (args: ChipStoryProps) => <Chip {...args} />,
92
+ };
93
+
94
+ export const WithLeadingIcon: Story = {
95
+ args: {
96
+ children: "Leading Icon",
97
+ checked: false,
98
+ icon: (
99
+ <svg
100
+ xmlns="http://www.w3.org/2000/svg"
101
+ viewBox="0 0 24 24"
102
+ fill="none"
103
+ stroke="currentColor"
104
+ strokeWidth="2"
105
+ strokeLinecap="round"
106
+ strokeLinejoin="round"
107
+ >
108
+ <circle cx="12" cy="12" r="10"></circle>
109
+ <path d="M12 8v4"></path>
110
+ <path d="M12 16h.01"></path>
111
+ </svg>
112
+ ),
113
+ },
114
+ render: (args: ChipStoryProps) => <Chip {...args} />,
115
+ };
@@ -0,0 +1,143 @@
1
+ import React, { forwardRef } from "react";
2
+ import {
3
+ Chip as MantineChip,
4
+ type ChipProps as MantineChipProps,
5
+ } from "@mantine/core";
6
+ import {
7
+ filterStylingProps,
8
+ type RecursicaOverStyled,
9
+ } from "../../utils/filterStylingProps";
10
+ import styles from "./Chip.module.css";
11
+
12
+ export interface RecursicaChipProps {
13
+ /** Enables the error state styling */
14
+ error?: boolean;
15
+
16
+ /** Leading icon content */
17
+ icon?: React.ReactNode;
18
+
19
+ /** Called when the remove (X) icon is clicked. If provided, the remove icon will be displayed. */
20
+ onRemove?: (e: React.MouseEvent<HTMLSpanElement, MouseEvent>) => void;
21
+
22
+ /** Screen reader label for the remove button. Defaults to 'Remove' */
23
+ removeLabel?: string;
24
+ }
25
+
26
+ export type ChipProps = RecursicaOverStyled<
27
+ Omit<MantineChipProps, "variant" | "size" | "color" | "radius"> &
28
+ RecursicaChipProps
29
+ >;
30
+
31
+ function CloseIcon(props: React.ComponentPropsWithoutRef<"svg">) {
32
+ return (
33
+ <svg
34
+ xmlns="http://www.w3.org/2000/svg"
35
+ width="24"
36
+ height="24"
37
+ viewBox="0 0 24 24"
38
+ fill="none"
39
+ stroke="currentColor"
40
+ strokeWidth="2"
41
+ strokeLinecap="round"
42
+ strokeLinejoin="round"
43
+ {...props}
44
+ >
45
+ <line x1="18" y1="6" x2="6" y2="18"></line>
46
+ <line x1="6" y1="6" x2="18" y2="18"></line>
47
+ </svg>
48
+ );
49
+ }
50
+
51
+ export const Chip = forwardRef<HTMLInputElement, ChipProps>(function Chip(
52
+ {
53
+ error = false,
54
+ icon,
55
+ onRemove,
56
+ removeLabel = "Remove",
57
+ children,
58
+ overStyled = false,
59
+ ...rest
60
+ },
61
+ ref,
62
+ ) {
63
+ const sanitizedProps = filterStylingProps(rest, overStyled);
64
+ const restRecord = sanitizedProps as Record<string, unknown>;
65
+
66
+ const mergedClassNames: Partial<Record<string, string>> = {
67
+ root: styles.root,
68
+ label: styles.label,
69
+ input: styles.input,
70
+ iconWrapper: styles.mantineIconWrapper,
71
+ checkIcon: styles.checkIcon,
72
+ };
73
+
74
+ const classNamesProp = restRecord.classNames;
75
+ if (
76
+ classNamesProp &&
77
+ typeof classNamesProp === "object" &&
78
+ !Array.isArray(classNamesProp)
79
+ ) {
80
+ const o = classNamesProp as Partial<Record<string, string>>;
81
+ mergedClassNames.root = o.root ? `${styles.root} ${o.root}` : styles.root;
82
+ mergedClassNames.label = o.label
83
+ ? `${styles.label} ${o.label}`
84
+ : styles.label;
85
+ }
86
+
87
+ const classNameProp = restRecord.className as string | undefined;
88
+ const finalClass = classNameProp
89
+ ? `${styles.root} ${classNameProp}`
90
+ : styles.root;
91
+
92
+ // Determine state
93
+ const dataError = error ? "" : undefined;
94
+ const isIconOnly = !children && (!!icon || !!onRemove);
95
+
96
+ return (
97
+ <MantineChip
98
+ ref={ref}
99
+ className={finalClass}
100
+ classNames={mergedClassNames}
101
+ {...(dataError !== undefined ? { "data-error": "" } : {})}
102
+ {...(isIconOnly ? { "data-icon-only": "" } : {})}
103
+ {...sanitizedProps}
104
+ >
105
+ <span className={styles.innerWrapper}>
106
+ {icon && (
107
+ <span className={styles.leadingIcon} aria-hidden>
108
+ {icon}
109
+ </span>
110
+ )}
111
+
112
+ <span className={styles.children}>{children}</span>
113
+
114
+ {onRemove && (
115
+ <span
116
+ role="button"
117
+ className={styles.removeIcon}
118
+ onClick={(e) => {
119
+ e.preventDefault();
120
+ e.stopPropagation();
121
+ onRemove(e);
122
+ }}
123
+ aria-label={removeLabel}
124
+ onKeyDown={(e) => {
125
+ if (e.key === "Enter" || e.key === " ") {
126
+ e.preventDefault();
127
+ e.stopPropagation();
128
+ onRemove(
129
+ e as unknown as React.MouseEvent<HTMLSpanElement, MouseEvent>,
130
+ );
131
+ }
132
+ }}
133
+ tabIndex={0}
134
+ >
135
+ <CloseIcon />
136
+ </span>
137
+ )}
138
+ </span>
139
+ </MantineChip>
140
+ );
141
+ });
142
+
143
+ Chip.displayName = "Chip";
@@ -0,0 +1,22 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { DatePicker } from "./DatePicker";
3
+ import { ComingSoon } from "@recursica/storybook-template";
4
+
5
+ const meta: Meta<typeof DatePicker> = {
6
+ title: "UI-Kit/🚧 DatePicker",
7
+ component: DatePicker,
8
+ tags: ["autodocs"],
9
+ argTypes: {
10
+ disabled: {
11
+ control: "boolean",
12
+ },
13
+ },
14
+ };
15
+
16
+ export default meta;
17
+
18
+ type Story = StoryObj<typeof DatePicker>;
19
+
20
+ export const Default: Story = {
21
+ render: () => <ComingSoon componentName="DatePicker" />,
22
+ };
@@ -0,0 +1,7 @@
1
+ import React from "react";
2
+
3
+ export type DatePickerProps = React.HTMLAttributes<HTMLDivElement>;
4
+
5
+ export const DatePicker: React.FC<DatePickerProps> = (props) => {
6
+ return <div {...props}>DatePicker</div>;
7
+ };
@@ -0,0 +1,22 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Dropdown } from "./Dropdown";
3
+ import { ComingSoon } from "@recursica/storybook-template";
4
+
5
+ const meta: Meta<typeof Dropdown> = {
6
+ title: "UI-Kit/🚧 Dropdown",
7
+ component: Dropdown,
8
+ tags: ["autodocs"],
9
+ argTypes: {
10
+ disabled: {
11
+ control: "boolean",
12
+ },
13
+ },
14
+ };
15
+
16
+ export default meta;
17
+
18
+ type Story = StoryObj<typeof Dropdown>;
19
+
20
+ export const Default: Story = {
21
+ render: () => <ComingSoon componentName="Dropdown" />,
22
+ };
@@ -0,0 +1,7 @@
1
+ import React from "react";
2
+
3
+ export type DropdownProps = React.HTMLAttributes<HTMLDivElement>;
4
+
5
+ export const Dropdown: React.FC<DropdownProps> = (props) => {
6
+ return <div {...props}>Dropdown</div>;
7
+ };
@@ -0,0 +1,22 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { FileInput } from "./FileInput";
3
+ import { ComingSoon } from "@recursica/storybook-template";
4
+
5
+ const meta: Meta<typeof FileInput> = {
6
+ title: "UI-Kit/🚧 FileInput",
7
+ component: FileInput,
8
+ tags: ["autodocs"],
9
+ argTypes: {
10
+ disabled: {
11
+ control: "boolean",
12
+ },
13
+ },
14
+ };
15
+
16
+ export default meta;
17
+
18
+ type Story = StoryObj<typeof FileInput>;
19
+
20
+ export const Default: Story = {
21
+ render: () => <ComingSoon componentName="FileInput" />,
22
+ };
@@ -0,0 +1,7 @@
1
+ import React from "react";
2
+
3
+ export type FileInputProps = React.HTMLAttributes<HTMLDivElement>;
4
+
5
+ export const FileInput: React.FC<FileInputProps> = (props) => {
6
+ return <div {...props}>FileInput</div>;
7
+ };
@@ -0,0 +1,22 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { FileUpload } from "./FileUpload";
3
+ import { ComingSoon } from "@recursica/storybook-template";
4
+
5
+ const meta: Meta<typeof FileUpload> = {
6
+ title: "UI-Kit/🚧 FileUpload",
7
+ component: FileUpload,
8
+ tags: ["autodocs"],
9
+ argTypes: {
10
+ disabled: {
11
+ control: "boolean",
12
+ },
13
+ },
14
+ };
15
+
16
+ export default meta;
17
+
18
+ type Story = StoryObj<typeof FileUpload>;
19
+
20
+ export const Default: Story = {
21
+ render: () => <ComingSoon componentName="FileUpload" />,
22
+ };
@@ -0,0 +1,7 @@
1
+ import React from "react";
2
+
3
+ export type FileUploadProps = React.HTMLAttributes<HTMLDivElement>;
4
+
5
+ export const FileUpload: React.FC<FileUploadProps> = (props) => {
6
+ return <div {...props}>FileUpload</div>;
7
+ };
@@ -0,0 +1,32 @@
1
+ # FormControlWrapper Implementation Notes
2
+
3
+ ## Architectural Philosophy
4
+
5
+ The `FormControlWrapper` is the ultimate structural replacement for Mantine's built-in `Input.Wrapper`. By abandoning Mantine's opinionated wrappers entirely across the design system, we centralize all label tracking, error rendering, ARIA generation, and grid layouts natively inside this single component.
6
+
7
+ ### 1. Bypassing `Input.Wrapper`
8
+
9
+ Under the hood of Mantine, elements like `TextInput` heavily rely on `Input.Wrapper`. We actively discourage their use. The central tenet of Recursica Forms is to strip the UI primitive back to its "naked" form (e.g. `<Input />`, `<Checkbox />`) and encapsulate it manually inside `<FormControlWrapper>`.
10
+
11
+ - **Why?** It enforces complete layout mastery. It natively enables `formLayout="side-by-side"` and completely disables Mantine's margin collisions without requiring messy CSS hacks.
12
+
13
+ ### 2. The `cloneElement` ARIA Map
14
+
15
+ Because we tore out Mantine's `InputContext` provider (which natively glued error strings to `<input>` tags using React Context), we explicitly utilize `React.cloneElement` on the nested children inside this wrapper.
16
+
17
+ - `aria-describedby` and `aria-errormessage` are dynamically generated using `React.useId()` and physically injected back onto the provided child node. Screen readers rely strictly on this mapping to announce the assistive fields correctly.
18
+
19
+ ### 3. Strict `AssistiveElement` Coupling
20
+
21
+ We completely abandoned generic `<Input.Description>` tags. The `FormControlWrapper` directly renders `<AssistiveElement>` primitives, parsing them seamlessly mapping them to `"error"` or `"help"` variants automatically depending on the component's internal state machine.
22
+
23
+ ### 4. Dynamic Geometric Variable Payloads
24
+
25
+ Because `FormControlWrapper` acts as an agnostic grid box encompassing raw primitives (like `TextField`, `Select`), it initially stretches `100%` across horizontal bounds.
26
+
27
+ - **The Bug:** If a child `TextField` carries its own hardcoded `max-width` token, it stops expanding early, but the wrapper and `<Label>` keep expanding, causing right-aligned labels to aggressively float past the field to the screen's edge dynamically.
28
+ - **The Variable Payload Resolution:** Instead of destroying grids with `width: fit-content` arrays, primitive components are required to pass their local `max-width` tokens UP to the wrapper explicitly via React `style`:
29
+ ```tsx
30
+ <FormControlWrapper style={{ "--form-control-max-width": "var(--...)" }}>
31
+ ```
32
+ The wrapper natively respects `max-width: var(--form-control-max-width, 100%)`. This structurally unifies the bounding caps so right-aligned labels flawlessly snap tightly to the explicit boundary edge of the specific primitive it is wrapping.
@@ -0,0 +1,46 @@
1
+ /* FormControlWrapper.module.css */
2
+
3
+ .root {
4
+ display: flex;
5
+ margin: 0; /* Hardcoded structural reset */
6
+ padding: 0; /* Hardcoded structural reset */
7
+ max-width: var(--form-control-max-width, 100%);
8
+ min-width: var(--form-control-min-width, auto);
9
+ }
10
+
11
+ /* Base Structural Stacked Layout */
12
+ .root[data-form-layout="stacked"] {
13
+ flex-direction: column;
14
+ }
15
+
16
+ /* Base Structural Grid Layout */
17
+ .root[data-form-layout="side-by-side"] {
18
+ flex-direction: row;
19
+ align-items: flex-start;
20
+ flex-wrap: wrap; /* Allows collapse if screen is too thin, though standard flex width should hold */
21
+ }
22
+
23
+ /* Left structural justification defaults (push input mapping) */
24
+ .root[data-form-alignment="left"] {
25
+ /* Inherited naturally via row/column flows */
26
+ }
27
+
28
+ /* Right structural justification defaults */
29
+ .root[data-form-alignment="right"] {
30
+ /* To justify entire form control components to the right */
31
+ justify-content: flex-end;
32
+ }
33
+
34
+ .labelSection {
35
+ /* Width dimensions are strictly governed natively inside Label.module.css's sizing variant mapping */
36
+ flex-shrink: 0;
37
+ display: flex;
38
+ flex-direction: column;
39
+ }
40
+
41
+ .inputSection {
42
+ display: flex; /* Groups internal field alongside subsequent errors/descriptions */
43
+ flex-direction: column;
44
+ flex: 1; /* Consumes the remaining x-axis in grid columns natively */
45
+ min-width: 0; /* Break boundary preventing internal flex explosion */
46
+ }
@@ -0,0 +1,116 @@
1
+ import React from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+ import {
4
+ FormControlWrapper,
5
+ type FormControlWrapperProps,
6
+ } from "./FormControlWrapper";
7
+ import { TextField } from "../TextField/TextField";
8
+ import { formControlArgTypes } from "../../../.storybook/commonArgTypes";
9
+
10
+ type WrapperStoryProps = FormControlWrapperProps;
11
+
12
+ const meta: Meta<WrapperStoryProps> = {
13
+ title: "UI-Kit/FormControlWrapper",
14
+ component: FormControlWrapper,
15
+ tags: ["autodocs"],
16
+ parameters: {
17
+ docs: {
18
+ description: {
19
+ component:
20
+ "The `FormControlWrapper` is the ultimate structural replacement for Mantine's built-in `Input.Wrapper`. By abandoning Mantine's opinionated wrappers entirely, we centralize all label tracking, error rendering, ARIA generation, and grid layouts natively inside this single component.\n\n### Usage with Naked Primitives\nThis component wraps 'naked' elements like `<Input>` directly. The demonstration stories below utilize `<TextField>` as a native display vehicle, since `<TextField>` natively pipes all its properties structurally back into this wrapper.",
21
+ },
22
+ },
23
+ },
24
+ argTypes: {
25
+ ...formControlArgTypes,
26
+ error: {
27
+ control: "text",
28
+ description:
29
+ "Error string driving native assistive component and validation markers.",
30
+ },
31
+ assistiveText: {
32
+ control: "text",
33
+ description:
34
+ "Helper instructions safely dynamically anchored below the input box.",
35
+ },
36
+ assistiveWithIcon: {
37
+ control: "boolean",
38
+ },
39
+ required: {
40
+ control: "boolean",
41
+ },
42
+ },
43
+ };
44
+
45
+ export default meta;
46
+
47
+ type Story = StoryObj<WrapperStoryProps>;
48
+
49
+ const renderWithTextField = (args: WrapperStoryProps) => (
50
+ <TextField placeholder="Form Control primitive mapped..." {...args} />
51
+ );
52
+
53
+ export const Default: Story = {
54
+ args: {
55
+ label: "Account Username",
56
+ formLayout: "stacked",
57
+ assistiveText: "Validation occurs immediately natively.",
58
+ },
59
+ render: renderWithTextField,
60
+ };
61
+
62
+ export const VisualErrorState: Story = {
63
+ args: {
64
+ label: "Encryption Protocol",
65
+ formLayout: "stacked",
66
+ error: "Strict validation limits reached. Handshake rejected securely.",
67
+ },
68
+ render: renderWithTextField,
69
+ };
70
+
71
+ export const RequiredArchitecture: Story = {
72
+ args: {
73
+ label: "Root Password",
74
+ formLayout: "side-by-side",
75
+ required: true,
76
+ assistiveText: "Bypass string structure required to initiate protocol.",
77
+ },
78
+ render: renderWithTextField,
79
+ };
80
+
81
+ export const WithoutAssistiveIcons: Story = {
82
+ args: {
83
+ label: "Server Domain",
84
+ assistiveText:
85
+ "A standard text boundary without default native icon parameters bounding.",
86
+ assistiveWithIcon: false,
87
+ },
88
+ render: renderWithTextField,
89
+ };
90
+
91
+ export const NativeChildrenDirectly: Story = {
92
+ description:
93
+ "Bypassing the TextField map to show exactly how native `<input>` hooks execute inside the raw wrapper natively perfectly.",
94
+ args: {
95
+ label: "Raw HTML Checkbox",
96
+ formLayout: "side-by-side",
97
+ assistiveText: "This wraps a raw HTML input tag mapping correctly.",
98
+ },
99
+ render: (args) => (
100
+ <div
101
+ style={{
102
+ display: "flex",
103
+ gap: "10px",
104
+ alignItems: "center",
105
+ }}
106
+ >
107
+ {/* We can cleanly wrap even un-styled HTML primitives! */}
108
+ <FormControlWrapper {...args}>
109
+ <input
110
+ type="checkbox"
111
+ style={{ margin: 0, width: "16px", height: "16px" }}
112
+ />
113
+ </FormControlWrapper>
114
+ </div>
115
+ ),
116
+ };