mirror of
https://github.com/chartdb/chartdb.git
synced 2026-02-09 13:14:31 -06:00
fix: add actions to empty state (#986)
This commit is contained in:
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user