tailwindcss-advanced-components

安装量: 102
排名: #8200

安装

npx skills add https://github.com/josiahsiegel/claude-plugin-marketplace --skill tailwindcss-advanced-components

Tailwind CSS Advanced Component Patterns Component Variants with CVA Class Variance Authority Integration npm install class-variance-authority

// components/Button.tsx import { cva, type VariantProps } from 'class-variance-authority'; import { cn } from '@/lib/utils';

const buttonVariants = cva( // Base styles 'inline-flex items-center justify-center rounded-lg font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50', { variants: { variant: { default: 'bg-brand-500 text-white hover:bg-brand-600 focus-visible:ring-brand-500', secondary: 'bg-gray-100 text-gray-900 hover:bg-gray-200 focus-visible:ring-gray-500', outline: 'border border-gray-300 bg-transparent hover:bg-gray-100 focus-visible:ring-gray-500', ghost: 'hover:bg-gray-100 focus-visible:ring-gray-500', destructive: 'bg-red-500 text-white hover:bg-red-600 focus-visible:ring-red-500', link: 'text-brand-500 underline-offset-4 hover:underline', }, size: { sm: 'h-8 px-3 text-sm', md: 'h-10 px-4 text-sm', lg: 'h-12 px-6 text-base', xl: 'h-14 px-8 text-lg', icon: 'h-10 w-10', }, fullWidth: { true: 'w-full', }, }, compoundVariants: [ { variant: 'outline', size: 'sm', className: 'border', }, { variant: 'outline', size: ['md', 'lg', 'xl'], className: 'border-2', }, ], defaultVariants: { variant: 'default', size: 'md', }, } );

export interface ButtonProps extends React.ButtonHTMLAttributes, VariantProps { asChild?: boolean; }

export function Button({ className, variant, size, fullWidth, asChild = false, ...props }: ButtonProps) { return (

Compound Components Pattern Context-Based Component System // components/Card/index.tsx import { createContext, useContext, forwardRef } from 'react'; import { cn } from '@/lib/utils';

// Context for shared state const CardContext = createContext<{ variant?: 'default' | 'elevated' | 'outline' }>({});

// Root component interface CardProps extends React.HTMLAttributes { variant?: 'default' | 'elevated' | 'outline'; }

const Card = forwardRef( ({ className, variant = 'default', children, ...props }, ref) => { const variants = { default: 'bg-white border border-gray-200', elevated: 'bg-white shadow-lg', outline: 'bg-transparent border-2 border-gray-300', };

return (
  <CardContext.Provider value={{ variant }}>
    <div
      ref={ref}
      className={cn(
        'rounded-xl',
        variants[variant],
        className
      )}
      {...props}
    >
      {children}
    </div>
  </CardContext.Provider>
);

} );

// Sub-components const CardHeader = forwardRef>( ({ className, ...props }, ref) => (

) );

const CardTitle = forwardRef>( ({ className, ...props }, ref) => (

) );

const CardDescription = forwardRef>( ({ className, ...props }, ref) => (

) );

const CardContent = forwardRef>( ({ className, ...props }, ref) => (

) );

const CardFooter = forwardRef>( ({ className, ...props }, ref) => (

) );

// Named exports Card.displayName = 'Card'; CardHeader.displayName = 'CardHeader'; CardTitle.displayName = 'CardTitle'; CardDescription.displayName = 'CardDescription'; CardContent.displayName = 'CardContent'; CardFooter.displayName = 'CardFooter';

export { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter };

Usage Account Settings Manage your account preferences

...

Data Attribute Variants CSS-Based State Management / Define data attribute variants / @custom-variant data-state-open (&[data-state="open"]); @custom-variant data-state-closed (&[data-state="closed"]); @custom-variant data-side-top (&[data-side="top"]); @custom-variant data-side-bottom (&[data-side="bottom"]); @custom-variant data-side-left (&[data-side="left"]); @custom-variant data-side-right (&[data-side="right"]); @custom-variant data-highlighted (&[data-highlighted]); @custom-variant data-disabled (&[data-disabled]);

// Dropdown component using data attributes function DropdownContent({ children, ...props }) { return ( <div data-state={isOpen ? 'open' : 'closed'} data-side={side} className=" absolute z-50 min-w-[8rem] overflow-hidden rounded-md border border-gray-200 bg-white p-1 shadow-lg

    data-state-open:animate-in
    data-state-open:fade-in-0
    data-state-open:zoom-in-95

    data-state-closed:animate-out
    data-state-closed:fade-out-0
    data-state-closed:zoom-out-95

    data-side-top:slide-in-from-bottom-2
    data-side-bottom:slide-in-from-top-2
    data-side-left:slide-in-from-right-2
    data-side-right:slide-in-from-left-2
  "
  {...props}
>
  {children}
</div>

); }

function DropdownItem({ children, disabled, ...props }) { return ( <div data-highlighted={isHighlighted || undefined} data-disabled={disabled || undefined} className=" relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none

    data-highlighted:bg-gray-100
    data-disabled:pointer-events-none
    data-disabled:opacity-50
  "
  {...props}
>
  {children}
</div>

); }

Group and Peer Patterns Complex State Propagation

Title

Description

Card content

Slot Pattern with @apply Reusable Component Slots @layer components { / Base dialog structure / .dialog { @apply fixed inset-0 z-50 flex items-center justify-center; }

.dialog-overlay { @apply fixed inset-0 bg-black/50 backdrop-blur-sm; @apply data-state-open:animate-in data-state-open:fade-in-0; @apply data-state-closed:animate-out data-state-closed:fade-out-0; }

.dialog-content { @apply relative z-50 w-full max-w-lg rounded-xl bg-white p-6 shadow-xl; @apply data-state-open:animate-in data-state-open:fade-in-0 data-state-open:zoom-in-95; @apply data-state-closed:animate-out data-state-closed:fade-out-0 data-state-closed:zoom-out-95; }

.dialog-header { @apply flex flex-col gap-1.5 text-center sm:text-left; }

.dialog-title { @apply text-lg font-semibold leading-none tracking-tight; }

.dialog-description { @apply text-sm text-gray-500; }

.dialog-footer { @apply flex flex-col-reverse gap-2 sm:flex-row sm:justify-end; }

.dialog-close { @apply absolute right-4 top-4 rounded-sm opacity-70 hover:opacity-100; @apply focus:outline-none focus:ring-2 focus:ring-offset-2; } }

Polymorphic Components "as" Prop Pattern import { forwardRef, ElementType, ComponentPropsWithoutRef } from 'react'; import { cn } from '@/lib/utils';

type PolymorphicRef = ComponentPropsWithoutRef['ref'];

type PolymorphicComponentProps = Props & { as?: C; className?: string; children?: React.ReactNode; } & Omit, 'as' | 'className' | keyof Props>;

// Text component that can be any element interface TextProps { size?: 'xs' | 'sm' | 'base' | 'lg' | 'xl' | '2xl'; weight?: 'normal' | 'medium' | 'semibold' | 'bold'; color?: 'default' | 'muted' | 'accent'; }

type TextComponent = ( props: PolymorphicComponentProps & { ref?: PolymorphicRef } ) => React.ReactElement | null;

export const Text: TextComponent = forwardRef( ( { as, size = 'base', weight = 'normal', color = 'default', className, children, ...props }: PolymorphicComponentProps, ref?: PolymorphicRef ) => { const Component = as || 'span';

const sizes = {
  xs: 'text-xs',
  sm: 'text-sm',
  base: 'text-base',
  lg: 'text-lg',
  xl: 'text-xl',
  '2xl': 'text-2xl',
};

const weights = {
  normal: 'font-normal',
  medium: 'font-medium',
  semibold: 'font-semibold',
  bold: 'font-bold',
};

const colors = {
  default: 'text-gray-900',
  muted: 'text-gray-500',
  accent: 'text-brand-500',
};

return (
  <Component
    ref={ref}
    className={cn(sizes[size], weights[weight], colors[color], className)}
    {...props}
  >
    {children}
  </Component>
);

} );

Usage Default span Large muted paragraph Bold heading Accent link

Headless Component Integration Headless UI with Tailwind import { Dialog, Transition } from '@headlessui/react'; import { Fragment } from 'react';

function Modal({ isOpen, onClose, title, children }) { return (

{/ Backdrop /}

    <div className="fixed inset-0 overflow-y-auto">
      <div className="flex min-h-full items-center justify-center p-4">
        <Transition.Child
          as={Fragment}
          enter="ease-out duration-300"
          enterFrom="opacity-0 scale-95"
          enterTo="opacity-100 scale-100"
          leave="ease-in duration-200"
          leaveFrom="opacity-100 scale-100"
          leaveTo="opacity-0 scale-95"
        >
          <Dialog.Panel className="
            w-full max-w-md transform overflow-hidden rounded-2xl
            bg-white p-6 text-left align-middle shadow-xl transition-all
          ">
            <Dialog.Title className="text-lg font-medium leading-6 text-gray-900">
              {title}
            </Dialog.Title>
            <div className="mt-2">
              {children}
            </div>
          </Dialog.Panel>
        </Transition.Child>
      </div>
    </div>
  </Dialog>
</Transition>

); }

Radix UI with Tailwind import * as DropdownMenu from '@radix-ui/react-dropdown-menu';

function Dropdown() { return ( Options

  <DropdownMenu.Portal>
    <DropdownMenu.Content
      className="
        min-w-[200px] rounded-md bg-white p-1 shadow-lg
        border border-gray-200
        animate-in fade-in-0 zoom-in-95
        data-[side=bottom]:slide-in-from-top-2
        data-[side=top]:slide-in-from-bottom-2
      "
      sideOffset={5}
    >
      <DropdownMenu.Item className="
        relative flex cursor-pointer select-none items-center
        rounded-sm px-2 py-2 text-sm outline-none
        data-[highlighted]:bg-gray-100
      ">
        Profile
      </DropdownMenu.Item>

      <DropdownMenu.Separator className="my-1 h-px bg-gray-200" />

      <DropdownMenu.Item className="
        relative flex cursor-pointer select-none items-center
        rounded-sm px-2 py-2 text-sm text-red-600 outline-none
        data-[highlighted]:bg-red-50
      ">
        Delete
      </DropdownMenu.Item>
    </DropdownMenu.Content>
  </DropdownMenu.Portal>
</DropdownMenu.Root>

); }

Animation Patterns Staggered Animations function StaggeredList({ items }) { return (

    {items.map((item, index) => (
  • ${index * 100}ms }} > {item.content}
  • ))}
); }

Skeleton Loading function Skeleton({ className, ...props }) { return (

); }

function CardSkeleton() { return (

); }

Best Practices 1. Use cn() Utility for Class Merging // lib/utils.ts import { clsx, type ClassValue } from 'clsx'; import { twMerge } from 'tailwind-merge';

export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); }

  1. Extract Common Patterns @layer components { / Consistent focus ring / .focus-ring { @apply focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-500 focus-visible:ring-offset-2; }

/ Consistent disabled state / .disabled-state { @apply disabled:pointer-events-none disabled:opacity-50; }

/ Truncate text / .truncate-lines-2 { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } }

  1. Document Component APIs /**
  2. Button component with multiple variants *
  3. @example
  4. *
  5. @example
  6. */ export function Button({ ... }) { ... }

  7. Test Component Variants // Button.test.tsx describe('Button', () => { it('renders all variants correctly', () => { const variants = ['default', 'secondary', 'outline', 'ghost', 'destructive'];

    variants.forEach(variant => { render(); // Assert classes are applied correctly }); }); });

返回排行榜