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

feat(Checkbox)!: introduce 2.0 component #1897

Merged
merged 2 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
152 changes: 152 additions & 0 deletions src/components/Checkbox/Checkbox-v2.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
@import '../../design-tokens/mixins.css';

.checkbox {
display: flex;
gap: 0.5rem;
}

.checkbox__input {
height: 1.125rem;
width: 1.125rem;

/* The parent Checkbox component is a flex container. Make sure the input doesn't shrink. */
flex-shrink: 0;

/* Remove the browser's checkbox styles, allowing us to provide our own. */
appearance: none;

/* Magic value to center the checkbox on its label. */
margin: 3px;

border: 0.125rem solid currentColor;

/* Place the ::before content smack in the middle of the input. */
display: grid;
place-content: center;

border-radius: calc(var(--eds-theme-border-radius-objects-sm) * 1px);
}

/* TODO-AH: applying roundrect to the clip-path is tricky. selected / indeterminate state includes a square shape */
.checkbox__input:checked::before {
background-color: currentColor;
content: '';

/* Clip this element in the shape of a checkbox. The element's background color will be visible
anywhere the svg path is solid. Whatever is behind the checkbox will show through wherever the
svg is transparent. See https://developer.mozilla.org/en-US/docs/Web/CSS/clip-path */
clip-path: path(
'M10.6 16.2L17.65 9.15L16.25 7.75L10.6 13.4L7.75 10.55L6.35 11.95L10.6 16.2ZM3 21V3H21V21H3Z'
);

/* Because the path does not have an explicit viewbox, this element's bounding box establishes
one. In other words, the height/width here need to match the expected viewbox for the path. */
height: 1.5rem;
width: 1.5rem;

}

.checkbox__input:indeterminate::before {
background-color: currentColor;
content: '';

clip-path: path('M7 13H17V11H7V13ZM3 21V3H21V21H3Z');

height: 1.5rem;
width: 1.5rem;
}

.checkbox__labels {
position: relative;
}

.checkbox__sub-label {
display: block;

color: var(--eds-theme-color-text-utility-default-secondary);
}

.checkbox__input:not(:checked) {
color: var(--eds-theme-color-border-utility-default-medium-emphasis);

&:hover {
color: var(--eds-theme-color-border-utility-default-medium-emphasis-hover);
background-color: var(--eds-theme-color-background-utility-default-no-emphasis-hover);
}

&:active {
color: var(--eds-theme-color-border-utility-default-medium-emphasis-active);
background-color: var(--eds-theme-color-background-utility-default-no-emphasis-active);
}

.checkbox--error & {
color: var(--eds-theme-color-text-utility-critical);

&:hover {
color: var(--eds-theme-color-border-utility-critical-hover);
background-color: var(--eds-theme-color-background-utility-critical-no-emphasis-hover);
}

&:active {
color: var(--eds-theme-color-border-utility-critical-active);
background-color: var(--eds-theme-color-background-utility-critical-no-emphasis-active);
}
}
}

.checkbox__input:checked,
.checkbox__input:indeterminate {
color: var(--eds-theme-color-border-utility-default-high-emphasis);

&:hover {
color: var(--eds-theme-color-border-utility-default-high-emphasis-hover);
}

&:active {
color: var(--eds-theme-color-border-utility-default-high-emphasis-active);
}

.checkbox--error & {
color: var(--eds-theme-color-text-utility-critical);

&:hover {
color: var(--eds-theme-color-border-utility-critical-hover);
}

&:active {
color: var(--eds-theme-color-border-utility-critical-active);
}
}
}

.checkbox__input:disabled {
color: var(--eds-theme-color-background-utility-disabled-medium-emphasis);
cursor: not-allowed;

&:not(:checked) {
color: var(--eds-theme-color-border-utility-disabled);
background-color: var(--eds-theme-color-text-utility-disabled-secondary);
}

&:checked,
&:indeterminate {
/* because we cut a hole with the clip-path, the color is the background */
color: var(--eds-theme-color-background-utility-disabled-medium-emphasis);
/* the clip path forms a hole, so in this case the background is the color of the check itself */
background-color: var(--eds-theme-color-text-utility-disabled-primary);
}
}

/**
* Handling focus ring
* TODO-AH: focus ring clamped to the actual radio button, not its bounding label (browser default)
*/
.checkbox__input:focus-visible {
outline: 0.125rem solid var(--eds-theme-color-border-utility-focus);
}

@supports not selector(:focus-visible) {
.checkbox__input {
outline: 0.125rem solid var(--eds-theme-color-border-utility-focus);
}
}
90 changes: 90 additions & 0 deletions src/components/Checkbox/Checkbox-v2.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import type { StoryObj, Meta } from '@storybook/react';
import React from 'react';
import { Checkbox } from './Checkbox-v2';

const meta: Meta<typeof Checkbox> = {
title: 'Components/V2/Checkbox',
component: Checkbox,
args: {
disabled: false,
label: 'Checkbox',
},
parameters: {
badges: ['intro-1.0', 'current-2.0'],
},

decorators: [(Story) => <div className="p-8">{Story()}</div>],
};

export default meta;

type Story = StoryObj<typeof Checkbox>;

export const Default: Story = {};

export const WithSublabel: Story = {
args: { subLabel: 'Additional descriptive text' },
};

export const Error: Story = {
args: {
isError: true,
label: 'In error state',
},
};

export const Checked: Story = {
...Default,
args: {
defaultChecked: true,
},
};

export const Indeterminate: Story = {
args: {
indeterminate: true,
},
};

/**
* `Checkbox` can be disabled in each available state.
*/
export const Disabled: Story = {
render: (args) => (
<div className="p-0">
<Checkbox {...args} checked={false} disabled label="Disabled" />
<Checkbox {...args} checked disabled label="Disabled" />
<Checkbox {...args} disabled indeterminate label="Disabled" />
</div>
),
parameters: {
axe: {
disabledRules: ['color-contrast'],
},
},
};

/**
* `Checkbox` doesn't require a visible label if `aria-label` is provided.
*/
export const WithoutVisibleLabel: Story = {
args: {
'aria-label': 'a checkbox has no name',
label: undefined,
},
};

/**
* Long labels will sit adjacent to the text box, and allow clicking to change the state of the checkbox. When constrained,
* the text will wrap, fixing the checkbox to the top edge.
*/
export const LongLabels: Story = {
args: {
label: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
},
parameters: {
axe: {
disabledRules: ['color-contrast'],
},
},
};
Loading
Loading