@dowel-ui/react 0.1.0 → 0.1.1

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 (120) hide show
  1. package/dist/components/ai-agent-status/ai-agent-status.d.ts +1 -1
  2. package/dist/components/ai-tool/ai-tool.d.ts +1 -1
  3. package/dist/components/alert/alert.d.ts +1 -1
  4. package/dist/components/avatar/avatar.d.ts +1 -1
  5. package/dist/components/badge/badge.d.ts +1 -1
  6. package/dist/components/button/button.d.ts +1 -1
  7. package/dist/components/progress/progress.d.ts +1 -1
  8. package/dist/components/spinner/spinner.d.ts +1 -1
  9. package/dist/components/toast/toast.d.ts +1 -1
  10. package/package.json +7 -4
  11. package/src/blocks/admin-users/admin-users.stories.tsx +0 -83
  12. package/src/blocks/admin-users/admin-users.test.tsx +0 -159
  13. package/src/blocks/ai-chat/ai-chat.stories.tsx +0 -149
  14. package/src/blocks/ai-chat/ai-chat.test.tsx +0 -166
  15. package/src/blocks/dashboard/dashboard.stories.tsx +0 -106
  16. package/src/blocks/dashboard/dashboard.test.tsx +0 -94
  17. package/src/blocks/forgot-password/forgot-password.stories.tsx +0 -41
  18. package/src/blocks/forgot-password/forgot-password.test.tsx +0 -62
  19. package/src/blocks/login/login.stories.tsx +0 -46
  20. package/src/blocks/login/login.test.tsx +0 -100
  21. package/src/blocks/pricing/pricing.stories.tsx +0 -62
  22. package/src/blocks/pricing/pricing.test.tsx +0 -101
  23. package/src/blocks/settings/settings.stories.tsx +0 -94
  24. package/src/blocks/settings/settings.test.tsx +0 -167
  25. package/src/blocks/signup/signup.stories.tsx +0 -27
  26. package/src/blocks/signup/signup.test.tsx +0 -97
  27. package/src/components/accordion/accordion.stories.tsx +0 -90
  28. package/src/components/accordion/accordion.test.tsx +0 -156
  29. package/src/components/activity-feed/activity-feed.stories.tsx +0 -163
  30. package/src/components/activity-feed/activity-feed.test.tsx +0 -125
  31. package/src/components/ai-agent-status/ai-agent-status.stories.tsx +0 -86
  32. package/src/components/ai-agent-status/ai-agent-status.test.tsx +0 -66
  33. package/src/components/ai-conversation/ai-conversation.stories.tsx +0 -126
  34. package/src/components/ai-conversation/ai-conversation.test.tsx +0 -144
  35. package/src/components/ai-message/ai-message.stories.tsx +0 -106
  36. package/src/components/ai-message/ai-message.test.tsx +0 -160
  37. package/src/components/ai-model-selector/ai-model-selector.stories.tsx +0 -66
  38. package/src/components/ai-model-selector/ai-model-selector.test.tsx +0 -120
  39. package/src/components/ai-prompt-input/ai-prompt-input.stories.tsx +0 -145
  40. package/src/components/ai-prompt-input/ai-prompt-input.test.tsx +0 -273
  41. package/src/components/ai-reasoning/ai-reasoning.stories.tsx +0 -60
  42. package/src/components/ai-reasoning/ai-reasoning.test.tsx +0 -95
  43. package/src/components/ai-response/ai-response.stories.tsx +0 -75
  44. package/src/components/ai-response/ai-response.test.tsx +0 -96
  45. package/src/components/ai-sources/ai-sources.stories.tsx +0 -93
  46. package/src/components/ai-sources/ai-sources.test.tsx +0 -102
  47. package/src/components/ai-token-usage/ai-token-usage.stories.tsx +0 -59
  48. package/src/components/ai-token-usage/ai-token-usage.test.tsx +0 -83
  49. package/src/components/ai-tool/ai-tool.stories.tsx +0 -96
  50. package/src/components/ai-tool/ai-tool.test.tsx +0 -137
  51. package/src/components/alert/alert.stories.tsx +0 -101
  52. package/src/components/alert/alert.test.tsx +0 -94
  53. package/src/components/avatar/avatar.stories.tsx +0 -58
  54. package/src/components/avatar/avatar.test.tsx +0 -64
  55. package/src/components/badge/badge.stories.tsx +0 -68
  56. package/src/components/badge/badge.test.tsx +0 -61
  57. package/src/components/button/button.stories.tsx +0 -128
  58. package/src/components/button/button.test.tsx +0 -224
  59. package/src/components/calendar/calendar.stories.tsx +0 -105
  60. package/src/components/calendar/calendar.test.tsx +0 -255
  61. package/src/components/card/card.stories.tsx +0 -61
  62. package/src/components/card/card.test.tsx +0 -66
  63. package/src/components/checkbox/checkbox.stories.tsx +0 -124
  64. package/src/components/checkbox/checkbox.test.tsx +0 -121
  65. package/src/components/code-block/code-block.stories.tsx +0 -62
  66. package/src/components/code-block/code-block.test.tsx +0 -139
  67. package/src/components/combobox/combobox.stories.tsx +0 -171
  68. package/src/components/combobox/combobox.test.tsx +0 -429
  69. package/src/components/command/command.stories.tsx +0 -152
  70. package/src/components/command/command.test.tsx +0 -380
  71. package/src/components/data-table/data-table.stories.tsx +0 -151
  72. package/src/components/data-table/data-table.test.tsx +0 -288
  73. package/src/components/date-picker/date-picker.stories.tsx +0 -103
  74. package/src/components/date-picker/date-picker.test.tsx +0 -205
  75. package/src/components/dialog/dialog.stories.tsx +0 -140
  76. package/src/components/dialog/dialog.test.tsx +0 -186
  77. package/src/components/drawer/drawer.stories.tsx +0 -111
  78. package/src/components/drawer/drawer.test.tsx +0 -187
  79. package/src/components/dropdown-menu/dropdown-menu.stories.tsx +0 -128
  80. package/src/components/dropdown-menu/dropdown-menu.test.tsx +0 -321
  81. package/src/components/empty-state/empty-state.stories.tsx +0 -70
  82. package/src/components/empty-state/empty-state.test.tsx +0 -97
  83. package/src/components/form/form.stories.tsx +0 -144
  84. package/src/components/form/form.test.tsx +0 -203
  85. package/src/components/input/input.stories.tsx +0 -84
  86. package/src/components/input/input.test.tsx +0 -108
  87. package/src/components/label/label.stories.tsx +0 -51
  88. package/src/components/label/label.test.tsx +0 -55
  89. package/src/components/pagination/pagination.stories.tsx +0 -129
  90. package/src/components/pagination/pagination.test.tsx +0 -119
  91. package/src/components/popover/popover.stories.tsx +0 -84
  92. package/src/components/popover/popover.test.tsx +0 -163
  93. package/src/components/progress/progress.stories.tsx +0 -90
  94. package/src/components/progress/progress.test.tsx +0 -78
  95. package/src/components/radio-group/radio-group.stories.tsx +0 -102
  96. package/src/components/radio-group/radio-group.test.tsx +0 -152
  97. package/src/components/select/select.stories.tsx +0 -137
  98. package/src/components/select/select.test.tsx +0 -209
  99. package/src/components/separator/separator.stories.tsx +0 -42
  100. package/src/components/separator/separator.test.tsx +0 -46
  101. package/src/components/sheet/sheet.stories.tsx +0 -107
  102. package/src/components/sheet/sheet.test.tsx +0 -152
  103. package/src/components/skeleton/skeleton.stories.tsx +0 -46
  104. package/src/components/skeleton/skeleton.test.tsx +0 -57
  105. package/src/components/slider/slider.stories.tsx +0 -87
  106. package/src/components/slider/slider.test.tsx +0 -148
  107. package/src/components/spinner/spinner.stories.tsx +0 -47
  108. package/src/components/spinner/spinner.test.tsx +0 -41
  109. package/src/components/switch/switch.stories.tsx +0 -70
  110. package/src/components/switch/switch.test.tsx +0 -101
  111. package/src/components/table/table.stories.tsx +0 -131
  112. package/src/components/table/table.test.tsx +0 -153
  113. package/src/components/tabs/tabs.stories.tsx +0 -139
  114. package/src/components/tabs/tabs.test.tsx +0 -148
  115. package/src/components/toast/toast.stories.tsx +0 -167
  116. package/src/components/toast/toast.test.tsx +0 -306
  117. package/src/components/tooltip/tooltip.stories.tsx +0 -89
  118. package/src/components/tooltip/tooltip.test.tsx +0 -120
  119. package/src/lib/utils.test.ts +0 -32
  120. package/src/registry/meta.test.ts +0 -234
@@ -1,120 +0,0 @@
1
- import { render, screen, waitFor } from "@testing-library/react";
2
- import userEvent from "@testing-library/user-event";
3
- import { describe, expect, it } from "vitest";
4
-
5
- import { expectNoA11yViolations } from "../../../test/a11y";
6
- import { Button } from "../button";
7
- import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./tooltip";
8
-
9
- function Example({ delayDuration = 0 }: { delayDuration?: number } = {}) {
10
- return (
11
- <Tooltip delayDuration={delayDuration}>
12
- <TooltipTrigger asChild>
13
- <Button size="icon" aria-label="Add item">
14
- +
15
- </Button>
16
- </TooltipTrigger>
17
- <TooltipContent>Add item</TooltipContent>
18
- </Tooltip>
19
- );
20
- }
21
-
22
- describe("Tooltip", () => {
23
- it("is hidden until the trigger is hovered", async () => {
24
- const user = userEvent.setup();
25
- render(<Example />);
26
-
27
- expect(screen.queryByRole("tooltip")).not.toBeInTheDocument();
28
- await user.hover(screen.getByRole("button", { name: "Add item" }));
29
- expect(await screen.findByRole("tooltip")).toHaveTextContent("Add item");
30
- });
31
-
32
- it("opens on keyboard focus, not only on hover", async () => {
33
- const user = userEvent.setup();
34
- render(<Example />);
35
-
36
- await user.tab();
37
- expect(screen.getByRole("button", { name: "Add item" })).toHaveFocus();
38
- expect(await screen.findByRole("tooltip")).toBeInTheDocument();
39
- });
40
-
41
- it("closes when the pointer leaves", async () => {
42
- const user = userEvent.setup();
43
- // disableHoverableContent removes the grace area that normally lets the
44
- // pointer travel from trigger to tooltip. That grace area is a polygon
45
- // computed from measured rects, and jsdom reports every rect as zero-sized,
46
- // so leaving it enabled would exercise the stub rather than the behaviour.
47
- render(
48
- <Tooltip delayDuration={0} disableHoverableContent>
49
- <TooltipTrigger>Trigger</TooltipTrigger>
50
- <TooltipContent>Hint</TooltipContent>
51
- </Tooltip>,
52
- );
53
-
54
- const trigger = screen.getByText("Trigger");
55
- await user.hover(trigger);
56
- await screen.findByRole("tooltip");
57
-
58
- await user.unhover(trigger);
59
- await waitFor(() => {
60
- expect(screen.queryByRole("tooltip")).not.toBeInTheDocument();
61
- });
62
- });
63
-
64
- it("closes on Escape", async () => {
65
- const user = userEvent.setup();
66
- render(<Example />);
67
-
68
- await user.tab();
69
- await screen.findByRole("tooltip");
70
-
71
- await user.keyboard("{Escape}");
72
- await waitFor(() => {
73
- expect(screen.queryByRole("tooltip")).not.toBeInTheDocument();
74
- });
75
- });
76
-
77
- it("works without an explicit provider", async () => {
78
- const user = userEvent.setup();
79
- render(<Example />);
80
-
81
- await user.hover(screen.getByRole("button", { name: "Add item" }));
82
- expect(await screen.findByRole("tooltip")).toBeInTheDocument();
83
- });
84
-
85
- it("works inside a shared provider", async () => {
86
- const user = userEvent.setup();
87
- render(
88
- <TooltipProvider delayDuration={0}>
89
- <Example />
90
- </TooltipProvider>,
91
- );
92
-
93
- await user.hover(screen.getByRole("button", { name: "Add item" }));
94
- expect(await screen.findByRole("tooltip")).toBeInTheDocument();
95
- });
96
-
97
- it("merges a consumer className", async () => {
98
- const user = userEvent.setup();
99
- render(
100
- <Tooltip delayDuration={0}>
101
- <TooltipTrigger>Trigger</TooltipTrigger>
102
- <TooltipContent className="max-w-xs">Hint</TooltipContent>
103
- </Tooltip>,
104
- );
105
-
106
- await user.hover(screen.getByText("Trigger"));
107
- const tooltip = await screen.findByRole("tooltip");
108
- expect(tooltip).toHaveClass("max-w-xs");
109
- expect(tooltip).not.toHaveClass("max-w-64");
110
- });
111
-
112
- it("has no accessibility violations while open", async () => {
113
- const user = userEvent.setup();
114
- const { baseElement } = render(<Example />);
115
- await user.hover(screen.getByRole("button", { name: "Add item" }));
116
- await screen.findByRole("tooltip");
117
-
118
- await expectNoA11yViolations(baseElement);
119
- });
120
- });
@@ -1,32 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
-
3
- import { cn } from "./utils";
4
-
5
- describe("cn", () => {
6
- it("joins class names", () => {
7
- expect(cn("a", "b")).toBe("a b");
8
- });
9
-
10
- it("drops falsy values", () => {
11
- expect(cn("a", false, undefined, null, "b")).toBe("a b");
12
- });
13
-
14
- it("lets the later Tailwind utility win a conflict", () => {
15
- expect(cn("px-2", "px-4")).toBe("px-4");
16
- });
17
-
18
- it("resolves conflicts across our custom font-size scale", () => {
19
- // Without the extendTailwindMerge config this returns "text-sm text-2xs",
20
- // and a consumer's size override would silently do nothing.
21
- expect(cn("text-sm", "text-2xs")).toBe("text-2xs");
22
- expect(cn("text-2xs", "text-lg")).toBe("text-lg");
23
- });
24
-
25
- it("keeps non-conflicting utilities", () => {
26
- expect(cn("rounded-md border", "bg-primary")).toBe("rounded-md border bg-primary");
27
- });
28
-
29
- it("accepts arrays and objects", () => {
30
- expect(cn(["a", { b: true, c: false }])).toBe("a b");
31
- });
32
- });
@@ -1,234 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
-
3
- import { blockMetas } from "./blocks";
4
- import { componentMetas } from "./components";
5
- import { COMPONENT_CATEGORIES, COMPONENT_STATUSES, type ComponentMeta } from "./schema";
6
-
7
- /**
8
- * Registry integrity.
9
- *
10
- * A registry that lies about a component's dependencies produces a broken
11
- * install for every user who runs `add`, and the failure surfaces in *their*
12
- * project rather than ours. So the declared metadata is checked against the
13
- * imports actually present in the source on every test run.
14
- */
15
-
16
- const metaModules = {
17
- ...import.meta.glob<{ meta: ComponentMeta }>("../components/*/meta.ts", { eager: true }),
18
- ...import.meta.glob<{ meta: ComponentMeta }>("../blocks/*/meta.ts", { eager: true }),
19
- };
20
-
21
- const sourceFiles = {
22
- ...import.meta.glob<string>("../components/*/*.{ts,tsx}", {
23
- eager: true,
24
- query: "?raw",
25
- import: "default",
26
- }),
27
- ...import.meta.glob<string>("../blocks/*/*.{ts,tsx}", {
28
- eager: true,
29
- query: "?raw",
30
- import: "default",
31
- }),
32
- };
33
-
34
- const packageJson = import.meta.glob<string>("../../package.json", {
35
- eager: true,
36
- query: "?raw",
37
- import: "default",
38
- });
39
-
40
- const declaredNpmDependencies = new Set(
41
- Object.keys(
42
- (
43
- JSON.parse(Object.values(packageJson)[0] ?? "{}") as {
44
- dependencies?: Record<string, string>;
45
- }
46
- ).dependencies ?? {},
47
- ),
48
- );
49
-
50
- /** Provided by the consuming app, never installed by the CLI. */
51
- const PEER_PACKAGES = new Set(["react", "react-dom", "react/jsx-runtime"]);
52
-
53
- /** Written by `<cli> init`, so components may import it without declaring it. */
54
- const INSTALLED_BY_INIT = new Set(["@/lib/utils", "@/lib/styles"]);
55
-
56
- function componentDir(metaPath: string): string {
57
- return metaPath.replace(/\/meta\.ts$/, "");
58
- }
59
-
60
- function componentName(metaPath: string): string {
61
- return componentDir(metaPath).split("/").pop() ?? "";
62
- }
63
-
64
- /** Collects module specifiers from `import`/`export ... from` and side-effect imports. */
65
- function extractSpecifiers(source: string): string[] {
66
- const specifiers: string[] = [];
67
- for (const match of source.matchAll(/\bfrom\s+["']([^"']+)["']/g)) {
68
- if (match[1]) specifiers.push(match[1]);
69
- }
70
- for (const match of source.matchAll(/\bimport\s+["']([^"']+)["']/g)) {
71
- if (match[1]) specifiers.push(match[1]);
72
- }
73
- return specifiers;
74
- }
75
-
76
- /** `@scope/pkg/sub` -> `@scope/pkg`, `pkg/sub` -> `pkg`. */
77
- function packageNameOf(specifier: string): string {
78
- const segments = specifier.split("/");
79
- return specifier.startsWith("@")
80
- ? segments.slice(0, 2).join("/")
81
- : (segments[0] ?? specifier);
82
- }
83
-
84
- interface ResolvedImports {
85
- npm: Set<string>;
86
- registry: Set<string>;
87
- internal: Set<string>;
88
- relative: Set<string>;
89
- }
90
-
91
- function resolveImports(sources: string[]): ResolvedImports {
92
- const resolved: ResolvedImports = {
93
- npm: new Set(),
94
- registry: new Set(),
95
- internal: new Set(),
96
- relative: new Set(),
97
- };
98
-
99
- for (const source of sources) {
100
- for (const specifier of extractSpecifiers(source)) {
101
- if (PEER_PACKAGES.has(specifier)) continue;
102
-
103
- if (specifier.startsWith("@/components/")) {
104
- const name = specifier.split("/")[2];
105
- if (name) resolved.registry.add(name);
106
- } else if (specifier.startsWith("@/")) {
107
- resolved.internal.add(specifier);
108
- } else if (specifier.startsWith(".")) {
109
- resolved.relative.add(specifier);
110
- } else {
111
- resolved.npm.add(packageNameOf(specifier));
112
- }
113
- }
114
- }
115
-
116
- return resolved;
117
- }
118
-
119
- const metaEntries = Object.entries(metaModules);
120
-
121
- describe("registry metadata", () => {
122
- it("finds at least one component", () => {
123
- expect(metaEntries.length).toBeGreaterThan(0);
124
- });
125
-
126
- describe.each(metaEntries)("%s", (metaPath, module) => {
127
- const meta = module.meta;
128
- const dir = componentDir(metaPath);
129
- const dirName = componentName(metaPath);
130
-
131
- const shippedSources = meta.files
132
- .map((file) => sourceFiles[`${dir}/${file}`])
133
- .filter((source): source is string => typeof source === "string");
134
-
135
- it("exports a well-formed meta object", () => {
136
- expect(meta.name).toMatch(/^[a-z][a-z0-9-]*$/);
137
- expect(meta.title.length).toBeGreaterThan(0);
138
- expect(meta.description.length).toBeGreaterThan(9);
139
- expect(COMPONENT_CATEGORIES).toContain(meta.category);
140
- expect(COMPONENT_STATUSES).toContain(meta.status);
141
- expect(meta.files.length).toBeGreaterThan(0);
142
- });
143
-
144
- it("uses its directory name as its registry name", () => {
145
- expect(meta.name).toBe(dirName);
146
- });
147
-
148
- it("lists only files that exist", () => {
149
- for (const file of meta.files) {
150
- expect(
151
- sourceFiles[`${dir}/${file}`],
152
- `${dir}/${file} is listed but missing`,
153
- ).toBeTypeOf("string");
154
- }
155
- expect(shippedSources).toHaveLength(meta.files.length);
156
- });
157
-
158
- it("ships an index, a test and a story", () => {
159
- expect(sourceFiles[`${dir}/index.ts`]).toBeTypeOf("string");
160
- expect(sourceFiles[`${dir}/${dirName}.test.tsx`]).toBeTypeOf("string");
161
- expect(sourceFiles[`${dir}/${dirName}.stories.tsx`]).toBeTypeOf("string");
162
- });
163
-
164
- it("declares exactly the npm packages its source imports", () => {
165
- const { npm } = resolveImports(shippedSources);
166
- expect([...npm].sort()).toEqual([...meta.dependencies].sort());
167
- });
168
-
169
- it("declares exactly the registry entries its source imports", () => {
170
- const { registry } = resolveImports(shippedSources);
171
- expect([...registry].sort()).toEqual([...meta.registryDependencies].sort());
172
- });
173
-
174
- it("does not let a component depend on a block", () => {
175
- // Blocks are assembled from components; the reverse would make a
176
- // component drag a whole page section into a project that wanted a button.
177
- if (meta.kind === "block") return;
178
- const blockNames = new Set(blockMetas.map((block) => block.name));
179
- for (const dependency of meta.registryDependencies) {
180
- expect(
181
- blockNames.has(dependency),
182
- `${meta.name} depends on block "${dependency}"`,
183
- ).toBe(false);
184
- }
185
- });
186
-
187
- it("only imports internal modules that `init` installs", () => {
188
- const { internal } = resolveImports(shippedSources);
189
- for (const specifier of internal) {
190
- expect(
191
- INSTALLED_BY_INIT.has(specifier),
192
- `${specifier} is imported but no CLI step installs it`,
193
- ).toBe(true);
194
- }
195
- });
196
-
197
- it("only uses relative imports that resolve inside the shipped file set", () => {
198
- const { relative } = resolveImports(shippedSources);
199
- for (const specifier of relative) {
200
- const target = specifier.replace(/^\.\//, "");
201
- const matches = meta.files.some((file) => file.replace(/\.tsx?$/, "") === target);
202
- expect(matches, `${specifier} is imported but not in meta.files`).toBe(true);
203
- }
204
- });
205
-
206
- it("declares dependencies that are actually installed in this workspace", () => {
207
- for (const dependency of meta.dependencies) {
208
- expect(
209
- declaredNpmDependencies.has(dependency),
210
- `${dependency} is declared in meta but missing from @dowel-ui/react dependencies`,
211
- ).toBe(true);
212
- }
213
- });
214
- });
215
-
216
- it("lists every entry in the registry barrels", () => {
217
- // The barrels are what the registry build reads. An entry that exists on
218
- // disk but is missing from one would simply never be publishable, with no
219
- // other signal that anything was wrong.
220
- const onDisk = metaEntries.map(([, module]) => module.meta.name).sort();
221
- const inBarrels = [...componentMetas, ...blockMetas].map((meta) => meta.name).sort();
222
- expect(inBarrels).toEqual(onDisk);
223
- });
224
-
225
- it("keeps components and blocks in separate barrels", () => {
226
- expect(componentMetas.every((meta) => meta.kind !== "block")).toBe(true);
227
- expect(blockMetas.every((meta) => meta.kind === "block")).toBe(true);
228
- });
229
-
230
- it("has no duplicate registry names", () => {
231
- const names = metaEntries.map(([, module]) => module.meta.name);
232
- expect(new Set(names).size).toBe(names.length);
233
- });
234
- });