@qumra/fanar 0.0.0 → 0.0.1
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 +3 -849
- 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 +833 -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,833 @@
|
|
|
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
|
+
/** سجلّ الأنواع — الافتراضي سجلّ قمرة */
|
|
623
|
+
kinds?: NotificationKindRegistry;
|
|
624
|
+
/** لغة الوقت النسبي — الافتراضي `ar` */
|
|
625
|
+
locale?: string;
|
|
626
|
+
/**
|
|
627
|
+
* دالة الترجمة من نظام مشروعك — لقوالب عناوين الأنواع.
|
|
628
|
+
*
|
|
629
|
+
* من غيرها القوالب بتتجاهل ونصّ السيرفر بيتعرض. المكتبة مابتترجمش
|
|
630
|
+
* بنفسها: بتنادي `t(key, vars)` وبس، فالنصوص بتفضل في كتالوجك
|
|
631
|
+
* والجمع شغل نظامك.
|
|
632
|
+
*/
|
|
633
|
+
t?: NotificationTranslate;
|
|
634
|
+
/**
|
|
635
|
+
* التنقّل لوجهة الإشعار.
|
|
636
|
+
*
|
|
637
|
+
* المكتبة مابتنقّلش بنفسها لأن الرسالة بتحمل `href` مش دالة.
|
|
638
|
+
* خدها من `n.action.href` ووصّلها لراوترك.
|
|
639
|
+
*/
|
|
640
|
+
onAction?: (n: Notification) => void;
|
|
641
|
+
children: ReactNode;
|
|
642
|
+
}
|
|
643
|
+
/** بيوزّع السجلّ واللغة والتنقّل على كل الأسطح — مرّة في الجذر */
|
|
644
|
+
declare function NotificationProvider({ kinds, locale, t, onAction, children, }: NotificationProviderProps): react.JSX.Element;
|
|
645
|
+
declare const useNotificationContext: () => NotificationContext;
|
|
646
|
+
interface NotificationBellProps {
|
|
647
|
+
unread: number;
|
|
648
|
+
/**
|
|
649
|
+
* منهم كام محتاج إجراء — بيغيّر **شكل** العدّاد مش رقمه.
|
|
650
|
+
*
|
|
651
|
+
* نقطة حمرا مابتروحش أبداً بترجّع نفس الضغط اللي بيخلّي التاجر
|
|
652
|
+
* يطفّي الإشعارات. فالعدّاد بيفضل رقم غير المقروء، والحرج بيتعلّم
|
|
653
|
+
* بحلقة حواليه — علامة تانية على نفس الرقم مش رقم تاني.
|
|
654
|
+
*/
|
|
655
|
+
needsAction?: number;
|
|
656
|
+
onClick: () => void;
|
|
657
|
+
/** بيوصل لـ`aria-expanded` — الجرس اللي بيفتح لوح لازم يقولها */
|
|
658
|
+
expanded?: boolean;
|
|
659
|
+
label?: string;
|
|
660
|
+
/** «٣ غير مقروءة» — الرقم في `aria-label` مش في الشكل وحده */
|
|
661
|
+
unreadLabel?: (n: number) => string;
|
|
662
|
+
className?: string;
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* زرّ الجرس بعدّاد غير المقروء.
|
|
666
|
+
*
|
|
667
|
+
* العدّاد أحمر ومحاط بحلقة من لون الخلفية — من غيرها الرقم بيتلزق في
|
|
668
|
+
* أي أيقونة وراه وبيتقرا جزء منها. والرقم في `aria-label` كمان: قارئ
|
|
669
|
+
* الشاشة مابيشوفش الشارة، وجرس بيقول «الإشعارات» وبس بيخلّي اللي بيسمع
|
|
670
|
+
* يفتح الصندوق كل مرة عشان يعرف فيه جديد ولا لأ.
|
|
671
|
+
*/
|
|
672
|
+
declare function NotificationBell({ unread, needsAction, onClick, expanded, label, unreadLabel, className, }: NotificationBellProps): react.JSX.Element;
|
|
673
|
+
interface NotificationRowProps {
|
|
674
|
+
item: Notification;
|
|
675
|
+
/** بيتنادى بعد فتح الوجهة — علّمه مقروء هنا */
|
|
676
|
+
onOpen?: (n: Notification) => void;
|
|
677
|
+
onDismiss?: (id: string) => void;
|
|
678
|
+
dismissLabel?: string;
|
|
679
|
+
unreadLabel?: string;
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* سطر إشعار واحد.
|
|
683
|
+
*
|
|
684
|
+
* غير المقروء بيتعلّم بحاجتين: نقطة وخطّ أتقل. اللون وحده مابيكفيش —
|
|
685
|
+
* واحد من كل اتني عشر راجل مابيفرّقش بين درجتين رماديين صغيّرين،
|
|
686
|
+
* والوزن بيتقرا بالعين مهما كان.
|
|
687
|
+
*
|
|
688
|
+
* والسطر **مش زرار كله**. الصندوق فيه سطور بتفتح وسطور بتتقري وبس،
|
|
689
|
+
* ولو السطر كله قابل للضغط اللي عايز يقرا بيدوس بالغلط ويلاقي نفسه في
|
|
690
|
+
* صفحة تانية. الإجراء زرار له اسم.
|
|
691
|
+
*/
|
|
692
|
+
declare function NotificationRow({ item, onOpen, onDismiss, dismissLabel, unreadLabel, }: NotificationRowProps): react.JSX.Element;
|
|
693
|
+
interface NotificationListProps {
|
|
694
|
+
items: Notification[];
|
|
695
|
+
onOpen?: (n: Notification) => void;
|
|
696
|
+
onDismiss?: (id: string) => void;
|
|
697
|
+
emptyTitle?: ReactNode;
|
|
698
|
+
emptyDescription?: ReactNode;
|
|
699
|
+
className?: string;
|
|
700
|
+
}
|
|
701
|
+
/** القايمة نفسها — نفسها في اللوح المنسدل وفي الدرج الجانبي بالظبط */
|
|
702
|
+
declare function NotificationList({ items, onOpen, onDismiss, emptyTitle, emptyDescription, className, }: NotificationListProps): react.JSX.Element;
|
|
703
|
+
interface NotificationPanelProps extends NotificationListProps {
|
|
704
|
+
title?: ReactNode;
|
|
705
|
+
unread?: number;
|
|
706
|
+
/**
|
|
707
|
+
* غير المقروء اللي «تعليم الكل» مش بيمسحه.
|
|
708
|
+
*
|
|
709
|
+
* لازم يتعرض: العدّاد بينزل من ١٢ لـ٢ وبيقف، واللي مايعرفش ليه
|
|
710
|
+
* بيدوس تاني ومافيش، فبيتقرا عطل. السطر بيقول السبب مرّة واحدة.
|
|
711
|
+
*/
|
|
712
|
+
needsAction?: number;
|
|
713
|
+
onMarkAllRead?: () => void;
|
|
714
|
+
markAllLabel?: string;
|
|
715
|
+
/** تذييل «عرض الكل» — من غيره الصندوق بيبقى نهاية الطريق */
|
|
716
|
+
onViewAll?: () => void;
|
|
717
|
+
viewAllLabel?: string;
|
|
718
|
+
/** زرّ كتم الصوت في الترويسة — مرّره من `useNotificationSound` */
|
|
719
|
+
sound?: {
|
|
720
|
+
muted: boolean;
|
|
721
|
+
setMuted: (m: boolean) => void;
|
|
722
|
+
ready: boolean;
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* اللوح المنسدل — صندوق الإشعارات جنب الجرس.
|
|
727
|
+
*
|
|
728
|
+
* ارتفاع القايمة محدود و**بيتمرّر جوّه**: اللوح اللي بيطول بطول القايمة
|
|
729
|
+
* بيوصل لآخر الشاشة وبيقص آخر إشعار من غير ما يبان إنه مقصوص.
|
|
730
|
+
*
|
|
731
|
+
* و«تعليم الكل كمقروء» بيختفي لما يبقى مفيش غير مقروء — زرار بيعمل لا
|
|
732
|
+
* حاجة موجود دايماً بيعلّم المستخدم إن الأزرار هنا مش بترد.
|
|
733
|
+
*/
|
|
734
|
+
declare function NotificationPanel({ items, title, unread, needsAction, onMarkAllRead, markAllLabel, onViewAll, viewAllLabel, sound, onOpen, onDismiss, emptyTitle, emptyDescription, className, }: NotificationPanelProps): react.JSX.Element;
|
|
735
|
+
interface SoundToggleProps {
|
|
736
|
+
muted: boolean;
|
|
737
|
+
setMuted: (m: boolean) => void;
|
|
738
|
+
/** الصوت اتفكّ — قبلها الزرار بيقول إنه مقفول من المتصفّح مش من المستخدم */
|
|
739
|
+
ready: boolean;
|
|
740
|
+
className?: string;
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* كتم صوت الإشعارات.
|
|
744
|
+
*
|
|
745
|
+
* الحالة التالتة مهمة: **مش مكتوم بس المتصفّح لسه قافل**. الزرار
|
|
746
|
+
* بيقولها بدل ما يدّعي إن الصوت شغّال — والمستخدم اللي مستني رنّة
|
|
747
|
+
* مش جاية يستاهل يعرف السبب.
|
|
748
|
+
*/
|
|
749
|
+
declare function SoundToggle({ muted, setMuted, ready, className }: SoundToggleProps): react.JSX.Element;
|
|
750
|
+
interface NotificationMenuProps extends Omit<NotificationPanelProps, 'unread'> {
|
|
751
|
+
unread: number;
|
|
752
|
+
bellLabel?: string;
|
|
753
|
+
/**
|
|
754
|
+
* الحافة اللي اللوح بيتحاذى عليها مع الجرس — الافتراضي `end`.
|
|
755
|
+
*
|
|
756
|
+
* الجرس في الشريط العلوي بيبقى ناحية `end`، فاللوح بيتمدّد ناحية
|
|
757
|
+
* `start` وبيفضل جوّه الشاشة. لو الجرس اتحرّك لازم ده يتقلب.
|
|
758
|
+
*/
|
|
759
|
+
align?: 'start' | 'end';
|
|
760
|
+
/**
|
|
761
|
+
* فوق الجرس ولا تحته — الافتراضي `bottom`.
|
|
762
|
+
*
|
|
763
|
+
* الشريط السفلي على الموبايل محتاج `top`: لوح بينزل من جرس على آخر
|
|
764
|
+
* الشاشة بيطلع كله برّه.
|
|
765
|
+
*/
|
|
766
|
+
side?: 'top' | 'bottom';
|
|
767
|
+
className?: string;
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* الجرس واللوح كوحدة — ده اللي الشريط العلوي بيحطّه.
|
|
771
|
+
*
|
|
772
|
+
* الإغلاق بالضغط برّه وبـEscape. والاتنين لازمين: الضغط برّه هو
|
|
773
|
+
* المتوقّع بالفأرة، وEscape هو المخرج الوحيد للي ماسك كيبورد.
|
|
774
|
+
*/
|
|
775
|
+
declare function NotificationMenu({ unread, bellLabel, align, side, className, onViewAll, onOpen, ...panel }: NotificationMenuProps): react.JSX.Element;
|
|
776
|
+
interface NotificationDrawerProps extends NotificationListProps {
|
|
777
|
+
open: boolean;
|
|
778
|
+
onClose: () => void;
|
|
779
|
+
title?: ReactNode;
|
|
780
|
+
unread?: number;
|
|
781
|
+
/** زي `NotificationPanel` — الشرح لازم يبان على الموبايل كمان */
|
|
782
|
+
needsAction?: number;
|
|
783
|
+
onMarkAllRead?: () => void;
|
|
784
|
+
markAllLabel?: string;
|
|
785
|
+
closeLabel?: string;
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* الدرج الجانبي — نفس الصندوق لكن من حافة الشاشة.
|
|
789
|
+
*
|
|
790
|
+
* ليه التنين مش واحد: اللوح المنسدل معلّق في الجرس، وعلى شاشة ٣٦٠
|
|
791
|
+
* بكسل ده يعني لوح بيغطّي الشاشة وطرفه بره الحافة. والدرج بيفتح من
|
|
792
|
+
* `inline-end` — الشمال في العربي واليمين في الإنجليزي، لوحده.
|
|
793
|
+
*
|
|
794
|
+
* والقايمة جوّاه **هي هي** بلا فرق حرف: اللي شاف إشعاراً على الديسكتوب
|
|
795
|
+
* وبصّ له على الموبايل لازم يشوف نفس السطر، وإلا بيفتكرهم اتنين.
|
|
796
|
+
*/
|
|
797
|
+
declare function NotificationDrawer({ open, onClose, items, title, unread, needsAction, onMarkAllRead, markAllLabel, closeLabel, onOpen, onDismiss, emptyTitle, emptyDescription, }: NotificationDrawerProps): react.JSX.Element;
|
|
798
|
+
/**
|
|
799
|
+
* ركن الشاشة اللي الطاير بينزل فيه.
|
|
800
|
+
*
|
|
801
|
+
* `start`/`end` منطقية مش يمين وشمال: `end` يعني الشمال في العربي
|
|
802
|
+
* واليمين في الإنجليزي. الجرس بيقلب مكانه مع اللغة، والإشعار لازم
|
|
803
|
+
* يقلب معاه — وإلا بيجي من ناحية وبيتخزّن في ناحية تانية.
|
|
804
|
+
*/
|
|
805
|
+
type NotificationPlacement = 'top-start' | 'top-end' | 'bottom-start' | 'bottom-end';
|
|
806
|
+
interface NotificationToastHostProps {
|
|
807
|
+
items: Notification[];
|
|
808
|
+
onClose: (id: string) => void;
|
|
809
|
+
onOpen?: (n: Notification) => void;
|
|
810
|
+
/**
|
|
811
|
+
* الركن — الافتراضي `top-end` عشان يقع جنب الجرس.
|
|
812
|
+
*
|
|
813
|
+
* وخلّيه بعيد عن ركن `ToastHost` (`bottom-start` افتراضاً): التوست
|
|
814
|
+
* بيقول «الفعل اللي عملته حصل» والإشعار بيقول «حاجة جت من برّه»،
|
|
815
|
+
* ولو الاتنين طلعوا من نفس المكان بيتقروا طابور واحد.
|
|
816
|
+
*/
|
|
817
|
+
placement?: NotificationPlacement;
|
|
818
|
+
/** عرض العمود فوق `sm` — تحتها بياخد العرض كله */
|
|
819
|
+
width?: string;
|
|
820
|
+
closeLabel?: string;
|
|
821
|
+
regionLabel?: string;
|
|
822
|
+
className?: string;
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* الإشعار الطاير — الكرت اللي بينزل لما حاجة توصل.
|
|
826
|
+
*
|
|
827
|
+
* `aria-live="polite"` مش `assertive`: الأخير بيقطع قارئ الشاشة في
|
|
828
|
+
* نصّ الجملة. الإشعار مقاطعة أصلاً، مايستاهلش يقاطع مرتين — اللي بيقرا
|
|
829
|
+
* هيسمعه لما يخلّص السطر.
|
|
830
|
+
*/
|
|
831
|
+
declare function NotificationToastHost({ items, onClose, onOpen, placement, width, closeLabel, regionLabel, className, }: NotificationToastHostProps): react.JSX.Element | null;
|
|
832
|
+
|
|
833
|
+
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 };
|