GrEmptyState
Берут, когда список пуст с самого начала.
Когда брать
- список пуст с самого начала — «пока нет выплат»: объяснение и следующий шаг вместо пустого прямоугольника;
- фильтр ничего не нашёл — текст и действие тут другие: сбросить фильтр, а не создать первую запись;
- раздел ещё не настроен — иконка, заголовок, описание и кнопка вместо инструкции в поддержке;
- состояние встроено в другой компонент — списки, таблицы и графики показывают его слотом
#empty.
Когда взять другое
| Нужно | Берите |
|---|---|
| Данные грузятся | GrSkeleton / GrLoading |
| Запрос упал | GrResponseErrorBanner |
| Предупреждение в потоке страницы | GrAlert |
| Нужен просто текст без иллюстрации | GrCard |
Пустой экран без объяснения читается как поломка. Разница между «здесь пока ничего нет» и «по этому фильтру ничего не нашлось» — важна: в первом случае предлагают создать, во втором — снять фильтр.
Заголовок
<GrEmptyState title="Пока нет выплат" description="Создайте первую выплату, чтобы отслеживать согласования.">
<GrButton size="sm">Создать выплату</GrButton>
</GrEmptyState>
Заголовок — настоящий heading: без него пустое состояние не находится
навигацией по заголовкам, а объясняет происходящее на экране именно оно.
Уровень задаётся headingLevel (2…6, по умолчанию 3) и настраивается
глобально через componentDefaults.GrEmptyState.headingLevel. h1 в наборе нет
намеренно: пустое состояние живёт внутри уже существующей структуры страницы, а
не задаёт её.
title необязателен: без него и без слота берётся строка из локали
(gr.emptyState.title), так что <GrEmptyState /> не превращается в пустую
карточку.
Слоты
| Слот | Зачем |
|---|---|
title | заголовок разметкой: ссылка, выделение, счётчик |
description | описание разметкой |
icon | своя иконка вместо встроенной lucide/inbox |
| по умолчанию | действия под текстом, центрируются |
Слот сильнее одноимённого пропа. Встроенная иконка декоративна
(aria-hidden) — смысл несёт текст.
Поверхность
<GrCard>
<GrEmptyState variant="ghost" title="Никого не пригласили" />
</GrCard>
variant (outlined по умолчанию, ghost) повторяет словарь
GrCard: ghost снимает рамку, фон и радиус. Он нужен внутри
уже существующей карточки — в GrDataTable #empty, GrList #empty или в своём
макете, где outlined даёт вторую рамку вокруг той же поверхности.
Размер
size (xs…lg, по умолчанию md, читается из GrConfigProvider) двигает
падинг карточки, коробку иконки, саму иконку, кегль заголовка и вертикальные
отступы. xs и sm — для пустой ячейки таблицы и панели дропдауна, где
полноразмерное состояние занимает весь список.
Оформление
Всё берётся из токенов: радиус — --gr-radius-md/--gr-radius-lg по ступени,
кегли — --gr-text-xs/--gr-text-sm/--gr-text-base, цвета — --gr-card,
--gr-brd, --gr-muted, --gr-muted-fg.
Playground 4
Загружается…
<GrEmptyState />Установка
npm i @feugene/granularityИмпорт
import { GrEmptyState } from '@feugene/granularity/components/GrEmptyState'API
Props
| Prop | Type | по умолчанию | Описание |
|---|---|---|---|
variant | "ghost" | "outlined" | undefined | undefined | `ghost` снимает рамку и фон: карточка внутри карточки рисует вторую рамку. |
title | string | undefined | undefined | Заголовок. Не задан — берётся из локали; слот `#title` сильнее обоих. |
size | "xs" | "sm" | "md" | "lg" | undefined | undefined | — |
description | string | undefined | undefined | — |
headingLevel | 2 | 3 | 4 | 5 | 6 | undefined | undefined | — |
Slots
| Slot | Type | Описание |
|---|---|---|
icon | any | Иконка вместо встроенной. |
title | any | Заголовок разметкой: ссылка, выделение, счётчик. |
description | any | — |
default | any | Действия под текстом: центрируются. |
Примеры 3
Главное действие внутри карточки
Классический сценарий: иконка, заголовок-heading, описание и основное действие в слоте по умолчанию.
No payouts yet
<script setup lang="ts">
import { GrButton, GrEmptyState } from '@feugene/granularity'
</script>
<template>
<GrEmptyState title="No payouts yet" description="Create the first scheduled payout to start tracking approval and transfer states.">
<GrButton size="sm">Create payout</GrButton>
</GrEmptyState>
</template>Поиск и фильтр без результатов
Ноль результатов поиска: фильтр сверху, компактный size="sm" и действия по сбросу или созданию объекта.
Nothing found
<script setup lang="ts">
import { computed, ref } from 'vue'
import { GrButton, GrEmptyState, GrInput } from '@feugene/granularity'
const query = ref('treasury')
const description = computed(() => {
return `No saved views match “${query.value}”. Try a broader term or create a new filtered workspace.`
})
</script>
<template>
<div class="grid gap-3">
<GrInput v-model="query" placeholder="Search views" />
<GrEmptyState size="sm" title="Nothing found" :description="description">
<div class="flex flex-wrap justify-center gap-2">
<GrButton size="sm" variant="outline">Clear filter</GrButton>
<GrButton size="sm">Create view</GrButton>
</div>
</GrEmptyState>
</div>
</template>Внутри двухпанельной раскладки
variant="ghost" внутри уже существующей карточки: без него получалась карточка в карточке со второй рамкой.
No team members invited
<script setup lang="ts">
import { GrButton, GrCard, GrEmptyState } from '@feugene/granularity'
</script>
<template>
<GrCard class="grid gap-4 md:grid-cols-[minmax(0,220px)_1fr] md:items-center">
<div class="rounded-xl border border-dashed border-[var(--gr-brd)] bg-[var(--gr-muted)] p-4 text-sm text-[var(--gr-muted-fg)]">
Left rail can keep filters, contextual hints or a compact KPI while the main area uses `GrEmptyState`.
</div>
<GrEmptyState variant="ghost" title="No team members invited" description="Inside an existing card the ghost variant drops the second border around the same surface.">
<GrButton size="sm">Invite teammate</GrButton>
</GrEmptyState>
</GrCard>
</template>