@ythknn/notify 0.0.0-stage → 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,17 @@
1
+ Copyright (c) 2026 ythknn. All rights reserved.
2
+
3
+ Permission is granted, free of charge, to install this package from the npm
4
+ registry and use it, unmodified, as a dependency of your own websites and
5
+ applications, including commercial ones.
6
+
7
+ You may not, without prior written permission:
8
+
9
+ - redistribute, sell, or sublicense this package or any of its files on
10
+ their own or as part of another library, component kit, or template;
11
+ - publish modified versions of this package;
12
+ - copy its code into another project instead of installing it as a
13
+ dependency.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR ITS USE.
package/README.md CHANGED
@@ -1,3 +1,120 @@
1
- # Temporary Holding Version
1
+ # @ythknn/notify
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
+ macOS-style notifications for React and Next.js. Banners slide in, stack by app, swipe away,
4
+ expand for actions and take inline replies. Notification Center keeps the history, with Focus
5
+ (Do Not Disturb), per-app muting and a right-click menu.
6
+
7
+ **Live demo and playground: [notify.vo1d.studio](https://notify.vo1d.studio)**
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install @ythknn/notify motion
13
+ ```
14
+
15
+ Requires React 19 and `motion`.
16
+
17
+ ## Set up
18
+
19
+ Add `<Notifier />` once, in your root layout:
20
+
21
+ ```tsx
22
+ // app/layout.tsx
23
+ import { Notifier } from "@ythknn/notify";
24
+ import "@ythknn/notify/notify.css";
25
+
26
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
27
+ return (
28
+ <html lang="en">
29
+ <body>
30
+ {children}
31
+ <Notifier position="top-right" />
32
+ </body>
33
+ </html>
34
+ );
35
+ }
36
+ ```
37
+
38
+ Then call `notify()` from any client component:
39
+
40
+ ```tsx
41
+ "use client";
42
+ import { notify } from "@ythknn/notify";
43
+
44
+ export function SaveButton() {
45
+ return (
46
+ <button onClick={() => notify({ app: "Notes", title: "Saved", body: "All changes are synced." })}>
47
+ Save
48
+ </button>
49
+ );
50
+ }
51
+ ```
52
+
53
+ ## Options
54
+
55
+ ```tsx
56
+ notify({
57
+ app: { name: "Chat", icon: "/icons/chat.png" }, // stacks group by app
58
+ title: "Maya Rivera",
59
+ subtitle: "Design review",
60
+ body: "Are we still on for 3?",
61
+ avatar: { initials: "MR" }, // or an image URL
62
+ image: "/photos/lisbon.jpg", // thumbnail, full width when expanded
63
+ actions: [
64
+ { label: "Reply", reply: true, onReply: (text) => send(text) },
65
+ { label: "Mark as Read", onSelect: () => markRead() },
66
+ { label: "Delete", destructive: true },
67
+ ],
68
+ style: "alert", // stays until dismissed
69
+ duration: 8000,
70
+ timeSensitive: true, // breaks through Focus
71
+ progress: 0.4, // 0–1 progress bar
72
+ onClick: () => router.push("/inbox"),
73
+ onDismiss: (reason) => console.log(reason),
74
+ });
75
+ ```
76
+
77
+ ## API
78
+
79
+ | Call | What it does |
80
+ | --- | --- |
81
+ | `notify(options \| title)` | Show a notification; returns its id. Reusing an id updates it. |
82
+ | `notify.update(id, patch)` | Change it in place. `realert: true` brings it back on screen. |
83
+ | `notify.promise(task, { loading, success, error }, base?)` | One notification for an async task. |
84
+ | `notify.dismiss(id?)` | Take banners off screen; they stay in Notification Center. |
85
+ | `notify.remove(id)` / `notify.clear()` | Remove notifications everywhere. |
86
+ | `notify.center.open()` / `close()` / `toggle()` / `clear(appId?)` | Drive Notification Center. |
87
+ | `notify.setFocus(on)` / `toggleFocus()` | Do Not Disturb. |
88
+ | `notify.mute(appId, ms \| "today")` / `unmute(appId)` | Silence one app. |
89
+ | `useNotifyState()` | Banners, history, Focus and Notification Center state. |
90
+ | `<NotificationPreview />` | A static card for docs, settings screens and marketing pages. |
91
+
92
+ ### `<Notifier />` props
93
+
94
+ All optional: `position` (`top-right`, `top-left`, `top-center`, `bottom-right`, `bottom-left`,
95
+ `bottom-center`), `theme` (`light`, `dark`, `system`), `material` (`glass`, `regular`, `thick`,
96
+ `solid`), `alertStyle` (`banner`, `alert`), `duration`, `max`, `stack`, `swipeToDismiss`,
97
+ `pauseOnHover`, `expandable`, `timestamps`, `closeButton` (`hover`, `always`, `never`),
98
+ `contextMenu`, `sound`, `volume`, `width`, `radius`, `gap`, `offset`, `accent`, `history`,
99
+ `formatTime`, `className`, `style`, `label`.
100
+
101
+ ### Theming
102
+
103
+ Every visual token is a CSS custom property on `.ntf-root`: `--ntf-bg`, `--ntf-filter`,
104
+ `--ntf-shadow`, `--ntf-label`, `--ntf-label-2`, `--ntf-tint`, `--ntf-radius`, `--ntf-width` and more.
105
+
106
+ ## Interactions
107
+
108
+ - **Hover**: shows the close button and expand chevron; pauses the timer.
109
+ - **Click**: runs `onClick`, expands, or fans out a stack.
110
+ - **Swipe toward the edge**: dismisses to Notification Center.
111
+ - **Right-click**: actions, Mute for 1 Hour, Mute for Today, Clear.
112
+ - **Keyboard**: Enter activates, Space expands, Delete closes, Esc collapses.
113
+ - Respects Reduce Motion and announces through a polite live region.
114
+
115
+ ## License
116
+
117
+ Free to use as an installed dependency in your own projects, including commercial ones.
118
+ Redistribution and modified copies are not permitted. See [LICENSE](./LICENSE).
119
+
120
+ macOS is a trademark of Apple Inc. This package is independent and not affiliated with Apple.
@@ -0,0 +1,181 @@
1
+
2
+ import { CSSProperties, ReactNode } from "react";
3
+ type NotifyPosition = "top-right" | "top-left" | "top-center" | "bottom-right" | "bottom-left" | "bottom-center";
4
+ type NotifyTheme = "light" | "dark" | "system";
5
+ /** `glass` is the layered, luminous look of recent macOS releases; `solid` turns blur off. */
6
+ type NotifyMaterial = "glass" | "regular" | "thick" | "solid";
7
+ /** Mirrors System Settings: banners leave on their own, alerts wait for you. */
8
+ type NotifyAlertStyle = "banner" | "alert";
9
+ type NotifyCloseButton = "hover" | "always" | "never";
10
+ type NotifyDismissReason = "timeout" | "swipe" | "dismiss" | "overflow" | "close" | "click" | "action" | "reply" | "clear";
11
+ interface NotifyApp {
12
+ /** Groups notifications into stacks. Defaults to a slug of `name`. */
13
+ id?: string;
14
+ name: string;
15
+ /** An image URL, or any node (an SVG works best). Clipped to the app-icon squircle. */
16
+ icon?: ReactNode;
17
+ }
18
+ /** An image URL, or initials rendered on the classic gray contact gradient. */
19
+ type NotifyAvatar = string | {
20
+ initials: string;
21
+ background?: string;
22
+ };
23
+ interface NotifyActionContext {
24
+ id: string;
25
+ dismiss: () => void;
26
+ }
27
+ interface NotifyAction {
28
+ label: string;
29
+ onSelect?: (ctx: NotifyActionContext) => void;
30
+ destructive?: boolean;
31
+ /** Turns the action into an inline text field, like replying from a banner. */
32
+ reply?: boolean | {
33
+ placeholder?: string;
34
+ };
35
+ onReply?: (text: string, ctx: NotifyActionContext) => void;
36
+ /** Keep the notification on screen after the action runs. */
37
+ keepOpen?: boolean;
38
+ }
39
+ interface NotifyOptions {
40
+ /** Reusing an id updates that notification in place and alerts again. */
41
+ id?: string;
42
+ app?: NotifyApp | string;
43
+ title: ReactNode;
44
+ subtitle?: ReactNode;
45
+ body?: ReactNode;
46
+ avatar?: NotifyAvatar;
47
+ /** Attachment: a thumbnail when collapsed, full width when expanded. */
48
+ image?: string;
49
+ time?: Date | number;
50
+ actions?: NotifyAction[];
51
+ style?: NotifyAlertStyle;
52
+ /** Milliseconds on screen. `Infinity` keeps it until dismissed. */
53
+ duration?: number;
54
+ /** Breaks through Focus and gets the Time Sensitive label. */
55
+ timeSensitive?: boolean;
56
+ /** 0–1 renders a progress bar. Update it with `notify.update`. */
57
+ progress?: number | null;
58
+ sound?: boolean;
59
+ /** Deliver straight to Notification Center without a banner. */
60
+ silent?: boolean;
61
+ onClick?: (ctx: NotifyActionContext) => void;
62
+ onDismiss?: (reason: NotifyDismissReason) => void;
63
+ }
64
+ interface NotifyRecord extends Omit<NotifyOptions, "id" | "app"> {
65
+ id: string;
66
+ app: NotifyApp & {
67
+ id: string;
68
+ };
69
+ createdAt: number;
70
+ /** Bumped whenever the notification alerts again; restarts its timer. */
71
+ alertAt: number;
72
+ }
73
+ interface NotifierProps {
74
+ position?: NotifyPosition;
75
+ theme?: NotifyTheme;
76
+ material?: NotifyMaterial;
77
+ alertStyle?: NotifyAlertStyle;
78
+ /** Default time on screen for banners, in milliseconds. */
79
+ duration?: number;
80
+ /** Most banners (or stacks) on screen at once. */
81
+ max?: number;
82
+ /** Group notifications from the same app into stacks. */
83
+ stack?: boolean;
84
+ swipeToDismiss?: boolean;
85
+ pauseOnHover?: boolean;
86
+ expandable?: boolean;
87
+ timestamps?: boolean;
88
+ closeButton?: NotifyCloseButton;
89
+ contextMenu?: boolean;
90
+ sound?: boolean;
91
+ volume?: number;
92
+ width?: number;
93
+ radius?: number;
94
+ gap?: number;
95
+ offset?: number | {
96
+ x?: number;
97
+ y?: number;
98
+ };
99
+ /** Tint for progress, reply and menu highlights. Defaults to system blue. */
100
+ accent?: string;
101
+ /** Notifications kept in Notification Center. */
102
+ history?: number;
103
+ formatTime?: (time: number, now: number) => string;
104
+ className?: string;
105
+ style?: CSSProperties;
106
+ label?: string;
107
+ }
108
+ interface NotifyGroup {
109
+ key: string;
110
+ app: NotifyRecord["app"];
111
+ items: NotifyRecord[];
112
+ }
113
+ /**
114
+ * Mount once, near the root of your app. Renders banners, Notification Center
115
+ * and the context menu into a portal on document.body.
116
+ */
117
+ export declare function Notifier(props: NotifierProps): import("react").ReactPortal | null;
118
+ interface NotificationPreviewProps extends Pick<NotifierProps, "theme" | "material" | "radius" | "width" | "accent" | "timestamps" | "closeButton" | "expandable" | "formatTime"> {
119
+ notification: NotifyOptions;
120
+ /** Render as the front of a collapsed stack of this many. */
121
+ stackCount?: number;
122
+ defaultExpanded?: boolean;
123
+ onClose?: () => void;
124
+ className?: string;
125
+ style?: CSSProperties;
126
+ }
127
+ /** A static, in-flow notification — for marketing pages, docs and settings previews. */
128
+ export declare function NotificationPreview({ notification, stackCount, defaultExpanded, onClose, className, style, ...config }: NotificationPreviewProps): import("react").JSX.Element;
129
+ interface NotifyState {
130
+ /** On screen right now, newest first. */
131
+ banners: NotifyRecord[];
132
+ /** Notification Center history, newest first. */
133
+ center: NotifyRecord[];
134
+ centerOpen: boolean;
135
+ /** Focus (Do Not Disturb): banners are held back unless time sensitive. */
136
+ focus: boolean;
137
+ /** App id → muted-until timestamp. */
138
+ muted: Record<string, number>;
139
+ }
140
+ export declare function groupRecords(records: NotifyRecord[], stack: boolean): NotifyGroup[];
141
+ type NotifyInput = NotifyOptions | string;
142
+ type NotifyPatch = Partial<Omit<NotifyOptions, "id">> & {
143
+ /** Alert again: restart the timer, move to the top, re-show if it already left. */
144
+ realert?: boolean;
145
+ };
146
+ declare function create(input: NotifyInput): string;
147
+ declare function update(id: string, patch: NotifyPatch): void;
148
+ type Resolvable<T> = NotifyInput | ((value: T) => NotifyInput);
149
+ declare function promise<T>(task: Promise<T>, messages: {
150
+ loading: NotifyInput;
151
+ success: Resolvable<T>;
152
+ error: Resolvable<unknown>;
153
+ }, base?: Partial<NotifyOptions>): string;
154
+ export declare const notify: typeof create & {
155
+ update: typeof update;
156
+ /** Take banners off screen; they stay in Notification Center. */
157
+ dismiss: (id?: string | string[]) => void;
158
+ /** Remove notifications everywhere. */
159
+ remove: (id: string | string[]) => void;
160
+ clear: () => void;
161
+ promise: typeof promise;
162
+ center: {
163
+ open: () => void;
164
+ close: () => void;
165
+ toggle: () => void;
166
+ clear: (appId?: string) => void;
167
+ };
168
+ setFocus: (on: boolean) => void;
169
+ toggleFocus: () => void;
170
+ mute: (appId: string, ms?: number | "today") => void;
171
+ unmute: (appId: string) => void;
172
+ isMuted: (appId: string) => boolean;
173
+ };
174
+ /** The whole notification state: banners, history, Focus and Notification Center. */
175
+ export declare function useNotifyState(): NotifyState;
176
+ /** Current time, refreshed every 15 seconds. 0 during server render. */
177
+ export declare function useNow(): number;
178
+ export declare function formatTime(time: number, current: number): string;
179
+ /** A short two-note glass chime, synthesized so there is no audio file to ship. */
180
+ export declare function playChime(volume?: number): void;
181
+ export type { NotificationPreviewProps, NotifierProps, NotifyAction, NotifyActionContext, NotifyAlertStyle, NotifyApp, NotifyAvatar, NotifyCloseButton, NotifyDismissReason, NotifyMaterial, NotifyOptions, NotifyPosition, NotifyRecord, NotifyState, NotifyTheme };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ "use client";
2
+ import{createContext as e,useCallback as t,useContext as n,useEffect as r,useLayoutEffect as i,useMemo as a,useRef as o,useState as s,useSyncExternalStore as c}from"react";import{createPortal as l,flushSync as u}from"react-dom";import{AnimatePresence as d,LayoutGroup as f,MotionConfig as p,motion as m,useDragControls as h,useMotionValue as g}from"motion/react";import{Fragment as _,jsx as v,jsxs as y}from"react/jsx-runtime";const b={banners:[],center:[],centerOpen:!1,focus:!1,muted:{}};let x=b;const S=/* @__PURE__ */ new Set,C={history:50};function w(e){x=e,S.forEach(e=>e())}const T={subscribe(e){return S.add(e),()=>{S.delete(e)}},getSnapshot:()=>x,getServerSnapshot:()=>b,connect(e){return Object.assign(C,e),()=>{C.onAlert=void 0}}};let E=0;const D=()=>`ntf-${Date.now().toString(36)}-${(E++).toString(36)}`,O=e=>e.toLowerCase().trim().replace(/[^a-z0-9]+/g,`-`)||`app`;function k(e){return e?typeof e==`string`?{id:O(e),name:e}:{...e,id:e.id??O(e.name)}:{id:`notify`,name:`Notification`}}const A=e=>x.banners.find(t=>t.id===e)??x.center.find(t=>t.id===e);function j(e){return!!e.silent||x.centerOpen||x.focus&&!e.timeSensitive||(x.muted[e.app.id]??0)>Date.now()}function M(e,t){if(!t)return e.map(e=>({key:e.id,app:e.app,items:[e]}));let n=/* @__PURE__ */ new Map;for(let t of e){let e=n.get(t.app.id);e||(e={key:`app:${t.app.id}`,app:t.app,items:[]},n.set(t.app.id,e)),e.items.push(t)}return[...n.values()]}const N=e=>typeof e==`string`?{title:e}:e;function P(e){let t=N(e);if(t.id&&A(t.id))return F(t.id,{...t,realert:!0}),t.id;let{id:n=D(),app:r,...i}=t,a=Date.now(),o={...i,id:n,app:k(r),createdAt:a,alertAt:a},s=j(o);return w({...x,banners:s?x.banners:[o,...x.banners],center:[o,...x.center].slice(0,C.history)}),s||C.onAlert?.(o),n}function F(e,t){let n=A(e);if(!n)return;let{realert:r,app:i,...a}=t,o={...n,...a,...i===void 0?null:{app:k(i)},...r?{alertAt:Date.now()}:null},s=t=>t.map(t=>t.id===e?o:t),c=x.banners.some(t=>t.id===e),l=!!r&&!j(o),u=s(x.banners);l&&(u=[o,...u.filter(t=>t.id!==e)]);let d=x.center.some(t=>t.id===e);w({...x,banners:c||l?u:x.banners,center:d?s(x.center):[o,...x.center].slice(0,C.history)}),l&&C.onAlert?.(o)}const I=[`timeout`,`swipe`,`dismiss`,`overflow`];function L(e,t){let n=e===null?null:new Set(Array.isArray(e)?e:[e]),r=e=>n===null||n.has(e.id),i=I.includes(t),a=/* @__PURE__ */ new Map;x.banners.filter(r).forEach(e=>a.set(e.id,e)),i||x.center.filter(r).forEach(e=>a.set(e.id,e)),a.size&&(w({...x,banners:x.banners.filter(e=>!r(e)),center:i?x.center:x.center.filter(e=>!r(e))}),a.forEach(e=>e.onDismiss?.(t)))}function R(e){if(e===x.centerOpen)return;let t=e?x.banners:[];w({...x,centerOpen:e,banners:e?[]:x.banners}),t.forEach(e=>e.onDismiss?.(`dismiss`))}function z(e,t,n={}){let r=P({...n,...N(t.loading),duration:1/0}),i=e=>F(r,{progress:null,duration:void 0,...n,...N(e),realert:!0});return e.then(e=>i(typeof t.success==`function`?t.success(e):t.success),e=>i(typeof t.error==`function`?t.error(e):t.error)),r}const ee=()=>{let e=/* @__PURE__ */ new Date;return e.setHours(23,59,59,999),e.getTime()-Date.now()},B=Object.assign(P,{update:F,dismiss:e=>L(e??null,`dismiss`),remove:e=>L(e,`clear`),clear:()=>L(null,`clear`),promise:z,center:{open:()=>R(!0),close:()=>R(!1),toggle:()=>R(!x.centerOpen),clear:e=>L(e?x.center.filter(t=>t.app.id===e).map(e=>e.id):null,`clear`)},setFocus:e=>w({...x,focus:e}),toggleFocus:()=>w({...x,focus:!x.focus}),mute:(e,t=36e5)=>w({...x,muted:{...x.muted,[e]:Date.now()+(t===`today`?ee():t)}}),unmute:e=>{let t={...x.muted};delete t[e],w({...x,muted:t})},isMuted:e=>(x.muted[e]??0)>Date.now()});function V(){return c(T.subscribe,T.getSnapshot,T.getServerSnapshot)}const te=()=>()=>{};function ne(){return c(te,()=>!0,()=>!1)}function re(e){return document.addEventListener(`visibilitychange`,e),()=>document.removeEventListener(`visibilitychange`,e)}function ie(){return c(re,()=>document.hidden,()=>!1)}let H=0,U;const W=/* @__PURE__ */ new Set;function ae(e){return U||=(H=Date.now(),setInterval(()=>{H=Date.now(),W.forEach(e=>e())},15e3)),W.add(e),()=>{W.delete(e),W.size||(clearInterval(U),U=void 0)}}function G(){return c(ae,()=>H,()=>0)}function oe(e,t){let n=t-e;if(!t||n<6e4)return`now`;let r=Math.floor(n/6e4);if(r<60)return`${r}m ago`;let i=Math.floor(r/60);if(i<24)return`${i}h ago`;let a=new Date(e),o=new Date(t);return o.setDate(o.getDate()-1),a.toDateString()===o.toDateString()?`Yesterday`:a.toLocaleDateString(void 0,{month:`short`,day:`numeric`})}function se(e,t,n,i){let a=o(i),s=o({key:e,duration:t,remaining:t});r(()=>{a.current=i}),r(()=>{let r=s.current;if((r.key!==e||r.duration!==t)&&(r.key=e,r.duration=t,r.remaining=t),n||!Number.isFinite(r.remaining))return;let i=Date.now(),o=setTimeout(()=>a.current(),Math.max(0,r.remaining));return()=>{clearTimeout(o),r.remaining-=Date.now()-i}},[e,t,n])}function K(e){let t=e.material??`glass`,n=typeof e.offset==`number`?{x:e.offset,y:e.offset}:e.offset??{};return{position:e.position??`top-right`,theme:e.theme??`system`,material:t,alertStyle:e.alertStyle??`banner`,duration:e.duration??6e3,max:Math.max(1,e.max??4),stack:e.stack??!0,swipeToDismiss:e.swipeToDismiss??!0,pauseOnHover:e.pauseOnHover??!0,expandable:e.expandable??!0,timestamps:e.timestamps??!0,closeButton:e.closeButton??`hover`,contextMenu:e.contextMenu??!0,sound:e.sound??!1,volume:e.volume??.6,width:e.width??356,radius:e.radius??(t===`glass`?24:16),gap:e.gap??10,offsetX:n.x??16,offsetY:n.y??16,accent:e.accent,history:e.history??50,formatTime:e.formatTime??oe}}function ce(e,t){return{"data-ntf-theme":e.theme,"data-ntf-material":e.material,"data-ntf-close":e.closeButton,style:{"--ntf-width":`${e.width}px`,"--ntf-radius":`${e.radius}px`,"--ntf-gap":`${e.gap}px`,"--ntf-ox":`${e.offsetX}px`,"--ntf-oy":`${e.offsetY}px`,...e.accent?{"--ntf-accent":e.accent}:null,...t}}}function le(e,t){return e.duration===void 0?(e.style??t.alertStyle)===`alert`?1/0:t.duration:e.duration}function q(e){return e.endsWith(`right`)?1:e.endsWith(`left`)?-1:0}const J={type:`spring`,bounce:.16,duration:.55},Y={duration:.32,ease:[.32,.72,0,1]};function ue(e){let t=q(e.position);return t===0?{y:e.position.startsWith(`top`)?-28:28,opacity:0,scale:.96}:{x:t*(e.width+e.offsetX+40)}}function de(e,t){let n=q(e.position),r=t||n;return r===0?{opacity:0,scale:.96,y:e.position.startsWith(`top`)?-12:12,transition:Y}:{x:r*(e.width+e.offsetX+80),opacity:n===0?0:1,transition:t?{type:`spring`,bounce:0,duration:.38}:Y}}const fe=/* @__PURE__ */ new Set,X=e({cfg:K({}),menuIds:fe}),Z=()=>n(X);function pe({size:e=8}){return/* @__PURE__ */ v(`svg`,{width:e,height:e,viewBox:`0 0 8 8`,"aria-hidden":`true`,children:/* @__PURE__ */ v(`path`,{d:`M1 1l6 6M7 1L1 7`,stroke:`currentColor`,strokeWidth:`1.5`,strokeLinecap:`round`})})}function me(){return/* @__PURE__ */ v(`svg`,{width:`9`,height:`9`,viewBox:`0 0 10 10`,"aria-hidden":`true`,children:/* @__PURE__ */ v(`path`,{d:`M2 3.75l3 3 3-3`,fill:`none`,stroke:`currentColor`,strokeWidth:`1.6`,strokeLinecap:`round`,strokeLinejoin:`round`})})}function he(){return/* @__PURE__ */ v(`svg`,{width:`12`,height:`12`,viewBox:`0 0 12 12`,"aria-hidden":`true`,children:/* @__PURE__ */ v(`path`,{d:`M6 10V2.4M2.6 5.6L6 2.2l3.4 3.4`,fill:`none`,stroke:`currentColor`,strokeWidth:`1.7`,strokeLinecap:`round`,strokeLinejoin:`round`})})}function ge(){return/* @__PURE__ */ v(`svg`,{width:`14`,height:`14`,viewBox:`0 0 14 14`,"aria-hidden":`true`,children:/* @__PURE__ */ v(`path`,{d:`M11.9 8.9A5.2 5.2 0 0 1 5.1 2.1a5.4 5.4 0 1 0 6.8 6.8z`,fill:`currentColor`})})}const _e=[[`#5ac8fa`,`#007aff`],[`#ffd60a`,`#ff9f0a`],[`#ff6b6b`,`#ff2d55`],[`#64d2ff`,`#5e5ce6`],[`#30d158`,`#00a86b`],[`#bf5af2`,`#8944ab`]];function ve(e){let t=0;for(let n=0;n<e.length;n++)t=t*31+e.charCodeAt(n)|0;return Math.abs(t)}function ye({app:e}){let{icon:t,name:n}=e;if(typeof t==`string`)return/* @__PURE__ */ v(`img`,{className:`ntf-appicon`,src:t,alt:``,draggable:!1});if(t)return/* @__PURE__ */ v(`span`,{className:`ntf-appicon`,children:t});let[r,i]=_e[ve(n)%_e.length];return/* @__PURE__ */ v(`span`,{className:`ntf-appicon ntf-monogram`,style:{background:`linear-gradient(180deg, ${r}, ${i})`},children:n.trim().charAt(0).toUpperCase()})}function be({avatar:e}){return typeof e==`string`?/* @__PURE__ */ v(`img`,{className:`ntf-avatar`,src:e,alt:``,draggable:!1}):/* @__PURE__ */ v(`span`,{className:`ntf-avatar`,style:e.background?{background:e.background}:void 0,children:e.initials.slice(0,2).toUpperCase()})}function xe({record:e}){return e.avatar?/* @__PURE__ */ y(_,{children:[/* @__PURE__ */ v(be,{avatar:e.avatar}),/* @__PURE__ */ v(`span`,{className:`ntf-badge`,children:/* @__PURE__ */ v(ye,{app:e.app})})]}):/* @__PURE__ */ v(ye,{app:e.app})}const Se=e=>typeof e==`string`||typeof e==`number`?String(e):``;function Ce({record:e,stackCount:t=1,menuRecords:n,dragging:i,defaultExpanded:a=!1,onClose:c,onExpandStack:l,onDone:u,onHold:f}){let{cfg:p,openMenu:h,menuIds:g}=Z(),_=G(),[b,x]=s(a),[S,C]=s(null),[w,T]=s(``),E=o(null),D=t>1,O=e.actions??[],k=typeof e.body==`string`&&e.body.length>90,A=p.expandable&&!D&&(O.length>0||!!e.image||k),j=A&&b,M=j?S:null,N=j||g.has(e.id);r(()=>{if(f)return f(e.id,N),()=>f(e.id,!1)},[f,e.id,N]),r(()=>{M&&E.current?.focus({preventScroll:!0})},[M]);let P={id:e.id,dismiss:()=>u?.(`action`)},F=()=>x(e=>!e),I=()=>{if(!i?.current){if(D)return l?.();e.onClick?(e.onClick(P),u?.(`click`)):A&&F()}},L=e=>{if(e.reply)return C(e);e.onSelect?.(P),e.keepOpen||u?.(`action`)},R=()=>{let e=w.trim();e&&M&&(M.onReply?.(e,P),T(``),C(null),u?.(`reply`))},z=e=>{e.target===e.currentTarget&&(e.key===`Enter`?(e.preventDefault(),I()):e.key===` `&&A?(e.preventDefault(),F()):e.key===`Escape`&&j?(e.preventDefault(),x(!1)):(e.key===`Delete`||e.key===`Backspace`)&&c&&(e.preventDefault(),c()))},ee=t=>{h&&p.contextMenu&&(t.preventDefault(),h({event:t,record:e,records:n??[e]}))},B=e=>t=>{t.stopPropagation(),e()},V=e.time===void 0?e.createdAt:+e.time,te=typeof M?.reply==`object`?M.reply.placeholder:void 0,ne=j&&(!!e.image||O.length>0);return/* @__PURE__ */ y(`article`,{className:`ntf-card`,"data-expanded":j||void 0,"data-stacked":D||void 0,"data-expandable":A||void 0,tabIndex:0,role:`group`,"aria-roledescription":`notification`,"aria-label":[e.app.name,Se(e.title),Se(e.body)].filter(Boolean).join(`. `),onClick:I,onKeyDown:z,onContextMenu:ee,children:[p.closeButton!==`never`&&c&&/* @__PURE__ */ v(`button`,{type:`button`,className:`ntf-close`,"aria-label":D?`Clear ${t} notifications`:`Close`,onClick:B(c),children:/* @__PURE__ */ v(pe,{})}),/* @__PURE__ */ y(`div`,{className:`ntf-main`,children:[/* @__PURE__ */ v(`div`,{className:`ntf-media`,children:/* @__PURE__ */ v(xe,{record:e})}),/* @__PURE__ */ y(`div`,{className:`ntf-text`,children:[e.timeSensitive&&/* @__PURE__ */ v(`div`,{className:`ntf-eyebrow`,children:`Time Sensitive`}),/* @__PURE__ */ y(`div`,{className:`ntf-head`,children:[/* @__PURE__ */ v(`div`,{className:`ntf-title`,children:e.title}),(p.timestamps||A)&&/* @__PURE__ */ y(`div`,{className:`ntf-meta`,children:[p.timestamps&&/* @__PURE__ */ v(`span`,{className:`ntf-time`,children:p.formatTime(V,_)}),A&&/* @__PURE__ */ v(`button`,{type:`button`,className:`ntf-chevron`,"aria-label":j?`Show less`:`Show more`,"aria-expanded":j,onClick:B(F),children:/* @__PURE__ */ v(me,{})})]})]}),e.subtitle&&/* @__PURE__ */ v(`div`,{className:`ntf-subtitle`,children:e.subtitle}),e.body&&/* @__PURE__ */ v(`div`,{className:`ntf-body`,children:e.body}),typeof e.progress==`number`&&/* @__PURE__ */ v(we,{value:e.progress})]}),e.image&&!j&&/* @__PURE__ */ v(`img`,{className:`ntf-thumb`,src:e.image,alt:``,draggable:!1})]}),/* @__PURE__ */ v(d,{initial:!1,children:ne&&/* @__PURE__ */ v(m.div,{className:`ntf-extra`,initial:{height:0,opacity:0},animate:{height:`auto`,opacity:1},exit:{height:0,opacity:0},transition:Y,children:/* @__PURE__ */ y(`div`,{className:`ntf-extra-inner`,children:[e.image&&/* @__PURE__ */ v(`img`,{className:`ntf-hero`,src:e.image,alt:``,draggable:!1}),M?/* @__PURE__ */ y(`form`,{className:`ntf-reply`,"data-ntf-nodrag":!0,onClick:e=>e.stopPropagation(),onSubmit:e=>{e.preventDefault(),R()},children:[/* @__PURE__ */ v(`input`,{ref:E,value:w,placeholder:te??`Reply…`,"aria-label":M.label,onChange:e=>T(e.target.value),onKeyDown:e=>{e.key===`Escape`&&(e.stopPropagation(),C(null))}}),/* @__PURE__ */ v(`button`,{type:`submit`,className:`ntf-send`,"aria-label":`Send`,disabled:!w.trim(),children:/* @__PURE__ */ v(he,{})})]}):O.length>0&&/* @__PURE__ */ v(`div`,{className:`ntf-actions`,"data-count":Math.min(O.length,3),children:O.map(e=>/* @__PURE__ */ v(`button`,{type:`button`,className:`ntf-action`,"data-destructive":e.destructive||void 0,onClick:B(()=>L(e)),children:e.label},e.label))})]})},`extra`)})]})}function we({value:e}){let t=Math.max(0,Math.min(1,e));return/* @__PURE__ */ v(`div`,{className:`ntf-progress`,role:`progressbar`,"aria-valuemin":0,"aria-valuemax":100,"aria-valuenow":Math.round(t*100),children:/* @__PURE__ */ v(`span`,{style:{transform:`scaleX(${t})`}})})}function Te(e,t,n){let r=h(),i=g(0),a=o(!1);return e?{dragging:a,props:{style:{x:i},drag:`x`,dragControls:r,dragListener:!1,dragConstraints:{left:0,right:0},dragElastic:t===1?{left:.06,right:.92}:t===-1?{left:.92,right:.06}:.92,dragTransition:{bounceStiffness:520,bounceDamping:38},onPointerDown(e){e.button!==0||e.target.closest(`button, input, textarea, select, a, [data-ntf-nodrag]`)||r.start(e)},onDragStart(){a.current=!0},onDragEnd(e,r){setTimeout(()=>{a.current=!1},0);let i=t||Math.sign(r.offset.x||r.velocity.x);i&&(r.offset.x*i>80||r.velocity.x*i>500)&&n(i)}}}:{dragging:a,props:{style:{x:i}}}}function Ee({ref:e,group:n,variant:r}){let{cfg:i}=Z(),{items:a,app:c}=n,l=a[0],f=a.map(e=>e.id),[p,h]=s(!1),[g,_]=s(!1),[b,x]=s(!1),[S,C]=s(0),[w,T]=s(0),E=o(/* @__PURE__ */ new Set),D=ie(),O=a.length>1,k=O&&p,A=O&&!k,j=q(i.position),M=r===`banner`?`swipe`:`close`,N=t((e,t)=>{t?E.current.add(e):E.current.delete(e),C(E.current.size)},[]),P=r!==`banner`||D||k||b||S>0||i.pauseOnHover&&g;se(`${l.id}:${l.alertAt}`,r===`banner`?le(l,i):1/0,P,()=>L(a.filter(e=>Number.isFinite(le(e,i))).map(e=>e.id),`timeout`));let F=Te(i.swipeToDismiss&&!k,j,e=>{u(()=>T(e)),L(f,M)});return/* @__PURE__ */ y(m.li,{ref:e,layout:`position`,className:`ntf-group`,"data-variant":r,"data-collapsed":A||void 0,"data-depth":A?Math.min(a.length-1,2):void 0,initial:ue(i),animate:{x:0,y:0,opacity:1,scale:1},exit:de(i,w),transition:J,onPointerEnter:e=>e.pointerType===`mouse`&&_(!0),onPointerLeave:()=>_(!1),onFocus:e=>{e.target.matches(`:focus-visible`)&&x(!0)},onBlur:e=>{e.currentTarget.contains(e.relatedTarget)||x(!1)},...F.props,children:[/* @__PURE__ */ v(d,{initial:!1,mode:`popLayout`,children:k&&/* @__PURE__ */ y(m.div,{className:`ntf-group-header`,initial:{opacity:0,y:8},animate:{opacity:1,y:0},exit:{opacity:0,y:8},transition:Y,children:[/* @__PURE__ */ v(`span`,{className:`ntf-group-title`,children:c.name}),/* @__PURE__ */ y(`span`,{className:`ntf-group-buttons`,children:[/* @__PURE__ */ v(`button`,{type:`button`,className:`ntf-pill`,onClick:()=>h(!1),children:`Show Less`}),/* @__PURE__ */ v(`button`,{type:`button`,className:`ntf-pill ntf-pill-icon`,"aria-label":`Clear all ${c.name} notifications`,onClick:()=>L(f,`clear`),children:/* @__PURE__ */ v(pe,{})})]})]},`header`)}),/* @__PURE__ */ y(`div`,{className:`ntf-stack`,children:[/* @__PURE__ */ v(d,{initial:!1,mode:`popLayout`,children:a.map((e,t)=>(t===0||k)&&/* @__PURE__ */ v(De,{record:e,index:t,items:a,collapsed:A,expanded:k,swipeReason:M,groupDragging:F.dragging,onExpandStack:()=>h(!0),onHold:N},e.id))}),/* @__PURE__ */ v(d,{initial:!1,children:A&&/* @__PURE__ */ y(m.div,{className:`ntf-peeks`,"aria-hidden":`true`,initial:{opacity:0},animate:{opacity:1},exit:{opacity:0},transition:Y,children:[/* @__PURE__ */ v(`span`,{className:`ntf-peek`}),a.length>2&&/* @__PURE__ */ v(`span`,{className:`ntf-peek`,"data-depth":`2`})]},`peeks`)})]})]})}function De({ref:e,record:t,index:n,items:r,collapsed:i,expanded:a,swipeReason:o,groupDragging:c,onExpandStack:l,onHold:d}){let{cfg:f}=Z(),[p,h]=s(0),g=Te(f.swipeToDismiss&&a,q(f.position),e=>{u(()=>h(e)),L(t.id,o)});return/* @__PURE__ */ v(m.div,{ref:e,layout:`position`,className:`ntf-slot`,initial:n===0?ue(f):{opacity:0,y:-18,scale:.95},animate:{x:0,y:0,opacity:1,scale:1},exit:p?de(f,p):{opacity:0,y:-14,scale:.95,transition:Y},transition:J,...g.props,children:/* @__PURE__ */ v(Ce,{record:t,stackCount:i?r.length:1,menuRecords:i?r:[t],dragging:a?g.dragging:c,onClose:()=>L(i?r.map(e=>e.id):t.id,`close`),onExpandStack:l,onDone:e=>L(t.id,e),onHold:d})})}function Oe({records:e,focus:t,side:n}){let{cfg:r}=Z(),i=a(()=>M(e,!0),[e]),o=(n===`left`?-1:1)*(r.width+r.offsetX*2+40);return/* @__PURE__ */ y(_,{children:[/* @__PURE__ */ v(m.div,{className:`ntf-scrim`,"data-side":n,onClick:()=>B.center.close(),initial:{opacity:0},animate:{opacity:1},exit:{opacity:0},transition:Y}),/* @__PURE__ */ y(m.aside,{className:`ntf-center`,"data-side":n,role:`dialog`,"aria-label":`Notification Center`,initial:{x:o},animate:{x:0},exit:{x:o,transition:Y},transition:J,children:[/* @__PURE__ */ y(`div`,{className:`ntf-center-bar`,children:[/* @__PURE__ */ y(`button`,{type:`button`,className:`ntf-focus`,"aria-pressed":t,onClick:()=>B.toggleFocus(),children:[/* @__PURE__ */ v(`span`,{className:`ntf-focus-icon`,children:/* @__PURE__ */ v(ge,{})}),/* @__PURE__ */ y(`span`,{className:`ntf-focus-text`,children:[/* @__PURE__ */ v(`strong`,{children:`Focus`}),/* @__PURE__ */ v(`span`,{children:t?`Do Not Disturb`:`Off`})]})]}),e.length>0&&/* @__PURE__ */ v(`button`,{type:`button`,className:`ntf-pill`,onClick:()=>B.center.clear(),children:`Clear All`})]}),/* @__PURE__ */ v(f,{id:`ntf-center`,children:/* @__PURE__ */ v(`ol`,{className:`ntf-center-list`,"aria-label":`Notification history`,children:/* @__PURE__ */ v(d,{mode:`popLayout`,initial:!1,children:i.map(e=>/* @__PURE__ */ v(Ee,{group:e,variant:`center`},e.key))})})}),/* @__PURE__ */ v(d,{initial:!1,children:e.length===0&&/* @__PURE__ */ v(m.p,{className:`ntf-empty`,initial:{opacity:0},animate:{opacity:1},exit:{opacity:0},transition:Y,children:`No Notifications`},`empty`)})]})]})}function ke({x:e,y:t,items:n,onClose:a}){let s=o(null);return i(()=>{let n=s.current;if(!n)return;let{width:r,height:i}=n.getBoundingClientRect(),a=Math.max(8,Math.min(e,window.innerWidth-r-8)),o=t+i>window.innerHeight-8?Math.max(8,t-i):t;n.style.left=`${a}px`,n.style.top=`${o}px`,n.querySelector(`button`)?.focus({preventScroll:!0})},[e,t]),r(()=>{let e=e=>{s.current?.contains(e.target)||a()},t=e=>{e.key===`Escape`&&a()};return window.addEventListener(`pointerdown`,e,!0),window.addEventListener(`keydown`,t),window.addEventListener(`resize`,a),window.addEventListener(`blur`,a),window.addEventListener(`scroll`,a,!0),()=>{window.removeEventListener(`pointerdown`,e,!0),window.removeEventListener(`keydown`,t),window.removeEventListener(`resize`,a),window.removeEventListener(`blur`,a),window.removeEventListener(`scroll`,a,!0)}},[a]),/* @__PURE__ */ v(m.div,{ref:s,className:`ntf-menu`,role:`menu`,initial:{opacity:0,scale:.98},animate:{opacity:1,scale:1,transition:{duration:.12}},exit:{opacity:0,transition:{duration:.18}},onKeyDown:e=>{if(e.key!==`ArrowDown`&&e.key!==`ArrowUp`)return;e.preventDefault();let t=[...s.current?.querySelectorAll(`button`)??[]];t[(t.indexOf(document.activeElement)+(e.key===`ArrowDown`?1:-1)+t.length)%t.length]?.focus()},onContextMenu:e=>e.preventDefault(),children:n.map((e,t)=>e===`separator`?/* @__PURE__ */ v(`div`,{className:`ntf-menu-sep`,role:`separator`},`sep-${t}`):/* @__PURE__ */ v(`button`,{type:`button`,role:`menuitem`,className:`ntf-menu-item`,"data-destructive":e.destructive||void 0,onClick:()=>{a(),e.onSelect()},children:e.label},e.label))})}let Q=null;function $(e=.6){if(typeof window<`u`)try{let t=window.AudioContext??window.webkitAudioContext;if(!t)return;Q??=new t,Q.state===`suspended`&&Q.resume();let n=Q,r=n.currentTime+.01,i=n.createGain();i.gain.value=Math.max(0,Math.min(1,e))*.16,i.connect(n.destination);for(let[e,t]of[[1567.98,0],[2349.32,.075]]){let a=n.createOscillator(),o=n.createGain();a.type=`sine`,a.frequency.value=e,o.gain.setValueAtTime(0,r+t),o.gain.linearRampToValueAtTime(1,r+t+.006),o.gain.exponentialRampToValueAtTime(1e-4,r+t+.85),a.connect(o).connect(i),a.start(r+t),a.stop(r+t+.9)}}catch{}}function Ae({record:e,records:t}){let{app:n}=e,r=[];if(t.length===1){let t={id:e.id,dismiss:()=>L(e.id,`action`)};for(let n of e.actions??[])n.reply||r.push({label:n.label,destructive:n.destructive,onSelect:()=>{n.onSelect?.(t),n.keepOpen||L(e.id,`action`)}});r.length&&r.push(`separator`)}return B.isMuted(n.id)?r.push({label:`Unmute ${n.name}`,onSelect:()=>B.unmute(n.id)}):r.push({label:`Mute for 1 Hour`,onSelect:()=>B.mute(n.id,36e5)},{label:`Mute for Today`,onSelect:()=>B.mute(n.id,`today`)}),r.push(`separator`,{label:t.length>1?`Clear All (${t.length})`:`Clear`,onSelect:()=>L(t.map(e=>e.id),`clear`)}),r}function je(e){let n=K(e),i=V(),o=ne(),[c,u]=s(null);r(()=>T.connect({history:n.history,onAlert:e=>{(e.sound??n.sound)&&$(n.volume)}}),[n.history,n.sound,n.volume]);let m=a(()=>M(i.banners,n.stack),[i.banners,n.stack]),h=m.slice(0,n.max),g=m.slice(n.max).flatMap(e=>e.items.map(e=>e.id)).join(` `);r(()=>{g&&L(g.split(` `),`overflow`)},[g]),r(()=>{if(!i.centerOpen)return;let e=e=>{e.key===`Escape`&&B.center.close()};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[i.centerOpen]);let _=t(({event:e,record:t,records:n})=>{u({x:e.clientX,y:e.clientY,record:t,records:n})},[]),b=t(()=>u(null),[]),x=a(()=>new Set(c?.records.map(e=>e.id)),[c]);if(!o)return null;let[S,C]=n.position.split(`-`);return l(/* @__PURE__ */ v(X,{value:{cfg:n,openMenu:_,menuIds:x},children:/* @__PURE__ */ v(p,{reducedMotion:`user`,children:/* @__PURE__ */ y(`div`,{className:e.className?`ntf-root ${e.className}`:`ntf-root`,...ce(n,e.style),children:[/* @__PURE__ */ v(f,{id:`ntf-banners`,children:/* @__PURE__ */ v(`ol`,{className:`ntf-viewport`,"data-x":C,"data-y":S,"aria-label":e.label??`Notifications`,"aria-live":`polite`,children:/* @__PURE__ */ v(d,{mode:`popLayout`,initial:!1,children:h.map(e=>/* @__PURE__ */ v(Ee,{group:e,variant:`banner`},e.key))})})}),/* @__PURE__ */ v(d,{children:i.centerOpen&&/* @__PURE__ */ v(Oe,{records:i.center,focus:i.focus,side:C===`left`?`left`:`right`},`center`)}),/* @__PURE__ */ v(d,{children:c&&/* @__PURE__ */ v(ke,{x:c.x,y:c.y,items:Ae(c),onClose:b},`${c.x}:${c.y}`)})]})})}),document.body)}const Me=/* @__PURE__ */ new Set;function Ne({notification:e,stackCount:t=1,defaultExpanded:n,onClose:r,className:i,style:o,...s}){let c=K({...s,contextMenu:!1}),l=G(),u=a(()=>{let{id:t=`preview`,app:n,...r}=e;return{...r,id:t,app:k(n),createdAt:l,alertAt:l}},[e,l]),d=Math.min(t-1,2);return/* @__PURE__ */ v(X,{value:{cfg:c,menuIds:Me},children:/* @__PURE__ */ v(`div`,{className:i?`ntf-root ntf-inline ${i}`:`ntf-root ntf-inline`,...ce(c,o),children:/* @__PURE__ */ v(`div`,{className:`ntf-group`,"data-variant":`preview`,"data-collapsed":d>0||void 0,"data-depth":d||void 0,children:/* @__PURE__ */ y(`div`,{className:`ntf-stack`,children:[/* @__PURE__ */ v(Ce,{record:u,stackCount:t,defaultExpanded:n,onClose:r}),d>0&&/* @__PURE__ */ y(`div`,{className:`ntf-peeks`,"aria-hidden":`true`,children:[/* @__PURE__ */ v(`span`,{className:`ntf-peek`}),d>1&&/* @__PURE__ */ v(`span`,{className:`ntf-peek`,"data-depth":`2`})]})]})})})})}export{Ne as NotificationPreview,je as Notifier,oe as formatTime,M as groupRecords,B as notify,$ as playChime,V as useNotifyState,G as useNow};
@@ -0,0 +1 @@
1
+ .ntf-root{--ntf-width:356px;--ntf-radius:24px;--ntf-gap:10px;--ntf-ox:16px;--ntf-oy:16px;--ntf-inner-radius:max(8px, calc(var(--ntf-radius) - 12px));--ntf-font:-apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", "Segoe UI", Roboto, Arial, sans-serif;--ntf-label:#000000e0;--ntf-label-2:#00000080;--ntf-fill:#0000000e;--ntf-fill-2:#00000017;--ntf-fill-3:#00000021;--ntf-separator:#0000001a;--ntf-tint:var(--ntf-accent,#007aff);--ntf-red:#ff3b30;--ntf-urgent:#ff9500;--ntf-focus:#5e5ce6;--ntf-close-bg:#fcfcfcf5;--ntf-close-fg:#0000009e;--ntf-close-edge:#00000024;--ntf-menu-bg:#f6f6f6d1;--ntf-menu-shadow:0 0 0 .5px #00000024, inset 0 .5px 0 #ffffffb3, 0 12px 36px #0003, 0 2px 8px #00000014;--ntf-bg:#fff9;--ntf-peek-bg:#ffffff73;--ntf-filter:blur(22px) saturate(190%);--ntf-shadow:0 0 0 .5px #00000017, inset 0 1px .5px #fffffff2, inset 0 -.5px .5px #ffffff80, 0 10px 34px #0000001f, 0 2px 8px #0000000d;--ntf-peek-shadow:0 0 0 .5px #00000012, 0 6px 18px #00000014;font-family:var(--ntf-font);letter-spacing:-.08px;color:var(--ntf-label);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-size:13px;line-height:16px}.ntf-root[data-ntf-material=regular]{--ntf-bg:#f6f6f8cc;--ntf-peek-bg:#f0f0f2b3;--ntf-filter:blur(30px) saturate(180%);--ntf-shadow:0 0 0 .5px #0000001f, 0 8px 26px #00000024, 0 1px 4px #0000000d}.ntf-root[data-ntf-material=thick]{--ntf-bg:#f9f9faed;--ntf-peek-bg:#f2f2f4e6;--ntf-filter:blur(40px) saturate(170%);--ntf-shadow:0 0 0 .5px #0000001f, 0 8px 26px #00000026, 0 1px 4px #0000000d}.ntf-root[data-ntf-material=solid]{--ntf-bg:#f6f6f8;--ntf-peek-bg:#ebebee;--ntf-filter:none;--ntf-shadow:0 0 0 .5px #0000001f, 0 8px 26px #00000024, 0 1px 4px #0000000d}.ntf-root[data-ntf-theme=dark]{--ntf-label:#ffffffeb;--ntf-label-2:#ffffff8c;--ntf-fill:#ffffff1a;--ntf-fill-2:#ffffff26;--ntf-fill-3:#fff3;--ntf-separator:#ffffff1f;--ntf-tint:var(--ntf-accent,#0a84ff);--ntf-red:#ff453a;--ntf-urgent:#ff9f0a;--ntf-focus:#6e6af0;--ntf-close-bg:#48484af5;--ntf-close-fg:#ffffffd1;--ntf-close-edge:#00000080;--ntf-menu-bg:#2a2a2ccc;--ntf-menu-shadow:0 0 0 .5px #0009, inset 0 0 0 .5px #ffffff24, 0 12px 36px #00000073;--ntf-bg:#1e1e20b8;--ntf-peek-bg:#1e1e2099;--ntf-shadow:0 0 0 .5px #0000008c, inset 0 .5px .5px #ffffff47, inset 0 0 0 .5px #ffffff12, 0 12px 38px #0000005c;--ntf-peek-shadow:0 0 0 .5px #00000080, inset 0 0 0 .5px #ffffff0f, 0 6px 18px #0000003d}.ntf-root[data-ntf-theme=dark][data-ntf-material=regular]{--ntf-bg:#28282ad6;--ntf-peek-bg:#28282abd;--ntf-shadow:0 0 0 .5px #0009, inset 0 0 0 .5px #ffffff1f, 0 10px 30px #0006}.ntf-root[data-ntf-theme=dark][data-ntf-material=thick]{--ntf-bg:#242426eb;--ntf-peek-bg:#242426d9;--ntf-shadow:0 0 0 .5px #0009, inset 0 0 0 .5px #ffffff1f, 0 10px 30px #0000006b}.ntf-root[data-ntf-theme=dark][data-ntf-material=solid]{--ntf-bg:#2c2c2e;--ntf-peek-bg:#242426;--ntf-shadow:0 0 0 .5px #0009, inset 0 0 0 .5px #ffffff1a, 0 10px 30px #0000006b}@media (prefers-color-scheme:dark){.ntf-root[data-ntf-theme=system]{--ntf-label:#ffffffeb;--ntf-label-2:#ffffff8c;--ntf-fill:#ffffff1a;--ntf-fill-2:#ffffff26;--ntf-fill-3:#fff3;--ntf-separator:#ffffff1f;--ntf-tint:var(--ntf-accent,#0a84ff);--ntf-red:#ff453a;--ntf-urgent:#ff9f0a;--ntf-focus:#6e6af0;--ntf-close-bg:#48484af5;--ntf-close-fg:#ffffffd1;--ntf-close-edge:#00000080;--ntf-menu-bg:#2a2a2ccc;--ntf-menu-shadow:0 0 0 .5px #0009, inset 0 0 0 .5px #ffffff24, 0 12px 36px #00000073;--ntf-bg:#1e1e20b8;--ntf-peek-bg:#1e1e2099;--ntf-shadow:0 0 0 .5px #0000008c, inset 0 .5px .5px #ffffff47, inset 0 0 0 .5px #ffffff12, 0 12px 38px #0000005c;--ntf-peek-shadow:0 0 0 .5px #00000080, inset 0 0 0 .5px #ffffff0f, 0 6px 18px #0000003d}.ntf-root[data-ntf-theme=system][data-ntf-material=regular]{--ntf-bg:#28282ad6;--ntf-peek-bg:#28282abd;--ntf-shadow:0 0 0 .5px #0009, inset 0 0 0 .5px #ffffff1f, 0 10px 30px #0006}.ntf-root[data-ntf-theme=system][data-ntf-material=thick]{--ntf-bg:#242426eb;--ntf-peek-bg:#242426d9;--ntf-shadow:0 0 0 .5px #0009, inset 0 0 0 .5px #ffffff1f, 0 10px 30px #0000006b}.ntf-root[data-ntf-theme=system][data-ntf-material=solid]{--ntf-bg:#2c2c2e;--ntf-peek-bg:#242426;--ntf-shadow:0 0 0 .5px #0009, inset 0 0 0 .5px #ffffff1a, 0 10px 30px #0000006b}}.ntf-root *,.ntf-root :before,.ntf-root :after{box-sizing:border-box}.ntf-root button{font:inherit;letter-spacing:inherit;cursor:default;-webkit-tap-highlight-color:transparent}.ntf-viewport{z-index:2147483000;gap:var(--ntf-gap);width:min(var(--ntf-width), calc(100vw - 2 * var(--ntf-ox)));pointer-events:none;flex-direction:column;margin:0;padding:0;list-style:none;display:flex;position:fixed}.ntf-viewport[data-y=top]{top:calc(var(--ntf-oy) + env(safe-area-inset-top,0px))}.ntf-viewport[data-y=bottom]{bottom:calc(var(--ntf-oy) + env(safe-area-inset-bottom,0px));flex-direction:column-reverse}.ntf-viewport[data-x=right]{right:var(--ntf-ox)}.ntf-viewport[data-x=left]{left:var(--ntf-ox)}.ntf-viewport[data-x=center]{margin-inline:auto;left:0;right:0}.ntf-group{pointer-events:auto;touch-action:pan-y;width:100%;list-style:none;position:relative}.ntf-group[data-collapsed]{padding-bottom:8px}.ntf-group[data-collapsed][data-depth="2"]{padding-bottom:15px}.ntf-stack{isolation:isolate;gap:var(--ntf-gap);flex-direction:column;display:flex;position:relative}.ntf-slot{touch-action:pan-y;position:relative}.ntf-peeks{z-index:-1;pointer-events:none;position:absolute;inset:0}.ntf-peek{z-index:2;border-radius:var(--ntf-radius);background:var(--ntf-peek-bg);height:40px;backdrop-filter:var(--ntf-filter);box-shadow:var(--ntf-peek-shadow);position:absolute;bottom:-8px;left:11px;right:11px}.ntf-peek[data-depth="2"]{z-index:1;opacity:.8;bottom:-15px;left:22px;right:22px}.ntf-group-header{justify-content:space-between;align-items:center;gap:8px;margin-bottom:8px;display:flex}.ntf-group-title{white-space:nowrap;text-overflow:ellipsis;background:var(--ntf-bg);min-width:0;height:26px;backdrop-filter:var(--ntf-filter);box-shadow:var(--ntf-peek-shadow);border-radius:13px;padding:0 11px;font-size:13px;font-weight:600;line-height:26px;overflow:hidden}.ntf-group-buttons{flex:none;gap:6px;display:flex}.ntf-pill{height:26px;color:var(--ntf-label);background:var(--ntf-bg);backdrop-filter:var(--ntf-filter);box-shadow:var(--ntf-peek-shadow);border:0;border-radius:13px;padding:0 11px;font-size:12px;font-weight:500;transition:background-color .15s,transform .15s}.ntf-pill:active{transform:scale(.96)}.ntf-pill-icon{width:26px;color:var(--ntf-label-2);place-items:center;padding:0;display:grid}.ntf-pill:focus-visible,.ntf-chevron:focus-visible,.ntf-close:focus-visible,.ntf-action:focus-visible,.ntf-focus:focus-visible{outline:2px solid var(--ntf-tint);outline-offset:2px}.ntf-card{border-radius:var(--ntf-radius);background:var(--ntf-bg);width:100%;backdrop-filter:var(--ntf-filter);box-shadow:var(--ntf-shadow);color:var(--ntf-label);text-align:left;cursor:default;-webkit-user-select:none;user-select:none;outline:none;padding:12px 14px 12px 12px;transition:box-shadow .2s;position:relative}.ntf-card:focus-visible{box-shadow:var(--ntf-shadow), 0 0 0 3px color-mix(in srgb, var(--ntf-tint) 55%, transparent)}.ntf-main{align-items:center;gap:10px;min-height:38px;display:flex}.ntf-media{flex:none;width:38px;height:38px;position:relative}.ntf-appicon{object-fit:cover;border-radius:23%;place-items:center;width:100%;height:100%;display:grid;overflow:hidden;box-shadow:0 .5px 1.5px #00000038,inset 0 0 0 .5px #0000000f}.ntf-appicon>svg{width:100%;height:100%;display:block}.ntf-monogram{color:#fff;letter-spacing:0;font-size:17px;font-weight:600}.ntf-avatar{object-fit:cover;color:#fff;letter-spacing:.01em;background:linear-gradient(#a9afbb,#868b96);border-radius:50%;place-items:center;width:100%;height:100%;padding:0 4px 3px 0;font-size:13px;font-weight:600;display:grid}.ntf-badge{width:17px;height:17px;position:absolute;bottom:-4px;right:-5px}.ntf-badge .ntf-appicon{box-shadow:0 0 0 1px var(--ntf-close-bg), 0 1px 2px #00000040}.ntf-badge .ntf-monogram{font-size:10px}.ntf-text{flex:1;min-width:0}.ntf-eyebrow{letter-spacing:.02em;text-transform:uppercase;color:var(--ntf-urgent);margin-bottom:1px;font-size:11px;font-weight:600;line-height:13px}.ntf-head{align-items:center;gap:8px;display:flex}.ntf-title{white-space:nowrap;text-overflow:ellipsis;flex:1;min-width:0;font-weight:600;overflow:hidden}.ntf-card[data-expanded] .ntf-title{white-space:normal}.ntf-meta{flex:none;justify-content:flex-end;align-items:center;min-width:20px;height:16px;display:flex;position:relative}.ntf-time{color:var(--ntf-label-2);white-space:nowrap;font-size:12px;transition:opacity .15s}.ntf-chevron{width:20px;height:20px;color:var(--ntf-label-2);background:var(--ntf-fill);opacity:0;border:0;border-radius:50%;place-items:center;padding:0;transition:opacity .15s,transform .2s,background-color .15s;display:grid;position:absolute;top:50%;right:-3px;transform:translateY(-50%)scale(.85)}.ntf-chevron:hover{background:var(--ntf-fill-2)}.ntf-chevron svg{transition:transform .25s cubic-bezier(.32,.72,0,1)}.ntf-card[data-expanded] .ntf-chevron svg{transform:rotate(180deg)}.ntf-card[data-expandable]:hover .ntf-chevron,.ntf-card[data-expandable]:focus-within .ntf-chevron,.ntf-card[data-expanded] .ntf-chevron{opacity:1;transform:translateY(-50%)}.ntf-card[data-expandable]:hover .ntf-time,.ntf-card[data-expandable]:focus-within .ntf-time,.ntf-card[data-expanded] .ntf-time{opacity:0}.ntf-subtitle{white-space:nowrap;text-overflow:ellipsis;font-weight:500;overflow:hidden}.ntf-card[data-expanded] .ntf-subtitle{white-space:normal}.ntf-body{-webkit-line-clamp:3;line-clamp:3;overflow-wrap:anywhere;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.ntf-card[data-expanded] .ntf-body{-webkit-line-clamp:12;line-clamp:12}.ntf-thumb{object-fit:cover;width:40px;height:40px;box-shadow:inset 0 0 0 .5px var(--ntf-separator);border-radius:8px;flex:none}.ntf-progress{background:var(--ntf-fill-2);border-radius:2px;height:4px;margin-top:7px;overflow:hidden}.ntf-progress>span{border-radius:inherit;background:var(--ntf-tint);transform-origin:0;height:100%;transition:transform .45s;display:block}.ntf-close{z-index:2;width:20px;height:20px;color:var(--ntf-close-fg);background:var(--ntf-close-bg);box-shadow:0 0 0 .5px var(--ntf-close-edge), 0 1px 4px #00000029;opacity:0;border:0;border-radius:50%;place-items:center;padding:0;transition:opacity .15s,transform .2s cubic-bezier(.32,.72,0,1);display:grid;position:absolute;top:-7px;left:-7px;transform:scale(.8)}.ntf-card:hover>.ntf-close,.ntf-card:focus-within>.ntf-close,.ntf-root[data-ntf-close=always] .ntf-close{opacity:1;transform:none}.ntf-close:active{transform:scale(.9)}.ntf-extra{overflow:hidden}.ntf-extra-inner{gap:8px;padding-top:10px;display:grid}.ntf-hero{object-fit:cover;border-radius:var(--ntf-inner-radius);width:100%;max-height:220px;display:block}.ntf-actions{grid-template-columns:1fr;gap:6px;display:grid}.ntf-actions[data-count="2"]{grid-template-columns:1fr 1fr}.ntf-action{border-radius:var(--ntf-inner-radius);height:30px;color:var(--ntf-label);background:var(--ntf-fill);white-space:nowrap;text-overflow:ellipsis;border:0;padding:0 10px;font-size:13px;font-weight:500;transition:background-color .15s;overflow:hidden}.ntf-action:hover{background:var(--ntf-fill-2)}.ntf-action:active{background:var(--ntf-fill-3)}.ntf-action[data-destructive]{color:var(--ntf-red)}.ntf-reply{border-radius:var(--ntf-inner-radius);background:var(--ntf-fill);align-items:center;gap:6px;margin:0;padding:4px 4px 4px 12px;display:flex}.ntf-reply input{min-width:0;height:24px;font:inherit;letter-spacing:inherit;color:var(--ntf-label);-webkit-user-select:text;user-select:text;background:0 0;border:0;outline:none;flex:1;padding:0}.ntf-reply input:focus,.ntf-reply input:focus-visible{box-shadow:none;outline:none}.ntf-reply:focus-within{box-shadow:0 0 0 2px color-mix(in srgb, var(--ntf-tint) 45%, transparent)}.ntf-reply input::placeholder{color:var(--ntf-label-2)}.ntf-send{color:#fff;background:var(--ntf-tint);border:0;border-radius:50%;flex:none;place-items:center;width:24px;height:24px;padding:0;transition:opacity .15s,transform .15s;display:grid}.ntf-send:disabled{opacity:.35}.ntf-send:not(:disabled):active{transform:scale(.9)}.ntf-scrim{z-index:2147482999;background:linear-gradient(270deg,#0000002e,#0000 560px);position:fixed;inset:0}.ntf-scrim[data-side=left]{background:linear-gradient(90deg,#0000002e,#0000 560px)}.ntf-center{z-index:2147483001;--ntf-bleed:48px;width:min(calc(var(--ntf-width) + 2 * var(--ntf-ox) + var(--ntf-bleed)), 100vw);padding:calc(var(--ntf-oy) + env(safe-area-inset-top,0px)) var(--ntf-ox) 32px calc(var(--ntf-ox) + var(--ntf-bleed));overscroll-behavior:contain;scrollbar-width:none;flex-direction:column;gap:14px;display:flex;position:fixed;top:0;bottom:0;right:0;overflow-y:auto}.ntf-center::-webkit-scrollbar{display:none}.ntf-center[data-side=left]{padding-left:var(--ntf-ox);padding-right:calc(var(--ntf-ox) + var(--ntf-bleed));left:0;right:auto}@media (width<=480px){.ntf-center{--ntf-bleed:0px}}.ntf-center-bar{justify-content:space-between;align-items:center;gap:8px;display:flex}.ntf-focus{height:46px;color:var(--ntf-label);text-align:left;background:var(--ntf-bg);backdrop-filter:var(--ntf-filter);box-shadow:var(--ntf-shadow);border:0;border-radius:23px;align-items:center;gap:9px;padding:0 14px 0 8px;display:flex}.ntf-focus-icon{width:30px;height:30px;color:var(--ntf-label);background:var(--ntf-fill-2);border-radius:50%;place-items:center;transition:background-color .2s,color .2s;display:grid}.ntf-focus[aria-pressed=true] .ntf-focus-icon{color:#fff;background:var(--ntf-focus)}.ntf-focus-text{line-height:15px;display:grid}.ntf-focus-text strong{font-weight:600}.ntf-focus-text span{color:var(--ntf-label-2);font-size:12px}.ntf-center-list{gap:var(--ntf-gap);flex-direction:column;margin:0;padding:0;list-style:none;display:flex}.ntf-empty{border-radius:var(--ntf-radius);text-align:center;color:var(--ntf-label-2);background:var(--ntf-bg);backdrop-filter:var(--ntf-filter);box-shadow:var(--ntf-shadow);margin:0;padding:16px;font-weight:500}.ntf-menu{z-index:2147483002;background:var(--ntf-menu-bg);backdrop-filter:blur(30px)saturate(180%);min-width:196px;box-shadow:var(--ntf-menu-shadow);transform-origin:0 0;border-radius:12px;padding:5px;position:fixed;top:0;left:0}.ntf-menu-item{text-align:left;width:100%;color:var(--ntf-label);white-space:nowrap;background:0 0;border:0;border-radius:6px;outline:none;padding:3px 10px;font-size:13px;line-height:18px;display:block}.ntf-menu-item:hover,.ntf-menu-item:focus-visible{color:#fff;background:var(--ntf-tint)}.ntf-menu-item[data-destructive]{color:var(--ntf-red)}.ntf-menu-item[data-destructive]:hover,.ntf-menu-item[data-destructive]:focus-visible{color:#fff;background:var(--ntf-red)}.ntf-menu-sep{background:var(--ntf-separator);height:1px;margin:5px 10px}.ntf-inline{width:min(var(--ntf-width), 100%);position:relative}@media (prefers-reduced-motion:reduce){.ntf-root *,.ntf-root :before,.ntf-root :after{transition-duration:.01ms!important}}
package/package.json CHANGED
@@ -1,6 +1,46 @@
1
1
  {
2
2
  "name": "@ythknn/notify",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "macOS-style notifications for React and Next.js: banners that stack, swipe away, expand for actions and take inline replies, plus a Notification Center.",
5
+ "keywords": [
6
+ "notifications",
7
+ "toast",
8
+ "banner",
9
+ "macos",
10
+ "react",
11
+ "nextjs",
12
+ "notification-center"
13
+ ],
14
+ "homepage": "https://notify.vo1d.studio",
15
+ "author": "ythknn",
16
+ "license": "SEE LICENSE IN LICENSE",
17
+ "type": "module",
18
+ "main": "./dist/index.js",
19
+ "types": "./dist/index.d.ts",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "default": "./dist/index.js"
24
+ },
25
+ "./notify.css": "./dist/notify.css",
26
+ "./package.json": "./package.json"
27
+ },
28
+ "sideEffects": [
29
+ "*.css"
30
+ ],
31
+ "files": [
32
+ "dist"
33
+ ],
34
+ "peerDependencies": {
35
+ "motion": ">=12",
36
+ "react": ">=19",
37
+ "react-dom": ">=19"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "scripts": {
43
+ "build": "tsdown && node build-css.mjs",
44
+ "prepublishOnly": "npm run build"
45
+ }
46
+ }