ΠΠ»Ρ Π½Π°ΡΠ°Π»Π° ΡΠ°Π·Π²Π΅ΡΠ½ΡΠΌ ΠΏΡΠΎΠ΅ΠΊΡ ΡΠ΅Π°ΠΊΡΠ° ΠΈ Π΄ΠΎΠ±Π°Π²ΠΈΠΌ Π² Π½Π΅Π³ΠΎ ΠΊΠ½ΠΎΠΏΠΊΡ Ρ ΠΎΠΏΠΈΡΠ°Π½ΠΈΠ΅ΠΌ ΠΏΡΠΈΠ½ΠΈΠΌΠ°Π΅ΠΌΡΡ Π΅Ρ ΠΏΡΠΎΠΏΡΠΎΠ²
components > Button > Button.tsx
import React from 'react'
import { ButtonProps } from './Button.props'
import './Button.css';
import cn from 'classnames';
export const Button = ({ appearance = 'primary', children, className, ...props }: ButtonProps) => {
return (
<button
className={cn('button', className, {
['primary']: appearance == 'primary',
['ghost']: appearance == 'ghost',
})}
{...props}
>
{children}
</button>
);
}
components > Button > Button.props.ts
import { ButtonHTMLAttributes, DetailedHTMLProps, ReactNode } from 'react';
export interface ButtonProps
extends DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement> {
children: ReactNode;
appearance?: 'primary' | 'ghost';
}
ΠΠ»Ρ ΡΡΡΠ°Π½ΠΎΠ²ΠΊΠΈ ΡΡΠΎΡΠΈΠ±ΡΠΊΠ° ΠΏΠΎΠ½Π°Π΄ΠΎΠ±ΠΈΡΡΡ Π² ΠΏΡΠΎΠ΅ΠΊΡΠ΅ Π²Π²Π΅ΡΡΠΈ Π΄Π°Π½Π½ΡΡ ΠΊΠΎΠΌΠ°Π½Π΄Ρ:
npx sb init
ΠΠΎΡΠ»Π΅ ΡΡΡΠ°Π½ΠΎΠ²ΠΊΠΈ, Ρ Π½Π°Ρ ΠΏΠΎΡΠ²ΠΈΡΡΡ ΡΠΊΡΠΈΠΏΡ Π΄Π»Ρ Π·Π°ΠΏΡΡΠΊΠ° ΡΡΠΎΡΠΈΠ±ΡΠΊΠ°
package.json
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"sb": "storybook dev -p 6006",
"build-storybook": "storybook build"
},
Π’Π°ΠΊ ΠΆΠ΅ ΡΡΠΎΡΠΌΠΈΡΡΡΡΡΡ Π΄Π²Π° ΡΠ°ΠΉΠ»Π° ΠΊΠΎΠ½ΡΠΈΠ³ΡΡΠ°ΡΠΈΠΈ, ΠΊΠΎΡΠΎΡΡΠ΅ Π±ΡΠ΄ΡΡ ΠΎΡΠ²Π΅ΡΠ°ΡΡ Π·Π° ΡΠΎ, ΠΊΠ°ΠΊΠΈΠ΅ ΡΠ°ΠΉΠ»Ρ Π±ΡΠ΄ΡΡ ΠΈ ΠΏΠΎ ΠΊΠ°ΠΊΠΈΠΌ ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡΠ°ΠΌ ΠΎΠΏΡΠ΅Π΄Π΅Π»ΡΡΡΡΡ ΠΊΠ°ΠΊ ΠΈΡΡΠΎΡΠ½ΠΈΠΊΠΈ ΠΈΡΡΠΎΡΠΈΠΉ Π΄Π»Ρ ΡΡΠΎΡΠΈΠ±ΡΠΊΠ°
.storybook > main.ts
import type { StorybookConfig } from "@storybook/react-webpack5";
const config: StorybookConfig = {
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
addons: [
"@storybook/addon-links",
"@storybook/addon-essentials",
"@storybook/preset-create-react-app",
"@storybook/addon-interactions",
],
framework: {
name: "@storybook/react-webpack5",
options: {},
},
docs: {
autodocs: "tag",
},
staticDirs: ["..\\public"],
};
export default config;
Π‘ΠΎΡ ΡΠ°Π½ΡΠ΅Ρ Π½Π°ΡΡΡΠΎΠΉΠΊΠΈ Π»Π΅ΠΉΠ°ΡΡΠ°
.storybook > preview.ts
import type { Preview } from "@storybook/react";
const preview: Preview = {
parameters: {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
},
};
export default preview;
Π ΡΠΆΠ΅ ΡΠΎΠ»ΡΠΊΠΎ ΡΠ΅ΠΉΡΠ°Ρ Π² ΠΏΠ°ΠΏΠΊΠ΅, Π³Π΄Π΅ Ρ Π½Π°Ρ ΡΠ°ΡΠΏΠΎΠ»Π°Π³Π°Π΅ΡΡΡ ΡΠ»Π΅ΠΌΠ΅Π½Ρ, ΠΌΡ ΠΌΠΎΠΆΠ΅ΠΌ ΡΠ΅Π°Π»ΠΈΠ·ΠΎΠ²Π°ΡΡ ΡΡΡΠ°Π½ΠΈΡΡ ΡΡΠΎΡΠΈΠ±ΡΠΊΠ° Ρ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠΎΠΌ
Button.stories.js
import { Button } from "./Button";
export default {
// Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΎΠΊ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ° ΠΠ°ΠΏΠΊΠ° (Π½Π΅ΠΎΠ±ΡΠ·Π°ΡΠ΅Π»ΡΠ½ΠΎ) - ΠΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ
title: 'UI/Button',
// ΡΠ°ΠΌ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ
component: Button
}
// ΡΠ°ΠΌ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ, ΠΊΠΎΡΠΎΡΡΠΉ Π±ΡΠ΄Π΅Ρ Π²ΡΠ²ΠΎΠ΄ΠΈΡΡΡΡ Π½Π° ΡΠΊΡΠ°Π½
export const DefaultButton = () => <Button>ΠΠ½ΠΎΠΏΠΊΠ°</Button>
ΠΠΎ Π±ΠΎΠ»Π΅Π΅ ΠΏΡΠ°Π²ΠΈΠ»ΡΠ½ΡΠΌ Π΄ΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠ΅ΠΌ ΡΠ»Π΅ΠΌΠ΅Π½ΡΠ° Π² ΡΡΠΎΡΠΈΡΡ Π±ΡΠ΄Π΅Ρ ΡΡΠΈΡΠ°ΡΡΡΡ Π΄ΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠ΅ Π΅Π³ΠΎ ΡΠ΅ΡΠ΅Π· ΡΠ΅ΠΌΠΏΠ»Π΅ΠΉΡ, ΡΡΠΎ ΠΏΠΎΠ·Π²ΠΎΠ»ΠΈΡ Π½Π°ΠΌ Π΄ΠΈΠ½Π°ΠΌΠΈΡΠ΅ΡΠΊΠΈ ΠΌΠ΅Π½ΡΡΡ Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅ ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡΠΎΠ² ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠΎΠ² ΠΏΡΡΠΌΠΎ Π² ΡΡΠΎΡΠΈΠ±ΡΠΊΠ΅
Button.stories.js
import { Button } from './Button';
export default {
title: 'Button',
component: Button,
};
// ΡΠΎΠ·Π΄Π°ΡΠΌ ΡΠ°Π±Π»ΠΎΠ½ Π΄Π»Ρ Π·Π°Π½Π΅ΡΠ΅Π½ΠΈΡ Π² Π½Π΅Π³ΠΎ ΠΏΡΠΎΠΏΡΠΎΠ²
const Template = (arg) => <Button {...arg} />;
// Π΄Π°Π»ΡΡΠ΅ ΠΏΡΠΈΠ²ΡΠ·ΡΠ²Π°Π΅ΠΌ ΠΊΠΎΠ½ΡΠ΅ΠΊΡΡ ΡΠ°Π±Π»ΠΎΠ½Π°
export const Default = Template.bind({});
// Π΄Π°Π»ΡΡΠ΅ ΠΏΠ΅ΡΠ΅Π΄Π°ΡΠΌ Π΄Π΅ΡΠΎΠ»ΡΠ½ΡΠ΅ Π°ΡΠ³ΡΠΌΠ΅Π½ΡΡ Π² ΠΊΠ½ΠΎΠΏΠΊΡ
Default.args = {
children: 'ΠΠ½ΠΎΠΏΠΊΠ°',
appearance: 'primary',
};
Π ΡΡΡ ΠΌΡ Π²ΠΈΠ΄ΠΈΠΌ ΠΊΠΎΠ½ΠΊΡΠ΅ΡΠ½ΡΠ΅ ΡΠΈΠΏΡ ΠΏΡΠΎΠΏΡΠΎΠ², ΠΊΠΎΡΠΎΡΡΠ΅ ΠΌΡ ΠΌΠΎΠΆΠ΅ΠΌ Π²ΡΠ±ΡΠ°ΡΡ. ΠΡΠΎ ΡΠ°Π±ΠΎΡΠ°Π΅Ρ Π±Π»Π°Π³ΠΎΠ΄Π°ΡΡ ΡΠΎΠΌΡ, ΡΡΠΎ TS ΠΎΠΏΠΈΡΠ°Π» ΠΈΠ½ΡΠ΅ΡΡΠ΅ΠΉΡ ΠΏΡΠΈΠ½ΠΈΠΌΠ°Π΅ΠΌΡΡ ΠΏΡΠΎΠΏΡΠΎΠ² ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠΎΠΌ
Π’Π°ΠΊ ΠΆΠ΅ ΠΌΡ ΠΌΠΎΠΆΠ΅ΠΌ ΡΠΎΠ·Π΄Π°ΡΡ Π½Π΅ΡΠΊΠΎΠ»ΡΠΊΠΎ Π²Π°ΡΠΈΠ°Π½ΡΠΎΠ² Π½Π°ΡΠΈΡ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠΎΠ², ΡΡΠΎΠ±Ρ Π½Π΅ ΠΌΠ΅Π½ΡΡΡ ΠΏΡΠΎΠΏΡΡ Π²ΡΡΡΠ½ΡΡ
`Button.stories.js
import { Button } from './Button';
export default {
title: 'Button',
component: Button,
};
const Template = (arg) => <Button {...arg} />;
export const Primary = Template.bind({});
Primary.args = {
children: 'ΠΠ½ΠΎΠΏΠΊΠ°',
appearance: 'primary',
};
export const Ghost = Template.bind({});
Ghost.args = {
children: 'ΠΠ½ΠΎΠΏΠΊΠ°',
appearance: 'ghost',
};
ΠΠΎ! ΠΡΠ»ΠΈ ΠΌΡ ΠΏΠΈΡΠ΅ΠΌ ΠΏΡΠΎΠ΅ΠΊΡ Π±Π΅Π· TS, ΡΠΎ ΡΠΈΠΏΡ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΡΡ
Π°ΡΠ³ΡΠΌΠ΅Π½ΡΠΎΠ² ΠΌΡ ΠΌΠΎΠΆΠ΅ΠΌ ΠΏΡΠΎΠΏΠΈΡΠ°ΡΡ ΡΠ°ΠΌΠΎΡΡΠΎΡΡΠ΅Π»ΡΠ½ΠΎ ΡΠ΅ΡΠ΅Π· Π·Π°Π΄Π°Π½ΠΈΠ΅ Π΄Π»Ρ ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ ΠΏΠ°ΡΠ°ΠΌΠ΅ΡΡΠ° ΡΠ²ΠΎΠΈΡ
ΡΠ²ΠΎΠΉΡΡΠ² Π²Π½ΡΡΡΠΈ ΡΠ²ΠΎΠΉΡΡΠ²Π° argTypes
import { Button } from './Button';
export default {
title: 'Button',
component: Button,
// ΡΠΈΠΏΡ Π°ΡΠ³ΡΠΌΠ΅Π½ΡΠΎΠ² ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ°
argTypes: {
// Π°ΡΠ³ΡΠΌΠ΅Π½Ρ ΠΎΡΠΎΡΠΌΠ»Π΅Π½ΠΈΡ
appearance: {
type: 'string', // ΡΠΈΠΏ Π°ΡΠ³ΡΠΌΠ΅Π½ΡΠ°
description: 'ΠΠ°ΡΠΈΠ°Π½Ρ Π²Π½Π΅ΡΠ½Π΅Π³ΠΎ Π²ΠΈΠ΄Π° ΠΊΠ½ΠΎΠΏΠΊΠΈ', // ΠΎΠΏΠΈΡΠ°Π½ΠΈΠ΅
defaultValue: 'primary', // Π΄Π΅ΡΠΎΠ»ΡΠ½ΠΎΠ΅ Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅
options: ['primary', 'ghost'], // Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΡΠ΅ ΠΎΠΏΡΠΈΠΈ
control: { // ΡΠΏΠΎΡΠΎΠ± ΠΏΠ΅ΡΠ΅ΠΊΠ»ΡΡΠ΅Π½ΠΈΡ ΠΎΠΏΡΠΈΠΉ
type: 'radio',
},
},
children: {
type: 'string',
name: 'label', // ΠΈΠΌΡ Π² ΡΡΠΎΡΠΈΠ±ΡΠΊΠ΅
defaultValue: 'ΠΠ½ΠΎΠΏΠΊΠ°',
},
},
};
const Template = (arg) => <Button {...arg} />;
export const Primary = Template.bind({});
Primary.args = {
children: 'ΠΠ½ΠΎΠΏΠΊΠ°',
appearance: 'primary',
};
export const Ghost = Template.bind({});
Ghost.args = {
children: 'ΠΠ½ΠΎΠΏΠΊΠ°',
appearance: 'ghost',
};
ΠΠΎ ΡΠ°ΠΊ ΠΆΠ΅ ΠΌΡ ΠΌΠΎΠΆΠ΅ΠΌ ΡΠ΄Π΅Π»Π°ΡΡ ΠΏΡΠΎΠ²Π΅ΡΠΊΡ ΡΠΈΠΏΠΎΠ² Ρ ΠΏΠΎΠΌΠΎΡΡΡ PropTypes Π²Π½ΡΡΡΠΈ ΡΠ°ΠΌΠΎΠ³ΠΎ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠ°, ΡΡΠΎ ΡΠ°ΠΊ ΠΆΠ΅ ΠΏΠΎΠ·Π²ΠΎΠ»ΠΈΡ Π·Π°ΠΌΠ΅Π½ΠΈΡΡ ΡΡΠ½ΠΊΡΠΈΠΎΠ½Π°Π» ΠΈΠ½ΡΠ΅ΡΡΠ΅ΠΉΡΠ° Π΄Π»Ρ ΠΎΡΠΎΠ±ΡΠ°ΠΆΠ΅Π½ΠΈΡ ΠΏΡΠΎΠΏΡΠΎΠ² Π² ΡΡΠΎΡΠΈΠ±ΡΠΊΠ΅
![]()