mirror of
https://github.com/ershisan99/flashcards-example-project.git
synced 2025-12-18 05:09:23 +00:00
chore: update deps, apply updated eslint ocnfig
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'}>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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')
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
})
|
||||
|
||||
@@ -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)}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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} />
|
||||
})
|
||||
|
||||
@@ -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}>…</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> = (
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'>({
|
||||
|
||||
Reference in New Issue
Block a user