Skip to content

Commit 209f856

Browse files
author
Alejandro Celaya
committed
Add RichCheckbox component
1 parent 613ef5c commit 209f856

3 files changed

Lines changed: 183 additions & 0 deletions

File tree

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import classnames from 'classnames';
2+
import type { ComponentChildren } from 'preact';
3+
import { useCallback } from 'preact/hooks';
4+
5+
import { CheckboxCheckedFilledIcon, CheckboxIcon } from '../icons';
6+
7+
export type RichCheckboxProps = {
8+
checked: boolean;
9+
onChange: (checked: boolean) => void;
10+
11+
/**
12+
* Content provided as children is vertically aligned with the checkbox icon
13+
*/
14+
children: ComponentChildren;
15+
16+
/**
17+
* Allows to provide extra content to be displayed under the children, in a
18+
* smaller and more subtle font color.
19+
*/
20+
subtitle?: ComponentChildren;
21+
};
22+
23+
/**
24+
* An opinionated `[role="checkbox"]` component which displays a checkbox icon
25+
* next to provided content.
26+
*
27+
* If a `subtitle` is provided, it will be shown in a lighter color right under
28+
* the main content, and aligned on the left with it.
29+
*/
30+
export default function RichCheckbox({
31+
checked,
32+
onChange,
33+
children,
34+
subtitle,
35+
}: RichCheckboxProps) {
36+
const toggle = useCallback(() => onChange(!checked), [checked, onChange]);
37+
38+
return (
39+
<div
40+
className={classnames(
41+
'group focus-visible-ring flex gap-x-1.5 items-start',
42+
'px-3 py-2 rounded-lg cursor-pointer',
43+
'hover:bg-grey-3/25 aria-checked:bg-grey-3/50',
44+
)}
45+
role="checkbox"
46+
aria-checked={checked}
47+
onClick={toggle}
48+
onKeyDown={e => ['Enter', ' '].includes(e.key) && toggle()}
49+
tabIndex={0}
50+
data-testid="pre-moderation"
51+
>
52+
{!checked && <CheckboxIcon className="mt-1" />}
53+
{checked && <CheckboxCheckedFilledIcon className="mt-1" />}
54+
<div>
55+
<p className="text-grey-7 group-hover:text-grey-8 group-aria-checked:text-grey-8">
56+
{children}
57+
</p>
58+
{subtitle && (
59+
<p className="text-grey-6 group-hover:text-grey-7 group-aria-checked:text-grey-7">
60+
{subtitle}
61+
</p>
62+
)}
63+
</div>
64+
</div>
65+
);
66+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { useState } from 'preact/hooks';
2+
3+
import type { RichCheckboxProps } from '../../../../components/input/RichCheckbox';
4+
import RichCheckbox from '../../../../components/input/RichCheckbox';
5+
import Library from '../../Library';
6+
7+
function RichCheckbox_({
8+
children,
9+
initialChecked = false,
10+
subtitle,
11+
}: Omit<RichCheckboxProps, 'checked' | 'onChange'> & {
12+
initialChecked?: boolean;
13+
}) {
14+
const [checked, setChecked] = useState(initialChecked);
15+
16+
return (
17+
<RichCheckbox checked={checked} onChange={setChecked} subtitle={subtitle}>
18+
{children}
19+
</RichCheckbox>
20+
);
21+
}
22+
23+
export default function CheckboxPage() {
24+
return (
25+
<Library.Page
26+
title="RichCheckbox"
27+
intro={
28+
<>
29+
<p>
30+
<code>RichCheckbox</code> is an opinionated controlled{' '}
31+
<code>
32+
[role={'"'}checkbox{'"'}]
33+
</code>{' '}
34+
component that includes a checkbox icon next to some content.
35+
</p>
36+
<p>
37+
It has predefined hover and checked styles, like the individual{' '}
38+
<code>Radio</code>s inside{' '}
39+
<Library.Link href="/input-radio-group">
40+
<code>RadioGroup</code>
41+
</Library.Link>
42+
.
43+
</p>
44+
</>
45+
}
46+
>
47+
<Library.SectionL2>
48+
<Library.Usage symbolName="RichCheckbox" />
49+
<Library.SectionL3>
50+
<Library.Demo title="Basic RichCheckbox" withSource>
51+
<div className="flex flex-col gap-y-2">
52+
<RichCheckbox_>Click me</RichCheckbox_>
53+
<RichCheckbox_ subtitle="This one includes a subtitle">
54+
Click me
55+
</RichCheckbox_>
56+
</div>
57+
</Library.Demo>
58+
</Library.SectionL3>
59+
</Library.SectionL2>
60+
61+
<Library.SectionL2 title="Component API">
62+
<Library.SectionL3 title="checked">
63+
<Library.Info>
64+
<Library.InfoItem label="description">
65+
Set whether the <code>RichCheckbox</code> is checked.
66+
</Library.InfoItem>
67+
<Library.InfoItem label="type">
68+
<code>{`boolean`}</code>
69+
</Library.InfoItem>
70+
</Library.Info>
71+
</Library.SectionL3>
72+
<Library.SectionL3 title="children">
73+
<Library.Info>
74+
<Library.InfoItem label="description">
75+
Main content of the checkbox. Will be displayed next to check
76+
checkbox icon, and vertically aligned with it.
77+
</Library.InfoItem>
78+
<Library.InfoItem label="type">
79+
<code>{`ComponentChildren`}</code>
80+
</Library.InfoItem>
81+
</Library.Info>
82+
</Library.SectionL3>
83+
<Library.SectionL3 title="onChange">
84+
<Library.Info>
85+
<Library.InfoItem label="description">
86+
Callback invoked check the <code>checked</code> value changes.
87+
</Library.InfoItem>
88+
<Library.InfoItem label="type">
89+
<code>{`(checked: boolean) => void`}</code>
90+
</Library.InfoItem>
91+
</Library.Info>
92+
</Library.SectionL3>
93+
<Library.SectionL3 title="subtitle">
94+
<Library.Info>
95+
<Library.InfoItem label="description">
96+
If provided, it will show extra content in a lighter font color,
97+
right below the main content, and aligned to the left with it.
98+
</Library.InfoItem>
99+
<Library.InfoItem label="type">
100+
<code>{`ComponentChildren`}</code>
101+
</Library.InfoItem>
102+
<Library.InfoItem label="default">
103+
<code>{`undefined`}</code>
104+
</Library.InfoItem>
105+
</Library.Info>
106+
</Library.SectionL3>
107+
</Library.SectionL2>
108+
</Library.Page>
109+
);
110+
}

src/pattern-library/routes.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import InputGroupPage from './components/patterns/input/InputGroupPage';
2424
import InputPage from './components/patterns/input/InputPage';
2525
import OptionButtonPage from './components/patterns/input/OptionButtonPage';
2626
import RadioGroupPage from './components/patterns/input/RadioGroupPage';
27+
import RichCheckboxPage from './components/patterns/input/RichCheckboxPage';
2728
import SelectPage from './components/patterns/input/SelectPage';
2829
import TextareaPage from './components/patterns/input/TextareaPage';
2930
import CardPage from './components/patterns/layout/CardPage';
@@ -208,6 +209,12 @@ const routes: PlaygroundRoute[] = [
208209
component: RadioGroupPage,
209210
route: '/input-radio-group',
210211
},
212+
{
213+
title: 'RichCheckbox',
214+
group: 'input',
215+
component: RichCheckboxPage,
216+
route: '/input-rich-checkbox',
217+
},
211218
{
212219
title: 'Selects',
213220
group: 'input',

0 commit comments

Comments
 (0)