@hotosm/hanko-auth 0.4.5 → 0.4.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotosm/hanko-auth",
3
- "version": "0.4.5",
3
+ "version": "0.4.7",
4
4
  "description": "Web component for HOTOSM SSO authentication with Hanko and OSM integration",
5
5
  "type": "module",
6
6
  "main": "dist/hanko-auth.umd.js",
@@ -3,7 +3,7 @@ import { css } from "lit";
3
3
  export const styles = css`
4
4
  :host {
5
5
  display: block;
6
- font-family: var(--hot-font-sans);
6
+ font-family: var(--font-family, var(--hot-font-sans));
7
7
  }
8
8
 
9
9
  .container {
@@ -38,28 +38,26 @@ export const styles = css`
38
38
  animation: spin 1s linear infinite;
39
39
  margin: 0 auto;
40
40
  }
41
- /* Container that mimics the login button dimensions */
41
+ /* Container that mimics the avatar/dropdown-trigger dimensions */
42
42
  .loading-placeholder {
43
43
  display: inline-grid;
44
44
  place-items: center;
45
- /* Use same styling as login-link button */
46
- padding: var(--login-btn-padding, var(--hot-spacing-x-small) var(--hot-spacing-medium));
47
- margin: var(--login-btn-margin, 0);
48
- font-size: var(--login-btn-text-size, var(--hot-font-size-medium));
49
- font-family: var(--login-btn-font-family, inherit);
50
- border-radius: var(--login-btn-border-radius, var(--hot-border-radius-medium));
45
+ /* Match dropdown-trigger padding so size is stable pre/post load */
46
+ padding: var(--hot-spacing-x-small);
47
+ width: var(--hot-spacing-2x-large);
48
+ height: var(--hot-spacing-2x-large);
49
+ box-sizing: content-box;
51
50
  }
52
51
 
53
52
  /* Invisible text to reserve button width */
54
53
  .loading-placeholder-text {
55
- visibility: hidden;
56
- grid-area: 1 / 1;
54
+ display: none;
57
55
  }
58
56
 
59
57
  .spinner-small {
60
58
  grid-area: 1 / 1;
61
- width: 1em;
62
- height: 1em;
59
+ width: var(--hot-spacing-2x-large);
60
+ height: var(--hot-spacing-2x-large);
63
61
  border: 2px solid var(--hot-color-gray-200);
64
62
  border-top: 2px solid var(--hot-color-gray-600);
65
63
  border-radius: 50%;
@@ -87,7 +85,8 @@ export const styles = css`
87
85
  border: none;
88
86
  border-radius: 6px;
89
87
  font-size: 14px;
90
- font-weight: 500;
88
+ font-family: var(--font-family, var(--hot-font-sans));
89
+ font-weight: var(--font-weight, 500);
91
90
  cursor: pointer;
92
91
  transition: all 0.2s;
93
92
  }
@@ -255,15 +254,21 @@ export const styles = css`
255
254
  .login-link {
256
255
  color: var(--login-btn-text-color, white);
257
256
  font-size: var(--login-btn-text-size, var(--hot-font-size-medium));
258
- border-radius: var(--login-btn-border-radius, var(--hot-border-radius-medium));
257
+ border-radius: var(
258
+ --login-btn-border-radius,
259
+ var(--hot-border-radius-medium)
260
+ );
259
261
  text-decoration: none;
260
- padding: var(--login-btn-padding, var(--hot-spacing-x-small) var(--hot-spacing-medium));
262
+ padding: var(
263
+ --login-btn-padding,
264
+ var(--hot-spacing-x-small) var(--hot-spacing-medium)
265
+ );
261
266
  margin: var(--login-btn-margin, 0);
262
267
  display: inline-block;
263
268
  cursor: pointer;
264
269
  transition: all 0.2s;
265
- font-weight: var(--hot-font-weight-medium);
266
- font-family: var(--login-btn-font-family, inherit);
270
+ font-weight: var(--login-btn-font-weight, var(--font-weight, var(--hot-font-weight-medium)));
271
+ font-family: var(--login-btn-font-family, var(--font-family, var(--hot-font-sans)));
267
272
  }
268
273
 
269
274
  /* Button variants - filled */
@@ -365,7 +370,6 @@ export const styles = css`
365
370
  position: absolute;
366
371
  right: 0;
367
372
  background: white;
368
- border: 1px solid var(--hot-color-gray-100);
369
373
  border-radius: var(--hot-border-radius-medium);
370
374
  z-index: 1000;
371
375
  opacity: 0;
@@ -386,7 +390,7 @@ export const styles = css`
386
390
  .dropdown-content.open {
387
391
  opacity: 1;
388
392
  visibility: visible;
389
- transform: translateY(0);
393
+ transform: translateY(-1px);
390
394
  }
391
395
 
392
396
  .dropdown-content button {
@@ -400,6 +404,7 @@ export const styles = css`
400
404
  text-align: left;
401
405
  transition: background-color 0.2s ease;
402
406
  gap: var(--hot-spacing-small);
407
+ font-family: var(--font-family, var(--hot-font-sans, inherit));
403
408
  font-size: var(--hot-font-size-small);
404
409
  color: var(--hot-color-gray-900);
405
410
  }
@@ -427,4 +432,65 @@ export const styles = css`
427
432
  width: 20px;
428
433
  height: 20px;
429
434
  }
435
+
436
+ /* Bar display mode */
437
+
438
+ :host([display="bar"]) {
439
+ width: 100%;
440
+ }
441
+
442
+ :host([display="bar"]) .dropdown {
443
+ display: block;
444
+ width: 100%;
445
+ }
446
+
447
+ .bar-trigger {
448
+ display: flex;
449
+ align-items: center;
450
+ width: 100%;
451
+ padding: var(--hot-spacing-small) var(--hot-spacing-medium);
452
+ background: none;
453
+ border: none;
454
+ cursor: pointer;
455
+ gap: var(--hot-spacing-small);
456
+ font-family: var(--font-family, var(--hot-font-sans, inherit));
457
+ }
458
+
459
+ .bar-trigger:hover,
460
+ .bar-trigger:active,
461
+ .bar-trigger:focus {
462
+ background: none;
463
+ outline: none;
464
+ }
465
+
466
+ .bar-info {
467
+ display: flex;
468
+ align-items: center;
469
+ gap: var(--hot-spacing-small);
470
+ flex: 1;
471
+ min-width: 0;
472
+ }
473
+
474
+ .bar-email {
475
+ font-size: var(--hot-font-size-medium);
476
+ color: var(--hot-color-gray-900);
477
+ overflow: hidden;
478
+ text-overflow: ellipsis;
479
+ white-space: nowrap;
480
+ }
481
+
482
+ .bar-chevron {
483
+ width: 16px;
484
+ height: 16px;
485
+ flex-shrink: 0;
486
+ color: var(--hot-color-gray-900);
487
+ }
488
+
489
+ /* When bar-trigger is used as a login-link, override width behavior */
490
+ a.bar-trigger.login-link {
491
+ display: flex;
492
+ width: 100%;
493
+ box-sizing: border-box;
494
+ text-decoration: none;
495
+ }
430
496
  `;
package/src/hanko-auth.ts CHANGED
@@ -27,6 +27,8 @@ import accountIcon from "../assets/icon-account.svg";
27
27
  import logoutIcon from "../assets/icon-logout.svg";
28
28
  import mapIcon from "../assets/icon-map.svg";
29
29
  import checkIcon from "../assets/icon-check.svg";
30
+ import chevronDownIcon from "../assets/chevron-down.svg";
31
+ import chevronUpIcon from "../assets/chevron-up.svg";
30
32
 
31
33
  // Track if Hanko has been registered globally
32
34
  let hankoRegistered = false;
@@ -129,6 +131,9 @@ export class HankoAuth extends LitElement {
129
131
  | "primary"
130
132
  | "neutral"
131
133
  | "danger" = "primary";
134
+ // Display mode: "default" (compact avatar button) or "bar" (full-width bar with avatar + email + chevron)
135
+ @property({ type: String, reflect: true }) display: "default" | "bar" =
136
+ "default";
132
137
 
133
138
  // Internal state
134
139
  @state() private user: UserState | null = null;
@@ -167,6 +172,40 @@ export class HankoAuth extends LitElement {
167
172
  }
168
173
  };
169
174
 
175
+ /** Dropdown menu content for bar display mode (no email, only action links) */
176
+ private renderBarDropdownContent() {
177
+ return html`
178
+ <div class="dropdown-content ${this.isOpen ? "open" : ""}">
179
+ <button data-action="profile" @click=${this.handleDropdownSelect}>
180
+ <img src="${accountIcon}" class="icon" alt="Account icon" />
181
+ ${this.t("myHotAccount")}
182
+ </button>
183
+ ${this.osmRequired
184
+ ? this.osmConnected
185
+ ? html`
186
+ <button class="osm-connected" disabled>
187
+ <img src="${checkIcon}" alt="Check icon" class="icon" />
188
+ ${this.t("connectedToOsm")} (@${this.osmData?.osm_username})
189
+ </button>
190
+ `
191
+ : html`
192
+ <button
193
+ data-action="connect-osm"
194
+ @click=${this.handleDropdownSelect}
195
+ >
196
+ <img src="${mapIcon}" alt="Check icon" class="icon" />
197
+ ${this.t("connectToOsm")}
198
+ </button>
199
+ `
200
+ : ""}
201
+ <button data-action="logout" @click=${this.handleDropdownSelect}>
202
+ <img src="${logoutIcon}" alt="Log out icon" class="icon" />
203
+ ${this.t("logOut")}
204
+ </button>
205
+ </div>
206
+ `;
207
+ }
208
+
170
209
  // Private fields
171
210
  private _trailingSlashCache: Record<string, boolean> = {};
172
211
  private _debugMode = false;
@@ -865,7 +904,11 @@ export class HankoAuth extends LitElement {
865
904
  super.updated(changedProperties);
866
905
  // Re-attach event listeners when user becomes null (after logout)
867
906
  // because a new <hanko-auth> element is created
868
- if (changedProperties.has("user") && this.user === null && this.showProfile) {
907
+ if (
908
+ changedProperties.has("user") &&
909
+ this.user === null &&
910
+ this.showProfile
911
+ ) {
869
912
  this.log("🔄 User logged out, re-attaching event listeners...");
870
913
  this._currentHankoAuthElement = null;
871
914
  this.setupEventListeners();
@@ -1483,8 +1526,34 @@ export class HankoAuth extends LitElement {
1483
1526
  </div>
1484
1527
  </div>
1485
1528
  `;
1529
+ } else if (this.display === "bar") {
1530
+ // Logged in, show-profile=false, display=bar: render full-width bar trigger
1531
+ return html`
1532
+ <div class="dropdown">
1533
+ <button
1534
+ @click=${this.toggleDropdown}
1535
+ aria-label="${this.t("openAccountMenu")}"
1536
+ aria-expanded=${this.isOpen}
1537
+ aria-haspopup="true"
1538
+ class="bar-trigger"
1539
+ >
1540
+ <div class="bar-info">
1541
+ <span class="header-avatar">${initial}</span>
1542
+ <span class="bar-email"
1543
+ >${this.user.email || this.user.id}</span
1544
+ >
1545
+ </div>
1546
+ <img
1547
+ src="${this.isOpen ? chevronUpIcon : chevronDownIcon}"
1548
+ class="bar-chevron"
1549
+ alt=""
1550
+ />
1551
+ </button>
1552
+ ${this.renderBarDropdownContent()}
1553
+ </div>
1554
+ `;
1486
1555
  } else {
1487
- // Logged in, show-profile=false: render dropdown
1556
+ // Logged in, show-profile=false, display=default: render compact dropdown
1488
1557
  return html`
1489
1558
  <div class="dropdown">
1490
1559
  <button
@@ -1621,6 +1690,24 @@ export class HankoAuth extends LitElement {
1621
1690
  returnTo,
1622
1691
  )}${this.osmRequired ? "&osm_required=true" : ""}${autoConnectParam}&lang=${this.lang}`;
1623
1692
 
1693
+ /* if (this.display === "bar") {
1694
+ return html`<a
1695
+ class="bar-trigger login-link ${this.buttonVariant} ${this.buttonColor}"
1696
+ href="${loginUrl}"
1697
+ @click=${(e: Event) => {
1698
+ e.preventDefault();
1699
+ window.location.href = loginUrl;
1700
+ }}
1701
+ >
1702
+ <span class="bar-email">${this.t("logIn")}</span>
1703
+ <img
1704
+ src="${chevronDownIcon}"
1705
+ class="bar-chevron"
1706
+ alt=""
1707
+ />
1708
+ </a>`;
1709
+ } */
1710
+
1624
1711
  return html`<a
1625
1712
  class="login-link ${this.buttonVariant} ${this.buttonColor}"
1626
1713
  href="${loginUrl}"