diff --git a/cypress/support/step-definitions/tabs-steps.js b/cypress/support/step-definitions/tabs-steps.js
index c32ef10141..a1c7b7d945 100644
--- a/cypress/support/step-definitions/tabs-steps.js
+++ b/cypress/support/step-definitions/tabs-steps.js
@@ -14,7 +14,6 @@ Then("Tab {int} content is visible", (id) => {
Then("Second Tab has a link property", () => {
tabById(2)
- .find("a")
.should("have.attr", "href", "https://carbon.sage.com/")
.and("have.attr", "target", "_blank");
});
diff --git a/src/components/tabs/__internal__/tab-title/index.d.ts b/src/components/tabs/__internal__/tab-title/index.d.ts
new file mode 100644
index 0000000000..ecb72c82ac
--- /dev/null
+++ b/src/components/tabs/__internal__/tab-title/index.d.ts
@@ -0,0 +1 @@
+export { default } from "./tab-title";
diff --git a/src/components/tabs/__internal__/tab-title/tab-title.component.js b/src/components/tabs/__internal__/tab-title/tab-title.component.js
index d99620eebd..12969d0986 100644
--- a/src/components/tabs/__internal__/tab-title/tab-title.component.js
+++ b/src/components/tabs/__internal__/tab-title/tab-title.component.js
@@ -37,6 +37,7 @@ const TabTitle = React.forwardRef(
isInSidebar,
href,
onKeyDown,
+ align,
...tabTitleProps
},
ref
@@ -73,6 +74,9 @@ const TabTitle = React.forwardRef(
return window.open(href, "_blank");
}
+ // safari does not focus buttons by default
+ ref.current?.focus();
+
return onClick(customEvent);
};
@@ -121,17 +125,16 @@ const TabTitle = React.forwardRef(
error={error}
warning={warning}
info={info}
- size={size}
noRightBorder={noRightBorder}
alternateStyling={alternateStyling || isInSidebar}
borders={borders}
isInSidebar={isInSidebar}
{...tabTitleProps}
+ {...(isHref && { href, target: "_blank", as: "a" })}
{...tagComponent("tab-header", tabTitleProps)}
onKeyDown={handleKeyDown}
>
{renderContent()}
{isHref && }
-
- {error && (
-
- )}
-
- {!error && warning && (
-
- )}
-
- {!warning && !error && info && (
-
- )}
-
+ {hasFailedValidation && (
+
+ {error && (
+
+ )}
+
+ {!error && warning && (
+
+ )}
+
+ {!warning && !error && info && (
+
+ )}
+
+ )}
{!(hasFailedValidation || hasAlternateStyling) && isTabSelected && (
+ ) => void;
+ onKeyDown?: (
+ ev: React.KeyboardEvent
+ ) => void;
+}
+
+declare function TabTitle(props: TabTitleProps): JSX.Element;
+
+export default TabTitle;
diff --git a/src/components/tabs/__internal__/tab-title/tab-title.spec.js b/src/components/tabs/__internal__/tab-title/tab-title.spec.js
index f0b6972163..e764630ba1 100644
--- a/src/components/tabs/__internal__/tab-title/tab-title.spec.js
+++ b/src/components/tabs/__internal__/tab-title/tab-title.spec.js
@@ -28,7 +28,7 @@ describe("TabTitle", () => {
backgroundColor: "transparent",
display: "inline-block",
fontWeight: "bold",
- height: "100%",
+ height: "40px",
},
render({}, mount).find(StyledTabTitle)
);
@@ -131,7 +131,7 @@ describe("TabTitle", () => {
marginLeft: "-1px",
},
wrapper.find(StyledTabTitle),
- { modifier: ":not(:first-of-type)" }
+ { modifier: ":nth-of-type(n + 1)" }
);
});
@@ -148,7 +148,7 @@ describe("TabTitle", () => {
);
assertStyleMatch(
- { paddingBottom: "6px" },
+ { paddingBottom: "9px" },
wrapper.find(StyledTitleContent)
);
@@ -203,7 +203,7 @@ describe("TabTitle", () => {
backgroundColor: "transparent",
borderBottom: "0px",
borderRight: `2px solid ${baseTheme.tab.background}`,
- display: "block",
+ display: "flex",
height: "auto",
marginLeft: "0px",
},
@@ -227,6 +227,24 @@ describe("TabTitle", () => {
);
});
+ it("renders as expected when `align='left'`", () => {
+ assertStyleMatch(
+ { justifyContent: "flex-start", textAlign: "left" },
+ render({ position: "left", align: "left" }, mount).find(
+ StyledTitleContent
+ )
+ );
+ });
+
+ it("renders as expected when `align='right'`", () => {
+ assertStyleMatch(
+ { justifyContent: "flex-end", textAlign: "right" },
+ render({ align: "right", position: "left" }, mount).find(
+ StyledTitleContent
+ )
+ );
+ });
+
it('applies proper styling when size is "large"', () => {
wrapper = render({ position: "left", size: "large" }, mount);
@@ -261,7 +279,7 @@ describe("TabTitle", () => {
marginTop: "-1px",
},
wrapper.find(StyledTabTitle),
- { modifier: ":not(:first-of-type)" }
+ { modifier: ":nth-of-type(n + 1)" }
);
assertStyleMatch(
@@ -308,15 +326,6 @@ describe("TabTitle", () => {
);
});
- it("applies proper styling when size is large", () => {
- wrapper = render({ isTabSelected: true, size: "large" }, mount);
-
- assertStyleMatch(
- { paddingBottom: "6px" },
- wrapper.find(StyledTitleContent)
- );
- });
-
it("does not apply selected styling", () => {
wrapper = render({ isTabSelected: true, error: true }, mount);
@@ -424,29 +433,6 @@ describe("TabTitle", () => {
);
});
- it('adjusts padding when isTabSelected is true and position is "top"', () => {
- wrapper = render(
- {
- title: "Tab 1",
- siblings: [foo, bar],
- titlePosition: "before",
- isTabSelected: true,
- },
- mount
- );
-
- expect(wrapper.find(StyledTitleContent).props().hasSiblings).toEqual(
- true
- );
- expect(
- wrapper.find(StyledTitleContent).props().children[0][0].props.children
- ).toEqual("Tab 1");
- assertStyleMatch(
- { paddingBottom: "8px" },
- wrapper.find(StyledTitleContent)
- );
- });
-
it('does not adjust padding when isTabSelected is true and position is "left"', () => {
wrapper = render(
{
@@ -1077,7 +1063,10 @@ describe("TabTitle", () => {
stopPropagation,
target: { dataset: { tabid: "uniqueid1" } },
};
- wrapper = render({ onClick }, mount);
+ wrapper = render(
+ { onClick, ref: { current: { focus: jest.fn() } } },
+ mount
+ );
wrapper
.find(StyledTitleContent)
diff --git a/src/components/tabs/__internal__/tab-title/tab-title.style.js b/src/components/tabs/__internal__/tab-title/tab-title.style.js
index 046f48e887..9107eab49e 100644
--- a/src/components/tabs/__internal__/tab-title/tab-title.style.js
+++ b/src/components/tabs/__internal__/tab-title/tab-title.style.js
@@ -4,8 +4,9 @@ import BaseTheme from "../../../../style/themes/base";
import StyledIcon from "../../../icon/icon.style";
import StyledValidationIcon from "../../../../__internal__/validations/validation-icon.style";
-const StyledTitleContent = styled.div`
+const StyledTitleContent = styled.span`
outline: none;
+ display: inline-block;
${({
theme,
@@ -15,17 +16,23 @@ const StyledTitleContent = styled.div`
noLeftBorder,
noRightBorder,
isTabSelected,
- hasSiblings,
- href,
+ hasHref,
error,
- warning,
- info,
alternateStyling,
+ align,
}) => css`
line-height: 20px;
margin: 0;
+ text-align: ${align};
+
+ ${position === "left" &&
+ css`
+ display: flex;
+ width: 100%;
+ justify-content: ${align === "right" ? "flex-end" : "flex-start"};
+ `}
- ${href &&
+ ${hasHref &&
css`
color: ${theme.text.color};
display: block;
@@ -72,13 +79,8 @@ const StyledTitleContent = styled.div`
position === "top" &&
css`
padding: 10px 24px;
+ ${borders && `padding-bottom: 9px;`}
font-size: 16px;
- ${isTabSelected &&
- !hasSiblings &&
- !(error || warning || info) &&
- css`
- padding-bottom: 6px;
- `}
`}
${size === "large" &&
@@ -92,18 +94,16 @@ const StyledTitleContent = styled.div`
${size === "default" &&
css`
padding: 10px 16px;
- ${isTabSelected &&
- !(error || warning || info) &&
- position === "top" &&
- css`
- padding-bottom: 8px;
- `}
+
+ ${borders && `padding-bottom: 9px;`}
${position === "left" &&
!isTabSelected &&
!alternateStyling &&
error &&
- `margin-right: -2px;`}
+ `
+ margin-right: -2px;
+ `}
`}
`}
@@ -135,7 +135,7 @@ const StyledTitleContent = styled.div`
padding-right: ${size === "large" ? "26px;" : "18px;"};
`}
- &:hover {
+ &:hover {
outline: 1px solid;
outline-offset: -1px;
@@ -182,10 +182,10 @@ const StyledTitleContent = styled.div`
${position === "left" &&
css`
border-right-color: transparent;
- padding-right: ${size === "large" ? "26px;" : "18px;"};
+ padding-right: ${size === "large" ? "26px" : "18px"};
`}
- &:hover {
+ &:hover {
outline: 2px solid ${theme.colors.error};
outline-offset: -2px;
${position === "top" &&
@@ -201,7 +201,7 @@ const StyledTitleContent = styled.div`
${position === "left" &&
css`
border-right-color: transparent;
- padding-right: ${size === "large" ? "26px;" : "18px;"};
+ padding-right: ${size === "large" ? "26px" : "18px"};
`}
}
`}
@@ -221,29 +221,14 @@ const StyledTitleContent = styled.div`
position === "top" &&
css`
height: 20px;
-
- ${size === "default" &&
- css`
- padding-top: 10px;
- padding-bottom: 10px;
-
- ${!(error || warning || info) &&
- isTabSelected &&
- css`
- padding-bottom: 8px;
- `}
- `}
+ padding-top: 10px;
+ padding-bottom: 10px;
${size === "large" &&
+ !(error || warning || info) &&
+ isTabSelected &&
css`
- padding-top: 10px;
- padding-bottom: 10px;
-
- ${!(error || warning || info) &&
- isTabSelected &&
- css`
- padding-bottom: 6px;
- `}
+ padding-bottom: 6px;
`}
`}
@@ -262,15 +247,15 @@ const StyledTitleContent = styled.div`
${position === "left" &&
css`
- padding: ${size === "large" ? "2px;" : "0px;"}
- ${isTabSelected &&
- css`
- padding-right: 0px;
- `}
- ${(error || warning || info) &&
- css`
- padding-right: ${size === "large" ? "26px" : "18px"};
- `};
+ padding: ${size === "large" ? "2px" : "0px"};
+ ${isTabSelected &&
+ css`
+ padding-right: 0px;
+ `}
+ ${(error || warning || info) &&
+ css`
+ padding-right: ${size === "large" ? "26px" : "18px"};
+ `}
`}
${position === "top" &&
@@ -282,53 +267,63 @@ const StyledTitleContent = styled.div`
`}
${(error || warning || info) &&
css`
- padding-bottom: ${size === "large" ? "4px;" : "2px;"}
- padding-right: ${size === "large" ? "18px;" : "14px;"}
-
- &:hover {
padding-bottom: ${size === "large" ? "4px;" : "2px;"}
- }
- `};
+ padding-right: ${size === "large" ? "18px;" : "14px;"}
+
+ &:hover {
+ padding-bottom: ${size === "large" ? "4px;" : "2px;"}
+ }
+ `};
`}
`}
`;
-const StyledTabTitle = styled.li`
+const StyledTabTitle = styled.button`
background-color: transparent;
display: inline-block;
font-weight: bold;
- height: 100%;
position: relative;
+ border: none;
+ cursor: pointer;
+ font-size: 14px;
+ padding: 0px;
+ text-decoration: none;
+ outline-offset: 0px;
+ margin: 0;
+
+ a:visited {
+ color: inherit;
+ }
- ${({ position, borders, noRightBorder, noLeftBorder }) => `
- ${
- position === "top" &&
- css`
- ${borders &&
- !(noRightBorder || noLeftBorder) &&
- css`
- &:not(:first-of-type) {
- margin-left: -1px;
- }
- `}
- `
- }
- ${
- position === "left" &&
- css`
- ${borders &&
- css`
- &:not(:first-of-type) {
- margin-top: -1px;
- }
- `}
- `
- }
- `}
+ ${({ position, borders, noRightBorder, noLeftBorder }) => css`
+ ${position === "top" &&
+ css`
+ height: 40px;
- &:first-child {
- margin-left: 0;
- }
+ ${borders &&
+ !(noRightBorder || noLeftBorder) &&
+ css`
+ &:nth-of-type(n + 1) {
+ margin-left: -1px;
+ }
+ &:first-child {
+ margin-left: 0;
+ }
+ `}
+ `}
+ ${position === "left" &&
+ css`
+ ${borders &&
+ css`
+ &:nth-of-type(n + 1) {
+ margin-top: -1px;
+ }
+ &:first-child {
+ margin-top: 0;
+ }
+ `}
+ `}
+ `}
${({ isTabSelected, theme }) =>
!isTabSelected &&
@@ -351,9 +346,7 @@ const StyledTabTitle = styled.li`
error,
warning,
info,
- size,
isInSidebar,
- position,
}) =>
isTabSelected &&
css`
@@ -361,29 +354,6 @@ const StyledTabTitle = styled.li`
color: ${theme.text.color};
background-color: ${theme.colors.white};
- ${
- alternateStyling &&
- css`
- border-bottom: 2px solid ${theme.tab.background};
- `
- }
-
- ${
- !alternateStyling &&
- css`
- padding-bottom: 2px;
- `
- }
-
- ${
- size === "large" &&
- css`
- ${position === "top" &&
- `
- padding-bottom: ${alternateStyling ? "3px" : "4px"};
- `}
- `
- }
${
(error || warning || info) &&
css`
@@ -412,7 +382,6 @@ const StyledTabTitle = styled.li`
${({
position,
- size,
borders,
theme,
alternateStyling,
@@ -439,7 +408,7 @@ const StyledTabTitle = styled.li`
}
`}
- display: block;
+ display: flex;
height: auto;
margin-left: 0px;
@@ -477,13 +446,6 @@ const StyledTabTitle = styled.li`
background-color: ${theme.colors.white};
- ${size === "large" &&
- css`
- & ${StyledTitleContent} {
- padding-right: 22px;
- }
- `}
-
&:hover {
${alternateStyling &&
` border-right-color: ${theme.tab.background};`}
@@ -529,7 +491,7 @@ const StyledLayoutWrapper = styled.div`
min-width: 100px;
`}
- ${({ hasCustomLayout, titlePosition, hasCustomSibling }) =>
+ ${({ hasCustomLayout, titlePosition, hasCustomSibling, position }) =>
!hasCustomLayout &&
css`
display: inline-flex;
@@ -559,7 +521,7 @@ const StyledLayoutWrapper = styled.div`
${StyledIcon} {
height: 16px;
left: -2px;
- top: 3px;
+ top: ${position === "left" ? "1px" : "3px"};
}
}
`}
diff --git a/src/components/tabs/__internal__/tabs-header/index.d.ts b/src/components/tabs/__internal__/tabs-header/index.d.ts
new file mode 100644
index 0000000000..36fbda7ccf
--- /dev/null
+++ b/src/components/tabs/__internal__/tabs-header/index.d.ts
@@ -0,0 +1 @@
+export { default } from "./tab-header";
diff --git a/src/components/tabs/__internal__/tabs-header/tab-header.d.ts b/src/components/tabs/__internal__/tabs-header/tab-header.d.ts
new file mode 100644
index 0000000000..3435c46afd
--- /dev/null
+++ b/src/components/tabs/__internal__/tabs-header/tab-header.d.ts
@@ -0,0 +1,16 @@
+import * as React from "react";
+
+export interface TabHeaderProps {
+ role?: string;
+ position?: "top" | "left";
+ extendedLine?: boolean;
+ noRightBorder?: boolean;
+ alternateStyling?: boolean;
+ isInSidebar?: boolean;
+ children: React.ReactNode;
+ align?: "left" | "right";
+}
+
+declare function TabHeader(props: TabHeaderProps): JSX.Element;
+
+export default TabHeader;
diff --git a/src/components/tabs/__internal__/tabs-header/tabs-header.style.js b/src/components/tabs/__internal__/tabs-header/tabs-header.style.js
index 55ea6485a1..bceda7847f 100644
--- a/src/components/tabs/__internal__/tabs-header/tabs-header.style.js
+++ b/src/components/tabs/__internal__/tabs-header/tabs-header.style.js
@@ -31,7 +31,7 @@ const StyledTabsHeaderWrapper = styled.div`
`}
`;
-const StyledTabsHeaderList = styled.ul`
+const StyledTabsHeaderList = styled.div`
display: flex;
box-shadow: inset 0px ${computeLineWidth} 0px 0px
${({ theme }) => theme.tab.background};
diff --git a/src/components/tabs/tabs.component.js b/src/components/tabs/tabs.component.js
index 5de643413c..8be3e9888c 100644
--- a/src/components/tabs/tabs.component.js
+++ b/src/components/tabs/tabs.component.js
@@ -257,6 +257,7 @@ const Tabs = ({
noRightBorder={["no right side", "no sides"].includes(borders)}
customLayout={customLayout}
isInSidebar={isInSidebar}
+ align={align}
onFocus={() => {
if (!hasTabStop(tabId)) {
setTabStopId(tabId);
diff --git a/src/components/tabs/tabs.stories.mdx b/src/components/tabs/tabs.stories.mdx
index 6b6a34e9b0..fcb6ff41cf 100644
--- a/src/components/tabs/tabs.stories.mdx
+++ b/src/components/tabs/tabs.stories.mdx
@@ -5,7 +5,6 @@ import { Tabs, Tab } from "./tabs.component";
import { Checkbox } from "../checkbox";
import Icon from "../icon";
import Pill from "../pill";
-import { ActionPopover, ActionPopoverItem } from "../action-popover";
import StyledSystemProps from "../../../.storybook/utils/styled-system-props";
@@ -1484,11 +1483,7 @@ prop to the `Tab` component.
Tab 1
@@ -1513,11 +1508,7 @@ prop to the `Tab` component.
Tab 2
@@ -1542,11 +1533,7 @@ prop to the `Tab` component.
Tab 3
@@ -1643,9 +1630,9 @@ The `headerWidth` prop works only if prop `position` is set to `left`.
errorMessage="error"
warningMessage="warning"
infoMessage="info"
- tabId="tab-1"
+ tabId="tabs-1-tab-1"
title="Very long title for Tab 1 without with prop it would be not well aligned with the second Tabs group"
- key="tab-1"
+ key="tabs-1-tab-1"
>
Content for tab 1
@@ -1653,9 +1640,9 @@ The `headerWidth` prop works only if prop `position` is set to `left`.
errorMessage="error"
warningMessage="warning"
infoMessage="info"
- tabId="tab-2"
+ tabId="tabs-1-tab-2"
title="Tab 2"
- key="tab-2"
+ key="tabs-1-tab-2"
>
Content for tab 2
@@ -1665,9 +1652,9 @@ The `headerWidth` prop works only if prop `position` is set to `left`.
errorMessage="error"
warningMessage="warning"
infoMessage="info"
- tabId="tab-1"
+ tabId="tabs-2-tab-1"
title="Tab 1"
- key="tab-1"
+ key="tabs-2-tab-1"
>
Content for tab 1
@@ -1675,9 +1662,9 @@ The `headerWidth` prop works only if prop `position` is set to `left`.
errorMessage="error"
warningMessage="warning"
infoMessage="info"
- tabId="tab-2"
+ tabId="tabs-2-tab-2"
title="Tab 2"
- key="tab-2"
+ key="tabs-2-tab-2"
>
Content for tab 2