1

I sometimes extract local Tailwind-styled components like this:

const Container: React.FC = ({ children }) => (
  <div className="bg-white px-5 py-5">
    {children}
  </div>
);

To make this easier, I would love to have a helper styleElement function that would create the Container for me, a bit like this:

const Container = styleElement("div", "bg-white px-5 py-5");

How would I write the styleElement function so that Container is correctly typed as a div and setting its className adds to the defaults? Ie:

// Renders as <div className="bg-white px-5 py-5 text-red">…</div>
<Container className="text-red">…</Container>

I stress that I want to keep the typing right/tight, so that calling styleElement("a", …) would produce a component that accepts the correct props for an a.

PS. I have seen this related question, but I’m having trouble pivoting the types from the answer there to what I want.

zoul
  • 102,279
  • 44
  • 260
  • 354

1 Answers1

1

The function

function createElement(tag, classes) {
  return ({ children }) => {
    const Element = tag;

    return <Element className={classes}>{ children }</Element>
  }
}

Based on this answer

Usage

function App() {
  const StyledDiv = createElement('div', 'p-4')
  return (
    <div className="App">
      <StyledDiv>Hello</StyledDiv>
    </div>
  );
}

Basic props

function createElement(tag: keyof JSX.IntrinsicElements, classes: string) {
  return ({
    children,
    ...props
  }: React.DetailedHTMLProps<
    React.BlockquoteHTMLAttributes<HTMLElement>,
    HTMLElement
  >) => {
    return React.createElement(tag, { ...props, className: classes }, children);
  };
}
Konrad
  • 21,590
  • 4
  • 28
  • 64
  • Thank you, but this doesn’t type the resulting element properly, ie. `createElement("div")` doesn’t lead to a component that accepts `HTMLDivElement`’s props. – zoul Sep 07 '22 at 11:25
  • I added a version that will work with TypeScript, but you can't pass tag-specific attributes like the type of button. I'm not sure if this can be done entirely generic. I will look at how styled-components do it. – Konrad Sep 07 '22 at 12:15
  • Seems like they map [every element](https://github.com/styled-components/styled-components/blob/main/packages/styled-components/src/utils/domElements.ts) [here](https://github.com/styled-components/styled-components/blob/main/packages/styled-components/src/constructors/styled.tsx#L14) – Konrad Sep 07 '22 at 12:52