astrogators-shared-ui 0.14.0 → 0.16.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 +21 -3
- package/dist/index.css +1 -1
- package/dist/index.js +817 -463
- package/dist/src/components/forms/AllyCodeDropdown.d.ts +10 -0
- package/dist/src/components/layout/AccountCluster.d.ts +13 -0
- package/dist/src/components/layout/MobileNavPanel.d.ts +24 -0
- package/dist/src/components/layout/NavBar.d.ts +33 -40
- package/dist/src/components/layout/NavBurger.d.ts +11 -0
- package/dist/src/components/layout/NavMenu.d.ts +17 -0
- package/dist/src/components/layout/index.d.ts +1 -1
- package/dist/src/hooks/useDismissableMenu.d.ts +18 -0
- package/dist/src/index.d.ts +3 -1
- package/dist/src/navigation/suiteNav.d.ts +28 -0
- package/dist/src/services/tokenRefresh.d.ts +5 -0
- package/package.json +1 -1
|
@@ -2,6 +2,16 @@ import { default as React } from 'react';
|
|
|
2
2
|
export interface AllyCodeDropdownProps {
|
|
3
3
|
onAllyCodeSelected?: (allyCode: string | null) => void;
|
|
4
4
|
className?: string;
|
|
5
|
+
/** Controlled manage-panel open state, so a parent (NavBar) can enforce
|
|
6
|
+
* "only one popover open at a time" across the whole bar. Falls back to
|
|
7
|
+
* self-managed state when omitted. */
|
|
8
|
+
isOpen?: boolean;
|
|
9
|
+
onOpenChange?: (isOpen: boolean) => void;
|
|
10
|
+
/** 'floating' (default): an absolutely-positioned popover, for the desktop
|
|
11
|
+
* bar. 'inline': a plain block in normal flow — used when this is nested
|
|
12
|
+
* inside MobileNavPanel's own scroll container, where a floating popover
|
|
13
|
+
* could get clipped by an ancestor's `overflow`. */
|
|
14
|
+
variant?: 'floating' | 'inline';
|
|
5
15
|
}
|
|
6
16
|
/**
|
|
7
17
|
* AllyCodeDropdown component
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
export interface AccountClusterProps {
|
|
3
|
+
/** Render the username/Admin/Logout (or Login/Sign Up) cluster. Default
|
|
4
|
+
* true. Set false for a static page that wants no auth UI. */
|
|
5
|
+
showAuth?: boolean;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The suite's account block: username/Profile + Admin-if-admin + Logout, or
|
|
9
|
+
* Login + Sign Up when logged out. Identical on every app — this is the one
|
|
10
|
+
* place the Admin link lives now, gated directly on `user.role === 'admin'`,
|
|
11
|
+
* so no app opts in or out of it individually.
|
|
12
|
+
*/
|
|
13
|
+
export declare const AccountCluster: React.FC<AccountClusterProps>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
import { SuiteAppId, SuiteNavSection } from '../../navigation/suiteNav';
|
|
3
|
+
export interface MobileNavPanelProps {
|
|
4
|
+
isOpen: boolean;
|
|
5
|
+
onClose: () => void;
|
|
6
|
+
currentApp: SuiteAppId;
|
|
7
|
+
activeSectionId?: string;
|
|
8
|
+
onSectionClick?: (section: SuiteNavSection, event: React.MouseEvent<HTMLAnchorElement>) => void;
|
|
9
|
+
rightExtras?: React.ReactNode;
|
|
10
|
+
showAuth?: boolean;
|
|
11
|
+
panelId: string;
|
|
12
|
+
/** The burger button — focus returns here when Escape closes the panel. */
|
|
13
|
+
returnFocusRef: React.RefObject<HTMLElement | null>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Portalled to `document.body`: TopBar's `backdrop-filter` makes the header
|
|
17
|
+
* a containing block for `position: fixed` descendants, which would clip a
|
|
18
|
+
* fixed panel/backdrop rendered inside it. This escapes that entirely.
|
|
19
|
+
*
|
|
20
|
+
* Unlike the lightweight desktop NavMenu disclosures, this behaves like a
|
|
21
|
+
* modal — focus moves in on open, Escape closes and returns focus to the
|
|
22
|
+
* burger, body scroll is locked while open.
|
|
23
|
+
*/
|
|
24
|
+
export declare const MobileNavPanel: React.FC<MobileNavPanelProps>;
|
|
@@ -1,48 +1,41 @@
|
|
|
1
1
|
import { default as React } from 'react';
|
|
2
|
-
|
|
3
|
-
* A single top-level section link in the NavBar tab strip.
|
|
4
|
-
*
|
|
5
|
-
* NavBar is intentionally router-agnostic: shared-ui takes no react-router
|
|
6
|
-
* dependency. The consumer decides whether a tab is `active` (e.g. from its own
|
|
7
|
-
* `useLocation`) and may supply `render` to inject a router `<Link>` for soft
|
|
8
|
-
* client-side navigation. Without `render`, the tab is a plain `<a href>`.
|
|
9
|
-
*/
|
|
10
|
-
export interface NavItem {
|
|
11
|
-
label: string;
|
|
12
|
-
href: string;
|
|
13
|
-
active?: boolean;
|
|
14
|
-
render?: (props: {
|
|
15
|
-
className: string;
|
|
16
|
-
children: React.ReactNode;
|
|
17
|
-
}) => React.ReactNode;
|
|
18
|
-
}
|
|
2
|
+
import { SuiteAppId, SuiteNavSection } from '../../navigation/suiteNav';
|
|
19
3
|
export interface NavBarProps {
|
|
20
|
-
/**
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
4
|
+
/** Which app is rendering the bar — drives which trigger is highlighted
|
|
5
|
+
* and which app's sections `onNavigate` fires for. */
|
|
6
|
+
currentApp: SuiteAppId;
|
|
7
|
+
/** Which of currentApp's own sections is active, e.g. from the app's own
|
|
8
|
+
* router. Apps detect "where am I" differently (some have no router at
|
|
9
|
+
* all), so this is supplied by the consumer, not computed by NavBar. */
|
|
10
|
+
activeSectionId?: string;
|
|
11
|
+
/** Fires only for sections belonging to currentApp — cross-app links are
|
|
12
|
+
* always real `<a href>` full page loads, never intercepted. Call
|
|
13
|
+
* `event.preventDefault()` to soft-navigate instead; the menu closes
|
|
14
|
+
* either way, since no page load will unmount it for you. */
|
|
15
|
+
onNavigate?: (section: SuiteNavSection, event: React.MouseEvent<HTMLAnchorElement>) => void;
|
|
16
|
+
/** The one per-app element allowed in the bar: RosterRefresh. Always in
|
|
17
|
+
* the same slot, just left of the ally-code dropdown on desktop, and
|
|
18
|
+
* moves into the mobile panel on small screens. Its position never
|
|
19
|
+
* varies — only its presence/content does. */
|
|
35
20
|
rightExtras?: React.ReactNode;
|
|
21
|
+
/** Render the account cluster (username/Admin/Logout, or Login/Sign Up).
|
|
22
|
+
* Default true. Set false only for a fully static page. */
|
|
23
|
+
showAuth?: boolean;
|
|
24
|
+
/** Non-layout hooks only (e.g. a data attribute for tests). Nothing
|
|
25
|
+
* passed here may change the bar's width, height, padding, or item
|
|
26
|
+
* positions — that capability doesn't exist on this component. */
|
|
36
27
|
className?: string;
|
|
37
28
|
}
|
|
38
29
|
/**
|
|
39
|
-
* NavBar — the
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* The
|
|
45
|
-
*
|
|
46
|
-
*
|
|
30
|
+
* NavBar — the suite-wide top bar. It is built from exactly one source,
|
|
31
|
+
* `SUITE_NAV` (`../../navigation/suiteNav.ts`), and exactly one component.
|
|
32
|
+
* No app passes in its own nav structure, name, or logo target — that
|
|
33
|
+
* capability was removed, not just left unused, because per-app
|
|
34
|
+
* configurability of the bar is what caused it to look different in every
|
|
35
|
+
* app before this component existed. The logo always targets `/`. If a
|
|
36
|
+
* future app needs something the manifest can't express, the manifest's
|
|
37
|
+
* shape grows to cover it; this component does not grow an escape-hatch
|
|
38
|
+
* prop instead. See `../../../CLAUDE.md`'s NavBar note for the full
|
|
39
|
+
* rationale.
|
|
47
40
|
*/
|
|
48
41
|
export declare const NavBar: React.FC<NavBarProps>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface NavBurgerProps {
|
|
2
|
+
isOpen: boolean;
|
|
3
|
+
onClick: () => void;
|
|
4
|
+
controlsId: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Mobile-only burger/✕ toggle. Stateless — NavBar owns whether the mobile
|
|
8
|
+
* panel is open; this just renders the current state and reports clicks.
|
|
9
|
+
* Forwards its ref so MobileNavPanel can return focus here on Escape.
|
|
10
|
+
*/
|
|
11
|
+
export declare const NavBurger: import('react').ForwardRefExoticComponent<NavBurgerProps & import('react').RefAttributes<HTMLButtonElement>>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
import { SuiteNavApp, SuiteNavSection } from '../../navigation/suiteNav';
|
|
3
|
+
export interface NavMenuProps {
|
|
4
|
+
app: SuiteNavApp;
|
|
5
|
+
isCurrentApp: boolean;
|
|
6
|
+
activeSectionId?: string;
|
|
7
|
+
isOpen: boolean;
|
|
8
|
+
onOpenChange: (isOpen: boolean) => void;
|
|
9
|
+
onSectionClick?: (section: SuiteNavSection, event: React.MouseEvent<HTMLAnchorElement>) => void;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* One trigger + dropdown panel per SUITE_NAV app. Disclosure pattern, not
|
|
13
|
+
* `role="menu"` — these are page links, so Tab moves through them like any
|
|
14
|
+
* other link list; arrow keys are an additive convenience, not a
|
|
15
|
+
* replacement for Tab.
|
|
16
|
+
*/
|
|
17
|
+
export declare const NavMenu: React.FC<NavMenuProps>;
|
|
@@ -3,6 +3,6 @@ export type { ContainerProps } from './Container';
|
|
|
3
3
|
export { TopBar } from './TopBar';
|
|
4
4
|
export type { TopBarProps } from './TopBar';
|
|
5
5
|
export { NavBar } from './NavBar';
|
|
6
|
-
export type { NavBarProps
|
|
6
|
+
export type { NavBarProps } from './NavBar';
|
|
7
7
|
export { Footer } from './Footer';
|
|
8
8
|
export type { FooterProps } from './Footer';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { RefObject } from 'react';
|
|
2
|
+
export interface UseDismissableMenuOptions {
|
|
3
|
+
isOpen: boolean;
|
|
4
|
+
onClose: () => void;
|
|
5
|
+
/** The trigger button — clicks on it don't count as "outside", and Escape
|
|
6
|
+
* returns focus here. */
|
|
7
|
+
triggerRef: RefObject<HTMLElement | null>;
|
|
8
|
+
/** The open panel — clicks inside it don't count as "outside" either. */
|
|
9
|
+
panelRef: RefObject<HTMLElement | null>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Shared outside-click + Escape-to-close-and-refocus behavior for the
|
|
13
|
+
* suite's small header popovers (per-app NavMenu, AllyCodeDropdown in its
|
|
14
|
+
* floating variant). Not used by MobileNavPanel: portalled to
|
|
15
|
+
* `document.body`, it has its own backdrop element to click and a full-panel
|
|
16
|
+
* focus-trap concern this hook doesn't try to cover.
|
|
17
|
+
*/
|
|
18
|
+
export declare function useDismissableMenu({ isOpen, onClose, triggerRef, panelRef, }: UseDismissableMenuOptions): void;
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { Container, TopBar, NavBar, Footer } from './components/layout';
|
|
2
|
-
export type { ContainerProps, TopBarProps, NavBarProps,
|
|
2
|
+
export type { ContainerProps, TopBarProps, NavBarProps, FooterProps } from './components/layout';
|
|
3
|
+
export { SUITE_NAV } from './navigation/suiteNav';
|
|
4
|
+
export type { SuiteAppId, SuiteNavApp, SuiteNavSection } from './navigation/suiteNav';
|
|
3
5
|
export { Button, Input, Select, AllyCodeDropdown, RosterRefresh } from './components/forms';
|
|
4
6
|
export type { ButtonProps, InputProps, SelectProps, SelectOption, AllyCodeDropdownProps, RosterRefreshProps } from './components/forms';
|
|
5
7
|
export { Card, Badge, Modal } from './components/display';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single source of truth for the suite-wide NavBar's contents.
|
|
3
|
+
*
|
|
4
|
+
* No app passes its own name, sections, or logo target into `NavBar` — it all
|
|
5
|
+
* comes from here. If a future app needs something this shape can't express,
|
|
6
|
+
* grow the shape (a new field on `SuiteNavApp`/`SuiteNavSection`); an app
|
|
7
|
+
* never gets its own bypass prop instead. See `NavBar`'s doc comment and
|
|
8
|
+
* `../../CLAUDE.md`'s NavBar note for the full rationale.
|
|
9
|
+
*/
|
|
10
|
+
export type SuiteAppId = 'hub' | 'mod-ledger' | 'navicharts' | 'nightwatcher';
|
|
11
|
+
export interface SuiteNavSection {
|
|
12
|
+
id: string;
|
|
13
|
+
label: string;
|
|
14
|
+
/** Absolute, same-origin path, e.g. '/mod-ledger/evaluations'. */
|
|
15
|
+
href: string;
|
|
16
|
+
/** Hidden when logged out. */
|
|
17
|
+
requiresAuth?: boolean;
|
|
18
|
+
/** Hidden unless the current user's role is in this list. */
|
|
19
|
+
roles?: string[];
|
|
20
|
+
}
|
|
21
|
+
export interface SuiteNavApp {
|
|
22
|
+
id: Exclude<SuiteAppId, 'hub'>;
|
|
23
|
+
label: string;
|
|
24
|
+
href: string;
|
|
25
|
+
status: 'available' | 'coming-soon';
|
|
26
|
+
sections: SuiteNavSection[];
|
|
27
|
+
}
|
|
28
|
+
export declare const SUITE_NAV: SuiteNavApp[];
|
|
@@ -19,6 +19,11 @@ export declare const refreshAccessToken: () => Promise<string>;
|
|
|
19
19
|
* refresh is needed but fails (e.g. the refresh token is also expired), the
|
|
20
20
|
* original 401 Response is returned so the caller can surface a re-login state.
|
|
21
21
|
*
|
|
22
|
+
* Transient unavailability (a backend swapped mid-deploy, a 502/503/504, a
|
|
23
|
+
* dropped connection) is retried a couple of times — see `fetchWithRetry` for
|
|
24
|
+
* the exact, deliberately-narrow policy (non-idempotent writes are only
|
|
25
|
+
* retried when no response was ever received).
|
|
26
|
+
*
|
|
22
27
|
* Works against ANY resource URL; only the refresh hop uses the configured
|
|
23
28
|
* auth base URL.
|
|
24
29
|
*/
|