Skip to content

Commit c16dd2f

Browse files
mcasimiraddaleax
andauthored
feat(connect-form): Replace hardcoded hex codes with color codes COMPASS-5475 (#2756)
* feat(connect-form): use color codes * move back to connect-form, fix connection list * add tests * Update packages/connect-form/src/hooks/use-connection-color.ts Co-authored-by: Anna Henningsen <[email protected]> * Update packages/connect-form/src/hooks/use-connection-color.ts Co-authored-by: Anna Henningsen <[email protected]> * fix lint errors * fix tests * add test for disable save button in the connection modal Co-authored-by: Anna Henningsen <[email protected]>
1 parent 146d9e1 commit c16dd2f

File tree

9 files changed

+335
-201
lines changed

9 files changed

+335
-201
lines changed

packages/connect-form/src/components/saved-connection-color-picker.tsx renamed to packages/connect-form/src/components/favorite-color-picker.tsx

Lines changed: 36 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,28 @@ import React from 'react';
22
import {
33
css,
44
cx,
5+
Label,
56
spacing,
67
uiColors,
7-
Label,
88
} from '@mongodb-js/compass-components';
9+
import {
10+
CONNECTION_COLOR_CODES,
11+
legacyColorsToColorCode,
12+
useConnectionColor,
13+
} from '../hooks/use-connection-color';
914

1015
/**
1116
* Default colors.
1217
*/
13-
const COLORS = [
14-
'#5fc86e',
15-
'#326fde',
16-
'#deb342',
17-
'#d4366e',
18-
'#59c1e2',
19-
'#2c5f4a',
20-
'#d66531',
21-
'#773819',
22-
'#3b8196',
23-
'#ababab',
24-
];
25-
2618
const colorOptionStyles = css({
2719
outline: 'none',
2820
margin: 0,
2921
padding: 0,
3022
marginRight: spacing[2],
3123
borderRadius: '50%',
3224
verticalAlign: 'middle',
33-
width: 36,
34-
height: 36,
25+
width: spacing[5] + spacing[1],
26+
height: spacing[5] + spacing[1],
3527
border: '1px solid transparent',
3628
boxShadow: `0 0 0 0 ${uiColors.focus}`,
3729
transition: 'box-shadow .16s ease-in',
@@ -66,10 +58,12 @@ const selectedColorCheckmarkStyles = css({
6658
function ColorOption({
6759
isSelected,
6860
onClick,
61+
code,
6962
hex,
7063
}: {
7164
isSelected: boolean;
7265
onClick: () => void;
66+
code: string;
7367
hex: string;
7468
}): React.ReactElement {
7569
return (
@@ -80,7 +74,7 @@ function ColorOption({
8074
[activeColorOptionStyles]: isSelected,
8175
[inActiveColorOptionStyles]: !isSelected,
8276
})}
83-
data-testid={`color-pick-${hex}${isSelected ? '-selected' : ''}`}
77+
data-testid={`color-pick-${code}${isSelected ? '-selected' : ''}`}
8478
onClick={onClick}
8579
title={hex}
8680
aria-pressed={isSelected}
@@ -110,13 +104,16 @@ function ColorOption({
110104
);
111105
}
112106

113-
function SavedConnectionColorPicker({
114-
hex,
107+
export function FavoriteColorPicker({
108+
colorCode,
115109
onChange,
116110
}: {
117-
hex?: string;
111+
colorCode?: string;
118112
onChange: (newColor?: string) => void;
119113
}): React.ReactElement {
114+
const selectedColorCode = legacyColorsToColorCode(colorCode);
115+
const { connectionColorToHex: colorCodeToHex } = useConnectionColor();
116+
const selectedColorHex = colorCodeToHex(selectedColorCode);
120117
return (
121118
<>
122119
<Label htmlFor="favorite-color-selector">Color</Label>
@@ -128,30 +125,34 @@ function SavedConnectionColorPicker({
128125
}}
129126
className={cx({
130127
[colorOptionStyles]: true,
131-
[activeColorOptionStyles]: !hex,
128+
[activeColorOptionStyles]: !selectedColorHex,
132129
})}
133130
onClick={() => {
134131
onChange();
135132
}}
136-
data-testid={`color-pick-no-color${!hex ? '-selected' : ''}`}
133+
data-testid={`color-pick-no-color${
134+
!selectedColorHex ? '-selected' : ''
135+
}`}
137136
title="No color"
138-
aria-pressed={!hex}
137+
aria-pressed={!selectedColorHex}
139138
>
140139
<div className={noColorRedBarStyles} />
141140
</button>
142-
{COLORS.map((color) => (
143-
<ColorOption
144-
onClick={() => {
145-
onChange(color);
146-
}}
147-
isSelected={color === hex}
148-
hex={color}
149-
key={color}
150-
/>
151-
))}
141+
{CONNECTION_COLOR_CODES.map((colorCode) => {
142+
const hex = colorCodeToHex(colorCode) || '';
143+
return (
144+
<ColorOption
145+
onClick={() => {
146+
onChange(colorCode);
147+
}}
148+
isSelected={colorCode === selectedColorCode}
149+
hex={hex}
150+
code={colorCode}
151+
key={colorCode}
152+
/>
153+
);
154+
})}
152155
</div>
153156
</>
154157
);
155158
}
156-
157-
export default SavedConnectionColorPicker;

packages/connect-form/src/components/save-connection-modal.spec.tsx

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ describe('SaveConnectionModal Component', function () {
4040

4141
describe('when the color and name are changed and save is clicked', function () {
4242
beforeEach(function () {
43-
fireEvent.click(screen.getByTestId('color-pick-#59c1e2'));
43+
fireEvent.click(screen.getByTestId('color-pick-color3'));
4444

4545
const textArea = screen.getByRole('textbox');
4646

@@ -72,7 +72,7 @@ describe('SaveConnectionModal Component', function () {
7272
expect(onSaveSpy.callCount).to.equal(1);
7373
expect(onSaveSpy.firstCall.args[0]).to.deep.equal({
7474
name: 'delicious cuban sandwich',
75-
color: '#59c1e2',
75+
color: 'color3',
7676
});
7777
});
7878

@@ -83,6 +83,42 @@ describe('SaveConnectionModal Component', function () {
8383
});
8484
});
8585

86+
describe('when the connection does not have a name', function () {
87+
beforeEach(function () {
88+
render(
89+
<SaveConnectionModal
90+
onSaveClicked={onSaveSpy}
91+
onCancelClicked={onCancelSpy}
92+
open
93+
initialFavoriteInfo={{ color: 'color1', name: '' }}
94+
/>
95+
);
96+
});
97+
98+
it('renders save disabled', function () {
99+
const button = screen.getByText('Save').closest('button');
100+
expect(button.disabled).to.be.true;
101+
});
102+
});
103+
104+
describe('when the connection does have a name', function () {
105+
beforeEach(function () {
106+
render(
107+
<SaveConnectionModal
108+
onSaveClicked={onSaveSpy}
109+
onCancelClicked={onCancelSpy}
110+
open
111+
initialFavoriteInfo={{ color: 'color1', name: 'some name' }}
112+
/>
113+
);
114+
});
115+
116+
it('renders save as enabled', function () {
117+
const button = screen.getByText('Save').closest('button');
118+
expect(button.disabled).not.to.be.true;
119+
});
120+
});
121+
86122
describe('when the loaded connection is already a favorite', function () {
87123
beforeEach(function () {
88124
render(
@@ -92,7 +128,7 @@ describe('SaveConnectionModal Component', function () {
92128
open
93129
initialFavoriteInfo={{
94130
name: 'pineapples',
95-
color: '#326fde',
131+
color: 'color3',
96132
}}
97133
/>
98134
);
@@ -104,7 +140,7 @@ describe('SaveConnectionModal Component', function () {
104140

105141
it('should have the color already selected', function () {
106142
expect(screen.queryByTestId('color-pick-no-color-selected')).to.not.exist;
107-
expect(screen.getByTestId('color-pick-#326fde-selected')).to.be.visible;
143+
expect(screen.getByTestId('color-pick-color3-selected')).to.be.visible;
108144
});
109145
});
110146
});

packages/connect-form/src/components/save-connection-modal.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
import type { ConnectionFavoriteOptions } from 'mongodb-data-service';
99

1010
import FormFieldContainer from './form-field-container';
11-
import SavedConnectionColorPicker from './saved-connection-color-picker';
11+
import { FavoriteColorPicker } from './favorite-color-picker';
1212

1313
const connectionNameInputStyles = css({
1414
marginTop: spacing[5],
@@ -41,6 +41,7 @@ function SaveConnectionModal({
4141
...editingFavorite,
4242
});
4343
}}
44+
submitDisabled={(editingFavorite.name || '').trim() ? false : true}
4445
onCancel={onCancelClicked}
4546
buttonText="Save"
4647
>
@@ -61,8 +62,8 @@ function SaveConnectionModal({
6162
/>
6263
</FormFieldContainer>
6364
<FormFieldContainer>
64-
<SavedConnectionColorPicker
65-
hex={editingFavorite.color}
65+
<FavoriteColorPicker
66+
colorCode={editingFavorite.color}
6667
onChange={(newColor?: string) => {
6768
setEditingFavorite({
6869
...editingFavorite,
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { cleanup, render } from '@testing-library/react';
2+
import { expect } from 'chai';
3+
import React from 'react';
4+
import {
5+
useConnectionColor,
6+
CONNECTION_COLOR_CODES,
7+
} from './use-connection-color';
8+
9+
function TestComponent({
10+
colorCode,
11+
}: {
12+
colorCode: string;
13+
}): React.ReactElement {
14+
const { connectionColorToHex } = useConnectionColor();
15+
return <div>{connectionColorToHex(colorCode)}</div>;
16+
}
17+
18+
describe('useConnectionColor', function () {
19+
afterEach(cleanup);
20+
21+
it('converts a color code to hex', function () {
22+
for (const colorCode of CONNECTION_COLOR_CODES) {
23+
const { container } = render(<TestComponent colorCode={colorCode} />);
24+
expect(container.firstChild.textContent).to.match(
25+
/^#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/
26+
);
27+
}
28+
});
29+
30+
it('converts legacy colors', function () {
31+
const legacyColors = {
32+
'#5fc86e': 'color1',
33+
'#326fde': 'color2',
34+
'#deb342': 'color3',
35+
'#d4366e': 'color4',
36+
'#59c1e2': 'color5',
37+
'#2c5f4a': 'color6',
38+
'#d66531': 'color7',
39+
'#773819': 'color8',
40+
'#3b8196': 'color9',
41+
'#ababab': 'color10',
42+
};
43+
44+
for (const [legacyColor, colorCode] of Object.entries(legacyColors)) {
45+
const { container: container1 } = render(
46+
<TestComponent colorCode={legacyColor} />
47+
);
48+
const { container: container2 } = render(
49+
<TestComponent colorCode={colorCode} />
50+
);
51+
expect(container1.firstChild.textContent).to.equal(
52+
container2.firstChild.textContent
53+
);
54+
}
55+
});
56+
57+
it('does not convert an unknown color code', function () {
58+
const { container } = render(
59+
<TestComponent colorCode={'someKindOfColor'} />
60+
);
61+
expect(container.firstChild.textContent).to.be.empty;
62+
});
63+
64+
it('does not convert an unknown hex code', function () {
65+
const { container } = render(<TestComponent colorCode={'#100000'} />);
66+
expect(container.firstChild.textContent).to.be.empty;
67+
});
68+
});
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { useCallback } from 'react';
2+
3+
type ColorCode = `color${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10}`;
4+
5+
const COLOR_CODES_TO_UI_COLORS_DARK_THEME_MAP: Record<ColorCode, string> = {
6+
color1: '#00A35C', // green.dark1
7+
color2: '#71F6BA', // green.light1
8+
color3: '#016BF8', // blue.base
9+
color4: '#0498EC', // blue.light1
10+
color5: '#FFC010', // yellow.base
11+
color6: '#EF5752', // red.light1
12+
color7: '#B45AF2', // purple.base
13+
color8: '#F1D4FD', // purple.light2
14+
color9: '#889397', // gray.base
15+
color10: '#C1C7C6', // gray.light1
16+
};
17+
18+
const LEGACY_COLORS_TO_COLOR_CODE_MAP: Record<string, ColorCode> = {
19+
'#5fc86e': 'color1',
20+
'#326fde': 'color2',
21+
'#deb342': 'color3',
22+
'#d4366e': 'color4',
23+
'#59c1e2': 'color5',
24+
'#2c5f4a': 'color6',
25+
'#d66531': 'color7',
26+
'#773819': 'color8',
27+
'#3b8196': 'color9',
28+
'#ababab': 'color10',
29+
};
30+
31+
export const CONNECTION_COLOR_CODES = Object.keys(
32+
COLOR_CODES_TO_UI_COLORS_DARK_THEME_MAP
33+
) as ColorCode[];
34+
35+
function isColorCode(hexOrColorCode: string | undefined): boolean {
36+
return CONNECTION_COLOR_CODES.includes(hexOrColorCode as ColorCode);
37+
}
38+
39+
export function legacyColorsToColorCode(
40+
hexOrColorCode: string | undefined
41+
): ColorCode | undefined {
42+
if (!hexOrColorCode) {
43+
return;
44+
}
45+
46+
if (isColorCode(hexOrColorCode)) {
47+
return hexOrColorCode as ColorCode;
48+
}
49+
50+
return LEGACY_COLORS_TO_COLOR_CODE_MAP[hexOrColorCode];
51+
}
52+
53+
export function useConnectionColor(): {
54+
connectionColorToHex: (colorCode: string | undefined) => string | undefined;
55+
} {
56+
const colorCodeToHex = useCallback(
57+
(colorCode: string | undefined): string | undefined => {
58+
if (!colorCode) {
59+
return;
60+
}
61+
62+
const migratedColor = legacyColorsToColorCode(colorCode);
63+
if (!migratedColor) {
64+
return;
65+
}
66+
67+
return COLOR_CODES_TO_UI_COLORS_DARK_THEME_MAP[migratedColor];
68+
},
69+
[]
70+
);
71+
72+
return {
73+
connectionColorToHex: colorCodeToHex,
74+
};
75+
}

packages/connect-form/src/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import ConnectForm from './components/connect-form';
22
import SaveConnectionModal from './components/save-connection-modal';
3-
4-
export { SaveConnectionModal };
3+
import { useConnectionColor } from './hooks/use-connection-color';
4+
useConnectionColor;
5+
export { SaveConnectionModal, useConnectionColor };
56
export default ConnectForm;

0 commit comments

Comments
 (0)