diff --git a/packages/react/src/components/Tabs/Tabs.tsx b/packages/react/src/components/Tabs/Tabs.tsx
index c441bbe3d3ca..fcecf77d7b7f 100644
--- a/packages/react/src/components/Tabs/Tabs.tsx
+++ b/packages/react/src/components/Tabs/Tabs.tsx
@@ -12,6 +12,7 @@ import debounce from 'lodash.debounce';
import PropTypes from 'prop-types';
import React, {
useCallback,
+ useLayoutEffect,
useState,
useRef,
useEffect,
@@ -26,6 +27,7 @@ import React, {
type ReactHTML,
type ElementType,
} from 'react';
+import { Grid } from '../Grid';
import { isElement } from 'react-is';
import { Tooltip } from '../Tooltip';
import { useControllableState } from '../../internal/useControllableState';
@@ -43,6 +45,8 @@ import { useEvent } from '../../internal/useEvent';
import { useMatchMedia } from '../../internal/useMatchMedia';
import { Text } from '../Text';
+const verticalTabHeight = 64;
+
// Used to manage the overall state of the Tabs
type TabsContextType = {
baseId: string;
@@ -76,6 +80,7 @@ const TabContext = React.createContext<{
});
const lgMediaQuery = `(min-width: ${breakpoints.lg.width})`;
+const smMediaQuery = `(max-width: ${breakpoints.md.width})`;
// Used to keep track of position in a list of tab panels
const TabPanelContext = React.createContext
(0);
@@ -199,6 +204,101 @@ Tabs.propTypes = {
selectedIndex: PropTypes.number,
};
+export interface TabsVerticalProps {
+ /**
+ * Provide child elements to be rendered inside the `TabsVertical`.
+ * These elements should render either `TabsListVertical` or `TabsPanels`
+ */
+ children?: ReactNode;
+
+ /**
+ * Specify which content tab should be initially selected when the component
+ * is first rendered
+ */
+ defaultSelectedIndex?: number;
+
+ /**
+ * Option to set a height style only if using vertical variation
+ */
+ height?: string;
+
+ /**
+ * Provide an optional function which is called
+ * whenever the state of the `Tabs` changes
+ */
+ onChange?(state: { selectedIndex: number }): void;
+
+ /**
+ * Control which content panel is currently selected. This puts the component
+ * in a controlled mode and should be used along with `onChange`
+ */
+ selectedIndex?: number;
+}
+
+function TabsVertical({
+ children,
+ height,
+ defaultSelectedIndex = 0,
+ onChange,
+ selectedIndex: controlledSelectedIndex,
+ ...rest
+}: TabsVerticalProps) {
+ const [selectedIndex, setSelectedIndex] = useControllableState({
+ value: controlledSelectedIndex,
+ defaultValue: defaultSelectedIndex,
+ onChange: (value) => onChange?.({ selectedIndex: value }),
+ });
+
+ const props = {
+ ...rest,
+ selectedIndex,
+ onChange: ({ selectedIndex }) => setSelectedIndex(selectedIndex),
+ };
+
+ const isSm = useMatchMedia(smMediaQuery);
+
+ if (!isSm) {
+ return (
+ // eslint-disable-next-line react/forbid-component-props
+
+ {children}
+
+ );
+ }
+ return {children};
+}
+
+TabsVertical.propTypes = {
+ /**
+ * Provide child elements to be rendered inside the `TabsVertical`.
+ * These elements should render either `TabsListVertical` or `TabsPanels`
+ */
+ children: PropTypes.node,
+
+ /**
+ * Specify which content tab should be initially selected when the component
+ * is first rendered
+ */
+ defaultSelectedIndex: PropTypes.number,
+
+ /**
+ * Option to set a height style only if using vertical variation
+ */
+ height: PropTypes.string,
+
+ /**
+ * Provide an optional function which is called whenever the state of the
+ * `Tabs` changes
+ */
+ onChange: PropTypes.func,
+
+ /**
+ * Control which content panel is currently selected. This puts the component
+ * in a controlled mode and should be used along with `onChange`
+ */
+ selectedIndex: PropTypes.number,
+};
+
/**
* Get the next index for a given keyboard event
* given a count of the total items and the current index
@@ -226,6 +326,33 @@ function getNextIndex(
}
}
+/**
+ * Get the next index for a given keyboard event
+ * given a count of the total items and the current index
+ */
+function getNextIndexVertical(
+ event: SyntheticEvent,
+ total: number,
+ index: number
+): number {
+ switch (true) {
+ case match(event, keys.ArrowDown):
+ return (index + 1) % total;
+
+ case match(event, keys.ArrowUp):
+ return (total + index - 1) % total;
+
+ case match(event, keys.Home):
+ return 0;
+
+ case match(event, keys.End):
+ return total - 1;
+
+ default:
+ return index;
+ }
+}
+
/**
* TabList
*/
@@ -684,6 +811,236 @@ TabList.propTypes = {
scrollIntoView: PropTypes.bool,
};
+/**
+ * TabListVertical
+ */
+
+export interface TabListVerticalProps extends DivAttributes {
+ /**
+ * Specify whether the content tab should be activated automatically or
+ * manually
+ */
+ activation?: 'automatic' | 'manual';
+
+ /**
+ * Provide an accessible label to be read when a user interacts with this
+ * component
+ */
+ 'aria-label': string;
+
+ /**
+ * Provide child elements to be rendered inside `ContentTabs`.
+ * These elements should render a `ContentTab`
+ */
+ children?: ReactNode;
+
+ /**
+ * Specify an optional className to be added to the container node
+ */
+ className?: string;
+
+ /**
+ * Choose whether to automatically scroll to newly selected tabs
+ * on component rerender
+ */
+ scrollIntoView?: boolean;
+}
+// type TabElement = HTMLElement & { disabled?: boolean };
+
+function TabListVertical({
+ activation = 'automatic',
+ 'aria-label': label,
+ children,
+ className: customClassName,
+ scrollIntoView,
+ ...rest
+}: TabListVerticalProps) {
+ const { activeIndex, selectedIndex, setSelectedIndex, setActiveIndex } =
+ React.useContext(TabsContext);
+ const prefix = usePrefix();
+ const ref = useRef(null);
+ const [isOverflowingBottom, setIsOverflowingBottom] = useState(false);
+ const [isOverflowingTop, setIsOverflowingTop] = useState(false);
+
+ const isSm = useMatchMedia(smMediaQuery);
+
+ const className = cx(
+ `${prefix}--tabs`,
+ `${prefix}--tabs--vertical`,
+ `${prefix}--tabs--contained`,
+ customClassName
+ );
+
+ const tabs = useRef([]);
+
+ function onKeyDown(event: KeyboardEvent) {
+ if (matches(event, [keys.ArrowDown, keys.ArrowUp, keys.Home, keys.End])) {
+ event.preventDefault();
+
+ const filtredTabs = tabs.current.filter((tab) => tab !== null);
+
+ const activeTabs: TabElement[] = filtredTabs.filter(
+ (tab) => !tab.disabled
+ );
+
+ const currentIndex = activeTabs.indexOf(
+ tabs.current[activation === 'automatic' ? selectedIndex : activeIndex]
+ );
+ const nextIndex = tabs.current.indexOf(
+ activeTabs[getNextIndexVertical(event, activeTabs.length, currentIndex)]
+ );
+
+ if (activation === 'automatic') {
+ setSelectedIndex(nextIndex);
+ } else if (activation === 'manual') {
+ setActiveIndex(nextIndex);
+ }
+ tabs.current[nextIndex]?.focus();
+ }
+ }
+
+ useEffectOnce(() => {
+ if (tabs.current[selectedIndex]?.disabled) {
+ const activeTabs = tabs.current.filter((tab) => {
+ return !tab.disabled;
+ });
+
+ if (activeTabs.length > 0) {
+ const tab = activeTabs[0];
+ setSelectedIndex(tabs.current.indexOf(tab));
+ }
+ }
+ });
+
+ useEffect(() => {
+ function handler() {
+ const containerHeight = ref.current?.offsetHeight;
+ const containerTop = ref.current?.getBoundingClientRect().top;
+ const selectedPositionTop =
+ tabs.current[selectedIndex]?.getBoundingClientRect().top;
+
+ const halfTabHeight = verticalTabHeight / 2;
+ if (containerTop && containerHeight) {
+ // scrolls so selected tab is in view
+ if (
+ selectedPositionTop - halfTabHeight < containerTop ||
+ selectedPositionTop -
+ containerTop +
+ verticalTabHeight +
+ halfTabHeight >
+ containerHeight
+ ) {
+ ref.current.scrollTo({
+ top: (selectedIndex - 1) * verticalTabHeight,
+ behavior: 'smooth',
+ });
+ }
+ }
+ }
+
+ window.addEventListener('resize', handler);
+
+ handler();
+
+ return () => {
+ window.removeEventListener('resize', handler);
+ };
+ }, [selectedIndex, scrollIntoView]);
+
+ useEffect(() => {
+ const element = ref.current;
+ if (!element) {
+ return;
+ }
+
+ const handler = () => {
+ const halfTabHeight = verticalTabHeight / 2;
+ setIsOverflowingBottom(
+ element.scrollTop + element.clientHeight + halfTabHeight <=
+ element.scrollHeight
+ );
+ setIsOverflowingTop(element.scrollTop > halfTabHeight);
+ };
+
+ const resizeObserver = new ResizeObserver(() => handler());
+ resizeObserver.observe(element);
+ element.addEventListener('scroll', handler);
+
+ return () => {
+ resizeObserver.disconnect();
+ element.removeEventListener('scroll', handler);
+ };
+ });
+
+ if (isSm) {
+ return (
+
+ {children}
+
+ );
+ }
+
+ return (
+
+ {isOverflowingTop && (
+
+ )}
+ {/* eslint-disable-next-line jsx-a11y/interactive-supports-focus */}
+
+ {React.Children.map(children, (child, index) => {
+ return !isElement(child) ? null : (
+
+ {React.cloneElement(child, {
+ ref: (node) => {
+ tabs.current[index] = node;
+ },
+ })}
+
+ );
+ })}
+
+ {isOverflowingBottom && (
+
+ )}
+
+ );
+}
+
+TabListVertical.propTypes = {
+ /**
+ * Specify whether the content tab should be activated automatically or
+ * manually
+ */
+ activation: PropTypes.oneOf(['automatic', 'manual']),
+
+ /**
+ * Provide an accessible label to be read when a user interacts with this
+ * component
+ */
+ 'aria-label': PropTypes.string.isRequired,
+
+ /**
+ * Provide child elements to be rendered inside `ContentTabs`.
+ * These elements should render a `ContentTab`
+ */
+ children: PropTypes.node,
+
+ /**
+ * Specify an optional className to be added to the container node
+ */
+ className: PropTypes.string,
+};
+
/**
* Helper function to set up the behavior when a button is "long pressed".
* This function will take a ref to the tablist, a direction, and a setter
@@ -819,6 +1176,13 @@ const Tab = forwardRef(function Tab(
const [ignoreHover, setIgnoreHover] = useState(false);
const id = `${baseId}-tab-${index}`;
const panelId = `${baseId}-tabpanel-${index}`;
+ const [isEllipsisApplied, setIsEllipsisApplied] = useState(false);
+
+ const isEllipsisActive = (element: any) => {
+ setIsEllipsisApplied(element.offsetHeight < element.scrollHeight);
+ return element.offsetHeight < element.scrollHeight;
+ };
+
const className = cx(
`${prefix}--tabs__nav-item`,
`${prefix}--tabs__nav-link`,
@@ -850,6 +1214,21 @@ const Tab = forwardRef(function Tab(
useEvent(dismissIconRef, 'mouseover', onDismissIconMouseEnter);
useEvent(dismissIconRef, 'mouseleave', onDismissIconMouseLeave);
+ useLayoutEffect(() => {
+ function handler() {
+ const elementTabId = document.getElementById(`${id}`);
+ const newElement = elementTabId?.getElementsByClassName(
+ `${prefix}--tabs__nav-item-label`
+ )[0];
+ isEllipsisActive(newElement);
+ }
+ handler();
+ window.addEventListener('resize', handler);
+ return () => {
+ window.removeEventListener('resize', handler);
+ };
+ }, [prefix, id]);
+
const handleClose = (evt) => {
evt.stopPropagation();
onTabCloseRequest?.(index);
@@ -924,6 +1303,55 @@ const Tab = forwardRef(function Tab(
const hasIcon = Icon ?? dismissable;
+ // should only happen for vertical variation, so no dissimisamble icon is needed here
+ if (isEllipsisApplied) {
+ return (
+ false}
+ closeOnActivation>
+ {
+ if (disabled) {
+ return;
+ }
+ setSelectedIndex(index);
+ onClick?.(evt);
+ }}
+ onKeyDown={handleKeyDown}
+ tabIndex={selectedIndex === index ? '0' : '-1'}
+ type="button">
+
+
+ {children}
+
+
+ {hasSecondaryLabel && secondaryLabel && (
+
+ {secondaryLabel}
+
+ )}
+
+
+ );
+ }
+
return (
<>
([]);
+ const hiddenStates = useRef([]);
+
+ useLayoutEffect(() => {
+ const tabContainer = refs.current[0]?.previousElementSibling;
+ const isVertical = tabContainer?.classList.contains(
+ `${prefix}--tabs--vertical`
+ );
+ const parentHasHeight = tabContainer?.parentElement?.style.height;
+
+ // Should only apply same height to vertical Tab Panels without a given height
+ if (isVertical && !parentHasHeight) {
+ hiddenStates.current = refs.current.map((ref) => ref?.hidden || false);
+
+ // un-hide hidden Tab Panels to get heights
+ refs.current.forEach((ref) => {
+ if (ref) {
+ ref.hidden = false;
+ }
+ });
+
+ // set max height to TabList
+ const heights = refs.current.map((ref) => ref?.offsetHeight || 0);
+ const max = Math.max(...heights);
+ (tabContainer as HTMLElement).style.height = max + 'px';
+
+ // re-hide hidden Tab Panels
+ refs.current.forEach((ref, index) => {
+ if (ref) {
+ ref.hidden = hiddenStates.current[index];
+ }
+ });
+ }
+ });
+
return (
<>
{React.Children.map(children, (child, index) => {
return (
- {child}
+ {React.cloneElement(child as React.ReactElement, {
+ ref: (element: HTMLDivElement) => {
+ refs.current[index] = element;
+ },
+ })}
);
})}
@@ -1269,4 +1737,13 @@ TabPanels.propTypes = {
children: PropTypes.node,
};
-export { Tabs, Tab, IconTab, TabPanel, TabPanels, TabList };
+export {
+ Tabs,
+ TabsVertical,
+ Tab,
+ IconTab,
+ TabPanel,
+ TabPanels,
+ TabList,
+ TabListVertical,
+};
diff --git a/packages/react/src/components/TextArea/TextArea.tsx b/packages/react/src/components/TextArea/TextArea.tsx
index 81236abd643b..6785ad74d60f 100644
--- a/packages/react/src/components/TextArea/TextArea.tsx
+++ b/packages/react/src/components/TextArea/TextArea.tsx
@@ -233,7 +233,10 @@ const TextArea = React.forwardRef((props: TextAreaProps, forwardRef) => {
if (!disabled && enableCounter && counterMode === 'word') {
const key = evt.which;
- if (maxCount && textCount >= maxCount && key === 32) {
+ if (
+ (maxCount && textCount >= maxCount && key === 32) ||
+ (maxCount && textCount >= maxCount && key === 13)
+ ) {
evt.preventDefault();
}
}
@@ -424,6 +427,7 @@ const TextArea = React.forwardRef((props: TextAreaProps, forwardRef) => {
{...other}
{...textareaProps}
placeholder={placeholder}
+ aria-readonly={other.readOnly ? true : false}
className={textareaClasses}
aria-invalid={invalid}
aria-describedby={ariaDescribedBy}
diff --git a/packages/styles/scss/components/modal/_modal.scss b/packages/styles/scss/components/modal/_modal.scss
index bd79cccfd024..6a7785de785d 100644
--- a/packages/styles/scss/components/modal/_modal.scss
+++ b/packages/styles/scss/components/modal/_modal.scss
@@ -17,6 +17,7 @@
@use '../../utilities/component-reset';
@use '../../utilities/focus-outline' as *;
@use '../../utilities/high-contrast-mode' as *;
+@use '../../utilities/update_fields_on_layer' as *;
@use '../../utilities/z-index' as *;
/// Modal styles
@@ -51,57 +52,7 @@
}
}
- // Fluid inputs
- .#{$prefix}--text-input--fluid .#{$prefix}--text-input,
- .#{$prefix}--text-area--fluid .#{$prefix}--text-area__wrapper,
- .#{$prefix}--text-area--fluid .#{$prefix}--text-area,
- .#{$prefix}--search--fluid .#{$prefix}--search-input,
- .#{$prefix}--select--fluid .#{$prefix}--select-input,
- .#{$prefix}--text-area--fluid
- .#{$prefix}--text-area__wrapper[data-invalid]
- .#{$prefix}--text-area__divider
- + .#{$prefix}--form-requirement,
- .#{$prefix}--list-box__wrapper--fluid .#{$prefix}--list-box,
- .#{$prefix}--list-box__wrapper--fluid.#{$prefix}--list-box__wrapper,
- .#{$prefix}--number-input--fluid input[type='number'],
- .#{$prefix}--number-input--fluid .#{$prefix}--number__control-btn::before,
- .#{$prefix}--number-input--fluid .#{$prefix}--number__control-btn::after,
- .#{$prefix}--date-picker--fluid
- .c#{$prefix}ds--date-picker-input__wrapper
- .#{$prefix}--date-picker__input {
- background-color: $field-01;
- }
-
- // Override to align layer token with field
- .#{$prefix}--list-box__wrapper--fluid .#{$prefix}--list-box__menu {
- background-color: $layer-01;
- }
-
- .#{$prefix}--list-box__menu-item:hover {
- background-color: $layer-hover-02;
- }
-
- .#{$prefix}--list-box__menu-item--active {
- background-color: $layer-selected-02;
- }
-
- .#{$prefix}--list-box__menu-item--active:hover {
- background-color: $layer-selected-hover-02;
- }
-
- .#{$prefix}--number-input--fluid
- .#{$prefix}--number__control-btn:hover::before,
- .#{$prefix}--number-input--fluid
- .#{$prefix}--number__control-btn:hover::after {
- background-color: $field-hover;
- }
-
- .#{$prefix}--number-input--fluid
- .#{$prefix}--number__control-btn:focus::before,
- .#{$prefix}--number-input--fluid
- .#{$prefix}--number__control-btn:focus::after {
- border-inline-start: 2px solid $focus;
- }
+ @include update_fields_on_layer;
}
.#{$prefix}--modal.is-visible .#{$prefix}--modal-container {
diff --git a/packages/styles/scss/components/tabs/_tabs.scss b/packages/styles/scss/components/tabs/_tabs.scss
index 52cc8d8acde0..b1855a2f0d16 100644
--- a/packages/styles/scss/components/tabs/_tabs.scss
+++ b/packages/styles/scss/components/tabs/_tabs.scss
@@ -20,6 +20,7 @@
@use '../../spacing' as *;
@use '../../motion' as *;
@use '../../layer' as *;
+@use '../../layer/layer-tokens';
@use '../../utilities/focus-outline' as *;
@use '../../utilities/rotate' as *;
@use '../../utilities/box-shadow' as *;
@@ -32,6 +33,7 @@
@use '../../utilities/high-contrast-mode' as *;
@use '../../utilities/convert';
@use '../../utilities/layout';
+@use '../../utilities/update_fields_on_layer' as *;
/// Tabs styles
/// @access public
@@ -59,6 +61,7 @@
inline-size: 100%;
max-block-size: custom-property.get-var('layout-size-height-xl');
min-block-size: layout.size('height');
+ overflow-x: hidden;
&.#{$prefix}--tabs--contained {
@include layout.use('size', $min: 'sm', $max: 'xl', $default: 'lg');
@@ -77,6 +80,42 @@
}
}
+ &.#{$prefix}--tabs--vertical {
+ background: $layer;
+ box-shadow: inset -1px 0 $border-subtle;
+ grid-column: span 2;
+ max-block-size: none;
+
+ @include breakpoint(lg) {
+ grid-column: span 4;
+ }
+
+ .#{$prefix}--tab--list {
+ flex-direction: column;
+ inline-size: 100%;
+ overflow-x: visible;
+ overflow-y: auto;
+ }
+
+ .#{$prefix}--tab--list-gradient_bottom {
+ position: absolute;
+ background: linear-gradient(to bottom, transparent, $layer);
+ block-size: $spacing-10;
+ inset-block-end: 0;
+ inset-inline: 0;
+ pointer-events: none;
+ }
+
+ .#{$prefix}--tab--list-gradient_top {
+ position: absolute;
+ background: linear-gradient(to top, transparent, $layer);
+ block-size: $spacing-10;
+ inset-block-start: 0;
+ inset-inline: 0;
+ pointer-events: none;
+ }
+ }
+
.#{$prefix}--tabs__nav {
@include component-reset.reset;
@@ -283,7 +322,18 @@
margin-inline-start: 0;
}
- &.#{$prefix}--tabs--contained
+ &.#{$prefix}--tabs--vertical .#{$prefix}--tabs__nav-item {
+ flex: none;
+ background-color: $layer-01;
+ block-size: $spacing-10;
+ border-block-end: 1px solid $border-subtle;
+ border-inline-end: 1px solid $border-subtle;
+ box-shadow: inset 3px 0 0 0 $border-subtle;
+ inline-size: 100%;
+ margin-inline-start: 0;
+ }
+
+ &.#{$prefix}--tabs--contained:not(.#{$prefix}--tabs--vertical)
.#{$prefix}--tabs__nav-item--selected
+ div
+ .#{$prefix}--tabs__nav-item {
@@ -432,7 +482,8 @@
}
}
- &.#{$prefix}--tabs--contained .#{$prefix}--tabs__nav-link {
+ &.#{$prefix}--tabs--contained:not(.#{$prefix}--tabs--vertical)
+ .#{$prefix}--tabs__nav-link {
border-block-end: 0;
padding-inline: layout.density('padding-inline');
}
@@ -449,6 +500,11 @@
min-block-size: convert.to-rem(16px);
}
+ &.#{$prefix}--tabs--vertical:not(.#{$prefix}--tabs--tall)
+ .#{$prefix}--tabs__nav-item-label {
+ line-height: var(--cds-body-compact-01-line-height);
+ }
+
//-----------------------------
// Icon Item
//-----------------------------
@@ -492,6 +548,16 @@
color: $text-primary;
}
+ &.#{$prefix}--tabs--vertical
+ .#{$prefix}--tabs__nav-item:not(
+ .#{$prefix}--tabs__nav-item--selected
+ ):not(.#{$prefix}--tabs__nav-item--disabled):not(
+ .#{$prefix}--tabs__nav-item--hover-off
+ ):hover {
+ background-color: $layer-hover;
+ box-shadow: inset 3px 0 0 0 $border-strong;
+ }
+
//-----------------------------
// Item Selected
//-----------------------------
@@ -512,6 +578,13 @@
box-shadow: inset 0 2px 0 0 $border-interactive;
}
+ &.#{$prefix}--tabs--vertical
+ .#{$prefix}--tabs__nav-item.#{$prefix}--tabs__nav-item--selected {
+ border-inline: none;
+ // Draws the border without affecting the inner-content
+ box-shadow: inset 3px 0 0 0 $border-interactive;
+ }
+
&.#{$prefix}--tabs--contained .#{$prefix}--tabs__nav-item--selected,
.#{$prefix}--tabs__nav-item--selected,
.#{$prefix}--tabs__nav-item--selected:focus
@@ -572,6 +645,14 @@
background-color: button.$button-disabled;
}
+ &.#{$prefix}--tabs--vertical
+ .#{$prefix}--tabs__nav-item.#{$prefix}--tabs__nav-item--disabled,
+ &.#{$prefix}--tabs--vertical
+ .#{$prefix}--tabs__nav-item.#{$prefix}--tabs__nav-item--disabled:hover {
+ background-color: $layer;
+ border-block-end: 1px solid $border-subtle;
+ }
+
.#{$prefix}--tabs__nav-item--disabled:focus,
.#{$prefix}--tabs__nav-item--disabled:active {
border-block-end: $tab-underline-disabled;
@@ -600,7 +681,8 @@
border-block-end-color: $border-subtle;
}
- &.#{$prefix}--tabs--contained .#{$prefix}--tabs__nav-item--disabled {
+ &.#{$prefix}--tabs--contained:not(.#{$prefix}--tabs--vertical)
+ .#{$prefix}--tabs__nav-item--disabled {
border-block-end: none;
color: $text-on-color-disabled;
}
@@ -618,6 +700,21 @@
.#{$prefix}--tabs--contained ~ .#{$prefix}--tab-content {
background: $layer;
+
+ > * {
+ @include layer-tokens.emit-layer-tokens(2);
+ }
+ }
+
+ .#{$prefix}--tabs--vertical ~ .#{$prefix}--tab-content {
+ @include update_fields_on_layer;
+
+ grid-column: 3/-1;
+ overflow-y: auto;
+
+ @include breakpoint(lg) {
+ grid-column: 5/-1;
+ }
}
.#{$prefix}--tab-content--interactive {
@@ -690,3 +787,16 @@
}
}
}
+
+.#{$prefix}--tabs.#{$prefix}--tabs--vertical {
+ .#{$prefix}--tabs__nav-link {
+ .#{$prefix}--tabs__nav-item-label {
+ display: -webkit-box;
+ overflow: hidden;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+ text-overflow: ellipsis;
+ white-space: normal;
+ }
+ }
+}
diff --git a/packages/styles/scss/utilities/_update_fields_on_layer.scss b/packages/styles/scss/utilities/_update_fields_on_layer.scss
new file mode 100644
index 000000000000..6265d0f3197b
--- /dev/null
+++ b/packages/styles/scss/utilities/_update_fields_on_layer.scss
@@ -0,0 +1,66 @@
+//
+// Copyright IBM Corp. 2021
+//
+// This source code is licensed under the Apache-2.0 license found in the
+// LICENSE file in the root directory of this source tree.
+//
+
+@use '../config' as *;
+@use '../theme' as *;
+
+/// Update tokens when fields are placed on `layer`
+/// @access public
+/// @group utilities
+@mixin update_fields_on_layer {
+ // Fluid inputs
+ .#{$prefix}--text-input--fluid .#{$prefix}--text-input,
+ .#{$prefix}--text-area--fluid .#{$prefix}--text-area__wrapper,
+ .#{$prefix}--text-area--fluid .#{$prefix}--text-area,
+ .#{$prefix}--search--fluid .#{$prefix}--search-input,
+ .#{$prefix}--select--fluid .#{$prefix}--select-input,
+ .#{$prefix}--text-area--fluid
+ .#{$prefix}--text-area__wrapper[data-invalid]
+ .#{$prefix}--text-area__divider
+ + .#{$prefix}--form-requirement,
+ .#{$prefix}--list-box__wrapper--fluid .#{$prefix}--list-box,
+ .#{$prefix}--list-box__wrapper--fluid.#{$prefix}--list-box__wrapper,
+ .#{$prefix}--number-input--fluid input[type='number'],
+ .#{$prefix}--number-input--fluid .#{$prefix}--number__control-btn::before,
+ .#{$prefix}--number-input--fluid .#{$prefix}--number__control-btn::after,
+ .#{$prefix}--date-picker--fluid
+ .c#{$prefix}ds--date-picker-input__wrapper
+ .#{$prefix}--date-picker__input {
+ background-color: $field-01;
+ }
+
+ // Override to align layer token with field
+ .#{$prefix}--list-box__wrapper--fluid .#{$prefix}--list-box__menu {
+ background-color: $layer-01;
+ }
+
+ .#{$prefix}--list-box__menu-item:hover {
+ background-color: $layer-hover-02;
+ }
+
+ .#{$prefix}--list-box__menu-item--active {
+ background-color: $layer-selected-02;
+ }
+
+ .#{$prefix}--list-box__menu-item--active:hover {
+ background-color: $layer-selected-hover-02;
+ }
+
+ .#{$prefix}--number-input--fluid
+ .#{$prefix}--number__control-btn:hover::before,
+ .#{$prefix}--number-input--fluid
+ .#{$prefix}--number__control-btn:hover::after {
+ background-color: $field-hover;
+ }
+
+ .#{$prefix}--number-input--fluid
+ .#{$prefix}--number__control-btn:focus::before,
+ .#{$prefix}--number-input--fluid
+ .#{$prefix}--number__control-btn:focus::after {
+ border-inline-start: 2px solid $focus;
+ }
+}
diff --git a/yarn.lock b/yarn.lock
index ca6d6418f0ca..e2e50fb67521 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2098,7 +2098,6 @@ __metadata:
use-resize-observer: "npm:^6.0.0"
webpack: "npm:^5.65.0"
webpack-dev-server: "npm:^4.7.4"
- wicg-inert: "npm:^3.1.1"
window-or-global: "npm:^1.0.1"
peerDependencies:
react: ^16.8.6 || ^17.0.1 || ^18.2.0
@@ -28407,13 +28406,6 @@ __metadata:
languageName: node
linkType: hard
-"wicg-inert@npm:^3.1.1":
- version: 3.1.1
- resolution: "wicg-inert@npm:3.1.1"
- checksum: 10/78710965132ec77d778e7e126cde4a0608a1b4fcbaf9ac1bc01cf7bd84f294b6de4f72a3555d7b3f773bb621a8a659bc9a2cd503cf0019a15385d0846cea9878
- languageName: node
- linkType: hard
-
"wide-align@npm:^1.1.0, wide-align@npm:^1.1.5":
version: 1.1.5
resolution: "wide-align@npm:1.1.5"