commit 83b26021b2fc0891df5e9866e8689205a488f40a Author: aiyimickey <39365912+aiyimickey@users.noreply.github.com> Date: Mon Jun 29 18:40:02 2026 +0800 feat: 共享 UI 组件库 - Button, Badge, Card, Table, Modal, Input, Select, Toast, Pagination - 统一样式规范,供 assets/issue 及后续站点使用 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/ui/Badge.tsx b/ui/Badge.tsx new file mode 100644 index 0000000..ba9af94 --- /dev/null +++ b/ui/Badge.tsx @@ -0,0 +1,12 @@ +'use client' +type Variant = 'default' | 'success' | 'warning' | 'danger' | 'info' +const v: Record = { + default: 'bg-slate-100 text-slate-700 dark:bg-slate-800 dark:text-slate-300', + success: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-500/10 dark:text-emerald-400', + warning: 'bg-amber-100 text-amber-700 dark:bg-amber-500/10 dark:text-amber-400', + danger: 'bg-red-100 text-red-700 dark:bg-red-500/10 dark:text-red-400', + info: 'bg-blue-100 text-blue-700 dark:bg-blue-500/10 dark:text-blue-400', +} +export default function Badge({ variant = 'default', children }: { variant?: Variant; children: React.ReactNode }) { + return {children} +} diff --git a/ui/Button.tsx b/ui/Button.tsx new file mode 100644 index 0000000..1b22521 --- /dev/null +++ b/ui/Button.tsx @@ -0,0 +1,20 @@ +'use client' +import { ButtonHTMLAttributes, forwardRef } from 'react' +type Variant = 'primary' | 'secondary' | 'danger' | 'ghost' +type Size = 'sm' | 'md' | 'lg' +interface ButtonProps extends ButtonHTMLAttributes { variant?: Variant; size?: Size; loading?: boolean } +const v: Record = { + primary: 'bg-blue-600 hover:bg-blue-700 text-white shadow-sm disabled:opacity-50 disabled:cursor-not-allowed', + secondary: 'bg-slate-100 hover:bg-slate-200 text-slate-700 dark:bg-slate-800 dark:hover:bg-slate-700 dark:text-slate-300', + danger: 'bg-red-600 hover:bg-red-700 text-white disabled:opacity-50 disabled:cursor-not-allowed', + ghost: 'text-slate-600 hover:bg-slate-100 dark:text-slate-400 dark:hover:bg-slate-800', +} +const s: Record = { sm: 'px-3 py-1.5 text-xs', md: 'px-4 py-2 text-sm', lg: 'px-6 py-2.5 text-base' } +const Button = forwardRef(({ variant = 'primary', size = 'md', loading, children, className = '', disabled, ...props }, ref) => ( + +)) +Button.displayName = 'Button' +export default Button diff --git a/ui/Card.tsx b/ui/Card.tsx new file mode 100644 index 0000000..a7c05ae --- /dev/null +++ b/ui/Card.tsx @@ -0,0 +1,5 @@ +'use client' +import { ReactNode } from 'react' +export default function Card({ children, className = '' }: { children: ReactNode; className?: string }) { + return
{children}
+} diff --git a/ui/Input.tsx b/ui/Input.tsx new file mode 100644 index 0000000..c359071 --- /dev/null +++ b/ui/Input.tsx @@ -0,0 +1,6 @@ +'use client' +import { InputHTMLAttributes } from 'react' +interface InputProps extends InputHTMLAttributes { label?: string; error?: string } +export function Input({ label, error, className = '', ...props }: InputProps) { + return (
{label && }{error &&

{error}

}
) +} diff --git a/ui/Modal.tsx b/ui/Modal.tsx new file mode 100644 index 0000000..6810733 --- /dev/null +++ b/ui/Modal.tsx @@ -0,0 +1,16 @@ +'use client' +import { ReactNode, useEffect } from 'react' +interface ModalProps { open: boolean; onClose: () => void; title?: string; children: ReactNode; maxWidth?: string } +export default function Modal({ open, onClose, title, children, maxWidth = 'max-w-lg' }: ModalProps) { + useEffect(() => { if (open) document.body.style.overflow = 'hidden'; return () => { document.body.style.overflow = '' } }, [open]) + if (!open) return null + return ( +
+
+
+ {title &&

{title}

} + {children} +
+
+ ) +} diff --git a/ui/Pagination.tsx b/ui/Pagination.tsx new file mode 100644 index 0000000..b810166 --- /dev/null +++ b/ui/Pagination.tsx @@ -0,0 +1,14 @@ +'use client' +interface PaginationProps { page: number; totalPages: number; onPageChange: (page: number) => void } +export function Pagination({ page, totalPages, onPageChange }: PaginationProps) { + if (totalPages <= 1) return null + return ( +
+ 第 {page} / {totalPages} 页 +
+ + +
+
+ ) +} diff --git a/ui/Select.tsx b/ui/Select.tsx new file mode 100644 index 0000000..a4540b0 --- /dev/null +++ b/ui/Select.tsx @@ -0,0 +1,6 @@ +'use client' +import { SelectHTMLAttributes } from 'react' +interface SelectProps extends SelectHTMLAttributes { label?: string; options: { value: string; label: string }[] } +export function Select({ label, options, className = '', ...props }: SelectProps) { + return (
{label && }
) +} diff --git a/ui/Table.tsx b/ui/Table.tsx new file mode 100644 index 0000000..b446634 --- /dev/null +++ b/ui/Table.tsx @@ -0,0 +1,15 @@ +'use client' +import { ReactNode } from 'react' +interface TableHeadersProps { headers: string[]; children: ReactNode } +export function Table({ headers, children }: TableHeadersProps) { + return ( +
+ + + {headers.map((h) => ())} + + {children} +
{h}
+
+ ) +} diff --git a/ui/Toast.tsx b/ui/Toast.tsx new file mode 100644 index 0000000..485229e --- /dev/null +++ b/ui/Toast.tsx @@ -0,0 +1,6 @@ +'use client' +interface ToastProps { message: string; type?: 'success' | 'error' | 'info'; onClose: () => void } +export function Toast({ message, type = 'info', onClose }: ToastProps) { + const c = { success: 'bg-emerald-50 text-emerald-800 border-emerald-200 dark:bg-emerald-500/10 dark:text-emerald-400 dark:border-emerald-500/20', error: 'bg-red-50 text-red-800 border-red-200 dark:bg-red-500/10 dark:text-red-400 dark:border-red-500/20', info: 'bg-blue-50 text-blue-800 border-blue-200 dark:bg-blue-500/10 dark:text-blue-400 dark:border-blue-500/20' } + return (
{message}
) +} diff --git a/ui/index.tsx b/ui/index.tsx new file mode 100644 index 0000000..26cf45b --- /dev/null +++ b/ui/index.tsx @@ -0,0 +1,9 @@ +export { default as Button } from './Button' +export { default as Badge } from './Badge' +export { default as Card } from './Card' +export { Table } from './Table' +export { default as Modal } from './Modal' +export { Input } from './Input' +export { Select } from './Select' +export { Toast } from './Toast' +export { Pagination } from './Pagination' diff --git a/ui/package.json b/ui/package.json new file mode 100644 index 0000000..fe7f959 --- /dev/null +++ b/ui/package.json @@ -0,0 +1,12 @@ +{ + "name": "@tlyq/shared-ui", + "version": "1.0.0", + "description": "TLYQ 统一 UI 组件库", + "main": "index.tsx", + "dependencies": { + "react": "^19.0.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0" + } +}