feat: add empty state UI components for enhanced user experience

- Introduced multiple components: Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, and EmptyTitle.
- Each component is designed to provide a consistent empty state layout with customizable class props.
- Utilized utility functions for class management to ensure styling flexibility.
This commit is contained in:
Yoga Pangestu 2026-07-24 09:56:08 +07:00
parent 0c732e71cf
commit fc3f939f66
6 changed files with 118 additions and 0 deletions

View File

@ -0,0 +1,20 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>
<template>
<div
data-slot="empty"
:class="cn(
'flex min-w-0 flex-1 flex-col items-center justify-center gap-6 text-balance rounded-lg border-dashed p-6 text-center md:p-12',
props.class,
)"
>
<slot />
</div>
</template>

View File

@ -0,0 +1,20 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>
<template>
<div
data-slot="empty-content"
:class="cn(
'flex w-full min-w-0 max-w-sm flex-col items-center gap-4 text-balance text-sm',
props.class,
)"
>
<slot />
</div>
</template>

View File

@ -0,0 +1,20 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>
<template>
<p
data-slot="empty-description"
:class="cn(
'text-muted-foreground [&>a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4',
$attrs.class ?? '',
)"
>
<slot />
</p>
</template>

View File

@ -0,0 +1,20 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>
<template>
<div
data-slot="empty-header"
:class="cn(
'flex max-w-sm flex-col items-center gap-2 text-center',
props.class,
)"
>
<slot />
</div>
</template>

View File

@ -0,0 +1,21 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import type { EmptyMediaVariants } from "."
import { cn } from "@/lib/utils"
import { emptyMediaVariants } from "."
const props = defineProps<{
class?: HTMLAttributes["class"]
variant?: EmptyMediaVariants["variant"]
}>()
</script>
<template>
<div
data-slot="empty-icon"
:data-variant="variant"
:class="cn(emptyMediaVariants({ variant }), props.class)"
>
<slot />
</div>
</template>

View File

@ -0,0 +1,17 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>
<template>
<div
data-slot="empty-title"
:class="cn('text-lg font-medium tracking-tight', props.class)"
>
<slot />
</div>
</template>