Skip to content

Latest commit

 

History

History
231 lines (179 loc) · 5.43 KB

TYPESCRIPT_CONVENTION.md

File metadata and controls

231 lines (179 loc) · 5.43 KB

TypeScript convention

Component

Public components are considered all components exported from @mui/material or @mui/lab.

Internal components are considered all components that are not exported from the packages, but only used in some public component. There is no need to have sx prop on these components

Props Interface

  • export interface {ComponentName}classes from {component}Classes.ts and add comment for generating api docs (for internal components, may or may not expose classes but don't need comment)
  • export interface {ComponentName}Props
  • always export props interface (use interface over type) from the component file
  • provide sx only for public component
Public component
// fooClasses.tsx

export interface FooClasses {
  /** Styles applied to the root element. */
  root: string;
  /** Styles applied to the foo element. */
  foo: string;
  /** Styles applied to the root element if `disabled=true`. */
  disabled: string;
}

const fooClasses: FooClasses = generateUtilityClasses('MuiFoo', ['root', 'foo', 'disabled']);

export default fooClasses;
// Foo.tsx
import { FooClasses } from './fooClasses';

export interface FooProps {
  /**
   * Override or extend the styles applied to the component.
   */
  classes?: Partial<FooClasses>;
  // ...other props
  /**
   * The system prop that allows defining system overrides as well as additional CSS styles.
   */
  sx?: SxProps<Theme>;
}
internal component
// Bar.tsx

// if this internal component can accept classes as prop
export interface BarClasses {
  root: string;
}

export interface BarProps {
  classes?: Partial<BarClasses>;
}

ClassKey

  • naming as {ComponentName}ClassKey
  • export if classes exists in props interface using keyof from {component}Classes.ts
// fooClasses.ts
export interface FooClasses {
  ...
}

export type FooClassKey = keyof FooClasses;
// verify that FooClassKey is union of string literal

Classes generator & Utility

  • export if classes exists in props interface from the component file
  • use {Component}Classes as type to preventing typo and missing classes
  • use Private prefix for internal component
Public component
// fooClasses.ts
export function getFooUtilityClass(slot: string) {
  return generateUtilityClass('MuiFoo', slot);
}

const useUtilityClasses = (ownerState: FooProps & { extraProp: boolean }) => {
  // extraProp might be the key/value from react context that this component access
  const { foo, disabled, classes } = ownerState;

  const slots = {
    root: ['root', foo && 'foo', disabled && 'disabled'],
  };

  return composeClasses(slots, getFooUtilityClass, classes);
};
internal component
// Bar.tsx
// in case that classes is not exposed.
// `classes` is used internally in this component
const classes = generateUtilityClasses('PrivateBar', ['root', 'bar']);

StyledComponent

  • naming using slot {ComponentName}{Slot}
  • use skipSx for internal component without specifying name, slot and overridesResolver
  • to extend interface of the styled component, pass argument to generic
public component
const FooRoot = styled(Typography, {
  name: 'MuiFoo',
  slot: 'Root',
  overridesResolver: (props, styles) => styles.root,
})({
  // styling
});
internal component
const BarRoot = styled(Typography, { skipSx: true })({
  // styling
});
extends interface
const BarRoot = styled(Typography, { skipSx: true })<{
  component?: React.ElementType;
  ownerState: BarProps;
}>(({ theme, ownerState }) => ({
  // styling
}));
// passing `component` to BarRoot is safe and we don't forget to pass ownerState
// <BarRoot component="span" ownerState={ownerState} />

Component declaration

  • prefer function Component() {} over React.FC
  • naming the render function in React.forwardRef (for devtools)
  • useThemeProps is needed only for public component
  • pass ownerState to StyledComponent for styling
public component
const Foo = React.forwardRef<HTMLSpanElement, FooProps>(function Foo(inProps, ref) => {
  // pass args like this, otherwise will get error about theme at return section
  const props = useThemeProps<Theme, FooProps, 'MuiFoo'>({
    props: inProps,
    name: 'MuiFoo',
  });
  const { children, className, ...other } = props

  // ...implementation

  const ownerState = { ...props, ...otherValue }

  const classes = useUtilityClasses(ownerState);

  return (
    <FooRoot
      ref={ref}
      className={clsx(classes.root, className)}
      ownerState={ownerState}
      {...other}
    >
      {children}
    </FooRoot>
  )
})
internal component
const classes = generateUtilityClasses('PrivateBar', ['selected']);

const BarRoot = styled('div', { skipSx: true })(({ theme }) => ({
  [`&.${classes.selected}`]: {
    color: theme.palette.text.primary,
  },
}));

// if this component does not need React.forwardRef, don't use React.FC
const Bar = (props: BarProps) => {
  const { className, selected, ...other } = props;
  return <BarRoot className={clsx({ [classes.selected]: selected })} {...other} />;
};