Skip to content
Open
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 .yarn/versions/plan-display-plans-2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
releases:
"@nimbus-ds/plan-display": minor

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This should be a major release, not minor. This PR removes the public highlighted prop from PlanDisplay.Card (see planDisplayCard.types.ts), which the CHANGELOG in this same PR correctly labels a "πŸ› /⚠️ Breaking change" and versions as 2.0.0. A minor release entry here means yarn bump:stable will actually bump @nimbus-ds/plan-display to 1.2.0, not 2.0.0 β€” contradicting the CHANGELOG and shipping a breaking API removal under a non-major version, which silently breaks consumers pinned with ^1.x.

This is also exactly the case the repo's own .coderabbit.yaml custom_checks.Unversioned Breaking Changes is designed to catch: "All breaking changes MUST have a MAJOR version bump (x.0.0)."

Please change this to:

releases:
  "@nimbus-ds/plan-display": major
  "@nimbus-ds/patterns-webpack": patch

"@nimbus-ds/patterns-webpack": patch

declined:
- nimbus-patterns
2 changes: 1 addition & 1 deletion packages/core/webpack/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"sideEffects": false,
"scripts": {
"clean": "rm -rf dist",
"g:webpack": "cd $INIT_CWD && webpack"
"g:webpack": "cd \"$INIT_CWD\" && webpack"
},
"homepage": "https://nimbus.nuvemshop.com.br/documentation",
"repository": {
Expand Down
12 changes: 11 additions & 1 deletion packages/react/src/components/PlanDisplay/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
# Changelog

EmptyApp allows the user to build marketing-style landing pages for apps. It features internal components meant to build hero sections, content sections with images and text with features, and payment plans.
PlanDisplay allows the user to present subscription plans and pricing. It features internal components to build plan cards with headers, prices, feature bullets, and footers β€” including featured-plan and gradient variants.

## 2026-06-11 `2.0.0`

### ✨ Features

- Reworked `PlanDisplay` to the Plans 2.0 design as the canonical layout, matching the Figma master component. `PlanDisplay.Card` now renders the default card with a level-2 shadow, plus two opt-in variants: `ribbonLabel` (a "MΓ‘s escogido" ribbon with a primary 2px border) and `gradient` (primary-surface β†’ background gradient). Added a `tag` slot on `PlanDisplay.Header` (e.g. "Plan actual"), a new `PlanDisplay.Price` subcomponent (current price + optional previous price, period and annual note), a `badge` slot on `PlanDisplay.Bullet` (e.g. "Nuevo"), and an `icon` slot on `PlanDisplay.Footer` for add-on rows. (by [@noecondoleo](https://github.com/noecondoleo))

### ⚠️ Breaking changes

- Removed the legacy `highlighted` prop on `PlanDisplay.Card`. Use `ribbonLabel` for the featured plan or `gradient` for the gradient variant. (by [@noecondoleo](https://github.com/noecondoleo))
Comment on lines +7 to +13

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

πŸ“ Maintainability & Code Quality | 🟠 Major | ⚑ Quick win

Fix CHANGELOG format to match repository contract (headers + PR references).

Line 7–13 uses non-standard headers and omits mandatory PR references in entries. Please switch to the required category names/emojis and include ([#PR](full-url) by [@contributor](profile-url)) for each item.

As per coding guidelines, "Use the following category headers with emojis: πŸ›  Breaking changes, πŸŽ‰ New features, πŸ› Bug fixes, πŸ“š 3rd party library updates, and πŸ’‘ Others" and "Each changelog entry must follow the format: Description... ([#PR](...) by [@contributor](...))". As per path instructions, "Ensure CHANGELOG.md updates follow the established format ... Each entry must include PR reference and author."

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react/src/components/PlanDisplay/CHANGELOG.md` around lines 7 - 13,
The CHANGELOG.md file uses non-standard headers and incomplete PR references
that don't match the repository contract. Update the category headers from "###
✨ Features" to "### πŸŽ‰ New features" and from "### ⚠️ Breaking changes" to "###
πŸ›  Breaking changes". Ensure each changelog entry follows the required format by
including the complete PR reference as `([`#PR`](full-url) by
[`@contributor`](profile-url))` at the end of each item description. The entries
mentioning the PlanDisplay rework and the highlighted prop removal need to have
their PR references properly formatted with the full GitHub URL and contributor
profile URL.

Sources: Coding guidelines, Path instructions

Comment on lines +7 to +13

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Formatting deviates from .cursor/rules/changelogs.mdc in two ways that matter beyond nitpicking, because the second one is also literally what the repo's custom_checks."Breaking Change Documentation Verification" cross-references for:

  1. Category headers should be #### (not ###) with the exact emojis πŸŽ‰ New features and πŸ›  Breaking changes (not ✨ Features / ⚠️ Breaking changes). The automated check looks for the literal string "πŸ›  Breaking changes" β€” as written, this section won't match it.
  2. Entries are missing the PR reference link. The rule's required format is - Description. ([#PR](url) by [@contributor](url)); both bullets here only have the contributor link, no [#170](https://github.com/TiendaNube/nimbus-patterns/pull/170).

Suggested fix:

#### πŸŽ‰ New features

- Reworked `PlanDisplay` to the Plans 2.0 design... ([#170](https://github.com/TiendaNube/nimbus-patterns/pull/170) by [@noecondoleo](https://github.com/noecondoleo))

#### πŸ›  Breaking changes

- Removed the legacy `highlighted` prop on `PlanDisplay.Card`... ([#170](https://github.com/TiendaNube/nimbus-patterns/pull/170) by [@noecondoleo](https://github.com/noecondoleo))


## 2026-01-15 `1.1.1`

Expand Down
54 changes: 54 additions & 0 deletions packages/react/src/components/PlanDisplay/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,57 @@ $ yarn add @nimbus-ds/plan-display
# or
$ npm install @nimbus-ds/plan-display
```

## Subcomponents

- `PlanDisplay.Card` β€” wraps a single plan. By default it renders the card with a level-2 shadow.
Pass `ribbonLabel` to render the featured ribbon on top (e.g. `"MΓ‘s escogido"`) with a primary 2px
border, or `gradient` for the gradient background variant.
- `PlanDisplay.Header` β€” renders the plan `subtitle` and `title`. Pass an optional `tag` slot (e.g. a
`<Tag>` like `"Plan actual"`) shown to the right of the subtitle.
- `PlanDisplay.Price` β€” renders the `price` and optional `previousPrice` (line-through), `period`
(e.g. `"/mes"`) and `annualNote`. Typically passed as the Header `title`.
- `PlanDisplay.Content` β€” wraps the plan bullets and supporting content.
- `PlanDisplay.Bullet` β€” a feature row with an `icon`, optional `disabled` state and an optional inline
`badge` slot (e.g. a `<Tag>` like `"Nuevo"`).
- `PlanDisplay.Footer` β€” bottom area. Pass an optional `icon` to render an add-on style row (icon + link).
- `PlanDisplay.Spacing` β€” a standalone divider line. Note that `PlanDisplay.Content` already renders
this divider at its top to separate the price block from the plan description (as in the Figma master),
so you usually don't need to add it manually.

## Plans 2.0 example

```tsx
import { PlanDisplay } from "@nimbus-ds/plan-display";
import { Box, Button, Link, Tag, Text } from "@nimbus-ds/components";
import { CashierIcon, CheckIcon } from "@nimbus-ds/icons";

<PlanDisplay minPlanWidth="236px">
<PlanDisplay.Card ribbonLabel="MΓ‘s escogido">
<PlanDisplay.Header
subtitle="Avanzado"
title={<PlanDisplay.Price price="$999" period="/mes" />}
/>
<PlanDisplay.Content>
<Box display="flex" flexDirection="column" gap="3" pb="2">
<Text>GestiΓ³n avanzada y control total para tu negocio.</Text>
<Button appearance="primary" fullWidth>
Subir de plan
</Button>
</Box>
<PlanDisplay.Bullet icon={<CheckIcon />} badge={<Tag appearance="primary">Nuevo</Tag>}>
Hasta 3 tablas de precios mayoristas
</PlanDisplay.Bullet>
</PlanDisplay.Content>
<PlanDisplay.Footer icon={<CashierIcon />}>
<Text>
ObtΓ©n{" "}
<Link as="a" href="#" appearance="primary" textDecoration="underline">
Punto de venta Plus
</Link>{" "}
extra por $149.90/mes
</Text>
</PlanDisplay.Footer>
</PlanDisplay.Card>
</PlanDisplay>;
```
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
PlanDisplayCardContent,
PlanDisplayCardFooter,
PlanDisplayCardHeader,
PlanDisplayCardPrice,
PlanDisplayCardSpacing,
} from "./components";

Expand All @@ -23,7 +24,7 @@ const PlanDisplay: React.FC<PlanDisplayProps> & PlanDisplayComponents = ({
gridTemplateColumns={`repeat(auto-fit, minmax(${minPlanWidth}, 1fr))`}
gap="6"
justifyContent="center"
alignItems="flex-start"
alignItems="stretch"
{...rest}
>
{children}
Expand All @@ -32,6 +33,7 @@ const PlanDisplay: React.FC<PlanDisplayProps> & PlanDisplayComponents = ({

PlanDisplay.Card = PlanDisplayCard;
PlanDisplay.Header = PlanDisplayCardHeader;
PlanDisplay.Price = PlanDisplayCardPrice;
PlanDisplay.Content = PlanDisplayCardContent;
PlanDisplay.Spacing = PlanDisplayCardSpacing;
PlanDisplay.Bullet = PlanDisplayBullet;
Expand All @@ -41,6 +43,7 @@ PlanDisplay.displayName = "PlanDisplay";
PlanDisplay.Card.displayName = "PlanDisplay.Card";
PlanDisplay.Bullet.displayName = "PlanDisplay.Bullet";
PlanDisplay.Header.displayName = "PlanDisplay.CardHeader";
PlanDisplay.Price.displayName = "PlanDisplay.Price";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: PlanDisplay.Price's generated docs come out empty because of this displayName.

Every other subcomponent here sets its public displayName to its internal component name so it matches the <Name>Properties type the docs generator (@nimbus-ds/scripts Docgen) looks up β€” e.g. PlanDisplay.Header.displayName = "PlanDisplay.CardHeader" resolves to PlanDisplayCardHeaderProperties. Price breaks that pattern: it's set to the public name "PlanDisplay.Price", so the generator looks for a type named PlanDisplayPriceProperties, but the actual exported type is PlanDisplayCardPriceProperties (in planDisplayCardPrice.types.ts).

I confirmed this by running yarn build:docs on this branch β€” it prints:

⚠️  Schema generation skipped for PlanDisplayPriceProperties: type PlanDisplayPriceProperties not found

and the resulting planDisplay.docs.json entry for PlanDisplay.Price comes out as {"name": "PlanDisplay.Price", "totalProps": 0, "props": []} β€” the flagship new subcomponent of this PR ships with zero documented props.

Also, planDisplay.docs.json itself was never regenerated in this PR at all (its diff against master is empty), even though props were added/removed/renamed across Card, Bullet, Header. Per .cursor/rules/json-docs.mdc this file must be regenerated via yarn build:docs after type changes β€” right now the published docs still show the removed highlighted prop and are missing gradient, ribbonLabel, tag, badge, icon, and PlanDisplay.Price entirely.

Fix: rename this to PlanDisplay.Price.displayName = "PlanDisplay.CardPrice"; (matching the type name), then run yarn build:docs and commit the regenerated planDisplay.docs.json.

PlanDisplay.Content.displayName = "PlanDisplay.CardContent";
PlanDisplay.Spacing.displayName = "PlanDisplay.CardSpacing";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,45 @@ import { PlanDisplayBulletProps } from "./planDisplayBullet.types";
const PlanDisplayBullet: React.FC<PlanDisplayBulletProps> = ({
icon,
disabled,
badge,
children,
}) => (
<Box display="flex" gap="2">
<Box display="flex" gap="2" alignItems="center">
<Box display="flex" alignItems="center">
<Text
as="span"
color={disabled ? "neutral-interactive" : "success-interactive"}
color={disabled ? "neutral-interactive" : "primary-interactive"}
>
<Box display="flex" alignItems="center">
{icon}
</Box>
Comment on lines 14 to 19

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟑 Minor | ⚑ Quick win

Avoid nesting a default Box inside Text as="span".

On Line 17, Box is rendered inside Text as="span". If Box renders as a block element, this creates invalid HTML nesting and can cause inconsistent layout/semantics. Render that inner Box as a span (or remove the wrapper).

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/react/src/components/PlanDisplay/src/components/PlanDisplayBullet/PlanDisplayBullet.tsx`
around lines 14 - 19, The Box component is nested inside the Text component that
renders as a span (inline element), which creates invalid HTML when Box renders
as a block element. To fix this, modify the Box component to render as a span by
adding the as="span" prop, or if the Box wrapper is not necessary for layout
purposes, remove it entirely and place the icon directly inside the Text
component with appropriate flex styling applied to the Text element instead.

</Text>
</Box>
<Text
fontWeight="medium"
color={disabled ? "neutral-interactive" : "neutral-textLow"}
>
{children}
</Text>
{badge ? (
<Text
fontWeight="regular"
color={disabled ? "neutral-interactive" : "neutral-textLow"}
>
{children}{" "}
{/* Native span: Box drops `style`, so `vertical-align` would never apply on a Box. */}
<span
style={{
display: "inline-flex",
gap: "var(--nimbus-spacing-2)",
verticalAlign: "middle",
}}
>
{badge}
</span>
</Text>
) : (
<Text
fontWeight="regular"
color={disabled ? "neutral-interactive" : "neutral-textLow"}
>
{children}
</Text>
)}
</Box>
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ import { PropsWithChildren, ReactNode } from "react";
export type PlanDisplayBulletProperties = {
icon: ReactNode;
disabled?: boolean;
/**
* Optional slot rendered inline after the bullet text (e.g. a `<Tag>` like "Nuevo").
* @TJS-type React.ReactNode
*/
badge?: ReactNode;
Comment on lines 3 to +10

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

πŸ“ Maintainability & Code Quality | 🟠 Major | ⚑ Quick win

Use an interface for PlanDisplayBulletProperties in .types.ts.

PlanDisplayBulletProperties is currently declared as a type alias. Repository rules for *.types.ts require ComponentNameProperties to be an exported interface.

As per coding guidelines, "**/*.types.ts: Export a ComponentNameProperties interface following PascalCase convention..." As per path instructions, "**/*.types.ts: Always export a 'ComponentNameProperties' interface..."

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/react/src/components/PlanDisplay/src/components/PlanDisplayBullet/planDisplayBullet.types.ts`
around lines 3 - 10, Convert the PlanDisplayBulletProperties type alias to an
exported interface. Replace the type keyword and assignment syntax with
interface syntax, keeping all the properties (icon, disabled, badge) and their
JSDoc comments intact. This aligns with the repository's coding guidelines that
require ComponentNameProperties to be exported as an interface in *.types.ts
files.

Sources: Coding guidelines, Path instructions

};
export type PlanDisplayBulletProps =
PropsWithChildren<PlanDisplayBulletProperties>;
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,16 @@ describe("GIVEN <PlanDisplayBullet />", () => {
expect(screen.getByText("Plan display bullet")).toBeDefined();
expect(screen.getByTestId("icon-id")).toBeDefined();
});

it("SHOULD render the badge slot when provided", () => {
makeSut({
icon,
children: "Plan display bullet",
badge: <span data-testid="badge-id">Nuevo</span>,
});

expect(screen.getByText("Plan display bullet")).toBeDefined();
expect(screen.getByTestId("badge-id")).toBeDefined();
});
});
});
Original file line number Diff line number Diff line change
@@ -1,23 +1,78 @@
import React from "react";
import { Box, BoxProps, Card } from "@nimbus-ds/components";
import { Box, Text } from "@nimbus-ds/components";
import { PlanDisplayCardProps } from "./planDisplayCard.types";

const GRADIENT_BACKGROUND =
"linear-gradient(194.55deg, var(--nimbus-colors-primary-surface) 4.18%, var(--nimbus-colors-neutral-background) 45.97%)";

const PlanDisplayCard: React.FC<PlanDisplayCardProps> = ({
highlighted,
ribbonLabel,
gradient,
children,
style,
...rest
}) => {
const highlightedProps: BoxProps = {
borderColor: "primary-interactive",
borderRadius: "2",
borderStyle: "solid",
borderWidth: "3",
boxShadow: "3",
};
const hasRibbon = Boolean(ribbonLabel);

return (
<Box {...(highlighted ? highlightedProps : {})} {...rest}>
<Card>{children}</Card>
<Box {...rest} display="flex" flexDirection="column" height="100%">
{/* Ribbon area: visible when `ribbonLabel` is set, an invisible placeholder of the
same height otherwise. Rendering it on every card reserves identical top space so
the content rows (subtitle, price, bullets) align across the grid. */}
<div
aria-hidden={hasRibbon ? undefined : true}
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
backgroundColor: hasRibbon
? "var(--nimbus-colors-primary-interactive)"
: "transparent",
paddingTop: "var(--nimbus-spacing-0-5)",
paddingBottom: "var(--nimbus-spacing-2)",
borderTopLeftRadius: "var(--nimbus-shape-border-radius-2)",
borderTopRightRadius: "var(--nimbus-shape-border-radius-2)",
marginBottom: "calc(var(--nimbus-spacing-2) * -1)",
}}
>
<Text fontWeight="medium" color="neutral-background" textAlign="center">
{ribbonLabel || "Β "}
</Text>
</div>
{/* Content grows to fill the card height (cards in a row stretch to match), so footers
anchored with `margin-top: auto` line up at the bottom. */}
<Box
flex="1 1 auto"
display="flex"
flexDirection="column"
backgroundColor="neutral-background"
borderRadius="2"
overflow="hidden"
{...(hasRibbon
? {
borderColor: "primary-interactive",
borderStyle: "solid",
borderWidth: "2",
}
: { boxShadow: "2" })}
>
{/* Content lives in a native element because @nimbus-ds/components Box drops the
`style` prop (it renders only sprinkle-generated styles). Routing the gradient
background and any consumer `style` through here is the only way they take effect.
The div fills the card as a flex column so footers still anchor with `margin-top: auto`. */}
<div
style={{
display: "flex",
flexDirection: "column",
flex: "1 1 auto",
padding: "var(--nimbus-spacing-4)",
background: gradient && !hasRibbon ? GRADIENT_BACKGROUND : undefined,
...style,
}}
>
{children}
</div>
</Box>
</Box>
Comment on lines +18 to 76

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This drops the Card import from @nimbus-ds/components entirely and rebuilds the card chrome (shadow, border, radius, overflow, ribbon, gradient) with two native <div>s and hand-written inline styles / raw CSS variables. That's a direct conflict with two explicit project rules: .cursor/rules/components.mdc ("We do NOT use custom styles in this repository. NEVER use custom SCSS/CSS") and .coderabbit.yaml's review instructions ("Consistent usage of @nimbus-ds/components, instead of native HTML elements").

I verified the stated justification in the comment on lines 59-62 β€” @nimbus-ds/components' Box really does silently discard the style prop at runtime (its dist/Box/index.js destructures style from props and then never uses it, using only the sprinkle-generated style instead) β€” so falling back to a native element for margin-top: auto / a custom gradient is a real, verified constraint, not an invented one. Given that, this is more of a design-system limitation than a bug in this PR.

That said, worth reconsidering before merging:

  • Several of the values hardcoded here as raw CSS vars (backgroundColor, paddingTop/paddingBottom, borderTopLeftRadius/borderTopRightRadius) are expressible through Box's own token props (backgroundColor, pt/pb) β€” only the per-corner radius and the gradient/margin-top:auto seem to genuinely need the escape hatch. Narrowing the native-element usage to just what's unavoidable would reduce the surface that's now unstyleable via the design system's normal props.
  • PlanDisplayCardContent (unchanged in this PR) still renders <Card.Body> as a child of this now-Card-less wrapper. Card.Body doesn't require a <Card> ancestor to render correctly (confirmed β€” it's a self-contained padded box), but it's a bit of an inconsistent signal to keep the DS's Card.Body while replacing its sibling <Card> with hand-rolled divs.
  • Since Box silently ignoring style is a real footgun (it's still typed as accepted, so this compiles fine while doing nothing), it might be worth filing that as an issue against @nimbus-ds/components β€” other component authors are likely to hit this same silent no-op.

);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,17 @@ describe("GIVEN <PlanDisplayCard />", () => {
expect(screen.getByText("Body content")).toBeDefined();
});

it("SHOULD render highlighted state correctly", () => {
makeSut({ highlighted: true });
it("SHOULD render the gradient variant correctly", () => {
makeSut({ gradient: true });

expect(screen.getByText("Body content")).toBeDefined();
});

it("SHOULD render the ribbon label when provided", () => {
makeSut({ ribbonLabel: "MΓ‘s escogido" });

expect(screen.getByText("MΓ‘s escogido")).toBeDefined();
expect(screen.getByText("Body content")).toBeDefined();
});
Comment on lines +21 to +32

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

πŸ“ Maintainability & Code Quality | πŸ”΅ Trivial | ⚑ Quick win

Add a precedence test for ribbonLabel over gradient.

The public contract documents that ribbonLabel overrides gradient, but this behavior is not directly tested. Add a case passing both props and asserting the ribbon path is rendered.

πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/react/src/components/PlanDisplay/src/components/PlanDisplayCard/planDisplayCard.spec.tsx`
around lines 21 - 32, Add a new test case in the planDisplayCard.spec.tsx file
that verifies the precedence behavior where ribbonLabel overrides gradient when
both props are provided. Create a test using makeSut that passes both gradient:
true and a ribbonLabel value, then assert that the ribbon label text is
rendered, confirming that the ribbon takes precedence over the gradient variant
as documented in the public contract.

});
});
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
import { BoxProps } from "@nimbus-ds/components";
import { PropsWithChildren } from "react";
import { PropsWithChildren, ReactNode } from "react";

export interface PlanDisplayCardProperties {
highlighted?: boolean;
/**
* Renders the card with the Plans 2.0 gradient background (primary-surface β†’ neutral-background).
* Ignored when `ribbonLabel` is provided.
*/
gradient?: boolean;
/**
* Label shown in a ribbon on top of the card (e.g. "MΓ‘s escogido"). When provided, the card
* is rendered with the featured style (primary ribbon + 2px primary border, no shadow),
* taking precedence over `gradient`.
* @TJS-type React.ReactNode
*/
ribbonLabel?: ReactNode;
}
export type PlanDisplayCardProps =
PropsWithChildren<PlanDisplayCardProperties> &
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const PlanDisplayCardContent: React.FC<PlanDisplayCardContentProperties> = ({
<Card.Body>
<PlanDisplayCardSpacing />

<Box display="flex" flexDirection="column" gap="1" py="4">
<Box display="flex" flexDirection="column" gap="3">
{children}
</Box>
</Card.Body>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,32 @@
import React from "react";
import { Box } from "@nimbus-ds/components";
import { Box, Divider, Text } from "@nimbus-ds/components";
import { PlanDisplayCardFooterProperties } from "./planDisplayCardFooter.types";
import PlanDisplayCardSpacing from "../PlanDisplayCardSpacing";

const PlanDisplayCardFooter: React.FC<PlanDisplayCardFooterProperties> = ({
icon,
children,
}) => (
<Box display="flex" flexDirection="column" gap="4">
<PlanDisplayCardSpacing />
// `marginTop: auto` pins the footer to the bottom of the (full-height, flex-column) card
// so footers stay aligned across cards in the same row. Native element on purpose: Box
// discards `style`, so the `margin-top: auto` would be a no-op on a Box.
<div style={{ display: "flex", flexDirection: "column", marginTop: "auto" }}>
<Box mt="3" mb="3">
<Divider />
</Box>

<Box>{children}</Box>
</Box>
{icon ? (
<Box display="flex" gap="2" alignItems="flex-start">
<Text as="span" color="primary-interactive">
<Box display="flex" alignItems="center">
{icon}
</Box>
</Text>
<Box>{children}</Box>
</Box>
) : (
<Box>{children}</Box>
)}
</div>
Comment on lines +12 to +29

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Same native-<div>-for-style pattern as PlanDisplayCard (justified β€” Box does drop style, see the other comment on PlanDisplayCard.tsx), but this one also drops the existing <PlanDisplayCardSpacing /> subcomponent in favor of re-implementing the same divider inline (<Box mt="3" mb="3"><Divider /></Box> here vs. PlanDisplayCardSpacing, which after this PR is literally <Box my="3"><Divider /></Box>). Minor, but it's now duplicated in two places with slightly different spacing props (mt+mb vs my) for what should be the same visual divider β€” worth reusing <PlanDisplayCardSpacing /> here instead, both for DRYness and so a future spacing change doesn't need to be made in two files.

);

export { PlanDisplayCardFooter };
Loading
Loading