Skip to content

kasper573/react-styled-factory

Repository files navigation

react-styled-factory

Abstract utility for creating typesafe styled React components without having to do the type and props wiring. Intended (but not limited) to be used together with a css compiler like vanilla-extract.

Try it on StackBlitz

Defining components

Use the provided factory to create a styled function that you will use to create react components.

import { createStyledFactory } from "react-styled-factory";

export const styled = createStyledFactory();

Use the styled function to produce a React component:

import { styled } from "./styled";

const Component = styled("div");

<Component style={{ color: "red" }} data-foo="123">
  Hello World
</Component>; // <div style="color: red;" data-foo="123">Hello World</div>

Custom components can be used as well:

import { HTMLAttributes } from "react";

function Impl(props: HTMLAttributes<HTMLSpanElement>) {
  return <span {...props} />;
}

const Component = styled(Impl);

<Component>Foo</Component>; // <span>Foo</span>

(Optional) Provide an sx compiler (i.e. @vanilla-extract/sprinkles) to the factory if you want to enable the sx prop on all components:

// styled.ts
import { createStyledFactory } from "react-styled-factory";
import { sprinkles } from "./styles.css.ts";

export const styled = createStyledFactory(sprinkles);

// Component.tsx
import { styled } from "./styled";

const Component = styled("div");

<Component sx={{ color: "primary", pt: "large" }} />;
// <div class="color-red padding-top-large"></div>

(Optional) When using an sx compiler you can also specify the sx value as the default style:

// styled.ts
import { createStyledFactory } from "react-styled-factory";
import { sprinkles } from "./styles.css.ts";

export const styled = createStyledFactory(sprinkles);

// Component.tsx
import { styled } from "./styled";

const Component = styled("div", { color: "primary", pt: "large" });

<Component />; // <div class="color-red padding-top-large"></div>

Styling components

Provide a variants compiler (i.e. @vanilla-extract/recipes) to automatically convert variant props to class names:

// styles.css.ts
import { recipe } from "@vanilla-extract/recipes";

export const myRecipe = recipe({
  variants: {
    size: {
      small: { fontSize: "10px" },
      large: { fontSize: "20px" },
    },
    color: {
      primary: { color: "blue" },
      secondary: { color: "red" },
    },
  },
});

// Component.tsx
import { myRecipe } from "./styles.css.ts";

const Component = styled("div", myRecipe);

<Component size="large" color="primary" />;
// <div class="size-large color-primary"></div>

You can also pass in plain class names for simpler components:

const Component = styled("div", "my-css-class");

<Component />; // <div class="my-css-class"></div>

Which integrates well with i.e. @vanilla-extract/css:

// styles.css.ts
import { style } from "@vanilla-extract/css";

export const myClass = style({ color: "red" });

// Component.tsx
import { myClass } from "./styles.css.ts";

const Component = styled("div", myClass);

Composing components

Styled components support merging with the immediate child:

const Component = styled("div", "my-css-class");

<Component asChild>
  <span>Hello World</span>
</Component>; // <span class="my-css-class">Hello World</span>

You can also produce a new component, identical but with the inner component changed:

// Component.tsx
export const Component = styled("div", "my-css-class");

// NewComponent.tsx
import { Component } from "./Component";

const NewComponent = Component.as("span");

<NewComponent />; // <span class="my-css-class"></span>

You can embed default props by using the attrs function:

const Component = styled("div").attrs({ role: "button" });

<Component />; //  <div role="button"></span>
<Component role="alert" />; // <div role="alert"></span>

By default, variant props will be filtered and not passed to the inner component. You can override this behavior by using shouldForwardProp:

// Dialog.css.ts
import { recipe } from "@vanilla-extract/recipes";

export const dialogRecipe = recipe({
  base: {
    transition: "opacity 0.3s",
  },
  variants: {
    open: {
      true: { opacity: 1 },
      false: { opacity: 0 },
    },
  },
});

// Dialog.tsx
import { dialogRecipe } from "./Dialog.css.ts";

const Dialog1 = styled("dialog", dialogRecipe);

// "open" will be interpreted and generate a class,
// but will not be passed to the inner component
<Dialog1 open />; // <dialog class="open-true"></dialog>

const Dialog2 = styled("dialog", dialogRecipe).shouldForwardProp(
  ({ name, isVariant }) => !isVariant || name == "open",
);

// Here "open" will be both used to generate a class
// and be passed to the inner component
<Dialog2 open />; // <dialog class="open-true" open></dialog>

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published