0

I've built a simple Panel.tsx component in React, using TypeScript:

import * as React from 'react';

import { Label, LabelType } from 'components/basic';

import styles from './Panel.module.scss';

interface IPanel {
  title?: string;
  children: React.ReactNode;
}

const Panel = ({ title, children }: IPanel) => {
  return (
    <div className={styles.main}>
      {title && <Label type={LabelType.Title} bold text={title} />}
      {children}
    </div>
  );
};

export default Panel;

Here's the companion Panel.module.scss file:

.main {
  border: 2px solid $black;
  border-radius: 10px;
  padding: 10px;
}

Now, if I want to inject a SCSS class into Panel, perhaps with a color, background-color, font-size, etc. is there a way to do that?

RobertW
  • 226
  • 1
  • 2
  • 10

1 Answers1

0

Yes, it is quite common to write components in such a way that they accept a className property (but which must be applied to the component root explicitly by the component author). E.g. all components from material-ui accept a classname (I'm not a big fan of material-ui but at least this aspect they got right).

You can either add it explicitly to your prop type

interface IPanel {
  title?: string;
  children: React.ReactNode;
  className?: string;
}

...and use it in your component

import classnames from 'clsx'; // this is a popular package to combine classnames

const Panel = ({ title, children, className }: IPanel) => {
  return (
    <div className={classnames(styles.main, className)}>
      {title && <Label type={LabelType.Title} bold text={title} />}
      {children}
    </div>
  );
};

...or you can use one of the utility types from React. E.g. I often just allow all common HTML attributes plus children to be added as props (Yes, I know, my types are not overly precise)

import type { FunctionComponent, HTMLAttributes } from 'react';
export type CommonPropsFunctionComponent<TProps = {}> = FunctionComponent<
  TProps & HTMLAttributes<Element>
>;
import { CommonPropsFunctionComponent } from './types';

interface IPanel {
  title?: string;
}

const Panel: CommonPropsFunctionComponent<IPanel>  = ({ title, children, className }) => {
  return (
    <div className={classnames(styles.main, className)}>
      {title && <Label type={LabelType.Title} bold text={title} />}
      {children}
    </div>
  );
};

Using the spread operator

You might be tempted to use it here; but beware: you must be careful to not overwrite your own local classname accidently. To illustrate this, in this example:

  • local tabIndex can be overwritten
  • local className list cannot be overwitten, only extended
const SomeComponent= ({ children, className, ...rest }) => {
  return (
    <div tabIndex={0} {...rest} className={classnames(styles.root, className)}>
      {children}
    </div>
  );
};
Martin
  • 5,714
  • 2
  • 21
  • 41
  • Thank you for your response. I'm very familiar with `cx(...)` and use it all the time. I'm confused by a few things with what you wrote. In which file does the `className` string passed via the `className` prop reside? Is it in a global SCSS file? Assuming this is the case, I suppose that would work. What would be better would be to pass in a custom class, created-on-the-fly, and simply defined within as JSON string, such as: `{background-color: yellow; border-width: 5px; padding: 17px;}` – RobertW Jun 30 '22 at 17:21
  • Usually the classname that is provided lives in another css-module file and would therefore during runtime look something like this `fsghs45shgdj526`. But that doesn't matter because in the calling code it is accessed like any other classname from a css-modlue via `style.mySpecialClass` – Martin Jun 30 '22 at 18:10