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:
parent
f2bb6f3d38
commit
fc580590ab
|
@ -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>
|
||||
)
|
||||
|
|
|
@ -17,6 +17,7 @@ body,
|
|||
}
|
||||
|
||||
#app {
|
||||
isolation: isolate;
|
||||
height: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 352px 1fr auto;
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
export type { ToastProps } from './toast'
|
||||
export { Toast } from './toast'
|
||||
export { ToastContainer, useToast } from './toast-container'
|
|
@ -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,
|
||||
})
|
|
@ -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
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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'
|
||||
|
|
|
@ -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 |
|
@ -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 |
23
yarn.lock
23
yarn.lock
|
@ -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==
|
||||
|
|
Loading…
Reference in New Issue