feat: major upgrades of many packages and configuration (#394) (#400)

This is a BREAKING CHANGE.

* update storybook, webpack, and other supporting packages
* upgrade node version in travis
* fix: clean up commonly repeated storybook configuration
* fix: update dependencies
* fix: misc config changes
* fix: height of fieldset

long term the .form-control class should not be used on fieldset. It now explicitly sets a height and was probably not intended to be used the way it is in paragon's fieldset.

* fix: remove css module import for font awesome in Icon story
* fix: update storyshot config to have no decorators
* fix: use whitelist for npm publishing
* fix: remove source maps from build. hopefully using less memory
* fix: update notifications to paragon@edx.org

feat: remove static build output (#398)

Removes a the static build output. Sets the stage to remove css modules and simplify JSX elsewhere.

feat: remove css module and namespace support (#399)

This changes the way scss should be imported with components. Rather than a SCSS file for each module, this prefers that consumers import the scss file (src/index.scss). It must be included after bootstrap variables are set. It's also up to the consumer to include font-awesome if needed.

See ADR for removal of CSS Modules.

* fix: remove usage of css module styles object
* feat: remove css modules and consolidate scss inclusion pattern
* fix: build scss separately
* fix: remove array around className prop for raw html nodes
This commit is contained in:
Adam Butterworth 2019-03-22 10:47:17 -07:00 committed by GitHub
parent 8af1f5d6b4
commit 0274806f8e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
98 changed files with 30851 additions and 98294 deletions

View File

@ -1,13 +1,21 @@
{
"presets": [
["env", {
"targets": {
"browsers": ["last 2 versions", "ie 11"]
[
"@babel/preset-env",
{
"targets": {
"browsers": ["last 2 versions", "ie 11"]
}
}
}],
"babel-preset-react"
],
"@babel/preset-react"
],
"plugins": [
"transform-object-rest-spread"
]
"@babel/plugin-proposal-object-rest-spread"
],
"env": {
"test": {
"plugins": ["require-context-hook"]
}
}
}

View File

@ -1,4 +1,3 @@
coverage/*
node_modules
static
themeable
dist/
node_modules/

40
.eslintrc Normal file
View File

@ -0,0 +1,40 @@
{
"extends": "eslint-config-edx",
"parser": "babel-eslint",
"rules": {
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": [
"**/*.stories.jsx",
"src/setupTest.js",
"**/*.test.jsx",
"**/*.test.js",
"config/*.js",
"*.config.js",
"*.config.*.js",
"**/*.config.js"
]
}
],
// https://github.com/evcohen/eslint-plugin-jsx-a11y/issues/340#issuecomment-338424908
"jsx-a11y/anchor-is-valid": [ "error", {
"components": [ "Link" ],
"specialLink": [ "to" ]
}]
},
"overrides": [
{
"files": ["*.stories.jsx"],
"rules": {
"no-console": "off"
}
}
],
"env": {
"jest": true
},
"globals": {
"newrelic": false
}
}

View File

@ -1,35 +0,0 @@
{
"extends": ["eslint-config-edx"],
"parserOptions": {
"ecmaFeatures": {
"jsx": true
}
},
"rules": {
"react/no-array-index-key": "off",
"compat/compat": "error",
"jsx-a11y/anchor-is-valid": [ "error", {
"components": [ "a" ],
"specialLink": [ "hrefLeft", "hrefRight" ],
"aspects": [ "noHref", "invalidHref", "preferButton" ]
}]
},
"settings": {
"import/resolver": {
"node": {
"extensions": [".js", ".jsx"]
}
}
},
"env": {
"jest": true,
"browser": true
},
"plugins": ["compat"],
"overrides": {
"files": ["*.stories.jsx", "*.test.jsx"],
"rules": {
"import/no-extraneous-dependencies": "off" // storybook & enzyme should stay devDependencies
}
}
}

3
.gitignore vendored
View File

@ -4,5 +4,4 @@ node_modules
npm-debug.log
coverage
jest*
static
themeable
dist

View File

@ -1,13 +0,0 @@
.DS_Store
.eslintcache
.storybook
node_modules
npm-debug.log
.travis.yml
.babelrc
.eslintignore
.eslintrc.json
commitlint.config.js
webpack.config.js
**.test.js
**.stories.js

View File

@ -2,18 +2,10 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
const styles = {
main: {
margin: 15,
maxWidth: 600,
lineHeight: 1.4,
fontFamily: '"Helvetica Neue", Helvetica, "Segoe UI", Arial, freesans, sans-serif',
},
};
storiesOf('Paragon', module)
.add('Welcome', () => (
<div style={styles.main}>
<div>
<h1>💎 Paragon</h1>
<p>
This is a documentation and demo space for the Paragon accessible UI component

View File

@ -1,17 +1,7 @@
import initStoryshots from '@storybook/addon-storyshots';
import '../src/utils/reactResponsive.mock';
// per https://github.com/storybooks/storybook/issues/2522
// TypeError: (0 , _reactDom.findDOMNode) is not a function errors due to a11y addon
jest.mock("react-dom", () => {
return {
render: () => null,
unmountComponentAtNode: () => null,
findDOMNode: () => null,
createPortal: () => null,
};
});
initStoryshots({
storyKindRegex: /^((?!.*?Modal).)*$/,
configPath: '.storybook/storyshots.config.js',
});

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,2 @@
import '@storybook/addon-options/register';
import '@storybook/addon-actions/register';
import '@storybook/addon-a11y/register';
import 'storybook-readme/register';

View File

@ -1,31 +1,34 @@
import React from 'react';
import { configure, addDecorator } from '@storybook/react';
import { setOptions } from '@storybook/addon-options';
import { setDefaults } from '@storybook/addon-info';
import { addParameters, addDecorator, configure } from '@storybook/react';
import { setConsoleOptions } from '@storybook/addon-console';
import { withInfo } from '@storybook/addon-info';
import CssJail from '../src/CssJail';
// Style applied to all stories
import "./style.scss";
setDefaults({
inline: false,
header: true,
source: true,
setConsoleOptions({
panelExclude: ['warn', 'error'],
});
setTimeout(() => setOptions({
name: '💎 PARAGON',
url: 'https://github.com/edx/paragon',
showDownPanel: true,
downPanelInRight: true,
}), 1000);
// Option defaults:
addParameters({
options: {
brandTitle: '💎 PARAGON',
brandUrl: 'https://github.com/edx/paragon',
},
info: {
inline: false,
source: true,
},
});
addDecorator(withInfo);
addDecorator(storyFn => <div className="p-5">{storyFn()}</div>);
const req = require.context('../src', true, /\.stories\.jsx$/);
addDecorator(story => (
<CssJail>
{story()}
</CssJail>
));
function loadStories() {
require('./Paragon.stories.jsx');
req.keys().forEach((filename) => req(filename));

View File

@ -0,0 +1,10 @@
import React from 'react';
import { configure } from '@storybook/react';
const req = require.context('../src', true, /\.stories\.jsx$/);
function loadStories() {
req.keys().forEach((filename) => req(filename));
}
configure(loadStories, module);

4
.storybook/style.scss Normal file
View File

@ -0,0 +1,4 @@
@import "~font-awesome/css/font-awesome.min.css";
@import '~@edx/edx-bootstrap/sass/open-edx/theme';
@import "~bootstrap/scss/bootstrap.scss";
@import "../src/index.scss";

View File

@ -1,43 +1,33 @@
const path = require('path');
const WebpackBuildNotifierPlugin = require('webpack-build-notifier');
// This config is merged with Storybook's default
// https://storybook.js.org/docs/configurations/custom-webpack-config/#extend-mode
module.exports = {
plugins: [
new WebpackBuildNotifierPlugin({
title: 'Paragon Storybook Build',
warningSound: true,
failureSound: true,
}),
],
devtool: "source-map",
module: {
rules: [
{
test: /\.scss|\.css$/,
use: [
loaders: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
options: {
modules: true,
localIdentName: '[local]',
sourceMap: true,
},
},
{
loader: 'sass-loader',
options: {
data: '@import "paragon-reset";',
includePaths: [
path.join(__dirname, '../src/utils'),
path.join(__dirname, '../node_modules'),
],
sourceMap: true,
},
},
],
include: path.resolve(__dirname, '../'),
},
{
test: /\.(woff2?|ttf|svg|eot)(\?v=\d+\.\d+\.\d+)?$/,

View File

@ -1,15 +1,14 @@
language: node_js
node_js:
- 10
- 8
- 6
cache:
directories:
- "~/.npm"
notifications:
email:
recipients:
- arizzitano@edx.org
- jbradley@edx.org
- paragon@edx.org
on_success: never
on_failure: always
hipchat:

View File

@ -21,17 +21,30 @@ Paragon requires React 16 or higher. To install Paragon into your project:
npm i --save @edx/paragon
```
Since Paragon is a React library, components must be rendered with React and ReactDOM. This doesn't mean the entire page has to be rendered with React. You can read up about rendering React into a page within the [official ReactDOM documentation](https://reactjs.org/docs/react-dom.html).
In your JSX files:
```
import { ComponentName } from '@edx/paragon';
```
### Export Targets
And in your SCSS file:
```
import '~bootstrap/scss/bootstrap'; // Or other inclusion of bootstrap
import '~@edx/paragon/src/index.scss';
```
This file contains style modifications for all paragon components. It is not large and is the simplest way to include Paragon styles. Note
Paragon's production build ships with two different export targets:
* It does not include bootstrap. You must include that yourself.
* It must be included after bootstrap functions, variables, and mixins
**`themeable`**: This is the default export intended for use within Bootstrap 4 pages. Components ship with stock Bootstrap classnames and can accept Bootstrap themes. This build assumes Bootstrap/Font Awesome are already available on the consuming page. You should not need to add any additional stylesheets -- just plug and go. Import syntax is as follows:
`import { ComponentName } from '@edx/paragon';`
If you are not using SCSS you can include a minified CSS file that also does not include bootstrap:
```
import '~@edx/paragon/dist/paragon.min.css';
```
**`static`**: The static build is intended for use within legacy pages not styled with Bootstrap 4. Component classnames are namespaced with the `paragon__` prefix so as not to conflict with existing classnames defined elsewhere on the page. Be cognizant of poorly scoped legacy rules (e.g. a rule for all `input` or `h1` tags), which can still affect Paragon components. This build comes with its own stylesheet, available at `/static/paragon.min.css`. You must include this stylesheet on any page that uses Paragon components. Components can be imported thus:
### Legacy Export Target [Removed]
**`static [Removed in version 4.0]`**: The static build is intended for use within legacy pages not styled with Bootstrap 4. Component classnames are namespaced with the `paragon__` prefix so as not to conflict with existing classnames defined elsewhere on the page. Be cognizant of poorly scoped legacy rules (e.g. a rule for all `input` or `h1` tags), which can still affect Paragon components. This build comes with its own stylesheet, available at `/static/paragon.min.css`. You must include this stylesheet on any page that uses Paragon components. Components can be imported thus:
`import { ComponentName } from '@edx/paragon/static';`

1
__mocks__/fileMock.js Normal file
View File

@ -0,0 +1 @@
module.exports = 'test-file-stub';

View File

@ -0,0 +1,32 @@
6. Removal of CSS Module Support
--------------------------------
Status
------
Accepted
Context
-------
Paragon utilizes CSS modules to create a namespaced version of Paragon components. This namespaced version included an export of bootstrap and paragon styles with the .paragon__ prefix. It's purpose was to allow Paragon to be used in pages that didn't include bootstrap and would like have conflicts if it was added. This "static" version is consumed in edx-platform in 11 places today.
A resulting complication of using CSS Modules is that each component JSX file must include a SCSS file that contains all the classes it will use. In most situations this is perfectly fine, but in our case we are including partials from bootstrap that contain variable names and mixins that have changed between minor versions. The result is that if a consuming application is using a 4.1.x version of bootstrap and Paragon's latest export is 4.2.x, there may be SCSS compile errors in the consuming app.
Decision
--------
Remove our usage of CSS Modules. If we find a need to have CSS modules in the future we should look at other solutions. Concretely this decision includes these changes:
* Remove all SCSS imports in Paragon components (JSX).
* Delete SCSS files that had no other purpose than to supply namespaced class names
Consequences
------------
Paragon will rely on bootstrap class names being present for styling. It will no longer attempt to solve CSS conflicts for consuming applications.
References
----------
* https://css-tricks.com/css-modules-part-1-need/

43153
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -2,21 +2,25 @@
"name": "@edx/paragon",
"version": "0.0.0-development",
"description": "Accessible, responsive UI component library based on Bootstrap.",
"main": "themeable/index.js",
"main": "dist/paragon.js",
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "https://github.com/edx/paragon.git"
},
"files": [
"/dist",
"/src"
],
"scripts": {
"build": "NODE_ENV=production webpack --config webpack.config.js",
"build": "webpack --mode production",
"build-storybook": "build-storybook",
"commit": "commit",
"coveralls": "cat ./coverage/lcov.info | coveralls",
"debug-test": "node --inspect-brk node_modules/.bin/jest --runInBand --coverage",
"deploy-storybook": "storybook-to-ghpages",
"deploy-storybook-ci": "storybook-to-ghpages --ci",
"gc": "commit",
"is-es5": "es-check es5 ./themeable/*.js ./static/*.js",
"is-es5": "es-check es5 ./dist/*.js",
"lint": "eslint --ext .js --ext .jsx .",
"prepublishOnly": "npm run build",
"semantic-release": "semantic-release",
@ -26,80 +30,70 @@
"travis-deploy-once": "travis-deploy-once"
},
"dependencies": {
"@edx/edx-bootstrap": "^1.0.0",
"@sambego/storybook-styles": "^1.0.0",
"airbnb-prop-types": "^2.10.0",
"babel-polyfill": "^6.26.0",
"classnames": "^2.2.5",
"email-prop-type": "^1.1.5",
"airbnb-prop-types": "^2.12.0",
"classnames": "^2.2.6",
"email-prop-type": "^3.0.0",
"font-awesome": "^4.7.0",
"mailto-link": "^1.0.0",
"prop-types": "^15.5.8",
"react": "^16.4.2",
"react-dom": "^16.1.0",
"react-element-proptypes": "^1.0.0",
"prop-types": "^15.7.2",
"react": "^16.8.4",
"react-dom": "^16.8.4",
"react-proptype-conditional-require": "^1.0.4",
"react-responsive": "^5.0.0",
"sanitize-html": "^1.18.2"
"react-responsive": "^6.1.1",
"sanitize-html": "^1.20.0"
},
"devDependencies": {
"@commitlint/cli": "^6.0.2",
"@commitlint/config-angular": "^6.0.2",
"@commitlint/prompt": "^6.0.2",
"@commitlint/prompt-cli": "^6.0.2",
"@storybook/addon-a11y": "^3.3.13",
"@storybook/addon-actions": "^3.4.6",
"@storybook/addon-centered": "^3.3.13",
"@storybook/addon-console": "^1.0.0",
"@storybook/addon-info": "^3.3.14",
"@storybook/addon-options": "^3.3.10",
"@storybook/addon-storyshots": "^3.3.10",
"@storybook/addons": "^3.3.10",
"@storybook/channels": "^3.3.10",
"@storybook/react": "^3.3.10",
"@storybook/storybook-deployer": "^2.3.0",
"babel-cli": "^6.24.1",
"babel-jest": "^23.0.0",
"babel-loader": "^7.0.0",
"babel-plugin-transform-object-rest-spread": "^6.23.0",
"babel-preset-env": "^1.4.0",
"@babel/core": "^7.3.4",
"@babel/plugin-proposal-object-rest-spread": "^7.3.4",
"@babel/preset-env": "^7.3.4",
"@babel/preset-react": "^7.0.0",
"@commitlint/cli": "^7.5.2",
"@commitlint/config-angular": "^7.5.0",
"@commitlint/prompt-cli": "^7.5.0",
"@edx/edx-bootstrap": "^1.0.4",
"@storybook/addon-a11y": "^5.0.3",
"@storybook/addon-actions": "^5.0.3",
"@storybook/addon-centered": "^5.0.3",
"@storybook/addon-console": "^1.1.0",
"@storybook/addon-info": "^5.0.3",
"@storybook/addon-storyshots": "^5.0.3",
"@storybook/react": "^5.0.3",
"@storybook/storybook-deployer": "^2.8.1",
"babel-eslint": "^10.0.1",
"babel-jest": "^24.5.0",
"babel-loader": "^8.0.5",
"babel-plugin-require-context-hook": "^1.0.0",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-preset-react": "^6.24.1",
"coveralls": "^3.0.0",
"css-loader": "^0.28.4",
"enzyme": "^3.1.1",
"enzyme-adapter-react-16": "^1.0.4",
"es-check": "^3.0.0",
"eslint": "^4.5.0",
"eslint-config-edx": "^4.0.3",
"eslint-import-resolver-webpack": "^0.10.0",
"eslint-plugin-compat": "^2.2.0",
"extract-text-webpack-plugin": "^3.0.1",
"file-loader": "^1.1.4",
"greenkeeper-lockfile": "^1.7.1",
"husky": "^1.0.0",
"bootstrap": "^4.3.1",
"clean-webpack-plugin": "^2.0.1",
"coveralls": "^3.0.3",
"css-loader": "^2.1.1",
"enzyme": "^3.9.0",
"enzyme-adapter-react-16": "^1.11.2",
"es-check": "^5.0.0",
"eslint-config-edx": "^4.0.4",
"greenkeeper-lockfile": "^1.15.1",
"husky": "^1.3.1",
"identity-obj-proxy": "^3.0.0",
"jest": "^22.0.4",
"jest-cli": "^22.0.4",
"jest": "^24.5.0",
"markdown-loader-jest": "^0.1.1",
"node-sass": "^4.9.4",
"postcss-scss": "^2.0.0",
"react-router-dom": "^4.1.1",
"react-test-renderer": "^16.1.0",
"sass-loader": "^6.0.5",
"semantic-release": "^15.1.7",
"source-map-loader": "^0.2.1",
"storybook-readme": "^3.3.0",
"style-loader": "^0.22.0",
"travis-deploy-once": "^5.0.0",
"uglifyjs-webpack-plugin": "^1.3.0",
"webpack": "^3.0.0",
"webpack-build-notifier": "^0.1.22",
"webpack-dev-server": "^3.0.0"
"mini-css-extract-plugin": "^0.5.0",
"node-sass": "^4.11.0",
"sass-loader": "^7.1.0",
"semantic-release": "^15.13.3",
"source-map-loader": "^0.2.4",
"storybook-readme": "^4.0.5",
"style-loader": "^0.23.1",
"travis-deploy-once": "^5.0.11",
"uglifyjs-webpack-plugin": "^2.1.2",
"webpack": "^4.29.6",
"webpack-cli": "^3.3.0"
},
"jest": {
"transform": {
"^.+\\.md?$": "markdown-loader-jest",
"^.+\\.jsx?$": "<rootDir>/node_modules/babel-jest"
"^.+\\.jsx?$": "babel-jest"
},
"setupFiles": [
"./src/setupTest.js"
@ -108,14 +102,17 @@
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js",
"\\.(css|scss)$": "identity-obj-proxy"
},
"collectCoverageFrom": [
"src/**/*.{js,jsx}"
],
"coveragePathIgnorePatterns": [
"/.storybook/",
"/node_modules/",
"(.stories)\\.(jsx)$",
"\\.(mock.js)$"
"src/setupTest.js",
"src/index.js",
"/tests/"
],
"transformIgnorePatterns": [
"/node_modules/(?!lodash-es/.*)"
"/node_modules/(?!(@edx/paragon)/).*/"
]
},
"browserslist": [

View File

@ -1,2 +0,0 @@
@import "~bootstrap/scss/_type";
@import "~bootstrap/scss/_utilities";

View File

@ -1,10 +1,5 @@
/* eslint-disable import/no-extraneous-dependencies */
import React from 'react';
import { storiesOf } from '@storybook/react';
import centered from '@storybook/addon-centered';
import { checkA11y } from '@storybook/addon-a11y';
import { withInfo } from '@storybook/addon-info';
import { withReadme } from 'storybook-readme';
import Breadcrumb from './index';
import README from './README.md';
@ -26,10 +21,7 @@ const sampleLinks = [
];
storiesOf('Breadcrumb', module)
.addDecorator((story, context) => withInfo()(story)(context))
.addDecorator(centered)
.addDecorator(checkA11y)
.addDecorator(withReadme(README))
.addParameters({ info: { text: README } })
.add('basic usage', () => (
<Breadcrumb links={sampleLinks} />
))

View File

@ -1,5 +1,3 @@
# Breadcrumb
Provides a basic breadcrumb component.
## API

View File

@ -2,7 +2,6 @@ import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import styles from './Breadcrumb.scss';
import Icon from '../Icon';
const Breadcrumbs = ({
@ -12,20 +11,20 @@ const Breadcrumbs = ({
return (
<nav aria-label="breadcrumb">
<ol className={classNames(styles['list-inline'])}>
<ol className={classNames('list-inline')}>
{links.map(({ url, label }, i) => (
<React.Fragment key={url}>
<li className={classNames(styles['list-inline-item'])}>
<li className={classNames('list-inline-item')}>
<a href={url} {...(clickHandler && { onClick: clickHandler })}>{label}</a>
</li>
{(activeLabel || ((i + 1) < linkCount)) &&
<li className={classNames(styles['list-inline-item'])} role="presentation" aria-label="spacer">
<li className={classNames('list-inline-item')} role="presentation" aria-label="spacer">
{spacer || <Icon className={['fa', 'fa-chevron-right']} id={`spacer-${i}`} />}
</li>
}
</React.Fragment>
))}
{activeLabel && <li className={classNames(styles['list-inline-item'])} key="active">{activeLabel}</li>}
{activeLabel && <li className={classNames('list-inline-item')} key="active">{activeLabel}</li>}
</ol>
</nav>
);

View File

@ -1,2 +0,0 @@
@import "~bootstrap/scss/_buttons";
@import "~bootstrap/scss/_close";

View File

@ -1,20 +1,12 @@
/* eslint-disable import/no-extraneous-dependencies */
import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import centered from '@storybook/addon-centered';
import { checkA11y } from '@storybook/addon-a11y';
import { withInfo } from '@storybook/addon-info';
import { withReadme } from 'storybook-readme';
import README from './README.md';
import Button from './index';
storiesOf('Button', module)
.addDecorator((story, context) => withInfo()(story)(context))
.addDecorator(centered)
.addDecorator(checkA11y)
.addDecorator(withReadme(README))
.addParameters({ info: { text: README } })
.add('basic usage', () => (
<Button
label="Click me and check the console!"

View File

@ -1,4 +1,3 @@
/* eslint-disable import/no-extraneous-dependencies */
import React from 'react';
import { mount } from 'enzyme';
import Button from './index';

View File

@ -2,7 +2,6 @@ import React from 'react';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import styles from './Button.scss';
class Button extends React.Component {
constructor(props) {
@ -63,12 +62,12 @@ class Button extends React.Component {
<button
{...other}
className={classNames([
styles.btn,
'btn',
...className,
], {
[styles[`btn-${buttonType}`]]: buttonType !== undefined,
[`btn-${buttonType}`]: buttonType !== undefined,
}, {
[styles.close]: isClose,
close: isClose,
})}
onBlur={this.onBlur}
onClick={this.onClick}

View File

@ -1,11 +1,5 @@
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable no-console */
import React from 'react';
import { storiesOf } from '@storybook/react';
import centered from '@storybook/addon-centered';
import { checkA11y } from '@storybook/addon-a11y';
import { withInfo } from '@storybook/addon-info';
import { withReadme } from 'storybook-readme';
import CheckBox from './index';
import Button from '../Button';
@ -47,10 +41,7 @@ class CheckBoxWrapper extends React.Component {
}
storiesOf('CheckBox', module)
.addDecorator((story, context) => withInfo()(story)(context))
.addDecorator(centered)
.addDecorator(checkA11y)
.addDecorator(withReadme(README))
.addParameters({ info: { text: README } })
.add('basic usage', () => (
<CheckBox
name="checkbox"

View File

@ -1,4 +1,3 @@
/* eslint-disable import/no-extraneous-dependencies */
import React from 'react';
import { mount } from 'enzyme';
import CheckBox from './index';

View File

@ -1,2 +0,0 @@
@import "~bootstrap/scss/_forms";
@import "~bootstrap/scss/mixins/_forms";

View File

@ -1,11 +1,5 @@
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable no-console */
import React from 'react';
import { storiesOf } from '@storybook/react';
import centered from '@storybook/addon-centered';
import { checkA11y } from '@storybook/addon-a11y';
import { withInfo } from '@storybook/addon-info';
import { withReadme } from 'storybook-readme';
import CheckBoxGroup from './index';
import CheckBox from '../CheckBox';
@ -13,10 +7,7 @@ import CheckBox from '../CheckBox';
import README from './README.md';
storiesOf('CheckBoxGroup', module)
.addDecorator((story, context) => withInfo()(story)(context))
.addDecorator(centered)
.addDecorator(checkA11y)
.addDecorator(withReadme(README))
.addParameters({ info: { text: README } })
.add('basic usage', () => (
<CheckBoxGroup>
<CheckBox

View File

@ -1,4 +1,3 @@
/* eslint-disable import/no-extraneous-dependencies */
import React from 'react';
import { mount } from 'enzyme';
import CheckBoxGroup from './index';

View File

@ -1,20 +1,17 @@
import React from 'react';
import PropTypes from 'prop-types';
import ElementPropTypes from 'react-element-proptypes';
import CheckBox from '../CheckBox';
import styles from './CheckBoxGroup.scss';
function CheckBoxGroup(props) {
return (
<div className={styles['form-group']}>
<div className="form-group">
{props.children}
</div>
);
}
CheckBoxGroup.propTypes = {
children: PropTypes.arrayOf(ElementPropTypes.elementOfType(CheckBox)).isRequired,
children: PropTypes.arrayOf(PropTypes.element).isRequired,
};
export default CheckBoxGroup;

View File

@ -1,7 +1,3 @@
@import "~bootstrap/scss/_variables";
@import "~bootstrap/scss/_grid";
@import "~bootstrap/scss/_utilities";
// Local Variables
$collapsible-border: 1px solid silver;

View File

@ -1,23 +1,12 @@
/* eslint-disable import/no-extraneous-dependencies */
import React from 'react';
import { storiesOf } from '@storybook/react';
import { checkA11y } from '@storybook/addon-a11y';
import { withInfo } from '@storybook/addon-info';
import { withReadme } from 'storybook-readme';
import styles from '@sambego/storybook-styles';
import README from './README.md';
import Collapsible from './index';
import { breakpoints } from '../Responsive';
storiesOf('Collapsible', module)
.addDecorator((story, context) => withInfo()(story)(context))
.addDecorator(checkA11y)
.addDecorator(withReadme(README))
.addDecorator(styles({
height: '500px',
margin: '250px',
}))
.addParameters({ info: { text: README } })
.add('basic usage without resizing', () => (
<Collapsible title="Click me to expand">
<p>Your stuff goes here</p>

View File

@ -31,23 +31,12 @@ resize to determine whether to display the collapsible or regular view. The
example below demonstrates a collapsible that will only show the open/close
button for non-desktop screens.
If no function is given, the collapsible does not handle resizing and will
always show the open/close button.
### `iconId` (string; optional)
`iconId` is the id attribute that is passed to the icon on the collapsible.
Defaults to the empty string.
```jsx
<Collapsible
title="Collapsible"
expandedTitle={<h2>Collapsible</h2>}
isCollapsible={() => window.matchMedia('(min-width: 992px)').matches}
>
<p>Child 1</p>
<p>Child 2</p>
</Collapsible>
```
If no function is given, the collapsible does not handle resizing and will
always show the open/close button.
### `onToggle` (function; optional)
`onToggle` is an optional callback that is trigged when the Collapsible components is opened or closed. A boolean is passed to the callback with the value of `isOpen` from the component's state.

View File

@ -4,7 +4,6 @@ import PropTypes from 'prop-types';
import Button from '../Button';
import Icon from '../Icon';
import styles from './Collapsible.scss';
class Collapsible extends React.Component {
constructor(props) {
@ -73,11 +72,11 @@ class Collapsible extends React.Component {
const { isExpanded, isOpen } = this.state;
return (
<div className={[classNames(
<div className={classNames(
'collapsible',
{ [styles.open]: isOpen && !isExpanded },
{ [styles.expanded]: isExpanded },
)]}
{ open: isOpen && !isExpanded },
{ expanded: isExpanded },
)}
>
{isExpanded ? (
expandedTitle
@ -86,8 +85,8 @@ class Collapsible extends React.Component {
aria-expanded={isOpen}
className={[classNames(
'btn-block text-left',
styles['btn-collapsible'],
{ [styles.open]: isOpen },
'btn-collapsible',
{ open: isOpen },
)]}
label={
<div className="collapsible-title">
@ -107,10 +106,10 @@ class Collapsible extends React.Component {
onClick={this.handleClick}
/>
)}
<div className={[classNames(
<div className={classNames(
'collapsible-body',
{ [styles.open]: isOpen || isExpanded },
)]}
{ open: isOpen || isExpanded },
)}
>
{children}
</div>

View File

@ -1 +0,0 @@
@import "~bootstrap/scss/_reboot";

View File

@ -1,13 +1,12 @@
import React from 'react';
import PropTypes from 'prop-types';
import styles from './CssJail.scss';
function CssJail({ children }) {
return (
<div
style={{ fontFamily: 'sans-serif' }}
className={styles['css-jail']}
className="css-jail"
>
{children}
</div>

View File

@ -1,21 +1,3 @@
@import "~bootstrap/scss/_dropdown";
@import "~bootstrap/scss/utilities/_borders";
@import "~bootstrap/scss/utilities/_display";
@import "~bootstrap/scss/utilities/_flex";
@import "~bootstrap/scss/utilities/_background";
// Trying to patch this upstream, then this can
// be removed.
// https://github.com/twbs/bootstrap/pull/23990
.show {
> a:focus {
outline-width: 2px;
outline-style: solid;
outline-color: Highlight;
outline: -webkit-focus-ring-color auto 5px;
}
}
.dropdown.has-icon {
.icon-container {
max-width: 40px;

View File

@ -1,20 +1,12 @@
/* eslint-disable import/no-extraneous-dependencies */
import React from 'react';
import { storiesOf } from '@storybook/react';
import centered from '@storybook/addon-centered';
import { checkA11y } from '@storybook/addon-a11y';
import { withInfo } from '@storybook/addon-info';
import { withReadme } from 'storybook-readme';
import Dropdown from './index';
import Icon from '../Icon';
import README from './README.md';
storiesOf('Dropdown', module)
.addDecorator((story, context) => withInfo()(story)(context))
.addDecorator(centered)
.addDecorator(checkA11y)
.addDecorator(withReadme(README))
.addParameters({ info: { text: README } })
.add('basic usage', () => (
<Dropdown
title="Search Engines"

View File

@ -1,4 +1,3 @@
/* eslint-disable import/no-extraneous-dependencies */
import React from 'react';
import { mount, shallow } from 'enzyme';

View File

@ -2,7 +2,6 @@ import React from 'react';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import styles from './Dropdown.scss';
import Button from '../Button';
export const triggerKeys = {
@ -107,7 +106,7 @@ class Dropdown extends React.Component {
if (React.isValidElement(menuItem)) {
const cloneProps = {
ref: (item) => { this.menuItems[i] = item; },
className: styles['dropdown-item'],
className: 'dropdown-item',
key: i,
onKeyDown: this.handleMenuKeyDown,
};
@ -115,9 +114,9 @@ class Dropdown extends React.Component {
}
return (
<a
className={styles['dropdown-item']}
className="dropdown-item"
href={menuItem.href}
key={i}
key={menuItem.href}
onKeyDown={this.handleMenuKeyDown}
ref={(item) => {
this.menuItems[i] = item;
@ -143,31 +142,31 @@ class Dropdown extends React.Component {
return (
<div
className={classNames([
styles.dropdown,
'dropdown',
{
[styles.show]: open,
[styles['has-icon']]: hasIconElement,
[styles.rounded]: hasIconElement,
[styles.border]: hasIconElement,
[styles['d-flex']]: hasIconElement,
[styles['bg-white']]: hasIconElement,
show: open,
'has-icon': hasIconElement,
rounded: hasIconElement,
border: hasIconElement,
'd-flex': hasIconElement,
'bg-white': hasIconElement,
},
])}
ref={(container) => { this.container = container; }}
>
{ hasIconElement &&
<div
className={[classNames([
styles['icon-container'],
styles['d-flex'],
styles['align-items-center'],
styles['justify-content-center'],
styles['border-right'],
])]}
className={classNames([
'icon-container',
'd-flex',
'align-items-center',
'justify-content-center',
'border-right',
])}
>
{React.cloneElement(iconElement, {
className: iconElement.props && Array.isArray(iconElement.props.className) ?
[...iconElement.props.className, styles['rounded-left']] : styles['rounded-left'],
[...iconElement.props.className, 'rounded-left'] : 'rounded-left',
})}
</div>
}
@ -179,11 +178,11 @@ class Dropdown extends React.Component {
onClick={this.toggle}
onKeyDown={this.handleToggleKeyDown}
className={[classNames([
styles['dropdown-toggle'],
'dropdown-toggle',
{
[styles['border-0']]: hasIconElement,
[styles['rounded-0']]: hasIconElement,
[styles['bg-white']]: hasIconElement,
'border-0': hasIconElement,
'rounded-0': hasIconElement,
'bg-white': hasIconElement,
},
])]}
type="button"
@ -193,8 +192,8 @@ class Dropdown extends React.Component {
aria-label={title}
aria-hidden={!open}
className={classNames([
styles['dropdown-menu'],
{ [styles.show]: open },
'dropdown-menu',
{ show: open },
])}
role="menu"
>

View File

@ -1,10 +1,9 @@
@import "~bootstrap/scss/_forms";
@import '~bootstrap/scss/utilities/_borders';
@import "~bootstrap/scss/utilities/_screenreaders.scss";
@import '~bootstrap/scss/utilities/_spacing';
.paragon-fieldset {
@extend .mb-4;
margin-bottom: $spacer * 1.5;
.form-control {
height: auto;
}
fieldset legend {
width: auto;

View File

@ -1,9 +1,5 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import centered from '@storybook/addon-centered';
import { checkA11y } from '@storybook/addon-a11y';
import { withInfo } from '@storybook/addon-info';
import { withReadme } from 'storybook-readme';
import Fieldset from './index';
import InputText from '../InputText/index';
@ -75,10 +71,7 @@ class ValidatedForm extends React.Component {
}
storiesOf('Fieldset', module)
.addDecorator((story, context) => withInfo()(story)(context))
.addDecorator(centered)
.addDecorator(checkA11y)
.addDecorator(withReadme(README))
.addParameters({ info: { text: README } })
.add('basic usage', () => (
<form>
<Fieldset

View File

@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import classNames from 'classnames';
import newId from '../utils/newId';
import styles from './Fieldset.scss';
import ValidationMessage from '../ValidationMessage/index';
import Variant from '../utils/constants';
@ -52,7 +52,7 @@ class Fieldset extends React.Component {
switch (variant.status) {
case Variant.status.INFO:
className = styles['is-invalid-nodanger'];
className = 'is-invalid-nodanger';
break;
default:
break;
@ -73,18 +73,18 @@ class Fieldset extends React.Component {
} = this.props;
const errorId = `error-${this.state.id}`;
return (
<div className={styles['paragon-fieldset']}>
<div className="paragon-fieldset">
<fieldset
className={classNames(
styles['form-control'],
styles['p-3'],
'form-control',
'p-3',
{ 'is-invalid': !isValid },
this.getVariantClassName(),
className,
)}
aria-describedby={errorId}
>
<legend className={styles['p-1']}>{legend}</legend>
<legend className="p-1">{legend}</legend>
{children}
</fieldset>
<ValidationMessage

View File

@ -1,23 +1,13 @@
/* eslint-disable import/no-extraneous-dependencies, no-console */
import React from 'react';
import { storiesOf } from '@storybook/react';
import centered from '@storybook/addon-centered';
import { checkA11y } from '@storybook/addon-a11y';
import { action } from '@storybook/addon-actions';
import { setConsoleOptions } from '@storybook/addon-console';
import { withInfo } from '@storybook/addon-info';
import { withReadme } from 'storybook-readme';
import 'font-awesome/css/font-awesome.min.css';
import README from './README.md';
import Hyperlink from './index';
import Icon from '../Icon/index';
setConsoleOptions({
panelExclude: ['warn', 'error'],
});
const onClick = (event) => {
console.log(`onClick fired for ${event.target}`);
@ -26,10 +16,7 @@ const onClick = (event) => {
};
storiesOf('HyperLink', module)
.addDecorator((story, context) => withInfo()(story)(context))
.addDecorator(centered)
.addDecorator(checkA11y)
.addDecorator(withReadme(README))
.addParameters({ info: { text: README } })
.add('minimal usage', () => <Hyperlink destination="https://en.wikipedia.org/wiki/Hyperlink" content="edX.org" />)
.add('with blank target', () => (
<Hyperlink

View File

@ -1,8 +1,6 @@
/* eslint-disable import/no-extraneous-dependencies */
import React from 'react';
import { shallow, mount } from 'enzyme';
import classNames from 'classnames';
import FontAwesomeStyles from 'font-awesome/css/font-awesome.min.css';
import Hyperlink from './index';
@ -47,7 +45,7 @@ describe('correct rendering', () => {
expect(icon.prop('aria-hidden')).toEqual(false);
expect(icon.prop('className'))
.toEqual(classNames(FontAwesomeStyles.fa, FontAwesomeStyles['fa-external-link']));
.toEqual(classNames('fa', 'fa-external-link'));
expect(icon.prop('aria-label')).toEqual(externalLinkAlternativeText);
expect(icon.prop('title')).toEqual(externalLinkTitle);
});

View File

@ -1,6 +1,5 @@
import React from 'react';
import classNames from 'classnames';
import FontAwesomeStyles from 'font-awesome/css/font-awesome.min.css';
import PropTypes from 'prop-types';
import isRequiredIf from 'react-proptype-conditional-require';
@ -22,7 +21,7 @@ function Hyperlink(props) {
// Space between content and icon
<span>{' '}
<span
className={classNames(FontAwesomeStyles.fa, FontAwesomeStyles['fa-external-link'])}
className={classNames('fa', 'fa-external-link')}
aria-hidden={false}
aria-label={externalLinkAlternativeText}
title={externalLinkTitle}

View File

@ -1 +0,0 @@
@import "~bootstrap/scss/utilities/_screenreaders.scss";

View File

@ -1,36 +1,20 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import centered from '@storybook/addon-centered';
import { checkA11y } from '@storybook/addon-a11y';
import { withInfo } from '@storybook/addon-info';
import { withReadme } from 'storybook-readme';
import FontAwesomeStyles from 'font-awesome/css/font-awesome.min.css';
import Icon from './index';
import README from './README.md';
storiesOf('Icon', module)
.addDecorator((story, context) => withInfo()(story)(context))
.addDecorator(centered)
.addDecorator(checkA11y)
.addDecorator(withReadme(README))
.addParameters({ info: { text: README } })
.add('basic usage', () => (
<Icon
className={[
FontAwesomeStyles.fa,
FontAwesomeStyles['fa-birthday-cake'],
FontAwesomeStyles['fa-4x'],
]}
className={['fa', 'fa-birthday-cake', 'fa-4x']}
/>
))
.add('with screenreader text', () => (
<Icon
id="SampleIcon"
className={[
FontAwesomeStyles.fa,
FontAwesomeStyles['fa-smile-o'],
FontAwesomeStyles['fa-4x'],
]}
className={['fa', 'fa-smile-o', 'fa-4x']}
screenReaderText="Happy Icon"
/>
));

View File

@ -1,13 +1,12 @@
import React from 'react';
import { mount } from 'enzyme';
import FontAwesomeStyles from 'font-awesome/css/font-awesome.min.css';
import Icon from './index';
const testId = 'testId';
const classNames = [
FontAwesomeStyles.fa,
FontAwesomeStyles['fa-check'],
'fa',
'fa-check',
];
const srTest = 'srTest';

View File

@ -2,7 +2,6 @@ import React from 'react';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import styles from './Icon.scss';
import newId from '../utils/newId';
function Icon(props) {
@ -14,7 +13,7 @@ function Icon(props) {
aria-hidden={props.hidden}
/>
{ props.screenReaderText &&
<span className={classNames(styles['sr-only'])}>
<span className={classNames('sr-only')}>
{props.screenReaderText}
</span>
}

View File

@ -1,19 +1,11 @@
/* eslint-disable import/no-extraneous-dependencies */
import React from 'react';
import { storiesOf } from '@storybook/react';
import centered from '@storybook/addon-centered';
import { checkA11y } from '@storybook/addon-a11y';
import { withInfo } from '@storybook/addon-info';
import { withReadme } from 'storybook-readme';
import InputSelect from './index';
import README from './README.md';
storiesOf('InputSelect', module)
.addDecorator((story, context) => withInfo()(story)(context))
.addDecorator(centered)
.addDecorator(checkA11y)
.addDecorator(withReadme(README))
.addParameters({ info: { text: README } })
.add('basic usage', () => (
<InputSelect
name="fruits"

View File

@ -1,12 +1,6 @@
/* eslint-disable import/no-extraneous-dependencies */
import React from 'react';
import { storiesOf } from '@storybook/react';
import centered from '@storybook/addon-centered';
import { checkA11y } from '@storybook/addon-a11y';
import { withInfo } from '@storybook/addon-info';
import { withReadme } from 'storybook-readme';
import FontAwesomeStyles from 'font-awesome/css/font-awesome.min.css';
import Button from '../Button';
import Icon from '../Icon';
@ -49,10 +43,7 @@ class FocusInputWrapper extends React.Component {
storiesOf('InputText', module)
.addDecorator((story, context) => withInfo()(story)(context))
.addDecorator(centered)
.addDecorator(checkA11y)
.addDecorator(withReadme(README))
.addParameters({ info: { text: README } })
.add('minimal usage', () => (
<InputText
name="name"
@ -269,8 +260,8 @@ storiesOf('InputText', module)
<Icon
id="checkmark"
className={[
FontAwesomeStyles.fa,
FontAwesomeStyles['fa-check'],
'fa',
'fa-check',
]}
screenReaderText="Checkmark"
/>
@ -290,8 +281,8 @@ storiesOf('InputText', module)
<Icon
id="checkmark"
className={[
FontAwesomeStyles.fa,
FontAwesomeStyles['fa-check'],
'fa',
'fa-check',
]}
screenReaderText="Checkmark"
/>

View File

@ -1 +0,0 @@
@import 'bootstrap/scss/_list-group.scss';

View File

@ -1,9 +1,6 @@
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable react/no-multi-comp */
import React from 'react';
import { storiesOf } from '@storybook/react';
import centered from '@storybook/addon-centered';
import 'bootstrap/scss/utilities/_screenreaders.scss';
import Button from '../Button';
import ListBox from '../ListBox';
@ -127,7 +124,6 @@ class ListBoxWrapperForSelectedOptionIndex extends React.Component {
}
storiesOf('ListBox', module)
.addDecorator(centered)
.add('basic usage', () => (
<ListBox>
<ListBoxOption>

View File

@ -1,10 +1,8 @@
import classNames from 'classnames';
import { elementType, nonNegativeInteger, or } from 'airbnb-prop-types';
import { nonNegativeInteger } from 'airbnb-prop-types';
import PropTypes from 'prop-types';
import React from 'react';
import ListBoxOption from '../ListBoxOption';
import styles from './ListBox.scss';
export default class ListBox extends React.Component {
constructor(props) {
@ -91,7 +89,7 @@ export default class ListBox extends React.Component {
this.props.tag,
{
'aria-activedescendant': this.state.selectedOptionIndex === null ? null : `list-box-option-${this.state.selectedOptionIndex}`,
className: classNames([styles['list-group'], this.props.className]),
className: classNames(['list-group', this.props.className]),
onFocus: this.onFocus,
onKeyDown: this.onKeyDown,
role: 'listbox',
@ -104,10 +102,7 @@ export default class ListBox extends React.Component {
}
ListBox.propTypes = {
children: or([
elementType(ListBoxOption),
PropTypes.arrayOf(elementType(ListBoxOption)),
]).isRequired,
children: PropTypes.node.isRequired,
className: PropTypes.string,
selectedOptionIndex: nonNegativeInteger,
tag: PropTypes.string,

View File

@ -1 +0,0 @@
@import 'bootstrap/scss/_list-group.scss';

View File

@ -2,7 +2,6 @@ import React from 'react';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import styles from './ListBoxOption.scss';
export default class ListBoxOption extends React.Component {
constructor(props) {
@ -45,8 +44,8 @@ export default class ListBoxOption extends React.Component {
{
'aria-selected': isSelected,
className: classNames(
styles['list-group-item'],
styles['list-group-item-action'],
'list-group-item',
'list-group-item-action',
{
active: this.props.isSelected,
},

View File

@ -2,19 +2,10 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { setConsoleOptions } from '@storybook/addon-console';
import { withInfo } from '@storybook/addon-info';
import centered from '@storybook/addon-centered';
import { checkA11y } from '@storybook/addon-a11y';
import { withReadme } from 'storybook-readme';
import MailtoLink from './index';
import README from './README.md';
setConsoleOptions({
panelExclude: ['warn', 'error'],
});
const onClick = (event) => {
console.log(`onClick fired for ${event.target}`);
@ -22,10 +13,7 @@ const onClick = (event) => {
};
storiesOf('MailtoLink', module)
.addDecorator((story, context) => withInfo()(story)(context))
.addDecorator(centered)
.addDecorator(checkA11y)
.addDecorator(withReadme(README))
.addParameters({ info: { text: README } })
.add('minimal usage', () => (
<MailtoLink
to="edx@example.com"

View File

@ -1,4 +1,3 @@
/* eslint-disable import/no-extraneous-dependencies */
import React from 'react';
import { shallow } from 'enzyme';

View File

@ -1,8 +1,3 @@
@import "~bootstrap/scss/_variables";
@import "~bootstrap/scss/_modal";
@import "~bootstrap/scss/_grid";
@import "~bootstrap/scss/_utilities";
.modal.show {
position: fixed;
background-color: transparent;

View File

@ -1,12 +1,7 @@
import { action } from '@storybook/addon-actions';
import FontAwesomeStyles from 'font-awesome/css/font-awesome.min.css';
import PropTypes from 'prop-types';
import React from 'react';
import centered from '@storybook/addon-centered';
import { storiesOf } from '@storybook/react';
import { checkA11y } from '@storybook/addon-a11y';
import { withInfo } from '@storybook/addon-info';
import { withReadme } from 'storybook-readme';
import { action } from '@storybook/addon-actions';
import Modal from './index';
import Button from '../Button';
@ -67,10 +62,7 @@ ModalWrapper.defaultProps = {
};
storiesOf('Modal', module)
.addDecorator((story, context) => withInfo()(story)(context))
.addDecorator(centered)
.addDecorator(checkA11y)
.addDecorator(withReadme(README))
.addParameters({ info: { text: README } })
.add('basic usage', () => (
<Modal
open
@ -147,8 +139,8 @@ storiesOf('Modal', module)
closeText={
<Icon
className={[
FontAwesomeStyles.fa,
FontAwesomeStyles['fa-ship'],
'fa',
'fa-ship',
]}
screenReaderText="Close"
/>}

View File

@ -1,4 +1,3 @@
/* eslint-disable import/no-extraneous-dependencies */
import React from 'react';
import { mount, shallow } from 'enzyme';

View File

@ -3,8 +3,6 @@ import ReactDOM from 'react-dom';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import FontAwesomeStyles from 'font-awesome/css/font-awesome.min.css';
import styles from './Modal.scss';
import Button, { buttonPropTypes } from '../Button';
import Icon from '../Icon';
import newId from '../utils/newId';
@ -74,10 +72,10 @@ class Modal extends React.Component {
switch (variant.status) {
case Variant.status.WARNING:
variantIconClassName = classNames(
FontAwesomeStyles.fa,
FontAwesomeStyles['fa-exclamation-triangle'],
FontAwesomeStyles['fa-3x'],
styles[`text-${variant.status.toLowerCase()}`],
'fa',
'fa-exclamation-triangle',
'fa-3x',
`text-${variant.status.toLowerCase()}`,
);
break;
default:
@ -91,14 +89,14 @@ class Modal extends React.Component {
const { variant } = this.props;
return (
<div className={styles['container-fluid']}>
<div className={styles.row}>
<div className={styles['col-md-10']}>
<div className="container-fluid">
<div className="row">
<div className="col-md-10">
<div>
{body}
</div>
</div>
<div className={styles['col-md-2']}>
<div className="col-md-2">
<Icon
id={newId(`Modal-${variant.status}`)}
className={[
@ -139,21 +137,17 @@ class Modal extends React.Component {
}
renderButtons() {
return this.props.buttons.map((button, i) => {
let buttonElement = button;
let buttonProps = button.props;
return this.props.buttons.map((button) => {
// button is either a Button component that we want clone or a set of props
const buttonProps = button.type === Button ? button.props : button;
if (button.type !== Button) {
buttonProps = button;
}
buttonElement = (<Button
{...buttonProps}
key={i}
onKeyDown={this.handleKeyDown}
/>);
return buttonElement;
return (
<Button
{...buttonProps}
key={buttonProps.label}
onKeyDown={this.handleKeyDown}
/>
);
});
}
@ -179,21 +173,21 @@ class Modal extends React.Component {
<div>
<div
className={classNames({
[styles['modal-backdrop']]: open,
[styles.show]: open,
[styles.fade]: !open,
'modal-backdrop': open,
show: open,
fade: !open,
})}
role="presentation"
/>
<div
className={classNames(
styles.modal,
'modal',
'js-close-modal-on-click',
{
[styles.show]: open,
[styles.fade]: !open,
[styles['d-block']]: open,
[styles['is-ie11']]: this.isIE11,
show: open,
fade: !open,
'd-block': open,
'is-ie11': this.isIE11,
},
)}
role="presentation"
@ -201,7 +195,7 @@ class Modal extends React.Component {
>
<div
className={classNames({
[styles['modal-dialog']]: open,
'modal-dialog': open,
})}
role="dialog"
aria-modal
@ -209,9 +203,9 @@ class Modal extends React.Component {
{...(!renderHeaderCloseButton ? { tabIndex: '-1' } : {})}
{...(!renderHeaderCloseButton ? { ref: this.setFirstFocusableElement } : {})}
>
<div className={styles['modal-content']}>
<div className={styles['modal-header']}>
<h2 className={styles['modal-title']} id={this.headerId}>{this.props.title}</h2>
<div className="modal-content">
<div className="modal-header">
<h2 className="modal-title" id={this.headerId}>{this.props.title}</h2>
{ renderHeaderCloseButton &&
<Button
label={<Icon className={['fa', 'fa-times', 'js-close-modal-on-click']} />}
@ -223,10 +217,10 @@ class Modal extends React.Component {
/>
}
</div>
<div className={styles['modal-body']}>
<div className="modal-body">
{this.renderBody()}
</div>
<div className={styles['modal-footer']}>
<div className="modal-footer">
{this.renderButtons()}
<Button
label={this.props.closeText}

View File

@ -1,10 +1,3 @@
@import "~bootstrap/scss/_pagination";
@import "~bootstrap/scss/_buttons";
@import "~bootstrap/scss/_variables";
@import "~bootstrap/scss/utilities/_screenreaders.scss";
@import "~bootstrap/scss/utilities/_spacing";
@import "~bootstrap/scss/utilities/_borders";
.page-link {
border-color: $pagination-border-color !important;
border-radius: 0 !important;

View File

@ -1,19 +1,12 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import centered from '@storybook/addon-centered';
import { checkA11y } from '@storybook/addon-a11y';
import { action } from '@storybook/addon-actions';
import { withInfo } from '@storybook/addon-info';
import { withReadme } from 'storybook-readme';
import Pagination from './index';
import README from './README.md';
storiesOf('Pagination', module)
.addDecorator((story, context) => withInfo()(story)(context))
.addDecorator(centered)
.addDecorator(checkA11y)
.addDecorator(withReadme(README))
.addParameters({ info: { text: README } })
.add('basic usage', () => (
<Pagination
paginationLabel="pagination navigation"

View File

@ -1,6 +1,5 @@
import { between } from 'airbnb-prop-types';
import classNames from 'classnames';
import FontAwesomeStyles from 'font-awesome/css/font-awesome.min.css';
import PropTypes from 'prop-types';
import React from 'react';
@ -10,8 +9,6 @@ import getTextFromElement from '../utils/getTextFromElement';
import Icon from '../Icon';
import newId from '../utils/newId';
import styles from './Pagination.scss';
class Pagination extends React.Component {
constructor(props) {
super(props);
@ -83,15 +80,15 @@ class Pagination extends React.Component {
renderEllipsisButton() {
return (
<li
className={classNames([styles['page-item'], styles.disabled])}
className={classNames(['page-item', 'disabled'])}
key={newId('pagination-ellipsis-')}
>
<span
className={classNames([
styles.btn,
styles['page-link'],
styles['ml-0'],
styles['border-0'],
'btn',
'page-link',
'ml-0',
'border-0',
])}
>
...
@ -120,15 +117,15 @@ class Pagination extends React.Component {
return (
<li
className={classNames([
styles['page-item'],
'page-item',
{
[styles.active]: active,
active,
},
])}
key={page}
>
<Button
className={[styles['page-link']]}
className={['page-link']}
aria-label={ariaLabel}
label={page.toString()}
inputRef={(element) => { this.pageRefs[page] = element; }}
@ -163,15 +160,15 @@ class Pagination extends React.Component {
return (
<li
className={classNames([styles['page-item'], styles.disabled])}
className={classNames(['page-item', 'disabled'])}
key={currentPage}
>
<span
className={classNames([
styles.btn,
styles['page-link'],
styles['mx-2'],
styles['border-0'],
'btn',
'page-link',
'mx-2',
'border-0',
])}
aria-label={ariaLabel}
>
@ -203,23 +200,23 @@ class Pagination extends React.Component {
return (
<li
className={classNames(
styles['page-item'],
'page-item',
{
[styles.disabled]: isFirstPage,
disabled: isFirstPage,
},
)}
>
<Button
className={['previous', styles['page-link']]}
className={['previous', 'page-link']}
aria-label={ariaLabel}
label={
<div>
<Icon
id={newId('pagination-')}
className={[
FontAwesomeStyles.fa,
FontAwesomeStyles['fa-chevron-left'],
styles['mr-2'],
'fa',
'fa-chevron-left',
'mr-2',
]}
/>
{buttonLabels.previous}
@ -256,14 +253,14 @@ class Pagination extends React.Component {
return (
<li
className={classNames(
styles['page-item'],
'page-item',
{
[styles.disabled]: isLastPage,
disabled: isLastPage,
},
)}
>
<Button
className={['next', styles['page-link']]}
className={['next', 'page-link']}
aria-label={ariaLabel}
label={
<div>
@ -271,9 +268,9 @@ class Pagination extends React.Component {
<Icon
id={newId('pagination-')}
className={[
FontAwesomeStyles.fa,
FontAwesomeStyles['fa-chevron-right'],
styles['ml-2'],
'fa',
'fa-chevron-right',
'ml-2',
]}
/>
</div>
@ -292,7 +289,7 @@ class Pagination extends React.Component {
const { buttonLabels, pageCount } = this.props;
return (
<div
className={styles['sr-only']}
className="sr-only"
aria-live="polite"
aria-relevant="text"
aria-atomic
@ -368,7 +365,7 @@ class Pagination extends React.Component {
className={this.props.className}
>
{this.renderScreenReaderSection()}
<ul className={styles.pagination}>
<ul className="pagination">
{this.renderPreviousButton()}
<ExtraSmall>
{this.renderPageOfCountButton()}

View File

@ -1,21 +1,12 @@
/* eslint-disable import/no-extraneous-dependencies, no-console */
import React from 'react';
import { storiesOf } from '@storybook/react';
import centered from '@storybook/addon-centered';
import { checkA11y } from '@storybook/addon-a11y';
import { action } from '@storybook/addon-actions';
import { setConsoleOptions } from '@storybook/addon-console';
import { withInfo } from '@storybook/addon-info';
import { withReadme } from 'storybook-readme';
import README from './README.md';
import RadioButtonGroup, { RadioButton } from './index';
setConsoleOptions({
panelExclude: ['warn', 'error'],
});
const onChange = (event) => {
console.log(`onChange fired for ${event.target.value}`);
@ -44,10 +35,7 @@ const onKeyDown = (event) => {
};
storiesOf('RadioButtonGroup', module)
.addDecorator((story, context) => withInfo()(story)(context))
.addDecorator(centered)
.addDecorator(checkA11y)
.addDecorator(withReadme(README))
.addParameters({ info: { text: README } })
.add('unselected minimal usage', () => (
<RadioButtonGroup
name="rbg"

View File

@ -2,7 +2,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import ElementPropTypes from 'react-element-proptypes';
class RadioButton extends React.PureComponent {
constructor(props) {
@ -169,7 +168,7 @@ RadioButtonGroup.defaultProps = {
};
RadioButtonGroup.propTypes = {
children: PropTypes.arrayOf(ElementPropTypes.elementOfType(RadioButton)).isRequired,
children: PropTypes.arrayOf(PropTypes.element).isRequired,
label: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
onBlur: PropTypes.func,

View File

@ -1,8 +1,3 @@
@import "~bootstrap/scss/_variables";
@import "~bootstrap/scss/utilities/_borders";
@import "~bootstrap/scss/utilities/_spacing";
@import "~bootstrap/scss/_buttons";
$search-field-clear-btn-width: 30px;
$search-field-border-radius: 30px;

View File

@ -1,19 +1,12 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import centered from '@storybook/addon-centered';
import { checkA11y } from '@storybook/addon-a11y';
import { withInfo } from '@storybook/addon-info';
import { withReadme } from 'storybook-readme';
import SearchField from './index';
import README from './README.md';
storiesOf('SearchField', module)
.addDecorator((story, context) => withInfo()(story)(context))
.addDecorator(centered)
.addDecorator(checkA11y)
.addDecorator(withReadme(README))
.addParameters({ info: { text: README } })
.add('basic usage', () => (
<SearchField
onSubmit={action('search-submitted')}

View File

@ -1,14 +1,12 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import FontAwesomeStyles from 'font-awesome/css/font-awesome.min.css';
import newId from '../utils/newId';
import Icon from '../Icon';
import InputText from '../InputText';
import Button from '../Button';
import styles from './SearchField.scss';
class SearchField extends React.Component {
constructor(props) {
@ -46,10 +44,10 @@ class SearchField extends React.Component {
const buttons = [
<Button
className={[classNames(
styles['search-btn'],
'search-btn',
{
[styles['border-left']]: !isFocused && this.shouldRenderClearButton(),
[styles['btn-outline-primary']]: isFocused && this.shouldRenderClearButton(),
'border-left': !isFocused && this.shouldRenderClearButton(),
'btn-outline-primary': isFocused && this.shouldRenderClearButton(),
},
)]}
label={
@ -65,15 +63,15 @@ class SearchField extends React.Component {
buttons.unshift((
<Button
className={[classNames(
styles['clear-btn'],
styles['ml-1'],
'clear-btn',
'ml-1',
)]}
label={(
<small>
<Icon
className={[classNames(
FontAwesomeStyles.fa,
FontAwesomeStyles['fa-times'],
'fa',
'fa-times',
)]}
id={newId('icon-SearchField')}
screenReaderText={screenReaderText.clearButton}
@ -139,10 +137,10 @@ class SearchField extends React.Component {
return (
<div
className={classNames(
styles.border,
styles['search-field'],
'border',
'search-field',
{
[styles.focused]: isFocused,
focused: isFocused,
},
)}
onFocus={this.handleFocus}
@ -150,9 +148,9 @@ class SearchField extends React.Component {
>
<InputText
className={[classNames(
styles.input,
'input',
{
[styles['no-clear-btn']]: !this.shouldRenderClearButton(),
'no-clear-btn': !this.shouldRenderClearButton(),
},
)]}
name="search"

View File

@ -1,4 +0,0 @@
@import "~bootstrap/scss/_alert";
@import "~bootstrap/scss/_buttons";
@import "~bootstrap/scss/_close";
@import "~bootstrap/scss/_transitions.scss";

View File

@ -1,12 +1,6 @@
/* 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 centered from '@storybook/addon-centered';
import { checkA11y } from '@storybook/addon-a11y';
import { withInfo } from '@storybook/addon-info';
import { withReadme } from 'storybook-readme';
import StatusAlert from './index';
import Button from '../Button';
@ -63,10 +57,7 @@ StatusAlertWrapper.defaultProps = {
};
storiesOf('StatusAlert', module)
.addDecorator((story, context) => withInfo()(story)(context))
.addDecorator(centered)
.addDecorator(checkA11y)
.addDecorator(withReadme(README))
.addParameters({ info: { text: README } })
.add('basic usage', () => (
<StatusAlert
dialog="You have a status alert!"

View File

@ -3,7 +3,6 @@ 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 {
@ -85,14 +84,14 @@ class StatusAlert extends React.Component {
<div
className={classNames([
...className,
styles.alert,
styles.fade,
'alert',
'fade',
], {
[styles['alert-dismissible']]: dismissible,
'alert-dismissible': dismissible,
}, {
[styles[`alert-${alertType}`]]: alertType !== undefined,
[`alert-${alertType}`]: alertType !== undefined,
}, {
[styles.show]: this.state.open,
show: this.state.open,
})}
role="alert"
hidden={!this.state.open}

View File

@ -1,9 +1,5 @@
@import "~bootstrap/scss/_tables";
@import "~bootstrap/scss/utilities/_screenreaders.scss";
@import "~bootstrap/scss/utilities/_spacing.scss";
.btn-header {
@extend .p-0;
padding: 0;
font-weight: $font-weight-bold;
}

View File

@ -1,9 +1,5 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import centered from '@storybook/addon-centered';
import { checkA11y } from '@storybook/addon-a11y';
import { withInfo } from '@storybook/addon-info';
import { withReadme } from 'storybook-readme';
import Table from './index';
import README from './README.md';
@ -78,10 +74,7 @@ const sort = function sort(firstElement, secondElement, key, direction) {
};
storiesOf('Table', module)
.addDecorator((story, context) => withInfo()(story)(context))
.addDecorator(centered)
.addDecorator(checkA11y)
.addDecorator(withReadme(README))
.addParameters({ info: { text: README } })
.add('unstyled', () => (
<Table
data={catData}

View File

@ -1,4 +1,3 @@
/* eslint-disable import/no-extraneous-dependencies */
import React from 'react';
import { shallow, mount } from 'enzyme';

View File

@ -1,10 +1,8 @@
import React from 'react';
import classNames from 'classnames';
import FontAwesomeStyles from 'font-awesome/css/font-awesome.min.css';
import isRequiredIf from 'react-proptype-conditional-require';
import PropTypes from 'prop-types';
import styles from './Table.scss';
import Button from '../Button';
class Table extends React.Component {
@ -57,7 +55,7 @@ class Table extends React.Component {
const sortIconClassName = ['fa-sort', sortDirection].filter(n => n).join('-');
return (<span
className={classNames(FontAwesomeStyles.fa, FontAwesomeStyles[sortIconClassName])}
className={classNames('fa', sortIconClassName)}
aria-hidden
/>);
}
@ -66,11 +64,11 @@ class Table extends React.Component {
let heading;
if (this.props.tableSortable && column.columnSortable) {
heading = (<Button
className={[styles['btn-header']]}
className={['btn-header']}
label={
<span>
{column.label}
<span className={classNames(styles['sr-only'])}>
<span className={classNames('sr-only')}>
{' '}
{this.getSortButtonScreenReaderText(column.key)}
</span>
@ -80,7 +78,7 @@ class Table extends React.Component {
onClick={() => this.onSortClick(column.key)}
/>);
} else if (column.hideHeader) {
heading = (<span className={classNames(styles['sr-only'])}>{column.label}</span>);
heading = (<span className={classNames('sr-only')}>{column.label}</span>);
} else {
heading = column.label;
}
@ -92,7 +90,7 @@ class Table extends React.Component {
return (
<thead
className={classNames(
...this.props.headingClassName.map(className => styles[className]),
...this.props.headingClassName,
{ 'd-inline': this.props.hasFixedColumnWidths },
)}
>
@ -118,6 +116,7 @@ class Table extends React.Component {
return (
<tbody className={classNames({ 'd-inline': this.props.hasFixedColumnWidths })}>
{this.props.data.map((row, i) => (
// eslint-disable-next-line react/no-array-index-key
<tr key={i} className={classNames({ 'd-flex': this.props.hasFixedColumnWidths })}>
{this.props.columns.map(({ key, width }) => (
React.createElement(
@ -139,8 +138,8 @@ class Table extends React.Component {
render() {
return (
<table className={classNames(
styles.table,
...this.props.className.map(className => styles[className]),
'table',
...this.props.className,
)}
>
{this.getCaption()}

View File

@ -1 +0,0 @@
@import "~bootstrap/scss/_nav";

View File

@ -1,19 +1,11 @@
/* eslint-disable import/no-extraneous-dependencies */
import React from 'react';
import { storiesOf } from '@storybook/react';
import centered from '@storybook/addon-centered';
import { checkA11y } from '@storybook/addon-a11y';
import { withInfo } from '@storybook/addon-info';
import { withReadme } from 'storybook-readme';
import Tabs from './index';
import README from './README.md';
storiesOf('Tabs', module)
.addDecorator((story, context) => withInfo()(story)(context))
.addDecorator(centered)
.addDecorator(checkA11y)
.addDecorator(withReadme(README))
.addParameters({ info: { text: README } })
.add('basic usage', () => (
<Tabs
labels={[

View File

@ -1,4 +1,3 @@
/* eslint-disable import/no-extraneous-dependencies */
import React from 'react';
import { shallow } from 'enzyme';
import Tabs from './index';

View File

@ -8,7 +8,6 @@ import classNames from 'classnames';
import PropTypes from 'prop-types';
import Button from '../Button';
import styles from './Tabs.scss';
import newId from '../utils/newId';
class Tabs extends React.Component {
@ -52,9 +51,9 @@ class Tabs extends React.Component {
key={labelId}
onClick={() => { this.toggle(i); }}
className={classNames(
styles['nav-link'],
styles['nav-item'],
{ [styles.active]: selected },
'nav-link',
'nav-item',
{ active: selected },
).split(' ')}
label={label}
/>
@ -72,8 +71,8 @@ class Tabs extends React.Component {
aria-hidden={!selected}
aria-labelledby={this.genLabelId(i)}
className={classNames(
styles['tab-pane'],
{ [styles.active]: selected },
'tab-pane',
{ active: selected },
)}
id={panelId}
key={panelId}
@ -94,13 +93,13 @@ class Tabs extends React.Component {
<div
role="tablist"
className={classNames([
styles.nav,
styles['nav-tabs'],
'nav',
'nav-tabs',
])}
>
{labels}
</div>
<div role="tabpanel" className={styles['tab-content']}>
<div role="tabpanel" className="tab-content">
{panels}
</div>
</div>

View File

@ -1,20 +1,12 @@
/* eslint-disable import/no-extraneous-dependencies */
import React from 'react';
import { storiesOf } from '@storybook/react';
import centered from '@storybook/addon-centered';
import { checkA11y } from '@storybook/addon-a11y';
import { withInfo } from '@storybook/addon-info';
import { withReadme } from 'storybook-readme';
import TextArea from './index';
import README from './README.md';
storiesOf('Textarea', module)
.addDecorator((story, context) => withInfo()(story)(context))
.addDecorator(centered)
.addDecorator(checkA11y)
.addDecorator(withReadme(README))
.addParameters({ info: { text: README } })
.add('minimal usage', () => (
<TextArea
name="name"

View File

@ -1,6 +1,3 @@
@import "~bootstrap/scss/_forms";
@import "~bootstrap/scss/utilities/_screenreaders.scss";
.form-control.is-invalid ~ .invalid-feedback-nodanger {
color: $body-color;
}

View File

@ -1,9 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import FontAwesomeStyles from 'font-awesome/css/font-awesome.min.css';
import styles from './ValidationMessage.scss';
import Variant from '../utils/constants';
const inputProps = {
@ -35,7 +33,7 @@ class ValidationMessage extends React.Component {
switch (variant.status) {
case Variant.status.INFO:
className = styles['invalid-feedback-nodanger'];
className = 'invalid-feedback-nodanger';
break;
default:
break;
@ -57,14 +55,14 @@ class ValidationMessage extends React.Component {
<React.Fragment>
<span
className={classNames(
FontAwesomeStyles.fa,
FontAwesomeStyles['fa-exclamation-circle'],
styles['fa-icon-spacing'],
'fa',
'fa-exclamation-circle',
'fa-icon-spacing',
)}
aria-hidden
/>
<span
className={classNames(styles['sr-only'])}
className={classNames('sr-only')}
>
{variantIconDescription}
</span>
@ -88,7 +86,7 @@ class ValidationMessage extends React.Component {
return (
<div
className={classNames(
styles['invalid-feedback'],
'invalid-feedback',
this.getVariantFeedbackClassName(),
className,
)}

View File

@ -1,8 +1,3 @@
@import "~bootstrap/scss/_forms";
@import "~bootstrap/scss/mixins/_forms";
@import "~bootstrap/scss/utilities/_screenreaders.scss";
@import '~bootstrap/scss/input-group';
.form-control.is-invalid.is-invalid-nodanger {
border-color: $input-border-color;
}

View File

@ -1,4 +1,3 @@
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable react/prop-types */
import React from 'react';
import { mount } from 'enzyme';

View File

@ -4,7 +4,6 @@ import PropTypes from 'prop-types';
import classNames from 'classnames';
import newId from '../utils/newId';
import styles from './asInput.scss';
import ValidationMessage from '../ValidationMessage/index';
import Variant from '../utils/constants';
@ -129,7 +128,7 @@ const asInput = (WrappedComponent, inputType = undefined, labelFirst = true) =>
if (this.props.description) {
desc.description = (
<small className={styles['form-text']} id={descriptionId} key="1">
<small className="form-text" id={descriptionId} key="1">
{this.props.description}
</small>
);
@ -157,7 +156,7 @@ const asInput = (WrappedComponent, inputType = undefined, labelFirst = true) =>
id={`label-${this.state.id}`}
htmlFor={this.state.id}
className={classNames({
[styles['form-check-label']]: this.isGroupedInput(),
'form-check-label': this.isGroupedInput(),
})}
>
{this.props.label}
@ -212,10 +211,10 @@ const asInput = (WrappedComponent, inputType = undefined, labelFirst = true) =>
{...this.state}
className={[classNames(
{
[styles['form-control']]: !this.isGroupedInput(),
[styles['form-check-input']]: this.isGroupedInput(),
[styles['is-invalid']]: !this.state.isValid,
[styles['is-invalid-nodanger']]: !this.hasDangerTheme(),
'form-control': !this.isGroupedInput(),
'form-check-input': this.isGroupedInput(),
'is-invalid': !this.state.isValid,
'is-invalid-nodanger': !this.hasDangerTheme(),
},
className,
).trim()]}
@ -229,7 +228,7 @@ const asInput = (WrappedComponent, inputType = undefined, labelFirst = true) =>
renderInputGroupAppend() {
return (
<div className={styles['input-group-append']}>
<div className="input-group-append">
{this.getAddons({ type: 'append', addonElements: this.props.inputGroupAppend })}
</div>
);
@ -237,7 +236,7 @@ const asInput = (WrappedComponent, inputType = undefined, labelFirst = true) =>
renderInputGroupPrepend() {
return (
<div className={styles['input-group-prepend']}>
<div className="input-group-prepend">
{this.getAddons({ type: 'prepend', addonElements: this.props.inputGroupPrepend })}
</div>
);
@ -247,14 +246,14 @@ const asInput = (WrappedComponent, inputType = undefined, labelFirst = true) =>
const { description, error, describedBy } = this.getDescriptions();
return (
<div className={classNames({
[styles['form-group']]: !this.isGroupedInput(),
[styles['form-inline']]: !this.isGroupedInput() && this.props.inline,
[styles['form-check']]: this.isGroupedInput(),
'form-group': !this.isGroupedInput(),
'form-inline': !this.isGroupedInput() && this.props.inline,
'form-check': this.isGroupedInput(),
})}
>
{labelFirst && this.getLabel()}
{this.props.inputGroupPrepend || this.props.inputGroupAppend ? (
<div className={classNames(styles['input-group'])}>
<div className={classNames('input-group')}>
{this.renderInputGroupPrepend()}
{this.renderInput(describedBy)}
{this.renderInputGroupAppend()}

9
src/index.scss Normal file
View File

@ -0,0 +1,9 @@
@import './asInput/asInput.scss';
@import './Collapsible/Collapsible.scss';
@import './Dropdown/Dropdown.scss';
@import './Fieldset/Fieldset.scss';
@import './Modal/Modal.scss';
@import './Pagination/Pagination.scss';
@import './SearchField/SearchField.scss';
@import './Table/Table.scss';
@import './ValidationMessage/ValidationMessage.scss';

View File

@ -1,9 +1,11 @@
// TODO: @jaebradley refactor this when Jest issue (https://github.com/facebook/jest/issues/4545#issuecomment-333004504) is patched
// eslint-disable-next-line no-unused-vars
import raf from './tempPolyfills'; // need to import this first to ignore warnings
// eslint-disable-next-line import/no-extraneous-dependencies, import/first
import Enzyme from 'enzyme';
// eslint-disable-next-line import/no-extraneous-dependencies, import/first
import Adapter from 'enzyme-adapter-react-16';
import registerRequireContextHook from 'babel-plugin-require-context-hook/register';
// Polyfill Webpack's require.context function for our test environment.
// It is currently used in .storybook/config.js. See this for more info:
// https://www.npmjs.com/package/@storybook/addon-storyshots#configure-jest-to-work-with-webpacks-requirecontext
registerRequireContextHook();
Enzyme.configure({ adapter: new Adapter() });

View File

@ -1,15 +0,0 @@
.paragon-component {
@import "~bootstrap/scss/_reboot";
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
box-sizing: border-box;
line-height: 1.15;
font-size: $font-size-base;
font-weight: $font-weight-base;
line-height: $line-height-base;
margin: 0;
color: $body-color;
background-color: $body-bg;
}

4
src/utils/bootstrap-variables.scss vendored Normal file
View File

@ -0,0 +1,4 @@
@import "~bootstrap/scss/functions";
@import "~bootstrap/scss/variables";
@import "~bootstrap/scss/mixins";

View File

@ -1,101 +0,0 @@
@import '~@edx/edx-bootstrap/sass/open-edx/theme';
@import "~bootstrap/scss/variables";
@import "~bootstrap/scss/mixins";
// Variable Overrides
$font-family-base: inherit;
$font-family-sans-serif: inherit;
$font-family-serif: inherit;
// Because Bootstrap styles are based on rems, the root HTML element's
// font size determines the size of the components. Because the consumer
// may want to render Paragon components alongside existing UI, we provide
// the option to scale all rem-based variables within Paragon based on
// their own base rem value.
// It's worth looking into doing this on-the-fly via postcss or something
// similar, so we don't have to hardcode the overrides.
// (font size on root element / default (16px))
$base-rem-size: 1 !default;
$spacer: $spacer / $base-rem-size;
$font-size-base: $font-size-base / $base-rem-size;
$font-size-lg: $font-size-lg / $base-rem-size;
$font-size-sm: $font-size-sm / $base-rem-size;
$h1-font-size: $h1-font-size / $base-rem-size;
$h2-font-size: $h2-font-size / $base-rem-size;
$h3-font-size: $h3-font-size / $base-rem-size;
$h4-font-size: $h4-font-size / $base-rem-size;
$h5-font-size: $h5-font-size / $base-rem-size;
$h6-font-size: $h6-font-size / $base-rem-size;
$display1-size: $display1-size / $base-rem-size;
$display2-size: $display2-size / $base-rem-size;
$display3-size: $display3-size / $base-rem-size;
$display4-size: $display4-size / $base-rem-size;
$lead-font-size: $lead-font-size / $base-rem-size;
$kbd-box-shadow: $kbd-box-shadow / $base-rem-size;
$border-radius: $border-radius / $base-rem-size;
$border-radius-lg: $border-radius-lg / $base-rem-size;
$border-radius-sm: $border-radius-sm / $base-rem-size;
$table-cell-padding: $table-cell-padding / $base-rem-size;
$table-cell-padding-sm: $table-cell-padding-sm / $base-rem-size;
$btn-block-spacing-y: $btn-block-spacing-y / $base-rem-size;
$input-btn-padding-x: $input-btn-padding-x / $base-rem-size;
$input-btn-padding-y: $input-btn-padding-y / $base-rem-size;
$input-btn-padding-x-sm: $input-btn-padding-x-sm / $base-rem-size;
$input-btn-padding-y-sm: $input-btn-padding-y-sm / $base-rem-size;
$input-btn-padding-x-lg: $input-btn-padding-x-lg / $base-rem-size;
$input-btn-padding-y-lg: $input-btn-padding-y-lg / $base-rem-size;
$form-text-margin-top: $form-text-margin-top / $base-rem-size;
$form-check-input-gutter: $form-check-input-gutter / $base-rem-size;
$form-check-input-margin-y: $form-check-input-margin-y / $base-rem-size;
$form-check-input-margin-x: $form-check-input-margin-x / $base-rem-size;
$form-check-inline-margin-x: $form-check-inline-margin-x / $base-rem-size;
$custom-control-gutter: $custom-control-gutter / $base-rem-size;
$custom-control-spacer-x: $custom-control-spacer-x / $base-rem-size;
$custom-control-indicator-size: $custom-control-indicator-size / $base-rem-size;
$custom-control-indicator-box-shadow: $custom-control-indicator-box-shadow / $base-rem-size;
$custom-select-padding-x: $custom-select-padding-x / $base-rem-size;
$custom-select-padding-y: $custom-select-padding-y / $base-rem-size;
$custom-select-indicator-padding: $custom-select-indicator-padding / $base-rem-size;
$custom-file-height: $custom-file-height / $base-rem-size;
$custom-file-focus-box-shadow: $custom-file-focus-box-shadow / $base-rem-size;
$custom-file-padding-x: $custom-file-padding-x / $base-rem-size;
$custom-file-padding-y: $custom-file-padding-y / $base-rem-size;
$custom-file-box-shadow: $custom-file-box-shadow / $base-rem-size;
$dropdown-min-width: $dropdown-min-width / $base-rem-size;
$dropdown-padding-y: $dropdown-padding-y / $base-rem-size;
$dropdown-spacer: $dropdown-spacer / $base-rem-size;
$dropdown-box-shadow: $dropdown-box-shadow / $base-rem-size;
$dropdown-item-padding-x: $dropdown-item-padding-x / $base-rem-size;
$dropdown-item-padding-y: $dropdown-item-padding-y / $base-rem-size;
$navbar-brand-padding-y: $navbar-brand-padding-y / $base-rem-size;
$navbar-toggler-padding-x: $navbar-toggler-padding-x / $base-rem-size;
$navbar-toggler-padding-y: $navbar-toggler-padding-y / $base-rem-size;
$pagination-padding-x: $pagination-padding-x / $base-rem-size;
$pagination-padding-y: $pagination-padding-y / $base-rem-size;
$pagination-padding-x-sm: $pagination-padding-x-sm / $base-rem-size;
$pagination-padding-y-sm: $pagination-padding-y-sm / $base-rem-size;
$pagination-padding-x-lg: $pagination-padding-x-lg / $base-rem-size;
$pagination-padding-y-lg: $pagination-padding-y-lg / $base-rem-size;
$jumbotron-padding: $jumbotron-padding / $base-rem-size;
$card-spacer-x: $card-spacer-x / $base-rem-size;
$card-spacer-y: $card-spacer-y / $base-rem-size;
$card-img-overlay-padding: $card-img-overlay-padding / $base-rem-size;
$card-columns-gap: $card-columns-gap / $base-rem-size;
$badge-pill-border-radius: $badge-pill-border-radius / $base-rem-size;
$alert-padding-x: $alert-padding-x / $base-rem-size;
$alert-padding-y: $alert-padding-y / $base-rem-size;
$progress-height: $progress-height / $base-rem-size;
$progress-font-size: $progress-font-size / $base-rem-size;
$progress-box-shadow: $progress-box-shadow / $base-rem-size;
$list-group-item-padding-x: $list-group-item-padding-x / $base-rem-size;
$list-group-item-padding-y: $list-group-item-padding-y / $base-rem-size;
$thumbnail-padding: $thumbnail-padding / $base-rem-size;
$breadcrumb-padding-y: $breadcrumb-padding-y / $base-rem-size;
$breadcrumb-padding-x: $breadcrumb-padding-x / $base-rem-size;
$breadcrumb-item-padding: $breadcrumb-item-padding / $base-rem-size;
$kbd-padding-x: $kbd-padding-x / $base-rem-size;
$kbd-padding-y: $kbd-padding-y / $base-rem-size;

View File

@ -1,112 +1,81 @@
const path = require('path');
const webpack = require('webpack');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
// we build the library for two different build targets:
// static (with scoped styles) and themeable (with stock,
// overrideable classnames)
const targetProperties = [{
baseDirectory: 'static',
localIdentName: 'paragon__[local]',
},
{
baseDirectory: 'themeable',
localIdentName: '[local]',
}];
module.exports = targetProperties.map(config => ({
devtool: 'source-map',
module.exports = {
entry: {
paragon: path.resolve('./src/index.js'),
paragon: './src/index.js',
style: './src/index.scss',
},
output: {
filename: `${config.baseDirectory}/index.js`,
filename: '[name].js',
library: 'paragon',
libraryTarget: 'umd',
},
resolve: {
extensions: ['.js', '.jsx'],
},
externals: {
react: {
commonjs: 'react',
commonjs2: 'react',
amd: 'React',
root: 'React',
},
'react-dom': {
commonjs: 'react-dom',
commonjs2: 'react-dom',
amd: 'ReactDOM',
root: 'ReactDOM',
},
},
plugins: [
new UglifyJsPlugin({
sourceMap: true,
}),
new ExtractTextPlugin(`${config.baseDirectory}/paragon.min.css`),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
new UglifyJsPlugin(),
new MiniCssExtractPlugin({
filename: 'paragon.min.css',
}),
// Be careful here. Our output path is the root of this project
// so without this config, CleanWebpackPlugin will destroy the project
// We should change the output path to dist/ in the next major version.
new CleanWebpackPlugin(),
],
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /(node_modules)/,
use: [
{
loader: 'babel-loader',
},
{ loader: 'source-map-loader' },
],
exclude: /node_modules/,
use: {
loader: 'babel-loader',
},
},
{
test: /\.scss|\.css$/,
use: ExtractTextPlugin.extract({
use: [
{
loader: 'css-loader',
options: {
modules: true,
localIdentName: config.localIdentName,
sourceMap: true,
minimize: true,
},
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
},
{
loader: 'sass-loader',
options: {
data: '@import "bootstrap-variables";',
includePaths: [
path.join(__dirname, './src/utils'),
path.join(__dirname, './node_modules'),
],
},
{
loader: 'sass-loader',
options: {
data: '@import "paragon-reset";',
includePaths: [
path.join(__dirname, './src/utils'),
path.join(__dirname, './node_modules'),
],
sourceMap: true,
},
},
],
}),
},
],
},
{
test: /\.(woff2?|ttf|svg|eot)(\?v=\d+\.\d+\.\d+)?$/,
loader: 'file-loader',
options: {
outputPath: `${config.baseDirectory}/`,
outputPath: 'assets',
},
},
],
},
externals: [{
react: {
root: 'React',
commonjs2: 'react',
commonjs: 'react',
amd: 'react',
},
},
{
'react-dom': {
root: 'ReactDOM',
commonjs2: 'react-dom',
commonjs: 'react-dom',
amd: 'react-dom',
},
},
{
'react-transition-group': {
root: 'ReactTransitionGroup',
commonjs2: 'react-transition-group',
commonjs: 'react-transition-group',
amd: 'react-transition-group',
},
}],
}));
};