Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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
52 changes: 52 additions & 0 deletions components/recharts-poc/RechartsPocPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { useRouter } from 'next/router';

import { theme } from './primitives/theme';
import { EngagementSection } from './sections/EngagementSection';
import { TrendsSection } from './sections/TrendsSection';

const CSS_VARS: Record<string, string> = {
'--rcp-background': theme.background,
'--rcp-surface': theme.surface,
'--rcp-border': theme.border,
'--rcp-border-strong': theme.borderStrong,
'--rcp-text': theme.text,
'--rcp-text-muted': theme.textMuted,
'--rcp-text-subtle': theme.textSubtle,
'--rcp-tooltip-bg': theme.tooltipBackground,
'--rcp-tooltip-text': theme.tooltipText,
};

export function RechartsPocPage() {
const router = useRouter();
const perfMode = router.query.perf === '1';

return (
<div
style={CSS_VARS as React.CSSProperties}
className="bg-[var(--rcp-background)] text-[var(--rcp-text)]"
>
<section className="mx-auto mb-16 flex w-full max-w-5xl flex-col gap-6 px-4 py-8">
<header className="flex flex-col gap-1">
<h1 className="text-2xl font-semibold tracking-tight text-[var(--rcp-text)]">
Engagement Dashboard
</h1>
<p className="text-sm text-[var(--rcp-text-muted)]">
Recharts POC — engagement layout.{' '}
<code className="rounded bg-[var(--rcp-surface)] px-1 text-xs">
?perf=1
</code>{' '}
disables animations.
{perfMode ? (
<span className="ml-2 rounded bg-[var(--rcp-tooltip-bg)] px-1.5 py-0.5 text-xs text-[var(--rcp-tooltip-text)]">
perf mode
</span>
) : null}
</p>
</header>

<EngagementSection perfMode={perfMode} />
<TrendsSection perfMode={perfMode} />
</section>
</div>
);
}
74 changes: 74 additions & 0 deletions components/recharts-poc/charts/FeatureAdoptionCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Bar, BarChart, CartesianGrid, Tooltip, XAxis, YAxis } from 'recharts';

import { FEATURE_ADOPTION } from '../data/engagement';
import { ChartCard } from '../primitives/ChartCard';
import { theme } from '../primitives/theme';

interface FeatureAdoptionCardProps {
perfMode: boolean;
}

const NUMBER_FMT = new Intl.NumberFormat('en-US');

export function FeatureAdoptionCard(props: FeatureAdoptionCardProps) {
const { perfMode } = props;
return (
<ChartCard
title="Feature Adoption"
description="Shows the number of active users who used each feature during the selected period."
height={300}
>
<BarChart
data={FEATURE_ADOPTION}
layout="vertical"
margin={{ top: 8, right: 24, bottom: 8, left: 0 }}
barCategoryGap="32%"
>
<CartesianGrid
stroke={theme.grid}
strokeDasharray="3 3"
horizontal={false}
/>
<XAxis
type="number"
domain={[0, 10_000]}
ticks={[0, 2_500, 5_000, 7_500, 10_000]}
stroke={theme.axis}
tickLine={false}
axisLine={false}
tick={{ fill: theme.textMuted, fontSize: 12 }}
tickFormatter={(v: number) => NUMBER_FMT.format(v).replace(/,/g, ' ')}
/>
<YAxis
type="category"
dataKey="label"
stroke={theme.axis}
tickLine={false}
axisLine={false}
tick={{ fill: theme.textMuted, fontSize: 12 }}
width={110}
/>
<Tooltip
cursor={{ fill: theme.border, opacity: 0.4 }}
contentStyle={{
background: theme.tooltipBackground,
border: 'none',
borderRadius: 6,
color: theme.tooltipText,
fontSize: 12,
}}
labelStyle={{ color: theme.tooltipText }}
itemStyle={{ color: theme.tooltipText }}
formatter={(v) => [NUMBER_FMT.format(Number(v)), 'Users']}
/>
<Bar
dataKey="users"
fill={theme.bar}
isAnimationActive={!perfMode}
radius={[0, 6, 6, 0]}
name="Users"
/>
</BarChart>
</ChartCard>
);
}
38 changes: 38 additions & 0 deletions components/recharts-poc/charts/FeatureRetentionCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { useState } from 'react';

import {
FEATURE_RETENTION,
FEATURES,
type FeatureKey,
} from '../data/engagement';
import { FeatureDropdown } from '../primitives/FeatureDropdown';
import { SimpleLineCard } from './SimpleLineCard';

interface FeatureRetentionCardProps {
perfMode: boolean;
}

export function FeatureRetentionCard(props: FeatureRetentionCardProps) {
const { perfMode } = props;
const [feature, setFeature] = useState<FeatureKey>('workout');

return (
<SimpleLineCard
title="Feature Retention"
description="Shows the share of active users each month, based on calendar-month data."
data={FEATURE_RETENTION[feature]}
seriesLabel="All users"
yTicks={[0, 25, 50, 75, 100]}
yDomain={[0, 100]}
yFormatter={(v) => `${v}%`}
perfMode={perfMode}
actions={
<FeatureDropdown
options={FEATURES}
value={feature}
onChange={setFeature}
/>
}
/>
);
}
21 changes: 21 additions & 0 deletions components/recharts-poc/charts/MonthlyActiveUsersCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { MONTHLY_ACTIVE_USERS } from '../data/engagement';
import { SimpleLineCard } from './SimpleLineCard';

interface MonthlyActiveUsersCardProps {
perfMode: boolean;
}

export function MonthlyActiveUsersCard(props: MonthlyActiveUsersCardProps) {
const { perfMode } = props;
return (
<SimpleLineCard
title="Monthly Active Users"
description="Shows the number of active users each month, based on calendar-month data."
data={MONTHLY_ACTIVE_USERS}
seriesLabel="All users"
yTicks={[0, 250, 500, 750, 1_000]}
yDomain={[0, 1_000]}
perfMode={perfMode}
/>
);
}
21 changes: 21 additions & 0 deletions components/recharts-poc/charts/NewUsersCohortCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { NEW_USERS_COHORT } from '../data/engagement';
import { SimpleLineCard } from './SimpleLineCard';

interface NewUsersCohortCardProps {
perfMode: boolean;
}

export function NewUsersCohortCard(props: NewUsersCohortCardProps) {
const { perfMode } = props;
return (
<SimpleLineCard
title="New users cohort"
description="Shows the number of new users over time, helping to identify changes in the user cohort. An increase may indicate user replacement or previously inactive users becoming active."
data={NEW_USERS_COHORT}
seriesLabel="New users"
yTicks={[0, 250, 500, 750, 1_000]}
yDomain={[0, 1_000]}
perfMode={perfMode}
/>
);
}
100 changes: 100 additions & 0 deletions components/recharts-poc/charts/ProgramUtilizationCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import {
Area,
AreaChart,
CartesianGrid,
Label,
ReferenceLine,
Tooltip,
XAxis,
YAxis,
} from 'recharts';

import { PROGRAM_UTILIZATION, UTILIZATION_TARGET } from '../data/engagement';
import { ChartCard } from '../primitives/ChartCard';
import { theme } from '../primitives/theme';

interface ProgramUtilizationCardProps {
perfMode: boolean;
}

export function ProgramUtilizationCard(props: ProgramUtilizationCardProps) {
const { perfMode } = props;
return (
<ChartCard
title="Program Utilization Over Time"
description="Shows the percentage of users who actively use the program over time."
height={260}
>
<AreaChart
data={PROGRAM_UTILIZATION}
margin={{ top: 16, right: 24, bottom: 8, left: 0 }}
>
<defs>
<linearGradient id="rcp-util-fill" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor={theme.area} stopOpacity={0.16} />
<stop offset="100%" stopColor={theme.area} stopOpacity={0.02} />
</linearGradient>
</defs>
<CartesianGrid
stroke={theme.grid}
strokeDasharray="3 3"
vertical={false}
/>
<XAxis
dataKey="month"
stroke={theme.axis}
tickLine={false}
axisLine={false}
tick={{ fill: theme.textMuted, fontSize: 12 }}
/>
<YAxis
stroke={theme.axis}
tickLine={false}
axisLine={false}
tick={{ fill: theme.textMuted, fontSize: 12 }}
ticks={[0, 25, 50, 75]}
domain={[0, 75]}
tickFormatter={(v: number) => `${v}%`}
/>
<Tooltip
cursor={{ stroke: theme.borderStrong, strokeDasharray: '3 3' }}
contentStyle={{
background: theme.tooltipBackground,
border: 'none',
borderRadius: 6,
color: theme.tooltipText,
fontSize: 12,
}}
labelStyle={{ color: theme.tooltipText }}
itemStyle={{ color: theme.tooltipText }}
formatter={(v) => [`${Number(v)}%`, 'Utilization']}
/>
<ReferenceLine
y={UTILIZATION_TARGET}
stroke={theme.reference}
strokeDasharray="3 3"
ifOverflow="extendDomain"
>
<Label
value={`Target ${UTILIZATION_TARGET}%`}
position="insideTopRight"
fill={theme.reference}
fontSize={11}
offset={8}
/>
</ReferenceLine>
<Area
type="linear"
dataKey="utilization"
stroke={theme.line}
strokeWidth={1.5}
fill="url(#rcp-util-fill)"
dot={{ r: 2.5, fill: theme.line, strokeWidth: 0 }}
activeDot={{ r: 4, fill: theme.line, strokeWidth: 0 }}
isAnimationActive={!perfMode}
name="Utilization %"
/>
</AreaChart>
</ChartCard>
);
}
Loading