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:
+2
-2
@@ -9,8 +9,8 @@
|
|||||||
|
|
||||||
| Task | Status | Datum | Notiz |
|
| Task | Status | Datum | Notiz |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| 0.1 | 🔄 in-progress | 2026-07-23 | Veraltete Planungsdokumente aktualisieren |
|
| 0.1 | ✅ done | 2026-07-23 | codebase-vs-requirements.md neu geschrieben, security-review-phase2.md Resolution Summary, architecture.md Implementation Status, MASTER-PLAN.md + PROGRESS.md erstellt |
|
||||||
| 0.2 | ⬜ pending | | lucide-react installieren + Icons migrieren |
|
| 0.2 | 🔄 in-progress | 2026-07-23 | lucide-react installieren + Icons migrieren |
|
||||||
| 0.3 | ⬜ pending | | date-fns installieren + Datum-Formatierung |
|
| 0.3 | ⬜ pending | | date-fns installieren + Datum-Formatierung |
|
||||||
| 0.4 | ⬜ pending | | hooks.ts aufteilen |
|
| 0.4 | ⬜ pending | | hooks.ts aufteilen |
|
||||||
| 0.5 | ⬜ pending | | Store-Verzeichnis konsolidieren |
|
| 0.5 | ⬜ pending | | Store-Verzeichnis konsolidieren |
|
||||||
|
|||||||
Generated
+9
@@ -25,6 +25,7 @@
|
|||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"i18next": "^23.14.0",
|
"i18next": "^23.14.0",
|
||||||
"i18next-browser-languagedetector": "^8.0.0",
|
"i18next-browser-languagedetector": "^8.0.0",
|
||||||
|
"lucide-react": "^1.25.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-hook-form": "^7.53.0",
|
"react-hook-form": "^7.53.0",
|
||||||
@@ -3854,6 +3855,14 @@
|
|||||||
"yallist": "^3.0.2"
|
"yallist": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lucide-react": {
|
||||||
|
"version": "1.25.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.25.0.tgz",
|
||||||
|
"integrity": "sha512-/mdJTRbiwcLOQ1NZZK1amZF9rIZyvO18D6r9TngE6TG1NmqHgFuT4eE7Xrkm9UsXMbBJD1NlfwHVltCDWHrOTw==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/lz-string": {
|
"node_modules/lz-string": {
|
||||||
"version": "1.5.0",
|
"version": "1.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"i18next": "^23.14.0",
|
"i18next": "^23.14.0",
|
||||||
"i18next-browser-languagedetector": "^8.0.0",
|
"i18next-browser-languagedetector": "^8.0.0",
|
||||||
|
"lucide-react": "^1.25.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-hook-form": "^7.53.0",
|
"react-hook-form": "^7.53.0",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
import { Loader2 } from 'lucide-react';
|
||||||
|
|
||||||
export type ButtonVariant = 'primary' | 'secondary' | 'danger' | 'ghost';
|
export type ButtonVariant = 'primary' | 'secondary' | 'danger' | 'ghost';
|
||||||
export type ButtonSize = 'sm' | 'md' | 'lg';
|
export type ButtonSize = 'sm' | 'md' | 'lg';
|
||||||
@@ -42,10 +43,7 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{isLoading && (
|
{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">
|
<Loader2 className="animate-spin motion-reduce:animate-none -ml-1 mr-2 h-4 w-4" 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>
|
|
||||||
)}
|
)}
|
||||||
{icon && !isLoading && <span className="mr-2" aria-hidden="true">{icon}</span>}
|
{icon && !isLoading && <span className="mr-2" aria-hidden="true">{icon}</span>}
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
import { FolderOpen } from 'lucide-react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export interface EmptyStateProps {
|
export interface EmptyStateProps {
|
||||||
@@ -35,9 +36,7 @@ export function EmptyState({
|
|||||||
)}
|
)}
|
||||||
{!icon && (
|
{!icon && (
|
||||||
<div className="mb-4 w-16 h-16 rounded-full bg-secondary-100 flex items-center justify-center" aria-hidden="true">
|
<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">
|
<FolderOpen className="w-8 h-8 text-secondary-400" />
|
||||||
<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>
|
</div>
|
||||||
)}
|
)}
|
||||||
<h3 className="text-lg font-semibold text-secondary-900">
|
<h3 className="text-lg font-semibold text-secondary-900">
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { useEffect, useRef } from 'react';
|
import React, { useEffect, useRef } from 'react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
import { X } from 'lucide-react';
|
||||||
|
|
||||||
export interface ModalProps {
|
export interface ModalProps {
|
||||||
open: boolean;
|
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"
|
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"
|
aria-label="Close dialog"
|
||||||
>
|
>
|
||||||
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
<X className="h-5 w-5" aria-hidden="true" />
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<div className={clsx(
|
<div className={clsx(
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export interface PaginationProps {
|
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"
|
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')}
|
aria-label={t('pagination.previous')}
|
||||||
>
|
>
|
||||||
<svg className="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
<ChevronLeft className="h-3 w-3" aria-hidden="true" />
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
</button>
|
||||||
{startPage > 1 && (
|
{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"
|
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')}
|
aria-label={t('pagination.next')}
|
||||||
>
|
>
|
||||||
<svg className="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
<ChevronRight className="h-3 w-3" aria-hidden="true" />
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { useState, useMemo } from 'react';
|
import React, { useState, useMemo } from 'react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
import { Loader2 } from 'lucide-react';
|
||||||
|
|
||||||
export interface TableColumn<T> {
|
export interface TableColumn<T> {
|
||||||
key: string;
|
key: string;
|
||||||
@@ -100,10 +101,7 @@ export function Table<T extends Record<string, any>>({
|
|||||||
<tr>
|
<tr>
|
||||||
<td colSpan={columns.length} className="px-6 py-8 text-center text-secondary-500">
|
<td colSpan={columns.length} className="px-6 py-8 text-center text-secondary-500">
|
||||||
<span className="inline-flex items-center gap-2">
|
<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">
|
<Loader2 className="animate-spin motion-reduce:animate-none h-5 w-5" 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>
|
|
||||||
Wird geladen...
|
Wird geladen...
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { useEffect, useCallback } from 'react';
|
import React, { useEffect, useCallback } from 'react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
import { Check, X, AlertTriangle, Info } from 'lucide-react';
|
||||||
import { useUIStore, Toast as ToastType } from '@/store/uiStore';
|
import { useUIStore, Toast as ToastType } from '@/store/uiStore';
|
||||||
|
|
||||||
const toastStyles: Record<ToastType['type'], string> = {
|
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',
|
info: 'bg-primary-50 border-primary-500 text-primary-800',
|
||||||
};
|
};
|
||||||
|
|
||||||
const toastIcons: Record<ToastType['type'], string> = {
|
const toastIcons: Record<ToastType['type'], React.ComponentType<{ className?: string }>> = {
|
||||||
success: 'M5 13l4 4L19 7',
|
success: Check,
|
||||||
error: 'M6 18L18 6M6 6l12 12',
|
error: X,
|
||||||
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',
|
warning: AlertTriangle,
|
||||||
info: 'M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z',
|
info: Info,
|
||||||
};
|
};
|
||||||
|
|
||||||
function ToastItem({ toast, onRemove }: { toast: ToastType; onRemove: (id: string) => void }) {
|
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"
|
role="alert"
|
||||||
aria-live="polite"
|
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]} />
|
const Icon = toastIcons[toast.type];
|
||||||
</svg>
|
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>
|
<p className="flex-1 text-sm font-medium">{toast.message}</p>
|
||||||
<button
|
<button
|
||||||
onClick={() => onRemove(toast.id)}
|
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"
|
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"
|
aria-label="Close notification"
|
||||||
>
|
>
|
||||||
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
<X className="h-4 w-4" aria-hidden="true" />
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user