Skip to content

Commit

Permalink
feat(ktoaster): refactor toastmanager [KHCP-11958] (#2220)
Browse files Browse the repository at this point in the history
* feat(ktoaster): refactor toastmanager [KHCP-11958]

* test(ktoaster): fix component tests [KHCP-11958]

* docs(toaster): update docs [KHCP-11958]

* fix(toastmanager): avoid using appcontext [KHCP-11958]

* docs(toaster): add comment [KHCP-11958]

* fix: address pr feedback [KHCP-11958]

* fix(toastmanager): replace uuid [KHCP-11958]

* docs(usage): minor fix [KHCP-11958]

* fix: cypress cache

---------

Co-authored-by: Adam DeHaven <adam.dehaven@konghq.com>
  • Loading branch information
portikM and adamdehaven authored Jun 14, 2024
1 parent 21c1662 commit 613d654
Show file tree
Hide file tree
Showing 11 changed files with 158 additions and 212 deletions.
4 changes: 3 additions & 1 deletion .github/actions/setup-pnpm-with-dependencies/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ runs:
id: dependency-cache
uses: actions/cache@v4
with:
path: '**/node_modules'
path: |
~/.cache/Cypress
**/node_modules
key: pnpm-${{ steps.node-version.outputs.pnpm-version }}-${{ steps.node-version.outputs.node-version }}-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}

- name: Install Dependencies
Expand Down
5 changes: 0 additions & 5 deletions docs/.vitepress/theme/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import type { EnhanceAppContext } from 'vitepress'
import DefaultTheme from 'vitepress/theme'
import Kongponents from '../../../src'
// Import component-specific files
import ToastManager from '../../../src/components/KToaster/ToastManager'
import RouterLink from '../components/RouterLink.vue'

// Theme styles
Expand All @@ -14,10 +13,6 @@ export default {
// Extend default theme custom behaviour
DefaultTheme.enhanceApp(ctx)

// Register ToastManager
// TODO: May need to handle SSR
ctx.app.config.globalProperties.$toaster = new ToastManager()

// Stub the <router-link> component; it doesn't exist in VitePress
ctx.app.component('RouterLink', RouterLink)

Expand Down
10 changes: 5 additions & 5 deletions docs/components/table.md
Original file line number Diff line number Diff line change
Expand Up @@ -876,9 +876,9 @@ Fired when the user changes the table's page size, performs sorting, resizes col
<script setup lang="ts">
import { AddIcon, SearchIcon, MoreIcon } from '@kong/icons'
import { getCurrentInstance } from 'vue'
import { ToastManager } from '@/index'
const $toaster = getCurrentInstance()?.appContext.config.globalProperties.$toaster
const toaster = new ToastManager()
const basicHeaders = (actions: boolean = false, sortable: string | null = null, hidable: string | null = null) => {
const keys = {
Expand Down Expand Up @@ -1081,15 +1081,15 @@ const numberedColumnsFetcher = () => {
}
const onRowClick = (event, row) => {
$toaster.open({ appearance: 'success', title: 'Row clicked! Row data:', message: row })
toaster.open({ appearance: 'success', title: 'Row clicked! Row data:', message: row })
}
const onButtonClick = () => {
$toaster.open({ appearance: 'system', title: 'Button clicked!', message: 'Button click is handled separately from row or cell clicks.' })
toaster.open({ appearance: 'system', title: 'Button clicked!', message: 'Button click is handled separately from row or cell clicks.' })
}
const onCellClick = (event, cell) => {
$toaster.open({ title: 'Cell clicked! Cell data:', message: cell })
toaster.open({ title: 'Cell clicked! Cell data:', message: cell })
}
</script>
Expand Down
133 changes: 74 additions & 59 deletions docs/components/toaster.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,63 +2,75 @@

KToaster a popup notification component. The toaster will close automatically after a set timeout or can be dismissed by clicking on the close icon.

KToaster is used via the `ToastManager` instance. All rendering is controlled from ToastManager via an intuitive, imperative API. It is recommended that you initialize `ToastManager` in your app via [`app.config.globalProperties`](https://vuejs.org/api/application.html#app-config-globalproperties) to allow you to access it on any component instance inside your application.
KToaster is used via the `ToastManager` instance. All rendering is controlled from `ToastManager` via an intuitive API.

The easiest way to use it is by creating a composable that you can use anywhere in your app. This way you don't have to initialize `ToastManager` in every component.

```ts
// main.ts
// composables/useToaster

import { createApp } from 'vue'
import { onBeforeUnmount } from 'vue'
import { ToastManager } from '@kong/kongponents'

const app = createApp()

// Available inside any component template in the application, and also on 'this' of any component instance
app.config.globalProperties.$toaster = new ToastManager({
// you can also override the default value of following properties while initializing ToastManager
id: 'custom-id', // default: 'toaster-container'
timeout: 500, // default: 5000
appearance: 'success', // default: 'info'
zIndex: 92929, // default: 10000
})
```
export default function useToaster() {
const toaster = new ToastManager()

For TypeScript, you should also augment the global property in your vue declaration file
onBeforeUnmount(() => {
toaster.destroy()
})

```ts
import { ToastManager } from '@kong/kongponents'

declare module 'vue' {
interface ComponentCustomProperties {
$toaster: typeof ToastManager
}
return { toaster }
}
```

Once `ToastManager` is added as a global property, you can access it's methods via `this.$toaster` if using the Vue Options API.
Once `ToastManager` instance is initialized, you can use it's methods to show toast messages:

<KButton @click="$toaster.open({ title: 'Basic Notification', message: 'Detailed message' })">Open Toaster</KButton>
<KButton @click="toaster.open({ title: 'Basic Notification', message: 'Detailed message' })">Open Toaster</KButton>

```html
<KButton @click="$toaster.open({ title: 'Basic Notification', message: 'Detailed message' })">Open Toaster</KButton>
```vue
<template>
<KButton @click="toaster.open({ title: 'Basic Notification', message: 'Detailed message' })">Open Toaster</KButton>
</template>
<script setup lang="ts">
import useToaster from '~/composables/useToaster'
const { toaster } = useToaster()
</script>
```

or within the `setup()` function in your component
or call them from within the `setup()` function in your component:

```ts
<script setup lang="ts">
import { getCurrentInstance } from 'vue'
import useToaster from '~/composables/useToaster'

const $toaster = getCurrentInstance()?.appContext.config.globalProperties.$toaster
const { toaster } = useToaster()

const showToast = (name: string) => {
$toaster.open(`Wow, ${name} is looking toasty!`)
toaster.open(`Hello ${name}!`)
}
</script>
```

:::warning NOTE
Don't forget to clean up the toaster instance by calling `toaster.destroy()` in `onBeforeUnmount`.
:::

Optionally, you can provide options object upon initialization. It takes one parameter:

```ts
interface ToasterOptions {
zIndex?: number // z-index of toast message element
}
```

::: warning NOTE
Using `getCurrentInstance` is a replacement of Vue 2's Vue.prototype which is no longer present in Vue 3. As with anything global, this should be used sparingly.
```ts
const toaster = new ToastManager({ zIndex: 1001 })
```

If a global property conflicts with a component’s own property, the component's own property will have higher priority.
:::warning NOTE
If you are using Kongponents in SSR mode, it is advised that you **only initialize `ToastManager` on the client side**.
:::

## Arguments
Expand All @@ -79,10 +91,10 @@ interface Toast {

Notification title.

<KButton @click="$toaster.open({ title: 'Notification Title' })">Open Toaster</KButton>
<KButton @click="toaster.open({ title: 'Notification Title' })">Open Toaster</KButton>

```html
<KButton @click="$toaster.open({ title: 'Notification Title' })">Open Toaster</KButton>
<KButton @click="toaster.open({ title: 'Notification Title' })">Open Toaster</KButton>
```

### message
Expand All @@ -92,23 +104,23 @@ The message string that allows for displaying longer strings of text to the user
Alternatively, if you provide a string as the only argument to the `open()` method, it will be treated as message.

<div class="horizontal-container">
<KButton @click="$toaster.open({
<KButton @click="toaster.open({
title: 'Title',
message: 'Detailed message. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.' })"
>
Open Toaster
</KButton>
<KButton @click="$toaster.open('String will become a message.')" appearance="secondary">Open Toaster</KButton>
<KButton @click="toaster.open('String will become a message.')" appearance="secondary">Open Toaster</KButton>
</div>

```html
<KButton @click="$toaster.open({
<KButton @click="toaster.open({
title: 'Title',
message: 'Detailed message. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.' })"
>
Open Toaster
</KButton>
<KButton @click="$toaster.open('String will become a message.')" appearance="secondary">Open Toaster</KButton>
<KButton @click="toaster.open('String will become a message.')" appearance="secondary">Open Toaster</KButton>
```

### appearance
Expand All @@ -122,27 +134,27 @@ Depending on the nature of notification, you might want to use different appeara
* `system`

<div class="horizontal-container">
<KButton @click="$toaster.open({ title: 'Info', appearance: 'info' })">
<KButton @click="toaster.open({ title: 'Info', appearance: 'info' })">
<InfoIcon />
Info
</KButton>
<KButton @click="$toaster.open({ title: 'Success', appearance: 'success' })">
<KButton @click="toaster.open({ title: 'Success', appearance: 'success' })">
<CheckCircleIcon />
Success
</KButton>
<KButton
@click="$toaster.open({ title: 'Danger', appearance: 'danger' })"
@click="toaster.open({ title: 'Danger', appearance: 'danger' })"
appearance="danger"
>
<ClearIcon />
Danger
</KButton>
<KButton @click="$toaster.open({ title: 'Warning', appearance: 'warning' })">
<KButton @click="toaster.open({ title: 'Warning', appearance: 'warning' })">
<WarningIcon />
Warning
</KButton>
<KButton
@click="$toaster.open({ title: 'System', appearance: 'system' })"
@click="toaster.open({ title: 'System', appearance: 'system' })"
appearance="secondary"
>
<KongIcon />
Expand All @@ -151,7 +163,7 @@ Depending on the nature of notification, you might want to use different appeara
</div>

```ts
$toaster.open({ title: 'Warning', appearance: 'warning' })
toaster.open({ title: 'Warning', appearance: 'warning' })
```

### timeoutMilliseconds
Expand All @@ -178,14 +190,14 @@ const toasterOptions: Toast = {
}
const openNotification = (options: Toast | string) => {
$toaster.open(options)
toaster.open(options)
}
</script>
```

## Toaster State

You can view the current state of active toasters by calling `this.$toaster.toasters`. Click the buttons below to watch the state change
You can view the current state of active toasts by calling `ToastManager.toasts`. Click the buttons below to watch the state change

<div class="horizontal-container">
<KButton @click="openNotification({ timeoutMilliseconds: 10000, title: 'Info Notification', appearance: 'info' })">
Expand All @@ -206,7 +218,7 @@ You can view the current state of active toasters by calling `this.$toaster.toas
</div>

<pre class="fixed-height-data-container">
{{ JSON.stringify(toasters || [], null, 2) }}
{{ JSON.stringify(toasts || [], null, 2) }}
</pre>

```vue
Expand All @@ -215,37 +227,40 @@ You can view the current state of active toasters by calling `this.$toaster.toas
Open Toaster
</KButton>
{{ toasters }}
{{ toasts }}
</template>
<script lang="ts">
import type { Toast } from '@kong/kongponents'
const toasters = ref<Toast>([])
const toasts = ref<Toast>([])
const openNotification = (options: Toast | string): void => {
$toaster.open(options)
toasters.value = $toaster.toasters.value
toaster.open(options)
toasts.value = toaster.toasts.value
}
</script>
```

<script setup lang="ts">
import { InfoIcon, CheckCircleIcon, WarningIcon, ClearIcon, KongIcon } from '@kong/icons'
import { getCurrentInstance, ref } from 'vue'
import { ref } from 'vue'
import type { ComponentInternalInstance } from 'vue'
import { ToastManager } from '@/index'

const toaster = new ToastManager()

const $toaster = getCurrentInstance()?.appContext.config.globalProperties.$toaster
const toasters = ref([])
const toasts = ref([])
const timeLeft = ref(4)

const openNotification = (options: Toast | string): void => {
$toaster.open(options)
toasters.value = $toaster.toasters.value
toaster.open(options)
toasts.value = toaster.toasts.value
}

const openNotificationElapse = (options: Toast | string): void => {
$toaster.open(options)
toasters.value = $toaster.toasters.value
toaster.open(options)
toasts.value = toaster.toasts.value
timeLeft.value -= 1

const interval = setInterval(() => {
Expand Down
10 changes: 5 additions & 5 deletions docs/guide/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,23 +68,23 @@ Import and registration can be done individually in the app entry file (e.g. `ma
```ts
// main.ts (or Vue entry file)

import { createApp } from 'vue'
import { KButton } from '@kong/kongponents'
// Kongponents rely on vue-bind-once directive to work properly
// The Kongponents bundle includes the vue-bind-once package so you won't need to install it separately, but it does need to be registered
import { BindOncePlugin } from 'vue-bind-once'
import { createApp } from 'vue'
import { KButton } from '@kong/kongponents'
import '@kong/kongponents/dist/style.css'
// If using Vue-CLI and webpack, you can likely use
// this path instead: import '~@kong/kongponents/dist/style.css'

const app = createApp(App)

// Register an individual Kongponent
app.component('KButton', KButton)

// Register the vue-bind-once directive as a Vue Plugin
app.use(BindOncePlugin)

// Register an individual Kongponent
app.component('KButton', KButton)

app.mount('#app')
```

Expand Down
12 changes: 12 additions & 0 deletions sandbox/composables/useSandboxToaster.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { onBeforeUnmount } from 'vue'
import { ToastManager } from '@/index'

export default function useSandboxToaster() {
const toaster = new ToastManager()

onBeforeUnmount(() => {
toaster.destroy()
})

return { toaster }
}
4 changes: 2 additions & 2 deletions sandbox/pages/SandboxToaster.vue
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,11 @@
import SandboxTitleComponent from '../components/SandboxTitleComponent.vue'
import SandboxSectionComponent from '../components/SandboxSectionComponent.vue'
import { inject } from 'vue'
import { ToastManager } from '@/index'
import type { Toast } from '@/types'
import { InfoIcon, CheckCircleIcon, WarningIcon, ClearIcon, KongIcon } from '@kong/icons'
import useSandboxToaster from '../composables/useSandboxToaster'
const toaster = new ToastManager()
const { toaster } = useSandboxToaster()
const openToaster = (argument: string) => {
let options: string | Toast = {
Expand Down
Loading

0 comments on commit 613d654

Please sign in to comment.