Link command menu items to specific page layout (#19706)

- Add a `pageLayoutId` foreign key to `CommandMenuItem`, allowing
command menu items to be scoped to a specific page layout instead of
being globally available
- Filter command menu items by the current page layout on the frontend.
Items with a `pageLayoutId` only appear when viewing that layout, while
items without one remain globally visible
- Create an effect to track the current page layout ID
- Include a seed example: a "Show Notification" command pinned to the
Star history standalone page layout

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Raphaël Bosi
2026-04-16 14:07:36 +02:00
committed by GitHub
parent 7af82fb6a4
commit 2b5b8a8b13
39 changed files with 342 additions and 51 deletions
@@ -0,0 +1,59 @@
import { type getDefaultStore } from 'jotai';
import { type Location, matchPath } from 'react-router-dom';
import { AppPath, CoreObjectNameSingular } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector';
import { recordPageLayoutByObjectMetadataIdFamilySelector } from '@/page-layout/states/selectors/recordPageLayoutByObjectMetadataIdFamilySelector';
import { getDefaultRecordPageLayoutId } from '@/page-layout/utils/getDefaultRecordPageLayoutId';
const DASHBOARD_NAME_SINGULAR = CoreObjectNameSingular.Dashboard;
export const getPageLayoutIdForLocation = ({
location,
store,
}: {
location: Location;
store: ReturnType<typeof getDefaultStore>;
}): string | null => {
const recordShowMatch = matchPath(AppPath.RecordShowPage, location.pathname);
if (isDefined(recordShowMatch?.params.objectNameSingular)) {
const objectNameSingular = recordShowMatch.params.objectNameSingular;
if (objectNameSingular === DASHBOARD_NAME_SINGULAR) {
return null;
}
const objectMetadataItem = store.get(
objectMetadataItemFamilySelector.selectorFamily({
objectName: objectNameSingular,
objectNameType: 'singular',
}),
);
if (!isDefined(objectMetadataItem)) {
return null;
}
const recordPageLayout = store.get(
recordPageLayoutByObjectMetadataIdFamilySelector.selectorFamily({
objectMetadataId: objectMetadataItem.id,
}),
);
return isDefined(recordPageLayout)
? recordPageLayout.id
: getDefaultRecordPageLayoutId({
targetObjectNameSingular: objectNameSingular,
});
}
const pageLayoutMatch = matchPath(AppPath.PageLayoutPage, location.pathname);
if (isDefined(pageLayoutMatch?.params.pageLayoutId)) {
return pageLayoutMatch.params.pageLayoutId;
}
return null;
};