about summary refs log tree commit diff
path: root/src/components/Toast/Toast.tsx
diff options
context:
space:
mode:
authorEric Bailey <git@esb.lol>2025-07-31 10:15:35 -0500
committerGitHub <noreply@github.com>2025-07-31 10:15:35 -0500
commit3bcfcba6d8176bac03202b496110915da748b0f1 (patch)
tree68c75c7c80945a8a5f5a32522dd9aa29f119e02a /src/components/Toast/Toast.tsx
parent33e071494881b13696e24b334857e594f29a4b1d (diff)
downloadvoidsky-3bcfcba6d8176bac03202b496110915da748b0f1.tar.zst
Some toasts cleanup and reorg (#8748)
* Reorg

* Move animation into css file

* Update style comment

* Extract core component, use platform-specific wrappers

* Pull out platform specific styles

* Just move styles into Toast component itself

* Rename cleanup

* Update API

* Add duration optional prop

* Add some type docs

* add exp eased slide aniamtions

* Make toasts full width on mobile web

---------

Co-authored-by: Samuel Newman <mozzius@protonmail.com>
Diffstat (limited to 'src/components/Toast/Toast.tsx')
-rw-r--r--src/components/Toast/Toast.tsx205
1 files changed, 205 insertions, 0 deletions
diff --git a/src/components/Toast/Toast.tsx b/src/components/Toast/Toast.tsx
new file mode 100644
index 000000000..0dc9d4b07
--- /dev/null
+++ b/src/components/Toast/Toast.tsx
@@ -0,0 +1,205 @@
+import {createContext, useContext, useMemo} from 'react'
+import {View} from 'react-native'
+
+import {atoms as a, select, useTheme} from '#/alf'
+import {Check_Stroke2_Corner0_Rounded as SuccessIcon} from '#/components/icons/Check'
+import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
+import {CircleInfo_Stroke2_Corner0_Rounded as ErrorIcon} from '#/components/icons/CircleInfo'
+import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning'
+import {type ToastType} from '#/components/Toast/types'
+import {Text} from '#/components/Typography'
+
+type ContextType = {
+  type: ToastType
+}
+
+export const ICONS = {
+  default: SuccessIcon,
+  success: SuccessIcon,
+  error: ErrorIcon,
+  warning: WarningIcon,
+  info: CircleInfo,
+}
+
+const Context = createContext<ContextType>({
+  type: 'default',
+})
+
+export function Toast({
+  type,
+  content,
+}: {
+  type: ToastType
+  content: React.ReactNode
+}) {
+  const t = useTheme()
+  const styles = useToastStyles({type})
+  const Icon = ICONS[type]
+
+  return (
+    <Context.Provider value={useMemo(() => ({type}), [type])}>
+      <View
+        style={[
+          a.flex_1,
+          a.py_lg,
+          a.pl_xl,
+          a.pr_2xl,
+          a.rounded_md,
+          a.border,
+          a.flex_row,
+          a.gap_sm,
+          t.atoms.shadow_sm,
+          {
+            backgroundColor: styles.backgroundColor,
+            borderColor: styles.borderColor,
+          },
+        ]}>
+        <Icon size="md" fill={styles.iconColor} />
+
+        <View style={[a.flex_1]}>
+          {typeof content === 'string' ? (
+            <ToastText>{content}</ToastText>
+          ) : (
+            content
+          )}
+        </View>
+      </View>
+    </Context.Provider>
+  )
+}
+
+export function ToastText({children}: {children: React.ReactNode}) {
+  const {type} = useContext(Context)
+  const {textColor} = useToastStyles({type})
+  return (
+    <Text
+      style={[
+        a.text_md,
+        a.font_bold,
+        a.leading_snug,
+        {
+          color: textColor,
+        },
+      ]}>
+      {children}
+    </Text>
+  )
+}
+
+function useToastStyles({type}: {type: ToastType}) {
+  const t = useTheme()
+  return useMemo(() => {
+    return {
+      default: {
+        backgroundColor: select(t.name, {
+          light: t.atoms.bg_contrast_25.backgroundColor,
+          dim: t.atoms.bg_contrast_100.backgroundColor,
+          dark: t.atoms.bg_contrast_100.backgroundColor,
+        }),
+        borderColor: select(t.name, {
+          light: t.atoms.border_contrast_low.borderColor,
+          dim: t.atoms.border_contrast_high.borderColor,
+          dark: t.atoms.border_contrast_high.borderColor,
+        }),
+        iconColor: select(t.name, {
+          light: t.atoms.text_contrast_medium.color,
+          dim: t.atoms.text_contrast_medium.color,
+          dark: t.atoms.text_contrast_medium.color,
+        }),
+        textColor: select(t.name, {
+          light: t.atoms.text_contrast_medium.color,
+          dim: t.atoms.text_contrast_medium.color,
+          dark: t.atoms.text_contrast_medium.color,
+        }),
+      },
+      success: {
+        backgroundColor: select(t.name, {
+          light: t.palette.primary_100,
+          dim: t.palette.primary_100,
+          dark: t.palette.primary_50,
+        }),
+        borderColor: select(t.name, {
+          light: t.palette.primary_500,
+          dim: t.palette.primary_500,
+          dark: t.palette.primary_500,
+        }),
+        iconColor: select(t.name, {
+          light: t.palette.primary_500,
+          dim: t.palette.primary_600,
+          dark: t.palette.primary_600,
+        }),
+        textColor: select(t.name, {
+          light: t.palette.primary_500,
+          dim: t.palette.primary_600,
+          dark: t.palette.primary_600,
+        }),
+      },
+      error: {
+        backgroundColor: select(t.name, {
+          light: t.palette.negative_200,
+          dim: t.palette.negative_25,
+          dark: t.palette.negative_25,
+        }),
+        borderColor: select(t.name, {
+          light: t.palette.negative_300,
+          dim: t.palette.negative_300,
+          dark: t.palette.negative_300,
+        }),
+        iconColor: select(t.name, {
+          light: t.palette.negative_600,
+          dim: t.palette.negative_600,
+          dark: t.palette.negative_600,
+        }),
+        textColor: select(t.name, {
+          light: t.palette.negative_600,
+          dim: t.palette.negative_600,
+          dark: t.palette.negative_600,
+        }),
+      },
+      warning: {
+        backgroundColor: select(t.name, {
+          light: t.atoms.bg_contrast_25.backgroundColor,
+          dim: t.atoms.bg_contrast_100.backgroundColor,
+          dark: t.atoms.bg_contrast_100.backgroundColor,
+        }),
+        borderColor: select(t.name, {
+          light: t.atoms.border_contrast_low.borderColor,
+          dim: t.atoms.border_contrast_high.borderColor,
+          dark: t.atoms.border_contrast_high.borderColor,
+        }),
+        iconColor: select(t.name, {
+          light: t.atoms.text_contrast_medium.color,
+          dim: t.atoms.text_contrast_medium.color,
+          dark: t.atoms.text_contrast_medium.color,
+        }),
+        textColor: select(t.name, {
+          light: t.atoms.text_contrast_medium.color,
+          dim: t.atoms.text_contrast_medium.color,
+          dark: t.atoms.text_contrast_medium.color,
+        }),
+      },
+      info: {
+        backgroundColor: select(t.name, {
+          light: t.atoms.bg_contrast_25.backgroundColor,
+          dim: t.atoms.bg_contrast_100.backgroundColor,
+          dark: t.atoms.bg_contrast_100.backgroundColor,
+        }),
+        borderColor: select(t.name, {
+          light: t.atoms.border_contrast_low.borderColor,
+          dim: t.atoms.border_contrast_high.borderColor,
+          dark: t.atoms.border_contrast_high.borderColor,
+        }),
+        iconColor: select(t.name, {
+          light: t.atoms.text_contrast_medium.color,
+          dim: t.atoms.text_contrast_medium.color,
+          dark: t.atoms.text_contrast_medium.color,
+        }),
+        textColor: select(t.name, {
+          light: t.atoms.text_contrast_medium.color,
+          dim: t.atoms.text_contrast_medium.color,
+          dark: t.atoms.text_contrast_medium.color,
+        }),
+      },
+    }[type]
+  }, [t, type])
+}