Skip to content

Commit

Permalink
feat(pageheader): new component [khcp-7898]
Browse files Browse the repository at this point in the history
  • Loading branch information
kaiarrowood committed Jul 13, 2023
1 parent e936207 commit a1df23c
Show file tree
Hide file tree
Showing 9 changed files with 345 additions and 10 deletions.
47 changes: 47 additions & 0 deletions packages/core/app-layout/docs/page-header.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# PageHeader.vue

A Kong UI dynamic page header component.

- [Features](#features)
- [Requirements](#requirements)
- [Usage](#usage)
- [Install](#install)
- [Props](#props)
- [Slots](#slots)

## Features

- Reactive updates based on `prop` value changes :rocket:
- Slottable areas for displaying custom content, icons, etc.

## Requirements

- `vue` must be initialized in the host application
- `@kong/kongponents` must be available as a `dependency` in the host application, along with the package's style imports. [See here for instructions on installing Kongponents](https://kongponents.konghq.com/#globally-install-all-kongponents). Specifically, the following Kongponents must be available:
- `KBreadcrumb`

## Usage

### Install

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

### Props

#### `title`

- type: `String`
- required: `false`
- default: `''`

The title text of the page.

### Slots

#### `center`

The main slot to use for navbar content if you don't need a left/center/right navbar layout.

---

[← Back to `@kong-ui-public/app-layout` docs](../README.md)
4 changes: 2 additions & 2 deletions packages/core/app-layout/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"test:unit:open": "cross-env FORCE_COLOR=1 vitest --ui"
},
"peerDependencies": {
"@kong/kongponents": "^8.83.5",
"@kong/kongponents": "^8.87.0",
"vue": "^3.2.47",
"vue-router": "^4.2.2"
},
Expand All @@ -48,7 +48,7 @@
"lodash.clonedeep": "^4.5.0"
},
"devDependencies": {
"@kong/kongponents": "^8.83.5",
"@kong/kongponents": "^8.87.0",
"@types/lodash.clonedeep": "^4.5.7",
"vue": "^3.2.47",
"vue-router": "^4.2.2"
Expand Down
3 changes: 3 additions & 0 deletions packages/core/app-layout/sandbox/components/NavLinks.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,8 @@
<router-link to="/kong-manager-example">
KM Example
</router-link>
<router-link to="/page-header">
PageHeader
</router-link>
</div>
</template>
5 changes: 5 additions & 0 deletions packages/core/app-layout/sandbox/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ const router = createRouter({
name: 'kong-manager-example',
component: () => import('./pages/KongManagerLayoutExample.vue'),
},
{
path: '/page-header',
name: 'page-header',
component: () => import('./pages/PageHeader.vue'),
},
],
})

Expand Down
66 changes: 66 additions & 0 deletions packages/core/app-layout/sandbox/pages/PageHeader.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<template>
<div class="sandbox-container">
<PageHeader
:breadcrumbs="breadcrumbs"
title="Cats are Cool"
>
<template #title-icon>
<KIcon
color="#169fcc"
icon="graduationHat"
size="20"
/>
</template>
<template #title-badge>
<KBadge appearance="info">
TRUTH
</KBadge>
</template>

<template #actions>
<div class="actions-wrapper">
<KInputSwitch
v-model="enabled"
class="mr-3"
:label="enabled ? 'Enabled' : 'Disabled'"
label-position="left"
/>
<KButton
appearance="creation"
icon="plus"
>
Do Things
</KButton>
</div>
</template>
</PageHeader>
</div>
</template>

<script setup lang="ts">
import { computed, ref } from 'vue'
import { PageHeader } from '../../src'
const breadcrumbs = computed(() => {
return [
{
key: 'home',
to: { name: 'home' },
text: 'Home',
icon: 'kong',
},
]
})
const enabled = ref(false)
</script>

<style lang="scss" scoped>
.mr-3 {
margin-right: 8px;
}
.actions-wrapper {
display: flex;
align-items: baseline;
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Cypress component test spec file

import PageHeader from './PageHeader.vue'

describe('<PageHeader />', () => {
it('should correctly render content when using props', () => {
const title = 'Cats are Cool'
const breadcrumbTitle = 'Home'

cy.mount(PageHeader, {
props: {
title,
breadcrumbs: [{
key: 'home',
to: { name: 'home' },
text: breadcrumbTitle,
icon: 'kong',
}],
},
})

cy.get('.kong-ui-page-header').should('exist')
cy.getTestId('page-breadcrumbs').should('be.visible')
cy.get('.k-breadcrumb-text').should('contain.text', breadcrumbTitle)
cy.getTestId('page-title-text').should('be.visible')
cy.getTestId('page-title-text').should('contain.text', title)
})

it('should correctly render content when use slots', () => {
const title = 'Cats are Cool'
const breadcrumbText = 'breadcrumbs-are-cool'
const iconText = 'title-icons-are-cool'
const badgeText = 'title-badges-are-cool'
const actionsText = 'actions-are-cool'

cy.mount(PageHeader, {
slots: {
breadcrumbs: breadcrumbText,
default: title,
'title-icon': iconText,
'title-badge': badgeText,
actions: actionsText,
},
})

cy.get('.kong-ui-page-header').should('exist')
cy.getTestId('page-breadcrumbs').should('be.visible')
cy.getTestId('page-breadcrumbs').should('contain.text', breadcrumbText)
cy.getTestId('page-title-icon').should('be.visible')
cy.getTestId('page-title-icon').should('contain.text', iconText)
cy.getTestId('page-title-text').should('be.visible')
cy.getTestId('page-title-text').should('contain.text', title)
cy.getTestId('page-title-badge').should('be.visible')
cy.getTestId('page-title-badge').should('contain.text', badgeText)
cy.getTestId('page-actions').should('be.visible')
cy.getTestId('page-actions').should('contain.text', actionsText)
})

it('should not render empty slots', () => {
const title = 'Cats are Cool'

cy.mount(PageHeader, {
props: {
title,
},
})

cy.get('.kong-ui-page-header').should('exist')
cy.getTestId('page-breadcrumbs').should('not.exist')
cy.getTestId('page-title-icon').should('not.exist')
cy.getTestId('page-title-badge').should('not.exist')
cy.getTestId('page-actions').should('not.exist')
})
})
113 changes: 113 additions & 0 deletions packages/core/app-layout/src/components/pageHeader/PageHeader.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<template>
<div class="kong-ui-page-header">
<div
v-if="hasBreadcrumbs"
class="page-breadcrumbs"
data-testid="page-breadcrumbs"
>
<slot name="breadcrumbs">
<KBreadcrumbs :items="breadcrumbs">
<template #divider>
/
</template>
</KBreadcrumbs>
</slot>
</div>

<div
class="page-title-section"
:data-testid="dataTestId"
>
<div class="page-title-wrapper">
<div
v-if="$slots['title-icon']"
class="page-title-icon"
data-testid="page-title-icon"
>
<slot name="title-icon" />
</div>
<h1
class="truncate"
data-testid="page-title-text"
:title="title"
>
<slot>
{{ title }}
</slot>
</h1>
<div
v-if="$slots['title-badge']"
class="page-title-badge"
data-testid="page-title-badge"
>
<slot name="title-badge" />
</div>
</div>

<div
v-if="$slots.actions"
class="page-actions"
data-testid="page-actions"
>
<slot name="actions" />
</div>
</div>
</div>
</template>

<script setup lang="ts">
import { computed, useSlots, PropType } from 'vue'
import type { BreadcrumbItem } from '@kong/kongponents'
const props = defineProps({
title: {
type: String,
default: '',
},
breadcrumbs: {
type: Array as PropType<BreadcrumbItem[]>,
default: () => ([]),
},
})
const slots = useSlots()
const hasBreadcrumbs = computed((): boolean => !!(props.breadcrumbs.length || slots.breadcrumbs))
const dataTestId = computed((): string => props.title ? `page-title-${props.title.toLowerCase().replace(/\s/g, '-')}` : 'page-title')
</script>

<style lang="scss" scoped>
.kong-ui-page-header {
.page-title-section {
display: flex;
justify-content: space-between;
.page-title-wrapper {
display: flex;
align-items: baseline;
.page-title-icon {
margin-right: var(--spacing-xs, 4px);
}
.page-title-badge {
align-self: center;
margin-left: var(--spacing-sm, 8px);
}
h1 {
color: var(--black-500, #0B172D);
font-size: 24px;
margin: var(--spacing-md, 16px) 0;
}
}
}
.truncate {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
</style>
12 changes: 7 additions & 5 deletions packages/core/app-layout/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import type { App } from 'vue'

import AccountDropdown from './components/navbar/AccountDropdown.vue'
import AppError from './components/errors/AppError.vue'
import AppLayout from './components/AppLayout.vue'
import AppNavbar from './components/navbar/AppNavbar.vue'
import AccountDropdown from './components/navbar/AccountDropdown.vue'

import AppSidebar from './components/sidebar/AppSidebar.vue'
import PageHeader from './components/pageHeader/PageHeader.vue'
import SidebarToggle from './components/sidebar/SidebarToggle.vue'
import AppError from './components/errors/AppError.vue'

// Export Vue plugin as the default
export default {
Expand All @@ -18,12 +19,13 @@ export default {

// Export individual Components
export {
AccountDropdown,
AppError,
AppLayout,
AppNavbar,
AppSidebar,
PageHeader,
SidebarToggle,
AppError,
AccountDropdown,
}

export * from './types'
Loading

0 comments on commit a1df23c

Please sign in to comment.