From 2bbaea1f36d8d8d066ea49a7ffe1a48459ef38ad Mon Sep 17 00:00:00 2001 From: marcelines Date: Wed, 18 Jan 2023 13:15:51 +0000 Subject: [PATCH] Add cross-platform sidebar (#331) * feat: add icon example and solve some types * fix: add accordion and emojis * fix: fixes animation issues and adds overall minor improvements * fix: more fixes and new emoji * fix: id from channel --- apps/mobile/App.tsx | 90 +++--- apps/vite/styles/app.css | 6 +- packages/components/package.json | 1 + .../components/src/accordion/accordion.tsx | 111 ++++++++ .../src/accordion/accordionItem.tsx | 115 ++++++++ packages/components/src/animations.native.ts | 18 ++ packages/components/src/animations.ts | 23 +- packages/components/src/avatar/avatar.tsx | 35 ++- packages/components/src/button/button.tsx | 17 +- packages/components/src/emoji/EmojiProps.ts | 9 + .../src/emoji/emojis/basketball.tsx | 44 +++ .../src/emoji/emojis/collaboration.tsx | 42 +++ packages/components/src/emoji/emojis/fire.tsx | 43 +++ .../components/src/emoji/emojis/peach.tsx | 57 ++++ packages/components/src/emoji/emojis/play.tsx | 46 ++++ .../components/src/emoji/emojis/unicorn.tsx | 57 ++++ packages/components/src/emoji/index.tsx | 6 + packages/components/src/emoji/themed.tsx | 27 ++ packages/components/src/icon/IconProps.ts | 18 ++ .../components/src/icon/icons/chevron.tsx | 27 ++ packages/components/src/icon/icons/group.tsx | 39 +++ packages/components/src/icon/icons/muted.tsx | 32 +++ packages/components/src/icon/index.tsx | 3 + packages/components/src/icon/themed.tsx | 15 + packages/components/src/input/input.tsx | 6 +- packages/components/src/sidebar/sidebar.tsx | 260 +++++++++++++++++- packages/components/src/tamagui.config.ts | 7 +- packages/components/src/themes.ts | 15 +- yarn.lock | 67 ++++- 29 files changed, 1119 insertions(+), 117 deletions(-) create mode 100644 packages/components/src/accordion/accordion.tsx create mode 100644 packages/components/src/accordion/accordionItem.tsx create mode 100644 packages/components/src/animations.native.ts create mode 100644 packages/components/src/emoji/EmojiProps.ts create mode 100644 packages/components/src/emoji/emojis/basketball.tsx create mode 100644 packages/components/src/emoji/emojis/collaboration.tsx create mode 100644 packages/components/src/emoji/emojis/fire.tsx create mode 100644 packages/components/src/emoji/emojis/peach.tsx create mode 100644 packages/components/src/emoji/emojis/play.tsx create mode 100644 packages/components/src/emoji/emojis/unicorn.tsx create mode 100644 packages/components/src/emoji/index.tsx create mode 100644 packages/components/src/emoji/themed.tsx create mode 100644 packages/components/src/icon/IconProps.ts create mode 100644 packages/components/src/icon/icons/chevron.tsx create mode 100644 packages/components/src/icon/icons/group.tsx create mode 100644 packages/components/src/icon/icons/muted.tsx create mode 100644 packages/components/src/icon/index.tsx create mode 100644 packages/components/src/icon/themed.tsx diff --git a/apps/mobile/App.tsx b/apps/mobile/App.tsx index f3c73f69..d600682a 100644 --- a/apps/mobile/App.tsx +++ b/apps/mobile/App.tsx @@ -15,7 +15,7 @@ import { } from '@status-im/components' import { Stack, TamaguiProvider } from '@tamagui/core' import { useFonts } from 'expo-font' -import { SafeAreaView, TouchableOpacity } from 'react-native' +import { SafeAreaView, ScrollView, TouchableOpacity } from 'react-native' import tamaguiConfig from './tamagui.config' @@ -37,51 +37,51 @@ export default function App() { return ( - - - - - Communities - - - This is an Heading 2 - - - Paragraph uppercased and bolded - - - This is a paragraph - - - This is a code line - 0x213abc190 ... 121ah4a9e - - - Theme selected - {theme} - setTheme(theme === 'dark' ? 'light' : 'dark')} + + + + - Toogle theme - - + + Communities + + + This is an Heading 2 + + + Paragraph uppercased and bolded + + + This is a paragraph + + + This is a code line + 0x213abc190 ... 121ah4a9e + + Theme selected - {theme} + setTheme(theme === 'dark' ? 'light' : 'dark')} + > + Toogle theme + + + ) diff --git a/apps/vite/styles/app.css b/apps/vite/styles/app.css index 49572f61..50e912be 100644 --- a/apps/vite/styles/app.css +++ b/apps/vite/styles/app.css @@ -16,8 +16,12 @@ body, grid-template-rows: 56px 1fr 100px; } -#sidebar, #main, #main > div { border: 1px solid rgba(0, 0, 0, 0.1); } + +#sidebar { + overflow: auto; + height: 100vh; +} diff --git a/packages/components/package.json b/packages/components/package.json index 3cade0b2..b34be96b 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -43,6 +43,7 @@ "@tamagui/vite-plugin": "1.0.15", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-native-svg": "^13.7.0", "react-native-web": "^0.18.0", "storybook": "^7.0.0-beta.21", "storybook-addon-designs": "^7.0.0-beta.2", diff --git a/packages/components/src/accordion/accordion.tsx b/packages/components/src/accordion/accordion.tsx new file mode 100644 index 00000000..3848bbf2 --- /dev/null +++ b/packages/components/src/accordion/accordion.tsx @@ -0,0 +1,111 @@ +import React from 'react' + +import { Stack } from '@tamagui/core' +import { AnimatePresence } from 'tamagui' + +import { Chevron } from '../icon' +import { Label, Paragraph } from '../typography' + +import type { GetProps } from '@tamagui/core' + +type BaseProps = GetProps + +type Props = { + children: React.ReactElement[] | React.ReactElement + isExpanded: boolean + onToggle?: () => void + title: string + numberOfNewMessages?: number + showNotifications?: boolean +} & BaseProps + +const Accordion = ({ + children, + isExpanded, + onToggle, + title, + numberOfNewMessages, + showNotifications, +}: Props) => { + return ( + + + + + + + + + + {title} + + + + {showNotifications && numberOfNewMessages && ( + + + + + + )} + + + + {isExpanded && ( + {children} + )} + + + + + ) +} + +export { Accordion } diff --git a/packages/components/src/accordion/accordionItem.tsx b/packages/components/src/accordion/accordionItem.tsx new file mode 100644 index 00000000..92defe67 --- /dev/null +++ b/packages/components/src/accordion/accordionItem.tsx @@ -0,0 +1,115 @@ +import { Stack } from '@tamagui/core' + +import { Muted } from '../icon' +import { Label, Paragraph } from '../typography' + +import type { GetProps } from '@tamagui/core' + +type BaseProps = GetProps + +type Props = { + isSelected?: boolean + onToggle?: () => void + title: string + channelStatus?: 'muted' | 'normal' | 'withMessages' | 'withMentions' + icon?: React.ReactNode + numberOfMessages?: number +} & BaseProps + +const textColor = { + muted: '$neutral-40', + normal: '$neutral-50', + withMessages: '$neutral-100', + withMentions: '$neutral-100', +} + +const AccordionItem = ({ + icon, + isSelected, + title, + channelStatus = 'normal', + numberOfMessages, + ...rest +}: Props) => { + return ( + + + {icon && <>{icon}} + + {title} + + + {channelStatus !== 'normal' && ( + + {channelStatus === 'withMentions' && ( + + + + + + )} + {channelStatus === 'withMessages' && ( + + + + )} + {channelStatus === 'muted' && } + + )} + + ) +} + +export { AccordionItem } diff --git a/packages/components/src/animations.native.ts b/packages/components/src/animations.native.ts new file mode 100644 index 00000000..c5ffb921 --- /dev/null +++ b/packages/components/src/animations.native.ts @@ -0,0 +1,18 @@ +import { createAnimations } from '@tamagui/animations-react-native' + +export const animations = createAnimations({ + fast: { + damping: 20, + mass: 1.2, + stiffness: 250, + }, + medium: { + damping: 10, + mass: 0.9, + stiffness: 100, + }, + slow: { + damping: 20, + stiffness: 60, + }, +}) diff --git a/packages/components/src/animations.ts b/packages/components/src/animations.ts index cb721cd4..d489ff4a 100644 --- a/packages/components/src/animations.ts +++ b/packages/components/src/animations.ts @@ -1,27 +1,6 @@ -import { createAnimations as createAnimationsCSS } from '@tamagui/animations-css' -import { createAnimations } from '@tamagui/animations-react-native' +import { createAnimations } from '@tamagui/animations-css' export const animations = createAnimations({ - bouncy: { - type: 'spring', - damping: 10, - mass: 0.9, - stiffness: 100, - }, - lazy: { - type: 'spring', - damping: 20, - stiffness: 60, - }, - quick: { - type: 'spring', - damping: 20, - mass: 1.2, - stiffness: 250, - }, -}) - -export const animationsCSS = createAnimationsCSS({ fast: 'ease-in 150ms', medium: 'ease-in 300ms', slow: 'ease-in 450ms', diff --git a/packages/components/src/avatar/avatar.tsx b/packages/components/src/avatar/avatar.tsx index 5420ee20..10c3c817 100644 --- a/packages/components/src/avatar/avatar.tsx +++ b/packages/components/src/avatar/avatar.tsx @@ -18,7 +18,9 @@ const Base = styled(Stack, { display: 'inline-flex', position: 'relative', overflow: 'hidden', - backgroundColor: 'rgb(255,255,255)', + backgroundColor: '$white-100', + justifyContent: 'center', + alignItems: 'center', variants: { size: { @@ -55,6 +57,12 @@ const Base = styled(Stack, { borderRadius: 16, }, }, + withOutline: { + true: { + borderWidth: 2, + borderColor: '$white-100', + }, + }, } as const, }) @@ -69,12 +77,13 @@ interface Props { size: NonNullable indicator?: 'online' | 'offline' shape?: 'circle' | 'rounded' + withOutline?: boolean } type ImageLoadingStatus = 'idle' | 'loading' | 'loaded' | 'error' const Avatar = (props: Props) => { - const { src, size, shape = 'circle' } = props + const { src, size, shape = 'circle', withOutline } = props const [status, setStatus] = useState('idle') @@ -83,7 +92,7 @@ const Avatar = (props: Props) => { }, [JSON.stringify(src)]) return ( - + { onLoad={() => setStatus('loaded')} onError={() => setStatus('error')} /> - - PP - + {status === 'error' && ( + + PP + + )} ) } diff --git a/packages/components/src/button/button.tsx b/packages/components/src/button/button.tsx index 8bb88fc8..23d7f0d8 100644 --- a/packages/components/src/button/button.tsx +++ b/packages/components/src/button/button.tsx @@ -1,9 +1,8 @@ -import { styled, Text } from '@tamagui/core' -import { ButtonFrame } from 'tamagui' +import { Stack, styled, Text } from '@tamagui/core' import type { GetProps } from '@tamagui/core' -const Base = styled(ButtonFrame, { +const Base = styled(Stack, { // tag: 'button', cursor: 'pointer', @@ -31,24 +30,22 @@ const Base = styled(ButtonFrame, { const ButtonText = styled(Text, { fontFamily: '$inter', textAlign: 'center', - color: '$white', + color: '$white-100', }) type BaseProps = GetProps -interface Props { +type Props = { type?: BaseProps['type'] children: string onPress?: () => void -} +} & Omit const Button = (props: Props) => { - const { type = 'primary', children, onPress } = props - console.log(onPress) + const { type = 'primary', children, onPress, ...rest } = props - console.log('Button', type) return ( - + {children} ) diff --git a/packages/components/src/emoji/EmojiProps.ts b/packages/components/src/emoji/EmojiProps.ts new file mode 100644 index 00000000..79f18c2c --- /dev/null +++ b/packages/components/src/emoji/EmojiProps.ts @@ -0,0 +1,9 @@ +import type { SizeTokens, StyleObject } from '@tamagui/core' +import type { SvgProps } from 'react-native-svg' + +export type EmojiProps = SvgProps & { + size?: number | SizeTokens + style?: StyleObject + sizeBackground?: number + hasBackground?: boolean +} diff --git a/packages/components/src/emoji/emojis/basketball.tsx b/packages/components/src/emoji/emojis/basketball.tsx new file mode 100644 index 00000000..de031c9c --- /dev/null +++ b/packages/components/src/emoji/emojis/basketball.tsx @@ -0,0 +1,44 @@ +import { memo } from 'react' + +import { Defs, Image, Path, Pattern, Svg, Use } from 'react-native-svg' + +import { themed } from '../themed' + +import type { EmojiProps } from '../EmojiProps' + +const Emoji = (props: EmojiProps) => { + const { size = 16, ...otherProps } = props + + return ( + + + + + + + + + + + ) +} + +Emoji.displayName = 'Basketball' + +export const Basketball = memo(themed(Emoji)) diff --git a/packages/components/src/emoji/emojis/collaboration.tsx b/packages/components/src/emoji/emojis/collaboration.tsx new file mode 100644 index 00000000..1eca8de5 --- /dev/null +++ b/packages/components/src/emoji/emojis/collaboration.tsx @@ -0,0 +1,42 @@ +import { memo } from 'react' + +import { Path, Svg } from 'react-native-svg' + +import { themed } from '../themed' + +import type { EmojiProps } from '../EmojiProps' + +const Emoji = (props: EmojiProps) => { + const { size = 16, ...otherProps } = props + + return ( + + + + + + + ) +} + +Emoji.displayName = 'Collaboration' + +export const Collaboration = memo(themed(Emoji)) diff --git a/packages/components/src/emoji/emojis/fire.tsx b/packages/components/src/emoji/emojis/fire.tsx new file mode 100644 index 00000000..a341ca8b --- /dev/null +++ b/packages/components/src/emoji/emojis/fire.tsx @@ -0,0 +1,43 @@ +import { memo } from 'react' + +import { Defs, Image, Path, Pattern, Svg, Use } from 'react-native-svg' + +import { themed } from '../themed' + +import type { EmojiProps } from '../EmojiProps' + +const Emoji = (props: EmojiProps) => { + const { size = 16, ...otherProps } = props + + return ( + + + + + + + + + + ) +} + +Emoji.displayName = 'Fire' + +export const Fire = memo(themed(Emoji)) diff --git a/packages/components/src/emoji/emojis/peach.tsx b/packages/components/src/emoji/emojis/peach.tsx new file mode 100644 index 00000000..fd009f7b --- /dev/null +++ b/packages/components/src/emoji/emojis/peach.tsx @@ -0,0 +1,57 @@ +import { memo } from 'react' + +import { + ClipPath, + Defs, + G, + Image, + Path, + Pattern, + Svg, + Use, +} from 'react-native-svg' + +import { themed } from '../themed' + +import type { EmojiProps } from '../EmojiProps' + +const Emoji = (props: EmojiProps) => { + const { size = 16, ...otherProps } = props + + return ( + + + + + + + + + + + + + + + ) +} + +Emoji.displayName = 'Peach' + +export const Peach = memo(themed(Emoji)) diff --git a/packages/components/src/emoji/emojis/play.tsx b/packages/components/src/emoji/emojis/play.tsx new file mode 100644 index 00000000..bbe75133 --- /dev/null +++ b/packages/components/src/emoji/emojis/play.tsx @@ -0,0 +1,46 @@ +import { memo } from 'react' + +import { Path, Svg } from 'react-native-svg' + +import { themed } from '../themed' + +import type { EmojiProps } from '../EmojiProps' + +const Emoji = (props: EmojiProps) => { + const { size = 16, ...otherProps } = props + + return ( + + + + + + + + ) +} + +Emoji.displayName = 'Play' + +export const Play = memo(themed(Emoji)) diff --git a/packages/components/src/emoji/emojis/unicorn.tsx b/packages/components/src/emoji/emojis/unicorn.tsx new file mode 100644 index 00000000..f96db2ba --- /dev/null +++ b/packages/components/src/emoji/emojis/unicorn.tsx @@ -0,0 +1,57 @@ +import { memo } from 'react' + +import { + ClipPath, + Defs, + G, + Image, + Path, + Pattern, + Svg, + Use, +} from 'react-native-svg' + +import { themed } from '../themed' + +import type { EmojiProps } from '../EmojiProps' + +const Emoji = (props: EmojiProps) => { + const { size = 16, ...otherProps } = props + + return ( + + + + + + + + + + + + + + + ) +} + +Emoji.displayName = 'Unicorn' + +export const Unicorn = memo(themed(Emoji)) diff --git a/packages/components/src/emoji/index.tsx b/packages/components/src/emoji/index.tsx new file mode 100644 index 00000000..75639ca4 --- /dev/null +++ b/packages/components/src/emoji/index.tsx @@ -0,0 +1,6 @@ +export { Basketball } from './emojis/basketball' +export { Collaboration } from './emojis/collaboration' +export { Fire } from './emojis/fire' +export { Peach } from './emojis/peach' +export { Play } from './emojis/play' +export { Unicorn } from './emojis/unicorn' diff --git a/packages/components/src/emoji/themed.tsx b/packages/components/src/emoji/themed.tsx new file mode 100644 index 00000000..bbd49ca0 --- /dev/null +++ b/packages/components/src/emoji/themed.tsx @@ -0,0 +1,27 @@ +import { Stack } from '@tamagui/core' + +import type { EmojiProps } from './EmojiProps' +import type React from 'react' + +export function themed(Component: React.ElementType) { + const useWrapped = (props: EmojiProps) => { + const { size, hasBackground, sizeBackground = 24, ...rest } = props + + if (hasBackground) { + return ( + + + + ) + } + return + } + return useWrapped +} diff --git a/packages/components/src/icon/IconProps.ts b/packages/components/src/icon/IconProps.ts new file mode 100644 index 00000000..b7cc9c19 --- /dev/null +++ b/packages/components/src/icon/IconProps.ts @@ -0,0 +1,18 @@ +import type { + SizeTokens, + StyleObject, + ThemeParsed, + Tokens, +} from '@tamagui/core' +import type { SvgProps } from 'react-native-svg' + +type GetTokenString = A extends string ? `$${A}` : `$${string}` +export type ColorTokens = + | GetTokenString + | GetTokenString + +export type IconProps = SvgProps & { + size?: number | SizeTokens + color?: ColorTokens + style?: StyleObject +} diff --git a/packages/components/src/icon/icons/chevron.tsx b/packages/components/src/icon/icons/chevron.tsx new file mode 100644 index 00000000..da6b2e9a --- /dev/null +++ b/packages/components/src/icon/icons/chevron.tsx @@ -0,0 +1,27 @@ +import { memo } from 'react' + +import { Path, Svg } from 'react-native-svg' + +import { themed } from '../themed' + +import type { IconProps } from '../IconProps' + +const Icon = (props: IconProps) => { + const { color, size = 16, ...otherProps } = props + + return ( + + + + ) +} + +Icon.displayName = 'Chevron' + +export const Chevron = memo(themed(Icon)) diff --git a/packages/components/src/icon/icons/group.tsx b/packages/components/src/icon/icons/group.tsx new file mode 100644 index 00000000..87c59ed4 --- /dev/null +++ b/packages/components/src/icon/icons/group.tsx @@ -0,0 +1,39 @@ +import { memo } from 'react' + +import { Path, Svg } from 'react-native-svg' + +import { themed } from '../themed' + +import type { IconProps } from '../IconProps' + +const Icon = (props: IconProps) => { + const { color, size = 16, ...otherProps } = props + + return ( + + + + + ) +} + +Icon.displayName = 'Group' + +export const Group = memo(themed(Icon)) diff --git a/packages/components/src/icon/icons/muted.tsx b/packages/components/src/icon/icons/muted.tsx new file mode 100644 index 00000000..bb2763bd --- /dev/null +++ b/packages/components/src/icon/icons/muted.tsx @@ -0,0 +1,32 @@ +import { memo } from 'react' + +import { Path, Svg } from 'react-native-svg' + +import { themed } from '../themed' + +import type { IconProps } from '../IconProps' + +const Icon = (props: IconProps) => { + const { color, size = 16, ...otherProps } = props + + return ( + + + + ) +} + +Icon.displayName = 'Muted' + +export const Muted = memo(themed(Icon)) diff --git a/packages/components/src/icon/index.tsx b/packages/components/src/icon/index.tsx new file mode 100644 index 00000000..c83fd463 --- /dev/null +++ b/packages/components/src/icon/index.tsx @@ -0,0 +1,3 @@ +export { Chevron } from './icons/chevron' +export { Group } from './icons/group' +export { Muted } from './icons/muted' diff --git a/packages/components/src/icon/themed.tsx b/packages/components/src/icon/themed.tsx new file mode 100644 index 00000000..1d0e7fb2 --- /dev/null +++ b/packages/components/src/icon/themed.tsx @@ -0,0 +1,15 @@ +import { useCurrentColor } from 'tamagui' + +import type { IconProps } from './IconProps' +import type React from 'react' + +export function themed(Component: React.ElementType) { + const useWrapped = (props: IconProps) => { + const { size, color: colorToken = '$neutral-100', ...rest } = props + + const color = useCurrentColor(colorToken) + + return + } + return useWrapped +} diff --git a/packages/components/src/input/input.tsx b/packages/components/src/input/input.tsx index 7e6be2ef..266cbb17 100644 --- a/packages/components/src/input/input.tsx +++ b/packages/components/src/input/input.tsx @@ -1,4 +1,6 @@ -import { setupReactNative, Stack, styled } from '@tamagui/core' +// eslint-disable-next-line eslint-comments/disable-enable-pair +/* eslint-disable import/namespace */ +import { setupReactNative, styled } from '@tamagui/core' // import { focusableInputHOC } from '@tamagui/focusable' import { TextInput } from 'react-native' @@ -43,7 +45,7 @@ export const InputFrame = styled( // },/ // focusStyle: { - // // borderColor: '$borderColorFocus', + // borderWidth: 2, // marginHorizontal: -1, // }, diff --git a/packages/components/src/sidebar/sidebar.tsx b/packages/components/src/sidebar/sidebar.tsx index c34fd07d..899ad794 100644 --- a/packages/components/src/sidebar/sidebar.tsx +++ b/packages/components/src/sidebar/sidebar.tsx @@ -1,7 +1,13 @@ +import { useState } from 'react' + 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' import { Image } from '../image' import { Heading, Paragraph } from '../typography' @@ -11,35 +17,259 @@ interface Props { membersCount: number } +interface Channels { + id: string + title: string + icon?: React.ReactNode + channelStatus?: 'muted' | 'normal' | 'withMessages' | 'withMentions' + numberOfMessages?: number +} +interface CommunitiesProps { + id: string + title: string + numberOfNewMessages?: number + channels: Channels[] +} + +// MOCK DATA +const COMMUNITIES: CommunitiesProps[] = [ + { + id: 'welcome', + title: 'Welcome', + numberOfNewMessages: 3, + channels: [ + { + id: 'welcome', + title: '# welcome', + icon: , + }, + { + id: 'general-welcome', + title: '# general', + icon: , + }, + { + id: 'random', + title: '# random', + icon: , + }, + { + id: 'onboarding', + title: '# onboarding', + icon: , + channelStatus: 'withMentions', + numberOfMessages: 3, + }, + ], + }, + { + id: 'community', + title: 'Community', + numberOfNewMessages: 5, + channels: [ + { + id: 'announcements', + title: '# announcements', + icon: , + }, + { + id: 'jobs', + title: '# jobs', + channelStatus: 'withMentions', + numberOfMessages: 3, + icon: , + }, + { + id: 'events', + title: '# events', + channelStatus: 'withMentions', + numberOfMessages: 2, + icon: , + }, + { + id: 'meetups', + title: '# meetups', + icon: , + }, + ], + }, + { + id: 'Design', + title: 'Design', + channels: [ + { + id: 'design', + title: '# design', + icon: , + }, + { + id: 'ux', + title: '# ux', + icon: , + }, + { + id: 'ui', + title: '# ui', + icon: , + }, + { + id: 'figma', + title: '# figma', + icon: , + }, + ], + }, + { + id: 'General', + title: 'General', + channels: [ + { + id: 'general', + title: '# general', + icon: , + }, + { + id: 'people-ops', + title: '# people-ops', + icon: , + }, + ], + }, + { + id: 'Frontend', + title: 'Frontend', + channels: [ + { + id: 'react', + title: '# react', + icon: , + channelStatus: 'withMessages', + }, + { + id: 'vue', + title: '# vue', + icon: , + }, + { + id: 'angular', + title: '# angular', + channelStatus: 'muted', + icon: , + }, + { + id: 'svelte', + title: '# svelte', + icon: , + }, + ], + }, + { + id: 'Backend', + title: 'Backend', + channels: [ + { + id: 'node', + title: '# node', + icon: , + }, + { + id: 'python', + title: '# python', + icon: , + }, + { + id: 'ruby', + title: '# ruby', + icon: , + }, + { + id: 'php', + title: '# php', + channelStatus: 'muted', + icon: , + }, + ], + }, +] + +const COMMUNITIES_EXPAND_CONTROL = COMMUNITIES.reduce( + (o, key) => ({ ...o, [key.id]: false }), + + {} as Record[] +) + const _Sidebar = (props: Props) => { const { name, description, membersCount } = props + const [isExpanded, setIsExpanded] = useState({ + ...COMMUNITIES_EXPAND_CONTROL, + welcome: true, + }) + + const [selectedChannel, setSelectedChannel] = useState('welcome') + + const handleToggle = (id: string) => { + setIsExpanded(prev => ({ + ...prev, + [id]: !prev[id as keyof typeof isExpanded], + })) + } return ( - + - - + + + + + {name} + {description} + + + {membersCount} + - {name} - {description} - {membersCount} - + {COMMUNITIES.map(community => ( + handleToggle(community.id)} + title={community.title} + numberOfNewMessages={community.numberOfNewMessages} + showNotifications={ + !isExpanded[community.id as keyof typeof isExpanded] + } + > + {community.channels.map(channel => ( + setSelectedChannel(channel.id)} + /> + ))} + + ))} + + {/* */} ) diff --git a/packages/components/src/tamagui.config.ts b/packages/components/src/tamagui.config.ts index 7e083d67..345c6e19 100644 --- a/packages/components/src/tamagui.config.ts +++ b/packages/components/src/tamagui.config.ts @@ -3,7 +3,7 @@ import { createInterFont } from '@tamagui/font-inter' import { createMedia } from '@tamagui/react-native-media-driver' import { shorthands } from '@tamagui/shorthands' -import { animations, animationsCSS } from './animations' +import { animations } from './animations' import { themes } from './themes' import { tokens } from './tokens' @@ -106,8 +106,5 @@ export const config = createTamagui({ pointerCoarse: { pointer: 'coarse' }, }), shorthands, - animations: { - ...animations, - ...animationsCSS, - }, + animations, }) diff --git a/packages/components/src/themes.ts b/packages/components/src/themes.ts index 4d895ca8..1b7c31fd 100644 --- a/packages/components/src/themes.ts +++ b/packages/components/src/themes.ts @@ -35,7 +35,10 @@ const light = createTheme({ beigeHover: tokens.color['beige-60'], }) -const dark = createTheme({ +// note: we set up a single consistent base type to validate the rest: +type BaseTheme = typeof light + +const dark: BaseTheme = createTheme({ background: tokens.color['neutral-95'], textPrimary: tokens.color['white-100'], primary: tokens.color['primary-60'], @@ -68,7 +71,15 @@ const dark = createTheme({ beigeHover: tokens.color['beige-50'], }) -export const themes = { +const allThemes = { light, dark, } + +type ThemeName = keyof typeof allThemes + +type Themes = { + [key in ThemeName]: BaseTheme +} + +export const themes: Themes = allThemes diff --git a/yarn.lock b/yarn.lock index fc7a5a0f..c2146403 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8488,7 +8488,26 @@ css-select@^4.1.3, css-select@^4.2.0: domutils "^2.8.0" nth-check "^2.0.1" -css-what@^6.0.1: +css-select@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6" + integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg== + dependencies: + boolbase "^1.0.0" + css-what "^6.1.0" + domhandler "^5.0.2" + domutils "^3.0.1" + nth-check "^2.0.1" + +css-tree@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" + integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== + dependencies: + mdn-data "2.0.14" + source-map "^0.6.1" + +css-what@^6.0.1, css-what@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== @@ -8880,12 +8899,21 @@ dom-serializer@^1.0.1: domhandler "^4.2.0" entities "^2.0.0" +dom-serializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" + integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.2" + entities "^4.2.0" + dom-walk@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.2.tgz#0c548bef048f4d1f2a97249002236060daa3fd84" integrity sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w== -domelementtype@^2.0.1, domelementtype@^2.2.0: +domelementtype@^2.0.1, domelementtype@^2.2.0, domelementtype@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== @@ -8897,6 +8925,13 @@ domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1: dependencies: domelementtype "^2.2.0" +domhandler@^5.0.1, domhandler@^5.0.2: + version "5.0.3" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" + integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== + dependencies: + domelementtype "^2.3.0" + domutils@^2.5.2, domutils@^2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" @@ -8906,6 +8941,15 @@ domutils@^2.5.2, domutils@^2.8.0: domelementtype "^2.2.0" domhandler "^4.2.0" +domutils@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.0.1.tgz#696b3875238338cb186b6c0612bd4901c89a4f1c" + integrity sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q== + dependencies: + dom-serializer "^2.0.0" + domelementtype "^2.3.0" + domhandler "^5.0.1" + dot-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" @@ -9001,6 +9045,11 @@ entities@^2.0.0: resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== +entities@^4.2.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.4.0.tgz#97bdaba170339446495e653cfd2db78962900174" + integrity sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA== + env-editor@^0.4.1: version "0.4.2" resolved "https://registry.yarnpkg.com/env-editor/-/env-editor-0.4.2.tgz#4e76568d0bd8f5c2b6d314a9412c8fe9aa3ae861" @@ -13300,6 +13349,11 @@ mdast-util-to-string@^1.0.0: resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-1.1.0.tgz#27055500103f51637bd07d01da01eb1967a43527" integrity sha512-jVU0Nr2B9X3MU4tSK7JP1CMkSvOj7X5l/GboG1tKRw52lLF1x2Ju92Ms9tNetCcbfX3hzlM73zYo2NKkWSfF/A== +mdn-data@2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" + integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== + media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -14079,7 +14133,6 @@ node-fetch-native@^1.0.1: node-fetch@2.6.7, node-fetch@^2.2.0, node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.7, node-fetch@^2.x.x: version "2.6.7" - uid "1b5d62978f2ed07b99444f64f0df39f960a6d34d" resolved "https://registry.npmjs.org/@achingbrain/node-fetch/-/node-fetch-2.6.7.tgz#1b5d62978f2ed07b99444f64f0df39f960a6d34d" node-forge@^1.1.0, node-forge@^1.2.1, node-forge@^1.3.1: @@ -15374,6 +15427,14 @@ react-native-gradle-plugin@^0.70.3: resolved "https://registry.yarnpkg.com/react-native-gradle-plugin/-/react-native-gradle-plugin-0.70.3.tgz#cbcf0619cbfbddaa9128701aa2d7b4145f9c4fc8" integrity sha512-oOanj84fJEXUg9FoEAQomA8ISG+DVIrTZ3qF7m69VQUJyOGYyDZmPqKcjvRku4KXlEH6hWO9i4ACLzNBh8gC0A== +react-native-svg@^13.7.0: + version "13.7.0" + resolved "https://registry.yarnpkg.com/react-native-svg/-/react-native-svg-13.7.0.tgz#be2ffb935e996762543dd7376bdc910722f7a43c" + integrity sha512-WR5CIURvee5cAfvMhmdoeOjh1SC8KdLq5u5eFsz4pbYzCtIFClGSkLnNgkMSDMVV5LV0qQa4jeIk75ieIBzaDA== + dependencies: + css-select "^5.1.0" + css-tree "^1.1.3" + react-native-web-internals@^1.0.15: version "1.0.15" resolved "https://registry.yarnpkg.com/react-native-web-internals/-/react-native-web-internals-1.0.15.tgz#8c4367e2461edf5bab4abab71471b351a08cb19f"