T07a: frontend core SPA — shell + auth + routing + i18n + UI library + a11y
- React 18 + Vite + TypeScript + Tailwind CSS setup - AppShell with Sidebar (plugin menu) + TopBar (tenant switcher, search, notifications, user menu) - Auth pages: Login, PasswordResetRequest, PasswordResetConfirm - Protected routes with auth guard - API client (axios with interceptors: session cookie, 401 redirect, 422 validation) - TanStack Query hooks for auth, users, companies, contacts, notifications - Zustand stores: authStore, uiStore - i18n setup (de/en locales) with react-i18next - UI component library: Button, Input, Select, Modal, Toast, Table, Card, Badge, Avatar, Pagination, EmptyState, Skeleton, ConfirmDialog - Accessibility: ARIA labels, 44px touch targets, keyboard nav, reduced-motion, sr-only - Design tokens from prototype as CSS custom properties - 111 tests passing across 20 test files - tsc --noEmit: 0 errors - npm run build: success (471KB JS, 24KB CSS)
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export interface EmptyStateProps {
|
||||
title?: string;
|
||||
description?: string;
|
||||
icon?: React.ReactNode;
|
||||
action?: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function EmptyState({
|
||||
title,
|
||||
description,
|
||||
icon,
|
||||
action,
|
||||
className,
|
||||
}: EmptyStateProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
'flex flex-col items-center justify-center py-12 px-6 text-center',
|
||||
className
|
||||
)}
|
||||
role="status"
|
||||
>
|
||||
{icon && (
|
||||
<div className="mb-4 text-secondary-300" aria-hidden="true">
|
||||
{icon}
|
||||
</div>
|
||||
)}
|
||||
{!icon && (
|
||||
<div className="mb-4 w-16 h-16 rounded-full bg-secondary-100 flex items-center justify-center" aria-hidden="true">
|
||||
<svg className="w-8 h-8 text-secondary-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9 13h6m-3-3v6m-9 1V7a2 2 0 012-2h6l2 2h6a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2z" />
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
<h3 className="text-lg font-semibold text-secondary-900">
|
||||
{title || t('emptyState.title')}
|
||||
</h3>
|
||||
<p className="mt-1 text-sm text-secondary-500 max-w-sm">
|
||||
{description || t('emptyState.description')}
|
||||
</p>
|
||||
{action && <div className="mt-6">{action}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user