Files
neta/components/i18n/localized-fields.tsx
T

118 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useMemo, useState } from "react";
import { Badge, Button, Input, Label, Textarea } from "poyraz-ui/atoms";
import { contentInputName, type ContentTranslationField } from "@/lib/i18n/content";
export type LocalizedFieldLocale = {
code: string;
nativeName: string;
status?: string;
};
export type LocalizedFieldValues = Record<string, Record<string, string | null | undefined>>;
type LocalizedFieldsProps = {
idPrefix: string;
defaultLocale: string;
locales: LocalizedFieldLocale[];
fields: ContentTranslationField[];
values?: LocalizedFieldValues | null;
fallbackValues?: Record<string, string | null | undefined>;
};
export function LocalizedFields({
idPrefix,
defaultLocale,
locales,
fields,
values,
fallbackValues,
}: LocalizedFieldsProps) {
const orderedLocales = useMemo(() => {
const localeMap = new Map(locales.map((locale) => [locale.code, locale]));
const defaultLocaleRecord = localeMap.get(defaultLocale);
return [
...(defaultLocaleRecord ? [defaultLocaleRecord] : []),
...locales.filter((locale) => locale.code !== defaultLocale),
];
}, [defaultLocale, locales]);
const [activeLocale, setActiveLocale] = useState(orderedLocales[0]?.code ?? defaultLocale);
const missingRequiredLocales = new Set(
orderedLocales
.filter((locale) =>
fields.some((field) => {
if (!field.required) return false;
const value = values?.[locale.code]?.[field.name] ?? (
locale.code === defaultLocale ? fallbackValues?.[field.name] : null
);
return !String(value ?? "").trim();
}),
)
.map((locale) => locale.code),
);
return (
<div className="grid gap-4 rounded-sm border border-border bg-muted/10 p-3">
<div className="tiny-scrollbar flex gap-2 overflow-x-auto pb-1">
{orderedLocales.map((locale) => (
<Button
key={locale.code}
type="button"
variant={activeLocale === locale.code ? "default" : "secondary"}
effect="shine"
size="sm"
className="shrink-0 gap-2"
onClick={() => setActiveLocale(locale.code)}
>
{locale.nativeName || locale.code}
{locale.code === defaultLocale ? <Badge variant="secondary">varsayılan</Badge> : null}
{missingRequiredLocales.has(locale.code) ? <span aria-hidden="true" className="text-amber-500"></span> : null}
</Button>
))}
</div>
{orderedLocales.map((locale) => (
<div key={locale.code} className={activeLocale === locale.code ? "grid gap-4" : "hidden"}>
{fields.map((field) => {
const id = `${idPrefix}-${locale.code}-${field.name}`;
const defaultValue =
values?.[locale.code]?.[field.name] ??
(locale.code === defaultLocale ? fallbackValues?.[field.name] : "") ??
"";
return (
<div key={field.name} className="grid gap-2">
<Label htmlFor={id}>
{field.label}
{field.required && locale.code === defaultLocale ? <span className="text-destructive"> *</span> : null}
</Label>
{field.kind === "textarea" ? (
<Textarea
id={id}
name={contentInputName(locale.code, field.name)}
defaultValue={String(defaultValue ?? "")}
maxLength={field.maxLength}
required={field.required && locale.code === defaultLocale}
placeholder={field.placeholder}
rows={field.name === "content" ? 8 : 3}
/>
) : (
<Input
id={id}
name={contentInputName(locale.code, field.name)}
defaultValue={String(defaultValue ?? "")}
maxLength={field.maxLength}
required={field.required && locale.code === defaultLocale}
placeholder={field.placeholder}
/>
)}
</div>
);
})}
</div>
))}
</div>
);
}