Skip to content
Draft
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
5 changes: 5 additions & 0 deletions .changeset/light-pillows-marry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@salt-ds/core": minor
---

Add `CardContent` to make it easier to build cards with full-width images.
7 changes: 6 additions & 1 deletion packages/core/src/card/Card.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@
.saltCard {
border-width: var(--saltCard-borderWidth, var(--salt-size-border));
border-style: var(--salt-container-borderStyle);
padding: var(--saltCard-padding, var(--salt-spacing-200));
padding: var(--saltCard-padding, var(--card-padding));
position: relative;
transition: box-shadow var(--salt-duration-instant) ease-in-out;
box-sizing: border-box;
border-radius: var(--saltCard-borderRadius, var(--salt-palette-corner, 0));
}

.saltCard:not(:has(.saltCardContent)),
.saltCard-padding {
--card-padding: var(--salt-spacing-200);
}

.saltCard-primary {
background: var(--saltCard-background, var(--salt-container-primary-background));
border-color: var(--salt-container-primary-borderColor);
Expand Down
48 changes: 27 additions & 21 deletions packages/core/src/card/Card.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { useComponentCssInjection } from "@salt-ds/styles";
import { useWindow } from "@salt-ds/window";
import { clsx } from "clsx";
import { type ComponentPropsWithoutRef, forwardRef } from "react";
import { type ComponentPropsWithoutRef, forwardRef, useState } from "react";

import { capitalize, makePrefixer } from "../utils";
import { capitalize, makePrefixer, useForkRef } from "../utils";

import cardCss from "./Card.css";
import { CardContext } from "./CardContext";

const withBaseName = makePrefixer("saltCard");
export interface CardProps extends ComponentPropsWithoutRef<"div"> {
Expand Down Expand Up @@ -56,26 +57,31 @@ export const Card = forwardRef<HTMLDivElement, CardProps>(
window: targetWindow,
});

const [padding, setPadding] = useState(true);

return (
<div
className={clsx(
withBaseName(),
withBaseName(variant),
{
[withBaseName("accent")]: accent,
[withBaseName(`accent${capitalize(accent || "")}`)]: accent,
[withBaseName("hoverable")]: hoverable,
/* **Deprecated:** InteractableCard should be used instead for these features */
[withBaseName("disabled")]: disabled,
[withBaseName("interactable")]: interactable,
},
className,
)}
ref={ref}
{...rest}
>
{children}
</div>
<CardContext.Provider value={{ setPadding }}>
<div
className={clsx(
withBaseName(),
withBaseName(variant),
{
[withBaseName("accent")]: accent,
[withBaseName(`accent${capitalize(accent || "")}`)]: accent,
[withBaseName("hoverable")]: hoverable,
/* **Deprecated:** InteractableCard should be used instead for these features */
[withBaseName("disabled")]: disabled,
[withBaseName("interactable")]: interactable,
[withBaseName("padding")]: padding,
},
className,
)}
ref={ref}
{...rest}
>
{children}
</div>
</CardContext.Provider>
);
},
);
3 changes: 3 additions & 0 deletions packages/core/src/card/CardContent.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.saltCardContent {
padding: var(--saltCardContent-padding, var(--salt-spacing-200));
}
38 changes: 38 additions & 0 deletions packages/core/src/card/CardContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { useComponentCssInjection } from "@salt-ds/styles";
import { useWindow } from "@salt-ds/window";
import { clsx } from "clsx";
import { type ComponentPropsWithRef, forwardRef, useLayoutEffect } from "react";
import { makePrefixer, useIsomorphicLayoutEffect } from "../utils";
import cardContentCss from "./CardContent.css";
import { useCardContext } from "./CardContext";

export interface CardContentProps extends ComponentPropsWithRef<"div"> {}

const withBaseName = makePrefixer("saltCardContent");

export const CardContent = forwardRef<HTMLDivElement, CardContentProps>(
function CardContent(props, ref) {
const { className, ...rest } = props;

const targetWindow = useWindow();
useComponentCssInjection({
testId: "salt-card-content",
css: cardContentCss,
window: targetWindow,
});

const { setPadding } = useCardContext();

useIsomorphicLayoutEffect(() => {
setPadding(false);

return () => {
setPadding(true);
};
}, [setPadding]);

return (
<div ref={ref} className={clsx(withBaseName(), className)} {...rest} />
);
},
);
14 changes: 14 additions & 0 deletions packages/core/src/card/CardContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { type Dispatch, type SetStateAction, useContext } from "react";
import { createContext } from "../utils";

export interface CardContextValue {
setPadding: Dispatch<SetStateAction<boolean>>;
}

export const CardContext = createContext<CardContextValue>("CardContext", {
setPadding: () => undefined,
});

export function useCardContext() {
return useContext(CardContext);
}
1 change: 1 addition & 0 deletions packages/core/src/card/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./Card";
export * from "./CardContent";
22 changes: 21 additions & 1 deletion packages/core/stories/card/card.qa.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { Card, H1, InteractableCard, Text } from "@salt-ds/core";
import {
Card,
CardContent,
H1,
InteractableCard,
Panel,
Text,
} from "@salt-ds/core";
import type { Meta, StoryFn } from "@storybook/react";
import {
QAContainer,
Expand Down Expand Up @@ -45,6 +52,19 @@ export const AllExamplesUsingText: StoryFn<
<H1>Accent left</H1>
<Text>Content</Text>
</Card>
<Card>
<CardContent>
<H1>Accent left</H1>
<Text>Content</Text>
</CardContent>
</Card>
<Card>
<Panel variant="secondary" style={{ height: 20 }} />
<CardContent>
<H1>Accent left</H1>
<Text>Content</Text>
</CardContent>
</Card>
</QAContainer>
);
};
Expand Down
11 changes: 8 additions & 3 deletions packages/core/stories/card/card.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
Button,
Card,
CardContent,
type CardProps,
H3,
Label,
Expand Down Expand Up @@ -36,16 +37,20 @@ export const Default: StoryFn<typeof Card> = (args) => (

export const DefaultWithImage: StoryFn<typeof Card> = (args) => (
<Card {...args} style={{ width: "260px" }}>
<StackLayout gap={3}>
<img alt="example" src={exampleImage} style={{ width: "100%" }} />
<img
alt="example"
src={exampleImage}
style={{ display: "block", width: "100%" }}
/>
<CardContent>
<StackLayout gap={1}>
<H3>Sustainable investing products</H3>
<Text>
We have a commitment to provide a wide range of investment solutions
to enable you to align your financial goals to your values.
</Text>
</StackLayout>
</StackLayout>
</CardContent>
</Card>
);

Expand Down
2 changes: 1 addition & 1 deletion site/docs/components/card/examples.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ You can incorporate actions like [links](../link) or [buttons](../button) into a
<LivePreview componentName="card" exampleName="FullWidthImage" >
## Full width image

When using images in cards, set the image to span the card's entire width. You can do this by setting `--saltCard-padding: 0` and putting padding around any content below the image.
When using images in cards, set the image to span the card's entire width. You can do this by using the `CardContent` component inside `Card`.

</LivePreview>

Expand Down
7 changes: 6 additions & 1 deletion site/docs/components/card/usage.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,12 @@ Interactable cards serve as a [toggle button](../toggle-button), [button](../but

## Import

To import `Card`, `InteractableCard` and `LinkCard` from the core Salt package, use:
To import `Card`, `CardContent`, `InteractableCard` and `LinkCard` from the core Salt package, use:

```js
import {
Card,
CardContent,
InteractableCard,
InteractableCardGroup,
LinkCard,
Expand All @@ -58,6 +59,10 @@ import {

<PropsTable packageName="core" componentName="Card" />

### `CardContent`

<PropsTable packageName="core" componentName="CardContent" />

### `LinkCard`

<PropsTable packageName="core" componentName="LinkCard" />
Expand Down
16 changes: 5 additions & 11 deletions site/src/examples/card/FullWidthImage.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,16 @@
import { Image } from "@jpmorganchase/mosaic-site-components";
import { Card, H3, Link, StackLayout, Text } from "@salt-ds/core";
import type { CSSProperties, ReactElement } from "react";
import { Card, CardContent, H3, Link, StackLayout, Text } from "@salt-ds/core";
import type { ReactElement } from "react";

export const FullWidthImage = (): ReactElement => {
return (
<Card style={{ "--saltCard-padding": 0, width: "260px" } as CSSProperties}>
<Card style={{ width: "260px" }}>
<Image
src="/img/examples/cardExample.jpg"
alt="placeholder image"
style={{ width: "100%" }}
/>
<StackLayout
// Apply padding around the content below the image for a full width image
style={{
padding: "var(--salt-spacing-200)",
}}
align="start"
>
<CardContent>
<StackLayout gap={1}>
<H3>Sustainable investing products</H3>
<Text>
Expand All @@ -27,7 +21,7 @@ export const FullWidthImage = (): ReactElement => {
<Link href="#" IconComponent={null}>
Learn more
</Link>
</StackLayout>
</CardContent>
</Card>
);
};