Active Foundation · Example
A domain contract and its messages
A domain contract and its messagesClient
In euros, up to two decimals.
Value '', status empty
Full source examples/ui/number-field-validation.tsx
// apps/docs/examples/ui/number-field-validation.tsx'use client';
import {useState} from 'react';
import type { NumberInputRejection, NumberInputStatus,} from '@workspace/active-format';import {NumberField} from '@workspace/active-ui/components/composed/number-field';import { Field, FieldDescription, FieldError, FieldLabel,} from '@workspace/active-ui/components/primitives/field';
/** * The application's own words for every reason the field can report. The * component does not translate: it reports a code, the application writes * the sentence in the reader's language. */const MESSAGES: Record<NumberInputRejection, string> = { 'not-a-number': 'Enter a number.', 'cross-convention': 'This looks like another notation, such as 1.234,56. Use a point for decimals.', 'grouping-position': 'A thousands separator is in the wrong place.', 'grouping-while-typing': 'Type the number without thousands separators; pasting them is fine.', 'unsupported-negative': 'Write a negative amount with a leading minus sign.', 'unsupported-notation': 'Write the amount out in full.', 'negative-not-allowed': 'The amount cannot be negative.', 'precision-exceeded': 'Use at most two decimals.', 'currency-mismatch': 'This amount is in another currency than euros.',};
/** * A field with a domain contract: euros, two decimals, never negative. While * the reader is still typing (`incomplete`) no error shows; a rejection * shows the application's message for its reason. Try `-5`, `12.345`, * `1,234` and pasting `€ 1,234.50`. */export default function NumberFieldValidation() { const [value, setValue] = useState(''); const [state, setState] = useState<{ status: NumberInputStatus; rejection?: NumberInputRejection; }>({status: 'empty'}); const message = state.status === 'invalid' && state.rejection ? MESSAGES[state.rejection] : null; return ( <div className="flex max-w-sm flex-col gap-3"> <Field data-invalid={message ? true : undefined}> <FieldLabel htmlFor="validation-amount">Contribution (EUR)</FieldLabel> <NumberField id="validation-amount" locale="en" currency="EUR" decimals={2} allowNegative={false} value={value} onValueChange={setValue} onStateChange={(status, rejection) => setState({status, rejection})} aria-describedby={ message ? 'validation-amount-description validation-amount-error' : 'validation-amount-description' } /> <FieldDescription id="validation-amount-description"> In euros, up to two decimals. </FieldDescription> {message ? ( <FieldError id="validation-amount-error">{message}</FieldError> ) : null} </Field> <p className="text-muted-foreground text-sm"> Value <span className="font-mono">{`'${value}'`}</span>, status{' '} <span className="font-mono" data-number-status> {state.status} </span> {state.rejection ? ( <> , reason{' '} <span className="font-mono" data-number-rejection> {state.rejection} </span> </> ) : null} </p> </div> );}