@trackunit/react-form-components 1.8.120 → 1.8.122

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/index.cjs.js CHANGED
@@ -15,8 +15,8 @@ var ReactAsyncSelect = require('react-select/async');
15
15
  var ReactAsyncCreatableSelect = require('react-select/async-creatable');
16
16
  var ReactCreatableSelect = require('react-select/creatable');
17
17
  var sharedUtils = require('@trackunit/shared-utils');
18
- var reactHookForm = require('react-hook-form');
19
18
  var tailwindMerge = require('tailwind-merge');
19
+ var reactHookForm = require('react-hook-form');
20
20
  var zod = require('zod');
21
21
 
22
22
  var defaultTranslations = {
@@ -1731,18 +1731,36 @@ const isValidHEXColor = (value) => {
1731
1731
  * ColorField validates that user enters a valid color address.
1732
1732
  *
1733
1733
  */
1734
- const ColorField = react.forwardRef(({ label, id, tip, helpText, errorMessage, helpAddon, className, defaultValue, "data-testid": dataTestId, value: propValue, onChange, isInvalid = false, onBlur, fieldSize = "medium", ...rest }, ref) => {
1735
- const renderAsDisabled = Boolean(rest.disabled);
1736
- const renderAsReadonly = Boolean(rest.readOnly);
1734
+ const ColorField = react.forwardRef(({ label, id, tip, helpText, errorMessage, helpAddon, className, defaultValue, "data-testid": dataTestId, value: propValue, onChange, isInvalid, onBlur, fieldSize = "medium", style, disabled, readOnly, nonInteractive, isWarning, inputClassName, ...inputProps }, ref) => {
1735
+ const renderAsDisabled = Boolean(disabled);
1736
+ const renderAsReadonly = Boolean(readOnly);
1737
1737
  const htmlForId = react.useMemo(() => (id ? id : "colorField-" + sharedUtils.uuidv4()), [id]);
1738
1738
  const innerRef = react.useRef(null);
1739
1739
  react.useImperativeHandle(ref, () => innerRef.current, []);
1740
1740
  const [t] = useTranslation();
1741
1741
  // Internal state for color value
1742
- const [innerValue, setInnerValue] = react.useState(propValue || defaultValue || "");
1743
- const [renderAsInvalid, setRenderAsInvalid] = react.useState(!!errorMessage || (innerValue && typeof innerValue === "string" && !isValidHEXColor(innerValue)) || isInvalid);
1744
- const errorType = react.useMemo(() => validateColorCode(innerValue, rest.required), [rest.required, innerValue]);
1742
+ const [innerValue, setInnerValue] = react.useState(propValue ?? defaultValue ?? "");
1743
+ const [hasBeenBlurred, setHasBeenBlurred] = react.useState(false);
1744
+ const errorType = react.useMemo(() => validateColorCode(innerValue, inputProps.required), [inputProps.required, innerValue]);
1745
1745
  const error = react.useMemo(() => (errorType ? t(`colorField.error.${errorType}`) : errorMessage), [errorType, errorMessage, t]);
1746
+ // Derive renderAsInvalid from props and internal validation
1747
+ const renderAsInvalid = react.useMemo(() => {
1748
+ if (isInvalid !== undefined) {
1749
+ return isInvalid;
1750
+ }
1751
+ if (errorMessage) {
1752
+ return true;
1753
+ }
1754
+ // Show invalid immediately if value is invalid
1755
+ if (Boolean(innerValue) && isString(innerValue) && !isValidHEXColor(innerValue)) {
1756
+ return true;
1757
+ }
1758
+ // Show invalid after blur if validation error exists
1759
+ if (hasBeenBlurred && errorType) {
1760
+ return true;
1761
+ }
1762
+ return false;
1763
+ }, [isInvalid, errorMessage, innerValue, hasBeenBlurred, errorType]);
1746
1764
  const handleInputChange = react.useCallback((event) => {
1747
1765
  const newValue = event.target.value;
1748
1766
  setInnerValue(newValue);
@@ -1753,18 +1771,19 @@ const ColorField = react.forwardRef(({ label, id, tip, helpText, errorMessage, h
1753
1771
  const handleBlur = react.useCallback(event => {
1754
1772
  const newValue = event.target.value;
1755
1773
  setInnerValue(newValue);
1756
- setRenderAsInvalid(!!errorType);
1774
+ setHasBeenBlurred(true);
1757
1775
  onBlur?.(event);
1758
- }, [errorType, onBlur]);
1759
- return (jsxRuntime.jsx(FormGroup, { "data-testid": dataTestId ? `${dataTestId}-FormGroup` : undefined, helpAddon: helpAddon, helpText: (renderAsInvalid && error) || helpText, htmlFor: htmlForId, isInvalid: renderAsInvalid, label: label, required: rest.required ? !(renderAsDisabled || renderAsReadonly) : false, tip: tip, children: jsxRuntime.jsxs("div", { className: cvaInput$1({
1776
+ }, [onBlur]);
1777
+ return (jsxRuntime.jsx(FormGroup, { "data-testid": dataTestId ? `${dataTestId}-FormGroup` : undefined, helpAddon: helpAddon, helpText: (renderAsInvalid && error) || helpText, htmlFor: htmlForId, isInvalid: renderAsInvalid, label: label, required: inputProps.required ? !(renderAsDisabled || renderAsReadonly) : false, tip: tip, children: jsxRuntime.jsxs("div", { className: cvaInput$1({
1760
1778
  size: fieldSize,
1761
1779
  disabled: renderAsDisabled,
1762
1780
  invalid: renderAsInvalid,
1763
1781
  readOnly: renderAsReadonly,
1782
+ isWarning,
1764
1783
  className,
1765
- }), "data-testid": dataTestId ? `${dataTestId}-container` : undefined, children: [jsxRuntime.jsx("input", { "aria-labelledby": htmlForId + "-label", className: cvaInputColorField({ readOnly: renderAsReadonly }), "data-testid": dataTestId, defaultValue: defaultValue, disabled: renderAsDisabled, id: htmlForId, onBlur: handleBlur, onChange: handleInputChange, readOnly: renderAsReadonly, ref: innerRef, type: "color", value: innerValue }), jsxRuntime.jsx("input", { "aria-labelledby": htmlForId + "-label-text", className: cvaInputElement({
1766
- className: "px-1 focus-visible:outline-none",
1767
- }), "data-testid": dataTestId ? `${dataTestId}-textField` : undefined, disabled: renderAsDisabled, onBlur: handleBlur, onChange: handleInputChange, readOnly: renderAsReadonly, type: "text", value: innerValue }), jsxRuntime.jsx(GenericActionsRenderer, { disabled: renderAsDisabled || renderAsReadonly, fieldSize: fieldSize, genericAction: "edit", innerRef: innerRef, tooltipLabel: t("colorField.tooltip") })] }) }));
1784
+ }), "data-testid": dataTestId ? `${dataTestId}-container` : undefined, style: style, children: [jsxRuntime.jsx("input", { "aria-labelledby": htmlForId + "-label", className: cvaInputColorField({ readOnly: renderAsReadonly }), "data-testid": dataTestId, defaultValue: defaultValue, disabled: renderAsDisabled, id: htmlForId, onBlur: handleBlur, onChange: handleInputChange, readOnly: renderAsReadonly, ref: innerRef, type: "color", value: innerValue }), jsxRuntime.jsx("input", { "aria-labelledby": htmlForId + "-label-text", className: cvaInputElement({
1785
+ className: tailwindMerge.twMerge("px-1 focus-visible:outline-none", inputClassName),
1786
+ }), "data-testid": dataTestId ? `${dataTestId}-textField` : undefined, disabled: renderAsDisabled, onBlur: handleBlur, onChange: handleInputChange, readOnly: renderAsReadonly || nonInteractive, type: "text", value: innerValue, ...inputProps }), jsxRuntime.jsx(GenericActionsRenderer, { disabled: renderAsDisabled || renderAsReadonly, fieldSize: fieldSize, genericAction: "edit", innerRef: innerRef, tooltipLabel: t("colorField.tooltip") })] }) }));
1768
1787
  });
1769
1788
  ColorField.displayName = "ColorField";
1770
1789
 
package/index.esm.js CHANGED
@@ -14,8 +14,8 @@ import ReactAsyncSelect from 'react-select/async';
14
14
  import ReactAsyncCreatableSelect from 'react-select/async-creatable';
15
15
  import ReactCreatableSelect from 'react-select/creatable';
16
16
  import { uuidv4, nonNullable } from '@trackunit/shared-utils';
17
- import { Controller } from 'react-hook-form';
18
17
  import { twMerge } from 'tailwind-merge';
18
+ import { Controller } from 'react-hook-form';
19
19
  import { z } from 'zod';
20
20
 
21
21
  var defaultTranslations = {
@@ -1730,18 +1730,36 @@ const isValidHEXColor = (value) => {
1730
1730
  * ColorField validates that user enters a valid color address.
1731
1731
  *
1732
1732
  */
1733
- const ColorField = forwardRef(({ label, id, tip, helpText, errorMessage, helpAddon, className, defaultValue, "data-testid": dataTestId, value: propValue, onChange, isInvalid = false, onBlur, fieldSize = "medium", ...rest }, ref) => {
1734
- const renderAsDisabled = Boolean(rest.disabled);
1735
- const renderAsReadonly = Boolean(rest.readOnly);
1733
+ const ColorField = forwardRef(({ label, id, tip, helpText, errorMessage, helpAddon, className, defaultValue, "data-testid": dataTestId, value: propValue, onChange, isInvalid, onBlur, fieldSize = "medium", style, disabled, readOnly, nonInteractive, isWarning, inputClassName, ...inputProps }, ref) => {
1734
+ const renderAsDisabled = Boolean(disabled);
1735
+ const renderAsReadonly = Boolean(readOnly);
1736
1736
  const htmlForId = useMemo(() => (id ? id : "colorField-" + uuidv4()), [id]);
1737
1737
  const innerRef = useRef(null);
1738
1738
  useImperativeHandle(ref, () => innerRef.current, []);
1739
1739
  const [t] = useTranslation();
1740
1740
  // Internal state for color value
1741
- const [innerValue, setInnerValue] = useState(propValue || defaultValue || "");
1742
- const [renderAsInvalid, setRenderAsInvalid] = useState(!!errorMessage || (innerValue && typeof innerValue === "string" && !isValidHEXColor(innerValue)) || isInvalid);
1743
- const errorType = useMemo(() => validateColorCode(innerValue, rest.required), [rest.required, innerValue]);
1741
+ const [innerValue, setInnerValue] = useState(propValue ?? defaultValue ?? "");
1742
+ const [hasBeenBlurred, setHasBeenBlurred] = useState(false);
1743
+ const errorType = useMemo(() => validateColorCode(innerValue, inputProps.required), [inputProps.required, innerValue]);
1744
1744
  const error = useMemo(() => (errorType ? t(`colorField.error.${errorType}`) : errorMessage), [errorType, errorMessage, t]);
1745
+ // Derive renderAsInvalid from props and internal validation
1746
+ const renderAsInvalid = useMemo(() => {
1747
+ if (isInvalid !== undefined) {
1748
+ return isInvalid;
1749
+ }
1750
+ if (errorMessage) {
1751
+ return true;
1752
+ }
1753
+ // Show invalid immediately if value is invalid
1754
+ if (Boolean(innerValue) && isString(innerValue) && !isValidHEXColor(innerValue)) {
1755
+ return true;
1756
+ }
1757
+ // Show invalid after blur if validation error exists
1758
+ if (hasBeenBlurred && errorType) {
1759
+ return true;
1760
+ }
1761
+ return false;
1762
+ }, [isInvalid, errorMessage, innerValue, hasBeenBlurred, errorType]);
1745
1763
  const handleInputChange = useCallback((event) => {
1746
1764
  const newValue = event.target.value;
1747
1765
  setInnerValue(newValue);
@@ -1752,18 +1770,19 @@ const ColorField = forwardRef(({ label, id, tip, helpText, errorMessage, helpAdd
1752
1770
  const handleBlur = useCallback(event => {
1753
1771
  const newValue = event.target.value;
1754
1772
  setInnerValue(newValue);
1755
- setRenderAsInvalid(!!errorType);
1773
+ setHasBeenBlurred(true);
1756
1774
  onBlur?.(event);
1757
- }, [errorType, onBlur]);
1758
- return (jsx(FormGroup, { "data-testid": dataTestId ? `${dataTestId}-FormGroup` : undefined, helpAddon: helpAddon, helpText: (renderAsInvalid && error) || helpText, htmlFor: htmlForId, isInvalid: renderAsInvalid, label: label, required: rest.required ? !(renderAsDisabled || renderAsReadonly) : false, tip: tip, children: jsxs("div", { className: cvaInput$1({
1775
+ }, [onBlur]);
1776
+ return (jsx(FormGroup, { "data-testid": dataTestId ? `${dataTestId}-FormGroup` : undefined, helpAddon: helpAddon, helpText: (renderAsInvalid && error) || helpText, htmlFor: htmlForId, isInvalid: renderAsInvalid, label: label, required: inputProps.required ? !(renderAsDisabled || renderAsReadonly) : false, tip: tip, children: jsxs("div", { className: cvaInput$1({
1759
1777
  size: fieldSize,
1760
1778
  disabled: renderAsDisabled,
1761
1779
  invalid: renderAsInvalid,
1762
1780
  readOnly: renderAsReadonly,
1781
+ isWarning,
1763
1782
  className,
1764
- }), "data-testid": dataTestId ? `${dataTestId}-container` : undefined, children: [jsx("input", { "aria-labelledby": htmlForId + "-label", className: cvaInputColorField({ readOnly: renderAsReadonly }), "data-testid": dataTestId, defaultValue: defaultValue, disabled: renderAsDisabled, id: htmlForId, onBlur: handleBlur, onChange: handleInputChange, readOnly: renderAsReadonly, ref: innerRef, type: "color", value: innerValue }), jsx("input", { "aria-labelledby": htmlForId + "-label-text", className: cvaInputElement({
1765
- className: "px-1 focus-visible:outline-none",
1766
- }), "data-testid": dataTestId ? `${dataTestId}-textField` : undefined, disabled: renderAsDisabled, onBlur: handleBlur, onChange: handleInputChange, readOnly: renderAsReadonly, type: "text", value: innerValue }), jsx(GenericActionsRenderer, { disabled: renderAsDisabled || renderAsReadonly, fieldSize: fieldSize, genericAction: "edit", innerRef: innerRef, tooltipLabel: t("colorField.tooltip") })] }) }));
1783
+ }), "data-testid": dataTestId ? `${dataTestId}-container` : undefined, style: style, children: [jsx("input", { "aria-labelledby": htmlForId + "-label", className: cvaInputColorField({ readOnly: renderAsReadonly }), "data-testid": dataTestId, defaultValue: defaultValue, disabled: renderAsDisabled, id: htmlForId, onBlur: handleBlur, onChange: handleInputChange, readOnly: renderAsReadonly, ref: innerRef, type: "color", value: innerValue }), jsx("input", { "aria-labelledby": htmlForId + "-label-text", className: cvaInputElement({
1784
+ className: twMerge("px-1 focus-visible:outline-none", inputClassName),
1785
+ }), "data-testid": dataTestId ? `${dataTestId}-textField` : undefined, disabled: renderAsDisabled, onBlur: handleBlur, onChange: handleInputChange, readOnly: renderAsReadonly || nonInteractive, type: "text", value: innerValue, ...inputProps }), jsx(GenericActionsRenderer, { disabled: renderAsDisabled || renderAsReadonly, fieldSize: fieldSize, genericAction: "edit", innerRef: innerRef, tooltipLabel: t("colorField.tooltip") })] }) }));
1767
1786
  });
1768
1787
  ColorField.displayName = "ColorField";
1769
1788
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trackunit/react-form-components",
3
- "version": "1.8.120",
3
+ "version": "1.8.122",
4
4
  "repository": "https://github.com/Trackunit/manager",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "engines": {
@@ -14,12 +14,12 @@
14
14
  "zod": "^3.23.8",
15
15
  "react-hook-form": "7.62.0",
16
16
  "tailwind-merge": "^2.0.0",
17
- "@trackunit/css-class-variance-utilities": "1.7.78",
18
- "@trackunit/react-components": "1.10.53",
19
- "@trackunit/ui-icons": "1.7.79",
20
- "@trackunit/shared-utils": "1.9.78",
21
- "@trackunit/ui-design-tokens": "1.7.78",
22
- "@trackunit/i18n-library-translation": "1.7.96",
17
+ "@trackunit/css-class-variance-utilities": "1.7.79",
18
+ "@trackunit/react-components": "1.10.54",
19
+ "@trackunit/ui-icons": "1.7.80",
20
+ "@trackunit/shared-utils": "1.9.79",
21
+ "@trackunit/ui-design-tokens": "1.7.79",
22
+ "@trackunit/i18n-library-translation": "1.7.97",
23
23
  "string-ts": "^2.0.0",
24
24
  "@js-temporal/polyfill": "^0.5.1",
25
25
  "@storybook/react-webpack5": "9.1.13"
@@ -1,6 +1,8 @@
1
+ import { MappedOmit } from "@trackunit/shared-utils";
1
2
  import { BaseInputProps } from "../BaseInput";
2
3
  import { FormGroupProps } from "../FormGroup/FormGroup";
3
4
  type FormGroupExposedProps = Pick<FormGroupProps, "label" | "tip" | "helpText" | "helpAddon">;
5
+ type ColorFieldBaseInputProps = MappedOmit<BaseInputProps, "prefix" | "suffix" | "addonBefore" | "addonAfter" | "actions" | "genericAction">;
4
6
  /**
5
7
  * Validates if the given value is a valid hex color.
6
8
  *
@@ -8,7 +10,7 @@ type FormGroupExposedProps = Pick<FormGroupProps, "label" | "tip" | "helpText" |
8
10
  * @returns {boolean} True if the value is a valid hex color, otherwise false.
9
11
  */
10
12
  export declare const isValidHEXColor: (value: string) => boolean;
11
- export interface ColorFieldProps extends FormGroupExposedProps, BaseInputProps {
13
+ export interface ColorFieldProps extends FormGroupExposedProps, ColorFieldBaseInputProps {
12
14
  /**
13
15
  * If a value is set, the field is rendered in its invalid state.
14
16
  */