Add Activity Center (#312)

* add activityCenter.ts

* use activityCenter.ts

* add "Launch via Vite Node"

* remove comments

* add comments

* type hook

* reverse order of notifications

* remove `activityCenter` from `provider.tsx`

* set `count`'s default value

* ref `ChatMessage` by id instead of object reference

* Revert "ref `ChatMessage` by id instead of object reference"

This reverts commit 1284386d22.

* add badge `totalCount`

* add `@radix-ui/react-tabs`

* skip non-mentions

* update comments

* add `member` to `ChatMessage`

* add `MarkAllAsReadIcon`

* add `40` avatar variant

* add `initialsCount` to `Avatar`

* add `TinyReplyIcon`

* use `Popover`

* move components

* move components

* wip

* ignore clicks on tags themselves

* add root route

* use chat display name

* fix icons

* use `asChild` for tabs

* remove `vertical`

* use `value` and `onValueChange`

* use active state with `compoundVariants`

* use `&[data-state]...` instead of `compoundVarinats` and `active` prop

* fixup `chatDisplayName`

* postion badge

* prevent dragging of the whole page in either direction

* add tooltips

* change message's background on hover

* change navigation style

* add `category` to `removeNotifications`

* prevent deleting of messages that are not mentions or replies from activity center

* show members sidebar by default

* fix members layout

* add comment

* add `date-fns`

* add date separator

* comment

* move `Badge` to `/system`

* move `Tag` to `/system`

* comment `Text`

* replace `Link` for `useNavigate`

* move `Activity` to `/system`

* comment

* export `Notification`

* update fixtures in `activityCenter`

* remove fixtures

* movet `Tabs` to `/system`

* update `mapChatMessage`

* update `.eslintignore`

* resolve typecheck errors

* update `.prettierignore`

* resolve formatting error

* upgrade tooltip

* use `scrollIntoView`

* remove comments

* rename `close` to `onNavigateChange`

* revert scrolling changes in `/member-sidebar`

* rename members label from `Online` to `All`

* rename `initialsCount` and use union

* rename `MarkAllAsReadIcon`

* rename `selected`

* fix typo

* revert formatting

* close `Avatar`

* use `css` where possible without changing elements

* extract props types to interfaces

* rename `ActivityCenter`

* remove `overflow`

* replace `style` for not yet stitched elements

* revert global style changes
This commit is contained in:
Felicio Mununga 2022-10-07 20:02:17 +02:00 committed by GitHub
parent 2b71b49e71
commit 36322d5a41
No known key found for this signature in database
GPG Key ID: 0EB8D75C775AB6F1
49 changed files with 1232 additions and 95 deletions

View File

@ -2,3 +2,4 @@
**/node_modules **/node_modules
**/protos **/protos
**/proto **/proto
**/coverage

View File

@ -3,3 +3,4 @@
.parcel-cache .parcel-cache
.github .github
**/protos **/protos
**/coverage

View File

@ -1,10 +1,11 @@
// todo?: rename to notifications (center?), inbox, or keep same as other platforms // todo?: rename to notifications (center?), inbox, or keep same as other platforms
// todo: use kebab case for the file name
import type { ChatMessage } from './chat' import type { ChatMessage } from './chat'
import type { Client } from './client' import type { Client } from './client'
// todo?: rename to Activity // todo?: rename to Activity
type Notification = { export type Notification = {
type: 'message' type: 'message'
value: ChatMessage value: ChatMessage
isMention?: boolean isMention?: boolean
@ -13,8 +14,9 @@ type Notification = {
export type ActivityCenterLatest = { export type ActivityCenterLatest = {
notifications: Notification[] notifications: Notification[]
// todo?: rename count to mentionsAndRepliesCount // todo?: rename count to mentionsAndRepliesCount, mentionsCount
unreadChats: Map<string, { count: number }> unreadChats: Map<string, { count: number }>
totalCount: number
} }
export class ActivityCenter { export class ActivityCenter {
@ -33,6 +35,7 @@ export class ActivityCenter {
public getLatest = (): ActivityCenterLatest => { public getLatest = (): ActivityCenterLatest => {
const notifications: Notification[] = [] const notifications: Notification[] = []
const unreadChats: Map<string, { count: number }> = new Map() const unreadChats: Map<string, { count: number }> = new Map()
let totalCount = 0
for (const notification of this.#notifications.values()) { for (const notification of this.#notifications.values()) {
if (notification.type === 'message') { if (notification.type === 'message') {
@ -41,8 +44,10 @@ export class ActivityCenter {
const chat = unreadChats.get(chatUuid) const chat = unreadChats.get(chatUuid)
let count = chat?.count ?? 0 let count = chat?.count ?? 0
if (notification.isMention || notification.isReply) { const isMention = notification.isMention || notification.isReply
if (isMention) {
count++ count++
totalCount++
} }
if (chat) { if (chat) {
@ -50,6 +55,10 @@ export class ActivityCenter {
} else { } else {
unreadChats.set(chatUuid, { count }) unreadChats.set(chatUuid, { count })
} }
if (!isMention) {
continue
}
} }
notifications.push(notification) notifications.push(notification)
@ -67,12 +76,14 @@ export class ActivityCenter {
return 0 return 0
}) })
// fixme!?: do not display regular messages, only mentions and replies return {
// todo?: group notifications (all, unreads, mentions, replies, _chats.{id,count}) // todo?: group notifications (all, mentions, replies)
return { notifications, unreadChats } notifications,
unreadChats,
totalCount,
}
} }
// todo: pass ids instead of values and resolve within
public addMessageNotification = ( public addMessageNotification = (
newMessage: ChatMessage, newMessage: ChatMessage,
referencedMessage?: ChatMessage referencedMessage?: ChatMessage
@ -98,15 +109,49 @@ export class ActivityCenter {
this.emitLatest() this.emitLatest()
} }
// todo?: also calls `clearChatNotifications` (on non-action items/notifications)?
// markAllAsRead = () => {}
// todo?: for example from chat with red bar in UI indicating start
// markChatNotificationsAsUnreadSince = (category, id) => {}
// todo?: rename to `clearChatNotifications`; separate (button) from `markAllAsRead`?
// todo?: merge with `removeChatNotifications`; together with `notification.type` condition
/** /**
* Removes all notifications. * Removes all notifications.
*/ */
removeNotifications = () => { removeNotifications = (category: 'all' | 'mentions' | 'replies') => {
this.#notifications.clear() // todo?: clear all non-actionable notification too
if (category === 'all') {
for (const notification of this.#notifications) {
const { isMention, isReply } = notification
if (!(isMention || isReply)) {
continue
}
this.#notifications.delete(notification)
}
} else if (category === 'mentions') {
// todo: extract to a func
this.#notifications.forEach(notification => {
if (notification.isMention) {
this.#notifications.delete(notification)
}
})
} else if (category === 'replies') {
this.#notifications.forEach(notification => {
if (notification.isReply) {
this.#notifications.delete(notification)
}
})
}
this.emitLatest() this.emitLatest()
} }
// todo?: call from UI on ESC
// todo?: call from UI if scrolled all the way to the end
/** /**
* Removes chat message notifications from the Activity Center. For example, * Removes chat message notifications from the Activity Center. For example,
* on only opening or after scrolling to the end. * on only opening or after scrolling to the end.

View File

@ -20,6 +20,7 @@ import type { MessageType } from '../protos/enums'
import type { Client } from './client' import type { Client } from './client'
import type { Community } from './community/community' import type { Community } from './community/community'
import type { Reactions } from './community/get-reactions' import type { Reactions } from './community/get-reactions'
import type { Member } from './member'
import type { WakuMessage } from 'js-waku' import type { WakuMessage } from 'js-waku'
export type ChatMessage = ChatMessageProto & { export type ChatMessage = ChatMessageProto & {
@ -28,6 +29,9 @@ export type ChatMessage = ChatMessageProto & {
reactions: Reactions reactions: Reactions
chatUuid: string chatUuid: string
signer: string signer: string
member: Member
communityDisplayName: string
chatDisplayName: string
responseToMessage?: ChatMessage responseToMessage?: ChatMessage
edittedClock?: bigint edittedClock?: bigint
pinnedClock?: bigint pinnedClock?: bigint

View File

@ -126,6 +126,8 @@ export function handleWakuMessage(
messageId, messageId,
chatUuid, chatUuid,
signerPublicKey, signerPublicKey,
community,
chat,
}) })
// handle // handle

View File

@ -1,5 +1,6 @@
import type { ChatMessage as ChatMessageProto } from '../../protos/chat-message' import type { ChatMessage as ChatMessageProto } from '../../protos/chat-message'
import type { ChatMessage } from '../chat' import type { Chat, ChatMessage } from '../chat'
import type { Community } from './community'
export function mapChatMessage( export function mapChatMessage(
decodedMessage: ChatMessageProto, decodedMessage: ChatMessageProto,
@ -7,9 +8,11 @@ export function mapChatMessage(
messageId: string messageId: string
chatUuid: string chatUuid: string
signerPublicKey: string signerPublicKey: string
community: Community
chat: Chat
} }
): ChatMessage { ): ChatMessage {
const { messageId, chatUuid, signerPublicKey } = props const { messageId, chatUuid, signerPublicKey, community, chat } = props
const message: ChatMessage = { const message: ChatMessage = {
...decodedMessage, ...decodedMessage,
@ -25,6 +28,10 @@ export function mapChatMessage(
SAD: new Set<string>(), SAD: new Set<string>(),
ANGRY: new Set<string>(), ANGRY: new Set<string>(),
}, },
member: community.getMember(signerPublicKey)!,
// todo?: asign as .community, .community.description, .communityDescription, or .community.displayName
communityDisplayName: community.description.identity!.displayName,
chatDisplayName: chat.description.identity!.displayName,
} }
return message return message

View File

@ -2,6 +2,7 @@ export type { Account } from './client/account'
export type { export type {
ActivityCenter, ActivityCenter,
ActivityCenterLatest, ActivityCenterLatest,
Notification,
} from './client/activityCenter' } from './client/activityCenter'
export type { ChatMessage as Message } from './client/chat' export type { ChatMessage as Message } from './client/chat'
export type { Client, ClientOptions } from './client/client' export type { Client, ClientOptions } from './client/client'

View File

@ -42,11 +42,13 @@
"@radix-ui/react-label": "^0.1.5", "@radix-ui/react-label": "^0.1.5",
"@radix-ui/react-popover": "^0.1.6", "@radix-ui/react-popover": "^0.1.6",
"@radix-ui/react-separator": "^0.1.4", "@radix-ui/react-separator": "^0.1.4",
"@radix-ui/react-tabs": "^1.0.0",
"@radix-ui/react-toggle-group": "^0.1.5", "@radix-ui/react-toggle-group": "^0.1.5",
"@radix-ui/react-tooltip": "^0.1.7", "@radix-ui/react-tooltip": "^1.0.0",
"@radix-ui/react-visually-hidden": "^0.1.4", "@radix-ui/react-visually-hidden": "^0.1.4",
"@status-im/js": "0.1.0-alpha.2", "@status-im/js": "0.1.0-alpha.2",
"@stitches/react": "^1.2.8", "@stitches/react": "^1.2.8",
"date-fns": "^2.29.3",
"emoji-mart": "^3.0.1", "emoji-mart": "^3.0.1",
"html-entities": "^2.3.2", "html-entities": "^2.3.2",
"qrcode.react": "^3.0.1", "qrcode.react": "^3.0.1",

View File

@ -11,7 +11,7 @@ export const EditGroupChatDialog = () => {
placeholder="A catchy name" placeholder="A catchy name"
maxLength={30} maxLength={30}
/> />
<Avatar size="120" /> <Avatar size="120" initialsLength={1} />
</Dialog.Body> </Dialog.Body>
<Dialog.Actions> <Dialog.Actions>
<Dialog.Action>Save changes</Dialog.Action> <Dialog.Action>Save changes</Dialog.Action>

View File

@ -4,7 +4,7 @@ import { NavLink } from 'react-router-dom'
import { useActivityCenter } from '../../../../protocol' import { useActivityCenter } from '../../../../protocol'
import { styled } from '../../../../styles/config' import { styled } from '../../../../styles/config'
import { Avatar } from '../../../../system' import { Avatar, Badge, Flex } from '../../../../system'
import type { Chat } from '../../../../protocol/use-sorted-chats' import type { Chat } from '../../../../protocol/use-sorted-chats'
import type { Ref } from 'react' import type { Ref } from 'react'
@ -30,7 +30,10 @@ const ChatItem = (props: Props, ref: Ref<HTMLAnchorElement>) => {
to={`/${chat.id}`} to={`/${chat.id}`}
state={muted ? 'muted' : unread ? 'unread' : undefined} state={muted ? 'muted' : unread ? 'unread' : undefined}
> >
<Avatar size={24} name={displayName} color={color} />#{displayName} <Flex gap={2} align="center">
<Avatar size={24} name={displayName} color={color} initialsLength={1} />
#{displayName}
</Flex>
{count > 0 && <Badge>{count}</Badge>} {count > 0 && <Badge>{count}</Badge>}
</Link> </Link>
) )
@ -46,11 +49,11 @@ const Link = styled(NavLink, {
fontFamily: '$sans', fontFamily: '$sans',
fontWeight: '$500', fontWeight: '$500',
fontSize: 15, fontSize: 15,
display: 'inline-flex', display: 'flex',
justifyContent: 'space-between',
color: '$accent-4', color: '$accent-4',
alignItems: 'center', alignItems: 'center',
width: '100%', width: '100%',
gap: '$2',
borderRadius: 8, borderRadius: 8,
padding: 8, padding: 8,
@ -75,15 +78,3 @@ const Link = styled(NavLink, {
}, },
}, },
}) })
const Badge = styled('div', {
textAlign: 'center',
position: 'absolute',
right: 8,
width: 22,
height: 22,
background: '$primary-1',
borderRadius: '$full',
fontSize: 12,
color: '$accent-11',
})

View File

@ -9,7 +9,7 @@ export const Chats = () => {
const { categories, chats } = useSortedChats() const { categories, chats } = useSortedChats()
return ( return (
<Box css={{ padding: '18px 0', overflow: 'auto' }}> <Box css={{ padding: '18px 0', overflowY: 'scroll' }}>
{chats.map(chat => ( {chats.map(chat => (
<ChatItem key={chat.id} chat={chat} /> <ChatItem key={chat.id} chat={chat} />
))} ))}

View File

@ -14,7 +14,7 @@ export const CommunityInfo = () => {
return ( return (
<DialogTrigger> <DialogTrigger>
<Button> <Button>
<Avatar size={36} name={displayName} color={color} /> <Avatar size={36} name={displayName} color={color} initialsLength={1} />
<div> <div>
<Text>{displayName}</Text> <Text>{displayName}</Text>
<Text color="gray" size={12}> <Text color="gray" size={12}>

View File

@ -38,7 +38,6 @@ const Wrapper = styled('div', {
flexDirection: 'column', flexDirection: 'column',
padding: '10px 16px', padding: '10px 16px',
backgroundColor: '$gray-4', backgroundColor: '$gray-4',
overflowY: 'scroll',
'@large': { '@large': {
display: 'flex', display: 'flex',

View File

@ -22,7 +22,7 @@ export function MemberSidebar() {
<UserItem account={account} /> <UserItem account={account} />
</MemberGroup> </MemberGroup>
)} )}
<MemberGroup label="Online"> <MemberGroup label="All">
{members.map(member => ( {members.map(member => (
<MemberItem <MemberItem
key={member.publicKey} key={member.publicKey}

View File

@ -17,6 +17,7 @@ export const WelcomeDialog = () => {
size="64" size="64"
src={identity?.displayName} src={identity?.displayName}
color={identity?.color} color={identity?.color}
initialsLength={1}
/> />
</Flex> </Flex>
<Text>{identity?.description}</Text> <Text>{identity?.description}</Text>

View File

@ -28,7 +28,7 @@ const reducer: Reducer<State, Action> = (state, action) => {
const initialState: State = { const initialState: State = {
state: 'loading', state: 'loading',
showMembers: false, showMembers: true,
} }
interface Props { interface Props {

View File

@ -90,7 +90,6 @@ function parseJSON<T>(value: string | null): T | undefined {
try { try {
return value === 'undefined' ? undefined : JSON.parse(value ?? '') return value === 'undefined' ? undefined : JSON.parse(value ?? '')
} catch { } catch {
console.log('Parse error', { value })
return undefined return undefined
} }
} }

View File

@ -0,0 +1,33 @@
import React from 'react'
export const DoubleTickIcon = (props: React.SVGProps<SVGSVGElement>) => {
return (
<svg
width="24"
height="16"
viewBox="0 0 24 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M22.864 0.421992C23.2086 0.651757 23.3017 1.11741 23.072 1.46206L13.1979 15.4222C13.0733 15.6091 12.8714 15.7303 12.6478 15.7525C12.4242 15.7746 12.2025 15.6953 12.0436 15.5365L6.04357 9.53646C5.75067 9.24357 5.75067 8.76869 6.04357 8.4758C6.33646 8.18291 6.81133 8.18291 7.10423 8.4758L12.4573 13.8289L21.8239 0.630005C22.0537 0.285358 22.5193 0.192228 22.864 0.421992Z"
fill="currentColor"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M17.8555 0.383649C18.1925 0.624401 18.2706 1.09281 18.0299 1.42988L12.0524 9.7986C11.8116 10.1357 11.3432 10.2137 11.0061 9.97298C10.6691 9.73223 10.591 9.26382 10.8318 8.92676L16.8092 0.558034C17.05 0.220971 17.5184 0.142897 17.8555 0.383649Z"
fill="currentColor"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M1.01928 8.47976C1.31098 8.18568 1.78585 8.18375 2.07993 8.47546L8.09754 14.4444C8.39162 14.7361 8.39355 15.211 8.10185 15.505C7.81015 15.7991 7.33528 15.801 7.0412 15.5093L1.02359 9.54041C0.729506 9.24871 0.727577 8.77384 1.01928 8.47976Z"
fill="currentColor"
/>
</svg>
)
}

View File

@ -0,0 +1,21 @@
import React from 'react'
export const TinyChevronRightIcon = (props: React.SVGProps<SVGSVGElement>) => {
return (
<svg
width={6}
height={9}
viewBox="0 0 6 9"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M.97.97a.75.75 0 000 1.06l2.116 2.116a.5.5 0 010 .708L.97 6.97a.75.75 0 001.06 1.06l3-3a.75.75 0 000-1.06l-3-3a.75.75 0 00-1.06 0z"
fill="currentColor"
/>
</svg>
)
}

View File

@ -0,0 +1,22 @@
// todo: add to /tiny folder
import React from 'react'
export const TinyCommunityIcon = (props: React.SVGProps<SVGSVGElement>) => {
return (
<svg
width={12}
height={12}
viewBox="0 0 12 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M8 12a2 2 0 002-2 2 2 0 002-2V2a2 2 0 00-2-2C4.477 0 0 4.477 0 10a2 2 0 002 2h6zm-5.5-2a.477.477 0 01-.485-.5A8.001 8.001 0 019.5 2.015c.276-.017.5.209.5.485v1a.534.534 0 01-.5.52A6.001 6.001 0 004.02 9.5a.534.534 0 01-.52.5h-1zM10 6.5c0-.276-.225-.503-.499-.47a4.002 4.002 0 00-3.47 3.471c-.034.274.193.499.469.499h1c.276 0 .494-.227.562-.495a2.003 2.003 0 011.443-1.443c.268-.068.495-.286.495-.562v-1z"
fill="currentColor"
/>
</svg>
)
}

View File

@ -0,0 +1,19 @@
import React from 'react'
export const TinyReplyIcon = (props: React.SVGProps<SVGSVGElement>) => {
return (
<svg
width={10}
height={11}
viewBox="0 0 10 11"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M5 2.59V1.123c0-.62.373-.812.824-.422l4.004 3.445a.449.449 0 01-.001.704L5.822 8.297C5.373 8.685 5 8.495 5 7.874V6.5c-2.502 0-3.817 1.887-4.476 3.532-.103.258-.253.258-.324-.01A5.912 5.912 0 010 8.498a5.996 5.996 0 015-5.91z"
fill="currentColor"
/>
</svg>
)
}

View File

@ -22,6 +22,9 @@ export const useActivityCenter = () => {
}, [client.activityCenter]) }, [client.activityCenter])
return { return {
activityCenter: client.activityCenter,
notifications: latest.notifications,
unreadChats: latest.unreadChats, unreadChats: latest.unreadChats,
totalCount: latest.totalCount,
} }
} }

View File

@ -20,6 +20,7 @@ export const ChatInfo = (props: Props) => {
size={36} size={36}
name={chat.identity?.displayName} name={chat.identity?.displayName}
color={chat.identity?.color} color={chat.identity?.color}
initialsLength={1}
/> />
<div> <div>
<Text>#{chat.identity?.displayName}</Text> <Text>#{chat.identity?.displayName}</Text>

View File

@ -7,7 +7,7 @@ import { useChatContext } from '../../../../contexts/chat-context'
// import { BellIcon } from '../../../../icons/bell-icon' // import { BellIcon } from '../../../../icons/bell-icon'
// import { PinIcon } from '../../../../icons/pin-icon' // import { PinIcon } from '../../../../icons/pin-icon'
import { useProtocol } from '../../../../protocol' import { useProtocol } from '../../../../protocol'
import { styled } from '../../../../styles/config' import { keyframes, styled } from '../../../../styles/config'
import { import {
Avatar, Avatar,
Box, Box,
@ -32,6 +32,7 @@ import type { Message, Reaction } from '../../../../protocol'
interface Props { interface Props {
message: Message message: Message
prevMessage?: Message prevMessage?: Message
highlight?: boolean
} }
// const MessageLink = forwardRef(function MessageLink( // const MessageLink = forwardRef(function MessageLink(
@ -60,7 +61,7 @@ export const ChatMessage = (props: Props) => {
const { params } = useMatch(':id')! const { params } = useMatch(':id')!
const chatId = params.id! const chatId = params.id!
const { message } = props const { message, highlight } = props
const mention = false const mention = false
const pinned = false const pinned = false
@ -172,7 +173,12 @@ export const ChatMessage = (props: Props) => {
return ( return (
<> <>
{/* <ContextMenuTrigger> */} {/* <ContextMenuTrigger> */}
<Wrapper mention={mention} pinned={pinned} data-active={reacting}> <Wrapper
mention={mention}
pinned={pinned}
data-active={reacting}
highlight={highlight}
>
{responseTo && <MessageReply messageId={responseTo} />} {responseTo && <MessageReply messageId={responseTo} />}
<Flex gap={2}> <Flex gap={2}>
<Box> <Box>
@ -262,6 +268,15 @@ export const ChatMessage = (props: Props) => {
) )
} }
const backgroundAnimation = keyframes({
from: {
backgroundColor: '$navigate-2',
},
to: {
backgroundColor: 'revert',
},
})
// TODO: Use compound variants https://stitches.dev/docs/variants#compound-variants // TODO: Use compound variants https://stitches.dev/docs/variants#compound-variants
const Wrapper = styled('div', { const Wrapper = styled('div', {
position: 'relative', position: 'relative',
@ -273,7 +288,7 @@ const Wrapper = styled('div', {
transitionDuration: '100ms', transitionDuration: '100ms',
'&:hover, &[data-open="true"], &[data-active="true"]': { '&:hover, &[data-open="true"], &[data-active="true"]': {
background: '$gray-4', background: '$gray-2',
}, },
a: { a: {
@ -317,6 +332,13 @@ const Wrapper = styled('div', {
}, },
}, },
}, },
highlight: {
true: {
'@motion': {
animation: `${backgroundAnimation} 3s ease-out 0s`,
},
},
},
}, },
compoundVariants: [], compoundVariants: [],

View File

@ -0,0 +1,184 @@
import React, { Fragment, useState } from 'react'
import format from 'date-fns/format'
import isSameDay from 'date-fns/isSameDay'
import isSameYear from 'date-fns/isSameYear'
import { BellIcon } from '../../../../../../icons/bell-icon'
import { DoubleTickIcon } from '../../../../../../icons/double-tick-icon'
import { useActivityCenter } from '../../../../../../protocol'
import {
Activity,
Badge,
IconButton,
Popover,
PopoverTrigger,
Tabs,
Text,
Tooltip,
} from '../../../../../../system'
import type { Notification } from '@status-im/js'
export const ActivityCenterPopover = () => {
const [open, setOpen] = useState(false)
const { activityCenter, notifications, totalCount } = useActivityCenter()
const initialValue: {
all: Notification[]
mentions: Notification[]
replies: Notification[]
} = {
all: notifications,
mentions: [],
replies: [],
}
const { all, mentions, replies } = notifications.reduce((acc, obj) => {
if (obj.type === 'message') {
if (obj.isMention) {
acc.mentions.push(obj)
}
if (obj.isReply) {
acc.replies.push(obj)
}
}
return acc
}, initialValue)
const createContent = (
notifications: Notification[]
): JSX.Element | JSX.Element[] => {
const mappedNotifications = notifications.map(
(currentNotification, notificationIndex, iteratedNotifications) => {
const previousNotification =
iteratedNotifications[notificationIndex - 1]
let showNewDateSeparator: boolean
if (!previousNotification) {
showNewDateSeparator = true
} else {
showNewDateSeparator = !isSameDay(
new Date(Number(currentNotification.value.timestamp)),
new Date(Number(previousNotification.value.timestamp))
)
}
let date: string | undefined
if (showNewDateSeparator) {
const _date = new Date(Number(currentNotification.value.timestamp))
const today = new Date()
const yesterday = new Date().setDate(today.getDate() - 1)
if (isSameDay(_date, today)) {
date = 'Today'
} else if (isSameDay(_date, yesterday)) {
date = 'Yesterday'
} else if (isSameYear(_date, today)) {
date = format(_date, 'iii, d MMMM')
} else {
date = format(_date, 'iii, d MMMM yyyy')
}
}
return (
<Fragment key={currentNotification.value.messageId}>
{/* todo: seperate separator component */}
{showNewDateSeparator && (
<Text
color="gray"
weight={400}
css={{ height: '34px', padding: '8px 16px 4px 16px' }}
>
{date}
</Text>
)}
<Activity onNavigateChange={() => setOpen(false)}>
{currentNotification}
</Activity>
</Fragment>
)
}
)
if (!mappedNotifications.length) {
return (
<Text
size="15"
color="gray"
weight="400"
align="center"
css={{
margin: 'auto',
position: 'relative',
top: '50%',
transform: 'translateY(-50%)',
}}
>
Notifications will appear here
</Text>
)
}
return mappedNotifications
}
return (
<PopoverTrigger open={open} onOpenChange={setOpen}>
<Tooltip label="Activity">
<IconButton
label="Show Activity Center"
onClick={() => setOpen(!open)}
css={{ position: 'relative' }}
>
<>
<BellIcon />
{totalCount > 0 && (
<Badge
variant="border"
css={{
position: 'absolute',
left: 16,
top: -2,
}}
>
{totalCount}
</Badge>
)}
</>
</IconButton>
</Tooltip>
<Popover side="bottom" css={{ width: 600, height: 770 }}>
<Tabs
tabs={[
{ title: 'All', value: 'all', content: createContent(all) },
{
title: 'Mentions',
value: 'mentions',
content: createContent(mentions),
},
{
title: 'Replies',
value: 'replies',
content: createContent(replies),
},
]}
actions={[
{
icon: <DoubleTickIcon />,
// todo: call it "Mark as Read" since the action is relative to active tab
label: 'Mark All As Read',
method: (activeTab: string) =>
activityCenter.removeNotifications(
activeTab as 'all' | 'mentions' | 'replies'
),
},
]}
/>
</Popover>
</PopoverTrigger>
)
}

View File

@ -4,13 +4,13 @@ import { useMatch } from 'react-router-dom'
// import { ChatMenu } from '../../../../components/chat-menu' // import { ChatMenu } from '../../../../components/chat-menu'
import { useAppState } from '../../../../contexts/app-context' import { useAppState } from '../../../../contexts/app-context'
// import { BellIcon } from '../../../../icons/bell-icon'
// import { DotsIcon } from '../../../../icons/dots-icon' // import { DotsIcon } from '../../../../icons/dots-icon'
import { GroupIcon } from '../../../../icons/group-icon' import { GroupIcon } from '../../../../icons/group-icon'
import { useChat } from '../../../../protocol' import { useChat } from '../../../../protocol'
import { styled } from '../../../../styles/config' import { styled } from '../../../../styles/config'
import { Flex, IconButton } from '../../../../system' import { Flex, IconButton, Separator } from '../../../../system'
import { ChatInfo } from '../chat-info' import { ChatInfo } from '../chat-info'
import { ActivityCenterPopover } from './components/activity-center-popover'
interface Props { interface Props {
enableMembers: boolean enableMembers: boolean
@ -20,6 +20,7 @@ export const Navbar = (props: Props) => {
const { enableMembers } = props const { enableMembers } = props
const { state, dispatch } = useAppState() const { state, dispatch } = useAppState()
const { params } = useMatch(':id')! // eslint-disable-line @typescript-eslint/no-non-null-assertion const { params } = useMatch(':id')! // eslint-disable-line @typescript-eslint/no-non-null-assertion
const chat = useChat(params.id!) const chat = useChat(params.id!)
@ -46,11 +47,9 @@ export const Navbar = (props: Props) => {
<ChatMenu type="dropdown" /> <ChatMenu type="dropdown" />
</DropdownMenuTrigger> */} </DropdownMenuTrigger> */}
{/* <Separator orientation="vertical" css={{ height: 24 }} /> */} <Separator orientation="vertical" css={{ height: 24 }} />
{/* <IconButton label="Show Activity Center"> <ActivityCenterPopover />
<BellIcon />
</IconButton> */}
</Flex> </Flex>
</NavbarWrapper> </NavbarWrapper>
) )
@ -60,5 +59,5 @@ const NavbarWrapper = styled('div', {
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
padding: '10px 16px', padding: '10px 20px',
}) })

View File

@ -1,6 +1,6 @@
import React, { useEffect, useRef } from 'react' import React, { useEffect, useRef } from 'react'
import { useMatch } from 'react-router-dom' import { useLocation, useMatch } from 'react-router-dom'
import { MemberSidebar } from '../../components/member-sidebar' import { MemberSidebar } from '../../components/member-sidebar'
import { useAppState } from '../../contexts/app-context' import { useAppState } from '../../contexts/app-context'
@ -23,7 +23,12 @@ const ChatStart = (props: ChatStartProps) => {
return ( return (
<Flex direction="column" gap="3" align="center" css={{ marginBottom: 50 }}> <Flex direction="column" gap="3" align="center" css={{ marginBottom: 50 }}>
<Avatar size={120} name={identity?.displayName} color={identity?.color} /> <Avatar
size={120}
name={identity?.displayName}
color={identity?.color}
initialsLength={1}
/>
<Heading>{identity?.displayName}</Heading> <Heading>{identity?.displayName}</Heading>
<Text> <Text>
Welcome to the beginning of the #{identity?.displayName} channel! Welcome to the beginning of the #{identity?.displayName} channel!
@ -43,11 +48,28 @@ const Body = () => {
const chat = client.community.getChat(chatId)! const chat = client.community.getChat(chatId)!
const messages = useMessages(chatId) const messages = useMessages(chatId)
const contentRef = useRef<HTMLDivElement>(null) const location = useLocation()
const selectedMessageId = (
location.state as { selectedMessageId: string } | undefined
)?.selectedMessageId
const contentRef = useRef<HTMLDivElement | null>(null)
// todo: more scrolling conditions
useEffect(() => { useEffect(() => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
if (selectedMessageId) {
document.getElementById(selectedMessageId)?.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'start',
})
// todo?: history.state clean-up
return
}
contentRef.current!.scrollTop = contentRef.current!.scrollHeight ?? 0 contentRef.current!.scrollTop = contentRef.current!.scrollHeight ?? 0
}, [chatId, messages.data.length]) }, [chatId, messages.data.length, selectedMessageId])
const handleMessageSubmit = (message: string) => { const handleMessageSubmit = (message: string) => {
chat.sendTextMessage(message, state.reply?.message.messageId) chat.sendTextMessage(message, state.reply?.message.messageId)
@ -57,9 +79,15 @@ const Body = () => {
<> <>
<ContentWrapper ref={contentRef}> <ContentWrapper ref={contentRef}>
<ChatStart chatId={chatId} /> <ChatStart chatId={chatId} />
{messages.data.map(message => ( {messages.data.map(message => {
<ChatMessage key={message.messageId} message={message} /> return (
))} <ChatMessage
key={message.messageId}
message={message}
highlight={message.messageId === selectedMessageId}
/>
)
})}
</ContentWrapper> </ContentWrapper>
{account && <ChatInput onSubmit={handleMessageSubmit} />} {account && <ChatInput onSubmit={handleMessageSubmit} />}
</> </>
@ -87,14 +115,11 @@ export const Chat = () => {
const Wrapper = styled('div', { const Wrapper = styled('div', {
flex: 1, flex: 1,
position: 'relative',
width: '100%',
height: '100%',
display: 'flex', display: 'flex',
alignItems: 'stretch', alignItems: 'stretch',
// https://medium.com/the-crazy-coder/the-mystery-of-css-flex-layout-items-shrinking-8748145e96d9
minWidth: 0,
background: '$background', background: '$background',
maxWidth: '100%',
minWidth: 1,
}) })
const ContentWrapper = styled('div', { const ContentWrapper = styled('div', {
@ -103,7 +128,6 @@ const ContentWrapper = styled('div', {
overflowX: 'hidden', overflowX: 'hidden',
WebkitOverflowScrolling: 'touch', WebkitOverflowScrolling: 'touch',
overscrollBehavior: 'contain', overscrollBehavior: 'contain',
minWidth: 1,
// scrollSnapType: 'y proximity', // scrollSnapType: 'y proximity',
@ -117,5 +141,5 @@ const Main = styled('div', {
flex: 1, flex: 1,
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
minWidth: 1, minWidth: 0,
}) })

View File

@ -22,6 +22,20 @@ interface Props extends Config {
meta?: string meta?: string
} }
// todo: use a better way to handle this
const RootGate = (props: { children: JSX.Element }) => {
const { client } = useProtocol()
// todo!: use sorted chats
const chat = client.community._chats[0]
if (!chat) {
return props.children
}
return <Navigate to={`/${chat.uuid}`} replace />
}
// TODO: use a better way to handle this // TODO: use a better way to handle this
const Gate = (props: { children: JSX.Element }) => { const Gate = (props: { children: JSX.Element }) => {
const { client } = useProtocol() const { client } = useProtocol()
@ -60,8 +74,20 @@ export const Community = (props: Props) => {
<MainSidebar /> <MainSidebar />
<Routes> <Routes>
<Route <Route
path="/:id" path="/"
element={ element={
<RootGate>
{/* todo?: empty state/page */}
{/* todo?: navbar at least; think community w/o chats yet, but already has members to inspect */}
<></>
</RootGate>
}
/>
{/* todo?: nest under `/` route */}
<Route
path=":id"
element={
// todo?: merge with `RootGate`
<Gate> <Gate>
<Chat /> <Chat />
</Gate> </Gate>

View File

@ -57,6 +57,8 @@ export const {
'pin-2': 'rgba(255, 159, 15, 0.2)', 'pin-2': 'rgba(255, 159, 15, 0.2)',
'pin-3': 'rgba(255, 159, 15, 0.1)', 'pin-3': 'rgba(255, 159, 15, 0.1)',
'navigate-2': 'rgba(255, 159, 15, 0.2)',
'accent-1': 'rgba(0, 0, 0, 1)', 'accent-1': 'rgba(0, 0, 0, 1)',
'accent-2': 'rgba(0, 0, 0, 0.9)', 'accent-2': 'rgba(0, 0, 0, 0.9)',
'accent-3': 'rgba(0, 0, 0, 0.8)', 'accent-3': 'rgba(0, 0, 0, 0.8)',

View File

@ -0,0 +1,127 @@
import React from 'react'
import { useNavigate } from 'react-router-dom'
import { styled } from '../../styles/config'
import { Avatar } from '../avatar'
import { Box } from '../box'
import { EthAddress } from '../eth-address'
import { Flex } from '../flex'
import { Tag } from '../tag'
import { Text } from '../text'
import type { Notification } from '@status-im/js'
const Base = styled('div', {
display: 'flex',
flexShrink: 0,
minHeight: '60px',
maxHeight: '126px',
padding: '8px 16px',
'&:hover': {
background: '$primary-3',
},
})
interface Props {
children: Notification
onNavigateChange: () => void
}
const Activity = (props: Props) => {
const value = props.children.value
const isReply = props.children.isReply
const navigate = useNavigate()
return (
<Base
onClick={() => {
props.onNavigateChange()
navigate(`/${value.chatUuid}`, {
// todo?: rename to `jumpedTo` or `navigateTo`
state: { selectedMessageId: value.messageId },
})
}}
>
<Flex
gap={2}
css={{
width: '100%',
}}
>
<Box>
<Avatar
size={40}
name={value.member.username}
colorHash={value.member.colorHash}
/>
</Box>
<Flex
direction="column"
css={{
width: '100%',
}}
>
<div>
<Flex gap="1" align="center">
<Text color="primary" weight="500" size="15">
{/* todo?: ens name */}
{/* todo?: nickname */}
{value.member.username}
</Text>
<EthAddress size={10} color="gray">
{value.member.chatKey}
</EthAddress>
<Text size="10" color="gray">
</Text>
<Text size="10" color="gray">
{new Date(Number(value.timestamp)).toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit',
})}
</Text>
</Flex>
</div>
{/* todo?: same comoponnent as for chat messages; think mention resolution */}
<Text
css={{
wordBreak: 'break-word',
overflow: 'hidden',
textOverflow: 'ellipsis',
display: '-webkit-box',
boxOrient: 'vertical',
WebkitLineClamp: 2,
WebkitBoxOrient: 'vertical',
}}
>
{value.text}
</Text>
<Flex
gap={1}
css={{
padding: '6px 0px 0px',
}}
>
<Tag
type="community"
communityDisplayName={value.communityDisplayName}
chatDisplayName={value.chatDisplayName}
chatUuid={value.chatUuid}
onNavigateChange={props.onNavigateChange}
/>
{isReply && (
<Tag
type="reply"
text={value.responseToMessage?.text ?? 'Message not available.'}
/>
)}
</Flex>
</Flex>
</Flex>
</Base>
)
}
export { Activity }

View File

@ -0,0 +1 @@
export { Activity } from './activity'

View File

@ -13,10 +13,19 @@ interface Props {
src?: string src?: string
color?: string color?: string
colorHash?: number[][] colorHash?: number[][]
initialsLength?: 1 | 2
} }
const Avatar = (props: Props) => { const Avatar = (props: Props) => {
const { size, name, src, color, indicator, colorHash } = props const {
size,
name,
src,
color,
indicator,
colorHash,
initialsLength = 2,
} = props
const identiconRing = useMemo(() => { const identiconRing = useMemo(() => {
if (colorHash) { if (colorHash) {
@ -25,7 +34,7 @@ const Avatar = (props: Props) => {
} }
}, [colorHash]) }, [colorHash])
const initials = name ? name.slice(0, 1) : '' const initials = name ? name.slice(0, initialsLength) : ''
return ( return (
<Base <Base

View File

@ -11,6 +11,11 @@ export const Base = styled('div', {
variants: { variants: {
size: { size: {
16: {
width: 16,
height: 16,
padding: 1,
},
20: { 20: {
width: 20, width: 20,
height: 20, height: 20,
@ -31,6 +36,11 @@ export const Base = styled('div', {
height: 36, height: 36,
padding: 2, padding: 2,
}, },
40: {
width: 40,
height: 40,
padding: 2,
},
44: { 44: {
width: 44, width: 44,
height: 44, height: 44,
@ -71,6 +81,7 @@ export const Indicator = styled('span', {
variants: { variants: {
size: { size: {
16: {},
20: {}, 20: {},
24: {}, 24: {},
32: { 32: {
@ -78,6 +89,7 @@ export const Indicator = styled('span', {
height: 12, height: 12,
}, },
36: {}, 36: {},
40: {},
44: {}, 44: {},
64: {}, 64: {},
80: {}, 80: {},
@ -105,10 +117,12 @@ export const Initials = styled('div', {
verticalAlign: 'baseline', verticalAlign: 'baseline',
variants: { variants: {
size: { size: {
16: { fontSize: 'calc(16 * 0.5px)', lineHeight: '16px' },
20: { fontSize: 'calc(20 * 0.5px)', lineHeight: '20px' }, 20: { fontSize: 'calc(20 * 0.5px)', lineHeight: '20px' },
24: { fontSize: 'calc(24 * 0.5px)', lineHeight: '24px' }, 24: { fontSize: 'calc(24 * 0.5px)', lineHeight: '24px' },
32: { fontSize: 'calc(32 * 0.5px)', lineHeight: '32px' }, 32: { fontSize: 'calc(32 * 0.5px)', lineHeight: '32px' },
36: { fontSize: 'calc(36 * 0.5px)', lineHeight: '36px' }, 36: { fontSize: 'calc(36 * 0.5px)', lineHeight: '36px' },
40: { fontSize: 'calc(40 * 0.5px)', lineHeight: '40px' },
44: { fontSize: 'calc(44 * 0.5px)', lineHeight: '44px' }, 44: { fontSize: 'calc(44 * 0.5px)', lineHeight: '44px' },
64: { fontSize: 'calc(64 * 0.5px)', lineHeight: '64px' }, 64: { fontSize: 'calc(64 * 0.5px)', lineHeight: '64px' },
80: { fontSize: 'calc(80 * 0.5px)', lineHeight: '80px' }, 80: { fontSize: 'calc(80 * 0.5px)', lineHeight: '80px' },

View File

@ -0,0 +1,28 @@
import React from 'react'
import { Text } from '../text'
import { Base } from './styles'
import type { Variants } from './styles'
import type Stitches from '@stitches/react'
interface Props {
children: number
variant?: Variants['variant']
css?: Stitches.CSS
}
const Badge = (props: Props) => {
const { children, variant, css } = props
return (
<Base variant={variant} css={css}>
<Text size="12" weight="500" align="center" css={{ color: '$accent-11' }}>
{children < 100 ? children : '99+'}
</Text>
</Base>
)
}
export { Badge }
export type { Props as BadgeProps }

View File

@ -0,0 +1,2 @@
export type { BadgeProps } from './badge'
export { Badge } from './badge'

View File

@ -0,0 +1,32 @@
import { styled } from '../../styles/config'
import type { VariantProps } from '../../styles/config'
export type Variants = VariantProps<typeof Base>
export const Base = styled('div', {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
minWidth: 22,
height: 22,
padding: '3px 7px',
background: '$primary-1',
/**
* @see https://copyprogramming.com/howto/css-set-border-radius-relative-to-height
* @see https://stackoverflow.com/a/66530702/6924883
*/
borderRadius: 9999,
variants: {
variant: {
border: {
minWidth: 18,
height: 18,
padding: '1px 5px',
// todo?: use border
outline: '2px solid $accent-11',
},
},
},
})

View File

@ -11,6 +11,7 @@ interface Props {
children: string children: string
disabled?: boolean disabled?: boolean
loading?: boolean loading?: boolean
active?: boolean
type?: ButtonProps['type'] type?: ButtonProps['type']
onClick?: ButtonProps['onClick'] onClick?: ButtonProps['onClick']
variant?: Variants['variant'] variant?: Variants['variant']

View File

@ -37,6 +37,19 @@ export const Base = styled('button', {
background: '$primary-2', background: '$primary-2',
}, },
}, },
secondary: {
background: '$transparent',
color: '$primary-1',
'&:hover': {
background: '$primary-3',
},
'&[data-state="active"]': {
background: '$primary-2',
'&:hover': {
backgroundColor: '$primary-2', // override default hover behavior
},
},
},
danger: { danger: {
background: '$danger-3', background: '$danger-3',
color: '$danger-1', color: '$danger-1',
@ -54,8 +67,9 @@ export const Base = styled('button', {
}, },
size: { size: {
small: { small: {
height: '38px',
fontSize: '13px', fontSize: '13px',
padding: '10px 12px', padding: '0px 12px',
}, },
}, },
loading: { loading: {

View File

@ -6,6 +6,7 @@ import { Link } from 'react-router-dom'
import { Base } from './styles' import { Base } from './styles'
import type { Variants } from './styles' import type { Variants } from './styles'
import type Stitches from '@stitches/react'
import type { Ref } from 'react' import type { Ref } from 'react'
import type { LinkProps } from 'react-router-dom' import type { LinkProps } from 'react-router-dom'
@ -20,6 +21,7 @@ interface Props {
color?: Variants['color'] color?: Variants['color']
active?: boolean active?: boolean
to?: LinkProps['to'] to?: LinkProps['to']
css?: Stitches.CSS
} }
const IconButton = (props: Props, ref: Ref<HTMLButtonElement>) => { const IconButton = (props: Props, ref: Ref<HTMLButtonElement>) => {
@ -32,6 +34,7 @@ const IconButton = (props: Props, ref: Ref<HTMLButtonElement>) => {
color, color,
active, active,
to, to,
css,
...buttonProps ...buttonProps
} = props } = props
@ -44,6 +47,7 @@ const IconButton = (props: Props, ref: Ref<HTMLButtonElement>) => {
intent={intent} intent={intent}
color={color} color={color}
active={active} active={active}
css={css}
> >
{children} {children}
</Base> </Base>
@ -60,6 +64,7 @@ const IconButton = (props: Props, ref: Ref<HTMLButtonElement>) => {
intent={intent} intent={intent}
color={color} color={color}
active={active} active={active}
css={css}
> >
<AccessibleIcon label={label}>{children}</AccessibleIcon> <AccessibleIcon label={label}>{children}</AccessibleIcon>
</Base> </Base>

View File

@ -1,4 +1,6 @@
export { Activity } from './activity'
export { Avatar } from './avatar' export { Avatar } from './avatar'
export { Badge } from './badge'
export { Box } from './box' export { Box } from './box'
export { Button } from './button' export { Button } from './button'
export { ButtonGroup } from './button-group' export { ButtonGroup } from './button-group'
@ -24,6 +26,8 @@ export { IconButton } from './icon-button'
export { Image } from './image' export { Image } from './image'
export { Popover, PopoverTrigger } from './popover' export { Popover, PopoverTrigger } from './popover'
export { Separator } from './separator' export { Separator } from './separator'
export { Tabs } from './tabs'
export { Tag } from './tag'
export { Text } from './text' export { Text } from './text'
export { TextInput } from './text-input' export { TextInput } from './text-input'
export { Tooltip } from './tooltip' export { Tooltip } from './tooltip'

View File

@ -5,6 +5,7 @@ import * as Primitive from '@radix-ui/react-popover'
import { Content } from './styles' import { Content } from './styles'
import type { PopoverContentProps } from '@radix-ui/react-popover' import type { PopoverContentProps } from '@radix-ui/react-popover'
import type Stitches from '@stitches/react'
import type { Ref } from 'react' import type { Ref } from 'react'
interface TriggerProps { interface TriggerProps {
@ -32,13 +33,14 @@ const _PopoverTrigger = forwardRef(PopoverTrigger)
interface PopoverProps extends PopoverContentProps { interface PopoverProps extends PopoverContentProps {
children: React.ReactNode children: React.ReactNode
css?: Stitches.CSS
} }
const Popover = (props: PopoverProps) => { const Popover = (props: PopoverProps) => {
const { children, ...contentProps } = props const { children, css, ...contentProps } = props
return ( return (
<Content as={Primitive.Content} {...contentProps}> <Content as={Primitive.Content} css={css} {...contentProps}>
{children} {children}
</Content> </Content>
) )

View File

@ -0,0 +1 @@
export { Tabs } from './tabs'

View File

@ -0,0 +1,110 @@
import React, { useState } from 'react'
import * as TabsPrimitive from '@radix-ui/react-tabs'
import { DoubleTickIcon } from '../../icons/double-tick-icon'
import { styled } from '../../styles/config'
import { Button } from '../button'
import { Flex } from '../flex'
import { IconButton } from '../icon-button'
import { Tooltip } from '../tooltip'
const TabsRoot = styled(TabsPrimitive.Root, {
display: 'flex',
flexDirection: 'column',
height: '100%',
})
const TabsList = styled(TabsPrimitive.List, { display: 'flex', gap: '8px' })
const TabsContent = styled(TabsPrimitive.Content, {
width: '100%',
height: '100%',
})
const ContentWrapper = styled('div', {
flex: 1,
overflowY: 'scroll',
overflowX: 'hidden',
})
interface Props {
tabs: Array<{
title: string
value: string
content: JSX.Element | JSX.Element[]
}>
actions: Array<{
icon: JSX.Element
label: string
method: (activeTab: string) => void
}>
}
const Tabs = (props: Props) => {
const [activeTab, setActiveTab] = useState('all')
const initialValue: {
triggers: JSX.Element[]
contents: JSX.Element[]
} = {
triggers: [],
contents: [],
}
const { triggers, contents } = props.tabs.reduce((results, currentTab) => {
results.triggers.push(
<TabsPrimitive.Trigger
key={currentTab.value}
value={currentTab.value}
asChild
>
<Button size="small" variant="secondary">
{currentTab.title}
</Button>
</TabsPrimitive.Trigger>
)
results.contents.push(
<TabsContent key={currentTab.value} value={currentTab.value}>
{currentTab.content}
</TabsContent>
)
return results
}, initialValue)
const actions = props.actions.map(action => {
return (
<Tooltip key={action.label} label={action.label} arrowOffset={7}>
<IconButton
label={action.label}
onClick={() => {
action.method(activeTab)
}}
>
<DoubleTickIcon />
</IconButton>
</Tooltip>
)
})
return (
<TabsRoot value={activeTab} onValueChange={setActiveTab}>
<Flex
css={{
height: '64px',
padding: '13px 16px',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
{/* todo?: if all empty, disable other tabs */}
{/* todo?: if active, disable hover and clicks */}
<TabsList>{triggers}</TabsList>
<div>{actions}</div>
</Flex>
<ContentWrapper>{contents}</ContentWrapper>
</TabsRoot>
)
}
export { Tabs }

View File

@ -0,0 +1 @@
export { Tag } from './tag'

View File

@ -0,0 +1,128 @@
import React from 'react'
import { useNavigate } from 'react-router-dom'
import { TinyChevronRightIcon } from '../../icons/tiny-chevron-right-icon'
import { TinyCommunityIcon } from '../../icons/tiny-community-icon'
import { TinyReplyIcon } from '../../icons/tiny-reply-icon'
import { styled } from '../../styles/config'
import { Avatar } from '../avatar'
import { Text } from '../text'
const Base = styled('div', {
padding: '0px 6px',
border: '1px solid rgba(0, 0, 0, 0.1)',
borderRadius: '11px',
height: '22px',
display: 'flex',
alignItems: 'center',
width: 'max-content',
color: '$gray-1',
gap: '6px',
'&:hover': {
cursor: 'default',
},
})
const Segment = styled('div', {
display: 'flex',
alignItems: 'center',
gap: '4px',
})
const PathLink = styled('a', {
'&:hover': {
textDecoration: 'underline',
},
})
interface CommunityProps {
type: 'community'
communityDisplayName: string
chatDisplayName: string
chatUuid: string
onNavigateChange: () => void
}
interface ReplyProps {
type: 'reply'
text: string
}
// fixme: clicking on flex gab/space between components captures and handles click events
const Tag = (props: CommunityProps | ReplyProps) => {
const { type } = props
// todo?: extract together with `PathLink`
const navigate = useNavigate()
switch (type) {
case 'community':
return (
<Base>
<Segment
role="none"
onClick={e => {
e.preventDefault()
e.stopPropagation()
}}
>
<TinyCommunityIcon />
<Avatar
size={16}
name={props.communityDisplayName}
initialsLength={1}
/>
<Text color="current" weight="500">
{props.communityDisplayName}
</Text>
</Segment>
<Segment
role="none"
onClick={e => {
e.preventDefault()
e.stopPropagation()
}}
>
<TinyChevronRightIcon />
</Segment>
<Segment>
<PathLink
onClick={e => {
e.preventDefault()
e.stopPropagation()
props.onNavigateChange()
navigate(`/${props.chatUuid}`)
}}
>
<Text color="current" weight="500">
#{props.chatDisplayName}
</Text>
</PathLink>
</Segment>
</Base>
)
case 'reply':
return (
<Base>
<Segment
role="none"
onClick={e => {
e.preventDefault()
e.stopPropagation()
}}
>
<TinyReplyIcon />
<Text color="current" weight="500">
{props.text}
</Text>
</Segment>
</Base>
)
default:
return null
}
}
export { Tag }

View File

@ -2,6 +2,7 @@ import { styled, theme } from '../../styles/config'
import type React from 'react' import type React from 'react'
// todo?: rich variant here (e.g. mentions, format, code)
const Text = styled('div', { const Text = styled('div', {
fontFamily: theme.fonts.sans, fontFamily: theme.fonts.sans,
overflowWrap: 'break-word', overflowWrap: 'break-word',
@ -35,6 +36,9 @@ const Text = styled('div', {
gray: { gray: {
color: '$gray-1', color: '$gray-1',
}, },
current: {
color: '$current',
},
}, },
weight: { weight: {
'400': { '400': {

View File

@ -27,7 +27,7 @@ export const Content = styled(Primitive.Content, {
fontWeight: '$500', fontWeight: '$500',
fontSize: 13, fontSize: 13,
padding: 8, padding: 8,
lineHeight: 1, lineHeight: '18px',
backgroundColor: '$accent-1', backgroundColor: '$accent-1',
color: '$accent-11', color: '$accent-11',
borderRadius: 8, borderRadius: 8,

View File

@ -4,7 +4,10 @@ import * as Primitive from '@radix-ui/react-tooltip'
import { Arrow, Content } from './styles' import { Arrow, Content } from './styles'
import type { TooltipContentProps } from '@radix-ui/react-tooltip' import type {
TooltipArrowProps,
TooltipContentProps,
} from '@radix-ui/react-tooltip'
import type { Ref } from 'react' import type { Ref } from 'react'
interface Props { interface Props {
@ -12,6 +15,8 @@ interface Props {
children: React.ReactElement children: React.ReactElement
side?: TooltipContentProps['side'] side?: TooltipContentProps['side']
sideOffset?: TooltipContentProps['sideOffset'] sideOffset?: TooltipContentProps['sideOffset']
align?: TooltipContentProps['align']
arrowOffset?: TooltipArrowProps['offset']
} }
const Tooltip = (props: Props, ref: Ref<HTMLButtonElement>) => { const Tooltip = (props: Props, ref: Ref<HTMLButtonElement>) => {
@ -20,19 +25,23 @@ const Tooltip = (props: Props, ref: Ref<HTMLButtonElement>) => {
label, label,
side = 'top', side = 'top',
sideOffset = 5, sideOffset = 5,
align = 'center',
arrowOffset = 0,
...triggerProps ...triggerProps
} = props } = props
return ( return (
<Primitive.Provider>
<Primitive.Root delayDuration={500}> <Primitive.Root delayDuration={500}>
<Primitive.Trigger asChild> <Primitive.Trigger asChild>
{cloneElement(children, { ref, ...triggerProps })} {cloneElement(children, { ref, ...triggerProps })}
</Primitive.Trigger> </Primitive.Trigger>
<Content side={side} sideOffset={sideOffset}> <Content side={side} sideOffset={sideOffset} align={align}>
{label} {label}
<Arrow /> <Arrow offset={arrowOffset} />
</Content> </Content>
</Primitive.Root> </Primitive.Root>
</Primitive.Provider>
) )
} }

272
yarn.lock
View File

@ -750,6 +750,26 @@
"@ethersproject/bytes" "^5.5.0" "@ethersproject/bytes" "^5.5.0"
"@ethersproject/logger" "^5.5.0" "@ethersproject/logger" "^5.5.0"
"@floating-ui/core@^0.7.3":
version "0.7.3"
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-0.7.3.tgz#d274116678ffae87f6b60e90f88cc4083eefab86"
integrity sha512-buc8BXHmG9l82+OQXOFU3Kr2XQx9ys01U/Q9HMIrZ300iLc8HLMgh7dcCqgYzAzf4BkoQvDcXf5Y+CuEZ5JBYg==
"@floating-ui/dom@^0.5.3":
version "0.5.4"
resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-0.5.4.tgz#4eae73f78bcd4bd553ae2ade30e6f1f9c73fe3f1"
integrity sha512-419BMceRLq0RrmTSDxn8hf9R3VCJv2K9PUfugh5JyEFmdjzDo+e8U5EdR8nzKq8Yj1htzLm3b6eQEEam3/rrtg==
dependencies:
"@floating-ui/core" "^0.7.3"
"@floating-ui/react-dom@0.7.2":
version "0.7.2"
resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-0.7.2.tgz#0bf4ceccb777a140fc535c87eb5d6241c8e89864"
integrity sha512-1T0sJcpHgX/u4I1OzIEhlcrvkUN8ln39nz7fMoE/2HDHrPiMFoOGR7++GYyfUmIQHkkrTinaeQsO3XWubjSvGg==
dependencies:
"@floating-ui/dom" "^0.5.3"
use-isomorphic-layout-effect "^1.1.1"
"@hcaptcha/react-hcaptcha@^1.0.0": "@hcaptcha/react-hcaptcha@^1.0.0":
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/@hcaptcha/react-hcaptcha/-/react-hcaptcha-1.1.0.tgz#ca770c9fc1f456e3c6b057bedf01a94693b2ec96" resolved "https://registry.yarnpkg.com/@hcaptcha/react-hcaptcha/-/react-hcaptcha-1.1.0.tgz#ca770c9fc1f456e3c6b057bedf01a94693b2ec96"
@ -1451,6 +1471,13 @@
dependencies: dependencies:
"@babel/runtime" "^7.13.10" "@babel/runtime" "^7.13.10"
"@radix-ui/primitive@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.0.0.tgz#e1d8ef30b10ea10e69c76e896f608d9276352253"
integrity sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-accessible-icon@^0.1.4": "@radix-ui/react-accessible-icon@^0.1.4":
version "0.1.4" version "0.1.4"
resolved "https://registry.yarnpkg.com/@radix-ui/react-accessible-icon/-/react-accessible-icon-0.1.4.tgz#bdbf1e3226a0e9e7778b68728b175bdc532b720c" resolved "https://registry.yarnpkg.com/@radix-ui/react-accessible-icon/-/react-accessible-icon-0.1.4.tgz#bdbf1e3226a0e9e7778b68728b175bdc532b720c"
@ -1480,6 +1507,14 @@
"@babel/runtime" "^7.13.10" "@babel/runtime" "^7.13.10"
"@radix-ui/react-primitive" "0.1.4" "@radix-ui/react-primitive" "0.1.4"
"@radix-ui/react-arrow@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-1.0.0.tgz#c461f4c2cab3317e3d42a1ae62910a4cbb0192a1"
integrity sha512-1MUuv24HCdepi41+qfv125EwMuxgQ+U+h0A9K3BjCO/J8nVRREKHHpkD9clwfnjEDk9hgGzCnff4aUKCPiRepw==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-primitive" "1.0.0"
"@radix-ui/react-checkbox@^0.1.5": "@radix-ui/react-checkbox@^0.1.5":
version "0.1.5" version "0.1.5"
resolved "https://registry.yarnpkg.com/@radix-ui/react-checkbox/-/react-checkbox-0.1.5.tgz#3a6bd54ba1720c8e5c03852acf460e35dfbe9da3" resolved "https://registry.yarnpkg.com/@radix-ui/react-checkbox/-/react-checkbox-0.1.5.tgz#3a6bd54ba1720c8e5c03852acf460e35dfbe9da3"
@ -1522,6 +1557,17 @@
"@radix-ui/react-primitive" "0.1.4" "@radix-ui/react-primitive" "0.1.4"
"@radix-ui/react-slot" "0.1.2" "@radix-ui/react-slot" "0.1.2"
"@radix-ui/react-collection@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-1.0.0.tgz#0ec4c72fabd35a03b5787075ac799e3b17ca5710"
integrity sha512-8i1pf5dKjnq90Z8udnnXKzdCEV3/FYrfw0n/b6NvB6piXEn3fO1bOh7HBcpG8XrnIXzxlYu2oCcR38QpyLS/mg==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-compose-refs" "1.0.0"
"@radix-ui/react-context" "1.0.0"
"@radix-ui/react-primitive" "1.0.0"
"@radix-ui/react-slot" "1.0.0"
"@radix-ui/react-compose-refs@0.1.0", "@radix-ui/react-compose-refs@^0.1.0": "@radix-ui/react-compose-refs@0.1.0", "@radix-ui/react-compose-refs@^0.1.0":
version "0.1.0" version "0.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-0.1.0.tgz#cff6e780a0f73778b976acff2c2a5b6551caab95" resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-0.1.0.tgz#cff6e780a0f73778b976acff2c2a5b6551caab95"
@ -1529,6 +1575,13 @@
dependencies: dependencies:
"@babel/runtime" "^7.13.10" "@babel/runtime" "^7.13.10"
"@radix-ui/react-compose-refs@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz#37595b1f16ec7f228d698590e78eeed18ff218ae"
integrity sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-context-menu@^0.1.6": "@radix-ui/react-context-menu@^0.1.6":
version "0.1.6" version "0.1.6"
resolved "https://registry.yarnpkg.com/@radix-ui/react-context-menu/-/react-context-menu-0.1.6.tgz#0c75f2faffec6c8697247a4b685a432b3c4d07f0" resolved "https://registry.yarnpkg.com/@radix-ui/react-context-menu/-/react-context-menu-0.1.6.tgz#0c75f2faffec6c8697247a4b685a432b3c4d07f0"
@ -1548,6 +1601,13 @@
dependencies: dependencies:
"@babel/runtime" "^7.13.10" "@babel/runtime" "^7.13.10"
"@radix-ui/react-context@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.0.0.tgz#f38e30c5859a9fb5e9aa9a9da452ee3ed9e0aee0"
integrity sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-dialog@0.1.7", "@radix-ui/react-dialog@^0.1.7": "@radix-ui/react-dialog@0.1.7", "@radix-ui/react-dialog@^0.1.7":
version "0.1.7" version "0.1.7"
resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-0.1.7.tgz#285414cf66f5bbf42bc9935314e0381abe01e7d0" resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-0.1.7.tgz#285414cf66f5bbf42bc9935314e0381abe01e7d0"
@ -1569,6 +1629,13 @@
aria-hidden "^1.1.1" aria-hidden "^1.1.1"
react-remove-scroll "^2.4.0" react-remove-scroll "^2.4.0"
"@radix-ui/react-direction@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.0.0.tgz#a2e0b552352459ecf96342c79949dd833c1e6e45"
integrity sha512-2HV05lGUgYcA6xgLQ4BKPDmtL+QbIZYH5fCOTAOOcJ5O0QbWS3i9lKaurLzliYUDhORI2Qr3pyjhJh44lKA3rQ==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-dismissable-layer@0.1.5": "@radix-ui/react-dismissable-layer@0.1.5":
version "0.1.5" version "0.1.5"
resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-0.1.5.tgz#9379032351e79028d472733a5cc8ba4a0ea43314" resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-0.1.5.tgz#9379032351e79028d472733a5cc8ba4a0ea43314"
@ -1582,6 +1649,18 @@
"@radix-ui/react-use-callback-ref" "0.1.0" "@radix-ui/react-use-callback-ref" "0.1.0"
"@radix-ui/react-use-escape-keydown" "0.1.0" "@radix-ui/react-use-escape-keydown" "0.1.0"
"@radix-ui/react-dismissable-layer@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.0.tgz#35b7826fa262fd84370faef310e627161dffa76b"
integrity sha512-n7kDRfx+LB1zLueRDvZ1Pd0bxdJWDUZNQ/GWoxDn2prnuJKRdxsjulejX/ePkOsLi2tTm6P24mDqlMSgQpsT6g==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/primitive" "1.0.0"
"@radix-ui/react-compose-refs" "1.0.0"
"@radix-ui/react-primitive" "1.0.0"
"@radix-ui/react-use-callback-ref" "1.0.0"
"@radix-ui/react-use-escape-keydown" "1.0.0"
"@radix-ui/react-dropdown-menu@^0.1.6": "@radix-ui/react-dropdown-menu@^0.1.6":
version "0.1.6" version "0.1.6"
resolved "https://registry.yarnpkg.com/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-0.1.6.tgz#3203229788cd57e552c9f19dcc7008e2b545919c" resolved "https://registry.yarnpkg.com/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-0.1.6.tgz#3203229788cd57e552c9f19dcc7008e2b545919c"
@ -1621,6 +1700,14 @@
"@babel/runtime" "^7.13.10" "@babel/runtime" "^7.13.10"
"@radix-ui/react-use-layout-effect" "0.1.0" "@radix-ui/react-use-layout-effect" "0.1.0"
"@radix-ui/react-id@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.0.0.tgz#8d43224910741870a45a8c9d092f25887bb6d11e"
integrity sha512-Q6iAB/U7Tq3NTolBBQbHTgclPmGWE3OlktGGqrClPozSw4vkQ1DfQAOtzgRPecKsMdJINE05iaoDUG8tRzCBjw==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-use-layout-effect" "1.0.0"
"@radix-ui/react-label@0.1.5", "@radix-ui/react-label@^0.1.5": "@radix-ui/react-label@0.1.5", "@radix-ui/react-label@^0.1.5":
version "0.1.5" version "0.1.5"
resolved "https://registry.yarnpkg.com/@radix-ui/react-label/-/react-label-0.1.5.tgz#12cd965bfc983e0148121d4c99fb8e27a917c45c" resolved "https://registry.yarnpkg.com/@radix-ui/react-label/-/react-label-0.1.5.tgz#12cd965bfc983e0148121d4c99fb8e27a917c45c"
@ -1692,6 +1779,22 @@
"@radix-ui/react-use-size" "0.1.1" "@radix-ui/react-use-size" "0.1.1"
"@radix-ui/rect" "0.1.1" "@radix-ui/rect" "0.1.1"
"@radix-ui/react-popper@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.0.0.tgz#fb4f937864bf39c48f27f55beee61fa9f2bef93c"
integrity sha512-k2dDd+1Wl0XWAMs9ZvAxxYsB9sOsEhrFQV4CINd7IUZf0wfdye4OHen9siwxvZImbzhgVeKTJi68OQmPRvVdMg==
dependencies:
"@babel/runtime" "^7.13.10"
"@floating-ui/react-dom" "0.7.2"
"@radix-ui/react-arrow" "1.0.0"
"@radix-ui/react-compose-refs" "1.0.0"
"@radix-ui/react-context" "1.0.0"
"@radix-ui/react-primitive" "1.0.0"
"@radix-ui/react-use-layout-effect" "1.0.0"
"@radix-ui/react-use-rect" "1.0.0"
"@radix-ui/react-use-size" "1.0.0"
"@radix-ui/rect" "1.0.0"
"@radix-ui/react-portal@0.1.4": "@radix-ui/react-portal@0.1.4":
version "0.1.4" version "0.1.4"
resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-0.1.4.tgz#17bdce3d7f1a9a0b35cb5e935ab8bc562441a7d2" resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-0.1.4.tgz#17bdce3d7f1a9a0b35cb5e935ab8bc562441a7d2"
@ -1701,6 +1804,14 @@
"@radix-ui/react-primitive" "0.1.4" "@radix-ui/react-primitive" "0.1.4"
"@radix-ui/react-use-layout-effect" "0.1.0" "@radix-ui/react-use-layout-effect" "0.1.0"
"@radix-ui/react-portal@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.0.0.tgz#7220b66743394fabb50c55cb32381395cc4a276b"
integrity sha512-a8qyFO/Xb99d8wQdu4o7qnigNjTPG123uADNecz0eX4usnQEj7o+cG4ZX4zkqq98NYekT7UoEQIjxBNWIFuqTA==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-primitive" "1.0.0"
"@radix-ui/react-presence@0.1.2": "@radix-ui/react-presence@0.1.2":
version "0.1.2" version "0.1.2"
resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-0.1.2.tgz#9f11cce3df73cf65bc348e8b76d891f0d54c1fe3" resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-0.1.2.tgz#9f11cce3df73cf65bc348e8b76d891f0d54c1fe3"
@ -1710,6 +1821,15 @@
"@radix-ui/react-compose-refs" "0.1.0" "@radix-ui/react-compose-refs" "0.1.0"
"@radix-ui/react-use-layout-effect" "0.1.0" "@radix-ui/react-use-layout-effect" "0.1.0"
"@radix-ui/react-presence@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.0.0.tgz#814fe46df11f9a468808a6010e3f3ca7e0b2e84a"
integrity sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-compose-refs" "1.0.0"
"@radix-ui/react-use-layout-effect" "1.0.0"
"@radix-ui/react-primitive@0.1.4": "@radix-ui/react-primitive@0.1.4":
version "0.1.4" version "0.1.4"
resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-0.1.4.tgz#6c233cf08b0cb87fecd107e9efecb3f21861edc1" resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-0.1.4.tgz#6c233cf08b0cb87fecd107e9efecb3f21861edc1"
@ -1718,6 +1838,14 @@
"@babel/runtime" "^7.13.10" "@babel/runtime" "^7.13.10"
"@radix-ui/react-slot" "0.1.2" "@radix-ui/react-slot" "0.1.2"
"@radix-ui/react-primitive@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-1.0.0.tgz#376cd72b0fcd5e0e04d252ed33eb1b1f025af2b0"
integrity sha512-EyXe6mnRlHZ8b6f4ilTDrXmkLShICIuOTTj0GX4w1rp+wSxf3+TD05u1UOITC8VsJ2a9nwHvdXtOXEOl0Cw/zQ==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-slot" "1.0.0"
"@radix-ui/react-roving-focus@0.1.5": "@radix-ui/react-roving-focus@0.1.5":
version "0.1.5" version "0.1.5"
resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-0.1.5.tgz#cc48d17a36b56f253d54905b0fd60ee134cb97ee" resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-0.1.5.tgz#cc48d17a36b56f253d54905b0fd60ee134cb97ee"
@ -1733,6 +1861,22 @@
"@radix-ui/react-use-callback-ref" "0.1.0" "@radix-ui/react-use-callback-ref" "0.1.0"
"@radix-ui/react-use-controllable-state" "0.1.0" "@radix-ui/react-use-controllable-state" "0.1.0"
"@radix-ui/react-roving-focus@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.0.tgz#aadeb65d5dbcdbdd037078156ae1f57c2ff754ee"
integrity sha512-lHvO4MhvoWpeNbiJAoyDsEtbKqP2jkkdwsMVJ3kfqbkC71J/aXE6Th6gkZA1xHEqSku+t+UgoDjvE7Z3gsBpcg==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/primitive" "1.0.0"
"@radix-ui/react-collection" "1.0.0"
"@radix-ui/react-compose-refs" "1.0.0"
"@radix-ui/react-context" "1.0.0"
"@radix-ui/react-direction" "1.0.0"
"@radix-ui/react-id" "1.0.0"
"@radix-ui/react-primitive" "1.0.0"
"@radix-ui/react-use-callback-ref" "1.0.0"
"@radix-ui/react-use-controllable-state" "1.0.0"
"@radix-ui/react-separator@^0.1.4": "@radix-ui/react-separator@^0.1.4":
version "0.1.4" version "0.1.4"
resolved "https://registry.yarnpkg.com/@radix-ui/react-separator/-/react-separator-0.1.4.tgz#383ad0f82b364d9982a978d752084af3598e4090" resolved "https://registry.yarnpkg.com/@radix-ui/react-separator/-/react-separator-0.1.4.tgz#383ad0f82b364d9982a978d752084af3598e4090"
@ -1749,6 +1893,29 @@
"@babel/runtime" "^7.13.10" "@babel/runtime" "^7.13.10"
"@radix-ui/react-compose-refs" "0.1.0" "@radix-ui/react-compose-refs" "0.1.0"
"@radix-ui/react-slot@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.0.0.tgz#7fa805b99891dea1e862d8f8fbe07f4d6d0fd698"
integrity sha512-3mrKauI/tWXo1Ll+gN5dHcxDPdm/Df1ufcDLCecn+pnCIVcdWE7CujXo8QaXOWRJyZyQWWbpB8eFwHzWXlv5mQ==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-compose-refs" "1.0.0"
"@radix-ui/react-tabs@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-tabs/-/react-tabs-1.0.0.tgz#135c67f1f2bd9ada69a3f6e38dd897d459af5fe5"
integrity sha512-oKUwEDsySVC0uuSEH7SHCVt1+ijmiDFAI9p+fHCtuZdqrRDKIFs09zp5nrmu4ggP6xqSx9lj1VSblnDH+n3IBA==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/primitive" "1.0.0"
"@radix-ui/react-context" "1.0.0"
"@radix-ui/react-direction" "1.0.0"
"@radix-ui/react-id" "1.0.0"
"@radix-ui/react-presence" "1.0.0"
"@radix-ui/react-primitive" "1.0.0"
"@radix-ui/react-roving-focus" "1.0.0"
"@radix-ui/react-use-controllable-state" "1.0.0"
"@radix-ui/react-toggle-group@^0.1.5": "@radix-ui/react-toggle-group@^0.1.5":
version "0.1.5" version "0.1.5"
resolved "https://registry.yarnpkg.com/@radix-ui/react-toggle-group/-/react-toggle-group-0.1.5.tgz#9e4d65e22c4fc0ba3a42fbc8d5496c430e5e9852" resolved "https://registry.yarnpkg.com/@radix-ui/react-toggle-group/-/react-toggle-group-0.1.5.tgz#9e4d65e22c4fc0ba3a42fbc8d5496c430e5e9852"
@ -1772,26 +1939,24 @@
"@radix-ui/react-primitive" "0.1.4" "@radix-ui/react-primitive" "0.1.4"
"@radix-ui/react-use-controllable-state" "0.1.0" "@radix-ui/react-use-controllable-state" "0.1.0"
"@radix-ui/react-tooltip@^0.1.7": "@radix-ui/react-tooltip@^1.0.0":
version "0.1.7" version "1.0.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-tooltip/-/react-tooltip-0.1.7.tgz#6f8c00d6e489565d14abf209ce0fb8853c8c8ee3" resolved "https://registry.yarnpkg.com/@radix-ui/react-tooltip/-/react-tooltip-1.0.0.tgz#f7fcecf2bac5c31cd14666b5acd056015fc21646"
integrity sha512-eiBUsVOHenZ0JR16tl970bB0DafJBz6mFgSGfIGIVpflFj0LIsIDiLMsYyvYdx1KwwsIUDTEZtxcPm/sWjPzqA== integrity sha512-RB06pov+O4Npy10ei1C6fsyB9QoOjz7Ubo8Sl3qdKtLgkL9iI96925DYtH0bxx6MH6YB2FuzLU6B75qn3AQQQw==
dependencies: dependencies:
"@babel/runtime" "^7.13.10" "@babel/runtime" "^7.13.10"
"@radix-ui/primitive" "0.1.0" "@radix-ui/primitive" "1.0.0"
"@radix-ui/react-compose-refs" "0.1.0" "@radix-ui/react-compose-refs" "1.0.0"
"@radix-ui/react-context" "0.1.1" "@radix-ui/react-context" "1.0.0"
"@radix-ui/react-id" "0.1.5" "@radix-ui/react-dismissable-layer" "1.0.0"
"@radix-ui/react-popper" "0.1.4" "@radix-ui/react-id" "1.0.0"
"@radix-ui/react-portal" "0.1.4" "@radix-ui/react-popper" "1.0.0"
"@radix-ui/react-presence" "0.1.2" "@radix-ui/react-portal" "1.0.0"
"@radix-ui/react-primitive" "0.1.4" "@radix-ui/react-presence" "1.0.0"
"@radix-ui/react-slot" "0.1.2" "@radix-ui/react-primitive" "1.0.0"
"@radix-ui/react-use-controllable-state" "0.1.0" "@radix-ui/react-slot" "1.0.0"
"@radix-ui/react-use-escape-keydown" "0.1.0" "@radix-ui/react-use-controllable-state" "1.0.0"
"@radix-ui/react-use-previous" "0.1.1" "@radix-ui/react-visually-hidden" "1.0.0"
"@radix-ui/react-use-rect" "0.1.1"
"@radix-ui/react-visually-hidden" "0.1.4"
"@radix-ui/react-use-body-pointer-events@0.1.1": "@radix-ui/react-use-body-pointer-events@0.1.1":
version "0.1.1" version "0.1.1"
@ -1808,6 +1973,13 @@
dependencies: dependencies:
"@babel/runtime" "^7.13.10" "@babel/runtime" "^7.13.10"
"@radix-ui/react-use-callback-ref@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.0.tgz#9e7b8b6b4946fe3cbe8f748c82a2cce54e7b6a90"
integrity sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-use-controllable-state@0.1.0": "@radix-ui/react-use-controllable-state@0.1.0":
version "0.1.0" version "0.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-0.1.0.tgz#4fced164acfc69a4e34fb9d193afdab973a55de1" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-0.1.0.tgz#4fced164acfc69a4e34fb9d193afdab973a55de1"
@ -1816,6 +1988,14 @@
"@babel/runtime" "^7.13.10" "@babel/runtime" "^7.13.10"
"@radix-ui/react-use-callback-ref" "0.1.0" "@radix-ui/react-use-callback-ref" "0.1.0"
"@radix-ui/react-use-controllable-state@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.0.tgz#a64deaafbbc52d5d407afaa22d493d687c538b7f"
integrity sha512-FohDoZvk3mEXh9AWAVyRTYR4Sq7/gavuofglmiXB2g1aKyboUD4YtgWxKj8O5n+Uak52gXQ4wKz5IFST4vtJHg==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-use-callback-ref" "1.0.0"
"@radix-ui/react-use-direction@0.1.0": "@radix-ui/react-use-direction@0.1.0":
version "0.1.0" version "0.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-direction/-/react-use-direction-0.1.0.tgz#97ac1d52e497c974389e7988f809238ed72e7df7" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-direction/-/react-use-direction-0.1.0.tgz#97ac1d52e497c974389e7988f809238ed72e7df7"
@ -1831,6 +2011,14 @@
"@babel/runtime" "^7.13.10" "@babel/runtime" "^7.13.10"
"@radix-ui/react-use-callback-ref" "0.1.0" "@radix-ui/react-use-callback-ref" "0.1.0"
"@radix-ui/react-use-escape-keydown@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.0.tgz#aef375db4736b9de38a5a679f6f49b45a060e5d1"
integrity sha512-JwfBCUIfhXRxKExgIqGa4CQsiMemo1Xt0W/B4ei3fpzpvPENKpMKQ8mZSB6Acj3ebrAEgi2xiQvcI1PAAodvyg==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-use-callback-ref" "1.0.0"
"@radix-ui/react-use-layout-effect@0.1.0": "@radix-ui/react-use-layout-effect@0.1.0":
version "0.1.0" version "0.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-0.1.0.tgz#ebf71bd6d2825de8f1fbb984abf2293823f0f223" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-0.1.0.tgz#ebf71bd6d2825de8f1fbb984abf2293823f0f223"
@ -1838,6 +2026,13 @@
dependencies: dependencies:
"@babel/runtime" "^7.13.10" "@babel/runtime" "^7.13.10"
"@radix-ui/react-use-layout-effect@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.0.tgz#2fc19e97223a81de64cd3ba1dc42ceffd82374dc"
integrity sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-use-previous@0.1.1": "@radix-ui/react-use-previous@0.1.1":
version "0.1.1" version "0.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-previous/-/react-use-previous-0.1.1.tgz#0226017f72267200f6e832a7103760e96a6db5d0" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-previous/-/react-use-previous-0.1.1.tgz#0226017f72267200f6e832a7103760e96a6db5d0"
@ -1853,6 +2048,14 @@
"@babel/runtime" "^7.13.10" "@babel/runtime" "^7.13.10"
"@radix-ui/rect" "0.1.1" "@radix-ui/rect" "0.1.1"
"@radix-ui/react-use-rect@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-1.0.0.tgz#b040cc88a4906b78696cd3a32b075ed5b1423b3e"
integrity sha512-TB7pID8NRMEHxb/qQJpvSt3hQU4sqNPM1VCTjTRjEOa7cEop/QMuq8S6fb/5Tsz64kqSvB9WnwsDHtjnrM9qew==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/rect" "1.0.0"
"@radix-ui/react-use-size@0.1.1": "@radix-ui/react-use-size@0.1.1":
version "0.1.1" version "0.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-size/-/react-use-size-0.1.1.tgz#f6b75272a5d41c3089ca78c8a2e48e5f204ef90f" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-size/-/react-use-size-0.1.1.tgz#f6b75272a5d41c3089ca78c8a2e48e5f204ef90f"
@ -1860,6 +2063,14 @@
dependencies: dependencies:
"@babel/runtime" "^7.13.10" "@babel/runtime" "^7.13.10"
"@radix-ui/react-use-size@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-size/-/react-use-size-1.0.0.tgz#a0b455ac826749419f6354dc733e2ca465054771"
integrity sha512-imZ3aYcoYCKhhgNpkNDh/aTiU05qw9hX+HHI1QDBTyIlcFjgeFlKKySNGMwTp7nYFLQg/j0VA2FmCY4WPDDHMg==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-use-layout-effect" "1.0.0"
"@radix-ui/react-visually-hidden@0.1.4", "@radix-ui/react-visually-hidden@^0.1.4": "@radix-ui/react-visually-hidden@0.1.4", "@radix-ui/react-visually-hidden@^0.1.4":
version "0.1.4" version "0.1.4"
resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-0.1.4.tgz#6c75eae34fb5d084b503506fbfc05587ced05f03" resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-0.1.4.tgz#6c75eae34fb5d084b503506fbfc05587ced05f03"
@ -1868,6 +2079,14 @@
"@babel/runtime" "^7.13.10" "@babel/runtime" "^7.13.10"
"@radix-ui/react-primitive" "0.1.4" "@radix-ui/react-primitive" "0.1.4"
"@radix-ui/react-visually-hidden@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.0.0.tgz#4d69d7e3b6d21ee4678ed6de5215dcd068394401"
integrity sha512-MwAhMdX+n6S4InwRKSnpUsp+lLkYG6izQF56ul6guSX2mBBLOMV9Frx7xJlkEe2GjKLzbNuHhaCS6e5gopmZNA==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-primitive" "1.0.0"
"@radix-ui/rect@0.1.1": "@radix-ui/rect@0.1.1":
version "0.1.1" version "0.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-0.1.1.tgz#95b5ba51f469bea6b1b841e2d427e17e37d38419" resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-0.1.1.tgz#95b5ba51f469bea6b1b841e2d427e17e37d38419"
@ -1875,6 +2094,13 @@
dependencies: dependencies:
"@babel/runtime" "^7.13.10" "@babel/runtime" "^7.13.10"
"@radix-ui/rect@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-1.0.0.tgz#0dc8e6a829ea2828d53cbc94b81793ba6383bf3c"
integrity sha512-d0O68AYy/9oeEy1DdC07bz1/ZXX+DqCskRd3i4JzLSTXwefzaepQrKjXC7aNM8lTHjFLDO0pDgaEiQ7jEk+HVg==
dependencies:
"@babel/runtime" "^7.13.10"
"@rollup/pluginutils@^4.2.1": "@rollup/pluginutils@^4.2.1":
version "4.2.1" version "4.2.1"
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.2.1.tgz#e6c6c3aba0744edce3fb2074922d3776c0af2a6d" resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.2.1.tgz#e6c6c3aba0744edce3fb2074922d3776c0af2a6d"
@ -2840,6 +3066,11 @@ datastore-core@^8.0.1:
it-take "^1.0.1" it-take "^1.0.1"
uint8arrays "^3.0.0" uint8arrays "^3.0.0"
date-fns@^2.29.3:
version "2.29.3"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.3.tgz#27402d2fc67eb442b511b70bbdf98e6411cd68a8"
integrity sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==
debug@^2.6.9: debug@^2.6.9:
version "2.6.9" version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@ -6832,6 +7063,11 @@ use-callback-ref@^1.2.3:
resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.2.5.tgz#6115ed242cfbaed5915499c0a9842ca2912f38a5" resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.2.5.tgz#6115ed242cfbaed5915499c0a9842ca2912f38a5"
integrity sha512-gN3vgMISAgacF7sqsLPByqoePooY3n2emTH59Ur5d/M8eg4WTWu1xp8i8DHjohftIyEx0S08RiYxbffr4j8Peg== integrity sha512-gN3vgMISAgacF7sqsLPByqoePooY3n2emTH59Ur5d/M8eg4WTWu1xp8i8DHjohftIyEx0S08RiYxbffr4j8Peg==
use-isomorphic-layout-effect@^1.1.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz#497cefb13d863d687b08477d9e5a164ad8c1a6fb"
integrity sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==
use-sidecar@^1.0.1: use-sidecar@^1.0.1:
version "1.0.5" version "1.0.5"
resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.0.5.tgz#ffff2a17c1df42e348624b699ba6e5c220527f2b" resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.0.5.tgz#ffff2a17c1df42e348624b699ba6e5c220527f2b"