api ulandi

This commit is contained in:
Samandar Turgunboyev
2025-12-22 11:35:55 +05:00
parent 37c7120d1b
commit 9978b4e3fe
75 changed files with 10255 additions and 11924 deletions

66
src/shared/ui/alert.tsx Normal file
View File

@@ -0,0 +1,66 @@
import * as React from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/shared/lib/utils';
const alertVariants = cva(
'relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current',
{
variants: {
variant: {
default: 'bg-card text-card-foreground',
destructive:
'text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90',
},
},
defaultVariants: {
variant: 'default',
},
},
);
function Alert({
className,
variant,
...props
}: React.ComponentProps<'div'> & VariantProps<typeof alertVariants>) {
return (
<div
data-slot="alert"
role="alert"
className={cn(alertVariants({ variant }), className)}
{...props}
/>
);
}
function AlertTitle({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="alert-title"
className={cn(
'col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight',
className,
)}
{...props}
/>
);
}
function AlertDescription({
className,
...props
}: React.ComponentProps<'div'>) {
return (
<div
data-slot="alert-description"
className={cn(
'text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed',
className,
)}
{...props}
/>
);
}
export { Alert, AlertTitle, AlertDescription };

View File

@@ -1,8 +1,8 @@
'use client';
import * as React from 'react';
import type * as LabelPrimitive from '@radix-ui/react-label';
import { Slot } from '@radix-ui/react-slot';
import * as React from 'react';
import {
Controller,
FormProvider,
@@ -15,6 +15,7 @@ import {
import { cn } from '@/shared/lib/utils';
import { Label } from '@/shared/ui/label';
import { useTranslations } from 'next-intl';
const Form = FormProvider;
@@ -137,6 +138,7 @@ function FormDescription({ className, ...props }: React.ComponentProps<'p'>) {
}
function FormMessage({ className, ...props }: React.ComponentProps<'p'>) {
const t = useTranslations();
const { error, formMessageId } = useFormField();
const body = error ? String(error?.message ?? '') : props.children;
@@ -151,18 +153,18 @@ function FormMessage({ className, ...props }: React.ComponentProps<'p'>) {
className={cn('text-destructive text-sm', className)}
{...props}
>
{body}
{error && error.message ? t(error.message) : body}
</p>
);
}
export {
useFormField,
Form,
FormItem,
FormLabel,
FormControl,
FormDescription,
FormMessage,
FormField,
FormItem,
FormLabel,
FormMessage,
useFormField,
};

View File

@@ -0,0 +1,102 @@
'use client';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import { Button } from './button';
type GlobalPaginationProps = {
page: number;
total: number;
pageSize?: number;
onChange: (page: number) => void;
};
const getPages = (current: number, total: number) => {
const pages: (number | 'dots')[] = [];
if (total <= 7) {
return Array.from({ length: total }, (_, i) => i + 1);
}
pages.push(1);
if (current > 4) {
pages.push('dots');
}
const start = Math.max(2, current - 1);
const end = Math.min(total - 1, current + 1);
for (let i = start; i <= end; i++) {
pages.push(i);
}
if (current < total - 3) {
pages.push('dots');
}
pages.push(total);
return pages;
};
export const GlobalPagination = ({
page,
total,
pageSize = 36,
onChange,
}: GlobalPaginationProps) => {
const totalPages = Math.ceil(total / pageSize);
if (totalPages <= 1) return null;
const pages = getPages(page, totalPages);
return (
<div className="flex items-center justify-center gap-2">
<Button
variant={'outline'}
onClick={() => page > 1 && onChange(page - 1)}
disabled={page === 1}
className="flex items-center justify-center w-10 cursor-pointer h-10 rounded-lg transition-all duration-200 hover:bg-gray-100 disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:bg-transparent"
>
<ChevronLeft className="!w-6 !h-6 text-gray-600" />
</Button>
{/* Page Numbers */}
<div className="flex items-center gap-1">
{pages.map((p, i) => (
<div key={i}>
{p === 'dots' ? (
<span className="flex items-center justify-center w-9 h-9 text-gray-400 font-medium">
···
</span>
) : (
<Button
variant={'outline'}
onClick={() => onChange(p)}
className={`
flex items-center justify-center w-10 h-10 px-3 rounded-lg font-medium transition-all duration-200
${
p === page
? 'bg-blue-600 text-white shadow-md shadow-blue-200 scale-105'
: 'text-gray-700 hover:bg-gray-100 hover:scale-105'
}
`}
>
{p}
</Button>
)}
</div>
))}
</div>
<Button
onClick={() => page < totalPages && onChange(page + 1)}
disabled={page === totalPages}
variant={'outline'}
className="flex items-center justify-center w-10 cursor-pointer h-10 rounded-lg transition-all duration-200 hover:bg-gray-100 disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:bg-transparent"
>
<ChevronRight className="!w-6 !h-6 text-gray-600" />
</Button>
</div>
);
};

View File

@@ -0,0 +1,126 @@
import {
ChevronLeftIcon,
ChevronRightIcon,
MoreHorizontalIcon,
} from 'lucide-react';
import * as React from 'react';
import { cn } from '@/shared/lib/utils';
import { buttonVariants, type Button } from '@/shared/ui/button';
function Pagination({ className, ...props }: React.ComponentProps<'nav'>) {
return (
<nav
role="navigation"
aria-label="pagination"
data-slot="pagination"
className={cn('mx-auto flex w-full justify-center', className)}
{...props}
/>
);
}
function PaginationContent({
className,
...props
}: React.ComponentProps<'ul'>) {
return (
<ul
data-slot="pagination-content"
className={cn('flex flex-row items-center gap-1', className)}
{...props}
/>
);
}
function PaginationItem({ ...props }: React.ComponentProps<'li'>) {
return <li data-slot="pagination-item" {...props} />;
}
type PaginationLinkProps = {
isActive?: boolean;
} & Pick<React.ComponentProps<typeof Button>, 'size'> &
React.ComponentProps<'a'>;
function PaginationLink({
className,
isActive,
size = 'icon',
...props
}: PaginationLinkProps) {
return (
<a
aria-current={isActive ? 'page' : undefined}
data-slot="pagination-link"
data-active={isActive}
className={cn(
buttonVariants({
variant: isActive ? 'outline' : 'ghost',
size,
}),
className,
)}
{...props}
/>
);
}
function PaginationPrevious({
className,
...props
}: React.ComponentProps<typeof PaginationLink>) {
return (
<PaginationLink
aria-label="Go to previous page"
size="default"
className={cn('gap-1 px-2.5 sm:pl-2.5', className)}
{...props}
>
<ChevronLeftIcon />
<span className="hidden sm:block">Previous</span>
</PaginationLink>
);
}
function PaginationNext({
className,
...props
}: React.ComponentProps<typeof PaginationLink>) {
return (
<PaginationLink
aria-label="Go to next page"
size="default"
className={cn('gap-1 px-2.5 sm:pr-2.5', className)}
{...props}
>
<ChevronRightIcon />
</PaginationLink>
);
}
function PaginationEllipsis({
className,
...props
}: React.ComponentProps<'span'>) {
return (
<span
aria-hidden
data-slot="pagination-ellipsis"
className={cn('flex size-9 items-center justify-center', className)}
{...props}
>
<MoreHorizontalIcon className="size-4" />
<span className="sr-only">More pages</span>
</span>
);
}
export {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
};

View File

@@ -0,0 +1,13 @@
import { cn } from '@/shared/lib/utils';
function Skeleton({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="skeleton"
className={cn('bg-accent animate-pulse rounded-md', className)}
{...props}
/>
);
}
export { Skeleton };