From 0d4026a9689633a713bebccda457145a16ccec82 Mon Sep 17 00:00:00 2001 From: Anders Olsson Date: Mon, 8 Jun 2026 06:50:57 +0200 Subject: [PATCH] =?UTF-8?q?feat(web):=20standardize=20loading=20on=20share?= =?UTF-8?q?d=20skeleton=20recipes;=20retire=20'=E2=80=A6'=20+=20empty=20st?= =?UTF-8?q?atus=20divs=20(#53)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/app.tsx | 11 ++++----- web/src/auth/require-auth.tsx | 3 ++- web/src/authorities/authorities-page.tsx | 30 +++++++++++++----------- web/src/fields/field-list.tsx | 12 ++-------- web/src/objects/object-edit-form.tsx | 3 ++- web/src/search/search-panel.tsx | 8 ++----- web/src/vocab/vocabulary-list.tsx | 28 ++++++++++++---------- web/src/vocab/vocabulary-terms.tsx | 30 +++++++++++++----------- 8 files changed, 59 insertions(+), 66 deletions(-) diff --git a/web/src/app.tsx b/web/src/app.tsx index c8bd44d..907a7cc 100644 --- a/web/src/app.tsx +++ b/web/src/app.tsx @@ -12,6 +12,7 @@ import { VocabulariesPage } from "./vocab/vocabularies-page"; import { VocabularyTerms } from "./vocab/vocabulary-terms"; import { SelectVocabularyPrompt } from "./vocab/select-vocabulary-prompt"; import { AuthoritiesPage } from "./authorities/authorities-page"; +import { FormSkeleton, ListSkeleton } from "@/components/ui/skeletons"; const ObjectNewPage = lazy(() => import("./objects/object-new-page").then((m) => ({ default: m.ObjectNewPage })), @@ -25,10 +26,6 @@ const FieldsPage = lazy(() => import("./fields/fields-page").then((m) => ({ default: m.FieldsPage })), ); -function FormFallback() { - return
Loading…
; -} - const router = createBrowserRouter( createRoutesFromElements( <> @@ -38,7 +35,7 @@ const router = createBrowserRouter( }> + }> } @@ -48,7 +45,7 @@ const router = createBrowserRouter( }> + }> } @@ -67,7 +64,7 @@ const router = createBrowserRouter( }> + }> } diff --git a/web/src/auth/require-auth.tsx b/web/src/auth/require-auth.tsx index cc60e88..23876f3 100644 --- a/web/src/auth/require-auth.tsx +++ b/web/src/auth/require-auth.tsx @@ -1,11 +1,12 @@ import { Navigate, Outlet } from "react-router-dom"; import { useMe } from "../api/queries"; +import { AppShellSkeleton } from "@/components/ui/skeletons"; export function RequireAuth() { const { data: user, isLoading } = useMe(); - if (isLoading) return
; + if (isLoading) return ; if (!user) return ; diff --git a/web/src/authorities/authorities-page.tsx b/web/src/authorities/authorities-page.tsx index ad821ac..19bd9f8 100644 --- a/web/src/authorities/authorities-page.tsx +++ b/web/src/authorities/authorities-page.tsx @@ -7,6 +7,7 @@ import { useAuthorities, useCreateAuthority } from "../api/queries"; import { LabelEditor } from "../components/label-editor"; import { Button } from "@/components/ui/button"; import { PageTitle } from "@/components/ui/page-title"; +import { ListSkeleton } from "@/components/ui/skeletons"; import { AuthorityRow } from "./authority-row"; import { useDocumentTitle } from "../lib/use-document-title"; import { useBreadcrumb } from "../shell/use-breadcrumb"; @@ -68,20 +69,21 @@ export function AuthoritiesPage() { ))}
-
    - {isLoading && ( -
  • - )} - {isError && ( -
  • {t("authorities.loadError")}
  • - )} - {!isLoading && !isError && authorities?.length === 0 && ( -
  • {t("authorities.empty")}
  • - )} - {authorities?.map((a) => ( - - ))} -
+ {isLoading ? ( + + ) : ( +
    + {isError && ( +
  • {t("authorities.loadError")}
  • + )} + {!isError && authorities?.length === 0 && ( +
  • {t("authorities.empty")}
  • + )} + {authorities?.map((a) => ( + + ))} +
+ )}
diff --git a/web/src/fields/field-list.tsx b/web/src/fields/field-list.tsx index 2c43168..b0eb8b1 100644 --- a/web/src/fields/field-list.tsx +++ b/web/src/fields/field-list.tsx @@ -4,7 +4,7 @@ import type { components } from "../api/schema"; import { useFieldDefinitions, useDeleteFieldDefinition } from "../api/queries"; import { labelText } from "../lib/labels"; import { DeleteConfirmDialog } from "../components/delete-confirm-dialog"; -import { Skeleton } from "@/components/ui/skeleton"; +import { ListSkeleton } from "@/components/ui/skeletons"; type FieldDefinitionView = components["schemas"]["FieldDefinitionView"]; @@ -20,15 +20,7 @@ export function FieldList({ const deleteField = useDeleteFieldDefinition(); const lang = i18n.language.startsWith("sv") ? "sv" : "en"; - if (isLoading) { - return ( -
- {Array.from({ length: 6 }).map((_, i) => ( - - ))} -
- ); - } + if (isLoading) return ; if (isError) return

{t("fields.loadError")}

; if (!data || data.length === 0) diff --git a/web/src/objects/object-edit-form.tsx b/web/src/objects/object-edit-form.tsx index c1eeecd..0677ac1 100644 --- a/web/src/objects/object-edit-form.tsx +++ b/web/src/objects/object-edit-form.tsx @@ -6,6 +6,7 @@ import type { components } from "../api/schema"; import { useObject, useUpdateObject, useSetFields, FieldRejection } from "../api/queries"; import { useBreadcrumb } from "../shell/use-breadcrumb"; import { ObjectForm, type ObjectCore, type ObjectFormValues } from "./object-form"; +import { FormSkeleton } from "@/components/ui/skeletons"; type AdminObjectView = components["schemas"]["AdminObjectView"]; @@ -15,7 +16,7 @@ export function ObjectEditForm() { const { data: object, isLoading } = useObject(id!); - if (isLoading) return
; + if (isLoading) return ; if (!object) return

{t("objects.notFound")}

; diff --git a/web/src/search/search-panel.tsx b/web/src/search/search-panel.tsx index b599862..b814e69 100644 --- a/web/src/search/search-panel.tsx +++ b/web/src/search/search-panel.tsx @@ -7,7 +7,7 @@ import { useDebouncedValue } from "../lib/use-debounced-value"; import { SearchResultRow } from "./search-result-row"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; -import { Skeleton } from "@/components/ui/skeleton"; +import { ListSkeleton } from "@/components/ui/skeletons"; const VIS = ["all", "draft", "internal", "public"] as const; @@ -84,11 +84,7 @@ export function SearchPanel() { {!hasQuery &&

{t("search.prompt")}

} {hasQuery && search.isLoading && ( -
- {Array.from({ length: 5 }).map((_, i) => ( - - ))} -
+ )} {hasQuery && search.isError && ( diff --git a/web/src/vocab/vocabulary-list.tsx b/web/src/vocab/vocabulary-list.tsx index 53cae92..86169f1 100644 --- a/web/src/vocab/vocabulary-list.tsx +++ b/web/src/vocab/vocabulary-list.tsx @@ -7,6 +7,7 @@ import { DeleteConfirmDialog } from "../components/delete-confirm-dialog"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; +import { ListSkeleton } from "@/components/ui/skeletons"; export function VocabularyList() { const { t } = useTranslation(); @@ -50,17 +51,17 @@ export function VocabularyList() {

)} -
    - {isLoading && ( -
  • - )} - {isError && ( -
  • {t("vocab.loadError")}
  • - )} - {data?.length === 0 && ( -
  • {t("vocab.empty")}
  • - )} - {data?.map((v) => ( + {isLoading ? ( + + ) : ( +
      + {isError && ( +
    • {t("vocab.loadError")}
    • + )} + {data?.length === 0 && ( +
    • {t("vocab.empty")}
    • + )} + {data?.map((v) => (
    • {editingId === v.id ? (
      )}
    • - ))} -
    + ))} +
+ )}
); } diff --git a/web/src/vocab/vocabulary-terms.tsx b/web/src/vocab/vocabulary-terms.tsx index dde518f..ed80fe2 100644 --- a/web/src/vocab/vocabulary-terms.tsx +++ b/web/src/vocab/vocabulary-terms.tsx @@ -10,6 +10,7 @@ import { TermRow } from "./term-row"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; +import { ListSkeleton } from "@/components/ui/skeletons"; type LabelInput = components["schemas"]["LabelInput"]; @@ -62,20 +63,21 @@ export function VocabularyTerms() {
{t("vocab.terms")}
-
    - {isLoading && ( -
  • - )} - {isError && ( -
  • {t("vocab.loadError")}
  • - )} - {!isLoading && !isError && terms?.length === 0 && ( -
  • {t("vocab.noTerms")}
  • - )} - {terms?.map((term) => ( - - ))} -
+ {isLoading ? ( + + ) : ( +
    + {isError && ( +
  • {t("vocab.loadError")}
  • + )} + {!isError && terms?.length === 0 && ( +
  • {t("vocab.noTerms")}
  • + )} + {terms?.map((term) => ( + + ))} +
+ )}
{t("vocab.addTerm")}