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:
Sqrcz
2023-12-05 17:00:44 +01:00
committed by GitHub
parent 3a49323c8a
commit 32d9c4a5bc
4 changed files with 34 additions and 15 deletions

View File

@@ -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'

View File

@@ -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,
} }
} }

View File

@@ -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)

View File

@@ -0,0 +1,4 @@
import { twMerge } from 'tailwind-merge'
export const useMergeClasses = (componentClasses: string): string =>
twMerge(componentClasses)