mirror of
https://github.com/roleypoly/roleypoly.git
synced 2025-04-24 19:39:11 +00:00
feat: Add majority of design system components, build system fixes (#11)
* feat(design-system): pre-port of roleypoly/ui * feat(design-system): port molecules * chore(design-system): prettier * feat(design-system): add intro card and MDX components * fix(common/utils): hack fixtures test data moved to design-system, update accordingly * chore: document protoReflection.ts * fix(design-system): some molecules missed the magic fuckery * ci: keep going on bazel test failures * fix(design-system): server masthead molecule missed the magic fuckery * chore: fix ts paths * chore: fix docker publisher * chore: fix docker publisher names * chore(discord-bot): fix publisher * chore(discord-bot): fix publisher
This commit is contained in:
parent
c41fcabfd0
commit
89f237cf22
133 changed files with 2795 additions and 278 deletions
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
|
@ -25,7 +25,7 @@ jobs:
|
|||
- name: Test
|
||||
run: |
|
||||
"${GITHUB_WORKSPACE}/bin/bazel" test \
|
||||
-c opt \
|
||||
-k -c opt \
|
||||
--stamp \
|
||||
--workspace_status_command hack/workspace_status.sh --\
|
||||
//src/... //hack/... -//hack/dev-container/...
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
import { roleypolyTheme } from './theme';
|
||||
import { mdxComponents } from '../src/design-system/atoms/typography/mdx';
|
||||
|
||||
export const parameters = {
|
||||
actions: { argTypesRegex: '^on[A-Z].*' },
|
||||
docs: {
|
||||
theme: roleypolyTheme,
|
||||
components: mdxComponents,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -9,7 +9,7 @@ export const roleypolyTheme = create({
|
|||
|
||||
// UI
|
||||
appBg: palette.taupe300,
|
||||
appContentBg: palette.taupe300,
|
||||
appContentBg: palette.taupe200,
|
||||
appBorderColor: palette.taupe100,
|
||||
appBorderRadius: 0,
|
||||
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
load("//:hack/react.bzl", "react_library")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
react_library(
|
||||
name = "fixtures",
|
||||
deps = [
|
||||
"//src/rpc/discord",
|
||||
"//src/rpc/platform",
|
||||
"//src/rpc/shared",
|
||||
],
|
||||
)
|
|
@ -1,237 +0,0 @@
|
|||
import { Member } from 'roleypoly/src/rpc/discord';
|
||||
import { Category, GuildData, GuildEnumeration } from 'roleypoly/src/rpc/platform';
|
||||
import {
|
||||
DiscordUser,
|
||||
Guild,
|
||||
GuildRoles,
|
||||
Role,
|
||||
RoleypolyUser,
|
||||
} from 'roleypoly/src/rpc/shared';
|
||||
|
||||
export const roleCategory: Role.AsObject[] = [
|
||||
{
|
||||
id: 'aaa',
|
||||
permissions: 0,
|
||||
name: 'She/Her',
|
||||
color: 0xffc0cb,
|
||||
position: 1,
|
||||
managed: false,
|
||||
safety: Role.RoleSafety.SAFE,
|
||||
},
|
||||
{
|
||||
id: 'bbb',
|
||||
permissions: 0,
|
||||
name: 'He/Him',
|
||||
color: 0xc0ebff,
|
||||
position: 2,
|
||||
managed: false,
|
||||
safety: Role.RoleSafety.SAFE,
|
||||
},
|
||||
{
|
||||
id: 'ccc',
|
||||
permissions: 0,
|
||||
name: 'They/Them',
|
||||
color: 0xc0ffd5,
|
||||
position: 3,
|
||||
managed: false,
|
||||
safety: Role.RoleSafety.SAFE,
|
||||
},
|
||||
{
|
||||
id: 'ddd',
|
||||
permissions: 0,
|
||||
name: 'Reee',
|
||||
color: 0xff0000,
|
||||
position: 4,
|
||||
managed: false,
|
||||
safety: Role.RoleSafety.SAFE,
|
||||
},
|
||||
{
|
||||
id: 'eee',
|
||||
permissions: 0,
|
||||
name: 'black but actually bravely default',
|
||||
color: 0x000000,
|
||||
position: 5,
|
||||
managed: false,
|
||||
safety: Role.RoleSafety.SAFE,
|
||||
},
|
||||
{
|
||||
id: 'fff',
|
||||
permissions: 0,
|
||||
name: 'b̻͌̆̽ͣ̃ͭ̊l͚̥͙̔ͨ̊aͥć͕k͎̟͍͕ͥ̋ͯ̓̈̉̋i͛̄̔͂̚̚҉̳͈͔̖̼̮ṣ̤̗̝͊̌͆h͈̭̰͔̥̯ͅ',
|
||||
color: 0x1,
|
||||
position: 6,
|
||||
managed: false,
|
||||
safety: Role.RoleSafety.SAFE,
|
||||
},
|
||||
{
|
||||
id: 'unsafe1',
|
||||
permissions: 0,
|
||||
name: 'too high',
|
||||
color: 0xff0088,
|
||||
position: 7,
|
||||
managed: false,
|
||||
safety: Role.RoleSafety.HIGHERTHANBOT,
|
||||
},
|
||||
{
|
||||
id: 'unsafe2',
|
||||
permissions: 0x00000008 | 0x10000000,
|
||||
name: 'too strong',
|
||||
color: 0x00ff88,
|
||||
position: 8,
|
||||
managed: false,
|
||||
safety: Role.RoleSafety.DANGEROUSPERMISSIONS,
|
||||
},
|
||||
];
|
||||
|
||||
export const mockCategory: Category.AsObject = {
|
||||
id: 'aaa',
|
||||
name: 'Mock',
|
||||
rolesList: roleCategory.map((x) => x.id),
|
||||
hidden: false,
|
||||
type: Category.CategoryType.MULTI,
|
||||
position: 0,
|
||||
};
|
||||
|
||||
export const roleCategory2: Role.AsObject[] = [
|
||||
{
|
||||
id: 'ddd2',
|
||||
permissions: 0,
|
||||
name: 'red',
|
||||
color: 0xff0000,
|
||||
position: 9,
|
||||
managed: false,
|
||||
safety: Role.RoleSafety.SAFE,
|
||||
},
|
||||
{
|
||||
id: 'eee2',
|
||||
permissions: 0,
|
||||
name: 'green',
|
||||
color: 0x00ff00,
|
||||
position: 10,
|
||||
managed: false,
|
||||
safety: Role.RoleSafety.SAFE,
|
||||
},
|
||||
];
|
||||
|
||||
export const mockCategorySingle: Category.AsObject = {
|
||||
id: 'bbb',
|
||||
name: 'Mock Single 岡野',
|
||||
rolesList: roleCategory2.map((x) => x.id),
|
||||
hidden: false,
|
||||
type: Category.CategoryType.SINGLE,
|
||||
position: 0,
|
||||
};
|
||||
|
||||
export const guildRoles: GuildRoles.AsObject = {
|
||||
id: 'aaa',
|
||||
rolesList: [...roleCategory, ...roleCategory2],
|
||||
};
|
||||
|
||||
export const roleWikiData = {
|
||||
aaa: 'Typically used by feminine-identifying people',
|
||||
bbb: 'Typically used by masculine-identifying people',
|
||||
ccc: 'Typically used to refer to all people as a singular neutral.',
|
||||
};
|
||||
|
||||
export const guild: Guild.AsObject = {
|
||||
name: 'emoji megaporium',
|
||||
id: 'aaa',
|
||||
icon:
|
||||
'https://cdn.discordapp.com/icons/421896162539470888/3372fd895ed913b55616c5e49cd50e60.png?size=256',
|
||||
ownerid: 'bbb',
|
||||
membercount: 23453,
|
||||
splash: '',
|
||||
};
|
||||
|
||||
export const guildMap: { [x: string]: Guild.AsObject } = {
|
||||
'emoji megaporium': guild,
|
||||
Roleypoly: {
|
||||
name: 'Roleypoly',
|
||||
id: 'aaa',
|
||||
icon:
|
||||
'https://cdn.discordapp.com/icons/203493697696956418/ff08d36f5aee1ff48f8377b65d031ab0.png?size=256',
|
||||
ownerid: 'bbb',
|
||||
membercount: 23453,
|
||||
splash: '',
|
||||
},
|
||||
'chamber of secrets': {
|
||||
name: 'chamber of secrets',
|
||||
id: 'aaa',
|
||||
icon: '',
|
||||
ownerid: 'bbb',
|
||||
membercount: 23453,
|
||||
splash: '',
|
||||
},
|
||||
Eclipse: {
|
||||
name: 'Eclipse',
|
||||
id: 'aaa',
|
||||
icon:
|
||||
'https://cdn.discordapp.com/icons/408821059161423873/49dfdd8b2456e2977e80a8b577b19c0d.png?size=256',
|
||||
ownerid: 'bbb',
|
||||
membercount: 23453,
|
||||
splash: '',
|
||||
},
|
||||
};
|
||||
|
||||
export const guildData: GuildData.AsObject = {
|
||||
id: 'aaa',
|
||||
message: 'henlo worl!!',
|
||||
categoriesList: [mockCategory, mockCategorySingle],
|
||||
entitlementsList: [],
|
||||
};
|
||||
|
||||
export const user: DiscordUser.AsObject = {
|
||||
id: '123',
|
||||
username: 'okano cat',
|
||||
discriminator: '3266',
|
||||
avatar:
|
||||
'https://cdn.discordapp.com/avatars/62601275618889728/b1292bb974557337702cb941fc038085.png',
|
||||
bot: false,
|
||||
};
|
||||
|
||||
export const member: Member.AsObject = {
|
||||
guildid: 'aaa',
|
||||
rolesList: ['aaa', 'eee', 'unsafe2', 'ddd2'],
|
||||
nick: 'okano cat',
|
||||
user: user,
|
||||
};
|
||||
|
||||
export const rpUser: RoleypolyUser.AsObject = {
|
||||
discorduser: user,
|
||||
};
|
||||
|
||||
export const guildEnum: GuildEnumeration.AsObject = {
|
||||
guildsList: [
|
||||
{
|
||||
id: 'aaa',
|
||||
guild: guildMap['emoji megaporium'],
|
||||
member,
|
||||
data: guildData,
|
||||
roles: guildRoles,
|
||||
},
|
||||
{
|
||||
id: 'bbb',
|
||||
guild: guildMap['Roleypoly'],
|
||||
member: {
|
||||
...member,
|
||||
rolesList: ['unsafe2'],
|
||||
},
|
||||
data: guildData,
|
||||
roles: guildRoles,
|
||||
},
|
||||
{
|
||||
id: 'ccc',
|
||||
guild: guildMap['chamber of secrets'],
|
||||
member,
|
||||
data: guildData,
|
||||
roles: guildRoles,
|
||||
},
|
||||
{
|
||||
id: 'ddd',
|
||||
guild: guildMap['Eclipse'],
|
||||
member,
|
||||
data: guildData,
|
||||
roles: guildRoles,
|
||||
},
|
||||
],
|
||||
};
|
|
@ -14,7 +14,7 @@ def render_deps(deps = []):
|
|||
"@npm//@improbable-eng/grpc-web",
|
||||
])
|
||||
has_added_grpc_deps = True
|
||||
elif dep.startswith("//"):
|
||||
elif dep.startswith("//") or dep.startswith("@npm//"):
|
||||
output_deps.append(dep)
|
||||
else:
|
||||
output_deps.append("@npm//" + dep)
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
"react": "16.13.1",
|
||||
"react-dom": "16.13.1",
|
||||
"react-icons": "^3.11.0",
|
||||
"react-tooltip": "^4.2.10",
|
||||
"styled-components": "^5.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -19,7 +19,7 @@ react_library(
|
|||
jest_test(
|
||||
src = ":utils",
|
||||
deps = [
|
||||
"//hack/fixtures",
|
||||
"//src/design-system/shared-types",
|
||||
"//src/rpc/shared",
|
||||
],
|
||||
)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { hasPermission, permissions, hasPermissionOrAdmin } from './hasPermission';
|
||||
import { Role } from 'roleypoly/src/rpc/shared';
|
||||
import { guildRoles } from 'roleypoly/hack/fixtures/storyData';
|
||||
import { guildRoles } from 'roleypoly/src/design-system/shared-types/storyData';
|
||||
|
||||
const roles: Role.AsObject[] = [
|
||||
{
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { DiscordUser } from 'roleypoly/src/rpc/shared';
|
||||
import { user } from 'roleypoly/hack/fixtures/storyData';
|
||||
import { user } from 'roleypoly/src/design-system/shared-types/storyData';
|
||||
import { AsObjectToProto } from './protoReflection';
|
||||
|
||||
it('converts a RoleypolyUser.AsObject back to protobuf', () => {
|
||||
|
|
|
@ -1,26 +1,43 @@
|
|||
import * as pbjs from 'google-protobuf';
|
||||
|
||||
// Protobuf Message itself
|
||||
type GenericObject<T extends pbjs.Message> = T;
|
||||
|
||||
// Message's "setter" call
|
||||
type ProtoFunction<T extends pbjs.Message, U extends ReturnType<T['toObject']>> = (
|
||||
value: U[keyof U]
|
||||
) => void;
|
||||
|
||||
/**
|
||||
* AsObjectToProto does the opposite of ProtoMessage.toObject().
|
||||
* This function turns regular JS objects back into their source protobuf message type,
|
||||
* with the help us copious amounts of reflection.
|
||||
* @param protoClass A protobuf message class
|
||||
* @param input A JS object that corresponds to the protobuf message class.
|
||||
*/
|
||||
export const AsObjectToProto = <T extends pbjs.Message>(
|
||||
protoClass: { new (): T },
|
||||
input: ReturnType<T['toObject']>
|
||||
): GenericObject<T> => {
|
||||
// First, we create the message itself
|
||||
const proto = new protoClass();
|
||||
|
||||
// We want the keys from the message, this will give us the setter names we need.
|
||||
const protoKeys = Object.getOwnPropertyNames((proto as any).__proto__);
|
||||
|
||||
// Loop over the input data keys
|
||||
for (let inputKey in input) {
|
||||
// As we loop, find the setter function for the key
|
||||
const setCallName = protoKeys.find(
|
||||
(key) => `set${inputKey.toLowerCase()}` === key.toLowerCase()
|
||||
) as keyof typeof proto;
|
||||
|
||||
// If we encounter a key without a place to go, we silently ignore it.
|
||||
if (!setCallName) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// But, if it all succeeds, we call the setter with the JS object's value.
|
||||
((proto[setCallName] as unknown) as ProtoFunction<T, typeof input>)(
|
||||
input[inputKey]
|
||||
);
|
||||
|
|
24
src/design-system/Intro.stories.mdx
Normal file
24
src/design-system/Intro.stories.mdx
Normal file
|
@ -0,0 +1,24 @@
|
|||
import { Meta } from '@storybook/addon-docs/blocks';
|
||||
import { Logotype } from 'roleypoly/src/design-system/atoms/branding';
|
||||
import { Space } from 'roleypoly/src/design-system/atoms/space';
|
||||
import { palette } from 'roleypoly/src/design-system/atoms/colors';
|
||||
|
||||
<Meta title="Roleypoly Design System" />
|
||||
|
||||
<Logotype height="4em" circleFill={palette.taupe100} />
|
||||
<Space />
|
||||
|
||||
# Rapid UI
|
||||
|
||||
#### Roleypoly Design System
|
||||
|
||||
This is a tool for Roleypoly developers to design and show off UI
|
||||
components as they build them.
|
||||
|
||||
If you're interested in helping build Roleypoly, [please visit the GitHub project.][roleypoly]
|
||||
|
||||
All components here follow the [Atomic Design System][atomic], and might be used in any number of Roleypoly UI systems, not limited to
|
||||
just the end user web application.
|
||||
|
||||
[roleypoly]: https://github.com/roleypoly/roleypoly
|
||||
[atomic]: https://bradfrost.com/blog/post/atomic-web-design/
|
|
@ -8,7 +8,7 @@ The Roleypoly Design System (rapid) is an atomic design system to help rapidly a
|
|||
|
||||
**Please follow hermeticity considerations.**
|
||||
|
||||
This package cannot reference RPC types, as they do not exist in the outside world. Storybook is the core component of this, and Storybook doesn't know how to find RPC types at CI build time, as Bazel is also not present.
|
||||
This package cannot reference RPC types, as they do not exist in the outside world. Storybook is the core component of this, and Storybook doesn't know how to find RPC types at CI build time, as Bazel is also not present. If you are worried about RPC types being compatible, please write a unit test and include the RPC types then.
|
||||
|
||||
You need:
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
load("//:hack/react.bzl", "react_library")
|
||||
load("//:hack/jest.bzl", "jest_test")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
|
@ -14,3 +15,7 @@ react_library(
|
|||
"@types/styled-components",
|
||||
],
|
||||
)
|
||||
|
||||
jest_test(
|
||||
src = ":tab-view",
|
||||
)
|
||||
|
|
|
@ -4,7 +4,7 @@ load("//:hack/jest.bzl", "jest_test")
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
react_library(
|
||||
name = "timings",
|
||||
name = "typist",
|
||||
deps = [
|
||||
"react",
|
||||
"@types/react",
|
||||
|
@ -12,5 +12,5 @@ react_library(
|
|||
)
|
||||
|
||||
jest_test(
|
||||
src = ":timings",
|
||||
src = ":typist",
|
||||
)
|
||||
|
|
|
@ -6,6 +6,8 @@ react_library(
|
|||
name = "typography",
|
||||
deps = [
|
||||
"styled-components",
|
||||
"//src/design-system/atoms/colors",
|
||||
"//src/design-system/atoms/timings",
|
||||
"@types/styled-components",
|
||||
],
|
||||
)
|
||||
|
|
33
src/design-system/atoms/typography/mdx.tsx
Normal file
33
src/design-system/atoms/typography/mdx.tsx
Normal file
|
@ -0,0 +1,33 @@
|
|||
import styled from 'styled-components';
|
||||
import * as _ from 'styled-components'; // tslint:disable-line:no-duplicate-imports
|
||||
import {
|
||||
AccentTitle,
|
||||
LargeTitle,
|
||||
Link,
|
||||
MediumTitle,
|
||||
SmallTitle,
|
||||
Text,
|
||||
text600,
|
||||
text700,
|
||||
text800,
|
||||
text900,
|
||||
} from './typography';
|
||||
|
||||
export const mdxComponents = {
|
||||
h1: styled.h1`
|
||||
${text900}
|
||||
`,
|
||||
h2: styled.h2`
|
||||
${text800}
|
||||
`,
|
||||
h3: styled.h3`
|
||||
${text700}
|
||||
`,
|
||||
h4: styled.h4`
|
||||
${text600}
|
||||
`,
|
||||
p: styled.p`
|
||||
${Text}
|
||||
`,
|
||||
a: Link,
|
||||
};
|
|
@ -107,3 +107,12 @@ export const Spacing = () => (
|
|||
})}
|
||||
</div>
|
||||
);
|
||||
|
||||
export const Link = () => (
|
||||
<typography.Link
|
||||
target="_blank"
|
||||
href="https://images.boredomfiles.com/wp-content/uploads/sites/5/2016/03/fox-door-5.png"
|
||||
>
|
||||
Here is a link <3
|
||||
</typography.Link>
|
||||
);
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import styled, { css } from 'styled-components';
|
||||
import { palette } from 'roleypoly/src/design-system/atoms/colors';
|
||||
import { transitions } from 'roleypoly/src/design-system/atoms/timings';
|
||||
import * as _ from 'styled-components'; // tslint:disable-line:no-duplicate-imports
|
||||
|
||||
const reset = css`
|
||||
|
@ -86,3 +88,12 @@ export const AmbientLarge = styled.span`
|
|||
export const AmbientSmall = styled.span`
|
||||
${text100}
|
||||
`;
|
||||
|
||||
export const Link = styled.a`
|
||||
color: ${palette.taupe500};
|
||||
text-decoration: none;
|
||||
transition: color ${transitions.actionable}s ease-in-out;
|
||||
&:hover {
|
||||
color: ${palette.taupe600};
|
||||
}
|
||||
`;
|
||||
|
|
16
src/design-system/molecules/demo-discord/BUILD.bazel
Normal file
16
src/design-system/molecules/demo-discord/BUILD.bazel
Normal file
|
@ -0,0 +1,16 @@
|
|||
load("//:hack/react.bzl", "react_library")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
react_library(
|
||||
name = "demo-discord",
|
||||
deps = [
|
||||
"react",
|
||||
"styled-components",
|
||||
"//src/design-system/atoms/colors",
|
||||
"//src/design-system/atoms/typist",
|
||||
"//src/design-system/shared-types",
|
||||
"@types/react",
|
||||
"@types/styled-components",
|
||||
],
|
||||
)
|
|
@ -0,0 +1,8 @@
|
|||
import * as React from 'react';
|
||||
import { DemoDiscord } from './DemoDiscord';
|
||||
|
||||
export default {
|
||||
title: 'Molecules/Role Demos',
|
||||
};
|
||||
|
||||
export const Discord = () => <DemoDiscord />;
|
|
@ -0,0 +1,68 @@
|
|||
import styled, { keyframes } from 'styled-components';
|
||||
import * as _ from 'styled-components'; // tslint:disable-line:no-duplicate-imports
|
||||
import { palette } from 'roleypoly/src/design-system/atoms/colors';
|
||||
|
||||
export const Base = styled.div`
|
||||
background-color: ${palette.discord100};
|
||||
border: solid 1px rgba(0, 0, 0, 0.15);
|
||||
border-radius: 3px;
|
||||
padding: 10px;
|
||||
user-select: none;
|
||||
`;
|
||||
|
||||
export const Timestamp = styled.span`
|
||||
padding: 0 5px;
|
||||
font-size: 0.7em;
|
||||
opacity: 0.3;
|
||||
`;
|
||||
|
||||
export const TextParts = styled.span`
|
||||
padding: 0 5px;
|
||||
`;
|
||||
|
||||
export const Username = styled(TextParts)`
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
export const InputBox = styled.div`
|
||||
margin-top: 10px;
|
||||
background-color: ${palette.discord200};
|
||||
padding: 7px 10px;
|
||||
border-radius: 3px;
|
||||
`;
|
||||
|
||||
const lineBlink = keyframes`
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
40% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
60% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Line = styled.div`
|
||||
background-color: ${palette.grey600};
|
||||
width: 1px;
|
||||
height: 1.5em;
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
right: -5px;
|
||||
animation: ${lineBlink} 0.5s ease-in-out infinite alternate-reverse;
|
||||
`;
|
||||
|
||||
export const InputTextAlignment = styled.div`
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
`;
|
53
src/design-system/molecules/demo-discord/DemoDiscord.tsx
Normal file
53
src/design-system/molecules/demo-discord/DemoDiscord.tsx
Normal file
|
@ -0,0 +1,53 @@
|
|||
import * as React from 'react';
|
||||
import {
|
||||
Base,
|
||||
Timestamp,
|
||||
TextParts,
|
||||
Username,
|
||||
InputBox,
|
||||
Line,
|
||||
InputTextAlignment,
|
||||
} from './DemoDiscord.styled';
|
||||
import { demoData } from 'roleypoly/src/design-system/shared-types/demoData';
|
||||
import { Typist } from 'roleypoly/src/design-system/atoms/typist';
|
||||
|
||||
export const DemoDiscord = () => {
|
||||
const time = new Date();
|
||||
const timeString = time.toTimeString();
|
||||
|
||||
const [easterEggCount, setEasterEggCount] = React.useState(0);
|
||||
|
||||
return (
|
||||
<Base>
|
||||
<Timestamp>
|
||||
{time.getHours() % 12}:{timeString.slice(3, 5)}
|
||||
{time.getHours() <= 12 ? 'AM' : 'PM'}
|
||||
</Timestamp>
|
||||
<Username onClick={() => setEasterEggCount(easterEggCount + 1)}>
|
||||
okano cat
|
||||
</Username>
|
||||
<TextParts>
|
||||
{easterEggCount >= 15
|
||||
? `NYAAAAAAA${'A'.repeat(easterEggCount - 15)}`
|
||||
: easterEggCount >= 11
|
||||
? `I'm.. I'm gonna...`
|
||||
: easterEggCount >= 10
|
||||
? `S-senpai... Be careful...`
|
||||
: easterEggCount >= 5
|
||||
? `H-hey... Stop that..`
|
||||
: `Hey, I'd like some roles!`}
|
||||
</TextParts>
|
||||
<InputBox>
|
||||
<InputTextAlignment>
|
||||
|
||||
<Typist
|
||||
resetTimeout={2000}
|
||||
charTimeout={75}
|
||||
lines={demoData.map((role) => `.iam ${role.name}`)}
|
||||
/>
|
||||
<Line />
|
||||
</InputTextAlignment>
|
||||
</InputBox>
|
||||
</Base>
|
||||
);
|
||||
};
|
1
src/design-system/molecules/demo-discord/index.ts
Normal file
1
src/design-system/molecules/demo-discord/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './DemoDiscord';
|
15
src/design-system/molecules/demo-picker/BUILD.bazel
Normal file
15
src/design-system/molecules/demo-picker/BUILD.bazel
Normal file
|
@ -0,0 +1,15 @@
|
|||
load("//:hack/react.bzl", "react_library")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
react_library(
|
||||
name = "demo-picker",
|
||||
deps = [
|
||||
"react",
|
||||
"styled-components",
|
||||
"//src/design-system/atoms/role",
|
||||
"//src/design-system/shared-types",
|
||||
"@types/react",
|
||||
"@types/styled-components",
|
||||
],
|
||||
)
|
|
@ -0,0 +1,8 @@
|
|||
import * as React from 'react';
|
||||
import { DemoPicker } from './DemoPicker';
|
||||
|
||||
export default {
|
||||
title: 'Molecules/Role Demos',
|
||||
};
|
||||
|
||||
export const Picker = () => <DemoPicker />;
|
46
src/design-system/molecules/demo-picker/DemoPicker.tsx
Normal file
46
src/design-system/molecules/demo-picker/DemoPicker.tsx
Normal file
|
@ -0,0 +1,46 @@
|
|||
import * as React from 'react';
|
||||
import { Role } from 'roleypoly/src/design-system/atoms/role';
|
||||
import { Role as RPCRole } from 'roleypoly/src/design-system/shared-types';
|
||||
import styled from 'styled-components';
|
||||
import { demoData } from 'roleypoly/src/design-system/shared-types/demoData';
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
height: 95px;
|
||||
`;
|
||||
|
||||
const RoleWrap = styled.div`
|
||||
padding: 2.5px;
|
||||
display: inline-block;
|
||||
`;
|
||||
|
||||
export const DemoPicker = () => {
|
||||
const [selectedStates, setSelectedStates] = React.useState<
|
||||
{
|
||||
[key in RPCRole['id']]: boolean;
|
||||
}
|
||||
>(demoData.reduce((acc, role) => ({ ...acc, [role.id]: false }), {}));
|
||||
|
||||
return (
|
||||
<Container>
|
||||
{demoData.map((role) => (
|
||||
<RoleWrap key={`role${role.id}`}>
|
||||
<Role
|
||||
role={role}
|
||||
selected={selectedStates[role.id]}
|
||||
onClick={() => {
|
||||
setSelectedStates({
|
||||
...selectedStates,
|
||||
[role.id]: !selectedStates[role.id],
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</RoleWrap>
|
||||
))}
|
||||
</Container>
|
||||
);
|
||||
};
|
1
src/design-system/molecules/demo-picker/index.ts
Normal file
1
src/design-system/molecules/demo-picker/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './DemoPicker';
|
17
src/design-system/molecules/footer/BUILD.bazel
Normal file
17
src/design-system/molecules/footer/BUILD.bazel
Normal file
|
@ -0,0 +1,17 @@
|
|||
load("//:hack/react.bzl", "react_library")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
react_library(
|
||||
name = "footer",
|
||||
deps = [
|
||||
"react",
|
||||
"react-icons",
|
||||
"styled-components",
|
||||
"//src/design-system/atoms/colors",
|
||||
"//src/design-system/atoms/timings",
|
||||
"//src/design-system/atoms/typography",
|
||||
"@types/react",
|
||||
"@types/styled-components",
|
||||
],
|
||||
)
|
94
src/design-system/molecules/footer/Flags.tsx
Normal file
94
src/design-system/molecules/footer/Flags.tsx
Normal file
|
@ -0,0 +1,94 @@
|
|||
import * as React from 'react';
|
||||
|
||||
type FlagsProps = {
|
||||
width?: number | string;
|
||||
height?: number | string;
|
||||
};
|
||||
|
||||
export const Flags = (props: FlagsProps) => (
|
||||
<svg width={props.width} height={props.height} viewBox="0 0 3372 900" version="1.1">
|
||||
<defs>
|
||||
<rect id="path-3" x="1772" y="0" width="1600" height="900" rx="100"></rect>
|
||||
</defs>
|
||||
<g id="Page-1" stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
|
||||
<g id="Rectangle-5"></g>
|
||||
<g id="Trans">
|
||||
<rect
|
||||
id="Rectangle"
|
||||
fill="#55CDFC"
|
||||
x="0"
|
||||
y="0"
|
||||
width="1600"
|
||||
height="900"
|
||||
rx="100"
|
||||
></rect>
|
||||
<rect
|
||||
id="Rectangle-2"
|
||||
fill="#F7A8B8"
|
||||
x="0"
|
||||
y="170"
|
||||
width="1600"
|
||||
height="560"
|
||||
></rect>
|
||||
<rect
|
||||
id="Rectangle-3"
|
||||
fill="#FFFFFF"
|
||||
x="0"
|
||||
y="350"
|
||||
width="1600"
|
||||
height="200"
|
||||
></rect>
|
||||
</g>
|
||||
<mask id="mask-4" fill="white">
|
||||
<use href="#path-3"></use>
|
||||
</mask>
|
||||
<g id="Rectangle-5"></g>
|
||||
<g id="Geyy" mask="url(#mask-4)">
|
||||
<g transform="translate(1772.000000, 0.000000)" id="Rectangle-4">
|
||||
<rect
|
||||
fill="#F9238B"
|
||||
x="0"
|
||||
y="0"
|
||||
width="1600"
|
||||
height="151.006711"
|
||||
></rect>
|
||||
<rect
|
||||
fill="#FB7B04"
|
||||
x="0"
|
||||
y="150"
|
||||
width="1600"
|
||||
height="151.006711"
|
||||
></rect>
|
||||
<rect
|
||||
fill="#FFCA66"
|
||||
x="0"
|
||||
y="300"
|
||||
width="1600"
|
||||
height="151.006711"
|
||||
></rect>
|
||||
<rect
|
||||
fill="#00B289"
|
||||
x="0"
|
||||
y="450"
|
||||
width="1600"
|
||||
height="151.006711"
|
||||
></rect>
|
||||
<rect
|
||||
fill="#5A38B5"
|
||||
x="0"
|
||||
y="598.993289"
|
||||
width="1600"
|
||||
height="151.006711"
|
||||
></rect>
|
||||
<rect
|
||||
fill="#B413F5"
|
||||
x="0"
|
||||
y="748.993289"
|
||||
width="1600"
|
||||
height="151.006711"
|
||||
></rect>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
9
src/design-system/molecules/footer/Footer.stories.tsx
Normal file
9
src/design-system/molecules/footer/Footer.stories.tsx
Normal file
|
@ -0,0 +1,9 @@
|
|||
import * as React from 'react';
|
||||
import { Footer as FooterComponent } from './Footer';
|
||||
|
||||
export default {
|
||||
title: 'Molecules',
|
||||
component: FooterComponent,
|
||||
};
|
||||
|
||||
export const Footer = (args) => <FooterComponent {...args} />;
|
31
src/design-system/molecules/footer/Footer.styled.ts
Normal file
31
src/design-system/molecules/footer/Footer.styled.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
import styled from 'styled-components';
|
||||
import * as _ from 'styled-components'; // tslint:disable-line:no-duplicate-imports
|
||||
import { palette } from 'roleypoly/src/design-system/atoms/colors';
|
||||
import { transitions } from 'roleypoly/src/design-system/atoms/timings';
|
||||
|
||||
export const FooterWrapper = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
|
||||
a {
|
||||
color: ${palette.taupe500};
|
||||
text-decoration: none;
|
||||
transition: color ${transitions.actionable}s ease-in-out;
|
||||
&:hover {
|
||||
color: ${palette.taupe600};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const HoverColor = styled.div`
|
||||
opacity: 0.3;
|
||||
filter: saturate(0);
|
||||
transition: all ${transitions.in2in}s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
filter: none;
|
||||
}
|
||||
`;
|
27
src/design-system/molecules/footer/Footer.tsx
Normal file
27
src/design-system/molecules/footer/Footer.tsx
Normal file
|
@ -0,0 +1,27 @@
|
|||
import * as React from 'react';
|
||||
import { FooterWrapper, HoverColor } from './Footer.styled';
|
||||
import { AmbientLarge } from 'roleypoly/src/design-system/atoms/typography';
|
||||
import { FaHeart } from 'react-icons/fa';
|
||||
import { Flags } from './Flags';
|
||||
|
||||
const year = new Date().getFullYear();
|
||||
|
||||
export const Footer = () => (
|
||||
<FooterWrapper>
|
||||
<AmbientLarge>
|
||||
<div>
|
||||
© {year} Roleypoly – Made with{' '}
|
||||
<FaHeart size={'0.8em'} color={'#fe4365'} />
|
||||
in Raleigh, NC
|
||||
</div>
|
||||
<div>
|
||||
<a href="https://discord.gg/Xj6rK3E">Discord/Support</a> –
|
||||
<a href="https://patreon.com/kata">Patreon</a> –
|
||||
<a href="https://github.com/roleypoly">GitHub</a>
|
||||
</div>
|
||||
<HoverColor>
|
||||
<Flags height={'1em'} />
|
||||
</HoverColor>
|
||||
</AmbientLarge>
|
||||
</FooterWrapper>
|
||||
);
|
1
src/design-system/molecules/footer/index.ts
Normal file
1
src/design-system/molecules/footer/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './Footer';
|
21
src/design-system/molecules/guild-nav/BUILD.bazel
Normal file
21
src/design-system/molecules/guild-nav/BUILD.bazel
Normal file
|
@ -0,0 +1,21 @@
|
|||
load("//:hack/react.bzl", "react_library")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
react_library(
|
||||
name = "guild-nav",
|
||||
deps = [
|
||||
"next",
|
||||
"react",
|
||||
"react-icons",
|
||||
"react-tooltip",
|
||||
"styled-components",
|
||||
"//src/common/utils",
|
||||
"//src/design-system/atoms/colors",
|
||||
"//src/design-system/atoms/timings",
|
||||
"//src/design-system/molecules/nav-slug",
|
||||
"//src/design-system/shared-types",
|
||||
"@types/react",
|
||||
"@types/styled-components",
|
||||
],
|
||||
)
|
21
src/design-system/molecules/guild-nav/GuildNav.stories.tsx
Normal file
21
src/design-system/molecules/guild-nav/GuildNav.stories.tsx
Normal file
|
@ -0,0 +1,21 @@
|
|||
import * as React from 'react';
|
||||
import { GuildNav } from './GuildNav';
|
||||
import { guildEnum } from 'roleypoly/src/design-system/shared-types/storyData';
|
||||
import { PopoverBase } from 'roleypoly/src/design-system/atoms/popover/Popover.styled';
|
||||
|
||||
export default {
|
||||
title: 'Molecules/Guild Nav',
|
||||
component: GuildNav,
|
||||
};
|
||||
|
||||
export const HasGuilds = () => (
|
||||
<PopoverBase active>
|
||||
<GuildNav guildEnumeration={guildEnum} />
|
||||
</PopoverBase>
|
||||
);
|
||||
|
||||
export const NoGuilds = () => (
|
||||
<PopoverBase active>
|
||||
<GuildNav guildEnumeration={{ guildsList: [] }} />
|
||||
</PopoverBase>
|
||||
);
|
22
src/design-system/molecules/guild-nav/GuildNav.styled.ts
Normal file
22
src/design-system/molecules/guild-nav/GuildNav.styled.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import styled from 'styled-components';
|
||||
import * as _ from 'styled-components'; // tslint:disable-line:no-duplicate-imports
|
||||
import { transitions } from 'roleypoly/src/design-system/atoms/timings';
|
||||
import { palette } from 'roleypoly/src/design-system/atoms/colors';
|
||||
|
||||
export const GuildNavItem = styled.a`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transition: border ${transitions.in2in}s ease-in-out;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 3px;
|
||||
box-sizing: border-box;
|
||||
margin: 5px;
|
||||
user-select: none;
|
||||
color: unset;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
border-color: ${palette.taupe300};
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
62
src/design-system/molecules/guild-nav/GuildNav.tsx
Normal file
62
src/design-system/molecules/guild-nav/GuildNav.tsx
Normal file
|
@ -0,0 +1,62 @@
|
|||
import Link from 'next/link';
|
||||
import * as React from 'react';
|
||||
import { GoStar, GoZap } from 'react-icons/go';
|
||||
import ReactTooltip from 'react-tooltip';
|
||||
import { hasPermission, permissions } from 'roleypoly/src/common/utils/hasPermission';
|
||||
import { sortBy } from 'roleypoly/src/common/utils/sortBy';
|
||||
import {
|
||||
GuildEnumeration,
|
||||
PresentableGuild,
|
||||
Role,
|
||||
Guild,
|
||||
} from 'roleypoly/src/design-system/shared-types';
|
||||
import { NavSlug } from 'roleypoly/src/design-system/molecules/nav-slug';
|
||||
import { GuildNavItem } from './GuildNav.styled';
|
||||
|
||||
type Props = {
|
||||
guildEnumeration: GuildEnumeration;
|
||||
};
|
||||
|
||||
const tooltipId = 'guildnav';
|
||||
|
||||
const Badges = (props: { guild: PresentableGuild }) => {
|
||||
return React.useMemo(() => {
|
||||
if (!props.guild.member) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const roles = props.guild.member.rolesList
|
||||
.map((id) => {
|
||||
if (!props.guild.roles) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return props.guild.roles.rolesList.find((role) => role.id === id);
|
||||
})
|
||||
.filter((x) => !!x) as Role[];
|
||||
|
||||
if (hasPermission(roles, permissions.ADMINISTRATOR)) {
|
||||
return <GoStar data-tip="Administrator" data-for={tooltipId} />;
|
||||
}
|
||||
|
||||
if (hasPermission(roles, permissions.MANAGE_ROLES)) {
|
||||
return <GoZap data-tip="Role Editor" data-for={tooltipId} />;
|
||||
}
|
||||
|
||||
return null;
|
||||
}, [props.guild]);
|
||||
};
|
||||
|
||||
export const GuildNav = (props: Props) => (
|
||||
<div>
|
||||
{sortBy(props.guildEnumeration.guildsList, 'id').map((guild) => (
|
||||
<Link href={`/s/${guild.id}`} passHref>
|
||||
<GuildNavItem>
|
||||
<NavSlug guild={guild.guild || null} key={guild.id} />
|
||||
<Badges guild={guild} />
|
||||
</GuildNavItem>
|
||||
</Link>
|
||||
))}
|
||||
<ReactTooltip id={tooltipId} />
|
||||
</div>
|
||||
);
|
1
src/design-system/molecules/guild-nav/index.ts
Normal file
1
src/design-system/molecules/guild-nav/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './GuildNav';
|
16
src/design-system/molecules/nav-slug/BUILD.bazel
Normal file
16
src/design-system/molecules/nav-slug/BUILD.bazel
Normal file
|
@ -0,0 +1,16 @@
|
|||
load("//:hack/react.bzl", "react_library")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
react_library(
|
||||
name = "nav-slug",
|
||||
deps = [
|
||||
"react",
|
||||
"react-icons",
|
||||
"styled-components",
|
||||
"//src/design-system/atoms/avatar",
|
||||
"//src/design-system/shared-types",
|
||||
"@types/react",
|
||||
"@types/styled-components",
|
||||
],
|
||||
)
|
11
src/design-system/molecules/nav-slug/NavSlug.stories.tsx
Normal file
11
src/design-system/molecules/nav-slug/NavSlug.stories.tsx
Normal file
|
@ -0,0 +1,11 @@
|
|||
import * as React from 'react';
|
||||
import { NavSlug } from './NavSlug';
|
||||
import { guild } from 'roleypoly/src/design-system/shared-types/storyData';
|
||||
|
||||
export default {
|
||||
title: 'Molecules/Server Slug',
|
||||
component: NavSlug,
|
||||
};
|
||||
|
||||
export const Empty = () => <NavSlug guild={null} />;
|
||||
export const Example = () => <NavSlug guild={guild} />;
|
17
src/design-system/molecules/nav-slug/NavSlug.styled.ts
Normal file
17
src/design-system/molecules/nav-slug/NavSlug.styled.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import styled from 'styled-components';
|
||||
import * as _ from 'styled-components'; // tslint:disable-line:no-duplicate-imports
|
||||
|
||||
export const SlugContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
padding: 5px;
|
||||
`;
|
||||
|
||||
export const SlugName = styled.div`
|
||||
padding: 0 10px;
|
||||
position: relative;
|
||||
top: -1px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
`;
|
18
src/design-system/molecules/nav-slug/NavSlug.tsx
Normal file
18
src/design-system/molecules/nav-slug/NavSlug.tsx
Normal file
|
@ -0,0 +1,18 @@
|
|||
import * as React from 'react';
|
||||
import { Guild } from 'roleypoly/src/design-system/shared-types';
|
||||
import { Avatar, utils } from 'roleypoly/src/design-system/atoms/avatar';
|
||||
import { SlugContainer, SlugName } from './NavSlug.styled';
|
||||
import { GoOrganization } from 'react-icons/go';
|
||||
|
||||
type Props = {
|
||||
guild: Guild | null;
|
||||
};
|
||||
|
||||
export const NavSlug = (props: Props) => (
|
||||
<SlugContainer>
|
||||
<Avatar src={props.guild?.icon} deliberatelyEmpty={!props.guild} size={35}>
|
||||
{props.guild ? utils.initialsFromName(props.guild.name) : <GoOrganization />}
|
||||
</Avatar>
|
||||
<SlugName>{props.guild?.name || <>Your Guilds</>}</SlugName>
|
||||
</SlugContainer>
|
||||
);
|
1
src/design-system/molecules/nav-slug/index.ts
Normal file
1
src/design-system/molecules/nav-slug/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './NavSlug';
|
17
src/design-system/molecules/picker-category/BUILD.bazel
Normal file
17
src/design-system/molecules/picker-category/BUILD.bazel
Normal file
|
@ -0,0 +1,17 @@
|
|||
load("//:hack/react.bzl", "react_library")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
react_library(
|
||||
name = "picker-category",
|
||||
deps = [
|
||||
"react",
|
||||
"react-tooltip",
|
||||
"styled-components",
|
||||
"//src/design-system/atoms/role",
|
||||
"//src/design-system/atoms/typography",
|
||||
"//src/design-system/shared-types",
|
||||
"@types/react",
|
||||
"@types/styled-components",
|
||||
],
|
||||
)
|
|
@ -0,0 +1,38 @@
|
|||
import * as React from 'react';
|
||||
import {
|
||||
roleWikiData,
|
||||
roleCategory,
|
||||
mockCategory,
|
||||
} from 'roleypoly/src/design-system/shared-types/storyData';
|
||||
import { PickerCategory } from './PickerCategory';
|
||||
|
||||
export default {
|
||||
title: 'Molecules/Picker Category',
|
||||
component: PickerCategory,
|
||||
args: {
|
||||
title: 'Pronouns',
|
||||
roles: roleCategory,
|
||||
category: mockCategory,
|
||||
selectedRoles: [],
|
||||
},
|
||||
};
|
||||
|
||||
export const Default = (args) => {
|
||||
return <PickerCategory {...args} />;
|
||||
};
|
||||
export const Single = (args) => {
|
||||
return <PickerCategory {...args} type="single" />;
|
||||
};
|
||||
Single.args = {
|
||||
type: 'single',
|
||||
};
|
||||
export const Multi = (args) => {
|
||||
return <PickerCategory {...args} type="single" />;
|
||||
};
|
||||
Multi.args = {
|
||||
type: 'multi',
|
||||
};
|
||||
|
||||
export const Wiki = (args) => {
|
||||
return <PickerCategory {...args} wikiMode roleWikiData={roleWikiData} />;
|
||||
};
|
|
@ -0,0 +1,21 @@
|
|||
import styled from 'styled-components';
|
||||
import * as _ from 'styled-components'; // tslint:disable-line:no-duplicate-imports
|
||||
|
||||
export const Head = styled.div`
|
||||
margin: 7px 5px;
|
||||
line-height: 200%;
|
||||
display: flex;
|
||||
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;
|
||||
`;
|
|
@ -0,0 +1,67 @@
|
|||
import * as React from 'react';
|
||||
import ReactTooltip from 'react-tooltip';
|
||||
import { Role } from 'roleypoly/src/design-system/atoms/role';
|
||||
import { AmbientLarge, LargeText } from 'roleypoly/src/design-system/atoms/typography';
|
||||
import {
|
||||
Category as RPCCategory,
|
||||
Role as RPCRole,
|
||||
RoleSafety,
|
||||
} from 'roleypoly/src/design-system/shared-types';
|
||||
import styled from 'styled-components';
|
||||
import { Head, HeadSub, HeadTitle } from './PickerCategory.styled';
|
||||
|
||||
export type CategoryProps = {
|
||||
title: string;
|
||||
roles: RPCRole[];
|
||||
category: RPCCategory;
|
||||
selectedRoles: string[];
|
||||
onChange: (role: RPCRole) => (newState: boolean) => void;
|
||||
type: 'single' | 'multi';
|
||||
} & (
|
||||
| {
|
||||
wikiMode: true;
|
||||
roleWikiData: { [roleId: string]: string };
|
||||
}
|
||||
| {
|
||||
wikiMode: false;
|
||||
}
|
||||
);
|
||||
|
||||
const Category = styled.div`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
`;
|
||||
|
||||
const Container = styled.div`
|
||||
overflow: hidden;
|
||||
padding: 5px;
|
||||
`;
|
||||
|
||||
export const PickerCategory = (props: CategoryProps) => (
|
||||
<div>
|
||||
<Head>
|
||||
<HeadTitle>
|
||||
<LargeText>{props.title}</LargeText>
|
||||
</HeadTitle>
|
||||
{props.type === 'single' && (
|
||||
<HeadSub>
|
||||
<AmbientLarge>Pick one</AmbientLarge>
|
||||
</HeadSub>
|
||||
)}
|
||||
</Head>
|
||||
<Category>
|
||||
{props.roles.map((role, idx) => (
|
||||
<Container key={idx}>
|
||||
<Role
|
||||
role={role}
|
||||
selected={props.selectedRoles.includes(role.id)}
|
||||
onClick={props.onChange(role)}
|
||||
disabled={role.safety !== RoleSafety.SAFE}
|
||||
tooltipId={props.category.id}
|
||||
/>
|
||||
</Container>
|
||||
))}
|
||||
</Category>
|
||||
<ReactTooltip id={props.category.id} />
|
||||
</div>
|
||||
);
|
1
src/design-system/molecules/picker-category/index.ts
Normal file
1
src/design-system/molecules/picker-category/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export { PickerCategory } from './PickerCategory';
|
17
src/design-system/molecules/preauth-greeting/BUILD.bazel
Normal file
17
src/design-system/molecules/preauth-greeting/BUILD.bazel
Normal file
|
@ -0,0 +1,17 @@
|
|||
load("//:hack/react.bzl", "react_library")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
react_library(
|
||||
name = "preauth-greeting",
|
||||
deps = [
|
||||
"react",
|
||||
"styled-components",
|
||||
"//src/design-system/atoms/avatar",
|
||||
"//src/design-system/atoms/space",
|
||||
"//src/design-system/atoms/typography",
|
||||
"//src/design-system/shared-types",
|
||||
"@types/react",
|
||||
"@types/styled-components",
|
||||
],
|
||||
)
|
|
@ -0,0 +1,13 @@
|
|||
import * as React from 'react';
|
||||
import { PreauthGreeting } from './PreauthGreeting';
|
||||
import { guild } from 'roleypoly/src/design-system/shared-types/storyData';
|
||||
|
||||
export default {
|
||||
title: 'Molecules/Preauth/Greeting',
|
||||
component: PreauthGreeting,
|
||||
args: {
|
||||
guildSlug: guild,
|
||||
},
|
||||
};
|
||||
|
||||
export const Greeting = (args) => <PreauthGreeting {...args} />;
|
|
@ -0,0 +1,32 @@
|
|||
import * as React from 'react';
|
||||
import { Avatar, utils as avatarUtils } from 'roleypoly/src/design-system/atoms/avatar';
|
||||
import { Guild } from 'roleypoly/src/design-system/shared-types';
|
||||
import { AccentTitle } from 'roleypoly/src/design-system/atoms/typography';
|
||||
import { Space } from 'roleypoly/src/design-system/atoms/space';
|
||||
import styled from 'styled-components';
|
||||
|
||||
type GreetingProps = {
|
||||
guildSlug: Guild;
|
||||
};
|
||||
|
||||
const Center = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
export const PreauthGreeting = (props: GreetingProps) => (
|
||||
<Center>
|
||||
<Avatar size={64} src={props.guildSlug.icon}>
|
||||
{avatarUtils.initialsFromName(props.guildSlug.name)}
|
||||
</Avatar>
|
||||
<AccentTitle>
|
||||
Hi there. <b>{props.guildSlug.name}</b> uses Roleypoly to help assign you
|
||||
roles.
|
||||
</AccentTitle>
|
||||
<Space />
|
||||
<Space />
|
||||
</Center>
|
||||
);
|
1
src/design-system/molecules/preauth-greeting/index.ts
Normal file
1
src/design-system/molecules/preauth-greeting/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './PreauthGreeting';
|
29
src/design-system/molecules/preauth-secret-code/BUILD.bazel
Normal file
29
src/design-system/molecules/preauth-secret-code/BUILD.bazel
Normal file
|
@ -0,0 +1,29 @@
|
|||
load("//:hack/react.bzl", "react_library")
|
||||
load("//:hack/jest.bzl", "jest_test")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
react_library(
|
||||
name = "preauth-secret-code",
|
||||
deps = [
|
||||
"react",
|
||||
"react-icons",
|
||||
"styled-components",
|
||||
"//src/design-system/atoms/button",
|
||||
"//src/design-system/atoms/fader",
|
||||
"//src/design-system/atoms/space",
|
||||
"//src/design-system/atoms/text-input",
|
||||
"//src/design-system/shared-types",
|
||||
"@types/react",
|
||||
"@types/styled-components",
|
||||
],
|
||||
)
|
||||
|
||||
jest_test(
|
||||
src = ":preauth-secret-code",
|
||||
deps = [
|
||||
"//src/design-system/atoms/button",
|
||||
"//src/design-system/atoms/fader",
|
||||
"//src/design-system/atoms/text-input",
|
||||
],
|
||||
)
|
|
@ -0,0 +1,37 @@
|
|||
jest.unmock('roleypoly/src/design-system/atoms/text-input');
|
||||
jest.unmock('./PreauthSecretCode');
|
||||
|
||||
import { Button } from 'roleypoly/src/design-system/atoms/button';
|
||||
import { TextInputWithIcon } from 'roleypoly/src/design-system/atoms/text-input';
|
||||
import { shallow } from 'enzyme';
|
||||
import * as React from 'react';
|
||||
import { PreauthSecretCode } from './PreauthSecretCode';
|
||||
import { FaderOpacity } from 'roleypoly/src/design-system/atoms/fader';
|
||||
|
||||
const value = 'unfathomable fishy sticks';
|
||||
const onSubmit = jest.fn();
|
||||
|
||||
it('sends the secret code when submitted', () => {
|
||||
const view = shallow(<PreauthSecretCode onSubmit={onSubmit} />);
|
||||
|
||||
view.find(TextInputWithIcon).simulate('change', { target: { value } });
|
||||
|
||||
view.find(Button).simulate('click');
|
||||
expect(onSubmit).toBeCalledWith(value);
|
||||
});
|
||||
|
||||
it('shows the submit button when secret code is not empty', () => {
|
||||
const view = shallow(<PreauthSecretCode onSubmit={onSubmit} />);
|
||||
|
||||
view.find(TextInputWithIcon).simulate('change', { target: { value } });
|
||||
|
||||
expect(view.find(FaderOpacity).props().isVisible).toBe(true);
|
||||
});
|
||||
|
||||
it('hides the submit button when secret code is empty', () => {
|
||||
const view = shallow(<PreauthSecretCode onSubmit={onSubmit} />);
|
||||
|
||||
view.find(TextInputWithIcon).simulate('change', { target: { value: '' } });
|
||||
|
||||
expect(view.find(FaderOpacity).props().isVisible).toBe(false);
|
||||
});
|
|
@ -0,0 +1,9 @@
|
|||
import * as React from 'react';
|
||||
import { PreauthSecretCode } from './PreauthSecretCode';
|
||||
|
||||
export default {
|
||||
title: 'Molecules/Preauth/Secret Code',
|
||||
component: PreauthSecretCode,
|
||||
};
|
||||
|
||||
export const SecretCode = (args) => <PreauthSecretCode {...args} />;
|
|
@ -0,0 +1,46 @@
|
|||
import * as React from 'react';
|
||||
import { TextInputWithIcon } from 'roleypoly/src/design-system/atoms/text-input';
|
||||
import { FiKey } from 'react-icons/fi';
|
||||
import { FaderOpacity } from 'roleypoly/src/design-system/atoms/fader';
|
||||
import { Button } from 'roleypoly/src/design-system/atoms/button';
|
||||
import { Space } from 'roleypoly/src/design-system/atoms/space';
|
||||
|
||||
type PreauthProps = {
|
||||
onSubmit: (code: string) => void;
|
||||
};
|
||||
|
||||
export const PreauthSecretCode = (props: PreauthProps) => {
|
||||
const [secretCode, setSecretCode] = React.useState('');
|
||||
|
||||
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSecretCode(event.target.value);
|
||||
};
|
||||
|
||||
const handleKeyPress = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (event.key === 'Enter') {
|
||||
props.onSubmit(secretCode);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
props.onSubmit(secretCode);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<TextInputWithIcon
|
||||
icon={<FiKey />}
|
||||
value={secretCode}
|
||||
placeholder="Super secret code..."
|
||||
onChange={handleChange}
|
||||
onKeyDown={handleKeyPress}
|
||||
/>
|
||||
<Space />
|
||||
<FaderOpacity isVisible={secretCode.length > 0}>
|
||||
<Button color="muted" onClick={handleSubmit}>
|
||||
Submit Code →
|
||||
</Button>
|
||||
</FaderOpacity>
|
||||
</div>
|
||||
);
|
||||
};
|
1
src/design-system/molecules/preauth-secret-code/index.ts
Normal file
1
src/design-system/molecules/preauth-secret-code/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './PreauthSecretCode';
|
24
src/design-system/molecules/reset-submit/BUILD.bazel
Normal file
24
src/design-system/molecules/reset-submit/BUILD.bazel
Normal file
|
@ -0,0 +1,24 @@
|
|||
load("//:hack/react.bzl", "react_library")
|
||||
load("//:hack/jest.bzl", "jest_test")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
react_library(
|
||||
name = "reset-submit",
|
||||
deps = [
|
||||
"react",
|
||||
"react-icons",
|
||||
"styled-components",
|
||||
"//src/design-system/atoms/breakpoints",
|
||||
"//src/design-system/atoms/button",
|
||||
"@types/react",
|
||||
"@types/styled-components",
|
||||
],
|
||||
)
|
||||
|
||||
jest_test(
|
||||
src = ":reset-submit",
|
||||
deps = [
|
||||
"//src/design-system/atoms/button",
|
||||
],
|
||||
)
|
|
@ -0,0 +1,23 @@
|
|||
import { Button } from 'roleypoly/src/design-system/atoms/button';
|
||||
import { shallow } from 'enzyme';
|
||||
import * as React from 'react';
|
||||
import { ResetSubmit } from './ResetSubmit';
|
||||
|
||||
const onReset = jest.fn();
|
||||
const onSubmit = jest.fn();
|
||||
|
||||
it('calls onReset when reset is clicked', () => {
|
||||
const view = shallow(<ResetSubmit onSubmit={onSubmit} onReset={onReset} />);
|
||||
|
||||
view.find(Button).at(0).simulate('click');
|
||||
|
||||
expect(onReset).toBeCalled();
|
||||
});
|
||||
|
||||
it('calls onSubmit when submit is clicked', () => {
|
||||
const view = shallow(<ResetSubmit onSubmit={onSubmit} onReset={onReset} />);
|
||||
|
||||
view.find(Button).at(1).simulate('click');
|
||||
|
||||
expect(onSubmit).toBeCalled();
|
||||
});
|
|
@ -0,0 +1,9 @@
|
|||
import * as React from 'react';
|
||||
import { ResetSubmit } from './ResetSubmit';
|
||||
|
||||
export default {
|
||||
title: 'Molecules',
|
||||
component: ResetSubmit,
|
||||
};
|
||||
|
||||
export const ResetAndSubmit = (args) => <ResetSubmit {...args} />;
|
|
@ -0,0 +1,20 @@
|
|||
import styled from 'styled-components';
|
||||
import * as _ from 'styled-components'; // tslint:disable-line:no-duplicate-imports
|
||||
import { onSmallScreen } from 'roleypoly/src/design-system/atoms/breakpoints';
|
||||
|
||||
export const Buttons = styled.div`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
`;
|
||||
|
||||
export const Left = styled.div`
|
||||
flex: 0;
|
||||
${onSmallScreen`
|
||||
flex: 1 1 100%;
|
||||
order: 2;
|
||||
`}
|
||||
`;
|
||||
|
||||
export const Right = styled.div`
|
||||
flex: 1;
|
||||
`;
|
42
src/design-system/molecules/reset-submit/ResetSubmit.tsx
Normal file
42
src/design-system/molecules/reset-submit/ResetSubmit.tsx
Normal file
|
@ -0,0 +1,42 @@
|
|||
import { onSmallScreen } from 'roleypoly/src/design-system/atoms/breakpoints';
|
||||
import { Button } from 'roleypoly/src/design-system/atoms/button';
|
||||
import * as React from 'react';
|
||||
import { MdRestore } from 'react-icons/md';
|
||||
import styled from 'styled-components';
|
||||
|
||||
type Props = {
|
||||
onSubmit: () => void;
|
||||
onReset: () => void;
|
||||
};
|
||||
|
||||
const Buttons = styled.div`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
`;
|
||||
|
||||
const Left = styled.div`
|
||||
flex: 0;
|
||||
${onSmallScreen`
|
||||
flex: 1 1 100%;
|
||||
order: 2;
|
||||
`}
|
||||
`;
|
||||
|
||||
const Right = styled.div`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
export const ResetSubmit = (props: Props) => {
|
||||
return (
|
||||
<Buttons>
|
||||
<Left>
|
||||
<Button color="muted" icon={<MdRestore />} onClick={props.onReset}>
|
||||
Reset
|
||||
</Button>
|
||||
</Left>
|
||||
<Right>
|
||||
<Button onClick={props.onSubmit}>Submit</Button>
|
||||
</Right>
|
||||
</Buttons>
|
||||
);
|
||||
};
|
1
src/design-system/molecules/reset-submit/index.ts
Normal file
1
src/design-system/molecules/reset-submit/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './ResetSubmit';
|
28
src/design-system/molecules/server-masthead/BUILD.bazel
Normal file
28
src/design-system/molecules/server-masthead/BUILD.bazel
Normal file
|
@ -0,0 +1,28 @@
|
|||
load("//:hack/react.bzl", "react_library")
|
||||
load("//:hack/jest.bzl", "jest_test")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
react_library(
|
||||
name = "server-masthead",
|
||||
deps = [
|
||||
"next",
|
||||
"react",
|
||||
"react-icons",
|
||||
"styled-components",
|
||||
"//src/design-system/atoms/avatar",
|
||||
"//src/design-system/atoms/colors",
|
||||
"//src/design-system/atoms/timings",
|
||||
"//src/design-system/atoms/typography",
|
||||
"//src/design-system/shared-types",
|
||||
"@types/react",
|
||||
"@types/styled-components",
|
||||
],
|
||||
)
|
||||
|
||||
jest_test(
|
||||
src = ":server-masthead",
|
||||
deps = [
|
||||
"//src/design-system/shared-types",
|
||||
],
|
||||
)
|
|
@ -0,0 +1,19 @@
|
|||
jest.unmock('./ServerMasthead');
|
||||
|
||||
import * as React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { ServerMasthead } from './ServerMasthead';
|
||||
import { guild } from 'roleypoly/src/design-system/shared-types/storyData';
|
||||
import { Editable } from './ServerMasthead.styled';
|
||||
|
||||
it('shows Edit Server when editable is true', () => {
|
||||
const view = shallow(<ServerMasthead editable={true} guild={guild} />);
|
||||
|
||||
expect(view.find(Editable).length).not.toBe(0);
|
||||
});
|
||||
|
||||
it('hides Edit Server when editable is true', () => {
|
||||
const view = shallow(<ServerMasthead editable={false} guild={guild} />);
|
||||
|
||||
expect(view.find(Editable).length).toBe(0);
|
||||
});
|
|
@ -0,0 +1,17 @@
|
|||
import * as React from 'react';
|
||||
import { ServerMasthead } from './ServerMasthead';
|
||||
import { guild } from 'roleypoly/src/design-system/shared-types/storyData';
|
||||
|
||||
export default {
|
||||
title: 'Molecules/Server Masthead',
|
||||
args: {
|
||||
editable: false,
|
||||
guild,
|
||||
},
|
||||
};
|
||||
|
||||
export const Default = (args) => <ServerMasthead {...args} />;
|
||||
export const Editable = (args) => <ServerMasthead {...args} />;
|
||||
Editable.args = {
|
||||
editable: true,
|
||||
};
|
|
@ -0,0 +1,37 @@
|
|||
import styled from 'styled-components';
|
||||
import { palette } from 'roleypoly/src/design-system/atoms/colors';
|
||||
import { transitions } from 'roleypoly/src/design-system/atoms/timings';
|
||||
import * as _ from 'styled-components'; // tslint:disable-line:no-duplicate-imports
|
||||
|
||||
export const Wrapper = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
export const Name = styled.div`
|
||||
margin: 0 10px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
`;
|
||||
|
||||
export const Icon = styled.div`
|
||||
flex-shrink: 0;
|
||||
`;
|
||||
|
||||
export const Editable = styled.div`
|
||||
color: ${palette.taupe500};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
user-select: none;
|
||||
transition: color ${transitions.actionable}s ease-in-out;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: ${palette.taupe600};
|
||||
}
|
||||
`;
|
|
@ -0,0 +1,36 @@
|
|||
import { Guild } from 'roleypoly/src/design-system/shared-types';
|
||||
import { Avatar, utils } from 'roleypoly/src/design-system/atoms/avatar';
|
||||
import { AccentTitle, AmbientLarge } from 'roleypoly/src/design-system/atoms/typography';
|
||||
import Link from 'next/link';
|
||||
import { guild } from 'roleypoly/src/design-system/shared-types/storyData';
|
||||
import * as React from 'react';
|
||||
import { GoPencil } from 'react-icons/go';
|
||||
import { Editable, Icon, Name, Wrapper } from './ServerMasthead.styled';
|
||||
|
||||
export type ServerMastheadProps = {
|
||||
guild: Guild;
|
||||
editable: boolean;
|
||||
};
|
||||
|
||||
export const ServerMasthead = (props: ServerMastheadProps) => {
|
||||
return (
|
||||
<Wrapper>
|
||||
<Icon>
|
||||
<Avatar size={props.editable ? 60 : 48} src={guild.icon}>
|
||||
{utils.initialsFromName(props.guild.name)}
|
||||
</Avatar>
|
||||
</Icon>
|
||||
<Name>
|
||||
<AccentTitle>{props.guild.name}</AccentTitle>
|
||||
{props.editable && (
|
||||
<Link href="/s/[id]/edit" as={`/s/${props.guild.id}/edit`}>
|
||||
<Editable role="button">
|
||||
<GoPencil />
|
||||
<AmbientLarge>Edit Server</AmbientLarge>
|
||||
</Editable>
|
||||
</Link>
|
||||
)}
|
||||
</Name>
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
1
src/design-system/molecules/server-masthead/index.ts
Normal file
1
src/design-system/molecules/server-masthead/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './ServerMasthead';
|
17
src/design-system/molecules/user-avatar-group/BUILD.bazel
Normal file
17
src/design-system/molecules/user-avatar-group/BUILD.bazel
Normal file
|
@ -0,0 +1,17 @@
|
|||
load("//:hack/react.bzl", "react_library")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
react_library(
|
||||
name = "user-avatar-group",
|
||||
deps = [
|
||||
"react",
|
||||
"styled-components",
|
||||
"//src/design-system/atoms/avatar",
|
||||
"//src/design-system/atoms/breakpoints",
|
||||
"//src/design-system/atoms/colors",
|
||||
"//src/design-system/shared-types",
|
||||
"@types/react",
|
||||
"@types/styled-components",
|
||||
],
|
||||
)
|
|
@ -0,0 +1,19 @@
|
|||
import * as React from 'react';
|
||||
import { UserAvatarGroup } from './UserAvatarGroup';
|
||||
import { user } from 'roleypoly/src/design-system/shared-types/storyData';
|
||||
import { Hero } from 'roleypoly/src/design-system/atoms/hero';
|
||||
|
||||
export default {
|
||||
title: 'Molecules/User Avatar Group',
|
||||
component: UserAvatarGroup,
|
||||
args: {
|
||||
user,
|
||||
preventCollapse: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const Default = (args) => (
|
||||
<Hero>
|
||||
<UserAvatarGroup {...args} />
|
||||
</Hero>
|
||||
);
|
|
@ -0,0 +1,30 @@
|
|||
import styled, { css } from 'styled-components';
|
||||
import { onSmallScreen } from 'roleypoly/src/design-system/atoms/breakpoints';
|
||||
import { palette } from 'roleypoly/src/design-system/atoms/colors';
|
||||
import * as _ from 'styled-components'; // tslint:disable-line:no-duplicate-imports
|
||||
|
||||
export const Collapse = styled.div<{ preventCollapse: boolean }>`
|
||||
${(props) =>
|
||||
!props.preventCollapse &&
|
||||
onSmallScreen(css`
|
||||
display: none;
|
||||
`)}
|
||||
`;
|
||||
|
||||
export const Group = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
white-space: nowrap;
|
||||
`;
|
||||
|
||||
export const Discriminator = styled.span`
|
||||
color: ${palette.taupe500};
|
||||
font-size: 75%;
|
||||
padding: 0 5px;
|
||||
`;
|
||||
|
||||
export const GroupText = styled.span`
|
||||
position: relative;
|
||||
top: -2px;
|
||||
`;
|
|
@ -0,0 +1,24 @@
|
|||
import * as React from 'react';
|
||||
import { DiscordUser } from 'roleypoly/src/design-system/shared-types';
|
||||
import { utils, Avatar } from 'roleypoly/src/design-system/atoms/avatar';
|
||||
import { Group, Collapse, Discriminator, GroupText } from './UserAvatarGroup.styled';
|
||||
|
||||
type Props = {
|
||||
user: DiscordUser;
|
||||
preventCollapse?: boolean;
|
||||
};
|
||||
|
||||
export const UserAvatarGroup = (props: Props) => (
|
||||
<Group>
|
||||
<Collapse preventCollapse={props.preventCollapse || false}>
|
||||
<GroupText>
|
||||
{props.user.username}
|
||||
<Discriminator>#{props.user.discriminator}</Discriminator>
|
||||
</GroupText>
|
||||
|
||||
</Collapse>
|
||||
<Avatar size={34} src={props.user.avatar}>
|
||||
{utils.initialsFromName(props.user.username)}
|
||||
</Avatar>
|
||||
</Group>
|
||||
);
|
1
src/design-system/molecules/user-avatar-group/index.ts
Normal file
1
src/design-system/molecules/user-avatar-group/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './UserAvatarGroup';
|
20
src/design-system/molecules/user-popover/BUILD.bazel
Normal file
20
src/design-system/molecules/user-popover/BUILD.bazel
Normal file
|
@ -0,0 +1,20 @@
|
|||
load("//:hack/react.bzl", "react_library")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
react_library(
|
||||
name = "user-popover",
|
||||
deps = [
|
||||
"next",
|
||||
"react",
|
||||
"react-icons",
|
||||
"styled-components",
|
||||
"//src/design-system/atoms/breakpoints",
|
||||
"//src/design-system/atoms/colors",
|
||||
"//src/design-system/atoms/timings",
|
||||
"//src/design-system/molecules/user-avatar-group",
|
||||
"//src/design-system/shared-types",
|
||||
"@types/react",
|
||||
"@types/styled-components",
|
||||
],
|
||||
)
|
|
@ -0,0 +1,18 @@
|
|||
import { user } from 'roleypoly/src/design-system/shared-types/storyData';
|
||||
import * as React from 'react';
|
||||
import { UserPopover as UserPopoverComponent } from './UserPopover';
|
||||
import { PopoverBase } from 'roleypoly/src/design-system/atoms/popover/Popover.styled';
|
||||
|
||||
export default {
|
||||
title: 'Molecules/User Popover',
|
||||
component: UserPopoverComponent,
|
||||
args: {
|
||||
user,
|
||||
},
|
||||
};
|
||||
|
||||
export const UserPopover = (args) => (
|
||||
<PopoverBase active>
|
||||
<UserPopoverComponent {...args} />
|
||||
</PopoverBase>
|
||||
);
|
|
@ -0,0 +1,34 @@
|
|||
import styled from 'styled-components';
|
||||
import { palette } from 'roleypoly/src/design-system/atoms/colors';
|
||||
import { transitions } from 'roleypoly/src/design-system/atoms/timings';
|
||||
import * as _ from 'styled-components'; // tslint:disable-line:no-duplicate-imports
|
||||
|
||||
export const Base = styled.div`
|
||||
text-align: right;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
user-select: none;
|
||||
`;
|
||||
|
||||
export const NavAction = styled.div`
|
||||
height: 2.25em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
transition: color ${transitions.actionable}s ease-in-out;
|
||||
color: ${palette.taupe500};
|
||||
box-sizing: border-box;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
color: ${palette.taupe600};
|
||||
}
|
||||
|
||||
svg {
|
||||
font-size: 120%;
|
||||
box-sizing: content-box;
|
||||
padding: 5px 8px;
|
||||
position: relative;
|
||||
top: 0.1em;
|
||||
}
|
||||
`;
|
30
src/design-system/molecules/user-popover/UserPopover.tsx
Normal file
30
src/design-system/molecules/user-popover/UserPopover.tsx
Normal file
|
@ -0,0 +1,30 @@
|
|||
import * as React from 'react';
|
||||
import { DiscordUser } from 'roleypoly/src/design-system/shared-types';
|
||||
import { UserAvatarGroup } from 'roleypoly/src/design-system/molecules/user-avatar-group';
|
||||
import { Base, NavAction } from './UserPopover.styled';
|
||||
import { GoGear, GoSignOut } from 'react-icons/go';
|
||||
import Link from 'next/link';
|
||||
|
||||
type UserPopoverProps = {
|
||||
user: DiscordUser;
|
||||
};
|
||||
|
||||
export const UserPopover = (props: UserPopoverProps) => (
|
||||
<Base>
|
||||
<UserAvatarGroup user={props.user} preventCollapse={true} />
|
||||
<NavAction>
|
||||
<Link href="/user/settings">
|
||||
<>
|
||||
Settings <GoGear />
|
||||
</>
|
||||
</Link>
|
||||
</NavAction>
|
||||
<NavAction>
|
||||
<Link href="/auth/machinery/logout">
|
||||
<>
|
||||
Log Out <GoSignOut />
|
||||
</>
|
||||
</Link>
|
||||
</NavAction>
|
||||
</Base>
|
||||
);
|
1
src/design-system/molecules/user-popover/index.ts
Normal file
1
src/design-system/molecules/user-popover/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './UserPopover';
|
18
src/design-system/organisms/app-shell/AppShell.story.tsx
Normal file
18
src/design-system/organisms/app-shell/AppShell.story.tsx
Normal file
|
@ -0,0 +1,18 @@
|
|||
import * as React from 'react';
|
||||
import { organismStories } from 'roleypoly/src/design-system/organisms/organisms.story';
|
||||
import { AppShell } from './AppShell';
|
||||
import { rpUser, guildEnum } from 'roleypoly/src/design-system/shared-types/storyData';
|
||||
|
||||
const story = organismStories('App Shell', module);
|
||||
|
||||
story.add('Guest', () => (
|
||||
<AppShell showFooter user={null}>
|
||||
<h1>Hello World</h1>
|
||||
</AppShell>
|
||||
));
|
||||
|
||||
story.add('Logged In', () => (
|
||||
<AppShell user={rpUser} guildEnumeration={guildEnum}>
|
||||
<h1>Hello World</h1>
|
||||
</AppShell>
|
||||
));
|
22
src/design-system/organisms/app-shell/AppShell.styled.tsx
Normal file
22
src/design-system/organisms/app-shell/AppShell.styled.tsx
Normal file
|
@ -0,0 +1,22 @@
|
|||
import styled, { createGlobalStyle } from 'styled-components';
|
||||
import { palette } from 'roleypoly/src/design-system/atoms/colors';
|
||||
|
||||
export const Content = styled.div<{ small?: boolean }>`
|
||||
margin: 0 auto;
|
||||
margin-top: 50px;
|
||||
width: ${(props) => (props.small ? '960px' : '1024px')};
|
||||
max-width: 98vw;
|
||||
max-height: calc(100vh - 50px);
|
||||
`;
|
||||
|
||||
export const GlobalStyles = createGlobalStyle`
|
||||
body {
|
||||
background-color: ${palette.taupe200};
|
||||
color: ${palette.grey600};
|
||||
overflow-y: hidden;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
`;
|
41
src/design-system/organisms/app-shell/AppShell.tsx
Normal file
41
src/design-system/organisms/app-shell/AppShell.tsx
Normal file
|
@ -0,0 +1,41 @@
|
|||
import * as React from 'react';
|
||||
import * as Masthead from 'roleypoly/src/design-system/organisms/masthead';
|
||||
import { RoleypolyUser } from '@roleypoly/rpc/shared';
|
||||
import { Footer } from 'roleypoly/src/design-system/molecules/footer';
|
||||
import { Content, GlobalStyles } from './AppShell.styled';
|
||||
import { GlobalStyleColors } from 'roleypoly/src/design-system/atoms/colors';
|
||||
import { GuildEnumeration } from '@roleypoly/rpc/platform';
|
||||
import { Scrollbars } from 'react-custom-scrollbars';
|
||||
|
||||
type AppShellProps = {
|
||||
children: React.ReactNode;
|
||||
user: RoleypolyUser.AsObject | null;
|
||||
showFooter?: boolean;
|
||||
small?: boolean;
|
||||
activeGuildId?: string | null;
|
||||
guildEnumeration?: GuildEnumeration.AsObject;
|
||||
};
|
||||
|
||||
export const AppShell = (props: AppShellProps) => (
|
||||
<>
|
||||
<GlobalStyles />
|
||||
<GlobalStyleColors />
|
||||
{props.user !== null ? (
|
||||
<Masthead.Authed
|
||||
guildEnumeration={props.guildEnumeration || { guildsList: [] }}
|
||||
activeGuildId={props.activeGuildId || null}
|
||||
user={props.user}
|
||||
/>
|
||||
) : (
|
||||
<Masthead.Guest />
|
||||
)}
|
||||
<Scrollbars
|
||||
style={{ height: 'calc(100vh - 25px)', margin: 0, padding: 0 }}
|
||||
autoHide
|
||||
universal
|
||||
>
|
||||
<Content small={props.small}>{props.children}</Content>
|
||||
{props.showFooter && <Footer />}
|
||||
</Scrollbars>
|
||||
</>
|
||||
);
|
1
src/design-system/organisms/app-shell/index.ts
Normal file
1
src/design-system/organisms/app-shell/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './AppShell';
|
|
@ -0,0 +1,16 @@
|
|||
import * as React from 'react';
|
||||
import { organismStories } from 'roleypoly/src/design-system/organisms/organisms.story';
|
||||
import { ErrorBanner } from './ErrorBanner';
|
||||
import { text } from '@storybook/addon-knobs';
|
||||
|
||||
const story = organismStories('Error Banner', module);
|
||||
|
||||
story.add('Error Banner', () => (
|
||||
<ErrorBanner
|
||||
message={{
|
||||
english: text('English', 'Primary Text'),
|
||||
japanese: text('Japanese (Subtext)', 'Subtext'),
|
||||
friendlyCode: text('Friendly Code', 'Oops!'),
|
||||
}}
|
||||
/>
|
||||
));
|
|
@ -0,0 +1,40 @@
|
|||
import { onSmallScreen } from 'roleypoly/src/design-system/atoms/breakpoints';
|
||||
import { palette } from 'roleypoly/src/design-system/atoms/colors';
|
||||
import { text300, text500, text700 } from 'roleypoly/src/design-system/atoms/typography';
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
export const ErrorWrapper = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
${onSmallScreen(css`
|
||||
display: block;
|
||||
text-align: center;
|
||||
`)}
|
||||
`;
|
||||
|
||||
export const ErrorDivider = styled.div`
|
||||
width: 1px;
|
||||
height: 3em;
|
||||
background: ${palette.grey600};
|
||||
margin: 0 1em;
|
||||
${onSmallScreen(css`
|
||||
display: none;
|
||||
`)}
|
||||
`;
|
||||
|
||||
export const ErrorSideCode = styled.div`
|
||||
${text700}
|
||||
${onSmallScreen(css`
|
||||
margin-bottom: 0.4em;
|
||||
`)}
|
||||
`;
|
||||
|
||||
export const ErrorText = styled.div`
|
||||
${text500}
|
||||
`;
|
||||
|
||||
export const ErrorTextLower = styled.div`
|
||||
${text300}
|
||||
color: ${palette.taupe500};
|
||||
`;
|
24
src/design-system/organisms/error-banner/ErrorBanner.tsx
Normal file
24
src/design-system/organisms/error-banner/ErrorBanner.tsx
Normal file
|
@ -0,0 +1,24 @@
|
|||
import * as React from 'react';
|
||||
import {
|
||||
ErrorWrapper,
|
||||
ErrorDivider,
|
||||
ErrorSideCode,
|
||||
ErrorText,
|
||||
ErrorTextLower,
|
||||
} from './ErrorBanner.styled';
|
||||
import { ErrorMessage } from 'templates/errors/errorStrings';
|
||||
|
||||
type ErrorBannerProps = {
|
||||
message: Required<ErrorMessage>;
|
||||
};
|
||||
|
||||
export const ErrorBanner = (props: ErrorBannerProps) => (
|
||||
<ErrorWrapper>
|
||||
<ErrorSideCode>{props.message.friendlyCode}</ErrorSideCode>
|
||||
<ErrorDivider />
|
||||
<div>
|
||||
<ErrorText>{props.message.english}</ErrorText>
|
||||
<ErrorTextLower>{props.message.japanese}</ErrorTextLower>
|
||||
</div>
|
||||
</ErrorWrapper>
|
||||
);
|
1
src/design-system/organisms/error-banner/index.ts
Normal file
1
src/design-system/organisms/error-banner/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './ErrorBanner';
|
|
@ -0,0 +1,19 @@
|
|||
import * as React from 'react';
|
||||
import { organismStories } from 'roleypoly/src/design-system/organisms/organisms.story';
|
||||
import { HelpPageBase } from './HelpPageBase';
|
||||
import { Content } from 'roleypoly/src/design-system/organisms/app-shell/AppShell.styled';
|
||||
|
||||
const baseStory = organismStories('Help Pages', module);
|
||||
|
||||
export const HelpStoryWrapper = (props: { children: React.ReactNode }) => (
|
||||
<Content>
|
||||
<HelpPageBase>{props.children}</HelpPageBase>
|
||||
</Content>
|
||||
);
|
||||
|
||||
baseStory.add('Base', () => (
|
||||
<HelpStoryWrapper>
|
||||
<h1>What is the world but vibrations?</h1>
|
||||
<p>Vibrations that synchronize and tie it together, running free forever.</p>
|
||||
</HelpStoryWrapper>
|
||||
));
|
21
src/design-system/organisms/help-page-base/HelpPageBase.tsx
Normal file
21
src/design-system/organisms/help-page-base/HelpPageBase.tsx
Normal file
|
@ -0,0 +1,21 @@
|
|||
import * as React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { palette } from 'roleypoly/src/design-system/atoms/colors';
|
||||
|
||||
export type HelpPageProps = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const Container = styled.div`
|
||||
background: ${palette.taupe300};
|
||||
padding: 2em 3em;
|
||||
width: 1024px;
|
||||
max-width: 98vw;
|
||||
margin: 0 auto;
|
||||
margin-top: 75px;
|
||||
box-sizing: border-box;
|
||||
`;
|
||||
|
||||
export const HelpPageBase = (props: HelpPageProps) => (
|
||||
<Container>{props.children}</Container>
|
||||
);
|
1
src/design-system/organisms/help-page-base/index.ts
Normal file
1
src/design-system/organisms/help-page-base/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './HelpPageBase';
|
|
@ -0,0 +1,10 @@
|
|||
import { WhyNoRoles } from './WhyNoRoles';
|
||||
import * as React from 'react';
|
||||
import { organismStories } from 'roleypoly/src/design-system/organisms/organisms.story';
|
||||
import { HelpStoryWrapper } from 'roleypoly/src/design-system/organisms/help-page-base/HelpPageBase.story';
|
||||
|
||||
organismStories('Help Pages/Pages', module).add('Why No Roles', () => (
|
||||
<HelpStoryWrapper>
|
||||
<WhyNoRoles />
|
||||
</HelpStoryWrapper>
|
||||
));
|
|
@ -0,0 +1,35 @@
|
|||
import styled, { css } from 'styled-components';
|
||||
import { palette, numberToChroma } from 'roleypoly/src/design-system/atoms/colors';
|
||||
import { Role } from '@roleypoly/rpc/shared';
|
||||
|
||||
export const DiscordBase = styled.div`
|
||||
background-color: ${palette.discord100};
|
||||
border: solid 1px rgba(0, 0, 0, 0.15);
|
||||
border-radius: 3px;
|
||||
padding: 10px;
|
||||
user-select: none;
|
||||
width: 175px;
|
||||
`;
|
||||
|
||||
const hover = (roleColor: string) => css`
|
||||
color: #efefef;
|
||||
background-color: ${roleColor};
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
export const DiscordRole = styled.div<{
|
||||
discordRole: Role.AsObject;
|
||||
isRoleypoly: boolean;
|
||||
}>`
|
||||
padding: 6px 10px;
|
||||
color: ${(props) => numberToChroma(props.discordRole.color).css()};
|
||||
border-radius: 3px;
|
||||
|
||||
:hover {
|
||||
${(props) => hover(numberToChroma(props.discordRole.color).alpha(0.5).css())}
|
||||
}
|
||||
|
||||
${(props) =>
|
||||
props.isRoleypoly &&
|
||||
hover(numberToChroma(props.discordRole.color).alpha(0.5).css())}
|
||||
`;
|
86
src/design-system/organisms/help-why-no-roles/WhyNoRoles.tsx
Normal file
86
src/design-system/organisms/help-why-no-roles/WhyNoRoles.tsx
Normal file
|
@ -0,0 +1,86 @@
|
|||
import * as React from 'react';
|
||||
import {
|
||||
HalfsiesContainer,
|
||||
HalfsiesItem,
|
||||
} from 'roleypoly/src/design-system/atoms/halfsies';
|
||||
import { FaCheck, FaTimes } from 'react-icons/fa';
|
||||
import { DiscordBase, DiscordRole } from './WhyNoRoles.styled';
|
||||
import { demoData } from 'roleypoly/src/design-system/shared-types/demoData';
|
||||
import { Role } from '@roleypoly/rpc/shared';
|
||||
import { palette } from 'roleypoly/src/design-system/atoms/colors';
|
||||
import chroma from 'chroma-js';
|
||||
import { SparkleOverlay } from 'roleypoly/src/design-system/atoms/sparkle';
|
||||
|
||||
const adminRoles: Role.AsObject[] = [
|
||||
{
|
||||
id: 'roley2',
|
||||
name: 'Admin',
|
||||
permissions: 0,
|
||||
color: chroma('hotpink').num(),
|
||||
position: -1,
|
||||
managed: true,
|
||||
safety: 0,
|
||||
},
|
||||
{
|
||||
id: 'roley3',
|
||||
name: 'Moderator',
|
||||
permissions: 0,
|
||||
color: chroma('lime').num(),
|
||||
position: -1,
|
||||
managed: true,
|
||||
safety: 0,
|
||||
},
|
||||
];
|
||||
|
||||
const roleypolyRole: Role.AsObject = {
|
||||
id: 'roley',
|
||||
name: 'Roleypoly',
|
||||
permissions: 0,
|
||||
color: chroma(palette.taupe500).num(),
|
||||
position: -1,
|
||||
managed: true,
|
||||
safety: 0,
|
||||
};
|
||||
|
||||
const goodRoles = [...adminRoles, roleypolyRole, ...demoData];
|
||||
|
||||
const badRoles = [...adminRoles, ...demoData, roleypolyRole];
|
||||
|
||||
const MaybeWithOverlay = (props: { children: React.ReactNode; withOverlay: boolean }) => {
|
||||
if (props.withOverlay) {
|
||||
return (
|
||||
<SparkleOverlay size={-5} repeatCount={10}>
|
||||
{props.children}
|
||||
</SparkleOverlay>
|
||||
);
|
||||
} else {
|
||||
return <>{props.children}</>;
|
||||
}
|
||||
};
|
||||
|
||||
const Example = (props: { roles: Role.AsObject[]; isGood: boolean }) => (
|
||||
<div>
|
||||
<DiscordBase>
|
||||
{props.roles.map((r) => (
|
||||
<MaybeWithOverlay withOverlay={props.isGood && r.name === 'Roleypoly'}>
|
||||
<DiscordRole discordRole={r} isRoleypoly={r.name === 'Roleypoly'}>
|
||||
{r.name}
|
||||
</DiscordRole>
|
||||
</MaybeWithOverlay>
|
||||
))}
|
||||
</DiscordBase>
|
||||
</div>
|
||||
);
|
||||
|
||||
export const WhyNoRoles = () => (
|
||||
<HalfsiesContainer>
|
||||
<HalfsiesItem>
|
||||
<FaCheck /> Good
|
||||
<Example isGood roles={goodRoles} />
|
||||
</HalfsiesItem>
|
||||
<HalfsiesItem>
|
||||
<FaTimes /> Baddd
|
||||
<Example isGood={false} roles={badRoles} />
|
||||
</HalfsiesItem>
|
||||
</HalfsiesContainer>
|
||||
);
|
1
src/design-system/organisms/help-why-no-roles/index.ts
Normal file
1
src/design-system/organisms/help-why-no-roles/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './WhyNoRoles';
|
7
src/design-system/organisms/landing/Landing.story.tsx
Normal file
7
src/design-system/organisms/landing/Landing.story.tsx
Normal file
|
@ -0,0 +1,7 @@
|
|||
import * as React from 'react';
|
||||
import { organismStories } from 'roleypoly/src/design-system/organisms/organisms.story';
|
||||
import { Landing } from './Landing';
|
||||
|
||||
const story = organismStories('Landing', module);
|
||||
|
||||
story.add('Landing', () => <Landing />);
|
31
src/design-system/organisms/landing/Landing.styled.ts
Normal file
31
src/design-system/organisms/landing/Landing.styled.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
import { onTablet } from 'roleypoly/src/design-system/atoms/breakpoints';
|
||||
import { text400 } from 'roleypoly/src/design-system/atoms/typography';
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
export const HeroText = styled.div`
|
||||
${onTablet(css`
|
||||
text-align: center;
|
||||
`)}
|
||||
`;
|
||||
|
||||
export const DemoSubtitle = styled.p`
|
||||
${text400}
|
||||
text-align: center;
|
||||
margin-top: 0.4em;
|
||||
`;
|
||||
|
||||
export const DemoAlignment = styled.div`
|
||||
min-height: 125px;
|
||||
${onTablet(css`
|
||||
min-height: 95px;
|
||||
`)}
|
||||
`;
|
||||
|
||||
export const HeroCentering = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: calc(100vh - 200px);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 2em;
|
||||
`;
|
41
src/design-system/organisms/landing/Landing.tsx
Normal file
41
src/design-system/organisms/landing/Landing.tsx
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { palette } from 'roleypoly/src/design-system/atoms/colors';
|
||||
import { Space } from 'roleypoly/src/design-system/atoms/space';
|
||||
import { LargeText, LargeTitle } from 'roleypoly/src/design-system/atoms/typography';
|
||||
import { DemoDiscord } from 'roleypoly/src/design-system/molecules/demo-discord';
|
||||
import { DemoPicker } from 'roleypoly/src/design-system/molecules/demo-picker';
|
||||
import * as React from 'react';
|
||||
import { DemoAlignment, DemoSubtitle, HeroCentering, HeroText } from './Landing.styled';
|
||||
import {
|
||||
HalfsiesContainer,
|
||||
HalfsiesItem,
|
||||
} from 'roleypoly/src/design-system/atoms/halfsies';
|
||||
|
||||
export const Landing = () => (
|
||||
<HeroCentering>
|
||||
<HeroText>
|
||||
<div>
|
||||
<LargeTitle>Discord roles for humans.</LargeTitle>
|
||||
</div>
|
||||
<div style={{ color: palette.taupe500 }}>
|
||||
<LargeText>
|
||||
Ditch the bot commands. It's {new Date().getFullYear()}.
|
||||
</LargeText>
|
||||
</div>
|
||||
</HeroText>
|
||||
<Space />
|
||||
<HalfsiesContainer>
|
||||
<HalfsiesItem style={{ marginTop: '2em' }}>
|
||||
<DemoAlignment>
|
||||
<DemoDiscord />
|
||||
</DemoAlignment>
|
||||
<DemoSubtitle>Why are you okay with antiques?</DemoSubtitle>
|
||||
</HalfsiesItem>
|
||||
<HalfsiesItem style={{ marginTop: '2em' }}>
|
||||
<DemoAlignment>
|
||||
<DemoPicker />
|
||||
</DemoAlignment>
|
||||
<DemoSubtitle>Just click or tap.</DemoSubtitle>
|
||||
</HalfsiesItem>
|
||||
</HalfsiesContainer>
|
||||
</HeroCentering>
|
||||
);
|
1
src/design-system/organisms/landing/index.ts
Normal file
1
src/design-system/organisms/landing/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './Landing';
|
99
src/design-system/organisms/masthead/Authed.tsx
Normal file
99
src/design-system/organisms/masthead/Authed.tsx
Normal file
|
@ -0,0 +1,99 @@
|
|||
import { GuildEnumeration } from '@roleypoly/rpc/platform';
|
||||
import { RoleypolyUser } from '@roleypoly/rpc/shared';
|
||||
import { Logomark } from 'roleypoly/src/design-system/atoms/branding';
|
||||
import { Popover } from 'roleypoly/src/design-system/atoms/popover';
|
||||
import { guildEnum } from 'roleypoly/src/design-system/shared-types/storyData';
|
||||
import { GuildNav } from 'roleypoly/src/design-system/molecules/guild-nav';
|
||||
import { NavSlug } from 'roleypoly/src/design-system/molecules/nav-slug';
|
||||
import { UserAvatarGroup } from 'roleypoly/src/design-system/molecules/user-avatar-group';
|
||||
import { UserPopover } from 'roleypoly/src/design-system/molecules/user-popover';
|
||||
import Link from 'next/link';
|
||||
import * as React from 'react';
|
||||
import { GoOrganization } from 'react-icons/go';
|
||||
import {
|
||||
GuildPopoverHead,
|
||||
InteractionBase,
|
||||
MastheadA,
|
||||
MastheadAlignment,
|
||||
MastheadBase,
|
||||
MastheadLeft,
|
||||
MastheadRight,
|
||||
} from './Masthead.styled';
|
||||
|
||||
type Props = {
|
||||
user: RoleypolyUser.AsObject;
|
||||
activeGuildId: string | null;
|
||||
guildEnumeration: GuildEnumeration.AsObject;
|
||||
};
|
||||
|
||||
export const Authed = (props: Props) => {
|
||||
const [userPopoverState, setUserPopoverState] = React.useState(false);
|
||||
const [serverPopoverState, setServerPopoverState] = React.useState(false);
|
||||
|
||||
return (
|
||||
<MastheadBase>
|
||||
<MastheadAlignment>
|
||||
<MastheadLeft>
|
||||
<Link href="/dashboard" passHref>
|
||||
<MastheadA>
|
||||
<Logomark height={40} />
|
||||
</MastheadA>
|
||||
</Link>
|
||||
<InteractionBase
|
||||
onClick={() => {
|
||||
setServerPopoverState(true);
|
||||
setUserPopoverState(false);
|
||||
}}
|
||||
hide={!serverPopoverState}
|
||||
>
|
||||
<NavSlug
|
||||
guild={
|
||||
guildEnum.guildsList.find(
|
||||
(g) => g.id === props.activeGuildId
|
||||
)?.guild || null
|
||||
}
|
||||
/>
|
||||
</InteractionBase>
|
||||
<Popover
|
||||
headContent={
|
||||
<GuildPopoverHead>
|
||||
<GoOrganization />
|
||||
My Guilds
|
||||
</GuildPopoverHead>
|
||||
}
|
||||
canDefocus
|
||||
position="bottom left"
|
||||
active={serverPopoverState}
|
||||
onExit={() => setServerPopoverState(false)}
|
||||
>
|
||||
<GuildNav guildEnumeration={props.guildEnumeration} />
|
||||
</Popover>
|
||||
</MastheadLeft>
|
||||
<MastheadRight>
|
||||
<InteractionBase
|
||||
onClick={() => {
|
||||
setUserPopoverState(true);
|
||||
setServerPopoverState(false);
|
||||
}}
|
||||
hide={!userPopoverState}
|
||||
>
|
||||
{props.user.discorduser && (
|
||||
<UserAvatarGroup user={props.user.discorduser} />
|
||||
)}
|
||||
</InteractionBase>
|
||||
<Popover
|
||||
headContent={<></>}
|
||||
canDefocus
|
||||
position="top right"
|
||||
active={userPopoverState}
|
||||
onExit={() => setUserPopoverState(false)}
|
||||
>
|
||||
{props.user.discorduser && (
|
||||
<UserPopover user={props.user.discorduser} />
|
||||
)}
|
||||
</Popover>
|
||||
</MastheadRight>
|
||||
</MastheadAlignment>
|
||||
</MastheadBase>
|
||||
);
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue