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.
106 lines
3.2 KiB
TypeScript
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>
|
|
);
|
|
}
|