Add useColorScheme hook to twenty-sdk (#21595)

Ability to update front compoonent design according to the dark or white
theme of the UI

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21595?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
This commit is contained in:
martmull
2026-06-15 14:15:28 +02:00
committed by GitHub
parent 02d6e2d76f
commit 5207493cda
12 changed files with 47 additions and 4 deletions
@@ -344,6 +344,7 @@ Available hooks:
| `useSelectedRecordIds()` | `string[]` | All selected record IDs (empty array if none selected) |
| `useRecordId()` | `string` or `null` | **Deprecated.** Use `useSelectedRecordIds()` instead |
| `useFrontComponentId()` | `string` | This component instance's ID |
| `useColorScheme()` | `'light'` or `'dark'` | The host UI's active color scheme (`System` is already resolved) |
| `useFrontComponentExecutionContext(selector)` | varies | Access the full execution context with a selector function |
## Application variables
@@ -20,6 +20,7 @@ const meta: Meta<typeof FrontComponentRenderer> = {
userId: null,
recordId: null,
selectedRecordIds: [],
colorScheme: 'light',
},
},
beforeEach: () => {
@@ -123,6 +124,7 @@ export const SdkContext: Story = {
userId: 'test-user-abc-123',
recordId: null,
selectedRecordIds: [],
colorScheme: 'light',
},
},
play: async ({ canvasElement }) => {
@@ -20,6 +20,7 @@ const meta: Meta<typeof FrontComponentRenderer> = {
userId: null,
recordId: null,
selectedRecordIds: [],
colorScheme: 'light',
},
},
beforeEach: () => {
@@ -27,6 +27,7 @@ export const FRONT_COMPONENT_STORY_DEFAULT_ARGS: NonNullable<
userId: null,
recordId: null,
selectedRecordIds: [],
colorScheme: 'light',
},
colorScheme: 'light',
frontComponentHostCommunicationApi: hostApiMocks,
@@ -23,6 +23,7 @@ export const runFrontComponentStory = ({
userId: null,
recordId: null,
selectedRecordIds: [],
colorScheme: 'light',
},
},
play,
@@ -7,4 +7,6 @@ export type FrontComponentExecutionContext = {
recordId: string | null;
/** All selected record IDs */
selectedRecordIds: string[];
/** Resolved color scheme of the host UI ('System' is already resolved) */
colorScheme: 'light' | 'dark';
};
@@ -39,6 +39,7 @@ export const FrontComponentRenderer = ({
frontComponentId,
commandMenuItemId,
selectedRecordIds,
colorScheme,
});
const handleError = useCallback(
@@ -93,11 +93,18 @@ jest.mock('~/hooks/useCopyToClipboard', () => ({
}));
const renderUseFrontComponentExecutionContext = (
params: Parameters<typeof useFrontComponentExecutionContext>[0],
params: Omit<
Parameters<typeof useFrontComponentExecutionContext>[0],
'colorScheme'
> & { colorScheme?: 'light' | 'dark' },
) =>
renderHook(() => useFrontComponentExecutionContext(params), {
wrapper: ({ children }) => I18nProvider({ i18n, children }),
});
renderHook(
() =>
useFrontComponentExecutionContext({ colorScheme: 'light', ...params }),
{
wrapper: ({ children }) => I18nProvider({ i18n, children }),
},
);
const FRONT_COMPONENT_ID = 'fc-test-id';
const COMMAND_MENU_ITEM_ID = 'cmd-item-1';
@@ -120,6 +127,7 @@ describe('useFrontComponentExecutionContext', () => {
userId: 'user-123',
recordId: 'record-456',
selectedRecordIds: ['record-456'],
colorScheme: 'light',
});
});
@@ -134,6 +142,7 @@ describe('useFrontComponentExecutionContext', () => {
userId: 'user-123',
recordId: null,
selectedRecordIds: ['record-1', 'record-2', 'record-3'],
colorScheme: 'light',
});
});
@@ -165,6 +174,15 @@ describe('useFrontComponentExecutionContext', () => {
expect(result.current.executionContext.recordId).toBeNull();
expect(result.current.executionContext.selectedRecordIds).toEqual([]);
});
it('should expose the provided colorScheme', () => {
const { result } = renderUseFrontComponentExecutionContext({
frontComponentId: FRONT_COMPONENT_ID,
colorScheme: 'dark',
});
expect(result.current.executionContext.colorScheme).toBe('dark');
});
});
describe('navigate', () => {
@@ -32,10 +32,12 @@ export const useFrontComponentExecutionContext = ({
frontComponentId,
commandMenuItemId,
selectedRecordIds,
colorScheme,
}: {
frontComponentId: string;
commandMenuItemId?: string;
selectedRecordIds?: string[];
colorScheme: 'light' | 'dark';
}): {
executionContext: FrontComponentExecutionContext;
frontComponentHostCommunicationApi: FrontComponentHostCommunicationApi;
@@ -141,6 +143,7 @@ export const useFrontComponentExecutionContext = ({
userId: currentUser?.id ?? null,
recordId: selectedRecordIds?.length === 1 ? selectedRecordIds[0] : null,
selectedRecordIds: selectedRecordIds ?? [],
colorScheme,
};
const unmountFrontComponent: FrontComponentHostCommunicationApi['unmountFrontComponent'] =
@@ -0,0 +1,10 @@
import { type FrontComponentExecutionContext } from '../types/FrontComponentExecutionContext';
import { useFrontComponentExecutionContext } from './useFrontComponentExecutionContext';
const selectColorScheme = (
context: FrontComponentExecutionContext,
): 'light' | 'dark' => context.colorScheme;
export const useColorScheme = (): 'light' | 'dark' => {
return useFrontComponentExecutionContext(selectColorScheme);
};
@@ -7,6 +7,7 @@ export { openCommandConfirmationModal } from './functions/openCommandConfirmatio
export { openSidePanelPage } from './functions/openSidePanelPage';
export { unmountFrontComponent } from './functions/unmountFrontComponent';
export { updateProgress } from './functions/updateProgress';
export { useColorScheme } from './hooks/useColorScheme';
export { useFrontComponentExecutionContext } from './hooks/useFrontComponentExecutionContext';
export { useFrontComponentId } from './hooks/useFrontComponentId';
export { useRecordId } from './hooks/useRecordId';
@@ -7,4 +7,6 @@ export type FrontComponentExecutionContext = {
recordId: string | null;
/** All selected record IDs */
selectedRecordIds: string[];
/** Resolved color scheme of the host UI ('System' is already resolved) */
colorScheme: 'light' | 'dark';
};