@neptune.fintech/web-ui 2.1.0 → 2.3.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.
Files changed (44) hide show
  1. package/dist/components/cards.d.ts +64 -0
  2. package/dist/components/cards.d.ts.map +1 -0
  3. package/dist/components/cards.js +508 -0
  4. package/dist/components/cards.js.map +1 -0
  5. package/dist/components/corporate.d.ts +84 -0
  6. package/dist/components/corporate.d.ts.map +1 -0
  7. package/dist/components/corporate.js +782 -0
  8. package/dist/components/corporate.js.map +1 -0
  9. package/dist/components/data-viz.d.ts +69 -0
  10. package/dist/components/data-viz.d.ts.map +1 -0
  11. package/dist/components/data-viz.js +526 -0
  12. package/dist/components/data-viz.js.map +1 -0
  13. package/dist/components/feedback-status.d.ts +80 -0
  14. package/dist/components/feedback-status.d.ts.map +1 -0
  15. package/dist/components/feedback-status.js +537 -0
  16. package/dist/components/feedback-status.js.map +1 -0
  17. package/dist/components/money-inputs.d.ts +105 -0
  18. package/dist/components/money-inputs.d.ts.map +1 -0
  19. package/dist/components/money-inputs.js +766 -0
  20. package/dist/components/money-inputs.js.map +1 -0
  21. package/dist/components/money-movement.d.ts +79 -0
  22. package/dist/components/money-movement.d.ts.map +1 -0
  23. package/dist/components/money-movement.js +740 -0
  24. package/dist/components/money-movement.js.map +1 -0
  25. package/dist/components/premium.d.ts +38 -0
  26. package/dist/components/premium.d.ts.map +1 -0
  27. package/dist/components/premium.js +275 -0
  28. package/dist/components/premium.js.map +1 -0
  29. package/dist/components/shell-layout.d.ts +103 -0
  30. package/dist/components/shell-layout.d.ts.map +1 -0
  31. package/dist/components/shell-layout.js +582 -0
  32. package/dist/components/shell-layout.js.map +1 -0
  33. package/dist/components/wallet-pay.d.ts +85 -0
  34. package/dist/components/wallet-pay.d.ts.map +1 -0
  35. package/dist/components/wallet-pay.js +633 -0
  36. package/dist/components/wallet-pay.js.map +1 -0
  37. package/dist/index.d.ts +10 -1
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +10 -1
  40. package/dist/index.js.map +1 -1
  41. package/dist/register.d.ts.map +1 -1
  42. package/dist/register.js +70 -0
  43. package/dist/register.js.map +1 -1
  44. package/package.json +1 -1
@@ -0,0 +1,782 @@
1
+ // © 2026 Neptune.Fintech (neptune.ly) · Neptune Odyssey Community License v1.0
2
+ // Neptune Odyssey — corporate & back-office
3
+ // <npt-approval-item>, <npt-batch-card>, <npt-audit-row>, <npt-user-row>,
4
+ // <npt-permission-toggle>, <npt-workflow-status>.
5
+ // Maker-checker, bulk payments, audit logs, user admin & access control.
6
+ // Custom-property driven only; logical layout → mirrors in RTL.
7
+ import { NptElement, css, html, A11Y } from "./base.js";
8
+ const esc = (v) => (v ?? "").replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]);
9
+ /**
10
+ * <npt-approval-item title="Vendor payment — ACME" amount="48,200.00" currency="LYD"
11
+ * maker="Mona Khaled" status="pending|approved|rejected"></npt-approval-item>
12
+ * A maker-checker queue item. Approve/Reject buttons emit `approve` / `reject`.
13
+ * Buttons hide once the item is no longer `pending`; the status chip reflects state.
14
+ */
15
+ export class NptApprovalItem extends NptElement {
16
+ constructor() {
17
+ super(...arguments);
18
+ this.onClick = (e) => {
19
+ const btn = e.target?.closest("[data-action]");
20
+ if (!btn)
21
+ return;
22
+ const action = btn.getAttribute("data-action");
23
+ if (action === "approve" || action === "reject") {
24
+ this.dispatchEvent(new CustomEvent(action, { bubbles: true }));
25
+ }
26
+ };
27
+ }
28
+ attributeChangedCallback() {
29
+ if (this.isConnected)
30
+ this.update();
31
+ }
32
+ connectedCallback() {
33
+ super.connectedCallback();
34
+ this.root.addEventListener("click", this.onClick);
35
+ }
36
+ disconnectedCallback() {
37
+ this.root.removeEventListener("click", this.onClick);
38
+ }
39
+ styles() {
40
+ return css `
41
+ ${A11Y}
42
+ :host {
43
+ display: block;
44
+ }
45
+ .item {
46
+ display: flex;
47
+ align-items: center;
48
+ flex-wrap: wrap;
49
+ gap: var(--npt-space-4, 16px);
50
+ padding-inline: var(--npt-space-4, 16px);
51
+ padding-block: var(--npt-space-4, 16px);
52
+ border-radius: var(--npt-corner-md, 16px);
53
+ background: var(--md-sys-color-surface-container-low);
54
+ color: var(--md-sys-color-on-surface);
55
+ box-sizing: border-box;
56
+ border: 1px solid var(--md-sys-color-outline-variant);
57
+ }
58
+ .body {
59
+ flex: 1 1 200px;
60
+ min-inline-size: 0;
61
+ }
62
+ .title {
63
+ font-family: var(--npt-font-text);
64
+ font-size: var(--npt-text-body-lg, 16px);
65
+ font-weight: 600;
66
+ color: var(--md-sys-color-on-surface);
67
+ margin: 0;
68
+ white-space: nowrap;
69
+ overflow: hidden;
70
+ text-overflow: ellipsis;
71
+ }
72
+ .maker {
73
+ font-family: var(--npt-font-text);
74
+ font-size: var(--npt-text-body, 14px);
75
+ color: var(--md-sys-color-on-surface-variant);
76
+ margin: 2px 0 0;
77
+ }
78
+ .amount {
79
+ font-family: var(--npt-font-num);
80
+ font-feature-settings: "tnum" 1;
81
+ font-variant-numeric: tabular-nums;
82
+ font-size: var(--npt-text-title, 18px);
83
+ font-weight: 600;
84
+ color: var(--md-sys-color-on-surface);
85
+ white-space: nowrap;
86
+ display: flex;
87
+ align-items: baseline;
88
+ gap: var(--npt-space-2, 8px);
89
+ }
90
+ .currency {
91
+ font-size: var(--npt-text-label, 14px);
92
+ opacity: 0.78;
93
+ }
94
+ .chip {
95
+ font-family: var(--npt-font-text);
96
+ font-size: var(--npt-text-caption, 12px);
97
+ font-weight: 600;
98
+ min-height: 24px;
99
+ display: inline-flex;
100
+ align-items: center;
101
+ gap: var(--npt-space-1, 4px);
102
+ padding-inline: var(--npt-space-3, 12px);
103
+ border-radius: var(--npt-corner-full, 999px);
104
+ background: var(--md-sys-color-surface-container-highest);
105
+ color: var(--md-sys-color-on-surface-variant);
106
+ }
107
+ :host([status="approved"]) .chip {
108
+ background: var(--md-sys-color-success);
109
+ color: var(--md-sys-color-on-success);
110
+ }
111
+ :host([status="rejected"]) .chip {
112
+ background: var(--md-sys-color-error-container);
113
+ color: var(--md-sys-color-on-error-container);
114
+ }
115
+ .actions {
116
+ display: inline-flex;
117
+ gap: var(--npt-space-2, 8px);
118
+ }
119
+ :host([status="approved"]) .actions,
120
+ :host([status="rejected"]) .actions {
121
+ display: none;
122
+ }
123
+ button[data-action] {
124
+ font-family: var(--npt-font-text);
125
+ font-size: var(--npt-text-label, 14px);
126
+ font-weight: 600;
127
+ min-height: 44px;
128
+ padding-inline: var(--npt-space-5, 20px);
129
+ border-radius: var(--npt-corner-full, 999px);
130
+ border: none;
131
+ cursor: pointer;
132
+ transition: box-shadow var(--npt-dur-fast, 200ms) var(--npt-ease-standard, ease),
133
+ background-color var(--npt-dur-fast, 200ms) var(--npt-ease-standard, ease);
134
+ }
135
+ button[data-action="approve"] {
136
+ background: var(--md-sys-color-success);
137
+ color: var(--md-sys-color-on-success);
138
+ }
139
+ button[data-action="approve"]:hover {
140
+ box-shadow: var(--npt-elevation-1, 0 1px 3px rgba(0, 0, 0, 0.2));
141
+ }
142
+ button[data-action="reject"] {
143
+ background: transparent;
144
+ color: var(--md-sys-color-error);
145
+ border: 1px solid var(--md-sys-color-outline);
146
+ }
147
+ `;
148
+ }
149
+ render() {
150
+ const title = esc(this.getAttribute("title"));
151
+ const amount = esc(this.getAttribute("amount"));
152
+ const currency = esc(this.getAttribute("currency"));
153
+ const maker = esc(this.getAttribute("maker"));
154
+ const status = this.getAttribute("status") || "pending";
155
+ const chipLabel = status === "approved" ? "Approved" : status === "rejected" ? "Rejected" : "Pending";
156
+ const chipIcon = status === "approved" ? "✓" : status === "rejected" ? "✕" : "•";
157
+ return html `<div class="item" part="item" role="group" aria-label="${title} ${chipLabel}">
158
+ <div class="body">
159
+ <p class="title">${title}</p>
160
+ ${maker ? html `<p class="maker">${maker}</p>` : ""}
161
+ </div>
162
+ <p class="amount"><span class="currency">${currency}</span>${amount}</p>
163
+ <span class="chip" part="chip"><span aria-hidden="true">${chipIcon}</span>${chipLabel}</span>
164
+ <div class="actions" part="actions">
165
+ <button type="button" data-action="approve" part="approve">Approve</button>
166
+ <button type="button" data-action="reject" part="reject">Reject</button>
167
+ </div>
168
+ </div>`;
169
+ }
170
+ }
171
+ NptApprovalItem.observedAttributes = ["title", "amount", "currency", "maker", "status"];
172
+ /**
173
+ * <npt-batch-card filename="payroll-jun.csv" totalAmount="1,204,800.00" currency="LYD"
174
+ * payeeCount="312" requiredApprovals="2" validated="308" warnings="3" errors="1">
175
+ * <npt-button slot="action">Submit batch</npt-button>
176
+ * </npt-batch-card>
177
+ * A bulk-payment batch summary with a validated/warnings/errors counts row and an
178
+ * action slot for the primary CTA.
179
+ */
180
+ export class NptBatchCard extends NptElement {
181
+ attributeChangedCallback() {
182
+ if (this.isConnected)
183
+ this.update();
184
+ }
185
+ styles() {
186
+ return css `
187
+ ${A11Y}
188
+ :host {
189
+ display: block;
190
+ }
191
+ .card {
192
+ border-radius: var(--npt-corner-lg, 24px);
193
+ padding: var(--npt-space-6, 24px);
194
+ background: var(--md-sys-color-surface-container);
195
+ color: var(--md-sys-color-on-surface);
196
+ box-sizing: border-box;
197
+ box-shadow: var(--npt-elevation-1, 0 1px 3px rgba(0, 0, 0, 0.2));
198
+ }
199
+ .head {
200
+ display: flex;
201
+ align-items: center;
202
+ gap: var(--npt-space-3, 12px);
203
+ }
204
+ .file-icon {
205
+ inline-size: 40px;
206
+ block-size: 40px;
207
+ flex: 0 0 auto;
208
+ border-radius: var(--npt-corner-sm, 12px);
209
+ background: var(--md-sys-color-primary-container);
210
+ color: var(--md-sys-color-on-primary-container);
211
+ display: grid;
212
+ place-items: center;
213
+ font-size: var(--npt-text-title, 18px);
214
+ }
215
+ .filename {
216
+ font-family: var(--npt-font-text);
217
+ font-size: var(--npt-text-body-lg, 16px);
218
+ font-weight: 600;
219
+ margin: 0;
220
+ white-space: nowrap;
221
+ overflow: hidden;
222
+ text-overflow: ellipsis;
223
+ }
224
+ .payees {
225
+ font-family: var(--npt-font-text);
226
+ font-size: var(--npt-text-body, 14px);
227
+ color: var(--md-sys-color-on-surface-variant);
228
+ margin: 2px 0 0;
229
+ }
230
+ .amount {
231
+ font-family: var(--npt-font-num);
232
+ font-feature-settings: "tnum" 1;
233
+ font-variant-numeric: tabular-nums;
234
+ font-size: var(--npt-text-headline, 28px);
235
+ line-height: var(--npt-leading-headline, 36px);
236
+ font-weight: 700;
237
+ letter-spacing: var(--npt-display-tracking, -0.02em);
238
+ margin: var(--npt-space-4, 16px) 0 0;
239
+ display: flex;
240
+ align-items: baseline;
241
+ gap: var(--npt-space-2, 8px);
242
+ }
243
+ .currency {
244
+ font-size: var(--npt-text-title, 18px);
245
+ opacity: 0.78;
246
+ }
247
+ .counts {
248
+ display: flex;
249
+ flex-wrap: wrap;
250
+ gap: var(--npt-space-2, 8px);
251
+ margin-block-start: var(--npt-space-4, 16px);
252
+ }
253
+ .count {
254
+ font-family: var(--npt-font-text);
255
+ font-size: var(--npt-text-caption, 12px);
256
+ font-weight: 600;
257
+ display: inline-flex;
258
+ align-items: center;
259
+ gap: var(--npt-space-1, 4px);
260
+ padding-inline: var(--npt-space-3, 12px);
261
+ padding-block: var(--npt-space-1, 4px);
262
+ border-radius: var(--npt-corner-full, 999px);
263
+ background: var(--md-sys-color-surface-container-highest);
264
+ color: var(--md-sys-color-on-surface-variant);
265
+ }
266
+ .count .n {
267
+ font-family: var(--npt-font-num);
268
+ font-variant-numeric: tabular-nums;
269
+ }
270
+ .count.ok {
271
+ background: var(--md-sys-color-success);
272
+ color: var(--md-sys-color-on-success);
273
+ }
274
+ .count.warn {
275
+ background: var(--md-sys-color-tertiary-container);
276
+ color: var(--md-sys-color-on-tertiary-container);
277
+ }
278
+ .count.err {
279
+ background: var(--md-sys-color-error-container);
280
+ color: var(--md-sys-color-on-error-container);
281
+ }
282
+ .footer {
283
+ display: flex;
284
+ align-items: center;
285
+ gap: var(--npt-space-4, 16px);
286
+ margin-block-start: var(--npt-space-5, 20px);
287
+ }
288
+ .approvals {
289
+ font-family: var(--npt-font-text);
290
+ font-size: var(--npt-text-body, 14px);
291
+ color: var(--md-sys-color-on-surface-variant);
292
+ }
293
+ .approvals .n {
294
+ font-family: var(--npt-font-num);
295
+ font-variant-numeric: tabular-nums;
296
+ color: var(--md-sys-color-on-surface);
297
+ font-weight: 600;
298
+ }
299
+ .action {
300
+ margin-inline-start: auto;
301
+ display: inline-flex;
302
+ }
303
+ `;
304
+ }
305
+ render() {
306
+ const filename = esc(this.getAttribute("filename"));
307
+ const amount = esc(this.getAttribute("totalamount"));
308
+ const currency = esc(this.getAttribute("currency"));
309
+ const payeeCount = esc(this.getAttribute("payeecount"));
310
+ const required = esc(this.getAttribute("requiredapprovals"));
311
+ const validated = esc(this.getAttribute("validated")) || "0";
312
+ const warnings = esc(this.getAttribute("warnings")) || "0";
313
+ const errors = esc(this.getAttribute("errors")) || "0";
314
+ return html `<div class="card" part="card" role="group" aria-label="Batch ${filename}">
315
+ <div class="head">
316
+ <span class="file-icon" aria-hidden="true">▦</span>
317
+ <div>
318
+ <p class="filename">${filename}</p>
319
+ ${payeeCount ? html `<p class="payees">${payeeCount} payees</p>` : ""}
320
+ </div>
321
+ </div>
322
+ <p class="amount"><span class="currency">${currency}</span>${amount}</p>
323
+ <div class="counts" part="counts" role="list" aria-label="Validation results">
324
+ <span class="count ok" role="listitem">
325
+ <span aria-hidden="true">✓</span><span class="n">${validated}</span> validated
326
+ </span>
327
+ <span class="count warn" role="listitem">
328
+ <span aria-hidden="true">!</span><span class="n">${warnings}</span> warnings
329
+ </span>
330
+ <span class="count err" role="listitem">
331
+ <span aria-hidden="true">✕</span><span class="n">${errors}</span> errors
332
+ </span>
333
+ </div>
334
+ <div class="footer">
335
+ ${required
336
+ ? html `<span class="approvals">Requires <span class="n">${required}</span> approvals</span>`
337
+ : ""}
338
+ <span class="action"><slot name="action"></slot></span>
339
+ </div>
340
+ </div>`;
341
+ }
342
+ }
343
+ NptBatchCard.observedAttributes = [
344
+ "filename",
345
+ "totalamount",
346
+ "currency",
347
+ "payeecount",
348
+ "requiredapprovals",
349
+ "validated",
350
+ "warnings",
351
+ "errors",
352
+ ];
353
+ /**
354
+ * <npt-audit-row actor="Mona Khaled" action="approved payment" target="#PAY-3192"
355
+ * time="2026-06-27 14:02"></npt-audit-row>
356
+ * A compact audit-log line with a leading status dot.
357
+ */
358
+ export class NptAuditRow extends NptElement {
359
+ attributeChangedCallback() {
360
+ if (this.isConnected)
361
+ this.update();
362
+ }
363
+ styles() {
364
+ return css `
365
+ :host {
366
+ display: block;
367
+ }
368
+ .row {
369
+ display: flex;
370
+ align-items: baseline;
371
+ gap: var(--npt-space-3, 12px);
372
+ min-height: 36px;
373
+ padding-block: var(--npt-space-2, 8px);
374
+ padding-inline: var(--npt-space-2, 8px);
375
+ border-bottom: 1px solid var(--md-sys-color-outline-variant);
376
+ font-family: var(--npt-font-text);
377
+ font-size: var(--npt-text-body, 14px);
378
+ }
379
+ .dot {
380
+ flex: 0 0 auto;
381
+ inline-size: 8px;
382
+ block-size: 8px;
383
+ border-radius: var(--npt-corner-full, 999px);
384
+ background: var(--md-sys-color-primary);
385
+ align-self: center;
386
+ }
387
+ .text {
388
+ flex: 1 1 auto;
389
+ min-inline-size: 0;
390
+ color: var(--md-sys-color-on-surface-variant);
391
+ }
392
+ .actor {
393
+ color: var(--md-sys-color-on-surface);
394
+ font-weight: 600;
395
+ }
396
+ .target {
397
+ color: var(--md-sys-color-primary);
398
+ font-weight: 600;
399
+ }
400
+ .time {
401
+ flex: 0 0 auto;
402
+ font-family: var(--npt-font-num);
403
+ font-variant-numeric: tabular-nums;
404
+ font-size: var(--npt-text-caption, 12px);
405
+ color: var(--md-sys-color-on-surface-variant);
406
+ white-space: nowrap;
407
+ }
408
+ `;
409
+ }
410
+ render() {
411
+ const actor = esc(this.getAttribute("actor"));
412
+ const action = esc(this.getAttribute("action"));
413
+ const target = esc(this.getAttribute("target"));
414
+ const time = esc(this.getAttribute("time"));
415
+ return html `<div class="row" part="row" role="listitem">
416
+ <span class="dot" aria-hidden="true"></span>
417
+ <span class="text">
418
+ <span class="actor">${actor}</span> ${action}
419
+ ${target ? html `<span class="target">${target}</span>` : ""}
420
+ </span>
421
+ ${time ? html `<time class="time">${time}</time>` : ""}
422
+ </div>`;
423
+ }
424
+ }
425
+ NptAuditRow.observedAttributes = ["actor", "action", "target", "time"];
426
+ /**
427
+ * <npt-user-row name="Mona Khaled" email="mona@bank.ly" role="Checker"
428
+ * initials="MK" src="" [suspended]>
429
+ * <npt-icon-button slot="actions">⋯</npt-icon-button>
430
+ * </npt-user-row>
431
+ * A user-admin list row: avatar/initials, name + email, role chip, status, and a
432
+ * trailing actions slot. `suspended` dims the row and shows a Suspended chip.
433
+ */
434
+ export class NptUserRow extends NptElement {
435
+ attributeChangedCallback() {
436
+ if (this.isConnected)
437
+ this.update();
438
+ }
439
+ styles() {
440
+ return css `
441
+ ${A11Y}
442
+ :host {
443
+ display: block;
444
+ }
445
+ .row {
446
+ display: flex;
447
+ align-items: center;
448
+ gap: var(--npt-space-4, 16px);
449
+ min-height: 56px;
450
+ padding-block: var(--npt-space-3, 12px);
451
+ padding-inline: var(--npt-space-2, 8px);
452
+ border-bottom: 1px solid var(--md-sys-color-outline-variant);
453
+ }
454
+ :host([suspended]) .avatar,
455
+ :host([suspended]) .body {
456
+ opacity: 0.55;
457
+ }
458
+ .avatar {
459
+ inline-size: 40px;
460
+ block-size: 40px;
461
+ flex: 0 0 auto;
462
+ border-radius: var(--npt-corner-full, 999px);
463
+ overflow: hidden;
464
+ display: grid;
465
+ place-items: center;
466
+ background: var(--md-sys-color-primary-container);
467
+ color: var(--md-sys-color-on-primary-container);
468
+ font-family: var(--npt-font-display);
469
+ font-weight: 600;
470
+ font-size: var(--npt-text-label, 14px);
471
+ }
472
+ .avatar img {
473
+ inline-size: 100%;
474
+ block-size: 100%;
475
+ object-fit: cover;
476
+ }
477
+ .body {
478
+ flex: 1 1 auto;
479
+ min-inline-size: 0;
480
+ }
481
+ .name {
482
+ font-family: var(--npt-font-text);
483
+ font-size: var(--npt-text-body-lg, 16px);
484
+ font-weight: 600;
485
+ color: var(--md-sys-color-on-surface);
486
+ margin: 0;
487
+ white-space: nowrap;
488
+ overflow: hidden;
489
+ text-overflow: ellipsis;
490
+ }
491
+ .email {
492
+ font-family: var(--npt-font-text);
493
+ font-size: var(--npt-text-body, 14px);
494
+ color: var(--md-sys-color-on-surface-variant);
495
+ margin: 2px 0 0;
496
+ white-space: nowrap;
497
+ overflow: hidden;
498
+ text-overflow: ellipsis;
499
+ }
500
+ .meta {
501
+ display: inline-flex;
502
+ align-items: center;
503
+ gap: var(--npt-space-2, 8px);
504
+ flex: 0 0 auto;
505
+ }
506
+ .chip {
507
+ font-family: var(--npt-font-text);
508
+ font-size: var(--npt-text-caption, 12px);
509
+ font-weight: 600;
510
+ min-height: 24px;
511
+ display: inline-flex;
512
+ align-items: center;
513
+ padding-inline: var(--npt-space-3, 12px);
514
+ border-radius: var(--npt-corner-full, 999px);
515
+ background: var(--md-sys-color-secondary-container);
516
+ color: var(--md-sys-color-on-secondary-container);
517
+ }
518
+ .chip.suspended {
519
+ background: var(--md-sys-color-error-container);
520
+ color: var(--md-sys-color-on-error-container);
521
+ }
522
+ .actions {
523
+ display: inline-flex;
524
+ gap: var(--npt-space-1, 4px);
525
+ }
526
+ `;
527
+ }
528
+ render() {
529
+ const name = esc(this.getAttribute("name"));
530
+ const email = esc(this.getAttribute("email"));
531
+ const role = esc(this.getAttribute("role"));
532
+ const initials = esc(this.getAttribute("initials"));
533
+ const src = esc(this.getAttribute("src"));
534
+ const suspended = this.hasAttribute("suspended");
535
+ const avatarInner = src
536
+ ? html `<img src="${src}" alt="${name}" />`
537
+ : html `<span aria-hidden="true">${initials}</span>`;
538
+ return html `<div class="row" part="row" role="listitem" aria-label="${name}${suspended ? " — suspended" : ""}">
539
+ <span class="avatar" part="avatar" role="img" aria-label="${name}">${avatarInner}</span>
540
+ <div class="body">
541
+ <p class="name">${name}</p>
542
+ ${email ? html `<p class="email">${email}</p>` : ""}
543
+ </div>
544
+ <div class="meta">
545
+ ${suspended ? html `<span class="chip suspended">Suspended</span>` : ""}
546
+ ${role ? html `<span class="chip" part="role">${role}</span>` : ""}
547
+ <span class="actions"><slot name="actions"></slot></span>
548
+ </div>
549
+ </div>`;
550
+ }
551
+ }
552
+ NptUserRow.observedAttributes = ["name", "email", "role", "initials", "src", "suspended"];
553
+ /**
554
+ * <npt-permission-toggle label="Approve payments"
555
+ * description="Allow this role to release outgoing transfers" [checked] [disabled]>
556
+ * </npt-permission-toggle>
557
+ * Label + description + a switch-like toggle. Emits `change` when toggled.
558
+ */
559
+ export class NptPermissionToggle extends NptElement {
560
+ constructor() {
561
+ super(...arguments);
562
+ this.onClick = () => this.toggle();
563
+ this.onKey = (e) => {
564
+ if (e.key === " " || e.key === "Enter") {
565
+ e.preventDefault();
566
+ this.toggle();
567
+ }
568
+ };
569
+ }
570
+ attributeChangedCallback() {
571
+ if (this.isConnected)
572
+ this.update();
573
+ }
574
+ connectedCallback() {
575
+ super.connectedCallback();
576
+ this.addEventListener("click", this.onClick);
577
+ this.addEventListener("keydown", this.onKey);
578
+ }
579
+ disconnectedCallback() {
580
+ this.removeEventListener("click", this.onClick);
581
+ this.removeEventListener("keydown", this.onKey);
582
+ }
583
+ toggle() {
584
+ if (this.hasAttribute("disabled"))
585
+ return;
586
+ this.toggleAttribute("checked");
587
+ this.dispatchEvent(new CustomEvent("change", { bubbles: true }));
588
+ }
589
+ styles() {
590
+ return css `
591
+ ${A11Y}
592
+ :host {
593
+ display: block;
594
+ cursor: pointer;
595
+ }
596
+ :host([disabled]) {
597
+ cursor: not-allowed;
598
+ opacity: 0.38;
599
+ }
600
+ .wrap {
601
+ display: flex;
602
+ align-items: center;
603
+ gap: var(--npt-space-4, 16px);
604
+ min-height: 48px;
605
+ padding-block: var(--npt-space-2, 8px);
606
+ }
607
+ .body {
608
+ flex: 1 1 auto;
609
+ min-inline-size: 0;
610
+ }
611
+ .label {
612
+ font-family: var(--npt-font-text);
613
+ font-size: var(--npt-text-body-lg, 16px);
614
+ color: var(--md-sys-color-on-surface);
615
+ margin: 0;
616
+ }
617
+ .description {
618
+ font-family: var(--npt-font-text);
619
+ font-size: var(--npt-text-body, 14px);
620
+ color: var(--md-sys-color-on-surface-variant);
621
+ margin: 2px 0 0;
622
+ }
623
+ .track {
624
+ flex: 0 0 auto;
625
+ inline-size: 52px;
626
+ block-size: 32px;
627
+ border-radius: var(--npt-corner-full, 999px);
628
+ background: var(--md-sys-color-surface-container-highest);
629
+ border: 2px solid var(--md-sys-color-outline);
630
+ position: relative;
631
+ box-sizing: border-box;
632
+ transition: background-color var(--npt-dur-fast, 200ms) var(--npt-ease-standard, ease),
633
+ border-color var(--npt-dur-fast, 200ms) var(--npt-ease-standard, ease);
634
+ }
635
+ .thumb {
636
+ position: absolute;
637
+ inset-block-start: 50%;
638
+ inset-inline-start: 6px;
639
+ inline-size: 16px;
640
+ block-size: 16px;
641
+ border-radius: var(--npt-corner-full, 999px);
642
+ background: var(--md-sys-color-outline);
643
+ transform: translateY(-50%);
644
+ transition: inset-inline-start var(--npt-dur-fast, 200ms) var(--npt-ease-spring, ease),
645
+ inline-size var(--npt-dur-fast, 200ms) var(--npt-ease-standard, ease),
646
+ background-color var(--npt-dur-fast, 200ms) var(--npt-ease-standard, ease);
647
+ }
648
+ :host([checked]) .track {
649
+ background: var(--md-sys-color-primary);
650
+ border-color: var(--md-sys-color-primary);
651
+ }
652
+ :host([checked]) .thumb {
653
+ inset-inline-start: 26px;
654
+ inline-size: 22px;
655
+ block-size: 22px;
656
+ background: var(--md-sys-color-on-primary);
657
+ }
658
+ `;
659
+ }
660
+ render() {
661
+ const label = esc(this.getAttribute("label"));
662
+ const description = esc(this.getAttribute("description"));
663
+ const checked = this.hasAttribute("checked");
664
+ return html `<div class="wrap" part="wrap">
665
+ <div class="body">
666
+ <p class="label">${label}</p>
667
+ ${description ? html `<p class="description">${description}</p>` : ""}
668
+ </div>
669
+ <span
670
+ class="track"
671
+ part="track"
672
+ role="switch"
673
+ aria-checked="${checked}"
674
+ aria-label="${label}"
675
+ tabindex="0"
676
+ ><span class="thumb" aria-hidden="true"></span></span>
677
+ </div>`;
678
+ }
679
+ }
680
+ NptPermissionToggle.observedAttributes = ["label", "description", "checked", "disabled"];
681
+ /**
682
+ * <npt-workflow-status steps="Submitted,Checked,Approved" active="1"></npt-workflow-status>
683
+ * A compact multi-step status indicator. `active` is the zero-based index of the
684
+ * current step; earlier steps render as complete, later steps as upcoming.
685
+ */
686
+ export class NptWorkflowStatus extends NptElement {
687
+ attributeChangedCallback() {
688
+ if (this.isConnected)
689
+ this.update();
690
+ }
691
+ styles() {
692
+ return css `
693
+ ${A11Y}
694
+ :host {
695
+ display: block;
696
+ }
697
+ .flow {
698
+ display: flex;
699
+ align-items: center;
700
+ gap: var(--npt-space-2, 8px);
701
+ flex-wrap: wrap;
702
+ }
703
+ .step {
704
+ display: inline-flex;
705
+ align-items: center;
706
+ gap: var(--npt-space-2, 8px);
707
+ font-family: var(--npt-font-text);
708
+ font-size: var(--npt-text-label, 14px);
709
+ }
710
+ .marker {
711
+ inline-size: 24px;
712
+ block-size: 24px;
713
+ flex: 0 0 auto;
714
+ border-radius: var(--npt-corner-full, 999px);
715
+ display: grid;
716
+ place-items: center;
717
+ font-family: var(--npt-font-num);
718
+ font-variant-numeric: tabular-nums;
719
+ font-size: var(--npt-text-caption, 12px);
720
+ font-weight: 600;
721
+ background: var(--md-sys-color-surface-container-highest);
722
+ color: var(--md-sys-color-on-surface-variant);
723
+ border: 2px solid transparent;
724
+ }
725
+ .name {
726
+ color: var(--md-sys-color-on-surface-variant);
727
+ }
728
+ .step.done .marker {
729
+ background: var(--md-sys-color-success);
730
+ color: var(--md-sys-color-on-success);
731
+ }
732
+ .step.active .marker {
733
+ background: var(--md-sys-color-primary);
734
+ color: var(--md-sys-color-on-primary);
735
+ }
736
+ .step.active .name {
737
+ color: var(--md-sys-color-on-surface);
738
+ font-weight: 600;
739
+ }
740
+ .connector {
741
+ flex: 0 0 auto;
742
+ inline-size: 16px;
743
+ block-size: 2px;
744
+ border-radius: var(--npt-corner-full, 999px);
745
+ background: var(--md-sys-color-outline-variant);
746
+ }
747
+ .connector.filled {
748
+ background: var(--md-sys-color-success);
749
+ }
750
+ `;
751
+ }
752
+ render() {
753
+ const raw = this.getAttribute("steps") ?? "";
754
+ const steps = raw
755
+ .split(",")
756
+ .map((s) => s.trim())
757
+ .filter((s) => s.length > 0);
758
+ const active = Math.max(0, Math.min(steps.length - 1, Number(this.getAttribute("active") ?? 0)));
759
+ const activeLabel = steps[active] ?? "";
760
+ const body = steps
761
+ .map((name, i) => {
762
+ const state = i < active ? "done" : i === active ? "active" : "upcoming";
763
+ const mark = i < active ? "✓" : String(i + 1);
764
+ const connector = i < steps.length - 1
765
+ ? html `<span class="connector ${i < active ? "filled" : ""}" aria-hidden="true"></span>`
766
+ : "";
767
+ return html `<span class="step ${state}">
768
+ <span class="marker" aria-hidden="true">${mark}</span>
769
+ <span class="name">${esc(name)}</span>
770
+ </span>${connector}`;
771
+ })
772
+ .join("");
773
+ return html `<div
774
+ class="flow"
775
+ part="flow"
776
+ role="group"
777
+ aria-label="Workflow: ${esc(activeLabel)} (step ${active + 1} of ${steps.length})"
778
+ >${body}</div>`;
779
+ }
780
+ }
781
+ NptWorkflowStatus.observedAttributes = ["steps", "active"];
782
+ //# sourceMappingURL=corporate.js.map