Skip to content

Commit

Permalink
feat(components): make alert atom component ui
Browse files Browse the repository at this point in the history
META DATA: #3
  • Loading branch information
dungdv100298 authored and toantranmei committed Apr 5, 2024
1 parent ba850ca commit a71a95f
Show file tree
Hide file tree
Showing 6 changed files with 262 additions and 0 deletions.
11 changes: 11 additions & 0 deletions playground/pages/alert.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<template>
<div>
Alert:
<mei-alert title="Heads up!" />
<mei-alert
icon="i-heroicons-command-line"
description="You can add components to your app using the cli."
title="Heads up!"
/>
</div>
</template>
197 changes: 197 additions & 0 deletions src/runtime/components/atoms/alert.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
<template>
<div
:class="alertClass"
v-bind="attrs"
>
<div
class="flex"
:class="[ui.gap, { 'items-start': (description || $slots.description), 'items-center': !description && !$slots.description }]"
>
<slot
name="icon"
:icon="icon"
>
<MeiIcon
v-if="icon"
:name="icon"
:ui="ui.icon.base"
/>
</slot>
<slot
name="avatar"
:avatar="avatar"
>
<MeiAvatar
v-if="avatar"
v-bind="{ size: ui.avatar.size, ...avatar }"
:class="ui.avatar.base"
/>
</slot>

<div :class="ui.inner">
<p
v-if="(title || $slots.title)"
:class="ui.title"
>
<slot
name="title"
:title="title"
>
{{ title }}
</slot>
</p>
<p
v-if="description || $slots.description"
:class="twMerge(ui.description, !(title && $slots.title) && 'mt-0 leading-5')"
>
<slot
name="description"
:description="description"
>
{{ description }}
</slot>
</p>

<div
v-if="(description || $slots.description) && actions.length"
:class="ui.actions"
>
<MeiButton
v-for="(action, index) of actions"
:key="index"
v-bind="{ ...(ui.default.actionButton || {}), ...action }"
@click.stop="onAction(action)"
/>
</div>
</div>
<div
v-if="closeButton || (!description && !$slots.description && actions.length)"
:class="twMerge(ui.actions, 'mt-0')"
>
<template v-if="!description && !$slots.description && actions.length">
<MeiButton
v-for="(action, index) of actions"
:key="index"
v-bind="{ ...(ui.default.actionButton || {}), ...action }"
@click.stop="onAction(action)"
/>
</template>

<MeiButton
v-if="closeButton"
aria-label="Close"
v-bind="{ ...(ui.default.closeButton || {}), ...closeButton }"
@click.stop="$emit('close')"
/>
</div>
</div>
</div>
</template>

<script lang="ts">
import { computed, toRef, defineComponent } from 'vue'
import type { PropType } from 'vue'
import { twMerge, twJoin } from 'tailwind-merge'
import MeiIcon from "./icon.vue";
import MeiAvatar from "./avatar.vue";
import MeiButton from './button.vue'
import { useMeiUI } from "../../composables/use-mei-ui";
import type { Avatar, Button, AlertColor, AlertVariant, AlertAction, Strategy } from '../../types'
import { mergeConfig } from '../../utils'
// @ts-expect-error
import appConfig from '#build/app.config'
import { alert } from '#mei-ui/ui-configs'
const config = mergeConfig<typeof alert>(appConfig.meiUI.strategy, appConfig.meiUI.alert, alert)
export default defineComponent({
components: {
MeiIcon,
MeiAvatar,
MeiButton
},
inheritAttrs: false,
props: {
title: {
type: String,
default: null
},
description: {
type: String,
default: null
},
icon: {
type: String,
default: () => config.default.icon
},
avatar: {
type: Object as PropType<Avatar>,
default: null
},
closeButton: {
type: Object as PropType<Button>,
default: () => config.default.closeButton as unknown as Button
},
actions: {
type: Array as PropType<AlertAction[]>,
default: () => []
},
color: {
type: String as PropType<AlertColor>,
default: () => config.default.color,
validator (value: string) {
return [...appConfig.meiUI.colors, ...Object.keys(config.color)].includes(value)
}
},
variant: {
type: String as PropType<AlertVariant>,
default: () => config.default.variant,
validator (value: string) {
return [
...Object.keys(config.variant),
...Object.values(config.color).flatMap(value => Object.keys(value))
].includes(value)
}
},
class: {
type: [String, Object, Array] as PropType<any>,
default: () => ''
},
ui: {
type: Object as PropType<Partial<typeof config> & { strategy?: Strategy }>,
default: () => ({})
}
},
emits: ['close'],
setup (props) {
const { ui, attrs } = useMeiUI('alert', toRef(props, 'ui'), config)
const alertClass = computed(() => {
const variant = ui.value.color?.[props.color as string]?.[props.variant as string] || ui.value.variant[props.variant]
return twMerge(twJoin(
ui.value.wrapper,
ui.value.rounded,
ui.value.shadow,
ui.value.padding,
variant?.replaceAll('{color}', props.color)
), props.class)
})
function onAction (action: AlertAction) {
if (action.click) {
action.click()
}
}
return {
// eslint-disable-next-line vue/no-dupe-keys
ui,
attrs,
alertClass,
onAction,
twMerge
}
}
})
</script>
12 changes: 12 additions & 0 deletions src/runtime/types/alert.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { alert } from "../ui-configs";
import type { NestedKeyOf, ExtractDeepKey, ExtractDeepObject } from '.'
import type { Button } from './button'
import colors from '#mei-ui-colors'
import type { AppConfig } from 'nuxt/schema'

export type AlertColor = keyof typeof alert.color | ExtractDeepKey<AppConfig, ['ui', 'alert', 'color']> | typeof colors[number]
export type AlertVariant = keyof typeof alert.variant | ExtractDeepKey<AppConfig, ['ui', 'alert', 'variant']> | NestedKeyOf<typeof alert.color> | NestedKeyOf<ExtractDeepObject<AppConfig, ['ui', 'alert', 'color']>>

export interface AlertAction extends Button {
click?: Function
}
1 change: 1 addition & 0 deletions src/runtime/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export * from "./kbd";
export * from "./dropdown";
export * from "./avatar";
export * from "./input";
export * from "./alert";
40 changes: 40 additions & 0 deletions src/runtime/ui-configs/alert.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
export default {
wrapper: 'w-full relative overflow-hidden',
inner: 'w-0 flex-1',
title: 'text-sm font-medium',
description: 'mt-1 text-sm leading-4 opacity-90',
actions: 'flex items-center gap-2 mt-3 flex-shrink-0',
shadow: '',
rounded: 'rounded-lg',
padding: 'p-4',
gap: 'gap-3',
icon: {
base: 'flex-shrink-0 w-5 h-5'
},
avatar: {
base: 'flex-shrink-0 self-center',
size: 'md' as const
},
color: {
white: {
solid: 'text-gray-900 dark:text-white bg-white dark:bg-gray-900 ring-1 ring-gray-200 dark:ring-gray-800'
}
},
variant: {
solid: 'bg-{color}-500 dark:bg-{color}-400 text-white dark:text-gray-900',
outline: 'text-{color}-500 dark:text-{color}-400 ring-1 ring-inset ring-{color}-500 dark:ring-{color}-400',
soft: 'bg-{color}-50 dark:bg-{color}-400 dark:bg-opacity-10 text-{color}-500 dark:text-{color}-400',
subtle: 'bg-{color}-50 dark:bg-{color}-400 dark:bg-opacity-10 text-{color}-500 dark:text-{color}-400 ring-1 ring-inset ring-{color}-500 dark:ring-{color}-400 ring-opacity-25 dark:ring-opacity-25'
},
default: {
color: 'white',
variant: 'solid',
icon: null,
closeButton: null,
actionButton: {
size: 'xs' as const,
color: 'primary' as const,
variant: 'link' as const
}
}
}
1 change: 1 addition & 0 deletions src/runtime/ui-configs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ export { default as avatar } from "./avatar";
export { default as modal } from "./modal";
export { default as card } from "./card";
export { default as input } from "./input";
export { default as alert } from "./alert";

0 comments on commit a71a95f

Please sign in to comment.