Skip to content

Commit

Permalink
feat(scene composer): color picker bug fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
divya-sea committed Aug 17, 2023
1 parent c5e071d commit f44b838
Show file tree
Hide file tree
Showing 9 changed files with 138 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ export const AnchorComponentEditor: React.FC<IAnchorComponentEditorProps> = ({

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 (
<SpaceBetween size='s'>
<FormField label={intl.formatMessage({ defaultMessage: 'Default Icon', description: 'Form field label' })}>
Expand All @@ -189,7 +189,7 @@ export const AnchorComponentEditor: React.FC<IAnchorComponentEditorProps> = ({
placeholder={intl.formatMessage({ defaultMessage: 'Choose an icon', description: 'placeholder' })}
/>
{hasIcon &&
(isAllValid ? (
(isCustomStyle ? (
<DecodeSvgString
selectedColor={anchorComponent.chosenColor ?? colors.customBlue}
iconString={iconString!}
Expand All @@ -201,12 +201,19 @@ export const AnchorComponentEditor: React.FC<IAnchorComponentEditorProps> = ({
))}
</Grid>
</FormField>
{isAllValid ? (
<FormField stretch>
{isCustomStyle ? (
<FormField>
<ColorPicker
color={anchorComponent.chosenColor ?? colors.customBlue}
onSelectColor={(pickedColor) => 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' })}
/>
</FormField>
) : null}
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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<boolean>(false);
const [newColor, setNewColor] = useState<string>(color);
const [showChromePicker, setShowChromePicker] = useState<boolean>(false);
const [hexCodeError, setHexCodeError] = useState<string>(''); // State variable for hex code error

const [customInternalColors, setCustomInternalColors] = useState<string[]>(customColors ?? []);
const intl = useIntl();

/**
Expand All @@ -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<InputProps.ChangeDetail>) => {
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);
Expand All @@ -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<InputProps.ChangeDetail>) => {
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 (
<SpaceBetween size='m'>
<SpaceBetween size='l'>
<FormField errorText={hexCodeError}>
<SpaceBetween size='m' direction='horizontal'>
<TextContent>
<h5>{label}</h5>
<h5>{colorPickerLabel}</h5>
</TextContent>
<Button
data-testid='color-preview'
ariaLabel={intl.formatMessage({ defaultMessage: 'colorPreview', description: 'color picker preview' })}
variant='inline-icon'
iconSvg={<Icon size='big' svg={colorPickerPreviewSvg(color)} />}
iconSvg={<Icon size='big' svg={colorPickerPreviewSvg(newColor)} />}
onClick={() => {
handleClick();
setHexCodeError('');
Expand All @@ -96,7 +153,7 @@ export const ColorPicker = ({ color, onSelectColor, label }: IColorPickerProps):
value={newColor}
onChange={handleHexCodeChange}
/>
<div style={tmColorPickerContainer}>
<div id='circle-picker' style={tmColorPickerContainer}>
{showPicker && !showChromePicker && (
<div style={tmColorPickerPopover}>
<div>
Expand All @@ -110,10 +167,21 @@ export const ColorPicker = ({ color, onSelectColor, label }: IColorPickerProps):
onChange={handleColorChange}
/>
</div>
<div style={tmDivider} />
<button style={tmAddButton} onClick={handleShowChromePicker}>
<Icon name='add-plus' />
</button>
<SpaceBetween size='s'>
<div style={tmDivider} />
<TextContent>
<h5>{customColorLabel}</h5>
</TextContent>
<CirclePicker
width='300px'
colors={[...new Set(customInternalColors)]}
color={newColor}
onChange={handleColorChange}
/>
<button style={tmAddButton} onClick={handleShowChromePicker}>
<Icon name='add-plus' />
</button>
</SpaceBetween>
</div>
)}
</div>
Expand All @@ -124,9 +192,9 @@ export const ColorPicker = ({ color, onSelectColor, label }: IColorPickerProps):
<div
aria-label={intl.formatMessage({ defaultMessage: 'chromePicker', description: 'chrome picker' })}
style={tmCover}
onClick={handleCloseChromePicker}
onClick={handleCloseCustomPicker}
/>
<ChromePicker color={color} onChangeComplete={(newColor) => onSelectColor(newColor.hex)} />
<SketchPicker disableAlpha={true} color={newColor} onChangeComplete={handleCustomPickerSelection} />
</div>
)}
</SpaceBetween>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -60,7 +60,12 @@ export const SceneRuleTargetEditor: React.FC<ISceneRuleTargetEditorProps> = ({
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 (
<Grid gridDefinition={[{ colspan: 4 }, { colspan: 8 }]}>
<Select
Expand Down Expand Up @@ -92,7 +97,7 @@ export const SceneRuleTargetEditor: React.FC<ISceneRuleTargetEditorProps> = ({
}}
chosenColor={chosenColor}
/>
{isAllValid && (
{isCustomStyle && (
<ColorPicker
color={chosenColor}
onSelectColor={(newColor) => {
Expand All @@ -101,7 +106,8 @@ export const SceneRuleTargetEditor: React.FC<ISceneRuleTargetEditorProps> = ({
onChange(convertToIotTwinMakerNamespace(targetInfo.type, colorWithIcon));
setChosenColor(newColor);
}}
label={formatMessage({ defaultMessage: 'Colors', description: 'Colors' })}
colorPickerLabel={formatMessage({ defaultMessage: 'Colors', description: 'Colors' })}
customColorLabel={formatMessage({ defaultMessage: 'Custom colors', description: 'Custom colors' })}
/>
)}
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const SceneRuleTargetIconEditor: React.FC<ISceneRuleTargetIconEditorProps
return btoa(SCENE_ICONS[selectedIcon]);
}, [selectedIcon]);

const isAllValid = tagStyle && targetValue === 'Custom';
const isCustomStyle = tagStyle && targetValue === 'Custom';
return (
<Grid gridDefinition={[{ colspan: 9 }, { colspan: 2 }]}>
<Select
Expand All @@ -60,7 +60,7 @@ export const SceneRuleTargetIconEditor: React.FC<ISceneRuleTargetIconEditorProps
'Specifies the localized string that describes an option as being selected. This is required to provide a good screen reader experience',
})}
/>
{isAllValid ? (
{isCustomStyle ? (
<DecodeSvgString
selectedColor={chosenColor ?? colors.customBlue}
iconString={iconString}
Expand Down
2 changes: 2 additions & 0 deletions packages/scene-composer/src/interfaces/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export interface IAnchorComponent extends ISceneComponent {
navLink?: INavLink;
offset?: Vector3;
chosenColor?: string;
customColors?: string[];
}
export interface IAnimationComponent extends ISceneComponent {
selector?: number;
Expand Down Expand Up @@ -77,6 +78,7 @@ export interface ITagData {
chosenColor?: string;
navLink?: INavLink;
dataBindingContext?: unknown;
customColors?: string[];
}

/**
Expand Down
1 change: 1 addition & 0 deletions packages/scene-composer/src/utils/eventDataUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const getBindingsFromTag = (
): ITagData => {
return {
chosenColor: component.chosenColor,
customColors: component.customColors,
navLink: component.navLink,
dataBindingContext: !component.valueDataBinding?.dataBindingContext
? undefined
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,10 @@
"note": "Placeholder",
"text": "Choose a shape"
},
"BvOcQf": {
"note": "Custom colors",
"text": "Custom colors"
},
"Bzw++H": {
"note": "label for the remove animation button",
"text": "remove"
Expand Down

0 comments on commit f44b838

Please sign in to comment.