Add skeleton and loading states (#366)
* feat: add skeleton placeholder components and stories * feat: adds gap messages component and stories * feat: add information box component and stories (WIP) * feat: add dismiss prop and more stories * fix: changes onDismiss existing function to onClose * feat: add sidebar skeleton loader * feat: makes the banner component more flexible * update information box * fix: changes from review * feat: add topbar-skeleton component * Fix Skeleton typo --------- Co-authored-by: Pavel Prichodko <14926950+prichodko@users.noreply.github.com>
This commit is contained in:
parent
ec8b5b0b44
commit
a0cde7d4a6
|
@ -77,3 +77,15 @@ body,
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes gradient {
|
||||||
|
0% {
|
||||||
|
background-position: 0% 50%;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
background-position: 100% 50%;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: 0% 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
/* Animation for skeleton placeholder */
|
||||||
|
@keyframes gradient {
|
||||||
|
0% {
|
||||||
|
background-position: 0% 50%;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
background-position: 100% 50%;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: 0% 50%;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,9 @@
|
||||||
|
import React from 'react'
|
||||||
import { Provider, ToastContainer } from '../src'
|
import { Provider, ToastContainer } from '../src'
|
||||||
import { Parameters, Decorator } from '@storybook/react'
|
import { Parameters, Decorator } from '@storybook/react'
|
||||||
|
|
||||||
import './reset.css'
|
import './reset.css'
|
||||||
|
import './components.css'
|
||||||
|
|
||||||
export const parameters: Parameters = {
|
export const parameters: Parameters = {
|
||||||
actions: { argTypesRegex: '^on[A-Z].*' },
|
actions: { argTypesRegex: '^on[A-Z].*' },
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { PinIcon } from '@status-im/icons/20'
|
import { AlertIcon, PinIcon, RecentIcon } from '@status-im/icons/20'
|
||||||
import { Stack } from '@tamagui/core'
|
import { Stack } from '@tamagui/core'
|
||||||
|
|
||||||
import { Banner } from './banner'
|
import { Banner } from './banner'
|
||||||
|
@ -38,6 +38,22 @@ export const NoCount: Story = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const NetworkStateConnecting: Story = {
|
||||||
|
args: {
|
||||||
|
backgroundColor: '$neutral-80-opa-5',
|
||||||
|
icon: <RecentIcon />,
|
||||||
|
children: 'Connecting...',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NetworkStateError: Story = {
|
||||||
|
args: {
|
||||||
|
backgroundColor: '$danger-50-opa-20',
|
||||||
|
icon: <AlertIcon />,
|
||||||
|
children: 'Network is down',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
export const AllVariants: Story = {
|
export const AllVariants: Story = {
|
||||||
args: {},
|
args: {},
|
||||||
render: () => (
|
render: () => (
|
||||||
|
@ -46,6 +62,12 @@ export const AllVariants: Story = {
|
||||||
Banner message
|
Banner message
|
||||||
</Banner>
|
</Banner>
|
||||||
<Banner count={5}>Banner message</Banner>
|
<Banner count={5}>Banner message</Banner>
|
||||||
|
<Banner backgroundColor="$neutral-80-opa-5" icon={<RecentIcon />}>
|
||||||
|
Connecting...
|
||||||
|
</Banner>
|
||||||
|
<Banner backgroundColor="$danger-50-opa-20" icon={<AlertIcon />}>
|
||||||
|
Network is down
|
||||||
|
</Banner>
|
||||||
<Banner icon={<PinIcon />}>Banner message</Banner>
|
<Banner icon={<PinIcon />}>Banner message</Banner>
|
||||||
</Stack>
|
</Stack>
|
||||||
),
|
),
|
||||||
|
|
|
@ -4,17 +4,25 @@ import { View } from 'react-native'
|
||||||
import { Counter } from '../counter'
|
import { Counter } from '../counter'
|
||||||
import { Text } from '../text'
|
import { Text } from '../text'
|
||||||
|
|
||||||
|
import type { ColorTokens } from '@tamagui/core'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
icon?: React.ReactNode
|
icon?: React.ReactNode
|
||||||
count?: number
|
count?: number
|
||||||
|
backgroundColor?: ColorTokens
|
||||||
}
|
}
|
||||||
|
|
||||||
const Banner = (props: Props) => {
|
const Banner = (props: Props) => {
|
||||||
const { icon, children, count } = props
|
const {
|
||||||
|
icon,
|
||||||
|
children,
|
||||||
|
count,
|
||||||
|
backgroundColor = '$primary-50-opa-20',
|
||||||
|
} = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Base>
|
<Base backgroundColor={backgroundColor}>
|
||||||
<Content>
|
<Content>
|
||||||
{icon}
|
{icon}
|
||||||
<Text size={13} color="$textPrimary">
|
<Text size={13} color="$textPrimary">
|
||||||
|
@ -30,7 +38,6 @@ export { Banner }
|
||||||
export type { Props as BannerProps }
|
export type { Props as BannerProps }
|
||||||
|
|
||||||
const Base = styled(View, {
|
const Base = styled(View, {
|
||||||
backgroundColor: '$primary-50-opa-20',
|
|
||||||
padding: 12,
|
padding: 12,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
|
|
@ -55,7 +55,7 @@ const ContextTag = (props: Props) => {
|
||||||
src,
|
src,
|
||||||
icon,
|
icon,
|
||||||
label,
|
label,
|
||||||
type = 'default',
|
// type = 'default', // this is commented because it's not being used
|
||||||
size = 24,
|
size = 24,
|
||||||
blur = false,
|
blur = false,
|
||||||
outline,
|
outline,
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { GapMessages } from './gap-messages'
|
||||||
|
|
||||||
|
import type { Meta, StoryObj } from '@storybook/react'
|
||||||
|
|
||||||
|
// More on how to set up stories at: https://storybook.js.org/docs/7.0/react/writing-stories/introduction
|
||||||
|
const meta: Meta<typeof GapMessages> = {
|
||||||
|
title: 'gap-messages',
|
||||||
|
component: GapMessages,
|
||||||
|
argTypes: {},
|
||||||
|
parameters: {
|
||||||
|
design: {
|
||||||
|
type: 'figma',
|
||||||
|
url: 'https://www.figma.com/file/IBmFKgGL1B4GzqD8LQTw6n/Design-System-for-Desktop%2FWeb?node-id=5187-181408&t=5dgANDld90Qfd00V-0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof GapMessages>
|
||||||
|
|
||||||
|
// More on writing stories with args: https://storybook.js.org/docs/7.0/react/writing-stories/args
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
message: 'This is a simple message.',
|
||||||
|
startDate: 'Jan 8 · 09:12',
|
||||||
|
endDate: 'Mar 8 · 22:42',
|
||||||
|
tooltipMessage: 'This is some tooltip message.',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default meta
|
|
@ -0,0 +1,110 @@
|
||||||
|
import { InfoIcon } from '@status-im/icons/16'
|
||||||
|
import { Stack, styled } from '@tamagui/core'
|
||||||
|
|
||||||
|
import { Text } from '../text'
|
||||||
|
import { Tooltip } from '../tooltip'
|
||||||
|
|
||||||
|
const NUM_CIRCLES = 200
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
startDate: string
|
||||||
|
endDate: string
|
||||||
|
message: string
|
||||||
|
tooltipMessage: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO try to find a solution for the inset shadow
|
||||||
|
const GapMessages = (props: Props) => {
|
||||||
|
const { startDate, endDate, message, tooltipMessage } = props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack backgroundColor="$neutral-5" width="100%">
|
||||||
|
<Stack>
|
||||||
|
<Stack flexDirection="row" width="100%" overflow="hidden" mt={-4}>
|
||||||
|
<Circles />
|
||||||
|
</Stack>
|
||||||
|
<Stack py={20} flexDirection="row">
|
||||||
|
<Stack
|
||||||
|
height="auto"
|
||||||
|
minHeight="100%"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
py={5}
|
||||||
|
pr={16}
|
||||||
|
pl={28}
|
||||||
|
>
|
||||||
|
<EmptyCircle />
|
||||||
|
<Divider flex={1} />
|
||||||
|
<EmptyCircle />
|
||||||
|
</Stack>
|
||||||
|
<Stack>
|
||||||
|
<Text size={11} color="$neutral-50">
|
||||||
|
{startDate}
|
||||||
|
</Text>
|
||||||
|
<Stack py={16}>
|
||||||
|
<Text size={15} truncate>
|
||||||
|
{message}
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
<Text size={11} color="$neutral-50">
|
||||||
|
{endDate}
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
<Stack position="absolute" right={26} top={20}>
|
||||||
|
<Tooltip side="bottom" sideOffset={4} content={<>{tooltipMessage}</>}>
|
||||||
|
<Stack width={20}>
|
||||||
|
<InfoIcon color="$neutral-50" />
|
||||||
|
</Stack>
|
||||||
|
</Tooltip>
|
||||||
|
</Stack>
|
||||||
|
<Stack flexDirection="row" width="100%" overflow="hidden" mb={-4}>
|
||||||
|
<Circles />
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { GapMessages }
|
||||||
|
export type { Props as GapMessageProps }
|
||||||
|
|
||||||
|
// TODO try to find a responsive solution if we need to keep the circles in the future
|
||||||
|
const Circles = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{[...Array(NUM_CIRCLES)].map((_, i) => (
|
||||||
|
<Circle key={i} />
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const Circle = styled(Stack, {
|
||||||
|
name: 'Circle',
|
||||||
|
width: 8,
|
||||||
|
height: 8,
|
||||||
|
borderRadius: 4,
|
||||||
|
backgroundColor: '$neutral-5',
|
||||||
|
marginRight: 7,
|
||||||
|
})
|
||||||
|
|
||||||
|
const EmptyCircle = styled(Stack, {
|
||||||
|
name: 'EmptyCircle',
|
||||||
|
width: 8,
|
||||||
|
height: 8,
|
||||||
|
borderRadius: 4,
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: '$neutral-40',
|
||||||
|
})
|
||||||
|
|
||||||
|
const Divider = styled(Stack, {
|
||||||
|
name: 'Divider',
|
||||||
|
backgroundColor: '$neutral-40',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: '$neutral-5',
|
||||||
|
borderStyle: 'dashed',
|
||||||
|
width: 1,
|
||||||
|
height: 'auto',
|
||||||
|
})
|
|
@ -0,0 +1 @@
|
||||||
|
export { GapMessages } from './gap-messages'
|
|
@ -3,14 +3,17 @@ export * from './button'
|
||||||
export * from './composer'
|
export * from './composer'
|
||||||
export * from './dividers'
|
export * from './dividers'
|
||||||
export * from './dynamic-button'
|
export * from './dynamic-button'
|
||||||
|
export * from './gap-messages'
|
||||||
export * from './icon-button'
|
export * from './icon-button'
|
||||||
export * from './image'
|
export * from './image'
|
||||||
|
export * from './information-box'
|
||||||
export * from './input'
|
export * from './input'
|
||||||
export * from './messages'
|
export * from './messages'
|
||||||
export * from './pinned-message'
|
export * from './pinned-message'
|
||||||
export * from './provider'
|
export * from './provider'
|
||||||
export * from './sidebar'
|
export * from './sidebar'
|
||||||
export * from './sidebar-members'
|
export * from './sidebar-members'
|
||||||
|
export * from './skeleton'
|
||||||
export * from './text'
|
export * from './text'
|
||||||
export * from './toast'
|
export * from './toast'
|
||||||
export * from './topbar'
|
export * from './topbar'
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
export { InformationBox } from './information-box'
|
|
@ -0,0 +1,167 @@
|
||||||
|
import { InfoIcon } from '@status-im/icons/16'
|
||||||
|
|
||||||
|
import { InformationBox } from './information-box'
|
||||||
|
|
||||||
|
import type { Meta, StoryObj } from '@storybook/react'
|
||||||
|
|
||||||
|
// More on how to set up stories at: https://storybook.js.org/docs/7.0/react/writing-stories/introduction
|
||||||
|
const meta: Meta<typeof InformationBox> = {
|
||||||
|
title: 'information-box',
|
||||||
|
component: InformationBox,
|
||||||
|
argTypes: {},
|
||||||
|
parameters: {
|
||||||
|
design: {
|
||||||
|
type: 'figma',
|
||||||
|
url: 'https://www.figma.com/file/IBmFKgGL1B4GzqD8LQTw6n/Design-System-for-Desktop%2FWeb?node-id=5187-181408&t=5dgANDld90Qfd00V-0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof InformationBox>
|
||||||
|
|
||||||
|
// More on writing stories with args: https://storybook.js.org/docs/7.0/react/writing-stories/args
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
message: 'This is a simple message.',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Information: Story = {
|
||||||
|
args: {
|
||||||
|
...Default.args,
|
||||||
|
variant: 'information',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Error: Story = {
|
||||||
|
args: {
|
||||||
|
...Default.args,
|
||||||
|
variant: 'error',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DefaultWithIcon: Story = {
|
||||||
|
args: {
|
||||||
|
message: 'This is a simple message with an info icon.',
|
||||||
|
icon: <InfoIcon />,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const InformationWithIcon: Story = {
|
||||||
|
args: {
|
||||||
|
...DefaultWithIcon.args,
|
||||||
|
variant: 'information',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ErrorWithIcon: Story = {
|
||||||
|
args: {
|
||||||
|
...DefaultWithIcon.args,
|
||||||
|
variant: 'error',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WithMaxWidth: Story = {
|
||||||
|
args: {
|
||||||
|
...Default.args,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WithIconAndTwoLines: Story = {
|
||||||
|
args: {
|
||||||
|
...DefaultWithIcon.args,
|
||||||
|
message: 'This is a message with an icon and two lines.',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WithButtonAndIconDefault: Story = {
|
||||||
|
args: {
|
||||||
|
...DefaultWithIcon.args,
|
||||||
|
message: 'This is a message with an icon and a button.',
|
||||||
|
buttonText: 'Button',
|
||||||
|
onButtonPress: () => alert('clicked'),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WithButtonAndIconInformation: Story = {
|
||||||
|
args: {
|
||||||
|
...WithButtonAndIconDefault.args,
|
||||||
|
variant: 'information',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WithButtonAndIconError: Story = {
|
||||||
|
args: {
|
||||||
|
...WithButtonAndIconDefault.args,
|
||||||
|
variant: 'error',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DefaultWithDismiss: Story = {
|
||||||
|
args: {
|
||||||
|
message: 'This is a simple message.',
|
||||||
|
onClosePress: () => alert('dismissed'),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const InformationWithDismiss: Story = {
|
||||||
|
args: {
|
||||||
|
...DefaultWithDismiss.args,
|
||||||
|
variant: 'information',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ErrorWithDismiss: Story = {
|
||||||
|
args: {
|
||||||
|
...DefaultWithDismiss.args,
|
||||||
|
variant: 'error',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DefaultWithIconAndDismiss: Story = {
|
||||||
|
args: {
|
||||||
|
message: 'This is a simple message with an info icon.',
|
||||||
|
icon: <InfoIcon />,
|
||||||
|
onClosePress: () => alert('dismissed'),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const InformationWithIconAndDismiss: Story = {
|
||||||
|
args: {
|
||||||
|
...DefaultWithIconAndDismiss.args,
|
||||||
|
variant: 'information',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ErrorWithIconAndDismiss: Story = {
|
||||||
|
args: {
|
||||||
|
...DefaultWithIconAndDismiss.args,
|
||||||
|
variant: 'error',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WithButtonAndIconAndDismiss: Story = {
|
||||||
|
args: {
|
||||||
|
...WithButtonAndIconDefault.args,
|
||||||
|
message: 'This is a message with an icon and a button.',
|
||||||
|
buttonText: 'Button',
|
||||||
|
onButtonPress: () => alert('clicked'),
|
||||||
|
onClosePress: () => alert('dismissed'),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WithButtonAndIconAndDismissInformation: Story = {
|
||||||
|
args: {
|
||||||
|
...WithButtonAndIconAndDismiss.args,
|
||||||
|
variant: 'information',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WithButtonAndIconAndDismissError: Story = {
|
||||||
|
args: {
|
||||||
|
...WithButtonAndIconAndDismiss.args,
|
||||||
|
variant: 'error',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default meta
|
|
@ -0,0 +1,134 @@
|
||||||
|
import { cloneElement } from 'react'
|
||||||
|
|
||||||
|
import { CloseIcon } from '@status-im/icons/12'
|
||||||
|
import { Stack, styled } from '@tamagui/core'
|
||||||
|
|
||||||
|
import { Button } from '../button'
|
||||||
|
import { Text } from '../text'
|
||||||
|
|
||||||
|
import type { GetVariants, MapColorToken } from '../types'
|
||||||
|
|
||||||
|
type Variants = GetVariants<typeof Base>
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
message: string
|
||||||
|
variant?: Variants['variant']
|
||||||
|
icon?: React.ReactElement
|
||||||
|
buttonText?: string
|
||||||
|
onButtonPress?: () => void
|
||||||
|
onClosePress?: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
type Variant = Props['variant']
|
||||||
|
|
||||||
|
const textColors: MapColorToken<Variant> = {
|
||||||
|
default: '$neutral-100',
|
||||||
|
information: '$neutral-100',
|
||||||
|
error: '$danger-50',
|
||||||
|
}
|
||||||
|
|
||||||
|
const iconColors: MapColorToken<Variant> = {
|
||||||
|
default: '$neutral-50',
|
||||||
|
information: '$neutral-50',
|
||||||
|
error: '$danger-50',
|
||||||
|
}
|
||||||
|
|
||||||
|
const buttonVariants: Record<NonNullable<Variant>, 'primary' | 'danger'> = {
|
||||||
|
default: 'primary',
|
||||||
|
information: 'primary',
|
||||||
|
error: 'danger',
|
||||||
|
}
|
||||||
|
|
||||||
|
const InformationBox = (props: Props) => {
|
||||||
|
const {
|
||||||
|
message,
|
||||||
|
variant = 'default',
|
||||||
|
icon,
|
||||||
|
buttonText,
|
||||||
|
onButtonPress,
|
||||||
|
onClosePress,
|
||||||
|
} = props
|
||||||
|
|
||||||
|
const textColor = textColors[variant]
|
||||||
|
const iconColor = iconColors[variant]
|
||||||
|
const buttonVariant = buttonVariants[variant]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Base variant={variant} {...props}>
|
||||||
|
<Stack
|
||||||
|
flexDirection="row"
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="flex-start"
|
||||||
|
width="100%"
|
||||||
|
>
|
||||||
|
{icon ? (
|
||||||
|
<Stack pr={8} pt={1} alignSelf="flex-start">
|
||||||
|
{cloneElement(icon, { color: iconColor })}
|
||||||
|
</Stack>
|
||||||
|
) : null}
|
||||||
|
<Stack flexShrink={1} width="100%">
|
||||||
|
<Text size={13} color={textColor}>
|
||||||
|
{message}
|
||||||
|
</Text>
|
||||||
|
{buttonText ? (
|
||||||
|
<Stack pt={8} width="fit-content">
|
||||||
|
<Button
|
||||||
|
onPress={() => onButtonPress?.()}
|
||||||
|
size={24}
|
||||||
|
variant={buttonVariant}
|
||||||
|
>
|
||||||
|
{buttonText}
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
) : null}
|
||||||
|
</Stack>
|
||||||
|
{onClosePress ? (
|
||||||
|
<Stack
|
||||||
|
pl={8}
|
||||||
|
pt={4}
|
||||||
|
onPress={() => onClosePress()}
|
||||||
|
cursor="pointer"
|
||||||
|
alignSelf="flex-start"
|
||||||
|
>
|
||||||
|
<CloseIcon />
|
||||||
|
</Stack>
|
||||||
|
) : null}
|
||||||
|
</Stack>
|
||||||
|
</Base>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { InformationBox }
|
||||||
|
export type { Props as InformationBoxProps }
|
||||||
|
|
||||||
|
const Base = styled(Stack, {
|
||||||
|
name: 'InformationBox',
|
||||||
|
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
justifyContent: 'center',
|
||||||
|
|
||||||
|
userSelect: 'none',
|
||||||
|
borderWidth: 1,
|
||||||
|
|
||||||
|
py: 11,
|
||||||
|
px: 16,
|
||||||
|
borderRadius: 12,
|
||||||
|
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: {
|
||||||
|
backgroundColor: '$white-100',
|
||||||
|
borderColor: '$neutral-20',
|
||||||
|
},
|
||||||
|
information: {
|
||||||
|
backgroundColor: '$blue-50-opa-5',
|
||||||
|
borderColor: '$blue-50-opa-10',
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
backgroundColor: '$danger-50-opa-5',
|
||||||
|
borderColor: '$danger-50-opa-10',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
|
@ -1,7 +1,19 @@
|
||||||
|
import { Stack } from '@tamagui/core'
|
||||||
|
|
||||||
import { CHANNEL_GROUPS } from './mock-data'
|
import { CHANNEL_GROUPS } from './mock-data'
|
||||||
import { Sidebar } from './sidebar'
|
import { Sidebar } from './sidebar'
|
||||||
|
|
||||||
import type { Meta, StoryObj } from '@storybook/react'
|
import type { SidebarProps } from './sidebar'
|
||||||
|
import type { Meta } from '@storybook/react'
|
||||||
|
|
||||||
|
const COMMUNITY = {
|
||||||
|
name: 'Rarible',
|
||||||
|
description:
|
||||||
|
'Multichain community-centric NFT marketplace. Create, buy and sell your NFTs.',
|
||||||
|
membersCount: 123,
|
||||||
|
imageUrl:
|
||||||
|
'https://images.unsplash.com/photo-1574786527860-f2e274867c91?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1764&q=80',
|
||||||
|
}
|
||||||
|
|
||||||
// More on how to set up stories at: https://storybook.js.org/docs/7.0/react/writing-stories/introduction
|
// More on how to set up stories at: https://storybook.js.org/docs/7.0/react/writing-stories/introduction
|
||||||
const meta: Meta<typeof Sidebar> = {
|
const meta: Meta<typeof Sidebar> = {
|
||||||
|
@ -9,6 +21,7 @@ const meta: Meta<typeof Sidebar> = {
|
||||||
component: Sidebar,
|
component: Sidebar,
|
||||||
args: {
|
args: {
|
||||||
channels: CHANNEL_GROUPS,
|
channels: CHANNEL_GROUPS,
|
||||||
|
community: COMMUNITY,
|
||||||
},
|
},
|
||||||
argTypes: {},
|
argTypes: {},
|
||||||
parameters: {
|
parameters: {
|
||||||
|
@ -19,11 +32,20 @@ const meta: Meta<typeof Sidebar> = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
type Story = StoryObj<typeof Sidebar>
|
export const Default = {
|
||||||
|
render: (args: SidebarProps) => (
|
||||||
|
<Stack width={352} height="100vh">
|
||||||
|
<Sidebar {...args} />
|
||||||
|
</Stack>
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
// More on writing stories with args: https://storybook.js.org/docs/7.0/react/writing-stories/args
|
export const LoadingSidebar = {
|
||||||
export const Default: Story = {
|
render: (args: SidebarProps) => (
|
||||||
args: {},
|
<Stack width={352} height="100vh">
|
||||||
|
<Sidebar {...args} isLoading />
|
||||||
|
</Stack>
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
export default meta
|
export default meta
|
||||||
|
|
|
@ -6,12 +6,13 @@ import { AccordionItem } from '../accordion/accordionItem'
|
||||||
import { Avatar } from '../avatar'
|
import { Avatar } from '../avatar'
|
||||||
import { Button } from '../button'
|
import { Button } from '../button'
|
||||||
import { Image } from '../image'
|
import { Image } from '../image'
|
||||||
|
import { SidebarSkeleton } from '../skeleton/sidebar-skeleton'
|
||||||
import { Text } from '../text'
|
import { Text } from '../text'
|
||||||
import { CHANNEL_GROUPS } from './mock-data'
|
import { CHANNEL_GROUPS } from './mock-data'
|
||||||
|
|
||||||
import type { ChannelGroup } from './mock-data'
|
import type { ChannelGroup } from './mock-data'
|
||||||
|
|
||||||
type Props = {
|
export type SidebarProps = {
|
||||||
community: {
|
community: {
|
||||||
name: string
|
name: string
|
||||||
description: string
|
description: string
|
||||||
|
@ -21,18 +22,24 @@ type Props = {
|
||||||
channels?: ChannelGroup[]
|
channels?: ChannelGroup[]
|
||||||
selectedChannelId?: string
|
selectedChannelId?: string
|
||||||
onChannelPress: (channelId: string) => void
|
onChannelPress: (channelId: string) => void
|
||||||
|
isLoading?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const Sidebar = (props: Props) => {
|
const Sidebar = (props: SidebarProps) => {
|
||||||
const {
|
const {
|
||||||
community,
|
community,
|
||||||
channels = CHANNEL_GROUPS,
|
channels = CHANNEL_GROUPS,
|
||||||
selectedChannelId,
|
selectedChannelId,
|
||||||
onChannelPress,
|
onChannelPress,
|
||||||
|
isLoading,
|
||||||
} = props
|
} = props
|
||||||
|
|
||||||
const { name, description, membersCount, imageUrl } = community
|
const { name, description, membersCount, imageUrl } = community
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <SidebarSkeleton />
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
backgroundColor="$background"
|
backgroundColor="$background"
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './message-skeleton'
|
||||||
|
export * from './skeleton'
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { MessageSkeleton } from './message-skeleton'
|
||||||
|
|
||||||
|
import type { Meta, StoryObj } from '@storybook/react'
|
||||||
|
|
||||||
|
const meta: Meta<typeof MessageSkeleton> = {
|
||||||
|
component: MessageSkeleton,
|
||||||
|
|
||||||
|
parameters: {
|
||||||
|
design: {
|
||||||
|
type: 'figma',
|
||||||
|
url: 'https://www.figma.com/file/1RN1MFwfSqA6jNFJBeNdEu/Posts-%26-Attachments-for-Web?t=1Xf5496ymHeazodw-0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof MessageSkeleton>
|
||||||
|
|
||||||
|
export const MessageSkeletonSmallest: Story = {
|
||||||
|
name: 'Smallest',
|
||||||
|
args: {
|
||||||
|
size: 'smallest',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
export const MessageSkeletonSmall: Story = {
|
||||||
|
name: 'Small',
|
||||||
|
args: {
|
||||||
|
size: 'small',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
export const MessageSkeletonMedium: Story = {
|
||||||
|
name: 'Medium',
|
||||||
|
args: {
|
||||||
|
size: 'medium',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
export const MessageSkeletonLarge: Story = {
|
||||||
|
name: 'Large',
|
||||||
|
args: {
|
||||||
|
size: 'large',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default meta
|
|
@ -0,0 +1,52 @@
|
||||||
|
import { Stack } from '@tamagui/core'
|
||||||
|
|
||||||
|
import { Skeleton } from './skeleton'
|
||||||
|
|
||||||
|
import type { StackProps } from '@tamagui/core'
|
||||||
|
|
||||||
|
type SizeVariant = 'smallest' | 'small' | 'medium' | 'large'
|
||||||
|
|
||||||
|
type Props = Omit<StackProps, 'size'> & {
|
||||||
|
size?: SizeVariant
|
||||||
|
}
|
||||||
|
|
||||||
|
const skeletonTopSize = {
|
||||||
|
smallest: 80,
|
||||||
|
small: 96,
|
||||||
|
medium: 112,
|
||||||
|
large: 124,
|
||||||
|
}
|
||||||
|
|
||||||
|
const skeletonBottomSizes = {
|
||||||
|
smallest: 144,
|
||||||
|
small: 156,
|
||||||
|
medium: 212,
|
||||||
|
large: 249,
|
||||||
|
}
|
||||||
|
|
||||||
|
const MessageSkeleton = (props: Props) => {
|
||||||
|
const { size, ...rest } = props
|
||||||
|
return (
|
||||||
|
<Stack flexDirection="row" p={8} width="100%" {...rest}>
|
||||||
|
{/* Avatar */}
|
||||||
|
<Skeleton />
|
||||||
|
<Stack flex={1} ml={8}>
|
||||||
|
{/* Text placeholders */}
|
||||||
|
<Skeleton
|
||||||
|
width={skeletonTopSize[size || 'medium']}
|
||||||
|
br={6}
|
||||||
|
height={8}
|
||||||
|
mb={8}
|
||||||
|
/>
|
||||||
|
<Skeleton
|
||||||
|
width={skeletonBottomSizes[size || 'medium']}
|
||||||
|
br={6}
|
||||||
|
height={16}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { MessageSkeleton }
|
||||||
|
export type { Props as MessageSkeletonProps }
|
|
@ -0,0 +1,152 @@
|
||||||
|
import { Stack } from '@tamagui/core'
|
||||||
|
|
||||||
|
import { Skeleton } from './skeleton'
|
||||||
|
|
||||||
|
const SidebarSkeleton = () => {
|
||||||
|
// Eventually we can in the future abstract some of these components to be reusable if we need to
|
||||||
|
return (
|
||||||
|
<Stack
|
||||||
|
backgroundColor="$background"
|
||||||
|
borderRightWidth={1}
|
||||||
|
borderColor="$neutral-10"
|
||||||
|
height="100%"
|
||||||
|
overflow="scroll"
|
||||||
|
>
|
||||||
|
<Stack height={135} width="100%" backgroundColor="$neutral-10" />
|
||||||
|
<Stack
|
||||||
|
paddingBottom={16}
|
||||||
|
marginTop={-16}
|
||||||
|
backgroundColor="$background"
|
||||||
|
borderTopLeftRadius={20}
|
||||||
|
borderTopRightRadius={20}
|
||||||
|
zIndex={10}
|
||||||
|
>
|
||||||
|
<Stack paddingHorizontal={16} paddingBottom={16}>
|
||||||
|
<Stack marginTop={-40} marginBottom={12}>
|
||||||
|
<Skeleton
|
||||||
|
height={80}
|
||||||
|
width={80}
|
||||||
|
borderRadius={40}
|
||||||
|
borderWidth={2}
|
||||||
|
borderColor="$white-100"
|
||||||
|
variant="secondary"
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
<Skeleton
|
||||||
|
height={24}
|
||||||
|
width={104}
|
||||||
|
borderRadius={8}
|
||||||
|
mb={14}
|
||||||
|
variant="secondary"
|
||||||
|
/>
|
||||||
|
<Skeleton
|
||||||
|
height={16}
|
||||||
|
width={312}
|
||||||
|
borderRadius={8}
|
||||||
|
mb={8}
|
||||||
|
variant="secondary"
|
||||||
|
/>
|
||||||
|
<Skeleton
|
||||||
|
height={16}
|
||||||
|
width={272}
|
||||||
|
borderRadius={8}
|
||||||
|
mb={12}
|
||||||
|
variant="secondary"
|
||||||
|
/>
|
||||||
|
<Stack flexDirection="row" alignItems="center" mb={18}>
|
||||||
|
<Skeleton height={14} width={14} mr={4} />
|
||||||
|
<Skeleton
|
||||||
|
height={12}
|
||||||
|
width={50}
|
||||||
|
borderRadius={5}
|
||||||
|
variant="secondary"
|
||||||
|
/>
|
||||||
|
<Skeleton height={14} width={14} ml={12} mr={4} />
|
||||||
|
<Skeleton
|
||||||
|
height={12}
|
||||||
|
width={50}
|
||||||
|
borderRadius={5}
|
||||||
|
variant="secondary"
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
<Stack flexDirection="row" alignItems="center" mb={27} gap={8}>
|
||||||
|
<Skeleton height={24} width={76} borderRadius={20} />
|
||||||
|
<Skeleton height={24} width={76} borderRadius={20} />
|
||||||
|
<Skeleton height={24} width={76} borderRadius={20} />
|
||||||
|
</Stack>
|
||||||
|
<Stack mb={27}>
|
||||||
|
<Skeleton height={12} width={50} borderRadius={5} mb={19} />
|
||||||
|
<Stack flexDirection="row" alignItems="center" mb={16}>
|
||||||
|
<Skeleton height={24} width={24} mr={8} />
|
||||||
|
<Skeleton
|
||||||
|
height={12}
|
||||||
|
width={80}
|
||||||
|
borderRadius={5}
|
||||||
|
variant="secondary"
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
<Stack flexDirection="row" alignItems="center" mb={16}>
|
||||||
|
<Skeleton height={24} width={24} mr={8} />
|
||||||
|
<Skeleton
|
||||||
|
height={12}
|
||||||
|
width={100}
|
||||||
|
borderRadius={5}
|
||||||
|
variant="secondary"
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
<Stack flexDirection="row" alignItems="center" mb={16}>
|
||||||
|
<Skeleton height={24} width={24} mr={8} />
|
||||||
|
<Skeleton
|
||||||
|
height={12}
|
||||||
|
width={70}
|
||||||
|
borderRadius={5}
|
||||||
|
variant="secondary"
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
<Stack flexDirection="row" alignItems="center">
|
||||||
|
<Skeleton height={24} width={24} mr={8} />
|
||||||
|
<Skeleton
|
||||||
|
height={12}
|
||||||
|
width={90}
|
||||||
|
borderRadius={5}
|
||||||
|
variant="secondary"
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
<Stack>
|
||||||
|
<Skeleton height={12} width={50} borderRadius={5} mb={19} />
|
||||||
|
<Stack flexDirection="row" alignItems="center" mb={16}>
|
||||||
|
<Skeleton height={24} width={24} mr={8} />
|
||||||
|
<Skeleton
|
||||||
|
height={12}
|
||||||
|
width={80}
|
||||||
|
borderRadius={5}
|
||||||
|
variant="secondary"
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
<Stack flexDirection="row" alignItems="center" mb={16}>
|
||||||
|
<Skeleton height={24} width={24} mr={8} />
|
||||||
|
<Skeleton
|
||||||
|
height={12}
|
||||||
|
width={100}
|
||||||
|
borderRadius={5}
|
||||||
|
variant="secondary"
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
<Stack flexDirection="row" alignItems="center" mb={16}>
|
||||||
|
<Skeleton height={24} width={24} mr={8} />
|
||||||
|
<Skeleton
|
||||||
|
height={12}
|
||||||
|
width={70}
|
||||||
|
borderRadius={5}
|
||||||
|
variant="secondary"
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { SidebarSkeleton }
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { Skeleton } from './skeleton'
|
||||||
|
|
||||||
|
import type { Meta, StoryObj } from '@storybook/react'
|
||||||
|
|
||||||
|
const meta: Meta<typeof Skeleton> = {
|
||||||
|
component: Skeleton,
|
||||||
|
argTypes: {},
|
||||||
|
parameters: {
|
||||||
|
design: {
|
||||||
|
type: 'figma',
|
||||||
|
url: 'https://www.figma.com/file/1RN1MFwfSqA6jNFJBeNdEu/Posts-%26-Attachments-for-Web?t=1Xf5496ymHeazodw-0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof Skeleton>
|
||||||
|
|
||||||
|
export const Avatar: Story = {
|
||||||
|
name: 'Avatar',
|
||||||
|
args: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Text: Story = {
|
||||||
|
name: 'Text',
|
||||||
|
args: {
|
||||||
|
width: 249,
|
||||||
|
br: 6,
|
||||||
|
height: 8,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default meta
|
|
@ -0,0 +1,57 @@
|
||||||
|
import { Stack, useTheme } from '@tamagui/core'
|
||||||
|
|
||||||
|
import type { ColorTokens, StackProps } from '@tamagui/core'
|
||||||
|
|
||||||
|
type Props = StackProps & {
|
||||||
|
width?: number | string
|
||||||
|
height?: number | string
|
||||||
|
borderRadius?: number
|
||||||
|
variant?: 'primary' | 'secondary'
|
||||||
|
}
|
||||||
|
|
||||||
|
const skeletonColor: Record<NonNullable<Props['variant']>, ColorTokens> = {
|
||||||
|
primary: '$neutral-10',
|
||||||
|
secondary: '$neutral-20',
|
||||||
|
}
|
||||||
|
|
||||||
|
const Skeleton = (props: Props) => {
|
||||||
|
const {
|
||||||
|
width = 32,
|
||||||
|
height = 32,
|
||||||
|
borderRadius = 16,
|
||||||
|
variant = 'primary',
|
||||||
|
...rest
|
||||||
|
} = props
|
||||||
|
|
||||||
|
const theme = useTheme()
|
||||||
|
/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */
|
||||||
|
// @ts-ignore
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const color = theme[skeletonColor[variant]]?.val
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack
|
||||||
|
height={height}
|
||||||
|
maxWidth={width}
|
||||||
|
width="100%"
|
||||||
|
borderRadius={borderRadius}
|
||||||
|
overflow="hidden"
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
maxWidth: width,
|
||||||
|
width: '100%',
|
||||||
|
height,
|
||||||
|
borderRadius,
|
||||||
|
background: `linear-gradient(-90deg, ${color}, #FCFCFC, ${color})`,
|
||||||
|
backgroundSize: '400% 400%',
|
||||||
|
animation: 'gradient 1.5s ease infinite',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Skeleton }
|
||||||
|
export type { Props as SkeletonProps }
|
|
@ -0,0 +1,54 @@
|
||||||
|
import { Stack } from '@tamagui/core'
|
||||||
|
import { BlurView } from 'expo-blur'
|
||||||
|
|
||||||
|
import { Skeleton } from './skeleton'
|
||||||
|
|
||||||
|
const TopbarSkeleton = () => {
|
||||||
|
return (
|
||||||
|
<BlurView intensity={40} style={{ zIndex: 100 }}>
|
||||||
|
<Stack flexDirection="column" width="100%" height={96}>
|
||||||
|
<Stack
|
||||||
|
flexDirection="row"
|
||||||
|
height={56}
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="space-between"
|
||||||
|
py={12}
|
||||||
|
px={16}
|
||||||
|
backgroundColor={'$blurBackground'}
|
||||||
|
borderBottomWidth={1}
|
||||||
|
borderColor={'$neutral-80-opa-10'}
|
||||||
|
width="100%"
|
||||||
|
>
|
||||||
|
<Stack
|
||||||
|
flexDirection="row"
|
||||||
|
alignItems="center"
|
||||||
|
flexWrap="wrap"
|
||||||
|
flexGrow={1}
|
||||||
|
flexShrink={1}
|
||||||
|
>
|
||||||
|
<Skeleton height={24} width={24} mr={8} />
|
||||||
|
<Skeleton
|
||||||
|
height={16}
|
||||||
|
width={92}
|
||||||
|
borderRadius={5}
|
||||||
|
variant="secondary"
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
<Stack
|
||||||
|
space={12}
|
||||||
|
flexDirection="row"
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="flex-end"
|
||||||
|
flexGrow={1}
|
||||||
|
flexShrink={1}
|
||||||
|
>
|
||||||
|
<Skeleton height={32} width={32} borderRadius={10} />
|
||||||
|
<Skeleton height={32} width={32} borderRadius={10} />
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</BlurView>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { TopbarSkeleton }
|
|
@ -35,6 +35,13 @@ export const Default: Story = {
|
||||||
args: {},
|
args: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const isLoading: Story = {
|
||||||
|
args: {
|
||||||
|
...Default.args,
|
||||||
|
isLoading: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
export const WithMembersSelected: Story = {
|
export const WithMembersSelected: Story = {
|
||||||
args: {
|
args: {
|
||||||
...Default.args,
|
...Default.args,
|
||||||
|
|
|
@ -16,6 +16,7 @@ import { BlurView } from 'expo-blur'
|
||||||
import { DropdownMenu } from '../dropdown-menu'
|
import { DropdownMenu } from '../dropdown-menu'
|
||||||
import { IconButton } from '../icon-button'
|
import { IconButton } from '../icon-button'
|
||||||
import { PinnedMessage } from '../pinned-message'
|
import { PinnedMessage } from '../pinned-message'
|
||||||
|
import { TopbarSkeleton } from '../skeleton/topbar-skeleton'
|
||||||
import { Text } from '../text'
|
import { Text } from '../text'
|
||||||
|
|
||||||
import type { Channel } from '../sidebar/mock-data'
|
import type { Channel } from '../sidebar/mock-data'
|
||||||
|
@ -41,13 +42,19 @@ type Props = {
|
||||||
goBack?: () => void
|
goBack?: () => void
|
||||||
channel: Channel
|
channel: Channel
|
||||||
blur?: boolean
|
blur?: boolean
|
||||||
|
isLoading?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const Topbar = (props: Props) => {
|
const Topbar = (props: Props) => {
|
||||||
const { showMembers, onMembersPress, goBack, blur, channel } = props
|
const { showMembers, onMembersPress, goBack, blur, channel, isLoading } =
|
||||||
|
props
|
||||||
|
|
||||||
const { title, description, emoji } = channel
|
const { title, description, emoji } = channel
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <TopbarSkeleton />
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BlurView intensity={40} style={{ zIndex: 100 }}>
|
<BlurView intensity={40} style={{ zIndex: 100 }}>
|
||||||
<Stack flexDirection="column" width="100%" height={96}>
|
<Stack flexDirection="column" width="100%" height={96}>
|
||||||
|
|
|
@ -28,6 +28,10 @@ export type MapVariant<
|
||||||
[key in V[K] & string]: ColorTokens
|
[key in V[K] & string]: ColorTokens
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type MapColorToken<V> = {
|
||||||
|
[key in V & string]: ColorTokens
|
||||||
|
}
|
||||||
|
|
||||||
export type GetVariants<A extends TamaguiComponent> = Required<
|
export type GetVariants<A extends TamaguiComponent> = Required<
|
||||||
GetStyledVariants<A>
|
GetStyledVariants<A>
|
||||||
>
|
>
|
||||||
|
|
|
@ -13232,7 +13232,6 @@ node-fetch-native@^1.0.1:
|
||||||
|
|
||||||
node-fetch@2.6.7, node-fetch@^2.2.0, node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.7:
|
node-fetch@2.6.7, node-fetch@^2.2.0, node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.7:
|
||||||
version "2.6.7"
|
version "2.6.7"
|
||||||
uid "1b5d62978f2ed07b99444f64f0df39f960a6d34d"
|
|
||||||
resolved "https://registry.npmjs.org/@achingbrain/node-fetch/-/node-fetch-2.6.7.tgz#1b5d62978f2ed07b99444f64f0df39f960a6d34d"
|
resolved "https://registry.npmjs.org/@achingbrain/node-fetch/-/node-fetch-2.6.7.tgz#1b5d62978f2ed07b99444f64f0df39f960a6d34d"
|
||||||
|
|
||||||
node-forge@^1.1.0, node-forge@^1.2.1, node-forge@^1.3.1:
|
node-forge@^1.1.0, node-forge@^1.2.1, node-forge@^1.3.1:
|
||||||
|
|
Loading…
Reference in New Issue