chore: update deps, apply updated eslint ocnfig

This commit is contained in:
2023-10-10 16:43:55 +02:00
parent d430ee54e2
commit c6aab409b8
70 changed files with 3131 additions and 3160 deletions

View File

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

View File

@@ -14,16 +14,16 @@ export const CheckEmail = ({ email }: Props) => {
return (
<Card className={s.card}>
<Typography variant="large" className={s.title}>
<Typography className={s.title} variant={'large'}>
Check your email
</Typography>
<div className={s.iconContainer}>
<Email />
</div>
<Typography variant="body2" className={s.instructions}>
<Typography className={s.instructions} variant={'body2'}>
{message}
</Typography>
<Button fullWidth as={Link} to={'/sing-in'}>
<Button as={Link} fullWidth to={'/sing-in'}>
Back to Sign in
</Button>
</Card>

View File

@@ -3,9 +3,9 @@ import type { Meta, StoryObj } from '@storybook/react'
import { NewPassword } from './'
const meta = {
title: 'Auth/New password',
component: NewPassword,
tags: ['autodocs'],
title: 'Auth/New password',
} satisfies Meta<typeof NewPassword>
export default meta

View File

@@ -1,9 +1,9 @@
import { DevTool } from '@hookform/devtools'
import { zodResolver } from '@hookform/resolvers/zod'
import { useForm } from 'react-hook-form'
import { z } from 'zod'
import { Button, Card, ControlledTextField, Typography } from '../../ui'
import { DevTool } from '@hookform/devtools'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
import s from './new-password.module.scss'
@@ -19,10 +19,10 @@ type Props = {
export const NewPassword = (props: Props) => {
const { control, handleSubmit } = useForm<FormType>({
resolver: zodResolver(schema),
defaultValues: {
password: '',
},
resolver: zodResolver(schema),
})
const handleFormSubmitted = handleSubmit(props.onSubmit)
@@ -31,18 +31,18 @@ export const NewPassword = (props: Props) => {
<>
<DevTool control={control} />
<Card className={s.card}>
<Typography variant="large" className={s.title}>
<Typography className={s.title} variant={'large'}>
Create new password
</Typography>
<form onSubmit={handleFormSubmitted}>
<ControlledTextField
placeholder={'Password'}
name={'password'}
control={control}
type={'password'}
containerProps={{ className: s.input }}
control={control}
name={'password'}
placeholder={'Password'}
type={'password'}
/>
<Typography variant="caption" className={s.instructions}>
<Typography className={s.instructions} variant={'caption'}>
Create new password and we will send you further instructions to email
</Typography>
<Button fullWidth type={'submit'}>

View File

@@ -3,9 +3,9 @@ import type { Meta, StoryObj } from '@storybook/react'
import { RecoverPassword } from './'
const meta = {
title: 'Auth/Recover password',
component: RecoverPassword,
tags: ['autodocs'],
title: 'Auth/Recover password',
} satisfies Meta<typeof RecoverPassword>
export default meta

View File

@@ -1,10 +1,10 @@
import { DevTool } from '@hookform/devtools'
import { zodResolver } from '@hookform/resolvers/zod'
import { useForm } from 'react-hook-form'
import { Link } from 'react-router-dom'
import { z } from 'zod'
import { Button, Card, ControlledTextField, Typography } from '../../ui'
import { DevTool } from '@hookform/devtools'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
import s from './recover-password.module.scss'
@@ -20,11 +20,11 @@ type Props = {
export const RecoverPassword = (props: Props) => {
const { control, handleSubmit } = useForm<FormType>({
mode: 'onSubmit',
resolver: zodResolver(schema),
defaultValues: {
email: '',
},
mode: 'onSubmit',
resolver: zodResolver(schema),
})
const handleFormSubmitted = handleSubmit(props.onSubmit)
@@ -33,24 +33,24 @@ export const RecoverPassword = (props: Props) => {
<>
<DevTool control={control} />
<Card className={s.card}>
<Typography variant="large" className={s.title}>
<Typography className={s.title} variant={'large'}>
Forgot your password?
</Typography>
<form onSubmit={handleFormSubmitted}>
<div className={s.form}>
<ControlledTextField placeholder={'Email'} name={'email'} control={control} />
<ControlledTextField control={control} name={'email'} placeholder={'Email'} />
</div>
<Typography variant="body2" className={s.instructions}>
<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 variant="body2" className={s.caption}>
<Typography className={s.caption} variant={'body2'}>
Did you remember your password?
</Typography>
<Typography variant="link1" as={Link} to="/sign-in" className={s.loginLink}>
<Typography as={Link} className={s.loginLink} to={'/sign-in'} variant={'link1'}>
Try logging in
</Typography>
</Card>

View File

@@ -3,9 +3,9 @@ import type { Meta, StoryObj } from '@storybook/react'
import { SignIn } from './'
const meta = {
title: 'Auth/Sign in',
component: SignIn,
tags: ['autodocs'],
title: 'Auth/Sign in',
} satisfies Meta<typeof SignIn>
export default meta

View File

@@ -1,10 +1,10 @@
import { DevTool } from '@hookform/devtools'
import { zodResolver } from '@hookform/resolvers/zod'
import { useForm } from 'react-hook-form'
import { Link } from 'react-router-dom'
import { z } from 'zod'
import { Button, Card, ControlledCheckbox, ControlledTextField, Typography } from '../../ui'
import { DevTool } from '@hookform/devtools'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
import s from './sign-in.module.scss'
@@ -22,13 +22,13 @@ type Props = {
export const SignIn = (props: Props) => {
const { control, handleSubmit } = useForm<FormType>({
mode: 'onSubmit',
resolver: zodResolver(schema),
defaultValues: {
email: '',
password: '',
rememberMe: false,
},
mode: 'onSubmit',
resolver: zodResolver(schema),
})
const handleFormSubmitted = handleSubmit(props.onSubmit)
@@ -37,37 +37,37 @@ export const SignIn = (props: Props) => {
<>
<DevTool control={control} />
<Card className={s.card}>
<Typography variant="large" className={s.title}>
<Typography className={s.title} variant={'large'}>
Sign In
</Typography>
<form onSubmit={handleFormSubmitted}>
<div className={s.form}>
<ControlledTextField
placeholder={'Email'}
control={control}
label={'Email'}
name={'email'}
control={control}
placeholder={'Email'}
/>
<ControlledTextField
placeholder={'Password'}
label={'Password'}
type={'password'}
name={'password'}
control={control}
label={'Password'}
name={'password'}
placeholder={'Password'}
type={'password'}
/>
</div>
<ControlledCheckbox
className={s.checkbox}
label={'Remember me'}
control={control}
label={'Remember me'}
name={'rememberMe'}
position={'left'}
/>
<Typography
variant="body2"
as={Link}
to="/recover-password"
className={s.recoverPasswordLink}
to={'/recover-password'}
variant={'body2'}
>
Forgot Password?
</Typography>
@@ -75,10 +75,10 @@ export const SignIn = (props: Props) => {
Sign In
</Button>
</form>
<Typography className={s.caption} variant="body2">
<Typography className={s.caption} variant={'body2'}>
{`Don't have an account?`}
</Typography>
<Typography variant="link1" as={Link} to="/sign-up" className={s.signUpLink}>
<Typography as={Link} className={s.signUpLink} to={'/sign-up'} variant={'link1'}>
Sign Up
</Typography>
</Card>

View File

@@ -3,9 +3,9 @@ import type { Meta, StoryObj } from '@storybook/react'
import { SignUp } from './'
const meta = {
title: 'Auth/Sign up',
component: SignUp,
tags: ['autodocs'],
title: 'Auth/Sign up',
} satisfies Meta<typeof SignUp>
export default meta

View File

@@ -1,11 +1,11 @@
import { DevTool } from '@hookform/devtools'
import { zodResolver } from '@hookform/resolvers/zod'
import { useForm } from 'react-hook-form'
import { Link } from 'react-router-dom'
import { omit } from 'remeda'
import { z } from 'zod'
import { Button, Card, ControlledTextField, Typography } from '../../ui'
import { DevTool } from '@hookform/devtools'
import { zodResolver } from '@hookform/resolvers/zod'
import { omit } from 'remeda'
import { z } from 'zod'
import s from './sign-up.module.scss'
@@ -18,8 +18,8 @@ const schema = z
.superRefine((data, ctx) => {
if (data.password !== data.passwordConfirmation) {
ctx.addIssue({
message: 'Passwords do not match',
code: z.ZodIssueCode.custom,
message: 'Passwords do not match',
path: ['passwordConfirmation'],
})
}
@@ -35,13 +35,13 @@ type Props = {
export const SignUp = (props: Props) => {
const { control, handleSubmit } = useForm<FormType>({
mode: 'onSubmit',
resolver: zodResolver(schema),
defaultValues: {
email: '',
password: '',
passwordConfirmation: '',
},
mode: 'onSubmit',
resolver: zodResolver(schema),
})
const handleFormSubmitted = handleSubmit(data =>
@@ -52,30 +52,30 @@ export const SignUp = (props: Props) => {
<>
<DevTool control={control} />
<Card className={s.card}>
<Typography variant="large" className={s.title}>
<Typography className={s.title} variant={'large'}>
Sign Up
</Typography>
<form onSubmit={handleFormSubmitted}>
<div className={s.form}>
<ControlledTextField
control={control}
label={'Email'}
placeholder={'Email'}
name={'email'}
control={control}
placeholder={'Email'}
/>
<ControlledTextField
placeholder={'Password'}
control={control}
label={'Password'}
type={'password'}
name={'password'}
control={control}
placeholder={'Password'}
type={'password'}
/>
<ControlledTextField
placeholder={'Confirm password'}
label={'Confirm password'}
type={'password'}
name={'passwordConfirmation'}
control={control}
label={'Confirm password'}
name={'passwordConfirmation'}
placeholder={'Confirm password'}
type={'password'}
/>
</div>
<Button className={s.button} fullWidth type={'submit'}>
@@ -83,10 +83,10 @@ export const SignUp = (props: Props) => {
</Button>
</form>
{/* eslint-disable-next-line react/no-unescaped-entities */}
<Typography variant="body2" className={s.caption}>
<Typography className={s.caption} variant={'body2'}>
Already have an account?
</Typography>
<Typography variant="link1" as={Link} to="/sign-in" className={s.signInLink}>
<Typography as={Link} className={s.signInLink} to={'/sign-in'} variant={'link1'}>
Sign In
</Typography>
</Card>

View File

@@ -5,23 +5,23 @@ import { formatDate } from '@/utils'
const columns: Column[] = [
{
key: 'question',
title: 'Question',
sortable: true,
title: 'Question',
},
{
key: 'answer',
title: 'Answer',
sortable: true,
title: 'Answer',
},
{
key: 'updated',
title: 'Last Updated',
sortable: true,
title: 'Last Updated',
},
{
key: 'grade',
title: 'Grade',
sortable: true,
title: 'Grade',
},
]

View File

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

View File

@@ -1,31 +1,31 @@
import { zodResolver } from '@hookform/resolvers/zod'
import { useForm } from 'react-hook-form'
import { ControlledCheckbox, ControlledTextField, Dialog, DialogProps } from '@/components'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
import s from './deck-dialog.module.scss'
import { ControlledCheckbox, ControlledTextField, Dialog, DialogProps } from '@/components'
const newDeckSchema = z.object({
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, 'onOpenChange' | 'open' | 'onCancel'> & {
onConfirm: (data: FormValues) => void
type Props = Pick<DialogProps, 'onCancel' | 'onOpenChange' | 'open'> & {
defaultValues?: FormValues
onConfirm: (data: FormValues) => void
}
export const DeckDialog = ({
onConfirm,
onCancel,
defaultValues = { isPrivate: false, name: '' },
onCancel,
onConfirm,
...dialogProps
}: Props) => {
const { control, handleSubmit, reset } = useForm<FormValues>({
resolver: zodResolver(newDeckSchema),
defaultValues,
resolver: zodResolver(newDeckSchema),
})
const onSubmit = handleSubmit(data => {
onConfirm(data)
@@ -38,13 +38,13 @@ export const DeckDialog = ({
}
return (
<Dialog {...dialogProps} title={'Create new deck'} onConfirm={onSubmit} onCancel={handleCancel}>
<Dialog {...dialogProps} onCancel={handleCancel} onConfirm={onSubmit} title={'Create new deck'}>
<form className={s.content} onSubmit={onSubmit}>
<ControlledTextField name={'name'} control={control} label={'Deck name'} />
<ControlledTextField control={control} label={'Deck name'} name={'name'} />
<ControlledCheckbox
name={'isPrivate'}
control={control}
label={'Private'}
name={'isPrivate'}
position={'left'}
/>
</form>

View File

@@ -1,7 +1,5 @@
import { Link } from 'react-router-dom'
import s from './decks-table.module.scss'
import { Edit2Outline, PlayCircleOutline, TrashOutline } from '@/assets'
import {
Button,
@@ -15,6 +13,8 @@ import {
} from '@/components'
import { Deck } from '@/services/decks'
import { formatDate } from '@/utils'
import s from './decks-table.module.scss'
const columns: Column[] = [
{
key: 'name',
@@ -39,12 +39,12 @@ const columns: Column[] = [
]
type Props = {
currentUserId: string
decks: Deck[] | undefined
onDeleteClick: (id: string) => void
currentUserId: string
onEditClick: (id: string) => void
}
export const DecksTable = ({ decks, onEditClick, onDeleteClick, currentUserId }: Props) => {
export const DecksTable = ({ currentUserId, decks, onDeleteClick, onEditClick }: Props) => {
const handleEditClick = (id: string) => () => onEditClick(id)
const handleDeleteClick = (id: string) => () => onDeleteClick(id)
@@ -55,7 +55,7 @@ export const DecksTable = ({ decks, onEditClick, onDeleteClick, currentUserId }:
{decks?.map(deck => (
<TableRow key={deck.id}>
<TableCell>
<Typography variant={'body2'} as={Link} to={`/decks/${deck.id}`}>
<Typography as={Link} to={`/decks/${deck.id}`} variant={'body2'}>
{deck.name}
</Typography>
</TableCell>
@@ -64,15 +64,15 @@ export const DecksTable = ({ decks, onEditClick, onDeleteClick, currentUserId }:
<TableCell>{deck.author.name}</TableCell>
<TableCell>
<div className={s.iconsContainer}>
<Button variant={'icon'} as={Link} to={`/decks/${deck.id}/learn`}>
<Button as={Link} to={`/decks/${deck.id}/learn`} variant={'icon'}>
<PlayCircleOutline />
</Button>
{deck.author.id === currentUserId && (
<>
<Button variant={'icon'} onClick={handleEditClick(deck.id)}>
<Button onClick={handleEditClick(deck.id)} variant={'icon'}>
<Edit2Outline />
</Button>
<Button variant={'icon'} onClick={handleDeleteClick(deck.id)}>
<Button onClick={handleDeleteClick(deck.id)} variant={'icon'}>
<TrashOutline />
</Button>
</>

View File

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

View File

@@ -1,8 +1,8 @@
import s from './delete-deck-dialog.module.scss'
import { Dialog, DialogProps } from '@/components'
import s from './delete-deck-dialog.module.scss'
export default {}
type Props = Pick<DialogProps, 'onConfirm' | 'onCancel' | 'open' | 'onOpenChange'> & {
type Props = Pick<DialogProps, 'onCancel' | 'onConfirm' | 'onOpenChange' | 'open'> & {
deckName: string
}
export const DeleteDeckDialog = ({ deckName, ...dialogProps }: Props) => {

View File

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

View File

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

View File

@@ -3,15 +3,15 @@ import type { Meta, StoryObj } from '@storybook/react'
import { Button } from './'
const meta = {
title: 'Components/Button',
component: Button,
tags: ['autodocs'],
argTypes: {
variant: {
options: ['primary', 'secondary', 'tertiary', 'link'],
control: { type: 'radio' },
options: ['primary', 'secondary', 'tertiary', 'link'],
},
},
component: Button,
tags: ['autodocs'],
title: 'Components/Button',
} satisfies Meta<typeof Button>
export default meta
@@ -19,47 +19,47 @@ type Story = StoryObj<typeof meta>
export const Primary: Story = {
args: {
variant: 'primary',
children: 'Primary Button',
disabled: false,
variant: 'primary',
},
}
export const Secondary: Story = {
args: {
variant: 'secondary',
children: 'Secondary Button',
disabled: false,
variant: 'secondary',
},
}
export const Tertiary: Story = {
args: {
variant: 'tertiary',
children: 'Tertiary Button',
disabled: false,
variant: 'tertiary',
},
}
export const Link: Story = {
args: {
variant: 'link',
children: 'Tertiary Button',
disabled: false,
variant: 'link',
},
}
export const FullWidth: Story = {
args: {
variant: 'primary',
children: 'Full Width Button',
disabled: false,
fullWidth: true,
variant: 'primary',
},
}
export const AsLink: Story = {
args: {
variant: 'primary',
children: 'Link that looks like a button',
as: 'a',
children: 'Link that looks like a button',
variant: 'primary',
},
}

View File

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

View File

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

View File

@@ -11,5 +11,5 @@ export const Card = forwardRef<HTMLDivElement, CardProps>(({ className, ...restP
root: clsx(s.root, className),
}
return <div ref={ref} className={classNames.root} {...restProps}></div>
return <div className={classNames.root} ref={ref} {...restProps}></div>
})

View File

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

View File

@@ -1,55 +1,54 @@
import { FC } from 'react'
import { Check } from '@/assets/icons'
import { Typography } from '@/components'
import * as CheckboxRadix from '@radix-ui/react-checkbox'
import * as LabelRadix from '@radix-ui/react-label'
import { clsx } from 'clsx'
import s from './checkbox.module.scss'
import { Check } from '@/assets/icons'
import { Typography } from '@/components'
export type CheckboxProps = {
className?: string
checked?: boolean
onChange?: (checked: boolean) => void
className?: string
disabled?: boolean
required?: boolean
label?: string
id?: string
label?: string
onChange?: (checked: boolean) => void
position?: 'left'
required?: boolean
}
export const Checkbox: FC<CheckboxProps> = ({
checked,
className,
disabled,
id,
label,
onChange,
position,
disabled,
required,
label,
id,
className,
}) => {
const classNames = {
container: clsx(s.container, className),
buttonWrapper: clsx(s.buttonWrapper, disabled && s.disabled, position === 'left' && s.left),
root: s.root,
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 variant="body2" className={classNames.label} as={'label'}>
<Typography as={'label'} className={classNames.label} variant={'body2'}>
<div className={classNames.buttonWrapper}>
<CheckboxRadix.Root
className={classNames.root}
checked={checked}
onCheckedChange={onChange}
className={classNames.root}
disabled={disabled}
required={required}
id={id}
onCheckedChange={onChange}
required={required}
>
{checked && (
<CheckboxRadix.Indicator className={classNames.indicator} forceMount>

View File

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

View File

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

View File

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

View File

@@ -1,13 +1,12 @@
import { useState } from 'react'
import { Dialog } from './'
import { Meta, StoryObj } from '@storybook/react'
import { Dialog } from './'
const meta = {
title: 'Components/Dialog',
component: Dialog,
tags: ['autodocs'],
title: 'Components/Dialog',
} satisfies Meta<typeof Dialog>
export default meta
@@ -15,10 +14,10 @@ type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {
open: true,
onOpenChange: () => {},
title: 'Modal',
children: 'Modal',
onOpenChange: () => {},
open: true,
title: 'Modal',
},
render: args => {
const [open, setOpen] = useState(false)

View File

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

View File

@@ -9,7 +9,7 @@ export type LabelProps = {
label?: ReactNode
} & ComponentPropsWithoutRef<'label'>
export const Label: FC<LabelProps> = ({ label, children, className, ...rest }) => {
export const Label: FC<LabelProps> = ({ children, className, label, ...rest }) => {
const classNames = {
label: clsx(s.label, className),
}

View File

@@ -1,13 +1,12 @@
import { useState } from 'react'
import { Modal } from '@/components'
import { Meta, StoryObj } from '@storybook/react'
import { Modal } from '@/components'
const meta = {
title: 'Components/Modal',
component: Modal,
tags: ['autodocs'],
title: 'Components/Modal',
} satisfies Meta<typeof Modal>
export default meta
@@ -15,10 +14,10 @@ type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {
open: true,
onOpenChange: () => {},
title: 'Modal',
children: 'Modal',
onOpenChange: () => {},
open: true,
title: 'Modal',
},
render: args => {
const [open, setOpen] = useState(false)

View File

@@ -1,18 +1,17 @@
import { ComponentPropsWithoutRef, ReactNode } from 'react'
import { Close } from '@/assets'
import { Typography } from '@/components'
import * as DialogPrimitive from '@radix-ui/react-dialog'
import s from './modal.module.scss'
import { Close } from '@/assets'
import { Typography } from '@/components'
export type ModalProps = {
title?: string
open: boolean
onOpenChange: (open: boolean) => void
children: ReactNode
} & Omit<ComponentPropsWithoutRef<typeof DialogPrimitive.Dialog>, 'open' | 'onOpenChange'>
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}>
@@ -21,7 +20,7 @@ export const Modal = ({ children, title, ...props }: ModalProps) => {
<DialogPrimitive.Content className={s.content}>
<div className={s.header}>
<DialogPrimitive.Title asChild>
<Typography variant={'h2'} as={'h2'}>
<Typography as={'h2'} variant={'h2'}>
{title}
</Typography>
</DialogPrimitive.Title>

View File

@@ -11,5 +11,5 @@ export const Page = forwardRef<HTMLDivElement, PageProps>(({ className, ...props
root: clsx(s.root, className),
}
return <div {...props} ref={ref} className={classNames.root} />
return <div {...props} className={classNames.root} ref={ref} />
})

View File

@@ -1,67 +1,66 @@
import { FC } from 'react'
import { usePagination } from './usePagination'
import { KeyboardArrowLeft, KeyboardArrowRight } from '@/assets'
import { clsx } from 'clsx'
import s from './pagination.module.scss'
import { usePagination } from './usePagination'
import { KeyboardArrowLeft, KeyboardArrowRight } from '@/assets'
type PaginationConditionals =
| {
perPage?: null
perPageOptions?: never
onPerPageChange?: never
}
| {
onPerPageChange: (itemPerPage: number) => void
perPage: number
perPageOptions: number[]
onPerPageChange: (itemPerPage: number) => void
}
| {
onPerPageChange?: never
perPage?: null
perPageOptions?: never
}
export type PaginationProps = {
count: number
page: number
onChange: (page: number) => void
siblings?: number
onPerPageChange?: (itemPerPage: number) => void
page: number
perPage?: number
perPageOptions?: number[]
onPerPageChange?: (itemPerPage: number) => void
siblings?: number
} & PaginationConditionals
const classNames = {
root: s.root,
container: s.container,
selectBox: s.selectBox,
select: s.select,
item: s.item,
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> = ({
onChange,
count,
onChange,
onPerPageChange,
page,
perPage = null,
perPageOptions,
onPerPageChange,
siblings,
}) => {
const {
paginationRange,
isLastPage,
isFirstPage,
handlePreviousPageClicked,
handleNextPageClicked,
handleMainPageClicked,
handleNextPageClicked,
handlePreviousPageClicked,
isFirstPage,
isLastPage,
paginationRange,
} = usePagination({
page,
count,
onChange,
page,
siblings,
})
@@ -70,7 +69,7 @@ export const Pagination: FC<PaginationProps> = ({
return (
<div className={classNames.root}>
<div className={classNames.container}>
<PrevButton onClick={handlePreviousPageClicked} disabled={isFirstPage} />
<PrevButton disabled={isFirstPage} onClick={handlePreviousPageClicked} />
<MainPaginationButtons
currentPage={page}
@@ -78,15 +77,15 @@ export const Pagination: FC<PaginationProps> = ({
paginationRange={paginationRange}
/>
<NextButton onClick={handleNextPageClicked} disabled={isLastPage} />
<NextButton disabled={isLastPage} onClick={handleNextPageClicked} />
</div>
{showPerPageSelect && (
<PerPageSelect
{...{
onPerPageChange,
perPage,
perPageOptions,
onPerPageChange,
}}
/>
)}
@@ -95,8 +94,8 @@ export const Pagination: FC<PaginationProps> = ({
}
type NavigationButtonProps = {
onClick: () => void
disabled?: boolean
onClick: () => void
}
type PageButtonProps = NavigationButtonProps & {
@@ -107,43 +106,43 @@ type PageButtonProps = NavigationButtonProps & {
const Dots: FC = () => {
return <span className={classNames.dots}>&#8230;</span>
}
const PageButton: FC<PageButtonProps> = ({ onClick, disabled, selected, page }) => {
const PageButton: FC<PageButtonProps> = ({ disabled, onClick, page, selected }) => {
return (
<button
onClick={onClick}
disabled={selected || disabled}
className={classNames.pageButton(selected)}
disabled={selected || disabled}
onClick={onClick}
>
{page}
</button>
)
}
const PrevButton: FC<NavigationButtonProps> = ({ onClick, disabled }) => {
const PrevButton: FC<NavigationButtonProps> = ({ disabled, onClick }) => {
return (
<button className={classNames.item} onClick={onClick} disabled={disabled}>
<button className={classNames.item} disabled={disabled} onClick={onClick}>
<KeyboardArrowLeft className={classNames.icon} />
</button>
)
}
const NextButton: FC<NavigationButtonProps> = ({ onClick, disabled }) => {
const NextButton: FC<NavigationButtonProps> = ({ disabled, onClick }) => {
return (
<button className={classNames.item} onClick={onClick} disabled={disabled}>
<button className={classNames.item} disabled={disabled} onClick={onClick}>
<KeyboardArrowRight className={classNames.icon} />
</button>
)
}
type MainPaginationButtonsProps = {
paginationRange: (number | string)[]
currentPage: number
onClick: (pageNumber: number) => () => void
paginationRange: (number | string)[]
}
const MainPaginationButtons: FC<MainPaginationButtonsProps> = ({
paginationRange,
currentPage,
onClick,
paginationRange,
}) => {
return (
<>
@@ -154,16 +153,16 @@ const MainPaginationButtons: FC<MainPaginationButtonsProps> = ({
return <Dots key={index} />
}
return <PageButton key={index} page={page} selected={isSelected} onClick={onClick(page)} />
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
}
export const PerPageSelect: FC<PerPageSelectProps> = (

View File

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

View File

@@ -1,12 +1,11 @@
import * as React from 'react'
import { Typography } from '@/components'
import * as RadioGroupPrimitive from '@radix-ui/react-radio-group'
import { clsx } from 'clsx'
import s from './radio-group.module.scss'
import { Typography } from '@/components'
const RadioGroupRoot = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
@@ -19,9 +18,9 @@ RadioGroupRoot.displayName = RadioGroupPrimitive.Root.displayName
const RadioGroupItem = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
>(({ className, children, ...props }, ref) => {
>(({ children, className, ...props }, ref) => {
return (
<RadioGroupPrimitive.Item ref={ref} className={clsx(s.option, className)} {...props}>
<RadioGroupPrimitive.Item className={clsx(s.option, className)} ref={ref} {...props}>
<div className={s.icon}></div>
</RadioGroupPrimitive.Item>
)
@@ -37,21 +36,21 @@ export type RadioGroupProps = Omit<
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>,
'children'
> & {
options: Option[]
errorMessage?: string
options: Option[]
}
const RadioGroup = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Root>,
RadioGroupProps
>((props, ref) => {
const { options, errorMessage, ...restProps } = props
const { errorMessage, options, ...restProps } = props
return (
<RadioGroupRoot {...restProps} ref={ref}>
{options.map(option => (
<div className={s.label} key={option.value}>
<RadioGroupItem value={option.value} id={option.value} />
<Typography variant={'body2'} as={'label'} htmlFor={option.value}>
<RadioGroupItem id={option.value} value={option.value} />
<Typography as={'label'} htmlFor={option.value} variant={'body2'}>
{option.label}
</Typography>
</div>
@@ -60,4 +59,4 @@ const RadioGroup = React.forwardRef<
)
})
export { RadioGroupRoot, RadioGroupItem, RadioGroup }
export { RadioGroup, RadioGroupItem, RadioGroupRoot }

View File

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

View File

@@ -1,12 +1,10 @@
import { Table, TableBody, TableCell, TableEmpty, TableHead, TableHeadCell, TableRow } from './'
import { Typography } from '@/components'
import { Meta } from '@storybook/react'
import { Table, TableBody, TableCell, TableEmpty, TableHead, TableHeadCell, TableRow } from './'
import { Typography } from '@/components'
export default {
title: 'Components/Table',
component: Table,
title: 'Components/Table',
} as Meta<typeof Table>
export const Default = {
@@ -16,7 +14,7 @@ export const Default = {
<TableHead>
<TableRow>
<TableHeadCell>Название</TableHeadCell>
<TableHeadCell align="center">Описание</TableHeadCell>
<TableHeadCell align={'center'}>Описание</TableHeadCell>
<TableHeadCell>Ссылка</TableHeadCell>
<TableHeadCell>Тип</TableHeadCell>
<TableHeadCell>Вид</TableHeadCell>
@@ -34,9 +32,9 @@ export const Default = {
<TableCell>
<Typography
as={'a'}
href={'https://it-incubator.io/'}
target={'_blank'}
variant={'link1'}
href="https://it-incubator.io/"
target="_blank"
>
Какая-то ссылка кудато на какой-то источник с информациейо ссылка кудато на какой-то
источник
@@ -69,27 +67,27 @@ export const Default = {
const data = [
{
category: 'Основной',
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor',
id: '01',
title: 'Web Basic',
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor',
link: 'Какая-то ссылка кудато на какой-то источник с информациейо ссылка кудато на какой-то',
category: 'Основной',
title: 'Web Basic',
type: 'Читать',
},
{
category: 'Основной',
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor',
id: '02',
title: 'Web Basic',
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor',
link: 'Какая-то ссылка куда-то',
category: 'Основной',
title: 'Web Basic',
type: 'Читать',
},
{
id: '03',
title: 'Web Basic',
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor',
link: 'Какая-то ссылка кудато на какой-то источник с информациейо ссылка кудато на какой-то. Какая-то ссылка кудато на какой-то источник с информациейо ссылка куда-то на какой-то',
category: 'Основной',
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor',
id: '03',
link: 'Какая-то ссылка кудато на какой-то источник с информациейо ссылка кудато на какой-то. Какая-то ссылка кудато на какой-то источник с информациейо ссылка куда-то на какой-то',
title: 'Web Basic',
type: 'Читать',
},
]
@@ -101,7 +99,7 @@ export const WithMapMethod = {
<TableHead>
<TableRow>
<TableHeadCell>Название</TableHeadCell>
<TableHeadCell align="center">Описание</TableHeadCell>
<TableHeadCell align={'center'}>Описание</TableHeadCell>
<TableHeadCell>Ссылка</TableHeadCell>
<TableHeadCell>Тип</TableHeadCell>
<TableHeadCell>Вид</TableHeadCell>

View File

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

View File

@@ -11,7 +11,7 @@ const TabsList = forwardRef<
ElementRef<typeof TabsPrimitive.List>,
ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => (
<TabsPrimitive.List ref={ref} className={clsx(s.list, className)} {...props} />
<TabsPrimitive.List className={clsx(s.list, className)} ref={ref} {...props} />
))
TabsList.displayName = TabsPrimitive.List.displayName
@@ -20,7 +20,7 @@ const TabsTrigger = forwardRef<
ElementRef<typeof TabsPrimitive.Trigger>,
ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger ref={ref} className={clsx(s.trigger, className)} {...props} />
<TabsPrimitive.Trigger className={clsx(s.trigger, className)} ref={ref} {...props} />
))
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
@@ -29,9 +29,9 @@ const TabsContent = forwardRef<
ElementRef<typeof TabsPrimitive.Content>,
ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Content ref={ref} className={clsx(s.content, className)} {...props} />
<TabsPrimitive.Content className={clsx(s.content, className)} ref={ref} {...props} />
))
TabsContent.displayName = TabsPrimitive.Content.displayName
export { Tabs, TabsList, TabsTrigger, TabsContent }
export { Tabs, TabsContent, TabsList, TabsTrigger }

View File

@@ -1,18 +1,17 @@
import { ChangeEvent, ComponentProps, ComponentPropsWithoutRef, forwardRef, useState } from 'react'
import { Eye, Search, VisibilityOff } from '@/assets'
import { Typography } from '@/components'
import { clsx } from 'clsx'
import s from './text-field.module.scss'
import { VisibilityOff, Eye, Search } from '@/assets'
import { Typography } from '@/components'
export type TextFieldProps = {
onValueChange?: (value: string) => void
containerProps?: ComponentProps<'div'>
labelProps?: ComponentProps<'label'>
errorMessage?: string
label?: string
labelProps?: ComponentProps<'label'>
onValueChange?: (value: string) => void
search?: boolean
} & ComponentPropsWithoutRef<'input'>
@@ -20,15 +19,15 @@ export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
(
{
className,
errorMessage,
placeholder,
type,
containerProps,
labelProps,
errorMessage,
label,
labelProps,
onChange,
onValueChange,
placeholder,
search,
type,
...restProps
},
ref
@@ -45,18 +44,18 @@ export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
}
const classNames = {
root: clsx(s.root, containerProps?.className),
fieldContainer: clsx(s.fieldContainer),
field: clsx(s.field, !!errorMessage && s.error, search && s.hasLeadingIcon, className),
label: clsx(s.label, labelProps?.className),
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 variant="body2" as="label" className={classNames.label}>
<Typography as={'label'} className={classNames.label} variant={'body2'}>
{label}
</Typography>
)}
@@ -64,24 +63,24 @@ export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
{search && <Search className={classNames.leadingIcon} />}
<input
className={classNames.field}
onChange={handleChange}
placeholder={placeholder}
ref={ref}
type={finalType}
onChange={handleChange}
{...restProps}
/>
{isShowPasswordButtonShown && (
<button
className={s.showPassword}
type={'button'}
onClick={() => setShowPassword(prev => !prev)}
type={'button'}
>
{showPassword ? <VisibilityOff /> : <Eye />}
</button>
)}
</div>
<Typography variant="error" className={classNames.error}>
<Typography className={classNames.error} variant={'error'}>
{errorMessage}
</Typography>
</div>

View File

@@ -3,11 +3,9 @@ import type { Meta, StoryObj } from '@storybook/react'
import { Typography } from './'
const meta = {
title: 'Components/Typography',
component: Typography,
tags: ['autodocs'],
argTypes: {
variant: {
control: { type: 'radio' },
options: [
'large',
'h1',
@@ -23,9 +21,11 @@ const meta = {
'link2',
'error',
],
control: { type: 'radio' },
},
},
component: Typography,
tags: ['autodocs'],
title: 'Components/Typography',
} satisfies Meta<typeof Typography>
export default meta

View File

@@ -6,22 +6,22 @@ import s from './typography.module.scss'
export interface TextProps<T extends ElementType> {
as?: T
children?: ReactNode
className?: string
variant?:
| 'large'
| 'body1'
| 'body2'
| 'caption'
| 'error'
| 'h1'
| 'h2'
| 'h3'
| 'body1'
| 'body2'
| 'subtitle1'
| 'subtitle2'
| 'caption'
| 'overline'
| 'large'
| 'link1'
| 'link2'
| 'error'
children?: ReactNode
className?: string
| 'overline'
| 'subtitle1'
| 'subtitle2'
}
export function Typography<T extends ElementType = 'p'>({