diff --git a/web/packages/shared/components/AccessRequests/NewRequest/ResourceList/Roles.tsx b/web/packages/shared/components/AccessRequests/NewRequest/ResourceList/Roles.tsx
deleted file mode 100644
index 5ec6e78f95ff1..0000000000000
--- a/web/packages/shared/components/AccessRequests/NewRequest/ResourceList/Roles.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-/**
- * Teleport
- * Copyright (C) 2024 Gravitational, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-
-import React from 'react';
-
-import { ListProps, StyledTable, renderActionCell } from './ResourceList';
-
-export function Roles(props: ListProps & { roles: string[] }) {
- const {
- roles = [],
- addedResources,
- addOrRemoveResource,
- requestStarted,
- } = props;
-
- return (
- ({ role }))}
- pagination={{ pagerPosition: 'top', pageSize: 10 }}
- isSearchable={true}
- columns={[
- {
- key: 'role',
- headerText: 'Role Name',
- isSortable: true,
- },
- {
- altKey: 'action-btn',
- render: ({ role }) =>
- renderActionCell(
- Boolean(addedResources.role[role]),
- requestStarted,
- () => addOrRemoveResource('role', role)
- ),
- },
- ]}
- emptyText="No Requestable Roles Found"
- />
- );
-}
diff --git a/web/packages/shared/components/AccessRequests/NewRequest/Roles/Roles.story.tsx b/web/packages/shared/components/AccessRequests/NewRequest/Roles/Roles.story.tsx
new file mode 100644
index 0000000000000..085bba838ab97
--- /dev/null
+++ b/web/packages/shared/components/AccessRequests/NewRequest/Roles/Roles.story.tsx
@@ -0,0 +1,53 @@
+/**
+ * Teleport
+ * Copyright (C) 2024 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import { useState } from 'react';
+
+import { Roles } from './Roles';
+
+export default {
+ title: 'Shared/AccessRequests/Roles',
+};
+
+export function Default() {
+ const [requested, setRequested] = useState(new Set());
+ return (
+
+ setRequested(prev => {
+ const newSet = new Set(prev);
+ newSet.has(role) ? newSet.delete(role) : newSet.add(role);
+ return newSet;
+ })
+ }
+ />
+ );
+}
+
+export function Disabled() {
+ return (
+ {}}
+ disabled
+ />
+ );
+}
diff --git a/web/packages/shared/components/AccessRequests/NewRequest/Roles/Roles.test.tsx b/web/packages/shared/components/AccessRequests/NewRequest/Roles/Roles.test.tsx
new file mode 100644
index 0000000000000..a49da62b82ed4
--- /dev/null
+++ b/web/packages/shared/components/AccessRequests/NewRequest/Roles/Roles.test.tsx
@@ -0,0 +1,70 @@
+/**
+ * Teleport
+ * Copyright (C) 2024 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import { within, screen } from '@testing-library/react';
+import { render } from 'design/utils/testing';
+
+import { Roles } from './Roles';
+
+test('renders requestable roles with a request option', async () => {
+ render(
+ {}}
+ />
+ );
+
+ const row = await screen.findByRole('row', {
+ name: /editor/i,
+ });
+
+ expect(
+ await within(row).findByRole('button', {
+ name: /request access/i,
+ })
+ ).toBeVisible();
+});
+
+test('renders requested roles with a remove option and requestable roles with an add option', async () => {
+ render(
+ {}}
+ />
+ );
+
+ const rowEditor = await screen.findByRole('row', {
+ name: /editor/i,
+ });
+ expect(
+ await within(rowEditor).findByRole('button', {
+ name: /remove/i,
+ })
+ ).toBeVisible();
+
+ const rowAccess = await screen.findByRole('row', {
+ name: /access/i,
+ });
+ expect(
+ await within(rowAccess).findByRole('button', {
+ name: /add to request/i,
+ })
+ ).toBeVisible();
+});
diff --git a/web/packages/shared/components/AccessRequests/NewRequest/Roles/Roles.tsx b/web/packages/shared/components/AccessRequests/NewRequest/Roles/Roles.tsx
new file mode 100644
index 0000000000000..69b56fda3ce93
--- /dev/null
+++ b/web/packages/shared/components/AccessRequests/NewRequest/Roles/Roles.tsx
@@ -0,0 +1,71 @@
+/**
+ * Teleport
+ * Copyright (C) 2024 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import Table, { Cell } from 'design/DataTable';
+import { ButtonPrimary, ButtonBorder } from 'design/Button';
+
+export function Roles(props: {
+ requestable: string[];
+ requested: Set;
+ onToggleRole(role: string): void;
+ /** Disables buttons.*/
+ disabled?: boolean;
+}) {
+ const addToRequestText = props.requested.size
+ ? '+ Add to Request'
+ : '+ Request Access';
+
+ return (
+ ({ role }))}
+ pagination={{ pagerPosition: 'top', pageSize: 10 }}
+ isSearchable={true}
+ columns={[
+ {
+ key: 'role',
+ headerText: 'Role Name',
+ isSortable: true,
+ },
+ {
+ altKey: 'action',
+ render: ({ role }) => {
+ const isAdded = props.requested.has(role);
+ const commonProps = {
+ disabled: props.disabled,
+ width: '137px',
+ size: 'small' as const,
+ onClick: () => props.onToggleRole(role),
+ };
+ return (
+
+ {isAdded ? (
+ Remove
+ ) : (
+
+ {addToRequestText}
+
+ )}
+ |
+ );
+ },
+ },
+ ]}
+ emptyText="No Requestable Roles Found"
+ />
+ );
+}
diff --git a/web/packages/shared/components/AccessRequests/NewRequest/ResourceList/index.ts b/web/packages/shared/components/AccessRequests/NewRequest/Roles/index.ts
similarity index 93%
rename from web/packages/shared/components/AccessRequests/NewRequest/ResourceList/index.ts
rename to web/packages/shared/components/AccessRequests/NewRequest/Roles/index.ts
index c00c1256c7617..0a6e77dddc722 100644
--- a/web/packages/shared/components/AccessRequests/NewRequest/ResourceList/index.ts
+++ b/web/packages/shared/components/AccessRequests/NewRequest/Roles/index.ts
@@ -16,4 +16,4 @@
* along with this program. If not, see .
*/
-export { ResourceList } from './ResourceList';
+export { Roles } from './Roles';
diff --git a/web/packages/shared/components/AccessRequests/NewRequest/index.ts b/web/packages/shared/components/AccessRequests/NewRequest/index.ts
index 61c58603809d1..dc3c2614a9d7d 100644
--- a/web/packages/shared/components/AccessRequests/NewRequest/index.ts
+++ b/web/packages/shared/components/AccessRequests/NewRequest/index.ts
@@ -17,7 +17,7 @@
*/
export * from './RequestCheckout';
-export * from './ResourceList';
+export * from './Roles';
export type { ResourceMap, RequestableResourceKind } from './resource';
export { getEmptyResourceState } from './resource';
export { isKubeClusterWithNamespaces } from './kube';