sek-consulting / solid-ui

Beautifully designed components. Built with Kobalte & corvu. Styled with Tailwind CSS.
https://www.solid-ui.com
MIT License
832 stars 32 forks source link

Maybe use `tailwind-variants` instead of `clsx` & `cva` combo #21

Closed carere closed 11 months ago

carere commented 11 months ago

I recently discover tailwind-variants which is a pretty nice library. It replaces clsx, cva & twMerge and add more tools for building Design System with tailwind. "Why Tailwind Variants" Worth noting that kobalte/pigment is already using it :) What about switching to it ?

sek-consulting commented 11 months ago

Thanks for your suggestion. :) I'll have a look at it and see how it performs in comparison. We use clsx/cva because shadcn started with it and all ports just adopted the feature/utility class.

carere commented 11 months ago

@sek-consulting For example here is your implementation of dialog component with tailwind-variants.

import { Dialog as DialogPrimitive } from "@kobalte/core";
import { type Component, type ComponentProps, splitProps } from "solid-js";
import FaIcon from "./fa-icon.tsx";
import { tv } from "tailwind-variants";

const dialog = tv({
  slots: {
    portal:
      "flex fixed inset-0 z-50 justify-center items-start sm:items-center",
    overlay:
      "fixed inset-0 z-50 bg-background/80 backdrop-blur-sm transition-all duration-100 data-[state=closed]:animate-out data-[state=closed]:fade-out data-[state=open]:fade-in",
    content:
      "fixed z-50 grid w-full gap-4 rounded-b-lg border bg-background p-6 shadow-lg animate-in data-[state=open]:fade-in-90 data-[state=open]:slide-in-from-bottom-10 sm:max-w-lg sm:rounded-lg sm:zoom-in-90 data-[state=open]:sm:slide-in-from-bottom-0",
    close:
      "absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground",
    header: "flex flex-col space-y-1.5 text-center sm:text-left",
    footer: "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
    title: "text-lg font-semibold leading-none tracking-tight",
    description: "text-sm text-muted-foreground",
  },
});

const { portal, overlay, content, close, header, footer, title, description } =
  dialog();

const Dialog = DialogPrimitive.Root;

const DialogTrigger: Component<DialogPrimitive.DialogTriggerProps> = (
  props,
) => {
  const [, rest] = splitProps(props, ["children"]);
  return (
    <DialogPrimitive.Trigger {...rest}>
      {props.children}
    </DialogPrimitive.Trigger>
  );
};

const DialogPortal: Component<DialogPrimitive.DialogPortalProps> = (props) => {
  const [, rest] = splitProps(props, ["children"]);
  return (
    <DialogPrimitive.Portal {...rest}>
      <div class={portal()}>
        {props.children}
      </div>
    </DialogPrimitive.Portal>
  );
};

const DialogOverlay: Component<DialogPrimitive.DialogOverlayProps> = (
  props,
) => {
  const [, rest] = splitProps(props, ["class"]);
  return (
    <DialogPrimitive.Overlay
      class={overlay({ class: props.class })}
      {...rest}
    />
  );
};

const DialogContent: Component<DialogPrimitive.DialogContentProps> = (
  props,
) => {
  const [, rest] = splitProps(props, ["class", "children"]);
  return (
    <DialogPortal>
      <DialogOverlay />
      <DialogPrimitive.Content
        class={content({ class: props.class })}
        {...rest}
      >
        {props.children}
        <DialogPrimitive.CloseButton class={close()}>
          <FaIcon name="xmark" style="Regular" class="w-4 h-4" />
          <span class="sr-only">Close</span>
        </DialogPrimitive.CloseButton>
      </DialogPrimitive.Content>
    </DialogPortal>
  );
};

const DialogHeader: Component<ComponentProps<"div">> = (props) => {
  const [, rest] = splitProps(props, ["class"]);
  return <div class={header({ class: props.class })} {...rest} />;
};

const DialogFooter: Component<ComponentProps<"div">> = (props) => {
  const [, rest] = splitProps(props, ["class"]);
  return <div class={footer({ class: props.class })} {...rest} />;
};

const DialogTitle: Component<DialogPrimitive.DialogTitleProps> = (props) => {
  const [, rest] = splitProps(props, ["class"]);
  return (
    <DialogPrimitive.Title class={title({ class: props.class })} {...rest} />
  );
};

const DialogDescription: Component<DialogPrimitive.DialogDescriptionProps> = (
  props,
) => {
  const [, rest] = splitProps(props, ["class"]);
  return (
    <DialogPrimitive.Description
      class={description({ class: props.class })}
      {...rest}
    />
  );
};

export {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
};
sek-consulting commented 11 months ago

Thanks for the example. But after consulting with the Michael we'll stick with cva since we don't need all the extra features and we don't really want to completely seperate the styling from the components.