KQ-1701 · TRIAL/RECAP
Trial Recap
The screen a workspace sees as its trial runs out: what the team did with it beside the plans. The first time it is seen, the days-left ring sweeps to its share on springs.drift while the day count rolls up, the recap's counts roll up on odometer wheels in cascade on springs.glide (each wheel turns only as the one below it rolls over), and the plan that fits the workspace's actual usage rises on glide as its reason chip stamps in on springs.recoil. The plans are a radiogroup with arrow keys and roving focus; the call to action rolls its monthly total on springs.snap, and an expired trial changes the copy and empties the ring.
Seven days left in your trial
Here is what your team did with the first 7 days.
Day 8 of 14
Ends Oct 19. Nothing is charged until you choose.
- Projects createdacross 4 teams
- 23
- Teammates invited14 seats in use
- 13
- Hours savedfrom automations
- 61
Choose a plan
7 days left · team fits 14 seats
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/trial-recap
Props
- usage
- TrialUsage
- default defaultTrialUsage
- What the workspace did: { projects, invited, seats, hoursSaved, teams? }.
- plans
- TrialPlan[]
- default defaultTrialPlans
- The plans offered: { id, name, price, seats, projects, blurb } with null for no limit.
- now
- number
- default defaultTrialNow
- The moment days are counted from, in ms; never the wall clock.
- endsAt
- number
- default now + 7 days
- When the trial ends, in ms; before now the trial reads as expired.
- trialDays
- number
- default 14
- How long the trial runs; the ring goes round once for it.
- value / defaultValue
- string
- default — / the plan that fits
- The chosen plan's id, controlled or initial.
- onValueChange
- (planId: string) => void
- default —
- Fires from the click or key that chose another plan.
- onChoosePlan
- (plan: TrialPlan, total: number) => void
- default —
- The call to action was pressed, with the plan and its monthly total for the workspace's seats.
- onExtend
- () => void
- default —
- Extend (or, once expired, Ask for more days) was pressed.
- onTalk
- () => void
- default —
- Talk to us was pressed.
- recommend
- (usage, plans) => { planId, reason } | null
- default recommendTrialPlan
- Picks the plan that fits and the reason it shows; by default the cheapest plan holding your seats and projects, explained by whichever ruled out the plan below.
- extendDays
- number
- default 7
- Days an extension asks for.
- workspace
- string
- default "Fieldline"
- The workspace's name in the header.
- ringStyle
- "solid" | "days" | "ticks"
- default "solid"
- The days-left ring: one round-capped arc, one segment per trial day, or a dial of fine ticks.
- density
- "compact" | "regular" | "roomy"
- default "regular"
- Padding, gaps, ring size (112, 136 or 156px) and the size of the numbers.
- tone
- "calm" | "urgent" | "quiet"
- default "calm"
- Voice and colour: calm cobalt ("Seven days left in your trial"), urgent amber ("Your trial ends in 7 days"), or quiet graphite ("Trial · 7 days left").
- formatPrice
- (amount: number) => string
- default US dollars
- Formats prices and the monthly total; cents only when there are some.
- status
- "ready" | "loading" | "error"
- default "ready"
- Whether the recap has arrived: an empty ring and breathing bars while loading, a retry on error.
- onRetry
- () => void
- default —
- Try again was pressed after the recap failed to load.
- disabled
- boolean
- default false
- Holds the plans and every action.
- className
- string
- default —
- Classes for the surface; give it a height and it scrolls inside itself.
| Prop | Type | Default | Description |
|---|---|---|---|
| usage | TrialUsage | defaultTrialUsage | What the workspace did: { projects, invited, seats, hoursSaved, teams? }. |
| plans | TrialPlan[] | defaultTrialPlans | The plans offered: { id, name, price, seats, projects, blurb } with null for no limit. |
| now | number | defaultTrialNow | The moment days are counted from, in ms; never the wall clock. |
| endsAt | number | now + 7 days | When the trial ends, in ms; before now the trial reads as expired. |
| trialDays | number | 14 | How long the trial runs; the ring goes round once for it. |
| value / defaultValue | string | — / the plan that fits | The chosen plan's id, controlled or initial. |
| onValueChange | (planId: string) => void | — | Fires from the click or key that chose another plan. |
| onChoosePlan | (plan: TrialPlan, total: number) => void | — | The call to action was pressed, with the plan and its monthly total for the workspace's seats. |
| onExtend | () => void | — | Extend (or, once expired, Ask for more days) was pressed. |
| onTalk | () => void | — | Talk to us was pressed. |
| recommend | (usage, plans) => { planId, reason } | null | recommendTrialPlan | Picks the plan that fits and the reason it shows; by default the cheapest plan holding your seats and projects, explained by whichever ruled out the plan below. |
| extendDays | number | 7 | Days an extension asks for. |
| workspace | string | "Fieldline" | The workspace's name in the header. |
| ringStyle | "solid" | "days" | "ticks" | "solid" | The days-left ring: one round-capped arc, one segment per trial day, or a dial of fine ticks. |
| density | "compact" | "regular" | "roomy" | "regular" | Padding, gaps, ring size (112, 136 or 156px) and the size of the numbers. |
| tone | "calm" | "urgent" | "quiet" | "calm" | Voice and colour: calm cobalt ("Seven days left in your trial"), urgent amber ("Your trial ends in 7 days"), or quiet graphite ("Trial · 7 days left"). |
| formatPrice | (amount: number) => string | US dollars | Formats prices and the monthly total; cents only when there are some. |
| status | "ready" | "loading" | "error" | "ready" | Whether the recap has arrived: an empty ring and breathing bars while loading, a retry on error. |
| onRetry | () => void | — | Try again was pressed after the recap failed to load. |
| disabled | boolean | false | Holds the plans and every action. |
| className | string | — | Classes for the surface; give it a height and it scrolls inside itself. |