Merge pull request #332 from prichodko/next
Add chat messages and other improvements
This commit is contained in:
commit
c67b5da2f9
|
@ -7,8 +7,8 @@ import { useState } from 'react'
|
|||
import {
|
||||
Code,
|
||||
Heading,
|
||||
Image,
|
||||
Label,
|
||||
Messages,
|
||||
Paragraph,
|
||||
Shape,
|
||||
Sidebar,
|
||||
|
@ -38,13 +38,6 @@ export default function App() {
|
|||
<TamaguiProvider config={tamaguiConfig} defaultTheme={theme}>
|
||||
<SafeAreaView>
|
||||
<ScrollView>
|
||||
<Image
|
||||
source={{
|
||||
uri: 'https://images.unsplash.com/photo-1673537074513-e66435b69012?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=687&q=80',
|
||||
height: 200,
|
||||
width: '100%',
|
||||
}}
|
||||
/>
|
||||
<Sidebar
|
||||
name="Rarible"
|
||||
description="Multichain community-centric NFT marketplace. Create, buy and sell your NFTs."
|
||||
|
@ -55,6 +48,7 @@ export default function App() {
|
|||
justifyContent="center"
|
||||
alignItems="center"
|
||||
paddingTop={20}
|
||||
paddingHorizontal={12}
|
||||
width="100%"
|
||||
backgroundColor="$background"
|
||||
>
|
||||
|
@ -74,12 +68,15 @@ export default function App() {
|
|||
<Code marginBottom={12}>This is a code line</Code>
|
||||
<Paragraph fontWeight="400">0x213abc190 ... 121ah4a9e</Paragraph>
|
||||
<Shape marginVertical={20} />
|
||||
|
||||
<Paragraph>Theme selected - {theme}</Paragraph>
|
||||
<TouchableOpacity
|
||||
onPress={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
|
||||
>
|
||||
<Paragraph>Toogle theme</Paragraph>
|
||||
</TouchableOpacity>
|
||||
|
||||
<Messages />
|
||||
</Stack>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@status-im/components": "*",
|
||||
"@status-im/icons": "*",
|
||||
"@tamagui/core": "1.0.15",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
Code,
|
||||
Heading,
|
||||
Label,
|
||||
Messages,
|
||||
Paragraph,
|
||||
Shape,
|
||||
Sidebar,
|
||||
|
@ -12,11 +13,13 @@ import {
|
|||
import { Stack, TamaguiProvider } from '@tamagui/core'
|
||||
|
||||
import tamaguiConfig from '../tamagui.config'
|
||||
import { Topbar } from './components/topbar'
|
||||
|
||||
type ThemeVars = 'light' | 'dark'
|
||||
|
||||
function App() {
|
||||
const [theme, setTheme] = useState<ThemeVars>('light')
|
||||
const [showMembers, setShowMembers] = useState(false)
|
||||
|
||||
return (
|
||||
<TamaguiProvider config={tamaguiConfig} defaultTheme={theme}>
|
||||
|
@ -30,14 +33,13 @@ function App() {
|
|||
</div>
|
||||
|
||||
<main id="main">
|
||||
<Stack
|
||||
backgroundColor="$background"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
>
|
||||
<Paragraph weight="semibold">Topbar</Paragraph>
|
||||
</Stack>
|
||||
<div>
|
||||
<Topbar
|
||||
membersVisisble={showMembers}
|
||||
onMembersPress={() => setShowMembers(show => !show)}
|
||||
/>
|
||||
<div id="content">
|
||||
<Messages />
|
||||
|
||||
<Stack width="100%" height="100%" backgroundColor="$background">
|
||||
<Stack
|
||||
flexDirection="column"
|
||||
|
@ -77,6 +79,8 @@ function App() {
|
|||
<Paragraph weight="semibold">Composer</Paragraph>
|
||||
</Stack>
|
||||
</main>
|
||||
|
||||
{showMembers && <div id="members">members</div>}
|
||||
</div>
|
||||
</TamaguiProvider>
|
||||
)
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
import { Stack, styled } from '@tamagui/core'
|
||||
|
||||
export const Circle = styled(Stack, {
|
||||
// access your tokens and theme values easily with $ props
|
||||
|
||||
backgroundColor: 'red',
|
||||
borderRadius: '100%',
|
||||
width: 100,
|
||||
height: 100
|
||||
// borderRadius: '$4',
|
||||
// // media and pseudo styles - this would take 15+ lines of brittle JS in RN
|
||||
|
||||
// $gtSm: {
|
||||
// pressStyle: {
|
||||
// borderRadius: '$6'
|
||||
// }
|
||||
// },
|
||||
// variants: {
|
||||
// // define variants <Circle pin="top" />
|
||||
|
||||
// // these will flatten, even when nesting multiple styled() calls
|
||||
|
||||
// pin: {
|
||||
// top: {
|
||||
// position: 'absolute',
|
||||
|
||||
// top: 0
|
||||
// }
|
||||
// },
|
||||
// size: {
|
||||
// // functional variants give incredible power and save bundle size
|
||||
|
||||
// '...size': (size, { tokens }) => {
|
||||
// return {
|
||||
// width: tokens.size[size] ?? size,
|
||||
|
||||
// height: tokens.size[size] ?? size
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// } as const
|
||||
})
|
|
@ -0,0 +1,42 @@
|
|||
import { Divider, IconButton, Paragraph } from '@status-im/components'
|
||||
import { LockedIcon, MembersIcon, OptionsIcon } from '@status-im/icons'
|
||||
import { Stack } from '@tamagui/core'
|
||||
|
||||
type Props = {
|
||||
membersVisisble: boolean
|
||||
onMembersPress: () => void
|
||||
}
|
||||
|
||||
export const Topbar = (props: Props) => {
|
||||
const { membersVisisble, onMembersPress } = props
|
||||
|
||||
return (
|
||||
<Stack
|
||||
flexDirection="row"
|
||||
height={56}
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
padding={16}
|
||||
>
|
||||
<Stack flexDirection="row" alignItems="center">
|
||||
<Paragraph weight="semibold" marginRight={4}>
|
||||
# random
|
||||
</Paragraph>
|
||||
<LockedIcon color="rgba(27, 39, 61, 0.4)" size={16} />
|
||||
<Divider height={16} />
|
||||
<Paragraph weight="medium" color="$neutral-80-opa-50" variant="smaller">
|
||||
Share random funny stuff with the community. Play nice.
|
||||
</Paragraph>
|
||||
</Stack>
|
||||
|
||||
<Stack space={12} flexDirection="row">
|
||||
<IconButton
|
||||
icon={<MembersIcon />}
|
||||
selected={membersVisisble}
|
||||
onPress={onMembersPress}
|
||||
/>
|
||||
<IconButton icon={<OptionsIcon />} />
|
||||
</Stack>
|
||||
</Stack>
|
||||
)
|
||||
}
|
|
@ -8,15 +8,18 @@ body,
|
|||
#app {
|
||||
height: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 352px 1fr;
|
||||
grid-template-columns: 352px 1fr auto;
|
||||
}
|
||||
|
||||
#main {
|
||||
display: grid;
|
||||
grid-template-rows: 56px 1fr 100px;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
#main,
|
||||
#sidebar,
|
||||
#members,
|
||||
#main > div {
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
@ -25,3 +28,13 @@ body,
|
|||
overflow: auto;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
#content {
|
||||
overflow: auto;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
#members {
|
||||
width: 352px;
|
||||
color: #000;
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
"@tamagui/react-native-media-driver": "1.0.15",
|
||||
"@tamagui/shorthands": "1.0.15",
|
||||
"@tamagui/theme-base": "1.0.15",
|
||||
"@status-im/icons": "*",
|
||||
"tamagui": "1.0.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
|
||||
import { Stack, styled, Text } from '@tamagui/core'
|
||||
import { Stack, styled, Text, Unspaced } from '@tamagui/core'
|
||||
|
||||
import { Image } from '../image'
|
||||
|
||||
|
@ -66,6 +66,66 @@ const Base = styled(Stack, {
|
|||
} as const,
|
||||
})
|
||||
|
||||
const Indicator = styled(Stack, {
|
||||
name: 'Indicator',
|
||||
|
||||
position: 'absolute',
|
||||
width: 8,
|
||||
height: 8,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
zIndex: 2,
|
||||
// borderWidth: 2,
|
||||
// borderColor: 'rgba(255,255,0,1.0)',
|
||||
|
||||
variants: {
|
||||
size: {
|
||||
56: {
|
||||
width: 10,
|
||||
height: 10,
|
||||
borderRadius: 10 / 2,
|
||||
},
|
||||
// FIXME: use catch all variant
|
||||
52: {
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: 8 / 2,
|
||||
},
|
||||
48: {
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: 8 / 2,
|
||||
},
|
||||
32: {
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: 8 / 2,
|
||||
},
|
||||
20: {
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: 8 / 2,
|
||||
},
|
||||
},
|
||||
|
||||
state: {
|
||||
online: {
|
||||
backgroundColor: '$success-50',
|
||||
},
|
||||
offline: {
|
||||
backgroundColor: '$neutral-40',
|
||||
},
|
||||
},
|
||||
|
||||
shape: {
|
||||
circle: {},
|
||||
rounded: {
|
||||
borderRadius: 16,
|
||||
},
|
||||
},
|
||||
} as const,
|
||||
})
|
||||
|
||||
const Fallback = styled(Text, {
|
||||
name: 'AvatarFallback',
|
||||
})
|
||||
|
@ -83,7 +143,7 @@ interface Props {
|
|||
type ImageLoadingStatus = 'idle' | 'loading' | 'loaded' | 'error'
|
||||
|
||||
const Avatar = (props: Props) => {
|
||||
const { src, size, shape = 'circle', withOutline } = props
|
||||
const { src, size, shape = 'circle', withOutline, indicator } = props
|
||||
|
||||
const [status, setStatus] = useState<ImageLoadingStatus>('idle')
|
||||
|
||||
|
@ -93,13 +153,21 @@ const Avatar = (props: Props) => {
|
|||
|
||||
return (
|
||||
<Base size={size} shape={shape} withOutline={withOutline}>
|
||||
<Image
|
||||
src={src}
|
||||
width={size}
|
||||
height={size}
|
||||
onLoad={() => setStatus('loaded')}
|
||||
onError={() => setStatus('error')}
|
||||
/>
|
||||
{indicator && (
|
||||
<Unspaced>
|
||||
<Indicator size={size} state={indicator} />
|
||||
</Unspaced>
|
||||
)}
|
||||
<Stack borderRadius={28} overflow="hidden">
|
||||
<Image
|
||||
src={src}
|
||||
width={size}
|
||||
height={size}
|
||||
onLoad={() => setStatus('loaded')}
|
||||
onError={() => setStatus('error')}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
{status === 'error' && (
|
||||
<Fallback
|
||||
width={size}
|
||||
|
|
|
@ -1,16 +1,23 @@
|
|||
import { Stack, styled, Text } from '@tamagui/core'
|
||||
import { Stack, styled } from '@tamagui/core'
|
||||
|
||||
import { Paragraph } from '../typography'
|
||||
|
||||
import type { GetProps } from '@tamagui/core'
|
||||
// import { Pressable } from 'react-native'
|
||||
|
||||
const Base = styled(Stack, {
|
||||
// tag: 'button',
|
||||
name: 'Button',
|
||||
accessibilityRole: 'button',
|
||||
|
||||
cursor: 'pointer',
|
||||
borderRadius: 12,
|
||||
display: 'inline-flex',
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 10,
|
||||
paddingTop: 7,
|
||||
paddingBottom: 9,
|
||||
animation: 'fast',
|
||||
userSelect: 'none',
|
||||
|
||||
variants: {
|
||||
type: {
|
||||
primary: {
|
||||
|
@ -27,12 +34,6 @@ const Base = styled(Stack, {
|
|||
} as const,
|
||||
})
|
||||
|
||||
const ButtonText = styled(Text, {
|
||||
fontFamily: '$inter',
|
||||
textAlign: 'center',
|
||||
color: '$white-100',
|
||||
})
|
||||
|
||||
type BaseProps = GetProps<typeof Base>
|
||||
|
||||
type Props = {
|
||||
|
@ -46,9 +47,12 @@ const Button = (props: Props) => {
|
|||
|
||||
return (
|
||||
<Base {...rest} type={type} onPress={onPress}>
|
||||
<ButtonText>{children}</ButtonText>
|
||||
<Paragraph color="$white-100" textAlign="center" weight="medium">
|
||||
{children}
|
||||
</Paragraph>
|
||||
</Base>
|
||||
)
|
||||
}
|
||||
|
||||
export { Button }
|
||||
export type { Props as ButtonProps }
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
import { ChatMessage } from './chat-message'
|
||||
|
||||
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 ChatMessage> = {
|
||||
component: ChatMessage,
|
||||
argTypes: {},
|
||||
parameters: {
|
||||
design: {
|
||||
type: 'figma',
|
||||
url: 'https://www.figma.com/file/IBmFKgGL1B4GzqD8LQTw6n/Design-System-for-Web?node-id=611%3A36006&t=Gyy71OAckl3b2TWj-4',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
type Story = StoryObj<typeof ChatMessage>
|
||||
|
||||
// More on writing stories with args: https://storybook.js.org/docs/7.0/react/writing-stories/args
|
||||
export const Simple: Story = {
|
||||
args: {
|
||||
text: 'This is a simple message.',
|
||||
},
|
||||
}
|
||||
|
||||
export const SimpleLongText: Story = {
|
||||
args: {
|
||||
text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
|
||||
},
|
||||
}
|
||||
|
||||
export const SimpleWithReactions: Story = {
|
||||
name: 'Simple with reactions',
|
||||
args: {
|
||||
text: 'This is a simple message.',
|
||||
reactions: ['thumb'],
|
||||
},
|
||||
}
|
||||
|
||||
export const Image: Story = {
|
||||
args: {
|
||||
images: [
|
||||
{
|
||||
url: 'https://images.unsplash.com/photo-1673831792265-68b44126c999?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=866&q=80',
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export default meta
|
|
@ -0,0 +1,87 @@
|
|||
import React from 'react'
|
||||
|
||||
import { Stack, Unspaced, XStack, YStack } from 'tamagui'
|
||||
|
||||
import { Avatar } from '../avatar'
|
||||
import { Image } from '../image'
|
||||
import { Paragraph } from '../typography'
|
||||
import { Actions } from './components/actions'
|
||||
import { Reactions } from './components/reactions'
|
||||
|
||||
interface Props {
|
||||
text?: React.ReactNode
|
||||
images?: Array<{ url: string }>
|
||||
reactions?: []
|
||||
}
|
||||
|
||||
const ChatMessage = (props: Props) => {
|
||||
const { text, images, reactions } = props
|
||||
|
||||
const [hovered, setHovered] = React.useState(false)
|
||||
|
||||
return (
|
||||
<XStack
|
||||
space={10}
|
||||
position="relative"
|
||||
alignItems="flex-start"
|
||||
paddingHorizontal={8}
|
||||
paddingVertical={12}
|
||||
borderRadius={16}
|
||||
hoverStyle={{
|
||||
backgroundColor: '$neutral-5',
|
||||
}}
|
||||
onHoverIn={() => setHovered(true)}
|
||||
onHoverOut={() => setHovered(false)}
|
||||
>
|
||||
{hovered && (
|
||||
<Unspaced>
|
||||
<Actions />
|
||||
</Unspaced>
|
||||
)}
|
||||
|
||||
<Avatar
|
||||
size={32}
|
||||
src="https://images.unsplash.com/photo-1524638431109-93d95c968f03?crop=entropy&cs=tinysrgb&fit=crop&fm=jpg&h=500&ixid=MnwxfDB8MXxyYW5kb218MHx8Z2lybHx8fHx8fDE2NzM4ODQ0NzU&ixlib=rb-4.0.3&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=500"
|
||||
indicator="online"
|
||||
/>
|
||||
|
||||
<YStack flex={1}>
|
||||
<XStack space={8} alignItems="center">
|
||||
<Paragraph weight="semibold" color="$neutral-100">
|
||||
Alisher Yakupov
|
||||
</Paragraph>
|
||||
<Paragraph fontFamily="$mono" color="$neutral-50" fontSize={11}>
|
||||
zQ3...9d4Gs0
|
||||
</Paragraph>
|
||||
<Paragraph color="$neutral-50" variant={11}>
|
||||
09:30
|
||||
</Paragraph>
|
||||
</XStack>
|
||||
|
||||
{text && (
|
||||
<Paragraph flexGrow={0} weight="regular" color="$neutral-100">
|
||||
{text}
|
||||
</Paragraph>
|
||||
)}
|
||||
|
||||
{images?.map(image => (
|
||||
<Stack
|
||||
key={image.url}
|
||||
borderRadius={12}
|
||||
overflow="hidden"
|
||||
marginTop={6}
|
||||
$gtMd={{
|
||||
maxWidth: 320,
|
||||
}}
|
||||
>
|
||||
<Image src={image.url} width="full" height={320} />
|
||||
</Stack>
|
||||
))}
|
||||
|
||||
{reactions && <Reactions />}
|
||||
</YStack>
|
||||
</XStack>
|
||||
)
|
||||
}
|
||||
|
||||
export { ChatMessage }
|
|
@ -0,0 +1,15 @@
|
|||
import { Stack } from 'tamagui'
|
||||
|
||||
import { Paragraph } from '../../typography'
|
||||
|
||||
interface Props {
|
||||
onClick: VoidFunction
|
||||
}
|
||||
|
||||
export const Actions = (_props: Props) => {
|
||||
return (
|
||||
<Stack position="absolute" top={0} right={0}>
|
||||
<Paragraph>actions</Paragraph>
|
||||
</Stack>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
import { Pressable } from 'react-native'
|
||||
import { Stack, XStack, YStack } from 'tamagui'
|
||||
|
||||
interface Props {}
|
||||
|
||||
const ReactionButton = ({ type }) => {
|
||||
return (
|
||||
<Stack
|
||||
padding={3}
|
||||
borderWidth={1}
|
||||
borderColor="$neutral-100"
|
||||
borderRadius={2}
|
||||
>
|
||||
{type}
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
export const Reactions = (props: Props) => {
|
||||
const {} = props
|
||||
|
||||
return (
|
||||
<XStack>
|
||||
<ReactionButton type="like" />
|
||||
<ReactionButton type="+1" />
|
||||
<ReactionButton type="-1" />
|
||||
<ReactionButton type="sad" />
|
||||
<ReactionButton type="angry" />
|
||||
</XStack>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import { ChatMessage } from './chat-message'
|
||||
|
||||
export * from './chat-message'
|
||||
|
||||
export const Messages = () => {
|
||||
return (
|
||||
<>
|
||||
<ChatMessage text="fsdjkf kasldjf ksdlfjksdlfj asdklfj sdkljf" />
|
||||
<ChatMessage text="fsdjkf kasldjf ksdlfjksdlfj asdklfj sdkljf" />
|
||||
<ChatMessage text="fsdjkf kasldjf ksdlfjksdlfj asdklfj sdkljf" />
|
||||
<ChatMessage
|
||||
images={[
|
||||
{
|
||||
url: 'https://images.unsplash.com/photo-1673433107234-14d1a4424658?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1470&q=80',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<ChatMessage text="fsdjkf kasldjf ksdlfjksdlfj asdklfj sdkljf" />
|
||||
<ChatMessage text="fsdjkf kasldjf ksdlfjksdlfj asdklfj sdkljf" />
|
||||
<ChatMessage text="fsdjkf kasldjf ksdlfjksdlfj asdklfj sdkljf" />
|
||||
<ChatMessage text="fsdjkf kasldjf ksdlfjksdlfj asdklfj sdkljf" />
|
||||
<ChatMessage text="fsdjkf kasldjf ksdlfjksdlfj asdklfj sdkljf" />
|
||||
<ChatMessage text="fsdjkf kasldjf ksdlfjksdlfj asdklfj sdkljf" />
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
import { Stack, styled } from '@tamagui/core'
|
||||
|
||||
export const Divider = styled(Stack, {
|
||||
name: 'Divider',
|
||||
backgroundColor: '$neutral-10',
|
||||
flexShrink: 0,
|
||||
// borderWidth: 0,
|
||||
flex: 1,
|
||||
height: '100%',
|
||||
// maxHeight: 0,
|
||||
width: 1,
|
||||
marginHorizontal: 12,
|
||||
// y: -0.5,
|
||||
|
||||
variants: {
|
||||
// vertical: {
|
||||
// true: {
|
||||
// y: 0,
|
||||
// x: -0.5,
|
||||
// height: isWeb ? 'initial' : 'auto',
|
||||
// // maxHeight auto WILL BE passed to style attribute, but for some reason not used?
|
||||
// // almost seems like a react or browser bug, but for now `initial` works
|
||||
// // also, it doesn't happen for `height`, but for consistency using the same values
|
||||
// maxHeight: isWeb ? 'initial' : 'auto',
|
||||
// width: 0,
|
||||
// maxWidth: 0,
|
||||
// borderBottomWidth: 0,
|
||||
// borderRightWidth: 1,
|
||||
// },
|
||||
// },
|
||||
} as const,
|
||||
})
|
|
@ -0,0 +1 @@
|
|||
export * from './divider'
|
|
@ -1,11 +0,0 @@
|
|||
// eslint-disable-next-line eslint-comments/disable-enable-pair
|
||||
/* eslint-disable @typescript-eslint/no-empty-interface */
|
||||
import { config } from './tamagui.config'
|
||||
|
||||
export type Conf = typeof config
|
||||
|
||||
declare module '@tamagui/core' {
|
||||
interface TamaguiCustomConfig extends Conf {}
|
||||
}
|
||||
|
||||
export default config
|
|
@ -0,0 +1,22 @@
|
|||
import { OptionsIcon } from '@status-im/icons'
|
||||
|
||||
import { IconButton } from './icon-button'
|
||||
|
||||
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 IconButton> = {
|
||||
component: IconButton,
|
||||
argTypes: {},
|
||||
}
|
||||
|
||||
type Story = StoryObj<typeof IconButton>
|
||||
|
||||
// More on writing stories with args: https://storybook.js.org/docs/7.0/react/writing-stories/args
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
icon: <OptionsIcon />,
|
||||
},
|
||||
}
|
||||
|
||||
export default meta
|
|
@ -0,0 +1,72 @@
|
|||
import { Stack, styled, Text } from '@tamagui/core'
|
||||
|
||||
// import { Pressable } from 'react-native'
|
||||
import type React from 'react'
|
||||
|
||||
const Base = styled(Stack, {
|
||||
name: 'IconButton',
|
||||
accessibilityRole: 'button',
|
||||
|
||||
cursor: 'pointer',
|
||||
userSelect: 'none',
|
||||
borderRadius: 10,
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
|
||||
width: 31,
|
||||
height: 31,
|
||||
borderWidth: 1,
|
||||
backgroundColor: '$neutral-10',
|
||||
borderColor: '$neutral-10',
|
||||
|
||||
hoverStyle: {
|
||||
backgroundColor: '$neutral-20',
|
||||
},
|
||||
|
||||
variants: {
|
||||
selected: {
|
||||
true: {
|
||||
backgroundColor: '$neutral-30',
|
||||
borderColor: '$neutral-30',
|
||||
},
|
||||
},
|
||||
} as const,
|
||||
})
|
||||
|
||||
const Icon = styled(Text, {
|
||||
color: '$neutral-50',
|
||||
width: 20,
|
||||
height: 20,
|
||||
|
||||
hoverStyle: {
|
||||
color: '$neutral-100',
|
||||
},
|
||||
|
||||
variants: {
|
||||
selected: {
|
||||
true: {
|
||||
color: '$neutral-100',
|
||||
},
|
||||
},
|
||||
} as const,
|
||||
})
|
||||
|
||||
interface Props {
|
||||
icon: React.ReactElement
|
||||
onPress?: () => void
|
||||
selected?: boolean
|
||||
}
|
||||
|
||||
const IconButton = (props: Props) => {
|
||||
const { icon, selected, onPress } = props
|
||||
|
||||
return (
|
||||
<Base selected={selected} onPress={onPress}>
|
||||
<Icon selected={selected}>{icon}</Icon>
|
||||
</Base>
|
||||
)
|
||||
}
|
||||
|
||||
export { IconButton }
|
||||
export type { Props as IconButtonProps }
|
|
@ -0,0 +1 @@
|
|||
export { type IconButtonProps, IconButton } from './icon-button'
|
|
@ -0,0 +1,48 @@
|
|||
import * as icons from '@status-im/icons'
|
||||
|
||||
import { Text } from '../typography'
|
||||
|
||||
import type { IconProps } from '@status-im/icons'
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
import type React from 'react'
|
||||
|
||||
// More on how to set up stories at: https://storybook.js.org/docs/7.0/react/writing-stories/introduction
|
||||
const meta: Meta = {
|
||||
title: 'icons',
|
||||
// component: Button,
|
||||
argTypes: {},
|
||||
}
|
||||
|
||||
type Story = StoryObj
|
||||
|
||||
function unpascal(str: string) {
|
||||
return str.replace(/([A-Z])/g, ' $1').trim()
|
||||
}
|
||||
|
||||
// More on writing stories with args: https://storybook.js.org/docs/7.0/react/writing-stories/args
|
||||
export const All: Story = {
|
||||
args: {},
|
||||
render: () => {
|
||||
return (
|
||||
<div style={{ display: 'grid', gap: 12 }}>
|
||||
{Object.keys(icons).map(name => {
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line import/namespace
|
||||
const Icon = icons[name] as React.FunctionComponent<IconProps>
|
||||
|
||||
return (
|
||||
<div
|
||||
key={name}
|
||||
style={{ display: 'flex', flexDirection: 'column' }}
|
||||
>
|
||||
<Icon color="black" />
|
||||
<Text>{unpascal(name)}</Text>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
export default meta
|
|
@ -1,4 +1,4 @@
|
|||
import React, { forwardRef } from 'react'
|
||||
import { forwardRef } from 'react'
|
||||
|
||||
import { isWeb, setupReactNative, styled } from '@tamagui/core'
|
||||
import { Image as RNImage } from 'react-native'
|
||||
|
@ -16,32 +16,48 @@ setupReactNative({
|
|||
const Base = styled(RNImage, {
|
||||
name: 'Image',
|
||||
position: 'relative',
|
||||
source: { uri: '' },
|
||||
zIndex: 1,
|
||||
source: {
|
||||
uri: '',
|
||||
},
|
||||
})
|
||||
|
||||
type InputProps = GetProps<typeof Image>
|
||||
|
||||
// type W = InputProps['width']
|
||||
|
||||
interface Props {
|
||||
src: string
|
||||
width: number
|
||||
height: number
|
||||
width: number | 'full'
|
||||
height?: number
|
||||
aspectRatio?: number
|
||||
// onLoad?: InputProps['onLoad']
|
||||
// onError?: InputProps['onError']
|
||||
}
|
||||
|
||||
const Image = (props: Props, ref: Ref<HTMLImageElement>) => {
|
||||
const { src } = props
|
||||
const { src, aspectRatio, ...rest } = props
|
||||
|
||||
const source =
|
||||
typeof src === 'string'
|
||||
? {
|
||||
uri: src,
|
||||
...(isWeb && { width: props.width, height: props.height }),
|
||||
}
|
||||
: src
|
||||
const width = props.width === 'full' ? '100%' : props.width
|
||||
const height = aspectRatio ? undefined : props.height
|
||||
|
||||
return <Base source={source} ref={ref} />
|
||||
const source = {
|
||||
uri: src,
|
||||
...(isWeb && { width, height }),
|
||||
}
|
||||
|
||||
return (
|
||||
<Base
|
||||
{...rest}
|
||||
ref={ref}
|
||||
source={source}
|
||||
width={width}
|
||||
height={height}
|
||||
style={{
|
||||
aspectRatio,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
// focusableInputHOC(Image)
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
export * from './button'
|
||||
export * from './chat-message'
|
||||
export * from './divider'
|
||||
export * from './icon-button'
|
||||
export * from './image'
|
||||
export * from './input'
|
||||
export * from './shape'
|
||||
|
|
|
@ -5,6 +5,7 @@ import { Stack } from '@tamagui/core'
|
|||
import { Accordion } from '../accordion/accordion'
|
||||
import { AccordionItem } from '../accordion/accordionItem'
|
||||
import { Avatar } from '../avatar'
|
||||
import { Button } from '../button'
|
||||
// import { Button } from '../button'
|
||||
import { Basketball, Collaboration, Fire, Peach, Play, Unicorn } from '../emoji'
|
||||
import { Group } from '../icon'
|
||||
|
@ -198,7 +199,7 @@ const COMMUNITIES_EXPAND_CONTROL = COMMUNITIES.reduce(
|
|||
{} as Record<string, boolean>[]
|
||||
)
|
||||
|
||||
const _Sidebar = (props: Props) => {
|
||||
const Sidebar = (props: Props) => {
|
||||
const { name, description, membersCount } = props
|
||||
const [isExpanded, setIsExpanded] = useState({
|
||||
...COMMUNITIES_EXPAND_CONTROL,
|
||||
|
@ -218,8 +219,8 @@ const _Sidebar = (props: Props) => {
|
|||
<Stack backgroundColor="$background">
|
||||
<Image
|
||||
src="https://images.unsplash.com/photo-1584475784921-d9dbfd9d17ca?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1740&q=80"
|
||||
width={352}
|
||||
height={136}
|
||||
width="full"
|
||||
aspectRatio={2.6}
|
||||
/>
|
||||
<Stack
|
||||
paddingBottom={16}
|
||||
|
@ -229,7 +230,7 @@ const _Sidebar = (props: Props) => {
|
|||
borderTopRightRadius={20}
|
||||
zIndex={10}
|
||||
>
|
||||
<Stack paddingHorizontal={16}>
|
||||
<Stack paddingHorizontal={16} paddingBottom={16}>
|
||||
<Stack marginTop={-32} marginBottom={12}>
|
||||
<Avatar
|
||||
withOutline
|
||||
|
@ -243,6 +244,8 @@ const _Sidebar = (props: Props) => {
|
|||
<Group color="$neutral-100" size={16} />
|
||||
<Paragraph ml={8}>{membersCount}</Paragraph>
|
||||
</Stack>
|
||||
|
||||
<Button>Join community</Button>
|
||||
</Stack>
|
||||
{COMMUNITIES.map(community => (
|
||||
<Accordion
|
||||
|
@ -275,4 +278,4 @@ const _Sidebar = (props: Props) => {
|
|||
)
|
||||
}
|
||||
|
||||
export const Sidebar = _Sidebar
|
||||
export { Sidebar }
|
||||
|
|
|
@ -7,6 +7,13 @@ import { animations } from './animations'
|
|||
import { themes } from './themes'
|
||||
import { tokens } from './tokens'
|
||||
|
||||
export type Conf = typeof config
|
||||
|
||||
declare module '@tamagui/core' {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
interface TamaguiCustomConfig extends Conf {}
|
||||
}
|
||||
|
||||
const interFont = createInterFont({
|
||||
size: {
|
||||
6: 11,
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
import { Stack } from '@tamagui/core'
|
||||
|
||||
import { Code, Heading, Paragraph } from '.'
|
||||
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'typography',
|
||||
argTypes: {},
|
||||
}
|
||||
|
||||
export const HeadingStory: StoryObj<typeof Heading> = {
|
||||
name: 'Heading',
|
||||
args: {
|
||||
children: 'The quick brown fox jumped over the lazy dog.',
|
||||
},
|
||||
render: args => (
|
||||
<Stack>
|
||||
<Heading {...args} weight="regular" />
|
||||
<Heading {...args} weight="medium" />
|
||||
<Heading {...args} weight="semibold" />
|
||||
<Heading {...args} weight="regular" heading="h2" />
|
||||
<Heading {...args} weight="medium" heading="h2" />
|
||||
<Heading {...args} weight="semibold" heading="h2" />
|
||||
</Stack>
|
||||
),
|
||||
}
|
||||
|
||||
export const TextStory: StoryObj<typeof Paragraph> = {
|
||||
name: 'Text',
|
||||
args: {
|
||||
children: 'The quick brown fox jumped over the lazy dog.',
|
||||
},
|
||||
render: args => (
|
||||
<Stack>
|
||||
<Paragraph {...args} weight="regular" />
|
||||
<Paragraph {...args} weight="medium" />
|
||||
<Paragraph {...args} weight="semibold" />
|
||||
<Paragraph {...args} weight="regular" variant="smaller" />
|
||||
<Paragraph {...args} weight="medium" variant="smaller" />
|
||||
<Paragraph {...args} weight="semibold" variant="smaller" />
|
||||
</Stack>
|
||||
),
|
||||
}
|
||||
|
||||
export const CodeStory: StoryObj<typeof Code> = {
|
||||
name: 'Code',
|
||||
args: {
|
||||
children: '// How to create variables:',
|
||||
},
|
||||
render: () => (
|
||||
<Stack>
|
||||
<Code weight="regular">
|
||||
The quick brown fox jumped over the lazy dog.
|
||||
</Code>
|
||||
</Stack>
|
||||
),
|
||||
}
|
||||
|
||||
export default meta
|
|
@ -60,6 +60,11 @@ export const Paragraph = styled(SizableText, {
|
|||
lineHeight: 18,
|
||||
letterSpacing: '-0.003em',
|
||||
},
|
||||
11: {
|
||||
fontSize: 11,
|
||||
lineHeight: 18,
|
||||
letterSpacing: '-0.003em',
|
||||
},
|
||||
},
|
||||
uppercase: {
|
||||
true: {
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"name": "@status-im/icons",
|
||||
"version": "0.0.1",
|
||||
"types": "src/index.tsx",
|
||||
"main": "src/index.tsx",
|
||||
"private": true,
|
||||
"files": [
|
||||
"types",
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "vite build --watch --mode development",
|
||||
"build": "vite build",
|
||||
"postbuild": "yarn typegen",
|
||||
"#test": "vitest",
|
||||
"typecheck": "tsc",
|
||||
"typegen": "tsc --noEmit false --emitDeclarationOnly || true",
|
||||
"lint": "eslint src",
|
||||
"format": "prettier --write src",
|
||||
"clean": "rm -rf dist node_modules .turbo"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.x || ^17.x || ^18.x"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^4.0.4"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
export { LockedIcon } from './lock-icon'
|
||||
export { MembersIcon } from './members-icon'
|
||||
export { OptionsIcon } from './options-icon'
|
||||
export type { IconProps } from './types'
|
|
@ -0,0 +1,24 @@
|
|||
import type { IconProps } from './types'
|
||||
|
||||
export function LockedIcon(props: IconProps) {
|
||||
const { color = 'currentColor', size = 20, ...rest } = props
|
||||
|
||||
// FIXME: not need to differentiate sizes in the designs
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...rest}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M8 .9h-.012c-.081 0-.137 0-.186.002a3 3 0 00-2.9 2.9c-.002.05-.002.105-.002.186v1.175a3.6 3.6 0 00-2.127 2.405c-.123.46-.123.998-.123 1.832v.2c0 .834 0 1.373.123 1.832a3.6 3.6 0 002.545 2.545c.46.123.998.123 1.832.123H8.85c.834 0 1.373 0 1.832-.123a3.6 3.6 0 002.545-2.545c.123-.46.123-.998.123-1.832v-.2c0-.834 0-1.373-.123-1.832A3.6 3.6 0 0011.1 5.163V3.988c0-.081 0-.137-.002-.186A3 3 0 008.012.9H8zm.75 5.2h-1.5c-.969 0-1.335.005-1.621.082a2.4 2.4 0 00-1.697 1.697c-.077.286-.082.652-.082 1.621s.005 1.335.082 1.621a2.4 2.4 0 001.697 1.697c.286.077.652.082 1.621.082h1.5c.969 0 1.335-.005 1.621-.082a2.4 2.4 0 001.697-1.697c.077-.286.082-.652.082-1.621s-.005-1.335-.082-1.621a2.4 2.4 0 00-1.697-1.697C10.085 6.105 9.72 6.1 8.75 6.1zm.288-1.2H9.9V4v-.159a1.8 1.8 0 00-1.741-1.74 5.726 5.726 0 00-.318 0 1.8 1.8 0 00-1.74 1.74V4.9H9.037zM7.4 8.25h1.2v2.5H7.4v-2.5z"
|
||||
fill={color}
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
import type { IconProps } from './types'
|
||||
|
||||
export function MembersIcon(props: IconProps) {
|
||||
const { color = 'currentColor', size = 20, ...rest } = props
|
||||
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...rest}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M6.15 6a1.85 1.85 0 113.7 0 1.85 1.85 0 01-3.7 0zM8 2.85a3.15 3.15 0 100 6.3 3.15 3.15 0 000-6.3zm-1.25 7.5a4.4 4.4 0 00-4.4 4.4c0 1.049.85 1.9 1.9 1.9h7.5a1.9 1.9 0 001.9-1.9 4.4 4.4 0 00-4.4-4.4h-2.5zm-3.1 4.4a3.1 3.1 0 013.1-3.1h2.5a3.1 3.1 0 013.1 3.1.6.6 0 01-.6.6h-7.5a.6.6 0 01-.6-.6z"
|
||||
fill={color}
|
||||
/>
|
||||
<path
|
||||
d="M13.5 11h.25a3.75 3.75 0 013.75 3.75v0c0 .69-.56 1.25-1.25 1.25H14.5M11.809 8c1.153 0 2.087-.895 2.087-2s-.934-2-2.087-2"
|
||||
stroke={color}
|
||||
strokeWidth={1.3}
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import type { IconProps } from './types'
|
||||
|
||||
export function OptionsIcon(props: IconProps) {
|
||||
const { color = 'currentColor', size = 20, ...rest } = props
|
||||
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...rest}
|
||||
>
|
||||
<circle cx={4.5} cy={10} r={1.5} fill={color} />
|
||||
<circle cx={10} cy={10} r={1.5} fill={color} />
|
||||
<circle cx={15.5} cy={10} r={1.5} fill={color} />
|
||||
</svg>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import type { SVGAttributes } from 'react'
|
||||
|
||||
export interface IconProps extends SVGAttributes<SVGElement> {
|
||||
children?: never
|
||||
width?: never
|
||||
height?: never
|
||||
color?: string
|
||||
size?: number
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"include": ["src"],
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"declarationDir": "dist/types",
|
||||
"resolveJsonModule": true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/// <reference types="vitest" />
|
||||
|
||||
import react from '@vitejs/plugin-react'
|
||||
import { defineConfig } from 'vite'
|
||||
|
||||
import { peerDependencies } from './package.json'
|
||||
|
||||
const external = [
|
||||
// ...Object.keys(dependencies || {}),
|
||||
...Object.keys(peerDependencies || {}),
|
||||
].map(name => new RegExp(`^${name}(/.*)?`))
|
||||
|
||||
export default defineConfig(({ mode }) => {
|
||||
return {
|
||||
build: {
|
||||
target: 'es2020',
|
||||
lib: {
|
||||
entry: './src/index.tsx',
|
||||
fileName: 'index',
|
||||
formats: ['es'],
|
||||
},
|
||||
sourcemap: true,
|
||||
emptyOutDir: mode === 'production',
|
||||
rollupOptions: {
|
||||
external,
|
||||
},
|
||||
},
|
||||
|
||||
plugins: [react()],
|
||||
|
||||
// plugins: [
|
||||
// react(),
|
||||
// ],
|
||||
|
||||
test: {
|
||||
environment: 'happy-dom',
|
||||
},
|
||||
}
|
||||
})
|
Loading…
Reference in New Issue