@codraoss/ui 0.9.4

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.
Files changed (44) hide show
  1. package/LICENSE +625 -0
  2. package/README.md +15 -0
  3. package/dist/chunk-HCQJTSV3.js +39 -0
  4. package/dist/chunk-HCQJTSV3.js.map +1 -0
  5. package/dist/chunk-OU66JJZD.js +7 -0
  6. package/dist/chunk-OU66JJZD.js.map +1 -0
  7. package/dist/chunk-PZ5AY32C.js +10 -0
  8. package/dist/chunk-PZ5AY32C.js.map +1 -0
  9. package/dist/chunk-TXNSYK4G.js +23 -0
  10. package/dist/chunk-TXNSYK4G.js.map +1 -0
  11. package/dist/components/motion/index.d.ts +92 -0
  12. package/dist/components/motion/index.js +643 -0
  13. package/dist/components/motion/index.js.map +1 -0
  14. package/dist/hooks/index.d.ts +3 -0
  15. package/dist/hooks/index.js +8 -0
  16. package/dist/hooks/index.js.map +1 -0
  17. package/dist/index.d.ts +241 -0
  18. package/dist/index.js +1144 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/lib/ease.d.ts +3 -0
  21. package/dist/lib/ease.js +8 -0
  22. package/dist/lib/ease.js.map +1 -0
  23. package/dist/lib/file-tree.d.ts +16 -0
  24. package/dist/lib/file-tree.js +46 -0
  25. package/dist/lib/file-tree.js.map +1 -0
  26. package/dist/lib/highlight.d.ts +14 -0
  27. package/dist/lib/highlight.js +35 -0
  28. package/dist/lib/highlight.js.map +1 -0
  29. package/dist/lib/markdown-plugins.d.ts +6 -0
  30. package/dist/lib/markdown-plugins.js +11041 -0
  31. package/dist/lib/markdown-plugins.js.map +1 -0
  32. package/dist/lib/prompt-diff.d.ts +21 -0
  33. package/dist/lib/prompt-diff.js +95 -0
  34. package/dist/lib/prompt-diff.js.map +1 -0
  35. package/dist/lib/selection.d.ts +11 -0
  36. package/dist/lib/selection.js +15 -0
  37. package/dist/lib/selection.js.map +1 -0
  38. package/dist/lib/theme.d.ts +14 -0
  39. package/dist/lib/theme.js +78 -0
  40. package/dist/lib/theme.js.map +1 -0
  41. package/dist/lib/utils.d.ts +15 -0
  42. package/dist/lib/utils.js +14 -0
  43. package/dist/lib/utils.js.map +1 -0
  44. package/package.json +150 -0
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Parsing for the rendered review prompt shown in the diff viewer.
3
+ *
4
+ * NOT `parseUnifiedDiff` from `@server/core/diff`: that parses real git output for the review
5
+ * pipeline; this reads the padded, gutter-prefixed form the prompt renders for the model.
6
+ */
7
+ interface DiffRow {
8
+ kind: 'add' | 'del' | 'ctx' | 'hunk';
9
+ oldNo: number | null;
10
+ newNo: number | null;
11
+ text: string;
12
+ }
13
+ declare function parsePromptDiff(diff: string): DiffRow[];
14
+ /** Cheap line scan (no row objects) so collapsed panels never pay for a full parse. */
15
+ declare function diffStats(diff: string | null): {
16
+ adds: number;
17
+ dels: number;
18
+ total: number;
19
+ };
20
+
21
+ export { type DiffRow, diffStats, parsePromptDiff };
@@ -0,0 +1,95 @@
1
+ import "../chunk-PZ5AY32C.js";
2
+
3
+ // src/lib/prompt-diff.ts
4
+ var HUNK_RE = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/;
5
+ function parsePaddedLine(line) {
6
+ if (line.length < 11 || line[4] !== " " || line[9] !== " ") return null;
7
+ const prefix = line[10];
8
+ if (prefix !== "+" && prefix !== "-" && prefix !== " ") return null;
9
+ const oldNo = line.slice(0, 4).trim();
10
+ const newNo = line.slice(5, 9).trim();
11
+ if (oldNo && !/^\d+$/.test(oldNo)) return null;
12
+ if (newNo && !/^\d+$/.test(newNo)) return null;
13
+ return { prefix, oldNo, newNo, content: line.slice(11) };
14
+ }
15
+ function parsePromptDiff(diff) {
16
+ const rows = [];
17
+ let started = false;
18
+ let oldNo = 0;
19
+ let newNo = 0;
20
+ for (const line of diff.split("\n")) {
21
+ const hunk = HUNK_RE.exec(line);
22
+ if (hunk) {
23
+ oldNo = Number(hunk[1]);
24
+ newNo = Number(hunk[2]);
25
+ started = true;
26
+ rows.push({ kind: "hunk", oldNo: null, newNo: null, text: line });
27
+ continue;
28
+ }
29
+ if (!started) continue;
30
+ if (line.startsWith("diff --git")) {
31
+ started = false;
32
+ continue;
33
+ }
34
+ if (line.startsWith("\\")) continue;
35
+ if (line.startsWith("[NOTE")) continue;
36
+ const padded = parsePaddedLine(line);
37
+ if (padded) {
38
+ if (padded.prefix === "+") {
39
+ rows.push({ kind: "add", oldNo: null, newNo: padded.newNo ? Number(padded.newNo) : null, text: padded.content });
40
+ } else if (padded.prefix === "-") {
41
+ rows.push({ kind: "del", oldNo: padded.oldNo ? Number(padded.oldNo) : null, newNo: null, text: padded.content });
42
+ } else {
43
+ rows.push({ kind: "ctx", oldNo: padded.oldNo ? Number(padded.oldNo) : null, newNo: padded.newNo ? Number(padded.newNo) : null, text: padded.content });
44
+ }
45
+ continue;
46
+ }
47
+ const p = line[0];
48
+ if (p === "+") rows.push({ kind: "add", oldNo: null, newNo: newNo++, text: line.slice(1) });
49
+ else if (p === "-") rows.push({ kind: "del", oldNo: oldNo++, newNo: null, text: line.slice(1) });
50
+ else if (p === " ") rows.push({ kind: "ctx", oldNo: oldNo++, newNo: newNo++, text: line.slice(1) });
51
+ }
52
+ const last = rows[rows.length - 1];
53
+ if (last && last.kind === "ctx" && last.text === "") rows.pop();
54
+ return rows;
55
+ }
56
+ function diffStats(diff) {
57
+ if (!diff) return { adds: 0, dels: 0, total: 0 };
58
+ let adds = 0;
59
+ let dels = 0;
60
+ let total = 0;
61
+ let started = false;
62
+ for (const line of diff.split("\n")) {
63
+ if (HUNK_RE.test(line)) {
64
+ started = true;
65
+ total++;
66
+ continue;
67
+ }
68
+ if (!started) continue;
69
+ if (line.startsWith("diff --git")) {
70
+ started = false;
71
+ continue;
72
+ }
73
+ const padded = parsePaddedLine(line);
74
+ if (padded) {
75
+ total++;
76
+ if (padded.prefix === "+") adds++;
77
+ else if (padded.prefix === "-") dels++;
78
+ continue;
79
+ }
80
+ const p = line[0];
81
+ if (p === "+" && !line.startsWith("+++")) {
82
+ adds++;
83
+ total++;
84
+ } else if (p === "-" && !line.startsWith("---")) {
85
+ dels++;
86
+ total++;
87
+ } else if (p === " ") total++;
88
+ }
89
+ return { adds, dels, total };
90
+ }
91
+ export {
92
+ diffStats,
93
+ parsePromptDiff
94
+ };
95
+ //# sourceMappingURL=prompt-diff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/prompt-diff.ts"],"sourcesContent":["/**\r\n * Parsing for the rendered review prompt shown in the diff viewer.\r\n *\r\n * NOT `parseUnifiedDiff` from `@server/core/diff`: that parses real git output for the review\r\n * pipeline; this reads the padded, gutter-prefixed form the prompt renders for the model.\r\n */\r\n\r\n// Codra renders each file's diff body as 4-wide padded number columns:\r\n// \"<oldNo> <newNo> <prefix><content>\" e.g. \" 615 615 const x = 1\"\r\n// We read those embedded line numbers directly and fall back to standard git-diff lines for\r\n// anything else. Only content inside a hunk is parsed, so the prompt preamble is ignored.\r\n\r\nexport interface DiffRow {\r\n kind: 'add' | 'del' | 'ctx' | 'hunk';\r\n oldNo: number | null;\r\n newNo: number | null;\r\n text: string;\r\n}\r\n\r\nconst HUNK_RE = /^@@ -(\\d+)(?:,\\d+)? \\+(\\d+)(?:,\\d+)? @@/;\r\n\r\n/** Parse a padded body line (\"NNNN MMMM Pcontent\"); null if it isn't one. */\r\nfunction parsePaddedLine(line: string) {\r\n if (line.length < 11 || line[4] !== ' ' || line[9] !== ' ') return null;\r\n const prefix = line[10];\r\n if (prefix !== '+' && prefix !== '-' && prefix !== ' ') return null;\r\n const oldNo = line.slice(0, 4).trim();\r\n const newNo = line.slice(5, 9).trim();\r\n if (oldNo && !/^\\d+$/.test(oldNo)) return null;\r\n if (newNo && !/^\\d+$/.test(newNo)) return null;\r\n return { prefix, oldNo, newNo, content: line.slice(11) };\r\n}\r\n\r\nexport function parsePromptDiff(diff: string): DiffRow[] {\r\n const rows: DiffRow[] = [];\r\n let started = false; // inside a hunk\r\n let oldNo = 0;\r\n let newNo = 0;\r\n\r\n for (const line of diff.split('\\n')) {\r\n const hunk = HUNK_RE.exec(line);\r\n if (hunk) {\r\n oldNo = Number(hunk[1]);\r\n newNo = Number(hunk[2]);\r\n started = true;\r\n rows.push({ kind: 'hunk', oldNo: null, newNo: null, text: line });\r\n continue;\r\n }\r\n if (!started) continue; // skip prompt preamble before the first hunk\r\n if (line.startsWith('diff --git')) { started = false; continue; }\r\n if (line.startsWith('\\\\')) continue; // \"\\"\r\n if (line.startsWith('[NOTE')) continue; // truncation note\r\n\r\n const padded = parsePaddedLine(line);\r\n if (padded) {\r\n if (padded.prefix === '+') {\r\n rows.push({ kind: 'add', oldNo: null, newNo: padded.newNo ? Number(padded.newNo) : null, text: padded.content });\r\n } else if (padded.prefix === '-') {\r\n rows.push({ kind: 'del', oldNo: padded.oldNo ? Number(padded.oldNo) : null, newNo: null, text: padded.content });\r\n } else {\r\n rows.push({ kind: 'ctx', oldNo: padded.oldNo ? Number(padded.oldNo) : null, newNo: padded.newNo ? Number(padded.newNo) : null, text: padded.content });\r\n }\r\n continue;\r\n }\r\n\r\n // Standard git-diff fallback.\r\n const p = line[0];\r\n if (p === '+') rows.push({ kind: 'add', oldNo: null, newNo: newNo++, text: line.slice(1) });\r\n else if (p === '-') rows.push({ kind: 'del', oldNo: oldNo++, newNo: null, text: line.slice(1) });\r\n else if (p === ' ') rows.push({ kind: 'ctx', oldNo: oldNo++, newNo: newNo++, text: line.slice(1) });\r\n }\r\n\r\n // Drop a single trailing blank context row left behind by the final newline.\r\n const last = rows[rows.length - 1];\r\n if (last && last.kind === 'ctx' && last.text === '') rows.pop();\r\n\r\n return rows;\r\n}\r\n\r\n/** Cheap line scan (no row objects) so collapsed panels never pay for a full parse. */\r\nexport function diffStats(diff: string | null) {\r\n if (!diff) return { adds: 0, dels: 0, total: 0 };\r\n let adds = 0;\r\n let dels = 0;\r\n let total = 0;\r\n let started = false;\r\n for (const line of diff.split('\\n')) {\r\n if (HUNK_RE.test(line)) { started = true; total++; continue; }\r\n if (!started) continue;\r\n if (line.startsWith('diff --git')) { started = false; continue; }\r\n const padded = parsePaddedLine(line);\r\n if (padded) {\r\n total++;\r\n if (padded.prefix === '+') adds++;\r\n else if (padded.prefix === '-') dels++;\r\n continue;\r\n }\r\n const p = line[0];\r\n if (p === '+' && !line.startsWith('+++')) { adds++; total++; }\r\n else if (p === '-' && !line.startsWith('---')) { dels++; total++; }\r\n else if (p === ' ') total++;\r\n }\r\n return { adds, dels, total };\r\n}\r\n"],"mappings":"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","names":[]}
@@ -0,0 +1,11 @@
1
+ import { MouseEvent } from 'react';
2
+
3
+ /**
4
+ * `<summary>` toggles its `<details>` on any click, including the one that ends a drag-select -
5
+ * so selecting text in an accordion header would immediately collapse the panel. This swallows
6
+ * the toggle only when the click ended a real selection inside this summary; a plain click still
7
+ * toggles normally.
8
+ */
9
+ declare function preventToggleOnTextSelection(event: MouseEvent<HTMLElement>): void;
10
+
11
+ export { preventToggleOnTextSelection };
@@ -0,0 +1,15 @@
1
+ import "../chunk-PZ5AY32C.js";
2
+
3
+ // src/lib/selection.ts
4
+ function preventToggleOnTextSelection(event) {
5
+ const selection = window.getSelection();
6
+ if (!selection || selection.isCollapsed) return;
7
+ const anchor = selection.anchorNode;
8
+ if (anchor && event.currentTarget.contains(anchor)) {
9
+ event.preventDefault();
10
+ }
11
+ }
12
+ export {
13
+ preventToggleOnTextSelection
14
+ };
15
+ //# sourceMappingURL=selection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/selection.ts"],"sourcesContent":["import type { MouseEvent } from 'react';\n\n/**\n * `<summary>` toggles its `<details>` on any click, including the one that ends a drag-select -\n * so selecting text in an accordion header would immediately collapse the panel. This swallows\n * the toggle only when the click ended a real selection inside this summary; a plain click still\n * toggles normally.\n */\nexport function preventToggleOnTextSelection(event: MouseEvent<HTMLElement>) {\n const selection = window.getSelection();\n if (!selection || selection.isCollapsed) return;\n\n // Scope to this header - a selection made elsewhere on the page shouldn't block the toggle.\n const anchor = selection.anchorNode;\n if (anchor && event.currentTarget.contains(anchor)) {\n event.preventDefault();\n }\n}\n"],"mappings":";;;AAQO,SAAS,6BAA6B,OAAgC;AAC3E,QAAM,YAAY,OAAO,aAAa;AACtC,MAAI,CAAC,aAAa,UAAU,YAAa;AAGzC,QAAM,SAAS,UAAU;AACzB,MAAI,UAAU,MAAM,cAAc,SAAS,MAAM,GAAG;AAClD,UAAM,eAAe;AAAA,EACvB;AACF;","names":[]}
@@ -0,0 +1,14 @@
1
+ import React__default from 'react';
2
+
3
+ type Theme = 'light' | 'dark';
4
+ interface ThemeContextType {
5
+ theme: Theme;
6
+ toggleTheme: () => void;
7
+ setTheme: (theme: Theme) => void;
8
+ }
9
+ declare function ThemeProvider({ children }: {
10
+ children: React__default.ReactNode;
11
+ }): React__default.JSX.Element;
12
+ declare function useTheme(): ThemeContextType;
13
+
14
+ export { type Theme, ThemeProvider, useTheme };
@@ -0,0 +1,78 @@
1
+ import "../chunk-PZ5AY32C.js";
2
+
3
+ // src/lib/theme.tsx
4
+ import { createContext, useContext, useState, useEffect, useCallback, useMemo } from "react";
5
+ import { jsx } from "react/jsx-runtime";
6
+ var ThemeContext = createContext(void 0);
7
+ function getSystemTheme() {
8
+ if (typeof window === "undefined") return "dark";
9
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
10
+ }
11
+ function getStoredTheme() {
12
+ try {
13
+ const v = localStorage.getItem("codra-theme");
14
+ return v === "light" || v === "dark" ? v : null;
15
+ } catch {
16
+ return null;
17
+ }
18
+ }
19
+ var themeTransitionPauseTimer;
20
+ function pauseThemeTransitions(root) {
21
+ if (typeof window === "undefined") return;
22
+ root.classList.add("theme-changing");
23
+ if (themeTransitionPauseTimer !== void 0) {
24
+ window.clearTimeout(themeTransitionPauseTimer);
25
+ }
26
+ themeTransitionPauseTimer = window.setTimeout(() => {
27
+ root.classList.remove("theme-changing");
28
+ themeTransitionPauseTimer = void 0;
29
+ }, 180);
30
+ }
31
+ function applyTheme(theme, options = {}) {
32
+ if (typeof document === "undefined") return;
33
+ const root = document.documentElement;
34
+ if (options.pauseTransitions) pauseThemeTransitions(root);
35
+ root.classList.toggle("dark", theme === "dark");
36
+ root.setAttribute("data-theme", theme);
37
+ root.setAttribute("data-mode", theme);
38
+ try {
39
+ localStorage.setItem("codra-theme", theme);
40
+ } catch {
41
+ }
42
+ }
43
+ var initial = getStoredTheme() ?? getSystemTheme();
44
+ applyTheme(initial);
45
+ function ThemeProvider({ children }) {
46
+ const [theme, setThemeState] = useState(() => getStoredTheme() ?? getSystemTheme());
47
+ const setTheme = useCallback((newTheme) => {
48
+ setThemeState(newTheme);
49
+ applyTheme(newTheme, { pauseTransitions: true });
50
+ }, []);
51
+ const toggleTheme = useCallback(() => {
52
+ setTheme(theme === "light" ? "dark" : "light");
53
+ }, [theme, setTheme]);
54
+ useEffect(() => {
55
+ const media = window.matchMedia("(prefers-color-scheme: dark)");
56
+ const handler = () => {
57
+ if (!getStoredTheme()) {
58
+ setThemeState(media.matches ? "dark" : "light");
59
+ }
60
+ };
61
+ media.addEventListener("change", handler);
62
+ return () => media.removeEventListener("change", handler);
63
+ }, []);
64
+ const value = useMemo(() => ({ theme, toggleTheme, setTheme }), [theme, toggleTheme, setTheme]);
65
+ return /* @__PURE__ */ jsx(ThemeContext.Provider, { value, children });
66
+ }
67
+ function useTheme() {
68
+ const context = useContext(ThemeContext);
69
+ if (context === void 0) {
70
+ throw new Error("useTheme must be used within a ThemeProvider");
71
+ }
72
+ return context;
73
+ }
74
+ export {
75
+ ThemeProvider,
76
+ useTheme
77
+ };
78
+ //# sourceMappingURL=theme.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/theme.tsx"],"sourcesContent":["import React, { createContext, useContext, useState, useEffect, useCallback, useMemo } from 'react';\n\nexport type Theme = 'light' | 'dark';\n\ninterface ThemeContextType {\n theme: Theme;\n toggleTheme: () => void;\n setTheme: (theme: Theme) => void;\n}\n\nconst ThemeContext = createContext<ThemeContextType | undefined>(undefined);\n\nfunction getSystemTheme(): Theme {\n if (typeof window === 'undefined') return 'dark';\n return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';\n}\n\nfunction getStoredTheme(): Theme | null {\n try {\n const v = localStorage.getItem('codra-theme');\n return v === 'light' || v === 'dark' ? v : null;\n } catch {\n return null;\n }\n}\n\nlet themeTransitionPauseTimer: number | undefined;\n\nfunction pauseThemeTransitions(root: HTMLElement) {\n if (typeof window === 'undefined') return;\n\n root.classList.add('theme-changing');\n\n if (themeTransitionPauseTimer !== undefined) {\n window.clearTimeout(themeTransitionPauseTimer);\n }\n\n themeTransitionPauseTimer = window.setTimeout(() => {\n root.classList.remove('theme-changing');\n themeTransitionPauseTimer = undefined;\n }, 180);\n}\n\nfunction applyTheme(theme: Theme, options: { pauseTransitions?: boolean } = {}) {\n if (typeof document === 'undefined') return;\n const root = document.documentElement;\n if (options.pauseTransitions) pauseThemeTransitions(root);\n root.classList.toggle('dark', theme === 'dark');\n root.setAttribute('data-theme', theme);\n // Mirror the theme onto `data-mode` too, for any CSS keyed off it.\n root.setAttribute('data-mode', theme);\n try {\n localStorage.setItem('codra-theme', theme);\n } catch {\n // ignore\n }\n}\n\n// Initial application to prevent flash\nconst initial = getStoredTheme() ?? getSystemTheme();\napplyTheme(initial);\n\nexport function ThemeProvider({ children }: { children: React.ReactNode }) {\n const [theme, setThemeState] = useState<Theme>(() => getStoredTheme() ?? getSystemTheme());\n\n const setTheme = useCallback((newTheme: Theme) => {\n setThemeState(newTheme);\n applyTheme(newTheme, { pauseTransitions: true });\n }, []);\n\n const toggleTheme = useCallback(() => {\n setTheme(theme === 'light' ? 'dark' : 'light');\n }, [theme, setTheme]);\n\n useEffect(() => {\n const media = window.matchMedia('(prefers-color-scheme: dark)');\n const handler = () => {\n if (!getStoredTheme()) {\n setThemeState(media.matches ? 'dark' : 'light');\n }\n };\n media.addEventListener('change', handler);\n return () => media.removeEventListener('change', handler);\n }, []);\n\n const value = useMemo(() => ({ theme, toggleTheme, setTheme }), [theme, toggleTheme, setTheme]);\n\n return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;\n}\n\nexport function useTheme() {\n const context = useContext(ThemeContext);\n if (context === undefined) {\n throw new Error('useTheme must be used within a ThemeProvider');\n }\n return context;\n}\n"],"mappings":";;;AAAA,SAAgB,eAAe,YAAY,UAAU,WAAW,aAAa,eAAe;AAuFnF;AA7ET,IAAM,eAAe,cAA4C,MAAS;AAE1E,SAAS,iBAAwB;AAC/B,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,SAAO,OAAO,WAAW,8BAA8B,EAAE,UAAU,SAAS;AAC9E;AAEA,SAAS,iBAA+B;AACtC,MAAI;AACF,UAAM,IAAI,aAAa,QAAQ,aAAa;AAC5C,WAAO,MAAM,WAAW,MAAM,SAAS,IAAI;AAAA,EAC7C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,IAAI;AAEJ,SAAS,sBAAsB,MAAmB;AAChD,MAAI,OAAO,WAAW,YAAa;AAEnC,OAAK,UAAU,IAAI,gBAAgB;AAEnC,MAAI,8BAA8B,QAAW;AAC3C,WAAO,aAAa,yBAAyB;AAAA,EAC/C;AAEA,8BAA4B,OAAO,WAAW,MAAM;AAClD,SAAK,UAAU,OAAO,gBAAgB;AACtC,gCAA4B;AAAA,EAC9B,GAAG,GAAG;AACR;AAEA,SAAS,WAAW,OAAc,UAA0C,CAAC,GAAG;AAC9E,MAAI,OAAO,aAAa,YAAa;AACrC,QAAM,OAAO,SAAS;AACtB,MAAI,QAAQ,iBAAkB,uBAAsB,IAAI;AACxD,OAAK,UAAU,OAAO,QAAQ,UAAU,MAAM;AAC9C,OAAK,aAAa,cAAc,KAAK;AAErC,OAAK,aAAa,aAAa,KAAK;AACpC,MAAI;AACF,iBAAa,QAAQ,eAAe,KAAK;AAAA,EAC3C,QAAQ;AAAA,EAER;AACF;AAGA,IAAM,UAAU,eAAe,KAAK,eAAe;AACnD,WAAW,OAAO;AAEX,SAAS,cAAc,EAAE,SAAS,GAAkC;AACzE,QAAM,CAAC,OAAO,aAAa,IAAI,SAAgB,MAAM,eAAe,KAAK,eAAe,CAAC;AAEzF,QAAM,WAAW,YAAY,CAAC,aAAoB;AAChD,kBAAc,QAAQ;AACtB,eAAW,UAAU,EAAE,kBAAkB,KAAK,CAAC;AAAA,EACjD,GAAG,CAAC,CAAC;AAEL,QAAM,cAAc,YAAY,MAAM;AACpC,aAAS,UAAU,UAAU,SAAS,OAAO;AAAA,EAC/C,GAAG,CAAC,OAAO,QAAQ,CAAC;AAEpB,YAAU,MAAM;AACd,UAAM,QAAQ,OAAO,WAAW,8BAA8B;AAC9D,UAAM,UAAU,MAAM;AACpB,UAAI,CAAC,eAAe,GAAG;AACrB,sBAAc,MAAM,UAAU,SAAS,OAAO;AAAA,MAChD;AAAA,IACF;AACA,UAAM,iBAAiB,UAAU,OAAO;AACxC,WAAO,MAAM,MAAM,oBAAoB,UAAU,OAAO;AAAA,EAC1D,GAAG,CAAC,CAAC;AAEL,QAAM,QAAQ,QAAQ,OAAO,EAAE,OAAO,aAAa,SAAS,IAAI,CAAC,OAAO,aAAa,QAAQ,CAAC;AAE9F,SAAO,oBAAC,aAAa,UAAb,EAAsB,OAAe,UAAS;AACxD;AAEO,SAAS,WAAW;AACzB,QAAM,UAAU,WAAW,YAAY;AACvC,MAAI,YAAY,QAAW;AACzB,UAAM,IAAI,MAAM,8CAA8C;AAAA,EAChE;AACA,SAAO;AACT;","names":[]}
@@ -0,0 +1,15 @@
1
+ import { ClassValue } from 'clsx';
2
+
3
+ declare function cn(...inputs: ClassValue[]): string;
4
+ declare function fmtNumber(n: number): string;
5
+ /**
6
+ * Like {@link fmtNumber} but returns the numeric part and its unit suffix
7
+ * separately, so a card can render "1.4" large and "M" as a smaller unit.
8
+ */
9
+ declare function fmtStat(n: number): {
10
+ value: string;
11
+ unit: string;
12
+ };
13
+ declare function formatPreciseDuration(ms: number | null | undefined): string;
14
+
15
+ export { cn, fmtNumber, fmtStat, formatPreciseDuration };
@@ -0,0 +1,14 @@
1
+ import {
2
+ cn,
3
+ fmtNumber,
4
+ fmtStat,
5
+ formatPreciseDuration
6
+ } from "../chunk-HCQJTSV3.js";
7
+ import "../chunk-PZ5AY32C.js";
8
+ export {
9
+ cn,
10
+ fmtNumber,
11
+ fmtStat,
12
+ formatPreciseDuration
13
+ };
14
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/package.json ADDED
@@ -0,0 +1,150 @@
1
+ {
2
+ "name": "@codraoss/ui",
3
+ "version": "0.9.4",
4
+ "description": "Codra's reusable React design-system primitives and hooks.",
5
+ "author": "Devarshi Shimpi",
6
+ "license": "AGPL-3.0-only",
7
+ "homepage": "https://codra.run",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/devarshishimpi/codra.git",
11
+ "directory": "packages/ui"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/devarshishimpi/codra/issues"
15
+ },
16
+ "type": "module",
17
+ "sideEffects": false,
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.js"
22
+ },
23
+ "./theme": {
24
+ "types": "./dist/lib/theme.d.ts",
25
+ "import": "./dist/lib/theme.js"
26
+ },
27
+ "./utils": {
28
+ "types": "./dist/lib/utils.d.ts",
29
+ "import": "./dist/lib/utils.js"
30
+ },
31
+ "./ease": {
32
+ "types": "./dist/lib/ease.d.ts",
33
+ "import": "./dist/lib/ease.js"
34
+ },
35
+ "./highlight": {
36
+ "types": "./dist/lib/highlight.d.ts",
37
+ "import": "./dist/lib/highlight.js"
38
+ },
39
+ "./selection": {
40
+ "types": "./dist/lib/selection.d.ts",
41
+ "import": "./dist/lib/selection.js"
42
+ },
43
+ "./file-tree": {
44
+ "types": "./dist/lib/file-tree.d.ts",
45
+ "import": "./dist/lib/file-tree.js"
46
+ },
47
+ "./prompt-diff": {
48
+ "types": "./dist/lib/prompt-diff.d.ts",
49
+ "import": "./dist/lib/prompt-diff.js"
50
+ },
51
+ "./markdown-plugins": {
52
+ "types": "./dist/lib/markdown-plugins.d.ts",
53
+ "import": "./dist/lib/markdown-plugins.js"
54
+ },
55
+ "./motion": {
56
+ "types": "./dist/components/motion/index.d.ts",
57
+ "import": "./dist/components/motion/index.js"
58
+ },
59
+ "./hooks": {
60
+ "types": "./dist/hooks/index.d.ts",
61
+ "import": "./dist/hooks/index.js"
62
+ }
63
+ },
64
+ "files": [
65
+ "dist",
66
+ "LICENSE",
67
+ "README.md"
68
+ ],
69
+ "publishConfig": {
70
+ "access": "public",
71
+ "main": "./dist/index.js",
72
+ "module": "./dist/index.js",
73
+ "types": "./dist/index.d.ts",
74
+ "exports": {
75
+ ".": {
76
+ "types": "./dist/index.d.ts",
77
+ "import": "./dist/index.js"
78
+ },
79
+ "./theme": {
80
+ "types": "./dist/lib/theme.d.ts",
81
+ "import": "./dist/lib/theme.js"
82
+ },
83
+ "./utils": {
84
+ "types": "./dist/lib/utils.d.ts",
85
+ "import": "./dist/lib/utils.js"
86
+ },
87
+ "./ease": {
88
+ "types": "./dist/lib/ease.d.ts",
89
+ "import": "./dist/lib/ease.js"
90
+ },
91
+ "./highlight": {
92
+ "types": "./dist/lib/highlight.d.ts",
93
+ "import": "./dist/lib/highlight.js"
94
+ },
95
+ "./selection": {
96
+ "types": "./dist/lib/selection.d.ts",
97
+ "import": "./dist/lib/selection.js"
98
+ },
99
+ "./file-tree": {
100
+ "types": "./dist/lib/file-tree.d.ts",
101
+ "import": "./dist/lib/file-tree.js"
102
+ },
103
+ "./prompt-diff": {
104
+ "types": "./dist/lib/prompt-diff.d.ts",
105
+ "import": "./dist/lib/prompt-diff.js"
106
+ },
107
+ "./markdown-plugins": {
108
+ "types": "./dist/lib/markdown-plugins.d.ts",
109
+ "import": "./dist/lib/markdown-plugins.js"
110
+ },
111
+ "./motion": {
112
+ "types": "./dist/components/motion/index.d.ts",
113
+ "import": "./dist/components/motion/index.js"
114
+ },
115
+ "./hooks": {
116
+ "types": "./dist/hooks/index.d.ts",
117
+ "import": "./dist/hooks/index.js"
118
+ }
119
+ }
120
+ },
121
+ "scripts": {
122
+ "build": "tsup",
123
+ "typecheck": "tsc -p tsconfig.json",
124
+ "prepack": "node ../../scripts/swap-publish-exports.mjs promote",
125
+ "postpack": "node ../../scripts/swap-publish-exports.mjs restore"
126
+ },
127
+ "peerDependencies": {
128
+ "react": "^19.0.0",
129
+ "react-dom": "^19.0.0",
130
+ "lucide-react": ">=1.0.0",
131
+ "motion": ">=12.0.0",
132
+ "recharts": ">=3.0.0",
133
+ "lenis": ">=1.0.0",
134
+ "sonner": ">=2.0.0"
135
+ },
136
+ "dependencies": {
137
+ "@codraoss/schema": "^0.9.4",
138
+ "@base-ui/react": "^1.6.0",
139
+ "class-variance-authority": "^0.7.1",
140
+ "clsx": "^2.1.1",
141
+ "tailwind-merge": "^3.5.0",
142
+ "sugar-high": "^2.0.0"
143
+ },
144
+ "devDependencies": {
145
+ "tsup": "^8.0.0"
146
+ },
147
+ "main": "./dist/index.js",
148
+ "module": "./dist/index.js",
149
+ "types": "./dist/index.d.ts"
150
+ }