-
Notifications
You must be signed in to change notification settings - Fork 4
Developer Contribution Guide
The Elements project was started in 2019, has gone through various iterations and we have learned a lot along the way. We are very open to discussing changes to our ways of working over at the discussions section in this repo. Agreed chances to patterns of development, workflow and so on will make their way into this guide and will become the de-facto standard going forward.
In the interim, we ask that contributors stick to the basic conventions below.
Early versions of Elements leveraged a lot of third-party components to get to market quickly. This proved to be a mistake for a few reasons:
- Lack of granular control over design, required by our design teams. Theming third party components was more time consuming than writing the CSS ourselves and meant a mountain of override files.
- Dependency management was really hard. Upgrading and maintaining Elements around other developer's release cycles and updates was time consuming and painful.
- Lack of control over markup and compatibility with other apps. See below for specific sections on the library aims - third party dependency prevented us from being able to offer a decent DX for non-react users and made compatibility with other libraries hard.
As such, we settled on an almost zero dependency approach. There are some edge exceptions;
- React Components still assume you are working in a React App - React is a peer-dependency.
- The Linaria CSS-in-JS library is used to generate CSS classes. While the source code depends on it, Linaria is zero-runtime and so is really a compile time step, where the CSS is extracted into a style sheet and classes are inlined in the React Components. As such Linaria is not strictly a dependency and does not need to be installed by a project using elements.
We recognise that there are many use-cases for Elements. Many teams will have their own behaviours they will wish to implement, will have their own preferred libraries for tasks like building forms and will want to extend functionality to suit their application. A core principle of the library is therefore to focus on the presentational over the functional.
The core goal of Elements is to implement the Reapit Design System
With this in mind, please keep the guidelines below in mind when contributing. In all cases, the codebase should serve as a reference point since they are well established patterns.
- Try to avoid using JS where there are native HTML and CSS options - users can decide if they want a "fancy" date-picker or select box and implement themselves - our aim is to offer a styled native input.
- Keep in mind compatibility with other common libraries. React Hook Form for example is used extensively internally and leverages passing refs to the underlying HTML element - we should ensure we don't break this compatibility by ensuring form inputs behave as if they were standard native react tags.
- Extend the HTML interface - similarly to the above, where a Component returns a native element eg an input, the interface of the component should extend the default props of the native tag eg
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> { }. This ensures you can pass any valid input prop to your Elements Input using the{...rest}operator. - Rely on callback props. Beyond presentational JavaScript, we want to allow users to have the flexibility to implement their own business logic, and if necessary, maintain their own wrapper around Elements e.g. pagination controls or nav bars. As such, try to ensure you are passing appropriate callbacks to components as props that allow enough flexibility to customise the component behaviour.
- Many of our users do not work with React and as such, will use the classes like Bootstrap. Clearly, they will not get the full benefits however, always keep in mind offering an "MVP" experience for non-React devs, where they can add their own JS to toggle classes on and off in whatever framework to re-create the React experience.
- React Shorthands vs Component Trees. Following the above whist we want a good non-react experience, when using React, we want to reduce the amount of code teams write. A solid approach to this is to export both individual classes / linaria tags, and a React Short hand component that adds additional presentational functionality. This approach means we can offer an "MVP" for non-react users or, users that want more flexibility and a "batteries included" component for the typical React use case, with config options as props.
- Mobile first support - all components should be functional at all resolutions. Please try to follow a mobile-first approach to writing media queries, with the queries modifying the base mobile CSS at each larger resolution.
- Accessibility - this is an area the library requires remediation. There are tickets in the backlog to add Storybook accessibility testing to the stack. In the interim, please follow a "good citizen" principle when working on the project and if you can see an accessibility improvement you can make, please do so and include in your PR.
When using an Elements React Component, you are essentially importing a series of JSX elements with classes added to them - see above regarding Linaria.
Base styles are largely handled by PascalCased react components, whereas modifiers are handled by standalone camelCased classes that are applied using the className prop.
All of out component and class names are then kebab-cased by the compiler for use in your code.
By convention all classes are prefixed by the el- modifier, to avoid conflicting with any other similar CSS classes you may have on your page.
Take the contrived example below;
import { FooBar, elFooMod } from '@reapit/elements'
const MyComponent = () => <FooBar className={elFooMod}>My cool content</FooBar>This will render your cool content like this:
<div class="el-foo-bar el-foo-mod">My cool content</div>The styled component base has provided you the .el-foo-bar class and the modifier has applied .el-foo-mod as an additional style.
In all the stories we expose all classes as React components, bundled together for convenience, plus the raw css classes as JSX components, and the compiled css classes - this gives the developer maximum flexibility over how to implement Elements.
Don't forget to export your component from the root manifest index.ts file or it won't be included in the NPM package!
We also ship a number of helper classes to handle things like flexbox sizing and spacing with margins and padding.
Non-React users can then just add the classes to their markup seen in the rendered markup canvases on each page of the docs.
With the v5 release refactor, we aim to achieve a consistent project structure for each component exported from the library, building developer familiarity and consistent separation of concerns.
At the highest level, everything exported from the project lives in the /src directory, where there is an index.ts file that is the entry point when the library is compiled for NPM distribution. When adding a new component, it's own index.ts should be added to the main index or it will not be included in the distributed lib.
At the next level up, the main directories are as follows (reference the Accordion component as an example):
Components: Each component in Storybook has it's own subdirectory within, containing;
-
index.ts- a barrel file exporting every component, type, function and style you wish to export from the main lib -
styles.ts- containing all the component specific Linaria components / classes being exported -
types.ts- containing the component interfaces and defined types to be exported -
<<component-name>>.tsx- the main React component molecule, complete with any auxiliary functions that handle behaviour -
<<component-name>>.atoms.tsx- where the main component is made up of composed atoms that you wish to also export to allow maximum developer flexibility. It is also fine to have one atom per file, or an atoms directory at developer preference. -
<<component-name>>.story.tsx- the rendered components to be included in the documentation.mdxfile -
<<component-name>>.mdx- the docs for the component rendering the story variants -
__tests__- directory containing relevant jest unit tests, one file per file, following the same naming convention as the tested file, but with the.test.tssuffix.
Hooks: Any hooks and accompanying providers should be exported from a directory using the use-<<feature-name>> convention. As with components, hooks should have an index, story, mdx docs and tests per directory.
Patterns: Patterns will be specified and documented as part of the design process. They will be composed of pre-existing components so only require a story and mdx file per pattern.
Storybook This directory contains any helpers or documentation used to build the Storybook itself e.g. the changelog and welcome pages - nothing in this folder is included for distribution in the library itself.
Styles A directory for miscellaneous styles like helpers, global styles and utility classes. These should be documented under a "Utilities" section of storybook. They do not require tests, but are exported by the main lib.
When documenting components, we should always take a developer experience first approach - we are developers, documenting for other developers so try to adopt the mindset of documenting what you would find useful.
Notes on usage from the Design Team should form the first part of the docs for any component - these will be added to the ticket under the "design guidelines" section. You should export the relevant section from Figma as an SVG and include in the MDX file for the component. See the pattern in the Accordion Component as an example. See also here for how to add a new guidelines SVG to the project.
Additionally, we have a Figma plugin in Storybook which inlines the original DS assets in a tab. See here for how to implement this.
When writing stories, for simple single "atom" type components like a Button or single Input you will just need ensure developers can visualise all the available states / and / or props available. There are two approaches to this, both are valid.
The first is to use controls, code example below, documenting a button.
export default {
title: 'Button',
component: Button,
}
// Using the args object, I can edit away in real time using any of the props using controls.
export const Usage = {
args: {
intent: 'primary',
children: 'Button Text',
},
}In this example, the controls are all editable as per the screenshot below so whist the button started as a "primary" button, I can change it in live time to have intent "danger".
The advantage of this is fewer stories and less documentation, however, you may prefer to create a separate story for each state. This is totally valid too and up to developer preference.
For more complex, composed stories, like a ButtonGroup with Buttons rendered within, you will need to use the render syntax below:
export const Group = {
render: ({}) => (
<ButtonGroup>
<Button intent="primary">Primary</Button>
<Button intent="neutral">Default</Button>
<Button intent="danger">Danger</Button>
</ButtonGroup>
)
}This has the advantage of maximum flexibility on what you want to render within your story and is arguably more readable and familiar for future maintainers.
Where a component is complex, like an Accordion, composed of atoms like AccordionBody and AccordionTitle we ask that you export and document, both the atomic "styles only" usage as well as the composed "batteries included" composed, React Component. The basic distinction in approaches is as follows:
Styles Only Components - everything is an atom, maximum flexibility, more code. Suitable for cross platform dev and when you need to do something non-vanilla. You will need to add all JS interactivity yourself.
Each of the linaria classes are wrapped and exported as atoms, example here. Then can be composed as you like, example here.
In this scenario, if you want to override a style, you can just add a class to any of the atoms using the className prop, from whatever framework you like - css modules, sass, linaria, whatever - and they will be passed down to the underlying markup. Max flexibility.
React Components, far less code, much more opinionated. Batteries included, all JS behaviours handled by the component, suitable only for React devs.
We do the heavy lifting for you by adding the standard JS behaviour and vanilla markup in the composed component e.g. here. The things that change are just passed as props and handled by the composed component - it "just works" for the happy path example.
In this scenario, far less code but also way less flexibility.
In the stories, please make the distinction between these two use cases by using the "Styles Only Usage" and "React Usage" naming convention as per below.
- Code style can be divisive and is a largely unproductive debate. Aside from the functional guidelines above we don't enforce a style guide, beyond adhering to ESLint and Prettier standards included in PR checks. Beyond this, we just ask that contributors look at the library as is and try to stick to the conventions already implemented.
- The project is heavily unit tested using Jest. Whilst we do not enforce any code coverage levels, the expectation is that any code changes or additions have appropriate test coverage included for the change requested.
- Storybook docs - many developers internally and externally use these docs, please use the existing docs as a guide as to the level of detail expected for each story.
From July '24 until completion of a v5 release candidate, we are conducting a methodical refactor of each component to achieve:
- Design system parity: The aim is to be 100% compliant with the Reapit Design System where possible. There will likely be some edge cases, for example the Material UI datepicker used by the Design Team can be used internally but would not be part of Elements.
- Code consistency: Whilst the codebase is relatively uniform, developer experience would be improved for both users and contributors if the codebase were harmonised.
- Introduce patterns: We have typically been good at documenting component atoms and molecules, however, we are far less good at documenting how users should implement pages and solve problems. For this version, we will bring a patterns section to the docs that will serve as a developer guide for common UI problems.
Each component will have its own ticket for refactor. Any ticket in the "TODO" column on the Kanban board is considered ready for dev by the Product and Design team, and can be picked up by any developer in the team. Similarly, any ticket in "To Review" is still awaiting finalised specification and will be moved to "TODO" when ready.
Each ticket will have a specification and follow the same basic template pattern - the spec will include, where relevant:
- Is new component or existing?
The vast majority of components will be pre-existing in the library however, in some cases a new component may be required
- Is the component being re-named?
At this release, some components will be re-named to align with the Design System naming conventions, for example, Nav will become AppBar in the v5 release
- Is the component design signed off by product and design?
This is just to inform developers that a ticket is ready for dev. If a given component is required and this is not signed off, reach out to the Product & Design teams in the first instance to determine progress
- Guide to component structure & documentation in storybook:
A link to this guide, for ease of reference
- Link to DS in Figma:
A direct link to the relevant component design in the Figma Design System
- Link to Elements Audit in Figma:
If the component is pre-existing, the Design Team will have audited the component with detailed notes on any changes that need to be made to align with the Design System - this section will direct link to the audit notes in Figma
- Additional behavioural specification:
Any additional notes for the developer - perhaps there is an animation to implement, or there needs to be a breakdown of the component into atoms and a composed React version
- Accessibility requirements:
Whilst a lot of work has been done to improve accessibility in the library recently, v5 gives us an opportunity to implement best practice in this area. Relevant links to specifications for each component will be supplied
Developers picking up a ticket should follow the developer checklist to ensure all development tasks are completed prior to requesting a pull request.
- Styles alignment between Design System and Elements.
The most important task - adjust the styles and markup to be aligned as closely as possible with the DS
- Check design tokens in Figma and implement CSS variable tokens if available for relevant component
_We now have syncing between design tokens defined in Figma and CSS variables in Elements. Where a design token is used in Figma, there should be a corresponding variable in the global library e.g. --button-text-size. If a relevant variable exists already, please implement it in your component.
- Align with accessibility standards / spec as per above.
Where an accessibility spec is provided, attempt to implement best practice in your component. Use the accessibility tab in Storybook as an aid to this
- If relevant, break down component into
Styles OnlyandReactcomponent structures
For more complex components, refer to the "Documenting Components" section above
- Ensure all variants of components are documented as appropriate
Again, refer to this guide
- Ensure unit test coverage is adequate for component
There are no fixed rules around coverage but generally more is preferred - all components should have adequate coverage based on reasonable developer discretion
- Update documentation in MDX file as per guidelines
Again, refer to this guide
- Changelog updated to reflect a single beta version per component ideally
Please add a new line item for a beta release to the changelog.mdx file, with a short summary of what has changed. Ideally, one beta release per component refactor to make implementation easier for teams
As part of each ticket, assuming the developer checklist has been completed, the following steps apply:
- Approved PR merged to main
One review from one of the team is required however, I would encourage two or more if the changes are complex
- Design & product review and feedback addressed by developer
Merging a PR triggers a beta dev release to https://elements-beta.dev.paas.reapit.cloud/ where it will be reviewed by the product and design teams and possibly remediation requested via the issue
- Beta release by product / engineering lead to next beta version
Assuming product and design sign off, the component can be released to beta using the developer workflow guide in these docs
From v3 to v4 of the library, we avoided any breaking changes to ensure a straightforward migration path for users to align with the new brand and achieve a decent level of parity with the design system.
For v5, breaking changes will be necessary to achieve full parity but of course, tearing up or re-naming a given component will mean more refactoring for teams downstream.
If you can perform a refactor without breaking component, class names, adding and removing props or renaming things, your colleagues will thank you later as they will have less work to do!
In summary, best efforts to avoid changing things with downstream impact but the main goal should always be to develop the best library we can and align with the Design System. In doing this, inevitably there will be managed breaking changes and this is fine for the v5 release.