@dismissible/react-client 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/README.md +55 -195
- package/dist/config/api.config.d.ts +0 -14
- package/dist/dismissible-client.es.js +205 -208
- package/dist/dismissible-client.umd.js +1 -1
- package/dist/hooks/useDismissibleItem.d.ts +16 -14
- package/dist/root.d.ts +0 -5
- package/package.json +17 -16
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# [2.0.0](https://github.com/DismissibleIo/dismissible-react-client/compare/v1.0.0...v2.0.0) (2026-01-06)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **test:** fixed test race condition ([#6](https://github.com/DismissibleIo/dismissible-react-client/issues/6)) ([da56324](https://github.com/DismissibleIo/dismissible-react-client/commit/da5632458c61a734d7bd55c89ef28d78afeabbf4))
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* **api:** aligned with api response ([#5](https://github.com/DismissibleIo/dismissible-react-client/issues/5)) ([8fe9f87](https://github.com/DismissibleIo/dismissible-react-client/commit/8fe9f879691f88cb761a8b4293c824a6d4477db2))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### BREAKING CHANGES
|
|
15
|
+
|
|
16
|
+
* **api:** You need to update any of your hook consumers to check dismissedAt instead of
|
|
17
|
+
dismissedOn. This aligns with the API response.
|
|
18
|
+
|
|
19
|
+
# 1.0.0 (2025-12-21)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Features
|
|
23
|
+
|
|
24
|
+
* **react-client:** dismissible React Client ([#1](https://github.com/DismissibleIo/dismissible-react-client/issues/1)) ([a4e6dc7](https://github.com/DismissibleIo/dismissible-react-client/commit/a4e6dc7786a4fd1abb81f4a796e22d37fdc12e0b))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### BREAKING CHANGES
|
|
28
|
+
|
|
29
|
+
* **react-client:** This is the first public release which requires both the DismissibleProvider and
|
|
30
|
+
Dismissible components to be used to force a userId.
|
package/README.md
CHANGED
|
@@ -1,26 +1,37 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://dismissible.io" target="_blank"><img src="https://raw.githubusercontent.com/DismissibleIo/dismissible-api/main/docs/images/dismissible_logo.png" width="240" alt="Dismissible" /></a>
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">Never Show The Same Thing Twice!</p>
|
|
6
|
+
<p align="center">
|
|
7
|
+
<a href="https://www.npmjs.com/package/@dismissible/react-client" target="_blank"><img src="https://img.shields.io/npm/v/@dismissible/react-client.svg" alt="NPM Version" /></a>
|
|
8
|
+
<a href="https://github.com/dismissibleio/dismissible-react-client/blob/main/LICENSE" target="_blank"><img src="https://img.shields.io/npm/l/@dismissible/react-client.svg" alt="Package License" /></a>
|
|
9
|
+
<a href="https://www.npmjs.com/package/@dismissible/react-client" target="_blank"><img src="https://img.shields.io/npm/dm/@dismissible/react-client.svg" alt="NPM Downloads" /></a>
|
|
10
|
+
<a href="https://github.com/dismissibleio/dismissible-react-client" target="_blank"><img alt="GitHub Actions Workflow Status" src="https://img.shields.io/github/actions/workflow/status/dismissibleio/dismissible-react-client/publish.yml" /></a>
|
|
11
|
+
<a href="https://paypal.me/joshstuartx" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg" /></a>
|
|
12
|
+
</p>
|
|
2
13
|
|
|
3
|
-
|
|
14
|
+
Dismissible manages the state of your UI elements across sessions, so your users see what matters, once! No more onboarding messages reappearing on every tab, no more notifications haunting users across devices. Dismissible syncs dismissal state everywhere, so every message is intentional, never repetitive.
|
|
4
15
|
|
|
5
|
-
|
|
16
|
+
# @dismissible/react-client
|
|
6
17
|
|
|
7
|
-
|
|
18
|
+
This is the React component library for creating dismissible UI elements with persistent state management.
|
|
8
19
|
|
|
9
|
-
[
|
|
10
|
-
|
|
20
|
+
This component is used with the [Dismissible API Server](https://github.com/DismissibleIo/dismissible-api), which you can self-host with Docker or integrate into your NestJS application.
|
|
21
|
+
|
|
22
|
+
**[dismissible.io](https://dismissible.io)** | **[Documentation](https://dismissible.io/docs)** | **[API Server](https://github.com/DismissibleIo/dismissible-api)**
|
|
11
23
|
|
|
12
24
|
## Features
|
|
13
25
|
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
- 🐳 **Self-hosted** - Works with your own Dismissible API server
|
|
26
|
+
- **Easy to use** - Simple component API for dismissible content
|
|
27
|
+
- **Persistent state** - Dismissal state is saved and restored across sessions when using the [Dismissible API Server](https://github.com/DismissibleIo/dismissible-api)
|
|
28
|
+
- **Restore support** - Restore previously dismissed items programmatically
|
|
29
|
+
- **JWT Authentication** - Built-in support for secure JWT-based authentication
|
|
30
|
+
- **Customizable** - Custom loading, error, and dismiss button components
|
|
31
|
+
- **Accessible** - Built with accessibility best practices
|
|
32
|
+
- **Hook-based** - Includes `useDismissibleItem` hook for custom implementations
|
|
33
|
+
- **Lightweight** - Minimal bundle size with tree-shaking support
|
|
34
|
+
- **TypeScript** - Full TypeScript support with complete type definitions
|
|
24
35
|
|
|
25
36
|
## Installation
|
|
26
37
|
|
|
@@ -40,7 +51,7 @@ npm install react react-dom
|
|
|
40
51
|
|
|
41
52
|
### 1. Set up the Dismissible API Server
|
|
42
53
|
|
|
43
|
-
First, you need a Dismissible API
|
|
54
|
+
First, you need a [Dismissible API Server](https://github.com/DismissibleIo/dismissible-api). The easiest way is with Docker:
|
|
44
55
|
|
|
45
56
|
```yaml
|
|
46
57
|
# docker-compose.yml
|
|
@@ -52,38 +63,29 @@ services:
|
|
|
52
63
|
- '3001:3001'
|
|
53
64
|
environment:
|
|
54
65
|
DISMISSIBLE_PORT: 3001
|
|
55
|
-
DISMISSIBLE_POSTGRES_STORAGE_CONNECTION_STRING: postgresql://postgres:postgres@db:5432/dismissible
|
|
56
|
-
depends_on:
|
|
57
|
-
- db
|
|
58
|
-
|
|
59
|
-
db:
|
|
60
|
-
image: postgres:15
|
|
61
|
-
environment:
|
|
62
|
-
POSTGRES_USER: postgres
|
|
63
|
-
POSTGRES_PASSWORD: postgres
|
|
64
|
-
POSTGRES_DB: dismissible
|
|
65
|
-
volumes:
|
|
66
|
-
- postgres_data:/var/lib/postgresql/data
|
|
67
|
-
|
|
68
|
-
volumes:
|
|
69
|
-
postgres_data:
|
|
70
66
|
```
|
|
71
67
|
|
|
72
68
|
```bash
|
|
73
69
|
docker-compose up -d
|
|
74
70
|
```
|
|
75
71
|
|
|
76
|
-
|
|
72
|
+
OR
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
docker run -p 3001:3001 -e DISMISSIBLE_PORT=3001 dismissibleio/dismissible-api:latest
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
See the [API Server documentation](https://github.com/DismissibleIo/dismissible-api) for more deployment options including NestJS integration, public Docker image and more.
|
|
77
79
|
|
|
78
80
|
### 2. Configure the Provider
|
|
79
81
|
|
|
80
|
-
Wrap your app with `DismissibleProvider`. The `userId` prop is **required** to track dismissals per user:
|
|
82
|
+
Wrap your app with `DismissibleProvider`. The `userId` prop is **required** to track all your dismissals per user:
|
|
81
83
|
|
|
82
84
|
```tsx
|
|
83
85
|
import { DismissibleProvider } from '@dismissible/react-client';
|
|
84
86
|
|
|
85
87
|
function App() {
|
|
86
|
-
const userId = getCurrentUserId();
|
|
88
|
+
const userId = getCurrentUserId();
|
|
87
89
|
|
|
88
90
|
return (
|
|
89
91
|
<DismissibleProvider userId={userId} baseUrl="http://localhost:3001">
|
|
@@ -94,6 +96,7 @@ function App() {
|
|
|
94
96
|
```
|
|
95
97
|
|
|
96
98
|
### 3. Use Dismissible Components
|
|
99
|
+
Now wrap any component you want to be dismissible with the `<Dismissible>` component, and the `itemId`, along with the `userId`, will become the unique key that is tracked across sessions and devices.
|
|
97
100
|
|
|
98
101
|
```tsx
|
|
99
102
|
import { Dismissible } from '@dismissible/react-client';
|
|
@@ -242,11 +245,11 @@ For custom implementations and advanced use cases.
|
|
|
242
245
|
|
|
243
246
|
| Property | Type | Description |
|
|
244
247
|
|----------|------|-------------|
|
|
245
|
-
| `
|
|
248
|
+
| `dismissedAt` | `string \| undefined` | ISO date string when item was dismissed, or undefined |
|
|
246
249
|
| `dismiss` | `() => Promise<void>` | Function to dismiss the item |
|
|
247
250
|
| `restore` | `() => Promise<void>` | Function to restore a dismissed item |
|
|
248
251
|
| `isLoading` | `boolean` | Loading state indicator |
|
|
249
|
-
| `error` | `Error \|
|
|
252
|
+
| `error` | `Error \| undefined` | Error state, if any |
|
|
250
253
|
| `item` | `IDismissibleItem \| undefined` | The full dismissible item object |
|
|
251
254
|
|
|
252
255
|
#### Example
|
|
@@ -255,7 +258,7 @@ For custom implementations and advanced use cases.
|
|
|
255
258
|
import { useDismissibleItem } from '@dismissible/react-client';
|
|
256
259
|
|
|
257
260
|
function CustomDismissible({ itemId, children }) {
|
|
258
|
-
const {
|
|
261
|
+
const { dismissedAt, dismiss, restore, isLoading, error } = useDismissibleItem(itemId);
|
|
259
262
|
|
|
260
263
|
if (isLoading) {
|
|
261
264
|
return <div>Loading...</div>;
|
|
@@ -265,7 +268,7 @@ function CustomDismissible({ itemId, children }) {
|
|
|
265
268
|
return <div>Error: {error.message}</div>;
|
|
266
269
|
}
|
|
267
270
|
|
|
268
|
-
if (
|
|
271
|
+
if (dismissedAt) {
|
|
269
272
|
return (
|
|
270
273
|
<div>
|
|
271
274
|
<p>This item was dismissed.</p>
|
|
@@ -338,7 +341,6 @@ function App() {
|
|
|
338
341
|
function Dashboard() {
|
|
339
342
|
return (
|
|
340
343
|
<div>
|
|
341
|
-
{/* Dismissible state is tracked per user */}
|
|
342
344
|
<Dismissible itemId="user-welcome-banner">
|
|
343
345
|
<div className="alert alert-info">
|
|
344
346
|
<h4>Welcome back!</h4>
|
|
@@ -427,19 +429,19 @@ function Dashboard() {
|
|
|
427
429
|
<div>
|
|
428
430
|
<Dismissible itemId="feature-announcement">
|
|
429
431
|
<div className="alert alert-success">
|
|
430
|
-
|
|
432
|
+
New feature: Dark mode is now available!
|
|
431
433
|
</div>
|
|
432
434
|
</Dismissible>
|
|
433
435
|
|
|
434
436
|
<Dismissible itemId="maintenance-notice">
|
|
435
437
|
<div className="alert alert-warning">
|
|
436
|
-
|
|
438
|
+
Scheduled maintenance: Sunday 2AM-4AM EST
|
|
437
439
|
</div>
|
|
438
440
|
</Dismissible>
|
|
439
441
|
|
|
440
442
|
<Dismissible itemId="survey-request">
|
|
441
443
|
<div className="alert alert-info">
|
|
442
|
-
|
|
444
|
+
Help us improve! Take our 2-minute survey.
|
|
443
445
|
</div>
|
|
444
446
|
</Dismissible>
|
|
445
447
|
</div>
|
|
@@ -468,74 +470,6 @@ function RobustBanner() {
|
|
|
468
470
|
}
|
|
469
471
|
```
|
|
470
472
|
|
|
471
|
-
### Integration with Auth Providers
|
|
472
|
-
|
|
473
|
-
```tsx
|
|
474
|
-
import { DismissibleProvider } from '@dismissible/react-client';
|
|
475
|
-
|
|
476
|
-
// With Firebase Auth
|
|
477
|
-
function AppWithFirebase() {
|
|
478
|
-
const user = firebase.auth().currentUser;
|
|
479
|
-
|
|
480
|
-
return (
|
|
481
|
-
<DismissibleProvider
|
|
482
|
-
userId={user.uid}
|
|
483
|
-
jwt={async () => {
|
|
484
|
-
if (user) {
|
|
485
|
-
return await user.getIdToken();
|
|
486
|
-
}
|
|
487
|
-
throw new Error('User not authenticated');
|
|
488
|
-
}}
|
|
489
|
-
baseUrl="https://api.yourapp.com"
|
|
490
|
-
>
|
|
491
|
-
<YourApp />
|
|
492
|
-
</DismissibleProvider>
|
|
493
|
-
);
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
// With Auth0
|
|
497
|
-
function AppWithAuth0() {
|
|
498
|
-
const { user, getAccessTokenSilently } = useAuth0();
|
|
499
|
-
|
|
500
|
-
return (
|
|
501
|
-
<DismissibleProvider
|
|
502
|
-
userId={user.sub}
|
|
503
|
-
jwt={async () => await getAccessTokenSilently()}
|
|
504
|
-
baseUrl="https://api.yourapp.com"
|
|
505
|
-
>
|
|
506
|
-
<YourApp />
|
|
507
|
-
</DismissibleProvider>
|
|
508
|
-
);
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
// With token refresh logic
|
|
512
|
-
function AppWithTokenRefresh() {
|
|
513
|
-
const { user } = useAuth();
|
|
514
|
-
|
|
515
|
-
return (
|
|
516
|
-
<DismissibleProvider
|
|
517
|
-
userId={user.id}
|
|
518
|
-
jwt={async () => {
|
|
519
|
-
try {
|
|
520
|
-
const response = await fetch('/api/auth/refresh', {
|
|
521
|
-
method: 'POST',
|
|
522
|
-
credentials: 'include'
|
|
523
|
-
});
|
|
524
|
-
const { accessToken } = await response.json();
|
|
525
|
-
return accessToken;
|
|
526
|
-
} catch (error) {
|
|
527
|
-
console.error('Failed to refresh token:', error);
|
|
528
|
-
throw error;
|
|
529
|
-
}
|
|
530
|
-
}}
|
|
531
|
-
baseUrl="https://api.yourapp.com"
|
|
532
|
-
>
|
|
533
|
-
<YourApp />
|
|
534
|
-
</DismissibleProvider>
|
|
535
|
-
);
|
|
536
|
-
}
|
|
537
|
-
```
|
|
538
|
-
|
|
539
473
|
### Using the Hook for Complex Logic
|
|
540
474
|
|
|
541
475
|
```tsx
|
|
@@ -543,12 +477,12 @@ import { useDismissibleItem } from '@dismissible/react-client';
|
|
|
543
477
|
import { useState, useEffect } from 'react';
|
|
544
478
|
|
|
545
479
|
function SmartNotification({ itemId, message, type = 'info' }) {
|
|
546
|
-
const {
|
|
480
|
+
const { dismissedAt, dismiss, isLoading } = useDismissibleItem(itemId);
|
|
547
481
|
const [autoHide, setAutoHide] = useState(false);
|
|
548
482
|
|
|
549
483
|
// Auto-hide after 10 seconds for info messages
|
|
550
484
|
useEffect(() => {
|
|
551
|
-
if (type === 'info' && !
|
|
485
|
+
if (type === 'info' && !dismissedAt) {
|
|
552
486
|
const timer = setTimeout(() => {
|
|
553
487
|
setAutoHide(true);
|
|
554
488
|
dismiss();
|
|
@@ -556,9 +490,9 @@ function SmartNotification({ itemId, message, type = 'info' }) {
|
|
|
556
490
|
|
|
557
491
|
return () => clearTimeout(timer);
|
|
558
492
|
}
|
|
559
|
-
}, [type,
|
|
493
|
+
}, [type, dismissedAt, dismiss]);
|
|
560
494
|
|
|
561
|
-
if (
|
|
495
|
+
if (dismissedAt || autoHide) {
|
|
562
496
|
return null;
|
|
563
497
|
}
|
|
564
498
|
|
|
@@ -585,12 +519,12 @@ Use the `restore` function to bring back previously dismissed content:
|
|
|
585
519
|
import { useDismissibleItem } from '@dismissible/react-client';
|
|
586
520
|
|
|
587
521
|
function RestorableBanner({ itemId }) {
|
|
588
|
-
const {
|
|
522
|
+
const { dismissedAt, dismiss, restore, isLoading } = useDismissibleItem(itemId);
|
|
589
523
|
|
|
590
|
-
if (
|
|
524
|
+
if (dismissedAt) {
|
|
591
525
|
return (
|
|
592
526
|
<div className="dismissed-placeholder">
|
|
593
|
-
<p>Banner was dismissed on {new Date(
|
|
527
|
+
<p>Banner was dismissed on {new Date(dismissedAt).toLocaleDateString()}</p>
|
|
594
528
|
<button onClick={restore} disabled={isLoading}>
|
|
595
529
|
{isLoading ? 'Restoring...' : 'Show Banner Again'}
|
|
596
530
|
</button>
|
|
@@ -610,51 +544,6 @@ function RestorableBanner({ itemId }) {
|
|
|
610
544
|
}
|
|
611
545
|
```
|
|
612
546
|
|
|
613
|
-
### Admin Panel with Restore Capability
|
|
614
|
-
|
|
615
|
-
```tsx
|
|
616
|
-
import { useDismissibleItem } from '@dismissible/react-client';
|
|
617
|
-
|
|
618
|
-
function AdminNotificationManager({ notificationId }) {
|
|
619
|
-
const { dismissedOn, dismiss, restore, item, isLoading, error } =
|
|
620
|
-
useDismissibleItem(notificationId);
|
|
621
|
-
|
|
622
|
-
if (error) {
|
|
623
|
-
return <div className="error">Error: {error.message}</div>;
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
return (
|
|
627
|
-
<div className="admin-panel">
|
|
628
|
-
<h4>Notification: {notificationId}</h4>
|
|
629
|
-
<p>Status: {dismissedOn ? 'Dismissed' : 'Active'}</p>
|
|
630
|
-
{dismissedOn && (
|
|
631
|
-
<p>Dismissed at: {new Date(dismissedOn).toLocaleString()}</p>
|
|
632
|
-
)}
|
|
633
|
-
|
|
634
|
-
<div className="actions">
|
|
635
|
-
{dismissedOn ? (
|
|
636
|
-
<button
|
|
637
|
-
onClick={restore}
|
|
638
|
-
disabled={isLoading}
|
|
639
|
-
className="btn-restore"
|
|
640
|
-
>
|
|
641
|
-
Restore
|
|
642
|
-
</button>
|
|
643
|
-
) : (
|
|
644
|
-
<button
|
|
645
|
-
onClick={dismiss}
|
|
646
|
-
disabled={isLoading}
|
|
647
|
-
className="btn-dismiss"
|
|
648
|
-
>
|
|
649
|
-
Dismiss
|
|
650
|
-
</button>
|
|
651
|
-
)}
|
|
652
|
-
</div>
|
|
653
|
-
</div>
|
|
654
|
-
);
|
|
655
|
-
}
|
|
656
|
-
```
|
|
657
|
-
|
|
658
547
|
## Styling
|
|
659
548
|
|
|
660
549
|
The library includes minimal default styles. You can override them or provide your own:
|
|
@@ -678,35 +567,6 @@ The library includes minimal default styles. You can override them or provide yo
|
|
|
678
567
|
}
|
|
679
568
|
```
|
|
680
569
|
|
|
681
|
-
## TypeScript Support
|
|
682
|
-
|
|
683
|
-
The library is written in TypeScript and exports all type definitions:
|
|
684
|
-
|
|
685
|
-
```tsx
|
|
686
|
-
import type {
|
|
687
|
-
DismissibleProps,
|
|
688
|
-
DismissibleProviderProps,
|
|
689
|
-
JwtToken,
|
|
690
|
-
} from '@dismissible/react-client';
|
|
691
|
-
|
|
692
|
-
// Custom provider wrapper
|
|
693
|
-
const AuthenticatedDismissibleProvider: React.FC<{
|
|
694
|
-
children: React.ReactNode;
|
|
695
|
-
}> = ({ children }) => {
|
|
696
|
-
const { user, getToken } = useAuth();
|
|
697
|
-
|
|
698
|
-
return (
|
|
699
|
-
<DismissibleProvider
|
|
700
|
-
userId={user.id}
|
|
701
|
-
jwt={getToken}
|
|
702
|
-
baseUrl={process.env.DISMISSIBLE_API_URL}
|
|
703
|
-
>
|
|
704
|
-
{children}
|
|
705
|
-
</DismissibleProvider>
|
|
706
|
-
);
|
|
707
|
-
};
|
|
708
|
-
```
|
|
709
|
-
|
|
710
570
|
## Self-Hosting
|
|
711
571
|
|
|
712
572
|
Dismissible is designed to be self-hosted. You have full control over your data.
|
|
@@ -733,9 +593,9 @@ See the [NestJS documentation](https://dismissible.io/docs/nestjs) for setup ins
|
|
|
733
593
|
|
|
734
594
|
## Support
|
|
735
595
|
|
|
736
|
-
-
|
|
737
|
-
-
|
|
738
|
-
-
|
|
596
|
+
- [Documentation](https://dismissible.io/docs)
|
|
597
|
+
- [GitHub - React Client](https://github.com/DismissibleIo/dismissible-react-client)
|
|
598
|
+
- [GitHub - API Server](https://github.com/DismissibleIo/dismissible-api)
|
|
739
599
|
|
|
740
600
|
## License
|
|
741
601
|
|
|
@@ -1,21 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* API configuration settings
|
|
3
|
-
*/
|
|
4
|
-
/**
|
|
5
|
-
* Configuration interface for API settings
|
|
6
|
-
*/
|
|
7
1
|
export interface ApiConfig {
|
|
8
2
|
/** Base URL for API requests */
|
|
9
3
|
baseUrl: string;
|
|
10
4
|
}
|
|
11
|
-
/**
|
|
12
|
-
* Returns the API configuration based on the current environment
|
|
13
|
-
* @returns The API configuration object
|
|
14
|
-
*/
|
|
15
5
|
export declare const getConfig: () => ApiConfig;
|
|
16
|
-
/**
|
|
17
|
-
* Gets the API base URL
|
|
18
|
-
* @returns The base URL for API requests
|
|
19
|
-
*/
|
|
20
6
|
export declare const getBaseUrl: () => string;
|
|
21
7
|
export default getConfig;
|