@recursica/adapter-mantine-v8 1.0.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.
- package/.storybook/commonArgTypes.ts +148 -0
- package/ARCHITECTURE.md +18 -0
- package/CHANGELOG.md +688 -0
- package/LICENSE +21 -0
- package/OVERSTYLING.md +56 -0
- package/README.md +70 -0
- package/SETUP.md +93 -0
- package/USAGE.md +57 -0
- package/dist/adapter-mantine-v8.cjs +12 -0
- package/dist/adapter-mantine-v8.cjs.map +1 -0
- package/dist/adapter-mantine-v8.css +1 -0
- package/dist/adapter-mantine-v8.js +5759 -0
- package/dist/adapter-mantine-v8.js.map +1 -0
- package/dist/index.d.ts +2095 -0
- package/dist/test.html +1 -0
- package/dist/vite.svg +1 -0
- package/docs/PHILOSOPHY.md +39 -0
- package/llms.txt +74 -0
- package/package.json +131 -0
- package/recursica_brand.json +2677 -0
- package/recursica_tokens.json +799 -0
- package/recursica_ui-kit.json +14981 -0
- package/recursica_variables_scoped.css +30489 -0
- package/src/GlobalExemptions.modules.css +7 -0
- package/src/Introduction.stories.tsx +154 -0
- package/src/OverStyling.tsx +79 -0
- package/src/Version.tsx +44 -0
- package/src/components/Accordion/ACCORDION_IMPLEMENTATION_NOTES.md +72 -0
- package/src/components/Accordion/Accordion.module.css +367 -0
- package/src/components/Accordion/Accordion.stories.tsx +189 -0
- package/src/components/Accordion/Accordion.test.tsx +1 -0
- package/src/components/Accordion/Accordion.tsx +220 -0
- package/src/components/Accordion/USAGE.md +51 -0
- package/src/components/Accordion/index.ts +1 -0
- package/src/components/AssistiveElement/ASSISTIVEELEMENT_IMPLEMENTATION_NOTES.md +45 -0
- package/src/components/AssistiveElement/AssistiveElement.module.css +111 -0
- package/src/components/AssistiveElement/AssistiveElement.stories.tsx +68 -0
- package/src/components/AssistiveElement/AssistiveElement.test.tsx +1 -0
- package/src/components/AssistiveElement/AssistiveElement.tsx +93 -0
- package/src/components/AssistiveElement/USAGE.md +36 -0
- package/src/components/AutoComplete/AUTOCOMPLETE_IMPLEMENTATION_NOTES.md +42 -0
- package/src/components/AutoComplete/AutoComplete.module.css +430 -0
- package/src/components/AutoComplete/AutoComplete.stories.tsx +336 -0
- package/src/components/AutoComplete/AutoComplete.test.tsx +1 -0
- package/src/components/AutoComplete/AutoComplete.tsx +179 -0
- package/src/components/AutoComplete/USAGE.md +80 -0
- package/src/components/Avatar/AVATAR_IMPLEMENTATION_NOTES.md +34 -0
- package/src/components/Avatar/Avatar.module.css +336 -0
- package/src/components/Avatar/Avatar.stories.tsx +92 -0
- package/src/components/Avatar/Avatar.test.tsx +1 -0
- package/src/components/Avatar/Avatar.tsx +108 -0
- package/src/components/Avatar/USAGE.md +38 -0
- package/src/components/Avatar/index.ts +1 -0
- package/src/components/Badge/BADGE_IMPLEMENTATION_NOTES.md +18 -0
- package/src/components/Badge/Badge.module.css +127 -0
- package/src/components/Badge/Badge.stories.tsx +78 -0
- package/src/components/Badge/Badge.test.tsx +1 -0
- package/src/components/Badge/Badge.tsx +64 -0
- package/src/components/Badge/USAGE.md +56 -0
- package/src/components/Breadcrumb/BREADCRUMB_IMPLEMENTATION_NOTES.md +47 -0
- package/src/components/Breadcrumb/Breadcrumb.module.css +48 -0
- package/src/components/Breadcrumb/Breadcrumb.stories.tsx +114 -0
- package/src/components/Breadcrumb/Breadcrumb.test.tsx +1 -0
- package/src/components/Breadcrumb/Breadcrumb.tsx +85 -0
- package/src/components/Breadcrumb/USAGE.md +55 -0
- package/src/components/Breadcrumb/markCurrentPageItem.test.ts +49 -0
- package/src/components/Button/Button.module.css +446 -0
- package/src/components/Button/Button.stories.tsx +196 -0
- package/src/components/Button/Button.styleIsolation.dom.test.tsx +98 -0
- package/src/components/Button/Button.tsx +184 -0
- package/src/components/Button/IMPLEMENTATION_NOTES.md +109 -0
- package/src/components/Button/USAGE.md +80 -0
- package/src/components/Button/index.ts +1 -0
- package/src/components/Card/CARD_IMPLEMENTATION_NOTES.md +17 -0
- package/src/components/Card/Card.module.css +119 -0
- package/src/components/Card/Card.stories.tsx +131 -0
- package/src/components/Card/Card.test.tsx +1 -0
- package/src/components/Card/Card.tsx +248 -0
- package/src/components/Card/USAGE.md +63 -0
- package/src/components/Checkbox/CHECKBOX_IMPLEMENTATION_NOTES.md +25 -0
- package/src/components/Checkbox/Checkbox.module.css +267 -0
- package/src/components/Checkbox/Checkbox.stories.tsx +97 -0
- package/src/components/Checkbox/Checkbox.test.tsx +1 -0
- package/src/components/Checkbox/Checkbox.tsx +151 -0
- package/src/components/Checkbox/CheckboxGroup.stories.tsx +139 -0
- package/src/components/Checkbox/CheckboxGroup.tsx +147 -0
- package/src/components/Checkbox/USAGE.md +48 -0
- package/src/components/Chip/CHIP_IMPLEMENTATION_NOTES.md +110 -0
- package/src/components/Chip/Chip.module.css +288 -0
- package/src/components/Chip/Chip.stories.tsx +128 -0
- package/src/components/Chip/Chip.test.tsx +1 -0
- package/src/components/Chip/Chip.tsx +157 -0
- package/src/components/Chip/USAGE.md +55 -0
- package/src/components/Container/CONTAINER_IMPLEMENTATION_NOTES.md +4 -0
- package/src/components/Container/Container.module.css +7 -0
- package/src/components/Container/Container.stories.tsx +118 -0
- package/src/components/Container/Container.test.tsx +1 -0
- package/src/components/Container/Container.tsx +69 -0
- package/src/components/Container/USAGE.md +47 -0
- package/src/components/DatePicker/DATEPICKER_IMPLEMENTATION_NOTES.md +31 -0
- package/src/components/DatePicker/DatePicker.icons.tsx +27 -0
- package/src/components/DatePicker/DatePicker.module.css +566 -0
- package/src/components/DatePicker/DatePicker.stories.tsx +158 -0
- package/src/components/DatePicker/DatePicker.test.tsx +1 -0
- package/src/components/DatePicker/DatePicker.tsx +218 -0
- package/src/components/DatePicker/USAGE.md +49 -0
- package/src/components/Dropdown/BareDropdown.tsx +97 -0
- package/src/components/Dropdown/DROPDOWN_IMPLEMENTATION_NOTES.md +12 -0
- package/src/components/Dropdown/Dropdown.module.css +477 -0
- package/src/components/Dropdown/Dropdown.stories.tsx +252 -0
- package/src/components/Dropdown/Dropdown.test.tsx +1 -0
- package/src/components/Dropdown/Dropdown.tsx +213 -0
- package/src/components/Dropdown/USAGE.md +69 -0
- package/src/components/FileInput/FILEINPUT_IMPLEMENTATION_NOTES.md +153 -0
- package/src/components/FileInput/FileInput.module.css +280 -0
- package/src/components/FileInput/FileInput.stories.tsx +313 -0
- package/src/components/FileInput/FileInput.test.tsx +1 -0
- package/src/components/FileInput/FileInput.tsx +417 -0
- package/src/components/FileInput/USAGE.md +145 -0
- package/src/components/FileUpload/FILEUPLOAD_IMPLEMENTATION_NOTES.md +244 -0
- package/src/components/FileUpload/FileUpload.module.css +205 -0
- package/src/components/FileUpload/FileUpload.stories.tsx +362 -0
- package/src/components/FileUpload/FileUpload.test.tsx +1 -0
- package/src/components/FileUpload/FileUpload.tsx +354 -0
- package/src/components/FileUpload/USAGE.md +199 -0
- package/src/components/Flex/FLEX_IMPLEMENTATION_NOTES.md +4 -0
- package/src/components/Flex/Flex.module.css +7 -0
- package/src/components/Flex/Flex.stories.tsx +128 -0
- package/src/components/Flex/Flex.test.tsx +1 -0
- package/src/components/Flex/Flex.tsx +75 -0
- package/src/components/Flex/USAGE.md +48 -0
- package/src/components/FormControlLayout/FormControlLayout.module.css +90 -0
- package/src/components/FormControlLayout/FormControlLayout.stories.tsx +118 -0
- package/src/components/FormControlLayout/FormControlLayout.test.tsx +1 -0
- package/src/components/FormControlLayout/FormControlLayout.tsx +74 -0
- package/src/components/FormControlLayout/USAGE.md +40 -0
- package/src/components/FormControlWrapper/FORMCONTROLWRAPPER_IMPLEMENTATION_NOTES.md +32 -0
- package/src/components/FormControlWrapper/FormControlWrapper.module.css +75 -0
- package/src/components/FormControlWrapper/FormControlWrapper.stories.tsx +129 -0
- package/src/components/FormControlWrapper/FormControlWrapper.test.tsx +1 -0
- package/src/components/FormControlWrapper/FormControlWrapper.tsx +151 -0
- package/src/components/FormControlWrapper/USAGE.md +47 -0
- package/src/components/Grid/GRID_IMPLEMENTATION_NOTES.md +18 -0
- package/src/components/Grid/Grid.module.css +11 -0
- package/src/components/Grid/Grid.stories.tsx +206 -0
- package/src/components/Grid/Grid.test.tsx +1 -0
- package/src/components/Grid/Grid.tsx +140 -0
- package/src/components/Grid/USAGE.md +47 -0
- package/src/components/Group/GROUP_IMPLEMENTATION_NOTES.md +4 -0
- package/src/components/Group/Group.module.css +7 -0
- package/src/components/Group/Group.stories.tsx +120 -0
- package/src/components/Group/Group.test.tsx +1 -0
- package/src/components/Group/Group.tsx +60 -0
- package/src/components/Group/USAGE.md +48 -0
- package/src/components/Heading/HEADING_IMPLEMENTATION_NOTES.md +18 -0
- package/src/components/Heading/Heading.module.css +13 -0
- package/src/components/Heading/Heading.stories.tsx +50 -0
- package/src/components/Heading/Heading.test.tsx +1 -0
- package/src/components/Heading/Heading.tsx +52 -0
- package/src/components/Heading/USAGE.md +36 -0
- package/src/components/HoverCard/HOVERCARD_IMPLEMENTATION_NOTES.md +77 -0
- package/src/components/HoverCard/HoverCard.module.css +90 -0
- package/src/components/HoverCard/HoverCard.stories.tsx +172 -0
- package/src/components/HoverCard/HoverCard.test.tsx +1 -0
- package/src/components/HoverCard/HoverCard.tsx +133 -0
- package/src/components/HoverCard/USAGE.md +52 -0
- package/src/components/Label/LABEL_IMPLEMENTATION_NOTES.md +48 -0
- package/src/components/Label/Label.module.css +141 -0
- package/src/components/Label/Label.stories.tsx +131 -0
- package/src/components/Label/Label.test.tsx +1 -0
- package/src/components/Label/Label.tsx +113 -0
- package/src/components/Label/USAGE.md +47 -0
- package/src/components/Label/index.ts +1 -0
- package/src/components/Layer/USAGE.md +54 -0
- package/src/components/Link/IMPLEMENTATION_NOTES.md +19 -0
- package/src/components/Link/Link.module.css +110 -0
- package/src/components/Link/Link.stories.tsx +58 -0
- package/src/components/Link/Link.test.tsx +1 -0
- package/src/components/Link/Link.tsx +67 -0
- package/src/components/Link/USAGE.md +56 -0
- package/src/components/Link/index.ts +1 -0
- package/src/components/Loader/LOADER_IMPLEMENTATION_NOTES.md +24 -0
- package/src/components/Loader/Loader.animate.dom.test.tsx +82 -0
- package/src/components/Loader/Loader.module.css +63 -0
- package/src/components/Loader/Loader.stories.tsx +123 -0
- package/src/components/Loader/Loader.test.tsx +1 -0
- package/src/components/Loader/Loader.tsx +69 -0
- package/src/components/Loader/USAGE.md +44 -0
- package/src/components/Menu/MENU_IMPLEMENTATION_NOTES.md +88 -0
- package/src/components/Menu/Menu.module.css +280 -0
- package/src/components/Menu/Menu.stories.tsx +371 -0
- package/src/components/Menu/Menu.test.tsx +1 -0
- package/src/components/Menu/Menu.tsx +373 -0
- package/src/components/Menu/USAGE.md +55 -0
- package/src/components/Modal/MODAL_IMPLEMENTATION_NOTES.md +32 -0
- package/src/components/Modal/Modal.module.css +302 -0
- package/src/components/Modal/Modal.stories.tsx +105 -0
- package/src/components/Modal/Modal.test.tsx +1 -0
- package/src/components/Modal/Modal.tsx +368 -0
- package/src/components/Modal/USAGE.md +58 -0
- package/src/components/Modal/index.ts +1 -0
- package/src/components/NumberInput/NUMBER_INPUT_IMPLEMENTATION_NOTES.md +24 -0
- package/src/components/NumberInput/NumberInput.module.css +302 -0
- package/src/components/NumberInput/NumberInput.stories.tsx +96 -0
- package/src/components/NumberInput/NumberInput.test.tsx +1 -0
- package/src/components/NumberInput/NumberInput.tsx +157 -0
- package/src/components/NumberInput/USAGE.md +52 -0
- package/src/components/Pagination/IMPLEMENTATION_NOTES.md +16 -0
- package/src/components/Pagination/Pagination.icons.tsx +66 -0
- package/src/components/Pagination/Pagination.module.css +188 -0
- package/src/components/Pagination/Pagination.stories.tsx +48 -0
- package/src/components/Pagination/Pagination.test.tsx +1 -0
- package/src/components/Pagination/Pagination.tsx +265 -0
- package/src/components/Pagination/USAGE.md +36 -0
- package/src/components/Pagination/index.ts +5 -0
- package/src/components/Panel/PANEL_IMPLEMENTATION_NOTES.md +116 -0
- package/src/components/Panel/Panel.module.css +209 -0
- package/src/components/Panel/Panel.stories.tsx +227 -0
- package/src/components/Panel/Panel.test.tsx +1 -0
- package/src/components/Panel/Panel.tsx +269 -0
- package/src/components/Panel/USAGE.md +56 -0
- package/src/components/Popover/IMPLEMENTATION_NOTES.md +77 -0
- package/src/components/Popover/Popover.module.css +86 -0
- package/src/components/Popover/Popover.stories.tsx +133 -0
- package/src/components/Popover/Popover.test.tsx +1 -0
- package/src/components/Popover/Popover.tsx +142 -0
- package/src/components/Popover/USAGE.md +61 -0
- package/src/components/Popover/index.ts +1 -0
- package/src/components/Radio/Radio.module.css +231 -0
- package/src/components/Radio/Radio.stories.tsx +93 -0
- package/src/components/Radio/Radio.test.tsx +1 -0
- package/src/components/Radio/Radio.tsx +168 -0
- package/src/components/Radio/RadioGroup.stories.tsx +117 -0
- package/src/components/Radio/RadioGroup.tsx +124 -0
- package/src/components/Radio/USAGE.md +36 -0
- package/src/components/ReadOnlyField/READ_ONLY_FIELD_IMPLEMENTATION_NOTES.md +15 -0
- package/src/components/ReadOnlyField/ReadOnlyBooleanField.tsx +21 -0
- package/src/components/ReadOnlyField/ReadOnlyField.module.css +54 -0
- package/src/components/ReadOnlyField/ReadOnlyField.stories.tsx +163 -0
- package/src/components/ReadOnlyField/ReadOnlyField.test.tsx +1 -0
- package/src/components/ReadOnlyField/ReadOnlyField.tsx +114 -0
- package/src/components/ReadOnlyField/ReadOnlySwitchField.tsx +21 -0
- package/src/components/ReadOnlyField/ReadOnlyTextField.tsx +48 -0
- package/src/components/ReadOnlyField/USAGE.md +44 -0
- package/src/components/ReadOnlyField/WithReadOnlyWrapper.tsx +93 -0
- package/src/components/ReadOnlyField/index.ts +5 -0
- package/src/components/RecursicaThemeProvider/USAGE.md +60 -0
- package/src/components/SegmentedControl/IMPLEMENTATION_NOTES.md +25 -0
- package/src/components/SegmentedControl/SegmentedControl.module.css +185 -0
- package/src/components/SegmentedControl/SegmentedControl.stories.tsx +106 -0
- package/src/components/SegmentedControl/SegmentedControl.test.tsx +1 -0
- package/src/components/SegmentedControl/SegmentedControl.tsx +110 -0
- package/src/components/SegmentedControl/USAGE.md +55 -0
- package/src/components/Slider/IMPLEMENTATION_NOTES.md +92 -0
- package/src/components/Slider/Slider.module.css +588 -0
- package/src/components/Slider/Slider.stories.tsx +282 -0
- package/src/components/Slider/Slider.test.tsx +1 -0
- package/src/components/Slider/Slider.tsx +391 -0
- package/src/components/Slider/USAGE.md +56 -0
- package/src/components/Stack/STACK_IMPLEMENTATION_NOTES.md +4 -0
- package/src/components/Stack/Stack.module.css +7 -0
- package/src/components/Stack/Stack.stories.tsx +108 -0
- package/src/components/Stack/Stack.test.tsx +1 -0
- package/src/components/Stack/Stack.tsx +62 -0
- package/src/components/Stack/USAGE.md +48 -0
- package/src/components/Stepper/IMPLEMENTATION_NOTES.md +28 -0
- package/src/components/Stepper/Stepper.module.css +404 -0
- package/src/components/Stepper/Stepper.stories.tsx +148 -0
- package/src/components/Stepper/Stepper.test.tsx +1 -0
- package/src/components/Stepper/Stepper.tsx +93 -0
- package/src/components/Stepper/USAGE.md +41 -0
- package/src/components/Switch/SWITCH_IMPLEMENTATION_NOTES.md +64 -0
- package/src/components/Switch/Switch.module.css +279 -0
- package/src/components/Switch/Switch.stories.tsx +114 -0
- package/src/components/Switch/Switch.test.tsx +1 -0
- package/src/components/Switch/Switch.tsx +177 -0
- package/src/components/Switch/SwitchGroup.stories.tsx +132 -0
- package/src/components/Switch/SwitchGroup.tsx +127 -0
- package/src/components/Switch/USAGE.md +42 -0
- package/src/components/Table/TABLE_IMPLEMENTATION_NOTES.md +78 -0
- package/src/components/Table/Table.icons.tsx +40 -0
- package/src/components/Table/Table.module.css +444 -0
- package/src/components/Table/Table.stories.tsx +140 -0
- package/src/components/Table/Table.test.tsx +1 -0
- package/src/components/Table/Table.tsx +243 -0
- package/src/components/Table/USAGE.md +78 -0
- package/src/components/Tabs/IMPLEMENTATION_NOTES.md +27 -0
- package/src/components/Tabs/Tabs.module.css +591 -0
- package/src/components/Tabs/Tabs.stories.tsx +154 -0
- package/src/components/Tabs/Tabs.test.tsx +1 -0
- package/src/components/Tabs/Tabs.tsx +114 -0
- package/src/components/Tabs/USAGE.md +45 -0
- package/src/components/Text/TEXT_IMPLEMENTATION_NOTES.md +12 -0
- package/src/components/Text/Text.module.css +13 -0
- package/src/components/Text/Text.stories.tsx +75 -0
- package/src/components/Text/Text.test.tsx +1 -0
- package/src/components/Text/Text.tsx +53 -0
- package/src/components/Text/USAGE.md +40 -0
- package/src/components/TextArea/TEXTAREA_IMPLEMENTATION_NOTES.md +7 -0
- package/src/components/TextArea/TextArea.module.css +183 -0
- package/src/components/TextArea/TextArea.stories.tsx +94 -0
- package/src/components/TextArea/TextArea.test.tsx +1 -0
- package/src/components/TextArea/TextArea.tsx +137 -0
- package/src/components/TextArea/USAGE.md +48 -0
- package/src/components/TextField/TEXTFIELD_IMPLEMENTATION_NOTES.md +19 -0
- package/src/components/TextField/TextField.module.css +264 -0
- package/src/components/TextField/TextField.stories.tsx +162 -0
- package/src/components/TextField/TextField.test.tsx +1 -0
- package/src/components/TextField/TextField.tsx +139 -0
- package/src/components/TextField/USAGE.md +44 -0
- package/src/components/TimePicker/TIMEPICKER_IMPLEMENTATION_NOTES.md +119 -0
- package/src/components/TimePicker/TimePicker.module.css +291 -0
- package/src/components/TimePicker/TimePicker.stories.tsx +141 -0
- package/src/components/TimePicker/TimePicker.test.tsx +1 -0
- package/src/components/TimePicker/TimePicker.tsx +304 -0
- package/src/components/TimePicker/USAGE.md +57 -0
- package/src/components/Timeline/TIMELINE_IMPLEMENTATION_NOTES.md +13 -0
- package/src/components/Timeline/Timeline.module.css +373 -0
- package/src/components/Timeline/Timeline.stories.tsx +128 -0
- package/src/components/Timeline/Timeline.test.tsx +1 -0
- package/src/components/Timeline/Timeline.tsx +73 -0
- package/src/components/Timeline/TimelineItem.tsx +79 -0
- package/src/components/Timeline/USAGE.md +49 -0
- package/src/components/Toast/TOAST_IMPLEMENTATION_NOTES.md +39 -0
- package/src/components/Toast/Toast.module.css +184 -0
- package/src/components/Toast/Toast.stories.tsx +67 -0
- package/src/components/Toast/Toast.test.tsx +1 -0
- package/src/components/Toast/Toast.tsx +82 -0
- package/src/components/Toast/USAGE.md +50 -0
- package/src/components/Toast/index.ts +1 -0
- package/src/components/Tooltip/TOOLTIP_IMPLEMENTATION_NOTES.md +85 -0
- package/src/components/Tooltip/Tooltip.module.css +91 -0
- package/src/components/Tooltip/Tooltip.stories.tsx +165 -0
- package/src/components/Tooltip/Tooltip.test.tsx +1 -0
- package/src/components/Tooltip/Tooltip.tsx +117 -0
- package/src/components/Tooltip/USAGE.md +80 -0
- package/src/components/TransferList/TRANSFERLIST_IMPLEMENTATION_NOTES.md +111 -0
- package/src/components/TransferList/TransferList.module.css +182 -0
- package/src/components/TransferList/TransferList.stories.tsx +121 -0
- package/src/components/TransferList/TransferList.test.tsx +1 -0
- package/src/components/TransferList/TransferList.tsx +419 -0
- package/src/components/TransferList/USAGE.md +77 -0
- package/src/components/Tree/IMPLEMENTATION_NOTES.md +48 -0
- package/src/components/Tree/Tree.module.css +232 -0
- package/src/components/Tree/Tree.stories.tsx +104 -0
- package/src/components/Tree/Tree.test.tsx +1 -0
- package/src/components/Tree/Tree.tsx +220 -0
- package/src/components/Tree/USAGE.md +96 -0
- package/src/components/Tree/index.ts +1 -0
- package/src/components/index.ts +52 -0
- package/src/env.d.ts +7 -0
- package/src/index.ts +179 -0
- package/src/types/index.ts +9 -0
- package/src/types/mantine.d.ts +7 -0
- package/src/types/scss-modules.d.ts +9 -0
- package/src/utils/ColorSchemeWrapper.tsx +27 -0
- package/src/utils/RequireAccessibleLabel.ts +1 -0
- package/src/utils/copyToClipboard.ts +36 -0
- package/src/utils/filterStylingProps.test.ts +85 -0
- package/src/utils/filterStylingProps.ts +150 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/renderRichOption.tsx +65 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// Placeholder for future Flex component tests. Add tests here if/when needed.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { forwardRef } from "react";
|
|
2
|
+
import {
|
|
3
|
+
Flex as MantineFlex,
|
|
4
|
+
type FlexProps as MantineFlexProps,
|
|
5
|
+
createPolymorphicComponent,
|
|
6
|
+
} from "@mantine/core";
|
|
7
|
+
import {
|
|
8
|
+
mapLayoutProps,
|
|
9
|
+
type WithRecursicaSpacing,
|
|
10
|
+
} from "../../utils/filterStylingProps";
|
|
11
|
+
import styles from "./Flex.module.css";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Flex layout wrapper.
|
|
15
|
+
*
|
|
16
|
+
* Note: Unlike complex UI components, primitive layout components (Flex, Stack, Group, Container)
|
|
17
|
+
* DO NOT use the `RecursicaOverStyled` gatekeeper. Developers must be able to freely pass
|
|
18
|
+
* width, height, padding, margins, and flexbox alignment props to construct structural layouts.
|
|
19
|
+
*
|
|
20
|
+
* No formal Recursica prop contract here: this simply passes through Mantine's own
|
|
21
|
+
* `FlexProps` (gap/rowGap/columnGap/direction/align/justify/wrap are all native to Mantine),
|
|
22
|
+
* layered only with rec- spacing token support via `WithRecursicaSpacing`.
|
|
23
|
+
*/
|
|
24
|
+
export type FlexProps = WithRecursicaSpacing<MantineFlexProps>;
|
|
25
|
+
|
|
26
|
+
const _Flex = forwardRef<HTMLDivElement, FlexProps>(function Flex(
|
|
27
|
+
{ children, gap = "rec-default", rowGap, columnGap, ...rest },
|
|
28
|
+
ref,
|
|
29
|
+
) {
|
|
30
|
+
const mergedClassNames: Partial<Record<string, string>> = {
|
|
31
|
+
root: styles.root,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const classNamesProp = rest.classNames;
|
|
35
|
+
if (
|
|
36
|
+
classNamesProp &&
|
|
37
|
+
typeof classNamesProp === "object" &&
|
|
38
|
+
!Array.isArray(classNamesProp)
|
|
39
|
+
) {
|
|
40
|
+
const o = classNamesProp as Partial<Record<string, string>>;
|
|
41
|
+
mergedClassNames.root = o.root ? `${styles.root} ${o.root}` : styles.root;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const classNameProp = rest.className as string | undefined;
|
|
45
|
+
const finalClass = classNameProp
|
|
46
|
+
? `${styles.root} ${classNameProp}`
|
|
47
|
+
: styles.root;
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<MantineFlex
|
|
51
|
+
ref={ref}
|
|
52
|
+
className={finalClass}
|
|
53
|
+
classNames={mergedClassNames}
|
|
54
|
+
{...mapLayoutProps({ gap, rowGap, columnGap, ...rest } as Record<
|
|
55
|
+
string,
|
|
56
|
+
unknown
|
|
57
|
+
>)}
|
|
58
|
+
>
|
|
59
|
+
{children}
|
|
60
|
+
</MantineFlex>
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
_Flex.displayName = "Flex";
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Recursica Flex layout wrapper.
|
|
67
|
+
*
|
|
68
|
+
* Supports polymorphism via the `component` prop or `renderRoot` for custom element rendering.
|
|
69
|
+
* @example
|
|
70
|
+
* ```tsx
|
|
71
|
+
* <Flex component="nav">...</Flex>
|
|
72
|
+
* <Flex component="section">...</Flex>
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export const Flex = createPolymorphicComponent<"div", FlexProps>(_Flex);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Flex - Usage Guide
|
|
2
|
+
|
|
3
|
+
This document describes how to integrate and use the `Flex` component in your projects using `@recursica/adapter-mantine-v8`.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Import Reference
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { Flex } from "@recursica/adapter-mantine-v8";
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 2. Basic Example
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import React from "react";
|
|
19
|
+
import { Flex } from "@recursica/adapter-mantine-v8";
|
|
20
|
+
|
|
21
|
+
export default function Demo() {
|
|
22
|
+
return (
|
|
23
|
+
<Flex gap="md" justify="space-between" align="center">
|
|
24
|
+
<Text>Item 1</Text>
|
|
25
|
+
<Text>Item 2</Text>
|
|
26
|
+
</Flex>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 3. Design System Integration
|
|
34
|
+
|
|
35
|
+
All Recursica components in the `@recursica/adapter-mantine-v8` package adhere strictly to design system spacing, scaling, and behavior patterns.
|
|
36
|
+
|
|
37
|
+
> [!IMPORTANT]
|
|
38
|
+
>
|
|
39
|
+
> - **Anti-override protection**: Rogues style injections (like inline `style` or arbitrary `className`) are automatically blocked by our prop layer unless `overStyled={true}` is explicitly provided.
|
|
40
|
+
> - **No Direct Layers**: Do not pass a `layer` prop to this component. To place it on a specific visual layer, wrap it in a `<Layer layer={0|1|2|3}>` component natively.
|
|
41
|
+
> - **Variables and Theming**: Styling is entirely determined by local CSS variables defined in `recursica_variables_scoped.css` and mapped in the component's CSS module.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 4. Key Integration Features & Constraints
|
|
46
|
+
|
|
47
|
+
The `Flex` component is a generic unopinionated flex layout wrapper mapped directly to Mantine's `Flex`.
|
|
48
|
+
All spacing props (`gap`, `align`, `justify`, `direction`, `wrap`) pass through as normal, with `rec-` dimension tokens mapped transparently to standard gap values.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/* FormControlLayout.module.css */
|
|
2
|
+
|
|
3
|
+
.root {
|
|
4
|
+
display: flex;
|
|
5
|
+
margin: 0; /* Hardcoded structural reset */
|
|
6
|
+
margin-bottom: var(
|
|
7
|
+
--form-control-margin-bottom,
|
|
8
|
+
var(--recursica_ui-kit_globals_form_properties_vertical-item-gap, 0)
|
|
9
|
+
);
|
|
10
|
+
padding: 0; /* Hardcoded structural reset */
|
|
11
|
+
max-width: var(--form-control-max-width, 100%);
|
|
12
|
+
min-width: var(--form-control-min-width, auto);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/* Base Structural Stacked Layout */
|
|
16
|
+
.root[data-form-layout="stacked"] {
|
|
17
|
+
flex-direction: column;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* Base Structural Grid Layout */
|
|
21
|
+
.root[data-form-layout="side-by-side"] {
|
|
22
|
+
flex-direction: row;
|
|
23
|
+
align-items: flex-start;
|
|
24
|
+
flex-wrap: wrap; /* Allows collapse if screen is too thin, though standard flex width should hold */
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.leftSection {
|
|
28
|
+
/* Width dimensions are strictly governed natively inside this layout column */
|
|
29
|
+
flex-shrink: 0;
|
|
30
|
+
display: flex;
|
|
31
|
+
flex-direction: column;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* Stacked Layout constraints */
|
|
35
|
+
.root[data-form-layout="stacked"] .leftSection {
|
|
36
|
+
padding-bottom: var(
|
|
37
|
+
--recursica_ui-kit_components_label_variants_layouts_stacked_properties_bottom-padding
|
|
38
|
+
);
|
|
39
|
+
min-height: var(
|
|
40
|
+
--recursica_ui-kit_components_label_variants_layouts_stacked_properties_min-height
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.root[data-form-layout="stacked"] .leftSection[data-size="default"] {
|
|
45
|
+
width: var(
|
|
46
|
+
--recursica_ui-kit_components_label_variants_layouts_stacked_variants_sizes_default_properties_width
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.root[data-form-layout="stacked"] .leftSection[data-size="small"] {
|
|
51
|
+
width: var(
|
|
52
|
+
--recursica_ui-kit_components_label_variants_layouts_stacked_variants_sizes_small_properties_width
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* SideBySide Layout constraints */
|
|
57
|
+
.root[data-form-layout="side-by-side"] .leftSection {
|
|
58
|
+
padding-top: var(
|
|
59
|
+
--recursica_ui-kit_components_label_variants_layouts_side-by-side_properties_vertical-padding
|
|
60
|
+
);
|
|
61
|
+
padding-bottom: var(
|
|
62
|
+
--recursica_ui-kit_components_label_variants_layouts_side-by-side_properties_vertical-padding
|
|
63
|
+
);
|
|
64
|
+
min-height: var(
|
|
65
|
+
--recursica_ui-kit_components_label_variants_layouts_side-by-side_properties_min-height
|
|
66
|
+
);
|
|
67
|
+
/* In side-by-side, the label column exists to the left of its input, so we use the gutter gap here. */
|
|
68
|
+
margin-right: var(
|
|
69
|
+
--recursica_ui-kit_components_label_variants_layouts_side-by-side_properties_gutter
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.root[data-form-layout="side-by-side"] .leftSection[data-size="default"] {
|
|
74
|
+
width: var(
|
|
75
|
+
--recursica_ui-kit_components_label_variants_layouts_side-by-side_variants_sizes_default_properties_width
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.root[data-form-layout="side-by-side"] .leftSection[data-size="small"] {
|
|
80
|
+
width: var(
|
|
81
|
+
--recursica_ui-kit_components_label_variants_layouts_side-by-side_variants_sizes_small_properties_width
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.rightSection {
|
|
86
|
+
display: flex; /* Groups internal field alongside subsequent errors/descriptions */
|
|
87
|
+
flex-direction: column;
|
|
88
|
+
flex: 1; /* Consumes the remaining x-axis in grid columns natively */
|
|
89
|
+
min-width: 0; /* Break boundary preventing internal flex explosion */
|
|
90
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { FormControlLayout } from "./FormControlLayout";
|
|
3
|
+
import { Switch } from "../Switch/Switch";
|
|
4
|
+
import { Label } from "../Label/Label";
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof FormControlLayout> = {
|
|
7
|
+
title: "UI-Kit/FormControlLayout",
|
|
8
|
+
component: FormControlLayout,
|
|
9
|
+
parameters: {
|
|
10
|
+
layout: "padded",
|
|
11
|
+
docs: {
|
|
12
|
+
description: {
|
|
13
|
+
component:
|
|
14
|
+
"A layout component used to correctly position form inputs alongside their labels.\n\n**When to use this:**\nTypically, you should use `FormControlWrapper` instead, which uses this component under the hood to handle layout automatically.\n\nHowever, this component is useful when you need to align standalone inputs (like a `Switch` or `Checkbox` without a label) so they perfectly match the spacing and alignment of your other form fields in a `side-by-side` layout.",
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
tags: ["autodocs"],
|
|
19
|
+
argTypes: {
|
|
20
|
+
formLayout: {
|
|
21
|
+
control: "radio",
|
|
22
|
+
options: ["stacked", "side-by-side"],
|
|
23
|
+
},
|
|
24
|
+
labelSize: {
|
|
25
|
+
control: "radio",
|
|
26
|
+
options: ["default", "small"],
|
|
27
|
+
description: "Dictates the physical width of the left column.",
|
|
28
|
+
},
|
|
29
|
+
children: {
|
|
30
|
+
table: { disable: true },
|
|
31
|
+
},
|
|
32
|
+
leftSection: {
|
|
33
|
+
table: { disable: true },
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export default meta;
|
|
39
|
+
type Story = StoryObj<typeof FormControlLayout>;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The Default layout demonstrates wrapping a standalone primitive without a Label.
|
|
43
|
+
* In a stacked layout, an omitted leftSection naturally results in no structural padding.
|
|
44
|
+
*/
|
|
45
|
+
export const Default: Story = {
|
|
46
|
+
args: {
|
|
47
|
+
formLayout: "stacked",
|
|
48
|
+
labelSize: "default",
|
|
49
|
+
leftSection: (
|
|
50
|
+
<div
|
|
51
|
+
style={{ padding: 8, border: "1px dashed #ccc", background: "#fafafa" }}
|
|
52
|
+
>
|
|
53
|
+
Left Section Boundary
|
|
54
|
+
</div>
|
|
55
|
+
),
|
|
56
|
+
children: <Switch label="Input area content" />,
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Demonstrates the stacked layout without a left section.
|
|
62
|
+
* The input should natively pull flush to the top left since there is no left column.
|
|
63
|
+
*/
|
|
64
|
+
export const StackedLayout: Story = {
|
|
65
|
+
args: {
|
|
66
|
+
formLayout: "stacked",
|
|
67
|
+
labelSize: "default",
|
|
68
|
+
children: <Switch label="Flush stacked switch" />,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Demonstrates how a naked boolean primitive perfectly aligns in a side-by-side layout
|
|
74
|
+
* by utilizing the layout wrapper. The left column maintains its precise design system width
|
|
75
|
+
* even when `leftSection` is undefined, forcing the component into the correct form column!
|
|
76
|
+
*/
|
|
77
|
+
export const SideBySideLayout: Story = {
|
|
78
|
+
args: {
|
|
79
|
+
formLayout: "side-by-side",
|
|
80
|
+
labelSize: "default",
|
|
81
|
+
children: <Switch label="Offset switch aligning with grid" />,
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* A real Label (not a placeholder) in a stacked layout, at `labelSize="default"`. Even
|
|
87
|
+
* though the label sits above the field rather than beside it, its own column still caps
|
|
88
|
+
* to the design system's stacked-layout width (224px) rather than stretching to the full
|
|
89
|
+
* container — long label text wraps instead of spanning edge-to-edge.
|
|
90
|
+
*/
|
|
91
|
+
export const StackedLayoutWithLabelDefault: Story = {
|
|
92
|
+
args: {
|
|
93
|
+
formLayout: "stacked",
|
|
94
|
+
labelSize: "default",
|
|
95
|
+
leftSection: (
|
|
96
|
+
<Label labelSize="default">
|
|
97
|
+
A fairly long label to show the stacked-layout width cap in action
|
|
98
|
+
</Label>
|
|
99
|
+
),
|
|
100
|
+
children: <Switch label="Input area content" />,
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Same as above, at `labelSize="small"` — the stacked-layout width cap is narrower (80px).
|
|
106
|
+
*/
|
|
107
|
+
export const StackedLayoutWithLabelSmall: Story = {
|
|
108
|
+
args: {
|
|
109
|
+
formLayout: "stacked",
|
|
110
|
+
labelSize: "small",
|
|
111
|
+
leftSection: (
|
|
112
|
+
<Label labelSize="small">
|
|
113
|
+
A fairly long label to show the stacked-layout width cap in action
|
|
114
|
+
</Label>
|
|
115
|
+
),
|
|
116
|
+
children: <Switch label="Input area content" />,
|
|
117
|
+
},
|
|
118
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// Placeholder for future FormControlLayout component tests. Add tests here if/when needed.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import React, { forwardRef } from "react";
|
|
2
|
+
import styles from "./FormControlLayout.module.css";
|
|
3
|
+
|
|
4
|
+
import { type RecursicaFormControlLayoutProps } from "@recursica/adapter-common";
|
|
5
|
+
|
|
6
|
+
export interface FormControlLayoutProps
|
|
7
|
+
extends React.HTMLAttributes<HTMLDivElement>,
|
|
8
|
+
RecursicaFormControlLayoutProps {}
|
|
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 const FormControlLayout = forwardRef<
|
|
22
|
+
HTMLDivElement,
|
|
23
|
+
FormControlLayoutProps
|
|
24
|
+
>(function FormControlLayout(
|
|
25
|
+
{
|
|
26
|
+
formLayout = "stacked",
|
|
27
|
+
labelSize = "default",
|
|
28
|
+
controlMaxWidth,
|
|
29
|
+
controlMinWidth,
|
|
30
|
+
leftSection,
|
|
31
|
+
children,
|
|
32
|
+
className,
|
|
33
|
+
style,
|
|
34
|
+
...rest
|
|
35
|
+
},
|
|
36
|
+
ref,
|
|
37
|
+
) {
|
|
38
|
+
const rootClass = className ? `${styles.root} ${className}` : styles.root;
|
|
39
|
+
|
|
40
|
+
// We explicitly render leftSection even if null in side-by-side to preserve the empty grid column bounding constraints.
|
|
41
|
+
// In stacked layouts, if there is no label, we omit the wrapper to avoid unnecessary padding.
|
|
42
|
+
const hasLeftContent = leftSection !== undefined && leftSection !== null;
|
|
43
|
+
const shouldRenderLeft = hasLeftContent || formLayout === "side-by-side";
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<div
|
|
47
|
+
ref={ref}
|
|
48
|
+
className={rootClass}
|
|
49
|
+
data-form-layout={formLayout}
|
|
50
|
+
style={
|
|
51
|
+
{
|
|
52
|
+
...style,
|
|
53
|
+
...(controlMaxWidth
|
|
54
|
+
? { "--form-control-max-width": controlMaxWidth }
|
|
55
|
+
: {}),
|
|
56
|
+
...(controlMinWidth
|
|
57
|
+
? { "--form-control-min-width": controlMinWidth }
|
|
58
|
+
: {}),
|
|
59
|
+
} as React.CSSProperties
|
|
60
|
+
}
|
|
61
|
+
{...rest}
|
|
62
|
+
>
|
|
63
|
+
{shouldRenderLeft && (
|
|
64
|
+
<div className={styles.leftSection} data-size={labelSize}>
|
|
65
|
+
{leftSection}
|
|
66
|
+
</div>
|
|
67
|
+
)}
|
|
68
|
+
|
|
69
|
+
<div className={styles.rightSection}>{children}</div>
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
FormControlLayout.displayName = "FormControlLayout";
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# FormControlLayout - Usage Guide
|
|
2
|
+
|
|
3
|
+
This document describes how to integrate and use the `FormControlLayout` component in your projects using `@recursica/adapter-mantine-v8`.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Import Reference
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { FormControlLayout } from "@recursica/adapter-mantine-v8";
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 2. Basic Example
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import React from "react";
|
|
19
|
+
import { FormControlLayout } from "@recursica/adapter-mantine-v8";
|
|
20
|
+
|
|
21
|
+
export default function Demo() {
|
|
22
|
+
return (
|
|
23
|
+
<FormControlLayout label="Input Field" required>
|
|
24
|
+
<input type="text" />
|
|
25
|
+
</FormControlLayout>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 3. Design System Integration
|
|
33
|
+
|
|
34
|
+
All Recursica components in the `@recursica/adapter-mantine-v8` package adhere strictly to design system spacing, scaling, and behavior patterns.
|
|
35
|
+
|
|
36
|
+
> [!IMPORTANT]
|
|
37
|
+
>
|
|
38
|
+
> - **Anti-override protection**: Rogues style injections (like inline `style` or arbitrary `className`) are automatically blocked by our prop layer unless `overStyled={true}` is explicitly provided.
|
|
39
|
+
> - **No Direct Layers**: Do not pass a `layer` prop to this component. To place it on a specific visual layer, wrap it in a `<Layer layer={0|1|2|3}>` component natively.
|
|
40
|
+
> - **Variables and Theming**: Styling is entirely determined by local CSS variables defined in `recursica_variables_scoped.css` and mapped in the component's CSS module.
|
|
@@ -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,75 @@
|
|
|
1
|
+
/* FormControlWrapper.module.css */
|
|
2
|
+
|
|
3
|
+
.root {
|
|
4
|
+
/* Global Form Field Colors */
|
|
5
|
+
--form-field-background: var(
|
|
6
|
+
--recursica_ui-kit_globals_form_field_colors_background-color
|
|
7
|
+
);
|
|
8
|
+
--form-field-background-read-only: var(
|
|
9
|
+
--recursica_ui-kit_globals_form_field_colors_background-color-read-only
|
|
10
|
+
);
|
|
11
|
+
--form-field-border: var(
|
|
12
|
+
--recursica_ui-kit_globals_form_field_colors_border-color
|
|
13
|
+
);
|
|
14
|
+
--form-field-border-error: var(
|
|
15
|
+
--recursica_ui-kit_globals_form_field_colors_error-border-color
|
|
16
|
+
);
|
|
17
|
+
--form-field-border-selected: var(
|
|
18
|
+
--recursica_ui-kit_globals_form_field_colors_border-selected
|
|
19
|
+
);
|
|
20
|
+
--form-field-disabled-background: var(
|
|
21
|
+
--recursica_ui-kit_globals_form_field_colors_disabled-background-color
|
|
22
|
+
);
|
|
23
|
+
--form-field-disabled-text: var(
|
|
24
|
+
--recursica_ui-kit_globals_form_field_colors_disabled-text
|
|
25
|
+
);
|
|
26
|
+
--form-field-icon: var(
|
|
27
|
+
--recursica_ui-kit_globals_form_field_colors_icon-color
|
|
28
|
+
);
|
|
29
|
+
--form-field-placeholder-text-opacity: var(
|
|
30
|
+
--recursica_ui-kit_globals_form_field_colors_placeholder-text-opacity
|
|
31
|
+
);
|
|
32
|
+
--form-field-text-valued: var(
|
|
33
|
+
--recursica_ui-kit_globals_form_field_colors_text-valued
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
/* Global Form Field Geometric Sizes */
|
|
37
|
+
/* No focus-specific border-color/border-size tokens exist anymore; components use the generic
|
|
38
|
+
--recursica_brand_states_focus_* ring tokens directly instead (see e.g. TextField.module.css). */
|
|
39
|
+
--form-field-border-radius: var(
|
|
40
|
+
--recursica_ui-kit_globals_form_field_size_border-radius
|
|
41
|
+
);
|
|
42
|
+
--form-field-border-size-default: var(
|
|
43
|
+
--recursica_ui-kit_globals_form_field_size_border-size-default
|
|
44
|
+
);
|
|
45
|
+
--form-field-horizontal-padding: var(
|
|
46
|
+
--recursica_ui-kit_globals_form_field_size_horizontal-padding
|
|
47
|
+
);
|
|
48
|
+
--form-field-icon-size: var(--recursica_ui-kit_globals_form_field_size_icon);
|
|
49
|
+
--form-field-icon-text-gap: var(
|
|
50
|
+
--recursica_ui-kit_globals_form_field_size_icon-text-gap
|
|
51
|
+
);
|
|
52
|
+
--form-field-max-width: var(
|
|
53
|
+
--recursica_ui-kit_globals_form_field_size_max-width
|
|
54
|
+
);
|
|
55
|
+
--form-field-min-width: var(
|
|
56
|
+
--recursica_ui-kit_globals_form_field_size_min-width
|
|
57
|
+
);
|
|
58
|
+
--form-field-single-line-input-height: var(
|
|
59
|
+
--recursica_ui-kit_globals_form_field_size_single-line-input-height
|
|
60
|
+
);
|
|
61
|
+
--form-field-vertical-gutter: var(
|
|
62
|
+
--recursica_ui-kit_globals_form_field_size_vertical-gutter
|
|
63
|
+
);
|
|
64
|
+
--form-field-vertical-padding: var(
|
|
65
|
+
--recursica_ui-kit_globals_form_field_size_vertical-padding
|
|
66
|
+
);
|
|
67
|
+
color: var(--form-field-text-valued);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.inputSection {
|
|
71
|
+
display: flex;
|
|
72
|
+
flex-direction: column;
|
|
73
|
+
flex: 1;
|
|
74
|
+
min-width: 0;
|
|
75
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import {
|
|
3
|
+
FormControlWrapper,
|
|
4
|
+
type FormControlWrapperProps,
|
|
5
|
+
} from "./FormControlWrapper";
|
|
6
|
+
import { TextField } from "../TextField/TextField";
|
|
7
|
+
import { formControlArgTypes } from "../../../.storybook/commonArgTypes";
|
|
8
|
+
|
|
9
|
+
type WrapperStoryProps = FormControlWrapperProps;
|
|
10
|
+
|
|
11
|
+
const meta: Meta<WrapperStoryProps> = {
|
|
12
|
+
title: "UI-Kit/FormControlWrapper",
|
|
13
|
+
component: FormControlWrapper,
|
|
14
|
+
tags: ["autodocs"],
|
|
15
|
+
parameters: {
|
|
16
|
+
docs: {
|
|
17
|
+
description: {
|
|
18
|
+
component:
|
|
19
|
+
"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.",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
argTypes: {
|
|
24
|
+
...formControlArgTypes,
|
|
25
|
+
error: {
|
|
26
|
+
control: "text",
|
|
27
|
+
description:
|
|
28
|
+
"Error string driving native assistive component and validation markers.",
|
|
29
|
+
},
|
|
30
|
+
assistiveText: {
|
|
31
|
+
control: "text",
|
|
32
|
+
description:
|
|
33
|
+
"Helper instructions safely dynamically anchored below the input box.",
|
|
34
|
+
},
|
|
35
|
+
assistiveWithIcon: {
|
|
36
|
+
control: "boolean",
|
|
37
|
+
},
|
|
38
|
+
required: {
|
|
39
|
+
control: "boolean",
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export default meta;
|
|
45
|
+
|
|
46
|
+
type Story = StoryObj<WrapperStoryProps>;
|
|
47
|
+
|
|
48
|
+
const renderWithTextField = (args: WrapperStoryProps) => (
|
|
49
|
+
// We explicitly cast args to 'any' here because Mantine enforces strict anatomy types for
|
|
50
|
+
// 'classNames', 'styles', and 'attributes'. WrapperStoryProps defines these for the Wrapper anatomy,
|
|
51
|
+
// but TextField expects them for the full TextInput anatomy, causing a TS intersection mismatch.
|
|
52
|
+
<TextField
|
|
53
|
+
placeholder="Form Control primitive mapped..."
|
|
54
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
55
|
+
{...(args as any)}
|
|
56
|
+
/>
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
export const Default: Story = {
|
|
60
|
+
args: {
|
|
61
|
+
label: "Account Username",
|
|
62
|
+
formLayout: "stacked",
|
|
63
|
+
assistiveText: "Validation occurs immediately natively.",
|
|
64
|
+
},
|
|
65
|
+
render: renderWithTextField,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const VisualErrorState: Story = {
|
|
69
|
+
args: {
|
|
70
|
+
label: "Encryption Protocol",
|
|
71
|
+
formLayout: "stacked",
|
|
72
|
+
error: "Strict validation limits reached. Handshake rejected securely.",
|
|
73
|
+
},
|
|
74
|
+
render: renderWithTextField,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const RequiredArchitecture: Story = {
|
|
78
|
+
args: {
|
|
79
|
+
label: "Root Password",
|
|
80
|
+
formLayout: "side-by-side",
|
|
81
|
+
required: true,
|
|
82
|
+
assistiveText: "Bypass string structure required to initiate protocol.",
|
|
83
|
+
},
|
|
84
|
+
render: renderWithTextField,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export const WithoutAssistiveIcons: Story = {
|
|
88
|
+
args: {
|
|
89
|
+
label: "Server Domain",
|
|
90
|
+
assistiveText:
|
|
91
|
+
"A standard text boundary without default native icon parameters bounding.",
|
|
92
|
+
assistiveWithIcon: false,
|
|
93
|
+
},
|
|
94
|
+
render: renderWithTextField,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export const NativeChildrenDirectly: Story = {
|
|
98
|
+
parameters: {
|
|
99
|
+
docs: {
|
|
100
|
+
description: {
|
|
101
|
+
story:
|
|
102
|
+
"Bypassing the TextField map to show exactly how native `<input>` hooks execute inside the raw wrapper natively perfectly.",
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
args: {
|
|
107
|
+
label: "Raw HTML Checkbox",
|
|
108
|
+
formLayout: "side-by-side",
|
|
109
|
+
assistiveText: "This wraps a raw HTML input tag mapping correctly.",
|
|
110
|
+
},
|
|
111
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
|
|
112
|
+
render: ({ withLayer, layer, ...args }: any) => (
|
|
113
|
+
<div
|
|
114
|
+
style={{
|
|
115
|
+
display: "flex",
|
|
116
|
+
gap: "10px",
|
|
117
|
+
alignItems: "center",
|
|
118
|
+
}}
|
|
119
|
+
>
|
|
120
|
+
{/* We can cleanly wrap even un-styled HTML primitives! */}
|
|
121
|
+
<FormControlWrapper {...args}>
|
|
122
|
+
<input
|
|
123
|
+
type="checkbox"
|
|
124
|
+
style={{ margin: 0, width: "16px", height: "16px" }}
|
|
125
|
+
/>
|
|
126
|
+
</FormControlWrapper>
|
|
127
|
+
</div>
|
|
128
|
+
),
|
|
129
|
+
};
|