mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
refactor: replace deprecated Popovers which open on click (#19709)
for #19397 dependency: #19635 This PR specifically covers components which open a popover that's triggered by a click, rather than a hover. --------- Co-authored-by: ケイラ <mckayla@hey.com>
This commit is contained in:
@@ -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<HTMLElement>("[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");
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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<SelectFilterProps> = ({
|
||||
</SelectMenuButton>
|
||||
</SelectMenuTrigger>
|
||||
<SelectMenuContent
|
||||
horizontal="right"
|
||||
css={{
|
||||
"& .MuiPaper-root": {
|
||||
// When including selectFilterSearch, we aim for the width to be as
|
||||
// wide as possible.
|
||||
width: selectFilterSearch ? "100%" : undefined,
|
||||
maxWidth: POPOVER_WIDTH,
|
||||
minWidth: width,
|
||||
},
|
||||
align="end"
|
||||
className={cn([
|
||||
// When including selectFilterSearch, we aim for the width to be as
|
||||
// wide as possible.
|
||||
selectFilterSearch && "w-full",
|
||||
"max-w-[320px]",
|
||||
])}
|
||||
style={{
|
||||
minWidth: width,
|
||||
}}
|
||||
>
|
||||
{selectFilterSearch}
|
||||
|
||||
@@ -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<IconFieldProps> = ({
|
||||
|
||||
<Global
|
||||
styles={css`
|
||||
em-emoji-picker {
|
||||
--rgb-background: ${theme.palette.background.paper};
|
||||
--rgb-input: ${theme.palette.primary.main};
|
||||
--rgb-color: ${theme.palette.text.primary};
|
||||
|
||||
// Hack to prevent the right side from being cut off
|
||||
width: 350px;
|
||||
}
|
||||
`}
|
||||
em-emoji-picker {
|
||||
--rgb-background: ${theme.palette.background.paper};
|
||||
--rgb-input: ${theme.palette.primary.main};
|
||||
--rgb-color: ${theme.palette.text.primary};
|
||||
}
|
||||
`}
|
||||
/>
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="outline" size="lg" className="flex-shrink-0">
|
||||
Emoji
|
||||
<ChevronDownIcon />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent id="emoji" horizontal="right">
|
||||
<PopoverContent id="emoji" side="bottom" align="end" className="w-min">
|
||||
<Suspense fallback={<Loader />}>
|
||||
<EmojiPicker
|
||||
onEmojiSelect={(emoji) => {
|
||||
|
||||
@@ -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<typeof PopoverPrimitive.Content>,
|
||||
ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
@@ -24,9 +26,21 @@ import { cn } from "utils/cn";
|
||||
|
||||
export const SelectMenu = Popover;
|
||||
|
||||
export const SelectMenuTrigger = PopoverTrigger;
|
||||
export const SelectMenuTrigger: FC<PopoverTriggerProps> = (props) => {
|
||||
return <PopoverTrigger asChild {...props} />;
|
||||
};
|
||||
|
||||
export const SelectMenuContent = PopoverContent;
|
||||
export const SelectMenuContent: FC<PopoverContentProps> = (props) => {
|
||||
return (
|
||||
<PopoverContent
|
||||
{...props}
|
||||
className={cn(
|
||||
"w-auto bg-surface-secondary border-surface-quaternary overflow-y-auto text-sm",
|
||||
props.className,
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
type SelectMenuButtonProps = ButtonProps & {
|
||||
startIcon?: React.ReactNode;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<DeploymentDropdownProps> = ({
|
||||
canViewConnectionLog,
|
||||
canViewHealth,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
if (
|
||||
!canViewAuditLog &&
|
||||
!canViewConnectionLog &&
|
||||
@@ -41,7 +39,7 @@ export const DeploymentDropdown: FC<DeploymentDropdownProps> = ({
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="outline" size="lg">
|
||||
Admin settings
|
||||
<ChevronDownIcon className="text-content-primary !size-icon-xs" />
|
||||
@@ -49,14 +47,8 @@ export const DeploymentDropdown: FC<DeploymentDropdownProps> = ({
|
||||
</PopoverTrigger>
|
||||
|
||||
<PopoverContent
|
||||
horizontal="right"
|
||||
css={{
|
||||
".MuiPaper-root": {
|
||||
minWidth: "auto",
|
||||
width: 180,
|
||||
boxShadow: theme.shadows[6],
|
||||
},
|
||||
}}
|
||||
align="end"
|
||||
className="bg-surface-secondary border-surface-quaternary w-[180px] min-w-auto"
|
||||
>
|
||||
<DeploymentDropdownContent
|
||||
canViewDeployment={canViewDeployment}
|
||||
@@ -77,61 +69,54 @@ const DeploymentDropdownContent: FC<DeploymentDropdownProps> = ({
|
||||
canViewHealth,
|
||||
canViewConnectionLog,
|
||||
}) => {
|
||||
const popover = usePopover();
|
||||
|
||||
const onPopoverClose = () => popover.setOpen(false);
|
||||
|
||||
return (
|
||||
<nav>
|
||||
{canViewDeployment && (
|
||||
<MenuItem
|
||||
component={NavLink}
|
||||
to="/deployment"
|
||||
css={styles.menuItem}
|
||||
onClick={onPopoverClose}
|
||||
>
|
||||
Deployment
|
||||
</MenuItem>
|
||||
<PopoverClose asChild>
|
||||
<MenuItem component={NavLink} to="/deployment" css={styles.menuItem}>
|
||||
Deployment
|
||||
</MenuItem>
|
||||
</PopoverClose>
|
||||
)}
|
||||
{canViewOrganizations && (
|
||||
<MenuItem
|
||||
component={NavLink}
|
||||
to="/organizations"
|
||||
css={styles.menuItem}
|
||||
onClick={onPopoverClose}
|
||||
>
|
||||
Organizations
|
||||
</MenuItem>
|
||||
<PopoverClose asChild>
|
||||
<MenuItem
|
||||
component={NavLink}
|
||||
to="/organizations"
|
||||
css={styles.menuItem}
|
||||
>
|
||||
Organizations
|
||||
</MenuItem>
|
||||
</PopoverClose>
|
||||
)}
|
||||
{canViewAuditLog && (
|
||||
<MenuItem
|
||||
component={NavLink}
|
||||
to={linkToAuditing}
|
||||
css={styles.menuItem}
|
||||
onClick={onPopoverClose}
|
||||
>
|
||||
Audit Logs
|
||||
</MenuItem>
|
||||
<PopoverClose asChild>
|
||||
<MenuItem
|
||||
component={NavLink}
|
||||
to={linkToAuditing}
|
||||
css={styles.menuItem}
|
||||
>
|
||||
Audit Logs
|
||||
</MenuItem>
|
||||
</PopoverClose>
|
||||
)}
|
||||
{canViewConnectionLog && (
|
||||
<MenuItem
|
||||
component={NavLink}
|
||||
to="/connectionlog"
|
||||
css={styles.menuItem}
|
||||
onClick={onPopoverClose}
|
||||
>
|
||||
Connection Logs
|
||||
</MenuItem>
|
||||
<PopoverClose asChild>
|
||||
<MenuItem
|
||||
component={NavLink}
|
||||
to="/connectionlog"
|
||||
css={styles.menuItem}
|
||||
>
|
||||
Connection Logs
|
||||
</MenuItem>
|
||||
</PopoverClose>
|
||||
)}
|
||||
{canViewHealth && (
|
||||
<MenuItem
|
||||
component={NavLink}
|
||||
to="/health"
|
||||
css={styles.menuItem}
|
||||
onClick={onPopoverClose}
|
||||
>
|
||||
Healthcheck
|
||||
</MenuItem>
|
||||
<PopoverClose asChild>
|
||||
<MenuItem component={NavLink} to="/health" css={styles.menuItem}>
|
||||
Healthcheck
|
||||
</MenuItem>
|
||||
</PopoverClose>
|
||||
)}
|
||||
</nav>
|
||||
);
|
||||
@@ -139,17 +124,17 @@ const DeploymentDropdownContent: FC<DeploymentDropdownProps> = ({
|
||||
|
||||
const styles = {
|
||||
menuItem: (theme) => css`
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
gap: 8px;
|
||||
padding: 8px 20px;
|
||||
font-size: 14px;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
gap: 8px;
|
||||
padding: 8px 20px;
|
||||
font-size: 14px;
|
||||
|
||||
&:hover {
|
||||
background-color: ${theme.palette.action.hover};
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
`,
|
||||
&:hover {
|
||||
background-color: ${theme.palette.action.hover};
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
`,
|
||||
menuItemIcon: (theme) => ({
|
||||
color: theme.palette.text.secondary,
|
||||
width: 20,
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { useTheme } from "@emotion/react";
|
||||
import type * as TypesGen from "api/typesGenerated";
|
||||
import { Avatar } from "components/Avatar/Avatar";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "components/deprecated/Popover/Popover";
|
||||
import { type FC, useState } from "react";
|
||||
} from "components/Popover/Popover";
|
||||
import type { FC } from "react";
|
||||
import { UserDropdownContent } from "./UserDropdownContent";
|
||||
|
||||
interface UserDropdownProps {
|
||||
@@ -22,12 +21,9 @@ export const UserDropdown: FC<UserDropdownProps> = ({
|
||||
supportLinks,
|
||||
onSignOut,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="bg-transparent border-0 cursor-pointer p-0"
|
||||
@@ -37,14 +33,8 @@ export const UserDropdown: FC<UserDropdownProps> = ({
|
||||
</PopoverTrigger>
|
||||
|
||||
<PopoverContent
|
||||
horizontal="right"
|
||||
css={{
|
||||
".MuiPaper-root": {
|
||||
minWidth: "auto",
|
||||
width: 260,
|
||||
boxShadow: theme.shadows[6],
|
||||
},
|
||||
}}
|
||||
align="end"
|
||||
className="min-w-auto w-[260px] bg-surface-secondary border-surface-quaternary"
|
||||
>
|
||||
<UserDropdownContent
|
||||
user={user}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { MockUserOwner } from "testHelpers/entities";
|
||||
import { render, waitForLoaderToBeRemoved } from "testHelpers/renderHelpers";
|
||||
import { screen } from "@testing-library/react";
|
||||
import { Popover } from "components/deprecated/Popover/Popover";
|
||||
import { Popover } from "components/Popover/Popover";
|
||||
import { Language, UserDropdownContent } from "./UserDropdownContent";
|
||||
|
||||
describe("UserDropdownContent", () => {
|
||||
|
||||
@@ -8,9 +8,9 @@ import Divider from "@mui/material/Divider";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import type { SvgIconProps } from "@mui/material/SvgIcon";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import { PopoverClose } from "@radix-ui/react-popover";
|
||||
import type * as TypesGen from "api/typesGenerated";
|
||||
import { CopyButton } from "components/CopyButton/CopyButton";
|
||||
import { usePopover } from "components/deprecated/Popover/Popover";
|
||||
import { ExternalImage } from "components/ExternalImage/ExternalImage";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import {
|
||||
@@ -44,12 +44,6 @@ export const UserDropdownContent: FC<UserDropdownContentProps> = ({
|
||||
supportLinks,
|
||||
onSignOut,
|
||||
}) => {
|
||||
const popover = usePopover();
|
||||
|
||||
const onPopoverClose = () => {
|
||||
popover.setOpen(false);
|
||||
};
|
||||
|
||||
const renderMenuIcon = (icon: string): JSX.Element => {
|
||||
switch (icon) {
|
||||
case "bug":
|
||||
@@ -80,17 +74,21 @@ export const UserDropdownContent: FC<UserDropdownContentProps> = ({
|
||||
<Divider css={{ marginBottom: 8 }} />
|
||||
|
||||
<Link to="/install" css={styles.link}>
|
||||
<MenuItem css={styles.menuItem} onClick={onPopoverClose}>
|
||||
<MonitorDownIcon css={styles.menuItemIcon} />
|
||||
<span css={styles.menuItemText}>Install CLI</span>
|
||||
</MenuItem>
|
||||
<PopoverClose asChild>
|
||||
<MenuItem css={styles.menuItem}>
|
||||
<MonitorDownIcon css={styles.menuItemIcon} />
|
||||
<span css={styles.menuItemText}>Install CLI</span>
|
||||
</MenuItem>
|
||||
</PopoverClose>
|
||||
</Link>
|
||||
|
||||
<Link to="/settings/account" css={styles.link}>
|
||||
<MenuItem css={styles.menuItem} onClick={onPopoverClose}>
|
||||
<CircleUserIcon css={styles.menuItemIcon} />
|
||||
<span css={styles.menuItemText}>{Language.accountLabel}</span>
|
||||
</MenuItem>
|
||||
<PopoverClose asChild>
|
||||
<MenuItem css={styles.menuItem}>
|
||||
<CircleUserIcon css={styles.menuItemIcon} />
|
||||
<span css={styles.menuItemText}>{Language.accountLabel}</span>
|
||||
</MenuItem>
|
||||
</PopoverClose>
|
||||
</Link>
|
||||
|
||||
<MenuItem css={styles.menuItem} onClick={onSignOut}>
|
||||
@@ -109,10 +107,12 @@ export const UserDropdownContent: FC<UserDropdownContentProps> = ({
|
||||
rel="noreferrer"
|
||||
css={styles.link}
|
||||
>
|
||||
<MenuItem css={styles.menuItem} onClick={onPopoverClose}>
|
||||
{renderMenuIcon(link.icon)}
|
||||
<span css={styles.menuItemText}>{link.name}</span>
|
||||
</MenuItem>
|
||||
<PopoverClose asChild>
|
||||
<MenuItem css={styles.menuItem}>
|
||||
{renderMenuIcon(link.icon)}
|
||||
<span css={styles.menuItemText}>{link.name}</span>
|
||||
</MenuItem>
|
||||
</PopoverClose>
|
||||
</a>
|
||||
))}
|
||||
</>
|
||||
@@ -133,23 +133,11 @@ export const UserDropdownContent: FC<UserDropdownContentProps> = ({
|
||||
</Tooltip>
|
||||
|
||||
{buildInfo?.deployment_id && (
|
||||
<div
|
||||
css={css`
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`}
|
||||
>
|
||||
<div className="flex items-center text-xs">
|
||||
<Tooltip title="Deployment Identifier">
|
||||
<div
|
||||
css={css`
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
`}
|
||||
>
|
||||
<span className="whitespace-nowrap overflow-hidden text-ellipsis">
|
||||
{buildInfo.deployment_id}
|
||||
</div>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<CopyButton
|
||||
text={buildInfo.deployment_id}
|
||||
@@ -200,14 +188,14 @@ const styles = {
|
||||
color: "inherit",
|
||||
},
|
||||
menuItem: (theme) => css`
|
||||
gap: 20px;
|
||||
padding: 8px 20px;
|
||||
gap: 20px;
|
||||
padding: 8px 20px;
|
||||
|
||||
&:hover {
|
||||
background-color: ${theme.palette.action.hover};
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
`,
|
||||
&:hover {
|
||||
background-color: ${theme.palette.action.hover};
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
`,
|
||||
menuItemIcon: (theme) => ({
|
||||
color: theme.palette.text.secondary,
|
||||
width: 20,
|
||||
@@ -217,18 +205,18 @@ const styles = {
|
||||
fontSize: 14,
|
||||
},
|
||||
footerText: (theme) => css`
|
||||
font-size: 12px;
|
||||
text-decoration: none;
|
||||
color: ${theme.palette.text.secondary};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 12px;
|
||||
text-decoration: none;
|
||||
color: ${theme.palette.text.secondary};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
||||
& svg {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
`,
|
||||
& svg {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
`,
|
||||
buildInfo: (theme) => ({
|
||||
color: theme.palette.text.primary,
|
||||
}),
|
||||
|
||||
@@ -26,16 +26,16 @@ import {
|
||||
WorkspaceAppSharingLevels,
|
||||
} from "api/typesGenerated";
|
||||
import { Button } from "components/Button/Button";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "components/deprecated/Popover/Popover";
|
||||
import {
|
||||
HelpTooltipLink,
|
||||
HelpTooltipText,
|
||||
HelpTooltipTitle,
|
||||
} from "components/HelpTooltip/HelpTooltip";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "components/Popover/Popover";
|
||||
import { Spinner } from "components/Spinner/Spinner";
|
||||
import {
|
||||
Tooltip,
|
||||
@@ -44,7 +44,6 @@ import {
|
||||
TooltipTrigger,
|
||||
} from "components/Tooltip/Tooltip";
|
||||
import { useFormik } from "formik";
|
||||
import { type ClassName, useClassName } from "hooks/useClassName";
|
||||
import {
|
||||
ChevronDownIcon,
|
||||
ExternalLinkIcon,
|
||||
@@ -77,7 +76,6 @@ export const PortForwardButton: FC<PortForwardButtonProps> = ({
|
||||
agent,
|
||||
}) => {
|
||||
const { entitlements } = useDashboard();
|
||||
const paper = useClassName(classNames.paper, []);
|
||||
|
||||
const { data: listeningPorts } = useQuery({
|
||||
queryKey: ["portForward", agent.id],
|
||||
@@ -95,7 +93,7 @@ export const PortForwardButton: FC<PortForwardButtonProps> = ({
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger>
|
||||
<PopoverTrigger asChild>
|
||||
<Button disabled={!listeningPorts} size="sm" variant="subtle">
|
||||
<Spinner loading={!listeningPorts}>
|
||||
<span css={styles.portCount}>{listeningPorts?.length}</span>
|
||||
@@ -104,7 +102,10 @@ export const PortForwardButton: FC<PortForwardButtonProps> = ({
|
||||
<ChevronDownIcon className="size-4" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent horizontal="right" classes={{ paper }}>
|
||||
<PopoverContent
|
||||
align="end"
|
||||
className="p-0 w-[404px] mt-1 text-content-secondary bg-surface-secondary border-surface-quaternary"
|
||||
>
|
||||
<PortForwardPopoverView
|
||||
host={host}
|
||||
agent={agent}
|
||||
@@ -618,15 +619,6 @@ export const PortForwardPopoverView: FC<PortForwardPopoverViewProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
const classNames = {
|
||||
paper: (css, theme) => css`
|
||||
padding: 0;
|
||||
width: 404px;
|
||||
color: ${theme.palette.text.secondary};
|
||||
margin-top: 4px;
|
||||
`,
|
||||
} satisfies Record<string, ClassName>;
|
||||
|
||||
const styles = {
|
||||
portCount: (theme) => ({
|
||||
fontSize: 12,
|
||||
|
||||
@@ -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<EditRolesButtonProps> = ({
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger>
|
||||
<PopoverTrigger asChild>
|
||||
<Tooltip title="Edit user roles">
|
||||
<Button
|
||||
variant="subtle"
|
||||
@@ -143,7 +143,10 @@ const EnabledEditRolesButton: FC<EditRolesButtonProps> = ({
|
||||
</Tooltip>
|
||||
</PopoverTrigger>
|
||||
|
||||
<PopoverContent className="w-96" disablePortal={false}>
|
||||
<PopoverContent
|
||||
align="start"
|
||||
className="w-96 bg-surface-secondary border-surface-quaternary"
|
||||
>
|
||||
<fieldset
|
||||
className="border-0 m-0 p-0 disabled:opacity-50"
|
||||
disabled={isLoading}
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "components/deprecated/Popover/Popover";
|
||||
} from "components/Popover/Popover";
|
||||
import dayjs from "dayjs";
|
||||
import { MoveRightIcon } from "lucide-react";
|
||||
import { type ComponentProps, type FC, useRef, useState } from "react";
|
||||
@@ -45,7 +45,7 @@ export const DateRange: FC<DateRangeProps> = ({ value, onChange }) => {
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="outline">
|
||||
<span>{dayjs(value.startDate).format("MMM D, YYYY")}</span>
|
||||
<MoveRightIcon />
|
||||
@@ -139,7 +139,7 @@ const styles = {
|
||||
fontSize: 14,
|
||||
color: theme.palette.text.secondary,
|
||||
|
||||
"&:hover .rdrStaticRangeLabel": {
|
||||
"&:is(:hover, :focus) .rdrStaticRangeLabel": {
|
||||
background: theme.palette.background.paper,
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { within } from "@testing-library/react";
|
||||
import type { ComponentProps } from "react";
|
||||
import { userEvent } from "storybook/test";
|
||||
import { TemplateInsightsControls } from "./TemplateInsightsPage";
|
||||
|
||||
const meta: Meta<typeof TemplateInsightsControls> = {
|
||||
title: "pages/TemplatePage/TemplateInsightsControls",
|
||||
component: TemplateInsightsControls,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof TemplateInsightsControls>;
|
||||
|
||||
const defaultArgs: Partial<ComponentProps<typeof TemplateInsightsControls>> = {
|
||||
dateRange: {
|
||||
startDate: new Date("2025-08-05"),
|
||||
endDate: new Date("2025-08-07"),
|
||||
},
|
||||
setDateRange: () => {},
|
||||
searchParams: new URLSearchParams(),
|
||||
setSearchParams: () => {},
|
||||
};
|
||||
|
||||
export const Day: Story = {
|
||||
args: {
|
||||
...defaultArgs,
|
||||
interval: "day",
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const datePicker = canvas.getAllByRole("button")[1];
|
||||
await userEvent.click(datePicker);
|
||||
},
|
||||
};
|
||||
|
||||
export const Week: Story = {
|
||||
args: {
|
||||
...defaultArgs,
|
||||
interval: "week",
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const dropdown = canvas.getAllByRole("button")[1];
|
||||
await userEvent.click(dropdown);
|
||||
},
|
||||
};
|
||||
@@ -47,7 +47,7 @@ import {
|
||||
} from "react";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { useQuery } from "react-query";
|
||||
import { useSearchParams } from "react-router";
|
||||
import { type SetURLSearchParams, useSearchParams } from "react-router";
|
||||
import { getLatencyColor } from "utils/latency";
|
||||
import {
|
||||
addTime,
|
||||
@@ -105,24 +105,13 @@ export default function TemplateInsightsPage() {
|
||||
</Helmet>
|
||||
<TemplateInsightsPageView
|
||||
controls={
|
||||
<>
|
||||
<IntervalMenu
|
||||
value={interval}
|
||||
onChange={(interval) => {
|
||||
// When going from daily to week we need to set a safe week range
|
||||
if (interval === "week") {
|
||||
setDateRange(lastWeeks(DEFAULT_NUMBER_OF_WEEKS));
|
||||
}
|
||||
searchParams.set("interval", interval);
|
||||
setSearchParams(searchParams);
|
||||
}}
|
||||
/>
|
||||
{interval === "day" ? (
|
||||
<DailyPicker value={dateRange} onChange={setDateRange} />
|
||||
) : (
|
||||
<WeekPicker value={dateRange} onChange={setDateRange} />
|
||||
)}
|
||||
</>
|
||||
<TemplateInsightsControls
|
||||
interval={interval}
|
||||
dateRange={dateRange}
|
||||
setDateRange={setDateRange}
|
||||
searchParams={searchParams}
|
||||
setSearchParams={setSearchParams}
|
||||
/>
|
||||
}
|
||||
templateInsights={templateInsights}
|
||||
userLatency={userLatency}
|
||||
@@ -134,6 +123,43 @@ export default function TemplateInsightsPage() {
|
||||
);
|
||||
}
|
||||
|
||||
interface TemplateInsightsControlsProps {
|
||||
interval: "day" | "week";
|
||||
dateRange: DateRangeValue;
|
||||
setDateRange: (value: DateRangeValue) => void;
|
||||
searchParams: URLSearchParams;
|
||||
setSearchParams: SetURLSearchParams;
|
||||
}
|
||||
|
||||
export const TemplateInsightsControls: FC<TemplateInsightsControlsProps> = ({
|
||||
interval,
|
||||
dateRange,
|
||||
setDateRange,
|
||||
searchParams,
|
||||
setSearchParams,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<IntervalMenu
|
||||
value={interval}
|
||||
onChange={(interval) => {
|
||||
// When going from daily to week we need to set a safe week range
|
||||
if (interval === "week") {
|
||||
setDateRange(lastWeeks(DEFAULT_NUMBER_OF_WEEKS));
|
||||
}
|
||||
searchParams.set("interval", interval);
|
||||
setSearchParams(searchParams);
|
||||
}}
|
||||
/>
|
||||
{interval === "day" ? (
|
||||
<DailyPicker value={dateRange} onChange={setDateRange} />
|
||||
) : (
|
||||
<WeekPicker value={dateRange} onChange={setDateRange} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const getDefaultInterval = (template: Template) => {
|
||||
const now = new Date();
|
||||
const templateCreateDate = new Date(template.created_at);
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import Link from "@mui/material/Link";
|
||||
import useTheme from "@mui/system/useTheme";
|
||||
import type { ProvisionerDaemon } from "api/typesGenerated";
|
||||
import { FormSection } from "components/Form/Form";
|
||||
import { TopbarButton } from "components/FullPageLayout/Topbar";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "components/deprecated/Popover/Popover";
|
||||
import { FormSection } from "components/Form/Form";
|
||||
import { TopbarButton } from "components/FullPageLayout/Topbar";
|
||||
} from "components/Popover/Popover";
|
||||
import { ChevronDownIcon } from "lucide-react";
|
||||
import { ProvisionerTagsField } from "modules/provisioners/ProvisionerTagsField";
|
||||
import type { FC } from "react";
|
||||
@@ -26,7 +26,7 @@ export const ProvisionerTagsPopover: FC<ProvisionerTagsPopoverProps> = ({
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger>
|
||||
<PopoverTrigger asChild>
|
||||
<TopbarButton
|
||||
color="neutral"
|
||||
css={{ paddingLeft: 0, paddingRight: 0, minWidth: "28px !important" }}
|
||||
@@ -36,8 +36,8 @@ export const ProvisionerTagsPopover: FC<ProvisionerTagsPopoverProps> = ({
|
||||
</TopbarButton>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
horizontal="right"
|
||||
css={{ ".MuiPaper-root": { width: 300 } }}
|
||||
align="end"
|
||||
className="w-[300px] bg-surface-secondary border-surface-quaternary"
|
||||
>
|
||||
<div
|
||||
css={{
|
||||
|
||||
@@ -7,12 +7,6 @@ import type {
|
||||
WorkspaceBuildParameter,
|
||||
} from "api/typesGenerated";
|
||||
import { Button } from "components/Button/Button";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
usePopover,
|
||||
} from "components/deprecated/Popover/Popover";
|
||||
import { FormFields } from "components/Form/Form";
|
||||
import { TopbarButton } from "components/FullPageLayout/Topbar";
|
||||
import {
|
||||
@@ -21,13 +15,18 @@ import {
|
||||
HelpTooltipText,
|
||||
HelpTooltipTitle,
|
||||
} from "components/HelpTooltip/HelpTooltip";
|
||||
import { Link } from "components/Link/Link";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "components/Popover/Popover";
|
||||
import { RichParameterInput } from "components/RichParameterInput/RichParameterInput";
|
||||
import { useFormik } from "formik";
|
||||
import { ChevronDownIcon } from "lucide-react";
|
||||
import type { FC } from "react";
|
||||
import { type FC, useState } from "react";
|
||||
import { useQuery } from "react-query";
|
||||
import { useNavigate } from "react-router";
|
||||
import { docs } from "utils/docs";
|
||||
import { getFormHelpers } from "utils/formUtils";
|
||||
import {
|
||||
@@ -48,6 +47,7 @@ export const BuildParametersPopover: FC<BuildParametersPopoverProps> = ({
|
||||
label,
|
||||
onSubmit,
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const { data: parameters } = useQuery({
|
||||
queryKey: ["workspace", workspace.id, "parameters"],
|
||||
queryFn: () => API.getWorkspaceParameters(workspace),
|
||||
@@ -57,8 +57,8 @@ export const BuildParametersPopover: FC<BuildParametersPopoverProps> = ({
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger>
|
||||
<Popover open={isOpen} onOpenChange={setIsOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<TopbarButton
|
||||
data-testid="build-parameters-button"
|
||||
disabled={disabled}
|
||||
@@ -69,14 +69,15 @@ export const BuildParametersPopover: FC<BuildParametersPopoverProps> = ({
|
||||
</TopbarButton>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
horizontal="right"
|
||||
css={{ ".MuiPaper-root": { width: 304 } }}
|
||||
align="end"
|
||||
className="bg-surface-secondary border-surface-quaternary w-[304px]"
|
||||
>
|
||||
<BuildParametersPopoverContent
|
||||
workspace={workspace}
|
||||
ephemeralParameters={ephemeralParameters}
|
||||
buildParameters={parameters?.buildParameters}
|
||||
onSubmit={onSubmit}
|
||||
setIsOpen={setIsOpen}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
@@ -88,6 +89,7 @@ interface BuildParametersPopoverContentProps {
|
||||
ephemeralParameters?: TemplateVersionParameter[];
|
||||
buildParameters?: WorkspaceBuildParameter[];
|
||||
onSubmit: (buildParameters: WorkspaceBuildParameter[]) => void;
|
||||
setIsOpen: (newOpen: boolean) => void;
|
||||
}
|
||||
|
||||
const BuildParametersPopoverContent: FC<BuildParametersPopoverContentProps> = ({
|
||||
@@ -95,23 +97,15 @@ const BuildParametersPopoverContent: FC<BuildParametersPopoverContentProps> = ({
|
||||
ephemeralParameters,
|
||||
buildParameters,
|
||||
onSubmit,
|
||||
setIsOpen,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const popover = usePopover();
|
||||
const navigate = useNavigate();
|
||||
|
||||
if (
|
||||
!workspace.template_use_classic_parameter_flow &&
|
||||
ephemeralParameters &&
|
||||
ephemeralParameters.length > 0
|
||||
) {
|
||||
const handleGoToParameters = () => {
|
||||
popover.setOpen(false);
|
||||
navigate(
|
||||
`/@${workspace.owner_name}/${workspace.name}/settings/parameters`,
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 p-5">
|
||||
<p className="m-0 text-sm text-content-secondary">
|
||||
@@ -137,9 +131,12 @@ const BuildParametersPopoverContent: FC<BuildParametersPopoverContentProps> = ({
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<Button className="w-full" onClick={handleGoToParameters}>
|
||||
<Link
|
||||
href={`/@${workspace.owner_name}/${workspace.name}/settings/parameters`}
|
||||
className="self-start"
|
||||
>
|
||||
Go to workspace parameters
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -165,7 +162,7 @@ const BuildParametersPopoverContent: FC<BuildParametersPopoverContentProps> = ({
|
||||
<Form
|
||||
onSubmit={(buildParameters) => {
|
||||
onSubmit(buildParameters);
|
||||
popover.setOpen(false);
|
||||
setIsOpen(false);
|
||||
}}
|
||||
ephemeralParameters={ephemeralParameters}
|
||||
buildParameters={buildParameters.map(
|
||||
|
||||
@@ -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<typeof DebugButton> = {
|
||||
@@ -41,11 +41,9 @@ 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 userEvent.click(screen.getByText("Debug with build parameters"));
|
||||
await waitFor(() =>
|
||||
expect(screen.getByText("Build Options")).toBeInTheDocument(),
|
||||
);
|
||||
|
||||
@@ -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<typeof RetryButton> = {
|
||||
@@ -41,14 +45,72 @@ 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 userEvent.click(screen.getByText("Retry with build parameters"));
|
||||
await waitFor(() =>
|
||||
expect(screen.getByText("Build Options")).toBeInTheDocument(),
|
||||
);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
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.getByText("Retry with build parameters"));
|
||||
expect(
|
||||
await screen.findByText(
|
||||
"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.getByText("Retry with build parameters"));
|
||||
expect(
|
||||
await screen.findByText(
|
||||
"This workspace has ephemeral parameters which may use a temporary value on workspace start. Configure the following parameters in workspace settings.",
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@@ -2,14 +2,14 @@ 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";
|
||||
} from "components/Popover/Popover";
|
||||
import { SearchEmpty } from "components/Search/Search";
|
||||
import { ChevronDownIcon, ExternalLinkIcon } from "lucide-react";
|
||||
import { linkToTemplate, useLinks } from "modules/navigation";
|
||||
@@ -54,21 +54,15 @@ export const WorkspacesButton: FC<WorkspacesButtonProps> = ({
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger>
|
||||
<PopoverTrigger asChild>
|
||||
<Button size="lg">
|
||||
{children}
|
||||
<ChevronDownIcon />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
horizontal="right"
|
||||
css={{
|
||||
".MuiPaper-root": {
|
||||
width: 320,
|
||||
padding: 0,
|
||||
borderRadius: 4,
|
||||
},
|
||||
}}
|
||||
align="end"
|
||||
className="bg-surface-secondary border-surface-quaternary w-[320px]"
|
||||
>
|
||||
<MenuSearch
|
||||
value={searchTerm}
|
||||
|
||||
@@ -1599,6 +1599,12 @@ export const MockPendingWorkspace: TypesGen.Workspace = {
|
||||
},
|
||||
};
|
||||
|
||||
export const MockNonClassicParameterFlowWorkspace: TypesGen.Workspace = {
|
||||
...MockWorkspace,
|
||||
id: "test-non-classic-parameter-flow-workspace",
|
||||
template_use_classic_parameter_flow: false,
|
||||
};
|
||||
|
||||
// just over one page of workspaces
|
||||
export const MockWorkspacesResponse: TypesGen.WorkspacesResponse = {
|
||||
workspaces: range(1, 27).map((id: number) => ({
|
||||
@@ -1696,6 +1702,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.",
|
||||
|
||||
Reference in New Issue
Block a user