Skip to content

Commit

Permalink
Global queue, stacking, animations
Browse files Browse the repository at this point in the history
  • Loading branch information
atomiks committed Feb 25, 2025
1 parent c000a63 commit d21e28c
Show file tree
Hide file tree
Showing 23 changed files with 733 additions and 45 deletions.
16 changes: 16 additions & 0 deletions docs/reference/generated/toast-close.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "ToastClose",
"description": "",
"props": {
"className": {
"type": "string | (state) => string",
"description": "CSS class applied to the element, or a function that\nreturns a class based on the component’s state."
},
"render": {
"type": "React.ReactElement | (props, state) => React.ReactElement",
"description": "Allows you to replace the component’s HTML element\nwith a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
}
},
"dataAttributes": {},
"cssVariables": {}
}
16 changes: 16 additions & 0 deletions docs/reference/generated/toast-content.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "ToastContent",
"description": "",
"props": {
"className": {
"type": "string | (state) => string",
"description": "CSS class applied to the element, or a function that\nreturns a class based on the component’s state."
},
"render": {
"type": "React.ReactElement | (props, state) => React.ReactElement",
"description": "Allows you to replace the component’s HTML element\nwith a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
}
},
"dataAttributes": {},
"cssVariables": {}
}
16 changes: 16 additions & 0 deletions docs/reference/generated/toast-description.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "ToastDescription",
"description": "",
"props": {
"className": {
"type": "string | (state) => string",
"description": "CSS class applied to the element, or a function that\nreturns a class based on the component’s state."
},
"render": {
"type": "React.ReactElement | (props, state) => React.ReactElement",
"description": "Allows you to replace the component’s HTML element\nwith a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
}
},
"dataAttributes": {},
"cssVariables": {}
}
7 changes: 7 additions & 0 deletions docs/reference/generated/toast-provider.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "ToastProvider",
"description": "",
"props": {},
"dataAttributes": {},
"cssVariables": {}
}
16 changes: 16 additions & 0 deletions docs/reference/generated/toast-root.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "ToastRoot",
"description": "",
"props": {
"className": {
"type": "string | (state) => string",
"description": "CSS class applied to the element, or a function that\nreturns a class based on the component’s state."
},
"render": {
"type": "React.ReactElement | (props, state) => React.ReactElement",
"description": "Allows you to replace the component’s HTML element\nwith a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
}
},
"dataAttributes": {},
"cssVariables": {}
}
16 changes: 16 additions & 0 deletions docs/reference/generated/toast-title.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "ToastTitle",
"description": "",
"props": {
"className": {
"type": "string | (state) => string",
"description": "CSS class applied to the element, or a function that\nreturns a class based on the component’s state."
},
"render": {
"type": "React.ReactElement | (props, state) => React.ReactElement",
"description": "Allows you to replace the component’s HTML element\nwith a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
}
},
"dataAttributes": {},
"cssVariables": {}
}
16 changes: 16 additions & 0 deletions docs/reference/generated/toast-viewport.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "ToastViewport",
"description": "",
"props": {
"className": {
"type": "string | (state) => string",
"description": "CSS class applied to the element, or a function that\nreturns a class based on the component’s state."
},
"render": {
"type": "React.ReactElement | (props, state) => React.ReactElement",
"description": "Allows you to replace the component’s HTML element\nwith a different tag, or compose it with another component.\n\nAccepts a `ReactElement` or a function that returns the element to render."
}
},
"dataAttributes": {},
"cssVariables": {}
}
56 changes: 56 additions & 0 deletions docs/src/app/(private)/experiments/toast.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
.viewport {
width: 300px;
position: fixed;
left: 50%;
top: 20px;
transform: translateX(-50%);
}

.root {
position: absolute;
left: 0;
right: 0;
margin: 0 auto;
transform-origin: top center;
background: white;
padding: 1rem;
width: 300px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
border-radius: 10px;
z-index: calc(1000 - var(--toast-index));
transition:
transform 0.5s cubic-bezier(0.22, 1, 0.36, 1),
opacity 0.5s;
transform: translateY(calc(var(--toast-index) * 15px)) scale(calc(1 - (var(--toast-index) * 0.1)));
opacity: 1;
--gap: 10px;

&::after {
content: '';
position: absolute;
left: 0;
top: 100%;
width: 100%;
height: calc(var(--gap) + 1px);
}
}

.root[data-expanded] {
transform: translateY(calc(var(--toast-offset) + calc(var(--toast-index) * var(--gap))));
}

.root[data-starting-style],
.root[data-ending-style] {
transform: translateY(-100%);
opacity: 0;
}

.root[data-ending-style] {
transition-duration: 0.2s;
transition-timing-function: ease-in;
}

.title {
font-weight: 600;
margin-bottom: 4px;
}
83 changes: 70 additions & 13 deletions docs/src/app/(private)/experiments/toast.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,96 @@
'use client';
import * as React from 'react';
import { Toast, useToast } from '@base-ui-components/react/toast';
import { Toast } from '@base-ui-components/react/toast';
import styles from './toast.module.css';

function showGlobalToast() {
Toast.add({
title: 'Global Toast',
description: 'This toast was created outside of a React component',
type: 'message',
});
}

function fetchUserData() {
return new Promise((resolve, reject) => {
// Simulate API call with a 50% chance of success
setTimeout(() => {
const success = Math.random() > 0.1;
if (success) {
resolve({ name: 'John Doe', email: '[email protected]' });
} else {
reject(new Error('Failed to fetch user data'));
}
}, 1000);
});
}

function ToastPromiseExample() {
const { promise } = Toast.useToast();

const handlePromiseClick = () => {
promise(fetchUserData(), {
loading: 'Fetching user data...',
success: 'User data loaded!',
error: 'Failed to load user data',
})
.then((data) => {
console.log('User data:', data);
})
.catch((err) => {
console.error('Error handled:', err);
});
};

return (
<button type="button" onClick={handlePromiseClick}>
Try Toast Promise
</button>
);
}

export default function Page() {
return (
<Toast.Provider>
<Toast.Viewport
style={{
position: 'fixed',
left: '50%',
top: 50,
transform: 'translatex(-50%)',
}}
>
<Toast.Viewport className={styles.viewport}>
<Toasts />
</Toast.Viewport>
<CreateToast />

<div
style={{
marginTop: '20px',
display: 'flex',
gap: '10px',
justifyContent: 'center',
}}
>
<button type="button" onClick={showGlobalToast}>
Show Global Toast
</button>
<ToastPromiseExample />
</div>
</Toast.Provider>
);
}

function Toasts() {
const { toasts } = useToast();
const { toasts } = Toast.useToast();
return toasts.map((toast) => (
<Toast.Root key={toast.id} toast={toast}>
<Toast.Root key={toast.id} toast={toast} className={styles.root}>
<Toast.Content>
<Toast.Description>{toast.description}</Toast.Description>
{toast.title && <div className={styles.title}>{toast.title}</div>}
{toast.description && (
<Toast.Description>{toast.description}</Toast.Description>
)}
</Toast.Content>
<Toast.Close>Close</Toast.Close>
</Toast.Root>
));
}

function CreateToast() {
const toast = useToast();
const toast = Toast.useToast();
return (
<button
style={{ position: 'fixed', right: 20, bottom: 20 }}
Expand Down
32 changes: 32 additions & 0 deletions docs/src/app/(public)/(content)/react/components/toast/page.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Toast

<Subtitle>A component for generating toast notifications.</Subtitle>

<Meta
name="description"
content="A high-quality, unstyled React toast component that generates notifications."
/>

<Demo path="./demos/hero" />

## API reference

Import the component and assemble its parts:

```jsx title="Anatomy"
import { Toast } from '@base-ui-components/react/toast';

<Toast.Provider>
<Toast.Viewport>
<Toast.Root>
<Toast.Content>
<Toast.Title />
<Toast.Description />
</Toast.Content>
<Toast.Close />
</Toast.Root>
</Toast.Viewport>
</Toast.Provider>;
```

<Reference component="Toast" parts="Provider, Viewport, Root, Title, Description, Content, Close" />
29 changes: 28 additions & 1 deletion packages/react/src/toast/close/ToastClose.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client';
import * as React from 'react';
import PropTypes from 'prop-types';
import type { BaseUIComponentProps } from '../../utils/types';
import { useComponentRenderer } from '../../utils/useComponentRenderer';
import { useToastRootContext } from '../root/ToastRootContext';
Expand All @@ -8,7 +9,10 @@ import { useToastContext } from '../provider/ToastProviderContext';
import { useToastViewportContext } from '../viewport/ToastViewportContext';

const state = {};

/**
*
* Documentation: [Base UI Toast](https://base-ui.com/react/components/toast)
*/
const ToastClose = React.forwardRef(function ToastClose(
props: ToastClose.Props,
ref: React.ForwardedRef<HTMLButtonElement>,
Expand Down Expand Up @@ -57,6 +61,29 @@ const ToastClose = React.forwardRef(function ToastClose(
return renderElement();
});

ToastClose.propTypes /* remove-proptypes */ = {
// ┌────────────────────────────── Warning ──────────────────────────────┐
// │ These PropTypes are generated from the TypeScript type definitions. │
// │ To update them, edit the TypeScript types and run `pnpm proptypes`. │
// └─────────────────────────────────────────────────────────────────────┘
/**
* @ignore
*/
children: PropTypes.node,
/**
* CSS class applied to the element, or a function that
* returns a class based on the component’s state.
*/
className: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
/**
* Allows you to replace the component’s HTML element
* with a different tag, or compose it with another component.
*
* Accepts a `ReactElement` or a function that returns the element to render.
*/
render: PropTypes.oneOfType([PropTypes.element, PropTypes.func]),
} as any;

namespace ToastClose {
export interface State {}

Expand Down
Loading

0 comments on commit d21e28c

Please sign in to comment.