mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat(site/src/pages/TemplateBuilder): deselect modules using button in main content area instead of sidebar (#27113)
closes DEVEX-588 Prototyped in #27077, broken off into a separate PR to make this work easier to track ## changes - Reveals the previously hidden trash can icon within `ModuleConfiguration` (main content area) - Removes the "x" icons from `ModuleSelection` (sidebar) ## context @tracyjohnsonux and I decided [in Slack](https://codercom.slack.com/archives/C0AUKB54P0E/p1783456607073329?thread_ts=1783450189.570979&cid=C0AUKB54P0E) that it would be a better UX to move the deletion action from the "x" icons in the sidebar to the trash can icons in the main content area. This change has the benefits of 1. making it harder to delete modules accidentally 2. removing the responsibility of deletion from the items in `ModuleSelection` - interacting with these items will serve only to navigate to configuring that module (DEVEX-587, to be done in a separate PR) <img width="1840" height="1191" alt="image" src="https://github.com/user-attachments/assets/4571ea2f-75cf-4cd7-b626-1826eea83bdf" />
This commit is contained in:
@@ -26,6 +26,7 @@ interface ModuleSettingsStepProps {
|
||||
moduleId: string,
|
||||
variables: Record<string, string>,
|
||||
) => void;
|
||||
onRemoveModule: (moduleId: string) => void;
|
||||
}
|
||||
|
||||
function variableToField(
|
||||
@@ -107,6 +108,7 @@ export const ModuleSettingsStep: FC<ModuleSettingsStepProps> = ({
|
||||
selectedModuleIds,
|
||||
moduleVariables,
|
||||
onChangeModuleVariables,
|
||||
onRemoveModule,
|
||||
}) => {
|
||||
const { data } = useQuery(templateBuilderModules(baseId));
|
||||
const modules = data?.modules ?? [];
|
||||
@@ -157,6 +159,7 @@ export const ModuleSettingsStep: FC<ModuleSettingsStepProps> = ({
|
||||
detailsUrl={moduleDetailsUrl(mod.id)}
|
||||
fields={requiredFields}
|
||||
optionalFields={optionalFields}
|
||||
onRemove={() => onRemoveModule(mod.id)}
|
||||
/>
|
||||
|
||||
{sensitiveVars.length > 0 && (
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { expect, fn, within } from "storybook/test";
|
||||
import { expect, within } from "storybook/test";
|
||||
import { SelectionSummary } from "./SelectionSummary";
|
||||
|
||||
const meta: Meta<typeof SelectionSummary> = {
|
||||
title: "pages/TemplateBuilder/SelectionSummary",
|
||||
component: SelectionSummary,
|
||||
args: {
|
||||
onDeselectModule: fn(),
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { cva } from "class-variance-authority";
|
||||
import { XIcon } from "lucide-react";
|
||||
import { createContext, type PropsWithChildren, useContext } from "react";
|
||||
import { Avatar } from "#/components/Avatar/Avatar";
|
||||
import { Button } from "#/components/Button/Button";
|
||||
import { cn } from "#/utils/cn";
|
||||
|
||||
type Variant = "complete" | "current" | "upcoming" | null | undefined;
|
||||
@@ -24,14 +22,12 @@ type SelectionSummaryProps = {
|
||||
currentStep: number;
|
||||
selectedTemplate?: SelectedTemplate;
|
||||
selectedModules?: SelectedModule[];
|
||||
onDeselectModule: (moduleId: string) => void;
|
||||
};
|
||||
|
||||
export const SelectionSummary: React.FC<SelectionSummaryProps> = ({
|
||||
currentStep,
|
||||
selectedTemplate,
|
||||
selectedModules,
|
||||
onDeselectModule,
|
||||
}) => {
|
||||
const variant = (step: number) => {
|
||||
if (currentStep === step) return "current";
|
||||
@@ -53,10 +49,7 @@ export const SelectionSummary: React.FC<SelectionSummaryProps> = ({
|
||||
<VariantContext.Provider value={variant(2)}>
|
||||
<StepIndicator step={2}>Modules</StepIndicator>
|
||||
{selectedModules ? (
|
||||
<ModuleSelection
|
||||
modules={selectedModules}
|
||||
onDeselectModule={onDeselectModule}
|
||||
/>
|
||||
<ModuleSelection modules={selectedModules} />
|
||||
) : (
|
||||
<StepDivider />
|
||||
)}
|
||||
@@ -161,19 +154,15 @@ const BaseTemplateSelection: React.FC<BaseTemplateSelectionProps> = ({
|
||||
|
||||
type ModuleSelectionProps = {
|
||||
modules: SelectedModule[];
|
||||
onDeselectModule: (moduleId: string) => void;
|
||||
};
|
||||
|
||||
const ModuleSelection: React.FC<ModuleSelectionProps> = ({
|
||||
modules,
|
||||
onDeselectModule,
|
||||
}) => {
|
||||
const ModuleSelection: React.FC<ModuleSelectionProps> = ({ modules }) => {
|
||||
return (
|
||||
<StepDivider className="max-h-72 overflow-y-auto">
|
||||
{modules.map((module) => (
|
||||
<div
|
||||
key={module.id}
|
||||
className="group flex items-start justify-between p-1 mb-1 rounded-sm hover:bg-surface-secondary"
|
||||
className="group flex items-start justify-between p-1 mb-1 rounded-sm"
|
||||
>
|
||||
<div className="h-[1lh] content-center">
|
||||
<Avatar src={module.iconUrl} size="sm" variant="icon" />
|
||||
@@ -181,17 +170,6 @@ const ModuleSelection: React.FC<ModuleSelectionProps> = ({
|
||||
<span className="flex-1 ml-2 text-content-secondary">
|
||||
{module.name}
|
||||
</span>
|
||||
<div className="h-[1lh] content-center">
|
||||
<Button
|
||||
size="xs"
|
||||
variant="subtle"
|
||||
className="flex opacity-0 group-hover:opacity-100 focus-visible:opacity-100"
|
||||
onClick={() => onDeselectModule(module.id)}
|
||||
aria-label="Deselect module"
|
||||
>
|
||||
<XIcon className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</StepDivider>
|
||||
|
||||
@@ -162,6 +162,7 @@ export const TemplateBuilderPageView: FC<TemplateBuilderPageViewProps> = ({
|
||||
moduleVarMap,
|
||||
createError,
|
||||
handleProvisionerStatusChange,
|
||||
handleDeselectModule,
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -199,7 +200,6 @@ export const TemplateBuilderPageView: FC<TemplateBuilderPageViewProps> = ({
|
||||
? state.selectedModules
|
||||
: undefined
|
||||
}
|
||||
onDeselectModule={handleDeselectModule}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -214,6 +214,7 @@ function renderStepContent(
|
||||
moduleVarMap: Record<string, Record<string, string>>,
|
||||
createError: Error | null,
|
||||
onProvisionerStatusChange: (value: boolean | undefined) => void,
|
||||
onRemoveModule: (moduleId: string) => void,
|
||||
): ReactNode {
|
||||
switch (stepId) {
|
||||
case "base-infra":
|
||||
@@ -259,6 +260,7 @@ function renderStepContent(
|
||||
variables,
|
||||
})
|
||||
}
|
||||
onRemoveModule={onRemoveModule}
|
||||
/>
|
||||
);
|
||||
case "customizations":
|
||||
|
||||
Reference in New Issue
Block a user