From 24cfbed7ac981d009ef9f0c5a8d63766954d8a0e Mon Sep 17 00:00:00 2001 From: Alex Carpenter Date: Mon, 14 Sep 2026 12:42:01 -0400 Subject: [PATCH] refactor(ui): bring Mosaic Button and Input under the complexity limit --- .changeset/mosaic-button-input-complexity.md | 2 + packages/ui/eslint-suppressions.json | 10 --- .../src/mosaic/components/button/button.tsx | 70 +++++++++++++------ .../ui/src/mosaic/components/input/input.tsx | 27 ++++--- 4 files changed, 67 insertions(+), 42 deletions(-) create mode 100644 .changeset/mosaic-button-input-complexity.md diff --git a/.changeset/mosaic-button-input-complexity.md b/.changeset/mosaic-button-input-complexity.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/mosaic-button-input-complexity.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/packages/ui/eslint-suppressions.json b/packages/ui/eslint-suppressions.json index 1b6aea25b65..d023e9b3a2a 100644 --- a/packages/ui/eslint-suppressions.json +++ b/packages/ui/eslint-suppressions.json @@ -134,16 +134,6 @@ "count": 4 } }, - "src/mosaic/components/button/button.tsx": { - "complexity": { - "count": 1 - } - }, - "src/mosaic/components/input/input.tsx": { - "complexity": { - "count": 1 - } - }, "src/mosaic/user-button/user-button.model.tsx": { "complexity": { "count": 1 diff --git a/packages/ui/src/mosaic/components/button/button.tsx b/packages/ui/src/mosaic/components/button/button.tsx index ae516872c93..9c3bd824d6c 100644 --- a/packages/ui/src/mosaic/components/button/button.tsx +++ b/packages/ui/src/mosaic/components/button/button.tsx @@ -94,6 +94,53 @@ export function withTruncatableLabel(children: React.ReactNode): React.ReactNode return result; } +interface ButtonStyleOptions { + color: NonNullable; + variant: NonNullable; + size: NonNullable; + shape: NonNullable; + fullWidth: boolean; + touchTarget: boolean; + disabled: boolean; + defaults: React.ContextType; + xstyle: ButtonProps['xstyle']; +} + +function buttonStyleProps({ + color, + variant, + size, + shape, + fullWidth, + touchTarget, + disabled, + defaults, + xstyle, +}: ButtonStyleOptions) { + const isIconShape = shape === 'square' || shape === 'circle'; + const hasTouchTarget = touchTarget && variant !== 'link'; + return stylex.props( + reset.base, + buttonScope, + // one ring for every color and variant — it reads as focus, not as the button's color + focusOutline.visible, + styles.base, + sizes[size], + variants[`${variant}-${color}`], + shape === 'square' && styles.shapeSquare, + shape === 'circle' && styles.shapeCircle, + isIconShape && iconSizes[size], + hasTouchTarget && styles.touchTarget, + hasTouchTarget && isIconShape && styles.touchTargetIcon, + defaults.sizeStyles, + isIconShape && defaults.iconStyles, + fullWidth && styles.fullWidth, + disabled && styles.disabled, + defaults.styles, + xstyle, + ); +} + export const Button = React.forwardRef(function MosaicButton( { color: colorProp, @@ -116,8 +163,6 @@ export const Button = React.forwardRef(function const size = sizeProp ?? defaults.size ?? 'md'; const shape = shapeProp ?? defaults.shape ?? 'default'; const disabled = defaults.disabled || disabledProp || false; - const isIconShape = shape === 'square' || shape === 'circle'; - const hasTouchTarget = touchTarget && variant !== 'link'; return ( (function focusableWhenDisabled={focusableWhenDisabled} {...mergeStyleProps( themeProps('button', { color, variant, size, shape, fullWidth, disabled }), - stylex.props( - reset.base, - buttonScope, - // one ring for every color and variant — it reads as focus, not as the button's color - focusOutline.visible, - styles.base, - sizes[size], - variants[`${variant}-${color}`], - shape === 'square' && styles.shapeSquare, - shape === 'circle' && styles.shapeCircle, - isIconShape && iconSizes[size], - hasTouchTarget && styles.touchTarget, - hasTouchTarget && isIconShape && styles.touchTargetIcon, - defaults.sizeStyles, - isIconShape && defaults.iconStyles, - fullWidth && styles.fullWidth, - disabled && styles.disabled, - defaults.styles, - xstyle, - ), + buttonStyleProps({ color, variant, size, shape, fullWidth, touchTarget, disabled, defaults, xstyle }), rest, )} > diff --git a/packages/ui/src/mosaic/components/input/input.tsx b/packages/ui/src/mosaic/components/input/input.tsx index 4aac8dcda8a..58efc01e305 100644 --- a/packages/ui/src/mosaic/components/input/input.tsx +++ b/packages/ui/src/mosaic/components/input/input.tsx @@ -19,6 +19,11 @@ export interface InputProps extends Omit, 'size'> variant?: InputVariant; } +const variantStyles = { + default: inputStyles.base, + ghost: styles.ghost, +} as const; + export const Input = React.forwardRef(function MosaicInput( { size: sizeProp, @@ -45,30 +50,32 @@ export const Input = React.forwardRef(function Mos ariaLabelledBy, ariaDescribedBy, }); + const control = fieldProps ?? { + id, + disabled: disabledProp, + required: requiredProp, + 'aria-invalid': ariaInvalid, + 'aria-labelledby': ariaLabelledBy, + 'aria-describedby': ariaDescribedBy, + }; const size = inputGroup?.size ?? sizeProp ?? 'md'; - const disabled = inputGroup?.disabled || fieldProps?.disabled || disabledProp || false; - const required = fieldProps?.required ?? requiredProp; - const ariaInvalidValue = inputGroup?.invalid ? true : (fieldProps?.['aria-invalid'] ?? ariaInvalid); + const disabled = inputGroup?.disabled || control.disabled || false; return useRender({ defaultTagName: 'input', render, ref: [forwardedRef, inputGroup?.inputRef], props: { + ...control, disabled, - required, - id: fieldProps?.id ?? id, - 'aria-invalid': ariaInvalidValue, - 'aria-labelledby': fieldProps?.['aria-labelledby'] ?? ariaLabelledBy, - 'aria-describedby': fieldProps?.['aria-describedby'] ?? ariaDescribedBy, + 'aria-invalid': inputGroup?.invalid ? true : control['aria-invalid'], ...mergeStyleProps( themeProps('input', { size, variant, disabled }), stylex.props( reset.base, styles.base, sizes[size], - variant === 'default' && inputStyles.base, - variant === 'ghost' && styles.ghost, + variantStyles[variant], variant === 'default' && disabled && inputStyles.disabled, xstyle, ),