update lesson-1 chapter 3 for new design

This commit is contained in:
2024-02-10 15:56:56 +01:00
parent 74d72e0d57
commit 2b1dc314fc
4 changed files with 33 additions and 109 deletions

View File

@@ -5,7 +5,8 @@
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
"build": "next build", "build": "next build",
"start": "next start" "start": "next start",
"postbuild": "rm -rf .next/cache"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View File

@@ -1,4 +1,5 @@
import { Callout } from 'nextra/components' import { Callout } from 'nextra/components'
import { FileTree } from 'nextra-theme-docs'
# Компоненты, полиморфные компоненты # Компоненты, полиморфные компоненты
@@ -10,17 +11,23 @@ import { Callout } from 'nextra/components'
- Создайте папку _button_ в _src/components/ui_ со следующей структурой: - Создайте папку _button_ в _src/components/ui_ со следующей структурой:
{/* prettier-ignore */} <FileTree>
```markdown <FileTree.Folder name={'src'} defaultOpen>
src <FileTree.Folder name={'components'} defaultOpen>
└── components <FileTree.Folder name={'ui'} defaultOpen>
└── ui <FileTree.Folder name={'button'} defaultOpen>
└── button <FileTree.File name={'button.tsx'} />
└── button.tsx <FileTree.File name={'button.module.scss'} />
└── button.module.scss <FileTree.File name={'button.stories.ts'} />
└── button.stories.ts <FileTree.File name={'index.ts'} />
└── index.ts </FileTree.Folder>
``` <FileTree.File name={'index.ts'} />
</FileTree.Folder>
<FileTree.File name={'index.ts'} />
</FileTree.Folder>
</FileTree.Folder>
</FileTree>
### Дизайн и варианты ### Дизайн и варианты
@@ -29,11 +36,10 @@ src
![Button](./images/Button.png) ![Button](./images/Button.png)
- Стандартная, она же основная, она же `primary` - Стандартная, она же основная, она же `primary`
- Стандартная, она же основная, она же `primary` с иконкой
- Второстепенная, она же `secondary` - Второстепенная, она же `secondary`
- Третьестепенная, она же `tertiary`, иногда ее называют `outlined` - Второстепенная, она же `secondary` с иконкой
- Кнопка, занимающая всю ширину родителя, она же `fullWidth` - Кнопка шириной в 100%, она же fullWidth, может быть как `primary`, так и `secondary`
- Кнопка, которая выглядит как ссылка, она же `link`
- Ссылка, которая выглядит как кнопка
### Варианты реализации ### Варианты реализации
@@ -54,7 +60,7 @@ src
import { ComponentPropsWithoutRef } from 'react' import { ComponentPropsWithoutRef } from 'react'
export type ButtonProps = { export type ButtonProps = {
variant?: 'primary' | 'secondary' | 'tertiary' | 'link' variant?: 'primary' | 'secondary'
fullWidth?: boolean fullWidth?: boolean
} & ComponentPropsWithoutRef<'button'> } & ComponentPropsWithoutRef<'button'>
``` ```
@@ -85,6 +91,9 @@ export const Button = ({ className, fullWidth, variant = 'primary', ...rest }: B
.button { .button {
all: unset; all: unset;
cursor: pointer; cursor: pointer;
box-sizing: border-box;
color: inherit;
font-family: inherit;
&:focus-visible { &:focus-visible {
outline: 2px solid var(--color-info-500); outline: 2px solid var(--color-info-500);
@@ -92,19 +101,11 @@ export const Button = ({ className, fullWidth, variant = 'primary', ...rest }: B
} }
.primary { .primary {
background-color: red; background-color: var(--color-primary-500);
} }
.secondary { .secondary {
background-color: green; background-color: var(--color-dark-300);
}
.tertiary {
background-color: blue;
}
.link {
background-color: yellow;
} }
.fullWidth { .fullWidth {
@@ -133,7 +134,7 @@ const meta = {
tags: ['autodocs'], tags: ['autodocs'],
argTypes: { argTypes: {
variant: { variant: {
options: ['primary', 'secondary', 'tertiary', 'link'], options: ['primary', 'secondary'],
control: { type: 'radio' }, control: { type: 'radio' },
}, },
}, },
@@ -157,25 +158,11 @@ export const Secondary: Story = {
disabled: false, disabled: false,
}, },
} }
export const Tertiary: Story = {
args: {
variant: 'tertiary',
children: 'Tertiary Button',
disabled: false,
},
}
export const Link: Story = {
args: {
variant: 'link',
children: 'Tertiary Button',
disabled: false,
},
}
export const FullWidth: Story = { export const FullWidth: Story = {
args: { args: {
variant: 'primary', variant: 'primary',
children: 'Full Width Button', children: 'Full Width Primary Button',
disabled: false, disabled: false,
fullWidth: true, fullWidth: true,
}, },
@@ -201,7 +188,7 @@ import s from './button.module.scss'
export type ButtonProps = { export type ButtonProps = {
as: any as: any
variant?: 'primary' | 'secondary' | 'tertiary' | 'link' variant?: 'primary' | 'secondary'
fullWidth?: boolean fullWidth?: boolean
} & ComponentPropsWithoutRef<'button'> } & ComponentPropsWithoutRef<'button'>
@@ -221,8 +208,6 @@ export const Button = ({
} }
``` ```
Этот
Добавим в stories пример использования: Добавим в stories пример использования:
```tsx filename="button.stories.ts" ```tsx filename="button.stories.ts"
@@ -241,11 +226,6 @@ export const AsLink: Story = {
Как видите, в DOM мы получили тег `a` с нужными пропсами. Как видите, в DOM мы получили тег `a` с нужными пропсами.
<Callout>
В данном примере кнопка с тэгом 'a' не выглядит так, как обычная кнопка. Это связано со
стандартными стилями браузера и будет работать как надо когда мы застилизуем нашу кнопку правильно
</Callout>
### Типизация ### Типизация
В примере выше мы использовали `as: any`, но это не очень хорошо, так как мы теряем типизацию. В примере выше мы использовали `as: any`, но это не очень хорошо, так как мы теряем типизацию.
@@ -261,7 +241,7 @@ import s from './button.module.scss'
export type ButtonProps<T extends ElementType = 'button'> = { export type ButtonProps<T extends ElementType = 'button'> = {
as?: T as?: T
children: ReactNode children: ReactNode
variant?: 'primary' | 'secondary' | 'tertiary' | 'link' variant?: 'primary' | 'secondary'
fullWidth?: boolean fullWidth?: boolean
className?: string className?: string
} & ComponentPropsWithoutRef<T> } & ComponentPropsWithoutRef<T>
@@ -289,63 +269,6 @@ export const Button = <T extends ElementType = 'button'>(props: ButtonProps<T>)
все еще иногда ошибается с TypeScript. все еще иногда ошибается с TypeScript.
</Callout> </Callout>
Все работает как надо, но нужно поправить еще один маленький нюанс: Из-за того, что мы заранее не
знаем какие пропсы будут у компонента, мы указали className как параметр нашей кнопки (расширяя
стандартные пропсы), чтобы не потерять его типизацию:
```tsx filename="button.tsx" showLineNumbers {9}
import { ComponentPropsWithoutRef, ElementType } from 'react'
import s from './button.module.scss'
export type ButtonProps<T extends ElementType = 'button'> = {
as?: T
variant?: 'primary' | 'secondary' | 'tertiary' | 'link'
fullWidth?: boolean
className?: string
} & ComponentPropsWithoutRef<T>
export const Button = <T extends ElementType = 'button'>(props: ButtonProps<T>) => {
const { variant = 'primary', fullWidth, className, as: Component = 'button', ...rest } = props
return (
<Component className={`${s[variant]} ${fullWidth ? s.fullWidth : ''} ${className}`} {...rest} />
)
}
```
Это может привести к неожиданным коллизиям и странным ошибкам TypeScript'а, поэтому мы добавим
небольшую проверку:
```tsx filename="button.tsx" showLineNumbers {13}
import { ComponentPropsWithoutRef, ElementType } from 'react'
import s from './button.module.scss'
export type ButtonProps<T extends ElementType = 'button'> = {
as?: T
variant?: 'primary' | 'secondary' | 'tertiary' | 'link'
fullWidth?: boolean
className?: string
} & ComponentPropsWithoutRef<T>
export const Button = <T extends ElementType = 'button'>(
props: ButtonProps<T> & Omit<ComponentPropsWithoutRef<T>, keyof ButtonProps<T>>
) => {
const { variant = 'primary', fullWidth, className, as: Component = 'button', ...rest } = props
return (
<Component className={`${s[variant]} ${fullWidth ? s.fullWidth : ''} ${className}`} {...rest} />
)
}
```
С помощью Omit мы убираем из пропсов переданного компонента все пропсы, которые уже есть в наших
кастомных пропсах, тем самым избегая коллизий.
Подробнее про типизацию полиморфных компонентов можно почитать
[вот тут](https://itnext.io/react-polymorphic-components-with-typescript-f7ce72ea7af2).
## Коммитим изменения ## Коммитим изменения
```bash filename="Terminal" ```bash filename="Terminal"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

After

Width:  |  Height:  |  Size: 319 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 207 KiB

After

Width:  |  Height:  |  Size: 215 KiB