diff --git a/apps/website/pages/components/card/code.tsx b/apps/website/pages/components/card/code.tsx new file mode 100644 index 0000000000..f23472e221 --- /dev/null +++ b/apps/website/pages/components/card/code.tsx @@ -0,0 +1,17 @@ +import Head from "next/head"; +import type { ReactElement } from "react"; +import CardCodePage from "screens/components/card/code/CardCodePage"; +import CardPageLayout from "screens/components/card/CardPageLayout"; + +const Code = () => ( + <> + + Card code — Halstack Design System + + + +); + +Code.getLayout = (page: ReactElement) => {page}; + +export default Code; diff --git a/apps/website/pages/components/card/index.tsx b/apps/website/pages/components/card/index.tsx index 918ffecb22..469d28150d 100644 --- a/apps/website/pages/components/card/index.tsx +++ b/apps/website/pages/components/card/index.tsx @@ -1,21 +1,17 @@ import Head from "next/head"; import type { ReactElement } from "react"; -import CardCodePage from "screens/components/card/code/CardCodePage"; +import CardOverviewPage from "screens/components/card/overview/CardOverviewPage"; import CardPageLayout from "screens/components/card/CardPageLayout"; -const Usage = () => { - return ( - <> - - Card — Halstack Design System - - - - ); -}; +const Index = () => ( + <> + + Card — Halstack Design System + + + +); -Usage.getLayout = function getLayout(page: ReactElement) { - return {page}; -}; +Index.getLayout = (page: ReactElement) => {page}; -export default Usage; +export default Index; diff --git a/apps/website/pages/components/card/specifications.tsx b/apps/website/pages/components/card/specifications.tsx deleted file mode 100644 index 60fd1086d9..0000000000 --- a/apps/website/pages/components/card/specifications.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import Head from "next/head"; -import type { ReactElement } from "react"; -import CardSpecsPage from "screens/components/card/specs/CardSpecsPage"; -import CardPageLayout from "screens/components/card/CardPageLayout"; - -const Specifications = () => { - return ( - <> - - Card Specs — Halstack Design System - - - - ); -}; - -Specifications.getLayout = function getLayout(page: ReactElement) { - return {page}; -}; - -export default Specifications; diff --git a/apps/website/pages/components/card/usage.tsx b/apps/website/pages/components/card/usage.tsx deleted file mode 100644 index 70dfef4007..0000000000 --- a/apps/website/pages/components/card/usage.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import Head from "next/head"; -import type { ReactElement } from "react"; -import CardUsagePage from "screens/components/card/usage/CardUsagePage"; -import CardPageLayout from "screens/components/card/CardPageLayout"; - -const Usage = () => { - return ( - <> - - Card Usage — Halstack Design System - - - - ); -}; - -Usage.getLayout = function getLayout(page: ReactElement) { - return {page}; -}; - -export default Usage; diff --git a/apps/website/screens/components/card/CardPageLayout.tsx b/apps/website/screens/components/card/CardPageLayout.tsx index 41ec875fe5..944de91fb7 100644 --- a/apps/website/screens/components/card/CardPageLayout.tsx +++ b/apps/website/screens/components/card/CardPageLayout.tsx @@ -6,9 +6,8 @@ import { ReactNode } from "react"; const CardPageHeading = ({ children }: { children: ReactNode }) => { const tabs = [ - { label: "Code", path: "/components/card" }, - { label: "Usage", path: "/components/card/usage" }, - { label: "Specifications", path: "/components/card/specifications" }, + { label: "Overview", path: "/components/card" }, + { label: "Code", path: "/components/card/code" }, ]; return ( @@ -17,13 +16,10 @@ const CardPageHeading = ({ children }: { children: ReactNode }) => { - Cards are a container of information, actions and data with a hierarchy to make easy scanning the content. A - card can be defined as a unit, so it has all the information within it, making the component useful to show - images, text, and interactive elements. The structure of the card can be seen as blocks, each block is - optional to be displayed but the overall element should make a cohesive design, even if it includes text, - images or other elements. + A card is a flexible, modular UI components used to group related information and actions within a + contained, clean and visually distinct layout. - + {children} diff --git a/apps/website/screens/components/card/code/CardCodePage.tsx b/apps/website/screens/components/card/code/CardCodePage.tsx index 054f24ec96..a988464222 100644 --- a/apps/website/screens/components/card/code/CardCodePage.tsx +++ b/apps/website/screens/components/card/code/CardCodePage.tsx @@ -22,14 +22,11 @@ const sections = [ - imageSrc - - string - + children - URL of the image that will be placed in the card component. In case of omission, the image container will - not appear and the content will occupy its space. + React.ReactNode + Custom content that will be placed inside the component. - @@ -42,6 +39,16 @@ const sections = [ 'black' + + imageCover + + boolean + + Whether the image must cover the its whole area of the card. + + false + + imagePadding @@ -66,66 +73,58 @@ const sections = [ - linkHref + imageSrc string - If defined, the card will be displayed as an anchor, using this prop as href. The component - will display visual information on mouse-over. + URL of the image that will be placed in the card component. In case of omission, the image container will + not appear and the content will occupy its space. - - onClick + linkHref - {"() => void"} + string - This function will be called when the user clicks the card. Component will show some visual feedback on - hover. + If defined, the card will be displayed as an anchor, using this prop as href. The component + will display visual information on mouse-over. - - imageCover + margin - boolean + 'xxsmall' | 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge' | 'xxlarge' | Margin - Whether the image must cover the its whole area of the card. - false + 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. + - - - outlined + onClick - boolean + {"() => void"} - Determines whether or not the component should have an outline. - true - - - - children - - React.ReactNode + This function will be called when the user clicks the card. Component will show some visual feedback on + hover. - Custom content that will be placed inside the component. - - margin + outlined - 'xxsmall' | 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge' | 'xxlarge' | Margin + boolean + Determines whether or not the component should have an outline. - 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. + true - - tabIndex @@ -154,15 +153,13 @@ const sections = [ }, ]; -const SelectCodePage = () => { - return ( - - - - - - - ); -}; +const CardCodePage = () => ( + + + + + + +); -export default SelectCodePage; +export default CardCodePage; diff --git a/apps/website/screens/components/card/overview/CardOverviewPage.tsx b/apps/website/screens/components/card/overview/CardOverviewPage.tsx new file mode 100644 index 0000000000..8c5323fff8 --- /dev/null +++ b/apps/website/screens/components/card/overview/CardOverviewPage.tsx @@ -0,0 +1,142 @@ +import { DxcBulletedList, DxcFlex, DxcParagraph } from "@dxc-technology/halstack-react"; +import QuickNavContainer from "@/common/QuickNavContainer"; +import QuickNavContainerLayout from "@/common/QuickNavContainerLayout"; +import DocFooter from "@/common/DocFooter"; +import anatomy from "./images/card_anatomy.png"; +import example from "./images/card_example.png"; +import Image from "@/common/Image"; + +const sections = [ + { + title: "Introduction", + content: ( + + Cards are versatile UI components used to group related content and actions within a contained layout. They help + organize information into digestible sections, making it easier for users to scan, compare, and interact with + individual items. Cards enhance readability and visual hierarchy and are commonly used in dashboards, product + listings, user profiles, and content previews to support structured and engaging user experiences. + + ), + }, + { + title: "Anatomy", + content: ( + <> + Card's anatomy + + + Container: the main structural wrapper that ensures padding, spacing, and alignment across + all card elements. + + + Custom content area: flexible space where text, icons, buttons, and other UI elements can + be placed. It adapts to different use cases while maintaining visual consistency. + + + Image (Optional): visual element that enhances the card's content. By default it + can be placed either on the left or right side of the card depending on layout needs. + + + Interactive elements (Optional): components such as buttons or icons that allow + users to take actions related to the card's content. + + + + + ** Please note that while these elements are not included by default in the component's configuration, they + are the common components in a card. While cards support optional elements like images and actions, it's + essential that the overall composition remains visually balanced and aligned with the design system's + structure. + + + + ), + }, + { + title: "Placing content in a Card", + content: ( + <> + + Cards are designed to accommodate flexible layouts and diverse content types. You can include any combination + of media and interactive elements, but it's important to follow consistent design patterns for usability and + clarity. + + + + Image placement: cards support placing the image either on the left or the right side of + the layout. The image should maintain a fixed ratio and size for visual harmony. By default, the component + provides layout options where the image can appear on the left or right side of the content. However, + alternative layouts —such as vertical image placements — can be achieved by placing the image directly within + the custom content area. This allows for more flexibility while still adhering to spacing and alignment + guidelines. + + + Text content: titles, descriptions, metadata, or status labels are typically placed in the + content area with a clear hierarchy — starting with a bold title, followed by supporting text. + + + Interactive elements: place buttons, links, or icons in the lower section of the content + area or aligned to the end of the card to support related user actions. + + + Avoid overloading: keep the content concise to prevent visual clutter. A well-structured + card should be easy to scan and act upon. + + + Card's example + + ), + }, + { + title: "Best practices", + content: ( + <> + + + Use a consistent layout: when displaying a collection of cards, maintain the same layout + and style across all instances. Avoid mixing card variants within the same set. + + + Support scanning: structure content so users can quickly identify what the card is about — + use visual hierarchy, spacing, and clear typography. + + + Use cards as independent units: each card should contain all the relevant information and + actions within it. It should make sense on its own, even if removed from the rest of the collection. + + + Minimize interaction complexity: don't overload the card with too many clickable areas. + Define clear action zones, and prioritize the most important interactions. + + + Respect spacing: ensure consistent padding and margins within and between cards to maintain + visual rhythm in the layout. + + + Use of white space: avoid cramming content. White space improves readability and prevents + cards from feeling cluttered. + + + Avoid over-design: too many effects, decorations, or inconsistent imagery can reduce + clarity and hurt the overall user experience. + + + Responsiveness: cards should adapt gracefully to different screen sizes and grid layouts, + maintaining structure and readability. + + + + ), + }, +]; + +const CardOverviewPage = () => ( + + + + + + +); + +export default CardOverviewPage; diff --git a/apps/website/screens/components/card/overview/images/card_anatomy.png b/apps/website/screens/components/card/overview/images/card_anatomy.png new file mode 100644 index 0000000000..05d74dc1ce Binary files /dev/null and b/apps/website/screens/components/card/overview/images/card_anatomy.png differ diff --git a/apps/website/screens/components/card/overview/images/card_example.png b/apps/website/screens/components/card/overview/images/card_example.png new file mode 100644 index 0000000000..476bfd7249 Binary files /dev/null and b/apps/website/screens/components/card/overview/images/card_example.png differ diff --git a/apps/website/screens/components/card/specs/CardSpecsPage.tsx b/apps/website/screens/components/card/specs/CardSpecsPage.tsx deleted file mode 100644 index 9d853326af..0000000000 --- a/apps/website/screens/components/card/specs/CardSpecsPage.tsx +++ /dev/null @@ -1,344 +0,0 @@ -import { DxcParagraph, DxcBulletedList, DxcFlex, DxcTable } from "@dxc-technology/halstack-react"; -import Image from "@/common/Image"; -import QuickNavContainer from "@/common/QuickNavContainer"; -import QuickNavContainerLayout from "@/common/QuickNavContainerLayout"; -import Figure from "@/common/Figure"; -import Code from "@/common/Code"; -import DocFooter from "@/common/DocFooter"; -import specsImage from "./images/card_specs.png"; -import statesImage from "./images/card_states.png"; -import anatomyImage from "./images/card_anatomy.png"; - -const sections = [ - { - title: "Specifications", - content: ( -
- Card design specifications -
- ), - }, - { - title: "States", - content: ( - <> - - Component states: enabled, hover and focus. - -
- Card states -
- - ), - }, - { - title: "Anatomy", - content: ( - <> - Card anatomy - - Card image - Custom content - Container - - - ), - }, - { - title: "Design tokens", - subSections: [ - { - title: "Color", - content: ( - <> - - - - Component token - Element - Core token - Value - - - - - - backgroundColor* - - Container - - color-white - - #ffffff - - - - focusColor* - - Container:focus - - color-blue-600 - - #0095ff - - - - - ), - }, - { - title: "Border", - content: ( - - - - Property - Element - Core token - Value - - - - - - border-width - - Container - - border-width-0 - - 0rem / 0px - - - - border-style - - Container - - border-style-none - - none - - - - border-radius - - Container - - border-radius-medium - - 0.25rem / 4px - - - - ), - }, - { - title: "Spacing", - content: ( - - - - Property - Element - Core token - Value - - - - - - margin-top - - Custom content - subtitle - - spacing-4 - - 0.25rem / 4px - - - - margin-bottom - - Custom content - title - - spacing-16 - - 1rem / 16px - - - - padding-left - - Custom content - - spacing-24 - - 1.5rem / 24px - - - - padding-top - - Custom content - - spacing-24 - - 1.5rem / 24px - - - - padding-bottom - - Custom content - - spacing-24 - - 1.5rem / 24px - - - - padding-right - - Container - - spacing-24 - - 1.5rem / 24px - - - - ), - }, - { - title: "Size", - content: ( - <> - - - - Component token - Element - Core token - Value - - - - - - height - - Container height - - - 220px - - - - width - - Container width - - - 400px - - - - - - - Property - Element - Value - - - - - - max-width - - Image - 140px - - - - - ), - }, - { - title: "Margin", - content: ( - <> - - Margin properties can be applied independently to top, right,{" "} - bottom and left sides of the card container. - - - - - Margin - Value - - - - - - xxsmall - - 6px - - - - xsmall - - 16px - - - - small - - 24px - - - - medium - - 36px - - - - large - - 48px - - - - xlarge - - 64px - - - - xxlarge - - 100px - - - - - ), - }, - ], - }, -]; - -const CardSpecsPage = () => { - return ( - - - - - - - ); -}; - -export default CardSpecsPage; diff --git a/apps/website/screens/components/card/specs/images/card_anatomy.png b/apps/website/screens/components/card/specs/images/card_anatomy.png deleted file mode 100644 index f372121855..0000000000 Binary files a/apps/website/screens/components/card/specs/images/card_anatomy.png and /dev/null differ diff --git a/apps/website/screens/components/card/specs/images/card_specs.png b/apps/website/screens/components/card/specs/images/card_specs.png deleted file mode 100644 index 23c76ab4a2..0000000000 Binary files a/apps/website/screens/components/card/specs/images/card_specs.png and /dev/null differ diff --git a/apps/website/screens/components/card/specs/images/card_states.png b/apps/website/screens/components/card/specs/images/card_states.png deleted file mode 100644 index d9a57ffd79..0000000000 Binary files a/apps/website/screens/components/card/specs/images/card_states.png and /dev/null differ diff --git a/apps/website/screens/components/card/usage/CardUsagePage.tsx b/apps/website/screens/components/card/usage/CardUsagePage.tsx deleted file mode 100644 index 23c6835d6b..0000000000 --- a/apps/website/screens/components/card/usage/CardUsagePage.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { DxcBulletedList, DxcFlex } from "@dxc-technology/halstack-react"; -import QuickNavContainer from "@/common/QuickNavContainer"; -import QuickNavContainerLayout from "@/common/QuickNavContainerLayout"; -import DocFooter from "@/common/DocFooter"; - -const sections = [ - { - title: "Usage", - content: ( - - - Organize the card collection so thery are easy to use, take a layout that presents the information in a clear - way and apply the same styles for every card. - - - If a collection want be create, won't use different card styles, use the same to keep consistency. - - - ), - }, -]; - -const CardUsagePage = () => { - return ( - - - - - - - ); -}; - -export default CardUsagePage; diff --git a/packages/lib/src/card/Card.tsx b/packages/lib/src/card/Card.tsx index be94105c4c..fc04e3456f 100644 --- a/packages/lib/src/card/Card.tsx +++ b/packages/lib/src/card/Card.tsx @@ -1,59 +1,15 @@ -import { useContext, useState } from "react"; -import styled, { ThemeProvider } from "styled-components"; +import { useState } from "react"; +import styled from "styled-components"; import { spaces } from "../common/variables"; import CardPropsType from "./types"; -import CoreTokens from "../common/coreTokens"; -import HalstackContext from "../HalstackContext"; - -const DxcCard = ({ - imageSrc, - imageBgColor = "black", - imagePadding, - imagePosition = "before", - linkHref, - onClick, - imageCover = false, - margin, - tabIndex = 0, - outlined = true, - children, -}: CardPropsType): JSX.Element => { - const colorsTheme = useContext(HalstackContext); - const [isHovered, changeIsHovered] = useState(false); - - return ( - - changeIsHovered(true)} - onMouseLeave={() => changeIsHovered(false)} - onClick={onClick} - tabIndex={onClick || linkHref ? tabIndex : -1} - as={linkHref && "a"} - href={linkHref} - shadowDepth={!outlined ? 0 : isHovered && (onClick || linkHref) ? 2 : 1} - > - - {imageSrc && ( - - - - )} - {children} - - - - ); -}; const Card = styled.div<{ hasAction: boolean; margin: CardPropsType["margin"]; shadowDepth: 0 | 1 | 2; }>` - display: inline-flex; - cursor: ${({ hasAction }) => (hasAction && "pointer") || "unset"}; + display: flex; + cursor: ${({ hasAction }) => (hasAction ? "pointer" : "unset")}; outline: ${({ hasAction }) => !hasAction && "none"}; margin: ${({ margin }) => (margin && typeof margin !== "object" ? spaces[margin] : "0px")}; margin-top: ${({ margin }) => (margin && typeof margin === "object" && margin.top ? spaces[margin.top] : "")}; @@ -65,14 +21,14 @@ const Card = styled.div<{ ${({ hasAction }) => hasAction && `:focus { - outline: #0095ff auto 1px; + outline: var(--border-width-m) var(--border-style-default) var(--border-color-secondary-medium); }`} - + border-radius: var(--border-radius-s); box-shadow: ${({ shadowDepth }) => shadowDepth === 1 - ? `0px 3px 6px ${CoreTokens.color_grey_300_a}` + ? "var(--shadow-low-x-position) var(--shadow-low-y-position) var(--shadow-low-blur) var(--shadow-low-spread) var(--shadow-dark)" : shadowDepth === 2 - ? `0px 3px 10px ${CoreTokens.color_grey_300_a}` + ? "var(--shadow-mid-x-position) var(--shadow-mid-y-position) var(--shadow-mid-blur) var(--shadow-mid-spread) var(--shadow-light)" : "none"}; `; @@ -80,10 +36,10 @@ const CardContainer = styled.div<{ hasAction: boolean; imagePosition: CardPropsType["imagePosition"] | "none"; }>` - display: inline-flex; - flex-direction: ${(props) => (props.imagePosition === "after" ? "row-reverse" : "row")}; - height: ${(props) => props.theme.height}; - width: ${(props) => props.theme.width}; + display: flex; + flex-direction: ${({ imagePosition }) => (imagePosition === "after" ? "row-reverse" : "row")}; + height: 220px; + width: 400px; &:hover { border-color: ${({ hasAction }) => (hasAction ? "" : "unset")}; } @@ -102,18 +58,63 @@ const TagImage = styled.img<{ imagePadding: CardPropsType["imagePadding"]; image `; const ImageContainer = styled.div<{ imageBgColor: CardPropsType["imageBgColor"] }>` - display: inline-flex; - justify-content: center; align-items: center; + background-color: ${({ imageBgColor }) => imageBgColor ?? "transparent"}; + display: flex; flex-shrink: 0; - width: 35%; height: 100%; - background-color: ${({ imageBgColor }) => imageBgColor}; + justify-content: center; + width: 35%; `; const CardContent = styled.div` - flex-grow: 1; + align-items: flex-start; + align-self: stretch; + display: flex; + flex-direction: column; + flex-shrink: 0; + gap: var(--spacing-gap-ml); overflow: hidden; + padding: var(--spacing-padding-l); `; +const DxcCard = ({ + imageSrc, + imageBgColor, + imagePadding, + imagePosition = "before", + linkHref, + onClick, + imageCover = false, + margin, + tabIndex = 0, + outlined = true, + children, +}: CardPropsType) => { + const [isHovered, changeIsHovered] = useState(false); + + return ( + changeIsHovered(true)} + onMouseLeave={() => changeIsHovered(false)} + onClick={onClick} + tabIndex={onClick || linkHref ? tabIndex : -1} + as={linkHref && "a"} + href={linkHref} + shadowDepth={!outlined ? 0 : isHovered && (onClick || linkHref) ? 2 : 1} + > + + {imageSrc && ( + + + + )} + {children} + + + ); +}; + export default DxcCard;