Skip to content

Commit

Permalink
feat(ktoaster): add ktoaster
Browse files Browse the repository at this point in the history
  • Loading branch information
adamdehaven committed Feb 10, 2022
1 parent 1060202 commit 6a24e4d
Show file tree
Hide file tree
Showing 7 changed files with 479 additions and 2 deletions.
4 changes: 2 additions & 2 deletions docs/.vuepress/clientAppEnhance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Kongponents from '../../src'

// Import component-specific files
import * as icons from '../../src/components/KIcon/icons' // KIcon icons
// import ToastManager from '../../src/components/KToaster/ToastManager'
import ToastManager from '../../src/components/KToaster/ToastManager'

// Import global VuePress components
import ColorSwatch from './components/ColorSwatch.vue'
Expand All @@ -14,7 +14,7 @@ export default defineClientAppEnhance(({ app, router, siteData }) => {
app.config.globalProperties.$icons = Object.keys(icons)

// Register ToastManager
// app.config.globalProperties.$toaster = new ToastManager()
app.config.globalProperties.$toaster = new ToastManager()

// Register all Kongponents
app.use(Kongponents)
Expand Down
1 change: 1 addition & 0 deletions docs/.vuepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export default defineUserConfig<DefaultThemeOptions, ViteBundlerOptions>({
'/components/label',
'/components/modal',
'/components/popover',
'/components/toaster',
'/components/tooltip',
]
},
Expand Down
190 changes: 190 additions & 0 deletions docs/components/toaster.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
# Toaster

**KToaster** - a popup notification typically used to show the result of an action. The toaster can close on its own but can also be manually dismissed.

KToaster is used via the a `ToastManager` instance. All rendering is controlled from ToastManager via an intuitive, imperative api. It is recommended that you initialize it as a singleton in your app such as `this.$toaster`.

```js
import Vue from 'vue';
import { ToastManager } from '@kongponents/ktoaster';

// optional singleton to allow any part of your app access ToastManager
Vue.prototype.$toaster = new ToastManager()
```

Once `ToastManager` is registered as a singleton, you can access it's methods via `this.$toaster` e.g.:

<KButton @click="$toaster.open('Basic Notification')">Open Toaster</KButton>

```vue
<KButton @click="$toaster.open('Basic Toaster')">Open Toaster</KButton>
```

## Arguments

### message

The default argument passed to the toaster is the message.

<KButton @click="$toaster.open('Default message here')">Open Toaster</KButton>

```vue
<KButton @click="$toaster.open('Default message here')">Open Toaster</KButton>
```

### appearance

The Toaster uses the same appearance values as [KAlert](/components/alert) and are applied the same way.

<KButton appearance="primary" @click="openNotification({'appearance': 'info', 'message':'This toaster is a info appeareance'})">Open Toaster</KButton>
<KButton class="success" appearance="primary" @click="openNotification({'appearance': 'success', 'message':'This toaster is a success appeareance'})">Open Toaster</KButton>
<KButton appearance="danger" @click="openNotification({'appearance': 'danger', 'message':'This toaster is a danger appeareance'})">Open Toaster</KButton>
<KButton class="warning" appearance="primary" @click="openNotification({'appearance': 'warning', 'message':'This toaster is a warning appeareance'})">Open Toaster</KButton>

```vue
<template>
<KButton @click="openNotification(toasterOptions)">Open Toaster</KButton>
</template>
<script>
export default {
data () {
return {
toasterOptions: {
appearance: 'danger',
message: 'This toaster is a danger appearance'
}
}
},
methods: {
openNotification(options) {
this.$toaster.open(options)
}
}
}
</script>
```

### timeout

The default timeout is 5000ms (5 seconds) however you can change this to by passing an override argument.

- `timeoutMilliseconds`

<KButton :disabled="timeLeft <= 3" @click="openNotificationElapse({timeoutMilliseconds: 3000, 'appearance': 'success', 'message': `This toaster has a 3 second timeout`})">{{timeLeft > 3 ? 'Open Toaster' : `Closing in ${timeLeft} seconds` }}</KButton>

```vue
<template>
<KButton @click="openNotification(toasterOptions)">Open Toaster</KButton>
</template>
<script>
export default {
data () {
return {
toasterOptions: {
appearance: 'success',
timeoutMilliseconds: 3000,
message: 'This toaster has a 3 second timeout'
},
}
},
methods: {
openNotification(options) {
this.$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

<KButton class="success" appearance="primary" @click="openNotification({timeoutMilliseconds: 10000, message: 'Success Notification', appearance: 'success'})">Open Toaster</KButton>
<KButton appearance="danger" @click="openNotification({'appearance': 'danger', 'message': 'Danger Notification'})">Open Toaster</KButton>
<KButton @click="openNotification('Basic Notification')">Open Toaster</KButton>

<pre class="language-json">
<code>
{{ JSON.stringify(toasters || [], null, 2) }}
</code>
</pre>

```vue
<template>
<KButton class="success" appearance="primary" @click="openNotification({timeoutMilliseconds: 10000, message: 'Success Notification', appearance: 'success'})">Open Toaster</KButton>
<KButton appearance="danger" @click="openNotification({'appearance': 'danger', 'message': 'Danger Notification'})">Open Toaster</KButton>
<KButton @click="openNotification('Basic Notification')">Open Toaster</KButton>
</template>
<script>
export default {
data: function () {
return {
toasters: []
}
},
methods: {
openNotification(options) {
this.$toaster.open(options)
this.toasters = this.$toaster.toasters
}
}
}
</script>
```

## Variations

### Long Content

<br>
<KButton @click="openNotification(`Before you release that email you're writing to spin up a new centralized decision-making group, it's worth talking about the four ways these groups consistently fail. They tend to be domineering, bottlenecked, status-oriented, or inert.`)">Prose</KButton>

<KButton @click="openNotification({message:`Proxy error: Could not proxy request /api/service_packages?fields=&s=%7B%22%24and%22%3A%5B%7B%22name%22%3A%7B%22%24contL%22%3A%22%22%7D%7D%5D%7D&filter=&or=&sort=created_at%2CDESC&join=&limit=100&offset=0&page=1 from localhost:8080 to http://localhost:3000 (ECONNREFUSED).`, appearance: 'danger'})">Raw error message</KButton>

<script lang="ts">
import { defineComponent } from 'vue'

export default defineComponent({
data: function () {
return {
toasters: [],
timeLeft: 4
}
},
methods: {
openNotification(options) {
this.$toaster.open(options)
this.toasters = this.$toaster.toasters
},
openNotificationElapse(options) {
this.$toaster.open(options)
this.toasters = this.$toaster.toasters
this.timeLeft -= 1
const interval = setInterval(() => {
this.timeLeft -= 1
if (this.timeLeft === 0){
this.timeLeft = 4
clearInterval(interval)
}
}, 1000)
},
}
})
</script>

<style lang="scss">
.success.k-button {
--KButtonPrimaryBase: var(--green-400);
--KButtonPrimaryHover: var(--green-300);
--KButtonPrimaryActive: var(--green-500)
}
.warning.k-button {
--KButtonPrimaryBase: var(--yellow-300);
--KButtonPrimaryHover: var(--yellow-200);
--KButtonPrimaryActive: var(--yellow-200);
color: var(--black-70) !important;
}
</style>
90 changes: 90 additions & 0 deletions src/components/KToaster/KToaster.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { mount } from '@cypress/vue'
import KToaster from '@/components/KToaster/KToaster.vue'
import ToastManager from '@/components/KToaster/ToastManager'

describe('KToaster', () => {
// describe('KToaster.vue', () => {
// it('renders toaster', () => {
// const wrapper = mount(KToaster)

// expect(wrapper.findAll('.toaster-container-outer span')).toHaveLength(0)

// wrapper.vm.toasterState.push({ message: 'hey toasty' })
// wrapper.vm.toasterState.push({ appearance: 'success', message: 'hey toasty' })
// wrapper.vm.toasterState.push({ appearance: 'danger', message: 'hey toasty' })
// wrapper.vm.toasterState.push({ appearance: 'danger', message: 'hey toasty' })

// expect(wrapper.findAll('div[role="alert"].success')).toHaveLength(1)
// expect(wrapper.findAll('div[role="alert"].danger')).toHaveLength(2)
// expect(wrapper.findAll('.toaster-container-outer div.k-alert-msg')).toHaveLength(4)

// expect(wrapper).toMatchSnapshot()
// })
// })

// describe('ToastManager', () => {
// it('opens toasters', () => {
// const tm = new ToastManager()

// tm.open('hey toasty')
// tm.open({message: 'yo toasty'})
// tm.open({key: 2, message: 'there has been an alert'})
// expect(tm.toasters).toHaveLength(3)
// })

// it('opens toasters - invalid appearance', () => {
// const tm = new ToastManager()

// tm.open({message: 'yo', appearance: 'ugly'})
// expect(tm.toasters).toHaveLength(1)
// expect(tm.toasters[0].appearance).toBe('info')
// })

// it('dismisses toasters after default timeout', () => {
// const tm = new ToastManager()

// tm.open('hey toasty')
// tm.open('hey toasty')
// expect(tm.toasters).toHaveLength(2)
// jest.advanceTimersByTime(4999)
// expect(tm.toasters).toHaveLength(2)
// jest.advanceTimersByTime(1)
// expect(tm.toasters).toHaveLength(0)
// })

// it('dismisses toasters after timeout per toast', () => {
// const tm = new ToastManager()

// tm.open({ message: 'hey toasty', timeoutMilliseconds: 1000 })
// tm.open({ message: 'hey toasty', timeoutMilliseconds: 2000 })
// tm.open({ message: 'hey toasty', timeoutMilliseconds: 3000 })
// tm.open({ message: 'hey toasty' }) // default 5000 milliseconds

// expect(tm.toasters).toHaveLength(4)
// jest.advanceTimersByTime(1000)
// expect(tm.toasters).toHaveLength(3)
// jest.advanceTimersByTime(1000)
// expect(tm.toasters).toHaveLength(2)
// jest.advanceTimersByTime(1000)
// expect(tm.toasters).toHaveLength(1)
// jest.advanceTimersByTime(1000)
// expect(tm.toasters).toHaveLength(1)
// jest.advanceTimersByTime(1000)
// expect(tm.toasters).toHaveLength(0)
// })

// it('closes toasters', () => {
// const tm = new ToastManager()

// tm.open({ key: '#123', message: 'hey toasty' })
// tm.open({ key: '#345', message: 'hey toasty' })

// expect(tm.toasters).toHaveLength(2)

// tm.close('#345')

// expect(tm.toasters).toHaveLength(1)
// expect(tm.toasters[0].key).toBe('#123')
// })
// })
})
Loading

0 comments on commit 6a24e4d

Please sign in to comment.