@codemation/ui 0.2.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.
Files changed (41) hide show
  1. package/.turbo/turbo-build.log +19 -0
  2. package/.turbo/turbo-lint.log +4 -0
  3. package/.turbo/turbo-typecheck.log +4 -0
  4. package/CHANGELOG.md +25 -0
  5. package/LICENSE +37 -0
  6. package/dist/index.cjs +845 -0
  7. package/dist/index.cjs.map +1 -0
  8. package/dist/index.d.cts +417 -0
  9. package/dist/index.d.ts +417 -0
  10. package/dist/index.js +749 -0
  11. package/dist/index.js.map +1 -0
  12. package/package.json +71 -0
  13. package/src/components/StatusPill.tsx +33 -0
  14. package/src/components/composite/CodemationDialog.tsx +137 -0
  15. package/src/components/composite/JsonMonacoEditor.tsx +75 -0
  16. package/src/components/reui/tree/Tree.tsx +35 -0
  17. package/src/components/reui/tree/TreeContext.ts +21 -0
  18. package/src/components/reui/tree/TreeDragLine.tsx +28 -0
  19. package/src/components/reui/tree/TreeItem.tsx +51 -0
  20. package/src/components/reui/tree/TreeItemLabel.tsx +58 -0
  21. package/src/components/ui/badge.tsx +40 -0
  22. package/src/components/ui/button.tsx +64 -0
  23. package/src/components/ui/collapsible.tsx +26 -0
  24. package/src/components/ui/dialog.tsx +137 -0
  25. package/src/components/ui/dropdown-menu.tsx +239 -0
  26. package/src/components/ui/input.tsx +20 -0
  27. package/src/components/ui/label.tsx +18 -0
  28. package/src/components/ui/select.tsx +171 -0
  29. package/src/components/ui/switch.tsx +29 -0
  30. package/src/components/ui/tabs.tsx +76 -0
  31. package/src/components/ui/textarea.tsx +18 -0
  32. package/src/index.ts +76 -0
  33. package/src/lib/cn.ts +6 -0
  34. package/src/lucide-icons.d.ts +46 -0
  35. package/test/StatusPill.test.tsx +26 -0
  36. package/test/primitives.test.tsx +707 -0
  37. package/test/setup.ts +7 -0
  38. package/test/ui-components.test.tsx +208 -0
  39. package/tsconfig.json +11 -0
  40. package/tsdown.config.ts +10 -0
  41. package/vitest.ui.config.ts +40 -0
package/test/setup.ts ADDED
@@ -0,0 +1,7 @@
1
+ import "@testing-library/jest-dom/vitest";
2
+ import { cleanup } from "@testing-library/react";
3
+ import { afterEach } from "vitest";
4
+
5
+ afterEach(() => {
6
+ cleanup();
7
+ });
@@ -0,0 +1,208 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import { describe, expect, it } from "vitest";
3
+
4
+ import { TreeContext } from "../src/components/reui/tree/TreeContext";
5
+ import { TreeDragLine } from "../src/components/reui/tree/TreeDragLine";
6
+ import { TreeItemLabel } from "../src/components/reui/tree/TreeItemLabel";
7
+ import {
8
+ DropdownMenu,
9
+ DropdownMenuContent,
10
+ DropdownMenuGroup,
11
+ DropdownMenuItem,
12
+ DropdownMenuPortal,
13
+ DropdownMenuRadioGroup,
14
+ DropdownMenuRadioItem,
15
+ DropdownMenuShortcut,
16
+ DropdownMenuSub,
17
+ DropdownMenuSubContent,
18
+ DropdownMenuSubTrigger,
19
+ DropdownMenuTrigger,
20
+ } from "../src/components/ui/dropdown-menu";
21
+
22
+ // ── DropdownMenu ──────────────────────────────────────────────────────────────
23
+
24
+ describe("DropdownMenu supplementary components", () => {
25
+ it("renders DropdownMenuShortcut as a span", () => {
26
+ const { container } = render(<DropdownMenuShortcut>⌘K</DropdownMenuShortcut>);
27
+ const el = container.querySelector("[data-slot='dropdown-menu-shortcut']");
28
+ expect(el).not.toBeNull();
29
+ expect(el?.textContent).toBe("⌘K");
30
+ });
31
+
32
+ it("renders DropdownMenuGroup within an open menu", () => {
33
+ render(
34
+ <DropdownMenu open>
35
+ <DropdownMenuTrigger>Open</DropdownMenuTrigger>
36
+ <DropdownMenuContent>
37
+ <DropdownMenuGroup>
38
+ <DropdownMenuItem>Item A</DropdownMenuItem>
39
+ </DropdownMenuGroup>
40
+ </DropdownMenuContent>
41
+ </DropdownMenu>,
42
+ );
43
+ expect(screen.getByText("Item A")).toBeInTheDocument();
44
+ });
45
+
46
+ it("renders DropdownMenuRadioGroup and DropdownMenuRadioItem", () => {
47
+ render(
48
+ <DropdownMenu open>
49
+ <DropdownMenuTrigger>Open</DropdownMenuTrigger>
50
+ <DropdownMenuContent>
51
+ <DropdownMenuRadioGroup value="a">
52
+ <DropdownMenuRadioItem value="a">Option A</DropdownMenuRadioItem>
53
+ </DropdownMenuRadioGroup>
54
+ </DropdownMenuContent>
55
+ </DropdownMenu>,
56
+ );
57
+ expect(screen.getByText("Option A")).toBeInTheDocument();
58
+ });
59
+
60
+ it("renders DropdownMenuSub with SubTrigger and SubContent via Portal", () => {
61
+ render(
62
+ <DropdownMenu open>
63
+ <DropdownMenuTrigger>Open</DropdownMenuTrigger>
64
+ <DropdownMenuPortal>
65
+ <DropdownMenuContent>
66
+ <DropdownMenuSub open>
67
+ <DropdownMenuSubTrigger>More</DropdownMenuSubTrigger>
68
+ <DropdownMenuPortal>
69
+ <DropdownMenuSubContent>
70
+ <DropdownMenuItem>Sub item</DropdownMenuItem>
71
+ </DropdownMenuSubContent>
72
+ </DropdownMenuPortal>
73
+ </DropdownMenuSub>
74
+ </DropdownMenuContent>
75
+ </DropdownMenuPortal>
76
+ </DropdownMenu>,
77
+ );
78
+ expect(screen.getByText("More")).toBeInTheDocument();
79
+ });
80
+ });
81
+
82
+ // ── TreeDragLine ───────────────────────────────────────────────────────────────
83
+
84
+ describe("TreeDragLine", () => {
85
+ it("renders nothing when tree has no drag line style", () => {
86
+ const { container } = render(
87
+ <TreeContext.Provider value={{ indent: 20, tree: { getDragLineStyle: () => null } }}>
88
+ <TreeDragLine />
89
+ </TreeContext.Provider>,
90
+ );
91
+ expect(container.firstChild).toBeNull();
92
+ });
93
+
94
+ it("renders a drag line when tree provides drag line style", () => {
95
+ const dragStyle = { top: 10, left: 0, width: 100 };
96
+ render(
97
+ <TreeContext.Provider value={{ indent: 20, tree: { getDragLineStyle: () => dragStyle } }}>
98
+ <TreeDragLine data-testid="drag-line" />
99
+ </TreeContext.Provider>,
100
+ );
101
+ expect(screen.getByTestId("drag-line")).toBeInTheDocument();
102
+ });
103
+
104
+ it("renders nothing when no tree context", () => {
105
+ const { container } = render(
106
+ <TreeContext.Provider value={{ indent: 20 }}>
107
+ <TreeDragLine />
108
+ </TreeContext.Provider>,
109
+ );
110
+ expect(container.firstChild).toBeNull();
111
+ });
112
+ });
113
+
114
+ describe("TreeItemLabel", () => {
115
+ it("renders null when no item is available", () => {
116
+ const { container } = render(
117
+ <TreeContext.Provider value={{ indent: 20 }}>
118
+ <TreeItemLabel />
119
+ </TreeContext.Provider>,
120
+ );
121
+ expect(container.firstChild).toBeNull();
122
+ });
123
+
124
+ it("renders children when item is provided", () => {
125
+ const item = {
126
+ isFolder: () => false,
127
+ isExpanded: () => false,
128
+ getItemName: () => "My Item",
129
+ };
130
+ render(
131
+ <TreeContext.Provider value={{ indent: 20 }}>
132
+ <TreeItemLabel item={item}>Label text</TreeItemLabel>
133
+ </TreeContext.Provider>,
134
+ );
135
+ expect(screen.getByText("Label text")).toBeInTheDocument();
136
+ });
137
+
138
+ it("renders item name when no children are provided", () => {
139
+ const item = {
140
+ isFolder: () => false,
141
+ isExpanded: () => false,
142
+ getItemName: () => "Document.txt",
143
+ };
144
+ render(
145
+ <TreeContext.Provider value={{ indent: 20 }}>
146
+ <TreeItemLabel item={item} />
147
+ </TreeContext.Provider>,
148
+ );
149
+ expect(screen.getByText("Document.txt")).toBeInTheDocument();
150
+ });
151
+
152
+ it("renders a chevron icon for folder items (chevron toggle)", () => {
153
+ const item = {
154
+ isFolder: () => true,
155
+ isExpanded: () => false,
156
+ getItemName: () => "Folder",
157
+ };
158
+ render(
159
+ <TreeContext.Provider value={{ indent: 20, toggleIconType: "chevron" }}>
160
+ <TreeItemLabel item={item} />
161
+ </TreeContext.Provider>,
162
+ );
163
+ // The folder label should render (with icon)
164
+ expect(screen.getByText("Folder")).toBeInTheDocument();
165
+ });
166
+
167
+ it("renders plus icon for collapsed folder with plus-minus toggle", () => {
168
+ const item = {
169
+ isFolder: () => true,
170
+ isExpanded: () => false,
171
+ getItemName: () => "CollapsedFolder",
172
+ };
173
+ render(
174
+ <TreeContext.Provider value={{ indent: 20, toggleIconType: "plus-minus" }}>
175
+ <TreeItemLabel item={item} />
176
+ </TreeContext.Provider>,
177
+ );
178
+ expect(screen.getByText("CollapsedFolder")).toBeInTheDocument();
179
+ });
180
+
181
+ it("renders minus icon for expanded folder with plus-minus toggle", () => {
182
+ const item = {
183
+ isFolder: () => true,
184
+ isExpanded: () => true,
185
+ getItemName: () => "ExpandedFolder",
186
+ };
187
+ render(
188
+ <TreeContext.Provider value={{ indent: 20, toggleIconType: "plus-minus" }}>
189
+ <TreeItemLabel item={item} />
190
+ </TreeContext.Provider>,
191
+ );
192
+ expect(screen.getByText("ExpandedFolder")).toBeInTheDocument();
193
+ });
194
+
195
+ it("renders as span by default", () => {
196
+ const item = {
197
+ isFolder: () => false,
198
+ isExpanded: () => false,
199
+ getItemName: () => "FileItem",
200
+ };
201
+ const { container } = render(
202
+ <TreeContext.Provider value={{ indent: 20 }}>
203
+ <TreeItemLabel item={item} />
204
+ </TreeContext.Provider>,
205
+ );
206
+ expect(container.querySelector("span[data-slot='tree-item-label']")).toBeInTheDocument();
207
+ });
208
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "lib": ["ES2022", "DOM"],
5
+ "jsx": "react-jsx",
6
+ "types": ["node"],
7
+ "noEmit": true,
8
+ "baseUrl": "."
9
+ },
10
+ "include": ["src/**/*.ts", "src/**/*.tsx"]
11
+ }
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from "tsdown";
2
+
3
+ export default defineConfig({
4
+ entry: "src/index.ts",
5
+ outDir: "dist",
6
+ clean: false,
7
+ dts: true,
8
+ format: ["esm", "cjs"],
9
+ sourcemap: true,
10
+ });
@@ -0,0 +1,40 @@
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { defineConfig } from "vitest/config";
4
+
5
+ const dirname = path.dirname(fileURLToPath(import.meta.url));
6
+
7
+ export default defineConfig({
8
+ esbuild: {
9
+ jsx: "automatic",
10
+ jsxImportSource: "react",
11
+ },
12
+ test: {
13
+ name: "@codemation/ui:ui",
14
+ root: import.meta.dirname,
15
+ environment: "jsdom",
16
+ include: ["test/**/*.test.tsx", "test/**/*.test.ts"],
17
+ setupFiles: ["./test/setup.ts"],
18
+ pool: "threads",
19
+ coverage: {
20
+ // Measure all source files so uncovered primitives don't silently inflate %.
21
+ all: true,
22
+ include: ["src/**"],
23
+ exclude: [
24
+ // Pure re-export barrel — no logic to test.
25
+ "src/index.ts",
26
+ // One-line twMerge(clsx(...)) wrapper — trivially correct and tested transitively.
27
+ "src/lib/cn.ts",
28
+ // Declaration file only (lucide icon ambient types) — no runtime code.
29
+ "src/**/*.d.ts",
30
+ // JsonMonacoEditor wraps @monaco-editor/react which cannot be mounted in jsdom
31
+ // (Monaco requires a real browser canvas/worker environment).
32
+ "src/components/composite/JsonMonacoEditor.tsx",
33
+ ],
34
+ },
35
+ },
36
+ resolve: {
37
+ conditions: ["development", "import", "module", "default"],
38
+ alias: [{ find: "@codemation/ui", replacement: path.resolve(dirname, "./src/index.ts") }],
39
+ },
40
+ });