@timbal-ai/timbal-react 1.6.1 → 1.7.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/CHANGELOG.md +24 -0
- package/dist/app.cjs +138 -73
- package/dist/app.d.cts +3 -3
- package/dist/app.d.ts +3 -3
- package/dist/app.esm.js +5 -5
- package/dist/{chart-artifact-Dpt4t5sf.d.cts → chart-artifact-CuTiCITz.d.cts} +221 -15
- package/dist/{chart-artifact-BYl5C-dk.d.ts → chart-artifact-U-x0UNJm.d.ts} +221 -15
- package/dist/chat.esm.js +3 -3
- package/dist/{chunk-YYEI6XME.esm.js → chunk-22KWC2LS.esm.js} +5 -5
- package/dist/{chunk-MBS7XHV2.esm.js → chunk-45NXD3IG.esm.js} +3 -3
- package/dist/{chunk-24B4I4XC.esm.js → chunk-64RHAJVG.esm.js} +1 -1
- package/dist/{chunk-WQIQW7EM.esm.js → chunk-7AGIAQE6.esm.js} +1 -1
- package/dist/{chunk-NO5AWNWT.esm.js → chunk-7WU3IKAN.esm.js} +1 -1
- package/dist/{chunk-UVPXH4MB.esm.js → chunk-M5IBJBEY.esm.js} +80 -21
- package/dist/{chunk-HSL36SJ4.esm.js → chunk-PMMI7LBV.esm.js} +20 -8
- package/dist/{chunk-TMP7RIA7.esm.js → chunk-VKXOHVDE.esm.js} +2 -2
- package/dist/{chunk-ELEY66OH.esm.js → chunk-XOCOZU7J.esm.js} +11 -1
- package/dist/cli/timbal-ui-lint.mjs +32 -1
- package/dist/index.cjs +274 -198
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.esm.js +9 -9
- package/dist/{kanban-FFBeaZPS.d.cts → kanban-BQxWliCS.d.cts} +17 -0
- package/dist/{kanban-FFBeaZPS.d.ts → kanban-BQxWliCS.d.ts} +17 -0
- package/dist/studio.cjs +104 -85
- package/dist/studio.esm.js +6 -6
- package/dist/ui.cjs +6 -6
- package/dist/ui.d.cts +1 -1
- package/dist/ui.d.ts +1 -1
- package/dist/ui.esm.js +4 -4
- package/package.json +1 -1
|
@@ -2,9 +2,11 @@ import {
|
|
|
2
2
|
SIDEBAR_INSET_PX_EXPANDED,
|
|
3
3
|
STORAGE_KEYS,
|
|
4
4
|
ShellInsetProvider,
|
|
5
|
+
ShellNavProvider,
|
|
5
6
|
studioChromeShellStyle,
|
|
6
|
-
studioSidebarWidthTransition
|
|
7
|
-
|
|
7
|
+
studioSidebarWidthTransition,
|
|
8
|
+
useOptionalShellNav
|
|
9
|
+
} from "./chunk-XOCOZU7J.esm.js";
|
|
8
10
|
import {
|
|
9
11
|
ChartArtifactView,
|
|
10
12
|
LineAreaChart,
|
|
@@ -16,7 +18,7 @@ import {
|
|
|
16
18
|
studioIntegrationCardClass,
|
|
17
19
|
studioTopbarPillHeightClass,
|
|
18
20
|
toNum
|
|
19
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-7AGIAQE6.esm.js";
|
|
20
22
|
import {
|
|
21
23
|
Checkbox,
|
|
22
24
|
CopyButton,
|
|
@@ -29,7 +31,7 @@ import {
|
|
|
29
31
|
SelectTrigger,
|
|
30
32
|
SelectValue,
|
|
31
33
|
Skeleton
|
|
32
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-7WU3IKAN.esm.js";
|
|
33
35
|
import {
|
|
34
36
|
PillSegmentedTabs
|
|
35
37
|
} from "./chunk-R4RQT2XQ.esm.js";
|
|
@@ -45,7 +47,7 @@ import {
|
|
|
45
47
|
TIMBAL_V2_SWITCH_TRACK_OFF,
|
|
46
48
|
TimbalV2Button,
|
|
47
49
|
controlClass
|
|
48
|
-
} from "./chunk-
|
|
50
|
+
} from "./chunk-45NXD3IG.esm.js";
|
|
49
51
|
import {
|
|
50
52
|
cn
|
|
51
53
|
} from "./chunk-EDEKQYSU.esm.js";
|
|
@@ -178,6 +180,13 @@ var HOUSE_RULES = [
|
|
|
178
180
|
slop: `<span className="text-blue-600 bg-green-50">`,
|
|
179
181
|
good: `<span className="text-primary bg-muted">`
|
|
180
182
|
},
|
|
183
|
+
{
|
|
184
|
+
id: "chart-token-color",
|
|
185
|
+
rule: "Pass chart and theme color tokens directly (var(--chart-1)) \u2014 never wrap them in hsl(), rgb(), or oklch().",
|
|
186
|
+
why: "The --chart-N and theme tokens are already full OKLCH colors. Wrapping them in hsl()/rgb() is invalid CSS, so the chart renders empty/uncolored \u2014 and the build still passes, so it's a silent runtime bug.",
|
|
187
|
+
slop: `<Cell fill="hsl(var(--chart-1))" />`,
|
|
188
|
+
good: `<Cell fill="var(--chart-1)" />`
|
|
189
|
+
},
|
|
181
190
|
{
|
|
182
191
|
id: "no-decorative-icons",
|
|
183
192
|
rule: "Icons must earn their place (action, nav, or status). Never add an icon beside a label that already says the thing.",
|
|
@@ -579,6 +588,7 @@ var RAW_COLOR_RE = new RegExp(
|
|
|
579
588
|
"g"
|
|
580
589
|
);
|
|
581
590
|
var COLOR_LITERAL_RE = /#[0-9a-fA-F]{3,8}\b|\b(?:oklch|rgba?|hsla?)\s*\(/g;
|
|
591
|
+
var COLOR_FN_WRAPPING_VAR_RE = /\b(?:hsl|hsla|rgb|rgba|oklch|oklab|lab|lch|hwb|color)\s*\(\s*var\(\s*--/i;
|
|
582
592
|
var INLINE_STYLE_COLOR_RE = /style=\{\{[^}]*\b(?:color|background|backgroundColor|borderColor|fill|stroke)\b/;
|
|
583
593
|
var BOLD_VALUE_RE = /text-(?:xl|2xl|3xl|4xl|5xl|6xl)[^"'`]*\bfont-(?:bold|extrabold|black|semibold)|font-(?:bold|extrabold|black|semibold)[^"'`]*text-(?:xl|2xl|3xl|4xl|5xl|6xl)/;
|
|
584
594
|
var GRADIENT_RE = /\bbg-(?:gradient|linear|radial|conic)-/;
|
|
@@ -601,11 +611,26 @@ var RESERVED_GRADIENT_SET = new Set(RESERVED_GRADIENT_TOKENS);
|
|
|
601
611
|
function stripVariants(util) {
|
|
602
612
|
return util.replace(/^(?:[a-z-]+:)*/, "");
|
|
603
613
|
}
|
|
614
|
+
function describeArg(value) {
|
|
615
|
+
if (value === null) return "null";
|
|
616
|
+
if (Array.isArray(value)) return "an array";
|
|
617
|
+
const t = typeof value;
|
|
618
|
+
if (t === "object") {
|
|
619
|
+
const keys = Object.keys(value).slice(0, 4);
|
|
620
|
+
return keys.length ? `an object with keys { ${keys.join(", ")} }` : "an object";
|
|
621
|
+
}
|
|
622
|
+
return `a ${t}`;
|
|
623
|
+
}
|
|
604
624
|
function isCommentOrImport(line) {
|
|
605
625
|
const t = line.trim();
|
|
606
626
|
return t.startsWith("//") || t.startsWith("*") || t.startsWith("/*") || t.startsWith("import ") || t.startsWith("export ");
|
|
607
627
|
}
|
|
608
628
|
function lintGeneratedUi(source, options = {}) {
|
|
629
|
+
if (typeof source !== "string") {
|
|
630
|
+
throw new TypeError(
|
|
631
|
+
`lintGeneratedUi(source, options?) expects the generated code as a string, but received ${describeArg(source)}. Pass the raw .tsx source \u2014 lintGeneratedUi(code) \u2014 not an object like { filename, source } and not a previous LintResult.`
|
|
632
|
+
);
|
|
633
|
+
}
|
|
609
634
|
const maxIcons = options.maxIconsPerView ?? SLOP_BUDGETS.maxIconsPerView;
|
|
610
635
|
const maxRowDividers = options.maxRowDividers ?? SLOP_BUDGETS.maxRowDividers;
|
|
611
636
|
const findings = [];
|
|
@@ -685,7 +710,17 @@ function lintGeneratedUi(source, options = {}) {
|
|
|
685
710
|
});
|
|
686
711
|
}
|
|
687
712
|
}
|
|
688
|
-
const
|
|
713
|
+
const wrapsTokenInColorFn = COLOR_FN_WRAPPING_VAR_RE.test(line);
|
|
714
|
+
if (wrapsTokenInColorFn) {
|
|
715
|
+
findings.push({
|
|
716
|
+
rule: "chart-token-color-fn",
|
|
717
|
+
severity: "error",
|
|
718
|
+
line: lineNo,
|
|
719
|
+
message: "Color function wrapping a token (e.g. hsl(var(--chart-1))). The --chart-N and theme tokens are already OKLCH colors \u2014 wrapping them in hsl()/rgb() is invalid CSS and renders an empty/uncolored chart (the build still passes). Pass the token directly: var(--chart-1), or let the app-kit charts use --chart-N automatically.",
|
|
720
|
+
snippet: line.trim().slice(0, 120)
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
const literals = wrapsTokenInColorFn ? null : line.match(COLOR_LITERAL_RE);
|
|
689
724
|
if (literals) {
|
|
690
725
|
findings.push({
|
|
691
726
|
rule: "color-literal",
|
|
@@ -836,6 +871,11 @@ function lintGeneratedUi(source, options = {}) {
|
|
|
836
871
|
};
|
|
837
872
|
}
|
|
838
873
|
function formatLintReport(findings) {
|
|
874
|
+
if (!Array.isArray(findings)) {
|
|
875
|
+
throw new TypeError(
|
|
876
|
+
`formatLintReport(findings) expects the findings array, but received ${describeArg(findings)}. Pass result.findings \u2014 formatLintReport(lintGeneratedUi(code).findings) \u2014 not the whole LintResult.`
|
|
877
|
+
);
|
|
878
|
+
}
|
|
839
879
|
if (findings.length === 0) return "";
|
|
840
880
|
const lines = findings.slice().sort((a, b) => a.line - b.line).map((f) => {
|
|
841
881
|
const tag = f.severity === "error" ? "ERROR" : "warn ";
|
|
@@ -2031,20 +2071,20 @@ function useAppShellChat() {
|
|
|
2031
2071
|
}
|
|
2032
2072
|
|
|
2033
2073
|
// src/app/layout/app-shell-nav-context.tsx
|
|
2034
|
-
|
|
2035
|
-
var
|
|
2036
|
-
|
|
2074
|
+
var AppShellNavProvider = ShellNavProvider;
|
|
2075
|
+
var NAV_NOOP = {
|
|
2076
|
+
open: false,
|
|
2077
|
+
setOpen: () => {
|
|
2078
|
+
},
|
|
2079
|
+
toggle: () => {
|
|
2080
|
+
}
|
|
2081
|
+
};
|
|
2037
2082
|
function useAppShellNav() {
|
|
2038
|
-
return
|
|
2039
|
-
open: false,
|
|
2040
|
-
setOpen: () => {
|
|
2041
|
-
},
|
|
2042
|
-
toggle: () => {
|
|
2043
|
-
}
|
|
2044
|
-
};
|
|
2083
|
+
return useOptionalShellNav() ?? NAV_NOOP;
|
|
2045
2084
|
}
|
|
2046
2085
|
|
|
2047
2086
|
// src/app/layout/AppShell.tsx
|
|
2087
|
+
import { MenuIcon } from "lucide-react";
|
|
2048
2088
|
import { motion, useReducedMotion } from "motion/react";
|
|
2049
2089
|
import { useCallback, useEffect, useMemo, useState as useState2 } from "react";
|
|
2050
2090
|
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
@@ -2054,6 +2094,12 @@ var floatingTriggerClass = cn(
|
|
|
2054
2094
|
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
|
|
2055
2095
|
"bottom-6 right-6 max-sm:bottom-4 max-sm:right-4"
|
|
2056
2096
|
);
|
|
2097
|
+
var floatingNavTriggerClass = cn(
|
|
2098
|
+
"aui-app-shell-nav-trigger-fixed fixed left-4 top-4 z-30 inline-flex size-10 items-center justify-center rounded-xl md:hidden",
|
|
2099
|
+
"border border-border/60 bg-card/85 text-foreground shadow-card-elevated backdrop-blur-xl supports-backdrop-filter:bg-card/75",
|
|
2100
|
+
"transition-colors hover:bg-card",
|
|
2101
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
|
|
2102
|
+
);
|
|
2057
2103
|
var floatingPanelClass = cn(
|
|
2058
2104
|
"aui-app-shell-chat-float fixed z-50 flex flex-col overflow-hidden rounded-2xl border border-border/60 shadow-card-elevated",
|
|
2059
2105
|
"bg-card/85 backdrop-blur-xl supports-backdrop-filter:bg-card/75",
|
|
@@ -2144,12 +2190,14 @@ var AppShell = ({
|
|
|
2144
2190
|
navOpen: navOpenProp,
|
|
2145
2191
|
defaultNavOpen = false,
|
|
2146
2192
|
onNavOpenChange,
|
|
2193
|
+
mobileSidebarTrigger = "auto",
|
|
2147
2194
|
className,
|
|
2148
2195
|
mainClassName,
|
|
2149
2196
|
contentFill = false
|
|
2150
2197
|
}) => {
|
|
2151
2198
|
const topbarContent = topbar ?? header;
|
|
2152
2199
|
const hasChat = Boolean(chat);
|
|
2200
|
+
const showFloatingNavTrigger = Boolean(sidebar) && mobileSidebarTrigger !== "none" && !(mobileSidebarTrigger === "topbar") && !topbarContent;
|
|
2153
2201
|
const [uncontrolledNavOpen, setUncontrolledNavOpen] = useState2(defaultNavOpen);
|
|
2154
2202
|
const isNavControlled = navOpenProp !== void 0;
|
|
2155
2203
|
const navOpen = isNavControlled ? navOpenProp : uncontrolledNavOpen;
|
|
@@ -2209,6 +2257,17 @@ var AppShell = ({
|
|
|
2209
2257
|
style: studioChromeShellStyle,
|
|
2210
2258
|
children: [
|
|
2211
2259
|
sidebar,
|
|
2260
|
+
showFloatingNavTrigger && !navOpen ? /* @__PURE__ */ jsx7(
|
|
2261
|
+
"button",
|
|
2262
|
+
{
|
|
2263
|
+
type: "button",
|
|
2264
|
+
"aria-label": "Open navigation",
|
|
2265
|
+
"aria-expanded": false,
|
|
2266
|
+
onClick: () => setNavOpen(true),
|
|
2267
|
+
className: floatingNavTriggerClass,
|
|
2268
|
+
children: /* @__PURE__ */ jsx7(MenuIcon, { className: "size-5", "aria-hidden": true })
|
|
2269
|
+
}
|
|
2270
|
+
) : null,
|
|
2212
2271
|
sidebar && navOpen ? /* @__PURE__ */ jsx7(
|
|
2213
2272
|
"button",
|
|
2214
2273
|
{
|
|
@@ -2293,7 +2352,7 @@ var AppShellChatTrigger = ({
|
|
|
2293
2352
|
};
|
|
2294
2353
|
|
|
2295
2354
|
// src/app/layout/AppShellSidebarTrigger.tsx
|
|
2296
|
-
import { MenuIcon } from "lucide-react";
|
|
2355
|
+
import { MenuIcon as MenuIcon2 } from "lucide-react";
|
|
2297
2356
|
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
2298
2357
|
var AppShellSidebarTrigger = ({
|
|
2299
2358
|
label = "Open navigation",
|
|
@@ -2312,7 +2371,7 @@ var AppShellSidebarTrigger = ({
|
|
|
2312
2371
|
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/10",
|
|
2313
2372
|
className
|
|
2314
2373
|
),
|
|
2315
|
-
children: /* @__PURE__ */ jsx9(
|
|
2374
|
+
children: /* @__PURE__ */ jsx9(MenuIcon2, { className: "size-5", "aria-hidden": true })
|
|
2316
2375
|
}
|
|
2317
2376
|
);
|
|
2318
2377
|
};
|
|
@@ -2450,9 +2509,9 @@ var Stack = ({
|
|
|
2450
2509
|
);
|
|
2451
2510
|
|
|
2452
2511
|
// src/app/copilot/app-copilot-context.tsx
|
|
2453
|
-
import { createContext as
|
|
2512
|
+
import { createContext as createContext3, useContext as useContext3 } from "react";
|
|
2454
2513
|
import { jsx as jsx14 } from "react/jsx-runtime";
|
|
2455
|
-
var AppCopilotContext =
|
|
2514
|
+
var AppCopilotContext = createContext3(null);
|
|
2456
2515
|
var AppCopilotProvider = ({
|
|
2457
2516
|
value,
|
|
2458
2517
|
children
|
|
@@ -2460,7 +2519,7 @@ var AppCopilotProvider = ({
|
|
|
2460
2519
|
return /* @__PURE__ */ jsx14(AppCopilotContext.Provider, { value, children });
|
|
2461
2520
|
};
|
|
2462
2521
|
function useAppCopilotContext() {
|
|
2463
|
-
return
|
|
2522
|
+
return useContext3(AppCopilotContext) ?? {};
|
|
2464
2523
|
}
|
|
2465
2524
|
|
|
2466
2525
|
// src/app/chat/AppChatPanel.tsx
|
|
@@ -16,11 +16,12 @@ import {
|
|
|
16
16
|
studioSidebarEntriesTransition,
|
|
17
17
|
studioSidebarEntryItemVariants,
|
|
18
18
|
studioSidebarWidthTransition,
|
|
19
|
+
useOptionalShellNav,
|
|
19
20
|
useShellInsetReporter
|
|
20
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-XOCOZU7J.esm.js";
|
|
21
22
|
import {
|
|
22
23
|
WorkforceSelector
|
|
23
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-VKXOHVDE.esm.js";
|
|
24
25
|
import {
|
|
25
26
|
Composer,
|
|
26
27
|
TimbalChat,
|
|
@@ -43,7 +44,7 @@ import {
|
|
|
43
44
|
studioTopbarIconPillClass,
|
|
44
45
|
studioTopbarPillHeightClass,
|
|
45
46
|
useTimbalRuntime
|
|
46
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-7AGIAQE6.esm.js";
|
|
47
48
|
import {
|
|
48
49
|
DropdownMenu,
|
|
49
50
|
DropdownMenuContent,
|
|
@@ -51,7 +52,7 @@ import {
|
|
|
51
52
|
DropdownMenuLabel,
|
|
52
53
|
DropdownMenuSeparator,
|
|
53
54
|
DropdownMenuTrigger
|
|
54
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-64RHAJVG.esm.js";
|
|
55
56
|
import {
|
|
56
57
|
PillSegmentedTabs
|
|
57
58
|
} from "./chunk-R4RQT2XQ.esm.js";
|
|
@@ -63,7 +64,7 @@ import {
|
|
|
63
64
|
Tooltip,
|
|
64
65
|
TooltipContent,
|
|
65
66
|
TooltipTrigger
|
|
66
|
-
} from "./chunk-
|
|
67
|
+
} from "./chunk-45NXD3IG.esm.js";
|
|
67
68
|
import {
|
|
68
69
|
cn
|
|
69
70
|
} from "./chunk-EDEKQYSU.esm.js";
|
|
@@ -1119,14 +1120,25 @@ var StudioSidebar = ({
|
|
|
1119
1120
|
window.addEventListener("resize", onResize);
|
|
1120
1121
|
return () => window.removeEventListener("resize", onResize);
|
|
1121
1122
|
}, [mobileBreakpointPx]);
|
|
1123
|
+
const shellNav = useOptionalShellNav();
|
|
1124
|
+
const isControlled = mobileOpenProp !== void 0;
|
|
1125
|
+
const usingShellNav = !isControlled && shellNav !== null;
|
|
1122
1126
|
const [internalMobileOpen, setInternalMobileOpen] = useState5(false);
|
|
1123
|
-
const mobileOpen = mobileOpenProp
|
|
1127
|
+
const mobileOpen = isControlled ? mobileOpenProp : usingShellNav ? shellNav.open : internalMobileOpen;
|
|
1124
1128
|
const setMobileOpen = useCallback3(
|
|
1125
1129
|
(next) => {
|
|
1126
|
-
if (
|
|
1130
|
+
if (isControlled) {
|
|
1131
|
+
onMobileOpenChangeProp?.(next);
|
|
1132
|
+
return;
|
|
1133
|
+
}
|
|
1134
|
+
if (usingShellNav) {
|
|
1135
|
+
shellNav.setOpen(next);
|
|
1136
|
+
} else {
|
|
1137
|
+
setInternalMobileOpen(next);
|
|
1138
|
+
}
|
|
1127
1139
|
onMobileOpenChangeProp?.(next);
|
|
1128
1140
|
},
|
|
1129
|
-
[
|
|
1141
|
+
[isControlled, usingShellNav, shellNav, onMobileOpenChangeProp]
|
|
1130
1142
|
);
|
|
1131
1143
|
const handleSelect = useCallback3(
|
|
1132
1144
|
(id) => {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
studioTopbarPillHeightClass
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-7AGIAQE6.esm.js";
|
|
4
4
|
import {
|
|
5
5
|
controlSurfaceClass
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-45NXD3IG.esm.js";
|
|
7
7
|
import {
|
|
8
8
|
cn
|
|
9
9
|
} from "./chunk-EDEKQYSU.esm.js";
|
|
@@ -119,6 +119,14 @@ function useShellInsetReporter() {
|
|
|
119
119
|
return useContext(ShellInsetContext);
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
// src/layout/shell-nav-context.tsx
|
|
123
|
+
import { createContext as createContext2, useContext as useContext2 } from "react";
|
|
124
|
+
var ShellNavContext = createContext2(null);
|
|
125
|
+
var ShellNavProvider = ShellNavContext.Provider;
|
|
126
|
+
function useOptionalShellNav() {
|
|
127
|
+
return useContext2(ShellNavContext);
|
|
128
|
+
}
|
|
129
|
+
|
|
122
130
|
export {
|
|
123
131
|
SIDEBAR_WIDTH_PX,
|
|
124
132
|
SIDEBAR_WIDTH_COLLAPSED_PX,
|
|
@@ -138,5 +146,7 @@ export {
|
|
|
138
146
|
studioSidebarDrawerTransition,
|
|
139
147
|
studioSidebarBackdropTransition,
|
|
140
148
|
ShellInsetProvider,
|
|
141
|
-
useShellInsetReporter
|
|
149
|
+
useShellInsetReporter,
|
|
150
|
+
ShellNavProvider,
|
|
151
|
+
useOptionalShellNav
|
|
142
152
|
};
|
|
@@ -86,6 +86,7 @@ var RAW_COLOR_RE = new RegExp(
|
|
|
86
86
|
"g"
|
|
87
87
|
);
|
|
88
88
|
var COLOR_LITERAL_RE = /#[0-9a-fA-F]{3,8}\b|\b(?:oklch|rgba?|hsla?)\s*\(/g;
|
|
89
|
+
var COLOR_FN_WRAPPING_VAR_RE = /\b(?:hsl|hsla|rgb|rgba|oklch|oklab|lab|lch|hwb|color)\s*\(\s*var\(\s*--/i;
|
|
89
90
|
var INLINE_STYLE_COLOR_RE = /style=\{\{[^}]*\b(?:color|background|backgroundColor|borderColor|fill|stroke)\b/;
|
|
90
91
|
var BOLD_VALUE_RE = /text-(?:xl|2xl|3xl|4xl|5xl|6xl)[^"'`]*\bfont-(?:bold|extrabold|black|semibold)|font-(?:bold|extrabold|black|semibold)[^"'`]*text-(?:xl|2xl|3xl|4xl|5xl|6xl)/;
|
|
91
92
|
var GRADIENT_RE = /\bbg-(?:gradient|linear|radial|conic)-/;
|
|
@@ -108,11 +109,26 @@ var RESERVED_GRADIENT_SET = new Set(RESERVED_GRADIENT_TOKENS);
|
|
|
108
109
|
function stripVariants(util) {
|
|
109
110
|
return util.replace(/^(?:[a-z-]+:)*/, "");
|
|
110
111
|
}
|
|
112
|
+
function describeArg(value) {
|
|
113
|
+
if (value === null) return "null";
|
|
114
|
+
if (Array.isArray(value)) return "an array";
|
|
115
|
+
const t = typeof value;
|
|
116
|
+
if (t === "object") {
|
|
117
|
+
const keys = Object.keys(value).slice(0, 4);
|
|
118
|
+
return keys.length ? `an object with keys { ${keys.join(", ")} }` : "an object";
|
|
119
|
+
}
|
|
120
|
+
return `a ${t}`;
|
|
121
|
+
}
|
|
111
122
|
function isCommentOrImport(line) {
|
|
112
123
|
const t = line.trim();
|
|
113
124
|
return t.startsWith("//") || t.startsWith("*") || t.startsWith("/*") || t.startsWith("import ") || t.startsWith("export ");
|
|
114
125
|
}
|
|
115
126
|
function lintGeneratedUi(source, options = {}) {
|
|
127
|
+
if (typeof source !== "string") {
|
|
128
|
+
throw new TypeError(
|
|
129
|
+
`lintGeneratedUi(source, options?) expects the generated code as a string, but received ${describeArg(source)}. Pass the raw .tsx source \u2014 lintGeneratedUi(code) \u2014 not an object like { filename, source } and not a previous LintResult.`
|
|
130
|
+
);
|
|
131
|
+
}
|
|
116
132
|
const maxIcons = options.maxIconsPerView ?? SLOP_BUDGETS.maxIconsPerView;
|
|
117
133
|
const maxRowDividers = options.maxRowDividers ?? SLOP_BUDGETS.maxRowDividers;
|
|
118
134
|
const findings = [];
|
|
@@ -192,7 +208,17 @@ function lintGeneratedUi(source, options = {}) {
|
|
|
192
208
|
});
|
|
193
209
|
}
|
|
194
210
|
}
|
|
195
|
-
const
|
|
211
|
+
const wrapsTokenInColorFn = COLOR_FN_WRAPPING_VAR_RE.test(line);
|
|
212
|
+
if (wrapsTokenInColorFn) {
|
|
213
|
+
findings.push({
|
|
214
|
+
rule: "chart-token-color-fn",
|
|
215
|
+
severity: "error",
|
|
216
|
+
line: lineNo,
|
|
217
|
+
message: "Color function wrapping a token (e.g. hsl(var(--chart-1))). The --chart-N and theme tokens are already OKLCH colors \u2014 wrapping them in hsl()/rgb() is invalid CSS and renders an empty/uncolored chart (the build still passes). Pass the token directly: var(--chart-1), or let the app-kit charts use --chart-N automatically.",
|
|
218
|
+
snippet: line.trim().slice(0, 120)
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
const literals = wrapsTokenInColorFn ? null : line.match(COLOR_LITERAL_RE);
|
|
196
222
|
if (literals) {
|
|
197
223
|
findings.push({
|
|
198
224
|
rule: "color-literal",
|
|
@@ -343,6 +369,11 @@ function lintGeneratedUi(source, options = {}) {
|
|
|
343
369
|
};
|
|
344
370
|
}
|
|
345
371
|
function formatLintReport(findings) {
|
|
372
|
+
if (!Array.isArray(findings)) {
|
|
373
|
+
throw new TypeError(
|
|
374
|
+
`formatLintReport(findings) expects the findings array, but received ${describeArg(findings)}. Pass result.findings \u2014 formatLintReport(lintGeneratedUi(code).findings) \u2014 not the whole LintResult.`
|
|
375
|
+
);
|
|
376
|
+
}
|
|
346
377
|
if (findings.length === 0) return "";
|
|
347
378
|
const lines = findings.slice().sort((a, b) => a.line - b.line).map((f) => {
|
|
348
379
|
const tag = f.severity === "error" ? "ERROR" : "warn ";
|