From b32d76059e754a33819a76835822154cec01724e Mon Sep 17 00:00:00 2001 From: Edward Irby Date: Tue, 3 Oct 2023 10:52:18 -0700 Subject: [PATCH] feat(client): refactors layout to resolve scroll bugs (#432) - Refactors layout to fix scroll bugs - Creates a Page component and subcomponents to resolve issues and create consistent way of creating new pages without introducing scroll related styling bugs - @wesbillman notification style I dropped under the transition to prevent it from making the screen black as the notification component as a whole covers the whole screen --- console/client/.eslintrc.cjs | 1 + console/client/index.html | 2 +- console/client/src/App.tsx | 27 +++---- console/client/src/components/PageHeader.tsx | 11 ++- console/client/src/components/index.ts | 6 ++ .../features/deployments/DeploymentPage.tsx | 12 +-- .../features/deployments/DeploymentsPage.tsx | 34 ++++---- .../client/src/features/graph/GraphPage.tsx | 13 ++- .../src/features/modules/ModulePage.tsx | 20 ++--- .../src/features/modules/ModulesPage.tsx | 15 ++-- .../src/features/timeline/TimelinePage.tsx | 14 ++-- .../client/src/features/verbs/VerbPage.tsx | 27 +++---- console/client/src/layout/Layout.tsx | 20 ++--- console/client/src/layout/Notification.tsx | 7 +- console/client/src/layout/Page.tsx | 79 +++++++++++++++++++ console/client/src/layout/SidePanel.tsx | 3 +- console/client/src/layout/index.ts | 2 + console/client/src/main.tsx | 3 + 18 files changed, 200 insertions(+), 96 deletions(-) create mode 100644 console/client/src/components/index.ts create mode 100644 console/client/src/layout/Page.tsx create mode 100644 console/client/src/layout/index.ts diff --git a/console/client/.eslintrc.cjs b/console/client/.eslintrc.cjs index 7b8ae2a15a..f94f680b21 100644 --- a/console/client/.eslintrc.cjs +++ b/console/client/.eslintrc.cjs @@ -24,6 +24,7 @@ module.exports = { rules: { 'react/react-in-jsx-scope': 'off', 'react/jsx-uses-react': 'off', + 'react/prop-types': 'off', 'func-style': ['error', 'expression'], '@typescript-eslint/consistent-type-definitions': ['error', 'interface'], '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }], diff --git a/console/client/index.html b/console/client/index.html index a6e97b543d..82085d444a 100644 --- a/console/client/index.html +++ b/console/client/index.html @@ -8,7 +8,7 @@ - +
diff --git a/console/client/src/App.tsx b/console/client/src/App.tsx index 518a46f661..2261e25017 100644 --- a/console/client/src/App.tsx +++ b/console/client/src/App.tsx @@ -7,26 +7,23 @@ import { ModulesPage } from './features/modules/ModulesPage.tsx' import { TimelinePage } from './features/timeline/TimelinePage.tsx' import { VerbPage } from './features/verbs/VerbPage.tsx' import { Layout } from './layout/Layout.tsx' -import { bgColor, textColor } from './utils/style.utils.ts' export const App = () => { return ( -
- - }> - } /> - } /> + + }> + } /> + } /> - } /> - } /> - } /> + } /> + } /> + } /> - } /> - } /> + } /> + } /> - } /> - - -
+ } /> + + ) } diff --git a/console/client/src/components/PageHeader.tsx b/console/client/src/components/PageHeader.tsx index 3bb128cb11..b7affa0192 100644 --- a/console/client/src/components/PageHeader.tsx +++ b/console/client/src/components/PageHeader.tsx @@ -1,5 +1,6 @@ import { ChevronRightIcon } from '@heroicons/react/20/solid' import React from 'react' +import { classNames } from '../utils' interface Breadcrumb { label: string @@ -11,11 +12,17 @@ interface Props { title: string children?: React.ReactNode breadcrumbs?: Breadcrumb[] + className?: string } -export const PageHeader = ({ icon, title, children, breadcrumbs }: Props) => { +export const PageHeader = ({ icon, title, children, breadcrumbs, className }: Props) => { return ( -
+
{icon} {breadcrumbs && breadcrumbs.length > 0 && ( diff --git a/console/client/src/components/index.ts b/console/client/src/components/index.ts new file mode 100644 index 0000000000..30590dfc35 --- /dev/null +++ b/console/client/src/components/index.ts @@ -0,0 +1,6 @@ +export * from './AttributeBadge' +export * from './ButtonSmall' +export * from './Card' +export * from './CloseButton' +export * from './CodeBlock' +export * from './DarkModeSwitch' diff --git a/console/client/src/features/deployments/DeploymentPage.tsx b/console/client/src/features/deployments/DeploymentPage.tsx index 5d13af7aa9..16191407ce 100644 --- a/console/client/src/features/deployments/DeploymentPage.tsx +++ b/console/client/src/features/deployments/DeploymentPage.tsx @@ -3,11 +3,11 @@ import React from 'react' import { useNavigate, useParams } from 'react-router-dom' import { ButtonSmall } from '../../components/ButtonSmall' import { Card } from '../../components/Card' -import { PageHeader } from '../../components/PageHeader' import { Module } from '../../protos/xyz/block/ftl/v1/console/console_pb' import { MetadataCalls, VerbRef } from '../../protos/xyz/block/ftl/v1/schema/schema_pb' import { modulesContext } from '../../providers/modules-provider' import { verbRefString } from '../verbs/verb.utils' +import { Page } from '../../layout' export const DeploymentPage = () => { const navigate = useNavigate() @@ -49,14 +49,14 @@ export const DeploymentPage = () => { }, [modules]) return ( - <> - + } title={module?.deploymentName || 'Loading...'} breadcrumbs={[{ label: 'Deployments', link: '/deployments' }]} /> -
+
{module?.verbs.map((verb) => ( { ))} -
- +
+ ) } diff --git a/console/client/src/features/deployments/DeploymentsPage.tsx b/console/client/src/features/deployments/DeploymentsPage.tsx index cb5b71b16b..c854ef0760 100644 --- a/console/client/src/features/deployments/DeploymentsPage.tsx +++ b/console/client/src/features/deployments/DeploymentsPage.tsx @@ -2,28 +2,30 @@ import { RocketLaunchIcon } from '@heroicons/react/24/outline' import React from 'react' import { useNavigate } from 'react-router-dom' import { Card } from '../../components/Card' -import { PageHeader } from '../../components/PageHeader' import { modulesContext } from '../../providers/modules-provider' +import { Page } from '../../layout' export const DeploymentsPage = () => { const modules = React.useContext(modulesContext) const navigate = useNavigate() return ( - <> - } title='Deployments' /> -
- {modules.modules.map((module) => ( - navigate(`/deployments/${module.deploymentName}`)} - > - {module.name} -

{module.deploymentName}

-
- ))} -
- + + } title='Deployments' /> + +
+ {modules.modules.map((module) => ( + navigate(`/deployments/${module.deploymentName}`)} + > + {module.name} +

{module.deploymentName}

+
+ ))} +
+
+
) } diff --git a/console/client/src/features/graph/GraphPage.tsx b/console/client/src/features/graph/GraphPage.tsx index 1b2af56079..9c81660620 100644 --- a/console/client/src/features/graph/GraphPage.tsx +++ b/console/client/src/features/graph/GraphPage.tsx @@ -2,12 +2,11 @@ import { CubeTransparentIcon } from '@heroicons/react/24/outline' import { useContext, useEffect } from 'react' import ReactFlow, { Controls, MiniMap, useEdgesState, useNodesState } from 'reactflow' import 'reactflow/dist/style.css' -import { PageHeader } from '../../components/PageHeader' import { modulesContext } from '../../providers/modules-provider' import { GroupNode } from './GroupNode' import { VerbNode } from './VerbNode' import { layoutNodes } from './create-layout' - +import { Page } from '../../layout' const nodeTypes = { groupNode: GroupNode, verbNode: VerbNode } export const GraphPage = () => { @@ -22,9 +21,9 @@ export const GraphPage = () => { }, [modules, setEdges, setNodes]) return ( - <> - } title='Graph' /> -
+ + } title='Graph' /> + { -
- + + ) } diff --git a/console/client/src/features/modules/ModulePage.tsx b/console/client/src/features/modules/ModulePage.tsx index 8af9a2ff25..1df5dbe5da 100644 --- a/console/client/src/features/modules/ModulePage.tsx +++ b/console/client/src/features/modules/ModulePage.tsx @@ -2,7 +2,7 @@ import { Square3Stack3DIcon } from '@heroicons/react/24/outline' import React from 'react' import { useNavigate, useParams } from 'react-router-dom' import { Card } from '../../components/Card' -import { PageHeader } from '../../components/PageHeader' +import { Page } from '../../layout' import { CallEvent, Module } from '../../protos/xyz/block/ftl/v1/console/console_pb' import { modulesContext } from '../../providers/modules-provider' import { getCalls } from '../../services/console.service' @@ -33,14 +33,14 @@ export const ModulePage = () => { }, [module]) return ( - <> -
+ + } + title={module?.name || ''} + breadcrumbs={[{ label: 'Modules', link: '/modules' }]} + /> +
- } - title={module?.name || ''} - breadcrumbs={[{ label: 'Modules', link: '/modules' }]} - />
{module?.verbs.map((verb) => ( @@ -59,7 +59,7 @@ export const ModulePage = () => {
-
- +
+
) } diff --git a/console/client/src/features/modules/ModulesPage.tsx b/console/client/src/features/modules/ModulesPage.tsx index ac07762a61..94e7dc4adf 100644 --- a/console/client/src/features/modules/ModulesPage.tsx +++ b/console/client/src/features/modules/ModulesPage.tsx @@ -1,12 +1,13 @@ import React from 'react' import { Square3Stack3DIcon } from '@heroicons/react/24/outline' -import { PageHeader } from '../../components/PageHeader' import { modulesContext } from '../../providers/modules-provider' import { generateDot } from './generate-dot' import { dotToSVG } from './dot-to-svg' import { formatSVG } from './format-svg' import { svgZoom } from './svg-zoom' import { createControls } from './create-controls' +import { Page } from '../../layout' + import './Modules.css' export const ModulesPage = () => { @@ -49,10 +50,12 @@ export const ModulesPage = () => { } }, [controls, svg]) return ( -
- } title='Modules' /> -
-
-
+ + } title='Modules' /> + +
+
+ + ) } diff --git a/console/client/src/features/timeline/TimelinePage.tsx b/console/client/src/features/timeline/TimelinePage.tsx index bdd583924c..9a306190d1 100644 --- a/console/client/src/features/timeline/TimelinePage.tsx +++ b/console/client/src/features/timeline/TimelinePage.tsx @@ -1,7 +1,7 @@ import { ListBulletIcon } from '@heroicons/react/24/outline' import React from 'react' import { useSearchParams } from 'react-router-dom' -import { PageHeader } from '../../components/PageHeader' +import { Page } from '../../layout' import { EventsQuery_Filter } from '../../protos/xyz/block/ftl/v1/console/console_pb' import { Timeline } from './Timeline' import { TimelineFilterPanel } from './filters/TimelineFilterPanel' @@ -31,22 +31,22 @@ export const TimelinePage = () => { } return ( - <> - } title='Events'> + + } title='Events'> - -
+ +
-
- + + ) } diff --git a/console/client/src/features/verbs/VerbPage.tsx b/console/client/src/features/verbs/VerbPage.tsx index d5b4cad70c..ce0174013d 100644 --- a/console/client/src/features/verbs/VerbPage.tsx +++ b/console/client/src/features/verbs/VerbPage.tsx @@ -2,7 +2,7 @@ import { Square3Stack3DIcon } from '@heroicons/react/24/outline' import React from 'react' import { useParams } from 'react-router-dom' import { CodeBlock } from '../../components/CodeBlock' -import { PageHeader } from '../../components/PageHeader' +import { Page } from '../../layout' import { CallEvent, Module, Verb } from '../../protos/xyz/block/ftl/v1/console/console_pb' import { modulesContext } from '../../providers/modules-provider' import { getCalls } from '../../services/console.service' @@ -40,17 +40,17 @@ export const VerbPage = () => { }, [module]) return ( - <> -
+ + } + title={verb?.verb?.name || ''} + breadcrumbs={[ + { label: 'Modules', link: '/modules' }, + { label: module?.name || '', link: `/modules/${module?.name}` }, + ]} + /> +
- } - title={verb?.verb?.name || ''} - breadcrumbs={[ - { label: 'Modules', link: '/modules' }, - { label: module?.name || '', link: `/modules/${module?.name}` }, - ]} - />
{verb?.verb?.request?.toJsonString() && ( { /> )}
-
-
- + + ) } diff --git a/console/client/src/layout/Layout.tsx b/console/client/src/layout/Layout.tsx index 954b687b6d..d4de35002c 100644 --- a/console/client/src/layout/Layout.tsx +++ b/console/client/src/layout/Layout.tsx @@ -2,20 +2,22 @@ import { Outlet } from 'react-router-dom' import { Navigation } from './Navigation' import { Notification } from './Notification' import { SidePanel } from './SidePanel' +import { bgColor, textColor } from '../utils' export const Layout = () => { return ( -
- - -
-
+ <> +
+ +
-
-
- + +
-
+ ) } diff --git a/console/client/src/layout/Notification.tsx b/console/client/src/layout/Notification.tsx index 112060b43a..189c573878 100644 --- a/console/client/src/layout/Notification.tsx +++ b/console/client/src/layout/Notification.tsx @@ -8,6 +8,7 @@ import { } from '@heroicons/react/24/outline' import React, { Fragment } from 'react' import { NotificationType, NotificationsContext } from '../providers/notifications-provider' +import { textColor } from '../utils' export const Notification = () => { const { isOpen, notification, closeNotification } = React.useContext(NotificationsContext) @@ -45,7 +46,7 @@ export const Notification = () => { return (
{ leaveFrom='opacity-100' leaveTo='opacity-0' > -
+
{icon()}
diff --git a/console/client/src/layout/Page.tsx b/console/client/src/layout/Page.tsx new file mode 100644 index 0000000000..fc994113c9 --- /dev/null +++ b/console/client/src/layout/Page.tsx @@ -0,0 +1,79 @@ +import { ChevronRightIcon } from '@heroicons/react/20/solid' +import React from 'react' +import { classNames } from '../utils' + +interface Breadcrumb { + label: string + link?: string +} + +interface Props { + icon?: React.ReactNode + title: string + children?: React.ReactNode + breadcrumbs?: Breadcrumb[] + className?: string +} + +const Header = ({ icon, title, children, breadcrumbs, className }: Props) => { + return ( +
+
+ {icon} + {breadcrumbs && breadcrumbs.length > 0 && ( + + )} + {title} +
+ {children} +
+ ) +} + +const Body: React.FC<{ + className?: string + style?: React.CSSProperties + children?: React.ReactNode +}> = ({ className, style, children }) => { + return ( +
+ {children} +
+ ) +} + +export const Page: React.FC<{ + className?: string + style?: React.CSSProperties + children?: React.ReactNode +}> & { + Header: typeof Header + Body: typeof Body +} = ({ className, style, children }) => { + return ( +
+ {children} +
+ ) +} + +Page.Header = Header +Page.Body = Body diff --git a/console/client/src/layout/SidePanel.tsx b/console/client/src/layout/SidePanel.tsx index 7e75fa9330..26efe99ff3 100644 --- a/console/client/src/layout/SidePanel.tsx +++ b/console/client/src/layout/SidePanel.tsx @@ -2,12 +2,13 @@ import { Transition } from '@headlessui/react' import { Fragment, useContext } from 'react' import { SidePanelContext } from '../providers/side-panel-provider' import { sidePanelColor } from '../utils/style.utils' +import { bgColor, textColor } from '../utils' export const SidePanel = () => { const { isOpen, component } = useContext(SidePanelContext) return ( -
+