feat(site): show non-assignable roles as disabled (#24846)

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.

<img width="672" height="392" alt="Screenshot 2026-04-30 at 1 40 44 PM"
src="https://github.com/user-attachments/assets/9d17c06a-fcf0-4a49-9557-99cbddb18cba"
/>
This commit is contained in:
Jeremy Ruppel
2026-04-30 13:52:55 -04:00
committed by GitHub
parent 201de14a9d
commit 1b2bd4a0da
2 changed files with 79 additions and 4 deletions
@@ -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<typeof RoleSelector> = {
title: "pages/CreateUserPage/RoleSelector",
component: RoleSelector,
args: {
onChange: action("change"),
selectedRoles: [],
},
};
export default meta;
type Story = StoryObj<typeof RoleSelector>;
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." }),
},
};
@@ -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<string, string> = {
owner:
@@ -33,9 +34,7 @@ export const RoleSelector: FC<RoleSelectorProps> = ({
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<RoleSelectorProps> = ({
<label
key={role.name}
htmlFor={checkboxId}
className="flex items-start gap-2 cursor-pointer"
className={cn(
"flex items-start gap-2",
role.assignable
? "cursor-pointer"
: "cursor-not-allowed opacity-50",
)}
>
<Checkbox
id={checkboxId}
checked={selectedRoles.includes(role.name)}
onCheckedChange={() => handleToggle(role.name)}
disabled={!role.assignable}
className="mt-1 shrink-0"
/>
<div className="flex flex-col">