Skip to content

Commit f07722e

Browse files
authored
feat(Form): horizontal layout support (#283)
1 parent 861ed17 commit f07722e

File tree

14 files changed

+138
-57
lines changed

14 files changed

+138
-57
lines changed

.changeset/famous-bananas-rest.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@cube-dev/ui-kit": patch
3+
---
4+
5+
Fix label positioning for side layout in Form.
6+
Fix RangeSlider's usage inside forms with `labelPosition="side"`.

src/components/forms/Checkbox/Checkbox.tsx

+4-7
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,11 @@ const CheckboxWrapperElement = tasty({
6464
position: 'relative',
6565
display: 'flex',
6666
placeItems: 'center start',
67+
placeContent: 'baseline',
6768
gap: '1x',
6869
flow: 'row',
6970
preset: 'default',
7071
cursor: 'pointer',
71-
margin: {
72-
'': 0,
73-
'inside-form & side-label': '1.5x top',
74-
},
7572
},
7673
});
7774

@@ -266,7 +263,7 @@ function Checkbox(
266263
ref={domRef}
267264
>
268265
{checkboxField}
269-
{label && (
266+
{label ? (
270267
<Element
271268
styles={labelStyles}
272269
mods={{
@@ -276,9 +273,9 @@ function Checkbox(
276273
}}
277274
{...filterBaseProps(labelProps)}
278275
>
279-
{label || children}
276+
{label}
280277
</Element>
281-
)}
278+
) : null}
282279
</CheckboxWrapperElement>
283280
);
284281
}

src/components/forms/Checkbox/CheckboxGroup.tsx

-5
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,6 @@ const WRAPPER_STYLES = {
2929
'': '1fr',
3030
'has-sider': 'max-content 1fr',
3131
},
32-
gap: {
33-
'': '0',
34-
'has-sider': '1x',
35-
},
3632
placeItems: 'baseline start',
3733
};
3834

@@ -50,7 +46,6 @@ const CheckGroupElement = tasty({
5046
'': '1x',
5147
horizontal: '1x 2x',
5248
},
53-
padding: '(1x - 1bw) 0',
5449
},
5550
});
5651

src/components/forms/FieldWrapper.tsx

+8-9
Original file line numberDiff line numberDiff line change
@@ -22,23 +22,22 @@ const FieldElement = tasty({
2222
display: 'grid',
2323
gridColumns: {
2424
'': '1fr',
25-
'has-sider': '@(label-width, auto) 1fr',
25+
'has-sider': '@(full-label-width, auto) 1fr',
2626
},
27-
gap: {
28-
'': '1x',
29-
'has-sider': '@(column-gap, 1x)',
30-
},
31-
placeItems: 'start stretch',
27+
gap: 0,
28+
placeItems: 'baseline stretch',
29+
'@full-label-width': '(@label-width + 1x)',
3230

3331
LabelArea: {
3432
display: 'block',
3533
width: {
3634
'': 'initial',
3735
'has-sider': '@label-width',
3836
},
39-
padding: {
40-
'': 'initial',
41-
'has-sider': '1.25x top',
37+
margin: {
38+
'': '1x bottom',
39+
'has-sider': '1x right',
40+
':empty': '0',
4241
},
4342
},
4443

src/components/forms/Form/ComplexForm.stories.tsx

+7-3
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ const Template: StoryFn<typeof Form> = (args) => {
266266
return !!email;
267267
}}
268268
>
269-
<TextInput type="email" label="Email field" />
269+
<TextInput type="email" size="small" label="Email field" />
270270
</Field>
271271
<Field name="password">
272272
<PasswordInput label="Password field" />
@@ -306,7 +306,7 @@ const Template: StoryFn<typeof Form> = (args) => {
306306
</CheckboxGroup>
307307
</Field>
308308
<Field name="radioGroup" label="Radio group">
309-
<Radio.Group>
309+
<Radio.Group orientation="horizontal">
310310
<Radio value="one">One</Radio>
311311
<Radio value="two">Two</Radio>
312312
<Radio value="three">Three</Radio>
@@ -322,7 +322,7 @@ const Template: StoryFn<typeof Form> = (args) => {
322322
name="switch"
323323
rules={[{ required: true, message: 'This field is required' }]}
324324
>
325-
<Switch label="Switch field" />
325+
<Switch label="Switch field">Switch value</Switch>
326326
</Field>
327327
<Field
328328
name="number"
@@ -331,6 +331,7 @@ const Template: StoryFn<typeof Form> = (args) => {
331331
<NumberInput label="Number field" minValue={-1} />
332332
</Field>
333333
<Field
334+
label="Slider"
334335
name="slider"
335336
rules={[{ required: true, message: 'This field is required' }]}
336337
>
@@ -352,6 +353,9 @@ export const FormInsideDialog: StoryFn = () => {
352353

353354
export const Default = Template.bind({});
354355

356+
export const ComplexFormSideLabel = Template.bind({});
357+
ComplexFormSideLabel.args = { labelPosition: 'side' };
358+
355359
export const ComplexErrorMessage = ComplexErrorTemplate.bind({});
356360

357361
export const AsyncValidation = AsyncValidationTemplate.bind({});

src/components/forms/Form/Field.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ export function Field<T extends FieldTypes>(allProps: CubeFieldProps<T>) {
188188
labelStyles,
189189
labelSuffix,
190190
} = props;
191+
191192
const nonInput = !name;
192193
const fieldName: string =
193194
name != null ? (Array.isArray(name) ? name.join('.') : name) : '';

src/components/forms/Form/Form.tsx

+30-4
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,18 @@ const FormElement = tasty({
3333
as: 'form',
3434
qa: 'Form',
3535
styles: {
36-
display: 'block',
37-
flow: 'column',
36+
display: {
37+
'': 'block',
38+
horizontal: 'flex',
39+
},
40+
flow: {
41+
'': 'column',
42+
horizontal: 'row',
43+
},
44+
placeItems: {
45+
'': 'initial',
46+
horizontal: 'center',
47+
},
3848
gap: '2x',
3949
'@label-width': '25x',
4050
},
@@ -76,6 +86,7 @@ export interface CubeFormProps<T extends FieldTypes = FieldTypes>
7686
form?: CubeFormInstance<T, CubeFormData<T>>;
7787
/** The size of the side area with labels. Only for `labelPosition="side"` */
7888
labelWidth?: Styles['width'];
89+
orientation?: 'vertical' | 'horizontal';
7990
}
8091

8192
function Form<T extends FieldTypes>(
@@ -87,7 +98,8 @@ function Form<T extends FieldTypes>(
8798
qa,
8899
name,
89100
children,
90-
labelPosition = 'top',
101+
labelPosition,
102+
orientation,
91103
isRequired,
92104
necessityIndicator,
93105
isDisabled,
@@ -105,6 +117,19 @@ function Form<T extends FieldTypes>(
105117
...otherProps
106118
} = props;
107119
const firstRunRef = useRef(true);
120+
const isHorizontal = orientation === 'horizontal';
121+
122+
if (!orientation) {
123+
orientation = 'vertical';
124+
}
125+
126+
if (!labelPosition) {
127+
labelPosition = isHorizontal ? 'side' : 'top';
128+
}
129+
130+
if (!labelWidth) {
131+
labelWidth = isHorizontal ? 'auto' : undefined;
132+
}
108133

109134
ref = useCombinedRefs(ref);
110135

@@ -180,6 +205,7 @@ function Form<T extends FieldTypes>(
180205
let ctx = {
181206
labelPosition,
182207
labelStyles,
208+
orientation,
183209
necessityIndicator,
184210
validateTrigger,
185211
requiredMark,
@@ -208,7 +234,7 @@ function Form<T extends FieldTypes>(
208234
ref={domRef}
209235
noValidate
210236
styles={styles}
211-
mods={{ 'has-sider': labelPosition === 'side' }}
237+
mods={{ 'has-sider': labelPosition === 'side', horizontal: isHorizontal }}
212238
onSubmit={onSubmitCallback}
213239
>
214240
<FormContext.Provider value={ctx}>

src/components/forms/RadioGroup/RadioGroup.tsx

+1-4
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,6 @@ const RadioGroupElement = tasty({
4242
horizontal: 'row wrap',
4343
},
4444
gap: '1x',
45-
padding: {
46-
'': 0,
47-
'inside-form & side-label': '1.5x 0',
48-
},
4945
},
5046
});
5147

@@ -124,6 +120,7 @@ function RadioGroup(props: WithNullableValue<CubeRadioGroupProps>, ref) {
124120
requiredMark,
125121
tooltip,
126122
isHidden,
123+
orientation,
127124
Component: radioGroup,
128125
ref: domRef,
129126
labelSuffix,

src/components/forms/RangeSlider/RangeInput.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ export function RangeInput(props: RangeInputProps) {
4040

4141
return (
4242
<NumberInput
43+
insideForm={false}
44+
labelPosition="top"
4345
{...otherProps}
4446
hideStepper
4547
size="small"

src/components/forms/RangeSlider/RangeSlider.tsx

+10-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
Styles,
1414
} from '../../../tasty';
1515
import { CubeNumberInputProps } from '../NumberInput/NumberInput';
16+
import { useFormProps } from '../Form';
1617

1718
import { Slide } from './Slide';
1819
import { Gradation } from './Gradation';
@@ -45,7 +46,12 @@ export interface CubeRangeSliderProps
4546
formatOptions?: CubeNumberInputProps['formatOptions'];
4647
}
4748

48-
function RangeSlider(props: CubeRangeSliderProps, ref: DOMRef<HTMLDivElement>) {
49+
function RangeSlider(
50+
allProps: CubeRangeSliderProps,
51+
ref: DOMRef<HTMLDivElement>,
52+
) {
53+
const props = useFormProps(allProps);
54+
4955
let {
5056
labelPosition,
5157
label,
@@ -64,7 +70,6 @@ function RangeSlider(props: CubeRangeSliderProps, ref: DOMRef<HTMLDivElement>) {
6470
gradation,
6571
step = 1,
6672
isDisabled,
67-
orientation = 'horizontal',
6873
showInput,
6974
inputStyles,
7075
minValue,
@@ -73,9 +78,12 @@ function RangeSlider(props: CubeRangeSliderProps, ref: DOMRef<HTMLDivElement>) {
7378
formatOptions,
7479
onChange,
7580
onChangeEnd,
81+
orientation: formOrientation,
7682
...otherProps
7783
} = props;
7884

85+
let orientation = allProps.orientation || 'horizontal';
86+
7987
const [min, max] = getMinMaxValue(minValue, maxValue, value || defaultValue);
8088
const ranges = getRanges(defaultValue || value || [0, 1]);
8189
const isSingle = ranges.length <= 1;

src/components/forms/RangeSlider/styled.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,12 @@ export const StyledControls = tasty({
7878
});
7979

8080
export const StyledSlider = tasty({
81+
qa: 'RangeSlider',
8182
as: 'section',
8283
styles: {
8384
position: 'relative',
84-
display: 'flex',
85+
display: 'inline-flex',
86+
verticalAlign: 'middle',
8587
gap: {
8688
'': '0',
8789
inputs: '1x',
@@ -92,10 +94,6 @@ export const StyledSlider = tasty({
9294
},
9395
alignItems: 'center',
9496
width: '100%',
95-
padding: {
96-
'': '0',
97-
sideLabel: '0.75x top',
98-
},
9997
},
10098
});
10199

0 commit comments

Comments
 (0)