From 2d589b988f7590aa888c2d43e4a0d4d16cd1b7da Mon Sep 17 00:00:00 2001 From: Katalina Okano Date: Tue, 6 Jul 2021 09:59:04 -0500 Subject: [PATCH] add actions, reordering base interactions --- packages/api/index.ts | 2 + .../EditableServerMessage.stories.tsx | 26 +++ .../EditableServerMessage.tsx | 37 ++++ .../editable-server-message/index.ts | 1 + .../EditorCategoryShort.spec.tsx | 12 -- .../EditorCategoryShort.stories.tsx | 30 ---- .../EditorCategoryShort.styled.ts | 60 ------- .../EditorCategoryShort.tsx | 31 ---- .../molecules/editor-category-short/index.ts | 1 - .../EditorCategory.stories.tsx | 35 ++-- .../editor-category/EditorCategory.styled.ts | 25 ++- .../editor-category/EditorCategory.tsx | 166 ++++-------------- .../molecules/editor-category/index.ts | 2 +- .../EditorCategoriesTab.stories.tsx | 12 -- .../EditorCategoriesTab.styled.ts | 8 - .../EditorCategoriesTab.tsx | 39 ---- .../organisms/editor-categories-tab/index.ts | 1 - .../editor-details-tab/EditorDetailsTab.tsx | 20 --- .../organisms/editor-details-tab/index.ts | 1 - .../editor-shell/EditorShell.stories.tsx | 10 +- .../organisms/editor-shell/EditorShell.tsx | 76 ++------ .../ServerCategoryEditor.styled.ts | 42 +++++ .../ServerCategoryEditor.tsx | 158 +++++++++++++++++ .../organisms/server-category-editor/index.ts | 1 + .../templates/editor/Editor.stories.tsx | 10 ++ packages/web/src/pages/editor.tsx | 4 +- 26 files changed, 383 insertions(+), 427 deletions(-) create mode 100644 packages/design-system/molecules/editable-server-message/EditableServerMessage.stories.tsx create mode 100644 packages/design-system/molecules/editable-server-message/EditableServerMessage.tsx create mode 100644 packages/design-system/molecules/editable-server-message/index.ts delete mode 100644 packages/design-system/molecules/editor-category-short/EditorCategoryShort.spec.tsx delete mode 100644 packages/design-system/molecules/editor-category-short/EditorCategoryShort.stories.tsx delete mode 100644 packages/design-system/molecules/editor-category-short/EditorCategoryShort.styled.ts delete mode 100644 packages/design-system/molecules/editor-category-short/EditorCategoryShort.tsx delete mode 100644 packages/design-system/molecules/editor-category-short/index.ts delete mode 100644 packages/design-system/organisms/editor-categories-tab/EditorCategoriesTab.stories.tsx delete mode 100644 packages/design-system/organisms/editor-categories-tab/EditorCategoriesTab.styled.ts delete mode 100644 packages/design-system/organisms/editor-categories-tab/EditorCategoriesTab.tsx delete mode 100644 packages/design-system/organisms/editor-categories-tab/index.ts delete mode 100644 packages/design-system/organisms/editor-details-tab/EditorDetailsTab.tsx delete mode 100644 packages/design-system/organisms/editor-details-tab/index.ts create mode 100644 packages/design-system/organisms/server-category-editor/ServerCategoryEditor.styled.ts create mode 100644 packages/design-system/organisms/server-category-editor/ServerCategoryEditor.tsx create mode 100644 packages/design-system/organisms/server-category-editor/index.ts diff --git a/packages/api/index.ts b/packages/api/index.ts index b0669da..36e40f0 100644 --- a/packages/api/index.ts +++ b/packages/api/index.ts @@ -8,6 +8,7 @@ import { LoginBounce } from './handlers/login-bounce'; import { LoginCallback } from './handlers/login-callback'; import { RevokeSession } from './handlers/revoke-session'; import { SyncFromLegacy } from './handlers/sync-from-legacy'; +import { UpdateGuild } from './handlers/update-guild'; import { UpdateRoles } from './handlers/update-roles'; import { Router } from './router'; import { respond } from './utils/api-tools'; @@ -28,6 +29,7 @@ router.add('POST', 'revoke-session', RevokeSession); router.add('GET', 'get-slug', GetSlug); router.add('GET', 'get-picker-data', GetPickerData); router.add('PATCH', 'update-roles', UpdateRoles); +router.add('PATCH', 'update-guild', UpdateGuild); router.add('POST', 'sync-from-legacy', SyncFromLegacy); router.add('POST', 'clear-guild-cache', ClearGuildCache); diff --git a/packages/design-system/molecules/editable-server-message/EditableServerMessage.stories.tsx b/packages/design-system/molecules/editable-server-message/EditableServerMessage.stories.tsx new file mode 100644 index 0000000..aa1fa0f --- /dev/null +++ b/packages/design-system/molecules/editable-server-message/EditableServerMessage.stories.tsx @@ -0,0 +1,26 @@ +import * as React from 'react'; +import { mastheadSlugs } from '../../fixtures/storyData'; +import { EditableServerMessage } from './EditableServerMessage'; +export default { + title: 'Molecules/Editable Server Message', + component: EditableServerMessage, + args: { + value: 'Hello World', + guild: mastheadSlugs[1], + }, +}; + +export const editableServerMessage = (args) => { + const [value, setValue] = React.useState(args.value); + React.useEffect(() => { + setValue(args.value); + }, [args.value]); + + return ( + setValue(message)} + /> + ); +}; diff --git a/packages/design-system/molecules/editable-server-message/EditableServerMessage.tsx b/packages/design-system/molecules/editable-server-message/EditableServerMessage.tsx new file mode 100644 index 0000000..40cbb20 --- /dev/null +++ b/packages/design-system/molecules/editable-server-message/EditableServerMessage.tsx @@ -0,0 +1,37 @@ +import { palette } from '@roleypoly/design-system/atoms/colors'; +import { FaderOpacity } from '@roleypoly/design-system/atoms/fader'; +import { MultilineTextInput } from '@roleypoly/design-system/atoms/text-input'; +import { + AmbientLarge, + Text as TextTypo, +} from '@roleypoly/design-system/atoms/typography'; +import { MessageBox } from '@roleypoly/design-system/organisms/role-picker/RolePicker.styled'; +import { GuildSlug } from '@roleypoly/types'; +import { GoEyeClosed } from 'react-icons/go'; + +type Props = { + guild: GuildSlug; + onChange: (newMessage: string) => void; + value: string; +}; + +export const EditableServerMessage = (props: Props) => ( + + Server Message + props.onChange(event.target.value)} + placeholder={`Hey friend from ${props.guild.name}! Pick your roles!`} + > + {props.value} + + + Shows a message to your server members. + +  Since the message is empty, this won't show up.    + + + + +); diff --git a/packages/design-system/molecules/editable-server-message/index.ts b/packages/design-system/molecules/editable-server-message/index.ts new file mode 100644 index 0000000..ba5bfc9 --- /dev/null +++ b/packages/design-system/molecules/editable-server-message/index.ts @@ -0,0 +1 @@ +export * from './EditableServerMessage'; diff --git a/packages/design-system/molecules/editor-category-short/EditorCategoryShort.spec.tsx b/packages/design-system/molecules/editor-category-short/EditorCategoryShort.spec.tsx deleted file mode 100644 index e6c6cf7..0000000 --- a/packages/design-system/molecules/editor-category-short/EditorCategoryShort.spec.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { mockCategory } from '@roleypoly/design-system/fixtures/storyData'; -import { render } from '@testing-library/react'; -import { EditorCategoryShort } from './EditorCategoryShort'; - -it('triggers onOpen when clicked', async () => { - const onOpen = jest.fn(); - const view = render(); - - view.getByRole('menuitem')?.click(); - - expect(onOpen).toHaveBeenCalled(); -}); diff --git a/packages/design-system/molecules/editor-category-short/EditorCategoryShort.stories.tsx b/packages/design-system/molecules/editor-category-short/EditorCategoryShort.stories.tsx deleted file mode 100644 index bc2b0a5..0000000 --- a/packages/design-system/molecules/editor-category-short/EditorCategoryShort.stories.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { mockCategory } from '@roleypoly/design-system/fixtures/storyData'; -import ReactTooltip from 'react-tooltip'; -import styled from 'styled-components'; -import { EditorCategoryShort } from './EditorCategoryShort'; - -const decorator = (story) => ( - - {story()} - - -); - -export default { - title: 'Molecules/Short Category', - component: EditorCategoryShort, - args: { - category: mockCategory, - }, - decorators: [decorator], -}; - -const Wrapper = styled.div` - box-shadow: 0 0 10px #000; -`; - -export const shortEditor = (args) => ; -export const shortEditorHidden = (args) => ; -shortEditorHidden.args = { - category: { ...mockCategory, hidden: true }, -}; diff --git a/packages/design-system/molecules/editor-category-short/EditorCategoryShort.styled.ts b/packages/design-system/molecules/editor-category-short/EditorCategoryShort.styled.ts deleted file mode 100644 index 2b9c2a0..0000000 --- a/packages/design-system/molecules/editor-category-short/EditorCategoryShort.styled.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { transitions } from '@roleypoly/design-system/atoms/timings'; -import styled from 'styled-components'; - -export const GrabberBox = styled.div` - display: flex; - align-items: center; - justify-content: center; - margin-right: 1em; - flex: 0; - cursor: grab; - position: relative; - - ::before { - content: ''; - position: absolute; - top: -5px; - left: -8px; - width: 32px; - height: 32px; - background-color: rgba(0, 0, 0, 0.25); - opacity: 0; - border-radius: 50%; - transition: opacity ${transitions.actionable}s ease-in-out; - } - - &:hover { - ::before { - opacity: 1; - } - } -`; - -export const Opener = styled.div` - opacity: 0; - transition: opacity ${transitions.actionable}s ease-in-out; -`; - -export const Container = styled.div` - display: flex; - padding: 1em; - cursor: pointer; - - &:hover { - ${Opener} { - opacity: 1; - } - } -`; - -export const Name = styled.div` - position: relative; - top: -2px; - margin-right: 1em; -`; - -export const Flags = styled.div``; - -export const Void = styled.div` - flex: 1; -`; diff --git a/packages/design-system/molecules/editor-category-short/EditorCategoryShort.tsx b/packages/design-system/molecules/editor-category-short/EditorCategoryShort.tsx deleted file mode 100644 index 67f1712..0000000 --- a/packages/design-system/molecules/editor-category-short/EditorCategoryShort.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { Category } from '@roleypoly/types'; -import { DragEventHandler, MouseEventHandler } from 'react'; -import { GoEyeClosed, GoGrabber, GoKebabHorizontal } from 'react-icons/go'; -import { - Container, - Flags, - GrabberBox, - Name, - Opener, - Void, -} from './EditorCategoryShort.styled'; - -type ShortProps = { - category: Category; - onDrag?: DragEventHandler; - onOpen?: MouseEventHandler; -}; - -export const EditorCategoryShort = (props: ShortProps) => ( - - - - - {props.category.name} - {props.category.hidden && } - - - - - -); diff --git a/packages/design-system/molecules/editor-category-short/index.ts b/packages/design-system/molecules/editor-category-short/index.ts deleted file mode 100644 index 0840f96..0000000 --- a/packages/design-system/molecules/editor-category-short/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './EditorCategoryShort'; diff --git a/packages/design-system/molecules/editor-category/EditorCategory.stories.tsx b/packages/design-system/molecules/editor-category/EditorCategory.stories.tsx index 332c913..e7f2156 100644 --- a/packages/design-system/molecules/editor-category/EditorCategory.stories.tsx +++ b/packages/design-system/molecules/editor-category/EditorCategory.stories.tsx @@ -1,19 +1,30 @@ import * as React from 'react'; -import { mockCategory, roleCategory, roleCategory2 } from '../../fixtures/storyData'; +import { mockCategory, roleCategory } from '../../fixtures/storyData'; import { EditorCategory } from './EditorCategory'; export default { - title: 'Molecules/Editor/Category', + title: 'Molecules/Editor Category', + component: EditorCategory, + args: { + title: 'Pronouns', + roles: roleCategory, + category: mockCategory, + selectedRoles: [], + }, }; -export const CategoryEditor = () => { - const [categoryData, setCategoryData] = React.useState(mockCategory); - return ( - setCategoryData(category)} - uncategorizedRoles={roleCategory} - guildRoles={[...roleCategory, ...roleCategory2]} - /> - ); +export const Default = (args) => { + return ; +}; +export const Single = (args) => { + return ; +}; +Single.args = { + type: 'single', +}; +export const Multi = (args) => { + return ; +}; +Multi.args = { + type: 'multi', }; diff --git a/packages/design-system/molecules/editor-category/EditorCategory.styled.ts b/packages/design-system/molecules/editor-category/EditorCategory.styled.ts index b3cf806..95fcee5 100644 --- a/packages/design-system/molecules/editor-category/EditorCategory.styled.ts +++ b/packages/design-system/molecules/editor-category/EditorCategory.styled.ts @@ -1,13 +1,20 @@ import styled from 'styled-components'; -export const RoleContainer = styled.div` +export const Head = styled.div` + margin: 7px 5px; + line-height: 200%; display: flex; - margin: 10px; - flex-wrap: wrap; - - & > div { - /* This should be a Role element */ - border: 1px solid rgba(0, 0, 0, 0.15); - margin: 1px; - } + align-items: center; + justify-content: space-between; +`; + +export const HeadTitle = styled.div` + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +`; + +export const HeadSub = styled.div` + flex-shrink: 0; + margin-top: -4px; `; diff --git a/packages/design-system/molecules/editor-category/EditorCategory.tsx b/packages/design-system/molecules/editor-category/EditorCategory.tsx index 57787ef..5d8d57f 100644 --- a/packages/design-system/molecules/editor-category/EditorCategory.tsx +++ b/packages/design-system/molecules/editor-category/EditorCategory.tsx @@ -1,142 +1,50 @@ -import { FaderOpacity } from '@roleypoly/design-system/atoms/fader'; -import { Popover } from '@roleypoly/design-system/atoms/popover'; -import { Role } from '@roleypoly/design-system/atoms/role'; -import { Space } from '@roleypoly/design-system/atoms/space'; -import { TextInput, TextInputWithIcon } from '@roleypoly/design-system/atoms/text-input'; -import { Toggle } from '@roleypoly/design-system/atoms/toggle'; +import { TextInput } from '@roleypoly/design-system/atoms/text-input'; import { Text } from '@roleypoly/design-system/atoms/typography'; -import { RoleSearch } from '@roleypoly/design-system/molecules/role-search'; -import { Category, CategoryType, Role as RoleType } from '@roleypoly/types'; +import { Category as CategoryT, Role as RoleT } from '@roleypoly/types'; import * as React from 'react'; -import { GoSearch } from 'react-icons/go'; -import { RoleContainer } from './EditorCategory.styled'; +import ReactTooltip from 'react-tooltip'; +import styled from 'styled-components'; +import { Head, HeadTitle } from './EditorCategory.styled'; -type Props = { - category: Category; - uncategorizedRoles: RoleType[]; - guildRoles: RoleType[]; - onChange: (category: Category) => void; +export type CategoryProps = { + title: string; + roles: RoleT[]; + category: CategoryT; + selectedRoles: string[]; + onChange: (updatedCategory: CategoryT) => void; + type: 'single' | 'multi'; }; -const typeEnumToSwitch = (typeData: CategoryType) => { - if (typeData === CategoryType.Single) { - return 'Single'; - } else { - return 'Multiple'; - } -}; +const Category = styled.div` + display: flex; + flex-wrap: wrap; +`; -const switchToTypeEnum = (typeData: 'Single' | 'Multiple') => { - if (typeData === 'Single') { - return CategoryType.Single; - } else { - return CategoryType.Multi; - } -}; +const Container = styled.div` + overflow: hidden; + padding: 5px; +`; -export const EditorCategory = (props: Props) => { - const [roleSearchPopoverActive, setRoleSearchPopoverActive] = React.useState(false); - const [roleSearchTerm, updateSearchTerm] = React.useState(''); - - const onUpdate = - (key: keyof typeof props.category, pred?: (newValue: any) => any) => - (newValue: any) => { - props.onChange({ - ...props.category, - [key]: pred ? pred(newValue) : newValue, - }); - }; - - const handleRoleSelect = (role: RoleType) => { - setRoleSearchPopoverActive(false); - updateSearchTerm(''); - props.onChange({ - ...props.category, - roles: [...props.category.roles, role.id], - }); - }; - - const handleRoleDeselect = (role: RoleType) => () => { - props.onChange({ - ...props.category, - roles: props.category.roles.filter((x) => x !== role.id), - }); +export const EditorCategory = (props: CategoryProps) => { + const updateValue = (key: T, value: CategoryT[T]) => { + props.onChange({ ...props.category, [key]: value }); }; return ( -
- Category Name - x.target.value)} - /> - - - -
- - x ? CategoryType.Multi : CategoryType.Single - )} - > - Allow users to pick multiple roles - -
- - -
- - Hide category from users - -
- - - Roles - setRoleSearchPopoverActive(false)} - > - {() => ( - updateSearchTerm(newTerm)} + <> + + +
+ Category Name +
+ updateValue('name', event.target.value)} /> - )} -
- - } - placeholder={'Type or drag a role...'} - onFocus={() => setRoleSearchPopoverActive(true)} - value={roleSearchTerm} - onChange={(x) => updateSearchTerm(x.target.value)} - /> - - {props.category.roles.map((id) => { - const role = props.guildRoles.find((x) => x.id === id); - if (!role) { - return <>; - } - - return ( - - ); - })} - - -
+ + + + + ); }; diff --git a/packages/design-system/molecules/editor-category/index.ts b/packages/design-system/molecules/editor-category/index.ts index 67c2556..f50a935 100644 --- a/packages/design-system/molecules/editor-category/index.ts +++ b/packages/design-system/molecules/editor-category/index.ts @@ -1 +1 @@ -export * from './EditorCategory'; +export { EditorCategory } from './EditorCategory'; diff --git a/packages/design-system/organisms/editor-categories-tab/EditorCategoriesTab.stories.tsx b/packages/design-system/organisms/editor-categories-tab/EditorCategoriesTab.stories.tsx deleted file mode 100644 index 2c9bfd6..0000000 --- a/packages/design-system/organisms/editor-categories-tab/EditorCategoriesTab.stories.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { guildEnum } from '@roleypoly/design-system/fixtures/storyData'; -import { EditorCategoriesTab } from './EditorCategoriesTab'; - -export default { - title: 'Organisms/Editor/Categories Tab', - component: EditorCategoriesTab, - args: { - guild: guildEnum.guilds[0], - }, -}; - -export const categoriesTab = (args) => ; diff --git a/packages/design-system/organisms/editor-categories-tab/EditorCategoriesTab.styled.ts b/packages/design-system/organisms/editor-categories-tab/EditorCategoriesTab.styled.ts deleted file mode 100644 index 61c0931..0000000 --- a/packages/design-system/organisms/editor-categories-tab/EditorCategoriesTab.styled.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { palette } from '@roleypoly/design-system/atoms/colors'; -import styled from 'styled-components'; - -export const CategoryContainer = styled.div` - background-color: ${palette.taupe100}; - padding: 10px; - margin: 15px 0; -`; diff --git a/packages/design-system/organisms/editor-categories-tab/EditorCategoriesTab.tsx b/packages/design-system/organisms/editor-categories-tab/EditorCategoriesTab.tsx deleted file mode 100644 index 3ce66d0..0000000 --- a/packages/design-system/organisms/editor-categories-tab/EditorCategoriesTab.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { TabDepth } from '@roleypoly/design-system/atoms/tab-view/TabView.styled'; -import { EditorCategory } from '@roleypoly/design-system/molecules/editor-category'; -import { EditorCategoryShort } from '@roleypoly/design-system/molecules/editor-category-short/EditorCategoryShort'; -import { EditorShellProps } from '@roleypoly/design-system/organisms/editor-shell'; -import { Category } from '@roleypoly/types'; -import { sortBy } from 'lodash'; -import * as React from 'react'; -import { CategoryContainer } from './EditorCategoriesTab.styled'; - -export const EditorCategoriesTab = (props: EditorShellProps) => { - const [openStates, setOpenStates] = React.useState([]); - - const onCategoryOpen = (id: Category['id']) => () => { - setOpenStates([...new Set(openStates).add(id)]); - }; - - return ( - - {sortBy(props.guild.data.categories, ['position', 'id']).map((category, idx) => - openStates.includes(category.id) ? ( - - props.onCategoryChange?.(category)} - /> - - ) : ( - - ) - )} - - ); -}; diff --git a/packages/design-system/organisms/editor-categories-tab/index.ts b/packages/design-system/organisms/editor-categories-tab/index.ts deleted file mode 100644 index 43cb362..0000000 --- a/packages/design-system/organisms/editor-categories-tab/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './EditorCategoriesTab'; diff --git a/packages/design-system/organisms/editor-details-tab/EditorDetailsTab.tsx b/packages/design-system/organisms/editor-details-tab/EditorDetailsTab.tsx deleted file mode 100644 index e647c61..0000000 --- a/packages/design-system/organisms/editor-details-tab/EditorDetailsTab.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { Space } from '@roleypoly/design-system/atoms/space'; -import { TabDepth } from '@roleypoly/design-system/atoms/tab-view/TabView.styled'; -import { MultilineTextInput } from '@roleypoly/design-system/atoms/text-input'; -import { Text } from '@roleypoly/design-system/atoms/typography'; -import { EditorShellProps } from '@roleypoly/design-system/organisms/editor-shell'; -import * as React from 'react'; - -export const EditorDetailsTab = (props: EditorShellProps) => { - return ( - - - Server Message - props.onMessageChange?.(eventData.target.value)} - /> - - - ); -}; diff --git a/packages/design-system/organisms/editor-details-tab/index.ts b/packages/design-system/organisms/editor-details-tab/index.ts deleted file mode 100644 index b40da5d..0000000 --- a/packages/design-system/organisms/editor-details-tab/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './EditorDetailsTab'; diff --git a/packages/design-system/organisms/editor-shell/EditorShell.stories.tsx b/packages/design-system/organisms/editor-shell/EditorShell.stories.tsx index 296a3c8..351ef29 100644 --- a/packages/design-system/organisms/editor-shell/EditorShell.stories.tsx +++ b/packages/design-system/organisms/editor-shell/EditorShell.stories.tsx @@ -1,12 +1,20 @@ import { BreakpointsProvider } from '@roleypoly/design-system/atoms/breakpoints'; import { guildEnum } from '@roleypoly/design-system/fixtures/storyData'; import * as React from 'react'; +import ReactTooltip from 'react-tooltip'; import { EditorShell } from './EditorShell'; export default { title: 'Organisms/Editor', component: EditorShell, - decorators: [(story) => {story()}], + decorators: [ + (story) => ( + + {story()} + + + ), + ], }; export const Shell = () => ; diff --git a/packages/design-system/organisms/editor-shell/EditorShell.tsx b/packages/design-system/organisms/editor-shell/EditorShell.tsx index 55e5aa1..c47ea3b 100644 --- a/packages/design-system/organisms/editor-shell/EditorShell.tsx +++ b/packages/design-system/organisms/editor-shell/EditorShell.tsx @@ -1,21 +1,12 @@ -import { palette } from '@roleypoly/design-system/atoms/colors'; -import { FaderOpacity } from '@roleypoly/design-system/atoms/fader'; import { Space } from '@roleypoly/design-system/atoms/space'; -import { MultilineTextInput } from '@roleypoly/design-system/atoms/text-input'; -import { AmbientLarge, LargeText } from '@roleypoly/design-system/atoms/typography'; -import { PickerCategory } from '@roleypoly/design-system/molecules/picker-category'; +import { EditableServerMessage } from '@roleypoly/design-system/molecules/editable-server-message'; import { ServerMasthead } from '@roleypoly/design-system/molecules/server-masthead'; import { SecondaryEditing } from '@roleypoly/design-system/organisms/masthead'; -import { - CategoryContainer, - Container, - MessageBox, -} from '@roleypoly/design-system/organisms/role-picker/RolePicker.styled'; -import { Category, CategoryType, PresentableGuild, Role } from '@roleypoly/types'; +import { Container } from '@roleypoly/design-system/organisms/role-picker/RolePicker.styled'; +import { ServerCategoryEditor } from '@roleypoly/design-system/organisms/server-category-editor'; +import { Category, PresentableGuild } from '@roleypoly/types'; import deepEqual from 'deep-equal'; -import { sortBy } from 'lodash'; import React from 'react'; -import { GoEyeClosed } from 'react-icons/go'; export type EditorShellProps = { guild: PresentableGuild; @@ -26,6 +17,7 @@ export type EditorShellProps = { export const EditorShell = (props: EditorShellProps) => { const [guild, setGuild] = React.useState(props.guild); + const [reorderMode, setReorderMode] = React.useState(false); React.useEffect(() => { setGuild(props.guild); @@ -35,12 +27,8 @@ export const EditorShell = (props: EditorShellProps) => { setGuild(props.guild); }; - const onCategoryChange = (category: Category) => { + const replaceCategories = (categories: Category[]) => { setGuild((currentGuild) => { - const categories = [ - ...currentGuild.data.categories.filter((x) => x.id !== category.id), - category, - ]; return { ...currentGuild, data: { ...currentGuild.data, categories } }; }); }; @@ -51,6 +39,10 @@ export const EditorShell = (props: EditorShellProps) => { }); }; + const doSubmit = () => { + props.onGuildChange?.(guild); + }; + const hasChanges = React.useMemo( () => !deepEqual(guild.data, props.guild.data), [guild.data, props.guild.data] @@ -62,52 +54,18 @@ export const EditorShell = (props: EditorShellProps) => { showReset={hasChanges} guild={props.guild.guild} onReset={reset} - onSubmit={() => props.onGuildChange?.(guild)} + onSubmit={doSubmit} /> - - - Server Message - onMessageChange(event.target.value)} - placeholder={`Hey friend from ${guild.guild.name}! Pick your roles!`} - > - {guild.data.message} - - - Shows a message to your server members. - -  Since the message is empty, this won't show up.    - - - - + - -
- {sortBy(props.guild.data.categories, 'position').map((category, idx) => ( - - props.guild.roles.find((r) => r.id === role)) - .filter((r) => r !== undefined) as Role[] - } - onChange={() => () => {}} - wikiMode={false} - type={category.type === CategoryType.Single ? 'single' : 'multi'} - /> - - ))} -
+
); diff --git a/packages/design-system/organisms/server-category-editor/ServerCategoryEditor.styled.ts b/packages/design-system/organisms/server-category-editor/ServerCategoryEditor.styled.ts new file mode 100644 index 0000000..79684cc --- /dev/null +++ b/packages/design-system/organisms/server-category-editor/ServerCategoryEditor.styled.ts @@ -0,0 +1,42 @@ +import { transitions } from '@roleypoly/design-system/atoms/timings'; +import { CategoryContainer } from '@roleypoly/design-system/organisms/role-picker/RolePicker.styled'; +import styled, { css } from 'styled-components'; + +export const CategoryActions = styled.div<{ right?: boolean }>` + display: flex; + flex-direction: row; + + ${(props) => + props.right && + css` + justify-content: flex-end; + `} + + & > * { + ${(props) => (props.right ? 'margin-left' : 'margin-right')}: 5px; + } +`; + +export const ReorderButton = styled.div` + height: 40px; + width: 40px; + font-size: 20px; + margin-right: 5px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + cursor: pointer; + transition: background-color ${transitions.actionable}s ease-in-out; + + &:hover { + background-color: rgba(0, 0, 0, 0.15); + } +`; + +export const ReorderCategoryContainer = styled(CategoryContainer)` + display: flex; + align-items: center; + justify-content: flex-start; + user-select: none; +`; diff --git a/packages/design-system/organisms/server-category-editor/ServerCategoryEditor.tsx b/packages/design-system/organisms/server-category-editor/ServerCategoryEditor.tsx new file mode 100644 index 0000000..bacf66c --- /dev/null +++ b/packages/design-system/organisms/server-category-editor/ServerCategoryEditor.tsx @@ -0,0 +1,158 @@ +import { BreakpointText } from '@roleypoly/design-system/atoms/breakpoints'; +import { Button } from '@roleypoly/design-system/atoms/button'; +import { FaderOpacity } from '@roleypoly/design-system/atoms/fader'; +import { LargeText } from '@roleypoly/design-system/atoms/typography'; +import { EditorCategory } from '@roleypoly/design-system/molecules/editor-category'; +import { CategoryContainer } from '@roleypoly/design-system/organisms/role-picker/RolePicker.styled'; +import { Category, CategoryType, PresentableGuild, Role } from '@roleypoly/types'; +import KSUID from 'ksuid'; +import { sortBy } from 'lodash'; +import React from 'react'; +import { CgReorder } from 'react-icons/cg'; +import { GoArrowDown, GoArrowUp, GoCheck, GoGrabber, GoPlus } from 'react-icons/go'; +import { + CategoryActions, + ReorderButton, + ReorderCategoryContainer, +} from './ServerCategoryEditor.styled'; + +type Props = { + guild: PresentableGuild; + onChange: (categories: PresentableGuild['data']['categories']) => void; +}; + +const resetOrder = (categories: Category[]) => + sortBy(categories, ['position', 'id']).map((c, index) => ({ ...c, position: index })); + +const forceOrder = (categories: Category[]) => + categories.map((c, index) => ({ ...c, position: index })); + +export const ServerCategoryEditor = (props: Props) => { + const [reorderMode, setReorderMode] = React.useState(false); + + const updateSingleCategory = (category: Category) => { + const newCategories = props.guild.data.categories.map((c) => { + if (c.id === category.id) { + return category; + } + return c; + }); + props.onChange(newCategories); + }; + + const createCategory = () => { + // Reset order now that we're creating a new category + const categories = resetOrder(props.guild.data.categories); + + const newCategory: Category = { + id: KSUID.randomSync().toString(), + name: 'New Category', + type: CategoryType.Multi, + position: categories.length, + roles: [], + hidden: false, + }; + + props.onChange([...categories, newCategory]); + }; + + const onReorder = (categories: Category[]) => { + setReorderMode(false); + props.onChange(resetOrder(categories)); + }; + + if (reorderMode) { + return ; + } + + return ( +
+ + + + + {sortBy(props.guild.data.categories, ['position', 'id']).map((category, idx) => ( + + props.guild.roles.find((r) => r.id === role)) + .filter((r) => r !== undefined) as Role[] + } + onChange={updateSingleCategory} + type={category.type === CategoryType.Single ? 'single' : 'multi'} + /> + + ))} +
+ ); +}; + +const ReorderMode = (props: Props & { exitReorderMode: (final: Category[]) => void }) => { + const [categories, setCategories] = React.useState(props.guild.data.categories); + + React.useEffect(() => { + setCategories(props.guild.data.categories); + }, [props.guild.data.categories]); + + const handleReorder = (category: Category, direction: 'up' | 'down') => () => { + const newCategories = [...categories]; + const index = newCategories.findIndex((c) => c.id === category.id); + const newIndex = direction === 'up' ? index - 1 : index + 1; + + if (newIndex < 0 || newIndex > newCategories.length - 1) { + return; + } + + newCategories.splice(index, 1); + newCategories.splice(newIndex, 0, category); + setCategories(forceOrder(newCategories)); + }; + + return ( +
+ + + + + {sortBy(categories, ['position', 'id']).map((category, idx, array) => ( + + + + + + + + + + + + + + + {category.name} + + ))} +
+ ); +}; diff --git a/packages/design-system/organisms/server-category-editor/index.ts b/packages/design-system/organisms/server-category-editor/index.ts new file mode 100644 index 0000000..4b2a47b --- /dev/null +++ b/packages/design-system/organisms/server-category-editor/index.ts @@ -0,0 +1 @@ +export * from './ServerCategoryEditor'; diff --git a/packages/design-system/templates/editor/Editor.stories.tsx b/packages/design-system/templates/editor/Editor.stories.tsx index ef1c966..0cd8c67 100644 --- a/packages/design-system/templates/editor/Editor.stories.tsx +++ b/packages/design-system/templates/editor/Editor.stories.tsx @@ -1,8 +1,18 @@ +import ReactTooltip from 'react-tooltip'; +import { BreakpointsProvider } from '../../atoms/breakpoints'; import { guildEnum, mastheadSlugs, user } from '../../fixtures/storyData'; import { EditorTemplate } from './Editor'; export default { title: 'Templates/Server Editor', component: EditorTemplate, + decorators: [ + (story) => ( + + {story()} + + + ), + ], args: { guilds: mastheadSlugs, user: user, diff --git a/packages/web/src/pages/editor.tsx b/packages/web/src/pages/editor.tsx index 52a884e..457ab64 100644 --- a/packages/web/src/pages/editor.tsx +++ b/packages/web/src/pages/editor.tsx @@ -1,4 +1,4 @@ -import { Redirect } from '@reach/router'; +import { navigate, Redirect } from '@reach/router'; import { EditorTemplate } from '@roleypoly/design-system/templates/editor'; import { GenericLoadingTemplate } from '@roleypoly/design-system/templates/generic-loading'; import { @@ -87,6 +87,8 @@ const Editor = (props: EditorProps) => { setGuild(guild); setPending(false); } + + navigate(`/s/${props.serverID}`); }; return (