Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions components/retroui/Empty.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { Text } from "@/components/retroui/Text";
import { cn } from "@/lib/utils";
import { Ghost } from "lucide-react";
import { HTMLAttributes } from "react";

interface IEmptyProps extends HTMLAttributes<HTMLDivElement> {
className?: string;
}

const Empty = ({ className, ...props }: IEmptyProps) => {
return (
<div
className={cn(
"flex flex-col items-center justify-center p-4 md:p-8 border-2 rounded shadow-md transition-all hover:shadow-none bg-card text-center",
className,
)}
{...props}
/>
);
};
Empty.displayName = "Empty";

const EmptyContent = ({ className, ...props }: IEmptyProps) => {
return (
<div
className={cn("flex flex-col items-center gap-3", className)}
{...props}
/>
);
};
EmptyContent.displayName = "Empty.Content";

const EmptyIcon = ({ children, className, ...props }: IEmptyProps) => {
return (
<div className={cn(className)} {...props}>
{children || <Ghost className="w-full h-full" />}
</div>
);
};
EmptyIcon.displayName = "Empty.Icon";

const EmptyTitle = ({ className, ...props }: IEmptyProps) => {
return (
<Text
as="h3"
className={cn("text-lg md:text-2xl font-bold", className)}
{...props}
/>
);
};
EmptyTitle.displayName = "Empty.Title";

const EmptySeparator = ({ className, ...props }: IEmptyProps) => {
return <div role="separator" className={cn("w-full h-1 bg-primary", className)} {...props} />;
};
EmptySeparator.displayName = "Empty.Separator";

const EmptyDescription = ({
className,
...props
}: HTMLAttributes<HTMLParagraphElement>) => (
<p
className={cn("text-muted-foreground max-w-[320px]", className)}
{...props}
/>
);
EmptyDescription.displayName = "Empty.Description";

const EmptyComponent = Object.assign(Empty, {
Content: EmptyContent,
Icon: EmptyIcon,
Title: EmptyTitle,
Separator: EmptySeparator,
Description: EmptyDescription,
});

export { EmptyComponent as Empty };
4 changes: 2 additions & 2 deletions components/retroui/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ export * from "./Radio";
export * from "./Select";
export * from "./Switch";
export * from "./Label";
export * from "./Input";
export * from "./Text";
export * from "./Accordion";
export * from "./Alert";
Expand All @@ -26,4 +25,5 @@ export * from "./Breadcrumb";
export * from "./CommandDisplay";
export * from "./Command";
export * from "./Loader";
export * from "./ContextMenu";
export * from "./ContextMenu";
export * from "./Empty";
25 changes: 24 additions & 1 deletion config/components.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { table } from "console";
import { lazy } from "react";

export const componentConfig: {
Expand Down Expand Up @@ -84,6 +83,10 @@ export const componentConfig: {
name: "drawer",
filePath: "components/retroui/Drawer.tsx",
},
empty: {
name: "empty",
filePath: "components/retroui/Empty.tsx",
},
input: {
name: "input",
filePath: "components/retroui/Input.tsx",
Expand Down Expand Up @@ -716,5 +719,25 @@ export const componentConfig: {
filePath: "preview/components/typography-p.tsx",
preview: lazy(() => import("@/preview/components/typography-p")),
},
"empty-style-default": {
name: "empty-style-default",
filePath: "preview/components/empty-style-default.tsx",
preview: lazy(() => import("@/preview/components/empty-style-default"))
},
"empty-style-custom-icon": {
name: "empty-style-custom-icon",
filePath: "preview/components/empty-style-custom-icon.tsx",
preview: lazy(() => import("@/preview/components/empty-style-custom-icon"))
},
"empty-style-custom-everything": {
name: "empty-style-custom-everything",
filePath: "preview/components/empty-style-custom-everything.tsx",
preview: lazy(() => import("@/preview/components/empty-style-custom-everything"))
},
"empty-style-table": {
name: "empty-style-table",
filePath: "preview/components/empty-style-table.tsx",
preview: lazy(() => import("@/preview/components/empty-style-table"))
},
},
};
1 change: 1 addition & 0 deletions config/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export const navConfig: INavigationConfig = {
{ title: "Command", href: `${componentsRoute}/command` },
{ title: "Dialog", href: `${componentsRoute}/dialog` },
{ title: "Drawer", href: `${componentsRoute}/drawer`, tag: "New" },
{ title: "Empty", href: `${componentsRoute}/empty`, tag: "New" },
{ title: "Input", href: `${componentsRoute}/input` },
{ title: "Label", href: `${componentsRoute}/label` },
{ title: "Loader", href: `${componentsRoute}/loader` },
Expand Down
70 changes: 70 additions & 0 deletions content/docs/components/empty.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
title: Empty
description: The component that shows when there is no data to show!
lastUpdated: 13 Jan, 2026
links:
source: https://github.com/Logging-Stuff/RetroUI/blob/main/components/retroui/Empty.tsx
---

<ComponentShowcase name="empty-style-default" />

<br />
<br />

## Installation

<ComponentInstall>
<ComponentInstall.Cli npmCommand="npx shadcn@latest add @retroui/empty" />
<ComponentInstall.Manual>

#### Copy the code 👇 into your project:

<ComponentSource name="empty" />
</ComponentInstall.Manual>
</ComponentInstall>

<br />
<br />

## Examples

### Default

<ComponentShowcase name="empty-style-default" />

<br />
<br />

### Custom Icon

<ComponentShowcase name="empty-style-custom-icon" />

<br />
<br />

### Customize everything

<ComponentShowcase name="empty-style-custom-everything" />

<br />
<br />

### Using with table

<ComponentShowcase name="empty-style-table" />

<br />
<br />

## API Reference

The Empty component is composed of several sub-components:

<br />

- `Empty` - The main component wrapper
- `Empty.Content` - Wrapper for the content elements
- `Empty.Icon` - Section for displaying an icon
- `Empty.Title` - The main heading for the empty state
- `Empty.Separator` - A visual separator line
- `Empty.Description` - Supporting text for the empty state
20 changes: 20 additions & 0 deletions preview/components/empty-style-custom-everything.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Button, Empty } from "@/components/retroui";
import { InboxIcon } from "lucide-react";


export default function CustomEverythingEmpty() {
return (
<Empty>
<Empty.Content>
<Empty.Icon>
<InboxIcon className="size-10 md:size-12" />
</Empty.Icon>
<Empty.Title>No data</Empty.Title>
<Empty.Description>
Get started by creating your first item
</Empty.Description>
<Button>Create now</Button>
</Empty.Content>
</Empty>
);
}
19 changes: 19 additions & 0 deletions preview/components/empty-style-custom-icon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Empty } from "@/components/retroui/Empty";

export default function CustomIconEmpty() {
return (
<Empty>
<Empty.Content>
<Empty.Icon>
<span className="text-4xl">👻</span>
</Empty.Icon>
<Empty.Title>No data</Empty.Title>
<Empty.Separator />
<Empty.Description>
There is nothing to show here yet. Imagine you wrote some good stuff
here.
</Empty.Description>
</Empty.Content>
</Empty>
);
}
16 changes: 16 additions & 0 deletions preview/components/empty-style-default.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Empty } from "@/components/retroui";

export default function DefaultEmpty() {
return (
<Empty>
<Empty.Content>
<Empty.Icon className="size-10 md:size-12" />
<Empty.Title>No Results</Empty.Title>
<Empty.Separator />
<Empty.Description>
Your search didn't match any items. Try adjusting your filters.
</Empty.Description>
</Empty.Content>
</Empty>
);
}
39 changes: 39 additions & 0 deletions preview/components/empty-style-table.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Empty } from "@/components/retroui/Empty";

import { Table } from "@/components/retroui/Table";

const invoices = [];

export default function TableEmpty() {
return (
<Table className="mx-auto">
<Table.Header>
<Table.Row>
<Table.Head className="w-[100px]">Invoice</Table.Head>
<Table.Head>Customer</Table.Head>
<Table.Head>Status</Table.Head>
<Table.Head>Method</Table.Head>
<Table.Head className="text-right">Amount</Table.Head>
</Table.Row>
</Table.Header>

<Table.Body>
{invoices?.length === 0 && (
<Table.Row>
<Table.Cell colSpan={5}>
<Empty className="border-none shadow-none">
<Empty.Content>
<Empty.Icon className="size-8" />
<Empty.Title className="md:text-base">Empty</Empty.Title>
<Empty.Description>
Get started by creating your first invoice.
</Empty.Description>
</Empty.Content>
</Empty>
</Table.Cell>
</Table.Row>
)}
</Table.Body>
</Table>
);
}