@ghub/identity-chrome 0.3.0 → 0.5.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 +16 -6
- package/dist/elements/account.d.ts +8 -0
- package/dist/elements/account.js +71 -4
- package/dist/react.d.ts +3 -1
- package/dist/react.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -76,7 +76,9 @@ const account = document.querySelector("gp-account")!;
|
|
|
76
76
|
account.core = chrome;
|
|
77
77
|
account.product = "GAdvisory"; // your own name, from your catalogue entry
|
|
78
78
|
account.person = { name: user.name, email: user.email, id: user.id, avatarUrl: user.picture };
|
|
79
|
-
account.organisations = [
|
|
79
|
+
account.organisations = [
|
|
80
|
+
{ id: "org_1", name: "Hafen Labs", role: "Admin", iconUrl: "/orgs/org_1/avatar", active: true },
|
|
81
|
+
];
|
|
80
82
|
account.onSwitch = async (id) => switchOrganisation(id); // reject when it did not switch
|
|
81
83
|
account.onSignOut = async () => {
|
|
82
84
|
await signOut(); // reject when it failed: the popup says they are still signed in
|
|
@@ -90,7 +92,8 @@ and the account popup renders nothing without a person.
|
|
|
90
92
|
|
|
91
93
|
**The account popup shows mostly what the product passes**: the name and address from your
|
|
92
94
|
session, your organisations with the role your product gives them there (its own label, already in the
|
|
93
|
-
person's language and shown as given; none, and the row has no second line) and
|
|
95
|
+
person's language and shown as given; none, and the row has no second line) and their own picture
|
|
96
|
+
where they have one (`iconUrl`; the initial otherwise, and if it does not load), and your two actions.
|
|
94
97
|
Identity adds the picture, the colour of the initial, the links to its own account and set up
|
|
95
98
|
screens and the footer, so the popup still signs somebody out while Identity is down. The
|
|
96
99
|
picture is Identity's if the person has one, else yours, else a coloured initial whose tint
|
|
@@ -104,7 +107,13 @@ Every word can be replaced through `labels`; the rest stay English.
|
|
|
104
107
|
|
|
105
108
|
```html
|
|
106
109
|
<div style="display: flex; align-items: center">
|
|
107
|
-
<gp-account
|
|
110
|
+
<gp-account
|
|
111
|
+
layout="row"
|
|
112
|
+
density="compact"
|
|
113
|
+
placement="top"
|
|
114
|
+
align="start"
|
|
115
|
+
style="flex: 1"
|
|
116
|
+
></gp-account>
|
|
108
117
|
<gp-apps placement="top" align="start"></gp-apps>
|
|
109
118
|
</div>
|
|
110
119
|
```
|
|
@@ -114,13 +123,14 @@ account.labels = { organisations: "Groups" };
|
|
|
114
123
|
account.items = [
|
|
115
124
|
{ id: "profile", label: "Profile", href: "/settings/profile", icon: userSvg },
|
|
116
125
|
{ id: "account", label: "Account", href: "/settings/account", icon: settingsSvg },
|
|
117
|
-
{ id: "
|
|
126
|
+
{ id: "shortcuts", label: "Keyboard shortcuts", onSelect: () => openShortcuts(), icon: keysSvg },
|
|
118
127
|
];
|
|
119
128
|
```
|
|
120
129
|
|
|
121
130
|
`layout="row"` draws the trigger as a full width row, the picture with the name over the
|
|
122
|
-
address; `
|
|
123
|
-
|
|
131
|
+
address; `density="compact"` draws the account popup a step smaller (320 wide, tighter rows),
|
|
132
|
+
so it does not cover the rail and half the page; `placement="top"` opens both popups upward.
|
|
133
|
+
Your items follow the organisations under your product's name, as links or as buttons; on a link, `onSelect` may call
|
|
124
134
|
`preventDefault()` and navigate with your own router. An icon is SVG markup on a 24 grid and
|
|
125
135
|
is kept to its shapes. Until you sign in through Identity, Sign out is the last row of your
|
|
126
136
|
items' group; with `viaIdentity` it ends both sessions and stands on its own.
|
|
@@ -35,6 +35,8 @@
|
|
|
35
35
|
* **In a sidebar** (`layout="row"`, `placement="top"`): the trigger is a full
|
|
36
36
|
* width row, the picture with the name over the address, and the popup opens
|
|
37
37
|
* upward from it. A product with a sidebar puts `<gp-apps>` at the row's end.
|
|
38
|
+
* `density="compact"` draws the popup a step smaller (320 wide, tighter rows),
|
|
39
|
+
* so it does not cover the rail and half the page.
|
|
38
40
|
*
|
|
39
41
|
* **The product's own items** (`items`) sit after the organisations, as a group
|
|
40
42
|
* headed with the product's name. Until the product signs in through Identity,
|
|
@@ -67,6 +69,12 @@ export interface AccountOrganisation {
|
|
|
67
69
|
* so the element has no list of its own. None, and the row has no second line.
|
|
68
70
|
*/
|
|
69
71
|
role?: string | null;
|
|
72
|
+
/**
|
|
73
|
+
* The organisation's own picture, absolute or relative to the page, http or
|
|
74
|
+
* https only. Drawn in place of the initial, which comes back if it does not
|
|
75
|
+
* load.
|
|
76
|
+
*/
|
|
77
|
+
iconUrl?: string | null;
|
|
70
78
|
active: boolean;
|
|
71
79
|
}
|
|
72
80
|
/** One of the product's own items in the popup, such as its profile or its settings. */
|
package/dist/elements/account.js
CHANGED
|
@@ -36,6 +36,8 @@
|
|
|
36
36
|
* **In a sidebar** (`layout="row"`, `placement="top"`): the trigger is a full
|
|
37
37
|
* width row, the picture with the name over the address, and the popup opens
|
|
38
38
|
* upward from it. A product with a sidebar puts `<gp-apps>` at the row's end.
|
|
39
|
+
* `density="compact"` draws the popup a step smaller (320 wide, tighter rows),
|
|
40
|
+
* so it does not cover the rail and half the page.
|
|
39
41
|
*
|
|
40
42
|
* **The product's own items** (`items`) sit after the organisations, as a group
|
|
41
43
|
* headed with the product's name. Until the product signs in through Identity,
|
|
@@ -227,8 +229,9 @@ exports.ACCOUNT_CSS = `${contract_js_1.CONTRACT_CSS}
|
|
|
227
229
|
.wrap > .radios:last-child > .org:last-child { border-radius: 4px 4px 18px 18px; }
|
|
228
230
|
.org-mark {
|
|
229
231
|
display: grid; place-items: center; flex-shrink: 0; width: 32px; height: 32px; border-radius: 9px;
|
|
230
|
-
color: ${tint_js_1.AVATAR_INK}; font-size: 14px; font-weight: 600;
|
|
232
|
+
overflow: hidden; color: ${tint_js_1.AVATAR_INK}; font-size: 14px; font-weight: 600;
|
|
231
233
|
}
|
|
234
|
+
.org-mark img { display: block; width: 100%; height: 100%; object-fit: cover; }
|
|
232
235
|
.org-text { flex: 1; min-width: 0; display: flex; flex-direction: column; }
|
|
233
236
|
.org-name { font-size: 14px; line-height: 20px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
234
237
|
.org-role { font-size: 12.5px; line-height: 17px; color: var(--_ink-2); }
|
|
@@ -258,6 +261,40 @@ exports.ACCOUNT_CSS = `${contract_js_1.CONTRACT_CSS}
|
|
|
258
261
|
.legal a:hover { text-decoration: underline; }
|
|
259
262
|
.legal a:focus-visible { outline: 2px solid var(--_ring); outline-offset: 2px; }
|
|
260
263
|
|
|
264
|
+
/* density="compact": the same popup a step smaller, for a sidebar, where the
|
|
265
|
+
full one covers the rail and half the page. Below 600 it is the same sheet. */
|
|
266
|
+
:host([density="compact"]) .popup:not(.sheet) { width: 320px; padding: 6px 6px 8px; border-radius: 22px; }
|
|
267
|
+
:host([density="compact"]) .top { min-height: 32px; padding-left: 36px; }
|
|
268
|
+
:host([density="compact"]) .context { font-size: 12px; line-height: 16px; }
|
|
269
|
+
:host([density="compact"]) .close { width: 32px; height: 32px; }
|
|
270
|
+
:host([density="compact"]) .card { padding: 14px 14px 14px; border-radius: 18px; gap: 1px; }
|
|
271
|
+
:host([density="compact"]) .portrait { width: 52px; height: 52px; margin-bottom: 8px; }
|
|
272
|
+
:host([density="compact"]) .s72 { width: 52px; height: 52px; font-size: 22px; }
|
|
273
|
+
:host([density="compact"]) .pencil { width: 24px; height: 24px; right: -5px; bottom: -3px; }
|
|
274
|
+
:host([density="compact"]) .name { font-size: 16px; line-height: 21px; }
|
|
275
|
+
:host([density="compact"]) .email { font-size: 13px; line-height: 18px; }
|
|
276
|
+
:host([density="compact"]) .pill {
|
|
277
|
+
min-height: 34px; margin-top: 10px; padding: 0 14px 0 10px; gap: 8px; font-size: 13px; line-height: 18px;
|
|
278
|
+
}
|
|
279
|
+
:host([density="compact"]) .pill img { width: 18px; height: 18px; }
|
|
280
|
+
:host([density="compact"]) .section { margin-top: 4px; }
|
|
281
|
+
:host([density="compact"]) .heading { padding: 6px 12px 2px; font-size: 12px; line-height: 16px; }
|
|
282
|
+
:host([density="compact"]) .heading img { width: 14px; height: 14px; }
|
|
283
|
+
:host([density="compact"]) .stack > :first-child { border-top-left-radius: 14px; border-top-right-radius: 14px; }
|
|
284
|
+
:host([density="compact"]) .stack > :last-child { border-bottom-left-radius: 14px; border-bottom-right-radius: 14px; }
|
|
285
|
+
:host([density="compact"]) .item { min-height: 38px; padding: 0 12px; gap: 10px; font-size: 13px; line-height: 18px; }
|
|
286
|
+
:host([density="compact"]) .item svg, :host([density="compact"]) .signout svg { width: 16px; height: 16px; }
|
|
287
|
+
:host([density="compact"]) .org { min-height: 44px; padding: 5px 12px; gap: 10px; }
|
|
288
|
+
:host([density="compact"]) .wrap > .radios:last-child > .org:last-child { border-radius: 4px 4px 14px 14px; }
|
|
289
|
+
:host([density="compact"]) .org-mark { width: 28px; height: 28px; border-radius: 8px; font-size: 12.5px; }
|
|
290
|
+
:host([density="compact"]) .org-name { font-size: 13px; line-height: 18px; }
|
|
291
|
+
:host([density="compact"]) .org-role { font-size: 11.5px; line-height: 15px; }
|
|
292
|
+
:host([density="compact"]) .active { font-size: 12px; gap: 5px; }
|
|
293
|
+
:host([density="compact"]) .more { min-height: 38px; padding: 0 12px; font-size: 13px; border-radius: 4px 4px 14px 14px; }
|
|
294
|
+
:host([density="compact"]) .signout { min-height: 40px; padding: 0 12px; gap: 10px; font-size: 13px; }
|
|
295
|
+
:host([density="compact"]) .error { padding: 4px 12px 2px; font-size: 12px; line-height: 17px; }
|
|
296
|
+
:host([density="compact"]) .legal { padding: 8px 0 2px; font-size: 11.5px; line-height: 16px; }
|
|
297
|
+
|
|
261
298
|
@media (prefers-reduced-motion: reduce) {
|
|
262
299
|
.popup, .popup.sheet { animation: none; }
|
|
263
300
|
.spinner { animation-duration: 2400ms; }
|
|
@@ -316,6 +353,30 @@ function emailLine(email) {
|
|
|
316
353
|
}
|
|
317
354
|
return line;
|
|
318
355
|
}
|
|
356
|
+
/** An organisation's picture where it has one that loads, else its initial on its tint. */
|
|
357
|
+
function orgMark(one) {
|
|
358
|
+
const initial = initialOf(one.name);
|
|
359
|
+
const mark = (0, dom_js_1.element)("span", {
|
|
360
|
+
class: `org-mark t${String((0, tint_js_1.avatarTint)(one.id))}`,
|
|
361
|
+
"aria-hidden": "true",
|
|
362
|
+
});
|
|
363
|
+
const picture = productAddress(one.iconUrl);
|
|
364
|
+
if (picture === null) {
|
|
365
|
+
mark.textContent = initial;
|
|
366
|
+
return mark;
|
|
367
|
+
}
|
|
368
|
+
const image = (0, dom_js_1.element)("img", {
|
|
369
|
+
src: picture,
|
|
370
|
+
alt: "",
|
|
371
|
+
decoding: "async",
|
|
372
|
+
referrerpolicy: "no-referrer",
|
|
373
|
+
});
|
|
374
|
+
image.addEventListener("error", () => {
|
|
375
|
+
mark.textContent = initial;
|
|
376
|
+
}, { once: true });
|
|
377
|
+
mark.append(image);
|
|
378
|
+
return mark;
|
|
379
|
+
}
|
|
319
380
|
/** An address the product gave, absolute or relative to the page, and only ever http or https. */
|
|
320
381
|
function productAddress(value) {
|
|
321
382
|
if (typeof value !== "string" || value === "")
|
|
@@ -450,11 +511,17 @@ function organisationsOf(value) {
|
|
|
450
511
|
for (const one of value) {
|
|
451
512
|
if (typeof one !== "object" || one === null)
|
|
452
513
|
continue;
|
|
453
|
-
const { id, name, role, active } = one;
|
|
514
|
+
const { id, name, role, iconUrl, active } = one;
|
|
454
515
|
if (typeof id !== "string" || id === "" || typeof name !== "string" || name.trim() === "")
|
|
455
516
|
continue;
|
|
456
517
|
const label = typeof role === "string" && role.trim() !== "" ? role.trim() : null;
|
|
457
|
-
out.push({
|
|
518
|
+
out.push({
|
|
519
|
+
id,
|
|
520
|
+
name: name.trim(),
|
|
521
|
+
role: label,
|
|
522
|
+
iconUrl: typeof iconUrl === "string" && iconUrl !== "" ? iconUrl : null,
|
|
523
|
+
active: active === true,
|
|
524
|
+
});
|
|
458
525
|
}
|
|
459
526
|
// The active one first: the row says which one you are acting in.
|
|
460
527
|
const active = out.findIndex((one) => one.active);
|
|
@@ -1131,7 +1198,7 @@ function createAccountElementClass() {
|
|
|
1131
1198
|
text.append((0, dom_js_1.element)("span", { class: "org-name", title: one.name }, one.name));
|
|
1132
1199
|
if (one.role)
|
|
1133
1200
|
text.append((0, dom_js_1.element)("span", { class: "org-role" }, one.role));
|
|
1134
|
-
row.append((
|
|
1201
|
+
row.append(orgMark(one), text);
|
|
1135
1202
|
if (one.active) {
|
|
1136
1203
|
const tag = (0, dom_js_1.element)("span", { class: "active", "aria-hidden": "true" });
|
|
1137
1204
|
tag.append((0, dom_js_1.element)("span", { class: "check" }), labels.active);
|
package/dist/react.d.ts
CHANGED
|
@@ -39,11 +39,13 @@ export interface GpAccountProps {
|
|
|
39
39
|
labels?: Partial<AccountLabels>;
|
|
40
40
|
/** `row` for the bottom of a sidebar: the picture with the name over the address, full width. */
|
|
41
41
|
layout?: "button" | "row";
|
|
42
|
+
/** `compact` for a sidebar: the popup a step smaller. */
|
|
43
|
+
density?: "regular" | "compact";
|
|
42
44
|
align?: Align;
|
|
43
45
|
placement?: Placement;
|
|
44
46
|
className?: string;
|
|
45
47
|
}
|
|
46
48
|
/** `<gp-account>`, the account popup. */
|
|
47
|
-
export declare function GpAccount({ core, person, organisations, product, viaIdentity, onSignOut, onSwitch, items, labels, layout, align, placement, className, }: GpAccountProps): ReactElement;
|
|
49
|
+
export declare function GpAccount({ core, person, organisations, product, viaIdentity, onSignOut, onSwitch, items, labels, layout, density, align, placement, className, }: GpAccountProps): ReactElement;
|
|
48
50
|
export {};
|
|
49
51
|
//# sourceMappingURL=react.d.ts.map
|
package/dist/react.js
CHANGED
|
@@ -31,7 +31,7 @@ function GpApps({ core, labels, align, placement, className }) {
|
|
|
31
31
|
return (0, react_1.createElement)("gp-apps", { ref, className, align, placement });
|
|
32
32
|
}
|
|
33
33
|
/** `<gp-account>`, the account popup. */
|
|
34
|
-
function GpAccount({ core, person, organisations = null, product = null, viaIdentity = false, onSignOut = null, onSwitch = null, items = null, labels, layout, align, placement, className, }) {
|
|
34
|
+
function GpAccount({ core, person, organisations = null, product = null, viaIdentity = false, onSignOut = null, onSwitch = null, items = null, labels, layout, density, align, placement, className, }) {
|
|
35
35
|
const ref = (0, react_1.useRef)(null);
|
|
36
36
|
(0, react_1.useEffect)(() => {
|
|
37
37
|
(0, index_js_1.defineChromeElements)();
|
|
@@ -72,6 +72,6 @@ function GpAccount({ core, person, organisations = null, product = null, viaIden
|
|
|
72
72
|
if (ref.current !== null && labels !== undefined)
|
|
73
73
|
ref.current.labels = labels;
|
|
74
74
|
}, [labels]);
|
|
75
|
-
return (0, react_1.createElement)("gp-account", { ref, className, layout, align, placement });
|
|
75
|
+
return (0, react_1.createElement)("gp-account", { ref, className, layout, density, align, placement });
|
|
76
76
|
}
|
|
77
77
|
//# sourceMappingURL=react.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ghub/identity-chrome",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "The shared header controls of the Gelhaus Solutions products: the apps picker and the account popup, as custom elements with shadow DOM and as React wrappers. The browser half (state, cache, the last list held for an outage) and the server half (the token calls and a route handler). No dependencies.",
|
|
5
5
|
"author": "Gelhaus Solutions (Enno Gelhaus) <egelhaus@ennogelhaus.de> (https://ennogelhaus.de)",
|
|
6
6
|
"homepage": "https://ennogelhaus.de",
|