mirror of
https://github.com/hanzoai/gui.git
synced 2026-10-25 02:31:41 +00:00
This repo could never sync. There was NO merge-base with tamagui — 372 commits
here against 15,791 there, unrelated histories — because it began as a
copy-paste rebrand with a flattened layout. Every "sync" since was a hand
re-application that drifted, which is why the tree sat on 7.3.1 while upstream
shipped fixes we then rediscovered by hand.
This commit's PARENT IS TAMAGUI. A future sync is `git merge tama/main` then
`node scripts/rebrand.mjs` — a real merge, because the history is now shared.
174 packages: 166 upstream, our 8 restored (chrome, shell, telemetry, recipes,
get, font-geist-{sans,mono}, @hanzo/booker), bento-get gained. 170/170 build.
Our patches to files UPSTREAM ALSO OWNS are carried, which the package-level
delta missed and which was where the value actually was:
- useMedia's server snapshot (`initState ?? getMedia()`). Upstream still
returns undefined there, so every Tamagui Dialog kills a prerender — the
exact crash hanzo.app spent a day on.
- data-slot on web host nodes, absent upstream entirely.
Both applied ONTO upstream's newer files (it rewrote useMedia +150/-63 and
createComponent +160/-55), marked `PATCH (ours, carried across the transplant)`.
Seven traps are encoded in scripts/rebrand.mjs, each found by a gate:
skip itself and lockfiles; special-case the double-branded names in BOTH scoped
and bare form; map path strings, not just paths; drop a level for the flattened
group; check disk rather than a stale index.
Rust build artifacts are NOT committed. An earlier draft of this commit swept
apps/desktop/src-tauri/target/debug in via `git add -A` — 3,859 files including
a 272MB .a, over GitHub's 100MB cap, so the push could never have landed. They
are gitignored now.
Co-Authored-By: Hanzo Dev <[email protected]>
533 lines
15 KiB
Plaintext
533 lines
15 KiB
Plaintext
---
|
|
title: Toast
|
|
description: A toast component with native features
|
|
name: toast
|
|
component: Toast
|
|
package: toast
|
|
demoName: Toast
|
|
---
|
|
|
|
# Toast <Beta />
|
|
|
|
<Description>Use to show feedback to user interactions</Description>
|
|
|
|
<HeroContainer showAnimationDriverControl>
|
|
<ToastDemo />
|
|
</HeroContainer>
|
|
|
|
```tsx hero template=Toast
|
|
|
|
```
|
|
|
|
<Highlights
|
|
features={[
|
|
`Automatically closes`,
|
|
`Pause closing on hover, focus, window blur and mobile touch`,
|
|
`Supports closing via swipe gesture`,
|
|
`Easily animatable with Gui's animation drivers`,
|
|
`Native toasts included for Android, iOS and web (notification API)`,
|
|
]}
|
|
/>
|
|
|
|
## Installation
|
|
|
|
Run the following command:
|
|
|
|
```bash
|
|
yarn add @hanzogui/toast burnt
|
|
```
|
|
|
|
Then, if targeting native, rebuild your React Native app. React Native requires sub-dependencies with native dependencies always be hoisted to your apps package.json and Toast relies on the amazing [Burnt](https://github.com/nandorojo/burnt) library by Fernando Rojo to provide its native functionality.
|
|
|
|
## Anatomy
|
|
|
|
```tsx
|
|
<ToastProvider>
|
|
<Toast>
|
|
<Toast.Title />
|
|
<Toast.Description />
|
|
<Toast.Action />
|
|
<Toast.Close />
|
|
</Toast>
|
|
|
|
<ToastViewport />
|
|
</ToastProvider>
|
|
```
|
|
|
|
## API Reference
|
|
|
|
### ToastProvider
|
|
|
|
Your toasts should be wrapped within a `ToastProvider`. This is usually done at the root of your application.
|
|
|
|
<PropsTable
|
|
data={[
|
|
{
|
|
name: 'label',
|
|
required: false,
|
|
type: 'string',
|
|
description: `An author-localized label for each toast. Used to help screen reader users associate the interruption with a toast.`,
|
|
default: 'Notification',
|
|
},
|
|
{
|
|
name: 'duration',
|
|
required: false,
|
|
type: 'number',
|
|
description: `Time in milliseconds that each toast should remain visible for. This could be overwritten at the toast level as well.`,
|
|
default: 5000,
|
|
},
|
|
{
|
|
name: 'swipeDirection',
|
|
required: false,
|
|
type: 'SwipeDirection',
|
|
description: `Direction of pointer swipe that should close the toast.`,
|
|
default: 'right',
|
|
},
|
|
{
|
|
name: 'swipeThreshold',
|
|
required: false,
|
|
type: 'number',
|
|
description: `Distance in pixels that the swipe must pass before a close is triggered.`,
|
|
default: 50,
|
|
},
|
|
{
|
|
name: 'id',
|
|
required: false,
|
|
type: 'string',
|
|
default: 'A unique generated ID',
|
|
},
|
|
{
|
|
name: 'native',
|
|
required: false,
|
|
type: 'boolean | ToastNativePlatform | ToastNativePlatform[]',
|
|
description: `Will show a native toast if is true or is set to the current platform. On iOS, it wraps \`SPIndicator\` and \`SPAlert\`. On Android, it wraps \`ToastAndroid\`. On web, it wraps Notification API. Mobile's native features are handled by \`burnt\`.`,
|
|
},
|
|
{
|
|
name: `burntOptions`,
|
|
required: false,
|
|
type: `Omit<BurntToastOptions, 'title' | 'message' | 'duration'>`,
|
|
description: `Options for the burnt package if you're using native toasts on mobile`,
|
|
},
|
|
{
|
|
name: `notificationOptions`,
|
|
required: false,
|
|
type: `NotificationOptions`,
|
|
description: `Options for the notification API if you're using native toasts on web`,
|
|
},
|
|
]}
|
|
/>
|
|
|
|
### ToastViewport
|
|
|
|
The portal for toasts to be directed to. Should be used inside [ToastProvider](#toastprovider). Beyond [Stack Props](/docs/components/stacks/1.0.0), adds:
|
|
|
|
<PropsTable
|
|
data={[
|
|
{
|
|
name: 'hotkey',
|
|
type: 'string[]',
|
|
default: "['F8']",
|
|
required: false,
|
|
description: `The keys to use as the keyboard shortcut that will move focus to the toast viewport.`,
|
|
},
|
|
{
|
|
name: 'label',
|
|
type: 'string',
|
|
default: 'Notifications ({hotkey})',
|
|
required: false,
|
|
description: `An author-localized label for the toast viewport to provide context for screen reader users when navigating page landmarks. The available \`{hotkey}\` placeholder will be replaced for you.`,
|
|
},
|
|
{
|
|
name: 'name',
|
|
type: 'string',
|
|
required: false,
|
|
description: `Used to reference the viewport if you want to have multiple viewports in the same provider.`,
|
|
},
|
|
{
|
|
name: 'multipleToasts',
|
|
type: 'boolean',
|
|
required: false,
|
|
description: `Pass this when you want to have multiple/duplicated toasts.`,
|
|
},
|
|
{
|
|
name: 'unstyled',
|
|
type: 'boolean',
|
|
default: 'false',
|
|
description: `When true, remove all default hanzogui styling.`,
|
|
},
|
|
]}
|
|
/>
|
|
|
|
### Toast
|
|
|
|
Contains the Title, Description, Action and Close component. Should be used inside [ToastProvider](#toastprovider). Extends [Stack](/docs/components/stack#api) and adds:
|
|
|
|
<PropsTable
|
|
data={[
|
|
{
|
|
name: 'forceMount',
|
|
type: 'boolean',
|
|
required: false,
|
|
description: `Used to force mounting when more control is needed. Useful when controlling animation with React animation libraries.`,
|
|
},
|
|
{
|
|
name: 'type',
|
|
type: "'foreground' | 'background'",
|
|
required: false,
|
|
description: `Control the sensitivity of the toast for accessibility purposes. For toasts that are the result of a user action, choose foreground. Toasts generated from background tasks should use background.`,
|
|
},
|
|
{
|
|
name: 'duration',
|
|
type: 'number',
|
|
required: false,
|
|
description: `Time in milliseconds that toast should remain visible for. Overrides value given to \`ToastProvider\`.`,
|
|
},
|
|
{
|
|
name: 'defaultOpen',
|
|
type: 'boolean',
|
|
required: false,
|
|
description: `The open state of the dialog when it is initially rendered. Use when you do not need to control its open state.`,
|
|
},
|
|
{
|
|
name: 'open',
|
|
type: 'boolean',
|
|
required: false,
|
|
description: `The controlled open state of the dialog. Must be used in conjunction with \`onOpenChange\`.`,
|
|
},
|
|
{
|
|
name: 'onOpenChange',
|
|
type: '(open: boolean): void',
|
|
required: false,
|
|
description: `Event handler called when the open state of the dialog changes.`,
|
|
},
|
|
{
|
|
name: 'onEscapeKeyDown',
|
|
type: "(): DismissableProps['onEscapeKeyDown']",
|
|
required: false,
|
|
description: `Event handler called when the escape key is down. It can be prevented by calling \`event.preventDefault\`.`,
|
|
},
|
|
{
|
|
name: 'onPause',
|
|
type: '(): void',
|
|
required: false,
|
|
description: `Event handler called when the dismiss timer is paused. On web, this occurs when the pointer is moved over the viewport, the viewport is focused or when the window is blurred. On mobile, this occurs when the toast is touched.`,
|
|
},
|
|
{
|
|
name: 'onResume',
|
|
type: '(): void',
|
|
required: false,
|
|
description: `Event handler called when the dismiss timer is resumed. On web, this occurs when the pointer is moved away from the viewport, the viewport is blurred or when the window is focused. On mobile, this occurs when the toast is released.`,
|
|
},
|
|
{
|
|
name: 'onSwipeStart',
|
|
type: '(event: SwipeEvent): void',
|
|
required: false,
|
|
description: `Event handler called when starting a swipe interaction. It can be prevented by calling \`event.preventDefault\`.`,
|
|
},
|
|
{
|
|
name: 'onSwipeMove',
|
|
type: '(event: SwipeEvent): void',
|
|
required: false,
|
|
description: `Event handler called during a swipe interaction. It can be prevented by calling \`event.preventDefault\`.`,
|
|
},
|
|
{
|
|
name: 'onSwipeCancel',
|
|
type: '(event: SwipeEvent): void',
|
|
required: false,
|
|
description: `Event handler called at the cancellation of a swipe interaction. It can be prevented by calling \`event.preventDefault\`.`,
|
|
},
|
|
{
|
|
name: 'onSwipeEnd',
|
|
type: '(event: SwipeEvent): void',
|
|
required: false,
|
|
description: `Event handler called at the end of a swipe interaction. It can be prevented by calling \`event.preventDefault\`.`,
|
|
},
|
|
{
|
|
name: 'viewportName',
|
|
type: 'string',
|
|
required: false,
|
|
description: `The viewport's name to send the toast to. Used when using multiple viewports and want to forward toasts to different ones.`,
|
|
default: 'default',
|
|
},
|
|
{
|
|
name: 'unstyled',
|
|
type: 'boolean',
|
|
default: 'false',
|
|
description: `When true, remove all default hanzogui styling.`,
|
|
},
|
|
]}
|
|
/>
|
|
|
|
### Toast.Title
|
|
|
|
Should be used inside [Toast](#toast). Extends [SizableText](/docs/components/text/1.0.0#sizabletext), adding:
|
|
|
|
<PropsTable
|
|
data={[
|
|
{
|
|
name: 'unstyled',
|
|
type: 'boolean',
|
|
default: 'false',
|
|
description: `When true, remove all default hanzogui styling.`,
|
|
},
|
|
]}
|
|
/>
|
|
|
|
### Toast.Description
|
|
|
|
Should be used inside [Toast](#toast). Extends [SizableText](/docs/components/text/1.0.0#sizabletext), adding:
|
|
|
|
<PropsTable
|
|
data={[
|
|
{
|
|
name: 'unstyled',
|
|
type: 'boolean',
|
|
default: 'false',
|
|
description: `When true, remove all default hanzogui styling.`,
|
|
},
|
|
]}
|
|
/>
|
|
|
|
### Toast.Close
|
|
|
|
Should be used inside [Toast](#toast). Extends [Stack](/docs/components/stacks/1.0.0). You can pass `asChild` to this component and use a custom `<Button>` inside.
|
|
|
|
### Toast.Action
|
|
|
|
Should be used inside [Toast](#toast). Extends [Stack](/docs/components/stacks/1.0.0). You can pass `asChild` to this component and use a custom `<Button>` inside.
|
|
|
|
### useToastController
|
|
|
|
Used to control the display of toasts. Should be used inside [ToastProvider](#toastprovider).
|
|
|
|
<PropsTable
|
|
title="Returns"
|
|
data={[
|
|
{
|
|
name: `show`,
|
|
type: '(title: string, showOptions?: ShowToastOptions): void',
|
|
description: `Call it to show a new toast. If you're using native toasts, you can pass native options using \`burntOptions\` or \`notificationOptions\` depending on the native platform (mobile/web).`,
|
|
},
|
|
{
|
|
name: `hide`,
|
|
type: '(): void',
|
|
description: `Call it to hide the currently displayed toast.`,
|
|
},
|
|
{
|
|
name: `options`,
|
|
type: `ToastOptions`,
|
|
description: `You can use if need to access the toast options.`,
|
|
},
|
|
]}
|
|
/>
|
|
|
|
### useToastState
|
|
|
|
Used to render out your toast contents. Should be used inside [ToastProvider](#toastprovider). Doesn't take in anything and returns `ToastData`.
|
|
|
|
```tsx
|
|
const CurrentToast = () => {
|
|
const toast = useToastState()
|
|
|
|
// don't show any toast if no toast is present or it's handled natively
|
|
if (!toast || toast.isHandledNatively) {
|
|
return null
|
|
}
|
|
|
|
return (
|
|
<Toast key={toast.id} duration={toast.duration} viewport={toast.viewport}>
|
|
<Toast.Title>{toast.title}</Toast.Title>
|
|
<Toast.Description>{toast.message}</Toast.Description>
|
|
</Toast>
|
|
)
|
|
}
|
|
```
|
|
|
|
## Examples
|
|
|
|
### Position the viewport
|
|
|
|
To position the viewport on native toasts:
|
|
|
|
- iOS (burnt): Supports top or bottom placements. Adjustable by passing `from` to `burntOptions`:
|
|
|
|
```tsx
|
|
<ToastProvider burntOptions={{ from: 'bottom' }}>
|
|
```
|
|
|
|
- Android (burnt): Not supported.
|
|
- Web (Notification API): Not supported.
|
|
|
|
To position the viewport on custom toasts:
|
|
|
|
You should change the positioning of your [`<ToastViewport>`](#toastviewport). For instance, if you want them to appear from top right:
|
|
|
|
```tsx
|
|
<ToastViewport flexDirection="column-reverse" top={0} right={0} />
|
|
```
|
|
|
|
Or for bottom center:
|
|
|
|
```tsx
|
|
<ToastViewport flexDirection="column" bottom={0} left={0} right={0} />
|
|
```
|
|
|
|
<Notice theme="blue">
|
|
When using multiple toasts, you can change the order of toasts by setting
|
|
`flexDirection` to `column` or `column-reverse`. Or even have them stack horizontally
|
|
using `row` or `row-reverse`.
|
|
</Notice>
|
|
|
|
### Mobile safe area
|
|
|
|
To show toasts inside device's safe area, install `react-native-safe-area-context` if you haven't, wrap your app inside `<SafeAreaProvider>`, and then use the safe area insets to position the viewport inside the safe area.
|
|
|
|
```tsx
|
|
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
|
|
const SafeToastViewport = () => {
|
|
const { left, top, right } = useSafeAreaInsets()
|
|
return (
|
|
<ToastViewport flexDirection="column-reverse" top={top} left={left} right={right} />
|
|
)
|
|
}
|
|
```
|
|
|
|
### Different viewports
|
|
|
|
To send toasts to different viewports, you can set up different viewports:
|
|
|
|
```tsx
|
|
const App = () => {
|
|
return (
|
|
<ToastProvider>
|
|
<ToastViewport /> {/* default viewport */}
|
|
<ToastViewport name="viewport-custom" />
|
|
</ToastProvider>
|
|
)
|
|
}
|
|
```
|
|
|
|
And then, use the viewport's name on the toasts.
|
|
|
|
```tsx
|
|
const MyComponent = () => {
|
|
return <Toast /> // default viewport
|
|
}
|
|
|
|
const MyComponent2 = () => {
|
|
return <Toast viewportName="viewport-custom" />
|
|
}
|
|
```
|
|
|
|
### Custom data
|
|
|
|
Just pass your custom data to the second parameter of the `show()` method.
|
|
|
|
```ts
|
|
const toastController = useToastController()
|
|
toastController.show('Title', { myPreset: 'error' }) // or toastController.show("Title", { customData: { myPreset: 'error' } })
|
|
```
|
|
|
|
then, when showing the toast, you can retrieve them like so:
|
|
|
|
```ts
|
|
const toastState = useToastState()
|
|
toastState.myPreset // or toastState.customData.myPreset
|
|
```
|
|
|
|
To add TypeScript auto-completion for your custom fields, you can use TS module augmentation:
|
|
|
|
```ts
|
|
declare module '@hanzogui/toast' {
|
|
interface CustomData {
|
|
myPreset: 'error' | 'success' | 'warning'
|
|
}
|
|
}
|
|
```
|
|
|
|
### Without hooks
|
|
|
|
You can also use toasts without the hooks.
|
|
|
|
<Notice>You can't use native toasts this way.</Notice>
|
|
|
|
#### Single Toast
|
|
|
|
```tsx
|
|
export default () => {
|
|
const [open, setOpen] = React.useState(false)
|
|
const timerRef = React.useRef(0)
|
|
|
|
React.useEffect(() => {
|
|
return () => clearTimeout(timerRef.current)
|
|
}, [])
|
|
|
|
return (
|
|
<YStack ai="center">
|
|
<Button
|
|
onPress={() => {
|
|
setOpen(false)
|
|
window.clearTimeout(timerRef.current)
|
|
timerRef.current = window.setTimeout(() => {
|
|
setOpen(true)
|
|
}, 150)
|
|
}}
|
|
>
|
|
Single Toast
|
|
</Button>
|
|
<Toast
|
|
onOpenChange={setOpen}
|
|
open={open}
|
|
animation="100ms"
|
|
enterStyle={{ x: -20, opacity: 0 }}
|
|
exitStyle={{ x: -20, opacity: 0 }}
|
|
opacity={1}
|
|
x={0}
|
|
>
|
|
<Toast.Title>Subscribed!</Toast.Title>
|
|
<Toast.Description>We'll be in touch.</Toast.Description>
|
|
</Toast>
|
|
</YStack>
|
|
)
|
|
}
|
|
```
|
|
|
|
#### Multiple Toasts
|
|
|
|
<Notice>
|
|
To use multiple toasts, you should pass `multipleToasts` to your `ToastViewport`.
|
|
Otherwise there'll be issues when swipe-dismissing or animating toasts.
|
|
</Notice>
|
|
|
|
```tsx
|
|
export default () => {
|
|
const [savedCount, setSavedCount] = React.useState(0)
|
|
|
|
return (
|
|
<YStack ai="center">
|
|
<Button
|
|
onPress={() => {
|
|
setSavedCount((old) => old + 1)
|
|
}}
|
|
>
|
|
Show toast
|
|
</Button>
|
|
{[...Array(savedCount)].map((_, index) => (
|
|
<Toast
|
|
key={index}
|
|
animation="100ms"
|
|
enterStyle={{ x: -20, opacity: 0 }}
|
|
exitStyle={{ x: -20, opacity: 0 }}
|
|
opacity={1}
|
|
x={0}
|
|
>
|
|
<Toast.Title>Subscribed!</Toast.Title>
|
|
<Toast.Description>We'll be in touch.</Toast.Description>
|
|
</Toast>
|
|
))}
|
|
</YStack>
|
|
)
|
|
}
|
|
```
|