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..0d0e23179b14b 100644 --- a/site/src/components/Filter/SelectFilter.tsx +++ b/site/src/components/Filter/SelectFilter.tsx @@ -60,7 +60,7 @@ export const SelectFilter: FC = ({ = (props) => { - return ; +export const HelpTooltip: FC = (props) => { + return ( + + + + ); }; -export const HelpTooltipContent: FC = (props) => { - const theme = useTheme(); - +export const HelpTooltipContent: FC = ({ + className, + ...props +}) => { return ( - ); }; @@ -76,7 +79,7 @@ export const HelpTooltipTrigger = forwardRef< }); return ( - + - + ); }); @@ -155,18 +158,12 @@ export const HelpTooltipAction: FC = ({ onClick, ariaLabel, }) => { - const popover = usePopover(); - return ( - + }> { diff --git a/site/src/components/InfoTooltip/InfoTooltip.stories.tsx b/site/src/components/InfoTooltip/InfoTooltip.stories.tsx index b531e052fd356..3cd09862d9b25 100644 --- a/site/src/components/InfoTooltip/InfoTooltip.stories.tsx +++ b/site/src/components/InfoTooltip/InfoTooltip.stories.tsx @@ -1,5 +1,5 @@ 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 { InfoTooltip } from "./InfoTooltip"; const meta = { @@ -16,13 +16,13 @@ export default meta; type Story = StoryObj; export const Example: Story = { - play: async ({ canvasElement, step }) => { - const screen = within(canvasElement); - + play: async ({ step }) => { await step("activate hover trigger", async () => { await userEvent.hover(screen.getByRole("button")); await waitFor(() => - expect(screen.getByText(meta.args.message)).toBeInTheDocument(), + expect(screen.getByRole("tooltip")).toHaveTextContent( + meta.args.message, + ), ); }); }, @@ -33,13 +33,13 @@ export const Notice = { type: "notice", message: "Unfortunately, there's a radio connected to my brain", }, - play: async ({ canvasElement, step }) => { - const screen = within(canvasElement); - + play: async ({ step }) => { await step("activate hover trigger", async () => { await userEvent.hover(screen.getByRole("button")); await waitFor(() => - expect(screen.getByText(Notice.args.message)).toBeInTheDocument(), + expect(screen.getByRole("tooltip")).toHaveTextContent( + Notice.args.message, + ), ); }); }, @@ -50,13 +50,13 @@ export const Warning = { type: "warning", message: "Unfortunately, there's a radio connected to my brain", }, - play: async ({ canvasElement, step }) => { - const screen = within(canvasElement); - + play: async ({ step }) => { await step("activate hover trigger", async () => { await userEvent.hover(screen.getByRole("button")); await waitFor(() => - expect(screen.getByText(Warning.args.message)).toBeInTheDocument(), + expect(screen.getByRole("tooltip")).toHaveTextContent( + Warning.args.message, + ), ); }); }, diff --git a/site/src/components/Popover/Popover.tsx b/site/src/components/Popover/Popover.tsx index b04aeb258fd7d..c6a9d9845639d 100644 --- a/site/src/components/Popover/Popover.tsx +++ b/site/src/components/Popover/Popover.tsx @@ -23,6 +23,7 @@ export const PopoverContent = forwardRef< ref={ref} align={align} sideOffset={sideOffset} + collisionPadding={16} // 1rem 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/SelectMenu/SelectMenu.tsx b/site/src/components/SelectMenu/SelectMenu.tsx index ece4128769705..5ecba4cb7d286 100644 --- a/site/src/components/SelectMenu/SelectMenu.tsx +++ b/site/src/components/SelectMenu/SelectMenu.tsx @@ -1,11 +1,15 @@ import MenuItem, { type MenuItemProps } from "@mui/material/MenuItem"; import MenuList, { type MenuListProps } from "@mui/material/MenuList"; +import type { + PopoverContentProps, + PopoverTriggerProps, +} from "@radix-ui/react-popover"; import { Button, type ButtonProps } from "components/Button/Button"; import { Popover, PopoverContent, PopoverTrigger, -} from "components/deprecated/Popover/Popover"; +} from "components/Popover/Popover"; import { SearchField, type SearchFieldProps, @@ -26,9 +30,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/components/Tooltip/Tooltip.tsx b/site/src/components/Tooltip/Tooltip.tsx index c437240ec949f..e5eff91a0c181 100644 --- a/site/src/components/Tooltip/Tooltip.tsx +++ b/site/src/components/Tooltip/Tooltip.tsx @@ -12,11 +12,15 @@ export const Tooltip = TooltipPrimitive.Root; export const TooltipTrigger = TooltipPrimitive.Trigger; +export type TooltipContentProps = React.ComponentPropsWithoutRef< + typeof TooltipPrimitive.Content +> & { + disablePortal?: boolean; +}; + export const TooltipContent = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef & { - disablePortal?: boolean; - } + TooltipContentProps >(({ className, sideOffset = 4, disablePortal, ...props }, ref) => { const content = ( = { - title: "components/PopoverDeprecated", - component: Popover, -}; - -export default meta; -type Story = StoryObj; - -const content = ` -According to all known laws of aviation, there is no way a bee should be able to fly. -Its wings are too small to get its fat little body off the ground. The bee, of course, -flies anyway because bees don't care what humans think is impossible. -`; - -export const Example: Story = { - args: { - children: ( - <> - - - - {content} - - ), - }, - play: async ({ canvasElement, step }) => { - const canvas = within(canvasElement); - - await step("click to open", async () => { - await userEvent.click(canvas.getByRole("button")); - await waitFor(() => - expect( - screen.getByText(/according to all known laws/i), - ).toBeInTheDocument(), - ); - }); - }, -}; - -export const Horizontal: Story = { - args: { - children: ( - <> - - - - {content} - - ), - }, - play: Example.play, -}; diff --git a/site/src/components/deprecated/Popover/Popover.tsx b/site/src/components/deprecated/Popover/Popover.tsx deleted file mode 100644 index 044306f325c4f..0000000000000 --- a/site/src/components/deprecated/Popover/Popover.tsx +++ /dev/null @@ -1,243 +0,0 @@ -import MuiPopover, { - type PopoverProps as MuiPopoverProps, - // biome-ignore lint/style/noRestrictedImports: This is the base component that our custom popover is based on -} from "@mui/material/Popover"; -import { - cloneElement, - createContext, - type FC, - type HTMLAttributes, - type PointerEvent, - type PointerEventHandler, - type ReactElement, - type ReactNode, - type RefObject, - useContext, - useEffect, - useId, - useRef, - useState, -} from "react"; - -type TriggerMode = "hover" | "click"; - -type TriggerRef = RefObject; - -// Have to append ReactNode type to satisfy React's cloneElement function. It -// has absolutely no bearing on what happens at runtime -type TriggerElement = ReactNode & - ReactElement<{ - ref: TriggerRef; - onClick?: () => void; - }>; - -type PopoverContextValue = { - id: string; - open: boolean; - setOpen: (open: boolean) => void; - triggerRef: TriggerRef; - mode: TriggerMode; -}; - -const PopoverContext = createContext( - undefined, -); - -type BasePopoverProps = { - children: ReactNode; - mode?: TriggerMode; -}; - -// By separating controlled and uncontrolled props, we achieve more accurate -// type inference. -type UncontrolledPopoverProps = BasePopoverProps & { - open?: undefined; - onOpenChange?: undefined; -}; - -type ControlledPopoverProps = BasePopoverProps & { - open: boolean; - onOpenChange: (open: boolean) => void; -}; - -export type PopoverProps = UncontrolledPopoverProps | ControlledPopoverProps; - -/** @deprecated prefer `components.Popover` */ -export const Popover: FC = (props) => { - const hookId = useId(); - const [uncontrolledOpen, setUncontrolledOpen] = useState(false); - const triggerRef: TriggerRef = useRef(null); - - // Helps makes sure that popovers close properly when the user switches to - // a different tab. This won't help with controlled instances of the - // component, but this is basically the most we can do from here - useEffect(() => { - const closeOnTabSwitch = () => setUncontrolledOpen(false); - window.addEventListener("blur", closeOnTabSwitch); - return () => window.removeEventListener("blur", closeOnTabSwitch); - }, []); - - const value: PopoverContextValue = { - triggerRef, - id: `${hookId}-popover`, - mode: props.mode ?? "click", - open: props.open ?? uncontrolledOpen, - setOpen: props.onOpenChange ?? setUncontrolledOpen, - }; - - return ( - - {props.children} - - ); -}; - -export const usePopover = () => { - const context = useContext(PopoverContext); - if (!context) { - throw new Error( - "Popover compound components cannot be rendered outside the Popover component", - ); - } - return context; -}; - -type PopoverTriggerRenderProps = Readonly<{ - isOpen: boolean; -}>; - -type PopoverTriggerProps = Readonly< - Omit, "children"> & { - children: - | TriggerElement - | ((props: PopoverTriggerRenderProps) => TriggerElement); - } ->; - -/** @deprecated prefer `components.Popover.PopoverTrigger` */ -export const PopoverTrigger: FC = (props) => { - const popover = usePopover(); - const { children, onClick, onPointerEnter, onPointerLeave, ...elementProps } = - props; - - const clickProps = { - onClick: (event: PointerEvent) => { - popover.setOpen(true); - onClick?.(event); - }, - }; - - const hoverProps = { - onPointerEnter: (event: PointerEvent) => { - popover.setOpen(true); - onPointerEnter?.(event); - }, - onPointerLeave: (event: PointerEvent) => { - popover.setOpen(false); - onPointerLeave?.(event); - }, - }; - - const evaluatedChildren = - typeof children === "function" - ? children({ isOpen: popover.open }) - : children; - - return cloneElement(evaluatedChildren, { - ...elementProps, - ...(popover.mode === "click" ? clickProps : hoverProps), - "aria-haspopup": true, - "aria-owns": popover.id, - "aria-expanded": popover.open, - ref: popover.triggerRef, - }); -}; - -type Horizontal = "left" | "right"; - -export type PopoverContentProps = Omit< - MuiPopoverProps, - "open" | "onClose" | "anchorEl" -> & { - horizontal?: Horizontal; -}; - -/** @deprecated prefer `components.Popover.PopoverContent` */ -export const PopoverContent: FC = ({ - horizontal = "left", - onPointerEnter, - onPointerLeave, - ...popoverProps -}) => { - const popover = usePopover(); - const hoverMode = popover.mode === "hover"; - - return ( - popover.setOpen(false)} - anchorEl={popover.triggerRef.current} - /> - ); -}; - -const modeProps = ( - popover: PopoverContextValue, - externalOnPointerEnter: PointerEventHandler | undefined, - externalOnPointerLeave: PointerEventHandler | undefined, -) => { - if (popover.mode === "hover") { - return { - onPointerEnter: (event: PointerEvent) => { - popover.setOpen(true); - externalOnPointerEnter?.(event); - }, - onPointerLeave: (event: PointerEvent) => { - popover.setOpen(false); - externalOnPointerLeave?.(event); - }, - }; - } - - return {}; -}; - -const horizontalProps = (horizontal: Horizontal) => { - if (horizontal === "right") { - return { - anchorOrigin: { - vertical: "bottom", - horizontal: "right", - }, - transformOrigin: { - vertical: "top", - horizontal: "right", - }, - } as const; - } - - return { - anchorOrigin: { - vertical: "bottom", - horizontal: "left", - }, - } as const; -}; 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..b38201f3a8c3d 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 { PopoverClose } from "@radix-ui/react-popover"; import { Button } from "components/Button/Button"; import { Popover, 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/modules/workspaces/WorkspaceOutdatedTooltip/WorkspaceOutdatedTooltip.stories.tsx b/site/src/modules/workspaces/WorkspaceOutdatedTooltip/WorkspaceOutdatedTooltip.stories.tsx index fc0a28815752b..c6fb40924c96d 100644 --- a/site/src/modules/workspaces/WorkspaceOutdatedTooltip/WorkspaceOutdatedTooltip.stories.tsx +++ b/site/src/modules/workspaces/WorkspaceOutdatedTooltip/WorkspaceOutdatedTooltip.stories.tsx @@ -5,7 +5,7 @@ import { } from "testHelpers/entities"; import { withDashboardProvider } from "testHelpers/storybook"; import type { Meta, StoryObj } from "@storybook/react-vite"; -import { expect, userEvent, waitFor, within } from "storybook/test"; +import { expect, screen, userEvent, waitFor, within } from "storybook/test"; import { WorkspaceOutdatedTooltip } from "./WorkspaceOutdatedTooltip"; const meta: Meta = { @@ -39,7 +39,9 @@ const Example: Story = { await step("activate hover trigger", async () => { await userEvent.hover(body.getByRole("button")); await waitFor(() => - expect(body.getByText(MockTemplateVersion.message)).toBeInTheDocument(), + expect(screen.getByRole("tooltip")).toHaveTextContent( + MockTemplateVersion.message, + ), ); }); }, diff --git a/site/src/modules/workspaces/WorkspaceOutdatedTooltip/WorkspaceOutdatedTooltip.tsx b/site/src/modules/workspaces/WorkspaceOutdatedTooltip/WorkspaceOutdatedTooltip.tsx index e1e83d502781a..c47c36003a162 100644 --- a/site/src/modules/workspaces/WorkspaceOutdatedTooltip/WorkspaceOutdatedTooltip.tsx +++ b/site/src/modules/workspaces/WorkspaceOutdatedTooltip/WorkspaceOutdatedTooltip.tsx @@ -4,7 +4,6 @@ import Skeleton from "@mui/material/Skeleton"; import { getErrorDetail, getErrorMessage } from "api/errors"; import { templateVersion } from "api/queries/templates"; import type { Workspace } from "api/typesGenerated"; -import { usePopover } from "components/deprecated/Popover/Popover"; import { displayError } from "components/GlobalSnackbar/utils"; import { HelpTooltip, @@ -17,7 +16,7 @@ import { } from "components/HelpTooltip/HelpTooltip"; import { InfoIcon, RotateCcwIcon } from "lucide-react"; import { linkToTemplate, useLinks } from "modules/navigation"; -import type { FC } from "react"; +import { type FC, useState } from "react"; import { useQuery } from "react-query"; import { useWorkspaceUpdate, @@ -29,24 +28,31 @@ interface TooltipProps { } export const WorkspaceOutdatedTooltip: FC = (props) => { + const [isOpen, setIsOpen] = useState(false); + return ( - + Outdated info - + ); }; -const WorkspaceOutdatedTooltipContent: FC = ({ workspace }) => { +type TooltipContentProps = TooltipProps & { isOpen: boolean }; + +const WorkspaceOutdatedTooltipContent: FC = ({ + workspace, + isOpen, +}) => { const getLink = useLinks(); const theme = useTheme(); - const popover = usePopover(); const { data: activeVersion } = useQuery({ ...templateVersion(workspace.template_active_version_id), - enabled: popover.open, + // TODO is making the parent HelpTooltip a controlled component the only way to track whether the tooltip is open? + enabled: isOpen, }); const updateWorkspace = useWorkspaceUpdate({ workspace, diff --git a/site/src/pages/DeploymentSettingsPage/AppearanceSettingsPage/AppearanceSettingsPageView.tsx b/site/src/pages/DeploymentSettingsPage/AppearanceSettingsPage/AppearanceSettingsPageView.tsx index 010c7a999e98f..950fd64f8359a 100644 --- a/site/src/pages/DeploymentSettingsPage/AppearanceSettingsPage/AppearanceSettingsPageView.tsx +++ b/site/src/pages/DeploymentSettingsPage/AppearanceSettingsPage/AppearanceSettingsPageView.tsx @@ -7,17 +7,18 @@ import { PremiumBadge, } from "components/Badges/Badges"; import { Button } from "components/Button/Button"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "components/deprecated/Popover/Popover"; import { PopoverPaywall } from "components/Paywall/PopoverPaywall"; import { SettingsHeader, SettingsHeaderDescription, SettingsHeaderTitle, } from "components/SettingsHeader/SettingsHeader"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "components/Tooltip/Tooltip"; import { useFormik } from "formik"; import type { FC } from "react"; import { getFormHelpers } from "utils/formUtils"; @@ -66,25 +67,31 @@ export const AppearanceSettingsPageView: FC< - - {isEntitled && !isPremium ? ( - - ) : ( - - - - - - )} + + + {isEntitled && !isPremium ? ( + + ) : ( + + + + + + )} - - - - + + + + +
= { args: { organizationSyncSettings: MockOrganizationSyncSettings2, claimFieldValues: Object.keys(MockOrganizationSyncSettings2.mapping), - organizations: [MockOrganization, MockOrganization2], + organizations: [MockOrganization, MockOrganization2, MockOrganization3], error: undefined, }, }; diff --git a/site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/OrganizationPills.tsx b/site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/OrganizationPills.tsx index 030e3889cac41..6768639a3221f 100644 --- a/site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/OrganizationPills.tsx +++ b/site/src/pages/DeploymentSettingsPage/IdpOrgSyncPage/OrganizationPills.tsx @@ -1,10 +1,10 @@ -import { useTheme } from "@emotion/react"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "components/deprecated/Popover/Popover"; import { Pill } from "components/Pill/Pill"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "components/Tooltip/Tooltip"; import type { FC } from "react"; import { cn } from "utils/cn"; import { isUUID } from "utils/uuid"; @@ -46,58 +46,35 @@ interface OverflowPillProps { } const OverflowPillList: FC = ({ organizations }) => { - const theme = useTheme(); - return ( - - - - +{organizations.length} - - + + + + + +{organizations.length} + + - -
    - {organizations.map((organization) => ( -
  • - - {organization.name} - -
  • - ))} -
-
-
+ +
    + {organizations.map((organization) => ( +
  • + + {organization.name} + +
  • + ))} +
+
+ + ); }; diff --git a/site/src/pages/DeploymentSettingsPage/ObservabilitySettingsPage/ObservabilitySettingsPageView.stories.tsx b/site/src/pages/DeploymentSettingsPage/ObservabilitySettingsPage/ObservabilitySettingsPageView.stories.tsx index 2fb5af8d75838..f52def7cd6036 100644 --- a/site/src/pages/DeploymentSettingsPage/ObservabilitySettingsPage/ObservabilitySettingsPageView.stories.tsx +++ b/site/src/pages/DeploymentSettingsPage/ObservabilitySettingsPage/ObservabilitySettingsPageView.stories.tsx @@ -52,3 +52,5 @@ export default meta; type Story = StoryObj; export const Page: Story = {}; + +export const Premium: Story = { args: { isPremium: true } }; diff --git a/site/src/pages/DeploymentSettingsPage/ObservabilitySettingsPage/ObservabilitySettingsPageView.tsx b/site/src/pages/DeploymentSettingsPage/ObservabilitySettingsPage/ObservabilitySettingsPageView.tsx index cd152293e930b..011db7f954912 100644 --- a/site/src/pages/DeploymentSettingsPage/ObservabilitySettingsPage/ObservabilitySettingsPageView.tsx +++ b/site/src/pages/DeploymentSettingsPage/ObservabilitySettingsPage/ObservabilitySettingsPageView.tsx @@ -4,11 +4,6 @@ import { EnterpriseBadge, PremiumBadge, } from "components/Badges/Badges"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "components/deprecated/Popover/Popover"; import { PopoverPaywall } from "components/Paywall/PopoverPaywall"; import { SettingsHeader, @@ -17,6 +12,12 @@ import { SettingsHeaderTitle, } from "components/SettingsHeader/SettingsHeader"; import { Stack } from "components/Stack/Stack"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "components/Tooltip/Tooltip"; import type { FC } from "react"; import { deploymentGroupHasParent } from "utils/deployOptions"; import { docs } from "utils/docs"; @@ -52,25 +53,31 @@ export const ObservabilitySettingsPageView: FC< - - {featureAuditLogEnabled && !isPremium ? ( - - ) : ( - - - - - - )} + + + {featureAuditLogEnabled && !isPremium ? ( + + ) : ( + + + + + + )} - - - - + + + + + diff --git a/site/src/pages/OrganizationSettingsPage/CreateOrganizationPageView.tsx b/site/src/pages/OrganizationSettingsPage/CreateOrganizationPageView.tsx index 2b1902646fb34..6b87ae2606fda 100644 --- a/site/src/pages/OrganizationSettingsPage/CreateOrganizationPageView.tsx +++ b/site/src/pages/OrganizationSettingsPage/CreateOrganizationPageView.tsx @@ -5,15 +5,16 @@ import { ErrorAlert } from "components/Alert/ErrorAlert"; import { Badges, PremiumBadge } from "components/Badges/Badges"; import { Button } from "components/Button/Button"; import { ChooseOne, Cond } from "components/Conditionals/ChooseOne"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "components/deprecated/Popover/Popover"; import { IconField } from "components/IconField/IconField"; import { Paywall } from "components/Paywall/Paywall"; import { PopoverPaywall } from "components/Paywall/PopoverPaywall"; import { Spinner } from "components/Spinner/Spinner"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "components/Tooltip/Tooltip"; import { useFormik } from "formik"; import { ArrowLeft } from "lucide-react"; import type { FC } from "react"; @@ -81,23 +82,29 @@ export const CreateOrganizationPageView: FC< )} - - {isEntitled && ( - - - - - - )} + + + {isEntitled && ( + + + + + + )} - - - - + + + + +
diff --git a/site/src/pages/OrganizationSettingsPage/CustomRolesPage/PermissionPillsList.tsx b/site/src/pages/OrganizationSettingsPage/CustomRolesPage/PermissionPillsList.tsx index 11071e0dab164..fea0dff643966 100644 --- a/site/src/pages/OrganizationSettingsPage/CustomRolesPage/PermissionPillsList.tsx +++ b/site/src/pages/OrganizationSettingsPage/CustomRolesPage/PermissionPillsList.tsx @@ -1,12 +1,13 @@ import { type Interpolation, type Theme, useTheme } from "@emotion/react"; import Stack from "@mui/material/Stack"; import type { Permission } from "api/typesGenerated"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "components/deprecated/Popover/Popover"; import { Pill } from "components/Pill/Pill"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "components/Tooltip/Tooltip"; import type { FC } from "react"; function getUniqueResourceTypes(jsonObject: readonly Permission[]) { @@ -76,52 +77,34 @@ const OverflowPermissionPill: FC = ({ const theme = useTheme(); return ( - - - - +{resources.length} more - - + + + + + +{resources.length} more + + - - {resources.map((resource) => ( - - ))} - - + +
    + {resources.map((resource) => ( +
  • + +
  • + ))} +
+
+ + ); }; diff --git a/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpPillList.tsx b/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpPillList.tsx index 877ba6c9a205a..330cd6bfdc9ac 100644 --- a/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpPillList.tsx +++ b/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpPillList.tsx @@ -1,11 +1,12 @@ -import { type Interpolation, type Theme, useTheme } from "@emotion/react"; +import type { Interpolation, Theme } from "@emotion/react"; import Stack from "@mui/material/Stack"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "components/deprecated/Popover/Popover"; import { Pill } from "components/Pill/Pill"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "components/Tooltip/Tooltip"; import type { FC } from "react"; import { isUUID } from "utils/uuid"; @@ -34,53 +35,26 @@ interface OverflowPillProps { } const OverflowPill: FC = ({ roles }) => { - const theme = useTheme(); - return ( - - - - +{roles.length} more - - + + + + +{roles.length} more + - - {roles.map((role) => ( - - {role} - - ))} - - + +
    + {roles.map((role) => ( +
  • + + {role} + +
  • + ))} +
+
+ + ); }; diff --git a/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpSyncPageView.stories.tsx b/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpSyncPageView.stories.tsx index b2eb64ab4eec5..8320ab0b895c0 100644 --- a/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpSyncPageView.stories.tsx +++ b/site/src/pages/OrganizationSettingsPage/IdpSyncPage/IdpSyncPageView.stories.tsx @@ -3,16 +3,18 @@ import { MockGroup2, MockGroupSyncSettings, MockGroupSyncSettings2, + MockMultipleOverflowGroupSyncSettings, MockLegacyMappingGroupSyncSettings, MockOrganization, MockRoleSyncSettings, + MockGroup3, } from "testHelpers/entities"; import type { Meta, StoryObj } from "@storybook/react-vite"; import { expect, userEvent } from "storybook/test"; import IdpSyncPageView from "./IdpSyncPageView"; const groupsMap = new Map(); -for (const group of [MockGroup, MockGroup2]) { +for (const group of [MockGroup, MockGroup2, MockGroup3]) { groupsMap.set(group.id, group.display_name || group.name); } @@ -70,6 +72,12 @@ export const MissingGroups: Story = { }, }; +export const MultipleOverflowGroups: Story = { + args: { + groupSyncSettings: MockMultipleOverflowGroupSyncSettings, + }, +}; + export const WithLegacyMapping: Story = { args: { groupSyncSettings: MockLegacyMappingGroupSyncSettings, diff --git a/site/src/pages/OrganizationSettingsPage/UserTable/EditRolesButton.stories.tsx b/site/src/pages/OrganizationSettingsPage/UserTable/EditRolesButton.stories.tsx index 7b6b29c4cca3d..48a03a15d2b43 100644 --- a/site/src/pages/OrganizationSettingsPage/UserTable/EditRolesButton.stories.tsx +++ b/site/src/pages/OrganizationSettingsPage/UserTable/EditRolesButton.stories.tsx @@ -43,6 +43,17 @@ export const Loading: Story = { }, }; +export const CannotSetRoles: Story = { + args: { + userLoginType: "oidc", + oidcRoleSync: true, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await userEvent.hover(canvas.getByLabelText("More info")); + }, +}; + export const AdvancedOpen: Story = { args: { selectedRoleNames: new Set([MockWorkspaceCreationBanRole.name]), diff --git a/site/src/pages/OrganizationSettingsPage/UserTable/EditRolesButton.tsx b/site/src/pages/OrganizationSettingsPage/UserTable/EditRolesButton.tsx index 4983e671aa5a6..5807cb01304ce 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 { HelpTooltipTrigger, } 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 ( - + - - - - - + + + + - + + + {userGroups.map((group) => { + const groupName = group.display_name || group.name; + return ( + - {groupName || N/A} - - - ); - })} - - - - + + + + {groupName || N/A} + + + ); + })} + + + + + )} ); diff --git a/site/src/pages/WorkspacePage/WorkspaceActions/BuildParametersPopover.tsx b/site/src/pages/WorkspacePage/WorkspaceActions/BuildParametersPopover.tsx index 7aef1dc7c7357..11cabf65162fb 100644 --- a/site/src/pages/WorkspacePage/WorkspaceActions/BuildParametersPopover.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceActions/BuildParametersPopover.tsx @@ -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 = ({ 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 = ({ : undefined; return ( - - + + = ({ {label} - + @@ -88,6 +86,7 @@ interface BuildParametersPopoverContentProps { ephemeralParameters?: TemplateVersionParameter[]; buildParameters?: WorkspaceBuildParameter[]; onSubmit: (buildParameters: WorkspaceBuildParameter[]) => void; + setIsOpen: React.Dispatch>; } const BuildParametersPopoverContent: FC = ({ @@ -95,23 +94,15 @@ const BuildParametersPopoverContent: FC = ({ 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 (

@@ -137,9 +128,12 @@ const BuildParametersPopoverContent: FC = ({

- + ); } @@ -165,7 +159,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/WorkspacePage/WorkspaceNotifications/Notifications.tsx b/site/src/pages/WorkspacePage/WorkspaceNotifications/Notifications.tsx index bc72396932e77..150cb2d00b5ec 100644 --- a/site/src/pages/WorkspacePage/WorkspaceNotifications/Notifications.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceNotifications/Notifications.tsx @@ -1,14 +1,14 @@ -import { type Interpolation, type Theme, useTheme } from "@emotion/react"; +import { useTheme, type Interpolation, type Theme } from "@emotion/react"; import type { AlertProps } from "components/Alert/Alert"; import { Button, type ButtonProps } from "components/Button/Button"; -import { - Popover, - PopoverContent, - PopoverTrigger, - usePopover, -} from "components/deprecated/Popover/Popover"; import { Pill } from "components/Pill/Pill"; -import type { FC, ReactNode } from "react"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "components/Tooltip/Tooltip"; +import { useState, type FC, type ReactNode } from "react"; import type { ThemeRole } from "theme/roles"; export type NotificationItem = { @@ -22,6 +22,7 @@ type NotificationsProps = { items: NotificationItem[]; severity: ThemeRole; icon: ReactNode; + isTooltipOpen: boolean; }; export const Notifications: FC = ({ @@ -29,32 +30,39 @@ export const Notifications: FC = ({ severity, icon, }) => { + const [isOpen, setIsOpen] = useState(false); const theme = useTheme(); return ( - - -
- -
-
- + + +
+ +
+
+ - {items.map((n) => ( - - ))} -
-
+ }} + > + {items.map((n) => ( + + ))} + + + ); }; @@ -62,15 +70,14 @@ const NotificationPill: FC = ({ items, severity, icon, + isTooltipOpen, }) => { - const popover = usePopover(); - return ( ({ "& svg": { color: theme.roles[severity].outline }, - borderColor: popover.open ? theme.roles[severity].outline : undefined, + borderColor: isTooltipOpen ? theme.roles[severity].outline : undefined, })} > {items.length} @@ -99,7 +106,7 @@ export const NotificationActionButton: FC = (props) => { }; const styles = { - // Adds some spacing from the popover content + // Adds some spacing from the Tooltip content pillContainer: { padding: "8px 0", }, diff --git a/site/src/pages/WorkspacePage/WorkspaceTopbar.stories.tsx b/site/src/pages/WorkspacePage/WorkspaceTopbar.stories.tsx index aafd16d9c099d..dae0874e7c93e 100644 --- a/site/src/pages/WorkspacePage/WorkspaceTopbar.stories.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceTopbar.stories.tsx @@ -320,9 +320,9 @@ export const TemplateInfoPopover: Story = { await step("activate hover trigger", async () => { await userEvent.hover(canvas.getByText(baseWorkspace.name)); await waitFor(() => - expect( - canvas.getByRole("presentation", { hidden: true }), - ).toHaveTextContent(MockTemplate.display_name), + expect(screen.getByRole("tooltip")).toHaveTextContent( + MockTemplate.display_name, + ), ); }); }, @@ -346,9 +346,9 @@ export const TemplateInfoPopoverWithoutDisplayName: Story = { await step("activate hover trigger", async () => { await userEvent.hover(canvas.getByText(baseWorkspace.name)); await waitFor(() => - expect( - canvas.getByRole("presentation", { hidden: true }), - ).toHaveTextContent(MockTemplate.name), + expect(screen.getByRole("tooltip")).toHaveTextContent( + MockTemplate.name, + ), ); }); }, diff --git a/site/src/pages/WorkspacePage/WorkspaceTopbar.tsx b/site/src/pages/WorkspacePage/WorkspaceTopbar.tsx index b6b21b6f226b9..6b972f39ba52c 100644 --- a/site/src/pages/WorkspacePage/WorkspaceTopbar.tsx +++ b/site/src/pages/WorkspacePage/WorkspaceTopbar.tsx @@ -6,7 +6,6 @@ import type * as TypesGen from "api/typesGenerated"; import { Avatar } from "components/Avatar/Avatar"; import { AvatarData } from "components/Avatar/AvatarData"; import { CopyButton } from "components/CopyButton/CopyButton"; -import { Popover, PopoverTrigger } from "components/deprecated/Popover/Popover"; import { Topbar, TopbarAvatar, @@ -15,7 +14,11 @@ import { TopbarIcon, TopbarIconButton, } from "components/FullPageLayout/Topbar"; -import { HelpTooltipContent } from "components/HelpTooltip/HelpTooltip"; +import { + HelpTooltip, + HelpTooltipContent, +} from "components/HelpTooltip/HelpTooltip"; +import { TooltipTrigger } from "components/Tooltip/Tooltip"; import { ChevronLeftIcon, CircleDollarSign, TrashIcon } from "lucide-react"; import { useDashboard } from "modules/dashboard/useDashboard"; import { linkToTemplate, useLinks } from "modules/navigation"; @@ -253,21 +256,18 @@ const OwnerBreadcrumb: FC = ({ ownerAvatarUrl, }) => { return ( - - + + {ownerName} - + - + - + ); }; @@ -283,8 +283,8 @@ const OrganizationBreadcrumb: FC = ({ orgIconUrl, }) => { return ( - - + + = ({ /> {orgName} - + - + = ({ imgFallbackText={orgName} /> - + ); }; @@ -346,8 +343,8 @@ const WorkspaceBreadcrumb: FC = ({ }) => { return (
- - + + = ({ {workspaceName} - + - + = ({ imgFallbackText={templateDisplayName} /> - +
); diff --git a/site/src/pages/WorkspacesPage/WorkspacesButton.tsx b/site/src/pages/WorkspacesPage/WorkspacesButton.tsx index eda60e4fdc8f9..1d90fb3116bfe 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesButton.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesButton.tsx @@ -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, searchStyles } from "components/Search/Search"; import { ChevronDownIcon, ExternalLinkIcon } from "lucide-react"; import { linkToTemplate, useLinks } from "modules/navigation"; @@ -54,14 +54,15 @@ export const WorkspacesButton: FC = ({ return ( - + ({ @@ -1695,6 +1713,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.", @@ -2881,6 +2914,20 @@ export const MockGroupSyncSettings2: TypesGen.GroupSyncSettings = { auto_create_missing_groups: false, }; +export const MockMultipleOverflowGroupSyncSettings: TypesGen.GroupSyncSettings = + { + field: "group-multiple-overflow-test", + mapping: { + "idp-group-1": [ + "fbd2116a-8961-4954-87ae-e4575bd29ce0", + "13de3eb4-9b4f-49e7-b0f8-0c3728a0d2e2", + "d3562dc1-c120-43a9-ba02-88e43bbca192", + ], + }, + regex_filter: "@[a-zA-Z0-9_]+", + auto_create_missing_groups: false, + }; + export const MockRoleSyncSettings: TypesGen.RoleSyncSettings = { field: "role-test", mapping: { @@ -2905,7 +2952,11 @@ export const MockOrganizationSyncSettings2: TypesGen.OrganizationSyncSettings = { field: "organization-test", mapping: { - "idp-org-1": ["my-organization-id", "my-organization-2-id"], + "idp-org-1": [ + "my-organization-id", + "my-organization-2-id", + "my-organization-3-id", + ], "idp-org-2": ["my-organization-id"], }, organization_assign_default: true, @@ -2946,6 +2997,20 @@ export const MockGroup2: TypesGen.Group = { total_member_count: 2, }; +export const MockGroup3: TypesGen.Group = { + id: "d3562dc1-c120-43a9-ba02-88e43bbca192", + name: "Back-End", + display_name: "", + avatar_url: "https://example.com", + organization_id: MockOrganization.id, + organization_name: MockOrganization.name, + organization_display_name: MockOrganization.display_name, + members: [MockUserOwner, MockUserMember], + quota_allowance: 5, + source: "user", + total_member_count: 2, +}; + const MockEveryoneGroup: TypesGen.Group = { // The "Everyone" group must have the same ID as a the organization it belongs // to.