Skip to content
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

frontend: Refactor workflows to use new layout #3159

Merged
merged 17 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from 16 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
21 changes: 17 additions & 4 deletions frontend/packages/core/src/AppProvider/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ const ClutchApp = ({

return (
<Router>
<Theme>
<Theme useWorkflowLayout={appConfiguration?.useWorkflowLayout}>
<div id="App">
<ApplicationContext.Provider value={appContextValue}>
<UserPreferencesProvider>
Expand Down Expand Up @@ -217,9 +217,22 @@ const ClutchApp = ({
: workflow.displayName;

const workflowLayoutProps: LayoutProps = {
...route.layoutProps,
heading: route.layoutProps?.heading || heading,
workflow,
title: heading,
subtitle: route.description,
variant:
route.layoutProps?.variant === null ||
route.layoutProps?.variant !== undefined
? route.layoutProps?.variant
: workflow.defaultLayoutProps?.variant,
breadcrumbsOnly:
route.layoutProps?.breadcrumbsOnly ??
workflow.defaultLayoutProps?.breadcrumbsOnly ??
false,
hideHeader:
route.layoutProps?.hideHeader ??
workflow.defaultLayoutProps?.hideHeader ??
false,
};

const workflowRouteComponent = (
Expand All @@ -240,7 +253,7 @@ const ClutchApp = ({
key={workflow.path}
path={`${route.path.replace(/^\/+/, "").replace(/\/+$/, "")}`}
element={
appConfiguration?.enableWorkflowLayout ? (
appConfiguration?.useWorkflowLayout ? (
<WorkflowLayout {...workflowLayoutProps}>
{workflowRouteComponent}
</WorkflowLayout>
Expand Down
11 changes: 9 additions & 2 deletions frontend/packages/core/src/AppProvider/themes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,21 @@ declare module "@mui/material/styles" {

const useTheme = () => useMuiTheme() as MuiTheme;

const Theme: React.FC = ({ children }) => {
interface ThemeProps {
useWorkflowLayout?: boolean;
}

const Theme: React.FC<ThemeProps> = ({ useWorkflowLayout = false, children }) => {
// Uncomment to use dark mode
/* // Detect system color mode
const prefersDarkMode =
window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches; */
const prefersDarkMode = false;
return (
<ThemeProvider variant={prefersDarkMode ? THEME_VARIANTS.dark : THEME_VARIANTS.light}>
<ThemeProvider
variant={prefersDarkMode ? THEME_VARIANTS.dark : THEME_VARIANTS.light}
useWorkflowLayout={useWorkflowLayout}
>
{children}
</ThemeProvider>
);
Expand Down
21 changes: 17 additions & 4 deletions frontend/packages/core/src/AppProvider/workflow.tsx
jecr marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,17 @@ interface WorkflowShortlinkConfiguration {
shortLink?: boolean;
}

export interface Workflow extends BaseWorkflowConfiguration, WorkflowShortlinkConfiguration {
interface WorkflowLayoutConfiguration {
/**
* (Optional) property to pass the defined layout properties to all of its defined routes
*/
defaultLayoutProps?: Omit<LayoutProps, "workflow" | "title" | "subtitle">;
}

export interface Workflow
extends BaseWorkflowConfiguration,
WorkflowShortlinkConfiguration,
WorkflowLayoutConfiguration {
/**
* An optional property that is set via the config and allows for the display of an icon given a path,
* this will override the default avatar.
Expand All @@ -68,7 +78,8 @@ export interface Workflow extends BaseWorkflowConfiguration, WorkflowShortlinkCo

export interface WorkflowConfiguration
extends BaseWorkflowConfiguration,
WorkflowShortlinkConfiguration {
WorkflowShortlinkConfiguration,
WorkflowLayoutConfiguration {
shortLink?: boolean;
routes: {
[key: string]: Route;
Expand All @@ -91,8 +102,10 @@ export interface Route {
* If this is not set the route will always be registered.
*/
featureFlag?: string;

layoutProps?: Omit<LayoutProps, "workflow">;
/**
* (Optional) property to define layout properties for a single route
*/
layoutProps?: Omit<LayoutProps, "workflow" | "title" | "subtitle">;
}

export interface ConfiguredRoute extends Route {
Expand Down
23 changes: 20 additions & 3 deletions frontend/packages/core/src/Theme/theme.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ declare module "@emotion/react" {
declare module "@mui/material/styles" {
interface Theme {
clutch: {
useWorkflowLayout: boolean;
spacing: {
none: number;
xs: number;
Expand All @@ -27,10 +28,14 @@ declare module "@mui/material/styles" {
lg: number;
xl: number;
};
layout: {
gutter: string;
};
};
}
interface ThemeOptions {
clutch: {
useWorkflowLayout: boolean;
spacing: {
none: number;
xs: number;
Expand All @@ -40,19 +45,23 @@ declare module "@mui/material/styles" {
lg: number;
xl: number;
};
layout: {
gutter: string;
};
};
}
}

// Create a Material UI theme is propagated to all children.
const createTheme = (variant: ThemeVariant): MuiTheme => {
const createTheme = (variant: ThemeVariant, useWorkflowLayout: boolean): MuiTheme => {
return createMuiTheme({
colors: clutchColors(variant),
palette: palette(variant),
// `8` is the default scaling factor in MUI, we are setting it again to make it explicit
// https://v5.mui.com/material-ui/customization/spacing/
spacing: 8,
clutch: {
useWorkflowLayout,
spacing: {
none: 0,
xs: 0.5,
Expand All @@ -62,6 +71,9 @@ const createTheme = (variant: ThemeVariant): MuiTheme => {
lg: 4,
xl: 5,
},
layout: {
gutter: useWorkflowLayout ? "0px" : "24px",
},
},
transitions: {
// https://material-ui.com/getting-started/faq/#how-can-i-disable-transitions-globally
Expand Down Expand Up @@ -114,12 +126,17 @@ const createTheme = (variant: ThemeVariant): MuiTheme => {

interface ThemeProps {
variant?: ThemeVariant;
useWorkflowLayout?: boolean;
children: React.ReactNode;
}

const ThemeProvider = ({ children, variant = THEME_VARIANTS.light }: ThemeProps) => (
const ThemeProvider = ({
children,
useWorkflowLayout = false,
variant = THEME_VARIANTS.light,
}: ThemeProps) => (
<StyledEngineProvider injectFirst>
<MuiThemeProvider theme={createTheme(variant)}>
<MuiThemeProvider theme={createTheme(variant, useWorkflowLayout)}>
<CssBaseline />
{children}
</MuiThemeProvider>
Expand Down
2 changes: 1 addition & 1 deletion frontend/packages/core/src/Types/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ export interface AppConfiguration {
/** Supports a react node or a string representing a public assets path */
logo?: React.ReactNode | string;
banners?: AppBanners;
enableWorkflowLayout?: boolean;
useWorkflowLayout?: boolean;
}
75 changes: 50 additions & 25 deletions frontend/packages/core/src/WorkflowLayout/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React from "react";
import { matchPath } from "react-router";
import { matchPath, useParams } from "react-router-dom";
import type { Interpolation } from "@emotion/styled";
import type { CSSObject, Theme } from "@mui/material";
import { alpha } from "@mui/material";

import type { Workflow } from "../AppProvider/workflow";
import Breadcrumbs from "../Breadcrumbs";
Expand All @@ -10,12 +11,14 @@ import styled from "../styled";
import { Typography } from "../typography";
import { generateBreadcrumbsEntries } from "../utils";

export type LayoutVariant = "standard" | "wizard" | "custom";
export type LayoutVariant = "standard" | "wizard";

export type LayoutProps = {
workflow: Workflow;
variant?: LayoutVariant;
heading?: string | React.ReactElement;
variant?: LayoutVariant | null;
title?: string | ((params: Record<string, string>) => string);
subtitle?: string;
breadcrumbsOnly?: boolean;
hideHeader?: boolean;
};

Expand Down Expand Up @@ -43,8 +46,6 @@ const getContainerVariantStyles = (variant: LayoutVariant, theme: Theme) => {
padding: theme.spacing(theme.clutch.spacing.lg, theme.clutch.spacing.none),
margin: theme.spacing(theme.clutch.spacing.none, "auto"),
},
// No styles
custom: {},
};
return layoutVariantStylesMap[variant];
};
Expand All @@ -63,49 +64,73 @@ const PageHeader = styled("div")(({ $variant, theme }: StyledVariantComponentPro
width: "100%",
}));

const PageHeaderMainContainer = styled("div")({
const PageHeaderBreadcrumbsWrapper = styled("div")(({ theme }: { theme: Theme }) => ({
marginBottom: theme.spacing(theme.clutch.spacing.xs),
}));

const PageHeaderMainContainer = styled("div")(({ theme }: { theme: Theme }) => ({
display: "flex",
alignItems: "center",
height: "70px",
marginBottom: theme.spacing(theme.clutch.spacing.sm),
}));

const PageHeaderInformation = styled("div")({
display: "flex",
flexDirection: "column",
justifyContent: "space-evenly",
height: "100%",
});

const Heading = styled(Typography)({
const Title = styled(Typography)({
lineHeight: 1,
});

const Subtitle = styled(Typography)(({ theme }: { theme: Theme }) => ({
color: alpha(theme.colors.neutral[900], 0.45),
}));

const WorkflowLayout = ({
workflow,
variant = "standard",
heading = null,
variant = null,
title = null,
subtitle = null,
breadcrumbsOnly = false,
hideHeader = false,
children,
}: React.PropsWithChildren<LayoutProps>) => {
const params = useParams();
const location = useLocation();

if (variant === null) {
return <>{children}</>;
}

const workflowPaths = workflow.routes.map(({ path }) => `/${workflow.path}/${path}`);
const breadcrumbsEntries = generateBreadcrumbsEntries(
location,
(url: string) =>
`/${workflow.path}` !== url &&
!workflowPaths.includes(url) &&
!workflowPaths.find(path => !!matchPath({ path }, url))
url => !!workflowPaths.find(path => !!matchPath({ path }, url))
septum marked this conversation as resolved.
Show resolved Hide resolved
);

return (
<LayoutContainer $variant={variant}>
{!hideHeader && (
<PageHeader $variant={variant}>
<Breadcrumbs entries={breadcrumbsEntries} />
<PageHeaderMainContainer>
{heading && (
<>
{React.isValidElement(heading) ? (
heading
) : (
<Heading variant="h2">{heading}</Heading>
<PageHeaderBreadcrumbsWrapper>
<Breadcrumbs entries={breadcrumbsEntries} />
</PageHeaderBreadcrumbsWrapper>
{!breadcrumbsOnly && (title || subtitle) && (
<PageHeaderMainContainer>
<PageHeaderInformation>
{title && (
<Title variant="h2" textTransform="capitalize">
{typeof title === "function" ? title(params) : title}
</Title>
)}
</>
)}
</PageHeaderMainContainer>
{subtitle && <Subtitle variant="subtitle2">{subtitle}</Subtitle>}
</PageHeaderInformation>
</PageHeaderMainContainer>
)}
</PageHeader>
)}
{children}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import type { Location } from "history";
import type { BreadcrumbEntry } from "../Breadcrumbs";

const generateBreadcrumbsEntries = (location: Location, validateUrl: (url: string) => boolean) => {
const labels = location.pathname
const labels = decodeURIComponent(location.pathname)
.split("/")
.slice(1, location.pathname.endsWith("/") ? -1 : undefined);

const entries: Array<BreadcrumbEntry> = [{ label: "Home", url: "/" }].concat(
labels.map((label, index) => {
let url = `/${labels.slice(0, index + 1).join("/")}`;

if (validateUrl(url)) {
if (!validateUrl(url)) {
url = undefined;
}

Expand Down
12 changes: 7 additions & 5 deletions frontend/packages/wizard/src/wizard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
useLocation,
useNavigate,
useSearchParams,
useTheme,
WizardContext,
} from "@clutch-sh/core";
import type { ManagerLayout } from "@clutch-sh/data-layout";
Expand Down Expand Up @@ -71,10 +72,10 @@ const Header = styled(Grid)<{ $orientation: MuiStepperProps["orientation"] }>(
);

const Container = styled(MuiContainer)<{ $width: ContainerProps["width"] }>(
{
paddingBlock: "24px 32px",
height: "calc(100% - 56px)",
},
({ theme }: { theme: Theme }) => ({
padding: theme.clutch.layout.gutter,
height: "100%",
}),
props => ({
width: props.$width === "full" ? "100%" : "800px",
})
Expand Down Expand Up @@ -126,6 +127,7 @@ const Wizard = ({
const locationState = useLocation().state as { origin?: string };
const navigate = useNavigate();
const [origin] = React.useState(locationState?.origin);
const theme = useTheme();

const updateStepData = (stepName: string, data: object) => {
setWizardStepData(prevState => {
Expand Down Expand Up @@ -253,7 +255,7 @@ const Wizard = ({
return (
<Container $width={width} maxWidth={false} className={className}>
<MaxHeightGrid container alignItems="stretch">
{heading && (
{!theme.clutch.useWorkflowLayout && heading && (
<Header item $orientation={orientation}>
{React.isValidElement(heading) ? (
heading
Expand Down
Loading
Loading