@ghub/identity-chrome 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -4
- package/dist/core.js +12 -4
- package/dist/elements/account.d.ts +96 -0
- package/dist/elements/account.js +1008 -0
- package/dist/elements/apps.d.ts +67 -0
- package/dist/elements/apps.js +689 -0
- package/dist/elements/contract.d.ts +29 -0
- package/dist/elements/contract.js +109 -0
- package/dist/elements/dom.d.ts +13 -0
- package/dist/elements/dom.js +45 -0
- package/dist/elements/index.d.ts +48 -0
- package/dist/elements/index.js +26 -0
- package/dist/index.d.ts +7 -3
- package/dist/index.js +17 -3
- package/dist/react.d.ts +41 -0
- package/dist/react.js +73 -0
- package/dist/tint.d.ts +22 -0
- package/dist/tint.js +41 -0
- package/dist/types.d.ts +24 -1
- package/dist/validate.js +25 -0
- package/package.json +17 -2
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# @ghub/identity-chrome
|
|
2
2
|
|
|
3
3
|
The shared header controls of the Gelhaus Solutions products: the apps picker and the
|
|
4
|
-
account popup.
|
|
5
|
-
|
|
6
|
-
half (a product's own server asking
|
|
7
|
-
|
|
4
|
+
account popup. Two custom elements with shadow DOM (`<gp-apps>`, `<gp-account>`) and React
|
|
5
|
+
wrappers for them; the browser half under both (what the controls know, what they keep, what
|
|
6
|
+
they do when Identity cannot be reached); and the server half (a product's own server asking
|
|
7
|
+
GPlatform Identity).
|
|
8
8
|
|
|
9
9
|
Nothing here depends on a product signing in through Identity. A product that has not
|
|
10
10
|
adopted it uses the same package the same way.
|
|
@@ -62,6 +62,75 @@ A one-off push of a product's users (`chrome.seed(emails)`) is refused unless st
|
|
|
62
62
|
opened a window for it on Identity's product screen, and `chrome.remove(emails)` says people
|
|
63
63
|
no longer have access.
|
|
64
64
|
|
|
65
|
+
## The two controls
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
import { createChromeCore, defineChromeElements } from "@ghub/identity-chrome";
|
|
69
|
+
|
|
70
|
+
defineChromeElements(); // <gp-apps> and <gp-account>, each once; a no-op on a server
|
|
71
|
+
|
|
72
|
+
const apps = document.querySelector("gp-apps")!;
|
|
73
|
+
apps.core = chrome; // the core above: one fetch and one cache behind both
|
|
74
|
+
|
|
75
|
+
const account = document.querySelector("gp-account")!;
|
|
76
|
+
account.core = chrome;
|
|
77
|
+
account.product = "GAdvisory"; // your own name, from your catalogue entry
|
|
78
|
+
account.person = { name: user.name, email: user.email, id: user.id, avatarUrl: user.picture };
|
|
79
|
+
account.organisations = [{ id: "org_1", name: "Hafen Labs", role: "admin", active: true }];
|
|
80
|
+
account.onSwitch = async (id) => switchOrganisation(id); // reject when it did not switch
|
|
81
|
+
account.onSignOut = async () => {
|
|
82
|
+
await signOut(); // reject when it failed: the popup says they are still signed in
|
|
83
|
+
location.assign("/"); // you navigate; as this resolves the element forgets the kept list
|
|
84
|
+
};
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Everything is a property, because a core, a person and a callback are objects. Each can be
|
|
88
|
+
set before the element is defined. Neither control renders anything while the core is off,
|
|
89
|
+
and the account popup renders nothing without a person.
|
|
90
|
+
|
|
91
|
+
**The account popup shows mostly what the product passes**: the name and address from your
|
|
92
|
+
session, your organisations with their roles (Owner, Admin or Member) and your two actions.
|
|
93
|
+
Identity adds the picture, the colour of the initial, the links to its own account and set up
|
|
94
|
+
screens and the footer, so the popup still signs somebody out while Identity is down. The
|
|
95
|
+
picture is Identity's if the person has one, else yours, else a coloured initial whose tint
|
|
96
|
+
Identity picks from its own id; for somebody with no Identity, pass your own stable user id as
|
|
97
|
+
`id` and the same hash (`avatarTint`) picks it. Set `viaIdentity` once you sign in through
|
|
98
|
+
Identity, and Sign out says it ends both. Sign out leaves this product only.
|
|
99
|
+
|
|
100
|
+
Every word can be replaced through `labels`; the rest stay English.
|
|
101
|
+
|
|
102
|
+
### In React
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
import { GpAccount, GpApps } from "@ghub/identity-chrome/react";
|
|
106
|
+
|
|
107
|
+
<GpApps core={chrome} />
|
|
108
|
+
<GpAccount core={chrome} product="GAdvisory" person={person} organisations={orgs} onSwitch={switchTo} onSignOut={signOut} />
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The wrappers define the elements and set the properties after mounting, so they render on a
|
|
112
|
+
server as the bare tags. React 19 or newer, as an optional peer.
|
|
113
|
+
|
|
114
|
+
### The host contract
|
|
115
|
+
|
|
116
|
+
Shadow DOM: nothing reaches in and nothing leaks out but the custom properties a page
|
|
117
|
+
declares on its root, which inherit into the shadow. The controls read the published
|
|
118
|
+
`@ghub/tokens` names, so a page on `@ghub/tokens` is styled with no setup, and Aurora's frosted
|
|
119
|
+
menu where the page is on Aurora:
|
|
120
|
+
|
|
121
|
+
| Reads | For |
|
|
122
|
+
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- |
|
|
123
|
+
| `--ga-menu`, else `--gm-overlay`; `--ga-frost-menu`; `--ga-shadow-menu`, else `--gm-shadow-overlay` | the picker's surface, its frost and its shadow |
|
|
124
|
+
| `--ga-menu`, else `--gm-sunken` | the account popup's tinted ground |
|
|
125
|
+
| `--ga-track`, else `--gm-overlay` | the account popup's panels |
|
|
126
|
+
| `--gm-plate`, `--gm-ink`, `--gm-ink-2`, `--gm-ink-3`, `--gm-hairline`, `--gm-control`, `--gm-hover`, `--gm-active`, `--gm-selected`, `--gm-accent`, `--gm-accent-text`, `--gm-ring`, `--gm-crit-ink` | everything else |
|
|
127
|
+
| `--font-sans` | the face |
|
|
128
|
+
|
|
129
|
+
A page that declares none of them gets modern light, or modern dark under
|
|
130
|
+
`prefers-color-scheme: dark`. There is no second accent: the controls take the page's. Both
|
|
131
|
+
follow reduced motion, reduced transparency and forced colours, and below 600 wide each is a
|
|
132
|
+
modal bottom sheet. `part="trigger"` and `part="popup"` are there for placement only.
|
|
133
|
+
|
|
65
134
|
## A product's own name and mark
|
|
66
135
|
|
|
67
136
|
Identity's catalogue is where every product's name and mark live. Read your own from it for
|
package/dist/core.js
CHANGED
|
@@ -29,7 +29,7 @@ exports.CACHE_KEY = "gpid.chrome.v1";
|
|
|
29
29
|
/** A list older than this is not drawn, however the fetch fares. */
|
|
30
30
|
exports.CACHE_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000;
|
|
31
31
|
exports.DEFAULT_TIMEOUT_MS = 5000;
|
|
32
|
-
const NO_PERSON = { hasIdentity: false, avatarUrl: null };
|
|
32
|
+
const NO_PERSON = { hasIdentity: false, avatarUrl: null, tint: null };
|
|
33
33
|
function isRecord(value) {
|
|
34
34
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
35
35
|
}
|
|
@@ -157,7 +157,15 @@ function createChromeCore(options = {}) {
|
|
|
157
157
|
void publicCall
|
|
158
158
|
.then((pub) => {
|
|
159
159
|
if (pub !== null && state.status === "loading" && state.view === null) {
|
|
160
|
-
set({
|
|
160
|
+
set({
|
|
161
|
+
view: {
|
|
162
|
+
person: NO_PERSON,
|
|
163
|
+
yours: [],
|
|
164
|
+
identity: null,
|
|
165
|
+
more: pub.more,
|
|
166
|
+
legal: pub.legal,
|
|
167
|
+
},
|
|
168
|
+
});
|
|
161
169
|
}
|
|
162
170
|
})
|
|
163
171
|
.catch(() => undefined);
|
|
@@ -173,7 +181,7 @@ function createChromeCore(options = {}) {
|
|
|
173
181
|
// No personal route configured: the public group is the whole answer.
|
|
174
182
|
set({
|
|
175
183
|
status: "ready",
|
|
176
|
-
view: { person: NO_PERSON, yours: [], ...publicHalf },
|
|
184
|
+
view: { person: NO_PERSON, yours: [], identity: null, ...publicHalf },
|
|
177
185
|
updatedAt: now(),
|
|
178
186
|
error: null,
|
|
179
187
|
});
|
|
@@ -186,7 +194,7 @@ function createChromeCore(options = {}) {
|
|
|
186
194
|
else if (publicHalf !== null) {
|
|
187
195
|
set({
|
|
188
196
|
status: "error",
|
|
189
|
-
view: { person: NO_PERSON, yours: [], ...publicHalf },
|
|
197
|
+
view: { person: NO_PERSON, yours: [], identity: null, ...publicHalf },
|
|
190
198
|
updatedAt: null,
|
|
191
199
|
error,
|
|
192
200
|
});
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<gp-account>`, the account popup (GPlatform Identity, sheet 32, option A).
|
|
3
|
+
*
|
|
4
|
+
* An avatar at the right end of a product's top bar and, under it, a card on a
|
|
5
|
+
* tinted ground: who is signed in, where their Identity account is, which
|
|
6
|
+
* organisation they are acting in, and how to sign out of this product and
|
|
7
|
+
* only this product (D21).
|
|
8
|
+
*
|
|
9
|
+
* **Most of what it shows is the product's.** The name, the address, the
|
|
10
|
+
* organisations and both actions are properties the product sets from its own
|
|
11
|
+
* session. Identity adds the picture, the tint, its own two links and the
|
|
12
|
+
* footer, through the same core as `<gp-apps>`, so the popup still signs
|
|
13
|
+
* somebody out while Identity cannot be reached.
|
|
14
|
+
*
|
|
15
|
+
* **What it is to assistive technology:**
|
|
16
|
+
*
|
|
17
|
+
* - The trigger is a `button` with `aria-haspopup="dialog"`, named "Account:
|
|
18
|
+
* Mira Kessler", with the same words as a tooltip on hover and on focus after
|
|
19
|
+
* half a second, which Escape dismisses without closing anything else (1.4.13).
|
|
20
|
+
* - The popup is a `dialog` labelled by the person's name and described by their
|
|
21
|
+
* address, not modal. Below 600 wide it is a modal sheet with a scrim and a
|
|
22
|
+
* contained focus loop, as the picker is.
|
|
23
|
+
* - Focus opens on the pill (Manage your Identity account, or Set up your
|
|
24
|
+
* Identity), else the active organisation, else Sign out. The close button is
|
|
25
|
+
* a stop and never the first one.
|
|
26
|
+
* - The organisations are a `radiogroup`, one tab stop, Up and Down moving
|
|
27
|
+
* between them. Choosing is Space or Enter and never an arrow, because
|
|
28
|
+
* choosing switches the product: it closes the popup and announces
|
|
29
|
+
* "Switched to ...". Five show; "Show all" opens the rest in place, scrolling
|
|
30
|
+
* inside, and focus lands on the row that took its place.
|
|
31
|
+
* - Signing out goes busy in place. A failure keeps the popup open, says in one
|
|
32
|
+
* sentence that the person is still signed in, and turns the row into Try
|
|
33
|
+
* again. It never claims to have signed out.
|
|
34
|
+
*
|
|
35
|
+
* **Hidden for now (D20):** the other accounts and "Add another account" are
|
|
36
|
+
* not drawn until Identity holds several sessions in one browser and products
|
|
37
|
+
* sign in through it, and no product can switch them on.
|
|
38
|
+
*
|
|
39
|
+
* The words are English by default and every one can be replaced (`labels`).
|
|
40
|
+
*/
|
|
41
|
+
export type OrganisationRole = "owner" | "admin" | "member";
|
|
42
|
+
/** Who is signed in, from the product's own session. */
|
|
43
|
+
export interface AccountPerson {
|
|
44
|
+
name: string;
|
|
45
|
+
email: string;
|
|
46
|
+
/** The product's own stable id for this person: it picks the tint where they have no Identity. Never shown. */
|
|
47
|
+
id?: string;
|
|
48
|
+
/** The product's own picture, drawn where Identity has none. */
|
|
49
|
+
avatarUrl?: string | null;
|
|
50
|
+
}
|
|
51
|
+
/** An organisation the person can act in, as the product or Identity passes it (D24, D33). */
|
|
52
|
+
export interface AccountOrganisation {
|
|
53
|
+
id: string;
|
|
54
|
+
name: string;
|
|
55
|
+
role: OrganisationRole;
|
|
56
|
+
active: boolean;
|
|
57
|
+
}
|
|
58
|
+
/** Signs the person out of this product. Rejecting means they are still signed in. */
|
|
59
|
+
export type SignOutHandler = () => unknown;
|
|
60
|
+
/** Switches the product to this organisation. Rejecting means it did not. */
|
|
61
|
+
export type SwitchHandler = (organisationId: string) => unknown;
|
|
62
|
+
export interface AccountLabels {
|
|
63
|
+
/** The trigger's name and its tooltip. */
|
|
64
|
+
trigger: (name: string) => string;
|
|
65
|
+
signedInTo: (product: string) => string;
|
|
66
|
+
close: string;
|
|
67
|
+
changePicture: string;
|
|
68
|
+
manage: string;
|
|
69
|
+
setUp: string;
|
|
70
|
+
organisations: string;
|
|
71
|
+
roles: Record<OrganisationRole, string>;
|
|
72
|
+
active: string;
|
|
73
|
+
showAll: (count: number) => string;
|
|
74
|
+
switched: (organisation: string) => string;
|
|
75
|
+
switchFailed: (organisation: string) => string;
|
|
76
|
+
signOut: (product: string) => string;
|
|
77
|
+
/** In a product that signs in through Identity, where signing out ends both. */
|
|
78
|
+
signOutBoth: (product: string) => string;
|
|
79
|
+
signingOut: (product: string) => string;
|
|
80
|
+
signOutFailed: (product: string) => string;
|
|
81
|
+
retry: string;
|
|
82
|
+
/** Stands in for the product's name where it was not given. */
|
|
83
|
+
thisProduct: string;
|
|
84
|
+
terms: string;
|
|
85
|
+
privacy: string;
|
|
86
|
+
}
|
|
87
|
+
export declare const ACCOUNT_LABELS: AccountLabels;
|
|
88
|
+
export declare const ACCOUNT_CSS: string;
|
|
89
|
+
/**
|
|
90
|
+
* Defines `<gp-account>` once, where custom elements exist. Safe to import on a
|
|
91
|
+
* server: nothing touches the DOM until this is called in a browser.
|
|
92
|
+
*/
|
|
93
|
+
export declare function defineAccountElement(name?: string): void;
|
|
94
|
+
/** The class itself, for a caller that registers it under its own name or in a scoped registry. */
|
|
95
|
+
export declare function createAccountElementClass(): CustomElementConstructor;
|
|
96
|
+
//# sourceMappingURL=account.d.ts.map
|