Feat: API Playground (#10376)

/claim #10283

---------

Co-authored-by: Félix Malfait <felix@twenty.com>
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
oliver
2025-03-07 09:03:57 -08:00
committed by GitHub
parent d1518764a8
commit fc287dac78
55 changed files with 2915 additions and 163 deletions

View File

@ -0,0 +1,59 @@
import { PageHeader } from '@/ui/layout/page/components/PageHeader';
import {
Breadcrumb,
BreadcrumbProps,
} from '@/ui/navigation/bread-crumb/components/Breadcrumb';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import '@scalar/api-reference-react/style.css';
import { IconButton, IconX, useIsMobile } from 'twenty-ui';
type FullScreenContainerProps = {
children: JSX.Element | JSX.Element[];
links: BreadcrumbProps['links'];
exitFullScreen(): void;
};
const StyledFullScreen = styled.div`
display: flex;
flex-direction: column;
width: 100dvw;
height: 100dvh;
background: ${({ theme }) => theme.background.noisy};
`;
const StyledMainContainer = styled.div`
border-top: 1px solid ${({ theme }) => theme.border.color.medium};
height: 100%;
width: 100%;
`;
export const FullScreenContainer = ({
children,
links,
exitFullScreen,
}: FullScreenContainerProps) => {
const isMobile = useIsMobile();
const { t } = useLingui();
const handleExitFullScreen = () => {
exitFullScreen();
};
return (
<StyledFullScreen>
<PageHeader title={<Breadcrumb links={links} />}>
<IconButton
Icon={IconX}
dataTestId="close-button"
size={isMobile ? 'medium' : 'small'}
variant="secondary"
accent="default"
onClick={handleExitFullScreen}
ariaLabel={t`Exit Full Screen`}
/>
</PageHeader>
<StyledMainContainer>{children}</StyledMainContainer>
</StyledFullScreen>
);
};

View File

@ -0,0 +1,45 @@
import { FullScreenContainer } from '@/ui/layout/fullscreen/components/FullScreenContainer';
import styled from '@emotion/styled';
import { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator, ComponentWithRouterDecorator } from 'twenty-ui';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
const meta: Meta<typeof FullScreenContainer> = {
title: 'UI/Layout/FullScreenContainer',
component: FullScreenContainer,
decorators: [
ComponentDecorator,
I18nFrontDecorator,
ComponentWithRouterDecorator,
],
};
export default meta;
type Story = StoryObj<typeof FullScreenContainer>;
const StyledContainer = styled.div`
width: 100%;
height: 100%;
background-color: white;
display: flex;
justify-content: center;
align-items: center;
`;
export const Default: Story = {
args: {
children: <StyledContainer>This is full-screen content</StyledContainer>,
links: [
{
children: 'Layout',
href: '/',
},
{
children: 'FullScreen',
href: '/',
},
],
exitFullScreen: () => {},
},
decorators: [ComponentDecorator],
};

View File

@ -0,0 +1,19 @@
import { useMemo } from 'react';
import { SettingsPath } from '@/types/SettingsPath';
import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';
export const useShowFullscreen = () => {
const { isMatchingLocation } = useIsMatchingLocation();
return useMemo(() => {
if (
isMatchingLocation('settings/' + SettingsPath.RestPlayground + '/*') ||
isMatchingLocation('settings/' + SettingsPath.GraphQLPlayground)
) {
return true;
}
return false;
}, [isMatchingLocation]);
};

View File

@ -8,6 +8,7 @@ import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage';
import { OBJECT_SETTINGS_WIDTH } from '@/settings/data-model/constants/ObjectSettings';
import { SignInAppNavigationDrawerMock } from '@/sign-in-background-mock/components/SignInAppNavigationDrawerMock';
import { SignInBackgroundMockPage } from '@/sign-in-background-mock/components/SignInBackgroundMockPage';
import { useShowFullscreen } from '@/ui/layout/fullscreen/hooks/useShowFullscreen';
import { useShowAuthModal } from '@/ui/layout/hooks/useShowAuthModal';
import { NAV_DRAWER_WIDTHS } from '@/ui/navigation/navigation-drawer/constants/NavDrawerWidths';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
@ -69,6 +70,7 @@ export const DefaultLayout = () => {
const theme = useTheme();
const windowsWidth = useScreenSize().width;
const showAuthModal = useShowAuthModal();
const useShowFullScreen = useShowFullscreen();
return (
<>
@ -90,7 +92,7 @@ export const DefaultLayout = () => {
<StyledPageContainer
animate={{
marginLeft:
isSettingsPage && !isMobile
isSettingsPage && !isMobile && !useShowFullScreen
? (windowsWidth -
(OBJECT_SETTINGS_WIDTH +
NAV_DRAWER_WIDTHS.menu.desktop.expanded +
@ -102,7 +104,7 @@ export const DefaultLayout = () => {
>
{showAuthModal ? (
<StyledAppNavigationDrawerMock />
) : (
) : useShowFullScreen ? null : (
<StyledAppNavigationDrawer />
)}
{showAuthModal ? (

View File

@ -1,15 +1,44 @@
import { ActivityRichTextEditor } from '@/activities/components/ActivityRichTextEditor';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { isNewViewableRecordLoadingState } from '@/object-record/record-right-drawer/states/isNewViewableRecordLoading';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { lazy, Suspense } from 'react';
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
import { useRecoilValue } from 'recoil';
const ActivityRichTextEditor = lazy(() =>
import('@/activities/components/ActivityRichTextEditor').then((module) => ({
default: module.ActivityRichTextEditor,
})),
);
const StyledShowPageActivityContainer = styled.div`
margin-top: ${({ theme }) => theme.spacing(6)};
width: 100%;
`;
const StyledSkeletonContainer = styled.div`
width: 100%;
`;
const LoadingSkeleton = () => {
const theme = useTheme();
return (
<StyledSkeletonContainer>
<SkeletonTheme
baseColor={theme.background.tertiary}
highlightColor={theme.background.transparent.lighter}
borderRadius={theme.border.radius.sm}
>
<Skeleton height={200} />
</SkeletonTheme>
</StyledSkeletonContainer>
);
};
export const ShowPageActivityContainer = ({
targetableObject,
}: {
@ -28,14 +57,16 @@ export const ShowPageActivityContainer = ({
componentInstanceId={`scroll-wrapper-tab-list-${targetableObject.id}`}
>
<StyledShowPageActivityContainer>
<ActivityRichTextEditor
activityId={targetableObject.id}
activityObjectNameSingular={
targetableObject.targetObjectNameSingular as
| CoreObjectNameSingular.Note
| CoreObjectNameSingular.Task
}
/>
<Suspense fallback={<LoadingSkeleton />}>
<ActivityRichTextEditor
activityId={targetableObject.id}
activityObjectNameSingular={
targetableObject.targetObjectNameSingular as
| CoreObjectNameSingular.Note
| CoreObjectNameSingular.Task
}
/>
</Suspense>
</StyledShowPageActivityContainer>
</ScrollWrapper>
) : (