/**
* 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