feat: migrate Message to shadcn-vue Alert
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
fed19667f7
commit
5d840fcc16
12 changed files with 253 additions and 102 deletions
|
|
@ -10,3 +10,4 @@ export { Checkbox } from './ui/checkbox'
|
||||||
export { CheckboxGroup } from './ui/checkbox-group'
|
export { CheckboxGroup } from './ui/checkbox-group'
|
||||||
export { RadioGroup, RadioGroupItem } from './ui/radio-group'
|
export { RadioGroup, RadioGroupItem } from './ui/radio-group'
|
||||||
export { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter, CardAction } from './ui/card'
|
export { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter, CardAction } from './ui/card'
|
||||||
|
export { Alert, AlertTitle, AlertDescription, AlertAction, alertVariants } from './ui/alert'
|
||||||
|
|
|
||||||
32
packages/library/src/components/ui/alert/Alert.vue
Normal file
32
packages/library/src/components/ui/alert/Alert.vue
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
import { XIcon } from '@lucide/vue'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { alertVariants } from '.'
|
||||||
|
import AlertAction from './AlertAction.vue'
|
||||||
|
import type { AlertVariants } from '.'
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
class?: HTMLAttributes['class']
|
||||||
|
variant?: AlertVariants['variant']
|
||||||
|
closable?: boolean
|
||||||
|
}>(), {
|
||||||
|
variant: 'info',
|
||||||
|
closable: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<{ close: [] }>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div data-slot="alert" role="alert" :class="cn(alertVariants({ variant }), props.class)">
|
||||||
|
<slot />
|
||||||
|
<AlertAction v-if="closable">
|
||||||
|
<Button variant="secondary" size="icon" class="size-6 bg-transparent hover:bg-black/10" @click="emit('close')">
|
||||||
|
<XIcon class="size-3.5" />
|
||||||
|
<span class="sr-only">Dismiss</span>
|
||||||
|
</Button>
|
||||||
|
</AlertAction>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
17
packages/library/src/components/ui/alert/AlertAction.vue
Normal file
17
packages/library/src/components/ui/alert/AlertAction.vue
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
data-slot="alert-action"
|
||||||
|
:class="cn('absolute top-2 right-2', props.class)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
data-slot="alert-description"
|
||||||
|
:class="cn('text-muted-foreground text-sm text-balance md:text-pretty [&_p:not(:last-child)]:mb-4 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground', props.class)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
17
packages/library/src/components/ui/alert/AlertTitle.vue
Normal file
17
packages/library/src/components/ui/alert/AlertTitle.vue
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
data-slot="alert-title"
|
||||||
|
:class="cn('font-medium group-has-[>svg]/alert:col-start-2 cn-font-heading [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground', props.class)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
26
packages/library/src/components/ui/alert/index.ts
Normal file
26
packages/library/src/components/ui/alert/index.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
import type { VariantProps } from 'class-variance-authority'
|
||||||
|
import { cva } from 'class-variance-authority'
|
||||||
|
|
||||||
|
export { default as Alert } from './Alert.vue'
|
||||||
|
export { default as AlertAction } from './AlertAction.vue'
|
||||||
|
export { default as AlertDescription } from './AlertDescription.vue'
|
||||||
|
export { default as AlertTitle } from './AlertTitle.vue'
|
||||||
|
|
||||||
|
export const alertVariants = cva(
|
||||||
|
'relative grid w-full gap-1 rounded-lg border px-3 py-2.5 text-sm has-data-[slot=alert-action]:pr-10',
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
info: 'bg-info/15 border-info/30 text-foreground [&_[data-slot=alert-title]]:text-info',
|
||||||
|
success: 'bg-success/15 border-success/30 text-foreground [&_[data-slot=alert-title]]:text-success',
|
||||||
|
warning: 'bg-warning/15 border-warning/30 text-foreground [&_[data-slot=alert-title]]:text-warning',
|
||||||
|
danger: 'bg-destructive/15 border-destructive/30 text-foreground [&_[data-slot=alert-title]]:text-destructive',
|
||||||
|
secondary: 'bg-secondary/40 border-border text-foreground',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: 'info',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
export type AlertVariants = VariantProps<typeof alertVariants>
|
||||||
|
|
@ -26,6 +26,9 @@ export {
|
||||||
CardFooter,
|
CardFooter,
|
||||||
CardAction,
|
CardAction,
|
||||||
Dialog,
|
Dialog,
|
||||||
Message,
|
Alert,
|
||||||
|
AlertTitle,
|
||||||
|
AlertDescription,
|
||||||
|
AlertAction,
|
||||||
Badge,
|
Badge,
|
||||||
} from './lib'
|
} from './lib'
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import type { App } from 'vue'
|
||||||
import {
|
import {
|
||||||
Select as PrimeSelect,
|
Select as PrimeSelect,
|
||||||
Dialog as PrimeDialog,
|
Dialog as PrimeDialog,
|
||||||
Message as PrimeMessage,
|
|
||||||
} from 'primevue'
|
} from 'primevue'
|
||||||
import { ThemeLibrary, type Themeable, ThemeEngine, type LibComponent, type Key } from '../engine'
|
import { ThemeLibrary, type Themeable, ThemeEngine, type LibComponent, type Key } from '../engine'
|
||||||
import {
|
import {
|
||||||
|
|
@ -25,6 +24,11 @@ import {
|
||||||
CardContent,
|
CardContent,
|
||||||
CardFooter,
|
CardFooter,
|
||||||
CardAction,
|
CardAction,
|
||||||
|
Alert,
|
||||||
|
AlertTitle,
|
||||||
|
AlertDescription,
|
||||||
|
AlertAction,
|
||||||
|
alertVariants,
|
||||||
} from '../components'
|
} from '../components'
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|
@ -46,6 +50,11 @@ export {
|
||||||
CardContent,
|
CardContent,
|
||||||
CardFooter,
|
CardFooter,
|
||||||
CardAction,
|
CardAction,
|
||||||
|
Alert,
|
||||||
|
AlertTitle,
|
||||||
|
AlertDescription,
|
||||||
|
AlertAction,
|
||||||
|
alertVariants,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -110,14 +119,6 @@ const nychthemeron: ThemeLibrary = {
|
||||||
'content',
|
'content',
|
||||||
'footer',
|
'footer',
|
||||||
]),
|
]),
|
||||||
Message: ptThemeable(PrimeMessage, 'message', [
|
|
||||||
'contentWrapper',
|
|
||||||
'content',
|
|
||||||
'icon',
|
|
||||||
'text',
|
|
||||||
'closeButton',
|
|
||||||
'closeIcon',
|
|
||||||
]),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -126,7 +127,6 @@ const _engine = new ThemeEngine(nychthemeron)
|
||||||
export const LoadingIcon = _engine.getComponent<typeof NychLoadingIcon>('LoadingIcon')
|
export const LoadingIcon = _engine.getComponent<typeof NychLoadingIcon>('LoadingIcon')
|
||||||
export const Select: typeof PrimeSelect = _engine.getComponent<typeof PrimeSelect>('Select')
|
export const Select: typeof PrimeSelect = _engine.getComponent<typeof PrimeSelect>('Select')
|
||||||
export const Dialog: typeof PrimeDialog = _engine.getComponent<typeof PrimeDialog>('Dialog')
|
export const Dialog: typeof PrimeDialog = _engine.getComponent<typeof PrimeDialog>('Dialog')
|
||||||
export const Message: typeof PrimeMessage = _engine.getComponent<typeof PrimeMessage>('Message')
|
|
||||||
|
|
||||||
export const createNychthemeron = (): {
|
export const createNychthemeron = (): {
|
||||||
Button: typeof Button
|
Button: typeof Button
|
||||||
|
|
@ -147,7 +147,10 @@ export const createNychthemeron = (): {
|
||||||
CardFooter: typeof CardFooter
|
CardFooter: typeof CardFooter
|
||||||
CardAction: typeof CardAction
|
CardAction: typeof CardAction
|
||||||
Dialog: typeof PrimeDialog
|
Dialog: typeof PrimeDialog
|
||||||
Message: typeof PrimeMessage
|
Alert: typeof Alert
|
||||||
|
AlertTitle: typeof AlertTitle
|
||||||
|
AlertDescription: typeof AlertDescription
|
||||||
|
AlertAction: typeof AlertAction
|
||||||
Badge: typeof Badge
|
Badge: typeof Badge
|
||||||
install: (app: App) => void
|
install: (app: App) => void
|
||||||
} => ({
|
} => ({
|
||||||
|
|
@ -169,7 +172,10 @@ export const createNychthemeron = (): {
|
||||||
CardFooter,
|
CardFooter,
|
||||||
CardAction,
|
CardAction,
|
||||||
Dialog,
|
Dialog,
|
||||||
Message,
|
Alert,
|
||||||
|
AlertTitle,
|
||||||
|
AlertDescription,
|
||||||
|
AlertAction,
|
||||||
Badge,
|
Badge,
|
||||||
install(app: App) {
|
install(app: App) {
|
||||||
app.component('NychButton', Button)
|
app.component('NychButton', Button)
|
||||||
|
|
@ -190,7 +196,10 @@ export const createNychthemeron = (): {
|
||||||
app.component('NychCardFooter', CardFooter)
|
app.component('NychCardFooter', CardFooter)
|
||||||
app.component('NychCardAction', CardAction)
|
app.component('NychCardAction', CardAction)
|
||||||
app.component('NychDialog', Dialog)
|
app.component('NychDialog', Dialog)
|
||||||
app.component('NychMessage', Message)
|
app.component('NychAlert', Alert)
|
||||||
|
app.component('NychAlertTitle', AlertTitle)
|
||||||
|
app.component('NychAlertDescription', AlertDescription)
|
||||||
|
app.component('NychAlertAction', AlertAction)
|
||||||
app.component('NychBadge', Badge)
|
app.component('NychBadge', Badge)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
||||||
40
packages/library/tests/Alert.spec.ts
Normal file
40
packages/library/tests/Alert.spec.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { describe, it, expect } from 'vitest'
|
||||||
|
import { h } from 'vue'
|
||||||
|
import { mount } from '@vue/test-utils'
|
||||||
|
import { Alert, AlertTitle, AlertDescription } from '../src/lib'
|
||||||
|
|
||||||
|
describe('Alert', () => {
|
||||||
|
it('defaults to the info variant', () => {
|
||||||
|
const wrapper = mount(Alert)
|
||||||
|
expect(wrapper.classes().join(' ')).toContain('bg-info/15')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('applies the danger variant class', () => {
|
||||||
|
const wrapper = mount(Alert, { props: { variant: 'danger' } })
|
||||||
|
expect(wrapper.classes().join(' ')).toContain('bg-destructive/15')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders title and description slots', () => {
|
||||||
|
const wrapper = mount(Alert, {
|
||||||
|
slots: {
|
||||||
|
default: () => [
|
||||||
|
h(AlertTitle, undefined, { default: () => 'The Oracle has spoken.' }),
|
||||||
|
h(AlertDescription, undefined, { default: () => 'Heed the omen.' }),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expect(wrapper.text()).toContain('The Oracle has spoken.')
|
||||||
|
expect(wrapper.text()).toContain('Heed the omen.')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not render a close button unless closable is set', () => {
|
||||||
|
const wrapper = mount(Alert)
|
||||||
|
expect(wrapper.find('button').exists()).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('emits close when the close button is clicked', async () => {
|
||||||
|
const wrapper = mount(Alert, { props: { closable: true } })
|
||||||
|
await wrapper.find('button').trigger('click')
|
||||||
|
expect(wrapper.emitted('close')).toHaveLength(1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -5,7 +5,6 @@ import {
|
||||||
LoadingIcon,
|
LoadingIcon,
|
||||||
Select,
|
Select,
|
||||||
Dialog,
|
Dialog,
|
||||||
Message,
|
|
||||||
} from '../src/index'
|
} from '../src/index'
|
||||||
|
|
||||||
//eslint-disable-next-line
|
//eslint-disable-next-line
|
||||||
|
|
@ -64,7 +63,6 @@ describe('themed pt components', () => {
|
||||||
const cases = [
|
const cases = [
|
||||||
{ name: 'Select', component: Select, base: 'nych-select' },
|
{ name: 'Select', component: Select, base: 'nych-select' },
|
||||||
{ name: 'Dialog', component: Dialog, base: 'nych-dialog' },
|
{ name: 'Dialog', component: Dialog, base: 'nych-dialog' },
|
||||||
{ name: 'Message', component: Message, base: 'nych-message' },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
it.each(cases)('$name is a valid Vue component', ({ component }) => {
|
it.each(cases)('$name is a valid Vue component', ({ component }) => {
|
||||||
|
|
|
||||||
77
packages/playground/stories/Alert.stories.ts
Normal file
77
packages/playground/stories/Alert.stories.ts
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
||||||
|
|
||||||
|
import { Alert as NychAlert, AlertTitle as NychAlertTitle, AlertDescription as NychAlertDescription } from '@nychthemeron/library'
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Components/Alert',
|
||||||
|
component: NychAlert,
|
||||||
|
parameters: {
|
||||||
|
layout: 'centered',
|
||||||
|
},
|
||||||
|
tags: ['autodocs'],
|
||||||
|
argTypes: {
|
||||||
|
variant: {
|
||||||
|
control: { type: 'select' },
|
||||||
|
options: ['info', 'success', 'warning', 'danger', 'secondary'],
|
||||||
|
},
|
||||||
|
closable: {
|
||||||
|
control: { type: 'boolean' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} satisfies Meta<typeof NychAlert>
|
||||||
|
|
||||||
|
export default meta
|
||||||
|
type Story = StoryObj<typeof meta>
|
||||||
|
|
||||||
|
export const Info: Story = {
|
||||||
|
args: { variant: 'info' },
|
||||||
|
render: (args) => ({
|
||||||
|
components: { NychAlert, NychAlertTitle },
|
||||||
|
setup: () => ({ args }),
|
||||||
|
template: `
|
||||||
|
<NychAlert v-bind="args" style="width: 24rem;">
|
||||||
|
<NychAlertTitle>The Oracle has spoken.</NychAlertTitle>
|
||||||
|
</NychAlert>
|
||||||
|
`,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Closable: Story = {
|
||||||
|
args: { variant: 'info', closable: true },
|
||||||
|
render: (args) => ({
|
||||||
|
components: { NychAlert, NychAlertTitle },
|
||||||
|
setup: () => ({ args }),
|
||||||
|
template: `
|
||||||
|
<NychAlert v-bind="args" style="width: 24rem;">
|
||||||
|
<NychAlertTitle>This omen may be dismissed.</NychAlertTitle>
|
||||||
|
</NychAlert>
|
||||||
|
`,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WithDescription: Story = {
|
||||||
|
render: () => ({
|
||||||
|
components: { NychAlert, NychAlertTitle, NychAlertDescription },
|
||||||
|
template: `
|
||||||
|
<NychAlert variant="warning" style="width: 24rem;">
|
||||||
|
<NychAlertTitle>The ferryman grows impatient.</NychAlertTitle>
|
||||||
|
<NychAlertDescription>One obol is required to cross the Styx.</NychAlertDescription>
|
||||||
|
</NychAlert>
|
||||||
|
`,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AllVariants: Story = {
|
||||||
|
render: () => ({
|
||||||
|
components: { NychAlert, NychAlertTitle },
|
||||||
|
template: `
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 0.75rem; width: 24rem;">
|
||||||
|
<NychAlert variant="info"><NychAlertTitle>The Oracle has spoken.</NychAlertTitle></NychAlert>
|
||||||
|
<NychAlert variant="success"><NychAlertTitle>The offering was accepted.</NychAlertTitle></NychAlert>
|
||||||
|
<NychAlert variant="warning"><NychAlertTitle>The ferryman grows impatient.</NychAlertTitle></NychAlert>
|
||||||
|
<NychAlert variant="danger"><NychAlertTitle>The gods have refused your plea.</NychAlertTitle></NychAlert>
|
||||||
|
<NychAlert variant="secondary"><NychAlertTitle>A neutral notice from the keeper.</NychAlertTitle></NychAlert>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
@ -1,86 +0,0 @@
|
||||||
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
|
||||||
|
|
||||||
import { Message as NychMessage } from '@nychthemeron/library'
|
|
||||||
|
|
||||||
const meta = {
|
|
||||||
title: 'Components/Message',
|
|
||||||
component: NychMessage,
|
|
||||||
parameters: {
|
|
||||||
layout: 'centered',
|
|
||||||
},
|
|
||||||
tags: ['autodocs'],
|
|
||||||
argTypes: {
|
|
||||||
severity: {
|
|
||||||
control: { type: 'select' },
|
|
||||||
options: ['info', 'success', 'warn', 'error', 'secondary'],
|
|
||||||
description: 'Message severity',
|
|
||||||
},
|
|
||||||
closable: {
|
|
||||||
control: { type: 'boolean' },
|
|
||||||
description: 'Show a close button',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} satisfies Meta<typeof NychMessage>
|
|
||||||
|
|
||||||
export default meta
|
|
||||||
type Story = StoryObj<typeof meta>
|
|
||||||
|
|
||||||
export const Info: Story = {
|
|
||||||
args: { severity: 'info' },
|
|
||||||
render: (args) => ({
|
|
||||||
components: { NychMessage },
|
|
||||||
setup: () => ({ args }),
|
|
||||||
template: `<NychMessage v-bind="args" style="width: 24rem;">The Oracle has spoken.</NychMessage>`,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Success: Story = {
|
|
||||||
args: { severity: 'success' },
|
|
||||||
render: (args) => ({
|
|
||||||
components: { NychMessage },
|
|
||||||
setup: () => ({ args }),
|
|
||||||
template: `<NychMessage v-bind="args" style="width: 24rem;">The offering was accepted.</NychMessage>`,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Warn: Story = {
|
|
||||||
args: { severity: 'warn' },
|
|
||||||
render: (args) => ({
|
|
||||||
components: { NychMessage },
|
|
||||||
setup: () => ({ args }),
|
|
||||||
template: `<NychMessage v-bind="args" style="width: 24rem;">The ferryman grows impatient.</NychMessage>`,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Error: Story = {
|
|
||||||
args: { severity: 'error' },
|
|
||||||
render: (args) => ({
|
|
||||||
components: { NychMessage },
|
|
||||||
setup: () => ({ args }),
|
|
||||||
template: `<NychMessage v-bind="args" style="width: 24rem;">The gods have refused your plea.</NychMessage>`,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Closable: Story = {
|
|
||||||
args: { severity: 'info', closable: true },
|
|
||||||
render: (args) => ({
|
|
||||||
components: { NychMessage },
|
|
||||||
setup: () => ({ args }),
|
|
||||||
template: `<NychMessage v-bind="args" style="width: 24rem;">This omen may be dismissed.</NychMessage>`,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
|
|
||||||
export const AllSeverities: Story = {
|
|
||||||
render: () => ({
|
|
||||||
components: { NychMessage },
|
|
||||||
template: `
|
|
||||||
<div style="display: flex; flex-direction: column; gap: 0.75rem; width: 24rem;">
|
|
||||||
<NychMessage severity="info">The Oracle has spoken.</NychMessage>
|
|
||||||
<NychMessage severity="success">The offering was accepted.</NychMessage>
|
|
||||||
<NychMessage severity="warn">The ferryman grows impatient.</NychMessage>
|
|
||||||
<NychMessage severity="error">The gods have refused your plea.</NychMessage>
|
|
||||||
<NychMessage severity="secondary">A neutral notice from the keeper.</NychMessage>
|
|
||||||
</div>
|
|
||||||
`,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
Loading…
Reference in a new issue