Make sure you have at least Node.js v6:
node -v
v6.2.1
Start by forking Semantic UI React to your GitHub account. Then clone your fork and install dependencies:
git clone git@github.com:<your-user>/Semantic-UI-React.git
cd Semantic-UI-React
yarn
Note: we use
yarn
and advise you do too while contributing. Get it here. You can usenpm install / npm ci
but we don't include apackage-lock.json
in the repository, so you may end up with slightly out of sync dependencies.
Add our repo as a git remote so you can pull/rebase your fork with our latest updates:
git remote add upstream git@github.com:Semantic-Org/Semantic-UI-React.git
Please follow the Angular Git Commit Guidelines format.
This list is not updated, you should run
yarn run
to see all scripts.
yarn start // run doc site
yarn ci // run all checks CI runs
yarn test // test once
yarn test:watch // test on file change
yarn build // build everything
yarn build:dist // build dist
yarn build:docs // build docs
yarn build:docs-toc // build toc for markdown files
yarn deploy:docs // deploy gh-pages doc site
yarn lint // lint once
yarn lint:fix // lint and attempt to fix
yarn lint:watch // lint on file change
Create components in src
. The directory structure follows SUI naming conventions. If you're updating a component, push a small change so you can open a PR early.
Stateless components should be written as a function
:
function Button(props) {
// ...
}
Stateful components should be classes:
import { AutoControlledComponent as Component } from '../../lib'
class Dropdown extends Component {
// ...
}
You probably need to extend our
AutoControlledComponent
to support both controlled and uncontrolled component patterns.
Every component must have fully described propTypes
.
import React, { PropTypes } from 'react'
function MyComponent(props) {
return <div className={props.position}>{props.children}</div>
}
MyComponent.propTypes = {
children: PropTypes.node,
position: PropTypes.oneOf(['left', 'right']),
}
Review common tests below. You should now add the isConformant()
common test and get it to pass. This will validate the _meta
and help you get your component off the ground.
This is a good time to open your PR. The component has been created, but the API and internals are not yet coded. We prefer to collaborate on these things to minimize rework.
This will also help with getting early feedback and smaller faster iterations on your component.
Review the SUI documentation for the component. Spec out the component's proposed API. The spec should demonstrate how your component's API will support all the native SUI features. You can reference this API proposal for the Input.
Once we have solidified the component spec, it's time to write some code. The following sections cover everything you'll need to spec and build your awesome component.
The primary areas of focus when designing a component API are:
Our goal is to map these to a declarative component API. We map HTML classes to component props. We map markup to sub components (and sometimes props).
SUI component definitions (style and behavior) are defined by HTML classes. These classes can be split into 4 groups:
- Standalone —
basic
compact
fluid
- Pairs —
left floated
right floated
- Mixed —
corner
top corner
,padded
very padded
- Groups — sizes:
tiny
small
big
, colors:red
green
blue
Each group has an API pattern and prop util for building up the className
and a Common test.
<Segment basic /> // standalone
<Segment floated='left' /> // pairs
<Segment padded /> // mixed
<Segment padded='very' />
<Segment size='small' color='red' /> // groups
<div class="ui basic segment"></div>
<div class="ui left floated segment"></div>
<div class="ui padded segment"></div>
<div class="ui very padded segment"></div>
<div class="ui small red segment"></div>
Use classNameBuilders
to extract the prop values and build up the className
. Grouped classes like color
and size
simply use the prop value as the className
.
import cx from 'clsx'
import { useKeyOnly, useValueAndKey, useKeyOrValueAndKey } from '../../lib'
function Segment({ size, color, basic, floated, padded }) {
const classes = cx(
'ui',
size,
color,
useKeyOnly(basic, 'basic'),
useValueAndKey(floated, 'floated'),
useKeyOrValueAndKey(padded, 'padded'),
'segment'
)
return <div className={classes} />
}
Use commonTests
to test the className
build up for each prop. These tests will run your component through all the possible usage permutations:
import * as common from 'test/specs/commonTests'
import Segment from 'src/elements/Segment/Segment'
describe('Segment', () => {
common.propValueOnlyToClassName(Segment, 'size')
common.propValueOnlyToClassName(Segment, 'color')
common.propKeyOnlyToClassName(Segment, 'basic')
common.propKeyAndValueToClassName(Segment, 'floated')
common.propKeyOrValueAndKeyToClassName(Segment, 'padded')
})
It is important to first differentiate between components and component parts in SUI. Per the SUI Glossary for ui
:
ui
is a special class name used to distinguish parts of components from components.For example, a list will receive the class
ui list
because it has a corresponding definition, however a list item, will receive just the classitem
.
The ui header
component is not the same as a header
component part. They share the same name but do not support the same features.
A ui header
accepts a size class. The ui modal
has a component part called header
. However, the size class is not valid on the header
component part. You size the ui modal
component instead.
Header Component
<div class="ui small header">...</div>
Modal Component (with header component part)
<div class="ui small modal">
<div class="header">...</div>
</div>
Top level Semantic UI React components correspond to SUI components. Stardust sub components correspond to SUI component parts.
This allows us to provide accurate propTypes
validation. It also separates concerns, isolating features and tests.
Use sub components to design component part markup.
<List>
<List.Item>Apples</List.Item>
<List.Item>Oranges</List.Item>
<List.Item>Pears</List.Item>
</List>
Create the sub component as a separate component in the parent component's directory:
function ListItem() {
// ...
}
Attach it to the parent via static properties:
import ListItem from './ListItem'
function List() {
// ...
}
List.Item = ListItem
import ListItem from './ListItem'
class List {
static Item = ListItem
}
Sometimes it is convenient to use props to generate markup. Example, the Label markup is minimal. One configuration includes an image and detail:
<a class="ui image label">
<img src="veronika.jpg">
Veronika
<div class="detail">Friend</div>
</a>
We allow props to define these minimal component parts:
<Label
image='veronika.jpg'
detail='Friend'
text='Veronica'
/>
When props are used for component markup generation, children are not allowed in order to prevent conflicts. See this response for more.
See src/factories
for special methods to convert props values into ReactElements for this purpose.
Run tests during development with yarn test:watch
to re-run tests on file changes.
All PRs must meet or exceed test coverage limits before they can be merged.
Every time tests run, /coverage
information is updated. Open coverage/lcov/index.html
to inspect test coverage. This interactive report will reveal areas lacking test coverage. You can then write tests for these areas and increase coverage.
There are many common things to test for. Because of this, we have test/specs/commonTests.js
.
This list is not updated, check the source for current tests and inline documentation.
common.isConformant()
common.hasUIClassName()
common.hasSubcomponents()
common.isTabbable()
common.rendersChildren()
common.implementsIconProp()
common.implementsImageProp()
common.implementsTextAlignProp()
common.implementsVerticalAlignProp()
common.implementsWidthProp()
common.propKeyOnlyToClassName()
common.propValueOnlyToClassName()
common.propKeyAndValueToClassName()
common.propKeyOrValueAndKeyToClassName()
Every common test receives your component as its first argument.
import React from 'react'
import * as common from 'test/specs/commonTests'
import Menu from 'src/collections/Menu/Menu'
import MenuItem from 'src/collections/Menu/MenuItem'
describe('Menu', () => {
common.isConformant(Menu)
common.hasUIClassName(Menu)
common.rendersChildren(Menu)
common.hasSubcomponents(Menu, [MenuItem]) // some take additional arguments
})
The last argument to a common test is always options
. You can configure the test here. For example, if your component requires certain props to render, you can pass in requiredProps
:
import * as common from 'test/specs/commonTests'
import Select from 'src/addons/Select/Select'
describe('Select', () => {
common.isConformant(Select, {
requiredProps: {
options: [],
},
})
})
This is the only required test. It ensures a consistent baseline for the framework. It also helps you get your component off the ground. You should add this test to new components right away.
This list is not updated, check the source for the latest assertions.
- Component and filename are correct
- Events are properly handled
- Extra
props
are spread - Base
className
s are applied - Component is exported if public / hidden if private
We are using Percy and Cypress to perform visual testing of our components. To create a new visual test there should an example in our docs that can be served by Cypress and a corresponding Cypress test, for example:
cypress/integration/Popup/Popup.visual.js
contains visual testsdocs/src/examples/modules/Popup/Visual/PopupVisualInsideModal.js
contains an example that will be used for visual tests
Strive to use stateless functional components when possible:
function MyComponent(props) {
return <div {...props} />
}
If your component requires event handlers, it is a stateful class component. Want to know why?
class MyComponent extends Component {
handleClick = (e) => {
console.log('Clicked my component!')
}
render() {
return <div onClick={this.handleClick} />
}
}
TODO
For now, you should reference Dropdown as an example implementation. You can also consult the comments in AutoControlledComponent.js for more background.
Our docs are generated from docblock comments, propTypes
, and hand-written examples.
Developing against the doc site is a good way to try your component as you build it. Run the doc site with:
yarn start
A docblock should appear above a component class or function to describe it:
/**
* A <Select /> is sugar for <Dropdown selection />.
* @see Dropdown
*/
function Select(props) {
return <Dropdown {...props} selection />
}
A docblock should appear above each prop in propTypes
to describe them:
Limited props shown for brevity.
Label.propTypes = {
/** An element type to render as (string or function). */
as: PropTypes.elementType,
/** A label can reduce its complexity. */
basic: PropTypes.bool,
/** Primary content. */
children: PropTypes.node,
/** Additional classes. */
className: PropTypes.string,
/** Color of the label. */
color: PropTypes.oneOf(Label._meta.props.color),
/** Place the label in one of the upper corners . */
corner: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.oneOf(['left', 'right']),
]),
/** Add an icon by icon className or pass an <Icon /> */
icon: PropTypes.oneOfType([
PropTypes.string,
PropTypes.element,
]),
}
This section is lacking in instruction as the docs are set to be overhauled (PRs welcome!).
Usage examples for a component live in docs/src/examples
. The examples follow the SUI doc site examples.
Adding documentation for new components is a bit tedious. The best way to do this (for now) is to copy an existing component's and update them.
On the latest clean master
:
npm run release:<major|minor|patch>
⚠️ npm
must be used. At the time of writingyarn
does not properly handle the credentials.
Releasing will update the changelog which requires github_changelog_generator.