Skip to content

Commit

Permalink
feat(vue): add component toast (#632)
Browse files Browse the repository at this point in the history
Co-authored-by: TylerAPfledderer <TylerAPfledderer@users.noreply.github.com>
Co-authored-by: codebender828 <codebender828@gmail.com>
Co-authored-by: codebender828 <codebender828@users.noreply.github.com>
  • Loading branch information
4 people committed May 20, 2023
1 parent fff5982 commit f8b1603
Show file tree
Hide file tree
Showing 19 changed files with 708 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .changeset/great-colts-knock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@ark-ui/vue': minor
---

Add component `Toast`
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
| Switch | 🟢 | 🟢 ||
| Tabs | 🟢 | 🟢 | 🟢 |
| Tags Input | 🟢 | 🟢 | 🟢 |
| Toast | 🟢 | 🟢 | 🟡 |
| Toast | 🟢 | 🟢 | 🟢 |
| Tooltip | 🟢 | 🟢 | 🟢 |

## Contributing
Expand Down
258 changes: 258 additions & 0 deletions packages/vue/src/toast/docs/toast.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
---
id: toast
name: Toast
description:
The toast component is used to give feedback to users after an action has
taken place.
---

## Import

```ts
import {
Toast,
ToastCloseTrigger,
ToastDescription,
ToastGroup,
ToastPlacements,
ToastProvider,
ToastTitle,
useToast,
} from '@ark-ui/vue'
```

## Usage

### Create the AppToastProvider

Use the toast components to compose a `ToastProvider` for your application. The
granular component structure allows access to every DOM element.

```vue
// app-toast-provider.vue
<template>
<ToastProvider>
<ToastPlacements v-slot="{ placements }">
<ToastGroup
v-for="(placement, placementIdx) in placements"
:key="placementIdx"
:placement="placement"
v-slot="{ toasts }"
>
<Toast v-for="toast in toasts" :key="toast.id" :toast="toast">
<ToastTitle />
<ToastDescription />
<ToastCloseTrigger>
<button>Close</button>
</ToastCloseTrigger>
</Toast>
</ToastGroup>
</ToastPlacements>
<slot></slot>
</ToastProvider>
</template>
<script setup lang="ts">
import {
ToastPlacements,
ToastGroup,
Toast,
ToastTitle,
ToastDescription,
ToastCloseTrigger,
ToastProvider,
} from '@ark-ui/vue'
</script>
```

### Create toasts with useToast

The `useToast` hook is your _central toast intelligence unit_ to control the
toasts in your application. Create, update, remove and upsert toasts with the
returned `toast` instance.

```vue
// example-component.vue
<template>
<button @click="handleOnClick">Show Toast</button>
</template>
<script lang="ts" setup>
const toast = useToast()
const handleOnClick = () => {
toast.value.create({
type: 'success',
title: 'Form submitted',
placement: 'bottom',
})
}
</script>
```

#### Use the shorthand type methods

Omitting the type property is supported for toasts with type `success`,
`loading` and `error`.

```tsx
toast.value.success({
title: 'Form submitted',
})
```

```tsx
toast.value.error({
title: 'An error occurred. Please try again!',
})
```

```tsx
toast.value.loading({
title: 'Submitting form',
})
```

### Update a toast by id

Update the description or other options for an existing toast.

```vue
<template>
<button @click="handleOnClick">Show Toast</button>
</template>
<script lang="ts" setup>
const toast.value = useToast()
async function submitForm() {
// simulate very slow request - wait for 3 seconds
await new Promise((r) => setTimeout(r, 3000))
}
const handleOnClick = async () => {
toast.value.create({
id: 'my-toast',
title: 'Submitting form',
placement: 'bottom',
})
await submitForm()
toast.value.update('my-toast', {
title: 'Form submitted successfully',
})
}
</script>
```

### Remove a toast by id

Remove a visible toast imperatively. E.g. on an event or after a async task.

```vue
<template>
<button @click="handleOnClick">Show Toast</button>
</template>
<script lang="ts" setup>
const toast = useToast()
async function submitForm() {
// simulate very slow request - wait for 3 seconds
await new Promise((r) => setTimeout(r, 3000))
}
const handleOnClick = async () => {
toast.value.create({
id: 'my-toast',
title: 'Submitting form',
placement: 'bottom',
})
await submitForm()
toast.value.remove('my-toast')
}
</script>
```

### Upsert a toast by id

`upsert` updates a toast if it exists, or it creates a new toast.

```vue
<template>
<button @click="handleOnClick">Show Toast</button>
</template>
<script lang="ts" setup>
const toast = useToast()
async function submitForm() {
// simulate very slow request - wait for 3 seconds
await new Promise((r) => setTimeout(r, 3000))
}
const handleOnClick = async () => {
toast.value.upsert({
id: 'my-toast',
title: 'Submitting form',
placement: 'bottom',
})
await submitForm()
toast.remove('my-toast')
}
</script>
```

### Visualize a Promise

```vue
<template>
<button @click="handleOnClick">Show toast</button>
</template>
<script lang="ts" setup>
const toast = useToast()
async function submitForm() {
// simulate very slow request - wait for 3 seconds
await new Promise((r) => setTimeout(r, 3000))
}
const handleOnClick = async () => {
await toast.value.promise(submitForm(), {
error: {
id: 'error',
type: 'error',
title: 'An error occurred. Please try again!',
placement: 'bottom',
duration: Infinity,
},
loading: {
id: 'loading',
type: 'loading',
title: 'Submitting form. Please wait.',
placement: 'bottom',
duration: Infinity,
},
success: {
id: 'success',
type: 'success',
title: 'Successfully submitted form.',
placement: 'bottom',
duration: 3_000,
},
})
}
</script>
```

### Toast visibility

Check if a toast is visible by id with `isVisible`.

```tsx
const toast = useToast()

const isVisible = computed(() => toast.value.isVisible('my-toast'))
```
51 changes: 51 additions & 0 deletions packages/vue/src/toast/docs/toast.types.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"ToastProps": { "toast": { "type": "Service", "isRequired": true } },
"ToastGroupProps": { "placement": { "type": "Placement", "isRequired": true } },
"ToastProviderProps": {
"dir": {
"type": "'ltr' | 'rtl'",
"isRequired": false,
"description": "The document's text/writing direction."
},
"getRootNode": {
"type": "() => ShadowRoot | Node | Document",
"isRequired": false,
"description": "A root node to correctly resolve document in custom environments. E.x.: Iframes, Electron."
},
"gutter": {
"type": "string",
"isRequired": false,
"description": "The gutter or spacing between toasts"
},
"id": {
"type": "string",
"isRequired": false,
"description": "The unique identifier of the machine."
},
"max": {
"type": "number",
"isRequired": false,
"description": "The maximum number of toasts that can be shown at once"
},
"offsets": {
"type": "string | Record<'top' | 'right' | 'bottom' | 'left', string>",
"isRequired": false,
"description": "The offset from the safe environment edge of the viewport"
},
"pauseOnInteraction": {
"type": "boolean",
"isRequired": false,
"description": "Whether to pause the toast when interacted with"
},
"pauseOnPageIdle": {
"type": "boolean",
"isRequired": false,
"description": "Whether to pause toast when the user leaves the browser tab"
},
"zIndex": {
"type": "number",
"isRequired": false,
"description": "The z-index applied to each toast group"
}
}
}
8 changes: 8 additions & 0 deletions packages/vue/src/toast/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export { Toast, type ToastProps } from './toast'
export { ToastCloseTrigger } from './toast-close-trigger'
export { ToastDescription, type ToastDescriptionProps } from './toast-description'
export { ToastGroup, type ToastGroupProps } from './toast-group'
export { ToastPlacements } from './toast-placements'
export { ToastProvider, useToast, type ToastProviderProps } from './toast-provider'
export { ToastTitle, type ToastTitleProps } from './toast-title'
export { toastAnatomy } from './toast.anatomy'
10 changes: 10 additions & 0 deletions packages/vue/src/toast/stories/basic.stories.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script setup lang="ts">
import { ChakraToastProvider } from './chakra-toast.provider'
import ExampleComponent from './example-component.vue'
</script>
<template>
<ChakraToastProvider>
<h1>Hello World</h1>
<ExampleComponent />
</ChakraToastProvider>
</template>
44 changes: 44 additions & 0 deletions packages/vue/src/toast/stories/chakra-toast.provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { defineComponent } from 'vue'
import {
Toast,
ToastCloseTrigger,
ToastDescription,
ToastGroup,
ToastProvider,
ToastTitle,
} from '..'
import type { ToastsContext } from '../toast-group'
import { ToastPlacements, type PlacementsContext } from '../toast-placements'

export const ChakraToastProvider = defineComponent((_, { slots }) => {
return () => (
<ToastProvider>
<ToastPlacements>
{({ placements }: { placements: PlacementsContext['placements'] }) => (
<>
{placements.map((placement) => (
<ToastGroup placement={placement} key={placement}>
{{
default: ({ toasts }: { toasts: ToastsContext['toasts'] }) => (
<>
{toasts.map((toast) => (
<Toast key={toast.id} toast={toast}>
<ToastTitle />
<ToastDescription />
<ToastCloseTrigger>
<button>Close</button>
</ToastCloseTrigger>
</Toast>
))}
</>
),
}}
</ToastGroup>
))}
</>
)}
</ToastPlacements>
{slots.default?.()}
</ToastProvider>
)
})
Loading

1 comment on commit f8b1603

@vercel
Copy link

@vercel vercel bot commented on f8b1603 May 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.