Files
familysync/apps/pwa/src/components/AddItemInput.tsx
T
Lucas Berger 982438dc10 style(13-03): apply Prettier formatting across repo
Mechanical reformat — no logic changes. 398 files changed, 19125
insertions(+), 16457 deletions(-). Prettier 3.8.4 with .prettierrc
(singleQuote:true, semi:true, tabWidth:2, trailingComma:all,
printWidth:100). Isolated per D-13-08 for reviewability.
2026-06-11 20:35:18 -04:00

106 lines
3.2 KiB
TypeScript

/**
* AddItemInput — sticky add-item text input at the bottom of ListDetail.
*
* Design contract (UI-SPEC §AddItemInput):
* - Sticky at the bottom, above keyboard on mobile
* - Horizontal flex: text input (flex:1) + "Add" button
* - Input: --text-body-*, 44px min-height, placeholder "Add an item…"
* - Button: "Add" label, --color-member-0 bg, white text, disabled when empty
* - Submit: Enter key OR tap "Add" button
*/
import { useState, useRef } from 'react';
interface AddItemInputProps {
onAdd: (text: string) => void;
/** Whether the add mutation is pending (parent controls this for optimistic state) */
isPending?: boolean;
}
export function AddItemInput({ onAdd, isPending = false }: AddItemInputProps) {
const [text, setText] = useState('');
const inputRef = useRef<HTMLInputElement>(null);
function handleSubmit() {
const trimmed = text.trim();
if (!trimmed) return;
onAdd(trimmed);
setText('');
inputRef.current?.focus();
}
function handleKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {
if (e.key === 'Enter') {
e.preventDefault();
handleSubmit();
}
}
return (
<div
style={{
position: 'sticky',
bottom: 0,
left: 0,
right: 0,
display: 'flex',
gap: 'var(--space-2)',
padding: 'var(--space-3) var(--space-4)',
background: 'var(--color-surface)',
borderTop: '1px solid var(--color-border)',
// Push above keyboard on iOS and bottom tab bar
paddingBottom: 'calc(var(--space-3) + env(safe-area-inset-bottom, 0px))',
fontFamily: 'var(--font-family-base)',
}}
>
<input
ref={inputRef}
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="Add an item…"
disabled={isPending}
style={{
flex: 1,
fontSize: 'var(--text-body-size, 15px)',
background: 'var(--color-surface)',
border: '1px solid var(--color-border)',
borderRadius: 'var(--space-1)',
padding: 'var(--space-2) var(--space-4)',
minHeight: '44px',
color: 'var(--color-text-primary)',
outline: 'none',
fontFamily: 'var(--font-family-base)',
}}
onFocus={(e) => {
e.currentTarget.style.borderColor = 'var(--color-focus-ring, #4A90D9)';
}}
onBlur={(e) => {
e.currentTarget.style.borderColor = 'var(--color-border)';
}}
/>
<button
onClick={handleSubmit}
disabled={!text.trim() || isPending}
aria-label="Add item"
style={{
background: 'var(--color-member-0)',
color: '#fff',
border: 'none',
borderRadius: 'var(--space-1)',
padding: '0 var(--space-4)',
minHeight: '44px',
fontSize: 'var(--text-label-size, 13px)',
fontWeight: 600,
cursor: !text.trim() || isPending ? 'not-allowed' : 'pointer',
opacity: !text.trim() || isPending ? 0.5 : 1,
fontFamily: 'var(--font-family-base)',
}}
>
Add
</button>
</div>
);
}