We are happy that you wish to contribute to this project. For that reason, we present you with this guide.
There are different ways to contribute, each with a different level of involvement and technical knowledge required, such as:
Please read this document carefully. It will help maintainers and readers in solving your issue(s), evaluating your feature request, etc.
We welcome clear, detailed bug reports.
Bugs are considered features that are not working as described in documentation.
If you've found a bug, please file a report in our issue tracker.
Search to see if it has already been reported via the issue search. If so, up-vote it (using GitHub reactions) or add additional helpful details to the existing issue to show that it's affecting multiple people.
Check if the issue has been fixed — try to reproduce it using the latest
master
or development branch in the repository.
New feature requests are welcomed. Analyze whether the idea fits within the scope of the project. Then, detail your request, ensuring context and use case is provided.
Please provide:
- A detailed description of the advantages of your request
- A potential implementation or design
- Whatever else you have in your mind 🤓
-
Fork the repository on Github.
-
Run the environment locally:
#clone repository git clone https://github.com/[GITHUB_USERNAME]/design-system.git #change directory to the cloned repository cd design-system #create new branch git checkout -b [BRANCH_NAME] #install dependencies npm install #start development server npm run dev
The server runs on port 5000 and url is opened automaticaly in browser as the project uses storybook for component developement and documentation.
#change directory according to component type
#ELEMENT_TYPE = atoms | molecules | organisms
cd core/[ELEMENT_TYPE]
#make component directory
mkdir [COMPONENT_NAME(in camel case)]
#change directory to component
cd [COMPONENT_NAME]
#make directories for stories and tests
mkdir __tests__
mkdir __stories__
mkdir __stories__/variants
-
Extensions: Use
.tsx
extension for components. -
Filename: Use PascalCase for filenames. E.g.,
DropdownOptions.tsx
.import DropdownOptions from './DropdownOptions';
-
Component Naming: Use the filename as the component name. For example,
DropdownOptions.tsx
should have a reference name ofDropdownOptions
. However, for root components of a directory, useindex.tsx
as the filename and use the directory name as the component name:
- Component types names must be uppercase, e.g. Appearance, Size, etc.
- Component Props interface should be named as
[COMPONENT_NAME]Props
, e.g. AvatarProps, HeadingProps. - Every component props interface should extend BaseProps (e.g className, data-test). Properties inside BaseProps interface are defined in
@/utils/types
.
- Export Component as named export, e.g. export Avatar from
./core/index.tsx
. - Export Component Props from
./core/index.type.tsx
- Jest and React Testing Library is used for unit testing and coverage.
- Snapshots and unit tests are written in
[COMPONENT_NAME].test.tsx
file inside__tests__
folder, e.g. Avatar.test.tsx.
- jsdoc is used for prop description
- Custom props can be passed to docPage from corresponding story as follows:
- Metadata is as follows:
title
: Determines where story will show up in the navigation UI story hierarchy. Read more here.- title: The title of the component you export in the default export controls the name shown in the sidebar.
Components
in below example is the title. - Grouping: It is also possible to group related components in an expandable interface in order to help with Storybook organization.
Avatar
in this example is the group. - Single Story: Last part of title will be hoisted up to replace their parent component in the sidebar. Example:
All
- title: The title of the component you export in the default export controls the name shown in the sidebar.
component
: Used by addons for automatic prop table generation and display of other component metadataparameters
: Used to control the behavior of Storybook features and addons.subcomponents
: Used to include subcomponents in prop table.
// Storybook CSF Format
export default {
title: 'Components/Avatar/All',
component: Avatar,
subcomponent: Avatar,
parameters: {
docs: {
docPage: {
noHtml: true, // Includes Html in docPage
noStory: true, // Includes Story in docPage
noProps: true, // Includes props table in docPage,
noSandbox: true, // Includes code sandbox button in docPage,
title: 'Avatar', // Custom title
description: 'Dummy description', // Custom description
customCode: '() => <Avatar>JD</Avatar>', // Custom code for live code editor,
},
},
},
};
Let's assume we want to add Avatar component in Atoms category.
cd core/atoms
mkdir avatar
cd avatar
mkdir __tests__
mkdir __stories__
mkdir __stories__/variants
Now we will add Avatar component logic.
- Import Design System components from
@index
. - Import Design System component props from
@/index.type
. - Import BaseProps from
@/utils/type
;
import * as React from 'react';
import classNames from 'classnames';
import { Text, Tooltip, Icon } from '@/index';
import { TooltipProps } from '@/index.type';
import { BaseProps, extractBaseProps } from '@/utils/types';
export type Appearance =
| 'secondary'
| 'primary'
| 'alert'
| 'warning'
| 'success'
| 'accent1'
| 'accent2'
| 'accent3'
| 'accent4';
export type Size = 'regular' | 'tiny';
- Component props interface should extend BaseProps.
- Additional information of a prop is written in
/** */
block.
export interface AvatarProps extends BaseProps {
/**
* Color of the `Avatar`
*/
appearance?: Appearance;
/**
* **Only first 2 characters are rendered (SOON TO BE DEPRECATED)**
*/
children?: string;
/**
* First Name
*/
firstName?: string;
/**
* Last Name
*/
lastName?: string;
/**
* Determines if tooltip is visible
*/
withTooltip: boolean;
/**
* Position to place the tooltip
*/
tooltipPosition: TooltipProps['position'];
/**
* Determines size of `Avatar`
*/
size: Size;
}
- Props are destructured (Note that Avatar is a Function Component).
export const Avatar = (props: AvatarProps) => {
const {
withTooltip,
tooltipPosition,
size,
children,
firstName,
lastName,
className,
appearance,
} = props;
- BaseProps are extracted via
extractBaseProps
function;
const baseProps = extractBaseProps(props);
const initials = children
? children.trim().slice(0, initialsLength)
: `${firstName ? firstName.trim()[0] : ''}${lastName ? lastName.trim()[0] : ''}`;
const tooltip = children || `${firstName || ''} ${lastName || ''}` || '';
const DefaultAppearance = 'secondary';
const colors = ['accent4', 'primary', 'accent3', 'alert', 'accent2', 'warning', 'accent1', 'success'];
const AvatarAppearance =
appearance || colors[(initials.charCodeAt(0) + (initials.charCodeAt(1) || 0)) % 8] || DefaultAppearance;
- ClassNames is a utility for conditionally joining CSS classNames together.
- CSS is added according to BEM Convention.
const classes = classNames(
{
Avatar: true,
[`Avatar--${size}`]: size,
[`Avatar--${AvatarAppearance}`]: AvatarAppearance,
['Avatar--disabled']: !initials || !withTooltip,
},
className
);
const ContentClass = classNames({
[`Avatar-content--${size}`]: size,
[`Avatar-content--${AvatarAppearance}`]: AvatarAppearance,
});
const IconClass = classNames({
[`Avatar-content--${AvatarAppearance}`]: AvatarAppearance,
});
- Add rendering logic and
data-test
attribute. - Convention for adding data-test id is
DesignSystem-[COMPONENT_NAME]
and it is used for testing.
const renderAvatar = () => {
return (
<span data-test="DesignSystem-Avatar" {...baseProps} className={classes} >
{initials && (
<Text
weight="medium"
appearance={'white'}
className={ContentClass}
>
{initials}
</Text>
)}
{!initials && (
<Icon
data-test="DesignSystem-AvatarIcon"
name="person"
size={size === 'regular' ? 16 : 12}
appearance={'white'}
className={IconClass}
/>
)}
</span>
);
};
const renderTooltip = () => {
if (withTooltip && initials) {
return (
<Tooltip tooltip={tooltip} position={tooltipPosition} triggerClass={'flex-grow-0'}>
{renderAvatar()}
</Tooltip>
);
}
return renderAvatar();
};
return renderTooltip();
};
- Export Avatar component
Avatar.displayName = 'Avatar';
export default Avatar;
export { default } from './Avatar';
export * from './Avatar';
A story captures the rendered state of a UI component. We write multiple stories per component that describe all the interesting states a component can support.
- Import component and story knobs.
import * as React from 'react';
import { select, text } from '@storybook/addon-knobs';
import { Avatar } from '@/index';
- Every named export in the file represents the name of the story, in this case
All
. - Storybook knobs (e.g. select, text, boolean) allows to edit props dynamically using the Storybook UI.
export const all = () => {
const appearance = select(
'appearance',
['primary', 'alert', 'warning', 'success', 'accent1', 'accent2', 'accent3', 'accent4', 'secondary'],
undefined
);
const size = select('size', ['regular', 'tiny'], undefined);
const withTooltip = boolean('withTooltip', true);
const children = text('children', 'JD');
return (
<Avatar appearance={appearance} size={size} withTooltip={withTooltip}>
{children}
</Avatar>
);
};
- The default export defines metadata about component.
- Title
Avatar/All
will show up in the navigation UI story hierarchy.
export default {
title: 'Components/Avatar/All'',
component: Avatar
};
We write stories corresponding to each prop in the variants folder. For example: size
and appearance
are props of Avatar component. Let us look at an example of size
variant story.
- Import the required components.
import * as React from 'react';
import { Avatar, Text } from '@/index';
- Name of the story in this case will be
Size
. - JSX corresponding to this story will look like this:
export const size = () => (
<div className="d-flex">
<div className="mr-9 d-flex flex-column">
<Text weight="strong">Regular</Text> <br />
<Avatar firstName="John" lastName="Doe" />
</div>
<div className="d-flex flex-column">
<Text weight="strong">Tiny</Text> <br />
<Avatar firstName="John" lastName="Doe" size="tiny" />
</div>
</div>
);
- The default export defines metadata about component.
export default {
title: 'Components/Avatar/Variants/Appearance',
component: Avatar,
};
Now we will add snapshot and unit testing.
import * as React from 'react';
import { render } from '@testing-library/react';
import { Avatar } from '@/index';
import { AvatarProps as Props } from '@/index.type';
import { testHelper, filterUndefined, valueHelper, testMessageHelper } from '@/utils/testHelper';
- Snapshot tests are a very useful tool whenever you want to make sure your UI does not change unexpectedly.
- Create a mapper object which includes required/iterable props.
const sizes: AvatarProps['size'][] = ['regular', 'tiny'];
describe('Avatar component', () => {
const mapper = {
size: valueHelper(sizes, { required: true, iterate: true }),
};
const testFunc = (props: Record<string, any>): void => {
const attr = filterUndefined(props) as Props;
it(testMessageHelper(attr), () => {
const { asFragment } = render(<Avatar {...attr}>JD</Avatar>);
expect(asFragment()).toMatchSnapshot();
});
};
testHelper(mapper, testFunc);
});
When our component has state or callbacks, we need to write custom code to render the story on docs page. Let us consider an example of Controlled Checkbox
story.
- Import the required components.
import * as React from 'react';
import { Checkbox } from '@/index';
- Name of the story in this case will be
Controlled Checkbox
. - We will create a state
checked
which will be false initially (showing that checkbox is not selected) and will update it when checkbox is clicked.
export const controlledCheckbox = () => {
const [checked, setChecked] = React.useState(false);
const handleParentChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setChecked(event.target.checked);
};
return <Checkbox label="Innovaccer" value="Innovaccer" checked={checked} onChange={handleParentChange} />;
};
- At this point, we will only see JSX in our live code editor. To fix this, we will write cutsom code for docs page.
const customCode = `() => {
const [checked, setChecked] = React.useState(false);
const handleParentChange = (event) => {
setChecked(event.target.checked);
};
return (
<Checkbox
label="Innovaccer"
value="Innovaccer"
checked={checked}
onChange={handleParentChange}
/>
);
}`;
- Now we will export metadata of our story.
export default {
title: 'Components/Checkbox/Variants/Controlled Checkbox',
component: Checkbox,
parameters: {
docs: {
docPage: {
customCode,
},
},
},
};
Patterns are best practice solutions for how a user achieves a goal. They show reusable combinations of components and templates that address common user objectives with sequences and flows.
Note: Patterns are only shown in docs tab
.
Let us create a pattern for SideNav.
- Name of the pattern will be
Side Nav
. - As patterns are shown only in docs tab, canvas tab will not render any element..
import * as React from 'react';
export const sideNav = () => <></>;
- We will write custom code for docs tab.
const customCode = `() => {
const menus = [
{
name: 'patient_360',
label: 'Patient 360',
icon: 'assignment_ind',
link: '/patient360',
},
{
name: 'care_management',
label: 'Care Management and Resources',
icon: 'forum',
subMenu: [
{
name: 'care_management.timeline',
label: 'Timeline',
icon: 'events'
},
{
name: 'care_management.care_plans',
label: 'Care Plans',
icon: 'events'
}
]
},
{
name: 'episodes',
label: 'Episodes',
disabled: true,
icon: 'airline_seat_flat_angled'
},
{
name: 'risk',
label: 'Risk',
icon: 'favorite',
subMenu: [
{
name: 'risk.timeline',
label: 'Timeline',
icon: 'events'
},
{
name: 'risk.care_plans',
label: 'Care Plans',
icon: 'events'
}
]
},
{
name: 'claims',
label: 'Claims',
icon: 'receipt'
},
];
const [expanded, setExpanded] = React.useState(false);
const [active, setActive] = React.useState({
name: 'care_management.timeline'
});
const onClickHandler = (menu) => {
console.log('menu-clicked: ', menu);
setActive(menu);
};
return (
<div style={{ height: 'calc(80vh)', background: 'var(--secondary-lightest)' }}>
<Collapsible expanded={expanded} onToggle={setExpanded}>
<VerticalNav
menus={menus}
expanded={expanded}
active={active}
onClick={onClickHandler}
hoverable={false}
/>
</Collapsible>
</div>
);
}`;
- We have created a mock data for side nav menus.
- We then need
expanded
andactive
state to manage VerticalNav component props. - We will then return JSX from our sideNav component.
- Now we will export metadata of our pattern. As we do not need props table for patterns, so we will add
noProps: true
in our metadata.
export default {
title: 'Patterns/VerticalNavigation/Side Nav',
parameters: {
docs: {
docPage: {
customCode,
title: 'Side Nav',
noProps: true
}
}
}
};
The following are the steps you should follow when creating a pull request. Subsequent pull requests only need to follow step 3 and beyond.
- Fork the repository on GitHub
- Clone the forked repository to your machine
- Create a new branch as per the branch naming conventions provided below.
- Make your changes and commit them to your local repository
- Rebase and push your commits to your GitHub remote fork/repository
- Issue a Pull Request to the official repository
- Your Pull Request is reviewed by a committer and merged into the repository
NOTE: While there are other ways to accomplish the steps using other tools, the examples here will assume most actions will be performed via git
on command line.
For more information on maintaining a fork, please see the GitHub Help article titled Fork a Repo, and information on rebasing.
Before committing, you must ensure there are no linting, formatting errors and all tests pass.
To ensure the above conditions, run:
For checking prettier issues:
npm run prettier:check
For formating prettier issues:
npm run prettier
For checking lint issues:
npm run lint:check
For formating lint issues:
npm run lint
For tests:
npm run test
Then, and only then, you can create your pull request.
The following are the steps you should follow when creating a new branch.
- Start the branch name with prefixes like fix-, feat-, test- as mentioned in conventional commit messages
- Add a short description of the task. This makes the branch name recognizable and distinct.
- Use hyphens as separators
For example:
feat-add-billing-module
fix-modal-height
We follow the conventional commit messages convention in order to automate CHANGELOG generation and to automate semantic versioning.
For example:
feat: A new feature
fix: A bug fix
A commit of the type feat introduces a new feature to the codebase (this correlates with MINOR in semantic versioning).
e.g.:
feat: xxxxxxxxxx
A commit of the type fix patches a bug in your codebase (this correlates with PATCH in semantic versioning).
e.g.:
fix: xxxxxxxxxx
Commits types such as as docs:
,style:
,refactor:
,perf:
,test:
and chore:
are valid but have no effect on versioning. It would be great if you use them.
PRs that do not follow the commit message guidelines will not be merged.
Any change in source code must include test updates.
For any change in source code of components that changes the API or functioning of the component corresponding story should be updated or a new story should be included.
We want to make sure everyone is recognized for their contributions !
To add yourself to the all-contributors
table in the README, you'll need to
run the following commands from the root of the repo:
# Add new contributor <username>, who made a contribution of type <contribution>
./node_modules/.bin/all-contributors add <username> <contribution>
# Example:
./node_modules/.bin/all-contributors add satyamyadav code,doc
Then, you'll need to generate the updated all-contributors
table by running
all-contributors generate
OR
./node_modules/.bin/all-contributors generate
Accessibility is the practice of making your websites usable by as many people as possible. A page should be
- Keyboard friendly
- Color blind friendly / Color contrast
- Screen Reader / Voice over
- Should not distort on Zoom In or Out.
To met the above criteria make sure following things are addressed.
- There is no
jsx-a11y
linting issue. - All the Storybook Accessibility plugin checks are met.
- All the WCAG 2.0 checks AA are covered
- All the WAI-ARIA Authoring Practices are met.