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
6 changes: 6 additions & 0 deletions .changeset/vast-facts-press.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@leafygreen-ui/card': minor
'@leafygreen-ui/tokens': minor
---

deprecated clickable styling and functionality, updated styles (removed shadows and added border). Added Tertiary Border color token
29 changes: 23 additions & 6 deletions packages/card/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,28 @@ import Card from '@leafygreen-ui/card';

Card is a styled wrapper for the Box component. Any properties you would pass to Box can also be passed to Card.

| Prop | Type | Description | Default |
| -------------- | ----------------------- | ----------------------------------------------------------------- | ----------------------------------------------------------------------- |
| `children` | `React.ReactNode` | Content rendered inside of the `<Card />` component | |
| `className` | `string` | Adds a className to the class attribute | |
| `contentStyle` | `'none'`, `'clickable'` | Whether the card should display as a visually clickable element. | `'clickable'` when a valid `onClick` handler or `href` link is provided |
| `darkMode` | `boolean` | Determines whether or not the component will appear in dark mode. | `false` |
| Prop | Type | Description | Default |
| ----------- | ----------------- | ----------------------------------------------------------------- | ------- |
| `children` | `React.ReactNode` | Content rendered inside of the `<Card />` component | |
| `className` | `string` | Adds a className to the class attribute | |
| `darkMode` | `boolean` | Determines whether or not the component will appear in dark mode. | `false` |

_Any other properties will be spread on the Box element._

## Usage and Interactivity

This component is designed to be a container for other elements and content. Adding `onClick` or `href` directly to the component is discouraged. Instead, we recommend adding interactive elements inside the component.

### DON'T

```js
<Card onClick={handleOnClick}>This is my card component</Card>
```

### DO

```js
<Card>
<CustomCardHeader onClick={handleOnClick} />
</Card>
```
8 changes: 1 addition & 7 deletions packages/card/src/Card.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import { storybookArgTypes, StoryMetaType } from '@lg-tools/storybook-utils';
import { StoryFn } from '@storybook/react';

import { Card, CardProps, ContentStyle } from '.';
import { Card, CardProps } from '.';

const loremIpsum = `Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy children ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.`;

Expand All @@ -14,7 +14,6 @@ const meta: StoryMetaType<typeof Card> = {
generate: {
combineArgs: {
darkMode: [false, true],
contentStyle: ['none', 'clickable'],
},
},
},
Expand All @@ -27,11 +26,6 @@ const meta: StoryMetaType<typeof Card> = {
as: storybookArgTypes.as,
darkMode: storybookArgTypes.darkMode,
children: storybookArgTypes.children,
contentStyle: {
options: Object.values(ContentStyle),
control: { type: 'radio' },
defaultValue: ContentStyle.None,
},
},
};
export default meta;
Expand Down
13 changes: 2 additions & 11 deletions packages/card/src/Card/Card.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import React from 'react';

import { cx } from '@leafygreen-ui/emotion';
import { useDarkMode } from '@leafygreen-ui/leafygreen-provider';
import {
InferredPolymorphic,
PolymorphicAs,
useInferredPolymorphic,
} from '@leafygreen-ui/polymorphic';

import { colorSet, containerStyle } from './styles';
import { getCardStyles } from './styles';
import { ContentStyle, InternalCardProps } from './types';

/**
Expand Down Expand Up @@ -40,15 +39,7 @@ export const Card = InferredPolymorphic<InternalCardProps, 'div'>(
return (
<Component
ref={ref}
className={cx(
containerStyle,
colorSet[theme].containerStyle,
{
[colorSet[theme].clickableStyle]:
contentStyle === ContentStyle.Clickable,
},
className,
)}
className={getCardStyles({ theme, contentStyle, className })}
{...rest}
/>
);
Expand Down
15 changes: 7 additions & 8 deletions packages/card/src/Card/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Theme } from '@leafygreen-ui/lib';
import { palette } from '@leafygreen-ui/palette';
import {
boxShadows,
color,
focusRing,
fontFamilies,
transitionDuration,
Expand All @@ -24,11 +25,10 @@ const darkHoverBoxShadow = boxShadows[Theme.Dark][2];
const lightFocusBoxShadow = focusRing.light.default;
const darkFocusBoxShadow = focusRing.dark.default;

export const colorSet: Record<Theme, ColorSet> = {
const colorSet: Record<Theme, ColorSet> = {
[Theme.Light]: {
containerStyle: css`
border: 1px solid ${palette.gray.light2};
box-shadow: ${lightBaseBoxShadow};
border: 1px solid ${color[Theme.Light].border.tertiary.default};
background-color: ${palette.white};
color: ${palette.gray.dark3};
`,
Expand All @@ -53,8 +53,7 @@ export const colorSet: Record<Theme, ColorSet> = {
},
[Theme.Dark]: {
containerStyle: css`
border: 1px solid ${palette.gray.dark2};
box-shadow: ${darkBaseBoxShadow};
border: 1px solid ${color[Theme.Dark].border.tertiary.default};
background-color: ${palette.black};
color: ${palette.white};
`,
Expand All @@ -77,7 +76,7 @@ export const colorSet: Record<Theme, ColorSet> = {
},
};

export const containerStyle = css`
const containerStyle = css`
position: relative;
transition: ${transitionDuration.default}ms ease-in-out;
transition-property: border, box-shadow;
Expand All @@ -95,13 +94,13 @@ export const getCardStyles = ({
className,
}: {
theme: Theme;
contentStyle: ContentStyle;
contentStyle?: ContentStyle;
className?: string;
}) =>
cx(
containerStyle,
colorSet[theme].containerStyle,
{
[colorSet[theme].containerStyle]: true,
[colorSet[theme].clickableStyle]: contentStyle === ContentStyle.Clickable,
},
className,
Expand Down
21 changes: 21 additions & 0 deletions packages/card/src/Card/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,17 @@ import {
PolymorphicAs,
} from '@leafygreen-ui/polymorphic';

/**
* @deprecated No longer supported. We don't want card to be clickable from a root level.
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The deprecation message should specify what developers should use instead. Consider adding guidance like: "@deprecated No longer supported. We don't want card to be clickable from a root level. If you need clickable behavior, wrap interactive elements inside the Card instead."

Copilot uses AI. Check for mistakes.
*/
export const ContentStyle = {
None: 'none',
Clickable: 'clickable',
} as const;

/**
* @deprecated No longer supported. We don't want card to be clickable from a root level.
*/
export type ContentStyle = (typeof ContentStyle)[keyof typeof ContentStyle];

export interface InternalCardProps extends DarkModeProps {
Expand All @@ -23,6 +29,7 @@ export interface InternalCardProps extends DarkModeProps {
* Defaults to `'clickable'` (when a valid `onClick` handler or `href` link is provided
*
* @default 'clickable' | 'none'
* @deprecated No longer supported. We don't want card to be clickable from a root level. Use interactive elements inside the card instead.
*/
contentStyle?: ContentStyle;

Expand All @@ -31,6 +38,20 @@ export interface InternalCardProps extends DarkModeProps {
*
*/
title?: string;

/**
* Click handler for the Card component.
*
* @deprecated No longer supported. We don't want card to be clickable from a root level. Use interactive elements inside the card instead.
*/
onClick?: React.MouseEventHandler<any>;

/**
* Link for the Card component.
*
* @deprecated No longer supported. We don't want card to be clickable from a root level. Use interactive elements inside the card instead.
*/
href?: string;
}

// External only
Expand Down
1 change: 1 addition & 0 deletions packages/tokens/src/color/color.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const Variant = {
Placeholder: 'placeholder',
Primary: 'primary',
Secondary: 'secondary',
Tertiary: 'tertiary',
InversePrimary: 'inversePrimary',
InverseSecondary: 'inverseSecondary',
Info: 'info',
Expand Down
5 changes: 5 additions & 0 deletions packages/tokens/src/color/darkModeColors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ const darkModeBorderColors = {
[InteractionState.Hover]: gray.dark2,
[InteractionState.Focus]: blue.light1,
},
[Variant.Tertiary]: {
[InteractionState.Default]: gray.dark1,
[InteractionState.Hover]: gray.dark1,
[InteractionState.Focus]: blue.light1,
},
[Variant.Success]: {
[InteractionState.Default]: green.dark1,
[InteractionState.Hover]: green.dark1,
Expand Down
5 changes: 5 additions & 0 deletions packages/tokens/src/color/lightModeColors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ const lightModeBorderColors = {
[InteractionState.Hover]: gray.light2,
[InteractionState.Focus]: blue.light1,
},
[Variant.Tertiary]: {
[InteractionState.Default]: gray.light1,
[InteractionState.Hover]: gray.light1,
[InteractionState.Focus]: blue.light1,
},
[Variant.Success]: {
[InteractionState.Default]: green.dark1,
[InteractionState.Hover]: green.dark1,
Expand Down
Loading