@qumra/fanar 0.0.0 → 0.0.2
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 +62 -16
- package/dist/ai.d.ts +342 -0
- package/dist/ai.js +2 -0
- package/dist/chunk-3CTGRTYV.js +1 -0
- package/dist/chunk-APE2XWSN.js +2 -0
- package/dist/chunk-DDRJTIIP.js +2 -0
- package/dist/chunk-JJCKT5OO.js +2 -0
- package/dist/chunk-L3NDN5GB.js +2 -0
- package/dist/chunk-QJPINFN2.js +2 -0
- package/dist/editor.css +204 -0
- package/dist/editor.d.ts +24 -0
- package/dist/editor.js +2 -0
- package/dist/index.d.ts +88 -918
- package/dist/index.js +1 -8085
- package/dist/lib-DOXggTK6.d.ts +42 -0
- package/dist/lib.js +1 -92
- package/dist/notifications.d.ts +846 -0
- package/dist/notifications.js +2 -0
- package/dist/orb.d.ts +21 -0
- package/dist/orb.js +2 -0
- package/dist/tokens.css +36 -333
- package/dist/tokens.js +1 -20
- package/dist/tokens.json +64 -155
- package/package.json +64 -6
- package/dist/chunk-SFA2EQFK.js +0 -159
|
@@ -0,0 +1,846 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ComponentType, ReactNode } from 'react';
|
|
3
|
+
import { T as Tone } from './lib-DOXggTK6.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Schema version.
|
|
7
|
+
*
|
|
8
|
+
* Stamped on every message so the receiver knows how to read it. A message
|
|
9
|
+
* with no `v` is read as `1` — those were sent before the field existed.
|
|
10
|
+
*/
|
|
11
|
+
declare const NOTIFICATION_SCHEMA_VERSION = 1;
|
|
12
|
+
/**
|
|
13
|
+
* Notification priority — three levels, each with real behaviour.
|
|
14
|
+
*
|
|
15
|
+
* `low` arrives silently
|
|
16
|
+
* `normal` the default
|
|
17
|
+
* `critical` chimes 35% louder, is **not** cleared by "mark all read",
|
|
18
|
+
* and counts toward `needsAction`
|
|
19
|
+
*
|
|
20
|
+
* ## There used to be four
|
|
21
|
+
*
|
|
22
|
+
* `high` existed in the union type and nowhere else — not one line read it.
|
|
23
|
+
* Four levels with one that does nothing is worse than three that all work:
|
|
24
|
+
* whoever picks agonises over a distinction that isn't there, and whoever
|
|
25
|
+
* reads the code hunts for behaviour they will never find.
|
|
26
|
+
*
|
|
27
|
+
* A message still sending `high` is read as `normal` with a warning, not
|
|
28
|
+
* rejected.
|
|
29
|
+
*/
|
|
30
|
+
type NotificationPriority = 'low' | 'normal' | 'critical';
|
|
31
|
+
declare const NOTIFICATION_PRIORITIES: NotificationPriority[];
|
|
32
|
+
/**
|
|
33
|
+
* An action — a destination, not a callback.
|
|
34
|
+
*
|
|
35
|
+
* `href` is an internal path (`/orders/1052`) or a full URL. The library
|
|
36
|
+
* never navigates on its own: it calls `onAction` and you wire that to your
|
|
37
|
+
* router. That is what lets the same message work in a React Router app, a
|
|
38
|
+
* Next app, and a static page.
|
|
39
|
+
*/
|
|
40
|
+
interface NotificationAction {
|
|
41
|
+
label: string;
|
|
42
|
+
href: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The message on the wire — what the server sends and the database stores.
|
|
46
|
+
*
|
|
47
|
+
* Every optional field has a default in `parseNotification`, so the server
|
|
48
|
+
* sends the minimum and the rest is derived.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* {
|
|
52
|
+
* "v": 1,
|
|
53
|
+
* "id": "ntf_01HZY8K3",
|
|
54
|
+
* "kind": "order.created",
|
|
55
|
+
* "at": "2026-08-10T14:32:00.000Z",
|
|
56
|
+
* "title": "New order #1052",
|
|
57
|
+
* "body": "Paid in full, awaiting fulfilment.",
|
|
58
|
+
* "priority": "normal",
|
|
59
|
+
* "data": { "orderId": "1052", "total": 1480, "currency": "EGP" },
|
|
60
|
+
* "action": { "label": "Open the order", "href": "/orders/1052" }
|
|
61
|
+
* }
|
|
62
|
+
*/
|
|
63
|
+
interface NotificationJSON {
|
|
64
|
+
/** Schema version — absent reads as `1` */
|
|
65
|
+
v?: number;
|
|
66
|
+
/** Stable unique id. Deduplication keys off it, so it must come from the server */
|
|
67
|
+
id: string;
|
|
68
|
+
/** Event name, `domain.event` — `order.created`, `stock.depleted` */
|
|
69
|
+
kind: string;
|
|
70
|
+
/** ISO 8601 in UTC — `2026-08-10T14:32:00.000Z` */
|
|
71
|
+
at: string;
|
|
72
|
+
title: string;
|
|
73
|
+
body?: string;
|
|
74
|
+
/**
|
|
75
|
+
* When it was read, ISO — `null` or absent means unread.
|
|
76
|
+
*
|
|
77
|
+
* ## Why a timestamp and not a boolean
|
|
78
|
+
*
|
|
79
|
+
* `read: true` is one-way: there is no way to say "make it unread again",
|
|
80
|
+
* and if you tried, the next sync would flip it back — because
|
|
81
|
+
* "read wins" has no reference to compare against.
|
|
82
|
+
*
|
|
83
|
+
* A timestamp gives the same guarantee (newest wins) and three more things:
|
|
84
|
+
* "unread" becomes a newer event, an optimistic rollback can tell its own
|
|
85
|
+
* mark from another device's mark that landed in the same instant, and
|
|
86
|
+
* time-to-read falls out for free.
|
|
87
|
+
*/
|
|
88
|
+
readAt?: string | null;
|
|
89
|
+
/** @deprecated Use `readAt` — this is still read and converted, and will be removed */
|
|
90
|
+
read?: boolean;
|
|
91
|
+
priority?: NotificationPriority;
|
|
92
|
+
/**
|
|
93
|
+
* Grouping key — collapses **toasts only**.
|
|
94
|
+
*
|
|
95
|
+
* A new message replaces the previous one from the same group in the toast
|
|
96
|
+
* queue. Three toasts from the same source landing together is exactly the
|
|
97
|
+
* noise that makes a merchant turn notifications off.
|
|
98
|
+
*
|
|
99
|
+
* ## The inbox does not collapse
|
|
100
|
+
*
|
|
101
|
+
* All three stay as separate rows. A toast interrupts, so collapsing it is
|
|
102
|
+
* a kindness; a row is read at leisure, so collapsing it hides information.
|
|
103
|
+
*
|
|
104
|
+
* Durable collapsing ("3 new messages" as one row) is the server's job — it
|
|
105
|
+
* is the only side that sees the whole time window. Client-side collapsing
|
|
106
|
+
* only sees what reached this tab. Toasts don't have that problem because
|
|
107
|
+
* they are per-tab by nature, live for seconds, and nobody counts them.
|
|
108
|
+
*/
|
|
109
|
+
group?: string;
|
|
110
|
+
/** Event payload — the library never reads it, it hands it back to you */
|
|
111
|
+
data?: Record<string, unknown>;
|
|
112
|
+
action?: NotificationAction;
|
|
113
|
+
/**
|
|
114
|
+
* Silence one message — `'none'` and nothing else.
|
|
115
|
+
*
|
|
116
|
+
* Sound normally comes from the kind registry. This is for the exception:
|
|
117
|
+
* the same kind arriving silently during a bulk import, say. There is no
|
|
118
|
+
* other value — the chime itself is not swappable per message.
|
|
119
|
+
*/
|
|
120
|
+
sound?: 'none';
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* The message after validation — this is what components work with.
|
|
124
|
+
*
|
|
125
|
+
* The difference from `NotificationJSON`: dates are `Date`, and optionals are
|
|
126
|
+
* filled with their defaults. So no component ever writes `?? 'normal'` or
|
|
127
|
+
* `new Date(...)` again — normalisation happens once, at the boundary.
|
|
128
|
+
*/
|
|
129
|
+
interface Notification {
|
|
130
|
+
v: number;
|
|
131
|
+
id: string;
|
|
132
|
+
kind: string;
|
|
133
|
+
at: Date;
|
|
134
|
+
title: string;
|
|
135
|
+
body?: string;
|
|
136
|
+
/** When it was read — `null` means unread */
|
|
137
|
+
readAt: Date | null;
|
|
138
|
+
/** Shorthand for `readAt !== null` — derived, never stored */
|
|
139
|
+
read: boolean;
|
|
140
|
+
priority: NotificationPriority;
|
|
141
|
+
group?: string;
|
|
142
|
+
data: Record<string, unknown>;
|
|
143
|
+
action?: NotificationAction;
|
|
144
|
+
sound?: 'none';
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* The translate function — yours, not the library's.
|
|
148
|
+
*
|
|
149
|
+
* ## Why the library does not translate
|
|
150
|
+
*
|
|
151
|
+
* The first implementation was `locale.startsWith('ar') ? '…' : '…'` inside
|
|
152
|
+
* the registry. That works for two kinds and falls apart at the twelfth:
|
|
153
|
+
*
|
|
154
|
+
* - Strings end up buried in code, where no translator can reach them
|
|
155
|
+
* - Arabic has a dual as well as a plural, and each inflects the noun
|
|
156
|
+
* differently (talab · talabaan · thalaath talabaat, for one, two and
|
|
157
|
+
* three orders). No `startsWith` check can produce that
|
|
158
|
+
* - And if the project already has an i18n system, this builds a second
|
|
159
|
+
* one beside it
|
|
160
|
+
*
|
|
161
|
+
* So the library owns the **hook point**, not the translation: it calls `t`
|
|
162
|
+
* with a key and values, and `t` comes from `NotificationProvider`. Strings
|
|
163
|
+
* stay in your catalogue, pluralisation stays with the system built for it,
|
|
164
|
+
* and the library stays unaware of language — exactly as it stays unaware of
|
|
165
|
+
* what `order.created` means.
|
|
166
|
+
*/
|
|
167
|
+
type NotificationTranslate = (key: string, vars?: Record<string, unknown>) => ReactNode;
|
|
168
|
+
/**
|
|
169
|
+
* A kind's sound — one chime, or silence.
|
|
170
|
+
*
|
|
171
|
+
* `notify` is the only notification chime. It is inlined as a data URI in
|
|
172
|
+
* `chime.ts`, so there is no file to copy and no path to break when the
|
|
173
|
+
* library moves to another project. `none` silences the whole kind — for
|
|
174
|
+
* things worth reading but not worth interrupting for.
|
|
175
|
+
*
|
|
176
|
+
* The chime itself is **not swappable**: it is part of Qumra's identity, like
|
|
177
|
+
* the colours and the typeface. What a project decides is which kinds ring
|
|
178
|
+
* and which arrive quietly.
|
|
179
|
+
*/
|
|
180
|
+
type NotificationSoundName = 'notify' | 'none';
|
|
181
|
+
/**
|
|
182
|
+
* A notification kind — the mapping from an event name to its look and sound.
|
|
183
|
+
*
|
|
184
|
+
* This is what makes the system portable: the library does not know what
|
|
185
|
+
* `order.created` means, it knows how to read the registry. Each project
|
|
186
|
+
* registers its own kinds.
|
|
187
|
+
*/
|
|
188
|
+
interface NotificationKind {
|
|
189
|
+
/** The event name exactly as the server sends it — `order.created` */
|
|
190
|
+
kind: string;
|
|
191
|
+
/** Human-readable name — shown in notification settings and filters */
|
|
192
|
+
label: string;
|
|
193
|
+
icon: ComponentType<{
|
|
194
|
+
size?: number;
|
|
195
|
+
strokeWidth?: number;
|
|
196
|
+
}>;
|
|
197
|
+
/**
|
|
198
|
+
* Status tone — neutral by default, on purpose.
|
|
199
|
+
*
|
|
200
|
+
* This says "this one is worrying", not "this one is from the orders
|
|
201
|
+
* module". If every kind took a colour, the inbox becomes a rainbow and the
|
|
202
|
+
* urgent item is lost in it.
|
|
203
|
+
*/
|
|
204
|
+
tone?: Tone;
|
|
205
|
+
/** The kind's priority, used when the message does not set one */
|
|
206
|
+
priority?: NotificationPriority;
|
|
207
|
+
/**
|
|
208
|
+
* Whether this kind rings — `notify` by default.
|
|
209
|
+
*
|
|
210
|
+
* This is the only decision sound exposes, and it is a product decision,
|
|
211
|
+
* not an identity one: "app updated" is not worth interrupting for,
|
|
212
|
+
* "payment failed" is. The chime, its loudness, and its rhythm are fixed in
|
|
213
|
+
* the library.
|
|
214
|
+
*/
|
|
215
|
+
sound?: NotificationSoundName;
|
|
216
|
+
/**
|
|
217
|
+
* Build the title from `data` instead of the server's text — **optional**.
|
|
218
|
+
*
|
|
219
|
+
* ## The problem it solves
|
|
220
|
+
*
|
|
221
|
+
* `title` is stored in whatever language was active when it was sent. The
|
|
222
|
+
* merchant switches language and finds last month's notifications still in
|
|
223
|
+
* the old one. This happens in Qumra for real — the apps ship in Arabic and
|
|
224
|
+
* English.
|
|
225
|
+
*
|
|
226
|
+
* ## Why it is opt-in and not the default
|
|
227
|
+
*
|
|
228
|
+
* If every kind rendered on the client, fixing a wording or a typo would
|
|
229
|
+
* need a **frontend deploy**. As it stands that is a server-side edit that
|
|
230
|
+
* lands immediately. So a kind with a template translates, a kind without
|
|
231
|
+
* takes the server's text — and you adopt it kind by kind, where it earns
|
|
232
|
+
* its keep.
|
|
233
|
+
*
|
|
234
|
+
* An unregistered kind keeps working off the server's text, so the backend
|
|
235
|
+
* can ship a new kind without waiting for the frontend.
|
|
236
|
+
*
|
|
237
|
+
* ## The template calls `t`, it does not translate
|
|
238
|
+
*
|
|
239
|
+
* Key and values only. The string lives in your catalogue, and plurals and
|
|
240
|
+
* inflection are your i18n system's job.
|
|
241
|
+
*
|
|
242
|
+
* If the project never passes `t` to the provider, the template is
|
|
243
|
+
* **ignored** and the server's text is shown — safer than a raw key in
|
|
244
|
+
* front of a merchant.
|
|
245
|
+
*
|
|
246
|
+
* @example
|
|
247
|
+
* {
|
|
248
|
+
* kind: 'order.created',
|
|
249
|
+
* title: (n, t) => t('notif.order.created', { id: n.data.orderId }),
|
|
250
|
+
* }
|
|
251
|
+
*/
|
|
252
|
+
title?: (n: Notification, t: NotificationTranslate) => ReactNode;
|
|
253
|
+
/** Same idea as `title`, for the line underneath */
|
|
254
|
+
body?: (n: Notification, t: NotificationTranslate) => ReactNode;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* A kind after resolution — look and sound guaranteed, templates not.
|
|
258
|
+
*
|
|
259
|
+
* A missing `title`/`body` is **meaning**, not absence: it says "use the
|
|
260
|
+
* server's text". So no default is filled in for them — one would overwrite
|
|
261
|
+
* text that came from the server with an empty string.
|
|
262
|
+
*/
|
|
263
|
+
interface ResolvedKind extends Required<Omit<NotificationKind, 'title' | 'body'>> {
|
|
264
|
+
title?: NotificationKind['title'];
|
|
265
|
+
body?: NotificationKind['body'];
|
|
266
|
+
/** This kind is not registered, and these values come from the fallback */
|
|
267
|
+
unknown: boolean;
|
|
268
|
+
}
|
|
269
|
+
interface NotificationKindRegistry {
|
|
270
|
+
/** Returns the kind, or the fallback with `unknown: true` if unregistered */
|
|
271
|
+
get: (kind: string) => ResolvedKind;
|
|
272
|
+
/** Every registered kind — for a notification settings screen */
|
|
273
|
+
list: () => ResolvedKind[];
|
|
274
|
+
has: (kind: string) => boolean;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
type NotificationParseResult = {
|
|
278
|
+
ok: true;
|
|
279
|
+
value: Notification;
|
|
280
|
+
warnings: string[];
|
|
281
|
+
} | {
|
|
282
|
+
ok: false;
|
|
283
|
+
issues: string[];
|
|
284
|
+
};
|
|
285
|
+
interface NotificationBatch {
|
|
286
|
+
items: Notification[];
|
|
287
|
+
/** اللي اتعزل — الرسالة الخام وسبب الرفض، عشان يتسجّل مش يتبلع */
|
|
288
|
+
rejected: {
|
|
289
|
+
raw: unknown;
|
|
290
|
+
issues: string[];
|
|
291
|
+
}[];
|
|
292
|
+
/** تحذيرات على رسايل عدّت — حقل اتجاهل، إصدار أحدث */
|
|
293
|
+
warnings: string[];
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* بيحقّق رسالة واحدة ويطبّعها.
|
|
297
|
+
*
|
|
298
|
+
* المطلوب: `id` و`kind` و`at` و`title`. الباقي بياخد افتراضي.
|
|
299
|
+
* `at` لازم يكون تاريخ ISO مقروء — الرقم أو النص المكسور بيترفض، لأن
|
|
300
|
+
* `Invalid Date` بيعدّي صامت وبيطلع «NaN من كذا دقيقة» في الواجهة.
|
|
301
|
+
*/
|
|
302
|
+
interface ParseOptions {
|
|
303
|
+
/**
|
|
304
|
+
* سجلّ الأنواع — منه بييجي افتراضي الأولوية لما الرسالة ما تحدّدش.
|
|
305
|
+
*
|
|
306
|
+
* من غيره الرسالة اللي مافيهاش `priority` بتاخد `normal` مهما كان
|
|
307
|
+
* نوعها — يعني `payment.failed` المسجّل `critical` بيوصل عادي،
|
|
308
|
+
* والسجلّ بيبقى زينة. وده بيكسر أساس النظام: المكتبة مابتعرفش
|
|
309
|
+
* `order.created` يعني إيه، بتعرف تقرا السجلّ.
|
|
310
|
+
*/
|
|
311
|
+
kinds?: NotificationKindRegistry;
|
|
312
|
+
}
|
|
313
|
+
declare function parseNotification(raw: unknown, options?: ParseOptions): NotificationParseResult;
|
|
314
|
+
/**
|
|
315
|
+
* بيحقّق دفعة ويعزل الوحش.
|
|
316
|
+
*
|
|
317
|
+
* بيقبل مصفوفة، أو نص JSON، أو كائن فيه `items`/`notifications` —
|
|
318
|
+
* التلات أشكال اللي الـAPIs بتبعت بيهم. وبيرتّب بالأحدث، وبيلغّي
|
|
319
|
+
* تكرار الـ`id` (آخر نسخة بتكسب: التحديث بيوصل بنفس المعرّف).
|
|
320
|
+
*/
|
|
321
|
+
declare function parseNotifications(raw: unknown, options?: ParseOptions): NotificationBatch;
|
|
322
|
+
/**
|
|
323
|
+
* بيرجّع الرسالة لصيغتها على السلك — للتخزين أو إعادة الإرسال.
|
|
324
|
+
*
|
|
325
|
+
* عكس `parseNotification` بالظبط: `parse(serialize(n))` بيدّي `n`.
|
|
326
|
+
* لازم يفضل كده، وإلا الرسالة اللي اتخزّنت مابترجعش زي ما دخلت.
|
|
327
|
+
*/
|
|
328
|
+
declare function serializeNotification(n: Notification): NotificationJSON;
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* بينشئ سجلّ أنواع.
|
|
332
|
+
*
|
|
333
|
+
* @param kinds الأنواع المسجّلة
|
|
334
|
+
* @param fallback تجاوز الشكل الاحتياطي للنوع المش معروف
|
|
335
|
+
*
|
|
336
|
+
* @example
|
|
337
|
+
* const KINDS = createKindRegistry([
|
|
338
|
+
* { kind: 'ticket.assigned', label: 'تذكرة', icon: Ticket, priority: 'critical' },
|
|
339
|
+
* ])
|
|
340
|
+
* KINDS.get('ticket.assigned').icon // Ticket
|
|
341
|
+
* KINDS.get('حاجة.غريبة').unknown // true
|
|
342
|
+
*/
|
|
343
|
+
declare function createKindRegistry(kinds: NotificationKind[], fallback?: Partial<Omit<ResolvedKind, 'kind' | 'unknown'>>): NotificationKindRegistry;
|
|
344
|
+
/**
|
|
345
|
+
* أنواع التجارة الإلكترونية — طقم جاهز لمنتجات قمرة.
|
|
346
|
+
*
|
|
347
|
+
* ده **مثال مسجّل** مش جزء من العقد. المشروع التاني بيبني سجلّه
|
|
348
|
+
* بـ`createKindRegistry` ومابياخدش الطقم ده.
|
|
349
|
+
*
|
|
350
|
+
* ── واللون هنا استثناء مش قاعدة ─────────────────────────────────────────
|
|
351
|
+
* أربعة بس من اتناشر ليهم نغمة: فشل الدفع والمخزون الخالص والطلب
|
|
352
|
+
* المتعثّر والمرتجع. الباقي محايد. لو كل نوع خد لونه، مافيش حاجة
|
|
353
|
+
* بتتشاف قبل حاجة — واللون بيبطّل يعني حالة.
|
|
354
|
+
*
|
|
355
|
+
* ── و`critical` تلاتة بس ────────────────────────────────────────────────
|
|
356
|
+
* القاعدة: نزيف **مستمرّ لحد ما حد يتصرّف**. فشل التحصيل والمخزون
|
|
357
|
+
* الخالص والطلب المتعثّر بيكلّفوا كل يوم بيعدّي؛ المرتجع حصل وخلاص.
|
|
358
|
+
* والفرق مش أكاديمي: `critical` مابتتمسحش بـ«تعليم الكل»، فلو اتوسّعت
|
|
359
|
+
* الحماية بتبقى ضجيج مالوش مفتاح إطفاء.
|
|
360
|
+
*
|
|
361
|
+
* ── والقوالب على اتنين بس ───────────────────────────────────────────────
|
|
362
|
+
* `order.created` و`payout.sent` عندهم قالب بينادي `t` بمفتاح وقيم،
|
|
363
|
+
* فبيترجموا من كتالوج المشروع. الباقي بياخد نصّ السيرفر — عشان تصليح
|
|
364
|
+
* صياغة يفضل تعديل سيرفر بيوصل فورًا مش نشر فرونت.
|
|
365
|
+
*
|
|
366
|
+
* ومن غير `t` على المزوّد، القوالب بتتجاهل والكل بياخد نصّ السيرفر.
|
|
367
|
+
*
|
|
368
|
+
* ── والصوت واحد أو ولا حاجة ─────────────────────────────────────────────
|
|
369
|
+
* النغمة نفسها لكل نوع بيستاهل مقاطعة، و`none` للي بيتقرا ومابيستاهلش:
|
|
370
|
+
* تحديث تطبيق وملخّص المجتمع. نغمات متعدّدة بتفترض إن التاجر هيحفظ
|
|
371
|
+
* إن النازلة يعني دفع فشل — وده مابيحصلش، بيسمع رنّة فبيبصّ.
|
|
372
|
+
* والإلحاح بيتقال بالعلوّ: `critical` بتعلى ٣٥٪.
|
|
373
|
+
*/
|
|
374
|
+
declare const QUMRA_KINDS: NotificationKind[];
|
|
375
|
+
/** سجلّ قمرة جاهز — `QUMRA_NOTIFICATIONS.get('order.created')` */
|
|
376
|
+
declare const QUMRA_NOTIFICATIONS: NotificationKindRegistry;
|
|
377
|
+
|
|
378
|
+
/** الصوت الوحيد في المكتبة — `none` بتسكّت النوع */
|
|
379
|
+
declare const NOTIFICATION_SOUNDS: Exclude<NotificationSoundName, 'none'>[];
|
|
380
|
+
interface UseNotificationSoundOptions {
|
|
381
|
+
/** سجلّ الأنواع — منه بييجي صوت كل نوع (`notify` أو `none`) */
|
|
382
|
+
kinds: NotificationKindRegistry;
|
|
383
|
+
}
|
|
384
|
+
interface NotificationSoundApi {
|
|
385
|
+
/** الصوت اتفكّ وجاهز — قبلها أي `play` بتتبلع */
|
|
386
|
+
ready: boolean;
|
|
387
|
+
muted: boolean;
|
|
388
|
+
setMuted: (muted: boolean) => void;
|
|
389
|
+
/**
|
|
390
|
+
* بيفكّ الصوت — **لازم من معالج حدث مستخدم حقيقي**.
|
|
391
|
+
*
|
|
392
|
+
* بتتنادى لوحدها على أول ضغطة لو `autoUnlock`. النداء اليدوي لزرار
|
|
393
|
+
* «شغّل الصوت» في الإعدادات.
|
|
394
|
+
*/
|
|
395
|
+
unlock: () => Promise<boolean>;
|
|
396
|
+
/** بيرنّ حسب النوع — بيحترم الكتم والحدّ الأدنى والأولوية */
|
|
397
|
+
play: (n: Notification) => void;
|
|
398
|
+
/** بيرنّ حالاً — لزرار «جرّب الصوت» في الإعدادات */
|
|
399
|
+
preview: () => void;
|
|
400
|
+
}
|
|
401
|
+
declare function useNotificationSound({ kinds }: UseNotificationSoundOptions): NotificationSoundApi;
|
|
402
|
+
|
|
403
|
+
interface NotificationsApi {
|
|
404
|
+
/** كل الرسايل، الأحدث الأول */
|
|
405
|
+
items: Notification[];
|
|
406
|
+
/** عدد غير المقروء — ده اللي الجرس بيعرضه */
|
|
407
|
+
unread: number;
|
|
408
|
+
/**
|
|
409
|
+
* غير المقروء اللي «تعليم الكل» مش بيمسحه — الحرج.
|
|
410
|
+
*
|
|
411
|
+
* مفصول عن `unread` عن قصد: بعد «تعليم الكل» العدّاد بينزل لكن
|
|
412
|
+
* مش لصفر، وده بيتقرا عطل لو مافيش رقم تاني بيفسّره. والفصل بيسمح
|
|
413
|
+
* بوزن بصري مختلف — نقطة حمرا مابتروحش أبداً بترجّع نفس الضغط
|
|
414
|
+
* اللي بيخلّي التاجر يطفّي الإشعارات.
|
|
415
|
+
*/
|
|
416
|
+
needsAction: number;
|
|
417
|
+
/** الوارد لسه — اللي الكروت الطايرة بتعرضه */
|
|
418
|
+
arrivals: Notification[];
|
|
419
|
+
/** المرفوض من آخر `hydrate`/`receive` — للتسجيل مش للعرض */
|
|
420
|
+
rejected: NotificationBatch['rejected'];
|
|
421
|
+
/**
|
|
422
|
+
* بيحمّل الموجود من قبل — **صامت وإضافي**.
|
|
423
|
+
*
|
|
424
|
+
* بيضيف ويحدّث ومابيشيلش. ده نداء أول تحميل، وتحميل صفحة تانية،
|
|
425
|
+
* وأي دفعة قديمة.
|
|
426
|
+
*/
|
|
427
|
+
hydrate: (raw: unknown) => NotificationBatch;
|
|
428
|
+
/**
|
|
429
|
+
* مزامنة — **صامتة وبتشيل**.
|
|
430
|
+
*
|
|
431
|
+
* زي `hydrate` بس بتشيل كمان اللي السيرفر مابعتوش، **جوّه المدى
|
|
432
|
+
* الزمني اللي الدفعة بتغطّيه وبس**. فمزامنة الصفحة الأولى مابتمسحش
|
|
433
|
+
* الصفحات اللي تحتها.
|
|
434
|
+
*/
|
|
435
|
+
sync: (raw: unknown) => NotificationBatch;
|
|
436
|
+
/**
|
|
437
|
+
* بيستقبل وارد جديد — بيرنّ وبيطلع طاير.
|
|
438
|
+
*
|
|
439
|
+
* بياخد رسالة واحدة أو دفعة. الرسالة المرفوضة بتتعزل ومابتوقّعش
|
|
440
|
+
* الباقي.
|
|
441
|
+
*/
|
|
442
|
+
receive: (raw: unknown) => NotificationBatch;
|
|
443
|
+
/**
|
|
444
|
+
* إشعار محلّي من غير سيرفر — بيملى `id` و`at` و`v` لوحده.
|
|
445
|
+
*
|
|
446
|
+
* للأفعال اللي التطبيق بيعرف نتيجتها فوراً. بيعدّي على نفس
|
|
447
|
+
* التحقّق، فالمحلّي مابياخدش طريق جانبي.
|
|
448
|
+
*/
|
|
449
|
+
push: (input: Omit<NotificationJSON, 'id' | 'at' | 'v'> & {
|
|
450
|
+
id?: string;
|
|
451
|
+
at?: string;
|
|
452
|
+
}) => string | null;
|
|
453
|
+
markRead: (id: string) => void;
|
|
454
|
+
markAllRead: () => void;
|
|
455
|
+
/**
|
|
456
|
+
* رجّعهم غير مقروئين — للتراجع، ولزرار «علّمه كغير مقروء».
|
|
457
|
+
*
|
|
458
|
+
* فعل باسمه زي باقي الـAPI (`dismiss` · `markAllRead` · `sync`)
|
|
459
|
+
* بدل `setRead(ids, false)`: العلَم البوليان بيخلّي مكان النداء
|
|
460
|
+
* يحتاج قراية التوقيع عشان يتفهم.
|
|
461
|
+
*/
|
|
462
|
+
markUnread: (ids: string[]) => void;
|
|
463
|
+
/** بيلغي شاهد الشيل وبيرجّع الرسالة — للتراجع لما السيرفر يرفض */
|
|
464
|
+
restore: (n: Notification) => void;
|
|
465
|
+
/**
|
|
466
|
+
* السيرفر أكّد الشيل — الشاهد مابقاش لازم.
|
|
467
|
+
*
|
|
468
|
+
* من غيرها الشاهد بيستنّى انتهاء صلاحيته على الفاضي، والمجموعة
|
|
469
|
+
* بتكبر مع كل شيل في الجلسة.
|
|
470
|
+
*/
|
|
471
|
+
confirmDismiss: (id: string) => void;
|
|
472
|
+
dismiss: (id: string) => void;
|
|
473
|
+
clearArrival: (id: string) => void;
|
|
474
|
+
clearAllArrivals: () => void;
|
|
475
|
+
}
|
|
476
|
+
interface UseNotificationsOptions {
|
|
477
|
+
/**
|
|
478
|
+
* سجلّ الأنواع — منه بييجي افتراضي الأولوية.
|
|
479
|
+
*
|
|
480
|
+
* من غيره أي رسالة مافيهاش `priority` بتاخد `normal`، فالنوع
|
|
481
|
+
* المسجّل `critical` بيوصل عادي و`needsAction` بتعدّ غلط.
|
|
482
|
+
*/
|
|
483
|
+
kinds?: NotificationKindRegistry;
|
|
484
|
+
/** رسايل أولية — بتتحمّل صامتة زي `hydrate` */
|
|
485
|
+
initial?: unknown;
|
|
486
|
+
/** بيتنادى لكل وارد جديد اتقبل — ده مكان الصوت */
|
|
487
|
+
onReceive?: (n: Notification) => void;
|
|
488
|
+
/** بيتنادى لما رسالة تترفض — سجّلها، متبلعهاش */
|
|
489
|
+
onReject?: (rejected: NotificationBatch['rejected']) => void;
|
|
490
|
+
}
|
|
491
|
+
/** `critical` مابيتعلّمش مقروء بـ«تعليم الكل» — «شفته» مش «عالجته» */
|
|
492
|
+
declare const isBulkReadable: (n: Notification) => boolean;
|
|
493
|
+
declare function useNotifications({ kinds, initial, onReceive, onReject, }?: UseNotificationsOptions): NotificationsApi;
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* تحديث تفاؤلي بتراجع.
|
|
497
|
+
*
|
|
498
|
+
* الواجهة بتتغيّر فوراً، والمزامنة بتجري. لو فشلت، `undo` بترجّع
|
|
499
|
+
* الحالة و`onError` بيقول للمستخدم — لأن التراجع الصامت أوحش من
|
|
500
|
+
* الفشل نفسه: العدّاد بيرجع يطلع والمستخدم مايعرفش ليه.
|
|
501
|
+
*
|
|
502
|
+
* `run` ممكن يرجّع `void` أو وعد، وممكن يرمي متزامن. التلات حالات
|
|
503
|
+
* بتتعامل واحد — المستدعي اللي مابيزامنش مابيدفعش تمن، واللي
|
|
504
|
+
* بيزامن مابيحتاجش يلفّ نداءه في `try`.
|
|
505
|
+
*/
|
|
506
|
+
declare function optimistic(run: (() => void | Promise<void>) | undefined, undo: () => void, onError?: (error: unknown) => void): void;
|
|
507
|
+
interface UseNotificationCenterOptions extends UseNotificationsOptions {
|
|
508
|
+
/** سجلّ الأنواع — بيروح للصوت وبيترجّع عشان يتحطّ في المزوّد */
|
|
509
|
+
kinds: NotificationKindRegistry;
|
|
510
|
+
/**
|
|
511
|
+
* اتقرا إشعار — بالفتح أو بـ«تعليم كمقروء».
|
|
512
|
+
*
|
|
513
|
+
* ده مكان المزامنة مع السيرفر. العلامة بتتحطّ **قبل** النداء
|
|
514
|
+
* (تفاؤلياً) عشان الواجهة ما تستناش الشبكة.
|
|
515
|
+
*
|
|
516
|
+
* ── ولو السيرفر رفض ─────────────────────────────────────────────────
|
|
517
|
+
* ارمي أو رجّع وعداً بيترفض، والمكتبة بترجّع العلامة لوحدها. من
|
|
518
|
+
* غير كده الواجهة بتوعد بحاجة السيرفر مانفّذهاش: التاجر بيشوف
|
|
519
|
+
* العدّاد وقع، وبيفتح من جهاز تاني فيلاقيه زي ما هو.
|
|
520
|
+
*
|
|
521
|
+
* @example
|
|
522
|
+
* onRead: async (n) => { await api.markRead(n.id) } // الفشل بيرجّع العلامة
|
|
523
|
+
*/
|
|
524
|
+
onRead?: (n: Notification) => void | Promise<void>;
|
|
525
|
+
/** اتقرا الكل — نداء واحد بدل نداء لكل رسالة. الفشل بيرجّع الكل */
|
|
526
|
+
onReadAll?: (ids: string[]) => void | Promise<void>;
|
|
527
|
+
/** اتشال من الصندوق. الفشل بيرجّع الرسالة لمكانها */
|
|
528
|
+
onDismiss?: (n: Notification) => void | Promise<void>;
|
|
529
|
+
/**
|
|
530
|
+
* فشلت مزامنة مع السيرفر واتراجعنا — اعرض توست هنا.
|
|
531
|
+
*
|
|
532
|
+
* التراجع الصامت أوحش من الفشل نفسه: العدّاد بيرجع يطلع والمستخدم
|
|
533
|
+
* مايعرفش ليه.
|
|
534
|
+
*
|
|
535
|
+
* و`items` جوّه السياق عشان الرسالة تبقى مفيدة: «تعذّر حفظ قراية»
|
|
536
|
+
* بتسأل «قراية إيه؟»، و«تعذّر حفظ قراية ٣ إشعارات» بترد.
|
|
537
|
+
*/
|
|
538
|
+
onSyncError?: (error: unknown, context: {
|
|
539
|
+
action: 'read' | 'readAll' | 'dismiss';
|
|
540
|
+
items: Notification[];
|
|
541
|
+
}) => void;
|
|
542
|
+
/**
|
|
543
|
+
* اتضغط إجراء الإشعار — الوجهة في `n.action.href`.
|
|
544
|
+
*
|
|
545
|
+
* وصّلها براوترك. الإشعار بيتعلّم مقروء لوحده قبل النداء.
|
|
546
|
+
*/
|
|
547
|
+
onOpen?: (n: Notification) => void;
|
|
548
|
+
}
|
|
549
|
+
interface NotificationCenter extends NotificationsApi {
|
|
550
|
+
sound: NotificationSoundApi;
|
|
551
|
+
/** نفس السجلّ اللي دخل — عشان `NotificationProvider` ياخده من مصدر واحد */
|
|
552
|
+
kinds: NotificationKindRegistry;
|
|
553
|
+
/**
|
|
554
|
+
* الخصائص الجاهزة للمزوّد والأسطح — بتوصّل الفتح والقراءة لوحدها.
|
|
555
|
+
*
|
|
556
|
+
* `<NotificationProvider {...inbox.bind.provider}>` بدل ما تكتب
|
|
557
|
+
* `onAction` و`onOpen` في كل سطح.
|
|
558
|
+
*/
|
|
559
|
+
bind: {
|
|
560
|
+
provider: {
|
|
561
|
+
kinds: NotificationKindRegistry;
|
|
562
|
+
onAction: (n: Notification) => void;
|
|
563
|
+
};
|
|
564
|
+
surface: {
|
|
565
|
+
items: Notification[];
|
|
566
|
+
unread: number;
|
|
567
|
+
needsAction: number;
|
|
568
|
+
sound: NotificationSoundApi;
|
|
569
|
+
onMarkAllRead: () => void;
|
|
570
|
+
onDismiss: (id: string) => void;
|
|
571
|
+
onOpen: (n: Notification) => void;
|
|
572
|
+
};
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* الحالة والصوت موصولين — نداء واحد.
|
|
577
|
+
*
|
|
578
|
+
* @example
|
|
579
|
+
* const inbox = useNotificationCenter({
|
|
580
|
+
* kinds: QUMRA_NOTIFICATIONS,
|
|
581
|
+
* initial: await api.notifications(), // بيتحمّل صامت
|
|
582
|
+
* })
|
|
583
|
+
*
|
|
584
|
+
* <NotificationProvider kinds={inbox.kinds} onAction={(n) => navigate(n.action.href)}>
|
|
585
|
+
* <NotificationMenu unread={inbox.unread} items={inbox.items} sound={inbox.sound} … />
|
|
586
|
+
* <NotificationToastHost items={inbox.arrivals} onClose={inbox.clearArrival} />
|
|
587
|
+
* </NotificationProvider>
|
|
588
|
+
*
|
|
589
|
+
* // والوارد من السوكت — بيرنّ وبيطلع طاير
|
|
590
|
+
* socket.on('notification', inbox.receive)
|
|
591
|
+
*/
|
|
592
|
+
declare function useNotificationCenter({ kinds, onReceive, onRead, onReadAll, onDismiss, onOpen, onSyncError, ...rest }: UseNotificationCenterOptions): NotificationCenter;
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* الوقت النسبي — «من دقيقتين»، «إمبارح»، «من ٣ شهور».
|
|
596
|
+
*
|
|
597
|
+
* أقل من نصّ دقيقة بيتقري «الآن» بدل «قبل ٣٠ ثانية»: الفرق مالوش
|
|
598
|
+
* معنى للقارئ، والرقم اللي بيتغيّر كل ثانية بيسحب العين. والنصّ من
|
|
599
|
+
* `Intl` — فبيشتغل على أي لغة من غير ما المكتبة تعرفها.
|
|
600
|
+
*
|
|
601
|
+
* @param at وقت الإشعار
|
|
602
|
+
* @param locale اللغة — الافتراضي `ar`
|
|
603
|
+
* @param now لحقن الوقت في الاختبار
|
|
604
|
+
*/
|
|
605
|
+
declare function formatRelativeTime(at: Date, locale?: string, now?: Date): string;
|
|
606
|
+
/**
|
|
607
|
+
* كل كام يجب إعادة الرسم عشان النصّ يفضل صح.
|
|
608
|
+
*
|
|
609
|
+
* «من دقيقة» بيبقى غلط بعد دقيقة، و«من ٣ شهور» بيفضل صح لأسبوع.
|
|
610
|
+
* المؤقّت بيتظبط على دقّة الوحدة بدل ما يشتغل كل ثانية على قايمة
|
|
611
|
+
* فيها أربعين سطر.
|
|
612
|
+
*/
|
|
613
|
+
declare function relativeTickMs(at: Date, now?: Date): number;
|
|
614
|
+
|
|
615
|
+
interface NotificationContext {
|
|
616
|
+
kinds: NotificationKindRegistry;
|
|
617
|
+
locale: string;
|
|
618
|
+
t?: NotificationTranslate;
|
|
619
|
+
onAction?: (n: Notification) => void;
|
|
620
|
+
}
|
|
621
|
+
interface NotificationProviderProps {
|
|
622
|
+
/** The kind registry — Qumra's registry by default */
|
|
623
|
+
kinds?: NotificationKindRegistry;
|
|
624
|
+
/** Locale for relative time — `ar` by default */
|
|
625
|
+
locale?: string;
|
|
626
|
+
/**
|
|
627
|
+
* The translate function from your own i18n system — for kind title templates.
|
|
628
|
+
*
|
|
629
|
+
* Without it templates are ignored and the server's text is shown. The
|
|
630
|
+
* library does not translate on its own: it calls `t(key, vars)` and
|
|
631
|
+
* nothing more, so strings stay in your catalogue and pluralisation stays
|
|
632
|
+
* your system's job.
|
|
633
|
+
*/
|
|
634
|
+
t?: NotificationTranslate;
|
|
635
|
+
/**
|
|
636
|
+
* Navigation to a notification's destination.
|
|
637
|
+
*
|
|
638
|
+
* The library does not navigate on its own, because a message carries an
|
|
639
|
+
* `href` and not a callback. Take it from `n.action.href` and hand it to
|
|
640
|
+
* your router.
|
|
641
|
+
*/
|
|
642
|
+
onAction?: (n: Notification) => void;
|
|
643
|
+
children: ReactNode;
|
|
644
|
+
}
|
|
645
|
+
/** Distributes registry, locale and navigation to every surface — once, at the root */
|
|
646
|
+
declare function NotificationProvider({ kinds, locale, t, onAction, children, }: NotificationProviderProps): react.JSX.Element;
|
|
647
|
+
declare const useNotificationContext: () => NotificationContext;
|
|
648
|
+
interface NotificationBellProps {
|
|
649
|
+
unread: number;
|
|
650
|
+
/**
|
|
651
|
+
* How many of them need action — this changes the badge's **shape**, not its
|
|
652
|
+
* number.
|
|
653
|
+
*
|
|
654
|
+
* A red dot that never goes away invites exactly the press that makes a
|
|
655
|
+
* merchant turn notifications off. So the badge stays the unread count, and
|
|
656
|
+
* critical items are marked with a ring around it — a second signal on the
|
|
657
|
+
* same number, not a second number.
|
|
658
|
+
*/
|
|
659
|
+
needsAction?: number;
|
|
660
|
+
onClick: () => void;
|
|
661
|
+
/** Reaches `aria-expanded` — a bell that opens a panel has to say so */
|
|
662
|
+
expanded?: boolean;
|
|
663
|
+
label?: string;
|
|
664
|
+
/** "3 unread" — the number belongs in `aria-label`, not only in the shape */
|
|
665
|
+
unreadLabel?: (n: number) => string;
|
|
666
|
+
className?: string;
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* The bell button with its unread badge.
|
|
670
|
+
*
|
|
671
|
+
* The badge is red and ringed in the background colour — without the ring the
|
|
672
|
+
* number collides with whatever icon is behind it and half of it becomes
|
|
673
|
+
* unreadable. The number is in `aria-label` too: a screen reader never sees
|
|
674
|
+
* the badge, and a bell that only says "notifications" makes a listener open
|
|
675
|
+
* the inbox every time just to find out whether anything is there.
|
|
676
|
+
*/
|
|
677
|
+
declare function NotificationBell({ unread, needsAction, onClick, expanded, label, unreadLabel, className, }: NotificationBellProps): react.JSX.Element;
|
|
678
|
+
interface NotificationRowProps {
|
|
679
|
+
item: Notification;
|
|
680
|
+
/** Called after the destination opens — mark it read here */
|
|
681
|
+
onOpen?: (n: Notification) => void;
|
|
682
|
+
onDismiss?: (id: string) => void;
|
|
683
|
+
dismissLabel?: string;
|
|
684
|
+
unreadLabel?: string;
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* A single notification row.
|
|
688
|
+
*
|
|
689
|
+
* Unread is marked twice: with a dot and with heavier text. Colour alone is
|
|
690
|
+
* not enough — one man in twelve cannot separate two close greys, and weight
|
|
691
|
+
* reads regardless.
|
|
692
|
+
*
|
|
693
|
+
* And the row is **not one big button**. An inbox holds rows that open
|
|
694
|
+
* something and rows that are only read, and if the whole row is pressable
|
|
695
|
+
* someone who meant to read lands on another page instead. The action is a
|
|
696
|
+
* button with a name.
|
|
697
|
+
*/
|
|
698
|
+
declare function NotificationRow({ item, onOpen, onDismiss, dismissLabel, unreadLabel, }: NotificationRowProps): react.JSX.Element;
|
|
699
|
+
interface NotificationListProps {
|
|
700
|
+
items: Notification[];
|
|
701
|
+
onOpen?: (n: Notification) => void;
|
|
702
|
+
onDismiss?: (id: string) => void;
|
|
703
|
+
emptyTitle?: ReactNode;
|
|
704
|
+
emptyDescription?: ReactNode;
|
|
705
|
+
className?: string;
|
|
706
|
+
}
|
|
707
|
+
/** The list itself — identical in the dropdown panel and in the side drawer */
|
|
708
|
+
declare function NotificationList({ items, onOpen, onDismiss, emptyTitle, emptyDescription, className, }: NotificationListProps): react.JSX.Element;
|
|
709
|
+
interface NotificationPanelProps extends NotificationListProps {
|
|
710
|
+
title?: ReactNode;
|
|
711
|
+
unread?: number;
|
|
712
|
+
/**
|
|
713
|
+
* The unread that "mark all read" does not clear.
|
|
714
|
+
*
|
|
715
|
+
* This has to be shown: the badge drops from 12 to 2 and stops, and anyone
|
|
716
|
+
* who does not know why presses again and nothing happens, so it reads as a
|
|
717
|
+
* bug. One line says why, once.
|
|
718
|
+
*/
|
|
719
|
+
needsAction?: number;
|
|
720
|
+
onMarkAllRead?: () => void;
|
|
721
|
+
markAllLabel?: string;
|
|
722
|
+
/** A "see all" footer — without it the inbox is the end of the road */
|
|
723
|
+
onViewAll?: () => void;
|
|
724
|
+
viewAllLabel?: string;
|
|
725
|
+
/** The mute button in the header — pass it from `useNotificationSound` */
|
|
726
|
+
sound?: {
|
|
727
|
+
muted: boolean;
|
|
728
|
+
setMuted: (m: boolean) => void;
|
|
729
|
+
ready: boolean;
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* The dropdown panel — the notification inbox beside the bell.
|
|
734
|
+
*
|
|
735
|
+
* The list has a bounded height and **scrolls inside itself**: a panel that
|
|
736
|
+
* grows with its list reaches the bottom of the screen and cuts the last
|
|
737
|
+
* notification off with nothing to show it was cut.
|
|
738
|
+
*
|
|
739
|
+
* And "mark all read" disappears once nothing is unread — a button that does
|
|
740
|
+
* nothing, always present, teaches the user that buttons here do not respond.
|
|
741
|
+
*/
|
|
742
|
+
declare function NotificationPanel({ items, title, unread, needsAction, onMarkAllRead, markAllLabel, onViewAll, viewAllLabel, sound, onOpen, onDismiss, emptyTitle, emptyDescription, className, }: NotificationPanelProps): react.JSX.Element;
|
|
743
|
+
interface SoundToggleProps {
|
|
744
|
+
muted: boolean;
|
|
745
|
+
setMuted: (m: boolean) => void;
|
|
746
|
+
/** Audio is unlocked — before that the button says the browser blocked it, not the user */
|
|
747
|
+
ready: boolean;
|
|
748
|
+
className?: string;
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* Muting notification sound.
|
|
752
|
+
*
|
|
753
|
+
* The third state matters: **not muted, but the browser has not unlocked
|
|
754
|
+
* audio yet**. The button says so instead of pretending sound is on — someone
|
|
755
|
+
* waiting for a chime that never comes deserves to know why.
|
|
756
|
+
*/
|
|
757
|
+
declare function SoundToggle({ muted, setMuted, ready, className }: SoundToggleProps): react.JSX.Element;
|
|
758
|
+
interface NotificationMenuProps extends Omit<NotificationPanelProps, 'unread'> {
|
|
759
|
+
unread: number;
|
|
760
|
+
bellLabel?: string;
|
|
761
|
+
/**
|
|
762
|
+
* Which edge the panel aligns to against the bell — `end` by default.
|
|
763
|
+
*
|
|
764
|
+
* The bell sits at the `end` of the top bar, so the panel extends toward
|
|
765
|
+
* `start` and stays on screen. Move the bell and this has to flip.
|
|
766
|
+
*/
|
|
767
|
+
align?: 'start' | 'end';
|
|
768
|
+
/**
|
|
769
|
+
* Above the bell or below it — `bottom` by default.
|
|
770
|
+
*
|
|
771
|
+
* A bottom bar on mobile needs `top`: a panel dropping from a bell at the
|
|
772
|
+
* bottom of the screen lands entirely outside it.
|
|
773
|
+
*/
|
|
774
|
+
side?: 'top' | 'bottom';
|
|
775
|
+
className?: string;
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* The bell and the panel as one unit — this is what a top bar mounts.
|
|
779
|
+
*
|
|
780
|
+
* It closes on an outside click and on Escape. Both are needed: the outside
|
|
781
|
+
* click is what a mouse expects, and Escape is the only way out for anyone on
|
|
782
|
+
* a keyboard.
|
|
783
|
+
*/
|
|
784
|
+
declare function NotificationMenu({ unread, bellLabel, align, side, className, onViewAll, onOpen, ...panel }: NotificationMenuProps): react.JSX.Element;
|
|
785
|
+
interface NotificationDrawerProps extends NotificationListProps {
|
|
786
|
+
open: boolean;
|
|
787
|
+
onClose: () => void;
|
|
788
|
+
title?: ReactNode;
|
|
789
|
+
unread?: number;
|
|
790
|
+
/** Same as `NotificationPanel` — the explanation has to show on mobile too */
|
|
791
|
+
needsAction?: number;
|
|
792
|
+
onMarkAllRead?: () => void;
|
|
793
|
+
markAllLabel?: string;
|
|
794
|
+
closeLabel?: string;
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* The side drawer — the same inbox, from the edge of the screen.
|
|
798
|
+
*
|
|
799
|
+
* Why two and not one: the dropdown panel is anchored to the bell, and on a
|
|
800
|
+
* 360px screen that means a panel covering the whole screen with one edge
|
|
801
|
+
* hanging off it. The drawer opens from `inline-end` — the left in Arabic and
|
|
802
|
+
* the right in English, by itself.
|
|
803
|
+
*
|
|
804
|
+
* And the list inside is **identical**, down to the character: someone who
|
|
805
|
+
* saw a notification on desktop and looks for it on mobile has to see the
|
|
806
|
+
* same row, or they will think there are two.
|
|
807
|
+
*/
|
|
808
|
+
declare function NotificationDrawer({ open, onClose, items, title, unread, needsAction, onMarkAllRead, markAllLabel, closeLabel, onOpen, onDismiss, emptyTitle, emptyDescription, }: NotificationDrawerProps): react.JSX.Element;
|
|
809
|
+
/**
|
|
810
|
+
* The screen corner a toast drops into.
|
|
811
|
+
*
|
|
812
|
+
* `start`/`end` are logical, not left and right: `end` means the left in
|
|
813
|
+
* Arabic and the right in English. The bell moves with the language, and the
|
|
814
|
+
* notification has to move with it — otherwise it arrives from one side and
|
|
815
|
+
* is filed on the other.
|
|
816
|
+
*/
|
|
817
|
+
type NotificationPlacement = 'top-start' | 'top-end' | 'bottom-start' | 'bottom-end';
|
|
818
|
+
interface NotificationToastHostProps {
|
|
819
|
+
items: Notification[];
|
|
820
|
+
onClose: (id: string) => void;
|
|
821
|
+
onOpen?: (n: Notification) => void;
|
|
822
|
+
/**
|
|
823
|
+
* The corner — `top-end` by default, so it lands beside the bell.
|
|
824
|
+
*
|
|
825
|
+
* Keep it away from `ToastHost`'s corner (`bottom-start` by default): a
|
|
826
|
+
* toast says "the thing you did happened" and a notification says
|
|
827
|
+
* "something arrived from outside", and if both come from the same place
|
|
828
|
+
* they read as one queue.
|
|
829
|
+
*/
|
|
830
|
+
placement?: NotificationPlacement;
|
|
831
|
+
/** Column width above `sm` — below it takes the full width */
|
|
832
|
+
width?: string;
|
|
833
|
+
closeLabel?: string;
|
|
834
|
+
regionLabel?: string;
|
|
835
|
+
className?: string;
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* The toast notification — the card that drops in when something arrives.
|
|
839
|
+
*
|
|
840
|
+
* `aria-live="polite"`, not `assertive`: the latter cuts a screen reader off
|
|
841
|
+
* mid-sentence. A notification is already an interruption; it does not earn a
|
|
842
|
+
* second one — whoever is listening will hear it once the line finishes.
|
|
843
|
+
*/
|
|
844
|
+
declare function NotificationToastHost({ items, onClose, onOpen, placement, width, closeLabel, regionLabel, className, }: NotificationToastHostProps): react.JSX.Element | null;
|
|
845
|
+
|
|
846
|
+
export { NOTIFICATION_PRIORITIES, NOTIFICATION_SCHEMA_VERSION, NOTIFICATION_SOUNDS, type Notification, type NotificationAction, type NotificationBatch, NotificationBell, type NotificationBellProps, type NotificationCenter, NotificationDrawer, type NotificationDrawerProps, type NotificationJSON, type NotificationKind, type NotificationKindRegistry, NotificationList, type NotificationListProps, NotificationMenu, type NotificationMenuProps, NotificationPanel, type NotificationPanelProps, type NotificationParseResult, type NotificationPlacement, type NotificationPriority, NotificationProvider, type NotificationProviderProps, NotificationRow, type NotificationRowProps, type NotificationSoundApi, type NotificationSoundName, NotificationToastHost, type NotificationToastHostProps, type NotificationTranslate, type NotificationsApi, type ParseOptions, QUMRA_KINDS, QUMRA_NOTIFICATIONS, type ResolvedKind, SoundToggle, type SoundToggleProps, type UseNotificationCenterOptions, type UseNotificationSoundOptions, type UseNotificationsOptions, createKindRegistry, formatRelativeTime, isBulkReadable, optimistic, parseNotification, parseNotifications, relativeTickMs, serializeNotification, useNotificationCenter, useNotificationContext, useNotificationSound, useNotifications };
|