diff --git a/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap b/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap index f1bdb3ef2dcc..5ce824302da6 100644 --- a/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap +++ b/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap @@ -7960,6 +7960,31 @@ Map { }, "render": [Function], }, + "unstable_IconTab" => Object { + "$$typeof": Symbol(react.forward_ref), + "propTypes": Object { + "children": Object { + "type": "node", + }, + "className": Object { + "type": "string", + }, + "defaultOpen": Object { + "type": "bool", + }, + "enterDelayMs": Object { + "type": "number", + }, + "label": Object { + "isRequired": true, + "type": "node", + }, + "leaveDelayMs": Object { + "type": "number", + }, + }, + "render": [Function], + }, "unstable_Layer" => Object { "propTypes": Object { "as": Object { @@ -8526,6 +8551,15 @@ Map { "contained": Object { "type": "bool", }, + "iconSize": Object { + "args": Array [ + Array [ + "default", + "lg", + ], + ], + "type": "oneOf", + }, "light": Object { "type": "bool", }, diff --git a/packages/react/src/__tests__/index-test.js b/packages/react/src/__tests__/index-test.js index 5dd2afd0ca51..c09fae710b74 100644 --- a/packages/react/src/__tests__/index-test.js +++ b/packages/react/src/__tests__/index-test.js @@ -203,6 +203,7 @@ Array [ "unstable_HStack", "unstable_Heading", "unstable_IconButton", + "unstable_IconTab", "unstable_Layer", "unstable_Menu", "unstable_MenuDivider", diff --git a/packages/react/src/components/Tabs/index.js b/packages/react/src/components/Tabs/index.js index b362256537d6..6c8ba98ddf3d 100644 --- a/packages/react/src/components/Tabs/index.js +++ b/packages/react/src/components/Tabs/index.js @@ -6,7 +6,13 @@ */ import * as FeatureFlags from '@carbon/feature-flags'; -import { Tabs as TabsNext, TabPanel, TabPanels, TabList } from './next/Tabs'; +import { + Tabs as TabsNext, + TabPanel, + TabPanels, + TabList, + IconTab, +} from './next/Tabs'; import { default as TabsClassic } from './Tabs'; import { default as TabsSkeletonClassic } from './Tabs.Skeleton'; import { default as TabsSkeletonNext } from './next/Tabs.Skeleton'; @@ -19,6 +25,6 @@ const TabsSkeleton = FeatureFlags.enabled('enable-v11-release') ? TabsSkeletonNext : TabsSkeletonClassic; -export { TabsSkeleton, TabPanels, TabPanel, TabList }; +export { TabsSkeleton, TabPanels, TabPanel, TabList, IconTab }; export default Tabs; diff --git a/packages/react/src/components/Tabs/next/Tabs.js b/packages/react/src/components/Tabs/next/Tabs.js index b7bc48d452a6..11175ce1eac6 100644 --- a/packages/react/src/components/Tabs/next/Tabs.js +++ b/packages/react/src/components/Tabs/next/Tabs.js @@ -8,6 +8,7 @@ import PropTypes from 'prop-types'; import React, { useState, useRef, useEffect } from 'react'; import cx from 'classnames'; +import { Tooltip } from '../../Tooltip/next'; import { keys, match, matches } from '../../../internal/keyboard'; import { usePrefix } from '../../../internal/usePrefix'; import { useId } from '../../../internal/useId'; @@ -125,6 +126,7 @@ function TabList({ light, scrollIntoView, contained = false, + iconSize, ...rest }) { const { @@ -138,6 +140,8 @@ function TabList({ const className = cx(`${prefix}--tabs`, customClassName, { [`${prefix}--tabs--contained`]: contained, [`${prefix}--tabs--light`]: light, + [`${prefix}--tabs__icon--default`]: iconSize === 'default', + [`${prefix}--tabs__icon--lg`]: iconSize === 'lg', }); const tabs = []; @@ -242,6 +246,10 @@ TabList.propTypes = { */ contained: PropTypes.bool, + /** + * If using `IconTab`, specify the size of the icon being used. + */ + iconSize: PropTypes.oneOf(['default', 'lg']), /** * Specify whether or not to use the light component variant */ @@ -343,6 +351,71 @@ Tab.propTypes = { renderButton: PropTypes.func, }; +const IconTab = React.forwardRef(function IconTab( + { + children, + className: customClassName, + defaultOpen = false, + enterDelayMs, + leaveDelayMs, + label, + ...rest + }, + ref +) { + const prefix = usePrefix(); + + const classNames = cx(`${prefix}--tabs__nav-item--icon`, customClassName); + return ( + + + {children} + + + ); +}); + +IconTab.propTypes = { + /** + * Provide an icon to be rendered inside of `IconTab` as the visual label for Tab. + */ + children: PropTypes.node, + + /** + * Specify an optional className to be added to your Tab + */ + className: PropTypes.string, + + /** + * Specify whether the tooltip for the icon should be open when it first renders + */ + defaultOpen: PropTypes.bool, + + /** + * Specify the duration in milliseconds to delay before displaying the tooltip for the icon. + */ + enterDelayMs: PropTypes.number, + + /** + * Provide the label to be rendered inside of the Tooltip. The label will use + * `aria-labelledby` and will fully describe the child node that is provided. + * This means that if you have text in the child node it will not be + * announced to the screen reader. + */ + label: PropTypes.node.isRequired, + + /** + * Specify the duration in milliseconds to delay before hiding the tooltip + */ + leaveDelayMs: PropTypes.number, +}; + const TabPanel = React.forwardRef(function TabPanel( { children, className: customClassName, ...rest }, forwardRef @@ -407,7 +480,7 @@ TabPanels.propTypes = { children: PropTypes.node, }; -export { Tabs, Tab, TabPanel, TabPanels, TabList }; +export { Tabs, Tab, IconTab, TabPanel, TabPanels, TabList }; // TO DO: implement horizontal scroll and the following props: // leftOverflowButtonProps diff --git a/packages/react/src/components/Tabs/next/Tabs.stories.js b/packages/react/src/components/Tabs/next/Tabs.stories.js index a5ec931fc9e6..afbc1f9c8344 100644 --- a/packages/react/src/components/Tabs/next/Tabs.stories.js +++ b/packages/react/src/components/Tabs/next/Tabs.stories.js @@ -6,11 +6,18 @@ */ import React from 'react'; -import { Tabs, TabList, Tab, TabPanels, TabPanel } from './Tabs'; +import { Tabs, TabList, Tab, TabPanels, TabPanel, IconTab } from './Tabs'; import Button from '../../Button'; import TabsSkeleton from './Tabs.Skeleton'; -import { Monster20, Corn20, Bat20 } from '@carbon/icons-react'; +import { + Monster20, + Corn20, + Bat20, + Monster16, + Corn16, + Bat16, +} from '@carbon/icons-react'; import { unstable_FeatureFlags as FeatureFlags } from 'carbon-components-react'; @@ -55,18 +62,39 @@ export const Default = () => ( ); +export const Icon20Only = () => ( + + + + + + + + + + + + + + Tab Panel 1 + Tab Panel 2 + Tab Panel 3 + + +); + export const IconOnly = () => ( - - - - - - - - - - + + + + + + + + + + Tab Panel 1 diff --git a/packages/react/src/index.js b/packages/react/src/index.js index c6e9f18638cd..8ad409cec51b 100644 --- a/packages/react/src/index.js +++ b/packages/react/src/index.js @@ -247,6 +247,7 @@ export { TabPanel as unstable_TabPanel, TabPanels as unstable_TabPanels, TabList as unstable_TabList, + IconTab as unstable_IconTab, } from './components/Tabs'; export { usePrefix as unstable_usePrefix } from './internal/usePrefix'; export { diff --git a/packages/styles/scss/components/tabs/_tabs.scss b/packages/styles/scss/components/tabs/_tabs.scss index 74df93d2f43f..b57a1555c3d4 100644 --- a/packages/styles/scss/components/tabs/_tabs.scss +++ b/packages/styles/scss/components/tabs/_tabs.scss @@ -23,12 +23,15 @@ @use '../../utilities/rotate' as *; @use '../../utilities/box-shadow' as *; @use '../../utilities/component-tokens' as *; +@use '../../utilities/custom-property'; @use '../../utilities/skeleton' as *; @use '../../utilities/visually-hidden' as *; @use '../../utilities/button-reset'; @use '../../utilities/high-contrast-mode' as *; @use '../../utilities/convert' as *; +$icon-tab-size: custom-property.get-var('icon-tab-size', rem(40px)); + /// Tabs styles /// @access public /// @group tabs @@ -244,6 +247,24 @@ text-align: left; } + //----------------------------- + // Icon Item + //----------------------------- + + .#{$prefix}--tabs__nav-item--icon, + &.#{$prefix}--tabs--contained .#{$prefix}--tabs__nav-item--icon { + display: flex; + width: $icon-tab-size; + height: $icon-tab-size; + align-items: center; + justify-content: center; + padding: 0; + } + + &.#{$prefix}--tabs__icon--lg { + @include custom-property.declaration('icon-tab-size', rem(48px)); + } + //----------------------------- // Item Hover //-----------------------------