Coder Social home page Coder Social logo

ecklf / tailwindcss-radix Goto Github PK

View Code? Open in Web Editor NEW
2.0K 8.0 64.0 3.1 MB

Utilities and variants for styling Radix state

Home Page: https://tailwindcss-radix.vercel.app

License: MIT License

TypeScript 99.64% Shell 0.36%
tailwindcss plugin radix react

tailwindcss-radix's Introduction

Utilities and variants for styling Radix state

tailwindcss v3 ready npm version npm downloads

What is this?

The main purpose of this library is adding classnames for accessing Radix data attributes, which gains you the benefit of auto-completion compared to using data-* variants.

TL;DR It's @headlessui-tailwindcss for Radix.

Installation

pnpm add tailwindcss-radix

Demo

Click on the banner to check out the demo components. You can find the code inside the demo folder.

Usage

Add the plugin to your plugins array:

module.exports = {
  theme: {
    // --snip--
  },
  variants: {
    // --snip--
  },
  plugins: [
    // Initialize with default values (see options below)
    require("tailwindcss-radix")(),
  ],
};

Options

require("tailwindcss-radix")({
  // Default: `radix`
  variantPrefix: "rdx",
});
// Example 1: Generates `rdx-[state/side/orientation]-*` utilities for `data-[state/side/orientation]="*"`
variantPrefix: "rdx",

// Example 2: Generates `[state/side/orientation]-*` utilities for `data-[state/side/orientation]="*"`
variantPrefix: false,

Styling state

Basic usage

This plugin works with CSS attribute selectors. Use the variants based on the data-* attribute added by Radix.

import React from "react";
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";

const App = () => {
  return (
    <DropdownMenuPrimitive.Root>
      <DropdownMenuPrimitive.Trigger className="border-black radix-state-open:border-2">
        Trigger
      </DropdownMenuPrimitive.Trigger>
      <DropdownMenuPrimitive.Content>
        <DropdownMenuPrimitive.Item>Item</DropdownMenuPrimitive.Item>
      </DropdownMenuPrimitive.Content>
    </DropdownMenuPrimitive.Root>
  );
};

export default App;

Accessing parent state

When you need to style an element based on the state of a parent element, mark the parent with the group class and style the target with group-radix-* modifiers.

Example usage of a conditional transform for a Radix Accordion:

import React from "react";
import * as AccordionPrimitive from "@radix-ui/react-accordion";
import { ChevronDownIcon } from "@radix-ui/react-icons";

const Accordion = () => {
  return (
    <AccordionPrimitive.Root type="multiple">
      <AccordionPrimitive.Item value="item-1">
        <AccordionPrimitive.Header>
          <AccordionPrimitive.Trigger className="group">
            <div className="flex items-center">
              Item 1
              <ChevronDownIcon className="w-5 h-5 ml-2 transform group-radix-state-open:rotate-180" />
            </div>
          </AccordionPrimitive.Trigger>
        </AccordionPrimitive.Header>
        <AccordionPrimitive.Content>Content 1</AccordionPrimitive.Content>
      </AccordionPrimitive.Item>
      <AccordionPrimitive.Item value="item-2">
        <AccordionPrimitive.Header>
          <AccordionPrimitive.Trigger className="group">
            <div className="flex items-center">
              Item 2
              <ChevronDownIcon className="w-5 h-5 ml-2 transform group-radix-state-open:rotate-180" />
            </div>
          </AccordionPrimitive.Trigger>
        </AccordionPrimitive.Header>
        <AccordionPrimitive.Content>Content 2</AccordionPrimitive.Content>
      </AccordionPrimitive.Item>
    </AccordionPrimitive.Root>
  );
};

export default App;

Accessing sibling state

When you need to style an element based on the state of a sibling element, mark the sibling with the peer class and style the target with peer-radix-* modifiers.

Example usage of a conditional icon color for a sibling of a Radix Checkbox:

import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
import { CheckIcon, TargetIcon } from "@radix-ui/react-icons";
import React from "react";

interface Props {}

const App = (props: Props) => {
  return (
    <>
      <CheckboxPrimitive.Root id="c1" defaultChecked className="peer h-5 w-5">
        <CheckboxPrimitive.Indicator>
          <CheckIcon />
        </CheckboxPrimitive.Indicator>
      </CheckboxPrimitive.Root>

      <TargetIcon className="text-red-500 peer-radix-state-checked:text-green-500" />
    </>
  );
};

export default App;

Disabled state

Use the generated disabled variant.

import React from "react";
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu";

const ContextMenu = () => {
  return (
    // --snip--
    <ContextMenuPrimitive.Item
      disabled
      className="radix-disabled:opacity-50 radix-disabled:cursor-not-allowed"
    >
      Item
    </ContextMenuPrimitive.Item>
    // --snip--
  );
};

CSS Variable Utilities

Origin position

.origin-radix-context-menu {
  transform-origin: var(--radix-context-menu-content-transform-origin);
}
.origin-radix-dropdown-menu {
  transform-origin: var(--radix-dropdown-menu-content-transform-origin);
}
.origin-radix-hover-card {
  transform-origin: var(--radix-hover-card-content-transform-origin);
}
.origin-radix-menubar {
  transform-origin: var(--radix-menubar-content-transform-origin);
}
.origin-radix-popover {
  transform-origin: var(--radix-popover-content-transform-origin);
}
.origin-radix-select {
  transform-origin: var(--radix-select-content-transform-origin);
}
.origin-radix-tooltip {
  transform-origin: var(--radix-tooltip-content-transform-origin);
}

Content / Viewport Width / Height

.w-radix-accordion-content {
  width: var(--radix-accordion-content-width);
}
.h-radix-accordion-content {
  height: var(--radix-accordion-content-height);
}
.w-radix-collapsible-content {
  width: var(--radix-collapsible-content-width);
}
.h-radix-collapsible-content {
  height: var(--radix-collapsible-content-height);
}
.w-radix-navigation-menu-viewport {
  width: var(--radix-navigation-menu-viewport-width);
}
.h-radix-navigation-menu-viewport {
  height: var(--radix-navigation-menu-viewport-height);
}

Content Available Width / Height

.w-radix-context-menu-content-available {
  width: var(--radix-context-menu-content-available-width);
}
.max-w-radix-context-menu-content-available {
  max-width: var(--radix-context-menu-content-available-width);
}
.h-radix-context-menu-content-available {
  height: var(--radix-context-menu-content-available-height);
}
.max-h-radix-context-menu-content-available {
  max-height: var(--radix-context-menu-content-available-height);
}
.w-radix-dropdown-menu-content-available {
  width: var(--radix-dropdown-menu-content-available-width);
}
.max-w-radix-dropdown-menu-content-available {
  max-width: var(--radix-dropdown-menu-content-available-width);
}
.h-radix-dropdown-menu-content-available {
  height: var(--radix-dropdown-menu-content-available-height);
}
.max-h-radix-dropdown-menu-content-available {
  max-height: var(--radix-dropdown-menu-content-available-height);
}
.w-radix-hover-card-content-available {
  width: var(--radix-hover-card-content-available-width);
}
.max-w-radix-hover-card-content-available {
  max-width: var(--radix-hover-card-content-available-width);
}
.h-radix-hover-card-content-available {
  height: var(--radix-hover-card-content-available-height);
}
.max-h-radix-hover-card-content-available {
  max-height: var(--radix-hover-card-content-available-height);
}
.w-radix-menubar-content-available {
  width: var(--radix-menubar-content-available-width);
}
.max-w-radix-menubar-content-available {
  max-width: var(--radix-menubar-content-available-width);
}
.h-radix-menubar-content-available {
  height: var(--radix-menubar-content-available-height);
}
.max-h-radix-menubar-content-available {
  max-height: var(--radix-menubar-content-available-height);
}
.w-radix-popover-content-available {
  width: var(--radix-popover-content-available-width);
}
.max-w-radix-popover-content-available {
  max-width: var(--radix-popover-content-available-width);
}
.h-radix-popover-content-available {
  height: var(--radix-popover-content-available-height);
}
.max-h-radix-popover-content-available {
  max-height: var(--radix-popover-content-available-height);
}
.w-radix-select-content-available {
  width: var(--radix-select-content-available-width);
}
.max-w-radix-select-content-available {
  max-width: var(--radix-select-content-available-width);
}
.h-radix-select-content-available {
  height: var(--radix-select-content-available-height);
}
.max-h-radix-select-content-available {
  max-height: var(--radix-select-content-available-height);
}
.w-radix-tooltip-content-available {
  width: var(--radix-tooltip-content-available-width);
}
.max-w-radix-tooltip-content-available {
  max-width: var(--radix-tooltip-content-available-width);
}
.h-radix-tooltip-content-available {
  height: var(--radix-tooltip-content-available-height);
}
.max-h-radix-tooltip-content-available {
  max-height: var(--radix-tooltip-content-available-height);
}

Trigger Available Width / Height

.w-radix-context-menu-trigger {
  width: var(--radix-context-menu-trigger-width);
}
.h-radix-context-menu-trigger {
  height: var(--radix-context-menu-trigger-height);
}
.w-radix-dropdown-menu-trigger {
  width: var(--radix-dropdown-menu-trigger-width);
}
.h-radix-dropdown-menu-trigger {
  height: var(--radix-dropdown-menu-trigger-height);
}
.w-radix-hover-card-trigger {
  width: var(--radix-hover-card-trigger-width);
}
.h-radix-hover-card-trigger {
  height: var(--radix-hover-card-trigger-height);
}
.w-radix-menubar-trigger {
  width: var(--radix-menubar-trigger-width);
}
.h-radix-menubar-trigger {
  height: var(--radix-menubar-trigger-height);
}
.w-radix-popover-trigger {
  width: var(--radix-popover-trigger-width);
}
.h-radix-popover-trigger {
  height: var(--radix-popover-trigger-height);
}
.w-radix-select-trigger {
  width: var(--radix-select-trigger-width);
}
.h-radix-select-trigger {
  height: var(--radix-select-trigger-height);
}
.w-radix-tooltip-trigger {
  width: var(--radix-tooltip-trigger-width);
}
.h-radix-tooltip-trigger {
  height: var(--radix-tooltip-trigger-height);
}

Toast swiping

.translate-x-radix-toast-swipe-end-x {
  transform: translateX(var(--radix-toast-swipe-end-x));
}
.translate-y-radix-toast-swipe-end-y {
  transform: translateY(var(--radix-toast-swipe-end-y));
}
.translate-x-radix-toast-swipe-move-x {
  transform: translateX(var(--radix-toast-swipe-move-x));
}
.translate-y-radix-toast-swipe-move-y {
  transform: translateY(var(--radix-toast-swipe-move-y));
}

Migrate from v1

To prevent a possible future name clashing the skipAttributeNames option has been removed. In case you used this option, please update the class names accordingly.

Migrate from v2

In case you use content-available utilities:

  • Add -content-available to the width-based classnames
  • Remove width and height from content-available-[width|height]
View diff

-w-radix-context-menu
+w-radix-context-menu-content-available

-h-radix-context-menu-content-available-height
+h-radix-context-menu-content-available

-max-w-radix-context-menu-content-available-width
+max-w-radix-context-menu-content-available

-max-h-radix-context-menu-content-available-height
+max-h-radix-context-menu-content-available


-w-radix-dropdown-menu
+w-radix-dropdown-menu-content-available

-h-radix-dropdown-menu-content-available-height
+h-radix-dropdown-menu-content-available

-max-w-radix-dropdown-menu-content-available-width
+max-w-radix-dropdown-menu-content-available

-max-h-radix-dropdown-menu-content-available-height
+max-h-radix-dropdown-menu-content-available


-w-radix-hover-card
+w-radix-hover-card-content-available

-h-radix-hover-card-content-available-height
+h-radix-hover-card-content-available

-max-w-radix-hover-card-content-available-width
+max-w-radix-hover-card-content-available

-max-h-radix-hover-card-content-available-height
+max-h-radix-hover-card-content-available


-w-radix-menubar
+w-radix-menubar-content-available

-h-radix-menubar-content-available-height
+h-radix-menubar-content-available

-max-w-radix-menubar-content-available-width
+max-w-radix-menubar-content-available

-max-h-radix-menubar-content-available-height
+max-h-radix-menubar-content-available


-w-radix-popover
+w-radix-popover-content-available

-h-radix-popover-content-available-height
+h-radix-popover-content-available

-max-w-radix-popover-content-available-width
+max-w-radix-popover-content-available

-max-h-radix-popover-content-available-height
+max-h-radix-popover-content-available


-w-radix-select
+w-radix-select-content-available

-h-radix-select
+h-radix-select-content-available

-max-w-radix-select-content-available-width
+max-w-radix-select-content-available

-max-h-radix-select-content-available-height
+max-h-radix-select-content-available


-w-radix-tooltip
+w-radix-tooltip-content-available

-h-radix-tooltip
+h-radix-tooltip-content-available

-max-w-radix-tooltip-content-available-width
+max-w-radix-tooltip-content-available

-max-h-radix-tooltip-content-available-height
+max-h-radix-tooltip-content-available

License

MIT

tailwindcss-radix's People

Contributors

domosedov avatar ecklf avatar jasondocton avatar johtso avatar junwen-k avatar kerumen avatar neupauer avatar nirtamir2 avatar tianenpang avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

tailwindcss-radix's Issues

Accordion animation example

Is there a working example with the radix-state-open/closed utilities?
or even the headless ui example in the readme? (how does currentItem get set?)

sparse documentation

I see all those utilities for the animations, but there's no documentation about how to use them. Also, when I go to see the animations in the demo, sometimes there is none (like in the case animating the accordion content height).

Not sure how to proceed without clear documentation.

Demo `origin-[top_center]` and other references

I'm perusing the demo here and trying to adapt some of the examples. I'm noticing there's reference to origin-[top_center] in the Navigation Menu demo:

"origin-[top_center] transition-[width_height] duration-300 ease-[ease]"
but I don't see where that variant has been added. I did a full search of the repo on both branches without much luck.

Implementing the demo as is, the menu is centered with respect to the parent element, and is missing the small caret that connects the flyout to the nav. Would appreciate any insight here!

Edit: I'm an actual idiot. It's Friday. Don't hate me. I'm now remembering the [ ] syntax in Tailwind.

Great job!

I just wanted to say thank you for the high quality work and huge effort you've been dedicating to this library.

The API you've created makes total sense and conforms to Radix/Tailwind conventions. It's so good that it seems like it was created by the Radix/Tailwind authors.

Also the work you've made here https://tailwindcss-radix.vercel.app is mind blowing, I have no words to describe how good it is.

Thank you very much!

requesting `peer` variant

I also have a custom plugin setup like yours.
But after I found your plugin, really wanna switch to yours and found peer variant missing.
You can find the peer variant of my plugin here.

Examples without using @headless-ui

Looking at the Dialog component, any way of managing the transitions without bringing @headless-ui in? Getting two headless libraries seems dull

Great job on the repo :-)

Missing DialogPrimitive.Portal in Dialog demos

Hi, I'm new to the library, I was learning how to use radix + tailwind and discovered an issue with the dialog demo.
dialogbug
There is a side effect when using flex + gap because the Transitions aren't wrapped with DialogPrimitive.Portal component.

<DialogPrimitive.Root open={isOpen} onOpenChange={setIsOpen}>
      <DialogPrimitive.Trigger asChild>
        <Button>Click</Button>
      </DialogPrimitive.Trigger>
      <DialogPrimitive.Portal> // <---- Here!
          <Transition.Root show={isOpen}>
            <Transition.Child
              as={Fragment}
              enter="ease-out duration-300"
              enterFrom="opacity-0"
              enterTo="opacity-100"
              leave="ease-in duration-200"
              leaveFrom="opacity-100"
              leaveTo="opacity-0"
            >

Thank you for the library, and the demos are fantastic!

My Opinion: Documentation Is Not Beginner-Friendly

I believe the documentation for this project may not beginner friendly for new contributor who are just starting their contributions

can someone write here the step by steps of installation to fork or clone

  1. fork or clone

Incompatibility with tailwind eslint

I'm receiving errors on my formatter:

./src/components/sidebar/account-dropdown.tsx
36:33  Warning: Classname 'radix-side-top:animate-slide-up' is not a Tailwind CSS class!  tailwindcss/no-custom-classname
36:33  Warning: Classname 'radix-side-bottom:animate-slide-down' is not a Tailwind CSS class!  tailwindcss/no-custom-classname

Config

{
  "extends": [
    "next/core-web-vitals",
    "prettier",
    "plugin:storybook/recommended",
    "plugin:tailwindcss/recommended"
  ]
}

Named groups not supported

Right now if I use this library like the following, it will not work as expected:

<AccordionContent class="group/my-named">
  <div class="group-radix-state-closed/my-named:opacity-0">
    // ...
  </div>
</AccordionContent>

I'm not sure when this named groups feature was implemented in TailwindCSS, but a helpful guide for how to make this library work with it can be found here: https://tailwindcss.com/docs/plugins#parent-and-sibling-states

I can work on this myself if I find time and if @ecklf shows interest. Either way just leaving this here so it gets documented.

Thanks!

Option for Toast Swipe Left?

Hi and thanks a lot for the excellent project. We've created several configurable wrapper functional components around your examples, and it's all working great.

One thing we recently tried to do was create options / props for positioning Toasts in either 'top-right', 'bottom-right', 'top-left' and 'bottom-left'.

We've got this working by modifying the keyframes and animation options in thailwind.config.js - as follows:

/** @type {import('tailwindcss').Config} */

const defaultTheme = require("tailwindcss/defaultTheme")

module.exports = {
  darkMode: 'class',
  content: [
    "./app/**/*.{js,ts,jsx,tsx}",
  ],
  plugins: [
    require('@tailwindcss/typography'),
    require('@tailwindcss/forms'),
    require("tailwindcss-radix")(),
  ],
  variants: {
    extend: {},
  },
  theme: {
    ...defaultTheme,
    fontFamily: {
      ...defaultTheme.fontFamily,
      display: ["Roboto", ...defaultTheme.fontFamily.sans],
      sans: ["Inter", ...defaultTheme.fontFamily.sans],
      mono: ["Source Code Pro", ...defaultTheme.fontFamily.mono],
    },
    gridTemplateColumns: {
      'auto-fit-280': 'repeat(auto-fill, minmax(280px, 1fr))',
      'auto-fit-320': 'repeat(auto-fill, minmax(320px, 1fr))',
      'auto-fit-480': 'repeat(auto-fill, minmax(480px, 1fr))',
    },
    extend: {
      boxShadow: {
        slider: "0 0 0 5px rgba(0, 0, 0, 0.3)",
      },
      keyframes: {
        // Dropdown menu
        "scale-in": {
          "0%": { opacity: 0, transform: "scale(0)" },
          "100%": { opacity: 1, transform: "scale(1)" },
        },
        "slide-down": {
          "0%": { opacity: 0, transform: "translateY(-10px)" },
          "100%": { opacity: 1, transform: "translateY(0)" },
        },
        "slide-up": {
          "0%": { opacity: 0, transform: "translateY(10px)" },
          "100%": { opacity: 1, transform: "translateY(0)" },
        },
        // Tooltip
        "slide-up-fade": {
          "0%": { opacity: 0, transform: "translateY(2px)" },
          "100%": { opacity: 1, transform: "translateY(0)" },
        },
        "slide-right-fade": {
          "0%": { opacity: 0, transform: "translateX(-2px)" },
          "100%": { opacity: 1, transform: "translateX(0)" },
        },
        "slide-down-fade": {
          "0%": { opacity: 0, transform: "translateY(-2px)" },
          "100%": { opacity: 1, transform: "translateY(0)" },
        },
        "slide-left-fade": {
          "0%": { opacity: 0, transform: "translateX(2px)" },
          "100%": { opacity: 1, transform: "translateX(0)" },
        },
        // Navigation menu
        "enter-from-right": {
          "0%": { transform: "translateX(200px)", opacity: 0 },
          "100%": { transform: "translateX(0)", opacity: 1 },
        },
        "enter-from-left": {
          "0%": { transform: "translateX(-200px)", opacity: 0 },
          "100%": { transform: "translateX(0)", opacity: 1 },
        },
        "exit-to-right": {
          "0%": { transform: "translateX(0)", opacity: 1 },
          "100%": { transform: "translateX(200px)", opacity: 0 },
        },
        "exit-to-left": {
          "0%": { transform: "translateX(0)", opacity: 1 },
          "100%": { transform: "translateX(-200px)", opacity: 0 },
        },
        "scale-in-content": {
          "0%": { transform: "rotateX(-30deg) scale(0.9)", opacity: 0 },
          "100%": { transform: "rotateX(0deg) scale(1)", opacity: 1 },
        },
        "scale-out-content": {
          "0%": { transform: "rotateX(0deg) scale(1)", opacity: 1 },
          "100%": { transform: "rotateX(-10deg) scale(0.95)", opacity: 0 },
        },
        "fade-in": {
          "0%": { opacity: 0 },
          "100%": { opacity: 1 },
        },
        "fade-out": {
          "0%": { opacity: 1 },
          "100%": { opacity: 0 },
        },
        // Toast
        "toast-hide": {
          "0%": { opacity: 1 },
          "100%": { opacity: 0 },
        },
        "toast-slide-in-right": {
          "0%": { transform: `translateX(calc(100% + 1rem))` },
          "100%": { transform: "translateX(0)" },
        },
        "toast-slide-in-left": {
          "0%": { transform: `translateX(calc(-1 * (100% + 1rem)))` },
          "100%": { transform: "translateX(0)" },
        },
        "toast-slide-in-bottom": {
          "0%": { transform: `translateY(calc(100% + 1rem))` },
          "100%": { transform: "translateY(0)" },
        },
        // Leave toast-swipe-out for compatibility - swipe out right
        "toast-swipe-out": {
          "0%": { transform: "translateX(var(--radix-toast-swipe-end-x))" },
          "100%": {
            transform: `translateX(calc(100% + 1rem))`,
          },
        },
        "toast-swipe-out-right": {
          "0%": { transform: "translateX(var(--radix-toast-swipe-end-x))" },
          "100%": {
            transform: `translateX(calc(100% + 1rem))`,
          },
        },
        "toast-swipe-out-left": {
          "0%": { transform: "translateX(var(--radix-toast-swipe-end-x))" },
          "100%": {
            transform: `translateX(calc(-1 * (100% + 1rem)))`,
          },
        },
      },
      animation: {
        // Dropdown menu
        "scale-in": "scale-in 0.2s ease-in-out",
        "slide-down": "slide-down 0.6s cubic-bezier(0.16, 1, 0.3, 1)",
        "slide-up": "slide-up 0.6s cubic-bezier(0.16, 1, 0.3, 1)",
        // Tooltip
        "slide-up-fade": "slide-up-fade 0.4s cubic-bezier(0.16, 1, 0.3, 1)",
        "slide-right-fade":
          "slide-right-fade 0.4s cubic-bezier(0.16, 1, 0.3, 1)",
        "slide-down-fade": "slide-down-fade 0.4s cubic-bezier(0.16, 1, 0.3, 1)",
        "slide-left-fade": "slide-left-fade 0.4s cubic-bezier(0.16, 1, 0.3, 1)",
        // Navigation menu
        "enter-from-right": "enter-from-right 0.25s ease",
        "enter-from-left": "enter-from-left 0.25s ease",
        "exit-to-right": "exit-to-right 0.25s ease",
        "exit-to-left": "exit-to-left 0.25s ease",
        "scale-in-content": "scale-in-content 0.2s ease",
        "scale-out-content": "scale-out-content 0.2s ease",
        "fade-in": "fade-in 0.2s ease",
        "fade-out": "fade-out 0.2s ease",
        // Toast
        "toast-hide": "toast-hide 100ms ease-in forwards",
        "toast-slide-in-right":
          "toast-slide-in-right 150ms cubic-bezier(0.16, 1, 0.3, 1)",
        "toast-slide-in-left":
          "toast-slide-in-left 150ms cubic-bezier(0.16, 1, 0.3, 1)",
        "toast-slide-in-bottom":
          "toast-slide-in-bottom 150ms cubic-bezier(0.16, 1, 0.3, 1)",
        // Leave toast-swipe-out for compatibility - swipe out right.
        "toast-swipe-out": "toast-swipe-out 100ms ease-out forwards",
        "toast-swipe-out-right": "toast-swipe-out-right 100ms ease-out forwards",
        "toast-swipe-out-left": "toast-swipe-out-left 100ms ease-out forwards",
      },
    },
  },
}

This is all working fine, and we can now position and animate the entrance and close of the Toast component to any corner.

There's just one problem. The direction of the pointer 'drag' and data attribute for 'move' event data-swipe='move' has its drag direction configured at the provider level - as in the docs here...

https://www.radix-ui.com/docs/primitives/components/toast#provider - with the swipeDirection prop and enum (for "right" | "left" | "up" | "down").

For example ToastPrimitive.Provider swipeDirection='right'

Which means we're not sure whether it's possible to configure this at the ToastPrimitive.Root level (which we wrap in our configurable component) to change the direction of the manual 'drag' and 'move' of the Toast.

Are we going about this the wrong way? Or is there no easy solution?

For interest, our complete Toast component implementation is here...

https://github.com/infonomic/remix.infonomic.io/blob/develop/app/ui/components/notifications/toast.tsx

Suggestions or thoughts greatly appreciated.

And again - thanks for the great project - and Happy New Year!

prefers-color-scheme: dark issue with Accordion

Hi,
First of all thanks for these amazing utilities!

I've ran into an issue and wanted to check if it's my lack of knowledge or a bug to report.
When using the Accordion and styling the Header in its open and close states, it works fine for the default light mode but the dark mode doesn't seem to work:

<AccordionPrimitive.Header className="w-full h-full leading-[3rem]">
 <AccordionPrimitive.Trigger
  className={clsx(
   "group",
   "radix-state-open:rounded-t-xl radix-state-closed:rounded-xl",
   "focus:outline-none",
   "inline-flex w-full items-center justify-between px-10 md:px-16 py-6 md:py-10 text-left",
   "radix-state-open:bg-white radix-state-closed:bg-neutral-200 hover:bg-neutral-100",
   "radix-state-open:dark:bg-neutral-700 radix-state-closed:dark:bg-neutral-900 hover:dark:bg-neutral-800 transition-colors"
   )}
  >
  (...)
  </AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>

In the above code I get an error in VS Code saying 'radix-state-open:dark:bg-neutral-700' applies the same CSS properties as 'radix-state-closed:dark:bg-neutral-900'.(cssConflict).

The visual result on my end is that the bg radix-states are being applied in the light mode (for some also strange reason the hover state doesn't). And that in the dark mode they just don't get implemented at all defaulting to the light mode values (and ironically the hover state works!).

Is this some mistake of mine? Or a bug to report?
Thank you

P.S.- If I remove the radix-states the hover works fine on both light and dark modes.

Missing Toast State and Animate Classes?

Hi @ecklf - thanks for the excellent project.

So far all works as demo'd, however, we've hit a speedbump with the @radix-ui/react-toast component.

We have tailwind configured in a Remix.run project via postCSS (we are importing other CSS files)

For example... our postcsss.config.js looks like this...

// postcss.config.js
module.exports = cfg => {

  const
    dev = cfg.env === 'development',
    scss = cfg.file.extname === '.scss'

  return {
    map: dev ? { inline: false } : false,
    parser:  scss ? 'postcss-scss' : false,

    plugins: {
      'postcss-import': {},
      'postcss-nested': {},
      tailwindcss: {},
      autoprefixer: {},
      ...(dev ? {} : { cssnano: {} }),
    },
  }
}

And our tailwind.config.js config looks like this...

/** @type {import('tailwindcss').Config} */

const defaultTheme = require("tailwindcss/defaultTheme")

module.exports = {
  darkMode: 'class',
  content: [
    "./app/**/*.{js,ts,jsx,tsx}",
  ],
  plugins: [
    require('@tailwindcss/typography'),
    require('@tailwindcss/forms'),
    require("tailwindcss-radix")(),
  ],
  theme: {
    ...defaultTheme,
    },
    extend: {},
  },
}

And in our app.css file, we import the tailwind base components and utilities files as:

@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";

However, after all of this, and despite including the require("tailwindcss-radix")(), in our tailwind.config.js file, we appear to be missing the radix variant state and animation classes - such as radix-state-open:animate-toast-slide-in-right. They're present as class attributes of the components/elements, but in devtools, we can see that no corresponding class definition is found.

Are we missing an import? Or configuration setting? Remix.run is using ESBuild - not sure if this is a factor?

Thoughts or suggestions greatly appreciated.

windy-radix-palette & tailwindcss-radix not work

I am using both windy-radix-palette and tailwindcss-radix.

But I found that the css class of tailwindcss-radix does not work properly.

For example this.

<DropdownMenu.Content
              align="end"
              sideOffset={5}
              className={cx(
                " radix-side-top:animate-slide-up radix-side-bottom:animate-slide-down",
                "rounded-md p-3 beautify-shadow",
                "bg-white dark:bg-gray-800"
              )}
              style={{
                width: '278px'
              }}
            >

There is no animation applied on

Optional state prefix.

Is there any way we can turn off -state prefix in data-state variants?
So the radix-state-checked will be transformed into radix-checked.
I think it's easier to read and understand (e.g. radix-disable)

Feat: Drawer component

I've created a Drawer component based off of the Radix Dialog component something I think the Radix library is missing. Do you have any interest in adding this component? Here's a rough sketch of how it would look:

drawer.tsx:

import { Transition } from "@headlessui/react";
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { Cross1Icon } from "@radix-ui/react-icons";
import { clsx } from "clsx";
import React, { Fragment } from "react";
import Button from "./shared/button";

type Position = "left" | "right";

export interface DrawerProps {
  title?: string;
  description?: string;
  position?: Position;
  isOpen: boolean;
  onOpenChange: () => void;
}

const Drawer = (props: DrawerProps) => {
  const {
    title = "Drawer",
    position = "right",
    description,
    isOpen,
    onOpenChange,
  } = props;
  return (
    <DialogPrimitive.Root open={isOpen} onOpenChange={onOpenChange}>
      <DialogPrimitive.Portal forceMount>
        <Transition.Root show={isOpen}>
          <Transition.Child
            as={Fragment}
            enter="ease-in-out duration-300"
            enterFrom="opacity-0"
            enterTo="opacity-100"
            leave="ease-in-out duration-300"
            leaveFrom="opacity-100"
            leaveTo="opacity-0"
          >
            <DialogPrimitive.Overlay className="fixed inset-0 bg-black/50" />
          </Transition.Child>
          <Transition.Child
            as={Fragment}
            enter="transform transition ease-in-out duration-300"
            enterFrom="translate-x-full"
            enterTo="translate-x-0"
            leave="transform transition ease-in-out duration-300"
            leaveFrom="translate-x-0"
            leaveTo="translate-x-full"
          >
            <DialogPrimitive.Content
              forceMount
              className={clsx(
                position === "right" ? "right-0" : "left-0",
                "fixed top-0 flex h-full w-screen max-w-md flex-col overflow-y-scroll bg-gray-800 py-6 shadow-xl"
              )}
            >
              <DialogPrimitive.Title className="mb-2 px-4 text-sm font-medium text-gray-900 dark:text-gray-100">
                {title}
              </DialogPrimitive.Title>
              {description && (
                <DialogPrimitive.Description className="px-4 text-sm font-normal text-gray-700 dark:text-gray-400">
                  {description}
                </DialogPrimitive.Description>
              )}
              <div className="mt-[25px] flex justify-end px-4">
                <DialogPrimitive.Close
                  className={clsx(
                    "inline-flex select-none justify-center rounded-md px-4 py-2 text-sm font-medium",
                    "bg-purple-600 text-white hover:bg-purple-700 dark:bg-purple-700 dark:text-gray-100 dark:hover:bg-purple-600",
                    "border border-transparent",
                    "focus:outline-none focus-visible:ring focus-visible:ring-purple-500 focus-visible:ring-opacity-75"
                  )}
                >
                  Save changes
                </DialogPrimitive.Close>
              </div>
              <DialogPrimitive.Close className="absolute top-6 right-3.5 inline-flex items-center justify-center rounded-full p-1 focus:outline-none focus-visible:ring focus-visible:ring-purple-500 focus-visible:ring-opacity-75">
                <Cross1Icon className="h-4 w-4 text-gray-500 hover:text-gray-700 dark:text-gray-500 dark:hover:text-gray-400" />
              </DialogPrimitive.Close>
            </DialogPrimitive.Content>
          </Transition.Child>
        </Transition.Root>
      </DialogPrimitive.Portal>
    </DialogPrimitive.Root>
  );
};

export { Drawer };

index.tsx:

const DrawerWithState = () => {
  const [drawerOpen, setDrawerOpen] = useState<boolean>(false);

  const handleDrawerToggle = () => {
    setDrawerOpen(!drawerOpen);
  };

  return (
    <>
      <Button onClick={handleDrawerToggle}>Click</Button>
      <Drawer
        isOpen={drawerOpen}
        onOpenChange={handleDrawerToggle}
        description="Navigation drawers provide access to destinations in your app. Side sheets are surfaces containing supplementary content that are anchored to the left or right edge of the screen."
      />
    </>
  );
};

{
  label: "Drawer",
  link: `${REPO_URL}/components/drawer.tsx`,
  component: <DrawerWithState />,
},

screenshots:
Screen Shot 2023-03-16 at 1 42 05 pm
Screen Shot 2023-03-16 at 1 43 21 pm

I have the changes on a PR locally. I'd very much like to contribute to your library moving forward ๐Ÿ‘ . Please let me know your thoughts and feedback? Thanks!

Unmount Animations

Radix supports leave animations by suspending unmount while your animation plays out, more info here.

While going over the source code, I've noticed how this plugin adds support for the closed state (radix-state-closed). However, I was not able to successfully animate the unmount phase using tailwind. Most if not all of the demo components have the same issue of being instantly unmounted without a smooth transition.

Is it possible with the current API to achieve this? Otherwise, is it feasible to implement support for it?

Issues using the plugin with DaisyUI

Hello, how are you?
I've been using tailwindcss-radix for a few weeks now and it's great. However, I recently included the library in a project that also uses DaisyUI, and I noticed that some functionalities are not compatible. It's possible that the classes generated by the library don't properly call the corresponding DaisyUI classes.

Here's an example of an implementation where I found the problem:

import { Accordion } from '@primitives/radix';
import { IAccordionProps } from './accordion.interface';

export function AccordionComponent(props: IAccordionProps): JSX.Element {
    return (
        <Accordion.Root type='multiple' disabled={props.disabled}>
            {props.items.map((item, index) => {
                return (
                    <>
                        <Accordion.Item className='collapse rounded-box collapse-arrow w-80 m-3 radix-state-open:collapse-open radix-state-closed:collapse-close' value={`item-${index}`}>
                           <Accordion.Trigger className='collapse-title bg-neutral-focus text-neutral-focus-content'>
                               {item.label}
                           </Accordion.Trigger>
                           <Accordion.Content className='collapse-content p-4 bg-neutral text-neutral-content'>
                                {item.content}
                            </Accordion.Content>
                        </Accordion.Item>
                    </>
                );
            })}
        </Accordion.Root>
    );
}

Basically, when using the radix-state passing tailwind's own classes, the application works correctly. I tested it with border, padding, and other classes. However, with DaisyUI's own classes, there seems to be no compatibility. Let me know if I can help by providing more information about the problem. Congratulations on the great work.

Example of using in a monorepo

Hello I'm trying to use this lib with a monorepo with pnpm workspaces and turborepo. I used this example from turborepo itself that contains tailwind. But I don't know what I'm missing to make it work properly.

My final result is this (Accordion and Select components):
image

You can see that the tailwind classes are working because of the green text (color that I configured in tailwindcss).
I copy your components from the demo but the styles are not working.
Can you help with this? Thanks for this amazing library!

Radix accordion state animation

Hi, we are trying to style the open and close state of the accordion in Radix, seems t not work or are we doing anything wrong....

Is there any example you can post for quick reference ?

How to use with ESM and variantPrefix?

import tailwindRadix from 'tailwindcss-radix';

tailwind.config.ts

  plugins: [
    tailwindRadix,
  ],

I am not sure how to specify variantPrefix with esm import.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.