-
Notifications
You must be signed in to change notification settings - Fork 433
Add Select component #1791
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Select component #1791
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| // This object is imported into the documentation site. An example for the documentation site should be part of the pull request for the component. The object key is the kabob case of the "URL folder". In the case of `http://localhost:8080/components/app-launcher/`, `app-launcher` is the `key`. The folder name is created by `components.component` value in `package.json`. The following uses webpack's raw-loader plugin to get "text files" that will be eval()'d by CodeMirror within the documentation site on page load. | ||
|
|
||
| /* eslint-env node */ | ||
| /* eslint-disable global-require */ | ||
|
|
||
| const siteStories = [ | ||
| require('raw-loader!@salesforce/design-system-react/components/select/__examples__/base.jsx'), | ||
| require('raw-loader!@salesforce/design-system-react/components/select/__examples__/required.jsx'), | ||
| require('raw-loader!@salesforce/design-system-react/components/select/__examples__/error.jsx'), | ||
| require('raw-loader!@salesforce/design-system-react/components/select/__examples__/disabled.jsx'), | ||
| require('raw-loader!@salesforce/design-system-react/components/select/__examples__/multipleSelectionNarrow.jsx'), | ||
| ]; | ||
|
|
||
| module.exports = siteStories; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import React from 'react'; | ||
| import { storiesOf } from '@storybook/react'; | ||
| import { SELECT } from '../../../utilities/constants'; | ||
| import IconSettings from '../../icon-settings'; | ||
|
|
||
| import Base from '../__examples__/base'; | ||
| import Required from '../__examples__/required'; | ||
| import Error from '../__examples__/error'; | ||
| import Disabled from '../__examples__/disabled'; | ||
| import MultipleSelectionNarrow from '../__examples__/multipleSelectionNarrow'; | ||
|
|
||
| storiesOf(SELECT, module) | ||
| .addDecorator((getStory) => ( | ||
| <div className="slds-p-around_medium"> | ||
| <IconSettings iconPath="/assets/icons">{getStory()}</IconSettings> | ||
| </div> | ||
| )) | ||
| .add('Base', () => <Base />) | ||
| .add('Required', () => <Required />) | ||
| .add('Error', () => <Error />) | ||
| .add('Disabled', () => <Disabled />) | ||
| .add('MultipleSelectionNarrow', () => <MultipleSelectionNarrow />); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| import React from 'react'; | ||
| import IconSettings from '~/components/icon-settings'; | ||
| import Select from '~/components/select'; | ||
| import SelectContainer from '../private/container'; | ||
| import Label from '../private/label'; | ||
|
|
||
| class Example extends React.Component { | ||
| render() { | ||
| return ( | ||
| <IconSettings iconPath="/assets/icons"> | ||
| <Select> | ||
| <Label id="select-01" label="Select Label" /> | ||
| <SelectContainer | ||
| id="select-01" | ||
| options={[ | ||
| 'Please select', | ||
| 'Option One', | ||
| 'Option Two', | ||
| 'Option Three', | ||
| ]} | ||
| /> | ||
| </Select> | ||
| </IconSettings> | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| Example.displayName = 'BaseExample'; // export is replaced with `ReactDOM.render(<Example />, mountNode);` at runtime | ||
| export default Example; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| import React from 'react'; | ||
| import IconSettings from '~/components/icon-settings'; | ||
| import Select from '~/components/select'; | ||
| import SelectContainer from '../private/container'; | ||
| import Label from '../private/label'; | ||
|
|
||
| class Example extends React.Component { | ||
| render() { | ||
| return ( | ||
| <IconSettings iconPath="/assets/icons"> | ||
| <Select> | ||
| <Label id="select-01" label="Select Label" /> | ||
| <SelectContainer | ||
| id="select-01" | ||
| options={[ | ||
| 'Please select', | ||
| 'Option One', | ||
| 'Option Two', | ||
| 'Option Three', | ||
| ]} | ||
| disabled | ||
| /> | ||
| </Select> | ||
| </IconSettings> | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| Example.displayName = 'DisabledExample'; // export is replaced with `ReactDOM.render(<Example />, mountNode);` at runtime | ||
| export default Example; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| import React from 'react'; | ||
| import IconSettings from '~/components/icon-settings'; | ||
| import Select from '~/components/select'; | ||
| import SelectContainer from '../private/container'; | ||
| import Label from '../private/label'; | ||
| import Error from '../private/error'; | ||
|
|
||
| class Example extends React.Component { | ||
| render() { | ||
| return ( | ||
| <IconSettings iconPath="/assets/icons"> | ||
| <Select hasError> | ||
| <Label id="select-01" label="Select Label" required /> | ||
| <SelectContainer | ||
| id="select-01" | ||
| errorText="This field is required" | ||
| aria-describedby="error-01" | ||
| options={[ | ||
| 'Please select', | ||
| 'Option One', | ||
| 'Option Two', | ||
| 'Option Three', | ||
| ]} | ||
| /> | ||
| <Error id="error-01" errorText="This field is required" /> | ||
| </Select> | ||
| </IconSettings> | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| Example.displayName = 'ErrorExample'; // export is replaced with `ReactDOM.render(<Example />, mountNode);` at runtime | ||
| export default Example; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| import React from 'react'; | ||
| import IconSettings from '~/components/icon-settings'; | ||
| import Select from '~/components/select'; | ||
| import SelectContainer from '../private/container'; | ||
| import Label from '../private/label'; | ||
|
|
||
| class Example extends React.Component { | ||
| render() { | ||
| return ( | ||
| <IconSettings iconPath="/assets/icons"> | ||
| <Select> | ||
| <Label id="select-01" label="Select Label" required /> | ||
| <SelectContainer | ||
| id="select-01" | ||
| options={[ | ||
| 'Please select', | ||
| 'Option One', | ||
| 'Option Two', | ||
| 'Option Three', | ||
| ]} | ||
| isMultipleSelection | ||
| /> | ||
| </Select> | ||
| </IconSettings> | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| Example.displayName = 'MultipleSelectionNarrowExample'; // export is replaced with `ReactDOM.render(<Example />, mountNode);` at runtime | ||
| export default Example; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| import React from 'react'; | ||
| import IconSettings from '~/components/icon-settings'; | ||
| import Select from '~/components/select'; | ||
| import SelectContainer from '../private/container'; | ||
| import Label from '../private/label'; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Label should be a prop of Select component. |
||
|
|
||
| class Example extends React.Component { | ||
| render() { | ||
| return ( | ||
| <IconSettings iconPath="/assets/icons"> | ||
| <Select> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The outputted markup is good, but this should all be one component called Select, since I don't foresee any rearrangement of these such as the label after the |
||
| <Label id="select-01" label="Select Label" required /> | ||
| <SelectContainer | ||
| id="select-01" | ||
| required | ||
| options={[ | ||
| 'Please select', | ||
| 'Option One', | ||
| 'Option Two', | ||
| 'Option Three', | ||
| ]} | ||
| /> | ||
| </Select> | ||
| </IconSettings> | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| Example.displayName = 'RequiredExample'; // export is replaced with `ReactDOM.render(<Example />, mountNode);` at runtime | ||
| export default Example; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| /* Copyright (c) 2015-present, salesforce.com, inc. All rights reserved */ | ||
| /* Licensed under BSD 3-Clause - see LICENSE.txt or git.io/sfdc-license */ | ||
|
|
||
| // # Select Component | ||
|
|
||
| // Implements the [Select design pattern](https://www.lightningdesignsystem.com/components/select/) in React. | ||
|
|
||
| import React from 'react'; | ||
| import PropTypes from 'prop-types'; | ||
| import classNames from '../../utilities/class-names'; | ||
|
|
||
| const propTypes = { | ||
| /** | ||
| * Shows red borders in the input element is there is an error | ||
| */ | ||
| hasError: PropTypes.bool, | ||
| /** | ||
| * Shows the content of the Select component. | ||
| */ | ||
| children: PropTypes.node.isRequired, | ||
| }; | ||
|
|
||
| class Select extends React.Component { | ||
| render() { | ||
| return ( | ||
| <div | ||
| className={classNames('slds-form-element', { | ||
| 'slds-has-error': this.props.hasError, | ||
| })} | ||
| > | ||
| {this.props.children} | ||
| </div> | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| Select.propTypes = propTypes; | ||
|
|
||
| export default Select; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| /* Copyright (c) 2015-present, salesforce.com, inc. All rights reserved */ | ||
| /* Licensed under BSD 3-Clause - see LICENSE.txt or git.io/sfdc-license */ | ||
|
|
||
| import React from 'react'; | ||
| import PropTypes from 'prop-types'; | ||
| import shortid from 'shortid'; | ||
| import classNames from '../../../utilities/class-names'; | ||
|
|
||
| const propTypes = { | ||
| /** | ||
| * The `aria-describedby` attribute is used to indicate the IDs of the elements that describe the object. It is used to establish a relationship between widgets or groups and text that described them. This is very similar to aria-labelledby: a label describes the essence of an object, while a description provides more information that the user might need. | ||
| */ | ||
| 'aria-describedby': PropTypes.string, | ||
| /** | ||
| * Every select input must have a unique ID in order to support keyboard navigation and ARIA support. | ||
| */ | ||
| id: PropTypes.string, | ||
| /** | ||
| * Highlights the select input as a required field (does not perform any validation). | ||
| */ | ||
| required: PropTypes.bool, | ||
| /** | ||
| * Disables the select input and prevents editing the contents. | ||
| */ | ||
| disabled: PropTypes.bool, | ||
| /** | ||
| * Displays options in a multiple selection narrow format. | ||
| */ | ||
| isMultipleSelection: PropTypes.boolean, | ||
| /** | ||
| * Select options. | ||
| */ | ||
| options: PropTypes.arrayOf(PropTypes.string), | ||
| }; | ||
|
|
||
| class SelectContainer extends React.Component { | ||
| componentWillMount() { | ||
| this.generatedId = shortid.generate(); | ||
| if (this.props.errorText) { | ||
| this.generatedErrorId = shortid.generate(); | ||
| } | ||
| } | ||
|
|
||
| getId = () => this.props.id || this.generatedId; | ||
|
|
||
| getOptions = () => | ||
| this.props.options.map((optionValue) => <option>{optionValue}</option>); | ||
|
|
||
| render() { | ||
| return ( | ||
| <div className="slds-form-element__control"> | ||
| <div | ||
| className={classNames({ | ||
| 'slds-select_container': !this.props.isMultipleSelection, | ||
| })} | ||
| > | ||
| <select | ||
| className="slds-select" | ||
| id={this.getId()} | ||
| required={this.props.required || this.props.errorText} | ||
| multiple={this.props.isMultipleSelection} | ||
| disabled={this.props.disabled} | ||
| aria-describedBy={this.props['aria-describedby']} | ||
| > | ||
| {this.getOptions()} | ||
| </select> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| SelectContainer.propTypes = propTypes; | ||
|
|
||
| export default SelectContainer; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| /* Copyright (c) 2015-present, salesforce.com, inc. All rights reserved */ | ||
| /* Licensed under BSD 3-Clause - see LICENSE.txt or git.io/sfdc-license */ | ||
|
|
||
| import React from 'react'; | ||
| import PropTypes from 'prop-types'; | ||
|
|
||
| const propTypes = { | ||
| /** | ||
| * Message to display when the input is in an error state. When this is present, also visually highlights the component as in error. | ||
| */ | ||
| errorText: PropTypes.oneOfType([PropTypes.node, PropTypes.string]) /** | ||
| * Every label must have a unique ID in order to support keyboard navigation and ARIA support. | ||
| */, | ||
| id: PropTypes.string.isRequired, | ||
| }; | ||
|
|
||
| /** | ||
| * The error text for the Select component | ||
| */ | ||
| const Error = ({ errorText, id }) => ( | ||
| <div className="slds-form-element__help" id={id}> | ||
| {errorText} | ||
| </div> | ||
| ); | ||
|
|
||
| Error.propTypes = propTypes; | ||
|
|
||
| export default Error; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| /* Copyright (c) 2015-present, salesforce.com, inc. All rights reserved */ | ||
| /* Licensed under BSD 3-Clause - see LICENSE.txt or git.io/sfdc-license */ | ||
|
|
||
| import React from 'react'; | ||
| import PropTypes from 'prop-types'; | ||
|
|
||
| const propTypes = { | ||
| /** | ||
| * The label for the select component. | ||
| */ | ||
| label: PropTypes.string.isRequired /** | ||
| * Every label must have a unique ID in order to support keyboard navigation and ARIA support. | ||
| */, | ||
| id: PropTypes.string.isRequired /** | ||
| * Highlights the input as a required field (does not perform any validation). | ||
| */, | ||
| required: PropTypes.bool, | ||
| }; | ||
|
|
||
| const defaultProps = { | ||
| required: 'false', | ||
| }; | ||
|
|
||
| /** | ||
| * The label for the Select component | ||
| */ | ||
| const Label = ({ label, id, required }) => ( | ||
| <label className="slds-form-element__label" htmlFor={id}> | ||
| {required === true ? ( | ||
| <abbr className="slds-required" title="required"> | ||
| * | ||
| </abbr> | ||
| ) : ( | ||
| '' | ||
| )} | ||
| {label} | ||
| </label> | ||
| ); | ||
|
|
||
| Label.propTypes = propTypes; | ||
| Label.defaultProps = defaultProps; | ||
|
|
||
| export default Label; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No components in private folder should be used by consumers or in examples