adding components
This commit is contained in:
@@ -0,0 +1,79 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: 'success',
|
||||||
|
validator: v => ['primary','secondary','success','warning','danger','error','info','light','dark','white'].includes(v),
|
||||||
|
},
|
||||||
|
dismissible: { type: Boolean, default: false },
|
||||||
|
fontWeight: { type: String, default: '' },
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['dismissed'])
|
||||||
|
|
||||||
|
const visible = ref(true)
|
||||||
|
|
||||||
|
function dismiss() {
|
||||||
|
visible.value = false
|
||||||
|
emit('dismissed')
|
||||||
|
}
|
||||||
|
|
||||||
|
const colorClasses = {
|
||||||
|
primary: 'bg-primary text-white',
|
||||||
|
secondary: 'bg-secondary text-white',
|
||||||
|
success: 'bg-success text-white',
|
||||||
|
warning: 'bg-warning text-white',
|
||||||
|
danger: 'bg-danger text-white',
|
||||||
|
error: 'bg-danger text-white',
|
||||||
|
info: 'bg-info text-white',
|
||||||
|
light: 'bg-light text-dark',
|
||||||
|
dark: 'bg-dark text-white',
|
||||||
|
white: 'bg-white text-dark border border-gray-200',
|
||||||
|
}
|
||||||
|
|
||||||
|
const weightClasses = {
|
||||||
|
light: 'font-light',
|
||||||
|
normal: 'font-normal',
|
||||||
|
bold: 'font-bold',
|
||||||
|
bolder: 'font-black',
|
||||||
|
}
|
||||||
|
|
||||||
|
const classes = computed(() => [
|
||||||
|
'relative flex items-start gap-3 rounded-lg px-4 py-3 text-sm',
|
||||||
|
colorClasses[props.color] ?? colorClasses.success,
|
||||||
|
props.fontWeight ? weightClasses[props.fontWeight] : '',
|
||||||
|
])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Transition name="alert-fade">
|
||||||
|
<div v-if="visible" :class="classes" role="alert">
|
||||||
|
<slot />
|
||||||
|
<button
|
||||||
|
v-if="dismissible"
|
||||||
|
type="button"
|
||||||
|
class="ml-auto shrink-0 opacity-70 transition-opacity hover:opacity-100"
|
||||||
|
aria-label="Close"
|
||||||
|
@click="dismiss"
|
||||||
|
>
|
||||||
|
<span class="material-icons text-lg leading-none">close</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.alert-fade-leave-active {
|
||||||
|
transition: opacity 0.2s ease, transform 0.2s ease, max-height 0.25s ease;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.alert-fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-4px);
|
||||||
|
max-height: 0;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
image: { type: String, required: true },
|
||||||
|
alt: { type: String, required: true },
|
||||||
|
size: { type: String, default: 'md' },
|
||||||
|
borderRadius: { type: String, default: '' },
|
||||||
|
})
|
||||||
|
|
||||||
|
const sizes = {
|
||||||
|
xxs: 'h-5 w-5',
|
||||||
|
xs: 'h-6 w-6',
|
||||||
|
sm: 'h-9 w-9',
|
||||||
|
md: 'h-11 w-11',
|
||||||
|
lg: 'h-14 w-14',
|
||||||
|
xl: 'h-[4.5rem] w-[4.5rem]',
|
||||||
|
xxl: 'h-24 w-24',
|
||||||
|
}
|
||||||
|
|
||||||
|
const radii = {
|
||||||
|
'': 'rounded-xl',
|
||||||
|
sm: 'rounded-sm',
|
||||||
|
md: 'rounded-md',
|
||||||
|
lg: 'rounded-lg',
|
||||||
|
xl: 'rounded-xl',
|
||||||
|
'2xl':'rounded-2xl',
|
||||||
|
full: 'rounded-full',
|
||||||
|
}
|
||||||
|
|
||||||
|
const classes = computed(() => [
|
||||||
|
'object-cover',
|
||||||
|
sizes[props.size] ?? sizes.md,
|
||||||
|
radii[props.borderRadius] ?? radii[''],
|
||||||
|
])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<img :src="image" :alt="alt" :class="classes" />
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
size: {
|
||||||
|
type: String,
|
||||||
|
default: 'md',
|
||||||
|
validator: v => ['sm', 'md', 'lg'].includes(v),
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: 'success',
|
||||||
|
validator: v => ['primary','secondary','info','success','warning','error','danger','light','dark','white'].includes(v),
|
||||||
|
},
|
||||||
|
variant: {
|
||||||
|
type: String,
|
||||||
|
default: 'fill',
|
||||||
|
validator: v => ['fill', 'gradient'].includes(v),
|
||||||
|
},
|
||||||
|
rounded: { type: Boolean, default: false },
|
||||||
|
})
|
||||||
|
|
||||||
|
const fill = {
|
||||||
|
primary: 'bg-primary text-white',
|
||||||
|
secondary: 'bg-secondary text-white',
|
||||||
|
info: 'bg-info text-white',
|
||||||
|
success: 'bg-success text-white',
|
||||||
|
warning: 'bg-warning text-white',
|
||||||
|
danger: 'bg-danger text-white',
|
||||||
|
error: 'bg-danger text-white',
|
||||||
|
light: 'bg-light text-dark',
|
||||||
|
dark: 'bg-dark text-white',
|
||||||
|
white: 'bg-white text-dark shadow-soft-xs',
|
||||||
|
}
|
||||||
|
|
||||||
|
const grad = {
|
||||||
|
primary: 'bg-gradient-primary shadow-primary text-white',
|
||||||
|
secondary: 'bg-gradient-secondary shadow-secondary text-white',
|
||||||
|
info: 'bg-gradient-info shadow-info text-white',
|
||||||
|
success: 'bg-gradient-success shadow-success text-white',
|
||||||
|
warning: 'bg-gradient-warning shadow-warning text-white',
|
||||||
|
danger: 'bg-gradient-danger shadow-danger text-white',
|
||||||
|
error: 'bg-gradient-danger shadow-danger text-white',
|
||||||
|
light: 'bg-gradient-light shadow-soft-xs text-dark',
|
||||||
|
dark: 'bg-gradient-dark shadow-dark text-white',
|
||||||
|
white: 'bg-white shadow-soft-xs text-dark',
|
||||||
|
}
|
||||||
|
|
||||||
|
const sizes = {
|
||||||
|
sm: 'px-2 py-0.5 text-xs',
|
||||||
|
md: 'px-2.5 py-1 text-xs',
|
||||||
|
lg: 'px-3 py-1.5 text-sm',
|
||||||
|
}
|
||||||
|
|
||||||
|
const classes = computed(() => [
|
||||||
|
'inline-flex items-center font-medium',
|
||||||
|
props.rounded ? 'rounded-full' : 'rounded-md',
|
||||||
|
sizes[props.size],
|
||||||
|
(props.variant === 'gradient' ? grad : fill)[props.color] ?? fill.success,
|
||||||
|
])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<span :class="classes">
|
||||||
|
<slot />
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
variant: {
|
||||||
|
type: String,
|
||||||
|
default: 'contained',
|
||||||
|
validator: v => ['contained', 'gradient', 'outline'].includes(v),
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: 'primary',
|
||||||
|
validator: v => ['primary','secondary','info','success','warning','danger','error','light','white','dark','none'].includes(v),
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: String,
|
||||||
|
default: 'md',
|
||||||
|
validator: v => ['sm', 'md', 'lg'].includes(v),
|
||||||
|
},
|
||||||
|
fullWidth: { type: Boolean, default: false },
|
||||||
|
disabled: { type: Boolean, default: false },
|
||||||
|
})
|
||||||
|
|
||||||
|
// Static string maps — Tailwind scanner reads these to include the classes.
|
||||||
|
const contained = {
|
||||||
|
primary: 'bg-primary text-white',
|
||||||
|
secondary: 'bg-secondary text-white',
|
||||||
|
success: 'bg-success text-white',
|
||||||
|
warning: 'bg-warning text-white',
|
||||||
|
danger: 'bg-danger text-white',
|
||||||
|
error: 'bg-danger text-white',
|
||||||
|
info: 'bg-info text-white',
|
||||||
|
light: 'bg-light text-dark',
|
||||||
|
white: 'bg-white text-dark shadow-soft-sm',
|
||||||
|
dark: 'bg-dark text-white',
|
||||||
|
none: 'bg-transparent text-dark',
|
||||||
|
}
|
||||||
|
|
||||||
|
const gradient = {
|
||||||
|
primary: 'bg-gradient-primary shadow-primary text-white',
|
||||||
|
secondary: 'bg-gradient-secondary shadow-secondary text-white',
|
||||||
|
success: 'bg-gradient-success shadow-success text-white',
|
||||||
|
warning: 'bg-gradient-warning shadow-warning text-white',
|
||||||
|
danger: 'bg-gradient-danger shadow-danger text-white',
|
||||||
|
error: 'bg-gradient-danger shadow-danger text-white',
|
||||||
|
info: 'bg-gradient-info shadow-info text-white',
|
||||||
|
light: 'bg-gradient-light shadow-soft-sm text-dark',
|
||||||
|
white: 'bg-white shadow-soft-md text-dark',
|
||||||
|
dark: 'bg-gradient-dark shadow-dark text-white',
|
||||||
|
none: 'bg-transparent text-dark',
|
||||||
|
}
|
||||||
|
|
||||||
|
const outline = {
|
||||||
|
primary: 'border border-primary text-primary hover:bg-primary/10',
|
||||||
|
secondary: 'border border-secondary text-secondary hover:bg-secondary/10',
|
||||||
|
success: 'border border-success text-success hover:bg-success/10',
|
||||||
|
warning: 'border border-warning text-warning hover:bg-warning/10',
|
||||||
|
danger: 'border border-danger text-danger hover:bg-danger/10',
|
||||||
|
error: 'border border-danger text-danger hover:bg-danger/10',
|
||||||
|
info: 'border border-info text-info hover:bg-info/10',
|
||||||
|
light: 'border border-gray-300 text-dark hover:bg-light',
|
||||||
|
white: 'border border-white text-white hover:bg-white/10',
|
||||||
|
dark: 'border border-dark text-dark hover:bg-dark/10',
|
||||||
|
none: 'border border-gray-300 text-dark hover:bg-gray-100',
|
||||||
|
}
|
||||||
|
|
||||||
|
const sizes = {
|
||||||
|
sm: 'px-4 py-1.5 text-xs',
|
||||||
|
md: 'px-6 py-2.5 text-sm',
|
||||||
|
lg: 'px-8 py-3.5 text-base',
|
||||||
|
}
|
||||||
|
|
||||||
|
const colorMap = { contained, gradient, outline }
|
||||||
|
|
||||||
|
const classes = computed(() => [
|
||||||
|
'inline-flex items-center justify-center gap-2 font-medium rounded-lg cursor-pointer select-none',
|
||||||
|
'transition-all duration-200 hover:-translate-y-0.5 active:translate-y-0',
|
||||||
|
colorMap[props.variant]?.[props.color] ?? '',
|
||||||
|
sizes[props.size],
|
||||||
|
props.fullWidth && 'w-full',
|
||||||
|
props.disabled && 'opacity-60 pointer-events-none cursor-not-allowed',
|
||||||
|
])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<button :class="classes" :disabled="disabled">
|
||||||
|
<slot />
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
id: { type: String, default: '' },
|
||||||
|
color: { type: String, default: 'dark' },
|
||||||
|
modelValue: { type: Boolean, default: false },
|
||||||
|
inputClass: { type: String, default: '' },
|
||||||
|
labelClass: { type: String, default: '' },
|
||||||
|
})
|
||||||
|
|
||||||
|
defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
|
// Use CSS custom properties from @theme to drive accent-color.
|
||||||
|
// This avoids generating per-color accent-* utilities.
|
||||||
|
const accentStyle = computed(() => ({
|
||||||
|
accentColor: props.color ? `var(--color-${props.color})` : undefined,
|
||||||
|
}))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex items-start gap-2.5">
|
||||||
|
<input
|
||||||
|
:id="id"
|
||||||
|
type="checkbox"
|
||||||
|
:checked="modelValue"
|
||||||
|
:class="['h-4 w-4 cursor-pointer rounded border-gray-300 transition-colors', inputClass]"
|
||||||
|
:style="accentStyle"
|
||||||
|
v-bind="$attrs"
|
||||||
|
@change="$emit('update:modelValue', $event.target.checked)"
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
v-if="$slots.default"
|
||||||
|
:for="id"
|
||||||
|
:class="['cursor-pointer select-none text-sm text-dark', labelClass]"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
id: { type: String, default: '' },
|
||||||
|
type: { type: String, default: 'text' },
|
||||||
|
label: { type: [String, Object],default: '' },
|
||||||
|
modelValue: { type: String, default: '' },
|
||||||
|
placeholder: { type: String, default: '' },
|
||||||
|
size: { type: String, default: 'md' },
|
||||||
|
error: { type: Boolean, default: false },
|
||||||
|
success: { type: Boolean, default: false },
|
||||||
|
isRequired: { type: Boolean, default: false },
|
||||||
|
isDisabled: { type: Boolean, default: false },
|
||||||
|
inputClass: { type: String, default: '' },
|
||||||
|
icon: { type: String, default: '' },
|
||||||
|
})
|
||||||
|
|
||||||
|
defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
|
const labelText = computed(() =>
|
||||||
|
typeof props.label === 'string' ? props.label : props.label?.text ?? ''
|
||||||
|
)
|
||||||
|
const labelExtraClass = computed(() =>
|
||||||
|
typeof props.label === 'object' ? props.label?.class ?? '' : ''
|
||||||
|
)
|
||||||
|
|
||||||
|
const sizes = {
|
||||||
|
sm: 'py-1.5 text-xs',
|
||||||
|
md: 'py-2 text-sm',
|
||||||
|
lg: 'py-2.5 text-base',
|
||||||
|
}
|
||||||
|
|
||||||
|
const borderClass = computed(() => {
|
||||||
|
if (props.error) return 'border-danger focus:border-danger focus:ring-1 focus:ring-danger'
|
||||||
|
if (props.success) return 'border-success focus:border-success focus:ring-1 focus:ring-success'
|
||||||
|
return 'border-gray-200 focus:border-primary focus:ring-1 focus:ring-primary'
|
||||||
|
})
|
||||||
|
|
||||||
|
const inputClasses = computed(() => [
|
||||||
|
'w-full rounded-lg border bg-white text-dark outline-none transition-colors placeholder:text-gray-400',
|
||||||
|
props.icon ? 'pl-9 pr-3' : 'px-3',
|
||||||
|
sizes[props.size] ?? sizes.md,
|
||||||
|
borderClass.value,
|
||||||
|
props.isDisabled && 'cursor-not-allowed opacity-60 bg-gray-50',
|
||||||
|
props.inputClass,
|
||||||
|
])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
v-if="labelText"
|
||||||
|
:for="id"
|
||||||
|
:class="['mb-1 block text-xs font-medium text-secondary', labelExtraClass]"
|
||||||
|
>
|
||||||
|
{{ labelText }}
|
||||||
|
<span v-if="isRequired" class="text-danger">*</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="relative">
|
||||||
|
<span
|
||||||
|
v-if="icon"
|
||||||
|
class="material-icons pointer-events-none absolute left-2.5 top-1/2 -translate-y-1/2 text-base text-secondary"
|
||||||
|
>
|
||||||
|
{{ icon }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<input
|
||||||
|
:id="id"
|
||||||
|
:type="type"
|
||||||
|
:value="modelValue"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:required="isRequired"
|
||||||
|
:disabled="isDisabled"
|
||||||
|
:class="inputClasses"
|
||||||
|
v-bind="$attrs"
|
||||||
|
@input="$emit('update:modelValue', $event.target.value)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p v-if="error && typeof error === 'string'" class="mt-1 text-xs text-danger">
|
||||||
|
{{ error }}
|
||||||
|
</p>
|
||||||
|
<p v-if="success && typeof success === 'string'" class="mt-1 text-xs text-success">
|
||||||
|
{{ success }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<script setup>
|
||||||
|
import { provide } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
color: { type: String, default: 'primary' },
|
||||||
|
size: { type: String, default: 'md' },
|
||||||
|
})
|
||||||
|
|
||||||
|
provide('mk-pagination-color', props.color)
|
||||||
|
provide('mk-pagination-size', props.size)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ul class="flex list-none items-center gap-1 p-0">
|
||||||
|
<slot />
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
<script setup>
|
||||||
|
import { inject, computed } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
label: { type: String, default: '' },
|
||||||
|
active: { type: Boolean, default: false },
|
||||||
|
disabled: { type: Boolean, default: false },
|
||||||
|
prev: { type: Boolean, default: false },
|
||||||
|
next: { type: Boolean, default: false },
|
||||||
|
})
|
||||||
|
|
||||||
|
defineEmits(['click'])
|
||||||
|
|
||||||
|
const color = inject('mk-pagination-color', 'primary')
|
||||||
|
const size = inject('mk-pagination-size', 'md')
|
||||||
|
|
||||||
|
const activeClasses = {
|
||||||
|
primary: 'bg-gradient-primary shadow-primary text-white',
|
||||||
|
secondary: 'bg-gradient-secondary shadow-secondary text-white',
|
||||||
|
success: 'bg-gradient-success shadow-success text-white',
|
||||||
|
warning: 'bg-gradient-warning shadow-warning text-white',
|
||||||
|
danger: 'bg-gradient-danger shadow-danger text-white',
|
||||||
|
info: 'bg-gradient-info shadow-info text-white',
|
||||||
|
dark: 'bg-gradient-dark shadow-dark text-white',
|
||||||
|
}
|
||||||
|
|
||||||
|
const sizeClasses = {
|
||||||
|
sm: 'h-7 min-w-7 text-xs',
|
||||||
|
md: 'h-9 min-w-9 text-sm',
|
||||||
|
lg: 'h-11 min-w-11 text-base',
|
||||||
|
}
|
||||||
|
|
||||||
|
const classes = computed(() => [
|
||||||
|
'flex cursor-pointer select-none items-center justify-center rounded-lg px-2 font-medium transition-all duration-200',
|
||||||
|
sizeClasses[size] ?? sizeClasses.md,
|
||||||
|
props.active
|
||||||
|
? (activeClasses[color] ?? activeClasses.primary)
|
||||||
|
: 'text-secondary hover:bg-gray-100 hover:text-dark',
|
||||||
|
props.disabled && 'pointer-events-none opacity-40',
|
||||||
|
])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<li>
|
||||||
|
<button :class="classes" @click="$emit('click')">
|
||||||
|
<span v-if="prev" class="material-icons text-base leading-none">chevron_left</span>
|
||||||
|
<span v-else-if="next" class="material-icons text-base leading-none">chevron_right</span>
|
||||||
|
<span v-else>{{ label }}</span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
variant: {
|
||||||
|
type: String,
|
||||||
|
default: 'contained',
|
||||||
|
validator: v => ['contained', 'gradient'].includes(v),
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: 'primary',
|
||||||
|
validator: v => ['primary','secondary','info','success','warning','danger','error','light','dark'].includes(v),
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const contained = {
|
||||||
|
primary: 'bg-primary',
|
||||||
|
secondary: 'bg-secondary',
|
||||||
|
info: 'bg-info',
|
||||||
|
success: 'bg-success',
|
||||||
|
warning: 'bg-warning',
|
||||||
|
danger: 'bg-danger',
|
||||||
|
error: 'bg-danger',
|
||||||
|
light: 'bg-light',
|
||||||
|
dark: 'bg-dark',
|
||||||
|
}
|
||||||
|
|
||||||
|
const grad = {
|
||||||
|
primary: 'bg-gradient-primary',
|
||||||
|
secondary: 'bg-gradient-secondary',
|
||||||
|
info: 'bg-gradient-info',
|
||||||
|
success: 'bg-gradient-success',
|
||||||
|
warning: 'bg-gradient-warning',
|
||||||
|
danger: 'bg-gradient-danger',
|
||||||
|
error: 'bg-gradient-danger',
|
||||||
|
light: 'bg-gradient-light',
|
||||||
|
dark: 'bg-gradient-dark',
|
||||||
|
}
|
||||||
|
|
||||||
|
const barClass = computed(() =>
|
||||||
|
(props.variant === 'gradient' ? grad : contained)[props.color] ?? contained.primary
|
||||||
|
)
|
||||||
|
|
||||||
|
const pct = computed(() => Math.min(100, Math.max(0, props.value)))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="w-full overflow-hidden rounded-full bg-gray-200" style="height: 6px">
|
||||||
|
<div
|
||||||
|
:class="barClass"
|
||||||
|
class="h-full rounded-full transition-all duration-500"
|
||||||
|
role="progressbar"
|
||||||
|
:style="{ width: `${pct}%` }"
|
||||||
|
:aria-valuenow="pct"
|
||||||
|
aria-valuemin="0"
|
||||||
|
aria-valuemax="100"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
route: { type: String, required: true },
|
||||||
|
color: { type: String, required: true },
|
||||||
|
component: { type: String, required: true },
|
||||||
|
label: { type: String, default: '' },
|
||||||
|
})
|
||||||
|
|
||||||
|
// Same contained map as MkButton — drives bg + text color.
|
||||||
|
const colorClasses = {
|
||||||
|
primary: 'bg-primary text-white hover:bg-primary/90',
|
||||||
|
secondary: 'bg-secondary text-white hover:bg-secondary/90',
|
||||||
|
success: 'bg-success text-white hover:bg-success/90',
|
||||||
|
warning: 'bg-warning text-white hover:bg-warning/90',
|
||||||
|
danger: 'bg-danger text-white hover:bg-danger/90',
|
||||||
|
info: 'bg-info text-white hover:bg-info/90',
|
||||||
|
dark: 'bg-dark text-white hover:bg-dark/90',
|
||||||
|
white: 'bg-white text-dark shadow-soft-sm hover:shadow-soft-md',
|
||||||
|
facebook: 'bg-[#3b5998] text-white hover:bg-[#344e86]',
|
||||||
|
twitter: 'bg-[#1da1f2] text-white hover:bg-[#0d8fd9]',
|
||||||
|
instagram: 'bg-[#e1306c] text-white hover:bg-[#c82761]',
|
||||||
|
youtube: 'bg-[#ff0000] text-white hover:bg-[#e60000]',
|
||||||
|
linkedin: 'bg-[#0077b5] text-white hover:bg-[#00669c]',
|
||||||
|
github: 'bg-[#333333] text-white hover:bg-[#222222]',
|
||||||
|
}
|
||||||
|
|
||||||
|
const classes = computed(() => [
|
||||||
|
'inline-flex items-center gap-2 rounded-lg px-4 py-2 text-sm font-medium transition-all duration-200',
|
||||||
|
colorClasses[props.color] ?? colorClasses.secondary,
|
||||||
|
])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<a :href="route" :class="classes" target="_blank" rel="noopener">
|
||||||
|
<!--
|
||||||
|
Renders a Font Awesome brand icon. Requires Font Awesome Free 6
|
||||||
|
to be loaded in the project (e.g. via CDN or npm).
|
||||||
|
The `component` prop maps to the FA icon slug: "facebook", "twitter", etc.
|
||||||
|
-->
|
||||||
|
<i class="fab" :class="`fa-${component}`" aria-hidden="true" />
|
||||||
|
<span v-if="label">{{ label }}</span>
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
id: { type: String, required: true },
|
||||||
|
modelValue: { type: Boolean, default: false },
|
||||||
|
color: { type: String, default: 'primary' },
|
||||||
|
labelClass: { type: String, default: '' },
|
||||||
|
})
|
||||||
|
|
||||||
|
defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
|
// Drive the checked background via CSS custom property so we don't
|
||||||
|
// need per-color utility classes for the track.
|
||||||
|
const trackStyle = computed(() =>
|
||||||
|
props.modelValue
|
||||||
|
? { backgroundColor: `var(--color-${props.color})` }
|
||||||
|
: {}
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<button
|
||||||
|
:id="id"
|
||||||
|
type="button"
|
||||||
|
role="switch"
|
||||||
|
:aria-checked="String(modelValue)"
|
||||||
|
class="relative inline-flex h-5 w-10 shrink-0 cursor-pointer rounded-full border-2 border-transparent bg-gray-300 transition-colors duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-1"
|
||||||
|
:style="trackStyle"
|
||||||
|
@click="$emit('update:modelValue', !modelValue)"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="pointer-events-none inline-block h-4 w-4 rounded-full bg-white shadow-sm transition-transform duration-200"
|
||||||
|
:class="modelValue ? 'translate-x-5' : 'translate-x-0'"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<label
|
||||||
|
v-if="$slots.default"
|
||||||
|
:for="id"
|
||||||
|
:class="['cursor-pointer select-none text-sm text-dark', labelClass]"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div v-if="$slots.description" class="mt-0.5 text-xs text-secondary">
|
||||||
|
<slot name="description" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
id: { type: String, default: 'message' },
|
||||||
|
rows: { type: Number, default: 4 },
|
||||||
|
modelValue: { type: String, default: '' },
|
||||||
|
placeholder: { type: String, default: '' },
|
||||||
|
labelClass: { type: String, default: '' },
|
||||||
|
error: { type: Boolean, default: false },
|
||||||
|
success: { type: Boolean, default: false },
|
||||||
|
isDisabled: { type: Boolean, default: false },
|
||||||
|
})
|
||||||
|
|
||||||
|
defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
|
const borderClass = computed(() => {
|
||||||
|
if (props.error) return 'border-danger focus:border-danger focus:ring-1 focus:ring-danger'
|
||||||
|
if (props.success) return 'border-success focus:border-success focus:ring-1 focus:ring-success'
|
||||||
|
return 'border-gray-200 focus:border-primary focus:ring-1 focus:ring-primary'
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
v-if="$slots.default"
|
||||||
|
:for="id"
|
||||||
|
:class="['mb-1 block text-xs font-medium text-secondary', labelClass]"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<textarea
|
||||||
|
:id="id"
|
||||||
|
:rows="rows"
|
||||||
|
:value="modelValue"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:disabled="isDisabled"
|
||||||
|
:class="[
|
||||||
|
'w-full resize-none rounded-lg border bg-white px-3 py-2 text-sm text-dark outline-none transition-colors placeholder:text-gray-400',
|
||||||
|
borderClass,
|
||||||
|
isDisabled && 'cursor-not-allowed opacity-60 bg-gray-50',
|
||||||
|
]"
|
||||||
|
v-bind="$attrs"
|
||||||
|
@input="$emit('update:modelValue', $event.target.value)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
Reference in New Issue
Block a user