Merge master into storybook-deploy

This commit is contained in:
github-actions[bot]
2023-12-28 11:06:41 +00:00
committed by GitHub
81 changed files with 4446 additions and 4050 deletions

View File

@@ -1,5 +1,3 @@
module.exports = {
...require('@it-incubator/prettier-config'),
tabWidth: 4,
//override settings here
}

View File

@@ -1,20 +1,24 @@
import type { StorybookConfig } from '@storybook/react-vite'
const config: StorybookConfig = {
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-onboarding',
'@storybook/addon-interactions',
'storybook-addon-react-router-v6',
],
framework: {
name: '@storybook/react-vite',
options: {},
},
docs: {
autodocs: 'tag',
},
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-onboarding',
'@storybook/addon-interactions',
'storybook-addon-react-router-v6',
],
framework: {
name: '@storybook/react-vite',
options: {},
},
docs: {
autodocs: 'tag',
},
viteFinal: async config => {
config.assetsInclude = ['/sb-preview/runtime.js']
return config
},
}
export default config

View File

@@ -2,11 +2,11 @@ const fs = require('fs')
const path = require('path')
const ruleFiles = fs
.readdirSync(__dirname)
.filter(file => file !== 'index.js' && !file.endsWith('test.js'))
.readdirSync(__dirname)
.filter(file => file !== 'index.js' && !file.endsWith('test.js'))
const rules = Object.fromEntries(
ruleFiles.map(file => [path.basename(file, '.js'), require('./' + file)])
ruleFiles.map(file => [path.basename(file, '.js'), require('./' + file)])
)
module.exports = { rules }

View File

@@ -1,26 +1,26 @@
// @ts-check
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
create(context) {
return {
ImportDeclaration(node) {
if (
(node.source && node.source.value === '@reduxjs/toolkit/query/') ||
node.source.value === '@reduxjs/toolkit/query'
) {
context.report({
fix(fixer) {
return fixer.replaceText(node.source, "'@reduxjs/toolkit/query/react'")
},
message:
"Import from '@reduxjs/toolkit/query/' is disallowed. Please import from '@reduxjs/toolkit/query/react'.",
node,
})
}
create(context) {
return {
ImportDeclaration(node) {
if (
(node.source && node.source.value === '@reduxjs/toolkit/query/') ||
node.source.value === '@reduxjs/toolkit/query'
) {
context.report({
fix(fixer) {
return fixer.replaceText(node.source, "'@reduxjs/toolkit/query/react'")
},
message:
"Import from '@reduxjs/toolkit/query/' is disallowed. Please import from '@reduxjs/toolkit/query/react'.",
node,
})
}
},
meta: {
fixable: 'code',
},
},
}
},
meta: {
fixable: 'code',
},
}

View File

@@ -1,68 +1,67 @@
{
"name": "example",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"format": "prettier --write src",
"lint": "eslint . --ext .jsx,.js,.tsx,.ts --no-error-on-unmatched-pattern --fix && stylelint --fix src/{,*/}*.{scss,css} --allow-empty-input",
"sb": "storybook dev -p 6006",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
},
"dependencies": {
"@fontsource/roboto": "^5.0.8",
"@hookform/resolvers": "^3.3.1",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-radio-group": "^1.1.3",
"@radix-ui/react-slider": "^1.1.2",
"@radix-ui/react-tabs": "^1.0.4",
"@reduxjs/toolkit": "^1.9.7",
"@storybook/theming": "^7.4.6",
"clsx": "^2.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.47.0",
"react-redux": "^8.1.3",
"react-router-dom": "^6.16.0",
"react-toastify": "^9.1.3",
"remeda": "^1.27.1",
"storybook-addon-react-router-v6": "^2.0.7",
"zod": "^3.22.4"
},
"devDependencies": {
"@hookform/devtools": "^4.3.1",
"@it-incubator/eslint-config": "^1.0.1",
"@it-incubator/prettier-config": "^0.1.2",
"@it-incubator/stylelint-config": "^0.1.5",
"@storybook/addon-essentials": "^7.4.6",
"@storybook/addon-interactions": "^7.4.6",
"@storybook/addon-links": "^7.4.6",
"@storybook/addon-onboarding": "^1.0.8",
"@storybook/blocks": "^7.4.6",
"@storybook/react": "^7.4.6",
"@storybook/react-vite": "^7.4.6",
"@storybook/testing-library": "^0.2.2",
"@types/node": "^20.8.4",
"@types/react": "^18.2.27",
"@types/react-dom": "^18.2.12",
"@typescript-eslint/eslint-plugin": "^6.7.5",
"@typescript-eslint/parser": "^6.7.5",
"@vitejs/plugin-react": "^4.1.0",
"eslint": "^8.51.0",
"eslint-plugin-myPlugin": "file:eslint",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
"eslint-plugin-storybook": "^0.6.15",
"sass": "^1.69.1",
"storybook": "^7.4.6",
"stylelint": "^15.10.3",
"typescript": "^5.2.2",
"vite": "^4.4.11"
}
"name": "example",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"format": "prettier --write src",
"lint": "eslint . --ext .jsx,.js,.tsx,.ts --no-error-on-unmatched-pattern --fix && stylelint --fix src/{,*/}*.{scss,css} --allow-empty-input",
"storybook": "storybook dev -p 6006",
"sb": "storybook dev -p 6006",
"build-storybook": "storybook build"
},
"dependencies": {
"@fontsource/roboto": "^5.0.8",
"@hookform/resolvers": "^3.3.3",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-radio-group": "^1.1.3",
"@radix-ui/react-slider": "^1.1.2",
"@radix-ui/react-tabs": "^1.0.4",
"@reduxjs/toolkit": "^2.0.1",
"@storybook/theming": "^7.6.6",
"clsx": "^2.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.49.2",
"react-redux": "^9.0.4",
"react-router-dom": "^6.21.1",
"react-toastify": "^9.1.3",
"remeda": "^1.33.0",
"storybook-addon-react-router-v6": "^2.0.10",
"zod": "^3.22.4"
},
"devDependencies": {
"@hookform/devtools": "^4.3.1",
"@it-incubator/eslint-config": "^1.0.2",
"@it-incubator/prettier-config": "^0.1.2",
"@it-incubator/stylelint-config": "^0.1.5",
"@storybook/addon-essentials": "^7.6.6",
"@storybook/addon-interactions": "^7.6.6",
"@storybook/addon-links": "^7.6.6",
"@storybook/blocks": "^7.6.6",
"@storybook/react": "^7.6.6",
"@storybook/react-vite": "^7.6.6",
"@storybook/testing-library": "^0.2.2",
"@types/node": "^20.10.5",
"@types/react": "^18.2.45",
"@types/react-dom": "^18.2.18",
"@typescript-eslint/eslint-plugin": "^6.16.0",
"@typescript-eslint/parser": "^6.16.0",
"@vitejs/plugin-react": "^4.2.1",
"eslint": "^8.56.0",
"eslint-plugin-myPlugin": "file:eslint",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.5",
"eslint-plugin-storybook": "^0.6.15",
"sass": "^1.69.5",
"storybook": "^7.6.6",
"stylelint": "^15.10.3",
"typescript": "^5.3.3",
"vite": "5.0.10"
}
}

3539
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,9 +4,9 @@ import { Router } from '@/router'
import { store } from '@/services/store'
export function App() {
return (
<Provider store={store}>
<Router />
</Provider>
)
return (
<Provider store={store}>
<Router />
</Provider>
)
}

View File

@@ -1,71 +1,71 @@
import { Ref, SVGProps, forwardRef, memo } from 'react'
const SvgComponent = (props: SVGProps<SVGSVGElement>, ref: Ref<SVGSVGElement>) => (
<svg
fill={'none'}
<svg
fill={'none'}
height={'44'}
ref={ref}
viewBox={'0 0 44 44'}
width={'44'}
xmlns={'http://www.w3.org/2000/svg'}
{...props}
>
<g filter={'url(#filter0_d_5918_2450)'}>
<rect fill={'#4C4C4C'} height={'24'} rx={'4'} width={'24'} x={'10'} y={'8'} />
<g clipPath={'url(#clip0_5918_2450)'}>
<path
d={
'M26.6666 25.3334H17.3333C17.1565 25.3334 16.9869 25.4036 16.8619 25.5286C16.7369 25.6537 16.6666 25.8232 16.6666 26C16.6666 26.1769 16.7369 26.3464 16.8619 26.4714C16.9869 26.5965 17.1565 26.6667 17.3333 26.6667H26.6666C26.8434 26.6667 27.013 26.5965 27.138 26.4714C27.2631 26.3464 27.3333 26.1769 27.3333 26C27.3333 25.8232 27.2631 25.6537 27.138 25.5286C27.013 25.4036 26.8434 25.3334 26.6666 25.3334Z'
}
fill={'white'}
/>
<path
d={
'M17.3333 24H17.3933L20.1733 23.7467C20.4778 23.7163 20.7626 23.5821 20.98 23.3667L26.98 17.3667C27.2128 17.1206 27.3387 16.7923 27.3299 16.4537C27.3212 16.115 27.1786 15.7937 26.9333 15.56L25.1066 13.7333C24.8682 13.5094 24.5558 13.3809 24.2288 13.3723C23.9019 13.3637 23.5831 13.4756 23.3333 13.6867L17.3333 19.6867C17.1178 19.904 16.9836 20.1888 16.9533 20.4933L16.6666 23.2733C16.6576 23.371 16.6703 23.4694 16.7037 23.5616C16.7371 23.6538 16.7905 23.7374 16.86 23.8067C16.9222 23.8684 16.9961 23.9173 17.0773 23.9505C17.1586 23.9837 17.2455 24.0005 17.3333 24ZM24.18 14.6667L26 16.4867L24.6666 17.7867L22.88 16L24.18 14.6667ZM18.2466 20.6067L22 16.88L23.8 18.68L20.0666 22.4133L18.0666 22.6L18.2466 20.6067Z'
}
fill={'white'}
/>
</g>
</g>
<defs>
<filter
colorInterpolationFilters={'sRGB'}
filterUnits={'userSpaceOnUse'}
height={'44'}
ref={ref}
viewBox={'0 0 44 44'}
id={'filter0_d_5918_2450'}
width={'44'}
xmlns={'http://www.w3.org/2000/svg'}
{...props}
>
<g filter={'url(#filter0_d_5918_2450)'}>
<rect fill={'#4C4C4C'} height={'24'} rx={'4'} width={'24'} x={'10'} y={'8'} />
<g clipPath={'url(#clip0_5918_2450)'}>
<path
d={
'M26.6666 25.3334H17.3333C17.1565 25.3334 16.9869 25.4036 16.8619 25.5286C16.7369 25.6537 16.6666 25.8232 16.6666 26C16.6666 26.1769 16.7369 26.3464 16.8619 26.4714C16.9869 26.5965 17.1565 26.6667 17.3333 26.6667H26.6666C26.8434 26.6667 27.013 26.5965 27.138 26.4714C27.2631 26.3464 27.3333 26.1769 27.3333 26C27.3333 25.8232 27.2631 25.6537 27.138 25.5286C27.013 25.4036 26.8434 25.3334 26.6666 25.3334Z'
}
fill={'white'}
/>
<path
d={
'M17.3333 24H17.3933L20.1733 23.7467C20.4778 23.7163 20.7626 23.5821 20.98 23.3667L26.98 17.3667C27.2128 17.1206 27.3387 16.7923 27.3299 16.4537C27.3212 16.115 27.1786 15.7937 26.9333 15.56L25.1066 13.7333C24.8682 13.5094 24.5558 13.3809 24.2288 13.3723C23.9019 13.3637 23.5831 13.4756 23.3333 13.6867L17.3333 19.6867C17.1178 19.904 16.9836 20.1888 16.9533 20.4933L16.6666 23.2733C16.6576 23.371 16.6703 23.4694 16.7037 23.5616C16.7371 23.6538 16.7905 23.7374 16.86 23.8067C16.9222 23.8684 16.9961 23.9173 17.0773 23.9505C17.1586 23.9837 17.2455 24.0005 17.3333 24ZM24.18 14.6667L26 16.4867L24.6666 17.7867L22.88 16L24.18 14.6667ZM18.2466 20.6067L22 16.88L23.8 18.68L20.0666 22.4133L18.0666 22.6L18.2466 20.6067Z'
}
fill={'white'}
/>
</g>
</g>
<defs>
<filter
colorInterpolationFilters={'sRGB'}
filterUnits={'userSpaceOnUse'}
height={'44'}
id={'filter0_d_5918_2450'}
width={'44'}
x={'0'}
y={'0'}
>
<feFlood floodOpacity={'0'} result={'BackgroundImageFix'} />
<feColorMatrix
in={'SourceAlpha'}
result={'hardAlpha'}
type={'matrix'}
values={'0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0'}
/>
<feOffset dy={'2'} />
<feGaussianBlur stdDeviation={'5'} />
<feColorMatrix
type={'matrix'}
values={'0 0 0 0 0.429167 0 0 0 0 0.429167 0 0 0 0 0.429167 0 0 0 0.25 0'}
/>
<feBlend
in2={'BackgroundImageFix'}
mode={'normal'}
result={'effect1_dropShadow_5918_2450'}
/>
<feBlend
in={'SourceGraphic'}
in2={'effect1_dropShadow_5918_2450'}
mode={'normal'}
result={'shape'}
/>
</filter>
<clipPath id={'clip0_5918_2450'}>
<rect fill={'white'} height={'16'} transform={'translate(14 12)'} width={'16'} />
</clipPath>
</defs>
</svg>
x={'0'}
y={'0'}
>
<feFlood floodOpacity={'0'} result={'BackgroundImageFix'} />
<feColorMatrix
in={'SourceAlpha'}
result={'hardAlpha'}
type={'matrix'}
values={'0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0'}
/>
<feOffset dy={'2'} />
<feGaussianBlur stdDeviation={'5'} />
<feColorMatrix
type={'matrix'}
values={'0 0 0 0 0.429167 0 0 0 0 0.429167 0 0 0 0 0.429167 0 0 0 0.25 0'}
/>
<feBlend
in2={'BackgroundImageFix'}
mode={'normal'}
result={'effect1_dropShadow_5918_2450'}
/>
<feBlend
in={'SourceGraphic'}
in2={'effect1_dropShadow_5918_2450'}
mode={'normal'}
result={'shape'}
/>
</filter>
<clipPath id={'clip0_5918_2450'}>
<rect fill={'white'} height={'16'} transform={'translate(14 12)'} width={'16'} />
</clipPath>
</defs>
</svg>
)
const ForwardRef = forwardRef(SvgComponent)

View File

@@ -1,21 +1,21 @@
import { Ref, SVGProps, forwardRef, memo } from 'react'
const SvgComponent = (props: SVGProps<SVGSVGElement>, ref: Ref<SVGSVGElement>) => (
<svg
fill={'none'}
height={18}
ref={ref}
viewBox={'0 0 18 18'}
width={18}
xmlns={'http://www.w3.org/2000/svg'}
{...props}
>
<path
d={
'M16 0H2a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2V2a2 2 0 00-2-2zM7 14L2 9l1.41-1.41L7 11.17l7.59-7.59L16 5l-9 9z'
}
fill={'currentColor'}
/>
</svg>
<svg
fill={'none'}
height={18}
ref={ref}
viewBox={'0 0 18 18'}
width={18}
xmlns={'http://www.w3.org/2000/svg'}
{...props}
>
<path
d={
'M16 0H2a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2V2a2 2 0 00-2-2zM7 14L2 9l1.41-1.41L7 11.17l7.59-7.59L16 5l-9 9z'
}
fill={'currentColor'}
/>
</svg>
)
const ForwardRef = forwardRef(SvgComponent)

View File

@@ -1,28 +1,28 @@
import { Ref, SVGProps, forwardRef, memo } from 'react'
const ChevronUp = (props: SVGProps<SVGSVGElement>, ref: Ref<SVGSVGElement>) => (
<svg
fill={'none'}
height={12}
ref={ref}
viewBox={'0 0 12 12'}
width={12}
xmlns={'http://www.w3.org/2000/svg'}
{...props}
>
<g clipPath={'url(#clip0_9209_3250)'}>
<path
d={
'M9.77084 7.25705C9.77107 7.37388 9.73039 7.4871 9.65584 7.57705C9.61387 7.62768 9.56232 7.66953 9.50415 7.70021C9.44597 7.73089 9.38232 7.74978 9.31683 7.75582C9.25134 7.76186 9.18531 7.75492 9.1225 7.7354C9.0597 7.71588 9.00137 7.68416 8.95084 7.64205L6.27084 5.40205L3.58584 7.56205C3.5347 7.60359 3.47585 7.6346 3.41268 7.65332C3.34951 7.67203 3.28327 7.67808 3.21775 7.67111C3.15224 7.66414 3.08875 7.64429 3.03093 7.61271C2.97311 7.58112 2.92211 7.53842 2.88084 7.48705C2.83533 7.43532 2.80099 7.37474 2.78 7.30911C2.759 7.24348 2.7518 7.17421 2.75884 7.10567C2.76589 7.03712 2.78702 6.97077 2.82092 6.91078C2.85482 6.85079 2.90076 6.79845 2.95584 6.75705L5.95584 4.34205C6.04531 4.26851 6.15753 4.22831 6.27334 4.22831C6.38916 4.22831 6.50138 4.26851 6.59084 4.34205L9.59084 6.84205C9.65135 6.89221 9.69918 6.95593 9.73046 7.02803C9.76173 7.10014 9.77557 7.1786 9.77084 7.25705Z'
}
fill={'white'}
/>
</g>
<defs>
<clipPath id={'clip0_9209_3250'}>
<rect fill={'white'} height={12} width={12} />
</clipPath>
</defs>
</svg>
<svg
fill={'none'}
height={12}
ref={ref}
viewBox={'0 0 12 12'}
width={12}
xmlns={'http://www.w3.org/2000/svg'}
{...props}
>
<g clipPath={'url(#clip0_9209_3250)'}>
<path
d={
'M9.77084 7.25705C9.77107 7.37388 9.73039 7.4871 9.65584 7.57705C9.61387 7.62768 9.56232 7.66953 9.50415 7.70021C9.44597 7.73089 9.38232 7.74978 9.31683 7.75582C9.25134 7.76186 9.18531 7.75492 9.1225 7.7354C9.0597 7.71588 9.00137 7.68416 8.95084 7.64205L6.27084 5.40205L3.58584 7.56205C3.5347 7.60359 3.47585 7.6346 3.41268 7.65332C3.34951 7.67203 3.28327 7.67808 3.21775 7.67111C3.15224 7.66414 3.08875 7.64429 3.03093 7.61271C2.97311 7.58112 2.92211 7.53842 2.88084 7.48705C2.83533 7.43532 2.80099 7.37474 2.78 7.30911C2.759 7.24348 2.7518 7.17421 2.75884 7.10567C2.76589 7.03712 2.78702 6.97077 2.82092 6.91078C2.85482 6.85079 2.90076 6.79845 2.95584 6.75705L5.95584 4.34205C6.04531 4.26851 6.15753 4.22831 6.27334 4.22831C6.38916 4.22831 6.50138 4.26851 6.59084 4.34205L9.59084 6.84205C9.65135 6.89221 9.69918 6.95593 9.73046 7.02803C9.76173 7.10014 9.77557 7.1786 9.77084 7.25705Z'
}
fill={'white'}
/>
</g>
<defs>
<clipPath id={'clip0_9209_3250'}>
<rect fill={'white'} height={12} width={12} />
</clipPath>
</defs>
</svg>
)
const ForwardRef = forwardRef(ChevronUp)
const Memo = memo(ForwardRef)

View File

@@ -1,37 +1,37 @@
import { Ref, SVGProps, forwardRef, memo } from 'react'
const SvgComponent = (props: SVGProps<SVGSVGElement>, ref: Ref<SVGSVGElement>) => (
<svg
fill={'none'}
height={'24'}
ref={ref}
viewBox={'0 0 24 24'}
width={'24'}
xmlns={'http://www.w3.org/2000/svg'}
{...props}
>
<svg
fill={'none'}
height={'24'}
ref={ref}
viewBox={'0 0 24 24'}
width={'24'}
xmlns={'http://www.w3.org/2000/svg'}
{...props}
fill={'none'}
height={'24'}
viewBox={'0 0 24 24'}
width={'24'}
xmlns={'http://www.w3.org/2000/svg'}
>
<svg
fill={'none'}
height={'24'}
viewBox={'0 0 24 24'}
width={'24'}
xmlns={'http://www.w3.org/2000/svg'}
>
<g clipPath={'url(#clip0_9883_2381)'}>
<path
d={
'M19 6.41L17.59 5L12 10.59L6.41 5L5 6.41L10.59 12L5 17.59L6.41 19L12 13.41L17.59 19L19 17.59L13.41 12L19 6.41Z'
}
fill={'white'}
/>
</g>
<defs>
<clipPath id={'clip0_9883_2381'}>
<rect fill={'white'} height={'24'} width={'24'} />
</clipPath>
</defs>
</svg>
<g clipPath={'url(#clip0_9883_2381)'}>
<path
d={
'M19 6.41L17.59 5L12 10.59L6.41 5L5 6.41L10.59 12L5 17.59L6.41 19L12 13.41L17.59 19L19 17.59L13.41 12L19 6.41Z'
}
fill={'white'}
/>
</g>
<defs>
<clipPath id={'clip0_9883_2381'}>
<rect fill={'white'} height={'24'} width={'24'} />
</clipPath>
</defs>
</svg>
</svg>
)
const ForwardRef = forwardRef(SvgComponent)

View File

@@ -1,33 +1,33 @@
import { SVGProps } from 'react'
const SvgComponent = (props: SVGProps<SVGSVGElement>) => (
<svg
fill={'none'}
height={'16'}
viewBox={'0 0 16 16'}
width={'16'}
xmlns={'http://www.w3.org/2000/svg'}
{...props}
>
<g clipPath={'url(#clip0_28366_3239)'}>
<path
d={
'M12.6666 13.3334H3.33329C3.15648 13.3334 2.98691 13.4036 2.86189 13.5286C2.73686 13.6537 2.66663 13.8232 2.66663 14C2.66663 14.1769 2.73686 14.3464 2.86189 14.4714C2.98691 14.5965 3.15648 14.6667 3.33329 14.6667H12.6666C12.8434 14.6667 13.013 14.5965 13.138 14.4714C13.2631 14.3464 13.3333 14.1769 13.3333 14C13.3333 13.8232 13.2631 13.6537 13.138 13.5286C13.013 13.4036 12.8434 13.3334 12.6666 13.3334Z'
}
fill={'white'}
/>
<path
d={
'M3.33329 12H3.39329L6.17329 11.7467C6.47782 11.7163 6.76264 11.5821 6.97995 11.3667L12.98 5.36665C13.2128 5.12063 13.3387 4.79233 13.3299 4.45368C13.3212 4.11503 13.1786 3.79366 12.9333 3.55999L11.1066 1.73332C10.8682 1.50938 10.5558 1.38089 10.2288 1.37229C9.90187 1.36368 9.58314 1.47557 9.33329 1.68665L3.33329 7.68665C3.1178 7.90396 2.98362 8.18879 2.95329 8.49332L2.66662 11.2733C2.65764 11.371 2.67031 11.4694 2.70373 11.5616C2.73715 11.6538 2.79049 11.7374 2.85995 11.8067C2.92225 11.8684 2.99612 11.9173 3.07735 11.9505C3.15857 11.9837 3.24555 12.0005 3.33329 12ZM10.18 2.66665L12 4.48665L10.6666 5.78665L8.87995 3.99999L10.18 2.66665ZM4.24662 8.60665L7.99995 4.87999L9.79995 6.67999L6.06662 10.4133L4.06662 10.6L4.24662 8.60665Z'
}
fill={'white'}
/>
</g>
<defs>
<clipPath id={'clip0_28366_3239'}>
<rect fill={'white'} height={'16'} width={'16'} />
</clipPath>
</defs>
</svg>
<svg
fill={'none'}
height={'16'}
viewBox={'0 0 16 16'}
width={'16'}
xmlns={'http://www.w3.org/2000/svg'}
{...props}
>
<g clipPath={'url(#clip0_28366_3239)'}>
<path
d={
'M12.6666 13.3334H3.33329C3.15648 13.3334 2.98691 13.4036 2.86189 13.5286C2.73686 13.6537 2.66663 13.8232 2.66663 14C2.66663 14.1769 2.73686 14.3464 2.86189 14.4714C2.98691 14.5965 3.15648 14.6667 3.33329 14.6667H12.6666C12.8434 14.6667 13.013 14.5965 13.138 14.4714C13.2631 14.3464 13.3333 14.1769 13.3333 14C13.3333 13.8232 13.2631 13.6537 13.138 13.5286C13.013 13.4036 12.8434 13.3334 12.6666 13.3334Z'
}
fill={'white'}
/>
<path
d={
'M3.33329 12H3.39329L6.17329 11.7467C6.47782 11.7163 6.76264 11.5821 6.97995 11.3667L12.98 5.36665C13.2128 5.12063 13.3387 4.79233 13.3299 4.45368C13.3212 4.11503 13.1786 3.79366 12.9333 3.55999L11.1066 1.73332C10.8682 1.50938 10.5558 1.38089 10.2288 1.37229C9.90187 1.36368 9.58314 1.47557 9.33329 1.68665L3.33329 7.68665C3.1178 7.90396 2.98362 8.18879 2.95329 8.49332L2.66662 11.2733C2.65764 11.371 2.67031 11.4694 2.70373 11.5616C2.73715 11.6538 2.79049 11.7374 2.85995 11.8067C2.92225 11.8684 2.99612 11.9173 3.07735 11.9505C3.15857 11.9837 3.24555 12.0005 3.33329 12ZM10.18 2.66665L12 4.48665L10.6666 5.78665L8.87995 3.99999L10.18 2.66665ZM4.24662 8.60665L7.99995 4.87999L9.79995 6.67999L6.06662 10.4133L4.06662 10.6L4.24662 8.60665Z'
}
fill={'white'}
/>
</g>
<defs>
<clipPath id={'clip0_28366_3239'}>
<rect fill={'white'} height={'16'} width={'16'} />
</clipPath>
</defs>
</svg>
)
export default SvgComponent

View File

@@ -1,34 +1,34 @@
import { Ref, SVGProps, forwardRef, memo } from 'react'
const SvgComponent = (props: SVGProps<SVGSVGElement>, ref: Ref<SVGSVGElement>) => (
<svg
fill={'none'}
height={'16'}
ref={ref}
viewBox={'0 0 16 16'}
width={'16'}
xmlns={'http://www.w3.org/2000/svg'}
{...props}
>
<g clipPath={'url(#clip0_5918_2436)'}>
<path
d={
'M12.6666 13.3334H3.33329C3.15648 13.3334 2.98691 13.4036 2.86189 13.5286C2.73686 13.6537 2.66663 13.8232 2.66663 14C2.66663 14.1769 2.73686 14.3464 2.86189 14.4714C2.98691 14.5965 3.15648 14.6667 3.33329 14.6667H12.6666C12.8434 14.6667 13.013 14.5965 13.138 14.4714C13.2631 14.3464 13.3333 14.1769 13.3333 14C13.3333 13.8232 13.2631 13.6537 13.138 13.5286C13.013 13.4036 12.8434 13.3334 12.6666 13.3334Z'
}
fill={'white'}
/>
<path
d={
'M3.33329 12H3.39329L6.17329 11.7467C6.47782 11.7163 6.76264 11.5821 6.97995 11.3667L12.98 5.36665C13.2128 5.12063 13.3387 4.79233 13.3299 4.45368C13.3212 4.11503 13.1786 3.79366 12.9333 3.55999L11.1066 1.73332C10.8682 1.50938 10.5558 1.38089 10.2288 1.37229C9.90187 1.36368 9.58314 1.47557 9.33329 1.68665L3.33329 7.68665C3.1178 7.90396 2.98362 8.18879 2.95329 8.49332L2.66662 11.2733C2.65764 11.371 2.67031 11.4694 2.70373 11.5616C2.73715 11.6538 2.79049 11.7374 2.85995 11.8067C2.92225 11.8684 2.99612 11.9173 3.07735 11.9505C3.15857 11.9837 3.24555 12.0005 3.33329 12ZM10.18 2.66665L12 4.48665L10.6666 5.78665L8.87995 3.99999L10.18 2.66665ZM4.24662 8.60665L7.99995 4.87999L9.79995 6.67999L6.06662 10.4133L4.06662 10.6L4.24662 8.60665Z'
}
fill={'white'}
/>
</g>
<defs>
<clipPath id={'clip0_5918_2436'}>
<rect fill={'white'} height={'16'} width={'16'} />
</clipPath>
</defs>
</svg>
<svg
fill={'none'}
height={'16'}
ref={ref}
viewBox={'0 0 16 16'}
width={'16'}
xmlns={'http://www.w3.org/2000/svg'}
{...props}
>
<g clipPath={'url(#clip0_5918_2436)'}>
<path
d={
'M12.6666 13.3334H3.33329C3.15648 13.3334 2.98691 13.4036 2.86189 13.5286C2.73686 13.6537 2.66663 13.8232 2.66663 14C2.66663 14.1769 2.73686 14.3464 2.86189 14.4714C2.98691 14.5965 3.15648 14.6667 3.33329 14.6667H12.6666C12.8434 14.6667 13.013 14.5965 13.138 14.4714C13.2631 14.3464 13.3333 14.1769 13.3333 14C13.3333 13.8232 13.2631 13.6537 13.138 13.5286C13.013 13.4036 12.8434 13.3334 12.6666 13.3334Z'
}
fill={'white'}
/>
<path
d={
'M3.33329 12H3.39329L6.17329 11.7467C6.47782 11.7163 6.76264 11.5821 6.97995 11.3667L12.98 5.36665C13.2128 5.12063 13.3387 4.79233 13.3299 4.45368C13.3212 4.11503 13.1786 3.79366 12.9333 3.55999L11.1066 1.73332C10.8682 1.50938 10.5558 1.38089 10.2288 1.37229C9.90187 1.36368 9.58314 1.47557 9.33329 1.68665L3.33329 7.68665C3.1178 7.90396 2.98362 8.18879 2.95329 8.49332L2.66662 11.2733C2.65764 11.371 2.67031 11.4694 2.70373 11.5616C2.73715 11.6538 2.79049 11.7374 2.85995 11.8067C2.92225 11.8684 2.99612 11.9173 3.07735 11.9505C3.15857 11.9837 3.24555 12.0005 3.33329 12ZM10.18 2.66665L12 4.48665L10.6666 5.78665L8.87995 3.99999L10.18 2.66665ZM4.24662 8.60665L7.99995 4.87999L9.79995 6.67999L6.06662 10.4133L4.06662 10.6L4.24662 8.60665Z'
}
fill={'white'}
/>
</g>
<defs>
<clipPath id={'clip0_5918_2436'}>
<rect fill={'white'} height={'16'} width={'16'} />
</clipPath>
</defs>
</svg>
)
const ForwardRef = forwardRef(SvgComponent)
const Memo = memo(ForwardRef)

View File

@@ -1,100 +1,100 @@
import { Ref, SVGProps, forwardRef, memo } from 'react'
const SvgComponent = (props: SVGProps<SVGSVGElement>, ref: Ref<SVGSVGElement>) => (
<svg
fill={'none'}
height={96}
ref={ref}
viewBox={'0 0 96 96'}
width={96}
xmlns={'http://www.w3.org/2000/svg'}
{...props}
>
<path
d={
'M95.5 48c0 26.234-21.266 47.5-47.5 47.5C21.767 95.5.5 74.234.5 48 .5 21.767 21.767.5 48 .5 74.234.5 95.5 21.767 95.5 48z'
}
fill={'#8C61FF'}
fillOpacity={0.05}
stroke={'#BEA6FF'}
/>
<path
d={
'M77.889 54.454a.651.651 0 0 1-.643-.643v-9.24a.643.643 0 0 1 1.285 0v9.25a.643.643 0 0 1-.641.633zM77.889 41.777a.643.643 0 0 1-.643-.643V39.24a.643.643 0 0 1 1.285 0v1.903a.634.634 0 0 1-.641.634zM25.786 32.503h-3.559a.668.668 0 0 1 0-1.335h3.56a.667.667 0 0 1 0 1.335z'
}
fill={'#BEA6FF'}
/>
<path
d={
'M24.007 34.282a.668.668 0 0 1-.667-.667v-3.559a.668.668 0 0 1 1.335 0v3.56c0 .367-.3.666-.668.666zM36.857 73.714a.857.857 0 1 1 0 1.715.857.857 0 0 1 0-1.715zM31.714 73.714a.857.857 0 1 1 0 1.715.857.857 0 0 1 0-1.715zM26.571 73.714a.857.857 0 1 1 0 1.715.857.857 0 0 1 0-1.715z'
}
fill={'#BEA6FF'}
/>
<path d={'M47.864 23.242l-24.1 19.799 24.1 19.808 24.1-19.808-24.1-19.8z'} fill={'#333'} />
<path
d={
'M47.865 63.544a.667.667 0 0 1-.417-.195l-24.1-19.808a.698.698 0 0 1-.183-.833.725.725 0 0 1 .183-.242l24.1-19.799a.695.695 0 0 1 .926 0l24.1 19.799a.725.725 0 0 1 .25.537.695.695 0 0 1-.25.538l-24.1 19.808a.668.668 0 0 1-.51.195zM24.859 43.04l23.006 18.91L70.87 43.04l-23.006-18.9-23.006 18.9z'
}
fill={'#BEA6FF'}
/>
<path d={'M65.013 28.803H30.717v40.784h34.296V28.803z'} fill={'#333'} />
<path d={'M55.41 40.853l-11.716 9.63v19.104h21.319V40.853H55.41z'} fill={'#333'} />
<path
d={
'M65.059 70.282H30.763a.704.704 0 0 1-.695-.695V28.803a.695.695 0 0 1 .695-.677H65.06a.695.695 0 0 1 .695.696v40.784a.705.705 0 0 1-.695.676zM31.412 68.91h32.905V29.517H31.412V68.91z'
}
fill={'#BEA6FF'}
/>
<path
d={
'M58.987 35.06H48.17a.695.695 0 0 1 0-1.39h10.817a.696.696 0 0 1 0 1.39zM44.463 35.06h-7.721a.696.696 0 0 1 0-1.39h7.72a.696.696 0 0 1 0 1.39zM58.987 39.694h-7.11a.695.695 0 0 1 0-1.39h7.11a.695.695 0 1 1 0 1.39zM48.17 39.694H36.742a.695.695 0 0 1 0-1.39H48.17a.695.695 0 1 1 0 1.39zM58.988 44.329H46.317a.696.696 0 0 1 0-1.39h12.67a.696.696 0 0 1 0 1.39zM42.609 44.329h-5.867a.696.696 0 0 1 0-1.39h5.867a.696.696 0 0 1 0 1.39zM58.987 48.964h-6.182a.695.695 0 0 1 0-1.39h6.182a.695.695 0 1 1 0 1.39zM49.097 48.964H36.742a.695.695 0 0 1 0-1.39h12.355a.695.695 0 1 1 0 1.39zM54.047 53.598H41.682a.695.695 0 1 1 0-1.39h12.365a.695.695 0 1 1 0 1.39z'
}
fill={'#BEA6FF'}
/>
<path
d={
'M23.765 43.04v26.547l16.147-13.273L23.765 43.04zM55.818 56.314l16.146 13.273V43.04L55.818 56.315z'
}
fill={'#333'}
/>
<path
d={'M47.864 62.849l-7.952-6.535-16.147 13.273h48.2L55.816 56.314l-7.953 6.535z'}
fill={'#333'}
/>
<path
d={
'M28.4 50.493v15.284l9.297-7.637-9.297-7.647zM63.196 57.88l8.768 7.212V50.678l-8.768 7.202z'
}
fill={'#333'}
/>
<path
d={
'M23.765 70.282a.67.67 0 0 1-.296-.074.686.686 0 0 1-.399-.62V43.04a.685.685 0 0 1 .399-.62.695.695 0 0 1 .741.083l16.147 13.273a.704.704 0 0 1 0 1.075L24.21 70.115a.686.686 0 0 1-.445.167zm.695-25.768v23.6l14.358-11.8-14.358-11.8z'
}
fill={'#BEA6FF'}
/>
<path
d={'M60.424 60.095l-10.15 8.343-7.08-5.821-8.473 6.97h37.243l-11.54-9.492z'}
fill={'#333'}
/>
<path
d={
'M71.964 70.282a.686.686 0 0 1-.445-.167L55.373 56.861a.703.703 0 0 1 0-1.076l16.174-13.282a.696.696 0 0 1 .742-.084.687.687 0 0 1 .398.621v26.547a.686.686 0 0 1-.398.621.668.668 0 0 1-.325.074zM56.911 56.314l14.358 11.8v-23.6l-14.358 11.8z'
}
fill={'#BEA6FF'}
/>
<path
d={
'M71.964 70.282h-48.2a.704.704 0 0 1-.657-.463.705.705 0 0 1 .213-.77l16.147-13.273a.695.695 0 0 1 .927 0l7.508 6.173 7.508-6.174a.695.695 0 0 1 .927 0L72.483 69.05a.705.705 0 0 1 .213.77.704.704 0 0 1-.732.463zm-46.262-1.371h44.325L55.79 57.212l-7.508 6.173a.695.695 0 0 1-.927 0l-7.508-6.173L25.702 68.91zM46.358 75.36h-3.56a.668.668 0 0 1 0-1.335h3.56a.668.668 0 0 1 0 1.335z'
}
fill={'#BEA6FF'}
/>
<path
d={
'M44.579 77.14a.668.668 0 0 1-.668-.668v-3.559a.668.668 0 0 1 1.335 0v3.56a.667.667 0 0 1-.667.666z'
}
fill={'#BEA6FF'}
/>
</svg>
<svg
fill={'none'}
height={96}
ref={ref}
viewBox={'0 0 96 96'}
width={96}
xmlns={'http://www.w3.org/2000/svg'}
{...props}
>
<path
d={
'M95.5 48c0 26.234-21.266 47.5-47.5 47.5C21.767 95.5.5 74.234.5 48 .5 21.767 21.767.5 48 .5 74.234.5 95.5 21.767 95.5 48z'
}
fill={'#8C61FF'}
fillOpacity={0.05}
stroke={'#BEA6FF'}
/>
<path
d={
'M77.889 54.454a.651.651 0 0 1-.643-.643v-9.24a.643.643 0 0 1 1.285 0v9.25a.643.643 0 0 1-.641.633zM77.889 41.777a.643.643 0 0 1-.643-.643V39.24a.643.643 0 0 1 1.285 0v1.903a.634.634 0 0 1-.641.634zM25.786 32.503h-3.559a.668.668 0 0 1 0-1.335h3.56a.667.667 0 0 1 0 1.335z'
}
fill={'#BEA6FF'}
/>
<path
d={
'M24.007 34.282a.668.668 0 0 1-.667-.667v-3.559a.668.668 0 0 1 1.335 0v3.56c0 .367-.3.666-.668.666zM36.857 73.714a.857.857 0 1 1 0 1.715.857.857 0 0 1 0-1.715zM31.714 73.714a.857.857 0 1 1 0 1.715.857.857 0 0 1 0-1.715zM26.571 73.714a.857.857 0 1 1 0 1.715.857.857 0 0 1 0-1.715z'
}
fill={'#BEA6FF'}
/>
<path d={'M47.864 23.242l-24.1 19.799 24.1 19.808 24.1-19.808-24.1-19.8z'} fill={'#333'} />
<path
d={
'M47.865 63.544a.667.667 0 0 1-.417-.195l-24.1-19.808a.698.698 0 0 1-.183-.833.725.725 0 0 1 .183-.242l24.1-19.799a.695.695 0 0 1 .926 0l24.1 19.799a.725.725 0 0 1 .25.537.695.695 0 0 1-.25.538l-24.1 19.808a.668.668 0 0 1-.51.195zM24.859 43.04l23.006 18.91L70.87 43.04l-23.006-18.9-23.006 18.9z'
}
fill={'#BEA6FF'}
/>
<path d={'M65.013 28.803H30.717v40.784h34.296V28.803z'} fill={'#333'} />
<path d={'M55.41 40.853l-11.716 9.63v19.104h21.319V40.853H55.41z'} fill={'#333'} />
<path
d={
'M65.059 70.282H30.763a.704.704 0 0 1-.695-.695V28.803a.695.695 0 0 1 .695-.677H65.06a.695.695 0 0 1 .695.696v40.784a.705.705 0 0 1-.695.676zM31.412 68.91h32.905V29.517H31.412V68.91z'
}
fill={'#BEA6FF'}
/>
<path
d={
'M58.987 35.06H48.17a.695.695 0 0 1 0-1.39h10.817a.696.696 0 0 1 0 1.39zM44.463 35.06h-7.721a.696.696 0 0 1 0-1.39h7.72a.696.696 0 0 1 0 1.39zM58.987 39.694h-7.11a.695.695 0 0 1 0-1.39h7.11a.695.695 0 1 1 0 1.39zM48.17 39.694H36.742a.695.695 0 0 1 0-1.39H48.17a.695.695 0 1 1 0 1.39zM58.988 44.329H46.317a.696.696 0 0 1 0-1.39h12.67a.696.696 0 0 1 0 1.39zM42.609 44.329h-5.867a.696.696 0 0 1 0-1.39h5.867a.696.696 0 0 1 0 1.39zM58.987 48.964h-6.182a.695.695 0 0 1 0-1.39h6.182a.695.695 0 1 1 0 1.39zM49.097 48.964H36.742a.695.695 0 0 1 0-1.39h12.355a.695.695 0 1 1 0 1.39zM54.047 53.598H41.682a.695.695 0 1 1 0-1.39h12.365a.695.695 0 1 1 0 1.39z'
}
fill={'#BEA6FF'}
/>
<path
d={
'M23.765 43.04v26.547l16.147-13.273L23.765 43.04zM55.818 56.314l16.146 13.273V43.04L55.818 56.315z'
}
fill={'#333'}
/>
<path
d={'M47.864 62.849l-7.952-6.535-16.147 13.273h48.2L55.816 56.314l-7.953 6.535z'}
fill={'#333'}
/>
<path
d={
'M28.4 50.493v15.284l9.297-7.637-9.297-7.647zM63.196 57.88l8.768 7.212V50.678l-8.768 7.202z'
}
fill={'#333'}
/>
<path
d={
'M23.765 70.282a.67.67 0 0 1-.296-.074.686.686 0 0 1-.399-.62V43.04a.685.685 0 0 1 .399-.62.695.695 0 0 1 .741.083l16.147 13.273a.704.704 0 0 1 0 1.075L24.21 70.115a.686.686 0 0 1-.445.167zm.695-25.768v23.6l14.358-11.8-14.358-11.8z'
}
fill={'#BEA6FF'}
/>
<path
d={'M60.424 60.095l-10.15 8.343-7.08-5.821-8.473 6.97h37.243l-11.54-9.492z'}
fill={'#333'}
/>
<path
d={
'M71.964 70.282a.686.686 0 0 1-.445-.167L55.373 56.861a.703.703 0 0 1 0-1.076l16.174-13.282a.696.696 0 0 1 .742-.084.687.687 0 0 1 .398.621v26.547a.686.686 0 0 1-.398.621.668.668 0 0 1-.325.074zM56.911 56.314l14.358 11.8v-23.6l-14.358 11.8z'
}
fill={'#BEA6FF'}
/>
<path
d={
'M71.964 70.282h-48.2a.704.704 0 0 1-.657-.463.705.705 0 0 1 .213-.77l16.147-13.273a.695.695 0 0 1 .927 0l7.508 6.173 7.508-6.174a.695.695 0 0 1 .927 0L72.483 69.05a.705.705 0 0 1 .213.77.704.704 0 0 1-.732.463zm-46.262-1.371h44.325L55.79 57.212l-7.508 6.173a.695.695 0 0 1-.927 0l-7.508-6.173L25.702 68.91zM46.358 75.36h-3.56a.668.668 0 0 1 0-1.335h3.56a.668.668 0 0 1 0 1.335z'
}
fill={'#BEA6FF'}
/>
<path
d={
'M44.579 77.14a.668.668 0 0 1-.668-.668v-3.559a.668.668 0 0 1 1.335 0v3.56a.667.667 0 0 1-.667.666z'
}
fill={'#BEA6FF'}
/>
</svg>
)
const ForwardRef = forwardRef(SvgComponent)

View File

@@ -1,23 +1,23 @@
import { Ref, SVGProps, forwardRef, memo } from 'react'
const SvgComponent = (props: SVGProps<SVGSVGElement>, ref: Ref<SVGSVGElement>) => (
<svg
fill={'none'}
height={20}
ref={ref}
viewBox={'0 0 24 24'}
width={20}
xmlns={'http://www.w3.org/2000/svg'}
{...props}
>
<path
clipRule={'evenodd'}
d={
'M12 4.5C7 4.5 2.73 7.6 1 12a11.83 11.83 0 0 0 22 0c-1.73-4.39-6-7.5-11-7.5zM12 17a5 5 0 1 1 0-10 5 5 0 0 1 0 10zm0-8a3 3 0 1 0 0 6 3 3 0 0 0 0-6z'
}
fill={'#fff'}
fillRule={'evenodd'}
/>
</svg>
<svg
fill={'none'}
height={20}
ref={ref}
viewBox={'0 0 24 24'}
width={20}
xmlns={'http://www.w3.org/2000/svg'}
{...props}
>
<path
clipRule={'evenodd'}
d={
'M12 4.5C7 4.5 2.73 7.6 1 12a11.83 11.83 0 0 0 22 0c-1.73-4.39-6-7.5-11-7.5zM12 17a5 5 0 1 1 0-10 5 5 0 0 1 0 10zm0-8a3 3 0 1 0 0 6 3 3 0 0 0 0-6z'
}
fill={'#fff'}
fillRule={'evenodd'}
/>
</svg>
)
const ForwardRef = forwardRef(SvgComponent)

View File

@@ -1,28 +1,26 @@
import { Ref, SVGProps, forwardRef, memo } from 'react'
const SvgComponent = (props: SVGProps<SVGSVGElement>, ref: Ref<SVGSVGElement>) => (
<svg
fill={'none'}
height={'16'}
ref={ref}
viewBox={'0 0 16 16'}
width={'16'}
xmlns={'http://www.w3.org/2000/svg'}
{...props}
>
<g clipPath={'url(#clip0_5928_3055)'}>
<path
d={
'M10.2733 11.06L7.21998 8L10.2733 4.94L9.33331 4L5.33331 8L9.33331 12L10.2733 11.06Z'
}
fill={'white'}
/>
</g>
<defs>
<clipPath id={'clip0_5928_3055'}>
<rect fill={'white'} height={'16'} width={'16'} />
</clipPath>
</defs>
</svg>
<svg
fill={'none'}
height={'16'}
ref={ref}
viewBox={'0 0 16 16'}
width={'16'}
xmlns={'http://www.w3.org/2000/svg'}
{...props}
>
<g clipPath={'url(#clip0_5928_3055)'}>
<path
d={'M10.2733 11.06L7.21998 8L10.2733 4.94L9.33331 4L5.33331 8L9.33331 12L10.2733 11.06Z'}
fill={'white'}
/>
</g>
<defs>
<clipPath id={'clip0_5928_3055'}>
<rect fill={'white'} height={'16'} width={'16'} />
</clipPath>
</defs>
</svg>
)
const ForwardRef = forwardRef(SvgComponent)

View File

@@ -1,28 +1,26 @@
import { Ref, SVGProps, forwardRef, memo } from 'react'
const SvgComponent = (props: SVGProps<SVGSVGElement>, ref: Ref<SVGSVGElement>) => (
<svg
fill={'none'}
height={'16'}
ref={ref}
viewBox={'0 0 16 16'}
width={'16'}
xmlns={'http://www.w3.org/2000/svg'}
{...props}
>
<g clipPath={'url(#clip0_5928_3027)'}>
<path
d={
'M5.72665 11.06L8.77999 8L5.72665 4.94L6.66665 4L10.6667 8L6.66665 12L5.72665 11.06Z'
}
fill={'white'}
/>
</g>
<defs>
<clipPath id={'clip0_5928_3027'}>
<rect fill={'white'} height={'16'} width={'16'} />
</clipPath>
</defs>
</svg>
<svg
fill={'none'}
height={'16'}
ref={ref}
viewBox={'0 0 16 16'}
width={'16'}
xmlns={'http://www.w3.org/2000/svg'}
{...props}
>
<g clipPath={'url(#clip0_5928_3027)'}>
<path
d={'M5.72665 11.06L8.77999 8L5.72665 4.94L6.66665 4L10.6667 8L6.66665 12L5.72665 11.06Z'}
fill={'white'}
/>
</g>
<defs>
<clipPath id={'clip0_5928_3027'}>
<rect fill={'white'} height={'16'} width={'16'} />
</clipPath>
</defs>
</svg>
)
const ForwardRef = forwardRef(SvgComponent)

View File

@@ -1,34 +1,34 @@
import { SVGProps } from 'react'
const SvgComponent = (props: SVGProps<SVGSVGElement>) => (
<svg
fill={'none'}
height={36}
viewBox={'0 0 157 36'}
width={157}
xmlns={'http://www.w3.org/2000/svg'}
{...props}
>
<path
d={
'M70.99 24.72a7.34 7.34 0 0 1-3.5-.83 6.43 6.43 0 0 1-2.44-2.33c-.6-1-.89-2.13-.89-3.38s.3-2.37.89-3.36c.6-1 1.41-1.77 2.45-2.32a7.25 7.25 0 0 1 3.5-.85c1.1 0 2.08.19 2.96.57.89.39 1.63.94 2.23 1.66l-1.87 1.74a4.02 4.02 0 0 0-3.17-1.48c-.78 0-1.48.17-2.09.52-.6.34-1.09.81-1.44 1.43-.33.61-.5 1.3-.5 2.1 0 .77.17 1.47.5 2.09.35.6.83 1.1 1.44 1.44.61.34 1.31.5 2.1.5a4 4 0 0 0 3.16-1.5l1.87 1.74c-.6.73-1.34 1.3-2.23 1.68a7.4 7.4 0 0 1-2.97.58zM83.66 24.72c-1.8 0-3.2-.5-4.22-1.5-1-1-1.5-2.43-1.5-4.28v-7.08h2.93v6.97c0 2.26.93 3.4 2.8 3.4.92 0 1.61-.28 2.1-.82.47-.55.71-1.41.71-2.58v-6.97h2.89v7.08c0 1.85-.5 3.28-1.52 4.28-1 1-2.4 1.5-4.2 1.5zM101.5 17.93a3.13 3.13 0 0 1 2.3 3.12c0 1.1-.42 1.96-1.29 2.56-.84.6-2.09.9-3.73.9h-6.52V11.86h6.16c1.53 0 2.7.3 3.53.89a2.8 2.8 0 0 1 1.24 2.4 3.04 3.04 0 0 1-1.68 2.78zm-6.35-3.86v2.97h2.9c.72 0 1.26-.12 1.63-.38.38-.25.56-.62.56-1.11 0-.5-.18-.86-.56-1.1a2.92 2.92 0 0 0-1.63-.38h-2.9zm3.4 8.23c.77 0 1.34-.13 1.73-.38.4-.25.6-.65.6-1.18 0-1.04-.78-1.57-2.33-1.57h-3.4v3.13h3.4zM114.32 21.8h-5.85l-1.12 2.7h-2.99l5.62-12.64h2.88l5.64 12.64h-3.07l-1.11-2.7zm-.92-2.23l-2-4.84-2 4.84h4zM121.78 14.25h-4.03v-2.39h10.98v2.39h-4.03V24.5h-2.92V14.25zM136.23 24.72a7.4 7.4 0 0 1-3.55-.85 6.39 6.39 0 0 1-2.47-2.33 6.5 6.5 0 0 1-.88-3.36 6.34 6.34 0 0 1 3.35-5.69 7.4 7.4 0 0 1 3.55-.84c1.3 0 2.48.28 3.53.85a6.35 6.35 0 0 1 3.37 5.68 6.4 6.4 0 0 1-.9 3.36 6.4 6.4 0 0 1-2.47 2.33 7.29 7.29 0 0 1-3.53.85zm0-2.5a3.69 3.69 0 0 0 3.42-1.95c.35-.6.52-1.3.52-2.09 0-.78-.17-1.48-.52-2.1a3.56 3.56 0 0 0-1.4-1.42 3.94 3.94 0 0 0-2.02-.52 3.94 3.94 0 0 0-3.44 1.95c-.34.61-.5 1.3-.5 2.1 0 .77.16 1.47.5 2.09a3.82 3.82 0 0 0 3.44 1.94zM153.35 24.5l-2.43-3.52H148.24v3.52h-2.92V11.86h5.45c1.12 0 2.09.19 2.9.56.83.38 1.47.9 1.91 1.6.44.68.67 1.49.67 2.43a4.3 4.3 0 0 1-.69 2.44 4.25 4.25 0 0 1-1.9 1.55l2.82 4.06h-3.13zm-.06-8.05c0-.71-.22-1.25-.68-1.63-.45-.38-1.11-.57-1.99-.57h-2.36v4.4h2.37c.88 0 1.54-.2 2-.58a2 2 0 0 0 .68-1.62zM0 11.88h2.91v12.6H0v-12.6zM8.5 14.25H4.49v-2.37h10.97v2.37h-4.03v10.22H8.5V14.25zM33.74 11.88h2.92v12.6h-2.92v-12.6zM51.2 11.88v12.6h-2.39l-6.28-7.65v7.64h-2.88v-12.6h2.41l6.27 7.65v-7.64h2.88z'
}
fill={'#fff'}
/>
<path
clipRule={'evenodd'}
d={'M34.99 6.53a1.96 1.96 0 1 1 0 3.93 1.96 1.96 0 0 1 0-3.93z'}
fill={'#FF0808'}
fillRule={'evenodd'}
/>
<path
clipRule={'evenodd'}
d={
'M19.5 16.04A2 2 0 0 1 21.5 18a2 2 0 0 1-2 1.96A2 2 0 0 1 17.46 18a2 2 0 0 1 2.02-1.96zM42.48 34.5a16.5 16.5 0 1 0 0-33 16.5 16.5 0 0 0 0 33zm0 1.5a18 18 0 1 0 0-36 18 18 0 0 0 0 36z'
}
fill={'#fff'}
fillRule={'evenodd'}
/>
</svg>
<svg
fill={'none'}
height={36}
viewBox={'0 0 157 36'}
width={157}
xmlns={'http://www.w3.org/2000/svg'}
{...props}
>
<path
d={
'M70.99 24.72a7.34 7.34 0 0 1-3.5-.83 6.43 6.43 0 0 1-2.44-2.33c-.6-1-.89-2.13-.89-3.38s.3-2.37.89-3.36c.6-1 1.41-1.77 2.45-2.32a7.25 7.25 0 0 1 3.5-.85c1.1 0 2.08.19 2.96.57.89.39 1.63.94 2.23 1.66l-1.87 1.74a4.02 4.02 0 0 0-3.17-1.48c-.78 0-1.48.17-2.09.52-.6.34-1.09.81-1.44 1.43-.33.61-.5 1.3-.5 2.1 0 .77.17 1.47.5 2.09.35.6.83 1.1 1.44 1.44.61.34 1.31.5 2.1.5a4 4 0 0 0 3.16-1.5l1.87 1.74c-.6.73-1.34 1.3-2.23 1.68a7.4 7.4 0 0 1-2.97.58zM83.66 24.72c-1.8 0-3.2-.5-4.22-1.5-1-1-1.5-2.43-1.5-4.28v-7.08h2.93v6.97c0 2.26.93 3.4 2.8 3.4.92 0 1.61-.28 2.1-.82.47-.55.71-1.41.71-2.58v-6.97h2.89v7.08c0 1.85-.5 3.28-1.52 4.28-1 1-2.4 1.5-4.2 1.5zM101.5 17.93a3.13 3.13 0 0 1 2.3 3.12c0 1.1-.42 1.96-1.29 2.56-.84.6-2.09.9-3.73.9h-6.52V11.86h6.16c1.53 0 2.7.3 3.53.89a2.8 2.8 0 0 1 1.24 2.4 3.04 3.04 0 0 1-1.68 2.78zm-6.35-3.86v2.97h2.9c.72 0 1.26-.12 1.63-.38.38-.25.56-.62.56-1.11 0-.5-.18-.86-.56-1.1a2.92 2.92 0 0 0-1.63-.38h-2.9zm3.4 8.23c.77 0 1.34-.13 1.73-.38.4-.25.6-.65.6-1.18 0-1.04-.78-1.57-2.33-1.57h-3.4v3.13h3.4zM114.32 21.8h-5.85l-1.12 2.7h-2.99l5.62-12.64h2.88l5.64 12.64h-3.07l-1.11-2.7zm-.92-2.23l-2-4.84-2 4.84h4zM121.78 14.25h-4.03v-2.39h10.98v2.39h-4.03V24.5h-2.92V14.25zM136.23 24.72a7.4 7.4 0 0 1-3.55-.85 6.39 6.39 0 0 1-2.47-2.33 6.5 6.5 0 0 1-.88-3.36 6.34 6.34 0 0 1 3.35-5.69 7.4 7.4 0 0 1 3.55-.84c1.3 0 2.48.28 3.53.85a6.35 6.35 0 0 1 3.37 5.68 6.4 6.4 0 0 1-.9 3.36 6.4 6.4 0 0 1-2.47 2.33 7.29 7.29 0 0 1-3.53.85zm0-2.5a3.69 3.69 0 0 0 3.42-1.95c.35-.6.52-1.3.52-2.09 0-.78-.17-1.48-.52-2.1a3.56 3.56 0 0 0-1.4-1.42 3.94 3.94 0 0 0-2.02-.52 3.94 3.94 0 0 0-3.44 1.95c-.34.61-.5 1.3-.5 2.1 0 .77.16 1.47.5 2.09a3.82 3.82 0 0 0 3.44 1.94zM153.35 24.5l-2.43-3.52H148.24v3.52h-2.92V11.86h5.45c1.12 0 2.09.19 2.9.56.83.38 1.47.9 1.91 1.6.44.68.67 1.49.67 2.43a4.3 4.3 0 0 1-.69 2.44 4.25 4.25 0 0 1-1.9 1.55l2.82 4.06h-3.13zm-.06-8.05c0-.71-.22-1.25-.68-1.63-.45-.38-1.11-.57-1.99-.57h-2.36v4.4h2.37c.88 0 1.54-.2 2-.58a2 2 0 0 0 .68-1.62zM0 11.88h2.91v12.6H0v-12.6zM8.5 14.25H4.49v-2.37h10.97v2.37h-4.03v10.22H8.5V14.25zM33.74 11.88h2.92v12.6h-2.92v-12.6zM51.2 11.88v12.6h-2.39l-6.28-7.65v7.64h-2.88v-12.6h2.41l6.27 7.65v-7.64h2.88z'
}
fill={'#fff'}
/>
<path
clipRule={'evenodd'}
d={'M34.99 6.53a1.96 1.96 0 1 1 0 3.93 1.96 1.96 0 0 1 0-3.93z'}
fill={'#FF0808'}
fillRule={'evenodd'}
/>
<path
clipRule={'evenodd'}
d={
'M19.5 16.04A2 2 0 0 1 21.5 18a2 2 0 0 1-2 1.96A2 2 0 0 1 17.46 18a2 2 0 0 1 2.02-1.96zM42.48 34.5a16.5 16.5 0 1 0 0-33 16.5 16.5 0 0 0 0 33zm0 1.5a18 18 0 1 0 0-36 18 18 0 0 0 0 36z'
}
fill={'#fff'}
fillRule={'evenodd'}
/>
</svg>
)
export default SvgComponent

View File

@@ -1,21 +1,23 @@
import { SVGProps, Ref, forwardRef, memo } from "react"
const SvgComponent = (
props: SVGProps<SVGSVGElement>,
ref: Ref<SVGSVGElement>
) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={24}
height={24}
fill="none"
ref={ref}
{...props}
>
<g fill="#000" clipPath="url(#a)">
<path d="M7 6a1 1 0 0 0 0-2H5a1 1 0 0 0-1 1v14a1 1 0 0 0 1 1h2a1 1 0 0 0 0-2H6V6h1Zm13.82 5.42-2.82-4a1 1 0 1 0-1.63 1.16L18.09 11H10a1 1 0 0 0 0 2h8l-1.8 2.4a1 1 0 0 0 1.6 1.2l3-4a1 1 0 0 0 .02-1.18Z" />
</g>
</svg>
import { Ref, SVGProps, forwardRef, memo } from 'react'
const SvgComponent = (props: SVGProps<SVGSVGElement>, ref: Ref<SVGSVGElement>) => (
<svg
fill={'none'}
height={24}
ref={ref}
width={24}
xmlns={'http://www.w3.org/2000/svg'}
{...props}
>
<g clipPath={'url(#a)'} fill={'#000'}>
<path
d={
'M7 6a1 1 0 0 0 0-2H5a1 1 0 0 0-1 1v14a1 1 0 0 0 1 1h2a1 1 0 0 0 0-2H6V6h1Zm13.82 5.42-2.82-4a1 1 0 1 0-1.63 1.16L18.09 11H10a1 1 0 0 0 0 2h8l-1.8 2.4a1 1 0 0 0 1.6 1.2l3-4a1 1 0 0 0 .02-1.18Z'
}
/>
</g>
</svg>
)
const ForwardRef = forwardRef(SvgComponent)
const Memo = memo(ForwardRef)
export default Memo

View File

@@ -1,26 +1,26 @@
import { SVGProps } from 'react'
const SvgComponent = (props: SVGProps<SVGSVGElement>) => (
<svg
fill={'none'}
height={16}
viewBox={'0 0 16 16'}
width={16}
xmlns={'http://www.w3.org/2000/svg'}
{...props}
>
<g clipPath={'url(#clip0_6693_1154)'} fill={'#fff'}>
<path
d={
'M8 7.334a2.667 2.667 0 1 0 0-5.333 2.667 2.667 0 0 0 0 5.334zm0-4a1.333 1.333 0 1 1 0 2.667 1.333 1.333 0 0 1 0-2.666zM8 8.668a4.667 4.667 0 0 0-4.667 4.666.667.667 0 1 0 1.334 0 3.333 3.333 0 0 1 6.666 0 .667.667 0 1 0 1.334 0A4.667 4.667 0 0 0 8 8.668z'
}
/>
</g>
<defs>
<clipPath id={'clip0_6693_1154'}>
<path d={'M0 0h16v16H0z'} fill={'#fff'} />
</clipPath>
</defs>
</svg>
<svg
fill={'none'}
height={16}
viewBox={'0 0 16 16'}
width={16}
xmlns={'http://www.w3.org/2000/svg'}
{...props}
>
<g clipPath={'url(#clip0_6693_1154)'} fill={'#fff'}>
<path
d={
'M8 7.334a2.667 2.667 0 1 0 0-5.333 2.667 2.667 0 0 0 0 5.334zm0-4a1.333 1.333 0 1 1 0 2.667 1.333 1.333 0 0 1 0-2.666zM8 8.668a4.667 4.667 0 0 0-4.667 4.666.667.667 0 1 0 1.334 0 3.333 3.333 0 0 1 6.666 0 .667.667 0 1 0 1.334 0A4.667 4.667 0 0 0 8 8.668z'
}
/>
</g>
<defs>
<clipPath id={'clip0_6693_1154'}>
<path d={'M0 0h16v16H0z'} fill={'#fff'} />
</clipPath>
</defs>
</svg>
)
export default SvgComponent

View File

@@ -1,33 +1,33 @@
import { SVGProps } from 'react'
const SvgComponent = (props: SVGProps<SVGSVGElement>) => (
<svg
fill={'none'}
height={'16'}
viewBox={'0 0 16 16'}
width={'16'}
xmlns={'http://www.w3.org/2000/svg'}
{...props}
>
<g clipPath={'url(#clip0_28366_3435)'}>
<path
d={
'M8.00004 1.33337C6.6815 1.33337 5.39257 1.72437 4.29624 2.45691C3.19991 3.18945 2.34543 4.23064 1.84085 5.44882C1.33626 6.66699 1.20424 8.00744 1.46148 9.30064C1.71871 10.5938 2.35365 11.7817 3.286 12.7141C4.21835 13.6464 5.40624 14.2814 6.69944 14.5386C7.99265 14.7958 9.33309 14.6638 10.5513 14.1592C11.7694 13.6547 12.8106 12.8002 13.5432 11.7038C14.2757 10.6075 14.6667 9.31858 14.6667 8.00004C14.6667 7.12456 14.4943 6.25766 14.1592 5.44882C13.8242 4.63998 13.3331 3.90505 12.7141 3.286C12.095 2.66694 11.3601 2.17588 10.5513 1.84084C9.74243 1.50581 8.87552 1.33337 8.00004 1.33337ZM8.00004 13.3334C6.94521 13.3334 5.91406 13.0206 5.037 12.4345C4.15994 11.8485 3.47635 11.0156 3.07269 10.041C2.66902 9.06648 2.5634 7.99412 2.76919 6.95956C2.97498 5.92499 3.48293 4.97468 4.22881 4.2288C4.97469 3.48292 5.925 2.97497 6.95956 2.76919C7.99413 2.5634 9.06648 2.66902 10.041 3.07268C11.0156 3.47635 11.8485 4.15994 12.4345 5.037C13.0206 5.91406 13.3334 6.94521 13.3334 8.00004C13.3334 9.41453 12.7715 10.7711 11.7713 11.7713C10.7711 12.7715 9.41453 13.3334 8.00004 13.3334Z'
}
fill={'white'}
/>
<path
d={
'M8.22666 4.96667C8.06331 4.81613 7.85932 4.71692 7.64004 4.68136C7.42077 4.6458 7.19588 4.67547 6.99333 4.76667C6.7967 4.84621 6.62825 4.98257 6.5095 5.15832C6.39075 5.33407 6.32709 5.54123 6.32666 5.75334V10.2467C6.32709 10.4588 6.39075 10.6659 6.5095 10.8417C6.62825 11.0174 6.7967 11.1538 6.99333 11.2333C7.13784 11.2989 7.29464 11.333 7.45333 11.3333C7.73927 11.3321 8.01467 11.2252 8.22666 11.0333L10.6667 8.78667C10.7758 8.68674 10.8629 8.56519 10.9226 8.42976C10.9822 8.29433 11.013 8.14798 11.013 8C11.013 7.85203 10.9822 7.70568 10.9226 7.57025C10.8629 7.43482 10.7758 7.31327 10.6667 7.21334L8.22666 4.96667ZM7.66666 9.73334V6.26667L9.53999 8L7.66666 9.73334Z'
}
fill={'white'}
/>
</g>
<defs>
<clipPath id={'clip0_28366_3435'}>
<rect fill={'white'} height={'16'} width={'16'} />
</clipPath>
</defs>
</svg>
<svg
fill={'none'}
height={'16'}
viewBox={'0 0 16 16'}
width={'16'}
xmlns={'http://www.w3.org/2000/svg'}
{...props}
>
<g clipPath={'url(#clip0_28366_3435)'}>
<path
d={
'M8.00004 1.33337C6.6815 1.33337 5.39257 1.72437 4.29624 2.45691C3.19991 3.18945 2.34543 4.23064 1.84085 5.44882C1.33626 6.66699 1.20424 8.00744 1.46148 9.30064C1.71871 10.5938 2.35365 11.7817 3.286 12.7141C4.21835 13.6464 5.40624 14.2814 6.69944 14.5386C7.99265 14.7958 9.33309 14.6638 10.5513 14.1592C11.7694 13.6547 12.8106 12.8002 13.5432 11.7038C14.2757 10.6075 14.6667 9.31858 14.6667 8.00004C14.6667 7.12456 14.4943 6.25766 14.1592 5.44882C13.8242 4.63998 13.3331 3.90505 12.7141 3.286C12.095 2.66694 11.3601 2.17588 10.5513 1.84084C9.74243 1.50581 8.87552 1.33337 8.00004 1.33337ZM8.00004 13.3334C6.94521 13.3334 5.91406 13.0206 5.037 12.4345C4.15994 11.8485 3.47635 11.0156 3.07269 10.041C2.66902 9.06648 2.5634 7.99412 2.76919 6.95956C2.97498 5.92499 3.48293 4.97468 4.22881 4.2288C4.97469 3.48292 5.925 2.97497 6.95956 2.76919C7.99413 2.5634 9.06648 2.66902 10.041 3.07268C11.0156 3.47635 11.8485 4.15994 12.4345 5.037C13.0206 5.91406 13.3334 6.94521 13.3334 8.00004C13.3334 9.41453 12.7715 10.7711 11.7713 11.7713C10.7711 12.7715 9.41453 13.3334 8.00004 13.3334Z'
}
fill={'white'}
/>
<path
d={
'M8.22666 4.96667C8.06331 4.81613 7.85932 4.71692 7.64004 4.68136C7.42077 4.6458 7.19588 4.67547 6.99333 4.76667C6.7967 4.84621 6.62825 4.98257 6.5095 5.15832C6.39075 5.33407 6.32709 5.54123 6.32666 5.75334V10.2467C6.32709 10.4588 6.39075 10.6659 6.5095 10.8417C6.62825 11.0174 6.7967 11.1538 6.99333 11.2333C7.13784 11.2989 7.29464 11.333 7.45333 11.3333C7.73927 11.3321 8.01467 11.2252 8.22666 11.0333L10.6667 8.78667C10.7758 8.68674 10.8629 8.56519 10.9226 8.42976C10.9822 8.29433 11.013 8.14798 11.013 8C11.013 7.85203 10.9822 7.70568 10.9226 7.57025C10.8629 7.43482 10.7758 7.31327 10.6667 7.21334L8.22666 4.96667ZM7.66666 9.73334V6.26667L9.53999 8L7.66666 9.73334Z'
}
fill={'white'}
/>
</g>
<defs>
<clipPath id={'clip0_28366_3435'}>
<rect fill={'white'} height={'16'} width={'16'} />
</clipPath>
</defs>
</svg>
)
export default SvgComponent

View File

@@ -1,28 +1,28 @@
import { Ref, SVGProps, forwardRef, memo } from 'react'
const SvgComponent = (props: SVGProps<SVGSVGElement>, ref: Ref<SVGSVGElement>) => (
<svg
fill={'none'}
height={20}
ref={ref}
viewBox={'0 0 20 20'}
width={20}
xmlns={'http://www.w3.org/2000/svg'}
{...props}
>
<g clipPath={'url(#clip0_21547_180)'}>
<path
d={
'M17.2583 16.075L14.425 13.25C15.3392 12.0854 15.8352 10.6472 15.8333 9.16667C15.8333 7.84813 15.4423 6.5592 14.7098 5.46287C13.9773 4.36654 12.9361 3.51206 11.7179 3.00747C10.4997 2.50289 9.15927 2.37087 7.86607 2.6281C6.57286 2.88534 5.38497 3.52027 4.45262 4.45262C3.52027 5.38497 2.88534 6.57286 2.6281 7.86607C2.37087 9.15927 2.50289 10.4997 3.00747 11.7179C3.51206 12.9361 4.36654 13.9773 5.46287 14.7098C6.5592 15.4423 7.84813 15.8333 9.16667 15.8333C10.6472 15.8352 12.0854 15.3392 13.25 14.425L16.075 17.2583C16.1525 17.3364 16.2446 17.3984 16.3462 17.4407C16.4477 17.4831 16.5567 17.5048 16.6667 17.5048C16.7767 17.5048 16.8856 17.4831 16.9871 17.4407C17.0887 17.3984 17.1809 17.3364 17.2583 17.2583C17.3364 17.1809 17.3984 17.0887 17.4407 16.9871C17.4831 16.8856 17.5048 16.7767 17.5048 16.6667C17.5048 16.5567 17.4831 16.4477 17.4407 16.3462C17.3984 16.2446 17.3364 16.1525 17.2583 16.075ZM4.16667 9.16667C4.16667 8.17776 4.45991 7.21106 5.00932 6.38882C5.55873 5.56657 6.33962 4.92571 7.25325 4.54727C8.16688 4.16883 9.17222 4.06982 10.1421 4.26274C11.112 4.45567 12.0029 4.93187 12.7022 5.63114C13.4015 6.3304 13.8777 7.22131 14.0706 8.19122C14.2635 9.16112 14.1645 10.1665 13.7861 11.0801C13.4076 11.9937 12.7668 12.7746 11.9445 13.324C11.1223 13.8734 10.1556 14.1667 9.16667 14.1667C7.84059 14.1667 6.56882 13.6399 5.63114 12.7022C4.69345 11.7645 4.16667 10.4928 4.16667 9.16667Z'
}
fill={'#808080'}
/>
</g>
<defs>
<clipPath id={'clip0_21547_180'}>
<rect fill={'white'} height={20} width={20} />
</clipPath>
</defs>
</svg>
<svg
fill={'none'}
height={20}
ref={ref}
viewBox={'0 0 20 20'}
width={20}
xmlns={'http://www.w3.org/2000/svg'}
{...props}
>
<g clipPath={'url(#clip0_21547_180)'}>
<path
d={
'M17.2583 16.075L14.425 13.25C15.3392 12.0854 15.8352 10.6472 15.8333 9.16667C15.8333 7.84813 15.4423 6.5592 14.7098 5.46287C13.9773 4.36654 12.9361 3.51206 11.7179 3.00747C10.4997 2.50289 9.15927 2.37087 7.86607 2.6281C6.57286 2.88534 5.38497 3.52027 4.45262 4.45262C3.52027 5.38497 2.88534 6.57286 2.6281 7.86607C2.37087 9.15927 2.50289 10.4997 3.00747 11.7179C3.51206 12.9361 4.36654 13.9773 5.46287 14.7098C6.5592 15.4423 7.84813 15.8333 9.16667 15.8333C10.6472 15.8352 12.0854 15.3392 13.25 14.425L16.075 17.2583C16.1525 17.3364 16.2446 17.3984 16.3462 17.4407C16.4477 17.4831 16.5567 17.5048 16.6667 17.5048C16.7767 17.5048 16.8856 17.4831 16.9871 17.4407C17.0887 17.3984 17.1809 17.3364 17.2583 17.2583C17.3364 17.1809 17.3984 17.0887 17.4407 16.9871C17.4831 16.8856 17.5048 16.7767 17.5048 16.6667C17.5048 16.5567 17.4831 16.4477 17.4407 16.3462C17.3984 16.2446 17.3364 16.1525 17.2583 16.075ZM4.16667 9.16667C4.16667 8.17776 4.45991 7.21106 5.00932 6.38882C5.55873 5.56657 6.33962 4.92571 7.25325 4.54727C8.16688 4.16883 9.17222 4.06982 10.1421 4.26274C11.112 4.45567 12.0029 4.93187 12.7022 5.63114C13.4015 6.3304 13.8777 7.22131 14.0706 8.19122C14.2635 9.16112 14.1645 10.1665 13.7861 11.0801C13.4076 11.9937 12.7668 12.7746 11.9445 13.324C11.1223 13.8734 10.1556 14.1667 9.16667 14.1667C7.84059 14.1667 6.56882 13.6399 5.63114 12.7022C4.69345 11.7645 4.16667 10.4928 4.16667 9.16667Z'
}
fill={'#808080'}
/>
</g>
<defs>
<clipPath id={'clip0_21547_180'}>
<rect fill={'white'} height={20} width={20} />
</clipPath>
</defs>
</svg>
)
export default memo(forwardRef(SvgComponent))

View File

@@ -1,27 +1,27 @@
import { SVGProps } from 'react'
const SvgComponent = (props: SVGProps<SVGSVGElement>) => (
<svg
fill={'none'}
height={'16'}
viewBox={'0 0 16 16'}
width={'16'}
xmlns={'http://www.w3.org/2000/svg'}
{...props}
>
<g clipPath={'url(#clip0_28366_3245)'}>
<path
d={
'M14 3.99998H10.6667V2.88665C10.6511 2.45986 10.4668 2.05669 10.1544 1.76551C9.842 1.47433 9.42687 1.31891 9.00004 1.33332H7.00004C6.57321 1.31891 6.15808 1.47433 5.84566 1.76551C5.53324 2.05669 5.34901 2.45986 5.33337 2.88665V3.99998H2.00004C1.82323 3.99998 1.65366 4.07022 1.52864 4.19525C1.40361 4.32027 1.33337 4.48984 1.33337 4.66665C1.33337 4.84346 1.40361 5.01303 1.52864 5.13805C1.65366 5.26308 1.82323 5.33332 2.00004 5.33332H2.66671V12.6666C2.66671 13.1971 2.87742 13.7058 3.25249 14.0809C3.62757 14.4559 4.13627 14.6666 4.66671 14.6666H11.3334C11.8638 14.6666 12.3725 14.4559 12.7476 14.0809C13.1227 13.7058 13.3334 13.1971 13.3334 12.6666V5.33332H14C14.1769 5.33332 14.3464 5.26308 14.4714 5.13805C14.5965 5.01303 14.6667 4.84346 14.6667 4.66665C14.6667 4.48984 14.5965 4.32027 14.4714 4.19525C14.3464 4.07022 14.1769 3.99998 14 3.99998ZM6.66671 2.88665C6.66671 2.77998 6.80671 2.66665 7.00004 2.66665H9.00004C9.19337 2.66665 9.33337 2.77998 9.33337 2.88665V3.99998H6.66671V2.88665ZM12 12.6666C12 12.8435 11.9298 13.013 11.8048 13.1381C11.6798 13.2631 11.5102 13.3333 11.3334 13.3333H4.66671C4.4899 13.3333 4.32033 13.2631 4.1953 13.1381C4.07028 13.013 4.00004 12.8435 4.00004 12.6666V5.33332H12V12.6666Z'
}
fill={'white'}
/>
</g>
<defs>
<clipPath id={'clip0_28366_3245'}>
<rect fill={'white'} height={'16'} width={'16'} />
</clipPath>
</defs>
</svg>
<svg
fill={'none'}
height={'16'}
viewBox={'0 0 16 16'}
width={'16'}
xmlns={'http://www.w3.org/2000/svg'}
{...props}
>
<g clipPath={'url(#clip0_28366_3245)'}>
<path
d={
'M14 3.99998H10.6667V2.88665C10.6511 2.45986 10.4668 2.05669 10.1544 1.76551C9.842 1.47433 9.42687 1.31891 9.00004 1.33332H7.00004C6.57321 1.31891 6.15808 1.47433 5.84566 1.76551C5.53324 2.05669 5.34901 2.45986 5.33337 2.88665V3.99998H2.00004C1.82323 3.99998 1.65366 4.07022 1.52864 4.19525C1.40361 4.32027 1.33337 4.48984 1.33337 4.66665C1.33337 4.84346 1.40361 5.01303 1.52864 5.13805C1.65366 5.26308 1.82323 5.33332 2.00004 5.33332H2.66671V12.6666C2.66671 13.1971 2.87742 13.7058 3.25249 14.0809C3.62757 14.4559 4.13627 14.6666 4.66671 14.6666H11.3334C11.8638 14.6666 12.3725 14.4559 12.7476 14.0809C13.1227 13.7058 13.3334 13.1971 13.3334 12.6666V5.33332H14C14.1769 5.33332 14.3464 5.26308 14.4714 5.13805C14.5965 5.01303 14.6667 4.84346 14.6667 4.66665C14.6667 4.48984 14.5965 4.32027 14.4714 4.19525C14.3464 4.07022 14.1769 3.99998 14 3.99998ZM6.66671 2.88665C6.66671 2.77998 6.80671 2.66665 7.00004 2.66665H9.00004C9.19337 2.66665 9.33337 2.77998 9.33337 2.88665V3.99998H6.66671V2.88665ZM12 12.6666C12 12.8435 11.9298 13.013 11.8048 13.1381C11.6798 13.2631 11.5102 13.3333 11.3334 13.3333H4.66671C4.4899 13.3333 4.32033 13.2631 4.1953 13.1381C4.07028 13.013 4.00004 12.8435 4.00004 12.6666V5.33332H12V12.6666Z'
}
fill={'white'}
/>
</g>
<defs>
<clipPath id={'clip0_28366_3245'}>
<rect fill={'white'} height={'16'} width={'16'} />
</clipPath>
</defs>
</svg>
)
export default SvgComponent

View File

@@ -1,21 +1,21 @@
import { Ref, SVGProps, forwardRef, memo } from 'react'
const SvgComponent = (props: SVGProps<SVGSVGElement>, ref: Ref<SVGSVGElement>) => (
<svg
fill={'none'}
height={20}
ref={ref}
viewBox={'0 0 24 24'}
width={20}
xmlns={'http://www.w3.org/2000/svg'}
{...props}
>
<path
d={
'M12 5.975a9.77 9.77 0 0 1 8.82 5.5 9.647 9.647 0 0 1-2.41 3.12l1.41 1.41c1.39-1.23 2.49-2.77 3.18-4.53-1.73-4.39-6-7.5-11-7.5-1.27 0-2.49.2-3.64.57l1.65 1.65c.65-.13 1.31-.22 1.99-.22zm-1.07 1.14L13 9.185c.57.25 1.03.71 1.28 1.28l2.07 2.07c.08-.34.14-.7.14-1.07.01-2.48-2.01-4.49-4.49-4.49-.37 0-.72.05-1.07.14zm-8.92-3.27l2.68 2.68A11.738 11.738 0 0 0 1 11.475c1.73 4.39 6 7.5 11 7.5 1.52 0 2.98-.29 4.32-.82l3.42 3.42 1.41-1.41L3.42 2.425l-1.41 1.42zm7.5 7.5l2.61 2.61c-.04.01-.08.02-.12.02a2.5 2.5 0 0 1-2.5-2.5c0-.05.01-.08.01-.13zm-3.4-3.4l1.75 1.75a4.6 4.6 0 0 0-.36 1.78 4.507 4.507 0 0 0 6.27 4.14l.98.98c-.88.24-1.8.38-2.75.38a9.77 9.77 0 0 1-8.82-5.5c.7-1.43 1.72-2.61 2.93-3.53z'
}
fill={'#fff'}
/>
</svg>
<svg
fill={'none'}
height={20}
ref={ref}
viewBox={'0 0 24 24'}
width={20}
xmlns={'http://www.w3.org/2000/svg'}
{...props}
>
<path
d={
'M12 5.975a9.77 9.77 0 0 1 8.82 5.5 9.647 9.647 0 0 1-2.41 3.12l1.41 1.41c1.39-1.23 2.49-2.77 3.18-4.53-1.73-4.39-6-7.5-11-7.5-1.27 0-2.49.2-3.64.57l1.65 1.65c.65-.13 1.31-.22 1.99-.22zm-1.07 1.14L13 9.185c.57.25 1.03.71 1.28 1.28l2.07 2.07c.08-.34.14-.7.14-1.07.01-2.48-2.01-4.49-4.49-4.49-.37 0-.72.05-1.07.14zm-8.92-3.27l2.68 2.68A11.738 11.738 0 0 0 1 11.475c1.73 4.39 6 7.5 11 7.5 1.52 0 2.98-.29 4.32-.82l3.42 3.42 1.41-1.41L3.42 2.425l-1.41 1.42zm7.5 7.5l2.61 2.61c-.04.01-.08.02-.12.02a2.5 2.5 0 0 1-2.5-2.5c0-.05.01-.08.01-.13zm-3.4-3.4l1.75 1.75a4.6 4.6 0 0 0-.36 1.78 4.507 4.507 0 0 0 6.27 4.14l.98.98c-.88.24-1.8.38-2.75.38a9.77 9.77 0 0 1-8.82-5.5c.7-1.43 1.72-2.61 2.93-3.53z'
}
fill={'#fff'}
/>
</svg>
)
const ForwardRef = forwardRef(SvgComponent)

View File

@@ -3,16 +3,16 @@ import type { Meta, StoryObj } from '@storybook/react'
import { CheckEmail } from './'
const meta = {
component: CheckEmail,
tags: ['autodocs'],
title: 'Auth/Check email',
component: CheckEmail,
tags: ['autodocs'],
title: 'Auth/Check email',
} satisfies Meta<typeof CheckEmail>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {
email: 'your_email@domain.com',
},
args: {
email: 'your_email@domain.com',
},
}

View File

@@ -6,26 +6,26 @@ import { Button, Card, Typography } from '../../ui'
import s from './check-email.module.scss'
type Props = {
email: string
email: string
}
export const CheckEmail = ({ email }: Props) => {
const message = `We've sent an e-mail with instructions to ${email}`
const message = `We've sent an e-mail with instructions to ${email}`
return (
<Card className={s.card}>
<Typography className={s.title} variant={'large'}>
Check your email
</Typography>
<div className={s.iconContainer}>
<Email />
</div>
<Typography className={s.instructions} variant={'body2'}>
{message}
</Typography>
<Button as={Link} fullWidth to={'/sing-in'}>
Back to Sign in
</Button>
</Card>
)
return (
<Card className={s.card}>
<Typography className={s.title} variant={'large'}>
Check your email
</Typography>
<div className={s.iconContainer}>
<Email />
</div>
<Typography className={s.instructions} variant={'body2'}>
{message}
</Typography>
<Button as={Link} fullWidth to={'/sing-in'}>
Back to Sign in
</Button>
</Card>
)
}

View File

@@ -3,16 +3,16 @@ import type { Meta, StoryObj } from '@storybook/react'
import { NewPassword } from './'
const meta = {
component: NewPassword,
tags: ['autodocs'],
title: 'Auth/New password',
component: NewPassword,
tags: ['autodocs'],
title: 'Auth/New password',
} satisfies Meta<typeof NewPassword>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {
onSubmit: data => console.info(data),
},
args: {
onSubmit: data => console.info(data),
},
}

View File

@@ -8,48 +8,48 @@ import { z } from 'zod'
import s from './new-password.module.scss'
const schema = z.object({
password: z.string().nonempty('Enter password'),
password: z.string().nonempty('Enter password'),
})
type FormType = z.infer<typeof schema>
type Props = {
onSubmit: (data: FormType) => void
onSubmit: (data: FormType) => void
}
export const NewPassword = (props: Props) => {
const { control, handleSubmit } = useForm<FormType>({
defaultValues: {
password: '',
},
resolver: zodResolver(schema),
})
const { control, handleSubmit } = useForm<FormType>({
defaultValues: {
password: '',
},
resolver: zodResolver(schema),
})
const handleFormSubmitted = handleSubmit(props.onSubmit)
const handleFormSubmitted = handleSubmit(props.onSubmit)
return (
<>
<DevTool control={control} />
<Card className={s.card}>
<Typography className={s.title} variant={'large'}>
Create new password
</Typography>
<form onSubmit={handleFormSubmitted}>
<ControlledTextField
containerProps={{ className: s.input }}
control={control}
name={'password'}
placeholder={'Password'}
type={'password'}
/>
<Typography className={s.instructions} variant={'caption'}>
Create new password and we will send you further instructions to email
</Typography>
<Button fullWidth type={'submit'}>
Create new password
</Button>
</form>
</Card>
</>
)
return (
<>
<DevTool control={control} />
<Card className={s.card}>
<Typography className={s.title} variant={'large'}>
Create new password
</Typography>
<form onSubmit={handleFormSubmitted}>
<ControlledTextField
containerProps={{ className: s.input }}
control={control}
name={'password'}
placeholder={'Password'}
type={'password'}
/>
<Typography className={s.instructions} variant={'caption'}>
Create new password and we will send you further instructions to email
</Typography>
<Button fullWidth type={'submit'}>
Create new password
</Button>
</form>
</Card>
</>
)
}

View File

@@ -3,16 +3,16 @@ import type { Meta, StoryObj } from '@storybook/react'
import { RecoverPassword } from './'
const meta = {
component: RecoverPassword,
tags: ['autodocs'],
title: 'Auth/Recover password',
component: RecoverPassword,
tags: ['autodocs'],
title: 'Auth/Recover password',
} satisfies Meta<typeof RecoverPassword>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {
onSubmit: data => console.info(data),
},
args: {
onSubmit: data => console.info(data),
},
}

View File

@@ -9,55 +9,51 @@ import { z } from 'zod'
import s from './recover-password.module.scss'
const schema = z.object({
email: z.string().email('Invalid email address').nonempty('Enter email'),
email: z.string().email('Invalid email address').nonempty('Enter email'),
})
type FormType = z.infer<typeof schema>
type Props = {
onSubmit: (data: FormType) => void
onSubmit: (data: FormType) => void
}
export const RecoverPassword = (props: Props) => {
const { control, handleSubmit } = useForm<FormType>({
defaultValues: {
email: '',
},
mode: 'onSubmit',
resolver: zodResolver(schema),
})
const { control, handleSubmit } = useForm<FormType>({
defaultValues: {
email: '',
},
mode: 'onSubmit',
resolver: zodResolver(schema),
})
const handleFormSubmitted = handleSubmit(props.onSubmit)
const handleFormSubmitted = handleSubmit(props.onSubmit)
return (
<>
<DevTool control={control} />
<Card className={s.card}>
<Typography className={s.title} variant={'large'}>
Forgot your password?
</Typography>
<form onSubmit={handleFormSubmitted}>
<div className={s.form}>
<ControlledTextField
control={control}
name={'email'}
placeholder={'Email'}
/>
</div>
<Typography className={s.instructions} variant={'body2'}>
Enter your email address and we will send you further instructions
</Typography>
<Button className={s.button} fullWidth type={'submit'}>
Send Instructions
</Button>
</form>
<Typography className={s.caption} variant={'body2'}>
Did you remember your password?
</Typography>
<Typography as={Link} className={s.loginLink} to={'/sign-in'} variant={'link1'}>
Try logging in
</Typography>
</Card>
</>
)
return (
<>
<DevTool control={control} />
<Card className={s.card}>
<Typography className={s.title} variant={'large'}>
Forgot your password?
</Typography>
<form onSubmit={handleFormSubmitted}>
<div className={s.form}>
<ControlledTextField control={control} name={'email'} placeholder={'Email'} />
</div>
<Typography className={s.instructions} variant={'body2'}>
Enter your email address and we will send you further instructions
</Typography>
<Button className={s.button} fullWidth type={'submit'}>
Send Instructions
</Button>
</form>
<Typography className={s.caption} variant={'body2'}>
Did you remember your password?
</Typography>
<Typography as={Link} className={s.loginLink} to={'/sign-in'} variant={'link1'}>
Try logging in
</Typography>
</Card>
</>
)
}

View File

@@ -3,16 +3,16 @@ import type { Meta, StoryObj } from '@storybook/react'
import { SignIn } from './'
const meta = {
component: SignIn,
tags: ['autodocs'],
title: 'Auth/Sign in',
component: SignIn,
tags: ['autodocs'],
title: 'Auth/Sign in',
} satisfies Meta<typeof SignIn>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {
onSubmit: data => console.info(data),
},
args: {
onSubmit: data => console.info(data),
},
}

View File

@@ -9,79 +9,79 @@ import { z } from 'zod'
import s from './sign-in.module.scss'
const schema = z.object({
email: z.string().email('Invalid email address').nonempty('Enter email'),
password: z.string().nonempty('Enter password'),
rememberMe: z.boolean().optional(),
email: z.string().email('Invalid email address').nonempty('Enter email'),
password: z.string().nonempty('Enter password'),
rememberMe: z.boolean().optional(),
})
type FormType = z.infer<typeof schema>
type Props = {
onSubmit: (data: FormType) => void
onSubmit: (data: FormType) => void
}
export const SignIn = (props: Props) => {
const { control, handleSubmit } = useForm<FormType>({
defaultValues: {
email: '',
password: '',
rememberMe: false,
},
mode: 'onSubmit',
resolver: zodResolver(schema),
})
const { control, handleSubmit } = useForm<FormType>({
defaultValues: {
email: '',
password: '',
rememberMe: false,
},
mode: 'onSubmit',
resolver: zodResolver(schema),
})
const handleFormSubmitted = handleSubmit(props.onSubmit)
const handleFormSubmitted = handleSubmit(props.onSubmit)
return (
<>
<DevTool control={control} />
<Card className={s.card}>
<Typography className={s.title} variant={'large'}>
Sign In
</Typography>
<form onSubmit={handleFormSubmitted}>
<div className={s.form}>
<ControlledTextField
control={control}
label={'Email'}
name={'email'}
placeholder={'Email'}
/>
<ControlledTextField
control={control}
label={'Password'}
name={'password'}
placeholder={'Password'}
type={'password'}
/>
</div>
<ControlledCheckbox
className={s.checkbox}
control={control}
label={'Remember me'}
name={'rememberMe'}
position={'left'}
/>
<Typography
as={Link}
className={s.recoverPasswordLink}
to={'/recover-password'}
variant={'body2'}
>
Forgot Password?
</Typography>
<Button className={s.button} fullWidth type={'submit'}>
Sign In
</Button>
</form>
<Typography className={s.caption} variant={'body2'}>
{`Don't have an account?`}
</Typography>
<Typography as={Link} className={s.signUpLink} to={'/sign-up'} variant={'link1'}>
Sign Up
</Typography>
</Card>
</>
)
return (
<>
<DevTool control={control} />
<Card className={s.card}>
<Typography className={s.title} variant={'large'}>
Sign In
</Typography>
<form onSubmit={handleFormSubmitted}>
<div className={s.form}>
<ControlledTextField
control={control}
label={'Email'}
name={'email'}
placeholder={'Email'}
/>
<ControlledTextField
control={control}
label={'Password'}
name={'password'}
placeholder={'Password'}
type={'password'}
/>
</div>
<ControlledCheckbox
className={s.checkbox}
control={control}
label={'Remember me'}
name={'rememberMe'}
position={'left'}
/>
<Typography
as={Link}
className={s.recoverPasswordLink}
to={'/recover-password'}
variant={'body2'}
>
Forgot Password?
</Typography>
<Button className={s.button} fullWidth type={'submit'}>
Sign In
</Button>
</form>
<Typography className={s.caption} variant={'body2'}>
{`Don't have an account?`}
</Typography>
<Typography as={Link} className={s.signUpLink} to={'/sign-up'} variant={'link1'}>
Sign Up
</Typography>
</Card>
</>
)
}

View File

@@ -3,16 +3,16 @@ import type { Meta, StoryObj } from '@storybook/react'
import { SignUp } from './'
const meta = {
component: SignUp,
tags: ['autodocs'],
title: 'Auth/Sign up',
component: SignUp,
tags: ['autodocs'],
title: 'Auth/Sign up',
} satisfies Meta<typeof SignUp>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {
onSubmit: data => console.info(data),
},
args: {
onSubmit: data => console.info(data),
},
}

View File

@@ -10,86 +10,86 @@ import { z } from 'zod'
import s from './sign-up.module.scss'
const schema = z
.object({
email: z.string().email('Invalid email address').nonempty('Enter email'),
password: z.string().nonempty('Enter password'),
passwordConfirmation: z.string().nonempty('Confirm your password'),
})
.superRefine((data, ctx) => {
if (data.password !== data.passwordConfirmation) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Passwords do not match',
path: ['passwordConfirmation'],
})
}
.object({
email: z.string().email('Invalid email address').nonempty('Enter email'),
password: z.string().nonempty('Enter password'),
passwordConfirmation: z.string().nonempty('Confirm your password'),
})
.superRefine((data, ctx) => {
if (data.password !== data.passwordConfirmation) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Passwords do not match',
path: ['passwordConfirmation'],
})
}
return data
})
return data
})
type FormType = z.infer<typeof schema>
type Props = {
onSubmit: (data: Omit<FormType, 'passwordConfirmation'>) => void
onSubmit: (data: Omit<FormType, 'passwordConfirmation'>) => void
}
export const SignUp = (props: Props) => {
const { control, handleSubmit } = useForm<FormType>({
defaultValues: {
email: '',
password: '',
passwordConfirmation: '',
},
mode: 'onSubmit',
resolver: zodResolver(schema),
})
const { control, handleSubmit } = useForm<FormType>({
defaultValues: {
email: '',
password: '',
passwordConfirmation: '',
},
mode: 'onSubmit',
resolver: zodResolver(schema),
})
const handleFormSubmitted = handleSubmit(data =>
props.onSubmit(omit(data, ['passwordConfirmation']))
)
const handleFormSubmitted = handleSubmit(data =>
props.onSubmit(omit(data, ['passwordConfirmation']))
)
return (
<>
<DevTool control={control} />
<Card className={s.card}>
<Typography className={s.title} variant={'large'}>
Sign Up
</Typography>
<form onSubmit={handleFormSubmitted}>
<div className={s.form}>
<ControlledTextField
control={control}
label={'Email'}
name={'email'}
placeholder={'Email'}
/>
<ControlledTextField
control={control}
label={'Password'}
name={'password'}
placeholder={'Password'}
type={'password'}
/>
<ControlledTextField
control={control}
label={'Confirm password'}
name={'passwordConfirmation'}
placeholder={'Confirm password'}
type={'password'}
/>
</div>
<Button className={s.button} fullWidth type={'submit'}>
Sign Up
</Button>
</form>
{/* eslint-disable-next-line react/no-unescaped-entities */}
<Typography className={s.caption} variant={'body2'}>
Already have an account?
</Typography>
<Typography as={Link} className={s.signInLink} to={'/sign-in'} variant={'link1'}>
Sign In
</Typography>
</Card>
</>
)
return (
<>
<DevTool control={control} />
<Card className={s.card}>
<Typography className={s.title} variant={'large'}>
Sign Up
</Typography>
<form onSubmit={handleFormSubmitted}>
<div className={s.form}>
<ControlledTextField
control={control}
label={'Email'}
name={'email'}
placeholder={'Email'}
/>
<ControlledTextField
control={control}
label={'Password'}
name={'password'}
placeholder={'Password'}
type={'password'}
/>
<ControlledTextField
control={control}
label={'Confirm password'}
name={'passwordConfirmation'}
placeholder={'Confirm password'}
type={'password'}
/>
</div>
<Button className={s.button} fullWidth type={'submit'}>
Sign Up
</Button>
</form>
{/* eslint-disable-next-line react/no-unescaped-entities */}
<Typography className={s.caption} variant={'body2'}>
Already have an account?
</Typography>
<Typography as={Link} className={s.signInLink} to={'/sign-in'} variant={'link1'}>
Sign In
</Typography>
</Card>
</>
)
}

View File

@@ -3,45 +3,45 @@ import { Card } from '@/services/decks'
import { formatDate } from '@/utils'
const columns: Column[] = [
{
key: 'question',
sortable: true,
title: 'Question',
},
{
key: 'answer',
sortable: true,
title: 'Answer',
},
{
key: 'updated',
sortable: true,
title: 'Last Updated',
},
{
key: 'grade',
sortable: true,
title: 'Grade',
},
{
key: 'question',
sortable: true,
title: 'Question',
},
{
key: 'answer',
sortable: true,
title: 'Answer',
},
{
key: 'updated',
sortable: true,
title: 'Last Updated',
},
{
key: 'grade',
sortable: true,
title: 'Grade',
},
]
type Props = {
cards: Card[] | undefined
cards: Card[] | undefined
}
export const CardsTable = ({ cards }: Props) => {
return (
<Table>
<TableHeader columns={columns} />
<TableBody>
{cards?.map(card => (
<TableRow key={card.id}>
<TableCell>{card.question}</TableCell>
<TableCell>{card.answer}</TableCell>
<TableCell>{formatDate(card.updated)}</TableCell>
<TableCell>{card.grade}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)
return (
<Table>
<TableHeader columns={columns} />
<TableBody>
{cards?.map(card => (
<TableRow key={card.id}>
<TableCell>{card.question}</TableCell>
<TableCell>{card.answer}</TableCell>
<TableCell>{formatDate(card.updated)}</TableCell>
<TableCell>{card.grade}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)
}

View File

@@ -5,68 +5,68 @@ import { Button } from '@/components'
import { Meta, StoryObj } from '@storybook/react'
const meta = {
component: DeckDialog,
tags: ['autodocs'],
title: 'Decks/Deck Dialog',
component: DeckDialog,
tags: ['autodocs'],
title: 'Decks/Deck Dialog',
} satisfies Meta<typeof DeckDialog>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {
onOpenChange: () => {},
open: true,
},
render: args => {
const [open, setOpen] = useState(false)
const closeModal = () => setOpen(false)
args: {
onOpenChange: () => {},
open: true,
},
render: args => {
const [open, setOpen] = useState(false)
const closeModal = () => setOpen(false)
return (
<>
<Button onClick={() => setOpen(true)}>Open Modal</Button>
<DeckDialog
{...args}
onCancel={closeModal}
onConfirm={data => {
console.log(data)
closeModal()
}}
onOpenChange={setOpen}
open={open}
/>
</>
)
},
return (
<>
<Button onClick={() => setOpen(true)}>Open Modal</Button>
<DeckDialog
{...args}
onCancel={closeModal}
onConfirm={data => {
console.log(data)
closeModal()
}}
onOpenChange={setOpen}
open={open}
/>
</>
)
},
}
export const WithDefaultValues: Story = {
args: {
onOpenChange: () => {},
open: true,
},
render: args => {
const [open, setOpen] = useState(false)
const closeModal = () => setOpen(false)
args: {
onOpenChange: () => {},
open: true,
},
render: args => {
const [open, setOpen] = useState(false)
const closeModal = () => setOpen(false)
return (
<>
<Button onClick={() => setOpen(true)}>Open Modal</Button>
<DeckDialog
{...args}
defaultValues={{
isPrivate: true,
name: 'some name',
}}
onCancel={closeModal}
onConfirm={data => {
console.log(data)
closeModal()
}}
onOpenChange={setOpen}
open={open}
/>
</>
)
},
return (
<>
<Button onClick={() => setOpen(true)}>Open Modal</Button>
<DeckDialog
{...args}
defaultValues={{
isPrivate: true,
name: 'some name',
}}
onCancel={closeModal}
onConfirm={data => {
console.log(data)
closeModal()
}}
onOpenChange={setOpen}
open={open}
/>
</>
)
},
}

View File

@@ -7,52 +7,47 @@ import { z } from 'zod'
import s from './deck-dialog.module.scss'
const newDeckSchema = z.object({
isPrivate: z.boolean(),
name: z.string().min(3).max(50),
isPrivate: z.boolean(),
name: z.string().min(3).max(50),
})
type FormValues = z.infer<typeof newDeckSchema>
type Props = Pick<DialogProps, 'onCancel' | 'onOpenChange' | 'open'> & {
defaultValues?: FormValues
onConfirm: (data: FormValues) => void
defaultValues?: FormValues
onConfirm: (data: FormValues) => void
}
export const DeckDialog = ({
defaultValues = { isPrivate: false, name: '' },
onCancel,
onConfirm,
...dialogProps
defaultValues = { isPrivate: false, name: '' },
onCancel,
onConfirm,
...dialogProps
}: Props) => {
const { control, handleSubmit, reset } = useForm<FormValues>({
defaultValues,
resolver: zodResolver(newDeckSchema),
})
const onSubmit = handleSubmit(data => {
onConfirm(data)
dialogProps.onOpenChange?.(false)
reset()
})
const handleCancel = () => {
reset()
onCancel?.()
}
const { control, handleSubmit, reset } = useForm<FormValues>({
defaultValues,
resolver: zodResolver(newDeckSchema),
})
const onSubmit = handleSubmit(data => {
onConfirm(data)
dialogProps.onOpenChange?.(false)
reset()
})
const handleCancel = () => {
reset()
onCancel?.()
}
return (
<Dialog
{...dialogProps}
onCancel={handleCancel}
onConfirm={onSubmit}
title={'Create new deck'}
>
<form className={s.content} onSubmit={onSubmit}>
<ControlledTextField control={control} label={'Deck name'} name={'name'} />
<ControlledCheckbox
control={control}
label={'Private'}
name={'isPrivate'}
position={'left'}
/>
</form>
</Dialog>
)
return (
<Dialog {...dialogProps} onCancel={handleCancel} onConfirm={onSubmit} title={'Create new deck'}>
<form className={s.content} onSubmit={onSubmit}>
<ControlledTextField control={control} label={'Deck name'} name={'name'} />
<ControlledCheckbox
control={control}
label={'Private'}
name={'isPrivate'}
position={'left'}
/>
</form>
</Dialog>
)
}

View File

@@ -2,89 +2,86 @@ import { Link } from 'react-router-dom'
import { Edit2Outline, PlayCircleOutline, TrashOutline } from '@/assets'
import {
Button,
Column,
Table,
TableBody,
TableCell,
TableHeader,
TableRow,
Typography,
Button,
Column,
Table,
TableBody,
TableCell,
TableHeader,
TableRow,
Typography,
} from '@/components'
import { Deck } from '@/services/decks'
import { formatDate } from '@/utils'
import s from './decks-table.module.scss'
const columns: Column[] = [
{
key: 'name',
title: 'Name',
},
{
key: 'cardsCount',
title: 'Cards',
},
{
key: 'updated',
title: 'Last Updated',
},
{
key: 'author',
title: 'Created By',
},
{
key: 'actions',
title: '',
},
{
key: 'name',
title: 'Name',
},
{
key: 'cardsCount',
title: 'Cards',
},
{
key: 'updated',
title: 'Last Updated',
},
{
key: 'author',
title: 'Created By',
},
{
key: 'actions',
title: '',
},
]
type Props = {
currentUserId: string
decks: Deck[] | undefined
onDeleteClick: (id: string) => void
onEditClick: (id: string) => void
currentUserId: string
decks: Deck[] | undefined
onDeleteClick: (id: string) => void
onEditClick: (id: string) => void
}
export const DecksTable = ({ currentUserId, decks, onDeleteClick, onEditClick }: Props) => {
const handleEditClick = (id: string) => () => onEditClick(id)
const handleDeleteClick = (id: string) => () => onDeleteClick(id)
const handleEditClick = (id: string) => () => onEditClick(id)
const handleDeleteClick = (id: string) => () => onDeleteClick(id)
return (
<Table>
<TableHeader columns={columns} />
<TableBody>
{decks?.map(deck => (
<TableRow key={deck.id}>
<TableCell>
<Typography as={Link} to={`/decks/${deck.id}`} variant={'body2'}>
{deck.name}
</Typography>
</TableCell>
<TableCell>{deck.cardsCount}</TableCell>
<TableCell>{formatDate(deck.updated)}</TableCell>
<TableCell>{deck.author.name}</TableCell>
<TableCell>
<div className={s.iconsContainer}>
<Button as={Link} to={`/decks/${deck.id}/learn`} variant={'icon'}>
<PlayCircleOutline />
</Button>
{deck.author.id === currentUserId && (
<>
<Button onClick={handleEditClick(deck.id)} variant={'icon'}>
<Edit2Outline />
</Button>
<Button
onClick={handleDeleteClick(deck.id)}
variant={'icon'}
>
<TrashOutline />
</Button>
</>
)}
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)
return (
<Table>
<TableHeader columns={columns} />
<TableBody>
{decks?.map(deck => (
<TableRow key={deck.id}>
<TableCell>
<Typography as={Link} to={`/decks/${deck.id}`} variant={'body2'}>
{deck.name}
</Typography>
</TableCell>
<TableCell>{deck.cardsCount}</TableCell>
<TableCell>{formatDate(deck.updated)}</TableCell>
<TableCell>{deck.author.name}</TableCell>
<TableCell>
<div className={s.iconsContainer}>
<Button as={Link} to={`/decks/${deck.id}/learn`} variant={'icon'}>
<PlayCircleOutline />
</Button>
{deck.author.id === currentUserId && (
<>
<Button onClick={handleEditClick(deck.id)} variant={'icon'}>
<Edit2Outline />
</Button>
<Button onClick={handleDeleteClick(deck.id)} variant={'icon'}>
<TrashOutline />
</Button>
</>
)}
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)
}

View File

@@ -5,35 +5,35 @@ import { Button } from '@/components'
import { Meta, StoryObj } from '@storybook/react'
const meta = {
component: DeleteDeckDialog,
tags: ['autodocs'],
title: 'Decks/Delete Deck Dialog',
component: DeleteDeckDialog,
tags: ['autodocs'],
title: 'Decks/Delete Deck Dialog',
} satisfies Meta<typeof DeleteDeckDialog>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {
deckName: 'Deck Name',
onOpenChange: () => {},
open: true,
},
render: args => {
const [open, setOpen] = useState(false)
const closeModal = () => setOpen(false)
args: {
deckName: 'Deck Name',
onOpenChange: () => {},
open: true,
},
render: args => {
const [open, setOpen] = useState(false)
const closeModal = () => setOpen(false)
return (
<>
<Button onClick={() => setOpen(true)}>Open Modal</Button>
<DeleteDeckDialog
{...args}
onCancel={closeModal}
onConfirm={closeModal}
onOpenChange={setOpen}
open={open}
/>
</>
)
},
return (
<>
<Button onClick={() => setOpen(true)}>Open Modal</Button>
<DeleteDeckDialog
{...args}
onCancel={closeModal}
onConfirm={closeModal}
onOpenChange={setOpen}
open={open}
/>
</>
)
},
}

View File

@@ -3,17 +3,17 @@ import { Dialog, DialogProps } from '@/components'
import s from './delete-deck-dialog.module.scss'
export default {}
type Props = Pick<DialogProps, 'onCancel' | 'onConfirm' | 'onOpenChange' | 'open'> & {
deckName: string
deckName: string
}
export const DeleteDeckDialog = ({ deckName, ...dialogProps }: Props) => {
return (
<Dialog {...dialogProps} title={'Delete deck'}>
<div className={s.content}>
<p>
Do you really want to remove <strong>{deckName}</strong>?
</p>
<p>All cards will be deleted.</p>
</div>
</Dialog>
)
return (
<Dialog {...dialogProps} title={'Delete deck'}>
<div className={s.content}>
<p>
Do you really want to remove <strong>{deckName}</strong>?
</p>
<p>All cards will be deleted.</p>
</div>
</Dialog>
)
}

View File

@@ -3,27 +3,27 @@ import type { Meta, StoryObj } from '@storybook/react'
import { PersonalInformation } from './'
const meta = {
component: PersonalInformation,
tags: ['autodocs'],
title: 'Profile/Personal information',
component: PersonalInformation,
tags: ['autodocs'],
title: 'Profile/Personal information',
} satisfies Meta<typeof PersonalInformation>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {
avatar: 'https://picsum.photos/200',
email: 'your_email@domain.com',
name: 'John Doe',
onAvatarChange: () => {
console.info('avatar changed')
},
onLogout: () => {
console.info('logout')
},
onNameChange: () => {
console.info('name changed')
},
args: {
avatar: 'https://picsum.photos/200',
email: 'your_email@domain.com',
name: 'John Doe',
onAvatarChange: () => {
console.info('avatar changed')
},
onLogout: () => {
console.info('logout')
},
onNameChange: () => {
console.info('name changed')
},
},
}

View File

@@ -4,62 +4,62 @@ import { Button, Card, Typography } from '../../ui'
import s from './personal-information.module.scss'
type Props = {
avatar: string
email: string
name: string
onAvatarChange: (newAvatar: string) => void
onLogout: () => void
onNameChange: (newName: string) => void
avatar: string
email: string
name: string
onAvatarChange: (newAvatar: string) => void
onLogout: () => void
onNameChange: (newName: string) => void
}
export const PersonalInformation = ({
avatar,
email,
name,
onAvatarChange,
onLogout,
onNameChange,
avatar,
email,
name,
onAvatarChange,
onLogout,
onNameChange,
}: Props) => {
const handleAvatarChanged = () => {
onAvatarChange('new Avatar')
}
const handleNameChanged = () => {
onNameChange('New name')
}
const handleLogout = () => {
onLogout()
}
const handleAvatarChanged = () => {
onAvatarChange('new Avatar')
}
const handleNameChanged = () => {
onNameChange('New name')
}
const handleLogout = () => {
onLogout()
}
return (
<Card className={s.card}>
<Typography className={s.title} variant={'large'}>
Personal Information
</Typography>
<div className={s.photoContainer}>
<div>
<img alt={'avatar'} src={avatar} />
<button className={s.editAvatarButton} onClick={handleAvatarChanged}>
<Camera />
</button>
</div>
</div>
<div className={s.nameWithEditButton}>
<Typography className={s.name} variant={'h1'}>
{name}
</Typography>
<button className={s.editNameButton} onClick={handleNameChanged}>
<Edit />
</button>
</div>
<Typography className={s.email} variant={'body2'}>
{/* eslint-disable-next-line react/no-unescaped-entities */}
{email}
</Typography>
<div className={s.buttonContainer}>
<Button onClick={handleLogout} variant={'secondary'}>
<Logout />
Sign Out
</Button>
</div>
</Card>
)
return (
<Card className={s.card}>
<Typography className={s.title} variant={'large'}>
Personal Information
</Typography>
<div className={s.photoContainer}>
<div>
<img alt={'avatar'} src={avatar} />
<button className={s.editAvatarButton} onClick={handleAvatarChanged}>
<Camera />
</button>
</div>
</div>
<div className={s.nameWithEditButton}>
<Typography className={s.name} variant={'h1'}>
{name}
</Typography>
<button className={s.editNameButton} onClick={handleNameChanged}>
<Edit />
</button>
</div>
<Typography className={s.email} variant={'body2'}>
{/* eslint-disable-next-line react/no-unescaped-entities */}
{email}
</Typography>
<div className={s.buttonContainer}>
<Button onClick={handleLogout} variant={'secondary'}>
<Logout />
Sign Out
</Button>
</div>
</Card>
)
}

View File

@@ -1,67 +1,71 @@
import type { Meta, StoryObj } from '@storybook/react'
import { Button } from './'
import {Camera} from "@/assets";
import { Camera } from '@/assets'
const meta = {
argTypes: {
variant: {
control: { type: 'radio' },
options: ['primary', 'secondary', 'tertiary', 'link'],
},
argTypes: {
variant: {
control: { type: 'radio' },
options: ['primary', 'secondary', 'tertiary', 'link'],
},
component: Button,
tags: ['autodocs'],
title: 'Components/Button',
},
component: Button,
tags: ['autodocs'],
title: 'Components/Button',
} satisfies Meta<typeof Button>
export default meta
type Story = StoryObj<typeof meta>
export const Primary: Story = {
args: {
children: <>Turn Camera On <Camera/></>,
disabled: false,
variant: 'primary',
},
args: {
children: (
<>
Turn Camera On <Camera />
</>
),
disabled: false,
variant: 'primary',
},
}
export const Secondary: Story = {
args: {
children: 'Secondary Button',
disabled: false,
variant: 'secondary',
},
args: {
children: 'Secondary Button',
disabled: false,
variant: 'secondary',
},
}
export const Tertiary: Story = {
args: {
children: 'Tertiary Button',
disabled: false,
variant: 'tertiary',
},
args: {
children: 'Tertiary Button',
disabled: false,
variant: 'tertiary',
},
}
export const Link: Story = {
args: {
children: 'Tertiary Button',
disabled: false,
variant: 'link',
},
args: {
children: 'Tertiary Button',
disabled: false,
variant: 'link',
},
}
export const FullWidth: Story = {
args: {
children: 'Full Width Button',
disabled: false,
fullWidth: true,
variant: 'primary',
},
args: {
children: 'Full Width Button',
disabled: false,
fullWidth: true,
variant: 'primary',
},
}
export const AsLink: Story = {
args: {
as: 'button',
children: 'Link that looks like a button',
variant: 'primary',
href: 'https://google.com',
},
args: {
as: 'button',
children: 'Link that looks like a button',
href: 'https://google.com',
variant: 'primary',
},
}

View File

@@ -3,20 +3,17 @@ import { ComponentPropsWithoutRef, ElementType, ReactNode } from 'react'
import s from './button.module.scss'
export type ButtonProps<T extends ElementType = 'button'> = {
as?: T
children: ReactNode
className?: string
fullWidth?: boolean
variant?: 'icon' | 'link' | 'primary' | 'secondary' | 'tertiary'
as?: T
children: ReactNode
className?: string
fullWidth?: boolean
variant?: 'icon' | 'link' | 'primary' | 'secondary' | 'tertiary'
} & ComponentPropsWithoutRef<T>
export const Button = <T extends ElementType = 'button'>(props: ButtonProps<T>) => {
const { as: Component = 'button', className, fullWidth, variant = 'primary', ...rest } = props
const { as: Component = 'button', className, fullWidth, variant = 'primary', ...rest } = props
return (
<Component
className={`${s[variant]} ${fullWidth ? s.fullWidth : ''} ${className}`}
{...rest}
/>
)
return (
<Component className={`${s[variant]} ${fullWidth ? s.fullWidth : ''} ${className}`} {...rest} />
)
}

View File

@@ -4,21 +4,21 @@ import { Card } from './'
import { Typography } from '@/components'
const meta = {
component: Card,
tags: ['autodocs'],
title: 'Components/Card',
component: Card,
tags: ['autodocs'],
title: 'Components/Card',
} satisfies Meta<typeof Card>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {
children: <Typography variant={'large'}>Card</Typography>,
style: {
height: '300px',
padding: '24px',
width: '300px',
},
args: {
children: <Typography variant={'large'}>Card</Typography>,
style: {
height: '300px',
padding: '24px',
width: '300px',
},
},
}

View File

@@ -7,9 +7,9 @@ import s from './card.module.scss'
export type CardProps = {} & ComponentPropsWithoutRef<'div'>
export const Card = forwardRef<HTMLDivElement, CardProps>(({ className, ...restProps }, ref) => {
const classNames = {
root: clsx(s.root, className),
}
const classNames = {
root: clsx(s.root, className),
}
return <div className={classNames.root} ref={ref} {...restProps}></div>
return <div className={classNames.root} ref={ref} {...restProps}></div>
})

View File

@@ -3,32 +3,32 @@ import { useState } from 'react'
import { Checkbox } from './checkbox'
import { Meta, StoryObj } from '@storybook/react'
const meta = {
component: Checkbox,
tags: ['autodocs'],
title: 'Components/Checkbox',
component: Checkbox,
tags: ['autodocs'],
title: 'Components/Checkbox',
} satisfies Meta<typeof Checkbox>
export default meta
type Story = StoryObj<typeof meta>
export const Uncontrolled: Story = {
args: {
disabled: false,
label: 'Click here',
},
args: {
disabled: false,
label: 'Click here',
},
}
export const Controlled: Story = {
render: args => {
const [checked, setChecked] = useState(false)
render: args => {
const [checked, setChecked] = useState(false)
return (
<Checkbox
{...args}
checked={checked}
label={'Click here'}
onChange={() => setChecked(!checked)}
/>
)
},
return (
<Checkbox
{...args}
checked={checked}
label={'Click here'}
onChange={() => setChecked(!checked)}
/>
)
},
}

View File

@@ -9,60 +9,57 @@ import { clsx } from 'clsx'
import s from './checkbox.module.scss'
export type CheckboxProps = {
checked?: boolean
className?: string
disabled?: boolean
id?: string
label?: string
onChange?: (checked: boolean) => void
position?: 'left'
required?: boolean
checked?: boolean
className?: string
disabled?: boolean
id?: string
label?: string
onChange?: (checked: boolean) => void
position?: 'left'
required?: boolean
}
export const Checkbox: FC<CheckboxProps> = ({
checked,
className,
disabled,
id,
label,
onChange,
position,
required,
checked,
className,
disabled,
id,
label,
onChange,
position,
required,
}) => {
const classNames = {
buttonWrapper: clsx(s.buttonWrapper, disabled && s.disabled, position === 'left' && s.left),
container: clsx(s.container, className),
indicator: s.indicator,
label: clsx(s.label, disabled && s.disabled),
root: s.root,
}
const classNames = {
buttonWrapper: clsx(s.buttonWrapper, disabled && s.disabled, position === 'left' && s.left),
container: clsx(s.container, className),
indicator: s.indicator,
label: clsx(s.label, disabled && s.disabled),
root: s.root,
}
return (
<div className={classNames.container}>
<LabelRadix.Root asChild>
<Typography as={'label'} className={classNames.label} variant={'body2'}>
<div className={classNames.buttonWrapper}>
<CheckboxRadix.Root
checked={checked}
className={classNames.root}
disabled={disabled}
id={id}
onCheckedChange={onChange}
required={required}
>
{checked && (
<CheckboxRadix.Indicator
className={classNames.indicator}
forceMount
>
<Check />
</CheckboxRadix.Indicator>
)}
</CheckboxRadix.Root>
</div>
{label}
</Typography>
</LabelRadix.Root>
</div>
)
return (
<div className={classNames.container}>
<LabelRadix.Root asChild>
<Typography as={'label'} className={classNames.label} variant={'body2'}>
<div className={classNames.buttonWrapper}>
<CheckboxRadix.Root
checked={checked}
className={classNames.root}
disabled={disabled}
id={id}
onCheckedChange={onChange}
required={required}
>
{checked && (
<CheckboxRadix.Indicator className={classNames.indicator} forceMount>
<Check />
</CheckboxRadix.Indicator>
)}
</CheckboxRadix.Root>
</div>
{label}
</Typography>
</LabelRadix.Root>
</div>
)
}

View File

@@ -3,34 +3,34 @@ import { FieldValues, UseControllerProps, useController } from 'react-hook-form'
import { Checkbox, CheckboxProps } from '../../'
export type ControlledCheckboxProps<TFieldValues extends FieldValues> =
UseControllerProps<TFieldValues> & Omit<CheckboxProps, 'id' | 'onChange' | 'value'>
UseControllerProps<TFieldValues> & Omit<CheckboxProps, 'id' | 'onChange' | 'value'>
export const ControlledCheckbox = <TFieldValues extends FieldValues>({
control,
defaultValue,
name,
rules,
shouldUnregister,
...checkboxProps
}: ControlledCheckboxProps<TFieldValues>) => {
const {
field: { onChange, value },
} = useController({
control,
defaultValue,
name,
rules,
shouldUnregister,
...checkboxProps
}: ControlledCheckboxProps<TFieldValues>) => {
const {
field: { onChange, value },
} = useController({
control,
defaultValue,
name,
rules,
shouldUnregister,
})
})
return (
<Checkbox
{...{
checked: value,
id: name,
onChange,
...checkboxProps,
}}
/>
)
return (
<Checkbox
{...{
checked: value,
id: name,
onChange,
...checkboxProps,
}}
/>
)
}

View File

@@ -3,28 +3,28 @@ import { Control, FieldPath, FieldValues, useController } from 'react-hook-form'
import { RadioGroup, RadioGroupProps } from '@/components/ui'
export type ControlledRadioGroupProps<TFieldValues extends FieldValues> = {
control: Control<TFieldValues>
name: FieldPath<TFieldValues>
control: Control<TFieldValues>
name: FieldPath<TFieldValues>
} & Omit<RadioGroupProps, 'id' | 'onChange' | 'value'>
export const ControlledRadioGroup = <TFieldValues extends FieldValues>(
props: ControlledRadioGroupProps<TFieldValues>
props: ControlledRadioGroupProps<TFieldValues>
) => {
const {
field: { onChange, ...field },
fieldState: { error },
} = useController({
control: props.control,
name: props.name,
})
const {
field: { onChange, ...field },
fieldState: { error },
} = useController({
control: props.control,
name: props.name,
})
return (
<RadioGroup
{...props}
{...field}
errorMessage={error?.message}
id={props.name}
onValueChange={onChange}
/>
)
return (
<RadioGroup
{...props}
{...field}
errorMessage={error?.message}
id={props.name}
onValueChange={onChange}
/>
)
}

View File

@@ -3,20 +3,20 @@ import { Control, FieldPath, FieldValues, useController } from 'react-hook-form'
import { TextField, TextFieldProps } from '@/components'
export type ControlledTextFieldProps<TFieldValues extends FieldValues> = {
control: Control<TFieldValues>
name: FieldPath<TFieldValues>
control: Control<TFieldValues>
name: FieldPath<TFieldValues>
} & Omit<TextFieldProps, 'id' | 'onChange' | 'value'>
export const ControlledTextField = <TFieldValues extends FieldValues>(
props: ControlledTextFieldProps<TFieldValues>
props: ControlledTextFieldProps<TFieldValues>
) => {
const {
field,
fieldState: { error },
} = useController({
control: props.control,
name: props.name,
})
const {
field,
fieldState: { error },
} = useController({
control: props.control,
name: props.name,
})
return <TextField {...props} {...field} errorMessage={error?.message} id={props.name} />
return <TextField {...props} {...field} errorMessage={error?.message} id={props.name} />
}

View File

@@ -4,31 +4,31 @@ import { Dialog } from './'
import { Meta, StoryObj } from '@storybook/react'
const meta = {
component: Dialog,
tags: ['autodocs'],
title: 'Components/Dialog',
component: Dialog,
tags: ['autodocs'],
title: 'Components/Dialog',
} satisfies Meta<typeof Dialog>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {
children: 'Modal',
onOpenChange: () => {},
open: true,
title: 'Modal',
},
render: args => {
const [open, setOpen] = useState(false)
args: {
children: 'Modal',
onOpenChange: () => {},
open: true,
title: 'Modal',
},
render: args => {
const [open, setOpen] = useState(false)
return (
<>
<button onClick={() => setOpen(true)}>Open Modal</button>
<Dialog {...args} onOpenChange={setOpen} open={open}>
Dialog content here
</Dialog>
</>
)
},
return (
<>
<button onClick={() => setOpen(true)}>Open Modal</button>
<Dialog {...args} onOpenChange={setOpen} open={open}>
Dialog content here
</Dialog>
</>
)
},
}

View File

@@ -3,28 +3,28 @@ import { Button, Modal, ModalProps } from '@/components'
import s from './dialog.module.scss'
export type DialogProps = ModalProps & {
cancelText?: string
confirmText?: string
onCancel?: () => void
onConfirm?: () => void
cancelText?: string
confirmText?: string
onCancel?: () => void
onConfirm?: () => void
}
export const Dialog = ({
cancelText = 'Cancel',
children,
confirmText = 'OK',
onCancel,
onConfirm,
...modalProps
cancelText = 'Cancel',
children,
confirmText = 'OK',
onCancel,
onConfirm,
...modalProps
}: DialogProps) => {
return (
<Modal {...modalProps}>
{children}
<div className={s.buttons}>
<Button onClick={onCancel} variant={'secondary'}>
{cancelText}
</Button>
<Button onClick={onConfirm}>{confirmText}</Button>
</div>
</Modal>
)
return (
<Modal {...modalProps}>
{children}
<div className={s.buttons}>
<Button onClick={onCancel} variant={'secondary'}>
{cancelText}
</Button>
<Button onClick={onConfirm}>{confirmText}</Button>
</div>
</Modal>
)
}

View File

@@ -6,18 +6,18 @@ import { clsx } from 'clsx'
import s from './label.module.scss'
export type LabelProps = {
label?: ReactNode
label?: ReactNode
} & ComponentPropsWithoutRef<'label'>
export const Label: FC<LabelProps> = ({ children, className, label, ...rest }) => {
const classNames = {
label: clsx(s.label, className),
}
const classNames = {
label: clsx(s.label, className),
}
return (
<LabelRadixUI.Root {...rest}>
{label && <div className={classNames.label}>{label}</div>}
{children}
</LabelRadixUI.Root>
)
return (
<LabelRadixUI.Root {...rest}>
{label && <div className={classNames.label}>{label}</div>}
{children}
</LabelRadixUI.Root>
)
}

View File

@@ -4,31 +4,31 @@ import { Modal } from '@/components'
import { Meta, StoryObj } from '@storybook/react'
const meta = {
component: Modal,
tags: ['autodocs'],
title: 'Components/Modal',
component: Modal,
tags: ['autodocs'],
title: 'Components/Modal',
} satisfies Meta<typeof Modal>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {
children: 'Modal',
onOpenChange: () => {},
open: true,
title: 'Modal',
},
render: args => {
const [open, setOpen] = useState(false)
args: {
children: 'Modal',
onOpenChange: () => {},
open: true,
title: 'Modal',
},
render: args => {
const [open, setOpen] = useState(false)
return (
<>
<button onClick={() => setOpen(true)}>Open Modal</button>
<Modal {...args} onOpenChange={setOpen} open={open}>
Modal content here
</Modal>
</>
)
},
return (
<>
<button onClick={() => setOpen(true)}>Open Modal</button>
<Modal {...args} onOpenChange={setOpen} open={open}>
Modal content here
</Modal>
</>
)
},
}

View File

@@ -7,30 +7,30 @@ import * as DialogPrimitive from '@radix-ui/react-dialog'
import s from './modal.module.scss'
export type ModalProps = {
children: ReactNode
onOpenChange: (open: boolean) => void
open: boolean
title?: string
children: ReactNode
onOpenChange: (open: boolean) => void
open: boolean
title?: string
} & Omit<ComponentPropsWithoutRef<typeof DialogPrimitive.Dialog>, 'onOpenChange' | 'open'>
export const Modal = ({ children, title, ...props }: ModalProps) => {
return (
<DialogPrimitive.Root {...props}>
<DialogPrimitive.Portal>
<DialogPrimitive.Overlay className={s.overlay} />
<DialogPrimitive.Content className={s.content}>
<div className={s.header}>
<DialogPrimitive.Title asChild>
<Typography as={'h2'} variant={'h2'}>
{title}
</Typography>
</DialogPrimitive.Title>
<DialogPrimitive.Close className={s.closeButton}>
<Close />
</DialogPrimitive.Close>
</div>
{children}
</DialogPrimitive.Content>
</DialogPrimitive.Portal>
</DialogPrimitive.Root>
)
return (
<DialogPrimitive.Root {...props}>
<DialogPrimitive.Portal>
<DialogPrimitive.Overlay className={s.overlay} />
<DialogPrimitive.Content className={s.content}>
<div className={s.header}>
<DialogPrimitive.Title asChild>
<Typography as={'h2'} variant={'h2'}>
{title}
</Typography>
</DialogPrimitive.Title>
<DialogPrimitive.Close className={s.closeButton}>
<Close />
</DialogPrimitive.Close>
</div>
{children}
</DialogPrimitive.Content>
</DialogPrimitive.Portal>
</DialogPrimitive.Root>
)
}

View File

@@ -7,9 +7,9 @@ import s from './page.module.scss'
export type PageProps = ComponentPropsWithoutRef<'div'>
export const Page = forwardRef<HTMLDivElement, PageProps>(({ className, ...props }, ref) => {
const classNames = {
root: clsx(s.root, className),
}
const classNames = {
root: clsx(s.root, className),
}
return <div {...props} className={classNames.root} ref={ref} />
return <div {...props} className={classNames.root} ref={ref} />
})

View File

@@ -7,194 +7,187 @@ import { clsx } from 'clsx'
import s from './pagination.module.scss'
type PaginationConditionals =
| {
onPerPageChange: (itemPerPage: number) => void
perPage: number
perPageOptions: number[]
}
| {
onPerPageChange?: never
perPage?: null
perPageOptions?: never
}
| {
onPerPageChange: (itemPerPage: number) => void
perPage: number
perPageOptions: number[]
}
| {
onPerPageChange?: never
perPage?: null
perPageOptions?: never
}
export type PaginationProps = {
count: number
onChange: (page: number) => void
onPerPageChange?: (itemPerPage: number) => void
page: number
perPage?: number
perPageOptions?: number[]
siblings?: number
count: number
onChange: (page: number) => void
onPerPageChange?: (itemPerPage: number) => void
page: number
perPage?: number
perPageOptions?: number[]
siblings?: number
} & PaginationConditionals
const classNames = {
container: s.container,
dots: s.dots,
icon: s.icon,
item: s.item,
pageButton(selected?: boolean) {
return clsx(this.item, selected && s.selected)
},
root: s.root,
select: s.select,
selectBox: s.selectBox,
container: s.container,
dots: s.dots,
icon: s.icon,
item: s.item,
pageButton(selected?: boolean) {
return clsx(this.item, selected && s.selected)
},
root: s.root,
select: s.select,
selectBox: s.selectBox,
}
export const Pagination: FC<PaginationProps> = ({
count,
onChange,
onPerPageChange,
page,
perPage = null,
perPageOptions,
siblings,
}) => {
const {
handleMainPageClicked,
handleNextPageClicked,
handlePreviousPageClicked,
isFirstPage,
isLastPage,
paginationRange,
} = usePagination({
count,
onChange,
onPerPageChange,
page,
perPage = null,
perPageOptions,
siblings,
}) => {
const {
handleMainPageClicked,
handleNextPageClicked,
handlePreviousPageClicked,
isFirstPage,
isLastPage,
paginationRange,
} = usePagination({
count,
onChange,
page,
siblings,
})
})
const showPerPageSelect = !!perPage && !!perPageOptions && !!onPerPageChange
const showPerPageSelect = !!perPage && !!perPageOptions && !!onPerPageChange
return (
<div className={classNames.root}>
<div className={classNames.container}>
<PrevButton disabled={isFirstPage} onClick={handlePreviousPageClicked} />
return (
<div className={classNames.root}>
<div className={classNames.container}>
<PrevButton disabled={isFirstPage} onClick={handlePreviousPageClicked} />
<MainPaginationButtons
currentPage={page}
onClick={handleMainPageClicked}
paginationRange={paginationRange}
/>
<MainPaginationButtons
currentPage={page}
onClick={handleMainPageClicked}
paginationRange={paginationRange}
/>
<NextButton disabled={isLastPage} onClick={handleNextPageClicked} />
</div>
<NextButton disabled={isLastPage} onClick={handleNextPageClicked} />
</div>
{showPerPageSelect && (
<PerPageSelect
{...{
onPerPageChange,
perPage,
perPageOptions,
}}
/>
)}
</div>
)
{showPerPageSelect && (
<PerPageSelect
{...{
onPerPageChange,
perPage,
perPageOptions,
}}
/>
)}
</div>
)
}
type NavigationButtonProps = {
disabled?: boolean
onClick: () => void
disabled?: boolean
onClick: () => void
}
type PageButtonProps = NavigationButtonProps & {
page: number
selected: boolean
page: number
selected: boolean
}
const Dots: FC = () => {
return <span className={classNames.dots}>&#8230;</span>
return <span className={classNames.dots}>&#8230;</span>
}
const PageButton: FC<PageButtonProps> = ({ disabled, onClick, page, selected }) => {
return (
<button
className={classNames.pageButton(selected)}
disabled={selected || disabled}
onClick={onClick}
>
{page}
</button>
)
return (
<button
className={classNames.pageButton(selected)}
disabled={selected || disabled}
onClick={onClick}
>
{page}
</button>
)
}
const PrevButton: FC<NavigationButtonProps> = ({ disabled, onClick }) => {
return (
<button className={classNames.item} disabled={disabled} onClick={onClick}>
<KeyboardArrowLeft className={classNames.icon} />
</button>
)
return (
<button className={classNames.item} disabled={disabled} onClick={onClick}>
<KeyboardArrowLeft className={classNames.icon} />
</button>
)
}
const NextButton: FC<NavigationButtonProps> = ({ disabled, onClick }) => {
return (
<button className={classNames.item} disabled={disabled} onClick={onClick}>
<KeyboardArrowRight className={classNames.icon} />
</button>
)
return (
<button className={classNames.item} disabled={disabled} onClick={onClick}>
<KeyboardArrowRight className={classNames.icon} />
</button>
)
}
type MainPaginationButtonsProps = {
currentPage: number
onClick: (pageNumber: number) => () => void
paginationRange: (number | string)[]
currentPage: number
onClick: (pageNumber: number) => () => void
paginationRange: (number | string)[]
}
const MainPaginationButtons: FC<MainPaginationButtonsProps> = ({
currentPage,
onClick,
paginationRange,
currentPage,
onClick,
paginationRange,
}) => {
return (
<>
{paginationRange.map((page: number | string, index) => {
const isSelected = page === currentPage
return (
<>
{paginationRange.map((page: number | string, index) => {
const isSelected = page === currentPage
if (typeof page !== 'number') {
return <Dots key={index} />
}
if (typeof page !== 'number') {
return <Dots key={index} />
}
return (
<PageButton
key={index}
onClick={onClick(page)}
page={page}
selected={isSelected}
/>
)
})}
</>
)
return <PageButton key={index} onClick={onClick(page)} page={page} selected={isSelected} />
})}
</>
)
}
export type PerPageSelectProps = {
onPerPageChange: (itemPerPage: number) => void
perPage: number
perPageOptions: number[]
onPerPageChange: (itemPerPage: number) => void
perPage: number
perPageOptions: number[]
}
export const PerPageSelect: FC<PerPageSelectProps> = (
{
// perPage,
// perPageOptions,
// onPerPageChange,
}
{
// perPage,
// perPageOptions,
// onPerPageChange,
}
) => {
// const selectOptions = perPageOptions.map(value => ({
// label: value,
// value,
// }))
// const selectOptions = perPageOptions.map(value => ({
// label: value,
// value,
// }))
return (
<div className={classNames.selectBox}>
Показать
{/*<Select*/}
{/* className={classNames.select}*/}
{/* value={perPage}*/}
{/* options={selectOptions}*/}
{/* onChange={onPerPageChange}*/}
{/* variant="pagination"*/}
{/*/>*/}
на странице
</div>
)
return (
<div className={classNames.selectBox}>
Показать
{/*<Select*/}
{/* className={classNames.select}*/}
{/* value={perPage}*/}
{/* options={selectOptions}*/}
{/* onChange={onPerPageChange}*/}
{/* variant="pagination"*/}
{/*/>*/}
на странице
</div>
)
}

View File

@@ -3,110 +3,110 @@ import { useCallback, useMemo } from 'react'
// original code: https://www.freecodecamp.org/news/build-a-custom-pagination-component-in-react/
const range = (start: number, end: number) => {
const length = end - start + 1
const length = end - start + 1
/*
/*
Create an array of certain length and set the elements within it from
start value to end value.
*/
return Array.from({ length }, (_, idx) => idx + start)
return Array.from({ length }, (_, idx) => idx + start)
}
const DOTS = '...'
type UsePaginationParamType = {
count: number
onChange: (pageNumber: number) => void
page: number
siblings?: number
count: number
onChange: (pageNumber: number) => void
page: number
siblings?: number
}
type PaginationRange = ('...' | number)[]
export const usePagination = ({ count, onChange, page, siblings = 1 }: UsePaginationParamType) => {
const paginationRange = useMemo(() => {
// Pages count is determined as siblingCount + firstPage + lastPage + page + 2*DOTS
const totalPageNumbers = siblings + 5
const paginationRange = useMemo(() => {
// Pages count is determined as siblingCount + firstPage + lastPage + page + 2*DOTS
const totalPageNumbers = siblings + 5
/*
/*
Case 1:
If the number of pages is less than the page numbers we want to show in our
paginationComponent, we return the range [1..totalPageCount]
*/
if (totalPageNumbers >= count) {
return range(1, count)
}
if (totalPageNumbers >= count) {
return range(1, count)
}
/*
/*
Calculate left and right sibling index and make sure they are within range 1 and totalPageCount
*/
const leftSiblingIndex = Math.max(page - siblings, 1)
const rightSiblingIndex = Math.min(page + siblings, count)
const leftSiblingIndex = Math.max(page - siblings, 1)
const rightSiblingIndex = Math.min(page + siblings, count)
/*
/*
We do not show dots when there is only one page number to be inserted
between the extremes of siblings and the page limits i.e 1 and totalPageCount.
Hence, we are using leftSiblingIndex > 2 and rightSiblingIndex < totalPageCount - 2
*/
const shouldShowLeftDots = leftSiblingIndex > 2
const shouldShowRightDots = rightSiblingIndex < count - 2
const shouldShowLeftDots = leftSiblingIndex > 2
const shouldShowRightDots = rightSiblingIndex < count - 2
const firstPageIndex = 1
const lastPageIndex = count
const firstPageIndex = 1
const lastPageIndex = count
/*
/*
Case 2: No left dots to show, but rights dots to be shown
*/
if (!shouldShowLeftDots && shouldShowRightDots) {
const leftItemCount = 3 + 2 * siblings
const leftRange = range(1, leftItemCount)
if (!shouldShowLeftDots && shouldShowRightDots) {
const leftItemCount = 3 + 2 * siblings
const leftRange = range(1, leftItemCount)
return [...leftRange, DOTS, count]
}
return [...leftRange, DOTS, count]
}
/*
/*
Case 3: No right dots to show, but left dots to be shown
*/
if (shouldShowLeftDots && !shouldShowRightDots) {
const rightItemCount = 3 + 2 * siblings
const rightRange = range(count - rightItemCount + 1, count)
if (shouldShowLeftDots && !shouldShowRightDots) {
const rightItemCount = 3 + 2 * siblings
const rightRange = range(count - rightItemCount + 1, count)
return [firstPageIndex, DOTS, ...rightRange]
}
return [firstPageIndex, DOTS, ...rightRange]
}
/*
/*
Case 4: Both left and right dots to be shown
*/
if (shouldShowLeftDots && shouldShowRightDots) {
const middleRange = range(leftSiblingIndex, rightSiblingIndex)
if (shouldShowLeftDots && shouldShowRightDots) {
const middleRange = range(leftSiblingIndex, rightSiblingIndex)
return [firstPageIndex, DOTS, ...middleRange, DOTS, lastPageIndex]
}
}, [siblings, page, count]) as PaginationRange
const lastPage = paginationRange.at(-1)
const isFirstPage = page === 1
const isLastPage = page === lastPage
const handleNextPageClicked = useCallback(() => {
onChange(page + 1)
}, [page, onChange])
const handlePreviousPageClicked = useCallback(() => {
onChange(page - 1)
}, [page, onChange])
function handleMainPageClicked(pageNumber: number) {
return () => onChange(pageNumber)
return [firstPageIndex, DOTS, ...middleRange, DOTS, lastPageIndex]
}
}, [siblings, page, count]) as PaginationRange
return {
handleMainPageClicked,
handleNextPageClicked,
handlePreviousPageClicked,
isFirstPage,
isLastPage,
paginationRange,
}
const lastPage = paginationRange.at(-1)
const isFirstPage = page === 1
const isLastPage = page === lastPage
const handleNextPageClicked = useCallback(() => {
onChange(page + 1)
}, [page, onChange])
const handlePreviousPageClicked = useCallback(() => {
onChange(page - 1)
}, [page, onChange])
function handleMainPageClicked(pageNumber: number) {
return () => onChange(pageNumber)
}
return {
handleMainPageClicked,
handleNextPageClicked,
handlePreviousPageClicked,
isFirstPage,
isLastPage,
paginationRange,
}
}

View File

@@ -3,29 +3,29 @@ import type { Meta, StoryObj } from '@storybook/react'
import { RadioGroup } from './'
const meta = {
component: RadioGroup,
tags: ['autodocs'],
title: 'Components/Radio Group',
component: RadioGroup,
tags: ['autodocs'],
title: 'Components/Radio Group',
} satisfies Meta<typeof RadioGroup>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {
options: [
{ label: 'Option One', value: 'option-one' },
{ label: 'Option Two', value: 'option-two' },
],
},
args: {
options: [
{ label: 'Option One', value: 'option-one' },
{ label: 'Option Two', value: 'option-two' },
],
},
}
export const Disabled: Story = {
args: {
disabled: true,
options: [
{ label: 'Option One', value: 'option-one' },
{ label: 'Option Two', value: 'option-two' },
],
},
args: {
disabled: true,
options: [
{ label: 'Option One', value: 'option-one' },
{ label: 'Option Two', value: 'option-two' },
],
},
}

View File

@@ -7,56 +7,56 @@ import { clsx } from 'clsx'
import s from './radio-group.module.scss'
const RadioGroupRoot = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
React.ElementRef<typeof RadioGroupPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
>(({ className, ...props }, ref) => {
return <RadioGroupPrimitive.Root className={clsx(s.root, className)} {...props} ref={ref} />
return <RadioGroupPrimitive.Root className={clsx(s.root, className)} {...props} ref={ref} />
})
RadioGroupRoot.displayName = RadioGroupPrimitive.Root.displayName
const RadioGroupItem = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
React.ElementRef<typeof RadioGroupPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
>(({ children, className, ...props }, ref) => {
return (
<RadioGroupPrimitive.Item className={clsx(s.option, className)} ref={ref} {...props}>
<div className={s.icon}></div>
</RadioGroupPrimitive.Item>
)
return (
<RadioGroupPrimitive.Item className={clsx(s.option, className)} ref={ref} {...props}>
<div className={s.icon}></div>
</RadioGroupPrimitive.Item>
)
})
RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
type Option = {
label: string
value: string
label: string
value: string
}
export type RadioGroupProps = Omit<
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>,
'children'
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>,
'children'
> & {
errorMessage?: string
options: Option[]
errorMessage?: string
options: Option[]
}
const RadioGroup = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Root>,
RadioGroupProps
React.ElementRef<typeof RadioGroupPrimitive.Root>,
RadioGroupProps
>((props, ref) => {
const { errorMessage, options, ...restProps } = props
const { errorMessage, options, ...restProps } = props
return (
<RadioGroupRoot {...restProps} ref={ref}>
{options.map(option => (
<div className={s.label} key={option.value}>
<RadioGroupItem id={option.value} value={option.value} />
<Typography as={'label'} htmlFor={option.value} variant={'body2'}>
{option.label}
</Typography>
</div>
))}
</RadioGroupRoot>
)
return (
<RadioGroupRoot {...restProps} ref={ref}>
{options.map(option => (
<div className={s.label} key={option.value}>
<RadioGroupItem id={option.value} value={option.value} />
<Typography as={'label'} htmlFor={option.value} variant={'body2'}>
{option.label}
</Typography>
</div>
))}
</RadioGroupRoot>
)
})
export { RadioGroup, RadioGroupItem, RadioGroupRoot }

View File

@@ -5,37 +5,37 @@ import { clsx } from 'clsx'
import s from './slider.module.scss'
const Slider = forwardRef<
ElementRef<typeof SliderPrimitive.Root>,
Omit<ComponentPropsWithoutRef<typeof SliderPrimitive.Root>, 'value'> & {
value?: (number | undefined)[]
}
ElementRef<typeof SliderPrimitive.Root>,
Omit<ComponentPropsWithoutRef<typeof SliderPrimitive.Root>, 'value'> & {
value?: (number | undefined)[]
}
>(({ className, max, onValueChange, value, ...props }, ref) => {
useEffect(() => {
if (value?.[1] === undefined || value?.[1] === null) {
onValueChange?.([value?.[0] ?? 0, max ?? 0])
}
}, [max, value, onValueChange])
useEffect(() => {
if (value?.[1] === undefined || value?.[1] === null) {
onValueChange?.([value?.[0] ?? 0, max ?? 0])
}
}, [max, value, onValueChange])
return (
<div className={s.container}>
<span>{value?.[0]}</span>
<SliderPrimitive.Root
className={clsx(s.root, className)}
max={max}
onValueChange={onValueChange}
ref={ref}
{...props}
value={[value?.[0] ?? 0, value?.[1] ?? max ?? 0]}
>
<SliderPrimitive.Track className={s.track}>
<SliderPrimitive.Range className={'absolute h-full bg-primary'} />
</SliderPrimitive.Track>
<SliderPrimitive.Thumb className={s.thumb} />
<SliderPrimitive.Thumb className={s.thumb} />
</SliderPrimitive.Root>
<span>{value?.[1]}</span>
</div>
)
return (
<div className={s.container}>
<span>{value?.[0]}</span>
<SliderPrimitive.Root
className={clsx(s.root, className)}
max={max}
onValueChange={onValueChange}
ref={ref}
{...props}
value={[value?.[0] ?? 0, value?.[1] ?? max ?? 0]}
>
<SliderPrimitive.Track className={s.track}>
<SliderPrimitive.Range className={'absolute h-full bg-primary'} />
</SliderPrimitive.Track>
<SliderPrimitive.Thumb className={s.thumb} />
<SliderPrimitive.Thumb className={s.thumb} />
</SliderPrimitive.Root>
<span>{value?.[1]}</span>
</div>
)
})
Slider.displayName = SliderPrimitive.Root.displayName

View File

@@ -3,127 +3,124 @@ import { Typography } from '@/components'
import { Meta } from '@storybook/react'
export default {
component: Table,
title: 'Components/Table',
component: Table,
title: 'Components/Table',
} as Meta<typeof Table>
export const Default = {
args: {
children: (
<>
<TableHead>
<TableRow>
<TableHeadCell>Название</TableHeadCell>
<TableHeadCell align={'center'}>Описание</TableHeadCell>
<TableHeadCell>Ссылка</TableHeadCell>
<TableHeadCell>Тип</TableHeadCell>
<TableHeadCell>Вид</TableHeadCell>
<TableHeadCell />
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<TableCell>Web Basic</TableCell>
<TableCell>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut sed do eiusmod tempoei usmodr sit amet, consectetur
adipiscing elit, sed do...
</TableCell>
<TableCell>
<Typography
as={'a'}
href={'https://it-incubator.io/'}
target={'_blank'}
variant={'link1'}
>
Какая-то ссылка кудато на какой-то источник с информациейо ссылка
кудато на какой-то источник
</Typography>
</TableCell>
<TableCell>Основной</TableCell>
<TableCell>Читать</TableCell>
<TableCell>🦎</TableCell>
</TableRow>
<TableRow>
<TableCell>Web Basic</TableCell>
<TableCell>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut sed do eiusmod tempoei usmodr sit amet, consectetur
adipiscing elit, sed do...
</TableCell>
<TableCell>
Какая-то ссылка кудато на какой-то источник с информациейо ссылка кудато
на какой-то источник
</TableCell>
<TableCell>Основной</TableCell>
<TableCell>Читать</TableCell>
<TableCell></TableCell>
</TableRow>
</TableBody>
</>
),
},
args: {
children: (
<>
<TableHead>
<TableRow>
<TableHeadCell>Название</TableHeadCell>
<TableHeadCell align={'center'}>Описание</TableHeadCell>
<TableHeadCell>Ссылка</TableHeadCell>
<TableHeadCell>Тип</TableHeadCell>
<TableHeadCell>Вид</TableHeadCell>
<TableHeadCell />
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<TableCell>Web Basic</TableCell>
<TableCell>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut sed do eiusmod tempoei usmodr sit amet, consectetur adipiscing elit, sed
do...
</TableCell>
<TableCell>
<Typography
as={'a'}
href={'https://it-incubator.io/'}
target={'_blank'}
variant={'link1'}
>
Какая-то ссылка кудато на какой-то источник с информациейо ссылка кудато на какой-то
источник
</Typography>
</TableCell>
<TableCell>Основной</TableCell>
<TableCell>Читать</TableCell>
<TableCell>🦎</TableCell>
</TableRow>
<TableRow>
<TableCell>Web Basic</TableCell>
<TableCell>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut sed do eiusmod tempoei usmodr sit amet, consectetur adipiscing elit, sed
do...
</TableCell>
<TableCell>
Какая-то ссылка кудато на какой-то источник с информациейо ссылка кудато на какой-то
источник
</TableCell>
<TableCell>Основной</TableCell>
<TableCell>Читать</TableCell>
<TableCell></TableCell>
</TableRow>
</TableBody>
</>
),
},
}
const data = [
{
category: 'Основной',
description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor',
id: '01',
link: 'Какая-то ссылка кудато на какой-то источник с информациейо ссылка кудато на какой-то',
title: 'Web Basic',
type: 'Читать',
},
{
category: 'Основной',
description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor',
id: '02',
link: 'Какая-то ссылка куда-то',
title: 'Web Basic',
type: 'Читать',
},
{
category: 'Основной',
description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor',
id: '03',
link: 'Какая-то ссылка кудато на какой-то источник с информациейо ссылка кудато на какой-то. Какая-то ссылка кудато на какой-то источник с информациейо ссылка куда-то на какой-то',
title: 'Web Basic',
type: 'Читать',
},
{
category: 'Основной',
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor',
id: '01',
link: 'Какая-то ссылка кудато на какой-то источник с информациейо ссылка кудато на какой-то',
title: 'Web Basic',
type: 'Читать',
},
{
category: 'Основной',
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor',
id: '02',
link: 'Какая-то ссылка куда-то',
title: 'Web Basic',
type: 'Читать',
},
{
category: 'Основной',
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor',
id: '03',
link: 'Какая-то ссылка кудато на какой-то источник с информациейо ссылка кудато на какой-то. Какая-то ссылка кудато на какой-то источник с информациейо ссылка куда-то на какой-то',
title: 'Web Basic',
type: 'Читать',
},
]
export const WithMapMethod = {
args: {
children: (
<>
<TableHead>
<TableRow>
<TableHeadCell>Название</TableHeadCell>
<TableHeadCell align={'center'}>Описание</TableHeadCell>
<TableHeadCell>Ссылка</TableHeadCell>
<TableHeadCell>Тип</TableHeadCell>
<TableHeadCell>Вид</TableHeadCell>
</TableRow>
</TableHead>
<TableBody>
{data.map(item => (
<TableRow key={item.id}>
<TableCell>{item.title}</TableCell>
<TableCell>{item.description}</TableCell>
<TableCell>{item.link}</TableCell>
<TableCell>{item.category}</TableCell>
<TableCell>{item.type}</TableCell>
</TableRow>
))}
</TableBody>
</>
),
},
args: {
children: (
<>
<TableHead>
<TableRow>
<TableHeadCell>Название</TableHeadCell>
<TableHeadCell align={'center'}>Описание</TableHeadCell>
<TableHeadCell>Ссылка</TableHeadCell>
<TableHeadCell>Тип</TableHeadCell>
<TableHeadCell>Вид</TableHeadCell>
</TableRow>
</TableHead>
<TableBody>
{data.map(item => (
<TableRow key={item.id}>
<TableCell>{item.title}</TableCell>
<TableCell>{item.description}</TableCell>
<TableCell>{item.link}</TableCell>
<TableCell>{item.category}</TableCell>
<TableCell>{item.type}</TableCell>
</TableRow>
))}
</TableBody>
</>
),
},
}
export const Empty = {
render: () => <TableEmpty />,
render: () => <TableEmpty />,
}

View File

@@ -6,125 +6,123 @@ import { clsx } from 'clsx'
import s from './table.module.scss'
export const Table = forwardRef<HTMLTableElement, ComponentPropsWithoutRef<'table'>>(
({ className, ...rest }, ref) => {
const classNames = {
table: clsx(className, s.table),
}
return <table className={classNames.table} {...rest} ref={ref} />
({ className, ...rest }, ref) => {
const classNames = {
table: clsx(className, s.table),
}
return <table className={classNames.table} {...rest} ref={ref} />
}
)
export const TableHead = forwardRef<ElementRef<'thead'>, ComponentPropsWithoutRef<'thead'>>(
({ ...rest }, ref) => {
return <thead {...rest} ref={ref} />
}
({ ...rest }, ref) => {
return <thead {...rest} ref={ref} />
}
)
export const TableBody = forwardRef<ElementRef<'tbody'>, ComponentPropsWithoutRef<'tbody'>>(
({ ...rest }, ref) => {
return <tbody {...rest} ref={ref} />
}
({ ...rest }, ref) => {
return <tbody {...rest} ref={ref} />
}
)
export const TableRow = forwardRef<ElementRef<'tr'>, ComponentPropsWithoutRef<'tr'>>(
({ ...rest }, ref) => {
return <tr {...rest} ref={ref} />
}
({ ...rest }, ref) => {
return <tr {...rest} ref={ref} />
}
)
export const TableHeadCell = forwardRef<ElementRef<'th'>, ComponentPropsWithoutRef<'th'>>(
({ children, className, ...rest }, ref) => {
const classNames = {
headCell: clsx(className, s.headCell),
}
return (
<th className={classNames.headCell} {...rest} ref={ref}>
<span>{children}</span>
</th>
)
({ children, className, ...rest }, ref) => {
const classNames = {
headCell: clsx(className, s.headCell),
}
return (
<th className={classNames.headCell} {...rest} ref={ref}>
<span>{children}</span>
</th>
)
}
)
export const TableCell = forwardRef<ElementRef<'td'>, ComponentPropsWithoutRef<'td'>>(
({ className, ...rest }, ref) => {
const classNames = {
cell: clsx(className, s.tableCell),
}
return <td className={classNames.cell} {...rest} ref={ref} />
({ className, ...rest }, ref) => {
const classNames = {
cell: clsx(className, s.tableCell),
}
return <td className={classNames.cell} {...rest} ref={ref} />
}
)
export const TableEmpty: FC<ComponentProps<'div'> & { mb?: string; mt?: string }> = ({
className,
mb,
mt = '89px',
className,
mb,
mt = '89px',
}) => {
const classNames = {
empty: clsx(className, s.empty),
}
const classNames = {
empty: clsx(className, s.empty),
}
return (
<Typography
className={classNames.empty}
style={{ marginBottom: mb, marginTop: mt }}
variant={'h2'}
>
Пока тут еще нет данных! :(
</Typography>
)
return (
<Typography
className={classNames.empty}
style={{ marginBottom: mb, marginTop: mt }}
variant={'h2'}
>
Пока тут еще нет данных! :(
</Typography>
)
}
export type Column = {
key: string
sortable?: boolean
title: string
key: string
sortable?: boolean
title: string
}
export type Sort = {
direction: 'asc' | 'desc'
key: string
direction: 'asc' | 'desc'
key: string
} | null
export const TableHeader: FC<
Omit<
ComponentPropsWithoutRef<'thead'> & {
columns: Column[]
onSort?: (sort: Sort) => void
sort?: Sort
},
'children'
>
Omit<
ComponentPropsWithoutRef<'thead'> & {
columns: Column[]
onSort?: (sort: Sort) => void
sort?: Sort
},
'children'
>
> = ({ columns, onSort, sort, ...restProps }) => {
const handleSort = (key: string, sortable?: boolean) => () => {
if (!onSort || !sortable) {
return
}
if (sort?.key !== key) {
return onSort({ direction: 'asc', key })
}
if (sort.direction === 'desc') {
return onSort(null)
}
return onSort({
direction: sort?.direction === 'asc' ? 'desc' : 'asc',
key,
})
const handleSort = (key: string, sortable?: boolean) => () => {
if (!onSort || !sortable) {
return
}
return (
<TableHead {...restProps}>
<TableRow>
{columns.map(({ key, sortable = true, title }) => (
<TableHeadCell key={key} onClick={handleSort(key, sortable)}>
{title}
{sort && sort.key === key && (
<span>{sort.direction === 'asc' ? '▲' : '▼'}</span>
)}
</TableHeadCell>
))}
</TableRow>
</TableHead>
)
if (sort?.key !== key) {
return onSort({ direction: 'asc', key })
}
if (sort.direction === 'desc') {
return onSort(null)
}
return onSort({
direction: sort?.direction === 'asc' ? 'desc' : 'asc',
key,
})
}
return (
<TableHead {...restProps}>
<TableRow>
{columns.map(({ key, sortable = true, title }) => (
<TableHeadCell key={key} onClick={handleSort(key, sortable)}>
{title}
{sort && sort.key === key && <span>{sort.direction === 'asc' ? '▲' : '▼'}</span>}
</TableHeadCell>
))}
</TableRow>
</TableHead>
)
}

View File

@@ -8,28 +8,28 @@ import s from './tabs.module.scss'
const Tabs = TabsPrimitive.Root
const TabsList = forwardRef<
ElementRef<typeof TabsPrimitive.List>,
ComponentPropsWithoutRef<typeof TabsPrimitive.List>
ElementRef<typeof TabsPrimitive.List>,
ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => (
<TabsPrimitive.List className={clsx(s.list, className)} ref={ref} {...props} />
<TabsPrimitive.List className={clsx(s.list, className)} ref={ref} {...props} />
))
TabsList.displayName = TabsPrimitive.List.displayName
const TabsTrigger = forwardRef<
ElementRef<typeof TabsPrimitive.Trigger>,
ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
ElementRef<typeof TabsPrimitive.Trigger>,
ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger className={clsx(s.trigger, className)} ref={ref} {...props} />
<TabsPrimitive.Trigger className={clsx(s.trigger, className)} ref={ref} {...props} />
))
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
const TabsContent = forwardRef<
ElementRef<typeof TabsPrimitive.Content>,
ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
ElementRef<typeof TabsPrimitive.Content>,
ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Content className={clsx(s.content, className)} ref={ref} {...props} />
<TabsPrimitive.Content className={clsx(s.content, className)} ref={ref} {...props} />
))
TabsContent.displayName = TabsPrimitive.Content.displayName

View File

@@ -3,33 +3,33 @@ import type { Meta, StoryObj } from '@storybook/react'
import { TextField } from './'
const meta = {
component: TextField,
tags: ['autodocs'],
title: 'Components/TextField',
component: TextField,
tags: ['autodocs'],
title: 'Components/TextField',
} satisfies Meta<typeof TextField>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {
label: 'Label',
placeholder: 'Placeholder',
},
args: {
label: 'Label',
placeholder: 'Placeholder',
},
}
export const Password: Story = {
args: {
label: 'Label',
placeholder: 'Password',
type: 'password',
},
args: {
label: 'Label',
placeholder: 'Password',
type: 'password',
},
}
export const Error: Story = {
args: {
errorMessage: 'Error message',
label: 'Input with error',
value: 'Wrong value',
},
args: {
errorMessage: 'Error message',
label: 'Input with error',
value: 'Wrong value',
},
}

View File

@@ -7,91 +7,91 @@ import { clsx } from 'clsx'
import s from './text-field.module.scss'
export type TextFieldProps = {
containerProps?: ComponentProps<'div'>
errorMessage?: string
label?: string
labelProps?: ComponentProps<'label'>
onValueChange?: (value: string) => void
search?: boolean
containerProps?: ComponentProps<'div'>
errorMessage?: string
label?: string
labelProps?: ComponentProps<'label'>
onValueChange?: (value: string) => void
search?: boolean
} & ComponentPropsWithoutRef<'input'>
export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
(
{
className,
containerProps,
errorMessage,
label,
labelProps,
onChange,
onValueChange,
placeholder,
search,
type,
...restProps
},
ref
) => {
const [showPassword, setShowPassword] = useState(false)
(
{
className,
containerProps,
errorMessage,
label,
labelProps,
onChange,
onValueChange,
placeholder,
search,
type,
...restProps
},
ref
) => {
const [showPassword, setShowPassword] = useState(false)
const isShowPasswordButtonShown = type === 'password'
const isShowPasswordButtonShown = type === 'password'
const finalType = getFinalType(type, showPassword)
const finalType = getFinalType(type, showPassword)
function handleChange(e: ChangeEvent<HTMLInputElement>) {
onChange?.(e)
onValueChange?.(e.target.value)
}
const classNames = {
error: clsx(s.error),
field: clsx(s.field, !!errorMessage && s.error, search && s.hasLeadingIcon, className),
fieldContainer: clsx(s.fieldContainer),
label: clsx(s.label, labelProps?.className),
leadingIcon: s.leadingIcon,
root: clsx(s.root, containerProps?.className),
}
return (
<div className={classNames.root}>
{label && (
<Typography as={'label'} className={classNames.label} variant={'body2'}>
{label}
</Typography>
)}
<div className={classNames.fieldContainer}>
{search && <Search className={classNames.leadingIcon} />}
<input
className={classNames.field}
onChange={handleChange}
placeholder={placeholder}
ref={ref}
type={finalType}
{...restProps}
/>
{isShowPasswordButtonShown && (
<button
className={s.showPassword}
onClick={() => setShowPassword(prev => !prev)}
type={'button'}
>
{showPassword ? <VisibilityOff /> : <Eye />}
</button>
)}
</div>
<Typography className={classNames.error} variant={'error'}>
{errorMessage}
</Typography>
</div>
)
function handleChange(e: ChangeEvent<HTMLInputElement>) {
onChange?.(e)
onValueChange?.(e.target.value)
}
const classNames = {
error: clsx(s.error),
field: clsx(s.field, !!errorMessage && s.error, search && s.hasLeadingIcon, className),
fieldContainer: clsx(s.fieldContainer),
label: clsx(s.label, labelProps?.className),
leadingIcon: s.leadingIcon,
root: clsx(s.root, containerProps?.className),
}
return (
<div className={classNames.root}>
{label && (
<Typography as={'label'} className={classNames.label} variant={'body2'}>
{label}
</Typography>
)}
<div className={classNames.fieldContainer}>
{search && <Search className={classNames.leadingIcon} />}
<input
className={classNames.field}
onChange={handleChange}
placeholder={placeholder}
ref={ref}
type={finalType}
{...restProps}
/>
{isShowPasswordButtonShown && (
<button
className={s.showPassword}
onClick={() => setShowPassword(prev => !prev)}
type={'button'}
>
{showPassword ? <VisibilityOff /> : <Eye />}
</button>
)}
</div>
<Typography className={classNames.error} variant={'error'}>
{errorMessage}
</Typography>
</div>
)
}
)
function getFinalType(type: ComponentProps<'input'>['type'], showPassword: boolean) {
if (type === 'password' && showPassword) {
return 'text'
}
if (type === 'password' && showPassword) {
return 'text'
}
return type
return type
}

View File

@@ -3,121 +3,121 @@ import type { Meta, StoryObj } from '@storybook/react'
import { Typography } from './'
const meta = {
argTypes: {
variant: {
control: { type: 'radio' },
options: [
'large',
'h1',
'h2',
'h3',
'body1',
'body2',
'subtitle1',
'subtitle2',
'caption',
'overline',
'link1',
'link2',
'error',
],
},
argTypes: {
variant: {
control: { type: 'radio' },
options: [
'large',
'h1',
'h2',
'h3',
'body1',
'body2',
'subtitle1',
'subtitle2',
'caption',
'overline',
'link1',
'link2',
'error',
],
},
component: Typography,
tags: ['autodocs'],
title: 'Components/Typography',
},
component: Typography,
tags: ['autodocs'],
title: 'Components/Typography',
} satisfies Meta<typeof Typography>
export default meta
type Story = StoryObj<typeof meta>
export const Large: Story = {
args: {
children: 'Card content',
variant: 'large',
},
args: {
children: 'Card content',
variant: 'large',
},
}
export const H1: Story = {
args: {
children: 'Card content',
variant: 'h1',
},
args: {
children: 'Card content',
variant: 'h1',
},
}
export const H2: Story = {
args: {
children: 'Card content',
variant: 'h2',
},
args: {
children: 'Card content',
variant: 'h2',
},
}
export const H3: Story = {
args: {
children: 'Card content',
variant: 'h3',
},
args: {
children: 'Card content',
variant: 'h3',
},
}
export const Body1: Story = {
args: {
children: 'Card content',
variant: 'body1',
},
args: {
children: 'Card content',
variant: 'body1',
},
}
export const Body2: Story = {
args: {
children: 'Card content',
variant: 'body2',
},
args: {
children: 'Card content',
variant: 'body2',
},
}
export const Subtitle1: Story = {
args: {
children: 'Card content',
variant: 'subtitle1',
},
args: {
children: 'Card content',
variant: 'subtitle1',
},
}
export const Subtitle2: Story = {
args: {
children: 'Card content',
variant: 'subtitle2',
},
args: {
children: 'Card content',
variant: 'subtitle2',
},
}
export const Caption: Story = {
args: {
children: 'Card content',
variant: 'caption',
},
args: {
children: 'Card content',
variant: 'caption',
},
}
export const Overline: Story = {
args: {
children: 'Card content',
variant: 'overline',
},
args: {
children: 'Card content',
variant: 'overline',
},
}
export const Link1: Story = {
args: {
children: 'Card content',
variant: 'link1',
},
args: {
children: 'Card content',
variant: 'link1',
},
}
export const Link2: Story = {
args: {
children: 'Card content',
variant: 'link2',
},
args: {
children: 'Card content',
variant: 'link2',
},
}
export const Error: Story = {
args: {
children: 'Card content',
variant: 'error',
},
args: {
children: 'Card content',
variant: 'error',
},
}

View File

@@ -5,33 +5,33 @@ import { clsx } from 'clsx'
import s from './typography.module.scss'
export interface TextProps<T extends ElementType> {
as?: T
children?: ReactNode
className?: string
variant?:
| 'body1'
| 'body2'
| 'caption'
| 'error'
| 'h1'
| 'h2'
| 'h3'
| 'large'
| 'link1'
| 'link2'
| 'overline'
| 'subtitle1'
| 'subtitle2'
as?: T
children?: ReactNode
className?: string
variant?:
| 'body1'
| 'body2'
| 'caption'
| 'error'
| 'h1'
| 'h2'
| 'h3'
| 'large'
| 'link1'
| 'link2'
| 'overline'
| 'subtitle1'
| 'subtitle2'
}
export function Typography<T extends ElementType = 'p'>({
as,
className,
variant = 'body1',
...restProps
as,
className,
variant = 'body1',
...restProps
}: TextProps<T> & Omit<ComponentPropsWithoutRef<T>, keyof TextProps<T>>) {
const classNames = clsx(s.text, s[variant], className)
const Component = as || 'p'
const classNames = clsx(s.text, s[variant], className)
const Component = as || 'p'
return <Component className={classNames} {...restProps} />
return <Component className={classNames} {...restProps} />
}

View File

@@ -8,7 +8,7 @@ import '@fontsource/roboto/700.css'
import './styles/index.scss'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>
<StrictMode>
<App />
</StrictMode>
)

View File

@@ -6,26 +6,26 @@ import { Pagination } from '@/components/ui/pagination'
import { useGetDeckByIdQuery, useGetDeckCardsQuery } from '@/services'
export const DeckPage = () => {
const { deckId } = useParams()
const [currentPage, setCurrentPage] = useState(1)
const { data: deckData } = useGetDeckByIdQuery({ id: deckId || '' })
const { data: cardsData } = useGetDeckCardsQuery({ id: deckId || '' })
const { deckId } = useParams()
const [currentPage, setCurrentPage] = useState(1)
const { data: deckData } = useGetDeckByIdQuery({ id: deckId || '' })
const { data: cardsData } = useGetDeckCardsQuery({ id: deckId || '' })
const learnLink = `/decks/${deckId}/learn`
const learnLink = `/decks/${deckId}/learn`
return (
<div>
<Typography variant={'large'}>{deckData?.name}</Typography>
<Button as={Link} to={learnLink}>
Learn
</Button>
<TextField placeholder={'Search cards'} search />
<CardsTable cards={cardsData?.items} />
<Pagination
count={cardsData?.pagination?.totalPages || 1}
onChange={setCurrentPage}
page={currentPage}
/>
</div>
)
return (
<div>
<Typography variant={'large'}>{deckData?.name}</Typography>
<Button as={Link} to={learnLink}>
Learn
</Button>
<TextField placeholder={'Search cards'} search />
<CardsTable cards={cardsData?.items} />
<Pagination
count={cardsData?.pagination?.totalPages || 1}
onChange={setCurrentPage}
page={currentPage}
/>
</div>
)
}

View File

@@ -1,24 +1,23 @@
import { useState } from 'react'
import { Button, Page, Slider, TextField, Typography } from '@/components'
import { DecksTable } from '@/components'
import { Button, DecksTable, Page, Slider, TextField, Typography } from '@/components'
import { DeckDialog } from '@/components/decks/deck-dialog'
import { DeleteDeckDialog } from '@/components/decks/delete-deck-dialog'
import { Pagination } from '@/components/ui/pagination'
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
import {
Tab,
useCreateDeckMutation,
useDeleteDeckMutation,
useGetDecksQuery,
useUpdateDeckMutation,
Tab,
useCreateDeckMutation,
useDeleteDeckMutation,
useGetDecksQuery,
useUpdateDeckMutation,
} from '@/services/decks'
import {
selectDecksCurrentPage,
selectDecksCurrentTab,
selectDecksMaxCards,
selectDecksMinCards,
selectDecksSearch,
selectDecksCurrentPage,
selectDecksCurrentTab,
selectDecksMaxCards,
selectDecksMinCards,
selectDecksSearch,
} from '@/services/decks/decks.selectors'
import { decksSlice } from '@/services/decks/decks.slice'
import { useAppDispatch, useAppSelector } from '@/services/store'
@@ -26,132 +25,127 @@ import { useAppDispatch, useAppSelector } from '@/services/store'
import s from './decks-page.module.scss'
export const DecksPage = () => {
const [showCreateModal, setShowCreateModal] = useState(false)
const [deckToDeleteId, setDeckToDeleteId] = useState<null | string>(null)
const [deckToEditId, setDeckToEditId] = useState<null | string>(null)
const [showCreateModal, setShowCreateModal] = useState(false)
const [deckToDeleteId, setDeckToDeleteId] = useState<null | string>(null)
const [deckToEditId, setDeckToEditId] = useState<null | string>(null)
const showEditModal = !!deckToEditId
const showEditModal = !!deckToEditId
const dispatch = useAppDispatch()
const currentPage = useAppSelector(selectDecksCurrentPage)
const minCards = useAppSelector(selectDecksMinCards)
const maxCards = useAppSelector(selectDecksMaxCards)
const currentTab = useAppSelector(selectDecksCurrentTab)
const search = useAppSelector(selectDecksSearch)
const setCurrentPage = (page: number) => dispatch(decksSlice.actions.setCurrentPage(page))
const setMinCards = (minCards: number) => dispatch(decksSlice.actions.setMinCards(minCards))
const setMaxCards = (maxCards: number) => dispatch(decksSlice.actions.setMaxCards(maxCards))
const setSearch = (search: string) => dispatch(decksSlice.actions.setSearch(search))
const setCurrentTab = (tab: Tab) => dispatch(decksSlice.actions.setCurrentTab(tab))
const dispatch = useAppDispatch()
const currentPage = useAppSelector(selectDecksCurrentPage)
const minCards = useAppSelector(selectDecksMinCards)
const maxCards = useAppSelector(selectDecksMaxCards)
const currentTab = useAppSelector(selectDecksCurrentTab)
const search = useAppSelector(selectDecksSearch)
const setCurrentPage = (page: number) => dispatch(decksSlice.actions.setCurrentPage(page))
const setMinCards = (minCards: number) => dispatch(decksSlice.actions.setMinCards(minCards))
const setMaxCards = (maxCards: number) => dispatch(decksSlice.actions.setMaxCards(maxCards))
const setSearch = (search: string) => dispatch(decksSlice.actions.setSearch(search))
const setCurrentTab = (tab: Tab) => dispatch(decksSlice.actions.setCurrentTab(tab))
const resetFilters = () => {
dispatch(decksSlice.actions.resetFilters())
setRangeValue([0, decks?.maxCardsCount || undefined])
}
const resetFilters = () => {
dispatch(decksSlice.actions.resetFilters())
setRangeValue([0, decks?.maxCardsCount || undefined])
}
const [rangeValue, setRangeValue] = useState([minCards, maxCards])
const [rangeValue, setRangeValue] = useState([minCards, maxCards])
const handleSliderCommitted = (value: number[]) => {
setMinCards(value[0])
setMaxCards(value[1])
}
const currentUserId = 'f2be95b9-4d07-4751-a775-bd612fc9553a'
const authorId = currentTab === 'my' ? currentUserId : undefined
const handleSliderCommitted = (value: number[]) => {
setMinCards(value[0])
setMaxCards(value[1])
}
const currentUserId = 'f2be95b9-4d07-4751-a775-bd612fc9553a'
const authorId = currentTab === 'my' ? currentUserId : undefined
const { data: decks } = useGetDecksQuery({
authorId,
currentPage,
maxCardsCount: maxCards,
minCardsCount: minCards,
name: search,
})
const { data: decks } = useGetDecksQuery({
authorId,
currentPage,
maxCardsCount: maxCards,
minCardsCount: minCards,
name: search,
})
const showConfirmDeleteModal = !!deckToDeleteId
const deckToDeleteName = decks?.items?.find(deck => deck.id === deckToDeleteId)?.name
const showConfirmDeleteModal = !!deckToDeleteId
const deckToDeleteName = decks?.items?.find(deck => deck.id === deckToDeleteId)?.name
const deckToEdit = decks?.items?.find(deck => deck.id === deckToEditId)
const deckToEdit = decks?.items?.find(deck => deck.id === deckToEditId)
const [createDeck] = useCreateDeckMutation()
const [deleteDeck] = useDeleteDeckMutation()
const [updateDeck] = useUpdateDeckMutation()
const openCreateModal = () => setShowCreateModal(true)
const [createDeck] = useCreateDeckMutation()
const [deleteDeck] = useDeleteDeckMutation()
const [updateDeck] = useUpdateDeckMutation()
const openCreateModal = () => setShowCreateModal(true)
if (!decks) {
return <div>loading...</div>
}
if (!decks) {
return <div>loading...</div>
}
return (
<Page>
<DeleteDeckDialog
deckName={deckToDeleteName ?? 'Selected deck'}
onCancel={() => setDeckToDeleteId(null)}
onConfirm={() => {
deleteDeck({ id: deckToDeleteId ?? '' })
setDeckToDeleteId(null)
}}
onOpenChange={() => setDeckToDeleteId(null)}
open={showConfirmDeleteModal}
/>
<DeckDialog
defaultValues={deckToEdit}
key={deckToEditId}
onConfirm={data => {
if (!deckToEditId) {
return
}
return (
<Page>
<DeleteDeckDialog
deckName={deckToDeleteName ?? 'Selected deck'}
onCancel={() => setDeckToDeleteId(null)}
onConfirm={() => {
deleteDeck({ id: deckToDeleteId ?? '' })
setDeckToDeleteId(null)
}}
onOpenChange={() => setDeckToDeleteId(null)}
open={showConfirmDeleteModal}
/>
<DeckDialog
defaultValues={deckToEdit}
key={deckToEditId}
onConfirm={data => {
if (!deckToEditId) {
return
}
updateDeck({ id: deckToEditId, ...data })
}}
onOpenChange={() => setDeckToEditId(null)}
open={showEditModal}
/>
<div className={s.root}>
<div className={s.header}>
<Typography variant={'large'}>Decks</Typography>
<Button onClick={openCreateModal}>Add new deck</Button>
<DeckDialog
onCancel={() => setShowCreateModal(false)}
onConfirm={createDeck}
onOpenChange={setShowCreateModal}
open={showCreateModal}
/>
</div>
<div className={s.filters}>
<TextField
onValueChange={setSearch}
placeholder={'Search'}
search
value={search}
/>
<Tabs onValueChange={value => setCurrentTab(value as Tab)} value={currentTab}>
<TabsList>
<TabsTrigger value={'my'}>My decks</TabsTrigger>
<TabsTrigger value={'all'}>All decks</TabsTrigger>
</TabsList>
</Tabs>
<Slider
max={decks?.maxCardsCount || 0}
min={0}
onValueChange={setRangeValue}
onValueCommit={handleSliderCommitted}
value={rangeValue}
/>
<Button onClick={resetFilters} variant={'secondary'}>
Clear filters
</Button>
</div>
<DecksTable
currentUserId={currentUserId}
decks={decks?.items}
onDeleteClick={setDeckToDeleteId}
onEditClick={setDeckToEditId}
/>
<Pagination
count={decks?.pagination?.totalPages || 1}
onChange={setCurrentPage}
page={currentPage}
/>
</div>
</Page>
)
updateDeck({ id: deckToEditId, ...data })
}}
onOpenChange={() => setDeckToEditId(null)}
open={showEditModal}
/>
<div className={s.root}>
<div className={s.header}>
<Typography variant={'large'}>Decks</Typography>
<Button onClick={openCreateModal}>Add new deck</Button>
<DeckDialog
onCancel={() => setShowCreateModal(false)}
onConfirm={createDeck}
onOpenChange={setShowCreateModal}
open={showCreateModal}
/>
</div>
<div className={s.filters}>
<TextField onValueChange={setSearch} placeholder={'Search'} search value={search} />
<Tabs onValueChange={value => setCurrentTab(value as Tab)} value={currentTab}>
<TabsList>
<TabsTrigger value={'my'}>My decks</TabsTrigger>
<TabsTrigger value={'all'}>All decks</TabsTrigger>
</TabsList>
</Tabs>
<Slider
max={decks?.maxCardsCount || 0}
min={0}
onValueChange={setRangeValue}
onValueCommit={handleSliderCommitted}
value={rangeValue}
/>
<Button onClick={resetFilters} variant={'secondary'}>
Clear filters
</Button>
</div>
<DecksTable
currentUserId={currentUserId}
decks={decks?.items}
onDeleteClick={setDeckToDeleteId}
onEditClick={setDeckToEditId}
/>
<Pagination
count={decks?.pagination?.totalPages || 1}
onChange={setCurrentPage}
page={currentPage}
/>
</div>
</Page>
)
}

View File

@@ -1,9 +1,9 @@
import { Page, SignIn } from '@/components'
export const SignInPage = () => {
return (
<Page>
<SignIn onSubmit={() => {}} />
</Page>
)
return (
<Page>
<SignIn onSubmit={() => {}} />
</Page>
)
}

View File

@@ -1,51 +1,51 @@
import {
Navigate,
Outlet,
RouteObject,
RouterProvider,
createBrowserRouter,
Navigate,
Outlet,
RouteObject,
RouterProvider,
createBrowserRouter,
} from 'react-router-dom'
import { DecksPage, SignInPage } from './pages'
import { DeckPage } from '@/pages/deck-page/deck-page'
const publicRoutes: RouteObject[] = [
{
children: [
{
element: <SignInPage />,
path: '/login',
},
],
element: <Outlet />,
},
{
children: [
{
element: <SignInPage />,
path: '/login',
},
],
element: <Outlet />,
},
]
const privateRoutes: RouteObject[] = [
{
element: <DecksPage />,
path: '/',
},
{
element: <DeckPage />,
path: '/decks/:deckId',
},
{
element: <DecksPage />,
path: '/',
},
{
element: <DeckPage />,
path: '/decks/:deckId',
},
]
const router = createBrowserRouter([
{
children: privateRoutes,
element: <PrivateRoutes />,
},
...publicRoutes,
{
children: privateRoutes,
element: <PrivateRoutes />,
},
...publicRoutes,
])
export const Router = () => {
return <RouterProvider router={router} />
return <RouterProvider router={router} />
}
function PrivateRoutes() {
const isAuthenticated = true
const isAuthenticated = true
return isAuthenticated ? <Outlet /> : <Navigate to={'/login'} />
return isAuthenticated ? <Outlet /> : <Navigate to={'/login'} />
}

View File

@@ -1,14 +1,14 @@
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
export const baseApi = createApi({
baseQuery: fetchBaseQuery({
baseUrl: 'https://api.flashcards.andrii.es',
credentials: 'include',
prepareHeaders: headers => {
headers.append('x-auth-skip', 'true')
},
}),
endpoints: () => ({}),
reducerPath: 'baseApi',
tagTypes: ['Decks'],
baseQuery: fetchBaseQuery({
baseUrl: 'https://api.flashcards.andrii.es',
credentials: 'include',
prepareHeaders: headers => {
headers.append('x-auth-skip', 'true')
},
}),
endpoints: () => ({}),
reducerPath: 'baseApi',
tagTypes: ['Decks'],
})

View File

@@ -1,68 +1,68 @@
import {
CardsResponse,
CreateDeckArgs,
DeckResponse,
DecksResponse,
GetDecksArgs,
UpdateDeckArgs,
CardsResponse,
CreateDeckArgs,
DeckResponse,
DecksResponse,
GetDecksArgs,
UpdateDeckArgs,
} from './decks.types'
import { baseApi } from '@/services'
const decksService = baseApi.injectEndpoints({
endpoints: builder => ({
createDeck: builder.mutation<DeckResponse, CreateDeckArgs>({
invalidatesTags: ['Decks'],
onQueryStarted: async (_, { dispatch, getCacheEntry, getState, queryFulfilled }) => {
const data = getCacheEntry()
const state = getState()
endpoints: builder => ({
createDeck: builder.mutation<DeckResponse, CreateDeckArgs>({
invalidatesTags: ['Decks'],
onQueryStarted: async (_, { dispatch, getCacheEntry, getState, queryFulfilled }) => {
const data = getCacheEntry()
const state = getState()
decksService.util.re
await queryFulfilled
},
query: body => ({
body,
method: 'POST',
url: `v1/decks`,
}),
}),
deleteDeck: builder.mutation<void, { id: string }>({
invalidatesTags: ['Decks'],
query: ({ id }) => ({
method: 'DELETE',
url: `v1/decks/${id}`,
}),
}),
getDeckById: builder.query<DeckResponse, { id: string }>({
query: ({ id }) => `v1/decks/${id}`,
}),
getDeckCards: builder.query<CardsResponse, { id: string }>({
query: ({ id }) => `v1/decks/${id}/cards`,
}),
getDecks: builder.query<DecksResponse, GetDecksArgs | void>({
providesTags: ['Decks'],
query: args => {
return {
params: args ?? undefined,
url: `v1/decks`,
}
},
}),
updateDeck: builder.mutation<DeckResponse, UpdateDeckArgs>({
invalidatesTags: ['Decks'],
query: ({ id, ...body }) => ({
body,
method: 'PATCH',
url: `v1/decks/${id}`,
}),
}),
decksService.util.re
await queryFulfilled
},
query: body => ({
body,
method: 'POST',
url: `v1/decks`,
}),
}),
deleteDeck: builder.mutation<void, { id: string }>({
invalidatesTags: ['Decks'],
query: ({ id }) => ({
method: 'DELETE',
url: `v1/decks/${id}`,
}),
}),
getDeckById: builder.query<DeckResponse, { id: string }>({
query: ({ id }) => `v1/decks/${id}`,
}),
getDeckCards: builder.query<CardsResponse, { id: string }>({
query: ({ id }) => `v1/decks/${id}/cards`,
}),
getDecks: builder.query<DecksResponse, GetDecksArgs | void>({
providesTags: ['Decks'],
query: args => {
return {
params: args ?? undefined,
url: `v1/decks`,
}
},
}),
updateDeck: builder.mutation<DeckResponse, UpdateDeckArgs>({
invalidatesTags: ['Decks'],
query: ({ id, ...body }) => ({
body,
method: 'PATCH',
url: `v1/decks/${id}`,
}),
}),
}),
})
export const {
useCreateDeckMutation,
useDeleteDeckMutation,
useGetDeckByIdQuery,
useGetDeckCardsQuery,
useGetDecksQuery,
useUpdateDeckMutation,
useCreateDeckMutation,
useDeleteDeckMutation,
useGetDeckByIdQuery,
useGetDeckCardsQuery,
useGetDecksQuery,
useUpdateDeckMutation,
} = decksService

View File

@@ -2,42 +2,42 @@ import { Tab } from '@/services'
import { PayloadAction, createSlice } from '@reduxjs/toolkit'
export const decksSlice = createSlice({
initialState: {
currentPage: 1,
currentTab: 'all' as Tab,
maxCards: undefined as number | undefined,
minCards: 0,
perPage: 10,
search: '',
initialState: {
currentPage: 1,
currentTab: 'all' as Tab,
maxCards: undefined as number | undefined,
minCards: 0,
perPage: 10,
search: '',
},
name: 'decks',
reducers: {
resetCurrentPage: state => {
state.currentPage = 1
},
name: 'decks',
reducers: {
resetCurrentPage: state => {
state.currentPage = 1
},
resetFilters: state => {
state.search = ''
state.currentTab = 'all'
state.minCards = 0
state.maxCards = undefined
},
setCurrentPage: (state, action: PayloadAction<number>) => {
state.currentPage = action.payload
},
setCurrentTab: (state, action: PayloadAction<Tab>) => {
state.currentTab = action.payload
},
setMaxCards: (state, action: PayloadAction<number>) => {
state.maxCards = action.payload
},
setMinCards: (state, action: PayloadAction<number>) => {
state.minCards = action.payload
},
setPerPage: (state, action: PayloadAction<number>) => {
state.perPage = action.payload
},
setSearch: (state, action: PayloadAction<string>) => {
state.search = action.payload
},
resetFilters: state => {
state.search = ''
state.currentTab = 'all'
state.minCards = 0
state.maxCards = undefined
},
setCurrentPage: (state, action: PayloadAction<number>) => {
state.currentPage = action.payload
},
setCurrentTab: (state, action: PayloadAction<Tab>) => {
state.currentTab = action.payload
},
setMaxCards: (state, action: PayloadAction<number>) => {
state.maxCards = action.payload
},
setMinCards: (state, action: PayloadAction<number>) => {
state.minCards = action.payload
},
setPerPage: (state, action: PayloadAction<number>) => {
state.perPage = action.payload
},
setSearch: (state, action: PayloadAction<string>) => {
state.search = action.payload
},
},
})

View File

@@ -1,72 +1,72 @@
export type Pagination = {
currentPage: number
itemsPerPage: number
totalItems: number
totalPages: number
currentPage: number
itemsPerPage: number
totalItems: number
totalPages: number
}
export type Author = {
id: string
name: string
id: string
name: string
}
export type Deck = {
author: Author
cardsCount: number
cover?: null | string
created: string
id: string
isBlocked?: boolean | null
isDeleted: boolean | null
isPrivate: boolean
name: string
rating: number
shots: number
updated: string
userId: string
author: Author
cardsCount: number
cover?: null | string
created: string
id: string
isBlocked?: boolean | null
isDeleted: boolean | null
isPrivate: boolean
name: string
rating: number
shots: number
updated: string
userId: string
}
export type DecksResponse = {
items: Deck[]
maxCardsCount: number
pagination: Pagination
items: Deck[]
maxCardsCount: number
pagination: Pagination
}
export type DeckResponse = Deck
export type CardsResponse = {
items: Card[]
pagination: Pagination
items: Card[]
pagination: Pagination
}
export type Card = {
answer: string
answerImg?: null | string
created: string
deckId: string
grade: number
id: string
question: string
questionImg?: null | string
shots: number
updated: string
userId: string
answer: string
answerImg?: null | string
created: string
deckId: string
grade: number
id: string
question: string
questionImg?: null | string
shots: number
updated: string
userId: string
}
export type GetDecksArgs = {
authorId?: string
currentPage?: number
itemsPerPage?: number
maxCardsCount?: number
minCardsCount?: number
name?: string
orderBy?: string
authorId?: string
currentPage?: number
itemsPerPage?: number
maxCardsCount?: number
minCardsCount?: number
name?: string
orderBy?: string
}
export type CreateDeckArgs = {
cover?: string
isPrivate?: boolean
name: string
cover?: string
isPrivate?: boolean
name: string
}
export type UpdateDeckArgs = Partial<CreateDeckArgs> & { id: Deck['id'] }

View File

@@ -6,11 +6,11 @@ import { configureStore } from '@reduxjs/toolkit'
import { setupListeners } from '@reduxjs/toolkit/query/react'
export const store = configureStore({
middleware: getDefaultMiddleware => getDefaultMiddleware().concat(baseApi.middleware),
reducer: {
[baseApi.reducerPath]: baseApi.reducer,
[decksSlice.name]: decksSlice.reducer,
},
middleware: getDefaultMiddleware => getDefaultMiddleware().concat(baseApi.middleware),
reducer: {
[baseApi.reducerPath]: baseApi.reducer,
[decksSlice.name]: decksSlice.reducer,
},
})
export type AppDispatch = typeof store.dispatch

View File

@@ -1,7 +1,7 @@
export function formatDate(date: string | undefined) {
if (!date) {
return ''
}
if (!date) {
return ''
}
return new Date(date).toLocaleDateString('ru-RU')
return new Date(date).toLocaleDateString('ru-RU')
}

View File

@@ -5,8 +5,8 @@ import { defineConfig } from 'vite'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
resolve: {
alias: [{ find: '@', replacement: path.resolve(__dirname, 'src') }],
},
plugins: [react()],
resolve: {
alias: [{ find: '@', replacement: path.resolve(__dirname, 'src') }],
},
})