/** * Il campo dove si scrive, con la sua etichetta sopra. * * Nasce dai commenti dei tester sulla schermata Modifica: i campi dicevano * cosa erano solo col segnaposto, che sparisce appena si scrive (nel risotto * restava un «4» da solo), ed erano una riga sottile che non si vedeva. Da qui * tre regole, le stesse ovunque si scriva: * * - l'etichetta sta sempre sopra, piccola e tenue, anche a campo pieno; * - il campo è una casella col bordo intero, in `colori.campo` (3:1, la soglia * per il contorno di un comando), e quando è attivo il bordo si fa accento; * - dentro il campo vuoto c'è un esempio («Es. 200»), non il nome del campo. * * `CampoScelta` ha la stessa faccia ma non si scrive: si tocca e apre una * scelta (la categoria). La freccia in fondo dice che si apre qualcosa. * * I colori non si scrivono qui: vengono da tema.ts, come nelle altre schermate. */ import type { ReactNode, Ref } from 'react'; import { useMemo, useState } from 'react'; import type { StyleProp, TextInputProps, ViewStyle } from 'react-native'; import { Pressable, StyleSheet, Text, TextInput, View } from 'react-native'; import type { Colori } from '../tema.ts'; import { RAGGIO, SPAZIO, TESTO, useColori } from '../tema.ts'; import Segno from './Segno.tsx'; export type PropsCampoTesto = Omit & { /** Assente quando il nome lo dà già un titolo sopra (il procedimento). */ etichetta?: string; /** La riga tenue sotto il campo. */ aiuto?: string; /** Il campo alto su più righe. */ alto?: boolean; /** Numeri allineati a destra: la quantità, che si legge in colonna. */ numerico?: boolean; stile?: StyleProp; ref?: Ref; }; export function CampoTesto({ etichetta, aiuto, alto = false, numerico = false, stile, ref, onFocus, onBlur, ...resto }: PropsCampoTesto) { const colori = useColori(); const stili = useMemo(() => creaStili(colori), [colori]); // Il bordo accento dice dove sta il cursore: con più caselle in fila (il // foglio dell'ingrediente) è l'unico modo di saperlo senza guardare il // cursore, che è sottile. const [attivo, setAttivo] = useState(false); return ( {etichetta !== undefined ? {etichetta} : null} { setAttivo(true); onFocus?.(e); }} onBlur={(e) => { setAttivo(false); onBlur?.(e); }} style={[ stili.casella, stili.testoCasella, alto ? stili.alta : null, numerico ? stili.numerica : null, attivo ? stili.attiva : null, ]} /> {aiuto !== undefined ? {aiuto} : null} ); } export interface PropsCampoScelta { etichetta: string; /** Quello che si vede nella casella: la categoria col suo colore, o «Nessuna». */ children: ReactNode; onPress: () => void; /** Il nome per chi usa il lettore di schermo: etichetta e valore insieme. */ accessibilityLabel: string; stile?: StyleProp; } export function CampoScelta({ etichetta, children, onPress, accessibilityLabel, stile }: PropsCampoScelta) { const colori = useColori(); const stili = useMemo(() => creaStili(colori), [colori]); return ( {etichetta} [stili.casella, stili.scelta, pressed ? stili.premuta : null]} > {children} {/* La freccia girata in giù: si apre una scelta, non si va altrove. */} ); } /** Il titolo grande di un blocco della pagina: «Ingredienti», «Procedimento». */ export function TitoloBlocco({ titolo, conteggio }: { titolo: string; conteggio?: string }) { const colori = useColori(); const stili = useMemo(() => creaStili(colori), [colori]); return ( {titolo} {conteggio !== undefined ? {conteggio} : null} ); } const creaStili = (colori: Colori) => StyleSheet.create({ blocco: { gap: 6 }, etichetta: { ...TESTO.piccolo, fontWeight: '600', color: colori.testoTenue }, casella: { minHeight: 48, borderRadius: RAGGIO.campo, borderWidth: 1, borderColor: colori.campo, backgroundColor: colori.superficie, paddingHorizontal: 14, }, testoCasella: { ...TESTO.corpo, color: colori.testo }, // Il bordo si ingrossa di un punto e il testo non deve spostarsi: il // padding perde quel punto. attiva: { borderWidth: 2, borderColor: colori.accento, paddingHorizontal: 13 }, alta: { minHeight: 112, paddingTop: 12, paddingBottom: 12, lineHeight: 24 }, numerica: { textAlign: 'right', fontVariant: ['tabular-nums'] }, aiuto: { ...TESTO.piccolo, color: colori.testoTenue, lineHeight: 18 }, scelta: { flexDirection: 'row', alignItems: 'center', gap: SPAZIO.s }, premuta: { backgroundColor: colori.bordo }, valore: { flex: 1, flexDirection: 'row', alignItems: 'center', gap: SPAZIO.s, minWidth: 0 }, giu: { transform: [{ rotate: '90deg' }] }, titoloBlocco: { flexDirection: 'row', alignItems: 'baseline', justifyContent: 'space-between', paddingHorizontal: SPAZIO.l, paddingTop: SPAZIO.xl + SPAZIO.xs, paddingBottom: 10, }, titoloTesto: { ...TESTO.voce, color: colori.testo }, conteggio: { ...TESTO.etichetta, color: colori.testoTenue, fontVariant: ['tabular-nums'] }, });