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

fix(ws): Change label titles in Workspace Creation



Add custom rules for drawer body to have full length

Signed-off-by: Charles Thao <cthao@redhat.com>
This commit is contained in:
Charles Thao 2025-05-15 10:50:23 -04:00 committed by GitHub
parent 273cdc92d2
commit c81f412fd5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 190 additions and 62 deletions

View File

@ -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>
</>
);
};

View File

@ -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>
@ -26,5 +23,5 @@ export const WorkspaceCreationImageDetails: React.FunctionComponent<
</List>
</>
)}
</>
</div>
);

View File

@ -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[];
@ -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(
() => (
@ -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>
);
};

View File

@ -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>
);

View File

@ -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;
@ -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} />,
@ -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>
);
};

View File

@ -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 = {
@ -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) => (
@ -24,7 +20,7 @@ export const WorkspaceCreationPodConfigDetails: React.FunctionComponent<
</ListItem>
))}
</List>
</>
</div>
)}
</>
);

View File

@ -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[];
@ -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(
() => (
@ -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>
);
};

View File

@ -769,6 +769,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%;
}
.mui-theme .pf-v6-c-pagination {
--pf-v6-c-pagination__total-items--Display: block;
}