Phase 0.2: migrate UI component SVGs to lucide-react
- Button.tsx: Loader2 spinner - EmptyState.tsx: FolderOpen default icon - Modal.tsx: X close icon - Pagination.tsx: ChevronLeft/ChevronRight - Table.tsx: Loader2 loading spinner - Toast.tsx: Check/X/AlertTriangle/Info + X close - lucide-react v1.25.0 installed - All 85 UI tests pass - 2 pre-existing TSC errors in Dms.tsx (unrelated)
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
|
||||
export type ButtonVariant = 'primary' | 'secondary' | 'danger' | 'ghost';
|
||||
export type ButtonSize = 'sm' | 'md' | 'lg';
|
||||
@@ -42,10 +43,7 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
{...props}
|
||||
>
|
||||
{isLoading && (
|
||||
<svg className="animate-spin motion-reduce:animate-none -ml-1 mr-2 h-4 w-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
|
||||
</svg>
|
||||
<Loader2 className="animate-spin motion-reduce:animate-none -ml-1 mr-2 h-4 w-4" aria-hidden="true" />
|
||||
)}
|
||||
{icon && !isLoading && <span className="mr-2" aria-hidden="true">{icon}</span>}
|
||||
{children}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { FolderOpen } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export interface EmptyStateProps {
|
||||
@@ -35,9 +36,7 @@ export function EmptyState({
|
||||
)}
|
||||
{!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>
|
||||
<FolderOpen className="w-8 h-8 text-secondary-400" />
|
||||
</div>
|
||||
)}
|
||||
<h3 className="text-lg font-semibold text-secondary-900">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { X } from 'lucide-react';
|
||||
|
||||
export interface ModalProps {
|
||||
open: boolean;
|
||||
@@ -99,9 +100,7 @@ export function Modal({
|
||||
className="absolute top-3 right-3 md:top-4 md:right-4 text-secondary-400 hover:text-secondary-600 min-h-touch min-w-touch flex items-center justify-center rounded-md focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500"
|
||||
aria-label="Close dialog"
|
||||
>
|
||||
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
<X className="h-5 w-5" aria-hidden="true" />
|
||||
</button>
|
||||
)}
|
||||
<div className={clsx(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export interface PaginationProps {
|
||||
@@ -41,9 +42,7 @@ export function Pagination({ currentPage, totalPages, total, pageSize, onPageCha
|
||||
className="px-1.5 py-0.5 text-xs rounded border border-secondary-300 hover:bg-secondary-50 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center focus:outline-none focus-visible:ring-1 focus-visible:ring-primary-500"
|
||||
aria-label={t('pagination.previous')}
|
||||
>
|
||||
<svg className="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
<ChevronLeft className="h-3 w-3" aria-hidden="true" />
|
||||
</button>
|
||||
{startPage > 1 && (
|
||||
<>
|
||||
@@ -91,9 +90,7 @@ export function Pagination({ currentPage, totalPages, total, pageSize, onPageCha
|
||||
className="px-1.5 py-0.5 text-xs rounded border border-secondary-300 hover:bg-secondary-50 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center focus:outline-none focus-visible:ring-1 focus-visible:ring-primary-500"
|
||||
aria-label={t('pagination.next')}
|
||||
>
|
||||
<svg className="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
<ChevronRight className="h-3 w-3" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
|
||||
export interface TableColumn<T> {
|
||||
key: string;
|
||||
@@ -100,10 +101,7 @@ export function Table<T extends Record<string, any>>({
|
||||
<tr>
|
||||
<td colSpan={columns.length} className="px-6 py-8 text-center text-secondary-500">
|
||||
<span className="inline-flex items-center gap-2">
|
||||
<svg className="animate-spin motion-reduce:animate-none h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
|
||||
</svg>
|
||||
<Loader2 className="animate-spin motion-reduce:animate-none h-5 w-5" aria-hidden="true" />
|
||||
Wird geladen...
|
||||
</span>
|
||||
</td>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useEffect, useCallback } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { Check, X, AlertTriangle, Info } from 'lucide-react';
|
||||
import { useUIStore, Toast as ToastType } from '@/store/uiStore';
|
||||
|
||||
const toastStyles: Record<ToastType['type'], string> = {
|
||||
@@ -9,11 +10,11 @@ const toastStyles: Record<ToastType['type'], string> = {
|
||||
info: 'bg-primary-50 border-primary-500 text-primary-800',
|
||||
};
|
||||
|
||||
const toastIcons: Record<ToastType['type'], string> = {
|
||||
success: 'M5 13l4 4L19 7',
|
||||
error: 'M6 18L18 6M6 6l12 12',
|
||||
warning: 'M12 9v2m0 4h.01M5 19h14a2 2 0 001.684-3.048l-7-12a2 2 0 00-3.368 0l-7 12A2 2 0 005 19z',
|
||||
info: 'M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z',
|
||||
const toastIcons: Record<ToastType['type'], React.ComponentType<{ className?: string }>> = {
|
||||
success: Check,
|
||||
error: X,
|
||||
warning: AlertTriangle,
|
||||
info: Info,
|
||||
};
|
||||
|
||||
function ToastItem({ toast, onRemove }: { toast: ToastType; onRemove: (id: string) => void }) {
|
||||
@@ -33,18 +34,17 @@ function ToastItem({ toast, onRemove }: { toast: ToastType; onRemove: (id: strin
|
||||
role="alert"
|
||||
aria-live="polite"
|
||||
>
|
||||
<svg className="h-5 w-5 flex-shrink-0 mt-0.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d={toastIcons[toast.type]} />
|
||||
</svg>
|
||||
{(() => {
|
||||
const Icon = toastIcons[toast.type];
|
||||
return <Icon className="h-5 w-5 flex-shrink-0 mt-0.5" aria-hidden="true" />;
|
||||
})()}
|
||||
<p className="flex-1 text-sm font-medium">{toast.message}</p>
|
||||
<button
|
||||
onClick={() => onRemove(toast.id)}
|
||||
className="flex-shrink-0 text-current opacity-60 hover:opacity-100 min-h-touch min-w-touch flex items-center justify-center rounded focus:outline-none focus-visible:ring-2 focus-visible:ring-current"
|
||||
aria-label="Close notification"
|
||||
>
|
||||
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
<X className="h-4 w-4" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user