@octanejs/sonner 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +76 -0
- package/package.json +51 -0
- package/src/assets.tsrx +103 -0
- package/src/hooks.tsrx +18 -0
- package/src/index.ts +8 -0
- package/src/sonner.tsrx +831 -0
- package/src/sonner.tsrx.d.ts +6 -0
- package/src/state.ts +292 -0
- package/src/styles.css +756 -0
- package/src/types.ts +226 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dominic Gannaway
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# @octanejs/sonner
|
|
2
|
+
|
|
3
|
+
[Sonner](https://sonner.emilkowal.ski/) ported to the
|
|
4
|
+
[Octane](https://github.com/octanejs/octane) renderer. The package tracks the
|
|
5
|
+
published `sonner@2.0.7` API and behavior: the callable `toast` API, `Toaster`,
|
|
6
|
+
`useSonner`, promise toasts, multiple toaster targets, actions, themes, rich
|
|
7
|
+
colors, stacking, timers, keyboard focus, and swipe dismissal.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pnpm add @octanejs/sonner
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
Mount one toaster near the root of the application, then call `toast` from any
|
|
18
|
+
event handler or module:
|
|
19
|
+
|
|
20
|
+
```tsrx
|
|
21
|
+
import { Toaster, toast } from '@octanejs/sonner';
|
|
22
|
+
|
|
23
|
+
export function App() {
|
|
24
|
+
return (
|
|
25
|
+
<main>
|
|
26
|
+
<button
|
|
27
|
+
onClick={() =>
|
|
28
|
+
toast.success('Saved', {
|
|
29
|
+
description: 'Your changes are live.',
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
>
|
|
33
|
+
Save
|
|
34
|
+
</button>
|
|
35
|
+
<Toaster position="top-right" richColors />
|
|
36
|
+
</main>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The component imports its default stylesheet automatically. The stylesheet is
|
|
42
|
+
also available explicitly as `@octanejs/sonner/dist/styles.css`.
|
|
43
|
+
|
|
44
|
+
Promise and custom toasts keep Sonner's API:
|
|
45
|
+
|
|
46
|
+
```tsrx
|
|
47
|
+
toast.promise(saveProject(), {
|
|
48
|
+
loading: 'Saving…',
|
|
49
|
+
success: (project) => `${project.name} saved`,
|
|
50
|
+
error: 'Could not save the project',
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
toast.custom((id) => (
|
|
54
|
+
<div>
|
|
55
|
+
Custom content
|
|
56
|
+
<button onClick={() => toast.dismiss(id)}>Dismiss</button>
|
|
57
|
+
</div>
|
|
58
|
+
));
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Octane adaptations
|
|
62
|
+
|
|
63
|
+
- Action and cancel handlers receive native `MouseEvent` objects because
|
|
64
|
+
Octane uses native delegated events.
|
|
65
|
+
- `Toaster` accepts `ref` as a normal prop; Octane does not use `forwardRef`.
|
|
66
|
+
- The document-visibility initializer is guarded for server rendering.
|
|
67
|
+
|
|
68
|
+
Everything else intentionally follows published `sonner@2.0.7`, including its
|
|
69
|
+
DOM attributes and stylesheet. Differential tests run the same `.tsrx` fixture
|
|
70
|
+
through this package and real Sonner on React and compare the resulting DOM.
|
|
71
|
+
|
|
72
|
+
See the [port plan](../../docs/sonner-port-plan.md) and generated
|
|
73
|
+
[bindings status](../../docs/bindings-status.md) for scope and evidence.
|
|
74
|
+
|
|
75
|
+
Sonner is Copyright (c) Emil Kowalski and contributors and distributed under
|
|
76
|
+
the MIT license.
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@octanejs/sonner",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=22"
|
|
8
|
+
},
|
|
9
|
+
"description": "Sonner for the octane renderer — an API- and behavior-compatible port of the opinionated React toast component.",
|
|
10
|
+
"author": {
|
|
11
|
+
"name": "Dominic Gannaway",
|
|
12
|
+
"email": "dg@domgan.com"
|
|
13
|
+
},
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/octanejs/octane.git",
|
|
20
|
+
"directory": "packages/sonner"
|
|
21
|
+
},
|
|
22
|
+
"main": "src/index.ts",
|
|
23
|
+
"module": "src/index.ts",
|
|
24
|
+
"types": "src/index.ts",
|
|
25
|
+
"files": [
|
|
26
|
+
"src",
|
|
27
|
+
"README.md"
|
|
28
|
+
],
|
|
29
|
+
"sideEffects": [
|
|
30
|
+
"./src/styles.css"
|
|
31
|
+
],
|
|
32
|
+
"exports": {
|
|
33
|
+
".": "./src/index.ts",
|
|
34
|
+
"./dist/styles.css": "./src/styles.css"
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"octane": "0.1.5"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@tsrx/react": "^0.2.37",
|
|
41
|
+
"esbuild": "^0.28.1",
|
|
42
|
+
"react": "^19.2.0",
|
|
43
|
+
"react-dom": "^19.2.0",
|
|
44
|
+
"sonner": "2.0.7",
|
|
45
|
+
"vitest": "^4.1.9",
|
|
46
|
+
"octane": "0.1.5"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"test": "vitest run"
|
|
50
|
+
}
|
|
51
|
+
}
|
package/src/assets.tsrx
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// Ported from sonner@2.0.7 src/assets.tsx.
|
|
2
|
+
import type { ToastTypes } from './types';
|
|
3
|
+
|
|
4
|
+
const SuccessIcon = <svg
|
|
5
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
6
|
+
viewBox="0 0 20 20"
|
|
7
|
+
fill="currentColor"
|
|
8
|
+
height="20"
|
|
9
|
+
width="20"
|
|
10
|
+
>
|
|
11
|
+
<path
|
|
12
|
+
fillRule="evenodd"
|
|
13
|
+
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z"
|
|
14
|
+
clipRule="evenodd"
|
|
15
|
+
/>
|
|
16
|
+
</svg>;
|
|
17
|
+
|
|
18
|
+
const WarningIcon = <svg
|
|
19
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
20
|
+
viewBox="0 0 24 24"
|
|
21
|
+
fill="currentColor"
|
|
22
|
+
height="20"
|
|
23
|
+
width="20"
|
|
24
|
+
>
|
|
25
|
+
<path
|
|
26
|
+
fillRule="evenodd"
|
|
27
|
+
d="M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z"
|
|
28
|
+
clipRule="evenodd"
|
|
29
|
+
/>
|
|
30
|
+
</svg>;
|
|
31
|
+
|
|
32
|
+
const InfoIcon = <svg
|
|
33
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
34
|
+
viewBox="0 0 20 20"
|
|
35
|
+
fill="currentColor"
|
|
36
|
+
height="20"
|
|
37
|
+
width="20"
|
|
38
|
+
>
|
|
39
|
+
<path
|
|
40
|
+
fillRule="evenodd"
|
|
41
|
+
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z"
|
|
42
|
+
clipRule="evenodd"
|
|
43
|
+
/>
|
|
44
|
+
</svg>;
|
|
45
|
+
|
|
46
|
+
const ErrorIcon = <svg
|
|
47
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
48
|
+
viewBox="0 0 20 20"
|
|
49
|
+
fill="currentColor"
|
|
50
|
+
height="20"
|
|
51
|
+
width="20"
|
|
52
|
+
>
|
|
53
|
+
<path
|
|
54
|
+
fillRule="evenodd"
|
|
55
|
+
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-5a.75.75 0 01.75.75v4.5a.75.75 0 01-1.5 0v-4.5A.75.75 0 0110 5zm0 10a1 1 0 100-2 1 1 0 000 2z"
|
|
56
|
+
clipRule="evenodd"
|
|
57
|
+
/>
|
|
58
|
+
</svg>;
|
|
59
|
+
|
|
60
|
+
export const CloseIcon = <svg
|
|
61
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
62
|
+
width="12"
|
|
63
|
+
height="12"
|
|
64
|
+
viewBox="0 0 24 24"
|
|
65
|
+
fill="none"
|
|
66
|
+
stroke="currentColor"
|
|
67
|
+
strokeWidth="1.5"
|
|
68
|
+
strokeLinecap="round"
|
|
69
|
+
strokeLinejoin="round"
|
|
70
|
+
>
|
|
71
|
+
<line x1="18" y1="6" x2="6" y2="18" />
|
|
72
|
+
<line x1="6" y1="6" x2="18" y2="18" />
|
|
73
|
+
</svg>;
|
|
74
|
+
|
|
75
|
+
export function getAsset(type: ToastTypes | undefined): any {
|
|
76
|
+
switch (type) {
|
|
77
|
+
case 'success':
|
|
78
|
+
return SuccessIcon;
|
|
79
|
+
case 'info':
|
|
80
|
+
return InfoIcon;
|
|
81
|
+
case 'warning':
|
|
82
|
+
return WarningIcon;
|
|
83
|
+
case 'error':
|
|
84
|
+
return ErrorIcon;
|
|
85
|
+
default:
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const bars = Array.from({ length: 12 }, (_, index) => index);
|
|
91
|
+
|
|
92
|
+
export function Loader(props: { visible: boolean; className?: string }) {
|
|
93
|
+
return <div
|
|
94
|
+
className={['sonner-loading-wrapper', props.className].filter(Boolean).join(' ')}
|
|
95
|
+
data-visible={props.visible}
|
|
96
|
+
>
|
|
97
|
+
<div className="sonner-spinner">
|
|
98
|
+
@for (const bar of bars; key bar) {
|
|
99
|
+
<div className="sonner-loading-bar" />
|
|
100
|
+
}
|
|
101
|
+
</div>
|
|
102
|
+
</div>;
|
|
103
|
+
}
|
package/src/hooks.tsrx
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Ported from sonner@2.0.7 src/hooks.tsx. The guarded initializer is the one
|
|
2
|
+
// deliberate SSR adaptation: Octane's server runtime executes this component,
|
|
3
|
+
// while upstream assumes a client boundary owns it.
|
|
4
|
+
import { useEffect, useState } from 'octane';
|
|
5
|
+
|
|
6
|
+
export function useIsDocumentHidden(): boolean {
|
|
7
|
+
const [isDocumentHidden, setIsDocumentHidden] = useState(
|
|
8
|
+
typeof document === 'undefined' ? false : document.hidden,
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
const callback = (): void => setIsDocumentHidden(document.hidden);
|
|
13
|
+
document.addEventListener('visibilitychange', callback);
|
|
14
|
+
return () => document.removeEventListener('visibilitychange', callback);
|
|
15
|
+
}, []);
|
|
16
|
+
|
|
17
|
+
return isDocumentHidden;
|
|
18
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// @octanejs/sonner — Sonner 2.0.7 for the octane renderer.
|
|
2
|
+
//
|
|
3
|
+
// The renderer is authored in `.tsrx` so Octane's compiler owns component
|
|
4
|
+
// templates and hook slots. The imperative observer/store remains plain
|
|
5
|
+
// TypeScript and intentionally mirrors upstream's state machine.
|
|
6
|
+
export { toast, Toaster, useSonner } from './sonner.tsrx';
|
|
7
|
+
export type { ExternalToast, ToastT, ToasterProps } from './types';
|
|
8
|
+
export type { ToastClassnames, ToastToDismiss, Action } from './types';
|