diff --git a/playground/app.vue b/playground/app.vue index ff53a8e0..705ae024 100644 --- a/playground/app.vue +++ b/playground/app.vue @@ -11,6 +11,7 @@ const appConfig = useAppConfig() const components = [ 'accordion', + 'alert', 'avatar', 'badge', 'button', diff --git a/playground/pages/alert.vue b/playground/pages/alert.vue new file mode 100644 index 00000000..b9e7216b --- /dev/null +++ b/playground/pages/alert.vue @@ -0,0 +1,35 @@ + + + diff --git a/src/runtime/components/Alert.vue b/src/runtime/components/Alert.vue new file mode 100644 index 00000000..4b1ca764 --- /dev/null +++ b/src/runtime/components/Alert.vue @@ -0,0 +1,106 @@ + + + + + diff --git a/src/runtime/types/index.d.ts b/src/runtime/types/index.d.ts index d1aad59e..07c6b65c 100644 --- a/src/runtime/types/index.d.ts +++ b/src/runtime/types/index.d.ts @@ -1,5 +1,6 @@ export * from '../components/App.vue' export * from '../components/Accordion.vue' +export * from '../components/Alert.vue' export * from '../components/Avatar.vue' export * from '../components/Badge.vue' export * from '../components/Button.vue' diff --git a/src/theme/alert.ts b/src/theme/alert.ts new file mode 100644 index 00000000..0ebd7847 --- /dev/null +++ b/src/theme/alert.ts @@ -0,0 +1,83 @@ +export default (config: { colors: string[] }) => ({ + slots: { + root: 'relative overflow-hidden w-full rounded-lg p-4 flex gap-2.5', + wrapper: 'min-w-0 flex-1 flex flex-col gap-1', + title: 'text-sm font-medium', + description: 'text-sm', + icon: 'shrink-0 size-5', + avatar: 'shrink-0', + actions: 'flex gap-1.5 shrink-0', + close: 'p-0' + }, + variants: { + color: { + ...Object.fromEntries(config.colors.map((color: string) => [color, ''])), + white: '', + gray: '', + black: '' + }, + variant: { + solid: '', + outline: '', + soft: '', + subtle: '' + }, + multiline: { + true: { + root: 'items-start', + actions: 'items-start mt-1' + }, + false: { + root: 'items-center', + actions: 'items-center' + } + } + }, + compoundVariants: [...config.colors.map((color: string) => ({ + color, + variant: 'solid', + class: { + root: `bg-${color}-500 dark:bg-${color}-400 text-white dark:text-gray-900` + } + })), ...config.colors.map((color: string) => ({ + color, + variant: 'outline', + class: { + root: `text-${color}-500 dark:text-${color}-400 ring ring-inset ring-${color}-500 dark:ring-${color}-400` + } + })), ...config.colors.map((color: string) => ({ + color, + variant: 'soft', + class: { + root: `bg-${color}-50 dark:bg-${color}-400/10 text-${color}-500 dark:text-${color}-400` + } + })), ...config.colors.map((color: string) => ({ + color, + variant: 'subtle', + class: { + root: `bg-${color}-50 dark:bg-${color}-400/10 text-${color}-500 dark:text-${color}-400 ring ring-inset ring-${color}-500/25 dark:ring-${color}-400/25` + } + })), { + color: 'white', + variant: 'solid', + class: { + root: 'ring ring-inset ring-gray-300 dark:ring-gray-700 text-gray-900 dark:text-white bg-white dark:bg-gray-900' + } + }, { + color: 'gray', + variant: 'solid', + class: { + root: 'ring ring-inset ring-gray-300 dark:ring-gray-700 text-gray-700 dark:text-gray-200 bg-gray-50 dark:bg-gray-800' + } + }, { + color: 'black', + variant: 'solid', + class: { + root: 'text-white dark:text-gray-900 bg-gray-900 dark:bg-white' + } + }], + defaultVariants: { + color: 'white', + variant: 'solid' + } +}) diff --git a/src/theme/index.ts b/src/theme/index.ts index 918be8bf..44bf620a 100644 --- a/src/theme/index.ts +++ b/src/theme/index.ts @@ -1,4 +1,5 @@ export { default as accordion } from './accordion' +export { default as alert } from './alert' export { default as avatar } from './avatar' export { default as badge } from './badge' export { default as button } from './button' diff --git a/test/components/Alert.spec.ts b/test/components/Alert.spec.ts new file mode 100644 index 00000000..816c8c4d --- /dev/null +++ b/test/components/Alert.spec.ts @@ -0,0 +1,29 @@ +import { describe, it, expect } from 'vitest' +import Alert, { type AlertProps } from '../../src/runtime/components/Alert.vue' +import ComponentRender from '../component-render' + +describe('Alert', () => { + it.each([ + ['with title', { props: { title: 'Title' } }], + ['with description', { props: { title: 'Title', description: 'Description' } }], + ['with icon', { props: { title: 'Title', icon: 'i-heroicons-light-bulb' } }], + ['with avatar', { props: { title: 'Title', avatar: { src: 'https://avatars.githubusercontent.com/u/739984?v=4' } } }], + ['with as', { props: { title: 'Title', as: 'article' } }], + ['with color green', { props: { title: 'Title', color: 'green' as const } }], + ['with color white', { props: { title: 'Title', color: 'white' as const } }], + ['with color gray', { props: { title: 'Title', color: 'gray' as const } }], + ['with color black', { props: { title: 'Title', color: 'black' as const } }], + ['with variant outline', { props: { title: 'Title', variant: 'outline' as const } }], + ['with variant soft', { props: { title: 'Title', variant: 'soft' as const } }], + ['with variant link', { props: { title: 'Title', variant: 'subtle' as const } }], + ['with class', { props: { class: 'w-48' } }], + ['with ui', { props: { ui: { title: 'font-bold' } } }], + ['with leading slot', { slots: { title: () => 'Leading slot' } }], + ['with title slot', { slots: { title: () => 'Title slot' } }], + ['with description slot', { slots: { description: () => 'Description slot' } }], + ['with close slot', { slots: { close: () => 'Close slot' } }] + ])('renders %s correctly', async (nameOrHtml: string, options: { props?: AlertProps, slots?: any }) => { + const html = await ComponentRender(nameOrHtml, options, Alert) + expect(html).toMatchSnapshot() + }) +}) diff --git a/test/components/__snapshots__/Alert.spec.ts.snap b/test/components/__snapshots__/Alert.spec.ts.snap new file mode 100644 index 00000000..1e6cf4b5 --- /dev/null +++ b/test/components/__snapshots__/Alert.spec.ts.snap @@ -0,0 +1,217 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Alert > renders with as correctly 1`] = ` +"
+ +
+
Title
+ + +
+ +
" +`; + +exports[`Alert > renders with avatar correctly 1`] = ` +"
+
+
Title
+ + +
+ +
" +`; + +exports[`Alert > renders with class correctly 1`] = ` +"
+ +
+ + + +
+ +
" +`; + +exports[`Alert > renders with close slot correctly 1`] = ` +"
+ +
+ + + +
+ +
" +`; + +exports[`Alert > renders with color black correctly 1`] = ` +"
+ +
+
Title
+ + +
+ +
" +`; + +exports[`Alert > renders with color gray correctly 1`] = ` +"
+ +
+
Title
+ + +
+ +
" +`; + +exports[`Alert > renders with color green correctly 1`] = ` +"
+ +
+
Title
+ + +
+ +
" +`; + +exports[`Alert > renders with color white correctly 1`] = ` +"
+ +
+
Title
+ + +
+ +
" +`; + +exports[`Alert > renders with description correctly 1`] = ` +"
+ +
+
Title
+
Description
+ +
+ +
" +`; + +exports[`Alert > renders with description slot correctly 1`] = ` +"
+ +
+ +
Description slot
+ +
+ +
" +`; + +exports[`Alert > renders with icon correctly 1`] = ` +"
+
+
Title
+ + +
+ +
" +`; + +exports[`Alert > renders with leading slot correctly 1`] = ` +"
+ +
+
Leading slot
+ + +
+ +
" +`; + +exports[`Alert > renders with title correctly 1`] = ` +"
+ +
+
Title
+ + +
+ +
" +`; + +exports[`Alert > renders with title slot correctly 1`] = ` +"
+ +
+
Title slot
+ + +
+ +
" +`; + +exports[`Alert > renders with ui correctly 1`] = ` +"
+ +
+ + + +
+ +
" +`; + +exports[`Alert > renders with variant link correctly 1`] = ` +"
+ +
+
Title
+ + +
+ +
" +`; + +exports[`Alert > renders with variant outline correctly 1`] = ` +"
+ +
+
Title
+ + +
+ +
" +`; + +exports[`Alert > renders with variant soft correctly 1`] = ` +"
+ +
+
Title
+ + +
+ +
" +`;