Merge pull request #332 from prichodko/next

Add chat messages and other improvements
This commit is contained in:
marcelines 2023-01-18 16:21:22 +00:00 committed by GitHub
commit c67b5da2f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 825 additions and 107 deletions

View File

@ -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>

View File

@ -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",

View File

@ -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>
)

View File

@ -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
})

View 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>
)
}

View File

@ -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;
}

View File

@ -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": {

View File

@ -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}

View File

@ -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 }

View File

@ -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

View 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 }

View 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>
)
}

View File

@ -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>
)
}

View 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" />
</>
)
}

View 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,
})

View File

@ -0,0 +1 @@
export * from './divider'

View File

@ -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

View 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

View 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 }

View File

@ -0,0 +1 @@
export { type IconButtonProps, IconButton } from './icon-button'

View 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

View File

@ -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)

View File

@ -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'

View File

@ -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 }

View File

@ -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,

View 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

View File

@ -60,6 +60,11 @@ export const Paragraph = styled(SizableText, {
lineHeight: 18,
letterSpacing: '-0.003em',
},
11: {
fontSize: 11,
lineHeight: 18,
letterSpacing: '-0.003em',
},
},
uppercase: {
true: {

View 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"
}
}

View 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'

View 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>
)
}

View 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>
)
}

View 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>
)
}

View 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
}

View File

@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.base.json",
"include": ["src"],
"compilerOptions": {
"outDir": "./dist",
"declarationDir": "dist/types",
"resolveJsonModule": true
}
}

View 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',
},
}
})