From 1405c881a80685210ce1d470133b67d23aba36bb Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 3 Oct 2021 15:36:58 -0700 Subject: [PATCH] Fix sidebar styling and remove hacky fixed positioning --- package.json | 5 +- .../scripts/components/admin/Sidebar.tsx | 80 +++++ resources/scripts/components/helpers.ts | 9 + .../scripts/plugins/usePersistedState.ts | 2 +- .../scripts/plugins/useUserPersistedState.ts | 9 + resources/scripts/routers/AdminRouter.tsx | 289 +++++------------- yarn.lock | 10 + 7 files changed, 194 insertions(+), 210 deletions(-) create mode 100644 resources/scripts/components/admin/Sidebar.tsx create mode 100644 resources/scripts/components/helpers.ts create mode 100644 resources/scripts/plugins/useUserPersistedState.ts diff --git a/package.json b/package.json index 0c7cfd316..384a5289d 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "@fortawesome/free-regular-svg-icons": "^5.15.4", "@fortawesome/free-solid-svg-icons": "^5.15.4", "@fortawesome/react-fontawesome": "^0.1.15", + "@heroicons/react": "^1.0.4", "@hot-loader/react-dom": "^16.14.0", "axios": "^0.21.4", "chart.js": "^2.9.4", @@ -169,8 +170,8 @@ }, "styledComponents": { "pure": true, - "displayName": false, - "fileName": false + "displayName": true, + "fileName": true } }, "packageManager": "yarn@3.0.2" diff --git a/resources/scripts/components/admin/Sidebar.tsx b/resources/scripts/components/admin/Sidebar.tsx new file mode 100644 index 000000000..d43839255 --- /dev/null +++ b/resources/scripts/components/admin/Sidebar.tsx @@ -0,0 +1,80 @@ +import tw, { css } from 'twin.macro'; +import styled from 'styled-components/macro'; +import { withSubComponents } from '@/components/helpers'; + +const Wrapper = styled.div` + ${tw`w-full flex flex-col px-4`}; + + & > a { + ${tw`h-10 w-full flex flex-row items-center text-neutral-300 cursor-pointer select-none px-4`}; + ${tw`hover:text-neutral-50`}; + + & > svg { + ${tw`h-6 w-6 flex flex-shrink-0`}; + } + + & > span { + ${tw`font-header font-medium text-lg whitespace-nowrap leading-none ml-3`}; + } + + &:active, &.active { + ${tw`text-neutral-50 bg-neutral-800 rounded`}; + } + } +`; + +const Section = styled.div` + ${tw`h-[18px] font-header font-medium text-xs text-neutral-300 whitespace-nowrap uppercase ml-4 mb-1 select-none`}; + + &:not(:first-of-type) { + ${tw`mt-4`}; + } +`; + +const User = styled.div` + ${tw`h-16 w-full flex items-center bg-neutral-700 justify-center`}; +`; + +const Sidebar = styled.div<{ $collapsed?: boolean }>` + ${tw`h-screen hidden md:flex flex-col items-center flex-shrink-0 bg-neutral-900 overflow-x-hidden ease-linear`}; + ${tw`transition-[width] duration-150 ease-in`}; + ${tw`w-[17.5rem]`}; + + & > a { + ${tw`h-10 w-full flex flex-row items-center text-neutral-300 cursor-pointer select-none px-8`}; + ${tw`hover:text-neutral-50`}; + + & > svg { + ${tw`transition-none h-6 w-6 flex flex-shrink-0`}; + } + + & > span { + ${tw`font-header font-medium text-lg whitespace-nowrap leading-none ml-3`}; + } + } + + ${props => props.$collapsed && css` + ${tw`w-20`}; + + ${Section} { + ${tw`invisible`}; + } + + ${Wrapper} > a { + ${tw`justify-center px-0`}; + } + + & > a { + ${tw`justify-center px-4`}; + } + + & > a > span, + ${User} > div, + ${User} > a, + ${Wrapper} > a > span { + ${tw`hidden`}; + } + `}; +`; + +export default withSubComponents(Sidebar, { Section, Wrapper, User }); diff --git a/resources/scripts/components/helpers.ts b/resources/scripts/components/helpers.ts new file mode 100644 index 000000000..47b9d5f00 --- /dev/null +++ b/resources/scripts/components/helpers.ts @@ -0,0 +1,9 @@ +import { StyledComponent } from 'styled-components/macro'; + +export const withSubComponents = , P extends Record> (component: C, properties: P): C & P => { + Object.keys(properties).forEach((key: keyof P) => { + (component as any)[key] = properties[key]; + }); + + return component as C & P; +}; diff --git a/resources/scripts/plugins/usePersistedState.ts b/resources/scripts/plugins/usePersistedState.ts index 034e81e1c..b1920477f 100644 --- a/resources/scripts/plugins/usePersistedState.ts +++ b/resources/scripts/plugins/usePersistedState.ts @@ -1,6 +1,6 @@ import { Dispatch, SetStateAction, useEffect, useState } from 'react'; -export function usePersistedState (key: string, defaultValue: S): [ S | undefined, Dispatch> ] { +export function usePersistedState (key: string, defaultValue: S): [ S, Dispatch> ] { const [ state, setState ] = useState( () => { try { diff --git a/resources/scripts/plugins/useUserPersistedState.ts b/resources/scripts/plugins/useUserPersistedState.ts new file mode 100644 index 000000000..7e0fe2c5a --- /dev/null +++ b/resources/scripts/plugins/useUserPersistedState.ts @@ -0,0 +1,9 @@ +import { useStoreState } from 'easy-peasy'; +import { usePersistedState } from '@/plugins/usePersistedState'; +import { Dispatch, SetStateAction } from 'react'; + +export default (key: string, defaultValue: S): [ S, Dispatch> ] => { + const uuid = useStoreState(state => state.user.data!.uuid); + + return usePersistedState(`${uuid}:${key}`, defaultValue); +}; diff --git a/resources/scripts/routers/AdminRouter.tsx b/resources/scripts/routers/AdminRouter.tsx index 88e747764..6fca9faa9 100644 --- a/resources/scripts/routers/AdminRouter.tsx +++ b/resources/scripts/routers/AdminRouter.tsx @@ -1,7 +1,7 @@ import { State, useStoreState } from 'easy-peasy'; import React from 'react'; import { NavLink, Route, RouteComponentProps, Switch } from 'react-router-dom'; -import tw, { styled } from 'twin.macro'; +import tw from 'twin.macro'; import OverviewContainer from '@/components/admin/overview/OverviewContainer'; import SettingsContainer from '@/components/admin/settings/SettingsContainer'; import DatabasesContainer from '@/components/admin/databases/DatabasesContainer'; @@ -28,249 +28,124 @@ import MountsContainer from '@/components/admin/mounts/MountsContainer'; import NewMountContainer from '@/components/admin/mounts/NewMountContainer'; import MountEditContainer from '@/components/admin/mounts/MountEditContainer'; import { NotFound } from '@/components/elements/ScreenBlock'; -import { usePersistedState } from '@/plugins/usePersistedState'; import { ApplicationStore } from '@/state'; import { AdminContext } from '@/state/admin'; -import { breakpoint } from '@/theme'; - -const Sidebar = styled.div<{ collapsed?: boolean }>` - ${tw`fixed h-screen hidden md:flex flex-col items-center flex-shrink-0 bg-neutral-900 overflow-x-hidden transition-all duration-250 ease-linear`}; - ${props => props.collapsed ? 'width: 70px' : 'width: 287px'}; - - & > div.header { - ${tw`h-16 w-full flex flex-col items-center justify-center mt-1 mb-3 select-none cursor-pointer`}; - } - - & > div.wrapper { - ${tw`w-full flex flex-col px-4`}; - - & > span { - height: 18px; - ${tw`font-header font-medium text-xs text-neutral-300 whitespace-nowrap uppercase ml-4 mb-1 select-none`}; - ${props => props.collapsed && tw`opacity-0`}; - - &:not(:first-of-type) { - ${tw`mt-4`}; - } - } - - & > a { - ${tw`h-10 w-full flex flex-row items-center text-neutral-300 cursor-pointer select-none`}; - ${props => props.collapsed ? tw`justify-center` : tw`px-4`}; - - & > svg { - ${tw`h-6 w-6 flex flex-shrink-0`}; - } - - & > span { - ${props => props.collapsed ? tw`hidden` : tw`font-header font-medium text-lg whitespace-nowrap leading-none ml-3`}; - } - - &:hover { - ${tw`text-neutral-50`}; - } - - &:active, &.active { - ${tw`text-neutral-50 bg-neutral-800 rounded`}; - } - } - } - - & > a { - ${tw`h-10 w-full flex flex-row items-center text-neutral-300 cursor-pointer select-none`}; - ${props => props.collapsed ? tw`justify-center px-4` : tw`px-8`}; - - & > svg { - ${tw`h-6 w-6 flex flex-shrink-0`}; - } - - & > span { - ${props => props.collapsed ? tw`hidden` : tw`font-header font-medium text-lg whitespace-nowrap leading-none ml-3`}; - } - - &:hover { - ${tw`text-neutral-50`}; - } - } - - & > div.user { - ${tw`h-16 w-full flex items-center bg-neutral-700 justify-center`}; - - & > div, a { - ${props => props.collapsed && tw`hidden`}; - } - } -`; - -const Container = styled.div<{ collapsed?: boolean }>` - ${tw`w-full flex flex-col items-center transition-all duration-250 ease-linear`}; - ${props => props.collapsed ? - breakpoint('md')`padding-left: 70px` - : - breakpoint('md')`padding-left: 287px`}; -`; +import { + CogIcon, + DatabaseIcon, + FolderAddIcon, + GlobeIcon, + OfficeBuildingIcon, + ReplyIcon, + ServerIcon, + TerminalIcon, + UserGroupIcon, + UsersIcon, + ViewGridIcon, +} from '@heroicons/react/outline'; +import CollapsedIcon from '@/assets/images/pterodactyl.svg'; +import Sidebar from '@/components/admin/Sidebar'; +import useUserPersistedState from '@/plugins/useUserPersistedState'; const AdminRouter = ({ location, match }: RouteComponentProps) => { - const user = useStoreState((state: State) => state.user.data); + const email = useStoreState((state: State) => state.user.data!.email); + const roleName = useStoreState((state: State) => state.user.data!.roleName); + const avatarURL = useStoreState((state: State) => state.user.data!.avatarURL); const applicationName = useStoreState((state: ApplicationStore) => state.settings.data!.name); - const uuid = useStoreState(state => state.user.data!.uuid); - const [ collapsed, setCollapsed ] = usePersistedState(`${uuid}:admin_sidebar_collapsed`, false); + const [ collapsed, setCollapsed ] = useUserPersistedState('admin_sidebar_collapsed', false); return ( -
- -
{ setCollapsed(!collapsed); } }> - { !collapsed ? +
+ +
setCollapsed(!collapsed)} + > + {!collapsed ?

{applicationName}

: - {'Pterodactyl + {'Pterodactyl }
- -
- Administration - + + Administration - - Overview + Overview - - Settings + Settings - - Management - + Management - - Databases + Databases - - Locations + Locations - - Nodes + Nodes - - Servers + Servers - - Users + Users - - Roles + Roles - - Service Management - + Service Management - - Nests + Nests - - Mounts + Mounts -
- + - - - - Return + Return - -
- Profile Picture - + + {avatarURL && + Profile Picture + }
- {user?.email} - {user?.roleName} + {email} + {roleName}
-
+
- - -
- {/* */} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {/* */} -
-
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
); }; diff --git a/yarn.lock b/yarn.lock index eccd4dcd3..fc0da1586 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2215,6 +2215,15 @@ __metadata: languageName: node linkType: hard +"@heroicons/react@npm:^1.0.4": + version: 1.0.4 + resolution: "@heroicons/react@npm:1.0.4" + peerDependencies: + react: ">= 16" + checksum: 08f2853a49e51f274f22e8e1bb4e7d8a49456b7eb770447da66fd06ac05bf821ddf5d93170088d61ec44ae66cfbdc877c287861d5a3d22aad1785659502e3a71 + languageName: node + linkType: hard + "@hot-loader/react-dom@npm:^16.14.0": version: 16.14.0 resolution: "@hot-loader/react-dom@npm:16.14.0" @@ -9942,6 +9951,7 @@ fsevents@^1.2.7: "@fortawesome/free-regular-svg-icons": ^5.15.4 "@fortawesome/free-solid-svg-icons": ^5.15.4 "@fortawesome/react-fontawesome": ^0.1.15 + "@heroicons/react": ^1.0.4 "@hot-loader/react-dom": ^16.14.0 "@tailwindcss/forms": ^0.3.3 "@types/chart.js": ^2.9.34