diff --git a/package.json b/package.json index b0b608f013..9739e5e37d 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "lint:fix": "biome check --write && foundry run eslint . --ext .js,.jsx,.ts,.tsx --fix", "lint:ci": "biome ci && foundry run eslint . --ext .js,.jsx,.ts,.tsx --quiet ", "lint:css": "foundry run stylelint '**/*.css'", + "lint:css:fix": "foundry run stylelint '**/*.css' --fix", "dev": "npm run docs:start", "docs": "npm run docs:start", "docs:start": "storybook dev -p 6006", @@ -95,4 +96,4 @@ "vitest": "^2.0.3", "vitest-github-actions-reporter": "^0.11.1" } -} +} \ No newline at end of file diff --git a/packages/circuit-ui/components/ColorInput/ColorInput.module.css b/packages/circuit-ui/components/ColorInput/ColorInput.module.css index 34f19ca918..99c4ecc5be 100644 --- a/packages/circuit-ui/components/ColorInput/ColorInput.module.css +++ b/packages/circuit-ui/components/ColorInput/ColorInput.module.css @@ -1,15 +1,15 @@ .suffix { + position: absolute; + top: 0; + right: 0; + width: var(--cui-spacings-exa); + height: var(--cui-spacings-exa); overflow: hidden; - border-top-right-radius: var(--cui-border-radius-byte); - border-bottom-right-radius: var(--cui-border-radius-byte); pointer-events: auto !important; border: none; border-left: 1px solid var(--cui-border-normal); - width: var(--cui-spacings-exa); - height: var(--cui-spacings-exa); - position: absolute; - top: 0; - right: 0; + border-top-right-radius: var(--cui-border-radius-byte); + border-bottom-right-radius: var(--cui-border-radius-byte); box-shadow: none; } @@ -20,13 +20,13 @@ line-height: var(--cui-spacings-mega); } -.colorInput { - opacity: 0; +.color-input { width: var(--cui-spacings-exa); height: var(--cui-spacings-exa); + padding: 0; border: none; box-shadow: none; - padding: 0; + opacity: 0; } .input { @@ -43,4 +43,4 @@ .colorpick:focus-within input { box-shadow: 0 0 0 2px var(--cui-border-accent); -} \ No newline at end of file +} diff --git a/packages/circuit-ui/components/ColorInput/ColorInput.spec.tsx b/packages/circuit-ui/components/ColorInput/ColorInput.spec.tsx index e323c5f449..6ed9b6aafc 100644 --- a/packages/circuit-ui/components/ColorInput/ColorInput.spec.tsx +++ b/packages/circuit-ui/components/ColorInput/ColorInput.spec.tsx @@ -16,7 +16,7 @@ import { describe, expect, it } from 'vitest'; import { createRef } from 'react'; -import { render, axe } from '../../util/test-utils.js'; +import { render, axe, screen } from '../../util/test-utils.js'; import type { InputElement } from '../Input/index.js'; import { ColorInput } from './ColorInput.js'; @@ -36,4 +36,29 @@ describe('ColorInput', () => { const actual = await axe(container); expect(actual).toHaveNoViolations(); }); + + describe('Labeling', () => { + const HEX_SYMBOL = '#'; + + it('should have the currency symbol as part of its accessible description', () => { + render(); + expect(screen.getByRole('textbox')).toHaveAccessibleDescription( + HEX_SYMBOL, + ); + }); + + it('should accept a custom description via aria-describedby', () => { + const customDescription = 'Custom description'; + const customDescriptionId = 'customDescriptionId'; + render( + <> + {customDescription} + + , + ); + expect(screen.getByRole('textbox')).toHaveAccessibleDescription( + `${HEX_SYMBOL} ${customDescription}`, + ); + }); + }); }); diff --git a/packages/circuit-ui/components/ColorInput/ColorInput.tsx b/packages/circuit-ui/components/ColorInput/ColorInput.tsx index 7f22e1b0c4..7c2ccaf2f6 100644 --- a/packages/circuit-ui/components/ColorInput/ColorInput.tsx +++ b/packages/circuit-ui/components/ColorInput/ColorInput.tsx @@ -67,7 +67,17 @@ export interface ColorInputProps export const ColorInput = forwardRef( ( - { onChange, className, value, defaultValue, pickerLabel, ...props }, + { + onChange, + className, + value, + defaultValue, + pickerLabel, + readOnly, + 'aria-describedby': descriptionId, + id, + ...props + }, ref, ) => { const [currentColor, setCurrentColor] = useState( @@ -76,6 +86,10 @@ export const ColorInput = forwardRef( const colorDisplayRef = useRef(null); const colorPickerRef = useRef(null); const pickerId = useId(); + const hexSymbolId = useId(); + const inputFallbackId = useId(); + const inputId = id || inputFallbackId; + const descriptionIds = clsx(hexSymbolId, descriptionId); const onPickerColorChange: ChangeEventHandler = (e) => { setCurrentColor(e.target.value); @@ -97,6 +111,8 @@ export const ColorInput = forwardRef( } }, [currentColor]); + // render suffix only once, otherwise if it gets re-rendered on color change + // the native color-picker widget might get mistakenly dismissed by the browser const renderSuffix = useCallback( () => (
(
), @@ -124,7 +142,7 @@ export const ColorInput = forwardRef( className={styles.colorpick} renderPrefix={({ className: cn }) => (
- # + #
)} renderSuffix={renderSuffix} @@ -133,6 +151,9 @@ export const ColorInput = forwardRef( maxLength={6} pattern="[0-9a-f]{3,6}" onChange={onInputChange} + aria-describedby={descriptionIds} + id={inputId} + readOnly={readOnly} {...props} /> );