mirror of
https://github.com/roleypoly/roleypoly.git
synced 2025-06-16 01:29:09 +00:00
feat: add access control
This commit is contained in:
parent
9c07ff0e54
commit
3f45153b66
47 changed files with 1084 additions and 164 deletions
|
@ -0,0 +1,16 @@
|
|||
import { presentableGuild } from '../../fixtures/storyData';
|
||||
import { EditableRoleList } from './EditableRoleList';
|
||||
|
||||
export default {
|
||||
title: 'Molecules/Editable Role List',
|
||||
component: EditableRoleList,
|
||||
args: {
|
||||
roles: presentableGuild.roles,
|
||||
selectedRoles: presentableGuild.data.categories[0].roles,
|
||||
unselectedRoles: presentableGuild.roles.filter(
|
||||
(r) => !presentableGuild.data.categories[0].roles.includes(r.id)
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
export const editableRoleList = (args) => <EditableRoleList {...args} />;
|
|
@ -0,0 +1,43 @@
|
|||
import { palette } from '@roleypoly/design-system/atoms/colors';
|
||||
import { transitions } from '@roleypoly/design-system/atoms/timings';
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
export const EditableRoleListStyled = styled.div`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
& > div {
|
||||
margin: 2.5px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const AddRoleButton = styled.div<{ long?: boolean }>`
|
||||
border: 2px solid ${palette.taupe500};
|
||||
color: ${palette.taupe500};
|
||||
border-radius: 24px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all ${transitions.actionable}s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
background-color: ${palette.taupe100};
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
${(props) =>
|
||||
props.long
|
||||
? css`
|
||||
padding: 0 14px;
|
||||
`
|
||||
: css`
|
||||
width: 32px;
|
||||
`};
|
||||
`;
|
|
@ -0,0 +1,103 @@
|
|||
import { Popover } from '@roleypoly/design-system/atoms/popover';
|
||||
import { Role } from '@roleypoly/design-system/atoms/role';
|
||||
import { RoleSearch } from '@roleypoly/design-system/molecules/role-search';
|
||||
import { Role as RoleT } from '@roleypoly/types';
|
||||
import { sortBy, uniq } from 'lodash';
|
||||
import React from 'react';
|
||||
import { GoPlus } from 'react-icons/go';
|
||||
import { AddRoleButton, EditableRoleListStyled } from './EditableRoleList.styled';
|
||||
|
||||
type Props = {
|
||||
roles: RoleT[];
|
||||
selectedRoles: RoleT['id'][];
|
||||
unselectedRoles: RoleT[];
|
||||
onChange: (roles: RoleT['id'][]) => void;
|
||||
};
|
||||
|
||||
export const EditableRoleList = (props: Props) => {
|
||||
const [searchOpen, setSearchOpen] = React.useState(false);
|
||||
|
||||
const handleRoleDelete = (role: RoleT) => () => {
|
||||
const updatedRoles = props.selectedRoles.filter((r) => r !== role.id);
|
||||
props.onChange(updatedRoles);
|
||||
};
|
||||
|
||||
const handleRoleAdd = (role: RoleT) => {
|
||||
const updatedRoles = uniq([...props.selectedRoles, role.id]);
|
||||
props.onChange(updatedRoles);
|
||||
setSearchOpen(false);
|
||||
};
|
||||
|
||||
const handleSearchOpen = () => {
|
||||
setSearchOpen(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<EditableRoleListStyled>
|
||||
{props.selectedRoles.length !== 0 ? (
|
||||
<>
|
||||
{sortBy(
|
||||
props.roles.filter((r) => props.selectedRoles.includes(r.id)),
|
||||
'position'
|
||||
).map((role) => (
|
||||
<Role
|
||||
key={role.id}
|
||||
role={role}
|
||||
selected={false}
|
||||
type="delete"
|
||||
onClick={handleRoleDelete(role)}
|
||||
/>
|
||||
))}
|
||||
<RoleAddButton onClick={handleSearchOpen} />
|
||||
</>
|
||||
) : (
|
||||
<RoleAddButton long onClick={handleSearchOpen} />
|
||||
)}
|
||||
<RoleSearchPopover
|
||||
isOpen={searchOpen}
|
||||
onExit={() => setSearchOpen(false)}
|
||||
unselectedRoles={props.unselectedRoles}
|
||||
onSelect={handleRoleAdd}
|
||||
/>
|
||||
</EditableRoleListStyled>
|
||||
);
|
||||
};
|
||||
|
||||
const RoleAddButton = (props: { onClick: () => void; long?: boolean }) => (
|
||||
<AddRoleButton
|
||||
data-tip="Add a role to the category"
|
||||
onClick={props.onClick}
|
||||
long={props.long}
|
||||
>
|
||||
{props.long && <>Add a role </>}
|
||||
<GoPlus />
|
||||
</AddRoleButton>
|
||||
);
|
||||
|
||||
const RoleSearchPopover = (props: {
|
||||
onSelect: (role: RoleT) => void;
|
||||
onExit: (type: string) => void;
|
||||
isOpen: boolean;
|
||||
unselectedRoles: RoleT[];
|
||||
}) => {
|
||||
const [searchTerm, setSearchTerm] = React.useState('');
|
||||
|
||||
return (
|
||||
<Popover
|
||||
position="top left"
|
||||
active={props.isOpen}
|
||||
canDefocus
|
||||
onExit={props.onExit}
|
||||
headContent={null}
|
||||
>
|
||||
{() => (
|
||||
<RoleSearch
|
||||
onSelect={props.onSelect}
|
||||
roles={props.unselectedRoles}
|
||||
searchTerm={searchTerm}
|
||||
onSearchUpdate={setSearchTerm}
|
||||
/>
|
||||
)}
|
||||
</Popover>
|
||||
);
|
||||
};
|
|
@ -0,0 +1 @@
|
|||
export * from './EditableRoleList';
|
Loading…
Add table
Add a link
Reference in a new issue