@tea-ui/admin 0.1.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/LICENSE +21 -0
- package/README.md +33 -0
- package/dist/data/stat-tile.d.ts +66 -0
- package/dist/data/stat-tile.js +104 -0
- package/dist/data/stat-tile.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -0
- package/dist/shell/index.d.ts +73 -0
- package/dist/shell/index.js +163 -0
- package/dist/shell/index.js.map +1 -0
- package/dist/states/index.d.ts +93 -0
- package/dist/states/index.js +195 -0
- package/dist/states/index.js.map +1 -0
- package/dist/styles.css +2 -0
- package/package.json +62 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 landnevermore
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# @tea-ui/admin
|
|
2
|
+
|
|
3
|
+
The admin layer: shell, navigation, data display and product states.
|
|
4
|
+
|
|
5
|
+
Part of [TEA UI](https://landnevermore.github.io/TEAui/) — a design system built once and reused across
|
|
6
|
+
products. The whole system is documented at https://landnevermore.github.io/TEAui/docs/.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @tea-ui/admin
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
React 18.2 or 19 is expected as a peer dependency.
|
|
15
|
+
|
|
16
|
+
## Use
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { Button } from "@tea-ui/admin";
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Styles
|
|
23
|
+
|
|
24
|
+
The token layer ships its stylesheet as a separate export, so it is only
|
|
25
|
+
downloaded when it is asked for:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import "@tea-ui/admin/styles.css";
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Licence
|
|
32
|
+
|
|
33
|
+
MIT. See [LICENSE](./LICENSE).
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import * as class_variance_authority_types from 'class-variance-authority/types';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { Tone } from '@tea-ui/tokens';
|
|
4
|
+
import { VariantProps } from '@tea-ui/utils';
|
|
5
|
+
export { stateAttributes } from '@tea-ui/core';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* TEA UI Admin — the metric tile.
|
|
9
|
+
*
|
|
10
|
+
* The audit found **six named stat-tile functions plus four inline variants**
|
|
11
|
+
* in one product, and **two near-identical 90%-overlapping `StatCard`s** in the
|
|
12
|
+
* other — including two functions with the *same name* and different props,
|
|
13
|
+
* which is the exact thing a shared library has to kill. They also disagreed
|
|
14
|
+
* about whether the value or the label came first.
|
|
15
|
+
*
|
|
16
|
+
* The resolution, made once:
|
|
17
|
+
* - the **value is the subject**, so the value leads and is the largest type;
|
|
18
|
+
* - the label sits above it as an uppercase micro-label, because that is what
|
|
19
|
+
* a scanning eye reads first in a row of tiles;
|
|
20
|
+
* - colour is a `tone`, never a raw class, and it is never the only signal —
|
|
21
|
+
* the tone always comes with a word;
|
|
22
|
+
* - a trend is an arrow *and* a signed percentage, so it survives a
|
|
23
|
+
* greyscale print and a screen reader.
|
|
24
|
+
*/
|
|
25
|
+
declare const statTileVariants: (props?: ({
|
|
26
|
+
layout?: "stacked" | "inline" | null | undefined;
|
|
27
|
+
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
28
|
+
type Trend = "up" | "down" | "flat";
|
|
29
|
+
interface StatTileProps extends React.ComponentProps<"div">, VariantProps<typeof statTileVariants> {
|
|
30
|
+
/** What is measured. Read as an eyebrow, above the value. */
|
|
31
|
+
label: string;
|
|
32
|
+
/** The measurement. A formatted string — formatting is the caller's job. */
|
|
33
|
+
value: React.ReactNode;
|
|
34
|
+
/** Secondary text under the value: a unit, a period, a comparison. */
|
|
35
|
+
hint?: React.ReactNode | undefined;
|
|
36
|
+
/** Semantic tone of the value. Never a raw colour. */
|
|
37
|
+
tone?: Tone | undefined;
|
|
38
|
+
/** Direction of change. An arrow plus a signed number, never colour alone. */
|
|
39
|
+
trend?: Trend | undefined;
|
|
40
|
+
/** The change itself, e.g. "+4 %". Required when `trend` is given. */
|
|
41
|
+
trendValue?: string | undefined;
|
|
42
|
+
/** Accessible description of the trend, e.g. "steigend gegenüber letzter Woche". */
|
|
43
|
+
trendDescription?: string | undefined;
|
|
44
|
+
/** Icon before the label. Decorative. */
|
|
45
|
+
icon?: React.ReactNode | undefined;
|
|
46
|
+
}
|
|
47
|
+
declare const StatTile: React.ForwardRefExoticComponent<Omit<StatTileProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
48
|
+
interface MetricCardProps extends StatTileProps {
|
|
49
|
+
/** A meter under the value, for a bounded resource. */
|
|
50
|
+
percent?: number | undefined;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* A stat tile with a bounded resource under it. The meter is the third signal
|
|
54
|
+
* beside the number and the tone, so a value of 87% does not depend on being
|
|
55
|
+
* read as amber.
|
|
56
|
+
*/
|
|
57
|
+
declare const MetricCard: React.ForwardRefExoticComponent<Omit<MetricCardProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
58
|
+
interface StatGridProps extends React.ComponentProps<"div"> {
|
|
59
|
+
/** Minimum tile width; the grid auto-fits. */
|
|
60
|
+
minTileWidth?: string | undefined;
|
|
61
|
+
children: React.ReactNode;
|
|
62
|
+
}
|
|
63
|
+
/** A responsive row of tiles. No per-breakpoint column counts to maintain. */
|
|
64
|
+
declare const StatGrid: React.ForwardRefExoticComponent<Omit<StatGridProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
65
|
+
|
|
66
|
+
export { MetricCard, type MetricCardProps, StatGrid, type StatGridProps, StatTile, type StatTileProps, type Trend, statTileVariants };
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { cn, cva } from "@tea-ui/utils";
|
|
4
|
+
import { Card, stateAttributes } from "@tea-ui/core";
|
|
5
|
+
const statTileVariants = cva("flex flex-col gap-1 border border-line bg-surface p-4", {
|
|
6
|
+
variants: {
|
|
7
|
+
layout: {
|
|
8
|
+
/** Value first. The default, and the right one for a dashboard. */
|
|
9
|
+
stacked: "",
|
|
10
|
+
/** Label and value side by side. For a dense table column. */
|
|
11
|
+
inline: "flex-row items-baseline justify-between gap-3"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
defaultVariants: { layout: "stacked" }
|
|
15
|
+
});
|
|
16
|
+
const TONE_TEXT = {
|
|
17
|
+
positive: "text-positive",
|
|
18
|
+
info: "text-info",
|
|
19
|
+
caution: "text-caution",
|
|
20
|
+
critical: "text-critical",
|
|
21
|
+
neutral: "text-fg"
|
|
22
|
+
};
|
|
23
|
+
const TREND_GLYPH = { up: "\u2191", down: "\u2193", flat: "\u2192" };
|
|
24
|
+
const TREND_WORD = { up: "steigend", down: "fallend", flat: "gleichbleibend" };
|
|
25
|
+
const StatTile = React.forwardRef(function StatTile2({
|
|
26
|
+
label,
|
|
27
|
+
value,
|
|
28
|
+
hint,
|
|
29
|
+
tone = "neutral",
|
|
30
|
+
trend,
|
|
31
|
+
trendValue,
|
|
32
|
+
trendDescription,
|
|
33
|
+
icon,
|
|
34
|
+
layout = "stacked",
|
|
35
|
+
className,
|
|
36
|
+
...props
|
|
37
|
+
}, ref) {
|
|
38
|
+
return /* @__PURE__ */ jsxs("div", { ref, className: cn(statTileVariants({ layout }), className), ...props, children: [
|
|
39
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
40
|
+
icon ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "text-fg-subtle", children: icon }) : null,
|
|
41
|
+
/* @__PURE__ */ jsx("span", { className: "text-label font-semibold uppercase tracking-widest text-fg-muted", children: label })
|
|
42
|
+
] }),
|
|
43
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2", children: [
|
|
44
|
+
/* @__PURE__ */ jsx("span", { className: cn("text-display font-semibold leading-none", TONE_TEXT[tone]), children: value }),
|
|
45
|
+
trend && trendValue ? /* @__PURE__ */ jsxs("span", { className: cn("text-micro", trend === "up" ? "text-positive" : trend === "down" ? "text-critical" : "text-fg-muted"), children: [
|
|
46
|
+
/* @__PURE__ */ jsxs("span", { "aria-hidden": "true", children: [
|
|
47
|
+
TREND_GLYPH[trend],
|
|
48
|
+
" "
|
|
49
|
+
] }),
|
|
50
|
+
trendValue,
|
|
51
|
+
/* @__PURE__ */ jsxs("span", { className: "sr-only", children: [
|
|
52
|
+
" ",
|
|
53
|
+
"(",
|
|
54
|
+
trendDescription ?? `${TREND_WORD[trend]} gegen\xFCber dem Vergleichszeitraum`,
|
|
55
|
+
")"
|
|
56
|
+
] })
|
|
57
|
+
] }) : null
|
|
58
|
+
] }),
|
|
59
|
+
hint ? /* @__PURE__ */ jsx("p", { className: "text-micro text-fg-muted", children: hint }) : null
|
|
60
|
+
] });
|
|
61
|
+
});
|
|
62
|
+
const MetricCard = React.forwardRef(function MetricCard2({ percent, tone = "neutral", ...props }, ref) {
|
|
63
|
+
return /* @__PURE__ */ jsxs(Card, { className: "flex flex-col gap-2 p-4", ref, children: [
|
|
64
|
+
/* @__PURE__ */ jsx(StatTile, { ...props, tone }),
|
|
65
|
+
typeof percent === "number" ? /* @__PURE__ */ jsx(
|
|
66
|
+
"div",
|
|
67
|
+
{
|
|
68
|
+
role: "meter",
|
|
69
|
+
"aria-valuenow": Math.round(percent),
|
|
70
|
+
"aria-valuemin": 0,
|
|
71
|
+
"aria-valuemax": 100,
|
|
72
|
+
"aria-label": props.label,
|
|
73
|
+
className: "h-1.5 w-full overflow-hidden rounded-pill bg-surface-3",
|
|
74
|
+
children: /* @__PURE__ */ jsx(
|
|
75
|
+
"div",
|
|
76
|
+
{
|
|
77
|
+
className: cn("h-full", TONE_TEXT[tone] !== "text-fg" ? "bg-current" : "bg-fg-muted"),
|
|
78
|
+
style: { width: `${Math.min(100, Math.max(0, percent))}%` }
|
|
79
|
+
}
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
) : null
|
|
83
|
+
] });
|
|
84
|
+
});
|
|
85
|
+
const StatGrid = React.forwardRef(function StatGrid2({ minTileWidth = "12rem", className, children, ...props }, ref) {
|
|
86
|
+
return /* @__PURE__ */ jsx(
|
|
87
|
+
"div",
|
|
88
|
+
{
|
|
89
|
+
ref,
|
|
90
|
+
style: { gridTemplateColumns: `repeat(auto-fit, minmax(min(${minTileWidth}, 100%), 1fr))` },
|
|
91
|
+
className: cn("grid gap-ui", className),
|
|
92
|
+
...props,
|
|
93
|
+
children
|
|
94
|
+
}
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
export {
|
|
98
|
+
MetricCard,
|
|
99
|
+
StatGrid,
|
|
100
|
+
StatTile,
|
|
101
|
+
statTileVariants,
|
|
102
|
+
stateAttributes
|
|
103
|
+
};
|
|
104
|
+
//# sourceMappingURL=stat-tile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/data/stat-tile.tsx"],"sourcesContent":["import * as React from \"react\";\nimport type { Tone } from \"@tea-ui/tokens\";\nimport { cn, cva, type VariantProps } from \"@tea-ui/utils\";\n\nimport { Card, stateAttributes } from \"@tea-ui/core\";\n\n/**\n * TEA UI Admin — the metric tile.\n *\n * The audit found **six named stat-tile functions plus four inline variants**\n * in one product, and **two near-identical 90%-overlapping `StatCard`s** in the\n * other — including two functions with the *same name* and different props,\n * which is the exact thing a shared library has to kill. They also disagreed\n * about whether the value or the label came first.\n *\n * The resolution, made once:\n * - the **value is the subject**, so the value leads and is the largest type;\n * - the label sits above it as an uppercase micro-label, because that is what\n * a scanning eye reads first in a row of tiles;\n * - colour is a `tone`, never a raw class, and it is never the only signal —\n * the tone always comes with a word;\n * - a trend is an arrow *and* a signed percentage, so it survives a\n * greyscale print and a screen reader.\n */\nexport const statTileVariants = cva(\"flex flex-col gap-1 border border-line bg-surface p-4\", {\n variants: {\n layout: {\n /** Value first. The default, and the right one for a dashboard. */\n stacked: \"\",\n /** Label and value side by side. For a dense table column. */\n inline: \"flex-row items-baseline justify-between gap-3\",\n },\n },\n defaultVariants: { layout: \"stacked\" },\n});\n\nexport type Trend = \"up\" | \"down\" | \"flat\";\n\nexport interface StatTileProps\n extends React.ComponentProps<\"div\">,\n VariantProps<typeof statTileVariants> {\n /** What is measured. Read as an eyebrow, above the value. */\n label: string;\n /** The measurement. A formatted string — formatting is the caller's job. */\n value: React.ReactNode;\n /** Secondary text under the value: a unit, a period, a comparison. */\n hint?: React.ReactNode | undefined;\n /** Semantic tone of the value. Never a raw colour. */\n tone?: Tone | undefined;\n /** Direction of change. An arrow plus a signed number, never colour alone. */\n trend?: Trend | undefined;\n /** The change itself, e.g. \"+4 %\". Required when `trend` is given. */\n trendValue?: string | undefined;\n /** Accessible description of the trend, e.g. \"steigend gegenüber letzter Woche\". */\n trendDescription?: string | undefined;\n /** Icon before the label. Decorative. */\n icon?: React.ReactNode | undefined;\n}\n\nconst TONE_TEXT: Record<Tone, string> = {\n positive: \"text-positive\",\n info: \"text-info\",\n caution: \"text-caution\",\n critical: \"text-critical\",\n neutral: \"text-fg\",\n};\n\nconst TREND_GLYPH: Record<Trend, string> = { up: \"\\u2191\", down: \"\\u2193\", flat: \"\\u2192\" };\nconst TREND_WORD: Record<Trend, string> = { up: \"steigend\", down: \"fallend\", flat: \"gleichbleibend\" };\n\nexport const StatTile = React.forwardRef<HTMLDivElement, StatTileProps>(function StatTile(\n {\n label,\n value,\n hint,\n tone = \"neutral\",\n trend,\n trendValue,\n trendDescription,\n icon,\n layout = \"stacked\",\n className,\n ...props\n },\n ref,\n) {\n return (\n <div ref={ref} className={cn(statTileVariants({ layout }), className)} {...props}>\n <div className=\"flex items-center gap-1.5\">\n {icon ? (\n <span aria-hidden=\"true\" className=\"text-fg-subtle\">\n {icon}\n </span>\n ) : null}\n <span className=\"text-label font-semibold uppercase tracking-widest text-fg-muted\">{label}</span>\n </div>\n\n <div className=\"flex items-baseline gap-2\">\n <span className={cn(\"text-display font-semibold leading-none\", TONE_TEXT[tone])}>{value}</span>\n {trend && trendValue ? (\n <span className={cn(\"text-micro\", trend === \"up\" ? \"text-positive\" : trend === \"down\" ? \"text-critical\" : \"text-fg-muted\")}>\n <span aria-hidden=\"true\">{TREND_GLYPH[trend]} </span>\n {trendValue}\n {/* The arrow glyph is decorative; this is the part that carries the\n meaning when the colour and the shape are unavailable. */}\n <span className=\"sr-only\">\n {\" \"}\n ({trendDescription ?? `${TREND_WORD[trend]} gegenüber dem Vergleichszeitraum`})\n </span>\n </span>\n ) : null}\n </div>\n\n {hint ? <p className=\"text-micro text-fg-muted\">{hint}</p> : null}\n </div>\n );\n});\n\nexport interface MetricCardProps extends StatTileProps {\n /** A meter under the value, for a bounded resource. */\n percent?: number | undefined;\n}\n\n/**\n * A stat tile with a bounded resource under it. The meter is the third signal\n * beside the number and the tone, so a value of 87% does not depend on being\n * read as amber.\n */\nexport const MetricCard = React.forwardRef<HTMLDivElement, MetricCardProps>(function MetricCard(\n { percent, tone = \"neutral\", ...props },\n ref,\n) {\n return (\n <Card className=\"flex flex-col gap-2 p-4\" ref={ref}>\n <StatTile {...props} tone={tone} />\n {typeof percent === \"number\" ? (\n <div\n role=\"meter\"\n aria-valuenow={Math.round(percent)}\n aria-valuemin={0}\n aria-valuemax={100}\n aria-label={props.label}\n className=\"h-1.5 w-full overflow-hidden rounded-pill bg-surface-3\"\n >\n <div\n className={cn(\"h-full\", TONE_TEXT[tone] !== \"text-fg\" ? \"bg-current\" : \"bg-fg-muted\")}\n style={{ width: `${Math.min(100, Math.max(0, percent))}%` }}\n />\n </div>\n ) : null}\n </Card>\n );\n});\n\n/* -------------------------------------------------------------------------- */\n\nexport interface StatGridProps extends React.ComponentProps<\"div\"> {\n /** Minimum tile width; the grid auto-fits. */\n minTileWidth?: string | undefined;\n children: React.ReactNode;\n}\n\n/** A responsive row of tiles. No per-breakpoint column counts to maintain. */\nexport const StatGrid = React.forwardRef<HTMLDivElement, StatGridProps>(function StatGrid(\n { minTileWidth = \"12rem\", className, children, ...props },\n ref,\n) {\n return (\n <div\n ref={ref}\n style={{ gridTemplateColumns: `repeat(auto-fit, minmax(min(${minTileWidth}, 100%), 1fr))` }}\n className={cn(\"grid gap-ui\", className)}\n {...props}\n >\n {children}\n </div>\n );\n});\n\nexport { stateAttributes };\n"],"mappings":"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","names":["StatTile","MetricCard","StatGrid"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { AdminShell, AdminShellProps, NavItem, Page, PageHeader, PageHeaderProps, PageProps } from './shell/index.js';
|
|
2
|
+
export { EmptyState, EmptyStateFiltered, EmptyStateNew, EmptyStateProps, ErrorState, ErrorStateProps, LoadingState, LoadingStateProps, MaintenanceState, NotFoundState, OfflineState, PermissionDeniedState, RefreshingIndicator, ServerErrorState, StateMessageProps } from './states/index.js';
|
|
3
|
+
export { MetricCard, MetricCardProps, StatGrid, StatGridProps, StatTile, StatTileProps, Trend, statTileVariants } from './data/stat-tile.js';
|
|
4
|
+
import 'react';
|
|
5
|
+
import '@tea-ui/ux-standards';
|
|
6
|
+
import 'class-variance-authority/types';
|
|
7
|
+
import '@tea-ui/tokens';
|
|
8
|
+
import '@tea-ui/utils';
|
|
9
|
+
import '@tea-ui/core';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { AdminShell, Page, PageHeader } from "./shell/index.js";
|
|
2
|
+
import {
|
|
3
|
+
EmptyState,
|
|
4
|
+
EmptyStateFiltered,
|
|
5
|
+
EmptyStateNew,
|
|
6
|
+
ErrorState,
|
|
7
|
+
LoadingState,
|
|
8
|
+
MaintenanceState,
|
|
9
|
+
NotFoundState,
|
|
10
|
+
OfflineState,
|
|
11
|
+
PermissionDeniedState,
|
|
12
|
+
RefreshingIndicator,
|
|
13
|
+
ServerErrorState
|
|
14
|
+
} from "./states/index.js";
|
|
15
|
+
import {
|
|
16
|
+
MetricCard,
|
|
17
|
+
StatGrid,
|
|
18
|
+
StatTile,
|
|
19
|
+
statTileVariants
|
|
20
|
+
} from "./data/stat-tile.js";
|
|
21
|
+
export {
|
|
22
|
+
AdminShell,
|
|
23
|
+
EmptyState,
|
|
24
|
+
EmptyStateFiltered,
|
|
25
|
+
EmptyStateNew,
|
|
26
|
+
ErrorState,
|
|
27
|
+
LoadingState,
|
|
28
|
+
MaintenanceState,
|
|
29
|
+
MetricCard,
|
|
30
|
+
NotFoundState,
|
|
31
|
+
OfflineState,
|
|
32
|
+
Page,
|
|
33
|
+
PageHeader,
|
|
34
|
+
PermissionDeniedState,
|
|
35
|
+
RefreshingIndicator,
|
|
36
|
+
ServerErrorState,
|
|
37
|
+
StatGrid,
|
|
38
|
+
StatTile,
|
|
39
|
+
statTileVariants
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * TEA UI Admin.\n *\n * The information-dense layer: an application shell, metric tiles, product\n * states and the data surfaces that a management tool is made of. Where Core\n * is neutral, Admin is optimised for scanning, for keyboard-heavy work, and for\n * showing a lot of state at once without the state competing with itself.\n *\n * Everything here composes Core. Nothing here calls an API or knows what a\n * server is — the domain arrives as props, as a wire value, or as a status key\n * resolved through `@tea-ui/ux-standards`.\n */\nexport { AdminShell, Page, PageHeader, type AdminShellProps, type NavItem, type PageHeaderProps, type PageProps } from \"./shell\";\n\nexport {\n EmptyState,\n EmptyStateFiltered,\n EmptyStateNew,\n ErrorState,\n LoadingState,\n MaintenanceState,\n NotFoundState,\n OfflineState,\n PermissionDeniedState,\n RefreshingIndicator,\n ServerErrorState,\n type EmptyStateProps,\n type ErrorStateProps,\n type LoadingStateProps,\n type StateMessageProps,\n} from \"./states\";\n\nexport {\n MetricCard,\n StatGrid,\n StatTile,\n statTileVariants,\n type MetricCardProps,\n type StatGridProps,\n type StatTileProps,\n type Trend,\n} from \"./data/stat-tile\";\n"],"mappings":"AAYA,SAAS,YAAY,MAAM,kBAA4F;AAEvH;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAKK;AAEP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAKK;","names":[]}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* TEA UI Admin — the application shell.
|
|
5
|
+
*
|
|
6
|
+
* This component decides the information architecture of an admin product, so
|
|
7
|
+
* the three rules it enforces are worth stating. All three were re-invented
|
|
8
|
+
* separately in both source products, which is exactly the cost TEA UI exists to
|
|
9
|
+
* remove.
|
|
10
|
+
*
|
|
11
|
+
* 1. **The sidebar collapses below 1024px and becomes a drawer.** Below `lg` a
|
|
12
|
+
* 256px sidebar stops being information and becomes an obstruction. Both
|
|
13
|
+
* source projects collapsed by hand, each with its own drawer and its own
|
|
14
|
+
* magic header height.
|
|
15
|
+
* 2. **There is a skip link, and it is the first focusable thing on the page.**
|
|
16
|
+
* Neither source product had one. Without it, a keyboard user tabs through
|
|
17
|
+
* the entire navigation on every page.
|
|
18
|
+
* 3. **The main region has a stable id**, because the skip link's target is an
|
|
19
|
+
* anchor, not a scroll hack.
|
|
20
|
+
*/
|
|
21
|
+
interface NavItem {
|
|
22
|
+
/** Stable key, matched against `activeId`. */
|
|
23
|
+
id: string;
|
|
24
|
+
/** The visible label. Say the thing, not "Home". */
|
|
25
|
+
label: string;
|
|
26
|
+
/** A router path, when the item is a link. */
|
|
27
|
+
href?: string | undefined;
|
|
28
|
+
onSelect?: (() => void) | undefined;
|
|
29
|
+
icon?: React.ReactNode | undefined;
|
|
30
|
+
/** A count or short status, rendered after the label. */
|
|
31
|
+
meta?: React.ReactNode | undefined;
|
|
32
|
+
/** Group heading, rendered above this item. */
|
|
33
|
+
group?: string | undefined;
|
|
34
|
+
disabled?: boolean | undefined;
|
|
35
|
+
}
|
|
36
|
+
interface AdminShellProps extends React.ComponentProps<"div"> {
|
|
37
|
+
/** Product name, shown in the sidebar header. */
|
|
38
|
+
product: string;
|
|
39
|
+
nav: readonly NavItem[];
|
|
40
|
+
/** Id of the current route. */
|
|
41
|
+
activeId: string;
|
|
42
|
+
onNavigate?: ((id: string) => void) | undefined;
|
|
43
|
+
/** Sidebar footer: a user menu, a version, a logout. */
|
|
44
|
+
sidebarFooter?: React.ReactNode | undefined;
|
|
45
|
+
/** Top bar, right of the title. */
|
|
46
|
+
actions?: React.ReactNode | undefined;
|
|
47
|
+
/** A live status summary, e.g. "Alle Dienste online". */
|
|
48
|
+
status?: React.ReactNode | undefined;
|
|
49
|
+
/** Id of the main region; also the skip link's target. */
|
|
50
|
+
mainId?: string | undefined;
|
|
51
|
+
children: React.ReactNode;
|
|
52
|
+
}
|
|
53
|
+
declare function AdminShell({ product, nav, activeId, onNavigate, sidebarFooter, actions, status, mainId, className, children, ...props }: AdminShellProps): React.ReactElement;
|
|
54
|
+
interface PageHeaderProps extends React.ComponentProps<"header"> {
|
|
55
|
+
title: string;
|
|
56
|
+
description?: string | undefined;
|
|
57
|
+
actions?: React.ReactNode | undefined;
|
|
58
|
+
/** Breadcrumb above the title. */
|
|
59
|
+
breadcrumb?: React.ReactNode | undefined;
|
|
60
|
+
}
|
|
61
|
+
/** The standard page head: optional breadcrumb, title, description, actions. */
|
|
62
|
+
declare const PageHeader: React.ForwardRefExoticComponent<Omit<PageHeaderProps, "ref"> & React.RefAttributes<HTMLElement>>;
|
|
63
|
+
interface PageProps extends React.ComponentProps<"div"> {
|
|
64
|
+
children: React.ReactNode;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The page body. One component, so that "how much padding does a TEA page have"
|
|
68
|
+
* has exactly one answer — the audit found `p-4`, `p-5` and `p-6` on the same
|
|
69
|
+
* kind of page inside one product.
|
|
70
|
+
*/
|
|
71
|
+
declare const Page: React.ForwardRefExoticComponent<Omit<PageProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
72
|
+
|
|
73
|
+
export { AdminShell, type AdminShellProps, type NavItem, Page, PageHeader, type PageHeaderProps, type PageProps };
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { Menu, X } from "@tea-ui/icons";
|
|
4
|
+
import { cn } from "@tea-ui/utils";
|
|
5
|
+
import { COPY } from "@tea-ui/ux-standards";
|
|
6
|
+
import {
|
|
7
|
+
Box,
|
|
8
|
+
Container,
|
|
9
|
+
Drawer,
|
|
10
|
+
DrawerBody,
|
|
11
|
+
DrawerContent,
|
|
12
|
+
DrawerHeader,
|
|
13
|
+
DrawerTitle,
|
|
14
|
+
HStack,
|
|
15
|
+
IconButton,
|
|
16
|
+
SkipLink,
|
|
17
|
+
stateAttributes
|
|
18
|
+
} from "@tea-ui/core";
|
|
19
|
+
function AdminShell({
|
|
20
|
+
product,
|
|
21
|
+
nav,
|
|
22
|
+
activeId,
|
|
23
|
+
onNavigate,
|
|
24
|
+
sidebarFooter,
|
|
25
|
+
actions,
|
|
26
|
+
status,
|
|
27
|
+
mainId = "tea-main",
|
|
28
|
+
className,
|
|
29
|
+
children,
|
|
30
|
+
...props
|
|
31
|
+
}) {
|
|
32
|
+
const [drawerOpen, setDrawerOpen] = React.useState(false);
|
|
33
|
+
const select = React.useCallback(
|
|
34
|
+
(item) => {
|
|
35
|
+
item.onSelect?.();
|
|
36
|
+
onNavigate?.(item.id);
|
|
37
|
+
setDrawerOpen(false);
|
|
38
|
+
},
|
|
39
|
+
[onNavigate]
|
|
40
|
+
);
|
|
41
|
+
return /* @__PURE__ */ jsxs(Box, { className: cn("min-h-dvh bg-canvas text-fg", className), ...props, children: [
|
|
42
|
+
/* @__PURE__ */ jsx(SkipLink, { targetId: mainId }),
|
|
43
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-h-dvh", children: [
|
|
44
|
+
/* @__PURE__ */ jsxs("aside", { className: "sticky top-0 hidden h-dvh w-64 shrink-0 flex-col border-e border-line bg-surface lg:flex", children: [
|
|
45
|
+
/* @__PURE__ */ jsx(SidebarHeader, { product }),
|
|
46
|
+
/* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto", children: /* @__PURE__ */ jsx(SidebarNav, { nav, activeId, onSelect: select }) }),
|
|
47
|
+
sidebarFooter ? /* @__PURE__ */ jsx("div", { className: "shrink-0 border-t border-line p-3", children: sidebarFooter }) : null
|
|
48
|
+
] }),
|
|
49
|
+
/* @__PURE__ */ jsx(Drawer, { open: drawerOpen, onOpenChange: setDrawerOpen, children: /* @__PURE__ */ jsxs(DrawerContent, { side: "start", showCloseButton: false, className: "w-72", children: [
|
|
50
|
+
/* @__PURE__ */ jsx(DrawerHeader, { children: /* @__PURE__ */ jsxs(DrawerTitle, { className: "flex items-center justify-between", children: [
|
|
51
|
+
product,
|
|
52
|
+
/* @__PURE__ */ jsx(IconButton, { label: COPY.navigation.closeMenu, variant: "ghost", size: "sm", onClick: () => setDrawerOpen(false), children: /* @__PURE__ */ jsx(X, { size: 16, "aria-hidden": "true" }) })
|
|
53
|
+
] }) }),
|
|
54
|
+
/* @__PURE__ */ jsx(DrawerBody, { className: "p-0", children: /* @__PURE__ */ jsx(SidebarNav, { nav, activeId, onSelect: select }) })
|
|
55
|
+
] }) }),
|
|
56
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [
|
|
57
|
+
/* @__PURE__ */ jsxs("header", { className: "sticky top-0 z-header flex h-14 shrink-0 items-center gap-3 border-b border-line bg-canvas px-4", children: [
|
|
58
|
+
/* @__PURE__ */ jsx(
|
|
59
|
+
IconButton,
|
|
60
|
+
{
|
|
61
|
+
label: COPY.navigation.toggleSidebar,
|
|
62
|
+
variant: "ghost",
|
|
63
|
+
size: "sm",
|
|
64
|
+
className: "lg:hidden",
|
|
65
|
+
onClick: () => setDrawerOpen(true),
|
|
66
|
+
children: /* @__PURE__ */ jsx(Menu, { size: 18, "aria-hidden": "true" })
|
|
67
|
+
}
|
|
68
|
+
),
|
|
69
|
+
/* @__PURE__ */ jsx("span", { className: "truncate text-ui font-semibold text-fg lg:hidden", children: product }),
|
|
70
|
+
status ? /* @__PURE__ */ jsx("div", { className: "hidden lg:flex", children: status }) : null,
|
|
71
|
+
/* @__PURE__ */ jsx(HStack, { gap: "ui", className: "ms-auto", children: actions })
|
|
72
|
+
] }),
|
|
73
|
+
/* @__PURE__ */ jsx("main", { id: mainId, tabIndex: -1, className: "min-w-0 flex-1 focus-visible:outline-none", children })
|
|
74
|
+
] })
|
|
75
|
+
] })
|
|
76
|
+
] });
|
|
77
|
+
}
|
|
78
|
+
function SidebarHeader({ product }) {
|
|
79
|
+
return /* @__PURE__ */ jsx("div", { className: "flex h-14 shrink-0 items-center border-b border-line px-4", children: /* @__PURE__ */ jsx("span", { className: "text-ui font-semibold text-fg", children: product }) });
|
|
80
|
+
}
|
|
81
|
+
function SidebarNav({
|
|
82
|
+
nav,
|
|
83
|
+
activeId,
|
|
84
|
+
onSelect
|
|
85
|
+
}) {
|
|
86
|
+
const groups = React.useMemo(() => groupNav(nav), [nav]);
|
|
87
|
+
return /* @__PURE__ */ jsx("nav", { "aria-label": COPY.navigation.main, className: "flex flex-col gap-4 p-3", children: groups.map((group) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5", children: [
|
|
88
|
+
group.label ? /* @__PURE__ */ jsx("p", { className: "px-2 pb-1 text-label font-semibold uppercase tracking-widest text-fg-subtle", children: group.label }) : null,
|
|
89
|
+
group.items.map((item) => /* @__PURE__ */ jsx(NavLink, { item, active: item.id === activeId, onSelect }, item.id))
|
|
90
|
+
] }, group.label || "_ungrouped")) });
|
|
91
|
+
}
|
|
92
|
+
function NavLink({
|
|
93
|
+
item,
|
|
94
|
+
active,
|
|
95
|
+
onSelect
|
|
96
|
+
}) {
|
|
97
|
+
const className = cn(
|
|
98
|
+
"flex w-full items-center gap-2 px-3 py-2 text-start text-ui transition-colors",
|
|
99
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
100
|
+
active ? "bg-accent-subtle font-medium text-accent" : "text-fg-muted hover:bg-surface-3 hover:text-fg"
|
|
101
|
+
);
|
|
102
|
+
const content = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
103
|
+
item.icon ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "shrink-0", children: item.icon }) : null,
|
|
104
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: item.label }),
|
|
105
|
+
item.meta ? /* @__PURE__ */ jsx("span", { className: "shrink-0 text-micro text-fg-subtle", children: item.meta }) : null
|
|
106
|
+
] });
|
|
107
|
+
const shared = {
|
|
108
|
+
className,
|
|
109
|
+
...active ? { "aria-current": "page" } : {},
|
|
110
|
+
...stateAttributes({ disabled: item.disabled, active })
|
|
111
|
+
};
|
|
112
|
+
if (item.href) {
|
|
113
|
+
return /* @__PURE__ */ jsx(
|
|
114
|
+
"a",
|
|
115
|
+
{
|
|
116
|
+
href: item.href,
|
|
117
|
+
onClick: (event) => {
|
|
118
|
+
if (!item.onSelect) return;
|
|
119
|
+
event.preventDefault();
|
|
120
|
+
onSelect(item);
|
|
121
|
+
},
|
|
122
|
+
...shared,
|
|
123
|
+
children: content
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
return /* @__PURE__ */ jsx("button", { type: "button", disabled: item.disabled, onClick: () => onSelect(item), ...shared, children: content });
|
|
128
|
+
}
|
|
129
|
+
function groupNav(nav) {
|
|
130
|
+
const order = [];
|
|
131
|
+
const map = /* @__PURE__ */ new Map();
|
|
132
|
+
for (const item of nav) {
|
|
133
|
+
const key = item.group ?? "";
|
|
134
|
+
if (!map.has(key)) {
|
|
135
|
+
map.set(key, []);
|
|
136
|
+
order.push(key);
|
|
137
|
+
}
|
|
138
|
+
map.get(key).push(item);
|
|
139
|
+
}
|
|
140
|
+
return order.map((label) => ({ label, items: map.get(label) ?? [] }));
|
|
141
|
+
}
|
|
142
|
+
const PageHeader = React.forwardRef(function PageHeader2({ title, description, actions, breadcrumb, className, children, ...props }, ref) {
|
|
143
|
+
return /* @__PURE__ */ jsxs("header", { ref, className: cn("flex flex-col gap-3 border-b border-line pb-4", className), ...props, children: [
|
|
144
|
+
breadcrumb,
|
|
145
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-start justify-between gap-3", children: [
|
|
146
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
147
|
+
/* @__PURE__ */ jsx("h1", { className: "text-title font-semibold text-fg", children: title }),
|
|
148
|
+
description ? /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-micro text-fg-muted", children: description }) : null
|
|
149
|
+
] }),
|
|
150
|
+
actions ? /* @__PURE__ */ jsx(HStack, { gap: "ui", children: actions }) : null
|
|
151
|
+
] }),
|
|
152
|
+
children
|
|
153
|
+
] });
|
|
154
|
+
});
|
|
155
|
+
const Page = React.forwardRef(function Page2({ className, children, ...props }, ref) {
|
|
156
|
+
return /* @__PURE__ */ jsx(Container, { size: "full", center: false, className: cn("py-4 md:py-6", className), ref, ...props, children });
|
|
157
|
+
});
|
|
158
|
+
export {
|
|
159
|
+
AdminShell,
|
|
160
|
+
Page,
|
|
161
|
+
PageHeader
|
|
162
|
+
};
|
|
163
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/shell/index.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { Menu, X } from \"@tea-ui/icons\";\nimport { cn } from \"@tea-ui/utils\";\nimport { COPY } from \"@tea-ui/ux-standards\";\nimport {\n Box,\n Container,\n Drawer,\n DrawerBody,\n DrawerContent,\n DrawerHeader,\n DrawerTitle,\n HStack,\n IconButton,\n SkipLink,\n stateAttributes,\n} from \"@tea-ui/core\";\n\n/**\n * TEA UI Admin — the application shell.\n *\n * This component decides the information architecture of an admin product, so\n * the three rules it enforces are worth stating. All three were re-invented\n * separately in both source products, which is exactly the cost TEA UI exists to\n * remove.\n *\n * 1. **The sidebar collapses below 1024px and becomes a drawer.** Below `lg` a\n * 256px sidebar stops being information and becomes an obstruction. Both\n * source projects collapsed by hand, each with its own drawer and its own\n * magic header height.\n * 2. **There is a skip link, and it is the first focusable thing on the page.**\n * Neither source product had one. Without it, a keyboard user tabs through\n * the entire navigation on every page.\n * 3. **The main region has a stable id**, because the skip link's target is an\n * anchor, not a scroll hack.\n */\nexport interface NavItem {\n /** Stable key, matched against `activeId`. */\n id: string;\n /** The visible label. Say the thing, not \"Home\". */\n label: string;\n /** A router path, when the item is a link. */\n href?: string | undefined;\n onSelect?: (() => void) | undefined;\n icon?: React.ReactNode | undefined;\n /** A count or short status, rendered after the label. */\n meta?: React.ReactNode | undefined;\n /** Group heading, rendered above this item. */\n group?: string | undefined;\n disabled?: boolean | undefined;\n}\n\nexport interface AdminShellProps extends React.ComponentProps<\"div\"> {\n /** Product name, shown in the sidebar header. */\n product: string;\n nav: readonly NavItem[];\n /** Id of the current route. */\n activeId: string;\n onNavigate?: ((id: string) => void) | undefined;\n /** Sidebar footer: a user menu, a version, a logout. */\n sidebarFooter?: React.ReactNode | undefined;\n /** Top bar, right of the title. */\n actions?: React.ReactNode | undefined;\n /** A live status summary, e.g. \"Alle Dienste online\". */\n status?: React.ReactNode | undefined;\n /** Id of the main region; also the skip link's target. */\n mainId?: string | undefined;\n children: React.ReactNode;\n}\n\nexport function AdminShell({\n product,\n nav,\n activeId,\n onNavigate,\n sidebarFooter,\n actions,\n status,\n mainId = \"tea-main\",\n className,\n children,\n ...props\n}: AdminShellProps): React.ReactElement {\n const [drawerOpen, setDrawerOpen] = React.useState(false);\n\n // Navigating from the drawer must close it, or the user lands on the new page\n // still behind an overlay, with focus left behind the scrim.\n const select = React.useCallback(\n (item: NavItem) => {\n item.onSelect?.();\n onNavigate?.(item.id);\n setDrawerOpen(false);\n },\n [onNavigate],\n );\n\n return (\n <Box className={cn(\"min-h-dvh bg-canvas text-fg\", className)} {...props}>\n <SkipLink targetId={mainId} />\n <div className=\"flex min-h-dvh\">\n <aside className=\"sticky top-0 hidden h-dvh w-64 shrink-0 flex-col border-e border-line bg-surface lg:flex\">\n <SidebarHeader product={product} />\n <div className=\"min-h-0 flex-1 overflow-y-auto\">\n <SidebarNav nav={nav} activeId={activeId} onSelect={select} />\n </div>\n {sidebarFooter ? <div className=\"shrink-0 border-t border-line p-3\">{sidebarFooter}</div> : null}\n </aside>\n\n <Drawer open={drawerOpen} onOpenChange={setDrawerOpen}>\n <DrawerContent side=\"start\" showCloseButton={false} className=\"w-72\">\n <DrawerHeader>\n <DrawerTitle className=\"flex items-center justify-between\">\n {product}\n <IconButton label={COPY.navigation.closeMenu} variant=\"ghost\" size=\"sm\" onClick={() => setDrawerOpen(false)}>\n <X size={16} aria-hidden=\"true\" />\n </IconButton>\n </DrawerTitle>\n </DrawerHeader>\n <DrawerBody className=\"p-0\">\n <SidebarNav nav={nav} activeId={activeId} onSelect={select} />\n </DrawerBody>\n </DrawerContent>\n </Drawer>\n\n <div className=\"flex min-w-0 flex-1 flex-col\">\n <header className=\"sticky top-0 z-header flex h-14 shrink-0 items-center gap-3 border-b border-line bg-canvas px-4\">\n <IconButton\n label={COPY.navigation.toggleSidebar}\n variant=\"ghost\"\n size=\"sm\"\n className=\"lg:hidden\"\n onClick={() => setDrawerOpen(true)}\n >\n <Menu size={18} aria-hidden=\"true\" />\n </IconButton>\n <span className=\"truncate text-ui font-semibold text-fg lg:hidden\">{product}</span>\n {status ? <div className=\"hidden lg:flex\">{status}</div> : null}\n <HStack gap=\"ui\" className=\"ms-auto\">\n {actions}\n </HStack>\n </header>\n <main id={mainId} tabIndex={-1} className=\"min-w-0 flex-1 focus-visible:outline-none\">\n {children}\n </main>\n </div>\n </div>\n </Box>\n );\n}\n\nfunction SidebarHeader({ product }: { product: string }): React.ReactElement {\n return (\n <div className=\"flex h-14 shrink-0 items-center border-b border-line px-4\">\n <span className=\"text-ui font-semibold text-fg\">{product}</span>\n </div>\n );\n}\n\nfunction SidebarNav({\n nav,\n activeId,\n onSelect,\n}: {\n nav: readonly NavItem[];\n activeId: string;\n onSelect: (item: NavItem) => void;\n}): React.ReactElement {\n const groups = React.useMemo(() => groupNav(nav), [nav]);\n return (\n <nav aria-label={COPY.navigation.main} className=\"flex flex-col gap-4 p-3\">\n {groups.map((group) => (\n <div key={group.label || \"_ungrouped\"} className=\"flex flex-col gap-0.5\">\n {group.label ? (\n <p className=\"px-2 pb-1 text-label font-semibold uppercase tracking-widest text-fg-subtle\">\n {group.label}\n </p>\n ) : null}\n {group.items.map((item) => (\n <NavLink key={item.id} item={item} active={item.id === activeId} onSelect={onSelect} />\n ))}\n </div>\n ))}\n </nav>\n );\n}\n\nfunction NavLink({\n item,\n active,\n onSelect,\n}: {\n item: NavItem;\n active: boolean;\n onSelect: (item: NavItem) => void;\n}): React.ReactElement {\n const className = cn(\n \"flex w-full items-center gap-2 px-3 py-2 text-start text-ui transition-colors\",\n \"disabled:pointer-events-none disabled:opacity-50\",\n active ? \"bg-accent-subtle font-medium text-accent\" : \"text-fg-muted hover:bg-surface-3 hover:text-fg\",\n );\n\n const content = (\n <>\n {item.icon ? (\n <span aria-hidden=\"true\" className=\"shrink-0\">\n {item.icon}\n </span>\n ) : null}\n <span className=\"min-w-0 flex-1 truncate\">{item.label}</span>\n {item.meta ? <span className=\"shrink-0 text-micro text-fg-subtle\">{item.meta}</span> : null}\n </>\n );\n\n // `aria-current` is what makes the position announceable. The background\n // colour alone is invisible to a screen reader and ambiguous in a greyscale\n // screenshot — the audit found active items marked by colour alone.\n const shared = {\n className,\n ...(active ? { \"aria-current\": \"page\" as const } : {}),\n ...stateAttributes({ disabled: item.disabled, active }),\n };\n\n if (item.href) {\n return (\n <a\n href={item.href}\n onClick={(event) => {\n // Let the browser handle a real href unless the consumer supplied\n // `onSelect`, in which case the router owns the navigation.\n if (!item.onSelect) return;\n event.preventDefault();\n onSelect(item);\n }}\n {...shared}\n >\n {content}\n </a>\n );\n }\n\n return (\n <button type=\"button\" disabled={item.disabled} onClick={() => onSelect(item)} {...shared}>\n {content}\n </button>\n );\n}\n\n/** Consecutive items sharing a `group` become one labelled block, in order. */\nfunction groupNav(nav: readonly NavItem[]): Array<{ label: string; items: NavItem[] }> {\n const order: string[] = [];\n const map = new Map<string, NavItem[]>();\n for (const item of nav) {\n const key = item.group ?? \"\";\n if (!map.has(key)) {\n map.set(key, []);\n order.push(key);\n }\n map.get(key)!.push(item);\n }\n return order.map((label) => ({ label, items: map.get(label) ?? [] }));\n}\n\n/* -------------------------------------------------------------------------- */\n\nexport interface PageHeaderProps extends React.ComponentProps<\"header\"> {\n title: string;\n description?: string | undefined;\n actions?: React.ReactNode | undefined;\n /** Breadcrumb above the title. */\n breadcrumb?: React.ReactNode | undefined;\n}\n\n/** The standard page head: optional breadcrumb, title, description, actions. */\nexport const PageHeader = React.forwardRef<HTMLElement, PageHeaderProps>(function PageHeader(\n { title, description, actions, breadcrumb, className, children, ...props },\n ref,\n) {\n return (\n <header ref={ref} className={cn(\"flex flex-col gap-3 border-b border-line pb-4\", className)} {...props}>\n {breadcrumb}\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <h1 className=\"text-title font-semibold text-fg\">{title}</h1>\n {description ? <p className=\"mt-0.5 text-micro text-fg-muted\">{description}</p> : null}\n </div>\n {actions ? <HStack gap=\"ui\">{actions}</HStack> : null}\n </div>\n {children}\n </header>\n );\n});\n\nexport interface PageProps extends React.ComponentProps<\"div\"> {\n children: React.ReactNode;\n}\n\n/**\n * The page body. One component, so that \"how much padding does a TEA page have\"\n * has exactly one answer — the audit found `p-4`, `p-5` and `p-6` on the same\n * kind of page inside one product.\n */\nexport const Page = React.forwardRef<HTMLDivElement, PageProps>(function Page(\n { className, children, ...props },\n ref,\n) {\n return (\n <Container size=\"full\" center={false} className={cn(\"py-4 md:py-6\", className)} ref={ref} {...props}>\n {children}\n </Container>\n );\n});\n"],"mappings":"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","names":["PageHeader","Page"]}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { ErrorAnatomy } from '@tea-ui/ux-standards';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* TEA UI Admin — product states.
|
|
6
|
+
*
|
|
7
|
+
* The audit found, in one product alone, eight hand-written "Lade …" loaders
|
|
8
|
+
* (five of them byte-identical), ten empty states, nine error banners and four
|
|
9
|
+
* visual dialects of each, plus a `Skeleton` component that was fully built and
|
|
10
|
+
* completely unused. This module is the answer: one implementation per state,
|
|
11
|
+
* and each one answers the questions its state actually raises.
|
|
12
|
+
*
|
|
13
|
+
* Every state here follows the standard's rule — **every meaningful empty state
|
|
14
|
+
* answers what is empty, why, and what to do next** — and every error follows
|
|
15
|
+
* the error anatomy: what happened, why, what to do, whether it recovers.
|
|
16
|
+
*/
|
|
17
|
+
interface EmptyStateProps extends React.ComponentProps<"div"> {
|
|
18
|
+
/** What is empty, named specifically. "Keine Server", not "Keine Einträge". */
|
|
19
|
+
title: string;
|
|
20
|
+
/** Why it is empty, when the reason is not obvious. */
|
|
21
|
+
description?: string | undefined;
|
|
22
|
+
/** The primary next action, when there is one. */
|
|
23
|
+
action?: React.ReactNode | undefined;
|
|
24
|
+
/** A secondary escape hatch, e.g. clearing a filter. */
|
|
25
|
+
secondaryAction?: React.ReactNode | undefined;
|
|
26
|
+
icon?: React.ReactNode | undefined;
|
|
27
|
+
className?: string | undefined;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* A dashed border is used deliberately: it reads as "placeholder", which is
|
|
31
|
+
* what an empty region is. A solid card would imply content is merely late.
|
|
32
|
+
*/
|
|
33
|
+
declare const EmptyState: React.ForwardRefExoticComponent<Omit<EmptyStateProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
34
|
+
/** "Nothing has been created yet" — distinct from "your filter matched nothing". */
|
|
35
|
+
declare const EmptyStateNew: React.ForwardRefExoticComponent<Omit<Omit<EmptyStateProps, "title"> & {
|
|
36
|
+
noun: string;
|
|
37
|
+
}, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
38
|
+
/** "Your filter excluded everything" — the reason is the filter, so say so. */
|
|
39
|
+
declare const EmptyStateFiltered: React.ForwardRefExoticComponent<Omit<Omit<EmptyStateProps, "title" | "description"> & {
|
|
40
|
+
noun: string;
|
|
41
|
+
}, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
42
|
+
interface LoadingStateProps extends React.ComponentProps<"div"> {
|
|
43
|
+
/** What is loading, for the announcement. */
|
|
44
|
+
label: string;
|
|
45
|
+
/**
|
|
46
|
+
* Skeleton count. A skeleton that mirrors the real layout beats a spinner
|
|
47
|
+
* because it reserves the space, so nothing reflows when the data lands.
|
|
48
|
+
*/
|
|
49
|
+
rows?: number | undefined;
|
|
50
|
+
className?: string | undefined;
|
|
51
|
+
}
|
|
52
|
+
declare const LoadingState: React.ForwardRefExoticComponent<Omit<LoadingStateProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
53
|
+
/**
|
|
54
|
+
* A *refreshing* indicator: content stays. The audit's clearest performance
|
|
55
|
+
* defect was replacing the whole region with a centred spinner on every poll, so
|
|
56
|
+
* a five-second auto-refresh flashed the UI away from content the user was
|
|
57
|
+
* reading.
|
|
58
|
+
*/
|
|
59
|
+
declare const RefreshingIndicator: React.ForwardRefExoticComponent<Omit<React.ClassAttributes<HTMLSpanElement> & React.HTMLAttributes<HTMLSpanElement> & {
|
|
60
|
+
label?: string | undefined;
|
|
61
|
+
}, "ref"> & React.RefAttributes<HTMLSpanElement>>;
|
|
62
|
+
interface ErrorStateProps extends React.ComponentProps<"div"> {
|
|
63
|
+
/** A structured error. The component builds the message from it. */
|
|
64
|
+
error: ErrorAnatomy;
|
|
65
|
+
/** Retry callback. Only rendered when a retry is a plausible action. */
|
|
66
|
+
onRetry?: (() => void) | undefined;
|
|
67
|
+
/** Label for the retry action. */
|
|
68
|
+
retryLabel?: string | undefined;
|
|
69
|
+
className?: string | undefined;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* An error, rendered as an answer rather than a string.
|
|
73
|
+
*
|
|
74
|
+
* The visible text is always "what happened, and why". The recovery action is
|
|
75
|
+
* only offered when a retry would plausibly succeed — offering it for a
|
|
76
|
+
* validation error is how people learn that buttons do nothing. The technical
|
|
77
|
+
* detail is behind a disclosure, because it is for the person filing the
|
|
78
|
+
* report, not for the person who hit the problem.
|
|
79
|
+
*/
|
|
80
|
+
declare const ErrorState: React.ForwardRefExoticComponent<Omit<ErrorStateProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
81
|
+
interface StateMessageProps extends React.ComponentProps<"div"> {
|
|
82
|
+
title?: string | undefined;
|
|
83
|
+
description?: string | undefined;
|
|
84
|
+
action?: React.ReactNode | undefined;
|
|
85
|
+
icon?: React.ReactNode | undefined;
|
|
86
|
+
}
|
|
87
|
+
declare const OfflineState: React.ForwardRefExoticComponent<Omit<StateMessageProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
88
|
+
declare const MaintenanceState: React.ForwardRefExoticComponent<Omit<StateMessageProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
89
|
+
declare const PermissionDeniedState: React.ForwardRefExoticComponent<Omit<StateMessageProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
90
|
+
declare const NotFoundState: React.ForwardRefExoticComponent<Omit<StateMessageProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
91
|
+
declare const ServerErrorState: React.ForwardRefExoticComponent<Omit<StateMessageProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
92
|
+
|
|
93
|
+
export { EmptyState, EmptyStateFiltered, EmptyStateNew, type EmptyStateProps, ErrorState, type ErrorStateProps, LoadingState, type LoadingStateProps, MaintenanceState, NotFoundState, OfflineState, PermissionDeniedState, RefreshingIndicator, ServerErrorState, type StateMessageProps };
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import {
|
|
4
|
+
CircleAlert,
|
|
5
|
+
CloudOff,
|
|
6
|
+
Lock,
|
|
7
|
+
RefreshCw,
|
|
8
|
+
SearchX,
|
|
9
|
+
ServerCrash,
|
|
10
|
+
Wrench
|
|
11
|
+
} from "@tea-ui/icons";
|
|
12
|
+
import { EMPTY_STATE_TITLES, ERROR_TITLES } from "@tea-ui/ux-standards";
|
|
13
|
+
import { cn } from "@tea-ui/utils";
|
|
14
|
+
import { Button, Skeleton, stateAttributes } from "@tea-ui/core";
|
|
15
|
+
const EmptyState = React.forwardRef(function EmptyState2({ title, description, action, secondaryAction, icon, className, ...props }, ref) {
|
|
16
|
+
return /* @__PURE__ */ jsxs(
|
|
17
|
+
"div",
|
|
18
|
+
{
|
|
19
|
+
ref,
|
|
20
|
+
className: cn(
|
|
21
|
+
"flex flex-col items-center gap-3 border border-dashed border-line p-8 text-center",
|
|
22
|
+
className
|
|
23
|
+
),
|
|
24
|
+
"data-tea-state": "empty",
|
|
25
|
+
"data-empty": "true",
|
|
26
|
+
...stateAttributes({ empty: true }),
|
|
27
|
+
...props,
|
|
28
|
+
children: [
|
|
29
|
+
icon ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "text-fg-subtle", children: icon }) : null,
|
|
30
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
31
|
+
/* @__PURE__ */ jsx("p", { className: "text-ui font-medium text-fg", children: title }),
|
|
32
|
+
description ? /* @__PURE__ */ jsx("p", { className: "mt-1 text-micro text-fg-muted", children: description }) : null
|
|
33
|
+
] }),
|
|
34
|
+
action || secondaryAction ? /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-center gap-2", children: [
|
|
35
|
+
action,
|
|
36
|
+
secondaryAction
|
|
37
|
+
] }) : null
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
const EmptyStateNew = React.forwardRef(
|
|
43
|
+
function EmptyStateNew2({ noun, ...props }, ref) {
|
|
44
|
+
return /* @__PURE__ */ jsx(EmptyState, { ref, title: `Noch keine ${noun}`, ...props });
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
const EmptyStateFiltered = React.forwardRef(function EmptyStateFiltered2({ noun, ...props }, ref) {
|
|
48
|
+
return /* @__PURE__ */ jsx(
|
|
49
|
+
EmptyState,
|
|
50
|
+
{
|
|
51
|
+
ref,
|
|
52
|
+
title: EMPTY_STATE_TITLES.filtered,
|
|
53
|
+
description: `Keine ${noun} passen zu den aktuellen Filtern.`,
|
|
54
|
+
icon: /* @__PURE__ */ jsx(SearchX, { size: 20, "aria-hidden": "true" }),
|
|
55
|
+
...props
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
const LoadingState = React.forwardRef(function LoadingState2({ label, rows = 3, className, ...props }, ref) {
|
|
60
|
+
return /* @__PURE__ */ jsxs(
|
|
61
|
+
"div",
|
|
62
|
+
{
|
|
63
|
+
ref,
|
|
64
|
+
role: "status",
|
|
65
|
+
"aria-live": "polite",
|
|
66
|
+
"aria-busy": "true",
|
|
67
|
+
className: cn("flex flex-col gap-2", className),
|
|
68
|
+
"data-tea-state": "loading",
|
|
69
|
+
...props,
|
|
70
|
+
children: [
|
|
71
|
+
Array.from({ length: Math.max(1, rows) }, (_, index) => /* @__PURE__ */ jsx(Skeleton, { className: cn("h-9 w-full", index === rows - 1 && "w-2/3") }, index)),
|
|
72
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: label })
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
const RefreshingIndicator = React.forwardRef(function RefreshingIndicator2({ label = "Wird aktualisiert", className, ...props }, ref) {
|
|
78
|
+
return /* @__PURE__ */ jsxs(
|
|
79
|
+
"span",
|
|
80
|
+
{
|
|
81
|
+
ref,
|
|
82
|
+
role: "status",
|
|
83
|
+
"aria-live": "polite",
|
|
84
|
+
className: cn("inline-flex items-center gap-1.5 text-micro text-fg-muted", className),
|
|
85
|
+
"data-tea-state": "refreshing",
|
|
86
|
+
...props,
|
|
87
|
+
children: [
|
|
88
|
+
/* @__PURE__ */ jsx(RefreshCw, { size: 12, "aria-hidden": "true", className: "animate-spin", "data-tea-motion": "decorative" }),
|
|
89
|
+
label
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
const RETRY_LABEL = {
|
|
95
|
+
automatic: "Erneut versuchen",
|
|
96
|
+
action: "Erneut versuchen",
|
|
97
|
+
choice: "Ausw\xE4hlen",
|
|
98
|
+
none: "Zur\xFCck"
|
|
99
|
+
};
|
|
100
|
+
const ErrorState = React.forwardRef(function ErrorState2({ error, onRetry, retryLabel, className, ...props }, ref) {
|
|
101
|
+
const showRetry = Boolean(onRetry) && (error.recovery === "action" || error.recovery === "automatic");
|
|
102
|
+
return /* @__PURE__ */ jsxs(
|
|
103
|
+
"div",
|
|
104
|
+
{
|
|
105
|
+
ref,
|
|
106
|
+
role: "alert",
|
|
107
|
+
className: cn("flex flex-col items-center gap-3 border border-critical-border bg-critical-subtle p-6 text-center", className),
|
|
108
|
+
"data-tea-state": "error",
|
|
109
|
+
...stateAttributes({ invalid: true }),
|
|
110
|
+
...props,
|
|
111
|
+
children: [
|
|
112
|
+
/* @__PURE__ */ jsx(CircleAlert, { size: 20, "aria-hidden": "true", className: "text-critical" }),
|
|
113
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
114
|
+
/* @__PURE__ */ jsx("p", { className: "text-ui font-medium text-fg", children: error.title }),
|
|
115
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-micro text-fg-muted", children: error.detail }),
|
|
116
|
+
error.action ? /* @__PURE__ */ jsx("p", { className: "mt-2 text-micro text-fg", children: error.action }) : null
|
|
117
|
+
] }),
|
|
118
|
+
showRetry ? /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: onRetry, children: retryLabel ?? RETRY_LABEL[error.recovery] }) : null,
|
|
119
|
+
error.technical ? /* @__PURE__ */ jsxs("details", { className: "w-full text-start", children: [
|
|
120
|
+
/* @__PURE__ */ jsx("summary", { className: "cursor-pointer text-micro text-fg-muted", children: "Technische Details" }),
|
|
121
|
+
/* @__PURE__ */ jsx("pre", { className: "mt-2 overflow-auto whitespace-pre-wrap border border-line bg-surface-2 p-2 text-left font-mono text-micro text-fg-muted", children: error.technical })
|
|
122
|
+
] }) : null,
|
|
123
|
+
error.reference ? /* @__PURE__ */ jsxs("p", { className: "font-mono text-micro text-fg-subtle", children: [
|
|
124
|
+
"Referenz: ",
|
|
125
|
+
error.reference
|
|
126
|
+
] }) : null
|
|
127
|
+
]
|
|
128
|
+
}
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
function StateMessage({
|
|
132
|
+
title,
|
|
133
|
+
description,
|
|
134
|
+
action,
|
|
135
|
+
icon,
|
|
136
|
+
className,
|
|
137
|
+
tone = "neutral",
|
|
138
|
+
...props
|
|
139
|
+
}) {
|
|
140
|
+
return /* @__PURE__ */ jsxs(
|
|
141
|
+
"div",
|
|
142
|
+
{
|
|
143
|
+
role: "status",
|
|
144
|
+
className: cn(
|
|
145
|
+
"flex flex-col items-center gap-3 border p-8 text-center",
|
|
146
|
+
tone === "caution" ? "border-caution-border bg-caution-subtle" : "border-line",
|
|
147
|
+
className
|
|
148
|
+
),
|
|
149
|
+
...props,
|
|
150
|
+
children: [
|
|
151
|
+
icon ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: tone === "caution" ? "text-caution" : "text-fg-subtle", children: icon }) : null,
|
|
152
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
153
|
+
/* @__PURE__ */ jsx("p", { className: "text-ui font-medium text-fg", children: title }),
|
|
154
|
+
description ? /* @__PURE__ */ jsx("p", { className: "mt-1 text-micro text-fg-muted", children: description }) : null
|
|
155
|
+
] }),
|
|
156
|
+
action
|
|
157
|
+
]
|
|
158
|
+
}
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
const OfflineState = React.forwardRef(function OfflineState2({ title = "Offline", description = "Es gibt keine Verbindung zum Server. \xC4nderungen werden nicht gespeichert.", ...props }, ref) {
|
|
162
|
+
return /* @__PURE__ */ jsx(StateMessage, { ref, tone: "caution", title, description, icon: /* @__PURE__ */ jsx(CloudOff, { size: 20, "aria-hidden": "true" }), "data-tea-state": "offline", ...props });
|
|
163
|
+
});
|
|
164
|
+
const MaintenanceState = React.forwardRef(function MaintenanceState2({ title = "Wartungsmodus", description = "Die Anwendung wird gerade gewartet und ist m\xF6glicherweise nicht verf\xFCgbar.", ...props }, ref) {
|
|
165
|
+
return /* @__PURE__ */ jsx(StateMessage, { ref, title, description, icon: /* @__PURE__ */ jsx(Wrench, { size: 20, "aria-hidden": "true" }), "data-tea-state": "maintenance", ...props });
|
|
166
|
+
});
|
|
167
|
+
const PermissionDeniedState = React.forwardRef(
|
|
168
|
+
function PermissionDeniedState2({
|
|
169
|
+
title = ERROR_TITLES.forbidden,
|
|
170
|
+
description = "F\xFCr diesen Bereich fehlt dir die n\xF6tige Berechtigung. Bitte wende dich an eine Administration.",
|
|
171
|
+
...props
|
|
172
|
+
}, ref) {
|
|
173
|
+
return /* @__PURE__ */ jsx(StateMessage, { ref, tone: "caution", title, description, icon: /* @__PURE__ */ jsx(Lock, { size: 20, "aria-hidden": "true" }), "data-tea-state": "forbidden", ...props });
|
|
174
|
+
}
|
|
175
|
+
);
|
|
176
|
+
const NotFoundState = React.forwardRef(function NotFoundState2({ title = ERROR_TITLES.notFound, description = "Diese Seite oder dieser Eintrag existiert nicht.", ...props }, ref) {
|
|
177
|
+
return /* @__PURE__ */ jsx(StateMessage, { ref, title, description, icon: /* @__PURE__ */ jsx(SearchX, { size: 20, "aria-hidden": "true" }), "data-tea-state": "notFound", ...props });
|
|
178
|
+
});
|
|
179
|
+
const ServerErrorState = React.forwardRef(function ServerErrorState2({ title = ERROR_TITLES.server, description = "Der Server konnte die Anfrage nicht verarbeiten.", ...props }, ref) {
|
|
180
|
+
return /* @__PURE__ */ jsx(StateMessage, { ref, tone: "caution", title, description, icon: /* @__PURE__ */ jsx(ServerCrash, { size: 20, "aria-hidden": "true" }), "data-tea-state": "error", ...props });
|
|
181
|
+
});
|
|
182
|
+
export {
|
|
183
|
+
EmptyState,
|
|
184
|
+
EmptyStateFiltered,
|
|
185
|
+
EmptyStateNew,
|
|
186
|
+
ErrorState,
|
|
187
|
+
LoadingState,
|
|
188
|
+
MaintenanceState,
|
|
189
|
+
NotFoundState,
|
|
190
|
+
OfflineState,
|
|
191
|
+
PermissionDeniedState,
|
|
192
|
+
RefreshingIndicator,
|
|
193
|
+
ServerErrorState
|
|
194
|
+
};
|
|
195
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/states/index.tsx"],"sourcesContent":["import * as React from \"react\";\nimport {\n CircleAlert,\n CloudOff,\n Lock,\n RefreshCw,\n SearchX,\n ServerCrash,\n Wrench,\n} from \"@tea-ui/icons\";\nimport { EMPTY_STATE_TITLES, ERROR_TITLES, type ErrorAnatomy, type ErrorRecovery } from \"@tea-ui/ux-standards\";\nimport { cn } from \"@tea-ui/utils\";\n\nimport { Button, Skeleton, stateAttributes } from \"@tea-ui/core\";\n\n/**\n * TEA UI Admin — product states.\n *\n * The audit found, in one product alone, eight hand-written \"Lade …\" loaders\n * (five of them byte-identical), ten empty states, nine error banners and four\n * visual dialects of each, plus a `Skeleton` component that was fully built and\n * completely unused. This module is the answer: one implementation per state,\n * and each one answers the questions its state actually raises.\n *\n * Every state here follows the standard's rule — **every meaningful empty state\n * answers what is empty, why, and what to do next** — and every error follows\n * the error anatomy: what happened, why, what to do, whether it recovers.\n */\n\n/* -------------------------------------------------------------------------- */\n/* Empty */\n/* -------------------------------------------------------------------------- */\n\nexport interface EmptyStateProps extends React.ComponentProps<\"div\"> {\n /** What is empty, named specifically. \"Keine Server\", not \"Keine Einträge\". */\n title: string;\n /** Why it is empty, when the reason is not obvious. */\n description?: string | undefined;\n /** The primary next action, when there is one. */\n action?: React.ReactNode | undefined;\n /** A secondary escape hatch, e.g. clearing a filter. */\n secondaryAction?: React.ReactNode | undefined;\n icon?: React.ReactNode | undefined;\n className?: string | undefined;\n}\n\n/**\n * A dashed border is used deliberately: it reads as \"placeholder\", which is\n * what an empty region is. A solid card would imply content is merely late.\n */\nexport const EmptyState = React.forwardRef<HTMLDivElement, EmptyStateProps>(function EmptyState(\n { title, description, action, secondaryAction, icon, className, ...props },\n ref,\n) {\n return (\n <div\n ref={ref}\n className={cn(\n \"flex flex-col items-center gap-3 border border-dashed border-line p-8 text-center\",\n className,\n )}\n data-tea-state=\"empty\"\n data-empty=\"true\"\n {...stateAttributes({ empty: true })}\n {...props}\n >\n {icon ? (\n <span aria-hidden=\"true\" className=\"text-fg-subtle\">\n {icon}\n </span>\n ) : null}\n <div>\n <p className=\"text-ui font-medium text-fg\">{title}</p>\n {description ? <p className=\"mt-1 text-micro text-fg-muted\">{description}</p> : null}\n </div>\n {action || secondaryAction ? (\n <div className=\"flex flex-wrap items-center justify-center gap-2\">\n {action}\n {secondaryAction}\n </div>\n ) : null}\n </div>\n );\n});\n\n/** \"Nothing has been created yet\" — distinct from \"your filter matched nothing\". */\nexport const EmptyStateNew = React.forwardRef<HTMLDivElement, Omit<EmptyStateProps, \"title\"> & { noun: string }>(\n function EmptyStateNew({ noun, ...props }, ref) {\n return <EmptyState ref={ref} title={`Noch keine ${noun}`} {...props} />;\n },\n);\n\n/** \"Your filter excluded everything\" — the reason is the filter, so say so. */\nexport const EmptyStateFiltered = React.forwardRef<\n HTMLDivElement,\n Omit<EmptyStateProps, \"title\" | \"description\"> & { noun: string }\n>(function EmptyStateFiltered({ noun, ...props }, ref) {\n return (\n <EmptyState\n ref={ref}\n title={EMPTY_STATE_TITLES.filtered}\n description={`Keine ${noun} passen zu den aktuellen Filtern.`}\n icon={<SearchX size={20} aria-hidden=\"true\" />}\n {...props}\n />\n );\n});\n\n/* -------------------------------------------------------------------------- */\n/* Loading */\n/* -------------------------------------------------------------------------- */\n\nexport interface LoadingStateProps extends React.ComponentProps<\"div\"> {\n /** What is loading, for the announcement. */\n label: string;\n /**\n * Skeleton count. A skeleton that mirrors the real layout beats a spinner\n * because it reserves the space, so nothing reflows when the data lands.\n */\n rows?: number | undefined;\n className?: string | undefined;\n}\n\nexport const LoadingState = React.forwardRef<HTMLDivElement, LoadingStateProps>(function LoadingState(\n { label, rows = 3, className, ...props },\n ref,\n) {\n return (\n <div\n ref={ref}\n role=\"status\"\n aria-live=\"polite\"\n aria-busy=\"true\"\n className={cn(\"flex flex-col gap-2\", className)}\n data-tea-state=\"loading\"\n {...props}\n >\n {Array.from({ length: Math.max(1, rows) }, (_, index) => (\n <Skeleton key={index} className={cn(\"h-9 w-full\", index === rows - 1 && \"w-2/3\")} />\n ))}\n <span className=\"sr-only\">{label}</span>\n </div>\n );\n});\n\n/**\n * A *refreshing* indicator: content stays. The audit's clearest performance\n * defect was replacing the whole region with a centred spinner on every poll, so\n * a five-second auto-refresh flashed the UI away from content the user was\n * reading.\n */\nexport const RefreshingIndicator = React.forwardRef<\n HTMLSpanElement,\n React.ComponentProps<\"span\"> & { label?: string | undefined }\n>(function RefreshingIndicator({ label = \"Wird aktualisiert\", className, ...props }, ref) {\n return (\n <span\n ref={ref}\n role=\"status\"\n aria-live=\"polite\"\n className={cn(\"inline-flex items-center gap-1.5 text-micro text-fg-muted\", className)}\n data-tea-state=\"refreshing\"\n {...props}\n >\n <RefreshCw size={12} aria-hidden=\"true\" className=\"animate-spin\" data-tea-motion=\"decorative\" />\n {label}\n </span>\n );\n});\n\n/* -------------------------------------------------------------------------- */\n/* Error */\n/* -------------------------------------------------------------------------- */\n\nexport interface ErrorStateProps extends React.ComponentProps<\"div\"> {\n /** A structured error. The component builds the message from it. */\n error: ErrorAnatomy;\n /** Retry callback. Only rendered when a retry is a plausible action. */\n onRetry?: (() => void) | undefined;\n /** Label for the retry action. */\n retryLabel?: string | undefined;\n className?: string | undefined;\n}\n\nconst RETRY_LABEL: Record<ErrorRecovery, string> = {\n automatic: \"Erneut versuchen\",\n action: \"Erneut versuchen\",\n choice: \"Auswählen\",\n none: \"Zurück\",\n};\n\n/**\n * An error, rendered as an answer rather than a string.\n *\n * The visible text is always \"what happened, and why\". The recovery action is\n * only offered when a retry would plausibly succeed — offering it for a\n * validation error is how people learn that buttons do nothing. The technical\n * detail is behind a disclosure, because it is for the person filing the\n * report, not for the person who hit the problem.\n */\nexport const ErrorState = React.forwardRef<HTMLDivElement, ErrorStateProps>(function ErrorState(\n { error, onRetry, retryLabel, className, ...props },\n ref,\n) {\n const showRetry = Boolean(onRetry) && (error.recovery === \"action\" || error.recovery === \"automatic\");\n return (\n <div\n ref={ref}\n role=\"alert\"\n className={cn(\"flex flex-col items-center gap-3 border border-critical-border bg-critical-subtle p-6 text-center\", className)}\n data-tea-state=\"error\"\n {...stateAttributes({ invalid: true })}\n {...props}\n >\n <CircleAlert size={20} aria-hidden=\"true\" className=\"text-critical\" />\n <div>\n <p className=\"text-ui font-medium text-fg\">{error.title}</p>\n <p className=\"mt-1 text-micro text-fg-muted\">{error.detail}</p>\n {error.action ? <p className=\"mt-2 text-micro text-fg\">{error.action}</p> : null}\n </div>\n {showRetry ? (\n <Button variant=\"outline\" size=\"sm\" onClick={onRetry}>\n {retryLabel ?? RETRY_LABEL[error.recovery]}\n </Button>\n ) : null}\n {error.technical ? (\n <details className=\"w-full text-start\">\n <summary className=\"cursor-pointer text-micro text-fg-muted\">Technische Details</summary>\n <pre className=\"mt-2 overflow-auto whitespace-pre-wrap border border-line bg-surface-2 p-2 text-left font-mono text-micro text-fg-muted\">\n {error.technical}\n </pre>\n </details>\n ) : null}\n {error.reference ? (\n <p className=\"font-mono text-micro text-fg-subtle\">Referenz: {error.reference}</p>\n ) : null}\n </div>\n );\n});\n\n/* -------------------------------------------------------------------------- */\n/* The specific states */\n/* -------------------------------------------------------------------------- */\n\nexport interface StateMessageProps extends React.ComponentProps<\"div\"> {\n title?: string | undefined;\n description?: string | undefined;\n action?: React.ReactNode | undefined;\n icon?: React.ReactNode | undefined;\n}\n\nfunction StateMessage({\n title,\n description,\n action,\n icon,\n className,\n tone = \"neutral\",\n ...props\n}: StateMessageProps & { tone?: string }): React.ReactElement {\n return (\n <div\n role=\"status\"\n className={cn(\n \"flex flex-col items-center gap-3 border p-8 text-center\",\n tone === \"caution\" ? \"border-caution-border bg-caution-subtle\" : \"border-line\",\n className,\n )}\n {...props}\n >\n {icon ? (\n <span aria-hidden=\"true\" className={tone === \"caution\" ? \"text-caution\" : \"text-fg-subtle\"}>\n {icon}\n </span>\n ) : null}\n <div>\n <p className=\"text-ui font-medium text-fg\">{title}</p>\n {description ? <p className=\"mt-1 text-micro text-fg-muted\">{description}</p> : null}\n </div>\n {action}\n </div>\n );\n}\n\nexport const OfflineState = React.forwardRef<HTMLDivElement, StateMessageProps>(function OfflineState(\n { title = \"Offline\", description = \"Es gibt keine Verbindung zum Server. Änderungen werden nicht gespeichert.\", ...props },\n ref,\n) {\n return <StateMessage ref={ref} tone=\"caution\" title={title} description={description} icon={<CloudOff size={20} aria-hidden=\"true\" />} data-tea-state=\"offline\" {...props} />;\n});\n\nexport const MaintenanceState = React.forwardRef<HTMLDivElement, StateMessageProps>(function MaintenanceState(\n { title = \"Wartungsmodus\", description = \"Die Anwendung wird gerade gewartet und ist möglicherweise nicht verfügbar.\", ...props },\n ref,\n) {\n return <StateMessage ref={ref} title={title} description={description} icon={<Wrench size={20} aria-hidden=\"true\" />} data-tea-state=\"maintenance\" {...props} />;\n});\n\nexport const PermissionDeniedState = React.forwardRef<HTMLDivElement, StateMessageProps>(\n function PermissionDeniedState(\n {\n title = ERROR_TITLES.forbidden,\n description = \"Für diesen Bereich fehlt dir die nötige Berechtigung. Bitte wende dich an eine Administration.\",\n ...props\n },\n ref,\n ) {\n return <StateMessage ref={ref} tone=\"caution\" title={title} description={description} icon={<Lock size={20} aria-hidden=\"true\" />} data-tea-state=\"forbidden\" {...props} />;\n },\n);\n\nexport const NotFoundState = React.forwardRef<HTMLDivElement, StateMessageProps>(function NotFoundState(\n { title = ERROR_TITLES.notFound, description = \"Diese Seite oder dieser Eintrag existiert nicht.\", ...props },\n ref,\n) {\n return <StateMessage ref={ref} title={title} description={description} icon={<SearchX size={20} aria-hidden=\"true\" />} data-tea-state=\"notFound\" {...props} />;\n});\n\nexport const ServerErrorState = React.forwardRef<HTMLDivElement, StateMessageProps>(function ServerErrorState(\n { title = ERROR_TITLES.server, description = \"Der Server konnte die Anfrage nicht verarbeiten.\", ...props },\n ref,\n) {\n return <StateMessage ref={ref} tone=\"caution\" title={title} description={description} icon={<ServerCrash size={20} aria-hidden=\"true\" />} data-tea-state=\"error\" {...props} />;\n});\n\n/* -------------------------------------------------------------------------- */\n"],"mappings":"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","names":["EmptyState","EmptyStateNew","EmptyStateFiltered","LoadingState","RefreshingIndicator","ErrorState","OfflineState","MaintenanceState","PermissionDeniedState","NotFoundState","ServerErrorState"]}
|
package/dist/styles.css
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
|
|
2
|
+
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial;--tw-ease:initial;--tw-content:"";--tw-animation-delay:0s;--tw-animation-direction:normal;--tw-animation-duration:initial;--tw-animation-fill-mode:none;--tw-animation-iteration-count:1;--tw-enter-blur:0;--tw-enter-opacity:1;--tw-enter-rotate:0;--tw-enter-scale:1;--tw-enter-translate-x:0;--tw-enter-translate-y:0;--tw-exit-blur:0;--tw-exit-opacity:1;--tw-exit-rotate:0;--tw-exit-scale:1;--tw-exit-translate-x:0;--tw-exit-translate-y:0}}}@layer theme{:root,:host{--spacing:.25rem;--container-xs:20rem;--container-sm:24rem;--container-md:28rem;--container-lg:32rem;--container-xl:36rem;--container-2xl:42rem;--container-3xl:48rem;--container-4xl:56rem;--container-5xl:64rem;--container-6xl:72rem;--tracking-tight:-.025em;--tracking-widest:.1em;--leading-tight:1.25;--animate-spin:spin 1s linear infinite;--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--default-font-family:var(--tea-font-sans);--default-mono-font-family:var(--tea-font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}*,:before,:after,::backdrop{border-color:var(--tea-line)}html{-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;scrollbar-color:var(--tea-line-strong) var(--tea-canvas);color-scheme:dark}body{background-color:var(--tea-canvas);color:var(--tea-fg);font-family:var(--tea-font-sans);font-size:var(--tea-text-ui);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;line-height:1.25rem}:focus-visible{outline:2px solid var(--tea-ring);outline-offset:2px}::selection{background-color:var(--tea-accent);color:var(--tea-accent-fg)}*{scrollbar-width:thin;scrollbar-color:var(--tea-line-strong) var(--tea-canvas)}::-webkit-scrollbar{width:10px;height:10px}::-webkit-scrollbar-track{background:var(--tea-canvas)}::-webkit-scrollbar-thumb{background:var(--tea-line-strong)}::-webkit-scrollbar-thumb:hover{background:var(--tea-accent)}::-webkit-scrollbar-corner{background:var(--tea-canvas)}@media (pointer:coarse){[data-tea-touch]{min-height:var(--tea-touch-min);min-width:var(--tea-touch-min)}}@media (prefers-reduced-motion:reduce){*,:before,:after,::backdrop{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}[data-tea-motion=essential]{animation-duration:.01ms!important}}}@layer components{[data-tea-motion=essential]:after{content:"";animation:1.6s steps(4,end) infinite tea-pulse-essential}}@layer utilities{.pointer-events-none{pointer-events:none}.collapse{visibility:collapse}.invisible{visibility:hidden}.visible{visibility:visible}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.inset-0{inset:0}.inset-x-0{inset-inline:0}.inset-y-0{inset-block:0}.start-0{inset-inline-start:calc(var(--spacing) * 0)}.start-1\/2{inset-inline-start:50%}.start-2{inset-inline-start:calc(var(--spacing) * 2)}.end-0{inset-inline-end:calc(var(--spacing) * 0)}.end-2{inset-inline-end:calc(var(--spacing) * 2)}.end-3{inset-inline-end:calc(var(--spacing) * 3)}.top-0{top:0}.top-1\/2{top:50%}.top-2{top:calc(var(--spacing) * 2)}.top-3{top:calc(var(--spacing) * 3)}.top-14{top:calc(var(--spacing) * 14)}.top-full{top:100%}.bottom-0{bottom:0}.z-50{z-index:50}.z-base{z-index:0}.z-dropdown{z-index:300}.z-header{z-index:200}.z-max{z-index:900}.z-modal{z-index:500}.z-overlay{z-index:400}.z-popover{z-index:600}.z-raised{z-index:10}.z-sticky{z-index:100}.z-toast{z-index:700}.z-tooltip{z-index:800}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.-mx-1{margin-inline:calc(var(--spacing) * -1)}.mx-auto{margin-inline:auto}.my-1{margin-block:var(--spacing)}.my-2{margin-block:calc(var(--spacing) * 2)}.my-3{margin-block:calc(var(--spacing) * 3)}.my-8{margin-block:calc(var(--spacing) * 8)}.-ms-1{margin-inline-start:calc(var(--spacing) * -1)}.-ms-px{margin-inline-start:-1px}.ms-0{margin-inline-start:0}.ms-1{margin-inline-start:var(--spacing)}.ms-5{margin-inline-start:calc(var(--spacing) * 5)}.ms-auto{margin-inline-start:auto}.-mt-1{margin-top:calc(var(--spacing) * -1)}.-mt-px{margin-top:-1px}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:var(--spacing)}.mt-1\.5{margin-top:calc(var(--spacing) * 1.5)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mt-5{margin-top:calc(var(--spacing) * 5)}.mt-6{margin-top:calc(var(--spacing) * 6)}.mt-8{margin-top:calc(var(--spacing) * 8)}.mt-10{margin-top:calc(var(--spacing) * 10)}.mt-12{margin-top:calc(var(--spacing) * 12)}.mt-16{margin-top:calc(var(--spacing) * 16)}.mb-1{margin-bottom:var(--spacing)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.size-1\.5{width:calc(var(--spacing) * 1.5);height:calc(var(--spacing) * 1.5)}.size-2{width:calc(var(--spacing) * 2);height:calc(var(--spacing) * 2)}.size-3{width:calc(var(--spacing) * 3);height:calc(var(--spacing) * 3)}.size-3\.5{width:calc(var(--spacing) * 3.5);height:calc(var(--spacing) * 3.5)}.size-4{width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.size-5{width:calc(var(--spacing) * 5);height:calc(var(--spacing) * 5)}.size-6{width:calc(var(--spacing) * 6);height:calc(var(--spacing) * 6)}.size-7{width:calc(var(--spacing) * 7);height:calc(var(--spacing) * 7)}.size-10{width:calc(var(--spacing) * 10);height:calc(var(--spacing) * 10)}.size-\[calc\(var\(--tea-control-h\)\+0\.5rem\)\]{width:calc(var(--tea-control-h) + .5rem);height:calc(var(--tea-control-h) + .5rem)}.size-\[calc\(var\(--tea-control-h\)-0\.25rem\)\]{width:calc(var(--tea-control-h) - .25rem);height:calc(var(--tea-control-h) - .25rem)}.size-\[length\:var\(--tea-control-h\)\]{width:var(--tea-control-h);height:var(--tea-control-h)}.size-full{width:100%;height:100%}.control{height:var(--tea-control-h);padding-inline:var(--tea-control-px)}.control-h{height:var(--tea-control-h)}.h-1\.5{height:calc(var(--spacing) * 1.5)}.h-2{height:calc(var(--spacing) * 2)}.h-3{height:calc(var(--spacing) * 3)}.h-4{height:calc(var(--spacing) * 4)}.h-5{height:calc(var(--spacing) * 5)}.h-6{height:calc(var(--spacing) * 6)}.h-8{height:calc(var(--spacing) * 8)}.h-9{height:calc(var(--spacing) * 9)}.h-14{height:calc(var(--spacing) * 14)}.h-16{height:calc(var(--spacing) * 16)}.h-\[calc\(100dvh-3\.5rem\)\]{height:calc(100dvh - 3.5rem)}.h-\[calc\(var\(--tea-control-h\)\+0\.25rem\)\]{height:calc(var(--tea-control-h) + .25rem)}.h-\[calc\(var\(--tea-control-h\)-0\.25rem\)\]{height:calc(var(--tea-control-h) - .25rem)}.h-auto{height:auto}.h-dvh{height:100dvh}.h-full{height:100%}.h-px{height:1px}.max-h-72{max-height:calc(var(--spacing) * 72)}.max-h-80{max-height:calc(var(--spacing) * 80)}.max-h-\[--radix-select-content-available-height\]{max-height:--radix-select-content-available-height}.max-h-\[80dvh\]{max-height:80dvh}.max-h-\[85dvh\]{max-height:85dvh}.max-h-\[calc\(100dvh-4rem\)\]{max-height:calc(100dvh - 4rem)}.max-h-\[var\(--radix-select-content-available-height\)\]{max-height:var(--radix-select-content-available-height)}.min-h-0{min-height:0}.min-h-\[60dvh\]{min-height:60dvh}.min-h-dvh{min-height:100dvh}.w-1\/3{width:33.3333%}.w-2\/3{width:66.6667%}.w-7{width:calc(var(--spacing) * 7)}.w-24{width:calc(var(--spacing) * 24)}.w-32{width:calc(var(--spacing) * 32)}.w-40{width:calc(var(--spacing) * 40)}.w-56{width:calc(var(--spacing) * 56)}.w-64{width:calc(var(--spacing) * 64)}.w-72{width:calc(var(--spacing) * 72)}.w-80{width:calc(var(--spacing) * 80)}.w-96{width:calc(var(--spacing) * 96)}.w-\[calc\(100\%-2rem\)\]{width:calc(100% - 2rem)}.w-\[min\(20rem\,85vw\)\]{width:min(20rem,85vw)}.w-\[min\(24rem\,90vw\)\]{width:min(24rem,90vw)}.w-\[min\(40rem\,90vw\)\]{width:min(40rem,90vw)}.w-full{width:100%}.w-px{width:1px}.max-w-2xl{max-width:var(--container-2xl)}.max-w-3xl{max-width:var(--container-3xl)}.max-w-4xl{max-width:var(--container-4xl)}.max-w-5xl{max-width:var(--container-5xl)}.max-w-6xl{max-width:var(--container-6xl)}.max-w-\[100rem\]{max-width:100rem}.max-w-lg{max-width:var(--container-lg)}.max-w-md{max-width:var(--container-md)}.max-w-none{max-width:none}.max-w-sm{max-width:var(--container-sm)}.max-w-xl{max-width:var(--container-xl)}.max-w-xs{max-width:var(--container-xs)}.min-w-0{min-width:0}.min-w-5{min-width:calc(var(--spacing) * 5)}.min-w-8{min-width:calc(var(--spacing) * 8)}.min-w-32{min-width:calc(var(--spacing) * 32)}.min-w-40{min-width:calc(var(--spacing) * 40)}.min-w-48{min-width:calc(var(--spacing) * 48)}.min-w-56{min-width:calc(var(--spacing) * 56)}.min-w-\[36rem\]{min-width:36rem}.min-w-\[40rem\]{min-width:40rem}.min-w-\[var\(--radix-select-trigger-width\)\]{min-width:var(--radix-select-trigger-width)}.flex-1{flex:1}.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.grow{flex-grow:1}.border-collapse{border-collapse:collapse}.origin-top{transform-origin:top}.-translate-x-1\/2{--tw-translate-x:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-y-1\/2{--tw-translate-y:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.scale-0{--tw-scale-x:0%;--tw-scale-y:0%;--tw-scale-z:0%;scale:var(--tw-scale-x) var(--tw-scale-y)}.animate-pulse{animation:var(--animate-pulse)}.animate-spin{animation:var(--animate-spin)}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.cursor-text{cursor:text}.touch-none{touch-action:none}.resize-none{resize:none}.resize-y{resize:vertical}.scroll-mt-20{scroll-margin-top:calc(var(--spacing) * 20)}.list-decimal{list-style-type:decimal}.list-disc{list-style-type:disc}.list-none{list-style-type:none}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.flex-row{flex-direction:row}.flex-wrap{flex-wrap:wrap}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-start{align-items:flex-start}.items-stretch{align-items:stretch}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.field-gap{gap:var(--tea-field-gap)}.gap-0{gap:0}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-5{gap:calc(var(--spacing) * 5)}.gap-6{gap:calc(var(--spacing) * 6)}.gap-8{gap:calc(var(--spacing) * 8)}.gap-10{gap:calc(var(--spacing) * 10)}.gap-ui{gap:var(--tea-gap)}.section-gap{gap:var(--tea-section-gap)}.gap-x-6{column-gap:calc(var(--spacing) * 6)}.gap-y-2{row-gap:calc(var(--spacing) * 2)}.self-stretch{align-self:stretch}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded-full{border-radius:3.40282e38px}.rounded-none{border-radius:0}.rounded-pill{border-radius:9999px}.rounded-t-none{border-top-left-radius:0;border-top-right-radius:0}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-s{border-inline-start-style:var(--tw-border-style);border-inline-start-width:1px}.border-s-2{border-inline-start-style:var(--tw-border-style);border-inline-start-width:2px}.border-e{border-inline-end-style:var(--tw-border-style);border-inline-end-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-l-2{border-left-style:var(--tw-border-style);border-left-width:2px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-caution{border-color:var(--tea-caution)}.border-caution-border{border-color:var(--tea-caution-border)}.border-critical{border-color:var(--tea-critical)}.border-critical-border{border-color:var(--tea-critical-border)}.border-destructive{border-color:var(--tea-destructive)}.border-info{border-color:var(--tea-info)}.border-info-border{border-color:var(--tea-info-border)}.border-line{border-color:var(--tea-line)}.border-line-strong{border-color:var(--tea-line-strong)}.border-line\/50{border-color:var(--tea-line)}@supports (color:color-mix(in lab, red, red)){.border-line\/50{border-color:color-mix(in oklab, var(--tea-line) 50%, transparent)}}.border-neutral-border{border-color:var(--tea-neutral-border)}.border-positive{border-color:var(--tea-positive)}.border-positive-border{border-color:var(--tea-positive-border)}.border-primary{border-color:var(--tea-primary)}.border-primary-border{border-color:var(--tea-primary-border)}.border-transparent{border-color:#0000}.bg-accent{background-color:var(--tea-accent)}.bg-accent-subtle{background-color:var(--tea-accent-subtle)}.bg-canvas{background-color:var(--tea-canvas)}.bg-caution{background-color:var(--tea-caution)}.bg-caution-subtle{background-color:var(--tea-caution-subtle)}.bg-critical{background-color:var(--tea-critical)}.bg-critical-subtle{background-color:var(--tea-critical-subtle)}.bg-current{background-color:currentColor}.bg-destructive{background-color:var(--tea-destructive)}.bg-fg{background-color:var(--tea-fg)}.bg-fg-muted{background-color:var(--tea-fg-muted)}.bg-info{background-color:var(--tea-info)}.bg-info-subtle{background-color:var(--tea-info-subtle)}.bg-line{background-color:var(--tea-line)}.bg-neutral{background-color:var(--tea-neutral)}.bg-neutral-subtle{background-color:var(--tea-neutral-subtle)}.bg-overlay{background-color:var(--tea-overlay)}.bg-positive{background-color:var(--tea-positive)}.bg-positive-subtle{background-color:var(--tea-positive-subtle)}.bg-primary{background-color:var(--tea-primary)}.bg-primary-fg{background-color:var(--tea-primary-fg)}.bg-primary-subtle{background-color:var(--tea-primary-subtle)}.bg-surface{background-color:var(--tea-surface)}.bg-surface-2{background-color:var(--tea-surface-2)}.bg-surface-3{background-color:var(--tea-surface-3)}.bg-transparent{background-color:#0000}.p-0{padding:0}.p-0\.5{padding:calc(var(--spacing) * .5)}.p-1{padding:var(--spacing)}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-6{padding:calc(var(--spacing) * 6)}.p-8{padding:calc(var(--spacing) * 8)}.pad-card{padding:var(--tea-pad-card)}.px-0{padding-inline:0}.px-1{padding-inline:var(--spacing)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-\[calc\(var\(--tea-control-px\)\+0\.25rem\)\]{padding-inline:calc(var(--tea-control-px) + .25rem)}.px-\[calc\(var\(--tea-control-px\)-0\.25rem\)\]{padding-inline:calc(var(--tea-control-px) - .25rem)}.px-\[length\:var\(--tea-control-px\)\]{padding-inline:var(--tea-control-px)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:var(--spacing)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-5{padding-block:calc(var(--spacing) * 5)}.py-6{padding-block:calc(var(--spacing) * 6)}.py-8{padding-block:calc(var(--spacing) * 8)}.py-10{padding-block:calc(var(--spacing) * 10)}.py-12{padding-block:calc(var(--spacing) * 12)}.py-14{padding-block:calc(var(--spacing) * 14)}.py-20{padding-block:calc(var(--spacing) * 20)}.ps-2{padding-inline-start:calc(var(--spacing) * 2)}.ps-3{padding-inline-start:calc(var(--spacing) * 3)}.ps-4{padding-inline-start:calc(var(--spacing) * 4)}.ps-8{padding-inline-start:calc(var(--spacing) * 8)}.pe-1{padding-inline-end:var(--spacing)}.pe-2{padding-inline-end:calc(var(--spacing) * 2)}.pe-4{padding-inline-end:calc(var(--spacing) * 4)}.pe-8{padding-inline-end:calc(var(--spacing) * 8)}.pe-12{padding-inline-end:calc(var(--spacing) * 12)}.pt-3{padding-top:calc(var(--spacing) * 3)}.pt-\[10vh\]{padding-top:10vh}.pb-1{padding-bottom:var(--spacing)}.pb-2{padding-bottom:calc(var(--spacing) * 2)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.pb-4{padding-bottom:calc(var(--spacing) * 4)}.text-center{text-align:center}.text-end{text-align:end}.text-justify{text-align:justify}.text-left{text-align:left}.text-start{text-align:start}.align-\[-2px\]{vertical-align:-2px}.align-top{vertical-align:top}.font-mono{font-family:var(--tea-font-mono)}.text-label{font-size:.6875rem;line-height:var(--tw-leading,.875rem);letter-spacing:var(--tw-tracking,.08em);font-weight:var(--tw-font-weight,600)}.text-display{font-size:2rem;line-height:var(--tw-leading,2.5rem);letter-spacing:var(--tw-tracking,-.02em)}.text-hero{font-size:3rem;line-height:var(--tw-leading,1.1);letter-spacing:var(--tw-tracking,-.03em)}.text-section{font-size:1.5rem;line-height:var(--tw-leading,2rem);letter-spacing:var(--tw-tracking,-.01em)}.text-body{font-size:1rem;line-height:var(--tw-leading,1.625rem)}.text-lead{font-size:1.125rem;line-height:var(--tw-leading,1.75rem)}.text-micro{font-size:.75rem;line-height:var(--tw-leading,1rem)}.text-mono{font-size:.8125rem;line-height:var(--tw-leading,1.25rem)}.text-title{font-size:1.25rem;line-height:var(--tw-leading,1.75rem)}.text-ui{font-size:var(--tea-text-ui);font-size:.875rem;line-height:1.25rem;line-height:var(--tw-leading,1.25rem)}.text-\[9px\]{font-size:9px}.leading-none{--tw-leading:1;line-height:1}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-bold{--tw-font-weight:700;font-weight:700}.font-medium{--tw-font-weight:500;font-weight:500}.font-normal{--tw-font-weight:400;font-weight:400}.font-semibold{--tw-font-weight:600;font-weight:600}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.text-balance{text-wrap:balance}.text-wrap{text-wrap:wrap}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-accent{color:var(--tea-accent)}.text-accent-fg{color:var(--tea-accent-fg)}.text-caution{color:var(--tea-caution)}.text-caution-fg{color:var(--tea-caution-fg)}.text-critical{color:var(--tea-critical)}.text-critical-fg{color:var(--tea-critical-fg)}.text-current{color:currentColor}.text-destructive-fg{color:var(--tea-destructive-fg)}.text-fg{color:var(--tea-fg)}.text-fg-inverse{color:var(--tea-fg-inverse)}.text-fg-muted{color:var(--tea-fg-muted)}.text-fg-subtle{color:var(--tea-fg-subtle)}.text-info{color:var(--tea-info)}.text-info-fg{color:var(--tea-info-fg)}.text-neutral-fg{color:var(--tea-neutral-fg)}.text-positive{color:var(--tea-positive)}.text-positive-fg{color:var(--tea-positive-fg)}.text-primary{color:var(--tea-primary)}.text-primary-fg{color:var(--tea-primary-fg)}.uppercase{text-transform:uppercase}.italic{font-style:italic}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.no-underline{text-decoration-line:none}.underline{text-decoration-line:underline}.underline-offset-4{text-underline-offset:4px}.accent-fg{accent-color:var(--tea-fg)}.opacity-50{opacity:.5}.shadow-modal{--tw-shadow:var(--tea-shadow-modal);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-overlay{--tw-shadow:var(--tea-shadow-overlay);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--tea-ease-standard));transition-duration:var(--tw-duration,.18s)}.transition-\[background-color\,border-color\,box-shadow\]{transition-property:background-color,border-color,box-shadow;transition-timing-function:var(--tw-ease,var(--tea-ease-standard));transition-duration:var(--tw-duration,.18s)}.transition-\[background-color\,border-color\,color\,box-shadow\,transform\]{transition-property:background-color,border-color,color,box-shadow,transform;transition-timing-function:var(--tw-ease,var(--tea-ease-standard));transition-duration:var(--tw-duration,.18s)}.transition-\[background-color\,border-color\,color\,box-shadow\]{transition-property:background-color,border-color,color,box-shadow;transition-timing-function:var(--tw-ease,var(--tea-ease-standard));transition-duration:var(--tw-duration,.18s)}.transition-\[background-color\,border-color\]{transition-property:background-color,border-color;transition-timing-function:var(--tw-ease,var(--tea-ease-standard));transition-duration:var(--tw-duration,.18s)}.transition-\[width\]{transition-property:width;transition-timing-function:var(--tw-ease,var(--tea-ease-standard));transition-duration:var(--tw-duration,.18s)}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--tea-ease-standard));transition-duration:var(--tw-duration,.18s)}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--tea-ease-standard));transition-duration:var(--tw-duration,.18s)}.duration-\[120ms\]{--tw-duration:.12s;transition-duration:.12s}.ease-standard{--tw-ease:cubic-bezier(.2, 0, 0, 1);transition-timing-function:cubic-bezier(.2,0,0,1)}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}.\[--tea-control-icon\:0\.875rem\]{--tea-control-icon:.875rem}.\[--tea-control-icon\:1\.25rem\]{--tea-control-icon:1.25rem}.\[--tea-control-icon\:1rem\]{--tea-control-icon:1rem}.paused{animation-play-state:paused}.running{animation-play-state:running}.group-open\:rotate-45:is(:where(.group):is([open],:popover-open,:open) *){rotate:45deg}.group-focus-visible\:ring-2:is(:where(.group):focus-visible *){--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.group-focus-visible\:ring-ring:is(:where(.group):focus-visible *){--tw-ring-color:var(--tea-ring)}.group-data-\[state\=checked\]\:scale-100:is(:where(.group)[data-state=checked] *){--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x) var(--tw-scale-y)}.group-data-\[state\=checked\]\:border-primary:is(:where(.group)[data-state=checked] *){border-color:var(--tea-primary)}.group-data-\[state\=checked\]\:bg-primary:is(:where(.group)[data-state=checked] *){background-color:var(--tea-primary)}.group-data-\[state\=checked\]\:font-medium:is(:where(.group)[data-state=checked] *){--tw-font-weight:500;font-weight:500}.group-data-\[state\=complete\]\:border-positive:is(:where(.group)[data-state=complete] *){border-color:var(--tea-positive)}.group-data-\[state\=complete\]\:bg-positive-subtle:is(:where(.group)[data-state=complete] *){background-color:var(--tea-positive-subtle)}.group-data-\[state\=complete\]\:text-positive:is(:where(.group)[data-state=complete] *){color:var(--tea-positive)}.group-data-\[state\=current\]\:border-primary:is(:where(.group)[data-state=current] *){border-color:var(--tea-primary)}.group-data-\[state\=current\]\:text-primary:is(:where(.group)[data-state=current] *){color:var(--tea-primary)}.group-data-\[state\=open\]\:rotate-180:is(:where(.group)[data-state=open] *){rotate:180deg}.marker\:text-fg-subtle ::marker{color:var(--tea-fg-subtle)}.marker\:text-fg-subtle::marker{color:var(--tea-fg-subtle)}.marker\:text-fg-subtle ::-webkit-details-marker{color:var(--tea-fg-subtle)}.marker\:text-fg-subtle::-webkit-details-marker{color:var(--tea-fg-subtle)}.marker\:content-none ::marker{--tw-content:none;content:none}.marker\:content-none::marker{--tw-content:none;content:none}.marker\:content-none ::-webkit-details-marker{--tw-content:none;content:none}.marker\:content-none::-webkit-details-marker{--tw-content:none;content:none}.file\:me-3::file-selector-button{margin-inline-end:calc(var(--spacing) * 3)}.file\:rounded-none::file-selector-button{border-radius:0}.file\:border-0::file-selector-button{border-style:var(--tw-border-style);border-width:0}.file\:bg-transparent::file-selector-button{background-color:#0000}.file\:text-ui::file-selector-button{font-size:var(--tea-text-ui);font-size:.875rem;line-height:1.25rem;line-height:var(--tw-leading,1.25rem)}.file\:font-medium::file-selector-button{--tw-font-weight:500;font-weight:500}.placeholder\:text-fg-subtle::placeholder{color:var(--tea-fg-subtle)}.before\:absolute:before{content:var(--tw-content);position:absolute}.before\:inset-y-0:before{content:var(--tw-content);inset-block:0}.before\:start-0:before{content:var(--tw-content);inset-inline-start:calc(var(--spacing) * 0)}.before\:w-0\.5:before{content:var(--tw-content);width:calc(var(--spacing) * .5)}.before\:bg-caution:before{content:var(--tw-content);background-color:var(--tea-caution)}.before\:bg-critical:before{content:var(--tw-content);background-color:var(--tea-critical)}.before\:bg-fg-muted:before{content:var(--tw-content);background-color:var(--tea-fg-muted)}.before\:bg-info:before{content:var(--tw-content);background-color:var(--tea-info)}.before\:bg-positive:before{content:var(--tw-content);background-color:var(--tea-positive)}.last\:border-b-0:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.read-only\:cursor-default:read-only{cursor:default}.read-only\:bg-surface-2:read-only{background-color:var(--tea-surface-2)}.read-only\:text-fg-muted:read-only{color:var(--tea-fg-muted)}.focus-within\:border-ring-strong:focus-within{border-color:var(--tea-ring-strong)}.focus-within\:ring-2:focus-within{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus-within\:ring-ring:focus-within{--tw-ring-color:var(--tea-ring)}@media (hover:hover){.hover\:border-line-strong:hover{border-color:var(--tea-line-strong)}.hover\:border-primary-hover:hover{border-color:var(--tea-primary-hover)}.hover\:bg-line:hover{background-color:var(--tea-line)}.hover\:bg-primary-hover:hover{background-color:var(--tea-primary-hover)}.hover\:bg-primary-subtle\/80:hover{background-color:var(--tea-primary-subtle)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-primary-subtle\/80:hover{background-color:color-mix(in oklab, var(--tea-primary-subtle) 80%, transparent)}}.hover\:bg-surface-2:hover{background-color:var(--tea-surface-2)}.hover\:bg-surface-3:hover{background-color:var(--tea-surface-3)}.hover\:text-fg:hover{color:var(--tea-fg)}.hover\:text-primary:hover{color:var(--tea-primary)}.hover\:no-underline:hover{text-decoration-line:none}.hover\:underline:hover{text-decoration-line:underline}.hover\:brightness-110:hover{--tw-brightness:brightness(110%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}}.focus\:not-sr-only:focus{clip-path:none;white-space:normal;width:auto;height:auto;margin:0;padding:0;position:static;overflow:visible}.focus\:fixed:focus{position:fixed}.focus\:start-3:focus{inset-inline-start:calc(var(--spacing) * 3)}.focus\:top-3:focus{top:calc(var(--spacing) * 3)}.focus\:border:focus{border-style:var(--tw-border-style);border-width:1px}.focus\:border-primary:focus{border-color:var(--tea-primary)}.focus\:bg-primary:focus{background-color:var(--tea-primary)}.focus\:px-3:focus{padding-inline:calc(var(--spacing) * 3)}.focus\:py-2:focus{padding-block:calc(var(--spacing) * 2)}.focus\:text-ui:focus{font-size:var(--tea-text-ui);font-size:.875rem;line-height:1.25rem;line-height:var(--tw-leading,1.25rem)}.focus\:font-medium:focus{--tw-font-weight:500;font-weight:500}.focus\:text-primary-fg:focus{color:var(--tea-primary-fg)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.focus-visible\:border-ring-strong:focus-visible{border-color:var(--tea-ring-strong)}.focus-visible\:border-transparent:focus-visible{border-color:#0000}.focus-visible\:bg-accent-subtle:focus-visible{background-color:var(--tea-accent-subtle)}.focus-visible\:ring-0:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus-visible\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus-visible\:ring-ring:focus-visible{--tw-ring-color:var(--tea-ring)}.focus-visible\:outline-none:focus-visible{--tw-outline-style:none;outline-style:none}.active\:translate-y-px:active{--tw-translate-y:1px;translate:var(--tw-translate-x) var(--tw-translate-y)}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:border-line:disabled{border-color:var(--tea-line)}.disabled\:bg-surface-2:disabled{background-color:var(--tea-surface-2)}.disabled\:text-fg-subtle:disabled{color:var(--tea-fg-subtle)}.disabled\:opacity-50:disabled{opacity:.5}.has-\[svg\]\:size-\[length\:var\(--tea-control-icon\)\]:has(:is(svg)){width:var(--tea-control-icon);height:var(--tea-control-icon)}.aria-disabled\:pointer-events-none[aria-disabled=true]{pointer-events:none}.aria-disabled\:opacity-50[aria-disabled=true]{opacity:.5}.aria-invalid\:border-critical[aria-invalid=true]{border-color:var(--tea-critical)}.aria-invalid\:bg-critical-subtle[aria-invalid=true]{background-color:var(--tea-critical-subtle)}.aria-\[current\=page\]\:border-primary[aria-current=page]{border-color:var(--tea-primary)}.aria-\[current\=page\]\:text-primary[aria-current=page]{color:var(--tea-primary)}.aria-\[invalid\=true\]\:border-critical[aria-invalid=true]{border-color:var(--tea-critical)}.aria-\[invalid\=true\]\:ring-0[aria-invalid=true]{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.aria-\[invalid\=true\]\:ring-critical[aria-invalid=true]{--tw-ring-color:var(--tea-critical)}.data-disabled\:pointer-events-none[data-disabled]{pointer-events:none}.data-disabled\:opacity-50[data-disabled]{opacity:.5}.data-\[active\]\:bg-accent-subtle[data-active]{background-color:var(--tea-accent-subtle)}.data-\[active\]\:text-fg[data-active]{color:var(--tea-fg)}.data-\[active\=true\]\:bg-accent-subtle[data-active=true]{background-color:var(--tea-accent-subtle)}.data-\[disabled\]\:opacity-50[data-disabled]{opacity:.5}.data-\[disabled\=true\]\:cursor-not-allowed[data-disabled=true]{cursor:not-allowed}.data-\[disabled\=true\]\:opacity-50[data-disabled=true]{opacity:.5}.data-\[highlighted\]\:bg-accent-subtle[data-highlighted]{background-color:var(--tea-accent-subtle)}.data-\[highlighted\]\:bg-critical-subtle[data-highlighted]{background-color:var(--tea-critical-subtle)}.data-\[highlighted\]\:text-critical[data-highlighted]{color:var(--tea-critical)}.data-\[highlighted\]\:text-fg[data-highlighted]{color:var(--tea-fg)}.data-\[hover\]\:border-line-strong[data-hover]{border-color:var(--tea-line-strong)}.data-\[hover\]\:bg-surface-3[data-hover]{background-color:var(--tea-surface-3)}.data-\[orientation\=horizontal\]\:h-1[data-orientation=horizontal]{height:var(--spacing)}.data-\[orientation\=horizontal\]\:h-4[data-orientation=horizontal]{height:calc(var(--spacing) * 4)}.data-\[orientation\=horizontal\]\:h-full[data-orientation=horizontal]{height:100%}.data-\[orientation\=horizontal\]\:w-full[data-orientation=horizontal]{width:100%}.data-\[orientation\=vertical\]\:h-full[data-orientation=vertical]{height:100%}.data-\[orientation\=vertical\]\:w-1[data-orientation=vertical]{width:var(--spacing)}.data-\[orientation\=vertical\]\:w-4[data-orientation=vertical]{width:calc(var(--spacing) * 4)}.data-\[orientation\=vertical\]\:w-full[data-orientation=vertical]{width:100%}.data-\[orientation\=vertical\]\:flex-col[data-orientation=vertical]{flex-direction:column}.data-\[orientation\=vertical\]\:border-s-2[data-orientation=vertical]{border-inline-start-style:var(--tw-border-style);border-inline-start-width:2px}.data-\[orientation\=vertical\]\:border-e[data-orientation=vertical]{border-inline-end-style:var(--tw-border-style);border-inline-end-width:1px}.data-\[orientation\=vertical\]\:border-e-0[data-orientation=vertical]{border-inline-end-style:var(--tw-border-style);border-inline-end-width:0}.data-\[orientation\=vertical\]\:border-b-0[data-orientation=vertical]{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.data-\[orientation\=vertical\]\:text-start[data-orientation=vertical]{text-align:start}.data-\[placeholder\]\:text-fg-muted[data-placeholder]{color:var(--tea-fg-muted)}.data-\[state\=active\]\:border-primary[data-state=active]{border-color:var(--tea-primary)}.data-\[state\=active\]\:font-medium[data-state=active]{--tw-font-weight:500;font-weight:500}.data-\[state\=active\]\:text-primary[data-state=active]{color:var(--tea-primary)}.data-\[state\=checked\]\:translate-x-3[data-state=checked]{--tw-translate-x:calc(var(--spacing) * 3);translate:var(--tw-translate-x) var(--tw-translate-y)}.data-\[state\=checked\]\:border-primary[data-state=checked]{border-color:var(--tea-primary)}.data-\[state\=checked\]\:bg-primary[data-state=checked]{background-color:var(--tea-primary)}.data-\[state\=checked\]\:bg-primary-fg[data-state=checked]{background-color:var(--tea-primary-fg)}.data-\[state\=checked\]\:text-primary[data-state=checked]{color:var(--tea-primary)}.data-\[state\=closed\]\:animate-out[data-state=closed]{animation:exit var(--tw-animation-duration,var(--tw-duration,.15s))var(--tw-ease,ease)var(--tw-animation-delay,0s)var(--tw-animation-iteration-count,1)var(--tw-animation-direction,normal)var(--tw-animation-fill-mode,none)}.data-\[state\=closed\]\:fade-out-0[data-state=closed]{--tw-exit-opacity:0}.data-\[state\=closed\]\:zoom-out-95[data-state=closed]{--tw-exit-scale:.95}.data-\[state\=closed\]\:slide-out-to-bottom[data-state=closed]{--tw-exit-translate-y:100%}.data-\[state\=closed\]\:slide-out-to-left[data-state=closed]{--tw-exit-translate-x:-100%}.data-\[state\=closed\]\:slide-out-to-right[data-state=closed]{--tw-exit-translate-x:100%}.data-\[state\=closed\]\:slide-out-to-right-full[data-state=closed]{--tw-exit-translate-x:calc(1*100%)}.data-\[state\=closed\]\:slide-out-to-top[data-state=closed]{--tw-exit-translate-y:-100%}.data-\[state\=delayed-open\]\:animate-in[data-state=delayed-open]{animation:enter var(--tw-animation-duration,var(--tw-duration,.15s))var(--tw-ease,ease)var(--tw-animation-delay,0s)var(--tw-animation-iteration-count,1)var(--tw-animation-direction,normal)var(--tw-animation-fill-mode,none)}.data-\[state\=delayed-open\]\:fade-in-0[data-state=delayed-open]{--tw-enter-opacity:0}.data-\[state\=delayed-open\]\:zoom-in-95[data-state=delayed-open]{--tw-enter-scale:.95}.data-\[state\=indeterminate\]\:border-primary[data-state=indeterminate]{border-color:var(--tea-primary)}.data-\[state\=indeterminate\]\:bg-primary[data-state=indeterminate]{background-color:var(--tea-primary)}.data-\[state\=on\]\:border-primary[data-state=on]{border-color:var(--tea-primary)}.data-\[state\=on\]\:bg-primary-subtle[data-state=on]{background-color:var(--tea-primary-subtle)}.data-\[state\=on\]\:text-primary[data-state=on]{color:var(--tea-primary)}.data-\[state\=open\]\:animate-in[data-state=open]{animation:enter var(--tw-animation-duration,var(--tw-duration,.15s))var(--tw-ease,ease)var(--tw-animation-delay,0s)var(--tw-animation-iteration-count,1)var(--tw-animation-direction,normal)var(--tw-animation-fill-mode,none)}.data-\[state\=open\]\:fade-in-0[data-state=open]{--tw-enter-opacity:0}.data-\[state\=open\]\:zoom-in-95[data-state=open]{--tw-enter-scale:.95}.data-\[state\=open\]\:slide-in-from-bottom[data-state=open]{--tw-enter-translate-y:100%}.data-\[state\=open\]\:slide-in-from-left[data-state=open]{--tw-enter-translate-x:-100%}.data-\[state\=open\]\:slide-in-from-right[data-state=open]{--tw-enter-translate-x:100%}.data-\[state\=open\]\:slide-in-from-right-full[data-state=open]{--tw-enter-translate-x:calc(1*100%)}.data-\[state\=open\]\:slide-in-from-top[data-state=open]{--tw-enter-translate-y:-100%}.data-\[state\=unchecked\]\:translate-x-0[data-state=unchecked],.data-\[swipe\=cancel\]\:translate-x-0[data-swipe=cancel]{--tw-translate-x:0px;translate:var(--tw-translate-x) var(--tw-translate-y)}.data-\[swipe\=end\]\:animate-out[data-swipe=end]{animation:exit var(--tw-animation-duration,var(--tw-duration,.15s))var(--tw-ease,ease)var(--tw-animation-delay,0s)var(--tw-animation-iteration-count,1)var(--tw-animation-direction,normal)var(--tw-animation-fill-mode,none)}.data-\[swipe\=move\]\:translate-x-\[var\(--radix-toast-swipe-move-x\)\][data-swipe=move]{--tw-translate-x:var(--radix-toast-swipe-move-x);translate:var(--tw-translate-x) var(--tw-translate-y)}.data-\[swipe\=move\]\:transition-none[data-swipe=move]{transition-property:none}@media (min-width:40rem){.sm\:flex{display:flex}.sm\:inline{display:inline}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:flex-row{flex-direction:row}.sm\:justify-end{justify-content:flex-end}}@media (min-width:48rem){.md\:flex{display:flex}.md\:hidden{display:none}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:grid-cols-\[2fr_repeat\(auto-fit\,minmax\(9rem\,1fr\)\)\]{grid-template-columns:2fr repeat(auto-fit,minmax(9rem,1fr))}.md\:flex-row{flex-direction:row}.md\:items-center{align-items:center}.md\:justify-between{justify-content:space-between}.md\:p-6{padding:calc(var(--spacing) * 6)}.md\:px-6{padding-inline:calc(var(--spacing) * 6)}.md\:py-6{padding-block:calc(var(--spacing) * 6)}}@media (min-width:64rem){.lg\:block{display:block}.lg\:flex{display:flex}.lg\:hidden{display:none}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (pointer:coarse){.pointer-coarse\:text-body{font-size:1rem;line-height:var(--tw-leading,1.625rem)}}.\[\&_svg\]\:size-4 svg{width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.\[\&_svg\]\:shrink-0 svg{flex-shrink:0}.\[\&_svg\]\:text-fg-muted svg{color:var(--tea-fg-muted)}.\[\&\>\*\+\*\]\:-ms-px>*+*{margin-inline-start:-1px}.\[\&\>\*\+\*\]\:-mt-px>*+*{margin-top:-1px}.\[\&\>svg\]\:mt-0\.5>svg{margin-top:calc(var(--spacing) * .5)}.\[\&\>svg\]\:shrink-0>svg{flex-shrink:0}}@property --tw-animation-delay{syntax:"*";inherits:false;initial-value:0s}@property --tw-animation-direction{syntax:"*";inherits:false;initial-value:normal}@property --tw-animation-duration{syntax:"*";inherits:false}@property --tw-animation-fill-mode{syntax:"*";inherits:false;initial-value:none}@property --tw-animation-iteration-count{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-translate-y{syntax:"*";inherits:false;initial-value:0}:root,[data-theme=tea]{color-scheme:dark;--tea-canvas:#111318;--tea-surface:#171a21;--tea-surface-2:#1e222b;--tea-surface-3:#232733;--tea-overlay:#000000c7;--tea-fg:#e8e6e0;--tea-fg-muted:#a3a096;--tea-fg-subtle:#83807a;--tea-fg-inverse:#111318;--tea-line:#343a46;--tea-line-strong:#4e5666;--tea-brand:#f2c012;--tea-primary:#f2c012;--tea-primary-fg:#111318;--tea-primary-hover:#f7c72e;--tea-primary-subtle:#f2c0121f;--tea-primary-border:#f2c01266;--tea-accent:#f2c012;--tea-accent-fg:#111318;--tea-accent-subtle:#f2c0121a;--tea-ring:#f2c012;--tea-ring-strong:#ffd84d;--tea-positive:#4ade80;--tea-positive-fg:#0b1f14;--tea-positive-subtle:#4ade801f;--tea-positive-border:#4ade8066;--tea-info:#60a5fa;--tea-info-fg:#0a1729;--tea-info-subtle:#60a5fa1f;--tea-info-border:#60a5fa66;--tea-caution:#fb923c;--tea-caution-fg:#241104;--tea-caution-subtle:#fb923c1f;--tea-caution-border:#fb923c66;--tea-critical:#ff6b5e;--tea-critical-fg:#240705;--tea-critical-subtle:#ff6b5e1f;--tea-critical-border:#ff6b5e66;--tea-neutral:#a3a096;--tea-neutral-fg:#111318;--tea-neutral-subtle:#a3a0961f;--tea-neutral-border:#a3a09666;--tea-destructive:var(--tea-critical);--tea-destructive-fg:var(--tea-critical-fg);--tea-destructive-subtle:var(--tea-critical-subtle);--tea-destructive-border:var(--tea-critical-border);--tea-density:default;--tea-font-sans:"Jost Variable", "Jost", "Futura", ui-sans-serif, system-ui, sans-serif;--tea-font-mono:"JetBrains Mono", ui-monospace, "SFMono-Regular", monospace;--tea-font-display:"Lilita One", "Jost Variable", ui-sans-serif, system-ui, sans-serif;--tea-shadow-raised:0 1px 3px #0000004d;--tea-shadow-overlay:6px 6px 0 0 #00000080;--tea-shadow-modal:8px 8px 0 0 #00000080;--tea-shadow-inset:inset 0 1px 0 0 #ffffff0a}[data-theme=hsm]{color-scheme:dark;--tea-canvas:#0e1116;--tea-surface:#161a21;--tea-surface-2:#1c212a;--tea-surface-3:#232935;--tea-overlay:#000c;--tea-fg:#e9eaee;--tea-fg-muted:#a1a8b4;--tea-fg-subtle:#808894;--tea-fg-inverse:#0e1116;--tea-line:#2f3743;--tea-line-strong:#49525f;--tea-brand:#e0332e;--tea-primary:#f2c012;--tea-primary-fg:#0e1116;--tea-primary-hover:#f7c72e;--tea-primary-subtle:#f2c0121f;--tea-primary-border:#f2c01266;--tea-accent:#f2c012;--tea-accent-fg:#0e1116;--tea-accent-subtle:#f2c0121a;--tea-ring:#f2c012;--tea-ring-strong:#ffd84d;--tea-positive:#4ade80;--tea-positive-fg:#0b1f14;--tea-positive-subtle:#4ade801f;--tea-positive-border:#4ade8066;--tea-info:#60a5fa;--tea-info-fg:#0a1729;--tea-info-subtle:#60a5fa1f;--tea-info-border:#60a5fa66;--tea-caution:#fb923c;--tea-caution-fg:#241104;--tea-caution-subtle:#fb923c1f;--tea-caution-border:#fb923c66;--tea-critical:#f87171;--tea-critical-fg:#240705;--tea-critical-subtle:#f871711f;--tea-critical-border:#f8717166;--tea-neutral:#a1a8b4;--tea-neutral-fg:#0e1116;--tea-neutral-subtle:#a1a8b41f;--tea-neutral-border:#a1a8b466;--tea-destructive:var(--tea-critical);--tea-destructive-fg:var(--tea-critical-fg);--tea-destructive-subtle:var(--tea-critical-subtle);--tea-destructive-border:var(--tea-critical-border);--tea-density:default;--tea-font-sans:"Jost Variable", "Jost", "Futura", ui-sans-serif, system-ui, sans-serif;--tea-font-mono:"JetBrains Mono", ui-monospace, "SFMono-Regular", monospace;--tea-font-display:"Lilita One", "Jost Variable", ui-sans-serif, system-ui, sans-serif;--tea-shadow-raised:0 1px 3px #00000059;--tea-shadow-overlay:6px 6px 0 0 #0000008c;--tea-shadow-modal:8px 8px 0 0 #0000008c;--tea-shadow-inset:inset 0 1px 0 0 #ffffff0a}[data-theme=lutea]{color-scheme:dark;--tea-canvas:#121317;--tea-surface:#1a1b1f;--tea-surface-2:#212329;--tea-surface-3:#2a2c33;--tea-overlay:#000000c7;--tea-fg:#ede9e1;--tea-fg-muted:#aba69b;--tea-fg-subtle:#8a857b;--tea-fg-inverse:#121317;--tea-line:#383b44;--tea-line-strong:#525663;--tea-brand:#f2c012;--tea-primary:#f2c012;--tea-primary-fg:#121317;--tea-primary-hover:#f7c72e;--tea-primary-subtle:#f2c0121f;--tea-primary-border:#f2c01266;--tea-accent:#f2c012;--tea-accent-fg:#121317;--tea-accent-subtle:#f2c0121a;--tea-ring:#f2c012;--tea-ring-strong:#ffd84d;--tea-positive:#4fc97e;--tea-positive-fg:#0a1f13;--tea-positive-subtle:#4fc97e1f;--tea-positive-border:#4fc97e66;--tea-info:#5b8def;--tea-info-fg:#08142b;--tea-info-subtle:#5b8def1f;--tea-info-border:#5b8def66;--tea-caution:#f2921e;--tea-caution-fg:#1f1204;--tea-caution-subtle:#f2921e1f;--tea-caution-border:#f2921e66;--tea-critical:#f0726a;--tea-critical-fg:#230604;--tea-critical-subtle:#f0726a1f;--tea-critical-border:#f0726a66;--tea-neutral:#aba69b;--tea-neutral-fg:#121317;--tea-neutral-subtle:#aba69b1f;--tea-neutral-border:#aba69b66;--tea-destructive:var(--tea-critical);--tea-destructive-fg:var(--tea-critical-fg);--tea-destructive-subtle:var(--tea-critical-subtle);--tea-destructive-border:var(--tea-critical-border);--tea-density:compact;--tea-font-sans:"Jost Variable", "Jost", "Futura", ui-sans-serif, system-ui, sans-serif;--tea-font-mono:"JetBrains Mono", ui-monospace, "SFMono-Regular", monospace;--tea-font-display:"Lilita One", "Jost Variable", ui-sans-serif, system-ui, sans-serif;--tea-shadow-raised:0 1px 3px #00000047;--tea-shadow-overlay:6px 6px 0 0 #00000080;--tea-shadow-modal:8px 8px 0 0 #00000080;--tea-shadow-inset:inset 0 1px 0 0 #ffffff0d}:root,[data-density=default]{--tea-control-h:2rem;--tea-control-px:.75rem;--tea-control-icon:1rem;--tea-row-h:2.25rem;--tea-cell-py:.5rem;--tea-cell-px:.75rem;--tea-gap:.75rem;--tea-section-gap:1.5rem;--tea-field-gap:1.25rem;--tea-pad-card:1.25rem;--tea-text-ui:.875rem;--tea-text-label:.6875rem;--tea-touch-min:2.75rem}[data-density=compact]{--tea-control-h:1.75rem;--tea-control-px:.625rem;--tea-control-icon:.875rem;--tea-row-h:2rem;--tea-cell-py:.375rem;--tea-cell-px:.625rem;--tea-gap:.5rem;--tea-section-gap:1.25rem;--tea-field-gap:1rem;--tea-pad-card:1rem;--tea-text-ui:.8125rem;--tea-text-label:.6875rem;--tea-touch-min:2.75rem}[data-density=comfortable]{--tea-control-h:2.25rem;--tea-control-px:.875rem;--tea-control-icon:1.125rem;--tea-row-h:2.75rem;--tea-cell-py:.75rem;--tea-cell-px:.875rem;--tea-gap:1rem;--tea-section-gap:2rem;--tea-field-gap:1.5rem;--tea-pad-card:1.5rem;--tea-text-ui:.9375rem;--tea-text-label:.75rem;--tea-touch-min:2.75rem}@keyframes tea-pulse-essential{0%{opacity:.35}50%{opacity:1}to{opacity:.35}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-content{syntax:"*";inherits:false;initial-value:""}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse{50%{opacity:.5}}@keyframes enter{0%{opacity:var(--tw-enter-opacity,1);transform:translate3d(var(--tw-enter-translate-x,0),var(--tw-enter-translate-y,0),0)scale3d(var(--tw-enter-scale,1),var(--tw-enter-scale,1),var(--tw-enter-scale,1))rotate(var(--tw-enter-rotate,0));filter:blur(var(--tw-enter-blur,0))}}@keyframes exit{to{opacity:var(--tw-exit-opacity,1);transform:translate3d(var(--tw-exit-translate-x,0),var(--tw-exit-translate-y,0),0)scale3d(var(--tw-exit-scale,1),var(--tw-exit-scale,1),var(--tw-exit-scale,1))rotate(var(--tw-exit-rotate,0));filter:blur(var(--tw-exit-blur,0))}}
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tea-ui/admin",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "The admin layer: shell, navigation, data display and product states.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"README.md",
|
|
11
|
+
"LICENSE"
|
|
12
|
+
],
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.js",
|
|
17
|
+
"default": "./dist/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./package.json": "./package.json",
|
|
20
|
+
"./styles.css": "./dist/styles.css"
|
|
21
|
+
},
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"module": "./dist/index.js",
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsup && node ../../scripts/fix-esm-extensions.mjs admin && node ../../scripts/sync-styles.mjs",
|
|
29
|
+
"prepublishOnly": "node ../../scripts/prepublish-guard.mjs"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@tea-ui/core": "^0.1.0",
|
|
33
|
+
"@tea-ui/icons": "^0.1.0",
|
|
34
|
+
"@tea-ui/tokens": "^0.1.0",
|
|
35
|
+
"@tea-ui/ux-standards": "^0.1.0",
|
|
36
|
+
"@tea-ui/utils": "^0.1.0",
|
|
37
|
+
"radix-ui": "^1.4.3"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"react": ">=18.2.0 <20.0.0",
|
|
41
|
+
"react-dom": ">=18.2.0 <20.0.0"
|
|
42
|
+
},
|
|
43
|
+
"keywords": [
|
|
44
|
+
"react",
|
|
45
|
+
"admin",
|
|
46
|
+
"dashboard",
|
|
47
|
+
"components",
|
|
48
|
+
"data-display",
|
|
49
|
+
"tea-ui",
|
|
50
|
+
"design-system"
|
|
51
|
+
],
|
|
52
|
+
"author": "landnevermore",
|
|
53
|
+
"repository": {
|
|
54
|
+
"type": "git",
|
|
55
|
+
"url": "git+https://github.com/landnevermore/TEAui.git",
|
|
56
|
+
"directory": "packages/admin"
|
|
57
|
+
},
|
|
58
|
+
"homepage": "https://landnevermore.github.io/TEAui/docs/",
|
|
59
|
+
"bugs": {
|
|
60
|
+
"url": "https://github.com/landnevermore/TEAui/issues"
|
|
61
|
+
}
|
|
62
|
+
}
|