@behivetech/atoms.toast 0.0.0-stage → 0.2.3

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/README.md CHANGED
@@ -1,3 +1,11 @@
1
- # Temporary Holding Version
1
+ # Toast
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ TODO: Add component description.
4
+
5
+ ## Usage
6
+
7
+ ```jsx
8
+ import { Toast } from "@behivetech/atoms.toast";
9
+
10
+ <Toast>Content</Toast>;
11
+ ```
package/dist/index.cjs ADDED
@@ -0,0 +1,223 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __commonJS = (cb, mod) => function __require() {
9
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
10
+ };
11
+ var __export = (target, all) => {
12
+ for (var name in all)
13
+ __defProp(target, name, { get: all[name], enumerable: true });
14
+ };
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") {
17
+ for (let key of __getOwnPropNames(from))
18
+ if (!__hasOwnProp.call(to, key) && key !== except)
19
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
20
+ }
21
+ return to;
22
+ };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
+ // If the importer is in node compatibility mode or this is not an ESM
25
+ // file that has been converted to a CommonJS file using a Babel-
26
+ // compatible transform (i.e. "__esModule" has not been set), then set
27
+ // "default" to the CommonJS "module.exports" for node compatibility.
28
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
+ mod
30
+ ));
31
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
+
33
+ // ../../../../node_modules/.pnpm/classnames@2.5.1/node_modules/classnames/index.js
34
+ var require_classnames = __commonJS({
35
+ "../../../../node_modules/.pnpm/classnames@2.5.1/node_modules/classnames/index.js"(exports2, module2) {
36
+ "use strict";
37
+ (function() {
38
+ "use strict";
39
+ var hasOwn = {}.hasOwnProperty;
40
+ function classNames() {
41
+ var classes = "";
42
+ for (var i = 0; i < arguments.length; i++) {
43
+ var arg = arguments[i];
44
+ if (arg) {
45
+ classes = appendClass(classes, parseValue(arg));
46
+ }
47
+ }
48
+ return classes;
49
+ }
50
+ function parseValue(arg) {
51
+ if (typeof arg === "string" || typeof arg === "number") {
52
+ return arg;
53
+ }
54
+ if (typeof arg !== "object") {
55
+ return "";
56
+ }
57
+ if (Array.isArray(arg)) {
58
+ return classNames.apply(null, arg);
59
+ }
60
+ if (arg.toString !== Object.prototype.toString && !arg.toString.toString().includes("[native code]")) {
61
+ return arg.toString();
62
+ }
63
+ var classes = "";
64
+ for (var key in arg) {
65
+ if (hasOwn.call(arg, key) && arg[key]) {
66
+ classes = appendClass(classes, key);
67
+ }
68
+ }
69
+ return classes;
70
+ }
71
+ function appendClass(value, newClass) {
72
+ if (!newClass) {
73
+ return value;
74
+ }
75
+ if (value) {
76
+ return value + " " + newClass;
77
+ }
78
+ return value + newClass;
79
+ }
80
+ if (typeof module2 !== "undefined" && module2.exports) {
81
+ classNames.default = classNames;
82
+ module2.exports = classNames;
83
+ } else if (typeof define === "function" && typeof define.amd === "object" && define.amd) {
84
+ define("classnames", [], function() {
85
+ return classNames;
86
+ });
87
+ } else {
88
+ window.classNames = classNames;
89
+ }
90
+ })();
91
+ }
92
+ });
93
+
94
+ // index.ts
95
+ var index_exports = {};
96
+ __export(index_exports, {
97
+ Toast: () => Toast,
98
+ showToast: () => showToast
99
+ });
100
+ module.exports = __toCommonJS(index_exports);
101
+
102
+ // toast.tsx
103
+ var import_react = require("react");
104
+ var RadixToast = __toESM(require("@radix-ui/react-toast"), 1);
105
+
106
+ // ../../../../packages/registry/class-names/dist/index.js
107
+ var import_classnames = __toESM(require_classnames(), 1);
108
+ var getClassName = ({
109
+ className,
110
+ modifiers = {},
111
+ rootClass,
112
+ styles = {}
113
+ }) => {
114
+ function getModifiers() {
115
+ const updatedModifiers = {};
116
+ const modifierKeys = Object.keys(modifiers);
117
+ if (modifierKeys.length) {
118
+ modifierKeys.forEach((modKey) => {
119
+ const classKey = `${rootClass}--${modKey}`;
120
+ const newClassName = styles[classKey] || classKey;
121
+ updatedModifiers[newClassName] = modifiers[modKey] ?? false;
122
+ });
123
+ }
124
+ return updatedModifiers;
125
+ }
126
+ function getChildClass(childClassName) {
127
+ const classKey = `${rootClass}__${childClassName}`;
128
+ return styles[classKey] || classKey;
129
+ }
130
+ function getRootStyles() {
131
+ return (0, import_classnames.default)(
132
+ className,
133
+ {
134
+ ...getModifiers()
135
+ },
136
+ styles[rootClass] || rootClass
137
+ );
138
+ }
139
+ return [getRootStyles(), getChildClass];
140
+ };
141
+
142
+ // toast.module.scss
143
+ var toast_module_default = {
144
+ "BHT__Toast__viewport": "BHT__Toast__viewport",
145
+ "BHT__Toast__root": "BHT__Toast__root",
146
+ "toast-in": "toast-in",
147
+ "toast-out": "toast-out",
148
+ "toast-swipe-out": "toast-swipe-out",
149
+ "BHT__Toast__title": "BHT__Toast__title",
150
+ "BHT__Toast__description": "BHT__Toast__description",
151
+ "BHT__Toast__action": "BHT__Toast__action",
152
+ "BHT__Toast__close": "BHT__Toast__close"
153
+ };
154
+
155
+ // toast.tsx
156
+ var import_jsx_runtime = require("react/jsx-runtime");
157
+ var TOAST_EVENT = "bht:toast";
158
+ function showToast(message) {
159
+ const detail = { id: crypto.randomUUID(), ...message };
160
+ window.dispatchEvent(new CustomEvent(TOAST_EVENT, { detail }));
161
+ }
162
+ var Toast = ({ duration = 5e3 }) => {
163
+ const [messages, setMessages] = (0, import_react.useState)([]);
164
+ const [, getChildClass] = getClassName({ rootClass: "BHT__Toast", styles: toast_module_default });
165
+ const dismiss = (0, import_react.useCallback)((id) => {
166
+ setMessages((current) => current.filter((message) => message.id !== id));
167
+ }, []);
168
+ (0, import_react.useEffect)(() => {
169
+ const handleToastEvent = (event) => {
170
+ const { detail } = event;
171
+ setMessages((current) => [...current, detail]);
172
+ };
173
+ window.addEventListener(TOAST_EVENT, handleToastEvent);
174
+ return () => window.removeEventListener(TOAST_EVENT, handleToastEvent);
175
+ }, []);
176
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(RadixToast.Provider, { duration, children: [
177
+ messages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
178
+ RadixToast.Root,
179
+ {
180
+ className: getChildClass("root"),
181
+ "data-variant": message.variant ?? "info",
182
+ onOpenChange: (open) => !open && dismiss(message.id),
183
+ children: [
184
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RadixToast.Title, { className: getChildClass("title"), children: message.title }),
185
+ message.description && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RadixToast.Description, { className: getChildClass("description"), children: message.description }),
186
+ message.action && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
187
+ RadixToast.Action,
188
+ {
189
+ className: getChildClass("action"),
190
+ altText: message.action.altText ?? message.action.label,
191
+ onClick: message.action.onClick,
192
+ children: message.action.label
193
+ }
194
+ ),
195
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
196
+ RadixToast.Close,
197
+ {
198
+ className: getChildClass("close"),
199
+ "aria-label": "Dismiss",
200
+ children: "\xD7"
201
+ }
202
+ )
203
+ ]
204
+ },
205
+ message.id
206
+ )),
207
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RadixToast.Viewport, { className: getChildClass("viewport") })
208
+ ] });
209
+ };
210
+ // Annotate the CommonJS export names for ESM import in node:
211
+ 0 && (module.exports = {
212
+ Toast,
213
+ showToast
214
+ });
215
+ /*! Bundled license information:
216
+
217
+ classnames/index.js:
218
+ (*!
219
+ Copyright (c) 2018 Jed Watson.
220
+ Licensed under the MIT License (MIT), see
221
+ http://jedwatson.github.io/classnames
222
+ *)
223
+ */
package/dist/index.css ADDED
@@ -0,0 +1,159 @@
1
+ /* esbuild-sass-plugin:css-chunktoast.module.scss */
2
+ .BHT__Toast__viewport {
3
+ position: fixed;
4
+ bottom: var(--spacing-4, 1rem);
5
+ right: var(--spacing-4, 1rem);
6
+ display: flex;
7
+ flex-direction: column;
8
+ gap: var(--spacing-2, 0.5rem);
9
+ width: min(22.5rem, 100vw - 2rem);
10
+ z-index: var(--z-toast, 500);
11
+ list-style: none;
12
+ margin: 0;
13
+ padding: 0;
14
+ outline: none;
15
+ }
16
+ .BHT__Toast__root {
17
+ display: grid;
18
+ grid-template-columns: 1fr auto;
19
+ align-items: start;
20
+ gap: var(--spacing-1, 0.25rem) var(--spacing-3, 0.75rem);
21
+ padding: var(--spacing-3, 0.75rem) var(--spacing-4, 1rem);
22
+ background: var(--md-sys-color-inverse-surface, var(--color-on-surface));
23
+ color: var(--md-sys-color-inverse-on-surface, var(--color-surface));
24
+ border: 1px solid transparent;
25
+ border-left: 3px solid var(--md-sys-color-inverse-primary, var(--color-primary));
26
+ border-radius: var(--md-sys-shape-corner-extra-small, 4px);
27
+ box-shadow: var(--md-sys-elevation-3, var(--shadow-lg));
28
+ }
29
+ .BHT__Toast__root[data-variant=success] {
30
+ border-left-color: var(--color-success);
31
+ }
32
+ .BHT__Toast__root[data-variant=error] {
33
+ border-left-color: var(--md-sys-color-error-container, var(--color-error));
34
+ }
35
+ .BHT__Toast__root[data-state=open] {
36
+ animation: toast-in var(--md-sys-motion-duration-short-3, 150ms) var(--md-sys-motion-easing-standard-decelerate, ease-out);
37
+ }
38
+ .BHT__Toast__root[data-state=closed] {
39
+ animation: toast-out var(--md-sys-motion-duration-short-3, 150ms) var(--md-sys-motion-easing-standard-accelerate, ease-in);
40
+ }
41
+ .BHT__Toast__root[data-swipe=move] {
42
+ transform: translateX(var(--radix-toast-swipe-move-x));
43
+ }
44
+ .BHT__Toast__root[data-swipe=end] {
45
+ animation: toast-swipe-out var(--md-sys-motion-duration-short-3, 150ms) var(--md-sys-motion-easing-standard-accelerate, ease-out);
46
+ }
47
+ .BHT__Toast__title {
48
+ grid-column: 1;
49
+ font-family: var(--md-sys-typescale-label-large-font, var(--font-body));
50
+ font-size: var(--md-sys-typescale-label-large-size, 0.875rem);
51
+ font-weight: var(--md-sys-typescale-label-large-weight, 500);
52
+ line-height: var(--md-sys-typescale-label-large-line-height, 1.25rem);
53
+ letter-spacing: var(--md-sys-typescale-label-large-tracking, 0.0071em);
54
+ }
55
+ .BHT__Toast__description {
56
+ grid-column: 1;
57
+ font-family: var(--md-sys-typescale-body-medium-font, var(--font-body));
58
+ font-size: var(--md-sys-typescale-body-medium-size, 0.875rem);
59
+ font-weight: var(--md-sys-typescale-body-medium-weight, 400);
60
+ line-height: var(--md-sys-typescale-body-medium-line-height, 1.25rem);
61
+ letter-spacing: var(--md-sys-typescale-body-medium-tracking, 0.0179em);
62
+ }
63
+ .BHT__Toast__action,
64
+ .BHT__Toast__close {
65
+ position: relative;
66
+ isolation: isolate;
67
+ background: transparent;
68
+ cursor: pointer;
69
+ }
70
+ .BHT__Toast__action::before,
71
+ .BHT__Toast__close::before {
72
+ content: "";
73
+ position: absolute;
74
+ top: 50%;
75
+ left: 50%;
76
+ translate: -50% -50%;
77
+ width: 100%;
78
+ height: 100%;
79
+ min-width: 48px;
80
+ min-height: 48px;
81
+ }
82
+ .BHT__Toast__action::after,
83
+ .BHT__Toast__close::after {
84
+ content: "";
85
+ position: absolute;
86
+ inset: 0;
87
+ border-radius: inherit;
88
+ background: currentColor;
89
+ opacity: 0;
90
+ pointer-events: none;
91
+ transition: opacity var(--md-sys-motion-duration-short-2, 100ms) var(--md-sys-motion-easing-standard, ease);
92
+ }
93
+ .BHT__Toast__action:hover::after,
94
+ .BHT__Toast__close:hover::after {
95
+ opacity: var(--md-sys-state-hover-opacity, 0.08);
96
+ }
97
+ .BHT__Toast__action:focus-visible::after,
98
+ .BHT__Toast__close:focus-visible::after {
99
+ opacity: var(--md-sys-state-focus-opacity, 0.1);
100
+ }
101
+ .BHT__Toast__action:active::after,
102
+ .BHT__Toast__close:active::after {
103
+ opacity: var(--md-sys-state-pressed-opacity, 0.1);
104
+ }
105
+ .BHT__Toast__action:focus-visible,
106
+ .BHT__Toast__close:focus-visible {
107
+ outline: 3px solid var(--md-sys-color-secondary, var(--color-border-focus));
108
+ outline-offset: 2px;
109
+ }
110
+ .BHT__Toast__action {
111
+ grid-column: 1;
112
+ justify-self: start;
113
+ margin-top: var(--spacing-1, 0.25rem);
114
+ padding: var(--spacing-1, 0.25rem) var(--spacing-3, 0.75rem);
115
+ border: 1px solid transparent;
116
+ border-radius: var(--md-sys-shape-corner-full, 9999px);
117
+ color: var(--md-sys-color-inverse-primary, var(--color-primary));
118
+ font-family: var(--md-sys-typescale-label-large-font, var(--font-body));
119
+ font-size: var(--md-sys-typescale-label-large-size, 0.875rem);
120
+ font-weight: var(--md-sys-typescale-label-large-weight, 500);
121
+ letter-spacing: var(--md-sys-typescale-label-large-tracking, 0.0071em);
122
+ line-height: 1;
123
+ }
124
+ .BHT__Toast__close {
125
+ grid-column: 2;
126
+ grid-row: 1/3;
127
+ border: none;
128
+ border-radius: var(--md-sys-shape-corner-full, 9999px);
129
+ color: var(--md-sys-color-inverse-on-surface, var(--color-surface));
130
+ font-size: 1.125rem;
131
+ line-height: 1;
132
+ padding: 0;
133
+ }
134
+ @keyframes toast-in {
135
+ from {
136
+ opacity: 0;
137
+ transform: translateY(0.5rem);
138
+ }
139
+ to {
140
+ opacity: 1;
141
+ transform: translateY(0);
142
+ }
143
+ }
144
+ @keyframes toast-out {
145
+ from {
146
+ opacity: 1;
147
+ }
148
+ to {
149
+ opacity: 0;
150
+ }
151
+ }
152
+ @keyframes toast-swipe-out {
153
+ from {
154
+ transform: translateX(var(--radix-toast-swipe-end-x));
155
+ }
156
+ to {
157
+ transform: translateX(100%);
158
+ }
159
+ }
@@ -0,0 +1,45 @@
1
+ import * as react from 'react';
2
+
3
+ type ToastVariant = "info" | "success" | "error";
4
+ interface ToastAction {
5
+ /** Button label, e.g. "Undo" */
6
+ label: string;
7
+ /** Fired on click; the toast dismisses itself afterwards */
8
+ onClick: () => void;
9
+ /**
10
+ * How to achieve the same thing without the toast, for assistive tech.
11
+ * Required by Radix; defaults to `label`.
12
+ */
13
+ altText?: string;
14
+ }
15
+ interface ToastMessage {
16
+ /** Unique identifier, auto-generated by `showToast` */
17
+ id: string;
18
+ /** Bold headline text */
19
+ title: string;
20
+ /** Optional supporting text shown below the title */
21
+ description?: string;
22
+ /** Controls the accent color; defaults to "info" */
23
+ variant?: ToastVariant;
24
+ /** Optional button rendered alongside the dismiss control, e.g. an undo */
25
+ action?: ToastAction;
26
+ }
27
+ /**
28
+ * Queues a toast on any mounted `Toast` component. Dispatches a plain `window`
29
+ * event under the hood, so it's callable from anywhere with no React context
30
+ * required — including directly from the browser console, e.g.
31
+ * `showToast({ title: "Saved", variant: "success" })`.
32
+ */
33
+ declare function showToast(message: Omit<ToastMessage, "id">): void;
34
+ interface ToastProps {
35
+ /** How long a toast stays visible before auto-dismissing, in ms. Defaults to 5000. */
36
+ duration?: number;
37
+ }
38
+ /**
39
+ * Mount once, anywhere in the app (e.g. a root layout). Listens for `showToast()`
40
+ * calls and renders each as a dismissible, auto-expiring Radix Toast in a fixed
41
+ * viewport.
42
+ */
43
+ declare const Toast: ({ duration }: ToastProps) => react.JSX.Element;
44
+
45
+ export { Toast, type ToastAction, type ToastMessage, type ToastProps, type ToastVariant, showToast };
package/dist/index.js ADDED
@@ -0,0 +1,208 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __commonJS = (cb, mod) => function __require() {
8
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
19
+ // If the importer is in node compatibility mode or this is not an ESM
20
+ // file that has been converted to a CommonJS file using a Babel-
21
+ // compatible transform (i.e. "__esModule" has not been set), then set
22
+ // "default" to the CommonJS "module.exports" for node compatibility.
23
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
24
+ mod
25
+ ));
26
+
27
+ // ../../../../node_modules/.pnpm/classnames@2.5.1/node_modules/classnames/index.js
28
+ var require_classnames = __commonJS({
29
+ "../../../../node_modules/.pnpm/classnames@2.5.1/node_modules/classnames/index.js"(exports, module) {
30
+ "use strict";
31
+ (function() {
32
+ "use strict";
33
+ var hasOwn = {}.hasOwnProperty;
34
+ function classNames() {
35
+ var classes = "";
36
+ for (var i = 0; i < arguments.length; i++) {
37
+ var arg = arguments[i];
38
+ if (arg) {
39
+ classes = appendClass(classes, parseValue(arg));
40
+ }
41
+ }
42
+ return classes;
43
+ }
44
+ function parseValue(arg) {
45
+ if (typeof arg === "string" || typeof arg === "number") {
46
+ return arg;
47
+ }
48
+ if (typeof arg !== "object") {
49
+ return "";
50
+ }
51
+ if (Array.isArray(arg)) {
52
+ return classNames.apply(null, arg);
53
+ }
54
+ if (arg.toString !== Object.prototype.toString && !arg.toString.toString().includes("[native code]")) {
55
+ return arg.toString();
56
+ }
57
+ var classes = "";
58
+ for (var key in arg) {
59
+ if (hasOwn.call(arg, key) && arg[key]) {
60
+ classes = appendClass(classes, key);
61
+ }
62
+ }
63
+ return classes;
64
+ }
65
+ function appendClass(value, newClass) {
66
+ if (!newClass) {
67
+ return value;
68
+ }
69
+ if (value) {
70
+ return value + " " + newClass;
71
+ }
72
+ return value + newClass;
73
+ }
74
+ if (typeof module !== "undefined" && module.exports) {
75
+ classNames.default = classNames;
76
+ module.exports = classNames;
77
+ } else if (typeof define === "function" && typeof define.amd === "object" && define.amd) {
78
+ define("classnames", [], function() {
79
+ return classNames;
80
+ });
81
+ } else {
82
+ window.classNames = classNames;
83
+ }
84
+ })();
85
+ }
86
+ });
87
+
88
+ // toast.tsx
89
+ import { useCallback, useEffect, useState } from "react";
90
+ import * as RadixToast from "@radix-ui/react-toast";
91
+
92
+ // ../../../../packages/registry/class-names/dist/index.js
93
+ var import_classnames = __toESM(require_classnames(), 1);
94
+ var getClassName = ({
95
+ className,
96
+ modifiers = {},
97
+ rootClass,
98
+ styles = {}
99
+ }) => {
100
+ function getModifiers() {
101
+ const updatedModifiers = {};
102
+ const modifierKeys = Object.keys(modifiers);
103
+ if (modifierKeys.length) {
104
+ modifierKeys.forEach((modKey) => {
105
+ const classKey = `${rootClass}--${modKey}`;
106
+ const newClassName = styles[classKey] || classKey;
107
+ updatedModifiers[newClassName] = modifiers[modKey] ?? false;
108
+ });
109
+ }
110
+ return updatedModifiers;
111
+ }
112
+ function getChildClass(childClassName) {
113
+ const classKey = `${rootClass}__${childClassName}`;
114
+ return styles[classKey] || classKey;
115
+ }
116
+ function getRootStyles() {
117
+ return (0, import_classnames.default)(
118
+ className,
119
+ {
120
+ ...getModifiers()
121
+ },
122
+ styles[rootClass] || rootClass
123
+ );
124
+ }
125
+ return [getRootStyles(), getChildClass];
126
+ };
127
+
128
+ // toast.module.scss
129
+ var toast_module_default = {
130
+ "BHT__Toast__viewport": "BHT__Toast__viewport",
131
+ "BHT__Toast__root": "BHT__Toast__root",
132
+ "toast-in": "toast-in",
133
+ "toast-out": "toast-out",
134
+ "toast-swipe-out": "toast-swipe-out",
135
+ "BHT__Toast__title": "BHT__Toast__title",
136
+ "BHT__Toast__description": "BHT__Toast__description",
137
+ "BHT__Toast__action": "BHT__Toast__action",
138
+ "BHT__Toast__close": "BHT__Toast__close"
139
+ };
140
+
141
+ // toast.tsx
142
+ import { jsx, jsxs } from "react/jsx-runtime";
143
+ var TOAST_EVENT = "bht:toast";
144
+ function showToast(message) {
145
+ const detail = { id: crypto.randomUUID(), ...message };
146
+ window.dispatchEvent(new CustomEvent(TOAST_EVENT, { detail }));
147
+ }
148
+ var Toast = ({ duration = 5e3 }) => {
149
+ const [messages, setMessages] = useState([]);
150
+ const [, getChildClass] = getClassName({ rootClass: "BHT__Toast", styles: toast_module_default });
151
+ const dismiss = useCallback((id) => {
152
+ setMessages((current) => current.filter((message) => message.id !== id));
153
+ }, []);
154
+ useEffect(() => {
155
+ const handleToastEvent = (event) => {
156
+ const { detail } = event;
157
+ setMessages((current) => [...current, detail]);
158
+ };
159
+ window.addEventListener(TOAST_EVENT, handleToastEvent);
160
+ return () => window.removeEventListener(TOAST_EVENT, handleToastEvent);
161
+ }, []);
162
+ return /* @__PURE__ */ jsxs(RadixToast.Provider, { duration, children: [
163
+ messages.map((message) => /* @__PURE__ */ jsxs(
164
+ RadixToast.Root,
165
+ {
166
+ className: getChildClass("root"),
167
+ "data-variant": message.variant ?? "info",
168
+ onOpenChange: (open) => !open && dismiss(message.id),
169
+ children: [
170
+ /* @__PURE__ */ jsx(RadixToast.Title, { className: getChildClass("title"), children: message.title }),
171
+ message.description && /* @__PURE__ */ jsx(RadixToast.Description, { className: getChildClass("description"), children: message.description }),
172
+ message.action && /* @__PURE__ */ jsx(
173
+ RadixToast.Action,
174
+ {
175
+ className: getChildClass("action"),
176
+ altText: message.action.altText ?? message.action.label,
177
+ onClick: message.action.onClick,
178
+ children: message.action.label
179
+ }
180
+ ),
181
+ /* @__PURE__ */ jsx(
182
+ RadixToast.Close,
183
+ {
184
+ className: getChildClass("close"),
185
+ "aria-label": "Dismiss",
186
+ children: "\xD7"
187
+ }
188
+ )
189
+ ]
190
+ },
191
+ message.id
192
+ )),
193
+ /* @__PURE__ */ jsx(RadixToast.Viewport, { className: getChildClass("viewport") })
194
+ ] });
195
+ };
196
+ export {
197
+ Toast,
198
+ showToast
199
+ };
200
+ /*! Bundled license information:
201
+
202
+ classnames/index.js:
203
+ (*!
204
+ Copyright (c) 2018 Jed Watson.
205
+ Licensed under the MIT License (MIT), see
206
+ http://jedwatson.github.io/classnames
207
+ *)
208
+ */
package/docs.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "$schema": "https://github.com/behivetech/bht-component-registry/docs.schema.json",
3
+ "version": 2,
4
+ "generatedAt": "2026-10-23T16:57:42.311Z",
5
+ "docs": [
6
+ {
7
+ "displayName": "Toast",
8
+ "description": "Mount once, anywhere in the app (e.g. a root layout). Listens for `showToast()`\ncalls and renders each as a dismissible, auto-expiring Radix Toast in a fixed\nviewport.",
9
+ "extendsNative": false,
10
+ "props": [
11
+ {
12
+ "name": "duration",
13
+ "type": "number",
14
+ "required": false,
15
+ "defaultValue": "5000",
16
+ "description": "How long a toast stays visible before auto-dismissing, in ms. Defaults to 5000."
17
+ }
18
+ ]
19
+ }
20
+ ],
21
+ "functions": [
22
+ {
23
+ "name": "showToast",
24
+ "kind": "function",
25
+ "description": "Queues a toast on any mounted `Toast` component. Dispatches a plain `window`\nevent under the hood, so it's callable from anywhere with no React context\nrequired — including directly from the browser console, e.g.\n`showToast({ title: \"Saved\", variant: \"success\" })`.",
26
+ "signatures": [
27
+ {
28
+ "text": "showToast(message: Omit<ToastMessage, \"id\">): void",
29
+ "params": [
30
+ {
31
+ "name": "message",
32
+ "type": "Omit<ToastMessage, \"id\">",
33
+ "optional": false,
34
+ "defaultValue": null,
35
+ "description": ""
36
+ }
37
+ ],
38
+ "returns": {
39
+ "type": "void",
40
+ "description": ""
41
+ }
42
+ }
43
+ ],
44
+ "examples": []
45
+ }
46
+ ]
47
+ }
package/index.ts ADDED
@@ -0,0 +1,7 @@
1
+ export { Toast, showToast } from "./toast.js";
2
+ export type {
3
+ ToastProps,
4
+ ToastMessage,
5
+ ToastVariant,
6
+ ToastAction,
7
+ } from "./toast.js";
package/package.json CHANGED
@@ -1,6 +1,62 @@
1
1
  {
2
2
  "name": "@behivetech/atoms.toast",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.2.3",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "private": false,
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js",
11
+ "require": "./dist/index.cjs"
12
+ },
13
+ "./styles.css": "./dist/index.css"
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md",
18
+ "index.ts",
19
+ "toast.tsx",
20
+ "toast.module.scss",
21
+ "*.example.tsx",
22
+ "docs.json"
23
+ ],
24
+ "registry": {
25
+ "status": "stable",
26
+ "tags": []
27
+ },
28
+ "dependencies": {
29
+ "@radix-ui/react-toast": "^1.2.23"
30
+ },
31
+ "peerDependencies": {
32
+ "react": "^19.2.7",
33
+ "react-dom": "^19.2.7"
34
+ },
35
+ "devDependencies": {
36
+ "@types/react": "19.2.17",
37
+ "@types/react-dom": "19.2.3",
38
+ "eslint": "^9.39.5",
39
+ "tsup": "^8.5.1",
40
+ "typescript": "^6.0.3",
41
+ "vitest": "^4.1.10",
42
+ "@bht-component-registry/class-names": "0.0.6",
43
+ "@bht-component-registry/env": "0.1.0"
44
+ },
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "https://github.com/behivetech/bht-galaxy.git",
48
+ "directory": "registry/behivetech/atoms/toast"
49
+ },
50
+ "publishConfig": {
51
+ "registry": "https://registry.npmjs.org",
52
+ "access": "public",
53
+ "provenance": true
54
+ },
55
+ "scripts": {
56
+ "build": "tsup && bht-registry-docgen",
57
+ "dev": "tsup --watch",
58
+ "lint": "eslint . --max-warnings 0",
59
+ "check-types": "tsc --noEmit",
60
+ "test": "vitest run"
61
+ }
6
62
  }
@@ -0,0 +1,41 @@
1
+ import { Toast, showToast } from "./toast.js";
2
+
3
+ export const BasicToast = () => (
4
+ <>
5
+ <button
6
+ type="button"
7
+ onClick={() =>
8
+ showToast({ title: "Saved", description: "Your changes were saved." })
9
+ }
10
+ >
11
+ Show info toast
12
+ </button>
13
+ <button
14
+ type="button"
15
+ onClick={() => showToast({ title: "Published", variant: "success" })}
16
+ >
17
+ Show success toast
18
+ </button>
19
+ <button
20
+ type="button"
21
+ onClick={() => showToast({ title: "Failed to save", variant: "error" })}
22
+ >
23
+ Show error toast
24
+ </button>
25
+ <button
26
+ type="button"
27
+ onClick={() =>
28
+ showToast({
29
+ title: "Structure moved",
30
+ action: {
31
+ label: "Undo",
32
+ onClick: () => showToast({ title: "Move undone" }),
33
+ },
34
+ })
35
+ }
36
+ >
37
+ Show toast with an action
38
+ </button>
39
+ <Toast />
40
+ </>
41
+ );
@@ -0,0 +1,205 @@
1
+ // Material Design 3 snackbar. `inverse-surface` and `inverse-primary` have no
2
+ // pre-MD3 names, so their fallbacks are the old surface/on-surface pair
3
+ // swapped, which is exactly what "inverse" means.
4
+ //
5
+ // The `data-variant` accent stripe is kept (it is part of the public API) even
6
+ // though MD3 snackbars have no variants.
7
+
8
+ .BHT__Toast {
9
+ &__viewport {
10
+ position: fixed;
11
+ bottom: var(--spacing-4, 1rem);
12
+ right: var(--spacing-4, 1rem);
13
+ display: flex;
14
+ flex-direction: column;
15
+ gap: var(--spacing-2, 0.5rem);
16
+ width: min(22.5rem, calc(100vw - 2rem));
17
+ z-index: var(--z-toast, 500);
18
+ list-style: none;
19
+ margin: 0;
20
+ padding: 0;
21
+ outline: none;
22
+ }
23
+
24
+ &__root {
25
+ display: grid;
26
+ grid-template-columns: 1fr auto;
27
+ align-items: start;
28
+ gap: var(--spacing-1, 0.25rem) var(--spacing-3, 0.75rem);
29
+ padding: var(--spacing-3, 0.75rem) var(--spacing-4, 1rem);
30
+ background: var(--md-sys-color-inverse-surface, var(--color-on-surface));
31
+ color: var(--md-sys-color-inverse-on-surface, var(--color-surface));
32
+ // Transparent rather than removed so the content box doesn't shift.
33
+ border: 1px solid transparent;
34
+ border-left: 3px solid
35
+ var(--md-sys-color-inverse-primary, var(--color-primary));
36
+ border-radius: var(--md-sys-shape-corner-extra-small, 4px);
37
+ box-shadow: var(--md-sys-elevation-3, var(--shadow-lg));
38
+
39
+ &[data-variant="success"] {
40
+ border-left-color: var(--color-success);
41
+ }
42
+
43
+ // `error-container`, not `error`: the stripe sits on the inverse surface,
44
+ // where `error` (tuned for the normal surface) has almost no contrast.
45
+ &[data-variant="error"] {
46
+ border-left-color: var(
47
+ --md-sys-color-error-container,
48
+ var(--color-error)
49
+ );
50
+ }
51
+
52
+ &[data-state="open"] {
53
+ animation: toast-in var(--md-sys-motion-duration-short-3, 150ms)
54
+ var(--md-sys-motion-easing-standard-decelerate, ease-out);
55
+ }
56
+
57
+ &[data-state="closed"] {
58
+ animation: toast-out var(--md-sys-motion-duration-short-3, 150ms)
59
+ var(--md-sys-motion-easing-standard-accelerate, ease-in);
60
+ }
61
+
62
+ &[data-swipe="move"] {
63
+ transform: translateX(var(--radix-toast-swipe-move-x));
64
+ }
65
+
66
+ &[data-swipe="end"] {
67
+ animation: toast-swipe-out var(--md-sys-motion-duration-short-3, 150ms)
68
+ var(--md-sys-motion-easing-standard-accelerate, ease-out);
69
+ }
70
+ }
71
+
72
+ &__title {
73
+ grid-column: 1;
74
+ font-family: var(--md-sys-typescale-label-large-font, var(--font-body));
75
+ font-size: var(--md-sys-typescale-label-large-size, 0.875rem);
76
+ font-weight: var(--md-sys-typescale-label-large-weight, 500);
77
+ line-height: var(--md-sys-typescale-label-large-line-height, 1.25rem);
78
+ letter-spacing: var(--md-sys-typescale-label-large-tracking, 0.0071em);
79
+ }
80
+
81
+ &__description {
82
+ grid-column: 1;
83
+ font-family: var(--md-sys-typescale-body-medium-font, var(--font-body));
84
+ font-size: var(--md-sys-typescale-body-medium-size, 0.875rem);
85
+ font-weight: var(--md-sys-typescale-body-medium-weight, 400);
86
+ line-height: var(--md-sys-typescale-body-medium-line-height, 1.25rem);
87
+ letter-spacing: var(--md-sys-typescale-body-medium-tracking, 0.0179em);
88
+ }
89
+
90
+ // Action and close share the interaction treatment: a currentColor state
91
+ // layer on ::after and an invisible 48px touch target on ::before (px, as on
92
+ // Button — a touch target is a physical size, not a typographic one). The
93
+ // target is centered on both axes because the close glyph is narrow as well
94
+ // as short.
95
+ &__action,
96
+ &__close {
97
+ position: relative;
98
+ isolation: isolate;
99
+ background: transparent;
100
+ cursor: pointer;
101
+
102
+ &::before {
103
+ content: "";
104
+ position: absolute;
105
+ top: 50%;
106
+ left: 50%;
107
+ translate: -50% -50%;
108
+ width: 100%;
109
+ height: 100%;
110
+ min-width: 48px;
111
+ min-height: 48px;
112
+ }
113
+
114
+ &::after {
115
+ content: "";
116
+ position: absolute;
117
+ inset: 0;
118
+ border-radius: inherit;
119
+ background: currentColor;
120
+ opacity: 0;
121
+ pointer-events: none;
122
+ transition: opacity var(--md-sys-motion-duration-short-2, 100ms)
123
+ var(--md-sys-motion-easing-standard, ease);
124
+ }
125
+
126
+ &:hover::after {
127
+ opacity: var(--md-sys-state-hover-opacity, 0.08);
128
+ }
129
+
130
+ &:focus-visible::after {
131
+ opacity: var(--md-sys-state-focus-opacity, 0.1);
132
+ }
133
+
134
+ &:active::after {
135
+ opacity: var(--md-sys-state-pressed-opacity, 0.1);
136
+ }
137
+
138
+ &:focus-visible {
139
+ outline: 3px solid
140
+ var(--md-sys-color-secondary, var(--color-border-focus));
141
+ outline-offset: 2px;
142
+ }
143
+ }
144
+
145
+ &__action {
146
+ grid-column: 1;
147
+ justify-self: start;
148
+ margin-top: var(--spacing-1, 0.25rem);
149
+ padding: var(--spacing-1, 0.25rem) var(--spacing-3, 0.75rem);
150
+ // MD3's snackbar action is a text button; the border stays, transparent,
151
+ // only so the button keeps its size.
152
+ border: 1px solid transparent;
153
+ border-radius: var(--md-sys-shape-corner-full, 9999px);
154
+ color: var(--md-sys-color-inverse-primary, var(--color-primary));
155
+ font-family: var(--md-sys-typescale-label-large-font, var(--font-body));
156
+ font-size: var(--md-sys-typescale-label-large-size, 0.875rem);
157
+ font-weight: var(--md-sys-typescale-label-large-weight, 500);
158
+ letter-spacing: var(--md-sys-typescale-label-large-tracking, 0.0071em);
159
+ line-height: 1;
160
+ }
161
+
162
+ &__close {
163
+ grid-column: 2;
164
+ grid-row: 1 / 3;
165
+ border: none;
166
+ border-radius: var(--md-sys-shape-corner-full, 9999px);
167
+ color: var(--md-sys-color-inverse-on-surface, var(--color-surface));
168
+ // A glyph size, not a type role — the × is sized as an icon.
169
+ font-size: 1.125rem;
170
+ line-height: 1;
171
+ padding: 0;
172
+ }
173
+ }
174
+
175
+ @keyframes toast-in {
176
+ from {
177
+ opacity: 0;
178
+ transform: translateY(0.5rem);
179
+ }
180
+
181
+ to {
182
+ opacity: 1;
183
+ transform: translateY(0);
184
+ }
185
+ }
186
+
187
+ @keyframes toast-out {
188
+ from {
189
+ opacity: 1;
190
+ }
191
+
192
+ to {
193
+ opacity: 0;
194
+ }
195
+ }
196
+
197
+ @keyframes toast-swipe-out {
198
+ from {
199
+ transform: translateX(var(--radix-toast-swipe-end-x));
200
+ }
201
+
202
+ to {
203
+ transform: translateX(100%);
204
+ }
205
+ }
package/toast.tsx ADDED
@@ -0,0 +1,114 @@
1
+ import { useCallback, useEffect, useState } from "react";
2
+ import * as RadixToast from "@radix-ui/react-toast";
3
+ import { getClassName } from "@bht-component-registry/class-names";
4
+ import styles from "./toast.module.scss";
5
+
6
+ export type ToastVariant = "info" | "success" | "error";
7
+
8
+ export interface ToastAction {
9
+ /** Button label, e.g. "Undo" */
10
+ label: string;
11
+ /** Fired on click; the toast dismisses itself afterwards */
12
+ onClick: () => void;
13
+ /**
14
+ * How to achieve the same thing without the toast, for assistive tech.
15
+ * Required by Radix; defaults to `label`.
16
+ */
17
+ altText?: string;
18
+ }
19
+
20
+ export interface ToastMessage {
21
+ /** Unique identifier, auto-generated by `showToast` */
22
+ id: string;
23
+ /** Bold headline text */
24
+ title: string;
25
+ /** Optional supporting text shown below the title */
26
+ description?: string;
27
+ /** Controls the accent color; defaults to "info" */
28
+ variant?: ToastVariant;
29
+ /** Optional button rendered alongside the dismiss control, e.g. an undo */
30
+ action?: ToastAction;
31
+ }
32
+
33
+ const TOAST_EVENT = "bht:toast";
34
+
35
+ /**
36
+ * Queues a toast on any mounted `Toast` component. Dispatches a plain `window`
37
+ * event under the hood, so it's callable from anywhere with no React context
38
+ * required — including directly from the browser console, e.g.
39
+ * `showToast({ title: "Saved", variant: "success" })`.
40
+ */
41
+ export function showToast(message: Omit<ToastMessage, "id">): void {
42
+ const detail: ToastMessage = { id: crypto.randomUUID(), ...message };
43
+ // `action.onClick` survives this trip intact: `CustomEvent.detail` is handed
44
+ // to same-realm listeners by reference, not structured-cloned, so functions
45
+ // are fine here. This is not a bug in need of a serializable payload.
46
+ window.dispatchEvent(new CustomEvent<ToastMessage>(TOAST_EVENT, { detail }));
47
+ }
48
+
49
+ export interface ToastProps {
50
+ /** How long a toast stays visible before auto-dismissing, in ms. Defaults to 5000. */
51
+ duration?: number;
52
+ }
53
+
54
+ /**
55
+ * Mount once, anywhere in the app (e.g. a root layout). Listens for `showToast()`
56
+ * calls and renders each as a dismissible, auto-expiring Radix Toast in a fixed
57
+ * viewport.
58
+ */
59
+ export const Toast = ({ duration = 5000 }: ToastProps) => {
60
+ const [messages, setMessages] = useState<ToastMessage[]>([]);
61
+ const [, getChildClass] = getClassName({ rootClass: "BHT__Toast", styles });
62
+
63
+ const dismiss = useCallback((id: string) => {
64
+ setMessages((current) => current.filter((message) => message.id !== id));
65
+ }, []);
66
+
67
+ useEffect(() => {
68
+ const handleToastEvent = (event: Event) => {
69
+ const { detail } = event as CustomEvent<ToastMessage>;
70
+ setMessages((current) => [...current, detail]);
71
+ };
72
+
73
+ window.addEventListener(TOAST_EVENT, handleToastEvent);
74
+ return () => window.removeEventListener(TOAST_EVENT, handleToastEvent);
75
+ }, []);
76
+
77
+ return (
78
+ <RadixToast.Provider duration={duration}>
79
+ {messages.map((message) => (
80
+ <RadixToast.Root
81
+ key={message.id}
82
+ className={getChildClass("root")}
83
+ data-variant={message.variant ?? "info"}
84
+ onOpenChange={(open) => !open && dismiss(message.id)}
85
+ >
86
+ <RadixToast.Title className={getChildClass("title")}>
87
+ {message.title}
88
+ </RadixToast.Title>
89
+ {message.description && (
90
+ <RadixToast.Description className={getChildClass("description")}>
91
+ {message.description}
92
+ </RadixToast.Description>
93
+ )}
94
+ {message.action && (
95
+ <RadixToast.Action
96
+ className={getChildClass("action")}
97
+ altText={message.action.altText ?? message.action.label}
98
+ onClick={message.action.onClick}
99
+ >
100
+ {message.action.label}
101
+ </RadixToast.Action>
102
+ )}
103
+ <RadixToast.Close
104
+ className={getChildClass("close")}
105
+ aria-label="Dismiss"
106
+ >
107
+ ×
108
+ </RadixToast.Close>
109
+ </RadixToast.Root>
110
+ ))}
111
+ <RadixToast.Viewport className={getChildClass("viewport")} />
112
+ </RadixToast.Provider>
113
+ );
114
+ };