diff --git a/apps/app/src/components/decisions/EmptyProposalsState.tsx b/apps/app/src/components/decisions/EmptyProposalsState.tsx
deleted file mode 100644
index aaaa03707..000000000
--- a/apps/app/src/components/decisions/EmptyProposalsState.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-'use client';
-
-import { ReactNode } from 'react';
-import { LuLeaf } from 'react-icons/lu';
-
-export function EmptyProposalsState({ children }: { children: ReactNode }) {
- return (
-
- );
-}
diff --git a/apps/app/src/components/decisions/MyBallot.tsx b/apps/app/src/components/decisions/MyBallot.tsx
index ca704b060..f93568f73 100644
--- a/apps/app/src/components/decisions/MyBallot.tsx
+++ b/apps/app/src/components/decisions/MyBallot.tsx
@@ -3,11 +3,12 @@
import { useUser } from '@/utils/UserProvider';
import { trpc } from '@op/api/client';
import { Checkbox } from '@op/ui/Checkbox';
+import { EmptyState } from '@op/ui/EmptyState';
import { Header3 } from '@op/ui/Header';
+import { LuLeaf } from 'react-icons/lu';
import { useTranslations } from '@/lib/i18n';
-import { EmptyProposalsState } from './EmptyProposalsState';
import {
ProposalCardContent,
ProposalCardDescription,
@@ -20,11 +21,11 @@ import { VotingProposalCard } from './VotingProposalCard';
export const NoVoteFound = () => {
const t = useTranslations();
return (
-
+ }>
{t('You did not vote in this process.')}
-
+
);
};
diff --git a/apps/app/src/components/decisions/ProposalsList.tsx b/apps/app/src/components/decisions/ProposalsList.tsx
index 6059e32ab..c8183a716 100644
--- a/apps/app/src/components/decisions/ProposalsList.tsx
+++ b/apps/app/src/components/decisions/ProposalsList.tsx
@@ -7,6 +7,7 @@ import { match } from '@op/core';
import { Button, ButtonLink } from '@op/ui/Button';
import { Checkbox } from '@op/ui/Checkbox';
import { Dialog, DialogTrigger } from '@op/ui/Dialog';
+import { EmptyState } from '@op/ui/EmptyState';
import { Header3 } from '@op/ui/Header';
import { Modal } from '@op/ui/Modal';
import { Select, SelectItem } from '@op/ui/Select';
@@ -14,13 +15,12 @@ import { Skeleton } from '@op/ui/Skeleton';
import { Surface } from '@op/ui/Surface';
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
import { useEffect, useMemo, useState } from 'react';
-import { LuArrowDownToLine } from 'react-icons/lu';
+import { LuArrowDownToLine, LuLeaf } from 'react-icons/lu';
import type { z } from 'zod';
import { useTranslations } from '@/lib/i18n';
import { Bullet } from '../Bullet';
-import { EmptyProposalsState } from './EmptyProposalsState';
import {
ProposalCard,
ProposalCardActions,
@@ -113,14 +113,14 @@ export const ProposalListSkeleton = () => {
const NoProposalsFound = () => {
const t = useTranslations();
return (
-
+ }>
{t('No proposals found matching the current filters.')}
{t('Try adjusting your filter selection above.')}
-
+
);
};
diff --git a/apps/app/src/components/decisions/ResultsList.tsx b/apps/app/src/components/decisions/ResultsList.tsx
index ac3704988..6362957de 100644
--- a/apps/app/src/components/decisions/ResultsList.tsx
+++ b/apps/app/src/components/decisions/ResultsList.tsx
@@ -1,11 +1,12 @@
'use client';
import { trpc } from '@op/api/client';
+import { EmptyState } from '@op/ui/EmptyState';
import { Header3 } from '@op/ui/Header';
+import { LuLeaf } from 'react-icons/lu';
import { useTranslations } from '@/lib/i18n';
-import { EmptyProposalsState } from './EmptyProposalsState';
import {
ProposalCard,
ProposalCardContent,
@@ -18,14 +19,14 @@ import {
const NoProposalsFound = () => {
const t = useTranslations();
return (
-
+ }>
{t('No results yet for this decision.')}
{t('Results are still being worked on.')}
-
+
);
};
diff --git a/apps/app/src/components/decisions/pages/ResultsPage.tsx b/apps/app/src/components/decisions/pages/ResultsPage.tsx
index 1f89e5650..2ca04fa86 100644
--- a/apps/app/src/components/decisions/pages/ResultsPage.tsx
+++ b/apps/app/src/components/decisions/pages/ResultsPage.tsx
@@ -3,9 +3,11 @@
import { APIErrorBoundary } from '@/utils/APIErrorBoundary';
import { trpc } from '@op/api/client';
import { match } from '@op/core';
+import { EmptyState } from '@op/ui/EmptyState';
import { Header3 } from '@op/ui/Header';
import { Skeleton } from '@op/ui/Skeleton';
import { Suspense } from 'react';
+import { LuLeaf } from 'react-icons/lu';
import { useTranslations } from '@/lib/i18n/routing';
@@ -15,7 +17,6 @@ import {
DecisionResultsTabPanel,
DecisionResultsTabs,
} from '../DecisionResultsTabs';
-import { EmptyProposalsState } from '../EmptyProposalsState';
import { MyBallot, NoVoteFound } from '../MyBallot';
import { ProposalListSkeleton } from '../ProposalsList';
import { ResultsList } from '../ResultsList';
@@ -146,14 +147,14 @@ function ResultsPageContent({
+ }>
{t('Results are still being processed.')}
{t('Check back again shortly for the results.')}
-
+
),
}}
>
diff --git a/apps/app/src/components/decisions/pages/StandardDecisionPage.tsx b/apps/app/src/components/decisions/pages/StandardDecisionPage.tsx
index 51edb68fa..e45c5c74a 100644
--- a/apps/app/src/components/decisions/pages/StandardDecisionPage.tsx
+++ b/apps/app/src/components/decisions/pages/StandardDecisionPage.tsx
@@ -3,14 +3,15 @@
import { getUniqueSubmitters } from '@/utils/proposalUtils';
import { trpc } from '@op/api/client';
import { match } from '@op/core';
+import { EmptyState } from '@op/ui/EmptyState';
import { Header3 } from '@op/ui/Header';
import { Suspense } from 'react';
+import { LuLeaf } from 'react-icons/lu';
import { useTranslations } from '@/lib/i18n/routing';
import { DecisionActionBar } from '../DecisionActionBar';
import { DecisionHero } from '../DecisionHero';
-import { EmptyProposalsState } from '../EmptyProposalsState';
import { MemberParticipationFacePile } from '../MemberParticipationFacePile';
import { ProposalListSkeleton, ProposalsList } from '../ProposalsList';
@@ -135,14 +136,14 @@ export function StandardDecisionPage({
{proposals.length === 0 ? (
-
+ }>
{t('No proposals yet')}
{t('You could be the first one to submit a proposal')}
-
+
) : (
}>
{
+ return (
+
+
+
+ {icon ?? }
+
+ {children}
+
+
+ );
+};
diff --git a/packages/ui/src/components/ui/table.tsx b/packages/ui/src/components/ui/table.tsx
index 5084666e0..913f9f45c 100644
--- a/packages/ui/src/components/ui/table.tsx
+++ b/packages/ui/src/components/ui/table.tsx
@@ -27,9 +27,8 @@ import {
import { LuArrowDown } from 'react-icons/lu';
import { twJoin, twMerge } from 'tailwind-merge';
-import { cx } from '@/lib/primitive';
-
-import { Checkbox } from '@/components/Checkbox';
+import { cx } from '../../lib/primitive';
+import { Checkbox } from '../Checkbox';
interface TableProps extends Omit {
allowResize?: boolean;
diff --git a/packages/ui/stories/EmptyState.stories.tsx b/packages/ui/stories/EmptyState.stories.tsx
new file mode 100644
index 000000000..c1ae001f0
--- /dev/null
+++ b/packages/ui/stories/EmptyState.stories.tsx
@@ -0,0 +1,73 @@
+import { LuFileText, LuInbox, LuSearch, LuUsers } from 'react-icons/lu';
+
+import { Button } from '../src/components/Button';
+import { EmptyState } from '../src/components/EmptyState';
+
+export default {
+ title: 'EmptyState',
+ component: EmptyState,
+ parameters: {
+ layout: 'centered',
+ },
+ tags: ['autodocs'],
+};
+
+export const Default = () => (
+
+ No items found
+
+);
+
+export const WithCustomIcon = () => (
+ }>
+ Your inbox is empty
+
+);
+
+export const WithDescription = () => (
+ }>
+ No results found
+ Try adjusting your search or filters
+
+);
+
+export const WithAction = () => (
+ }>
+ No team members
+
+ Get started by inviting your first team member
+
+
+
+);
+
+export const Examples = () => (
+
+
+
+
+
}>
+
Your inbox is empty
+
+
+
+
+
}>
+
No results found
+
Try adjusting your search
+
+
+
+
+
}>
+
No documents
+
Upload your first document
+
+
+
+
+);