[WEB-3567] chore: empty state refactoring and translation fix (#8014)

* chore: empty state component improvement and code refactor

* chore: translation code refactor

* chore: empty state code refactor
This commit is contained in:
Anmol Singh Bhatia 2025-10-27 17:03:35 +05:30 committed by GitHub
parent be0d1871f0
commit 3faf768112
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 121 additions and 116 deletions

View file

@ -8,9 +8,9 @@ import useSWR from "swr";
import { EUserPermissions, EUserPermissionsLevel, WORKSPACE_SETTINGS_TRACKER_ELEMENTS } from "@plane/constants"; import { EUserPermissions, EUserPermissionsLevel, WORKSPACE_SETTINGS_TRACKER_ELEMENTS } from "@plane/constants";
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
// components // components
import { EmptyStateCompact } from "@plane/propel/empty-state";
import { NotAuthorizedView } from "@/components/auth-screens/not-authorized-view"; import { NotAuthorizedView } from "@/components/auth-screens/not-authorized-view";
import { PageHead } from "@/components/core/page-title"; import { PageHead } from "@/components/core/page-title";
import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root";
import { SettingsContentWrapper } from "@/components/settings/content-wrapper"; import { SettingsContentWrapper } from "@/components/settings/content-wrapper";
import { SettingsHeading } from "@/components/settings/heading"; import { SettingsHeading } from "@/components/settings/heading";
import { WebhookSettingsLoader } from "@/components/ui/loader/settings/web-hook"; import { WebhookSettingsLoader } from "@/components/ui/loader/settings/web-hook";
@ -20,7 +20,6 @@ import { captureClick } from "@/helpers/event-tracker.helper";
import { useWebhook } from "@/hooks/store/use-webhook"; import { useWebhook } from "@/hooks/store/use-webhook";
import { useWorkspace } from "@/hooks/store/use-workspace"; import { useWorkspace } from "@/hooks/store/use-workspace";
import { useUserPermissions } from "@/hooks/store/user"; import { useUserPermissions } from "@/hooks/store/user";
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
const WebhooksListPage = observer(() => { const WebhooksListPage = observer(() => {
// states // states
@ -35,7 +34,6 @@ const WebhooksListPage = observer(() => {
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
// derived values // derived values
const canPerformWorkspaceAdminActions = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.WORKSPACE); const canPerformWorkspaceAdminActions = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.WORKSPACE);
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/workspace-settings/webhooks" });
useSWR( useSWR(
workspaceSlug && canPerformWorkspaceAdminActions ? `WEBHOOKS_LIST_${workspaceSlug}` : null, workspaceSlug && canPerformWorkspaceAdminActions ? `WEBHOOKS_LIST_${workspaceSlug}` : null,
@ -90,21 +88,23 @@ const WebhooksListPage = observer(() => {
) : ( ) : (
<div className="flex h-full w-full flex-col"> <div className="flex h-full w-full flex-col">
<div className="h-full w-full flex items-center justify-center"> <div className="h-full w-full flex items-center justify-center">
<DetailedEmptyState <EmptyStateCompact
className="!p-0" assetKey="webhook"
title="" title={t("settings.webhooks.title")}
description="" description={t("settings.webhooks.description")}
assetPath={resolvedPath} actions={[
size="md" {
primaryButton={{ label: t("settings.webhooks.cta_primary"),
text: t("workspace_settings.settings.webhooks.add_webhook"), onClick: () => {
onClick: () => { captureClick({
captureClick({ elementName: WORKSPACE_SETTINGS_TRACKER_ELEMENTS.EMPTY_STATE_ADD_WEBHOOK_BUTTON,
elementName: WORKSPACE_SETTINGS_TRACKER_ELEMENTS.EMPTY_STATE_ADD_WEBHOOK_BUTTON, });
}); setShowCreateWebhookModal(true);
setShowCreateWebhookModal(true); },
}, },
}} ]}
align="start"
rootClassName="py-20"
/> />
</div> </div>
</div> </div>

View file

@ -4,15 +4,14 @@ import { useParams } from "next/navigation";
import useSWR from "swr"; import useSWR from "swr";
// plane imports // plane imports
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
import { EmptyStateDetailed } from "@plane/propel/empty-state";
import type { TCycleFilters } from "@plane/types"; import type { TCycleFilters } from "@plane/types";
import { calculateTotalFilters } from "@plane/utils"; import { calculateTotalFilters } from "@plane/utils";
// components // components
import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root";
import { CycleModuleListLayoutLoader } from "@/components/ui/loader/cycle-module-list-loader"; import { CycleModuleListLayoutLoader } from "@/components/ui/loader/cycle-module-list-loader";
// hooks // hooks
import { useCycle } from "@/hooks/store/use-cycle"; import { useCycle } from "@/hooks/store/use-cycle";
import { useCycleFilter } from "@/hooks/store/use-cycle-filter"; import { useCycleFilter } from "@/hooks/store/use-cycle-filter";
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
// local imports // local imports
import { CycleAppliedFiltersList } from "../applied-filters"; import { CycleAppliedFiltersList } from "../applied-filters";
import { ArchivedCyclesView } from "./view"; import { ArchivedCyclesView } from "./view";
@ -28,7 +27,6 @@ export const ArchivedCycleLayoutRoot: React.FC = observer(() => {
const { clearAllFilters, currentProjectArchivedFilters, updateFilters } = useCycleFilter(); const { clearAllFilters, currentProjectArchivedFilters, updateFilters } = useCycleFilter();
// derived values // derived values
const totalArchivedCycles = currentProjectArchivedCycleIds?.length ?? 0; const totalArchivedCycles = currentProjectArchivedCycleIds?.length ?? 0;
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/archived/empty-cycles" });
useSWR( useSWR(
workspaceSlug && projectId ? `ARCHIVED_CYCLES_${workspaceSlug.toString()}_${projectId.toString()}` : null, workspaceSlug && projectId ? `ARCHIVED_CYCLES_${workspaceSlug.toString()}_${projectId.toString()}` : null,
@ -69,10 +67,10 @@ export const ArchivedCycleLayoutRoot: React.FC = observer(() => {
)} )}
{totalArchivedCycles === 0 ? ( {totalArchivedCycles === 0 ? (
<div className="h-full place-items-center"> <div className="h-full place-items-center">
<DetailedEmptyState <EmptyStateDetailed
title={t("project_cycles.empty_state.archived.title")} assetKey="archived-cycle"
description={t("project_cycles.empty_state.archived.description")} title={t("workspace.archive_cycles.title")}
assetPath={resolvedPath} description={t("workspace.archive_cycles.description")}
/> />
</div> </div>
) : ( ) : (

View file

@ -4,15 +4,13 @@ import useSWR, { mutate } from "swr";
import { MoveLeft, MoveRight, RefreshCw } from "lucide-react"; import { MoveLeft, MoveRight, RefreshCw } from "lucide-react";
// plane imports // plane imports
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
import { EmptyStateCompact } from "@plane/propel/empty-state";
import type { IExportData } from "@plane/types"; import type { IExportData } from "@plane/types";
import { Table } from "@plane/ui"; import { Table } from "@plane/ui";
// components // components
import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root";
import { ImportExportSettingsLoader } from "@/components/ui/loader/settings/import-and-export"; import { ImportExportSettingsLoader } from "@/components/ui/loader/settings/import-and-export";
// constants // constants
import { EXPORT_SERVICES_LIST } from "@/constants/fetch-keys"; import { EXPORT_SERVICES_LIST } from "@/constants/fetch-keys";
// hooks
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
// services // services
import { IntegrationService } from "@/services/integrations"; import { IntegrationService } from "@/services/integrations";
// local imports // local imports
@ -35,7 +33,6 @@ export const PrevExports = observer((props: Props) => {
// hooks // hooks
const { t } = useTranslation(); const { t } = useTranslation();
const columns = useExportColumns(); const columns = useExportColumns();
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/workspace-settings/exports" });
const { data: exporterServices } = useSWR( const { data: exporterServices } = useSWR(
workspaceSlug && cursor ? EXPORT_SERVICES_LIST(workspaceSlug as string, cursor, `${per_page}`) : null, workspaceSlug && cursor ? EXPORT_SERVICES_LIST(workspaceSlug as string, cursor, `${per_page}`) : null,
@ -125,12 +122,12 @@ export const PrevExports = observer((props: Props) => {
</div> </div>
) : ( ) : (
<div className="flex h-full w-full items-center justify-center"> <div className="flex h-full w-full items-center justify-center">
<DetailedEmptyState <EmptyStateCompact
title={t("workspace_settings.empty_state.exports.title")} assetKey="export"
description={t("workspace_settings.empty_state.exports.description")} title={t("settings.exports.title")}
assetPath={resolvedPath} description={t("settings.exports.description")}
className="w-full !px-0" align="start"
size="sm" rootClassName="py-20"
/> />
</div> </div>
) )

View file

@ -162,6 +162,7 @@ export const InboxSidebar: FC<IInboxSidebarProps> = observer((props) => {
title="No request closed yet" title="No request closed yet"
description="All the work items whether accepted or declined can be found here." description="All the work items whether accepted or declined can be found here."
assetClassName="size-20" assetClassName="size-20"
className="px-10"
/> />
)} )}
</div> </div>

View file

@ -3,15 +3,12 @@ import { useParams } from "next/navigation";
// plane imports // plane imports
import { EUserPermissionsLevel } from "@plane/constants"; import { EUserPermissionsLevel } from "@plane/constants";
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
import { EmptyStateDetailed } from "@plane/propel/empty-state";
import { EIssuesStoreType, EUserProjectRoles } from "@plane/types"; import { EIssuesStoreType, EUserProjectRoles } from "@plane/types";
// components
import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root";
// hooks // hooks
import { useIssues } from "@/hooks/store/use-issues";
import { useUserPermissions } from "@/hooks/store/user"; import { useUserPermissions } from "@/hooks/store/user";
import { useWorkItemFilterInstance } from "@/hooks/store/work-item-filters/use-work-item-filter-instance"; import { useWorkItemFilterInstance } from "@/hooks/store/work-item-filters/use-work-item-filter-instance";
import { useAppRouter } from "@/hooks/use-app-router"; import { useAppRouter } from "@/hooks/use-app-router";
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
export const ProjectArchivedEmptyState: React.FC = observer(() => { export const ProjectArchivedEmptyState: React.FC = observer(() => {
// router // router
@ -22,48 +19,45 @@ export const ProjectArchivedEmptyState: React.FC = observer(() => {
// plane hooks // plane hooks
const { t } = useTranslation(); const { t } = useTranslation();
// store hooks // store hooks
const { issuesFilter } = useIssues(EIssuesStoreType.ARCHIVED);
const { allowPermissions } = useUserPermissions(); const { allowPermissions } = useUserPermissions();
// derived values // derived values
const archivedWorkItemFilter = projectId const archivedWorkItemFilter = projectId
? useWorkItemFilterInstance(EIssuesStoreType.ARCHIVED, projectId) ? useWorkItemFilterInstance(EIssuesStoreType.ARCHIVED, projectId)
: undefined; : undefined;
const activeLayout = issuesFilter?.issueFilters?.displayFilters?.layout;
const additionalPath = archivedWorkItemFilter?.hasActiveFilters ? (activeLayout ?? "list") : undefined;
const canPerformEmptyStateActions = allowPermissions( const canPerformEmptyStateActions = allowPermissions(
[EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER], [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER],
EUserPermissionsLevel.PROJECT EUserPermissionsLevel.PROJECT
); );
const emptyFilterResolvedPath = useResolvedAssetPath({
basePath: "/empty-state/empty-filters/",
additionalPath: additionalPath,
});
const archivedIssuesResolvedPath = useResolvedAssetPath({
basePath: "/empty-state/archived/empty-issues",
});
return ( return (
<div className="relative h-full w-full overflow-y-auto"> <div className="relative h-full w-full overflow-y-auto">
{archivedWorkItemFilter?.hasActiveFilters ? ( {archivedWorkItemFilter?.hasActiveFilters ? (
<DetailedEmptyState <EmptyStateDetailed
title={t("project_issues.empty_state.issues_empty_filter.title")} assetKey="search"
assetPath={emptyFilterResolvedPath} title={t("common.search.title")}
secondaryButton={{ description={t("common.search.description")}
text: t("project_issues.empty_state.issues_empty_filter.secondary_button.text"), actions={[
onClick: archivedWorkItemFilter?.clearFilters, {
disabled: !canPerformEmptyStateActions || !archivedWorkItemFilter, label: t("common.search.cta_secondary"),
}} onClick: archivedWorkItemFilter?.clearFilters,
disabled: !canPerformEmptyStateActions || !archivedWorkItemFilter,
variant: "outline-primary",
},
]}
/> />
) : ( ) : (
<DetailedEmptyState <EmptyStateDetailed
title={t("project_issues.empty_state.no_archived_issues.title")} assetKey="archived-work-item"
description={t("project_issues.empty_state.no_archived_issues.description")} title={t("workspace.archive_work_items.title")}
assetPath={archivedIssuesResolvedPath} description={t("workspace.archive_work_items.description")}
primaryButton={{ actions={[
text: t("project_issues.empty_state.no_archived_issues.primary_button.text"), {
onClick: () => router.push(`/${workspaceSlug}/settings/projects/${projectId}/automations`), label: t("workspace.archive_work_items.cta_primary"),
disabled: !canPerformEmptyStateActions, onClick: () => router.push(`/${workspaceSlug}/settings/projects/${projectId}/automations`),
}} disabled: !canPerformEmptyStateActions,
variant: "primary",
},
]}
/> />
)} )}
</div> </div>

View file

@ -4,17 +4,16 @@ import { useParams } from "next/navigation";
import useSWR from "swr"; import useSWR from "swr";
// plane imports // plane imports
import { useTranslation } from "@plane/i18n"; import { useTranslation } from "@plane/i18n";
import { EmptyStateDetailed } from "@plane/propel/empty-state";
import type { TModuleFilters } from "@plane/types"; import type { TModuleFilters } from "@plane/types";
// components // components
import { calculateTotalFilters } from "@plane/utils"; import { calculateTotalFilters } from "@plane/utils";
import { DetailedEmptyState } from "@/components/empty-state/detailed-empty-state-root";
import { ArchivedModulesView, ModuleAppliedFiltersList } from "@/components/modules"; import { ArchivedModulesView, ModuleAppliedFiltersList } from "@/components/modules";
import { CycleModuleListLayoutLoader } from "@/components/ui/loader/cycle-module-list-loader"; import { CycleModuleListLayoutLoader } from "@/components/ui/loader/cycle-module-list-loader";
// helpers // helpers
// hooks // hooks
import { useModule } from "@/hooks/store/use-module"; import { useModule } from "@/hooks/store/use-module";
import { useModuleFilter } from "@/hooks/store/use-module-filter"; import { useModuleFilter } from "@/hooks/store/use-module-filter";
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
export const ArchivedModuleLayoutRoot: React.FC = observer(() => { export const ArchivedModuleLayoutRoot: React.FC = observer(() => {
// router // router
@ -26,7 +25,6 @@ export const ArchivedModuleLayoutRoot: React.FC = observer(() => {
const { clearAllFilters, currentProjectArchivedFilters, updateFilters } = useModuleFilter(); const { clearAllFilters, currentProjectArchivedFilters, updateFilters } = useModuleFilter();
// derived values // derived values
const totalArchivedModules = projectArchivedModuleIds?.length ?? 0; const totalArchivedModules = projectArchivedModuleIds?.length ?? 0;
const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/archived/empty-modules" });
useSWR( useSWR(
workspaceSlug && projectId ? `ARCHIVED_MODULES_${workspaceSlug.toString()}_${projectId.toString()}` : null, workspaceSlug && projectId ? `ARCHIVED_MODULES_${workspaceSlug.toString()}_${projectId.toString()}` : null,
@ -72,10 +70,10 @@ export const ArchivedModuleLayoutRoot: React.FC = observer(() => {
)} )}
{totalArchivedModules === 0 ? ( {totalArchivedModules === 0 ? (
<div className="h-full place-items-center"> <div className="h-full place-items-center">
<DetailedEmptyState <EmptyStateDetailed
title={t("project_module.empty_state.archived.title")} assetKey="archived-module"
description={t("project_module.empty_state.archived.description")} title={t("workspace.archive_modules.title")}
assetPath={resolvedPath} description={t("workspace.archive_modules.description")}
/> />
</div> </div>
) : ( ) : (

View file

@ -32,7 +32,7 @@ export enum ETranslationFiles {
TRANSLATIONS = "translations", TRANSLATIONS = "translations",
ACCESSIBILITY = "accessibility", ACCESSIBILITY = "accessibility",
EDITOR = "editor", EDITOR = "editor",
EMPTY_STATE = "emptyState", EMPTY_STATE = "empty-state",
} }
export const LANGUAGE_STORAGE_KEY = "userLanguage"; export const LANGUAGE_STORAGE_KEY = "userLanguage";

View file

@ -12,114 +12,114 @@ export const locales = {
translations: () => import("./en/translations"), translations: () => import("./en/translations"),
accessibility: () => import("./en/accessibility"), accessibility: () => import("./en/accessibility"),
editor: () => import("./en/editor"), editor: () => import("./en/editor"),
emptyState: () => import("./en/empty-state"), "empty-state": () => import("./en/empty-state"),
}, },
fr: { fr: {
translations: () => import("./fr/translations"), translations: () => import("./fr/translations"),
accessibility: () => import("./fr/accessibility"), accessibility: () => import("./fr/accessibility"),
editor: () => import("./fr/editor"), editor: () => import("./fr/editor"),
emptyState: () => import("./fr/empty-state"), "empty-state": () => import("./fr/empty-state"),
}, },
es: { es: {
translations: () => import("./es/translations"), translations: () => import("./es/translations"),
accessibility: () => import("./es/accessibility"), accessibility: () => import("./es/accessibility"),
editor: () => import("./es/editor"), editor: () => import("./es/editor"),
emptyState: () => import("./es/empty-state"), "empty-state": () => import("./es/empty-state"),
}, },
ja: { ja: {
translations: () => import("./ja/translations"), translations: () => import("./ja/translations"),
accessibility: () => import("./ja/accessibility"), accessibility: () => import("./ja/accessibility"),
editor: () => import("./ja/editor"), editor: () => import("./ja/editor"),
emptyState: () => import("./ja/empty-state"), "empty-state": () => import("./ja/empty-state"),
}, },
"zh-CN": { "zh-CN": {
translations: () => import("./zh-CN/translations"), translations: () => import("./zh-CN/translations"),
accessibility: () => import("./zh-CN/accessibility"), accessibility: () => import("./zh-CN/accessibility"),
editor: () => import("./zh-CN/editor"), editor: () => import("./zh-CN/editor"),
emptyState: () => import("./zh-CN/empty-state"), "empty-state": () => import("./zh-CN/empty-state"),
}, },
"zh-TW": { "zh-TW": {
translations: () => import("./zh-TW/translations"), translations: () => import("./zh-TW/translations"),
accessibility: () => import("./zh-TW/accessibility"), accessibility: () => import("./zh-TW/accessibility"),
editor: () => import("./zh-TW/editor"), editor: () => import("./zh-TW/editor"),
emptyState: () => import("./zh-TW/empty-state"), "empty-state": () => import("./zh-TW/empty-state"),
}, },
ru: { ru: {
translations: () => import("./ru/translations"), translations: () => import("./ru/translations"),
accessibility: () => import("./ru/accessibility"), accessibility: () => import("./ru/accessibility"),
editor: () => import("./ru/editor"), editor: () => import("./ru/editor"),
emptyState: () => import("./ru/empty-state"), "empty-state": () => import("./ru/empty-state"),
}, },
it: { it: {
translations: () => import("./it/translations"), translations: () => import("./it/translations"),
accessibility: () => import("./it/accessibility"), accessibility: () => import("./it/accessibility"),
editor: () => import("./it/editor"), editor: () => import("./it/editor"),
emptyState: () => import("./it/empty-state"), "empty-state": () => import("./it/empty-state"),
}, },
cs: { cs: {
translations: () => import("./cs/translations"), translations: () => import("./cs/translations"),
accessibility: () => import("./cs/accessibility"), accessibility: () => import("./cs/accessibility"),
editor: () => import("./cs/editor"), editor: () => import("./cs/editor"),
emptyState: () => import("./cs/empty-state"), "empty-state": () => import("./cs/empty-state"),
}, },
sk: { sk: {
translations: () => import("./sk/translations"), translations: () => import("./sk/translations"),
accessibility: () => import("./sk/accessibility"), accessibility: () => import("./sk/accessibility"),
editor: () => import("./sk/editor"), editor: () => import("./sk/editor"),
emptyState: () => import("./sk/empty-state"), "empty-state": () => import("./sk/empty-state"),
}, },
de: { de: {
translations: () => import("./de/translations"), translations: () => import("./de/translations"),
accessibility: () => import("./de/accessibility"), accessibility: () => import("./de/accessibility"),
editor: () => import("./de/editor"), editor: () => import("./de/editor"),
emptyState: () => import("./de/empty-state"), "empty-state": () => import("./de/empty-state"),
}, },
ua: { ua: {
translations: () => import("./ua/translations"), translations: () => import("./ua/translations"),
accessibility: () => import("./ua/accessibility"), accessibility: () => import("./ua/accessibility"),
editor: () => import("./ua/editor"), editor: () => import("./ua/editor"),
emptyState: () => import("./ua/empty-state"), "empty-state": () => import("./ua/empty-state"),
}, },
pl: { pl: {
translations: () => import("./pl/translations"), translations: () => import("./pl/translations"),
accessibility: () => import("./pl/accessibility"), accessibility: () => import("./pl/accessibility"),
editor: () => import("./pl/editor"), editor: () => import("./pl/editor"),
emptyState: () => import("./pl/empty-state"), "empty-state": () => import("./pl/empty-state"),
}, },
ko: { ko: {
translations: () => import("./ko/translations"), translations: () => import("./ko/translations"),
accessibility: () => import("./ko/accessibility"), accessibility: () => import("./ko/accessibility"),
editor: () => import("./ko/editor"), editor: () => import("./ko/editor"),
emptyState: () => import("./ko/empty-state"), "empty-state": () => import("./ko/empty-state"),
}, },
"pt-BR": { "pt-BR": {
translations: () => import("./pt-BR/translations"), translations: () => import("./pt-BR/translations"),
accessibility: () => import("./pt-BR/accessibility"), accessibility: () => import("./pt-BR/accessibility"),
editor: () => import("./pt-BR/editor"), editor: () => import("./pt-BR/editor"),
emptyState: () => import("./pt-BR/empty-state"), "empty-state": () => import("./pt-BR/empty-state"),
}, },
id: { id: {
translations: () => import("./id/translations"), translations: () => import("./id/translations"),
accessibility: () => import("./id/accessibility"), accessibility: () => import("./id/accessibility"),
editor: () => import("./id/editor"), editor: () => import("./id/editor"),
emptyState: () => import("./id/empty-state"), "empty-state": () => import("./id/empty-state"),
}, },
ro: { ro: {
translations: () => import("./ro/translations"), translations: () => import("./ro/translations"),
accessibility: () => import("./ro/accessibility"), accessibility: () => import("./ro/accessibility"),
editor: () => import("./ro/editor"), editor: () => import("./ro/editor"),
emptyState: () => import("./ro/empty-state"), "empty-state": () => import("./ro/empty-state"),
}, },
"vi-VN": { "vi-VN": {
translations: () => import("./vi-VN/translations"), translations: () => import("./vi-VN/translations"),
accessibility: () => import("./vi-VN/accessibility"), accessibility: () => import("./vi-VN/accessibility"),
editor: () => import("./vi-VN/editor"), editor: () => import("./vi-VN/editor"),
emptyState: () => import("./vi-VN/empty-state"), "empty-state": () => import("./vi-VN/empty-state"),
}, },
"tr-TR": { "tr-TR": {
translations: () => import("./tr-TR/translations"), translations: () => import("./tr-TR/translations"),
accessibility: () => import("./tr-TR/accessibility"), accessibility: () => import("./tr-TR/accessibility"),
editor: () => import("./tr-TR/editor"), editor: () => import("./tr-TR/editor"),
emptyState: () => import("./tr-TR/empty-state"), "empty-state": () => import("./tr-TR/empty-state"),
}, },
}; };

View file

@ -39,6 +39,7 @@ import {
DraftVerticalStackIllustration, DraftVerticalStackIllustration,
EpicVerticalStackIllustration, EpicVerticalStackIllustration,
Error404VerticalStackIllustration, Error404VerticalStackIllustration,
InitiativeVerticalStackIllustration,
InvalidLinkVerticalStackIllustration, InvalidLinkVerticalStackIllustration,
ModuleVerticalStackIllustration, ModuleVerticalStackIllustration,
NoAccessVerticalStackIllustration, NoAccessVerticalStackIllustration,
@ -85,6 +86,7 @@ export const VERTICAL_STACK_ASSETS: Record<VerticalStackAssetType, React.Compone
draft: DraftVerticalStackIllustration, draft: DraftVerticalStackIllustration,
epic: EpicVerticalStackIllustration, epic: EpicVerticalStackIllustration,
"error-404": Error404VerticalStackIllustration, "error-404": Error404VerticalStackIllustration,
initiative: InitiativeVerticalStackIllustration,
"invalid-link": InvalidLinkVerticalStackIllustration, "invalid-link": InvalidLinkVerticalStackIllustration,
module: ModuleVerticalStackIllustration, module: ModuleVerticalStackIllustration,
"no-access": NoAccessVerticalStackIllustration, "no-access": NoAccessVerticalStackIllustration,

View file

@ -32,6 +32,7 @@ export type VerticalStackAssetType =
| "draft" | "draft"
| "epic" | "epic"
| "error-404" | "error-404"
| "initiative"
| "invalid-link" | "invalid-link"
| "module" | "module"
| "no-access" | "no-access"

View file

@ -8,6 +8,7 @@ import {
DraftVerticalStackIllustration, DraftVerticalStackIllustration,
EpicVerticalStackIllustration, EpicVerticalStackIllustration,
Error404VerticalStackIllustration, Error404VerticalStackIllustration,
InitiativeVerticalStackIllustration,
InvalidLinkVerticalStackIllustration, InvalidLinkVerticalStackIllustration,
ModuleVerticalStackIllustration, ModuleVerticalStackIllustration,
NoAccessVerticalStackIllustration, NoAccessVerticalStackIllustration,
@ -56,6 +57,10 @@ export const VerticalStackAssetsMap = [
asset: <Error404VerticalStackIllustration />, asset: <Error404VerticalStackIllustration />,
title: "Error404VerticalStackIllustration", title: "Error404VerticalStackIllustration",
}, },
{
asset: <InitiativeVerticalStackIllustration />,
title: "InitiativeVerticalStackIllustration",
},
{ {
asset: <InvalidLinkVerticalStackIllustration />, asset: <InvalidLinkVerticalStackIllustration />,
title: "InvalidLinkVerticalStackIllustration", title: "InvalidLinkVerticalStackIllustration",

View file

@ -15,6 +15,7 @@ export const EmptyStateCompact: React.FC<BaseEmptyStateCommonProps> = ({
rootClassName, rootClassName,
assetClassName, assetClassName,
align = "center", align = "center",
customButton,
}) => { }) => {
// Determine which asset to use: assetKey takes precedence, fallback to custom asset // Determine which asset to use: assetKey takes precedence, fallback to custom asset
const resolvedAsset = assetKey ? getCompactAsset(assetKey as CompactAssetType, assetClassName) : asset; const resolvedAsset = assetKey ? getCompactAsset(assetKey as CompactAssetType, assetClassName) : asset;
@ -39,18 +40,21 @@ export const EmptyStateCompact: React.FC<BaseEmptyStateCommonProps> = ({
title && <p className="text-sm leading-5 text-custom-text-300">{title}</p> title && <p className="text-sm leading-5 text-custom-text-300">{title}</p>
)} )}
{actions && actions.length > 0 && ( {customButton
<div className="flex flex-col gap-4 sm:flex-row"> ? customButton
{actions.map((action, index) => { : actions &&
const { label, variant, ...rest } = action; actions.length > 0 && (
return ( <div className="flex flex-col gap-4 sm:flex-row">
<Button key={index} variant={variant} {...rest}> {actions.map((action, index) => {
{label} const { label, variant, ...rest } = action;
</Button> return (
); <Button key={index} variant={variant} {...rest}>
})} {label}
</div> </Button>
)} );
})}
</div>
)}
</div> </div>
</div> </div>
</div> </div>

View file

@ -14,6 +14,7 @@ export const EmptyStateDetailed: React.FC<BaseEmptyStateCommonProps> = ({
className, className,
rootClassName, rootClassName,
assetClassName, assetClassName,
customButton,
}) => { }) => {
// Determine which asset to use: assetKey takes precedence, fallback to custom asset // Determine which asset to use: assetKey takes precedence, fallback to custom asset
const resolvedAsset = assetKey ? getDetailedAsset(assetKey as DetailedAssetType, assetClassName) : asset; const resolvedAsset = assetKey ? getDetailedAsset(assetKey as DetailedAssetType, assetClassName) : asset;
@ -31,18 +32,21 @@ export const EmptyStateDetailed: React.FC<BaseEmptyStateCommonProps> = ({
</div> </div>
)} )}
{actions && actions.length > 0 && ( {customButton
<div className="flex flex-col gap-4 sm:flex-row"> ? customButton
{actions.map((action, index) => { : actions &&
const { label, variant, ...rest } = action; actions.length > 0 && (
return ( <div className="flex flex-col gap-4 sm:flex-row">
<Button key={index} variant={variant} {...rest}> {actions.map((action, index) => {
{label} const { label, variant, ...rest } = action;
</Button> return (
); <Button key={index} variant={variant} {...rest}>
})} {label}
</div> </Button>
)} );
})}
</div>
)}
</div> </div>
</div> </div>
</div> </div>

View file

@ -21,4 +21,5 @@ export interface BaseEmptyStateCommonProps {
assetKey?: CompactAssetType | DetailedAssetType; assetKey?: CompactAssetType | DetailedAssetType;
asset?: React.ReactNode; asset?: React.ReactNode;
align?: TAlign; align?: TAlign;
customButton?: React.ReactNode;
} }