Skip to content

Commit 26678e6

Browse files
authored
chore(workspaces): move workspace tab theming to provider (#7035)
1 parent c4472dc commit 26678e6

File tree

14 files changed

+317
-218
lines changed

14 files changed

+317
-218
lines changed

packages/compass-collection/src/plugin-tab-title.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import toNS from 'mongodb-ns';
44
import {
55
useConnectionInfo,
66
useConnectionsListRef,
7-
useTabConnectionTheme,
87
} from '@mongodb-js/compass-connections/provider';
98
import {
109
WorkspaceTab,
@@ -32,7 +31,6 @@ function _PluginTitle({
3231
namespace,
3332
...tabProps
3433
}: PluginTitleProps) {
35-
const { getThemeOf } = useTabConnectionTheme();
3634
const { getConnectionById } = useConnectionsListRef();
3735
const { id: connectionId } = useConnectionInfo();
3836

@@ -75,7 +73,6 @@ function _PluginTitle({
7573
: 'Folder'
7674
}
7775
data-namespace={ns}
78-
tabTheme={getThemeOf(connectionId)}
7976
isNonExistent={isNonExistent}
8077
/>
8178
);

packages/compass-components/src/components/workspace-tabs/tab.tsx

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { useDefaultAction } from '../../hooks/use-default-action';
1313
import { LogoIcon } from '../icons/logo-icon';
1414
import { Tooltip } from '../leafygreen';
1515
import { ServerIcon } from '../icons/server-icon';
16+
import { useTabTheme } from './use-tab-theme';
1617

1718
function focusedChild(className: string) {
1819
return `&:hover ${className}, &:focus-visible ${className}, &:focus-within:not(:focus) ${className}`;
@@ -86,20 +87,6 @@ const tabStyles = css({
8687
},
8788
});
8889

89-
export type TabTheme = {
90-
'--workspace-tab-background-color': string;
91-
'--workspace-tab-selected-background-color': string;
92-
'--workspace-tab-top-border-color': string;
93-
'--workspace-tab-selected-top-border-color': string;
94-
'--workspace-tab-border-color': string;
95-
'--workspace-tab-color': string;
96-
'--workspace-tab-selected-color': string;
97-
'&:focus-visible': {
98-
'--workspace-tab-selected-color': string;
99-
'--workspace-tab-border-color': string;
100-
};
101-
};
102-
10390
const tabLightThemeStyles = css({
10491
'--workspace-tab-background-color': palette.gray.light3,
10592
'--workspace-tab-selected-background-color': palette.white,
@@ -199,7 +186,6 @@ export type WorkspaceTabPluginProps = {
199186
isNonExistent?: boolean;
200187
iconGlyph: GlyphName | 'Logo' | 'Server';
201188
tooltip?: [string, string][];
202-
tabTheme?: Partial<TabTheme>;
203189
};
204190

205191
export type WorkspaceTabCoreProps = {
@@ -224,7 +210,6 @@ function Tab({
224210
onClose,
225211
tabContentId,
226212
iconGlyph,
227-
tabTheme,
228213
className: tabClassName,
229214
...props
230215
}: TabProps & Omit<React.HTMLProps<HTMLDivElement>, 'title'>) {
@@ -233,6 +218,7 @@ function Tab({
233218
const { listeners, setNodeRef, transform, transition } = useSortable({
234219
id: tabContentId,
235220
});
221+
const tabTheme = useTabTheme();
236222

237223
const tabProps = mergeProps<HTMLDivElement>(
238224
defaultActionProps,
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import React, { useContext } from 'react';
2+
3+
export type TabTheme = {
4+
'--workspace-tab-background-color': string;
5+
'--workspace-tab-selected-background-color': string;
6+
'--workspace-tab-top-border-color': string;
7+
'--workspace-tab-selected-top-border-color': string;
8+
'--workspace-tab-border-color': string;
9+
'--workspace-tab-color': string;
10+
'--workspace-tab-selected-color': string;
11+
'&:focus-visible': {
12+
'--workspace-tab-selected-color': string;
13+
'--workspace-tab-border-color': string;
14+
};
15+
};
16+
17+
type TabThemeProviderValue = Partial<TabTheme> | undefined;
18+
19+
type TabThemeContextValue = TabThemeProviderValue | null;
20+
21+
const TabThemeContext = React.createContext<TabThemeContextValue>(null);
22+
23+
export const TabThemeProvider: React.FunctionComponent<{
24+
children: React.ReactNode;
25+
theme: Partial<TabTheme> | undefined | null;
26+
}> = ({ children, theme }) => {
27+
return (
28+
<TabThemeContext.Provider value={theme}>
29+
{children}
30+
</TabThemeContext.Provider>
31+
);
32+
};
33+
34+
export function useTabTheme(): Partial<TabTheme> | undefined | null {
35+
const context = useContext(TabThemeContext);
36+
37+
return context;
38+
}

packages/compass-components/src/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,12 @@ import { Accordion } from './components/accordion';
4040
import { CollapsibleFieldSet } from './components/collapsible-field-set';
4141
export {
4242
Tab as WorkspaceTab,
43-
type TabTheme,
4443
type WorkspaceTabCoreProps,
4544
} from './components/workspace-tabs/tab';
45+
export {
46+
TabThemeProvider,
47+
useTabTheme,
48+
} from './components/workspace-tabs/use-tab-theme';
4649
import { WorkspaceTabs } from './components/workspace-tabs/workspace-tabs';
4750
import ResizableSidebar, {
4851
defaultSidebarWidth,
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
import React from 'react';
2+
import { expect } from 'chai';
3+
import { useTabTheme } from '@mongodb-js/compass-components/src/components/workspace-tabs/use-tab-theme';
4+
import { render } from '@mongodb-js/testing-library-compass';
5+
import { ConnectionThemeProvider } from './connection-tab-theme-provider';
6+
7+
const CONNECTION_INFO = {
8+
id: '1234',
9+
connectionOptions: {
10+
connectionString: 'mongodb://localhost:27017',
11+
},
12+
favorite: {
13+
color: 'color3',
14+
name: 'my kingdom for a hook',
15+
},
16+
};
17+
18+
const CONNECTION_INFO_NO_COLOR = {
19+
id: '1234',
20+
connectionOptions: {
21+
connectionString: 'mongodb://localhost:27017',
22+
},
23+
favorite: {
24+
name: 'look what is done cannot be now amended',
25+
},
26+
};
27+
28+
describe('ConnectionThemeProvider', function () {
29+
describe('when a connection does not exist', function () {
30+
it('should not provide a theme to useTabTheme', function () {
31+
let capturedTheme: ReturnType<typeof useTabTheme> = undefined;
32+
33+
const TestComponent = () => {
34+
capturedTheme = useTabTheme();
35+
return null;
36+
};
37+
38+
render(
39+
<ConnectionThemeProvider connectionId="nonexistent-connection">
40+
<TestComponent />
41+
</ConnectionThemeProvider>,
42+
{
43+
connections: [CONNECTION_INFO],
44+
}
45+
);
46+
47+
expect(capturedTheme).to.equal(undefined);
48+
});
49+
});
50+
51+
describe('when a connection exists with a valid color', function () {
52+
it('should provide the correct theme to useTabTheme', function () {
53+
let capturedTheme: ReturnType<typeof useTabTheme> = undefined;
54+
55+
const TestComponent = () => {
56+
capturedTheme = useTabTheme();
57+
return null;
58+
};
59+
60+
render(
61+
<ConnectionThemeProvider connectionId={CONNECTION_INFO.id}>
62+
<TestComponent />
63+
</ConnectionThemeProvider>,
64+
{
65+
connections: [CONNECTION_INFO],
66+
}
67+
);
68+
69+
expect(capturedTheme).to.have.property(
70+
'--workspace-tab-background-color',
71+
'#D5EFFF'
72+
);
73+
expect(capturedTheme).to.have.property(
74+
'--workspace-tab-top-border-color',
75+
'#D5EFFF'
76+
);
77+
expect(capturedTheme).to.have.property(
78+
'--workspace-tab-selected-top-border-color',
79+
'#C2E5FF'
80+
);
81+
expect(capturedTheme).to.have.deep.property('&:focus-visible');
82+
});
83+
});
84+
85+
describe('when a connection exists without a color', function () {
86+
it('should not provide a theme to useTabTheme', function () {
87+
let capturedTheme: ReturnType<typeof useTabTheme> = undefined;
88+
89+
const TestComponent = () => {
90+
capturedTheme = useTabTheme();
91+
return null;
92+
};
93+
94+
render(
95+
<ConnectionThemeProvider connectionId={CONNECTION_INFO_NO_COLOR.id}>
96+
<TestComponent />
97+
</ConnectionThemeProvider>,
98+
{
99+
connections: [CONNECTION_INFO_NO_COLOR],
100+
}
101+
);
102+
103+
expect(capturedTheme).to.equal(undefined);
104+
});
105+
});
106+
107+
describe('when a connection exists with an invalid color', function () {
108+
it('should not provide a theme to useTabTheme', function () {
109+
let capturedTheme: ReturnType<typeof useTabTheme> = undefined;
110+
const INVALID_COLOR_CONNECTION = {
111+
id: '5678',
112+
connectionOptions: {
113+
connectionString: 'mongodb://localhost:27017',
114+
},
115+
favorite: {
116+
color: 'notavalidcolor',
117+
name: 'invalid color connection',
118+
},
119+
};
120+
121+
const TestComponent = () => {
122+
capturedTheme = useTabTheme();
123+
return null;
124+
};
125+
126+
render(
127+
<ConnectionThemeProvider connectionId={INVALID_COLOR_CONNECTION.id}>
128+
<TestComponent />
129+
</ConnectionThemeProvider>,
130+
{
131+
connections: [INVALID_COLOR_CONNECTION],
132+
}
133+
);
134+
135+
expect(capturedTheme).to.equal(undefined);
136+
});
137+
});
138+
139+
describe('when a connection color is updated', function () {
140+
it('should update the theme provided to useTabTheme', async function () {
141+
let capturedTheme: ReturnType<typeof useTabTheme> = undefined;
142+
const connection = {
143+
id: 'changeable-color',
144+
connectionOptions: {
145+
connectionString: 'mongodb://localhost:27017',
146+
},
147+
favorite: {
148+
color: 'color3', // Initial color
149+
name: 'changing colors',
150+
},
151+
};
152+
153+
const TestComponent = () => {
154+
capturedTheme = useTabTheme();
155+
return <div>Theme consumer</div>;
156+
};
157+
158+
const { rerender, connectionsStore } = render(
159+
<ConnectionThemeProvider connectionId={connection.id}>
160+
<TestComponent />
161+
</ConnectionThemeProvider>,
162+
{
163+
connections: [connection],
164+
}
165+
);
166+
167+
// Initial theme should have color3 values
168+
expect(capturedTheme).to.not.equal(null);
169+
expect(capturedTheme).to.have.property(
170+
'--workspace-tab-background-color',
171+
'#D5EFFF'
172+
);
173+
174+
// Update the connection color
175+
await connectionsStore.actions.saveEditedConnection({
176+
...connection,
177+
favorite: {
178+
...connection.favorite,
179+
color: 'color1', // Change to color1
180+
},
181+
});
182+
183+
// Re-render to pick up the new color
184+
rerender(
185+
<ConnectionThemeProvider connectionId={connection.id}>
186+
<TestComponent />
187+
</ConnectionThemeProvider>
188+
);
189+
190+
// Theme should have been updated with color1 values
191+
expect(capturedTheme).to.not.equal(null);
192+
// color1 should have a different background color than color3
193+
expect(capturedTheme)
194+
.to.have.property('--workspace-tab-background-color')
195+
.that.does.not.equal('#D5EFFF');
196+
});
197+
});
198+
});
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import React, { useMemo } from 'react';
2+
import { type ConnectionInfo } from '@mongodb-js/connection-info';
3+
import { useConnectionColor } from '@mongodb-js/connection-form';
4+
import {
5+
palette,
6+
useDarkMode,
7+
TabThemeProvider,
8+
} from '@mongodb-js/compass-components';
9+
import { useConnectionsColorList } from '../stores/store-context';
10+
11+
export const ConnectionThemeProvider: React.FunctionComponent<{
12+
children: React.ReactNode;
13+
connectionId?: ConnectionInfo['id'];
14+
}> = ({ children, connectionId }) => {
15+
const { connectionColorToHex, connectionColorToHexActive } =
16+
useConnectionColor();
17+
const connectionColorsList = useConnectionsColorList();
18+
const darkMode = useDarkMode();
19+
20+
const theme = useMemo(() => {
21+
const color = connectionColorsList.find((connection) => {
22+
return connection.id === connectionId;
23+
})?.color;
24+
const bgColor = connectionColorToHex(color);
25+
const activeBgColor = connectionColorToHexActive(color);
26+
27+
if (!color || !bgColor || !activeBgColor) {
28+
return;
29+
}
30+
31+
return {
32+
'--workspace-tab-background-color': bgColor,
33+
'--workspace-tab-top-border-color': bgColor,
34+
'--workspace-tab-border-color': darkMode
35+
? palette.gray.dark2
36+
: palette.gray.light2,
37+
'--workspace-tab-color': darkMode
38+
? palette.gray.base
39+
: palette.gray.dark1,
40+
'--workspace-tab-selected-background-color': darkMode
41+
? palette.black
42+
: palette.white,
43+
'--workspace-tab-selected-top-border-color': activeBgColor,
44+
'--workspace-tab-selected-color': darkMode
45+
? palette.white
46+
: palette.gray.dark3,
47+
'&:focus-visible': {
48+
'--workspace-tab-border-color': darkMode
49+
? palette.blue.light1
50+
: palette.blue.base,
51+
'--workspace-tab-selected-color': darkMode
52+
? palette.blue.light1
53+
: palette.blue.base,
54+
},
55+
};
56+
}, [
57+
connectionId,
58+
connectionColorsList,
59+
connectionColorToHex,
60+
connectionColorToHexActive,
61+
darkMode,
62+
]);
63+
64+
return <TabThemeProvider theme={theme}>{children}</TabThemeProvider>;
65+
};

0 commit comments

Comments
 (0)