04b0a65e73
[Figma Design](https://www.figma.com/design/xt8O9mFeLl46C5InWwoMrN/Twenty?node-id=81380-344641&t=FpjWNOK2gZuDQQfr-0) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Adds a side panel layout for the Command Menu, routes modals into a local container, updates the top bar and context chips, and standardizes small button sizes. > > - **Command Menu**: > - **Side Panel Layout**: Introduces `CommandMenuSidePanelLayout` with animated width, hosts `CommandMenuRouter`, and provides a modal container via `ModalContainerContext`. > - **Top Bar**: Redesign (`CommandMenuTopBar`) with back icon, optional AI sparkles action, compact height (`COMMAND_MENU_SEARCH_BAR_HEIGHT=40`), and updated placeholder. > - **Context Chips**: Adds `CommandMenuLastContextChip` and `CommandMenuRecordInfo`; extends `CommandMenuContextChip` with `page` prop; updates `CommandMenuContextChipGroups` to render last chip as record info when applicable. > - **Container Simplification**: `CommandMenuContainer` simplified to just provide contexts and `AgentChatProvider`. > - **Modal System**: > - Adds `ModalContainerContext` and updates `Modal` to portal into provided container; `Modal.Backdrop` supports `isInContainer`. > - Updates usages (e.g., `UserOrMetadataLoader`, `ActionModal`) to align with new modal behavior. > - **Page Integration**: > - Replaces `PageBody` with `CommandMenuSidePanelLayout` in `RecordShowPage` and `RecordIndexContainerGater`. > - Removes global `CommandMenuRouter` from `DefaultLayout` (keeps keyboard shortcuts). > - **UI/Styling**: > - Standardizes several buttons to `size="small"` (e.g., command actions, open record, options, reply, workflow footer). > - Adjusts `ShowPageSubContainer` styling when rendered inside command menu. > - Storybook tests updated for new placeholder text. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 81fcaa145618a2fa1c3e11e2dc88fe832c51374c. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Félix Malfait <felix.malfait@gmail.com> Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com> Co-authored-by: Devessier <baptiste@devessier.fr> Co-authored-by: Aman Raj <92664006+araj00@users.noreply.github.com> Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com> Co-authored-by: Paul Rastoin <45004772+prastoin@users.noreply.github.com>
84 lines
2.5 KiB
TypeScript
84 lines
2.5 KiB
TypeScript
import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutFromContextStoreTargetedRecord';
|
|
import { useWidgetInEditMode } from '@/command-menu/pages/page-layout/hooks/useWidgetInEditMode';
|
|
import { FormTextFieldInput } from '@/object-record/record-field/ui/form-types/components/FormTextFieldInput';
|
|
import { useUpdatePageLayoutWidget } from '@/page-layout/hooks/useUpdatePageLayoutWidget';
|
|
import styled from '@emotion/styled';
|
|
import { t } from '@lingui/core/macro';
|
|
import { isNonEmptyString, isString } from '@sniptt/guards';
|
|
import { useState } from 'react';
|
|
import { isDefined, isValidUrl } from 'twenty-shared/utils';
|
|
|
|
const StyledContainer = styled.div`
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: ${({ theme }) => theme.spacing(3)};
|
|
padding: ${({ theme }) => theme.spacing(2)};
|
|
`;
|
|
|
|
export const CommandMenuPageLayoutIframeSettings = () => {
|
|
const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord();
|
|
|
|
const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId);
|
|
|
|
const { updatePageLayoutWidget } = useUpdatePageLayoutWidget(pageLayoutId);
|
|
|
|
if (!isDefined(widgetInEditMode)) {
|
|
throw new Error('Widget ID must be present while editing the widget');
|
|
}
|
|
|
|
const configUrl =
|
|
widgetInEditMode.configuration && 'url' in widgetInEditMode.configuration
|
|
? widgetInEditMode.configuration.url
|
|
: null;
|
|
|
|
const [url, setUrl] = useState<string | null>(
|
|
isString(configUrl) ? configUrl : null,
|
|
);
|
|
const [urlError, setUrlError] = useState('');
|
|
|
|
const validateUrl = (urlString: string): boolean => {
|
|
const trimmedUrl = urlString.trim();
|
|
|
|
if (!isNonEmptyString(trimmedUrl)) {
|
|
setUrlError('');
|
|
return true;
|
|
}
|
|
|
|
if (!isValidUrl(trimmedUrl)) {
|
|
setUrlError(t`Please enter a valid URL`);
|
|
return false;
|
|
}
|
|
|
|
setUrlError('');
|
|
return true;
|
|
};
|
|
|
|
const handleUrlChange = (value: string) => {
|
|
setUrl(value);
|
|
|
|
if (validateUrl(value)) {
|
|
const trimmedValue = value.trim();
|
|
updatePageLayoutWidget(widgetInEditMode.id, {
|
|
configuration: {
|
|
...widgetInEditMode.configuration,
|
|
url: trimmedValue || null,
|
|
},
|
|
});
|
|
}
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<StyledContainer>
|
|
<FormTextFieldInput
|
|
label={t`URL to Embed`}
|
|
placeholder="https://example.com/embed"
|
|
defaultValue={url}
|
|
onChange={handleUrlChange}
|
|
error={urlError}
|
|
/>
|
|
</StyledContainer>
|
|
</>
|
|
);
|
|
};
|