// Towbook Android — core component library // Material 3 conventions, Towbook tokens. All components are self-contained, no external deps beyond // React and the global I (icons) / TBT (tokens) helpers. const { useState: useStateC, useEffect: useEffectC, useRef: useRefC } = React; // ─────── Buttons ────────────────────────────────────────────── // Variants: filled, tonal, outlined, text, destructive // Sizes: sm | md | lg function TBButton({ variant = 'filled', size = 'md', icon, trailingIcon, children, onClick, disabled, fullWidth, style, }) { const [pressed, setPressed] = useStateC(false); const [hover, setHover] = useStateC(false); const h = size === 'sm' ? 36 : size === 'lg' ? 52 : 44; const padX = size === 'sm' ? 14 : size === 'lg' ? 24 : 18; const fs = size === 'sm' ? 13 : size === 'lg' ? 16 : 14; const iconSize = size === 'lg' ? 20 : 18; const palettes = { filled: { bg: pressed ? 'var(--tb-primary-press)' : (hover ? 'var(--tb-primary-hover)' : 'var(--tb-primary)'), fg: 'white', border: 'transparent', shadow: '0 1px 2px rgba(7,113,242,0.20)', }, tonal: { bg: pressed ? 'var(--blue-5)' : (hover ? 'var(--blue-4)' : 'var(--blue-3)'), fg: 'var(--blue-11)', border: 'transparent', shadow: 'none', }, outlined: { bg: pressed ? 'var(--slate-3)' : (hover ? 'var(--slate-2)' : 'transparent'), fg: 'var(--tb-on-surface)', border: 'var(--tb-border-strong)', shadow: 'none', }, text: { bg: pressed ? 'var(--blue-a4)' : (hover ? 'var(--blue-a3)' : 'transparent'), fg: 'var(--blue-11)', border: 'transparent', shadow: 'none', }, destructive: { bg: pressed ? 'var(--red-10)' : (hover ? 'var(--red-9)' : 'var(--red-9)'), fg: 'white', border: 'transparent', shadow: '0 1px 2px rgba(220,62,66,0.20)', }, }; const p = palettes[variant]; return ( ); } // FAB — floating action button. Variants: regular, extended (with label), small. function TBFab({ icon = I.Plus, label, size = 'regular', onClick, color = 'primary', style }) { const [pressed, setPressed] = useStateC(false); const dim = size === 'small' ? 40 : 56; const iconSize = size === 'small' ? 20 : 24; const isExtended = !!label; const palette = color === 'primary' ? { bg: 'var(--tb-primary)', fg: 'white' } : color === 'tonal' ? { bg: 'var(--blue-3)', fg: 'var(--blue-11)' } : { bg: 'var(--tb-surface)', fg: 'var(--blue-11)' }; return ( ); } // ─────── Inputs ────────────────────────────────────────────── // Outlined text field (M3) with floating label, supporting + error states. function TBTextField({ label, value, onChange, placeholder, leadingIcon, trailingIcon, supporting, error, type = 'text', disabled, fullWidth = true, multiline, rows = 4, }) { const [focused, setFocused] = useStateC(false); const filled = (value !== undefined && value !== '') || focused; const borderColor = error ? 'var(--red-9)' : focused ? 'var(--tb-primary)' : 'var(--tb-border-strong)'; const labelColor = error ? 'var(--red-11)' : focused ? 'var(--blue-11)' : 'var(--tb-on-surface-muted)'; const input = multiline ? (