Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend Component Documentation #176

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/latest/pages/docs/examples/react/tailwind-css.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,19 @@ import { StackBlitz } from "../../../../components/StackBlitz";

# React with Tailwind CSS

1. [Basic Components](#basic-components)
2. [Compound Components](#compound-components)

## Basic Components

<StackBlitz
dir="examples/react-with-tailwindcss"
file="src/components/button/button.tsx"
/>

## Compound Components

<StackBlitz
dir="examples/react-with-tailwindcss-compound"
file="src/components/nav/nav.tsx"
/>
18 changes: 1 addition & 17 deletions docs/latest/pages/docs/faqs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,7 @@ Long story short: it's unnecessary.
- Polymorphism is free; just apply the class to your preferred HTML element
- Less opinionated; you're free to build components with `cva` however you'd like

<details>

{" "}

<summary>Example: Polymorphic Components</summary>

There's no `as` prop in `cva`, because HTML is free:

```diff
-- // A familiar `styled` button as a link
-- <Button as="a" href="#" variant="primary">Button as a link</Button>

++ // A `cva` button as a link
++ <a href="#" class={button({variant: "primary"})}>Button as a link</a>
```

</details>
See the ["Polymorphism"](/docs/getting-started/polymorphism) documentation for further recommendations.

## How Can I Create [Responsive Variants like Stitches.js](https://stitches.dev/docs/responsive-styles#responsive-variants)?

Expand Down
6 changes: 4 additions & 2 deletions docs/latest/pages/docs/getting-started/_meta.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
{
"installation": "",
"variants": "",
"extending-components": "",
"compound-components": "",
"typescript": "TypeScript",
"composing-components": ""
"extending-components": "",
"mixing-components": "",
"polymorphism": ""
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Composing Components
# Mixing Components

Whilst `cva` doesn't yet offer a built-in method for composing components, it does offer the tools to _extend_ components on your own terms…
Whilst `cva` doesn't yet offer a built-in method for mixing components together, it does offer the tools to _extend_ components on your own terms…

For example; two `cva` components, concatenated together with `cx`:

Expand Down
14 changes: 14 additions & 0 deletions docs/latest/pages/docs/getting-started/compound-components.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Compound Components

Compound Components are a term that React.js developers will be familiar with, and feels like the best way to describe this pattern.

Build compound components with the power of CSS selectors and variables – no additional JS required.

TODO

1. Tab with NormalCSS
2. Tab with TailwindCss

## Examples

- [React with Tailwind CSS](../examples/react/tailwind-css#compound-components)
44 changes: 44 additions & 0 deletions docs/latest/pages/docs/getting-started/mixing-components.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Mixing Components

Whilst `cva` doesn't yet offer a built-in method for mixing components together, it does offer the tools to _extend_ components on your own terms…

For example; two `cva` components, concatenated together with `cx`:

```ts
// components/card.ts
import type { VariantProps } from "class-variance-authority";
import { cva, cx } from "class-variance-authority";

/**
* Box
*/
export type BoxProps = VariantProps<typeof box>;
export const box = cva(["box", "box-border"], {
variants: {
margin: { 0: "m-0", 2: "m-2", 4: "m-4", 8: "m-8" },
padding: { 0: "p-0", 2: "p-2", 4: "p-4", 8: "p-8" },
},
defaultVariants: {
margin: 0,
padding: 0,
},
});

/**
* Card
*/
type CardBaseProps = VariantProps<typeof cardBase>;
const cardBase = cva(["card", "border-solid", "border-slate-300", "rounded"], {
variants: {
shadow: {
md: "drop-shadow-md",
lg: "drop-shadow-lg",
xl: "drop-shadow-xl",
},
},
});

export interface CardProps extends BoxProps, CardBaseProps {}
export const card = ({ margin, padding, shadow }: CardProps = {}) =>
cx(box({ margin, padding }), cardBase({ shadow }));
```
81 changes: 81 additions & 0 deletions docs/latest/pages/docs/getting-started/polymorphism.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Polymorphism

`cva` components are polymorphic (and framework-agnostic) by default; just apply the class to your preferred HTML element…

```tsx
import { button } from "./components/button";

export default () => (
<a className={button()} href="/sign-up">
Sign up
</a>
);
```

## Alternative Approaches

### React

If you'd prefer to use a React-based API, `cva` strongly recommends using [`@radix-ui`'s `Slot` component](https://www.radix-ui.com/docs/primitives/utilities/slot) to create your own `asChild` prop.

```tsx
// ./components/button.tsx
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";

const button = cva("button", {
variants: {
intent: {
primary: [
"bg-blue-500 text-white",
"text-white",
"border-transparent",
"hover:bg-blue-600",
],
secondary: [
"bg-white",
"text-gray-800",
"border-gray-400",
"hover:bg-gray-100",
],
},
},
defaultVariants: {
intent: "primary",
},
});

export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof button> {
asChild?: boolean;
}

export const Button: React.FC<ButtonProps> = ({
asChild,
className,
intent,
...props
}) => {
const Comp = asChild ? Slot : "button";

return <Comp className={button({ intent, className })} {...props} />;
};
```

#### Usage

```tsx
import { Button } from "./components/button";

// Renders:
// <a href="/sign-up" class="bg-blue-500 text-white text-white border-transparent hover:bg-blue-600">
// Sign up
// </a>
export default () => (
<Button asChild>
<a href="/sign-up">Sign up</a>
</Button>
);
```
39 changes: 24 additions & 15 deletions docs/latest/pages/docs/getting-started/variants.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,46 +25,55 @@ import { cva } from "class-variance-authority";
const button = cva(["font-semibold", "border", "rounded"], {
variants: {
intent: {
primary: [
"bg-blue-500",
"text-white",
"border-transparent",
"hover:bg-blue-600",
],
primary: ["bg-blue-500", "text-white", "border-transparent"],
// **or**
// primary: "bg-blue-500 text-white border-transparent hover:bg-blue-600",
secondary: [
"bg-white",
"text-gray-800",
"border-gray-400",
"hover:bg-gray-100",
],
secondary: ["bg-white", "text-gray-800", "border-gray-400"],
},
size: {
small: ["text-sm", "py-1", "px-2"],
medium: ["text-base", "py-2", "px-4"],
},
// `boolean` variants are also supported!
disabled: {
false: null,
true: ["opacity-50", "cursor-not-allowed"],
},
},
compoundVariants: [
{
intent: "primary",
disabled: false,
class: "hover:bg-blue-600",
},
{
intent: "secondary",
disabled: false,
class: "hover:bg-gray-100",
},
{
intent: "primary",
size: "medium",
class: "uppercase",
// **or** if you're a React.js user, `className` may feel more consistent:
// className: "uppercase"
class: "uppercase",
},
],
defaultVariants: {
intent: "primary",
size: "medium",
disabled: false,
},
});

button();
// => "font-semibold border rounded bg-blue-500 text-white border-transparent hover:bg-blue-600 text-base py-2 px-4 uppercase"
// => "font-semibold border rounded bg-blue-500 text-white border-transparent text-base py-2 px-4 hover:bg-blue-600 uppercase"

button({ disabled: true });
// => "font-semibold border rounded bg-blue-500 text-white border-transparent text-base py-2 px-4 opacity-50 cursor-not-allowed uppercase"

button({ intent: "secondary", size: "small" });
// => "font-semibold border rounded bg-white text-gray-800 border-gray-400 hover:bg-gray-100 text-sm py-1 px-2"
// => "font-semibold border rounded bg-white text-gray-800 border-gray-400 text-sm py-1 px-2 hover:bg-gray-100"
```

## Compound Variants
Expand Down
46 changes: 30 additions & 16 deletions examples/astro-with-tailwindcss/src/components/button.astro
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,52 @@ import { cva, type VariantProps } from "class-variance-authority";
const button = cva("button", {
variants: {
intent: {
primary: [
"bg-blue-500",
"text-white",
"border-transparent",
"hover:bg-blue-600",
],
secondary: [
"bg-white",
"text-gray-800",
"border-gray-400",
"hover:bg-gray-100",
],
primary: ["bg-blue-500", "text-white", "border-transparent"],
secondary: ["bg-white", "text-gray-800", "border-gray-400"],
},
size: {
small: ["text-sm", "py-1", "px-2"],
medium: ["text-base", "py-2", "px-4"],
},
disabled: {
false: null,
true: ["opacity-50", "cursor-not-allowed"],
},
},
compoundVariants: [{ intent: "primary", size: "medium", class: "uppercase" }],
compoundVariants: [
{
intent: "primary",
disabled: false,
class: "hover:bg-blue-600",
},
{
intent: "secondary",
disabled: false,
class: "hover:bg-gray-100",
},
{ intent: "primary", size: "medium", class: "uppercase" },
],
});

export interface Props
extends HTMLAttributes<"button">,
extends Omit<HTMLAttributes<"button">, "disabled">,
VariantProps<typeof button> {}

/**
* For Astro components, we recommend setting your defaultVariants within
* Astro.props (which are `undefined` by default)
*/
const { intent = "primary", size = "medium" } = Astro.props;
const {
class: className,
intent = "primary",
size = "medium",
disabled = false,
} = Astro.props;
---

<button class={button({ intent, size })}>
<button
class={button({ intent, size, disabled, className })}
disabled={disabled}
>
<slot />
</button>
Loading
Loading