@recursica/mantine-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 (140) 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 +660 -586
  7. package/dist/mantine-adapter.js.map +1 -1
  8. package/dist/src/components/Checkbox/CheckboxGroup.d.ts +1 -1
  9. package/dist/src/components/Chip/Chip.d.ts +14 -2
  10. package/package.json +5 -2
  11. package/src/OverStyling.stories.tsx +174 -0
  12. package/src/Version.stories.tsx +56 -0
  13. package/src/components/Accordion/ACCORDION_IMPLEMENTATION_NOTES.md +48 -0
  14. package/src/components/Accordion/Accordion.module.css +261 -0
  15. package/src/components/Accordion/Accordion.stories.tsx +145 -0
  16. package/src/components/Accordion/Accordion.tsx +200 -0
  17. package/src/components/Accordion/index.ts +1 -0
  18. package/src/components/AssistiveElement/AssistiveElement.module.css +111 -0
  19. package/src/components/AssistiveElement/AssistiveElement.stories.tsx +73 -0
  20. package/src/components/AssistiveElement/AssistiveElement.tsx +91 -0
  21. package/src/components/Avatar/AVATAR_IMPLEMENTATION_NOTES.md +24 -0
  22. package/src/components/Avatar/Avatar.module.css +340 -0
  23. package/src/components/Avatar/Avatar.stories.tsx +102 -0
  24. package/src/components/Avatar/Avatar.tsx +100 -0
  25. package/src/components/Avatar/index.ts +1 -0
  26. package/src/components/Badge/BADGE_IMPLEMENTATION_NOTES.md +18 -0
  27. package/src/components/Badge/Badge.module.css +124 -0
  28. package/src/components/Badge/Badge.stories.tsx +77 -0
  29. package/src/components/Badge/Badge.tsx +66 -0
  30. package/src/components/Breadcrumb/BREADCRUMB_IMPLEMENTATION_NOTES.md +14 -0
  31. package/src/components/Breadcrumb/Breadcrumb.module.css +26 -0
  32. package/src/components/Breadcrumb/Breadcrumb.stories.tsx +77 -0
  33. package/src/components/Breadcrumb/Breadcrumb.tsx +64 -0
  34. package/src/components/Button/Button.module.css +275 -0
  35. package/src/components/Button/Button.stories.tsx +105 -0
  36. package/src/components/Button/Button.tsx +122 -0
  37. package/src/components/Button/IMPLEMENTATION_NOTES.md +45 -0
  38. package/src/components/Button/index.ts +1 -0
  39. package/src/components/Card/CARD_IMPLEMENTATION_NOTES.md +10 -0
  40. package/src/components/Card/Card.module.css +60 -0
  41. package/src/components/Card/Card.stories.tsx +141 -0
  42. package/src/components/Card/Card.tsx +176 -0
  43. package/src/components/Checkbox/CHECKBOX_IMPLEMENTATION_NOTES.md +25 -0
  44. package/src/components/Checkbox/Checkbox.module.css +204 -0
  45. package/src/components/Checkbox/Checkbox.stories.tsx +73 -0
  46. package/src/components/Checkbox/Checkbox.tsx +111 -0
  47. package/src/components/Checkbox/CheckboxGroup.stories.tsx +138 -0
  48. package/src/components/Checkbox/CheckboxGroup.tsx +140 -0
  49. package/src/components/Chip/CHIP_IMPLEMENTATION_NOTES.md +35 -0
  50. package/src/components/Chip/Chip.module.css +218 -0
  51. package/src/components/Chip/Chip.stories.tsx +115 -0
  52. package/src/components/Chip/Chip.tsx +143 -0
  53. package/src/components/DatePicker/DatePicker.stories.tsx +22 -0
  54. package/src/components/DatePicker/DatePicker.tsx +7 -0
  55. package/src/components/Dropdown/Dropdown.stories.tsx +22 -0
  56. package/src/components/Dropdown/Dropdown.tsx +7 -0
  57. package/src/components/FileInput/FileInput.stories.tsx +22 -0
  58. package/src/components/FileInput/FileInput.tsx +7 -0
  59. package/src/components/FileUpload/FileUpload.stories.tsx +22 -0
  60. package/src/components/FileUpload/FileUpload.tsx +7 -0
  61. package/src/components/FormControlWrapper/FORMCONTROLWRAPPER_IMPLEMENTATION_NOTES.md +32 -0
  62. package/src/components/FormControlWrapper/FormControlWrapper.module.css +46 -0
  63. package/src/components/FormControlWrapper/FormControlWrapper.stories.tsx +116 -0
  64. package/src/components/FormControlWrapper/FormControlWrapper.tsx +170 -0
  65. package/src/components/HoverCard/HoverCard.stories.tsx +17 -0
  66. package/src/components/HoverCard/HoverCard.tsx +7 -0
  67. package/src/components/Label/LABEL_IMPLEMENTATION_NOTES.md +48 -0
  68. package/src/components/Label/Label.module.css +177 -0
  69. package/src/components/Label/Label.stories.tsx +123 -0
  70. package/src/components/Label/Label.tsx +132 -0
  71. package/src/components/Label/index.ts +1 -0
  72. package/src/components/Link/Link.stories.tsx +17 -0
  73. package/src/components/Link/Link.tsx +7 -0
  74. package/src/components/Loader/Loader.stories.tsx +17 -0
  75. package/src/components/Loader/Loader.tsx +7 -0
  76. package/src/components/Menu/Menu.stories.tsx +17 -0
  77. package/src/components/Menu/Menu.tsx +7 -0
  78. package/src/components/Modal/Modal.stories.tsx +17 -0
  79. package/src/components/Modal/Modal.tsx +7 -0
  80. package/src/components/NumberInput/NumberInput.stories.tsx +22 -0
  81. package/src/components/NumberInput/NumberInput.tsx +7 -0
  82. package/src/components/Pagination/Pagination.stories.tsx +17 -0
  83. package/src/components/Pagination/Pagination.tsx +7 -0
  84. package/src/components/Panel/Panel.stories.tsx +17 -0
  85. package/src/components/Panel/Panel.tsx +7 -0
  86. package/src/components/Popover/Popover.stories.tsx +17 -0
  87. package/src/components/Popover/Popover.tsx +7 -0
  88. package/src/components/Radio/Radio.stories.tsx +22 -0
  89. package/src/components/Radio/Radio.tsx +7 -0
  90. package/src/components/ReadOnlyField/READ_ONLY_FIELD_IMPLEMENTATION_NOTES.md +15 -0
  91. package/src/components/ReadOnlyField/ReadOnlyField.module.css +49 -0
  92. package/src/components/ReadOnlyField/ReadOnlyField.stories.tsx +120 -0
  93. package/src/components/ReadOnlyField/ReadOnlyField.tsx +75 -0
  94. package/src/components/ReadOnlyField/ReadOnlyTextField.tsx +46 -0
  95. package/src/components/ReadOnlyField/WithReadOnlyWrapper.tsx +85 -0
  96. package/src/components/ReadOnlyField/index.ts +3 -0
  97. package/src/components/Search/Search.stories.tsx +17 -0
  98. package/src/components/Search/Search.tsx +7 -0
  99. package/src/components/SegmentedControl/SegmentedControl.stories.tsx +22 -0
  100. package/src/components/SegmentedControl/SegmentedControl.tsx +7 -0
  101. package/src/components/Slider/Slider.stories.tsx +22 -0
  102. package/src/components/Slider/Slider.tsx +7 -0
  103. package/src/components/Stepper/Stepper.stories.tsx +17 -0
  104. package/src/components/Stepper/Stepper.tsx +7 -0
  105. package/src/components/Switch/Switch.stories.tsx +22 -0
  106. package/src/components/Switch/Switch.tsx +7 -0
  107. package/src/components/Table/Table.stories.tsx +17 -0
  108. package/src/components/Table/Table.tsx +7 -0
  109. package/src/components/Tabs/Tabs.stories.tsx +17 -0
  110. package/src/components/Tabs/Tabs.tsx +7 -0
  111. package/src/components/Text/Text.stories.tsx +88 -0
  112. package/src/components/Text/Text.tsx +54 -0
  113. package/src/components/TextArea/TextArea.stories.tsx +22 -0
  114. package/src/components/TextArea/TextArea.tsx +7 -0
  115. package/src/components/TextField/TEXTFIELD_IMPLEMENTATION_NOTES.md +19 -0
  116. package/src/components/TextField/TextField.module.css +225 -0
  117. package/src/components/TextField/TextField.stories.tsx +162 -0
  118. package/src/components/TextField/TextField.tsx +138 -0
  119. package/src/components/TimePicker/TimePicker.stories.tsx +22 -0
  120. package/src/components/TimePicker/TimePicker.tsx +7 -0
  121. package/src/components/Timeline/Timeline.stories.tsx +17 -0
  122. package/src/components/Timeline/Timeline.tsx +7 -0
  123. package/src/components/Title/Title.stories.tsx +63 -0
  124. package/src/components/Title/Title.tsx +45 -0
  125. package/src/components/Toast/Toast.stories.tsx +17 -0
  126. package/src/components/Toast/Toast.tsx +7 -0
  127. package/src/components/Tooltip/Tooltip.stories.tsx +17 -0
  128. package/src/components/Tooltip/Tooltip.tsx +7 -0
  129. package/src/components/TransferList/TransferList.stories.tsx +17 -0
  130. package/src/components/TransferList/TransferList.tsx +7 -0
  131. package/src/components/index.ts +39 -0
  132. package/src/env.d.ts +7 -0
  133. package/src/index.ts +3 -0
  134. package/src/types/index.ts +9 -0
  135. package/src/types/mantine.d.ts +7 -0
  136. package/src/types/scss-modules.d.ts +9 -0
  137. package/src/utils/ColorSchemeWrapper.tsx +27 -0
  138. package/src/utils/copyToClipboard.ts +36 -0
  139. package/src/utils/filterStylingProps.ts +139 -0
  140. package/src/utils/index.ts +1 -0
@@ -0,0 +1,35 @@
1
+ # Chip Implementation Notes
2
+
3
+ ## Architecture decisions
4
+
5
+ ### Mantine DOM Structure & Label Overrides
6
+
7
+ Mantine's `<Chip>` behaves like an input element (`radio` or `checkbox`). Under the hood, it renders:
8
+
9
+ 1. `.mantine-Chip-root` (wrapper)
10
+ 2. `input` (hidden visual structure)
11
+ 3. `.mantine-Chip-label` (The actual visible button-like pill)
12
+
13
+ Because the `.label` is the primary visual surface and handles Mantine's built-in `:hover` and active states, we direct our Recursica styling natively to `.label`.
14
+
15
+ ### Icon and Remove Implementations
16
+
17
+ The Recursica specifications for the Chip component define `--chip-icon` and `--chip-close` which map to leading and trailing states.
18
+ To achieve this without breaking Mantine's `Chip` input architecture, we wrapped the internal `children` using a standard `span` DOM strategy:
19
+
20
+ ```tsx
21
+ <span className={styles.innerWrapper}>
22
+ {icon}
23
+ <span className={styles.children}>{children}</span>
24
+ {onRemove}
25
+ </span>
26
+ ```
27
+
28
+ ### Accessibility of Remove Action
29
+
30
+ Because the Chip fundamentally functions as a `<label>` linked to an `<input>`, placing a raw interactive element like `<button>` directly inside the standard Chip sub-tree violates nested interactive element ARIA constraints in strict validators.
31
+ To accommodate this, the visual "close" icon uses a `<span>` element configured with `role="button"` and `tabIndex={0}` to hook into standard keyboard activations without triggering generic nested `<form>` conflicts native to Mantine's baseline constraints.
32
+
33
+ ### Removing Sizing Properties
34
+
35
+ During implementation, the parsed Figma design tokens natively exported specific height/padding vectors dynamically (e.g., `--recursica_ui-kit_components_chip_properties_icon-size`) rather than explicit string variants (`sm`, `md`, `lg`). Therefore, we omitted `size` conceptually from the `RecursicaChipProps` wrapper to lock down size evaluation natively against the active layer variables.
@@ -0,0 +1,218 @@
1
+ /* HARDCODED VALUES
2
+ border-style: solid; (Baseline reset)
3
+ box-sizing: border-box; (Baseline reset)
4
+ display: inline-flex; (Baseline reset)
5
+ align-items: center; (Baseline reset)
6
+ outline: none; (Accessible fallback)
7
+ padding: 0; (Override mantine default)
8
+ background: transparent; (Override mantine default)
9
+ */
10
+
11
+ .root {
12
+ /* Set preset layout variables to override mantine defaults safely */
13
+ box-sizing: border-box;
14
+ display: inline-flex;
15
+
16
+ /* Token properties */
17
+ --chip-height: max-content; /* Sizing is driven by padding and line height */
18
+ --chip-bg: var(
19
+ --recursica_ui-kit_components_chip_variants_styles_unselected_properties_colors_background
20
+ );
21
+ --chip-border: var(
22
+ --recursica_ui-kit_components_chip_variants_styles_unselected_properties_colors_border-color
23
+ );
24
+ --chip-text: var(
25
+ --recursica_ui-kit_components_chip_variants_styles_unselected_properties_colors_text
26
+ );
27
+ --chip-icon: var(
28
+ --recursica_ui-kit_components_chip_variants_styles_unselected_properties_colors_leading-icon-color
29
+ );
30
+ --chip-close: var(
31
+ --recursica_ui-kit_components_chip_variants_styles_unselected_properties_colors_close-icon-color
32
+ );
33
+
34
+ border-radius: var(
35
+ --recursica_ui-kit_components_chip_properties_border-radius
36
+ );
37
+ }
38
+
39
+ /* We target the mantine label directly because that is the visible container in Mantine's Chip */
40
+ .label {
41
+ box-sizing: border-box;
42
+ display: inline-flex;
43
+ align-items: center;
44
+ justify-content: center;
45
+
46
+ /* Reset mantine styles */
47
+ background: transparent !important; /* HARDCODE: Mantine forces background styling on label */
48
+ border-style: solid;
49
+ border-width: var(--recursica_ui-kit_components_chip_properties_border-size);
50
+ padding: var(--recursica_ui-kit_components_chip_properties_vertical-padding)
51
+ var(--recursica_ui-kit_components_chip_properties_horizontal-padding) !important;
52
+
53
+ /* Typography */
54
+ font-family: var(
55
+ --recursica_ui-kit_components_chip_properties_text_font-family
56
+ );
57
+ font-size: var(--recursica_ui-kit_components_chip_properties_text_font-size);
58
+ font-weight: var(
59
+ --recursica_ui-kit_components_chip_properties_text_font-weight
60
+ );
61
+ letter-spacing: var(
62
+ --recursica_ui-kit_components_chip_properties_text_letter-spacing
63
+ );
64
+ line-height: var(
65
+ --recursica_ui-kit_components_chip_properties_text_line-height
66
+ );
67
+ text-decoration: var(
68
+ --recursica_ui-kit_components_chip_properties_text_text-decoration
69
+ );
70
+ text-transform: var(
71
+ --recursica_ui-kit_components_chip_properties_text_text-transform
72
+ );
73
+
74
+ /* Token evaluation */
75
+ background-color: var(--chip-bg) !important;
76
+ border-color: var(--chip-border) !important;
77
+ color: var(--chip-text) !important;
78
+ min-width: var(--recursica_ui-kit_components_chip_properties_min-width);
79
+ max-width: var(--recursica_ui-kit_components_chip_properties_max-width);
80
+ box-shadow: var(
81
+ --recursica_ui-kit_components_chip_properties_elevation,
82
+ none
83
+ );
84
+
85
+ transition: all 0.2s ease;
86
+ }
87
+
88
+ /* Default (Unselected) hover/active interactions (Mantine overrides) */
89
+ .label[data-checked] {
90
+ --chip-bg: var(
91
+ --recursica_ui-kit_components_chip_variants_styles_selected_properties_colors_background
92
+ );
93
+ --chip-border: var(
94
+ --recursica_ui-kit_components_chip_variants_styles_selected_properties_colors_border-color
95
+ );
96
+ --chip-text: var(
97
+ --recursica_ui-kit_components_chip_variants_styles_selected_properties_colors_text
98
+ );
99
+ --chip-icon: var(
100
+ --recursica_ui-kit_components_chip_variants_styles_selected_properties_colors_selected-icon-color
101
+ );
102
+ --chip-close: var(
103
+ --recursica_ui-kit_components_chip_variants_styles_selected_properties_colors_close-icon-color
104
+ );
105
+ }
106
+
107
+ .root[data-error] .label {
108
+ --chip-bg: var(
109
+ --recursica_ui-kit_components_chip_variants_styles_error_properties_colors_background
110
+ );
111
+ --chip-border: var(
112
+ --recursica_ui-kit_components_chip_variants_styles_error_properties_colors_border-color
113
+ );
114
+ --chip-text: var(
115
+ --recursica_ui-kit_components_chip_variants_styles_error_properties_colors_text
116
+ );
117
+ --chip-icon: var(
118
+ --recursica_ui-kit_components_chip_variants_styles_error_properties_colors_icon
119
+ );
120
+ --chip-close: var(
121
+ --recursica_ui-kit_components_chip_variants_styles_error_properties_colors_close-icon-color
122
+ );
123
+ }
124
+
125
+ .root[data-error] .label[data-checked] {
126
+ --chip-bg: var(
127
+ --recursica_ui-kit_components_chip_variants_styles_error-selected_properties_colors_background
128
+ );
129
+ --chip-border: var(
130
+ --recursica_ui-kit_components_chip_variants_styles_error-selected_properties_colors_border-color
131
+ );
132
+ --chip-text: var(
133
+ --recursica_ui-kit_components_chip_variants_styles_error-selected_properties_colors_text
134
+ );
135
+ --chip-icon: var(
136
+ --recursica_ui-kit_components_chip_variants_styles_error-selected_properties_colors_selected-icon-color
137
+ );
138
+ --chip-close: var(
139
+ --recursica_ui-kit_components_chip_variants_styles_error-selected_properties_colors_close-icon-color
140
+ );
141
+ }
142
+
143
+ .root[data-disabled] .label {
144
+ opacity: var(--recursica_brand_states_disabled, 0.5);
145
+ cursor: not-allowed;
146
+ }
147
+
148
+ /* Override Mantine's built-in check icon colors and size if we keep it */
149
+ .mantineIconWrapper {
150
+ color: var(--chip-icon) !important;
151
+ width: var(
152
+ --recursica_ui-kit_components_chip_properties_icon-size
153
+ ) !important;
154
+ height: var(
155
+ --recursica_ui-kit_components_chip_properties_icon-size
156
+ ) !important;
157
+ margin-right: var(
158
+ --recursica_ui-kit_components_chip_properties_icon-text-gap
159
+ );
160
+ }
161
+
162
+ .innerWrapper {
163
+ display: inline-flex;
164
+ align-items: center;
165
+ gap: var(--recursica_ui-kit_components_chip_properties_icon-text-gap);
166
+ width: 100%;
167
+ }
168
+
169
+ .children {
170
+ flex-grow: 1;
171
+ text-overflow: ellipsis;
172
+ overflow: hidden;
173
+ white-space: nowrap;
174
+ }
175
+
176
+ .leadingIcon {
177
+ display: inline-flex;
178
+ align-items: center;
179
+ justify-content: center;
180
+ color: var(--chip-icon);
181
+ width: var(--recursica_ui-kit_components_chip_properties_icon-size);
182
+ height: var(--recursica_ui-kit_components_chip_properties_icon-size);
183
+ }
184
+
185
+ .leadingIcon svg {
186
+ width: 100%;
187
+ height: 100%;
188
+ }
189
+
190
+ .removeIcon {
191
+ display: inline-flex;
192
+ align-items: center;
193
+ justify-content: center;
194
+ color: var(--chip-close);
195
+ width: var(--recursica_ui-kit_components_chip_properties_close-icon-size);
196
+ height: var(--recursica_ui-kit_components_chip_properties_close-icon-size);
197
+ cursor: pointer;
198
+ border-radius: 50%; /* HARDCODE: Make focus ring rounded */
199
+ outline: none;
200
+ }
201
+
202
+ .removeIcon:focus-visible {
203
+ box-shadow: 0 0 0 2px var(--chip-border); /* HARDCODE: Focus indication */
204
+ }
205
+
206
+ .removeIcon svg {
207
+ width: 100%;
208
+ height: 100%;
209
+ }
210
+
211
+ .root[data-icon-only] .label {
212
+ padding-left: var(
213
+ --recursica_ui-kit_components_chip_properties_vertical-padding
214
+ ) !important;
215
+ padding-right: var(
216
+ --recursica_ui-kit_components_chip_properties_vertical-padding
217
+ ) !important;
218
+ }
@@ -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.