@growth-operations/ui-shared 1.5.1 → 1.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@growth-operations/ui-shared",
3
- "version": "1.5.1",
3
+ "version": "1.5.3",
4
4
  "description": "Shared HubSpot UI-extension modules (Home, Billing) for Growth Operations apps.",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -2,6 +2,7 @@ import React, { useState } from "react";
2
2
  import {
3
3
  hubspot,
4
4
  Flex,
5
+ AutoGrid,
5
6
  Tile,
6
7
  Text,
7
8
  Heading,
@@ -37,7 +38,15 @@ import { fmtMoney } from "../lib/format";
37
38
  // feature bullets, and a CTA: "Current" (disabled) for the active tier,
38
39
  // "Contact sales" for talk_to_sales tiers, else "Choose" which pre-creates a
39
40
  // checkout session for that tier's price in the selected interval.
40
- function PlanCard({ plan, interval, onChoose, choosing, checkoutUrl, supportUrl }) {
41
+ function PlanCard({
42
+ plan,
43
+ interval,
44
+ onChoose,
45
+ choosing,
46
+ checkoutUrl,
47
+ supportUrl,
48
+ maxFeatures = 0,
49
+ }) {
41
50
  const leg = interval === "annual" ? plan.annual : plan.monthly;
42
51
  const periodLabel = interval === "annual" ? "/yr" : "/mo";
43
52
 
@@ -47,25 +56,38 @@ function PlanCard({ plan, interval, onChoose, choosing, checkoutUrl, supportUrl
47
56
  ? `${fmtMoney(leg.unit_amount, leg.currency)}${periodLabel}`
48
57
  : "Free";
49
58
 
59
+ const features = plan.features ?? [];
60
+ // Align the CTA buttons across cards. HubSpot's column Flex does NOT stretch
61
+ // to the Tile height (AutoGrid equalizes the card height but flex={1}/justify
62
+ // had no vertical room to consume — verified in QA), so we can't push the CTA
63
+ // down with flex. Instead make the cards' CONTENT equal height: pad each
64
+ // feature list to maxFeatures with non-breaking-space spacer lines (a plain
65
+ // space collapses to zero height;   renders a real line). The CTAs then
66
+ // naturally land at the same y.
67
+ const padCount = Math.max(0, maxFeatures - features.length);
68
+
50
69
  return (
51
70
  <Tile>
52
71
  <Flex direction="column" gap="small">
53
72
  <Flex direction="row" gap="small" align="center">
54
73
  <Heading>{plan.name ?? plan.tier}</Heading>
55
- {plan.current && <StatusTag variant="success">Current plan</StatusTag>}
74
+ {plan.current && (
75
+ <StatusTag variant="success">Current plan</StatusTag>
76
+ )}
56
77
  </Flex>
57
78
 
58
79
  <Text format={{ fontWeight: "bold", fontSize: "lg" }}>{priceText}</Text>
59
80
 
60
81
  {plan.credits_per_period != null && (
61
- <Text>
62
- {plan.credits_per_period.toLocaleString()} credits / month
63
- </Text>
82
+ <Text>{plan.credits_per_period.toLocaleString()} credits / month</Text>
64
83
  )}
65
84
 
66
- {(plan.features ?? []).map((f, i) => (
85
+ {features.map((f, i) => (
67
86
  <Text key={i}>• {f}</Text>
68
87
  ))}
88
+ {Array.from({ length: padCount }).map((_, i) => (
89
+ <Text key={`pad-${i}`}>{" "}</Text>
90
+ ))}
69
91
 
70
92
  {/* CTA. Current tier => disabled marker. talk_to_sales => contact link.
71
93
  Otherwise pre-create-then-link checkout for the selected interval. */}
@@ -119,6 +141,12 @@ export function PlanGrid({ context, state, appKey }) {
119
141
  if (plans.length === 0) return null;
120
142
 
121
143
  const supportUrl = state?.helpful_links?.supportUrl ?? null;
144
+ // Max feature count across cards — each card pads to this so all cards share
145
+ // content height and the CTAs align (see PlanCard comment).
146
+ const maxFeatures = plans.reduce(
147
+ (m, p) => Math.max(m, (p.features ?? []).length),
148
+ 0
149
+ );
122
150
 
123
151
  async function choose(plan, leg) {
124
152
  setError(null);
@@ -183,7 +211,9 @@ export function PlanGrid({ context, state, appKey }) {
183
211
  </Alert>
184
212
  )}
185
213
 
186
- <Flex direction="row" gap="medium" wrap="wrap">
214
+ {/* AutoGrid(flexible): equal-width columns that wrap responsively. Card
215
+ height is equalized via the maxFeatures padding in PlanCard. */}
216
+ <AutoGrid columnWidth={240} flexible={true} gap="medium">
187
217
  {plans.map((plan) => (
188
218
  <PlanCard
189
219
  key={plan.tier}
@@ -193,9 +223,10 @@ export function PlanGrid({ context, state, appKey }) {
193
223
  choosing={choosingTier === plan.tier}
194
224
  checkoutUrl={checkoutUrls[`${plan.tier}:${interval}`]}
195
225
  supportUrl={supportUrl}
226
+ maxFeatures={maxFeatures}
196
227
  />
197
228
  ))}
198
- </Flex>
229
+ </AutoGrid>
199
230
 
200
231
  <Text format={{ fontStyle: "italic" }}>
201
232
  Billing is managed in Stripe across all Growth Operations apps. Your new