From d21714347a20bb1f69bd3b09f99d7bc1b1eea1eb Mon Sep 17 00:00:00 2001 From: Lucas Grimauth Evangelista Date: Fri, 15 Jan 2021 14:19:55 -0300 Subject: [PATCH 1/7] release: 1.1.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 92231b9c..50c90126 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@boomerang-io/carbon-addons-boomerang-react", "description": "Carbon Addons for Boomerang apps", - "version": "1.1.4-beta.5", + "version": "1.1.4", "author": { "name": "Tim Bula", "email": "timrbula@gmail.com" From 500daea6cf6376f994485716c1db9dfd46cbe383 Mon Sep 17 00:00:00 2001 From: Lucas Grimauth Evangelista Date: Fri, 13 Aug 2021 14:35:51 -0300 Subject: [PATCH 2/7] fix: add tabIndex to uishell links --- package.json | 2 +- src/components/Header/Header.js | 2 +- src/components/Header/HeaderListItem.js | 1 + src/components/Header/HeaderLogo.js | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 7addb973..5a78f3cd 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@boomerang-io/carbon-addons-boomerang-react", "description": "Carbon Addons for Boomerang apps", - "version": "1.2.3", + "version": "1.2.4-beta.0", "author": { "name": "Tim Bula", "email": "timrbula@gmail.com" diff --git a/src/components/Header/Header.js b/src/components/Header/Header.js index 3f7132f2..d293135c 100644 --- a/src/components/Header/Header.js +++ b/src/components/Header/Header.js @@ -258,7 +258,7 @@ class Header extends React.Component { {Array.isArray(navLinks) && navLinks.map((link, i) => (
  • - {link.name} + {link.name}
  • ))} diff --git a/src/components/Header/HeaderListItem.js b/src/components/Header/HeaderListItem.js index 744a4112..24fd968c 100644 --- a/src/components/Header/HeaderListItem.js +++ b/src/components/Header/HeaderListItem.js @@ -35,6 +35,7 @@ const HeaderListItem = (props) => { window.location.href.startsWith(href), })} href={href} + tabIndex="0" {...other} > {children} diff --git a/src/components/Header/HeaderLogo.js b/src/components/Header/HeaderLogo.js index d7a207fb..8abf2e88 100644 --- a/src/components/Header/HeaderLogo.js +++ b/src/components/Header/HeaderLogo.js @@ -9,7 +9,7 @@ const HeaderLogo = (props) => { return (
    - + {children}
    {(platformName || appName) && ( From 7f93200574c59a875321c45bf48dbfc7ea338647 Mon Sep 17 00:00:00 2001 From: Matheus-de-Souza Date: Wed, 18 Aug 2021 10:30:33 -0300 Subject: [PATCH 3/7] fix: header links changed to div element --- src/components/Header/HeaderListItem.js | 5 +++-- src/components/Header/_header.scss | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/Header/HeaderListItem.js b/src/components/Header/HeaderListItem.js index 24fd968c..402f0089 100644 --- a/src/components/Header/HeaderListItem.js +++ b/src/components/Header/HeaderListItem.js @@ -26,7 +26,7 @@ const HeaderListItem = (props) => { {children}
    ) : ( -
    { window.location.href.startsWith(href), })} href={href} + role="button" tabIndex="0" {...other} > {children} - +
    )} ); diff --git a/src/components/Header/_header.scss b/src/components/Header/_header.scss index 32f241a0..137fedd3 100644 --- a/src/components/Header/_header.scss +++ b/src/components/Header/_header.scss @@ -169,6 +169,7 @@ $breakpoints: ( .#{$prefix}--bmrg-header-list__link { align-items: center; color: $bmrg-secondary-color; + cursor: pointer; display: flex; font-size: rem(14px); height: 100%; From 4b6eb33c7ce95889277d28c4ce306464594c3179 Mon Sep 17 00:00:00 2001 From: Matheus-de-Souza Date: Wed, 18 Aug 2021 10:51:47 -0300 Subject: [PATCH 4/7] fix: version 1.2.4-beta.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5a78f3cd..9f175efa 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@boomerang-io/carbon-addons-boomerang-react", "description": "Carbon Addons for Boomerang apps", - "version": "1.2.4-beta.0", + "version": "1.2.4-beta.1", "author": { "name": "Tim Bula", "email": "timrbula@gmail.com" From 331ea077e2633486c07bbdd70e33768b72d245b0 Mon Sep 17 00:00:00 2001 From: MatheusGooier Date: Thu, 9 Sep 2021 19:43:43 -0300 Subject: [PATCH 5/7] fix: recreate tabs component replacing list element to div element --- src/components/Tab/Tab.js | 154 ++++++++ src/components/Tab/index.js | 1 + src/components/TabContent/TabContent.js | 62 +++ src/components/TabContent/index.js | 1 + src/components/Tabs/Tabs.Skeleton.js | 51 +++ src/components/Tabs/Tabs.js | 500 ++++++++++++++++++++++++ src/components/Tabs/Tabs.stories.js | 56 +++ src/components/Tabs/_tabs.scss | 19 + src/components/Tabs/index.js | 2 + src/index.js | 11 +- src/internal/useIsomorphicEffect.js | 6 + src/styles/index.scss | 4 +- 12 files changed, 862 insertions(+), 5 deletions(-) create mode 100644 src/components/Tab/Tab.js create mode 100644 src/components/Tab/index.js create mode 100644 src/components/TabContent/TabContent.js create mode 100644 src/components/TabContent/index.js create mode 100644 src/components/Tabs/Tabs.Skeleton.js create mode 100644 src/components/Tabs/Tabs.js create mode 100644 src/components/Tabs/Tabs.stories.js create mode 100644 src/components/Tabs/_tabs.scss create mode 100644 src/components/Tabs/index.js create mode 100644 src/internal/useIsomorphicEffect.js diff --git a/src/components/Tab/Tab.js b/src/components/Tab/Tab.js new file mode 100644 index 00000000..996898d1 --- /dev/null +++ b/src/components/Tab/Tab.js @@ -0,0 +1,154 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import classNames from 'classnames'; +import { settings } from 'carbon-components'; +import deprecate from '../../internal/deprecate'; + +const { prefix } = settings; +export default class Tab extends React.Component { + static propTypes = { + /** Specify an optional className to be added to your Tab*/ + className: PropTypes.string, + + /** Whether your Tab is disabled.*/ + disabled: PropTypes.bool, + + /** + * A handler that is invoked when a user clicks on the control. + * Reserved for usage in Tabs + */ + handleTabClick: PropTypes.func, + + /** + * A handler that is invoked on the key down event for the control. + * Reserved for usage in Tabs + */ + handleTabKeyDown: PropTypes.func, + + /** Provide a string that represents the `href` of the Tab */ + href: deprecate(PropTypes.string), + + /** The element ID for the top-level element. */ + id: PropTypes.string, + + /** The index of your Tab in your Tabs. Reserved for usage in Tabs */ + index: PropTypes.number, + + /** Provide the contents of your Tab */ + label: PropTypes.node, + + /** Provide a handler that is invoked when a user clicks on the control */ + onClick: PropTypes.func.isRequired, + + /** Provide a handler that is invoked on the key down event for the control */ + onKeyDown: PropTypes.func.isRequired, + + /* + * An optional parameter to allow overriding the anchor rendering. + * Useful for using Tab along with react-router or other client + * side router libraries. + **/ + renderAnchor: deprecate(PropTypes.func), + renderButton: PropTypes.func, + + /* An optional parameter to allow overriding the content rendering. */ + renderContent: PropTypes.func, + + /** Provide an accessibility role for your Tab */ + role: PropTypes.string.isRequired, + + /** + * Whether your Tab is selected. + * Reserved for usage in Tabs + */ + selected: PropTypes.bool.isRequired, + + /** Specify the tab index of the ` + )} + + ); + } +} diff --git a/src/components/Tab/index.js b/src/components/Tab/index.js new file mode 100644 index 00000000..dcfd2afd --- /dev/null +++ b/src/components/Tab/index.js @@ -0,0 +1 @@ +export { default } from './Tab'; diff --git a/src/components/TabContent/TabContent.js b/src/components/TabContent/TabContent.js new file mode 100644 index 00000000..d720c3ce --- /dev/null +++ b/src/components/TabContent/TabContent.js @@ -0,0 +1,62 @@ +import PropTypes from 'prop-types'; +import React, { useState, useRef } from 'react'; +import classNames from 'classnames'; +import { settings } from 'carbon-components'; +import { selectorTabbable } from '../../internal/keyboard/navigation'; +import useIsomorphicEffect from '../../internal/useIsomorphicEffect'; + +const { prefix } = settings; + +/** + * Determine if the node within the provided ref contains content that is tabbable. + */ +function useTabbableContent(ref) { + const [hasTabbableContent, setHasTabbableContent] = useState(false); + + useIsomorphicEffect(() => { + if (ref.current) { + setHasTabbableContent(ref.current.querySelector(selectorTabbable)); + } + }); + + return hasTabbableContent; +} + +const TabContent = (props) => { + const { className, selected, children, ...other } = props; + const tabContentClasses = classNames(`${prefix}--tab-content`, { + [className]: className, + }); + const ref = useRef(null); + const hasTabbableContent = useTabbableContent(ref); + return ( + + ); +}; + +TabContent.propTypes = { + /** Pass in content to render inside of the TabContent */ + children: PropTypes.node, + + /** Provide a className for the tab content container */ + className: PropTypes.string, + + /** Specify whether the TabContent is selected */ + selected: PropTypes.bool, +}; + +TabContent.defaultProps = { + selected: false, +}; + +export default TabContent; diff --git a/src/components/TabContent/index.js b/src/components/TabContent/index.js new file mode 100644 index 00000000..6f62c396 --- /dev/null +++ b/src/components/TabContent/index.js @@ -0,0 +1 @@ +export { default } from './TabContent.js'; diff --git a/src/components/Tabs/Tabs.Skeleton.js b/src/components/Tabs/Tabs.Skeleton.js new file mode 100644 index 00000000..777a92cd --- /dev/null +++ b/src/components/Tabs/Tabs.Skeleton.js @@ -0,0 +1,51 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import cx from 'classnames'; +import { settings } from 'carbon-components'; + +const { prefix } = settings; + +const tab = ( +
  • +
    + +
    +
  • +); + +function TabsSkeleton({ className, type, ...rest }) { + const tabClasses = cx( + className, + `${prefix}--tabs`, + `${prefix}--skeleton`, + `${prefix}--tabs--scrollable`, + { + [`${prefix}--tabs--scrollable--container`]: type === 'container', + } + ); + return ( +
    +
      + {tab} + {tab} + {tab} + {tab} + {tab} +
    +
    + ); +} + +TabsSkeleton.propTypes = { + /** + * Specify an optional className to add. + */ + className: PropTypes.string, + + /** + * Provide the type of Tab + */ + type: PropTypes.oneOf(['', 'default', 'container']), +}; + +export default TabsSkeleton; diff --git a/src/components/Tabs/Tabs.js b/src/components/Tabs/Tabs.js new file mode 100644 index 00000000..ca359d32 --- /dev/null +++ b/src/components/Tabs/Tabs.js @@ -0,0 +1,500 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import classNames from 'classnames'; +import { settings } from 'carbon-components'; +import { ChevronLeft16, ChevronRight16 } from '@carbon/icons-react'; +import debounce from 'lodash.debounce'; +import { keys, match, matches } from '../../internal/keyboard'; +import TabContent from '../TabContent'; + +const { prefix } = settings; + +export default class Tabs extends React.Component { + static propTypes = { + /** + * Pass in a collection of children to be rendered depending on the + * currently selected tab + */ + children: PropTypes.node, + + /** + * Provide a className that is applied to the root
    component for the + * + */ + className: PropTypes.string, + + /** + * Specify whether the Tab content is hidden + */ + hidden: PropTypes.bool, + + /** + * Provide the props that describe the left overflow button + */ + leftOverflowButtonProps: PropTypes.object, + + /** + * Specify whether or not to use the light component variant + */ + light: PropTypes.bool, + + /** + * Optionally provide an `onClick` handler that is invoked when a is + * clicked + */ + onClick: PropTypes.func, + + /** + * Optionally provide an `onKeyDown` handler that is invoked when keyed + * navigation is triggered + */ + onKeyDown: PropTypes.func, + + /** + * Provide an optional handler that is called whenever the selection + * changes. This method is called with the index of the tab that was + * selected + */ + onSelectionChange: PropTypes.func, + + /** + * Provide the props that describe the right overflow button + */ + rightOverflowButtonProps: PropTypes.object, + + /** + * Choose whether or not to automatically scroll to newly selected tabs + * on component rerender + */ + scrollIntoView: PropTypes.bool, + + /** + * Optionally provide an index for the currently selected + */ + selected: PropTypes.number, + + /** + * Choose whether or not to automatically change selection on focus + */ + selectionMode: PropTypes.oneOf(['automatic', 'manual']), + + /** + * Provide a className that is applied to the components + */ + tabContentClassName: PropTypes.string, + + /** + * Provide the type of Tab + */ + type: PropTypes.oneOf(['default', 'container']), + }; + + static defaultProps = { + type: 'default', + scrollIntoView: true, + selected: 0, + selectionMode: 'automatic', + }; + + state = { + horizontalOverflow: false, + }; + + tablist = React.createRef(); + leftOverflowNavButton = React.createRef(); + rightOverflowNavButton = React.createRef(); + // width of the overflow buttons + OVERFLOW_BUTTON_OFFSET = 40; + + static getDerivedStateFromProps({ selected }, state) { + const { prevSelected } = state; + return prevSelected === selected + ? null + : { + selected, + prevSelected: selected, + }; + } + + /** + * `scroll` event handler to save tablist clientWidth, scrollWidth, and + * scrollLeft + */ + handleScroll = () => { + if (!this.tablist?.current) { + return; + } + const { + clientWidth: tablistClientWidth, + scrollLeft: tablistScrollLeft, + scrollWidth: tablistScrollWidth, + } = this.tablist.current; + this.setState({ + tablistClientWidth, + horizontalOverflow: tablistScrollWidth >= tablistClientWidth, + tablistScrollWidth, + tablistScrollLeft, + }); + }; + + /** + * The debounced version of the `resize` event handler. + * @type {Function} + * @private + */ + _debouncedHandleWindowResize = null; + + _handleWindowResize = this.handleScroll; + + componentDidMount() { + if (!this._debouncedHandleWindowResize) { + this._debouncedHandleWindowResize = debounce(this._handleWindowResize, 200); + } + + this._handleWindowResize(); + window.addEventListener('resize', this._debouncedHandleWindowResize); + + // scroll selected tab into view on mount + const { + clientWidth: tablistClientWidth, + scrollLeft: tablistScrollLeft, + scrollWidth: tablistScrollWidth, + } = this.tablist?.current || {}; + const tab = this.getTabAt(this.state.selected); + const horizontalOverflow = tablistScrollWidth > tablistClientWidth; + + if (horizontalOverflow) { + const leftOverflowNavButtonHidden = + tab?.tabAnchor?.getBoundingClientRect().right < + tab?.tabAnchor?.offsetParent.getBoundingClientRect().right; + const rightOverflowNavButtonHidden = + tablistScrollLeft + tablistClientWidth === tablistScrollWidth; + this.props.scrollIntoView && + tab?.tabAnchor?.scrollIntoView({ block: 'nearest', inline: 'nearest' }); + + // account for overflow buttons in scroll position on mount + if (!leftOverflowNavButtonHidden && !rightOverflowNavButtonHidden) { + this.tablist.current.scrollLeft += this.OVERFLOW_BUTTON_OFFSET * 2; + } + } + } + + componentWillUnmount() { + if (this._debouncedHandleWindowResize) { + this._debouncedHandleWindowResize.cancel(); + } + window.removeEventListener('resize', this._debouncedHandleWindowResize); + } + + componentDidUpdate(_, prevState) { + // compare current tablist properties to current state + const { + clientWidth: tablistClientWidth, + scrollLeft: tablistScrollLeft, + scrollWidth: tablistScrollWidth, + } = this.tablist.current; + const { + tablistClientWidth: currentStateClientWidth, + tablistScrollLeft: currentStateScrollLeft, + tablistScrollWidth: currentStateScrollWidth, + selected, + } = this.state; + + if ( + tablistClientWidth !== currentStateClientWidth || + tablistScrollLeft !== currentStateScrollLeft || + tablistScrollWidth !== currentStateScrollWidth + ) { + this.setState({ + horizontalOverflow: tablistScrollWidth > tablistClientWidth, + tablistClientWidth, + tablistScrollLeft, + tablistScrollWidth, + }); + } + + if (this.props.scrollIntoView && prevState.selected !== selected) { + this.getTabAt(selected)?.tabAnchor?.scrollIntoView({ + block: 'nearest', + inline: 'nearest', + }); + } + } + + getEnabledTabs = () => + React.Children.toArray(this.props.children).reduce( + (enabledTabs, tab, index) => (!tab.props.disabled ? enabledTabs.concat(index) : enabledTabs), + [] + ); + + getNextIndex = (index, direction) => { + const enabledTabs = this.getEnabledTabs(); + const nextIndex = Math.max( + enabledTabs.indexOf(index) + direction, + // For `tab` not found in `enabledTabs` + -1 + ); + const nextIndexLooped = + nextIndex >= 0 && nextIndex < enabledTabs.length + ? nextIndex + : nextIndex - Math.sign(nextIndex) * enabledTabs.length; + return enabledTabs[nextIndexLooped]; + }; + + getDirection = (evt) => { + if (match(evt, keys.ArrowLeft)) { + return -1; + } + if (match(evt, keys.ArrowRight)) { + return 1; + } + return 0; + }; + + getTabAt = (index, useFresh) => + (!useFresh && this[`tab${index}`]) || React.Children.toArray(this.props.children)[index]; + + scrollTabIntoView = (event, { index }) => { + const tab = this.getTabAt(index); + if (matches(event, [keys.ArrowLeft, keys.ArrowRight]) || event.type === 'click') { + const currentScrollLeft = this.state.tablistScrollLeft; + tab?.tabAnchor?.scrollIntoView({ block: 'nearest', inline: 'nearest' }); + tab?.tabAnchor?.focus(); + const newScrollLeft = this.tablist.current.scrollLeft; + if (newScrollLeft > currentScrollLeft) { + this.tablist.current.scrollLeft += this.OVERFLOW_BUTTON_OFFSET; + } + } + }; + + selectTabAt = (event, { index, onSelectionChange }) => { + this.scrollTabIntoView(event, { index }); + if (this.state.selected !== index) { + this.setState({ + selected: index, + }); + if (typeof onSelectionChange === 'function') { + onSelectionChange(index); + } + } + }; + + handleTabKeyDown = (onSelectionChange) => { + return (index, evt) => { + if (matches(evt, [keys.Enter, keys.Space])) { + this.selectTabAt(evt, { index, onSelectionChange }); + } + + const nextIndex = (() => { + if (matches(evt, [keys.ArrowLeft, keys.ArrowRight])) { + return this.getNextIndex(index, this.getDirection(evt)); + } + if (match(evt, keys.Home)) { + return 0; + } + if (match(evt, keys.End)) { + return this.getEnabledTabs().pop(); + } + })(); + const tab = this.getTabAt(nextIndex); + + if (matches(evt, [keys.ArrowLeft, keys.ArrowRight, keys.Home, keys.End])) { + evt.preventDefault(); + if (this.props.selectionMode !== 'manual') { + this.selectTabAt(evt, { index: nextIndex, onSelectionChange }); + } else { + this.scrollTabIntoView(evt, { index: nextIndex }); + } + tab?.tabAnchor?.focus(); + } + }; + }; + + getTabs = () => React.Children.map(this.props.children, (tab) => tab); + + // following functions (handle*) are Props on Tab.js, see Tab.js for parameters + handleTabClick = (onSelectionChange) => (index, evt) => { + evt.preventDefault(); + this.selectTabAt(evt, { index, onSelectionChange }); + }; + + setTabAt = (index, tabRef) => { + this[`tab${index}`] = tabRef; + }; + + overflowNavInterval = null; + + handleOverflowNavClick = (_, { direction, multiplier = 10 }) => { + // account for overflow button appearing and causing tablist width change + const { clientWidth, scrollLeft, scrollWidth } = this.tablist?.current; + if (direction === 1 && !scrollLeft) { + this.tablist.current.scrollLeft += this.OVERFLOW_BUTTON_OFFSET; + } + + this.tablist.current.scrollLeft += direction * multiplier; + + const leftEdgeReached = direction === -1 && scrollLeft < this.OVERFLOW_BUTTON_OFFSET; + const rightEdgeReached = + direction === 1 && scrollLeft + clientWidth >= scrollWidth - this.OVERFLOW_BUTTON_OFFSET; + if (leftEdgeReached || rightEdgeReached) { + if (leftEdgeReached) { + this.rightOverflowNavButton?.current?.focus(); + } + if (rightEdgeReached) { + this.leftOverflowNavButton?.current?.focus(); + } + } + }; + + handleOverflowNavMouseDown = (event, { direction }) => { + // disregard mouse buttons aside from LMB + if (event.buttons !== 1) { + return; + } + this.overflowNavInterval = setInterval(() => { + const { clientWidth, scrollLeft, scrollWidth } = this.tablist?.current; + + // clear interval if scroll reaches left or right edge + const leftEdgeReached = direction === -1 && scrollLeft < this.OVERFLOW_BUTTON_OFFSET; + const rightEdgeReached = + direction === 1 && scrollLeft + clientWidth >= scrollWidth - this.OVERFLOW_BUTTON_OFFSET; + if (leftEdgeReached || rightEdgeReached) { + clearInterval(this.overflowNavInterval); + } + + // account for overflow button appearing and causing tablist width change + this.handleOverflowNavClick(event, { direction }); + }); + }; + + handleOverflowNavMouseUp = () => { + clearInterval(this.overflowNavInterval); + }; + + render() { + const { + className, + type, + light, + onSelectionChange, + scrollIntoView, // eslint-disable-line no-unused-vars + selectionMode, // eslint-disable-line no-unused-vars + tabContentClassName, + leftOverflowButtonProps, + rightOverflowButtonProps, + ...other + } = this.props; + + /** + * The tab panel acts like a tab panel when the screen is wider, but acts + * like a select list when the screen is narrow. In the wide case we want + * to allow the user to use the tab key to set the focus in the tab panel + * and then use the left and right arrow keys to navigate the tabs. In the + * narrow case we want to use the tab key to select different options in + * the list. + * + * We set the tab index based on the different states so the browser will treat + * the whole tab panel as a single focus component when it looks like a tab + * panel and separate components when it looks like a select list. + */ + const tabsWithProps = this.getTabs().map((tab, index) => { + const tabIndex = index === this.state.selected ? 0 : -1; + const newTab = React.cloneElement(tab, { + index, + selected: index === this.state.selected, + handleTabClick: this.handleTabClick(onSelectionChange), + tabIndex, + ref: (e) => { + this.setTabAt(index, e); + }, + handleTabKeyDown: this.handleTabKeyDown(onSelectionChange), + }); + + return newTab; + }); + + const tabContentWithProps = React.Children.map(tabsWithProps, (tab) => { + const { id: tabId, children, selected, renderContent: Content = TabContent } = tab.props; + + return ( + + ); + }); + + const leftOverflowNavButtonHidden = + !this.state.horizontalOverflow || !this.state.tablistScrollLeft; + + const rightOverflowNavButtonHidden = + !this.state.horizontalOverflow || + this.state.tablistScrollLeft + this.state.tablistClientWidth === + this.state.tablistScrollWidth; + const classes = { + tabs: classNames(className, `${prefix}--tabs--scrollable`, { + [`${prefix}--tabs--scrollable--container`]: type === 'container', + [`${prefix}--tabs--scrollable--light`]: light, + }), + tablist: classNames(`${prefix}--tabs--scrollable__nav`), + leftOverflowButtonClasses: classNames({ + [`${prefix}--tab--overflow-nav-button`]: this.state.horizontalOverflow, + [`${prefix}--tab--overflow-nav-button--hidden`]: leftOverflowNavButtonHidden, + }), + rightOverflowButtonClasses: classNames({ + [`${prefix}--tab--overflow-nav-button`]: this.state.horizontalOverflow, + [`${prefix}--tab--overflow-nav-button--hidden`]: rightOverflowNavButtonHidden, + }), + }; + + return ( + <> +
    + + {!leftOverflowNavButtonHidden && ( +
    + )} +
    + {tabsWithProps} +
    + {!rightOverflowNavButtonHidden && ( +
    + )} + +
    + {tabContentWithProps} + + ); + } +} diff --git a/src/components/Tabs/Tabs.stories.js b/src/components/Tabs/Tabs.stories.js new file mode 100644 index 00000000..ae36373e --- /dev/null +++ b/src/components/Tabs/Tabs.stories.js @@ -0,0 +1,56 @@ +import React from 'react'; +import { storiesOf } from '@storybook/react'; +import { withKnobs } from '@storybook/addon-knobs'; +import Tabs from '../Tabs'; +import Tab from '../Tab'; +import TabsSkeleton from '../Tabs/Tabs.Skeleton'; +import { Button } from 'carbon-components-react'; + +storiesOf('Tabs', module) + .addDecorator(withKnobs) + .add( + 'default', + () => ( + + +

    Content for first tab goes here.

    +
    + +

    Content for second tab goes here.

    + +
    + +

    Content for third tab goes here.

    +
    + +

    Content for fourth tab goes here.

    +
    + Custom Label
    }> +

    Content for fifth tab goes here.

    + + + ), + { + info: { + text: ` + Tabs are used to quickly navigate between views within the same context. Create individual + Tab components for each item in the Tabs list. + `, + }, + } + ) + .add( + 'skeleton', + () => ( +
    + +
    + ), + { + info: { + text: ` + Placeholder skeleton state to use when content is loading. + `, + }, + } + ); diff --git a/src/components/Tabs/_tabs.scss b/src/components/Tabs/_tabs.scss new file mode 100644 index 00000000..725805d4 --- /dev/null +++ b/src/components/Tabs/_tabs.scss @@ -0,0 +1,19 @@ +@include exports('addons-bmrg-tabs') { + .#{$prefix}--tabs--container ~ div { + min-height: 320px; + background-color: $ui-01; + } + + .#{$prefix}--tabs--container.bx--tabs--light ~ div { + background-color: $ui-background; + } + + .tabs-story-wrapper--light { + background-color: $ui-01; + } + + .container-tabs-story-wrapper--light { + padding: 2rem 1rem; + background-color: $ui-01; + } +} diff --git a/src/components/Tabs/index.js b/src/components/Tabs/index.js new file mode 100644 index 00000000..15269f5d --- /dev/null +++ b/src/components/Tabs/index.js @@ -0,0 +1,2 @@ +export * from './Tabs.Skeleton'; +export { default } from './Tabs.js'; diff --git a/src/index.js b/src/index.js index 15b363eb..0477d06c 100755 --- a/src/index.js +++ b/src/index.js @@ -64,6 +64,9 @@ export { default as PrivacyStatement } from './components/PrivacyStatement'; export { default as ProtectedRoute } from './components/ProtectedRoute'; export { default as RadioGroup } from './components/RadioGroup'; export { default as Sidenav } from './components/Sidenav'; +export { default as Tab } from './components/Tab'; +export { default as TabContent } from './components/TabContent'; +export { default as Tabs } from './components/Tabs'; export { default as TextArea } from './components/TextArea'; export { default as TextInput } from './components/TextInput'; export { default as Toggle } from './components/Toggle'; @@ -155,9 +158,9 @@ export { StructuredListRow, StructuredListInput, StructuredListCell, - Tab, - TabContent, - Tabs, + Tab as CarbonTab, + TabContent as CarbonTabContent, + Tabs as CarbonTabs, Tag, Tile, ClickableTile, @@ -197,7 +200,7 @@ export { SelectSkeleton, SliderSkeleton, StructuredListSkeleton, - TabsSkeleton, + TabsSkeleton as CarbonTabsSkeleton, TagSkeleton, TextAreaSkeleton, TextInputSkeleton, diff --git a/src/internal/useIsomorphicEffect.js b/src/internal/useIsomorphicEffect.js new file mode 100644 index 00000000..2d8ac446 --- /dev/null +++ b/src/internal/useIsomorphicEffect.js @@ -0,0 +1,6 @@ +import { useEffect, useLayoutEffect } from 'react'; + +// useLayoutEffect on the client, useEffect on the server +const useIsomorphicEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect; + +export default useIsomorphicEffect; diff --git a/src/styles/index.scss b/src/styles/index.scss index 0f6db6e3..f39746de 100644 --- a/src/styles/index.scss +++ b/src/styles/index.scss @@ -55,6 +55,7 @@ $css--plex: true !default; @import '~carbon-components/scss/components/text-input/text-input'; @import '~carbon-components/scss/components/tooltip/tooltip'; @import '~carbon-components/scss/components/toggle/toggle'; +@import '~carbon-components/scss/components/tabs/tabs'; //only for testing @import '~carbon-components/scss/components/dropdown/dropdown'; @@ -106,6 +107,7 @@ $css--plex: true !default; @import '../components/ReportBug/reportBug'; @import '../components/Sidenav/sidenav'; @import '../components/SignOut/signOut'; +@import '../components/Tabs/tabs'; @import '../components/TextArea/textArea'; @import '../components/TextInput/textInput'; @import '../components/Toggle/toggle'; @@ -116,7 +118,7 @@ $css--plex: true !default; // Scope to modal styles so they don't overwrite other carbon styles .#{$prefix}--loading-overlay { //harcoded to the RGBA value of the ui-background variable - background-color: rgba(242,244,248,0.5); + background-color: rgba(242, 244, 248, 0.5); } .#{$prefix}--tooltip { z-index: 9999; From 9ae4e3a5c2bfcd9a2e1ba99b3fe2dceb327000b4 Mon Sep 17 00:00:00 2001 From: Matheus-de-Souza Date: Fri, 10 Sep 2021 11:20:13 -0300 Subject: [PATCH 6/7] fix: change to version beta 1.2.4-beta.8 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9f175efa..bb7aa4c2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@boomerang-io/carbon-addons-boomerang-react", "description": "Carbon Addons for Boomerang apps", - "version": "1.2.4-beta.1", + "version": "1.2.4-beta.8", "author": { "name": "Tim Bula", "email": "timrbula@gmail.com" From 89787f4268547a935f793f66187a9e9d48858c9d Mon Sep 17 00:00:00 2001 From: Lucas Grimauth Evangelista Date: Fri, 10 Sep 2021 11:58:48 -0300 Subject: [PATCH 7/7] release: 1.2.4-beta.9 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bb7aa4c2..28858a85 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@boomerang-io/carbon-addons-boomerang-react", "description": "Carbon Addons for Boomerang apps", - "version": "1.2.4-beta.8", + "version": "1.2.4-beta.9", "author": { "name": "Tim Bula", "email": "timrbula@gmail.com"