diff --git a/app/src/components/ui/Badge.tsx b/app/src/components/ui/Badge.tsx new file mode 100644 index 0000000..1709d1c --- /dev/null +++ b/app/src/components/ui/Badge.tsx @@ -0,0 +1,34 @@ +import type { ReactNode } from 'react' + +interface BadgeProps { + children: ReactNode + color: string + bg: string +} + +export function Badge({ children, color, bg }: BadgeProps) { + return ( + + {children} + + ) +} + +export const STATUS_COLORS = { + active: { color: '#14F195', bg: 'rgba(20,241,149,.15)' }, + inactive: { color: '#8A8FA3', bg: 'rgba(138,143,163,.16)' }, + awaitingConfirm: { color: '#F5B23E', bg: 'rgba(245,178,62,.15)' }, + disputed: { color: '#FF7A59', bg: 'rgba(255,122,89,.15)' }, + complete: { color: '#4FD1E0', bg: 'rgba(79,209,224,.15)' }, + cancelled: { color: '#8A8FA3', bg: 'rgba(138,143,163,.16)' }, +} diff --git a/app/src/components/ui/Button.tsx b/app/src/components/ui/Button.tsx new file mode 100644 index 0000000..74bbc6e --- /dev/null +++ b/app/src/components/ui/Button.tsx @@ -0,0 +1,52 @@ +import type { CSSProperties, ReactNode, ButtonHTMLAttributes } from 'react' + +export type ButtonVariant = 'primary' | 'outline' | 'danger' | 'ghost' + +interface ButtonProps extends ButtonHTMLAttributes { + variant?: ButtonVariant + children: ReactNode +} + +const variantStyles: Record = { + primary: { + background: 'linear-gradient(135deg, var(--acc), var(--acc2))', + color: '#0b0613', + border: 'none', + }, + outline: { + background: 'transparent', + border: '1px solid var(--bd)', + color: 'var(--tx)', + }, + danger: { + background: 'transparent', + border: '1px solid var(--danger)', + color: 'var(--danger)', + }, + ghost: { + background: 'var(--bg3)', + border: '1px solid var(--bd)', + color: 'var(--tx)', + }, +} + +export function Button({ variant = 'primary', children, style, disabled, ...rest }: ButtonProps) { + return ( + + ) +} diff --git a/app/src/components/ui/FieldRow.tsx b/app/src/components/ui/FieldRow.tsx new file mode 100644 index 0000000..1cfe115 --- /dev/null +++ b/app/src/components/ui/FieldRow.tsx @@ -0,0 +1,75 @@ +import type { ReactNode } from 'react' + +interface FieldRowProps { + label: string + children: ReactNode + last?: boolean +} + +export function FieldRow({ label, children, last }: FieldRowProps) { + return ( +
+
+ {label} +
+
{children}
+
+ ) +} + +export function MonoChip({ + value, + onCopy, + onClick, + sub, +}: { + value: string + onCopy?: () => void + onClick?: () => void + sub?: string +}) { + function copy() { + navigator.clipboard.writeText(value) + onCopy?.() + } + return ( +
+
+ + {value} + + +
+ {sub &&
{sub}
} +
+ ) +} diff --git a/app/src/components/ui/Toast.tsx b/app/src/components/ui/Toast.tsx new file mode 100644 index 0000000..2240277 --- /dev/null +++ b/app/src/components/ui/Toast.tsx @@ -0,0 +1,59 @@ +'use client' + +import { createContext, useCallback, useContext, useState, type ReactNode } from 'react' + +const ToastCtx = createContext<(msg: string) => void>(() => {}) + +export function useToast() { + return useContext(ToastCtx) +} + +export function ToastProvider({ children }: { children: ReactNode }) { + const [msg, setMsg] = useState('') + + const show = useCallback((m: string) => { + setMsg(m) + setTimeout(() => setMsg(''), 2800) + }, []) + + return ( + + {children} + {msg && ( +
+ + {msg} +
+ )} +
+ ) +}