feat(UI): add role picker, auth helpers, refactor for viability

This commit is contained in:
41666 2020-12-18 00:14:30 -05:00
parent 3fe3cfc21f
commit e4e4bb9024
32 changed files with 408 additions and 136 deletions

View file

@ -9,6 +9,7 @@ export default {
},
args: {
initials: 'KR',
hash: 'aa',
},
};

View file

@ -5,13 +5,14 @@ export type AvatarProps = {
src?: string;
children?: string | React.ReactNode;
size?: number;
hash?: string;
deliberatelyEmpty?: boolean;
};
/** Chuldren is recommended to not be larger than 2 uppercase letters. */
export const Avatar = (props: AvatarProps) => (
<Container size={props.size} deliberatelyEmpty={props.deliberatelyEmpty}>
{props.src && (
{props.src && props.hash && (
<Image
style={{
backgroundImage: `url(${props.src})`,

View file

@ -27,16 +27,18 @@ export const PopoverBase = styled.div<PopoverStyledProps>`
opacity: 0;
pointer-events: none;
`}
${onSmallScreen(css`
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
min-width: unset;
width: 100vw;
height: 100vh;
`)};
${onSmallScreen(
css`
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
min-width: unset;
width: 100vw;
height: 100vh;
`
)};
`;
export const DefocusHandler = styled.div<PopoverStyledProps>`
@ -84,4 +86,5 @@ export const PopoverHeadCloser = styled.div`
export const PopoverContent = styled.div`
padding: 5px;
overflow-y: hidden;
`;

View file

@ -16,13 +16,14 @@ type PopoverProps = {
canDefocus?: boolean;
onExit?: (type: 'escape' | 'defocus' | 'explicit') => void;
headContent: React.ReactNode;
preferredWidth?: number;
};
export const Popover = (props: PopoverProps) => {
globalOnKeyUp(['Escape'], () => props.onExit?.('escape'), props.active);
return (
<>
<PopoverBase active={props.active}>
<PopoverBase active={props.active} preferredWidth={props.preferredWidth}>
<PopoverHead>
<PopoverHeadCloser onClick={() => props.onExit?.('explicit')}>
<IoMdClose />

View file

@ -32,8 +32,7 @@ export const Circle = styled.div<StyledProps>`
svg {
width: 10px;
height: 10px;
fill-opacity: ${(props) =>
props.selected || props.disabled || props.type !== 'delete' ? 1 : 0};
fill-opacity: ${(props) => (props.selected || props.disabled ? 1 : 0)};
transition: fill-opacity ${transitions.in2in}s ease-in-out;
fill: ${(props) =>
props.disabled && props.defaultColor

View file

@ -1,5 +1,6 @@
import Link from 'next/link';
import * as React from 'react';
import Scrollbars from 'react-custom-scrollbars';
import { GoStar, GoZap } from 'react-icons/go';
import ReactTooltip from 'react-tooltip';
import { GuildSlug, UserGuildPermissions } from 'roleypoly/common/types';
@ -29,14 +30,23 @@ const Badges = (props: { guild: GuildSlug }) => {
export const GuildNav = (props: Props) => (
<div>
{sortBy(props.guilds, 'id').map((guild) => (
<Link href={`/s/${guild.id}`} passHref>
<GuildNavItem>
<NavSlug guild={guild || null} key={guild.id} />
<Badges guild={guild} />
</GuildNavItem>
</Link>
))}
<ReactTooltip id={tooltipId} />
<Scrollbars
universal
autoHide
// autoHeight
style={{ height: 'calc(100vh - 45px - 1.4em)', overflowX: 'hidden' }}
>
{sortBy(props.guilds, 'name', (a: string, b: string) =>
a.toLowerCase() > b.toLowerCase() ? 1 : -1
).map((guild) => (
<Link href={`/s/${guild.id}`} passHref key={guild.id}>
<GuildNavItem>
<NavSlug guild={guild || null} key={guild.id} />
<Badges guild={guild} />
</GuildNavItem>
</Link>
))}
<ReactTooltip id={tooltipId} />
</Scrollbars>
</div>
);

View file

@ -11,9 +11,11 @@ type Props = {
export const NavSlug = (props: Props) => (
<SlugContainer>
<Avatar
hash={props.guild ? props.guild.icon : undefined}
src={
(props.guild && utils.avatarHash(props.guild.id, props.guild.icon)) ||
undefined
props.guild
? utils.avatarHash(props.guild.id, props.guild.icon)
: undefined
}
deliberatelyEmpty={!props.guild}
size={35}

View file

@ -27,6 +27,7 @@ export const PreauthGreeting = (props: GreetingProps) => (
'icons',
512
)}
hash={props.guildSlug.icon}
>
{avatarUtils.initialsFromName(props.guildSlug.name)}
</Avatar>

View file

@ -1,14 +1,13 @@
import Link from 'next/link';
import * as React from 'react';
import { GoPencil } from 'react-icons/go';
import { Guild } from 'roleypoly/common/types';
import { guild } from 'roleypoly/common/types/storyData';
import { GuildSlug } from 'roleypoly/common/types';
import { Avatar, utils } from 'roleypoly/design-system/atoms/avatar';
import { AccentTitle, AmbientLarge } from 'roleypoly/design-system/atoms/typography';
import { Editable, Icon, Name, Wrapper } from './ServerMasthead.styled';
export type ServerMastheadProps = {
guild: Guild;
guild: GuildSlug;
editable: boolean;
};
@ -17,8 +16,9 @@ export const ServerMasthead = (props: ServerMastheadProps) => {
<Wrapper>
<Icon>
<Avatar
hash={props.guild.icon}
size={props.editable ? 60 : 48}
src={utils.avatarHash(guild.id, guild.icon, 'icons', 512)}
src={utils.avatarHash(props.guild.id, props.guild.icon, 'icons', 512)}
>
{utils.initialsFromName(props.guild.name)}
</Avatar>

View file

@ -19,6 +19,7 @@ export const UserAvatarGroup = (props: Props) => (
</Collapse>
<Avatar
size={34}
hash={props.user.avatar}
src={utils.avatarHash(props.user.id, props.user.avatar, 'avatars')}
>
{utils.initialsFromName(props.user.username)}

View file

@ -62,6 +62,7 @@ export const Authed = (props: Props) => {
canDefocus
position="bottom left"
active={serverPopoverState}
preferredWidth={560}
onExit={() => setServerPopoverState(false)}
>
{() => <GuildNav guilds={props.guilds} />}

View file

@ -1,16 +1,19 @@
import NextLink from 'next/link';
import * as React from 'react';
import { GoInfo } from 'react-icons/go';
import {
Category,
CategoryType,
Guild,
GuildData,
GuildSlug,
Member,
Role,
} from 'roleypoly/common/types';
import { ReactifyNewlines } from 'roleypoly/common/utils/ReactifyNewlines';
import { sortBy } from 'roleypoly/common/utils/sortBy';
import { FaderOpacity } from 'roleypoly/design-system/atoms/fader';
import { Space } from 'roleypoly/design-system/atoms/space';
import { Link } from 'roleypoly/design-system/atoms/typography';
import { PickerCategory } from 'roleypoly/design-system/molecules/picker-category';
import { ResetSubmit } from 'roleypoly/design-system/molecules/reset-submit';
import { ServerMasthead } from 'roleypoly/design-system/molecules/server-masthead';
@ -23,7 +26,7 @@ import {
} from './RolePicker.styled';
export type RolePickerProps = {
guild: Guild;
guild: GuildSlug;
guildData: GuildData;
member: Member;
roles: Role[];
@ -81,32 +84,34 @@ export const RolePicker = (props: RolePickerProps) => {
{props.guildData.categories.length !== 0 ? (
<>
<div>
{props.guildData.categories.map((category, idx) => (
<CategoryContainer key={idx}>
<PickerCategory
key={idx}
category={category}
title={category.name}
selectedRoles={selectedRoles.filter((roleId) =>
category.roles.includes(roleId)
)}
roles={
category.roles
.map((role) =>
props.roles.find((r) => r.id === role)
)
.filter((r) => r !== undefined) as Role[]
}
onChange={handleChange(category)}
wikiMode={false}
type={
category.type === CategoryType.Single
? 'single'
: 'multi'
}
/>
</CategoryContainer>
))}
{sortBy(props.guildData.categories, 'position').map(
(category, idx) => (
<CategoryContainer key={idx}>
<PickerCategory
key={idx}
category={category}
title={category.name}
selectedRoles={selectedRoles.filter((roleId) =>
category.roles.includes(roleId)
)}
roles={
category.roles
.map((role) =>
props.roles.find((r) => r.id === role)
)
.filter((r) => r !== undefined) as Role[]
}
onChange={handleChange(category)}
wikiMode={false}
type={
category.type === CategoryType.Single
? 'single'
: 'multi'
}
/>
</CategoryContainer>
)
)}
</div>
<FaderOpacity
isVisible={!arrayMatches(selectedRoles, props.member.roles)}
@ -126,6 +131,18 @@ export const RolePicker = (props: RolePickerProps) => {
</InfoIcon>
<div>
There are currently no roles available for you to choose from.
{props.editable && (
<>
{' '}
<NextLink
passHref
href={`/s/[id]/edit`}
as={`/s/${props.guild.id}/edit`}
>
<Link>Add some roles!</Link>
</NextLink>
</>
)}
</div>
</InfoBox>
)}

View file

@ -1,9 +1,10 @@
import * as React from 'react';
import { AppShell } from 'roleypoly/design-system/organisms/app-shell';
import { Landing } from 'roleypoly/design-system/organisms/landing';
import { ProvidableAppShellProps } from 'roleypoly/providers/appShellData';
export const LandingTemplate = () => (
<AppShell showFooter>
export const LandingTemplate = (props: ProvidableAppShellProps) => (
<AppShell showFooter {...props}>
<Landing />
</AppShell>
);

View file

@ -5,7 +5,7 @@ import {
RolePickerProps,
} from 'roleypoly/design-system/organisms/role-picker';
export type RolePickerTemplateProps = RolePickerProps & AppShellProps;
export type RolePickerTemplateProps = RolePickerProps & Omit<AppShellProps, 'children'>;
export const RolePickerTemplate = (props: RolePickerTemplateProps) => {
const { user, guilds, activeGuildId, ...pickerProps } = props;