feat(studio): localize catalog studio UI and polish responsive layout
Route all Catalog Studio strings through the admin.studio namespace (workspace, filters, review, inspector, quality, sync, scale32, organize imports and page titles/subtitles) with English fallback, and add shared StudioPage chrome plus a loading skeleton. Tighten mobile padding on the header and sub-bars.
This commit is contained in:
1 parent
d7d6382b60
commit
56676effc3
28 files changed
+1293
-634
No files matched your search
@@ -10,6 +10,7 @@ import {
|
||||
Search,
|
||||
X,
|
||||
} from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { memo, useMemo, useState, useTransition } from "react";
|
||||
import {
|
||||
refreshCatalogTree,
|
||||
@@ -25,6 +26,7 @@ import type { TreeNode } from "@/types/catalog";
|
||||
* member has no other signal that the import they just ran is not on screen.
|
||||
*/
|
||||
function LiveCatalogNotice() {
|
||||
const t = useTranslations("admin.studio");
|
||||
const { refreshError, refreshing } = useLiveCatalog();
|
||||
const [retrying, startRetry] = useTransition();
|
||||
if (!refreshError && !refreshing) return null;
|
||||
@@ -41,7 +43,7 @@ function LiveCatalogNotice() {
|
||||
{refreshError ? (
|
||||
<>
|
||||
<AlertTriangle size={14} className="shrink-0" />
|
||||
<span className="min-w-0 flex-1">Catalog could not refresh.</span>
|
||||
<span className="min-w-0 flex-1">{t("rail.refreshFailed")}</span>
|
||||
<button
|
||||
type="button"
|
||||
disabled={retrying}
|
||||
@@ -53,13 +55,13 @@ function LiveCatalogNotice() {
|
||||
className="flex items-center gap-1 rounded px-1.5 py-0.5 font-medium underline disabled:opacity-50"
|
||||
>
|
||||
<RefreshCw size={12} className={cn(retrying && "animate-spin")} />
|
||||
Retry
|
||||
{t("rail.retry")}
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Loader2 size={14} className="shrink-0 animate-spin" />
|
||||
<span className="min-w-0 flex-1">Updating catalog…</span>
|
||||
<span className="min-w-0 flex-1">{t("rail.updating")}</span>
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
@@ -77,6 +79,7 @@ export const CatalogRail = memo(function CatalogRail({
|
||||
onSelect: (id: number | null) => void;
|
||||
onClose?: () => void;
|
||||
}) {
|
||||
const t = useTranslations("admin.studio");
|
||||
const [collapsed, setCollapsed] = useState<Set<number>>(new Set());
|
||||
const [search, setSearch] = useState("");
|
||||
const visible = useMemo(() => {
|
||||
@@ -100,7 +103,7 @@ export const CatalogRail = memo(function CatalogRail({
|
||||
}
|
||||
return (
|
||||
<aside
|
||||
aria-label="Catalog categories"
|
||||
aria-label={t("aria.catalogCategories")}
|
||||
className="absolute inset-y-0 left-0 z-30 flex w-64 max-w-[85%] flex-col border-r border-[var(--admin-border)] bg-[var(--admin-surface)] shadow-xl lg:static lg:z-auto lg:shrink-0 lg:shadow-none"
|
||||
>
|
||||
<div className="flex items-center gap-2 px-3 pt-3">
|
||||
@@ -109,12 +112,12 @@ export const CatalogRail = memo(function CatalogRail({
|
||||
style={{ fontSize: 14, margin: 0 }}
|
||||
className="flex-1 text-sm font-semibold"
|
||||
>
|
||||
Categories
|
||||
{t("rail.title")}
|
||||
</h2>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
aria-label="Close categories"
|
||||
aria-label={t("aria.closeCategories")}
|
||||
className="grid h-9 w-9 place-items-center rounded-md hover:bg-[var(--admin-accent)]/10"
|
||||
>
|
||||
<X size={16} />
|
||||
@@ -125,8 +128,8 @@ export const CatalogRail = memo(function CatalogRail({
|
||||
<input
|
||||
value={search}
|
||||
onChange={(event) => setSearch(event.target.value)}
|
||||
placeholder="Find a category…"
|
||||
aria-label="Find a category"
|
||||
placeholder={t("aria.findCategory")}
|
||||
aria-label={t("aria.findCategory")}
|
||||
className="h-9 min-w-0 w-full border-0 bg-transparent text-sm outline-none"
|
||||
/>
|
||||
</label>
|
||||
@@ -136,13 +139,13 @@ export const CatalogRail = memo(function CatalogRail({
|
||||
aria-pressed={selectedId === null}
|
||||
className="mx-2 mb-2 rounded-md px-3 py-2 text-left text-sm hover:bg-[var(--admin-accent)]/10 aria-pressed:bg-[var(--admin-accent)]/15"
|
||||
>
|
||||
All categories
|
||||
{t("rail.all")}
|
||||
</button>
|
||||
<LiveCatalogNotice />
|
||||
<div className="min-h-0 flex-1 overflow-y-auto px-2 pb-3">
|
||||
{visible.length === 0 && (
|
||||
<p className="p-3 text-sm text-[var(--admin-text-muted)]">
|
||||
No matching categories.
|
||||
{t("rail.noMatches")}
|
||||
</p>
|
||||
)}
|
||||
{visible.map((node) => (
|
||||
@@ -159,7 +162,11 @@ export const CatalogRail = memo(function CatalogRail({
|
||||
{node.childCount > 0 && !search.trim() ? (
|
||||
<button
|
||||
type="button"
|
||||
aria-label={`${collapsed.has(node.id) ? "Expand" : "Collapse"} ${node.caption}`}
|
||||
aria-label={
|
||||
collapsed.has(node.id)
|
||||
? t("aria.expandCategory", { name: node.caption })
|
||||
: t("aria.collapseCategory", { name: node.caption })
|
||||
}
|
||||
aria-expanded={!collapsed.has(node.id)}
|
||||
onClick={() => toggle(node.id)}
|
||||
className="grid h-10 w-8 shrink-0 place-items-center rounded-md hover:bg-[var(--admin-accent)]/10"
|
||||
|
||||
Reference in new issue
Block a user