refactor(web): migrate feature screens to design tokens + radius token (#49)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -26,7 +26,7 @@ const FieldsPage = lazy(() =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
function FormFallback() {
|
function FormFallback() {
|
||||||
return <div role="status" className="p-4 text-sm text-neutral-400">Loading…</div>;
|
return <div role="status" className="p-4 text-sm text-muted-foreground">Loading…</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export function LoginPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{errorKey && (
|
{errorKey && (
|
||||||
<p role="alert" className="text-sm text-red-600">
|
<p role="alert" className="text-sm text-destructive">
|
||||||
{t(errorKey)}
|
{t(errorKey)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export function AuthoritiesPage() {
|
|||||||
role="tab"
|
role="tab"
|
||||||
aria-selected={k === currentKind}
|
aria-selected={k === currentKind}
|
||||||
className={({ isActive }) =>
|
className={({ isActive }) =>
|
||||||
`rounded px-3 py-1 text-sm ${isActive ? "bg-neutral-800 text-white" : "border"}`
|
`rounded-md px-3 py-1 text-sm ${isActive ? "bg-primary text-primary-foreground" : "border"}`
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{t(`authorities.${k}`)}
|
{t(`authorities.${k}`)}
|
||||||
@@ -63,13 +63,13 @@ export function AuthoritiesPage() {
|
|||||||
|
|
||||||
<ul className="mb-4">
|
<ul className="mb-4">
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
<li className="text-sm text-neutral-400">…</li>
|
<li className="text-sm text-muted-foreground">…</li>
|
||||||
)}
|
)}
|
||||||
{isError && (
|
{isError && (
|
||||||
<li className="text-sm text-red-600">{t("authorities.loadError")}</li>
|
<li className="text-sm text-destructive">{t("authorities.loadError")}</li>
|
||||||
)}
|
)}
|
||||||
{!isLoading && !isError && authorities?.length === 0 && (
|
{!isLoading && !isError && authorities?.length === 0 && (
|
||||||
<li className="text-sm text-neutral-500">{t("authorities.empty")}</li>
|
<li className="text-sm text-muted-foreground">{t("authorities.empty")}</li>
|
||||||
)}
|
)}
|
||||||
{authorities?.map((a) => (
|
{authorities?.map((a) => (
|
||||||
<AuthorityRow key={a.id} authority={a} kind={currentKind} lang={lang} />
|
<AuthorityRow key={a.id} authority={a} kind={currentKind} lang={lang} />
|
||||||
@@ -84,13 +84,13 @@ export function AuthoritiesPage() {
|
|||||||
<LabelEditor value={labels} onChange={setLabels} />
|
<LabelEditor value={labels} onChange={setLabels} />
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<p role="alert" className="text-xs text-red-600">
|
<p role="alert" className="text-xs text-destructive">
|
||||||
{t("form.required")}
|
{t("form.required")}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{create.isError && (
|
{create.isError && (
|
||||||
<p role="alert" className="text-xs text-red-600">
|
<p role="alert" className="text-xs text-destructive">
|
||||||
{t("form.rejected")}
|
{t("form.rejected")}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export function DeleteConfirmDialog({
|
|||||||
<AlertDialog open={open} onOpenChange={setOpen}>
|
<AlertDialog open={open} onOpenChange={setOpen}>
|
||||||
<AlertDialogTrigger
|
<AlertDialogTrigger
|
||||||
render={
|
render={
|
||||||
<Button variant="ghost" size="sm" className="text-red-600">
|
<Button variant="ghost" size="sm" className="text-destructive">
|
||||||
{triggerLabel ?? t("actions.delete")}
|
{triggerLabel ?? t("actions.delete")}
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
@@ -56,7 +56,7 @@ export function DeleteConfirmDialog({
|
|||||||
<AlertDialogTitle>{t("actions.delete")}</AlertDialogTitle>
|
<AlertDialogTitle>{t("actions.delete")}</AlertDialogTitle>
|
||||||
<AlertDialogDescription>{description}</AlertDialogDescription>
|
<AlertDialogDescription>{description}</AlertDialogDescription>
|
||||||
{message && (
|
{message && (
|
||||||
<p role="alert" className="text-sm text-red-600">
|
<p role="alert" className="text-sm text-destructive">
|
||||||
{message}
|
{message}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ export function FieldForm({
|
|||||||
value={dataType}
|
value={dataType}
|
||||||
disabled={isEdit}
|
disabled={isEdit}
|
||||||
onChange={(e) => setDataType(e.target.value)}
|
onChange={(e) => setDataType(e.target.value)}
|
||||||
className="w-full rounded border px-2 py-1 text-sm disabled:opacity-60"
|
className="w-full rounded-md border px-2 py-1 text-sm disabled:opacity-60"
|
||||||
>
|
>
|
||||||
{TYPES.map((type) => (
|
{TYPES.map((type) => (
|
||||||
<option key={type} value={type}>
|
<option key={type} value={type}>
|
||||||
@@ -140,7 +140,7 @@ export function FieldForm({
|
|||||||
value={vocabularyId}
|
value={vocabularyId}
|
||||||
disabled={isEdit}
|
disabled={isEdit}
|
||||||
onChange={(e) => setVocabularyId(e.target.value)}
|
onChange={(e) => setVocabularyId(e.target.value)}
|
||||||
className="w-full rounded border px-2 py-1 text-sm disabled:opacity-60"
|
className="w-full rounded-md border px-2 py-1 text-sm disabled:opacity-60"
|
||||||
>
|
>
|
||||||
<option value="">{t("form.selectPlaceholder")}</option>
|
<option value="">{t("form.selectPlaceholder")}</option>
|
||||||
{vocabularies?.map((vocab) => (
|
{vocabularies?.map((vocab) => (
|
||||||
@@ -160,7 +160,7 @@ export function FieldForm({
|
|||||||
value={authorityKind}
|
value={authorityKind}
|
||||||
disabled={isEdit}
|
disabled={isEdit}
|
||||||
onChange={(e) => setAuthorityKind(e.target.value)}
|
onChange={(e) => setAuthorityKind(e.target.value)}
|
||||||
className="w-full rounded border px-2 py-1 text-sm disabled:opacity-60"
|
className="w-full rounded-md border px-2 py-1 text-sm disabled:opacity-60"
|
||||||
>
|
>
|
||||||
<option value="">{t("fields.anyKind")}</option>
|
<option value="">{t("fields.anyKind")}</option>
|
||||||
{KINDS.map((kind) => (
|
{KINDS.map((kind) => (
|
||||||
@@ -183,12 +183,12 @@ export function FieldForm({
|
|||||||
</label>
|
</label>
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<p role="alert" className="text-xs text-red-600">
|
<p role="alert" className="text-xs text-destructive">
|
||||||
{t("form.required")}
|
{t("form.required")}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{failed && (
|
{failed && (
|
||||||
<p role="alert" className="text-xs text-red-600">
|
<p role="alert" className="text-xs text-destructive">
|
||||||
{t("form.rejected")}
|
{t("form.rejected")}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -30,9 +30,9 @@ export function FieldList({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isError) return <p className="p-4 text-sm text-red-600">{t("fields.loadError")}</p>;
|
if (isError) return <p className="p-4 text-sm text-destructive">{t("fields.loadError")}</p>;
|
||||||
if (!data || data.length === 0)
|
if (!data || data.length === 0)
|
||||||
return <p className="p-4 text-sm text-neutral-500">{t("fields.empty")}</p>;
|
return <p className="p-4 text-sm text-muted-foreground">{t("fields.empty")}</p>;
|
||||||
|
|
||||||
const groups = new Map<string, FieldDefinitionView[]>();
|
const groups = new Map<string, FieldDefinitionView[]>();
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ export function FieldList({
|
|||||||
<ul className="overflow-auto">
|
<ul className="overflow-auto">
|
||||||
{entries.map(([group, defs]) => (
|
{entries.map(([group, defs]) => (
|
||||||
<li key={group}>
|
<li key={group}>
|
||||||
<div className="border-b bg-neutral-50 px-3 py-1 text-xs font-medium uppercase tracking-wide text-neutral-500">
|
<div className="border-b bg-muted px-3 py-1 label-caption">
|
||||||
{group}
|
{group}
|
||||||
</div>
|
</div>
|
||||||
<ul>
|
<ul>
|
||||||
@@ -61,7 +61,7 @@ export function FieldList({
|
|||||||
<li
|
<li
|
||||||
key={def.key}
|
key={def.key}
|
||||||
className={`flex items-center gap-2 border-b px-3 py-2 text-sm ${
|
className={`flex items-center gap-2 border-b px-3 py-2 text-sm ${
|
||||||
def.key === selectedKey ? "bg-indigo-50" : ""
|
def.key === selectedKey ? "bg-primary/10" : ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
@@ -71,13 +71,13 @@ export function FieldList({
|
|||||||
onClick={() => onSelect(def)}
|
onClick={() => onSelect(def)}
|
||||||
>
|
>
|
||||||
<span className="font-medium">{labelText(def.labels, lang)}</span>
|
<span className="font-medium">{labelText(def.labels, lang)}</span>
|
||||||
<span className="text-xs text-neutral-400">{def.key}</span>
|
<span className="text-xs text-muted-foreground">{def.key}</span>
|
||||||
<span className="rounded bg-neutral-100 px-1.5 py-0.5 text-xs text-neutral-600">
|
<span className="rounded-md bg-muted px-1.5 py-0.5 text-xs text-muted-foreground">
|
||||||
{t(`fields.types.${def.data_type}`)}
|
{t(`fields.types.${def.data_type}`)}
|
||||||
</span>
|
</span>
|
||||||
{def.required && (
|
{def.required && (
|
||||||
<span
|
<span
|
||||||
className="text-xs text-red-600"
|
className="text-xs text-destructive"
|
||||||
title={t("fields.required")}
|
title={t("fields.required")}
|
||||||
aria-label={t("fields.required")}
|
aria-label={t("fields.required")}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export function DeleteObjectDialog({ id }: { id: string }) {
|
|||||||
<AlertDialog open={open} onOpenChange={setOpen}>
|
<AlertDialog open={open} onOpenChange={setOpen}>
|
||||||
<AlertDialogTrigger
|
<AlertDialogTrigger
|
||||||
render={
|
render={
|
||||||
<Button variant="ghost" size="sm" className="text-red-600">
|
<Button variant="ghost" size="sm" className="text-destructive">
|
||||||
{t("actions.delete")}
|
{t("actions.delete")}
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
@@ -49,7 +49,7 @@ export function DeleteObjectDialog({ id }: { id: string }) {
|
|||||||
<AlertDialogTitle>{t("actions.delete")}</AlertDialogTitle>
|
<AlertDialogTitle>{t("actions.delete")}</AlertDialogTitle>
|
||||||
<AlertDialogDescription>{t("actions.confirmDelete")}</AlertDialogDescription>
|
<AlertDialogDescription>{t("actions.confirmDelete")}</AlertDialogDescription>
|
||||||
{error && (
|
{error && (
|
||||||
<p role="alert" className="text-sm text-red-600">
|
<p role="alert" className="text-sm text-destructive">
|
||||||
{t("form.rejected")}
|
{t("form.rejected")}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -49,9 +49,9 @@ function TermValue({
|
|||||||
if (typeof value !== "string") return <>—</>;
|
if (typeof value !== "string") return <>—</>;
|
||||||
const term = terms?.find((x) => x.id === value);
|
const term = terms?.find((x) => x.id === value);
|
||||||
if (term) return <>{labelText(term.labels, lang)}</>;
|
if (term) return <>{labelText(term.labels, lang)}</>;
|
||||||
if (isLoading) return <span className="text-neutral-400">…</span>;
|
if (isLoading) return <span className="text-muted-foreground">…</span>;
|
||||||
return (
|
return (
|
||||||
<span className="text-neutral-400">
|
<span className="text-muted-foreground">
|
||||||
{value} {t("objects.unknownRef")}
|
{value} {t("objects.unknownRef")}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
@@ -72,9 +72,9 @@ function AuthorityValue({
|
|||||||
if (typeof value !== "string") return <>—</>;
|
if (typeof value !== "string") return <>—</>;
|
||||||
const authority = authorities?.find((x) => x.id === value);
|
const authority = authorities?.find((x) => x.id === value);
|
||||||
if (authority) return <>{labelText(authority.labels, lang)}</>;
|
if (authority) return <>{labelText(authority.labels, lang)}</>;
|
||||||
if (isLoading) return <span className="text-neutral-400">…</span>;
|
if (isLoading) return <span className="text-muted-foreground">…</span>;
|
||||||
return (
|
return (
|
||||||
<span className="text-neutral-400">
|
<span className="text-muted-foreground">
|
||||||
{value} {t("objects.unknownRef")}
|
{value} {t("objects.unknownRef")}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export function ObjectDetailDrawer({
|
|||||||
<div className="flex justify-end border-b p-2">
|
<div className="flex justify-end border-b p-2">
|
||||||
<DrawerClose
|
<DrawerClose
|
||||||
aria-label={t("actions.closeDetail")}
|
aria-label={t("actions.closeDetail")}
|
||||||
className="rounded p-1 text-neutral-500 hover:bg-neutral-100 hover:text-neutral-900"
|
className="rounded-md p-1 text-muted-foreground hover:bg-muted hover:text-foreground"
|
||||||
>
|
>
|
||||||
<X className="size-4" aria-hidden="true" />
|
<X className="size-4" aria-hidden="true" />
|
||||||
</DrawerClose>
|
</DrawerClose>
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ function Field({ label, value }: { label: string; value: ReactNode }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="border-b py-2">
|
<div className="border-b py-2">
|
||||||
<div className="text-xs uppercase tracking-wide text-neutral-400">{label}</div>
|
<div className="label-caption">{label}</div>
|
||||||
<div className="text-sm text-neutral-900">{empty ? "—" : value}</div>
|
<div className="text-sm text-foreground">{empty ? "—" : value}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -39,9 +39,9 @@ export function ObjectDetail() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isError) return <p className="p-4 text-sm text-red-600">{t("objects.loadError")}</p>;
|
if (isError) return <p className="p-4 text-sm text-destructive">{t("objects.loadError")}</p>;
|
||||||
|
|
||||||
if (!object) return <p className="p-4 text-sm text-neutral-500">{t("objects.notFound")}</p>;
|
if (!object) return <p className="p-4 text-sm text-muted-foreground">{t("objects.notFound")}</p>;
|
||||||
|
|
||||||
// Prefer the active locale's label, then English, then the raw key.
|
// Prefer the active locale's label, then English, then the raw key.
|
||||||
const lang = i18n.language.startsWith("sv") ? "sv" : "en";
|
const lang = i18n.language.startsWith("sv") ? "sv" : "en";
|
||||||
@@ -105,7 +105,7 @@ export function ObjectDetail() {
|
|||||||
/>
|
/>
|
||||||
{groups.map((g) => (
|
{groups.map((g) => (
|
||||||
<div key={g.group} className="mt-4">
|
<div key={g.group} className="mt-4">
|
||||||
<div className="mb-1 text-xs font-medium uppercase text-neutral-500">{g.group}</div>
|
<div className="mb-1 label-caption">{g.group}</div>
|
||||||
{g.defs.map((d) => (
|
{g.defs.map((d) => (
|
||||||
<Field
|
<Field
|
||||||
key={d.key}
|
key={d.key}
|
||||||
@@ -119,7 +119,7 @@ export function ObjectDetail() {
|
|||||||
key={key}
|
key={key}
|
||||||
label={key}
|
label={key}
|
||||||
value={
|
value={
|
||||||
<span className="text-neutral-400">
|
<span className="text-muted-foreground">
|
||||||
{typeof value === "object" ? JSON.stringify(value) : String(value)}
|
{typeof value === "object" ? JSON.stringify(value) : String(value)}
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export function ObjectEditForm() {
|
|||||||
|
|
||||||
if (isLoading) return <div className="p-4" role="status" aria-label="loading" />;
|
if (isLoading) return <div className="p-4" role="status" aria-label="loading" />;
|
||||||
|
|
||||||
if (!object) return <p className="p-4 text-sm text-neutral-500">{t("objects.notFound")}</p>;
|
if (!object) return <p className="p-4 text-sm text-muted-foreground">{t("objects.notFound")}</p>;
|
||||||
|
|
||||||
const core: ObjectCore = {
|
const core: ObjectCore = {
|
||||||
object_number: object.object_number,
|
object_number: object.object_number,
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ export function ObjectForm({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{errors.core?.[key] && (
|
{errors.core?.[key] && (
|
||||||
<p role="alert" className="text-xs text-red-600">
|
<p role="alert" className="text-xs text-destructive">
|
||||||
{t("form.required")}
|
{t("form.required")}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -127,7 +127,7 @@ export function ObjectForm({
|
|||||||
return (
|
return (
|
||||||
<form onSubmit={submit} className="space-y-4 overflow-auto p-4">
|
<form onSubmit={submit} className="space-y-4 overflow-auto p-4">
|
||||||
{formError && (
|
{formError && (
|
||||||
<p role="alert" className="text-sm text-red-600">
|
<p role="alert" className="text-sm text-destructive">
|
||||||
{formError}
|
{formError}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -147,7 +147,7 @@ export function ObjectForm({
|
|||||||
|
|
||||||
<select
|
<select
|
||||||
id="visibility"
|
id="visibility"
|
||||||
className="w-full rounded border px-2 py-1 text-sm"
|
className="w-full rounded-md border px-2 py-1 text-sm"
|
||||||
{...register("visibility")}
|
{...register("visibility")}
|
||||||
>
|
>
|
||||||
<option value="draft">{t("form.draft")}</option>
|
<option value="draft">{t("form.draft")}</option>
|
||||||
@@ -158,7 +158,7 @@ export function ObjectForm({
|
|||||||
|
|
||||||
{definitions && definitions.length > 0 && (
|
{definitions && definitions.length > 0 && (
|
||||||
<fieldset className="space-y-3 border-t pt-3">
|
<fieldset className="space-y-3 border-t pt-3">
|
||||||
<legend className="text-xs font-medium uppercase text-neutral-500">
|
<legend className="label-caption">
|
||||||
{t("form.flexibleHeading")}
|
{t("form.flexibleHeading")}
|
||||||
</legend>
|
</legend>
|
||||||
|
|
||||||
@@ -167,7 +167,7 @@ export function ObjectForm({
|
|||||||
<FieldInput definition={def} form={form} />
|
<FieldInput definition={def} form={form} />
|
||||||
|
|
||||||
{errors.fields?.[def.key] && (
|
{errors.fields?.[def.key] && (
|
||||||
<p role="alert" className="text-xs text-red-600">
|
<p role="alert" className="text-xs text-destructive">
|
||||||
{errors.fields[def.key]?.message ?? t("form.required")}
|
{errors.fields[def.key]?.message ?? t("form.required")}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export function ObjectsPage() {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={closeDetail}
|
onClick={closeDetail}
|
||||||
aria-label={t("actions.closeDetail")}
|
aria-label={t("actions.closeDetail")}
|
||||||
className="rounded p-1 text-neutral-500 hover:bg-neutral-100 hover:text-neutral-900"
|
className="rounded-md p-1 text-muted-foreground hover:bg-muted hover:text-foreground"
|
||||||
>
|
>
|
||||||
<X className="size-4" aria-hidden="true" />
|
<X className="size-4" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -138,10 +138,10 @@ export function ObjectsTable() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => toggleSort(col)}
|
onClick={() => toggleSort(col)}
|
||||||
className="flex items-center gap-1 hover:text-neutral-900"
|
className="flex items-center gap-1 hover:text-foreground"
|
||||||
>
|
>
|
||||||
{t(COLUMN_KEYS[col])}
|
{t(COLUMN_KEYS[col])}
|
||||||
<Icon className="size-3.5 text-neutral-400" aria-hidden="true" />
|
<Icon className="size-3.5 text-muted-foreground" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
</th>
|
</th>
|
||||||
);
|
);
|
||||||
@@ -170,7 +170,7 @@ export function ObjectsTable() {
|
|||||||
type="button"
|
type="button"
|
||||||
aria-pressed={active}
|
aria-pressed={active}
|
||||||
onClick={() => setVisibility(value)}
|
onClick={() => setVisibility(value)}
|
||||||
className={`rounded px-2 py-1 ${active ? "bg-indigo-600 text-white" : "border"}`}
|
className={`rounded-md px-2 py-1 ${active ? "bg-primary text-primary-foreground" : "border"}`}
|
||||||
>
|
>
|
||||||
{value === "all" ? t("search.all") : t(`visibility.${value}`)}
|
{value === "all" ? t("search.all") : t(`visibility.${value}`)}
|
||||||
</button>
|
</button>
|
||||||
@@ -184,7 +184,7 @@ export function ObjectsTable() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const columns = (
|
const columns = (
|
||||||
<thead className="border-b bg-neutral-50 text-xs text-neutral-500">
|
<thead className="border-b bg-muted text-xs text-muted-foreground">
|
||||||
<tr>
|
<tr>
|
||||||
{headerCell("object_number")}
|
{headerCell("object_number")}
|
||||||
{headerCell("object_name")}
|
{headerCell("object_name")}
|
||||||
@@ -220,7 +220,7 @@ export function ObjectsTable() {
|
|||||||
body = (
|
body = (
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={6} className="px-3 py-6 text-center text-sm text-red-600">
|
<td colSpan={6} className="px-3 py-6 text-center text-sm text-destructive">
|
||||||
{t("objects.loadError")}
|
{t("objects.loadError")}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -230,7 +230,7 @@ export function ObjectsTable() {
|
|||||||
body = (
|
body = (
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={6} className="px-3 py-6 text-center text-sm text-neutral-500">
|
<td colSpan={6} className="px-3 py-6 text-center text-sm text-muted-foreground">
|
||||||
{t("objects.empty")}
|
{t("objects.empty")}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -254,17 +254,17 @@ export function ObjectsTable() {
|
|||||||
if (event.key === "Enter") navigate(`/objects/${object.id}?${params}`);
|
if (event.key === "Enter") navigate(`/objects/${object.id}?${params}`);
|
||||||
}}
|
}}
|
||||||
className={`cursor-pointer border-b text-sm ${
|
className={`cursor-pointer border-b text-sm ${
|
||||||
selected ? "bg-indigo-50" : "hover:bg-neutral-50"
|
selected ? "bg-primary/10" : "hover:bg-muted"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<td className="px-3 py-2 text-neutral-500">{object.object_number}</td>
|
<td className="px-3 py-2 text-muted-foreground">{object.object_number}</td>
|
||||||
<td className="px-3 py-2 font-medium">{object.object_name}</td>
|
<td className="px-3 py-2 font-medium">{object.object_name}</td>
|
||||||
<td className="px-3 py-2">
|
<td className="px-3 py-2">
|
||||||
<VisibilityBadge visibility={object.visibility} />
|
<VisibilityBadge visibility={object.visibility} />
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-2 text-neutral-600">{object.current_location ?? "—"}</td>
|
<td className="px-3 py-2 text-muted-foreground">{object.current_location ?? "—"}</td>
|
||||||
<td className="px-3 py-2 text-right tabular-nums">{object.number_of_objects}</td>
|
<td className="px-3 py-2 text-right tabular-nums">{object.number_of_objects}</td>
|
||||||
<td className="px-3 py-2 text-neutral-600">{formatUpdated(object.updated_at)}</td>
|
<td className="px-3 py-2 text-muted-foreground">{formatUpdated(object.updated_at)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -281,14 +281,14 @@ export function ObjectsTable() {
|
|||||||
{body}
|
{body}
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between gap-2 border-t px-3 py-2 text-xs text-neutral-500">
|
<div className="flex items-center justify-between gap-2 border-t px-3 py-2 text-xs text-muted-foreground">
|
||||||
<label className="flex items-center gap-1">
|
<label className="flex items-center gap-1">
|
||||||
<span>{t("objects.pageSize")}</span>
|
<span>{t("objects.pageSize")}</span>
|
||||||
<select
|
<select
|
||||||
value={limit}
|
value={limit}
|
||||||
onChange={(event) => setLimit(Number(event.target.value))}
|
onChange={(event) => setLimit(Number(event.target.value))}
|
||||||
aria-label={t("objects.pageSize")}
|
aria-label={t("objects.pageSize")}
|
||||||
className="rounded border bg-white px-1 py-0.5"
|
className="rounded-md border bg-white px-1 py-0.5"
|
||||||
>
|
>
|
||||||
{PAGE_SIZES.map((size) => (
|
{PAGE_SIZES.map((size) => (
|
||||||
<option key={size} value={size}>
|
<option key={size} value={size}>
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export function OptionsCombobox({
|
|||||||
<ComboboxList>
|
<ComboboxList>
|
||||||
{(option: Option) => (
|
{(option: Option) => (
|
||||||
<ComboboxItem key={option.id} value={option}>
|
<ComboboxItem key={option.id} value={option}>
|
||||||
<ComboboxItemIndicator className="text-indigo-600">✓</ComboboxItemIndicator>
|
<ComboboxItemIndicator className="text-primary">✓</ComboboxItemIndicator>
|
||||||
{labelText(option.labels, lang)}
|
{labelText(option.labels, lang)}
|
||||||
</ComboboxItem>
|
</ComboboxItem>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export function PublishControl({ object }: { object: AdminObjectView }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="border-t p-4">
|
<section className="border-t p-4">
|
||||||
<div className="mb-2 text-xs font-medium uppercase text-neutral-500">
|
<div className="mb-2 label-caption">
|
||||||
{t("publish.heading")}
|
{t("publish.heading")}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -59,10 +59,10 @@ export function PublishControl({ object }: { object: AdminObjectView }) {
|
|||||||
aria-current={i === currentIndex ? "step" : undefined}
|
aria-current={i === currentIndex ? "step" : undefined}
|
||||||
className={`flex-1 border px-2 py-1 text-center text-xs ${
|
className={`flex-1 border px-2 py-1 text-center text-xs ${
|
||||||
i === currentIndex
|
i === currentIndex
|
||||||
? "bg-neutral-800 font-semibold text-white"
|
? "bg-primary font-semibold text-primary-foreground"
|
||||||
: i < currentIndex
|
: i < currentIndex
|
||||||
? "bg-neutral-100 text-neutral-600"
|
? "bg-muted text-muted-foreground"
|
||||||
: "text-neutral-400"
|
: "text-muted-foreground"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{t(`visibility.${step}`)}
|
{t(`visibility.${step}`)}
|
||||||
@@ -117,7 +117,7 @@ export function PublishControl({ object }: { object: AdminObjectView }) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{errorKind === "gate" && (
|
{errorKind === "gate" && (
|
||||||
<p role="alert" className="mt-2 text-sm text-red-600">
|
<p role="alert" className="mt-2 text-sm text-destructive">
|
||||||
{t("publish.gateError")}{" "}
|
{t("publish.gateError")}{" "}
|
||||||
<Link to={`/objects/${object.id}/edit`} className="underline">
|
<Link to={`/objects/${object.id}/edit`} className="underline">
|
||||||
{t("publish.editLink")}
|
{t("publish.editLink")}
|
||||||
@@ -125,12 +125,12 @@ export function PublishControl({ object }: { object: AdminObjectView }) {
|
|||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{errorKind === "illegal" && (
|
{errorKind === "illegal" && (
|
||||||
<p role="alert" className="mt-2 text-sm text-red-600">
|
<p role="alert" className="mt-2 text-sm text-destructive">
|
||||||
{t("publish.illegalError")}
|
{t("publish.illegalError")}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{errorKind === "other" && (
|
{errorKind === "other" && (
|
||||||
<p role="alert" className="mt-2 text-sm text-red-600">
|
<p role="alert" className="mt-2 text-sm text-destructive">
|
||||||
{t("form.rejected")}
|
{t("form.rejected")}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ export function SearchPanel() {
|
|||||||
type="button"
|
type="button"
|
||||||
aria-pressed={active}
|
aria-pressed={active}
|
||||||
onClick={() => setVisibility(value)}
|
onClick={() => setVisibility(value)}
|
||||||
className={`rounded px-2 py-0.5 ${active ? "bg-indigo-600 text-white" : "border"}`}
|
className={`rounded-md px-2 py-0.5 ${active ? "bg-primary text-primary-foreground" : "border"}`}
|
||||||
>
|
>
|
||||||
{value === "all" ? t("search.all") : t(`visibility.${value}`)}
|
{value === "all" ? t("search.all") : t(`visibility.${value}`)}
|
||||||
</button>
|
</button>
|
||||||
@@ -81,7 +81,7 @@ export function SearchPanel() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 overflow-auto">
|
<div className="flex-1 overflow-auto">
|
||||||
{!hasQuery && <p className="p-4 text-sm text-neutral-400">{t("search.prompt")}</p>}
|
{!hasQuery && <p className="p-4 text-sm text-muted-foreground">{t("search.prompt")}</p>}
|
||||||
|
|
||||||
{hasQuery && search.isLoading && (
|
{hasQuery && search.isLoading && (
|
||||||
<div className="space-y-2 p-3">
|
<div className="space-y-2 p-3">
|
||||||
@@ -92,7 +92,7 @@ export function SearchPanel() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{hasQuery && search.isError && (
|
{hasQuery && search.isError && (
|
||||||
<p className="p-4 text-sm text-red-600">
|
<p className="p-4 text-sm text-destructive">
|
||||||
{search.error instanceof HttpError && search.error.status === 503
|
{search.error instanceof HttpError && search.error.status === 503
|
||||||
? t("search.unavailable")
|
? t("search.unavailable")
|
||||||
: t("search.loadError")}
|
: t("search.loadError")}
|
||||||
@@ -100,12 +100,12 @@ export function SearchPanel() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{hasQuery && !search.isLoading && !search.isError && hits.length === 0 && (
|
{hasQuery && !search.isLoading && !search.isError && hits.length === 0 && (
|
||||||
<p className="p-4 text-sm text-neutral-500">{t("search.empty")}</p>
|
<p className="p-4 text-sm text-muted-foreground">{t("search.empty")}</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{hits.length > 0 && (
|
{hits.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<p className="px-3 pt-2 text-xs text-neutral-500">
|
<p className="px-3 pt-2 text-xs text-muted-foreground">
|
||||||
{t("search.resultCount", { count: total })}
|
{t("search.resultCount", { count: total })}
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
|
|||||||
@@ -12,16 +12,16 @@ export function SearchResultRow({ hit }: { hit: SearchHitView }) {
|
|||||||
<NavLink
|
<NavLink
|
||||||
to={`/search/${hit.id}`}
|
to={`/search/${hit.id}`}
|
||||||
className={({ isActive }) =>
|
className={({ isActive }) =>
|
||||||
`block border-b px-3 py-2 ${isActive ? "bg-indigo-50" : "hover:bg-neutral-50"}`
|
`block border-b px-3 py-2 ${isActive ? "bg-primary/10" : "hover:bg-muted"}`
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="text-sm font-semibold">{hit.object_name}</div>
|
<div className="text-sm font-semibold">{hit.object_name}</div>
|
||||||
<div className="mt-0.5 flex items-center gap-2 text-xs text-neutral-500">
|
<div className="mt-0.5 flex items-center gap-2 text-xs text-muted-foreground">
|
||||||
<span>{hit.object_number}</span>
|
<span>{hit.object_number}</span>
|
||||||
<VisibilityBadge visibility={hit.visibility} />
|
<VisibilityBadge visibility={hit.visibility} />
|
||||||
</div>
|
</div>
|
||||||
{hit.snippet && (
|
{hit.snippet && (
|
||||||
<p className="mt-1 line-clamp-2 text-xs text-neutral-600">
|
<p className="mt-1 line-clamp-2 text-xs text-muted-foreground">
|
||||||
<Highlight text={hit.snippet} />
|
<Highlight text={hit.snippet} />
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export function SelectSearchPrompt() {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full items-center justify-center p-4 text-sm text-neutral-400">
|
<div className="flex h-full items-center justify-center p-4 text-sm text-muted-foreground">
|
||||||
{t("search.selectPrompt")}
|
{t("search.selectPrompt")}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export function LangSwitch() {
|
|||||||
key={lng}
|
key={lng}
|
||||||
onClick={() => setLocale(lng)}
|
onClick={() => setLocale(lng)}
|
||||||
aria-pressed={base === lng}
|
aria-pressed={base === lng}
|
||||||
className={base === lng ? "font-bold" : "text-neutral-400"}
|
className={base === lng ? "font-bold" : "text-muted-foreground"}
|
||||||
>
|
>
|
||||||
{lng.toUpperCase()}
|
{lng.toUpperCase()}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -41,10 +41,10 @@ function readStored(): boolean {
|
|||||||
function navLinkClass(collapsed: boolean) {
|
function navLinkClass(collapsed: boolean) {
|
||||||
return ({ isActive }: { isActive: boolean }) =>
|
return ({ isActive }: { isActive: boolean }) =>
|
||||||
cn(
|
cn(
|
||||||
"flex items-center gap-2 rounded px-2 py-1 outline-none",
|
"flex items-center gap-2 rounded-md px-2 py-1 outline-none",
|
||||||
"focus-visible:ring-3 focus-visible:ring-ring/50",
|
"focus-visible:ring-3 focus-visible:ring-ring/50",
|
||||||
collapsed && "justify-center",
|
collapsed && "justify-center",
|
||||||
isActive && "bg-neutral-200 font-medium",
|
isActive && "bg-accent font-medium",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ export function Sidebar() {
|
|||||||
return (
|
return (
|
||||||
<aside
|
<aside
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex shrink-0 flex-col border-r bg-neutral-50 p-3 transition-[width]",
|
"flex shrink-0 flex-col border-r bg-muted p-3 transition-[width]",
|
||||||
collapsed ? "w-14" : "w-44",
|
collapsed ? "w-14" : "w-44",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -82,8 +82,8 @@ export function Sidebar() {
|
|||||||
aria-label={t(collapsed ? "nav.expandSidebar" : "nav.collapseSidebar")}
|
aria-label={t(collapsed ? "nav.expandSidebar" : "nav.collapseSidebar")}
|
||||||
title={t(collapsed ? "nav.expandSidebar" : "nav.collapseSidebar")}
|
title={t(collapsed ? "nav.expandSidebar" : "nav.collapseSidebar")}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center justify-center rounded p-1 outline-none",
|
"flex items-center justify-center rounded-md p-1 outline-none",
|
||||||
"hover:bg-neutral-200 focus-visible:ring-3 focus-visible:ring-ring/50",
|
"hover:bg-accent focus-visible:ring-3 focus-visible:ring-ring/50",
|
||||||
"disabled:pointer-events-none disabled:opacity-50",
|
"disabled:pointer-events-none disabled:opacity-50",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export function SelectVocabularyPrompt() {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full items-center justify-center p-4 text-sm text-neutral-400">
|
<div className="flex h-full items-center justify-center p-4 text-sm text-muted-foreground">
|
||||||
{t("vocab.selectPrompt")}
|
{t("vocab.selectPrompt")}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -45,20 +45,20 @@ export function VocabularyList() {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{create.isError && (
|
{create.isError && (
|
||||||
<p role="alert" className="text-xs text-red-600">
|
<p role="alert" className="text-xs text-destructive">
|
||||||
{t("form.rejected")}
|
{t("form.rejected")}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</form>
|
</form>
|
||||||
<ul className="flex-1 overflow-auto">
|
<ul className="flex-1 overflow-auto">
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
<li className="p-3 text-sm text-neutral-400">…</li>
|
<li className="p-3 text-sm text-muted-foreground">…</li>
|
||||||
)}
|
)}
|
||||||
{isError && (
|
{isError && (
|
||||||
<li className="p-3 text-sm text-red-600">{t("vocab.loadError")}</li>
|
<li className="p-3 text-sm text-destructive">{t("vocab.loadError")}</li>
|
||||||
)}
|
)}
|
||||||
{data?.length === 0 && (
|
{data?.length === 0 && (
|
||||||
<li className="p-3 text-sm text-neutral-500">{t("vocab.empty")}</li>
|
<li className="p-3 text-sm text-muted-foreground">{t("vocab.empty")}</li>
|
||||||
)}
|
)}
|
||||||
{data?.map((v) => (
|
{data?.map((v) => (
|
||||||
<li key={v.id} className="flex items-center gap-1 border-b pr-2">
|
<li key={v.id} className="flex items-center gap-1 border-b pr-2">
|
||||||
@@ -85,7 +85,7 @@ export function VocabularyList() {
|
|||||||
{t("form.cancel")}
|
{t("form.cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
{renameVocabulary.isError && (
|
{renameVocabulary.isError && (
|
||||||
<p role="alert" className="text-xs text-red-600">
|
<p role="alert" className="text-xs text-destructive">
|
||||||
{t("form.rejected")}
|
{t("form.rejected")}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -95,7 +95,7 @@ export function VocabularyList() {
|
|||||||
<NavLink
|
<NavLink
|
||||||
to={`/vocabularies/${v.id}`}
|
to={`/vocabularies/${v.id}`}
|
||||||
className={({ isActive }) =>
|
className={({ isActive }) =>
|
||||||
`block flex-1 px-3 py-2 text-sm ${isActive ? "bg-indigo-50" : "hover:bg-neutral-50"}`
|
`block flex-1 px-3 py-2 text-sm ${isActive ? "bg-primary/10" : "hover:bg-muted"}`
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{v.key}
|
{v.key}
|
||||||
|
|||||||
@@ -49,18 +49,18 @@ export function VocabularyTerms() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="overflow-auto p-4">
|
<div className="overflow-auto p-4">
|
||||||
<h3 className="mb-2 text-sm font-medium uppercase text-neutral-500">
|
<h3 className="mb-2 label-caption">
|
||||||
{t("vocab.terms")}
|
{t("vocab.terms")}
|
||||||
</h3>
|
</h3>
|
||||||
<ul className="mb-4">
|
<ul className="mb-4">
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
<li className="text-sm text-neutral-400">…</li>
|
<li className="text-sm text-muted-foreground">…</li>
|
||||||
)}
|
)}
|
||||||
{isError && (
|
{isError && (
|
||||||
<li className="text-sm text-red-600">{t("vocab.loadError")}</li>
|
<li className="text-sm text-destructive">{t("vocab.loadError")}</li>
|
||||||
)}
|
)}
|
||||||
{!isLoading && !isError && terms?.length === 0 && (
|
{!isLoading && !isError && terms?.length === 0 && (
|
||||||
<li className="text-sm text-neutral-500">{t("vocab.noTerms")}</li>
|
<li className="text-sm text-muted-foreground">{t("vocab.noTerms")}</li>
|
||||||
)}
|
)}
|
||||||
{terms?.map((term) => (
|
{terms?.map((term) => (
|
||||||
<TermRow key={term.id} vocabularyId={id} term={term} lang={lang} />
|
<TermRow key={term.id} vocabularyId={id} term={term} lang={lang} />
|
||||||
@@ -78,12 +78,12 @@ export function VocabularyTerms() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{error && (
|
{error && (
|
||||||
<p role="alert" className="text-xs text-red-600">
|
<p role="alert" className="text-xs text-destructive">
|
||||||
{t("form.required")}
|
{t("form.required")}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{addTerm.isError && (
|
{addTerm.isError && (
|
||||||
<p role="alert" className="text-xs text-red-600">
|
<p role="alert" className="text-xs text-destructive">
|
||||||
{t("form.rejected")}
|
{t("form.rejected")}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user