@behivetech/forms.form-field 0.0.0-stage → 0.3.3
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 +37 -2
- package/dist/index.cjs +187 -0
- package/dist/index.css +41 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +173 -0
- package/docs.json +64 -0
- package/form-field.example.tsx +29 -0
- package/form-field.module.scss +63 -0
- package/form-field.tsx +71 -0
- package/index.ts +2 -0
- package/package.json +56 -3
package/README.md
CHANGED
|
@@ -1,3 +1,38 @@
|
|
|
1
|
-
#
|
|
1
|
+
# FormField
|
|
2
2
|
|
|
3
|
-
|
|
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
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -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-23T16:57:40.284Z",
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,59 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@behivetech/forms.form-field",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.3.3",
|
|
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
|
}
|