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:
Agent Zero
2026-07-23 00:47:23 +02:00
parent 3f2307ab54
commit e9a5eee524
9 changed files with 34 additions and 33 deletions
+2 -4
View File
@@ -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}
+2 -3
View File
@@ -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">
+2 -3
View File
@@ -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(
+3 -6
View File
@@ -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>
+2 -4
View File
@@ -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>
+11 -11
View File
@@ -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>
);