Files
twenty/packages/twenty-website/src/ui/NumberField.tsx
T
Abdullah. 569d887d1e [Website] Cut over to the rebuilt site (#21825)
Renaming the package so any further PRs directed to the website are
targeted to the reworked code instead of diverging. Once merged, I will
start preparing this for deployment to dev to test before releasing to
prod. Any improvements will also be applied to this package.

I avoided making significant changes to API routes so nothing breaks,
but will test it thoroughly today to confirm. That said, everything is
ported - double checked.

Big diff PR, impossible to review, but last one! No more rebuilds.
2026-06-19 10:22:46 +02:00

97 lines
1.9 KiB
TypeScript

'use client';
import { styled } from '@linaria/react';
import {
color,
fontFamily,
fontSize,
radius,
semanticColor,
spacing,
} from '@/tokens';
const Wrapper = styled.div`
align-items: center;
border: 1px solid ${semanticColor.lineStrong};
border-radius: ${radius(2)};
box-sizing: border-box;
display: flex;
height: clamp(40px, 5.5vh, 56px);
padding: 0 ${spacing(3)};
width: 100%;
&:focus-within {
border-color: ${color('blue')};
}
&[data-invalid] {
border-color: ${color('error')};
}
`;
const Prefix = styled.span`
color: ${semanticColor.inkMuted};
font-family: ${fontFamily('sans')};
font-size: ${fontSize(4)};
margin-right: ${spacing(2)};
`;
const Control = styled.input`
background: none;
border: none;
color: ${semanticColor.ink};
flex: 1 1 auto;
font-family: ${fontFamily('sans')};
font-size: ${fontSize(4)};
height: 100%;
min-width: 0;
&::placeholder {
color: ${semanticColor.inkSubtle};
}
&:focus {
outline: none;
}
`;
// A numeric amount field with an optional prefix adornment (a currency symbol).
// It strips anything but digits and a decimal point on input, keeping the value
// clean for a consumer's parseFloat.
export function NumberField({
ariaLabel,
invalid = false,
name,
onValueChange,
placeholder,
prefix,
value,
}: {
ariaLabel: string;
invalid?: boolean;
name: string;
onValueChange: (value: string) => void;
placeholder?: string;
prefix?: string;
value: string;
}) {
return (
<Wrapper data-invalid={invalid ? '' : undefined}>
{prefix !== undefined ? <Prefix aria-hidden>{prefix}</Prefix> : null}
<Control
aria-label={ariaLabel}
autoComplete="off"
inputMode="decimal"
name={name}
onChange={(event) =>
onValueChange(event.target.value.replace(/[^\d.]/g, ''))
}
placeholder={placeholder}
type="text"
value={value}
/>
</Wrapper>
);
}