diff --git a/apps/website/pages/components/link/code.tsx b/apps/website/pages/components/link/code.tsx new file mode 100644 index 0000000000..c1de068bfa --- /dev/null +++ b/apps/website/pages/components/link/code.tsx @@ -0,0 +1,17 @@ +import Head from "next/head"; +import type { ReactElement } from "react"; +import LinkPageLayout from "screens/components/link/LinkPageLayout"; +import LinkCodePage from "screens/components/link/code/LinkCodePage"; + +const Code = () => ( + <> + + Link code — Halstack Design System + + + +); + +Code.getLayout = (page: ReactElement) => {page}; + +export default Code; diff --git a/apps/website/pages/components/link/index.tsx b/apps/website/pages/components/link/index.tsx index e3bf049d24..ac2fb0eb4e 100644 --- a/apps/website/pages/components/link/index.tsx +++ b/apps/website/pages/components/link/index.tsx @@ -1,21 +1,17 @@ import Head from "next/head"; import type { ReactElement } from "react"; import LinkPageLayout from "screens/components/link/LinkPageLayout"; -import LinkCodePage from "screens/components/link/code/LinkCodePage"; +import LinkOverviewPage from "screens/components/link/overview/LinkOverviewPage"; -const Index = () => { - return ( - <> - - Link — Halstack Design System - - - - ); -}; +const Index = () => ( + <> + + Link — Halstack Design System + + + +); -Index.getLayout = function getLayout(page: ReactElement) { - return {page}; -}; +Index.getLayout = (page: ReactElement) => {page}; export default Index; diff --git a/apps/website/pages/components/link/specifications.tsx b/apps/website/pages/components/link/specifications.tsx deleted file mode 100644 index 2b0ca22e29..0000000000 --- a/apps/website/pages/components/link/specifications.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import Head from "next/head"; -import type { ReactElement } from "react"; -import LinkPageLayout from "screens/components/link/LinkPageLayout"; -import LinkSpecsPage from "screens/components/link/specs/LinkSpecsPage"; - -const Specifications = () => { - return ( - <> - - Link Specs — Halstack Design System - - - - ); -}; - -Specifications.getLayout = function getLayout(page: ReactElement) { - return {page}; -}; - -export default Specifications; diff --git a/apps/website/pages/components/link/usage.tsx b/apps/website/pages/components/link/usage.tsx deleted file mode 100644 index 4e2a3786c6..0000000000 --- a/apps/website/pages/components/link/usage.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import Head from "next/head"; -import type { ReactElement } from "react"; -import LinkPageLayout from "screens/components/link/LinkPageLayout"; -import LinkUsagePage from "screens/components/link/usage/LinkUsagePage"; - -const Index = () => { - return ( - <> - - Link usage — Halstack Design System - - - - ); -}; - -Index.getLayout = function getLayout(page: ReactElement) { - return {page}; -}; - -export default Index; diff --git a/apps/website/screens/components/link/LinkPageLayout.tsx b/apps/website/screens/components/link/LinkPageLayout.tsx index 3a2c17a655..d2d2573222 100644 --- a/apps/website/screens/components/link/LinkPageLayout.tsx +++ b/apps/website/screens/components/link/LinkPageLayout.tsx @@ -6,9 +6,8 @@ import { ReactNode } from "react"; const LinkPageHeading = ({ children }: { children: ReactNode }) => { const tabs = [ - { label: "Code", path: "/components/link" }, - { label: "Usage", path: "/components/link/usage" }, - { label: "Specifications", path: "/components/link/specifications" }, + { label: "Overview", path: "/components/link" }, + { label: "Code", path: "/components/link/code" }, ]; return ( @@ -17,8 +16,9 @@ const LinkPageHeading = ({ children }: { children: ReactNode }) => { - Links are used as navigational elements. They may appear isolated, inside a sentence or paragraph or - following the content. + Links serve as navigational elements, allowing users to move between pages or access related content. They + can appear independently, be embedded within text, or follow a section to provide additional information or + actions. diff --git a/apps/website/screens/components/link/code/LinkCodePage.tsx b/apps/website/screens/components/link/code/LinkCodePage.tsx index 3c3cfefd01..5d4de67884 100644 --- a/apps/website/screens/components/link/code/LinkCodePage.tsx +++ b/apps/website/screens/components/link/code/LinkCodePage.tsx @@ -27,25 +27,36 @@ const sections = [ - disabled - boolean + + + children + - If true, the link is disabled. - false + string + Text of the link. + - - inheritColor + disabled boolean - If true, the color is inherited from parent. + If true, the link is disabled. false + + href + + string + + Page to be opened when the user clicks on the link. + - + icon @@ -59,7 +70,6 @@ const sections = [ replace spaces with underscores. By default they are outlined if you want it to be filled prefix the symbol name with "filled_". - - @@ -73,55 +83,44 @@ const sections = [ - href - - string - - Page to be opened when the user clicks on the link. - - - - - newWindow + inheritColor boolean - If true, the page is opened in a new browser tab. + If true, the color is inherited from parent. false - onClick + margin - {"() => void"} + 'xxsmall' | 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge' | 'xxlarge' | Margin - If defined, the link will be displayed as a button. This function will be called when the user clicks the - link. + Size of the margin to be applied to the component. You can pass an object with 'top', 'bottom', 'left' and + 'right' properties in order to specify different margin sizes. - + newWindow - - - children - + boolean + If true, the page is opened in a new browser tab. - string + false - Text of the link. - - - margin + onClick - 'xxsmall' | 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge' | 'xxlarge' | Margin + {"() => void"} - Size of the margin to be applied to the component. You can pass an object with 'top', 'bottom', 'left' and - 'right' properties in order to specify different margin sizes. + If defined, the link will be displayed as a button. This function will be called when the user clicks the + link. - diff --git a/apps/website/screens/components/link/overview/LinkOverviewPage.tsx b/apps/website/screens/components/link/overview/LinkOverviewPage.tsx new file mode 100644 index 0000000000..8e062fd01a --- /dev/null +++ b/apps/website/screens/components/link/overview/LinkOverviewPage.tsx @@ -0,0 +1,81 @@ +import { DxcLink, DxcBulletedList, DxcFlex, DxcTable, DxcParagraph } from "@dxc-technology/halstack-react"; +import DocFooter from "@/common/DocFooter"; +import QuickNavContainer from "@/common/QuickNavContainer"; +import QuickNavContainerLayout from "@/common/QuickNavContainerLayout"; +import Image from "@/common/Image"; +import linkAnatomy from "./images/link-anatomy.png"; + +const sections = [ + { + title: "Introduction", + content: ( + + Links are essential interactive elements that enable users to navigate between pages, access + external resources, or explore related content. They can be placed within text, used as standalone elements, or + positioned after sections to provide additional actions or information. Links enhance usability by clearly + indicating their purpose and destination, ensuring a seamless and intuitive browsing experience. Proper usage of + links helps maintain accessibility and improves content discoverability across digital products. + + ), + }, + { + title: "Anatomy", + content: ( + <> + Link's anatomy + + + Icon: an optional visual element that can be used to represent more graphically the purpose + of the link. It can be placed before or after the link it’s representing. + + + Label: displays the textual content of the link, conveying where exactly it’s going to + navigate the component. + + + + ), + }, + { + title: "Best practices", + content: ( + <> + + + Use clear and descriptive labels: link labels should clearly indicate what users can expect + when they click. Avoid generic labels like "click here". + + + Indicate external links appropriately: If a link directs users to an external site or opens + a new tab, provide an appropriate icon to set expectations. + + + Avoid excessive linking: too many links within a paragraph can overwhelm users and make + content harder to read. Use links strategically. + + + Use appropriate link placement: position links logically within content, either integrated + into sentences or placed at the end of a section for additional navigation. + + + Differentiate links from buttons: links are primarily for navigation, while buttons trigger + actions like form submissions or modal openings. Use each element correctly. + + + + ), + }, +]; + +const LinkOverviewPage = () => { + return ( + + + + + + + ); +}; + +export default LinkOverviewPage; diff --git a/apps/website/screens/components/link/overview/images/link-anatomy.png b/apps/website/screens/components/link/overview/images/link-anatomy.png new file mode 100644 index 0000000000..05bfb822cf Binary files /dev/null and b/apps/website/screens/components/link/overview/images/link-anatomy.png differ diff --git a/apps/website/screens/components/link/specs/LinkSpecsPage.tsx b/apps/website/screens/components/link/specs/LinkSpecsPage.tsx deleted file mode 100644 index b9ae6b4490..0000000000 --- a/apps/website/screens/components/link/specs/LinkSpecsPage.tsx +++ /dev/null @@ -1,347 +0,0 @@ -import { DxcLink, DxcBulletedList, DxcFlex, DxcTable, DxcParagraph } from "@dxc-technology/halstack-react"; -import DocFooter from "@/common/DocFooter"; -import Figure from "@/common/Figure"; -import QuickNavContainer from "@/common/QuickNavContainer"; -import QuickNavContainerLayout from "@/common/QuickNavContainerLayout"; -import Image from "@/common/Image"; -import Code from "@/common/Code"; -import linkSpecs from "./images/link_specs.png"; -import linkStates from "./images/link_states.png"; - -const sections = [ - { - title: "Specifications", - content: ( -
- Link design specifications -
- ), - }, - { - title: "States", - content: ( - <> - - States: enabled, hover, focus, active,{" "} - visited and disabled. - -
- Link states -
- - ), - }, - { - title: "Design tokens", - subSections: [ - { - title: "Color", - content: ( - - - - Component token - Element - Core token - Value - - - - - - fontColor - - Label - - color-blue-800 - - #0067b3 - - - - hoverFontColor - - Label:hover - - color-blue-800 - - #0067b3 - - - - activeFontColor - - Label:active - - color-black - - #000000 - - - - disabledFontColor - - Label:disabled - - color-grey-500 - - #999999 - - - - visitedFontColor - - Label:visited - - color-purple-700 - - #5f249f - - - - hoverUnderlineColor - - Underline:hover - - color-blue-800 - - #0067b3 - - - - activeUnderlineColor - - Underline:active - - color-black - - #000000 - - - - visitedUnderlineColor - - Underline:visited - - color-purple-700 - - #5f249f - - - - focusColor - - Outline:focus - - color-blue-600 - - #0095ff - - - - ), - }, - { - title: "Margin", - content: ( - - - - Margin - Value - - - - - - xxsmall - - 6px - - - - xsmall - - 16px - - - - small - - 24px - - - - medium - - 36px - - - - large - - 48px - - - - xlarge - - 64px - - - - xxlarge - - 100px - - - - ), - }, - { - title: "Typography", - content: ( - - - - Property - Value - - - - - - font-size - - 1rem/16px - - - - font-weight - - 400 - - - - ), - }, - { - title: "Border", - content: ( - - - - Property - Element - Core token - Value - - - - - - border-bottom-width - - Link container:hover - - border-width-1 - - 1px - - - - border-style - - Link container:hover - - border-style-solid - - solid - - - - ), - }, - ], - }, - { - title: "Icon specs", - content: ( - - - - Property - Element - Value - - - - - - height/width - - icon - 16/16px - - - - padding-left - - icon - 4px - - - - ), - }, - { - title: "Accessibility", - subSections: [ - { - title: "WCAG 2.2", - content: ( - - - Understanding WCAG 2.2 -{" "} - - 2.4.9: Link Purpose (Link Only) - - - - Understanding WCAG 2.2 -{" "} - - 2.4.4: Link Purpose (In Context) - - - - ), - }, - { - title: "WAI-ARIA 1.2", - content: ( - - - WAI-ARIA authoring practices 1.2 -{" "} - - 3.13 Link - - - - ), - }, - ], - }, -]; - -const LinkSpecsPage = () => { - return ( - - - - - - - ); -}; - -export default LinkSpecsPage; diff --git a/apps/website/screens/components/link/specs/images/link_specs.png b/apps/website/screens/components/link/specs/images/link_specs.png deleted file mode 100644 index a243ef924e..0000000000 Binary files a/apps/website/screens/components/link/specs/images/link_specs.png and /dev/null differ diff --git a/apps/website/screens/components/link/specs/images/link_states.png b/apps/website/screens/components/link/specs/images/link_states.png deleted file mode 100644 index 6e9c3e0928..0000000000 Binary files a/apps/website/screens/components/link/specs/images/link_states.png and /dev/null differ diff --git a/apps/website/screens/components/link/usage/LinkUsagePage.tsx b/apps/website/screens/components/link/usage/LinkUsagePage.tsx deleted file mode 100644 index e063d00bfe..0000000000 --- a/apps/website/screens/components/link/usage/LinkUsagePage.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { DxcBulletedList, DxcFlex, DxcParagraph } from "@dxc-technology/halstack-react"; -import DocFooter from "@/common/DocFooter"; -import QuickNavContainer from "@/common/QuickNavContainer"; -import QuickNavContainerLayout from "@/common/QuickNavContainerLayout"; -import iconUsage from "./examples/iconUsage"; -import Example from "@/common/example/Example"; - -const sections = [ - { - title: "Usage", - content: ( - - Provide visual cue to suggest clickability for all types of links. - Distinguish the visited and unvisited for navigation links. - Clearly explain where the link will take you to. - Front-load the most relevant keyword. - - ), - }, - { - title: "Icon usage", - content: ( - <> - - An icon can be used either in the normal or underlined mode to represent more graphical the purpose of the - link, placing the icon before or after the link that is representing. - - - - ), - }, -]; - -const LinkUsagePage = () => { - return ( - - - - - - - ); -}; - -export default LinkUsagePage; diff --git a/apps/website/screens/components/link/usage/examples/iconUsage.ts b/apps/website/screens/components/link/usage/examples/iconUsage.ts deleted file mode 100644 index fb34b0faa0..0000000000 --- a/apps/website/screens/components/link/usage/examples/iconUsage.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { DxcLink, DxcFlex, DxcInset } from "@dxc-technology/halstack-react"; - -const code = `() => { - const icon = ( - - - - - ); - - return ( - - - - - Link - - - Link - - - Link - - - - - Link - - - Link - - - Link - - - - - ); -}`; - -const scope = { - DxcLink, - DxcFlex, - DxcInset, -}; - -export default { code, scope }; diff --git a/apps/website/screens/components/link/usage/images/link_states_icon.png b/apps/website/screens/components/link/usage/images/link_states_icon.png deleted file mode 100644 index 9f3a76808c..0000000000 Binary files a/apps/website/screens/components/link/usage/images/link_states_icon.png and /dev/null differ diff --git a/packages/lib/src/link/Link.stories.tsx b/packages/lib/src/link/Link.stories.tsx index a6ea36ce92..cb5f0b386c 100644 --- a/packages/lib/src/link/Link.stories.tsx +++ b/packages/lib/src/link/Link.stories.tsx @@ -20,12 +20,6 @@ const icon = ( ); -const opinionatedTheme = { - link: { - baseColor: "#fabada", - }, -}; - const Link = () => ( <> @@ -63,7 +57,9 @@ const Link = () => ( </ExampleContainer> <ExampleContainer> <Title title="Inherit color" theme="light" level={4} /> - This is a <DxcLink inheritColor>Test</DxcLink>. + <span style={{ color: "#fabada" }}> + This is a <DxcLink inheritColor>Test</DxcLink>. + </span> </ExampleContainer> <ExampleContainer pseudoState="pseudo-focus"> <Title title="With brackets and focus" theme="light" level={4} /> @@ -198,13 +194,6 @@ const Link = () => ( Test </DxcLink> </ExampleContainer> - <Title title="Opinionated theme" theme="light" level={2} /> - <ExampleContainer pseudoState="pseudo-visited"> - <HalstackProvider theme={opinionatedTheme}> - <Title title="With link visited" theme="light" level={4} /> - <DxcLink href="https://www.google.com">Test</DxcLink> - </HalstackProvider> - </ExampleContainer> </> ); diff --git a/packages/lib/src/link/Link.tsx b/packages/lib/src/link/Link.tsx index c67677ec62..9c56afa8b3 100644 --- a/packages/lib/src/link/Link.tsx +++ b/packages/lib/src/link/Link.tsx @@ -1,10 +1,8 @@ -import { forwardRef, Ref, useContext } from "react"; -import styled, { ThemeProvider } from "styled-components"; +import { forwardRef, Ref } from "react"; +import styled from "styled-components"; import { spaces } from "../common/variables"; import DxcIcon from "../icon/Icon"; -import HalstackContext from "../HalstackContext"; import { LinkProps } from "./types"; -import CoreTokens from "../common/coreTokens"; const StyledLink = styled.a<{ margin: LinkProps["margin"]; @@ -25,30 +23,27 @@ const StyledLink = styled.a<{ props.margin && typeof props.margin === "object" && props.margin.left ? spaces[props.margin.left] : ""}; background: none; border: none; - border-radius: 4px; + border-radius: var(--spacing-gap-xs); width: fit-content; - ${(props) => `padding-bottom: ${props.theme.underlineSpacing};`} - font-family: ${(props) => props.theme.fontFamily}; - font-size: ${(props) => props.theme.fontSize}; - font-style: ${(props) => props.theme.fontStyle}; - font-weight: ${(props) => props.theme.fontWeight}; - line-height: ${CoreTokens.type_leading_compact_02}; + font-family: var(--typography-font-family); + font-size: var(--typography-link-m); + font-weight: var(--typography-link-regular); text-decoration: none; color: ${(props) => - props.inheritColor ? "inherit" : !props.disabled ? props.theme.fontColor : props.theme.disabledFontColor}; + props.inheritColor + ? "inherit" + : !props.disabled + ? "var(--color-fg-secondary-strong)" + : "var(--color-fg-neutral-medium)"}; ${(props) => (props.disabled ? "cursor: default;" : "cursor: pointer;")} ${(props) => (props.disabled ? "pointer-events: none;" : "")} &:visited { - color: ${(props) => (!props.inheritColor && !props.disabled ? props.theme.visitedFontColor : "")}; - & > span:hover { - ${(props) => `color: ${props.theme.visitedFontColor}; - border-bottom-color: ${props.theme.visitedUnderlineColor};`} - } + color: ${(props) => (!props.inheritColor && !props.disabled ? "var(--color-fg-primary-strong)" : "")}; } &:focus { - outline: 2px solid ${(props) => props.theme.focusColor}; - outline-offset: 2px; + outline: var(--border-width-m) var(--border-style-default) var(--border-color-secondary-medium); + outline-offset: var(--border-width-m); ${(props) => props.disabled && "outline: none"} } `; @@ -57,30 +52,26 @@ const LinkContainer = styled.span<{ iconPosition: LinkProps["iconPosition"]; inheritColor: LinkProps["inheritColor"]; }>` - ${(props) => `border-bottom: ${props.theme.underlineThickness} ${props.theme.underlineStyle} transparent;`} display: inline-flex; align-items: center; ${(props) => (props.iconPosition === "before" ? "flex-direction: row-reverse;" : "")} - gap: ${(props) => props.theme.iconSpacing}; + gap: var(--spacing-gap-xs); &:hover { - ${(props) => - `color: ${props.theme.hoverFontColor}; - cursor: pointer; - border-bottom-color: ${props.theme.hoverUnderlineColor};`} + color: var(--color-fg-secondary-stronger); + cursor: pointer; } &:active { - ${(props) => `color: ${props.theme.activeFontColor} !important; - border-bottom-color: ${props.theme.activeUnderlineColor} !important;`} + color: var(--color-fg-neutral-dark) !important; } `; const LinkIconContainer = styled.div` display: flex; - font-size: ${(props) => props.theme.iconSize}; + font-size: var(--height-xxs); svg { - width: ${(props) => props.theme.iconSize}; - height: ${(props) => props.theme.iconSize}; + width: 16px; + height: var(--height-xxs); } `; @@ -101,28 +92,24 @@ const DxcLink = forwardRef( }: LinkProps, ref: Ref<HTMLAnchorElement> ): JSX.Element => { - const colorsTheme = useContext(HalstackContext); - return ( - <ThemeProvider theme={colorsTheme.link}> - <StyledLink - as={onClick && !href ? "button" : "a"} - tabIndex={tabIndex} - onClick={!disabled ? onClick : undefined} - href={!disabled && href ? href : undefined} - target={href ? (newWindow ? "_blank" : "_self") : undefined} - disabled={disabled} - inheritColor={inheritColor} - margin={margin} - ref={ref} - {...otherProps} - > - <LinkContainer iconPosition={iconPosition} inheritColor={inheritColor}> - {children} - {icon && <LinkIconContainer>{typeof icon === "string" ? <DxcIcon icon={icon} /> : icon}</LinkIconContainer>} - </LinkContainer> - </StyledLink> - </ThemeProvider> + <StyledLink + as={onClick && !href ? "button" : "a"} + tabIndex={tabIndex} + onClick={!disabled ? onClick : undefined} + href={!disabled && href ? href : undefined} + target={href ? (newWindow ? "_blank" : "_self") : undefined} + disabled={disabled} + inheritColor={inheritColor} + margin={margin} + ref={ref} + {...otherProps} + > + <LinkContainer iconPosition={iconPosition} inheritColor={inheritColor}> + {children} + {icon && <LinkIconContainer>{typeof icon === "string" ? <DxcIcon icon={icon} /> : icon}</LinkIconContainer>} + </LinkContainer> + </StyledLink> ); } );