add frontend
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
const props = defineProps({
|
||||
variant: { type: String, default: 'info', validator: v => ['critical', 'warning', 'info', 'success'].includes(v) },
|
||||
size: { type: String, default: 'sm' },
|
||||
})
|
||||
|
||||
const classes = computed(() => {
|
||||
const base = 'inline-flex items-center font-medium rounded-full'
|
||||
const sizes = { xs: 'px-1.5 py-0.5 text-xs', sm: 'px-2 py-0.5 text-xs', md: 'px-2.5 py-1 text-sm' }
|
||||
const variants = {
|
||||
critical: 'bg-severity-critical/10 text-severity-critical dark:bg-severity-critical-dark/20 dark:text-red-300',
|
||||
warning: 'bg-severity-warning/10 text-severity-warning dark:bg-severity-warning-dark/20 dark:text-amber-300',
|
||||
info: 'bg-severity-info/10 text-severity-info dark:bg-severity-info-dark/20 dark:text-blue-300',
|
||||
success: 'bg-green-100 text-green-800 dark:bg-green-900/20 dark:text-green-300',
|
||||
}
|
||||
return twMerge(base, sizes[props.size], variants[props.variant])
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span :class="classes"><slot /></span>
|
||||
</template>
|
||||
@@ -0,0 +1,55 @@
|
||||
<script setup>
|
||||
import { computed, useAttrs } from 'vue'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
defineOptions({ inheritAttrs: false })
|
||||
|
||||
const props = defineProps({
|
||||
variant: {
|
||||
type: String,
|
||||
default: 'primary',
|
||||
validator: v => ['primary', 'secondary', 'danger', 'ghost'].includes(v),
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'md',
|
||||
validator: v => ['sm', 'md', 'lg'].includes(v),
|
||||
},
|
||||
disabled: { type: Boolean, default: false },
|
||||
type: { type: String, default: 'button' },
|
||||
})
|
||||
|
||||
const attrs = useAttrs()
|
||||
|
||||
const classes = computed(() => {
|
||||
const base =
|
||||
'inline-flex items-center justify-center font-medium rounded-lg transition duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none'
|
||||
const sizes = {
|
||||
sm: 'px-3 py-1.5 text-xs',
|
||||
md: 'px-4 py-2 text-sm',
|
||||
lg: 'px-5 py-2.5 text-base',
|
||||
}
|
||||
const variants = {
|
||||
primary:
|
||||
'bg-blue-600 text-white hover:bg-blue-700 focus-visible:ring-blue-500 dark:bg-blue-500 dark:hover:bg-blue-600',
|
||||
secondary:
|
||||
'bg-gray-100 text-gray-900 hover:bg-gray-200 focus-visible:ring-gray-400 dark:bg-gray-700 dark:text-gray-100 dark:hover:bg-gray-600',
|
||||
danger:
|
||||
'bg-red-600 text-white hover:bg-red-700 focus-visible:ring-red-500 dark:bg-red-500 dark:hover:bg-red-600',
|
||||
ghost:
|
||||
'bg-transparent text-gray-700 hover:bg-gray-100 focus-visible:ring-gray-400 dark:text-gray-300 dark:hover:bg-gray-800',
|
||||
}
|
||||
return twMerge(base, sizes[props.size], variants[props.variant], attrs.class)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
:type="type"
|
||||
:disabled="disabled"
|
||||
:class="classes"
|
||||
v-bind="{ ...attrs, class: undefined }"
|
||||
>
|
||||
<slot />
|
||||
</button>
|
||||
</template>
|
||||
@@ -0,0 +1,32 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
const props = defineProps({
|
||||
padding: {
|
||||
type: String,
|
||||
default: 'md',
|
||||
validator: v => ['none', 'sm', 'md', 'lg'].includes(v),
|
||||
},
|
||||
})
|
||||
|
||||
const classes = computed(() => {
|
||||
const base =
|
||||
'rounded-lg border border-gray-200 bg-white shadow-sm dark:border-gray-700 dark:bg-gray-900'
|
||||
const paddings = {
|
||||
none: '',
|
||||
sm: 'p-3',
|
||||
md: 'p-4',
|
||||
lg: 'p-6',
|
||||
}
|
||||
return twMerge(base, paddings[props.padding])
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="classes">
|
||||
<slot name="header" />
|
||||
<slot />
|
||||
<slot name="footer" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,32 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
message: { type: String, default: 'No data' },
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col items-center justify-center py-12 text-center">
|
||||
<div class="mb-4 text-gray-400 dark:text-gray-500">
|
||||
<slot name="icon">
|
||||
<svg
|
||||
class="mx-auto h-12 w-12"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="1.5"
|
||||
d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"
|
||||
/>
|
||||
</svg>
|
||||
</slot>
|
||||
</div>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">{{ message }}</p>
|
||||
<div v-if="$slots.action" class="mt-4">
|
||||
<slot name="action" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,27 @@
|
||||
<script setup>
|
||||
import { onMounted, onBeforeUnmount, ref } from 'vue'
|
||||
|
||||
defineProps({ open: Boolean, title: String })
|
||||
const emit = defineEmits(['close'])
|
||||
const modalRef = ref(null)
|
||||
|
||||
function onKeydown(e) {
|
||||
if (e.key === 'Escape') emit('close')
|
||||
}
|
||||
|
||||
onMounted(() => document.addEventListener('keydown', onKeydown))
|
||||
onBeforeUnmount(() => document.removeEventListener('keydown', onKeydown))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<div v-if="open" class="fixed inset-0 z-50 flex items-center justify-center">
|
||||
<div class="absolute inset-0 bg-black/50 backdrop-blur-sm" @click="$emit('close')" />
|
||||
<div ref="modalRef" role="dialog" aria-modal="true"
|
||||
class="relative z-10 w-full max-w-md rounded-lg bg-white p-6 shadow-lg dark:bg-gray-800">
|
||||
<h2 v-if="title" class="mb-4 text-lg font-semibold dark:text-white">{{ title }}</h2>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
@@ -0,0 +1,15 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
rows: { type: Number, default: 3 },
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="animate-pulse space-y-3" role="status" aria-label="Loading">
|
||||
<div
|
||||
v-for="n in rows"
|
||||
:key="n"
|
||||
class="h-12 rounded-lg bg-gray-200 dark:bg-gray-700"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user