add FormattedMessage component

This commit is contained in:
Gauthier Roebroeck 2025-07-22 17:22:17 +08:00
parent bab8e88843
commit 565b59c4d0
3 changed files with 110 additions and 0 deletions

View file

@ -0,0 +1,11 @@
import {Canvas, Meta} from '@storybook/addon-docs/blocks';
import * as Stories from './FormattedMessage.stories';
<Meta of={Stories} />
# FormattedMessage
Component to display formatted `formatjs` messages using dynamic slots.
<Canvas of={Stories.Default} />

View file

@ -0,0 +1,73 @@
import type { Meta, StoryObj } from '@storybook/vue3-vite'
import FormattedMessage from './FormattedMessage.ts'
const meta = {
component: FormattedMessage,
parameters: {
// More on how to position stories at: https://storybook.js.org/docs/configure/story-layout
},
args: {},
} satisfies Meta<typeof Details>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
render: (args: object) => ({
components: { FormattedMessage },
setup() {
return { args }
},
template: `
<FormattedMessage :messageDescriptor="{
defaultMessage: 'Please accept <a>terms <b>and<br></br></b> conditions</a> first',
id: 'test',
}">
<template #a="Content">
<a href="#" target="_blank">
<component :is="Content"/>
</a>
</template>
<template #b="Content">
<strong>
<component :is="Content"/>
</strong>
</template>
<template #br>
<br/>
</template>
</FormattedMessage>
`,
}),
}
export const NoMarkup: Story = {
render: (args: object) => ({
components: { FormattedMessage },
setup() {
return { args }
},
template: `
<FormattedMessage :messageDescriptor="{
defaultMessage: 'Please accept terms and conditions first',
id: 'test',
}"/>
`,
}),
}
export const NoMarkupTag: Story = {
render: (args: object) => ({
components: { FormattedMessage },
setup() {
return { args }
},
template: `
<FormattedMessage :messageDescriptor="{
defaultMessage: 'Please accept terms and conditions first',
id: 'test',
}" tag="div"/>
`,
}),
}

View file

@ -0,0 +1,26 @@
import { defineComponent, h } from 'vue'
import { type MessageDescriptor, useIntl } from 'vue-intl'
// from: https://github.com/formatjs/formatjs/discussions/3961
export default defineComponent({
props: {
messageDescriptor: Object as PropType<MessageDescriptor>,
tag: String,
values: Object,
},
setup(props, context) {
const { messageDescriptor, tag, values = {} } = props
const intl = useIntl()
const slotNames = Object.keys(context.slots)
const message = intl.formatMessage(messageDescriptor, {
...values,
...slotNames.reduce((slots, name) => {
slots[name] = (content) => context.slots[name](() => content)
return slots
}, {}),
})
return () => (tag || Array.isArray(message) ? h(tag || 'div', message) : message)
},
})