@dirstack/ui 1.0.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 +67 -0
- package/dist/components/animated-number.d.ts +18 -0
- package/dist/components/animated-number.d.ts.map +1 -0
- package/dist/components/animated-number.js +29 -0
- package/dist/components/animated-number.js.map +1 -0
- package/dist/components/avatar.d.ts +8 -0
- package/dist/components/avatar.d.ts.map +1 -0
- package/dist/components/avatar.js +27 -0
- package/dist/components/avatar.js.map +1 -0
- package/dist/components/badge.d.ts +58 -0
- package/dist/components/badge.d.ts.map +1 -0
- package/dist/components/badge.js +51 -0
- package/dist/components/badge.js.map +1 -0
- package/dist/components/button.d.ts +63 -0
- package/dist/components/button.d.ts.map +1 -0
- package/dist/components/button.js +66 -0
- package/dist/components/button.js.map +1 -0
- package/dist/components/callout.d.ts +41 -0
- package/dist/components/callout.d.ts.map +1 -0
- package/dist/components/callout.js +49 -0
- package/dist/components/callout.js.map +1 -0
- package/dist/components/card.d.ts +61 -0
- package/dist/components/card.d.ts.map +1 -0
- package/dist/components/card.js +67 -0
- package/dist/components/card.js.map +1 -0
- package/dist/components/chart.d.ts +32 -0
- package/dist/components/chart.d.ts.map +1 -0
- package/dist/components/chart.js +121 -0
- package/dist/components/chart.js.map +1 -0
- package/dist/components/copy-button.d.ts +22 -0
- package/dist/components/copy-button.d.ts.map +1 -0
- package/dist/components/copy-button.js +50 -0
- package/dist/components/copy-button.js.map +1 -0
- package/dist/components/copy-input.d.ts +13 -0
- package/dist/components/copy-input.d.ts.map +1 -0
- package/dist/components/copy-input.js +28 -0
- package/dist/components/copy-input.js.map +1 -0
- package/dist/components/detail-row.d.ts +93 -0
- package/dist/components/detail-row.d.ts.map +1 -0
- package/dist/components/detail-row.js +69 -0
- package/dist/components/detail-row.js.map +1 -0
- package/dist/components/dialog.d.ts +173 -0
- package/dist/components/dialog.d.ts.map +1 -0
- package/dist/components/dialog.js +139 -0
- package/dist/components/dialog.js.map +1 -0
- package/dist/components/empty-state.d.ts +96 -0
- package/dist/components/empty-state.d.ts.map +1 -0
- package/dist/components/empty-state.js +69 -0
- package/dist/components/empty-state.js.map +1 -0
- package/dist/components/entity-avatar.d.ts +77 -0
- package/dist/components/entity-avatar.d.ts.map +1 -0
- package/dist/components/entity-avatar.js +48 -0
- package/dist/components/entity-avatar.js.map +1 -0
- package/dist/components/form-row.d.ts +35 -0
- package/dist/components/form-row.d.ts.map +1 -0
- package/dist/components/form-row.js +46 -0
- package/dist/components/form-row.js.map +1 -0
- package/dist/components/form-section.d.ts +24 -0
- package/dist/components/form-section.d.ts.map +1 -0
- package/dist/components/form-section.js +39 -0
- package/dist/components/form-section.js.map +1 -0
- package/dist/components/form.d.ts +28 -0
- package/dist/components/form.d.ts.map +1 -0
- package/dist/components/form.js +88 -0
- package/dist/components/form.js.map +1 -0
- package/dist/components/heading.d.ts +44 -0
- package/dist/components/heading.d.ts.map +1 -0
- package/dist/components/heading.js +45 -0
- package/dist/components/heading.js.map +1 -0
- package/dist/components/input.d.ts +55 -0
- package/dist/components/input.d.ts.map +1 -0
- package/dist/components/input.js +33 -0
- package/dist/components/input.js.map +1 -0
- package/dist/components/kbd.d.ts +28 -0
- package/dist/components/kbd.d.ts.map +1 -0
- package/dist/components/kbd.js +31 -0
- package/dist/components/kbd.js.map +1 -0
- package/dist/components/label.d.ts +21 -0
- package/dist/components/label.d.ts.map +1 -0
- package/dist/components/label.js +21 -0
- package/dist/components/label.js.map +1 -0
- package/dist/components/menu.d.ts +22 -0
- package/dist/components/menu.d.ts.map +1 -0
- package/dist/components/menu.js +73 -0
- package/dist/components/menu.js.map +1 -0
- package/dist/components/nav-button.d.ts +34 -0
- package/dist/components/nav-button.d.ts.map +1 -0
- package/dist/components/nav-button.js +70 -0
- package/dist/components/nav-button.js.map +1 -0
- package/dist/components/nav.d.ts +44 -0
- package/dist/components/nav.d.ts.map +1 -0
- package/dist/components/nav.js +53 -0
- package/dist/components/nav.js.map +1 -0
- package/dist/components/number-input.d.ts +18 -0
- package/dist/components/number-input.d.ts.map +1 -0
- package/dist/components/number-input.js +25 -0
- package/dist/components/number-input.js.map +1 -0
- package/dist/components/page.d.ts +24 -0
- package/dist/components/page.d.ts.map +1 -0
- package/dist/components/page.js +24 -0
- package/dist/components/page.js.map +1 -0
- package/dist/components/pagination.d.ts +17 -0
- package/dist/components/pagination.d.ts.map +1 -0
- package/dist/components/pagination.js +63 -0
- package/dist/components/pagination.js.map +1 -0
- package/dist/components/popover.d.ts +8 -0
- package/dist/components/popover.d.ts.map +1 -0
- package/dist/components/popover.js +24 -0
- package/dist/components/popover.js.map +1 -0
- package/dist/components/search-input.d.ts +14 -0
- package/dist/components/search-input.d.ts.map +1 -0
- package/dist/components/search-input.js +36 -0
- package/dist/components/search-input.js.map +1 -0
- package/dist/components/section-header.d.ts +107 -0
- package/dist/components/section-header.d.ts.map +1 -0
- package/dist/components/section-header.js +147 -0
- package/dist/components/section-header.js.map +1 -0
- package/dist/components/select.d.ts +17 -0
- package/dist/components/select.d.ts.map +1 -0
- package/dist/components/select.js +94 -0
- package/dist/components/select.js.map +1 -0
- package/dist/components/skeleton.d.ts +5 -0
- package/dist/components/skeleton.d.ts.map +1 -0
- package/dist/components/skeleton.js +13 -0
- package/dist/components/skeleton.js.map +1 -0
- package/dist/components/stack.d.ts +54 -0
- package/dist/components/stack.d.ts.map +1 -0
- package/dist/components/stack.js +46 -0
- package/dist/components/stack.js.map +1 -0
- package/dist/components/stat-strip.d.ts +38 -0
- package/dist/components/stat-strip.d.ts.map +1 -0
- package/dist/components/stat-strip.js +57 -0
- package/dist/components/stat-strip.js.map +1 -0
- package/dist/components/steps.d.ts +19 -0
- package/dist/components/steps.d.ts.map +1 -0
- package/dist/components/steps.js +29 -0
- package/dist/components/steps.js.map +1 -0
- package/dist/components/switch.d.ts +6 -0
- package/dist/components/switch.d.ts.map +1 -0
- package/dist/components/switch.js +17 -0
- package/dist/components/switch.js.map +1 -0
- package/dist/components/tabs.d.ts +62 -0
- package/dist/components/tabs.d.ts.map +1 -0
- package/dist/components/tabs.js +64 -0
- package/dist/components/tabs.js.map +1 -0
- package/dist/components/textarea.d.ts +9 -0
- package/dist/components/textarea.d.ts.map +1 -0
- package/dist/components/textarea.js +18 -0
- package/dist/components/textarea.js.map +1 -0
- package/dist/components/toaster.d.ts +6 -0
- package/dist/components/toaster.d.ts.map +1 -0
- package/dist/components/toaster.js +37 -0
- package/dist/components/toaster.js.map +1 -0
- package/dist/components/tooltip.d.ts +15 -0
- package/dist/components/tooltip.d.ts.map +1 -0
- package/dist/components/tooltip.js +41 -0
- package/dist/components/tooltip.js.map +1 -0
- package/dist/lib/animation.d.ts +12 -0
- package/dist/lib/animation.d.ts.map +1 -0
- package/dist/lib/animation.js +14 -0
- package/dist/lib/animation.js.map +1 -0
- package/dist/lib/classes.d.ts +29 -0
- package/dist/lib/classes.d.ts.map +1 -0
- package/dist/lib/classes.js +39 -0
- package/dist/lib/classes.js.map +1 -0
- package/dist/lib/icons.d.ts +21 -0
- package/dist/lib/icons.d.ts.map +1 -0
- package/dist/lib/icons.js +43 -0
- package/dist/lib/icons.js.map +1 -0
- package/dist/lib/interactive.d.ts +41 -0
- package/dist/lib/interactive.d.ts.map +1 -0
- package/dist/lib/interactive.js +30 -0
- package/dist/lib/interactive.js.map +1 -0
- package/dist/lib/slot.d.ts +12 -0
- package/dist/lib/slot.d.ts.map +1 -0
- package/dist/lib/slot.js +19 -0
- package/dist/lib/slot.js.map +1 -0
- package/dist/lib/variants.d.ts +3 -0
- package/dist/lib/variants.js +3 -0
- package/dist/styles.css +169 -0
- package/package.json +112 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { cn, variants } from "../lib/variants.js";
|
|
2
|
+
import { SectionHeader } from "./section-header.js";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
import { useRender } from "@base-ui/react/use-render";
|
|
5
|
+
//#region src/components/card.tsx
|
|
6
|
+
/**
|
|
7
|
+
* The one raised surface in the app: a white panel on the canvas, hairline border, 12px radius
|
|
8
|
+
* and a whisper of shadow, clipping whatever sits in its corners. Every card, list card and
|
|
9
|
+
* stat strip is this recipe — change it here and it changes everywhere.
|
|
10
|
+
*/
|
|
11
|
+
const cardSurfaceClasses = "overflow-clip rounded-xl border bg-card shadow-xs";
|
|
12
|
+
const cardVariants = variants({
|
|
13
|
+
base: [cardSurfaceClasses, "relative flex flex-col hover:[&[href]]:z-10 hover:[&[href]]:border-ring"],
|
|
14
|
+
variants: {
|
|
15
|
+
/**
|
|
16
|
+
* Separate the children with hairlines, for a card whose body is a run of rows or panels.
|
|
17
|
+
*/
|
|
18
|
+
divided: { true: "divide-y" } }
|
|
19
|
+
});
|
|
20
|
+
const cardPanelVariants = variants({
|
|
21
|
+
base: "border-t first:border-t-0",
|
|
22
|
+
variants: { size: {
|
|
23
|
+
md: "p-4 md:p-6",
|
|
24
|
+
lg: "p-6 md:p-8"
|
|
25
|
+
} },
|
|
26
|
+
defaultVariants: { size: "md" }
|
|
27
|
+
});
|
|
28
|
+
/**
|
|
29
|
+
* The card heading: `SectionHeader` preset to the card scale (an `h4` title, tight spacing). Pass
|
|
30
|
+
* `size="panel"` for the smaller dashboard-panel scale.
|
|
31
|
+
*/
|
|
32
|
+
function CardHeader(props) {
|
|
33
|
+
return /* @__PURE__ */ jsx(SectionHeader, {
|
|
34
|
+
size: "card",
|
|
35
|
+
...props
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
function Card({ render, className, divided, ...props }) {
|
|
39
|
+
return useRender({
|
|
40
|
+
render,
|
|
41
|
+
defaultTagName: "div",
|
|
42
|
+
props: {
|
|
43
|
+
className: cn(cardVariants({
|
|
44
|
+
divided,
|
|
45
|
+
className
|
|
46
|
+
})),
|
|
47
|
+
...props
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
function CardPanel({ className, render, size, ...props }) {
|
|
52
|
+
return useRender({
|
|
53
|
+
render,
|
|
54
|
+
defaultTagName: "div",
|
|
55
|
+
props: {
|
|
56
|
+
className: cn(cardPanelVariants({
|
|
57
|
+
size,
|
|
58
|
+
className
|
|
59
|
+
})),
|
|
60
|
+
...props
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
//#endregion
|
|
65
|
+
export { Card, CardHeader, CardPanel, cardSurfaceClasses };
|
|
66
|
+
|
|
67
|
+
//# sourceMappingURL=card.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"card.js","names":[],"sources":["../../src/components/card.tsx"],"sourcesContent":["import { useRender } from \"@base-ui/react/use-render\"\nimport type { ComponentProps } from \"react\"\nimport { SectionHeader } from \"~/components/section-header\"\nimport { variants, cn, type VariantProps } from \"~/lib/variants\"\n\n/**\n * The one raised surface in the app: a white panel on the canvas, hairline border, 12px radius\n * and a whisper of shadow, clipping whatever sits in its corners. Every card, list card and\n * stat strip is this recipe — change it here and it changes everywhere.\n */\nexport const cardSurfaceClasses = \"overflow-clip rounded-xl border bg-card shadow-xs\"\n\nconst cardVariants = variants({\n base: [\n cardSurfaceClasses,\n \"relative flex flex-col hover:[&[href]]:z-10 hover:[&[href]]:border-ring\",\n ],\n\n variants: {\n /**\n * Separate the children with hairlines, for a card whose body is a run of rows or panels.\n */\n divided: {\n true: \"divide-y\",\n },\n },\n})\n\nconst cardPanelVariants = variants({\n base: \"border-t first:border-t-0\",\n\n variants: {\n size: {\n md: \"p-4 md:p-6\",\n lg: \"p-6 md:p-8\",\n },\n },\n\n defaultVariants: {\n size: \"md\",\n },\n})\n\n/**\n * The card heading: `SectionHeader` preset to the card scale (an `h4` title, tight spacing). Pass\n * `size=\"panel\"` for the smaller dashboard-panel scale.\n */\nfunction CardHeader(props: ComponentProps<typeof SectionHeader>) {\n return <SectionHeader size=\"card\" {...props} />\n}\n\nexport type CardProps = useRender.ComponentProps<\"div\"> & VariantProps<typeof cardVariants>\n\nfunction Card({ render, className, divided, ...props }: CardProps) {\n return useRender({\n render,\n defaultTagName: \"div\",\n props: { className: cn(cardVariants({ divided, className })), ...props },\n })\n}\n\nexport type CardPanelProps = useRender.ComponentProps<\"div\"> &\n VariantProps<typeof cardPanelVariants>\n\nfunction CardPanel({ className, render, size, ...props }: CardPanelProps) {\n return useRender({\n render,\n defaultTagName: \"div\",\n props: {\n className: cn(cardPanelVariants({ size, className })),\n ...props,\n },\n })\n}\n\nexport { Card, CardHeader, CardPanel }\n"],"mappings":";;;;;;;;;;AAUA,MAAa,qBAAqB;AAElC,MAAM,eAAe,SAAS;CAC5B,MAAM,CACJ,oBACA,yEACF;CAEA,UAAU;;;;AAIR,SAAS,EACP,MAAM,WACR,EACF;AACF,CAAC;AAED,MAAM,oBAAoB,SAAS;CACjC,MAAM;CAEN,UAAU,EACR,MAAM;EACJ,IAAI;EACJ,IAAI;CACN,EACF;CAEA,iBAAiB,EACf,MAAM,KACR;AACF,CAAC;;;;;AAMD,SAAS,WAAW,OAA6C;CAC/D,OAAO,oBAAC,eAAD;EAAe,MAAK;EAAO,GAAI;CAAQ,CAAA;AAChD;AAIA,SAAS,KAAK,EAAE,QAAQ,WAAW,SAAS,GAAG,SAAoB;CACjE,OAAO,UAAU;EACf;EACA,gBAAgB;EAChB,OAAO;GAAE,WAAW,GAAG,aAAa;IAAE;IAAS;GAAU,CAAC,CAAC;GAAG,GAAG;EAAM;CACzE,CAAC;AACH;AAKA,SAAS,UAAU,EAAE,WAAW,QAAQ,MAAM,GAAG,SAAyB;CACxE,OAAO,UAAU;EACf;EACA,gBAAgB;EAChB,OAAO;GACL,WAAW,GAAG,kBAAkB;IAAE;IAAM;GAAU,CAAC,CAAC;GACpD,GAAG;EACL;CACF,CAAC;AACH"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ComponentProps, ComponentType, ReactNode } from "react";
|
|
2
|
+
import { ResponsiveContainer, Tooltip, TooltipContentProps } from "recharts";
|
|
3
|
+
//#region src/components/chart.d.ts
|
|
4
|
+
export type ChartConfig = { [k in string]: {
|
|
5
|
+
label?: ReactNode;
|
|
6
|
+
icon?: ComponentType;
|
|
7
|
+
} & ({
|
|
8
|
+
color?: string;
|
|
9
|
+
theme?: never;
|
|
10
|
+
} | {
|
|
11
|
+
color?: never;
|
|
12
|
+
theme: {
|
|
13
|
+
light: string;
|
|
14
|
+
dark: string;
|
|
15
|
+
};
|
|
16
|
+
}); };
|
|
17
|
+
declare function ChartContainer({ id, className, children, config, ...props }: ComponentProps<"div"> & {
|
|
18
|
+
config: ChartConfig;
|
|
19
|
+
children: ComponentProps<typeof ResponsiveContainer>["children"];
|
|
20
|
+
}): import("react").JSX.Element;
|
|
21
|
+
declare const ChartTooltip: typeof Tooltip;
|
|
22
|
+
type ChartTooltipContentProps = Partial<TooltipContentProps> & ComponentProps<"div"> & {
|
|
23
|
+
hideLabel?: boolean;
|
|
24
|
+
hideIndicator?: boolean;
|
|
25
|
+
indicator?: "line" | "dot" | "dashed";
|
|
26
|
+
nameKey?: string;
|
|
27
|
+
labelKey?: string;
|
|
28
|
+
};
|
|
29
|
+
declare function ChartTooltipContent({ active, payload, className, indicator, hideLabel, hideIndicator, label, labelFormatter, labelClassName, formatter, color, nameKey, labelKey }: ChartTooltipContentProps): import("react").JSX.Element | null;
|
|
30
|
+
//#endregion
|
|
31
|
+
export { ChartContainer, ChartTooltip, ChartTooltipContent };
|
|
32
|
+
//# sourceMappingURL=chart.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chart.d.ts","names":[],"sources":["../../src/components/chart.tsx"],"mappings":";;;YAQY,iBACT;EACC,QAAQ;EACR,OAAO;;EAEH;EAAgB;;EAChB;EAAe;IAAS;IAAe;;;iBAoBtC,iBACP,IACA,WACA,UACA,WACG,SACF;EACD,QAAQ;EACR,UAAU,sBAAsB;oBACjC,IAAA;cAmDK,qBAAY;KAGb,2BAA2B,QAAQ,uBACtC;EACE;EACA;EACA;EACA;EACA;;iBAGK,sBACP,QACA,SACA,WACA,WACA,WACA,eACA,OACA,gBACA,gBACA,WACA,OACA,SACA,YACC,2CAAwB,IAAA"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "../lib/variants.js";
|
|
3
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { createContext, useContext, useId, useMemo } from "react";
|
|
5
|
+
import { ResponsiveContainer, Tooltip } from "recharts";
|
|
6
|
+
//#region src/components/chart.tsx
|
|
7
|
+
const ChartContext = createContext(null);
|
|
8
|
+
function useChart() {
|
|
9
|
+
const context = useContext(ChartContext);
|
|
10
|
+
if (!context) throw new Error("useChart must be used within a <ChartContainer />");
|
|
11
|
+
return context;
|
|
12
|
+
}
|
|
13
|
+
function ChartContainer({ id, className, children, config, ...props }) {
|
|
14
|
+
const uniqueId = useId();
|
|
15
|
+
const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`;
|
|
16
|
+
return /* @__PURE__ */ jsx(ChartContext.Provider, {
|
|
17
|
+
value: { config },
|
|
18
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
19
|
+
"data-chart": chartId,
|
|
20
|
+
className: cn("flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none [&_[class*='recharts-zIndex-layer']]:outline-none", className),
|
|
21
|
+
...props,
|
|
22
|
+
children: [/* @__PURE__ */ jsx(ChartStyle, {
|
|
23
|
+
id: chartId,
|
|
24
|
+
config
|
|
25
|
+
}), /* @__PURE__ */ jsx(ResponsiveContainer, { children })]
|
|
26
|
+
})
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Publishes each series color as a `--color-<key>` custom property on the chart. One block, no
|
|
31
|
+
* theme selectors: a `theme` entry becomes a `light-dark()` pair, resolved by `color-scheme`.
|
|
32
|
+
*/
|
|
33
|
+
function ChartStyle({ id, config }) {
|
|
34
|
+
const colorConfig = Object.entries(config).filter(([, config]) => config.theme || config.color);
|
|
35
|
+
if (!colorConfig.length) return null;
|
|
36
|
+
return /* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: `[data-chart=${id}] {
|
|
37
|
+
${colorConfig.map(([key, itemConfig]) => {
|
|
38
|
+
return ` --color-${key}: ${itemConfig.theme ? `light-dark(${itemConfig.theme.light}, ${itemConfig.theme.dark})` : itemConfig.color};`;
|
|
39
|
+
}).join("\n")}
|
|
40
|
+
}` } });
|
|
41
|
+
}
|
|
42
|
+
const ChartTooltip = Tooltip;
|
|
43
|
+
function ChartTooltipContent({ active, payload, className, indicator = "dot", hideLabel = false, hideIndicator = false, label, labelFormatter, labelClassName, formatter, color, nameKey, labelKey }) {
|
|
44
|
+
const { config } = useChart();
|
|
45
|
+
const tooltipLabel = useMemo(() => {
|
|
46
|
+
if (hideLabel || !payload?.length) return null;
|
|
47
|
+
const [item] = payload;
|
|
48
|
+
const key = `${labelKey || item?.dataKey || item?.name || "value"}`;
|
|
49
|
+
const itemConfig = getPayloadConfigFromPayload(config, item, key);
|
|
50
|
+
const value = !labelKey && typeof label === "string" ? config[label]?.label || label : itemConfig?.label;
|
|
51
|
+
if (labelFormatter) return /* @__PURE__ */ jsx("div", {
|
|
52
|
+
className: cn("font-medium", labelClassName),
|
|
53
|
+
children: labelFormatter(value, payload)
|
|
54
|
+
});
|
|
55
|
+
if (!value) return null;
|
|
56
|
+
return /* @__PURE__ */ jsx("div", {
|
|
57
|
+
className: cn("font-medium", labelClassName),
|
|
58
|
+
children: value
|
|
59
|
+
});
|
|
60
|
+
}, [
|
|
61
|
+
label,
|
|
62
|
+
labelFormatter,
|
|
63
|
+
payload,
|
|
64
|
+
hideLabel,
|
|
65
|
+
labelClassName,
|
|
66
|
+
config,
|
|
67
|
+
labelKey
|
|
68
|
+
]);
|
|
69
|
+
if (!active || !payload?.length) return null;
|
|
70
|
+
const nestLabel = payload.length === 1 && indicator !== "dot";
|
|
71
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
72
|
+
className: cn("grid min-w-[8rem] items-start gap-1.5 rounded-md border bg-popover px-2.5 py-1.5 text-popover-foreground text-xs shadow-md", className),
|
|
73
|
+
children: [!nestLabel ? tooltipLabel : null, /* @__PURE__ */ jsx("div", {
|
|
74
|
+
className: "grid gap-1.5",
|
|
75
|
+
children: payload.map((item, index) => {
|
|
76
|
+
const key = `${nameKey || item.name || item.dataKey || "value"}`;
|
|
77
|
+
const itemConfig = getPayloadConfigFromPayload(config, item, key);
|
|
78
|
+
const indicatorColor = color || item.payload.fill || item.color;
|
|
79
|
+
return /* @__PURE__ */ jsx("div", {
|
|
80
|
+
className: cn("flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground", indicator === "dot" && "items-center"),
|
|
81
|
+
children: formatter && item?.value !== void 0 && item.name ? formatter(item.value, item.name, item, index, item.payload) : /* @__PURE__ */ jsxs(Fragment, { children: [itemConfig?.icon ? /* @__PURE__ */ jsx(itemConfig.icon, {}) : !hideIndicator && /* @__PURE__ */ jsx("div", {
|
|
82
|
+
className: cn("shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)", {
|
|
83
|
+
"h-2.5 w-2.5": indicator === "dot",
|
|
84
|
+
"w-1": indicator === "line",
|
|
85
|
+
"w-0 border-[1.5px] border-dashed bg-transparent": indicator === "dashed",
|
|
86
|
+
"my-0.5": nestLabel && indicator === "dashed"
|
|
87
|
+
}),
|
|
88
|
+
style: {
|
|
89
|
+
"--color-bg": indicatorColor,
|
|
90
|
+
"--color-border": indicatorColor
|
|
91
|
+
}
|
|
92
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
93
|
+
className: cn("flex flex-1 justify-between gap-4 leading-none", nestLabel ? "items-end" : "items-center"),
|
|
94
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
95
|
+
className: "grid gap-1.5",
|
|
96
|
+
children: [nestLabel ? tooltipLabel : null, /* @__PURE__ */ jsx("span", {
|
|
97
|
+
className: "text-muted-foreground",
|
|
98
|
+
children: itemConfig?.label || item.name
|
|
99
|
+
})]
|
|
100
|
+
}), item.value != null && /* @__PURE__ */ jsx("span", {
|
|
101
|
+
className: "font-display font-medium tabular-nums",
|
|
102
|
+
children: item.value.toLocaleString()
|
|
103
|
+
})]
|
|
104
|
+
})] })
|
|
105
|
+
}, `${item.dataKey ?? item.name ?? index}`);
|
|
106
|
+
})
|
|
107
|
+
})]
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function getPayloadConfigFromPayload(config, payload, key) {
|
|
111
|
+
if (typeof payload !== "object" || payload === null) return;
|
|
112
|
+
const payloadPayload = "payload" in payload && typeof payload.payload === "object" && payload.payload !== null ? payload.payload : void 0;
|
|
113
|
+
let configLabelKey = key;
|
|
114
|
+
if (key in payload && typeof payload[key] === "string") configLabelKey = payload[key];
|
|
115
|
+
else if (payloadPayload && key in payloadPayload && typeof payloadPayload[key] === "string") configLabelKey = payloadPayload[key];
|
|
116
|
+
return configLabelKey in config ? config[configLabelKey] : config[key];
|
|
117
|
+
}
|
|
118
|
+
//#endregion
|
|
119
|
+
export { ChartContainer, ChartTooltip, ChartTooltipContent };
|
|
120
|
+
|
|
121
|
+
//# sourceMappingURL=chart.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chart.js","names":[],"sources":["../../src/components/chart.tsx"],"sourcesContent":["\"use client\"\n\nimport type { ComponentProps, ComponentType, CSSProperties, ReactNode } from \"react\"\nimport { createContext, useContext, useId, useMemo } from \"react\"\nimport { ResponsiveContainer, Tooltip } from \"recharts\"\nimport type { TooltipContentProps } from \"recharts\"\nimport { cn } from \"~/lib/variants\"\n\nexport type ChartConfig = {\n [k in string]: {\n label?: ReactNode\n icon?: ComponentType\n } & (\n | { color?: string; theme?: never }\n | { color?: never; theme: { light: string; dark: string } }\n )\n}\n\ninterface ChartContextProps {\n config: ChartConfig\n}\n\nconst ChartContext = createContext<ChartContextProps | null>(null)\n\nfunction useChart() {\n const context = useContext(ChartContext)\n\n if (!context) {\n throw new Error(\"useChart must be used within a <ChartContainer />\")\n }\n\n return context\n}\n\nfunction ChartContainer({\n id,\n className,\n children,\n config,\n ...props\n}: ComponentProps<\"div\"> & {\n config: ChartConfig\n children: ComponentProps<typeof ResponsiveContainer>[\"children\"]\n}) {\n const uniqueId = useId()\n const chartId = `chart-${id || uniqueId.replace(/:/g, \"\")}`\n\n return (\n <ChartContext.Provider value={{ config }}>\n <div\n data-chart={chartId}\n className={cn(\n \"flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none [&_[class*='recharts-zIndex-layer']]:outline-none\",\n className,\n )}\n {...props}\n >\n <ChartStyle id={chartId} config={config} />\n <ResponsiveContainer>{children}</ResponsiveContainer>\n </div>\n </ChartContext.Provider>\n )\n}\n\n/**\n * Publishes each series color as a `--color-<key>` custom property on the chart. One block, no\n * theme selectors: a `theme` entry becomes a `light-dark()` pair, resolved by `color-scheme`.\n */\nfunction ChartStyle({ id, config }: { id: string; config: ChartConfig }) {\n const colorConfig = Object.entries(config).filter(([, config]) => config.theme || config.color)\n\n if (!colorConfig.length) {\n return null\n }\n\n return (\n <style\n dangerouslySetInnerHTML={{\n __html: `[data-chart=${id}] {\n${colorConfig\n .map(([key, itemConfig]) => {\n const color = itemConfig.theme\n ? `light-dark(${itemConfig.theme.light}, ${itemConfig.theme.dark})`\n : itemConfig.color\n\n return ` --color-${key}: ${color};`\n })\n .join(\"\\n\")}\n}`,\n }}\n />\n )\n}\n\nconst ChartTooltip = Tooltip\n\n// recharts injects the payload props at render time, so they are optional to the caller.\ntype ChartTooltipContentProps = Partial<TooltipContentProps> &\n ComponentProps<\"div\"> & {\n hideLabel?: boolean\n hideIndicator?: boolean\n indicator?: \"line\" | \"dot\" | \"dashed\"\n nameKey?: string\n labelKey?: string\n }\n\nfunction ChartTooltipContent({\n active,\n payload,\n className,\n indicator = \"dot\",\n hideLabel = false,\n hideIndicator = false,\n label,\n labelFormatter,\n labelClassName,\n formatter,\n color,\n nameKey,\n labelKey,\n}: ChartTooltipContentProps) {\n const { config } = useChart()\n\n const tooltipLabel = useMemo(() => {\n if (hideLabel || !payload?.length) {\n return null\n }\n\n const [item] = payload\n const key = `${labelKey || item?.dataKey || item?.name || \"value\"}`\n const itemConfig = getPayloadConfigFromPayload(config, item, key)\n const value =\n !labelKey && typeof label === \"string\"\n ? config[label as keyof typeof config]?.label || label\n : itemConfig?.label\n\n if (labelFormatter) {\n return (\n <div className={cn(\"font-medium\", labelClassName)}>{labelFormatter(value, payload)}</div>\n )\n }\n\n if (!value) {\n return null\n }\n\n return <div className={cn(\"font-medium\", labelClassName)}>{value}</div>\n }, [label, labelFormatter, payload, hideLabel, labelClassName, config, labelKey])\n\n if (!active || !payload?.length) {\n return null\n }\n\n const nestLabel = payload.length === 1 && indicator !== \"dot\"\n\n return (\n <div\n className={cn(\n \"grid min-w-[8rem] items-start gap-1.5 rounded-md border bg-popover px-2.5 py-1.5 text-popover-foreground text-xs shadow-md\",\n className,\n )}\n >\n {!nestLabel ? tooltipLabel : null}\n <div className=\"grid gap-1.5\">\n {payload.map((item, index) => {\n const key = `${nameKey || item.name || item.dataKey || \"value\"}`\n const itemConfig = getPayloadConfigFromPayload(config, item, key)\n const indicatorColor = color || item.payload.fill || item.color\n\n return (\n <div\n key={`${item.dataKey ?? item.name ?? index}`}\n className={cn(\n \"flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground\",\n indicator === \"dot\" && \"items-center\",\n )}\n >\n {formatter && item?.value !== undefined && item.name ? (\n formatter(item.value, item.name, item, index, item.payload)\n ) : (\n <>\n {itemConfig?.icon ? (\n <itemConfig.icon />\n ) : (\n !hideIndicator && (\n <div\n className={cn(\n \"shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)\",\n {\n \"h-2.5 w-2.5\": indicator === \"dot\",\n \"w-1\": indicator === \"line\",\n \"w-0 border-[1.5px] border-dashed bg-transparent\":\n indicator === \"dashed\",\n \"my-0.5\": nestLabel && indicator === \"dashed\",\n },\n )}\n style={\n {\n \"--color-bg\": indicatorColor,\n \"--color-border\": indicatorColor,\n } as CSSProperties\n }\n />\n )\n )}\n <div\n className={cn(\n \"flex flex-1 justify-between gap-4 leading-none\",\n nestLabel ? \"items-end\" : \"items-center\",\n )}\n >\n <div className=\"grid gap-1.5\">\n {nestLabel ? tooltipLabel : null}\n <span className=\"text-muted-foreground\">\n {itemConfig?.label || item.name}\n </span>\n </div>\n {item.value != null && (\n <span className=\"font-display font-medium tabular-nums\">\n {item.value.toLocaleString()}\n </span>\n )}\n </div>\n </>\n )}\n </div>\n )\n })}\n </div>\n </div>\n )\n}\n\n// Helper to extract item config from a payload.\nfunction getPayloadConfigFromPayload(config: ChartConfig, payload: unknown, key: string) {\n if (typeof payload !== \"object\" || payload === null) {\n return undefined\n }\n\n const payloadPayload =\n \"payload\" in payload && typeof payload.payload === \"object\" && payload.payload !== null\n ? payload.payload\n : undefined\n\n let configLabelKey: string = key\n\n if (key in payload && typeof payload[key as keyof typeof payload] === \"string\") {\n configLabelKey = payload[key as keyof typeof payload] as string\n } else if (\n payloadPayload &&\n key in payloadPayload &&\n typeof payloadPayload[key as keyof typeof payloadPayload] === \"string\"\n ) {\n configLabelKey = payloadPayload[key as keyof typeof payloadPayload] as string\n }\n\n return configLabelKey in config ? config[configLabelKey] : config[key as keyof typeof config]\n}\n\nexport { ChartContainer, ChartTooltip, ChartTooltipContent }\n"],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Button } from "./button.js";
|
|
2
|
+
import { ComponentProps, ReactNode } from "react";
|
|
3
|
+
//#region src/components/copy-button.d.ts
|
|
4
|
+
type CopyButtonProps = Omit<ComponentProps<typeof Button>, "prefix" | "onClick" | "children"> & {
|
|
5
|
+
value: string;
|
|
6
|
+
/**
|
|
7
|
+
* Visible label. Left out, the button is icon-only with a tooltip.
|
|
8
|
+
*/
|
|
9
|
+
label?: ReactNode;
|
|
10
|
+
/**
|
|
11
|
+
* What the label reads while the copied state is showing.
|
|
12
|
+
*/
|
|
13
|
+
copiedLabel?: ReactNode;
|
|
14
|
+
tooltip?: ReactNode;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Copies `value` to the clipboard and swaps its icon to a check for a couple of seconds. Owns the
|
|
18
|
+
* tooltip so the copied state survives the tooltip closing on click.
|
|
19
|
+
*/
|
|
20
|
+
export declare function CopyButton({ value, label, copiedLabel, tooltip, ...props }: CopyButtonProps): import("react").JSX.Element;
|
|
21
|
+
//#endregion
|
|
22
|
+
//# sourceMappingURL=copy-button.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copy-button.d.ts","names":[],"sources":["../../src/components/copy-button.tsx"],"mappings":";;;KAyCK,kBAAkB,KAAK,sBAAsB;EAChD;;;;EAIA,QAAQ;;;;EAIR,cAAc;EACd,UAAU;;;;;;wBAOI,aACd,OACA,OACA,aACA,YACG,SACF,kCAAe,IAAA"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { CheckIcon, CopyIcon } from "../lib/icons.js";
|
|
2
|
+
import { Button } from "./button.js";
|
|
3
|
+
import { Tooltip } from "./tooltip.js";
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
6
|
+
//#region src/components/copy-button.tsx
|
|
7
|
+
/**
|
|
8
|
+
* Copies text to the clipboard and flips `copied` to `true` for `timeout` ms, then back.
|
|
9
|
+
* A tiny local stand-in for `@mantine/hooks`' `useClipboard`, so the package carries no
|
|
10
|
+
* extra hook dependency.
|
|
11
|
+
*/
|
|
12
|
+
function useClipboard({ timeout = 2e3 } = {}) {
|
|
13
|
+
const [copied, setCopied] = useState(false);
|
|
14
|
+
const timer = useRef(void 0);
|
|
15
|
+
const copy = useCallback((value) => {
|
|
16
|
+
clearTimeout(timer.current);
|
|
17
|
+
navigator.clipboard.writeText(value).then(() => {
|
|
18
|
+
setCopied(true);
|
|
19
|
+
timer.current = setTimeout(() => setCopied(false), timeout);
|
|
20
|
+
}, () => setCopied(false));
|
|
21
|
+
}, [timeout]);
|
|
22
|
+
useEffect(() => () => clearTimeout(timer.current), []);
|
|
23
|
+
return {
|
|
24
|
+
copied,
|
|
25
|
+
copy
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Copies `value` to the clipboard and swaps its icon to a check for a couple of seconds. Owns the
|
|
30
|
+
* tooltip so the copied state survives the tooltip closing on click.
|
|
31
|
+
*/
|
|
32
|
+
function CopyButton({ value, label, copiedLabel = "Copied", tooltip = label ? void 0 : "Copy to clipboard", ...props }) {
|
|
33
|
+
const clipboard = useClipboard({ timeout: 2e3 });
|
|
34
|
+
return /* @__PURE__ */ jsx(Tooltip, {
|
|
35
|
+
tooltip: tooltip && (clipboard.copied ? copiedLabel : tooltip),
|
|
36
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
37
|
+
type: "button",
|
|
38
|
+
size: "sm",
|
|
39
|
+
"aria-label": label ? void 0 : "Copy to clipboard",
|
|
40
|
+
prefix: clipboard.copied ? /* @__PURE__ */ jsx(CheckIcon, { className: "text-success" }) : /* @__PURE__ */ jsx(CopyIcon, {}),
|
|
41
|
+
onClick: () => clipboard.copy(value),
|
|
42
|
+
...props,
|
|
43
|
+
children: label && (clipboard.copied ? copiedLabel : label)
|
|
44
|
+
})
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
export { CopyButton };
|
|
49
|
+
|
|
50
|
+
//# sourceMappingURL=copy-button.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copy-button.js","names":[],"sources":["../../src/components/copy-button.tsx"],"sourcesContent":["import {\n type ComponentProps,\n type ReactNode,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from \"react\"\nimport { Button } from \"~/components/button\"\nimport { Tooltip } from \"~/components/tooltip\"\nimport { CheckIcon, CopyIcon } from \"~/lib/icons\"\n\n/**\n * Copies text to the clipboard and flips `copied` to `true` for `timeout` ms, then back.\n * A tiny local stand-in for `@mantine/hooks`' `useClipboard`, so the package carries no\n * extra hook dependency.\n */\nfunction useClipboard({ timeout = 2000 }: { timeout?: number } = {}) {\n const [copied, setCopied] = useState(false)\n const timer = useRef<ReturnType<typeof setTimeout>>(undefined)\n\n const copy = useCallback(\n (value: string) => {\n clearTimeout(timer.current)\n\n navigator.clipboard.writeText(value).then(\n () => {\n setCopied(true)\n timer.current = setTimeout(() => setCopied(false), timeout)\n },\n () => setCopied(false),\n )\n },\n [timeout],\n )\n\n useEffect(() => () => clearTimeout(timer.current), [])\n\n return { copied, copy }\n}\n\ntype CopyButtonProps = Omit<ComponentProps<typeof Button>, \"prefix\" | \"onClick\" | \"children\"> & {\n value: string\n /**\n * Visible label. Left out, the button is icon-only with a tooltip.\n */\n label?: ReactNode\n /**\n * What the label reads while the copied state is showing.\n */\n copiedLabel?: ReactNode\n tooltip?: ReactNode\n}\n\n/**\n * Copies `value` to the clipboard and swaps its icon to a check for a couple of seconds. Owns the\n * tooltip so the copied state survives the tooltip closing on click.\n */\nexport function CopyButton({\n value,\n label,\n copiedLabel = \"Copied\",\n tooltip = label ? undefined : \"Copy to clipboard\",\n ...props\n}: CopyButtonProps) {\n const clipboard = useClipboard({ timeout: 2000 })\n\n return (\n <Tooltip tooltip={tooltip && (clipboard.copied ? copiedLabel : tooltip)}>\n <Button\n type=\"button\"\n size=\"sm\"\n aria-label={label ? undefined : \"Copy to clipboard\"}\n prefix={clipboard.copied ? <CheckIcon className=\"text-success\" /> : <CopyIcon />}\n onClick={() => clipboard.copy(value)}\n {...props}\n >\n {label && (clipboard.copied ? copiedLabel : label)}\n </Button>\n </Tooltip>\n )\n}\n"],"mappings":";;;;;;;;;;;AAiBA,SAAS,aAAa,EAAE,UAAU,QAA+B,CAAC,GAAG;CACnE,MAAM,CAAC,QAAQ,aAAa,SAAS,KAAK;CAC1C,MAAM,QAAQ,OAAsC,KAAA,CAAS;CAE7D,MAAM,OAAO,aACV,UAAkB;EACjB,aAAa,MAAM,OAAO;EAE1B,UAAU,UAAU,UAAU,KAAK,CAAC,CAAC,WAC7B;GACJ,UAAU,IAAI;GACd,MAAM,UAAU,iBAAiB,UAAU,KAAK,GAAG,OAAO;EAC5D,SACM,UAAU,KAAK,CACvB;CACF,GACA,CAAC,OAAO,CACV;CAEA,sBAAsB,aAAa,MAAM,OAAO,GAAG,CAAC,CAAC;CAErD,OAAO;EAAE;EAAQ;CAAK;AACxB;;;;;AAmBA,SAAgB,WAAW,EACzB,OACA,OACA,cAAc,UACd,UAAU,QAAQ,KAAA,IAAY,qBAC9B,GAAG,SACe;CAClB,MAAM,YAAY,aAAa,EAAE,SAAS,IAAK,CAAC;CAEhD,OACE,oBAAC,SAAD;EAAS,SAAS,YAAY,UAAU,SAAS,cAAc;EAC7D,UAAA,oBAAC,QAAD;GACE,MAAK;GACL,MAAK;GACL,cAAY,QAAQ,KAAA,IAAY;GAChC,QAAQ,UAAU,SAAS,oBAAC,WAAD,EAAW,WAAU,eAAgB,CAAA,IAAI,oBAAC,UAAD,CAAW,CAAA;GAC/E,eAAe,UAAU,KAAK,KAAK;GACnC,GAAI;GAEH,UAAA,UAAU,UAAU,SAAS,cAAc;EACtC,CAAA;CACD,CAAA;AAEb"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Input } from "./input.js";
|
|
2
|
+
import { ComponentProps } from "react";
|
|
3
|
+
//#region src/components/copy-input.d.ts
|
|
4
|
+
type CopyInputProps = Omit<ComponentProps<typeof Input>, "value" | "readOnly"> & {
|
|
5
|
+
value: string;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Read-only input with a copy-to-clipboard button pinned to its right edge. Props go to the
|
|
9
|
+
* input itself so `ref`, `aria-label` and font classes land on the field.
|
|
10
|
+
*/
|
|
11
|
+
export declare function CopyInput({ value, className, ...props }: CopyInputProps): import("react").JSX.Element;
|
|
12
|
+
//#endregion
|
|
13
|
+
//# sourceMappingURL=copy-input.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copy-input.d.ts","names":[],"sources":["../../src/components/copy-input.tsx"],"mappings":";;;KAKK,iBAAiB,KAAK,sBAAsB;EAC/C;;;;;;wBAOc,YAAY,OAAO,cAAc,SAAS,iCAAc,IAAA"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { cn } from "../lib/variants.js";
|
|
2
|
+
import { CopyButton } from "./copy-button.js";
|
|
3
|
+
import { Input } from "./input.js";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
//#region src/components/copy-input.tsx
|
|
6
|
+
/**
|
|
7
|
+
* Read-only input with a copy-to-clipboard button pinned to its right edge. Props go to the
|
|
8
|
+
* input itself so `ref`, `aria-label` and font classes land on the field.
|
|
9
|
+
*/
|
|
10
|
+
function CopyInput({ value, className, ...props }) {
|
|
11
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
12
|
+
className: "relative w-full",
|
|
13
|
+
children: [/* @__PURE__ */ jsx(Input, {
|
|
14
|
+
readOnly: true,
|
|
15
|
+
value,
|
|
16
|
+
className: cn("pr-10", className),
|
|
17
|
+
...props
|
|
18
|
+
}), /* @__PURE__ */ jsx(CopyButton, {
|
|
19
|
+
value,
|
|
20
|
+
variant: "ghost",
|
|
21
|
+
className: "absolute top-1/2 right-1 -translate-y-1/2"
|
|
22
|
+
})]
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
export { CopyInput };
|
|
27
|
+
|
|
28
|
+
//# sourceMappingURL=copy-input.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copy-input.js","names":[],"sources":["../../src/components/copy-input.tsx"],"sourcesContent":["import type { ComponentProps } from \"react\"\nimport { CopyButton } from \"~/components/copy-button\"\nimport { Input } from \"~/components/input\"\nimport { cn } from \"~/lib/variants\"\n\ntype CopyInputProps = Omit<ComponentProps<typeof Input>, \"value\" | \"readOnly\"> & {\n value: string\n}\n\n/**\n * Read-only input with a copy-to-clipboard button pinned to its right edge. Props go to the\n * input itself so `ref`, `aria-label` and font classes land on the field.\n */\nexport function CopyInput({ value, className, ...props }: CopyInputProps) {\n return (\n <div className=\"relative w-full\">\n <Input readOnly value={value} className={cn(\"pr-10\", className)} {...props} />\n\n <CopyButton\n value={value}\n variant=\"ghost\"\n className=\"absolute top-1/2 right-1 -translate-y-1/2\"\n />\n </div>\n )\n}\n"],"mappings":";;;;;;;;;AAaA,SAAgB,UAAU,EAAE,OAAO,WAAW,GAAG,SAAyB;CACxE,OACE,qBAAC,OAAD;EAAK,WAAU;EAAf,UAAA,CACE,oBAAC,OAAD;GAAO,UAAA;GAAgB;GAAO,WAAW,GAAG,SAAS,SAAS;GAAG,GAAI;EAAQ,CAAA,GAE7E,oBAAC,YAAD;GACS;GACP,SAAQ;GACR,WAAU;EACX,CAAA,CACE;;AAET"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { VariantProps } from "../lib/variants.js";
|
|
2
|
+
import { useRender } from "@base-ui/react/use-render";
|
|
3
|
+
import { ReactNode } from "react";
|
|
4
|
+
//#region src/components/detail-row.d.ts
|
|
5
|
+
declare const detailRowVariants: import("tailwind-variants").TVReturnType<{
|
|
6
|
+
density: {
|
|
7
|
+
form: {
|
|
8
|
+
root: string;
|
|
9
|
+
label: string;
|
|
10
|
+
value: string;
|
|
11
|
+
};
|
|
12
|
+
comfortable: {
|
|
13
|
+
root: string;
|
|
14
|
+
label: string;
|
|
15
|
+
value: string;
|
|
16
|
+
};
|
|
17
|
+
compact: {
|
|
18
|
+
root: string;
|
|
19
|
+
label: string;
|
|
20
|
+
value: string;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
}, {
|
|
24
|
+
root: string;
|
|
25
|
+
label: string;
|
|
26
|
+
value: string;
|
|
27
|
+
}, undefined, {
|
|
28
|
+
density: {
|
|
29
|
+
form: {
|
|
30
|
+
root: string;
|
|
31
|
+
label: string;
|
|
32
|
+
value: string;
|
|
33
|
+
};
|
|
34
|
+
comfortable: {
|
|
35
|
+
root: string;
|
|
36
|
+
label: string;
|
|
37
|
+
value: string;
|
|
38
|
+
};
|
|
39
|
+
compact: {
|
|
40
|
+
root: string;
|
|
41
|
+
label: string;
|
|
42
|
+
value: string;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
}, {
|
|
46
|
+
root: string;
|
|
47
|
+
label: string;
|
|
48
|
+
value: string;
|
|
49
|
+
}, import("tailwind-variants").TVReturnTypeLike<{
|
|
50
|
+
density: {
|
|
51
|
+
form: {
|
|
52
|
+
root: string;
|
|
53
|
+
label: string;
|
|
54
|
+
value: string;
|
|
55
|
+
};
|
|
56
|
+
comfortable: {
|
|
57
|
+
root: string;
|
|
58
|
+
label: string;
|
|
59
|
+
value: string;
|
|
60
|
+
};
|
|
61
|
+
compact: {
|
|
62
|
+
root: string;
|
|
63
|
+
label: string;
|
|
64
|
+
value: string;
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
}, {
|
|
68
|
+
root: string;
|
|
69
|
+
label: string;
|
|
70
|
+
value: string;
|
|
71
|
+
}>>;
|
|
72
|
+
type DetailRowProps = useRender.ComponentProps<"div"> & VariantProps<typeof detailRowVariants> & {
|
|
73
|
+
/**
|
|
74
|
+
* Row label. Pass a `FormLabel` when the row wraps a form field so the label
|
|
75
|
+
* keeps its `htmlFor` wiring.
|
|
76
|
+
*/
|
|
77
|
+
label: ReactNode;
|
|
78
|
+
description?: ReactNode;
|
|
79
|
+
/**
|
|
80
|
+
* Extra classes for the value column, e.g. `sm:items-end` to keep a button
|
|
81
|
+
* at its intrinsic width instead of stretching.
|
|
82
|
+
*/
|
|
83
|
+
valueClassName?: string;
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* One "label plus value" line, in the three densities the app uses: `form` for an editable
|
|
87
|
+
* settings row (label and description left, control right), `comfortable` for a detail card's
|
|
88
|
+
* `<dl>`, `compact` for a sidebar timeline. Rows sit in a `divide-y` parent, which draws the
|
|
89
|
+
* hairlines between them.
|
|
90
|
+
*/
|
|
91
|
+
export declare function DetailRow({ density, label, description, valueClassName, className, children, render, ...props }: DetailRowProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
92
|
+
//#endregion
|
|
93
|
+
//# sourceMappingURL=detail-row.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detail-row.d.ts","names":[],"sources":["../../src/components/detail-row.tsx"],"mappings":";;;;cAMM,+CAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAgClB,iBAAiB,UAAU,wBAC9B,oBAAoB;;;;;EAKlB,OAAO;EACP,cAAc;;;;;EAKd;;;;;;;;wBASY,YACd,SACA,OACA,aACA,gBACA,WACA,UACA,WACG,SACF,iCAAc,+CAAA"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { variants } from "../lib/variants.js";
|
|
2
|
+
import { slot } from "../lib/slot.js";
|
|
3
|
+
import "../lib/classes.js";
|
|
4
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { useRender } from "@base-ui/react/use-render";
|
|
6
|
+
import { isValidElement } from "react";
|
|
7
|
+
//#region src/components/detail-row.tsx
|
|
8
|
+
const detailRowVariants = variants({
|
|
9
|
+
slots: {
|
|
10
|
+
root: "",
|
|
11
|
+
label: "",
|
|
12
|
+
value: ""
|
|
13
|
+
},
|
|
14
|
+
variants: { density: {
|
|
15
|
+
form: {
|
|
16
|
+
root: "grid items-start gap-x-8 gap-y-3 py-5 sm:grid-cols-[minmax(0,1fr)_minmax(0,20rem)]",
|
|
17
|
+
label: "flex min-w-0 flex-col gap-1",
|
|
18
|
+
value: "flex w-full min-w-0 flex-col gap-2 sm:justify-self-end"
|
|
19
|
+
},
|
|
20
|
+
comfortable: {
|
|
21
|
+
root: "grid items-start gap-x-8 gap-y-1 py-3 sm:grid-cols-[minmax(0,12rem)_minmax(0,1fr)]",
|
|
22
|
+
label: "text-muted-foreground text-sm",
|
|
23
|
+
value: "min-w-0 text-sm"
|
|
24
|
+
},
|
|
25
|
+
compact: {
|
|
26
|
+
root: "flex items-baseline justify-between gap-4 py-2 first:pt-0 last:pb-0",
|
|
27
|
+
label: "text-muted-foreground",
|
|
28
|
+
value: "tabular-nums"
|
|
29
|
+
}
|
|
30
|
+
} },
|
|
31
|
+
defaultVariants: { density: "comfortable" }
|
|
32
|
+
});
|
|
33
|
+
/**
|
|
34
|
+
* One "label plus value" line, in the three densities the app uses: `form` for an editable
|
|
35
|
+
* settings row (label and description left, control right), `comfortable` for a detail card's
|
|
36
|
+
* `<dl>`, `compact` for a sidebar timeline. Rows sit in a `divide-y` parent, which draws the
|
|
37
|
+
* hairlines between them.
|
|
38
|
+
*/
|
|
39
|
+
function DetailRow({ density = "comfortable", label, description, valueClassName, className, children, render, ...props }) {
|
|
40
|
+
const styles = detailRowVariants({ density });
|
|
41
|
+
const isForm = density === "form";
|
|
42
|
+
const LabelTag = isForm ? "div" : "dt";
|
|
43
|
+
const ValueTag = isForm ? "div" : "dd";
|
|
44
|
+
return useRender({
|
|
45
|
+
render,
|
|
46
|
+
defaultTagName: "div",
|
|
47
|
+
props: {
|
|
48
|
+
className: styles.root({ className }),
|
|
49
|
+
children: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(LabelTag, {
|
|
50
|
+
className: styles.label(),
|
|
51
|
+
children: [isForm ? /* @__PURE__ */ jsx("div", {
|
|
52
|
+
className: "font-medium text-sm",
|
|
53
|
+
children: label
|
|
54
|
+
}) : label, description && (isValidElement(description) ? slot(description, { className: "text-pretty text-muted-foreground text-sm" }) : /* @__PURE__ */ jsx("p", {
|
|
55
|
+
className: "text-pretty text-muted-foreground text-sm",
|
|
56
|
+
children: description
|
|
57
|
+
}))]
|
|
58
|
+
}), /* @__PURE__ */ jsx(ValueTag, {
|
|
59
|
+
className: styles.value({ className: valueClassName }),
|
|
60
|
+
children
|
|
61
|
+
})] }),
|
|
62
|
+
...props
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
//#endregion
|
|
67
|
+
export { DetailRow };
|
|
68
|
+
|
|
69
|
+
//# sourceMappingURL=detail-row.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detail-row.js","names":[],"sources":["../../src/components/detail-row.tsx"],"sourcesContent":["import { useRender } from \"@base-ui/react/use-render\"\nimport { isValidElement, type ReactNode } from \"react\"\nimport { descriptionClasses } from \"~/lib/classes\"\nimport { slot } from \"~/lib/slot\"\nimport { variants, type VariantProps } from \"~/lib/variants\"\n\nconst detailRowVariants = variants({\n slots: {\n root: \"\",\n label: \"\",\n value: \"\",\n },\n\n variants: {\n density: {\n form: {\n root: \"grid items-start gap-x-8 gap-y-3 py-5 sm:grid-cols-[minmax(0,1fr)_minmax(0,20rem)]\",\n label: \"flex min-w-0 flex-col gap-1\",\n value: \"flex w-full min-w-0 flex-col gap-2 sm:justify-self-end\",\n },\n comfortable: {\n root: \"grid items-start gap-x-8 gap-y-1 py-3 sm:grid-cols-[minmax(0,12rem)_minmax(0,1fr)]\",\n label: \"text-muted-foreground text-sm\",\n value: \"min-w-0 text-sm\",\n },\n compact: {\n root: \"flex items-baseline justify-between gap-4 py-2 first:pt-0 last:pb-0\",\n label: \"text-muted-foreground\",\n value: \"tabular-nums\",\n },\n },\n },\n\n defaultVariants: {\n density: \"comfortable\",\n },\n})\n\ntype DetailRowProps = useRender.ComponentProps<\"div\"> &\n VariantProps<typeof detailRowVariants> & {\n /**\n * Row label. Pass a `FormLabel` when the row wraps a form field so the label\n * keeps its `htmlFor` wiring.\n */\n label: ReactNode\n description?: ReactNode\n /**\n * Extra classes for the value column, e.g. `sm:items-end` to keep a button\n * at its intrinsic width instead of stretching.\n */\n valueClassName?: string\n }\n\n/**\n * One \"label plus value\" line, in the three densities the app uses: `form` for an editable\n * settings row (label and description left, control right), `comfortable` for a detail card's\n * `<dl>`, `compact` for a sidebar timeline. Rows sit in a `divide-y` parent, which draws the\n * hairlines between them.\n */\nexport function DetailRow({\n density = \"comfortable\",\n label,\n description,\n valueClassName,\n className,\n children,\n render,\n ...props\n}: DetailRowProps) {\n const styles = detailRowVariants({ density })\n const isForm = density === \"form\"\n const LabelTag = isForm ? \"div\" : \"dt\"\n const ValueTag = isForm ? \"div\" : \"dd\"\n\n return useRender({\n render,\n defaultTagName: \"div\",\n props: {\n className: styles.root({ className }),\n children: (\n <>\n <LabelTag className={styles.label()}>\n {isForm ? <div className=\"font-medium text-sm\">{label}</div> : label}\n\n {description &&\n (isValidElement(description) ? (\n slot(description, { className: descriptionClasses })\n ) : (\n <p className={descriptionClasses}>{description}</p>\n ))}\n </LabelTag>\n\n <ValueTag className={styles.value({ className: valueClassName })}>{children}</ValueTag>\n </>\n ),\n ...props,\n },\n })\n}\n"],"mappings":";;;;;;;AAMA,MAAM,oBAAoB,SAAS;CACjC,OAAO;EACL,MAAM;EACN,OAAO;EACP,OAAO;CACT;CAEA,UAAU,EACR,SAAS;EACP,MAAM;GACJ,MAAM;GACN,OAAO;GACP,OAAO;EACT;EACA,aAAa;GACX,MAAM;GACN,OAAO;GACP,OAAO;EACT;EACA,SAAS;GACP,MAAM;GACN,OAAO;GACP,OAAO;EACT;CACF,EACF;CAEA,iBAAiB,EACf,SAAS,cACX;AACF,CAAC;;;;;;;AAuBD,SAAgB,UAAU,EACxB,UAAU,eACV,OACA,aACA,gBACA,WACA,UACA,QACA,GAAG,SACc;CACjB,MAAM,SAAS,kBAAkB,EAAE,QAAQ,CAAC;CAC5C,MAAM,SAAS,YAAY;CAC3B,MAAM,WAAW,SAAS,QAAQ;CAClC,MAAM,WAAW,SAAS,QAAQ;CAElC,OAAO,UAAU;EACf;EACA,gBAAgB;EAChB,OAAO;GACL,WAAW,OAAO,KAAK,EAAE,UAAU,CAAC;GACpC,UACE,qBAAA,UAAA,EAAA,UAAA,CACE,qBAAC,UAAD;IAAU,WAAW,OAAO,MAAM;IAAlC,UAAA,CACG,SAAS,oBAAC,OAAD;KAAK,WAAU;KAAuB,UAAA;IAAW,CAAA,IAAI,OAE9D,gBACE,eAAe,WAAW,IACzB,KAAK,aAAa,EAAE,WAAA,4CAA8B,CAAC,IAEnD,oBAAC,KAAD;KAAG,WAAA;KAAgC,UAAA;IAAe,CAAA,EAE9C;GAEV,CAAA,GAAA,oBAAC,UAAD;IAAU,WAAW,OAAO,MAAM,EAAE,WAAW,eAAe,CAAC;IAAI;GAAmB,CAAA,CACtF,EAAA,CAAA;GAEJ,GAAG;EACL;CACF,CAAC;AACH"}
|