feat: add toast (#122)
* feat: add toast * feat: add new york version * feat: add `VNode` type to toast description * docs: use `toast` in form demos * chore: run build registry * docs: update announcement component, menu label * refactor: change 'onUpdate:open' to 'onOpenChange' a more friendlier name --------- Co-authored-by: Nik <dev@nkutinha.slmail.me> Co-authored-by: Sadegh Barati <sadeghbaratiwork@gmail.com> Co-authored-by: zernonia <zernonia@gmail.com>
This commit is contained in:
parent
a4774ff48d
commit
ef4609abbf
|
|
@ -3,6 +3,7 @@ import PageHeader from '../components/PageHeader.vue'
|
||||||
import PageHeaderHeading from '../components/PageHeaderHeading.vue'
|
import PageHeaderHeading from '../components/PageHeaderHeading.vue'
|
||||||
import PageHeaderDescription from '../components/PageHeaderDescription.vue'
|
import PageHeaderDescription from '../components/PageHeaderDescription.vue'
|
||||||
import ExamplesNav from '../components/ExamplesNav.vue'
|
import ExamplesNav from '../components/ExamplesNav.vue'
|
||||||
|
import { announcementConfig } from '../config/site'
|
||||||
import ArrowRightIcon from '~icons/radix-icons/arrow-right'
|
import ArrowRightIcon from '~icons/radix-icons/arrow-right'
|
||||||
import GitHubIcon from '~icons/radix-icons/github-logo'
|
import GitHubIcon from '~icons/radix-icons/github-logo'
|
||||||
|
|
||||||
|
|
@ -16,12 +17,12 @@ import DashboardExample from '@/examples/dashboard/Example.vue'
|
||||||
<template>
|
<template>
|
||||||
<PageHeader class="page-header pb-8">
|
<PageHeader class="page-header pb-8">
|
||||||
<a
|
<a
|
||||||
href="/docs/components/form"
|
:href="announcementConfig.link"
|
||||||
class="inline-flex items-center rounded-lg bg-muted px-3 py-1 text-sm font-medium"
|
class="inline-flex items-center rounded-lg bg-muted px-3 py-1 text-sm font-medium"
|
||||||
>
|
>
|
||||||
🎉 <Separator class="mx-2 h-4" orientation="vertical" />
|
{{ announcementConfig.icon }} <Separator class="mx-2 h-4" orientation="vertical" />
|
||||||
<span class="sm:hidden">New form & pagination component</span>
|
<span class="sm:hidden">{{ announcementConfig.title }}</span>
|
||||||
<span class="hidden sm:inline">New form & pagination component
|
<span class="hidden sm:inline">{{ announcementConfig.title }}
|
||||||
</span>
|
</span>
|
||||||
<!-- <ArrowRightIcon class="ml-1 h-4 w-4" /> -->
|
<!-- <ArrowRightIcon class="ml-1 h-4 w-4" /> -->
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -216,7 +216,6 @@ export const docsConfig: DocsConfig = {
|
||||||
{
|
{
|
||||||
title: 'Form',
|
title: 'Form',
|
||||||
href: '/docs/components/form',
|
href: '/docs/components/form',
|
||||||
label: 'New',
|
|
||||||
items: [],
|
items: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -247,7 +246,6 @@ export const docsConfig: DocsConfig = {
|
||||||
{
|
{
|
||||||
title: 'Pagination',
|
title: 'Pagination',
|
||||||
href: '/docs/components/pagination',
|
href: '/docs/components/pagination',
|
||||||
label: 'New',
|
|
||||||
items: [],
|
items: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -315,13 +313,12 @@ export const docsConfig: DocsConfig = {
|
||||||
href: '/docs/components/textarea',
|
href: '/docs/components/textarea',
|
||||||
items: [],
|
items: [],
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// title: "Toast",
|
title: 'Toast',
|
||||||
// href: "#",
|
href: '/docs/components/toast',
|
||||||
// label: "Soon",
|
label: 'New',
|
||||||
// disabled: true,
|
items: [],
|
||||||
// items: []
|
},
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
title: 'Toggle',
|
title: 'Toggle',
|
||||||
href: '/docs/components/toggle',
|
href: '/docs/components/toggle',
|
||||||
|
|
|
||||||
|
|
@ -12,3 +12,9 @@ export const siteConfig = {
|
||||||
},
|
},
|
||||||
keywords: 'shadcn,Vue,Nuxt,Vue Components,TailwindCSS,Radix Vue',
|
keywords: 'shadcn,Vue,Nuxt,Vue Components,TailwindCSS,Radix Vue',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const announcementConfig = {
|
||||||
|
icon: '✨',
|
||||||
|
title: 'New Toast compoent',
|
||||||
|
link: '/docs/components/toast',
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import PageHeader from '../components/PageHeader.vue'
|
||||||
import PageHeaderHeading from '../components/PageHeaderHeading.vue'
|
import PageHeaderHeading from '../components/PageHeaderHeading.vue'
|
||||||
import PageHeaderDescription from '../components/PageHeaderDescription.vue'
|
import PageHeaderDescription from '../components/PageHeaderDescription.vue'
|
||||||
import ExamplesNav from '../components/ExamplesNav.vue'
|
import ExamplesNav from '../components/ExamplesNav.vue'
|
||||||
|
import { announcementConfig } from '../config/site'
|
||||||
import ArrowRightIcon from '~icons/radix-icons/arrow-right'
|
import ArrowRightIcon from '~icons/radix-icons/arrow-right'
|
||||||
|
|
||||||
import { buttonVariants } from '@/lib/registry/new-york/ui/button'
|
import { buttonVariants } from '@/lib/registry/new-york/ui/button'
|
||||||
|
|
@ -14,13 +15,13 @@ import { cn } from '@/lib/utils'
|
||||||
<div class="container relative">
|
<div class="container relative">
|
||||||
<PageHeader class="page-header pb-8">
|
<PageHeader class="page-header pb-8">
|
||||||
<a
|
<a
|
||||||
href="/docs/components/form"
|
:href="announcementConfig.link"
|
||||||
class="inline-flex items-center rounded-lg bg-muted px-3 py-1 text-sm font-medium"
|
class="inline-flex items-center rounded-lg bg-muted px-3 py-1 text-sm font-medium"
|
||||||
>
|
>
|
||||||
🎉 <Separator class="mx-2 h-4" orientation="vertical" />
|
{{ announcementConfig.icon }} <Separator class="mx-2 h-4" orientation="vertical" />
|
||||||
<span class="sm:hidden">New form & pagination component</span>
|
<span class="sm:hidden">{{ announcementConfig.title }}</span>
|
||||||
<span class="hidden sm:inline">
|
<span class="hidden sm:inline">
|
||||||
New form & pagination component
|
{{ announcementConfig.title }}
|
||||||
</span>
|
</span>
|
||||||
<ArrowRightIcon class="ml-1 h-4 w-4" />
|
<ArrowRightIcon class="ml-1 h-4 w-4" />
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,12 @@ import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, Command
|
||||||
|
|
||||||
import { Button } from '@/lib/registry/default/ui/button'
|
import { Button } from '@/lib/registry/default/ui/button'
|
||||||
import RadixIconsGithubLogo from '~icons/radix-icons/github-logo'
|
import RadixIconsGithubLogo from '~icons/radix-icons/github-logo'
|
||||||
import TablerBrandX from '~icons/tabler/brand-x'
|
|
||||||
import RadixIconsMoon from '~icons/radix-icons/moon'
|
import RadixIconsMoon from '~icons/radix-icons/moon'
|
||||||
import RadixIconsSun from '~icons/radix-icons/sun'
|
import RadixIconsSun from '~icons/radix-icons/sun'
|
||||||
import { useConfigStore } from '@/stores/config'
|
import { useConfigStore } from '@/stores/config'
|
||||||
import { Dialog, DialogContent } from '@/lib/registry/default/ui/dialog'
|
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 File from '~icons/radix-icons/file'
|
||||||
import Circle from '~icons/radix-icons/circle'
|
import Circle from '~icons/radix-icons/circle'
|
||||||
|
|
@ -279,5 +280,7 @@ watch(() => $route.path, (n) => {
|
||||||
</Command>
|
</Command>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
<DefaultToaster />
|
||||||
|
<NewYorkToaster />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -103,14 +103,14 @@ export const Index = {
|
||||||
CheckboxFormMultiple: {
|
CheckboxFormMultiple: {
|
||||||
name: 'CheckboxFormMultiple',
|
name: 'CheckboxFormMultiple',
|
||||||
type: 'components:example',
|
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),
|
component: () => import('../src/lib/registry/default/example/CheckboxFormMultiple.vue').then(m => m.default),
|
||||||
files: ['../src/lib/registry/default/example/CheckboxFormMultiple.vue'],
|
files: ['../src/lib/registry/default/example/CheckboxFormMultiple.vue'],
|
||||||
},
|
},
|
||||||
CheckboxFormSingle: {
|
CheckboxFormSingle: {
|
||||||
name: 'CheckboxFormSingle',
|
name: 'CheckboxFormSingle',
|
||||||
type: 'components:example',
|
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),
|
component: () => import('../src/lib/registry/default/example/CheckboxFormSingle.vue').then(m => m.default),
|
||||||
files: ['../src/lib/registry/default/example/CheckboxFormSingle.vue'],
|
files: ['../src/lib/registry/default/example/CheckboxFormSingle.vue'],
|
||||||
},
|
},
|
||||||
|
|
@ -145,7 +145,7 @@ export const Index = {
|
||||||
ComboboxForm: {
|
ComboboxForm: {
|
||||||
name: 'ComboboxForm',
|
name: 'ComboboxForm',
|
||||||
type: 'components:example',
|
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),
|
component: () => import('../src/lib/registry/default/example/ComboboxForm.vue').then(m => m.default),
|
||||||
files: ['../src/lib/registry/default/example/ComboboxForm.vue'],
|
files: ['../src/lib/registry/default/example/ComboboxForm.vue'],
|
||||||
},
|
},
|
||||||
|
|
@ -194,7 +194,7 @@ export const Index = {
|
||||||
DatePickerForm: {
|
DatePickerForm: {
|
||||||
name: 'DatePickerForm',
|
name: 'DatePickerForm',
|
||||||
type: 'components:example',
|
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),
|
component: () => import('../src/lib/registry/default/example/DatePickerForm.vue').then(m => m.default),
|
||||||
files: ['../src/lib/registry/default/example/DatePickerForm.vue'],
|
files: ['../src/lib/registry/default/example/DatePickerForm.vue'],
|
||||||
},
|
},
|
||||||
|
|
@ -257,14 +257,14 @@ export const Index = {
|
||||||
InputForm: {
|
InputForm: {
|
||||||
name: 'InputForm',
|
name: 'InputForm',
|
||||||
type: 'components:example',
|
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),
|
component: () => import('../src/lib/registry/default/example/InputForm.vue').then(m => m.default),
|
||||||
files: ['../src/lib/registry/default/example/InputForm.vue'],
|
files: ['../src/lib/registry/default/example/InputForm.vue'],
|
||||||
},
|
},
|
||||||
InputFormAutoAnimate: {
|
InputFormAutoAnimate: {
|
||||||
name: 'InputFormAutoAnimate',
|
name: 'InputFormAutoAnimate',
|
||||||
type: 'components:example',
|
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),
|
component: () => import('../src/lib/registry/default/example/InputFormAutoAnimate.vue').then(m => m.default),
|
||||||
files: ['../src/lib/registry/default/example/InputFormAutoAnimate.vue'],
|
files: ['../src/lib/registry/default/example/InputFormAutoAnimate.vue'],
|
||||||
},
|
},
|
||||||
|
|
@ -341,7 +341,7 @@ export const Index = {
|
||||||
RadioGroupForm: {
|
RadioGroupForm: {
|
||||||
name: 'RadioGroupForm',
|
name: 'RadioGroupForm',
|
||||||
type: 'components:example',
|
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),
|
component: () => import('../src/lib/registry/default/example/RadioGroupForm.vue').then(m => m.default),
|
||||||
files: ['../src/lib/registry/default/example/RadioGroupForm.vue'],
|
files: ['../src/lib/registry/default/example/RadioGroupForm.vue'],
|
||||||
},
|
},
|
||||||
|
|
@ -362,7 +362,7 @@ export const Index = {
|
||||||
SelectForm: {
|
SelectForm: {
|
||||||
name: 'SelectForm',
|
name: 'SelectForm',
|
||||||
type: 'components:example',
|
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),
|
component: () => import('../src/lib/registry/default/example/SelectForm.vue').then(m => m.default),
|
||||||
files: ['../src/lib/registry/default/example/SelectForm.vue'],
|
files: ['../src/lib/registry/default/example/SelectForm.vue'],
|
||||||
},
|
},
|
||||||
|
|
@ -404,7 +404,7 @@ export const Index = {
|
||||||
SwitchForm: {
|
SwitchForm: {
|
||||||
name: 'SwitchForm',
|
name: 'SwitchForm',
|
||||||
type: 'components:example',
|
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),
|
component: () => import('../src/lib/registry/default/example/SwitchForm.vue').then(m => m.default),
|
||||||
files: ['../src/lib/registry/default/example/SwitchForm.vue'],
|
files: ['../src/lib/registry/default/example/SwitchForm.vue'],
|
||||||
},
|
},
|
||||||
|
|
@ -439,7 +439,7 @@ export const Index = {
|
||||||
TextareaForm: {
|
TextareaForm: {
|
||||||
name: 'TextareaForm',
|
name: 'TextareaForm',
|
||||||
type: 'components:example',
|
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),
|
component: () => import('../src/lib/registry/default/example/TextareaForm.vue').then(m => m.default),
|
||||||
files: ['../src/lib/registry/default/example/TextareaForm.vue'],
|
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),
|
component: () => import('../src/lib/registry/default/example/TextareaWithText.vue').then(m => m.default),
|
||||||
files: ['../src/lib/registry/default/example/TextareaWithText.vue'],
|
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: {
|
ToggleDemo: {
|
||||||
name: 'ToggleDemo',
|
name: 'ToggleDemo',
|
||||||
type: 'components:example',
|
type: 'components:example',
|
||||||
|
|
@ -581,21 +616,21 @@ export const Index = {
|
||||||
type: 'components:example',
|
type: 'components:example',
|
||||||
registryDependencies: ['button', 'card', 'themes', 'config'],
|
registryDependencies: ['button', 'card', 'themes', 'config'],
|
||||||
component: () => import('../src/lib/registry/default/example/Cards/ActivityGoal.vue').then(m => m.default),
|
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: {
|
DataTable: {
|
||||||
name: 'DataTable',
|
name: 'DataTable',
|
||||||
type: 'components:example',
|
type: 'components:example',
|
||||||
registryDependencies: ['button', 'checkbox', 'dropdown-menu', 'input', 'table', 'card', 'utils'],
|
registryDependencies: ['button', 'checkbox', 'dropdown-menu', 'input', 'table', 'card', 'utils'],
|
||||||
component: () => import('../src/lib/registry/default/example/Cards/DataTable.vue').then(m => m.default),
|
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: {
|
Metric: {
|
||||||
name: 'Metric',
|
name: 'Metric',
|
||||||
type: 'components:example',
|
type: 'components:example',
|
||||||
registryDependencies: ['card', 'config'],
|
registryDependencies: ['card', 'config'],
|
||||||
component: () => import('../src/lib/registry/default/example/Cards/Metric.vue').then(m => m.default),
|
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': {
|
'new-york': {
|
||||||
|
|
@ -700,14 +735,14 @@ export const Index = {
|
||||||
CheckboxFormMultiple: {
|
CheckboxFormMultiple: {
|
||||||
name: 'CheckboxFormMultiple',
|
name: 'CheckboxFormMultiple',
|
||||||
type: 'components:example',
|
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),
|
component: () => import('../src/lib/registry/new-york/example/CheckboxFormMultiple.vue').then(m => m.default),
|
||||||
files: ['../src/lib/registry/new-york/example/CheckboxFormMultiple.vue'],
|
files: ['../src/lib/registry/new-york/example/CheckboxFormMultiple.vue'],
|
||||||
},
|
},
|
||||||
CheckboxFormSingle: {
|
CheckboxFormSingle: {
|
||||||
name: 'CheckboxFormSingle',
|
name: 'CheckboxFormSingle',
|
||||||
type: 'components:example',
|
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),
|
component: () => import('../src/lib/registry/new-york/example/CheckboxFormSingle.vue').then(m => m.default),
|
||||||
files: ['../src/lib/registry/new-york/example/CheckboxFormSingle.vue'],
|
files: ['../src/lib/registry/new-york/example/CheckboxFormSingle.vue'],
|
||||||
},
|
},
|
||||||
|
|
@ -742,7 +777,7 @@ export const Index = {
|
||||||
ComboboxForm: {
|
ComboboxForm: {
|
||||||
name: 'ComboboxForm',
|
name: 'ComboboxForm',
|
||||||
type: 'components:example',
|
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),
|
component: () => import('../src/lib/registry/new-york/example/ComboboxForm.vue').then(m => m.default),
|
||||||
files: ['../src/lib/registry/new-york/example/ComboboxForm.vue'],
|
files: ['../src/lib/registry/new-york/example/ComboboxForm.vue'],
|
||||||
},
|
},
|
||||||
|
|
@ -791,7 +826,7 @@ export const Index = {
|
||||||
DatePickerForm: {
|
DatePickerForm: {
|
||||||
name: 'DatePickerForm',
|
name: 'DatePickerForm',
|
||||||
type: 'components:example',
|
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),
|
component: () => import('../src/lib/registry/new-york/example/DatePickerForm.vue').then(m => m.default),
|
||||||
files: ['../src/lib/registry/new-york/example/DatePickerForm.vue'],
|
files: ['../src/lib/registry/new-york/example/DatePickerForm.vue'],
|
||||||
},
|
},
|
||||||
|
|
@ -854,14 +889,14 @@ export const Index = {
|
||||||
InputForm: {
|
InputForm: {
|
||||||
name: 'InputForm',
|
name: 'InputForm',
|
||||||
type: 'components:example',
|
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),
|
component: () => import('../src/lib/registry/new-york/example/InputForm.vue').then(m => m.default),
|
||||||
files: ['../src/lib/registry/new-york/example/InputForm.vue'],
|
files: ['../src/lib/registry/new-york/example/InputForm.vue'],
|
||||||
},
|
},
|
||||||
InputFormAutoAnimate: {
|
InputFormAutoAnimate: {
|
||||||
name: 'InputFormAutoAnimate',
|
name: 'InputFormAutoAnimate',
|
||||||
type: 'components:example',
|
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),
|
component: () => import('../src/lib/registry/new-york/example/InputFormAutoAnimate.vue').then(m => m.default),
|
||||||
files: ['../src/lib/registry/new-york/example/InputFormAutoAnimate.vue'],
|
files: ['../src/lib/registry/new-york/example/InputFormAutoAnimate.vue'],
|
||||||
},
|
},
|
||||||
|
|
@ -938,7 +973,7 @@ export const Index = {
|
||||||
RadioGroupForm: {
|
RadioGroupForm: {
|
||||||
name: 'RadioGroupForm',
|
name: 'RadioGroupForm',
|
||||||
type: 'components:example',
|
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),
|
component: () => import('../src/lib/registry/new-york/example/RadioGroupForm.vue').then(m => m.default),
|
||||||
files: ['../src/lib/registry/new-york/example/RadioGroupForm.vue'],
|
files: ['../src/lib/registry/new-york/example/RadioGroupForm.vue'],
|
||||||
},
|
},
|
||||||
|
|
@ -959,7 +994,7 @@ export const Index = {
|
||||||
SelectForm: {
|
SelectForm: {
|
||||||
name: 'SelectForm',
|
name: 'SelectForm',
|
||||||
type: 'components:example',
|
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),
|
component: () => import('../src/lib/registry/new-york/example/SelectForm.vue').then(m => m.default),
|
||||||
files: ['../src/lib/registry/new-york/example/SelectForm.vue'],
|
files: ['../src/lib/registry/new-york/example/SelectForm.vue'],
|
||||||
},
|
},
|
||||||
|
|
@ -1001,7 +1036,7 @@ export const Index = {
|
||||||
SwitchForm: {
|
SwitchForm: {
|
||||||
name: 'SwitchForm',
|
name: 'SwitchForm',
|
||||||
type: 'components:example',
|
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),
|
component: () => import('../src/lib/registry/new-york/example/SwitchForm.vue').then(m => m.default),
|
||||||
files: ['../src/lib/registry/new-york/example/SwitchForm.vue'],
|
files: ['../src/lib/registry/new-york/example/SwitchForm.vue'],
|
||||||
},
|
},
|
||||||
|
|
@ -1036,7 +1071,7 @@ export const Index = {
|
||||||
TextareaForm: {
|
TextareaForm: {
|
||||||
name: 'TextareaForm',
|
name: 'TextareaForm',
|
||||||
type: 'components:example',
|
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),
|
component: () => import('../src/lib/registry/new-york/example/TextareaForm.vue').then(m => m.default),
|
||||||
files: ['../src/lib/registry/new-york/example/TextareaForm.vue'],
|
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),
|
component: () => import('../src/lib/registry/new-york/example/TextareaWithText.vue').then(m => m.default),
|
||||||
files: ['../src/lib/registry/new-york/example/TextareaWithText.vue'],
|
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: {
|
ToggleDemo: {
|
||||||
name: 'ToggleDemo',
|
name: 'ToggleDemo',
|
||||||
type: 'components:example',
|
type: 'components:example',
|
||||||
|
|
@ -1178,21 +1248,21 @@ export const Index = {
|
||||||
type: 'components:example',
|
type: 'components:example',
|
||||||
registryDependencies: ['button', 'card', 'themes', 'config'],
|
registryDependencies: ['button', 'card', 'themes', 'config'],
|
||||||
component: () => import('../src/lib/registry/new-york/example/Cards/ActivityGoal.vue').then(m => m.default),
|
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: {
|
DataTable: {
|
||||||
name: 'DataTable',
|
name: 'DataTable',
|
||||||
type: 'components:example',
|
type: 'components:example',
|
||||||
registryDependencies: ['button', 'checkbox', 'dropdown-menu', 'input', 'table', 'card', 'utils'],
|
registryDependencies: ['button', 'checkbox', 'dropdown-menu', 'input', 'table', 'card', 'utils'],
|
||||||
component: () => import('../src/lib/registry/new-york/example/Cards/DataTable.vue').then(m => m.default),
|
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: {
|
Metric: {
|
||||||
name: 'Metric',
|
name: 'Metric',
|
||||||
type: 'components:example',
|
type: 'components:example',
|
||||||
registryDependencies: ['card', 'config'],
|
registryDependencies: ['card', 'config'],
|
||||||
component: () => import('../src/lib/registry/new-york/example/Cards/Metric.vue').then(m => m.default),
|
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'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
93
apps/www/src/content/docs/components/toast.md
Normal file
93
apps/www/src/content/docs/components/toast.md
Normal file
|
|
@ -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
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
<ComponentPreview name="ToastDemo" />
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
|
||||||
|
### 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}
|
||||||
|
<script setup lang="ts">
|
||||||
|
import Toaster from '@/components/ui/toast/Toaster.vue'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Toaster />
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
|
||||||
|
## 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
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { useToast } from '@/components/ui/toast/use-toast'
|
||||||
|
|
||||||
|
const { toast } = useToast()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Button
|
||||||
|
@click="() => {
|
||||||
|
toast({
|
||||||
|
title: 'Scheduled: Catch up',
|
||||||
|
description: 'Friday, February 10, 2023 at 5:57 PM',
|
||||||
|
});
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
Add to calander
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
<Callout>
|
||||||
|
|
||||||
|
To display multiple toasts at the same time, you can update the `TOAST_LIMIT` in `use-toast.ts`.
|
||||||
|
|
||||||
|
</Callout>
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Simple
|
||||||
|
|
||||||
|
<ComponentPreview name="ToastSimple" />
|
||||||
|
|
||||||
|
### With Title
|
||||||
|
|
||||||
|
<ComponentPreview name="ToastWithTitle" />
|
||||||
|
|
||||||
|
### With Action
|
||||||
|
|
||||||
|
<ComponentPreview name="ToastWithAction" />
|
||||||
|
|
||||||
|
### Destructive
|
||||||
|
|
||||||
|
Use `toast({ variant: "destructive" })` to display a destructive toast.
|
||||||
|
|
||||||
|
<ComponentPreview name="ToastDestructive" />
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { h, ref } from 'vue'
|
||||||
import * as z from 'zod'
|
import * as z from 'zod'
|
||||||
import { format } from 'date-fns'
|
import { format } from 'date-fns'
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
|
|
@ -26,6 +26,7 @@ import {
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from '@/lib/registry/default/ui/popover'
|
} from '@/lib/registry/default/ui/popover'
|
||||||
import { Calendar } from '@/lib/registry/new-york/ui/calendar'
|
import { Calendar } from '@/lib/registry/new-york/ui/calendar'
|
||||||
|
import { toast } from '@/lib/registry/new-york/ui/toast'
|
||||||
|
|
||||||
const open = ref(false)
|
const open = ref(false)
|
||||||
|
|
||||||
|
|
@ -63,7 +64,10 @@ const filterFunction = (list: typeof languages, search: string) => list.filter(i
|
||||||
// https://github.com/logaretm/vee-validate/issues/3521
|
// https://github.com/logaretm/vee-validate/issues/3521
|
||||||
// https://github.com/logaretm/vee-validate/discussions/3571
|
// https://github.com/logaretm/vee-validate/discussions/3571
|
||||||
async function onSubmit(values: any) {
|
async function onSubmit(values: any) {
|
||||||
console.log('Form submitted!', values)
|
toast({
|
||||||
|
title: 'You submitted the following values:',
|
||||||
|
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { h } from 'vue'
|
||||||
import { useForm } from 'vee-validate'
|
import { useForm } from 'vee-validate'
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
import * as z from 'zod'
|
import * as z from 'zod'
|
||||||
|
|
@ -9,6 +10,7 @@ import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessa
|
||||||
import { Separator } from '@/lib/registry/new-york/ui/separator'
|
import { Separator } from '@/lib/registry/new-york/ui/separator'
|
||||||
import { RadioGroup, RadioGroupItem } from '@/lib/registry/default/ui/radio-group'
|
import { RadioGroup, RadioGroupItem } from '@/lib/registry/default/ui/radio-group'
|
||||||
import { Button, buttonVariants } from '@/lib/registry/new-york/ui/button'
|
import { Button, buttonVariants } from '@/lib/registry/new-york/ui/button'
|
||||||
|
import { toast } from '@/lib/registry/new-york/ui/toast'
|
||||||
|
|
||||||
const appearanceFormSchema = toTypedSchema(z.object({
|
const appearanceFormSchema = toTypedSchema(z.object({
|
||||||
theme: z.enum(['light', 'dark'], {
|
theme: z.enum(['light', 'dark'], {
|
||||||
|
|
@ -29,7 +31,10 @@ const { handleSubmit } = useForm({
|
||||||
})
|
})
|
||||||
|
|
||||||
const onSubmit = handleSubmit((values) => {
|
const onSubmit = handleSubmit((values) => {
|
||||||
console.log('Form submitted!', values)
|
toast({
|
||||||
|
title: 'You submitted the following values:',
|
||||||
|
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { h } from 'vue'
|
||||||
import { useForm } from 'vee-validate'
|
import { useForm } from 'vee-validate'
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
import * as z from 'zod'
|
import * as z from 'zod'
|
||||||
|
|
@ -7,6 +8,7 @@ import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessa
|
||||||
import { Separator } from '@/lib/registry/new-york/ui/separator'
|
import { Separator } from '@/lib/registry/new-york/ui/separator'
|
||||||
import { Checkbox } from '@/lib/registry/new-york/ui/checkbox'
|
import { Checkbox } from '@/lib/registry/new-york/ui/checkbox'
|
||||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
|
import { toast } from '@/lib/registry/new-york/ui/toast'
|
||||||
|
|
||||||
const items = [
|
const items = [
|
||||||
{
|
{
|
||||||
|
|
@ -53,7 +55,10 @@ const { handleSubmit } = useForm({
|
||||||
})
|
})
|
||||||
|
|
||||||
const onSubmit = handleSubmit((values) => {
|
const onSubmit = handleSubmit((values) => {
|
||||||
console.log('Form submitted!', values)
|
toast({
|
||||||
|
title: 'You submitted the following values:',
|
||||||
|
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { h } from 'vue'
|
||||||
import { useForm } from 'vee-validate'
|
import { useForm } from 'vee-validate'
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
import * as z from 'zod'
|
import * as z from 'zod'
|
||||||
|
|
@ -9,6 +10,7 @@ import { RadioGroup, RadioGroupItem } from '@/lib/registry/new-york/ui/radio-gro
|
||||||
import { Switch } from '@/lib/registry/new-york/ui/switch'
|
import { Switch } from '@/lib/registry/new-york/ui/switch'
|
||||||
import { Checkbox } from '@/lib/registry/new-york/ui/checkbox'
|
import { Checkbox } from '@/lib/registry/new-york/ui/checkbox'
|
||||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
|
import { toast } from '@/lib/registry/new-york/ui/toast'
|
||||||
|
|
||||||
const notificationsFormSchema = toTypedSchema(z.object({
|
const notificationsFormSchema = toTypedSchema(z.object({
|
||||||
type: z.enum(['all', 'mentions', 'none'], {
|
type: z.enum(['all', 'mentions', 'none'], {
|
||||||
|
|
@ -32,7 +34,10 @@ const { handleSubmit } = useForm({
|
||||||
})
|
})
|
||||||
|
|
||||||
const onSubmit = handleSubmit((values, { resetForm }) => {
|
const onSubmit = handleSubmit((values, { resetForm }) => {
|
||||||
console.log('Form submitted!', values)
|
toast({
|
||||||
|
title: 'You submitted the following values:',
|
||||||
|
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { h, ref } from 'vue'
|
||||||
import { FieldArray, useForm } from 'vee-validate'
|
import { FieldArray, useForm } from 'vee-validate'
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
import * as z from 'zod'
|
import * as z from 'zod'
|
||||||
|
|
@ -19,6 +19,7 @@ import {
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@/lib/registry/new-york/ui/select'
|
} from '@/lib/registry/new-york/ui/select'
|
||||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
|
import { toast } from '@/lib/registry/new-york/ui/toast'
|
||||||
|
|
||||||
const verifiedEmails = ref(['m@example.com', 'm@google.com', 'm@support.com'])
|
const verifiedEmails = ref(['m@example.com', 'm@google.com', 'm@support.com'])
|
||||||
|
|
||||||
|
|
@ -58,7 +59,10 @@ const { handleSubmit, resetForm } = useForm({
|
||||||
})
|
})
|
||||||
|
|
||||||
const onSubmit = handleSubmit((values) => {
|
const onSubmit = handleSubmit((values) => {
|
||||||
console.log('Form submitted!', values)
|
toast({
|
||||||
|
title: 'You submitted the following values:',
|
||||||
|
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { h } from 'vue'
|
||||||
import { useForm } from 'vee-validate'
|
import { useForm } from 'vee-validate'
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
import * as z from 'zod'
|
import * as z from 'zod'
|
||||||
|
|
@ -13,6 +14,7 @@ import {
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from '@/lib/registry/default/ui/form'
|
} from '@/lib/registry/default/ui/form'
|
||||||
import { Checkbox } from '@/lib/registry/default/ui/checkbox'
|
import { Checkbox } from '@/lib/registry/default/ui/checkbox'
|
||||||
|
import { toast } from '@/lib/registry/default/ui/toast'
|
||||||
|
|
||||||
const items = [
|
const items = [
|
||||||
{
|
{
|
||||||
|
|
@ -55,7 +57,10 @@ const { handleSubmit } = useForm({
|
||||||
})
|
})
|
||||||
|
|
||||||
const onSubmit = handleSubmit((values) => {
|
const onSubmit = handleSubmit((values) => {
|
||||||
console.log('Form submitted!', values)
|
toast({
|
||||||
|
title: 'You submitted the following values:',
|
||||||
|
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { h } from 'vue'
|
||||||
import { useForm } from 'vee-validate'
|
import { useForm } from 'vee-validate'
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
import * as z from 'zod'
|
import * as z from 'zod'
|
||||||
|
|
@ -13,6 +14,7 @@ import {
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from '@/lib/registry/default/ui/form'
|
} from '@/lib/registry/default/ui/form'
|
||||||
import { Checkbox } from '@/lib/registry/default/ui/checkbox'
|
import { Checkbox } from '@/lib/registry/default/ui/checkbox'
|
||||||
|
import { toast } from '@/lib/registry/default/ui/toast'
|
||||||
|
|
||||||
const formSchema = toTypedSchema(z.object({
|
const formSchema = toTypedSchema(z.object({
|
||||||
mobile: z.boolean().default(false).optional(),
|
mobile: z.boolean().default(false).optional(),
|
||||||
|
|
@ -26,7 +28,10 @@ const { handleSubmit } = useForm({
|
||||||
})
|
})
|
||||||
|
|
||||||
const onSubmit = handleSubmit((values) => {
|
const onSubmit = handleSubmit((values) => {
|
||||||
console.log('Form submitted!', values)
|
toast({
|
||||||
|
title: 'You submitted the following values:',
|
||||||
|
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { h } from 'vue'
|
||||||
import { useForm } from 'vee-validate'
|
import { useForm } from 'vee-validate'
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
import * as z from 'zod'
|
import * as z from 'zod'
|
||||||
|
|
@ -26,6 +27,7 @@ import {
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from '@/lib/registry/default/ui/popover'
|
} from '@/lib/registry/default/ui/popover'
|
||||||
|
import { toast } from '@/lib/registry/default/ui/toast'
|
||||||
|
|
||||||
const languages = [
|
const languages = [
|
||||||
{ label: 'English', value: 'en' },
|
{ label: 'English', value: 'en' },
|
||||||
|
|
@ -50,7 +52,10 @@ const { handleSubmit, setValues, values } = useForm({
|
||||||
})
|
})
|
||||||
|
|
||||||
const onSubmit = handleSubmit((values) => {
|
const onSubmit = handleSubmit((values) => {
|
||||||
console.log('Form submitted!', values)
|
toast({
|
||||||
|
title: 'You submitted the following values:',
|
||||||
|
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { h } from 'vue'
|
||||||
import { format } from 'date-fns'
|
import { format } from 'date-fns'
|
||||||
import { Calendar as CalendarIcon } from 'lucide-vue-next'
|
import { Calendar as CalendarIcon } from 'lucide-vue-next'
|
||||||
import { useForm } from 'vee-validate'
|
import { useForm } from 'vee-validate'
|
||||||
|
|
@ -22,6 +23,7 @@ import {
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from '@/lib/registry/default/ui/popover'
|
} from '@/lib/registry/default/ui/popover'
|
||||||
|
import { toast } from '@/lib/registry/default/ui/toast'
|
||||||
|
|
||||||
const formSchema = toTypedSchema(z.object({
|
const formSchema = toTypedSchema(z.object({
|
||||||
dob: z.date({
|
dob: z.date({
|
||||||
|
|
@ -34,7 +36,10 @@ const { handleSubmit } = useForm({
|
||||||
})
|
})
|
||||||
|
|
||||||
const onSubmit = handleSubmit((values) => {
|
const onSubmit = handleSubmit((values) => {
|
||||||
console.log('Form submitted!', values)
|
toast({
|
||||||
|
title: 'You submitted the following values:',
|
||||||
|
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { h } from 'vue'
|
||||||
import { useForm } from 'vee-validate'
|
import { useForm } from 'vee-validate'
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
import * as z from 'zod'
|
import * as z from 'zod'
|
||||||
|
|
@ -13,6 +14,7 @@ import {
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from '@/lib/registry/default/ui/form'
|
} from '@/lib/registry/default/ui/form'
|
||||||
import { Input } from '@/lib/registry/default/ui/input'
|
import { Input } from '@/lib/registry/default/ui/input'
|
||||||
|
import { toast } from '@/lib/registry/default/ui/toast'
|
||||||
|
|
||||||
const formSchema = toTypedSchema(z.object({
|
const formSchema = toTypedSchema(z.object({
|
||||||
username: z.string().min(2).max(50),
|
username: z.string().min(2).max(50),
|
||||||
|
|
@ -23,7 +25,10 @@ const { handleSubmit } = useForm({
|
||||||
})
|
})
|
||||||
|
|
||||||
const onSubmit = handleSubmit((values) => {
|
const onSubmit = handleSubmit((values) => {
|
||||||
console.log('Form submitted!', values)
|
toast({
|
||||||
|
title: 'You submitted the following values:',
|
||||||
|
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { h } from 'vue'
|
||||||
import { useForm } from 'vee-validate'
|
import { useForm } from 'vee-validate'
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
import * as z from 'zod'
|
import * as z from 'zod'
|
||||||
|
|
@ -14,6 +15,7 @@ import {
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from '@/lib/registry/default/ui/form'
|
} from '@/lib/registry/default/ui/form'
|
||||||
import { Input } from '@/lib/registry/default/ui/input'
|
import { Input } from '@/lib/registry/default/ui/input'
|
||||||
|
import { toast } from '@/lib/registry/default/ui/toast'
|
||||||
|
|
||||||
const formSchema = toTypedSchema(z.object({
|
const formSchema = toTypedSchema(z.object({
|
||||||
username: z.string().min(2).max(50),
|
username: z.string().min(2).max(50),
|
||||||
|
|
@ -24,7 +26,10 @@ const { handleSubmit } = useForm({
|
||||||
})
|
})
|
||||||
|
|
||||||
const onSubmit = handleSubmit((values) => {
|
const onSubmit = handleSubmit((values) => {
|
||||||
console.log('Form submitted!', values)
|
toast({
|
||||||
|
title: 'You submitted the following values:',
|
||||||
|
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { h } from 'vue'
|
||||||
import { useForm } from 'vee-validate'
|
import { useForm } from 'vee-validate'
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
import * as z from 'zod'
|
import * as z from 'zod'
|
||||||
|
|
@ -12,6 +13,7 @@ import {
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from '@/lib/registry/default/ui/form'
|
} from '@/lib/registry/default/ui/form'
|
||||||
import { RadioGroup, RadioGroupItem } from '@/lib/registry/default/ui/radio-group'
|
import { RadioGroup, RadioGroupItem } from '@/lib/registry/default/ui/radio-group'
|
||||||
|
import { toast } from '@/lib/registry/default/ui/toast'
|
||||||
|
|
||||||
const formSchema = toTypedSchema(z.object({
|
const formSchema = toTypedSchema(z.object({
|
||||||
type: z.enum(['all', 'mentions', 'none'], {
|
type: z.enum(['all', 'mentions', 'none'], {
|
||||||
|
|
@ -24,7 +26,10 @@ const { handleSubmit } = useForm({
|
||||||
})
|
})
|
||||||
|
|
||||||
const onSubmit = handleSubmit((values) => {
|
const onSubmit = handleSubmit((values) => {
|
||||||
console.log('Form submitted!', values)
|
toast({
|
||||||
|
title: 'You submitted the following values:',
|
||||||
|
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { h } from 'vue'
|
||||||
import { useForm } from 'vee-validate'
|
import { useForm } from 'vee-validate'
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
import * as z from 'zod'
|
import * as z from 'zod'
|
||||||
|
|
@ -20,6 +21,7 @@ import {
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@/lib/registry/default/ui/select'
|
} from '@/lib/registry/default/ui/select'
|
||||||
|
import { toast } from '@/lib/registry/default/ui/toast'
|
||||||
|
|
||||||
const formSchema = toTypedSchema(z.object({
|
const formSchema = toTypedSchema(z.object({
|
||||||
email: z
|
email: z
|
||||||
|
|
@ -34,7 +36,10 @@ const { handleSubmit } = useForm({
|
||||||
})
|
})
|
||||||
|
|
||||||
const onSubmit = handleSubmit((values) => {
|
const onSubmit = handleSubmit((values) => {
|
||||||
console.log('Form submitted!', values)
|
toast({
|
||||||
|
title: 'You submitted the following values:',
|
||||||
|
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { h } from 'vue'
|
||||||
import { useForm } from 'vee-validate'
|
import { useForm } from 'vee-validate'
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
import * as z from 'zod'
|
import * as z from 'zod'
|
||||||
|
|
@ -12,6 +13,7 @@ import {
|
||||||
FormLabel,
|
FormLabel,
|
||||||
} from '@/lib/registry/default/ui/form'
|
} from '@/lib/registry/default/ui/form'
|
||||||
import { Switch } from '@/lib/registry/default/ui/switch'
|
import { Switch } from '@/lib/registry/default/ui/switch'
|
||||||
|
import { toast } from '@/lib/registry/default/ui/toast'
|
||||||
|
|
||||||
const formSchema = toTypedSchema(z.object({
|
const formSchema = toTypedSchema(z.object({
|
||||||
marketing_emails: z.boolean().default(false).optional(),
|
marketing_emails: z.boolean().default(false).optional(),
|
||||||
|
|
@ -26,7 +28,10 @@ const { handleSubmit } = useForm({
|
||||||
})
|
})
|
||||||
|
|
||||||
const onSubmit = handleSubmit((values) => {
|
const onSubmit = handleSubmit((values) => {
|
||||||
console.log('Form submitted!', values)
|
toast({
|
||||||
|
title: 'You submitted the following values:',
|
||||||
|
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { h } from 'vue'
|
||||||
import { useForm } from 'vee-validate'
|
import { useForm } from 'vee-validate'
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
import * as z from 'zod'
|
import * as z from 'zod'
|
||||||
|
|
@ -13,6 +14,7 @@ import {
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from '@/lib/registry/default/ui/form'
|
} from '@/lib/registry/default/ui/form'
|
||||||
import { Textarea } from '@/lib/registry/default/ui/textarea'
|
import { Textarea } from '@/lib/registry/default/ui/textarea'
|
||||||
|
import { toast } from '@/lib/registry/default/ui/toast'
|
||||||
|
|
||||||
const formSchema = toTypedSchema(z.object({
|
const formSchema = toTypedSchema(z.object({
|
||||||
bio: z
|
bio: z
|
||||||
|
|
@ -30,7 +32,10 @@ const { handleSubmit } = useForm({
|
||||||
})
|
})
|
||||||
|
|
||||||
const onSubmit = handleSubmit((values) => {
|
const onSubmit = handleSubmit((values) => {
|
||||||
console.log('Form submitted!', values)
|
toast({
|
||||||
|
title: 'You submitted the following values:',
|
||||||
|
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
19
apps/www/src/lib/registry/default/example/ToastDemo.vue
Normal file
19
apps/www/src/lib/registry/default/example/ToastDemo.vue
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Button } from '@/lib/registry/default/ui/button'
|
||||||
|
import { useToast } from '@/lib/registry/default/ui/toast/use-toast'
|
||||||
|
|
||||||
|
const { toast } = useToast()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Button
|
||||||
|
variant="outline" @click="() => {
|
||||||
|
toast({
|
||||||
|
title: 'Scheduled: Catch up',
|
||||||
|
description: 'Friday, February 10, 2023 at 5:57 PM',
|
||||||
|
});
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
Add to calander
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { h } from 'vue'
|
||||||
|
import { Button } from '@/lib/registry/default/ui/button'
|
||||||
|
import { useToast } from '@/lib/registry/default/ui/toast/use-toast'
|
||||||
|
import { ToastAction } from '@/lib/registry/default/ui/toast'
|
||||||
|
|
||||||
|
const { toast } = useToast()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Button
|
||||||
|
variant="outline" @click="() => {
|
||||||
|
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
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
18
apps/www/src/lib/registry/default/example/ToastSimple.vue
Normal file
18
apps/www/src/lib/registry/default/example/ToastSimple.vue
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Button } from '@/lib/registry/default/ui/button'
|
||||||
|
import { useToast } from '@/lib/registry/default/ui/toast/use-toast'
|
||||||
|
|
||||||
|
const { toast } = useToast()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Button
|
||||||
|
variant="outline" @click="() => {
|
||||||
|
toast({
|
||||||
|
description: 'Your message has been sent.',
|
||||||
|
});
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
Show Toast
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { h } from 'vue'
|
||||||
|
import { Button } from '@/lib/registry/default/ui/button'
|
||||||
|
import { useToast } from '@/lib/registry/default/ui/toast/use-toast'
|
||||||
|
import { ToastAction } from '@/lib/registry/default/ui/toast'
|
||||||
|
|
||||||
|
const { toast } = useToast()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Button
|
||||||
|
variant="outline" @click="() => {
|
||||||
|
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
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
19
apps/www/src/lib/registry/default/example/ToastWithTitle.vue
Normal file
19
apps/www/src/lib/registry/default/example/ToastWithTitle.vue
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Button } from '@/lib/registry/default/ui/button'
|
||||||
|
import { useToast } from '@/lib/registry/default/ui/toast/use-toast'
|
||||||
|
|
||||||
|
const { toast } = useToast()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Button
|
||||||
|
variant="outline" @click="() => {
|
||||||
|
toast({
|
||||||
|
title: 'Uh oh! Something went wrong.',
|
||||||
|
description: 'There was a problem with your request.',
|
||||||
|
});
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
Show Toast
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
|
@ -12,7 +12,6 @@ const props = defineProps<SwitchRootProps & { class?: string }>()
|
||||||
const emits = defineEmits<SwitchRootEmits>()
|
const emits = defineEmits<SwitchRootEmits>()
|
||||||
|
|
||||||
const forwarded = useForwardPropsEmits(props, emits)
|
const forwarded = useForwardPropsEmits(props, emits)
|
||||||
console.log(props, forwarded)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
||||||
32
apps/www/src/lib/registry/default/ui/toast/Toast.vue
Normal file
32
apps/www/src/lib/registry/default/ui/toast/Toast.vue
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { ToastRootEmits, ToastRootProps } from 'radix-vue'
|
||||||
|
import type { VariantProps } from 'class-variance-authority'
|
||||||
|
|
||||||
|
interface ToastVariantProps extends VariantProps<typeof toastVariants> {}
|
||||||
|
|
||||||
|
export interface ToastProps extends ToastRootProps {
|
||||||
|
class?: string
|
||||||
|
variant?: ToastVariantProps['variant']
|
||||||
|
'onOpenChange'?: ((value: boolean) => void) | undefined
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ToastRoot, useEmitAsProps } from 'radix-vue'
|
||||||
|
|
||||||
|
import { toastVariants } from '.'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<ToastProps>()
|
||||||
|
const emits = defineEmits<ToastRootEmits>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ToastRoot
|
||||||
|
v-bind="{ ...props, ...useEmitAsProps(emits) }"
|
||||||
|
:class="cn(toastVariants({ variant: props.variant }), props.class)"
|
||||||
|
@update:open="onOpenChange"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</ToastRoot>
|
||||||
|
</template>
|
||||||
12
apps/www/src/lib/registry/default/ui/toast/ToastAction.vue
Normal file
12
apps/www/src/lib/registry/default/ui/toast/ToastAction.vue
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ToastAction, type ToastActionProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<ToastActionProps & { class?: string }>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ToastAction v-bind="props" :class="cn('inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive', props.class)">
|
||||||
|
<slot />
|
||||||
|
</ToastAction>
|
||||||
|
</template>
|
||||||
15
apps/www/src/lib/registry/default/ui/toast/ToastClose.vue
Normal file
15
apps/www/src/lib/registry/default/ui/toast/ToastClose.vue
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ToastClose } from 'radix-vue'
|
||||||
|
import { XIcon } from 'lucide-vue-next'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: string
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ToastClose v-bind="props" :class="cn('absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600', props.class)">
|
||||||
|
<XIcon class="h-4 w-4" />
|
||||||
|
</ToastClose>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ToastDescription, type ToastDescriptionProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<ToastDescriptionProps & { class?: string }>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ToastDescription :class="cn('text-sm opacity-90', props.class)" v-bind="props">
|
||||||
|
<slot />
|
||||||
|
</ToastDescription>
|
||||||
|
</template>
|
||||||
11
apps/www/src/lib/registry/default/ui/toast/ToastProvider.vue
Normal file
11
apps/www/src/lib/registry/default/ui/toast/ToastProvider.vue
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ToastProvider, type ToastProviderProps } from 'radix-vue'
|
||||||
|
|
||||||
|
const props = defineProps<ToastProviderProps>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ToastProvider v-bind="props">
|
||||||
|
<slot />
|
||||||
|
</ToastProvider>
|
||||||
|
</template>
|
||||||
12
apps/www/src/lib/registry/default/ui/toast/ToastTitle.vue
Normal file
12
apps/www/src/lib/registry/default/ui/toast/ToastTitle.vue
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ToastTitle, type ToastTitleProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<ToastTitleProps & { class?: string }>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ToastTitle v-bind="props" :class="cn('text-sm font-semibold', props.class)">
|
||||||
|
<slot />
|
||||||
|
</ToastTitle>
|
||||||
|
</template>
|
||||||
10
apps/www/src/lib/registry/default/ui/toast/ToastViewport.vue
Normal file
10
apps/www/src/lib/registry/default/ui/toast/ToastViewport.vue
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ToastViewport, type ToastViewportProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<ToastViewportProps & { class?: string }>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ToastViewport v-bind="props" :class="cn('fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]', props.class)" />
|
||||||
|
</template>
|
||||||
30
apps/www/src/lib/registry/default/ui/toast/Toaster.vue
Normal file
30
apps/www/src/lib/registry/default/ui/toast/Toaster.vue
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { isVNode } from 'vue'
|
||||||
|
import { useToast } from './use-toast'
|
||||||
|
import { Toast, ToastClose, ToastDescription, ToastProvider, ToastTitle, ToastViewport } from '.'
|
||||||
|
|
||||||
|
const { toasts } = useToast()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ToastProvider>
|
||||||
|
<Toast v-for="toast in toasts" :key="toast.id" v-bind="toast">
|
||||||
|
<div class="grid gap-1">
|
||||||
|
<ToastTitle v-if="toast.title">
|
||||||
|
{{ toast.title }}
|
||||||
|
</ToastTitle>
|
||||||
|
<template v-if="toast.description">
|
||||||
|
<ToastDescription v-if="isVNode(toast.description)">
|
||||||
|
<component :is="toast.description" />
|
||||||
|
</ToastDescription>
|
||||||
|
<ToastDescription v-else>
|
||||||
|
{{ toast.description }}
|
||||||
|
</ToastDescription>
|
||||||
|
</template>
|
||||||
|
<ToastClose />
|
||||||
|
</div>
|
||||||
|
<component :is="toast.action" />
|
||||||
|
</Toast>
|
||||||
|
<ToastViewport />
|
||||||
|
</ToastProvider>
|
||||||
|
</template>
|
||||||
27
apps/www/src/lib/registry/default/ui/toast/index.ts
Normal file
27
apps/www/src/lib/registry/default/ui/toast/index.ts
Normal file
|
|
@ -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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
165
apps/www/src/lib/registry/default/ui/toast/use-toast.ts
Normal file
165
apps/www/src/lib/registry/default/ui/toast/use-toast.ts
Normal file
|
|
@ -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<ToasterToast>
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: ActionType['DISMISS_TOAST']
|
||||||
|
toastId?: ToasterToast['id']
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: ActionType['REMOVE_TOAST']
|
||||||
|
toastId?: ToasterToast['id']
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
toasts: ToasterToast[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
|
||||||
|
|
||||||
|
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<State>({
|
||||||
|
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<ToasterToast, 'id'>
|
||||||
|
|
||||||
|
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 }
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { h } from 'vue'
|
||||||
import { useForm } from 'vee-validate'
|
import { useForm } from 'vee-validate'
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
import * as z from 'zod'
|
import * as z from 'zod'
|
||||||
|
|
@ -13,6 +14,7 @@ import {
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from '@/lib/registry/new-york/ui/form'
|
} from '@/lib/registry/new-york/ui/form'
|
||||||
import { Checkbox } from '@/lib/registry/new-york/ui/checkbox'
|
import { Checkbox } from '@/lib/registry/new-york/ui/checkbox'
|
||||||
|
import { toast } from '@/lib/registry/new-york/ui/toast'
|
||||||
|
|
||||||
const items = [
|
const items = [
|
||||||
{
|
{
|
||||||
|
|
@ -55,7 +57,10 @@ const { handleSubmit } = useForm({
|
||||||
})
|
})
|
||||||
|
|
||||||
const onSubmit = handleSubmit((values) => {
|
const onSubmit = handleSubmit((values) => {
|
||||||
console.log('Form submitted!', values)
|
toast({
|
||||||
|
title: 'You submitted the following values:',
|
||||||
|
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { h } from 'vue'
|
||||||
import { useForm } from 'vee-validate'
|
import { useForm } from 'vee-validate'
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
import * as z from 'zod'
|
import * as z from 'zod'
|
||||||
|
|
@ -13,6 +14,7 @@ import {
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from '@/lib/registry/new-york/ui/form'
|
} from '@/lib/registry/new-york/ui/form'
|
||||||
import { Checkbox } from '@/lib/registry/new-york/ui/checkbox'
|
import { Checkbox } from '@/lib/registry/new-york/ui/checkbox'
|
||||||
|
import { toast } from '@/lib/registry/new-york/ui/toast'
|
||||||
|
|
||||||
const formSchema = toTypedSchema(z.object({
|
const formSchema = toTypedSchema(z.object({
|
||||||
mobile: z.boolean().default(false).optional(),
|
mobile: z.boolean().default(false).optional(),
|
||||||
|
|
@ -26,7 +28,10 @@ const { handleSubmit } = useForm({
|
||||||
})
|
})
|
||||||
|
|
||||||
const onSubmit = handleSubmit((values) => {
|
const onSubmit = handleSubmit((values) => {
|
||||||
console.log('Form submitted!', values)
|
toast({
|
||||||
|
title: 'You submitted the following values:',
|
||||||
|
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { h } from 'vue'
|
||||||
import { useForm } from 'vee-validate'
|
import { useForm } from 'vee-validate'
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
import * as z from 'zod'
|
import * as z from 'zod'
|
||||||
|
|
@ -26,6 +27,7 @@ import {
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from '@/lib/registry/new-york/ui/popover'
|
} from '@/lib/registry/new-york/ui/popover'
|
||||||
|
import { toast } from '@/lib/registry/new-york/ui/toast'
|
||||||
|
|
||||||
const languages = [
|
const languages = [
|
||||||
{ label: 'English', value: 'en' },
|
{ label: 'English', value: 'en' },
|
||||||
|
|
@ -53,7 +55,10 @@ const { handleSubmit, setValues, values } = useForm({
|
||||||
})
|
})
|
||||||
|
|
||||||
const onSubmit = handleSubmit((values) => {
|
const onSubmit = handleSubmit((values) => {
|
||||||
console.log('Form submitted!', values)
|
toast({
|
||||||
|
title: 'You submitted the following values:',
|
||||||
|
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { h } from 'vue'
|
||||||
import { format } from 'date-fns'
|
import { format } from 'date-fns'
|
||||||
import { CalendarIcon } from '@radix-icons/vue'
|
import { CalendarIcon } from '@radix-icons/vue'
|
||||||
import { useForm } from 'vee-validate'
|
import { useForm } from 'vee-validate'
|
||||||
|
|
@ -22,6 +23,7 @@ import {
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from '@/lib/registry/new-york/ui/popover'
|
} from '@/lib/registry/new-york/ui/popover'
|
||||||
|
import { toast } from '@/lib/registry/new-york/ui/toast'
|
||||||
|
|
||||||
const formSchema = toTypedSchema(z.object({
|
const formSchema = toTypedSchema(z.object({
|
||||||
dob: z.date({
|
dob: z.date({
|
||||||
|
|
@ -34,7 +36,10 @@ const { handleSubmit } = useForm({
|
||||||
})
|
})
|
||||||
|
|
||||||
const onSubmit = handleSubmit((values) => {
|
const onSubmit = handleSubmit((values) => {
|
||||||
console.log('Form submitted!', values)
|
toast({
|
||||||
|
title: 'You submitted the following values:',
|
||||||
|
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { h } from 'vue'
|
||||||
import { useForm } from 'vee-validate'
|
import { useForm } from 'vee-validate'
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
import * as z from 'zod'
|
import * as z from 'zod'
|
||||||
|
|
@ -13,6 +14,7 @@ import {
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from '@/lib/registry/new-york/ui/form'
|
} from '@/lib/registry/new-york/ui/form'
|
||||||
import { Input } from '@/lib/registry/new-york/ui/input'
|
import { Input } from '@/lib/registry/new-york/ui/input'
|
||||||
|
import { toast } from '@/lib/registry/new-york/ui/toast/use-toast'
|
||||||
|
|
||||||
const formSchema = toTypedSchema(z.object({
|
const formSchema = toTypedSchema(z.object({
|
||||||
username: z.string().min(2).max(50),
|
username: z.string().min(2).max(50),
|
||||||
|
|
@ -23,7 +25,10 @@ const { isFieldDirty, handleSubmit } = useForm({
|
||||||
})
|
})
|
||||||
|
|
||||||
const onSubmit = handleSubmit((values) => {
|
const onSubmit = handleSubmit((values) => {
|
||||||
console.log('Form submitted!', values)
|
toast({
|
||||||
|
title: 'You submitted the following values:',
|
||||||
|
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { h } from 'vue'
|
||||||
import { useForm } from 'vee-validate'
|
import { useForm } from 'vee-validate'
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
import * as z from 'zod'
|
import * as z from 'zod'
|
||||||
|
|
@ -14,6 +15,7 @@ import {
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from '@/lib/registry/new-york/ui/form'
|
} from '@/lib/registry/new-york/ui/form'
|
||||||
import { Input } from '@/lib/registry/new-york/ui/input'
|
import { Input } from '@/lib/registry/new-york/ui/input'
|
||||||
|
import { toast } from '@/lib/registry/new-york/ui/toast'
|
||||||
|
|
||||||
const formSchema = toTypedSchema(z.object({
|
const formSchema = toTypedSchema(z.object({
|
||||||
username: z.string().min(2).max(50),
|
username: z.string().min(2).max(50),
|
||||||
|
|
@ -24,7 +26,10 @@ const { isFieldDirty, handleSubmit } = useForm({
|
||||||
})
|
})
|
||||||
|
|
||||||
const onSubmit = handleSubmit((values) => {
|
const onSubmit = handleSubmit((values) => {
|
||||||
console.log('Form submitted!', values)
|
toast({
|
||||||
|
title: 'You submitted the following values:',
|
||||||
|
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { h } from 'vue'
|
||||||
import { useForm } from 'vee-validate'
|
import { useForm } from 'vee-validate'
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
import * as z from 'zod'
|
import * as z from 'zod'
|
||||||
|
|
@ -12,6 +13,7 @@ import {
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from '@/lib/registry/new-york/ui/form'
|
} from '@/lib/registry/new-york/ui/form'
|
||||||
import { RadioGroup, RadioGroupItem } from '@/lib/registry/new-york/ui/radio-group'
|
import { RadioGroup, RadioGroupItem } from '@/lib/registry/new-york/ui/radio-group'
|
||||||
|
import { toast } from '@/lib/registry/new-york/ui/toast'
|
||||||
|
|
||||||
const formSchema = toTypedSchema(z.object({
|
const formSchema = toTypedSchema(z.object({
|
||||||
type: z.enum(['all', 'mentions', 'none'], {
|
type: z.enum(['all', 'mentions', 'none'], {
|
||||||
|
|
@ -24,7 +26,10 @@ const { handleSubmit } = useForm({
|
||||||
})
|
})
|
||||||
|
|
||||||
const onSubmit = handleSubmit((values) => {
|
const onSubmit = handleSubmit((values) => {
|
||||||
console.log('Form submitted!', values)
|
toast({
|
||||||
|
title: 'You submitted the following values:',
|
||||||
|
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { h } from 'vue'
|
||||||
import { useForm } from 'vee-validate'
|
import { useForm } from 'vee-validate'
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
import * as z from 'zod'
|
import * as z from 'zod'
|
||||||
|
|
@ -20,6 +21,7 @@ import {
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@/lib/registry/new-york/ui/select'
|
} from '@/lib/registry/new-york/ui/select'
|
||||||
|
import { toast } from '@/lib/registry/new-york/ui/toast'
|
||||||
|
|
||||||
const formSchema = toTypedSchema(z.object({
|
const formSchema = toTypedSchema(z.object({
|
||||||
email: z
|
email: z
|
||||||
|
|
@ -34,7 +36,10 @@ const { handleSubmit } = useForm({
|
||||||
})
|
})
|
||||||
|
|
||||||
const onSubmit = handleSubmit((values) => {
|
const onSubmit = handleSubmit((values) => {
|
||||||
console.log('Form submitted!', values)
|
toast({
|
||||||
|
title: 'You submitted the following values:',
|
||||||
|
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { h } from 'vue'
|
||||||
import { useForm } from 'vee-validate'
|
import { useForm } from 'vee-validate'
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
import * as z from 'zod'
|
import * as z from 'zod'
|
||||||
|
|
@ -12,6 +13,7 @@ import {
|
||||||
FormLabel,
|
FormLabel,
|
||||||
} from '@/lib/registry/new-york/ui/form'
|
} from '@/lib/registry/new-york/ui/form'
|
||||||
import { Switch } from '@/lib/registry/new-york/ui/switch'
|
import { Switch } from '@/lib/registry/new-york/ui/switch'
|
||||||
|
import { toast } from '@/lib/registry/new-york/ui/toast'
|
||||||
|
|
||||||
const formSchema = toTypedSchema(z.object({
|
const formSchema = toTypedSchema(z.object({
|
||||||
marketing_emails: z.boolean().default(false).optional(),
|
marketing_emails: z.boolean().default(false).optional(),
|
||||||
|
|
@ -26,7 +28,10 @@ const { handleSubmit } = useForm({
|
||||||
})
|
})
|
||||||
|
|
||||||
const onSubmit = handleSubmit((values) => {
|
const onSubmit = handleSubmit((values) => {
|
||||||
console.log('Form submitted!', values)
|
toast({
|
||||||
|
title: 'You submitted the following values:',
|
||||||
|
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { h } from 'vue'
|
||||||
import { useForm } from 'vee-validate'
|
import { useForm } from 'vee-validate'
|
||||||
import { toTypedSchema } from '@vee-validate/zod'
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
import * as z from 'zod'
|
import * as z from 'zod'
|
||||||
|
|
@ -13,6 +14,7 @@ import {
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from '@/lib/registry/new-york/ui/form'
|
} from '@/lib/registry/new-york/ui/form'
|
||||||
import { Textarea } from '@/lib/registry/new-york/ui/textarea'
|
import { Textarea } from '@/lib/registry/new-york/ui/textarea'
|
||||||
|
import { toast } from '@/lib/registry/new-york/ui/toast'
|
||||||
|
|
||||||
const formSchema = toTypedSchema(z.object({
|
const formSchema = toTypedSchema(z.object({
|
||||||
bio: z
|
bio: z
|
||||||
|
|
@ -30,7 +32,10 @@ const { handleSubmit } = useForm({
|
||||||
})
|
})
|
||||||
|
|
||||||
const onSubmit = handleSubmit((values) => {
|
const onSubmit = handleSubmit((values) => {
|
||||||
console.log('Form submitted!', values)
|
toast({
|
||||||
|
title: 'You submitted the following values:',
|
||||||
|
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
19
apps/www/src/lib/registry/new-york/example/ToastDemo.vue
Normal file
19
apps/www/src/lib/registry/new-york/example/ToastDemo.vue
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
|
import { useToast } from '@/lib/registry/new-york/ui/toast/use-toast'
|
||||||
|
|
||||||
|
const { toast } = useToast()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Button
|
||||||
|
variant="outline" @click="() => {
|
||||||
|
toast({
|
||||||
|
title: 'Scheduled: Catch up',
|
||||||
|
description: 'Friday, February 10, 2023 at 5:57 PM',
|
||||||
|
});
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
Add to calander
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { h } from 'vue'
|
||||||
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
|
import { useToast } from '@/lib/registry/new-york/ui/toast/use-toast'
|
||||||
|
import { ToastAction } from '@/lib/registry/new-york/ui/toast'
|
||||||
|
|
||||||
|
const { toast } = useToast()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Button
|
||||||
|
variant="outline" @click="() => {
|
||||||
|
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
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
18
apps/www/src/lib/registry/new-york/example/ToastSimple.vue
Normal file
18
apps/www/src/lib/registry/new-york/example/ToastSimple.vue
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
|
import { useToast } from '@/lib/registry/new-york/ui/toast/use-toast'
|
||||||
|
|
||||||
|
const { toast } = useToast()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Button
|
||||||
|
variant="outline" @click="() => {
|
||||||
|
toast({
|
||||||
|
description: 'Your message has been sent.',
|
||||||
|
});
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
Show Toast
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { h } from 'vue'
|
||||||
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
|
import { useToast } from '@/lib/registry/new-york/ui/toast/use-toast'
|
||||||
|
import { ToastAction } from '@/lib/registry/new-york/ui/toast'
|
||||||
|
|
||||||
|
const { toast } = useToast()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Button
|
||||||
|
variant="outline" @click="() => {
|
||||||
|
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
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
|
import { useToast } from '@/lib/registry/new-york/ui/toast/use-toast'
|
||||||
|
|
||||||
|
const { toast } = useToast()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Button
|
||||||
|
variant="outline" @click="() => {
|
||||||
|
toast({
|
||||||
|
title: 'Uh oh! Something went wrong.',
|
||||||
|
description: 'There was a problem with your request.',
|
||||||
|
});
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
Show Toast
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
32
apps/www/src/lib/registry/new-york/ui/toast/Toast.vue
Normal file
32
apps/www/src/lib/registry/new-york/ui/toast/Toast.vue
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { ToastRootEmits, ToastRootProps } from 'radix-vue'
|
||||||
|
import type { VariantProps } from 'class-variance-authority'
|
||||||
|
|
||||||
|
interface ToastVariantProps extends VariantProps<typeof toastVariants> {}
|
||||||
|
|
||||||
|
export interface ToastProps extends ToastRootProps {
|
||||||
|
class?: string
|
||||||
|
variant?: ToastVariantProps['variant']
|
||||||
|
'onOpenChange'?: ((value: boolean) => void) | undefined
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ToastRoot, useEmitAsProps } from 'radix-vue'
|
||||||
|
|
||||||
|
import { toastVariants } from '.'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<ToastProps>()
|
||||||
|
const emits = defineEmits<ToastRootEmits>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ToastRoot
|
||||||
|
v-bind="{ ...props, ...useEmitAsProps(emits) }"
|
||||||
|
:class="cn(toastVariants({ variant: props.variant }), props.class)"
|
||||||
|
@update:open="onOpenChange"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</ToastRoot>
|
||||||
|
</template>
|
||||||
12
apps/www/src/lib/registry/new-york/ui/toast/ToastAction.vue
Normal file
12
apps/www/src/lib/registry/new-york/ui/toast/ToastAction.vue
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ToastAction, type ToastActionProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<ToastActionProps & { class?: string }>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ToastAction v-bind="props" :class="cn('inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors hover:bg-secondary focus:outline-none focus:ring-1 focus:ring-ring disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive', props.class)">
|
||||||
|
<slot />
|
||||||
|
</ToastAction>
|
||||||
|
</template>
|
||||||
15
apps/www/src/lib/registry/new-york/ui/toast/ToastClose.vue
Normal file
15
apps/www/src/lib/registry/new-york/ui/toast/ToastClose.vue
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ToastClose } from 'radix-vue'
|
||||||
|
import { XIcon } from 'lucide-vue-next'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: string
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ToastClose v-bind="props" :class="cn('absolute right-1 top-1 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-1 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600', props.class)">
|
||||||
|
<XIcon class="h-4 w-4" />
|
||||||
|
</ToastClose>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ToastDescription, type ToastDescriptionProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<ToastDescriptionProps & { class?: string }>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ToastDescription :class="cn('text-sm opacity-90', props.class)" v-bind="props">
|
||||||
|
<slot />
|
||||||
|
</ToastDescription>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ToastProvider, type ToastProviderProps } from 'radix-vue'
|
||||||
|
|
||||||
|
const props = defineProps<ToastProviderProps>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ToastProvider v-bind="props">
|
||||||
|
<slot />
|
||||||
|
</ToastProvider>
|
||||||
|
</template>
|
||||||
12
apps/www/src/lib/registry/new-york/ui/toast/ToastTitle.vue
Normal file
12
apps/www/src/lib/registry/new-york/ui/toast/ToastTitle.vue
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ToastTitle, type ToastTitleProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<ToastTitleProps & { class?: string }>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ToastTitle v-bind="props" :class="cn('text-sm font-semibold [&+div]:text-xs', props.class)">
|
||||||
|
<slot />
|
||||||
|
</ToastTitle>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ToastViewport, type ToastViewportProps } from 'radix-vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
const props = defineProps<ToastViewportProps & { class?: string }>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ToastViewport v-bind="props" :class="cn('fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]', props.class)" />
|
||||||
|
</template>
|
||||||
30
apps/www/src/lib/registry/new-york/ui/toast/Toaster.vue
Normal file
30
apps/www/src/lib/registry/new-york/ui/toast/Toaster.vue
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { isVNode } from 'vue'
|
||||||
|
import { useToast } from './use-toast'
|
||||||
|
import { Toast, ToastClose, ToastDescription, ToastProvider, ToastTitle, ToastViewport } from '.'
|
||||||
|
|
||||||
|
const { toasts } = useToast()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ToastProvider>
|
||||||
|
<Toast v-for="toast in toasts" :key="toast.id" v-bind="toast">
|
||||||
|
<div class="grid gap-1">
|
||||||
|
<ToastTitle v-if="toast.title">
|
||||||
|
{{ toast.title }}
|
||||||
|
</ToastTitle>
|
||||||
|
<template v-if="toast.description">
|
||||||
|
<ToastDescription v-if="isVNode(toast.description)">
|
||||||
|
<component :is="toast.description" />
|
||||||
|
</ToastDescription>
|
||||||
|
<ToastDescription v-else>
|
||||||
|
{{ toast.description }}
|
||||||
|
</ToastDescription>
|
||||||
|
</template>
|
||||||
|
<ToastClose />
|
||||||
|
</div>
|
||||||
|
<component :is="toast.action" />
|
||||||
|
</Toast>
|
||||||
|
<ToastViewport />
|
||||||
|
</ToastProvider>
|
||||||
|
</template>
|
||||||
27
apps/www/src/lib/registry/new-york/ui/toast/index.ts
Normal file
27
apps/www/src/lib/registry/new-york/ui/toast/index.ts
Normal file
|
|
@ -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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
165
apps/www/src/lib/registry/new-york/ui/toast/use-toast.ts
Normal file
165
apps/www/src/lib/registry/new-york/ui/toast/use-toast.ts
Normal file
|
|
@ -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<ToasterToast>
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: ActionType['DISMISS_TOAST']
|
||||||
|
toastId?: ToasterToast['id']
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: ActionType['REMOVE_TOAST']
|
||||||
|
toastId?: ToasterToast['id']
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
toasts: ToasterToast[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
|
||||||
|
|
||||||
|
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<State>({
|
||||||
|
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<ToasterToast, 'id'>
|
||||||
|
|
||||||
|
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 }
|
||||||
|
|
@ -600,6 +600,28 @@
|
||||||
],
|
],
|
||||||
"type": "components:ui"
|
"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",
|
"name": "toggle",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
"files": [
|
"files": [
|
||||||
{
|
{
|
||||||
"name": "Switch.vue",
|
"name": "Switch.vue",
|
||||||
"content": "<script setup lang=\"ts\">\nimport {\n SwitchRoot,\n type SwitchRootEmits,\n type SwitchRootProps,\n SwitchThumb,\n useForwardPropsEmits,\n} from 'radix-vue'\nimport { cn } from '@/lib/utils'\n\nconst props = defineProps<SwitchRootProps & { class?: string }>()\nconst emits = defineEmits<SwitchRootEmits>()\n\nconst forwarded = useForwardPropsEmits(props, emits)\nconsole.log(props, forwarded)\n</script>\n\n<template>\n <SwitchRoot\n v-bind=\"forwarded\"\n :class=\"\n cn(\n 'peer inline-flex h-[24px] w-[44px] shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input',\n props.class,\n )\n \"\n >\n <SwitchThumb\n :class=\"\n cn(\n 'pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0',\n )\n \"\n />\n </SwitchRoot>\n</template>\n"
|
"content": "<script setup lang=\"ts\">\nimport {\n SwitchRoot,\n type SwitchRootEmits,\n type SwitchRootProps,\n SwitchThumb,\n useForwardPropsEmits,\n} from 'radix-vue'\nimport { cn } from '@/lib/utils'\n\nconst props = defineProps<SwitchRootProps & { class?: string }>()\nconst emits = defineEmits<SwitchRootEmits>()\n\nconst forwarded = useForwardPropsEmits(props, emits)\n</script>\n\n<template>\n <SwitchRoot\n v-bind=\"forwarded\"\n :class=\"\n cn(\n 'peer inline-flex h-[24px] w-[44px] shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input',\n props.class,\n )\n \"\n >\n <SwitchThumb\n :class=\"\n cn(\n 'pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0',\n )\n \"\n />\n </SwitchRoot>\n</template>\n"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "index.ts",
|
"name": "index.ts",
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,51 @@
|
||||||
{
|
{
|
||||||
"name": "toast",
|
"name": "toast",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"@radix-ui/react-toast"
|
"radix-vue"
|
||||||
|
],
|
||||||
|
"registryDependencies": [
|
||||||
|
"utils"
|
||||||
],
|
],
|
||||||
"files": [
|
"files": [
|
||||||
{
|
{
|
||||||
"name": "toast.tsx",
|
"name": "Toast.vue",
|
||||||
"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<typeof ToastPrimitives.Viewport>,\n React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>\n>(({ class, ...props }, ref) => (\n <ToastPrimitives.Viewport\n ref={ref}\n class={cn(\n \"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]\",\n class\n )}\n {...props}\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<typeof ToastPrimitives.Root>,\n React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &\n VariantProps<typeof toastVariants>\n>(({ class, variant, ...props }, ref) => {\n return (\n <ToastPrimitives.Root\n ref={ref}\n class={cn(toastVariants({ variant }), class)}\n {...props}\n />\n )\n})\nToast.displayName = ToastPrimitives.Root.displayName\n\nconst ToastAction = React.forwardRef<\n React.ElementRef<typeof ToastPrimitives.Action>,\n React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>\n>(({ class, ...props }, ref) => (\n <ToastPrimitives.Action\n ref={ref}\n class={cn(\n \"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive\",\n class\n )}\n {...props}\n />\n))\nToastAction.displayName = ToastPrimitives.Action.displayName\n\nconst ToastClose = React.forwardRef<\n React.ElementRef<typeof ToastPrimitives.Close>,\n React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>\n>(({ class, ...props }, ref) => (\n <ToastPrimitives.Close\n ref={ref}\n class={cn(\n \"absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600\",\n class\n )}\n toast-close=\"\"\n {...props}\n >\n <X class=\"h-4 w-4\" />\n </ToastPrimitives.Close>\n))\nToastClose.displayName = ToastPrimitives.Close.displayName\n\nconst ToastTitle = React.forwardRef<\n React.ElementRef<typeof ToastPrimitives.Title>,\n React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>\n>(({ class, ...props }, ref) => (\n <ToastPrimitives.Title\n ref={ref}\n class={cn(\"text-sm font-semibold\", class)}\n {...props}\n />\n))\nToastTitle.displayName = ToastPrimitives.Title.displayName\n\nconst ToastDescription = React.forwardRef<\n React.ElementRef<typeof ToastPrimitives.Description>,\n React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>\n>(({ class, ...props }, ref) => (\n <ToastPrimitives.Description\n ref={ref}\n class={cn(\"text-sm opacity-90\", class)}\n {...props}\n />\n))\nToastDescription.displayName = ToastPrimitives.Description.displayName\n\ntype ToastProps = React.ComponentPropsWithoutRef<typeof Toast>\n\ntype ToastActionElement = React.ReactElement<typeof ToastAction>\n\nexport {\n type ToastProps,\n type ToastActionElement,\n ToastProvider,\n ToastViewport,\n Toast,\n ToastTitle,\n ToastDescription,\n ToastClose,\n ToastAction,\n}\n"
|
"content": "<script lang=\"ts\">\nimport type { ToastRootEmits, ToastRootProps } from 'radix-vue'\nimport type { VariantProps } from 'class-variance-authority'\n\ninterface ToastVariantProps extends VariantProps<typeof toastVariants> {}\n\nexport interface ToastProps extends ToastRootProps {\n class?: string\n variant?: ToastVariantProps['variant']\n 'onOpenChange'?: ((value: boolean) => void) | undefined\n};\n</script>\n\n<script setup lang=\"ts\">\nimport { ToastRoot, useEmitAsProps } from 'radix-vue'\n\nimport { toastVariants } from '.'\nimport { cn } from '@/lib/utils'\n\nconst props = defineProps<ToastProps>()\nconst emits = defineEmits<ToastRootEmits>()\n</script>\n\n<template>\n <ToastRoot\n v-bind=\"{ ...props, ...useEmitAsProps(emits) }\"\n :class=\"cn(toastVariants({ variant: props.variant }), props.class)\"\n @update:open=\"onOpenChange\"\n >\n <slot />\n </ToastRoot>\n</template>\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ToastAction.vue",
|
||||||
|
"content": "<script setup lang=\"ts\">\nimport { ToastAction, type ToastActionProps } from 'radix-vue'\nimport { cn } from '@/lib/utils'\n\nconst props = defineProps<ToastActionProps & { class?: string }>()\n</script>\n\n<template>\n <ToastAction v-bind=\"props\" :class=\"cn('inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive', props.class)\">\n <slot />\n </ToastAction>\n</template>\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ToastClose.vue",
|
||||||
|
"content": "<script setup lang=\"ts\">\nimport { ToastClose } from 'radix-vue'\nimport { XIcon } from 'lucide-vue-next'\nimport { cn } from '@/lib/utils'\n\nconst props = defineProps<{\n class?: string\n}>()\n</script>\n\n<template>\n <ToastClose v-bind=\"props\" :class=\"cn('absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600', props.class)\">\n <XIcon class=\"h-4 w-4\" />\n </ToastClose>\n</template>\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ToastDescription.vue",
|
||||||
|
"content": "<script setup lang=\"ts\">\nimport { ToastDescription, type ToastDescriptionProps } from 'radix-vue'\nimport { cn } from '@/lib/utils'\n\nconst props = defineProps<ToastDescriptionProps & { class?: string }>()\n</script>\n\n<template>\n <ToastDescription :class=\"cn('text-sm opacity-90', props.class)\" v-bind=\"props\">\n <slot />\n </ToastDescription>\n</template>\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ToastProvider.vue",
|
||||||
|
"content": "<script setup lang=\"ts\">\nimport { ToastProvider, type ToastProviderProps } from 'radix-vue'\n\nconst props = defineProps<ToastProviderProps>()\n</script>\n\n<template>\n <ToastProvider v-bind=\"props\">\n <slot />\n </ToastProvider>\n</template>\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ToastTitle.vue",
|
||||||
|
"content": "<script setup lang=\"ts\">\nimport { ToastTitle, type ToastTitleProps } from 'radix-vue'\nimport { cn } from '@/lib/utils'\n\nconst props = defineProps<ToastTitleProps & { class?: string }>()\n</script>\n\n<template>\n <ToastTitle v-bind=\"props\" :class=\"cn('text-sm font-semibold', props.class)\">\n <slot />\n </ToastTitle>\n</template>\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ToastViewport.vue",
|
||||||
|
"content": "<script setup lang=\"ts\">\nimport { ToastViewport, type ToastViewportProps } from 'radix-vue'\nimport { cn } from '@/lib/utils'\n\nconst props = defineProps<ToastViewportProps & { class?: string }>()\n</script>\n\n<template>\n <ToastViewport v-bind=\"props\" :class=\"cn('fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]', props.class)\" />\n</template>\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Toaster.vue",
|
||||||
|
"content": "<script setup lang=\"ts\">\nimport { isVNode } from 'vue'\nimport { useToast } from './use-toast'\nimport { Toast, ToastClose, ToastDescription, ToastProvider, ToastTitle, ToastViewport } from '.'\n\nconst { toasts } = useToast()\n</script>\n\n<template>\n <ToastProvider>\n <Toast v-for=\"toast in toasts\" :key=\"toast.id\" v-bind=\"toast\">\n <div class=\"grid gap-1\">\n <ToastTitle v-if=\"toast.title\">\n {{ toast.title }}\n </ToastTitle>\n <template v-if=\"toast.description\">\n <ToastDescription v-if=\"isVNode(toast.description)\">\n <component :is=\"toast.description\" />\n </ToastDescription>\n <ToastDescription v-else>\n {{ toast.description }}\n </ToastDescription>\n </template>\n <ToastClose />\n </div>\n <component :is=\"toast.action\" />\n </Toast>\n <ToastViewport />\n </ToastProvider>\n</template>\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",
|
"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<ToasterToast>\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<string, ReturnType<typeof setTimeout>>()\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<ToasterToast, \"id\">\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<State>(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"
|
"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<ToasterToast>\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<string, ReturnType<typeof setTimeout>>()\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<State>({\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<ToasterToast, 'id'>\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"
|
||||||
},
|
|
||||||
{
|
|
||||||
"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 <ToastProvider>\n {toasts.map(function ({ id, title, description, action, ...props }) {\n return (\n <Toast key={id} {...props}>\n <div class=\"grid gap-1\">\n {title && <ToastTitle>{title}</ToastTitle>}\n {description && (\n <ToastDescription>{description}</ToastDescription>\n )}\n </div>\n {action}\n <ToastClose />\n </Toast>\n )\n })}\n <ToastViewport />\n </ToastProvider>\n )\n}\n"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"type": "components:ui"
|
"type": "components:ui"
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,51 @@
|
||||||
{
|
{
|
||||||
"name": "toast",
|
"name": "toast",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"@radix-ui/react-toast"
|
"radix-vue"
|
||||||
|
],
|
||||||
|
"registryDependencies": [
|
||||||
|
"utils"
|
||||||
],
|
],
|
||||||
"files": [
|
"files": [
|
||||||
{
|
{
|
||||||
"name": "toast.tsx",
|
"name": "Toast.vue",
|
||||||
"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<typeof ToastPrimitives.Viewport>,\n React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>\n>(({ class, ...props }, ref) => (\n <ToastPrimitives.Viewport\n ref={ref}\n class={cn(\n \"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]\",\n class\n )}\n {...props}\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<typeof ToastPrimitives.Root>,\n React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &\n VariantProps<typeof toastVariants>\n>(({ class, variant, ...props }, ref) => {\n return (\n <ToastPrimitives.Root\n ref={ref}\n class={cn(toastVariants({ variant }), class)}\n {...props}\n />\n )\n})\nToast.displayName = ToastPrimitives.Root.displayName\n\nconst ToastAction = React.forwardRef<\n React.ElementRef<typeof ToastPrimitives.Action>,\n React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>\n>(({ class, ...props }, ref) => (\n <ToastPrimitives.Action\n ref={ref}\n class={cn(\n \"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors hover:bg-secondary focus:outline-none focus:ring-1 focus:ring-ring disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive\",\n class\n )}\n {...props}\n />\n))\nToastAction.displayName = ToastPrimitives.Action.displayName\n\nconst ToastClose = React.forwardRef<\n React.ElementRef<typeof ToastPrimitives.Close>,\n React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>\n>(({ class, ...props }, ref) => (\n <ToastPrimitives.Close\n ref={ref}\n class={cn(\n \"absolute right-1 top-1 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-1 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600\",\n class\n )}\n toast-close=\"\"\n {...props}\n >\n <Cross2Icon class=\"h-4 w-4\" />\n </ToastPrimitives.Close>\n))\nToastClose.displayName = ToastPrimitives.Close.displayName\n\nconst ToastTitle = React.forwardRef<\n React.ElementRef<typeof ToastPrimitives.Title>,\n React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>\n>(({ class, ...props }, ref) => (\n <ToastPrimitives.Title\n ref={ref}\n class={cn(\"text-sm font-semibold [&+div]:text-xs\", class)}\n {...props}\n />\n))\nToastTitle.displayName = ToastPrimitives.Title.displayName\n\nconst ToastDescription = React.forwardRef<\n React.ElementRef<typeof ToastPrimitives.Description>,\n React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>\n>(({ class, ...props }, ref) => (\n <ToastPrimitives.Description\n ref={ref}\n class={cn(\"text-sm opacity-90\", class)}\n {...props}\n />\n))\nToastDescription.displayName = ToastPrimitives.Description.displayName\n\ntype ToastProps = React.ComponentPropsWithoutRef<typeof Toast>\n\ntype ToastActionElement = React.ReactElement<typeof ToastAction>\n\nexport {\n type ToastProps,\n type ToastActionElement,\n ToastProvider,\n ToastViewport,\n Toast,\n ToastTitle,\n ToastDescription,\n ToastClose,\n ToastAction,\n}\n"
|
"content": "<script lang=\"ts\">\nimport type { ToastRootEmits, ToastRootProps } from 'radix-vue'\nimport type { VariantProps } from 'class-variance-authority'\n\ninterface ToastVariantProps extends VariantProps<typeof toastVariants> {}\n\nexport interface ToastProps extends ToastRootProps {\n class?: string\n variant?: ToastVariantProps['variant']\n 'onOpenChange'?: ((value: boolean) => void) | undefined\n};\n</script>\n\n<script setup lang=\"ts\">\nimport { ToastRoot, useEmitAsProps } from 'radix-vue'\n\nimport { toastVariants } from '.'\nimport { cn } from '@/lib/utils'\n\nconst props = defineProps<ToastProps>()\nconst emits = defineEmits<ToastRootEmits>()\n</script>\n\n<template>\n <ToastRoot\n v-bind=\"{ ...props, ...useEmitAsProps(emits) }\"\n :class=\"cn(toastVariants({ variant: props.variant }), props.class)\"\n @update:open=\"onOpenChange\"\n >\n <slot />\n </ToastRoot>\n</template>\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ToastAction.vue",
|
||||||
|
"content": "<script setup lang=\"ts\">\nimport { ToastAction, type ToastActionProps } from 'radix-vue'\nimport { cn } from '@/lib/utils'\n\nconst props = defineProps<ToastActionProps & { class?: string }>()\n</script>\n\n<template>\n <ToastAction v-bind=\"props\" :class=\"cn('inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors hover:bg-secondary focus:outline-none focus:ring-1 focus:ring-ring disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive', props.class)\">\n <slot />\n </ToastAction>\n</template>\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ToastClose.vue",
|
||||||
|
"content": "<script setup lang=\"ts\">\nimport { ToastClose } from 'radix-vue'\nimport { XIcon } from 'lucide-vue-next'\nimport { cn } from '@/lib/utils'\n\nconst props = defineProps<{\n class?: string\n}>()\n</script>\n\n<template>\n <ToastClose v-bind=\"props\" :class=\"cn('absolute right-1 top-1 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-1 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600', props.class)\">\n <XIcon class=\"h-4 w-4\" />\n </ToastClose>\n</template>\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ToastDescription.vue",
|
||||||
|
"content": "<script setup lang=\"ts\">\nimport { ToastDescription, type ToastDescriptionProps } from 'radix-vue'\nimport { cn } from '@/lib/utils'\n\nconst props = defineProps<ToastDescriptionProps & { class?: string }>()\n</script>\n\n<template>\n <ToastDescription :class=\"cn('text-sm opacity-90', props.class)\" v-bind=\"props\">\n <slot />\n </ToastDescription>\n</template>\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ToastProvider.vue",
|
||||||
|
"content": "<script setup lang=\"ts\">\nimport { ToastProvider, type ToastProviderProps } from 'radix-vue'\n\nconst props = defineProps<ToastProviderProps>()\n</script>\n\n<template>\n <ToastProvider v-bind=\"props\">\n <slot />\n </ToastProvider>\n</template>\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ToastTitle.vue",
|
||||||
|
"content": "<script setup lang=\"ts\">\nimport { ToastTitle, type ToastTitleProps } from 'radix-vue'\nimport { cn } from '@/lib/utils'\n\nconst props = defineProps<ToastTitleProps & { class?: string }>()\n</script>\n\n<template>\n <ToastTitle v-bind=\"props\" :class=\"cn('text-sm font-semibold [&+div]:text-xs', props.class)\">\n <slot />\n </ToastTitle>\n</template>\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ToastViewport.vue",
|
||||||
|
"content": "<script setup lang=\"ts\">\nimport { ToastViewport, type ToastViewportProps } from 'radix-vue'\nimport { cn } from '@/lib/utils'\n\nconst props = defineProps<ToastViewportProps & { class?: string }>()\n</script>\n\n<template>\n <ToastViewport v-bind=\"props\" :class=\"cn('fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]', props.class)\" />\n</template>\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Toaster.vue",
|
||||||
|
"content": "<script setup lang=\"ts\">\nimport { isVNode } from 'vue'\nimport { useToast } from './use-toast'\nimport { Toast, ToastClose, ToastDescription, ToastProvider, ToastTitle, ToastViewport } from '.'\n\nconst { toasts } = useToast()\n</script>\n\n<template>\n <ToastProvider>\n <Toast v-for=\"toast in toasts\" :key=\"toast.id\" v-bind=\"toast\">\n <div class=\"grid gap-1\">\n <ToastTitle v-if=\"toast.title\">\n {{ toast.title }}\n </ToastTitle>\n <template v-if=\"toast.description\">\n <ToastDescription v-if=\"isVNode(toast.description)\">\n <component :is=\"toast.description\" />\n </ToastDescription>\n <ToastDescription v-else>\n {{ toast.description }}\n </ToastDescription>\n </template>\n <ToastClose />\n </div>\n <component :is=\"toast.action\" />\n </Toast>\n <ToastViewport />\n </ToastProvider>\n</template>\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",
|
"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<ToasterToast>\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<string, ReturnType<typeof setTimeout>>()\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<ToasterToast, \"id\">\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<State>(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"
|
"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<ToasterToast>\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<string, ReturnType<typeof setTimeout>>()\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<State>({\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<ToasterToast, 'id'>\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"
|
||||||
},
|
|
||||||
{
|
|
||||||
"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 <ToastProvider>\n {toasts.map(function ({ id, title, description, action, ...props }) {\n return (\n <Toast key={id} {...props}>\n <div class=\"grid gap-1\">\n {title && <ToastTitle>{title}</ToastTitle>}\n {description && (\n <ToastDescription>{description}</ToastDescription>\n )}\n </div>\n {action}\n <ToastClose />\n </Toast>\n )\n })}\n <ToastViewport />\n </ToastProvider>\n )\n}\n"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"type": "components:ui"
|
"type": "components:ui"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user