ui
Manual Address Inputs
Manual address form primitives.
Installation
pnpm dlx shadcn@latest add https://registry.circle.health/r/manual-address-inputs.jsonRegistry dependencies
Component
"use client";
import * as React from "react";
import { Dropdown, TextBox } from "@/registry/berlin/ui/form";
export type SupportedLocale = "en" | "de";
export interface Address {
address?: string;
address2?: string;
city?: string;
postal_code?: string;
country?: string;
country_code?: string;
province?: string;
}
const labels = {
en: {
search: "Search address",
address: "Street address",
address2: "Address line 2",
city: "City",
postal_code: "Postal code",
country: "Country",
province: "State / Province"
},
de: {
search: "Adresse suchen",
address: "Straße und Hausnummer",
address2: "Adresszusatz",
city: "Stadt",
postal_code: "Postleitzahl",
country: "Land",
province: "Bundesland"
}
} satisfies Record<SupportedLocale, Record<string, string>>;
function updateAddress(
value: Address | undefined,
onChange: ((address: Address) => void) | undefined,
key: keyof Address,
nextValue: string
) {
onChange?.({
...(value ?? {}),
[key]: nextValue
});
}
export interface AutoAddressInputsProps {
locale?: SupportedLocale;
accessToken?: string;
onSelectAddress?: (address: Address) => void;
}
export function AutoAddressInputs({
locale = "en",
onSelectAddress
}: AutoAddressInputsProps) {
return (
<TextBox
name="address-search"
label={labels[locale].search}
placeholder={labels[locale].address}
onChange={(event) =>
onSelectAddress?.({
address: event.target.value
})
}
/>
);
}
export interface ManualAddressInputsProps {
value?: Address;
locale?: SupportedLocale;
onChange?: (address: Address) => void;
}
export function ManualAddressInputs({
value,
locale = "en",
onChange
}: ManualAddressInputsProps) {
return (
<div className="grid gap-4">
<TextBox
name="address"
label={labels[locale].address}
value={value?.address ?? ""}
onChange={(event) => updateAddress(value, onChange, "address", event.target.value)}
/>
<TextBox
name="address2"
label={labels[locale].address2}
value={value?.address2 ?? ""}
onChange={(event) => updateAddress(value, onChange, "address2", event.target.value)}
/>
<div className="grid gap-4 sm:grid-cols-2">
<TextBox
name="city"
label={labels[locale].city}
value={value?.city ?? ""}
onChange={(event) => updateAddress(value, onChange, "city", event.target.value)}
/>
<TextBox
name="postal_code"
label={labels[locale].postal_code}
value={value?.postal_code ?? ""}
onChange={(event) => updateAddress(value, onChange, "postal_code", event.target.value)}
/>
</div>
<div className="grid gap-4 sm:grid-cols-2">
<TextBox
name="province"
label={labels[locale].province}
value={value?.province ?? ""}
onChange={(event) => updateAddress(value, onChange, "province", event.target.value)}
/>
<Dropdown
name="country"
label={labels[locale].country}
value={value?.country_code ?? ""}
onChange={(event) => {
const next = event.target.value;
onChange?.({
...(value ?? {}),
country_code: next,
country: next === "DE" ? "Germany" : next === "US" ? "United States" : next
});
}}
options={{
DE: locale === "de" ? "Deutschland" : "Germany",
US: locale === "de" ? "Vereinigte Staaten" : "United States",
GB: locale === "de" ? "Vereinigtes Königreich" : "United Kingdom"
}}
/>
</div>
</div>
);
}
export interface AddressInputProps extends ManualAddressInputsProps {
accessToken?: string;
}
export function AddressInput({
value,
onChange,
locale = "en"
}: AddressInputProps) {
return (
<div className="grid gap-4">
<AutoAddressInputs locale={locale} onSelectAddress={(address) => onChange?.({ ...(value ?? {}), ...address })} />
<ManualAddressInputs locale={locale} value={value} onChange={onChange} />
</div>
);
}
Raw registry JSON/r/manual-address-inputs.json