From 1765885c6ed58d9464d01ab0b2b8da6e86accca8 Mon Sep 17 00:00:00 2001 From: Gary Chisholm Date: Tue, 5 Jan 2016 10:00:46 +1100 Subject: [PATCH] New: Add react-select component - Custom style overrides - single and multi examples and tests - Downgraded sass naming convention errors to warnings until we can do inline/file based rules/exclusions - Added spacing between example components and removed unnecessary `
`s - `allowCreate` dependant on https://github.com/JedWatson/react-select/pull/660 - Removed unnecessary box shadow length properties --- .sass-lint.yml | 4 +- package.json | 5 +- src/components/Main.js | 134 +++++++++++++++------- src/components/distributionEntry.js | 4 + src/styles/App.scss | 4 + src/styles/_react-select-custom.scss | 96 ++++++++++++++++ src/styles/bootstrapOverrides/Button.scss | 4 +- test/components/MainTest.js | 70 +++++++++-- 8 files changed, 266 insertions(+), 55 deletions(-) create mode 100644 src/styles/_react-select-custom.scss diff --git a/.sass-lint.yml b/.sass-lint.yml index 174275eba..964332061 100644 --- a/.sass-lint.yml +++ b/.sass-lint.yml @@ -42,7 +42,9 @@ rules: force-pseudo-nesting: 2 # Name Formats - class-name-format: 2 + # TODO: When inline linting is supported set as error (https://github.com/sasstools/sass-lint/pull/402) and ignore + # in Select.scss + class-name-format: 1 function-name-format: 2 mixin-name-format: 2 placeholder-name-format: 2 diff --git a/package.json b/package.json index 67d9385ea..716d09018 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ ], "author": "Adslot", "devDependencies": { - "alexandria-adslot": "^0.7.0", + "alexandria-adslot": "^0.7.1", "autoprefixer-loader": "^3.1.0", "babel-core": "^6.4.0", "babel-eslint": "^5.0.0-beta6", @@ -90,6 +90,7 @@ "dependencies": { "lodash": "^3.10.1", "react": "^0.14.6", - "react-dom": "^0.14.6" + "react-dom": "^0.14.6", + "react-select": "^1.0.0-beta8" } } diff --git a/src/components/Main.js b/src/components/Main.js index 56e657303..1c0c18686 100644 --- a/src/components/Main.js +++ b/src/components/Main.js @@ -8,21 +8,50 @@ import { Tab, Radio, RadioGroup, + Select, Toggle, } from './distributionEntry'; require('styles/App.scss'); +const selectCountriesOptions = [ + { value: 'au', label: 'Australia' }, + { value: 'ca', label: 'Canada' }, + { value: 'uk', label: 'United Kingdom' }, + { value: 'jp', label: 'Japan', disabled: true }, +]; + +const selectFlavoursOptions = [ + { label: 'Chocolate', value: 'chocolate' }, + { label: 'Vanilla', value: 'vanilla' }, + { label: 'Strawberry', value: 'strawberry' }, + { label: 'Caramel', value: 'caramel' }, + { label: 'Cookies and Cream', value: 'cookiescream' }, + { label: 'Peppermint', value: 'peppermint' }, +]; + class AppComponent extends React.Component { constructor(props) { super(props); + this.setSelectedCountry = this.setSelectedCountry.bind(this); + this.setSelectedFlavours = this.setSelectedFlavours.bind(this); this.toggleSimpleModal = this.toggleSimpleModal.bind(this); this.state = { + selectedCountry: 'au', + selectedFlavours: 'vanilla', showSimpleModal: false, }; } + setSelectedCountry(newValue) { + this.setState({ selectedCountry: newValue.value }); + } + + setSelectedFlavours(newValue) { + this.setState({ selectedFlavours: newValue }); + } + toggleSimpleModal() { this.setState({ showSimpleModal: !this.state.showSimpleModal }); } @@ -107,52 +136,73 @@ class AppComponent extends React.Component { +

Checkboxes

-
- -
-
- -
-
- -
-
- -
+ +
+ +
+ +
+ + +

Radio Buttons

-
- - - - -
-
- - - - -
+ + + + +
+ + + + +

Toggle

-
- -
+ Left Right + + +

Select

+
); } diff --git a/src/components/distributionEntry.js b/src/components/distributionEntry.js index 139609bee..ee9e027f6 100644 --- a/src/components/distributionEntry.js +++ b/src/components/distributionEntry.js @@ -2,6 +2,7 @@ require('styles/_bootstrap-custom.scss'); require('styles/_icheck-custom.scss'); +require('styles/_react-select-custom.scss'); require('styles/_react-toggle-custom.scss'); import Button from 'react-bootstrap/lib/Button'; @@ -24,6 +25,8 @@ import { Totals, } from 'alexandria-adslot'; +import Select from 'react-select'; + module.exports = { Alert, Breadcrumb, @@ -37,6 +40,7 @@ module.exports = { Radio, RadioGroup, Search, + Select, Slicey, Tab, Tabs, diff --git a/src/styles/App.scss b/src/styles/App.scss index 1dccc6b15..781d4ef98 100644 --- a/src/styles/App.scss +++ b/src/styles/App.scss @@ -23,4 +23,8 @@ body { > .btn-panel + .btn-panel { margin-top: $spacing-etalon; } + + > br { + line-height: $spacing-etalon; + } } diff --git a/src/styles/_react-select-custom.scss b/src/styles/_react-select-custom.scss new file mode 100644 index 000000000..ce4f4fe71 --- /dev/null +++ b/src/styles/_react-select-custom.scss @@ -0,0 +1,96 @@ +@import 'variable'; + +// control options +$select-input-border-color: $color-border-lighter; +$select-input-border-radius: $border-radius-base; +$select-input-border-focus: $select-input-border-color; +$select-input-height: 26px; +$select-padding-vertical: 4px; +$select-padding-horizontal: 7px; +$select-text-color: $color-text; + +// menu options +$select-option-color: $color-text-light; +$select-option-focused-color: $select-option-color; +$select-option-focused-bg: $color-gray-white; + +// clear "x" button +$select-clear-color: $color-text-light; +$select-clear-hover-color: $color-negative; +$select-clear-size: 15px; + +// arrow indicator +$select-arrow-color: $color-text-light; +$select-arrow-color-hover: $color-text; + +// multi-select item +$select-item-gutter: 2px; +$select-item-color: $color-text-inverse; +$select-item-hover-color: $color-text-light; +$select-item-bg: $color-gray-darker; +$select-item-hover-bg: $select-item-bg; +$select-item-font-size: $font-size-base; +$select-item-padding-vertical: 2px; +$select-item-padding-horizontal: 5px; + + +@import '../../node_modules/react-select/scss/default'; + + +.Select { + $select-input-border-active: $color-border; + font-weight: $font-weight-medium; + + &-menu-outer { + border-color: $select-input-border-active; + } + + &:not(.is-disabled) { + .Select-control { + box-shadow: 0 1px $color-border-light; + + &:hover, + &:focus { + border-color: $select-input-border-active; + } + } + + &.is-open { + > .Select-control { + border-color: $select-input-border-active; + } + } + + &.is-focused { + &:not(.is-open) { + > .Select-control { + border-color: $select-input-border-active; + } + } + } + } + + &--multi { + + .Select-value { + border: 0; + margin-top: 1px; + + &-icon + &-label { + padding-right: 0; + } + + &-icon { + border: 0; + float: right; + font-size: $select-clear-size; + font-weight: $font-weight-light; + line-height: $select-clear-size; + margin: $select-item-padding-vertical $select-item-padding-horizontal $select-item-padding-vertical 0; + padding: 0; + text-align: center; + width: $select-item-font-size; + } + } + } +} diff --git a/src/styles/bootstrapOverrides/Button.scss b/src/styles/bootstrapOverrides/Button.scss index cd7ab52b5..54fb6e091 100644 --- a/src/styles/bootstrapOverrides/Button.scss +++ b/src/styles/bootstrapOverrides/Button.scss @@ -67,7 +67,7 @@ } .btn { - box-shadow: 0 1px 0 $color-border-light; + box-shadow: 0 1px $color-border-light; margin-right: 5px; // Make border the same colour as the fill. @@ -86,7 +86,7 @@ &:hover, &:focus { - box-shadow: 0 2px 0 $color-border-light; + box-shadow: 0 2px $color-border-light; transform: translateY(-1px); } diff --git a/test/components/MainTest.js b/test/components/MainTest.js index 378301327..ed5d09d81 100644 --- a/test/components/MainTest.js +++ b/test/components/MainTest.js @@ -3,11 +3,14 @@ import createComponent from 'helpers/shallowRenderHelper'; import Main from 'components/Main'; -import { isElementOfType } from 'react-addons-test-utils'; +import React from 'react'; +import { isElementOfType, createRenderer } from 'react-addons-test-utils'; import { Checkbox, + Modal, Radio, RadioGroup, + Select, Toggle, } from '../../src/components/distributionEntry'; @@ -24,22 +27,20 @@ describe('MainComponent', () => { it('should have a modal component', () => { const modalComponent = MainComponent.props.children[8]; - expect(modalComponent.props.bsClass).to.equal('modal'); + expect(isElementOfType(modalComponent, Modal)).to.equal(true); expect(modalComponent.props.bsSize).to.equal('small'); expect(modalComponent.props.keyboard).to.equal(false); expect(modalComponent.props.backdrop).to.equal(true); }); it('should have a checkbox component', () => { - const checkboxExampleContainer = MainComponent.props.children[10]; - const checkboxComponent = checkboxExampleContainer.props.children; + const checkboxComponent = MainComponent.props.children[10]; expect(isElementOfType(checkboxComponent, Checkbox)).to.equal(true); expect(checkboxComponent.props.label).to.equal('Unchecked'); }); it('should have a radio button component', () => { - const radioButtonContainer = MainComponent.props.children[15]; - const radioGroupComponent = radioButtonContainer.props.children; + const radioGroupComponent = MainComponent.props.children[18]; expect(isElementOfType(radioGroupComponent, RadioGroup)).to.equal(true); const radioComponent = radioGroupComponent.props.children[0]; expect(isElementOfType(radioComponent, Radio)).to.equal(true); @@ -47,8 +48,61 @@ describe('MainComponent', () => { }); it('should have a toggle component', () => { - const toggleExampleContainer = MainComponent.props.children[18]; - const toggleComponent = toggleExampleContainer.props.children; + const toggleComponent = MainComponent.props.children[24]; expect(isElementOfType(toggleComponent, Toggle)).to.equal(true); }); + + it('should set and change values for single select', () => { + const getRenderOutputAndCheck = ({ renderer, expectedValue }) => { + const componentRenderOutput = renderer.getRenderOutput(); + + const selectComponent = componentRenderOutput.props.children[28]; + expect(isElementOfType(selectComponent, Select)).to.equal(true); + expect(selectComponent.props.value).to.equal(expectedValue); + + return { selectComponent }; + }; + + const renderer = createRenderer(); + renderer.render(
); + + const { selectComponent } = getRenderOutputAndCheck({ + renderer, + expectedValue: 'au', + }); + + selectComponent.props.onChange({ value: 'uk' }); + + getRenderOutputAndCheck({ + renderer, + expectedValue: 'uk', + }); + }); + + it('should set and change values for multi select', () => { + const getRenderOutputAndCheck = ({ renderer, expectedValue }) => { + const componentRenderOutput = renderer.getRenderOutput(); + + const selectComponent = componentRenderOutput.props.children[30]; + expect(isElementOfType(selectComponent, Select)).to.equal(true); + expect(selectComponent.props.value).to.eql(expectedValue); + + return { selectComponent }; + }; + + const renderer = createRenderer(); + renderer.render(
); + + const { selectComponent } = getRenderOutputAndCheck({ + renderer, + expectedValue: 'vanilla', + }); + + selectComponent.props.onChange('vanilla,chocolate'); + + getRenderOutputAndCheck({ + renderer, + expectedValue: 'vanilla,chocolate', + }); + }); });