2

I am creating a custom components library for personal usage, and I want to make it customizable. The approach I would like to use here is something like in the code below. I am not sure if this is the best approach and if it is even possible to use it in this way. I'd be gladful for any advice to make the code looks nice. Thanks

Component.tsx

interface IProps {
  cssClass: any; <-- what type to put here?
}

export const SomeCustomComponent = (props: IProps) => {
  return (
    <>
      <div className={cssClass}>
        <p className={cssClass.header}>Header</p>
        <Icon className={cssClass.icon}/>
      </div>
    </>
  )
}

Styles.scss

.myComponent {
  color: white;
  .header { margin-left: 10px; }
  .icon { margin-right: 10px; } 
}

MainComponent.tsx

import styles from './Styles.scss'
const MainComponent = () => {
  return (
    <>
      <SomeCustomComponent cssClass={styles.myComponent}/>
    </>
  )
}

1 Answers1

0

There are multiple ways to achieve customized styling and two come to mind (as well as a third after some research). There are other ways to do this as well. The first two options do this without CSS-Modules. The third is an attempt to solve this problem using CSS-Modules.

Option 1

Pass styles from a parent component to a child component. You can update the styles dynamically in the parent and pass it to the child. Because you are passing an object as a prop, this can potentially cause unnecessary re-renders and harm performance. Inline styles are nice because they are easy to dynamically create and pass as props. They are the de facto way to pass dynamic styles.

import React, { CSSProperties } from 'react';

interface CSSStyles {
  [key: string]: CSSProperties;
}

const MainComponent = () => {
  const styles: CSSStyles = {
    header: { marginLeft: "10px" }, // Dynamically update
    icon: { marginRight: "10px" }, // Dynamically update
    myComponent: { color: "black" }, // Dynamically update
  };

  return (
    <>
      <SomeCustomComponent cssClasses={styles} />
    </>
  );
};

interface Props {
  cssClasses: CSSStyles;
}
const SomeCustomComponent = ({ cssClasses }: Props) => {
  return (
    <div style={cssClasses.myComponent}>
      <p style={cssClasses.header}>Header</p>
      <Icon style={cssClasses.icon} />
    </div>
  );
};

Option 2

You can pass a className as a string and update it in the parent. This requires you to make multiple classes in your .scss file. Since the cssClasses prop is passing an object here, this can also potentially cause unnecessary re-renders. You can tweak this if performance becomes and issue.

.tsx

interface CSSStyles {
  [key: string]: string;
}

const MainComponent = () => {
  const styles: CSSStyles = {
    header: "header", // Dynamically change
    icon: "icon", // Dynamically change
    myComponent: "myOtherComponent", // Dynamically change
  };

  return (
    <>
      <SomeCustomComponent cssClasses={styles} />
    </>
  );
};

interface Props {
  cssClasses: CSSStyles;
}
const SomeCustomComponent = ({ cssClasses }: Props) => {
  return (
    <div className={cssClasses.myComponent}>
      <p className={cssClasses.header}>Header</p>
      <Icon className={cssClasses.icon} />
    </div>
  );
};

.scss

.myComponent {
  color: white;
  .header {
    margin-left: 10px;
  }
  .icon {
    margin-right: 10px;
  }
}

.myOtherComponent {
  color: black;
  .header {
    margin-left: 100px;
  }
  .icon {
    margin-right: 100px;
  }
}

Option 3

A CSS-Module Specific way. Looks like you can:

  • Wrap each child in a div
  • Pass the className as props
  • Use the CSS child combinator
  • Create multiple CSS classes and pass the className (similar to option 2).

These seem to be specific to CSS-Modules, but seem more complex than option 1 or 2. I am not certain of the performance implications for the CSS-Modules.

See https://stackoverflow.com/a/55082008/9931154 and https://stackoverflow.com/a/34036086/9931154 for references. The CSS-Modules documentation didn't seem to mention anything relating to your problem.