Toggle component (#17126)

* toggle

* box-sizing

* fixes

* whoopsy

* add specs for toggle component

Co-authored-by: Suzanne Aitchison <suzanne@forem.com>
This commit is contained in:
ludwiczakpawel 2022-04-11 09:44:43 +02:00 committed by GitHub
parent 168698fe28
commit d6f204afc1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 177 additions and 66 deletions

View file

@ -4,17 +4,16 @@
[class^='crayons']::after,
[class^='crayons'] *,
[class^='crayons'] *::before,
[class^='crayons'] *::after {
[class^='crayons'] *::after,
[class^='c-'],
[class^='c-']::before,
[class^='c-']::after,
[class^='c-'] *,
[class^='c-'] *::before,
[class^='c-'] *::after {
box-sizing: border-box;
}
// TODO: uncomment when we're ready!
// *,
// *::before,
// *::after {
// box-sizing: border-box;
// }
// Remove default padding
ul[class],
ol[class] {

View file

@ -275,61 +275,3 @@ textarea.crayons-textfield.crayons-textfield--ghost {
min-width: min-content;
}
}
///////////////////////////////////////////////////
.c-toggle {
--knob-position: 2px;
--size: calc(1.5em - var(--su-1));
display: inline-flex;
border-radius: 1000px;
$this: '.c-toggle';
&__control {
width: 3em;
height: 1.5em;
padding: 2px;
cursor: pointer;
display: inline-flex;
position: relative;
background: var(--toggle-rail-bg);
border-radius: 1000px;
transition: var(--transition-props);
&::after {
content: '';
background: var(--toggle-knob-bg);
position: absolute;
left: var(--knob-position);
right: 100%;
width: var(--size);
height: var(--size);
border-radius: 1000px;
transition: var(--transition-props);
box-shadow: 0 0 0 1px rgba(var(--black), 0.05),
0 2px 3px rgba(var(--black), 0.2);
}
}
[type='checkbox'] {
position: absolute;
left: -10000px;
top: auto;
width: 1px;
height: 1px;
overflow: hidden;
&:checked + #{$this}__control {
background: var(--toggle-rail-checked-bg);
&::after {
left: calc(100% - var(--size) - 2px);
right: var(--knob-position);
}
}
}
&:focus-within {
box-shadow: var(--focus-ring);
outline: 0;
}
}

View file

@ -0,0 +1,61 @@
.c-toggle {
--gap: 2px;
width: 2.75em;
height: 1.5em;
display: inline-flex;
cursor: pointer;
position: relative;
$this: '.c-toggle';
&__control {
width: 100%;
height: 100%;
display: inline-flex;
background: var(--toggle-rail-bg);
border-radius: 1000px;
transition: var(--transition-props);
&::after {
content: '';
background: var(--toggle-knob-bg);
position: absolute;
inset: var(--gap);
aspect-ratio: 1;
height: calc(100% - (var(--gap) * 2));
border-radius: 1000px;
transition: var(--transition-props);
box-shadow: 0 0 0 1px rgba(var(--black), 0.05),
0 2px 3px rgba(var(--black), 0.2);
}
}
[type='checkbox'] {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
cursor: pointer;
z-index: var(--z-elevate);
opacity: var(--opacity-0);
margin: 0;
&:checked + #{$this}__control {
background: var(--toggle-rail-checked-bg);
&::after {
transform: translateX(100%);
}
}
&:focus-visible + #{$this}__control {
box-shadow: var(--focus-ring);
outline: 0;
}
&[disabled] + #{$this}__control {
opacity: 0.6;
cursor: not-allowed;
}
}
}

View file

@ -18,6 +18,7 @@
@import 'components/dropdowns';
@import 'components/editor-toolbar';
@import 'components/forms';
@import 'components/toggles';
@import 'components/hamburger';
@import 'components/header';
@import 'components/headers';

View file

@ -0,0 +1,12 @@
import { h } from 'preact';
export const Toggle = ({ ...otherProps }) => {
return (
<div class="c-toggle">
<input type="checkbox" {...otherProps} />
<span class="c-toggle__control" />
</div>
);
};
Toggle.displayName = 'Toggle';

View file

@ -0,0 +1,17 @@
# Toggles
Toggle is basically a more fancy styled `input[type="checkbox"]` element (it literally has checkbox under the hood).
For that reason Toggles inherit all props usually associated with classic checkboxes.
## Usage
There's no actual configuration for this component but keep in mind:
> **Toggles (as all form elements) in general should be used wrapped with actual `<label>s` providing all the necessary context and purpose for a given form element.**
So the `<label>` element used in Storybook is just exemplary.
## Design
<iframe style="border: 1px solid rgba(0, 0, 0, 0.1);" width="100%" height="450" src="https://www.figma.com/embed?embed_host=share&url=https%3A%2F%2Fwww.figma.com%2Ffile%2FgHpwugeu9jFGW4d9u3ReuR%2FCrayons---NEW%3Fnode-id%3D508%253A564" allowfullscreen></iframe>

View file

@ -0,0 +1,41 @@
import { h } from 'preact';
import { Toggle } from '..';
import ToggleDoc from './Toggles.mdx';
export default {
component: Toggle,
title: 'Components/Form Elements/Toggles',
decorators: [
(story) => (
<label class="flex gap-2">
{story()}
Remember: form elements should be wrapped with labels!
</label>
),
],
parameters: {
docs: {
page: ToggleDoc,
},
},
argTypes: {
checked: {
description:
'In general Toggle is just a `checkbox` wrapped in more fancy package. And the `checked` prop is the only one affecting visuals of the toggle.',
table: {
defaultValue: { summary: false },
},
},
},
};
export const Default = (args) => <Toggle {...args} />;
Default.args = {
checked: false,
};
export const Checked = (args) => <Toggle {...args} />;
Checked.args = {
...Default.args,
checked: true,
};

View file

@ -0,0 +1,31 @@
import { h } from 'preact';
import { render } from '@testing-library/preact';
import { axe } from 'jest-axe';
import { Toggle } from '@crayons';
describe('<Toggle />', () => {
it('should have no a11y violations when rendered with a label', async () => {
const { container } = render(
// Disabling this lint warning, as the linter doesn't know the <Toggle /> component includes the input it's looking for
// eslint-disable-next-line jsx-a11y/label-has-associated-control
<label>
Example label
<Toggle />
</label>,
);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
it('should render default', () => {
const { container } = render(<Toggle />);
expect(container.innerHTML).toMatchSnapshot();
});
it('should render with additional input props', () => {
const { container } = render(
<Toggle disabled={true} className="example-class" />,
);
expect(container.innerHTML).toMatchSnapshot();
});
});

View file

@ -0,0 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Toggle /> should render default 1`] = `"<div class=\\"c-toggle\\"><input type=\\"checkbox\\"><span class=\\"c-toggle__control\\"></span></div>"`;
exports[`<Toggle /> should render with additional input props 1`] = `"<div class=\\"c-toggle\\"><input type=\\"checkbox\\" disabled=\\"\\" class=\\"example-class\\"><span class=\\"c-toggle__control\\"></span></div>"`;

View file

@ -0,0 +1 @@
export * from './Toggle';

View file

@ -1,3 +1,4 @@
export * from './FormField';
export * from './RadioButton';
export * from './ColorPicker';
export * from './Toggles';