Skip to content

Commit eb05f86

Browse files
committed
feature #3213 [Toolkit][Shadcn] add Empty component (bernard-ng)
This PR was merged into the 2.x branch. Discussion ---------- [Toolkit][Shadcn] add Empty component | Q | A | -------------- | --- | Bug fix? | no | New feature? | yes | Deprecations? | no | Documentation? | no | License | MIT Add Empty component to display an empty state. ref: https://ui.shadcn.com/docs/components/empty <img width="1104" height="396" alt="Screenshot 2025-12-05 at 01 09 23" src="https://github.com/user-attachments/assets/1bf5f7ad-7942-4cbc-a1c0-e497ae92d92a" /> <img width="1114" height="385" alt="Screenshot 2025-12-05 at 01 09 35" src="https://github.com/user-attachments/assets/3b12ccaf-708a-414e-aed4-26b478750ab4" /> <img width="1111" height="394" alt="Screenshot 2025-12-05 at 01 09 45" src="https://github.com/user-attachments/assets/47b71369-581b-4c6f-ba45-f009861a2f9f" /> <img width="1143" height="420" alt="Screenshot 2025-12-05 at 01 09 53" src="https://github.com/user-attachments/assets/aa6fb920-7132-44f1-ae91-efd6b5cee56b" /> Commits ------- ca743f9 [Toolkit][Shadcn] add Empty component
2 parents 72e0f7b + ca743f9 commit eb05f86

12 files changed

+366
-0
lines changed
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Examples
2+
3+
## Outline
4+
5+
```twig {"preview":true,"height":"280px"}
6+
<twig:Empty class="border border-dashed">
7+
<twig:Empty:Header>
8+
<twig:Empty:Media variant="icon">
9+
<twig:ux:icon name="tabler:cloud" class="size-5" />
10+
</twig:Empty:Media>
11+
<twig:Empty:Title>Cloud storage empty</twig:Empty:Title>
12+
<twig:Empty:Description>
13+
Upload files to your cloud storage to access them anywhere.
14+
</twig:Empty:Description>
15+
</twig:Empty:Header>
16+
<twig:Empty:Content>
17+
<twig:Button variant="outline" size="sm">
18+
Upload files
19+
</twig:Button>
20+
</twig:Empty:Content>
21+
</twig:Empty>
22+
```
23+
24+
## Background
25+
26+
```twig {"preview":true,"height":"280px"}
27+
<twig:Empty class="from-muted/50 to-background h-full bg-linear-to-b from-30%">
28+
<twig:Empty:Header>
29+
<twig:Empty:Media variant="icon">
30+
<twig:ux:icon name="lucide:bell" class="size-5" />
31+
</twig:Empty:Media>
32+
<twig:Empty:Title>No notifications</twig:Empty:Title>
33+
<twig:Empty:Description>
34+
You're all caught up. New notifications will appear here.
35+
</twig:Empty:Description>
36+
</twig:Empty:Header>
37+
<twig:Empty:Content>
38+
<twig:Button variant="outline" size="sm">
39+
<twig:ux:icon name="lucide:refresh-ccw" class="size-4" />
40+
Refresh
41+
</twig:Button>
42+
</twig:Empty:Content>
43+
</twig:Empty>
44+
```
45+
46+
## Avatar
47+
48+
```twig {"preview":true,"height":"280px"}
49+
<twig:Empty>
50+
<twig:Empty:Header>
51+
<twig:Empty:Media>
52+
<twig:Avatar class="size-12">
53+
<twig:Avatar:Image
54+
src="https://github.com/shadcn.png"
55+
alt="@shadcn"
56+
class="grayscale"
57+
/>
58+
<twig:Avatar:Text>LR</twig:Avatar:Text>
59+
</twig:Avatar>
60+
</twig:Empty:Media>
61+
<twig:Empty:Title>User offline</twig:Empty:Title>
62+
<twig:Empty:Description>
63+
This user is currently offline. You can leave a message to notify them or try again later.
64+
</twig:Empty:Description>
65+
</twig:Empty:Header>
66+
<twig:Empty:Content>
67+
<twig:Button size="sm">Leave message</twig:Button>
68+
</twig:Empty:Content>
69+
</twig:Empty>
70+
```
71+
72+
## Avatar group
73+
74+
```twig {"preview":true,"height":"300px"}
75+
<twig:Empty>
76+
<twig:Empty:Header>
77+
<twig:Empty:Media>
78+
<div class="*:data-[slot=avatar]:ring-background flex -space-x-2 *:data-[slot=avatar]:size-12 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale">
79+
<twig:Avatar>
80+
<twig:Avatar:Image src="https://github.com/shadcn.png" alt="@shadcn" />
81+
<twig:Avatar:Text>CN</twig:Avatar:Text>
82+
</twig:Avatar>
83+
<twig:Avatar>
84+
<twig:Avatar:Image src="https://github.com/maxleiter.png" alt="@maxleiter" />
85+
<twig:Avatar:Text>LR</twig:Avatar:Text>
86+
</twig:Avatar>
87+
<twig:Avatar>
88+
<twig:Avatar:Image src="https://github.com/evilrabbit.png" alt="@evilrabbit" />
89+
<twig:Avatar:Text>ER</twig:Avatar:Text>
90+
</twig:Avatar>
91+
</div>
92+
</twig:Empty:Media>
93+
<twig:Empty:Title>No team members</twig:Empty:Title>
94+
<twig:Empty:Description>
95+
Invite your team to collaborate on this project.
96+
</twig:Empty:Description>
97+
</twig:Empty:Header>
98+
<twig:Empty:Content>
99+
<twig:Button size="sm">
100+
<twig:ux:icon name="lucide:plus" class="size-4" />
101+
Invite members
102+
</twig:Button>
103+
</twig:Empty:Content>
104+
</twig:Empty>
105+
```
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"$schema": "../../../schema-kit-recipe-v1.json",
3+
"type": "component",
4+
"name": "Empty",
5+
"description": "Use the Empty component to display an empty state.",
6+
"copy-files": {
7+
"templates/": "templates/"
8+
},
9+
"dependencies": {
10+
"composer": ["twig/extra-bundle", "twig/html-extra:^3.12.0", "tales-from-a-dev/twig-tailwind-extra:^1.0.0"]
11+
}
12+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{# @block content The default block #}
2+
<div
3+
data-slot="empty"
4+
class="{{ 'flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border-dashed p-6 text-center text-balance md:p-12 ' ~ attributes.render('class')|tailwind_merge }}"
5+
{{ attributes }}
6+
>
7+
{%- block content %}{% endblock -%}
8+
</div>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{# @block content The default block #}
2+
<div
3+
data-slot="empty-content"
4+
class="{{ 'flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance ' ~ attributes.render('class')|tailwind_merge }}"
5+
{{ attributes }}
6+
>
7+
{%- block content %}{% endblock -%}
8+
</div>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{# @block content The default block #}
2+
<div
3+
data-slot="empty-description"
4+
class="{{ 'text-muted-foreground [&>a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4 ' ~ attributes.render('class')|tailwind_merge }}"
5+
{{ attributes }}
6+
>
7+
{%- block content %}{% endblock -%}
8+
</div>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{# @block content The default block #}
2+
<div
3+
data-slot="empty-header"
4+
class="{{ 'flex max-w-sm flex-col items-center gap-2 text-center ' ~ attributes.render('class')|tailwind_merge }}"
5+
{{ attributes }}
6+
>
7+
{%- block content %}{% endblock -%}
8+
</div>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{# @prop variant 'default'|'icon' The variant, default to `default` #}
2+
{# @block content The default block #}
3+
{%- props variant = 'default' -%}
4+
{%- set style = html_cva(
5+
base: 'flex shrink-0 items-center justify-center mb-2 [&_svg]:pointer-events-none [&_svg]:shrink-0',
6+
variants: {
7+
variant: {
8+
default: 'bg-transparent',
9+
icon: "bg-muted text-foreground flex size-10 shrink-0 items-center justify-center rounded-lg [&_svg:not([class*='size-'])]:size-6",
10+
},
11+
},
12+
default_variant: {
13+
variant: 'default',
14+
},
15+
) -%}
16+
17+
<div
18+
data-slot="empty-icon"
19+
data-variant="{{ variant }}"
20+
class="{{ style.apply({variant: variant}, attributes.render('class'))|tailwind_merge }}"
21+
{{ attributes }}
22+
>
23+
{%- block content %}{% endblock -%}
24+
</div>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{# @block content The default block #}
2+
<div
3+
data-slot="empty-title"
4+
class="{{ 'text-lg font-medium tracking-tight ' ~ attributes.render('class')|tailwind_merge }}"
5+
{{ attributes }}
6+
>
7+
{%- block content %}{% endblock -%}
8+
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<!--
2+
- Kit: Shadcn UI
3+
- Component: Empty
4+
- Code:
5+
```twig
6+
<twig:Empty class="border border-dashed">
7+
<twig:Empty:Header>
8+
<twig:Empty:Media variant="icon">
9+
<twig:ux:icon name="tabler:cloud" class="size-5" />
10+
</twig:Empty:Media>
11+
<twig:Empty:Title>Cloud storage empty</twig:Empty:Title>
12+
<twig:Empty:Description>
13+
Upload files to your cloud storage to access them anywhere.
14+
</twig:Empty:Description>
15+
</twig:Empty:Header>
16+
<twig:Empty:Content>
17+
<twig:Button variant="outline" size="sm">
18+
Upload files
19+
</twig:Button>
20+
</twig:Empty:Content>
21+
</twig:Empty>
22+
```
23+
- Rendered code (prettified for testing purposes, run "php vendor/bin/phpunit -d --update-snapshots" to update snapshots): -->
24+
<div data-slot="empty" class="flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border-dashed p-6 text-center text-balance md:p-12 border border-dashed">
25+
<div data-slot="empty-header" class="flex max-w-sm flex-col items-center gap-2 text-center ">
26+
<div data-slot="empty-icon" data-variant="icon" class="flex shrink-0 items-center justify-center mb-2 [&amp;_svg]:pointer-events-none [&amp;_svg]:shrink-0 bg-muted text-foreground size-10 rounded-lg [&amp;_svg:not([class*='size-'])]:size-6">
27+
<svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" fill="currentColor" class="size-5" aria-hidden="true"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6.657 18C4.085 18 2 15.993 2 13.517s2.085-4.482 4.657-4.482c.393-1.762 1.794-3.2 3.675-3.773c1.88-.572 3.956-.193 5.444 1c1.488 1.19 2.162 3.007 1.77 4.769h.99c1.913 0 3.464 1.56 3.464 3.486s-1.551 3.487-3.465 3.487H6.657"></path></svg>
28+
</div>
29+
<div data-slot="empty-title" class="text-lg font-medium tracking-tight ">Cloud storage empty</div>
30+
<div data-slot="empty-description" class="text-muted-foreground [&amp;&gt;a:hover]:text-primary text-sm/relaxed [&amp;&gt;a]:underline [&amp;&gt;a]:underline-offset-4 ">Upload files to your cloud storage to access them anywhere.
31+
</div>
32+
</div>
33+
<div data-slot="empty-content" class="flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance ">
34+
<button data-slot="button" class="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&amp;_svg]:pointer-events-none [&amp;_svg:not([class*='size-'])]:size-4 shrink-0 [&amp;_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 h-8 gap-1.5 px-3 has-[&gt;svg]:px-2.5">Upload files
35+
</button>
36+
</div>
37+
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<!--
2+
- Kit: Shadcn UI
3+
- Component: Empty
4+
- Code:
5+
```twig
6+
<twig:Empty class="from-muted/50 to-background h-full bg-linear-to-b from-30%">
7+
<twig:Empty:Header>
8+
<twig:Empty:Media variant="icon">
9+
<twig:ux:icon name="lucide:bell" class="size-5" />
10+
</twig:Empty:Media>
11+
<twig:Empty:Title>No notifications</twig:Empty:Title>
12+
<twig:Empty:Description>
13+
You're all caught up. New notifications will appear here.
14+
</twig:Empty:Description>
15+
</twig:Empty:Header>
16+
<twig:Empty:Content>
17+
<twig:Button variant="outline" size="sm">
18+
<twig:ux:icon name="lucide:refresh-ccw" class="size-4" />
19+
Refresh
20+
</twig:Button>
21+
</twig:Empty:Content>
22+
</twig:Empty>
23+
```
24+
- Rendered code (prettified for testing purposes, run "php vendor/bin/phpunit -d --update-snapshots" to update snapshots): -->
25+
<div data-slot="empty" class="flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border-dashed p-6 text-center text-balance md:p-12 from-muted/50 to-background h-full bg-linear-to-b from-30%">
26+
<div data-slot="empty-header" class="flex max-w-sm flex-col items-center gap-2 text-center ">
27+
<div data-slot="empty-icon" data-variant="icon" class="flex shrink-0 items-center justify-center mb-2 [&amp;_svg]:pointer-events-none [&amp;_svg]:shrink-0 bg-muted text-foreground size-10 rounded-lg [&amp;_svg:not([class*='size-'])]:size-6">
28+
<svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" fill="currentColor" class="size-5" aria-hidden="true"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.268 21a2 2 0 0 0 3.464 0m-10.47-5.674A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326"></path></svg>
29+
</div>
30+
<div data-slot="empty-title" class="text-lg font-medium tracking-tight ">No notifications</div>
31+
<div data-slot="empty-description" class="text-muted-foreground [&amp;&gt;a:hover]:text-primary text-sm/relaxed [&amp;&gt;a]:underline [&amp;&gt;a]:underline-offset-4 ">You're all caught up. New notifications will appear here.
32+
</div>
33+
</div>
34+
<div data-slot="empty-content" class="flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance ">
35+
<button data-slot="button" class="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&amp;_svg]:pointer-events-none [&amp;_svg:not([class*='size-'])]:size-4 shrink-0 [&amp;_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 h-8 gap-1.5 px-3 has-[&gt;svg]:px-2.5"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24" fill="currentColor" class="size-4" aria-hidden="true"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M21 12a9 9 0 0 0-9-9a9.75 9.75 0 0 0-6.74 2.74L3 8"></path><path d="M3 3v5h5m-5 4a9 9 0 0 0 9 9a9.75 9.75 0 0 0 6.74-2.74L21 16"></path><path d="M16 16h5v5"></path></g></svg>
36+
Refresh
37+
</button>
38+
</div>
39+
</div>

0 commit comments

Comments
 (0)