Compare commits

..
Author SHA1 Message Date
Ken 955589ca0e refactor: Restructured expansion-card, link-card, list 2026-10-21 21:31:46 +02:00
25 changed files with 547 additions and 492 deletions

No files matched your search

@@ -1,10 +1,10 @@
import React, { forwardRef, useContext } from "react";
import { useThemeInternal } from "../theme/Theme";
import { BodyLong } from "../typography";
import { cl } from "../utils/helpers";
import { ExpansionCardContext } from "./context";
import { useThemeInternal } from "../../theme/Theme";
import { BodyLong } from "../../typography";
import { cl } from "../../utils/helpers";
import { ExpansionCardContext } from "../root/ExpansionCardRoot.context";
export interface ExpansionCardContentProps extends React.HTMLAttributes<HTMLDivElement> {
interface ExpansionCardContentProps extends React.HTMLAttributes<HTMLDivElement> {
children: React.ReactNode;
}
@@ -44,4 +44,5 @@ const ExpansionCardContent = forwardRef<
);
});
export default ExpansionCardContent;
export { ExpansionCardContent };
export type { ExpansionCardContentProps };
@@ -1,13 +0,0 @@
import { createContext } from "react";
export type ExpansionCardContextProps = {
open: boolean;
toggleOpen: () => void;
size: "medium" | "small";
};
export const ExpansionCardContext = createContext<ExpansionCardContextProps>({
open: false,
toggleOpen: () => {},
size: "medium",
});
@@ -1,13 +1,13 @@
import React, { forwardRef, useContext } from "react";
import { BodyLong } from "../typography";
import { cl } from "../utils/helpers";
import { ExpansionCardContext } from "./context";
import { BodyLong } from "../../typography";
import { cl } from "../../utils/helpers";
import { ExpansionCardContext } from "../root/ExpansionCardRoot.context";
export interface ExpansionCardDescriptionProps extends React.HTMLAttributes<HTMLParagraphElement> {
interface ExpansionCardDescriptionProps extends React.HTMLAttributes<HTMLParagraphElement> {
children: React.ReactNode;
}
export const ExpansionCardDescription = forwardRef<
const ExpansionCardDescription = forwardRef<
HTMLParagraphElement,
ExpansionCardDescriptionProps
>(({ className, ...rest }, ref) => {
@@ -31,4 +31,5 @@ export const ExpansionCardDescription = forwardRef<
);
});
export default ExpansionCardDescription;
export { ExpansionCardDescription };
export type { ExpansionCardDescriptionProps };
@@ -1,10 +1,10 @@
import React, { forwardRef, useContext } from "react";
import { ChevronDownIcon } from "@navikt/aksel-icons";
import { cl } from "../utils/helpers";
import { useI18n } from "../utils/i18n/i18n.hooks";
import { ExpansionCardContext } from "./context";
import { cl } from "../../utils/helpers";
import { useI18n } from "../../utils/i18n/i18n.hooks";
import { ExpansionCardContext } from "../root/ExpansionCardRoot.context";
export interface ExpansionCardHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
interface ExpansionCardHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
children: React.ReactNode;
}
@@ -46,4 +46,5 @@ const ExpansionCardHeader = forwardRef<
);
});
export default ExpansionCardHeader;
export { ExpansionCardHeader };
export type { ExpansionCardHeaderProps };
@@ -1,9 +1,9 @@
"use client";
export {
type ExpansionCardProps,
default as ExpansionCard,
} from "./ExpansionCard";
export { default as ExpansionCardContent } from "./ExpansionCardContent";
export { default as ExpansionCardHeader } from "./ExpansionCardHeader";
export { default as ExpansionCardTitle } from "./ExpansionCardTitle";
export { default as ExpansionCardDescription } from "./ExpansionCardDescription";
ExpansionCard,
ExpansionCardContent,
ExpansionCardHeader,
ExpansionCardTitle,
ExpansionCardDescription,
} from "./root/ExpansionCardRoot";
export type { ExpansionCardProps } from "./root/ExpansionCardRoot";
@@ -0,0 +1,16 @@
import { createContext } from "react";
type ExpansionCardContextProps = {
open: boolean;
toggleOpen: () => void;
size: "medium" | "small";
};
const ExpansionCardContext = createContext<ExpansionCardContextProps>({
open: false,
toggleOpen: () => {},
size: "medium",
});
export { ExpansionCardContext };
export type { ExpansionCardContextProps };
@@ -1,51 +1,24 @@
import React, { forwardRef } from "react";
import type { AkselColor } from "../types";
import type { OverridableComponent } from "../utils-external";
import { cl } from "../utils/helpers";
import { useControllableState } from "../utils/hooks";
import ExpansionCardContent, {
import type { AkselColor } from "../../types";
import { cl } from "../../utils/helpers";
import { useControllableState } from "../../utils/hooks";
import {
ExpansionCardContent,
type ExpansionCardContentProps,
} from "./ExpansionCardContent";
} from "../content/ExpansionCardContent";
import {
ExpansionCardDescription,
type ExpansionCardDescriptionProps,
} from "./ExpansionCardDescription";
import ExpansionCardHeader, {
} from "../description/ExpansionCardDescription";
import {
ExpansionCardHeader,
type ExpansionCardHeaderProps,
} from "./ExpansionCardHeader";
} from "../header/ExpansionCardHeader";
import {
ExpansionCardTitle,
type ExpansionCardTitleProps,
} from "./ExpansionCardTitle";
import { ExpansionCardContext } from "./context";
interface ExpansionCardComponent extends React.ForwardRefExoticComponent<
ExpansionCardProps & React.RefAttributes<HTMLDivElement>
> {
/**
* @see 🏷️ {@link ExpansionCardHeaderProps}
*/
Header: React.ForwardRefExoticComponent<
ExpansionCardHeaderProps & React.RefAttributes<HTMLDivElement>
>;
/**
* @see 🏷️ {@link ExpansionCardTitleProps}
* @see [🤖 OverridableComponent](https://aksel.nav.no/grunnleggende/kode/overridablecomponent) support
*/
Title: OverridableComponent<ExpansionCardTitleProps, HTMLHeadingElement>;
/**
* @see 🏷️ {@link ExpansionCardDescriptionProps}
*/
Description: React.ForwardRefExoticComponent<
ExpansionCardDescriptionProps & React.RefAttributes<HTMLParagraphElement>
>;
/**
* @see 🏷️ {@link ExpansionCardContentProps}
*/
Content: React.ForwardRefExoticComponent<
ExpansionCardContentProps & React.RefAttributes<HTMLDivElement>
>;
}
} from "../title/ExpansionCardTitle";
import { ExpansionCardContext } from "./ExpansionCardRoot.context";
interface ExpansionCardCommonProps extends Omit<
React.HTMLAttributes<HTMLDivElement>,
@@ -93,28 +66,10 @@ type ExpansionCardConditionalProps =
"aria-labelledby": string;
};
export type ExpansionCardProps = ExpansionCardCommonProps &
type ExpansionCardProps = ExpansionCardCommonProps &
ExpansionCardConditionalProps;
/**
* A component that displays an expandable card.
*
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/expansioncard)
* @see 🏷️ {@link ExpansionCardProps}
*
* @example
* ```jsx
* <ExpansionCard aria-label="Utbetaling av sykepenger">
* <ExpansionCard.Header>
* <ExpansionCard.Title>Utbetaling av sykepenger</ExpansionCard.Title>
* </ExpansionCard.Header>
* <ExpansionCard.Content>
* <Innhold />
* </ExpansionCard.Content>
* </ExpansionCard>
* ```
*/
export const ExpansionCard = forwardRef<HTMLDivElement, ExpansionCardProps>(
const ExpansionCardRoot = forwardRef<HTMLDivElement, ExpansionCardProps>(
(
{
className,
@@ -154,11 +109,57 @@ export const ExpansionCard = forwardRef<HTMLDivElement, ExpansionCardProps>(
</ExpansionCardContext.Provider>
);
},
) as ExpansionCardComponent;
);
ExpansionCard.Header = ExpansionCardHeader;
ExpansionCard.Content = ExpansionCardContent;
ExpansionCard.Title = ExpansionCardTitle;
ExpansionCard.Description = ExpansionCardDescription;
/**
* A component that displays an expandable card.
*
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/expansioncard)
* @see 🏷️ {@link ExpansionCardProps}
*
* @example
* ```jsx
* <ExpansionCard aria-label="Utbetaling av sykepenger">
* <ExpansionCard.Header>
* <ExpansionCard.Title>Utbetaling av sykepenger</ExpansionCard.Title>
* </ExpansionCard.Header>
* <ExpansionCard.Content>
* <Innhold />
* </ExpansionCard.Content>
* </ExpansionCard>
* ```
*/
const ExpansionCard = Object.assign(ExpansionCardRoot, {
/**
* @see 🏷️ {@link ExpansionCardHeaderProps}
*/
Header: ExpansionCardHeader,
/**
* @see 🏷️ {@link ExpansionCardTitleProps}
* @see [🤖 OverridableComponent](https://aksel.nav.no/grunnleggende/kode/overridablecomponent) support
*/
Title: ExpansionCardTitle,
/**
* @see 🏷️ {@link ExpansionCardDescriptionProps}
*/
Description: ExpansionCardDescription,
/**
* @see 🏷️ {@link ExpansionCardContentProps}
*/
Content: ExpansionCardContent,
});
export default ExpansionCard;
export {
ExpansionCard,
ExpansionCardHeader,
ExpansionCardTitle,
ExpansionCardDescription,
ExpansionCardContent,
};
export type {
ExpansionCardProps,
ExpansionCardHeaderProps,
ExpansionCardTitleProps,
ExpansionCardDescriptionProps,
ExpansionCardContentProps,
};
@@ -1,8 +1,8 @@
import React, { forwardRef } from "react";
import type { OverridableComponent } from "../utils-external";
import { cl } from "../utils/helpers";
import type { OverridableComponent } from "../../utils-external";
import { cl } from "../../utils/helpers";
export interface ExpansionCardTitleProps extends React.HTMLAttributes<HTMLHeadingElement> {
interface ExpansionCardTitleProps extends React.HTMLAttributes<HTMLHeadingElement> {
children: React.ReactNode;
/**
* Changes text-sizing
@@ -11,7 +11,7 @@ export interface ExpansionCardTitleProps extends React.HTMLAttributes<HTMLHeadin
size?: "large" | "medium" | "small";
}
export const ExpansionCardTitle: OverridableComponent<
const ExpansionCardTitle: OverridableComponent<
ExpansionCardTitleProps,
HTMLHeadingElement
> = forwardRef(
@@ -32,4 +32,5 @@ export const ExpansionCardTitle: OverridableComponent<
},
);
export default ExpansionCardTitle;
export { ExpansionCardTitle };
export type { ExpansionCardTitleProps };
@@ -13,7 +13,7 @@ import {
LinkCardIcon,
LinkCardImage,
LinkCardTitle,
} from "./LinkCard";
} from "./index";
type Story = StoryObj<typeof LinkCard>;
@@ -1,334 +0,0 @@
import React, { type HTMLAttributes, forwardRef } from "react";
import type { AkselColor } from "../types";
import { BodyLong, Heading } from "../typography";
import {
LinkAnchor,
LinkAnchorArrow,
LinkAnchorOverlay,
type LinkAnchorProps,
} from "../utils/components/link-anchor";
import { cl, createStrictContext } from "../utils/helpers";
/* ------------------------------ LinkCard Root ----------------------------- */
interface LinkCardProps extends HTMLAttributes<HTMLDivElement> {
/**
* @default true
*/
arrow?: boolean;
/**
* Adjusts arrow position.
* @default "baseline"
*/
arrowPosition?: "baseline" | "center";
/**
* Changes padding and typo sizes.
* @default "medium"
*/
size?: "small" | "medium";
/**
* Overrides color.
*
* We recommend avoiding status-colors (`info`, `success`, `warning`, `danger`) in LinkCards.
* @default "neutral"
* @see 🏷️ {@link AkselColor}
* @see [📝 Documentation](https://aksel.nav.no/grunnleggende/styling/farger-tokens)
*/
"data-color"?: AkselColor;
/**
* Changes the HTML element used for the root element.
*
* **When using `section`, provide either `aria-label` or `aria-labelledby` for better accessibility.**
* `axe-core` might warn about unique landmarks if you have multiple Accordions on page with the same label.
* In those cases consider updating to unique `aria-label` or `aria-labelledby` props.
* @see [📝 Landmarks unique](https://dequeuniversity.com/rules/axe/4.6/landmark-unique)
*
*
* **When using `article`, make sure `<LinkCard.Title />` is a heading and not a `span`.**
* @default "div"
*/
as?: "div" | "section" | "article";
}
type LinkCardContextProps = {
size: LinkCardProps["size"];
};
const { Provider: LinkCardContextProvider, useContext: useLinkCardContext } =
createStrictContext<LinkCardContextProps>({
name: "LinkCardContextProvider",
});
interface LinkCardComponent extends React.ForwardRefExoticComponent<
LinkCardProps & React.RefAttributes<HTMLDivElement>
> {
/**
* @see 🏷️ {@link LinkCardTitleProps}
*/
Title: typeof LinkCardTitle;
/**
* @see 🏷️ {@link LinkCardAnchorProps}
*/
Anchor: typeof LinkCardAnchor;
/**
* @see 🏷️ {@link LinkCardDescriptionProps}
*/
Description: typeof LinkCardDescription;
/**
* @see 🏷️ {@link LinkCardFooterProps}
*/
Footer: typeof LinkCardFooter;
/**
* @see 🏷️ {@link LinkCardIconProps}
*/
Icon: typeof LinkCardIcon;
/**
* @see 🏷️ {@link LinkCardImageProps}
*/
Image: typeof LinkCardImage;
}
/**
* Accessible clickable card as a link.
*
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/linkcard)
* @see 🏷️ {@link LinkCardProps}
*
*
* @example
* ```tsx
* <LinkCard>
* <LinkCard.Icon>
* <IconOrPictogram />
* </LinkCard.Icon>
* <LinkCard.Title>
* <LinkCard.Anchor href="/href">
* LinkCard title
* </LinkCard.Anchor>
* </LinkCard.Title>
* <LinkCard.Description>
* This is a description of the link card.
* </LinkCard.Description>
* <LinkCard.Footer>Footer content</LinkCard.Footer>
* </LinkCard>
* ```
*/
export const LinkCard = forwardRef<HTMLDivElement, LinkCardProps>(
(
{
children,
className,
arrow = true,
arrowPosition = "baseline",
size = "medium",
as: Component = "div",
...restProps
}: LinkCardProps,
forwardedRef,
) => {
return (
<LinkCardContextProvider size={size}>
<LinkAnchorOverlay asChild>
<BodyLong
as={Component}
size={size}
ref={forwardedRef}
data-color="neutral"
className={cl(
"aksel-link-card",
className,
`aksel-link-card--${size}`,
)}
data-align-arrow={arrowPosition}
{...restProps}
>
{children}
{arrow && (
<LinkAnchorArrow
fontSize={size === "medium" ? "1.75rem" : "1.5rem"}
className="aksel-link-card__arrow"
/>
)}
</BodyLong>
</LinkAnchorOverlay>
</LinkCardContextProvider>
);
},
) as LinkCardComponent;
/* ---------------------------- LinkCard Title ---------------------------- */
type LinkCardTitleProps = HTMLAttributes<HTMLHeadingElement> & {
children: React.ReactNode;
/**
* Heading tag. Use "span" if you want a non header defining card
* (eg. you have a lot of them all at once, such as in a grid)
* @default "span"
*/
as?: "span" | "h2" | "h3" | "h4" | "h5" | "h6";
};
/**
* @see 🏷️ {@link LinkCardTitleProps}
*/
export const LinkCardTitle = forwardRef<HTMLHeadingElement, LinkCardTitleProps>(
(
{ children, as = "span", className, ...restProps }: LinkCardTitleProps,
forwardedRef,
) => {
const context = useLinkCardContext();
return (
<Heading
ref={forwardedRef}
as={as}
size={context.size === "medium" ? "small" : "xsmall"}
className={cl("aksel-link-card__title", className)}
{...restProps}
>
{children}
</Heading>
);
},
);
/* ---------------------------- LinkCard Anchor ---------------------------- */
type LinkCardAnchorProps = LinkAnchorProps;
/**
* @see 🏷️ {@link LinkCardAnchorProps}
*/
export const LinkCardAnchor = LinkAnchor;
/* ---------------------------- LinkCard Description ---------------------------- */
interface LinkCardDescriptionProps extends HTMLAttributes<HTMLDivElement> {
children: React.ReactNode;
}
/**
* @see 🏷️ {@link LinkCardDescriptionProps}
*/
export const LinkCardDescription = forwardRef<
HTMLDivElement,
LinkCardDescriptionProps
>(
(
{ children, className, ...restProps }: LinkCardDescriptionProps,
forwardedRef,
) => {
return (
<div
ref={forwardedRef}
className={cl("aksel-link-card__description", className)}
{...restProps}
>
{children}
</div>
);
},
);
/* ---------------------------- LinkCard Footer ---------------------------- */
interface LinkCardFooterProps extends HTMLAttributes<HTMLDivElement> {
children: React.ReactNode;
}
/**
* @see 🏷️ {@link LinkCardFooterProps}
*/
export const LinkCardFooter = forwardRef<HTMLDivElement, LinkCardFooterProps>(
(
{ children, className, ...restProps }: LinkCardFooterProps,
forwardedRef,
) => {
return (
<div
ref={forwardedRef}
className={cl("aksel-link-card__footer", className)}
{...restProps}
>
{children}
</div>
);
},
);
/* ---------------------------- LinkCard Icon ---------------------------- */
interface LinkCardIconProps extends HTMLAttributes<HTMLDivElement> {
children: React.ReactNode;
}
/**
* @see 🏷️ {@link LinkCardIconProps}
*/
export const LinkCardIcon = forwardRef<HTMLDivElement, LinkCardIconProps>(
({ children, className, ...restProps }: LinkCardIconProps, forwardedRef) => {
return (
<div
ref={forwardedRef}
aria-hidden
className={cl("aksel-link-card__icon", className)}
{...restProps}
>
{children}
</div>
);
},
);
/* ---------------------------- LinkCard Image ---------------------------- */
type ImageAspectRatio = "1/1" | "16/9" | "16/10" | "4/3" | (string & {});
interface LinkCardImageProps extends HTMLAttributes<HTMLDivElement> {
children: React.ReactNode;
/**
* The aspect-ratio CSS property allows you to define the desired width-to-height ratio of an element's box.
* This means that even if the parent container or viewport size changes, the browser will adjust the element's dimensions to maintain the specified width-to-height ratio.
*/
aspectRatio?: ImageAspectRatio;
}
/**
* @see 🏷️ {@link LinkCardImageProps}
*/
export const LinkCardImage = forwardRef<HTMLDivElement, LinkCardImageProps>(
(
{
children,
className,
aspectRatio,
style,
...restProps
}: LinkCardImageProps,
forwardedRef,
) => {
return (
<div
ref={forwardedRef}
className={cl("aksel-link-card__image-container", className)}
style={{
...style,
aspectRatio,
}}
{...restProps}
>
{children}
</div>
);
},
);
LinkCard.Title = LinkCardTitle;
LinkCard.Anchor = LinkCardAnchor;
LinkCard.Description = LinkCardDescription;
LinkCard.Footer = LinkCardFooter;
LinkCard.Icon = LinkCardIcon;
LinkCard.Image = LinkCardImage;
export type {
LinkCardAnchorProps,
LinkCardDescriptionProps,
LinkCardFooterProps,
LinkCardIconProps,
LinkCardImageProps,
LinkCardProps,
LinkCardTitleProps,
};
@@ -0,0 +1,14 @@
import {
LinkAnchor,
type LinkAnchorProps,
} from "../../utils/components/link-anchor";
type LinkCardAnchorProps = LinkAnchorProps;
/**
* @see 🏷️ {@link LinkCardAnchorProps}
*/
const LinkCardAnchor = LinkAnchor;
export { LinkCardAnchor };
export type { LinkCardAnchorProps };
@@ -0,0 +1,32 @@
import React, { type HTMLAttributes, forwardRef } from "react";
import { cl } from "../../utils/helpers";
interface LinkCardDescriptionProps extends HTMLAttributes<HTMLDivElement> {
children: React.ReactNode;
}
/**
* @see 🏷️ {@link LinkCardDescriptionProps}
*/
const LinkCardDescription = forwardRef<
HTMLDivElement,
LinkCardDescriptionProps
>(
(
{ children, className, ...restProps }: LinkCardDescriptionProps,
forwardedRef,
) => {
return (
<div
ref={forwardedRef}
className={cl("aksel-link-card__description", className)}
{...restProps}
>
{children}
</div>
);
},
);
export { LinkCardDescription };
export type { LinkCardDescriptionProps };
@@ -0,0 +1,29 @@
import React, { type HTMLAttributes, forwardRef } from "react";
import { cl } from "../../utils/helpers";
interface LinkCardFooterProps extends HTMLAttributes<HTMLDivElement> {
children: React.ReactNode;
}
/**
* @see 🏷️ {@link LinkCardFooterProps}
*/
const LinkCardFooter = forwardRef<HTMLDivElement, LinkCardFooterProps>(
(
{ children, className, ...restProps }: LinkCardFooterProps,
forwardedRef,
) => {
return (
<div
ref={forwardedRef}
className={cl("aksel-link-card__footer", className)}
{...restProps}
>
{children}
</div>
);
},
);
export { LinkCardFooter };
export type { LinkCardFooterProps };
@@ -0,0 +1,27 @@
import React, { type HTMLAttributes, forwardRef } from "react";
import { cl } from "../../utils/helpers";
interface LinkCardIconProps extends HTMLAttributes<HTMLDivElement> {
children: React.ReactNode;
}
/**
* @see 🏷️ {@link LinkCardIconProps}
*/
const LinkCardIcon = forwardRef<HTMLDivElement, LinkCardIconProps>(
({ children, className, ...restProps }: LinkCardIconProps, forwardedRef) => {
return (
<div
ref={forwardedRef}
aria-hidden
className={cl("aksel-link-card__icon", className)}
{...restProps}
>
{children}
</div>
);
},
);
export { LinkCardIcon };
export type { LinkCardIconProps };
@@ -0,0 +1,46 @@
import React, { type HTMLAttributes, forwardRef } from "react";
import { cl } from "../../utils/helpers";
type ImageAspectRatio = "1/1" | "16/9" | "16/10" | "4/3" | (string & {});
interface LinkCardImageProps extends HTMLAttributes<HTMLDivElement> {
children: React.ReactNode;
/**
* The aspect-ratio CSS property allows you to define the desired width-to-height ratio of an element's box.
* This means that even if the parent container or viewport size changes, the browser will adjust the element's dimensions to maintain the specified width-to-height ratio.
*/
aspectRatio?: ImageAspectRatio;
}
/**
* @see 🏷️ {@link LinkCardImageProps}
*/
const LinkCardImage = forwardRef<HTMLDivElement, LinkCardImageProps>(
(
{
children,
className,
aspectRatio,
style,
...restProps
}: LinkCardImageProps,
forwardedRef,
) => {
return (
<div
ref={forwardedRef}
className={cl("aksel-link-card__image-container", className)}
style={{
...style,
aspectRatio,
}}
{...restProps}
>
{children}
</div>
);
},
);
export { LinkCardImage };
export type { LinkCardImageProps };
+2 -2
View File
@@ -7,7 +7,7 @@ export {
LinkCardAnchor,
LinkCardIcon,
LinkCardImage,
} from "./LinkCard";
} from "./root/LinkCardRoot";
export type {
LinkCardProps,
@@ -17,4 +17,4 @@ export type {
LinkCardAnchorProps,
LinkCardIconProps,
LinkCardImageProps,
} from "./LinkCard";
} from "./root/LinkCardRoot";
@@ -0,0 +1,14 @@
import { createStrictContext } from "../../utils/helpers";
import type { LinkCardProps } from "./LinkCardRoot";
type LinkCardContextProps = {
size: LinkCardProps["size"];
};
const { Provider: LinkCardContextProvider, useContext: useLinkCardContext } =
createStrictContext<LinkCardContextProps>({
name: "LinkCardContextProvider",
});
export { LinkCardContextProvider, useLinkCardContext };
export type { LinkCardContextProps };
@@ -0,0 +1,177 @@
import React, { type HTMLAttributes, forwardRef } from "react";
import type { AkselColor } from "../../types";
import { BodyLong } from "../../typography";
import {
LinkAnchorArrow,
LinkAnchorOverlay,
} from "../../utils/components/link-anchor";
import { cl } from "../../utils/helpers";
import {
LinkCardAnchor,
type LinkCardAnchorProps,
} from "../anchor/LinkCardAnchor";
import {
LinkCardDescription,
type LinkCardDescriptionProps,
} from "../description/LinkCardDescription";
import {
LinkCardFooter,
type LinkCardFooterProps,
} from "../footer/LinkCardFooter";
import { LinkCardIcon, type LinkCardIconProps } from "../icon/LinkCardIcon";
import { LinkCardImage, type LinkCardImageProps } from "../image/LinkCardImage";
import { LinkCardTitle, type LinkCardTitleProps } from "../title/LinkCardTitle";
import { LinkCardContextProvider } from "./LinkCardRoot.context";
interface LinkCardProps extends HTMLAttributes<HTMLDivElement> {
/**
* @default true
*/
arrow?: boolean;
/**
* Adjusts arrow position.
* @default "baseline"
*/
arrowPosition?: "baseline" | "center";
/**
* Changes padding and typo sizes.
* @default "medium"
*/
size?: "small" | "medium";
/**
* Overrides color.
*
* We recommend avoiding status-colors (`info`, `success`, `warning`, `danger`) in LinkCards.
* @default "neutral"
* @see 🏷️ {@link AkselColor}
* @see [📝 Documentation](https://aksel.nav.no/grunnleggende/styling/farger-tokens)
*/
"data-color"?: AkselColor;
/**
* Changes the HTML element used for the root element.
*
* **When using `section`, provide either `aria-label` or `aria-labelledby` for better accessibility.**
* `axe-core` might warn about unique landmarks if you have multiple Accordions on page with the same label.
* In those cases consider updating to unique `aria-label` or `aria-labelledby` props.
* @see [📝 Landmarks unique](https://dequeuniversity.com/rules/axe/4.6/landmark-unique)
*
*
* **When using `article`, make sure `<LinkCard.Title />` is a heading and not a `span`.**
* @default "div"
*/
as?: "div" | "section" | "article";
}
const LinkCardRoot = forwardRef<HTMLDivElement, LinkCardProps>(
(
{
children,
className,
arrow = true,
arrowPosition = "baseline",
size = "medium",
as: Component = "div",
...restProps
}: LinkCardProps,
forwardedRef,
) => {
return (
<LinkCardContextProvider size={size}>
<LinkAnchorOverlay asChild>
<BodyLong
as={Component}
size={size}
ref={forwardedRef}
data-color="neutral"
className={cl(
"aksel-link-card",
className,
`aksel-link-card--${size}`,
)}
data-align-arrow={arrowPosition}
{...restProps}
>
{children}
{arrow && (
<LinkAnchorArrow
fontSize={size === "medium" ? "1.75rem" : "1.5rem"}
className="aksel-link-card__arrow"
/>
)}
</BodyLong>
</LinkAnchorOverlay>
</LinkCardContextProvider>
);
},
);
/**
* Accessible clickable card as a link.
*
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/linkcard)
* @see 🏷️ {@link LinkCardProps}
*
*
* @example
* ```tsx
* <LinkCard>
* <LinkCard.Icon>
* <IconOrPictogram />
* </LinkCard.Icon>
* <LinkCard.Title>
* <LinkCard.Anchor href="/href">
* LinkCard title
* </LinkCard.Anchor>
* </LinkCard.Title>
* <LinkCard.Description>
* This is a description of the link card.
* </LinkCard.Description>
* <LinkCard.Footer>Footer content</LinkCard.Footer>
* </LinkCard>
* ```
*/
const LinkCard = Object.assign(LinkCardRoot, {
/**
* @see 🏷️ {@link LinkCardTitleProps}
*/
Title: LinkCardTitle,
/**
* @see 🏷️ {@link LinkCardAnchorProps}
*/
Anchor: LinkCardAnchor,
/**
* @see 🏷️ {@link LinkCardDescriptionProps}
*/
Description: LinkCardDescription,
/**
* @see 🏷️ {@link LinkCardFooterProps}
*/
Footer: LinkCardFooter,
/**
* @see 🏷️ {@link LinkCardIconProps}
*/
Icon: LinkCardIcon,
/**
* @see 🏷️ {@link LinkCardImageProps}
*/
Image: LinkCardImage,
});
export {
LinkCard,
LinkCardTitle,
LinkCardDescription,
LinkCardFooter,
LinkCardAnchor,
LinkCardIcon,
LinkCardImage,
};
export type {
LinkCardProps,
LinkCardTitleProps,
LinkCardDescriptionProps,
LinkCardFooterProps,
LinkCardAnchorProps,
LinkCardIconProps,
LinkCardImageProps,
};
@@ -0,0 +1,41 @@
import React, { type HTMLAttributes, forwardRef } from "react";
import { Heading } from "../../typography";
import { cl } from "../../utils/helpers";
import { useLinkCardContext } from "../root/LinkCardRoot.context";
type LinkCardTitleProps = HTMLAttributes<HTMLHeadingElement> & {
children: React.ReactNode;
/**
* Heading tag. Use "span" if you want a non header defining card
* (eg. you have a lot of them all at once, such as in a grid)
* @default "span"
*/
as?: "span" | "h2" | "h3" | "h4" | "h5" | "h6";
};
/**
* @see 🏷️ {@link LinkCardTitleProps}
*/
const LinkCardTitle = forwardRef<HTMLHeadingElement, LinkCardTitleProps>(
(
{ children, as = "span", className, ...restProps }: LinkCardTitleProps,
forwardedRef,
) => {
const context = useLinkCardContext();
return (
<Heading
ref={forwardedRef}
as={as}
size={context.size === "medium" ? "small" : "xsmall"}
className={cl("aksel-link-card__title", className)}
{...restProps}
>
{children}
</Heading>
);
},
);
export { LinkCardTitle };
export type { LinkCardTitleProps };
+1 -1
View File
@@ -9,7 +9,7 @@ import {
import { Box } from "../primitives/box";
import { VStack } from "../primitives/stack";
import { BodyLong } from "../typography";
import List from "./List";
import { List } from "./index";
export default {
title: "ds-react/List",
+4 -2
View File
@@ -1,4 +1,4 @@
export interface ListProps extends React.HTMLAttributes<HTMLDivElement> {
interface ListProps extends React.HTMLAttributes<HTMLDivElement> {
children: React.ReactNode;
/**
* HTML list element to render.
@@ -16,7 +16,7 @@ export interface ListProps extends React.HTMLAttributes<HTMLDivElement> {
size?: "small" | "medium" | "large";
}
export interface ListItemProps extends React.HTMLAttributes<HTMLLIElement> {
interface ListItemProps extends React.HTMLAttributes<HTMLLIElement> {
/**
* List item content.
*/
@@ -30,3 +30,5 @@ export interface ListItemProps extends React.HTMLAttributes<HTMLLIElement> {
*/
icon?: React.ReactNode;
}
export type { ListProps, ListItemProps };
+2 -2
View File
@@ -1,3 +1,3 @@
"use client";
export { default as List, ListItem } from "./List";
export type { ListProps, ListItemProps } from "./List";
export { List, ListItem } from "./root/ListRoot";
export type { ListProps, ListItemProps } from "./root/ListRoot";
@@ -1,14 +1,14 @@
import React, { forwardRef, useContext } from "react";
import { BodyLong } from "../typography";
import { cl } from "../utils/helpers";
import { consoleWarning } from "../utils/helpers/consoleWarning";
import { ListContext } from "./List.context";
import type { ListItemProps } from "./List.types";
import { BodyLong } from "../../typography";
import { cl } from "../../utils/helpers";
import { consoleWarning } from "../../utils/helpers/consoleWarning";
import type { ListItemProps } from "../List.types";
import { ListContext } from "../root/ListRoot.context";
/**
* @see 🏷️ {@link ListItemProps}
*/
export const ListItem = forwardRef<HTMLLIElement, ListItemProps>(
const ListItem = forwardRef<HTMLLIElement, ListItemProps>(
({ className, children, title, icon, ...rest }, ref) => {
const { listType, size } = useContext(ListContext);
@@ -57,4 +57,6 @@ export const ListItem = forwardRef<HTMLLIElement, ListItemProps>(
);
ListItem.displayName = "List.Item";
export default ListItem;
export { ListItem };
export type { ListItemProps };
@@ -1,12 +1,14 @@
import { createContext } from "react";
import type { ListProps } from "./List.types";
import type { ListProps } from "../List.types";
interface ListContextProps {
listType: Exclude<ListProps["as"], undefined>;
size: Exclude<ListProps["size"], undefined>;
}
export const ListContext = createContext<ListContextProps>({
const ListContext = createContext<ListContextProps>({
listType: "ul",
size: "medium",
});
export { ListContext };
@@ -1,35 +1,11 @@
import React, { forwardRef, useContext } from "react";
import { BodyLong } from "../typography";
import { cl } from "../utils/helpers";
import { ListItem } from "./List.Item";
import { ListContext } from "./List.context";
import type { ListItemProps, ListProps } from "./List.types";
import { BodyLong } from "../../typography";
import { cl } from "../../utils/helpers";
import type { ListItemProps, ListProps } from "../List.types";
import { ListItem } from "../item/ListItem";
import { ListContext } from "./ListRoot.context";
interface ListComponent extends React.ForwardRefExoticComponent<
ListProps & React.RefAttributes<HTMLDivElement>
> {
/**
* @see 🏷️ {@link ListItemProps}
*/
Item: typeof ListItem;
}
/**
* A list component
*
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/list)
* @see 🏷️ {@link ListProps | Props}
*
* @example
* ```jsx
* <List>
* <List.Item>Coffee</List.Item>
* <List.Item>Tea</List.Item>
* <List.Item>Milk</List.Item>
* </List>
* ```
*/
export const List = forwardRef<HTMLDivElement, ListProps>(
const ListRoot = forwardRef<HTMLDivElement, ListProps>(
(
{
children,
@@ -71,10 +47,29 @@ export const List = forwardRef<HTMLDivElement, ListProps>(
</ListContext.Provider>
);
},
) as ListComponent;
);
List.Item = ListItem;
/**
* A list component
*
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/list)
* @see 🏷️ {@link ListProps | Props}
*
* @example
* ```jsx
* <List>
* <List.Item>Coffee</List.Item>
* <List.Item>Tea</List.Item>
* <List.Item>Milk</List.Item>
* </List>
* ```
*/
const List = Object.assign(ListRoot, {
/**
* @see 🏷️ {@link ListItemProps}
*/
Item: ListItem,
});
export default List;
export { ListItem };
export { List, ListItem };
export type { ListProps, ListItemProps };