@alpic-ai/ui 1.163.0 → 1.165.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/dist/components/combobox.mjs +3 -2
- package/dist/components/form.d.mts +1 -1
- package/dist/components/stat.d.mts +3 -2
- package/dist/components/stat.mjs +6 -3
- package/package.json +3 -3
- package/src/components/area-chart.tsx +4 -1
- package/src/components/bar-chart.tsx +5 -1
- package/src/components/combobox.tsx +26 -7
- package/src/components/form.tsx +2 -0
- package/src/components/input-group.tsx +1 -0
- package/src/components/line-chart.tsx +1 -0
- package/src/components/sidebar.tsx +2 -0
- package/src/components/stat.tsx +15 -4
- package/src/hooks/use-chart-theme.ts +2 -0
- package/src/stories/area-chart.stories.tsx +2 -0
- package/src/stories/bar-chart.stories.tsx +2 -0
|
@@ -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 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.
|
|
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;
|
package/dist/components/stat.mjs
CHANGED
|
@@ -26,11 +26,14 @@ const SEMANTIC_KEY = {
|
|
|
26
26
|
warning: "warning",
|
|
27
27
|
success: "success"
|
|
28
28
|
};
|
|
29
|
-
|
|
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
|
-
|
|
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": "1.
|
|
3
|
+
"version": "1.165.0",
|
|
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.
|
|
68
|
-
"react-hook-form": "^7.
|
|
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",
|
|
@@ -133,7 +133,10 @@ function AreaChart({
|
|
|
133
133
|
) : (
|
|
134
134
|
<ResponsiveContainer width="100%" height="100%" initialDimension={{ width: 0, height }}>
|
|
135
135
|
<RechartsAreaChart
|
|
136
|
-
data={
|
|
136
|
+
data={
|
|
137
|
+
// biome-ignore lint/plugin/no-type-assertion: Recharts expects a string-indexed data shape.
|
|
138
|
+
data as Record<string, string | number>[]
|
|
139
|
+
}
|
|
137
140
|
stackOffset={variant === "expand" ? "expand" : "none"}
|
|
138
141
|
margin={margin}
|
|
139
142
|
>
|
|
@@ -176,7 +176,10 @@ function BarChart({
|
|
|
176
176
|
) : (
|
|
177
177
|
<ResponsiveContainer width="100%" height="100%" initialDimension={{ width: 0, height }}>
|
|
178
178
|
<RechartsBarChart
|
|
179
|
-
data={
|
|
179
|
+
data={
|
|
180
|
+
// biome-ignore lint/plugin/no-type-assertion: Recharts expects a string-indexed data shape.
|
|
181
|
+
data as Record<string, string | number>[]
|
|
182
|
+
}
|
|
180
183
|
stackOffset={variant === "expand" ? "expand" : "none"}
|
|
181
184
|
margin={margin}
|
|
182
185
|
barCategoryGap={barCategoryGap ?? (stacked ? "20%" : "16%")}
|
|
@@ -262,6 +265,7 @@ function BarChart({
|
|
|
262
265
|
data.map((row, rowIndex) => {
|
|
263
266
|
const roundedTop = stacked ? topSlotByRow[rowIndex] === slot : true;
|
|
264
267
|
const isPartial = partialLastBar && rowIndex === data.length - 1;
|
|
268
|
+
// biome-ignore lint/plugin/no-type-assertion: Recharts' Cell radius type does not model its tuple input.
|
|
265
269
|
const cellRadius = (roundedTop
|
|
266
270
|
? [BAR_RADIUS, BAR_RADIUS, 0, 0]
|
|
267
271
|
: [0, 0, 0, 0]) as unknown as number;
|
|
@@ -69,10 +69,12 @@ function Combobox(props: ComboboxProps) {
|
|
|
69
69
|
} = props;
|
|
70
70
|
|
|
71
71
|
const [uncontrolledSingleValue, setUncontrolledSingleValue] = useState<string | null>(
|
|
72
|
+
// biome-ignore lint/plugin/no-type-assertion: discriminated props cannot narrow through the separate multiple flag.
|
|
72
73
|
!multiple ? ((props as ComboboxSingleProps).defaultValue ?? null) : null,
|
|
73
74
|
);
|
|
74
75
|
|
|
75
76
|
const [uncontrolledMultiValue, setUncontrolledMultiValue] = useState<string[]>(
|
|
77
|
+
// biome-ignore lint/plugin/no-type-assertion: discriminated props cannot narrow through the separate multiple flag.
|
|
76
78
|
multiple ? ((props as ComboboxMultipleProps).defaultValue ?? []) : [],
|
|
77
79
|
);
|
|
78
80
|
|
|
@@ -93,13 +95,17 @@ function Combobox(props: ComboboxProps) {
|
|
|
93
95
|
|
|
94
96
|
const singleValue = multiple
|
|
95
97
|
? null
|
|
96
|
-
:
|
|
97
|
-
|
|
98
|
+
: // biome-ignore lint/plugin/no-type-assertion: discriminated props cannot narrow through the separate multiple flag.
|
|
99
|
+
(((props as ComboboxSingleProps).value !== undefined
|
|
100
|
+
? // biome-ignore lint/plugin/no-type-assertion: discriminated props cannot narrow through the separate multiple flag.
|
|
101
|
+
(props as ComboboxSingleProps).value
|
|
98
102
|
: uncontrolledSingleValue) ?? null);
|
|
99
103
|
|
|
100
104
|
const multiValues = multiple
|
|
101
|
-
?
|
|
102
|
-
|
|
105
|
+
? // biome-ignore lint/plugin/no-type-assertion: discriminated props cannot narrow through the separate multiple flag.
|
|
106
|
+
(props as ComboboxMultipleProps).value !== undefined
|
|
107
|
+
? // biome-ignore lint/plugin/no-type-assertion: discriminated props cannot narrow through the separate multiple flag.
|
|
108
|
+
((props as ComboboxMultipleProps).value as string[])
|
|
103
109
|
: uncontrolledMultiValue
|
|
104
110
|
: [];
|
|
105
111
|
|
|
@@ -114,9 +120,13 @@ function Combobox(props: ComboboxProps) {
|
|
|
114
120
|
);
|
|
115
121
|
|
|
116
122
|
// Extract specific callbacks to avoid depending on the entire props object
|
|
123
|
+
// biome-ignore lint/plugin/no-type-assertion: discriminated props cannot narrow through the separate multiple flag.
|
|
117
124
|
const onValueChangeSingle = !multiple ? (props as ComboboxSingleProps).onValueChange : undefined;
|
|
125
|
+
// biome-ignore lint/plugin/no-type-assertion: discriminated props cannot narrow through the separate multiple flag.
|
|
118
126
|
const onValueChangeMulti = multiple ? (props as ComboboxMultipleProps).onValueChange : undefined;
|
|
127
|
+
// biome-ignore lint/plugin/no-type-assertion: discriminated props cannot narrow through the separate multiple flag.
|
|
119
128
|
const isControlledSingle = !multiple && (props as ComboboxSingleProps).value !== undefined;
|
|
129
|
+
// biome-ignore lint/plugin/no-type-assertion: discriminated props cannot narrow through the separate multiple flag.
|
|
120
130
|
const isControlledMulti = multiple && (props as ComboboxMultipleProps).value !== undefined;
|
|
121
131
|
|
|
122
132
|
const onSelect = useCallback(
|
|
@@ -197,6 +207,7 @@ function ComboboxTrigger({ className, size, placeholder, children, ...props }: C
|
|
|
197
207
|
const { multiple, value, values, open, onDeselect } = useComboboxContext();
|
|
198
208
|
|
|
199
209
|
const isEmpty = multiple ? values.length === 0 : value === null || value === undefined;
|
|
210
|
+
const hasCustomMultipleValue = multiple && children !== undefined;
|
|
200
211
|
|
|
201
212
|
return (
|
|
202
213
|
<PopoverTrigger asChild>
|
|
@@ -211,11 +222,19 @@ function ComboboxTrigger({ className, size, placeholder, children, ...props }: C
|
|
|
211
222
|
<span
|
|
212
223
|
className={cn(
|
|
213
224
|
"flex flex-1 items-center gap-1.5 text-left",
|
|
214
|
-
multiple && "flex-wrap",
|
|
215
|
-
isEmpty && "text-placeholder",
|
|
225
|
+
multiple && !hasCustomMultipleValue && "flex-wrap",
|
|
226
|
+
isEmpty && !hasCustomMultipleValue && "text-placeholder",
|
|
216
227
|
)}
|
|
217
228
|
>
|
|
218
|
-
{
|
|
229
|
+
{hasCustomMultipleValue ? (
|
|
230
|
+
children
|
|
231
|
+
) : isEmpty ? (
|
|
232
|
+
placeholder
|
|
233
|
+
) : multiple ? (
|
|
234
|
+
<ComboboxTags values={values} onDeselect={onDeselect} />
|
|
235
|
+
) : (
|
|
236
|
+
children
|
|
237
|
+
)}
|
|
219
238
|
</span>
|
|
220
239
|
<ChevronDownIcon className="size-5 shrink-0 text-muted-foreground" />
|
|
221
240
|
</button>
|
package/src/components/form.tsx
CHANGED
|
@@ -31,6 +31,7 @@ type FormFieldContextValue<
|
|
|
31
31
|
name: TName;
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
+
// biome-ignore lint/plugin/no-type-assertion: consumers require a non-null context default before a provider is mounted.
|
|
34
35
|
const FormFieldContext = React.createContext<FormFieldContextValue>({} as FormFieldContextValue);
|
|
35
36
|
|
|
36
37
|
const FormField = <
|
|
@@ -74,6 +75,7 @@ type FormItemContextValue = {
|
|
|
74
75
|
id: string;
|
|
75
76
|
};
|
|
76
77
|
|
|
78
|
+
// biome-ignore lint/plugin/no-type-assertion: consumers require a non-null context default before a provider is mounted.
|
|
77
79
|
const FormItemContext = React.createContext<FormItemContextValue>({} as FormItemContextValue);
|
|
78
80
|
|
|
79
81
|
function FormItem({ className, ...props }: React.ComponentProps<"div">) {
|
|
@@ -68,6 +68,7 @@ function InputGroupAddon({
|
|
|
68
68
|
data-align={align}
|
|
69
69
|
className={cn(inputGroupAddonVariants({ align }), className)}
|
|
70
70
|
onClick={(event) => {
|
|
71
|
+
// biome-ignore lint/plugin/no-type-assertion: click targets are DOM elements for this handler.
|
|
71
72
|
if ((event.target as HTMLElement).closest("button")) {
|
|
72
73
|
return;
|
|
73
74
|
}
|
|
@@ -111,6 +111,7 @@ function LineChart({
|
|
|
111
111
|
</div>
|
|
112
112
|
) : (
|
|
113
113
|
<ResponsiveContainer width="100%" height="100%" initialDimension={{ width: 0, height }}>
|
|
114
|
+
{/* biome-ignore lint/plugin/no-type-assertion: Recharts expects a string-indexed data shape. */}
|
|
114
115
|
<RechartsLineChart data={data as Record<string, string | number>[]} margin={margin}>
|
|
115
116
|
<CartesianGrid vertical={false} stroke={theme.grid} strokeDasharray="2 4" />
|
|
116
117
|
<XAxis
|
|
@@ -127,6 +127,7 @@ function SidebarProvider({
|
|
|
127
127
|
<div
|
|
128
128
|
data-slot="sidebar-wrapper"
|
|
129
129
|
style={
|
|
130
|
+
// biome-ignore lint/plugin/no-type-assertion: CSS custom properties are not included in React's style type.
|
|
130
131
|
{
|
|
131
132
|
"--sidebar-width": SIDEBAR_WIDTH,
|
|
132
133
|
"--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
|
|
@@ -195,6 +196,7 @@ function Sidebar({
|
|
|
195
196
|
data-mobile="true"
|
|
196
197
|
className="bg-sidebar-surface text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden"
|
|
197
198
|
style={
|
|
199
|
+
// biome-ignore lint/plugin/no-type-assertion: CSS custom properties are not included in React's style type.
|
|
198
200
|
{
|
|
199
201
|
"--sidebar-width": SIDEBAR_WIDTH_MOBILE,
|
|
200
202
|
} as React.CSSProperties
|
package/src/components/stat.tsx
CHANGED
|
@@ -29,13 +29,14 @@ export interface StatDelta {
|
|
|
29
29
|
indicator?: "arrow" | "sign";
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
export interface StatProps extends React.
|
|
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
|
-
|
|
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
|
-
<
|
|
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
|
-
</
|
|
92
|
+
</Root>
|
|
82
93
|
);
|
|
83
94
|
}
|
|
84
95
|
|
|
@@ -54,8 +54,10 @@ const readTheme = (): ChartTheme => {
|
|
|
54
54
|
const styles = window.getComputedStyle(document.documentElement);
|
|
55
55
|
const entries = Object.entries(TOKEN_MAP).map(([key, token]) => {
|
|
56
56
|
const value = styles.getPropertyValue(token).trim();
|
|
57
|
+
// biome-ignore lint/plugin/no-type-assertion: token keys correspond to the chart theme's required color keys.
|
|
57
58
|
return [key, value || FALLBACK[key as keyof ChartTheme]] as const;
|
|
58
59
|
});
|
|
60
|
+
// biome-ignore lint/plugin/no-type-assertion: Object.fromEntries cannot preserve the statically known chart-theme keys.
|
|
59
61
|
const colors = Object.fromEntries(entries) as unknown as Omit<ChartTheme, "isDark">;
|
|
60
62
|
return { ...colors, isDark: document.documentElement.classList.contains("dark") };
|
|
61
63
|
};
|
|
@@ -82,9 +82,11 @@ const fmtK = (value: number) => (value >= 1000 ? `${(value / 1000).toFixed(value
|
|
|
82
82
|
const ms = (value: number) => `${value}ms`;
|
|
83
83
|
|
|
84
84
|
const sessionsTotal = stacked.reduce(
|
|
85
|
+
// biome-ignore lint/plugin/no-type-assertion: each client key is populated with a numeric sample in this fixture.
|
|
85
86
|
(sum, row) => sum + CLIENTS.reduce((acc, client) => acc + (row[client] as number), 0),
|
|
86
87
|
0,
|
|
87
88
|
);
|
|
89
|
+
// biome-ignore lint/plugin/no-type-assertion: each client key is populated with a numeric sample in this fixture.
|
|
88
90
|
const sessionsSpark = stacked.map((row) => CLIENTS.reduce((acc, client) => acc + (row[client] as number), 0));
|
|
89
91
|
|
|
90
92
|
const latencyPeak = latency.reduce((best, row) => (row.p95 > best.p95 ? row : best));
|
|
@@ -70,9 +70,11 @@ const fmtK = (value: number) => (value >= 1000 ? `${(value / 1000).toFixed(value
|
|
|
70
70
|
const ms = (value: number) => `${value}ms`;
|
|
71
71
|
|
|
72
72
|
const sessionsTotal = stacked.reduce(
|
|
73
|
+
// biome-ignore lint/plugin/no-type-assertion: each client key is populated with a numeric sample in this fixture.
|
|
73
74
|
(sum, row) => sum + CLIENTS.reduce((acc, client) => acc + (row[client] as number), 0),
|
|
74
75
|
0,
|
|
75
76
|
);
|
|
77
|
+
// biome-ignore lint/plugin/no-type-assertion: each client key is populated with a numeric sample in this fixture.
|
|
76
78
|
const sessionsSpark = stacked.map((row) => CLIENTS.reduce((acc, client) => acc + (row[client] as number), 0));
|
|
77
79
|
|
|
78
80
|
const errorsPeak = errors.reduce((best, row) => (row.mcp + row.tool > best.mcp + best.tool ? row : best));
|