panelui-web 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -2
- package/dist/components/bar-picker/index.d.ts +61 -0
- package/dist/components/bar-picker/index.js +139 -0
- package/dist/components/card/index.d.ts +107 -0
- package/dist/components/card/index.js +88 -0
- package/dist/components/command-dock/index.d.ts +118 -0
- package/dist/components/command-dock/index.js +239 -0
- package/dist/components/day-grid/index.d.ts +87 -0
- package/dist/components/day-grid/index.js +251 -0
- package/dist/components/feedback/index.d.ts +135 -0
- package/dist/components/feedback/index.js +283 -0
- package/dist/components/flow/flow-paths.d.ts +94 -0
- package/dist/components/flow/flow-paths.js +270 -0
- package/dist/components/flow/index.d.ts +235 -0
- package/dist/components/flow/index.js +597 -0
- package/dist/components/frame/index.d.ts +189 -0
- package/dist/components/frame/index.js +181 -0
- package/dist/components/kpi/index.d.ts +211 -0
- package/dist/components/kpi/index.js +243 -0
- package/dist/components/lane-board/index.d.ts +125 -0
- package/dist/components/lane-board/index.js +691 -0
- package/dist/components/planet/index.d.ts +132 -0
- package/dist/components/planet/index.js +653 -0
- package/dist/components/planet/land.d.ts +8 -0
- package/dist/components/planet/land.js +122 -0
- package/dist/components/range-meter/index.d.ts +70 -0
- package/dist/components/range-meter/index.js +170 -0
- package/dist/components/rolling-number/index.d.ts +46 -0
- package/dist/components/rolling-number/index.js +81 -0
- package/dist/components/sheet/index.d.ts +148 -0
- package/dist/components/sheet/index.js +330 -0
- package/dist/components/tilt-card/index.d.ts +60 -0
- package/dist/components/tilt-card/index.js +114 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +15 -0
- package/dist/lib/cn.d.ts +3 -0
- package/dist/lib/cn.js +6 -0
- package/package.json +72 -4
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frame — a widget shell: a panel of rows sitting in a tray, with the tray's
|
|
3
|
+
* one exposed strip along the top carrying the title.
|
|
4
|
+
*
|
|
5
|
+
* ```tsx
|
|
6
|
+
* <Frame>
|
|
7
|
+
* <Frame.Header>
|
|
8
|
+
* <Frame.Title>Deploys</Frame.Title>
|
|
9
|
+
* <Frame.Action>Last 24 hours</Frame.Action>
|
|
10
|
+
* </Frame.Header>
|
|
11
|
+
* <Frame.Panel>
|
|
12
|
+
* <Frame.Row>
|
|
13
|
+
* <Frame.Media><RocketIcon /></Frame.Media>
|
|
14
|
+
* <Frame.Content>
|
|
15
|
+
* <Frame.Title>main · a41c2e9</Frame.Title>
|
|
16
|
+
* <Frame.Description>Production · 2 minutes ago</Frame.Description>
|
|
17
|
+
* </Frame.Content>
|
|
18
|
+
* <Frame.Actions><Badge>Ready</Badge></Frame.Actions>
|
|
19
|
+
* </Frame.Row>
|
|
20
|
+
* </Frame.Panel>
|
|
21
|
+
* </Frame>
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* ## Two surfaces, one exposed edge
|
|
25
|
+
*
|
|
26
|
+
* The panel sits flush against the shell's left, right and bottom, so the
|
|
27
|
+
* shell reads as something the panel is set into rather than as a border
|
|
28
|
+
* round it — and the strip left at the top is the header, which is why the
|
|
29
|
+
* header needs no rule under it and no background of its own. The panel's
|
|
30
|
+
* bottom corners are not drawn at all: the shell clips them to its own radius.
|
|
31
|
+
*
|
|
32
|
+
* ## Four ways to nest them
|
|
33
|
+
*
|
|
34
|
+
* `default` is the tray. `band` holds a bordered card in a thin muted band,
|
|
35
|
+
* with the band's top strip naming what is inside — the frame for a widget
|
|
36
|
+
* with one thing to show, a figure or a chart, rather than a list of rows.
|
|
37
|
+
* `inset` floats the panel clear of the shell on all four sides, at the radius
|
|
38
|
+
* that keeps the two curves concentric, and the band left round it is a recess
|
|
39
|
+
* — darker than the panel in a light theme and a dark one alike — that
|
|
40
|
+
* `Frame.Footer` puts its actions in. `plain` drops the shell, for a frame
|
|
41
|
+
* inside something that already draws an edge, where a second one just inside
|
|
42
|
+
* it reads as a double line.
|
|
43
|
+
*
|
|
44
|
+
* ## Rows divide themselves
|
|
45
|
+
*
|
|
46
|
+
* The panel draws the hairline between its children, so no row has to know
|
|
47
|
+
* whether it is first. A row with `href` is a link and one with `onClick` is a
|
|
48
|
+
* button, each with the hover and focus a pressable row should have, and
|
|
49
|
+
* `chevron` marks one that leads somewhere.
|
|
50
|
+
*
|
|
51
|
+
* ## It renders on the server
|
|
52
|
+
*
|
|
53
|
+
* The variant reaches the parts through a data attribute and CSS rather than
|
|
54
|
+
* through context, so a frame has no client JavaScript of its own.
|
|
55
|
+
*/
|
|
56
|
+
import type { ComponentProps, MouseEventHandler, ReactNode } from 'react';
|
|
57
|
+
export type FrameVariant = 'default' | 'band' | 'inset' | 'plain';
|
|
58
|
+
export interface FrameProps extends ComponentProps<'div'> {
|
|
59
|
+
/**
|
|
60
|
+
* `default` sets the panel in a tray. `band` holds a bordered card in a
|
|
61
|
+
* thin muted band, for a widget showing one figure or chart. `inset` floats
|
|
62
|
+
* the panel in a recessed band and gives `Frame.Footer` somewhere to sit.
|
|
63
|
+
* `plain` drops the shell, for a frame inside a container that already draws
|
|
64
|
+
* its own edge.
|
|
65
|
+
*/
|
|
66
|
+
variant?: FrameVariant;
|
|
67
|
+
}
|
|
68
|
+
declare function FrameRoot({ variant, className, ...props }: FrameProps): import("react").JSX.Element;
|
|
69
|
+
declare namespace FrameRoot {
|
|
70
|
+
var displayName: string;
|
|
71
|
+
}
|
|
72
|
+
export interface FrameHeaderProps extends ComponentProps<'div'> {
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* The strip of shell above the panel: a title, an optional description under
|
|
76
|
+
* it, and `Frame.Action` on the trailing side.
|
|
77
|
+
*/
|
|
78
|
+
declare function FrameHeader({ className, ...props }: FrameHeaderProps): import("react").JSX.Element;
|
|
79
|
+
declare namespace FrameHeader {
|
|
80
|
+
var displayName: string;
|
|
81
|
+
}
|
|
82
|
+
export interface FrameTitleProps extends ComponentProps<'h3'> {
|
|
83
|
+
/** The heading level in a header. Default `h3`; a row title is always a `p`. */
|
|
84
|
+
as?: 'h2' | 'h3' | 'h4' | 'p';
|
|
85
|
+
}
|
|
86
|
+
/** A caption in the header; the subject line inside `Frame.Content`. */
|
|
87
|
+
declare function FrameTitle({ as: Tag, className, ...props }: FrameTitleProps): import("react").JSX.Element;
|
|
88
|
+
declare namespace FrameTitle {
|
|
89
|
+
var displayName: string;
|
|
90
|
+
}
|
|
91
|
+
export interface FrameDescriptionProps extends ComponentProps<'p'> {
|
|
92
|
+
}
|
|
93
|
+
/** A line under a title, in the header or in a row. */
|
|
94
|
+
declare function FrameDescription({ className, ...props }: FrameDescriptionProps): import("react").JSX.Element;
|
|
95
|
+
declare namespace FrameDescription {
|
|
96
|
+
var displayName: string;
|
|
97
|
+
}
|
|
98
|
+
export interface FrameActionProps extends ComponentProps<'div'> {
|
|
99
|
+
}
|
|
100
|
+
/** The header's trailing side: a short status, a link, a small button. */
|
|
101
|
+
declare function FrameAction({ className, ...props }: FrameActionProps): import("react").JSX.Element;
|
|
102
|
+
declare namespace FrameAction {
|
|
103
|
+
var displayName: string;
|
|
104
|
+
}
|
|
105
|
+
export interface FramePanelProps extends ComponentProps<'div'> {
|
|
106
|
+
}
|
|
107
|
+
/** The card the rows sit on. It draws the hairline between its children. */
|
|
108
|
+
declare function FramePanel({ className, ...props }: FramePanelProps): import("react").JSX.Element;
|
|
109
|
+
declare namespace FramePanel {
|
|
110
|
+
var displayName: string;
|
|
111
|
+
}
|
|
112
|
+
export interface FrameRowProps extends Omit<ComponentProps<'div'>, 'onClick'> {
|
|
113
|
+
/** Make the row a link to this address. */
|
|
114
|
+
href?: string;
|
|
115
|
+
/** Where a link row opens, as on an `<a>`. */
|
|
116
|
+
target?: string;
|
|
117
|
+
/** The relationship of a link row's target, as on an `<a>`. */
|
|
118
|
+
rel?: string;
|
|
119
|
+
/** Make the row a button that calls this. Ignored when `href` is set. */
|
|
120
|
+
onClick?: MouseEventHandler<HTMLElement>;
|
|
121
|
+
/** A trailing chevron, for a row that leads somewhere. */
|
|
122
|
+
chevron?: boolean;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* One line of the panel: `Frame.Media`, `Frame.Content` and `Frame.Actions`
|
|
126
|
+
* side by side. With `href` it is a link and with `onClick` a button.
|
|
127
|
+
*/
|
|
128
|
+
declare function FrameRow({ className, children, chevron, href, target, rel, ...props }: FrameRowProps): import("react").JSX.Element;
|
|
129
|
+
declare namespace FrameRow {
|
|
130
|
+
var displayName: string;
|
|
131
|
+
}
|
|
132
|
+
export interface FrameMediaProps extends ComponentProps<'div'> {
|
|
133
|
+
}
|
|
134
|
+
/** A row's leading icon, avatar or picture. Holds its size. */
|
|
135
|
+
declare function FrameMedia({ className, ...props }: FrameMediaProps): import("react").JSX.Element;
|
|
136
|
+
declare namespace FrameMedia {
|
|
137
|
+
var displayName: string;
|
|
138
|
+
}
|
|
139
|
+
export interface FrameContentProps extends ComponentProps<'div'> {
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* The flexible middle of a row, usually a title over a description. It takes
|
|
143
|
+
* what the media and actions leave and may shrink past its content, which is
|
|
144
|
+
* what keeps a long line from pushing the rest of the row off the edge.
|
|
145
|
+
*/
|
|
146
|
+
declare function FrameContent({ className, ...props }: FrameContentProps): import("react").JSX.Element;
|
|
147
|
+
declare namespace FrameContent {
|
|
148
|
+
var displayName: string;
|
|
149
|
+
}
|
|
150
|
+
export interface FrameActionsProps extends ComponentProps<'div'> {
|
|
151
|
+
}
|
|
152
|
+
/** A row's trailing side: a badge, a value, a switch, a small button. Holds its size. */
|
|
153
|
+
declare function FrameActions({ className, ...props }: FrameActionsProps): import("react").JSX.Element;
|
|
154
|
+
declare namespace FrameActions {
|
|
155
|
+
var displayName: string;
|
|
156
|
+
}
|
|
157
|
+
export interface FrameSectionProps extends Omit<ComponentProps<'section'>, 'title'> {
|
|
158
|
+
/** The heading over the section's rows. */
|
|
159
|
+
title?: ReactNode;
|
|
160
|
+
}
|
|
161
|
+
/** A labelled group of rows, for a panel holding more than one. */
|
|
162
|
+
declare function FrameSection({ title, className, children, ...props }: FrameSectionProps): import("react").JSX.Element;
|
|
163
|
+
declare namespace FrameSection {
|
|
164
|
+
var displayName: string;
|
|
165
|
+
}
|
|
166
|
+
export interface FrameFooterProps extends ComponentProps<'div'> {
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* The strip below the panel. Under `inset` its children become equal pills in
|
|
170
|
+
* the recessed band, the shape a row of decisions takes.
|
|
171
|
+
*/
|
|
172
|
+
declare function FrameFooter({ className, ...props }: FrameFooterProps): import("react").JSX.Element;
|
|
173
|
+
declare namespace FrameFooter {
|
|
174
|
+
var displayName: string;
|
|
175
|
+
}
|
|
176
|
+
export declare const Frame: typeof FrameRoot & {
|
|
177
|
+
Header: typeof FrameHeader;
|
|
178
|
+
Title: typeof FrameTitle;
|
|
179
|
+
Description: typeof FrameDescription;
|
|
180
|
+
Action: typeof FrameAction;
|
|
181
|
+
Panel: typeof FramePanel;
|
|
182
|
+
Row: typeof FrameRow;
|
|
183
|
+
Media: typeof FrameMedia;
|
|
184
|
+
Content: typeof FrameContent;
|
|
185
|
+
Actions: typeof FrameActions;
|
|
186
|
+
Section: typeof FrameSection;
|
|
187
|
+
Footer: typeof FrameFooter;
|
|
188
|
+
};
|
|
189
|
+
export { FrameHeader, FrameTitle, FrameDescription, FrameAction, FramePanel, FrameRow, FrameMedia, FrameContent, FrameActions, FrameSection, FrameFooter, };
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { tv } from 'tailwind-variants';
|
|
3
|
+
/*
|
|
4
|
+
* `inset` geometry. The panel's radius is the shell's less the shell's padding,
|
|
5
|
+
* so the two curves are concentric — a panel radius that ignored the band
|
|
6
|
+
* would leave a crescent of shell thicker at the corners than along the sides.
|
|
7
|
+
*/
|
|
8
|
+
const frameVariants = tv({
|
|
9
|
+
slots: {
|
|
10
|
+
root: 'group/frame flex flex-col text-foreground',
|
|
11
|
+
header: [
|
|
12
|
+
'grid grid-cols-[minmax(0,1fr)] items-center gap-x-3 gap-y-0.5 px-4 pt-2.5 pb-3',
|
|
13
|
+
'has-data-[slot=frame-action]:grid-cols-[minmax(0,1fr)_auto]',
|
|
14
|
+
'group-data-[variant=inset]/frame:px-3 group-data-[variant=inset]/frame:pt-1 group-data-[variant=inset]/frame:pb-2.5',
|
|
15
|
+
'group-data-[variant=plain]/frame:px-1',
|
|
16
|
+
'group-data-[variant=band]/frame:px-3.5 group-data-[variant=band]/frame:py-2.5',
|
|
17
|
+
],
|
|
18
|
+
/*
|
|
19
|
+
* One part in two places. In the header it labels the tray, so it stays
|
|
20
|
+
* quiet; inside a row's content it is the row's subject, so it takes the
|
|
21
|
+
* foreground and the weight, and truncates rather than pushing the row's
|
|
22
|
+
* trailing slot out of view.
|
|
23
|
+
*/
|
|
24
|
+
title: [
|
|
25
|
+
'text-sm font-medium text-muted-foreground',
|
|
26
|
+
'in-data-[slot=frame-content]:truncate in-data-[slot=frame-content]:text-foreground',
|
|
27
|
+
// A band names one thing rather than captioning a list, so its title reads first.
|
|
28
|
+
'group-data-[variant=band]/frame:text-foreground',
|
|
29
|
+
],
|
|
30
|
+
description: 'text-sm text-muted-foreground in-data-[slot=frame-content]:truncate',
|
|
31
|
+
action: 'col-start-2 row-span-2 row-start-1 text-sm text-muted-foreground',
|
|
32
|
+
// The panel's shape follows the root's variant, read from its data attribute.
|
|
33
|
+
panel: [
|
|
34
|
+
'flex flex-col divide-y overflow-hidden bg-card text-card-foreground',
|
|
35
|
+
'group-data-[variant=default]/frame:rounded-t-2xl group-data-[variant=default]/frame:border-t',
|
|
36
|
+
'group-data-[variant=plain]/frame:rounded-3xl group-data-[variant=plain]/frame:border group-data-[variant=plain]/frame:shadow-xs',
|
|
37
|
+
'group-data-[variant=inset]/frame:rounded-[30px] group-data-[variant=inset]/frame:bg-popover group-data-[variant=inset]/frame:text-popover-foreground group-data-[variant=inset]/frame:shadow-xs',
|
|
38
|
+
// 18px is the band's 24 less its 6px of padding: one corner, two curves.
|
|
39
|
+
'group-data-[variant=band]/frame:rounded-[1.125rem] group-data-[variant=band]/frame:border group-data-[variant=band]/frame:shadow-xs',
|
|
40
|
+
],
|
|
41
|
+
row: 'flex w-full items-center gap-3 px-4 py-3 text-start',
|
|
42
|
+
rowPressable: [
|
|
43
|
+
'cursor-pointer outline-none transition-colors hover:bg-muted/60',
|
|
44
|
+
'focus-visible:bg-muted/60 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset',
|
|
45
|
+
],
|
|
46
|
+
chevron: 'size-4 shrink-0 text-muted-foreground/70 rtl:-scale-x-100',
|
|
47
|
+
media: 'flex shrink-0 items-center justify-center',
|
|
48
|
+
content: 'flex min-w-0 flex-1 flex-col gap-0.5',
|
|
49
|
+
actions: 'flex shrink-0 items-center gap-1.5',
|
|
50
|
+
section: 'flex flex-col divide-y',
|
|
51
|
+
sectionTitle: 'bg-muted/40 px-4 pt-2.5 pb-1.5 text-xs font-medium uppercase tracking-wider text-muted-foreground',
|
|
52
|
+
footer: [
|
|
53
|
+
'flex items-center gap-2 px-4 pt-3 pb-3.5',
|
|
54
|
+
// Under `inset` the band is a row of equal decisions: equal widths, full pills.
|
|
55
|
+
'group-data-[variant=inset]/frame:gap-3.5 group-data-[variant=inset]/frame:px-[18px] group-data-[variant=inset]/frame:pt-4 group-data-[variant=inset]/frame:pb-2.5',
|
|
56
|
+
'group-data-[variant=inset]/frame:*:h-11 group-data-[variant=inset]/frame:*:flex-1 group-data-[variant=inset]/frame:*:rounded-full',
|
|
57
|
+
'group-data-[variant=plain]/frame:px-1',
|
|
58
|
+
'group-data-[variant=band]/frame:px-3.5 group-data-[variant=band]/frame:pt-2.5 group-data-[variant=band]/frame:pb-1',
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
variants: {
|
|
62
|
+
variant: {
|
|
63
|
+
default: {
|
|
64
|
+
// `overflow-hidden` is what gives the panel the shell's bottom corners.
|
|
65
|
+
root: 'overflow-hidden rounded-3xl border bg-muted',
|
|
66
|
+
},
|
|
67
|
+
plain: {},
|
|
68
|
+
band: {
|
|
69
|
+
root: 'rounded-3xl bg-muted p-1.5',
|
|
70
|
+
},
|
|
71
|
+
inset: {
|
|
72
|
+
// The band is the popover surface with black mixed in, so it is the
|
|
73
|
+
// darker of the two in either theme, which a step on a grey scale
|
|
74
|
+
// cannot promise: those run darker one way and lighter the other.
|
|
75
|
+
root: [
|
|
76
|
+
'rounded-[38px] p-2 ring-1 ring-foreground/[0.06]',
|
|
77
|
+
'bg-[color-mix(in_oklab,var(--popover),black_5%)] dark:bg-[color-mix(in_oklab,var(--popover),black_14%)]',
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
defaultVariants: { variant: 'default' },
|
|
83
|
+
});
|
|
84
|
+
function FrameRoot({ variant = 'default', className, ...props }) {
|
|
85
|
+
const slots = frameVariants({ variant });
|
|
86
|
+
return _jsx("div", { "data-slot": "frame", "data-variant": variant, className: slots.root({ className }), ...props });
|
|
87
|
+
}
|
|
88
|
+
FrameRoot.displayName = 'Frame';
|
|
89
|
+
/**
|
|
90
|
+
* The strip of shell above the panel: a title, an optional description under
|
|
91
|
+
* it, and `Frame.Action` on the trailing side.
|
|
92
|
+
*/
|
|
93
|
+
function FrameHeader({ className, ...props }) {
|
|
94
|
+
return _jsx("div", { "data-slot": "frame-header", className: frameVariants().header({ className }), ...props });
|
|
95
|
+
}
|
|
96
|
+
FrameHeader.displayName = 'Frame.Header';
|
|
97
|
+
/** A caption in the header; the subject line inside `Frame.Content`. */
|
|
98
|
+
function FrameTitle({ as: Tag = 'h3', className, ...props }) {
|
|
99
|
+
return _jsx(Tag, { "data-slot": "frame-title", className: frameVariants().title({ className }), ...props });
|
|
100
|
+
}
|
|
101
|
+
FrameTitle.displayName = 'Frame.Title';
|
|
102
|
+
/** A line under a title, in the header or in a row. */
|
|
103
|
+
function FrameDescription({ className, ...props }) {
|
|
104
|
+
return (_jsx("p", { "data-slot": "frame-description", className: frameVariants().description({ className }), ...props }));
|
|
105
|
+
}
|
|
106
|
+
FrameDescription.displayName = 'Frame.Description';
|
|
107
|
+
/** The header's trailing side: a short status, a link, a small button. */
|
|
108
|
+
function FrameAction({ className, ...props }) {
|
|
109
|
+
return _jsx("div", { "data-slot": "frame-action", className: frameVariants().action({ className }), ...props });
|
|
110
|
+
}
|
|
111
|
+
FrameAction.displayName = 'Frame.Action';
|
|
112
|
+
/** The card the rows sit on. It draws the hairline between its children. */
|
|
113
|
+
function FramePanel({ className, ...props }) {
|
|
114
|
+
return _jsx("div", { "data-slot": "frame-panel", className: frameVariants().panel({ className }), ...props });
|
|
115
|
+
}
|
|
116
|
+
FramePanel.displayName = 'Frame.Panel';
|
|
117
|
+
const Chevron = ({ className }) => (_jsx("svg", { viewBox: "0 0 24 24", "aria-hidden": true, className: className, fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("path", { d: "m9 6 6 6-6 6" }) }));
|
|
118
|
+
/**
|
|
119
|
+
* One line of the panel: `Frame.Media`, `Frame.Content` and `Frame.Actions`
|
|
120
|
+
* side by side. With `href` it is a link and with `onClick` a button.
|
|
121
|
+
*/
|
|
122
|
+
function FrameRow({ className, children, chevron = false, href, target, rel, ...props }) {
|
|
123
|
+
const slots = frameVariants();
|
|
124
|
+
const inside = (_jsxs(_Fragment, { children: [children, chevron ? _jsx(Chevron, { className: slots.chevron() }) : null] }));
|
|
125
|
+
if (href !== undefined) {
|
|
126
|
+
return (_jsx("a", { "data-slot": "frame-row", href: href, target: target, rel: rel, className: slots.row({ className: [slots.rowPressable(), className] }), ...props, children: inside }));
|
|
127
|
+
}
|
|
128
|
+
// `onClick` stays in `props`, so it reaches whichever element is rendered.
|
|
129
|
+
if (props.onClick !== undefined) {
|
|
130
|
+
return (_jsx("button", { type: "button", "data-slot": "frame-row", className: slots.row({ className: [slots.rowPressable(), className] }), ...props, children: inside }));
|
|
131
|
+
}
|
|
132
|
+
return (_jsx("div", { "data-slot": "frame-row", className: slots.row({ className }), ...props, children: inside }));
|
|
133
|
+
}
|
|
134
|
+
FrameRow.displayName = 'Frame.Row';
|
|
135
|
+
/** A row's leading icon, avatar or picture. Holds its size. */
|
|
136
|
+
function FrameMedia({ className, ...props }) {
|
|
137
|
+
return _jsx("div", { "data-slot": "frame-media", className: frameVariants().media({ className }), ...props });
|
|
138
|
+
}
|
|
139
|
+
FrameMedia.displayName = 'Frame.Media';
|
|
140
|
+
/**
|
|
141
|
+
* The flexible middle of a row, usually a title over a description. It takes
|
|
142
|
+
* what the media and actions leave and may shrink past its content, which is
|
|
143
|
+
* what keeps a long line from pushing the rest of the row off the edge.
|
|
144
|
+
*/
|
|
145
|
+
function FrameContent({ className, ...props }) {
|
|
146
|
+
return _jsx("div", { "data-slot": "frame-content", className: frameVariants().content({ className }), ...props });
|
|
147
|
+
}
|
|
148
|
+
FrameContent.displayName = 'Frame.Content';
|
|
149
|
+
/** A row's trailing side: a badge, a value, a switch, a small button. Holds its size. */
|
|
150
|
+
function FrameActions({ className, ...props }) {
|
|
151
|
+
return _jsx("div", { "data-slot": "frame-actions", className: frameVariants().actions({ className }), ...props });
|
|
152
|
+
}
|
|
153
|
+
FrameActions.displayName = 'Frame.Actions';
|
|
154
|
+
/** A labelled group of rows, for a panel holding more than one. */
|
|
155
|
+
function FrameSection({ title, className, children, ...props }) {
|
|
156
|
+
const slots = frameVariants();
|
|
157
|
+
return (_jsxs("section", { "data-slot": "frame-section", className: slots.section({ className }), ...props, children: [title !== undefined ? _jsx("h4", { className: slots.sectionTitle(), children: title }) : null, children] }));
|
|
158
|
+
}
|
|
159
|
+
FrameSection.displayName = 'Frame.Section';
|
|
160
|
+
/**
|
|
161
|
+
* The strip below the panel. Under `inset` its children become equal pills in
|
|
162
|
+
* the recessed band, the shape a row of decisions takes.
|
|
163
|
+
*/
|
|
164
|
+
function FrameFooter({ className, ...props }) {
|
|
165
|
+
return _jsx("div", { "data-slot": "frame-footer", className: frameVariants().footer({ className }), ...props });
|
|
166
|
+
}
|
|
167
|
+
FrameFooter.displayName = 'Frame.Footer';
|
|
168
|
+
export const Frame = Object.assign(FrameRoot, {
|
|
169
|
+
Header: FrameHeader,
|
|
170
|
+
Title: FrameTitle,
|
|
171
|
+
Description: FrameDescription,
|
|
172
|
+
Action: FrameAction,
|
|
173
|
+
Panel: FramePanel,
|
|
174
|
+
Row: FrameRow,
|
|
175
|
+
Media: FrameMedia,
|
|
176
|
+
Content: FrameContent,
|
|
177
|
+
Actions: FrameActions,
|
|
178
|
+
Section: FrameSection,
|
|
179
|
+
Footer: FrameFooter,
|
|
180
|
+
});
|
|
181
|
+
export { FrameHeader, FrameTitle, FrameDescription, FrameAction, FramePanel, FrameRow, FrameMedia, FrameContent, FrameActions, FrameSection, FrameFooter, };
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kpi — one number, and what it is doing.
|
|
3
|
+
*
|
|
4
|
+
* ```tsx
|
|
5
|
+
* <Kpi>
|
|
6
|
+
* <Kpi.Header>
|
|
7
|
+
* <Kpi.Title>Subscribers</Kpi.Title>
|
|
8
|
+
* </Kpi.Header>
|
|
9
|
+
* <Kpi.Content>
|
|
10
|
+
* <Kpi.Value>18,240</Kpi.Value>
|
|
11
|
+
* <Kpi.Trend value={6.4} />
|
|
12
|
+
* </Kpi.Content>
|
|
13
|
+
* <Kpi.Chart data={month} />
|
|
14
|
+
* </Kpi>
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* ## The number is the message
|
|
18
|
+
*
|
|
19
|
+
* A metric card is not a chart with a caption. The parts are sized and ordered
|
|
20
|
+
* round the number: a title that stays quiet, a value that does not, a trend
|
|
21
|
+
* that says which way and by how much, and a sparkline that is allowed to be
|
|
22
|
+
* small because nobody reads values off it.
|
|
23
|
+
*
|
|
24
|
+
* ## The trend is given a number
|
|
25
|
+
*
|
|
26
|
+
* `Kpi.Trend` takes the change as a number and decides the sign and the colour
|
|
27
|
+
* itself, so a minus sign and a red cannot fall out of step at the call site.
|
|
28
|
+
* Where a rise is the bad news, such as churn, latency or cost, say so with
|
|
29
|
+
* `goodDirection="down"` and the colour follows the meaning and not the sign.
|
|
30
|
+
*
|
|
31
|
+
* ## The same parts as the app
|
|
32
|
+
*
|
|
33
|
+
* The parts and their names are the app library's Kpi, so a metric written for
|
|
34
|
+
* one reads the same in the other. Two things differ because the platform
|
|
35
|
+
* does: the chart takes a plain list of numbers, and colours are named by the
|
|
36
|
+
* theme's roles as everywhere in this collection.
|
|
37
|
+
*
|
|
38
|
+
* ## Not a client module
|
|
39
|
+
*
|
|
40
|
+
* It holds no state and handles no events, so there is no `'use client'` at
|
|
41
|
+
* the top. A Server Component can render it, dotted parts and all, and it
|
|
42
|
+
* adds nothing to the JavaScript a page sends.
|
|
43
|
+
*/
|
|
44
|
+
import type { ComponentProps, ReactNode } from 'react';
|
|
45
|
+
/** Which of the theme's roles a chart or a bar is drawn in. */
|
|
46
|
+
export type KpiColor = 'primary' | 'info' | 'success' | 'warning' | 'destructive';
|
|
47
|
+
/** Which direction of movement is the good news for this metric. */
|
|
48
|
+
export type KpiGoodDirection = 'up' | 'down' | 'none';
|
|
49
|
+
/** How a part is coloured once the metric's polarity has been applied. */
|
|
50
|
+
export type KpiTone = 'good' | 'bad' | 'flat' | 'neutral';
|
|
51
|
+
export interface KpiProps extends ComponentProps<'div'> {
|
|
52
|
+
/** Draws the metric as a card of its own. Leave it off inside a frame or a group. */
|
|
53
|
+
surface?: boolean;
|
|
54
|
+
}
|
|
55
|
+
declare function KpiRoot({ surface, className, ...props }: KpiProps): import("react").JSX.Element;
|
|
56
|
+
declare namespace KpiRoot {
|
|
57
|
+
var displayName: string;
|
|
58
|
+
}
|
|
59
|
+
export interface KpiHeaderProps extends ComponentProps<'div'> {
|
|
60
|
+
}
|
|
61
|
+
/** The row over the number: an icon, the title, and anything at the end. */
|
|
62
|
+
declare function KpiHeader({ className, ...props }: KpiHeaderProps): import("react").JSX.Element;
|
|
63
|
+
declare namespace KpiHeader {
|
|
64
|
+
var displayName: string;
|
|
65
|
+
}
|
|
66
|
+
export interface KpiIconProps extends ComponentProps<'span'> {
|
|
67
|
+
/** How the tile is coloured: by good or bad news, or left neutral. */
|
|
68
|
+
tone?: KpiTone;
|
|
69
|
+
}
|
|
70
|
+
/** A small tile for an icon, at the start of the header. */
|
|
71
|
+
declare function KpiIcon({ tone, className, ...props }: KpiIconProps): import("react").JSX.Element;
|
|
72
|
+
declare namespace KpiIcon {
|
|
73
|
+
var displayName: string;
|
|
74
|
+
}
|
|
75
|
+
export interface KpiTitleProps extends ComponentProps<'div'> {
|
|
76
|
+
}
|
|
77
|
+
/** What the number measures. */
|
|
78
|
+
declare function KpiTitle({ className, ...props }: KpiTitleProps): import("react").JSX.Element;
|
|
79
|
+
declare namespace KpiTitle {
|
|
80
|
+
var displayName: string;
|
|
81
|
+
}
|
|
82
|
+
export interface KpiActionsProps extends ComponentProps<'div'> {
|
|
83
|
+
}
|
|
84
|
+
/** Whatever belongs at the end of the header: a period, a badge, a button. */
|
|
85
|
+
declare function KpiActions({ className, ...props }: KpiActionsProps): import("react").JSX.Element;
|
|
86
|
+
declare namespace KpiActions {
|
|
87
|
+
var displayName: string;
|
|
88
|
+
}
|
|
89
|
+
export interface KpiContentProps extends ComponentProps<'div'> {
|
|
90
|
+
/** `inline` puts a chart beside the number in a fixed box; `below` leaves it under everything. */
|
|
91
|
+
layout?: 'below' | 'inline';
|
|
92
|
+
}
|
|
93
|
+
/** The row the number is in, with a trend or a chart at its other end. */
|
|
94
|
+
declare function KpiContent({ layout, className, ...props }: KpiContentProps): import("react").JSX.Element;
|
|
95
|
+
declare namespace KpiContent {
|
|
96
|
+
var displayName: string;
|
|
97
|
+
}
|
|
98
|
+
export interface KpiStatProps extends ComponentProps<'div'> {
|
|
99
|
+
}
|
|
100
|
+
/** Title, value and trend stacked as one block, for a row with a chart beside it. */
|
|
101
|
+
declare function KpiStat({ className, ...props }: KpiStatProps): import("react").JSX.Element;
|
|
102
|
+
declare namespace KpiStat {
|
|
103
|
+
var displayName: string;
|
|
104
|
+
}
|
|
105
|
+
export interface KpiValueProps extends ComponentProps<'div'> {
|
|
106
|
+
}
|
|
107
|
+
/** The number. */
|
|
108
|
+
declare function KpiValue({ className, ...props }: KpiValueProps): import("react").JSX.Element;
|
|
109
|
+
declare namespace KpiValue {
|
|
110
|
+
var displayName: string;
|
|
111
|
+
}
|
|
112
|
+
export interface KpiTrendProps extends Omit<ComponentProps<'span'>, 'children'> {
|
|
113
|
+
/** The change. Its sign decides the direction. */
|
|
114
|
+
value: number;
|
|
115
|
+
/** Writes the change. Defaults to a signed percentage with one decimal: "+7.8%". */
|
|
116
|
+
format?: (value: number) => string;
|
|
117
|
+
/** Which direction is the good news. `down` for churn, latency, cost; `none` for no colour. */
|
|
118
|
+
goodDirection?: KpiGoodDirection;
|
|
119
|
+
/** `text` is a line of colour; `badge` puts a pill and an arrow round it. */
|
|
120
|
+
variant?: 'text' | 'badge';
|
|
121
|
+
/** Quiet words after the change: "this month". */
|
|
122
|
+
caption?: string;
|
|
123
|
+
/** A change this small or smaller is drawn as no change. */
|
|
124
|
+
threshold?: number;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* The change. Colour comes from what the movement means, not from its sign: a
|
|
128
|
+
* fall in churn is good news and is drawn as good news.
|
|
129
|
+
*/
|
|
130
|
+
declare function KpiTrend({ value, format, goodDirection, variant, caption, threshold, className, ...props }: KpiTrendProps): import("react").JSX.Element;
|
|
131
|
+
declare namespace KpiTrend {
|
|
132
|
+
var displayName: string;
|
|
133
|
+
}
|
|
134
|
+
export interface KpiChartProps extends Omit<ComponentProps<'svg'>, 'children' | 'color'> {
|
|
135
|
+
/** The values, oldest first. */
|
|
136
|
+
data: number[];
|
|
137
|
+
/** Which of the theme's roles the line is drawn in. */
|
|
138
|
+
color?: KpiColor;
|
|
139
|
+
/** Fills under the line with a wash of the same colour. */
|
|
140
|
+
filled?: boolean;
|
|
141
|
+
/** How tall the chart is, in pixels. */
|
|
142
|
+
height?: number;
|
|
143
|
+
/**
|
|
144
|
+
* A fixed box for the end of a `layout="inline"` row. Charts beside numbers
|
|
145
|
+
* of different lengths are then all one width, and line up down a column.
|
|
146
|
+
*/
|
|
147
|
+
inline?: boolean;
|
|
148
|
+
/** How thick the line is, in pixels. */
|
|
149
|
+
strokeWidth?: number;
|
|
150
|
+
}
|
|
151
|
+
/** The sparkline: the shape of the number over time, with no axis and no values. */
|
|
152
|
+
declare function KpiChart({ data, color, filled, height, inline, strokeWidth, className, style, ...props }: KpiChartProps): import("react").JSX.Element;
|
|
153
|
+
declare namespace KpiChart {
|
|
154
|
+
var displayName: string;
|
|
155
|
+
}
|
|
156
|
+
export interface KpiProgressProps extends Omit<ComponentProps<'div'>, 'children' | 'color'> {
|
|
157
|
+
/** How far along. */
|
|
158
|
+
value: number;
|
|
159
|
+
/** What `value` is out of. */
|
|
160
|
+
maxValue?: number;
|
|
161
|
+
/** Words at the start of the row over the bar: "of 5,000". */
|
|
162
|
+
label?: ReactNode;
|
|
163
|
+
/** Writes the percentage at the end of the row over the bar. */
|
|
164
|
+
showValueLabel?: boolean;
|
|
165
|
+
/** Which of the theme's roles the bar is drawn in. */
|
|
166
|
+
color?: KpiColor;
|
|
167
|
+
}
|
|
168
|
+
/** Progress towards a target, under the number. */
|
|
169
|
+
declare function KpiProgress({ value, maxValue, label, showValueLabel, color, className, ...props }: KpiProgressProps): import("react").JSX.Element;
|
|
170
|
+
declare namespace KpiProgress {
|
|
171
|
+
var displayName: string;
|
|
172
|
+
}
|
|
173
|
+
export interface KpiFooterProps extends ComponentProps<'div'> {
|
|
174
|
+
}
|
|
175
|
+
/** A quiet line under everything: when it was measured, how long is left. */
|
|
176
|
+
declare function KpiFooter({ className, ...props }: KpiFooterProps): import("react").JSX.Element;
|
|
177
|
+
declare namespace KpiFooter {
|
|
178
|
+
var displayName: string;
|
|
179
|
+
}
|
|
180
|
+
export interface KpiSeparatorProps extends ComponentProps<'div'> {
|
|
181
|
+
}
|
|
182
|
+
/** A rule between two parts of one card. */
|
|
183
|
+
declare function KpiSeparator({ className, ...props }: KpiSeparatorProps): import("react").JSX.Element;
|
|
184
|
+
declare namespace KpiSeparator {
|
|
185
|
+
var displayName: string;
|
|
186
|
+
}
|
|
187
|
+
export interface KpiGroupProps extends ComponentProps<'div'> {
|
|
188
|
+
/** Side by side with upright rules, or stacked with level ones. */
|
|
189
|
+
orientation?: 'horizontal' | 'vertical';
|
|
190
|
+
}
|
|
191
|
+
/** Several metrics as one panel, with a rule between each and the next. */
|
|
192
|
+
declare function KpiGroup({ orientation, className, ...props }: KpiGroupProps): import("react").JSX.Element;
|
|
193
|
+
declare namespace KpiGroup {
|
|
194
|
+
var displayName: string;
|
|
195
|
+
}
|
|
196
|
+
export declare const Kpi: typeof KpiRoot & {
|
|
197
|
+
Header: typeof KpiHeader;
|
|
198
|
+
Icon: typeof KpiIcon;
|
|
199
|
+
Title: typeof KpiTitle;
|
|
200
|
+
Actions: typeof KpiActions;
|
|
201
|
+
Content: typeof KpiContent;
|
|
202
|
+
Stat: typeof KpiStat;
|
|
203
|
+
Value: typeof KpiValue;
|
|
204
|
+
Trend: typeof KpiTrend;
|
|
205
|
+
Chart: typeof KpiChart;
|
|
206
|
+
Progress: typeof KpiProgress;
|
|
207
|
+
Footer: typeof KpiFooter;
|
|
208
|
+
Separator: typeof KpiSeparator;
|
|
209
|
+
Group: typeof KpiGroup;
|
|
210
|
+
};
|
|
211
|
+
export { KpiHeader, KpiIcon, KpiTitle, KpiActions, KpiContent, KpiStat, KpiValue, KpiTrend, KpiChart, KpiProgress, KpiFooter, KpiSeparator, KpiGroup, };
|