From 464fccd8075a65a67e8f977597da48b36a9716f5 Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Thu, 27 Feb 2025 17:20:33 +0000 Subject: [PATCH 001/675] chore: create collapsible summary component (#16705) This is based on the Figma designs here: https://www.figma.com/design/WfqIgsTFXN2BscBSSyXWF8/Coder-kit?node-id=507-1525&m=dev --------- Co-authored-by: Steven Masley --- .../CollapsibleSummary.stories.tsx | 120 ++++++++++++++++++ .../CollapsibleSummary/CollapsibleSummary.tsx | 91 +++++++++++++ .../UserTable/EditRolesButton.tsx | 48 ++----- 3 files changed, 224 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..98f63c24ccbc7 --- /dev/null +++ b/site/src/components/CollapsibleSummary/CollapsibleSummary.stories.tsx @@ -0,0 +1,120 @@ +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) => ( +
+ 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) => ( -