Skip to content

Commit dbb4d41

Browse files
refactor: Toast core and add docs
1 parent 4fdcb82 commit dbb4d41

25 files changed

Lines changed: 980 additions & 99 deletions

File tree

apps/showcase/__store__/index.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,12 @@ export const Store: Record<string, Record<string, Record<string, { component: Re
442442
'filePath': 'demo/headless/timeline/basic-demo.tsx',
443443
},
444444
},
445+
'toast': {
446+
'basic-demo': {
447+
'component': React.lazy(() => import('demo/headless/toast/basic-demo')),
448+
'filePath': 'demo/headless/toast/basic-demo.tsx',
449+
},
450+
},
445451
'togglebutton': {
446452
'basic-demo': {
447453
'component': React.lazy(() => import('demo/headless/togglebutton/basic-demo')),
@@ -822,6 +828,12 @@ export const Store: Record<string, Record<string, Record<string, { component: Re
822828
'filePath': 'demo/primitives/timeline/basic-demo.tsx',
823829
},
824830
},
831+
'toast': {
832+
'basic-demo': {
833+
'component': React.lazy(() => import('demo/primitives/toast/basic-demo')),
834+
'filePath': 'demo/primitives/toast/basic-demo.tsx',
835+
},
836+
},
825837
'togglebutton': {
826838
'basic-demo': {
827839
'component': React.lazy(() => import('demo/primitives/togglebutton/basic-demo')),

apps/showcase/assets/menu/submenu/menu-headless.data.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ export const headlessMenu = [
185185
children: [
186186
{
187187
name: 'useUpload',
188-
href: '/docs/headless/upload'
188+
href: '/docs/headless/fileupload'
189189
}
190190
]
191191
},
@@ -216,6 +216,10 @@ export const headlessMenu = [
216216
{
217217
name: 'useMessage',
218218
href: '/docs/headless/message'
219+
},
220+
{
221+
name: 'useToast',
222+
href: '/docs/headless/toast'
219223
}
220224
]
221225
},

apps/showcase/assets/menu/submenu/menu-primitives.data.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ export const primitivesMenu = [
185185
children: [
186186
{
187187
name: 'Upload',
188-
href: '/docs/primitives/upload'
188+
href: '/docs/primitives/fileupload'
189189
}
190190
]
191191
},
@@ -216,6 +216,10 @@ export const primitivesMenu = [
216216
{
217217
name: 'Message',
218218
href: '/docs/primitives/message'
219+
},
220+
{
221+
name: 'Toast',
222+
href: '/docs/primitives/toast'
219223
}
220224
]
221225
},

apps/showcase/demo/headless/scrollarea/basic-demo.tsx

Lines changed: 39 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,45 @@
11
'use client';
22
import { useScrollArea } from '@primereact/headless/scrollarea';
33

4+
export default function BasicDemo() {
5+
const { rootProps, viewportProps, contentProps, scrollbarYProps, thumbYProps, hiddenState } = useScrollArea();
6+
7+
return (
8+
<div className="max-w-56 w-full mx-auto">
9+
<div
10+
{...rootProps}
11+
className="relative h-72 border border-surface-200 dark:border-surface-700 rounded has-[:focus-visible]:border-primary overflow-hidden"
12+
>
13+
<div {...viewportProps} className="h-full w-full overflow-scroll outline-none" style={{ scrollbarWidth: 'none' }}>
14+
<div {...contentProps} className="flex flex-col p-1">
15+
{tags.map((tag) => (
16+
<div key={tag} className="py-2 px-3 text-sm text-surface-700 dark:text-surface-0 rounded-md">
17+
{tag}
18+
</div>
19+
))}
20+
</div>
21+
</div>
22+
{!hiddenState.y && (
23+
<div
24+
{...scrollbarYProps}
25+
className="absolute top-0 right-0 w-2.5 h-full flex touch-none select-none"
26+
style={{ padding: '2px' }}
27+
>
28+
<div
29+
{...thumbYProps}
30+
className="relative rounded-full bg-primary hover:bg-primary-emphasis transition-colors flex-1"
31+
style={{
32+
height: 'var(--thumb-height, 40px)',
33+
transform: 'translate3d(0, var(--thumb-offset, 0), 0)'
34+
}}
35+
/>
36+
</div>
37+
)}
38+
</div>
39+
</div>
40+
);
41+
}
42+
443
const tags = [
544
'Autocomplete',
645
'Avatar',
@@ -55,42 +94,3 @@ const tags = [
5594
'Tree',
5695
'Tree Table'
5796
];
58-
59-
export default function BasicDemo() {
60-
const { rootProps, viewportProps, contentProps, getScrollbarProps, getThumbProps, hiddenState } = useScrollArea();
61-
62-
return (
63-
<div className="max-w-56 w-full mx-auto">
64-
<div
65-
{...rootProps}
66-
className="relative h-72 border border-surface-200 dark:border-surface-700 rounded has-[:focus-visible]:border-primary overflow-hidden"
67-
>
68-
<div {...viewportProps} className="h-full w-full overflow-scroll outline-none" style={{ scrollbarWidth: 'none' }}>
69-
<div {...contentProps} className="flex flex-col p-1">
70-
{tags.map((tag) => (
71-
<div key={tag} className="py-2 px-3 text-sm text-surface-700 dark:text-surface-0 rounded-md">
72-
{tag}
73-
</div>
74-
))}
75-
</div>
76-
</div>
77-
{!hiddenState.y && (
78-
<div
79-
{...getScrollbarProps('vertical')}
80-
className="absolute top-0 right-0 w-2.5 h-full flex touch-none select-none"
81-
style={{ padding: '2px' }}
82-
>
83-
<div
84-
{...getThumbProps('vertical')}
85-
className="relative rounded-full bg-primary hover:bg-primary-emphasis transition-colors flex-1"
86-
style={{
87-
height: 'var(--thumb-height, 40px)',
88-
transform: 'translate3d(0, var(--thumb-offset, 0), 0)'
89-
}}
90-
/>
91-
</div>
92-
)}
93-
</div>
94-
</div>
95-
);
96-
}

apps/showcase/demo/headless/timeline/basic-demo.tsx

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,25 @@ const events = [
99
];
1010

1111
export default function BasicDemo() {
12-
const { rootProps } = useTimeline();
12+
const { rootProps, eventProps, separatorProps, markerProps, connectorProps, contentProps, oppositeProps } = useTimeline();
1313

1414
return (
15-
<div className="max-w-md mx-auto">
15+
<div className="max-w-lg mx-auto">
1616
<div {...rootProps} className="flex flex-col">
1717
{events.map((event, index) => (
18-
<div key={index} className="flex gap-4">
19-
<div className="flex flex-col items-center">
20-
<div className="w-3 h-3 rounded-full bg-primary shrink-0" />
21-
{index !== events.length - 1 && <div className="w-0.5 grow bg-surface-200 dark:bg-surface-700" />}
18+
<div key={index} {...eventProps} className="flex gap-4 relative min-h-16">
19+
<div {...oppositeProps} className="flex-1 text-right text-xs text-surface-500 dark:text-surface-400 pt-0.5">
20+
{event.date}
2221
</div>
23-
<div className="pb-6">
24-
<div className="text-sm font-medium text-surface-700 dark:text-surface-0">{event.status}</div>
25-
<div className="text-xs text-surface-500 dark:text-surface-400 mt-1">{event.date}</div>
22+
<div {...separatorProps} className="flex flex-col items-center">
23+
<div
24+
{...markerProps}
25+
className="inline-flex items-center justify-center relative self-baseline size-4 shrink-0 rounded-full border-2 border-surface-200 dark:border-surface-700 bg-surface-0 dark:bg-surface-900 before:rounded-full before:size-1.5 before:bg-primary after:absolute after:size-full after:rounded-full after:shadow-[0px_0.5px_0px_0px_rgba(0,0,0,0.06),0px_1px_1px_0px_rgba(0,0,0,0.12)]"
26+
/>
27+
{index !== events.length - 1 && <div {...connectorProps} className="w-0.5 grow bg-surface-200 dark:bg-surface-700" />}
28+
</div>
29+
<div {...contentProps} className="flex-1 text-sm font-medium text-surface-700 dark:text-surface-0 pb-6">
30+
{event.status}
2631
</div>
2732
</div>
2833
))}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
'use client';
2+
import { Check } from '@primeicons/react/check';
3+
import { ExclamationTriangle } from '@primeicons/react/exclamation-triangle';
4+
import { InfoCircle } from '@primeicons/react/info-circle';
5+
import { Times } from '@primeicons/react/times';
6+
import { usePortal } from '@primereact/headless/portal';
7+
import { useToast } from '@primereact/headless/toast';
8+
import { toast, useToaster } from '@primereact/headless/toaster';
9+
import { ToastType } from 'primereact/toaster';
10+
import * as React from 'react';
11+
import { createPortal } from 'react-dom';
12+
13+
const variantIcons: Record<string, React.ReactNode> = {
14+
success: <Check />,
15+
danger: <Times />,
16+
warn: <ExclamationTriangle />,
17+
info: <InfoCircle />
18+
};
19+
20+
function ToastItem({ toastData, toaster }: { toastData: ToastType; toaster: ReturnType<typeof useToaster> }) {
21+
const { rootProps, closeProps } = useToast({ toast: toastData, toaster });
22+
23+
return (
24+
<div
25+
{...rootProps}
26+
className={[
27+
// base
28+
'w-full p-4 rounded-lg border border-surface-200 dark:border-surface-800 bg-surface-0 dark:bg-surface-900 text-surface-900 dark:text-surface-0',
29+
'outline-none absolute touch-none shadow-sm [z-index:var(--toast-z-index)]',
30+
'opacity-0 [transform:translateX(var(--offset-x))_translateY(calc(100%*var(--raise-factor)*-1))]',
31+
'[transition:transform_0.3s,opacity_0.3s,height_0.3s]',
32+
// focus
33+
'focus-visible:outline-1 focus-visible:outline-primary focus-visible:outline-offset-2',
34+
// mounted
35+
'data-[mounted]:opacity-100 data-[mounted]:[transform:translateY(0)]',
36+
// collapsed stack
37+
'not-data-[expanded]:not-data-[front]:overflow-hidden not-data-[expanded]:not-data-[front]:[height:var(--front-toast-height)] not-data-[expanded]:not-data-[front]:[transform:translateX(var(--offset-x))_translateY(calc(var(--raise-factor)*var(--toast-index)*var(--gap)))_scale(calc(var(--toast-index)*-0.05+1))]',
38+
// expanded
39+
'data-[mounted]:data-[expanded]:[height:var(--initial-height)] data-[mounted]:data-[expanded]:[transform:translateX(var(--offset-x))_translateY(var(--offset-y))]',
40+
// expanded gap pseudo
41+
'data-[expanded]:after:content-[""] data-[expanded]:after:absolute data-[expanded]:after:left-0 data-[expanded]:after:[height:calc(var(--gap)+1px)] data-[expanded]:after:w-full data-[expanded]:after:bottom-full',
42+
// hidden
43+
'not-data-[visible]:!opacity-0 not-data-[visible]:!pointer-events-none not-data-[visible]:!select-none',
44+
// removed front
45+
'data-[removed]:data-[front]:not-data-[swipe-out]:opacity-0 data-[removed]:data-[front]:not-data-[swipe-out]:[transform:translateX(var(--offset-x))_translateY(calc(var(--raise-factor)*-100%))]',
46+
// removed non-front expanded
47+
'data-[removed]:not-data-[front]:not-data-[swipe-out]:data-[expanded]:opacity-0 data-[removed]:not-data-[front]:not-data-[swipe-out]:data-[expanded]:[transform:translateX(var(--offset-x))_translateY(calc(var(--raise-factor)*var(--offset-y)*0.4))]',
48+
// removed non-front collapsed
49+
'data-[removed]:not-data-[front]:not-data-[swipe-out]:not-data-[expanded]:opacity-0 data-[removed]:not-data-[front]:not-data-[swipe-out]:not-data-[expanded]:[transform:translateX(var(--offset-x))_translateY(calc(var(--raise-factor)*40%*-1))] data-[removed]:not-data-[front]:not-data-[swipe-out]:not-data-[expanded]:[transition:transform_500ms,opacity_200ms]',
50+
// swiping
51+
'data-[swiping]:![transition:none] data-[swiping]:![transform:translateX(var(--offset-x))_translateY(var(--offset-y))]',
52+
'data-[swiped]:select-none',
53+
// swipe out directions
54+
'data-[swipe-out]:data-[swipe-direction=up]:opacity-0 data-[swipe-out]:data-[swipe-direction=up]:![transform:translateX(var(--offset-x))_translateY(calc(var(--offset-y)-100%))]',
55+
'data-[swipe-out]:data-[swipe-direction=down]:opacity-0 data-[swipe-out]:data-[swipe-direction=down]:![transform:translateX(var(--offset-x))_translateY(calc(var(--offset-y)+100%))]',
56+
'data-[swipe-out]:data-[swipe-direction=left]:opacity-0 data-[swipe-out]:data-[swipe-direction=left]:![transform:translateX(calc(var(--offset-x)-100%))_translateY(var(--offset-y))]',
57+
'data-[swipe-out]:data-[swipe-direction=right]:opacity-0 data-[swipe-out]:data-[swipe-direction=right]:![transform:translateX(calc(var(--offset-x)+100%))_translateY(var(--offset-y))] data-[swipe-out]:data-[swipe-direction=right]:[transition:transform_500ms,opacity_200ms]'
58+
].join(' ')}
59+
style={
60+
{
61+
...rootProps.style,
62+
'--offset-y': `calc(var(--swipe-amount-y) + (var(--toast-offset) + var(--toast-index) * var(--gap)) * var(--raise-factor))`,
63+
'--offset-x': 'var(--swipe-amount-x)',
64+
bottom: 0,
65+
right: 0,
66+
'--raise-factor': '-1'
67+
} as React.CSSProperties
68+
}
69+
>
70+
<div className="grid grid-cols-[auto_1fr] items-start gap-3">
71+
{variantIcons[toastData.variant as string] && (
72+
<span className="[&>svg]:size-3.5 mt-1 text-surface-800 dark:text-surface-100">{variantIcons[toastData.variant as string]}</span>
73+
)}
74+
<div>
75+
{toastData.title && <div className="text-sm font-semibold text-surface-800 dark:text-surface-100">{toastData.title}</div>}
76+
{toastData.description && <div className="text-sm text-surface-500 dark:text-surface-400 mt-1">{toastData.description}</div>}
77+
</div>
78+
</div>
79+
{toastData.dismissible !== false && toastData.variant !== 'loading' && (
80+
<button
81+
{...closeProps}
82+
className="absolute top-2 right-2 p-1 rounded text-surface-400 hover:text-surface-600 dark:hover:text-surface-200 cursor-pointer focus-visible:outline-1 focus-visible:outline-primary"
83+
>
84+
<Times className="size-3" />
85+
</button>
86+
)}
87+
</div>
88+
);
89+
}
90+
91+
export default function BasicDemo() {
92+
const toaster = useToaster({ position: 'bottom-right', group: 'headless-basic' });
93+
const portal = usePortal();
94+
95+
return (
96+
<div className="flex flex-wrap items-center justify-center gap-4">
97+
<button
98+
onClick={() =>
99+
toast.success({
100+
title: 'Saved successfully',
101+
description: 'Your changes have been saved.',
102+
group: 'headless-basic'
103+
})
104+
}
105+
className="px-2.5 py-1.5 text-sm rounded-md border border-surface-200 dark:border-surface-700 text-surface-700 dark:text-surface-200 hover:bg-surface-50 dark:hover:bg-surface-800 cursor-pointer focus-visible:outline-1 focus-visible:outline-primary"
106+
>
107+
Create toast
108+
</button>
109+
{portal.state.mounted &&
110+
createPortal(
111+
<div {...toaster.regionProps} className="fixed w-75 z-[2000] right-8 bottom-8" style={toaster.regionProps.style}>
112+
{toaster.toasts.map((t) => (
113+
<ToastItem key={t.id} toastData={t} toaster={toaster} />
114+
))}
115+
</div>,
116+
117+
document.body
118+
)}
119+
</div>
120+
);
121+
}

apps/showcase/demo/primitives/timeline/basic-demo.module.css

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,36 @@
3030
}
3131

3232
.marker {
33-
width: 0.75rem;
34-
height: 0.75rem;
33+
display: inline-flex;
34+
align-items: center;
35+
justify-content: center;
36+
position: relative;
37+
align-self: baseline;
38+
width: 1rem;
39+
height: 1rem;
3540
border-radius: 9999px;
36-
background-color: var(--p-primary-color);
41+
border: 2px solid var(--p-content-border-color);
42+
background-color: light-dark(var(--p-surface-0), var(--p-surface-900));
3743
flex-shrink: 0;
3844
}
3945

46+
.marker::before {
47+
content: '';
48+
width: 0.375rem;
49+
height: 0.375rem;
50+
border-radius: 9999px;
51+
background-color: var(--p-primary-color);
52+
}
53+
54+
.marker::after {
55+
content: '';
56+
position: absolute;
57+
width: 100%;
58+
height: 100%;
59+
border-radius: 9999px;
60+
box-shadow: 0px 0.5px 0px 0px rgba(0, 0, 0, 0.06), 0px 1px 1px 0px rgba(0, 0, 0, 0.12);
61+
}
62+
4063
.connector {
4164
width: 2px;
4265
flex-grow: 1;

0 commit comments

Comments
 (0)