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(ktoaster): refactor toastmanager [KHCP-11958] #2220

Merged
merged 13 commits into from
Jun 14, 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
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