Skip to content

Commit

Permalink
feat: custom checkbox component
Browse files Browse the repository at this point in the history
  • Loading branch information
Robbert committed Sep 25, 2021
1 parent 2ba5735 commit 9fe5980
Show file tree
Hide file tree
Showing 7 changed files with 546 additions and 1 deletion.
6 changes: 5 additions & 1 deletion components/checkbox/bem.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@
* Copyright (c) 2021 Robbert Broersma
*/

.utrecht-checkbox {
@mixin reset-input-checkbox {
margin-block-end: 0; /* reset native margin for input[type="checkbox"] */
margin-block-start: 0;
margin-inline-end: 0;
margin-inline-start: 0;
}

.utrecht-checkbox {
@include reset-input-checkbox;
}

.utrecht-checkbox--disabled {
cursor: var(--utrecht-action-disabled-cursor);
}
46 changes: 46 additions & 0 deletions components/custom-checkbox/bem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* @license EUPL-1.2
* Copyright (c) 2021 Robbert Broersma
*/

import clsx from 'clsx';

export const defaultArgs = {
active: false,
checked: false,
disabled: false,
focus: false,
indeterminate: false,
invalid: false,
required: false,
value: '',
};

export const CustomCheckbox = ({
active = false,
checked = false,
disabled = false,
focus = false,
indeterminate = false,
invalid = false,
required = false,
value = '',
}) =>
`<span class="utrecht-custom-checkbox">
<input type="checkbox"${checked ? ' checked' : ''}${disabled ? ' disabled' : ''}${required ? ' required' : ''}${
value ? ` value="${value}"` : ''
} class="utrecht-custom-checkbox__input">
<span class="${clsx(
'utrecht-custom-checkbox__box',
active && 'utrecht-custom-checkbox__box--active',
checked && 'utrecht-custom-checkbox__box--checked',
!checked && 'utrecht-custom-checkbox__box--not-checked',
disabled && 'utrecht-custom-checkbox__box--disabled',
focus && 'utrecht-custom-checkbox__box--focus',
invalid && 'utrecht-custom-checkbox__box--invalid',
indeterminate && 'utrecht-custom-checkbox__box--indeterminate',
)}">
<utrecht-icon-checkmark class="utrecht-custom-checkbox__icon utrecht-custom-checkbox__icon--checked">✔</utrecht-icon-checkmark>
<utrecht-icon-indeterminate class="utrecht-custom-checkbox__icon utrecht-custom-checkbox__icon--indeterminate">■</utrecht-icon-indeterminate>
</span>
</span>`;
134 changes: 134 additions & 0 deletions components/custom-checkbox/bem.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/**
* @license EUPL-1.2
* Copyright (c) 2021 Robbert Broersma
*/

@import "../checkbox/bem";
@import "../focus/bem";

.utrecht-custom-checkbox {
--utrecht-icon-size: var(--utrecht-custom-checkbox-icon-size, calc(0.75 * var(--utrecht-custom-checkbox-size)));
display: inline-block;
height: var(--utrecht-custom-checkbox-size);
position: relative;
width: var(--utrecht-custom-checkbox-size);
}

.utrecht-custom-checkbox__input,
.utrecht-custom-checkbox__box {
left: 0;
position: absolute;
top: 0;
}

.utrecht-custom-checkbox__input {
@include reset-input-checkbox;
height: var(--utrecht-custom-checkbox-size);
opacity: 0%;
width: var(--utrecht-custom-checkbox-size);
z-index: 10;
}

.utrecht-custom-checkbox__box {
align-items: center;
background-color: var(--utrecht-custom-checkbox-background-color);
border-color: var(--utrecht-custom-checkbox-border-color, var(--utrecht-form-input-border-color));
border-radius: var(--utrecht-custom-checkbox-border-radius, var(--utrecht-form-input-border-radius));
border-style: solid;
border-width: var(--utrecht-custom-checkbox-border-width, var(--utrecht-form-input-border-width));
box-sizing: border-box;
color: var(--utrecht-custom-checkbox-color);
display: flex;
height: var(--utrecht-custom-checkbox-size);
justify-content: center;
pointer-events: none;
width: var(--utrecht-custom-checkbox-size);
z-index: 1000;
}

.utrecht-custom-checkbox__box--checked {
background-color: var(
--utrecht-custom-checkbox-checked-background-color,
var(--utrecht-custom-checkbox-background-color)
);
border-color: var(--utrecht-custom-checkbox-checked-border-color, var(--utrecht-custom-checkbox-border-color));
border-width: var(--utrecht-custom-checkbox-checked-border-width, var(--utrecht-custom-checkbox-border-width));
color: var(--utrecht-custom-checkbox-checked-color, var(--utrecht-custom-checkbox-color));
}

.utrecht-custom-checkbox__box--disabled {
background-color: var(
--utrecht-custom-checkbox-disabled-background-color,
var(--utrecht-custom-checkbox-background-color)
);
border-color: var(--utrecht-custom-checkbox-disabled-border-color, var(--utrecht-custom-checkbox-border-color));
border-width: var(--utrecht-custom-checkbox-disabled-border-width, var(--utrecht-custom-checkbox-border-width));
color: var(--utrecht-custom-checkbox-disabled-color, var(--utrecht-custom-checkbox-color));
cursor: var(--utrecht-action-disabled-cursor);
}

.utrecht-custom-checkbox__box--indeterminate {
color: var(--utrecht-custom-checkbox-indeterminate-color, var(--utrecht-custom-checkbox-color));
}

.utrecht-custom-checkbox__box--invalid {
background-color: var(
--utrecht-custom-checkbox-invalid-background-color,
var(--utrecht-custom-checkbox-background-color)
);
border-color: var(--utrecht-custom-checkbox-invalid-border-color, var(--utrecht-custom-checkbox-border-color));
border-width: var(--utrecht-custom-checkbox-invalid-border-width, var(--utrecht-custom-checkbox-border-width));
color: var(--utrecht-custom-checkbox-invalid-color, var(--utrecht-custom-checkbox-color));
}

.utrecht-custom-checkbox__box--active {
background-color: var(
--utrecht-custom-checkbox-active-background-color,
var(--utrecht-custom-checkbox-background-color)
);
border-color: var(--utrecht-custom-checkbox-active-border-color, var(--utrecht-custom-checkbox-border-color));
border-width: var(--utrecht-custom-checkbox-active-border-width, var(--utrecht-custom-checkbox-border-width));
color: var(--utrecht-custom-checkbox-active-color, var(--utrecht-custom-checkbox-color));
}

.utrecht-custom-checkbox__box--focus {
background-color: var(
--utrecht-custom-checkbox-focus-background-color,
var(--utrecht-custom-checkbox-background-color)
);
border-color: var(--utrecht-custom-checkbox-focus-border-color, var(--utrecht-custom-checkbox-border-color));
border-width: var(--utrecht-custom-checkbox-focus-border-width, var(--utrecht-custom-checkbox-border-width));
color: var(--utrecht-custom-checkbox-focus-color, var(--utrecht-custom-checkbox-color));
}

.utrecht-custom-checkbox__box--focus-visible {
@include utrecht-focus();
}

.utrecht-custom-checkbox__input:focus ~ .utrecht-custom-checkbox__box {
@extend .utrecht-custom-checkbox__box--focus;
}

.utrecht-custom-checkbox__input:focus-visible ~ .utrecht-custom-checkbox__box {
@extend .utrecht-custom-checkbox__box--focus-visible;
}

.utrecht-custom-checkbox__input:indeterminate ~ .utrecht-custom-checkbox__box {
@extend .utrecht-custom-checkbox__box--indeterminate;
}

.utrecht-custom-checkbox__icon {
display: none;
}

.utrecht-custom-checkbox__icon--checked {
.utrecht-custom-checkbox__box--checked & {
display: block;
}
}

.utrecht-custom-checkbox__icon--indeterminate {
.utrecht-custom-checkbox__box--indeterminate & {
display: block;
}
}
161 changes: 161 additions & 0 deletions components/custom-checkbox/bem.stories.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
<!--
@license EUPL-1.2
Copyright (c) 2021 Robbert Broersma
-->

import { Meta, Story, Canvas, ArgsTable } from "@storybook/addon-docs";
import { defaultArgs, CustomCheckbox } from "./bem";
import "./bem.scss";

<Meta
title="CSS Component/Custom Checkbox"
argTypes={{
active: {
description: "Active",
control: "boolean",
},
checked: {
description: "Checked",
control: "boolean",
},
disabled: {
description: "Disabled",
control: "boolean",
},
focus: {
description: "Focus",
control: "boolean",
},
indeterminate: {
description: "Indeterminate",
control: "boolean",
},
invalid: {
description: "Invalid",
control: "boolean",
},
required: {
description: "Required",
control: "boolean",
},
value: {
description: "Set the value of the text box",
control: "text",
},
}}
decorators={[(story) => `<form method="POST" action="#">${story()}</form>`]}
parameters={{
docs: {
transformSource: (_src, { args }) => CustomCheckbox(args),
},
status: {
type: "WORK IN PROGRESS",
},
}}
/>

# Checkbox Component

<Canvas>
<Story
name="Custom Checkbox"
args={{
...defaultArgs,
}}
>
{CustomCheckbox.bind({})}
</Story>
</Canvas>

<ArgsTable story="Custom Checkbox" />

## States

### Disabled

<Canvas>
<Story
name="Disabled checkbox"
args={{
...defaultArgs,
disabled: true,
}}
>
{CustomCheckbox.bind({})}
</Story>
</Canvas>

### Checked

<Canvas>
<Story
name="Checked checkbox"
args={{
...defaultArgs,
checked: true,
}}
>
{CustomCheckbox.bind({})}
</Story>
</Canvas>

### Indeterminate

<Canvas>
<Story
name="Indeterminate checkbox"
args={{
...defaultArgs,
indeterminate: true,
}}
>
{CustomCheckbox.bind({})}
</Story>
</Canvas>

### Required and invalid

The native checkbox should have the same position and size as the custom checkbox, so the native error message tooltip aligns with the custom checkbox.

<Canvas>
<Story
name="Required checkbox"
args={{
...defaultArgs,
invalid: true,
required: true,
}}
>
{CustomCheckbox.bind({})}
</Story>
</Canvas>

## Interactive states

### Active

<Canvas>
<Story
name="Active checkbox"
args={{
...defaultArgs,
active: true,
}}
>
{CustomCheckbox.bind({})}
</Story>
</Canvas>

### Focus

<Canvas>
<Story
name="Focus checkbox"
args={{
...defaultArgs,
focus: true,
}}
>
{CustomCheckbox.bind({})}
</Story>
</Canvas>
Loading

0 comments on commit 9fe5980

Please sign in to comment.