From 3045c38ef2ccb349500c7d6042af899dd720fa74 Mon Sep 17 00:00:00 2001 From: Josh Wooding <12938082+joshwooding@users.noreply.github.com> Date: Fri, 16 Aug 2024 11:01:36 +0100 Subject: [PATCH] ComboBox fixes (#3993) --- .changeset/twelve-glasses-obey.md | 7 + packages/core/src/combo-box/ComboBox.css | 2 +- packages/core/src/combo-box/ComboBox.tsx | 4 +- packages/core/src/pill-input/PillInput.css | 142 ++++++++++----------- packages/core/src/pill-input/PillInput.tsx | 17 +++ site/src/examples/combo-box/Disabled.tsx | 19 ++- 6 files changed, 112 insertions(+), 79 deletions(-) create mode 100644 .changeset/twelve-glasses-obey.md diff --git a/.changeset/twelve-glasses-obey.md b/.changeset/twelve-glasses-obey.md new file mode 100644 index 0000000000..08022ec851 --- /dev/null +++ b/.changeset/twelve-glasses-obey.md @@ -0,0 +1,7 @@ +--- +"@salt-ds/core": patch +--- + +- Fixed ComboBox breaking when `inputRef` is used. +- Fixed ComboBox having an incorrect focus ring color when validation is applied. +- Fixed ComboBox having incorrect active styling. diff --git a/packages/core/src/combo-box/ComboBox.css b/packages/core/src/combo-box/ComboBox.css index 4acb35a23b..3263d7775e 100644 --- a/packages/core/src/combo-box/ComboBox.css +++ b/packages/core/src/combo-box/ComboBox.css @@ -1,3 +1,3 @@ .saltComboBox-focused { - outline: var(--salt-focused-outline); + outline: var(--saltPillInput-outline, var(--salt-focused-outlineWidth) var(--salt-focused-outlineStyle) var(--pillInput-outlineColor)); } diff --git a/packages/core/src/combo-box/ComboBox.tsx b/packages/core/src/combo-box/ComboBox.tsx index 97b210dcd3..0e41135e1c 100644 --- a/packages/core/src/combo-box/ComboBox.tsx +++ b/packages/core/src/combo-box/ComboBox.tsx @@ -77,6 +77,7 @@ export const ComboBox = forwardRef(function ComboBox( onOpenChange, onChange, open, + inputRef: inputRefProp, inputProps: inputPropsProp, variant = "primary", onKeyDown, @@ -106,6 +107,7 @@ export const ComboBox = forwardRef(function ComboBox( const disabled = Boolean(disabledProp) || formFieldDisabled; const readOnly = Boolean(readOnlyProp) || formFieldReadOnly; const inputRef = useRef(null); + const handleInputRef = useForkRef(inputRef, inputRefProp); const listControl = useComboBox({ open, @@ -447,7 +449,7 @@ export const ComboBox = forwardRef(function ComboBox( }} aria-activedescendant={activeState?.id} variant={variant} - inputRef={inputRef} + inputRef={handleInputRef} value={valueState} ref={handleRef} bordered={bordered} diff --git a/packages/core/src/pill-input/PillInput.css b/packages/core/src/pill-input/PillInput.css index 2bb2a733be..8d074abb9c 100644 --- a/packages/core/src/pill-input/PillInput.css +++ b/packages/core/src/pill-input/PillInput.css @@ -1,27 +1,27 @@ /* Style applied to the root element */ .saltPillInput { - --input-border: none; - --input-borderColor: var(--salt-editable-borderColor); - --input-borderStyle: var(--salt-editable-borderStyle); - --input-outlineColor: var(--salt-focused-outlineColor); - --input-borderWidth: var(--salt-size-border); + --pillInput-border: none; + --pillInput-borderColor: var(--salt-editable-borderColor); + --pillInput-borderStyle: var(--salt-editable-borderStyle); + --pillInput-outlineColor: var(--salt-focused-outlineColor); + --pillInput-borderWidth: var(--salt-size-border); align-items: center; - background: var(--saltInput-background, var(--input-background)); - color: var(--saltInput-color, var(--salt-content-primary-foreground)); + background: var(--saltPillInput-background, var(--pillInput-background)); + border-radius: var(--salt-palette-corner-weak, 0); + border: var(--pillInput-border); + color: var(--saltPillInput-color, var(--salt-content-primary-foreground)); display: inline-flex; font-family: var(--salt-text-fontFamily); - font-size: var(--saltInput-fontSize, var(--salt-text-fontSize)); - line-height: var(--saltInput-lineHeight, var(--salt-text-lineHeight)); - min-height: var(--saltInput-minHeight, var(--salt-size-base)); - min-width: var(--saltInput-minWidth, 4em); - padding-left: var(--saltInput-paddingLeft, var(--salt-spacing-100)); - padding-right: var(--saltInput-paddingRight, var(--salt-spacing-100)); + font-size: var(--saltPillInput-fontSize, var(--salt-text-fontSize)); + line-height: var(--saltPillInput-lineHeight, var(--salt-text-lineHeight)); + min-height: var(--saltPillInput-minHeight, var(--salt-size-base)); + min-width: var(--saltPillInput-minWidth, 4em); + padding-left: var(--saltPillInput-paddingLeft, var(--salt-spacing-100)); + padding-right: var(--saltPillInput-paddingRight, var(--salt-spacing-100)); position: relative; width: 100%; box-sizing: border-box; - border-radius: var(--salt-palette-corner-weak, 0); - border: var(--input-border); overflow: hidden; } @@ -34,71 +34,71 @@ } .saltPillInput:hover { - --input-borderStyle: var(--salt-editable-borderStyle-hover); - --input-borderColor: var(--salt-editable-borderColor-hover); + --pillInput-borderStyle: var(--salt-editable-borderStyle-hover); + --pillInput-borderColor: var(--salt-editable-borderColor-hover); - background: var(--saltInput-background-hover, var(--input-background-hover)); + background: var(--saltPillInput-background-hover, var(--pillInput-background-hover)); cursor: var(--salt-editable-cursor-hover); } .saltPillInput:active { - --input-borderColor: var(--salt-editable-borderColor-active); - --input-borderStyle: var(--salt-editable-borderStyle-active); - --input-borderWidth: var(--salt-editable-borderWidth-active); + --pillInput-borderColor: var(--salt-editable-borderColor-active); + --pillInput-borderStyle: var(--salt-editable-borderStyle-active); + --pillInput-borderWidth: var(--salt-editable-borderWidth-active); - background: var(--saltInput-background-active, var(--input-background-active)); + background: var(--saltPillInput-background-active, var(--pillInput-background-active)); cursor: var(--salt-editable-cursor-active); } /* Class applied if `variant="primary"` */ .saltPillInput-primary { - --input-background: var(--salt-editable-primary-background); - --input-background-active: var(--salt-editable-primary-background-active); - --input-background-hover: var(--salt-editable-primary-background-hover); - --input-background-disabled: var(--salt-editable-primary-background-disabled); - --input-background-readonly: var(--salt-editable-primary-background-readonly); + --pillInput-background: var(--salt-editable-primary-background); + --pillInput-background-active: var(--salt-editable-primary-background-active); + --pillInput-background-hover: var(--salt-editable-primary-background-hover); + --pillInput-background-disabled: var(--salt-editable-primary-background-disabled); + --pillInput-background-readonly: var(--salt-editable-primary-background-readonly); } /* Class applied if `variant="secondary"` */ .saltPillInput-secondary { - --input-background: var(--salt-editable-secondary-background); - --input-background-active: var(--salt-editable-secondary-background-active); - --input-background-hover: var(--salt-editable-secondary-background-active); - --input-background-disabled: var(--salt-editable-secondary-background-disabled); - --input-background-readonly: var(--salt-editable-secondary-background-readonly); + --pillInput-background: var(--salt-editable-secondary-background); + --pillInput-background-active: var(--salt-editable-secondary-background-active); + --pillInput-background-hover: var(--salt-editable-secondary-background-active); + --pillInput-background-disabled: var(--salt-editable-secondary-background-disabled); + --pillInput-background-readonly: var(--salt-editable-secondary-background-readonly); } /* Style applied to input if `validationState="error"` */ .saltPillInput-error, .saltPillInput-error:hover { - --input-background: var(--salt-status-error-background); - --input-background-active: var(--salt-status-error-background); - --input-background-hover: var(--salt-status-error-background); - --input-borderColor: var(--salt-status-error-borderColor); - --input-outlineColor: var(--salt-status-error-borderColor); - --input-background-readonly: var(--salt-status-error-background); + --pillInput-background: var(--salt-status-error-background); + --pillInput-background-active: var(--salt-status-error-background); + --pillInput-background-hover: var(--salt-status-error-background); + --pillInput-borderColor: var(--salt-status-error-borderColor); + --pillInput-outlineColor: var(--salt-status-error-borderColor); + --pillInput-background-readonly: var(--salt-status-error-background); } /* Style applied to input if `validationState="warning"` */ .saltPillInput-warning, .saltPillInput-warning:hover { - --input-background: var(--salt-status-warning-background); - --input-background-active: var(--salt-status-warning-background); - --input-background-hover: var(--salt-status-warning-background); - --input-borderColor: var(--salt-status-warning-borderColor); - --input-outlineColor: var(--salt-status-warning-borderColor); - --input-background-readonly: var(--salt-status-warning-background); + --pillInput-background: var(--salt-status-warning-background); + --pillInput-background-active: var(--salt-status-warning-background); + --pillInput-background-hover: var(--salt-status-warning-background); + --pillInput-borderColor: var(--salt-status-warning-borderColor); + --pillInput-outlineColor: var(--salt-status-warning-borderColor); + --pillInput-background-readonly: var(--salt-status-warning-background); } /* Style applied to input if `validationState="success"` */ .saltPillInput-success, .saltPillInput-success:hover { - --input-background: var(--salt-status-success-background); - --input-background-active: var(--salt-status-success-background); - --input-background-hover: var(--salt-status-success-background); - --input-borderColor: var(--salt-status-success-borderColor); - --input-outlineColor: var(--salt-status-success-borderColor); - --input-background-readonly: var(--salt-status-success-background); + --pillInput-background: var(--salt-status-success-background); + --pillInput-background-active: var(--salt-status-success-background); + --pillInput-background-hover: var(--salt-status-success-background); + --pillInput-borderColor: var(--salt-status-success-borderColor); + --pillInput-outlineColor: var(--salt-status-success-borderColor); + --pillInput-background-readonly: var(--salt-status-success-background); } /* Style applied to inner input component */ @@ -112,12 +112,12 @@ flex: 1; font: inherit; height: 100%; - letter-spacing: var(--saltInput-letterSpacing, 0); + letter-spacing: var(--saltPillInput-letterSpacing, 0); margin: 0; min-width: 0; overflow: hidden; padding: 0; - text-align: var(--input-textAlign); + text-align: var(--pillInput-textAlign); width: 100%; } @@ -140,31 +140,31 @@ /* Styling when focused */ .saltPillInput-focused, .saltPillInput-focused:hover { - --input-borderColor: var(--input-outlineColor); - --input-borderWidth: var(--salt-editable-borderWidth-active); + --pillInput-borderColor: var(--pillInput-outlineColor); + --pillInput-borderWidth: var(--salt-editable-borderWidth-active); - outline: var(--saltInput-outline, var(--salt-focused-outlineWidth) var(--salt-focused-outlineStyle) var(--input-outlineColor)); + outline: var(--saltPillInput-outline, var(--salt-focused-outlineWidth) var(--salt-focused-outlineStyle) var(--pillInput-outlineColor)); } /* Style applied if `readOnly={true}` */ .saltPillInput.saltPillInput-readOnly { - --input-borderColor: var(--salt-editable-borderColor-readonly); - --input-borderStyle: var(--salt-editable-borderStyle-readonly); - --input-borderWidth: var(--salt-size-border); + --pillInput-borderColor: var(--salt-editable-borderColor-readonly); + --pillInput-borderStyle: var(--salt-editable-borderStyle-readonly); + --pillInput-borderWidth: var(--salt-size-border); - background: var(--input-background-readonly); + background: var(--pillInput-background-readonly); cursor: var(--salt-editable-cursor-readonly); } /* Styling when focused if `disabled={true}` */ .saltPillInput-focused.saltPillInput-disabled { - --input-borderWidth: var(--salt-size-border); + --pillInput-borderWidth: var(--salt-size-border); outline: none; } /* Styling when focused if `readOnly={true}` */ .saltPillInput-focused.saltPillInput-readOnly { - --input-borderWidth: var(--salt-size-border); + --pillInput-borderWidth: var(--salt-size-border); } /* Style applied to selected input if `disabled={true}` */ @@ -176,13 +176,13 @@ .saltPillInput.saltPillInput-disabled, .saltPillInput.saltPillInput-disabled:hover, .saltPillInput.saltPillInput-disabled:active { - --input-borderColor: var(--salt-editable-borderColor-disabled); - --input-borderStyle: var(--salt-editable-borderStyle-disabled); - --input-borderWidth: var(--salt-size-border); + --pillInput-borderColor: var(--salt-editable-borderColor-disabled); + --pillInput-borderStyle: var(--salt-editable-borderStyle-disabled); + --pillInput-borderWidth: var(--salt-size-border); - background: var(--input-background-disabled); + background: var(--pillInput-background-disabled); cursor: var(--salt-editable-cursor-disabled); - color: var(--saltInput-color-disabled, var(--salt-content-primary-foreground-disabled)); + color: var(--saltPillInput-color-disabled, var(--salt-content-primary-foreground-disabled)); } .saltPillInput-activationIndicator { @@ -190,26 +190,26 @@ bottom: 0; width: 100%; position: absolute; - border-bottom: var(--input-borderWidth) var(--input-borderStyle) var(--input-borderColor); + border-bottom: var(--pillInput-borderWidth) var(--pillInput-borderStyle) var(--pillInput-borderColor); } /* Style applied if `bordered={true}` */ .saltPillInput.saltPillInput-bordered { - --input-border: var(--salt-size-border) var(--salt-container-borderStyle) var(--input-borderColor); - --input-borderWidth: 0; + --pillInput-border: var(--salt-size-border) var(--salt-container-borderStyle) var(--pillInput-borderColor); + --pillInput-borderWidth: 0; } /* Style applied if focused or active when `bordered={true}` */ .saltPillInput-bordered.saltPillInput-focused, .saltPillInput-bordered:active { - --input-borderWidth: var(--salt-editable-borderWidth-active); + --pillInput-borderWidth: var(--salt-editable-borderWidth-active); } /* Styling when focused if `disabled={true}` or `readOnly={true}` when `bordered={true}` */ .saltPillInput-bordered.saltPillInput-readOnly, .saltPillInput-bordered.saltPillInput-disabled:hover, .saltPillInput-bordered.saltPillInput-disabled.saltPillInput-focused { - --input-borderWidth: 0; + --pillInput-borderWidth: 0; } /* Style applied to start adornments */ diff --git a/packages/core/src/pill-input/PillInput.tsx b/packages/core/src/pill-input/PillInput.tsx index 3018ef37df..31edd73daf 100644 --- a/packages/core/src/pill-input/PillInput.tsx +++ b/packages/core/src/pill-input/PillInput.tsx @@ -5,6 +5,7 @@ import { clsx } from "clsx"; import { type ChangeEvent, type ComponentPropsWithoutRef, + type FocusEvent, type ForwardedRef, type InputHTMLAttributes, type KeyboardEvent, @@ -140,6 +141,7 @@ export const PillInput = forwardRef(function PillInput( const isReadOnly = readOnlyProp || formFieldReadOnly; const validationStatus = formFieldValidationStatus ?? validationStatusProp; + const [focused, setFocused] = useState(false); const [focusedPillIndex, setFocusedPillIndex] = useState(-1); const isEmptyReadOnly = isReadOnly && !defaultValueProp && !valueProp; @@ -148,6 +150,8 @@ export const PillInput = forwardRef(function PillInput( const { "aria-describedby": inputDescribedBy, "aria-labelledby": inputLabelledBy, + onBlur, + onFocus, onChange, required: inputPropsRequired, onKeyDown: inputOnKeyDown, @@ -238,6 +242,16 @@ export const PillInput = forwardRef(function PillInput( inputRef.current?.focus(); }; + const handleBlur = (event: FocusEvent) => { + onBlur?.(event); + setFocused(false); + }; + + const handleFocus = (event: FocusEvent) => { + onFocus?.(event); + setFocused(true); + }; + const inputStyle = { "--input-textAlign": textAlign, ...style, @@ -249,6 +263,7 @@ export const PillInput = forwardRef(function PillInput( withBaseName(), withBaseName(variant), { + [withBaseName("focused")]: !isDisabled && focused, [withBaseName("disabled")]: isDisabled, [withBaseName("readOnly")]: isReadOnly, [withBaseName("truncate")]: truncate, @@ -321,7 +336,9 @@ export const PillInput = forwardRef(function PillInput( ref={handleInputRef} role={role} tabIndex={isDisabled ? -1 : 0} + onBlur={handleBlur} onChange={handleChange} + onFocus={!isDisabled ? handleFocus : undefined} onKeyDown={handleKeyDown} placeholder={placeholder} value={value} diff --git a/site/src/examples/combo-box/Disabled.tsx b/site/src/examples/combo-box/Disabled.tsx index 10489fee3f..fc097370de 100644 --- a/site/src/examples/combo-box/Disabled.tsx +++ b/site/src/examples/combo-box/Disabled.tsx @@ -1,13 +1,20 @@ -import { ComboBox, Option } from "@salt-ds/core"; +import { ComboBox, Option, StackLayout } from "@salt-ds/core"; import type { ReactElement } from "react"; import { shortColorData } from "./exampleData"; export const Disabled = (): ReactElement => { return ( - - {shortColorData.map((color) => ( - + + + {shortColorData.map((color) => ( + + + {shortColorData.map((color) => ( + + ); };