28 lines
1.9 KiB
TypeScript
28 lines
1.9 KiB
TypeScript
import type { ReactNode } from 'react'
|
|
import { AlertCircle, CheckCircle2, Inbox, X } from 'lucide-react'
|
|
import { Button } from './Button'
|
|
import { IconButton } from './IconButton'
|
|
import styles from './Feedback.module.css'
|
|
|
|
export function Spinner({ label = '加载中' }: { label?: string }) {
|
|
return <span className={styles.spinner} role="status"><span /> <span className="sr-only">{label}</span></span>
|
|
}
|
|
|
|
export function Skeleton({ height = 80 }: { height?: number }) {
|
|
return <span className={styles.skeleton} style={{ height }} aria-hidden="true" />
|
|
}
|
|
|
|
export function EmptyState({ title = '暂无内容', description = '新的内容会显示在这里', action }: { title?: string; description?: string; action?: ReactNode }) {
|
|
return <div className={styles.empty}><Inbox /><strong>{title}</strong><span>{description}</span>{action}</div>
|
|
}
|
|
|
|
interface DialogProps { open: boolean; title: string; description?: string; onClose: () => void; children?: ReactNode }
|
|
export function Dialog({ open, title, description, onClose, children }: DialogProps) {
|
|
if (!open) return null
|
|
return <div className={styles.backdrop} role="presentation" onMouseDown={onClose}><section className={styles.dialog} role="dialog" aria-modal="true" aria-labelledby="dialog-title" onMouseDown={event => event.stopPropagation()}><header><span><strong id="dialog-title">{title}</strong>{description && <small>{description}</small>}</span><IconButton label="关闭" icon={<X />} onClick={onClose} /></header><div className={styles.dialogBody}>{children}</div><footer><Button variant="ghost" onClick={onClose}>取消</Button><Button variant="primary" leadingIcon={<CheckCircle2 />}>确认</Button></footer></section></div>
|
|
}
|
|
|
|
export function InlineAlert({ tone = 'info', children }: { tone?: 'info' | 'danger'; children: ReactNode }) {
|
|
return <div className={styles.alert} data-tone={tone}>{tone === 'danger' ? <AlertCircle /> : <CheckCircle2 />}{children}</div>
|
|
}
|