mirror of
https://github.com/yangshun/tech-interview-handbook.git
synced 2025-07-28 04:33:42 +08:00
ui: add components
This commit is contained in:
41
apps/storybook/stories/badge.stories.tsx
Normal file
41
apps/storybook/stories/badge.stories.tsx
Normal file
@ -0,0 +1,41 @@
|
||||
import { ComponentMeta } from '@storybook/react';
|
||||
import { Badge, BadgeVariant } from '@tih/ui';
|
||||
import React from 'react';
|
||||
|
||||
const badgeVariants: ReadonlyArray<BadgeVariant> = [
|
||||
'primary',
|
||||
'info',
|
||||
'danger',
|
||||
'success',
|
||||
'warning',
|
||||
];
|
||||
|
||||
export default {
|
||||
title: 'Badge',
|
||||
component: Badge,
|
||||
argTypes: {
|
||||
variant: {
|
||||
options: badgeVariants,
|
||||
control: { type: 'select' },
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof Badge>;
|
||||
|
||||
export const Basic = {
|
||||
args: {
|
||||
label: 'Hello',
|
||||
variant: 'primary',
|
||||
},
|
||||
};
|
||||
|
||||
export function Variants() {
|
||||
return (
|
||||
<div className="space-x-4">
|
||||
<Badge label="Primary" variant="primary" />
|
||||
<Badge label="Success" variant="success" />
|
||||
<Badge label="Information" variant="info" />
|
||||
<Badge label="Warning" variant="warning" />
|
||||
<Badge label="Danger" variant="danger" />
|
||||
</div>
|
||||
);
|
||||
}
|
@ -45,7 +45,7 @@ export default {
|
||||
control: 'boolean',
|
||||
},
|
||||
label: {
|
||||
control: 'string',
|
||||
control: 'text',
|
||||
},
|
||||
size: {
|
||||
options: buttonSizes,
|
||||
|
56
apps/storybook/stories/dialog.stories.tsx
Normal file
56
apps/storybook/stories/dialog.stories.tsx
Normal file
@ -0,0 +1,56 @@
|
||||
import { ComponentMeta } from '@storybook/react';
|
||||
import { Button, Dialog } from '@tih/ui';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
export default {
|
||||
title: 'Dialog',
|
||||
component: Dialog,
|
||||
argTypes: {
|
||||
title: {
|
||||
control: 'text',
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof Dialog>;
|
||||
|
||||
export function Basic({ children, title }) {
|
||||
const [isShown, setIsShown] = useState(false);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button label="Open" variant="primary" onClick={() => setIsShown(true)} />
|
||||
<Dialog
|
||||
isShown={isShown}
|
||||
primaryButton={
|
||||
<Button
|
||||
display="block"
|
||||
label="OK"
|
||||
variant="primary"
|
||||
onClick={() => setIsShown(false)}
|
||||
/>
|
||||
}
|
||||
secondaryButton={
|
||||
<Button
|
||||
display="block"
|
||||
label="Cancel"
|
||||
variant="tertiary"
|
||||
onClick={() => setIsShown(false)}
|
||||
/>
|
||||
}
|
||||
title={title}
|
||||
onClose={() => setIsShown(false)}>
|
||||
{children}
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Basic.args = {
|
||||
title: 'Lorem ipsum, dolor sit amet',
|
||||
children: (
|
||||
<div>
|
||||
Lorem ipsum, dolor sit amet consectetur adipisicing elit. Eius aliquam
|
||||
laudantium explicabo pariatur iste dolorem animi vitae error totam. At
|
||||
sapiente aliquam accusamus facere veritatis.
|
||||
</div>
|
||||
),
|
||||
};
|
124
apps/storybook/stories/dropdown-menu.stories.tsx
Normal file
124
apps/storybook/stories/dropdown-menu.stories.tsx
Normal file
@ -0,0 +1,124 @@
|
||||
import { ComponentMeta } from '@storybook/react';
|
||||
import { DropdownMenu, DropdownMenuAlignment, DropdownMenuSize } from '@tih/ui';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
const DropdownMenuAlignments: ReadonlyArray<DropdownMenuAlignment> = [
|
||||
'start',
|
||||
'end',
|
||||
];
|
||||
const DropdownMenuSizes: ReadonlyArray<DropdownMenuSize> = [
|
||||
'inherit',
|
||||
'regular',
|
||||
];
|
||||
|
||||
export default {
|
||||
title: 'DropdownMenu',
|
||||
component: DropdownMenu,
|
||||
parameters: {
|
||||
docs: {
|
||||
inlineStories: false,
|
||||
iframeHeight: 300,
|
||||
},
|
||||
},
|
||||
argTypes: {
|
||||
align: {
|
||||
options: DropdownMenuAlignments,
|
||||
control: { type: 'select' },
|
||||
},
|
||||
label: {
|
||||
control: 'text',
|
||||
},
|
||||
size: {
|
||||
options: DropdownMenuSizes,
|
||||
control: { type: 'select' },
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof DropdownMenu>;
|
||||
|
||||
export function Basic({ align, label, size }) {
|
||||
const menuItems = [
|
||||
{
|
||||
label: 'Apple',
|
||||
value: 'apple',
|
||||
},
|
||||
{
|
||||
label: 'Banana',
|
||||
value: 'banana',
|
||||
},
|
||||
{
|
||||
label: 'Orange',
|
||||
value: 'orange',
|
||||
},
|
||||
];
|
||||
|
||||
const [selectedValue, setSelectedValue] = useState('apple');
|
||||
|
||||
return (
|
||||
<DropdownMenu align={align} label={label} size={size}>
|
||||
{menuItems.map(({ label, value }) => (
|
||||
<DropdownMenu.Item
|
||||
key={value}
|
||||
isSelected={value === selectedValue}
|
||||
label={label}
|
||||
onClick={() => {
|
||||
setSelectedValue(value);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
|
||||
Basic.args = {
|
||||
align: 'start',
|
||||
label: 'Select fruitzz',
|
||||
size: 'regular',
|
||||
};
|
||||
|
||||
export function Align() {
|
||||
const menuItems = [
|
||||
{
|
||||
label: 'Apple',
|
||||
value: 'apple',
|
||||
},
|
||||
{
|
||||
label: 'Banana',
|
||||
value: 'banana',
|
||||
},
|
||||
{
|
||||
label: 'Orange',
|
||||
value: 'orange',
|
||||
},
|
||||
];
|
||||
|
||||
const [selectedValue, setSelectedValue] = useState('apple');
|
||||
|
||||
return (
|
||||
<div className="flex justify-between">
|
||||
<DropdownMenu align="start" label="Select fruit" size="regular">
|
||||
{menuItems.map(({ label, value }) => (
|
||||
<DropdownMenu.Item
|
||||
key={value}
|
||||
isSelected={value === selectedValue}
|
||||
label={label}
|
||||
onClick={() => {
|
||||
setSelectedValue(value);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenu>
|
||||
<DropdownMenu align="end" label="Select fruit" size="regular">
|
||||
{menuItems.map(({ label, value }) => (
|
||||
<DropdownMenu.Item
|
||||
key={value}
|
||||
isSelected={value === selectedValue}
|
||||
label={label}
|
||||
onClick={() => {
|
||||
setSelectedValue(value);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
);
|
||||
}
|
88
apps/storybook/stories/select.stories.tsx
Normal file
88
apps/storybook/stories/select.stories.tsx
Normal file
@ -0,0 +1,88 @@
|
||||
import { ComponentMeta } from '@storybook/react';
|
||||
import { Select, SelectDisplay } from '@tih/ui';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
const SelectDisplays: ReadonlyArray<SelectDisplay> = ['inline', 'block'];
|
||||
|
||||
export default {
|
||||
title: 'Select',
|
||||
component: Select,
|
||||
argTypes: {
|
||||
display: {
|
||||
options: SelectDisplays,
|
||||
control: { type: 'select' },
|
||||
},
|
||||
isLabelHidden: {
|
||||
control: 'boolean',
|
||||
},
|
||||
label: {
|
||||
control: 'text',
|
||||
},
|
||||
name: {
|
||||
control: 'text',
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof Select>;
|
||||
|
||||
export function Basic({ display, isLabelHidden, label, name }) {
|
||||
const [value, setValue] = useState('apple');
|
||||
|
||||
return (
|
||||
<Select
|
||||
display={display}
|
||||
isLabelHidden={isLabelHidden}
|
||||
label={label}
|
||||
onChange={setValue}
|
||||
name={name}
|
||||
options={[
|
||||
{
|
||||
label: 'Apple',
|
||||
value: 'apple',
|
||||
},
|
||||
{
|
||||
label: 'Banana',
|
||||
value: 'banana',
|
||||
},
|
||||
{
|
||||
label: 'Orange',
|
||||
value: 'orange',
|
||||
},
|
||||
]}
|
||||
value={value}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Basic.args = {
|
||||
label: 'Select fruit',
|
||||
display: 'inline',
|
||||
isLabelHidden: false,
|
||||
};
|
||||
|
||||
export function Display() {
|
||||
const [value, setValue] = useState('apple');
|
||||
|
||||
return (
|
||||
<div className="space-x-4">
|
||||
<Select
|
||||
label="Select a fruit"
|
||||
onChange={setValue}
|
||||
options={[
|
||||
{
|
||||
label: 'Apple',
|
||||
value: 'apple',
|
||||
},
|
||||
{
|
||||
label: 'Banana',
|
||||
value: 'banana',
|
||||
},
|
||||
{
|
||||
label: 'Orange',
|
||||
value: 'orange',
|
||||
},
|
||||
]}
|
||||
value={value}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
49
apps/storybook/stories/slide-out.stories.tsx
Normal file
49
apps/storybook/stories/slide-out.stories.tsx
Normal file
@ -0,0 +1,49 @@
|
||||
import { ComponentMeta } from '@storybook/react';
|
||||
import { Button, SlideOut, SlideOutEnterFrom, SlideOutSize } from '@tih/ui';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
const slideOutEnterFrom: ReadonlyArray<SlideOutEnterFrom> = ['start', 'end'];
|
||||
const slideOutSize: ReadonlyArray<SlideOutSize> = ['sm', 'md', 'lg', 'xl'];
|
||||
|
||||
export default {
|
||||
title: 'SlideOut',
|
||||
component: SlideOut,
|
||||
argTypes: {
|
||||
title: {
|
||||
control: 'text',
|
||||
},
|
||||
enterFrom: {
|
||||
options: slideOutEnterFrom,
|
||||
control: { type: 'select' },
|
||||
},
|
||||
size: {
|
||||
options: slideOutSize,
|
||||
control: { type: 'select' },
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof SlideOut>;
|
||||
|
||||
export function Basic({ children, enterFrom, size, title }) {
|
||||
const [isShown, setIsShown] = useState(false);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button label="Open" variant="primary" onClick={() => setIsShown(true)} />
|
||||
<SlideOut
|
||||
enterFrom={enterFrom}
|
||||
isShown={isShown}
|
||||
size={size}
|
||||
title={title}
|
||||
onClose={() => setIsShown(false)}>
|
||||
{children}
|
||||
</SlideOut>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Basic.args = {
|
||||
title: 'Navigation',
|
||||
children: <div className="p-4">Hello World</div>,
|
||||
enterFrom: 'end',
|
||||
size: 'md',
|
||||
};
|
@ -20,7 +20,7 @@ export default {
|
||||
control: { type: 'select' },
|
||||
},
|
||||
label: {
|
||||
control: 'string',
|
||||
control: 'text',
|
||||
},
|
||||
size: {
|
||||
options: spinnerSizes,
|
||||
|
43
apps/storybook/stories/tabs.stories.tsx
Normal file
43
apps/storybook/stories/tabs.stories.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
import { ComponentMeta } from '@storybook/react';
|
||||
import { Tabs } from '@tih/ui';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
export default {
|
||||
title: 'Tabs',
|
||||
component: Tabs,
|
||||
argTypes: {
|
||||
label: {
|
||||
control: 'text',
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof Tabs>;
|
||||
|
||||
export function Basic({ label }) {
|
||||
const [value, setValue] = useState('apple');
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
label={label}
|
||||
onChange={setValue}
|
||||
tabs={[
|
||||
{
|
||||
label: 'Apple',
|
||||
value: 'apple',
|
||||
},
|
||||
{
|
||||
label: 'Banana',
|
||||
value: 'banana',
|
||||
},
|
||||
{
|
||||
label: 'Orange',
|
||||
value: 'orange',
|
||||
},
|
||||
]}
|
||||
value={value}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Basic.args = {
|
||||
label: 'Fruits Navigation',
|
||||
};
|
129
apps/storybook/stories/text-input.stories.tsx
Normal file
129
apps/storybook/stories/text-input.stories.tsx
Normal file
@ -0,0 +1,129 @@
|
||||
import {
|
||||
EnvelopeIcon,
|
||||
KeyIcon,
|
||||
QuestionMarkCircleIcon,
|
||||
} from '@heroicons/react/24/solid';
|
||||
import { ComponentMeta } from '@storybook/react';
|
||||
import { TextInput } from '@tih/ui';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
export default {
|
||||
title: 'TextInput',
|
||||
component: TextInput,
|
||||
argTypes: {
|
||||
autoComplete: {
|
||||
control: 'text',
|
||||
},
|
||||
errorMessage: {
|
||||
control: 'text',
|
||||
},
|
||||
isDisabled: {
|
||||
control: 'boolean',
|
||||
},
|
||||
isLabelHidden: {
|
||||
control: 'boolean',
|
||||
},
|
||||
label: {
|
||||
control: 'text',
|
||||
},
|
||||
name: {
|
||||
control: 'text',
|
||||
},
|
||||
placeholder: {
|
||||
control: 'text',
|
||||
},
|
||||
type: {
|
||||
control: 'text',
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof TextInput>;
|
||||
|
||||
export const Basic = {
|
||||
args: {
|
||||
label: 'Name',
|
||||
placeholder: 'John Doe',
|
||||
},
|
||||
};
|
||||
|
||||
export function HiddenLabel() {
|
||||
const [value, setValue] = useState('');
|
||||
|
||||
return (
|
||||
<TextInput
|
||||
isLabelHidden={true}
|
||||
label="Name"
|
||||
placeholder="John Doe"
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function Email() {
|
||||
const [value, setValue] = useState('');
|
||||
|
||||
return (
|
||||
<TextInput
|
||||
label="Email"
|
||||
placeholder="john.doe@email.com"
|
||||
startIcon={EnvelopeIcon}
|
||||
type="email"
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function Icon() {
|
||||
const [value, setValue] = useState('');
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<TextInput
|
||||
endIcon={QuestionMarkCircleIcon}
|
||||
label="Account number"
|
||||
placeholder="000-00-0000"
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
/>
|
||||
<TextInput
|
||||
startIcon={QuestionMarkCircleIcon}
|
||||
label="Account number"
|
||||
placeholder="000-00-0000"
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function Disabled() {
|
||||
return (
|
||||
<TextInput
|
||||
isDisabled={true}
|
||||
label="Disabled input"
|
||||
placeholder="John Doe"
|
||||
type="text"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function Error() {
|
||||
const [value, setValue] = useState('1234');
|
||||
|
||||
return (
|
||||
<TextInput
|
||||
label="Email"
|
||||
errorMessage={
|
||||
value.length < 6 ? 'Password must be at least 6 characters' : undefined
|
||||
}
|
||||
startIcon={KeyIcon}
|
||||
type="password"
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
/>
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user