ony3000 / prettier-plugin-classnames

A Prettier plugin that wraps verbose class name based on the `printWidth` option.
MIT License
91 stars 0 forks source link

Incorrect formatting of template interpolations #68

Open sxxov opened 1 week ago

sxxov commented 1 week ago

Thanks for the awesome plugin! It really is a must have.

Dependency information

"prettier": "^3.3.2",
"prettier-plugin-classnames": "^0.7.0",
"prettier-plugin-merge": "^0.7.0",
"prettier-plugin-tailwindcss": "^0.6.5",
"tailwindcss": "^3.4.4",

Steps to reproduce

Use a template literal with long interpolations

The current behavior

The plugin seems to override prettier's template tag formatting, collapsing whitespace & newlines inserted by prettier.

<Component
    className={`relative text-sm font-bold uppercase leading-[0.9] tracking-widest
        transition-all duration-500 ease-out hover:brightness-105 active:shadow-sm
        active:brightness-110 active:transition-none ${
        variant === 'underline'
                ? 'pb-2 text-accent-1 underline underline-offset-8 hover:underline-offset-[10px]'
                : `flex min-h-10 w-max min-w-28 items-center justify-between gap-2 overflow-hidden
                    rounded px-[max(1.5rem,_var(--radius))] py-5 text-center hover:shadow-md`
        } ${variant === 'default' && 'border border-bg-3 bg-bg-2 text-accent-1'} ${
        variant === 'primary' &&
        'active:accent-1 border border-accent-2 !bg-accent-1 text-bg-1' } ${ variant ===
        'outline' && 'border border-current !bg-transparent' } ${className}`}
>

The expected behavior

Something closer to what prettier output without the plugin, but with the classes inside still being formatted.

<Component
    className={`relative text-sm font-bold uppercase leading-[0.9] tracking-widest
        transition-all duration-500 ease-out hover:brightness-105 active:shadow-sm
        active:brightness-110 active:transition-none ${
            variant === 'underline'
                ? 'pb-2 text-accent-1 underline underline-offset-8 hover:underline-offset-[10px]'
                : `flex min-h-10 w-max min-w-28 items-center justify-between gap-2 overflow-hidden
                    rounded px-[max(1.5rem,_var(--radius))] py-5 text-center hover:shadow-md`
        } ${
            variant === 'default' &&
                'border border-bg-3 bg-bg-2 text-accent-1'
        } ${
            variant === 'primary' &&
                'active:accent-1 border border-accent-2 !bg-accent-1 text-bg-1'
        } ${
            variant === 'outline' &&
                'border border-current !bg-transparent'
        } ${className}`}
>
ony3000 commented 1 week ago

Hello! Thanks for reporting the issue!

Formatting nested expressions is a challenging problem. :dizzy_face:

In the current logic, formatted inner class names are temporarily frozen until formatting of the outermost class names is complete. Since these pieces of “ice” are treated as a single class name from the perspective of formatting the outer class name, some whitespace characters may be collapsed.

I will think about ways to make the output better. Until this issue is fixed, you can work around it using one of the following methods:

  1. Use utility libraries such as classnames or clsx.

    import classNames from 'classnames';
    
    <Component
    className={classNames(
        `relative text-sm font-bold uppercase leading-[0.9] tracking-widest
        transition-all duration-500 ease-out hover:brightness-105 active:shadow-sm
        active:brightness-110 active:transition-none`,
        {
            'pb-2 text-accent-1 underline underline-offset-8 hover:underline-offset-[10px]':
                variant === 'underline',
            [`flex min-h-10 w-max min-w-28 items-center justify-between gap-2 overflow-hidden
            rounded px-[max(1.5rem,_var(--radius))] py-5 text-center hover:shadow-md`]:
                variant !== 'underline',
            'border border-bg-3 bg-bg-2 text-accent-1':
                variant === 'default',
            'active:accent-1 border border-accent-2 !bg-accent-1 text-bg-1':
                variant === 'primary',
            'border border-current !bg-transparent':
                variant === 'outline',
        },
        className,
    )}
    >
  2. You can also use class-variance-authority.

    import { cva } from 'class-variance-authority';
    
    const yourComponentVariants = cva('', {
    variants: {
        variant: {
            default: 'border border-bg-3 bg-bg-2 text-accent-1',
            primary: 'active:accent-1 border border-accent-2 !bg-accent-1 text-bg-1',
            outline: 'border border-current !bg-transparent',
            undeline: 'pb-2 text-accent-1 underline underline-offset-8 hover:underline-offset-[10px]',
        },
    },
    compoundVariants: [
        {
            variant: ['default', 'primary', 'outline'],
            className: 'flex min-h-10 w-max min-w-28 items-center justify-between gap-2 overflow-hidden rounded px-[max(1.5rem,_var(--radius))] py-5 text-center hover:shadow-md',
        },
    ],
    });
    
    <Component
    className={`relative text-sm font-bold uppercase leading-[0.9] tracking-widest
        transition-all duration-500 ease-out hover:brightness-105 active:shadow-sm
        active:brightness-110 active:transition-none ${yourComponentVariants({ variant,
        })} ${className}`}
    >