@imfusion/web-ui 0.5.1-dev.3.g8e0a047a → 0.5.1-dev.30.gf9b152db

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 (57) hide show
  1. package/README.md +148 -55
  2. package/bin/install.js +374 -0
  3. package/bin/install.test.ts +243 -0
  4. package/dist/build/vite-css-module-names/index.d.ts +20 -0
  5. package/dist/build/vite-css-module-names.js +17 -0
  6. package/dist/components/field/field.d.ts +104 -0
  7. package/dist/components/field/field.meta.d.ts +2 -0
  8. package/dist/components/field/index.d.ts +2 -0
  9. package/dist/components/fieldset/fieldset.d.ts +29 -0
  10. package/dist/components/fieldset/fieldset.meta.d.ts +2 -0
  11. package/dist/components/fieldset/index.d.ts +2 -0
  12. package/dist/index.d.ts +2 -0
  13. package/dist/index.js +4956 -4317
  14. package/dist/integrations/image-display-options.js +1 -1
  15. package/dist/style.css +1 -1
  16. package/dist/{tabs-DqBFSqq6.js → tabs-CVp_SgBl.js} +1 -1
  17. package/package.json +35 -24
  18. package/src/docgen/doc.gen.json +327 -0
  19. package/src/llms/llms.gen.txt +12 -0
  20. package/src/llms/skills/imf-web-ui/SKILL.md +15 -11
  21. package/src/llms/skills/imf-web-ui-agent-setup/SKILL.md +82 -0
  22. package/src/llms/skills/imf-web-ui-agent-setup/templates/hooks/baseline-staleness.sh +17 -0
  23. package/src/llms/skills/imf-web-ui-agent-setup/templates/hooks/post-tool-use.sh +33 -0
  24. package/src/llms/skills/imf-web-ui-agent-setup/templates/hooks/session-start.sh +4 -0
  25. package/src/llms/skills/imf-web-ui-agent-setup/templates/hooks/user-prompt-submit.sh +4 -0
  26. package/src/llms/skills/imf-web-ui-agent-setup/templates/settings.json +37 -0
  27. package/src/llms/skills/imf-web-ui-components/SKILL.md +2 -1
  28. package/src/llms/skills/imf-web-ui-frontend-conventions/SKILL.md +46 -0
  29. package/src/llms/skills/imf-web-ui-frontend-conventions/references/assets.md +23 -0
  30. package/src/llms/skills/imf-web-ui-frontend-conventions/references/class-names.md +42 -0
  31. package/src/llms/skills/imf-web-ui-frontend-conventions/references/components.md +63 -0
  32. package/src/llms/skills/imf-web-ui-frontend-conventions/references/data.md +201 -0
  33. package/src/llms/skills/imf-web-ui-frontend-conventions/references/docs-structure.md +44 -0
  34. package/src/llms/skills/imf-web-ui-frontend-conventions/references/git.md +33 -0
  35. package/src/llms/skills/imf-web-ui-frontend-conventions/references/library-boundary.md +37 -0
  36. package/src/llms/skills/imf-web-ui-frontend-conventions/references/npm-project.md +57 -0
  37. package/src/llms/skills/imf-web-ui-frontend-conventions/references/project-structure.md +21 -0
  38. package/src/llms/skills/{imf-web-ui-frontend-patterns/references/react-patterns.md → imf-web-ui-frontend-conventions/references/react.md} +28 -12
  39. package/src/llms/skills/imf-web-ui-frontend-conventions/references/stack.md +39 -0
  40. package/src/llms/skills/imf-web-ui-frontend-conventions/references/styling.md +91 -0
  41. package/src/llms/skills/imf-web-ui-frontend-conventions/references/testing.md +18 -0
  42. package/src/llms/skills/imf-web-ui-frontend-conventions/references/tooling.md +76 -0
  43. package/src/llms/skills/imf-web-ui-frontend-conventions/references/typescript.md +46 -0
  44. package/src/llms/skills/imf-web-ui-frontend-conventions/references/validation.md +88 -0
  45. package/src/llms/skills/imf-web-ui-frontend-setup/SKILL.md +66 -0
  46. package/src/llms/skills/imf-web-ui-frontend-setup/templates/AGENTS.md +34 -0
  47. package/src/llms/skills/imf-web-ui-frontend-setup/templates/FRONTEND_SETUP_REPORT.md +45 -0
  48. package/src/llms/skills/imf-web-ui-frontend-setup/templates/README.md +27 -0
  49. package/src/llms/skills/imf-web-ui-library-setup/SKILL.md +36 -0
  50. package/src/llms/skills/imf-web-ui-update/SKILL.md +166 -0
  51. package/src/llms/skills/imf-web-ui-ux/SKILL.md +4 -4
  52. package/src/llms/skills/imf-web-ui-ux/references/forms.md +1 -1
  53. package/bin/install-skill.js +0 -180
  54. package/src/llms/skills/imf-web-ui-frontend-patterns/SKILL.md +0 -93
  55. package/src/llms/skills/imf-web-ui-frontend-patterns/references/code-conventions.md +0 -133
  56. package/src/llms/skills/imf-web-ui-imfusion-frontend-setup/SKILL.md +0 -201
  57. package/src/llms/skills/imf-web-ui-setup/SKILL.md +0 -57
@@ -0,0 +1,243 @@
1
+ import { beforeEach, describe, expect, it } from "vitest";
2
+ import { spawnSync } from "node:child_process";
3
+ import {
4
+ chmodSync,
5
+ existsSync,
6
+ lstatSync,
7
+ mkdirSync,
8
+ mkdtempSync,
9
+ readdirSync,
10
+ readFileSync,
11
+ statSync,
12
+ symlinkSync,
13
+ writeFileSync
14
+ } from "node:fs";
15
+ import { tmpdir } from "node:os";
16
+ import { join, resolve } from "node:path";
17
+ import { fileURLToPath } from "node:url";
18
+
19
+ const installer = resolve(fileURLToPath(new URL(".", import.meta.url)), "install.js");
20
+
21
+ function runInstall(cwd: string, ...args: string[]) {
22
+ const result = spawnSync("node", [installer, ...args], { cwd, encoding: "utf-8" });
23
+ expect(result.status).toBe(0);
24
+ return result;
25
+ }
26
+
27
+ function readSettings(cwd: string) {
28
+ return JSON.parse(readFileSync(join(cwd, ".claude", "settings.json"), "utf-8")) as {
29
+ permissions?: { allow: string[] };
30
+ hooks: Record<string, { hooks: { command?: string }[] }[]>;
31
+ };
32
+ }
33
+
34
+ let consumer: string;
35
+
36
+ beforeEach(() => {
37
+ consumer = mkdtempSync(join(tmpdir(), "web-ui-install-"));
38
+ });
39
+
40
+ describe("web-ui-install", () => {
41
+ it("installs skills into both targets with version markers", () => {
42
+ runInstall(consumer, "--skills", "--target", "agents", "--target", "claude");
43
+
44
+ const installed = readdirSync(join(consumer, ".agents", "skills"));
45
+ expect(installed).toContain("imf-web-ui");
46
+ expect(installed).toContain("imf-web-ui-frontend-conventions");
47
+ expect(installed).toContain("imf-web-ui-frontend-setup");
48
+
49
+ for (const name of installed) {
50
+ expect(existsSync(join(consumer, ".agents", "skills", name, ".imf-web-ui-skill-version.json"))).toBe(true);
51
+ expect(lstatSync(join(consumer, ".claude", "skills", name)).isSymbolicLink()).toBe(true);
52
+ }
53
+ });
54
+
55
+ it("removes skills that are no longer included in the bundle", () => {
56
+ const orphan = "imf-web-ui-retired-skill";
57
+ const agentsOrphan = join(consumer, ".agents", "skills", orphan);
58
+ mkdirSync(agentsOrphan, { recursive: true });
59
+ writeFileSync(join(agentsOrphan, "SKILL.md"), "# Retired skill\n");
60
+ const claudeOrphan = join(consumer, ".claude", "skills", orphan);
61
+ mkdirSync(join(consumer, ".claude", "skills"), { recursive: true });
62
+ symlinkSync(agentsOrphan, claudeOrphan);
63
+ expect(lstatSync(claudeOrphan).isSymbolicLink()).toBe(true);
64
+
65
+ const result = runInstall(consumer, "--skills", "--target", "agents", "--target", "claude");
66
+
67
+ expect(existsSync(agentsOrphan)).toBe(false);
68
+ expect(lstatSync(claudeOrphan, { throwIfNoEntry: false })).toBeUndefined();
69
+ expect(result.stdout).toContain(`Removed orphaned skill: ${orphan} (no longer in this bundle)`);
70
+ });
71
+
72
+ it("installs hooks into an installer-owned directory and registers them", () => {
73
+ runInstall(consumer, "--hooks");
74
+
75
+ const hookDir = join(consumer, ".agents", "hooks", "imf-web-ui");
76
+ const scripts = readdirSync(hookDir);
77
+ expect(scripts).toEqual(expect.arrayContaining(["session-start.sh", "user-prompt-submit.sh", "post-tool-use.sh"]));
78
+ for (const script of scripts) {
79
+ expect(statSync(join(hookDir, script)).mode & 0o100).toBeTruthy();
80
+ }
81
+
82
+ const settings = readSettings(consumer);
83
+ expect(Object.keys(settings.hooks)).toEqual(expect.arrayContaining(["SessionStart", "UserPromptSubmit", "PostToolUse"]));
84
+ // --hooks alone installs no skills
85
+ expect(existsSync(join(consumer, ".agents", "skills"))).toBe(false);
86
+ });
87
+
88
+ it("merges registrations without clobbering existing settings, idempotently", () => {
89
+ mkdirSync(join(consumer, ".claude"), { recursive: true });
90
+ writeFileSync(
91
+ join(consumer, ".claude", "settings.json"),
92
+ JSON.stringify({
93
+ permissions: { allow: ["Bash(git *)"] },
94
+ hooks: { PostToolUse: [{ matcher: "Edit", hooks: [{ type: "command", command: "echo existing" }] }] }
95
+ })
96
+ );
97
+
98
+ runInstall(consumer, "--hooks");
99
+ runInstall(consumer, "--hooks");
100
+
101
+ const settings = readSettings(consumer);
102
+ expect(settings.permissions?.allow).toEqual(["Bash(git *)"]);
103
+ const commands = settings.hooks.PostToolUse.flatMap(entry => entry.hooks.map(hook => hook.command));
104
+ expect(commands).toContain("echo existing");
105
+ // the shipped registration is added exactly once across both runs
106
+ expect(commands.filter(command => command?.includes("post-tool-use.sh"))).toHaveLength(1);
107
+ });
108
+
109
+ it("recognises registrations written with braced ${CLAUDE_PROJECT_DIR}", () => {
110
+ mkdirSync(join(consumer, ".claude"), { recursive: true });
111
+ writeFileSync(
112
+ join(consumer, ".claude", "settings.json"),
113
+ JSON.stringify({
114
+ hooks: {
115
+ PostToolUse: [
116
+ {
117
+ matcher: "Edit|MultiEdit|Write",
118
+ hooks: [
119
+ {
120
+ type: "command",
121
+ command: '"${CLAUDE_PROJECT_DIR}/.agents/hooks/imf-web-ui/post-tool-use.sh"',
122
+ statusMessage: "Verifying file"
123
+ }
124
+ ]
125
+ },
126
+ // a foreign entry carrying no `command` at all must not throw
127
+ { matcher: "Bash", hooks: [{ type: "command", args: ["${CLAUDE_PROJECT_DIR}/other/gate.mjs"] }] }
128
+ ]
129
+ }
130
+ })
131
+ );
132
+
133
+ runInstall(consumer, "--hooks");
134
+
135
+ const settings = readSettings(consumer);
136
+ const commands = settings.hooks.PostToolUse.flatMap(entry => entry.hooks.map(hook => hook.command));
137
+ expect(commands.filter(command => command?.includes("post-tool-use.sh"))).toHaveLength(1);
138
+ expect(commands).toContain('"${CLAUDE_PROJECT_DIR}/.agents/hooks/imf-web-ui/post-tool-use.sh"');
139
+ });
140
+
141
+ it("does not touch settings.json when every registration is already present", () => {
142
+ runInstall(consumer, "--hooks");
143
+ const settingsFile = join(consumer, ".claude", "settings.json");
144
+ const settings = readSettings(consumer);
145
+ settings.hooks.PreToolUse = [{ hooks: [{ command: "./other/gate.mjs" }] }];
146
+ const before = JSON.stringify(settings, null, 4) + "\n";
147
+ writeFileSync(settingsFile, before);
148
+
149
+ const result = runInstall(consumer, "--hooks");
150
+
151
+ expect(readFileSync(settingsFile, "utf-8")).toBe(before);
152
+ expect(result.stdout).toContain("Hook registrations already present");
153
+ });
154
+
155
+ it("upserts the fenced AGENTS.md block and leaves the rest untouched", () => {
156
+ const agentsPath = join(consumer, "AGENTS.md");
157
+ writeFileSync(
158
+ agentsPath,
159
+ "# AGENTS.md\n\nRepo-specific intro.\n\n<!-- imf-web-ui:begin stale -->\nold content\n<!-- imf-web-ui:end -->\n\nRepo-specific outro.\n"
160
+ );
161
+
162
+ runInstall(consumer, "--skills", "--target", "agents");
163
+ const updated = readFileSync(agentsPath, "utf-8");
164
+ expect(updated).toContain("Repo-specific intro.");
165
+ expect(updated).toContain("Repo-specific outro.");
166
+ expect(updated).not.toContain("old content");
167
+ expect(updated).toContain("resynced with `npx web-ui-install`");
168
+
169
+ // no fence yet → block is appended; missing AGENTS.md entirely → left alone
170
+ writeFileSync(agentsPath, "# AGENTS.md\n\nNo fence here.\n");
171
+ runInstall(consumer, "--skills", "--target", "agents");
172
+ const appended = readFileSync(agentsPath, "utf-8");
173
+ expect(appended).toContain("No fence here.");
174
+ expect(appended).toContain("<!-- imf-web-ui:begin");
175
+ });
176
+
177
+ it("ships hooks that stay advisory: exit 0 with and without a usable payload", () => {
178
+ runInstall(consumer, "--hooks");
179
+ const hookDir = join(consumer, ".agents", "hooks", "imf-web-ui");
180
+
181
+ const noFile = spawnSync("sh", [join(hookDir, "post-tool-use.sh")], { input: "{}", encoding: "utf-8" });
182
+ expect(noFile.status).toBe(0);
183
+
184
+ const missingFile = spawnSync("sh", [join(hookDir, "post-tool-use.sh")], {
185
+ input: JSON.stringify({ tool_input: { file_path: join(consumer, "nope.ts") } }),
186
+ encoding: "utf-8"
187
+ });
188
+ expect(missingFile.status).toBe(0);
189
+
190
+ const sessionStart = spawnSync("sh", [join(hookDir, "session-start.sh")], { encoding: "utf-8" });
191
+ expect(sessionStart.status).toBe(0);
192
+ expect(sessionStart.stdout).toContain("imf-web-ui-frontend-conventions");
193
+
194
+ // no node_modules in the consumer → silent success
195
+ const staleness = spawnSync("sh", [join(hookDir, "baseline-staleness.sh")], { cwd: consumer, encoding: "utf-8" });
196
+ expect(staleness.status).toBe(0);
197
+ expect(staleness.stdout).toBe("");
198
+ });
199
+
200
+ it("runs post-edit checks from the package containing the edited file", () => {
201
+ runInstall(consumer, "--hooks");
202
+
203
+ const frontend = join(consumer, "frontend");
204
+ const source = join(frontend, "src", "example.ts");
205
+ const binDir = join(frontend, "node_modules", ".bin");
206
+ const log = join(consumer, "hook.log");
207
+ mkdirSync(join(frontend, "src"), { recursive: true });
208
+ mkdirSync(binDir, { recursive: true });
209
+ writeFileSync(join(frontend, "package.json"), JSON.stringify({ private: true }));
210
+ writeFileSync(source, "export const example = true;\n");
211
+
212
+ for (const tool of ["eslint", "prettier"]) {
213
+ const path = join(binDir, tool);
214
+ writeFileSync(path, '#!/usr/bin/env sh\nprintf "%s|%s\\n" "$PWD" "$*" >> "$HOOK_LOG"\n');
215
+ chmodSync(path, 0o755);
216
+ }
217
+
218
+ const result = spawnSync("sh", [join(consumer, ".agents", "hooks", "imf-web-ui", "post-tool-use.sh")], {
219
+ cwd: consumer,
220
+ env: { ...process.env, HOOK_LOG: log },
221
+ input: JSON.stringify({ tool_input: { file_path: source } }),
222
+ encoding: "utf-8"
223
+ });
224
+
225
+ expect(result.status).toBe(0);
226
+ const calls = readFileSync(log, "utf-8").trim().split("\n");
227
+ expect(calls).toEqual([`${frontend}|--cache ${source}`, `${frontend}|--check ${source}`]);
228
+ });
229
+
230
+ it("warns from the staleness script when skill markers lag the package", () => {
231
+ runInstall(consumer, "--skills", "--hooks", "--target", "agents");
232
+ const pkgDir = join(consumer, "node_modules", "@imfusion", "web-ui");
233
+ mkdirSync(pkgDir, { recursive: true });
234
+ writeFileSync(join(pkgDir, "package.json"), JSON.stringify({ version: "99.0.0" }));
235
+
236
+ const result = spawnSync("sh", [join(consumer, ".agents", "hooks", "imf-web-ui", "baseline-staleness.sh")], {
237
+ cwd: consumer,
238
+ encoding: "utf-8"
239
+ });
240
+ expect(result.status).toBe(0);
241
+ expect(result.stdout).toContain("npx web-ui-install");
242
+ });
243
+ });
@@ -0,0 +1,20 @@
1
+ import { Plugin } from 'vite';
2
+ export interface ReadableCssModuleNamesOptions {
3
+ /** Namespace for every generated class name. Short, stable, app-scoped (`imf-ui`, `acme`). */
4
+ prefix: string;
5
+ }
6
+ /**
7
+ * Generates readable CSS Module class names instead of the default hash, so the DOM
8
+ * stays legible in devtools and browser automation. `button.module.css` yields
9
+ * `{prefix}-button-module-root` — `[name]` is the filename, which keeps its `.module`
10
+ * suffix.
11
+ *
12
+ * Register it in every tool that compiles the CSS (the app build AND Storybook). A
13
+ * compiler left out generates different class names for the same source file and its
14
+ * styles silently don't apply.
15
+ *
16
+ * Defaults to opting into Lightning CSS, which drops `css.modules` options it has no
17
+ * equivalent for (`localsConvention`). Set `css.transformer: "postcss"` to stay on
18
+ * the PostCSS pipeline.
19
+ */
20
+ export declare function readableCssModuleNames({ prefix }: ReadableCssModuleNamesOptions): Plugin;
@@ -0,0 +1,17 @@
1
+ //#region src/build/vite-css-module-names/index.ts
2
+ function e(e, t) {
3
+ let { lightningcss: n, modules: r, transformer: i } = e.css ?? {}, a = n?.cssModules;
4
+ if (!(typeof a == "boolean" || r === !1) && !a?.pattern && !(typeof r == "object" && r.generateScopedName)) return i === "postcss" ? { css: { modules: { generateScopedName: t } } } : { css: {
5
+ transformer: "lightningcss",
6
+ lightningcss: { cssModules: { pattern: t } }
7
+ } };
8
+ }
9
+ function t({ prefix: t }) {
10
+ let n = `${t}-[name]-[local]`;
11
+ return {
12
+ name: "imf-ui:readable-css-module-names",
13
+ config: (t) => e(t, n)
14
+ };
15
+ }
16
+ //#endregion
17
+ export { t as readableCssModuleNames };
@@ -0,0 +1,104 @@
1
+ import { Field as FieldPrimitive } from '@base-ui/react/field';
2
+ type UpstreamRoot = React.ComponentProps<typeof FieldPrimitive.Root>;
3
+ type UpstreamItem = React.ComponentProps<typeof FieldPrimitive.Item>;
4
+ type UpstreamLabel = React.ComponentProps<typeof FieldPrimitive.Label>;
5
+ type UpstreamDescription = React.ComponentProps<typeof FieldPrimitive.Description>;
6
+ type UpstreamControl = React.ComponentProps<typeof FieldPrimitive.Control>;
7
+ type UpstreamError = React.ComponentProps<typeof FieldPrimitive.Error>;
8
+ type UpstreamValidity = React.ComponentProps<typeof FieldPrimitive.Validity>;
9
+ type RootProps = Omit<React.HTMLAttributes<HTMLDivElement>, "className" | "style"> & {
10
+ name?: string;
11
+ actionsRef?: UpstreamRoot["actionsRef"];
12
+ dirty?: boolean;
13
+ touched?: boolean;
14
+ /** @default false */
15
+ disabled?: boolean;
16
+ invalid?: boolean;
17
+ validate?: UpstreamRoot["validate"];
18
+ /** @default "onSubmit" */
19
+ validationMode?: UpstreamRoot["validationMode"];
20
+ /** @default 0 */
21
+ validationDebounceTime?: number;
22
+ className?: UpstreamRoot["className"];
23
+ style?: UpstreamRoot["style"];
24
+ render?: UpstreamRoot["render"];
25
+ };
26
+ /**
27
+ * Field.Root — groups a label, control, validation state, and supporting copy.
28
+ *
29
+ * @doc https://base-ui.com/react/components/field
30
+ * @llm https://base-ui.com/react/components/field.md
31
+ */
32
+ export declare function Root({ className, ...props }: RootProps): import("react/jsx-runtime").JSX.Element;
33
+ export declare namespace Root {
34
+ var displayName: string;
35
+ }
36
+ type ItemProps = Omit<React.HTMLAttributes<HTMLDivElement>, "className" | "style"> & {
37
+ /** @default false */
38
+ disabled?: boolean;
39
+ className?: UpstreamItem["className"];
40
+ style?: UpstreamItem["style"];
41
+ render?: UpstreamItem["render"];
42
+ };
43
+ /** Field.Item — groups one checkable option with its label and description. */
44
+ export declare function Item({ className, ...props }: ItemProps): import("react/jsx-runtime").JSX.Element;
45
+ export declare namespace Item {
46
+ var displayName: string;
47
+ }
48
+ type LabelProps = Omit<React.LabelHTMLAttributes<HTMLLabelElement>, "className" | "style"> & {
49
+ /** @default true */
50
+ nativeLabel?: boolean;
51
+ className?: UpstreamLabel["className"];
52
+ style?: UpstreamLabel["style"];
53
+ render?: UpstreamLabel["render"];
54
+ };
55
+ /** Field.Label — an accessible label automatically associated with the control. */
56
+ export declare function Label({ className, ...props }: LabelProps): import("react/jsx-runtime").JSX.Element;
57
+ export declare namespace Label {
58
+ var displayName: string;
59
+ }
60
+ type DescriptionProps = Omit<React.HTMLAttributes<HTMLParagraphElement>, "className" | "style"> & {
61
+ className?: UpstreamDescription["className"];
62
+ style?: UpstreamDescription["style"];
63
+ render?: UpstreamDescription["render"];
64
+ };
65
+ /** Field.Description — supporting information for a field. */
66
+ export declare function Description({ className, ...props }: DescriptionProps): import("react/jsx-runtime").JSX.Element;
67
+ export declare namespace Description {
68
+ var displayName: string;
69
+ }
70
+ type ControlProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, "className" | "style" | "defaultValue"> & {
71
+ defaultValue?: UpstreamControl["defaultValue"];
72
+ onValueChange?: UpstreamControl["onValueChange"];
73
+ className?: UpstreamControl["className"];
74
+ style?: UpstreamControl["style"];
75
+ render?: UpstreamControl["render"];
76
+ };
77
+ /** Field.Control — a native input control with Field labeling and validation. */
78
+ export declare function Control({ className, ...props }: ControlProps): import("react/jsx-runtime").JSX.Element;
79
+ export declare namespace Control {
80
+ var displayName: string;
81
+ }
82
+ type ErrorProps = Omit<React.HTMLAttributes<HTMLDivElement>, "className" | "style"> & {
83
+ match?: UpstreamError["match"];
84
+ className?: UpstreamError["className"];
85
+ style?: UpstreamError["style"];
86
+ render?: UpstreamError["render"];
87
+ };
88
+ /** Field.Error — an inline validation message shown when its match condition fails. */
89
+ export declare function Error({ className, ...props }: ErrorProps): import("react/jsx-runtime").JSX.Element;
90
+ export declare namespace Error {
91
+ var displayName: string;
92
+ }
93
+ type ValidityProps = {
94
+ children: UpstreamValidity["children"];
95
+ };
96
+ /**
97
+ * Field.Validity — renderless access to the field validity data for custom messages.
98
+ * Its function child owns the rendered element and therefore its identity and styling.
99
+ */
100
+ export declare function Validity({ children }: ValidityProps): import("react/jsx-runtime").JSX.Element;
101
+ export declare namespace Validity {
102
+ var displayName: string;
103
+ }
104
+ export {};
@@ -0,0 +1,2 @@
1
+ import { ComponentMeta } from '../../types/meta';
2
+ export declare const fieldMeta: ComponentMeta;
@@ -0,0 +1,2 @@
1
+ export * as Field from './field';
2
+ export { fieldMeta } from './field.meta';
@@ -0,0 +1,29 @@
1
+ import { Fieldset as FieldsetPrimitive } from '@base-ui/react/fieldset';
2
+ type UpstreamRoot = React.ComponentProps<typeof FieldsetPrimitive.Root>;
3
+ type UpstreamLegend = React.ComponentProps<typeof FieldsetPrimitive.Legend>;
4
+ type RootProps = Omit<React.FieldsetHTMLAttributes<HTMLFieldSetElement>, "className" | "style"> & {
5
+ className?: UpstreamRoot["className"];
6
+ style?: UpstreamRoot["style"];
7
+ render?: UpstreamRoot["render"];
8
+ };
9
+ /**
10
+ * Fieldset.Root — semantic grouping for related form controls and a shared legend.
11
+ *
12
+ * @doc https://base-ui.com/react/components/fieldset
13
+ * @llm https://base-ui.com/react/components/fieldset.md
14
+ */
15
+ export declare function Root({ className, ...props }: RootProps): import("react/jsx-runtime").JSX.Element;
16
+ export declare namespace Root {
17
+ var displayName: string;
18
+ }
19
+ type LegendProps = Omit<React.HTMLAttributes<HTMLDivElement>, "className" | "style"> & {
20
+ className?: UpstreamLegend["className"];
21
+ style?: UpstreamLegend["style"];
22
+ render?: UpstreamLegend["render"];
23
+ };
24
+ /** Fieldset.Legend — the accessible label for its related controls. */
25
+ export declare function Legend({ className, ...props }: LegendProps): import("react/jsx-runtime").JSX.Element;
26
+ export declare namespace Legend {
27
+ var displayName: string;
28
+ }
29
+ export {};
@@ -0,0 +1,2 @@
1
+ import { ComponentMeta } from '../../types/meta';
2
+ export declare const fieldsetMeta: ComponentMeta;
@@ -0,0 +1,2 @@
1
+ export * as Fieldset from './fieldset';
2
+ export { fieldsetMeta } from './fieldset.meta';
package/dist/index.d.ts CHANGED
@@ -2,6 +2,8 @@ export { Button, buttonMeta } from './components/button';
2
2
  export { ChipLink, chipLinkMeta } from './components/chip-link';
3
3
  export { CopyButton, copyButtonMeta } from './components/copy-button';
4
4
  export { Checkbox, checkboxMeta } from './components/checkbox';
5
+ export { Field, fieldMeta } from './components/field';
6
+ export { Fieldset, fieldsetMeta } from './components/fieldset';
5
7
  export { Input, inputMeta } from './components/input';
6
8
  export { Select, selectMeta } from './components/select';
7
9
  export { Slider, sliderMeta } from './components/slider';