@alpic-ai/ui 0.0.0-staging.gfab65e2 → 0.0.0-staging.gfabea7f

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.
@@ -5,6 +5,9 @@ interface BarListProps {
5
5
  data: ReadonlyArray<Record<string, string | number | null | undefined>>;
6
6
  index: string;
7
7
  dataKey?: string;
8
+ secondaryKey?: string;
9
+ secondaryVariantKey?: string;
10
+ titleKey?: string;
8
11
  maxItems?: number;
9
12
  palette?: ChartPaletteName;
10
13
  /** Renders bars in a single semantic hue (e.g. red for errors) rather than the palette ramp. */
@@ -14,6 +17,6 @@ interface BarListProps {
14
17
  labelFormatter?: (label: string | number) => string;
15
18
  className?: string;
16
19
  }
17
- declare function BarList({ data, index, dataKey, maxItems, palette, semantic, loading, valueFormatter, labelFormatter, className }: BarListProps): React$1.JSX.Element;
20
+ declare function BarList({ data, index, dataKey, secondaryKey, secondaryVariantKey, titleKey, maxItems, palette, semantic, loading, valueFormatter, labelFormatter, className }: BarListProps): React$1.JSX.Element;
18
21
  //#endregion
19
22
  export { BarList, BarListProps };
@@ -1,8 +1,9 @@
1
1
  "use client";
2
2
  import { cn } from "../lib/cn.mjs";
3
3
  import { rampColor } from "../lib/chart-palette.mjs";
4
- import { formatShare } from "../lib/chart.mjs";
5
4
  import { useChartContext } from "./chart-container.mjs";
5
+ import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip.mjs";
6
+ import { Badge } from "./badge.mjs";
6
7
  import { jsx, jsxs } from "react/jsx-runtime";
7
8
  import * as React$1 from "react";
8
9
  //#region src/components/bar-list.tsx
@@ -11,19 +12,28 @@ const SEMANTIC_KEY = {
11
12
  warning: "warning",
12
13
  success: "success"
13
14
  };
15
+ const SECONDARY_BADGE_VARIANT = {
16
+ error: "error",
17
+ warning: "warning",
18
+ success: "success"
19
+ };
14
20
  const PLACEHOLDER_HEIGHT = 168;
15
21
  const RAMP_CEILING = .8;
16
22
  const SEMANTIC_FLOOR = .62;
17
23
  const IN_FILL_SHADOW = "0 1px 2px rgb(0 0 0 / 0.28)";
18
- function BarList({ data, index, dataKey = "value", maxItems, palette, semantic, loading = false, valueFormatter = (value) => value.toLocaleString("en-US"), labelFormatter, className }) {
24
+ const ROW_INTERACTIVE_CLASS = "rounded-md text-left outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-card";
25
+ function BarList({ data, index, dataKey = "value", secondaryKey, secondaryVariantKey, titleKey, maxItems, palette, semantic, loading = false, valueFormatter = (value) => value.toLocaleString("en-US"), labelFormatter, className }) {
19
26
  const { paletteName, theme } = useChartContext(palette);
20
27
  const accent = semantic ? theme[SEMANTIC_KEY[semantic]] : null;
21
28
  const [active, setActive] = React$1.useState(null);
22
- const total = React$1.useMemo(() => data.reduce((sum, row) => sum + (Number(row[dataKey]) || 0), 0), [data, dataKey]);
23
29
  const rows = React$1.useMemo(() => {
30
+ const optionalText = (row, key) => key != null && row[key] != null ? String(row[key]) : void 0;
24
31
  const mapped = data.map((row) => ({
25
32
  name: String(row[index] ?? ""),
26
- value: Number(row[dataKey]) || 0
33
+ value: Number(row[dataKey]) || 0,
34
+ secondary: optionalText(row, secondaryKey),
35
+ secondaryVariant: SECONDARY_BADGE_VARIANT[optionalText(row, secondaryVariantKey) ?? ""],
36
+ title: optionalText(row, titleKey)
27
37
  }));
28
38
  mapped.sort((lower, upper) => upper.value - lower.value);
29
39
  const capped = maxItems ? mapped.slice(0, maxItems) : mapped;
@@ -39,11 +49,15 @@ function BarList({ data, index, dataKey = "value", maxItems, palette, semantic,
39
49
  data,
40
50
  index,
41
51
  dataKey,
52
+ secondaryKey,
53
+ secondaryVariantKey,
54
+ titleKey,
42
55
  maxItems,
43
56
  paletteName,
44
57
  accent
45
58
  ]);
46
59
  const maxValue = rows.reduce((max, row) => row.value > max ? row.value : max, 0);
60
+ const hasSecondary = rows.some((row) => row.secondary != null);
47
61
  const isEmpty = rows.length === 0;
48
62
  const formatName = (name) => labelFormatter ? labelFormatter(name) : name;
49
63
  if (loading) return /* @__PURE__ */ jsxs("div", {
@@ -64,10 +78,12 @@ function BarList({ data, index, dataKey = "value", maxItems, palette, semantic,
64
78
  const fillWidth = `${fraction * 100}%`;
65
79
  const dimmed = active !== null && active !== slot;
66
80
  const isActive = active === slot;
67
- return /* @__PURE__ */ jsxs("div", {
81
+ const hasTooltip = row.title != null;
82
+ const bar = /* @__PURE__ */ jsxs(hasTooltip ? "button" : "div", {
83
+ type: hasTooltip ? "button" : void 0,
68
84
  onMouseEnter: () => setActive(slot),
69
85
  onMouseLeave: () => setActive(null),
70
- className: "flex items-center gap-3 py-1",
86
+ className: cn("flex items-center gap-3 py-1", hasTooltip && ROW_INTERACTIVE_CLASS),
71
87
  children: [/* @__PURE__ */ jsxs("div", {
72
88
  className: "relative h-[30px] flex-1 overflow-hidden rounded-md bg-muted",
73
89
  children: [/* @__PURE__ */ jsx("span", {
@@ -91,12 +107,30 @@ function BarList({ data, index, dataKey = "value", maxItems, palette, semantic,
91
107
  children: formatName(row.name)
92
108
  })
93
109
  })]
94
- }), /* @__PURE__ */ jsx("span", {
95
- className: "min-w-[4rem] shrink-0 whitespace-nowrap text-right font-mono text-text-xs tabular-nums motion-safe:transition-colors",
96
- style: { color: isActive ? row.color : void 0 },
97
- children: isActive ? formatShare(total > 0 ? row.value / total : 0) : valueFormatter(row.value)
110
+ }), /* @__PURE__ */ jsxs("div", {
111
+ className: "flex shrink-0 items-center justify-end gap-2.5 whitespace-nowrap font-mono text-text-xs tabular-nums",
112
+ children: [/* @__PURE__ */ jsx("span", {
113
+ className: "min-w-[4rem] text-right motion-safe:transition-colors",
114
+ style: { color: isActive ? row.color : void 0 },
115
+ children: valueFormatter(row.value)
116
+ }), hasSecondary && /* @__PURE__ */ jsx("span", {
117
+ className: "flex min-w-[3.5rem] justify-end",
118
+ children: row.secondary != null && /* @__PURE__ */ jsx(Badge, {
119
+ variant: row.secondaryVariant ?? "secondary",
120
+ size: "sm",
121
+ children: row.secondary
122
+ })
123
+ })]
98
124
  })]
99
125
  }, row.name);
126
+ if (row.title == null) return bar;
127
+ return /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
128
+ asChild: true,
129
+ children: bar
130
+ }), /* @__PURE__ */ jsx(TooltipContent, {
131
+ className: "max-w-md text-pretty text-left",
132
+ children: row.title
133
+ })] }, row.name);
100
134
  })
101
135
  });
102
136
  }
@@ -102,6 +102,7 @@ function Combobox(props) {
102
102
  function ComboboxTrigger({ className, size, placeholder, children, ...props }) {
103
103
  const { multiple, value, values, open, onDeselect } = useComboboxContext();
104
104
  const isEmpty = multiple ? values.length === 0 : value === null || value === void 0;
105
+ const hasCustomMultipleValue = multiple && children !== void 0;
105
106
  return /* @__PURE__ */ jsx(PopoverTrigger, {
106
107
  asChild: true,
107
108
  children: /* @__PURE__ */ jsxs("button", {
@@ -112,8 +113,8 @@ function ComboboxTrigger({ className, size, placeholder, children, ...props }) {
112
113
  className: cn(selectTriggerVariants({ size }), className),
113
114
  ...props,
114
115
  children: [/* @__PURE__ */ jsx("span", {
115
- className: cn("flex flex-1 items-center gap-1.5 text-left", multiple && "flex-wrap", isEmpty && "text-placeholder"),
116
- children: isEmpty ? placeholder : multiple ? /* @__PURE__ */ jsx(ComboboxTags, {
116
+ className: cn("flex min-w-0 flex-1 items-center gap-1.5 text-left", multiple && !hasCustomMultipleValue && "flex-wrap", isEmpty && !hasCustomMultipleValue && "text-placeholder"),
117
+ children: hasCustomMultipleValue ? children : isEmpty ? placeholder : multiple ? /* @__PURE__ */ jsx(ComboboxTags, {
117
118
  values,
118
119
  onDeselect
119
120
  }) : children
@@ -12,7 +12,7 @@ declare const useFormField: () => {
12
12
  isDirty: boolean;
13
13
  isTouched: boolean;
14
14
  isValidating: boolean;
15
- error?: import("react-hook-form").FieldError;
15
+ error?: import("react-hook-form").FieldError | undefined;
16
16
  id: string;
17
17
  name: string;
18
18
  formItemId: string;
@@ -10,7 +10,7 @@ interface StatDelta {
10
10
  label?: React$1.ReactNode;
11
11
  indicator?: "arrow" | "sign";
12
12
  }
13
- interface StatProps extends React$1.ComponentProps<"div"> {
13
+ interface StatProps extends Omit<React$1.HTMLAttributes<HTMLElement>, "onClick"> {
14
14
  value: React$1.ReactNode;
15
15
  unit?: string;
16
16
  delta?: StatDelta | null;
@@ -19,8 +19,9 @@ interface StatProps extends React$1.ComponentProps<"div"> {
19
19
  }>;
20
20
  semantic?: "error" | "warning" | "success";
21
21
  compact?: boolean;
22
+ onClick?: () => void;
22
23
  }
23
- declare function Stat({ value, unit, delta, sparkline, semantic, compact, className, ...props }: StatProps): React$1.JSX.Element;
24
+ declare function Stat({ value, unit, delta, sparkline, semantic, compact, className, onClick, ...props }: StatProps): React$1.JSX.Element;
24
25
  declare function StatSparkline({ sparkline, semantic, className }: Pick<StatProps, "sparkline" | "semantic"> & {
25
26
  className?: string;
26
27
  }): React$1.JSX.Element | null;
@@ -26,11 +26,14 @@ const SEMANTIC_KEY = {
26
26
  warning: "warning",
27
27
  success: "success"
28
28
  };
29
- function Stat({ value, unit, delta, sparkline, semantic, compact = false, className, ...props }) {
29
+ const STAT_INTERACTIVE_CLASS = "rounded-sm text-left outline-none transition-opacity focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-card";
30
+ function Stat({ value, unit, delta, sparkline, semantic, compact = false, className, onClick, ...props }) {
30
31
  const sentiment = delta && (delta.invert ? delta.direction === "down" : delta.direction === "up") ? "positive" : "negative";
31
- return /* @__PURE__ */ jsxs("div", {
32
+ return /* @__PURE__ */ jsxs(onClick ? "button" : "div", {
32
33
  "data-slot": "stat",
33
- className: cn("flex min-w-0 flex-col gap-2.5", className),
34
+ type: onClick ? "button" : void 0,
35
+ onClick,
36
+ className: cn("flex min-w-0 flex-col gap-2.5", onClick && STAT_INTERACTIVE_CLASS, className),
34
37
  ...props,
35
38
  children: [/* @__PURE__ */ jsxs("div", {
36
39
  className: cn("flex min-w-0 items-baseline gap-x-2.5", compact && "gap-x-1.5"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alpic-ai/ui",
3
- "version": "0.0.0-staging.gfab65e2",
3
+ "version": "0.0.0-staging.gfabea7f",
4
4
  "description": "Alpic design system — shared UI components",
5
5
  "type": "module",
6
6
  "exports": {
@@ -64,8 +64,8 @@
64
64
  "@tailwindcss/postcss": "^4.3.3",
65
65
  "@types/react": "19.2.18",
66
66
  "@types/react-dom": "19.2.4",
67
- "lucide-react": "^1.28.0",
68
- "react-hook-form": "^7.84.0",
67
+ "lucide-react": "^1.30.0",
68
+ "react-hook-form": "^7.85.0",
69
69
  "shx": "^0.4.0",
70
70
  "sonner": "^2.0.7",
71
71
  "tailwindcss": "^4.3.3",
@@ -79,6 +79,6 @@
79
79
  "test:type": "tsgo --noEmit",
80
80
  "test:format": "biome check --error-on-warnings .",
81
81
  "dev:ui": "ladle serve",
82
- "publish:npm": "pnpm publish --tag \"${NPM_TAG}\" --access public --no-git-checks"
82
+ "publish:npm": "../../scripts/publish-if-new.sh"
83
83
  }
84
84
  }
@@ -2,16 +2,20 @@
2
2
 
3
3
  import * as React from "react";
4
4
 
5
- import { formatShare } from "../lib/chart";
6
5
  import type { ChartPaletteName } from "../lib/chart-palette";
7
6
  import { rampColor } from "../lib/chart-palette";
8
7
  import { cn } from "../lib/cn";
8
+ import { Badge, type BadgeVariant } from "./badge";
9
9
  import { useChartContext } from "./chart-container";
10
+ import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip";
10
11
 
11
12
  export interface BarListProps {
12
13
  data: ReadonlyArray<Record<string, string | number | null | undefined>>;
13
14
  index: string;
14
15
  dataKey?: string;
16
+ secondaryKey?: string;
17
+ secondaryVariantKey?: string;
18
+ titleKey?: string;
15
19
  maxItems?: number;
16
20
  palette?: ChartPaletteName;
17
21
  /** Renders bars in a single semantic hue (e.g. red for errors) rather than the palette ramp. */
@@ -23,6 +27,11 @@ export interface BarListProps {
23
27
  }
24
28
 
25
29
  const SEMANTIC_KEY = { error: "destructive", warning: "warning", success: "success" } as const;
30
+ const SECONDARY_BADGE_VARIANT: Record<string, BadgeVariant> = {
31
+ error: "error",
32
+ warning: "warning",
33
+ success: "success",
34
+ };
26
35
 
27
36
  const PLACEHOLDER_HEIGHT = 168;
28
37
  // Cap the ramp off its palest end so low-rank bars stay legible on a light card.
@@ -30,11 +39,16 @@ const RAMP_CEILING = 0.8;
30
39
  // The lightest semantic bar keeps this much hue (mixed toward white) so a long list never fades to near-invisible.
31
40
  const SEMANTIC_FLOOR = 0.62;
32
41
  const IN_FILL_SHADOW = "0 1px 2px rgb(0 0 0 / 0.28)";
42
+ const ROW_INTERACTIVE_CLASS =
43
+ "rounded-md text-left outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-card";
33
44
 
34
45
  function BarList({
35
46
  data,
36
47
  index,
37
48
  dataKey = "value",
49
+ secondaryKey,
50
+ secondaryVariantKey,
51
+ titleKey,
38
52
  maxItems,
39
53
  palette,
40
54
  semantic,
@@ -47,12 +61,15 @@ function BarList({
47
61
  const accent = semantic ? theme[SEMANTIC_KEY[semantic]] : null;
48
62
  const [active, setActive] = React.useState<number | null>(null);
49
63
 
50
- const total = React.useMemo(() => data.reduce((sum, row) => sum + (Number(row[dataKey]) || 0), 0), [data, dataKey]);
51
-
52
64
  const rows = React.useMemo(() => {
65
+ const optionalText = (row: (typeof data)[number], key: string | undefined) =>
66
+ key != null && row[key] != null ? String(row[key]) : undefined;
53
67
  const mapped = data.map((row) => ({
54
68
  name: String(row[index] ?? ""),
55
69
  value: Number(row[dataKey]) || 0,
70
+ secondary: optionalText(row, secondaryKey),
71
+ secondaryVariant: SECONDARY_BADGE_VARIANT[optionalText(row, secondaryVariantKey) ?? ""],
72
+ title: optionalText(row, titleKey),
56
73
  }));
57
74
  mapped.sort((lower, upper) => upper.value - lower.value);
58
75
  const capped = maxItems ? mapped.slice(0, maxItems) : mapped;
@@ -66,9 +83,10 @@ function BarList({
66
83
  : rampColor(paletteName, rankFraction * RAMP_CEILING),
67
84
  };
68
85
  });
69
- }, [data, index, dataKey, maxItems, paletteName, accent]);
86
+ }, [data, index, dataKey, secondaryKey, secondaryVariantKey, titleKey, maxItems, paletteName, accent]);
70
87
 
71
88
  const maxValue = rows.reduce((max, row) => (row.value > max ? row.value : max), 0);
89
+ const hasSecondary = rows.some((row) => row.secondary != null);
72
90
  const isEmpty = rows.length === 0;
73
91
  const formatName = (name: string) => (labelFormatter ? labelFormatter(name) : name);
74
92
 
@@ -108,13 +126,16 @@ function BarList({
108
126
  const fillWidth = `${fraction * 100}%`;
109
127
  const dimmed = active !== null && active !== slot;
110
128
  const isActive = active === slot;
111
- return (
112
- // biome-ignore lint/a11y/noStaticElementInteractions: decorative hover-sync; all values are visible without it
113
- <div
129
+ const hasTooltip = row.title != null;
130
+ // Only tooltip rows take focus, so a list without one keeps the tab order it had.
131
+ const Row = hasTooltip ? "button" : "div";
132
+ const bar = (
133
+ <Row
114
134
  key={row.name}
135
+ type={hasTooltip ? "button" : undefined}
115
136
  onMouseEnter={() => setActive(slot)}
116
137
  onMouseLeave={() => setActive(null)}
117
- className="flex items-center gap-3 py-1"
138
+ className={cn("flex items-center gap-3 py-1", hasTooltip && ROW_INTERACTIVE_CLASS)}
118
139
  >
119
140
  <div className="relative h-[30px] flex-1 overflow-hidden rounded-md bg-muted">
120
141
  <span className="pointer-events-none absolute inset-x-3 top-1/2 z-0 -translate-y-1/2 truncate type-text-xs font-medium text-foreground">
@@ -141,13 +162,33 @@ function BarList({
141
162
  </span>
142
163
  </div>
143
164
  </div>
144
- <span
145
- className="min-w-[4rem] shrink-0 whitespace-nowrap text-right font-mono text-text-xs tabular-nums motion-safe:transition-colors"
146
- style={{ color: isActive ? row.color : undefined }}
147
- >
148
- {isActive ? formatShare(total > 0 ? row.value / total : 0) : valueFormatter(row.value)}
149
- </span>
150
- </div>
165
+ <div className="flex shrink-0 items-center justify-end gap-2.5 whitespace-nowrap font-mono text-text-xs tabular-nums">
166
+ <span
167
+ className="min-w-[4rem] text-right motion-safe:transition-colors"
168
+ style={{ color: isActive ? row.color : undefined }}
169
+ >
170
+ {valueFormatter(row.value)}
171
+ </span>
172
+ {hasSecondary && (
173
+ <span className="flex min-w-[3.5rem] justify-end">
174
+ {row.secondary != null && (
175
+ <Badge variant={row.secondaryVariant ?? "secondary"} size="sm">
176
+ {row.secondary}
177
+ </Badge>
178
+ )}
179
+ </span>
180
+ )}
181
+ </div>
182
+ </Row>
183
+ );
184
+ if (row.title == null) {
185
+ return bar;
186
+ }
187
+ return (
188
+ <Tooltip key={row.name}>
189
+ <TooltipTrigger asChild>{bar}</TooltipTrigger>
190
+ <TooltipContent className="max-w-md text-pretty text-left">{row.title}</TooltipContent>
191
+ </Tooltip>
151
192
  );
152
193
  })}
153
194
  </div>
@@ -207,6 +207,7 @@ function ComboboxTrigger({ className, size, placeholder, children, ...props }: C
207
207
  const { multiple, value, values, open, onDeselect } = useComboboxContext();
208
208
 
209
209
  const isEmpty = multiple ? values.length === 0 : value === null || value === undefined;
210
+ const hasCustomMultipleValue = multiple && children !== undefined;
210
211
 
211
212
  return (
212
213
  <PopoverTrigger asChild>
@@ -220,12 +221,20 @@ function ComboboxTrigger({ className, size, placeholder, children, ...props }: C
220
221
  >
221
222
  <span
222
223
  className={cn(
223
- "flex flex-1 items-center gap-1.5 text-left",
224
- multiple && "flex-wrap",
225
- isEmpty && "text-placeholder",
224
+ "flex min-w-0 flex-1 items-center gap-1.5 text-left",
225
+ multiple && !hasCustomMultipleValue && "flex-wrap",
226
+ isEmpty && !hasCustomMultipleValue && "text-placeholder",
226
227
  )}
227
228
  >
228
- {isEmpty ? placeholder : multiple ? <ComboboxTags values={values} onDeselect={onDeselect} /> : children}
229
+ {hasCustomMultipleValue ? (
230
+ children
231
+ ) : isEmpty ? (
232
+ placeholder
233
+ ) : multiple ? (
234
+ <ComboboxTags values={values} onDeselect={onDeselect} />
235
+ ) : (
236
+ children
237
+ )}
229
238
  </span>
230
239
  <ChevronDownIcon className="size-5 shrink-0 text-muted-foreground" />
231
240
  </button>
@@ -29,13 +29,14 @@ export interface StatDelta {
29
29
  indicator?: "arrow" | "sign";
30
30
  }
31
31
 
32
- export interface StatProps extends React.ComponentProps<"div"> {
32
+ export interface StatProps extends Omit<React.HTMLAttributes<HTMLElement>, "onClick"> {
33
33
  value: React.ReactNode;
34
34
  unit?: string;
35
35
  delta?: StatDelta | null;
36
36
  sparkline?: number[] | Array<{ value: number }>;
37
37
  semantic?: "error" | "warning" | "success";
38
38
  compact?: boolean;
39
+ onClick?: () => void;
39
40
  }
40
41
 
41
42
  const toSparkData = (sparkline: StatProps["sparkline"]) =>
@@ -45,12 +46,22 @@ const toSparkData = (sparkline: StatProps["sparkline"]) =>
45
46
 
46
47
  const SEMANTIC_KEY = { error: "destructive", warning: "warning", success: "success" } as const;
47
48
 
48
- function Stat({ value, unit, delta, sparkline, semantic, compact = false, className, ...props }: StatProps) {
49
+ const STAT_INTERACTIVE_CLASS =
50
+ "rounded-sm text-left outline-none transition-opacity focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-card";
51
+
52
+ function Stat({ value, unit, delta, sparkline, semantic, compact = false, className, onClick, ...props }: StatProps) {
49
53
  const sentiment =
50
54
  delta && (delta.invert ? delta.direction === "down" : delta.direction === "up") ? "positive" : "negative";
55
+ const Root = onClick ? "button" : "div";
51
56
 
52
57
  return (
53
- <div data-slot="stat" className={cn("flex min-w-0 flex-col gap-2.5", className)} {...props}>
58
+ <Root
59
+ data-slot="stat"
60
+ type={onClick ? "button" : undefined}
61
+ onClick={onClick}
62
+ className={cn("flex min-w-0 flex-col gap-2.5", onClick && STAT_INTERACTIVE_CLASS, className)}
63
+ {...props}
64
+ >
54
65
  <div className={cn("flex min-w-0 items-baseline gap-x-2.5", compact && "gap-x-1.5")}>
55
66
  <span
56
67
  className={cn(
@@ -78,7 +89,7 @@ function Stat({ value, unit, delta, sparkline, semantic, compact = false, classN
78
89
  )}
79
90
  </div>
80
91
  <StatSparkline sparkline={sparkline} semantic={semantic} />
81
- </div>
92
+ </Root>
82
93
  );
83
94
  }
84
95