Skip to content

Commit 1b636b5

Browse files
authored
Merge pull request #101 from arturbien/feature/menu-checkbox-and-radio
Feature/menu checkbox and radio
2 parents c8e0910 + 8eb8238 commit 1b636b5

File tree

5 files changed

+259
-24
lines changed

5 files changed

+259
-24
lines changed

src/components/Checkbox/Checkbox.js

Lines changed: 84 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { createDisabledTextStyles, createHatchedBackground } from '../common';
77
import { padding, fontSizes } from '../common/system';
88
import useControlledOrUncontrolled from '../common/hooks/useControlledOrUncontrolled';
99
import Cutout from '../Cutout/Cutout';
10+
import { StyledListItem } from '../ListItem/ListItem';
1011

1112
const checkboxSize = 20;
1213

@@ -22,6 +23,11 @@ const StyledLabel = styled.label`
2223
user-select: none;
2324
font-size: ${fontSizes.md};
2425
${props => props.isDisabled && createDisabledTextStyles()}
26+
27+
${StyledListItem} & {
28+
margin: 0;
29+
height: 100%;
30+
}
2531
`;
2632

2733
const StyledInput = styled.input`
@@ -63,6 +69,21 @@ const StyledFlatCheckbox = styled.div`
6369
background: ${({ theme, isDisabled }) =>
6470
isDisabled ? theme.flatLight : theme.canvas};
6571
`;
72+
73+
const StyledMenuCheckbox = styled.div`
74+
position: relative;
75+
box-sizing: border-box;
76+
display: inline-block;
77+
background: ${({ theme, isDisabled }) =>
78+
isDisabled ? theme.flatLight : theme.canvas};
79+
${sharedCheckboxStyles}
80+
width: ${checkboxSize - 4}px;
81+
height: ${checkboxSize - 4}px;
82+
background: none;
83+
border: none;
84+
outline: none;
85+
`;
86+
6687
const CheckmarkIcon = styled.span.attrs(() => ({
6788
'data-testid': 'checkmarkIcon'
6889
}))`
@@ -84,15 +105,46 @@ const CheckmarkIcon = styled.span.attrs(() => ({
84105
isDisabled ? theme.checkmarkDisabled : theme.checkmark};
85106
border-width: 0 3px 3px 0;
86107
transform: translate(-50%, -50%) rotate(45deg);
108+
109+
${({ variant, theme, isDisabled }) =>
110+
variant === 'menu'
111+
? css`
112+
border-color: ${isDisabled ? theme.textDisabled : theme.text};
113+
filter: drop-shadow(
114+
1px 1px 0px
115+
${isDisabled ? theme.textDisabledShadow : 'transparent'}
116+
);
117+
`
118+
: css`
119+
border-color: ${isDisabled
120+
? theme.checkmarkDisabled
121+
: theme.checkmark};
122+
`}
123+
${StyledListItem}:hover & {
124+
${({ theme, isDisabled, variant }) =>
125+
!isDisabled &&
126+
variant === 'menu' &&
127+
css`
128+
border-color: ${theme.textInvert};
129+
`};
87130
}
88131
`;
89132
const IndeterminateIcon = styled.span.attrs(() => ({
90133
'data-testid': 'indeterminateIcon'
91134
}))`
92135
display: inline-block;
93136
position: relative;
94-
width: 100%;
95-
height: 100%;
137+
138+
${({ variant }) =>
139+
variant === 'menu'
140+
? css`
141+
height: calc(100% - 4px);
142+
width: calc(100% - 4px);
143+
`
144+
: css`
145+
width: 100%;
146+
height: 100%;
147+
`}
96148
&:after {
97149
content: '';
98150
display: block;
@@ -104,12 +156,33 @@ const IndeterminateIcon = styled.span.attrs(() => ({
104156
createHatchedBackground({
105157
mainColor: isDisabled ? theme.checkmarkDisabled : theme.checkmark
106158
})}
107-
background-position: -1px -1px, 1px 1px;
108-
outline: 1px solid
109-
${({ theme, isDisabled }) => (isDisabled ? theme.material : theme.canvas)};
110-
outline-offset: -1px;
159+
background-position: 0px 0px, 2px 2px;
160+
161+
${({ variant, isDisabled, theme }) =>
162+
variant === 'menu' &&
163+
css`
164+
${StyledListItem}:hover & {
165+
${createHatchedBackground({
166+
mainColor: theme.textInvert
167+
})}
168+
}
169+
filter: drop-shadow(
170+
1px 1px 0px ${isDisabled ? theme.textDisabledShadow : 'transparent'}
171+
);
172+
`};
111173
}
112174
`;
175+
const LabelText = styled.span`
176+
display: inline-block;
177+
line-height: 1;
178+
`;
179+
180+
const CheckboxComponents = {
181+
flat: StyledFlatCheckbox,
182+
default: StyledCheckbox,
183+
menu: StyledMenuCheckbox
184+
};
185+
113186
const Checkbox = React.forwardRef(function Checkbox(props, ref) {
114187
const {
115188
onChange,
@@ -134,8 +207,8 @@ const Checkbox = React.forwardRef(function Checkbox(props, ref) {
134207
setState(newState);
135208
if (onChange) onChange(e);
136209
};
137-
const CheckboxComponent =
138-
variant === 'flat' ? StyledFlatCheckbox : StyledCheckbox;
210+
211+
const CheckboxComponent = CheckboxComponents[variant];
139212

140213
let Icon = null;
141214
if (indeterminate) {
@@ -151,9 +224,9 @@ const Checkbox = React.forwardRef(function Checkbox(props, ref) {
151224
isDisabled={disabled}
152225
role='presentation'
153226
>
154-
{Icon && <Icon isDisabled={disabled} />}
227+
{Icon && <Icon isDisabled={disabled} variant={variant} />}
155228
</CheckboxComponent>
156-
{label && <span>{label}</span>}
229+
{label && <LabelText>{label}</LabelText>}
157230
<StyledInput
158231
disabled={disabled}
159232
onChange={disabled ? undefined : handleChange}
@@ -195,7 +268,7 @@ Checkbox.propTypes = {
195268
label: propTypes.oneOfType([propTypes.string, propTypes.number]),
196269
checked: propTypes.bool,
197270
disabled: propTypes.bool,
198-
variant: propTypes.oneOf(['default', 'flat']),
271+
variant: propTypes.oneOf(['default', 'flat', 'menu']),
199272
style: propTypes.shape([propTypes.string, propTypes.number]),
200273
defaultChecked: propTypes.bool,
201274
indeterminate: propTypes.bool,

src/components/Checkbox/Checkbox.stories.js

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,15 @@ import React from 'react';
22
import { storiesOf } from '@storybook/react';
33
import styled from 'styled-components';
44

5-
import { Checkbox, Fieldset, Button, Cutout } from '..';
5+
import {
6+
Checkbox,
7+
Fieldset,
8+
Button,
9+
Cutout,
10+
List,
11+
ListItem,
12+
Divider
13+
} from '..';
614

715
const StyledCutout = styled(Cutout)`
816
background: ${({ theme }) => theme.canvas};
@@ -64,6 +72,42 @@ storiesOf('Checkbox', module)
6472
</div>
6573
</StyledCutout>
6674
))
75+
)
76+
.add('menu', () =>
77+
React.createElement(() => (
78+
<List>
79+
<ListItem size='md'>
80+
<Checkbox
81+
name='useGradient'
82+
variant='menu'
83+
value='useGradient'
84+
label='Use gradient'
85+
defaultChecked
86+
/>
87+
</ListItem>
88+
<ListItem size='md'>
89+
<Checkbox
90+
name='thickBrush'
91+
variant='menu'
92+
defaultChecked={false}
93+
value='thickBrush'
94+
label='Thick brush'
95+
indeterminate
96+
/>
97+
</ListItem>
98+
<Divider />
99+
<ListItem size='md' disabled>
100+
<Checkbox
101+
name='autoSave'
102+
variant='menu'
103+
value='autoSave'
104+
checked
105+
label='Auto-save'
106+
disabled
107+
/>
108+
</ListItem>
109+
</List>
110+
))
67111
);
68112

69113
class ControlledCheckboxGroupExample extends React.Component {

src/components/ListItem/ListItem.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,20 @@ import styled from 'styled-components';
55
import { createDisabledTextStyles } from '../common';
66
import { padding, blockSizes } from '../common/system';
77

8-
const StyledListItem = styled.li`
8+
export const StyledListItem = styled.li`
99
box-sizing: border-box;
1010
11-
display: block;
11+
display: flex;
12+
align-items: center;
1213
position: relative;
1314
height: ${props => blockSizes[props.size]};
1415
width: ${props => (props.square ? blockSizes[props.size] : 'auto')};
1516
padding: 0 ${padding.sm};
1617
1718
white-space: nowrap;
18-
text-align: ${props => (props.square ? 'center' : 'left')};
19+
justify-content: ${props =>
20+
props.square ? 'space-around' : 'space-between'};
21+
text-align: center;
1922
line-height: ${props => blockSizes[props.size]};
2023
color: ${({ theme }) => theme.text};
2124
pointer-events: ${({ isDisabled }) => (isDisabled ? 'none' : 'auto')};

src/components/Radio/Radio.js

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import styled, { css } from 'styled-components';
55
import { createDisabledTextStyles, createFlatBoxStyles } from '../common';
66
import { padding, fontSizes } from '../common/system';
77
import Cutout from '../Cutout/Cutout';
8+
import { StyledListItem } from '../ListItem/ListItem';
89

910
const radioSize = '20px';
1011
const StyledLabel = styled.label`
@@ -19,6 +20,11 @@ const StyledLabel = styled.label`
1920
user-select: none;
2021
font-size: ${fontSizes.md};
2122
${props => props.isDisabled && createDisabledTextStyles()}
23+
24+
${StyledListItem} & {
25+
margin: 0;
26+
height: 100%;
27+
}
2228
`;
2329

2430
const StyledInput = styled.input`
@@ -75,6 +81,15 @@ const StyledFlatCheckbox = styled.div`
7581
border-radius: 50%;
7682
}
7783
`;
84+
const StyledMenuCheckbox = styled.div`
85+
${sharedCheckboxStyles}
86+
position: relative;
87+
display: inline-block;
88+
box-sizing: border-box;
89+
border: none;
90+
outline: none;
91+
background: none;
92+
`;
7893
const Icon = styled.span.attrs(() => ({
7994
'data-testid': 'checkmarkIcon'
8095
}))`
@@ -87,10 +102,37 @@ const Icon = styled.span.attrs(() => ({
87102
height: 6px;
88103
transform: translate(-50%, -50%);
89104
border-radius: 50%;
90-
background: ${({ theme, isDisabled }) =>
91-
isDisabled ? theme.checkmarkDisabled : theme.checkmark};
105+
${({ variant, theme, isDisabled }) =>
106+
variant === 'menu'
107+
? css`
108+
background: ${isDisabled ? theme.textDisabled : theme.text};
109+
filter: drop-shadow(
110+
1px 1px 0px ${isDisabled ? theme.textDisabledShadow : 'transparent'}
111+
);
112+
`
113+
: css`
114+
background: ${isDisabled ? theme.checkmarkDisabled : theme.checkmark};
115+
`}
116+
${StyledListItem}:hover & {
117+
${({ theme, isDisabled, variant }) =>
118+
!isDisabled &&
119+
variant === 'menu' &&
120+
css`
121+
background: ${theme.textInvert};
122+
`};
123+
}
124+
`;
125+
const LabelText = styled.span`
126+
display: inline-block;
127+
line-height: 1;
92128
`;
93129

130+
const CheckboxComponents = {
131+
flat: StyledFlatCheckbox,
132+
default: StyledCheckbox,
133+
menu: StyledMenuCheckbox
134+
};
135+
94136
const Radio = React.forwardRef(function Radio(props, ref) {
95137
const {
96138
onChange,
@@ -104,8 +146,8 @@ const Radio = React.forwardRef(function Radio(props, ref) {
104146
style,
105147
...otherProps
106148
} = props;
107-
const CheckboxComponent =
108-
variant === 'flat' ? StyledFlatCheckbox : StyledCheckbox;
149+
150+
const CheckboxComponent = CheckboxComponents[variant];
109151

110152
return (
111153
<StyledLabel isDisabled={disabled} className={className} style={style}>
@@ -114,9 +156,9 @@ const Radio = React.forwardRef(function Radio(props, ref) {
114156
isDisabled={disabled}
115157
role='presentation'
116158
>
117-
{checked && <Icon isDisabled={disabled} />}
159+
{checked && <Icon isDisabled={disabled} variant={variant} />}
118160
</CheckboxComponent>
119-
{label && <span>{label}</span>}
161+
{label && <LabelText>{label}</LabelText>}
120162
<StyledInput
121163
disabled={disabled}
122164
onChange={disabled ? undefined : onChange}
@@ -155,7 +197,7 @@ Radio.propTypes = {
155197
label: propTypes.oneOfType([propTypes.string, propTypes.number]),
156198
checked: propTypes.bool,
157199
disabled: propTypes.bool,
158-
variant: propTypes.oneOf(['default', 'flat']),
200+
variant: propTypes.oneOf(['default', 'flat', 'menu']),
159201
style: propTypes.shape([propTypes.string, propTypes.number]),
160202
className: propTypes.string
161203
};

0 commit comments

Comments
 (0)