Add Toast (#369)

* add icorrect icon

* update correct icon

* add radix toast dep

* set isolation

Co-authored-by: Pavel <prichodko@users.noreply.github.com>

* add toast

 Co-authored-by: Pavel <prichodko@users.noreply.github.com>

* move ToastContainer to separate file

* add custom fn

---------

Co-authored-by: Pavel <prichodko@users.noreply.github.com>
Co-authored-by: Pavel Prichodko <14926950+prichodko@users.noreply.github.com>
This commit is contained in:
Felicio Mununga 2023-04-04 15:52:02 +02:00 committed by GitHub
parent f2bb6f3d38
commit fc580590ab
No known key found for this signature in database
GPG Key ID: 0EB8D75C775AB6F1
15 changed files with 337 additions and 24 deletions

View File

@ -6,7 +6,7 @@ import '@tamagui/font-inter/css/700.css'
import { StrictMode } from 'react'
import { Provider } from '@status-im/components'
import { Provider, ToastContainer } from '@status-im/components'
import { createRoot } from 'react-dom/client'
import App from './app'
@ -17,6 +17,7 @@ createRoot(root).render(
<StrictMode>
<Provider>
<App />
<ToastContainer />
</Provider>
</StrictMode>
)

View File

@ -17,6 +17,7 @@ body,
}
#app {
isolation: isolate;
height: 100%;
display: grid;
grid-template-columns: 352px 1fr auto;

View File

@ -1,4 +1,4 @@
import { Provider } from '../src'
import { Provider, ToastContainer } from '../src'
import { Parameters, Decorator } from '@storybook/react'
import './reset.css'
@ -17,6 +17,7 @@ const withThemeProvider: Decorator = (Story, _context) => {
return (
<Provider>
<Story />
<ToastContainer />
</Provider>
)
}

View File

@ -33,6 +33,7 @@
"@radix-ui/react-dropdown-menu": "^2.0.4",
"@radix-ui/react-popover": "^1.0.5",
"@radix-ui/react-tabs": "^1.0.3",
"@radix-ui/react-toast": "^1.1.3",
"@radix-ui/react-tooltip": "^1.0.5",
"@status-im/icons": "*",
"@tamagui/animations-css": "1.7.7",

View File

@ -12,6 +12,7 @@ export * from './provider'
export * from './sidebar'
export * from './sidebar-members'
export * from './text'
export * from './toast'
export * from './topbar'
export * from './user-list'

View File

@ -0,0 +1,3 @@
export type { ToastProps } from './toast'
export { Toast } from './toast'
export { ToastContainer, useToast } from './toast-container'

View File

@ -0,0 +1,89 @@
import { useMemo } from 'react'
import { Provider, Root, Viewport } from '@radix-ui/react-toast'
import { styled } from 'tamagui'
import { create } from 'zustand'
import { Toast } from './toast'
import type { ToastProps } from './toast'
type ToastState = {
toast: ToastProps | null
dismiss: () => void
positive: (
message: string,
actionProps?: Pick<ToastProps, 'action' | 'onAction'>
) => void
negative: (
message: string,
actionProps?: Pick<ToastProps, 'action' | 'onAction'>
) => void
custom: (
message: string,
icon: React.ReactElement,
actionProps?: Pick<ToastProps, 'action' | 'onAction'>
) => void
}
const useStore = create<ToastState>()(set => ({
toast: null,
positive: (message, actionProps) =>
set({ toast: { ...actionProps, message, type: 'positive' } }),
negative: (message, actionProps) =>
set({ toast: { ...actionProps, message, type: 'negative' } }),
custom: (message, icon, actionProps) =>
set({ toast: { ...actionProps, message, icon } }),
dismiss: () => set({ toast: null }),
}))
const ToastContainer = () => {
const store = useStore()
if (store.toast === null) {
return null
}
const handleOpenChange = (open: boolean) => {
if (!open) {
store.dismiss()
}
}
return (
<Provider>
<ToastRoot
defaultOpen
onOpenChange={handleOpenChange}
style={{ position: 'fixed' }}
>
<Toast {...store.toast} />
</ToastRoot>
<Viewport />
</Provider>
)
}
const useToast = () => {
const store = useStore()
return useMemo(
() => ({
positive: store.positive,
negative: store.negative,
custom: store.custom,
}),
[store]
)
}
export { ToastContainer, useToast }
const ToastRoot = styled(Root, {
name: 'ToastRoot',
acceptsClassName: true,
bottom: 12,
right: 12,
zIndex: 1000,
})

View File

@ -0,0 +1,73 @@
import { PlaceholderIcon } from '@status-im/icons/20'
import { Stack } from '@tamagui/core'
import { Button } from '../button'
import { Toast, useToast } from './'
import type { Meta, StoryObj } from '@storybook/react'
const meta: Meta<typeof Toast> = {
component: Toast,
args: {},
argTypes: {},
parameters: {
design: {
type: 'figma',
url: 'https://www.figma.com/file/IBmFKgGL1B4GzqD8LQTw6n/Design-System-for-Desktop%2FWeb?node-id=3928-77614&t=hp2XwjFgrFl3hhDm-4',
},
},
}
type Story = StoryObj<typeof Toast>
const ToastWithHook = () => {
const toast = useToast()
return (
<Button
size={32}
onPress={() => toast.positive('Great success! This means good stuff!')}
>
Show Toast
</Button>
)
}
export const Default: Story = {
args: {},
render: () => (
<Stack space flexDirection="row">
<ToastWithHook />
</Stack>
),
}
export const AllVariants: Story = {
args: {},
render: () => (
<Stack space>
<Toast
type="negative"
message="You can only add 6 photos to your message"
/>
<Toast type="positive" message="Great success! This means good stuff!" />
<Toast icon={<PlaceholderIcon />} message="Something happened" />
<Toast
type="negative"
action="Retry"
message="Couldn't fetch information"
/>
<Toast
type="negative"
message="You can only add 6 photos to your message and something more"
/>
<Toast
type="negative"
action="Retry"
message="You can only add 6 photos to your message and something more"
/>
</Stack>
),
}
export default meta

View File

@ -0,0 +1,91 @@
import { cloneElement, forwardRef } from 'react'
import { Action, Description } from '@radix-ui/react-toast'
import { CorrectIcon, IncorrectIcon } from '@status-im/icons/20'
import { Stack, styled } from '@tamagui/core'
import { Button } from '../button'
import { Text } from '../text'
type Props = {
message: string
action?: string
onAction?: () => void
} & (
| {
type: 'positive' | 'negative'
}
| {
type?: never
icon: React.ReactElement
}
)
const Toast = (props: Props) => {
const { message, action, onAction } = props
const renderIcon = () => {
if (!props.type) {
return cloneElement(props.icon, { color: '$white-70' })
}
if (props.type === 'positive') {
return <CorrectIcon />
}
return <IncorrectIcon />
}
return (
<Base action={Boolean(action)}>
<Stack flex={1} flexDirection="row" gap={4}>
<Stack width={20}>{renderIcon()}</Stack>
<Description asChild>
<Text size={13} weight={'medium'} color="$white-100">
{message}
</Text>
</Description>
</Stack>
{action && (
<Stack alignSelf="flex-start">
<Action asChild altText={action}>
<Button size={24} variant={'grey'} onPress={onAction}>
{action}
</Button>
</Action>
</Stack>
)}
</Base>
)
}
const _Toast = forwardRef(Toast)
export { _Toast as Toast }
export type { Props as ToastProps }
const Base = styled(Stack, {
name: 'Toast',
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 8,
gap: 12,
width: 351,
minHeight: 40,
backgroundColor: '$neutral-80-opa-70',
borderRadius: 12,
justifyContent: 'space-between',
variants: {
action: {
true: {
paddingVertical: 8,
},
false: {
paddingVertical: 10,
},
},
},
})

View File

@ -18,15 +18,8 @@ const SvgCorrectIcon = (props: IconProps) => {
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<Circle
cx={10}
cy={10}
r={6.75}
stroke="#26A69A"
strokeOpacity={0.4}
strokeWidth={1.3}
/>
<Path d="M6.833 10.5 9 12.5l4.333-5" stroke="#26A69A" strokeWidth={1.3} />
<Circle cx={10} cy={10} r={7.5} stroke="#23ADA0" strokeWidth={1.2} />
<Path d="m7.25 10.75 2 1.5 3.5-4.5" stroke="#23ADA0" strokeWidth={1.2} />
</Svg>
)
}

View File

@ -0,0 +1,31 @@
import { useTheme } from '@tamagui/core'
import { Circle, Path, Svg } from 'react-native-svg'
import type { IconProps } from '../types'
const SvgIncorrectIcon = (props: IconProps) => {
const { color: token = '$neutral-100' } = props
const theme = useTheme()
/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const color = theme[token]?.val ?? token
return (
<Svg
width={20}
height={20}
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<Circle cx={10} cy={10} r={7.5} stroke="#E95460" strokeWidth={1.2} />
<Path
fillRule="evenodd"
clipRule="evenodd"
d="m10.75 5.5-.2 6h-1.1l-.2-6h1.5ZM10 13a.75.75 0 1 1 0 1.5.75.75 0 0 1 0-1.5Z"
fill="#E95460"
/>
</Svg>
)
}
export default SvgIncorrectIcon

View File

@ -85,6 +85,7 @@ export { default as HistoryIcon } from './history-icon'
export { default as HoldIcon } from './hold-icon'
export { default as ImageIcon } from './image-icon'
export { default as InactiveIcon } from './inactive-icon'
export { default as IncorrectIcon } from './incorrect-icon'
export { default as InfoBadgeIcon } from './info-badge-icon'
export { default as InfoIcon } from './info-icon'
export { default as ItalicIcon } from './italic-icon'

View File

@ -5,17 +5,10 @@
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle
cx="10"
cy="10"
r="6.75"
stroke="#26A69A"
stroke-opacity="0.4"
stroke-width="1.3"
/>
<circle cx="10" cy="10" r="7.5" stroke="#23ADA0" stroke-width="1.2" />
<path
d="M6.83334 10.5L9 12.5L13.3333 7.5"
stroke="#26A69A"
stroke-width="1.3"
d="M7.25 10.75L9.25 12.25L12.75 7.75"
stroke="#23ADA0"
stroke-width="1.2"
/>
</svg>

Before

Width:  |  Height:  |  Size: 333 B

After

Width:  |  Height:  |  Size: 286 B

View File

@ -0,0 +1,15 @@
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="10" cy="10" r="7.5" stroke="#E95460" stroke-width="1.2" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M10.75 5.5L10.55 11.5H9.45L9.25 5.5H10.75ZM10 13C10.4142 13 10.75 13.3358 10.75 13.75C10.75 14.1642 10.4142 14.5 10 14.5C9.58579 14.5 9.25 14.1642 9.25 13.75C9.25 13.3358 9.58579 13 10 13Z"
fill="#E95460"
/>
</svg>

After

Width:  |  Height:  |  Size: 464 B

View File

@ -3561,6 +3561,25 @@
"@radix-ui/react-roving-focus" "1.0.3"
"@radix-ui/react-use-controllable-state" "1.0.0"
"@radix-ui/react-toast@^1.1.3":
version "1.1.3"
resolved "https://registry.yarnpkg.com/@radix-ui/react-toast/-/react-toast-1.1.3.tgz#41098f05bace7976cd4c07f6ff418261f86ede6e"
integrity sha512-yHFgpxi9wjbfPvpSPdYAzivCqw48eA1ofT8m/WqYOVTxKPdmQMuVKRYPlMmj4C1d6tJdFj/LBa1J4iY3fL4OwQ==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/primitive" "1.0.0"
"@radix-ui/react-collection" "1.0.2"
"@radix-ui/react-compose-refs" "1.0.0"
"@radix-ui/react-context" "1.0.0"
"@radix-ui/react-dismissable-layer" "1.0.3"
"@radix-ui/react-portal" "1.0.2"
"@radix-ui/react-presence" "1.0.0"
"@radix-ui/react-primitive" "1.0.2"
"@radix-ui/react-use-callback-ref" "1.0.0"
"@radix-ui/react-use-controllable-state" "1.0.0"
"@radix-ui/react-use-layout-effect" "1.0.0"
"@radix-ui/react-visually-hidden" "1.0.2"
"@radix-ui/react-tooltip@^1.0.5":
version "1.0.5"
resolved "https://registry.yarnpkg.com/@radix-ui/react-tooltip/-/react-tooltip-1.0.5.tgz#fe20274aeac874db643717fc7761d5a8abdd62d1"
@ -6364,7 +6383,7 @@
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc"
integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==
"@types/react-dom@^18.0.11":
"@types/react-dom@18.0.11", "@types/react-dom@^18.0.11":
version "18.0.11"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.11.tgz#321351c1459bc9ca3d216aefc8a167beec334e33"
integrity sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==
@ -6378,7 +6397,7 @@
dependencies:
"@types/react" "*"
"@types/react@*", "@types/react@>=16", "@types/react@^18.0.28":
"@types/react@*", "@types/react@18.0.28", "@types/react@>=16", "@types/react@^18.0.28":
version "18.0.28"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.28.tgz#accaeb8b86f4908057ad629a26635fe641480065"
integrity sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==