From ff613fd696264e411075776c2334d96f322d0226 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 24 Feb 2025 14:50:14 -0600 Subject: [PATCH 1/4] test: add unit test for workspace ban role --- .cursorignore | 2 + sample.json | 189 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 191 insertions(+) create mode 100644 .cursorignore create mode 100644 sample.json diff --git a/.cursorignore b/.cursorignore new file mode 100644 index 0000000000000..cd42b9091557a --- /dev/null +++ b/.cursorignore @@ -0,0 +1,2 @@ +# Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv) +/offlinedocs diff --git a/sample.json b/sample.json new file mode 100644 index 0000000000000..3c04d1fa413fe --- /dev/null +++ b/sample.json @@ -0,0 +1,189 @@ +{ + { + "name": "coder_image", + "type": "string", + "form_control": "radio", + "display_name": "Coder Image", + "description": "The Docker image used to run your workspace. Choose between nix and non-nix images.", + "default": "codercom/oss-dogfood:latest", + "icon": "", + "options": [ + { + "name": "Dogfood (Default)", + "description": "", + "value": "codercom/oss-dogfood:latest", + "icon": "/icon/coder.svg" + }, + { + "name": "Dogfood Nix (Experimental)", + "description": "", + "value": "codercom/oss-dogfood-nix:latest", + "icon": "/icon/nix.svg" + } + ], + "order": 1, + "mutable": true, + "diagnostics": [ + { + "severity": "error|warning", + "summary": "short version of the error", + "detail": "detailed version of the error", + "subject": { + "filename": "docker-compose.yml", + "start": 1, + "end": 1 + }, + "context": { + "filename": "docker-compose.yml", + "start": 1, + "end": 1 + }, + "expression": "", + "extra": {} + } + ], + }, + { + "name": "jetbrains_ide", + "type": "string", + "form_control": "dropdown", + "display_name": "JetBrains IDE", + "description": "", + "default": "GO", + "icon": "/icon/gateway.svg", + "options": [ + { + "name": "GoLand", + "description": "", + "value": "GO", + "icon": "/icon/goland.svg" + }, + { + "name": "WebStorm", + "description": "", + "value": "WS", + "icon": "/icon/webstorm.svg" + } + ], + "order": 2, + "mutable": true, + "diagnostics": [ + { + "severity": "error|warning", + "summary": "short version of the error", + "detail": "detailed version of the error", + "subject": { + "filename": "docker-compose.yml", + "start": 1, + "end": 1 + }, + "context": { + "filename": "docker-compose.yml", + "start": 1, + "end": 1 + }, + "expression": "", + "extra": {} + } + ], + }, + { + "name": "region", + "type": "string", + "form_control": "radio", + "description": "", + "default": "us-pittsburgh", + "icon": "/emojis/1f30e.png", + "required": true, + "options": [ + { + "name": "Pittsburgh", + "description": "", + "value": "us-pittsburgh", + "icon": "/emojis/1f1fa-1f1f8.png" + }, + { + "name": "Helsinki", + "description": "", + "value": "eu-helsinki", + "icon": "/emojis/1f1eb-1f1ee.png" + }, + { + "name": "Sydney", + "description": "", + "value": "ap-sydney", + "icon": "/emojis/1f1e6-1f1fa.png" + }, + { + "name": "São Paulo", + "description": "", + "value": "sa-saopaulo", + "icon": "/emojis/1f1e7-1f1f7.png" + }, + { + "name": "Cape Town", + "description": "", + "value": "za-cpt", + "icon": "/emojis/1f1ff-1f1e6.png" + } + ], + "order": 3, + "mutable": true, + "diagnostics": [ + { + "severity": "error|warning", + "summary": "short version of the error", + "detail": "detailed version of the error", + "subject": { + "filename": "docker-compose.yml", + "start": 1, + "end": 1 + }, + "context": { + "filename": "docker-compose.yml", + "start": 1, + "end": 1 + }, + "expression": "", + "extra": {} + } + ], + }, + { + "name": "memory_usage_threshold", + "display_name": "Memory Usage Threshold", + "type": "number", + "form_control": "input", + "description": "The memory usage threshold used in resources monitoring to trigger notifications.", + "value": 80, + "default": 80, + "order": 3, + "mutable": true, + "diagnostics": [ + { + "severity": "error|warning", + "summary": "short version of the error", + "detail": "detailed version of the error", + "subject": { + "filename": "docker-compose.yml", + "start": 1, + "end": 1 + }, + "context": { + "filename": "docker-compose.yml", + "start": 1, + "end": 1 + }, + "expression": "", + "extra": {} + } + ], + }, + "diagnostics": [ + { + "severity": "error|warning", + "summary": "short version of the error", + "detail": "detailed version of the error" + } + ] +} From 3d139bede97d627aed256d1fc566312d26d8c13e Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Tue, 25 Feb 2025 22:37:17 +0000 Subject: [PATCH 2/4] feat: extract collapsible summary component --- .../CollapsibleSummary.stories.tsx | 121 ++++++++++++++++++ .../CollapsibleSummary/CollapsibleSummary.tsx | 91 +++++++++++++ .../UserTable/EditRolesButton.tsx | 48 ++----- 3 files changed, 225 insertions(+), 35 deletions(-) create mode 100644 site/src/components/CollapsibleSummary/CollapsibleSummary.stories.tsx create mode 100644 site/src/components/CollapsibleSummary/CollapsibleSummary.tsx diff --git a/site/src/components/CollapsibleSummary/CollapsibleSummary.stories.tsx b/site/src/components/CollapsibleSummary/CollapsibleSummary.stories.tsx new file mode 100644 index 0000000000000..840ed5c0982b0 --- /dev/null +++ b/site/src/components/CollapsibleSummary/CollapsibleSummary.stories.tsx @@ -0,0 +1,121 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { Button } from "../Button/Button"; +import { CollapsibleSummary } from "./CollapsibleSummary"; + +const meta: Meta = { + title: "components/CollapsibleSummary", + component: CollapsibleSummary, + args: { + label: "Advanced options", + children: ( + <> +
+ Option 1 +
+
+ Option 2 +
+
+ Option 3 +
+ + ), + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; + +export const DefaultOpen: Story = { + args: { + defaultOpen: true, + }, +}; + +export const MediumSize: Story = { + args: { + size: "md", + }, +}; + +export const SmallSize: Story = { + args: { + size: "sm", + }, +}; + +export const CustomClassName: Story = { + args: { + className: "text-blue-500 font-bold", + }, +}; + +export const ManyChildren: Story = { + args: { + defaultOpen: true, + children: ( + <> + {Array.from({ length: 10 }).map((_, i) => ( +
+ key={i} + className="p-2 border border-border rounded-md border-solid" + > + Option {i + 1} +
+ ))} + + ), + }, +}; + +export const NestedCollapsible: Story = { + args: { + defaultOpen: true, + children: ( + <> +
+ Option 1 +
+ +
+ Nested Option 1 +
+
+ Nested Option 2 +
+
+
+ Option 3 +
+ + ), + }, +}; + +export const ComplexContent: Story = { + args: { + defaultOpen: true, + children: ( +
+

Complex Content

+

+ This is a more complex content example with various elements. +

+
+ + +
+
+ ), + }, +}; + +export const LongLabel: Story = { + args: { + label: + "This is a very long label that might wrap or cause layout issues if not handled properly", + }, +}; diff --git a/site/src/components/CollapsibleSummary/CollapsibleSummary.tsx b/site/src/components/CollapsibleSummary/CollapsibleSummary.tsx new file mode 100644 index 0000000000000..675500685adf3 --- /dev/null +++ b/site/src/components/CollapsibleSummary/CollapsibleSummary.tsx @@ -0,0 +1,91 @@ +import { type VariantProps, cva } from "class-variance-authority"; +import { ChevronRightIcon } from "lucide-react"; +import { type FC, type ReactNode, useState } from "react"; +import { cn } from "utils/cn"; + +const collapsibleSummaryVariants = cva( + `flex items-center gap-1 p-0 bg-transparent border-0 text-inherit cursor-pointer + transition-colors text-content-secondary hover:text-content-primary font-medium + whitespace-nowrap`, + { + variants: { + size: { + md: "text-sm", + sm: "text-xs", + }, + }, + defaultVariants: { + size: "md", + }, + }, +); + +export interface CollapsibleSummaryProps + extends VariantProps { + /** + * The label to display for the collapsible section + */ + label: string; + /** + * The content to show when expanded + */ + children: ReactNode; + /** + * Whether the section is initially expanded + */ + defaultOpen?: boolean; + /** + * Optional className for the button + */ + className?: string; + /** + * The size of the component + */ + size?: "md" | "sm"; +} + +export const CollapsibleSummary: FC = ({ + label, + children, + defaultOpen = false, + className, + size, +}) => { + const [isOpen, setIsOpen] = useState(defaultOpen); + + return ( +
+ + + {isOpen &&
{children}
} +
+ ); +}; diff --git a/site/src/pages/OrganizationSettingsPage/UserTable/EditRolesButton.tsx b/site/src/pages/OrganizationSettingsPage/UserTable/EditRolesButton.tsx index c8eb4001e406a..9efd99bccf106 100644 --- a/site/src/pages/OrganizationSettingsPage/UserTable/EditRolesButton.tsx +++ b/site/src/pages/OrganizationSettingsPage/UserTable/EditRolesButton.tsx @@ -3,6 +3,7 @@ import Checkbox from "@mui/material/Checkbox"; 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 { HelpTooltip, HelpTooltipContent, @@ -159,41 +160,18 @@ export const EditRolesButton: FC = ({ /> ))} {advancedRoles.length > 0 && ( - <> - - - {isAdvancedOpen && - advancedRoles.map((role) => ( -