diff --git a/packages/scene-composer/src/augmentations/components/three-fiber/anchor/AnchorWidget.tsx b/packages/scene-composer/src/augmentations/components/three-fiber/anchor/AnchorWidget.tsx index 145017dd0..9ba4b9ccc 100644 --- a/packages/scene-composer/src/augmentations/components/three-fiber/anchor/AnchorWidget.tsx +++ b/packages/scene-composer/src/augmentations/components/three-fiber/anchor/AnchorWidget.tsx @@ -41,6 +41,7 @@ export interface AnchorWidgetProps { valueDataBinding?: IValueDataBinding; rule?: IRuleBasedMap; navLink?: INavLink; + customColors?: string[]; } type overrideCustomColorType = (rulevalue: string | undefined) => void; diff --git a/packages/scene-composer/src/augmentations/components/three-fiber/anchor/__tests__/AnchorWidget.spec.tsx b/packages/scene-composer/src/augmentations/components/three-fiber/anchor/__tests__/AnchorWidget.spec.tsx index c3d5c56bd..396c7881d 100644 --- a/packages/scene-composer/src/augmentations/components/three-fiber/anchor/__tests__/AnchorWidget.spec.tsx +++ b/packages/scene-composer/src/augmentations/components/three-fiber/anchor/__tests__/AnchorWidget.spec.tsx @@ -2,6 +2,7 @@ import renderer, { act } from 'react-test-renderer'; import React from 'react'; import * as THREE from 'three'; import { useLoader } from '@react-three/fiber'; +import wrapper from '@awsui/components-react/test-utils/dom'; import { AnchorWidget } from '../AnchorWidget'; import { DefaultAnchorStatus, DEFAULT_TAG_GLOBAL_SETTINGS, KnownComponentType } from '../../../../..'; diff --git a/packages/scene-composer/src/components/panels/scene-components/AnchorComponentEditor.tsx b/packages/scene-composer/src/components/panels/scene-components/AnchorComponentEditor.tsx index c6dbb76a2..2bc01ffea 100644 --- a/packages/scene-composer/src/components/panels/scene-components/AnchorComponentEditor.tsx +++ b/packages/scene-composer/src/components/panels/scene-components/AnchorComponentEditor.tsx @@ -164,7 +164,8 @@ export const AnchorComponentEditor: React.FC = ({ const hasIcon = iconSelectedOptionIndex >= 0; const iconGridDefinition = hasIcon ? [{ colspan: 10 }, { colspan: 2 }] : [{ colspan: 12 }]; - const isAllValid = tagStyle && iconOptions[iconSelectedOptionIndex]?.value === 'Custom'; + const isCustomStyle = tagStyle && iconOptions[iconSelectedOptionIndex]?.value === 'Custom'; + return ( @@ -189,7 +190,7 @@ export const AnchorComponentEditor: React.FC = ({ placeholder={intl.formatMessage({ defaultMessage: 'Choose an icon', description: 'placeholder' })} /> {hasIcon && - (isAllValid ? ( + (isCustomStyle ? ( = ({ ))} - {isAllValid ? ( - + {isCustomStyle ? ( + onUpdateCallback({ chosenColor: pickedColor })} - label={intl.formatMessage({ defaultMessage: 'Colors', description: 'Colors' })} + onSelectColor={(pickedColor) => { + onUpdateCallback({ + chosenColor: pickedColor, + }); + }} + onUpdateCustomColors={(chosenCustomColors) => onUpdateCallback({ customColors: chosenCustomColors })} + customColors={anchorComponent.customColors} + colorPickerLabel={intl.formatMessage({ defaultMessage: 'Colors', description: 'Colors' })} + customColorLabel={intl.formatMessage({ defaultMessage: 'Custom colors', description: 'Custom colors' })} /> ) : null} diff --git a/packages/scene-composer/src/components/panels/scene-components/tag-style/ColorPicker/ColorPicker.tsx b/packages/scene-composer/src/components/panels/scene-components/tag-style/ColorPicker/ColorPicker.tsx index 54ef33350..eb4008dd6 100644 --- a/packages/scene-composer/src/components/panels/scene-components/tag-style/ColorPicker/ColorPicker.tsx +++ b/packages/scene-composer/src/components/panels/scene-components/tag-style/ColorPicker/ColorPicker.tsx @@ -1,7 +1,7 @@ import { Button, FormField, Icon, Input, InputProps, SpaceBetween, TextContent } from '@awsui/components-react'; import { NonCancelableCustomEvent } from '@awsui/components-react/internal/events'; -import React, { useCallback, useState } from 'react'; -import { ChromePicker, CirclePicker } from 'react-color'; +import React, { useCallback, useEffect, useState } from 'react'; +import { CirclePicker, ColorResult, SketchPicker } from 'react-color'; import { useIntl } from 'react-intl'; import { IColorPickerProps } from '../interface'; @@ -17,12 +17,19 @@ import { import { colorPickerPreviewSvg } from './ColorPickerUtils/SvgParserHelper'; import { palleteColors } from './ColorPickerUtils/TagColors'; -export const ColorPicker = ({ color, onSelectColor, label }: IColorPickerProps): JSX.Element => { +export const ColorPicker = ({ + color, + onSelectColor, + customColors, + onUpdateCustomColors, + colorPickerLabel, + customColorLabel, +}: IColorPickerProps): JSX.Element => { const [showPicker, setShowPicker] = useState(false); const [newColor, setNewColor] = useState(color); const [showChromePicker, setShowChromePicker] = useState(false); const [hexCodeError, setHexCodeError] = useState(''); // State variable for hex code error - + const [customInternalColors, setCustomInternalColors] = useState(customColors ?? []); const intl = useIntl(); /** @@ -34,30 +41,40 @@ export const ColorPicker = ({ color, onSelectColor, label }: IColorPickerProps): * @returns */ const isValidHexCode = (hexCode: string) => { - const hexRegex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/; + const hexRegex = /^#([A-Fa-f0-9]{6})$/; return hexRegex.test(hexCode); }; - const handleClick = () => { - setShowPicker(!showPicker); - }; - - const handleColorChange = (color) => { - setNewColor(color.hex); - onSelectColor(color.hex); - }; + const handleOutsideClick = useCallback((event: MouseEvent) => { + const target = event.target as HTMLElement; + const pickerContainer = document.getElementById('circle-picker'); + if (pickerContainer && !pickerContainer.contains(target)) { + setShowPicker(false); + } + }, []); - const handleHexCodeChange = useCallback((event: NonCancelableCustomEvent) => { - setNewColor(event.detail.value); - if (isValidHexCode(event.detail.value)) { - setHexCodeError(''); // Clear any existing error message - onSelectColor(event.detail.value); + useEffect(() => { + if (showPicker) { + document.addEventListener('click', handleOutsideClick); } else { - setHexCodeError( - intl.formatMessage({ defaultMessage: 'Invalid hex code', description: 'hex validations messages' }), - ); // Set the error message + document.removeEventListener('click', handleOutsideClick); } - }, []); + + return () => { + document.removeEventListener('click', handleOutsideClick); + }; + }, [showPicker, handleOutsideClick]); + + const checkIfCustomColor = (color: any) => { + if (!Object.values(palleteColors).includes(color)) { + setCustomInternalColors(customInternalColors.concat(color)); + onUpdateCustomColors?.([...new Set(customInternalColors)]); + } + }; + + const handleClick = () => { + setShowPicker(!showPicker); + }; const handleShowChromePicker = () => { setShowChromePicker(true); @@ -69,22 +86,62 @@ export const ColorPicker = ({ color, onSelectColor, label }: IColorPickerProps): setShowChromePicker(false); }; - const handleCloseChromePicker = () => { + const handleCloseCustomPicker = () => { setShowChromePicker(false); }; + const handleColorChange = useCallback( + (color: ColorResult) => { + checkIfCustomColor(color.hex); + setHexCodeError(''); // Clear any existing error message + setNewColor(color.hex); + onSelectColor(color.hex); + }, + [color, onSelectColor, onUpdateCustomColors], + ); + + const handleHexCodeChange = useCallback( + (event: NonCancelableCustomEvent) => { + setNewColor(event.detail.value); + if (isValidHexCode(event.detail.value)) { + setHexCodeError(''); // Clear any existing error message + onSelectColor(event.detail.value); + checkIfCustomColor(event.detail.value); + } else { + setHexCodeError( + intl.formatMessage({ defaultMessage: 'Invalid hex code', description: 'hex validations messages' }), + ); // Set the error message + } + }, + [color, onSelectColor], + ); + + const handleCustomPickerSelection = (color) => { + checkIfCustomColor(color.hex); + setNewColor(color.hex); + onSelectColor(color.hex); + }; + + useEffect(() => { + setNewColor(color); + }, [color]); + + useEffect(() => { + checkIfCustomColor(color); + }, [color]); + return ( - + -
{label}
+
{colorPickerLabel}
+ +
+ +
{customColorLabel}
+
+ + +
)} @@ -124,9 +192,9 @@ export const ColorPicker = ({ color, onSelectColor, label }: IColorPickerProps):
- onSelectColor(newColor.hex)} /> +
)}
diff --git a/packages/scene-composer/src/components/panels/scene-components/tag-style/ColorPicker/ColorPickerUtils/SvgParserHelper.tsx b/packages/scene-composer/src/components/panels/scene-components/tag-style/ColorPicker/ColorPickerUtils/SvgParserHelper.tsx index 892ff766d..c8e30357f 100644 --- a/packages/scene-composer/src/components/panels/scene-components/tag-style/ColorPicker/ColorPickerUtils/SvgParserHelper.tsx +++ b/packages/scene-composer/src/components/panels/scene-components/tag-style/ColorPicker/ColorPickerUtils/SvgParserHelper.tsx @@ -9,7 +9,7 @@ export const replaceFillAttribute = (element: Element, selectedColor: string): v element.setAttribute('stroke', selectedColor); } if (tagName === 'circle') { - element.setAttribute('fill', selectedColor!); + element.setAttribute('fill', selectedColor); } }; diff --git a/packages/scene-composer/src/components/panels/scene-components/tag-style/interface.tsx b/packages/scene-composer/src/components/panels/scene-components/tag-style/interface.tsx index 2814a7fee..557cf5992 100644 --- a/packages/scene-composer/src/components/panels/scene-components/tag-style/interface.tsx +++ b/packages/scene-composer/src/components/panels/scene-components/tag-style/interface.tsx @@ -7,5 +7,8 @@ export interface IColorPickerProps { color: string; onSelectColor: (color: string) => void; iconSvg?: string; - label?: string; + colorPickerLabel?: string; + customColorLabel?: string; + customColors?: string[]; + onUpdateCustomColors?: (customColors: string[]) => void; } diff --git a/packages/scene-composer/src/components/panels/scene-rule-components/SceneRuleTargetEditor.tsx b/packages/scene-composer/src/components/panels/scene-rule-components/SceneRuleTargetEditor.tsx index 2119c806f..40f29ae1e 100644 --- a/packages/scene-composer/src/components/panels/scene-rule-components/SceneRuleTargetEditor.tsx +++ b/packages/scene-composer/src/components/panels/scene-rule-components/SceneRuleTargetEditor.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { useIntl, defineMessages } from 'react-intl'; import { Grid, Select } from '@awsui/components-react'; @@ -60,7 +60,12 @@ export const SceneRuleTargetEditor: React.FC = ({ label: formatMessage(i18nSceneResourceTypeStrings[SceneResourceType[type]]) || SceneResourceType[type], value: SceneResourceType[type], })); - const isAllValid = tagStyle && targetInfo.value === 'Custom'; + const isCustomStyle = tagStyle && targetInfo.value === 'Custom'; + + useEffect(() => { + setChosenColor(getCustomColor); + }, [getCustomColor]); + return ( - {isAllValid ? ( + {isCustomStyle ? ( { }); render(); - const sceneRuleTargetIconEditor = screen.getByLabelText('Custom style'); + const sceneRuleTargetIconEditor = screen.getByRole('button', { name: /Custom style/i }); expect(sceneRuleTargetIconEditor).toBeTruthy(); const colorPicker = screen.getByTestId('color-preview'); colorPicker.click(); diff --git a/packages/scene-composer/src/components/three-fiber/AnchorComponent/index.tsx b/packages/scene-composer/src/components/three-fiber/AnchorComponent/index.tsx index f0d0d655c..bc04f4a97 100644 --- a/packages/scene-composer/src/components/three-fiber/AnchorComponent/index.tsx +++ b/packages/scene-composer/src/components/three-fiber/AnchorComponent/index.tsx @@ -14,7 +14,6 @@ interface IAnchorComponentProps { const AnchorComponent: React.FC = ({ node, component }: IAnchorComponentProps) => { const sceneComposerId = useSceneComposerId(); const rule = useStore(sceneComposerId)((state) => state.getSceneRuleMapById(component.ruleBasedMapId)); - return ( { return { chosenColor: component.chosenColor, + customColors: component.customColors, navLink: component.navLink, dataBindingContext: !component.valueDataBinding?.dataBindingContext ? undefined diff --git a/packages/scene-composer/translations/IotAppKitSceneComposer.en_US.json b/packages/scene-composer/translations/IotAppKitSceneComposer.en_US.json index 74c822475..319a0549f 100644 --- a/packages/scene-composer/translations/IotAppKitSceneComposer.en_US.json +++ b/packages/scene-composer/translations/IotAppKitSceneComposer.en_US.json @@ -255,6 +255,10 @@ "note": "Placeholder", "text": "Choose a shape" }, + "BvOcQf": { + "note": "Custom colors", + "text": "Custom colors" + }, "CyWR+b": { "note": "placeholder", "text": "Value"