Perseus UI components
To see examples of the components in storybook live visit: ui-components demo page
Perseus now has a library containing the UI components to be reused used by all frontend projects that are exposed to end users. Internal project may use it as well if applicable.
Buttons, inputs, checkboxes, text and other components will be placed here and made available in npm and can be imported and used in any project.
Are you starting a new service? Are you creating something that uses a card with some shadow and inside you have input and buttons?...
... Then you are in the right place. Just add this library as your dependency and import the desired components (button, input and card) and use it ... They have the Perseus look and feel (reviewed by the design team) and you won't need much additional CSS for customisation.
Motivation
Last year (2019), Perseus had a rebrand/revamp initiative. With that frontend engineers had to revisit and redo the visual feedback of several components.
Example of what changed:
- Buttons had the background color and click animation
- Padding/margin of several components
- Shadows in cards
- And many more components in all pages...
Luckily by that time, there was only one project (super-frontend) to update. We took advantage of this task to identify and create a few generic component and get rid of duplications.
Now, we have created sales-tool. This project has a very similar look and feel, which means that components could have been reused from super-frontend. Although, we did not have time to work on a proper solution and we ended up duplicating the needed components (like buttons, input and cards for example).
How to generate and publish versions
when you are satified with your code progress and ready to commit your changes, make sure to add all the new changes by running the usual commands:
git add file/s
and
git commit -m 'commit message'
Now to generate a version before pushing changes to gitlab, run:
npm version 'type'
Example: npm version minor
- if changes are just a new feature
type = minor
- if changes are major upate with breaking changes
type = major
- if changes are just bug-fixes
type = patch
NB: (if the current branch already has a remote branch, it will be automatically pushed to the remote origin)
Tooling
Typescript
We are using Typescript to help the development:
-
Prevent runtime type errors. Who never had problems like
cannot read property of undefined
, or even the classicundefined is not a function
😂 . Not anymore! Now we catch those errors early in the development and prevent ourselves to publish something that can generate unpredictable error in any project. -
Documentation for those using the library. Once the types are created, they are the documentation that any other developer using this library will need :thinking_face:. No more wondering "What are the props I can pass to this component?", "what type is this prop 'disabled'?" or "I can pass 1 or 0 here... It will be converted to boolean in the end
🤷 ". Nope... Not anymore✋ . Good documentation for what and how you can use components👍 - And more advantages like learning!
🤓
Storybook
It is quite a challenge to develop a component library without actually seeing what you are building. No worries, we have Storybook in place!
When developing a new component for the library, please create a .stories.tsx
file with examples of how to use the component (you base yourself in the existing ones) and use it to see how your component is looking like. Storybook is also useful to test any functionality you may add (like animations, callbacks or event listeners).
All you have to do is run the following command and all components will be available in localhost:6006
. Feel free to explore the existing components and check how they look like.
npm run storybook
Prettier + Eslint
In order to establish a common ground or code styling practices we have the combination of Prettier and ESLint. They assure that all developers, regarding to which team he or she belongs.
For those who uses VSCode, there is a very helpful extension called Prettier. You can configure that to adjust your code when you save a file, which is very handy.
For those who doesn't use VSCode, you can run prettier in the command line as well by running:
npm run prettier
There is no automatic validation of the code at the moment, but in the future it will be very interesting to introduce pre-commit hooks to run tests and linters and prevent developers to merge broken code into master.
Rollup
Rollup is the bundler we use for parsing the typescript
files, types
and bundle
everything together to be published to npm. Once you build the application (see build section).
Styles
The library uses Emotion
in order to apply default styles and give the components the Perseus' look and feel. The lib gets often reviewed by Perseus Design Team to be make sure it follows the guidelines.
All styles can be found within the components themselves and generic values are placed under the folder src/utils/styles.ts
.
We are not using theme approach because it is uncertain how the project using the library could use it ... It may already have a theme provider in place and, therefore, things can conflict.
1 - Spread props in the top level container
2 - Use styled or regular HTML component If you want CSS to be overridable like. Example:
import styled from '@emotion/styled'
import Text from 'app/components/Text'
const MyStyledComponent = styled(Text)({
// THIS WILL OVERRIDE same style within Text component only if:
// 1 - Text is spreading props in the root (...props will carry both className and css)
// 2 - It does not use css property but styled instead (see Text component for example)
// - Otherwise, CSS can't be overruled 😕
padding: 12,
})
Explanations for that:
- https://emotion.sh/docs/css-prop#style-precedence
- https://codesandbox.io/s/emotion-10-styled-component-override-77q9y?from-embed
Generic props spreading
In order to make components generic, we need to spread props ({...props}
) in the root element.
Example: if your root element is a button, you probably want to support any props that can be passed to a button. But you don't want to map every single prop tha a button can receive. Therefore, you just want to do something like:
return (
<button {...props} />
)
although, eslint/typescript might complain about ...props
types. Fear nothing, for that you just need to extend your prop types declaration according to the HTML element you are exporting. In the past we had GenericProps
type that was in place just to provide type support for that. But now @Rener has learned how to do it in a better and more understandable way. Here is a handy list for what to extend and when:
https://gist.github.com/renerbaffa/b0177eeb3566db34e6782010592c7715
HTML sanitizer and markdown support
THe library supports Markdown for captions and texts. For that 2 other external libraries are used:
-
dompurify
- Lib is used to sanitize arguments received and props and prevent security leaks like XSS attacks and other attacks that make use of script injection. More details here -
html-react-parser
- Lib is used to parse strings received to HTML elements.
Dependencies
In the project (more specific in the package.json
file), we can specify what are the dependencies of the library. It is important to notice the difference between peerDependencies
vs dependencies
.
-
Basically
dependencies
will be installed together with the library in the project that is using it. -
On the other hand
peerDependencies
requires to be already installed in the project using our lib
Installation
Getting the library ready in your machine is very straight forward. All you need to do is clone the repo and run the following command in the root folder:
npm install
It will use npm to download and get all required dependencies ready.
Build
After installation you will be good to build the project. You can do so by running the following command also in the root folder:
npm run build
This command creates a folder called lib
. As mentioned in previous sections, it uses rollup
to parse and bundle everything and create the structure that will be eventually published to npm.
Versioning
There are a few details when it comes to versioning patterns and best practices that basically all packages available in npm follow. We are trying to follow them here.
- Every new version published should follow the
semver
approach. In summary this versioning system consists in managing a 3 digits number separated by.
like:3.2.16
. Here is a table to guide you when to increment each one:
Current Version | Stage | Changes in the code | Rule | Example version |
---|---|---|---|---|
1.0.0 |
Patch release | Backward compatible bug fix | Increment the third digit | 1.0.1 |
1.0.1 |
Minor release | Backward compatible new features | Increment the second digit and reset the third one to zero | 1.1.0 |
1.1.1 |
Major release | Any breaking change (new features or bug fixes that break the backward compatibility) | Increment the first digit and reset the other two to zero | 2.0.0 |
-
Every version published to npm should have a tag branch in the repo. This is recommended mainly for debugging. It means that if you have the version
1.0.5
installed in your project but the latest version published is4.5.0
and you need to check documentation; need to debug code for that version; or anything related to that particular version and you don't want/can't update the version right now, you can go to the repo, checkout to that particular tag (tags/1.0.5
) and you will have access to the exact code you need. -
Every publish will use the version described in the
package.json
file.
Please always remember to include version change in your MR (Merge Request) and be reasonable when it comes to deciding the version number. It is crucial to how npm works
Development process
Time to talk about how to contribute with the library. Have you gotten any new ideas? New components? Do you have a fix for a bug?... This section will teach you how to contribute!
-
First step is to create your branch out of master (or latest tag if something else is already in master). How to know which one to use? For that you will need to compare the commit of the latest tag and the commit in master. If they differ, it means that master contain more features that were not published yet. You need to be careful here to not publish undesired code with your feature.
-
Work on your task... do your commits... push your branch!
💪 -
Create the MR (updating the package version as per Versioning section)
-
Ask for review
🔍 -
Once you get the review approved by at least 1 other dev, merge it :merge:
-
Create a tag in gitlab from the commit you just merged.
Tag should match the version in the
package.json
- Manually build and publish the tag to npm (more details in the section below)
How to publish versions
Currently there is no automatic way to publish a new version to npm, but we will work on that soon. The manual steps to publish to npm is:
-
You need an account on npm
-
You need to be part of
frontend-engineers
team withinperseus-de
organisation in npm.
As of now, all frontend engineers of PPG team have admin rights, so all of them are able to publish and manage the organisation teams.
- You need to login in npm:
npm login
- Next is the build part (for more details, please check the build section):
npm run build
- last but not least, publish:
npm publish
Example how to import/use the library
Are creating a new project for Perseus? Got interested in reusing the look-and-feel of the components?...
... All you need to do in your project is to run:
npm install --save perseus-ui-components
and you will be good to start using the components. here is an example of how to use it after installation:
import * as React from 'react'
import { Text } from 'perseus-ui-components'
function MyComponent() {
return (
<div>
<div>Some cool stuff</div>
<Text header="wooow" content="This lib is awesome" />
</div>
)
}
export default MyComponent
More examples with the other components will be provided soon. For now you can check which components are available and how to use them through Storybook (please check the Storybook section here in the documentation).