/** * Kpi — one number, and what it is doing. * * A metric card is not a chart with a caption. The number is the message and * the chart is the footnote, so the parts here are sized and ordered around * that: a title that stays quiet, a value that does not, a trend that says * which way and by how much, and a sparkline that is allowed to be small * because nobody is reading values off it. * * ```tsx * * * Active users * * * 12,480 * * * * * ``` * * The trend is given a number rather than a written string, so the component * decides the direction and the colour rather than the call site remembering * to keep a minus sign and a red in step. Where a rise is the bad news — * churn, latency, cost — say so with `goodDirection="down"` and the colour * follows the meaning instead of the sign. */ import { type ReactNode } from 'react'; import { View, type ViewProps } from 'react-native'; import { type VariantProps } from 'tailwind-variants'; import { type SeriesColorIndex } from '../../utils/chart.js'; import { type LineChartDatum } from '../line-chart/index.js'; declare const kpiVariants: import("tailwind-variants").TVReturnType<{ /** * Which way the number moved, *after* the metric's own polarity has been * applied — so `good` is green whether it went up or down. */ tone: { good: { trendLabel: string; icon: string; }; bad: { trendLabel: string; icon: string; }; flat: { trendLabel: string; icon: string; }; neutral: { trendLabel: string; icon: string; }; }; /** * How the change is drawn. * * `text` is the quieter of the two and the default: a line of colour under * the number, which is where the eye already is. `badge` puts a pill round * it for a card dense enough that a bare line of colour gets lost. */ trendVariant: { text: { trend: string; trendLabel: string; }; badge: { trend: string; trendLabel: string; }; }; /** Where the chart sits relative to the number. */ layout: { /** Under everything, full width. The chart is a footnote. */ below: {}; /** * Beside the number: the text takes the width and the chart takes a * fixed column on the end. The number is read first and the shape * second, which is the order they sit in — so this is the shape a stat * card wants, and `below` is for a chart big enough to be looked at. */ inline: { content: string; }; }; }, { root: string; header: string; icon: string; title: string; content: string; value: string; /** The stacked title/value/trend block, tight enough to read as one thing. */ stat: string; trend: string; trendLabel: string; footer: string; separator: string; group: string; groupSeparator: string; }, undefined, { /** * Which way the number moved, *after* the metric's own polarity has been * applied — so `good` is green whether it went up or down. */ tone: { good: { trendLabel: string; icon: string; }; bad: { trendLabel: string; icon: string; }; flat: { trendLabel: string; icon: string; }; neutral: { trendLabel: string; icon: string; }; }; /** * How the change is drawn. * * `text` is the quieter of the two and the default: a line of colour under * the number, which is where the eye already is. `badge` puts a pill round * it for a card dense enough that a bare line of colour gets lost. */ trendVariant: { text: { trend: string; trendLabel: string; }; badge: { trend: string; trendLabel: string; }; }; /** Where the chart sits relative to the number. */ layout: { /** Under everything, full width. The chart is a footnote. */ below: {}; /** * Beside the number: the text takes the width and the chart takes a * fixed column on the end. The number is read first and the shape * second, which is the order they sit in — so this is the shape a stat * card wants, and `below` is for a chart big enough to be looked at. */ inline: { content: string; }; }; }, { root: string; header: string; icon: string; title: string; content: string; value: string; /** The stacked title/value/trend block, tight enough to read as one thing. */ stat: string; trend: string; trendLabel: string; footer: string; separator: string; group: string; groupSeparator: string; }, import("tailwind-variants").TVReturnType<{ /** * Which way the number moved, *after* the metric's own polarity has been * applied — so `good` is green whether it went up or down. */ tone: { good: { trendLabel: string; icon: string; }; bad: { trendLabel: string; icon: string; }; flat: { trendLabel: string; icon: string; }; neutral: { trendLabel: string; icon: string; }; }; /** * How the change is drawn. * * `text` is the quieter of the two and the default: a line of colour under * the number, which is where the eye already is. `badge` puts a pill round * it for a card dense enough that a bare line of colour gets lost. */ trendVariant: { text: { trend: string; trendLabel: string; }; badge: { trend: string; trendLabel: string; }; }; /** Where the chart sits relative to the number. */ layout: { /** Under everything, full width. The chart is a footnote. */ below: {}; /** * Beside the number: the text takes the width and the chart takes a * fixed column on the end. The number is read first and the shape * second, which is the order they sit in — so this is the shape a stat * card wants, and `below` is for a chart big enough to be looked at. */ inline: { content: string; }; }; }, { root: string; header: string; icon: string; title: string; content: string; value: string; /** The stacked title/value/trend block, tight enough to read as one thing. */ stat: string; trend: string; trendLabel: string; footer: string; separator: string; group: string; groupSeparator: string; }, undefined, unknown, unknown, undefined>>; type KpiVariantProps = VariantProps; /** How a trend is coloured once the metric's polarity has been applied. */ export type KpiTone = NonNullable; /** Which direction of movement is the good news for this metric. */ export type KpiGoodDirection = 'up' | 'down' | 'none'; export interface KpiProps extends Omit { className?: string; /** * Which `--color-chart-*` token the sparkline and the icon take. Set on the * card rather than on the chart so a row of cards can be given five * different series colours without repeating the choice on every part. */ colorIndex?: SeriesColorIndex; /** * Which way is the good news. `up` for revenue and signups, `down` for churn * and latency, `none` for a number that is neither — a headcount, a version. * Defaults to `up`. */ goodDirection?: KpiGoodDirection; /** Draw the card on a surface. Turn off to place it in a shell of your own. */ surface?: boolean; children: ReactNode; } export interface KpiHeaderProps extends ViewProps { className?: string; children: ReactNode; } /** The top row: an icon, the metric's name, and anything acting on it. */ declare function KpiHeader({ className, children, ...props }: KpiHeaderProps): import("react").JSX.Element; declare namespace KpiHeader { var displayName: string; } export interface KpiIconProps extends ViewProps { className?: string; /** Overrides the tint the card's `colorIndex` would give it. */ tone?: KpiTone; children: ReactNode; } /** * A tinted square for a glyph. * * It takes the element rather than drawing one, because a metric's icon comes * from whatever set the app already uses — and an icon from outside this * library will not read an ambient colour, so pass it one. */ declare function KpiIcon({ className, tone, children, ...props }: KpiIconProps): import("react").JSX.Element; declare namespace KpiIcon { var displayName: string; } export interface KpiTitleProps { className?: string; children: ReactNode; } /** The metric's name. Quiet on purpose — the value is the thing being read. */ declare function KpiTitle({ className, children }: KpiTitleProps): import("react").JSX.Element; declare namespace KpiTitle { var displayName: string; } export interface KpiStatProps extends ViewProps { className?: string; children: ReactNode; } /** * The stacked title / value / change block. * * Its own container rather than three loose children of the card, because the * three belong together more tightly than they belong to whatever is above or * below them — 4pt between the lines of one fact, and the card's own spacing * between facts. It also takes the width in an `inline` row, leaving the chart * its column on the end. */ declare function KpiStat({ className, children, ...props }: KpiStatProps): import("react").JSX.Element; declare namespace KpiStat { var displayName: string; } export interface KpiActionsProps extends ViewProps { className?: string; children: ReactNode; } /** The trailing end of the header — a menu trigger, a filter, a link. */ declare function KpiActions({ className, children, ...props }: KpiActionsProps): import("react").JSX.Element; declare namespace KpiActions { var displayName: string; } export interface KpiContentProps extends ViewProps { className?: string; /** `inline` puts the chart beside the value instead of under everything. */ layout?: NonNullable; children: ReactNode; } /** The row the value and the trend share. */ declare function KpiContent({ className, layout, children, ...props }: KpiContentProps): import("react").JSX.Element; declare namespace KpiContent { var displayName: string; } export interface KpiValueProps { className?: string; children: ReactNode; } /** * The number. * * Formatted by the caller, not here: thousands separators, currency symbols * and units are locale decisions, and a component that guessed them would be * wrong in a way that is hard to notice and impossible to override. */ declare function KpiValue({ className, children }: KpiValueProps): import("react").JSX.Element; declare namespace KpiValue { var displayName: string; } export interface KpiTrendProps extends Omit { className?: string; /** * How much it moved, as a percentage. The sign carries the direction, so * `-4.2` is a fall of 4.2%; there is no separate direction prop to keep in * step with it. */ value: number; /** * Writes the number yourself. Receives the raw value, sign and all. The * default prints one decimal place with an explicit `+` or `−`. */ format?: (value: number) => string; /** Overrides the card's own `goodDirection` for this one figure. */ goodDirection?: KpiGoodDirection; /** * `text` is a line of colour under the number — the default, and what a * stat card usually wants. `badge` puts a pill round it, with an arrow, for * a card busy enough that a bare line of colour is lost in it. */ variant?: NonNullable; /** What it is being compared against — "last 30d", "vs last week". */ caption?: string; /** Anything after the number, when a caption is not enough. */ children?: ReactNode; /** Below which a movement counts as no movement. Defaults to `0`. */ threshold?: number; } /** * The change. * * Colour comes from what the movement *means*, not from its sign: a fall in * churn is good news and is drawn as good news. That is the whole reason this * takes a number rather than a string — a caller writing "−4.2%" into a green * label has to remember to change the colour when the metric changes, and * nobody does. */ declare function KpiTrend({ className, value, format, goodDirection, variant, caption, threshold, children, ...props }: KpiTrendProps): import("react").JSX.Element; declare namespace KpiTrend { var displayName: string; } export interface KpiSparklineProps { className?: string; /** The rows. One point each, in order. */ data: LineChartDatum[]; /** Key holding the y values. */ dataKey: string; /** Overrides the card's `colorIndex`. */ colorIndex?: SeriesColorIndex; /** * Fill under the line. Off beside the number, where the chart is a gesture * and a fill would make it a second block competing with the value; on when * it has the full width under everything and is being looked at properly. */ filled?: boolean; /** Height in points. */ height?: number; /** * Put it beside the number, taking whatever width the text leaves rather * than a column of its own. Pair with `layout="inline"` on the content row. */ inline?: boolean; strokeWidth?: number; } /** * The sparkline. * * A line chart with the axis padding dropped, which is what `compact` on the * chart itself means — there is no grid, no axis and no crosshair here, so * every point of padding is a point the shape is not using. Nobody reads a * value off one of these; they read whether it is going up. */ declare function KpiSparkline({ className, data, dataKey, colorIndex, filled, height, inline, strokeWidth, }: KpiSparklineProps): import("react").JSX.Element; declare namespace KpiSparkline { var displayName: string; } export interface KpiProgressProps { className?: string; /** Where it has got to. */ value: number; /** The value at which the bar reads as full. Defaults to `100`. */ maxValue?: number; /** A caption above the bar. */ label?: string; /** Print the percentage on the right of the caption row. */ showValueLabel?: boolean; } /** Progress towards a target, for a metric that has one. */ declare function KpiProgressBar({ className, value, maxValue, label, showValueLabel, }: KpiProgressProps): import("react").JSX.Element; declare namespace KpiProgressBar { var displayName: string; } export interface KpiFooterProps extends ViewProps { className?: string; children: ReactNode; } /** The bottom strip — a comparison period, a caveat, a link. */ declare function KpiFooter({ className, children, ...props }: KpiFooterProps): import("react").JSX.Element; declare namespace KpiFooter { var displayName: string; } export interface KpiSeparatorProps extends ViewProps { className?: string; } /** A hairline across the card. */ declare function KpiSeparator({ className, ...props }: KpiSeparatorProps): import("react").JSX.Element; declare namespace KpiSeparator { var displayName: string; } export type KpiGroupOrientation = 'horizontal' | 'vertical'; export interface KpiGroupProps extends ViewProps { className?: string; /** `horizontal` splits the row between the cards; `vertical` stacks them. */ orientation?: KpiGroupOrientation; /** * Draw a hairline between the cards rather than spacing them apart. Several * metrics separated by a rule read as one panel; several spaced apart read * as several panels that happen to be adjacent. */ separated?: boolean; children: ReactNode; } export declare const Kpi: import("react").ForwardRefExoticComponent> & { Header: typeof KpiHeader; Icon: typeof KpiIcon; Title: typeof KpiTitle; Stat: typeof KpiStat; Actions: typeof KpiActions; Content: typeof KpiContent; Value: typeof KpiValue; Trend: typeof KpiTrend; Chart: typeof KpiSparkline; Progress: typeof KpiProgressBar; Footer: typeof KpiFooter; Separator: typeof KpiSeparator; Group: import("react").ForwardRefExoticComponent>; }; export {}; //# sourceMappingURL=index.d.ts.map