From 1b2bd4a0da97a9c94e3cd431b8c79cbb044b3fa0 Mon Sep 17 00:00:00 2001 From: Jeremy Ruppel Date: Thu, 30 Apr 2026 13:52:55 -0400 Subject: [PATCH] feat(site): show non-assignable roles as disabled (#24846) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, non-assignable roles were filtered out of the role selector entirely. Now all roles are shown, with non-assignable roles rendered as disabled (dimmed, `cursor-not-allowed`, checkbox disabled) so users can see they exist but cannot toggle them. Screenshot 2026-04-30 at 1 40 44 PM --- .../CreateUserPage/RoleSelector.stories.tsx | 70 +++++++++++++++++++ .../src/pages/CreateUserPage/RoleSelector.tsx | 13 ++-- 2 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 site/src/pages/CreateUserPage/RoleSelector.stories.tsx diff --git a/site/src/pages/CreateUserPage/RoleSelector.stories.tsx b/site/src/pages/CreateUserPage/RoleSelector.stories.tsx new file mode 100644 index 0000000000..3e73126423 --- /dev/null +++ b/site/src/pages/CreateUserPage/RoleSelector.stories.tsx @@ -0,0 +1,70 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { action } from "storybook/actions"; +import { + assignableRole, + MockAuditorRole, + MockOwnerRole, + MockTemplateAdminRole, + MockUserAdminRole, + mockApiError, +} from "#/testHelpers/entities"; +import { RoleSelector } from "./RoleSelector"; + +const meta: Meta = { + title: "pages/CreateUserPage/RoleSelector", + component: RoleSelector, + args: { + onChange: action("change"), + selectedRoles: [], + }, +}; + +export default meta; +type Story = StoryObj; + +const allAssignable = [ + assignableRole(MockOwnerRole, true), + assignableRole(MockUserAdminRole, true), + assignableRole(MockTemplateAdminRole, true), + assignableRole(MockAuditorRole, true), +]; + +const someNonAssignable = [ + assignableRole(MockOwnerRole, false), + assignableRole(MockUserAdminRole, true), + assignableRole(MockTemplateAdminRole, false), + assignableRole(MockAuditorRole, true), +]; + +export const Default: Story = { + args: { + roles: allAssignable, + }, +}; + +export const WithSelections: Story = { + args: { + roles: allAssignable, + selectedRoles: [MockUserAdminRole.name, MockAuditorRole.name], + }, +}; + +export const WithNonAssignableRoles: Story = { + args: { + roles: someNonAssignable, + }, +}; + +export const Loading: Story = { + args: { + roles: [], + loading: true, + }, +}; + +export const WithError: Story = { + args: { + roles: [], + error: mockApiError({ message: "Failed to fetch assignable roles." }), + }, +}; diff --git a/site/src/pages/CreateUserPage/RoleSelector.tsx b/site/src/pages/CreateUserPage/RoleSelector.tsx index f7a7e96d27..a68615202f 100644 --- a/site/src/pages/CreateUserPage/RoleSelector.tsx +++ b/site/src/pages/CreateUserPage/RoleSelector.tsx @@ -5,6 +5,7 @@ import type { AssignableRoles } from "#/api/typesGenerated"; import { Alert, AlertTitle } from "#/components/Alert/Alert"; import { Checkbox } from "#/components/Checkbox/Checkbox"; import { Skeleton } from "#/components/Skeleton/Skeleton"; +import { cn } from "#/utils/cn"; const roleDescriptions: Record = { owner: @@ -33,9 +34,7 @@ export const RoleSelector: FC = ({ error, }) => { const baseId = useId(); - const selectableRoles = roles.filter( - (r) => r.assignable && r.name !== "member", - ); + const selectableRoles = roles.filter((r) => r.name !== "member"); const handleToggle = (roleName: string) => { if (selectedRoles.includes(roleName)) { @@ -98,12 +97,18 @@ export const RoleSelector: FC = ({