@recursica/mui-adapter 0.5.0 → 0.6.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/CHANGELOG.md +9 -0
- package/dist/mui-adapter.cjs +74 -1
- package/dist/mui-adapter.cjs.map +1 -1
- package/dist/mui-adapter.css +1 -1
- package/dist/mui-adapter.js +6652 -204
- package/dist/mui-adapter.js.map +1 -1
- package/dist/src/components/Accordion/Accordion.d.ts +63 -2
- package/dist/src/components/AssistiveElement/AssistiveElement.d.ts +9 -2
- package/dist/src/components/Autocomplete/Autocomplete.d.ts +16 -2
- package/dist/src/components/Avatar/Avatar.d.ts +14 -2
- package/dist/src/components/Badge/Badge.d.ts +13 -3
- package/dist/src/components/Breadcrumb/Breadcrumb.d.ts +10 -3
- package/dist/src/components/Button/Button.d.ts +1 -1
- package/dist/src/components/Card/Card.d.ts +40 -3
- package/dist/src/components/Checkbox/Checkbox.d.ts +15 -2
- package/dist/src/components/Checkbox/CheckboxGroup.d.ts +16 -0
- package/dist/src/components/Checkbox/index.d.ts +5 -1
- package/dist/src/components/Chip/Chip.d.ts +18 -2
- package/dist/src/components/Dropdown/Dropdown.d.ts +23 -3
- package/dist/src/components/FormControlLayout/FormControlLayout.d.ts +10 -2
- package/dist/src/components/FormControlWrapper/FormControlWrapper.d.ts +24 -2
- package/dist/src/components/HoverCard/HoverCard.d.ts +55 -3
- package/dist/src/components/Label/Label.d.ts +13 -2
- package/dist/src/components/Link/Link.d.ts +1 -1
- package/dist/src/components/Menu/Menu.d.ts +99 -3
- package/dist/src/components/Modal/Modal.d.ts +36 -2
- package/dist/src/components/NumberInput/NumberInput.d.ts +14 -2
- package/dist/src/components/Pagination/Pagination.d.ts +44 -2
- package/dist/src/components/Pagination/Pagination.icons.d.ts +8 -0
- package/dist/src/components/Panel/Panel.d.ts +65 -3
- package/dist/src/components/Radio/Radio.d.ts +13 -3
- package/dist/src/components/Radio/RadioGroup.d.ts +13 -0
- package/dist/src/components/ReadOnlyField/ReadOnlyBooleanField.d.ts +5 -0
- package/dist/src/components/ReadOnlyField/ReadOnlyField.d.ts +7 -2
- package/dist/src/components/ReadOnlyField/ReadOnlySwitchField.d.ts +5 -0
- package/dist/src/components/ReadOnlyField/ReadOnlyTextField.d.ts +8 -0
- package/dist/src/components/ReadOnlyField/WithReadOnlyWrapper.d.ts +28 -0
- package/dist/src/components/ReadOnlyField/index.d.ts +1 -0
- package/dist/src/components/SegmentedControl/SegmentedControl.d.ts +31 -3
- package/dist/src/components/Slider/Slider.d.ts +28 -2
- package/dist/src/components/Stepper/Stepper.d.ts +21 -2
- package/dist/src/components/Switch/Switch.d.ts +10 -3
- package/dist/src/components/Switch/SwitchGroup.d.ts +13 -0
- package/dist/src/components/Tabs/Tabs.d.ts +35 -3
- package/dist/src/components/Text/Text.d.ts +1 -1
- package/dist/src/components/TextArea/TextArea.d.ts +18 -2
- package/dist/src/components/TextField/TextField.d.ts +16 -2
- package/dist/src/components/Timeline/Timeline.d.ts +14 -2
- package/dist/src/components/Timeline/TimelineItem.d.ts +29 -0
- package/dist/src/components/Title/Title.d.ts +1 -1
- package/dist/src/components/Toast/Toast.d.ts +25 -2
- package/dist/src/components/Tooltip/Tooltip.d.ts +42 -3
- package/dist/src/components/Typography/Typography.d.ts +1 -1
- package/package.json +9 -2
- package/src/components/Accordion/Accordion.module.css +295 -56
- package/src/components/Accordion/Accordion.stories.tsx +149 -21
- package/src/components/Accordion/Accordion.tsx +264 -5
- package/src/components/AssistiveElement/AssistiveElement.module.css +111 -20
- package/src/components/AssistiveElement/AssistiveElement.stories.tsx +76 -19
- package/src/components/AssistiveElement/AssistiveElement.tsx +85 -5
- package/src/components/Autocomplete/Autocomplete.module.css +320 -42
- package/src/components/Autocomplete/Autocomplete.stories.tsx +180 -19
- package/src/components/Autocomplete/Autocomplete.tsx +199 -6
- package/src/components/Avatar/Avatar.module.css +343 -73
- package/src/components/Avatar/Avatar.stories.tsx +94 -22
- package/src/components/Avatar/Avatar.tsx +110 -6
- package/src/components/Badge/Badge.module.css +127 -29
- package/src/components/Badge/Badge.stories.tsx +65 -22
- package/src/components/Badge/Badge.tsx +70 -6
- package/src/components/Breadcrumb/Breadcrumb.module.css +25 -5
- package/src/components/Breadcrumb/Breadcrumb.stories.tsx +57 -22
- package/src/components/Breadcrumb/Breadcrumb.tsx +62 -6
- package/src/components/Button/Button.module.css +3 -3
- package/src/components/Card/Card.module.css +118 -38
- package/src/components/Card/Card.stories.tsx +117 -19
- package/src/components/Card/Card.tsx +179 -5
- package/src/components/Checkbox/Checkbox.module.css +232 -39
- package/src/components/Checkbox/Checkbox.stories.tsx +81 -19
- package/src/components/Checkbox/Checkbox.tsx +146 -5
- package/src/components/Checkbox/CheckboxGroup.tsx +117 -0
- package/src/components/Checkbox/index.ts +6 -1
- package/src/components/Chip/Chip.module.css +228 -43
- package/src/components/Chip/Chip.stories.tsx +101 -21
- package/src/components/Chip/Chip.tsx +138 -6
- package/src/components/Dropdown/Dropdown.module.css +291 -41
- package/src/components/Dropdown/Dropdown.stories.tsx +80 -21
- package/src/components/Dropdown/Dropdown.tsx +182 -6
- package/src/components/FormControlLayout/FormControlLayout.module.css +92 -0
- package/src/components/FormControlLayout/FormControlLayout.stories.tsx +74 -19
- package/src/components/FormControlLayout/FormControlLayout.tsx +58 -4
- package/src/components/FormControlWrapper/FormControlWrapper.module.css +96 -0
- package/src/components/FormControlWrapper/FormControlWrapper.stories.tsx +122 -19
- package/src/components/FormControlWrapper/FormControlWrapper.tsx +145 -6
- package/src/components/HoverCard/HoverCard.module.css +90 -22
- package/src/components/HoverCard/HoverCard.stories.tsx +158 -21
- package/src/components/HoverCard/HoverCard.tsx +150 -5
- package/src/components/Label/Label.module.css +136 -35
- package/src/components/Label/Label.stories.tsx +98 -17
- package/src/components/Label/Label.tsx +88 -5
- package/src/components/Menu/Menu.module.css +285 -51
- package/src/components/Menu/Menu.stories.tsx +307 -21
- package/src/components/Menu/Menu.tsx +318 -5
- package/src/components/Modal/Modal.module.css +171 -35
- package/src/components/Modal/Modal.stories.tsx +66 -22
- package/src/components/Modal/Modal.tsx +216 -4
- package/src/components/NumberInput/NumberInput.module.css +276 -42
- package/src/components/NumberInput/NumberInput.stories.tsx +83 -20
- package/src/components/NumberInput/NumberInput.tsx +148 -6
- package/src/components/Pagination/Pagination.icons.tsx +66 -0
- package/src/components/Pagination/Pagination.module.css +246 -20
- package/src/components/Pagination/Pagination.stories.tsx +42 -21
- package/src/components/Pagination/Pagination.tsx +193 -5
- package/src/components/Panel/Panel.module.css +206 -38
- package/src/components/Panel/Panel.stories.tsx +214 -22
- package/src/components/Panel/Panel.tsx +153 -5
- package/src/components/Radio/Radio.module.css +220 -37
- package/src/components/Radio/Radio.stories.tsx +77 -19
- package/src/components/Radio/Radio.tsx +178 -5
- package/src/components/Radio/RadioGroup.tsx +111 -0
- package/src/components/ReadOnlyField/ReadOnlyBooleanField.tsx +21 -0
- package/src/components/ReadOnlyField/ReadOnlyField.module.css +53 -15
- package/src/components/ReadOnlyField/ReadOnlyField.stories.tsx +175 -16
- package/src/components/ReadOnlyField/ReadOnlyField.tsx +109 -5
- package/src/components/ReadOnlyField/ReadOnlySwitchField.tsx +21 -0
- package/src/components/ReadOnlyField/ReadOnlyTextField.tsx +48 -0
- package/src/components/ReadOnlyField/WithReadOnlyWrapper.tsx +93 -0
- package/src/components/ReadOnlyField/index.ts +1 -0
- package/src/components/SegmentedControl/SegmentedControl.module.css +203 -47
- package/src/components/SegmentedControl/SegmentedControl.stories.tsx +102 -20
- package/src/components/SegmentedControl/SegmentedControl.tsx +110 -6
- package/src/components/Slider/Slider.module.css +542 -86
- package/src/components/Slider/Slider.stories.tsx +157 -19
- package/src/components/Slider/Slider.tsx +282 -5
- package/src/components/Stepper/Stepper.module.css +410 -71
- package/src/components/Stepper/Stepper.stories.tsx +132 -19
- package/src/components/Stepper/Stepper.tsx +145 -6
- package/src/components/Switch/Switch.module.css +238 -39
- package/src/components/Switch/Switch.stories.tsx +98 -19
- package/src/components/Switch/Switch.tsx +153 -6
- package/src/components/Switch/SwitchGroup.tsx +111 -0
- package/src/components/Tabs/Tabs.module.css +422 -79
- package/src/components/Tabs/Tabs.stories.tsx +138 -19
- package/src/components/Tabs/Tabs.tsx +103 -6
- package/src/components/TextArea/TextArea.module.css +155 -32
- package/src/components/TextArea/TextArea.stories.tsx +80 -21
- package/src/components/TextArea/TextArea.tsx +156 -6
- package/src/components/TextField/TextField.module.css +261 -42
- package/src/components/TextField/TextField.stories.tsx +168 -15
- package/src/components/TextField/TextField.tsx +171 -6
- package/src/components/Timeline/Timeline.module.css +365 -76
- package/src/components/Timeline/Timeline.stories.tsx +114 -21
- package/src/components/Timeline/Timeline.tsx +80 -5
- package/src/components/Timeline/TimelineItem.tsx +101 -0
- package/src/components/Toast/Toast.module.css +166 -28
- package/src/components/Toast/Toast.stories.tsx +54 -22
- package/src/components/Toast/Toast.tsx +90 -5
- package/src/components/Tooltip/Tooltip.module.css +89 -24
- package/src/components/Tooltip/Tooltip.stories.tsx +152 -22
- package/src/components/Tooltip/Tooltip.tsx +114 -5
|
@@ -1,8 +1,184 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import
|
|
1
|
+
import React, { forwardRef, ReactNode } from "react";
|
|
2
|
+
import {
|
|
3
|
+
Select as MuiSelect,
|
|
4
|
+
SelectProps as MuiSelectProps,
|
|
5
|
+
MenuItem,
|
|
6
|
+
} from "@mui/material";
|
|
7
|
+
import { type ReadOnlyControlProps } from "@recursica/adapter-common";
|
|
8
|
+
import {
|
|
9
|
+
filterStylingProps,
|
|
10
|
+
type RecursicaOverStyled,
|
|
11
|
+
} from "../../utils/filterStylingProps";
|
|
12
|
+
import { type RecursicaFormControlWrapperProps } from "../FormControlWrapper/FormControlWrapper";
|
|
13
|
+
import { WithReadOnlyWrapper } from "../ReadOnlyField/WithReadOnlyWrapper";
|
|
14
|
+
import styles from "./Dropdown.module.css";
|
|
3
15
|
|
|
4
|
-
export
|
|
16
|
+
export interface RecursicaDropdownProps
|
|
17
|
+
extends Omit<
|
|
18
|
+
MuiSelectProps,
|
|
19
|
+
"size" | "variant" | "classes" | "inputProps" | "SelectDisplayProps"
|
|
20
|
+
>,
|
|
21
|
+
Pick<
|
|
22
|
+
RecursicaFormControlWrapperProps,
|
|
23
|
+
| "assistiveText"
|
|
24
|
+
| "assistiveWithIcon"
|
|
25
|
+
| "formLayout"
|
|
26
|
+
| "labelSize"
|
|
27
|
+
| "labelAlignment"
|
|
28
|
+
| "labelOptionalText"
|
|
29
|
+
| "labelWithEditIcon"
|
|
30
|
+
| "onLabelEditClick"
|
|
31
|
+
>,
|
|
32
|
+
ReadOnlyControlProps {
|
|
33
|
+
/** Internal hook for Selects to override raw layout width properties when necessary */
|
|
34
|
+
containerWidth?: React.CSSProperties["width"];
|
|
35
|
+
data?: (string | { value: string; label: ReactNode; disabled?: boolean })[];
|
|
36
|
+
searchable?: boolean;
|
|
37
|
+
clearable?: boolean;
|
|
38
|
+
withAsterisk?: boolean;
|
|
39
|
+
}
|
|
5
40
|
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
41
|
+
export type DropdownProps = RecursicaOverStyled<RecursicaDropdownProps>;
|
|
42
|
+
|
|
43
|
+
export const Dropdown = forwardRef<HTMLInputElement, DropdownProps>(
|
|
44
|
+
function Dropdown(props, ref) {
|
|
45
|
+
const {
|
|
46
|
+
overStyled = false,
|
|
47
|
+
formLayout = "stacked",
|
|
48
|
+
containerWidth,
|
|
49
|
+
|
|
50
|
+
// Label & Wrapper Maps
|
|
51
|
+
labelSize,
|
|
52
|
+
labelAlignment,
|
|
53
|
+
labelOptionalText,
|
|
54
|
+
labelWithEditIcon,
|
|
55
|
+
onLabelEditClick,
|
|
56
|
+
|
|
57
|
+
label,
|
|
58
|
+
assistiveText,
|
|
59
|
+
assistiveWithIcon,
|
|
60
|
+
error,
|
|
61
|
+
required,
|
|
62
|
+
withAsterisk,
|
|
63
|
+
id,
|
|
64
|
+
className,
|
|
65
|
+
style,
|
|
66
|
+
disabled,
|
|
67
|
+
readOnly,
|
|
68
|
+
readOnlyComponent,
|
|
69
|
+
emptyValueComponent,
|
|
70
|
+
value,
|
|
71
|
+
defaultValue,
|
|
72
|
+
data,
|
|
73
|
+
searchable, // Not natively supported by basic MUI Select, stubbed
|
|
74
|
+
clearable, // Not natively supported by basic MUI Select, stubbed
|
|
75
|
+
...rest
|
|
76
|
+
} = props;
|
|
77
|
+
const sanitizedProps = filterStylingProps(rest, overStyled);
|
|
78
|
+
const restRecord = sanitizedProps as Record<string, unknown>;
|
|
79
|
+
|
|
80
|
+
// Delete prohibited sizing hooks
|
|
81
|
+
delete restRecord["size"];
|
|
82
|
+
delete restRecord["variant"];
|
|
83
|
+
|
|
84
|
+
const injectedStyles = {
|
|
85
|
+
...((style as React.CSSProperties) || {}),
|
|
86
|
+
width: containerWidth || "100%",
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const wrapperClass = className
|
|
90
|
+
? `${styles.layoutOverride} ${className}`
|
|
91
|
+
: styles.layoutOverride;
|
|
92
|
+
|
|
93
|
+
const renderOptions = () => {
|
|
94
|
+
if (!data) return null;
|
|
95
|
+
return data.map((item, index) => {
|
|
96
|
+
if (typeof item === "string") {
|
|
97
|
+
return (
|
|
98
|
+
<MenuItem
|
|
99
|
+
key={`${item}-${index}`}
|
|
100
|
+
value={item}
|
|
101
|
+
className={styles.option}
|
|
102
|
+
>
|
|
103
|
+
{item}
|
|
104
|
+
</MenuItem>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
return (
|
|
108
|
+
<MenuItem
|
|
109
|
+
key={`${item.value}-${index}`}
|
|
110
|
+
value={item.value}
|
|
111
|
+
disabled={item.disabled}
|
|
112
|
+
className={styles.option}
|
|
113
|
+
>
|
|
114
|
+
{item.label}
|
|
115
|
+
</MenuItem>
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<WithReadOnlyWrapper
|
|
122
|
+
className={wrapperClass}
|
|
123
|
+
style={injectedStyles}
|
|
124
|
+
controlMaxWidth="var(--recursica_ui-kit_components_dropdown_properties_max-width)"
|
|
125
|
+
controlMinWidth="var(--recursica_ui-kit_components_dropdown_properties_min-width)"
|
|
126
|
+
overStyled={overStyled as true}
|
|
127
|
+
formLayout={formLayout}
|
|
128
|
+
labelSize={labelSize}
|
|
129
|
+
labelAlignment={labelAlignment}
|
|
130
|
+
labelOptionalText={labelOptionalText}
|
|
131
|
+
labelWithEditIcon={labelWithEditIcon}
|
|
132
|
+
onLabelEditClick={onLabelEditClick}
|
|
133
|
+
label={label as ReactNode}
|
|
134
|
+
assistiveText={assistiveText}
|
|
135
|
+
assistiveWithIcon={assistiveWithIcon}
|
|
136
|
+
error={!!error}
|
|
137
|
+
required={required}
|
|
138
|
+
withAsterisk={withAsterisk}
|
|
139
|
+
id={id}
|
|
140
|
+
readOnly={readOnly}
|
|
141
|
+
readOnlyComponent={readOnlyComponent}
|
|
142
|
+
emptyValueComponent={emptyValueComponent}
|
|
143
|
+
readOnlyType="text"
|
|
144
|
+
readOnlyValue={
|
|
145
|
+
value !== undefined
|
|
146
|
+
? String(value)
|
|
147
|
+
: defaultValue
|
|
148
|
+
? String(defaultValue)
|
|
149
|
+
: undefined
|
|
150
|
+
}
|
|
151
|
+
readOnlyNativeProps={props}
|
|
152
|
+
activeComponent={
|
|
153
|
+
<MuiSelect
|
|
154
|
+
ref={ref}
|
|
155
|
+
disabled={disabled}
|
|
156
|
+
value={value}
|
|
157
|
+
defaultValue={defaultValue}
|
|
158
|
+
error={!!error}
|
|
159
|
+
required={required}
|
|
160
|
+
displayEmpty
|
|
161
|
+
className={styles.root}
|
|
162
|
+
classes={{
|
|
163
|
+
select: styles.input,
|
|
164
|
+
icon: styles.icon,
|
|
165
|
+
}}
|
|
166
|
+
MenuProps={{
|
|
167
|
+
classes: { paper: styles.dropdown },
|
|
168
|
+
}}
|
|
169
|
+
inputProps={{
|
|
170
|
+
"data-disabled": disabled ? "true" : undefined,
|
|
171
|
+
"data-error": error ? "true" : undefined,
|
|
172
|
+
...(restRecord.inputProps as any),
|
|
173
|
+
}}
|
|
174
|
+
{...(sanitizedProps as unknown as MuiSelectProps)}
|
|
175
|
+
>
|
|
176
|
+
{renderOptions()}
|
|
177
|
+
</MuiSelect>
|
|
178
|
+
}
|
|
179
|
+
/>
|
|
180
|
+
);
|
|
181
|
+
},
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
Dropdown.displayName = "Dropdown";
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
.root {
|
|
2
|
+
/* Recursica Reset Boundary */
|
|
3
|
+
box-sizing: border-box;
|
|
4
|
+
display: flex;
|
|
5
|
+
margin: 0; /* Hardcoded structural reset */
|
|
6
|
+
width: 100%;
|
|
7
|
+
|
|
8
|
+
/* Recursica Layout Overrides mapped down dynamically from components */
|
|
9
|
+
padding: 0; /* Hardcoded structural reset */
|
|
10
|
+
max-width: var(--form-control-max-width);
|
|
11
|
+
min-width: var(--form-control-min-width);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/* =========================================
|
|
15
|
+
LAYOUT MODES
|
|
16
|
+
========================================= */
|
|
17
|
+
|
|
18
|
+
.root[data-form-layout="stacked"] {
|
|
19
|
+
flex-direction: column;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.root[data-form-layout="side-by-side"] {
|
|
23
|
+
/* Aligns the Input/Component identically horizontally beside the label mapping */
|
|
24
|
+
flex-direction: row;
|
|
25
|
+
align-items: flex-start;
|
|
26
|
+
flex-wrap: wrap; /* Allows collapse if screen is too thin, though standard flex width should hold */
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* =========================================
|
|
30
|
+
GRID SECTIONS
|
|
31
|
+
========================================= */
|
|
32
|
+
|
|
33
|
+
.leftSection {
|
|
34
|
+
flex-shrink: 0;
|
|
35
|
+
display: flex;
|
|
36
|
+
flex-direction: column;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/*
|
|
40
|
+
Stacked margins natively apply spacing *below* the label
|
|
41
|
+
*/
|
|
42
|
+
.root[data-form-layout="stacked"] .leftSection {
|
|
43
|
+
margin-bottom: var(
|
|
44
|
+
--recursica_ui-kit_globals_form_properties_label-field-gap-vertical
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/*
|
|
49
|
+
Side-by-side margins natively apply spacing *right* of the label
|
|
50
|
+
and forces vertical padding to align the label perfectly with 1st line of input text
|
|
51
|
+
*/
|
|
52
|
+
.root[data-form-layout="side-by-side"] .leftSection {
|
|
53
|
+
margin-right: var(
|
|
54
|
+
--recursica_ui-kit_components_label_variants_layouts_side-by-side_properties_gutter
|
|
55
|
+
);
|
|
56
|
+
padding-top: var(
|
|
57
|
+
--recursica_ui-kit_components_label_variants_layouts_side-by-side_properties_vertical-padding
|
|
58
|
+
);
|
|
59
|
+
min-height: var(
|
|
60
|
+
--recursica_ui-kit_components_label_variants_layouts_side-by-side_properties_min-height
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* Label column sizing hooks (Only applies functionally in side-by-side mode) */
|
|
65
|
+
.root[data-form-layout="side-by-side"] .leftSection[data-label-size="small"] {
|
|
66
|
+
width: var(
|
|
67
|
+
--recursica_ui-kit_components_label_variants_layouts_side-by-side_variants_sizes_small_properties_width
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.root[data-form-layout="side-by-side"] .leftSection[data-label-size="default"] {
|
|
72
|
+
width: var(
|
|
73
|
+
--recursica_ui-kit_components_label_variants_layouts_side-by-side_variants_sizes_default_properties_width
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.root[data-form-layout="side-by-side"] .leftSection[data-label-size="md"] {
|
|
78
|
+
/* No 'medium' defined explicitly in generated token scope, mapped fallback to default */
|
|
79
|
+
width: var(
|
|
80
|
+
--recursica_ui-kit_components_label_variants_layouts_side-by-side_variants_sizes_default_properties_width
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/*
|
|
85
|
+
Right Section dynamically wraps the input element natively
|
|
86
|
+
*/
|
|
87
|
+
.rightSection {
|
|
88
|
+
display: flex; /* Groups internal field alongside subsequent errors/descriptions */
|
|
89
|
+
flex-direction: column;
|
|
90
|
+
flex: 1; /* Consumes the remaining x-axis in grid columns natively */
|
|
91
|
+
min-width: 0; /* Break boundary preventing internal flex explosion */
|
|
92
|
+
}
|
|
@@ -1,28 +1,40 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import React from "react";
|
|
1
3
|
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
4
|
import { FormControlLayout } from "./FormControlLayout";
|
|
3
|
-
import {
|
|
5
|
+
import { Switch } from "../Switch/Switch";
|
|
4
6
|
|
|
5
7
|
const meta: Meta<typeof FormControlLayout> = {
|
|
6
|
-
title: "UI-Kit
|
|
8
|
+
title: "UI-Kit/FormControlLayout",
|
|
7
9
|
component: FormControlLayout,
|
|
8
|
-
tags: ["autodocs"],
|
|
9
10
|
parameters: {
|
|
11
|
+
layout: "padded",
|
|
12
|
+
docs: {
|
|
13
|
+
description: {
|
|
14
|
+
component:
|
|
15
|
+
"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.",
|
|
16
|
+
},
|
|
17
|
+
},
|
|
10
18
|
controls: {
|
|
11
|
-
include: [
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
include: ["formLayout", "labelSize"],
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
tags: ["autodocs"],
|
|
23
|
+
argTypes: {
|
|
24
|
+
formLayout: {
|
|
25
|
+
control: "radio",
|
|
26
|
+
options: ["stacked", "side-by-side"],
|
|
27
|
+
},
|
|
28
|
+
labelSize: {
|
|
29
|
+
control: "radio",
|
|
30
|
+
options: ["default", "small", "md"],
|
|
31
|
+
description: "Dictates the physical width of the left column.",
|
|
32
|
+
},
|
|
33
|
+
children: {
|
|
34
|
+
table: { disable: true },
|
|
35
|
+
},
|
|
36
|
+
leftSection: {
|
|
37
|
+
table: { disable: true },
|
|
26
38
|
},
|
|
27
39
|
},
|
|
28
40
|
};
|
|
@@ -30,6 +42,49 @@ const meta: Meta<typeof FormControlLayout> = {
|
|
|
30
42
|
export default meta;
|
|
31
43
|
type Story = StoryObj<typeof FormControlLayout>;
|
|
32
44
|
|
|
45
|
+
/**
|
|
46
|
+
* The Default layout demonstrates wrapping a standalone primitive without a Label.
|
|
47
|
+
* In a stacked layout, an omitted leftSection naturally results in no structural padding.
|
|
48
|
+
*/
|
|
33
49
|
export const Default: Story = {
|
|
34
|
-
|
|
50
|
+
args: {
|
|
51
|
+
formLayout: "stacked",
|
|
52
|
+
labelSize: "default",
|
|
53
|
+
leftSection: (
|
|
54
|
+
<div
|
|
55
|
+
style={{ padding: 8, border: "1px dashed #ccc", background: "#fafafa" }}
|
|
56
|
+
>
|
|
57
|
+
Left Section Boundary
|
|
58
|
+
</div>
|
|
59
|
+
),
|
|
60
|
+
children: <Switch label="Input area content" />,
|
|
61
|
+
},
|
|
62
|
+
render: ({ ...args }: any) => <FormControlLayout {...args} />,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Demonstrates the stacked layout without a left section.
|
|
67
|
+
* The input should natively pull flush to the top left since there is no left column.
|
|
68
|
+
*/
|
|
69
|
+
export const StackedLayout: Story = {
|
|
70
|
+
args: {
|
|
71
|
+
formLayout: "stacked",
|
|
72
|
+
labelSize: "default",
|
|
73
|
+
children: <Switch label="Flush stacked switch" />,
|
|
74
|
+
},
|
|
75
|
+
render: ({ ...args }: any) => <FormControlLayout {...args} />,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Demonstrates how a naked boolean primitive perfectly aligns in a side-by-side layout
|
|
80
|
+
* by utilizing the layout wrapper. The left column maintains its precise design system width
|
|
81
|
+
* even when `leftSection` is undefined, forcing the component into the correct form column!
|
|
82
|
+
*/
|
|
83
|
+
export const SideBySideLayout: Story = {
|
|
84
|
+
args: {
|
|
85
|
+
formLayout: "side-by-side",
|
|
86
|
+
labelSize: "default",
|
|
87
|
+
children: <Switch label="Offset switch aligning with grid" />,
|
|
88
|
+
},
|
|
89
|
+
render: ({ ...args }: any) => <FormControlLayout {...args} />,
|
|
35
90
|
};
|
|
@@ -1,7 +1,61 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
+
import { filterStylingProps } from "../../utils/filterStylingProps";
|
|
3
|
+
import styles from "./FormControlLayout.module.css";
|
|
2
4
|
|
|
3
|
-
export
|
|
5
|
+
export interface FormControlLayoutProps
|
|
6
|
+
extends React.HTMLAttributes<HTMLDivElement> {
|
|
7
|
+
overStyled?: boolean;
|
|
8
|
+
formLayout?: "stacked" | "side-by-side";
|
|
9
|
+
labelSize?: "small" | "default" | "md";
|
|
10
|
+
controlMaxWidth?: string;
|
|
11
|
+
controlMinWidth?: string;
|
|
12
|
+
leftSection?: React.ReactNode;
|
|
13
|
+
children: React.ReactNode;
|
|
14
|
+
}
|
|
4
15
|
|
|
5
|
-
export const FormControlLayout
|
|
6
|
-
|
|
7
|
-
|
|
16
|
+
export const FormControlLayout = React.forwardRef<
|
|
17
|
+
HTMLDivElement,
|
|
18
|
+
FormControlLayoutProps
|
|
19
|
+
>(function FormControlLayout(props, ref) {
|
|
20
|
+
const {
|
|
21
|
+
overStyled = false,
|
|
22
|
+
formLayout = "stacked",
|
|
23
|
+
labelSize = "default",
|
|
24
|
+
leftSection,
|
|
25
|
+
className,
|
|
26
|
+
children,
|
|
27
|
+
...rest
|
|
28
|
+
} = props;
|
|
29
|
+
|
|
30
|
+
const sanitizedProps = filterStylingProps(rest, overStyled);
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div
|
|
34
|
+
ref={ref}
|
|
35
|
+
className={className ? `${styles.root} ${className}` : styles.root}
|
|
36
|
+
data-form-layout={formLayout}
|
|
37
|
+
{...sanitizedProps}
|
|
38
|
+
>
|
|
39
|
+
{/*
|
|
40
|
+
The left section strictly enforces the Label boundary sizes
|
|
41
|
+
We render it unconditionally if side-by-side to preserve the grid column layout
|
|
42
|
+
*/}
|
|
43
|
+
{formLayout === "side-by-side" ? (
|
|
44
|
+
<div className={styles.leftSection} data-label-size={labelSize}>
|
|
45
|
+
{leftSection}
|
|
46
|
+
</div>
|
|
47
|
+
) : (
|
|
48
|
+
leftSection && (
|
|
49
|
+
<div className={styles.leftSection} data-label-size={labelSize}>
|
|
50
|
+
{leftSection}
|
|
51
|
+
</div>
|
|
52
|
+
)
|
|
53
|
+
)}
|
|
54
|
+
|
|
55
|
+
{/* The main content area natively flows into the remaining space */}
|
|
56
|
+
<div className={styles.rightSection}>{children}</div>
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
FormControlLayout.displayName = "FormControlLayout";
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/*
|
|
2
|
+
FORM CONTROL WRAPPER
|
|
3
|
+
Strict architectural layout mapping handling grid bounds around MUI Form Controls natively.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
.root {
|
|
7
|
+
/* Inherit MUI Reset overrides */
|
|
8
|
+
display: block;
|
|
9
|
+
width: 100%;
|
|
10
|
+
|
|
11
|
+
/* Global Form Field Colors */
|
|
12
|
+
--form-field-background: var(
|
|
13
|
+
--recursica_ui-kit_globals_form_field_colors_background
|
|
14
|
+
);
|
|
15
|
+
--form-field-background-read-only: var(
|
|
16
|
+
--recursica_ui-kit_globals_form_field_colors_background-read-only
|
|
17
|
+
);
|
|
18
|
+
--form-field-border: var(--recursica_ui-kit_globals_form_field_colors_border);
|
|
19
|
+
--form-field-border-error: var(
|
|
20
|
+
--recursica_ui-kit_globals_form_field_colors_border-error
|
|
21
|
+
);
|
|
22
|
+
--form-field-border-focus: var(
|
|
23
|
+
--recursica_ui-kit_globals_form_field_colors_border-focus
|
|
24
|
+
);
|
|
25
|
+
--form-field-border-selected: var(
|
|
26
|
+
--recursica_ui-kit_globals_form_field_colors_border-selected
|
|
27
|
+
);
|
|
28
|
+
--form-field-disabled-background: var(
|
|
29
|
+
--recursica_ui-kit_globals_form_field_colors_disabled-background
|
|
30
|
+
);
|
|
31
|
+
--form-field-disabled-text: var(
|
|
32
|
+
--recursica_ui-kit_globals_form_field_colors_disabled-text
|
|
33
|
+
);
|
|
34
|
+
--form-field-icon: var(--recursica_ui-kit_globals_form_field_colors_icon);
|
|
35
|
+
--form-field-placeholder-text-opacity: var(
|
|
36
|
+
--recursica_ui-kit_globals_form_field_colors_placeholder-text-opacity
|
|
37
|
+
);
|
|
38
|
+
--form-field-text-valued: var(
|
|
39
|
+
--recursica_ui-kit_globals_form_field_colors_text-valued
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
/* Global Form Field Geometric Sizes */
|
|
43
|
+
--form-field-border-radius: var(
|
|
44
|
+
--recursica_ui-kit_globals_form_field_size_border-radius
|
|
45
|
+
);
|
|
46
|
+
--form-field-border-size-default: var(
|
|
47
|
+
--recursica_ui-kit_globals_form_field_size_border-size-default
|
|
48
|
+
);
|
|
49
|
+
--form-field-border-size-focus: var(
|
|
50
|
+
--recursica_ui-kit_globals_form_field_size_border-size-focus
|
|
51
|
+
);
|
|
52
|
+
--form-field-horizontal-padding: var(
|
|
53
|
+
--recursica_ui-kit_globals_form_field_size_horizontal-padding
|
|
54
|
+
);
|
|
55
|
+
--form-field-icon-size: var(--recursica_ui-kit_globals_form_field_size_icon);
|
|
56
|
+
--form-field-icon-text-gap: var(
|
|
57
|
+
--recursica_ui-kit_globals_form_field_size_icon-text-gap
|
|
58
|
+
);
|
|
59
|
+
--form-field-max-width: var(
|
|
60
|
+
--recursica_ui-kit_globals_form_field_size_max-width
|
|
61
|
+
);
|
|
62
|
+
--form-field-min-width: var(
|
|
63
|
+
--recursica_ui-kit_globals_form_field_size_min-width
|
|
64
|
+
);
|
|
65
|
+
--form-field-single-line-input-height: var(
|
|
66
|
+
--recursica_ui-kit_globals_form_field_size_single-line-input-height
|
|
67
|
+
);
|
|
68
|
+
--form-field-vertical-gutter: var(
|
|
69
|
+
--recursica_ui-kit_globals_form_field_size_vertical-gutter
|
|
70
|
+
);
|
|
71
|
+
--form-field-vertical-padding: var(
|
|
72
|
+
--recursica_ui-kit_globals_form_field_size_vertical-padding
|
|
73
|
+
);
|
|
74
|
+
color: var(--form-field-text-valued);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.inputContainer {
|
|
78
|
+
display: flex;
|
|
79
|
+
flex-direction: column;
|
|
80
|
+
flex: 1;
|
|
81
|
+
min-width: 0;
|
|
82
|
+
|
|
83
|
+
/* Constrain the input natively to custom properties */
|
|
84
|
+
max-width: var(--form-control-max-width);
|
|
85
|
+
min-width: var(--form-control-min-width);
|
|
86
|
+
|
|
87
|
+
/* Hook to allow child components to add margin below themselves dynamically
|
|
88
|
+
via CSS variables (e.g. padding beneath TextField before AssistiveText renders) */
|
|
89
|
+
margin-bottom: var(--form-control-margin-bottom);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.assistive {
|
|
93
|
+
margin-top: var(
|
|
94
|
+
--recursica_ui-kit_globals_form_properties_label-field-gap-vertical
|
|
95
|
+
);
|
|
96
|
+
}
|