Skip to content

Commit

Permalink
feat(entities-shared): create shared empty state component [KHCP-1435…
Browse files Browse the repository at this point in the history
…5] (#1850)

* feat(entities-shared): create shared empty state component [KHCP-14355]

* fix(*): fix imports and styles

* fix(*): fix card styles

* fix(*): card children styles

* fix(*): card children styles

* fix(*): emit event on learning hub button click and slot icon for feature card

* fix(*): add icons to features

* fix(*): apply pr feedback

* fix(*): apply pr feedback
  • Loading branch information
mptap authored Dec 13, 2024
1 parent 353d880 commit 8ac62d5
Show file tree
Hide file tree
Showing 9 changed files with 360 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: `Boolean`
- default: false

If provided, will show the Learning Hub button for the entity.

#### `features`

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

If provided, will display card for each feature of that entity, along with an icon, a title 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,47 @@
<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"
learn-more-link
pricing="Lorem ipsum dolor sit amet consectetur adipisicing elit."
title="Gateway Manager"
@create-button-clicked="console.log('create button clicked')"
@learning-hub-button-clicked="console.log('learning hub button clicked')"
>
<template #icon>
<RuntimesIcon />
</template>
</EntityEmptyState>
</main>
</div>
</template>

<script setup lang="ts">
import { EntityEmptyState } from '../../src'
import { RuntimesIcon } from '@kong/icons'
const features = [
{
iconVariant: 'deploy',
title: 'First',
description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Id quidem aperiam similique vitae beatae',
},
{
iconVariant: 'plug',
title: 'Second',
description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Id quidem aperiam similique vitae beatae',
}, {
iconVariant: 'chartData',
title: 'Third ',
description: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Id quidem aperiam similique vitae beatae',
}, {
iconVariant: 'analytics',
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,216 @@
<template>
<div class="kong-ui-public-entity-empty-state">
<div v-if="$slots.image">
<slot name="image" />
</div>
<div class="entity-empty-state-content">
<div
v-if="title || $slots.title || $slots['title-after']"
class="entity-empty-state-title"
>
<slot name="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> <slot name="pricing">
{{ pricing }}
</slot>
</p>
</div>
</div>
<div
v-if="$slots.message"
class="entity-empty-state-message"
>
<slot name="message" />
</div>
<div class="entity-empty-state-action">
<KButton
v-if="actionButtonText || $slots.action"
appearance="primary"
size="large"
@click="$emit('create-button-clicked')"
>
<AddIcon />
{{ actionButtonText }}
</KButton>
<KButton
v-if="learnMoreLink"
appearance="secondary"
size="large"
@click="$emit('learning-hub-button-clicked')"
>
<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">
<template #title>
<component
:is="getEntityIcon(feature.iconVariant)"
:color="KUI_COLOR_TEXT_NEUTRAL_STRONGER"
:size="KUI_ICON_SIZE_40"
/>
<div class="card-title">
{{ feature.title }}
</div>
</template>
{{ feature.description }}
</KCard>
</template>
</div>
</div>
</template>

<script lang="ts" setup>
import { type PropType } from 'vue'
import { KButton } from '@kong/kongponents'
import { BookIcon, AddIcon, DeployIcon, PlugIcon, ChartDataIcon, AnalyticsIcon } from '@kong/icons'
import composables from '../../composables'
import type { EmptyStateFeature } from 'src/types/entity-empty-state'
import { KUI_ICON_SIZE_40, KUI_COLOR_TEXT_NEUTRAL_STRONGER } from '@kong/design-tokens'
const getEntityIcon = (iconType: string): object => {
switch (iconType) {
case 'deploy':
return DeployIcon
case 'plug':
return PlugIcon
case 'chartData':
return ChartDataIcon
case 'analytics':
return AnalyticsIcon
default:
return BookIcon
}
}
defineProps({
title: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
pricing: {
type: String,
default: '',
},
actionButtonText: {
type: String,
default: '',
},
learnMoreLink: {
type: Boolean,
default: false,
},
features: {
type: Array as PropType<EmptyStateFeature[]>,
default: () => [],
},
})
defineEmits(['create-button-clicked', 'learning-hub-button-clicked'])
const { i18n: { t } } = composables.useI18n()
</script>

<style lang="scss" scoped>
.kong-ui-public-entity-empty-state {
align-items: center;
background-color: $kui-color-background;
box-sizing: border-box;
display: flex;
flex-direction: column;
font-family: $kui-font-family-text;
gap: $kui-space-100;
padding: $kui-space-130 $kui-space-150;
width: 100%;
.entity-empty-state-content {
align-items: center;
display: flex;
flex-direction: column;
gap: $kui-space-40;
text-align: center;
width: 100%;
.entity-empty-state-title {
color: $kui-color-text;
font-size: $kui-font-size-70;
font-weight: $kui-font-weight-bold;
line-height: $kui-line-height-60;
}
}
.entity-empty-state-description, .entity-empty-state-pricing {
color: $kui-color-text-neutral-strong;
font-size: $kui-font-size-30;
font-weight: $kui-font-weight-regular;
line-height: $kui-line-height-30;
max-width: 640px; // limit width so the description stays readable if it is too long
p {
margin: $kui-space-30;
}
}
.entity-empty-state-action {
align-items: center;
display: flex;
gap: $kui-space-50;
}
.entity-empty-state-card-container {
display: grid !important;
gap: $kui-space-60;
grid-template-columns: auto auto !important;
.entity-empty-state-card {
background-color: $kui-color-background-neutral-weakest;
border: $kui-border-width-10 solid $kui-color-border;
border-radius: $kui-border-radius-30;
color: $kui-color-text-neutral-weak;
gap: $kui-space-60;
height: 160px;
padding: $kui-space-80;
width: 312px;
:deep(.card-title) {
font-size: $kui-font-size-30;
font-weight: $kui-font-weight-semibold;
}
:deep(.card-content) {
color: $kui-color-text-neutral;
}
}
}
}
</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, useFetcherCacheKey, 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, useFetcherCacheKey, 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
}
Loading

0 comments on commit 8ac62d5

Please sign in to comment.