react-form-ux 0.0.1 → 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sayan Paul
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,11 +1,188 @@
1
1
  # react-form-ux
2
2
 
3
- UX helpers for React forms.
3
+ Fix common React form UX problems in seconds.
4
4
 
5
- Features:
5
+ `react-form-ux` is a lightweight utility library that improves the **user experience of React forms** by handling common UX tasks like:
6
6
 
7
- - Focus first invalid field
8
- - Scroll to first error
9
- - Accessible error summaries
7
+ - focusing the first invalid field
8
+ - scrolling to validation errors
9
+ - generating accessible error summaries
10
10
 
11
- Work in progress.
11
+ The library is **headless and framework-agnostic**, so it works with any React form solution.
12
+
13
+ ---
14
+
15
+ ## ✨ Features
16
+
17
+ - Focus the **first invalid input automatically**
18
+ - Scroll to validation errors in long forms
19
+ - Generate **accessible error summaries**
20
+ - Works with **React Hook Form, Formik, or custom forms**
21
+ - Tiny bundle size
22
+ - Headless API (bring your own UI)
23
+
24
+ ---
25
+
26
+ ## 📦 Installation
27
+
28
+ ```bash
29
+ npm install react-form-ux
30
+ ```
31
+
32
+ or
33
+
34
+ ```bash
35
+ yarn add react-form-ux
36
+ ```
37
+
38
+ ---
39
+
40
+ ## ⚡Quick Example
41
+
42
+ ```javascript
43
+ import { useFormUX } from "react-form-ux";
44
+
45
+ function MyForm({ errors }) {
46
+ const { focusFirstError } = useFormUX({ errors });
47
+
48
+ const handleSubmit = () => {
49
+ if (Object.keys(errors).length > 0) {
50
+ focusFirstError();
51
+ }
52
+ };
53
+
54
+ return <form onSubmit={handleSubmit}>{/* form inputs */}</form>;
55
+ }
56
+ ```
57
+
58
+ ---
59
+
60
+ ## 🧠 The Problem
61
+
62
+ Most React form libraries focus on **form state and validation**, but developers still need to manually implement UX behaviors such as:
63
+
64
+ - focusing the first invalid input after submit
65
+ - scrolling long forms to the first error
66
+ - showing accessible error summaries
67
+ - guiding the user to fix validation issues
68
+
69
+ Example of common ad-hoc code developers write repeatedly:
70
+
71
+ ```javascript
72
+ const firstError = document.querySelector("[aria-invalid='true']");
73
+ firstError?.focus();
74
+ ```
75
+
76
+ This logic gets duplicated across projects.
77
+
78
+ `react-form-ux` provides **reusable UX primitives** so you don’t have to rewrite it.
79
+
80
+ ---
81
+
82
+ ## 🛠 Usage
83
+
84
+ Basic usage with any form library:
85
+
86
+ ```javascript
87
+ import { useFormUX } from "react-form-ux";
88
+
89
+ const { focusFirstError, scrollToError } = useFormUX({
90
+ errors,
91
+ });
92
+ ```
93
+
94
+ Available helpers:
95
+
96
+ | Function | Description |
97
+ | ----------------- | --------------------------------------------- |
98
+ | focusFirstError() | Focus the first invalid input field |
99
+ | scrollToError() | Scroll smoothly to the first validation error |
100
+ | getErrorFields() | Get a list of fields with validation errors |
101
+
102
+ ---
103
+
104
+ ## 📋 ErrorSummary Component
105
+
106
+ You can also display a summary of validation errors.
107
+
108
+ ```javascript
109
+ import { ErrorSummary } from "react-form-ux";
110
+
111
+ <ErrorSummary errors={errors} />
112
+ ```
113
+
114
+ This improves accessibility and helps users quickly identify issues in long forms.
115
+
116
+ ---
117
+
118
+ ## 🔗 Example with React Hook Form
119
+
120
+ ```javascript
121
+ import { useForm } from "react-hook-form";
122
+ import { useFormUX } from "react-form-ux";
123
+
124
+ function SignupForm() {
125
+ const { register, handleSubmit, formState } = useForm();
126
+
127
+ const { focusFirstError } = useFormUX({
128
+ errors: formState.errors
129
+ });
130
+
131
+ const onSubmit = () => {};
132
+
133
+ const onError = () => {
134
+ focusFirstError();
135
+ };
136
+
137
+ return (
138
+ <form onSubmit={handleSubmit(onSubmit, onError)}>
139
+ <input {...register("email")} />
140
+ <input {...register("password")} />
141
+
142
+ <button type="submit">Submit</button>
143
+ </form>
144
+ );
145
+ }
146
+ ```
147
+
148
+ ---
149
+
150
+ ## ⚙️ Compatibility
151
+
152
+ `react-form-ux` works with:
153
+
154
+ - React 18+
155
+ - React Hook Form
156
+ - Formik
157
+ - Custom React forms
158
+
159
+ The library does not depend on any specific form framework.
160
+
161
+ ---
162
+
163
+ ## 🚧 Status
164
+
165
+ Early development.
166
+
167
+ Current features:
168
+
169
+ - `focusFirstError`
170
+ - `scrollToError`
171
+ - `getErrorFields`
172
+ - `ErrorSummary` component
173
+
174
+ More improvements coming soon.
175
+
176
+ ---
177
+
178
+ ## 📜 License
179
+
180
+ MIT License
181
+
182
+ ---
183
+
184
+ ## 🤝 Contributing
185
+
186
+ Contributions and ideas are welcome.
187
+
188
+ If you find a bug or have a feature suggestion, please open an issue.
package/dist/index.cjs ADDED
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ ErrorSummary: () => ErrorSummary,
24
+ useFormUX: () => useFormUX
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+
28
+ // src/utils/getErrorFields.ts
29
+ function getErrorFields(errors, parent = "") {
30
+ const fields = [];
31
+ for (const key in errors) {
32
+ const value = errors[key];
33
+ const path = parent ? `${parent}.${key}` : key;
34
+ if (value && typeof value === "object" && !value.message) {
35
+ fields.push(...getErrorFields(value, path));
36
+ } else {
37
+ fields.push(path);
38
+ }
39
+ }
40
+ return fields;
41
+ }
42
+
43
+ // src/utils/focusFirstError.ts
44
+ function focusFirstError(errors) {
45
+ const fields = getErrorFields(errors);
46
+ if (!fields.length) return;
47
+ const firstField = fields[0];
48
+ const element = document.querySelector(
49
+ `[name="${firstField}"]`
50
+ );
51
+ element == null ? void 0 : element.focus();
52
+ }
53
+
54
+ // src/utils/scrollToError.ts
55
+ function scrollToError(errors) {
56
+ const fields = getErrorFields(errors);
57
+ if (!fields.length) return;
58
+ const firstField = fields[0];
59
+ const element = document.querySelector(
60
+ `[name="${firstField}"]`
61
+ );
62
+ element == null ? void 0 : element.scrollIntoView({
63
+ behavior: "smooth",
64
+ block: "center"
65
+ });
66
+ }
67
+
68
+ // src/hooks/useFormUX.ts
69
+ function useFormUX(options) {
70
+ const { errors } = options;
71
+ return {
72
+ focusFirstError: () => focusFirstError(errors),
73
+ scrollToError: () => scrollToError(errors),
74
+ getErrorFields: () => getErrorFields(errors)
75
+ };
76
+ }
77
+
78
+ // src/components/ErrorSummary.tsx
79
+ var import_jsx_runtime = require("react/jsx-runtime");
80
+ function ErrorSummary({
81
+ errors,
82
+ title = "Please fix the following errors:"
83
+ }) {
84
+ const fields = getErrorFields(errors);
85
+ if (!fields.length) return null;
86
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { role: "alert", "aria-live": "assertive", children: [
87
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: title }),
88
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { children: fields.map((field) => {
89
+ var _a, _b;
90
+ const message = (_b = (_a = errors == null ? void 0 : errors[field]) == null ? void 0 : _a.message) != null ? _b : "Invalid field";
91
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
92
+ "button",
93
+ {
94
+ type: "button",
95
+ onClick: () => {
96
+ const element = document.querySelector(
97
+ `[name="${field}"]`
98
+ );
99
+ element == null ? void 0 : element.focus();
100
+ },
101
+ children: message
102
+ }
103
+ ) }, field);
104
+ }) })
105
+ ] });
106
+ }
107
+ // Annotate the CommonJS export names for ESM import in node:
108
+ 0 && (module.exports = {
109
+ ErrorSummary,
110
+ useFormUX
111
+ });
112
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/utils/getErrorFields.ts","../src/utils/focusFirstError.ts","../src/utils/scrollToError.ts","../src/hooks/useFormUX.ts","../src/components/ErrorSummary.tsx"],"sourcesContent":["export * from \"./hooks/useFormUX\"\r\nexport * from \"./components/ErrorSummary\"","export function getErrorFields(\r\n errors: Record<string, any>,\r\n parent = \"\"\r\n): string[] {\r\n const fields: string[] = []\r\n\r\n for (const key in errors) {\r\n const value = errors[key]\r\n\r\n const path = parent ? `${parent}.${key}` : key\r\n\r\n if (value && typeof value === \"object\" && !value.message) {\r\n fields.push(...getErrorFields(value, path))\r\n } else {\r\n fields.push(path)\r\n }\r\n }\r\n\r\n return fields\r\n}","import { getErrorFields } from \"./getErrorFields\"\r\n\r\nexport function focusFirstError(errors: Record<string, unknown>) {\r\n const fields = getErrorFields(errors)\r\n\r\n if (!fields.length) return\r\n\r\n const firstField = fields[0]\r\n\r\n const element = document.querySelector(\r\n `[name=\"${firstField}\"]`\r\n ) as HTMLElement | null\r\n\r\n element?.focus()\r\n}","import { getErrorFields } from \"./getErrorFields\"\r\n\r\nexport function scrollToError(errors: Record<string, unknown>) {\r\n const fields = getErrorFields(errors)\r\n\r\n if (!fields.length) return\r\n\r\n const firstField = fields[0]\r\n\r\n const element = document.querySelector(\r\n `[name=\"${firstField}\"]`\r\n )\r\n\r\n element?.scrollIntoView({\r\n behavior: \"smooth\",\r\n block: \"center\"\r\n })\r\n}","import { UseFormUXOptions, UseFormUXReturn } from \"../types/formUX.types\"\r\nimport { focusFirstError } from \"../utils/focusFirstError\"\r\nimport { scrollToError } from \"../utils/scrollToError\"\r\nimport { getErrorFields } from \"../utils/getErrorFields\"\r\n\r\nexport function useFormUX(options: UseFormUXOptions): UseFormUXReturn {\r\n const { errors } = options\r\n\r\n return {\r\n focusFirstError: () => focusFirstError(errors),\r\n scrollToError: () => scrollToError(errors),\r\n getErrorFields: () => getErrorFields(errors)\r\n }\r\n}","import React from \"react\"\r\nimport { ErrorSummaryProps } from \"../types/formUX.types\"\r\nimport { getErrorFields } from \"../utils/getErrorFields\"\r\n\r\nexport function ErrorSummary({\r\n errors,\r\n title = \"Please fix the following errors:\"\r\n}: ErrorSummaryProps) {\r\n const fields = getErrorFields(errors)\r\n\r\n if (!fields.length) return null\r\n\r\n return (\r\n <div role=\"alert\" aria-live=\"assertive\">\r\n <strong>{title}</strong>\r\n\r\n <ul>\r\n {fields.map((field) => {\r\n const message = errors?.[field]?.message ?? \"Invalid field\"\r\n\r\n return (\r\n <li key={field}>\r\n <button\r\n type=\"button\"\r\n onClick={() => {\r\n const element = document.querySelector(\r\n `[name=\"${field}\"]`\r\n ) as HTMLElement | null\r\n\r\n element?.focus()\r\n }}\r\n >\r\n {message}\r\n </button>\r\n </li>\r\n )\r\n })}\r\n </ul>\r\n </div>\r\n )\r\n}"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAO,SAAS,eACd,QACA,SAAS,IACC;AACV,QAAM,SAAmB,CAAC;AAE1B,aAAW,OAAO,QAAQ;AACxB,UAAM,QAAQ,OAAO,GAAG;AAExB,UAAM,OAAO,SAAS,GAAG,MAAM,IAAI,GAAG,KAAK;AAE3C,QAAI,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,SAAS;AACxD,aAAO,KAAK,GAAG,eAAe,OAAO,IAAI,CAAC;AAAA,IAC5C,OAAO;AACL,aAAO,KAAK,IAAI;AAAA,IAClB;AAAA,EACF;AAEA,SAAO;AACT;;;ACjBO,SAAS,gBAAgB,QAAiC;AAC/D,QAAM,SAAS,eAAe,MAAM;AAEpC,MAAI,CAAC,OAAO,OAAQ;AAEpB,QAAM,aAAa,OAAO,CAAC;AAE3B,QAAM,UAAU,SAAS;AAAA,IACvB,UAAU,UAAU;AAAA,EACtB;AAEA,qCAAS;AACX;;;ACZO,SAAS,cAAc,QAAiC;AAC7D,QAAM,SAAS,eAAe,MAAM;AAEpC,MAAI,CAAC,OAAO,OAAQ;AAEpB,QAAM,aAAa,OAAO,CAAC;AAE3B,QAAM,UAAU,SAAS;AAAA,IACvB,UAAU,UAAU;AAAA,EACtB;AAEA,qCAAS,eAAe;AAAA,IACtB,UAAU;AAAA,IACV,OAAO;AAAA,EACT;AACF;;;ACZO,SAAS,UAAU,SAA4C;AACpE,QAAM,EAAE,OAAO,IAAI;AAEnB,SAAO;AAAA,IACL,iBAAiB,MAAM,gBAAgB,MAAM;AAAA,IAC7C,eAAe,MAAM,cAAc,MAAM;AAAA,IACzC,gBAAgB,MAAM,eAAe,MAAM;AAAA,EAC7C;AACF;;;ACAI;AATG,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA,QAAQ;AACV,GAAsB;AACpB,QAAM,SAAS,eAAe,MAAM;AAEpC,MAAI,CAAC,OAAO,OAAQ,QAAO;AAE3B,SACE,6CAAC,SAAI,MAAK,SAAQ,aAAU,aAC1B;AAAA,gDAAC,YAAQ,iBAAM;AAAA,IAEf,4CAAC,QACE,iBAAO,IAAI,CAAC,UAAU;AAjB/B;AAkBU,YAAM,WAAU,4CAAS,WAAT,mBAAiB,YAAjB,YAA4B;AAE5C,aACE,4CAAC,QACC;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS,MAAM;AACb,kBAAM,UAAU,SAAS;AAAA,cACvB,UAAU,KAAK;AAAA,YACjB;AAEA,+CAAS;AAAA,UACX;AAAA,UAEC;AAAA;AAAA,MACH,KAZO,KAaT;AAAA,IAEJ,CAAC,GACH;AAAA,KACF;AAEJ;","names":[]}
@@ -0,0 +1,20 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ interface UseFormUXOptions {
4
+ errors: Record<string, unknown>;
5
+ }
6
+ interface UseFormUXReturn {
7
+ focusFirstError: () => void;
8
+ scrollToError: () => void;
9
+ getErrorFields: () => string[];
10
+ }
11
+ interface ErrorSummaryProps {
12
+ errors: Record<string, any>;
13
+ title?: string;
14
+ }
15
+
16
+ declare function useFormUX(options: UseFormUXOptions): UseFormUXReturn;
17
+
18
+ declare function ErrorSummary({ errors, title }: ErrorSummaryProps): react_jsx_runtime.JSX.Element | null;
19
+
20
+ export { ErrorSummary, useFormUX };
@@ -0,0 +1,20 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ interface UseFormUXOptions {
4
+ errors: Record<string, unknown>;
5
+ }
6
+ interface UseFormUXReturn {
7
+ focusFirstError: () => void;
8
+ scrollToError: () => void;
9
+ getErrorFields: () => string[];
10
+ }
11
+ interface ErrorSummaryProps {
12
+ errors: Record<string, any>;
13
+ title?: string;
14
+ }
15
+
16
+ declare function useFormUX(options: UseFormUXOptions): UseFormUXReturn;
17
+
18
+ declare function ErrorSummary({ errors, title }: ErrorSummaryProps): react_jsx_runtime.JSX.Element | null;
19
+
20
+ export { ErrorSummary, useFormUX };
package/dist/index.mjs ADDED
@@ -0,0 +1,84 @@
1
+ // src/utils/getErrorFields.ts
2
+ function getErrorFields(errors, parent = "") {
3
+ const fields = [];
4
+ for (const key in errors) {
5
+ const value = errors[key];
6
+ const path = parent ? `${parent}.${key}` : key;
7
+ if (value && typeof value === "object" && !value.message) {
8
+ fields.push(...getErrorFields(value, path));
9
+ } else {
10
+ fields.push(path);
11
+ }
12
+ }
13
+ return fields;
14
+ }
15
+
16
+ // src/utils/focusFirstError.ts
17
+ function focusFirstError(errors) {
18
+ const fields = getErrorFields(errors);
19
+ if (!fields.length) return;
20
+ const firstField = fields[0];
21
+ const element = document.querySelector(
22
+ `[name="${firstField}"]`
23
+ );
24
+ element == null ? void 0 : element.focus();
25
+ }
26
+
27
+ // src/utils/scrollToError.ts
28
+ function scrollToError(errors) {
29
+ const fields = getErrorFields(errors);
30
+ if (!fields.length) return;
31
+ const firstField = fields[0];
32
+ const element = document.querySelector(
33
+ `[name="${firstField}"]`
34
+ );
35
+ element == null ? void 0 : element.scrollIntoView({
36
+ behavior: "smooth",
37
+ block: "center"
38
+ });
39
+ }
40
+
41
+ // src/hooks/useFormUX.ts
42
+ function useFormUX(options) {
43
+ const { errors } = options;
44
+ return {
45
+ focusFirstError: () => focusFirstError(errors),
46
+ scrollToError: () => scrollToError(errors),
47
+ getErrorFields: () => getErrorFields(errors)
48
+ };
49
+ }
50
+
51
+ // src/components/ErrorSummary.tsx
52
+ import { jsx, jsxs } from "react/jsx-runtime";
53
+ function ErrorSummary({
54
+ errors,
55
+ title = "Please fix the following errors:"
56
+ }) {
57
+ const fields = getErrorFields(errors);
58
+ if (!fields.length) return null;
59
+ return /* @__PURE__ */ jsxs("div", { role: "alert", "aria-live": "assertive", children: [
60
+ /* @__PURE__ */ jsx("strong", { children: title }),
61
+ /* @__PURE__ */ jsx("ul", { children: fields.map((field) => {
62
+ var _a, _b;
63
+ const message = (_b = (_a = errors == null ? void 0 : errors[field]) == null ? void 0 : _a.message) != null ? _b : "Invalid field";
64
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
65
+ "button",
66
+ {
67
+ type: "button",
68
+ onClick: () => {
69
+ const element = document.querySelector(
70
+ `[name="${field}"]`
71
+ );
72
+ element == null ? void 0 : element.focus();
73
+ },
74
+ children: message
75
+ }
76
+ ) }, field);
77
+ }) })
78
+ ] });
79
+ }
80
+ export {
81
+ ErrorSummary,
82
+ useFormUX
83
+ };
84
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/getErrorFields.ts","../src/utils/focusFirstError.ts","../src/utils/scrollToError.ts","../src/hooks/useFormUX.ts","../src/components/ErrorSummary.tsx"],"sourcesContent":["export function getErrorFields(\r\n errors: Record<string, any>,\r\n parent = \"\"\r\n): string[] {\r\n const fields: string[] = []\r\n\r\n for (const key in errors) {\r\n const value = errors[key]\r\n\r\n const path = parent ? `${parent}.${key}` : key\r\n\r\n if (value && typeof value === \"object\" && !value.message) {\r\n fields.push(...getErrorFields(value, path))\r\n } else {\r\n fields.push(path)\r\n }\r\n }\r\n\r\n return fields\r\n}","import { getErrorFields } from \"./getErrorFields\"\r\n\r\nexport function focusFirstError(errors: Record<string, unknown>) {\r\n const fields = getErrorFields(errors)\r\n\r\n if (!fields.length) return\r\n\r\n const firstField = fields[0]\r\n\r\n const element = document.querySelector(\r\n `[name=\"${firstField}\"]`\r\n ) as HTMLElement | null\r\n\r\n element?.focus()\r\n}","import { getErrorFields } from \"./getErrorFields\"\r\n\r\nexport function scrollToError(errors: Record<string, unknown>) {\r\n const fields = getErrorFields(errors)\r\n\r\n if (!fields.length) return\r\n\r\n const firstField = fields[0]\r\n\r\n const element = document.querySelector(\r\n `[name=\"${firstField}\"]`\r\n )\r\n\r\n element?.scrollIntoView({\r\n behavior: \"smooth\",\r\n block: \"center\"\r\n })\r\n}","import { UseFormUXOptions, UseFormUXReturn } from \"../types/formUX.types\"\r\nimport { focusFirstError } from \"../utils/focusFirstError\"\r\nimport { scrollToError } from \"../utils/scrollToError\"\r\nimport { getErrorFields } from \"../utils/getErrorFields\"\r\n\r\nexport function useFormUX(options: UseFormUXOptions): UseFormUXReturn {\r\n const { errors } = options\r\n\r\n return {\r\n focusFirstError: () => focusFirstError(errors),\r\n scrollToError: () => scrollToError(errors),\r\n getErrorFields: () => getErrorFields(errors)\r\n }\r\n}","import React from \"react\"\r\nimport { ErrorSummaryProps } from \"../types/formUX.types\"\r\nimport { getErrorFields } from \"../utils/getErrorFields\"\r\n\r\nexport function ErrorSummary({\r\n errors,\r\n title = \"Please fix the following errors:\"\r\n}: ErrorSummaryProps) {\r\n const fields = getErrorFields(errors)\r\n\r\n if (!fields.length) return null\r\n\r\n return (\r\n <div role=\"alert\" aria-live=\"assertive\">\r\n <strong>{title}</strong>\r\n\r\n <ul>\r\n {fields.map((field) => {\r\n const message = errors?.[field]?.message ?? \"Invalid field\"\r\n\r\n return (\r\n <li key={field}>\r\n <button\r\n type=\"button\"\r\n onClick={() => {\r\n const element = document.querySelector(\r\n `[name=\"${field}\"]`\r\n ) as HTMLElement | null\r\n\r\n element?.focus()\r\n }}\r\n >\r\n {message}\r\n </button>\r\n </li>\r\n )\r\n })}\r\n </ul>\r\n </div>\r\n )\r\n}"],"mappings":";AAAO,SAAS,eACd,QACA,SAAS,IACC;AACV,QAAM,SAAmB,CAAC;AAE1B,aAAW,OAAO,QAAQ;AACxB,UAAM,QAAQ,OAAO,GAAG;AAExB,UAAM,OAAO,SAAS,GAAG,MAAM,IAAI,GAAG,KAAK;AAE3C,QAAI,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,SAAS;AACxD,aAAO,KAAK,GAAG,eAAe,OAAO,IAAI,CAAC;AAAA,IAC5C,OAAO;AACL,aAAO,KAAK,IAAI;AAAA,IAClB;AAAA,EACF;AAEA,SAAO;AACT;;;ACjBO,SAAS,gBAAgB,QAAiC;AAC/D,QAAM,SAAS,eAAe,MAAM;AAEpC,MAAI,CAAC,OAAO,OAAQ;AAEpB,QAAM,aAAa,OAAO,CAAC;AAE3B,QAAM,UAAU,SAAS;AAAA,IACvB,UAAU,UAAU;AAAA,EACtB;AAEA,qCAAS;AACX;;;ACZO,SAAS,cAAc,QAAiC;AAC7D,QAAM,SAAS,eAAe,MAAM;AAEpC,MAAI,CAAC,OAAO,OAAQ;AAEpB,QAAM,aAAa,OAAO,CAAC;AAE3B,QAAM,UAAU,SAAS;AAAA,IACvB,UAAU,UAAU;AAAA,EACtB;AAEA,qCAAS,eAAe;AAAA,IACtB,UAAU;AAAA,IACV,OAAO;AAAA,EACT;AACF;;;ACZO,SAAS,UAAU,SAA4C;AACpE,QAAM,EAAE,OAAO,IAAI;AAEnB,SAAO;AAAA,IACL,iBAAiB,MAAM,gBAAgB,MAAM;AAAA,IAC7C,eAAe,MAAM,cAAc,MAAM;AAAA,IACzC,gBAAgB,MAAM,eAAe,MAAM;AAAA,EAC7C;AACF;;;ACAI,SACE,KADF;AATG,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA,QAAQ;AACV,GAAsB;AACpB,QAAM,SAAS,eAAe,MAAM;AAEpC,MAAI,CAAC,OAAO,OAAQ,QAAO;AAE3B,SACE,qBAAC,SAAI,MAAK,SAAQ,aAAU,aAC1B;AAAA,wBAAC,YAAQ,iBAAM;AAAA,IAEf,oBAAC,QACE,iBAAO,IAAI,CAAC,UAAU;AAjB/B;AAkBU,YAAM,WAAU,4CAAS,WAAT,mBAAiB,YAAjB,YAA4B;AAE5C,aACE,oBAAC,QACC;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS,MAAM;AACb,kBAAM,UAAU,SAAS;AAAA,cACvB,UAAU,KAAK;AAAA,YACjB;AAEA,+CAAS;AAAA,UACX;AAAA,UAEC;AAAA;AAAA,MACH,KAZO,KAaT;AAAA,IAEJ,CAAC,GACH;AAAA,KACF;AAEJ;","names":[]}
package/package.json CHANGED
@@ -1,20 +1,49 @@
1
1
  {
2
2
  "name": "react-form-ux",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
4
  "description": "UX helpers for React forms (focus errors, scroll to validation, error summaries).",
5
- "main": "index.js",
5
+ "main": "./dist/index.cjs",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.cjs"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
6
18
  "scripts": {
7
- "test": "echo \"Error: no test specified\" && exit 1"
19
+ "build": "tsup",
20
+ "dev": "tsup --watch",
21
+ "test": "vitest",
22
+ "test:ui": "vitest --ui"
23
+ },
24
+ "peerDependencies": {
25
+ "react": ">=18"
8
26
  },
9
27
  "keywords": [
10
28
  "react",
11
- "forms",
12
29
  "react-hook-form",
13
- "formik",
14
- "ux",
15
- "validation"
30
+ "forms",
31
+ "form-validation",
32
+ "form-ux",
33
+ "frontend",
34
+ "react-forms",
35
+ "validation",
36
+ "accessibility"
16
37
  ],
17
38
  "author": "Sayan Paul",
18
39
  "license": "MIT",
19
- "type": "commonjs"
40
+ "devDependencies": {
41
+ "@testing-library/jest-dom": "^6.9.1",
42
+ "@testing-library/react": "^16.3.2",
43
+ "@types/react": "^19.2.14",
44
+ "jsdom": "^28.1.0",
45
+ "tsup": "^8.5.1",
46
+ "typescript": "^5.9.3",
47
+ "vitest": "^4.0.18"
48
+ }
20
49
  }
package/index.js DELETED
@@ -1,2 +0,0 @@
1
- // Placeholder package
2
- module.exports = {}
Binary file