From de5f17266d384ae2decdf556cd8581e3436bc689 Mon Sep 17 00:00:00 2001 From: Guy Ben-Aharon Date: Thu, 27 Nov 2025 14:40:55 +0200 Subject: [PATCH] fix: add actions to empty state (#986) --- src/components/empty-state/empty-state.tsx | 106 ++++++++++++++++-- .../custom-types-section.tsx | 8 ++ .../side-panel/refs-section/refs-section.tsx | 10 ++ .../tables-section/tables-section.tsx | 13 +++ .../visuals-section/areas-tab/areas-tab.tsx | 10 ++ .../visuals-section/notes-tab/notes-tab.tsx | 10 ++ 6 files changed, 150 insertions(+), 7 deletions(-) diff --git a/src/components/empty-state/empty-state.tsx b/src/components/empty-state/empty-state.tsx index 3d7186cd..5e08f57b 100644 --- a/src/components/empty-state/empty-state.tsx +++ b/src/components/empty-state/empty-state.tsx @@ -1,4 +1,4 @@ -import React, { forwardRef } from 'react'; +import React, { forwardRef, useMemo } from 'react'; import EmptyStateImage from '@/assets/empty_state.png'; import EmptyStateImageDark from '@/assets/empty_state_dark.png'; import { cn } from '@/lib/utils'; @@ -11,6 +11,22 @@ import { EmptyMedia, EmptyTitle, } from '../empty/empty'; +import { Button } from '../button/button'; + +export interface EmptyStateActionButton { + label: string; + onClick?: () => void; + icon?: React.ReactNode; + disabled?: boolean; +} + +export interface EmptyStateFooterAction { + label: string; + href?: string; + onClick?: () => void; + icon?: React.ReactNode; + disabled?: boolean; +} export interface EmptyStateProps { title: string; @@ -18,6 +34,9 @@ export interface EmptyStateProps { imageClassName?: string; titleClassName?: string; descriptionClassName?: string; + primaryAction?: EmptyStateActionButton; + secondaryAction?: EmptyStateActionButton; + footerAction?: EmptyStateFooterAction; } export const EmptyState = forwardRef< @@ -32,11 +51,29 @@ export const EmptyState = forwardRef< titleClassName, descriptionClassName, imageClassName, + primaryAction, + secondaryAction, + footerAction, }, ref ) => { const { effectiveTheme } = useTheme(); + // Determine if we have any actions to show + const hasActions = useMemo( + () => !!(primaryAction || secondaryAction), + [primaryAction, secondaryAction] + ); + const hasFooterAction = useMemo(() => !!footerAction, [footerAction]); + + const emptyStateImage = useMemo( + () => + effectiveTheme === 'dark' + ? EmptyStateImageDark + : EmptyStateImage, + [effectiveTheme] + ); + return (
{/* */} Empty state @@ -66,7 +99,66 @@ export const EmptyState = forwardRef< {description} - + + {/* Action buttons section */} + {hasActions && ( + +
+ {primaryAction && ( + + )} + {secondaryAction && ( + + )} +
+
+ )} + + {/* Footer action link */} + {hasFooterAction && footerAction && ( + + )} + + {/* Render empty content if no actions */} + {!hasActions && !hasFooterAction && }
); diff --git a/src/pages/editor-page/side-panel/custom-types-section/custom-types-section.tsx b/src/pages/editor-page/side-panel/custom-types-section/custom-types-section.tsx index 6040ebaf..96bcf385 100644 --- a/src/pages/editor-page/side-panel/custom-types-section/custom-types-section.tsx +++ b/src/pages/editor-page/side-panel/custom-types-section/custom-types-section.tsx @@ -73,6 +73,14 @@ export const CustomTypesSection: React.FC = () => { 'side_panel.custom_types_section.empty_state.description' )} className="mt-20" + secondaryAction={ + !readonly + ? { + label: 'New Type', + onClick: handleCreateCustomType, + } + : undefined + } /> ) : filterText && filteredCustomTypes.length === 0 ? (
diff --git a/src/pages/editor-page/side-panel/refs-section/refs-section.tsx b/src/pages/editor-page/side-panel/refs-section/refs-section.tsx index 8fa68b24..6045405c 100644 --- a/src/pages/editor-page/side-panel/refs-section/refs-section.tsx +++ b/src/pages/editor-page/side-panel/refs-section/refs-section.tsx @@ -216,6 +216,16 @@ export const RefsSection: React.FC = () => { 'side_panel.refs_section.empty_state.description' )} className="mt-20" + secondaryAction={ + !readonly + ? { + label: t( + 'side_panel.refs_section.add_relationship' + ), + onClick: handleCreateRelationship, + } + : undefined + } /> ) : ( diff --git a/src/pages/editor-page/side-panel/tables-section/tables-section.tsx b/src/pages/editor-page/side-panel/tables-section/tables-section.tsx index 44c3faae..1bf9a5aa 100644 --- a/src/pages/editor-page/side-panel/tables-section/tables-section.tsx +++ b/src/pages/editor-page/side-panel/tables-section/tables-section.tsx @@ -184,6 +184,19 @@ export const TablesSection: React.FC = () => { 'side_panel.tables_section.empty_state.description' )} className="mt-20" + secondaryAction={ + !readonly + ? { + label: t( + 'side_panel.tables_section.add_table' + ), + onClick: () => + handleCreateTable({ + view: false, + }), + } + : undefined + } /> ) : filterText && filteredTables.length === 0 ? (
diff --git a/src/pages/editor-page/side-panel/visuals-section/areas-tab/areas-tab.tsx b/src/pages/editor-page/side-panel/visuals-section/areas-tab/areas-tab.tsx index 3973db05..1bdd0981 100644 --- a/src/pages/editor-page/side-panel/visuals-section/areas-tab/areas-tab.tsx +++ b/src/pages/editor-page/side-panel/visuals-section/areas-tab/areas-tab.tsx @@ -92,6 +92,16 @@ export const AreasTab: React.FC = () => { 'side_panel.areas_section.empty_state.description' )} className="mt-20" + secondaryAction={ + !readonly + ? { + label: t( + 'side_panel.areas_section.add_area' + ), + onClick: handleCreateArea, + } + : undefined + } /> ) : filterText && filteredAreas.length === 0 ? (
diff --git a/src/pages/editor-page/side-panel/visuals-section/notes-tab/notes-tab.tsx b/src/pages/editor-page/side-panel/visuals-section/notes-tab/notes-tab.tsx index ac449632..9a04963b 100644 --- a/src/pages/editor-page/side-panel/visuals-section/notes-tab/notes-tab.tsx +++ b/src/pages/editor-page/side-panel/visuals-section/notes-tab/notes-tab.tsx @@ -92,6 +92,16 @@ export const NotesTab: React.FC = () => { 'side_panel.notes_section.empty_state.description' )} className="mt-20" + secondaryAction={ + !readonly + ? { + label: t( + 'side_panel.notes_section.add_note' + ), + onClick: handleCreateNote, + } + : undefined + } /> ) : filterText && filteredNotes.length === 0 ? (