Feature/twmerge as composable (#250)
* twMerge extracted to global composable + support for user classes in FwbButton * useMergeClasses added to Navbar * css class as prop * NavBar component updated to use class prop
This commit is contained in:
@@ -77,12 +77,14 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, resolveComponent, toRefs } from 'vue'
|
import { computed, resolveComponent, toRefs } from 'vue'
|
||||||
|
import { useMergeClasses } from '@/composables/useMergeClasses'
|
||||||
import FwbSpinner from '../FwbSpinner/FwbSpinner.vue'
|
import FwbSpinner from '../FwbSpinner/FwbSpinner.vue'
|
||||||
import { useButtonClasses } from './composables/useButtonClasses'
|
import { useButtonClasses } from './composables/useButtonClasses'
|
||||||
import { useButtonSpinner } from './composables/useButtonSpinner'
|
import { useButtonSpinner } from './composables/useButtonSpinner'
|
||||||
import type { ButtonGradient, ButtonMonochromeGradient, ButtonSize, ButtonVariant } from './types'
|
import type { ButtonGradient, ButtonMonochromeGradient, ButtonSize, ButtonVariant } from './types'
|
||||||
|
|
||||||
interface IButtonProps {
|
interface IButtonProps {
|
||||||
|
class?: string
|
||||||
color?: ButtonVariant
|
color?: ButtonVariant
|
||||||
gradient?: ButtonGradient | null
|
gradient?: ButtonGradient | null
|
||||||
size?: ButtonSize
|
size?: ButtonSize
|
||||||
@@ -97,6 +99,7 @@ interface IButtonProps {
|
|||||||
tag?: string
|
tag?: string
|
||||||
}
|
}
|
||||||
const props = withDefaults(defineProps<IButtonProps>(), {
|
const props = withDefaults(defineProps<IButtonProps>(), {
|
||||||
|
class: '',
|
||||||
color: 'default',
|
color: 'default',
|
||||||
gradient: null,
|
gradient: null,
|
||||||
size: 'md',
|
size: 'md',
|
||||||
@@ -111,12 +114,15 @@ const props = withDefaults(defineProps<IButtonProps>(), {
|
|||||||
tag: 'a',
|
tag: 'a',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const buttonClasses = useButtonClasses(toRefs(props))
|
||||||
|
const wrapperClasses = computed(() => useMergeClasses(buttonClasses.wrapperClasses))
|
||||||
|
const spanClasses = computed(() => useMergeClasses(buttonClasses.spanClasses))
|
||||||
|
|
||||||
const isOutlineGradient = computed(() => props.outline && props.gradient)
|
const isOutlineGradient = computed(() => props.outline && props.gradient)
|
||||||
|
|
||||||
const loadingPrefix = computed(() => props.loading && props.loadingPosition === 'prefix')
|
const loadingPrefix = computed(() => props.loading && props.loadingPosition === 'prefix')
|
||||||
const loadingSuffix = computed(() => props.loading && props.loadingPosition === 'suffix')
|
const loadingSuffix = computed(() => props.loading && props.loadingPosition === 'suffix')
|
||||||
|
|
||||||
const { wrapperClasses, spanClasses } = useButtonClasses(toRefs(props))
|
|
||||||
const { color: spinnerColor, size: spinnerSize } = useButtonSpinner(toRefs(props))
|
const { color: spinnerColor, size: spinnerSize } = useButtonSpinner(toRefs(props))
|
||||||
|
|
||||||
const linkComponent = props.tag !== 'a' ? resolveComponent(props.tag) : 'a'
|
const linkComponent = props.tag !== 'a' ? resolveComponent(props.tag) : 'a'
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { computed, type Ref, useSlots } from 'vue'
|
import { computed, type Ref, useSlots } from 'vue'
|
||||||
import { twMerge } from 'tailwind-merge'
|
|
||||||
import type { ButtonDuotoneGradient, ButtonGradient, ButtonMonochromeGradient, ButtonSize, ButtonVariant } from '../types'
|
import type { ButtonDuotoneGradient, ButtonGradient, ButtonMonochromeGradient, ButtonSize, ButtonVariant } from '../types'
|
||||||
|
|
||||||
export type ButtonClassMap<T extends string> = { hover: Record<T, string>; default: Record<T, string> }
|
export type ButtonClassMap<T extends string> = { hover: Record<T, string>; default: Record<T, string> }
|
||||||
@@ -146,6 +145,7 @@ const buttonShadowClasses: Record<ButtonMonochromeGradient, string> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type UseButtonClassesProps = {
|
export type UseButtonClassesProps = {
|
||||||
|
class: Ref<string>
|
||||||
pill: Ref<boolean>
|
pill: Ref<boolean>
|
||||||
disabled: Ref<boolean>
|
disabled: Ref<boolean>
|
||||||
loading: Ref<boolean>
|
loading: Ref<boolean>
|
||||||
@@ -160,7 +160,7 @@ export type UseButtonClassesProps = {
|
|||||||
const simpleGradients = ['blue', 'green', 'cyan', 'teal', 'lime', 'red', 'pink', 'purple']
|
const simpleGradients = ['blue', 'green', 'cyan', 'teal', 'lime', 'red', 'pink', 'purple']
|
||||||
const alternativeColors = ['alternative', 'light']
|
const alternativeColors = ['alternative', 'light']
|
||||||
|
|
||||||
export function useButtonClasses (props: UseButtonClassesProps): { wrapperClasses: Ref<string>; spanClasses: Ref<string> } {
|
export function useButtonClasses (props: UseButtonClassesProps): { wrapperClasses: string; spanClasses: string } {
|
||||||
const slots = useSlots()
|
const slots = useSlots()
|
||||||
|
|
||||||
const sizeClasses = computed(() => {
|
const sizeClasses = computed(() => {
|
||||||
@@ -223,7 +223,7 @@ export function useButtonClasses (props: UseButtonClassesProps): { wrapperClasse
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return twMerge(
|
return [
|
||||||
backgroundClass,
|
backgroundClass,
|
||||||
hoverClass,
|
hoverClass,
|
||||||
shadowClass,
|
shadowClass,
|
||||||
@@ -231,23 +231,24 @@ export function useButtonClasses (props: UseButtonClassesProps): { wrapperClasse
|
|||||||
props.disabled.value && 'cursor-not-allowed opacity-50',
|
props.disabled.value && 'cursor-not-allowed opacity-50',
|
||||||
isGradient && isOutline ? 'p-0.5' : sizeClasses.value,
|
isGradient && isOutline ? 'p-0.5' : sizeClasses.value,
|
||||||
(slots.prefix || slots.suffix || props.loading.value) && 'inline-flex items-center',
|
(slots.prefix || slots.suffix || props.loading.value) && 'inline-flex items-center',
|
||||||
)
|
props.class.value,
|
||||||
|
].filter((str) => (str)).join(' ')
|
||||||
})
|
})
|
||||||
|
|
||||||
const spanClasses = computed(() => {
|
const spanClasses = computed(() => {
|
||||||
if (!!props.gradient.value && props.outline.value) {
|
if (!!props.gradient.value && props.outline.value) {
|
||||||
// ONLY FOR GRADIENT OUTLINE BUTTON
|
// ONLY FOR GRADIENT OUTLINE BUTTON
|
||||||
return twMerge(
|
return [
|
||||||
'relative bg-white dark:bg-gray-900 rounded-md inline-flex items-center',
|
'relative bg-white dark:bg-gray-900 rounded-md inline-flex items-center',
|
||||||
sizeClasses.value,
|
sizeClasses.value,
|
||||||
!props.disabled.value ? 'group-hover:bg-opacity-0 transition-all ease-in duration-75' : '',
|
!props.disabled.value ? 'group-hover:bg-opacity-0 transition-all ease-in duration-75' : '',
|
||||||
)
|
].filter((str) => (str)).join(' ')
|
||||||
}
|
}
|
||||||
return ''
|
return ''
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
wrapperClasses: bindClasses,
|
wrapperClasses: bindClasses.value,
|
||||||
spanClasses,
|
spanClasses: spanClasses.value,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,9 +41,13 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref, useSlots } from 'vue'
|
import { computed, ref, useSlots } from 'vue'
|
||||||
import { breakpointsTailwind, useBreakpoints, useToggle } from '@vueuse/core'
|
import { breakpointsTailwind, useBreakpoints, useToggle } from '@vueuse/core'
|
||||||
import classNames from 'classnames'
|
import { useMergeClasses } from '@/composables/useMergeClasses'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
class: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
sticky: {
|
sticky: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
@@ -57,6 +61,7 @@ const props = defineProps({
|
|||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const slots = useSlots()
|
const slots = useSlots()
|
||||||
|
|
||||||
const breakpoints = useBreakpoints(breakpointsTailwind)
|
const breakpoints = useBreakpoints(breakpointsTailwind)
|
||||||
@@ -69,11 +74,14 @@ const navbarRoundedClasses = 'rounded'
|
|||||||
const navbarSolidClasses = 'p-3 bg-gray-50 dark:bg-gray-800 dark:border-gray-700'
|
const navbarSolidClasses = 'p-3 bg-gray-50 dark:bg-gray-800 dark:border-gray-700'
|
||||||
const navbarWhiteClasses = 'bg-white px-2 sm:px-4 py-2.5 dark:bg-gray-900'
|
const navbarWhiteClasses = 'bg-white px-2 sm:px-4 py-2.5 dark:bg-gray-900'
|
||||||
|
|
||||||
const navbarClasses = computed(() => classNames(
|
const navbarClasses = computed(() => useMergeClasses(
|
||||||
|
[
|
||||||
navbarBaseClasses,
|
navbarBaseClasses,
|
||||||
props.sticky ? navbarFloatClasses : '',
|
props.sticky ? navbarFloatClasses : '',
|
||||||
props.rounded ? navbarRoundedClasses : '',
|
props.rounded ? navbarRoundedClasses : '',
|
||||||
props.solid ? navbarSolidClasses : navbarWhiteClasses,
|
props.solid ? navbarSolidClasses : navbarWhiteClasses,
|
||||||
|
props.class,
|
||||||
|
].join(' '),
|
||||||
))
|
))
|
||||||
|
|
||||||
const isShowMenu = computed(() => (!isMobile)
|
const isShowMenu = computed(() => (!isMobile)
|
||||||
|
|||||||
4
src/composables/useMergeClasses.ts
Normal file
4
src/composables/useMergeClasses.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
|
||||||
|
export const useMergeClasses = (componentClasses: string): string =>
|
||||||
|
twMerge(componentClasses)
|
||||||
Reference in New Issue
Block a user