Skip to content

Latest commit

 

History

History
235 lines (185 loc) · 5.24 KB

TYPESCRIPT_CONVENTION.md

File metadata and controls

235 lines (185 loc) · 5.24 KB

TypeScript convention

Component

Public components are considered all components exported from @material-ui/core or @material-ui/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 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
// Foo.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;
}

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
// Foo.tsx

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

Classes generator & Utility

  • export if classes exist 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
// Foo.tsx
export function getFooUtilityClass(slot: string) {
  return generateUtilityClass('MuiFoo', slot);
}

// make sure it has ClassKey as arg in generic
export const fooClasses: FooClasses = generateUtilityClasses('MuiFoo', ['root', 'foo', 'disabled']);

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

  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 }>({
  // styling
});
// passing `component` to BarRoot is safe
// <BarRoot component="span" />

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 styleProps 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 styleProps = { ...props, ...otherValue }

  const classes = useUtilityClasses(styleProps);

  return (
    <FooRoot
      ref={ref}
      className={clsx(classes.root, className)}
      styleProps={styleProps}
      {...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} />;
};