Skip to content

Commit

Permalink
feat(entities-shared): create shared empty state component [KHCP-14355]
Browse files Browse the repository at this point in the history
  • Loading branch information
mptap committed Dec 12, 2024
1 parent dd86fdc commit 05c0fef
Show file tree
Hide file tree
Showing 9 changed files with 344 additions and 1 deletion.
1 change: 1 addition & 0 deletions packages/entities/entities-shared/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ import '@kong-ui-public/entities-shared/dist/style.css'
- [`<PermissionsWrapper.vue />`](docs/permissions-wrapper.md)
- [`<EntityFormSection.vue />`](docs/entity-form-section.md)
- [`<EntityLink.vue />`](docs/entity-link.md)
- [`<EntityEmptyState.vue />`](docs/entity-empty-state.md)
- [`<EntityToggleModal.vue />`](docs/entity-toggle-modal.md)
- [`<EntityBaseConfigCard.vue />`](docs/entity-base-config-card.md)

Expand Down
77 changes: 77 additions & 0 deletions packages/entities/entities-shared/docs/entity-empty-state.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# EntityEmptyState.vue

An empty state component that displays title, description, and optionally pricing, action button, learn more, and a set of features cards. Used for engaging and onboarding new users with rich information and context.

- [Requirements](#requirements)
- [Usage](#usage)
- [Install](#install)
- [Props](#props)
- [Usage example](#usage-example)
- [TypeScript interfaces](#typescript-interfaces)

## Requirements

- `vue` must be initialized in the host application
- `@kong/kongponents` must be added as a `dependency` in the host application, globally available via the Vue Plugin installation, and the package's style imports must be added in the app entry file. [See here for instructions on installing Kongponents](https://kongponents.konghq.com/#globally-install-all-kongponents).

## Usage

### Install

[See instructions for installing the `@kong-ui-public/entities-shared` package.](../README.md#install)

### Props

#### `title`

- type: `String`
- required: `true`

Title for the empty state.

#### `description`

- type: `String`
- default: `''`

Description for the empty state.

#### `pricing`

- type: `String`
- default: ``

If provided, will display pricing information for transparency.

#### `actionButtonText`

- type: `String`
- default: ``

If provided, a CTA button will show with text and icon typically, for creating an entity.

#### `learnMoreLink`

- type: `String`
- default: ``

If provided, will link to the Learning Hub link for the entity.

#### `features`

- type: `Array`
- default: `[]`

If provided, will display card for each feature of that entity, along with an icon, a tilte and a short description.

### Usage example

Please refer to the [sandbox](../src/components/entity-empty-state/EntityEmptyState.vue).

## TypeScript interfaces

TypeScript interfaces [are available here](https://github.com/Kong/public-ui-components/blob/main/packages/entities/entities-shared/src/types/entity-empty-state.ts) and can be directly imported into your host application. The following type interfaces are available for import:

```ts
import type { EmptyStateFeature } from '@kong-ui-public/entities-shared'
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<template>
<div class="sandbox-container">
<main>
<h3>Entity empty state</h3>
<EntityEmptyState
action-button-text="Create a gateway"
description="Lorem ipsum dolor sit amet consectetur adipisicing elit. Id quidem aperiam similique vitae beatae. Repellat quam voluptas vitae, maxime consequuntur praesentium suscipit. Numquam aliquid nulla vel esse accusantium reiciendis error?"
:features="features"
title="Gateway Manager"
>
<template #icon>
<RuntimesIcon />
</template>
</EntityEmptyState>
</main>
</div>
</template>

<script setup lang="ts">
import { EntityEmptyState } from '../../src'
import { RuntimesIcon } from '@kong/icons'
const features = [
{
icon: RuntimesIcon,
title: 'First',
description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Id quidem aperiam similique vitae beatae',
},
{
icon: RuntimesIcon,
title: 'Second',
description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Id quidem aperiam similique vitae beatae',
}, {
icon: RuntimesIcon,
title: 'Third ',
description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Id quidem aperiam similique vitae beatae',
}, {
icon: RuntimesIcon,
title: 'Fourth',
description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Id quidem aperiam similique vitae beatae',
},
]
</script>
7 changes: 7 additions & 0 deletions packages/entities/entities-shared/sandbox/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ export const routes: Array<RouteRecordRaw & { label?: string }> = [
label: 'EntityLink',
component: () => import('./pages/EntityLinkPage.vue'),
},

{
path: '/entity-empty-state',
name: 'entity-empty-state',
label: 'EntityEmptyState',
component: () => import('./pages/EntityEmptyStatePage.vue'),
},
{
path: '/entity-delete-modal',
name: 'entity-delete-modal',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
<template>
<div class="kong-ui-public-entity-empty-state">
<div
v-if="$slots.icon"
class="entity-empty-state-icon"
>
<slot name="icon" />
</div>
<div
v-else
class="entity-empty-state-image"
>
<slot name="image" />
</div>
<div class="entity-empty-state-content">
<div
v-if="title || $slots.title"
class="entity-empty-state-title"
>
<div :title="title">
{{ title }}
</div>
<span
v-if="$slots['title-after']"
>
<slot
name="title-after"
/>
</span>
</div>
<div
v-if="description || $slots.default"
class="entity-empty-state-description"
>
<slot name="default">
<p>
{{ description }}
</p>
</slot>
</div>
<div
v-if="pricing"
class="entity-empty-state-pricing"
>
<p>
<b>{{ t('emptyState.pricingTitle') }}</b> {{ pricing }}
</p>
</div>
</div>
<div
v-if="$slots.message"
class="entity-empty-state-message"
>
<slot name="message" />
</div>
</div>
<div
v-if="(actionButtonText) || $slots.action"
class="entity-empty-state-action"
>
<KButton
appearance="primary"
@click="$emit('click-action')"
>
{{ actionButtonText }}
</KButton>
<KButton
v-if="learnMoreLink"
appearance="secondary"
:to="learnMoreLink"
>
<BookIcon decorative />
{{ t('emptyState.learnMore') }}
</KButton>
</div>
<div class="entity-empty-state-card-container">
<template
v-for="feature in features"
:key="feature"
>
<KCard
class="entity-empty-state-card"
:title="feature.title"
>
<template #title>
<component
:is="feature.iconVariant"
:color="`var(--kui-color-text-neutral-stronger, ${KUI_COLOR_TEXT_NEUTRAL_STRONGER})`"
:size="KUI_ICON_SIZE_30"
/>
<div>{{ feature.title }}</div>
</template>
<template #default>
{{ feature.description }}
</template>
</KCard>
</template>
</div>
</template>

<script lang="ts" setup>
import { type PropType } from 'vue'
import KButton from '@kong/kongponents'
import composables from '../../composables'
import type { EmptyStateFeature } from 'src/types/entity-empty-state'
import { KUI_ICON_SIZE_30, KUI_COLOR_TEXT_NEUTRAL_STRONGER } from '@kong/design-tokens'
defineProps({
title: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
pricing: {
type: String,
default: '',
},
actionButtonText: {
type: String,
default: '',
},
learnMoreLink: {
type: String,
default: '',
},
features: {
type: Array as PropType<EmptyStateFeature[]>,
default: () => [],
},
})
defineEmits(['click-action'])
const { i18n: { t } } = composables.useI18n()
</script>

<style lang="scss" scoped>
.kong-ui-public-entity-empty-state {
align-items: center;
background-color: var(--kui-color-background, $kui-color-background);
box-sizing: border-box;
display: flex;
flex-direction: column;
font-family: var(--kui-font-family-text, $kui-font-family-text);
gap: var(--kui-space-60, $kui-space-60);
padding: var(--kui-space-130, $kui-space-150) var(--kui-space-130, $kui-space-150);
width: 100%;
.entity-empty-state-icon {
:deep() {
height: var(--kui-icon-size-50, $kui-icon-size-50) !important;
width: var(--kui-icon-size-50, $kui-icon-size-50) !important;
}
}
.entity-empty-state-content {
align-items: center;
display: flex;
flex-direction: column;
gap: var(--kui-space-40, $kui-space-40);
text-align: center;
width: 100%;
.entity-empty-state-title {
color: var(--kui-color-text, $kui-color-text);
font-size: var(--kui-font-size-70, $kui-font-size-70);
font-weight: var(--kui-font-weight-bold, $kui-font-weight-bold);
line-height: var(--kui-line-height-60, $kui-line-height-60);
}
}
.entity-empty-state-description {
color: var(--kui-color-text-neutral-strong, $kui-color-text-neutral-strong);
font-size: var(--kui-font-size-30, $kui-font-size-30);
font-weight: var(--kui-font-weight-regular, $kui-font-weight-regular);
line-height: var(--kui-line-height-30, $kui-line-height-30);
max-width: 640px; // limit width so the description stays readable if it is too long
p {
margin: var(--kui-space-0, $kui-space-0);
}
}
.entity-empty-state-action {
align-items: center;
display: flex;
gap: var(--kui-space-50, $kui-space-50);
}
.entity-empty-state-card-container {
display: grid !important;
gap: var(--kui-space-60, $kui-space-60);
grid-template-columns: auto auto !important;
.entity-empty-state-card {
color: var(--kui-color-text-neutral-weak, $kui-color-text-neutral-weak);
margin-bottom: $kui-space-40;
}
}
}
</style>
3 changes: 2 additions & 1 deletion packages/entities/entities-shared/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import EntityToggleModal from './components/entity-toggle-modal/EntityToggleModa
import PermissionsWrapper from './components/permissions-wrapper/PermissionsWrapper.vue'
import EntityFormSection from './components/entity-form-section/EntityFormSection.vue'
import EntityLink from './components/entity-link/EntityLink.vue'
import EntityEmptyState from './components/entity-empty-state/EntityEmptyState.vue'
import JsonCodeBlock from './components/common/JsonCodeBlock.vue'
import TerraformCodeBlock from './components/common/TerraformCodeBlock.vue'
import YamlCodeBlock from './components/common/YamlCodeBlock.vue'
Expand All @@ -20,7 +21,7 @@ import composables from './composables'
const { useAxios, useDeleteUrlBuilder, useErrors, useExternalLinkCreator, useFetchUrlBuilder, useFetcher, useDebouncedFilter, useStringHelpers, useHelpers, useGatewayFeatureSupported, useTruncationDetector, useValidators, useSchemaProvider } = composables

// Components
export { EntityBaseConfigCard, ConfigCardItem, ConfigCardDisplay, InternalLinkItem, EntityBaseForm, EntityBaseTable, EntityDeleteModal, EntityFilter, EntityToggleModal, PermissionsWrapper, EntityFormSection, EntityLink, JsonCodeBlock, TerraformCodeBlock, YamlCodeBlock, TableTags }
export { EntityBaseConfigCard, ConfigCardItem, ConfigCardDisplay, InternalLinkItem, EntityBaseForm, EntityBaseTable, EntityDeleteModal, EntityFilter, EntityToggleModal, PermissionsWrapper, EntityFormSection, EntityLink, EntityEmptyState, JsonCodeBlock, TerraformCodeBlock, YamlCodeBlock, TableTags }

// Composables
export { useAxios, useDeleteUrlBuilder, useErrors, useExternalLinkCreator, useFetchUrlBuilder, useFetcher, useDebouncedFilter, useStringHelpers, useHelpers, useGatewayFeatureSupported, useTruncationDetector, useValidators, useSchemaProvider }
Expand Down
4 changes: 4 additions & 0 deletions packages/entities/entities-shared/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@
"structuredFormat": "Structured"
}
},
"emptyState": {
"learnMore": "Learn more",
"pricingTitle": "Pricing: "
},
"filter": {
"filterButtonText": "Filter",
"fieldLabel": "Filter by:",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface EmptyStateFeature {
iconVariant: string,
title: string,
description: string
}
1 change: 1 addition & 0 deletions packages/entities/entities-shared/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ export * from './entity-base-table'
export * from './entity-base-config-card'
export * from './entity-filter'
export * from './entity-link'
export * from './entity-empty-state'
export * from './utils'

0 comments on commit 05c0fef

Please sign in to comment.