diff --git a/site/src/components/Filter/SelectFilter.stories.tsx b/site/src/components/Filter/SelectFilter.stories.tsx index 136332ccfa883..cdc68a2f6198b 100644 --- a/site/src/components/Filter/SelectFilter.stories.tsx +++ b/site/src/components/Filter/SelectFilter.stories.tsx @@ -3,7 +3,7 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { Avatar } from "components/Avatar/Avatar"; import { useState } from "react"; import { action } from "storybook/actions"; -import { expect, userEvent, within } from "storybook/test"; +import { expect, screen, userEvent, within } from "storybook/test"; import { SelectFilter, type SelectFilterOption, @@ -88,7 +88,7 @@ export const SelectingOption: Story = { const canvas = within(canvasElement); const button = canvas.getByRole("button"); await userEvent.click(button); - const option = canvas.getByText("Option 25"); + const option = screen.getByText("Option 25"); await userEvent.click(option); await expect(button).toHaveTextContent("Option 25"); }, @@ -102,7 +102,7 @@ export const UnselectingOption: Story = { const canvas = within(canvasElement); const button = canvas.getByRole("button"); await userEvent.click(button); - const menu = canvasElement.querySelector("[role=menu]")!; + const menu = screen.getByRole("menu"); const option = within(menu).getByText("Option 26"); await userEvent.click(option); await expect(button).toHaveTextContent("All options"); @@ -140,7 +140,7 @@ export const SearchingOption: Story = { const canvas = within(canvasElement); const button = canvas.getByRole("button"); await userEvent.click(button); - const search = canvas.getByLabelText("Search options"); + const search = screen.getByLabelText("Search options"); await userEvent.type(search, "option-2"); }, }; diff --git a/site/src/components/Filter/SelectFilter.tsx b/site/src/components/Filter/SelectFilter.tsx index f7354e1854f5b..387903806561f 100644 --- a/site/src/components/Filter/SelectFilter.tsx +++ b/site/src/components/Filter/SelectFilter.tsx @@ -10,9 +10,9 @@ import { SelectMenuTrigger, } from "components/SelectMenu/SelectMenu"; import { type FC, type ReactNode, useState } from "react"; +import { cn } from "utils/cn"; const BASE_WIDTH = 200; -const POPOVER_WIDTH = 320; export type SelectFilterOption = { startIcon?: ReactNode; @@ -60,15 +60,15 @@ export const SelectFilter: FC = ({ {selectFilterSearch} diff --git a/site/src/components/IconField/IconField.tsx b/site/src/components/IconField/IconField.tsx index 65632ee04e10f..4c6156899b1f1 100644 --- a/site/src/components/IconField/IconField.tsx +++ b/site/src/components/IconField/IconField.tsx @@ -3,13 +3,13 @@ import InputAdornment from "@mui/material/InputAdornment"; import TextField, { type TextFieldProps } from "@mui/material/TextField"; import { visuallyHidden } from "@mui/utils"; import { Button } from "components/Button/Button"; +import { ExternalImage } from "components/ExternalImage/ExternalImage"; +import { Loader } from "components/Loader/Loader"; import { Popover, PopoverContent, PopoverTrigger, -} from "components/deprecated/Popover/Popover"; -import { ExternalImage } from "components/ExternalImage/ExternalImage"; -import { Loader } from "components/Loader/Loader"; +} from "components/Popover/Popover"; import { ChevronDownIcon } from "lucide-react"; import { type FC, lazy, Suspense, useState } from "react"; @@ -80,24 +80,21 @@ export const IconField: FC = ({ - + - + }> { diff --git a/site/src/components/Popover/Popover.tsx b/site/src/components/Popover/Popover.tsx index b04aeb258fd7d..25a4daf01c88a 100644 --- a/site/src/components/Popover/Popover.tsx +++ b/site/src/components/Popover/Popover.tsx @@ -10,10 +10,16 @@ import { } from "react"; import { cn } from "utils/cn"; +export type PopoverContentProps = PopoverPrimitive.PopoverContentProps; + +export type PopoverTriggerProps = PopoverPrimitive.PopoverTriggerProps; + export const Popover = PopoverPrimitive.Root; export const PopoverTrigger = PopoverPrimitive.Trigger; +export const PopoverClose = PopoverPrimitive.PopoverClose; + export const PopoverContent = forwardRef< ElementRef, ComponentPropsWithoutRef @@ -23,6 +29,7 @@ export const PopoverContent = forwardRef< ref={ref} align={align} sideOffset={sideOffset} + collisionPadding={16} className={cn( `z-50 w-72 rounded-md border border-solid bg-surface-primary text-content-primary shadow-md outline-none diff --git a/site/src/components/Search/Search.tsx b/site/src/components/Search/Search.tsx index 7fecd57df2bf9..e0ce6590f22a2 100644 --- a/site/src/components/Search/Search.tsx +++ b/site/src/components/Search/Search.tsx @@ -104,14 +104,3 @@ const SearchEmptyStyles = { paddingBottom: 8, }), } satisfies Record>; - -/** - * Reusable styles for consumers of the base components - */ -export const searchStyles = { - content: { - width: 320, - padding: 0, - borderRadius: 4, - }, -} satisfies Record>; diff --git a/site/src/components/SelectMenu/SelectMenu.tsx b/site/src/components/SelectMenu/SelectMenu.tsx index ece4128769705..394ba9598299c 100644 --- a/site/src/components/SelectMenu/SelectMenu.tsx +++ b/site/src/components/SelectMenu/SelectMenu.tsx @@ -4,8 +4,10 @@ import { Button, type ButtonProps } from "components/Button/Button"; import { Popover, PopoverContent, + type PopoverContentProps, PopoverTrigger, -} from "components/deprecated/Popover/Popover"; + type PopoverTriggerProps, +} from "components/Popover/Popover"; import { SearchField, type SearchFieldProps, @@ -26,9 +28,21 @@ const SIDE_PADDING = 16; export const SelectMenu = Popover; -export const SelectMenuTrigger = PopoverTrigger; +export const SelectMenuTrigger: FC = (props) => { + return ; +}; -export const SelectMenuContent = PopoverContent; +export const SelectMenuContent: FC = (props) => { + return ( + + ); +}; type SelectMenuButtonProps = ButtonProps & { startIcon?: React.ReactNode; diff --git a/site/src/hooks/useClassName.ts b/site/src/hooks/useClassName.ts index 5155d1795a4a5..80a86e965bd96 100644 --- a/site/src/hooks/useClassName.ts +++ b/site/src/hooks/useClassName.ts @@ -2,7 +2,7 @@ import { css } from "@emotion/css"; import { type Theme, useTheme } from "@emotion/react"; import { type DependencyList, useMemo } from "react"; -export type ClassName = (cssFn: typeof css, theme: Theme) => string; +type ClassName = (cssFn: typeof css, theme: Theme) => string; /** * @deprecated This hook was used as an escape hatch to generate class names diff --git a/site/src/modules/dashboard/Navbar/DeploymentDropdown.tsx b/site/src/modules/dashboard/Navbar/DeploymentDropdown.tsx index b78e3a6954a58..7089342727c63 100644 --- a/site/src/modules/dashboard/Navbar/DeploymentDropdown.tsx +++ b/site/src/modules/dashboard/Navbar/DeploymentDropdown.tsx @@ -1,12 +1,12 @@ -import { css, type Interpolation, type Theme, useTheme } from "@emotion/react"; +import { css, type Interpolation, type Theme } from "@emotion/react"; import MenuItem from "@mui/material/MenuItem"; import { Button } from "components/Button/Button"; import { Popover, + PopoverClose, PopoverContent, PopoverTrigger, - usePopover, -} from "components/deprecated/Popover/Popover"; +} from "components/Popover/Popover"; import { ChevronDownIcon } from "lucide-react"; import { linkToAuditing } from "modules/navigation"; import type { FC } from "react"; @@ -27,8 +27,6 @@ export const DeploymentDropdown: FC = ({ canViewConnectionLog, canViewHealth, }) => { - const theme = useTheme(); - if ( !canViewAuditLog && !canViewConnectionLog && @@ -41,7 +39,7 @@ export const DeploymentDropdown: FC = ({ return ( - + - + = ({ ); }; -const classNames = { - paper: (css, theme) => css` - padding: 0; - width: 404px; - color: ${theme.palette.text.secondary}; - margin-top: 4px; - `, -} satisfies Record; - const styles = { portCount: (theme) => ({ fontSize: 12, diff --git a/site/src/pages/OrganizationSettingsPage/UserTable/EditRolesButton.tsx b/site/src/pages/OrganizationSettingsPage/UserTable/EditRolesButton.tsx index 62fa29e834f98..5d0f23219337a 100644 --- a/site/src/pages/OrganizationSettingsPage/UserTable/EditRolesButton.tsx +++ b/site/src/pages/OrganizationSettingsPage/UserTable/EditRolesButton.tsx @@ -3,11 +3,6 @@ import Tooltip from "@mui/material/Tooltip"; import type { SlimRole } from "api/typesGenerated"; import { Button } from "components/Button/Button"; import { CollapsibleSummary } from "components/CollapsibleSummary/CollapsibleSummary"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "components/deprecated/Popover/Popover"; import { HelpTooltip, HelpTooltipContent, @@ -16,6 +11,11 @@ import { HelpTooltipTitle, } from "components/HelpTooltip/HelpTooltip"; import { EditSquare } from "components/Icons/EditSquare"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "components/Popover/Popover"; import { UserIcon } from "lucide-react"; import { type FC, useEffect, useState } from "react"; @@ -130,7 +130,7 @@ const EnabledEditRolesButton: FC = ({ return ( - + + ); } @@ -165,7 +162,7 @@ const BuildParametersPopoverContent: FC = ({
{ onSubmit(buildParameters); - popover.setOpen(false); + setIsOpen(false); }} ephemeralParameters={ephemeralParameters} buildParameters={buildParameters.map( diff --git a/site/src/pages/WorkspacePage/WorkspaceActions/DebugButton.stories.tsx b/site/src/pages/WorkspacePage/WorkspaceActions/DebugButton.stories.tsx index e1e4fb4851eb0..3777abe13b9cd 100644 --- a/site/src/pages/WorkspacePage/WorkspaceActions/DebugButton.stories.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceActions/DebugButton.stories.tsx @@ -1,6 +1,6 @@ import { MockWorkspace } from "testHelpers/entities"; import type { Meta, StoryObj } from "@storybook/react-vite"; -import { expect, userEvent, waitFor, within } from "storybook/test"; +import { expect, screen, userEvent, waitFor } from "storybook/test"; import { DebugButton } from "./DebugButton"; const meta: Meta = { @@ -41,9 +41,7 @@ export const WithOpenBuildParameters: Story = { }, ], }, - play: async ({ canvasElement, step }) => { - const screen = within(canvasElement); - + play: async ({ step }) => { await step("open popover", async () => { await userEvent.click(screen.getByTestId("build-parameters-button")); await waitFor(() => diff --git a/site/src/pages/WorkspacePage/WorkspaceActions/RetryButton.stories.tsx b/site/src/pages/WorkspacePage/WorkspaceActions/RetryButton.stories.tsx index 12ff75dc64616..86d2400cab45f 100644 --- a/site/src/pages/WorkspacePage/WorkspaceActions/RetryButton.stories.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceActions/RetryButton.stories.tsx @@ -1,6 +1,10 @@ -import { MockWorkspace } from "testHelpers/entities"; +import { + MockNonClassicParameterFlowWorkspace, + MockTemplateVersionParameter6, + MockWorkspace, +} from "testHelpers/entities"; import type { Meta, StoryObj } from "@storybook/react-vite"; -import { expect, userEvent, waitFor, within } from "storybook/test"; +import { expect, screen, userEvent, waitFor } from "storybook/test"; import { RetryButton } from "./RetryButton"; const meta: Meta = { @@ -41,9 +45,7 @@ export const WithOpenBuildParameters: Story = { }, ], }, - play: async ({ canvasElement, step }) => { - const screen = within(canvasElement); - + play: async ({ step }) => { await step("open popover", async () => { await userEvent.click(screen.getByTestId("build-parameters-button")); await waitFor(() => @@ -52,3 +54,67 @@ export const WithOpenBuildParameters: Story = { }); }, }; + +export const WithOpenEphemeralBuildParameters: Story = { + args: { + enableBuildParameters: true, + workspace: MockWorkspace, + }, + parameters: { + queries: [ + { + key: ["workspace", MockWorkspace.id, "parameters"], + data: { + templateVersionRichParameters: [MockTemplateVersionParameter6], + buildParameters: [], + }, + }, + ], + }, + play: async ({ step }) => { + await step("open popover", async () => { + await userEvent.click(screen.getByTestId("build-parameters-button")); + await waitFor(() => + expect( + screen.getByText( + "These parameters only apply for a single workspace start.", + ), + ).toBeInTheDocument(), + ); + }); + }, +}; + +export const WithOpenEphemeralBuildParametersNotClassic: Story = { + args: { + enableBuildParameters: true, + workspace: MockNonClassicParameterFlowWorkspace, + }, + parameters: { + queries: [ + { + key: [ + "workspace", + MockNonClassicParameterFlowWorkspace.id, + "parameters", + ], + data: { + templateVersionRichParameters: [MockTemplateVersionParameter6], + buildParameters: [], + }, + }, + ], + }, + play: async ({ step }) => { + await step("open popover", async () => { + await userEvent.click(screen.getByTestId("build-parameters-button")); + await waitFor(() => + expect( + screen.getByText( + "This workspace has ephemeral parameters which may use a temporary value on workspace start. Configure the following parameters in workspace settings.", + ), + ).toBeInTheDocument(), + ); + }); + }, +}; diff --git a/site/src/pages/WorkspacesPage/WorkspacesButton.tsx b/site/src/pages/WorkspacesPage/WorkspacesButton.tsx index eda60e4fdc8f9..38e5adc10c2e5 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesButton.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesButton.tsx @@ -2,15 +2,15 @@ import Link from "@mui/material/Link"; import type { Template } from "api/typesGenerated"; import { Avatar } from "components/Avatar/Avatar"; import { Button } from "components/Button/Button"; +import { Loader } from "components/Loader/Loader"; +import { MenuSearch } from "components/Menu/MenuSearch"; +import { OverflowY } from "components/OverflowY/OverflowY"; import { Popover, PopoverContent, PopoverTrigger, -} from "components/deprecated/Popover/Popover"; -import { Loader } from "components/Loader/Loader"; -import { MenuSearch } from "components/Menu/MenuSearch"; -import { OverflowY } from "components/OverflowY/OverflowY"; -import { SearchEmpty, searchStyles } from "components/Search/Search"; +} from "components/Popover/Popover"; +import { SearchEmpty } from "components/Search/Search"; import { ChevronDownIcon, ExternalLinkIcon } from "lucide-react"; import { linkToTemplate, useLinks } from "modules/navigation"; import { type FC, type ReactNode, useState } from "react"; @@ -54,17 +54,15 @@ export const WorkspacesButton: FC = ({ return ( - + ({ @@ -1695,6 +1701,21 @@ const MockTemplateVersionParameter5: TypesGen.TemplateVersionParameter = { ephemeral: false, }; +export const MockTemplateVersionParameter6: TypesGen.TemplateVersionParameter = + { + name: "ephemeral_parameter", + type: "string", + form_type: "input", + description: "This is ephemeral parameter", + description_plaintext: "Markdown: This is ephemeral parameter", + default_value: "abc", + mutable: true, + icon: "/icon/folder.svg", + options: [], + required: true, + ephemeral: true, + }; + export const MockTemplateVersionVariable1: TypesGen.TemplateVersionVariable = { name: "first_variable", description: "This is first variable.",