Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 9 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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, 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
}
Loading
Loading