mirror of
https://github.com/s-frick/effigenix.git
synced 2026-03-28 13:59:36 +01:00
feat(frontend): Tauri Apps, Shared UI Library und Nix Flake
- packages/ui: Shared React Component Library (Button, Card, Badge, Input) mit Tailwind v4 @theme Design Tokens (oklch) - apps/web: ERP Web-UI (Vite + React + Tailwind v4, API-Proxy :8080) - apps/scanner: Tauri v2 Mobile App mit Barcode-Scanner Plugin (cfg(mobile) für Desktop-Kompatibilität) - flake.nix: Nix Flake mit rust-overlay, Tauri System-Deps (GTK, WebKitGTK, libsoup, OpenSSL), ersetzt shell.nix - justfile: Dev-Befehle für alle Projekte (backend, cli, web, scanner) - frontend/CLAUDE.md: Agent Guide mit Base UI Docs Referenz
This commit is contained in:
parent
b9b89e3f0e
commit
ef50eb8279
96 changed files with 11682 additions and 16 deletions
60
frontend/packages/ui/package.json
Normal file
60
frontend/packages/ui/package.json
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
{
|
||||
"name": "@effigenix/ui",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"description": "Shared React component library with Tailwind v4 theme",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"./theme.css": "./src/theme.css"
|
||||
},
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"files": [
|
||||
"dist",
|
||||
"src/theme.css"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"dev": "tsup --watch",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"clean": "rm -rf dist"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.48",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"clsx": "^2.1.1",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
"tsup": "^8.0.1",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"clsx": "^2.1.1",
|
||||
"tailwind-merge": "^2.6.0"
|
||||
},
|
||||
"tsup": {
|
||||
"entry": [
|
||||
"src/index.ts"
|
||||
],
|
||||
"format": [
|
||||
"esm"
|
||||
],
|
||||
"dts": true,
|
||||
"clean": true,
|
||||
"sourcemap": true,
|
||||
"splitting": false,
|
||||
"external": [
|
||||
"react",
|
||||
"react-dom"
|
||||
]
|
||||
}
|
||||
}
|
||||
34
frontend/packages/ui/src/components/Badge.tsx
Normal file
34
frontend/packages/ui/src/components/Badge.tsx
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import { forwardRef, type HTMLAttributes } from 'react';
|
||||
import { cn } from '../lib/cn';
|
||||
|
||||
type BadgeVariant = 'success' | 'warning' | 'danger' | 'info' | 'neutral';
|
||||
|
||||
export interface BadgeProps extends HTMLAttributes<HTMLSpanElement> {
|
||||
variant?: BadgeVariant;
|
||||
}
|
||||
|
||||
const variantStyles: Record<BadgeVariant, string> = {
|
||||
success: 'bg-success-100 text-success-800 border-success-300',
|
||||
warning: 'bg-warning-100 text-warning-800 border-warning-300',
|
||||
danger: 'bg-danger-100 text-danger-800 border-danger-300',
|
||||
info: 'bg-info-100 text-info-800 border-info-300',
|
||||
neutral: 'bg-warm-100 text-warm-700 border-warm-300',
|
||||
};
|
||||
|
||||
export const Badge = forwardRef<HTMLSpanElement, BadgeProps>(
|
||||
({ className, variant = 'neutral', ...props }, ref) => {
|
||||
return (
|
||||
<span
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-medium',
|
||||
variantStyles[variant],
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Badge.displayName = 'Badge';
|
||||
48
frontend/packages/ui/src/components/Button.tsx
Normal file
48
frontend/packages/ui/src/components/Button.tsx
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import { forwardRef, type ButtonHTMLAttributes } from 'react';
|
||||
import { cn } from '../lib/cn';
|
||||
|
||||
type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger';
|
||||
type ButtonSize = 'sm' | 'md' | 'lg';
|
||||
|
||||
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
variant?: ButtonVariant;
|
||||
size?: ButtonSize;
|
||||
}
|
||||
|
||||
const variantStyles: Record<ButtonVariant, string> = {
|
||||
primary:
|
||||
'bg-brand-600 text-white hover:bg-brand-700 active:bg-brand-800 focus-visible:ring-brand-500',
|
||||
secondary:
|
||||
'bg-warm-100 text-warm-800 hover:bg-warm-200 active:bg-warm-300 focus-visible:ring-warm-400 border border-warm-300',
|
||||
ghost: 'text-warm-700 hover:bg-warm-100 active:bg-warm-200 focus-visible:ring-warm-400',
|
||||
danger:
|
||||
'bg-danger-600 text-white hover:bg-danger-700 active:bg-danger-800 focus-visible:ring-danger-500',
|
||||
};
|
||||
|
||||
const sizeStyles: Record<ButtonSize, string> = {
|
||||
sm: 'px-3 py-1.5 text-sm',
|
||||
md: 'px-4 py-2 text-sm',
|
||||
lg: 'px-5 py-2.5 text-base',
|
||||
};
|
||||
|
||||
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant = 'primary', size = 'md', disabled, ...props }, ref) => {
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'inline-flex items-center justify-center font-medium rounded-md transition-colors',
|
||||
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',
|
||||
'disabled:opacity-50 disabled:pointer-events-none',
|
||||
variantStyles[variant],
|
||||
sizeStyles[size],
|
||||
className
|
||||
)}
|
||||
disabled={disabled}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Button.displayName = 'Button';
|
||||
19
frontend/packages/ui/src/components/Card.tsx
Normal file
19
frontend/packages/ui/src/components/Card.tsx
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { forwardRef, type HTMLAttributes } from 'react';
|
||||
import { cn } from '../lib/cn';
|
||||
|
||||
export interface CardProps extends HTMLAttributes<HTMLDivElement> {}
|
||||
|
||||
export const Card = forwardRef<HTMLDivElement, CardProps>(({ className, ...props }, ref) => {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'rounded-lg border border-warm-200 bg-white p-6 shadow-sm',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
Card.displayName = 'Card';
|
||||
22
frontend/packages/ui/src/components/Input.tsx
Normal file
22
frontend/packages/ui/src/components/Input.tsx
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { forwardRef, type InputHTMLAttributes } from 'react';
|
||||
import { cn } from '../lib/cn';
|
||||
|
||||
export interface InputProps extends InputHTMLAttributes<HTMLInputElement> {}
|
||||
|
||||
export const Input = forwardRef<HTMLInputElement, InputProps>(({ className, ...props }, ref) => {
|
||||
return (
|
||||
<input
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'block w-full rounded-md border border-warm-300 bg-white px-3 py-2 text-sm text-warm-900',
|
||||
'placeholder:text-warm-400',
|
||||
'focus:border-brand-500 focus:outline-none focus:ring-2 focus:ring-brand-500/20',
|
||||
'disabled:cursor-not-allowed disabled:bg-warm-50 disabled:opacity-50',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
Input.displayName = 'Input';
|
||||
5
frontend/packages/ui/src/index.ts
Normal file
5
frontend/packages/ui/src/index.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
export { Button, type ButtonProps } from './components/Button';
|
||||
export { Card, type CardProps } from './components/Card';
|
||||
export { Badge, type BadgeProps } from './components/Badge';
|
||||
export { Input, type InputProps } from './components/Input';
|
||||
export { cn } from './lib/cn';
|
||||
6
frontend/packages/ui/src/lib/cn.ts
Normal file
6
frontend/packages/ui/src/lib/cn.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { type ClassValue, clsx } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export function cn(...inputs: ClassValue[]): string {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
88
frontend/packages/ui/src/theme.css
Normal file
88
frontend/packages/ui/src/theme.css
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
@theme {
|
||||
/* Brand – warm orange/amber */
|
||||
--color-brand-50: oklch(0.98 0.02 75);
|
||||
--color-brand-100: oklch(0.95 0.04 75);
|
||||
--color-brand-200: oklch(0.9 0.08 75);
|
||||
--color-brand-300: oklch(0.83 0.12 75);
|
||||
--color-brand-400: oklch(0.75 0.16 70);
|
||||
--color-brand-500: oklch(0.65 0.19 55);
|
||||
--color-brand-600: oklch(0.58 0.2 50);
|
||||
--color-brand-700: oklch(0.5 0.18 50);
|
||||
--color-brand-800: oklch(0.42 0.14 50);
|
||||
--color-brand-900: oklch(0.35 0.1 50);
|
||||
--color-brand-950: oklch(0.25 0.07 50);
|
||||
|
||||
/* Warm neutrals */
|
||||
--color-warm-50: oklch(0.98 0.005 75);
|
||||
--color-warm-100: oklch(0.95 0.01 75);
|
||||
--color-warm-200: oklch(0.91 0.015 75);
|
||||
--color-warm-300: oklch(0.85 0.02 75);
|
||||
--color-warm-400: oklch(0.7 0.02 75);
|
||||
--color-warm-500: oklch(0.55 0.02 75);
|
||||
--color-warm-600: oklch(0.45 0.02 75);
|
||||
--color-warm-700: oklch(0.37 0.015 75);
|
||||
--color-warm-800: oklch(0.3 0.01 75);
|
||||
--color-warm-900: oklch(0.22 0.008 75);
|
||||
--color-warm-950: oklch(0.15 0.005 75);
|
||||
|
||||
/* Success – green */
|
||||
--color-success-50: oklch(0.97 0.03 145);
|
||||
--color-success-100: oklch(0.93 0.06 145);
|
||||
--color-success-200: oklch(0.87 0.1 145);
|
||||
--color-success-300: oklch(0.78 0.15 145);
|
||||
--color-success-400: oklch(0.68 0.18 145);
|
||||
--color-success-500: oklch(0.6 0.17 145);
|
||||
--color-success-600: oklch(0.52 0.15 145);
|
||||
--color-success-700: oklch(0.45 0.12 145);
|
||||
--color-success-800: oklch(0.38 0.1 145);
|
||||
--color-success-900: oklch(0.32 0.07 145);
|
||||
--color-success-950: oklch(0.22 0.05 145);
|
||||
|
||||
/* Warning – yellow/amber */
|
||||
--color-warning-50: oklch(0.98 0.03 95);
|
||||
--color-warning-100: oklch(0.95 0.07 95);
|
||||
--color-warning-200: oklch(0.9 0.12 90);
|
||||
--color-warning-300: oklch(0.84 0.17 85);
|
||||
--color-warning-400: oklch(0.78 0.18 80);
|
||||
--color-warning-500: oklch(0.72 0.17 75);
|
||||
--color-warning-600: oklch(0.62 0.16 65);
|
||||
--color-warning-700: oklch(0.52 0.13 60);
|
||||
--color-warning-800: oklch(0.44 0.1 60);
|
||||
--color-warning-900: oklch(0.37 0.07 60);
|
||||
--color-warning-950: oklch(0.27 0.05 60);
|
||||
|
||||
/* Danger – red */
|
||||
--color-danger-50: oklch(0.97 0.02 25);
|
||||
--color-danger-100: oklch(0.93 0.05 25);
|
||||
--color-danger-200: oklch(0.87 0.1 25);
|
||||
--color-danger-300: oklch(0.78 0.15 25);
|
||||
--color-danger-400: oklch(0.68 0.18 25);
|
||||
--color-danger-500: oklch(0.6 0.2 25);
|
||||
--color-danger-600: oklch(0.52 0.19 25);
|
||||
--color-danger-700: oklch(0.45 0.16 25);
|
||||
--color-danger-800: oklch(0.38 0.12 25);
|
||||
--color-danger-900: oklch(0.32 0.09 25);
|
||||
--color-danger-950: oklch(0.22 0.06 25);
|
||||
|
||||
/* Info – blue */
|
||||
--color-info-50: oklch(0.97 0.02 245);
|
||||
--color-info-100: oklch(0.93 0.05 245);
|
||||
--color-info-200: oklch(0.87 0.09 245);
|
||||
--color-info-300: oklch(0.78 0.14 245);
|
||||
--color-info-400: oklch(0.68 0.17 245);
|
||||
--color-info-500: oklch(0.6 0.18 245);
|
||||
--color-info-600: oklch(0.52 0.17 245);
|
||||
--color-info-700: oklch(0.45 0.14 245);
|
||||
--color-info-800: oklch(0.38 0.11 245);
|
||||
--color-info-900: oklch(0.32 0.08 245);
|
||||
--color-info-950: oklch(0.22 0.06 245);
|
||||
|
||||
/* Font */
|
||||
--font-sans: 'Poppins', ui-sans-serif, system-ui, sans-serif;
|
||||
|
||||
/* Radius */
|
||||
--radius-sm: 0.375rem;
|
||||
--radius-md: 0.5rem;
|
||||
--radius-lg: 0.75rem;
|
||||
--radius-xl: 1rem;
|
||||
}
|
||||
11
frontend/packages/ui/tsconfig.json
Normal file
11
frontend/packages/ui/tsconfig.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"composite": false,
|
||||
"incremental": false
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue