RFC: Component Library

Status

Proposed

Author(s)

Antonella Sgalatta

Updated

2024-02-22

Objective

To implement the LiteFarm design system as a collection of generic React components that can be reused across the application, regardless of the page or feature being built.

Motivation

  • Currently the webapp package of our repository has two directories containing React components, components and containers. Components is meant to contain "pure" components which hold no business logic and don't connect to the Redux store, whereas containers holds more complex components handling application logic. However, there are several cases of files in the components folder containing smarter, more complex components, which indicates the distinction between the two can be confusing.

  • Because the components in the components folder are meant to not hold any logic, they should be more reusable and resemble more closely an implementation of a design system. However, a lot of these components are not generic and are built out for specific screens and purposes, e.g. CertificationSurvey, ChooseFarm or CropListPage, which means they aren’t applicable to new features and are not part of the design system.

  • All of the above makes it hard for developers to identify which components can be reused when building a new feature – this fosters the creation of more domain-specific components with each new feature built, which in turn aggravates the problem and slows down development.

  • Another ill consequence of having domain-specific components is the challenge to creating a cohesive user experience. When a UX or visual pattern is updated on the design side, several domain-specific components will need to be updated. An example of this is the PureTaskDropdownFilter component. This components implements quick filters, sorting and a filter button for more advanced filters – all features that could be useful for other pages in the application. However, since it’s built out specifically for tasks, if we wanted to introduce this same UX pattern to, for example, the Crops page, we’d need to either create another domain-specific component just for crops or either spend some time abstracting the component to make it reusable. Either way, shifting the mindset and creating reusable components to be generic from the start would save significant development time.

User Benefit

The main benefit to users from building the component library will be to ensure a cohesive user experience across the whole app. Secondarily, increased speed of development from reusing components would also benefit users indirectly as we could ship features to them faster.

Design Proposal

Generic Components

The key to speeding up development through the use of a component library will be to ensure that components are generic. I like to call generic components domain-agnostic, because they don’t contain any business logic or know anything about which portion of the app, page, or feature they will be used for. Some good examples of component libraries that implement generic components are Microsoft’s FluentUI and Atlassian’s atlaskit.

Separate Package

The proposal is to create the component library as a separate package within the monorepo, parallel to /webapp, /api, /end-to-end and /shared. 

Short term, keeping the library isolated from the webapp ensures:

  • That we shift our mindset towards abstract, reusable components and decouple them as much as possible from specific features within the actual UI code.

  • That the library acts as a clear interface or black box between any external libraries and our UI. This interface has the responsibility of ensuring cohesiveness and reflecting the vision of our own design system, regardless of which external libraries and components are used to build it. We can use any dependencies we want to get our components built faster, without that affecting the way we consume them in the final client, or without worrying about changes spreading throughout the app if we ever decide to deprecate the use of an external library or migrate it to something different.

Long term, there are some additional advantages to this decoupling:

  • If we ever split our webapp into multiple services, or if LiteFarm ever grows to a point where we’re maintaining multiple applications, we can easily ensure a uniform look and feel among them and save time and effort in building UI by reusing the same components.

  • If we get to that point, it would be easy to also actually version and publish the separate package as its own NPM library so that each client can specify which version they want to consume, and we can make changes to the components without breaking UI.

For more clarity on the proposal, check the diagram below. The component library can make use of different pre-built components or dependencies, but delivers a cohesive set of patterns that can be consumed by as many clients as needed. The main driving force for how the patterns in the component library should behave should be the design system.

Captura de pantalla 2024-02-22 a la(s) 12.56.14.png

 

Consistent Styling

We’re currently using three different approaches to styling in our components:

  • Most widely, SCSS modules. SCSS modules have the advantages of keeping the styles scoped to each specific component and only importing styles that are needed.

  • Inline styling. This approach is particularly unadvisable because these styles take precedence over the SCSS applied styles, making it really hard to debug styling problems and to reuse the same component with different styling.

  • MUI makeStyles. This approach has been deprecated by MUI in its latest version.

For the component library, the proposal is to use SCSS styling consistently. The team is most familiar with it and there aren’t any big cons with this approach.

Use of External Libraries

As mentioned earlier, as long as we keep the LiteFarm component library providing a consistent experience across the whole app, we can build those components basing them on external libraries (such as MUI) under the hood. The component library will essentially be a black box to the webapp. This means if we ever want to deprecate our use of a library or migrate it to something different, the changes will be isolated and encapsulated within the component library and the interface between it and the webapp will be able to remain the same.

As always, when we use external libraries we’ll have to be mindful of considerations such as bundle size and how well maintained they are.

Storybook

Right now, very similarly to our components folder, Storybook contains stories both for generic, reusable components and also for very specific components that are only used once throughout the app. Visualising those components in Storybook has limited value vs the effort required to add those stories, and results in a cluttered Storybook that makes it hard to find the components that are reusable.

The proposal is that we’d remove Storybook from the webapp package and instead implement it in the component library. Storybook would only contain stories for generic components that can be reused across multiple domains.

Accessibility

The component library will contain the building blocks for all of our UI. Taking that into account, ensuring these components are accessible should be our bare minimum. To ensure that, we can make use of a Storybook addon that we’re currently using on webapp, storybook-addon-a11y. This addon adds a tab to every story showing WCAG accessibility checks and whether the particular story passes them or not. While developing new components, we can check this tab. Having all checks pass should be part of our Definition of Done for tasks related to the component library.

Ideally, we would also run these accessibility checks on the CI such that it wouldn’t be possible to merge PRs where accessibility checks are not passing. This article illustrates how to do this.

Library Setup

There’s a currently open branch called LF-3893-RFC-for-component-library, where there's a basic setup for the component library structure, plus an example of one component with its corresponding stories.

The setup includes a separate project within the monorepo for the component library, with its own dependencies and Storybook setup. Some points to take into consideration:

  • While the previous components from webapp are being migrated, two Storybooks will coexist, which is less than ideal. This means the migration of key components that are actively being used in current development should be prioritised.

  • I haven’t set up i18next within the component library project. Since few core components have text that’s generic enough to be translated within the component itself, it seemed overkill to maintain a second translation structure in this repository. Instead, we’ll pass the already translated texts as props to core components, or pass in the translation function if needed.

  • For the new package, I setup the stories within each component’s folder, instead of on a separate directory. This will make it easier to see component implementation and story together.

  • Core styles, variables and mixins will also be part of and maintained withn the component library. For their use in webapp, we can import them from there.

Collaboration with Design

Connecting Storybook to Figma

As we’re going through the process of documenting specific components within the design system in Figma here, it’d be the perfect time to start connecting Storybook and Figma together. This would allow developers to visualise the designs when navigating through stories, and would also in turn allow designers to see which components in the design system have already been implemented and what that implementation looks like. There’s a Figma plugin for Storybook that we can use to achieve this goal.

Chromatic

Chromatic allows us to publish Storybook and do visual reviews on new components. There’s a Chromatic GitHub workflow setup that will publish Storybook to Chromatic on every push to a pull request that targets the integration branch. If there are any changes to any components, a UI review request will be open in Chromatic https://www.chromatic.com/reviews?appId=65d4c98178c38e062c35fc18 . UI reviews work very similarly to pull requests. Design will be able to see which components have changed with the diff highlighted, and approve or request additional changes. There’ll be a pending check on the pull request on GitHub until the UI review has been completed, so that the pull request won’t be able to be merged until Design has approved.

Component Implementation Workflow

With the use of Chromatic and Figma, we can come up with a new process for interacting and involving Design and making sure both developers and designers work collaboratively when developing new components for the library or implementing changes. Rough steps for tasks that involve changes to the component library would be as follows:

  1. Jira ticket is created. The ticket may come from technical breakdown of user stories by developers or from a request from Design, in the same way it currently works.

  2. Developer assigned to the ticket implements new components or changes to existing components. It’s key that stories are added for new components, or that existing stories are updated.

  3. Developer opens PR with changes. On top of regular reviewers for the pull request, there’ll automatically be a reviewer assigned for the UI review on Chromatic.

  4. Both PR review and UI review need to pass for the PR to be merged.

Engineering Impact

There’s little impact in effort for the development of new components, since the current effort also involves creating stories and the only difference now will be a mindset shift when designing them and working in a different project within the monorepo. There may be a slower development process at first due to the additional step of UI reviews, but this will ensure that what’s developed is aligned with design requirements and minimise the need for changes after components have been merged.

The largest effort involved will be migrating current components to the new project. We want to be careful when doing this and not migrate everything as is, since that would mean we’d end up in the same situation as we’re now. Two things we need to consider:

  • We need to selectively migrate components that are actually reusable and can be considered “core”, and remove stories for those that aren’t, until we get to a point where the previous Storybook can be removed altogether.

  • We need to make sure that for the components we do migrate, their specification matches the updated design system. If there isn’t an up to date design spec for the component, we should work with Design to get that in Figma.

To achieve this, I suggest doing a review of existing components and drafting a proposal for which ones should be included in the new library, guided by the design system that’s being implemented in Figma. Once we have that, we can create tickets to migrate the core components over time.

Sources

Template for this RFC: