Skip to content

Latest commit

 

History

History
562 lines (402 loc) · 16.4 KB

CONTRIBUTING.md

File metadata and controls

562 lines (402 loc) · 16.4 KB

CONTRIBUTING

Getting Started

Make sure you have at least Node.js v6:

node -v

v6.2.1

Fork, Clone & Install

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 use npm install / npm ci but we don't include a package-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

Commit Messages

Please follow the Angular Git Commit Guidelines format.

Commands

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

Workflow

Create a Component

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.

Using propTypes

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']),
}

Conformance Test

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.

Open A PR

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.

Spec out the API

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.

API

The primary areas of focus when designing a component API are:

  1. SUI HTML Classes
  2. SUI HTML Markup

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 HTML Classes

SUI component definitions (style and behavior) are defined by HTML classes. These classes can be split into 4 groups:

  1. Standalone — basic compact fluid
  2. Pairs — left floated right floated
  3. Mixed — corner top corner, padded very padded
  4. 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.

API Patterns

<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>

Building className

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} />
}

Testing className

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')
})

SUI HTML Markup

SUI Components vs Component Parts

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 class item.

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>

React Components & Sub Components

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
}

Component Part Props

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.

Testing

Run tests during development with yarn test:watch to re-run tests on file changes.

Coverage

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.

Common Tests

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()

Usage

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: [],
    },
  })
})

isConformant (required)

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.

  1. Component and filename are correct
  2. Events are properly handled
  3. Extra props are spread
  4. Base classNames are applied
  5. Component is exported if public / hidden if private

Visual testing

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 tests
  • docs/src/examples/modules/Popup/Visual/PopupVisualInsideModal.js contains an example that will be used for visual tests

State

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} />
  }
}

AutoControlledComponent

TODO

For now, you should reference Dropdown as an example implementation. You can also consult the comments in AutoControlledComponent.js for more background.

Documentation

Our docs are generated from docblock comments, propTypes, and hand-written examples.

Website

Developing against the doc site is a good way to try your component as you build it. Run the doc site with:

yarn start

Components

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 />
}

Props

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,
  ]),
}

Examples

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.

Releasing

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.