Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Selector size variations and switch SelectorGroup to horizontal orientation #943

Merged
merged 5 commits into from
Jun 18, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/hot-cheetahs-press.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sumup/circuit-ui': major
---

`Selector` now has multiple `size` options. `SelectorGroup` is changed to horizontal layout and to be inline element by default with an option to stretch to full width.
10 changes: 10 additions & 0 deletions packages/circuit-ui/components/Selector/Selector.docs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ same screen that they are currently in. For example, choosing a payment method o

<Story id="forms-selector--disabled" />

### Sizes

Selector component supports 3 different sizes:

- **mega**, the default size
- **kilo**, used when there are sizing constraints
- **flexible**, used for more complex content within selector which may define its own margins. **flexible** just adds a minimal equal margin on all sides.

<Story id="forms-selector--sizes" />

## SelectorGroup

<Story id="forms-selector-selectorgroup--base" />
Expand Down
20 changes: 20 additions & 0 deletions packages/circuit-ui/components/Selector/Selector.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@

import React from 'react';

import { Stack } from '../../../../.storybook/components';
import SelectorGroup from '../SelectorGroup';
import Body from '../Body';
mykolaharmash marked this conversation as resolved.
Show resolved Hide resolved

import docs from './Selector.docs.mdx';
import { Selector, SelectorProps } from './Selector';
Expand Down Expand Up @@ -62,3 +64,21 @@ Disabled.args = {
name: 'disabled',
disabled: true,
};
export const Sizes = (args: SelectorProps) => (
<Stack>
<Selector {...args} size="kilo">
Kilo
</Selector>
<Selector {...args} size="mega">
Mega
</Selector>
<Selector {...args} size="flexible">
<Body variant="highlight" noMargin>
Flexible
</Body>
<Body noMargin>Hello World!</Body>
</Selector>
</Stack>
);

Disabled.args = { ...baseArgs, disabled: true };
186 changes: 128 additions & 58 deletions packages/circuit-ui/components/Selector/Selector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,18 @@
import React, { Fragment, Ref, HTMLProps } from 'react';
import { css, jsx } from '@emotion/core';
import { Dispatch as TrackingProps } from '@sumup/collector';
import { Theme } from '@sumup/design-tokens';

import styled, { StyleProps } from '../../styles/styled';
import {
focusOutline,
hideVisually,
disableVisually,
} from '../../styles/style-mixins';
import { hideVisually, disableVisually } from '../../styles/style-mixins';
import { uniqueId } from '../../util/id';
import useClickHandler from '../../hooks/use-click-handler';
import deprecate from '../../util/deprecate';

export interface SelectorProps extends HTMLProps<HTMLInputElement> {
export type SelectorSize = 'kilo' | 'mega' | 'flexible';

export interface SelectorProps
extends Omit<HTMLProps<HTMLInputElement>, 'size'> {
/**
* Value string for input.
*/
Expand All @@ -42,6 +42,10 @@ export interface SelectorProps extends HTMLProps<HTMLInputElement> {
* The name of the selector.
*/
name?: string;
/**
* Choose from 3 sizes. Default: 'mega'.
*/
size?: SelectorSize;
/**
* Whether the selector is selected or not.
*/
Expand All @@ -68,50 +72,84 @@ export interface SelectorProps extends HTMLProps<HTMLInputElement> {
tracking?: TrackingProps;
}

type LabelElProps = Pick<SelectorProps, 'disabled' | 'noMargin'>;

const baseStyles = ({ theme }: StyleProps) => css`
label: selector__label;
display: block;
cursor: pointer;
padding: ${theme.spacings.mega} ${theme.spacings.giga};
border-radius: ${theme.borderRadius.byte};
background-color: ${theme.colors.white};
text-align: center;
position: relative;
margin-bottom: ${theme.spacings.mega};

&::before {
display: block;
content: '';
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: ${theme.borderRadius.byte};
border: ${theme.borderWidth.kilo} solid ${theme.colors.n300};
transition: border 0.1s ease-in-out;
}
type LabelElProps = Pick<
SelectorProps,
'disabled' | 'noMargin' | 'size' | 'checked'
>;

&:hover {
background-color: ${theme.colors.n100};
// const outlineStyles = ({
// theme,
// checked,
// hasFocus,
// }: StyleProps & LabelElProps) => {
mykolaharmash marked this conversation as resolved.
Show resolved Hide resolved
// const defaultBorder = `0 0 0 ${theme.borderWidth.kilo} ${theme.colors.n300}`;
// const hoverBorder = `0 0 0 ${theme.borderWidth.kilo} ${theme.colors.n500}`;
// const activeBorder = `0 0 0 ${theme.borderWidth.kilo} ${theme.colors.n700}`;
// const checkedBorder = `0 0 0 ${theme.borderWidth.mega} ${theme.colors.p500}`;
// const focusOutline = hasFocus
// ? `, 0 0 0 ${checked ? '5px' : '4px'} ${theme.colors.p300}`
// : '';

&::before {
border-color: ${theme.colors.n500};
}
}
// return css`
// box-shadow: ${`${checked ? checkedBorder : defaultBorder}${focusOutline}`};

// &:hover {
// box-shadow: ${`${checked ? checkedBorder : hoverBorder}${focusOutline}`};
// }

// &:active {
// box-shadow: ${`${checked ? checkedBorder : activeBorder}${focusOutline}`};
// }
// `;
// };

&:active {
background-color: ${theme.colors.n200};
interface OutlineStyles {
default: string;
hover: string;
active: string;
}

const getBorderStyles = (theme: Theme, checked = false): OutlineStyles => {
const defaultBorder = `0 0 0 ${theme.borderWidth.kilo} ${theme.colors.n300}`;
const hoverBorder = `0 0 0 ${theme.borderWidth.kilo} ${theme.colors.n500}`;
const activeBorder = `0 0 0 ${theme.borderWidth.kilo} ${theme.colors.n700}`;
const checkedBorder = `0 0 0 ${theme.borderWidth.mega} ${theme.colors.p500}`;

return {
default: checked ? checkedBorder : defaultBorder,
hover: checked ? checkedBorder : hoverBorder,
active: checked ? checkedBorder : activeBorder,
};
};

&::before {
border-color: ${theme.colors.n700};
const baseStyles = ({ theme, checked }: StyleProps & LabelElProps) => {
const borderStyles = getBorderStyles(theme, checked);

return css`
label: selector__label;
display: inline-block;
cursor: pointer;
padding: ${theme.spacings.mega} ${theme.spacings.giga};
background-color: ${checked ? theme.colors.p100 : theme.colors.white};
text-align: center;
position: relative;
margin-bottom: ${theme.spacings.mega};
border: none;
border-radius: ${theme.borderRadius.tera};
transition: box-shadow 0.1s ease-in-out;
box-shadow: ${borderStyles.default};

&:hover {
background-color: ${theme.colors.n100};
box-shadow: ${borderStyles.hover};
}
}
`;

&:active {
background-color: ${theme.colors.n200};
box-shadow: ${borderStyles.active};
}
`;
};

const disabledStyles = ({ disabled }: LabelElProps) =>
disabled &&
Expand All @@ -137,30 +175,59 @@ const noMarginStyles = ({ noMargin }: LabelElProps) => {
`;
};

const sizeStyles = ({ theme, size = 'mega' }: LabelElProps & StyleProps) => {
const sizeMap = {
kilo: {
padding: `${theme.spacings.bit} ${theme.spacings.mega}`,
},
mega: {
// +1px is to match the height of other form components
// like Input or Select that also have +1px for vertical padding
padding: `calc(${theme.spacings.byte} + 1px) ${theme.spacings.giga}`,
Comment on lines +158 to +160
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will be able to remove this as part of DS-18.

},
flexible: {
padding: `${theme.spacings.mega} ${theme.spacings.mega}`,
},
};

return css({
label: `selector__label--${size}`,
...sizeMap[size],
});
};

const SelectorLabel = styled('label')<LabelElProps>(
baseStyles,
sizeStyles,
disabledStyles,
noMarginStyles,
);

const inputStyles = ({ theme }: StyleProps) => css`
label: selector__input;
${hideVisually()};
const inputStyles = ({ theme, checked }: StyleProps & LabelElProps) => {
const borderStyles = getBorderStyles(theme, checked);
const focusOutline = `0 0 0 ${checked ? '5px' : '4px'} ${theme.colors.p300}`;

&:focus + label::before {
${focusOutline({ theme })};
}
return css`
label: selector__input;
${hideVisually()};

&:checked + label {
background-color: ${theme.colors.p100};
&:focus + label {
box-shadow: ${borderStyles.default}, ${focusOutline};
}

&::before {
border: ${theme.borderWidth.mega} solid ${theme.colors.p500};
&:focus + label:hover {
box-shadow: ${borderStyles.hover}, ${focusOutline};
}
}
`;

const SelectorInput = styled('input')<SelectorProps>(inputStyles);
&:focus + label:active {
box-shadow: ${borderStyles.active}, ${focusOutline};
}
`;
};

const SelectorInput = styled('input')<
HTMLProps<HTMLInputElement> & LabelElProps
>(inputStyles);

/**
* A selector allows users to choose between several mutually-exclusive choices
Expand All @@ -181,6 +248,7 @@ export const Selector = React.forwardRef(
className,
style,
noMargin,
size,
...props
}: SelectorProps,
ref: SelectorProps['ref'],
Expand Down Expand Up @@ -213,6 +281,8 @@ export const Selector = React.forwardRef(
<SelectorLabel
htmlFor={inputId}
disabled={disabled}
checked={checked}
size={size}
className={className}
style={style}
noMargin={noMargin}
Expand Down
Loading