0

I was trying to pass a custom css class to tailwindCSS through a template literal. However, being the component rendered on Storybook, this doesn't seem to work due to some conflict. Instead, I'd like to pass a computed property, but I'm not sure how to write it in the most scalable way, since maybe there will be more css properties in the future.

This doesn't work:

    const textSize = computed(() => { return `text-${props.textSize}`}

This works but it's pretty ugly:

    const textSize = computed(() => {
    return props.textSize === "xs"
        ? "text-xs"
        : props.textSize === "sm"
        ? "text-sm"
        : props.textSize === "base"
        ? "text-base"
        : props.textSize === "lg"
        ? "text-lg"
        : props.textSize === "xl"
        ? "text-xl"
        : props.textSize === "2xl"
        ? "text-2xl"
        : "text-3xl"
})

The whole component:

<template>
    <div :class="[`h-full w-full p-2`, !props.isDisabled || `opacity-30`]">
        <div
            :class="[
                'flex relative items-center h-full w-full',
                props.checkBoxType === 'reverse-between' ? 'justify-between' : 'justify-start',
                props.direction === 'vertical' ? 'flex-col justify-start gap-2' : 'items-center',
            ]"
        >
            <input
                type="checkbox"
                v-model="checked"
                :class="[
                    'absolute z-50 order-1 w-6 h-6 opacity-0',
                    props.checkBoxType === 'reverse-between' && ' right-0',
                    props.checkBoxType === 'standard' ? 'order-0' : 'order-1',
                    props.direction === 'vertical' && 'top-0 right-4',
                ]"
                :disabled="props.isDisabled"
            />
            <div
                :class="[
                    'bg-white w-6 h-6 flex flex-shrink-0 justify-center items-center p-1 border-2 border-gray',
                    props.checkBoxType === 'standard' && props.direction === 'horizontal' && 'order-0 mr-2',
                    props.checkBoxType === 'reverse-between' && props.direction === 'horizontal' && 'order-1 ml-2',
                    props.direction === 'vertical' && 'mr-0',
                    inputShape === 'square' ? 'rounded' : 'rounded-full',
                    props.isToggleEnabled || checked ? 'border-3' : 'border-1',
                ]"
            >
                <div
                    :class="[
                        'bg-gray w-3 h-3',
                        props.inputShape === 'circle' ? 'rounded-full' : 'rounded-sm',
                        props.isToggleEnabled || checked ? 'visible' : 'hidden',
                    ]"
                ></div>
            </div>
            <div
                :class="[
                    'select-none font-base flex justify-center items-center',
                    props.checkBoxType === 'standard' ? 'order-1' : 'order-0',
                    props.direction === 'vertical' && 'text-center',
                ]"
            >
                <span :class="[checked || isToggleEnabled ? 'font-bold' : 'font-normal', 'text-gray', textSize]">{{
                    props.label
                }}</span>
            </div>
        </div>
    </div>
</template>

<script lang="ts" setup>
    import { defineProps, ref, computed } from "vue"

    const checked = ref(false)
    const textSize = computed(() => {
        return props.textSize === "xs"
            ? "text-xs"
            : props.textSize === "sm"
            ? "text-sm"
            : props.textSize === "base"
            ? "text-base"
            : props.textSize === "lg"
            ? "text-lg"
            : props.textSize === "xl"
            ? "text-xl"
            : props.textSize === "2xl"
            ? "text-2xl"
            : "text-3xl"
    })

    const props = defineProps({
        /**
         * Sets the label for the input element
         */
        label: {
            type: String,
            default: "example",
        },
        /**
         * Changes the layout between text and checkbox button
         */
        checkBoxType: {
            default: "standard",
        },
        /**
         * Sets the shape of the checkbox as squared or circled
         */
        inputShape: {
            default: "square",
        },
        /**
         * Sets the direction of the component as horizontal or vertical
         */
        direction: {
            default: "horizontal",
        },
        /**
         * Sets the text size
         */
        textSize: {
            // type: String as PropType<FontSize>,
            type: String,
            default: "base",
        },
        /**
         * Toggles or untoggles the checkbox
         */
        isToggleEnabled: {
            type: Boolean,
        },
        /**
         * If false, the whole component is greyed out
         */
        isDisabled: {
            type: Boolean,
            default: false,
        },
    })
</script>


juzello
  • 231
  • 3
  • 16
  • 2
    Interpolation of class names is indeed not feasible in Tailwind. Otherwise, even if it's ugly it's pretty much the clean way to do things. You could create yourself some kind of helpful to make it more pretty. – kissu Jun 29 '22 at 09:34
  • Thank you for your answer. Yes, that's what I was fearing. Glad I had the right approach tho, thank you! – juzello Jun 29 '22 at 09:36
  • This question could help: https://stackoverflow.com/questions/69687530/dynamically-build-classnames-in-tailwindcss You could use the `safeList` option to never purge the possible classes. – Kapcash Jun 29 '22 at 09:47
  • There are maybe some packages who do that in a more abstract way. I didn't used [Stitches](https://stitches.dev/) yet, nor [UnoCSS](https://uno.antfu.me/) but they may have a nice modern approach regarding CSS nowadays. I didn't dig into that myself yet. – kissu Jun 29 '22 at 09:48
  • @Kapcash this breaks the whole idea of having something performant tho. Not sure that it's a wished side-effect when using TW. – kissu Jun 29 '22 at 09:49
  • The point is that having to fill an extra object (or a swtich statement, etc) kinda ruins the whole scalability of the thing. But I guess there's not really another option – juzello Jun 29 '22 at 09:51
  • 1
    @kissu sure! They mention on the documentation the `safeList` is a last resort option. I'm just pointing possible solutions for this case :) – Kapcash Jun 29 '22 at 09:56
  • 1
    Having `fast` + `reusable` + `small amount of code` at the same time is quite difficult. Pick 2 haha. – kissu Jun 29 '22 at 09:58

1 Answers1

1

As @kissu mentioned in his comment Interpolation of class names is indeed not feasible in Tailwind , so you need to create a clean object with prop value as field and the class name as value :

const textSizes = {
    'xs':'text-xs',
    'sm':'text-sm',
    'base':'text-base',
    'lg':'text-lg',
    'xl':'text-xl',
    '2xl':'text-2xl',
    '3xl':'text-3xl',
}

   const textSize = computed(() => textSizes[props.textSize])
Boussadjra Brahim
  • 82,684
  • 19
  • 144
  • 164
  • 1
    Here is the reference regarding the interpolation: https://tailwindcss.com/docs/content-configuration#class-detection-in-depth – kissu Jun 29 '22 at 09:42