Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions apps/website/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,7 @@ export default function App({ Component, pageProps, emotionCache = clientSideEmo
</Head>
<DxcApplicationLayout
header={
<DxcApplicationLayout.Header
branding={{ logo: { src: dxcLogo, alt: "DXC Technology" }, appTitle: "Halstack react" }}
/>
<DxcApplicationLayout.Header logo={{ src: dxcLogo, alt: "DXC Technology" }} appTitle="Halstack react" />
}
sidenav={
<DxcApplicationLayout.Sidenav
Expand Down
27 changes: 16 additions & 11 deletions apps/website/screens/components/header/code/HeaderCodePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,11 @@ import QuickNavContainer from "@/common/QuickNavContainer";
import Code, { ExtendedTableCode, TableCode } from "@/common/Code";
import StatusBadge from "@/common/StatusBadge";

const brandingTypeString = `{
logo : {
src: string | SVG;
alt: string;
href?: string;
onClick?: () => void;
};
appTitle?: string;
const logoTypeString = `{
src: string | SVG;
alt: string;
href?: string;
onClick?: () => void;
}`;

const navItemsTypeString = `(GroupItem | Item)[]`;
Expand Down Expand Up @@ -45,17 +42,25 @@ const sections = [
</tr>
</thead>
<tbody>
<tr>
<td>appTitle</td>
<td>
<TableCode>string</TableCode>
</td>
<td>Object used to configure the header application title.</td>
<td>-</td>
</tr>
<tr>
<td>
<DxcFlex direction="column" gap="var(--spacing-gap-xs)" alignItems="baseline">
<StatusBadge status="required" />
branding
logo
</DxcFlex>
</td>
<td>
<ExtendedTableCode>{brandingTypeString}</ExtendedTableCode>
<ExtendedTableCode>{logoTypeString}</ExtendedTableCode>
</td>
<td>Object used to configure the header branding, including logo and application title.</td>
<td>Object used to configure the header logo.</td>
<td>-</td>
</tr>
<tr>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,33 @@ const sections = [
},
],
},
{
title: "Responsiveness",
content: (
<>
<DxcParagraph>
On smaller viewports, the header adapts to its responsive version. This layout is designed to accommodate the
component's elements clearly and consistently, so the experience remains coherent across all screen sizes. The
responsive header is organized into three sections: the top bar, the body, and the bottom.
</DxcParagraph>
<DxcBulletedList>
<DxcBulletedList.Item>
<strong>Top bar:</strong> this section displays the product or application branding, along with the most
relevant utilities from the right slot of the standard header.
</DxcBulletedList.Item>
<DxcBulletedList.Item>
<strong>Body:</strong> this is where the main navigation items are placed. Visually, these items follow a
structure similar to the ones in our sidenav component, and their behavior remains consistent with how they
function in the default sidenav.
</DxcBulletedList.Item>
<DxcBulletedList.Item>
<strong>Bottom:</strong> this area is custom, but it's reserved for the header's call-to-action elements,
when present.
</DxcBulletedList.Item>
</DxcBulletedList>
</>
),
},
{
title: "Best practices",
subSections: [
Expand Down Expand Up @@ -236,6 +263,31 @@ const sections = [
</DxcBulletedList>
),
},
{
title: "Responsive mode",
content: (
<DxcBulletedList>
<DxcBulletedList.Item>
Even though the responsive header is composed of custom sections,{" "}
<strong>each area should remain aligned</strong> with the structure described above to preserve clarity
and consistency across smaller viewports.
</DxcBulletedList.Item>
<DxcBulletedList.Item>
<strong>Avoid overcrowding the top bar</strong> in the responsive layout. Only the most essential and
frequently used actions should remain visible there. Additional actions or CTAs should be placed in the
bottom section, while navigation items should be housed in the body.
</DxcBulletedList.Item>
<DxcBulletedList.Item>
<strong>Keep visual hierarchy</strong> as simple as possible. In reduced spaces, users scan content
faster, so each section should present its elements in a clear and predictable order.
</DxcBulletedList.Item>
<DxcBulletedList.Item>
Ensure that labels, icons, and interactive elements resize or reorganize in a way that maintains
readability and touch friendliness, especially on mobile devices.
</DxcBulletedList.Item>
</DxcBulletedList>
),
},
],
},
];
Expand Down
17 changes: 8 additions & 9 deletions packages/lib/src/header/Header.accessibility.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,13 @@ const iconSVG = (

const iconUrl = "https://iconape.com/wp-content/files/yd/367773/svg/logo-linkedin-logo-icon-png-svg.png";

const branding = {
logo: {
src: iconSVG,
alt: "DXC Logo",
href: iconUrl,
},
appTitle:
"Application Title with a very long name that exceeds the normal length to test how the header manages overflow situations",
const logo = {
src: iconSVG,
alt: "DXC Logo",
href: iconUrl,
};
const appTitle =
"Application Title with a very long name that exceeds the normal length to test how the header manages overflow situations";

const items = [
{
Expand Down Expand Up @@ -73,7 +71,8 @@ describe("Header component accessibility tests", () => {
it("Should not have basic accessibility issues", async () => {
const { container } = render(
<DxcHeader
branding={branding}
logo={logo}
appTitle={appTitle}
navItems={items}
sideContent={
<DxcButton title="Settings" icon="settings" mode="tertiary" size={{ height: "medium" }} onClick={() => {}} />
Expand Down
85 changes: 47 additions & 38 deletions packages/lib/src/header/Header.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import DxcFlex from "../flex/Flex";
import Title from "../../.storybook/components/Title";
import DxcApplicationLayout from "../layout/ApplicationLayout";
import DxcParagraph from "../paragraph/Paragraph";
import { dxcLogo } from "./Icons";
import DxcButton from "../button/Button";
import { userEvent, within } from "storybook/internal/test";
import preview from "../../.storybook/preview";
Expand Down Expand Up @@ -41,38 +40,43 @@ export default {
},
} satisfies Meta<typeof DxcHeader>;

const branding = {
logo: {
src: "https://picsum.photos/id/1000/104/34",
alt: "DXC Logo",
href: "https://www.dxc.com",
},
appTitle: "Application Title",
};
const dxcLogo = (
<svg xmlns="http://www.w3.org/2000/svg" width="73" height="40" viewBox="0 0 73 40">
<title>DXC Logo</title>
<g transform="translate(0)">
<g>
<path
d="M91.613-28.177v2.514H90.231V-28.15l-2.415-3.82h1.616l1.5,2.532,1.526-2.532h1.571ZM83.9-25.555A3.15,3.15,0,0,1,80.6-28.8v-.018a3.231,3.231,0,0,1,3.294-3.262,3.442,3.442,0,0,1,2.469.865l-.87,1.054a2.311,2.311,0,0,0-1.643-.64,1.891,1.891,0,0,0-1.8,1.964v.018a1.886,1.886,0,0,0,1.9,2,2.2,2.2,0,0,0,1.3-.378v-.9H83.858v-1.2h2.729v2.738A4.071,4.071,0,0,1,83.9-25.555Zm-6.416-3.261a1.913,1.913,0,0,0-1.9-1.982A1.883,1.883,0,0,0,73.7-28.835v.018a1.913,1.913,0,0,0,1.9,1.982A1.883,1.883,0,0,0,77.486-28.8Zm-1.9,3.261a3.225,3.225,0,0,1-3.33-3.243v-.018A3.255,3.255,0,0,1,75.6-32.078a3.225,3.225,0,0,1,3.331,3.243v.018A3.255,3.255,0,0,1,75.583-25.555Zm-9.173-.108V-31.97h1.382v5.045h3.133v1.261Zm-3.433-3.153a1.913,1.913,0,0,0-1.9-1.982,1.883,1.883,0,0,0-1.886,1.964v.018a1.913,1.913,0,0,0,1.9,1.982A1.883,1.883,0,0,0,62.978-28.8Zm-1.9,3.261a3.225,3.225,0,0,1-3.33-3.243v-.018a3.255,3.255,0,0,1,3.348-3.262,3.225,3.225,0,0,1,3.331,3.243v.018A3.255,3.255,0,0,1,61.075-25.555Zm-6.508-.108-3.043-4.009v4.009H50.159V-31.97h1.275l2.944,3.883V-31.97h1.364v6.306Zm-8.246,0v-2.531h-2.55v2.531H42.389V-31.97h1.382v2.5h2.55v-2.5H47.7v6.306Zm-8.432.108A3.178,3.178,0,0,1,34.666-28.8v-.018a3.2,3.2,0,0,1,3.276-3.262,3.237,3.237,0,0,1,2.478.973l-.88,1.018a2.315,2.315,0,0,0-1.606-.712,1.866,1.866,0,0,0-1.822,1.964v.018a1.87,1.87,0,0,0,1.822,1.982,2.265,2.265,0,0,0,1.651-.739l.88.891A3.206,3.206,0,0,1,37.889-25.555Zm-9.805-.108V-31.97h4.739v1.235H29.458v1.279h2.962v1.234H29.458V-26.9h3.411v1.234ZM24.322-30.69v5.027H22.939V-30.69H21.028v-1.28h5.206v1.28H24.322"
transform="translate(-21.028 65.555)"
fill="#010101"
/>
<path
d="M75.836-76.712a8.975,8.975,0,0,1,2.246-3.9,8.393,8.393,0,0,1,6.058-2.457h9.824v-5.67H84.139a14.611,14.611,0,0,0-10.232,4.221,14.509,14.509,0,0,0-3.076,4.536,11.913,11.913,0,0,0-.973,3.271Zm0,4.325a8.978,8.978,0,0,0,2.246,3.9,8.394,8.394,0,0,0,6.058,2.457h9.824v5.67H84.139A14.611,14.611,0,0,1,73.907-64.58a14.506,14.506,0,0,1-3.076-4.536,11.91,11.91,0,0,1-.973-3.271ZM57.522-69.832l-7.5,9.473H42.581L53.818-74.55,42.581-88.739H50.02l7.5,9.472,7.5-9.472h7.439L61.225-74.55l11.237,14.19H65.023Zm-12.336-6.88a11.935,11.935,0,0,0-.973-3.271,14.515,14.515,0,0,0-3.076-4.536A14.612,14.612,0,0,0,30.9-88.739H21.081v5.67H30.9a8.394,8.394,0,0,1,6.058,2.457,8.978,8.978,0,0,1,2.246,3.9Zm0,4.325a11.932,11.932,0,0,1-.973,3.271,14.511,14.511,0,0,1-3.076,4.536A14.611,14.611,0,0,1,30.9-60.359H21.081v-5.67H30.9a8.4,8.4,0,0,0,6.058-2.457,8.981,8.981,0,0,0,2.246-3.9h5.978"
transform="translate(-21.049 88.739)"
fill="#603494"
/>
</g>
</g>
</svg>
);

const brandingWithoutTitle = {
logo: {
src: "https://picsum.photos/id/1000/104/34",
alt: "DXC Logo",
href: "https://www.dxc.com",
},
const logo = {
src: "https://picsum.photos/id/1000/104/34",
alt: "DXC Logo",
href: "https://www.dxc.com",
};

const dxcBrandedLogo = {
logo: {
src: dxcLogo,
alt: "DXC Logo",
},
src: dxcLogo,
alt: "DXC Logo",
};

const longBranding = {
logo: {
src: "https://picsum.photos/id/1000/104/34",
alt: "DXC Logo",
href: "https://www.dxc.com",
},
appTitle:
"Application Title with a very long name that exceeds the normal length to test how the header manages overflow situations",
const longAppTitle = "This is a very long application title to test the header component behavior with long titles";

const longLogo = {
src: "https://picsum.photos/id/1000/104/34",
alt: "DXC Logo",
href: "https://www.dxc.com",
};

const longSideContent = `Long side content that is intended to test how the header component handles situations where the side content exceeds the typical length. This content should ideally wrap or be truncated based on the design specifications of the header component within the application.`;
Expand Down Expand Up @@ -118,25 +122,30 @@ const longItems = [
const Header = () => (
<DxcFlex gap="var(--spacing-gap-m)" direction="column">
<Title title="Default Header" theme="light" level={3} />
<DxcHeader branding={brandingWithoutTitle} />
<DxcHeader branding={branding} />
<DxcHeader branding={branding} sideContent={<div>Side Content</div>} />
<DxcHeader branding={branding} navItems={items} sideContent={<div>Side Content</div>} />
<DxcHeader logo={logo} />
<DxcHeader logo={logo} />
<DxcHeader logo={logo} sideContent={<div>Side Content</div>} />
<DxcHeader logo={logo} navItems={items} sideContent={<div>Side Content</div>} />
<Title title="Header with long content" theme="light" level={3} />
<DxcHeader branding={branding} navItems={items} sideContent={<div>{longSideContent}</div>} />
<DxcHeader branding={longBranding} navItems={items} />
<DxcHeader branding={longBranding} navItems={items} sideContent={<div>{longSideContent}</div>} />
<DxcHeader branding={longBranding} sideContent={<div>{longSideContent}</div>} />
<DxcHeader branding={longBranding} navItems={longItems} />
<DxcHeader branding={longBranding} navItems={longItems} sideContent={<div>{longSideContent}</div>} />
<DxcHeader logo={logo} navItems={items} sideContent={<div>{longSideContent}</div>} />
<DxcHeader logo={longLogo} appTitle={longAppTitle} navItems={items} />
<DxcHeader logo={longLogo} appTitle={longAppTitle} navItems={items} sideContent={<div>{longSideContent}</div>} />
<DxcHeader logo={longLogo} appTitle={longAppTitle} sideContent={<div>{longSideContent}</div>} />
<DxcHeader logo={longLogo} appTitle={longAppTitle} navItems={longItems} />
<DxcHeader
logo={longLogo}
appTitle={longAppTitle}
navItems={longItems}
sideContent={<div>{longSideContent}</div>}
/>
</DxcFlex>
);

const HeaderInLayout = () => (
<DxcApplicationLayout
header={
<DxcHeader
branding={dxcBrandedLogo}
logo={dxcBrandedLogo}
navItems={items}
sideContent={(isResponsive) =>
isResponsive ? (
Expand Down
8 changes: 3 additions & 5 deletions packages/lib/src/header/Header.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ import { render } from "@testing-library/react";
import DxcHeader from "./Header";

const defaultHeaderBranding = {
logo: {
src: "url-to-dxc-logo",
alt: "DXC Logo",
},
src: "url-to-dxc-logo",
alt: "DXC Logo",
};

describe("Header component tests", () => {
Expand All @@ -18,7 +16,7 @@ describe("Header component tests", () => {
});
});
test("Header renders with default logo", () => {
const { getByAltText } = render(<DxcHeader branding={defaultHeaderBranding} />);
const { getByAltText } = render(<DxcHeader logo={defaultHeaderBranding} />);
expect(getByAltText("DXC Logo")).toBeTruthy();
});
});
28 changes: 12 additions & 16 deletions packages/lib/src/header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { useEffect, useMemo, useState } from "react";
import DxcNavigationTree from "../navigation-tree/NavigationTree";
import { responsiveSizes } from "../common/variables";
import DxcButton from "../button/Button";
import scrollbarStyles from "../styles/scroll";

const MAX_MAIN_NAV_SIZE = "60%";
const LEVEL_LIMIT = 1;
Expand Down Expand Up @@ -79,6 +80,9 @@ const HamburguerButton = ({ onClick }: { onClick: () => void }) => {
const ResponsiveMenuContainer = styled.div`
display: grid;
grid-template-rows: auto 1fr;
max-height: 100%;
overflow: auto;
${scrollbarStyles}
`;

const ResponsiveMenu = styled.div`
Expand Down Expand Up @@ -126,7 +130,7 @@ const sanitizeNavItems = (navItems: HeaderProps["navItems"], level?: number): (G
return sanitizedItems;
};

const DxcHeader = ({ branding, navItems, sideContent, responsiveBottomContent }: HeaderProps): JSX.Element => {
const DxcHeader = ({ logo, appTitle, navItems, sideContent, responsiveBottomContent }: HeaderProps): JSX.Element => {
const [isResponsive, setIsResponsive] = useState(false);
const [isMenuVisible, setIsMenuVisible] = useState(false);

Expand Down Expand Up @@ -168,25 +172,17 @@ const DxcHeader = ({ branding, navItems, sideContent, responsiveBottomContent }:
placeItems="center"
>
<BrandingContainer>
<LogoContainer
role={branding.logo.onClick ? "button" : undefined}
as={branding.logo.href ? "a" : undefined}
>
{typeof branding.logo.src === "string" ? (
<DxcImage
src={branding.logo.src}
alt={branding.logo.alt}
height="var(--height-m)"
objectFit="contain"
/>
<LogoContainer role={logo.onClick ? "button" : undefined} as={logo.href ? "a" : undefined}>
{typeof logo.src === "string" ? (
<DxcImage src={logo.src} alt={logo.alt} height="var(--height-m)" objectFit="contain" />
) : (
branding.logo.src
logo.src
)}
</LogoContainer>
{branding.appTitle && !isResponsive && (
{appTitle && !isResponsive && (
<>
<DxcDivider orientation="vertical" />
<DxcHeading text={branding.appTitle} as="h1" level={5} />
<DxcHeading text={appTitle} as="h1" level={5} />
</>
)}
</BrandingContainer>
Expand All @@ -213,7 +209,7 @@ const DxcHeader = ({ branding, navItems, sideContent, responsiveBottomContent }:
{isResponsive && isMenuVisible && (
<ResponsiveMenuContainer>
<ResponsiveMenu>
{branding.appTitle && <DxcHeading text={branding.appTitle} as="h1" level={5} />}
{appTitle && <DxcHeading text={appTitle} as="h1" level={5} />}
<DxcNavigationTree
items={sanitizedNavItems}
displayGroupLines={false}
Expand Down
19 changes: 0 additions & 19 deletions packages/lib/src/header/Icons.tsx

This file was deleted.

Loading