Skip to content

Commit

Permalink
RAC-225: DSM checkbox (#12686)
Browse files Browse the repository at this point in the history
RAC-225: Add Checkbox component

Co-authored-by: StevenVAIDIE <StevenVAIDIE@users.noreply.github.com>

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: StevenVAIDIE <StevenVAIDIE@users.noreply.github.com>
  • Loading branch information
3 people committed Sep 11, 2020
1 parent c65634b commit 29ec1b5
Show file tree
Hide file tree
Showing 26 changed files with 650 additions and 20 deletions.
12 changes: 6 additions & 6 deletions .github/workflows/dsm-test.yml
Expand Up @@ -25,6 +25,12 @@ jobs:
run: cd akeneo-design-system && yarn test:visual:run
id: visual_test_execution

- uses: actions/upload-artifact@v2
if: failure()
with:
name: test-folder
path: akeneo-design-system/src

# Update visual by doing a pull request
- name: Update visual tests
if: failure() && steps.visual_test_execution.outcome == 'failure'
Expand Down Expand Up @@ -74,9 +80,3 @@ jobs:
A new version of the staging env has been deployed :tada:
Check it out here: https://samirboulil.github.io/dsm/${{ github.event.number }}/
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- uses: actions/upload-artifact@v2
if: failure()
with:
name: test-folder
path: akeneo-design-system/src
3 changes: 2 additions & 1 deletion akeneo-design-system/.storybook/main.js
Expand Up @@ -5,6 +5,7 @@ module.exports = {
],
"addons": [
"@storybook/addon-links",
"@storybook/addon-essentials"
"@storybook/addon-essentials",
"themeprovider-storybook/register"
]
}
10 changes: 9 additions & 1 deletion akeneo-design-system/.storybook/preview.tsx
@@ -1,9 +1,17 @@
import React from 'react';
import {addDecorator} from '@storybook/react';
import {addDecorator, addParameters} from '@storybook/react';
import { withThemesProvider } from "themeprovider-storybook";
import {StoryStyle} from '../src/shared/global';
import {pimTheme} from '../src/theme/pim';

addDecorator(story => (
<>
<StoryStyle>{story()}</StoryStyle>
</>
));

addDecorator(withThemesProvider([pimTheme]))

addParameters({
viewMode: 'docs',
});
7 changes: 4 additions & 3 deletions akeneo-design-system/package.json
Expand Up @@ -15,8 +15,8 @@
"test:visual:run": "jest --config ./jest.visual.config.js",
"test:unit:watch": "jest --watch",
"eslint": "eslint --config .eslintrc.json --quiet src/**/*.tsx",
"lint:fix": "prettier --config .prettierrc.json --parser typescript --write \"./src/**/*.ts\";",
"lint:check": "prettier --config .prettierrc.json --parser typescript --check \"./src/**/*.ts\" && eslint --config .eslintrc.json --quiet src/**/*.{ts,tsx}",
"lint:fix": "prettier --config .prettierrc.json --parser typescript --write \"./src/**/*.{ts,tsx}\";",
"lint:check": "prettier --config .prettierrc.json --parser typescript --check \"./src/**/*.{ts,tsx}\" && eslint --config .eslintrc.json --quiet src/**/*.{ts,tsx}",
"storybook:start": "start-storybook -p 6006",
"storybook:ci": "start-storybook -p 6006 --ci",
"storybook:build": "build-storybook",
Expand All @@ -41,7 +41,7 @@
"@storybook/addon-actions": "^6.0.4",
"@storybook/addon-essentials": "^6.0.4",
"@storybook/addon-links": "^6.0.4",
"@storybook/react": "^6.0.4",
"@storybook/react": "^6.0.21",
"@testing-library/jest-dom": "^5.11.3",
"@testing-library/react": "^10.4.8",
"@types/expect-puppeteer": "^4.4.3",
Expand All @@ -67,6 +67,7 @@
"rollup-plugin-babel": "^4.4.0",
"rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-node-resolve": "^5.2.0",
"themeprovider-storybook": "^1.6.2",
"ts-jest": "^26.2.0",
"typescript": "^4.0.2"
}
Expand Down
87 changes: 87 additions & 0 deletions akeneo-design-system/src/components/Checkbox/Checkbox.stories.mdx
@@ -0,0 +1,87 @@
import { useState } from 'react';
import { Meta, Story, ArgsTable, Canvas } from '@storybook/addon-docs/blocks';
import { action } from "@storybook/addon-actions";
import { Checkbox } from "./Checkbox.tsx";

<Meta
title="Components/Checkbox"
component={Checkbox}
argTypes={{
readOnly: {control: {type: 'boolean'}},
checked: {control: {type: 'boolean'}},
label: {control: {type: 'text'}},
onChange: {action: 'Checkbox component onChange'},
}}
args={{
label: 'Checkbox'
}}
/>

# Checkbox

## Usage
The checkboxes are applied when users can select all, several, or none of the options from a given list.

### Checkbox states
The checkbox control allows three states: selected, unselected, and undetermined. The undetermined state comes into play when the checkbox contains a sublist of selections, some of which are selected, and others aren't.

Users must be able to check the box by clicking directly on the box or by clicking on its label.

### Content
#### Title

If necessary, a header can accompany a set of checkboxes to provide more context or clarity.

#### Labels

Always use clear and concise labels for the checkboxes. The labels appears on the right of the checkboxes.

## Playground

Use this playground to test the checkbox component

<Canvas>
<Story name="Standard">
{(args) => {
return <Checkbox {...args}/>;
}}
</Story>
</Canvas>

<ArgsTable story="Standard" />

## Variation on state
<Story name="State">
{(args) => {
return (
<>
<Checkbox {...args} checked={true} label={'Checkbox checked'}/>
<Checkbox {...args} checked={false} undetermined={true} label={'Checkbox undetermined'}/>
<Checkbox {...args} checked={false} label={'Checkbox unchecked'}/>
</>
)
}}
</Story>

## Variation on disabled
<Story name="Disabled">
{(args) => {
return (
<>
<Checkbox {...args} readOnly={true} checked={true} label={'Checked disabled'}/>
<Checkbox {...args} readOnly={true} checked={false} undetermined={true} label={'Undetermined disabled'}/>
<Checkbox {...args} readOnly={true} checked={false} label={'Unchecked disabled'}/>
</>
)
}}
</Story>

## Animation
<Story name="Animation">
{(args) => {
const [checked, setChecked] = useState(true);
return (
<Checkbox {...args} checked={checked} onChange={(newCheck) => setChecked(newCheck)}/>
)
}}
</Story>
131 changes: 131 additions & 0 deletions akeneo-design-system/src/components/Checkbox/Checkbox.tsx
@@ -0,0 +1,131 @@
import React from 'react';
import styled, {css, keyframes} from 'styled-components';
import {AkeneoThemedProps, getColor} from '../../theme';
import {CheckIcon, PartialCheckIcon} from '../../icons';

const checkTick = keyframes`
to {
stroke-dashoffset: 0;
}
`;

const uncheckTick = keyframes`
to {
stroke-dashoffset: 20px;
}
`;

const Container = styled.div`
display: flex;
`;

const TickIcon = styled(CheckIcon)`
animation: ${uncheckTick} 0.2s ease-in forwards;
opacity: 0;
stroke-dasharray: 0px;
stroke-dashoffset: 0;
transition-delay: 0.2s;
transition: opacity 0.1s ease-out;
`;

const CheckboxContainer = styled.div<{checked: boolean; readOnly: boolean} & AkeneoThemedProps>`
background-color: transparent;
height: 20px;
width: 20px;
border: 1px solid ${getColor('grey80')};
border-radius: 3px;
outline: none;
background-color: ${getColor('grey20')};
${(props) =>
props.checked &&
css`
background-color: ${getColor('blue100')};
${TickIcon} {
animation-delay: 0.2s;
animation: ${checkTick} 0.2s ease-out forwards;
stroke-dashoffset: 20px;
opacity: 1;
transition-delay: 0s;
}
`}
${(props) =>
props.checked &&
props.readOnly &&
css`
background-color: ${getColor('blue20')};
border-color: ${getColor('blue40')};
`}
${(props) =>
!props.checked &&
props.readOnly &&
css`
background-color: ${getColor('grey60')};
border-color: ${getColor('grey100')};
`}
`;

const LabelContainer = styled.div<{readOnly: boolean} & AkeneoThemedProps>`
color: ${getColor('grey140')};
font-weight: 400;
font-size: 15px;
padding-left: 10px;
${(props) =>
props.readOnly &&
css`
color: ${getColor('grey100')};
`}
`;

type CheckboxProps = {
/**
* State of the Checkbox
*/
checked: boolean;

/**
* Displays the value of the input, but does not allow changes.s
*/
readOnly?: boolean;

/**
* The undetermined state comes into play when the checkbox contains a sublist of selections,
* some of which are selected, and others aren't.
*/
undetermined?: boolean;

/**
* Provide a description of the Checkbox, the label appears on the right of the checkboxes.
*/
label?: string;

/**
* The handler called when clicking on Checkbox
*/
onChange?: (value: boolean) => void;
};

/**
* The checkboxes are applied when users can select all, several, or none of the options from a given list.
*/
const Checkbox = ({label, checked, onChange, undetermined = false, readOnly = false}: CheckboxProps) => {
if (undefined === onChange && false === readOnly) {
throw new Error('A Checkbox element expect an onChange attribute if not readOnly');
}

const handleChange = () => onChange && !readOnly && onChange(!checked);

return (
<Container onClick={handleChange}>
<CheckboxContainer checked={checked || undetermined} readOnly={readOnly}>
{undetermined ? <PartialCheckIcon height={20} width={20} /> : <TickIcon height={20} width={20} />}
</CheckboxContainer>
{label ? <LabelContainer readOnly={readOnly}>{label}</LabelContainer> : null}
</Container>
);
};

export {Checkbox};
33 changes: 33 additions & 0 deletions akeneo-design-system/src/components/Checkbox/Checkbox.unit.tsx
@@ -0,0 +1,33 @@
import React from 'react';
import {fireEvent, render} from '../../shared/test-util';
import {Checkbox} from './Checkbox';

it('it calls onChange handler when user clicks on checkbox', () => {
const onChange = jest.fn();
const {getByText} = render(<Checkbox checked={true} onChange={onChange} label="Checkbox" />);

const checkbox = getByText('Checkbox');
fireEvent.click(checkbox);

expect(onChange).toBeCalledWith(false);
});

it('it does not call onChange handler when read-only', () => {
const onChange = jest.fn();
const {getByText} = render(<Checkbox checked={true} readOnly={true} onChange={onChange} label="Checkbox" />);

const checkbox = getByText('Checkbox');
fireEvent.click(checkbox);

expect(onChange).not.toBeCalled();
});

it('it cannot be instantiated without handler when not readonly', () => {
jest.spyOn(console, 'error').mockImplementation(() => {
// do nothing.
});

expect(() => {
render(<Checkbox checked={true} label="Checkbox" />);
}).toThrow('A Checkbox element expect an onChange attribute if not readOnly');
});
37 changes: 37 additions & 0 deletions akeneo-design-system/src/components/Checkbox/Checkbox.visual.tsx
@@ -0,0 +1,37 @@
import 'expect-puppeteer';

import {toMatchImageSnapshot} from 'jest-image-snapshot';

expect.extend({toMatchImageSnapshot});

describe('Checkbox visual tests', () => {
it('Renders standard checkbox component correctly', async () => {
await page.goto('http://localhost:6006/iframe.html?id=components-checkbox--standard');
const root = await page.$('#root');
if (null === root) return;

const image = await root.screenshot();

expect(image).toMatchImageSnapshot();
});

it('Renders checkbox component varying by state', async () => {
await page.goto('http://localhost:6006/iframe.html?id=components-checkbox--state');
const root = await page.$('#root');
if (null === root) return;

const image = await root.screenshot();

expect(image).toMatchImageSnapshot();
});

it('Renders checkbox component varying on disabled', async () => {
await page.goto('http://localhost:6006/iframe.html?id=components-checkbox--disabled');
const root = await page.$('#root');
if (null === root) return;

const image = await root.screenshot();

expect(image).toMatchImageSnapshot();
});
});
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 4 additions & 4 deletions akeneo-design-system/src/components/Dummy/Dummy.stories.mdx
@@ -1,4 +1,4 @@
import { Meta, Story, Preview, Props, ArgsTable, Canvas } from '@storybook/addon-docs/blocks';
import { Meta, Story, ArgsTable, Canvas } from '@storybook/addon-docs/blocks';
import { action } from "@storybook/addon-actions";
import { Dummy, Type } from "./Dummy.tsx";

Expand All @@ -20,13 +20,13 @@ The dummy component is a little bit dumb, but that's why it exists.

Use this playground to test the dummy component

<Preview>
<Canvas>
<Story name="Standard">
{(args) => <Dummy {...args} >Coucou</Dummy>}
</Story>
</Preview>
</Canvas>

<Props story="Standard" />
<ArgsTable story="Standard" />


## Variation on size
Expand Down

0 comments on commit 29ec1b5

Please sign in to comment.