fix: add actions to empty state (#986)

This commit is contained in:
Guy Ben-Aharon
2025-11-27 14:40:55 +02:00
committed by GitHub
parent 34b1a60737
commit de5f17266d
6 changed files with 150 additions and 7 deletions

View File

@@ -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 (
<div
ref={ref}
@@ -50,11 +87,7 @@ export const EmptyState = forwardRef<
<EmptyMedia variant="icon">
{/* <Group /> */}
<img
src={
effectiveTheme === 'dark'
? EmptyStateImageDark
: EmptyStateImage
}
src={emptyStateImage}
alt="Empty state"
className={cn('p-2', imageClassName)}
/>
@@ -66,7 +99,66 @@ export const EmptyState = forwardRef<
{description}
</EmptyDescription>
</EmptyHeader>
<EmptyContent />
{/* Action buttons section */}
{hasActions && (
<EmptyContent>
<div className="flex gap-2">
{primaryAction && (
<Button
onClick={primaryAction.onClick}
disabled={primaryAction.disabled}
className="h-8 font-normal"
>
{primaryAction.label}
{primaryAction.icon}
</Button>
)}
{secondaryAction && (
<Button
variant="outline"
onClick={secondaryAction.onClick}
disabled={secondaryAction.disabled}
className="h-8 font-normal"
>
{secondaryAction.label}
{secondaryAction.icon}
</Button>
)}
</div>
</EmptyContent>
)}
{/* Footer action link */}
{hasFooterAction && footerAction && (
<Button
variant="link"
asChild={!!footerAction.href}
className="text-muted-foreground"
size="sm"
disabled={footerAction.disabled}
onClick={
!footerAction.href
? footerAction.onClick
: undefined
}
>
{footerAction.href ? (
<a href={footerAction.href}>
{footerAction.label}
{footerAction.icon}
</a>
) : (
<span>
{footerAction.label}
{footerAction.icon}
</span>
)}
</Button>
)}
{/* Render empty content if no actions */}
{!hasActions && !hasFooterAction && <EmptyContent />}
</Empty>
</div>
);

View File

@@ -73,6 +73,14 @@ export const CustomTypesSection: React.FC<CustomTypesSectionProps> = () => {
'side_panel.custom_types_section.empty_state.description'
)}
className="mt-20"
secondaryAction={
!readonly
? {
label: 'New Type',
onClick: handleCreateCustomType,
}
: undefined
}
/>
) : filterText && filteredCustomTypes.length === 0 ? (
<div className="mt-10 flex flex-col items-center gap-2">

View File

@@ -216,6 +216,16 @@ export const RefsSection: React.FC<RefsSectionProps> = () => {
'side_panel.refs_section.empty_state.description'
)}
className="mt-20"
secondaryAction={
!readonly
? {
label: t(
'side_panel.refs_section.add_relationship'
),
onClick: handleCreateRelationship,
}
: undefined
}
/>
) : (
<RefsList refs={filteredRefs} />

View File

@@ -184,6 +184,19 @@ export const TablesSection: React.FC<TablesSectionProps> = () => {
'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 ? (
<div className="mt-10 flex flex-col items-center gap-2">

View File

@@ -92,6 +92,16 @@ export const AreasTab: React.FC<AreasTabProps> = () => {
'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 ? (
<div className="mt-10 flex flex-col items-center gap-2">

View File

@@ -92,6 +92,16 @@ export const NotesTab: React.FC<NotesTabProps> = () => {
'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 ? (
<div className="mt-10 flex flex-col items-center gap-2">