mirror of
https://github.com/roleypoly/roleypoly.git
synced 2025-06-16 17:49:09 +00:00
feat(UI): add role picker, auth helpers, refactor for viability
This commit is contained in:
parent
3fe3cfc21f
commit
e4e4bb9024
32 changed files with 408 additions and 136 deletions
|
@ -9,6 +9,7 @@ export default {
|
|||
},
|
||||
args: {
|
||||
initials: 'KR',
|
||||
hash: 'aa',
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -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})`,
|
||||
|
|
|
@ -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;
|
||||
`;
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -27,6 +27,7 @@ export const PreauthGreeting = (props: GreetingProps) => (
|
|||
'icons',
|
||||
512
|
||||
)}
|
||||
hash={props.guildSlug.icon}
|
||||
>
|
||||
{avatarUtils.initialsFromName(props.guildSlug.name)}
|
||||
</Avatar>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)}
|
||||
|
|
|
@ -62,6 +62,7 @@ export const Authed = (props: Props) => {
|
|||
canDefocus
|
||||
position="bottom left"
|
||||
active={serverPopoverState}
|
||||
preferredWidth={560}
|
||||
onExit={() => setServerPopoverState(false)}
|
||||
>
|
||||
{() => <GuildNav guilds={props.guilds} />}
|
||||
|
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue