From 5207493cda154f2fa93f2f1e6dd1d93d64c1ea05 Mon Sep 17 00:00:00 2001 From: martmull Date: Mon, 15 Jun 2026 14:15:28 +0200 Subject: [PATCH] Add useColorScheme hook to twenty-sdk (#21595) Ability to update front compoonent design according to the dark or white theme of the UI Review in cubic --- .../extend/apps/layout/front-components.mdx | 1 + .../FrontComponentRenderer.stories.tsx | 2 ++ .../src/__stories__/UILibraries.stories.tsx | 1 + .../createFrontComponentStoryMeta.ts | 1 + .../test-utils/runFrontComponentStory.ts | 1 + .../types/FrontComponentExecutionContext.ts | 2 ++ .../components/FrontComponentRenderer.tsx | 1 + ...useFrontComponentExecutionContext.test.tsx | 26 ++++++++++++++++--- .../useFrontComponentExecutionContext.ts | 3 +++ .../front-component/hooks/useColorScheme.ts | 10 +++++++ .../src/sdk/front-component/index.ts | 1 + .../types/FrontComponentExecutionContext.ts | 2 ++ 12 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 packages/twenty-sdk/src/sdk/front-component/hooks/useColorScheme.ts diff --git a/packages/twenty-docs/developers/extend/apps/layout/front-components.mdx b/packages/twenty-docs/developers/extend/apps/layout/front-components.mdx index 54c9474569..0ded788038 100644 --- a/packages/twenty-docs/developers/extend/apps/layout/front-components.mdx +++ b/packages/twenty-docs/developers/extend/apps/layout/front-components.mdx @@ -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 diff --git a/packages/twenty-front-component-renderer/src/__stories__/FrontComponentRenderer.stories.tsx b/packages/twenty-front-component-renderer/src/__stories__/FrontComponentRenderer.stories.tsx index 2a6e0a0fa9..3df4351ae3 100644 --- a/packages/twenty-front-component-renderer/src/__stories__/FrontComponentRenderer.stories.tsx +++ b/packages/twenty-front-component-renderer/src/__stories__/FrontComponentRenderer.stories.tsx @@ -20,6 +20,7 @@ const meta: Meta = { 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 }) => { diff --git a/packages/twenty-front-component-renderer/src/__stories__/UILibraries.stories.tsx b/packages/twenty-front-component-renderer/src/__stories__/UILibraries.stories.tsx index 9829d95660..f49aa4b858 100644 --- a/packages/twenty-front-component-renderer/src/__stories__/UILibraries.stories.tsx +++ b/packages/twenty-front-component-renderer/src/__stories__/UILibraries.stories.tsx @@ -20,6 +20,7 @@ const meta: Meta = { userId: null, recordId: null, selectedRecordIds: [], + colorScheme: 'light', }, }, beforeEach: () => { diff --git a/packages/twenty-front-component-renderer/src/__stories__/shared/test-utils/createFrontComponentStoryMeta.ts b/packages/twenty-front-component-renderer/src/__stories__/shared/test-utils/createFrontComponentStoryMeta.ts index 2c3def8305..90db4eab3a 100644 --- a/packages/twenty-front-component-renderer/src/__stories__/shared/test-utils/createFrontComponentStoryMeta.ts +++ b/packages/twenty-front-component-renderer/src/__stories__/shared/test-utils/createFrontComponentStoryMeta.ts @@ -27,6 +27,7 @@ export const FRONT_COMPONENT_STORY_DEFAULT_ARGS: NonNullable< userId: null, recordId: null, selectedRecordIds: [], + colorScheme: 'light', }, colorScheme: 'light', frontComponentHostCommunicationApi: hostApiMocks, diff --git a/packages/twenty-front-component-renderer/src/__stories__/shared/test-utils/runFrontComponentStory.ts b/packages/twenty-front-component-renderer/src/__stories__/shared/test-utils/runFrontComponentStory.ts index be2dfbca36..cb1447db62 100644 --- a/packages/twenty-front-component-renderer/src/__stories__/shared/test-utils/runFrontComponentStory.ts +++ b/packages/twenty-front-component-renderer/src/__stories__/shared/test-utils/runFrontComponentStory.ts @@ -23,6 +23,7 @@ export const runFrontComponentStory = ({ userId: null, recordId: null, selectedRecordIds: [], + colorScheme: 'light', }, }, play, diff --git a/packages/twenty-front-component-renderer/src/types/FrontComponentExecutionContext.ts b/packages/twenty-front-component-renderer/src/types/FrontComponentExecutionContext.ts index dea0e745db..ab6282a834 100644 --- a/packages/twenty-front-component-renderer/src/types/FrontComponentExecutionContext.ts +++ b/packages/twenty-front-component-renderer/src/types/FrontComponentExecutionContext.ts @@ -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'; }; diff --git a/packages/twenty-front/src/modules/front-components/components/FrontComponentRenderer.tsx b/packages/twenty-front/src/modules/front-components/components/FrontComponentRenderer.tsx index 9b9d173c06..0eebb91573 100644 --- a/packages/twenty-front/src/modules/front-components/components/FrontComponentRenderer.tsx +++ b/packages/twenty-front/src/modules/front-components/components/FrontComponentRenderer.tsx @@ -39,6 +39,7 @@ export const FrontComponentRenderer = ({ frontComponentId, commandMenuItemId, selectedRecordIds, + colorScheme, }); const handleError = useCallback( diff --git a/packages/twenty-front/src/modules/front-components/hooks/__tests__/useFrontComponentExecutionContext.test.tsx b/packages/twenty-front/src/modules/front-components/hooks/__tests__/useFrontComponentExecutionContext.test.tsx index 38ae9056fe..2ac67f0e6c 100644 --- a/packages/twenty-front/src/modules/front-components/hooks/__tests__/useFrontComponentExecutionContext.test.tsx +++ b/packages/twenty-front/src/modules/front-components/hooks/__tests__/useFrontComponentExecutionContext.test.tsx @@ -93,11 +93,18 @@ jest.mock('~/hooks/useCopyToClipboard', () => ({ })); const renderUseFrontComponentExecutionContext = ( - params: Parameters[0], + params: Omit< + Parameters[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', () => { diff --git a/packages/twenty-front/src/modules/front-components/hooks/useFrontComponentExecutionContext.ts b/packages/twenty-front/src/modules/front-components/hooks/useFrontComponentExecutionContext.ts index 58a2a04c11..26b5f66371 100644 --- a/packages/twenty-front/src/modules/front-components/hooks/useFrontComponentExecutionContext.ts +++ b/packages/twenty-front/src/modules/front-components/hooks/useFrontComponentExecutionContext.ts @@ -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'] = diff --git a/packages/twenty-sdk/src/sdk/front-component/hooks/useColorScheme.ts b/packages/twenty-sdk/src/sdk/front-component/hooks/useColorScheme.ts new file mode 100644 index 0000000000..4b7555d728 --- /dev/null +++ b/packages/twenty-sdk/src/sdk/front-component/hooks/useColorScheme.ts @@ -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); +}; diff --git a/packages/twenty-sdk/src/sdk/front-component/index.ts b/packages/twenty-sdk/src/sdk/front-component/index.ts index 750cfeea1e..c03ac6da7b 100644 --- a/packages/twenty-sdk/src/sdk/front-component/index.ts +++ b/packages/twenty-sdk/src/sdk/front-component/index.ts @@ -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'; diff --git a/packages/twenty-sdk/src/sdk/front-component/types/FrontComponentExecutionContext.ts b/packages/twenty-sdk/src/sdk/front-component/types/FrontComponentExecutionContext.ts index dea0e745db..ab6282a834 100644 --- a/packages/twenty-sdk/src/sdk/front-component/types/FrontComponentExecutionContext.ts +++ b/packages/twenty-sdk/src/sdk/front-component/types/FrontComponentExecutionContext.ts @@ -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'; };