Skip to content

feat: add MetricCard component for dashboard KPIs#92

Open
itsprade wants to merge 3 commits intomainfrom
feat/metriccard
Open

feat: add MetricCard component for dashboard KPIs#92
itsprade wants to merge 3 commits intomainfrom
feat/metriccard

Conversation

@itsprade
Copy link
Contributor

  • Add MetricCard with label, value, optional trend, comparison, and icon
  • Match DescriptionCard border style; icon uses label (muted) color
  • Export MetricCard and MetricCardProps from core
  • Add tests, docs (metric-card.md), and demo in app-module (horizontal flex row)
  • Add minor changeset for @tailor-platform/app-shell

- Add MetricCard with label, value, optional trend, comparison, and icon
- Match DescriptionCard border style; icon uses label (muted) color
- Export MetricCard and MetricCardProps from core
- Add tests, docs (metric-card.md), and demo in app-module (horizontal flex row)
- Add minor changeset for @tailor-platform/app-shell

Made-with: Cursor
@itsprade
Copy link
Contributor Author

/review

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Generated by API Design Review for issue #92

@@ -0,0 +1,2 @@
export { MetricCard, default } from "./MetricCard";
export type { MetricCardProps } from "./types";
Copy link
Contributor

Choose a reason for hiding this comment

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

[1/2 — Low] MetricCardTrend and MetricCardTrendDirection not exported from public API

These constructible sub-types are defined in types.ts and exported there, but they're never re-exported at the component barrel or the package root — inconsistent with the established pattern for other components:

Component Constructible sub-type Exported from barrel?
ActionPanel ActionItem
DescriptionCard FieldConfig, FieldDefinition, etc.
MetricCard MetricCardTrend

A consumer who wants to store or compute a trend value with an explicit type currently has no clean way to do it:

// ❌ No exported type — deep imports are unsupported
import type { MetricCardTrend } from "`@tailor-platform/app-shell`/dist/...";

// ✅ Workaround (verbose)
type MetricCardTrend = NonNullable(MetricCardProps["trend"]);

Suggested fix — add both types to the component barrel:

 export { MetricCard, default } from "./MetricCard";
-export type { MetricCardProps } from "./types";
+export type { MetricCardProps, MetricCardTrend, MetricCardTrendDirection } from "./types";

And update the package root (packages/core/src/index.ts):

-export { MetricCard, type MetricCardProps } from "./components/metric-card";
+export { MetricCard, type MetricCardProps, type MetricCardTrend, type MetricCardTrendDirection } from "./components/metric-card";

Not blocking — consumers can work around it with NonNullable(MetricCardProps["trend"]) — but exporting the named type is more ergonomic and consistent with peers.

value: ReactNode;
/** Optional trend indicator */
trend?: MetricCardTrend;
/** Optional comparison text (e.g. "vs last period") */
Copy link
Contributor

Choose a reason for hiding this comment

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

[2/2 — Low] comparison="" silently suppressed — undocumented behavior

The implementation normalizes empty string to absent:

const hasMeta = trend != null || (comparison != null && comparison !== "");
// ...
{comparison != null && comparison !== "" && <span>{comparison}</span>}

This means passing comparison="" has the same effect as omitting the prop entirely — the comparison text is not rendered. But the prop type and JSDoc give no indication of this:

/** Optional comparison text (e.g. "vs last period") */
comparison?: string;

Consider documenting the empty-string normalization in the JSDoc:

/**
 * Optional comparison text (e.g. "vs last period").
 * Empty strings are treated as absent and the comparison row is not rendered.
 */
comparison?: string;

Alternatively, narrow the type to string & {} or use undefined-only absence if empty strings are truly unsupported. As-is, callers can pass comparison="" expecting a render and get nothing — a subtle footgun.

- Document empty-string behavior for comparison in docs and JSDoc
- Add test for comparison="" and simplify meta row assertions (single assertion)
- Remove semicolon from changeset code sample
- Vary fourth demo card (Revenue MTD) in example app

Made-with: Cursor
@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 18, 2026

Open in StackBlitz

npm i https://pkg.pr.new/@tailor-platform/app-shell@92
npm i https://pkg.pr.new/@tailor-platform/app-shell-vite-plugin@92

commit: 6046839

@itsprade itsprade requested a review from IzumiSy March 18, 2026 09:24
@IzumiSy
Copy link
Contributor

IzumiSy commented Mar 18, 2026

@itsprade can you run formatter locally? pnpm fmt.

- Update changeset for metric card component
- Update metric-card documentation
- Update custom module example usage
- Update MetricCard tests

Made-with: Cursor
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants