add role search prototype

This commit is contained in:
41666 2021-07-08 14:53:40 -05:00
parent 38e2aa1c50
commit ef63260bd5
6 changed files with 113 additions and 23 deletions

View file

@ -45,17 +45,17 @@ export const RoleContainer = styled.div`
} }
`; `;
export const AddRoleButton = styled.div` export const AddRoleButton = styled.div<{ long?: boolean }>`
border: 2px solid ${palette.taupe500}; border: 2px solid ${palette.taupe500};
color: ${palette.taupe500}; color: ${palette.taupe500};
border-radius: 24px; border-radius: 24px;
width: 32px;
height: 32px; height: 32px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
cursor: pointer; cursor: pointer;
transition: all ${transitions.actionable}s ease-in-out; transition: all ${transitions.actionable}s ease-in-out;
&:hover { &:hover {
background-color: ${palette.taupe100}; background-color: ${palette.taupe100};
transform: translateY(-2px); transform: translateY(-2px);
@ -66,4 +66,13 @@ export const AddRoleButton = styled.div`
transform: translateY(0); transform: translateY(0);
box-shadow: none; box-shadow: none;
} }
${(props) =>
props.long
? css`
padding: 0 14px;
`
: css`
width: 32px;
`};
`; `;

View file

@ -1,9 +1,11 @@
import { Popover } from '@roleypoly/design-system/atoms/popover';
import { Role } from '@roleypoly/design-system/atoms/role'; import { Role } from '@roleypoly/design-system/atoms/role';
import { TextInput } from '@roleypoly/design-system/atoms/text-input'; import { TextInput } from '@roleypoly/design-system/atoms/text-input';
import { Toggle } from '@roleypoly/design-system/atoms/toggle'; import { Toggle } from '@roleypoly/design-system/atoms/toggle';
import { Text } from '@roleypoly/design-system/atoms/typography'; import { Text } from '@roleypoly/design-system/atoms/typography';
import { RoleSearch } from '@roleypoly/design-system/molecules/role-search';
import { Category as CategoryT, CategoryType, Role as RoleT } from '@roleypoly/types'; import { Category as CategoryT, CategoryType, Role as RoleT } from '@roleypoly/types';
import { sortBy } from 'lodash'; import { sortBy, uniq } from 'lodash';
import * as React from 'react'; import * as React from 'react';
import { GoPlus } from 'react-icons/go'; import { GoPlus } from 'react-icons/go';
import ReactTooltip from 'react-tooltip'; import ReactTooltip from 'react-tooltip';
@ -13,10 +15,13 @@ export type CategoryProps = {
title: string; title: string;
roles: RoleT[]; roles: RoleT[];
category: CategoryT; category: CategoryT;
unselectedRoles: RoleT[];
onChange: (updatedCategory: CategoryT) => void; onChange: (updatedCategory: CategoryT) => void;
}; };
export const EditorCategory = (props: CategoryProps) => { export const EditorCategory = (props: CategoryProps) => {
const [searchOpen, setSearchOpen] = React.useState(false);
const updateValue = <T extends keyof CategoryT>(key: T, value: CategoryT[T]) => { const updateValue = <T extends keyof CategoryT>(key: T, value: CategoryT[T]) => {
props.onChange({ ...props.category, [key]: value }); props.onChange({ ...props.category, [key]: value });
}; };
@ -26,6 +31,16 @@ export const EditorCategory = (props: CategoryProps) => {
updateValue('roles', updatedRoles); updateValue('roles', updatedRoles);
}; };
const handleRoleAdd = (role: RoleT) => {
const updatedRoles = uniq([...props.category.roles, role.id]);
updateValue('roles', updatedRoles);
setSearchOpen(false);
};
const handleSearchOpen = () => {
setSearchOpen(true);
};
return ( return (
<Box> <Box>
<Section> <Section>
@ -65,6 +80,8 @@ export const EditorCategory = (props: CategoryProps) => {
<Text>Roles</Text> <Text>Roles</Text>
</div> </div>
<RoleContainer> <RoleContainer>
{props.roles.length > 0 ? (
<>
{sortBy(props.roles, 'position').map((role) => ( {sortBy(props.roles, 'position').map((role) => (
<Role <Role
key={role.id} key={role.id}
@ -74,12 +91,21 @@ export const EditorCategory = (props: CategoryProps) => {
onClick={handleRoleDelete(role)} onClick={handleRoleDelete(role)}
/> />
))} ))}
<AddRoleButton <RoleAddButton onClick={handleSearchOpen} tooltipId={props.category.id} />
data-tip="Add a role to the category" </>
data-for={props.category.id} ) : (
> <RoleAddButton
<GoPlus /> long
</AddRoleButton> onClick={handleSearchOpen}
tooltipId={props.category.id}
/>
)}
<RoleSearchPopover
isOpen={searchOpen}
onExit={() => setSearchOpen(false)}
unselectedRoles={props.unselectedRoles}
onSelect={handleRoleAdd}
/>
</RoleContainer> </RoleContainer>
</Section> </Section>
@ -87,3 +113,47 @@ export const EditorCategory = (props: CategoryProps) => {
</Box> </Box>
); );
}; };
const RoleAddButton = (props: {
onClick: () => void;
tooltipId: string;
long?: boolean;
}) => (
<AddRoleButton
data-tip="Add a role to the category"
data-for={props.tooltipId}
onClick={props.onClick}
long={props.long}
>
{props.long && <>Add a role&nbsp;&nbsp;</>}
<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>
);
};

View file

@ -17,7 +17,6 @@ export type EditorShellProps = {
export const EditorShell = (props: EditorShellProps) => { export const EditorShell = (props: EditorShellProps) => {
const [guild, setGuild] = React.useState<PresentableGuild>(props.guild); const [guild, setGuild] = React.useState<PresentableGuild>(props.guild);
const [reorderMode, setReorderMode] = React.useState<boolean>(false);
React.useEffect(() => { React.useEffect(() => {
setGuild(props.guild); setGuild(props.guild);
@ -43,10 +42,10 @@ export const EditorShell = (props: EditorShellProps) => {
props.onGuildChange?.(guild); props.onGuildChange?.(guild);
}; };
const hasChanges = React.useMemo( const hasChanges = React.useMemo(() => !deepEqual(guild.data, props.guild.data), [
() => !deepEqual(guild.data, props.guild.data), guild.data,
[guild.data, props.guild.data] props.guild.data,
); ]);
return ( return (
<> <>

View file

@ -6,7 +6,7 @@ import { EditorCategory } from '@roleypoly/design-system/molecules/editor-catego
import { CategoryContainer } from '@roleypoly/design-system/organisms/role-picker/RolePicker.styled'; import { CategoryContainer } from '@roleypoly/design-system/organisms/role-picker/RolePicker.styled';
import { Category, CategoryType, PresentableGuild, Role } from '@roleypoly/types'; import { Category, CategoryType, PresentableGuild, Role } from '@roleypoly/types';
import KSUID from 'ksuid'; import KSUID from 'ksuid';
import { sortBy } from 'lodash'; import { flatten, sortBy } from 'lodash';
import React from 'react'; import React from 'react';
import { DragDropContext, Draggable, Droppable, DropResult } from 'react-beautiful-dnd'; import { DragDropContext, Draggable, Droppable, DropResult } from 'react-beautiful-dnd';
import { CgReorder } from 'react-icons/cg'; import { CgReorder } from 'react-icons/cg';
@ -31,6 +31,11 @@ const forceOrder = (categories: Category[]) =>
export const ServerCategoryEditor = (props: Props) => { export const ServerCategoryEditor = (props: Props) => {
const [reorderMode, setReorderMode] = React.useState(false); const [reorderMode, setReorderMode] = React.useState(false);
const unselectedRoles = React.useMemo(() => {
const selectedRoles = flatten(props.guild.data.categories.map((c) => c.roles));
return props.guild.roles.filter((r) => !selectedRoles.includes(r.id));
}, [props.guild.data.categories, props.guild.roles]);
const updateSingleCategory = (category: Category) => { const updateSingleCategory = (category: Category) => {
const newCategories = props.guild.data.categories.map((c) => { const newCategories = props.guild.data.categories.map((c) => {
if (c.id === category.id) { if (c.id === category.id) {
@ -85,6 +90,7 @@ export const ServerCategoryEditor = (props: Props) => {
<EditorCategory <EditorCategory
category={category} category={category}
title={category.name} title={category.name}
unselectedRoles={unselectedRoles}
roles={ roles={
category.roles category.roles
.map((role) => props.guild.roles.find((r) => r.id === role)) .map((role) => props.guild.roles.find((r) => r.id === role))

View file

@ -19,6 +19,7 @@
"react-helmet": "^6.1.0", "react-helmet": "^6.1.0",
"react-icons": "^4.2.0", "react-icons": "^4.2.0",
"react-is": "^17.0.2", "react-is": "^17.0.2",
"react-tiny-popover": "^6.0.5",
"react-tooltip": "^4.2.21", "react-tooltip": "^4.2.21",
"styled-components": "^5.3.0", "styled-components": "^5.3.0",
"styled-normalize": "^8.0.7" "styled-normalize": "^8.0.7"

View file

@ -14183,6 +14183,11 @@ react-textarea-autosize@^8.3.0:
use-composed-ref "^1.0.0" use-composed-ref "^1.0.0"
use-latest "^1.0.0" use-latest "^1.0.0"
react-tiny-popover@^6.0.5:
version "6.0.5"
resolved "https://registry.yarnpkg.com/react-tiny-popover/-/react-tiny-popover-6.0.5.tgz#2d5eb21d8a2758396ffa5bf5b761baac87dd6297"
integrity sha512-na6ZghMy5kqTPFSATb1pdSHO+/MikZvUxNk+zjXlz+gMXgiaOVuik5AiC5Oyj4yHpPf0nxoOmVQOmOmuDob6+A==
react-tooltip@^4.2.21: react-tooltip@^4.2.21:
version "4.2.21" version "4.2.21"
resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-4.2.21.tgz#840123ed86cf33d50ddde8ec8813b2960bfded7f" resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-4.2.21.tgz#840123ed86cf33d50ddde8ec8813b2960bfded7f"