@the-portland-company/shell 0.16.4 → 0.16.6

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/dist/native.cjs CHANGED
@@ -214,7 +214,25 @@ function NavChromeProvider({
214
214
  fetchJson,
215
215
  children
216
216
  }) {
217
- return /* @__PURE__ */ jsxRuntime.jsx(NavChromeContext.Provider, { value: { fetchJson }, children });
217
+ const [me, setMe] = react.useState(null);
218
+ const [meLoaded, setMeLoaded] = react.useState(false);
219
+ react.useEffect(() => {
220
+ let cancelled = false;
221
+ fetchJson("me").then((data) => {
222
+ if (!cancelled) setMe(data);
223
+ }).catch(() => {
224
+ }).finally(() => {
225
+ if (!cancelled) setMeLoaded(true);
226
+ });
227
+ return () => {
228
+ cancelled = true;
229
+ };
230
+ }, [fetchJson]);
231
+ return /* @__PURE__ */ jsxRuntime.jsx(NavChromeContext.Provider, { value: { fetchJson, me, meLoaded }, children });
232
+ }
233
+ function useChromeMe() {
234
+ const ctx = react.useContext(NavChromeContext);
235
+ return { me: ctx?.me ?? null, loaded: ctx?.meLoaded ?? false };
218
236
  }
219
237
  function useNavChrome(component) {
220
238
  const ctx = react.useContext(NavChromeContext);
@@ -677,23 +695,21 @@ function useThemePreference() {
677
695
  setValueState(pref);
678
696
  applyThemePreference(pref);
679
697
  }, []);
698
+ const meCtx = react.useContext(NavChromeContext);
680
699
  react.useEffect(() => {
681
- if (!fetchJson || reconciled.current) return;
682
- reconciled.current = true;
683
- fetchJson("me").then((data) => {
684
- const pref = data?.themePreference;
685
- if (isThemePreference(pref)) {
686
- setValueState(pref);
687
- applyThemePreference(pref);
688
- writeThemeCookie(pref);
689
- try {
690
- localStorage.setItem(THEME_STORAGE_KEY, pref);
691
- } catch {
692
- }
700
+ if (reconciled.current) return;
701
+ const pref = meCtx?.me?.themePreference;
702
+ if (isThemePreference(pref)) {
703
+ reconciled.current = true;
704
+ setValueState(pref);
705
+ applyThemePreference(pref);
706
+ writeThemeCookie(pref);
707
+ try {
708
+ localStorage.setItem(THEME_STORAGE_KEY, pref);
709
+ } catch {
693
710
  }
694
- }).catch(() => {
695
- });
696
- }, [fetchJson]);
711
+ }
712
+ }, [meCtx?.me]);
697
713
  react.useEffect(() => {
698
714
  const onStorage = (e) => {
699
715
  if (e.key === THEME_STORAGE_KEY && isThemePreference(e.newValue)) {
@@ -1225,8 +1241,11 @@ function NavTopBar({
1225
1241
  actions,
1226
1242
  themeToggle
1227
1243
  }) {
1228
- const initials = (user?.name ?? "Politogy").slice(0, 2).toUpperCase();
1229
- const secondary = user?.email || [user?.tier, user?.role].filter(Boolean).join(" \xB7 ") || "member";
1244
+ const rawName = user?.name?.trim() || "";
1245
+ const hasRealName = rawName.length > 0 && !rawName.includes("@");
1246
+ const displayName = hasRealName ? formatFirstLast(rawName) : user?.email ?? "Signed in";
1247
+ const initials = (hasRealName ? rawName : user?.email ?? "P").slice(0, 2).toUpperCase();
1248
+ const secondary = hasRealName ? user?.email || [user?.tier, user?.role].filter(Boolean).join(" \xB7 ") || "member" : [user?.tier, user?.role].filter(Boolean).join(" \xB7 ") || "member";
1230
1249
  return /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "sticky top-0 z-20 flex h-20 items-center justify-between gap-4 border-b border-[color:var(--line)] bg-[color:var(--panel)] px-4 backdrop-blur-xl", children: [
1231
1250
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
1232
1251
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -1239,7 +1258,7 @@ function NavTopBar({
1239
1258
  ),
1240
1259
  subtitle ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-sm text-[color:var(--muted)]", children: subtitle }) : null
1241
1260
  ] }),
1242
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-[var(--topbar-icon-gap,8px)]", children: [
1261
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex shrink-0 items-center gap-[var(--topbar-icon-gap,8px)]", children: [
1243
1262
  actionsList && actionsList.length > 0 ? actionsList.map((action) => /* @__PURE__ */ jsxRuntime.jsx(NavTopBarActionButton, { action }, action.key)) : null,
1244
1263
  actions,
1245
1264
  themeToggle,
@@ -1254,7 +1273,7 @@ function NavTopBar({
1254
1273
  }
1255
1274
  ) : initials }),
1256
1275
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "hidden flex-col leading-tight md:flex", children: [
1257
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold text-[color:var(--foreground)]", children: user?.name ?? "Signed in" }),
1276
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold text-[color:var(--foreground)]", children: displayName }),
1258
1277
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-[color:var(--muted)]", children: secondary })
1259
1278
  ] }),
1260
1279
  /* @__PURE__ */ jsxRuntime.jsx(ChevronDown, {}),
@@ -1263,7 +1282,7 @@ function NavTopBar({
1263
1282
  {
1264
1283
  title: "My Account",
1265
1284
  description: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1266
- user?.name ?? "Signed in",
1285
+ hasRealName ? rawName : user?.email ?? "Signed in",
1267
1286
  user?.email ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1268
1287
  /* @__PURE__ */ jsxRuntime.jsx("br", {}),
1269
1288
  user.email
@@ -1391,10 +1410,64 @@ function NavTopBar({
1391
1410
  }
1392
1411
  ) : null
1393
1412
  ] })
1394
- ] })
1413
+ ] }),
1414
+ !hasRealName && user ? /* @__PURE__ */ jsxRuntime.jsx(NamePromptBubble, { profileHref: userMenu?.myProfileHref }) : null
1395
1415
  ] })
1396
1416
  ] });
1397
1417
  }
1418
+ function NamePromptBubble({ profileHref }) {
1419
+ const KEY = "politogy_name_prompt_dismissed";
1420
+ const [dismissed, setDismissed] = react.useState(true);
1421
+ react.useEffect(() => {
1422
+ try {
1423
+ setDismissed(sessionStorage.getItem(KEY) === "1");
1424
+ } catch {
1425
+ setDismissed(false);
1426
+ }
1427
+ }, []);
1428
+ if (dismissed) return null;
1429
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute right-4 top-full z-30 mt-2 w-64 rounded-xl border border-[color:var(--line)] bg-[color:var(--panel-elevated)] p-3 shadow-[var(--shadow-soft)]", children: [
1430
+ /* @__PURE__ */ jsxRuntime.jsx(
1431
+ "span",
1432
+ {
1433
+ "aria-hidden": true,
1434
+ className: "absolute -top-[5px] right-[11px] h-2.5 w-2.5 rotate-45 border-l border-t border-[color:var(--line)] bg-[color:var(--panel-elevated)]"
1435
+ }
1436
+ ),
1437
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-semibold text-[color:var(--foreground)]", children: "Add your name" }),
1438
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-0.5 text-xs leading-snug text-[color:var(--muted)]", children: "Set your first and last name so teammates see you \u2014 not your email." }),
1439
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-2 flex items-center gap-2", children: [
1440
+ profileHref ? /* @__PURE__ */ jsxRuntime.jsx(
1441
+ "a",
1442
+ {
1443
+ href: safeHref(profileHref),
1444
+ className: "rounded-full bg-[color:var(--color-race-red)] px-3 py-1 text-xs font-semibold text-white hover:opacity-90",
1445
+ children: "Set name"
1446
+ }
1447
+ ) : null,
1448
+ /* @__PURE__ */ jsxRuntime.jsx(
1449
+ "button",
1450
+ {
1451
+ type: "button",
1452
+ onClick: () => {
1453
+ setDismissed(true);
1454
+ try {
1455
+ sessionStorage.setItem(KEY, "1");
1456
+ } catch {
1457
+ }
1458
+ },
1459
+ className: "rounded-full border border-[color:var(--line)] px-3 py-1 text-xs font-semibold text-[color:var(--foreground)] hover:bg-[color:var(--panel)]",
1460
+ children: "Dismiss"
1461
+ }
1462
+ )
1463
+ ] })
1464
+ ] });
1465
+ }
1466
+ function formatFirstLast(name) {
1467
+ const parts = name.split(/\s+/).filter(Boolean);
1468
+ if (parts.length === 1) return parts[0];
1469
+ return `${parts[0]} ${parts[parts.length - 1][0].toUpperCase()}.`;
1470
+ }
1398
1471
  function ProfileIcon() {
1399
1472
  return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: 16, height: 16, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
1400
1473
  /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" }),
@@ -1603,6 +1676,7 @@ exports.envChipClasses = envChipClasses;
1603
1676
  exports.isNavItemActive = isNavItemActive;
1604
1677
  exports.standardPolitogyNav = standardPolitogyNav;
1605
1678
  exports.topBarIconButtonClass = topBarIconButtonClass;
1679
+ exports.useChromeMe = useChromeMe;
1606
1680
  exports.useChromeOrgs = useChromeOrgs;
1607
1681
  exports.useThemePreference = useThemePreference;
1608
1682
  //# sourceMappingURL=native.cjs.map