@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 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
+ }
@@ -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';