@spark-web/field 0.0.0-snapshot-release-20260409001813

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.
@@ -0,0 +1,212 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var react = require('react');
6
+ var react$1 = require('@emotion/react');
7
+ var a11y = require('@spark-web/a11y');
8
+ var box = require('@spark-web/box');
9
+ var icon = require('@spark-web/icon');
10
+ var stack = require('@spark-web/stack');
11
+ var text = require('@spark-web/text');
12
+ var theme = require('@spark-web/theme');
13
+ var jsxRuntime = require('@emotion/react/jsx-runtime');
14
+
15
+ var FieldContext = /*#__PURE__*/react.createContext(null);
16
+ var FieldContextProvider = FieldContext.Provider;
17
+ var FIELD_CONTEXT_ERROR_MESSAGE = 'Input components must be inside a `Field`.';
18
+ function useFieldContext() {
19
+ var ctx = react.useContext(FieldContext);
20
+ if (!ctx) {
21
+ throw new Error(FIELD_CONTEXT_ERROR_MESSAGE);
22
+ }
23
+ return ctx;
24
+ }
25
+
26
+ /**
27
+ * Using a [context](https://reactjs.org/docs/context.html), the field
28
+ * component connects the label, description, and message to the input element.
29
+ */
30
+ var Field = /*#__PURE__*/react.forwardRef(function (_ref, forwardedRef) {
31
+ var children = _ref.children,
32
+ idProp = _ref.id,
33
+ data = _ref.data,
34
+ description = _ref.description,
35
+ _ref$disabled = _ref.disabled,
36
+ disabled = _ref$disabled === void 0 ? false : _ref$disabled,
37
+ label = _ref.label,
38
+ adornment = _ref.adornment,
39
+ _ref$labelVisibility = _ref.labelVisibility,
40
+ labelVisibility = _ref$labelVisibility === void 0 ? 'visible' : _ref$labelVisibility,
41
+ message = _ref.message,
42
+ secondaryLabel = _ref.secondaryLabel,
43
+ _ref$tone = _ref.tone,
44
+ tone = _ref$tone === void 0 ? 'neutral' : _ref$tone,
45
+ _ref$readOnly = _ref.readOnly,
46
+ readOnly = _ref$readOnly === void 0 ? false : _ref$readOnly;
47
+ var _useFieldIds = useFieldIds(idProp),
48
+ descriptionId = _useFieldIds.descriptionId,
49
+ inputId = _useFieldIds.inputId,
50
+ messageId = _useFieldIds.messageId;
51
+
52
+ // field context
53
+ var invalid = Boolean(message && tone === 'critical');
54
+ var fieldContext = react.useMemo(function () {
55
+ return [{
56
+ disabled: disabled,
57
+ invalid: invalid,
58
+ readOnly: readOnly
59
+ }, {
60
+ 'aria-describedby': a11y.mergeIds(message && messageId, description ? descriptionId : undefined),
61
+ 'aria-invalid': invalid || undefined,
62
+ id: inputId
63
+ }];
64
+ }, [description, descriptionId, disabled, inputId, invalid, message, messageId, readOnly]);
65
+
66
+ // label prep
67
+ var hiddenLabel = jsxRuntime.jsxs(a11y.VisuallyHidden, {
68
+ as: "label",
69
+ htmlFor: inputId,
70
+ children: [label, " ", secondaryLabel]
71
+ });
72
+ var labelElement = {
73
+ hidden: hiddenLabel,
74
+ visible: jsxRuntime.jsx(box.Box, {
75
+ as: "label",
76
+ htmlFor: inputId,
77
+ children: jsxRuntime.jsxs(text.Text, {
78
+ tone: disabled || readOnly ? 'field' : 'neutral',
79
+ weight: "semibold",
80
+ children: [label, ' ', secondaryLabel && jsxRuntime.jsx(text.Text, {
81
+ inline: true,
82
+ tone: disabled || readOnly ? 'field' : 'muted',
83
+ weight: "regular",
84
+ children: secondaryLabel
85
+ })]
86
+ })
87
+ }),
88
+ 'reserve-space': jsxRuntime.jsxs(react.Fragment, {
89
+ children: [hiddenLabel, jsxRuntime.jsx(text.Text, {
90
+ "aria-hidden": true,
91
+ children: "\xA0"
92
+ })]
93
+ })
94
+ };
95
+ var LabelWrapper = labelVisibility === 'hidden' ? react.Fragment : FieldLabelWrapper;
96
+ return jsxRuntime.jsx(FieldContextProvider, {
97
+ value: fieldContext,
98
+ children: jsxRuntime.jsxs(stack.Stack, {
99
+ ref: forwardedRef,
100
+ data: data,
101
+ gap: "medium",
102
+ children: [jsxRuntime.jsxs(LabelWrapper, {
103
+ children: [labelElement[labelVisibility], adornment]
104
+ }), description && (typeof description === 'string' ? jsxRuntime.jsx(text.Text, {
105
+ tone: "muted",
106
+ size: "small",
107
+ id: descriptionId,
108
+ children: description
109
+ }) : jsxRuntime.jsx(box.Box, {
110
+ as: "label",
111
+ htmlFor: descriptionId,
112
+ children: description
113
+ })), children, message && jsxRuntime.jsx(FieldMessage, {
114
+ tone: tone,
115
+ id: messageId,
116
+ message: message
117
+ })]
118
+ })
119
+ });
120
+ });
121
+ Field.displayName = 'Field';
122
+
123
+ // Utils
124
+ // ------------------------------
125
+
126
+ function useFieldIds(id) {
127
+ var inputId = a11y.useId(id);
128
+ var descriptionId = a11y.composeId(inputId, 'description');
129
+ var messageId = a11y.composeId(inputId, 'message');
130
+ return {
131
+ descriptionId: descriptionId,
132
+ inputId: inputId,
133
+ messageId: messageId
134
+ };
135
+ }
136
+
137
+ // Styled components
138
+ // ------------------------------
139
+ function FieldLabelWrapper(_ref2) {
140
+ var children = _ref2.children;
141
+ return jsxRuntime.jsx(box.Box, {
142
+ display: "flex",
143
+ alignItems: "center",
144
+ justifyContent: "spaceBetween",
145
+ gap: "large",
146
+ children: children
147
+ });
148
+ }
149
+ var messageToneMap = {
150
+ critical: 'critical',
151
+ neutral: 'muted',
152
+ positive: 'positive'
153
+ };
154
+
155
+ // NOTE: use icons in addition to color for folks with visions issues
156
+ var messageIconMap = {
157
+ critical: icon.ExclamationCircleIcon,
158
+ neutral: null,
159
+ positive: icon.CheckCircleIcon
160
+ };
161
+ var FieldMessage = function FieldMessage(_ref3) {
162
+ var message = _ref3.message,
163
+ id = _ref3.id,
164
+ tone = _ref3.tone;
165
+ var textTone = messageToneMap[tone];
166
+ var Icon = messageIconMap[tone];
167
+ return jsxRuntime.jsxs(box.Box, {
168
+ display: "flex",
169
+ gap: "xsmall",
170
+ children: [Icon ? jsxRuntime.jsx(IndicatorContainer, {
171
+ children: jsxRuntime.jsx(Icon, {
172
+ size: "xxsmall",
173
+ tone: tone
174
+ })
175
+ }) : null, jsxRuntime.jsx(text.Text, {
176
+ tone: textTone,
177
+ size: "small",
178
+ id: id,
179
+ children: message
180
+ })]
181
+ });
182
+ };
183
+ function IndicatorContainer(_ref4) {
184
+ var children = _ref4.children;
185
+ var theme$1 = theme.useTheme();
186
+ var _theme$typography$tex = theme$1.typography.text.small,
187
+ mobile = _theme$typography$tex.mobile,
188
+ tablet = _theme$typography$tex.tablet;
189
+ var responsiveStyles = theme$1.utils.responsiveStyles({
190
+ mobile: {
191
+ height: mobile.capHeight
192
+ },
193
+ tablet: {
194
+ height: tablet.capHeight
195
+ }
196
+ });
197
+ return jsxRuntime.jsx(box.Box, {
198
+ display: "flex",
199
+ alignItems: "center",
200
+ "aria-hidden": true,
201
+ cursor: "default",
202
+ flexShrink: 0,
203
+ css: react$1.css(responsiveStyles),
204
+ children: children
205
+ });
206
+ }
207
+
208
+ exports.Field = Field;
209
+ exports.FieldContextProvider = FieldContextProvider;
210
+ exports.FieldMessage = FieldMessage;
211
+ exports.useFieldContext = useFieldContext;
212
+ exports.useFieldIds = useFieldIds;
@@ -0,0 +1,204 @@
1
+ import { createContext, useContext, forwardRef, useMemo, Fragment } from 'react';
2
+ import { css } from '@emotion/react';
3
+ import { mergeIds, VisuallyHidden, useId, composeId } from '@spark-web/a11y';
4
+ import { Box } from '@spark-web/box';
5
+ import { ExclamationCircleIcon, CheckCircleIcon } from '@spark-web/icon';
6
+ import { Stack } from '@spark-web/stack';
7
+ import { Text } from '@spark-web/text';
8
+ import { useTheme } from '@spark-web/theme';
9
+ import { jsxs, jsx } from '@emotion/react/jsx-runtime';
10
+
11
+ var FieldContext = /*#__PURE__*/createContext(null);
12
+ var FieldContextProvider = FieldContext.Provider;
13
+ var FIELD_CONTEXT_ERROR_MESSAGE = 'Input components must be inside a `Field`.';
14
+ function useFieldContext() {
15
+ var ctx = useContext(FieldContext);
16
+ if (!ctx) {
17
+ throw new Error(FIELD_CONTEXT_ERROR_MESSAGE);
18
+ }
19
+ return ctx;
20
+ }
21
+
22
+ /**
23
+ * Using a [context](https://reactjs.org/docs/context.html), the field
24
+ * component connects the label, description, and message to the input element.
25
+ */
26
+ var Field = /*#__PURE__*/forwardRef(function (_ref, forwardedRef) {
27
+ var children = _ref.children,
28
+ idProp = _ref.id,
29
+ data = _ref.data,
30
+ description = _ref.description,
31
+ _ref$disabled = _ref.disabled,
32
+ disabled = _ref$disabled === void 0 ? false : _ref$disabled,
33
+ label = _ref.label,
34
+ adornment = _ref.adornment,
35
+ _ref$labelVisibility = _ref.labelVisibility,
36
+ labelVisibility = _ref$labelVisibility === void 0 ? 'visible' : _ref$labelVisibility,
37
+ message = _ref.message,
38
+ secondaryLabel = _ref.secondaryLabel,
39
+ _ref$tone = _ref.tone,
40
+ tone = _ref$tone === void 0 ? 'neutral' : _ref$tone,
41
+ _ref$readOnly = _ref.readOnly,
42
+ readOnly = _ref$readOnly === void 0 ? false : _ref$readOnly;
43
+ var _useFieldIds = useFieldIds(idProp),
44
+ descriptionId = _useFieldIds.descriptionId,
45
+ inputId = _useFieldIds.inputId,
46
+ messageId = _useFieldIds.messageId;
47
+
48
+ // field context
49
+ var invalid = Boolean(message && tone === 'critical');
50
+ var fieldContext = useMemo(function () {
51
+ return [{
52
+ disabled: disabled,
53
+ invalid: invalid,
54
+ readOnly: readOnly
55
+ }, {
56
+ 'aria-describedby': mergeIds(message && messageId, description ? descriptionId : undefined),
57
+ 'aria-invalid': invalid || undefined,
58
+ id: inputId
59
+ }];
60
+ }, [description, descriptionId, disabled, inputId, invalid, message, messageId, readOnly]);
61
+
62
+ // label prep
63
+ var hiddenLabel = jsxs(VisuallyHidden, {
64
+ as: "label",
65
+ htmlFor: inputId,
66
+ children: [label, " ", secondaryLabel]
67
+ });
68
+ var labelElement = {
69
+ hidden: hiddenLabel,
70
+ visible: jsx(Box, {
71
+ as: "label",
72
+ htmlFor: inputId,
73
+ children: jsxs(Text, {
74
+ tone: disabled || readOnly ? 'field' : 'neutral',
75
+ weight: "semibold",
76
+ children: [label, ' ', secondaryLabel && jsx(Text, {
77
+ inline: true,
78
+ tone: disabled || readOnly ? 'field' : 'muted',
79
+ weight: "regular",
80
+ children: secondaryLabel
81
+ })]
82
+ })
83
+ }),
84
+ 'reserve-space': jsxs(Fragment, {
85
+ children: [hiddenLabel, jsx(Text, {
86
+ "aria-hidden": true,
87
+ children: "\xA0"
88
+ })]
89
+ })
90
+ };
91
+ var LabelWrapper = labelVisibility === 'hidden' ? Fragment : FieldLabelWrapper;
92
+ return jsx(FieldContextProvider, {
93
+ value: fieldContext,
94
+ children: jsxs(Stack, {
95
+ ref: forwardedRef,
96
+ data: data,
97
+ gap: "medium",
98
+ children: [jsxs(LabelWrapper, {
99
+ children: [labelElement[labelVisibility], adornment]
100
+ }), description && (typeof description === 'string' ? jsx(Text, {
101
+ tone: "muted",
102
+ size: "small",
103
+ id: descriptionId,
104
+ children: description
105
+ }) : jsx(Box, {
106
+ as: "label",
107
+ htmlFor: descriptionId,
108
+ children: description
109
+ })), children, message && jsx(FieldMessage, {
110
+ tone: tone,
111
+ id: messageId,
112
+ message: message
113
+ })]
114
+ })
115
+ });
116
+ });
117
+ Field.displayName = 'Field';
118
+
119
+ // Utils
120
+ // ------------------------------
121
+
122
+ function useFieldIds(id) {
123
+ var inputId = useId(id);
124
+ var descriptionId = composeId(inputId, 'description');
125
+ var messageId = composeId(inputId, 'message');
126
+ return {
127
+ descriptionId: descriptionId,
128
+ inputId: inputId,
129
+ messageId: messageId
130
+ };
131
+ }
132
+
133
+ // Styled components
134
+ // ------------------------------
135
+ function FieldLabelWrapper(_ref2) {
136
+ var children = _ref2.children;
137
+ return jsx(Box, {
138
+ display: "flex",
139
+ alignItems: "center",
140
+ justifyContent: "spaceBetween",
141
+ gap: "large",
142
+ children: children
143
+ });
144
+ }
145
+ var messageToneMap = {
146
+ critical: 'critical',
147
+ neutral: 'muted',
148
+ positive: 'positive'
149
+ };
150
+
151
+ // NOTE: use icons in addition to color for folks with visions issues
152
+ var messageIconMap = {
153
+ critical: ExclamationCircleIcon,
154
+ neutral: null,
155
+ positive: CheckCircleIcon
156
+ };
157
+ var FieldMessage = function FieldMessage(_ref3) {
158
+ var message = _ref3.message,
159
+ id = _ref3.id,
160
+ tone = _ref3.tone;
161
+ var textTone = messageToneMap[tone];
162
+ var Icon = messageIconMap[tone];
163
+ return jsxs(Box, {
164
+ display: "flex",
165
+ gap: "xsmall",
166
+ children: [Icon ? jsx(IndicatorContainer, {
167
+ children: jsx(Icon, {
168
+ size: "xxsmall",
169
+ tone: tone
170
+ })
171
+ }) : null, jsx(Text, {
172
+ tone: textTone,
173
+ size: "small",
174
+ id: id,
175
+ children: message
176
+ })]
177
+ });
178
+ };
179
+ function IndicatorContainer(_ref4) {
180
+ var children = _ref4.children;
181
+ var theme = useTheme();
182
+ var _theme$typography$tex = theme.typography.text.small,
183
+ mobile = _theme$typography$tex.mobile,
184
+ tablet = _theme$typography$tex.tablet;
185
+ var responsiveStyles = theme.utils.responsiveStyles({
186
+ mobile: {
187
+ height: mobile.capHeight
188
+ },
189
+ tablet: {
190
+ height: tablet.capHeight
191
+ }
192
+ });
193
+ return jsx(Box, {
194
+ display: "flex",
195
+ alignItems: "center",
196
+ "aria-hidden": true,
197
+ cursor: "default",
198
+ flexShrink: 0,
199
+ css: css(responsiveStyles),
200
+ children: children
201
+ });
202
+ }
203
+
204
+ export { Field, FieldContextProvider, FieldMessage, useFieldContext, useFieldIds };
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@spark-web/field",
3
+ "version": "0.0.0-snapshot-release-20260409001813",
4
+ "homepage": "https://github.com/brighte-labs/spark-web#readme",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/brighte-labs/spark-web.git",
8
+ "directory": "packages/field"
9
+ },
10
+ "main": "dist/spark-web-field.cjs.js",
11
+ "module": "dist/spark-web-field.esm.js",
12
+ "files": [
13
+ "CHANGELOG.md",
14
+ "CLAUDE.md",
15
+ "dist",
16
+ "README.md"
17
+ ],
18
+ "dependencies": {
19
+ "@babel/runtime": "^7.25.0",
20
+ "@emotion/react": "^11.14.0",
21
+ "@spark-web/a11y": "^5.3.0",
22
+ "@spark-web/box": "0.0.0-snapshot-release-20260409001813",
23
+ "@spark-web/icon": "^5.1.0",
24
+ "@spark-web/stack": "0.0.0-snapshot-release-20260409001813",
25
+ "@spark-web/text": "0.0.0-snapshot-release-20260409001813",
26
+ "@spark-web/theme": "^5.13.0",
27
+ "@spark-web/utils": "^5.1.0"
28
+ },
29
+ "devDependencies": {
30
+ "@types/react": "^19.1.0",
31
+ "react": "^19.1.0"
32
+ },
33
+ "peerDependencies": {
34
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
35
+ },
36
+ "engines": {
37
+ "node": ">=14"
38
+ }
39
+ }