feat(ui): complete phase 4 Poyraz UI foundation

This commit is contained in:
poyrazavsever
2026-07-16 23:20:30 +03:00
parent 5d863280bf
commit 561af11b70
58 changed files with 2555 additions and 7806 deletions
@@ -0,0 +1,61 @@
"use client";
import { Button } from "poyraz-ui/atoms";
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "poyraz-ui/molecules";
type DestructiveConfirmationProps = {
cancelLabel?: string;
confirmLabel: string;
description: string;
loading?: boolean;
onConfirm: () => void;
onOpenChange: (open: boolean) => void;
open: boolean;
title: string;
};
export function DestructiveConfirmation({
cancelLabel = "Vazgeç",
confirmLabel,
description,
loading = false,
onConfirm,
onOpenChange,
open,
title,
}: DestructiveConfirmationProps) {
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent surface="solid" radius="lg" mobile="floating">
<DialogHeader>
<DialogTitle>{title}</DialogTitle>
<DialogDescription>{description}</DialogDescription>
</DialogHeader>
<DialogFooter>
<DialogClose asChild>
<Button type="button" variant="outline" disabled={loading}>
{cancelLabel}
</Button>
</DialogClose>
<Button
type="button"
variant="destructive"
loading={loading}
aria-busy={loading}
onClick={onConfirm}
>
{confirmLabel}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
+69
View File
@@ -0,0 +1,69 @@
import { Button, Card, CardContent, Skeleton, Typography } from "poyraz-ui/atoms";
import { Alert, AlertDescription, AlertTitle } from "poyraz-ui/molecules";
import { Ban, CircleAlert, Inbox } from "lucide-react";
import type { ReactNode } from "react";
type FeedbackStateProps = {
action?: ReactNode;
description: string;
title: string;
variant: "empty" | "error" | "forbidden";
};
export function FeedbackState({ action, description, title, variant }: FeedbackStateProps) {
if (variant === "empty") {
return (
<Card variant="soft">
<CardContent className="flex min-h-56 flex-col items-center justify-center gap-3 p-8 text-center">
<span className="flex h-11 w-11 items-center justify-center rounded-full bg-primary-muted text-primary-muted-foreground">
<Inbox className="h-5 w-5" aria-hidden="true" />
</span>
<div className="space-y-1">
<Typography component="h2" variant="h4">{title}</Typography>
<Typography component="p" variant="muted" className="max-w-lg">{description}</Typography>
</div>
{action}
</CardContent>
</Card>
);
}
const forbidden = variant === "forbidden";
return (
<Alert
role="alert"
variant={forbidden ? "warning" : "destructive"}
appearance="soft"
icon={forbidden ? <Ban aria-hidden="true" /> : <CircleAlert aria-hidden="true" />}
>
<AlertTitle>{title}</AlertTitle>
<AlertDescription className="space-y-3">
<p>{description}</p>
{action}
</AlertDescription>
</Alert>
);
}
export function LoadingState({ label = "İçerik yükleniyor" }: { label?: string }) {
return (
<div className="space-y-4" aria-busy="true" aria-label={label} role="status">
<span className="sr-only">{label}</span>
<Skeleton className="h-8 w-2/5" />
<Skeleton className="h-4 w-3/5" />
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
{Array.from({ length: 3 }).map((_, index) => (
<Skeleton key={index} className="h-36 w-full" />
))}
</div>
</div>
);
}
export function RetryAction({ onClick }: { onClick: () => void }) {
return (
<Button type="button" variant="outline" size="sm" onClick={onClick}>
Yeniden dene
</Button>
);
}
+45
View File
@@ -0,0 +1,45 @@
import { Typography } from "poyraz-ui/atoms";
import type { ReactNode } from "react";
type PageHeaderProps = {
title: string;
description?: string;
eyebrow?: string;
primaryAction?: ReactNode;
secondaryActions?: ReactNode;
};
export function PageHeader({
title,
description,
eyebrow,
primaryAction,
secondaryActions,
}: PageHeaderProps) {
return (
<header className="flex flex-col gap-4 border-b border-border pb-5 lg:flex-row lg:items-end lg:justify-between">
<div className="min-w-0 space-y-1.5">
{eyebrow ? (
<Typography component="p" variant="caption" className="font-semibold uppercase tracking-wider text-primary">
{eyebrow}
</Typography>
) : null}
<Typography component="h1" variant="h1" balance>
{title}
</Typography>
{description ? (
<Typography component="p" variant="muted" className="max-w-3xl">
{description}
</Typography>
) : null}
</div>
{primaryAction || secondaryActions ? (
<div className="flex shrink-0 flex-wrap items-center gap-2">
{secondaryActions}
{primaryAction}
</div>
) : null}
</header>
);
}
+41
View File
@@ -0,0 +1,41 @@
import { Badge } from "poyraz-ui/atoms";
import type { ComponentProps } from "react";
const statusPresentation = {
accepted: { label: "Kabul edildi", variant: "success" },
active: { label: "Aktif", variant: "success" },
archived: { label: "Arşivlendi", variant: "outline" },
cancelled: { label: "İptal edildi", variant: "outline" },
completed: { label: "Tamamlandı", variant: "success" },
done: { label: "Tamamlandı", variant: "success" },
draft: { label: "Taslak", variant: "secondary" },
expired: { label: "Süresi doldu", variant: "destructive" },
in_progress: { label: "Devam ediyor", variant: "info" },
overdue: { label: "Gecikmiş", variant: "destructive" },
paid: { label: "Ödendi", variant: "success" },
paused: { label: "Duraklatıldı", variant: "warning" },
pending: { label: "Bekliyor", variant: "warning" },
planned: { label: "Planlandı", variant: "secondary" },
planning: { label: "Planlanıyor", variant: "secondary" },
rejected: { label: "Reddedildi", variant: "destructive" },
revoked: { label: "İptal edildi", variant: "destructive" },
sent: { label: "Gönderildi", variant: "info" },
todo: { label: "Yapılacak", variant: "secondary" },
} as const satisfies Record<string, { label: string; variant: NonNullable<ComponentProps<typeof Badge>["variant"]> }>;
export type NetaStatus = keyof typeof statusPresentation;
type StatusBadgeProps = Omit<ComponentProps<typeof Badge>, "children" | "variant"> & {
status: NetaStatus;
};
export function StatusBadge({ status, ...props }: StatusBadgeProps) {
const presentation = statusPresentation[status];
return (
<Badge variant={presentation.variant} {...props}>
{presentation.label}
</Badge>
);
}
export { statusPresentation };