refactor(web): adopt useLang + segmentClass/rowStateClass across sites (#66)

This commit is contained in:
2026-06-08 23:45:24 +02:00
parent 00a7ce772e
commit 900f85f8ac
9 changed files with 33 additions and 29 deletions
+5 -7
View File
@@ -6,17 +6,17 @@ import { FilteredRecordList } from "../components/filtered-record-list";
import { LabelledRecordCreateForm } from "../components/labelled-record-create-form";
import { PageTitle } from "@/components/ui/page-title";
import { AuthorityRow } from "./authority-row";
import { focusRing } from "../lib/focus-ring";
import { useLang } from "../lib/use-lang";
import { segmentClass } from "../lib/class-recipes";
import { useDocumentTitle } from "../lib/use-document-title";
import { useBreadcrumb } from "../shell/use-breadcrumb";
import { cn } from "@/lib/utils";
const KINDS = ["person", "organisation", "place"] as const;
export function AuthoritiesPage() {
const { t, i18n } = useTranslation();
const { t } = useTranslation();
const { kind } = useParams();
const lang = i18n.language.startsWith("sv") ? "sv" : "en";
const lang = useLang();
const isValidKind = (KINDS as readonly string[]).includes(kind ?? "");
const currentKind = isValidKind ? (kind as string) : "person";
@@ -37,9 +37,7 @@ export function AuthoritiesPage() {
<NavLink
key={k}
to={`/authorities/${k}`}
className={({ isActive }) =>
cn("rounded-md px-3 py-1 text-sm", focusRing, isActive ? "bg-primary text-primary-foreground" : "border")
}
className={({ isActive }) => segmentClass(isActive, "px-3 py-1 text-sm")}
>
{t(`authorities.${k}`)}
</NavLink>
+7 -5
View File
@@ -3,6 +3,8 @@ import { useTranslation } from "react-i18next";
import type { components } from "../api/schema";
import { useFieldDefinitions, useDeleteFieldDefinition } from "../api/queries";
import { useLang } from "../lib/use-lang";
import { rowStateClass } from "../lib/class-recipes";
import { labelText } from "../lib/labels";
import { byLabel, compareStrings } from "../lib/sort";
import { focusRing } from "../lib/focus-ring";
@@ -21,10 +23,10 @@ export function FieldList({
selectedKey: string | null;
onSelect: (def: FieldDefinitionView) => void;
}) {
const { t, i18n } = useTranslation();
const { t } = useTranslation();
const { data, isLoading, isError } = useFieldDefinitions();
const deleteField = useDeleteFieldDefinition();
const lang = i18n.language.startsWith("sv") ? "sv" : "en";
const lang = useLang();
const [filter, setFilter] = useState("");
if (isLoading) return <ListSkeleton rows={6} />;
@@ -82,9 +84,9 @@ export function FieldList({
{[...defs].sort(byLabel(lang)).map((def) => (
<li
key={def.key}
className={`flex items-center gap-2 border-b px-3 py-2 text-sm ${
def.key === selectedKey ? "bg-primary/10" : ""
}`}
className={`flex items-center gap-2 border-b px-3 py-2 text-sm ${rowStateClass(
def.key === selectedKey,
)}`}
>
<button
type="button"
+3 -2
View File
@@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next";
import type { components } from "../api/schema";
import { useAuthorities, useTerms } from "../api/queries";
import { useConfig } from "../config/config-context";
import { useLang } from "../lib/use-lang";
import { labelText } from "../lib/labels";
import { OptionsCombobox } from "./options-combobox";
import { Checkbox } from "@/components/ui/checkbox";
@@ -27,9 +28,9 @@ export function FieldInput<TValues extends { fields: Record<string, unknown> }>(
definition: FieldDefinitionView;
form: FieldForm<TValues>;
}) {
const { t, i18n } = useTranslation();
const { t } = useTranslation();
const { default_language } = useConfig();
const lang = i18n.language.startsWith("sv") ? "sv" : "en";
const lang = useLang();
const label = labelText(definition.labels, lang);
const name = fieldPath<TValues>(definition.key);
const placeholder = t("form.selectPlaceholder");
+3 -2
View File
@@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next";
import type { components } from "../api/schema";
import { useObject, useFieldDefinitions } from "../api/queries";
import { useLang } from "../lib/use-lang";
import { groupDefinitions } from "../lib/group-fields";
import { formatDate } from "../lib/format-date";
import { useDocumentTitle } from "../lib/use-document-title";
@@ -49,14 +50,14 @@ export function ObjectDetail() {
}
function ObjectDetailLoaded({ object }: { object: AdminObjectView }) {
const { t, i18n } = useTranslation();
const { t } = useTranslation();
const { data: definitions } = useFieldDefinitions();
useDocumentTitle(object.object_number);
useBreadcrumb([{ label: t("nav.objects"), to: "/objects" }, { label: object.object_number }]);
// Prefer the active locale's label, then English, then the raw key.
const lang = i18n.language.startsWith("sv") ? "sv" : "en";
const lang = useLang();
const labelFor = (key: string) => {
const labels = definitions?.find((d) => d.key === key)?.labels;
const byLang = labels?.find((l) => l.lang === lang)?.label;
+3 -4
View File
@@ -7,6 +7,7 @@ import type { components } from "../api/schema";
import { useObjectsPage } from "../api/queries";
import { useDebouncedValue } from "../lib/use-debounced-value";
import { focusRing } from "../lib/focus-ring";
import { segmentClass, rowStateClass } from "../lib/class-recipes";
import { useConfig } from "../config/config-context";
import { VisibilityBadge } from "./visibility-badge";
import { Button, buttonVariants } from "@/components/ui/button";
@@ -171,7 +172,7 @@ export function ObjectsTable() {
type="button"
aria-pressed={active}
onClick={() => setVisibility(value)}
className={`${focusRing} rounded-md px-2 py-1 ${active ? "bg-primary text-primary-foreground" : "border"}`}
className={segmentClass(active, "px-2 py-1")}
>
{value === "all" ? t("search.all") : t(`visibility.${value}`)}
</button>
@@ -248,9 +249,7 @@ export function ObjectsTable() {
<tr
key={object.id}
onClick={() => navigate(`/objects/${object.id}?${params}`)}
className={`cursor-pointer border-b text-sm ${
selected ? "bg-primary/10" : "hover:bg-muted"
}`}
className={`cursor-pointer border-b text-sm ${rowStateClass(selected)}`}
>
<td className="px-3 py-2 text-muted-foreground">
<Link
+2 -3
View File
@@ -4,9 +4,8 @@ import { useTranslation } from "react-i18next";
import { useSearch, HttpError } from "../api/queries";
import { useDebouncedValue } from "../lib/use-debounced-value";
import { focusRing } from "../lib/focus-ring";
import { segmentClass } from "../lib/class-recipes";
import { SearchResultRow } from "./search-result-row";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { ListSkeleton } from "@/components/ui/skeletons";
@@ -73,7 +72,7 @@ export function SearchPanel() {
type="button"
aria-pressed={active}
onClick={() => setVisibility(value)}
className={cn("rounded-md px-2 py-0.5", focusRing, active ? "bg-primary text-primary-foreground" : "border")}
className={segmentClass(active, "px-2 py-0.5")}
>
{value === "all" ? t("search.all") : t(`visibility.${value}`)}
</button>
+2 -1
View File
@@ -2,6 +2,7 @@ import { NavLink } from "react-router-dom";
import type { components } from "../api/schema";
import { VisibilityBadge } from "../objects/visibility-badge";
import { rowStateClass } from "../lib/class-recipes";
import { Highlight } from "./highlight";
type SearchHitView = components["schemas"]["SearchHitView"];
@@ -12,7 +13,7 @@ export function SearchResultRow({ hit }: { hit: SearchHitView }) {
<NavLink
to={`/search/${hit.id}`}
className={({ isActive }) =>
`block border-b px-3 py-2 ${isActive ? "bg-primary/10" : "hover:bg-muted"}`
`block border-b px-3 py-2 ${rowStateClass(isActive)}`
}
>
<div className="text-sm font-semibold">{hit.object_name}</div>
+5 -3
View File
@@ -3,6 +3,8 @@ import { NavLink } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { useVocabularies, useCreateVocabulary, useRenameVocabulary, useDeleteVocabulary } from "../api/queries";
import { useLang } from "../lib/use-lang";
import { rowStateClass } from "../lib/class-recipes";
import { byKey } from "../lib/sort";
import { DeleteConfirmDialog } from "../components/delete-confirm-dialog";
import { MutationError } from "../components/mutation-error";
@@ -12,9 +14,9 @@ import { Label } from "@/components/ui/label";
import { ListSkeleton } from "@/components/ui/skeletons";
export function VocabularyList() {
const { t, i18n } = useTranslation();
const { t } = useTranslation();
const lang = i18n.language.startsWith("sv") ? "sv" : "en";
const lang = useLang();
const { data, isLoading, isError } = useVocabularies();
@@ -110,7 +112,7 @@ export function VocabularyList() {
<NavLink
to={`/vocabularies/${v.id}`}
className={({ isActive }) =>
`block flex-1 px-3 py-2 text-sm ${isActive ? "bg-primary/10" : "hover:bg-muted"}`
`block flex-1 px-3 py-2 text-sm ${rowStateClass(isActive)}`
}
>
{v.key}
+3 -2
View File
@@ -2,15 +2,16 @@ import { useParams } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { useTerms, useAddTerm, useVocabularies } from "../api/queries";
import { useLang } from "../lib/use-lang";
import { useBreadcrumb } from "../shell/use-breadcrumb";
import { FilteredRecordList } from "../components/filtered-record-list";
import { LabelledRecordCreateForm } from "../components/labelled-record-create-form";
import { TermRow } from "./term-row";
export function VocabularyTerms() {
const { t, i18n } = useTranslation();
const { t } = useTranslation();
const { id } = useParams();
const lang = i18n.language.startsWith("sv") ? "sv" : "en";
const lang = useLang();
const { data: terms, isLoading, isError } = useTerms(id);
const addTerm = useAddTerm();