@hermesihq/react 0.2.0 → 0.2.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/CHANGELOG.md +138 -120
- package/README.md +122 -120
- package/dist/index.cjs +9 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +13 -6
- package/dist/index.css.map +1 -1
- package/dist/index.js +10 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,120 +1,138 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to `@hermesihq/react`.
|
|
4
|
-
|
|
5
|
-
This file describes what a consumer gets, not how it was built. The repository's
|
|
6
|
-
history is where the reasoning lives.
|
|
7
|
-
|
|
8
|
-
## Versioning
|
|
9
|
-
|
|
10
|
-
**`0.x` means the public API can still change.** A minor bump (`0.1` → `0.2`) may
|
|
11
|
-
contain a breaking change; a patch bump will not. That is the ordinary `0.x` reading
|
|
12
|
-
of semver and it is chosen deliberately rather than by default: this SDK's shape is
|
|
13
|
-
still being learned from the first integrations, and promising stability before anyone
|
|
14
|
-
has built against it would be a promise made to nobody and broken later.
|
|
15
|
-
|
|
16
|
-
`1.0.0` is the commitment that a breaking change requires a major bump. It waits until
|
|
17
|
-
at least one real integration exists.
|
|
18
|
-
|
|
19
|
-
Each release lists breaking changes first, because that is the only section that
|
|
20
|
-
decides whether an upgrade is a decision or a formality.
|
|
21
|
-
|
|
22
|
-
## 0.2.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
-
|
|
119
|
-
|
|
120
|
-
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@hermesihq/react`.
|
|
4
|
+
|
|
5
|
+
This file describes what a consumer gets, not how it was built. The repository's
|
|
6
|
+
history is where the reasoning lives.
|
|
7
|
+
|
|
8
|
+
## Versioning
|
|
9
|
+
|
|
10
|
+
**`0.x` means the public API can still change.** A minor bump (`0.1` → `0.2`) may
|
|
11
|
+
contain a breaking change; a patch bump will not. That is the ordinary `0.x` reading
|
|
12
|
+
of semver and it is chosen deliberately rather than by default: this SDK's shape is
|
|
13
|
+
still being learned from the first integrations, and promising stability before anyone
|
|
14
|
+
has built against it would be a promise made to nobody and broken later.
|
|
15
|
+
|
|
16
|
+
`1.0.0` is the commitment that a breaking change requires a major bump. It waits until
|
|
17
|
+
at least one real integration exists.
|
|
18
|
+
|
|
19
|
+
Each release lists breaking changes first, because that is the only section that
|
|
20
|
+
decides whether an upgrade is a decision or a formality.
|
|
21
|
+
|
|
22
|
+
## 0.2.1 (2026-10-01)
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- **The panel had no styling.** `<HermsInbox />` renders its panel in a portal under `<body>`, so
|
|
27
|
+
the panel is not inside the bell's root, and every colour variable was defined only on that
|
|
28
|
+
root. Measured in a browser on 0.2.0: the panel was transparent, had no border and square
|
|
29
|
+
corners, and its text stayed black in dark mode. The `theme` and `colorScheme` props put their
|
|
30
|
+
values on the root, so they could never reach the panel either. The panel now carries the
|
|
31
|
+
variables, the forced colour scheme and the `theme` values itself, and forcing light mode on a
|
|
32
|
+
dark system works. If you worked around this with your own CSS on `.herms-inbox__panel`, it may
|
|
33
|
+
now be redundant.
|
|
34
|
+
- `className` is now applied to the panel as well as to the bell, so one rule themes both.
|
|
35
|
+
- The panel is sized with `box-sizing: border-box`. Its 380px width now includes its border; it
|
|
36
|
+
used to render at 382px.
|
|
37
|
+
- The dialog has an accessible name, taken from its title. It used to be announced as just
|
|
38
|
+
"dialog".
|
|
39
|
+
|
|
40
|
+
## 0.2.0 (2026-09-30)
|
|
41
|
+
|
|
42
|
+
No breaking changes, and nothing to change in your code: everything this package exported
|
|
43
|
+
before it exports now, from the same place.
|
|
44
|
+
|
|
45
|
+
**The client and the state moved into a new package, `@hermesihq/js`, which this one now
|
|
46
|
+
depends on.** The list, count and preference state that lived inside the hooks is now held
|
|
47
|
+
in framework-free stores, and the hooks are a thin binding over them. That is what lets a
|
|
48
|
+
Vue, Angular or plain JavaScript page use the same code. You do not need to install or
|
|
49
|
+
import `@hermesihq/js` yourself; `HermsClient`, `HermsApiError`, `HERMS_CHANNELS` and the
|
|
50
|
+
rest are still exported from here. If you also import `@hermesihq/js` directly, npm
|
|
51
|
+
installs one shared copy, so both see the same `HermsClient` class.
|
|
52
|
+
|
|
53
|
+
Four things a consumer can notice:
|
|
54
|
+
|
|
55
|
+
- **A further page for the previous filter no longer lands in the new list.** With
|
|
56
|
+
`useInbox`, switching tabs while "load more" was in flight could append the old tab's
|
|
57
|
+
rows to the new tab's list. Any response that arrives after the list was reloaded, the
|
|
58
|
+
filter changed or the hook unmounted is now discarded.
|
|
59
|
+
- **A load's result and its loading flag now change together.** The rows, or the error,
|
|
60
|
+
used to be published one update before `isLoading` cleared, so a component that
|
|
61
|
+
renders on every update could briefly show "failed and still loading" or "here are
|
|
62
|
+
your rows and still loading".
|
|
63
|
+
- **The functions `useInbox` and `usePreferences` return keep their identity.**
|
|
64
|
+
`loadMore`, `markRead`, `archive` and the rest used to be recreated whenever
|
|
65
|
+
`isLoadingMore` or `hasMore` changed, so a `useCallback` or `memo` listing one re-ran
|
|
66
|
+
on every page.
|
|
67
|
+
- **Two providers over one client no longer close each other's connection.** The
|
|
68
|
+
real-time stream is reference-counted: it opens for the first and closes with the last.
|
|
69
|
+
|
|
70
|
+
A listener that throws is now reported to the host without stopping the other listeners
|
|
71
|
+
or corrupting the state it was notified about.
|
|
72
|
+
|
|
73
|
+
## 0.1.0 (2026-09-28)
|
|
74
|
+
|
|
75
|
+
The first published release. Nothing precedes it, so there is nothing to migrate from
|
|
76
|
+
and no deprecations; the entries below describe the surface rather than changes to one.
|
|
77
|
+
|
|
78
|
+
### The inbox
|
|
79
|
+
|
|
80
|
+
- `HermsClient`: the framework-agnostic core. Reads and writes the subscriber's inbox,
|
|
81
|
+
opens a realtime stream for new items and unread counts, and falls back to polling
|
|
82
|
+
when the runtime has no `EventSource` or the stream keeps failing. It imports nothing
|
|
83
|
+
from React, so a Vue or vanilla wrapper is a thin layer rather than a second
|
|
84
|
+
implementation.
|
|
85
|
+
- `HermsProvider` / `useHermsContext`: one shared connection per provider, fanned out
|
|
86
|
+
to every hook beneath it. Mounting three components does not open three streams.
|
|
87
|
+
- `useInbox`: the item list, with filtering, cursor pagination, and mark-read,
|
|
88
|
+
mark-all-read, archive and delete.
|
|
89
|
+
- `useUnreadCount`: the badge, kept live off the same stream.
|
|
90
|
+
- `HermsInbox`: a rendered bell and panel, keyboard-navigable, in English or French.
|
|
91
|
+
Optional: the hooks above are the whole API if you would rather build your own.
|
|
92
|
+
|
|
93
|
+
### Preferences
|
|
94
|
+
|
|
95
|
+
- `client.getPreferences()` and `client.updatePreference({ channel, enabled, categoryId })`,
|
|
96
|
+
plus the `usePreferences` hook.
|
|
97
|
+
- `PATCH` answers with the whole updated state, so a caller never refetches. The hook
|
|
98
|
+
does **not** update optimistically: turning off a channel is a consent decision, and
|
|
99
|
+
showing it as done before the server agreed tells somebody they have opted out when
|
|
100
|
+
they may not have.
|
|
101
|
+
- A setting of `null` means no preference was expressed and the default applies. It is
|
|
102
|
+
not `false`. Categories with `isCritical: true` are always delivered. Render them so
|
|
103
|
+
a subscriber can see what they receive, but not as a control.
|
|
104
|
+
- There is deliberately no `unsubscribe()`. `POST /v1/client/unsubscribe` is the target
|
|
105
|
+
of a `List-Unsubscribe` one-click link (RFC 8058) whose token is minted into an
|
|
106
|
+
outgoing email's headers and invoked by a mail client, not by application code. The
|
|
107
|
+
equivalent here is `updatePreference({ channel: 'email', enabled: false })`.
|
|
108
|
+
|
|
109
|
+
### Identities and errors
|
|
110
|
+
|
|
111
|
+
- `client.registerChannel` / `client.deregisterChannel`, and `HERMS_CHANNELS`: the
|
|
112
|
+
channels an identity can be registered for, exported as an array so a preference
|
|
113
|
+
centre can iterate them, with `HermsChannel` derived from it.
|
|
114
|
+
- `HermsApiError`: every failed request rejects with it, carrying the API's `type`,
|
|
115
|
+
`code`, `message`, `request_id`, `detail` and `docUrl`. A response that is not JSON
|
|
116
|
+
(a proxy's error page, an empty body where one was expected) arrives as this too,
|
|
117
|
+
rather than as a `TypeError` from inside the SDK.
|
|
118
|
+
- `decodeSubscriberTokenExp`: reads a subscriber token's expiry so a host can refresh
|
|
119
|
+
ahead of it. It parses; it does not validate a signature and cannot, since this
|
|
120
|
+
package never holds a key.
|
|
121
|
+
|
|
122
|
+
### Requirements
|
|
123
|
+
|
|
124
|
+
React 18 or newer, as a peer dependency with no upper bound. That is deliberate and
|
|
125
|
+
the opposite of how this repository pins its own dependencies: an application locks its
|
|
126
|
+
versions so two builds of one commit are identical, whereas a library that bounds a
|
|
127
|
+
peer range forces a warning on every consumer the day React ships a major, whether or
|
|
128
|
+
not anything actually broke. You control React; this package should not have an opinion
|
|
129
|
+
about which one you run.
|
|
130
|
+
|
|
131
|
+
Ships ESM and CommonJS builds with type declarations for both, and
|
|
132
|
+
`@hermesihq/react/styles.css` for `HermsInbox`.
|
|
133
|
+
|
|
134
|
+
### Known gaps
|
|
135
|
+
|
|
136
|
+
- No `<HermsPreferences />` component. The headless surface for one is here; the
|
|
137
|
+
rendered interface is not, because it wants a design pass rather than an invention.
|
|
138
|
+
- No Vue or vanilla wrapper yet.
|
package/README.md
CHANGED
|
@@ -1,120 +1,122 @@
|
|
|
1
|
-
# @hermesihq/react
|
|
2
|
-
|
|
3
|
-
React bindings for Hermesi's in-app inbox: a ready-made bell and panel, and the hooks it is
|
|
4
|
-
built on if you would rather build your own.
|
|
5
|
-
|
|
6
|
-
```sh
|
|
7
|
-
npm install @hermesihq/react
|
|
8
|
-
```
|
|
9
|
-
|
|
10
|
-
Requires React 18 or newer. It depends on [`@hermesihq/js`](https://github.com/hermesihq/sdk/tree/main/packages/js),
|
|
11
|
-
which npm installs for you. You do not import it yourself unless you want the stores directly.
|
|
12
|
-
|
|
13
|
-
## Before you start
|
|
14
|
-
|
|
15
|
-
Three things: a **public key** (`hm_pk_...`, safe to ship in a bundle), your **API base URL
|
|
16
|
-
ending in `/v1/client`**, and a **subscriber token minted by your own backend**. The token
|
|
17
|
-
proves which subscriber this page acts for. Your backend signs it with your secret key; this
|
|
18
|
-
package never sees that key and cannot mint a token. You give the client a function that
|
|
19
|
-
asks your backend for a fresh one, and it calls that function before every request.
|
|
20
|
-
|
|
21
|
-
The base URL is the client API, not the host root: every request is this string plus a path
|
|
22
|
-
such as `/inbox`. A base URL without `/v1/client` makes every request a 404.
|
|
23
|
-
|
|
24
|
-
## Quick start
|
|
25
|
-
|
|
26
|
-
```tsx
|
|
27
|
-
import { HermsClient, HermsInbox, HermsProvider } from '@hermesihq/react'
|
|
28
|
-
import '@hermesihq/react/styles.css'
|
|
29
|
-
|
|
30
|
-
const client = new HermsClient({
|
|
31
|
-
publicKey: 'hm_pk_prod_...',
|
|
32
|
-
apiBaseUrl: 'https://your-hermesi-host/v1/client',
|
|
33
|
-
getSubscriberToken: () => fetch('/api/hermesi-token').then((response) => response.text()), // calls *your* backend
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
export function AppHeader() {
|
|
37
|
-
return (
|
|
38
|
-
<HermsProvider client={client}>
|
|
39
|
-
<HermsInbox />
|
|
40
|
-
</HermsProvider>
|
|
41
|
-
)
|
|
42
|
-
}
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
Two lines are easy to miss. The stylesheet import is required: **the bundle does not load its
|
|
46
|
-
own CSS**, so without it the panel renders with no styling. And `HermsProvider` must sit above
|
|
47
|
-
anything that uses a hook, or the hook throws an error that names the missing provider.
|
|
48
|
-
|
|
49
|
-
Create the client **once**, at module scope or in a `useMemo`, not on every render. A new
|
|
50
|
-
client each render closes and reopens the real-time connection each render.
|
|
51
|
-
|
|
52
|
-
## `<HermsInbox />`
|
|
53
|
-
|
|
54
|
-
A real `<button>` with an accessible name that includes the unread count, and a panel with
|
|
55
|
-
arrow-key navigation, `Home` and `End`, and `Escape` to close.
|
|
56
|
-
|
|
57
|
-
| Prop | Default | |
|
|
58
|
-
|---|---|---|
|
|
59
|
-
| `placement` | `'bottom-end'` | `'bottom-start'`, `'bottom-end'`, `'top-start'` or `'top-end'` |
|
|
60
|
-
| `onItemClick` | | Called with the item when one is activated. Use it to drive your own router. Without it, an item with an `actionUrl` navigates there. Either way the item is marked read first. |
|
|
61
|
-
| `theme` | | `{ accent, radius }`, the two most re-themed values |
|
|
62
|
-
| `colorScheme` | `'auto'` | `'auto'` follows the visitor's OS setting; `'light'` or `'dark'` force one |
|
|
63
|
-
| `locale` | `'en'` | `'en'` or `'fr'` |
|
|
64
|
-
| `className` | | Added to the root element |
|
|
65
|
-
|
|
66
|
-
Everything else is reachable by overriding these CSS custom properties: `--herms-color-accent`,
|
|
67
|
-
`--herms-color-accent-foreground`, `--herms-color-bg`, `--herms-color-border`,
|
|
68
|
-
`--herms-color-danger`, `--herms-color-hover`, `--herms-color-muted`, `--herms-color-surface`,
|
|
69
|
-
`--herms-color-text`, `--herms-color-unread-dot`, `--herms-font-family` and `--herms-radius`.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
|
80
|
-
|
|
81
|
-
| `
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
1
|
+
# @hermesihq/react
|
|
2
|
+
|
|
3
|
+
React bindings for Hermesi's in-app inbox: a ready-made bell and panel, and the hooks it is
|
|
4
|
+
built on if you would rather build your own.
|
|
5
|
+
|
|
6
|
+
```sh
|
|
7
|
+
npm install @hermesihq/react
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Requires React 18 or newer. It depends on [`@hermesihq/js`](https://github.com/hermesihq/sdk/tree/main/packages/js),
|
|
11
|
+
which npm installs for you. You do not import it yourself unless you want the stores directly.
|
|
12
|
+
|
|
13
|
+
## Before you start
|
|
14
|
+
|
|
15
|
+
Three things: a **public key** (`hm_pk_...`, safe to ship in a bundle), your **API base URL
|
|
16
|
+
ending in `/v1/client`**, and a **subscriber token minted by your own backend**. The token
|
|
17
|
+
proves which subscriber this page acts for. Your backend signs it with your secret key; this
|
|
18
|
+
package never sees that key and cannot mint a token. You give the client a function that
|
|
19
|
+
asks your backend for a fresh one, and it calls that function before every request.
|
|
20
|
+
|
|
21
|
+
The base URL is the client API, not the host root: every request is this string plus a path
|
|
22
|
+
such as `/inbox`. A base URL without `/v1/client` makes every request a 404.
|
|
23
|
+
|
|
24
|
+
## Quick start
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
import { HermsClient, HermsInbox, HermsProvider } from '@hermesihq/react'
|
|
28
|
+
import '@hermesihq/react/styles.css'
|
|
29
|
+
|
|
30
|
+
const client = new HermsClient({
|
|
31
|
+
publicKey: 'hm_pk_prod_...',
|
|
32
|
+
apiBaseUrl: 'https://your-hermesi-host/v1/client',
|
|
33
|
+
getSubscriberToken: () => fetch('/api/hermesi-token').then((response) => response.text()), // calls *your* backend
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
export function AppHeader() {
|
|
37
|
+
return (
|
|
38
|
+
<HermsProvider client={client}>
|
|
39
|
+
<HermsInbox />
|
|
40
|
+
</HermsProvider>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Two lines are easy to miss. The stylesheet import is required: **the bundle does not load its
|
|
46
|
+
own CSS**, so without it the panel renders with no styling. And `HermsProvider` must sit above
|
|
47
|
+
anything that uses a hook, or the hook throws an error that names the missing provider.
|
|
48
|
+
|
|
49
|
+
Create the client **once**, at module scope or in a `useMemo`, not on every render. A new
|
|
50
|
+
client each render closes and reopens the real-time connection each render.
|
|
51
|
+
|
|
52
|
+
## `<HermsInbox />`
|
|
53
|
+
|
|
54
|
+
A real `<button>` with an accessible name that includes the unread count, and a panel with
|
|
55
|
+
arrow-key navigation, `Home` and `End`, and `Escape` to close.
|
|
56
|
+
|
|
57
|
+
| Prop | Default | |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| `placement` | `'bottom-end'` | `'bottom-start'`, `'bottom-end'`, `'top-start'` or `'top-end'` |
|
|
60
|
+
| `onItemClick` | | Called with the item when one is activated. Use it to drive your own router. Without it, an item with an `actionUrl` navigates there. Either way the item is marked read first. |
|
|
61
|
+
| `theme` | | `{ accent, radius }`, the two most re-themed values |
|
|
62
|
+
| `colorScheme` | `'auto'` | `'auto'` follows the visitor's OS setting; `'light'` or `'dark'` force one |
|
|
63
|
+
| `locale` | `'en'` | `'en'` or `'fr'` |
|
|
64
|
+
| `className` | | Added to the root element **and to the panel**, so one rule themes both |
|
|
65
|
+
|
|
66
|
+
Everything else is reachable by overriding these CSS custom properties: `--herms-color-accent`,
|
|
67
|
+
`--herms-color-accent-foreground`, `--herms-color-bg`, `--herms-color-border`,
|
|
68
|
+
`--herms-color-danger`, `--herms-color-hover`, `--herms-color-muted`, `--herms-color-surface`,
|
|
69
|
+
`--herms-color-text`, `--herms-color-unread-dot`, `--herms-font-family` and `--herms-radius`.
|
|
70
|
+
Set them on a rule that matches the `className` you pass: it is applied to the bell and to the
|
|
71
|
+
panel. The panel is rendered under `<body>`, not inside the bell, so a rule on an ancestor of the
|
|
72
|
+
bell does not reach it. The stylesheet is scoped under `.herms-inbox` and uses no Tailwind, so
|
|
73
|
+
your build cannot bleed into the widget or the reverse.
|
|
74
|
+
|
|
75
|
+
## Hooks
|
|
76
|
+
|
|
77
|
+
Use these when the bell and panel are not what you want. Same data, no chrome.
|
|
78
|
+
|
|
79
|
+
| Hook | Returns |
|
|
80
|
+
|---|---|
|
|
81
|
+
| `useUnreadCount()` | `{ unread, unseen, isLoading }`, kept live |
|
|
82
|
+
| `useInbox({ status?, category? })` | `{ items, isLoading, isLoadingMore, error, hasMore, loadMore, markRead, markAllRead, archive, remove, refetch }` |
|
|
83
|
+
| `usePreferences()` | `{ preferences, isLoading, error, setPreference, reload }` |
|
|
84
|
+
|
|
85
|
+
They are a thin binding over the stores in `@hermesihq/js`, which is where the behaviour is
|
|
86
|
+
documented. The parts worth knowing here:
|
|
87
|
+
|
|
88
|
+
- The functions a hook returns keep the same identity for as long as it is mounted, so they
|
|
89
|
+
are safe in a `useCallback` or `memo` dependency list.
|
|
90
|
+
- Changing `status` or `category` reloads the list and keeps the previous rows on screen,
|
|
91
|
+
flagged as loading, until the new ones arrive. A response for the old filter that lands
|
|
92
|
+
late is discarded.
|
|
93
|
+
- Mutations go to the server first and patch local state with what it answered. A failed one
|
|
94
|
+
rejects.
|
|
95
|
+
- `usePreferences` does not update optimistically. Turning off a channel is a consent
|
|
96
|
+
decision, and showing it as done before the server agreed tells somebody they have opted
|
|
97
|
+
out when they may not have.
|
|
98
|
+
- A setting of `null` means no preference was expressed and the default applies. It is not
|
|
99
|
+
`false`, and treating them as the same would opt somebody out of something they never
|
|
100
|
+
declined. Categories with `isCritical: true` are always delivered: show them, but not as a
|
|
101
|
+
control.
|
|
102
|
+
|
|
103
|
+
## Server rendering
|
|
104
|
+
|
|
105
|
+
Importing this package touches no DOM, and a component using the hooks renders its loading
|
|
106
|
+
state on the server. Nothing connects and no request is made until it mounts in the browser.
|
|
107
|
+
|
|
108
|
+
## Also exported
|
|
109
|
+
|
|
110
|
+
`HermsClient`, `HermsApiError`, `HERMS_CHANNELS` and `decodeSubscriberTokenExp`, with their
|
|
111
|
+
types, are re-exported from `@hermesihq/js`, so an existing import from this package keeps
|
|
112
|
+
working. Every failed request rejects with a `HermsApiError`; quote its `requestId` in a
|
|
113
|
+
support conversation.
|
|
114
|
+
|
|
115
|
+
## Not included
|
|
116
|
+
|
|
117
|
+
A rendered preference centre. The headless `usePreferences()` is here; the interface is not,
|
|
118
|
+
because it wants a design pass rather than an invention.
|
|
119
|
+
|
|
120
|
+
## License
|
|
121
|
+
|
|
122
|
+
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -210,6 +210,7 @@ function HermsInbox({ placement = "bottom-end", onItemClick, theme, colorScheme
|
|
|
210
210
|
const itemRefs = (0, import_react5.useRef)([]);
|
|
211
211
|
const seenIdsRef = (0, import_react5.useRef)(/* @__PURE__ */ new Set());
|
|
212
212
|
const strings = getHermsInboxStrings(locale);
|
|
213
|
+
const titleId = (0, import_react5.useId)();
|
|
213
214
|
(0, import_react5.useEffect)(() => {
|
|
214
215
|
if (!open || items.length === 0) return;
|
|
215
216
|
const toMark = items.filter((item) => !seenIdsRef.current.has(item.id)).map((item) => item.id);
|
|
@@ -269,7 +270,9 @@ function HermsInbox({ placement = "bottom-end", onItemClick, theme, colorScheme
|
|
|
269
270
|
}, [theme?.accent, theme?.radius]);
|
|
270
271
|
const { side, align } = placementToSideAlign[placement];
|
|
271
272
|
const rootClassName = ["herms-inbox", className].filter(Boolean).join(" ");
|
|
272
|
-
|
|
273
|
+
const panelClassName = ["herms-inbox__panel", className].filter(Boolean).join(" ");
|
|
274
|
+
const colorSchemeAttribute = colorScheme === "auto" ? void 0 : colorScheme;
|
|
275
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: rootClassName, style: rootStyle, "data-herms-color-scheme": colorSchemeAttribute, children: [
|
|
273
276
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "herms-inbox__visually-hidden", role: "status", "aria-live": "polite", children: unread > 0 ? strings.bellLabelWithUnread(unread) : "" }),
|
|
274
277
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Popover.Root, { open, onOpenChange: setOpen, children: [
|
|
275
278
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Popover.Trigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
@@ -288,7 +291,10 @@ function HermsInbox({ placement = "bottom-end", onItemClick, theme, colorScheme
|
|
|
288
291
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Popover.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
289
292
|
Popover.Content,
|
|
290
293
|
{
|
|
291
|
-
className:
|
|
294
|
+
className: panelClassName,
|
|
295
|
+
style: rootStyle,
|
|
296
|
+
"data-herms-color-scheme": colorSchemeAttribute,
|
|
297
|
+
"aria-labelledby": titleId,
|
|
292
298
|
side,
|
|
293
299
|
align,
|
|
294
300
|
sideOffset: 8,
|
|
@@ -300,7 +306,7 @@ function HermsInbox({ placement = "bottom-end", onItemClick, theme, colorScheme
|
|
|
300
306
|
},
|
|
301
307
|
children: [
|
|
302
308
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "herms-inbox__header", children: [
|
|
303
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "herms-inbox__title", children: strings.panelTitle }),
|
|
309
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "herms-inbox__title", id: titleId, children: strings.panelTitle }),
|
|
304
310
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "herms-inbox__header-actions", children: [
|
|
305
311
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: "herms-inbox__text-button", onClick: () => void markAllRead(), disabled: unread === 0, children: strings.markAllRead }),
|
|
306
312
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Popover.Close, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: "herms-inbox__icon-button", "aria-label": strings.close, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CloseIcon, {}) }) })
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/HermsProvider.tsx","../src/useUnreadCount.ts","../src/useInbox.ts","../src/usePreferences.ts","../src/HermsInbox.tsx","../src/locale.ts"],"sourcesContent":["/**\n * `@hermesihq/react`: React bindings for Hermesi's in-app inbox.\n *\n * Two parts, from the one entry point:\n *\n * - **The client surface**, re-exported from `@hermesihq/js` so that\n * `import { HermsClient } from '@hermesihq/react'` keeps working. It is exactly the\n * names this package has always exported from there and no more: the stores and the\n * session are `@hermesihq/js`'s to export, and a React host has the hooks.\n * - **`HermsProvider`, `useUnreadCount`, `useInbox`, `usePreferences` and\n * `<HermsInbox />`.** The hooks are a thin binding over `@hermesihq/js`'s stores; the\n * component is a ready bell and panel built on them.\n */\nexport { HermsClient, HermsApiError, HERMS_CHANNELS, decodeSubscriberTokenExp } from '@hermesihq/js'\nexport type {\n HermsCategoryPreference,\n HermsChannel,\n HermsChannelPreference,\n HermsClientOptions,\n HermsErrorDetail,\n HermsEventListener,\n HermsInboxCategory,\n HermsInboxCounts,\n HermsInboxItem,\n HermsInboxListParams,\n HermsInboxPage,\n HermsInboxReadStatus,\n HermsPreferences,\n HermsPreferenceUpdate,\n HermsRealtimeEvent,\n HermsRegisterChannelParams,\n} from '@hermesihq/js'\n\nexport { HermsProvider, useHermsContext, type HermsProviderProps } from './HermsProvider'\nexport { useUnreadCount, type UseUnreadCountResult } from './useUnreadCount'\nexport { useInbox, type UseInboxOptions, type UseInboxResult } from './useInbox'\nexport { usePreferences, type UsePreferencesResult } from './usePreferences'\nexport { HermsInbox, type HermsInboxProps, type HermsInboxPlacement, type HermsInboxTheme } from './HermsInbox'\nexport { type HermsLocale } from './locale'\n","import { createContext, useContext, useEffect, useMemo, type ReactNode } from 'react'\r\nimport type { HermsClient } from '@hermesihq/js'\r\nimport { HermsSession, type HermsStoreHost } from '@hermesihq/js'\r\n\r\n/**\r\n * What every hook in this package reads from context. Exactly the shape the stores take\r\n * as their host, so a hook hands this straight to the store it creates.\r\n */\r\ntype HermsContextValue = HermsStoreHost\r\n\r\nconst HermsContext = createContext<HermsContextValue | null>(null)\r\n\r\nexport interface HermsProviderProps {\r\n client: HermsClient\r\n children: ReactNode\r\n}\r\n\r\n/**\r\n * `<HermsProvider client={new HermsClient({...})}>`: one `HermsClient` instance for the\r\n * whole subtree. Opens the client's real-time subscription once on mount and tears it\r\n * (and the client's own `onTokenExpiring` timer) down on unmount; every listener\r\n * registered via `addEventListener` shares that single connection rather than each hook\r\n * paying for its own.\r\n *\r\n * The connection itself is `HermsSession`, which knows nothing about React: this\r\n * component is the few lines that tie its lifetime to a mount. That is deliberate, and it\r\n * is the same session a Vue or vanilla page uses.\r\n *\r\n * `client` is expected to be a stable reference (constructed once, e.g. in\r\n * `useMemo`/module scope in the host app). Passing a new instance on every render tears\r\n * down and reopens the underlying connection every render.\r\n */\r\nexport function HermsProvider({ client, children }: HermsProviderProps) {\r\n const session = useMemo(() => new HermsSession(client), [client])\r\n\r\n // `connect()` returns its own release, which is the effect's cleanup. Reference-counted\r\n // in the session, so Strict Mode's connect, release, connect comes out right.\r\n useEffect(() => session.connect(), [session])\r\n\r\n const value = useMemo<HermsContextValue>(\r\n () => ({ client, addEventListener: session.addEventListener }),\r\n [client, session],\r\n )\r\n\r\n return <HermsContext.Provider value={value}>{children}</HermsContext.Provider>\r\n}\r\n\r\n/** Every hook/component in this package throws this same error outside a\r\n * `<HermsProvider>`: there is no sensible default client to fall back to. */\r\nexport function useHermsContext(): HermsContextValue {\r\n const ctx = useContext(HermsContext)\r\n if (!ctx) {\r\n throw new Error('@hermesihq/react: this hook/component must be rendered inside <HermsProvider client={...}>.')\r\n }\r\n return ctx\r\n}\r\n","import { useEffect, useMemo, useSyncExternalStore } from 'react'\r\nimport { CountsStore, type CountsState } from '@hermesihq/js'\r\nimport { useHermsContext } from './HermsProvider'\r\n\r\nexport type UseUnreadCountResult = CountsState\r\n\r\n/**\r\n * Headless. `{unread, unseen, isLoading}`, kept live via the provider's shared\r\n * subscription: one `getCounts()` call on mount, then recomputed on every\r\n * `counts.changed` event (real-time push, or the client's own 60s polling fallback) for\r\n * as long as this component stays mounted.\r\n *\r\n * A binding over `CountsStore`, which holds all of that. The store's constructor does\r\n * nothing, so React discarding a memoised one costs nothing: it is the `connect()` in the\r\n * effect that starts work.\r\n */\r\nexport function useUnreadCount(): UseUnreadCountResult {\r\n const host = useHermsContext()\r\n const store = useMemo(() => new CountsStore(host), [host])\r\n\r\n useEffect(() => store.connect(), [store])\r\n\r\n // The initial state is also the server snapshot: nothing connects during a server\r\n // render, so this is exactly what the server would produce.\r\n return useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot)\r\n}\r\n","import { useEffect, useMemo, useSyncExternalStore } from 'react'\r\nimport { InboxStore } from '@hermesihq/js'\r\nimport type { HermsInboxItem, HermsInboxReadStatus } from '@hermesihq/js'\r\nimport { useHermsContext } from './HermsProvider'\r\n\r\nexport interface UseInboxOptions {\r\n status?: HermsInboxReadStatus\r\n category?: string\r\n}\r\n\r\nexport interface UseInboxResult {\r\n items: HermsInboxItem[]\r\n isLoading: boolean\r\n isLoadingMore: boolean\r\n error: Error | null\r\n hasMore: boolean\r\n loadMore: () => void\r\n markRead: (id: string) => Promise<void>\r\n markAllRead: () => Promise<void>\r\n archive: (id: string) => Promise<void>\r\n remove: (id: string) => Promise<void>\r\n refetch: () => void\r\n}\r\n\r\n/**\r\n * Headless, cursor-paginated inbox list state: thin wrappers around `HermsClient`'s own\r\n * methods that also patch local state, so a consumer never has to manually refetch the\r\n * whole list after every mutating action.\r\n *\r\n * Live-updated on `item.created`: the new item is prepended immediately from the\r\n * (deliberately minimal: `{id, title, created_at}`) SSE payload so the UI reacts\r\n * instantly, then reconciled against a fresh first page fetched in the background so the\r\n * authoritative row (body, category, action_url) lands without a full list\r\n * reload/flicker.\r\n *\r\n * All of that lives in `InboxStore`; this is the binding. The filter is handed to the\r\n * store rather than being part of what identifies it, so changing tabs reloads the list\r\n * in place, keeping the rows on screen, instead of discarding the store and blanking the\r\n * panel.\r\n */\r\nexport function useInbox(options: UseInboxOptions = {}): UseInboxResult {\r\n const host = useHermsContext()\r\n const { status, category } = options\r\n\r\n // Only the first render's filter is used to create the store. Later changes go through\r\n // `setFilter` below, so this memo is keyed on the host alone.\r\n const store = useMemo(() => new InboxStore(host, { status, category }), [host])\r\n\r\n useEffect(() => store.connect(), [store])\r\n // A no-op when the filter is the one the store already has, which is the case on the\r\n // first render: `connect()` above has just loaded it.\r\n useEffect(() => {\r\n store.setFilter({ status, category })\r\n }, [store, status, category])\r\n\r\n const state = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot)\r\n\r\n return useMemo(\r\n () => ({\r\n ...state,\r\n // Arrow-function fields on the store: the same references for its whole life, so a\r\n // consumer's `useCallback`/`memo` dependencies do not churn as the state changes.\r\n loadMore: store.loadMore,\r\n markRead: store.markRead,\r\n markAllRead: store.markAllRead,\r\n archive: store.archive,\r\n remove: store.remove,\r\n refetch: store.refetch,\r\n }),\r\n [state, store],\r\n )\r\n}\r\n","import { useEffect, useMemo, useSyncExternalStore } from 'react'\r\nimport { PreferencesStore } from '@hermesihq/js'\r\nimport type { HermsPreferences, HermsPreferenceUpdate } from '@hermesihq/js'\r\nimport { useHermsContext } from './HermsProvider'\r\n\r\nexport interface UsePreferencesResult {\r\n /** `null` until the first read resolves. */\r\n preferences: HermsPreferences | null\r\n isLoading: boolean\r\n /** The failure of the last read or write, or `null`. Kept rather than thrown so a\r\n * preference centre can show it beside the controls instead of unmounting them. */\r\n error: Error | null\r\n /** Change one setting. Resolves once the server's new state has been applied. */\r\n setPreference: (update: HermsPreferenceUpdate) => Promise<void>\r\n /** Re-read, for a retry button. */\r\n reload: () => Promise<void>\r\n}\r\n\r\n/**\r\n * Headless preferences, the counterpart to `useInbox`.\r\n *\r\n * **No optimistic update, deliberately.** A toggle here is not a like button: turning\r\n * off a channel is a consent decision, and showing it as done before the server agreed is\r\n * showing somebody they have opted out when they may not have. The server answers `PATCH`\r\n * with the *whole* updated state, so the correct view arrives with the write and there is\r\n * nothing to reconcile. A per-row pending state in the host is a better answer to the\r\n * latency than a lie.\r\n *\r\n * **`error` is state, not an exception.** `setPreference` still rejects, so a caller can\r\n * `await` it and react per-control, but the hook also holds the failure so a preference\r\n * centre can render it without every consumer writing the same try/catch. A failed write\r\n * leaves the last known-good state on screen rather than an empty page.\r\n *\r\n * Critical categories arrive with `isCritical: true` and are always delivered. Render\r\n * them (a subscriber should see what they are receiving), but not as a control: an\r\n * affordance that refuses is worse than none.\r\n *\r\n * A binding over `PreferencesStore`, which holds the behaviour above.\r\n */\r\nexport function usePreferences(): UsePreferencesResult {\r\n const host = useHermsContext()\r\n const store = useMemo(() => new PreferencesStore(host), [host])\r\n\r\n useEffect(() => store.connect(), [store])\r\n\r\n const state = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot)\r\n\r\n return useMemo(\r\n () => ({ ...state, setPreference: store.setPreference, reload: store.reload }),\r\n [state, store],\r\n )\r\n}\r\n","import { useCallback, useEffect, useMemo, useRef, useState, type CSSProperties, type KeyboardEvent } from 'react'\r\nimport * as Popover from '@radix-ui/react-popover'\r\nimport type { HermsInboxItem } from '@hermesihq/js'\r\nimport { useHermsContext } from './HermsProvider'\r\nimport { useInbox } from './useInbox'\r\nimport { useUnreadCount } from './useUnreadCount'\r\nimport { getHermsInboxStrings, type HermsLocale } from './locale'\r\nimport './HermsInbox.css'\r\n\r\nexport type HermsInboxPlacement = 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end'\r\n\r\nexport interface HermsInboxTheme {\r\n accent?: string\r\n radius?: string\r\n}\r\n\r\nexport interface HermsInboxProps {\r\n /** Popover position relative to the bell trigger. Default `bottom-end`. */\r\n placement?: HermsInboxPlacement\r\n /** Called when an item is activated (click, or Enter on a focused row).\r\n * Lets a host app drive its own router (for example\r\n * `onItemClick={(item) => navigate(item.actionUrl)}`) instead of a hard\r\n * page navigation. When omitted, activating an item with a non-null\r\n * `actionUrl` does a plain `window.location.assign` to it. Either way, the\r\n * item is marked read first. */\r\n onItemClick?: (item: HermsInboxItem) => void\r\n /** A couple of the most commonly re-themed CSS custom properties, settable\r\n * without a host app writing any CSS of its own. Anything else is\r\n * reachable by overriding `--herms-color-*`/`--herms-radius` variables\r\n * directly (see `HermsInbox.css`). */\r\n theme?: HermsInboxTheme\r\n /** `'auto'` (default) follows the visitor's OS `prefers-color-scheme`;\r\n * `'light'`/`'dark'` force one regardless of it. */\r\n colorScheme?: 'auto' | 'light' | 'dark'\r\n locale?: HermsLocale\r\n className?: string\r\n}\r\n\r\nconst placementToSideAlign: Record<HermsInboxPlacement, { side: 'top' | 'bottom'; align: 'start' | 'end' }> = {\r\n 'bottom-start': { side: 'bottom', align: 'start' },\r\n 'bottom-end': { side: 'bottom', align: 'end' },\r\n 'top-start': { side: 'top', align: 'start' },\r\n 'top-end': { side: 'top', align: 'end' },\r\n}\r\n\r\nfunction BellIcon() {\r\n return (\r\n <svg className=\"herms-inbox__bell\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\r\n <path d=\"M6 8a6 6 0 0 1 12 0c0 5 2 6 2 6H4s2-1 2-6\" />\r\n <path d=\"M10 21a2 2 0 0 0 4 0\" />\r\n </svg>\r\n )\r\n}\r\n\r\nfunction CloseIcon() {\r\n return (\r\n <svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden=\"true\">\r\n <path d=\"M18 6 6 18M6 6l12 12\" />\r\n </svg>\r\n )\r\n}\r\n\r\nfunction ArchiveIcon() {\r\n return (\r\n <svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\r\n <rect x=\"3\" y=\"4\" width=\"18\" height=\"4\" rx=\"1\" />\r\n <path d=\"M5 8v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8\" />\r\n <path d=\"M10 13h4\" />\r\n </svg>\r\n )\r\n}\r\n\r\nfunction AlertIcon() {\r\n return (\r\n <svg viewBox=\"0 0 24 24\" width=\"20\" height=\"20\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\r\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\r\n <path d=\"M12 8v5M12 16h.01\" />\r\n </svg>\r\n )\r\n}\r\n\r\nfunction relativeTime(iso: string, locale: HermsLocale): string {\r\n const then = new Date(iso).getTime()\r\n if (Number.isNaN(then)) return ''\r\n const diffSeconds = Math.max(0, Math.round((Date.now() - then) / 1000))\r\n const formatter = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' })\r\n if (diffSeconds < 60) return formatter.format(0, 'minute')\r\n const diffMinutes = Math.round(diffSeconds / 60)\r\n if (diffMinutes < 60) return formatter.format(-diffMinutes, 'minute')\r\n const diffHours = Math.round(diffMinutes / 60)\r\n if (diffHours < 24) return formatter.format(-diffHours, 'hour')\r\n const diffDays = Math.round(diffHours / 24)\r\n return formatter.format(-diffDays, 'day')\r\n}\r\n\r\n/**\r\n * `<HermsInbox />`: bell trigger + dropdown panel. Built\r\n * entirely on the headless `useInbox`/`useUnreadCount` hooks; every state\r\n * (loading, empty, error, populated) renders here, none of it invented by a\r\n * host app. Accessible: a real `<button>` trigger with `aria-label`/\r\n * `aria-expanded`, the panel as `role=\"menu\"` with roving-tabindex arrow-key\r\n * navigation, `Home`/`End`, `Escape`-to-close (native to Radix `Popover`,\r\n * which also returns focus to the trigger), and `Enter`/`Space` (native\r\n * `<button>` behavior) to activate the focused item.\r\n */\r\nexport function HermsInbox({ placement = 'bottom-end', onItemClick, theme, colorScheme = 'auto', locale, className }: HermsInboxProps) {\r\n const { client } = useHermsContext()\r\n const { unread, unseen } = useUnreadCount()\r\n const { items, isLoading, isLoadingMore, error, hasMore, loadMore, markRead, markAllRead, archive, refetch } = useInbox()\r\n const [open, setOpen] = useState(false)\r\n const [activeIndex, setActiveIndex] = useState(0)\r\n const itemRefs = useRef<Array<HTMLButtonElement | null>>([])\r\n const seenIdsRef = useRef<Set<string>>(new Set())\r\n const strings = getHermsInboxStrings(locale)\r\n\r\n // \"Seen\" (the bell was opened) is distinct from \"read.\" Marks\r\n // every currently loaded, not-yet-seen id seen once the panel is open and\r\n // has items, including any additional ids that arrive later via\r\n // `loadMore`/live updates while it stays open.\r\n useEffect(() => {\r\n if (!open || items.length === 0) return\r\n const toMark = items.filter((item) => !seenIdsRef.current.has(item.id)).map((item) => item.id)\r\n if (toMark.length === 0) return\r\n for (const id of toMark) seenIdsRef.current.add(id)\r\n void client.markSeen(toMark)\r\n }, [open, items, client])\r\n\r\n useEffect(() => {\r\n if (open) setActiveIndex(0)\r\n }, [open])\r\n\r\n const focusItem = useCallback((index: number) => {\r\n setActiveIndex(index)\r\n itemRefs.current[index]?.focus()\r\n }, [])\r\n\r\n const handleListKeyDown = useCallback(\r\n (event: KeyboardEvent<HTMLUListElement>) => {\r\n if (items.length === 0) return\r\n switch (event.key) {\r\n case 'ArrowDown':\r\n event.preventDefault()\r\n focusItem(Math.min(activeIndex + 1, items.length - 1))\r\n break\r\n case 'ArrowUp':\r\n event.preventDefault()\r\n focusItem(Math.max(activeIndex - 1, 0))\r\n break\r\n case 'Home':\r\n event.preventDefault()\r\n focusItem(0)\r\n break\r\n case 'End':\r\n event.preventDefault()\r\n focusItem(items.length - 1)\r\n break\r\n default:\r\n break\r\n }\r\n },\r\n [items.length, activeIndex, focusItem],\r\n )\r\n\r\n const handleActivate = useCallback(\r\n (item: HermsInboxItem) => {\r\n if (!item.readAt) void markRead(item.id)\r\n if (onItemClick) {\r\n onItemClick(item)\r\n } else if (item.actionUrl && typeof window !== 'undefined') {\r\n window.location.assign(item.actionUrl)\r\n }\r\n },\r\n [markRead, onItemClick],\r\n )\r\n\r\n const rootStyle = useMemo<CSSProperties>(() => {\r\n const style: CSSProperties & Record<string, string> = {}\r\n if (theme?.accent) style['--herms-color-accent'] = theme.accent\r\n if (theme?.radius) style['--herms-radius'] = theme.radius\r\n return style\r\n }, [theme?.accent, theme?.radius])\r\n\r\n const { side, align } = placementToSideAlign[placement]\r\n const rootClassName = ['herms-inbox', className].filter(Boolean).join(' ')\r\n\r\n return (\r\n <div className={rootClassName} style={rootStyle} data-herms-color-scheme={colorScheme === 'auto' ? undefined : colorScheme}>\r\n <span className=\"herms-inbox__visually-hidden\" role=\"status\" aria-live=\"polite\">\r\n {unread > 0 ? strings.bellLabelWithUnread(unread) : ''}\r\n </span>\r\n <Popover.Root open={open} onOpenChange={setOpen}>\r\n <Popover.Trigger asChild>\r\n <button\r\n type=\"button\"\r\n className=\"herms-inbox__trigger\"\r\n aria-label={unread > 0 ? strings.bellLabelWithUnread(unread) : strings.bellLabel}\r\n aria-expanded={open}\r\n >\r\n <BellIcon />\r\n {unseen > 0 && (\r\n <span className=\"herms-inbox__badge\" aria-hidden=\"true\">\r\n {unseen > 99 ? '99+' : unseen}\r\n </span>\r\n )}\r\n </button>\r\n </Popover.Trigger>\r\n <Popover.Portal>\r\n <Popover.Content\r\n className=\"herms-inbox__panel\"\r\n side={side}\r\n align={align}\r\n sideOffset={8}\r\n onOpenAutoFocus={(event) => {\r\n // Radix's default is to focus the content wrapper itself; we\r\n // want the first item (or the header when there are none)\r\n // focused instead, so keyboard users land somewhere useful.\r\n if (items.length > 0) {\r\n event.preventDefault()\r\n itemRefs.current[0]?.focus()\r\n }\r\n }}\r\n >\r\n <div className=\"herms-inbox__header\">\r\n <p className=\"herms-inbox__title\">{strings.panelTitle}</p>\r\n <div className=\"herms-inbox__header-actions\">\r\n <button type=\"button\" className=\"herms-inbox__text-button\" onClick={() => void markAllRead()} disabled={unread === 0}>\r\n {strings.markAllRead}\r\n </button>\r\n <Popover.Close asChild>\r\n <button type=\"button\" className=\"herms-inbox__icon-button\" aria-label={strings.close}>\r\n <CloseIcon />\r\n </button>\r\n </Popover.Close>\r\n </div>\r\n </div>\r\n\r\n {isLoading ? (\r\n <div role=\"status\" aria-label={strings.loading}>\r\n {[0, 1, 2, 3].map((i) => (\r\n <div key={i} className=\"herms-inbox__skeleton-line\" style={{ width: i % 2 === 0 ? '85%' : '65%' }} />\r\n ))}\r\n </div>\r\n ) : error ? (\r\n <div className=\"herms-inbox__state\" role=\"alert\">\r\n <AlertIcon />\r\n <p className=\"herms-inbox__state-title\">{strings.errorTitle}</p>\r\n <button type=\"button\" className=\"herms-inbox__text-button\" onClick={() => refetch()}>\r\n {strings.retry}\r\n </button>\r\n </div>\r\n ) : items.length === 0 ? (\r\n <div className=\"herms-inbox__state\">{strings.empty}</div>\r\n ) : (\r\n <>\r\n <ul className=\"herms-inbox__list\" role=\"menu\" aria-label={strings.panelTitle} onKeyDown={handleListKeyDown}>\r\n {items.map((item, index) => (\r\n <li key={item.id} className=\"herms-inbox__list-row\" role=\"none\">\r\n <button\r\n type=\"button\"\r\n role=\"menuitem\"\r\n ref={(el) => {\r\n itemRefs.current[index] = el\r\n }}\r\n tabIndex={index === activeIndex ? 0 : -1}\r\n className=\"herms-inbox__item\"\r\n data-unread={!item.readAt}\r\n onFocus={() => setActiveIndex(index)}\r\n onClick={() => handleActivate(item)}\r\n >\r\n <span className=\"herms-inbox__item-title\">{item.title}</span>\r\n {item.body && <span className=\"herms-inbox__item-body\">{item.body}</span>}\r\n <span className=\"herms-inbox__item-meta\">{relativeTime(item.createdAt, locale ?? 'en')}</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n className=\"herms-inbox__archive-button\"\r\n aria-label={`${strings.archive}: ${item.title}`}\r\n onClick={(event) => {\r\n event.stopPropagation()\r\n void archive(item.id)\r\n }}\r\n >\r\n <ArchiveIcon />\r\n </button>\r\n </li>\r\n ))}\r\n </ul>\r\n {hasMore && (\r\n <button type=\"button\" className=\"herms-inbox__load-more\" onClick={loadMore} disabled={isLoadingMore}>\r\n {isLoadingMore ? strings.loadingMore : strings.loadMore}\r\n </button>\r\n )}\r\n </>\r\n )}\r\n </Popover.Content>\r\n </Popover.Portal>\r\n </Popover.Root>\r\n </div>\r\n )\r\n}\r\n","/**\r\n * `<HermsInbox />`'s own tiny, self-contained EN/FR string table.\r\n *\r\n * Deliberately **not** i18next: this package ships to arbitrary host apps\r\n * (with zero hard dependency on the host app's state library) that may run\r\n * their own i18n stack, a different one, or none at all. Pulling i18next in\r\n * as a real dependency here would be exactly the kind of host-app-shaped\r\n * assumption the SDK is supposed to avoid, for a handful of short strings.\r\n * `locale` is a prop of `<HermsInbox />`.\r\n */\r\n\r\nexport type HermsLocale = 'en' | 'fr'\r\n\r\nexport interface HermsInboxStrings {\r\n bellLabel: string\r\n bellLabelWithUnread: (count: number) => string\r\n panelTitle: string\r\n markAllRead: string\r\n close: string\r\n loading: string\r\n empty: string\r\n errorTitle: string\r\n retry: string\r\n loadMore: string\r\n loadingMore: string\r\n archive: string\r\n unreadDot: string\r\n}\r\n\r\nconst en: HermsInboxStrings = {\r\n bellLabel: 'Notifications',\r\n bellLabelWithUnread: (count) => `Notifications, ${count} unread`,\r\n panelTitle: 'Notifications',\r\n markAllRead: 'Mark all as read',\r\n close: 'Close',\r\n loading: 'Loading notifications…',\r\n empty: \"You're all caught up\",\r\n errorTitle: \"Couldn't load notifications.\",\r\n retry: 'Retry',\r\n loadMore: 'Load more',\r\n loadingMore: 'Loading…',\r\n archive: 'Archive',\r\n unreadDot: 'Unread',\r\n}\r\n\r\nconst fr: HermsInboxStrings = {\r\n bellLabel: 'Notifications',\r\n bellLabelWithUnread: (count) => `Notifications, ${count} non lues`,\r\n panelTitle: 'Notifications',\r\n markAllRead: 'Tout marquer comme lu',\r\n close: 'Fermer',\r\n loading: 'Chargement des notifications…',\r\n empty: 'Vous êtes à jour',\r\n errorTitle: \"Impossible de charger les notifications.\",\r\n retry: 'Réessayer',\r\n loadMore: 'Charger plus',\r\n loadingMore: 'Chargement…',\r\n archive: 'Archiver',\r\n unreadDot: 'Non lu',\r\n}\r\n\r\nconst dictionaries: Record<HermsLocale, HermsInboxStrings> = { en, fr }\r\n\r\nexport function getHermsInboxStrings(locale: HermsLocale | undefined): HermsInboxStrings {\r\n return dictionaries[locale ?? 'en'] ?? en\r\n}\r\n"],"mappings":"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","names":["import_js","import_react","import_js","import_react","import_js","import_react","import_js","import_react","import_jsx_runtime"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/HermsProvider.tsx","../src/useUnreadCount.ts","../src/useInbox.ts","../src/usePreferences.ts","../src/HermsInbox.tsx","../src/locale.ts"],"sourcesContent":["/**\n * `@hermesihq/react`: React bindings for Hermesi's in-app inbox.\n *\n * Two parts, from the one entry point:\n *\n * - **The client surface**, re-exported from `@hermesihq/js` so that\n * `import { HermsClient } from '@hermesihq/react'` keeps working. It is exactly the\n * names this package has always exported from there and no more: the stores and the\n * session are `@hermesihq/js`'s to export, and a React host has the hooks.\n * - **`HermsProvider`, `useUnreadCount`, `useInbox`, `usePreferences` and\n * `<HermsInbox />`.** The hooks are a thin binding over `@hermesihq/js`'s stores; the\n * component is a ready bell and panel built on them.\n */\nexport { HermsClient, HermsApiError, HERMS_CHANNELS, decodeSubscriberTokenExp } from '@hermesihq/js'\nexport type {\n HermsCategoryPreference,\n HermsChannel,\n HermsChannelPreference,\n HermsClientOptions,\n HermsErrorDetail,\n HermsEventListener,\n HermsInboxCategory,\n HermsInboxCounts,\n HermsInboxItem,\n HermsInboxListParams,\n HermsInboxPage,\n HermsInboxReadStatus,\n HermsPreferences,\n HermsPreferenceUpdate,\n HermsRealtimeEvent,\n HermsRegisterChannelParams,\n} from '@hermesihq/js'\n\nexport { HermsProvider, useHermsContext, type HermsProviderProps } from './HermsProvider'\nexport { useUnreadCount, type UseUnreadCountResult } from './useUnreadCount'\nexport { useInbox, type UseInboxOptions, type UseInboxResult } from './useInbox'\nexport { usePreferences, type UsePreferencesResult } from './usePreferences'\nexport { HermsInbox, type HermsInboxProps, type HermsInboxPlacement, type HermsInboxTheme } from './HermsInbox'\nexport { type HermsLocale } from './locale'\n","import { createContext, useContext, useEffect, useMemo, type ReactNode } from 'react'\r\nimport type { HermsClient } from '@hermesihq/js'\r\nimport { HermsSession, type HermsStoreHost } from '@hermesihq/js'\r\n\r\n/**\r\n * What every hook in this package reads from context. Exactly the shape the stores take\r\n * as their host, so a hook hands this straight to the store it creates.\r\n */\r\ntype HermsContextValue = HermsStoreHost\r\n\r\nconst HermsContext = createContext<HermsContextValue | null>(null)\r\n\r\nexport interface HermsProviderProps {\r\n client: HermsClient\r\n children: ReactNode\r\n}\r\n\r\n/**\r\n * `<HermsProvider client={new HermsClient({...})}>`: one `HermsClient` instance for the\r\n * whole subtree. Opens the client's real-time subscription once on mount and tears it\r\n * (and the client's own `onTokenExpiring` timer) down on unmount; every listener\r\n * registered via `addEventListener` shares that single connection rather than each hook\r\n * paying for its own.\r\n *\r\n * The connection itself is `HermsSession`, which knows nothing about React: this\r\n * component is the few lines that tie its lifetime to a mount. That is deliberate, and it\r\n * is the same session a Vue or vanilla page uses.\r\n *\r\n * `client` is expected to be a stable reference (constructed once, e.g. in\r\n * `useMemo`/module scope in the host app). Passing a new instance on every render tears\r\n * down and reopens the underlying connection every render.\r\n */\r\nexport function HermsProvider({ client, children }: HermsProviderProps) {\r\n const session = useMemo(() => new HermsSession(client), [client])\r\n\r\n // `connect()` returns its own release, which is the effect's cleanup. Reference-counted\r\n // in the session, so Strict Mode's connect, release, connect comes out right.\r\n useEffect(() => session.connect(), [session])\r\n\r\n const value = useMemo<HermsContextValue>(\r\n () => ({ client, addEventListener: session.addEventListener }),\r\n [client, session],\r\n )\r\n\r\n return <HermsContext.Provider value={value}>{children}</HermsContext.Provider>\r\n}\r\n\r\n/** Every hook/component in this package throws this same error outside a\r\n * `<HermsProvider>`: there is no sensible default client to fall back to. */\r\nexport function useHermsContext(): HermsContextValue {\r\n const ctx = useContext(HermsContext)\r\n if (!ctx) {\r\n throw new Error('@hermesihq/react: this hook/component must be rendered inside <HermsProvider client={...}>.')\r\n }\r\n return ctx\r\n}\r\n","import { useEffect, useMemo, useSyncExternalStore } from 'react'\r\nimport { CountsStore, type CountsState } from '@hermesihq/js'\r\nimport { useHermsContext } from './HermsProvider'\r\n\r\nexport type UseUnreadCountResult = CountsState\r\n\r\n/**\r\n * Headless. `{unread, unseen, isLoading}`, kept live via the provider's shared\r\n * subscription: one `getCounts()` call on mount, then recomputed on every\r\n * `counts.changed` event (real-time push, or the client's own 60s polling fallback) for\r\n * as long as this component stays mounted.\r\n *\r\n * A binding over `CountsStore`, which holds all of that. The store's constructor does\r\n * nothing, so React discarding a memoised one costs nothing: it is the `connect()` in the\r\n * effect that starts work.\r\n */\r\nexport function useUnreadCount(): UseUnreadCountResult {\r\n const host = useHermsContext()\r\n const store = useMemo(() => new CountsStore(host), [host])\r\n\r\n useEffect(() => store.connect(), [store])\r\n\r\n // The initial state is also the server snapshot: nothing connects during a server\r\n // render, so this is exactly what the server would produce.\r\n return useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot)\r\n}\r\n","import { useEffect, useMemo, useSyncExternalStore } from 'react'\r\nimport { InboxStore } from '@hermesihq/js'\r\nimport type { HermsInboxItem, HermsInboxReadStatus } from '@hermesihq/js'\r\nimport { useHermsContext } from './HermsProvider'\r\n\r\nexport interface UseInboxOptions {\r\n status?: HermsInboxReadStatus\r\n category?: string\r\n}\r\n\r\nexport interface UseInboxResult {\r\n items: HermsInboxItem[]\r\n isLoading: boolean\r\n isLoadingMore: boolean\r\n error: Error | null\r\n hasMore: boolean\r\n loadMore: () => void\r\n markRead: (id: string) => Promise<void>\r\n markAllRead: () => Promise<void>\r\n archive: (id: string) => Promise<void>\r\n remove: (id: string) => Promise<void>\r\n refetch: () => void\r\n}\r\n\r\n/**\r\n * Headless, cursor-paginated inbox list state: thin wrappers around `HermsClient`'s own\r\n * methods that also patch local state, so a consumer never has to manually refetch the\r\n * whole list after every mutating action.\r\n *\r\n * Live-updated on `item.created`: the new item is prepended immediately from the\r\n * (deliberately minimal: `{id, title, created_at}`) SSE payload so the UI reacts\r\n * instantly, then reconciled against a fresh first page fetched in the background so the\r\n * authoritative row (body, category, action_url) lands without a full list\r\n * reload/flicker.\r\n *\r\n * All of that lives in `InboxStore`; this is the binding. The filter is handed to the\r\n * store rather than being part of what identifies it, so changing tabs reloads the list\r\n * in place, keeping the rows on screen, instead of discarding the store and blanking the\r\n * panel.\r\n */\r\nexport function useInbox(options: UseInboxOptions = {}): UseInboxResult {\r\n const host = useHermsContext()\r\n const { status, category } = options\r\n\r\n // Only the first render's filter is used to create the store. Later changes go through\r\n // `setFilter` below, so this memo is keyed on the host alone.\r\n const store = useMemo(() => new InboxStore(host, { status, category }), [host])\r\n\r\n useEffect(() => store.connect(), [store])\r\n // A no-op when the filter is the one the store already has, which is the case on the\r\n // first render: `connect()` above has just loaded it.\r\n useEffect(() => {\r\n store.setFilter({ status, category })\r\n }, [store, status, category])\r\n\r\n const state = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot)\r\n\r\n return useMemo(\r\n () => ({\r\n ...state,\r\n // Arrow-function fields on the store: the same references for its whole life, so a\r\n // consumer's `useCallback`/`memo` dependencies do not churn as the state changes.\r\n loadMore: store.loadMore,\r\n markRead: store.markRead,\r\n markAllRead: store.markAllRead,\r\n archive: store.archive,\r\n remove: store.remove,\r\n refetch: store.refetch,\r\n }),\r\n [state, store],\r\n )\r\n}\r\n","import { useEffect, useMemo, useSyncExternalStore } from 'react'\r\nimport { PreferencesStore } from '@hermesihq/js'\r\nimport type { HermsPreferences, HermsPreferenceUpdate } from '@hermesihq/js'\r\nimport { useHermsContext } from './HermsProvider'\r\n\r\nexport interface UsePreferencesResult {\r\n /** `null` until the first read resolves. */\r\n preferences: HermsPreferences | null\r\n isLoading: boolean\r\n /** The failure of the last read or write, or `null`. Kept rather than thrown so a\r\n * preference centre can show it beside the controls instead of unmounting them. */\r\n error: Error | null\r\n /** Change one setting. Resolves once the server's new state has been applied. */\r\n setPreference: (update: HermsPreferenceUpdate) => Promise<void>\r\n /** Re-read, for a retry button. */\r\n reload: () => Promise<void>\r\n}\r\n\r\n/**\r\n * Headless preferences, the counterpart to `useInbox`.\r\n *\r\n * **No optimistic update, deliberately.** A toggle here is not a like button: turning\r\n * off a channel is a consent decision, and showing it as done before the server agreed is\r\n * showing somebody they have opted out when they may not have. The server answers `PATCH`\r\n * with the *whole* updated state, so the correct view arrives with the write and there is\r\n * nothing to reconcile. A per-row pending state in the host is a better answer to the\r\n * latency than a lie.\r\n *\r\n * **`error` is state, not an exception.** `setPreference` still rejects, so a caller can\r\n * `await` it and react per-control, but the hook also holds the failure so a preference\r\n * centre can render it without every consumer writing the same try/catch. A failed write\r\n * leaves the last known-good state on screen rather than an empty page.\r\n *\r\n * Critical categories arrive with `isCritical: true` and are always delivered. Render\r\n * them (a subscriber should see what they are receiving), but not as a control: an\r\n * affordance that refuses is worse than none.\r\n *\r\n * A binding over `PreferencesStore`, which holds the behaviour above.\r\n */\r\nexport function usePreferences(): UsePreferencesResult {\r\n const host = useHermsContext()\r\n const store = useMemo(() => new PreferencesStore(host), [host])\r\n\r\n useEffect(() => store.connect(), [store])\r\n\r\n const state = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot)\r\n\r\n return useMemo(\r\n () => ({ ...state, setPreference: store.setPreference, reload: store.reload }),\r\n [state, store],\r\n )\r\n}\r\n","import { useCallback, useEffect, useId, useMemo, useRef, useState, type CSSProperties, type KeyboardEvent } from 'react'\nimport * as Popover from '@radix-ui/react-popover'\nimport type { HermsInboxItem } from '@hermesihq/js'\nimport { useHermsContext } from './HermsProvider'\nimport { useInbox } from './useInbox'\nimport { useUnreadCount } from './useUnreadCount'\nimport { getHermsInboxStrings, type HermsLocale } from './locale'\nimport './HermsInbox.css'\n\nexport type HermsInboxPlacement = 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end'\n\nexport interface HermsInboxTheme {\n accent?: string\n radius?: string\n}\n\nexport interface HermsInboxProps {\n /** Popover position relative to the bell trigger. Default `bottom-end`. */\n placement?: HermsInboxPlacement\n /** Called when an item is activated (click, or Enter on a focused row).\n * Lets a host app drive its own router (for example\n * `onItemClick={(item) => navigate(item.actionUrl)}`) instead of a hard\n * page navigation. When omitted, activating an item with a non-null\n * `actionUrl` does a plain `window.location.assign` to it. Either way, the\n * item is marked read first. */\n onItemClick?: (item: HermsInboxItem) => void\n /** A couple of the most commonly re-themed CSS custom properties, settable\n * without a host app writing any CSS of its own. Anything else is\n * reachable by overriding `--herms-color-*`/`--herms-radius` variables\n * directly (see `HermsInbox.css`). */\n theme?: HermsInboxTheme\n /** `'auto'` (default) follows the visitor's OS `prefers-color-scheme`;\n * `'light'`/`'dark'` force one regardless of it. */\n colorScheme?: 'auto' | 'light' | 'dark'\n locale?: HermsLocale\n className?: string\n}\n\nconst placementToSideAlign: Record<HermsInboxPlacement, { side: 'top' | 'bottom'; align: 'start' | 'end' }> = {\n 'bottom-start': { side: 'bottom', align: 'start' },\n 'bottom-end': { side: 'bottom', align: 'end' },\n 'top-start': { side: 'top', align: 'start' },\n 'top-end': { side: 'top', align: 'end' },\n}\n\nfunction BellIcon() {\n return (\n <svg className=\"herms-inbox__bell\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M6 8a6 6 0 0 1 12 0c0 5 2 6 2 6H4s2-1 2-6\" />\n <path d=\"M10 21a2 2 0 0 0 4 0\" />\n </svg>\n )\n}\n\nfunction CloseIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden=\"true\">\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </svg>\n )\n}\n\nfunction ArchiveIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <rect x=\"3\" y=\"4\" width=\"18\" height=\"4\" rx=\"1\" />\n <path d=\"M5 8v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8\" />\n <path d=\"M10 13h4\" />\n </svg>\n )\n}\n\nfunction AlertIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" width=\"20\" height=\"20\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\n <path d=\"M12 8v5M12 16h.01\" />\n </svg>\n )\n}\n\nfunction relativeTime(iso: string, locale: HermsLocale): string {\n const then = new Date(iso).getTime()\n if (Number.isNaN(then)) return ''\n const diffSeconds = Math.max(0, Math.round((Date.now() - then) / 1000))\n const formatter = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' })\n if (diffSeconds < 60) return formatter.format(0, 'minute')\n const diffMinutes = Math.round(diffSeconds / 60)\n if (diffMinutes < 60) return formatter.format(-diffMinutes, 'minute')\n const diffHours = Math.round(diffMinutes / 60)\n if (diffHours < 24) return formatter.format(-diffHours, 'hour')\n const diffDays = Math.round(diffHours / 24)\n return formatter.format(-diffDays, 'day')\n}\n\n/**\n * `<HermsInbox />`: bell trigger + dropdown panel. Built\n * entirely on the headless `useInbox`/`useUnreadCount` hooks; every state\n * (loading, empty, error, populated) renders here, none of it invented by a\n * host app. Accessible: a real `<button>` trigger with `aria-label`/\n * `aria-expanded`, the panel as `role=\"menu\"` with roving-tabindex arrow-key\n * navigation, `Home`/`End`, `Escape`-to-close (native to Radix `Popover`,\n * which also returns focus to the trigger), and `Enter`/`Space` (native\n * `<button>` behavior) to activate the focused item.\n */\nexport function HermsInbox({ placement = 'bottom-end', onItemClick, theme, colorScheme = 'auto', locale, className }: HermsInboxProps) {\n const { client } = useHermsContext()\n const { unread, unseen } = useUnreadCount()\n const { items, isLoading, isLoadingMore, error, hasMore, loadMore, markRead, markAllRead, archive, refetch } = useInbox()\n const [open, setOpen] = useState(false)\n const [activeIndex, setActiveIndex] = useState(0)\n const itemRefs = useRef<Array<HTMLButtonElement | null>>([])\n const seenIdsRef = useRef<Set<string>>(new Set())\n const strings = getHermsInboxStrings(locale)\n const titleId = useId()\n\n // \"Seen\" (the bell was opened) is distinct from \"read.\" Marks\n // every currently loaded, not-yet-seen id seen once the panel is open and\n // has items, including any additional ids that arrive later via\n // `loadMore`/live updates while it stays open.\n useEffect(() => {\n if (!open || items.length === 0) return\n const toMark = items.filter((item) => !seenIdsRef.current.has(item.id)).map((item) => item.id)\n if (toMark.length === 0) return\n for (const id of toMark) seenIdsRef.current.add(id)\n void client.markSeen(toMark)\n }, [open, items, client])\n\n useEffect(() => {\n if (open) setActiveIndex(0)\n }, [open])\n\n const focusItem = useCallback((index: number) => {\n setActiveIndex(index)\n itemRefs.current[index]?.focus()\n }, [])\n\n const handleListKeyDown = useCallback(\n (event: KeyboardEvent<HTMLUListElement>) => {\n if (items.length === 0) return\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault()\n focusItem(Math.min(activeIndex + 1, items.length - 1))\n break\n case 'ArrowUp':\n event.preventDefault()\n focusItem(Math.max(activeIndex - 1, 0))\n break\n case 'Home':\n event.preventDefault()\n focusItem(0)\n break\n case 'End':\n event.preventDefault()\n focusItem(items.length - 1)\n break\n default:\n break\n }\n },\n [items.length, activeIndex, focusItem],\n )\n\n const handleActivate = useCallback(\n (item: HermsInboxItem) => {\n if (!item.readAt) void markRead(item.id)\n if (onItemClick) {\n onItemClick(item)\n } else if (item.actionUrl && typeof window !== 'undefined') {\n window.location.assign(item.actionUrl)\n }\n },\n [markRead, onItemClick],\n )\n\n const rootStyle = useMemo<CSSProperties>(() => {\n const style: CSSProperties & Record<string, string> = {}\n if (theme?.accent) style['--herms-color-accent'] = theme.accent\n if (theme?.radius) style['--herms-radius'] = theme.radius\n return style\n }, [theme?.accent, theme?.radius])\n\n const { side, align } = placementToSideAlign[placement]\n const rootClassName = ['herms-inbox', className].filter(Boolean).join(' ')\n // The panel is rendered by Radix in a portal under <body>, so it is not inside the root\n // above and inherits nothing from it. Whatever the root carries so that the stylesheet can\n // theme it, the panel has to carry as well: the theme's inline variables, the forced colour\n // scheme, and the host's class (so that one rule on `className` themes both).\n const panelClassName = ['herms-inbox__panel', className].filter(Boolean).join(' ')\n const colorSchemeAttribute = colorScheme === 'auto' ? undefined : colorScheme\n\n return (\n <div className={rootClassName} style={rootStyle} data-herms-color-scheme={colorSchemeAttribute}>\n <span className=\"herms-inbox__visually-hidden\" role=\"status\" aria-live=\"polite\">\n {unread > 0 ? strings.bellLabelWithUnread(unread) : ''}\n </span>\n <Popover.Root open={open} onOpenChange={setOpen}>\n <Popover.Trigger asChild>\n <button\n type=\"button\"\n className=\"herms-inbox__trigger\"\n aria-label={unread > 0 ? strings.bellLabelWithUnread(unread) : strings.bellLabel}\n aria-expanded={open}\n >\n <BellIcon />\n {unseen > 0 && (\n <span className=\"herms-inbox__badge\" aria-hidden=\"true\">\n {unseen > 99 ? '99+' : unseen}\n </span>\n )}\n </button>\n </Popover.Trigger>\n <Popover.Portal>\n <Popover.Content\n className={panelClassName}\n style={rootStyle}\n data-herms-color-scheme={colorSchemeAttribute}\n // The dialog needs a name of its own. Only the list, the bell and the loading\n // state were named; a dialog without one is announced as just \"dialog\".\n aria-labelledby={titleId}\n side={side}\n align={align}\n sideOffset={8}\n onOpenAutoFocus={(event) => {\n // Radix's default is to focus the content wrapper itself; we\n // want the first item (or the header when there are none)\n // focused instead, so keyboard users land somewhere useful.\n if (items.length > 0) {\n event.preventDefault()\n itemRefs.current[0]?.focus()\n }\n }}\n >\n <div className=\"herms-inbox__header\">\n <p className=\"herms-inbox__title\" id={titleId}>{strings.panelTitle}</p>\n <div className=\"herms-inbox__header-actions\">\n <button type=\"button\" className=\"herms-inbox__text-button\" onClick={() => void markAllRead()} disabled={unread === 0}>\n {strings.markAllRead}\n </button>\n <Popover.Close asChild>\n <button type=\"button\" className=\"herms-inbox__icon-button\" aria-label={strings.close}>\n <CloseIcon />\n </button>\n </Popover.Close>\n </div>\n </div>\n\n {isLoading ? (\n <div role=\"status\" aria-label={strings.loading}>\n {[0, 1, 2, 3].map((i) => (\n <div key={i} className=\"herms-inbox__skeleton-line\" style={{ width: i % 2 === 0 ? '85%' : '65%' }} />\n ))}\n </div>\n ) : error ? (\n <div className=\"herms-inbox__state\" role=\"alert\">\n <AlertIcon />\n <p className=\"herms-inbox__state-title\">{strings.errorTitle}</p>\n <button type=\"button\" className=\"herms-inbox__text-button\" onClick={() => refetch()}>\n {strings.retry}\n </button>\n </div>\n ) : items.length === 0 ? (\n <div className=\"herms-inbox__state\">{strings.empty}</div>\n ) : (\n <>\n <ul className=\"herms-inbox__list\" role=\"menu\" aria-label={strings.panelTitle} onKeyDown={handleListKeyDown}>\n {items.map((item, index) => (\n <li key={item.id} className=\"herms-inbox__list-row\" role=\"none\">\n <button\n type=\"button\"\n role=\"menuitem\"\n ref={(el) => {\n itemRefs.current[index] = el\n }}\n tabIndex={index === activeIndex ? 0 : -1}\n className=\"herms-inbox__item\"\n data-unread={!item.readAt}\n onFocus={() => setActiveIndex(index)}\n onClick={() => handleActivate(item)}\n >\n <span className=\"herms-inbox__item-title\">{item.title}</span>\n {item.body && <span className=\"herms-inbox__item-body\">{item.body}</span>}\n <span className=\"herms-inbox__item-meta\">{relativeTime(item.createdAt, locale ?? 'en')}</span>\n </button>\n <button\n type=\"button\"\n className=\"herms-inbox__archive-button\"\n aria-label={`${strings.archive}: ${item.title}`}\n onClick={(event) => {\n event.stopPropagation()\n void archive(item.id)\n }}\n >\n <ArchiveIcon />\n </button>\n </li>\n ))}\n </ul>\n {hasMore && (\n <button type=\"button\" className=\"herms-inbox__load-more\" onClick={loadMore} disabled={isLoadingMore}>\n {isLoadingMore ? strings.loadingMore : strings.loadMore}\n </button>\n )}\n </>\n )}\n </Popover.Content>\n </Popover.Portal>\n </Popover.Root>\n </div>\n )\n}\n","/**\r\n * `<HermsInbox />`'s own tiny, self-contained EN/FR string table.\r\n *\r\n * Deliberately **not** i18next: this package ships to arbitrary host apps\r\n * (with zero hard dependency on the host app's state library) that may run\r\n * their own i18n stack, a different one, or none at all. Pulling i18next in\r\n * as a real dependency here would be exactly the kind of host-app-shaped\r\n * assumption the SDK is supposed to avoid, for a handful of short strings.\r\n * `locale` is a prop of `<HermsInbox />`.\r\n */\r\n\r\nexport type HermsLocale = 'en' | 'fr'\r\n\r\nexport interface HermsInboxStrings {\r\n bellLabel: string\r\n bellLabelWithUnread: (count: number) => string\r\n panelTitle: string\r\n markAllRead: string\r\n close: string\r\n loading: string\r\n empty: string\r\n errorTitle: string\r\n retry: string\r\n loadMore: string\r\n loadingMore: string\r\n archive: string\r\n unreadDot: string\r\n}\r\n\r\nconst en: HermsInboxStrings = {\r\n bellLabel: 'Notifications',\r\n bellLabelWithUnread: (count) => `Notifications, ${count} unread`,\r\n panelTitle: 'Notifications',\r\n markAllRead: 'Mark all as read',\r\n close: 'Close',\r\n loading: 'Loading notifications…',\r\n empty: \"You're all caught up\",\r\n errorTitle: \"Couldn't load notifications.\",\r\n retry: 'Retry',\r\n loadMore: 'Load more',\r\n loadingMore: 'Loading…',\r\n archive: 'Archive',\r\n unreadDot: 'Unread',\r\n}\r\n\r\nconst fr: HermsInboxStrings = {\r\n bellLabel: 'Notifications',\r\n bellLabelWithUnread: (count) => `Notifications, ${count} non lues`,\r\n panelTitle: 'Notifications',\r\n markAllRead: 'Tout marquer comme lu',\r\n close: 'Fermer',\r\n loading: 'Chargement des notifications…',\r\n empty: 'Vous êtes à jour',\r\n errorTitle: \"Impossible de charger les notifications.\",\r\n retry: 'Réessayer',\r\n loadMore: 'Charger plus',\r\n loadingMore: 'Chargement…',\r\n archive: 'Archiver',\r\n unreadDot: 'Non lu',\r\n}\r\n\r\nconst dictionaries: Record<HermsLocale, HermsInboxStrings> = { en, fr }\r\n\r\nexport function getHermsInboxStrings(locale: HermsLocale | undefined): HermsInboxStrings {\r\n return dictionaries[locale ?? 'en'] ?? en\r\n}\r\n"],"mappings":"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","names":["import_js","import_react","import_js","import_react","import_js","import_react","import_js","import_react","import_jsx_runtime"]}
|
package/dist/index.css
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* src/HermsInbox.css */
|
|
2
|
-
.herms-inbox
|
|
2
|
+
.herms-inbox,
|
|
3
|
+
.herms-inbox__panel {
|
|
3
4
|
--herms-color-accent: #5b5bd6;
|
|
4
5
|
--herms-color-accent-foreground: #ffffff;
|
|
5
6
|
--herms-color-bg: #ffffff;
|
|
@@ -18,14 +19,17 @@
|
|
|
18
19
|
Helvetica,
|
|
19
20
|
Arial,
|
|
20
21
|
sans-serif;
|
|
21
|
-
position: relative;
|
|
22
|
-
display: inline-block;
|
|
23
22
|
font-family: var(--herms-font-family);
|
|
24
23
|
color: var(--herms-color-text);
|
|
25
24
|
line-height: 1.4;
|
|
26
25
|
}
|
|
26
|
+
.herms-inbox {
|
|
27
|
+
position: relative;
|
|
28
|
+
display: inline-block;
|
|
29
|
+
}
|
|
27
30
|
@media (prefers-color-scheme: dark) {
|
|
28
|
-
.herms-inbox:not([data-herms-color-scheme=light])
|
|
31
|
+
.herms-inbox:not([data-herms-color-scheme=light]),
|
|
32
|
+
.herms-inbox__panel:not([data-herms-color-scheme=light]) {
|
|
29
33
|
--herms-color-bg: #201f1a;
|
|
30
34
|
--herms-color-surface: #201f1a;
|
|
31
35
|
--herms-color-border: #38362f;
|
|
@@ -34,7 +38,8 @@
|
|
|
34
38
|
--herms-color-hover: #2b2a24;
|
|
35
39
|
}
|
|
36
40
|
}
|
|
37
|
-
.herms-inbox[data-herms-color-scheme=dark]
|
|
41
|
+
.herms-inbox[data-herms-color-scheme=dark],
|
|
42
|
+
.herms-inbox__panel[data-herms-color-scheme=dark] {
|
|
38
43
|
--herms-color-bg: #201f1a;
|
|
39
44
|
--herms-color-surface: #201f1a;
|
|
40
45
|
--herms-color-border: #38362f;
|
|
@@ -42,7 +47,9 @@
|
|
|
42
47
|
--herms-color-muted: #a6a19a;
|
|
43
48
|
--herms-color-hover: #2b2a24;
|
|
44
49
|
}
|
|
45
|
-
.herms-inbox
|
|
50
|
+
.herms-inbox *,
|
|
51
|
+
.herms-inbox__panel,
|
|
52
|
+
.herms-inbox__panel * {
|
|
46
53
|
box-sizing: border-box;
|
|
47
54
|
}
|
|
48
55
|
.herms-inbox__trigger {
|
package/dist/index.css.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/HermsInbox.css"],"sourcesContent":["/*\r\n * `<HermsInbox />`'s styles: scoped entirely under `.herms-inbox` and driven\r\n * by CSS custom properties, never Tailwind utility classes, so that Tailwind in the\r\n * host app cannot bleed into the widget. This\r\n * package has no Tailwind dependency at all; a host app's own Tailwind\r\n * build never touches these rules, and these rules never leak into the host's\r\n * own layout beyond `.herms-inbox`'s own box.\r\n *\r\n * Every visual value is a `--herms-*` custom property with a sensible\r\n * default here, overridable by a host app either globally (setting the\r\n * variable higher up the DOM) or per-instance via `<HermsInbox theme={...}>`,\r\n * which sets a few of these inline on the root element.\r\n *\r\n * Dark mode: `data-herms-color-scheme=\"dark\"` on the root element (set by the\r\n * `colorScheme` prop) forces dark values; leaving it unset (`colorScheme=\"auto\"`,\r\n * the default) follows the visitor's OS-level `prefers-color-scheme` instead.\r\n * A host app's own dark-mode mechanism (e.g. a `data-theme`\r\n * attribute) is a different, unrelated attribute and never bleeds in here.\r\n */\r\n\r\n.herms-inbox {\r\n --herms-color-accent: #5b5bd6;\r\n --herms-color-accent-foreground: #ffffff;\r\n --herms-color-bg: #ffffff;\r\n --herms-color-surface: #ffffff;\r\n --herms-color-border: #e5e3df;\r\n --herms-color-text: #1c1b18;\r\n --herms-color-muted: #6f6b64;\r\n --herms-color-danger: #b03a2a;\r\n --herms-color-unread-dot: #b03a2a;\r\n --herms-color-hover: #f4f3f1;\r\n --herms-radius: 10px;\r\n --herms-font-family:\r\n -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;\r\n\r\n position: relative;\r\n display: inline-block;\r\n font-family: var(--herms-font-family);\r\n color: var(--herms-color-text);\r\n line-height: 1.4;\r\n}\r\n\r\n@media (prefers-color-scheme: dark) {\r\n .herms-inbox:not([data-herms-color-scheme='light']) {\r\n --herms-color-bg: #201f1a;\r\n --herms-color-surface: #201f1a;\r\n --herms-color-border: #38362f;\r\n --herms-color-text: #f3f1ec;\r\n --herms-color-muted: #a6a19a;\r\n --herms-color-hover: #2b2a24;\r\n }\r\n}\r\n\r\n.herms-inbox[data-herms-color-scheme='dark'] {\r\n --herms-color-bg: #201f1a;\r\n --herms-color-surface: #201f1a;\r\n --herms-color-border: #38362f;\r\n --herms-color-text: #f3f1ec;\r\n --herms-color-muted: #a6a19a;\r\n --herms-color-hover: #2b2a24;\r\n}\r\n\r\n.herms-inbox * {\r\n box-sizing: border-box;\r\n}\r\n\r\n.herms-inbox__trigger {\r\n position: relative;\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 36px;\r\n height: 36px;\r\n border-radius: 999px;\r\n border: none;\r\n background: transparent;\r\n color: var(--herms-color-text);\r\n cursor: pointer;\r\n}\r\n\r\n.herms-inbox__trigger:hover {\r\n background: var(--herms-color-hover);\r\n}\r\n\r\n.herms-inbox__trigger:focus-visible {\r\n outline: 2px solid var(--herms-color-accent);\r\n outline-offset: 2px;\r\n}\r\n\r\n.herms-inbox__bell {\r\n width: 20px;\r\n height: 20px;\r\n}\r\n\r\n.herms-inbox__badge {\r\n position: absolute;\r\n top: 4px;\r\n right: 4px;\r\n min-width: 16px;\r\n height: 16px;\r\n padding: 0 4px;\r\n border-radius: 999px;\r\n background: var(--herms-color-unread-dot);\r\n color: #ffffff;\r\n font-size: 10px;\r\n font-weight: 600;\r\n line-height: 16px;\r\n text-align: center;\r\n}\r\n\r\n.herms-inbox__panel {\r\n width: 380px;\r\n max-width: calc(100vw - 24px);\r\n max-height: 480px;\r\n display: flex;\r\n flex-direction: column;\r\n border-radius: var(--herms-radius);\r\n border: 1px solid var(--herms-color-border);\r\n background: var(--herms-color-surface);\r\n box-shadow: 0 8px 30px rgba(0, 0, 0, 0.16);\r\n overflow: hidden;\r\n z-index: 50;\r\n}\r\n\r\n.herms-inbox__header {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n gap: 8px;\r\n padding: 12px 14px;\r\n border-bottom: 1px solid var(--herms-color-border);\r\n}\r\n\r\n.herms-inbox__title {\r\n font-size: 14px;\r\n font-weight: 600;\r\n margin: 0;\r\n}\r\n\r\n.herms-inbox__header-actions {\r\n display: flex;\r\n align-items: center;\r\n gap: 4px;\r\n}\r\n\r\n.herms-inbox__text-button {\r\n border: none;\r\n background: transparent;\r\n color: var(--herms-color-accent);\r\n font-size: 12px;\r\n font-weight: 500;\r\n cursor: pointer;\r\n padding: 4px 6px;\r\n border-radius: 6px;\r\n}\r\n\r\n.herms-inbox__text-button:hover {\r\n background: var(--herms-color-hover);\r\n}\r\n\r\n.herms-inbox__text-button:disabled {\r\n opacity: 0.5;\r\n cursor: not-allowed;\r\n}\r\n\r\n.herms-inbox__icon-button {\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 26px;\r\n height: 26px;\r\n border: none;\r\n background: transparent;\r\n color: var(--herms-color-muted);\r\n border-radius: 6px;\r\n cursor: pointer;\r\n}\r\n\r\n.herms-inbox__icon-button:hover {\r\n background: var(--herms-color-hover);\r\n color: var(--herms-color-text);\r\n}\r\n\r\n.herms-inbox__icon-button:focus-visible,\r\n.herms-inbox__text-button:focus-visible,\r\n.herms-inbox__item:focus-visible {\r\n outline: 2px solid var(--herms-color-accent);\r\n outline-offset: -2px;\r\n}\r\n\r\n.herms-inbox__list {\r\n list-style: none;\r\n margin: 0;\r\n padding: 4px;\r\n overflow-y: auto;\r\n flex: 1 1 auto;\r\n}\r\n\r\n.herms-inbox__list-row {\r\n display: flex;\r\n align-items: stretch;\r\n gap: 2px;\r\n}\r\n\r\n.herms-inbox__item {\r\n flex: 1 1 auto;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 2px;\r\n text-align: left;\r\n border: none;\r\n background: transparent;\r\n border-radius: 8px;\r\n padding: 8px 10px;\r\n cursor: pointer;\r\n color: inherit;\r\n font: inherit;\r\n}\r\n\r\n.herms-inbox__item:hover {\r\n background: var(--herms-color-hover);\r\n}\r\n\r\n.herms-inbox__item[data-unread='true'] .herms-inbox__item-title::before {\r\n content: '';\r\n display: inline-block;\r\n width: 6px;\r\n height: 6px;\r\n margin-right: 6px;\r\n border-radius: 999px;\r\n background: var(--herms-color-accent);\r\n}\r\n\r\n.herms-inbox__item-title {\r\n font-size: 13px;\r\n font-weight: 600;\r\n display: flex;\r\n align-items: center;\r\n}\r\n\r\n.herms-inbox__item-body {\r\n font-size: 12.5px;\r\n color: var(--herms-color-muted);\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n display: -webkit-box;\r\n -webkit-line-clamp: 2;\r\n -webkit-box-orient: vertical;\r\n}\r\n\r\n.herms-inbox__item-meta {\r\n font-size: 11px;\r\n color: var(--herms-color-muted);\r\n}\r\n\r\n.herms-inbox__archive-button {\r\n flex: 0 0 auto;\r\n align-self: center;\r\n width: 28px;\r\n height: 28px;\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n border: none;\r\n background: transparent;\r\n color: var(--herms-color-muted);\r\n border-radius: 6px;\r\n cursor: pointer;\r\n}\r\n\r\n.herms-inbox__archive-button:hover {\r\n background: var(--herms-color-hover);\r\n color: var(--herms-color-danger);\r\n}\r\n\r\n.herms-inbox__state {\r\n padding: 32px 16px;\r\n text-align: center;\r\n color: var(--herms-color-muted);\r\n font-size: 13px;\r\n}\r\n\r\n.herms-inbox__state-title {\r\n color: var(--herms-color-text);\r\n font-weight: 600;\r\n margin: 0 0 6px;\r\n}\r\n\r\n.herms-inbox__load-more {\r\n display: block;\r\n width: calc(100% - 8px);\r\n margin: 4px;\r\n padding: 8px;\r\n border-radius: 8px;\r\n border: 1px dashed var(--herms-color-border);\r\n background: transparent;\r\n color: var(--herms-color-accent);\r\n font-size: 12.5px;\r\n font-weight: 500;\r\n cursor: pointer;\r\n}\r\n\r\n.herms-inbox__load-more:hover {\r\n background: var(--herms-color-hover);\r\n}\r\n\r\n.herms-inbox__load-more:disabled {\r\n opacity: 0.6;\r\n cursor: not-allowed;\r\n}\r\n\r\n.herms-inbox__visually-hidden {\r\n position: absolute;\r\n width: 1px;\r\n height: 1px;\r\n padding: 0;\r\n margin: -1px;\r\n overflow: hidden;\r\n clip: rect(0, 0, 0, 0);\r\n white-space: nowrap;\r\n border: 0;\r\n}\r\n\r\n.herms-inbox__skeleton-line {\r\n height: 12px;\r\n border-radius: 4px;\r\n background: var(--herms-color-hover);\r\n margin: 6px 10px;\r\n}\r\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/HermsInbox.css"],"sourcesContent":["/*\n * `<HermsInbox />`'s styles: scoped entirely under `.herms-inbox` and driven\n * by CSS custom properties, never Tailwind utility classes, so that Tailwind in the\n * host app cannot bleed into the widget. This\n * package has no Tailwind dependency at all; a host app's own Tailwind\n * build never touches these rules, and these rules never leak into the host's\n * own layout beyond `.herms-inbox`'s own box.\n *\n * Every visual value is a `--herms-*` custom property with a sensible\n * default here, overridable by a host app either globally (setting the\n * variable higher up the DOM) or per-instance via `<HermsInbox theme={...}>`,\n * which sets a few of these inline on the root element.\n *\n * Dark mode: `data-herms-color-scheme=\"dark\"` on the root element (set by the\n * `colorScheme` prop) forces dark values; leaving it unset (`colorScheme=\"auto\"`,\n * the default) follows the visitor's OS-level `prefers-color-scheme` instead.\n * A host app's own dark-mode mechanism (e.g. a `data-theme`\n * attribute) is a different, unrelated attribute and never bleeds in here.\n */\n\n/*\n * The bell's root and the panel are NOT parent and child. Radix renders the panel in a portal\n * under <body>, so nothing the panel needs can be inherited from `.herms-inbox`. Everything the\n * root declares for its descendants (the variables, the text properties, the colour-scheme\n * rules, box-sizing) is therefore declared on both, in the same rule, so the two cannot\n * disagree. Before this the panel was transparent, borderless and square, and `theme` and\n * `colorScheme` could never reach it.\n */\n.herms-inbox,\n.herms-inbox__panel {\n --herms-color-accent: #5b5bd6;\n --herms-color-accent-foreground: #ffffff;\n --herms-color-bg: #ffffff;\n --herms-color-surface: #ffffff;\n --herms-color-border: #e5e3df;\n --herms-color-text: #1c1b18;\n --herms-color-muted: #6f6b64;\n --herms-color-danger: #b03a2a;\n --herms-color-unread-dot: #b03a2a;\n --herms-color-hover: #f4f3f1;\n --herms-radius: 10px;\n --herms-font-family:\n -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;\n\n font-family: var(--herms-font-family);\n color: var(--herms-color-text);\n line-height: 1.4;\n}\n\n.herms-inbox {\n position: relative;\n display: inline-block;\n}\n\n@media (prefers-color-scheme: dark) {\n .herms-inbox:not([data-herms-color-scheme='light']),\n .herms-inbox__panel:not([data-herms-color-scheme='light']) {\n --herms-color-bg: #201f1a;\n --herms-color-surface: #201f1a;\n --herms-color-border: #38362f;\n --herms-color-text: #f3f1ec;\n --herms-color-muted: #a6a19a;\n --herms-color-hover: #2b2a24;\n }\n}\n\n.herms-inbox[data-herms-color-scheme='dark'],\n.herms-inbox__panel[data-herms-color-scheme='dark'] {\n --herms-color-bg: #201f1a;\n --herms-color-surface: #201f1a;\n --herms-color-border: #38362f;\n --herms-color-text: #f3f1ec;\n --herms-color-muted: #a6a19a;\n --herms-color-hover: #2b2a24;\n}\n\n.herms-inbox *,\n.herms-inbox__panel,\n.herms-inbox__panel * {\n box-sizing: border-box;\n}\n\n.herms-inbox__trigger {\n position: relative;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n border-radius: 999px;\n border: none;\n background: transparent;\n color: var(--herms-color-text);\n cursor: pointer;\n}\n\n.herms-inbox__trigger:hover {\n background: var(--herms-color-hover);\n}\n\n.herms-inbox__trigger:focus-visible {\n outline: 2px solid var(--herms-color-accent);\n outline-offset: 2px;\n}\n\n.herms-inbox__bell {\n width: 20px;\n height: 20px;\n}\n\n.herms-inbox__badge {\n position: absolute;\n top: 4px;\n right: 4px;\n min-width: 16px;\n height: 16px;\n padding: 0 4px;\n border-radius: 999px;\n background: var(--herms-color-unread-dot);\n color: #ffffff;\n font-size: 10px;\n font-weight: 600;\n line-height: 16px;\n text-align: center;\n}\n\n.herms-inbox__panel {\n width: 380px;\n max-width: calc(100vw - 24px);\n max-height: 480px;\n display: flex;\n flex-direction: column;\n border-radius: var(--herms-radius);\n border: 1px solid var(--herms-color-border);\n background: var(--herms-color-surface);\n box-shadow: 0 8px 30px rgba(0, 0, 0, 0.16);\n overflow: hidden;\n z-index: 50;\n}\n\n.herms-inbox__header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 12px 14px;\n border-bottom: 1px solid var(--herms-color-border);\n}\n\n.herms-inbox__title {\n font-size: 14px;\n font-weight: 600;\n margin: 0;\n}\n\n.herms-inbox__header-actions {\n display: flex;\n align-items: center;\n gap: 4px;\n}\n\n.herms-inbox__text-button {\n border: none;\n background: transparent;\n color: var(--herms-color-accent);\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n padding: 4px 6px;\n border-radius: 6px;\n}\n\n.herms-inbox__text-button:hover {\n background: var(--herms-color-hover);\n}\n\n.herms-inbox__text-button:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.herms-inbox__icon-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 26px;\n height: 26px;\n border: none;\n background: transparent;\n color: var(--herms-color-muted);\n border-radius: 6px;\n cursor: pointer;\n}\n\n.herms-inbox__icon-button:hover {\n background: var(--herms-color-hover);\n color: var(--herms-color-text);\n}\n\n.herms-inbox__icon-button:focus-visible,\n.herms-inbox__text-button:focus-visible,\n.herms-inbox__item:focus-visible {\n outline: 2px solid var(--herms-color-accent);\n outline-offset: -2px;\n}\n\n.herms-inbox__list {\n list-style: none;\n margin: 0;\n padding: 4px;\n overflow-y: auto;\n flex: 1 1 auto;\n}\n\n.herms-inbox__list-row {\n display: flex;\n align-items: stretch;\n gap: 2px;\n}\n\n.herms-inbox__item {\n flex: 1 1 auto;\n display: flex;\n flex-direction: column;\n gap: 2px;\n text-align: left;\n border: none;\n background: transparent;\n border-radius: 8px;\n padding: 8px 10px;\n cursor: pointer;\n color: inherit;\n font: inherit;\n}\n\n.herms-inbox__item:hover {\n background: var(--herms-color-hover);\n}\n\n.herms-inbox__item[data-unread='true'] .herms-inbox__item-title::before {\n content: '';\n display: inline-block;\n width: 6px;\n height: 6px;\n margin-right: 6px;\n border-radius: 999px;\n background: var(--herms-color-accent);\n}\n\n.herms-inbox__item-title {\n font-size: 13px;\n font-weight: 600;\n display: flex;\n align-items: center;\n}\n\n.herms-inbox__item-body {\n font-size: 12.5px;\n color: var(--herms-color-muted);\n overflow: hidden;\n text-overflow: ellipsis;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n}\n\n.herms-inbox__item-meta {\n font-size: 11px;\n color: var(--herms-color-muted);\n}\n\n.herms-inbox__archive-button {\n flex: 0 0 auto;\n align-self: center;\n width: 28px;\n height: 28px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: none;\n background: transparent;\n color: var(--herms-color-muted);\n border-radius: 6px;\n cursor: pointer;\n}\n\n.herms-inbox__archive-button:hover {\n background: var(--herms-color-hover);\n color: var(--herms-color-danger);\n}\n\n.herms-inbox__state {\n padding: 32px 16px;\n text-align: center;\n color: var(--herms-color-muted);\n font-size: 13px;\n}\n\n.herms-inbox__state-title {\n color: var(--herms-color-text);\n font-weight: 600;\n margin: 0 0 6px;\n}\n\n.herms-inbox__load-more {\n display: block;\n width: calc(100% - 8px);\n margin: 4px;\n padding: 8px;\n border-radius: 8px;\n border: 1px dashed var(--herms-color-border);\n background: transparent;\n color: var(--herms-color-accent);\n font-size: 12.5px;\n font-weight: 500;\n cursor: pointer;\n}\n\n.herms-inbox__load-more:hover {\n background: var(--herms-color-hover);\n}\n\n.herms-inbox__load-more:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n.herms-inbox__visually-hidden {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n}\n\n.herms-inbox__skeleton-line {\n height: 12px;\n border-radius: 4px;\n background: var(--herms-color-hover);\n margin: 6px 10px;\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.js
CHANGED
|
@@ -76,7 +76,7 @@ function usePreferences() {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
// src/HermsInbox.tsx
|
|
79
|
-
import { useCallback, useEffect as useEffect5, useMemo as useMemo5, useRef, useState } from "react";
|
|
79
|
+
import { useCallback, useEffect as useEffect5, useId, useMemo as useMemo5, useRef, useState } from "react";
|
|
80
80
|
import * as Popover from "@radix-ui/react-popover";
|
|
81
81
|
|
|
82
82
|
// src/locale.ts
|
|
@@ -167,6 +167,7 @@ function HermsInbox({ placement = "bottom-end", onItemClick, theme, colorScheme
|
|
|
167
167
|
const itemRefs = useRef([]);
|
|
168
168
|
const seenIdsRef = useRef(/* @__PURE__ */ new Set());
|
|
169
169
|
const strings = getHermsInboxStrings(locale);
|
|
170
|
+
const titleId = useId();
|
|
170
171
|
useEffect5(() => {
|
|
171
172
|
if (!open || items.length === 0) return;
|
|
172
173
|
const toMark = items.filter((item) => !seenIdsRef.current.has(item.id)).map((item) => item.id);
|
|
@@ -226,7 +227,9 @@ function HermsInbox({ placement = "bottom-end", onItemClick, theme, colorScheme
|
|
|
226
227
|
}, [theme?.accent, theme?.radius]);
|
|
227
228
|
const { side, align } = placementToSideAlign[placement];
|
|
228
229
|
const rootClassName = ["herms-inbox", className].filter(Boolean).join(" ");
|
|
229
|
-
|
|
230
|
+
const panelClassName = ["herms-inbox__panel", className].filter(Boolean).join(" ");
|
|
231
|
+
const colorSchemeAttribute = colorScheme === "auto" ? void 0 : colorScheme;
|
|
232
|
+
return /* @__PURE__ */ jsxs("div", { className: rootClassName, style: rootStyle, "data-herms-color-scheme": colorSchemeAttribute, children: [
|
|
230
233
|
/* @__PURE__ */ jsx2("span", { className: "herms-inbox__visually-hidden", role: "status", "aria-live": "polite", children: unread > 0 ? strings.bellLabelWithUnread(unread) : "" }),
|
|
231
234
|
/* @__PURE__ */ jsxs(Popover.Root, { open, onOpenChange: setOpen, children: [
|
|
232
235
|
/* @__PURE__ */ jsx2(Popover.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
@@ -245,7 +248,10 @@ function HermsInbox({ placement = "bottom-end", onItemClick, theme, colorScheme
|
|
|
245
248
|
/* @__PURE__ */ jsx2(Popover.Portal, { children: /* @__PURE__ */ jsxs(
|
|
246
249
|
Popover.Content,
|
|
247
250
|
{
|
|
248
|
-
className:
|
|
251
|
+
className: panelClassName,
|
|
252
|
+
style: rootStyle,
|
|
253
|
+
"data-herms-color-scheme": colorSchemeAttribute,
|
|
254
|
+
"aria-labelledby": titleId,
|
|
249
255
|
side,
|
|
250
256
|
align,
|
|
251
257
|
sideOffset: 8,
|
|
@@ -257,7 +263,7 @@ function HermsInbox({ placement = "bottom-end", onItemClick, theme, colorScheme
|
|
|
257
263
|
},
|
|
258
264
|
children: [
|
|
259
265
|
/* @__PURE__ */ jsxs("div", { className: "herms-inbox__header", children: [
|
|
260
|
-
/* @__PURE__ */ jsx2("p", { className: "herms-inbox__title", children: strings.panelTitle }),
|
|
266
|
+
/* @__PURE__ */ jsx2("p", { className: "herms-inbox__title", id: titleId, children: strings.panelTitle }),
|
|
261
267
|
/* @__PURE__ */ jsxs("div", { className: "herms-inbox__header-actions", children: [
|
|
262
268
|
/* @__PURE__ */ jsx2("button", { type: "button", className: "herms-inbox__text-button", onClick: () => void markAllRead(), disabled: unread === 0, children: strings.markAllRead }),
|
|
263
269
|
/* @__PURE__ */ jsx2(Popover.Close, { asChild: true, children: /* @__PURE__ */ jsx2("button", { type: "button", className: "herms-inbox__icon-button", "aria-label": strings.close, children: /* @__PURE__ */ jsx2(CloseIcon, {}) }) })
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/HermsProvider.tsx","../src/useUnreadCount.ts","../src/useInbox.ts","../src/usePreferences.ts","../src/HermsInbox.tsx","../src/locale.ts"],"sourcesContent":["/**\n * `@hermesihq/react`: React bindings for Hermesi's in-app inbox.\n *\n * Two parts, from the one entry point:\n *\n * - **The client surface**, re-exported from `@hermesihq/js` so that\n * `import { HermsClient } from '@hermesihq/react'` keeps working. It is exactly the\n * names this package has always exported from there and no more: the stores and the\n * session are `@hermesihq/js`'s to export, and a React host has the hooks.\n * - **`HermsProvider`, `useUnreadCount`, `useInbox`, `usePreferences` and\n * `<HermsInbox />`.** The hooks are a thin binding over `@hermesihq/js`'s stores; the\n * component is a ready bell and panel built on them.\n */\nexport { HermsClient, HermsApiError, HERMS_CHANNELS, decodeSubscriberTokenExp } from '@hermesihq/js'\nexport type {\n HermsCategoryPreference,\n HermsChannel,\n HermsChannelPreference,\n HermsClientOptions,\n HermsErrorDetail,\n HermsEventListener,\n HermsInboxCategory,\n HermsInboxCounts,\n HermsInboxItem,\n HermsInboxListParams,\n HermsInboxPage,\n HermsInboxReadStatus,\n HermsPreferences,\n HermsPreferenceUpdate,\n HermsRealtimeEvent,\n HermsRegisterChannelParams,\n} from '@hermesihq/js'\n\nexport { HermsProvider, useHermsContext, type HermsProviderProps } from './HermsProvider'\nexport { useUnreadCount, type UseUnreadCountResult } from './useUnreadCount'\nexport { useInbox, type UseInboxOptions, type UseInboxResult } from './useInbox'\nexport { usePreferences, type UsePreferencesResult } from './usePreferences'\nexport { HermsInbox, type HermsInboxProps, type HermsInboxPlacement, type HermsInboxTheme } from './HermsInbox'\nexport { type HermsLocale } from './locale'\n","import { createContext, useContext, useEffect, useMemo, type ReactNode } from 'react'\r\nimport type { HermsClient } from '@hermesihq/js'\r\nimport { HermsSession, type HermsStoreHost } from '@hermesihq/js'\r\n\r\n/**\r\n * What every hook in this package reads from context. Exactly the shape the stores take\r\n * as their host, so a hook hands this straight to the store it creates.\r\n */\r\ntype HermsContextValue = HermsStoreHost\r\n\r\nconst HermsContext = createContext<HermsContextValue | null>(null)\r\n\r\nexport interface HermsProviderProps {\r\n client: HermsClient\r\n children: ReactNode\r\n}\r\n\r\n/**\r\n * `<HermsProvider client={new HermsClient({...})}>`: one `HermsClient` instance for the\r\n * whole subtree. Opens the client's real-time subscription once on mount and tears it\r\n * (and the client's own `onTokenExpiring` timer) down on unmount; every listener\r\n * registered via `addEventListener` shares that single connection rather than each hook\r\n * paying for its own.\r\n *\r\n * The connection itself is `HermsSession`, which knows nothing about React: this\r\n * component is the few lines that tie its lifetime to a mount. That is deliberate, and it\r\n * is the same session a Vue or vanilla page uses.\r\n *\r\n * `client` is expected to be a stable reference (constructed once, e.g. in\r\n * `useMemo`/module scope in the host app). Passing a new instance on every render tears\r\n * down and reopens the underlying connection every render.\r\n */\r\nexport function HermsProvider({ client, children }: HermsProviderProps) {\r\n const session = useMemo(() => new HermsSession(client), [client])\r\n\r\n // `connect()` returns its own release, which is the effect's cleanup. Reference-counted\r\n // in the session, so Strict Mode's connect, release, connect comes out right.\r\n useEffect(() => session.connect(), [session])\r\n\r\n const value = useMemo<HermsContextValue>(\r\n () => ({ client, addEventListener: session.addEventListener }),\r\n [client, session],\r\n )\r\n\r\n return <HermsContext.Provider value={value}>{children}</HermsContext.Provider>\r\n}\r\n\r\n/** Every hook/component in this package throws this same error outside a\r\n * `<HermsProvider>`: there is no sensible default client to fall back to. */\r\nexport function useHermsContext(): HermsContextValue {\r\n const ctx = useContext(HermsContext)\r\n if (!ctx) {\r\n throw new Error('@hermesihq/react: this hook/component must be rendered inside <HermsProvider client={...}>.')\r\n }\r\n return ctx\r\n}\r\n","import { useEffect, useMemo, useSyncExternalStore } from 'react'\r\nimport { CountsStore, type CountsState } from '@hermesihq/js'\r\nimport { useHermsContext } from './HermsProvider'\r\n\r\nexport type UseUnreadCountResult = CountsState\r\n\r\n/**\r\n * Headless. `{unread, unseen, isLoading}`, kept live via the provider's shared\r\n * subscription: one `getCounts()` call on mount, then recomputed on every\r\n * `counts.changed` event (real-time push, or the client's own 60s polling fallback) for\r\n * as long as this component stays mounted.\r\n *\r\n * A binding over `CountsStore`, which holds all of that. The store's constructor does\r\n * nothing, so React discarding a memoised one costs nothing: it is the `connect()` in the\r\n * effect that starts work.\r\n */\r\nexport function useUnreadCount(): UseUnreadCountResult {\r\n const host = useHermsContext()\r\n const store = useMemo(() => new CountsStore(host), [host])\r\n\r\n useEffect(() => store.connect(), [store])\r\n\r\n // The initial state is also the server snapshot: nothing connects during a server\r\n // render, so this is exactly what the server would produce.\r\n return useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot)\r\n}\r\n","import { useEffect, useMemo, useSyncExternalStore } from 'react'\r\nimport { InboxStore } from '@hermesihq/js'\r\nimport type { HermsInboxItem, HermsInboxReadStatus } from '@hermesihq/js'\r\nimport { useHermsContext } from './HermsProvider'\r\n\r\nexport interface UseInboxOptions {\r\n status?: HermsInboxReadStatus\r\n category?: string\r\n}\r\n\r\nexport interface UseInboxResult {\r\n items: HermsInboxItem[]\r\n isLoading: boolean\r\n isLoadingMore: boolean\r\n error: Error | null\r\n hasMore: boolean\r\n loadMore: () => void\r\n markRead: (id: string) => Promise<void>\r\n markAllRead: () => Promise<void>\r\n archive: (id: string) => Promise<void>\r\n remove: (id: string) => Promise<void>\r\n refetch: () => void\r\n}\r\n\r\n/**\r\n * Headless, cursor-paginated inbox list state: thin wrappers around `HermsClient`'s own\r\n * methods that also patch local state, so a consumer never has to manually refetch the\r\n * whole list after every mutating action.\r\n *\r\n * Live-updated on `item.created`: the new item is prepended immediately from the\r\n * (deliberately minimal: `{id, title, created_at}`) SSE payload so the UI reacts\r\n * instantly, then reconciled against a fresh first page fetched in the background so the\r\n * authoritative row (body, category, action_url) lands without a full list\r\n * reload/flicker.\r\n *\r\n * All of that lives in `InboxStore`; this is the binding. The filter is handed to the\r\n * store rather than being part of what identifies it, so changing tabs reloads the list\r\n * in place, keeping the rows on screen, instead of discarding the store and blanking the\r\n * panel.\r\n */\r\nexport function useInbox(options: UseInboxOptions = {}): UseInboxResult {\r\n const host = useHermsContext()\r\n const { status, category } = options\r\n\r\n // Only the first render's filter is used to create the store. Later changes go through\r\n // `setFilter` below, so this memo is keyed on the host alone.\r\n const store = useMemo(() => new InboxStore(host, { status, category }), [host])\r\n\r\n useEffect(() => store.connect(), [store])\r\n // A no-op when the filter is the one the store already has, which is the case on the\r\n // first render: `connect()` above has just loaded it.\r\n useEffect(() => {\r\n store.setFilter({ status, category })\r\n }, [store, status, category])\r\n\r\n const state = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot)\r\n\r\n return useMemo(\r\n () => ({\r\n ...state,\r\n // Arrow-function fields on the store: the same references for its whole life, so a\r\n // consumer's `useCallback`/`memo` dependencies do not churn as the state changes.\r\n loadMore: store.loadMore,\r\n markRead: store.markRead,\r\n markAllRead: store.markAllRead,\r\n archive: store.archive,\r\n remove: store.remove,\r\n refetch: store.refetch,\r\n }),\r\n [state, store],\r\n )\r\n}\r\n","import { useEffect, useMemo, useSyncExternalStore } from 'react'\r\nimport { PreferencesStore } from '@hermesihq/js'\r\nimport type { HermsPreferences, HermsPreferenceUpdate } from '@hermesihq/js'\r\nimport { useHermsContext } from './HermsProvider'\r\n\r\nexport interface UsePreferencesResult {\r\n /** `null` until the first read resolves. */\r\n preferences: HermsPreferences | null\r\n isLoading: boolean\r\n /** The failure of the last read or write, or `null`. Kept rather than thrown so a\r\n * preference centre can show it beside the controls instead of unmounting them. */\r\n error: Error | null\r\n /** Change one setting. Resolves once the server's new state has been applied. */\r\n setPreference: (update: HermsPreferenceUpdate) => Promise<void>\r\n /** Re-read, for a retry button. */\r\n reload: () => Promise<void>\r\n}\r\n\r\n/**\r\n * Headless preferences, the counterpart to `useInbox`.\r\n *\r\n * **No optimistic update, deliberately.** A toggle here is not a like button: turning\r\n * off a channel is a consent decision, and showing it as done before the server agreed is\r\n * showing somebody they have opted out when they may not have. The server answers `PATCH`\r\n * with the *whole* updated state, so the correct view arrives with the write and there is\r\n * nothing to reconcile. A per-row pending state in the host is a better answer to the\r\n * latency than a lie.\r\n *\r\n * **`error` is state, not an exception.** `setPreference` still rejects, so a caller can\r\n * `await` it and react per-control, but the hook also holds the failure so a preference\r\n * centre can render it without every consumer writing the same try/catch. A failed write\r\n * leaves the last known-good state on screen rather than an empty page.\r\n *\r\n * Critical categories arrive with `isCritical: true` and are always delivered. Render\r\n * them (a subscriber should see what they are receiving), but not as a control: an\r\n * affordance that refuses is worse than none.\r\n *\r\n * A binding over `PreferencesStore`, which holds the behaviour above.\r\n */\r\nexport function usePreferences(): UsePreferencesResult {\r\n const host = useHermsContext()\r\n const store = useMemo(() => new PreferencesStore(host), [host])\r\n\r\n useEffect(() => store.connect(), [store])\r\n\r\n const state = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot)\r\n\r\n return useMemo(\r\n () => ({ ...state, setPreference: store.setPreference, reload: store.reload }),\r\n [state, store],\r\n )\r\n}\r\n","import { useCallback, useEffect, useMemo, useRef, useState, type CSSProperties, type KeyboardEvent } from 'react'\r\nimport * as Popover from '@radix-ui/react-popover'\r\nimport type { HermsInboxItem } from '@hermesihq/js'\r\nimport { useHermsContext } from './HermsProvider'\r\nimport { useInbox } from './useInbox'\r\nimport { useUnreadCount } from './useUnreadCount'\r\nimport { getHermsInboxStrings, type HermsLocale } from './locale'\r\nimport './HermsInbox.css'\r\n\r\nexport type HermsInboxPlacement = 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end'\r\n\r\nexport interface HermsInboxTheme {\r\n accent?: string\r\n radius?: string\r\n}\r\n\r\nexport interface HermsInboxProps {\r\n /** Popover position relative to the bell trigger. Default `bottom-end`. */\r\n placement?: HermsInboxPlacement\r\n /** Called when an item is activated (click, or Enter on a focused row).\r\n * Lets a host app drive its own router (for example\r\n * `onItemClick={(item) => navigate(item.actionUrl)}`) instead of a hard\r\n * page navigation. When omitted, activating an item with a non-null\r\n * `actionUrl` does a plain `window.location.assign` to it. Either way, the\r\n * item is marked read first. */\r\n onItemClick?: (item: HermsInboxItem) => void\r\n /** A couple of the most commonly re-themed CSS custom properties, settable\r\n * without a host app writing any CSS of its own. Anything else is\r\n * reachable by overriding `--herms-color-*`/`--herms-radius` variables\r\n * directly (see `HermsInbox.css`). */\r\n theme?: HermsInboxTheme\r\n /** `'auto'` (default) follows the visitor's OS `prefers-color-scheme`;\r\n * `'light'`/`'dark'` force one regardless of it. */\r\n colorScheme?: 'auto' | 'light' | 'dark'\r\n locale?: HermsLocale\r\n className?: string\r\n}\r\n\r\nconst placementToSideAlign: Record<HermsInboxPlacement, { side: 'top' | 'bottom'; align: 'start' | 'end' }> = {\r\n 'bottom-start': { side: 'bottom', align: 'start' },\r\n 'bottom-end': { side: 'bottom', align: 'end' },\r\n 'top-start': { side: 'top', align: 'start' },\r\n 'top-end': { side: 'top', align: 'end' },\r\n}\r\n\r\nfunction BellIcon() {\r\n return (\r\n <svg className=\"herms-inbox__bell\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\r\n <path d=\"M6 8a6 6 0 0 1 12 0c0 5 2 6 2 6H4s2-1 2-6\" />\r\n <path d=\"M10 21a2 2 0 0 0 4 0\" />\r\n </svg>\r\n )\r\n}\r\n\r\nfunction CloseIcon() {\r\n return (\r\n <svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden=\"true\">\r\n <path d=\"M18 6 6 18M6 6l12 12\" />\r\n </svg>\r\n )\r\n}\r\n\r\nfunction ArchiveIcon() {\r\n return (\r\n <svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\r\n <rect x=\"3\" y=\"4\" width=\"18\" height=\"4\" rx=\"1\" />\r\n <path d=\"M5 8v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8\" />\r\n <path d=\"M10 13h4\" />\r\n </svg>\r\n )\r\n}\r\n\r\nfunction AlertIcon() {\r\n return (\r\n <svg viewBox=\"0 0 24 24\" width=\"20\" height=\"20\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\r\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\r\n <path d=\"M12 8v5M12 16h.01\" />\r\n </svg>\r\n )\r\n}\r\n\r\nfunction relativeTime(iso: string, locale: HermsLocale): string {\r\n const then = new Date(iso).getTime()\r\n if (Number.isNaN(then)) return ''\r\n const diffSeconds = Math.max(0, Math.round((Date.now() - then) / 1000))\r\n const formatter = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' })\r\n if (diffSeconds < 60) return formatter.format(0, 'minute')\r\n const diffMinutes = Math.round(diffSeconds / 60)\r\n if (diffMinutes < 60) return formatter.format(-diffMinutes, 'minute')\r\n const diffHours = Math.round(diffMinutes / 60)\r\n if (diffHours < 24) return formatter.format(-diffHours, 'hour')\r\n const diffDays = Math.round(diffHours / 24)\r\n return formatter.format(-diffDays, 'day')\r\n}\r\n\r\n/**\r\n * `<HermsInbox />`: bell trigger + dropdown panel. Built\r\n * entirely on the headless `useInbox`/`useUnreadCount` hooks; every state\r\n * (loading, empty, error, populated) renders here, none of it invented by a\r\n * host app. Accessible: a real `<button>` trigger with `aria-label`/\r\n * `aria-expanded`, the panel as `role=\"menu\"` with roving-tabindex arrow-key\r\n * navigation, `Home`/`End`, `Escape`-to-close (native to Radix `Popover`,\r\n * which also returns focus to the trigger), and `Enter`/`Space` (native\r\n * `<button>` behavior) to activate the focused item.\r\n */\r\nexport function HermsInbox({ placement = 'bottom-end', onItemClick, theme, colorScheme = 'auto', locale, className }: HermsInboxProps) {\r\n const { client } = useHermsContext()\r\n const { unread, unseen } = useUnreadCount()\r\n const { items, isLoading, isLoadingMore, error, hasMore, loadMore, markRead, markAllRead, archive, refetch } = useInbox()\r\n const [open, setOpen] = useState(false)\r\n const [activeIndex, setActiveIndex] = useState(0)\r\n const itemRefs = useRef<Array<HTMLButtonElement | null>>([])\r\n const seenIdsRef = useRef<Set<string>>(new Set())\r\n const strings = getHermsInboxStrings(locale)\r\n\r\n // \"Seen\" (the bell was opened) is distinct from \"read.\" Marks\r\n // every currently loaded, not-yet-seen id seen once the panel is open and\r\n // has items, including any additional ids that arrive later via\r\n // `loadMore`/live updates while it stays open.\r\n useEffect(() => {\r\n if (!open || items.length === 0) return\r\n const toMark = items.filter((item) => !seenIdsRef.current.has(item.id)).map((item) => item.id)\r\n if (toMark.length === 0) return\r\n for (const id of toMark) seenIdsRef.current.add(id)\r\n void client.markSeen(toMark)\r\n }, [open, items, client])\r\n\r\n useEffect(() => {\r\n if (open) setActiveIndex(0)\r\n }, [open])\r\n\r\n const focusItem = useCallback((index: number) => {\r\n setActiveIndex(index)\r\n itemRefs.current[index]?.focus()\r\n }, [])\r\n\r\n const handleListKeyDown = useCallback(\r\n (event: KeyboardEvent<HTMLUListElement>) => {\r\n if (items.length === 0) return\r\n switch (event.key) {\r\n case 'ArrowDown':\r\n event.preventDefault()\r\n focusItem(Math.min(activeIndex + 1, items.length - 1))\r\n break\r\n case 'ArrowUp':\r\n event.preventDefault()\r\n focusItem(Math.max(activeIndex - 1, 0))\r\n break\r\n case 'Home':\r\n event.preventDefault()\r\n focusItem(0)\r\n break\r\n case 'End':\r\n event.preventDefault()\r\n focusItem(items.length - 1)\r\n break\r\n default:\r\n break\r\n }\r\n },\r\n [items.length, activeIndex, focusItem],\r\n )\r\n\r\n const handleActivate = useCallback(\r\n (item: HermsInboxItem) => {\r\n if (!item.readAt) void markRead(item.id)\r\n if (onItemClick) {\r\n onItemClick(item)\r\n } else if (item.actionUrl && typeof window !== 'undefined') {\r\n window.location.assign(item.actionUrl)\r\n }\r\n },\r\n [markRead, onItemClick],\r\n )\r\n\r\n const rootStyle = useMemo<CSSProperties>(() => {\r\n const style: CSSProperties & Record<string, string> = {}\r\n if (theme?.accent) style['--herms-color-accent'] = theme.accent\r\n if (theme?.radius) style['--herms-radius'] = theme.radius\r\n return style\r\n }, [theme?.accent, theme?.radius])\r\n\r\n const { side, align } = placementToSideAlign[placement]\r\n const rootClassName = ['herms-inbox', className].filter(Boolean).join(' ')\r\n\r\n return (\r\n <div className={rootClassName} style={rootStyle} data-herms-color-scheme={colorScheme === 'auto' ? undefined : colorScheme}>\r\n <span className=\"herms-inbox__visually-hidden\" role=\"status\" aria-live=\"polite\">\r\n {unread > 0 ? strings.bellLabelWithUnread(unread) : ''}\r\n </span>\r\n <Popover.Root open={open} onOpenChange={setOpen}>\r\n <Popover.Trigger asChild>\r\n <button\r\n type=\"button\"\r\n className=\"herms-inbox__trigger\"\r\n aria-label={unread > 0 ? strings.bellLabelWithUnread(unread) : strings.bellLabel}\r\n aria-expanded={open}\r\n >\r\n <BellIcon />\r\n {unseen > 0 && (\r\n <span className=\"herms-inbox__badge\" aria-hidden=\"true\">\r\n {unseen > 99 ? '99+' : unseen}\r\n </span>\r\n )}\r\n </button>\r\n </Popover.Trigger>\r\n <Popover.Portal>\r\n <Popover.Content\r\n className=\"herms-inbox__panel\"\r\n side={side}\r\n align={align}\r\n sideOffset={8}\r\n onOpenAutoFocus={(event) => {\r\n // Radix's default is to focus the content wrapper itself; we\r\n // want the first item (or the header when there are none)\r\n // focused instead, so keyboard users land somewhere useful.\r\n if (items.length > 0) {\r\n event.preventDefault()\r\n itemRefs.current[0]?.focus()\r\n }\r\n }}\r\n >\r\n <div className=\"herms-inbox__header\">\r\n <p className=\"herms-inbox__title\">{strings.panelTitle}</p>\r\n <div className=\"herms-inbox__header-actions\">\r\n <button type=\"button\" className=\"herms-inbox__text-button\" onClick={() => void markAllRead()} disabled={unread === 0}>\r\n {strings.markAllRead}\r\n </button>\r\n <Popover.Close asChild>\r\n <button type=\"button\" className=\"herms-inbox__icon-button\" aria-label={strings.close}>\r\n <CloseIcon />\r\n </button>\r\n </Popover.Close>\r\n </div>\r\n </div>\r\n\r\n {isLoading ? (\r\n <div role=\"status\" aria-label={strings.loading}>\r\n {[0, 1, 2, 3].map((i) => (\r\n <div key={i} className=\"herms-inbox__skeleton-line\" style={{ width: i % 2 === 0 ? '85%' : '65%' }} />\r\n ))}\r\n </div>\r\n ) : error ? (\r\n <div className=\"herms-inbox__state\" role=\"alert\">\r\n <AlertIcon />\r\n <p className=\"herms-inbox__state-title\">{strings.errorTitle}</p>\r\n <button type=\"button\" className=\"herms-inbox__text-button\" onClick={() => refetch()}>\r\n {strings.retry}\r\n </button>\r\n </div>\r\n ) : items.length === 0 ? (\r\n <div className=\"herms-inbox__state\">{strings.empty}</div>\r\n ) : (\r\n <>\r\n <ul className=\"herms-inbox__list\" role=\"menu\" aria-label={strings.panelTitle} onKeyDown={handleListKeyDown}>\r\n {items.map((item, index) => (\r\n <li key={item.id} className=\"herms-inbox__list-row\" role=\"none\">\r\n <button\r\n type=\"button\"\r\n role=\"menuitem\"\r\n ref={(el) => {\r\n itemRefs.current[index] = el\r\n }}\r\n tabIndex={index === activeIndex ? 0 : -1}\r\n className=\"herms-inbox__item\"\r\n data-unread={!item.readAt}\r\n onFocus={() => setActiveIndex(index)}\r\n onClick={() => handleActivate(item)}\r\n >\r\n <span className=\"herms-inbox__item-title\">{item.title}</span>\r\n {item.body && <span className=\"herms-inbox__item-body\">{item.body}</span>}\r\n <span className=\"herms-inbox__item-meta\">{relativeTime(item.createdAt, locale ?? 'en')}</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n className=\"herms-inbox__archive-button\"\r\n aria-label={`${strings.archive}: ${item.title}`}\r\n onClick={(event) => {\r\n event.stopPropagation()\r\n void archive(item.id)\r\n }}\r\n >\r\n <ArchiveIcon />\r\n </button>\r\n </li>\r\n ))}\r\n </ul>\r\n {hasMore && (\r\n <button type=\"button\" className=\"herms-inbox__load-more\" onClick={loadMore} disabled={isLoadingMore}>\r\n {isLoadingMore ? strings.loadingMore : strings.loadMore}\r\n </button>\r\n )}\r\n </>\r\n )}\r\n </Popover.Content>\r\n </Popover.Portal>\r\n </Popover.Root>\r\n </div>\r\n )\r\n}\r\n","/**\r\n * `<HermsInbox />`'s own tiny, self-contained EN/FR string table.\r\n *\r\n * Deliberately **not** i18next: this package ships to arbitrary host apps\r\n * (with zero hard dependency on the host app's state library) that may run\r\n * their own i18n stack, a different one, or none at all. Pulling i18next in\r\n * as a real dependency here would be exactly the kind of host-app-shaped\r\n * assumption the SDK is supposed to avoid, for a handful of short strings.\r\n * `locale` is a prop of `<HermsInbox />`.\r\n */\r\n\r\nexport type HermsLocale = 'en' | 'fr'\r\n\r\nexport interface HermsInboxStrings {\r\n bellLabel: string\r\n bellLabelWithUnread: (count: number) => string\r\n panelTitle: string\r\n markAllRead: string\r\n close: string\r\n loading: string\r\n empty: string\r\n errorTitle: string\r\n retry: string\r\n loadMore: string\r\n loadingMore: string\r\n archive: string\r\n unreadDot: string\r\n}\r\n\r\nconst en: HermsInboxStrings = {\r\n bellLabel: 'Notifications',\r\n bellLabelWithUnread: (count) => `Notifications, ${count} unread`,\r\n panelTitle: 'Notifications',\r\n markAllRead: 'Mark all as read',\r\n close: 'Close',\r\n loading: 'Loading notifications…',\r\n empty: \"You're all caught up\",\r\n errorTitle: \"Couldn't load notifications.\",\r\n retry: 'Retry',\r\n loadMore: 'Load more',\r\n loadingMore: 'Loading…',\r\n archive: 'Archive',\r\n unreadDot: 'Unread',\r\n}\r\n\r\nconst fr: HermsInboxStrings = {\r\n bellLabel: 'Notifications',\r\n bellLabelWithUnread: (count) => `Notifications, ${count} non lues`,\r\n panelTitle: 'Notifications',\r\n markAllRead: 'Tout marquer comme lu',\r\n close: 'Fermer',\r\n loading: 'Chargement des notifications…',\r\n empty: 'Vous êtes à jour',\r\n errorTitle: \"Impossible de charger les notifications.\",\r\n retry: 'Réessayer',\r\n loadMore: 'Charger plus',\r\n loadingMore: 'Chargement…',\r\n archive: 'Archiver',\r\n unreadDot: 'Non lu',\r\n}\r\n\r\nconst dictionaries: Record<HermsLocale, HermsInboxStrings> = { en, fr }\r\n\r\nexport function getHermsInboxStrings(locale: HermsLocale | undefined): HermsInboxStrings {\r\n return dictionaries[locale ?? 'en'] ?? en\r\n}\r\n"],"mappings":"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","names":["useEffect","useMemo","useMemo","useEffect","useEffect","useMemo","useSyncExternalStore","useMemo","useEffect","useSyncExternalStore","useEffect","useMemo","useSyncExternalStore","useMemo","useEffect","useSyncExternalStore","useEffect","useMemo","jsx","useEffect","useMemo"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/HermsProvider.tsx","../src/useUnreadCount.ts","../src/useInbox.ts","../src/usePreferences.ts","../src/HermsInbox.tsx","../src/locale.ts"],"sourcesContent":["/**\n * `@hermesihq/react`: React bindings for Hermesi's in-app inbox.\n *\n * Two parts, from the one entry point:\n *\n * - **The client surface**, re-exported from `@hermesihq/js` so that\n * `import { HermsClient } from '@hermesihq/react'` keeps working. It is exactly the\n * names this package has always exported from there and no more: the stores and the\n * session are `@hermesihq/js`'s to export, and a React host has the hooks.\n * - **`HermsProvider`, `useUnreadCount`, `useInbox`, `usePreferences` and\n * `<HermsInbox />`.** The hooks are a thin binding over `@hermesihq/js`'s stores; the\n * component is a ready bell and panel built on them.\n */\nexport { HermsClient, HermsApiError, HERMS_CHANNELS, decodeSubscriberTokenExp } from '@hermesihq/js'\nexport type {\n HermsCategoryPreference,\n HermsChannel,\n HermsChannelPreference,\n HermsClientOptions,\n HermsErrorDetail,\n HermsEventListener,\n HermsInboxCategory,\n HermsInboxCounts,\n HermsInboxItem,\n HermsInboxListParams,\n HermsInboxPage,\n HermsInboxReadStatus,\n HermsPreferences,\n HermsPreferenceUpdate,\n HermsRealtimeEvent,\n HermsRegisterChannelParams,\n} from '@hermesihq/js'\n\nexport { HermsProvider, useHermsContext, type HermsProviderProps } from './HermsProvider'\nexport { useUnreadCount, type UseUnreadCountResult } from './useUnreadCount'\nexport { useInbox, type UseInboxOptions, type UseInboxResult } from './useInbox'\nexport { usePreferences, type UsePreferencesResult } from './usePreferences'\nexport { HermsInbox, type HermsInboxProps, type HermsInboxPlacement, type HermsInboxTheme } from './HermsInbox'\nexport { type HermsLocale } from './locale'\n","import { createContext, useContext, useEffect, useMemo, type ReactNode } from 'react'\r\nimport type { HermsClient } from '@hermesihq/js'\r\nimport { HermsSession, type HermsStoreHost } from '@hermesihq/js'\r\n\r\n/**\r\n * What every hook in this package reads from context. Exactly the shape the stores take\r\n * as their host, so a hook hands this straight to the store it creates.\r\n */\r\ntype HermsContextValue = HermsStoreHost\r\n\r\nconst HermsContext = createContext<HermsContextValue | null>(null)\r\n\r\nexport interface HermsProviderProps {\r\n client: HermsClient\r\n children: ReactNode\r\n}\r\n\r\n/**\r\n * `<HermsProvider client={new HermsClient({...})}>`: one `HermsClient` instance for the\r\n * whole subtree. Opens the client's real-time subscription once on mount and tears it\r\n * (and the client's own `onTokenExpiring` timer) down on unmount; every listener\r\n * registered via `addEventListener` shares that single connection rather than each hook\r\n * paying for its own.\r\n *\r\n * The connection itself is `HermsSession`, which knows nothing about React: this\r\n * component is the few lines that tie its lifetime to a mount. That is deliberate, and it\r\n * is the same session a Vue or vanilla page uses.\r\n *\r\n * `client` is expected to be a stable reference (constructed once, e.g. in\r\n * `useMemo`/module scope in the host app). Passing a new instance on every render tears\r\n * down and reopens the underlying connection every render.\r\n */\r\nexport function HermsProvider({ client, children }: HermsProviderProps) {\r\n const session = useMemo(() => new HermsSession(client), [client])\r\n\r\n // `connect()` returns its own release, which is the effect's cleanup. Reference-counted\r\n // in the session, so Strict Mode's connect, release, connect comes out right.\r\n useEffect(() => session.connect(), [session])\r\n\r\n const value = useMemo<HermsContextValue>(\r\n () => ({ client, addEventListener: session.addEventListener }),\r\n [client, session],\r\n )\r\n\r\n return <HermsContext.Provider value={value}>{children}</HermsContext.Provider>\r\n}\r\n\r\n/** Every hook/component in this package throws this same error outside a\r\n * `<HermsProvider>`: there is no sensible default client to fall back to. */\r\nexport function useHermsContext(): HermsContextValue {\r\n const ctx = useContext(HermsContext)\r\n if (!ctx) {\r\n throw new Error('@hermesihq/react: this hook/component must be rendered inside <HermsProvider client={...}>.')\r\n }\r\n return ctx\r\n}\r\n","import { useEffect, useMemo, useSyncExternalStore } from 'react'\r\nimport { CountsStore, type CountsState } from '@hermesihq/js'\r\nimport { useHermsContext } from './HermsProvider'\r\n\r\nexport type UseUnreadCountResult = CountsState\r\n\r\n/**\r\n * Headless. `{unread, unseen, isLoading}`, kept live via the provider's shared\r\n * subscription: one `getCounts()` call on mount, then recomputed on every\r\n * `counts.changed` event (real-time push, or the client's own 60s polling fallback) for\r\n * as long as this component stays mounted.\r\n *\r\n * A binding over `CountsStore`, which holds all of that. The store's constructor does\r\n * nothing, so React discarding a memoised one costs nothing: it is the `connect()` in the\r\n * effect that starts work.\r\n */\r\nexport function useUnreadCount(): UseUnreadCountResult {\r\n const host = useHermsContext()\r\n const store = useMemo(() => new CountsStore(host), [host])\r\n\r\n useEffect(() => store.connect(), [store])\r\n\r\n // The initial state is also the server snapshot: nothing connects during a server\r\n // render, so this is exactly what the server would produce.\r\n return useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot)\r\n}\r\n","import { useEffect, useMemo, useSyncExternalStore } from 'react'\r\nimport { InboxStore } from '@hermesihq/js'\r\nimport type { HermsInboxItem, HermsInboxReadStatus } from '@hermesihq/js'\r\nimport { useHermsContext } from './HermsProvider'\r\n\r\nexport interface UseInboxOptions {\r\n status?: HermsInboxReadStatus\r\n category?: string\r\n}\r\n\r\nexport interface UseInboxResult {\r\n items: HermsInboxItem[]\r\n isLoading: boolean\r\n isLoadingMore: boolean\r\n error: Error | null\r\n hasMore: boolean\r\n loadMore: () => void\r\n markRead: (id: string) => Promise<void>\r\n markAllRead: () => Promise<void>\r\n archive: (id: string) => Promise<void>\r\n remove: (id: string) => Promise<void>\r\n refetch: () => void\r\n}\r\n\r\n/**\r\n * Headless, cursor-paginated inbox list state: thin wrappers around `HermsClient`'s own\r\n * methods that also patch local state, so a consumer never has to manually refetch the\r\n * whole list after every mutating action.\r\n *\r\n * Live-updated on `item.created`: the new item is prepended immediately from the\r\n * (deliberately minimal: `{id, title, created_at}`) SSE payload so the UI reacts\r\n * instantly, then reconciled against a fresh first page fetched in the background so the\r\n * authoritative row (body, category, action_url) lands without a full list\r\n * reload/flicker.\r\n *\r\n * All of that lives in `InboxStore`; this is the binding. The filter is handed to the\r\n * store rather than being part of what identifies it, so changing tabs reloads the list\r\n * in place, keeping the rows on screen, instead of discarding the store and blanking the\r\n * panel.\r\n */\r\nexport function useInbox(options: UseInboxOptions = {}): UseInboxResult {\r\n const host = useHermsContext()\r\n const { status, category } = options\r\n\r\n // Only the first render's filter is used to create the store. Later changes go through\r\n // `setFilter` below, so this memo is keyed on the host alone.\r\n const store = useMemo(() => new InboxStore(host, { status, category }), [host])\r\n\r\n useEffect(() => store.connect(), [store])\r\n // A no-op when the filter is the one the store already has, which is the case on the\r\n // first render: `connect()` above has just loaded it.\r\n useEffect(() => {\r\n store.setFilter({ status, category })\r\n }, [store, status, category])\r\n\r\n const state = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot)\r\n\r\n return useMemo(\r\n () => ({\r\n ...state,\r\n // Arrow-function fields on the store: the same references for its whole life, so a\r\n // consumer's `useCallback`/`memo` dependencies do not churn as the state changes.\r\n loadMore: store.loadMore,\r\n markRead: store.markRead,\r\n markAllRead: store.markAllRead,\r\n archive: store.archive,\r\n remove: store.remove,\r\n refetch: store.refetch,\r\n }),\r\n [state, store],\r\n )\r\n}\r\n","import { useEffect, useMemo, useSyncExternalStore } from 'react'\r\nimport { PreferencesStore } from '@hermesihq/js'\r\nimport type { HermsPreferences, HermsPreferenceUpdate } from '@hermesihq/js'\r\nimport { useHermsContext } from './HermsProvider'\r\n\r\nexport interface UsePreferencesResult {\r\n /** `null` until the first read resolves. */\r\n preferences: HermsPreferences | null\r\n isLoading: boolean\r\n /** The failure of the last read or write, or `null`. Kept rather than thrown so a\r\n * preference centre can show it beside the controls instead of unmounting them. */\r\n error: Error | null\r\n /** Change one setting. Resolves once the server's new state has been applied. */\r\n setPreference: (update: HermsPreferenceUpdate) => Promise<void>\r\n /** Re-read, for a retry button. */\r\n reload: () => Promise<void>\r\n}\r\n\r\n/**\r\n * Headless preferences, the counterpart to `useInbox`.\r\n *\r\n * **No optimistic update, deliberately.** A toggle here is not a like button: turning\r\n * off a channel is a consent decision, and showing it as done before the server agreed is\r\n * showing somebody they have opted out when they may not have. The server answers `PATCH`\r\n * with the *whole* updated state, so the correct view arrives with the write and there is\r\n * nothing to reconcile. A per-row pending state in the host is a better answer to the\r\n * latency than a lie.\r\n *\r\n * **`error` is state, not an exception.** `setPreference` still rejects, so a caller can\r\n * `await` it and react per-control, but the hook also holds the failure so a preference\r\n * centre can render it without every consumer writing the same try/catch. A failed write\r\n * leaves the last known-good state on screen rather than an empty page.\r\n *\r\n * Critical categories arrive with `isCritical: true` and are always delivered. Render\r\n * them (a subscriber should see what they are receiving), but not as a control: an\r\n * affordance that refuses is worse than none.\r\n *\r\n * A binding over `PreferencesStore`, which holds the behaviour above.\r\n */\r\nexport function usePreferences(): UsePreferencesResult {\r\n const host = useHermsContext()\r\n const store = useMemo(() => new PreferencesStore(host), [host])\r\n\r\n useEffect(() => store.connect(), [store])\r\n\r\n const state = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot)\r\n\r\n return useMemo(\r\n () => ({ ...state, setPreference: store.setPreference, reload: store.reload }),\r\n [state, store],\r\n )\r\n}\r\n","import { useCallback, useEffect, useId, useMemo, useRef, useState, type CSSProperties, type KeyboardEvent } from 'react'\nimport * as Popover from '@radix-ui/react-popover'\nimport type { HermsInboxItem } from '@hermesihq/js'\nimport { useHermsContext } from './HermsProvider'\nimport { useInbox } from './useInbox'\nimport { useUnreadCount } from './useUnreadCount'\nimport { getHermsInboxStrings, type HermsLocale } from './locale'\nimport './HermsInbox.css'\n\nexport type HermsInboxPlacement = 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end'\n\nexport interface HermsInboxTheme {\n accent?: string\n radius?: string\n}\n\nexport interface HermsInboxProps {\n /** Popover position relative to the bell trigger. Default `bottom-end`. */\n placement?: HermsInboxPlacement\n /** Called when an item is activated (click, or Enter on a focused row).\n * Lets a host app drive its own router (for example\n * `onItemClick={(item) => navigate(item.actionUrl)}`) instead of a hard\n * page navigation. When omitted, activating an item with a non-null\n * `actionUrl` does a plain `window.location.assign` to it. Either way, the\n * item is marked read first. */\n onItemClick?: (item: HermsInboxItem) => void\n /** A couple of the most commonly re-themed CSS custom properties, settable\n * without a host app writing any CSS of its own. Anything else is\n * reachable by overriding `--herms-color-*`/`--herms-radius` variables\n * directly (see `HermsInbox.css`). */\n theme?: HermsInboxTheme\n /** `'auto'` (default) follows the visitor's OS `prefers-color-scheme`;\n * `'light'`/`'dark'` force one regardless of it. */\n colorScheme?: 'auto' | 'light' | 'dark'\n locale?: HermsLocale\n className?: string\n}\n\nconst placementToSideAlign: Record<HermsInboxPlacement, { side: 'top' | 'bottom'; align: 'start' | 'end' }> = {\n 'bottom-start': { side: 'bottom', align: 'start' },\n 'bottom-end': { side: 'bottom', align: 'end' },\n 'top-start': { side: 'top', align: 'start' },\n 'top-end': { side: 'top', align: 'end' },\n}\n\nfunction BellIcon() {\n return (\n <svg className=\"herms-inbox__bell\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M6 8a6 6 0 0 1 12 0c0 5 2 6 2 6H4s2-1 2-6\" />\n <path d=\"M10 21a2 2 0 0 0 4 0\" />\n </svg>\n )\n}\n\nfunction CloseIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden=\"true\">\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </svg>\n )\n}\n\nfunction ArchiveIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <rect x=\"3\" y=\"4\" width=\"18\" height=\"4\" rx=\"1\" />\n <path d=\"M5 8v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8\" />\n <path d=\"M10 13h4\" />\n </svg>\n )\n}\n\nfunction AlertIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" width=\"20\" height=\"20\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\n <path d=\"M12 8v5M12 16h.01\" />\n </svg>\n )\n}\n\nfunction relativeTime(iso: string, locale: HermsLocale): string {\n const then = new Date(iso).getTime()\n if (Number.isNaN(then)) return ''\n const diffSeconds = Math.max(0, Math.round((Date.now() - then) / 1000))\n const formatter = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' })\n if (diffSeconds < 60) return formatter.format(0, 'minute')\n const diffMinutes = Math.round(diffSeconds / 60)\n if (diffMinutes < 60) return formatter.format(-diffMinutes, 'minute')\n const diffHours = Math.round(diffMinutes / 60)\n if (diffHours < 24) return formatter.format(-diffHours, 'hour')\n const diffDays = Math.round(diffHours / 24)\n return formatter.format(-diffDays, 'day')\n}\n\n/**\n * `<HermsInbox />`: bell trigger + dropdown panel. Built\n * entirely on the headless `useInbox`/`useUnreadCount` hooks; every state\n * (loading, empty, error, populated) renders here, none of it invented by a\n * host app. Accessible: a real `<button>` trigger with `aria-label`/\n * `aria-expanded`, the panel as `role=\"menu\"` with roving-tabindex arrow-key\n * navigation, `Home`/`End`, `Escape`-to-close (native to Radix `Popover`,\n * which also returns focus to the trigger), and `Enter`/`Space` (native\n * `<button>` behavior) to activate the focused item.\n */\nexport function HermsInbox({ placement = 'bottom-end', onItemClick, theme, colorScheme = 'auto', locale, className }: HermsInboxProps) {\n const { client } = useHermsContext()\n const { unread, unseen } = useUnreadCount()\n const { items, isLoading, isLoadingMore, error, hasMore, loadMore, markRead, markAllRead, archive, refetch } = useInbox()\n const [open, setOpen] = useState(false)\n const [activeIndex, setActiveIndex] = useState(0)\n const itemRefs = useRef<Array<HTMLButtonElement | null>>([])\n const seenIdsRef = useRef<Set<string>>(new Set())\n const strings = getHermsInboxStrings(locale)\n const titleId = useId()\n\n // \"Seen\" (the bell was opened) is distinct from \"read.\" Marks\n // every currently loaded, not-yet-seen id seen once the panel is open and\n // has items, including any additional ids that arrive later via\n // `loadMore`/live updates while it stays open.\n useEffect(() => {\n if (!open || items.length === 0) return\n const toMark = items.filter((item) => !seenIdsRef.current.has(item.id)).map((item) => item.id)\n if (toMark.length === 0) return\n for (const id of toMark) seenIdsRef.current.add(id)\n void client.markSeen(toMark)\n }, [open, items, client])\n\n useEffect(() => {\n if (open) setActiveIndex(0)\n }, [open])\n\n const focusItem = useCallback((index: number) => {\n setActiveIndex(index)\n itemRefs.current[index]?.focus()\n }, [])\n\n const handleListKeyDown = useCallback(\n (event: KeyboardEvent<HTMLUListElement>) => {\n if (items.length === 0) return\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault()\n focusItem(Math.min(activeIndex + 1, items.length - 1))\n break\n case 'ArrowUp':\n event.preventDefault()\n focusItem(Math.max(activeIndex - 1, 0))\n break\n case 'Home':\n event.preventDefault()\n focusItem(0)\n break\n case 'End':\n event.preventDefault()\n focusItem(items.length - 1)\n break\n default:\n break\n }\n },\n [items.length, activeIndex, focusItem],\n )\n\n const handleActivate = useCallback(\n (item: HermsInboxItem) => {\n if (!item.readAt) void markRead(item.id)\n if (onItemClick) {\n onItemClick(item)\n } else if (item.actionUrl && typeof window !== 'undefined') {\n window.location.assign(item.actionUrl)\n }\n },\n [markRead, onItemClick],\n )\n\n const rootStyle = useMemo<CSSProperties>(() => {\n const style: CSSProperties & Record<string, string> = {}\n if (theme?.accent) style['--herms-color-accent'] = theme.accent\n if (theme?.radius) style['--herms-radius'] = theme.radius\n return style\n }, [theme?.accent, theme?.radius])\n\n const { side, align } = placementToSideAlign[placement]\n const rootClassName = ['herms-inbox', className].filter(Boolean).join(' ')\n // The panel is rendered by Radix in a portal under <body>, so it is not inside the root\n // above and inherits nothing from it. Whatever the root carries so that the stylesheet can\n // theme it, the panel has to carry as well: the theme's inline variables, the forced colour\n // scheme, and the host's class (so that one rule on `className` themes both).\n const panelClassName = ['herms-inbox__panel', className].filter(Boolean).join(' ')\n const colorSchemeAttribute = colorScheme === 'auto' ? undefined : colorScheme\n\n return (\n <div className={rootClassName} style={rootStyle} data-herms-color-scheme={colorSchemeAttribute}>\n <span className=\"herms-inbox__visually-hidden\" role=\"status\" aria-live=\"polite\">\n {unread > 0 ? strings.bellLabelWithUnread(unread) : ''}\n </span>\n <Popover.Root open={open} onOpenChange={setOpen}>\n <Popover.Trigger asChild>\n <button\n type=\"button\"\n className=\"herms-inbox__trigger\"\n aria-label={unread > 0 ? strings.bellLabelWithUnread(unread) : strings.bellLabel}\n aria-expanded={open}\n >\n <BellIcon />\n {unseen > 0 && (\n <span className=\"herms-inbox__badge\" aria-hidden=\"true\">\n {unseen > 99 ? '99+' : unseen}\n </span>\n )}\n </button>\n </Popover.Trigger>\n <Popover.Portal>\n <Popover.Content\n className={panelClassName}\n style={rootStyle}\n data-herms-color-scheme={colorSchemeAttribute}\n // The dialog needs a name of its own. Only the list, the bell and the loading\n // state were named; a dialog without one is announced as just \"dialog\".\n aria-labelledby={titleId}\n side={side}\n align={align}\n sideOffset={8}\n onOpenAutoFocus={(event) => {\n // Radix's default is to focus the content wrapper itself; we\n // want the first item (or the header when there are none)\n // focused instead, so keyboard users land somewhere useful.\n if (items.length > 0) {\n event.preventDefault()\n itemRefs.current[0]?.focus()\n }\n }}\n >\n <div className=\"herms-inbox__header\">\n <p className=\"herms-inbox__title\" id={titleId}>{strings.panelTitle}</p>\n <div className=\"herms-inbox__header-actions\">\n <button type=\"button\" className=\"herms-inbox__text-button\" onClick={() => void markAllRead()} disabled={unread === 0}>\n {strings.markAllRead}\n </button>\n <Popover.Close asChild>\n <button type=\"button\" className=\"herms-inbox__icon-button\" aria-label={strings.close}>\n <CloseIcon />\n </button>\n </Popover.Close>\n </div>\n </div>\n\n {isLoading ? (\n <div role=\"status\" aria-label={strings.loading}>\n {[0, 1, 2, 3].map((i) => (\n <div key={i} className=\"herms-inbox__skeleton-line\" style={{ width: i % 2 === 0 ? '85%' : '65%' }} />\n ))}\n </div>\n ) : error ? (\n <div className=\"herms-inbox__state\" role=\"alert\">\n <AlertIcon />\n <p className=\"herms-inbox__state-title\">{strings.errorTitle}</p>\n <button type=\"button\" className=\"herms-inbox__text-button\" onClick={() => refetch()}>\n {strings.retry}\n </button>\n </div>\n ) : items.length === 0 ? (\n <div className=\"herms-inbox__state\">{strings.empty}</div>\n ) : (\n <>\n <ul className=\"herms-inbox__list\" role=\"menu\" aria-label={strings.panelTitle} onKeyDown={handleListKeyDown}>\n {items.map((item, index) => (\n <li key={item.id} className=\"herms-inbox__list-row\" role=\"none\">\n <button\n type=\"button\"\n role=\"menuitem\"\n ref={(el) => {\n itemRefs.current[index] = el\n }}\n tabIndex={index === activeIndex ? 0 : -1}\n className=\"herms-inbox__item\"\n data-unread={!item.readAt}\n onFocus={() => setActiveIndex(index)}\n onClick={() => handleActivate(item)}\n >\n <span className=\"herms-inbox__item-title\">{item.title}</span>\n {item.body && <span className=\"herms-inbox__item-body\">{item.body}</span>}\n <span className=\"herms-inbox__item-meta\">{relativeTime(item.createdAt, locale ?? 'en')}</span>\n </button>\n <button\n type=\"button\"\n className=\"herms-inbox__archive-button\"\n aria-label={`${strings.archive}: ${item.title}`}\n onClick={(event) => {\n event.stopPropagation()\n void archive(item.id)\n }}\n >\n <ArchiveIcon />\n </button>\n </li>\n ))}\n </ul>\n {hasMore && (\n <button type=\"button\" className=\"herms-inbox__load-more\" onClick={loadMore} disabled={isLoadingMore}>\n {isLoadingMore ? strings.loadingMore : strings.loadMore}\n </button>\n )}\n </>\n )}\n </Popover.Content>\n </Popover.Portal>\n </Popover.Root>\n </div>\n )\n}\n","/**\r\n * `<HermsInbox />`'s own tiny, self-contained EN/FR string table.\r\n *\r\n * Deliberately **not** i18next: this package ships to arbitrary host apps\r\n * (with zero hard dependency on the host app's state library) that may run\r\n * their own i18n stack, a different one, or none at all. Pulling i18next in\r\n * as a real dependency here would be exactly the kind of host-app-shaped\r\n * assumption the SDK is supposed to avoid, for a handful of short strings.\r\n * `locale` is a prop of `<HermsInbox />`.\r\n */\r\n\r\nexport type HermsLocale = 'en' | 'fr'\r\n\r\nexport interface HermsInboxStrings {\r\n bellLabel: string\r\n bellLabelWithUnread: (count: number) => string\r\n panelTitle: string\r\n markAllRead: string\r\n close: string\r\n loading: string\r\n empty: string\r\n errorTitle: string\r\n retry: string\r\n loadMore: string\r\n loadingMore: string\r\n archive: string\r\n unreadDot: string\r\n}\r\n\r\nconst en: HermsInboxStrings = {\r\n bellLabel: 'Notifications',\r\n bellLabelWithUnread: (count) => `Notifications, ${count} unread`,\r\n panelTitle: 'Notifications',\r\n markAllRead: 'Mark all as read',\r\n close: 'Close',\r\n loading: 'Loading notifications…',\r\n empty: \"You're all caught up\",\r\n errorTitle: \"Couldn't load notifications.\",\r\n retry: 'Retry',\r\n loadMore: 'Load more',\r\n loadingMore: 'Loading…',\r\n archive: 'Archive',\r\n unreadDot: 'Unread',\r\n}\r\n\r\nconst fr: HermsInboxStrings = {\r\n bellLabel: 'Notifications',\r\n bellLabelWithUnread: (count) => `Notifications, ${count} non lues`,\r\n panelTitle: 'Notifications',\r\n markAllRead: 'Tout marquer comme lu',\r\n close: 'Fermer',\r\n loading: 'Chargement des notifications…',\r\n empty: 'Vous êtes à jour',\r\n errorTitle: \"Impossible de charger les notifications.\",\r\n retry: 'Réessayer',\r\n loadMore: 'Charger plus',\r\n loadingMore: 'Chargement…',\r\n archive: 'Archiver',\r\n unreadDot: 'Non lu',\r\n}\r\n\r\nconst dictionaries: Record<HermsLocale, HermsInboxStrings> = { en, fr }\r\n\r\nexport function getHermsInboxStrings(locale: HermsLocale | undefined): HermsInboxStrings {\r\n return dictionaries[locale ?? 'en'] ?? en\r\n}\r\n"],"mappings":"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","names":["useEffect","useMemo","useMemo","useEffect","useEffect","useMemo","useSyncExternalStore","useMemo","useEffect","useSyncExternalStore","useEffect","useMemo","useSyncExternalStore","useMemo","useEffect","useSyncExternalStore","useEffect","useMemo","jsx","useEffect","useMemo"]}
|
package/package.json
CHANGED