mirror of
https://github.com/ershisan99/flashcards-example-project.git
synced 2025-12-18 05:09:23 +00:00
add components
This commit is contained in:
0
src/components/decks/decks-table.tsx
Normal file
0
src/components/decks/decks-table.tsx
Normal file
@@ -52,7 +52,6 @@
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
|
||||
background-color: var(--color-dark-900);
|
||||
border-radius: 50%;
|
||||
|
||||
&.disabled {
|
||||
|
||||
@@ -3,5 +3,8 @@ export * from './card'
|
||||
export * from './typography'
|
||||
export * from './checkbox'
|
||||
export * from './text-field'
|
||||
export * from './table'
|
||||
export * from './controlled'
|
||||
export * from './radio-group'
|
||||
export * from './page'
|
||||
export * from './slider'
|
||||
|
||||
1
src/components/ui/page/index.ts
Normal file
1
src/components/ui/page/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './page'
|
||||
6
src/components/ui/page/page.module.scss
Normal file
6
src/components/ui/page/page.module.scss
Normal file
@@ -0,0 +1,6 @@
|
||||
.root {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 36px;
|
||||
padding-inline: 24px;
|
||||
}
|
||||
15
src/components/ui/page/page.tsx
Normal file
15
src/components/ui/page/page.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { ComponentPropsWithoutRef, forwardRef } from 'react'
|
||||
|
||||
import { clsx } from 'clsx'
|
||||
|
||||
import s from './page.module.scss'
|
||||
|
||||
export type PageProps = ComponentPropsWithoutRef<'div'>
|
||||
|
||||
export const Page = forwardRef<HTMLDivElement, PageProps>(({ className, ...props }, ref) => {
|
||||
const classNames = {
|
||||
root: clsx(s.root, className),
|
||||
}
|
||||
|
||||
return <div {...props} ref={ref} className={classNames.root} />
|
||||
})
|
||||
1
src/components/ui/slider/index.ts
Normal file
1
src/components/ui/slider/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './slider'
|
||||
52
src/components/ui/slider/slider.module.scss
Normal file
52
src/components/ui/slider/slider.module.scss
Normal file
@@ -0,0 +1,52 @@
|
||||
.container {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.root {
|
||||
touch-action: none;
|
||||
user-select: none;
|
||||
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.track {
|
||||
position: relative;
|
||||
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
|
||||
opacity: 0.5;
|
||||
background-color: var(--color-accent-500);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.range {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
background-color: var(--color-accent-500);
|
||||
}
|
||||
|
||||
.thumb {
|
||||
touch-action: pan-x;
|
||||
cursor: pointer;
|
||||
|
||||
display: block;
|
||||
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
|
||||
background-color: var(--color-light-100);
|
||||
border-radius: 9999px;
|
||||
|
||||
transition: transform 0.2s ease-in-out;
|
||||
}
|
||||
26
src/components/ui/slider/slider.tsx
Normal file
26
src/components/ui/slider/slider.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { ComponentPropsWithoutRef, ElementRef, forwardRef } from 'react'
|
||||
|
||||
import * as SliderPrimitive from '@radix-ui/react-slider'
|
||||
import { clsx } from 'clsx'
|
||||
|
||||
import s from './slider.module.scss'
|
||||
const Slider = forwardRef<
|
||||
ElementRef<typeof SliderPrimitive.Root>,
|
||||
ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div className={s.container}>
|
||||
<span>{props?.value?.[0]}</span>
|
||||
<SliderPrimitive.Root ref={ref} className={clsx(s.root, className)} {...props}>
|
||||
<SliderPrimitive.Track className={s.track}>
|
||||
<SliderPrimitive.Range className="absolute h-full bg-primary" />
|
||||
</SliderPrimitive.Track>
|
||||
<SliderPrimitive.Thumb className={s.thumb} />
|
||||
<SliderPrimitive.Thumb className={s.thumb} />
|
||||
</SliderPrimitive.Root>
|
||||
<span>{props?.value?.[1]}</span>
|
||||
</div>
|
||||
))
|
||||
|
||||
Slider.displayName = SliderPrimitive.Root.displayName
|
||||
|
||||
export { Slider }
|
||||
@@ -1,5 +1,6 @@
|
||||
.table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
color: var(--color-light-100);
|
||||
border: 1px solid var(--color-dark-500);
|
||||
}
|
||||
|
||||
@@ -75,3 +75,49 @@ export const TableEmpty: FC<ComponentProps<'div'> & { mt?: string; mb?: string }
|
||||
</Typography>
|
||||
)
|
||||
}
|
||||
export type Column = {
|
||||
key: string
|
||||
title: string
|
||||
sortable?: boolean
|
||||
}
|
||||
export type Sort = {
|
||||
key: string
|
||||
direction: 'asc' | 'desc'
|
||||
} | null
|
||||
|
||||
export const TableHeader: FC<
|
||||
Omit<
|
||||
ComponentPropsWithoutRef<'thead'> & {
|
||||
columns: Column[]
|
||||
sort?: Sort
|
||||
onSort?: (sort: Sort) => void
|
||||
},
|
||||
'children'
|
||||
>
|
||||
> = ({ columns, sort, onSort, ...restProps }) => {
|
||||
const handleSort = (key: string, sortable?: boolean) => () => {
|
||||
if (!onSort || !sortable) return
|
||||
|
||||
if (sort?.key !== key) return onSort({ key, direction: 'asc' })
|
||||
|
||||
if (sort.direction === 'desc') return onSort(null)
|
||||
|
||||
return onSort({
|
||||
key,
|
||||
direction: sort?.direction === 'asc' ? 'desc' : 'asc',
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<TableHead {...restProps}>
|
||||
<TableRow>
|
||||
{columns.map(({ title, key, sortable = true }) => (
|
||||
<TableHeadCell key={key} onClick={handleSort(key, sortable)}>
|
||||
{title}
|
||||
{sort && sort.key === key && <span>{sort.direction === 'asc' ? '▲' : '▼'}</span>}
|
||||
</TableHeadCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
)
|
||||
}
|
||||
|
||||
1
src/components/ui/tabs/index.ts
Normal file
1
src/components/ui/tabs/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './tabs'
|
||||
34
src/components/ui/tabs/tabs.module.scss
Normal file
34
src/components/ui/tabs/tabs.module.scss
Normal file
@@ -0,0 +1,34 @@
|
||||
.list {
|
||||
display: inline-flex;
|
||||
background-color: var(--color-dark-900);
|
||||
}
|
||||
|
||||
.trigger {
|
||||
cursor: pointer;
|
||||
|
||||
display: inline-flex;
|
||||
flex-shrink: 0;
|
||||
flex-wrap: nowrap;
|
||||
|
||||
padding: 6px 24px;
|
||||
|
||||
background-color: var(--color-dark-900);
|
||||
border: 1px solid var(--color-dark-300);
|
||||
|
||||
&:last-of-type {
|
||||
border-radius: 0 2px 2px 0;
|
||||
}
|
||||
|
||||
&:first-of-type {
|
||||
border-radius: 2px 0 0 2px;
|
||||
}
|
||||
|
||||
&[data-state='active'] {
|
||||
background-color: var(--color-accent-500);
|
||||
border-color: var(--color-accent-500);
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
color: inherit;
|
||||
}
|
||||
37
src/components/ui/tabs/tabs.tsx
Normal file
37
src/components/ui/tabs/tabs.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import { ComponentPropsWithoutRef, ElementRef, forwardRef } from 'react'
|
||||
|
||||
import * as TabsPrimitive from '@radix-ui/react-tabs'
|
||||
import { clsx } from 'clsx'
|
||||
|
||||
import s from './tabs.module.scss'
|
||||
|
||||
const Tabs = TabsPrimitive.Root
|
||||
|
||||
const TabsList = forwardRef<
|
||||
ElementRef<typeof TabsPrimitive.List>,
|
||||
ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.List ref={ref} className={clsx(s.list, className)} {...props} />
|
||||
))
|
||||
|
||||
TabsList.displayName = TabsPrimitive.List.displayName
|
||||
|
||||
const TabsTrigger = forwardRef<
|
||||
ElementRef<typeof TabsPrimitive.Trigger>,
|
||||
ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.Trigger ref={ref} className={clsx(s.trigger, className)} {...props} />
|
||||
))
|
||||
|
||||
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
|
||||
|
||||
const TabsContent = forwardRef<
|
||||
ElementRef<typeof TabsPrimitive.Content>,
|
||||
ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.Content ref={ref} className={clsx(s.content, className)} {...props} />
|
||||
))
|
||||
|
||||
TabsContent.displayName = TabsPrimitive.Content.displayName
|
||||
|
||||
export { Tabs, TabsList, TabsTrigger, TabsContent }
|
||||
@@ -40,6 +40,10 @@
|
||||
color: var(--color-danger-300);
|
||||
border-color: var(--color-danger-300);
|
||||
}
|
||||
|
||||
&.hasLeadingIcon {
|
||||
padding-left: 41px;
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
@@ -47,6 +51,27 @@
|
||||
color: var(--color-dark-100);
|
||||
}
|
||||
|
||||
.leadingIcon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
bottom: 50%;
|
||||
left: 0;
|
||||
transform: translateY(-50%);
|
||||
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-left: 12px;
|
||||
padding: 0;
|
||||
|
||||
background: transparent;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
|
||||
&:focus-visible {
|
||||
outline: var(--outline-focus);
|
||||
}
|
||||
}
|
||||
|
||||
.showPassword {
|
||||
cursor: pointer;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { clsx } from 'clsx'
|
||||
|
||||
import s from './text-field.module.scss'
|
||||
|
||||
import { VisibilityOff, Eye } from '@/assets'
|
||||
import { VisibilityOff, Eye, Search } from '@/assets'
|
||||
import { Typography } from '@/components'
|
||||
|
||||
export type TextFieldProps = {
|
||||
@@ -13,6 +13,7 @@ export type TextFieldProps = {
|
||||
labelProps?: ComponentProps<'label'>
|
||||
errorMessage?: string
|
||||
label?: string
|
||||
search?: boolean
|
||||
} & ComponentPropsWithoutRef<'input'>
|
||||
|
||||
export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
|
||||
@@ -27,6 +28,7 @@ export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
|
||||
label,
|
||||
onChange,
|
||||
onValueChange,
|
||||
search,
|
||||
...restProps
|
||||
},
|
||||
ref
|
||||
@@ -45,9 +47,10 @@ 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, className),
|
||||
field: clsx(s.field, !!errorMessage && s.error, search && s.hasLeadingIcon, className),
|
||||
label: clsx(s.label, labelProps?.className),
|
||||
error: clsx(s.error),
|
||||
leadingIcon: s.leadingIcon,
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -58,6 +61,7 @@ export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
|
||||
</Typography>
|
||||
)}
|
||||
<div className={classNames.fieldContainer}>
|
||||
{search && <Search className={classNames.leadingIcon} />}
|
||||
<input
|
||||
className={classNames.field}
|
||||
placeholder={placeholder}
|
||||
|
||||
Reference in New Issue
Block a user