@mirohq/design-system-base-input 0.1.0-forms.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/dist/module.js CHANGED
@@ -1,134 +1,219 @@
1
- import { booleanify, removeEventProps } from '@mirohq/design-system-utils';
2
- import { styled } from '@mirohq/design-system-stitches';
1
+ import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
2
+ import React, { createContext, useRef, useState, useContext } from 'react';
3
+ import { booleanify, mergeRefs, addPropsToChildren, booleanishAttrValue } from '@mirohq/design-system-utils';
4
+ import { usePress } from '@mirohq/design-system-use-press';
5
+ import { useHover } from '@react-aria/interactions';
6
+ import { mergeProps } from '@react-aria/utils';
3
7
  import { Primitive } from '@mirohq/design-system-primitive';
8
+ import { styled } from '@mirohq/design-system-stitches';
9
+ import { IconExclamationPointCircle, IconCheckMark } from '@mirohq/design-system-icons';
10
+ import { styles } from '@mirohq/design-system-base-text-field';
11
+ import { BaseButton } from '@mirohq/design-system-base-button';
12
+ import { focus } from '@mirohq/design-system-styles';
13
+ import { useAriaDisabled } from '@mirohq/design-system-use-aria-disabled';
14
+ import { Tooltip } from '@mirohq/design-system-tooltip';
15
+ import { isIconComponent } from '@mirohq/design-system-base-icon';
16
+ import { useLayoutEffect } from '@mirohq/design-system-use-layout-effect';
4
17
 
5
- const styles = {
18
+ const StyledIconSlot = styled(Primitive.div, {
19
+ order: 1,
20
+ alignContent: "center",
21
+ display: "flex",
22
+ justifyContent: "center",
23
+ color: "$icon-neutrals-subtle",
24
+ "& svg, & img": {
25
+ pointerEvents: "none"
26
+ },
6
27
  variants: {
7
- idle: {
8
- background: "$background-neutrals-container",
9
- border: "1px solid $border-neutrals",
10
- borderRadius: "$50",
11
- fontSize: "$200",
12
- lineHeight: "1.5"
13
- },
14
- focused: {
15
- boxShadow: "$focus-controls",
16
- borderColor: "$border-neutrals"
17
- },
18
- hovered: {
19
- borderColor: "$border-primary-hover"
20
- },
21
- readOnly: {
22
- background: "$background-neutrals-disabled",
23
- color: "$text-neutrals-subtle"
24
- },
25
28
  disabled: {
26
- background: "$background-neutrals-disabled"
27
- },
28
- invalid: {
29
- idle: {
30
- borderColor: "$border-danger"
31
- },
32
- focused: {
33
- borderColor: "$border-danger",
34
- boxShadow: "$focus-controls-error"
35
- },
36
- hovered: {
37
- borderColor: "$border-danger-hover"
38
- }
39
- },
40
- valid: {
41
- idle: {
42
- borderColor: "$border-success"
43
- },
44
- focused: {
45
- borderColor: "$border-success",
46
- boxShadow: "$focus-controls-success"
47
- },
48
- hovered: {
49
- borderColor: "$border-success-hover"
29
+ true: {
30
+ "& svg": {
31
+ color: "$icon-neutrals-disabled"
32
+ }
50
33
  }
51
34
  }
35
+ }
36
+ });
37
+
38
+ const StyledActionButton = styled(BaseButton, {
39
+ backgroundColor: "transparent",
40
+ color: "$icon-neutrals-subtle",
41
+ display: "flex",
42
+ justifyContent: "center",
43
+ order: 3,
44
+ padding: "6px",
45
+ square: "$7",
46
+ "& svg:not([data-icon-component]), & img:not([data-icon-component])": {
47
+ square: "$icon-200",
48
+ "--svg-stroke-width": "$stroke-width$thin"
52
49
  },
53
- base: {
54
- placeholder: {
55
- color: "$text-neutrals-subtle"
50
+ ...focus.css({
51
+ boxShadow: "$focus-small"
52
+ }),
53
+ "&:hover": {
54
+ backgroundColor: "$background-neutrals-subtle-hover"
55
+ },
56
+ "&:active, &[data-pressed]": {
57
+ backgroundColor: "$background-neutrals-subtle-active"
58
+ },
59
+ variants: {
60
+ readOnlyAppearance: {
61
+ true: {
62
+ "& svg[data-icon-component], & img[data-icon-component]": {
63
+ color: "$icon-neutrals-subtle"
64
+ }
65
+ }
56
66
  },
57
- disabled: {
58
- caretColor: "transparent",
59
- "&, &::placeholder": {
60
- color: "$text-neutrals-disabled",
61
- "-webkit-text-fill-color": "$colors$text-neutrals-disabled"
62
- },
63
- "&:selection": {
64
- background: "transparent"
67
+ disableAppearance: {
68
+ true: {
69
+ color: "$icon-neutrals-disabled"
65
70
  }
66
71
  }
67
72
  }
68
- };
73
+ });
69
74
 
70
- const keyboardEventHandler = (e) => {
71
- if (e.key !== "Tab") {
72
- e.preventDefault();
75
+ const StyledIconExclamationPointCircle = styled(
76
+ IconExclamationPointCircle,
77
+ {
78
+ color: "$icon-danger"
73
79
  }
74
- };
75
- const useAriaDisabled = (props, ariaDisabled) => {
76
- if (!booleanify(ariaDisabled)) {
77
- return props;
78
- }
79
- const formattedProps = removeEventProps(props, [
80
- "onFocus",
81
- "onBlur",
82
- "onPointerMove"
83
- ]);
84
- formattedProps.onKeyDown = keyboardEventHandler;
85
- formattedProps.onKeyUp = keyboardEventHandler;
86
- return formattedProps;
87
- };
88
-
89
- const FloatingLabel = styled(Primitive.label, {
90
- transitionProperty: "transform, left, top, font-size",
91
- transitionDuration: "200ms",
92
- transitionTimingFunction: "cubic-bezier(0, 0, 0.2, 1)",
80
+ );
81
+ const StyledIconCheckMark = styled(IconCheckMark, {
82
+ color: "$icon-success"
83
+ });
84
+ const StyledValidityBox = styled(Primitive.div, {
85
+ order: 3,
86
+ display: "flex",
87
+ alignItems: "center",
88
+ padding: "6px"
89
+ });
90
+ const StyledBaseInput = styled("div", {
91
+ position: "relative",
92
+ alignItems: "center",
93
+ display: "inline-flex",
94
+ height: "max-content",
95
+ boxSizing: "border-box",
96
+ width: "100%",
97
+ ...styles.variants.idle,
93
98
  variants: {
94
- floating: {
95
- true: {
96
- transform: "translate(2px, -8px)",
97
- fontSize: "$150",
98
- lineHeight: 1.5,
99
- height: "1.5em",
100
- color: "$text-neutrals-subtle",
101
- padding: "0 2px",
102
- backgroundColor: "$background-neutrals-container"
103
- },
104
- false: {
105
- transform: "translate(0, 0)",
106
- fontSize: "$200",
107
- lineHeight: 1.5,
108
- height: "1.5em",
109
- ...styles.base.placeholder
110
- }
99
+ hovered: {
100
+ true: {},
101
+ false: {}
102
+ },
103
+ focused: {
104
+ true: styles.variants.focused,
105
+ false: {}
106
+ },
107
+ valid: {
108
+ true: {},
109
+ false: {}
110
+ },
111
+ readOnly: {
112
+ true: styles.variants.readOnly,
113
+ false: {}
114
+ },
115
+ disabled: {
116
+ true: styles.variants.disabled,
117
+ false: {}
118
+ },
119
+ ariaDisabled: {
120
+ true: styles.variants.disabled,
121
+ false: {}
111
122
  },
112
123
  size: {
113
- large: {},
114
- "x-large": {}
124
+ large: {
125
+ height: "$10",
126
+ padding: "0 $100 ",
127
+ ["& ".concat(StyledIconSlot)]: {
128
+ paddingRight: "$50"
129
+ },
130
+ ["& ".concat(StyledActionButton, ", & ").concat(StyledValidityBox)]: {
131
+ marginLeft: "$50"
132
+ },
133
+ ["& ".concat(StyledValidityBox)]: {
134
+ paddingRight: "6px"
135
+ }
136
+ },
137
+ "x-large": {
138
+ height: "$12",
139
+ padding: "0 $150",
140
+ ["& ".concat(StyledIconSlot)]: {
141
+ paddingRight: "$100"
142
+ },
143
+ ["& ".concat(StyledActionButton, ", & ").concat(StyledValidityBox)]: {
144
+ marginLeft: "$100"
145
+ },
146
+ ["& ".concat(StyledValidityBox)]: {
147
+ paddingRight: "6px"
148
+ }
149
+ }
115
150
  }
116
151
  },
117
152
  compoundVariants: [
153
+ /** Idle states */
118
154
  {
119
- size: "large",
120
- floating: false,
121
- css: {
122
- top: "7px"
123
- }
155
+ valid: false,
156
+ readOnly: false,
157
+ disabled: false,
158
+ ariaDisabled: false,
159
+ css: styles.variants.invalid.idle
160
+ },
161
+ {
162
+ valid: true,
163
+ readOnly: false,
164
+ disabled: false,
165
+ ariaDisabled: false,
166
+ css: styles.variants.valid.idle
124
167
  },
168
+ /** Focus States */
125
169
  {
126
- size: "x-large",
127
- floating: false,
170
+ focused: true,
171
+ readOnly: false,
128
172
  css: {
129
- top: "11px",
130
- left: "$200"
173
+ borderColor: styles.variants.focused.borderColor
131
174
  }
175
+ },
176
+ {
177
+ focused: true,
178
+ readOnly: false,
179
+ valid: false,
180
+ disabled: false,
181
+ ariaDisabled: false,
182
+ css: styles.variants.invalid.focused
183
+ },
184
+ {
185
+ focused: true,
186
+ readOnly: false,
187
+ valid: true,
188
+ disabled: false,
189
+ ariaDisabled: false,
190
+ css: styles.variants.valid.focused
191
+ },
192
+ /** Hover states */
193
+ {
194
+ hovered: true,
195
+ disabled: false,
196
+ ariaDisabled: false,
197
+ readOnly: false,
198
+ css: styles.variants.hovered
199
+ },
200
+ {
201
+ hovered: true,
202
+ focused: false,
203
+ valid: false,
204
+ readOnly: false,
205
+ disabled: false,
206
+ ariaDisabled: false,
207
+ css: styles.variants.invalid.hovered
208
+ },
209
+ {
210
+ hovered: true,
211
+ focused: false,
212
+ valid: true,
213
+ readOnly: false,
214
+ disabled: false,
215
+ ariaDisabled: false,
216
+ css: styles.variants.valid.hovered
132
217
  }
133
218
  ],
134
219
  defaultVariants: {
@@ -136,16 +221,271 @@ const FloatingLabel = styled(Primitive.label, {
136
221
  }
137
222
  });
138
223
 
139
- const inputSymbol = Symbol.for("input");
140
- const isInputComponent = (inputComponent) => {
141
- var _a, _b;
142
- return Boolean(
143
- (_b = inputComponent[inputSymbol]) != null ? _b : (
144
- // @ts-expect-error
145
- (_a = inputComponent == null ? void 0 : inputComponent.type) == null ? void 0 : _a[inputSymbol]
146
- )
224
+ const disabledAndReadonlySelectors = ':read-only, :disabled, [aria-disabled="true"], [data-disabled]';
225
+ const StyledInput = styled(Primitive.input, {
226
+ all: "unset",
227
+ background: "transparent",
228
+ color: "$text-neutrals",
229
+ width: "100%",
230
+ borderRadius: "$50",
231
+ order: 2,
232
+ padding: "0 $50",
233
+ height: "100%",
234
+ "&::placeholder": {
235
+ fontStyle: "italic"
236
+ },
237
+ ["&:not(".concat(disabledAndReadonlySelectors, ")::placeholder")]: styles.base.placeholder,
238
+ "&:read-only": {
239
+ color: styles.variants.readOnly.color
240
+ },
241
+ "&:disabled, &[aria-disabled=true], &[data-disabled]": styles.base.disabled
242
+ });
243
+
244
+ const InputContext = createContext({});
245
+ const InputProvider = ({
246
+ children,
247
+ disabled,
248
+ "aria-disabled": ariaDisabled,
249
+ readOnly,
250
+ required,
251
+ valid,
252
+ ...restProps
253
+ }) => {
254
+ const actionButtonRef = useRef(null);
255
+ const inputRef = useRef(null);
256
+ const [focused, setFocused] = useState(false);
257
+ const [hovered, setHovered] = useState(false);
258
+ const [hasIconSlot, setHasIconSlot] = useState(false);
259
+ const editable = !booleanify(disabled) && !booleanify(ariaDisabled) && !booleanify(readOnly);
260
+ return /* @__PURE__ */ jsx(
261
+ InputContext.Provider,
262
+ {
263
+ value: {
264
+ ...restProps,
265
+ setFocused,
266
+ setHovered,
267
+ setHasIconSlot,
268
+ hasIconSlot,
269
+ hovered,
270
+ focused,
271
+ disabled,
272
+ "aria-disabled": ariaDisabled,
273
+ readOnly,
274
+ editable,
275
+ inputRef,
276
+ actionButtonRef,
277
+ required,
278
+ valid
279
+ },
280
+ children
281
+ }
147
282
  );
148
283
  };
284
+ const useInputContext = () => useContext(InputContext);
285
+
286
+ const Input = React.forwardRef(
287
+ ({
288
+ id,
289
+ "aria-describedby": ariaDescribedBy,
290
+ "aria-invalid": ariaInvalid,
291
+ ...restProps
292
+ }, forwardRef) => {
293
+ const {
294
+ "aria-disabled": ariaDisabled,
295
+ disabled,
296
+ setFocused,
297
+ inputRef,
298
+ required,
299
+ readOnly,
300
+ onChange
301
+ } = useInputContext();
302
+ const { onBlur, onFocus, ...elementProps } = useAriaDisabled({
303
+ ...restProps,
304
+ ariaDisabled
305
+ });
306
+ const onFocusHandler = React.useCallback(
307
+ (e) => {
308
+ setFocused(true);
309
+ onFocus == null ? void 0 : onFocus(e);
310
+ },
311
+ [setFocused, onFocus]
312
+ );
313
+ const onBlurHandler = React.useCallback(
314
+ (e) => {
315
+ setFocused(false);
316
+ onBlur == null ? void 0 : onBlur(e);
317
+ },
318
+ [setFocused, onBlur]
319
+ );
320
+ return /* @__PURE__ */ jsx(
321
+ StyledInput,
322
+ {
323
+ ...elementProps,
324
+ id,
325
+ readOnly,
326
+ "aria-describedby": ariaDescribedBy,
327
+ "aria-invalid": ariaInvalid,
328
+ disabled: disabled === true || void 0,
329
+ required: required === true || void 0,
330
+ onFocus: onFocusHandler,
331
+ onBlur: onBlurHandler,
332
+ onChange,
333
+ ref: mergeRefs([inputRef, forwardRef])
334
+ }
335
+ );
336
+ }
337
+ );
338
+
339
+ const ActionButton = React.forwardRef(({ "aria-label": ariaLabel, label, children, ...restProps }, forwardRef) => {
340
+ const {
341
+ valid,
342
+ hovered,
343
+ editable,
344
+ "aria-disabled": ariaDisabled,
345
+ disabled,
346
+ readOnly,
347
+ actionButtonRef
348
+ } = useInputContext();
349
+ const showInputSlot = valid === void 0 || booleanify(hovered);
350
+ let formattedChildren = children;
351
+ formattedChildren = addPropsToChildren(children, isIconComponent, {
352
+ "aria-hidden": true
353
+ });
354
+ const hasPressEvent = Object.keys(restProps).some(
355
+ (key) => key.startsWith("onPress")
356
+ );
357
+ const customDisabled = restProps.disabled;
358
+ return /* @__PURE__ */ jsx(Fragment, { children: showInputSlot && /* @__PURE__ */ jsxs(Tooltip, { children: [
359
+ /* @__PURE__ */ jsx(Tooltip.Trigger, { asChild: true, children: /* @__PURE__ */ jsx(
360
+ StyledActionButton,
361
+ {
362
+ type: "button",
363
+ ...restProps,
364
+ "aria-label": ariaLabel != null ? ariaLabel : label,
365
+ disabled: hasPressEvent ? customDisabled : customDisabled != null ? customDisabled : !editable,
366
+ disableAppearance: hasPressEvent ? !booleanify(readOnly) && customDisabled : booleanify(disabled != null ? disabled : ariaDisabled),
367
+ readOnlyAppearance: readOnly,
368
+ ref: mergeRefs([forwardRef, actionButtonRef]),
369
+ children: formattedChildren
370
+ }
371
+ ) }),
372
+ /* @__PURE__ */ jsx(Tooltip.Content, { children: label })
373
+ ] }) });
374
+ });
375
+
376
+ const IconSlot = React.forwardRef(({ children, ...restProps }, forwardRef) => {
377
+ const {
378
+ "aria-disabled": ariaDisabled,
379
+ disabled,
380
+ setHasIconSlot
381
+ } = useInputContext();
382
+ const formattedChildren = addPropsToChildren(children, isIconComponent, {
383
+ size: "small",
384
+ weight: "thin",
385
+ "aria-hidden": true
386
+ });
387
+ useLayoutEffect(() => {
388
+ setHasIconSlot(true);
389
+ return () => setHasIconSlot(false);
390
+ }, [setHasIconSlot]);
391
+ return /* @__PURE__ */ jsx(
392
+ StyledIconSlot,
393
+ {
394
+ ...restProps,
395
+ disabled: booleanify(disabled != null ? disabled : ariaDisabled),
396
+ ref: forwardRef,
397
+ children: formattedChildren
398
+ }
399
+ );
400
+ });
401
+ IconSlot.displayName = "IconSlot";
402
+
403
+ const Root = React.forwardRef(({ children, size, ...restProps }, forwardRef) => {
404
+ const ref = useRef(null);
405
+ const {
406
+ valid,
407
+ "aria-disabled": ariaDisabled,
408
+ disabled,
409
+ readOnly,
410
+ focused,
411
+ setHovered,
412
+ inputRef,
413
+ actionButtonRef,
414
+ setFocused
415
+ } = useInputContext();
416
+ const { hoverProps, isHovered: hovered } = useHover({
417
+ onHoverChange: setHovered
418
+ });
419
+ const { pressProps } = usePress({
420
+ onPressStart: (e) => {
421
+ const { target } = e.originalEvent;
422
+ const shouldPrevent = document.activeElement === inputRef.current && target !== inputRef.current;
423
+ if (shouldPrevent) {
424
+ e.originalEvent.preventDefault();
425
+ }
426
+ },
427
+ onPressEnd: (e) => {
428
+ var _a, _b;
429
+ const { target } = e.originalEvent;
430
+ const isActionButton = target === actionButtonRef.current || ((_a = actionButtonRef.current) == null ? void 0 : _a.contains(target)) === true;
431
+ const shouldFocusInput = !isActionButton && !booleanify(disabled) && !booleanify(focused);
432
+ if (shouldFocusInput) {
433
+ (_b = inputRef.current) == null ? void 0 : _b.focus();
434
+ setFocused(true);
435
+ }
436
+ },
437
+ preventFocusOnPress: "auto"
438
+ });
439
+ const ariaDisabledOrDisabled = booleanify(ariaDisabled) || booleanify(disabled);
440
+ const showValidityIcon = !booleanify(readOnly) && !ariaDisabledOrDisabled && !hovered && !focused && valid !== void 0;
441
+ const ValidIcon = valid === true ? StyledIconCheckMark : StyledIconExclamationPointCircle;
442
+ return /* @__PURE__ */ jsxs(
443
+ StyledBaseInput,
444
+ {
445
+ ...mergeProps(restProps, hoverProps, pressProps),
446
+ "data-invalid": booleanishAttrValue(valid === false),
447
+ "data-valid": booleanishAttrValue(valid === true),
448
+ "data-form-element": "input",
449
+ ref: mergeRefs([ref, forwardRef]),
450
+ size,
451
+ hovered,
452
+ focused,
453
+ valid,
454
+ disabled: booleanify(disabled),
455
+ ariaDisabled: booleanify(ariaDisabled),
456
+ readOnly: booleanify(readOnly),
457
+ children: [
458
+ children,
459
+ showValidityIcon && /* @__PURE__ */ jsx(StyledValidityBox, { children: /* @__PURE__ */ jsx(ValidIcon, { size: "small", weight: "thin" }) })
460
+ ]
461
+ }
462
+ );
463
+ });
464
+ const BaseInput = React.forwardRef(
465
+ ({
466
+ "aria-disabled": ariaDisabled,
467
+ disabled,
468
+ valid,
469
+ readOnly,
470
+ required,
471
+ onChange,
472
+ ...restProps
473
+ }, forwardRef) => /* @__PURE__ */ jsx(
474
+ InputProvider,
475
+ {
476
+ valid,
477
+ disabled,
478
+ "aria-disabled": ariaDisabled,
479
+ readOnly,
480
+ required,
481
+ onChange,
482
+ children: /* @__PURE__ */ jsx(Root, { ...restProps, ref: forwardRef })
483
+ }
484
+ )
485
+ );
486
+ BaseInput.Input = Input;
487
+ BaseInput.ActionButton = ActionButton;
488
+ BaseInput.IconSlot = IconSlot;
149
489
 
150
- export { FloatingLabel, inputSymbol, isInputComponent, styles, useAriaDisabled };
490
+ export { BaseInput, useInputContext };
151
491
  //# sourceMappingURL=module.js.map