mirror of
https://github.com/roleypoly/roleypoly.git
synced 2025-04-25 11:59:11 +00:00
add role search prototype
This commit is contained in:
parent
38e2aa1c50
commit
ef63260bd5
6 changed files with 113 additions and 23 deletions
|
@ -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;
|
||||||
|
`};
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -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,21 +80,32 @@ export const EditorCategory = (props: CategoryProps) => {
|
||||||
<Text>Roles</Text>
|
<Text>Roles</Text>
|
||||||
</div>
|
</div>
|
||||||
<RoleContainer>
|
<RoleContainer>
|
||||||
{sortBy(props.roles, 'position').map((role) => (
|
{props.roles.length > 0 ? (
|
||||||
<Role
|
<>
|
||||||
key={role.id}
|
{sortBy(props.roles, 'position').map((role) => (
|
||||||
role={role}
|
<Role
|
||||||
selected={false}
|
key={role.id}
|
||||||
type="delete"
|
role={role}
|
||||||
onClick={handleRoleDelete(role)}
|
selected={false}
|
||||||
|
type="delete"
|
||||||
|
onClick={handleRoleDelete(role)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<RoleAddButton onClick={handleSearchOpen} tooltipId={props.category.id} />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<RoleAddButton
|
||||||
|
long
|
||||||
|
onClick={handleSearchOpen}
|
||||||
|
tooltipId={props.category.id}
|
||||||
/>
|
/>
|
||||||
))}
|
)}
|
||||||
<AddRoleButton
|
<RoleSearchPopover
|
||||||
data-tip="Add a role to the category"
|
isOpen={searchOpen}
|
||||||
data-for={props.category.id}
|
onExit={() => setSearchOpen(false)}
|
||||||
>
|
unselectedRoles={props.unselectedRoles}
|
||||||
<GoPlus />
|
onSelect={handleRoleAdd}
|
||||||
</AddRoleButton>
|
/>
|
||||||
</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 </>}
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
@ -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 (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Add table
Reference in a new issue