diff --git a/apps/portal/src/server/router/questions-question-router.ts b/apps/portal/src/server/router/questions-question-router.ts deleted file mode 100644 index e69de29b..00000000 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/apps/storybook/stories/radio-list.stories.tsx b/apps/storybook/stories/radio-list.stories.tsx index 4217bd13..91ca7b72 100644 --- a/apps/storybook/stories/radio-list.stories.tsx +++ b/apps/storybook/stories/radio-list.stories.tsx @@ -29,7 +29,11 @@ export default { export function Basic({ description, label, -}: Pick, 'description' | 'label'>) { + orientation, +}: Pick< + React.ComponentProps, + 'description' | 'label' | 'orientation' +>) { const items = [ { label: 'Apple', @@ -50,7 +54,8 @@ export function Basic({ defaultValue="apple" description={description} label={label} - name="fruit"> + name="fruit" + orientation={orientation}> {items.map(({ label: itemLabel, value }) => ( ))} @@ -61,6 +66,7 @@ export function Basic({ Basic.args = { description: 'Your favorite fruit', label: 'Choose a fruit', + orientation: 'vertical', }; export function Controlled() { @@ -148,22 +154,10 @@ export function Disabled() { }, ]; - const [value, setValue] = useState('apple'); - return (
setValue(newValue)}> - {items.map(({ label: itemLabel, value: itemValue }) => ( - - ))} - - - {items.map( 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/RadioList/RadioList.tsx b/packages/ui/src/RadioList/RadioList.tsx index 974491f1..6919c819 100644 --- a/packages/ui/src/RadioList/RadioList.tsx +++ b/packages/ui/src/RadioList/RadioList.tsx @@ -11,7 +11,6 @@ type Props = Readonly<{ children: ReadonlyArray>; defaultValue?: T; description?: string; - disabled?: boolean; isLabelHidden?: boolean; label: string; name?: string; @@ -27,10 +26,9 @@ export default function RadioList({ children, defaultValue, description, - disabled, - orientation = 'vertical', isLabelHidden, name, + orientation = 'vertical', label, required, value, @@ -41,7 +39,7 @@ export default function RadioList({ + value={{ defaultValue, name, onChange, value }}>