diff --git a/packages/components/src/components/breadcrumb/_breadcrumb.scss b/packages/components/src/components/breadcrumb/_breadcrumb.scss index 168a428e565c..f8df64429378 100644 --- a/packages/components/src/components/breadcrumb/_breadcrumb.scss +++ b/packages/components/src/components/breadcrumb/_breadcrumb.scss @@ -74,6 +74,72 @@ } } + // Overflow Menu overrides + .#{$prefix}--breadcrumb-item .#{$prefix}--overflow-menu { + position: relative; + width: rem(20px); + height: rem(20px); + + &:focus { + outline: 1px solid $focus; + } + + &:hover { + background: transparent; + } + + // Used to mimic link underline + &::after { + position: absolute; + bottom: 2px; + width: rem(12px); + height: 1px; + background: $hover-primary-text; + opacity: 0; + transition: opacity $duration--fast-01 motion(standard, productive); + content: ''; + } + } + + .#{$prefix}--breadcrumb-item .#{$prefix}--overflow-menu:hover::after { + opacity: 1; + } + + .#{$prefix}--breadcrumb-item + .#{$prefix}--overflow-menu.#{$prefix}--overflow-menu--open { + background: transparent; + box-shadow: none; + } + + .#{$prefix}--breadcrumb-item .#{$prefix}--overflow-menu__icon { + position: relative; + transform: translateY(4px); + fill: $link-01; + } + + .#{$prefix}--breadcrumb-item + .#{$prefix}--overflow-menu:hover + .#{$prefix}--overflow-menu__icon { + fill: $hover-primary-text; + } + + .#{$prefix}--breadcrumb-menu-options:focus { + outline: none; + } + + $caret-size: rem(7px); + .#{$prefix}--breadcrumb-menu-options.#{$prefix}--overflow-menu-options::after { + top: -$caret-size; + left: $caret-size * 2; + width: 0; + height: 0; + margin: 0 auto; + background: transparent; + border-right: $caret-size solid transparent; + border-bottom: $caret-size solid $field-01; + border-left: $caret-size solid transparent; + } + // Skeleton State .#{$prefix}--breadcrumb.#{$prefix}--skeleton .#{$prefix}--link { @include skeleton; diff --git a/packages/components/src/components/button/_mixins.scss b/packages/components/src/components/button/_mixins.scss index e2df2b53f4bf..24c5f4320357 100644 --- a/packages/components/src/components/button/_mixins.scss +++ b/packages/components/src/components/button/_mixins.scss @@ -104,6 +104,6 @@ @mixin button-padding-large { align-items: baseline; padding-top: $spacing-05; - padding-right: $layout-05; + padding-right: $spacing-10; padding-left: $spacing-05; } diff --git a/packages/components/src/components/code-snippet/_code-snippet.scss b/packages/components/src/components/code-snippet/_code-snippet.scss index 6a3c87adee73..4b749e0dacdd 100644 --- a/packages/components/src/components/code-snippet/_code-snippet.scss +++ b/packages/components/src/components/code-snippet/_code-snippet.scss @@ -197,20 +197,19 @@ } } - //closed snippet container + //collapsed snippet container .#{$prefix}--snippet--multi .#{$prefix}--snippet-container { position: relative; order: 1; min-height: rem(56px); - max-height: rem(238px); - overflow: hidden; + max-height: 100%; + overflow-y: auto; transition: max-height $duration--moderate-01 motion(standard, productive); } // expanded snippet container .#{$prefix}--snippet--multi.#{$prefix}--snippet--expand .#{$prefix}--snippet-container { - max-height: 100%; padding-bottom: $spacing-05; transition: max-height $duration--moderate-01 motion(standard, productive); } @@ -220,7 +219,7 @@ word-wrap: break-word; } - // closed pre + // collapsed pre .#{$prefix}--snippet--multi .#{$prefix}--snippet-container pre { padding-right: $carbon--spacing-08; padding-bottom: rem(24px); diff --git a/packages/components/src/components/data-table/_data-table-action.scss b/packages/components/src/components/data-table/_data-table-action.scss index 2bc60927df1e..1d6e8d0f5192 100644 --- a/packages/components/src/components/data-table/_data-table-action.scss +++ b/packages/components/src/components/data-table/_data-table-action.scss @@ -21,7 +21,7 @@ position: relative; display: flex; width: 100%; - height: $layout-04; + height: $spacing-09; overflow: hidden; background: $ui-01; } @@ -30,7 +30,7 @@ display: flex; justify-content: flex-end; width: 100%; - height: $layout-04; + height: $spacing-09; transform: translate3d(0, 0, 0); transition: transform $duration--fast-02 motion(standard, productive), clip-path $duration--fast-02 motion(standard, productive); @@ -60,15 +60,15 @@ //------------------------------------------------- .#{$prefix}--toolbar-search-container-expandable { position: relative; - width: $layout-04; - height: $layout-04; + width: $spacing-09; + height: $spacing-09; box-shadow: none; transition: flex $transition--expansion $carbon--standard-easing; } .#{$prefix}--toolbar-search-container-expandable .#{$prefix}--search { position: initial; - width: $layout-04; + width: $spacing-09; height: 100%; } @@ -76,8 +76,8 @@ .#{$prefix}--search .#{$prefix}--search-magnifier { left: 0; - width: $layout-04; - height: $layout-04; + width: $spacing-09; + height: $spacing-09; padding: $spacing-05; cursor: pointer; transition: background $duration--fast-02 motion(entrance, productive); @@ -135,8 +135,8 @@ .#{$prefix}--toolbar-search-container-expandable .#{$prefix}--search .#{$prefix}--search-close { - width: $layout-04; - height: $layout-04; + width: $spacing-09; + height: $spacing-09; &::before { top: 2px; @@ -239,8 +239,8 @@ @include button-reset; display: flex; - width: $layout-04; - height: $layout-04; + width: $spacing-09; + height: $spacing-09; padding: $spacing-05; cursor: pointer; transition: background $duration--fast-02 motion(entrance, productive); @@ -251,8 +251,8 @@ @include button-reset; display: flex; - width: $layout-04; - height: $layout-04; + width: $spacing-09; + height: $spacing-09; cursor: pointer; transition: background $duration--fast-02 motion(entrance, productive); } @@ -291,7 +291,7 @@ } .#{$prefix}--overflow-menu--data-table { - height: $layout-04; + height: $spacing-09; } //------------------------------------------------- @@ -299,8 +299,8 @@ //------------------------------------------------- .#{$prefix}--toolbar-action__icon { width: auto; - max-width: $layout-01; - height: $layout-01; + max-width: $spacing-05; + height: $spacing-05; fill: $icon-01; } @@ -310,7 +310,7 @@ .#{$prefix}--toolbar-search-container-persistent { position: relative; width: 100%; - height: $layout-04; + height: $spacing-09; opacity: 1; } @@ -333,7 +333,7 @@ .#{$prefix}--toolbar-search-container-persistent .#{$prefix}--search .#{$prefix}--search-input { - height: $layout-04; + height: $spacing-09; padding: 0 $spacing-09; border: none; } @@ -362,8 +362,8 @@ .#{$prefix}--toolbar-search-container-persistent .#{$prefix}--search .#{$prefix}--search-close { - width: $layout-04; - height: $layout-04; + width: $spacing-09; + height: $spacing-09; } .#{$prefix}--batch-actions--active ~ .#{$prefix}--toolbar-search-container, @@ -470,7 +470,7 @@ left: 0; display: block; width: rem(1px); - height: $layout-01; + height: $spacing-05; background-color: $text-04; border: none; opacity: 1; @@ -544,6 +544,10 @@ padding: $spacing-03; } + .#{$prefix}--toolbar-action.#{$prefix}--toolbar-search-container-persistent { + width: 100%; + } + //hidden .#{$prefix}--toolbar-search-container-expandable { width: rem(32px); diff --git a/packages/components/src/components/data-table/_data-table-core.scss b/packages/components/src/components/data-table/_data-table-core.scss index b5d90a4f2132..b80538a6b215 100644 --- a/packages/components/src/components/data-table/_data-table-core.scss +++ b/packages/components/src/components/data-table/_data-table-core.scss @@ -70,7 +70,7 @@ .#{$prefix}--data-table tr { width: 100%; - height: $layout-04; + height: $spacing-09; border: none; } @@ -319,7 +319,6 @@ .#{$prefix}--data-table--tall thead th.#{$prefix}--table-expand, .#{$prefix}--data-table--tall tbody td.#{$prefix}--table-expand { - width: rem(64px); height: rem(64px); } @@ -532,7 +531,6 @@ .#{$prefix}--data-table--sticky-header { display: block; - // max-height: rem(300px); overflow-y: scroll; thead, @@ -587,10 +585,6 @@ min-height: 3rem; } - // .#{$prefix}--parent-row td { - // padding: 1rem; - // } - &:not(.#{$prefix}--data-table--compact):not(.#{$prefix}--data-table--tall):not(.#{$prefix}--data-table--short) td:not(.#{$prefix}--table-column-menu):not(.#{$prefix}--table-column-checkbox) { padding-top: rem(14px); diff --git a/packages/components/src/components/data-table/_data-table-expandable.scss b/packages/components/src/components/data-table/_data-table-expandable.scss index b1d49729da5d..155bbddb2d52 100644 --- a/packages/components/src/components/data-table/_data-table-expandable.scss +++ b/packages/components/src/components/data-table/_data-table-expandable.scss @@ -70,6 +70,30 @@ background-color $duration--fast-02 motion(standard, productive); } + //compact child row padding + .#{$prefix}--data-table--compact + tr.#{$prefix}--parent-row.#{$prefix}--expandable-row + + tr[data-child-row] + td { + padding-left: 2.5rem; + } + + //short child row padding + .#{$prefix}--data-table--short + tr.#{$prefix}--parent-row.#{$prefix}--expandable-row + + tr[data-child-row] + td { + padding-left: 3rem; + } + + //tall child row padding + .#{$prefix}--data-table--tall + tr.#{$prefix}--parent-row.#{$prefix}--expandable-row + + tr[data-child-row] + td { + padding-left: 5rem; + } + tr.#{$prefix}--parent-row.#{$prefix}--expandable-row + tr[data-child-row] td @@ -198,6 +222,7 @@ width: 100%; // Account for the border in `.bx--table-expand` height: calc(100% + 1px); + padding: 0 1rem; vertical-align: inherit; } diff --git a/packages/components/src/components/data-table/_data-table-sort.scss b/packages/components/src/components/data-table/_data-table-sort.scss index 78825528bc72..2e0cb87390d1 100644 --- a/packages/components/src/components/data-table/_data-table-sort.scss +++ b/packages/components/src/components/data-table/_data-table-sort.scss @@ -18,7 +18,7 @@ // ------------------------------------- .#{$prefix}--data-table--sort th, .#{$prefix}--data-table th[aria-sort] { - height: $layout-04; + height: $spacing-09; padding: 0; border-top: none; border-bottom: none; @@ -122,7 +122,7 @@ .#{$prefix}--table-sort__icon-unsorted { width: rem(20px); - min-width: $layout-01; + min-width: $spacing-05; margin-right: $spacing-03; margin-left: $spacing-03; opacity: 0; @@ -151,7 +151,7 @@ .#{$prefix}--table-sort__icon { width: rem(20px); - min-width: $layout-01; + min-width: $spacing-05; margin-right: $spacing-03; margin-left: $spacing-03; transform: rotate(0); diff --git a/packages/components/src/components/dropdown/_dropdown.scss b/packages/components/src/components/dropdown/_dropdown.scss index 074c3f72897a..20fafa6f724f 100644 --- a/packages/components/src/components/dropdown/_dropdown.scss +++ b/packages/components/src/components/dropdown/_dropdown.scss @@ -66,9 +66,6 @@ // Menu's triggering element updated to button with Downshift v5 upgrade .#{$prefix}--dropdown .#{$prefix}--list-box__field { - @include button-reset; - - padding: 0 rem(48px) 0 rem(16px); text-align: left; // Windows, Firefox HCM Fix diff --git a/packages/components/src/components/list-box/_list-box.scss b/packages/components/src/components/list-box/_list-box.scss index 9c5033b2b6ef..ea2263425674 100644 --- a/packages/components/src/components/list-box/_list-box.scss +++ b/packages/components/src/components/list-box/_list-box.scss @@ -395,7 +395,6 @@ $list-box-menu-width: rem(300px); @include button-reset($width: false); position: absolute; - top: $carbon--spacing-03; right: $carbon--spacing-05; display: flex; align-items: center; diff --git a/packages/components/src/globals/scss/_layout.scss b/packages/components/src/globals/scss/_layout.scss index c076ca870522..7c8c36b619a4 100644 --- a/packages/components/src/globals/scss/_layout.scss +++ b/packages/components/src/globals/scss/_layout.scss @@ -12,14 +12,17 @@ /// @type Map /// @group global-layout $z-indexes: ( + // Dropdowns that render outside of a Modal should render above a Modal. + // Dropdowns below the modal will close when the Modal is opened, so + // having a higher z-index *should* not cause issues. + dropdown: 9100, modal: 9000, - overlay: 6000, - dropdown: 6000, header: 8000, + overlay: 6000, + floating: 6000, footer: 5000, hidden: - 1, - overflowHidden: - 1, - floating: 6000, + overflowHidden: - 1 ); /// @access public diff --git a/packages/components/src/globals/scss/_spacing.scss b/packages/components/src/globals/scss/_spacing.scss index acd8edb30abe..63d786f8dc63 100644 --- a/packages/components/src/globals/scss/_spacing.scss +++ b/packages/components/src/globals/scss/_spacing.scss @@ -109,40 +109,47 @@ $spacing-3xl: $spacing-baseline * 3 !default; /// @access public /// @type Number /// @group global-spacing +/// @deprecated $layout-2xs: $spacing-baseline !default; /// 24px layout in rem units /// @access public /// @type Number /// @group global-spacing +/// @deprecated $layout-xs: $spacing-baseline * 1.5 !default; /// 32px layout in rem units /// @access public /// @type Number /// @group global-spacing +/// @deprecated $layout-sm: $spacing-baseline * 2 !default; /// 48px layout in rem units /// @access public /// @type Number /// @group global-spacing +/// @deprecated $layout-md: $spacing-baseline * 3 !default; /// 64px layout in rem units /// @access public /// @type Number /// @group global-spacing +/// @deprecated $layout-lg: $spacing-baseline * 4 !default; /// 96px layout in rem units /// @access public /// @type Number /// @group global-spacing +/// @deprecated $layout-xl: $spacing-baseline * 6 !default; /// 160px layout in rem units /// @access public /// @type Number /// @group global-spacing +/// @deprecated $layout-2xl: $spacing-baseline * 10 !default; diff --git a/packages/elements/src/__tests__/__snapshots__/PublicAPI-test.js.snap b/packages/elements/src/__tests__/__snapshots__/PublicAPI-test.js.snap index 6df53f6619e1..661c33410862 100644 --- a/packages/elements/src/__tests__/__snapshots__/PublicAPI-test.js.snap +++ b/packages/elements/src/__tests__/__snapshots__/PublicAPI-test.js.snap @@ -250,6 +250,7 @@ Array [ "spacing10", "spacing11", "spacing12", + "spacing13", "styles", "support01", "support02", diff --git a/packages/layout/src/index.js b/packages/layout/src/index.js index 35e45a709224..c89f028c01db 100644 --- a/packages/layout/src/index.js +++ b/packages/layout/src/index.js @@ -102,6 +102,7 @@ export const spacing09 = miniUnits(6); export const spacing10 = miniUnits(8); export const spacing11 = miniUnits(10); export const spacing12 = miniUnits(12); +export const spacing13 = miniUnits(20); export const spacing = [ spacing01, spacing02, @@ -115,6 +116,7 @@ export const spacing = [ spacing10, spacing11, spacing12, + spacing13, ]; // Fluid spacing @@ -130,6 +132,7 @@ export const fluidSpacing = [ ]; // Layout +// Deprecated -- Remove in v11 export const layout01 = miniUnits(2); export const layout02 = miniUnits(3); export const layout03 = miniUnits(4); diff --git a/packages/layout/src/tokens.js b/packages/layout/src/tokens.js index f93068747b2d..674ef0a51240 100644 --- a/packages/layout/src/tokens.js +++ b/packages/layout/src/tokens.js @@ -19,6 +19,7 @@ export const unstable_tokens = [ 'spacing10', 'spacing11', 'spacing12', + 'spacing13', // Fluid spacing 'fluidSpacing01', @@ -27,6 +28,7 @@ export const unstable_tokens = [ 'fluidSpacing04', // Layout + // Deprecated -- Remove in v11 'layout01', 'layout02', 'layout03', diff --git a/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap b/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap index 33d423097682..563e407cdead 100644 --- a/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap +++ b/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap @@ -383,6 +383,12 @@ Map { "light": Object { "type": "bool", }, + "maxCollapsedNumberOfRows": Object { + "type": "number", + }, + "maxExpandedNumberOfRows": Object { + "type": "number", + }, "onClick": Object { "type": "func", }, @@ -3982,6 +3988,9 @@ Map { "notificationType": Object { "type": "string", }, + "onClose": Object { + "type": "func", + }, "onCloseButtonClick": Object { "type": "func", }, @@ -4048,6 +4057,9 @@ Map { "notificationType": Object { "type": "string", }, + "onClose": Object { + "type": "func", + }, "onCloseButtonClick": Object { "type": "func", }, diff --git a/packages/react/src/components/Breadcrumb/Breadcrumb-story.js b/packages/react/src/components/Breadcrumb/Breadcrumb-story.js index e748b7efe4a3..8e0616d83c64 100644 --- a/packages/react/src/components/Breadcrumb/Breadcrumb-story.js +++ b/packages/react/src/components/Breadcrumb/Breadcrumb-story.js @@ -11,6 +11,8 @@ import React from 'react'; import { action } from '@storybook/addon-actions'; import { withKnobs, boolean } from '@storybook/addon-knobs'; import { Breadcrumb, BreadcrumbItem, BreadcrumbSkeleton } from '../Breadcrumb'; +import OverflowMenu from '../OverflowMenu'; +import OverflowMenuItem from '../OverflowMenuItem'; import mdx from './Breadcrumb.mdx'; export default { @@ -38,6 +40,22 @@ export const breadcrumb = () => ( ); +export const breadcrumbWithOverflowMenu = () => ( + + + Breadcrumb 1 + + Breadcrumb 2 + + + + + + + Breadcrumb 5 + +); + export const skeleton = () => ; const props = () => ({ diff --git a/packages/react/src/components/Breadcrumb/Breadcrumb.mdx b/packages/react/src/components/Breadcrumb/Breadcrumb.mdx index 26d721bbbff3..298973851edb 100644 --- a/packages/react/src/components/Breadcrumb/Breadcrumb.mdx +++ b/packages/react/src/components/Breadcrumb/Breadcrumb.mdx @@ -28,6 +28,17 @@ responsible for displaying the page links in the hierarchy. +## Breadcrumb with `OverflowMenu` + +When space becomes limited, use an `OverflowMenu` to truncate the breadcrumbs. +The first and last two page links should be shown, but the remaining breadcrumbs +in between are condensed into an overflow menu. Breadcrumbs should never wrap +onto a second line. + + + + + ## Skeleton state You can use the `BreadcrumbSkeleton` component to render a skeleton variant of a diff --git a/packages/react/src/components/Breadcrumb/BreadcrumbItem.js b/packages/react/src/components/Breadcrumb/BreadcrumbItem.js index ce9e0c574f4f..47ccb62763a9 100644 --- a/packages/react/src/components/Breadcrumb/BreadcrumbItem.js +++ b/packages/react/src/components/Breadcrumb/BreadcrumbItem.js @@ -10,6 +10,7 @@ import React from 'react'; import cx from 'classnames'; import { settings } from 'carbon-components'; import Link from '../Link'; +import { OverflowMenuHorizontal16 } from '@carbon/icons-react'; const { prefix } = settings; @@ -33,6 +34,25 @@ const BreadcrumbItem = React.forwardRef(function BreadcrumbItem( [customClassName]: !!customClassName, }); + if ( + children.type && + children.type.displayName !== undefined && + children.type.displayName === 'OverflowMenu' + ) { + const horizontalOverflowIcon = ( + + ); + return ( +
  • + {React.cloneElement(children, { + menuOptionsClass: `${prefix}--breadcrumb-menu-options`, + menuOffset: { top: 10, left: 59 }, + renderIcon: () => horizontalOverflowIcon, + })} +
  • + ); + } + if (typeof children === 'string' && href) { return (
  • diff --git a/packages/react/src/components/CodeSnippet/CodeSnippet-story.js b/packages/react/src/components/CodeSnippet/CodeSnippet-story.js index bb2bf1df6442..3ed94de292d9 100644 --- a/packages/react/src/components/CodeSnippet/CodeSnippet-story.js +++ b/packages/react/src/components/CodeSnippet/CodeSnippet-story.js @@ -7,7 +7,13 @@ import React from 'react'; import { action } from '@storybook/addon-actions'; -import { withKnobs, boolean, text, select } from '@storybook/addon-knobs'; +import { + withKnobs, + boolean, + text, + select, + number, +} from '@storybook/addon-knobs'; import CodeSnippet from '../CodeSnippet'; import CodeSnippetSkeleton from './CodeSnippet.Skeleton'; import mdx from './CodeSnippet.mdx'; @@ -43,6 +49,14 @@ const props = () => ({ copyButtonDescription: text('Copy button title', 'Copy code snippet'), ariaLabel: text('ARIA label', 'Container label'), wrapText: boolean('Wrap text (wrapText)', true), + maxCollapsedNumberOfRows: number( + 'maxCollapsedNumberOfRows: Specify the maximum number of rows to be shown when in collapsed view', + 15 + ), + maxExpandedNumberOfRows: number( + 'maxExpandedNumberOfRows: Specify the maximum number of rows to be shown when in expanded view', + 0 + ), }); export const inline = () => ( diff --git a/packages/react/src/components/CodeSnippet/CodeSnippet.js b/packages/react/src/components/CodeSnippet/CodeSnippet.js index f451581785bf..f19bcfcd9b5c 100644 --- a/packages/react/src/components/CodeSnippet/CodeSnippet.js +++ b/packages/react/src/components/CodeSnippet/CodeSnippet.js @@ -20,8 +20,9 @@ import copy from 'copy-to-clipboard'; const { prefix } = settings; -const rowHeightInPixels = 18; -const defaultCollapsedHeightInRows = 15; +const rowHeightInPixels = 16; +const defaultMaxCollapsedNumberOfRows = 15; +const defaultMaxExpandedNumberOfRows = 0; function CodeSnippet({ className, @@ -38,6 +39,8 @@ function CodeSnippet({ showLessText, hideCopyButton, wrapText, + maxCollapsedNumberOfRows = defaultMaxCollapsedNumberOfRows, + maxExpandedNumberOfRows = defaultMaxExpandedNumberOfRows, ...rest }) { const [expandedCode, setExpandedCode] = useState(false); @@ -93,23 +96,35 @@ function CodeSnippet({ ); }, [type, getCodeRefDimensions]); - useResizeObserver({ - ref: getCodeRef(), - onResize: () => { - if (codeContentRef?.current && type === 'multi') { - const { height } = codeContentRef.current.getBoundingClientRect(); - setShouldShowMoreLessBtn( - height > defaultCollapsedHeightInRows * rowHeightInPixels - ); - } - if ( - (codeContentRef?.current && type === 'multi') || - (codeContainerRef?.current && type === 'single') - ) { - debounce(handleScroll, 200); - } + useResizeObserver( + { + ref: getCodeRef(), + onResize: () => { + if (codeContentRef?.current && type === 'multi') { + const { height } = codeContentRef.current.getBoundingClientRect(); + + if ( + maxCollapsedNumberOfRows > 0 && + (maxExpandedNumberOfRows === 0 || + maxExpandedNumberOfRows > maxCollapsedNumberOfRows) && + height > maxCollapsedNumberOfRows * rowHeightInPixels + ) { + setShouldShowMoreLessBtn(true); + } else { + setShouldShowMoreLessBtn(false); + setExpandedCode(false); + } + } + if ( + (codeContentRef?.current && type === 'multi') || + (codeContainerRef?.current && type === 'single') + ) { + debounce(handleScroll, 200); + } + }, }, - }); + [type, maxCollapsedNumberOfRows, maxExpandedNumberOfRows, rowHeightInPixels] + ); useEffect(() => { handleScroll(); @@ -156,6 +171,23 @@ function CodeSnippet({ ); } + let containerStyle = {}; + if (type === 'multi') { + if (expandedCode) { + if (maxExpandedNumberOfRows > 0) { + containerStyle.style = { + maxHeight: maxExpandedNumberOfRows * rowHeightInPixels, + }; + } + } else { + if (maxCollapsedNumberOfRows > 0) { + containerStyle.style = { + maxHeight: maxCollapsedNumberOfRows * rowHeightInPixels, + }; + } + } + } + return (
    + onScroll={(type === 'single' && handleScroll) || null} + {...containerStyle}>
     (
               
               
                 
    diff --git a/packages/react/src/components/DataTable/stories/dynamic-content/story.scss b/packages/react/src/components/DataTable/stories/dynamic-content/story.scss
    index 39309b50e817..7f42f72aec10 100644
    --- a/packages/react/src/components/DataTable/stories/dynamic-content/story.scss
    +++ b/packages/react/src/components/DataTable/stories/dynamic-content/story.scss
    @@ -1,5 +1,3 @@
     .demo-expanded-td td {
       padding-left: 4rem;
    -  padding-bottom: 1.5rem;
    -  padding-top: 1rem;
     }
    diff --git a/packages/react/src/components/DataTable/stories/expansion/DataTable-expansion-story.js b/packages/react/src/components/DataTable/stories/expansion/DataTable-expansion-story.js
    index 6c2603b17690..b1faa8eaf372 100644
    --- a/packages/react/src/components/DataTable/stories/expansion/DataTable-expansion-story.js
    +++ b/packages/react/src/components/DataTable/stories/expansion/DataTable-expansion-story.js
    @@ -6,8 +6,10 @@
      */
     
     import './DataTable-expansion-story.scss';
    +import { action } from '@storybook/addon-actions';
     
     import React from 'react';
    +import Button from '../../../Button';
     import DataTable, {
       Table,
       TableBody,
    @@ -19,6 +21,11 @@ import DataTable, {
       TableHead,
       TableHeader,
       TableRow,
    +  TableToolbar,
    +  TableToolbarAction,
    +  TableToolbarContent,
    +  TableToolbarSearch,
    +  TableToolbarMenu,
     } from '../../../DataTable';
     import { rows, headers } from '../shared';
     import mdx from '../../DataTable.mdx';
    @@ -93,6 +100,171 @@ export const Usage = () => (
       
     );
     
    +export const CompactExpansion = () => (
    +  
    +    {({
    +      rows,
    +      headers,
    +      getHeaderProps,
    +      getRowProps,
    +      getTableProps,
    +      onInputChange,
    +      getToolbarProps,
    +      getTableContainerProps,
    +    }) => (
    +      
    +        
    +          
    +            
    +            
    +               alert('Alert 1')}>
    +                Action 1
    +              
    +               alert('Alert 2')}>
    +                Action 2
    +              
    +               alert('Alert 3')}>
    +                Action 3
    +              
    +            
    +            
    +          
    +        
    +        
    +          
    +            
    +              
    +              {headers.map((header, i) => (
    +                
    +                  {header.header}
    +                
    +              ))}
    +            
    +          
    +          
    +            {rows.map((row) => (
    +              
    +                
    +                  {row.cells.map((cell) => (
    +                    {cell.value}
    +                  ))}
    +                
    +                
    +                  
    Expandable row content
    +
    Description here
    +
    +
    + ))} +
    +
    +
    + )} +
    +); + +export const ShortExpansion = () => ( + + {({ + rows, + headers, + getHeaderProps, + getRowProps, + getTableProps, + getTableContainerProps, + }) => ( + + + + + + {headers.map((header, i) => ( + + {header.header} + + ))} + + + + {rows.map((row) => ( + + + {row.cells.map((cell) => ( + {cell.value} + ))} + + +
    Expandable row content
    +
    Description here
    +
    +
    + ))} +
    +
    +
    + )} +
    +); + +export const TallExpansion = () => ( + + {({ + rows, + headers, + getHeaderProps, + getRowProps, + getTableProps, + getTableContainerProps, + }) => ( + + + + + + {headers.map((header, i) => ( + + {header.header} + + ))} + + + + {rows.map((row) => ( + + + {row.cells.map((cell) => ( + {cell.value} + ))} + + +
    Expandable row content
    +
    Description here
    +
    +
    + ))} +
    +
    +
    + )} +
    +); + export const BatchExpansion = () => ( ({ 'describes the status icon' ), hideCloseButton: boolean('Hide close button (hideCloseButton)', false), + onClose: action('onClose'), onCloseButtonClick: action('onCloseButtonClick'), }); diff --git a/packages/react/src/components/Notification/Notification-test.js b/packages/react/src/components/Notification/Notification-test.js index f03a68d95d65..66ffe3aad2f1 100644 --- a/packages/react/src/components/Notification/Notification-test.js +++ b/packages/react/src/components/Notification/Notification-test.js @@ -178,6 +178,36 @@ describe('ToastNotification', () => { expect(wrapper.children().length).toBe(0); }); + it('closes notification if `onClose` is provided', () => { + const wrapper = mount( + {}} + /> + ); + + wrapper.find('button').simulate('click'); + expect(wrapper.children().length).toBe(0); + }); + + it('keeps notification open if `onClose` returns false', () => { + const wrapper = mount( + false} + /> + ); + + wrapper.find('button').simulate('click'); + expect(wrapper.children().length).not.toBe(0); + }); + it('renders null when open state is false', () => { const wrapper = mount( { expect(wrapper.children().length).toBe(0); }); + it('closes notification if `onClose` is provided', () => { + const wrapper = mount( + {}} + /> + ); + + wrapper.find('button').simulate('click'); + expect(wrapper.children().length).toBe(0); + }); + + it('keeps notification open if `onClose` returns false', () => { + const wrapper = mount( + false} + /> + ); + + wrapper.find('button').simulate('click'); + expect(wrapper.children().length).not.toBe(0); + }); + it('renders null when open state is false', () => { const wrapper = mount( { + if (!onClose || onClose(evt) !== false) { + setIsOpen(false); + } + }; + function handleCloseButtonClick(event) { - setIsOpen(false); onCloseButtonClick(event); + handleClose(event); } + const savedOnClose = useRef(onClose); + + useEffect(() => { + savedOnClose.current = onClose; + }); + useEffect(() => { if (!timeout) { return; } - const timeoutId = window.setTimeout(() => { + const timeoutId = window.setTimeout((event) => { setIsOpen(false); - onCloseButtonClick(event); - }, timeout); - + if (savedOnClose.current) { + savedOnClose.current(event); + } + }); return () => { window.clearTimeout(timeoutId); }; - }, [onCloseButtonClick, timeout]); + }, [timeout]); if (!isOpen) { return null; @@ -368,6 +382,11 @@ ToastNotification.propTypes = { /** * Provide a function that is called when menu is closed */ + onClose: PropTypes.func, + + /** + * Provide a function that is called when the close button is clicked + */ onCloseButtonClick: PropTypes.func, /** @@ -412,6 +431,7 @@ export function InlineNotification({ actions, role, notificationType, + onClose, onCloseButtonClick, iconDescription, statusIconDescription, @@ -432,9 +452,15 @@ export function InlineNotification({ [`${prefix}--inline-notification--hide-close-button`]: hideCloseButton, }); + const handleClose = (evt) => { + if (!onClose || onClose(evt) !== false) { + setIsOpen(false); + } + }; + function handleCloseButtonClick(event) { - setIsOpen(false); onCloseButtonClick(event); + handleClose(event); } if (!isOpen) { @@ -520,6 +546,11 @@ InlineNotification.propTypes = { /** * Provide a function that is called when menu is closed */ + onClose: PropTypes.func, + + /** + * Provide a function that is called when the close button is clicked + */ onCloseButtonClick: PropTypes.func, /** diff --git a/packages/react/src/components/UIShell/SideNav.js b/packages/react/src/components/UIShell/SideNav.js index 12a88afe2057..df66e88cb090 100644 --- a/packages/react/src/components/UIShell/SideNav.js +++ b/packages/react/src/components/UIShell/SideNav.js @@ -10,6 +10,7 @@ import { settings } from 'carbon-components'; import cx from 'classnames'; import PropTypes from 'prop-types'; import { AriaLabelPropType } from '../../prop-types/AriaPropTypes'; +import { CARBON_SIDENAV_ITEMS } from './_utils'; // TO-DO: comment back in when footer is added for rails // import SideNavFooter from './SideNavFooter'; @@ -87,8 +88,13 @@ const SideNav = React.forwardRef(function SideNav(props, ref) { let currentExpansionState = controlled ? expandedViaHoverState || expanded : expanded; + // avoid spreading `isSideNavExpanded` to non-Carbon UI Shell children return React.cloneElement(child, { - isSideNavExpanded: currentExpansionState, + ...(CARBON_SIDENAV_ITEMS.includes(child.type?.displayName) + ? { + isSideNavExpanded: currentExpansionState, + } + : {}), }); }); } diff --git a/packages/react/src/components/UIShell/SideNavItems.js b/packages/react/src/components/UIShell/SideNavItems.js index b8998e308f89..465c929c8a17 100644 --- a/packages/react/src/components/UIShell/SideNavItems.js +++ b/packages/react/src/components/UIShell/SideNavItems.js @@ -9,6 +9,7 @@ import { settings } from 'carbon-components'; import cx from 'classnames'; import PropTypes from 'prop-types'; import React from 'react'; +import { CARBON_SIDENAV_ITEMS } from './_utils'; const { prefix } = settings; @@ -20,7 +21,14 @@ const SideNavItems = ({ const className = cx([`${prefix}--side-nav__items`], customClassName); const childrenWithExpandedState = React.Children.map(children, (child) => { if (React.isValidElement(child)) { - return React.cloneElement(child, { isSideNavExpanded }); + // avoid spreading `isSideNavExpanded` to non-Carbon UI Shell children + return React.cloneElement(child, { + ...(CARBON_SIDENAV_ITEMS.includes(child.type?.displayName) + ? { + isSideNavExpanded, + } + : {}), + }); } }); return
      {childrenWithExpandedState}
    ; diff --git a/packages/react/src/components/UIShell/SideNavLink.js b/packages/react/src/components/UIShell/SideNavLink.js index 2d0471f47f05..9fcd9e7f2330 100644 --- a/packages/react/src/components/UIShell/SideNavLink.js +++ b/packages/react/src/components/UIShell/SideNavLink.js @@ -23,8 +23,6 @@ const SideNavLink = React.forwardRef(function SideNavLink( renderIcon: IconElement, isActive, large, - // eslint-disable-next-line no-unused-vars - isSideNavExpanded, ...rest }, ref diff --git a/packages/react/src/components/UIShell/_utils.js b/packages/react/src/components/UIShell/_utils.js new file mode 100644 index 000000000000..8953a0806a96 --- /dev/null +++ b/packages/react/src/components/UIShell/_utils.js @@ -0,0 +1,6 @@ +export const CARBON_SIDENAV_ITEMS = [ + 'SideNavFooter', + 'SideNavHeader', + 'SideNavItems', + 'SideNavMenu', +]; diff --git a/packages/themes/src/g10.js b/packages/themes/src/g10.js index 0cb2aaf544d4..334c2549334b 100644 --- a/packages/themes/src/g10.js +++ b/packages/themes/src/g10.js @@ -184,12 +184,14 @@ export { spacing10, spacing11, spacing12, + spacing13, // Fluid spacing fluidSpacing01, fluidSpacing02, fluidSpacing03, fluidSpacing04, // Layout + // Deprecated -- Remove in v11 layout01, layout02, layout03, diff --git a/packages/themes/src/g100.js b/packages/themes/src/g100.js index 3f77c8d2fc07..7a89d9c9a287 100644 --- a/packages/themes/src/g100.js +++ b/packages/themes/src/g100.js @@ -183,12 +183,14 @@ export { spacing10, spacing11, spacing12, + spacing13, // Fluid spacing fluidSpacing01, fluidSpacing02, fluidSpacing03, fluidSpacing04, // Layout + // Deprecated -- Remove in v11 layout01, layout02, layout03, diff --git a/packages/themes/src/g90.js b/packages/themes/src/g90.js index 9d75d7c23303..10779238cb5a 100644 --- a/packages/themes/src/g90.js +++ b/packages/themes/src/g90.js @@ -185,12 +185,14 @@ export { spacing10, spacing11, spacing12, + spacing13, // Fluid spacing fluidSpacing01, fluidSpacing02, fluidSpacing03, fluidSpacing04, // Layout + // Deprecated -- Remove in v11 layout01, layout02, layout03, diff --git a/packages/themes/src/v9.js b/packages/themes/src/v9.js index cd00b8bfe762..a9c901147d29 100644 --- a/packages/themes/src/v9.js +++ b/packages/themes/src/v9.js @@ -148,12 +148,14 @@ export { spacing10, spacing11, spacing12, + spacing13, // Fluid spacing fluidSpacing01, fluidSpacing02, fluidSpacing03, fluidSpacing04, // Layout + // Deprecated -- Remove in v11 layout01, layout02, layout03, diff --git a/packages/themes/src/white.js b/packages/themes/src/white.js index 784189d81e5c..c8b0f5adbd40 100644 --- a/packages/themes/src/white.js +++ b/packages/themes/src/white.js @@ -187,12 +187,14 @@ export { spacing10, spacing11, spacing12, + spacing13, // Fluid spacing fluidSpacing01, fluidSpacing02, fluidSpacing03, fluidSpacing04, // Layout + // Deprecated -- Remove in v11 layout01, layout02, layout03,