From b6901f9ecd5ff12d467d2a47567f99eb01f6c9f5 Mon Sep 17 00:00:00 2001 From: Maximilian Schoell Date: Wed, 19 Nov 2025 17:09:44 +0100 Subject: [PATCH 1/3] feat: implement style api for autosuggest component --- pages/autosuggest/style-permutations.page.tsx | 125 ++++++++++ .../__snapshots__/documenter.test.ts.snap | 230 ++++++++++++++++++ src/autosuggest/interfaces.ts | 50 ++++ src/autosuggest/internal.tsx | 2 + .../components/autosuggest-input/index.tsx | 4 + 5 files changed, 411 insertions(+) create mode 100644 pages/autosuggest/style-permutations.page.tsx diff --git a/pages/autosuggest/style-permutations.page.tsx b/pages/autosuggest/style-permutations.page.tsx new file mode 100644 index 0000000000..cb4da07307 --- /dev/null +++ b/pages/autosuggest/style-permutations.page.tsx @@ -0,0 +1,125 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import React from 'react'; + +import Autosuggest, { AutosuggestProps } from '~components/autosuggest'; + +import createPermutations from '../utils/permutations'; +import PermutationsView from '../utils/permutations-view'; +import ScreenshotArea from '../utils/screenshot-area'; + +const style1 = { + root: { + borderColor: { + default: 'light-dark(#f59e0b, #fbbf24)', + hover: 'light-dark(#b45309, #fcd34d)', + focus: 'light-dark(#d97706, #fde047)', + disabled: 'light-dark(#fcd34d, #fef08a)', + readonly: 'light-dark(#fcd34d, #fef08a)', + }, + borderWidth: '2px', + borderRadius: '16px', + backgroundColor: { + default: 'light-dark(#fef3c7, #000000)', + hover: 'light-dark(#fefce8, #0a0a0a)', + focus: 'light-dark(#fef9c3, #0f0f0f)', + disabled: 'light-dark(#fcd34d, #1a1a1a)', + readonly: 'light-dark(#fef3c7, #0a0a0a)', + }, + boxShadow: { + default: '0 2px 8px rgba(245, 158, 11, 0.15)', + hover: '0 6px 16px rgba(245, 158, 11, 0.25)', + focus: '0 0 0 4px rgba(245, 158, 11, 0.25), 0 6px 16px rgba(245, 158, 11, 0.3)', + disabled: 'none', + readonly: '0 2px 8px rgba(245, 158, 11, 0.15)', + }, + color: { + default: 'light-dark(#78350f, #fef3c7)', + hover: 'light-dark(#78350f, #fef3c7)', + focus: 'light-dark(#78350f, #fef3c7)', + disabled: 'light-dark(#78350f, #fef3c7)', + readonly: 'light-dark(#92400e, #fde68a)', + }, + }, +}; + +const style2 = { + root: { + borderColor: { + default: 'light-dark(#10b981, #34d399)', + hover: 'light-dark(#047857, #6ee7b7)', + focus: 'light-dark(#059669, #4ade80)', + disabled: 'light-dark(#6ee7b7, #a7f3d0)', + readonly: 'light-dark(#6ee7b7, #a7f3d0)', + }, + borderWidth: '3px', + borderRadius: '0px', + backgroundColor: { + default: 'light-dark(#ecfdf5, #000000)', + hover: 'light-dark(#f0fdf4, #0a0a0a)', + focus: 'light-dark(#ecfdf5, #0f0f0f)', + disabled: 'light-dark(#a7f3d0, #1a1a1a)', + readonly: 'light-dark(#ecfdf5, #0a0a0a)', + }, + boxShadow: { + default: '0 1px 2px rgba(0, 0, 0, 0.05)', + hover: '0 2px 4px rgba(16, 185, 129, 0.1)', + focus: '0 0 0 4px rgba(16, 185, 129, 0.2), 0 2px 4px rgba(16, 185, 129, 0.15)', + disabled: 'none', + readonly: '0 1px 2px rgba(0, 0, 0, 0.05)', + }, + color: { + default: 'light-dark(#064e3b, #d1fae5)', + hover: 'light-dark(#064e3b, #d1fae5)', + focus: 'light-dark(#064e3b, #d1fae5)', + disabled: 'light-dark(#064e3b, #d1fae5)', + readonly: 'light-dark(#065f46, #a7f3d0)', + }, + }, +}; + +const enteredTextLabel = (value: string) => `Use: ${value}`; + +const permutations = createPermutations([ + { + value: ['This is a test value'], + placeholder: [''], + disabled: [false, true], + readOnly: [false, true], + invalid: [false, true], + options: [ + [ + { value: 'option1', label: 'Option 1' }, + { value: 'option2', label: 'Option 2' }, + ], + undefined, + ], + enteredTextLabel: [enteredTextLabel], + style: [style1, style2], + }, +]); + +export default function AutosuggestStylePermutations() { + return ( + <> +

Autosuggest Style permutations

+ + ( +
+ { + /*empty handler to suppress react controlled property warning*/ + }} + {...permutation} + /> +
+ )} + /> +
+ + ); +} diff --git a/src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap b/src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap index 3e377e1fcd..2fb16d8b96 100644 --- a/src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap +++ b/src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap @@ -3555,6 +3555,236 @@ This is required to provide a good screen reader experience. For more informatio "optional": true, "type": "string", }, + { + "inlineType": { + "name": "AutosuggestProps.Style", + "properties": [ + { + "inlineType": { + "name": "object", + "properties": [ + { + "name": "color", + "optional": true, + "type": "string", + }, + { + "name": "fontSize", + "optional": true, + "type": "string", + }, + { + "name": "fontStyle", + "optional": true, + "type": "string", + }, + { + "name": "fontWeight", + "optional": true, + "type": "string", + }, + ], + "type": "object", + }, + "name": "placeholder", + "optional": true, + "type": "{ color?: string | undefined; fontSize?: string | undefined; fontStyle?: string | undefined; fontWeight?: string | undefined; }", + }, + { + "inlineType": { + "name": "object", + "properties": [ + { + "inlineType": { + "name": "object", + "properties": [ + { + "name": "default", + "optional": true, + "type": "string", + }, + { + "name": "disabled", + "optional": true, + "type": "string", + }, + { + "name": "focus", + "optional": true, + "type": "string", + }, + { + "name": "hover", + "optional": true, + "type": "string", + }, + { + "name": "readonly", + "optional": true, + "type": "string", + }, + ], + "type": "object", + }, + "name": "backgroundColor", + "optional": true, + "type": "{ default?: string | undefined; disabled?: string | undefined; focus?: string | undefined; hover?: string | undefined; readonly?: string | undefined; }", + }, + { + "inlineType": { + "name": "object", + "properties": [ + { + "name": "default", + "optional": true, + "type": "string", + }, + { + "name": "disabled", + "optional": true, + "type": "string", + }, + { + "name": "focus", + "optional": true, + "type": "string", + }, + { + "name": "hover", + "optional": true, + "type": "string", + }, + { + "name": "readonly", + "optional": true, + "type": "string", + }, + ], + "type": "object", + }, + "name": "borderColor", + "optional": true, + "type": "{ default?: string | undefined; disabled?: string | undefined; focus?: string | undefined; hover?: string | undefined; readonly?: string | undefined; }", + }, + { + "name": "borderRadius", + "optional": true, + "type": "string", + }, + { + "name": "borderWidth", + "optional": true, + "type": "string", + }, + { + "inlineType": { + "name": "object", + "properties": [ + { + "name": "default", + "optional": true, + "type": "string", + }, + { + "name": "disabled", + "optional": true, + "type": "string", + }, + { + "name": "focus", + "optional": true, + "type": "string", + }, + { + "name": "hover", + "optional": true, + "type": "string", + }, + { + "name": "readonly", + "optional": true, + "type": "string", + }, + ], + "type": "object", + }, + "name": "boxShadow", + "optional": true, + "type": "{ default?: string | undefined; disabled?: string | undefined; focus?: string | undefined; hover?: string | undefined; readonly?: string | undefined; }", + }, + { + "inlineType": { + "name": "object", + "properties": [ + { + "name": "default", + "optional": true, + "type": "string", + }, + { + "name": "disabled", + "optional": true, + "type": "string", + }, + { + "name": "focus", + "optional": true, + "type": "string", + }, + { + "name": "hover", + "optional": true, + "type": "string", + }, + { + "name": "readonly", + "optional": true, + "type": "string", + }, + ], + "type": "object", + }, + "name": "color", + "optional": true, + "type": "{ default?: string | undefined; disabled?: string | undefined; focus?: string | undefined; hover?: string | undefined; readonly?: string | undefined; }", + }, + { + "name": "fontSize", + "optional": true, + "type": "string", + }, + { + "name": "fontWeight", + "optional": true, + "type": "string", + }, + { + "name": "paddingBlock", + "optional": true, + "type": "string", + }, + { + "name": "paddingInline", + "optional": true, + "type": "string", + }, + ], + "type": "object", + }, + "name": "root", + "optional": true, + "type": "{ backgroundColor?: { default?: string | undefined; disabled?: string | undefined; focus?: string | undefined; hover?: string | undefined; readonly?: string | undefined; } | undefined; ... 8 more ...; paddingInline?: string | undefined; }", + }, + ], + "type": "object", + }, + "name": "style", + "optional": true, + "systemTags": [ + "core", + ], + "type": "AutosuggestProps.Style", + }, { "description": "Specifies the text entered into the form element.", "name": "value", diff --git a/src/autosuggest/interfaces.ts b/src/autosuggest/interfaces.ts index 4237abb348..892b1ed9fb 100644 --- a/src/autosuggest/interfaces.ts +++ b/src/autosuggest/interfaces.ts @@ -125,6 +125,11 @@ export interface AutosuggestProps * [accessibility guidelines](/components/autosuggest/?tabId=usage#accessibility-guidelines). */ renderHighlightedAriaLive?: AutosuggestProps.ContainingOptionAndGroupString; + + /** + * @awsuiSystem core + */ + style?: AutosuggestProps.Style; } export namespace AutosuggestProps { @@ -160,6 +165,51 @@ export namespace AutosuggestProps { */ select(): void; } + + export interface Style { + 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; + }; + } } // TODO: use DropdownOption type same as in select and multiselect diff --git a/src/autosuggest/internal.tsx b/src/autosuggest/internal.tsx index 60e4f045cf..60bbf09475 100644 --- a/src/autosuggest/internal.tsx +++ b/src/autosuggest/internal.tsx @@ -59,6 +59,7 @@ const InternalAutosuggest = React.forwardRef((props: InternalAutosuggestProps, r expandToViewport, onSelect, renderHighlightedAriaLive, + style, __internalRootRef, ...restProps } = props; @@ -223,6 +224,7 @@ const InternalAutosuggest = React.forwardRef((props: InternalAutosuggestProps, r ariaControls={listId} ariaActivedescendant={highlightedOptionId} dropdownExpanded={shouldRenderDropdownContent} + style={style} dropdownContent={ shouldRenderDropdownContent && ( void; onPressArrowUp?: () => void; onPressEnter?: () => boolean; + style?: InputProps['style']; } interface AutosuggestInputFocusOptions { @@ -94,6 +96,7 @@ const AutosuggestInput = React.forwardRef( onPressArrowDown, onPressArrowUp, onPressEnter, + style, __internalRootRef, ...restProps }: AutosuggestInputProps, @@ -306,6 +309,7 @@ const AutosuggestInput = React.forwardRef( autoComplete={false} nativeInputAttributes={processAttributes(nativeAttributes, nativeInputAttributes, 'Autosuggest')} __skipNativeAttributesWarnings={Object.keys(nativeAttributes)} + style={style} {...formFieldContext} /> } From 27e291547affcb7f8bc8d70866054e26b6e59716 Mon Sep 17 00:00:00 2001 From: Maximilian Schoell Date: Thu, 20 Nov 2025 13:39:28 +0100 Subject: [PATCH 2/3] chore: reduce permutation number --- pages/autosuggest/style-permutations.page.tsx | 57 +++++++------------ src/input/styles.scss | 4 +- 2 files changed, 22 insertions(+), 39 deletions(-) diff --git a/pages/autosuggest/style-permutations.page.tsx b/pages/autosuggest/style-permutations.page.tsx index cb4da07307..2aa9e234c2 100644 --- a/pages/autosuggest/style-permutations.page.tsx +++ b/pages/autosuggest/style-permutations.page.tsx @@ -41,40 +41,8 @@ const style1 = { readonly: 'light-dark(#92400e, #fde68a)', }, }, -}; - -const style2 = { - root: { - borderColor: { - default: 'light-dark(#10b981, #34d399)', - hover: 'light-dark(#047857, #6ee7b7)', - focus: 'light-dark(#059669, #4ade80)', - disabled: 'light-dark(#6ee7b7, #a7f3d0)', - readonly: 'light-dark(#6ee7b7, #a7f3d0)', - }, - borderWidth: '3px', - borderRadius: '0px', - backgroundColor: { - default: 'light-dark(#ecfdf5, #000000)', - hover: 'light-dark(#f0fdf4, #0a0a0a)', - focus: 'light-dark(#ecfdf5, #0f0f0f)', - disabled: 'light-dark(#a7f3d0, #1a1a1a)', - readonly: 'light-dark(#ecfdf5, #0a0a0a)', - }, - boxShadow: { - default: '0 1px 2px rgba(0, 0, 0, 0.05)', - hover: '0 2px 4px rgba(16, 185, 129, 0.1)', - focus: '0 0 0 4px rgba(16, 185, 129, 0.2), 0 2px 4px rgba(16, 185, 129, 0.15)', - disabled: 'none', - readonly: '0 1px 2px rgba(0, 0, 0, 0.05)', - }, - color: { - default: 'light-dark(#064e3b, #d1fae5)', - hover: 'light-dark(#064e3b, #d1fae5)', - focus: 'light-dark(#064e3b, #d1fae5)', - disabled: 'light-dark(#064e3b, #d1fae5)', - readonly: 'light-dark(#065f46, #a7f3d0)', - }, + placeholder: { + color: 'light-dark(black, white)', }, }; @@ -83,19 +51,32 @@ const enteredTextLabel = (value: string) => `Use: ${value}`; const permutations = createPermutations([ { value: ['This is a test value'], - placeholder: [''], disabled: [false, true], - readOnly: [false, true], invalid: [false, true], + warning: [false, true], + style: [style1], + options: [ + [ + { value: 'option1', label: 'Option 1' }, + { value: 'option2', label: 'Option 2' }, + ], + ], + enteredTextLabel: [enteredTextLabel], + }, + { + value: [''], + placeholder: ['Placeholder'], + disabled: [false, true], + invalid: [false, true], + warning: [false, true], + style: [style1], options: [ [ { value: 'option1', label: 'Option 1' }, { value: 'option2', label: 'Option 2' }, ], - undefined, ], enteredTextLabel: [enteredTextLabel], - style: [style1, style2], }, ]); diff --git a/src/input/styles.scss b/src/input/styles.scss index 1de81b4733..ee9911e8fe 100644 --- a/src/input/styles.scss +++ b/src/input/styles.scss @@ -116,7 +116,9 @@ ); box-shadow: var(#{custom-props.$styleBoxShadowDisabled}); @include placeholder { - @include styles.form-placeholder-disabled; + @include styles.form-placeholder-disabled( + $color: var(#{custom-props.$stylePlaceholderColor}, awsui.$color-text-input-placeholder-disabled) + ); } } From b46554e0750728cbd574cc97ecb570c541a98ad4 Mon Sep 17 00:00:00 2001 From: Maximilian Schoell Date: Thu, 20 Nov 2025 13:50:35 +0100 Subject: [PATCH 3/3] refactor: fallback to custom focus box shadow --- src/input/styles.scss | 6 ++++-- src/internal/styles/forms/mixins.scss | 16 ++++++++++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/input/styles.scss b/src/input/styles.scss index ee9911e8fe..549aee077e 100644 --- a/src/input/styles.scss +++ b/src/input/styles.scss @@ -130,7 +130,8 @@ &.input-invalid { @include styles.form-invalid-control( $color: var(#{custom-props.$styleColorDefault}, awsui.$color-text-status-error), - $border-color: var(#{custom-props.$styleBorderColorDefault}, awsui.$color-text-status-error) + $border-color: var(#{custom-props.$styleBorderColorDefault}, awsui.$color-text-status-error), + $focus-box-shadow: var(#{custom-props.$styleBoxShadowFocus}, foundation.$box-shadow-focused-light-invalid) ); &.input-has-icon-left { padding-inline-start: calc( @@ -143,7 +144,8 @@ &.input-warning { @include styles.form-warning-control( $color: var(#{custom-props.$styleColorDefault}, awsui.$color-text-status-warning), - $border-color: var(#{custom-props.$styleBorderColorDefault}, awsui.$color-text-status-warning) + $border-color: var(#{custom-props.$styleBorderColorDefault}, awsui.$color-text-status-warning), + $focus-box-shadow: var(#{custom-props.$styleBoxShadowFocus}, foundation.$box-shadow-focused-light-invalid) ); &.input-has-icon-left { padding-inline-start: calc( diff --git a/src/internal/styles/forms/mixins.scss b/src/internal/styles/forms/mixins.scss index a7631951a3..e2fcd389da 100644 --- a/src/internal/styles/forms/mixins.scss +++ b/src/internal/styles/forms/mixins.scss @@ -193,24 +193,32 @@ @include typography.font-body-s; } -@mixin form-invalid-control($color: awsui.$color-text-status-error, $border-color: awsui.$color-text-status-error) { +@mixin form-invalid-control( + $color: awsui.$color-text-status-error, + $border-color: awsui.$color-text-status-error, + $focus-box-shadow: foundation.$box-shadow-focused-light-invalid +) { color: $color; border-color: $border-color; padding-inline-start: constants.$invalid-control-left-padding; border-inline-start-width: constants.$invalid-control-left-border; &:focus { - box-shadow: foundation.$box-shadow-focused-light-invalid; + box-shadow: $focus-box-shadow; } @content; } -@mixin form-warning-control($color: awsui.$color-text-status-warning, $border-color: awsui.$color-text-status-warning) { +@mixin form-warning-control( + $color: awsui.$color-text-status-warning, + $border-color: awsui.$color-text-status-warning, + $focus-box-shadow: foundation.$box-shadow-focused-light-invalid +) { color: $color; border-color: $border-color; padding-inline-start: constants.$invalid-control-left-padding; border-inline-start-width: constants.$invalid-control-left-border; &:focus { - box-shadow: foundation.$box-shadow-focused-light-invalid; + box-shadow: $focus-box-shadow; } @content; }