diff --git a/apps/www/.vitepress/theme/components/LandingPage.vue b/apps/www/.vitepress/theme/components/LandingPage.vue index dd3db7ea..ea6bab61 100644 --- a/apps/www/.vitepress/theme/components/LandingPage.vue +++ b/apps/www/.vitepress/theme/components/LandingPage.vue @@ -3,6 +3,7 @@ import PageHeader from '../components/PageHeader.vue' import PageHeaderHeading from '../components/PageHeaderHeading.vue' import PageHeaderDescription from '../components/PageHeaderDescription.vue' import ExamplesNav from '../components/ExamplesNav.vue' +import { announcementConfig } from '../config/site' import ArrowRightIcon from '~icons/radix-icons/arrow-right' import GitHubIcon from '~icons/radix-icons/github-logo' @@ -16,12 +17,12 @@ import DashboardExample from '@/examples/dashboard/Example.vue' - 🎉 - New form & pagination component - New form & pagination component + {{ announcementConfig.icon }} + {{ announcementConfig.title }} + {{ announcementConfig.title }} diff --git a/apps/www/.vitepress/theme/config/docs.ts b/apps/www/.vitepress/theme/config/docs.ts index ba060901..52cddca3 100644 --- a/apps/www/.vitepress/theme/config/docs.ts +++ b/apps/www/.vitepress/theme/config/docs.ts @@ -216,7 +216,6 @@ export const docsConfig: DocsConfig = { { title: 'Form', href: '/docs/components/form', - label: 'New', items: [], }, { @@ -247,7 +246,6 @@ export const docsConfig: DocsConfig = { { title: 'Pagination', href: '/docs/components/pagination', - label: 'New', items: [], }, { @@ -315,13 +313,12 @@ export const docsConfig: DocsConfig = { href: '/docs/components/textarea', items: [], }, - // { - // title: "Toast", - // href: "#", - // label: "Soon", - // disabled: true, - // items: [] - // }, + { + title: 'Toast', + href: '/docs/components/toast', + label: 'New', + items: [], + }, { title: 'Toggle', href: '/docs/components/toggle', diff --git a/apps/www/.vitepress/theme/config/site.ts b/apps/www/.vitepress/theme/config/site.ts index b4ce6775..9d7860b5 100644 --- a/apps/www/.vitepress/theme/config/site.ts +++ b/apps/www/.vitepress/theme/config/site.ts @@ -12,3 +12,9 @@ export const siteConfig = { }, keywords: 'shadcn,Vue,Nuxt,Vue Components,TailwindCSS,Radix Vue', } + +export const announcementConfig = { + icon: '✨', + title: 'New Toast compoent', + link: '/docs/components/toast', +} diff --git a/apps/www/.vitepress/theme/layout/ExamplesLayout.vue b/apps/www/.vitepress/theme/layout/ExamplesLayout.vue index dbdd458e..c81a3f9f 100644 --- a/apps/www/.vitepress/theme/layout/ExamplesLayout.vue +++ b/apps/www/.vitepress/theme/layout/ExamplesLayout.vue @@ -3,6 +3,7 @@ import PageHeader from '../components/PageHeader.vue' import PageHeaderHeading from '../components/PageHeaderHeading.vue' import PageHeaderDescription from '../components/PageHeaderDescription.vue' import ExamplesNav from '../components/ExamplesNav.vue' +import { announcementConfig } from '../config/site' import ArrowRightIcon from '~icons/radix-icons/arrow-right' import { buttonVariants } from '@/lib/registry/new-york/ui/button' @@ -14,13 +15,13 @@ import { cn } from '@/lib/utils' - 🎉 - New form & pagination component + {{ announcementConfig.icon }} + {{ announcementConfig.title }} - New form & pagination component + {{ announcementConfig.title }} diff --git a/apps/www/.vitepress/theme/layout/MainLayout.vue b/apps/www/.vitepress/theme/layout/MainLayout.vue index 533e367b..9a0b7a0c 100644 --- a/apps/www/.vitepress/theme/layout/MainLayout.vue +++ b/apps/www/.vitepress/theme/layout/MainLayout.vue @@ -12,11 +12,12 @@ import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, Command import { Button } from '@/lib/registry/default/ui/button' import RadixIconsGithubLogo from '~icons/radix-icons/github-logo' -import TablerBrandX from '~icons/tabler/brand-x' import RadixIconsMoon from '~icons/radix-icons/moon' import RadixIconsSun from '~icons/radix-icons/sun' import { useConfigStore } from '@/stores/config' import { Dialog, DialogContent } from '@/lib/registry/default/ui/dialog' +import { Toaster as DefaultToaster } from '@/lib/registry/default/ui/toast' +import { Toaster as NewYorkToaster } from '@/lib/registry/new-york/ui/toast' import File from '~icons/radix-icons/file' import Circle from '~icons/radix-icons/circle' @@ -279,5 +280,7 @@ watch(() => $route.path, (n) => { + + diff --git a/apps/www/__registry__/index.ts b/apps/www/__registry__/index.ts index 0251b406..61fbc8ca 100644 --- a/apps/www/__registry__/index.ts +++ b/apps/www/__registry__/index.ts @@ -103,14 +103,14 @@ export const Index = { CheckboxFormMultiple: { name: 'CheckboxFormMultiple', type: 'components:example', - registryDependencies: ['button', 'form', 'checkbox'], + registryDependencies: ['button', 'form', 'checkbox', 'toast'], component: () => import('../src/lib/registry/default/example/CheckboxFormMultiple.vue').then(m => m.default), files: ['../src/lib/registry/default/example/CheckboxFormMultiple.vue'], }, CheckboxFormSingle: { name: 'CheckboxFormSingle', type: 'components:example', - registryDependencies: ['button', 'form', 'checkbox'], + registryDependencies: ['button', 'form', 'checkbox', 'toast'], component: () => import('../src/lib/registry/default/example/CheckboxFormSingle.vue').then(m => m.default), files: ['../src/lib/registry/default/example/CheckboxFormSingle.vue'], }, @@ -145,7 +145,7 @@ export const Index = { ComboboxForm: { name: 'ComboboxForm', type: 'components:example', - registryDependencies: ['utils', 'button', 'command', 'form', 'popover'], + registryDependencies: ['utils', 'button', 'command', 'form', 'popover', 'toast'], component: () => import('../src/lib/registry/default/example/ComboboxForm.vue').then(m => m.default), files: ['../src/lib/registry/default/example/ComboboxForm.vue'], }, @@ -194,7 +194,7 @@ export const Index = { DatePickerForm: { name: 'DatePickerForm', type: 'components:example', - registryDependencies: ['utils', 'button', 'calendar', 'form', 'popover'], + registryDependencies: ['utils', 'button', 'calendar', 'form', 'popover', 'toast'], component: () => import('../src/lib/registry/default/example/DatePickerForm.vue').then(m => m.default), files: ['../src/lib/registry/default/example/DatePickerForm.vue'], }, @@ -257,14 +257,14 @@ export const Index = { InputForm: { name: 'InputForm', type: 'components:example', - registryDependencies: ['button', 'form', 'input'], + registryDependencies: ['button', 'form', 'input', 'toast'], component: () => import('../src/lib/registry/default/example/InputForm.vue').then(m => m.default), files: ['../src/lib/registry/default/example/InputForm.vue'], }, InputFormAutoAnimate: { name: 'InputFormAutoAnimate', type: 'components:example', - registryDependencies: ['button', 'form', 'input'], + registryDependencies: ['button', 'form', 'input', 'toast'], component: () => import('../src/lib/registry/default/example/InputFormAutoAnimate.vue').then(m => m.default), files: ['../src/lib/registry/default/example/InputFormAutoAnimate.vue'], }, @@ -341,7 +341,7 @@ export const Index = { RadioGroupForm: { name: 'RadioGroupForm', type: 'components:example', - registryDependencies: ['button', 'form', 'radio-group'], + registryDependencies: ['button', 'form', 'radio-group', 'toast'], component: () => import('../src/lib/registry/default/example/RadioGroupForm.vue').then(m => m.default), files: ['../src/lib/registry/default/example/RadioGroupForm.vue'], }, @@ -362,7 +362,7 @@ export const Index = { SelectForm: { name: 'SelectForm', type: 'components:example', - registryDependencies: ['button', 'form', 'select'], + registryDependencies: ['button', 'form', 'select', 'toast'], component: () => import('../src/lib/registry/default/example/SelectForm.vue').then(m => m.default), files: ['../src/lib/registry/default/example/SelectForm.vue'], }, @@ -404,7 +404,7 @@ export const Index = { SwitchForm: { name: 'SwitchForm', type: 'components:example', - registryDependencies: ['button', 'form', 'switch'], + registryDependencies: ['button', 'form', 'switch', 'toast'], component: () => import('../src/lib/registry/default/example/SwitchForm.vue').then(m => m.default), files: ['../src/lib/registry/default/example/SwitchForm.vue'], }, @@ -439,7 +439,7 @@ export const Index = { TextareaForm: { name: 'TextareaForm', type: 'components:example', - registryDependencies: ['button', 'form', 'textarea'], + registryDependencies: ['button', 'form', 'textarea', 'toast'], component: () => import('../src/lib/registry/default/example/TextareaForm.vue').then(m => m.default), files: ['../src/lib/registry/default/example/TextareaForm.vue'], }, @@ -464,6 +464,41 @@ export const Index = { component: () => import('../src/lib/registry/default/example/TextareaWithText.vue').then(m => m.default), files: ['../src/lib/registry/default/example/TextareaWithText.vue'], }, + ToastDemo: { + name: 'ToastDemo', + type: 'components:example', + registryDependencies: ['button', 'use-toast'], + component: () => import('../src/lib/registry/default/example/ToastDemo.vue').then(m => m.default), + files: ['../src/lib/registry/default/example/ToastDemo.vue'], + }, + ToastDestructive: { + name: 'ToastDestructive', + type: 'components:example', + registryDependencies: ['button', 'use-toast', 'toast'], + component: () => import('../src/lib/registry/default/example/ToastDestructive.vue').then(m => m.default), + files: ['../src/lib/registry/default/example/ToastDestructive.vue'], + }, + ToastSimple: { + name: 'ToastSimple', + type: 'components:example', + registryDependencies: ['button', 'use-toast'], + component: () => import('../src/lib/registry/default/example/ToastSimple.vue').then(m => m.default), + files: ['../src/lib/registry/default/example/ToastSimple.vue'], + }, + ToastWithAction: { + name: 'ToastWithAction', + type: 'components:example', + registryDependencies: ['button', 'use-toast', 'toast'], + component: () => import('../src/lib/registry/default/example/ToastWithAction.vue').then(m => m.default), + files: ['../src/lib/registry/default/example/ToastWithAction.vue'], + }, + ToastWithTitle: { + name: 'ToastWithTitle', + type: 'components:example', + registryDependencies: ['button', 'use-toast'], + component: () => import('../src/lib/registry/default/example/ToastWithTitle.vue').then(m => m.default), + files: ['../src/lib/registry/default/example/ToastWithTitle.vue'], + }, ToggleDemo: { name: 'ToggleDemo', type: 'components:example', @@ -581,21 +616,21 @@ export const Index = { type: 'components:example', registryDependencies: ['button', 'card', 'themes', 'config'], component: () => import('../src/lib/registry/default/example/Cards/ActivityGoal.vue').then(m => m.default), - files: ['../src/lib/registry/default/example/ActivityGoal.vue'], + files: ['../src/lib/registry/default/example/Cards/ActivityGoal.vue'], }, DataTable: { name: 'DataTable', type: 'components:example', registryDependencies: ['button', 'checkbox', 'dropdown-menu', 'input', 'table', 'card', 'utils'], component: () => import('../src/lib/registry/default/example/Cards/DataTable.vue').then(m => m.default), - files: ['../src/lib/registry/default/example/DataTable.vue'], + files: ['../src/lib/registry/default/example/Cards/DataTable.vue'], }, Metric: { name: 'Metric', type: 'components:example', registryDependencies: ['card', 'config'], component: () => import('../src/lib/registry/default/example/Cards/Metric.vue').then(m => m.default), - files: ['../src/lib/registry/default/example/Metric.vue'], + files: ['../src/lib/registry/default/example/Cards/Metric.vue'], }, }, 'new-york': { @@ -700,14 +735,14 @@ export const Index = { CheckboxFormMultiple: { name: 'CheckboxFormMultiple', type: 'components:example', - registryDependencies: ['button', 'form', 'checkbox'], + registryDependencies: ['button', 'form', 'checkbox', 'toast'], component: () => import('../src/lib/registry/new-york/example/CheckboxFormMultiple.vue').then(m => m.default), files: ['../src/lib/registry/new-york/example/CheckboxFormMultiple.vue'], }, CheckboxFormSingle: { name: 'CheckboxFormSingle', type: 'components:example', - registryDependencies: ['button', 'form', 'checkbox'], + registryDependencies: ['button', 'form', 'checkbox', 'toast'], component: () => import('../src/lib/registry/new-york/example/CheckboxFormSingle.vue').then(m => m.default), files: ['../src/lib/registry/new-york/example/CheckboxFormSingle.vue'], }, @@ -742,7 +777,7 @@ export const Index = { ComboboxForm: { name: 'ComboboxForm', type: 'components:example', - registryDependencies: ['utils', 'button', 'command', 'form', 'popover'], + registryDependencies: ['utils', 'button', 'command', 'form', 'popover', 'toast'], component: () => import('../src/lib/registry/new-york/example/ComboboxForm.vue').then(m => m.default), files: ['../src/lib/registry/new-york/example/ComboboxForm.vue'], }, @@ -791,7 +826,7 @@ export const Index = { DatePickerForm: { name: 'DatePickerForm', type: 'components:example', - registryDependencies: ['utils', 'button', 'calendar', 'form', 'popover'], + registryDependencies: ['utils', 'button', 'calendar', 'form', 'popover', 'toast'], component: () => import('../src/lib/registry/new-york/example/DatePickerForm.vue').then(m => m.default), files: ['../src/lib/registry/new-york/example/DatePickerForm.vue'], }, @@ -854,14 +889,14 @@ export const Index = { InputForm: { name: 'InputForm', type: 'components:example', - registryDependencies: ['button', 'form', 'input'], + registryDependencies: ['button', 'form', 'input', 'toast'], component: () => import('../src/lib/registry/new-york/example/InputForm.vue').then(m => m.default), files: ['../src/lib/registry/new-york/example/InputForm.vue'], }, InputFormAutoAnimate: { name: 'InputFormAutoAnimate', type: 'components:example', - registryDependencies: ['button', 'form', 'input'], + registryDependencies: ['button', 'form', 'input', 'toast'], component: () => import('../src/lib/registry/new-york/example/InputFormAutoAnimate.vue').then(m => m.default), files: ['../src/lib/registry/new-york/example/InputFormAutoAnimate.vue'], }, @@ -938,7 +973,7 @@ export const Index = { RadioGroupForm: { name: 'RadioGroupForm', type: 'components:example', - registryDependencies: ['button', 'form', 'radio-group'], + registryDependencies: ['button', 'form', 'radio-group', 'toast'], component: () => import('../src/lib/registry/new-york/example/RadioGroupForm.vue').then(m => m.default), files: ['../src/lib/registry/new-york/example/RadioGroupForm.vue'], }, @@ -959,7 +994,7 @@ export const Index = { SelectForm: { name: 'SelectForm', type: 'components:example', - registryDependencies: ['button', 'form', 'select'], + registryDependencies: ['button', 'form', 'select', 'toast'], component: () => import('../src/lib/registry/new-york/example/SelectForm.vue').then(m => m.default), files: ['../src/lib/registry/new-york/example/SelectForm.vue'], }, @@ -1001,7 +1036,7 @@ export const Index = { SwitchForm: { name: 'SwitchForm', type: 'components:example', - registryDependencies: ['button', 'form', 'switch'], + registryDependencies: ['button', 'form', 'switch', 'toast'], component: () => import('../src/lib/registry/new-york/example/SwitchForm.vue').then(m => m.default), files: ['../src/lib/registry/new-york/example/SwitchForm.vue'], }, @@ -1036,7 +1071,7 @@ export const Index = { TextareaForm: { name: 'TextareaForm', type: 'components:example', - registryDependencies: ['button', 'form', 'textarea'], + registryDependencies: ['button', 'form', 'textarea', 'toast'], component: () => import('../src/lib/registry/new-york/example/TextareaForm.vue').then(m => m.default), files: ['../src/lib/registry/new-york/example/TextareaForm.vue'], }, @@ -1061,6 +1096,41 @@ export const Index = { component: () => import('../src/lib/registry/new-york/example/TextareaWithText.vue').then(m => m.default), files: ['../src/lib/registry/new-york/example/TextareaWithText.vue'], }, + ToastDemo: { + name: 'ToastDemo', + type: 'components:example', + registryDependencies: ['button', 'use-toast'], + component: () => import('../src/lib/registry/new-york/example/ToastDemo.vue').then(m => m.default), + files: ['../src/lib/registry/new-york/example/ToastDemo.vue'], + }, + ToastDestructive: { + name: 'ToastDestructive', + type: 'components:example', + registryDependencies: ['button', 'use-toast', 'toast'], + component: () => import('../src/lib/registry/new-york/example/ToastDestructive.vue').then(m => m.default), + files: ['../src/lib/registry/new-york/example/ToastDestructive.vue'], + }, + ToastSimple: { + name: 'ToastSimple', + type: 'components:example', + registryDependencies: ['button', 'use-toast'], + component: () => import('../src/lib/registry/new-york/example/ToastSimple.vue').then(m => m.default), + files: ['../src/lib/registry/new-york/example/ToastSimple.vue'], + }, + ToastWithAction: { + name: 'ToastWithAction', + type: 'components:example', + registryDependencies: ['button', 'use-toast', 'toast'], + component: () => import('../src/lib/registry/new-york/example/ToastWithAction.vue').then(m => m.default), + files: ['../src/lib/registry/new-york/example/ToastWithAction.vue'], + }, + ToastWithTitle: { + name: 'ToastWithTitle', + type: 'components:example', + registryDependencies: ['button', 'use-toast'], + component: () => import('../src/lib/registry/new-york/example/ToastWithTitle.vue').then(m => m.default), + files: ['../src/lib/registry/new-york/example/ToastWithTitle.vue'], + }, ToggleDemo: { name: 'ToggleDemo', type: 'components:example', @@ -1178,21 +1248,21 @@ export const Index = { type: 'components:example', registryDependencies: ['button', 'card', 'themes', 'config'], component: () => import('../src/lib/registry/new-york/example/Cards/ActivityGoal.vue').then(m => m.default), - files: ['../src/lib/registry/new-york/example/ActivityGoal.vue'], + files: ['../src/lib/registry/new-york/example/Cards/ActivityGoal.vue'], }, DataTable: { name: 'DataTable', type: 'components:example', registryDependencies: ['button', 'checkbox', 'dropdown-menu', 'input', 'table', 'card', 'utils'], component: () => import('../src/lib/registry/new-york/example/Cards/DataTable.vue').then(m => m.default), - files: ['../src/lib/registry/new-york/example/DataTable.vue'], + files: ['../src/lib/registry/new-york/example/Cards/DataTable.vue'], }, Metric: { name: 'Metric', type: 'components:example', registryDependencies: ['card', 'config'], component: () => import('../src/lib/registry/new-york/example/Cards/Metric.vue').then(m => m.default), - files: ['../src/lib/registry/new-york/example/Metric.vue'], + files: ['../src/lib/registry/new-york/example/Cards/Metric.vue'], }, }, } diff --git a/apps/www/src/content/docs/components/toast.md b/apps/www/src/content/docs/components/toast.md new file mode 100644 index 00000000..fcba11d6 --- /dev/null +++ b/apps/www/src/content/docs/components/toast.md @@ -0,0 +1,93 @@ +--- +title: Toast +description: A succinct message that is displayed temporarily. +source: apps/www/src/lib/registry/default/ui/toast +primitive: https://www.radix-vue.com/components/toast.html +--- + + + + +## Installation + + + + +### Run the following command + +```bash +npx shadcn-vue@latest add toast +``` + +### Add the Toaster component + +Add the following `Toaster` component to your `App.vue` file: + +```vue title="App.vue" {2,6} + + + + + +``` + + + + +## Usage + +The `useToast` hook returns a `toast` function that you can use to display a toast. + +```tsx +import { useToast } from '@/components/ui/toast/use-toast' +``` + +```vue + + + + { + toast({ + title: 'Scheduled: Catch up', + description: 'Friday, February 10, 2023 at 5:57 PM', + }); + }" + > + Add to calander + + +``` + + + +To display multiple toasts at the same time, you can update the `TOAST_LIMIT` in `use-toast.ts`. + + + +## Examples + +### Simple + + + +### With Title + + + +### With Action + + + +### Destructive + +Use `toast({ variant: "destructive" })` to display a destructive toast. + + diff --git a/apps/www/src/examples/forms/components/AccountForm.vue b/apps/www/src/examples/forms/components/AccountForm.vue index 43d21c13..d2e92a39 100644 --- a/apps/www/src/examples/forms/components/AccountForm.vue +++ b/apps/www/src/examples/forms/components/AccountForm.vue @@ -1,5 +1,5 @@ diff --git a/apps/www/src/examples/forms/components/AppearanceForm.vue b/apps/www/src/examples/forms/components/AppearanceForm.vue index 9b58d9c1..363a424f 100644 --- a/apps/www/src/examples/forms/components/AppearanceForm.vue +++ b/apps/www/src/examples/forms/components/AppearanceForm.vue @@ -1,4 +1,5 @@ diff --git a/apps/www/src/examples/forms/components/DisplayForm.vue b/apps/www/src/examples/forms/components/DisplayForm.vue index ea74a654..cf4c49a7 100644 --- a/apps/www/src/examples/forms/components/DisplayForm.vue +++ b/apps/www/src/examples/forms/components/DisplayForm.vue @@ -1,4 +1,5 @@ diff --git a/apps/www/src/examples/forms/components/NotificationsForm.vue b/apps/www/src/examples/forms/components/NotificationsForm.vue index f1eb20ca..62fefdc4 100644 --- a/apps/www/src/examples/forms/components/NotificationsForm.vue +++ b/apps/www/src/examples/forms/components/NotificationsForm.vue @@ -1,4 +1,5 @@ diff --git a/apps/www/src/examples/forms/components/ProfileForm.vue b/apps/www/src/examples/forms/components/ProfileForm.vue index e53df428..916a78f4 100644 --- a/apps/www/src/examples/forms/components/ProfileForm.vue +++ b/apps/www/src/examples/forms/components/ProfileForm.vue @@ -1,5 +1,5 @@ diff --git a/apps/www/src/lib/registry/default/example/CheckboxFormMultiple.vue b/apps/www/src/lib/registry/default/example/CheckboxFormMultiple.vue index 81cd6fe1..cba68c1d 100644 --- a/apps/www/src/lib/registry/default/example/CheckboxFormMultiple.vue +++ b/apps/www/src/lib/registry/default/example/CheckboxFormMultiple.vue @@ -1,4 +1,5 @@ diff --git a/apps/www/src/lib/registry/default/example/CheckboxFormSingle.vue b/apps/www/src/lib/registry/default/example/CheckboxFormSingle.vue index 4982b6a2..4cb8453a 100644 --- a/apps/www/src/lib/registry/default/example/CheckboxFormSingle.vue +++ b/apps/www/src/lib/registry/default/example/CheckboxFormSingle.vue @@ -1,4 +1,5 @@ diff --git a/apps/www/src/lib/registry/default/example/ComboboxForm.vue b/apps/www/src/lib/registry/default/example/ComboboxForm.vue index d4981802..5ab4b6bb 100644 --- a/apps/www/src/lib/registry/default/example/ComboboxForm.vue +++ b/apps/www/src/lib/registry/default/example/ComboboxForm.vue @@ -1,4 +1,5 @@ diff --git a/apps/www/src/lib/registry/default/example/DatePickerForm.vue b/apps/www/src/lib/registry/default/example/DatePickerForm.vue index b57292a3..9d6e79a5 100644 --- a/apps/www/src/lib/registry/default/example/DatePickerForm.vue +++ b/apps/www/src/lib/registry/default/example/DatePickerForm.vue @@ -1,4 +1,5 @@ diff --git a/apps/www/src/lib/registry/default/example/InputForm.vue b/apps/www/src/lib/registry/default/example/InputForm.vue index 65e138bf..b03dedef 100644 --- a/apps/www/src/lib/registry/default/example/InputForm.vue +++ b/apps/www/src/lib/registry/default/example/InputForm.vue @@ -1,4 +1,5 @@ diff --git a/apps/www/src/lib/registry/default/example/InputFormAutoAnimate.vue b/apps/www/src/lib/registry/default/example/InputFormAutoAnimate.vue index d75b37d6..682ffdc7 100644 --- a/apps/www/src/lib/registry/default/example/InputFormAutoAnimate.vue +++ b/apps/www/src/lib/registry/default/example/InputFormAutoAnimate.vue @@ -1,4 +1,5 @@ diff --git a/apps/www/src/lib/registry/default/example/RadioGroupForm.vue b/apps/www/src/lib/registry/default/example/RadioGroupForm.vue index 967950f6..adc5f273 100644 --- a/apps/www/src/lib/registry/default/example/RadioGroupForm.vue +++ b/apps/www/src/lib/registry/default/example/RadioGroupForm.vue @@ -1,4 +1,5 @@ diff --git a/apps/www/src/lib/registry/default/example/SelectForm.vue b/apps/www/src/lib/registry/default/example/SelectForm.vue index 991a4274..a46653ab 100644 --- a/apps/www/src/lib/registry/default/example/SelectForm.vue +++ b/apps/www/src/lib/registry/default/example/SelectForm.vue @@ -1,4 +1,5 @@ diff --git a/apps/www/src/lib/registry/default/example/SwitchForm.vue b/apps/www/src/lib/registry/default/example/SwitchForm.vue index 9c9f4bcd..ea5e4c38 100644 --- a/apps/www/src/lib/registry/default/example/SwitchForm.vue +++ b/apps/www/src/lib/registry/default/example/SwitchForm.vue @@ -1,4 +1,5 @@ diff --git a/apps/www/src/lib/registry/default/example/TextareaForm.vue b/apps/www/src/lib/registry/default/example/TextareaForm.vue index 8befc0ee..e6f9dece 100644 --- a/apps/www/src/lib/registry/default/example/TextareaForm.vue +++ b/apps/www/src/lib/registry/default/example/TextareaForm.vue @@ -1,4 +1,5 @@ diff --git a/apps/www/src/lib/registry/default/example/ToastDemo.vue b/apps/www/src/lib/registry/default/example/ToastDemo.vue new file mode 100644 index 00000000..abb8cbb7 --- /dev/null +++ b/apps/www/src/lib/registry/default/example/ToastDemo.vue @@ -0,0 +1,19 @@ + + + + { + toast({ + title: 'Scheduled: Catch up', + description: 'Friday, February 10, 2023 at 5:57 PM', + }); + }" + > + Add to calander + + diff --git a/apps/www/src/lib/registry/default/example/ToastDestructive.vue b/apps/www/src/lib/registry/default/example/ToastDestructive.vue new file mode 100644 index 00000000..0cf00554 --- /dev/null +++ b/apps/www/src/lib/registry/default/example/ToastDestructive.vue @@ -0,0 +1,27 @@ + + + + { + toast({ + title: 'Uh oh! Something went wrong.', + description: 'There was a problem with your request.', + variant: 'destructive', + action: h(ToastAction, { + altText: 'Try again', + }, { + default: () => 'Try again', + }), + }); + }" + > + Show Toast + + diff --git a/apps/www/src/lib/registry/default/example/ToastSimple.vue b/apps/www/src/lib/registry/default/example/ToastSimple.vue new file mode 100644 index 00000000..2310017e --- /dev/null +++ b/apps/www/src/lib/registry/default/example/ToastSimple.vue @@ -0,0 +1,18 @@ + + + + { + toast({ + description: 'Your message has been sent.', + }); + }" + > + Show Toast + + diff --git a/apps/www/src/lib/registry/default/example/ToastWithAction.vue b/apps/www/src/lib/registry/default/example/ToastWithAction.vue new file mode 100644 index 00000000..03f7640a --- /dev/null +++ b/apps/www/src/lib/registry/default/example/ToastWithAction.vue @@ -0,0 +1,26 @@ + + + + { + toast({ + title: 'Uh oh! Something went wrong.', + description: 'There was a problem with your request.', + action: h(ToastAction, { + altText: 'Try again', + }, { + default: () => 'Try again', + }), + }); + }" + > + Show Toast + + diff --git a/apps/www/src/lib/registry/default/example/ToastWithTitle.vue b/apps/www/src/lib/registry/default/example/ToastWithTitle.vue new file mode 100644 index 00000000..800bf9e3 --- /dev/null +++ b/apps/www/src/lib/registry/default/example/ToastWithTitle.vue @@ -0,0 +1,19 @@ + + + + { + toast({ + title: 'Uh oh! Something went wrong.', + description: 'There was a problem with your request.', + }); + }" + > + Show Toast + + diff --git a/apps/www/src/lib/registry/default/ui/switch/Switch.vue b/apps/www/src/lib/registry/default/ui/switch/Switch.vue index 5de798cc..25dd554f 100644 --- a/apps/www/src/lib/registry/default/ui/switch/Switch.vue +++ b/apps/www/src/lib/registry/default/ui/switch/Switch.vue @@ -12,7 +12,6 @@ const props = defineProps() const emits = defineEmits() const forwarded = useForwardPropsEmits(props, emits) -console.log(props, forwarded) diff --git a/apps/www/src/lib/registry/default/ui/toast/Toast.vue b/apps/www/src/lib/registry/default/ui/toast/Toast.vue new file mode 100644 index 00000000..980f2fb4 --- /dev/null +++ b/apps/www/src/lib/registry/default/ui/toast/Toast.vue @@ -0,0 +1,32 @@ + + + + + + + + + diff --git a/apps/www/src/lib/registry/default/ui/toast/ToastAction.vue b/apps/www/src/lib/registry/default/ui/toast/ToastAction.vue new file mode 100644 index 00000000..b5ccbefe --- /dev/null +++ b/apps/www/src/lib/registry/default/ui/toast/ToastAction.vue @@ -0,0 +1,12 @@ + + + + + + + diff --git a/apps/www/src/lib/registry/default/ui/toast/ToastClose.vue b/apps/www/src/lib/registry/default/ui/toast/ToastClose.vue new file mode 100644 index 00000000..766593cf --- /dev/null +++ b/apps/www/src/lib/registry/default/ui/toast/ToastClose.vue @@ -0,0 +1,15 @@ + + + + + + + diff --git a/apps/www/src/lib/registry/default/ui/toast/ToastDescription.vue b/apps/www/src/lib/registry/default/ui/toast/ToastDescription.vue new file mode 100644 index 00000000..5f71b1a8 --- /dev/null +++ b/apps/www/src/lib/registry/default/ui/toast/ToastDescription.vue @@ -0,0 +1,12 @@ + + + + + + + diff --git a/apps/www/src/lib/registry/default/ui/toast/ToastProvider.vue b/apps/www/src/lib/registry/default/ui/toast/ToastProvider.vue new file mode 100644 index 00000000..340cbd83 --- /dev/null +++ b/apps/www/src/lib/registry/default/ui/toast/ToastProvider.vue @@ -0,0 +1,11 @@ + + + + + + + diff --git a/apps/www/src/lib/registry/default/ui/toast/ToastTitle.vue b/apps/www/src/lib/registry/default/ui/toast/ToastTitle.vue new file mode 100644 index 00000000..b317850a --- /dev/null +++ b/apps/www/src/lib/registry/default/ui/toast/ToastTitle.vue @@ -0,0 +1,12 @@ + + + + + + + diff --git a/apps/www/src/lib/registry/default/ui/toast/ToastViewport.vue b/apps/www/src/lib/registry/default/ui/toast/ToastViewport.vue new file mode 100644 index 00000000..5f2f2b95 --- /dev/null +++ b/apps/www/src/lib/registry/default/ui/toast/ToastViewport.vue @@ -0,0 +1,10 @@ + + + + + diff --git a/apps/www/src/lib/registry/default/ui/toast/Toaster.vue b/apps/www/src/lib/registry/default/ui/toast/Toaster.vue new file mode 100644 index 00000000..f86ce40b --- /dev/null +++ b/apps/www/src/lib/registry/default/ui/toast/Toaster.vue @@ -0,0 +1,30 @@ + + + + + + + + {{ toast.title }} + + + + + + + {{ toast.description }} + + + + + + + + + diff --git a/apps/www/src/lib/registry/default/ui/toast/index.ts b/apps/www/src/lib/registry/default/ui/toast/index.ts new file mode 100644 index 00000000..331bf98c --- /dev/null +++ b/apps/www/src/lib/registry/default/ui/toast/index.ts @@ -0,0 +1,27 @@ +export { default as Toaster } from './Toaster.vue' +export { default as Toast } from './Toast.vue' +export { default as ToastViewport } from './ToastViewport.vue' +export { default as ToastAction } from './ToastAction.vue' +export { default as ToastClose } from './ToastClose.vue' +export { default as ToastTitle } from './ToastTitle.vue' +export { default as ToastDescription } from './ToastDescription.vue' +export { default as ToastProvider } from './ToastProvider.vue' +export { toast, useToast } from './use-toast' + +import { cva } from 'class-variance-authority' + +export const toastVariants = cva( + 'group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full', + { + variants: { + variant: { + default: 'border bg-background text-foreground', + destructive: + 'destructive group border-destructive bg-destructive text-destructive-foreground', + }, + }, + defaultVariants: { + variant: 'default', + }, + }, +) diff --git a/apps/www/src/lib/registry/default/ui/toast/use-toast.ts b/apps/www/src/lib/registry/default/ui/toast/use-toast.ts new file mode 100644 index 00000000..02e6973b --- /dev/null +++ b/apps/www/src/lib/registry/default/ui/toast/use-toast.ts @@ -0,0 +1,165 @@ +import { computed, ref } from 'vue' +import type { Component, VNode } from 'vue' +import type { ToastProps } from './Toast.vue' + +const TOAST_LIMIT = 1 +const TOAST_REMOVE_DELAY = 1000000 + +export type StringOrVNode = + | string + | VNode + | (() => VNode) + +type ToasterToast = ToastProps & { + id: string + title?: string + description?: StringOrVNode + action?: Component +} + +const actionTypes = { + ADD_TOAST: 'ADD_TOAST', + UPDATE_TOAST: 'UPDATE_TOAST', + DISMISS_TOAST: 'DISMISS_TOAST', + REMOVE_TOAST: 'REMOVE_TOAST', +} as const + +let count = 0 + +function genId() { + count = (count + 1) % Number.MAX_VALUE + return count.toString() +} + +type ActionType = typeof actionTypes + +type Action = + | { + type: ActionType['ADD_TOAST'] + toast: ToasterToast + } + | { + type: ActionType['UPDATE_TOAST'] + toast: Partial + } + | { + type: ActionType['DISMISS_TOAST'] + toastId?: ToasterToast['id'] + } + | { + type: ActionType['REMOVE_TOAST'] + toastId?: ToasterToast['id'] + } + +interface State { + toasts: ToasterToast[] +} + +const toastTimeouts = new Map>() + +function addToRemoveQueue(toastId: string) { + if (toastTimeouts.has(toastId)) + return + + const timeout = setTimeout(() => { + toastTimeouts.delete(toastId) + dispatch({ + type: actionTypes.REMOVE_TOAST, + toastId, + }) + }, TOAST_REMOVE_DELAY) + + toastTimeouts.set(toastId, timeout) +} + +const state = ref({ + toasts: [], +}) + +function dispatch(action: Action) { + switch (action.type) { + case actionTypes.ADD_TOAST: + state.value.toasts = [action.toast, ...state.value.toasts].slice(0, TOAST_LIMIT) + break + + case actionTypes.UPDATE_TOAST: + state.value.toasts = state.value.toasts.map(t => + t.id === action.toast.id ? { ...t, ...action.toast } : t, + ) + break + + case actionTypes.DISMISS_TOAST: { + const { toastId } = action + + if (toastId) { + addToRemoveQueue(toastId) + } + else { + state.value.toasts.forEach((toast) => { + addToRemoveQueue(toast.id) + }) + } + + state.value.toasts = state.value.toasts.map(t => + t.id === toastId || toastId === undefined + ? { + ...t, + open: false, + } + : t, + ) + break + } + + case actionTypes.REMOVE_TOAST: + if (action.toastId === undefined) + state.value.toasts = [] + else + state.value.toasts = state.value.toasts.filter(t => t.id !== action.toastId) + + break + } +} + +function useToast() { + return { + toasts: computed(() => state.value.toasts), + toast, + dismiss: (toastId?: string) => dispatch({ type: actionTypes.DISMISS_TOAST, toastId }), + } +} + +type Toast = Omit + +function toast(props: Toast) { + const id = genId() + + const update = (props: ToasterToast) => + dispatch({ + type: actionTypes.UPDATE_TOAST, + toast: { ...props, id }, + }) + + const dismiss = () => dispatch({ type: actionTypes.DISMISS_TOAST, toastId: id }) + + dispatch({ + type: actionTypes.ADD_TOAST, + toast: { + ...props, + id, + open: true, + onOpenChange: (open: boolean) => { + if (!open) + dismiss() + }, + }, + }) + + return { + id, + dismiss, + update, + } +} + +export { toast, useToast } diff --git a/apps/www/src/lib/registry/new-york/example/CheckboxFormMultiple.vue b/apps/www/src/lib/registry/new-york/example/CheckboxFormMultiple.vue index 2f4ff721..41a55c43 100644 --- a/apps/www/src/lib/registry/new-york/example/CheckboxFormMultiple.vue +++ b/apps/www/src/lib/registry/new-york/example/CheckboxFormMultiple.vue @@ -1,4 +1,5 @@ diff --git a/apps/www/src/lib/registry/new-york/example/CheckboxFormSingle.vue b/apps/www/src/lib/registry/new-york/example/CheckboxFormSingle.vue index 69506c2f..b1699959 100644 --- a/apps/www/src/lib/registry/new-york/example/CheckboxFormSingle.vue +++ b/apps/www/src/lib/registry/new-york/example/CheckboxFormSingle.vue @@ -1,4 +1,5 @@ diff --git a/apps/www/src/lib/registry/new-york/example/ComboboxForm.vue b/apps/www/src/lib/registry/new-york/example/ComboboxForm.vue index bd79d9ea..eb112afc 100644 --- a/apps/www/src/lib/registry/new-york/example/ComboboxForm.vue +++ b/apps/www/src/lib/registry/new-york/example/ComboboxForm.vue @@ -1,4 +1,5 @@ diff --git a/apps/www/src/lib/registry/new-york/example/DatePickerForm.vue b/apps/www/src/lib/registry/new-york/example/DatePickerForm.vue index 728af491..7203b9db 100644 --- a/apps/www/src/lib/registry/new-york/example/DatePickerForm.vue +++ b/apps/www/src/lib/registry/new-york/example/DatePickerForm.vue @@ -1,4 +1,5 @@ diff --git a/apps/www/src/lib/registry/new-york/example/InputForm.vue b/apps/www/src/lib/registry/new-york/example/InputForm.vue index 316f4fb2..60037681 100644 --- a/apps/www/src/lib/registry/new-york/example/InputForm.vue +++ b/apps/www/src/lib/registry/new-york/example/InputForm.vue @@ -1,4 +1,5 @@ diff --git a/apps/www/src/lib/registry/new-york/example/InputFormAutoAnimate.vue b/apps/www/src/lib/registry/new-york/example/InputFormAutoAnimate.vue index 564bb989..dc6ae024 100644 --- a/apps/www/src/lib/registry/new-york/example/InputFormAutoAnimate.vue +++ b/apps/www/src/lib/registry/new-york/example/InputFormAutoAnimate.vue @@ -1,4 +1,5 @@ diff --git a/apps/www/src/lib/registry/new-york/example/RadioGroupForm.vue b/apps/www/src/lib/registry/new-york/example/RadioGroupForm.vue index c4d02519..e5066f94 100644 --- a/apps/www/src/lib/registry/new-york/example/RadioGroupForm.vue +++ b/apps/www/src/lib/registry/new-york/example/RadioGroupForm.vue @@ -1,4 +1,5 @@ diff --git a/apps/www/src/lib/registry/new-york/example/SelectForm.vue b/apps/www/src/lib/registry/new-york/example/SelectForm.vue index 8c743df9..1e486310 100644 --- a/apps/www/src/lib/registry/new-york/example/SelectForm.vue +++ b/apps/www/src/lib/registry/new-york/example/SelectForm.vue @@ -1,4 +1,5 @@ diff --git a/apps/www/src/lib/registry/new-york/example/SwitchForm.vue b/apps/www/src/lib/registry/new-york/example/SwitchForm.vue index 0085115e..db5de661 100644 --- a/apps/www/src/lib/registry/new-york/example/SwitchForm.vue +++ b/apps/www/src/lib/registry/new-york/example/SwitchForm.vue @@ -1,4 +1,5 @@ diff --git a/apps/www/src/lib/registry/new-york/example/TextareaForm.vue b/apps/www/src/lib/registry/new-york/example/TextareaForm.vue index 5c19b78f..cb53f390 100644 --- a/apps/www/src/lib/registry/new-york/example/TextareaForm.vue +++ b/apps/www/src/lib/registry/new-york/example/TextareaForm.vue @@ -1,4 +1,5 @@ diff --git a/apps/www/src/lib/registry/new-york/example/ToastDemo.vue b/apps/www/src/lib/registry/new-york/example/ToastDemo.vue new file mode 100644 index 00000000..041565e1 --- /dev/null +++ b/apps/www/src/lib/registry/new-york/example/ToastDemo.vue @@ -0,0 +1,19 @@ + + + + { + toast({ + title: 'Scheduled: Catch up', + description: 'Friday, February 10, 2023 at 5:57 PM', + }); + }" + > + Add to calander + + diff --git a/apps/www/src/lib/registry/new-york/example/ToastDestructive.vue b/apps/www/src/lib/registry/new-york/example/ToastDestructive.vue new file mode 100644 index 00000000..b58f885c --- /dev/null +++ b/apps/www/src/lib/registry/new-york/example/ToastDestructive.vue @@ -0,0 +1,27 @@ + + + + { + toast({ + title: 'Uh oh! Something went wrong.', + description: 'There was a problem with your request.', + variant: 'destructive', + action: h(ToastAction, { + altText: 'Try again', + }, { + default: () => 'Try again', + }), + }); + }" + > + Show Toast + + diff --git a/apps/www/src/lib/registry/new-york/example/ToastSimple.vue b/apps/www/src/lib/registry/new-york/example/ToastSimple.vue new file mode 100644 index 00000000..620038fd --- /dev/null +++ b/apps/www/src/lib/registry/new-york/example/ToastSimple.vue @@ -0,0 +1,18 @@ + + + + { + toast({ + description: 'Your message has been sent.', + }); + }" + > + Show Toast + + diff --git a/apps/www/src/lib/registry/new-york/example/ToastWithAction.vue b/apps/www/src/lib/registry/new-york/example/ToastWithAction.vue new file mode 100644 index 00000000..a56e3fba --- /dev/null +++ b/apps/www/src/lib/registry/new-york/example/ToastWithAction.vue @@ -0,0 +1,26 @@ + + + + { + toast({ + title: 'Uh oh! Something went wrong.', + description: 'There was a problem with your request.', + action: h(ToastAction, { + altText: 'Try again', + }, { + default: () => 'Try again', + }), + }); + }" + > + Show Toast + + diff --git a/apps/www/src/lib/registry/new-york/example/ToastWithTitle.vue b/apps/www/src/lib/registry/new-york/example/ToastWithTitle.vue new file mode 100644 index 00000000..5f30a0d2 --- /dev/null +++ b/apps/www/src/lib/registry/new-york/example/ToastWithTitle.vue @@ -0,0 +1,19 @@ + + + + { + toast({ + title: 'Uh oh! Something went wrong.', + description: 'There was a problem with your request.', + }); + }" + > + Show Toast + + diff --git a/apps/www/src/lib/registry/new-york/ui/toast/Toast.vue b/apps/www/src/lib/registry/new-york/ui/toast/Toast.vue new file mode 100644 index 00000000..980f2fb4 --- /dev/null +++ b/apps/www/src/lib/registry/new-york/ui/toast/Toast.vue @@ -0,0 +1,32 @@ + + + + + + + + + diff --git a/apps/www/src/lib/registry/new-york/ui/toast/ToastAction.vue b/apps/www/src/lib/registry/new-york/ui/toast/ToastAction.vue new file mode 100644 index 00000000..20747e70 --- /dev/null +++ b/apps/www/src/lib/registry/new-york/ui/toast/ToastAction.vue @@ -0,0 +1,12 @@ + + + + + + + diff --git a/apps/www/src/lib/registry/new-york/ui/toast/ToastClose.vue b/apps/www/src/lib/registry/new-york/ui/toast/ToastClose.vue new file mode 100644 index 00000000..183e1aeb --- /dev/null +++ b/apps/www/src/lib/registry/new-york/ui/toast/ToastClose.vue @@ -0,0 +1,15 @@ + + + + + + + diff --git a/apps/www/src/lib/registry/new-york/ui/toast/ToastDescription.vue b/apps/www/src/lib/registry/new-york/ui/toast/ToastDescription.vue new file mode 100644 index 00000000..5f71b1a8 --- /dev/null +++ b/apps/www/src/lib/registry/new-york/ui/toast/ToastDescription.vue @@ -0,0 +1,12 @@ + + + + + + + diff --git a/apps/www/src/lib/registry/new-york/ui/toast/ToastProvider.vue b/apps/www/src/lib/registry/new-york/ui/toast/ToastProvider.vue new file mode 100644 index 00000000..340cbd83 --- /dev/null +++ b/apps/www/src/lib/registry/new-york/ui/toast/ToastProvider.vue @@ -0,0 +1,11 @@ + + + + + + + diff --git a/apps/www/src/lib/registry/new-york/ui/toast/ToastTitle.vue b/apps/www/src/lib/registry/new-york/ui/toast/ToastTitle.vue new file mode 100644 index 00000000..0230bd31 --- /dev/null +++ b/apps/www/src/lib/registry/new-york/ui/toast/ToastTitle.vue @@ -0,0 +1,12 @@ + + + + + + + diff --git a/apps/www/src/lib/registry/new-york/ui/toast/ToastViewport.vue b/apps/www/src/lib/registry/new-york/ui/toast/ToastViewport.vue new file mode 100644 index 00000000..5f2f2b95 --- /dev/null +++ b/apps/www/src/lib/registry/new-york/ui/toast/ToastViewport.vue @@ -0,0 +1,10 @@ + + + + + diff --git a/apps/www/src/lib/registry/new-york/ui/toast/Toaster.vue b/apps/www/src/lib/registry/new-york/ui/toast/Toaster.vue new file mode 100644 index 00000000..f86ce40b --- /dev/null +++ b/apps/www/src/lib/registry/new-york/ui/toast/Toaster.vue @@ -0,0 +1,30 @@ + + + + + + + + {{ toast.title }} + + + + + + + {{ toast.description }} + + + + + + + + + diff --git a/apps/www/src/lib/registry/new-york/ui/toast/index.ts b/apps/www/src/lib/registry/new-york/ui/toast/index.ts new file mode 100644 index 00000000..331bf98c --- /dev/null +++ b/apps/www/src/lib/registry/new-york/ui/toast/index.ts @@ -0,0 +1,27 @@ +export { default as Toaster } from './Toaster.vue' +export { default as Toast } from './Toast.vue' +export { default as ToastViewport } from './ToastViewport.vue' +export { default as ToastAction } from './ToastAction.vue' +export { default as ToastClose } from './ToastClose.vue' +export { default as ToastTitle } from './ToastTitle.vue' +export { default as ToastDescription } from './ToastDescription.vue' +export { default as ToastProvider } from './ToastProvider.vue' +export { toast, useToast } from './use-toast' + +import { cva } from 'class-variance-authority' + +export const toastVariants = cva( + 'group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full', + { + variants: { + variant: { + default: 'border bg-background text-foreground', + destructive: + 'destructive group border-destructive bg-destructive text-destructive-foreground', + }, + }, + defaultVariants: { + variant: 'default', + }, + }, +) diff --git a/apps/www/src/lib/registry/new-york/ui/toast/use-toast.ts b/apps/www/src/lib/registry/new-york/ui/toast/use-toast.ts new file mode 100644 index 00000000..02e6973b --- /dev/null +++ b/apps/www/src/lib/registry/new-york/ui/toast/use-toast.ts @@ -0,0 +1,165 @@ +import { computed, ref } from 'vue' +import type { Component, VNode } from 'vue' +import type { ToastProps } from './Toast.vue' + +const TOAST_LIMIT = 1 +const TOAST_REMOVE_DELAY = 1000000 + +export type StringOrVNode = + | string + | VNode + | (() => VNode) + +type ToasterToast = ToastProps & { + id: string + title?: string + description?: StringOrVNode + action?: Component +} + +const actionTypes = { + ADD_TOAST: 'ADD_TOAST', + UPDATE_TOAST: 'UPDATE_TOAST', + DISMISS_TOAST: 'DISMISS_TOAST', + REMOVE_TOAST: 'REMOVE_TOAST', +} as const + +let count = 0 + +function genId() { + count = (count + 1) % Number.MAX_VALUE + return count.toString() +} + +type ActionType = typeof actionTypes + +type Action = + | { + type: ActionType['ADD_TOAST'] + toast: ToasterToast + } + | { + type: ActionType['UPDATE_TOAST'] + toast: Partial + } + | { + type: ActionType['DISMISS_TOAST'] + toastId?: ToasterToast['id'] + } + | { + type: ActionType['REMOVE_TOAST'] + toastId?: ToasterToast['id'] + } + +interface State { + toasts: ToasterToast[] +} + +const toastTimeouts = new Map>() + +function addToRemoveQueue(toastId: string) { + if (toastTimeouts.has(toastId)) + return + + const timeout = setTimeout(() => { + toastTimeouts.delete(toastId) + dispatch({ + type: actionTypes.REMOVE_TOAST, + toastId, + }) + }, TOAST_REMOVE_DELAY) + + toastTimeouts.set(toastId, timeout) +} + +const state = ref({ + toasts: [], +}) + +function dispatch(action: Action) { + switch (action.type) { + case actionTypes.ADD_TOAST: + state.value.toasts = [action.toast, ...state.value.toasts].slice(0, TOAST_LIMIT) + break + + case actionTypes.UPDATE_TOAST: + state.value.toasts = state.value.toasts.map(t => + t.id === action.toast.id ? { ...t, ...action.toast } : t, + ) + break + + case actionTypes.DISMISS_TOAST: { + const { toastId } = action + + if (toastId) { + addToRemoveQueue(toastId) + } + else { + state.value.toasts.forEach((toast) => { + addToRemoveQueue(toast.id) + }) + } + + state.value.toasts = state.value.toasts.map(t => + t.id === toastId || toastId === undefined + ? { + ...t, + open: false, + } + : t, + ) + break + } + + case actionTypes.REMOVE_TOAST: + if (action.toastId === undefined) + state.value.toasts = [] + else + state.value.toasts = state.value.toasts.filter(t => t.id !== action.toastId) + + break + } +} + +function useToast() { + return { + toasts: computed(() => state.value.toasts), + toast, + dismiss: (toastId?: string) => dispatch({ type: actionTypes.DISMISS_TOAST, toastId }), + } +} + +type Toast = Omit + +function toast(props: Toast) { + const id = genId() + + const update = (props: ToasterToast) => + dispatch({ + type: actionTypes.UPDATE_TOAST, + toast: { ...props, id }, + }) + + const dismiss = () => dispatch({ type: actionTypes.DISMISS_TOAST, toastId: id }) + + dispatch({ + type: actionTypes.ADD_TOAST, + toast: { + ...props, + id, + open: true, + onOpenChange: (open: boolean) => { + if (!open) + dismiss() + }, + }, + }) + + return { + id, + dismiss, + update, + } +} + +export { toast, useToast } diff --git a/apps/www/src/public/registry/index.json b/apps/www/src/public/registry/index.json index 0f1b727d..328d58fd 100644 --- a/apps/www/src/public/registry/index.json +++ b/apps/www/src/public/registry/index.json @@ -600,6 +600,28 @@ ], "type": "components:ui" }, + { + "name": "toast", + "dependencies": [ + "radix-vue" + ], + "registryDependencies": [ + "utils" + ], + "files": [ + "ui/toast/Toast.vue", + "ui/toast/ToastAction.vue", + "ui/toast/ToastClose.vue", + "ui/toast/ToastDescription.vue", + "ui/toast/ToastProvider.vue", + "ui/toast/ToastTitle.vue", + "ui/toast/ToastViewport.vue", + "ui/toast/Toaster.vue", + "ui/toast/index.ts", + "ui/toast/use-toast.ts" + ], + "type": "components:ui" + }, { "name": "toggle", "dependencies": [ diff --git a/apps/www/src/public/registry/styles/default/switch.json b/apps/www/src/public/registry/styles/default/switch.json index d0e168cd..3ab1479f 100644 --- a/apps/www/src/public/registry/styles/default/switch.json +++ b/apps/www/src/public/registry/styles/default/switch.json @@ -9,7 +9,7 @@ "files": [ { "name": "Switch.vue", - "content": "\n\n\n \n \n \n\n" + "content": "\n\n\n \n \n \n\n" }, { "name": "index.ts", diff --git a/apps/www/src/public/registry/styles/default/toast.json b/apps/www/src/public/registry/styles/default/toast.json index 0a5e62b5..93dba953 100644 --- a/apps/www/src/public/registry/styles/default/toast.json +++ b/apps/www/src/public/registry/styles/default/toast.json @@ -1,20 +1,51 @@ { "name": "toast", "dependencies": [ - "@radix-ui/react-toast" + "radix-vue" + ], + "registryDependencies": [ + "utils" ], "files": [ { - "name": "toast.tsx", - "content": "import * as React from \"react\"\nimport * as ToastPrimitives from \"@radix-ui/react-toast\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst ToastProvider = ToastPrimitives.Provider\n\nconst ToastViewport = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ class, ...props }, ref) => (\n \n))\nToastViewport.displayName = ToastPrimitives.Viewport.displayName\n\nconst toastVariants = cva(\n \"group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full\",\n {\n variants: {\n variant: {\n default: \"border bg-background text-foreground\",\n destructive:\n \"destructive group border-destructive bg-destructive text-destructive-foreground\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n },\n }\n)\n\nconst Toast = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef &\n VariantProps\n>(({ class, variant, ...props }, ref) => {\n return (\n \n )\n})\nToast.displayName = ToastPrimitives.Root.displayName\n\nconst ToastAction = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ class, ...props }, ref) => (\n \n))\nToastAction.displayName = ToastPrimitives.Action.displayName\n\nconst ToastClose = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ class, ...props }, ref) => (\n \n \n \n))\nToastClose.displayName = ToastPrimitives.Close.displayName\n\nconst ToastTitle = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ class, ...props }, ref) => (\n \n))\nToastTitle.displayName = ToastPrimitives.Title.displayName\n\nconst ToastDescription = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ class, ...props }, ref) => (\n \n))\nToastDescription.displayName = ToastPrimitives.Description.displayName\n\ntype ToastProps = React.ComponentPropsWithoutRef\n\ntype ToastActionElement = React.ReactElement\n\nexport {\n type ToastProps,\n type ToastActionElement,\n ToastProvider,\n ToastViewport,\n Toast,\n ToastTitle,\n ToastDescription,\n ToastClose,\n ToastAction,\n}\n" + "name": "Toast.vue", + "content": "\n\n\n\n\n \n \n \n\n" + }, + { + "name": "ToastAction.vue", + "content": "\n\n\n \n \n \n\n" + }, + { + "name": "ToastClose.vue", + "content": "\n\n\n \n \n \n\n" + }, + { + "name": "ToastDescription.vue", + "content": "\n\n\n \n \n \n\n" + }, + { + "name": "ToastProvider.vue", + "content": "\n\n\n \n \n \n\n" + }, + { + "name": "ToastTitle.vue", + "content": "\n\n\n \n \n \n\n" + }, + { + "name": "ToastViewport.vue", + "content": "\n\n\n \n\n" + }, + { + "name": "Toaster.vue", + "content": "\n\n\n \n \n \n \n {{ toast.title }}\n \n \n \n \n \n \n {{ toast.description }}\n \n \n \n \n \n \n \n \n\n" + }, + { + "name": "index.ts", + "content": "export { default as Toaster } from './Toaster.vue'\nexport { default as Toast } from './Toast.vue'\nexport { default as ToastViewport } from './ToastViewport.vue'\nexport { default as ToastAction } from './ToastAction.vue'\nexport { default as ToastClose } from './ToastClose.vue'\nexport { default as ToastTitle } from './ToastTitle.vue'\nexport { default as ToastDescription } from './ToastDescription.vue'\nexport { default as ToastProvider } from './ToastProvider.vue'\nexport { toast, useToast } from './use-toast'\n\nimport { cva } from 'class-variance-authority'\n\nexport const toastVariants = cva(\n 'group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full',\n {\n variants: {\n variant: {\n default: 'border bg-background text-foreground',\n destructive:\n 'destructive group border-destructive bg-destructive text-destructive-foreground',\n },\n },\n defaultVariants: {\n variant: 'default',\n },\n },\n)\n" }, { "name": "use-toast.ts", - "content": "// Inspired by react-hot-toast library\nimport * as React from \"react\"\n\nimport type {\n ToastActionElement,\n ToastProps,\n} from \"@/registry/default/ui/toast\"\n\nconst TOAST_LIMIT = 1\nconst TOAST_REMOVE_DELAY = 1000000\n\ntype ToasterToast = ToastProps & {\n id: string\n title?: React.ReactNode\n description?: React.ReactNode\n action?: ToastActionElement\n}\n\nconst actionTypes = {\n ADD_TOAST: \"ADD_TOAST\",\n UPDATE_TOAST: \"UPDATE_TOAST\",\n DISMISS_TOAST: \"DISMISS_TOAST\",\n REMOVE_TOAST: \"REMOVE_TOAST\",\n} as const\n\nlet count = 0\n\nfunction genId() {\n count = (count + 1) % Number.MAX_VALUE\n return count.toString()\n}\n\ntype ActionType = typeof actionTypes\n\ntype Action =\n | {\n type: ActionType[\"ADD_TOAST\"]\n toast: ToasterToast\n }\n | {\n type: ActionType[\"UPDATE_TOAST\"]\n toast: Partial\n }\n | {\n type: ActionType[\"DISMISS_TOAST\"]\n toastId?: ToasterToast[\"id\"]\n }\n | {\n type: ActionType[\"REMOVE_TOAST\"]\n toastId?: ToasterToast[\"id\"]\n }\n\ninterface State {\n toasts: ToasterToast[]\n}\n\nconst toastTimeouts = new Map>()\n\nconst addToRemoveQueue = (toastId: string) => {\n if (toastTimeouts.has(toastId)) {\n return\n }\n\n const timeout = setTimeout(() => {\n toastTimeouts.delete(toastId)\n dispatch({\n type: \"REMOVE_TOAST\",\n toastId: toastId,\n })\n }, TOAST_REMOVE_DELAY)\n\n toastTimeouts.set(toastId, timeout)\n}\n\nexport const reducer = (state: State, action: Action): State => {\n switch (action.type) {\n case \"ADD_TOAST\":\n return {\n ...state,\n toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),\n }\n\n case \"UPDATE_TOAST\":\n return {\n ...state,\n toasts: state.toasts.map((t) =>\n t.id === action.toast.id ? { ...t, ...action.toast } : t\n ),\n }\n\n case \"DISMISS_TOAST\": {\n const { toastId } = action\n\n // ! Side effects ! - This could be extracted into a dismissToast() action,\n // but I'll keep it here for simplicity\n if (toastId) {\n addToRemoveQueue(toastId)\n } else {\n state.toasts.forEach((toast) => {\n addToRemoveQueue(toast.id)\n })\n }\n\n return {\n ...state,\n toasts: state.toasts.map((t) =>\n t.id === toastId || toastId === undefined\n ? {\n ...t,\n open: false,\n }\n : t\n ),\n }\n }\n case \"REMOVE_TOAST\":\n if (action.toastId === undefined) {\n return {\n ...state,\n toasts: [],\n }\n }\n return {\n ...state,\n toasts: state.toasts.filter((t) => t.id !== action.toastId),\n }\n }\n}\n\nconst listeners: Array<(state: State) => void> = []\n\nlet memoryState: State = { toasts: [] }\n\nfunction dispatch(action: Action) {\n memoryState = reducer(memoryState, action)\n listeners.forEach((listener) => {\n listener(memoryState)\n })\n}\n\ntype Toast = Omit\n\nfunction toast({ ...props }: Toast) {\n const id = genId()\n\n const update = (props: ToasterToast) =>\n dispatch({\n type: \"UPDATE_TOAST\",\n toast: { ...props, id },\n })\n const dismiss = () => dispatch({ type: \"DISMISS_TOAST\", toastId: id })\n\n dispatch({\n type: \"ADD_TOAST\",\n toast: {\n ...props,\n id,\n open: true,\n onOpenChange: (open) => {\n if (!open) dismiss()\n },\n },\n })\n\n return {\n id: id,\n dismiss,\n update,\n }\n}\n\nfunction useToast() {\n const [state, setState] = React.useState(memoryState)\n\n React.useEffect(() => {\n listeners.push(setState)\n return () => {\n const index = listeners.indexOf(setState)\n if (index > -1) {\n listeners.splice(index, 1)\n }\n }\n }, [state])\n\n return {\n ...state,\n toast,\n dismiss: (toastId?: string) => dispatch({ type: \"DISMISS_TOAST\", toastId }),\n }\n}\n\nexport { useToast, toast }\n" - }, - { - "name": "toaster.tsx", - "content": "\"use client\"\n\nimport {\n Toast,\n ToastClose,\n ToastDescription,\n ToastProvider,\n ToastTitle,\n ToastViewport,\n} from \"@/registry/default/ui/toast\"\nimport { useToast } from \"@/registry/default/ui/use-toast\"\n\nexport function Toaster() {\n const { toasts } = useToast()\n\n return (\n \n {toasts.map(function ({ id, title, description, action, ...props }) {\n return (\n \n \n {title && {title}}\n {description && (\n {description}\n )}\n \n {action}\n \n \n )\n })}\n \n \n )\n}\n" + "content": "import { computed, ref } from 'vue'\nimport type { Component, VNode } from 'vue'\nimport type { ToastProps } from './Toast.vue'\n\nconst TOAST_LIMIT = 1\nconst TOAST_REMOVE_DELAY = 1000000\n\nexport type StringOrVNode =\n | string\n | VNode\n | (() => VNode)\n\ntype ToasterToast = ToastProps & {\n id: string\n title?: string\n description?: StringOrVNode\n action?: Component\n}\n\nconst actionTypes = {\n ADD_TOAST: 'ADD_TOAST',\n UPDATE_TOAST: 'UPDATE_TOAST',\n DISMISS_TOAST: 'DISMISS_TOAST',\n REMOVE_TOAST: 'REMOVE_TOAST',\n} as const\n\nlet count = 0\n\nfunction genId() {\n count = (count + 1) % Number.MAX_VALUE\n return count.toString()\n}\n\ntype ActionType = typeof actionTypes\n\ntype Action =\n | {\n type: ActionType['ADD_TOAST']\n toast: ToasterToast\n }\n | {\n type: ActionType['UPDATE_TOAST']\n toast: Partial\n }\n | {\n type: ActionType['DISMISS_TOAST']\n toastId?: ToasterToast['id']\n }\n | {\n type: ActionType['REMOVE_TOAST']\n toastId?: ToasterToast['id']\n }\n\ninterface State {\n toasts: ToasterToast[]\n}\n\nconst toastTimeouts = new Map>()\n\nfunction addToRemoveQueue(toastId: string) {\n if (toastTimeouts.has(toastId))\n return\n\n const timeout = setTimeout(() => {\n toastTimeouts.delete(toastId)\n dispatch({\n type: actionTypes.REMOVE_TOAST,\n toastId,\n })\n }, TOAST_REMOVE_DELAY)\n\n toastTimeouts.set(toastId, timeout)\n}\n\nconst state = ref({\n toasts: [],\n})\n\nfunction dispatch(action: Action) {\n switch (action.type) {\n case actionTypes.ADD_TOAST:\n state.value.toasts = [action.toast, ...state.value.toasts].slice(0, TOAST_LIMIT)\n break\n\n case actionTypes.UPDATE_TOAST:\n state.value.toasts = state.value.toasts.map(t =>\n t.id === action.toast.id ? { ...t, ...action.toast } : t,\n )\n break\n\n case actionTypes.DISMISS_TOAST: {\n const { toastId } = action\n\n if (toastId) {\n addToRemoveQueue(toastId)\n }\n else {\n state.value.toasts.forEach((toast) => {\n addToRemoveQueue(toast.id)\n })\n }\n\n state.value.toasts = state.value.toasts.map(t =>\n t.id === toastId || toastId === undefined\n ? {\n ...t,\n open: false,\n }\n : t,\n )\n break\n }\n\n case actionTypes.REMOVE_TOAST:\n if (action.toastId === undefined)\n state.value.toasts = []\n else\n state.value.toasts = state.value.toasts.filter(t => t.id !== action.toastId)\n\n break\n }\n}\n\nfunction useToast() {\n return {\n toasts: computed(() => state.value.toasts),\n toast,\n dismiss: (toastId?: string) => dispatch({ type: actionTypes.DISMISS_TOAST, toastId }),\n }\n}\n\ntype Toast = Omit\n\nfunction toast(props: Toast) {\n const id = genId()\n\n const update = (props: ToasterToast) =>\n dispatch({\n type: actionTypes.UPDATE_TOAST,\n toast: { ...props, id },\n })\n\n const dismiss = () => dispatch({ type: actionTypes.DISMISS_TOAST, toastId: id })\n\n dispatch({\n type: actionTypes.ADD_TOAST,\n toast: {\n ...props,\n id,\n open: true,\n onOpenChange: (open: boolean) => {\n if (!open)\n dismiss()\n },\n },\n })\n\n return {\n id,\n dismiss,\n update,\n }\n}\n\nexport { toast, useToast }\n" } ], "type": "components:ui" diff --git a/apps/www/src/public/registry/styles/new-york/toast.json b/apps/www/src/public/registry/styles/new-york/toast.json index a3b074b1..82c3d702 100644 --- a/apps/www/src/public/registry/styles/new-york/toast.json +++ b/apps/www/src/public/registry/styles/new-york/toast.json @@ -1,20 +1,51 @@ { "name": "toast", "dependencies": [ - "@radix-ui/react-toast" + "radix-vue" + ], + "registryDependencies": [ + "utils" ], "files": [ { - "name": "toast.tsx", - "content": "import * as React from \"react\"\nimport { Cross2Icon } from \"@radix-ui/react-icons\"\nimport * as ToastPrimitives from \"@radix-ui/react-toast\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst ToastProvider = ToastPrimitives.Provider\n\nconst ToastViewport = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ class, ...props }, ref) => (\n \n))\nToastViewport.displayName = ToastPrimitives.Viewport.displayName\n\nconst toastVariants = cva(\n \"group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full\",\n {\n variants: {\n variant: {\n default: \"border bg-background text-foreground\",\n destructive:\n \"destructive group border-destructive bg-destructive text-destructive-foreground\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n },\n }\n)\n\nconst Toast = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef &\n VariantProps\n>(({ class, variant, ...props }, ref) => {\n return (\n \n )\n})\nToast.displayName = ToastPrimitives.Root.displayName\n\nconst ToastAction = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ class, ...props }, ref) => (\n \n))\nToastAction.displayName = ToastPrimitives.Action.displayName\n\nconst ToastClose = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ class, ...props }, ref) => (\n \n \n \n))\nToastClose.displayName = ToastPrimitives.Close.displayName\n\nconst ToastTitle = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ class, ...props }, ref) => (\n \n))\nToastTitle.displayName = ToastPrimitives.Title.displayName\n\nconst ToastDescription = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ class, ...props }, ref) => (\n \n))\nToastDescription.displayName = ToastPrimitives.Description.displayName\n\ntype ToastProps = React.ComponentPropsWithoutRef\n\ntype ToastActionElement = React.ReactElement\n\nexport {\n type ToastProps,\n type ToastActionElement,\n ToastProvider,\n ToastViewport,\n Toast,\n ToastTitle,\n ToastDescription,\n ToastClose,\n ToastAction,\n}\n" + "name": "Toast.vue", + "content": "\n\n\n\n\n \n \n \n\n" + }, + { + "name": "ToastAction.vue", + "content": "\n\n\n \n \n \n\n" + }, + { + "name": "ToastClose.vue", + "content": "\n\n\n \n \n \n\n" + }, + { + "name": "ToastDescription.vue", + "content": "\n\n\n \n \n \n\n" + }, + { + "name": "ToastProvider.vue", + "content": "\n\n\n \n \n \n\n" + }, + { + "name": "ToastTitle.vue", + "content": "\n\n\n \n \n \n\n" + }, + { + "name": "ToastViewport.vue", + "content": "\n\n\n \n\n" + }, + { + "name": "Toaster.vue", + "content": "\n\n\n \n \n \n \n {{ toast.title }}\n \n \n \n \n \n \n {{ toast.description }}\n \n \n \n \n \n \n \n \n\n" + }, + { + "name": "index.ts", + "content": "export { default as Toaster } from './Toaster.vue'\nexport { default as Toast } from './Toast.vue'\nexport { default as ToastViewport } from './ToastViewport.vue'\nexport { default as ToastAction } from './ToastAction.vue'\nexport { default as ToastClose } from './ToastClose.vue'\nexport { default as ToastTitle } from './ToastTitle.vue'\nexport { default as ToastDescription } from './ToastDescription.vue'\nexport { default as ToastProvider } from './ToastProvider.vue'\nexport { toast, useToast } from './use-toast'\n\nimport { cva } from 'class-variance-authority'\n\nexport const toastVariants = cva(\n 'group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full',\n {\n variants: {\n variant: {\n default: 'border bg-background text-foreground',\n destructive:\n 'destructive group border-destructive bg-destructive text-destructive-foreground',\n },\n },\n defaultVariants: {\n variant: 'default',\n },\n },\n)\n" }, { "name": "use-toast.ts", - "content": "// Inspired by react-hot-toast library\nimport * as React from \"react\"\n\nimport type {\n ToastActionElement,\n ToastProps,\n} from \"@/registry/default/ui/toast\"\n\nconst TOAST_LIMIT = 1\nconst TOAST_REMOVE_DELAY = 1000000\n\ntype ToasterToast = ToastProps & {\n id: string\n title?: React.ReactNode\n description?: React.ReactNode\n action?: ToastActionElement\n}\n\nconst actionTypes = {\n ADD_TOAST: \"ADD_TOAST\",\n UPDATE_TOAST: \"UPDATE_TOAST\",\n DISMISS_TOAST: \"DISMISS_TOAST\",\n REMOVE_TOAST: \"REMOVE_TOAST\",\n} as const\n\nlet count = 0\n\nfunction genId() {\n count = (count + 1) % Number.MAX_VALUE\n return count.toString()\n}\n\ntype ActionType = typeof actionTypes\n\ntype Action =\n | {\n type: ActionType[\"ADD_TOAST\"]\n toast: ToasterToast\n }\n | {\n type: ActionType[\"UPDATE_TOAST\"]\n toast: Partial\n }\n | {\n type: ActionType[\"DISMISS_TOAST\"]\n toastId?: ToasterToast[\"id\"]\n }\n | {\n type: ActionType[\"REMOVE_TOAST\"]\n toastId?: ToasterToast[\"id\"]\n }\n\ninterface State {\n toasts: ToasterToast[]\n}\n\nconst toastTimeouts = new Map>()\n\nconst addToRemoveQueue = (toastId: string) => {\n if (toastTimeouts.has(toastId)) {\n return\n }\n\n const timeout = setTimeout(() => {\n toastTimeouts.delete(toastId)\n dispatch({\n type: \"REMOVE_TOAST\",\n toastId: toastId,\n })\n }, TOAST_REMOVE_DELAY)\n\n toastTimeouts.set(toastId, timeout)\n}\n\nexport const reducer = (state: State, action: Action): State => {\n switch (action.type) {\n case \"ADD_TOAST\":\n return {\n ...state,\n toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),\n }\n\n case \"UPDATE_TOAST\":\n return {\n ...state,\n toasts: state.toasts.map((t) =>\n t.id === action.toast.id ? { ...t, ...action.toast } : t\n ),\n }\n\n case \"DISMISS_TOAST\": {\n const { toastId } = action\n\n // ! Side effects ! - This could be extracted into a dismissToast() action,\n // but I'll keep it here for simplicity\n if (toastId) {\n addToRemoveQueue(toastId)\n } else {\n state.toasts.forEach((toast) => {\n addToRemoveQueue(toast.id)\n })\n }\n\n return {\n ...state,\n toasts: state.toasts.map((t) =>\n t.id === toastId || toastId === undefined\n ? {\n ...t,\n open: false,\n }\n : t\n ),\n }\n }\n case \"REMOVE_TOAST\":\n if (action.toastId === undefined) {\n return {\n ...state,\n toasts: [],\n }\n }\n return {\n ...state,\n toasts: state.toasts.filter((t) => t.id !== action.toastId),\n }\n }\n}\n\nconst listeners: Array<(state: State) => void> = []\n\nlet memoryState: State = { toasts: [] }\n\nfunction dispatch(action: Action) {\n memoryState = reducer(memoryState, action)\n listeners.forEach((listener) => {\n listener(memoryState)\n })\n}\n\ntype Toast = Omit\n\nfunction toast({ ...props }: Toast) {\n const id = genId()\n\n const update = (props: ToasterToast) =>\n dispatch({\n type: \"UPDATE_TOAST\",\n toast: { ...props, id },\n })\n const dismiss = () => dispatch({ type: \"DISMISS_TOAST\", toastId: id })\n\n dispatch({\n type: \"ADD_TOAST\",\n toast: {\n ...props,\n id,\n open: true,\n onOpenChange: (open) => {\n if (!open) dismiss()\n },\n },\n })\n\n return {\n id: id,\n dismiss,\n update,\n }\n}\n\nfunction useToast() {\n const [state, setState] = React.useState(memoryState)\n\n React.useEffect(() => {\n listeners.push(setState)\n return () => {\n const index = listeners.indexOf(setState)\n if (index > -1) {\n listeners.splice(index, 1)\n }\n }\n }, [state])\n\n return {\n ...state,\n toast,\n dismiss: (toastId?: string) => dispatch({ type: \"DISMISS_TOAST\", toastId }),\n }\n}\n\nexport { useToast, toast }\n" - }, - { - "name": "toaster.tsx", - "content": "\"use client\"\n\nimport {\n Toast,\n ToastClose,\n ToastDescription,\n ToastProvider,\n ToastTitle,\n ToastViewport,\n} from \"@/registry/new-york/ui/toast\"\nimport { useToast } from \"@/registry/new-york/ui/use-toast\"\n\nexport function Toaster() {\n const { toasts } = useToast()\n\n return (\n \n {toasts.map(function ({ id, title, description, action, ...props }) {\n return (\n \n \n {title && {title}}\n {description && (\n {description}\n )}\n \n {action}\n \n \n )\n })}\n \n \n )\n}\n" + "content": "import { computed, ref } from 'vue'\nimport type { Component, VNode } from 'vue'\nimport type { ToastProps } from './Toast.vue'\n\nconst TOAST_LIMIT = 1\nconst TOAST_REMOVE_DELAY = 1000000\n\nexport type StringOrVNode =\n | string\n | VNode\n | (() => VNode)\n\ntype ToasterToast = ToastProps & {\n id: string\n title?: string\n description?: StringOrVNode\n action?: Component\n}\n\nconst actionTypes = {\n ADD_TOAST: 'ADD_TOAST',\n UPDATE_TOAST: 'UPDATE_TOAST',\n DISMISS_TOAST: 'DISMISS_TOAST',\n REMOVE_TOAST: 'REMOVE_TOAST',\n} as const\n\nlet count = 0\n\nfunction genId() {\n count = (count + 1) % Number.MAX_VALUE\n return count.toString()\n}\n\ntype ActionType = typeof actionTypes\n\ntype Action =\n | {\n type: ActionType['ADD_TOAST']\n toast: ToasterToast\n }\n | {\n type: ActionType['UPDATE_TOAST']\n toast: Partial\n }\n | {\n type: ActionType['DISMISS_TOAST']\n toastId?: ToasterToast['id']\n }\n | {\n type: ActionType['REMOVE_TOAST']\n toastId?: ToasterToast['id']\n }\n\ninterface State {\n toasts: ToasterToast[]\n}\n\nconst toastTimeouts = new Map>()\n\nfunction addToRemoveQueue(toastId: string) {\n if (toastTimeouts.has(toastId))\n return\n\n const timeout = setTimeout(() => {\n toastTimeouts.delete(toastId)\n dispatch({\n type: actionTypes.REMOVE_TOAST,\n toastId,\n })\n }, TOAST_REMOVE_DELAY)\n\n toastTimeouts.set(toastId, timeout)\n}\n\nconst state = ref({\n toasts: [],\n})\n\nfunction dispatch(action: Action) {\n switch (action.type) {\n case actionTypes.ADD_TOAST:\n state.value.toasts = [action.toast, ...state.value.toasts].slice(0, TOAST_LIMIT)\n break\n\n case actionTypes.UPDATE_TOAST:\n state.value.toasts = state.value.toasts.map(t =>\n t.id === action.toast.id ? { ...t, ...action.toast } : t,\n )\n break\n\n case actionTypes.DISMISS_TOAST: {\n const { toastId } = action\n\n if (toastId) {\n addToRemoveQueue(toastId)\n }\n else {\n state.value.toasts.forEach((toast) => {\n addToRemoveQueue(toast.id)\n })\n }\n\n state.value.toasts = state.value.toasts.map(t =>\n t.id === toastId || toastId === undefined\n ? {\n ...t,\n open: false,\n }\n : t,\n )\n break\n }\n\n case actionTypes.REMOVE_TOAST:\n if (action.toastId === undefined)\n state.value.toasts = []\n else\n state.value.toasts = state.value.toasts.filter(t => t.id !== action.toastId)\n\n break\n }\n}\n\nfunction useToast() {\n return {\n toasts: computed(() => state.value.toasts),\n toast,\n dismiss: (toastId?: string) => dispatch({ type: actionTypes.DISMISS_TOAST, toastId }),\n }\n}\n\ntype Toast = Omit\n\nfunction toast(props: Toast) {\n const id = genId()\n\n const update = (props: ToasterToast) =>\n dispatch({\n type: actionTypes.UPDATE_TOAST,\n toast: { ...props, id },\n })\n\n const dismiss = () => dispatch({ type: actionTypes.DISMISS_TOAST, toastId: id })\n\n dispatch({\n type: actionTypes.ADD_TOAST,\n toast: {\n ...props,\n id,\n open: true,\n onOpenChange: (open: boolean) => {\n if (!open)\n dismiss()\n },\n },\n })\n\n return {\n id,\n dismiss,\n update,\n }\n}\n\nexport { toast, useToast }\n" } ], "type": "components:ui"