@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,252 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
3
|
+
import { Dropdown } from "./Dropdown";
|
|
4
|
+
import { formControlArgTypes } from "../../../.storybook/commonArgTypes";
|
|
5
|
+
import { renderRichOption } from "../../utils/renderRichOption";
|
|
6
|
+
import styles from "./Dropdown.module.css";
|
|
7
|
+
|
|
8
|
+
type DropdownStoryProps = React.ComponentProps<typeof Dropdown>;
|
|
9
|
+
|
|
10
|
+
const meta: Meta<DropdownStoryProps> = {
|
|
11
|
+
title: "UI-Kit/Dropdown",
|
|
12
|
+
component: Dropdown,
|
|
13
|
+
tags: ["autodocs"],
|
|
14
|
+
parameters: {
|
|
15
|
+
docs: {
|
|
16
|
+
description: {
|
|
17
|
+
component:
|
|
18
|
+
"Dropdown provides a selectable list of options, mapping natively over Mantine's Select component encapsulated within the standardized FormControlWrapper.",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
args: {
|
|
23
|
+
label: "Country Selection",
|
|
24
|
+
assistiveText: "Select your country of origin.",
|
|
25
|
+
placeholder: "Pick value",
|
|
26
|
+
data: ["United States", "Canada", "Mexico", "United Kingdom", "France"],
|
|
27
|
+
disabled: false,
|
|
28
|
+
required: false,
|
|
29
|
+
readOnly: false,
|
|
30
|
+
clearable: false,
|
|
31
|
+
},
|
|
32
|
+
argTypes: {
|
|
33
|
+
disabled: {
|
|
34
|
+
control: "boolean",
|
|
35
|
+
},
|
|
36
|
+
...formControlArgTypes,
|
|
37
|
+
readOnly: {
|
|
38
|
+
control: "boolean",
|
|
39
|
+
},
|
|
40
|
+
clearable: {
|
|
41
|
+
control: "boolean",
|
|
42
|
+
},
|
|
43
|
+
wrapItemText: {
|
|
44
|
+
control: "boolean",
|
|
45
|
+
description:
|
|
46
|
+
"Wraps option label/supportingText onto additional lines instead of truncating with an ellipsis.",
|
|
47
|
+
},
|
|
48
|
+
containerWidth: {
|
|
49
|
+
table: { disable: true },
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export default meta;
|
|
55
|
+
|
|
56
|
+
type Story = StoryObj<DropdownStoryProps>;
|
|
57
|
+
|
|
58
|
+
export const Default: Story = {
|
|
59
|
+
args: {},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const Clearable: Story = {
|
|
63
|
+
args: {
|
|
64
|
+
label: "Clearable Options",
|
|
65
|
+
clearable: true,
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export const WithLeadingIcon: Story = {
|
|
70
|
+
args: {
|
|
71
|
+
label: "Destination",
|
|
72
|
+
leftSection: (
|
|
73
|
+
<svg
|
|
74
|
+
width="24"
|
|
75
|
+
height="24"
|
|
76
|
+
viewBox="0 0 24 24"
|
|
77
|
+
fill="none"
|
|
78
|
+
stroke="currentColor"
|
|
79
|
+
strokeWidth="2"
|
|
80
|
+
strokeLinecap="round"
|
|
81
|
+
strokeLinejoin="round"
|
|
82
|
+
>
|
|
83
|
+
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path>
|
|
84
|
+
<circle cx="12" cy="10" r="3"></circle>
|
|
85
|
+
</svg>
|
|
86
|
+
),
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const UserIcon = (
|
|
91
|
+
<svg
|
|
92
|
+
width="16"
|
|
93
|
+
height="16"
|
|
94
|
+
viewBox="0 0 24 24"
|
|
95
|
+
fill="none"
|
|
96
|
+
stroke="currentColor"
|
|
97
|
+
strokeWidth="2"
|
|
98
|
+
strokeLinecap="round"
|
|
99
|
+
strokeLinejoin="round"
|
|
100
|
+
>
|
|
101
|
+
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
|
|
102
|
+
<circle cx="12" cy="7" r="4"></circle>
|
|
103
|
+
</svg>
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
export const WithRichOptions: Story = {
|
|
107
|
+
args: {
|
|
108
|
+
label: "Assignee",
|
|
109
|
+
placeholder: "Pick a team member",
|
|
110
|
+
assistiveText:
|
|
111
|
+
"Each option can show a leading icon and supporting text — see MANTINE_ADAPTER_RICH_OPTION_DATA.md.",
|
|
112
|
+
data: [
|
|
113
|
+
{
|
|
114
|
+
value: "jdoe",
|
|
115
|
+
label: "Jane Doe",
|
|
116
|
+
leadingIcon: UserIcon,
|
|
117
|
+
supportingText: "jane.doe@example.com",
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
value: "asmith",
|
|
121
|
+
label: "Alex Smith",
|
|
122
|
+
leadingIcon: UserIcon,
|
|
123
|
+
supportingText: "alex.smith@example.com",
|
|
124
|
+
},
|
|
125
|
+
{ value: "unassigned", label: "Unassigned" },
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export const WithRichOptionsWrapped: Story = {
|
|
131
|
+
args: {
|
|
132
|
+
label: "Assignee",
|
|
133
|
+
placeholder: "Pick a team member",
|
|
134
|
+
wrapItemText: true,
|
|
135
|
+
data: [
|
|
136
|
+
{
|
|
137
|
+
value: "jdoe",
|
|
138
|
+
label: "Jane Doe, Senior Staff Engineer, Platform Infrastructure",
|
|
139
|
+
leadingIcon: UserIcon,
|
|
140
|
+
supportingText:
|
|
141
|
+
"jane.doe@example.com — Platform Infrastructure team, on-call rotation lead",
|
|
142
|
+
},
|
|
143
|
+
{ value: "unassigned", label: "Unassigned" },
|
|
144
|
+
],
|
|
145
|
+
assistiveText:
|
|
146
|
+
"wrapItemText=true — long label/supportingText wrap instead of truncating.",
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const optionRowPreviewClassNames = {
|
|
151
|
+
optionContent: styles.optionContent,
|
|
152
|
+
optionIcon: styles.optionIcon,
|
|
153
|
+
optionText: styles.optionText,
|
|
154
|
+
optionTextWrap: styles.optionTextWrap,
|
|
155
|
+
optionSupportingText: styles.optionSupportingText,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const OPTION_ROW_PREVIEW_ITEMS = [
|
|
159
|
+
{
|
|
160
|
+
value: "icon-and-supporting",
|
|
161
|
+
label: "Jane Doe",
|
|
162
|
+
leadingIcon: UserIcon,
|
|
163
|
+
supportingText: "jane.doe@example.com",
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
value: "no-icon",
|
|
167
|
+
label: "Alex Smith",
|
|
168
|
+
supportingText:
|
|
169
|
+
"No leadingIcon — label/supportingText shift left, no reserved icon space",
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
value: "no-supporting-text",
|
|
173
|
+
label: "Taylor Rivera",
|
|
174
|
+
leadingIcon: UserIcon,
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
value: "plain",
|
|
178
|
+
label: "Plain option — no leadingIcon, no supportingText",
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
value: "long-text",
|
|
182
|
+
label:
|
|
183
|
+
"A very long option label that, with wrapItemText, wraps onto a second line instead of overflowing the fixed-width dropdown — otherwise it truncates with an ellipsis",
|
|
184
|
+
leadingIcon: UserIcon,
|
|
185
|
+
supportingText:
|
|
186
|
+
"A similarly long supporting text string, to confirm the same wrap-or-truncate behavior applies to it too",
|
|
187
|
+
},
|
|
188
|
+
];
|
|
189
|
+
|
|
190
|
+
// Renders the option row content directly — outside the floating/portal menu — inside a
|
|
191
|
+
// container sized to Dropdown's own max-width token. Spacing between rows, icon/supportingText
|
|
192
|
+
// presence-or-absence alignment, and long-text wrapping/truncation are all much easier to inspect
|
|
193
|
+
// this way than by opening the real (portal-rendered) combobox dropdown. See
|
|
194
|
+
// MANTINE_ADAPTER_RICH_OPTION_DATA.md.
|
|
195
|
+
const renderOptionRowPreview = (wrapItemText: boolean) => (
|
|
196
|
+
<div
|
|
197
|
+
className={styles.dropdown}
|
|
198
|
+
style={{
|
|
199
|
+
width:
|
|
200
|
+
"var(--recursica_ui-kit_components_dropdown_variants_layouts_stacked_properties_max-width)",
|
|
201
|
+
}}
|
|
202
|
+
>
|
|
203
|
+
{OPTION_ROW_PREVIEW_ITEMS.map((item) => (
|
|
204
|
+
<div key={item.value} className={styles.option}>
|
|
205
|
+
{renderRichOption(
|
|
206
|
+
{ option: item },
|
|
207
|
+
optionRowPreviewClassNames,
|
|
208
|
+
wrapItemText,
|
|
209
|
+
)}
|
|
210
|
+
</div>
|
|
211
|
+
))}
|
|
212
|
+
</div>
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
// Default: `wrapItemText` is false — label/supportingText truncate to a single line with an
|
|
216
|
+
// ellipsis instead of wrapping.
|
|
217
|
+
export const RichOptionRowPreview: Story = {
|
|
218
|
+
parameters: {
|
|
219
|
+
controls: { disable: true },
|
|
220
|
+
},
|
|
221
|
+
render: () => renderOptionRowPreview(false),
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
// `wrapItemText: true` — label/supportingText wrap onto additional lines instead of truncating.
|
|
225
|
+
export const RichOptionRowPreviewWrapped: Story = {
|
|
226
|
+
parameters: {
|
|
227
|
+
controls: { disable: true },
|
|
228
|
+
},
|
|
229
|
+
render: () => renderOptionRowPreview(true),
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
export const StaticError: Story = {
|
|
233
|
+
args: {
|
|
234
|
+
error: "You must choose a valid destination.",
|
|
235
|
+
value: "Invalid Island",
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
export const StaticDisabled: Story = {
|
|
240
|
+
args: {
|
|
241
|
+
disabled: true,
|
|
242
|
+
value: "United States",
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
export const StaticReadOnly: Story = {
|
|
247
|
+
args: {
|
|
248
|
+
label: "Read Only View",
|
|
249
|
+
readOnly: true,
|
|
250
|
+
value: "Canada",
|
|
251
|
+
},
|
|
252
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// Placeholder for future Dropdown component tests. Add tests here if/when needed.
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import React, { forwardRef } from "react";
|
|
2
|
+
import {
|
|
3
|
+
Select as MantineSelect,
|
|
4
|
+
type SelectProps as MantineSelectProps,
|
|
5
|
+
} from "@mantine/core";
|
|
6
|
+
import {
|
|
7
|
+
type ReadOnlyControlProps,
|
|
8
|
+
normalizeComboboxData,
|
|
9
|
+
} from "@recursica/adapter-common";
|
|
10
|
+
import {
|
|
11
|
+
filterStylingProps,
|
|
12
|
+
omitUnsupportedProps,
|
|
13
|
+
mergeClassNames,
|
|
14
|
+
type RecursicaOverStyled,
|
|
15
|
+
} from "../../utils/filterStylingProps";
|
|
16
|
+
import { type RecursicaFormControlWrapperProps } from "../FormControlWrapper/FormControlWrapper";
|
|
17
|
+
import { WithReadOnlyWrapper } from "../ReadOnlyField/WithReadOnlyWrapper";
|
|
18
|
+
import { renderRichOption } from "../../utils/renderRichOption";
|
|
19
|
+
import styles from "./Dropdown.module.css";
|
|
20
|
+
|
|
21
|
+
import { type RecursicaDropdownProps as BaseRecursicaDropdownProps } from "@recursica/adapter-common";
|
|
22
|
+
|
|
23
|
+
export interface RecursicaDropdownProps
|
|
24
|
+
extends Omit<
|
|
25
|
+
MantineSelectProps,
|
|
26
|
+
"size" | "variant" | "radius" | "wrapperProps" | "data" | "searchable"
|
|
27
|
+
>,
|
|
28
|
+
Omit<
|
|
29
|
+
RecursicaFormControlWrapperProps,
|
|
30
|
+
"controlMaxWidth" | "controlMinWidth"
|
|
31
|
+
>,
|
|
32
|
+
ReadOnlyControlProps,
|
|
33
|
+
BaseRecursicaDropdownProps {}
|
|
34
|
+
|
|
35
|
+
export type DropdownProps = RecursicaOverStyled<RecursicaDropdownProps>;
|
|
36
|
+
|
|
37
|
+
// Props this component intentionally doesn't support — deleted at runtime so they can't leak
|
|
38
|
+
// through even if a caller forces them via plain JavaScript, bypassing the `Omit<>` above.
|
|
39
|
+
const UNSUPPORTED_PROPS = [
|
|
40
|
+
"size", // Recursica controls sizing via the `size` variant + design tokens, not raw dimensions.
|
|
41
|
+
"variant", // Colors/variants are token-driven; the library's native palette isn't exposed.
|
|
42
|
+
"radius", // Recursica does not expose the library's native corner-radius system.
|
|
43
|
+
"searchable", // Not a supported Dropdown feature — that's what makes it an AutoComplete.
|
|
44
|
+
] as const satisfies readonly (keyof MantineSelectProps)[];
|
|
45
|
+
|
|
46
|
+
export const Dropdown = forwardRef<HTMLInputElement, DropdownProps>(
|
|
47
|
+
function Dropdown(props, ref) {
|
|
48
|
+
const {
|
|
49
|
+
overStyled = false,
|
|
50
|
+
formLayout = "stacked",
|
|
51
|
+
containerWidth,
|
|
52
|
+
|
|
53
|
+
// Label & Wrapper Maps
|
|
54
|
+
labelSize,
|
|
55
|
+
labelAlignment,
|
|
56
|
+
labelOptionalText,
|
|
57
|
+
labelWithEditIcon,
|
|
58
|
+
onLabelEditClick,
|
|
59
|
+
|
|
60
|
+
label,
|
|
61
|
+
assistiveText,
|
|
62
|
+
assistiveWithIcon,
|
|
63
|
+
error,
|
|
64
|
+
required,
|
|
65
|
+
withAsterisk,
|
|
66
|
+
id,
|
|
67
|
+
className,
|
|
68
|
+
style,
|
|
69
|
+
disabled,
|
|
70
|
+
readOnly,
|
|
71
|
+
readOnlyComponent,
|
|
72
|
+
emptyValueComponent,
|
|
73
|
+
value,
|
|
74
|
+
defaultValue,
|
|
75
|
+
data,
|
|
76
|
+
clearButtonProps,
|
|
77
|
+
renderOption,
|
|
78
|
+
wrapItemText = false,
|
|
79
|
+
...rest
|
|
80
|
+
} = props;
|
|
81
|
+
const sanitizedProps = omitUnsupportedProps(
|
|
82
|
+
filterStylingProps(rest, overStyled) as Record<string, unknown>,
|
|
83
|
+
UNSUPPORTED_PROPS,
|
|
84
|
+
);
|
|
85
|
+
const restRecord = sanitizedProps as Record<string, unknown>;
|
|
86
|
+
|
|
87
|
+
// Mantine's own data parser only preserves extra fields (`leadingIcon`/`supportingText`) when
|
|
88
|
+
// an item already has both `value` and `label` — an item with `value` only is rebuilt into a
|
|
89
|
+
// bare `{value, label: value, disabled}` object, silently dropping them (see
|
|
90
|
+
// get-parsed-combobox-data.mjs, and AutoComplete.tsx's identical use of this). Matters here
|
|
91
|
+
// too now that `label` is optional (shared `RecursicaComboboxItem` — see adapter-common).
|
|
92
|
+
const normalizedData = normalizeComboboxData(data);
|
|
93
|
+
|
|
94
|
+
// Mantine's own clear button (rendered when `clearable` + a value are both present) otherwise
|
|
95
|
+
// renders unstyled — bare `CloseButton` defaults, no Recursica icon-button treatment. Merge in
|
|
96
|
+
// our own class (see `.clearButton` in Dropdown.module.css) alongside anything the caller
|
|
97
|
+
// already passed, same merge shape as `mergeClassNames` but for a single `className` string
|
|
98
|
+
// rather than a per-slot classNames map.
|
|
99
|
+
const consumerClearButtonProps = clearButtonProps as
|
|
100
|
+
| Record<string, unknown>
|
|
101
|
+
| undefined;
|
|
102
|
+
const mergedClearButtonProps = {
|
|
103
|
+
...consumerClearButtonProps,
|
|
104
|
+
className: consumerClearButtonProps?.className
|
|
105
|
+
? `${styles.clearButton} ${consumerClearButtonProps.className as string}`
|
|
106
|
+
: styles.clearButton,
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// Securely map core native blocks down ensuring nested CSS modules map precisely
|
|
110
|
+
const mergedClassNames = mergeClassNames(
|
|
111
|
+
{
|
|
112
|
+
wrapper: styles.root, // The nested Input internal relative wrapper bounding box
|
|
113
|
+
input: styles.input,
|
|
114
|
+
section: styles.section,
|
|
115
|
+
dropdown: styles.dropdown,
|
|
116
|
+
option: styles.option,
|
|
117
|
+
},
|
|
118
|
+
restRecord.classNames as Partial<Record<string, string>> | undefined,
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
const optionClassNames = {
|
|
122
|
+
optionContent: styles.optionContent,
|
|
123
|
+
optionIcon: styles.optionIcon,
|
|
124
|
+
optionText: styles.optionText,
|
|
125
|
+
optionTextWrap: styles.optionTextWrap,
|
|
126
|
+
optionSupportingText: styles.optionSupportingText,
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const injectedStyles = {
|
|
130
|
+
...((style as React.CSSProperties) || {}),
|
|
131
|
+
width: containerWidth || "100%",
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const wrapperClass = className
|
|
135
|
+
? `${styles.layoutOverride} ${className}`
|
|
136
|
+
: styles.layoutOverride;
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<WithReadOnlyWrapper
|
|
140
|
+
className={wrapperClass}
|
|
141
|
+
style={injectedStyles}
|
|
142
|
+
controlMaxWidth="var(--dropdown-control-max-width)"
|
|
143
|
+
controlMinWidth="var(--dropdown-control-min-width)"
|
|
144
|
+
overStyled={overStyled as true}
|
|
145
|
+
formLayout={formLayout}
|
|
146
|
+
labelSize={labelSize}
|
|
147
|
+
labelAlignment={labelAlignment}
|
|
148
|
+
labelOptionalText={labelOptionalText}
|
|
149
|
+
labelWithEditIcon={labelWithEditIcon}
|
|
150
|
+
onLabelEditClick={onLabelEditClick}
|
|
151
|
+
label={label}
|
|
152
|
+
assistiveText={assistiveText}
|
|
153
|
+
assistiveWithIcon={assistiveWithIcon}
|
|
154
|
+
error={error}
|
|
155
|
+
required={required}
|
|
156
|
+
withAsterisk={withAsterisk}
|
|
157
|
+
id={id}
|
|
158
|
+
readOnly={readOnly}
|
|
159
|
+
readOnlyComponent={readOnlyComponent}
|
|
160
|
+
emptyValueComponent={emptyValueComponent}
|
|
161
|
+
readOnlyType="text"
|
|
162
|
+
readOnlyValue={
|
|
163
|
+
value !== undefined
|
|
164
|
+
? String(value)
|
|
165
|
+
: defaultValue
|
|
166
|
+
? String(defaultValue)
|
|
167
|
+
: undefined
|
|
168
|
+
}
|
|
169
|
+
readOnlyNativeProps={props}
|
|
170
|
+
activeComponent={
|
|
171
|
+
/* Naked Select execution safely decoupled from Mantine's macro Input.Wrapper DOM hooks */
|
|
172
|
+
<MantineSelect
|
|
173
|
+
ref={ref}
|
|
174
|
+
{...(sanitizedProps as unknown as MantineSelectProps)}
|
|
175
|
+
classNames={mergedClassNames}
|
|
176
|
+
disabled={disabled}
|
|
177
|
+
value={value}
|
|
178
|
+
defaultValue={defaultValue}
|
|
179
|
+
data={
|
|
180
|
+
(normalizedData as unknown as MantineSelectProps["data"]) || []
|
|
181
|
+
}
|
|
182
|
+
renderOption={
|
|
183
|
+
renderOption ??
|
|
184
|
+
((input) =>
|
|
185
|
+
renderRichOption(input, optionClassNames, wrapItemText))
|
|
186
|
+
}
|
|
187
|
+
clearButtonProps={mergedClearButtonProps}
|
|
188
|
+
label={undefined}
|
|
189
|
+
description={undefined}
|
|
190
|
+
error={undefined}
|
|
191
|
+
required={undefined}
|
|
192
|
+
withAsterisk={undefined}
|
|
193
|
+
// `wrapperProps` targets Mantine's *outer* `Input.Wrapper` (the label/description/error
|
|
194
|
+
// stacking element) — a different, ancestor element from the "wrapper" styles-api slot
|
|
195
|
+
// that actually carries `styles.root`'s border/background. Dropdown.module.css's error/
|
|
196
|
+
// disabled rules (`.root[data-error]`/`[data-disabled]`) need the attribute on that
|
|
197
|
+
// inner element, so `attributes.wrapper` (which targets the same slot as
|
|
198
|
+
// `classNames.wrapper`) is the correct hook — `wrapperProps` here meant the error state
|
|
199
|
+
// never actually applied a border color.
|
|
200
|
+
attributes={{
|
|
201
|
+
wrapper: {
|
|
202
|
+
"data-disabled": disabled ? "true" : undefined,
|
|
203
|
+
"data-error": error ? "true" : undefined,
|
|
204
|
+
},
|
|
205
|
+
}}
|
|
206
|
+
/>
|
|
207
|
+
}
|
|
208
|
+
/>
|
|
209
|
+
);
|
|
210
|
+
},
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
Dropdown.displayName = "Dropdown";
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Dropdown - Usage Guide
|
|
2
|
+
|
|
3
|
+
This document describes how to integrate and use the `Dropdown` component in your projects using `@recursica/adapter-mantine-v8`.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Import Reference
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { Dropdown } from "@recursica/adapter-mantine-v8";
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 2. Basic Example
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import React from "react";
|
|
19
|
+
import { Dropdown } from "@recursica/adapter-mantine-v8";
|
|
20
|
+
|
|
21
|
+
export default function Demo() {
|
|
22
|
+
return (
|
|
23
|
+
<Dropdown placeholder="Select an option">
|
|
24
|
+
<Dropdown.Item value="1">Option 1</Dropdown.Item>
|
|
25
|
+
<Dropdown.Item value="2">Option 2</Dropdown.Item>
|
|
26
|
+
</Dropdown>
|
|
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. Notes
|
|
46
|
+
|
|
47
|
+
- `Dropdown` is styled independently from `TextField`; even though they look similar, they are themed using separate design tokens.
|
|
48
|
+
- Pass `leftSection` for a leading icon, and `clearable` (with a value present) to show a clear button — both render using the dropdown's own icon-color tokens.
|
|
49
|
+
- `data` items can carry a `leadingIcon` and `supportingText`, rendered inside each option row:
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
<Dropdown
|
|
53
|
+
label="Assignee"
|
|
54
|
+
data={[
|
|
55
|
+
{
|
|
56
|
+
value: "jdoe",
|
|
57
|
+
label: "Jane Doe",
|
|
58
|
+
leadingIcon: <UserIcon />,
|
|
59
|
+
supportingText: "jane.doe@example.com",
|
|
60
|
+
},
|
|
61
|
+
{ value: "asmith", label: "Alex Smith" },
|
|
62
|
+
]}
|
|
63
|
+
/>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Pass your own `renderOption` to opt out of this default rendering for a given instance.
|
|
67
|
+
|
|
68
|
+
By default `label`/`supportingText` truncate to a single line with an ellipsis. Set
|
|
69
|
+
`wrapItemText` to wrap them onto additional lines instead: `<Dropdown data={data} wrapItemText />`.
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# FileInput Implementation Notes
|
|
2
|
+
|
|
3
|
+
## Architecture overview
|
|
4
|
+
|
|
5
|
+
`FileInput` replaces the previous "coming soon" stub with a fully custom composite, the same way
|
|
6
|
+
`FileUpload` was built (see `../FileUpload/FILEUPLOAD_IMPLEMENTATION_NOTES.md`). It shares
|
|
7
|
+
`FileUpload`'s selection/validation contract (`accept`/`maxSize`/`maxFiles`, `onFilesRejected`,
|
|
8
|
+
`readOnly`) via a common `RecursicaFileUploadItem` type and near-identical `handleFiles`/drag
|
|
9
|
+
logic, but is presented as a single-line, `TextField`-shaped control rather than a dropzone —
|
|
10
|
+
Forge's own `file-input` token set is shaped almost identically to `text-field`'s (border-radius,
|
|
11
|
+
horizontal/vertical padding, icon-size, icon-text-gap, min-height, placeholder-opacity, a full
|
|
12
|
+
`text_*` type-style block), confirming the design intent directly rather than needing to guess it
|
|
13
|
+
from the reference screenshot alone.
|
|
14
|
+
|
|
15
|
+
The control is one clickable, focusable `<div role="button">` containing:
|
|
16
|
+
|
|
17
|
+
1. **A leading icon** — the upload icon by default, overridable via `icon`.
|
|
18
|
+
2. **A content area** — placeholder text when empty, or a horizontally scrollable row of `Chip`s
|
|
19
|
+
(reusing `FileUpload`'s chip, same as its file list) once one or more files are selected —
|
|
20
|
+
single- and multiple-file mode render identically here; only the selection/replace behavior
|
|
21
|
+
differs (see "`multiple` defaults to `false`" below).
|
|
22
|
+
3. **A trailing "clear" `Button`** — shown whenever a file is selected; clears the entire current
|
|
23
|
+
selection, icon overridable via `clearIcon` (see "Trailing clear button" below).
|
|
24
|
+
4. **A hidden `<input type="file">`**, triggered programmatically — same approach as
|
|
25
|
+
`FileUpload`'s dropzone, not the "invisible overlay input" pattern some styled file inputs use
|
|
26
|
+
(that pattern would need every interactive child — chip remove icons, the clear icon — to sit
|
|
27
|
+
at a higher stacking context than the overlay just to receive its own clicks; keeping the input
|
|
28
|
+
hidden and driving it via `ref.click()` avoids that entirely).
|
|
29
|
+
|
|
30
|
+
## The stub's exemption comments described a schema that no longer exists
|
|
31
|
+
|
|
32
|
+
Before writing any CSS, the stub `FileInput.module.css`'s 43 `recursica-ignore` comments were
|
|
33
|
+
checked against `packages/official-release/recursica_variables_scoped.css` directly, rather than
|
|
34
|
+
trusted as the token inventory. They didn't match — the stub encoded a `states.default`/
|
|
35
|
+
`states.focus`-shaped schema (mirroring the same stale-plugin-export drift documented in
|
|
36
|
+
`docs/alignment/ALIGNMENT_2026-08-11.md` §3.2 for other components) that the current export has
|
|
37
|
+
never had for `file-input`. The real schema (40 variables, matching that report's own L5 count)
|
|
38
|
+
has:
|
|
39
|
+
|
|
40
|
+
- **Flat, non-state-scoped base properties and colors** (`properties_border-size`,
|
|
41
|
+
`properties_colors_{background-color,border-color,leading-icon,text-color,trailing-icon}`) —
|
|
42
|
+
much closer to `TextField`'s shape than `FileUpload`'s.
|
|
43
|
+
- **Only two states — `disabled` and `error`.** No `default` state (the flat properties above
|
|
44
|
+
are the default) and no `focus` state at all.
|
|
45
|
+
- **`layouts.{stacked,side-by-side}` each carrying their own `max-width`/`min-width`/
|
|
46
|
+
`top-bottom-margin`** — three properties per layout, not the single flat `max-width`/
|
|
47
|
+
`min-width` an early draft of this component assumed.
|
|
48
|
+
|
|
49
|
+
Every token actually referenced in `FileInput.module.css` was re-verified against this real list
|
|
50
|
+
(38 applied + 2 exempted = 40, zero gap on either adapter).
|
|
51
|
+
|
|
52
|
+
## No forge-defined focus state — generic ring, same fallback as TextField
|
|
53
|
+
|
|
54
|
+
Since `file-input` has no `states.focus` axis, `:focus-visible` on the root falls back to the
|
|
55
|
+
generic `--recursica_brand_states_focus_*` box-shadow ring, exactly like `TextField`/`DatePicker`
|
|
56
|
+
already do for the same reason.
|
|
57
|
+
|
|
58
|
+
## Disabled/error border-size intentionally unused
|
|
59
|
+
|
|
60
|
+
Unlike `FileUpload` (which has no border-size token at all to choose from), `file-input`'s export
|
|
61
|
+
does define real per-state `border-size` variables for `disabled` and `error`. They're
|
|
62
|
+
`recursica-ignore`d anyway and the flat `properties_border-size` is applied uniformly instead —
|
|
63
|
+
same house policy `TextField`/`DatePicker` already follow (a border that changes thickness across
|
|
64
|
+
states shifts the layout), just applied here to real rather than phantom tokens.
|
|
65
|
+
|
|
66
|
+
## Why the root is a focusable, clickable `<div>` (a deliberate divergence from FileUpload)
|
|
67
|
+
|
|
68
|
+
`FileUpload`'s dropzone deliberately has no `onClick`/`role="button"`/`tabIndex` of its own — the
|
|
69
|
+
separate "Browse files" `<Button>` is its sole click/keyboard affordance, avoiding a
|
|
70
|
+
nested-interactive-element ambiguity (see `FILEUPLOAD_IMPLEMENTATION_NOTES.md`, "Not a nested
|
|
71
|
+
interactive element"). `FileInput` has no separate Browse button in the reference design — the
|
|
72
|
+
box itself plays that role, the same way a real `<input type="file">` is its own focusable,
|
|
73
|
+
clickable, `Enter`/`Space`-activatable control. So it needs to be one directly: `role="button"`,
|
|
74
|
+
`tabIndex={0}` when interactive, `onClick`/`onKeyDown` (`Enter`/`Space`) both call the same
|
|
75
|
+
`openFilePicker`, and an explicit `aria-label` (`browseLabel`, defaulting to `"Choose file"`)
|
|
76
|
+
since there's no native `<label for>` association available for a custom `div` the way a real
|
|
77
|
+
`<input>` gets one implicitly.
|
|
78
|
+
|
|
79
|
+
## `multiple` defaults to `false`, unlike `FileUpload`'s `true`
|
|
80
|
+
|
|
81
|
+
The reference design shows "Single File" and "Multiple Files" as two distinct, deliberately-named
|
|
82
|
+
usages, and a single-line `TextField`-shaped control reads most naturally as single-value by
|
|
83
|
+
default — matching a native `<input type="file">`, which is single-file unless `multiple` is set.
|
|
84
|
+
`FileUpload`'s dropzone is the opposite by design (it defaults to accepting a batch).
|
|
85
|
+
|
|
86
|
+
## Single-file mode replaces; it doesn't append
|
|
87
|
+
|
|
88
|
+
`onFilesAdded`/`onFileRemove` keep the exact same shape as `FileUpload`'s, but single-file mode's
|
|
89
|
+
effective cap is hardcoded to 1 (`currentCount` is never read from the existing `files` prop in
|
|
90
|
+
that mode) rather than counting the file already there — picking a new file is meant to replace
|
|
91
|
+
the old one, not be rejected as "over the limit." This is a documentation convention, not special
|
|
92
|
+
component state: see USAGE.md §2 for the "handlers typically replace, not append" note for
|
|
93
|
+
single-file consumers. Dropping more than one file onto a single-file control still routes the
|
|
94
|
+
extras to `onFilesRejected` via the same effective-cap-of-1 logic, with the default
|
|
95
|
+
`maxFilesMessage` reading `"Only one file is allowed"` instead of `FileUpload`'s
|
|
96
|
+
`"Maximum of N files allowed"` phrasing.
|
|
97
|
+
|
|
98
|
+
## Trailing clear button
|
|
99
|
+
|
|
100
|
+
The clear-all affordance shown whenever `files.length > 0` is a real shared `Button`
|
|
101
|
+
(`variant="text" size="small"`, icon-only), not a bespoke `<span role="button">` — it gets real
|
|
102
|
+
button semantics and keyboard handling for free, the same reasoning as `Tree`'s expand/collapse
|
|
103
|
+
button (see `Tree.module.css`'s `.expandButton`). It's rendered through Button's own tokened
|
|
104
|
+
color/disabled states, so `file-input`'s own `properties_colors_trailing-icon` token (and its
|
|
105
|
+
`disabled`/`error` state variants) is `recursica-ignore`d rather than applied — it has no
|
|
106
|
+
"clear button is disabled" state of its own anyway, since `disabled` already omits interactivity
|
|
107
|
+
entirely. Clicking/activating it calls `onFileRemove` once per currently-selected file (reusing
|
|
108
|
+
the same callback `FileUpload`'s individual chip removal uses, rather than introducing a separate
|
|
109
|
+
`onClear` prop) — in single-file mode that's one call; in multiple-file mode it clears everything
|
|
110
|
+
at once, distinct from a chip's own individual remove icon. The icon itself defaults to the
|
|
111
|
+
built-in X glyph and is overridable via `clearIcon` — the same pattern as the leading `icon`,
|
|
112
|
+
giving Forge's `leading-icon`/`trailing-icon` token pair a matching pair of override props.
|
|
113
|
+
|
|
114
|
+
## Why each chip is wrapped with its own `stopPropagation`
|
|
115
|
+
|
|
116
|
+
`FileUpload`'s chip list sits _below_ its dropzone, entirely outside the click-to-browse surface,
|
|
117
|
+
so a click on a chip's body never risks also opening the file picker. `FileInput`'s chip row lives
|
|
118
|
+
_inside_ the same clickable root, so each chip is wrapped in a thin `<span onClick={(e) =>
|
|
119
|
+
e.stopPropagation()}>` — blank space within the row (and the leading icon/placeholder/filename
|
|
120
|
+
text) still bubbles up to open the picker, but clicking directly on a chip's body doesn't also
|
|
121
|
+
trigger it. Chip's own remove icon already calls `stopPropagation` internally (see `Chip.tsx`), so
|
|
122
|
+
it needed no changes for this.
|
|
123
|
+
|
|
124
|
+
## Keyboard navigation for the chip row
|
|
125
|
+
|
|
126
|
+
Same roving-tabindex model as `FileUpload`'s file list (`activeChipIndex`, `deleteIconRefs`,
|
|
127
|
+
Left/Right/Up/Down roving, focus-survives-removal `useEffect`) — reused rather than reinvented,
|
|
128
|
+
and applies in single-file mode too (a one-chip roving group is a no-op but needs no special
|
|
129
|
+
casing). The only addition specific to `FileInput` is that `Tab` reaches the root control itself
|
|
130
|
+
first (since it's the click/keyboard surface for opening the picker), _then_ the chip row's
|
|
131
|
+
roving group, _then_ the trailing clear button as its own stop.
|
|
132
|
+
|
|
133
|
+
## The chip row scrolls horizontally instead of wrapping or clipping
|
|
134
|
+
|
|
135
|
+
`.chipRow` uses `overflow-x: auto; overflow-y: hidden` rather than `FileUpload`'s file list, which
|
|
136
|
+
wraps onto multiple lines below the dropzone — `FileInput` is a fixed single-line, `min-height`d
|
|
137
|
+
control, so wrapping would grow it vertically. Enough chips to overflow the control's own width
|
|
138
|
+
scroll horizontally within it instead (mouse wheel/trackpad or a native scrollbar), same tradeoff
|
|
139
|
+
already made for `.value`'s ellipsis truncation. The native scrollbar itself is hidden
|
|
140
|
+
(`scrollbar-width: none`, `-ms-overflow-style: none`, `::-webkit-scrollbar { display: none }`) —
|
|
141
|
+
the control's `min-height` isn't tall enough to host a visible horizontal scrollbar without
|
|
142
|
+
clipping the chips, and scrolling (wheel/trackpad/drag) still works with it hidden.
|
|
143
|
+
|
|
144
|
+
## Read-only vs disabled
|
|
145
|
+
|
|
146
|
+
`readOnly` renders the same content (placeholder/filename/chip row) but the root loses its
|
|
147
|
+
`role="button"` interactivity (no `tabIndex`, no click/keyboard/drag handlers), chips render with
|
|
148
|
+
no `deleteLabel`/`onDelete` (so `Chip` itself renders no delete icon, same as `FileUpload`'s
|
|
149
|
+
read-only chips), and the trailing clear icon is omitted entirely. `disabled` keeps the control
|
|
150
|
+
structurally the same but inert (`tabIndex={-1}`, `aria-disabled`, no handlers, native `<input>`
|
|
151
|
+
disabled) — both are computed together as a single `interactive` flag used throughout, but remain
|
|
152
|
+
independently toggleable props (a `readOnly` control is never simultaneously `disabled` in
|
|
153
|
+
practice, but nothing enforces that at the type level, matching `FileUpload`'s own convention).
|