Skip to content

Commit 667fbda

Browse files
authored
CheckboxGroup & RadioGroup wrapping and inline height (#1028)
1 parent 7ea07f7 commit 667fbda

File tree

9 files changed

+317
-155
lines changed

9 files changed

+317
-155
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## UNRELEASED
99

10+
### Added
11+
12+
- `CheckboxGroup` and `RadioGroup` now support wrapping when `inline` is used, and they use the same height as other inputs
13+
1014
### Changed
1115

1216
- `Icon` now supports "t-shirt" sizing. (i.e.: `size="small"`)

packages/components/src/Form/Fields/FieldCheckboxGroup/FieldCheckboxGroup.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,16 @@ import { Field, FieldProps, omitFieldProps, pickFieldProps } from '../Field'
3333

3434
export interface FieldCheckboxGroupProps
3535
extends CheckboxGroupProps,
36-
Omit<FieldProps, 'detail'> {}
36+
Omit<FieldProps, 'detail'> {
37+
inputsInline?: boolean
38+
}
3739

3840
const FieldCheckboxGroupLayout: FC<FieldCheckboxGroupProps> = ({
3941
id: propsID,
4042
options,
4143
value,
4244
name,
45+
inputsInline,
4346
...props
4447
}) => {
4548
const validationMessage = useFormContext(props)
@@ -56,7 +59,7 @@ const FieldCheckboxGroupLayout: FC<FieldCheckboxGroupProps> = ({
5659
aria-describedby={`${id}-describedby`}
5760
aria-labelledby={`${id}-labelledby`}
5861
id={id}
59-
inline={props.inline}
62+
inline={props.inline || inputsInline}
6063
name={name || id}
6164
options={options}
6265
value={value}

packages/components/src/Form/Fields/FieldRadioGroup/FieldRadioGroup.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,15 @@ import { RadioGroup, RadioGroupProps } from '../../Inputs/OptionsGroup'
3333

3434
export interface FieldRadioGroupProps
3535
extends RadioGroupProps,
36-
Omit<FieldProps, 'detail'> {}
36+
Omit<FieldProps, 'detail'> {
37+
inputsInline?: boolean
38+
}
3739

3840
const FieldRadioGroupLayout: FC<FieldRadioGroupProps> = ({
3941
id: propsID,
4042
options,
4143
name,
44+
inputsInline,
4245
...props
4346
}) => {
4447
const validationMessage = useFormContext(props)
@@ -55,7 +58,7 @@ const FieldRadioGroupLayout: FC<FieldRadioGroupProps> = ({
5558
aria-describedby={`${id}-describedby`}
5659
aria-labelledby={`${id}-labelledby`}
5760
id={id}
58-
inline={props.inline}
61+
inline={props.inline || inputsInline}
5962
name={name || id}
6063
options={options}
6164
/>

packages/components/src/Form/Inputs/OptionsGroup/CheckboxGroup.tsx

Lines changed: 72 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,12 @@
2525
*/
2626

2727
import xor from 'lodash/xor'
28-
import React, { FC, useCallback, useRef } from 'react'
28+
import React, { forwardRef, useCallback, useRef, Ref } from 'react'
29+
import styled from 'styled-components'
2930
import { useID } from '../../../utils'
3031
import { Fieldset } from '../../Fieldset'
31-
import { FieldCheckbox } from '../../Fields'
32+
import { FieldCheckbox } from '../../Fields/FieldCheckbox'
33+
import { inputHeight } from '../InputText/InputText'
3234
import { OptionsGroupProps } from './OptionsGroup'
3335

3436
export type CheckboxGroupProps = OptionsGroupProps<string[]>
@@ -45,54 +47,75 @@ function getCheckedProps(
4547
return { [key]: valueToUse.includes(optionValue) }
4648
}
4749

48-
export const CheckboxGroup: FC<CheckboxGroupProps> = ({
49-
disabled,
50-
name: propsName,
51-
options,
52-
defaultValue = [],
53-
value,
54-
onChange,
55-
...rest
56-
}) => {
57-
const name = useID(propsName)
58-
// Keep track of the group's value for onChange argument if value prop is not used
59-
// (i.e.uncontrolled but with onChange prop)
60-
const uncontrolledValueRef = useRef(defaultValue)
61-
62-
const getChangeHandler = useCallback(
63-
(optionValue: string) => {
64-
return onChange
65-
? () => {
66-
const oldValue = value || uncontrolledValueRef.current
67-
const newValue = xor(oldValue, [optionValue])
68-
onChange(newValue)
69-
uncontrolledValueRef.current = newValue
70-
}
71-
: undefined
72-
},
73-
[onChange, value]
74-
)
75-
76-
const checkboxes = options.map((option) => {
77-
const checkedProps = getCheckedProps(option.value, value, defaultValue)
78-
const handleChange = getChangeHandler(option.value)
50+
const CheckboxGroupLayout = forwardRef(
51+
(
52+
{
53+
disabled,
54+
inline,
55+
name: propsName,
56+
options,
57+
defaultValue = [],
58+
value,
59+
onChange,
60+
...rest
61+
}: CheckboxGroupProps,
62+
ref: Ref<HTMLDivElement>
63+
) => {
64+
const name = useID(propsName)
65+
// Keep track of the group's value for onChange argument if value prop is not used
66+
// (i.e.uncontrolled but with onChange prop)
67+
const uncontrolledValueRef = useRef(defaultValue)
68+
69+
const getChangeHandler = useCallback(
70+
(optionValue: string) => {
71+
return onChange
72+
? () => {
73+
const oldValue = value || uncontrolledValueRef.current
74+
const newValue = xor(oldValue, [optionValue])
75+
onChange(newValue)
76+
uncontrolledValueRef.current = newValue
77+
}
78+
: undefined
79+
},
80+
[onChange, value]
81+
)
82+
83+
const checkboxes = options.map((option) => {
84+
const checkedProps = getCheckedProps(option.value, value, defaultValue)
85+
const handleChange = getChangeHandler(option.value)
86+
87+
return (
88+
<FieldCheckbox
89+
onChange={handleChange}
90+
disabled={option.disabled || disabled}
91+
key={option.value}
92+
label={option.label}
93+
name={name}
94+
value={option.value}
95+
{...checkedProps}
96+
/>
97+
)
98+
})
7999

80100
return (
81-
<FieldCheckbox
82-
onChange={handleChange}
83-
disabled={option.disabled || disabled}
84-
key={option.value}
85-
label={option.label}
86-
name={name}
87-
value={option.value}
88-
{...checkedProps}
89-
/>
101+
<Fieldset
102+
data-testid="checkbox-list"
103+
inline={inline}
104+
flexWrap={inline ? 'wrap' : undefined}
105+
width={inline ? 'auto' : undefined}
106+
ref={ref}
107+
{...rest}
108+
>
109+
{checkboxes}
110+
</Fieldset>
90111
)
91-
})
112+
}
113+
)
92114

93-
return (
94-
<Fieldset data-testid="checkbox-list" {...rest}>
95-
{checkboxes}
96-
</Fieldset>
97-
)
98-
}
115+
CheckboxGroupLayout.displayName = 'CheckboxGroupLayout'
116+
117+
export const CheckboxGroup = styled(CheckboxGroupLayout)`
118+
${FieldCheckbox} {
119+
${({ inline }) => (inline ? `line-height: ${inputHeight};` : '')}
120+
}
121+
`

packages/components/src/Form/Inputs/OptionsGroup/RadioGroup.tsx

Lines changed: 64 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,12 @@
2323
SOFTWARE.
2424
2525
*/
26-
import React, { FC, useCallback } from 'react'
26+
import React, { forwardRef, useCallback, Ref } from 'react'
27+
import styled from 'styled-components'
2728
import { useID } from '../../../utils'
2829
import { Fieldset } from '../../Fieldset'
29-
import { FieldRadio } from '../../Fields'
30+
import { FieldRadio } from '../../Fields/FieldRadio'
31+
import { inputHeight } from '../InputText/InputText'
3032
import { OptionsGroupProps } from './OptionsGroup'
3133

3234
export type RadioGroupProps = OptionsGroupProps<string>
@@ -44,48 +46,69 @@ function getCheckedProps(
4446
return { [key]: valueToUse === optionValue }
4547
}
4648

47-
export const RadioGroup: FC<RadioGroupProps> = ({
48-
disabled,
49-
name: propsName,
50-
options,
51-
defaultValue,
52-
value,
53-
onChange,
54-
...rest
55-
}) => {
56-
const name = useID(propsName)
57-
const isControlled = onChange !== undefined && defaultValue === undefined
58-
59-
const getChangeHandler = useCallback(
60-
(optionValue: string) => {
61-
return onChange ? () => onChange(optionValue) : undefined
62-
},
63-
[onChange]
64-
)
65-
66-
const radios = options.map((option) => {
67-
const checkedProps = getCheckedProps(
68-
option.value,
69-
isControlled,
49+
const RadioGroupLayout = forwardRef(
50+
(
51+
{
52+
disabled,
53+
inline,
54+
name: propsName,
55+
options,
56+
defaultValue,
7057
value,
71-
defaultValue
58+
onChange,
59+
...rest
60+
}: RadioGroupProps,
61+
ref: Ref<HTMLDivElement>
62+
) => {
63+
const name = useID(propsName)
64+
const isControlled = onChange !== undefined && defaultValue === undefined
65+
66+
const getChangeHandler = useCallback(
67+
(optionValue: string) => {
68+
return onChange ? () => onChange(optionValue) : undefined
69+
},
70+
[onChange]
7271
)
7372

73+
const radios = options.map((option) => {
74+
const checkedProps = getCheckedProps(
75+
option.value,
76+
isControlled,
77+
value,
78+
defaultValue
79+
)
80+
81+
return (
82+
<FieldRadio
83+
disabled={option.disabled || disabled}
84+
key={option.value}
85+
label={option.label}
86+
name={name}
87+
onChange={getChangeHandler(option.value)}
88+
{...checkedProps}
89+
/>
90+
)
91+
})
92+
7493
return (
75-
<FieldRadio
76-
disabled={option.disabled || disabled}
77-
key={option.value}
78-
label={option.label}
79-
name={name}
80-
onChange={getChangeHandler(option.value)}
81-
{...checkedProps}
82-
/>
94+
<Fieldset
95+
data-testid="radio-list"
96+
inline={inline}
97+
flexWrap={inline ? 'wrap' : undefined}
98+
width={inline ? 'auto' : undefined}
99+
ref={ref}
100+
{...rest}
101+
>
102+
{radios}
103+
</Fieldset>
83104
)
84-
})
105+
}
106+
)
85107

86-
return (
87-
<Fieldset data-testid="radio-list" disabled={disabled} {...rest}>
88-
{radios}
89-
</Fieldset>
90-
)
91-
}
108+
RadioGroupLayout.displayName = 'RadioGroupLayout'
109+
110+
export const RadioGroup = styled(RadioGroupLayout)`
111+
${FieldRadio} {
112+
${({ inline }) => (inline ? `line-height: ${inputHeight};` : '')}
113+
}
114+
`

packages/components/src/Layout/Space/Space.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,19 @@ export const spaceCSS = css`
8888
`
8989

9090
const fauxFlexGap = (space: SpaceHelperProps) => css`
91+
${({ theme }) =>
92+
space.flexWrap === 'wrap'
93+
? `margin-left: -${theme.space[space.gap || defaultSpaceSize]};`
94+
: ''}
95+
9196
&& > * {
9297
margin-left: ${({ theme }) => theme.space[space.gap || defaultSpaceSize]};
9398
}
9499
95100
${({ theme }) =>
96-
space.reverse
101+
space.flexWrap === 'wrap'
102+
? ''
103+
: space.reverse
97104
? `&& > *:last-child { margin-left: ${theme.space.none}; }`
98105
: `&& > *:first-child { margin-left: ${theme.space.none}; }`}
99106
`

0 commit comments

Comments
 (0)