Three new product-agnostic packages unlock visible elegance lifts
across every product:
═══════════════════════════════════════════════════════════════════════
@bytelyst/motion@0.1.0 — Wave 4 elegance primitives (2.21 KB / 8 KB)
═══════════════════════════════════════════════════════════════════════
<Reveal> — IntersectionObserver-based fade/slide entry,
6 directions, configurable spring + delay
<StaggerList> — sequenced reveal of children with per-item delay
<NumberFlow> — RAF-tweened number counter, cubic-out easing,
Intl-formatted, prefers-reduced-motion aware
<TiltCard> — 3D perspective tilt + cursor-tracking glare
overlay (single-element ref, no React rerenders)
<ScrollProgress> — fixed scroll-position bar (window or any element)
Plus exported `SPRINGS` (4 cubic-bezier presets) + `prefersReducedMotion`
helper. Every primitive accepts `disableMotion` for snapshot tests.
═══════════════════════════════════════════════════════════════════════
@bytelyst/data-viz@0.1.0 — Wave 5b viz primitives (2.63 KB / 10 KB)
═══════════════════════════════════════════════════════════════════════
<Sparkline> — line trend with gradient fill + last-point marker
<BarSparkline> — discrete-bar mini-chart with max-bar highlight
<KpiCard> — label + headline + delta arrow + sparkline; supports
'goodWhen=lower' for latency/cost metrics
<ProgressRing> — circular progress with center content slot,
animated stroke-dashoffset
<Heatmap> — GitHub-style calendar grid with color-mix() intensity
All pure SVG / CSS — zero runtime dependencies.
═══════════════════════════════════════════════════════════════════════
@bytelyst/notifications-ui@0.1.0 — Wave 7 essentials (3.31 KB / 10 KB)
═══════════════════════════════════════════════════════════════════════
<NotificationCenter> — bell trigger + badge + dropdown panel with
All / Unread / Mentions tabs, outside-click
+ Escape close, mark-all-read action
<InboxItem> — single row with unread dot, kind glyph,
relative timestamp, optional action buttons
<BannerStack> — top-of-page strip with maxVisible + +N more,
accent-bordered tone variants, dismissible
<Announcement> — inline 'What's new' pill (3 tone variants)
5 notification kinds (info/success/warning/danger/mention) + 5 banner
kinds (... + announcement gradient).
═══════════════════════════════════════════════════════════════════════
Quality gates
═══════════════════════════════════════════════════════════════════════
All three packages: tsc --noEmit clean, build clean.
Tests: motion 16/16 · data-viz 14/14 · notifications-ui 17/17
Bundles: motion 2.21 KB · data-viz 2.63 KB · noti-ui 3.31 KB
Budgets: added to .size-limit.cjs (8/10/10 KB respectively)
Refs:
learning_ai_uxui_web/docs/ROADMAP_2026.md
§Wave 4 (Motion), §Wave 5b (Charts), §Wave 7 (Productisation)
Decisions doc §13 (mobile-native = tokens-only) leaves room for these
web-first packages to be the canonical surface
170 lines
4.7 KiB
TypeScript
170 lines
4.7 KiB
TypeScript
import type { CSSProperties, ReactNode } from 'react';
|
|
import { Sparkline } from './Sparkline.js';
|
|
|
|
export interface KpiCardProps {
|
|
/** Metric label (e.g. 'Active users'). */
|
|
label: string;
|
|
/** Pre-formatted display value (e.g. '12.4k', '$8,421'). */
|
|
value: ReactNode;
|
|
/** Delta percentage (e.g. 6.6 for +6.6%). */
|
|
deltaPercent?: number;
|
|
/** Override the delta label (e.g. 'vs last 7d'). */
|
|
deltaLabel?: string;
|
|
/** Inline trend data. Optional. */
|
|
trend?: number[];
|
|
/** Icon rendered top-right. */
|
|
icon?: ReactNode;
|
|
/** Sparkline stroke override. */
|
|
trendColor?: string;
|
|
/** Hide the up/down arrow next to delta. Default false. */
|
|
hideDeltaArrow?: boolean;
|
|
/** Override the threshold below which a delta is rendered as 'good'.
|
|
* Useful for cost / latency KPIs where lower is better. Default `>=0`. */
|
|
goodWhen?: 'higher' | 'lower';
|
|
className?: string;
|
|
style?: CSSProperties;
|
|
}
|
|
|
|
/**
|
|
* `<KpiCard>` — token-themed key-performance-indicator tile.
|
|
*
|
|
* ┌────────────────────────┐
|
|
* │ Active users ✦ │
|
|
* │ 8,421 │
|
|
* │ ▲ +6.6% vs last 7d ╱╲╱ │
|
|
* └────────────────────────┘
|
|
*
|
|
* Renders the headline number prominently, an optional delta with
|
|
* color-coded arrow, and an inline `<Sparkline>` for context.
|
|
*/
|
|
export function KpiCard({
|
|
label,
|
|
value,
|
|
deltaPercent,
|
|
deltaLabel = 'vs previous',
|
|
trend,
|
|
icon,
|
|
trendColor,
|
|
hideDeltaArrow,
|
|
goodWhen = 'higher',
|
|
className,
|
|
style,
|
|
}: KpiCardProps) {
|
|
const hasDelta = typeof deltaPercent === 'number';
|
|
const up = hasDelta && deltaPercent! >= 0;
|
|
const good =
|
|
!hasDelta ||
|
|
(goodWhen === 'higher' ? up : !up && Math.abs(deltaPercent!) > 0.0001);
|
|
const deltaColor = good
|
|
? 'var(--bl-success, #22c55e)'
|
|
: 'var(--bl-danger, #ef4444)';
|
|
|
|
return (
|
|
<article
|
|
data-testid="bl-kpi-card"
|
|
className={className}
|
|
style={{
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
gap: 'var(--bl-space-2, 8px)',
|
|
padding: 'var(--bl-space-4, 16px)',
|
|
border: '1px solid var(--bl-border, rgba(0,0,0,0.08))',
|
|
borderRadius: 'var(--bl-radius-card, 12px)',
|
|
background: 'var(--bl-surface-card, #fff)',
|
|
color: 'var(--bl-text-primary, inherit)',
|
|
minWidth: 180,
|
|
...style,
|
|
}}
|
|
>
|
|
<header
|
|
style={{
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
gap: 'var(--bl-space-2, 8px)',
|
|
}}
|
|
>
|
|
<span
|
|
style={{
|
|
fontSize: '0.75rem',
|
|
fontWeight: 600,
|
|
textTransform: 'uppercase',
|
|
letterSpacing: '0.04em',
|
|
color: 'var(--bl-text-tertiary, #888)',
|
|
}}
|
|
>
|
|
{label}
|
|
</span>
|
|
{icon && (
|
|
<span
|
|
aria-hidden
|
|
style={{
|
|
display: 'inline-flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
width: 28,
|
|
height: 28,
|
|
borderRadius: 'var(--bl-radius-pill, 999px)',
|
|
background: 'var(--bl-accent-muted, rgba(99,102,241,0.12))',
|
|
color: 'var(--bl-accent, #6366f1)',
|
|
}}
|
|
>
|
|
{icon}
|
|
</span>
|
|
)}
|
|
</header>
|
|
|
|
<div
|
|
data-testid="bl-kpi-value"
|
|
style={{
|
|
fontSize: '2.1rem',
|
|
fontWeight: 700,
|
|
lineHeight: 1.05,
|
|
fontVariantNumeric: 'tabular-nums',
|
|
}}
|
|
>
|
|
{value}
|
|
</div>
|
|
|
|
<div
|
|
style={{
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: 'var(--bl-space-2, 8px)',
|
|
fontSize: '0.78rem',
|
|
}}
|
|
>
|
|
{hasDelta && (
|
|
<span
|
|
data-testid="bl-kpi-delta"
|
|
data-good={good ? 'true' : 'false'}
|
|
style={{
|
|
display: 'inline-flex',
|
|
alignItems: 'center',
|
|
gap: 2,
|
|
color: deltaColor,
|
|
fontWeight: 600,
|
|
}}
|
|
>
|
|
{!hideDeltaArrow && <span aria-hidden>{up ? '▲' : '▼'}</span>}
|
|
{(up ? '+' : '') + deltaPercent!.toFixed(1)}%
|
|
</span>
|
|
)}
|
|
{hasDelta && (
|
|
<span style={{ color: 'var(--bl-text-tertiary, #888)' }}>{deltaLabel}</span>
|
|
)}
|
|
{trend && trend.length > 1 && (
|
|
<div style={{ marginLeft: 'auto' }}>
|
|
<Sparkline
|
|
data={trend}
|
|
width={88}
|
|
height={28}
|
|
stroke={trendColor ?? (good ? 'var(--bl-success, #22c55e)' : 'var(--bl-danger, #ef4444)')}
|
|
/>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</article>
|
|
);
|
|
}
|