@behivetech/forms.form-field 0.0.0-stage → 0.3.2

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/README.md CHANGED
@@ -1,3 +1,38 @@
1
- # Temporary Holding Version
1
+ # FormField
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Dumb wrapper that provides label + hint/error + required-marker layout around any form control (`Checkbox`, `Input`, `Textarea`, `Select`, `Toggle`, ...) — the same job AntD's `Form.Item` does. The control itself owns none of this chrome; it just needs an `id` matching `htmlFor`.
4
+
5
+ ## Usage
6
+
7
+ ```jsx
8
+ import { FormField } from '@behivetech/forms.form-field';
9
+ import { Checkbox } from '@behivetech/atoms.checkbox';
10
+
11
+ <FormField label="Accept terms" htmlFor="terms" required>
12
+ <Checkbox id="terms" />
13
+ </FormField>
14
+
15
+ <FormField label="Email" htmlFor="email" hint="We'll never share this">
16
+ <input id="email" type="email" />
17
+ </FormField>
18
+
19
+ <FormField label="Email" htmlFor="email" error="A valid email is required">
20
+ <input id="email" type="email" />
21
+ </FormField>
22
+ ```
23
+
24
+ `hint` is hidden whenever `error` is present. If `label`, `hint`, and `error` are all omitted, `FormField` renders as a plain wrapper around `children`.
25
+
26
+ **Known limitation**: `FormField` doesn't automatically wire `aria-describedby` from the control to the hint/error text — pass it yourself if you need that linkage today.
27
+
28
+ ## Props
29
+
30
+ | Prop | Type | Default | Description |
31
+ | ----------- | ----------- | ------- | ----------------------------------------------------------------------------- |
32
+ | `label` | `ReactNode` | — | Field label |
33
+ | `htmlFor` | `string` | — | Associates the label with the control — pass the same id you give the control |
34
+ | `hint` | `ReactNode` | — | Supporting hint text; hidden while `error` is present |
35
+ | `error` | `ReactNode` | — | Error message; also visually marks the field as invalid |
36
+ | `required` | `boolean` | `false` | Shows a required marker next to the label |
37
+ | `children` | `ReactNode` | — | The form control this field wraps (required) |
38
+ | `className` | `string` | — | Additional class names |
package/dist/index.cjs ADDED
@@ -0,0 +1,187 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __commonJS = (cb, mod) => function __require() {
9
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
10
+ };
11
+ var __export = (target, all) => {
12
+ for (var name in all)
13
+ __defProp(target, name, { get: all[name], enumerable: true });
14
+ };
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") {
17
+ for (let key of __getOwnPropNames(from))
18
+ if (!__hasOwnProp.call(to, key) && key !== except)
19
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
20
+ }
21
+ return to;
22
+ };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
+ // If the importer is in node compatibility mode or this is not an ESM
25
+ // file that has been converted to a CommonJS file using a Babel-
26
+ // compatible transform (i.e. "__esModule" has not been set), then set
27
+ // "default" to the CommonJS "module.exports" for node compatibility.
28
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
+ mod
30
+ ));
31
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
+
33
+ // ../../../../node_modules/.pnpm/classnames@2.5.1/node_modules/classnames/index.js
34
+ var require_classnames = __commonJS({
35
+ "../../../../node_modules/.pnpm/classnames@2.5.1/node_modules/classnames/index.js"(exports2, module2) {
36
+ "use strict";
37
+ (function() {
38
+ "use strict";
39
+ var hasOwn = {}.hasOwnProperty;
40
+ function classNames() {
41
+ var classes = "";
42
+ for (var i = 0; i < arguments.length; i++) {
43
+ var arg = arguments[i];
44
+ if (arg) {
45
+ classes = appendClass(classes, parseValue(arg));
46
+ }
47
+ }
48
+ return classes;
49
+ }
50
+ function parseValue(arg) {
51
+ if (typeof arg === "string" || typeof arg === "number") {
52
+ return arg;
53
+ }
54
+ if (typeof arg !== "object") {
55
+ return "";
56
+ }
57
+ if (Array.isArray(arg)) {
58
+ return classNames.apply(null, arg);
59
+ }
60
+ if (arg.toString !== Object.prototype.toString && !arg.toString.toString().includes("[native code]")) {
61
+ return arg.toString();
62
+ }
63
+ var classes = "";
64
+ for (var key in arg) {
65
+ if (hasOwn.call(arg, key) && arg[key]) {
66
+ classes = appendClass(classes, key);
67
+ }
68
+ }
69
+ return classes;
70
+ }
71
+ function appendClass(value, newClass) {
72
+ if (!newClass) {
73
+ return value;
74
+ }
75
+ if (value) {
76
+ return value + " " + newClass;
77
+ }
78
+ return value + newClass;
79
+ }
80
+ if (typeof module2 !== "undefined" && module2.exports) {
81
+ classNames.default = classNames;
82
+ module2.exports = classNames;
83
+ } else if (typeof define === "function" && typeof define.amd === "object" && define.amd) {
84
+ define("classnames", [], function() {
85
+ return classNames;
86
+ });
87
+ } else {
88
+ window.classNames = classNames;
89
+ }
90
+ })();
91
+ }
92
+ });
93
+
94
+ // index.ts
95
+ var index_exports = {};
96
+ __export(index_exports, {
97
+ FormField: () => FormField
98
+ });
99
+ module.exports = __toCommonJS(index_exports);
100
+
101
+ // ../../../../packages/registry/class-names/dist/index.js
102
+ var import_classnames = __toESM(require_classnames(), 1);
103
+ var getClassName = ({
104
+ className,
105
+ modifiers = {},
106
+ rootClass,
107
+ styles = {}
108
+ }) => {
109
+ function getModifiers() {
110
+ const updatedModifiers = {};
111
+ const modifierKeys = Object.keys(modifiers);
112
+ if (modifierKeys.length) {
113
+ modifierKeys.forEach((modKey) => {
114
+ const classKey = `${rootClass}--${modKey}`;
115
+ const newClassName = styles[classKey] || classKey;
116
+ updatedModifiers[newClassName] = modifiers[modKey] ?? false;
117
+ });
118
+ }
119
+ return updatedModifiers;
120
+ }
121
+ function getChildClass(childClassName) {
122
+ const classKey = `${rootClass}__${childClassName}`;
123
+ return styles[classKey] || classKey;
124
+ }
125
+ function getRootStyles() {
126
+ return (0, import_classnames.default)(
127
+ className,
128
+ {
129
+ ...getModifiers()
130
+ },
131
+ styles[rootClass] || rootClass
132
+ );
133
+ }
134
+ return [getRootStyles(), getChildClass];
135
+ };
136
+
137
+ // form-field.module.scss
138
+ var form_field_module_default = {
139
+ "BHT__FormField": "BHT__FormField",
140
+ "BHT__FormField__label": "BHT__FormField__label",
141
+ "BHT__FormField__required": "BHT__FormField__required",
142
+ "BHT__FormField__hint": "BHT__FormField__hint",
143
+ "BHT__FormField__error": "BHT__FormField__error",
144
+ "BHT__FormField--error": "BHT__FormField--error"
145
+ };
146
+
147
+ // form-field.tsx
148
+ var import_jsx_runtime = require("react/jsx-runtime");
149
+ var FormField = ({
150
+ className,
151
+ label,
152
+ htmlFor,
153
+ hint,
154
+ error,
155
+ required,
156
+ children,
157
+ ...rest
158
+ }) => {
159
+ const [rootClass, getChildClass] = getClassName({
160
+ className,
161
+ rootClass: "BHT__FormField",
162
+ modifiers: { error: !!error },
163
+ styles: form_field_module_default
164
+ });
165
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { ...rest, className: rootClass, children: [
166
+ label !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: getChildClass("label"), htmlFor, children: [
167
+ label,
168
+ required && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: getChildClass("required"), "aria-hidden": "true", children: "*" })
169
+ ] }),
170
+ children,
171
+ hint !== void 0 && error === void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: getChildClass("hint"), children: hint }),
172
+ error !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: getChildClass("error"), role: "alert", children: error })
173
+ ] });
174
+ };
175
+ // Annotate the CommonJS export names for ESM import in node:
176
+ 0 && (module.exports = {
177
+ FormField
178
+ });
179
+ /*! Bundled license information:
180
+
181
+ classnames/index.js:
182
+ (*!
183
+ Copyright (c) 2018 Jed Watson.
184
+ Licensed under the MIT License (MIT), see
185
+ http://jedwatson.github.io/classnames
186
+ *)
187
+ */
package/dist/index.css ADDED
@@ -0,0 +1,41 @@
1
+ /* esbuild-sass-plugin:css-chunkform-field.module.scss */
2
+ .BHT__FormField {
3
+ display: flex;
4
+ flex-direction: column;
5
+ gap: var(--spacing-1, 0.25rem);
6
+ }
7
+ .BHT__FormField__label {
8
+ font-family: var(--md-sys-typescale-body-small-font, var(--font-body));
9
+ font-size: var(--md-sys-typescale-body-small-size, 0.75rem);
10
+ font-weight: var(--md-sys-typescale-body-small-weight, 400);
11
+ line-height: var(--md-sys-typescale-body-small-line-height, 1rem);
12
+ letter-spacing: var(--md-sys-typescale-body-small-tracking, 0.0333em);
13
+ color: var(--md-sys-color-on-surface-variant, var(--color-text-muted));
14
+ display: flex;
15
+ align-items: center;
16
+ gap: var(--spacing-1, 0.25rem);
17
+ transition: color var(--md-sys-motion-duration-short-3, 150ms) var(--md-sys-motion-easing-standard, ease);
18
+ }
19
+ .BHT__FormField__required {
20
+ color: var(--md-sys-color-error, var(--color-error));
21
+ }
22
+ .BHT__FormField__hint,
23
+ .BHT__FormField__error {
24
+ font-family: var(--md-sys-typescale-body-small-font, var(--font-body));
25
+ font-size: var(--md-sys-typescale-body-small-size, 0.75rem);
26
+ font-weight: var(--md-sys-typescale-body-small-weight, 400);
27
+ line-height: var(--md-sys-typescale-body-small-line-height, 1rem);
28
+ letter-spacing: var(--md-sys-typescale-body-small-tracking, 0.0333em);
29
+ }
30
+ .BHT__FormField__hint {
31
+ color: var(--md-sys-color-on-surface-variant, var(--color-text-muted));
32
+ }
33
+ .BHT__FormField__error {
34
+ color: var(--md-sys-color-error, var(--color-error));
35
+ }
36
+ .BHT__FormField:focus-within:not(.BHT__FormField--error) .BHT__FormField__label {
37
+ color: var(--md-sys-color-primary, var(--color-primary));
38
+ }
39
+ .BHT__FormField--error .BHT__FormField__label {
40
+ color: var(--md-sys-color-error, var(--color-error));
41
+ }
@@ -0,0 +1,28 @@
1
+ import * as react from 'react';
2
+ import { HTMLAttributes, ReactNode } from 'react';
3
+
4
+ interface FormFieldProps extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
5
+ /** Additional class names to merge with the component root element */
6
+ className?: string;
7
+ /** Field label */
8
+ label?: ReactNode;
9
+ /** Associates the label with the control — pass the same id you give the control */
10
+ htmlFor?: string;
11
+ /** Supporting hint text; hidden while `error` is present */
12
+ hint?: ReactNode;
13
+ /** Error message; also visually marks the field as invalid */
14
+ error?: ReactNode;
15
+ /** Shows a required marker next to the label */
16
+ required?: boolean;
17
+ /** The form control this field wraps */
18
+ children: ReactNode;
19
+ }
20
+ /**
21
+ * Dumb wrapper providing label + hint/error + required-marker layout around
22
+ * any form control (Checkbox, Input, Textarea, Select, Toggle, ...) — the
23
+ * same job AntD's `Form.Item` does. The control itself owns none of this
24
+ * chrome; it just needs an `id` matching `htmlFor`.
25
+ */
26
+ declare const FormField: ({ className, label, htmlFor, hint, error, required, children, ...rest }: FormFieldProps) => react.JSX.Element;
27
+
28
+ export { FormField, type FormFieldProps };
package/dist/index.js ADDED
@@ -0,0 +1,173 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __commonJS = (cb, mod) => function __require() {
8
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
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 __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
19
+ // If the importer is in node compatibility mode or this is not an ESM
20
+ // file that has been converted to a CommonJS file using a Babel-
21
+ // compatible transform (i.e. "__esModule" has not been set), then set
22
+ // "default" to the CommonJS "module.exports" for node compatibility.
23
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
24
+ mod
25
+ ));
26
+
27
+ // ../../../../node_modules/.pnpm/classnames@2.5.1/node_modules/classnames/index.js
28
+ var require_classnames = __commonJS({
29
+ "../../../../node_modules/.pnpm/classnames@2.5.1/node_modules/classnames/index.js"(exports, module) {
30
+ "use strict";
31
+ (function() {
32
+ "use strict";
33
+ var hasOwn = {}.hasOwnProperty;
34
+ function classNames() {
35
+ var classes = "";
36
+ for (var i = 0; i < arguments.length; i++) {
37
+ var arg = arguments[i];
38
+ if (arg) {
39
+ classes = appendClass(classes, parseValue(arg));
40
+ }
41
+ }
42
+ return classes;
43
+ }
44
+ function parseValue(arg) {
45
+ if (typeof arg === "string" || typeof arg === "number") {
46
+ return arg;
47
+ }
48
+ if (typeof arg !== "object") {
49
+ return "";
50
+ }
51
+ if (Array.isArray(arg)) {
52
+ return classNames.apply(null, arg);
53
+ }
54
+ if (arg.toString !== Object.prototype.toString && !arg.toString.toString().includes("[native code]")) {
55
+ return arg.toString();
56
+ }
57
+ var classes = "";
58
+ for (var key in arg) {
59
+ if (hasOwn.call(arg, key) && arg[key]) {
60
+ classes = appendClass(classes, key);
61
+ }
62
+ }
63
+ return classes;
64
+ }
65
+ function appendClass(value, newClass) {
66
+ if (!newClass) {
67
+ return value;
68
+ }
69
+ if (value) {
70
+ return value + " " + newClass;
71
+ }
72
+ return value + newClass;
73
+ }
74
+ if (typeof module !== "undefined" && module.exports) {
75
+ classNames.default = classNames;
76
+ module.exports = classNames;
77
+ } else if (typeof define === "function" && typeof define.amd === "object" && define.amd) {
78
+ define("classnames", [], function() {
79
+ return classNames;
80
+ });
81
+ } else {
82
+ window.classNames = classNames;
83
+ }
84
+ })();
85
+ }
86
+ });
87
+
88
+ // ../../../../packages/registry/class-names/dist/index.js
89
+ var import_classnames = __toESM(require_classnames(), 1);
90
+ var getClassName = ({
91
+ className,
92
+ modifiers = {},
93
+ rootClass,
94
+ styles = {}
95
+ }) => {
96
+ function getModifiers() {
97
+ const updatedModifiers = {};
98
+ const modifierKeys = Object.keys(modifiers);
99
+ if (modifierKeys.length) {
100
+ modifierKeys.forEach((modKey) => {
101
+ const classKey = `${rootClass}--${modKey}`;
102
+ const newClassName = styles[classKey] || classKey;
103
+ updatedModifiers[newClassName] = modifiers[modKey] ?? false;
104
+ });
105
+ }
106
+ return updatedModifiers;
107
+ }
108
+ function getChildClass(childClassName) {
109
+ const classKey = `${rootClass}__${childClassName}`;
110
+ return styles[classKey] || classKey;
111
+ }
112
+ function getRootStyles() {
113
+ return (0, import_classnames.default)(
114
+ className,
115
+ {
116
+ ...getModifiers()
117
+ },
118
+ styles[rootClass] || rootClass
119
+ );
120
+ }
121
+ return [getRootStyles(), getChildClass];
122
+ };
123
+
124
+ // form-field.module.scss
125
+ var form_field_module_default = {
126
+ "BHT__FormField": "BHT__FormField",
127
+ "BHT__FormField__label": "BHT__FormField__label",
128
+ "BHT__FormField__required": "BHT__FormField__required",
129
+ "BHT__FormField__hint": "BHT__FormField__hint",
130
+ "BHT__FormField__error": "BHT__FormField__error",
131
+ "BHT__FormField--error": "BHT__FormField--error"
132
+ };
133
+
134
+ // form-field.tsx
135
+ import { jsx, jsxs } from "react/jsx-runtime";
136
+ var FormField = ({
137
+ className,
138
+ label,
139
+ htmlFor,
140
+ hint,
141
+ error,
142
+ required,
143
+ children,
144
+ ...rest
145
+ }) => {
146
+ const [rootClass, getChildClass] = getClassName({
147
+ className,
148
+ rootClass: "BHT__FormField",
149
+ modifiers: { error: !!error },
150
+ styles: form_field_module_default
151
+ });
152
+ return /* @__PURE__ */ jsxs("div", { ...rest, className: rootClass, children: [
153
+ label !== void 0 && /* @__PURE__ */ jsxs("label", { className: getChildClass("label"), htmlFor, children: [
154
+ label,
155
+ required && /* @__PURE__ */ jsx("span", { className: getChildClass("required"), "aria-hidden": "true", children: "*" })
156
+ ] }),
157
+ children,
158
+ hint !== void 0 && error === void 0 && /* @__PURE__ */ jsx("span", { className: getChildClass("hint"), children: hint }),
159
+ error !== void 0 && /* @__PURE__ */ jsx("span", { className: getChildClass("error"), role: "alert", children: error })
160
+ ] });
161
+ };
162
+ export {
163
+ FormField
164
+ };
165
+ /*! Bundled license information:
166
+
167
+ classnames/index.js:
168
+ (*!
169
+ Copyright (c) 2018 Jed Watson.
170
+ Licensed under the MIT License (MIT), see
171
+ http://jedwatson.github.io/classnames
172
+ *)
173
+ */
package/docs.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "$schema": "https://github.com/behivetech/bht-component-registry/docs.schema.json",
3
+ "version": 2,
4
+ "generatedAt": "2026-10-20T05:49:19.774Z",
5
+ "docs": [
6
+ {
7
+ "displayName": "FormField",
8
+ "description": "Dumb wrapper providing label + hint/error + required-marker layout around\nany form control (Checkbox, Input, Textarea, Select, Toggle, ...) — the\nsame job AntD's `Form.Item` does. The control itself owns none of this\nchrome; it just needs an `id` matching `htmlFor`.",
9
+ "extendsNative": true,
10
+ "props": [
11
+ {
12
+ "name": "children",
13
+ "type": "ReactNode",
14
+ "required": true,
15
+ "defaultValue": null,
16
+ "description": "The form control this field wraps"
17
+ },
18
+ {
19
+ "name": "className",
20
+ "type": "string",
21
+ "required": false,
22
+ "defaultValue": null,
23
+ "description": "Additional class names to merge with the component root element"
24
+ },
25
+ {
26
+ "name": "error",
27
+ "type": "ReactNode",
28
+ "required": false,
29
+ "defaultValue": null,
30
+ "description": "Error message; also visually marks the field as invalid"
31
+ },
32
+ {
33
+ "name": "hint",
34
+ "type": "ReactNode",
35
+ "required": false,
36
+ "defaultValue": null,
37
+ "description": "Supporting hint text; hidden while `error` is present"
38
+ },
39
+ {
40
+ "name": "htmlFor",
41
+ "type": "string",
42
+ "required": false,
43
+ "defaultValue": null,
44
+ "description": "Associates the label with the control — pass the same id you give the control"
45
+ },
46
+ {
47
+ "name": "label",
48
+ "type": "ReactNode",
49
+ "required": false,
50
+ "defaultValue": null,
51
+ "description": "Field label"
52
+ },
53
+ {
54
+ "name": "required",
55
+ "type": "boolean",
56
+ "required": false,
57
+ "defaultValue": null,
58
+ "description": "Shows a required marker next to the label"
59
+ }
60
+ ]
61
+ }
62
+ ],
63
+ "functions": []
64
+ }
@@ -0,0 +1,29 @@
1
+ import { FormField } from "./form-field.js";
2
+
3
+ export const BasicFormField = () => (
4
+ <FormField label="Email" htmlFor="email">
5
+ <input id="email" type="email" />
6
+ </FormField>
7
+ );
8
+
9
+ export const RequiredFormField = () => (
10
+ <FormField label="Email" htmlFor="email-required" required>
11
+ <input id="email-required" type="email" />
12
+ </FormField>
13
+ );
14
+
15
+ export const FormFieldWithHint = () => (
16
+ <FormField label="Email" htmlFor="email-hint" hint="We'll never share this">
17
+ <input id="email-hint" type="email" />
18
+ </FormField>
19
+ );
20
+
21
+ export const FormFieldWithError = () => (
22
+ <FormField
23
+ label="Email"
24
+ htmlFor="email-error"
25
+ error="A valid email is required"
26
+ >
27
+ <input id="email-error" type="email" />
28
+ </FormField>
29
+ );
@@ -0,0 +1,63 @@
1
+ // Material Design 3 text-field chrome: the label and supporting text that sit
2
+ // outside the control. Both use body-small, as MD3's outlined field does once
3
+ // its label has floated.
4
+
5
+ .BHT__FormField {
6
+ display: flex;
7
+ flex-direction: column;
8
+ gap: var(--spacing-1, 0.25rem);
9
+
10
+ &__label {
11
+ font-family: var(--md-sys-typescale-body-small-font, var(--font-body));
12
+ font-size: var(--md-sys-typescale-body-small-size, 0.75rem);
13
+ font-weight: var(--md-sys-typescale-body-small-weight, 400);
14
+ line-height: var(--md-sys-typescale-body-small-line-height, 1rem);
15
+ letter-spacing: var(--md-sys-typescale-body-small-tracking, 0.0333em);
16
+ color: var(--md-sys-color-on-surface-variant, var(--color-text-muted));
17
+ display: flex;
18
+ align-items: center;
19
+ gap: var(--spacing-1, 0.25rem);
20
+ transition: color var(--md-sys-motion-duration-short-3, 150ms)
21
+ var(--md-sys-motion-easing-standard, ease);
22
+ }
23
+
24
+ &__required {
25
+ color: var(--md-sys-color-error, var(--color-error));
26
+ }
27
+
28
+ &__hint,
29
+ &__error {
30
+ font-family: var(--md-sys-typescale-body-small-font, var(--font-body));
31
+ font-size: var(--md-sys-typescale-body-small-size, 0.75rem);
32
+ font-weight: var(--md-sys-typescale-body-small-weight, 400);
33
+ line-height: var(--md-sys-typescale-body-small-line-height, 1rem);
34
+ letter-spacing: var(--md-sys-typescale-body-small-tracking, 0.0333em);
35
+ }
36
+
37
+ &__hint {
38
+ color: var(--md-sys-color-on-surface-variant, var(--color-text-muted));
39
+ }
40
+
41
+ &__error {
42
+ color: var(--md-sys-color-error, var(--color-error));
43
+ }
44
+
45
+ // MD3 tints the label with the focus color while the control is active.
46
+ // Excluding the error modifier keeps an invalid field's label red on focus,
47
+ // as MD3 does; without it this 0-3-0 rule would outrank the 0-2-0 error one.
48
+ &:focus-within:not(.BHT__FormField--error) {
49
+ .BHT__FormField {
50
+ &__label {
51
+ color: var(--md-sys-color-primary, var(--color-primary));
52
+ }
53
+ }
54
+ }
55
+
56
+ &--error {
57
+ .BHT__FormField {
58
+ &__label {
59
+ color: var(--md-sys-color-error, var(--color-error));
60
+ }
61
+ }
62
+ }
63
+ }
package/form-field.tsx ADDED
@@ -0,0 +1,71 @@
1
+ import { type HTMLAttributes, type ReactNode } from "react";
2
+ import { getClassName } from "@bht-component-registry/class-names";
3
+ import styles from "./form-field.module.scss";
4
+
5
+ export interface FormFieldProps extends Omit<
6
+ HTMLAttributes<HTMLDivElement>,
7
+ "children"
8
+ > {
9
+ /** Additional class names to merge with the component root element */
10
+ className?: string;
11
+ /** Field label */
12
+ label?: ReactNode;
13
+ /** Associates the label with the control — pass the same id you give the control */
14
+ htmlFor?: string;
15
+ /** Supporting hint text; hidden while `error` is present */
16
+ hint?: ReactNode;
17
+ /** Error message; also visually marks the field as invalid */
18
+ error?: ReactNode;
19
+ /** Shows a required marker next to the label */
20
+ required?: boolean;
21
+ /** The form control this field wraps */
22
+ children: ReactNode;
23
+ }
24
+
25
+ /**
26
+ * Dumb wrapper providing label + hint/error + required-marker layout around
27
+ * any form control (Checkbox, Input, Textarea, Select, Toggle, ...) — the
28
+ * same job AntD's `Form.Item` does. The control itself owns none of this
29
+ * chrome; it just needs an `id` matching `htmlFor`.
30
+ */
31
+ export const FormField = ({
32
+ className,
33
+ label,
34
+ htmlFor,
35
+ hint,
36
+ error,
37
+ required,
38
+ children,
39
+ ...rest
40
+ }: FormFieldProps) => {
41
+ const [rootClass, getChildClass] = getClassName({
42
+ className,
43
+ rootClass: "BHT__FormField",
44
+ modifiers: { error: !!error },
45
+ styles,
46
+ });
47
+
48
+ return (
49
+ <div {...rest} className={rootClass}>
50
+ {label !== undefined && (
51
+ <label className={getChildClass("label")} htmlFor={htmlFor}>
52
+ {label}
53
+ {required && (
54
+ <span className={getChildClass("required")} aria-hidden="true">
55
+ *
56
+ </span>
57
+ )}
58
+ </label>
59
+ )}
60
+ {children}
61
+ {hint !== undefined && error === undefined && (
62
+ <span className={getChildClass("hint")}>{hint}</span>
63
+ )}
64
+ {error !== undefined && (
65
+ <span className={getChildClass("error")} role="alert">
66
+ {error}
67
+ </span>
68
+ )}
69
+ </div>
70
+ );
71
+ };
package/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { FormField } from "./form-field.js";
2
+ export type { FormFieldProps } from "./form-field.js";
package/package.json CHANGED
@@ -1,6 +1,59 @@
1
1
  {
2
2
  "name": "@behivetech/forms.form-field",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.3.2",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "private": false,
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js",
11
+ "require": "./dist/index.cjs"
12
+ },
13
+ "./styles.css": "./dist/index.css"
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md",
18
+ "index.ts",
19
+ "form-field.tsx",
20
+ "form-field.module.scss",
21
+ "*.example.tsx",
22
+ "docs.json"
23
+ ],
24
+ "registry": {
25
+ "status": "stable",
26
+ "tags": []
27
+ },
28
+ "peerDependencies": {
29
+ "react": "^19.2.7",
30
+ "react-dom": "^19.2.7"
31
+ },
32
+ "devDependencies": {
33
+ "@types/react": "19.2.17",
34
+ "@types/react-dom": "19.2.3",
35
+ "eslint": "^9.39.5",
36
+ "tsup": "^8.5.1",
37
+ "typescript": "^6.0.3",
38
+ "vitest": "^4.1.10",
39
+ "@bht-component-registry/class-names": "0.0.6",
40
+ "@bht-component-registry/env": "0.1.0"
41
+ },
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "https://github.com/behivetech/bht-galaxy.git",
45
+ "directory": "registry/behivetech/forms/form-field"
46
+ },
47
+ "publishConfig": {
48
+ "registry": "https://registry.npmjs.org",
49
+ "access": "public",
50
+ "provenance": true
51
+ },
52
+ "scripts": {
53
+ "build": "tsup && bht-registry-docgen",
54
+ "dev": "tsup --watch",
55
+ "lint": "eslint . --max-warnings 0",
56
+ "check-types": "tsc --noEmit",
57
+ "test": "vitest run"
58
+ }
6
59
  }