From 0da41c265d2c0883f496de4c074d2e3d2c03d2b5 Mon Sep 17 00:00:00 2001 From: Yangshun Tay Date: Sun, 9 Oct 2022 09:18:30 +0800 Subject: [PATCH] [ui][checkbox list] implementation --- .../stories/checkbox-input.stories.tsx | 99 +++++++ .../stories/checkbox-list.stories.tsx | 259 ++++++++++++++++++ .../ui/src/CheckboxInput/CheckboxInput.tsx | 83 ++++++ packages/ui/src/CheckboxList/CheckboxList.tsx | 46 ++++ packages/ui/src/index.tsx | 6 + 5 files changed, 493 insertions(+) create mode 100644 apps/storybook/stories/checkbox-input.stories.tsx create mode 100644 apps/storybook/stories/checkbox-list.stories.tsx create mode 100644 packages/ui/src/CheckboxInput/CheckboxInput.tsx create mode 100644 packages/ui/src/CheckboxList/CheckboxList.tsx diff --git a/apps/storybook/stories/checkbox-input.stories.tsx b/apps/storybook/stories/checkbox-input.stories.tsx new file mode 100644 index 00000000..637925f5 --- /dev/null +++ b/apps/storybook/stories/checkbox-input.stories.tsx @@ -0,0 +1,99 @@ +import React, { useState } from 'react'; +import type { ComponentMeta } from '@storybook/react'; +import { CheckboxInput } from '@tih/ui'; + +export default { + argTypes: { + defaultValue: { + control: 'boolean', + }, + description: { + control: 'text', + }, + disabled: { + control: 'boolean', + }, + label: { + control: 'text', + }, + value: { + control: 'boolean', + }, + }, + component: CheckboxInput, + title: 'CheckboxInput', +} as ComponentMeta; + +export function Basic({ + defaultValue, + description, + disabled, + label, +}: Pick< + React.ComponentProps, + 'defaultValue' | 'description' | 'disabled' | 'label' +>) { + return ( + + ); +} + +Basic.args = { + description: 'I will be responsible for any mistakes', + disabled: false, + label: 'I have read the terms and conditions', +}; + +export function Controlled() { + const [value, setValue] = useState(true); + + return ( + { + setValue(newValue); + }} + /> + ); +} + +export function Disabled() { + return ( +
+ + + + +
+ ); +} + +export function ItemDescriptions() { + return ( + + ); +} diff --git a/apps/storybook/stories/checkbox-list.stories.tsx b/apps/storybook/stories/checkbox-list.stories.tsx new file mode 100644 index 00000000..1bfd9c00 --- /dev/null +++ b/apps/storybook/stories/checkbox-list.stories.tsx @@ -0,0 +1,259 @@ +import React, { useState } from 'react'; +import type { ComponentMeta } from '@storybook/react'; +import type { CheckboxListOrientation } from '@tih/ui'; +import { HorizontalDivider } from '@tih/ui'; +import { CheckboxInput, CheckboxList } from '@tih/ui'; + +const CheckboxListOrientations: ReadonlyArray = [ + 'horizontal', + 'vertical', +]; + +export default { + argTypes: { + description: { + control: 'text', + }, + label: { + control: 'text', + }, + orientation: { + control: { type: 'select' }, + options: CheckboxListOrientations, + }, + }, + component: CheckboxList, + title: 'CheckboxList', +} as ComponentMeta; + +export function Basic({ + description, + label, + orientation, +}: Pick< + React.ComponentProps, + 'description' | 'label' | 'orientation' +>) { + const items = [ + { + label: 'Apple', + name: 'apple', + value: true, + }, + { + label: 'Banana', + name: 'banana', + value: true, + }, + { + label: 'Orange', + name: 'orange', + value: false, + }, + ]; + + return ( + + {items.map(({ label: itemLabel, name, value: itemValue }) => ( + + ))} + + ); +} + +Basic.args = { + description: 'Selected fruits will be served after dinner', + label: 'Select your favorite fruits', + orientation: 'vertical', +}; + +export function Controlled() { + const items = [ + { + label: 'Apple', + value: 'apple', + }, + { + label: 'Banana', + value: 'banana', + }, + { + label: 'Orange', + value: 'orange', + }, + ]; + + const [values, setValues] = useState(new Set(['apple'])); + + return ( + + {items.map(({ label: itemLabel, value: itemValue }) => ( + { + if (newValue) { + setValues(new Set([...Array.from(values), itemValue])); + } else { + setValues( + new Set(Array.from(values).filter((v) => v !== itemValue)), + ); + } + }} + /> + ))} + + ); +} + +export function Disabled() { + const items = [ + { + description: 'A red fruit', + disabled: false, + label: 'Apple', + value: 'apple', + }, + { + description: 'A yellow fruit', + disabled: true, + label: 'Banana', + value: 'banana', + }, + { + description: 'An orange fruit', + disabled: false, + label: 'Orange', + value: 'orange', + }, + ]; + + const [values, setValues] = useState(new Set(['apple', 'banana'])); + + return ( +
+ + {items.map(({ disabled, label: itemLabel, value: itemValue }) => ( + { + if (newValue) { + setValues(new Set([...Array.from(values), itemValue])); + } else { + setValues( + new Set(Array.from(values).filter((v) => v !== itemValue)), + ); + } + }} + /> + ))} + +
+ ); +} + +export function ItemDescriptions() { + const items = [ + { + description: 'A red fruit', + label: 'Apple', + value: 'apple', + }, + { + description: 'A yellow fruit', + label: 'Banana', + value: 'banana', + }, + { + description: 'An orange fruit', + label: 'Orange', + value: 'orange', + }, + ]; + + const [values, setValues] = useState(new Set(['apple', 'banana'])); + + return ( +
+ + {items.map(({ description, label: itemLabel, value: itemValue }) => ( + { + if (newValue) { + setValues(new Set([...Array.from(values), itemValue])); + } else { + setValues( + new Set(Array.from(values).filter((v) => v !== itemValue)), + ); + } + }} + /> + ))} + +
+ ); +} + +export function Orientation() { + const items = [ + { + label: 'Apple', + name: 'apple', + value: true, + }, + { + label: 'Banana', + name: 'banana', + value: false, + }, + { + label: 'Orange', + name: 'orange', + value: true, + }, + ]; + + return ( +
+ + {items.map(({ label: itemLabel, name, value: itemValue }) => ( + + ))} + + + + {items.map(({ label: itemLabel, name, value: itemValue }) => ( + + ))} + +
+ ); +} diff --git a/packages/ui/src/CheckboxInput/CheckboxInput.tsx b/packages/ui/src/CheckboxInput/CheckboxInput.tsx new file mode 100644 index 00000000..cf252512 --- /dev/null +++ b/packages/ui/src/CheckboxInput/CheckboxInput.tsx @@ -0,0 +1,83 @@ +import clsx from 'clsx'; +import type { ChangeEvent } from 'react'; +import { useId } from 'react'; + +type Props = Readonly<{ + defaultValue?: boolean; + description?: string; + disabled?: boolean; + label: string; + name?: string; + onChange?: ( + value: boolean, + event: ChangeEvent, + ) => undefined | void; + value?: boolean; +}>; + +export default function CheckboxInput({ + defaultValue, + description, + disabled = false, + label, + name, + value, + onChange, +}: Props) { + const id = useId(); + const descriptionId = useId(); + + return ( +
+
+ { + onChange?.(event.target.checked, event); + } + : undefined + } + /> +
+
+ + {description && ( +

+ {description} +

+ )} +
+
+ ); +} diff --git a/packages/ui/src/CheckboxList/CheckboxList.tsx b/packages/ui/src/CheckboxList/CheckboxList.tsx new file mode 100644 index 00000000..cf462a22 --- /dev/null +++ b/packages/ui/src/CheckboxList/CheckboxList.tsx @@ -0,0 +1,46 @@ +import clsx from 'clsx'; +import { useId } from 'react'; + +import type CheckboxInput from '../CheckboxInput/CheckboxInput'; + +export type CheckboxListOrientation = 'horizontal' | 'vertical'; + +type Props = Readonly<{ + children: ReadonlyArray>; + description?: string; + isLabelHidden?: boolean; + label: string; + orientation?: CheckboxListOrientation; +}>; + +export default function CheckboxList({ + children, + description, + isLabelHidden, + label, + orientation = 'vertical', +}: Props) { + const labelId = useId(); + return ( +
+
+ + {description && ( +

{description}

+ )} +
+
+ {children} +
+
+ ); +} diff --git a/packages/ui/src/index.tsx b/packages/ui/src/index.tsx index 545db2db..1e4efb66 100644 --- a/packages/ui/src/index.tsx +++ b/packages/ui/src/index.tsx @@ -7,6 +7,12 @@ export { default as Badge } from './Badge/Badge'; // Button export * from './Button/Button'; export { default as Button } from './Button/Button'; +// CheckboxInput +export * from './CheckboxInput/CheckboxInput'; +export { default as CheckboxInput } from './CheckboxInput/CheckboxInput'; +// CheckboxList +export * from './CheckboxList/CheckboxList'; +export { default as CheckboxList } from './CheckboxList/CheckboxList'; // Collapsible export * from './Collapsible/Collapsible'; export { default as Collapsible } from './Collapsible/Collapsible';