npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@cmpsr/components

v7.4.0

Published

[![GitHub Actions status](https://github.com/cmpsr/composer/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/cmpsr/composer/actions/workflows/test.yml) [![Chromatic Actions status](https://github.com/cmpsr/composer/actions/workflows

Downloads

1,295

Readme

Composer logo Composer components library

GitHub Actions status Chromatic Actions status Storybook node version

The composer component library is an opinionated wrapper on top of chakra-ui that allows defining a branded theme instead of a generic one designed to work with the composer ecosystem.

Design resources

Implementing new components

For the time being only components defined in figma will be accepted as contributions to the library and the implementation must match the design specifications for the pull request to be approved and merged.

When adding new components to the library ideally you will reuse the chakra's equivalent as much as possible (see an example), unless you find that the counterpart does fit the specs (see an example).

It is required to add stories for all component, the storybook can be started by executing yarn storybook from the ./packages/components folder.

cd packages/components
yarn storybook

The implementation of a component will be divided in two parts, theming and implementation. Ideally when a new component is added to the library the main focus of the pull request will be on the theme and not in the implementation of the component.

Theming components

All the UI customisation applied to a component must be defined inside the theme.

The theme of a component will be defined in a file with the name of the component inside the packages/components/src/theme/ComposerTheme/Components folder. This is true for components that already exists in chakra and for new components added by us, like TextPairing. Once defined the component has to be re-exported from the index file.

  • The text style applied to a component should be one of the text styles already defined. In some cases applying it using the textStyle prop is not possible (chakra will not apply it to the component) and we have to apply the properties of the style one by one, in those cases use the functional option theme param to apply the styles:
export const Component: ComponentStyleConfig = {
  sizes: {
    xs: ({ theme }) => ({
      ...theme.textStyles["text-body-floating-label-medium"],
    }),
  },
};
  • always use rem/em instead of px, do not use either for 0 (see for more details [1][2][3])
export const Component: ComponentStyleConfig = {
  sizes: {
    xs: {
      p: "1rem", // 👍
      p: "16px", // 👎
      p: 0, // 👍
      p: "0", // 👍
      p: "0rem", // 👎
      p: "0px", // 👎
    },
  },
};
export const Component: ComponentStyleConfig = {
  sizes: {
    xs: {
      borderRadius: "0.125rem", // 👍
      px: "0.5rem", //  👍
      py: "spacer-2", // 👎 (unless specified in figma)
    },
  },
};

export const Card: ComponentStyleConfig = {
  baseStyle: {
    borderRadius: "radii-card", // 👍 specified in figma
    padding: "1.25rem", //  👍
  },
};
  • If a component only have a single variant do not add a variant, use the baseStyle instead:
export const Component: ComponentStyleConfig = {
  // 👎
  variant: {
    onlyVariant: {
      borderRadius: "0.125rem",
    },
  },
};

export const Component: ComponentStyleConfig = {
  //  👍
  baseStyle: {
    borderRadius: "0.125rem",
  },
};
  • All components with multiple variants or sizes must have a defaultProps section in their props with the default variant and or size. If the default values are not defined in figma ping the design team in slack or add a comment in the pull request and will get those values for you.
export const Component: ComponentStyleConfig = {
  variants: {
    primary: {},
    secondary: {},
  },
  sizes: {
    s: {},
    m: {},
    l: {},
  }
  defaultProps: {
    variant: 'primary'
    size: 'm',
  },
};

Implementing the component

The non-theme related code of the component will live in the packages/components/src/components folder, inside a folder that defines the category of the component like form, layouts, navigation, etc. For each component we will create a new folder inside the corresponding category folder, if the category folder does not exists it should be created too, so a new form Component will have to be defined in the packages/components/src/components/form/Component folder for instance.

  • Every category folder will have an index file that will re-export all the components inside the category (see form folder example), the re-exports have to be done in alphabetical order. If a new category is added a re-export in the parent folder index has to be added too, also in alphabetical order:
// packages/components/src/components/index.ts
export * from "./form";
export * from "./layouts";

// packages/components/src/components/layout/index.ts
export * from "./Box";
export * from "./Flex";
export * from "./Grid";
export * from "./Section";
  • For each component we will create 5 files. For more details on what to do and not to do in each continue reading.

    • types.ts: types associated to the component (Props, Size, Variant, ...)
    • Component.tsx: component implementation
    • Component.test.tsx: unit tests
    • Component.stories.tsx: component storybook
    • index.ts: re-export the component and the types
  • In the types.ts we will define all types associated for a component. As a bare minimum this file will contain the props that the component accepts. If a component is based on a chakra component its properties should extends its counterpart props. If a component has variants and or sizes we will create a custom type for those.

import { ComponentsProps as ChakraComponentProps } from "@chakra-ui/react";

// 👍
export const componentVariants = ["primary", "secondary"] as const;
export type ComponentVariant = (typeof componentVariants)[number];
export const componentSizes = ["xs", "s", "m", "l"] as const;
export type ComponentSize = (typeof componentSizes)[number];

export interface ComponentProps extends ChakraComponentProps {
  variant?: ComponentVariant;
  size?: ComponentSize;
}

// 👎 prefix with the component name
export const variants = ["primary", "secondary"] as const;
export const Variant = typeof variants[number];

// 👎 do not use first upper cased later for variables, only for types
export const ComponentVariants = ["primary", "secondary"] as const;

// 👎 do not use plural for size or variant types
export const ComponentVariants = typeof componentVariants[number];
export const ComponentSizes = typeof componentSizes[number];
  • If the props are exactly the same as the chakra component do not define a new type, just re-export the props from types.ts
export { FlexProps } from "@chakra-ui/react"; // 👍

// 👎 Do not define a new _empty_ type
import { FlexProps as ChakraFlexProps } from "@chakra-ui/react";
export interface FlexProps extends ChakraFlexProps {}
  • In the Component.tsx is where the actual implementation of the component will be. The goal is to have the minimum amount of code possible, i.e. only add logic to a chakra component if there are no other options.

  • All components has to be exported as forward references using the forwardRef function exposed by chakra and not React.forwardRef. This rule might not be followed in the examples shown in the documentation for simplicity.

export { forwardRef } from "@chakra-ui/react"; // 👍
export { forwardRef } from "react"; // 👎
  • From the implementation file you should never access a theme file, if you need to assign some theme props by code use the useStyleConfig (docs) or useMultiStyleConfig (docs) and then apply the props:
// 👍
const Component: FC<ComponentProps> = ({ size, ...rest }) => {
  const styles = useStyleConfig("Component", { size }) as ComponentStyle;
  return (
    <ChakraComponent data-testid="cmpsr.component.id" {...styles} {...rest} />
  );
};

// 👎
// The main issue with this approach is that if a user overrides our theme
// we will not apply the appropriate values.
//
// Our build script should detect this and fail
import { Component as Styles } from "@theme/ComposerTheme/Components/Component";
const Component: FC<ComponentProps> = ({ size, ...rest }) => {
  const styles = Styles[size] as ComponentStyle;
  return (
    <ChakraComponent data-testid="cmpsr.component.id" {...styles} {...rest} />
  );
};
  • any id added to a component should be prefixed with cmpsr.component-name.prop-name, including data-testid, as show in the previous snipped. And should be overridable.
// 👍
const Component: FC<ComponentProps> = (props) => (
  <ChakraComponent data-testid="cmpsr.component.data-testid" {...props} />
);

// 👎
const Component: FC<ComponentProps> = (props) => (
  <ChakraComponent {...props} data-testid="cmpsr.component.data-testid" />
);
  • you should avoid adding layout/theme values to the component implementation, they should be defined in the theme file instead:
// 👍
export const Component: FC<ComponentProps> = (props) => {
  const { childrenBg, p } = useStyleConfig("Component", props);
  return (
    <ChakraComponent p={p}>
      <Children bg={childrenBg} />
    </ChakraComponent>
  );
};

// 🤔
// Doing this is not a red flag, but should be avoided
export const Component: FC<ComponentProps> = (props) => (
  <ChakraComponent p="0.5rem" {...props}>
    <Children bg="background-action-active" />
  </ChakraComponent>
);
  • The Component.test.tsx file is for unit testing the component. As we are relying on chakra-ui and we know their components are properly tested we will reduce the amount of unit tests added to a component, for example there is no reason to add a unit test to verify that the onClick prop is called when a Button is clicked. It is required to unit test any logic added to the component, but no UI changes as those will be verified by storybook+chromatic. If you find yourself mocking chakra-ui inside the unit test of a component you should consider this as a red flag. Testing variant, size and other theme props can be considered as a red flag too.

  • The storybook file Component.stories.tsx must render all the variants and sizes of the component. The storybook should be clear and self-explanatory, ideally in a table format. All stories file must have a Playground component where the user can interact with the component and test the main properties:

const Template = ({ showLeadingIcon, showTrailingIcon, ...args }) => (
  <Button
    {...(showLeadingIcon && { leadingIcon: Icons.IconExternalLink })}
    {...(showTrailingIcon && { trailingIcon: Icons.IconExternalLink })}
    {...args}
  >
    Playground
  </Button>
);

export const Playground = Template.bind({});
Playground.args = {
  variant: "primary",
  size: "m",
  children: "Composer button!",
  isLoading: false,
  showLeadingIcon: true,
  showTrailingIcon: true,
  isDisabled: false,
};
  • if a component has variant and/or size we should tell storybook how to handle those properly:
export default {
  component: Component,
  title: "Components/Form/Component",
  argTypes: {
    variant: {
      options: componentVariants,
      control: { type: "select" },
    },
  },
} as Meta;
  • the index.ts is responsible of re-exporting the component and the associated types:
export * from "./Component";
export * from "./types"; // It is ok to only re-export ComponentProps
  • if you have to import a component or hook in the component you are currently working on it should be imported from @/components or @hooks unless the imported file is in the same folder as your component. You can use the relative path import if you are going only going one level out:
import { OtherComponent } from "@components"; // 👍
import { useResponsiveValue } from "@hooks"; // 👍
import { OtherComponent } from "."; // 👍
import { ComponentProps } from "./types"; // 👍
import { OtherComponent } from ".."; // 👍
import { OtherComponent } from "../OtherComponent"; // 👍
import { OtherComponent } from "../../OtherComponent"; // 👎
  • you can only import other components directly from chakra if the component does not exists in our library:
import { VStack } from "@chakra-ui/react"; // 👍
import { Button } from "@chakra-ui/react"; // 👎

Using dot notation

We will favour using dot notation over composed component names.

// 👍
const Component = forwardRef<ComponentProps, 'div'>((props, ref) => (
    <div ref={ref} {...props}/>
  )
);
const Child = forwardRef<ComponentChildProps, 'div'>((props, ref) => (
    <div ref={ref} {...props}/>
  )
);
const ComponentNamespace = Object.assign(Component, { Child });
export { ComponentNamespace as Component };

// 👎
export Component = forwardRef<ComponentProps, 'div'>((props, ref) => (
    <div ref={ref} {...props}/>
  )
);
export const ComponentChild = forwardRef<ComponentChildProps, 'div'>((props, ref) => (
    <div ref={ref} {...props}/>
  )
);

You can take a look to the implementation of the Slider or Breadcrumb components for more details about how we do it.

Special cases

There are a few cases of components that we will use literally as it from chakra (like Box or Flex). In those cases we will only add an index.ts and a Component.stories.tsx. The index file will just re-export the component and its props from chakra:

export { Box, BoxProps } from "@chakra-ui/react";

Common pitfalls

  • Chakra components are designed as multipart/composite components. A common mistake we have experienced is assigning the textStyle to a multipart component, this will not always produce the desired effect. In general the textStyle should be applied to the label part.
export const Component: ComponentStyleConfig = {
  sizes: {
    xs: ({ theme }) => ({
      label: {
        ...theme.textStyles["text-body-floating-label-medium"],
      }),
    },
  },
};

Composer Icons

Composer icons are a subset of @tabler/icons-react.

Icons Update

If a new version of tabler icons has been release and you just want to update the existing icons, then you only need to update the @tabler/icons-react version in the package.json and you should be done.

But to be safe, run yarn gen:icons to ensure that @tabler/icons-react hasn't released a breaking change. No icons will be generated, but the script will throw if there is a breaking change.

Icons Addition or Removal

If you wanna add or remove an icon, ensure to add or remove it from the icons list on the generate_icons.js file.

Once @tabler/icons-react has been updated and all icons you want to keep are on the list, you can then run this command to generate the icons and their stories.

yarn gen:icons

All icon files will be generated and ready to commit. Just remember that if an icon has been removed or renamed that is a breaking change in Composer and you should update the Composer version accordingly.

tabler/icons breaking change

If @tabler/icons-react releases a breaking change, the generate_icons.js script may throw and it should be adjusted as needed.

Releasing your changes

Before creating the pull request you have to generate a changeset for your components, follow the instructions in here.