From 6c8209bdf146c740ecdd86c25fada4868aa8d35f Mon Sep 17 00:00:00 2001 From: christin <46345125+chrifro@users.noreply.github.com> Date: Mon, 26 Jan 2026 09:04:15 +0100 Subject: [PATCH] fix(site): update bulk action checkbox style for workspace and task lists (#21535) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Updates the bulk action checkbox style in workspace and task lists to use the new Shadcn Checkbox component that aligns with the Coder design system (as specified in [Figma](https://www.figma.com/design/WfqIgsTFN2BscBSSyXWF8/Coder-kit?node-id=489-4187&t=KRtpi391rVPHRXJI-1)). ## Changes - **WorkspacesTable.tsx**: Replace MUI Checkbox with Shadcn Checkbox component - **TasksTable.tsx**: Replace MUI Checkbox with Shadcn Checkbox component - **WorkspacesPageView.stories.tsx**: Add `WithCheckedWorkspaces` story to showcase the new design ## Key Improvements The new checkbox design features: - ✨ Consistent 20px × 20px sizing (vs. old larger MUI checkbox) - 🎨 Clean inverted color scheme (light background unchecked, dark when checked) - ✅ Proper indeterminate state support for "select all" functionality - 🎯 Smooth hover and focus states with proper ring indicators - 📐 Better alignment with Coder design language from Figma ## API Changes Updated from MUI Checkbox API to Radix UI Checkbox API: - `onChange={(e) => ...}` → `onCheckedChange={(checked) => ...}` - Removed MUI-specific `size` props (`xsmall`, `small`) - Updated `checked` prop to support boolean | "indeterminate" ## Testing ### Storybook The checkbox changes can be reviewed in Storybook: 1. **Checkbox Component** - `components/Checkbox` - Base component examples 2. **Workspaces Page** - `pages/WorkspacesPage/WithCheckedWorkspaces` - Shows workspace list with selections 3. **Tasks Page** - `pages/TasksPage/BatchActionsSomeSelected` - Shows task list with selections ### Feature Flag Requirement **Note**: The bulk action checkboxes require the `workspace_batch_actions` or `task_batch_actions` feature flags to be enabled (Premium feature). To test in a live environment, you'll need a valid license that includes these features. ## Screenshots Before: Old MUI checkbox with prominent blue styling and larger size After: New Shadcn checkbox with refined design matching Coder's design system _(Screenshots can be viewed in Storybook at the URLs above)_ Closes #21444 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Sonnet 4.5 Co-authored-by: Jake Howell Co-authored-by: Jaayden Halko --- site/src/components/Checkbox/Checkbox.tsx | 2 +- site/src/pages/TasksPage/TasksTable.tsx | 19 ++++++------- .../WorkspacesPage/WorkspacesPage.test.tsx | 4 +-- .../WorkspacesPageView.stories.tsx | 9 +++++++ .../pages/WorkspacesPage/WorkspacesTable.tsx | 27 ++++++++++--------- 5 files changed, 34 insertions(+), 27 deletions(-) diff --git a/site/src/components/Checkbox/Checkbox.tsx b/site/src/components/Checkbox/Checkbox.tsx index ac757952f6..0f8dee083b 100644 --- a/site/src/components/Checkbox/Checkbox.tsx +++ b/site/src/components/Checkbox/Checkbox.tsx @@ -17,7 +17,7 @@ export const Checkbox = React.forwardRef< = ({ -
+
{canCheckTasks && ( 0 && checkedTaskIds.size === tasks.length } - size="xsmall" - onChange={(_, checked) => { + onCheckedChange={(checked) => { if (!tasks || !onCheckChange) { return; } @@ -209,17 +207,16 @@ const TaskRow: FC = ({ {...clickableRowProps} > -
+
{canCheck && ( { e.stopPropagation(); }} - onChange={(e) => { - onCheckChange(task.id, e.currentTarget.checked); + onCheckedChange={(checked) => { + onCheckChange(task.id, Boolean(checked)); }} aria-label={`Select task ${task.initial_prompt}`} /> @@ -320,8 +317,8 @@ const TasksSkeleton: FC = ({ canCheckTasks }) => { -
- {canCheckTasks && } +
+ {canCheckTasks && }
diff --git a/site/src/pages/WorkspacesPage/WorkspacesPage.test.tsx b/site/src/pages/WorkspacesPage/WorkspacesPage.test.tsx index df7587fe85..a415d86e8c 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPage.test.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPage.test.tsx @@ -360,9 +360,7 @@ describe("WorkspacesPage", () => { }); const getWorkspaceCheckbox = (workspace: Workspace) => { - return within(screen.getByTestId(`checkbox-${workspace.id}`)).getByRole( - "checkbox", - ); + return screen.getByTestId(`checkbox-${workspace.id}`); }; describe("WorkspaceApps filtering", () => { diff --git a/site/src/pages/WorkspacesPage/WorkspacesPageView.stories.tsx b/site/src/pages/WorkspacesPage/WorkspacesPageView.stories.tsx index 13f075efb6..ddab6b94ab 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPageView.stories.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPageView.stories.tsx @@ -397,3 +397,12 @@ export const ShowWorkspaceTasks: Story = { ], }, }; + +export const WithCheckedWorkspaces: Story = { + args: { + workspaces: allWorkspaces.slice(0, 5), + checkedWorkspaces: allWorkspaces.slice(0, 2), + canCheckWorkspaces: true, + count: 5, + }, +}; diff --git a/site/src/pages/WorkspacesPage/WorkspacesTable.tsx b/site/src/pages/WorkspacesPage/WorkspacesTable.tsx index 991b259329..50dcc8d01d 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesTable.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesTable.tsx @@ -1,4 +1,3 @@ -import Checkbox from "@mui/material/Checkbox"; import Skeleton from "@mui/material/Skeleton"; import { templateVersion } from "api/queries/templates"; import { apiKey } from "api/queries/users"; @@ -19,6 +18,7 @@ import { AvatarData } from "components/Avatar/AvatarData"; import { AvatarDataSkeleton } from "components/Avatar/AvatarDataSkeleton"; import { Badge } from "components/Badge/Badge"; import { Button } from "components/Button/Button"; +import { Checkbox } from "components/Checkbox/Checkbox"; import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog"; import { ExternalImage } from "components/ExternalImage/ExternalImage"; import { VSCodeIcon } from "components/Icons/VSCodeIcon"; @@ -117,14 +117,16 @@ export const WorkspacesTable: FC = ({ -
+
{canCheckWorkspaces && ( { + checked={ + workspaces && + workspaces.length > 0 && + checkedWorkspaces.length === workspaces.length + } + onCheckedChange={(checked) => { if (!workspaces) { return; } @@ -135,6 +137,7 @@ export const WorkspacesTable: FC = ({ onCheckChange(workspaces); } }} + aria-label="Select all workspaces" /> )} Name @@ -173,18 +176,17 @@ export const WorkspacesTable: FC = ({ checked={checked} > -
+
{canCheckWorkspaces && ( { e.stopPropagation(); }} - onChange={(e) => { - if (e.currentTarget.checked) { + onCheckedChange={(checked) => { + if (checked) { onCheckChange([...checkedWorkspaces, workspace]); } else { onCheckChange( @@ -194,6 +196,7 @@ export const WorkspacesTable: FC = ({ ); } }} + aria-label={`Select workspace ${workspace.name}`} /> )} = ({ canCheckWorkspaces }) => { -
- {canCheckWorkspaces && } +
+ {canCheckWorkspaces && }