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 7 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
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: 72 additions & 61 deletions docs/components/toaster.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,65 +2,73 @@

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.

```ts
// main.ts

import { createApp } from 'vue'
import { ToastManager } from '@kong/kongponents'
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.

const app = createApp()
```ts
// composables/useToaster

// 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
})
```
import { onBeforeUnmount } from 'vue'
import { ToastManager } from '@/index'
portikM marked this conversation as resolved.
Show resolved Hide resolved

For TypeScript, you should also augment the global property in your vue declaration file
export default function useToaster() {
const toaster = new ToastManager()

```ts
import { ToastManager } from '@kong/kongponents'
onBeforeUnmount(() => {
toaster.destroy()
})

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
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.

If a global property conflicts with a component’s own property, the component's own property will have higher priority.
:::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
}
```

```ts
const toaster = new ToastManager({ zIndex: 1001 })
```

## Arguments

KToaster is the underlying component rendered by the `ToastManager` instance, so all component properties are passed down via `ToastManager.open()` methods' arguments. The accepted argument type is `string` or object that is instance of `Toast`.
Expand All @@ -79,10 +87,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 +100,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 +130,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 +159,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 +186,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 +214,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 +223,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
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 }
}
portikM marked this conversation as resolved.
Show resolved Hide resolved
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