diff --git a/src/input/styles.tsx b/src/input/styles.tsx index ac9178b9d2..e6229badc8 100644 --- a/src/input/styles.tsx +++ b/src/input/styles.tsx @@ -1,46 +1,8 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { SYSTEM } from '../internal/environment'; -import customCssProps from '../internal/generated/custom-css-properties'; +import { getInputStylesCss } from '../internal/utils/input-styles'; import { InputProps } from './interfaces'; export function getInputStyles(style: InputProps['style']) { - let properties = {}; - - if (SYSTEM === 'core') { - properties = { - borderRadius: style?.root?.borderRadius, - borderWidth: style?.root?.borderWidth, - fontSize: style?.root?.fontSize, - fontWeight: style?.root?.fontWeight, - paddingBlock: style?.root?.paddingBlock, - paddingInline: style?.root?.paddingInline, - [customCssProps.styleBackgroundDefault]: style?.root?.backgroundColor?.default, - [customCssProps.styleBackgroundDisabled]: style?.root?.backgroundColor?.disabled, - [customCssProps.styleBackgroundHover]: style?.root?.backgroundColor?.hover, - [customCssProps.styleBackgroundFocus]: style?.root?.backgroundColor?.focus, - [customCssProps.styleBackgroundReadonly]: style?.root?.backgroundColor?.readonly, - [customCssProps.styleBorderColorDefault]: style?.root?.borderColor?.default, - [customCssProps.styleBorderColorDisabled]: style?.root?.borderColor?.disabled, - [customCssProps.styleBorderColorHover]: style?.root?.borderColor?.hover, - [customCssProps.styleBorderColorFocus]: style?.root?.borderColor?.focus, - [customCssProps.styleBorderColorReadonly]: style?.root?.borderColor?.readonly, - [customCssProps.styleBoxShadowDefault]: style?.root?.boxShadow?.default, - [customCssProps.styleBoxShadowDisabled]: style?.root?.boxShadow?.disabled, - [customCssProps.styleBoxShadowHover]: style?.root?.boxShadow?.hover, - [customCssProps.styleBoxShadowFocus]: style?.root?.boxShadow?.focus, - [customCssProps.styleBoxShadowReadonly]: style?.root?.boxShadow?.readonly, - [customCssProps.styleColorDefault]: style?.root?.color?.default, - [customCssProps.styleColorDisabled]: style?.root?.color?.disabled, - [customCssProps.styleColorHover]: style?.root?.color?.hover, - [customCssProps.styleColorFocus]: style?.root?.color?.focus, - [customCssProps.styleColorReadonly]: style?.root?.color?.readonly, - [customCssProps.stylePlaceholderColor]: style?.placeholder?.color, - [customCssProps.stylePlaceholderFontSize]: style?.placeholder?.fontSize, - [customCssProps.stylePlaceholderFontWeight]: style?.placeholder?.fontWeight, - [customCssProps.stylePlaceholderFontStyle]: style?.placeholder?.fontStyle, - }; - - return properties; - } + return getInputStylesCss(style); } diff --git a/src/internal/utils/__tests__/input-styles.test.ts b/src/internal/utils/__tests__/input-styles.test.ts new file mode 100644 index 0000000000..8b5dd298a0 --- /dev/null +++ b/src/internal/utils/__tests__/input-styles.test.ts @@ -0,0 +1,160 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import customCssProps from '../../generated/custom-css-properties'; +import { getInputStylesCss, InputStyleProps } from '../input-styles'; + +// Mock the environment module +jest.mock('../../environment', () => ({ + SYSTEM: 'core', +})); + +const fullStyle: InputStyleProps = { + root: { + borderRadius: '4px', + borderWidth: '1px', + fontSize: '14px', + fontWeight: '400', + paddingBlock: '8px', + paddingInline: '12px', + backgroundColor: { + default: '#ffffff', + disabled: '#f0f0f0', + hover: '#fafafa', + focus: '#ffffff', + readonly: '#f5f5f5', + }, + borderColor: { + default: '#cccccc', + disabled: '#e0e0e0', + hover: '#999999', + focus: '#0073bb', + readonly: '#e0e0e0', + }, + boxShadow: { + default: 'none', + disabled: 'none', + hover: '0 1px 2px rgba(0,0,0,0.1)', + focus: '0 0 0 2px #0073bb', + readonly: 'none', + }, + color: { + default: '#000000', + disabled: '#999999', + hover: '#000000', + focus: '#000000', + readonly: '#666666', + }, + }, + placeholder: { + color: '#999999', + fontSize: '14px', + fontStyle: 'italic', + fontWeight: '400', + }, +}; + +describe('getInputStylesCss', () => { + afterEach(() => { + jest.resetModules(); + }); + + describe('when SYSTEM is core', () => { + test('returns full style object when all properties provided', () => { + const result = getInputStylesCss(fullStyle); + + expect(result).toEqual({ + borderRadius: '4px', + borderWidth: '1px', + fontSize: '14px', + fontWeight: '400', + paddingBlock: '8px', + paddingInline: '12px', + [customCssProps.styleBackgroundDefault]: '#ffffff', + [customCssProps.styleBackgroundDisabled]: '#f0f0f0', + [customCssProps.styleBackgroundHover]: '#fafafa', + [customCssProps.styleBackgroundFocus]: '#ffffff', + [customCssProps.styleBackgroundReadonly]: '#f5f5f5', + [customCssProps.styleBorderColorDefault]: '#cccccc', + [customCssProps.styleBorderColorDisabled]: '#e0e0e0', + [customCssProps.styleBorderColorHover]: '#999999', + [customCssProps.styleBorderColorFocus]: '#0073bb', + [customCssProps.styleBorderColorReadonly]: '#e0e0e0', + [customCssProps.styleBoxShadowDefault]: 'none', + [customCssProps.styleBoxShadowDisabled]: 'none', + [customCssProps.styleBoxShadowHover]: '0 1px 2px rgba(0,0,0,0.1)', + [customCssProps.styleBoxShadowFocus]: '0 0 0 2px #0073bb', + [customCssProps.styleBoxShadowReadonly]: 'none', + [customCssProps.styleColorDefault]: '#000000', + [customCssProps.styleColorDisabled]: '#999999', + [customCssProps.styleColorHover]: '#000000', + [customCssProps.styleColorFocus]: '#000000', + [customCssProps.styleColorReadonly]: '#666666', + [customCssProps.stylePlaceholderColor]: '#999999', + [customCssProps.stylePlaceholderFontSize]: '14px', + [customCssProps.stylePlaceholderFontWeight]: '400', + [customCssProps.stylePlaceholderFontStyle]: 'italic', + }); + }); + + test('returns object with undefined values when style is undefined', () => { + const result = getInputStylesCss(undefined); + + expect(result).toBeDefined(); + expect(result?.borderRadius).toBeUndefined(); + expect(result?.[customCssProps.styleBackgroundDefault]).toBeUndefined(); + }); + + test('returns object with undefined values when style is empty object', () => { + const result = getInputStylesCss({}); + + expect(result).toBeDefined(); + expect(result?.borderRadius).toBeUndefined(); + }); + + describe('requireRoot parameter', () => { + test('returns undefined when requireRoot=true and style is undefined', () => { + const result = getInputStylesCss(undefined, true); + expect(result).toBeUndefined(); + }); + + test('returns undefined when requireRoot=true and style.root is undefined', () => { + const result = getInputStylesCss({}, true); + expect(result).toBeUndefined(); + }); + + test('returns undefined when requireRoot=true and style.root is missing', () => { + const result = getInputStylesCss({ placeholder: { color: '#999' } }, true); + expect(result).toBeUndefined(); + }); + + test('returns style object when requireRoot=true and style.root is provided', () => { + const result = getInputStylesCss({ root: { borderRadius: '4px' } }, true); + expect(result).toBeDefined(); + expect(result?.borderRadius).toBe('4px'); + }); + }); + }); + + describe('when SYSTEM is not core', () => { + beforeEach(() => { + jest.resetModules(); + jest.doMock('../../environment', () => ({ + SYSTEM: 'visual-refresh', + })); + }); + + test('returns undefined regardless of style input', async () => { + const { getInputStylesCss: getInputStylesCssNonCore } = await import('../input-styles'); + + expect(getInputStylesCssNonCore(fullStyle)).toBeUndefined(); + expect(getInputStylesCssNonCore(undefined)).toBeUndefined(); + expect(getInputStylesCssNonCore({})).toBeUndefined(); + }); + + test('returns undefined even with requireRoot=true', async () => { + const { getInputStylesCss: getInputStylesCssNonCore } = await import('../input-styles'); + + expect(getInputStylesCssNonCore(fullStyle, true)).toBeUndefined(); + }); + }); +}); diff --git a/src/internal/utils/input-styles.ts b/src/internal/utils/input-styles.ts new file mode 100644 index 0000000000..cc695befc2 --- /dev/null +++ b/src/internal/utils/input-styles.ts @@ -0,0 +1,104 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { SYSTEM } from '../environment'; +import customCssProps from '../generated/custom-css-properties'; + +/** + * Shared style interface for input-like components (input, textarea, text-filter). + * Components can extend or alias this interface in their own interfaces.ts files. + */ +export interface InputStyleProps { + root?: { + backgroundColor?: { + default?: string; + disabled?: string; + focus?: string; + hover?: string; + readonly?: string; + }; + borderColor?: { + default?: string; + disabled?: string; + focus?: string; + hover?: string; + readonly?: string; + }; + borderRadius?: string; + borderWidth?: string; + boxShadow?: { + default?: string; + disabled?: string; + focus?: string; + hover?: string; + readonly?: string; + }; + color?: { + default?: string; + disabled?: string; + focus?: string; + hover?: string; + readonly?: string; + }; + fontSize?: string; + fontWeight?: string; + paddingBlock?: string; + paddingInline?: string; + }; + placeholder?: { + color?: string; + fontSize?: string; + fontStyle?: string; + fontWeight?: string; + }; +} + +/** + * Maps input-like component style props to CSS custom properties. + * Used by input, textarea, and text-filter components. + * + * @param style - The style props to map + * @param requireRoot - If true, returns undefined when style.root is not provided (textarea behavior). + * If false, returns object with undefined values (input/text-filter behavior). + */ +export function getInputStylesCss(style: InputStyleProps | undefined, requireRoot = false) { + if (SYSTEM !== 'core') { + return undefined; + } + + if (requireRoot && !style?.root) { + return undefined; + } + + return { + borderRadius: style?.root?.borderRadius, + borderWidth: style?.root?.borderWidth, + fontSize: style?.root?.fontSize, + fontWeight: style?.root?.fontWeight, + paddingBlock: style?.root?.paddingBlock, + paddingInline: style?.root?.paddingInline, + [customCssProps.styleBackgroundDefault]: style?.root?.backgroundColor?.default, + [customCssProps.styleBackgroundDisabled]: style?.root?.backgroundColor?.disabled, + [customCssProps.styleBackgroundHover]: style?.root?.backgroundColor?.hover, + [customCssProps.styleBackgroundFocus]: style?.root?.backgroundColor?.focus, + [customCssProps.styleBackgroundReadonly]: style?.root?.backgroundColor?.readonly, + [customCssProps.styleBorderColorDefault]: style?.root?.borderColor?.default, + [customCssProps.styleBorderColorDisabled]: style?.root?.borderColor?.disabled, + [customCssProps.styleBorderColorHover]: style?.root?.borderColor?.hover, + [customCssProps.styleBorderColorFocus]: style?.root?.borderColor?.focus, + [customCssProps.styleBorderColorReadonly]: style?.root?.borderColor?.readonly, + [customCssProps.styleBoxShadowDefault]: style?.root?.boxShadow?.default, + [customCssProps.styleBoxShadowDisabled]: style?.root?.boxShadow?.disabled, + [customCssProps.styleBoxShadowHover]: style?.root?.boxShadow?.hover, + [customCssProps.styleBoxShadowFocus]: style?.root?.boxShadow?.focus, + [customCssProps.styleBoxShadowReadonly]: style?.root?.boxShadow?.readonly, + [customCssProps.styleColorDefault]: style?.root?.color?.default, + [customCssProps.styleColorDisabled]: style?.root?.color?.disabled, + [customCssProps.styleColorHover]: style?.root?.color?.hover, + [customCssProps.styleColorFocus]: style?.root?.color?.focus, + [customCssProps.styleColorReadonly]: style?.root?.color?.readonly, + [customCssProps.stylePlaceholderColor]: style?.placeholder?.color, + [customCssProps.stylePlaceholderFontSize]: style?.placeholder?.fontSize, + [customCssProps.stylePlaceholderFontWeight]: style?.placeholder?.fontWeight, + [customCssProps.stylePlaceholderFontStyle]: style?.placeholder?.fontStyle, + }; +} diff --git a/src/text-filter/styles.tsx b/src/text-filter/styles.tsx index ff66880d0f..56c92bda6c 100644 --- a/src/text-filter/styles.tsx +++ b/src/text-filter/styles.tsx @@ -1,46 +1,8 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { SYSTEM } from '../internal/environment'; -import customCssProps from '../internal/generated/custom-css-properties'; +import { getInputStylesCss } from '../internal/utils/input-styles'; import { TextFilterProps } from './interfaces'; export function getTextFilterStyles(style: TextFilterProps['style']) { - let properties = {}; - - if (SYSTEM === 'core') { - properties = { - borderRadius: style?.root?.borderRadius, - borderWidth: style?.root?.borderWidth, - fontSize: style?.root?.fontSize, - fontWeight: style?.root?.fontWeight, - paddingBlock: style?.root?.paddingBlock, - paddingInline: style?.root?.paddingInline, - [customCssProps.styleBackgroundDefault]: style?.root?.backgroundColor?.default, - [customCssProps.styleBackgroundDisabled]: style?.root?.backgroundColor?.disabled, - [customCssProps.styleBackgroundHover]: style?.root?.backgroundColor?.hover, - [customCssProps.styleBackgroundFocus]: style?.root?.backgroundColor?.focus, - [customCssProps.styleBackgroundReadonly]: style?.root?.backgroundColor?.readonly, - [customCssProps.styleBorderColorDefault]: style?.root?.borderColor?.default, - [customCssProps.styleBorderColorDisabled]: style?.root?.borderColor?.disabled, - [customCssProps.styleBorderColorHover]: style?.root?.borderColor?.hover, - [customCssProps.styleBorderColorFocus]: style?.root?.borderColor?.focus, - [customCssProps.styleBorderColorReadonly]: style?.root?.borderColor?.readonly, - [customCssProps.styleBoxShadowDefault]: style?.root?.boxShadow?.default, - [customCssProps.styleBoxShadowDisabled]: style?.root?.boxShadow?.disabled, - [customCssProps.styleBoxShadowHover]: style?.root?.boxShadow?.hover, - [customCssProps.styleBoxShadowFocus]: style?.root?.boxShadow?.focus, - [customCssProps.styleBoxShadowReadonly]: style?.root?.boxShadow?.readonly, - [customCssProps.styleColorDefault]: style?.root?.color?.default, - [customCssProps.styleColorDisabled]: style?.root?.color?.disabled, - [customCssProps.styleColorHover]: style?.root?.color?.hover, - [customCssProps.styleColorFocus]: style?.root?.color?.focus, - [customCssProps.styleColorReadonly]: style?.root?.color?.readonly, - [customCssProps.stylePlaceholderColor]: style?.placeholder?.color, - [customCssProps.stylePlaceholderFontSize]: style?.placeholder?.fontSize, - [customCssProps.stylePlaceholderFontWeight]: style?.placeholder?.fontWeight, - [customCssProps.stylePlaceholderFontStyle]: style?.placeholder?.fontStyle, - }; - - return properties; - } + return getInputStylesCss(style); } diff --git a/src/textarea/styles.tsx b/src/textarea/styles.tsx index c31e80006c..24cf3f73f1 100644 --- a/src/textarea/styles.tsx +++ b/src/textarea/styles.tsx @@ -1,56 +1,8 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { SYSTEM } from '../internal/environment'; -import customCssProps from '../internal/generated/custom-css-properties'; +import { getInputStylesCss } from '../internal/utils/input-styles'; import { TextareaProps } from './interfaces'; export function getTextareaStyles(style: TextareaProps['style']) { - let properties = {}; - - if (style?.root && SYSTEM === 'core') { - properties = { - borderRadius: style?.root?.borderRadius, - borderWidth: style?.root?.borderWidth, - fontSize: style?.root?.fontSize, - fontWeight: style?.root?.fontWeight, - paddingBlock: style?.root?.paddingBlock, - paddingInline: style?.root?.paddingInline, - ...(style?.root?.backgroundColor && { - [customCssProps.styleBackgroundDefault]: style.root.backgroundColor?.default, - [customCssProps.styleBackgroundDisabled]: style.root.backgroundColor?.disabled, - [customCssProps.styleBackgroundHover]: style.root.backgroundColor?.hover, - [customCssProps.styleBackgroundFocus]: style.root.backgroundColor?.focus, - [customCssProps.styleBackgroundReadonly]: style.root.backgroundColor?.readonly, - }), - ...(style?.root?.borderColor && { - [customCssProps.styleBorderColorDefault]: style.root.borderColor?.default, - [customCssProps.styleBorderColorDisabled]: style.root.borderColor?.disabled, - [customCssProps.styleBorderColorHover]: style.root.borderColor?.hover, - [customCssProps.styleBorderColorFocus]: style.root.borderColor?.focus, - [customCssProps.styleBorderColorReadonly]: style.root.borderColor?.readonly, - }), - ...(style?.root?.boxShadow && { - [customCssProps.styleBoxShadowDefault]: style.root.boxShadow?.default, - [customCssProps.styleBoxShadowDisabled]: style.root.boxShadow?.disabled, - [customCssProps.styleBoxShadowHover]: style.root.boxShadow?.hover, - [customCssProps.styleBoxShadowFocus]: style.root.boxShadow?.focus, - [customCssProps.styleBoxShadowReadonly]: style.root.boxShadow?.readonly, - }), - ...(style?.root?.color && { - [customCssProps.styleColorDefault]: style.root.color?.default, - [customCssProps.styleColorDisabled]: style.root.color?.disabled, - [customCssProps.styleColorHover]: style.root.color?.hover, - [customCssProps.styleColorFocus]: style.root.color?.focus, - [customCssProps.styleColorReadonly]: style.root.color?.readonly, - }), - ...(style?.placeholder && { - [customCssProps.stylePlaceholderColor]: style.placeholder?.color, - [customCssProps.stylePlaceholderFontSize]: style.placeholder?.fontSize, - [customCssProps.stylePlaceholderFontWeight]: style.placeholder?.fontWeight, - [customCssProps.stylePlaceholderFontStyle]: style.placeholder?.fontStyle, - }), - }; - - return properties; - } + return getInputStylesCss(style, true); }