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:
parent
273cdc92d2
commit
c81f412fd5
|
@ -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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
|
@ -9,12 +9,9 @@ type WorkspaceCreationImageDetailsProps = {
|
||||||
export const WorkspaceCreationImageDetails: React.FunctionComponent<
|
export const WorkspaceCreationImageDetails: React.FunctionComponent<
|
||||||
WorkspaceCreationImageDetailsProps
|
WorkspaceCreationImageDetailsProps
|
||||||
> = ({ workspaceImage }) => (
|
> = ({ workspaceImage }) => (
|
||||||
<>
|
<div style={{ marginLeft: 'var(--pf-t--global--spacer--md)' }}>
|
||||||
{!workspaceImage && <p>Select an image to view its details here.</p>}
|
|
||||||
|
|
||||||
{workspaceImage && (
|
{workspaceImage && (
|
||||||
<>
|
<>
|
||||||
<Title headingLevel="h6">Image</Title>
|
|
||||||
<Title headingLevel="h3">{workspaceImage.displayName}</Title>
|
<Title headingLevel="h3">{workspaceImage.displayName}</Title>
|
||||||
<br />
|
<br />
|
||||||
<List isPlain>
|
<List isPlain>
|
||||||
|
@ -26,5 +23,5 @@ export const WorkspaceCreationImageDetails: React.FunctionComponent<
|
||||||
</List>
|
</List>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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 { Content, Divider, Split, SplitItem } from '@patternfly/react-core';
|
||||||
import { useMemo, useState } from 'react';
|
|
||||||
import { WorkspaceCreationImageDetails } from '~/app/pages/Workspaces/Creation/image/WorkspaceCreationImageDetails';
|
import { WorkspaceCreationImageDetails } from '~/app/pages/Workspaces/Creation/image/WorkspaceCreationImageDetails';
|
||||||
import { WorkspaceCreationImageList } from '~/app/pages/Workspaces/Creation/image/WorkspaceCreationImageList';
|
import { WorkspaceCreationImageList } from '~/app/pages/Workspaces/Creation/image/WorkspaceCreationImageList';
|
||||||
import { FilterByLabels } from '~/app/pages/Workspaces/Creation/labelFilter/FilterByLabels';
|
import { FilterByLabels } from '~/app/pages/Workspaces/Creation/labelFilter/FilterByLabels';
|
||||||
import { WorkspaceImageConfigValue } from '~/shared/api/backendApiTypes';
|
import { WorkspaceImageConfigValue } from '~/shared/api/backendApiTypes';
|
||||||
|
import { WorkspaceCreationDrawer } from '~/app/pages/Workspaces/Creation/WorkspaceCreationDrawer';
|
||||||
|
|
||||||
interface WorkspaceCreationImageSelectionProps {
|
interface WorkspaceCreationImageSelectionProps {
|
||||||
images: WorkspaceImageConfigValue[];
|
images: WorkspaceImageConfigValue[];
|
||||||
|
@ -16,6 +16,26 @@ const WorkspaceCreationImageSelection: React.FunctionComponent<
|
||||||
WorkspaceCreationImageSelectionProps
|
WorkspaceCreationImageSelectionProps
|
||||||
> = ({ images, selectedImage, onSelect }) => {
|
> = ({ images, selectedImage, onSelect }) => {
|
||||||
const [selectedLabels, setSelectedLabels] = useState<Map<string, Set<string>>>(new Map());
|
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(
|
const imageFilterContent = useMemo(
|
||||||
() => (
|
() => (
|
||||||
|
@ -37,18 +57,25 @@ const WorkspaceCreationImageSelection: React.FunctionComponent<
|
||||||
<Content style={{ height: '100%' }}>
|
<Content style={{ height: '100%' }}>
|
||||||
<p>Select a workspace image and image version to use for the workspace.</p>
|
<p>Select a workspace image and image version to use for the workspace.</p>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Split hasGutter>
|
<WorkspaceCreationDrawer
|
||||||
<SplitItem style={{ minWidth: '200px' }}>{imageFilterContent}</SplitItem>
|
title="Image"
|
||||||
<SplitItem isFilled>
|
info={imageDetailsContent}
|
||||||
<WorkspaceCreationImageList
|
isExpanded={isExpanded}
|
||||||
images={images}
|
onCloseClick={onCloseClick}
|
||||||
selectedLabels={selectedLabels}
|
onExpand={onExpand}
|
||||||
selectedImage={selectedImage}
|
>
|
||||||
onSelect={onSelect}
|
<Split hasGutter>
|
||||||
/>
|
<SplitItem style={{ minWidth: '200px' }}>{imageFilterContent}</SplitItem>
|
||||||
</SplitItem>
|
<SplitItem isFilled>
|
||||||
<SplitItem style={{ minWidth: '200px' }}>{imageDetailsContent}</SplitItem>
|
<WorkspaceCreationImageList
|
||||||
</Split>
|
images={images}
|
||||||
|
selectedLabels={selectedLabels}
|
||||||
|
selectedImage={selectedImage}
|
||||||
|
onSelect={onClick}
|
||||||
|
/>
|
||||||
|
</SplitItem>
|
||||||
|
</Split>
|
||||||
|
</WorkspaceCreationDrawer>
|
||||||
</Content>
|
</Content>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,15 +9,12 @@ type WorkspaceCreationKindDetailsProps = {
|
||||||
export const WorkspaceCreationKindDetails: React.FunctionComponent<
|
export const WorkspaceCreationKindDetails: React.FunctionComponent<
|
||||||
WorkspaceCreationKindDetailsProps
|
WorkspaceCreationKindDetailsProps
|
||||||
> = ({ workspaceKind }) => (
|
> = ({ workspaceKind }) => (
|
||||||
<>
|
<div style={{ marginLeft: 'var(--pf-t--global--spacer--md)' }}>
|
||||||
{!workspaceKind && <p>Select a workspace kind to view its details here.</p>}
|
|
||||||
|
|
||||||
{workspaceKind && (
|
{workspaceKind && (
|
||||||
<>
|
<>
|
||||||
<Title headingLevel="h6">Workspace kind</Title>
|
|
||||||
<Title headingLevel="h3">{workspaceKind.name}</Title>
|
<Title headingLevel="h3">{workspaceKind.name}</Title>
|
||||||
<p>{workspaceKind.description}</p>
|
<p>{workspaceKind.description}</p>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import * as React from 'react';
|
import React, { useState, useRef, useMemo, useCallback } from 'react';
|
||||||
import { Content, Divider, Split, SplitItem } from '@patternfly/react-core';
|
import { Content, Divider } from '@patternfly/react-core';
|
||||||
import { useMemo } from 'react';
|
|
||||||
import { WorkspaceKind } from '~/shared/api/backendApiTypes';
|
import { WorkspaceKind } from '~/shared/api/backendApiTypes';
|
||||||
|
import useWorkspaceKinds from '~/app/hooks/useWorkspaceKinds';
|
||||||
import { WorkspaceCreationKindDetails } from '~/app/pages/Workspaces/Creation/kind/WorkspaceCreationKindDetails';
|
import { WorkspaceCreationKindDetails } from '~/app/pages/Workspaces/Creation/kind/WorkspaceCreationKindDetails';
|
||||||
import { WorkspaceCreationKindList } from '~/app/pages/Workspaces/Creation/kind/WorkspaceCreationKindList';
|
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 {
|
interface WorkspaceCreationKindSelectionProps {
|
||||||
selectedKind: WorkspaceKind | undefined;
|
selectedKind: WorkspaceKind | undefined;
|
||||||
|
@ -15,6 +15,26 @@ const WorkspaceCreationKindSelection: React.FunctionComponent<
|
||||||
WorkspaceCreationKindSelectionProps
|
WorkspaceCreationKindSelectionProps
|
||||||
> = ({ selectedKind, onSelect }) => {
|
> = ({ selectedKind, onSelect }) => {
|
||||||
const [workspaceKinds, loaded, error] = useWorkspaceKinds();
|
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(
|
const kindDetailsContent = useMemo(
|
||||||
() => <WorkspaceCreationKindDetails workspaceKind={selectedKind} />,
|
() => <WorkspaceCreationKindDetails workspaceKind={selectedKind} />,
|
||||||
|
@ -31,18 +51,21 @@ const WorkspaceCreationKindSelection: React.FunctionComponent<
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Content style={{ height: '100%' }}>
|
<Content style={{ height: '100%' }}>
|
||||||
<p>Select a workspace kind to use for the workspace.</p>
|
<WorkspaceCreationDrawer
|
||||||
<Divider />
|
title="Workspace kind"
|
||||||
<Split hasGutter>
|
info={kindDetailsContent}
|
||||||
<SplitItem isFilled>
|
isExpanded={isExpanded}
|
||||||
<WorkspaceCreationKindList
|
onCloseClick={onCloseClick}
|
||||||
allWorkspaceKinds={workspaceKinds}
|
onExpand={onExpand}
|
||||||
selectedKind={selectedKind}
|
>
|
||||||
onSelect={onSelect}
|
<p>Select a workspace kind to use for the workspace.</p>
|
||||||
/>
|
<Divider />
|
||||||
</SplitItem>
|
<WorkspaceCreationKindList
|
||||||
<SplitItem style={{ minWidth: '200px' }}>{kindDetailsContent}</SplitItem>
|
allWorkspaceKinds={workspaceKinds}
|
||||||
</Split>
|
selectedKind={selectedKind}
|
||||||
|
onSelect={onClick}
|
||||||
|
/>
|
||||||
|
</WorkspaceCreationDrawer>
|
||||||
</Content>
|
</Content>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
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';
|
import { WorkspacePodConfigValue } from '~/shared/api/backendApiTypes';
|
||||||
|
|
||||||
type WorkspaceCreationPodConfigDetailsProps = {
|
type WorkspaceCreationPodConfigDetailsProps = {
|
||||||
|
@ -10,12 +10,8 @@ export const WorkspaceCreationPodConfigDetails: React.FunctionComponent<
|
||||||
WorkspaceCreationPodConfigDetailsProps
|
WorkspaceCreationPodConfigDetailsProps
|
||||||
> = ({ workspacePodConfig }) => (
|
> = ({ workspacePodConfig }) => (
|
||||||
<>
|
<>
|
||||||
{!workspacePodConfig && <p>Select a pod config to view its details here.</p>}
|
|
||||||
|
|
||||||
{workspacePodConfig && (
|
{workspacePodConfig && (
|
||||||
<>
|
<div style={{ marginLeft: 'var(--pf-t--global--spacer--md)' }}>
|
||||||
<Title headingLevel="h6">Pod config</Title>
|
|
||||||
<Title headingLevel="h3">{workspacePodConfig.displayName}</Title>
|
|
||||||
<p>{workspacePodConfig.description}</p>
|
<p>{workspacePodConfig.description}</p>
|
||||||
<List isPlain>
|
<List isPlain>
|
||||||
{workspacePodConfig.labels.map((label) => (
|
{workspacePodConfig.labels.map((label) => (
|
||||||
|
@ -24,7 +20,7 @@ export const WorkspaceCreationPodConfigDetails: React.FunctionComponent<
|
||||||
</ListItem>
|
</ListItem>
|
||||||
))}
|
))}
|
||||||
</List>
|
</List>
|
||||||
</>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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 { 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 { WorkspaceCreationPodConfigDetails } from '~/app/pages/Workspaces/Creation/podConfig/WorkspaceCreationPodConfigDetails';
|
||||||
import { WorkspaceCreationPodConfigList } from '~/app/pages/Workspaces/Creation/podConfig/WorkspaceCreationPodConfigList';
|
import { WorkspaceCreationPodConfigList } from '~/app/pages/Workspaces/Creation/podConfig/WorkspaceCreationPodConfigList';
|
||||||
import { FilterByLabels } from '~/app/pages/Workspaces/Creation/labelFilter/FilterByLabels';
|
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 {
|
interface WorkspaceCreationPodConfigSelectionProps {
|
||||||
podConfigs: WorkspacePodConfigValue[];
|
podConfigs: WorkspacePodConfigValue[];
|
||||||
|
@ -16,6 +16,26 @@ const WorkspaceCreationPodConfigSelection: React.FunctionComponent<
|
||||||
WorkspaceCreationPodConfigSelectionProps
|
WorkspaceCreationPodConfigSelectionProps
|
||||||
> = ({ podConfigs, selectedPodConfig, onSelect }) => {
|
> = ({ podConfigs, selectedPodConfig, onSelect }) => {
|
||||||
const [selectedLabels, setSelectedLabels] = useState<Map<string, Set<string>>>(new Map());
|
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(
|
const podConfigFilterContent = useMemo(
|
||||||
() => (
|
() => (
|
||||||
|
@ -37,18 +57,26 @@ const WorkspaceCreationPodConfigSelection: React.FunctionComponent<
|
||||||
<Content style={{ height: '100%' }}>
|
<Content style={{ height: '100%' }}>
|
||||||
<p>Select a pod config to use for the workspace.</p>
|
<p>Select a pod config to use for the workspace.</p>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Split hasGutter>
|
|
||||||
<SplitItem style={{ minWidth: '200px' }}>{podConfigFilterContent}</SplitItem>
|
<WorkspaceCreationDrawer
|
||||||
<SplitItem isFilled>
|
title="Pod config"
|
||||||
<WorkspaceCreationPodConfigList
|
info={podConfigDetailsContent}
|
||||||
podConfigs={podConfigs}
|
isExpanded={isExpanded}
|
||||||
selectedLabels={selectedLabels}
|
onCloseClick={onCloseClick}
|
||||||
selectedPodConfig={selectedPodConfig}
|
onExpand={onExpand}
|
||||||
onSelect={onSelect}
|
>
|
||||||
/>
|
<Split hasGutter>
|
||||||
</SplitItem>
|
<SplitItem style={{ minWidth: '200px' }}>{podConfigFilterContent}</SplitItem>
|
||||||
<SplitItem style={{ minWidth: '200px' }}>{podConfigDetailsContent}</SplitItem>
|
<SplitItem isFilled>
|
||||||
</Split>
|
<WorkspaceCreationPodConfigList
|
||||||
|
podConfigs={podConfigs}
|
||||||
|
selectedLabels={selectedLabels}
|
||||||
|
selectedPodConfig={selectedPodConfig}
|
||||||
|
onSelect={onClick}
|
||||||
|
/>
|
||||||
|
</SplitItem>
|
||||||
|
</Split>
|
||||||
|
</WorkspaceCreationDrawer>
|
||||||
</Content>
|
</Content>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -769,6 +769,11 @@
|
||||||
flex-grow: 0;
|
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 {
|
.mui-theme .pf-v6-c-pagination {
|
||||||
--pf-v6-c-pagination__total-items--Display: block;
|
--pf-v6-c-pagination__total-items--Display: block;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue