### Run Storybook Source: https://github.com/vtex/styleguide/blob/master/README.md Start Storybook to build and test components in real time. Access it at http://localhost:6006/. Ensure you have reviewed the Storybook organization guide before adding new stories. ```shell yarn storybook ``` -------------------------------- ### Install Dependencies Source: https://github.com/vtex/styleguide/blob/master/README.md Run this command to install all project dependencies. ```shell yarn install ``` -------------------------------- ### Full Blown Table Example Source: https://github.com/vtex/styleguide/blob/master/react/components/Table/README.md This example demonstrates a comprehensive usage of the Table component, integrating Toolbar, Totalizers, Pagination, and Filters. It includes setup for initial state, data slicing, and various event handlers for user interactions. ```javascript const ArrowDown = require('../icon/ArrowDown').default const ArrowUp = require('../icon/ArrowUp').default const Checkbox = require('../Checkbox').default const Input = require('../Input').default const sampleData = require('./sampleData').default const Tag = require('../Tag').default const tableLength = 5 const initialState = { tableLength, currentPage: 1, slicedData: sampleData.items.slice(0, tableLength), currentItemFrom: 1, currentItemTo: tableLength, searchValue: '', itemsLength: sampleData.items.length, emptyStateLabel: 'Nothing to show.', filterStatements: [], } const jsonschema = { properties: { name: { title: 'Name', }, email: { title: 'Email', width: 300, }, number: { title: 'Number', width: 150, }, color: { title: 'Color', cellRenderer: ({ cellData }) => { return ( {cellData.label} ) }, }, }, } class ResourceListExample extends React.Component { constructor() { super() this.state = initialState this.handleNextClick = this.handleNextClick.bind(this) this.handlePrevClick = this.handlePrevClick.bind(this) this.goToPage = this.goToPage.bind(this) this.handleInputSearchChange = this.handleInputSearchChange.bind(this) this.handleInputSearchSubmit = this.handleInputSearchSubmit.bind(this) this.handleInputSearchClear = this.handleInputSearchClear.bind(this) this.handleRowsChange = this.handleRowsChange.bind(this) this.simpleInputObject = this.simpleInputObject.bind(this) this.simpleInputVerbsAndLabel = this.simpleInputVerbsAndLabel.bind(this) this.numberInputObject = this.numberInputObject.bind(this) this.numberInputRangeObject = this.numberInputRangeObject.bind(this) this.colorSelectorObject = this.colorSelectorObject.bind(this) this.handleFiltersChange = this.handleFiltersChange.bind(this) } handleNextClick() { const newPage = this.state.currentPage + 1 const itemFrom = this.state.currentItemTo + 1 const itemTo = tableLength * newPage const data = sampleData.items.slice(itemFrom - 1, itemTo) this.goToPage(newPage, itemFrom, itemTo, data) } handlePrevClick() { if (this.state.currentPage === 0) return const newPage = this.state.currentPage - 1 const itemFrom = this.state.currentItemFrom - tableLength const itemTo = this.state.currentItemFrom - 1 const data = sampleData.items.slice(itemFrom - 1, itemTo) this.goToPage(newPage, itemFrom, itemTo, data) } goToPage(currentPage, currentItemFrom, currentItemTo, slicedData) { this.setState({ currentPage, currentItemFrom, currentItemTo, slicedData, }) } handleRowsChange(e, value) { this.setState( { tableLength: parseInt(value), currentItemTo: parseInt(value), }, () => { // this callback garantees new sliced items respect filters and tableLength const { filterStatements } = this.state this.handleFiltersChange(filterStatements) } ) } handleInputSearchChange(e) { this.setState({ searchValue: e.target.value }) } handleInputSearchClear(e) { this.setState({ ...initialState }) } handleInputSearchSubmit(e) { const value = e && e.target && e.target.value const regex = new RegExp(value, 'i') if (!value) { this.setState({ ...initialState }) } else { this.setState({ slicedData: initialState.slicedData .slice() .filter(item => regex.test(item.name) || regex.test(item.email)), }) } } simpleInputObject({ statements, values, statementIndex, error, extraParams, onChangeObjectCallback, }) { return ( onChangeObjectCallback(e.target.value)} /> ) } simpleInputVerbsAndLabel() { return { 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: { renderFn: this.simpleInputObject, extraParams: {}, }, }, { label: 'is not', value: '!=', object: { renderFn: this.simpleInputObject, extraParams: {}, }, }, { label: 'contains', value: 'contains', object: { renderFn: this.simpleInputObject, extraParams: {}, }, }, ], } ``` -------------------------------- ### Install VTEX Styleguide with npm/yarn Source: https://context7.com/vtex/styleguide/llms.txt Install the Styleguide library and the vtex-tachyons CSS utility library using yarn. ```bash yarn add @vtex/styleguide # Also install the CSS utility library yarn add vtex-tachyons ``` -------------------------------- ### Run Styleguide Source: https://github.com/vtex/styleguide/blob/master/README.md Execute this command to start the Styleguide development server. ```shell yarn styleguide ``` -------------------------------- ### Simple Product Filter Example Source: https://github.com/vtex/styleguide/blob/master/react/components/FilterBar/README.md Demonstrates a basic FilterBar setup for filtering products by ID, category, and brand. It uses a custom Input component for value entry and defines verbs like 'is', 'is not', and 'contains'. ```javascript const Input = require('../Input').default function SimpleInputObject({ value, onChange }) { return 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 => , }, { label: 'is not', value: '!=', object: props => , }, { label: 'contains', value: 'contains', object: 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 ( 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(), }, }} /> ) } } ; ``` -------------------------------- ### Table Component with Toolbar Example Source: https://github.com/vtex/styleguide/blob/master/react/components/Table/README.md Demonstrates the integration of the Table component with its toolbar features, including search, density, import/export, field toggling, extra actions, and a new line button. This example sets up the initial state and schema for the table. ```javascript const ArrowDown = require('../icon/ArrowDown').default const ArrowUp = require('../icon/ArrowUp').default const sampleData = require('./sampleData').default const Tag = require('../Tag').default const tableLength = 5 const initialState = { slicedData: sampleData.items.slice(0, tableLength), searchValue: '', emptyStateLabel: 'Nothing to show.', } const jsonschema = { properties: { name: { title: 'Name', width: 170, }, email: { title: 'Email', width: 300, }, number: { title: 'Number', width: 150, }, color: { title: 'Color', width: 170, cellRenderer: ({ cellData }) => { return ( {cellData.label} ) }, }, }, } class ResourceListExample extends React.Component { constructor() { super() this.state = initialState this.handleInputSearchChange = this.handleInputSearchChange.bind(this) this.handleInputSearchSubmit = this.handleInputSearchSubmit.bind(this) this.handleInputSearchClear = this.handleInputSearchClear.bind(this) } handleInputSearchChange(e) { this.setState({ searchValue: e.target.value }) } handleInputSearchClear(e) { this.setState({ ...initialState }) } handleInputSearchSubmit(e) { e.preventDefault() if (!this.state.searchValue) { this.setState({ ...initialState }) } else { this.setState({ slicedData: [], emptyStateLabel: 'No results found.', }) } } render() { return ( alert('Callback()'), }, upload: { label: 'Import', handleCallback: () => alert('Callback()'), }, fields: { label: 'Toggle visible fields', showAllLabel: 'Show All', hideAllLabel: 'Hide All', onToggleColumn: (params) => { console.log(params.toggledField) console.log(params.activeFields) }, onHideAllColumns: (activeFields) => console.log(activeFields), onShowAllColumns: (activeFields) => console.log(activeFields), }, extraActions: { label: 'More options', actions: [ { label: 'An action', handleCallback: () => alert('An action'), }, { label: 'Another action', handleCallback: () => alert('Another action'), }, { label: 'A third action', handleCallback: () => alert('A third action'), }, ], }, newLine: { label: 'New', handleCallback: () => alert('handle new line callback'), }, }} /> ) } } ; ``` -------------------------------- ### Toggle Example Source: https://github.com/vtex/styleguide/blob/master/react/utilities/useDisclosure/docs.md Example demonstrating how to use the useDisclosure hook with a Toggle component. ```javascript import useDisclosure from './index' import Toggle from '../../components/Toggle' const ToggleWithUseDisclosure = () => { const { isOpen, onToggle } = useDisclosure() return } ; ``` -------------------------------- ### Example: Click to Show Content Source: https://github.com/vtex/styleguide/blob/master/react/components/ButtonPlain/README.md This example uses ButtonPlain to progressively reveal content. Clicking the button toggles the visibility of related information, such as shipping options or a coupon form. ```javascript initialState = { showShippingOptions: false, showCoupon: false } ;

Shipping

{!state.showShippingOptions ? (
setState({ showShippingOptions: true }) }> View shipping options
) : (
List of options here!
)}

Coupon

{!state.showCoupon ? (
setState({ showCoupon: true }) }> Add a discount coupon
) : (
Coupon code form here!
)}
``` -------------------------------- ### ToastProvider Setup and ToastConsumer Usage Source: https://github.com/vtex/styleguide/blob/master/react/components/ToastProvider/README.md Wrap your application with ToastProvider and use ToastConsumer to access the showToast function for displaying messages. This example demonstrates showing simple text toasts and toasts with an action button. ```javascript import { useState } from 'react' const Button = require('../Button').default const ToastConsumer = require('./index').ToastConsumer const App = () => ( // Wrap the entire application on a toast provider ) const Content = () => { const [count, setCount] = useState(0) return( // Wrap the components that are going to display toasts // on a ToastConsumer, with a function as a child, as // the example below:
Click on a button to show the corresponding toast
{ ({ showToast }) => (
)}
Toast duration and control
{({showToast, hideToast}) => (
)}
Toast position
{({showToast, hideToast}) => (
{chunkedIconsMatrix.map((iconsLine, row) => ( {iconsLine.map((icon, cell) => { const IconComponent = ICONS[icon] return ( ) })} ))}
{icon}
) } } ; ``` -------------------------------- ### Full Table Example Source: https://github.com/vtex/styleguide/blob/master/react/components/EXPERIMENTAL_Table/docs/Components.md Renders a complete example of the experimental table component, likely including data, headers, and interactive elements. ```jsx ``` -------------------------------- ### Modal Example Source: https://github.com/vtex/styleguide/blob/master/react/utilities/useDisclosure/docs.md Example demonstrating how to use the useDisclosure hook with a Modal component. ```javascript import useDisclosure from './index' import Button from '../../components/Button' import Modal from '../../components/Modal' const ModalWithUseDisclosure = () => { const { isOpen, onOpen, onClose } = useDisclosure() return ( <> Modal with useDisclosure() ) } ; ``` -------------------------------- ### Simple Conditions Example Source: https://github.com/vtex/styleguide/blob/master/react/components/Conditions/README.md Demonstrates the basic usage of the Conditions component with multiple options and verbs. This example shows how to configure subjects like 'name' and 'email' with their respective comparison verbs. ```javascript const Input = require('../Input').default function SimpleInputObject({ value, onChange }) { return onChange(e.target.value)} /> } class SimpleConditionsCase extends React.Component { constructor() { super() this.state = { simpleStatements: [], operator: 'all', } this.handleToggleOperator = this.handleToggleOperator.bind(this) } handleToggleOperator(operator) { this.setState({ operator: this.state.operator === 'all' ? 'any' : 'all' }) } render() { const options = { name: { label: 'User name', verbs: [ { label: 'is', value: '=', object: props => , }, { label: 'is not', value: '!=', object: props => , }, ], }, email: { label: 'Email', verbs: [ { label: 'contains', value: 'contains', object: props => , }, { label: 'is', value: '=', object: props => , }, { label: 'is not', value: '!=', object: props => , }, ], }, } return ( { console.log('Changed statements to:', statements) this.setState({ simpleStatements: statements }) }} operator={this.state.operator} options={options} subjectPlaceholder="Select subject" statements={this.state.simpleStatements} /> ) } } ; ``` -------------------------------- ### All Steps to Do Progress Source: https://github.com/vtex/styleguide/blob/master/react/components/Progress/README.md Use this when all steps are yet to be started. It displays multiple 'toDo' states. ```jsx ``` -------------------------------- ### Centered Layout Example Source: https://github.com/vtex/styleguide/blob/master/react/components/Layout/README.md Use the centered layout for forms, setups, and CRUDs where guiding the user through a specific action is important. It maintains a max width to keep content centered on large screens. ```javascript const PageHeader = require('../PageHeader').default const PageBlock = require('../PageBlock').default const Button = require('../Button').default }> ``` -------------------------------- ### Complex Conditions Example Source: https://github.com/vtex/styleguide/blob/master/react/components/Conditions/README.md Demonstrates how to use the Conditions component with various data types like numeric inputs, dropdowns, selects, and date pickers. This example showcases a complex setup for user attributes. ```javascript class ComplexConditionsCase extends React.Component { constructor() { super() this.state = { statements: [], operator: 'all', } this.handleToggleOperator = this.handleToggleOperator.bind(this) } handleToggleOperator(operator) { this.setState({ operator: this.state.operator === 'all' ? 'any' : 'all' }) } render() { const options = { age: { unique: true, label: 'User age', verbs: [ { label: 'is', value: '=', object: props => , }, { label: 'is between', value: 'between', object: props => , }, ], }, color: { unique: true, label: 'User favorite color', verbs: [ { label: 'is', value: '=', object: props => , }, { label: 'is any of', value: 'any', object: props => , }, ], }, birthday: { unique: true, label: 'User birthday', verbs: [ { label: 'is', value: '=', object: props => , }, { label: 'is between', value: 'between', object: props => , }, ], }, } return ( { console.log('Changed statements to:', statements) this.setState({ statements }) }} onChangeOperator={this.handleToggleOperator} operator={this.state.operator} options={options} statements={this.state.statements} subjectPlaceholder="Select subject" /> ) } } ; ``` -------------------------------- ### Basic Table Usage Example Source: https://github.com/vtex/styleguide/blob/master/react/components/Table/README.md Demonstrates how to use the Table component with sample data and a defined schema. Includes configuration for full width, density, and row click handling. ```javascript const sampleData = require('./sampleData').default const itemsCopy = sampleData.items .slice() .reverse() .splice(15) const defaultSchema = { properties: { name: { title: 'Name', width: 300, }, email: { title: 'Email', minWidth: 350, }, number: { title: 'Number', // default is 200px minWidth: 100, }, }, } ;
{ alert( `you just clicked ${rowData.name}, number is ${rowData.number} and email ${rowData.email}` ) }} /> ``` -------------------------------- ### Publish Styleguide Source: https://github.com/vtex/styleguide/blob/master/README.md Command to publish the styleguide to npm and VTEX IO, including generating Github Release Notes. Requires Personal Token configuration for npm. ```shell releasy --stable ``` -------------------------------- ### Table Component Full Example Source: https://github.com/vtex/styleguide/blob/master/react/components/Table/README.md This snippet demonstrates a comprehensive configuration of the Table component, showcasing its extensive features including search, density, download/upload, field toggling, pagination, totalizers, bulk actions, and filters. It's useful for understanding the full capabilities and integration of the component. ```javascript value: this.state.searchValue, placeholder: 'Search stuff...', onChange: this.handleInputSearchChange, onClear: this.handleInputSearchClear, onSubmit: this.handleInputSearchSubmit, }, density: { buttonLabel: 'Line density', lowOptionLabel: 'Low', mediumOptionLabel: 'Medium', highOptionLabel: 'High', }, download: { label: 'Export', handleCallback: () => alert('Callback()'), }, upload: { label: 'Import', handleCallback: () => alert('Callback()'), }, fields: { label: 'Toggle visible fields', showAllLabel: 'Show All', hideAllLabel: 'Hide All', onToggleColumn: (params) => { console.log(params.toggledField) console.log(params.activeFields) }, onHideAllColumns: (activeFields) => console.log(activeFields), onShowAllColumns: (activeFields) => console.log(activeFields), }, extraActions: { label: 'More options', actions: [ { label: 'An action', handleCallback: () => alert('An action'), }, { label: 'Another action', handleCallback: () => alert('Another action'), }, { label: 'A third action', handleCallback: () => alert('A third action'), }, ], }, newLine: { label: 'New', handleCallback: () => alert('handle new line callback'), actions: [ 'General', 'Desktop & Screen Saver', 'Dock', 'Language & Region', ].map(label => ({ label, onClick: () => {}, })), }, }} pagination={{ onNextClick: this.handleNextClick, onPrevClick: this.handlePrevClick, currentItemFrom: this.state.currentItemFrom, currentItemTo: this.state.currentItemTo, onRowsChange: this.handleRowsChange, textShowRows: 'Show rows', textOf: 'of', totalItems: this.state.itemsLength, rowsOptions: [5, 10, 15, 25], }} totalizers={[ { label: 'Account balance', value: 23837, }, { label: 'Tickets', value: '$ 36239,05', iconBackgroundColor: '#eafce3', icon: , }, { label: 'Outputs', value: '-$ 13.485,26', icon: , }, { label: 'Sales', value: 23837, isLoading: true, }, ]} bulkActions={{ texts: { secondaryActionsLabel: 'Actions', rowsSelected: qty => ( Selected rows: {qty} ), selectAll: 'Select all', allRowsSelected: qty => ( All rows selected: {qty} ), }, totalItems: 122, onChange: params => console.log(params), main: { label: 'Main Action', handleCallback: params => console.log(params), }, others: [ { label: 'Action 1', handleCallback: params => console.log(params), }, { label: 'Action 2', handleCallback: params => console.log(params), }, { label: 'Dangerous action', isDangerous: true, handleCallback: params => console.log(params), }, ], }} filters={{ alwaysVisibleFilters: ['color', 'name'], statements: this.state.filterStatements, onChangeStatements: this.handleFiltersChange, clearAllFiltersButtonLabel: 'Clear Filters', collapseLeft: true, options: { color: { label: 'Color', 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) => { ``` -------------------------------- ### Default Vertical BarChart Source: https://github.com/vtex/styleguide/blob/master/react/components/EXPERIMENTAL_Charts/BarChart/README.md Renders a default vertical bar chart. This example explicitly sets the layout to 'horizontal' but configures axes for a vertical orientation, demonstrating the default behavior when axis types match the standard vertical setup. ```javascript ``` -------------------------------- ### Toggle with Labels and Sizes Source: https://github.com/vtex/styleguide/blob/master/react/components/Toggle/README.md Shows how to use labels to reflect the state of the Toggle and demonstrates the 'large' size variation. Includes an example with help text. ```javascript initialState = { checked: true, checked2: false, checkedLarge1: true, checkedLarge2: false };
setState(prevState => ({ checked: !prevState.checked }))} />

setState(prevState => ({ checked2: !prevState.checked2 }))} />

setState(prevState => ({ checkedLarge1: !prevState.checkedLarge1 })) } />

setState(prevState => ({ checkedLarge2: !prevState.checkedLarge2 })) } helpText="You can add help text!" />
; ``` -------------------------------- ### Table with Toolbar Example Source: https://github.com/vtex/styleguide/blob/master/react/components/EXPERIMENTAL_Table/docs/Components.md Demonstrates a fully functional table with a configurable toolbar, including search, column visibility toggles, density settings, export/import options, and custom actions. It utilizes hooks for managing table measures and visibility. ```javascript import Table from '../index' import useTableMeasures from '../hooks/useTableMeasures' import useTableVisibility from '../hooks/useTableVisibility' import Tag from '../../Tag' import Icons from 'react-icons/fa' import data from './sampleData' const columns = [ { id: 'id', title: 'ID', }, { id: 'icon', title: 'Icon', cellRenderer: ({ data, rowHeight, motion }) => ( ), }, { id: 'name', title: 'Name', }, { id: 'status', title: 'Status', cellRenderer: ({ data }) => , }, ] function Icon({ name, height, style }) { const SelectedIcon = Icons[name] return } function Status({ status }) { const type = status === 'ACTIVE' ? 'success' : 'neutral' return {status} } const items = data.payments function reducer(state, action) { switch (action.type) { case 'change': { const { value } = action return { ...state, inputValue: value, } } case 'clear': { return { inputValue: '', displayItems: items, } } case 'submit': { const inputClear = state.input === '' const filterFn = item => item.name.toLowerCase().includes(state.inputValue.toLowerCase()) return { ...state, displayItems: inputClear ? items : items.filter(filterFn), } } default: { return state } } } function ToolbarExample() { const [state, dispatch] = React.useReducer(reducer, { inputValue: '', displayItems: items, }) const visibility = useTableVisibility({ columns, items, hiddenColumns: ['id'], }) const measures = useTableMeasures({ size: state.displayItems.length, }) const inputSearch = { value: state.inputValue, placeholder: 'Search stuff...', onChange: e => dispatch({ type: 'change', value: e.currentTarget.value }), onClear: () => { dispatch({ type: 'clear' }) }, onSubmit: e => { e.preventDefault() dispatch({ type: 'submit' }) }, } const buttonColumns = { label: 'Toggle visible fields', showAllLabel: 'Show All', hideAllLabel: 'Hide All', visibility, } const density = { label: 'Line density', compactLabel: 'Compact', regularLabel: 'Regular', comfortableLabel: 'Comfortable', } const download = { label: 'Export', onClick: () => alert('Clicked EXPORT'), } const upload = { label: 'Import', onClick: () => alert('Clicked IMPORT'), } const extraActions = { label: 'More options', actions: [ { label: 'An action', onClick: () => alert('An action'), }, { label: 'Another action', onClick: () => alert('Another action'), }, { label: 'A third action', onClick: () => alert('A third action'), }, ], } const newLine = { label: 'New', onClick: () => alert('handle new line callback'), actions: [ 'General', 'Desktop & Screen Saver', 'Dock', 'Language & Region', ].map(label => ({ label, onClick: () => alert(`Clicked ${label}`), })), } const emptyState = { label: 'The table is empty', } const empty = React.useMemo( () => state.displayItems.length === 0 || Object.keys(visibility.visibleColumns).length === 0, [visibility.visibleColumns, state.displayItems] ) return (
) } ; ``` -------------------------------- ### Add Styleguide to Other Projects (Yarn) Source: https://github.com/vtex/styleguide/blob/master/docs/developing.md Install the VTEX Styleguide as a dependency in projects outside of VTEX IO using Yarn. This command adds the package to your project's dependencies. ```shell yarn add @vtex/styleguide ``` -------------------------------- ### Example: Edit Info Toggle Source: https://github.com/vtex/styleguide/blob/master/react/components/ButtonPlain/README.md This example uses ButtonPlain to toggle between viewing and editing information. It switches between displaying address details and a textarea for editing, with 'Edit' and 'Cancel' buttons. ```javascript const Textarea = require('../Textarea').default initialState = { showEdit: false, showAddress: true } ;
{state.showAddress ? ( <>
Shipping options
1585 Broadway, New York, NY
10036, United States
) : ( )}
{state.showAddress ? ( setState({ showAddress: false, showEdit: true }) }> Edit ) : ( setState({ showAddress: true, showEdit: false }) }> Cancel )}
``` -------------------------------- ### Basic Floating Action Bar Example Source: https://github.com/vtex/styleguide/blob/master/react/components/FloatingActionBar/README.md Demonstrates how to implement a Floating Action Bar with save and cancel actions. The save action includes loading state management. ```javascript class ActionBar extends React.Component { constructor(props) { super(props) this.state = { loading: false } } render() { return ( { this.setState({ loading: true }) setTimeout(() => { alert('This was invoked because save was pressed') this.setState({ loading: false }) }, 2000) }, }} cancel={{ label: 'cancel', }} /> ) } } ; ``` -------------------------------- ### Steps Progress Example Source: https://github.com/vtex/styleguide/blob/master/react/components/Progress/README.md Use this to show a linear progression with steps. Ensure steps are ordered correctly: 'Completed', 'In Progress', 'To Do'. ```jsx ```