mirror of
https://github.com/status-im/status-web-archive.git
synced 2025-02-17 18:16:27 +00:00
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
|
||||
})
|
42
apps/vite/src/components/topbar.tsx
Normal file
42
apps/vite/src/components/topbar.tsx
Normal file
@ -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
|
87
packages/components/src/chat-message/chat-message.tsx
Normal file
87
packages/components/src/chat-message/chat-message.tsx
Normal file
@ -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 }
|
15
packages/components/src/chat-message/components/actions.tsx
Normal file
15
packages/components/src/chat-message/components/actions.tsx
Normal file
@ -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>
|
||||
)
|
||||
}
|
26
packages/components/src/chat-message/index.tsx
Normal file
26
packages/components/src/chat-message/index.tsx
Normal file
@ -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" />
|
||||
</>
|
||||
)
|
||||
}
|
32
packages/components/src/divider/divider.tsx
Normal file
32
packages/components/src/divider/divider.tsx
Normal file
@ -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,
|
||||
})
|
1
packages/components/src/divider/index.tsx
Normal file
1
packages/components/src/divider/index.tsx
Normal file
@ -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
|
22
packages/components/src/icon-button/icon-button.stories.tsx
Normal file
22
packages/components/src/icon-button/icon-button.stories.tsx
Normal file
@ -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
|
72
packages/components/src/icon-button/icon-button.tsx
Normal file
72
packages/components/src/icon-button/icon-button.tsx
Normal file
@ -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 }
|
1
packages/components/src/icon-button/index.tsx
Normal file
1
packages/components/src/icon-button/index.tsx
Normal file
@ -0,0 +1 @@
|
||||
export { type IconButtonProps, IconButton } from './icon-button'
|
48
packages/components/src/icons/icons.stories.tsx
Normal file
48
packages/components/src/icons/icons.stories.tsx
Normal file
@ -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,
|
||||
|
60
packages/components/src/typography/typography.stories.tsx
Normal file
60
packages/components/src/typography/typography.stories.tsx
Normal file
@ -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: {
|
||||
|
28
packages/icons/package.json
Normal file
28
packages/icons/package.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
4
packages/icons/src/index.tsx
Normal file
4
packages/icons/src/index.tsx
Normal file
@ -0,0 +1,4 @@
|
||||
export { LockedIcon } from './lock-icon'
|
||||
export { MembersIcon } from './members-icon'
|
||||
export { OptionsIcon } from './options-icon'
|
||||
export type { IconProps } from './types'
|
24
packages/icons/src/lock-icon.tsx
Normal file
24
packages/icons/src/lock-icon.tsx
Normal file
@ -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>
|
||||
)
|
||||
}
|
29
packages/icons/src/members-icon.tsx
Normal file
29
packages/icons/src/members-icon.tsx
Normal file
@ -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>
|
||||
)
|
||||
}
|
20
packages/icons/src/options-icon.tsx
Normal file
20
packages/icons/src/options-icon.tsx
Normal file
@ -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>
|
||||
)
|
||||
}
|
9
packages/icons/src/types.ts
Normal file
9
packages/icons/src/types.ts
Normal file
@ -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
|
||||
}
|
9
packages/icons/tsconfig.json
Normal file
9
packages/icons/tsconfig.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"include": ["src"],
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"declarationDir": "dist/types",
|
||||
"resolveJsonModule": true
|
||||
}
|
||||
}
|
39
packages/icons/vite.config.ts
Normal file
39
packages/icons/vite.config.ts
Normal file
@ -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…
x
Reference in New Issue
Block a user