feat(module)!: use tailwind-merge for class merging (#509)

This commit is contained in:
Benjamin Canac
2023-08-12 17:17:00 +02:00
parent 6d7973f6e1
commit 8880bdc456
47 changed files with 685 additions and 376 deletions

View File

@@ -1,5 +1,5 @@
<template>
<div :class="ui.wrapper">
<div :class="wrapperClass">
<div class="flex items-center h-5">
<input
:id="name"
@@ -13,7 +13,7 @@
type="checkbox"
class="form-checkbox"
:class="inputClass"
v-bind="$attrs"
v-bind="attrs"
@change="onChange"
>
</div>
@@ -32,8 +32,9 @@
<script lang="ts">
import { computed, defineComponent } from 'vue'
import type { PropType } from 'vue'
import { defu } from 'defu'
import { classNames } from '../../utils'
import { omit } from 'lodash-es'
import { twMerge, twJoin } from 'tailwind-merge'
import { defuTwMerge } from '../../utils'
import { useFormGroup } from '../../composables/useFormGroup'
import { useAppConfig } from '#imports'
// TODO: Remove
@@ -88,17 +89,21 @@ export default defineComponent({
return appConfig.ui.colors.includes(value)
}
},
inputClass: {
type: String,
default: ''
},
ui: {
type: Object as PropType<Partial<typeof appConfig.ui.checkbox>>,
default: () => appConfig.ui.checkbox
default: () => ({})
}
},
emits: ['update:modelValue', 'change'],
setup (props, { emit }) {
setup (props, { emit, attrs }) {
// TODO: Remove
const appConfig = useAppConfig()
const ui = computed<Partial<typeof appConfig.ui.checkbox>>(() => defu({}, props.ui, appConfig.ui.checkbox))
const ui = computed<Partial<typeof appConfig.ui.checkbox>>(() => defuTwMerge({}, props.ui, appConfig.ui.checkbox))
const { emitFormChange, formGroup } = useFormGroup()
const color = computed(() => formGroup?.error?.value ? 'red' : props.color)
@@ -117,21 +122,26 @@ export default defineComponent({
emitFormChange()
}
const wrapperClass = computed(() => twMerge(ui.value.wrapper, attrs.class as string))
const inputClass = computed(() => {
return classNames(
return twMerge(twJoin(
ui.value.base,
ui.value.rounded,
ui.value.background,
ui.value.border,
ui.value.ring.replaceAll('{color}', color.value),
ui.value.color.replaceAll('{color}', color.value)
)
), props.inputClass)
})
return {
attrs: omit(attrs, ['class']),
// eslint-disable-next-line vue/no-dupe-keys
ui,
toggle,
wrapperClass,
// eslint-disable-next-line vue/no-dupe-keys
inputClass,
onChange
}

View File

@@ -1,5 +1,5 @@
<template>
<div :class="ui.wrapper">
<div :class="wrapperClass" v-bind="attrs">
<label>
<div v-if="label" :class="[ui.label.wrapper, size]">
<p :class="[ui.label.base, required ? ui.label.required : '']">{{ label }}</p>
@@ -11,7 +11,7 @@
<div :class="[label ? ui.container : '']">
<slot v-bind="{ error }" />
<p v-if="error" :class="[ui.error, size]">{{ error }}</p>
<p v-if="error && typeof error !== 'boolean'" :class="[ui.error, size]">{{ error }}</p>
<p v-else-if="help" :class="[ui.help, size]">{{ help }}</p>
</div>
</label>
@@ -21,9 +21,11 @@
<script lang="ts">
import { computed, defineComponent, provide, inject } from 'vue'
import type { PropType } from 'vue'
import { defu } from 'defu'
import { omit } from 'lodash-es'
import { twMerge } from 'tailwind-merge'
import type { FormError } from '../../types'
import { defuTwMerge } from '../../utils'
import { useAppConfig } from '#imports'
// TODO: Remove
// @ts-expect-error
import appConfig from '#build/app.config'
@@ -31,6 +33,7 @@ import appConfig from '#build/app.config'
// const appConfig = useAppConfig()
export default defineComponent({
inheritAttrs: false,
props: {
name: {
type: String,
@@ -69,14 +72,16 @@ export default defineComponent({
},
ui: {
type: Object as PropType<Partial<typeof appConfig.ui.formGroup>>,
default: () => appConfig.ui.formGroup
default: () => ({})
}
},
setup (props) {
setup (props, { attrs }) {
// TODO: Remove
const appConfig = useAppConfig()
const ui = computed<Partial<typeof appConfig.ui.formGroup>>(() => defu({}, props.ui, appConfig.ui.formGroup))
const ui = computed<Partial<typeof appConfig.ui.formGroup>>(() => defuTwMerge({}, props.ui, appConfig.ui.formGroup))
const wrapperClass = computed(() => twMerge(ui.value.wrapper, attrs.class as string))
const formErrors = inject<Ref<FormError[]> | null>('form-errors', null)
@@ -95,8 +100,10 @@ export default defineComponent({
})
return {
attrs: omit(attrs, ['class']),
// eslint-disable-next-line vue/no-dupe-keys
ui,
wrapperClass,
// eslint-disable-next-line vue/no-dupe-keys
size,
// eslint-disable-next-line vue/no-dupe-keys

View File

@@ -1,5 +1,5 @@
<template>
<div :class="ui.wrapper">
<div :class="wrapperClass">
<input
ref="input"
:name="name"
@@ -10,7 +10,7 @@
:disabled="disabled || loading"
class="form-input"
:class="inputClass"
v-bind="$attrs"
v-bind="attrs"
@input="onInput"
@blur="onBlur"
>
@@ -33,10 +33,11 @@
<script lang="ts">
import { ref, computed, onMounted, defineComponent } from 'vue'
import type { PropType } from 'vue'
import { defu } from 'defu'
import { omit } from 'lodash-es'
import { twMerge, twJoin } from 'tailwind-merge'
import UIcon from '../elements/Icon.vue'
import { defuTwMerge } from '../../utils'
import { useFormGroup } from '../../composables/useFormGroup'
import { classNames } from '../../utils'
import { useAppConfig } from '#imports'
// TODO: Remove
// @ts-expect-error
@@ -134,17 +135,21 @@ export default defineComponent({
].includes(value)
}
},
inputClass: {
type: String,
default: null
},
ui: {
type: Object as PropType<Partial<typeof appConfig.ui.input>>,
default: () => appConfig.ui.input
default: () => ({})
}
},
emits: ['update:modelValue', 'blur'],
setup (props, { emit, slots }) {
setup (props, { emit, attrs, slots }) {
// TODO: Remove
const appConfig = useAppConfig()
const ui = computed<Partial<typeof appConfig.ui.input>>(() => defu({}, props.ui, appConfig.ui.input))
const ui = computed<Partial<typeof appConfig.ui.input>>(() => defuTwMerge({}, props.ui, appConfig.ui.input))
const { emitFormBlur, emitFormInput, formGroup } = useFormGroup()
const color = computed(() => formGroup?.error?.value ? 'red' : props.color)
@@ -174,10 +179,12 @@ export default defineComponent({
}, 100)
})
const wrapperClass = computed(() => twMerge(ui.value.wrapper, attrs.class as string))
const inputClass = computed(() => {
const variant = ui.value.color?.[color.value as string]?.[props.variant as string] || ui.value.variant[props.variant]
return classNames(
return twMerge(twJoin(
ui.value.base,
ui.value.rounded,
ui.value.placeholder,
@@ -186,7 +193,7 @@ export default defineComponent({
variant?.replaceAll('{color}', color.value),
(isLeading.value || slots.leading) && ui.value.leading.padding[size.value],
(isTrailing.value || slots.trailing) && ui.value.trailing.padding[size.value]
)
), props.inputClass)
})
const isLeading = computed(() => {
@@ -214,7 +221,7 @@ export default defineComponent({
})
const leadingWrapperIconClass = computed(() => {
return classNames(
return twJoin(
ui.value.icon.leading.wrapper,
ui.value.icon.leading.pointer,
ui.value.icon.leading.padding[size.value]
@@ -222,7 +229,7 @@ export default defineComponent({
})
const leadingIconClass = computed(() => {
return classNames(
return twJoin(
ui.value.icon.base,
appConfig.ui.colors.includes(color.value) && ui.value.icon.color.replaceAll('{color}', color.value),
ui.value.icon.size[size.value],
@@ -231,7 +238,7 @@ export default defineComponent({
})
const trailingWrapperIconClass = computed(() => {
return classNames(
return twJoin(
ui.value.icon.trailing.wrapper,
ui.value.icon.trailing.pointer,
ui.value.icon.trailing.padding[size.value]
@@ -239,7 +246,7 @@ export default defineComponent({
})
const trailingIconClass = computed(() => {
return classNames(
return twJoin(
ui.value.icon.base,
appConfig.ui.colors.includes(color.value) && ui.value.icon.color.replaceAll('{color}', color.value),
ui.value.icon.size[size.value],
@@ -248,11 +255,14 @@ export default defineComponent({
})
return {
attrs: omit(attrs, ['class']),
// eslint-disable-next-line vue/no-dupe-keys
ui,
input,
isLeading,
isTrailing,
wrapperClass,
// eslint-disable-next-line vue/no-dupe-keys
inputClass,
leadingIconName,
leadingIconClass,

View File

@@ -1,5 +1,5 @@
<template>
<div :class="ui.wrapper">
<div :class="wrapperClass">
<div class="flex items-center h-5">
<input
v-model="pick"
@@ -10,7 +10,7 @@
type="radio"
class="form-radio"
:class="inputClass"
v-bind="$attrs"
v-bind="attrs"
>
</div>
<div v-if="label || $slots.label" class="ms-3 text-sm">
@@ -28,8 +28,9 @@
<script lang="ts">
import { computed, defineComponent } from 'vue'
import type { PropType } from 'vue'
import { defu } from 'defu'
import { classNames } from '../../utils'
import { omit } from 'lodash-es'
import { twMerge, twJoin } from 'tailwind-merge'
import { defuTwMerge } from '../../utils'
import { useFormGroup } from '../../composables/useFormGroup'
import { useAppConfig } from '#imports'
// TODO: Remove
@@ -76,17 +77,21 @@ export default defineComponent({
return appConfig.ui.colors.includes(value)
}
},
inputClass: {
type: String,
default: null
},
ui: {
type: Object as PropType<Partial<typeof appConfig.ui.radio>>,
default: () => appConfig.ui.radio
default: () => ({})
}
},
emits: ['update:modelValue'],
setup (props, { emit }) {
setup (props, { emit, attrs }) {
// TODO: Remove
const appConfig = useAppConfig()
const ui = computed<Partial<typeof appConfig.ui.radio>>(() => defu({}, props.ui, appConfig.ui.radio))
const ui = computed<Partial<typeof appConfig.ui.radio>>(() => defuTwMerge({}, props.ui, appConfig.ui.radio))
const { emitFormChange, formGroup } = useFormGroup()
const color = computed(() => formGroup?.error?.value ? 'red' : props.color)
@@ -103,20 +108,25 @@ export default defineComponent({
}
})
const wrapperClass = computed(() => twMerge(ui.value.wrapper, attrs.class as string))
const inputClass = computed(() => {
return classNames(
return twMerge(twJoin(
ui.value.base,
ui.value.background,
ui.value.border,
ui.value.ring.replaceAll('{color}', color.value),
ui.value.color.replaceAll('{color}', color.value)
)
), props.inputClass)
})
return {
attrs: omit(attrs, ['class']),
// eslint-disable-next-line vue/no-dupe-keys
ui,
pick,
wrapperClass,
// eslint-disable-next-line vue/no-dupe-keys
inputClass
}
}

View File

@@ -10,7 +10,7 @@
:step="step"
type="range"
:class="[inputClass, thumbClass, trackClass]"
v-bind="$attrs"
v-bind="attrs"
@change="onChange"
>
@@ -21,8 +21,9 @@
<script lang="ts">
import { computed, defineComponent } from 'vue'
import type { PropType } from 'vue'
import { defu } from 'defu'
import { classNames } from '../../utils'
import { omit } from 'lodash-es'
import { twMerge, twJoin } from 'tailwind-merge'
import { defuTwMerge } from '../../utils'
import { useFormGroup } from '../../composables/useFormGroup'
import { useAppConfig } from '#imports'
// TODO: Remove
@@ -70,17 +71,21 @@ export default defineComponent({
return appConfig.ui.colors.includes(value)
}
},
inputClass: {
type: String,
default: null
},
ui: {
type: Object as PropType<Partial<typeof appConfig.ui.range>>,
default: () => appConfig.ui.range
default: () => ({})
}
},
emits: ['update:modelValue', 'change'],
setup (props, { emit }) {
setup (props, { emit, attrs }) {
// TODO: Remove
const appConfig = useAppConfig()
const ui = computed<Partial<typeof appConfig.ui.range>>(() => defu({}, props.ui, appConfig.ui.range))
const ui = computed<Partial<typeof appConfig.ui.range>>(() => defuTwMerge({}, props.ui, appConfig.ui.range))
const { emitFormChange, formGroup } = useFormGroup()
const color = computed(() => formGroup?.error?.value ? 'red' : props.color)
@@ -101,24 +106,24 @@ export default defineComponent({
}
const wrapperClass = computed(() => {
return classNames(
return twMerge(twJoin(
ui.value.wrapper,
ui.value.size[size.value]
)
), attrs.class as string)
})
const inputClass = computed(() => {
return classNames(
return twMerge(twJoin(
ui.value.base,
ui.value.background,
ui.value.rounded,
ui.value.ring.replaceAll('{color}', color.value),
ui.value.size[size.value]
)
), props.inputClass)
})
const thumbClass = computed(() => {
return classNames(
return twJoin(
ui.value.thumb.base,
// Intermediate class to allow thumb ring or background color (set to `current`) as it's impossible to safelist with arbitrary values
ui.value.thumb.color.replaceAll('{color}', color.value),
@@ -129,7 +134,7 @@ export default defineComponent({
})
const trackClass = computed(() => {
return classNames(
return twJoin(
ui.value.track.base,
ui.value.track.background,
ui.value.track.rounded,
@@ -138,7 +143,7 @@ export default defineComponent({
})
const progressClass = computed(() => {
return classNames(
return twJoin(
ui.value.progress.base,
ui.value.progress.rounded,
ui.value.progress.background.replaceAll('{color}', color.value),
@@ -156,10 +161,12 @@ export default defineComponent({
})
return {
attrs: omit(attrs, ['class']),
// eslint-disable-next-line vue/no-dupe-keys
ui,
value,
wrapperClass,
// eslint-disable-next-line vue/no-dupe-keys
inputClass,
thumbClass,
trackClass,

View File

@@ -1,5 +1,5 @@
<template>
<div :class="ui.wrapper">
<div :class="wrapperClass">
<select
:name="name"
:value="modelValue"
@@ -7,7 +7,7 @@
:disabled="disabled || loading"
class="form-select"
:class="selectClass"
v-bind="$attrs"
v-bind="attrs"
@input="onInput"
@change="onChange"
>
@@ -55,10 +55,10 @@
<script lang="ts">
import { computed, defineComponent } from 'vue'
import type { PropType, ComputedRef } from 'vue'
import { get } from 'lodash-es'
import { defu } from 'defu'
import { get, omit } from 'lodash-es'
import { twMerge, twJoin } from 'tailwind-merge'
import UIcon from '../elements/Icon.vue'
import { classNames } from '../../utils'
import { defuTwMerge } from '../../utils'
import { useFormGroup } from '../../composables/useFormGroup'
import { useAppConfig } from '#imports'
// TODO: Remove
@@ -161,17 +161,21 @@ export default defineComponent({
type: String,
default: 'value'
},
selectClass: {
type: String,
default: null
},
ui: {
type: Object as PropType<Partial<typeof appConfig.ui.select>>,
default: () => appConfig.ui.select
default: () => ({})
}
},
emits: ['update:modelValue', 'change'],
setup (props, { emit, slots }) {
setup (props, { emit, attrs, slots }) {
// TODO: Remove
const appConfig = useAppConfig()
const ui = computed<Partial<typeof appConfig.ui.select>>(() => defu({}, props.ui, appConfig.ui.select))
const ui = computed<Partial<typeof appConfig.ui.select>>(() => defuTwMerge({}, props.ui, appConfig.ui.select))
const { emitFormChange, formGroup } = useFormGroup()
const color = computed(() => formGroup?.error?.value ? 'red' : props.color)
@@ -239,10 +243,12 @@ export default defineComponent({
return foundOption[props.valueAttribute]
})
const wrapperClass = computed(() => twMerge(ui.value.wrapper, attrs.class as string))
const selectClass = computed(() => {
const variant = ui.value.color?.[color.value as string]?.[props.variant as string] || ui.value.variant[props.variant]
return classNames(
return twMerge(twJoin(
ui.value.base,
ui.value.rounded,
ui.value.size[size.value],
@@ -250,7 +256,7 @@ export default defineComponent({
variant?.replaceAll('{color}', color.value),
(isLeading.value || slots.leading) && ui.value.leading.padding[size.value],
(isTrailing.value || slots.trailing) && ui.value.trailing.padding[size.value]
)
), props.selectClass)
})
const isLeading = computed(() => {
@@ -278,7 +284,7 @@ export default defineComponent({
})
const leadingWrapperIconClass = computed(() => {
return classNames(
return twJoin(
ui.value.icon.leading.wrapper,
ui.value.icon.leading.pointer,
ui.value.icon.leading.padding[size.value]
@@ -286,7 +292,7 @@ export default defineComponent({
})
const leadingIconClass = computed(() => {
return classNames(
return twJoin(
ui.value.icon.base,
appConfig.ui.colors.includes(color.value) && ui.value.icon.color.replaceAll('{color}', color.value),
ui.value.icon.size[size.value],
@@ -295,7 +301,7 @@ export default defineComponent({
})
const trailingWrapperIconClass = computed(() => {
return classNames(
return twJoin(
ui.value.icon.trailing.wrapper,
ui.value.icon.trailing.pointer,
ui.value.icon.trailing.padding[size.value]
@@ -303,7 +309,7 @@ export default defineComponent({
})
const trailingIconClass = computed(() => {
return classNames(
return twJoin(
ui.value.icon.base,
appConfig.ui.colors.includes(color.value) && ui.value.icon.color.replaceAll('{color}', color.value),
ui.value.icon.size[size.value],
@@ -312,12 +318,15 @@ export default defineComponent({
})
return {
attrs: omit(attrs, ['class']),
// eslint-disable-next-line vue/no-dupe-keys
ui,
normalizedOptionsWithPlaceholder,
normalizedValue,
isLeading,
isTrailing,
wrapperClass,
// eslint-disable-next-line vue/no-dupe-keys
selectClass,
leadingIconName,
leadingIconClass,

View File

@@ -8,7 +8,7 @@
:multiple="multiple"
:disabled="disabled || loading"
as="div"
:class="uiMenu.wrapper"
:class="wrapperClass"
@update:model-value="onUpdate"
>
<input
@@ -28,7 +28,7 @@
class="inline-flex w-full"
>
<slot :open="open" :disabled="disabled" :loading="loading">
<button :class="selectClass" :disabled="disabled || loading" type="button" v-bind="$attrs">
<button :class="selectClass" :disabled="disabled || loading" type="button" v-bind="attrs">
<span v-if="(isLeading && leadingIconName) || $slots.leading" :class="leadingWrapperIconClass">
<slot name="leading" :disabled="disabled" :loading="loading">
<UIcon :name="leadingIconName" :class="leadingIconClass" />
@@ -131,9 +131,11 @@ import {
} from '@headlessui/vue'
import { computedAsync, useDebounceFn } from '@vueuse/core'
import { defu } from 'defu'
import { omit } from 'lodash-es'
import { twMerge, twJoin } from 'tailwind-merge'
import UIcon from '../elements/Icon.vue'
import UAvatar from '../elements/Avatar.vue'
import { classNames } from '../../utils'
import { defuTwMerge } from '../../utils'
import { usePopper } from '../../composables/usePopper'
import { useFormGroup } from '../../composables/useFormGroup'
import type { PopperOptions } from '../../types'
@@ -284,22 +286,26 @@ export default defineComponent({
type: Object as PropType<PopperOptions>,
default: () => ({})
},
selectClass: {
type: String,
default: null
},
ui: {
type: Object as PropType<Partial<typeof appConfig.ui.select>>,
default: () => appConfig.ui.select
default: () => ({})
},
uiMenu: {
type: Object as PropType<Partial<typeof appConfig.ui.selectMenu>>,
default: () => appConfig.ui.selectMenu
default: () => ({})
}
},
emits: ['update:modelValue', 'open', 'close', 'change'],
setup (props, { emit, slots }) {
setup (props, { emit, attrs, slots }) {
// TODO: Remove
const appConfig = useAppConfig()
const ui = computed<Partial<typeof appConfig.ui.select>>(() => defu({}, props.ui, appConfig.ui.select))
const uiMenu = computed<Partial<typeof appConfig.ui.selectMenu>>(() => defu({}, props.uiMenu, appConfig.ui.selectMenu))
const ui = computed<Partial<typeof appConfig.ui.select>>(() => defuTwMerge({}, props.ui, appConfig.ui.select))
const uiMenu = computed<Partial<typeof appConfig.ui.selectMenu>>(() => defuTwMerge({}, props.uiMenu, appConfig.ui.selectMenu))
const popper = computed<PopperOptions>(() => defu({}, props.popper, uiMenu.value.popper as PopperOptions))
@@ -311,10 +317,12 @@ export default defineComponent({
const query = ref('')
const searchInput = ref<ComponentPublicInstance<HTMLElement>>()
const wrapperClass = computed(() => twMerge(ui.value.wrapper, attrs.class as string))
const selectClass = computed(() => {
const variant = ui.value.color?.[color.value as string]?.[props.variant as string] || ui.value.variant[props.variant]
return classNames(
return twMerge(twJoin(
ui.value.base,
ui.value.rounded,
'text-left cursor-default',
@@ -325,7 +333,7 @@ export default defineComponent({
(isLeading.value || slots.leading) && ui.value.leading.padding[size.value],
(isTrailing.value || slots.trailing) && ui.value.trailing.padding[size.value],
'inline-flex items-center'
)
), props.selectClass)
})
const isLeading = computed(() => {
@@ -353,7 +361,7 @@ export default defineComponent({
})
const leadingWrapperIconClass = computed(() => {
return classNames(
return twJoin(
ui.value.icon.leading.wrapper,
ui.value.icon.leading.pointer,
ui.value.icon.leading.padding[size.value]
@@ -361,7 +369,7 @@ export default defineComponent({
})
const leadingIconClass = computed(() => {
return classNames(
return twJoin(
ui.value.icon.base,
appConfig.ui.colors.includes(color.value) && ui.value.icon.color.replaceAll('{color}', color.value),
ui.value.icon.size[size.value],
@@ -370,7 +378,7 @@ export default defineComponent({
})
const trailingWrapperIconClass = computed(() => {
return classNames(
return twJoin(
ui.value.icon.trailing.wrapper,
ui.value.icon.trailing.pointer,
ui.value.icon.trailing.padding[size.value]
@@ -378,7 +386,7 @@ export default defineComponent({
})
const trailingIconClass = computed(() => {
return classNames(
return twJoin(
ui.value.icon.base,
appConfig.ui.colors.includes(color.value) && ui.value.icon.color.replaceAll('{color}', color.value),
ui.value.icon.size[size.value],
@@ -429,12 +437,15 @@ export default defineComponent({
}
return {
attrs: omit(attrs, ['class']),
// eslint-disable-next-line vue/no-dupe-keys
uiMenu,
trigger,
container,
isLeading,
isTrailing,
wrapperClass,
// eslint-disable-next-line vue/no-dupe-keys
selectClass,
leadingIconName,
leadingIconClass,

View File

@@ -1,5 +1,5 @@
<template>
<div :class="ui.wrapper">
<div :class="wrapperClass">
<textarea
ref="textarea"
:value="modelValue"
@@ -10,7 +10,7 @@
:placeholder="placeholder"
class="form-textarea"
:class="textareaClass"
v-bind="$attrs"
v-bind="attrs"
@input="onInput"
@blur="onBlur"
/>
@@ -20,8 +20,9 @@
<script lang="ts">
import { ref, computed, watch, onMounted, nextTick, defineComponent } from 'vue'
import type { PropType } from 'vue'
import { defu } from 'defu'
import { classNames } from '../../utils'
import { omit } from 'lodash-es'
import { twMerge, twJoin } from 'tailwind-merge'
import { defuTwMerge } from '../../utils'
import { useFormGroup } from '../../composables/useFormGroup'
import { useAppConfig } from '#imports'
// TODO: Remove
@@ -97,19 +98,23 @@ export default defineComponent({
].includes(value)
}
},
textareaClass: {
type: String,
default: null
},
ui: {
type: Object as PropType<Partial<typeof appConfig.ui.textarea>>,
default: () => appConfig.ui.textarea
default: () => ({})
}
},
emits: ['update:modelValue', 'blur'],
setup (props, { emit }) {
setup (props, { emit, attrs }) {
const textarea = ref<HTMLTextAreaElement | null>(null)
// TODO: Remove
const appConfig = useAppConfig()
const ui = computed<Partial<typeof appConfig.ui.textarea>>(() => defu({}, props.ui, appConfig.ui.textarea))
const ui = computed<Partial<typeof appConfig.ui.textarea>>(() => defuTwMerge({}, props.ui, appConfig.ui.textarea))
const { emitFormBlur, emitFormInput, formGroup } = useFormGroup()
const color = computed(() => formGroup?.error?.value ? 'red' : props.color)
@@ -172,10 +177,12 @@ export default defineComponent({
}, 100)
})
const wrapperClass = computed(() => twMerge(ui.value.wrapper, attrs.class as string))
const textareaClass = computed(() => {
const variant = ui.value.color?.[color.value as string]?.[props.variant as string] || ui.value.variant[props.variant]
return classNames(
return twMerge(twJoin(
ui.value.base,
ui.value.rounded,
ui.value.placeholder,
@@ -183,13 +190,16 @@ export default defineComponent({
props.padded ? ui.value.padding[size.value] : 'p-0',
variant?.replaceAll('{color}', color.value),
!props.resize && 'resize-none'
)
), props.textareaClass)
})
return {
attrs: omit(attrs, ['class']),
// eslint-disable-next-line vue/no-dupe-keys
ui,
textarea,
wrapperClass,
// eslint-disable-next-line vue/no-dupe-keys
textareaClass,
onInput,
onBlur

View File

@@ -4,6 +4,7 @@
:name="name"
:disabled="disabled"
:class="switchClass"
v-bind="attrs"
>
<span :class="[active ? ui.container.active : ui.container.inactive, ui.container.base]">
<span v-if="onIcon" :class="[active ? ui.icon.active : ui.icon.inactive, ui.icon.base]" aria-hidden="true">
@@ -19,10 +20,11 @@
<script lang="ts">
import { computed, defineComponent } from 'vue'
import type { PropType } from 'vue'
import { defu } from 'defu'
import { Switch as HSwitch } from '@headlessui/vue'
import { omit } from 'lodash-es'
import { twMerge, twJoin } from 'tailwind-merge'
import UIcon from '../elements/Icon.vue'
import { classNames } from '../../utils'
import { defuTwMerge } from '../../utils'
import { useFormGroup } from '../../composables/useFormGroup'
import { useAppConfig } from '#imports'
// TODO: Remove
@@ -36,6 +38,7 @@ export default defineComponent({
HSwitch,
UIcon
},
inheritAttrs: false,
props: {
name: {
type: String,
@@ -66,15 +69,15 @@ export default defineComponent({
},
ui: {
type: Object as PropType<Partial<typeof appConfig.ui.toggle>>,
default: () => appConfig.ui.toggle
default: () => ({})
}
},
emits: ['update:modelValue'],
setup (props, { emit }) {
setup (props, { emit, attrs }) {
// TODO: Remove
const appConfig = useAppConfig()
const ui = computed<Partial<typeof appConfig.ui.toggle>>(() => defu({}, props.ui, appConfig.ui.toggle))
const ui = computed<Partial<typeof appConfig.ui.toggle>>(() => defuTwMerge({}, props.ui, appConfig.ui.toggle))
const { emitFormChange, formGroup } = useFormGroup()
const color = computed(() => formGroup?.error?.value ? 'red' : props.color)
@@ -90,27 +93,28 @@ export default defineComponent({
})
const switchClass = computed(() => {
return classNames(
return twMerge(twJoin(
ui.value.base,
ui.value.rounded,
ui.value.ring.replaceAll('{color}', color.value),
(active.value ? ui.value.active : ui.value.inactive).replaceAll('{color}', color.value)
)
), attrs.class as string)
})
const onIconClass = computed(() => {
return classNames(
return twJoin(
ui.value.icon.on.replaceAll('{color}', color.value)
)
})
const offIconClass = computed(() => {
return classNames(
return twJoin(
ui.value.icon.off.replaceAll('{color}', color.value)
)
})
return {
attrs: omit(attrs, ['class']),
// eslint-disable-next-line vue/no-dupe-keys
ui,
active,