Commit 5dd244c3 by Farhanah Sheets

README creation for Paragon components

parent fd4b1bdd
yarn.lock binary
.DS_Store .DS_Store
.eslintcache .eslintcache
.storybook
node_modules node_modules
npm-debug.log npm-debug.log
.travis.yml
...@@ -476,7 +476,11 @@ exports[`Storyshots Modal basic usage 1`] = ` ...@@ -476,7 +476,11 @@ exports[`Storyshots Modal basic usage 1`] = `
onKeyDown={[Function]} onKeyDown={[Function]}
type="button" type="button"
> >
× <span
aria-hidden="true"
>
×
</span>
</button> </button>
</div> </div>
<div <div
...@@ -534,7 +538,11 @@ exports[`Storyshots Modal configurable buttons 1`] = ` ...@@ -534,7 +538,11 @@ exports[`Storyshots Modal configurable buttons 1`] = `
onKeyDown={[Function]} onKeyDown={[Function]}
type="button" type="button"
> >
× <span
aria-hidden="true"
>
×
</span>
</button> </button>
</div> </div>
<div <div
...@@ -619,7 +627,11 @@ exports[`Storyshots Modal configurable buttons that perform actions 1`] = ` ...@@ -619,7 +627,11 @@ exports[`Storyshots Modal configurable buttons that perform actions 1`] = `
onKeyDown={[Function]} onKeyDown={[Function]}
type="button" type="button"
> >
× <span
aria-hidden="true"
>
×
</span>
</button> </button>
</div> </div>
<div <div
...@@ -686,7 +698,11 @@ exports[`Storyshots Modal configurable close button 1`] = ` ...@@ -686,7 +698,11 @@ exports[`Storyshots Modal configurable close button 1`] = `
onKeyDown={[Function]} onKeyDown={[Function]}
type="button" type="button"
> >
× <span
aria-hidden="true"
>
×
</span>
</button> </button>
</div> </div>
<div <div
...@@ -744,7 +760,11 @@ exports[`Storyshots Modal configurable title and body 1`] = ` ...@@ -744,7 +760,11 @@ exports[`Storyshots Modal configurable title and body 1`] = `
onKeyDown={[Function]} onKeyDown={[Function]}
type="button" type="button"
> >
× <span
aria-hidden="true"
>
×
</span>
</button> </button>
</div> </div>
<div <div
...@@ -812,7 +832,11 @@ exports[`Storyshots Modal modal invoked via a button 1`] = ` ...@@ -812,7 +832,11 @@ exports[`Storyshots Modal modal invoked via a button 1`] = `
onKeyDown={[Function]} onKeyDown={[Function]}
type="button" type="button"
> >
× <span
aria-hidden="true"
>
×
</span>
</button> </button>
</div> </div>
<div <div
...@@ -880,7 +904,11 @@ exports[`Storyshots Modal modal with element body 1`] = ` ...@@ -880,7 +904,11 @@ exports[`Storyshots Modal modal with element body 1`] = `
onKeyDown={[Function]} onKeyDown={[Function]}
type="button" type="button"
> >
× <span
aria-hidden="true"
>
×
</span>
</button> </button>
</div> </div>
<div <div
...@@ -982,6 +1010,210 @@ exports[`Storyshots Paragon Welcome 1`] = ` ...@@ -982,6 +1010,210 @@ exports[`Storyshots Paragon Welcome 1`] = `
</div> </div>
`; `;
exports[`Storyshots StatusAlert Non-dismissible alert 1`] = `
<div
className="alert fade alert-danger show"
hidden={false}
role="alert"
>
<div
className="alert-dialog"
>
You can't get rid of me!
</div>
</div>
`;
exports[`Storyshots StatusAlert alert invoked via a button 1`] = `
<div>
<div
className="alert fade alert-dismissible alert-success"
hidden={true}
role="alert"
>
<button
aria-label="Close"
className="btn close"
onBlur={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
type="button"
>
<span
aria-hidden="true"
>
×
</span>
</button>
<div
className="alert-dialog"
>
Success! You triggered the alert!
</div>
</div>
<button
className="btn btn-light"
onBlur={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
type="button"
>
Click me to open a Status Alert!
</button>
</div>
`;
exports[`Storyshots StatusAlert alert with a link 1`] = `
<div
className="alert fade alert-dismissible alert-info show"
hidden={false}
role="alert"
>
<button
aria-label="Close"
className="btn close"
onBlur={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
type="button"
>
<span
aria-hidden="true"
>
×
</span>
</button>
<div
className="alert-dialog"
>
<div>
<span>
Love cats?
</span>
<a
href="https://www.factretriever.com/cat-facts"
rel="noopener noreferrer"
target="_blank"
>
Click me!
</a>
</div>
</div>
</div>
`;
exports[`Storyshots StatusAlert basic usage 1`] = `
<div
className="alert fade alert-dismissible alert-warning show"
hidden={false}
role="alert"
>
<button
aria-label="Close"
className="btn close"
onBlur={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
type="button"
>
<span
aria-hidden="true"
>
×
</span>
</button>
<div
className="alert-dialog"
>
You have a status alert!
</div>
</div>
`;
exports[`Storyshots StatusAlert danger alert 1`] = `
<div
className="alert fade alert-dismissible alert-danger show"
hidden={false}
role="alert"
>
<button
aria-label="Close"
className="btn close"
onBlur={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
type="button"
>
<span
aria-hidden="true"
>
×
</span>
</button>
<div
className="alert-dialog"
>
Error!
</div>
</div>
`;
exports[`Storyshots StatusAlert informational alert 1`] = `
<div
className="alert fade alert-dismissible alert-info show"
hidden={false}
role="alert"
>
<button
aria-label="Close"
className="btn close"
onBlur={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
type="button"
>
<span
aria-hidden="true"
>
×
</span>
</button>
<div
className="alert-dialog"
>
Get some info here!
</div>
</div>
`;
exports[`Storyshots StatusAlert success alert 1`] = `
<div
className="alert fade alert-dismissible alert-success show"
hidden={false}
role="alert"
>
<button
aria-label="Close"
className="btn close"
onBlur={[Function]}
onClick={[Function]}
onKeyDown={[Function]}
type="button"
>
<span
aria-hidden="true"
>
×
</span>
</button>
<div
className="alert-dialog"
>
Success!
</div>
</div>
`;
exports[`Storyshots Table default heading 1`] = ` exports[`Storyshots Table default heading 1`] = `
<table <table
className="table" className="table"
......
...@@ -3,16 +3,9 @@ language: node_js ...@@ -3,16 +3,9 @@ language: node_js
node_js: node_js:
- 6 - 6
before_install:
- yarn global add greenkeeper-lockfile@1
before_script:
- greenkeeper-lockfile-update
script: script:
- yarn run lint - npm run lint
- yarn run test - npm run test
after_script: after_script:
- greenkeeper-lockfile-upload - npm run coveralls
- yarn run coveralls
paragon.cool
...@@ -10,12 +10,12 @@ Components' markup, keyboard triggers, and behavior are based on the [WAI-ARIA 1 ...@@ -10,12 +10,12 @@ Components' markup, keyboard triggers, and behavior are based on the [WAI-ARIA 1
## Development ## Development
First, clone the repo and install dependencies. You must be running Node 6 or newer. We recommend [Yarn](https://yarnpkg.com) for installation. First, clone the repo and install dependencies. You must be running Node 6 or newer.
``` ```
$ git clone git@github.com:edx/paragon.git $ git clone git@github.com:edx/paragon.git
$ cd paragon $ cd paragon
$ yarn install $ npm install
``` ```
### Storybook ### Storybook
...@@ -25,7 +25,7 @@ Paragon uses [Storybook](https://storybook.js.org/) to generate and serve its do ...@@ -25,7 +25,7 @@ Paragon uses [Storybook](https://storybook.js.org/) to generate and serve its do
To start the Storybook server locally, run the following: To start the Storybook server locally, run the following:
``` ```
$ yarn run start $ npm run start
``` ```
Storybook will serve at http://localhost:6006. It's important to note that the Storybook server uses its own [webpack config file](https://github.com/edx/paragon/blob/master/.storybook/webpack.config.js) which is separate from the project root config. Storybook will serve at http://localhost:6006. It's important to note that the Storybook server uses its own [webpack config file](https://github.com/edx/paragon/blob/master/.storybook/webpack.config.js) which is separate from the project root config.
...@@ -43,7 +43,7 @@ Make sure to define PropTypes and DefaultProps on your components, using the [pr ...@@ -43,7 +43,7 @@ Make sure to define PropTypes and DefaultProps on your components, using the [pr
Paragon runs ESLint as a pre-commit hook. If your code fails linting, you will not be able to commit. To avoid hitting a giant-wall-of-linter-failures when you try to commit, we recommend [configuring your editor to run ESLint](http://eslint.org/docs/user-guide/integrations). To run ESLint in the console at any time, run the following: Paragon runs ESLint as a pre-commit hook. If your code fails linting, you will not be able to commit. To avoid hitting a giant-wall-of-linter-failures when you try to commit, we recommend [configuring your editor to run ESLint](http://eslint.org/docs/user-guide/integrations). To run ESLint in the console at any time, run the following:
``` ```
$ yarn run lint $ npm run lint
``` ```
Paragon's ESLint config is based off [eslint-config-edx](https://github.com/edx/eslint-config-edx/tree/master/packages/eslint-config-edx), which itself is based off [eslint-config-airbnb](https://www.npmjs.com/package/eslint-config-airbnb). Paragon uses ESLint 3 (and will upgrade to v4 as soon as eslint-config-airbnb releases a supported version), which itself comes with a number of built-in rules. This configuration is highly opinionated and may contain some rules with which you aren't yet familiar, like [comma-dangle](http://eslint.org/docs/rules/comma-dangle), but rest assured, you're writing modern, best-practice JS 💅 Paragon's ESLint config is based off [eslint-config-edx](https://github.com/edx/eslint-config-edx/tree/master/packages/eslint-config-edx), which itself is based off [eslint-config-airbnb](https://www.npmjs.com/package/eslint-config-airbnb). Paragon uses ESLint 3 (and will upgrade to v4 as soon as eslint-config-airbnb releases a supported version), which itself comes with a number of built-in rules. This configuration is highly opinionated and may contain some rules with which you aren't yet familiar, like [comma-dangle](http://eslint.org/docs/rules/comma-dangle), but rest assured, you're writing modern, best-practice JS 💅
...@@ -63,7 +63,7 @@ Paragon also uses Airbnb's [Enzyme](http://airbnb.io/enzyme/) library to help re ...@@ -63,7 +63,7 @@ Paragon also uses Airbnb's [Enzyme](http://airbnb.io/enzyme/) library to help re
To run the unit tests, run: To run the unit tests, run:
``` ```
yarn run test npm run test
``` ```
To add unit tests for a component, create a file in your component's directory named `<ComponentName>.test.js`. Jest will automatically pick up this file and run the tests as part of the suite. Take a look at [Dropdown.test.jsx](https://github.com/edx/paragon/blob/master/src/Dropdown/Dropdown.test.jsx) or [CheckBox.test.jsx](https://github.com/edx/paragon/blob/master/src/CheckBox/CheckBox.test.jsx) for examples of good component unit tests. To add unit tests for a component, create a file in your component's directory named `<ComponentName>.test.js`. Jest will automatically pick up this file and run the tests as part of the suite. Take a look at [Dropdown.test.jsx](https://github.com/edx/paragon/blob/master/src/Dropdown/Dropdown.test.jsx) or [CheckBox.test.jsx](https://github.com/edx/paragon/blob/master/src/CheckBox/CheckBox.test.jsx) for examples of good component unit tests.
...@@ -75,7 +75,7 @@ Jest has built-in [snapshot testing](http://facebook.github.io/jest/docs/en/snap ...@@ -75,7 +75,7 @@ Jest has built-in [snapshot testing](http://facebook.github.io/jest/docs/en/snap
When you modify components or stories (or add new components or stories), make sure to update the snapshots or else the snapshot tests will fail. It's easy to do -- just run: When you modify components or stories (or add new components or stories), make sure to update the snapshots or else the snapshot tests will fail. It's easy to do -- just run:
``` ```
$ yarn run snapshot $ npm run snapshot
``` ```
If the snapshot tests fail, it's generally pretty easy to tell whether it's happening because of a bug or because the snapshots need to be updated. Don't be afraid to inspect the test output for clues! If the snapshot tests fail, it's generally pretty easy to tell whether it's happening because of a bug or because the snapshots need to be updated. Don't be afraid to inspect the test output for clues!
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
{ {
"name": "paragon", "name": "@edx/paragon",
"version": "0.0.1", "version": "0.1.0",
"description": "Accessible, responsive UI component library based on Bootstrap.", "description": "Accessible, responsive UI component library based on Bootstrap.",
"main": "src/index.js", "main": "src/index.js",
"author": "arizzitano", "author": "arizzitano",
...@@ -11,17 +11,18 @@ ...@@ -11,17 +11,18 @@
"coveralls": "cat ./coverage/lcov.info | coveralls", "coveralls": "cat ./coverage/lcov.info | coveralls",
"deploy-storybook": "storybook-to-ghpages", "deploy-storybook": "storybook-to-ghpages",
"lint": "eslint --ext .js --ext .jsx .", "lint": "eslint --ext .js --ext .jsx .",
"precommit": "yarn run lint", "precommit": "npm run lint",
"snapshot": "jest --updateSnapshot", "snapshot": "jest --updateSnapshot",
"start": "start-storybook -p 6006", "start": "start-storybook -p 6006",
"test": "jest --coverage" "test": "jest --coverage"
}, },
"dependencies": { "dependencies": {
"@edx/edx-bootstrap": "^0.3.3",
"classnames": "^2.2.5", "classnames": "^2.2.5",
"edx-bootstrap": "^0.2.1",
"prop-types": "^15.5.8", "prop-types": "^15.5.8",
"react": "^15.5.4", "react": "^15.5.4",
"react-dom": "^15.5.4" "react-dom": "^15.5.4",
"react-proptype-conditional-require": "^1.0.4"
}, },
"devDependencies": { "devDependencies": {
"@storybook/addon-actions": "^3.2.12", "@storybook/addon-actions": "^3.2.12",
...@@ -32,12 +33,11 @@ ...@@ -32,12 +33,11 @@
"babel-cli": "^6.24.1", "babel-cli": "^6.24.1",
"babel-jest": "^21.0.0", "babel-jest": "^21.0.0",
"babel-loader": "^7.0.0", "babel-loader": "^7.0.0",
"babel-minify": "^0.2.0",
"babel-minify-webpack-plugin": "^0.2.0",
"babel-plugin-transform-object-rest-spread": "^6.23.0", "babel-plugin-transform-object-rest-spread": "^6.23.0",
"babel-preset-babili": "^0.1.4",
"babel-preset-env": "^1.4.0", "babel-preset-env": "^1.4.0",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1", "babel-preset-react": "^6.24.1",
"babili-webpack-plugin": "^0.1.1",
"coveralls": "^3.0.0", "coveralls": "^3.0.0",
"css-loader": "^0.28.4", "css-loader": "^0.28.4",
"enzyme": "^2.8.2", "enzyme": "^2.8.2",
......
@import "~bootstrap/scss/_buttons"; @import "~bootstrap/scss/_buttons";
@import "~bootstrap/scss/_close";
# Button
Provides a button component that can be customized and handle multiple event handlers such as `onBlur`, `onClick`, and `onKeyDown`.
## API
### `buttonType` (string; optional)
`buttonType` is used to determine the type of button to be rendered. See [Bootstrap's buttons documentation](https://getbootstrap.com/docs/4.0/components/buttons/) for a list of applicable button types. For example, `buttonType="light"`. The default is `undefined`.
### `className` (string array; optional)
`className` specifies Bootstrap class names to apply to the button. See [Bootstrap's buttons documentation](https://getbootstrap.com/docs/4.0/components/buttons/) for a list of applicable class names. The default is an empty array.
### `display` (string; required)
`display` specifies the text that is displayed within the button.
### `inputRef` (function; optional)
`inputRef` is a function that defines a reference for the button. An example `inputRef` from the calling component could look something like: `inputRef={(input) => { this.button = input; }}`. The default is an empty function.
### `isClose` (boolean; optional)
`isClose` is used to determine if the button is a "Close" style button to leverage bootstrap styling. Example use case is with the Status Alert [dismiss button](https://getbootstrap.com/docs/4.0/components/alerts/#dismissing). The default is false.
### `onBlur` (function; optional)
`onBlur` is a function that would specify what the button should do when the `onBlur` event is triggered. For example, the button could change in color or `buttonType` when focus is changed. The default is an empty function.
### `onClick` (function; optional)
`onClick` is a function that would specify what the button should do when the `onClick` event is triggered. For example, the button could launch a `Modal`. The default is an empty function.
### `onKeyDown` (function; optional)
`onKeyDown` is a function that would specify what the button should do when the `onKeyDown` event is triggered. For example, this could handle using the `Escape` key to trigger the button's action. The default is an empty function.
### `type` (string; optional)
`type` is used to set the `type` attribute on the `button` tag. The default type is `button`.
...@@ -10,6 +10,7 @@ function Button(props) { ...@@ -10,6 +10,7 @@ function Button(props) {
className, className,
display, display,
inputRef, inputRef,
isClose,
onBlur, onBlur,
onClick, onClick,
onKeyDown, onKeyDown,
...@@ -24,6 +25,8 @@ function Button(props) { ...@@ -24,6 +25,8 @@ function Button(props) {
styles.btn, styles.btn,
], { ], {
[styles[`btn-${buttonType}`]]: buttonType !== undefined, [styles[`btn-${buttonType}`]]: buttonType !== undefined,
}, {
[styles.close]: isClose,
})} })}
onBlur={onBlur} onBlur={onBlur}
onClick={onClick} onClick={onClick}
...@@ -40,8 +43,9 @@ function Button(props) { ...@@ -40,8 +43,9 @@ function Button(props) {
export const buttonPropTypes = { export const buttonPropTypes = {
buttonType: PropTypes.string, buttonType: PropTypes.string,
className: PropTypes.arrayOf(PropTypes.string), className: PropTypes.arrayOf(PropTypes.string),
display: PropTypes.string.isRequired, display: PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired,
inputRef: PropTypes.func, inputRef: PropTypes.func,
isClose: PropTypes.bool,
onBlur: PropTypes.func, onBlur: PropTypes.func,
onClick: PropTypes.func, onClick: PropTypes.func,
onKeyDown: PropTypes.func, onKeyDown: PropTypes.func,
...@@ -54,6 +58,7 @@ Button.defaultProps = { ...@@ -54,6 +58,7 @@ Button.defaultProps = {
buttonType: undefined, buttonType: undefined,
className: [], className: [],
inputRef: () => {}, inputRef: () => {},
isClose: false,
onBlur: () => {}, onBlur: () => {},
onClick: () => {}, onClick: () => {},
onKeyDown: () => {}, onKeyDown: () => {},
......
# CssJail
Provides the wrapper for CSS rules of all children components.
## API
### `children` (element array; required)
`children` specifies the list of elements that will be displayed within the `CssJail` wrapper. Children should not be passed as Props, but should instead be nested between the opening and closing `<CssJail> </CssJail>` tags.
# Dropdown
Provides a dropdown component that will maintain focus and keyboard navigation on an array of `menuItems` that is passed in.
## API
### `buttonType` (string; optional)
`buttonType` is used to determine the type of button to be rendered. See [Bootstrap's buttons documentation](https://getbootstrap.com/docs/4.0/components/buttons/) for a list of applicable button types. The default is `buttonType="light"`.
### `menuItems` (shape array; required)
`menuItems` specifies the list of items that will be rendered within the dropdown for selection. It takes in the type `shape` that appears a Javascript object containing the menu item `label` and the `href` properties as strings.
### `title` (string; required)
`title` specifies the text that is displayed within the original dropdown button.
# InputSelect
Provides an selector component called InputSelect that allows for various selection options including separate option groups and including extra validation.
## API
### `inputProps` (view asInput component for details)
`inputProps` specifies all of the properties that are necessary from the included `asInput` component. Please see details for input requirements within that component.
### `options` (string array or object array; required)
`options` specifies the list of options that the component will allow users to select from. This can be a simple array of strings, listing their options. It can also be an array containing more complex object in order to show the options in a grouped format.
# InputText
Provides an input component called InputText that gives users a reusable input field.
## API
### `inputProps` (view asInput component for details)
`inputProps` specifies all of the properties that are necessary from the included `asInput` component. Please see details for input requirements within that component.
...@@ -96,7 +96,7 @@ class Modal extends React.Component { ...@@ -96,7 +96,7 @@ class Modal extends React.Component {
<div className={styles['modal-header']}> <div className={styles['modal-header']}>
<h5 className={styles['modal-title']} id={this.headerId}>{this.props.title}</h5> <h5 className={styles['modal-title']} id={this.headerId}>{this.props.title}</h5>
<Button <Button
display="&times;" display={<span aria-hidden="true">&times;</span>}
aria-label={this.props.closeText} aria-label={this.props.closeText}
buttonType="light" buttonType="light"
onClick={this.close} onClick={this.close}
......
# StatusAlert
Provides a status alert component with customizable dialog options. StatusAlert has an X button on the right by default (see dismissible option).
## API
### `alertType` (string; optional)
`alertType` specifies the type of alert that is being diplayed. It defaults to "warning". See the other available [bootstrap](https://v4-alpha.getbootstrap.com/components/alerts/) options.
### `className` (string array; optional)
`className` is a string array that defines the classes to be used within the status alert.
### `dialog` (string or element; required)
`dialog` is a string or an element that is rendered inside of the status alert as the main data.
### `dismissible` (boolean; optional)
`dismissible` specifies if the status alert will include the dismissible X button to close the alert. It defaults to true.
### `onClose` (function; conditionally required)
`onClose` is a function that is called on close. It can be used to perform actions upon closing of the status alert, such as restoring focus to the previous logical focusable element. It is only required if `dismissible` is set to `true` and not required if the alert if not `dismissible`.
### `open` (boolean; optional)
`open` specifies whether the status alert renders open or closed on the initial render. It defaults to false.
@import "~bootstrap/scss/_alert";
@import "~bootstrap/scss/_buttons";
@import "~bootstrap/scss/_close";
@import "~bootstrap/scss/_transitions.scss";
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable no-console */
import React from 'react';
import { storiesOf } from '@storybook/react';
import PropTypes from 'prop-types';
import StatusAlert from './index';
import Button from '../Button';
class StatusAlertWrapper extends React.Component {
constructor(props) {
super(props);
this.openStatusAlert = this.openStatusAlert.bind(this);
this.resetStatusAlertWrapperState = this.resetStatusAlertWrapperState.bind(this);
this.state = { open: false };
}
openStatusAlert() {
this.setState({ open: true });
}
resetStatusAlertWrapperState() {
this.setState({ open: false });
this.button.focus();
}
render() {
return (
<div>
<StatusAlert
alertType={this.props.alertType}
open={this.state.open}
dialog={this.props.dialog}
onClose={this.resetStatusAlertWrapperState}
/>
<Button
onClick={this.openStatusAlert}
display="Click me to open a Status Alert!"
buttonType="light"
inputRef={(input) => { this.button = input; }}
/>
</div>
);
}
}
StatusAlertWrapper.propTypes = {
alertType: PropTypes.string,
dialog: PropTypes.string.isRequired,
};
StatusAlertWrapper.defaultProps = {
alertType: 'warning',
};
storiesOf('StatusAlert', module)
.add('basic usage', () => (
<StatusAlert
dialog="You have a status alert!"
onClose={() => {}}
open
/>
))
.add('success alert', () => (
<StatusAlert
alertType="success"
dialog="Success!"
onClose={() => {}}
open
/>
))
.add('danger alert', () => (
<StatusAlert
alertType="danger"
dialog="Error!"
onClose={() => {}}
open
/>
))
.add('informational alert', () => (
<StatusAlert
alertType="info"
dialog="Get some info here!"
onClose={() => {}}
open
/>
))
.add('Non-dismissible alert', () => (
<StatusAlert
alertType="danger"
dismissible={false}
dialog="You can't get rid of me!"
open
/>
))
.add('alert invoked via a button', () => (
<StatusAlertWrapper
alertType="success"
dialog="Success! You triggered the alert!"
/>
))
.add('alert with a link', () => (
<StatusAlert
alertType="info"
dialog={(
<div>
<span>Love cats? </span>
<a
href="https://www.factretriever.com/cat-facts"
target="_blank"
rel="noopener noreferrer"
>
Click me!
</a>
</div>
)}
onClose={() => {}}
open
/>
));
import React from 'react';
import { mount } from 'enzyme';
import StatusAlert from './index';
const statusAlertOpen = (isOpen, wrapper) => {
expect(wrapper.hasClass('show')).toEqual(isOpen);
expect(wrapper.state('open')).toEqual(isOpen);
};
const dialog = 'Status Alert dialog';
const defaultProps = {
dialog,
onClose: () => {},
open: true,
};
let wrapper;
describe('<StatusAlert />', () => {
describe('correct rendering', () => {
it('renders default view', () => {
wrapper = mount(
<StatusAlert
{...defaultProps}
/>,
);
const statusAlertDialog = wrapper.find('.alert-dialog');
expect(statusAlertDialog.text()).toEqual(dialog);
expect(wrapper.find('button')).toHaveLength(1);
});
it('renders non-dismissible view', () => {
wrapper = mount(
<StatusAlert
{...defaultProps}
dismissible={false}
/>,
);
const statusAlertDialog = wrapper.find('.alert-dialog');
expect(statusAlertDialog.text()).toEqual(dialog);
expect(wrapper.find('button')).toHaveLength(0);
});
});
describe('props received correctly', () => {
it('component receives props', () => {
wrapper = mount(
<StatusAlert
dialog={dialog}
onClose={() => {}}
/>,
);
statusAlertOpen(false, wrapper);
wrapper.setProps({ open: true });
statusAlertOpen(true, wrapper);
});
it('component receives props and ignores prop change', () => {
wrapper = mount(
<StatusAlert
{...defaultProps}
/>,
);
statusAlertOpen(true, wrapper);
wrapper.setProps({ dialog: 'Changed alert dialog' });
statusAlertOpen(true, wrapper);
});
});
describe('close functions properly', () => {
beforeEach(() => {
wrapper = mount(
<StatusAlert
{...defaultProps}
/>,
);
});
it('closes when x button pressed', () => {
statusAlertOpen(true, wrapper);
wrapper.find('button').at(0).simulate('click');
statusAlertOpen(false, wrapper);
});
it('closes when Enter key pressed', () => {
statusAlertOpen(true, wrapper);
wrapper.find('button').at(0).simulate('keyDown', { key: 'Enter' });
statusAlertOpen(false, wrapper);
});
it('closes when Escape key pressed', () => {
statusAlertOpen(true, wrapper);
wrapper.find('button').at(0).simulate('keyDown', { key: 'Escape' });
statusAlertOpen(false, wrapper);
});
it('calls callback function on close', () => {
const spy = jest.fn();
wrapper = mount(
<StatusAlert
{...defaultProps}
onClose={spy}
/>,
);
expect(spy).toHaveBeenCalledTimes(0);
// press X button
wrapper.find('button').at(0).simulate('click');
expect(spy).toHaveBeenCalledTimes(1);
});
});
describe('invalid keystrokes do nothing', () => {
beforeEach(() => {
wrapper = mount(
<StatusAlert
{...defaultProps}
/>,
);
});
it('does nothing on invalid keystroke q', () => {
const buttons = wrapper.find('button');
expect(buttons.at(0).matchesElement(document.activeElement)).toEqual(true);
statusAlertOpen(true, wrapper);
buttons.at(0).simulate('keyDown', { key: 'q' });
expect(buttons.at(0).matchesElement(document.activeElement)).toEqual(true);
statusAlertOpen(true, wrapper);
});
it('does nothing on invalid keystroke + ctrl', () => {
const buttons = wrapper.find('button');
expect(buttons.at(0).matchesElement(document.activeElement)).toEqual(true);
statusAlertOpen(true, wrapper);
buttons.at(0).simulate('keyDown', { key: 'Tab', ctrlKey: true });
expect(buttons.at(0).matchesElement(document.activeElement)).toEqual(true);
statusAlertOpen(true, wrapper);
});
});
});
import React from 'react';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import isRequiredIf from 'react-proptype-conditional-require';
import styles from './StatusAlert.scss';
import Button from '../Button';
class StatusAlert extends React.Component {
constructor(props) {
super(props);
this.close = this.close.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
this.renderDialog = this.renderDialog.bind(this);
this.state = {
open: props.open,
};
}
componentDidMount() {
if (this.xButton) {
this.xButton.focus();
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.open !== this.props.open) {
this.setState({ open: nextProps.open });
}
}
componentDidUpdate(prevState) {
if (this.state.open && !prevState.open) {
this.xButton.focus();
}
}
close() {
this.setState({ open: false });
this.props.onClose();
}
handleKeyDown(e) {
if (e.key === 'Enter' || e.key === 'Escape') {
e.preventDefault();
this.close();
}
}
renderDialog() {
const { dialog } = this.props;
return (
<div className="alert-dialog">
{ dialog }
</div>
);
}
renderDismissible() {
const { dismissible } = this.props;
return (dismissible) ? (
<Button
aria-label="Close"
inputRef={(input) => { this.xButton = input; }}
onClick={this.close}
onKeyDown={this.handleKeyDown}
display={<span aria-hidden="true">&times;</span>}
isClose
/>
) : null;
}
render() {
const { alertType, className, dismissible } = this.props;
return (
<div
className={classNames([
...className,
styles.alert,
styles.fade,
], {
[styles['alert-dismissible']]: dismissible,
}, {
[styles[`alert-${alertType}`]]: alertType !== undefined,
}, {
[styles.show]: this.state.open,
})}
role="alert"
hidden={!this.state.open}
>
{this.renderDismissible()}
{this.renderDialog()}
</div>
);
}
}
StatusAlert.propTypes = {
alertType: PropTypes.string,
className: PropTypes.arrayOf(PropTypes.string),
dialog: PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired,
dismissible: PropTypes.bool,
/* eslint-disable react/require-default-props */
onClose: isRequiredIf(PropTypes.func, props => props.dismissible),
open: PropTypes.bool,
};
StatusAlert.defaultProps = {
alertType: 'warning',
className: [],
dismissible: true,
open: false,
};
export default StatusAlert;
# Tabs
Provides the ability for a Tab view that allows for switching between tabs to view panels within a page.
## API
### `children` (element array; required)
`children` specifies the list of elements that will be displayed within each of the tabbed views. It is the content relevant to each label. Children should not be passed as Props, but should instead be nested between the opening and closing `<Tabs> </Tabs>` tags.
### `labels` (string array or element array; required)
`labels` specifies the list of headings that will appear on all of the tabs that will be created.
# asInput
Handles all necessary props that are related to Input typed components.
## API
### `className` (string array; optional)
`className` specifies Bootstrap class names to apply to the input component. The default is an empty array.
### `description` (string or element; optional)
`description` can be used to provide a longer description of the component. It will show up below the input component specified. The default is an empty string.
### `disabled` (boolean; optional)
`disabled` specifies if the component is disabled. The default type is false.
### `label` (string; required)
`label` specifies the label to be used for the overarching form-group. It appears above the input component.
### `name` (string; required)
`name` specifies the value for the name property within the component.
### `onBlur` (function; optional)
`onBlur` is a function that would specify what the input component should do when the `onBlur` event is triggered. For example, it could be used to update which element is currently in focus within the state. The default is an empty function.
### `onChange` (function; optional)
`onChange` is a function that would specify what the input component should do when the `onChange` event is triggered. For example, it could be storing the updated input data within the state. The default is an empty function.
### `required` (boolean; optional)
`required` specifies if the component is required. The default type is false.
### `validator` (function; optional)
`validator` specifies the function to use for validation logic if the input needs to be validated. Default is undefined.
### `value` (string; optional)
`value` specifies the value for the value property within the component. The default is an empty string.
@import '~edx-bootstrap/sass/open-edx/theme'; @import '~@edx/edx-bootstrap/sass/open-edx/theme';
@import "~bootstrap/scss/variables"; @import "~bootstrap/scss/variables";
@import "~bootstrap/scss/mixins"; @import "~bootstrap/scss/mixins";
......
const path = require('path'); const path = require('path');
const BabiliPlugin = require('babili-webpack-plugin'); const MinifyPlugin = require('babel-minify-webpack-plugin');
const env = process.env.NODE_ENV || 'dev'; const env = process.env.NODE_ENV || 'dev';
...@@ -17,7 +17,7 @@ const base = { ...@@ -17,7 +17,7 @@ const base = {
extensions: ['.js', '.jsx'], extensions: ['.js', '.jsx'],
}, },
plugins: [ plugins: [
new BabiliPlugin(), new MinifyPlugin(),
], ],
module: { module: {
rules: [ rules: [
...@@ -28,7 +28,7 @@ const base = { ...@@ -28,7 +28,7 @@ const base = {
{ {
loader: 'babel-loader', loader: 'babel-loader',
options: { options: {
presets: ['env', 'babili'], presets: ['env', 'minify'],
}, },
}, },
{ loader: 'source-map-loader' }, { loader: 'source-map-loader' },
......
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment