Skip to content

feat(ws): Introduce drawer to workspace creation wizard #310

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React, { Ref } from 'react';
import {
Drawer,
DrawerPanelContent,
DrawerContent,
DrawerContentBody,
DrawerHead,
DrawerActions,
DrawerCloseButton,
Title,
} from '@patternfly/react-core';

interface WorkspaceCreationDrawerProps {
children: React.ReactNode;
title: string;
info: React.ReactNode;
isExpanded: boolean;
drawerRef?: Ref<HTMLSpanElement>;
onCloseClick: () => void;
onExpand: () => void;
}

export const WorkspaceCreationDrawer: React.FC<WorkspaceCreationDrawerProps> = ({
children,
isExpanded,
drawerRef,
title,
info,
onCloseClick,
onExpand,
}) => {
const panelContent = (
<DrawerPanelContent>
<DrawerHead>
<span role="button" tabIndex={isExpanded ? 0 : -1} ref={drawerRef as Ref<HTMLSpanElement>}>
<Title headingLevel="h6">{title}</Title>
</span>
<DrawerActions>
<DrawerCloseButton onClick={onCloseClick} />
</DrawerActions>
</DrawerHead>
{info}
</DrawerPanelContent>
);

return (
<>
<Drawer isExpanded={isExpanded} isInline onExpand={onExpand}>
<DrawerContent panelContent={panelContent}>
<DrawerContentBody>{children}</DrawerContentBody>
</DrawerContent>
</Drawer>
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,9 @@ type WorkspaceCreationImageDetailsProps = {
export const WorkspaceCreationImageDetails: React.FunctionComponent<
WorkspaceCreationImageDetailsProps
> = ({ workspaceImage }) => (
<>
{!workspaceImage && <p>Select an image to view its details here.</p>}

<div style={{ marginLeft: 'var(--pf-t--global--spacer--md)' }}>
{workspaceImage && (
<>
<Title headingLevel="h6">Image</Title>
<Title headingLevel="h3">{workspaceImage.displayName}</Title>
<br />
<List isPlain>
Expand All @@ -26,5 +23,5 @@ export const WorkspaceCreationImageDetails: React.FunctionComponent<
</List>
</>
)}
</>
</div>
);
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import * as React from 'react';
import React, { useMemo, useState, useCallback } from 'react';
import { Content, Divider, Split, SplitItem } from '@patternfly/react-core';
import { useMemo, useState } from 'react';
import { WorkspaceCreationImageDetails } from '~/app/pages/Workspaces/Creation/image/WorkspaceCreationImageDetails';
import { WorkspaceCreationImageList } from '~/app/pages/Workspaces/Creation/image/WorkspaceCreationImageList';
import { FilterByLabels } from '~/app/pages/Workspaces/Creation/labelFilter/FilterByLabels';
import { WorkspaceImageConfigValue } from '~/shared/api/backendApiTypes';
import { WorkspaceCreationDrawer } from '~/app/pages/Workspaces/Creation/WorkspaceCreationDrawer';

interface WorkspaceCreationImageSelectionProps {
images: WorkspaceImageConfigValue[];
Expand All @@ -16,6 +16,26 @@ const WorkspaceCreationImageSelection: React.FunctionComponent<
WorkspaceCreationImageSelectionProps
> = ({ images, selectedImage, onSelect }) => {
const [selectedLabels, setSelectedLabels] = useState<Map<string, Set<string>>>(new Map());
const [isExpanded, setIsExpanded] = useState(false);
const drawerRef = React.useRef<HTMLSpanElement>(undefined);

const onExpand = useCallback(() => {
if (drawerRef.current) {
drawerRef.current.focus();
}
}, []);

const onClick = useCallback(
(image?: WorkspaceImageConfigValue) => {
setIsExpanded(true);
onSelect(image);
},
[onSelect],
);

const onCloseClick = useCallback(() => {
setIsExpanded(false);
}, []);

const imageFilterContent = useMemo(
() => (
Expand All @@ -37,18 +57,25 @@ const WorkspaceCreationImageSelection: React.FunctionComponent<
<Content style={{ height: '100%' }}>
<p>Select a workspace image and image version to use for the workspace.</p>
<Divider />
<Split hasGutter>
<SplitItem style={{ minWidth: '200px' }}>{imageFilterContent}</SplitItem>
<SplitItem isFilled>
<WorkspaceCreationImageList
images={images}
selectedLabels={selectedLabels}
selectedImage={selectedImage}
onSelect={onSelect}
/>
</SplitItem>
<SplitItem style={{ minWidth: '200px' }}>{imageDetailsContent}</SplitItem>
</Split>
<WorkspaceCreationDrawer
title="Image"
info={imageDetailsContent}
isExpanded={isExpanded}
onCloseClick={onCloseClick}
onExpand={onExpand}
>
<Split hasGutter>
<SplitItem style={{ minWidth: '200px' }}>{imageFilterContent}</SplitItem>
<SplitItem isFilled>
<WorkspaceCreationImageList
images={images}
selectedLabels={selectedLabels}
selectedImage={selectedImage}
onSelect={onClick}
/>
</SplitItem>
</Split>
</WorkspaceCreationDrawer>
</Content>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,12 @@ type WorkspaceCreationKindDetailsProps = {
export const WorkspaceCreationKindDetails: React.FunctionComponent<
WorkspaceCreationKindDetailsProps
> = ({ workspaceKind }) => (
<>
{!workspaceKind && <p>Select a workspace kind to view its details here.</p>}

<div style={{ marginLeft: 'var(--pf-t--global--spacer--md)' }}>
{workspaceKind && (
<>
<Title headingLevel="h6">Workspace kind</Title>
<Title headingLevel="h3">{workspaceKind.name}</Title>
<p>{workspaceKind.description}</p>
</>
)}
</>
</div>
);
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import * as React from 'react';
import { Content, Divider, Split, SplitItem } from '@patternfly/react-core';
import { useMemo } from 'react';
import React, { useState, useRef, useMemo, useCallback } from 'react';
import { Content, Divider } from '@patternfly/react-core';
import { WorkspaceKind } from '~/shared/api/backendApiTypes';
import useWorkspaceKinds from '~/app/hooks/useWorkspaceKinds';
import { WorkspaceCreationKindDetails } from '~/app/pages/Workspaces/Creation/kind/WorkspaceCreationKindDetails';
import { WorkspaceCreationKindList } from '~/app/pages/Workspaces/Creation/kind/WorkspaceCreationKindList';
import useWorkspaceKinds from '~/app/hooks/useWorkspaceKinds';
import { WorkspaceCreationDrawer } from '~/app/pages/Workspaces/Creation/WorkspaceCreationDrawer';

interface WorkspaceCreationKindSelectionProps {
selectedKind: WorkspaceKind | undefined;
Expand All @@ -15,6 +15,26 @@ const WorkspaceCreationKindSelection: React.FunctionComponent<
WorkspaceCreationKindSelectionProps
> = ({ selectedKind, onSelect }) => {
const [workspaceKinds, loaded, error] = useWorkspaceKinds();
const [isExpanded, setIsExpanded] = useState(false);
const drawerRef = useRef<HTMLSpanElement>(undefined);

const onExpand = useCallback(() => {
if (drawerRef.current) {
drawerRef.current.focus();
}
}, []);

const onClick = useCallback(
(kind?: WorkspaceKind) => {
setIsExpanded(true);
onSelect(kind);
},
[onSelect],
);

const onCloseClick = useCallback(() => {
setIsExpanded(false);
}, []);

const kindDetailsContent = useMemo(
() => <WorkspaceCreationKindDetails workspaceKind={selectedKind} />,
Expand All @@ -31,18 +51,21 @@ const WorkspaceCreationKindSelection: React.FunctionComponent<

return (
<Content style={{ height: '100%' }}>
<p>Select a workspace kind to use for the workspace.</p>
<Divider />
<Split hasGutter>
<SplitItem isFilled>
<WorkspaceCreationKindList
allWorkspaceKinds={workspaceKinds}
selectedKind={selectedKind}
onSelect={onSelect}
/>
</SplitItem>
<SplitItem style={{ minWidth: '200px' }}>{kindDetailsContent}</SplitItem>
</Split>
<WorkspaceCreationDrawer
title="Workspace kind"
info={kindDetailsContent}
isExpanded={isExpanded}
onCloseClick={onCloseClick}
onExpand={onExpand}
>
<p>Select a workspace kind to use for the workspace.</p>
<Divider />
<WorkspaceCreationKindList
allWorkspaceKinds={workspaceKinds}
selectedKind={selectedKind}
onSelect={onClick}
/>
</WorkspaceCreationDrawer>
</Content>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { List, ListItem, Title } from '@patternfly/react-core';
import { List, ListItem } from '@patternfly/react-core';
import { WorkspacePodConfigValue } from '~/shared/api/backendApiTypes';

type WorkspaceCreationPodConfigDetailsProps = {
Expand All @@ -10,12 +10,8 @@ export const WorkspaceCreationPodConfigDetails: React.FunctionComponent<
WorkspaceCreationPodConfigDetailsProps
> = ({ workspacePodConfig }) => (
<>
{!workspacePodConfig && <p>Select a pod config to view its details here.</p>}

{workspacePodConfig && (
<>
<Title headingLevel="h6">Pod config</Title>
<Title headingLevel="h3">{workspacePodConfig.displayName}</Title>
<div style={{ marginLeft: 'var(--pf-t--global--spacer--md)' }}>
<p>{workspacePodConfig.description}</p>
<List isPlain>
{workspacePodConfig.labels.map((label) => (
Expand All @@ -24,7 +20,7 @@ export const WorkspaceCreationPodConfigDetails: React.FunctionComponent<
</ListItem>
))}
</List>
</>
</div>
)}
</>
);
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import * as React from 'react';
import React, { useCallback, useMemo, useState } from 'react';
import { Content, Divider, Split, SplitItem } from '@patternfly/react-core';
import { useMemo, useState } from 'react';
import { WorkspacePodConfigValue } from '~/shared/api/backendApiTypes';
import { WorkspaceCreationPodConfigDetails } from '~/app/pages/Workspaces/Creation/podConfig/WorkspaceCreationPodConfigDetails';
import { WorkspaceCreationPodConfigList } from '~/app/pages/Workspaces/Creation/podConfig/WorkspaceCreationPodConfigList';
import { FilterByLabels } from '~/app/pages/Workspaces/Creation/labelFilter/FilterByLabels';
import { WorkspaceCreationDrawer } from '~/app/pages/Workspaces/Creation/WorkspaceCreationDrawer';
import { WorkspacePodConfigValue } from '~/shared/api/backendApiTypes';

interface WorkspaceCreationPodConfigSelectionProps {
podConfigs: WorkspacePodConfigValue[];
Expand All @@ -16,6 +16,26 @@ const WorkspaceCreationPodConfigSelection: React.FunctionComponent<
WorkspaceCreationPodConfigSelectionProps
> = ({ podConfigs, selectedPodConfig, onSelect }) => {
const [selectedLabels, setSelectedLabels] = useState<Map<string, Set<string>>>(new Map());
const [isExpanded, setIsExpanded] = useState(false);
const drawerRef = React.useRef<HTMLSpanElement>(undefined);

const onExpand = useCallback(() => {
if (drawerRef.current) {
drawerRef.current.focus();
}
}, []);

const onClick = useCallback(
(podConfig?: WorkspacePodConfigValue) => {
setIsExpanded(true);
onSelect(podConfig);
},
[onSelect],
);

const onCloseClick = useCallback(() => {
setIsExpanded(false);
}, []);

const podConfigFilterContent = useMemo(
() => (
Expand All @@ -37,18 +57,26 @@ const WorkspaceCreationPodConfigSelection: React.FunctionComponent<
<Content style={{ height: '100%' }}>
<p>Select a pod config to use for the workspace.</p>
<Divider />
<Split hasGutter>
<SplitItem style={{ minWidth: '200px' }}>{podConfigFilterContent}</SplitItem>
<SplitItem isFilled>
<WorkspaceCreationPodConfigList
podConfigs={podConfigs}
selectedLabels={selectedLabels}
selectedPodConfig={selectedPodConfig}
onSelect={onSelect}
/>
</SplitItem>
<SplitItem style={{ minWidth: '200px' }}>{podConfigDetailsContent}</SplitItem>
</Split>

<WorkspaceCreationDrawer
title="Pod config"
info={podConfigDetailsContent}
isExpanded={isExpanded}
onCloseClick={onCloseClick}
onExpand={onExpand}
>
<Split hasGutter>
<SplitItem style={{ minWidth: '200px' }}>{podConfigFilterContent}</SplitItem>
<SplitItem isFilled>
<WorkspaceCreationPodConfigList
podConfigs={podConfigs}
selectedLabels={selectedLabels}
selectedPodConfig={selectedPodConfig}
onSelect={onClick}
/>
</SplitItem>
</Split>
</WorkspaceCreationDrawer>
</Content>
);
};
Expand Down
5 changes: 5 additions & 0 deletions workspaces/frontend/src/shared/style/MUI-theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -772,6 +772,11 @@
flex-grow: 0;
}

// TODO: Remove when https://github.com/patternfly/patternfly-react/issues/11826 is resolved.
.pf-v6-c-page__main-section .pf-v6-c-page__main-body {
height: 100%;
}

Comment on lines +776 to +779
Copy link
Member

@jenny-s51 jenny-s51 May 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that we've created our own custom implementation of a "Wizard", for the Workspace Creation, this may be the best we can do right now. We really shouldn't have to apply a CSS override for the Drawer to render at full height though.

I've opened patternfly/patternfly-react#11826 so Patternfly can handle custom component integration in Wizards better, since our use case has some pretty specific instances of components that the Wizard isn't currently built to support.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great idea! I'll follow the issue as well. Thank you for opening it!

.mui-theme .pf-v6-c-pagination {
--pf-v6-c-pagination__total-items--Display: block;
}
Expand Down