UNPKG

@vtex/styleguide

Version:

> VTEX Styleguide React components ([Docs](https://vtex.github.io/styleguide))

707 lines (660 loc) 19.6 kB
#### The FilterBar is a slim, optimized way of displaying filter atoms. Although tailored to be used with the Table component, it can also be used on its own with any other way you chose to display your data. <div className="center mw7 pv6"> ![](./filters_table.png) </div> Different from regular filter panels, ours optimizes for both the screen real-estate and discoverability of new filters. You can have dozens of different, complex available filters, and still present to the user a slim and simple-to-use interface. ### 👍 Dos - Apply user research to choose which filters to show in the highlighted section. - Use the FilterBar always on top of the content that will be filtered. - Try offering as many filters and operators as possible. With the diversity of operations VTEX supports, we can never predict all the diverse use cases our merchants need. ### 👎 Don'ts - Don't present too many filters in the highlighted zone. ### Related components - For applications with a small viewport or when working with modals prefer using the <a href="#/Components/Display/FilterOptions">FilterOptions</a> component. Simple product filter example ```js const Input = require('../Input').default function SimpleInputObject({ value, onChange }) { return <Input value={value || ''} onChange={e => onChange(e.target.value)} /> } class MySimpleFilter extends React.Component { constructor() { super() this.state = { statements: [] } this.getSimpleVerbs = this.getSimpleVerbs.bind(this) this.renderSimpleFilterLabel = this.renderSimpleFilterLabel.bind(this) } getSimpleVerbs() { return [ { label: 'is', value: '=', object: props => <SimpleInputObject {...props} />, }, { label: 'is not', value: '!=', object: props => <SimpleInputObject {...props} />, }, { label: 'contains', value: 'contains', object: props => <SimpleInputObject {...props} />, }, ] } renderSimpleFilterLabel(statement) { if (!statement || !statement.object) { // you should treat empty object cases only for alwaysVisibleFilters return 'Any' } return `${ statement.verb === '=' ? 'is' : statement.verb === '!=' ? 'is not' : 'contains' } ${statement.object}` } render() { return ( <FilterBar alwaysVisibleFilters={['id', 'category', 'brand']} statements={this.state.statements} onChangeStatements={statements => this.setState({ statements })} clearAllFiltersButtonLabel="Clear Filters" options={{ id: { label: 'ID', renderFilterLabel: this.renderSimpleFilterLabel, verbs: this.getSimpleVerbs(), }, category: { label: 'Category', renderFilterLabel: this.renderSimpleFilterLabel, verbs: this.getSimpleVerbs(), }, brand: { label: 'Brand', renderFilterLabel: this.renderSimpleFilterLabel, verbs: this.getSimpleVerbs(), }, }} /> ) } } ;<MySimpleFilter /> ``` Filter users example ```js const Input = require('../Input').default const Checkbox = require('../Checkbox').default const CPF_VALIDATION_REGEX = RegExp('[0-9]{3}.[0-9]{3}.[0-9]{3}-[0-9]{2}') function SimpleInputObject({ value, onChange }) { return <Input value={value || ''} onChange={e => onChange(e.target.value)} /> } function CpfInputObject({ value, error, onChange }) { return ( <Input placeholder="Insert cpf (with punctuation)…" errorMessage={value && error} value={value || ''} onChange={e => onChange(e.target.value)} /> ) } function CpfInputObjectWithValidation({ onChange, ...props }) { return ( <CpfInputObject {...props} onChange={value => { if (value && value.length < 15 && CPF_VALIDATION_REGEX.test(value)) { onChange(value) } else { onChange(value, 'Invalid CPF') } }} /> ) } function AgeInputObject({ value, onChange }) { return ( <Input placeholder="Insert age…" type="number" min="0" max="180" value={value || ''} onChange={e => { onChange(e.target.value.replace(/\D/g, '')) }} /> ) } function AgeInputRangeObject({ value, onChange }) { const { from = '', to = '' } = value return ( <div className="flex"> <Input placeholder="Age from…" errorMessage={ parseInt(from) >= parseInt(to) ? 'Must be smaller than other input' : '' } value={from} onChange={e => { const newValue = { from, to } newValue.from = e.target.value.replace(/\D/g, '') onChange(newValue) }} /> <div className="mv4 mh3 c-muted-2 b">and</div> <Input placeholder="Age to…" value={to} onChange={e => { const newValue = { from, to } newValue.to = e.target.value.replace(/\D/g, '') onChange(newValue) }} /> </div> ) } function ClassSelectorObject({ value, error, onChange }) { const initialValue = { vip: true, gold: true, silver: true, platinum: true, ...(value || {}), } const toggleValueByKey = key => { const newValues = { ...(value || initialValue), [key]: value ? !value[key] : false, } return newValues } return ( <div> {Object.keys(initialValue).map((opt, index) => { return ( <div className="mb3" key={`class-statment-object-${opt}-${index}`}> <Checkbox checked={value ? value[opt] : initialValue[opt]} id={`class-${opt}`} label={opt} name="class-checkbox-group" onChange={() => { const newValue = toggleValueByKey(`${opt}`) onChange(newValue) }} value={opt} /> </div> ) })} </div> ) } class MyUsersFilter extends React.Component { constructor() { super() this.state = { statements: [] } } render() { return ( <FilterBar alwaysVisibleFilters={['name', 'email', 'class']} statements={this.state.statements} onChangeStatements={statements => this.setState({ statements })} clearAllFiltersButtonLabel="Clear Filters" options={{ name: { label: 'Name', renderFilterLabel: st => { if (!st || !st.object) { // you should treat empty object cases only for alwaysVisibleFilters return 'Any' } return `${ st.verb === '=' ? 'is' : st.verb === '!=' ? 'is not' : 'contains' } ${st.object}` }, verbs: [ { label: 'is', value: '=', object: props => <SimpleInputObject {...props} />, }, { label: 'is not', value: '!=', object: props => <SimpleInputObject {...props} />, }, { label: 'contains', value: 'contains', object: props => <SimpleInputObject {...props} />, }, ], }, email: { label: 'Email', renderFilterLabel: st => { if (!st || !st.object) { // you should treat empty object cases only for alwaysVisibleFilters return 'Any' } return `${ st.verb === '=' ? 'is' : st.verb === '!=' ? 'is not' : 'contains ' } ${st.object}` }, verbs: [ { label: 'contains', value: 'contains', object: props => <SimpleInputObject {...props} />, }, { label: 'is', value: '=', object: props => <SimpleInputObject {...props} />, }, { label: 'is not', value: '!=', object: props => <SimpleInputObject {...props} />, }, ], }, class: { label: 'Class', renderFilterLabel: st => { if (!st || !st.object) { // you should treat empty object cases only for alwaysVisibleFilters return 'All' } const keys = st.object ? Object.keys(st.object) : {} const isAllTrue = !keys.some(key => !st.object[key]) const isAllFalse = !keys.some(key => st.object[key]) const trueKeys = keys.filter(key => st.object[key]) let trueKeysLabel = '' trueKeys.forEach((key, index) => { trueKeysLabel += `${key}${ index === trueKeys.length - 1 ? '' : ', ' }` }) return `${ isAllTrue ? 'All' : isAllFalse ? 'None' : `${trueKeysLabel}` }` }, verbs: [ { label: 'includes', value: 'includes', object: props => <ClassSelectorObject {...props} />, }, ], }, age: { label: 'Age', renderFilterLabel: st => `${ st.verb === 'between' ? `between ${st.object.first} and ${st.object.last}` : `is ${st.object}` }`, verbs: [ { label: 'is', value: '=', object: props => <AgeInputObject {...props} />, }, { label: 'is between', value: 'between', object: props => <AgeInputRangeObject {...props} />, }, ], }, cpf: { label: 'Document', renderFilterLabel: st => `${st.verb === '=' ? 'is' : 'contains'} ${st.object}`, verbs: [ { label: 'is', value: '=', object: props => <CpfInputObjectWithValidation {...props} />, }, { label: 'contains', value: 'contains', object: props => <CpfInputObject {...props} />, }, ], }, }} /> ) } } ;<MyUsersFilter /> ``` Filter orders example ```js const Input = require('../Input').default const Checkbox = require('../Checkbox').default const DatePicker = require('../DatePicker').default function SimpleInputObject({ value, onChange }) { return <Input value={value || ''} onChange={e => onChange(e.target.value)} /> } function DatePickerObject({ value, onChange }) { return ( <div className="w-100"> <DatePicker value={value || new Date()} onChange={date => { onChange(date) }} locale="pt-BR" /> </div> ) } function DatePickerRangeObject({ value, onChange }) { return ( <div className="flex flex-column w-100"> <br /> <DatePicker label="from" value={(value && value.from) || new Date()} onChange={date => { onChange({ ...(value || {}), from: date }) }} locale="pt-BR" /> <br /> <DatePicker label="to" value={(value && value.to) || new Date()} onChange={date => { onChange({ ...(value || {}), to: date }) }} locale="pt-BR" /> </div> ) } function StatusSelectorObject({ value, onChange }) { const initialValue = { 'Window to cancelation': true, Canceling: true, Canceled: true, 'Payment pending': true, 'Payment approved': true, 'Ready for handling': true, 'Handling shipping': true, 'Ready for invoice': true, Invoiced: true, Complete: true, ...(value || {}), } const toggleValueByKey = key => { return { ...(value || initialValue), [key]: value ? !value[key] : false, } } return ( <div> {Object.keys(initialValue).map((opt, index) => { return ( <div className="mb3" key={`class-statment-object-${opt}-${index}`}> <Checkbox checked={value ? value[opt] : initialValue[opt]} id={`status-${opt}`} label={opt} name="status-checkbox-group" onChange={() => { const newValue = toggleValueByKey(`${opt}`) onChange(newValue) }} value={opt} /> </div> ) })} </div> ) } class MyOrdersFilter extends React.Component { constructor() { super() this.state = { statements: [] } this.simpleInputVerbs = this.simpleInputVerbs.bind(this) } simpleInputVerbs() { return [ { label: 'is', value: '=', object: props => <SimpleInputObject {...props} />, }, { label: 'is not', value: '!=', object: props => <SimpleInputObject {...props} />, }, { label: 'contains', value: 'contains', object: props => <SimpleInputObject {...props} />, }, ] } render() { return ( <FilterBar alwaysVisibleFilters={['id', 'email', 'status', 'invoicedate']} statements={this.state.statements} onChangeStatements={statements => this.setState({ statements })} clearAllFiltersButtonLabel="Clear Filters" options={{ id: { label: 'Order ID', renderFilterLabel: st => { if (!st || !st.object) { // you should treat empty object cases only for alwaysVisibleFilters return 'Any' } return `${ st.verb === '=' ? 'is' : st.verb === '!=' ? 'is not' : 'contains' } ${st.object}` }, verbs: this.simpleInputVerbs(), }, email: { label: 'Email', renderFilterLabel: st => { if (!st || !st.object) { // you should treat empty object cases only for alwaysVisibleFilters return 'Any' } return `${ st.verb === '=' ? 'is' : st.verb === '!=' ? 'is not' : 'contains ' } ${st.object}` }, verbs: this.simpleInputVerbs(), }, status: { label: 'Status', renderFilterLabel: st => { if (!st || !st.object) { // you should treat empty object cases only for alwaysVisibleFilters return 'All' } const keys = st.object ? Object.keys(st.object) : {} const isAllTrue = !keys.some(key => !st.object[key]) const isAllFalse = !keys.some(key => st.object[key]) const trueKeys = keys.filter(key => st.object[key]) let trueKeysLabel = '' trueKeys.forEach((key, index) => { trueKeysLabel += `${key}${ index === trueKeys.length - 1 ? '' : ', ' }` }) return `${ isAllTrue ? 'All' : isAllFalse ? 'None' : `${trueKeysLabel}` }` }, verbs: [ { label: 'includes', value: 'includes', object: props => <StatusSelectorObject {...props} />, }, ], }, invoicedate: { label: 'Invoiced date', renderFilterLabel: st => { if (!st || !st.object) return 'All' return `${ st.verb === 'between' ? `between ${st.object.from} and ${st.object.to}` : `is ${st.object}` }` }, verbs: [ { label: 'is', value: '=', object: props => <DatePickerObject {...props} />, }, { label: 'is between', value: 'between', object: props => <DatePickerRangeObject {...props} />, }, ], }, utm: { label: 'UTM Source', renderFilterLabel: st => `${st.verb === '=' ? 'is' : 'contains'} ${st.object}`, verbs: this.simpleInputVerbs(), }, seller: { label: 'Seller', renderFilterLabel: st => `${st.verb === '=' ? 'is' : 'contains'} ${st.object}`, verbs: this.simpleInputVerbs(), }, }} /> ) } } ;<MyOrdersFilter /> ``` FilterBar disabled example ```js const Input = require('../Input').default function SimpleInputObject({ value, onChange }) { return <Input value={value || ''} onChange={e => onChange(e.target.value)} /> } class FilterBarDisabled extends React.Component { constructor() { super() this.state = { statements: [ { object: 'category 1', subject: 'category', verb: '=', error: null }, ], } this.getSimpleVerbs = this.getSimpleVerbs.bind(this) this.renderSimpleFilterLabel = this.renderSimpleFilterLabel.bind(this) } getSimpleVerbs() { return [ { label: 'is', value: '=', object: props => <SimpleInputObject {...props} />, }, { label: 'is not', value: '!=', object: props => <SimpleInputObject {...props} />, }, { label: 'contains', value: 'contains', object: props => <SimpleInputObject {...props} />, }, ] } renderSimpleFilterLabel(statement) { if (!statement || !statement.object) { // you should treat empty object cases only for alwaysVisibleFilters return 'Any' } return `${ statement.verb === '=' ? 'is' : statement.verb === '!=' ? 'is not' : 'contains' } ${statement.object}` } render() { return ( <FilterBar alwaysVisibleFilters={['id', 'category', 'brand']} statements={this.state.statements} onChangeStatements={statements => this.setState({ statements })} clearAllFiltersButtonLabel="Clear Filters" disabled options={{ id: { label: 'ID', renderFilterLabel: this.renderSimpleFilterLabel, verbs: this.getSimpleVerbs(), }, category: { label: 'Category', renderFilterLabel: this.renderSimpleFilterLabel, verbs: this.getSimpleVerbs(), }, brand: { label: 'Brand', renderFilterLabel: this.renderSimpleFilterLabel, verbs: this.getSimpleVerbs(), }, }} /> ) } } ;<FilterBarDisabled /> ```