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:
parent
168698fe28
commit
d6f204afc1
11 changed files with 177 additions and 66 deletions
|
|
@ -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] {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
61
app/assets/stylesheets/components/toggles.scss
Normal file
61
app/assets/stylesheets/components/toggles.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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';
|
||||
|
|
|
|||
12
app/javascript/crayons/formElements/Toggles/Toggle.jsx
Normal file
12
app/javascript/crayons/formElements/Toggles/Toggle.jsx
Normal 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';
|
||||
|
|
@ -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>
|
||||
|
||||
|
|
@ -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,
|
||||
};
|
||||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
@ -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>"`;
|
||||
1
app/javascript/crayons/formElements/Toggles/index.js
Normal file
1
app/javascript/crayons/formElements/Toggles/index.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from './Toggle';
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
export * from './FormField';
|
||||
export * from './RadioButton';
|
||||
export * from './ColorPicker';
|
||||
export * from './Toggles';
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue