@gearbox-protocol/ui-kit 4.0.0-next.13 → 4.0.0-next.14

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.
Files changed (36) hide show
  1. package/dist/cjs/components/index.cjs +1 -1
  2. package/dist/cjs/components/layout/app-footer/app-footer.cjs +1 -1
  3. package/dist/cjs/components/sync-status-pill/constants.cjs +1 -0
  4. package/dist/cjs/components/sync-status-pill/get-sync-status.cjs +1 -0
  5. package/dist/cjs/components/sync-status-pill/index.cjs +1 -0
  6. package/dist/cjs/components/sync-status-pill/sync-status-pill.cjs +1 -0
  7. package/dist/cjs/index.cjs +1 -1
  8. package/dist/cjs/locale/en.json.cjs +1 -1
  9. package/dist/esm/components/index.js +586 -581
  10. package/dist/esm/components/layout/app-footer/app-footer.js +20 -23
  11. package/dist/esm/components/sync-status-pill/constants.js +10 -0
  12. package/dist/esm/components/sync-status-pill/get-sync-status.js +78 -0
  13. package/dist/esm/components/sync-status-pill/index.js +9 -0
  14. package/dist/esm/components/sync-status-pill/sync-status-pill.js +137 -0
  15. package/dist/esm/index.js +732 -727
  16. package/dist/esm/locale/en.json.js +8 -3
  17. package/dist/globals.css +1 -1
  18. package/dist/types/components/index.d.ts +1 -1
  19. package/dist/types/components/layout/app-footer/app-footer.d.ts +9 -15
  20. package/dist/types/components/sync-status-pill/constants.d.ts +7 -0
  21. package/dist/types/components/sync-status-pill/get-sync-status.d.ts +100 -0
  22. package/dist/types/components/sync-status-pill/index.d.ts +3 -0
  23. package/dist/types/components/sync-status-pill/sync-status-pill.d.ts +42 -0
  24. package/dist/types/index.d.ts +2 -2
  25. package/dist/types/locale/en.json.d.ts +8 -3
  26. package/dist/types/types/footer.d.ts +1 -45
  27. package/package.json +1 -1
  28. package/dist/cjs/components/block-sync-pill/block-sync-pill.cjs +0 -1
  29. package/dist/cjs/components/block-sync-pill/block-sync-status.cjs +0 -1
  30. package/dist/cjs/components/block-sync-pill/index.cjs +0 -1
  31. package/dist/esm/components/block-sync-pill/block-sync-pill.js +0 -211
  32. package/dist/esm/components/block-sync-pill/block-sync-status.js +0 -12
  33. package/dist/esm/components/block-sync-pill/index.js +0 -4
  34. package/dist/types/components/block-sync-pill/block-sync-pill.d.ts +0 -35
  35. package/dist/types/components/block-sync-pill/block-sync-status.d.ts +0 -29
  36. package/dist/types/components/block-sync-pill/index.d.ts +0 -1
@@ -7,7 +7,6 @@ export * from './badge';
7
7
  export * from './base';
8
8
  export * from './base-link';
9
9
  export * from './base-table';
10
- export * from './block-sync-pill';
11
10
  export * from './breadcrumbs';
12
11
  export * from './breakpoint-provider';
13
12
  export * from './buttons';
@@ -70,6 +69,7 @@ export * from './spinner-loader';
70
69
  export * from './split-list';
71
70
  export * from './status-dot';
72
71
  export * from './status-elements';
72
+ export * from './sync-status-pill';
73
73
  export * from './tab-control';
74
74
  export * from './table';
75
75
  export * from './tabs';
@@ -1,4 +1,3 @@
1
- import { BlockSyncProps } from '../../../types/footer';
2
1
  import * as React from "react";
3
2
  /**
4
3
  * A single legal/navigation link rendered in {@link AppFooter}.
@@ -30,20 +29,14 @@ export interface AppFooterProps extends React.HTMLAttributes<HTMLElement> {
30
29
  */
31
30
  year?: number;
32
31
  /**
33
- * Whether to show the status pill on the far right. Defaults to `true`.
32
+ * Slot between the copyright and the links typically a
33
+ * {@link SyncStatusPill}. The footer knows nothing about what goes in it.
34
+ *
35
+ * A slot rather than sync-specific props: the footer used to accept a fixed
36
+ * `blockSyncProps` shape and render its own pill from it, which made every
37
+ * change to the indicator a change to the footer's API.
34
38
  */
35
- showStatus?: boolean;
36
- /**
37
- * Label rendered inside the status pill. Defaults to `Status`.
38
- */
39
- statusLabel?: React.ReactNode;
40
- /**
41
- * When provided, the status pill becomes a live network-synchronization
42
- * indicator ({@link BlockSyncPill}): the dot color reflects the time since
43
- * the last block update and hovering reveals per-chain block details.
44
- * Without it, a static green status pill is rendered.
45
- */
46
- blockSyncProps?: BlockSyncProps;
39
+ status?: React.ReactNode;
47
40
  }
48
41
  /**
49
42
  * AppFooter — minimal, Hyperliquid-style footer used by the Gearbox App
@@ -56,7 +49,8 @@ export interface AppFooterProps extends React.HTMLAttributes<HTMLElement> {
56
49
  * @example
57
50
  * ```tsx
58
51
  * <AppFooter />
59
- * <AppFooter links={myLinks} copyright="© Acme, 2026" showStatus={false} />
52
+ * <AppFooter links={myLinks} copyright="© Acme, 2026" />
53
+ * <AppFooter status={<SyncStatusPill {...syncProps} />} />
60
54
  * ```
61
55
  */
62
56
  export declare const AppFooter: React.ForwardRefExoticComponent<AppFooterProps & React.RefAttributes<HTMLElement>>;
@@ -0,0 +1,7 @@
1
+ export declare class SyncStatusPillTest {
2
+ static root: string;
3
+ static dot: string;
4
+ static tooltip: string;
5
+ static headline: string;
6
+ static chainRow: string;
7
+ }
@@ -0,0 +1,100 @@
1
+ import { ResponseMetadata } from '@gearbox-protocol/sdk/model';
2
+ /**
3
+ * Health of one source (onchain or offchain) as far as one chain is concerned.
4
+ *
5
+ * `unknown` is not an error: it means this source has not spoken about the
6
+ * chain yet, which is what the first frames of a two-source read look like.
7
+ */
8
+ export type SourceHealth = "ok" | "stale" | "failed" | "unknown";
9
+ /**
10
+ * What the user sees and can do on one chain.
11
+ *
12
+ * Derived from `onchain` alone — see {@link deriveChainHealth}. `offchain`
13
+ * never moves a chain between these values, it only annotates the row.
14
+ */
15
+ export type ChainHealth =
16
+ /** Onchain is fresh: what is on screen is current and actions go through. */
17
+ "live"
18
+ /** Onchain lagged or never answered: rows come from the backend. */
19
+ | "cached"
20
+ /** Nobody answered. */
21
+ | "none";
22
+ /** Rolled-up health of the whole app. */
23
+ export type AppSyncHealth = "starting" | "live" | "cached" | "none";
24
+ /** Where the app's single SDK is in its attach lifecycle. */
25
+ export type SdkAttachStatus = "loading" | "error" | "ready";
26
+ /**
27
+ * One source exactly as the SDK returned it, plus the local clock reading of
28
+ * its last success.
29
+ *
30
+ * `meta` is `DataResponse.meta` verbatim — no projection, no intermediate
31
+ * format. The previous footer indicator died because ui-kit owned its own
32
+ * shape (`BlockByChain`) and the app had to hand-translate SDK state into it;
33
+ * the translator outlived nothing and the format outlived its source.
34
+ */
35
+ export interface SyncSourceInput {
36
+ /** `DataResponse.meta`, or `undefined` while the source has never answered. */
37
+ meta: ResponseMetadata | undefined;
38
+ /**
39
+ * react-query `dataUpdatedAt` in ms. The one number that cannot come from
40
+ * the SDK: `ChainSucceeded.timestamp` is the *block's* time, which answers
41
+ * "how old is the chain state", not "is our polling loop still alive".
42
+ * `0` / `undefined` mean no success yet.
43
+ */
44
+ updatedAt?: number;
45
+ }
46
+ /** Per-chain outcome, ready to render as one tooltip row. */
47
+ export interface ChainStatus {
48
+ chainId: number;
49
+ /** Decides {@link health}, and through it the dot colour. */
50
+ onchain: SourceHealth;
51
+ /** Never decides colour — only the row's suffix. */
52
+ offchain: SourceHealth;
53
+ health: ChainHealth;
54
+ }
55
+ export interface SyncStatus {
56
+ health: AppSyncHealth;
57
+ chains: ChainStatus[];
58
+ /**
59
+ * Local clock (ms) of the last success behind what is currently shown, or
60
+ * `undefined` when nothing has been shown yet. Feeds the "· 12s ago" in the
61
+ * tooltip header.
62
+ */
63
+ syncedAt: number | undefined;
64
+ }
65
+ export interface GetSyncStatusProps {
66
+ onchain: SyncSourceInput;
67
+ offchain: SyncSourceInput;
68
+ attach?: SdkAttachStatus;
69
+ /** Reference "now" in ms. */
70
+ now: number;
71
+ /** Age after which a source counts as stale. */
72
+ staleAfterMs: number;
73
+ }
74
+ /**
75
+ * Pure derivation of the sync indicator's state.
76
+ *
77
+ * The whole model rests on one asymmetry: **onchain is the ability to act,
78
+ * offchain is the trimmings** (charts, APY, history). So the colour tracks
79
+ * `onchain` and nothing else — a dead backend under a healthy chain is still
80
+ * green, because the user sees current data and their transactions land. The
81
+ * reverse, a live polling loop over a lagging node, is *not* green: the screen
82
+ * looks current while a transaction would be built against stale state.
83
+ *
84
+ * The roll-up is deliberately optimistic: one dead chain annotates its own row
85
+ * and leaves the dot green. Statuspage-style pessimism ("any component down ⇒
86
+ * system degraded") assumes components of one service; our chains are
87
+ * independent, and greying the dot for a chain the user is not on is a false
88
+ * alarm.
89
+ */
90
+ export declare function getSyncStatus({ onchain, offchain, attach, now, staleAfterMs, }: GetSyncStatusProps): SyncStatus;
91
+ /** Dot variants this indicator is allowed to use. */
92
+ export type SyncStatusDotVariant = "success" | "default";
93
+ /**
94
+ * The palette rule, as a function rather than a comment: green when the user
95
+ * can act, muted grey otherwise. Never `alert` or `warning` — an alarming dot
96
+ * in permanent view teaches people to ignore it, and the screens surface their
97
+ * own failures. Exported so the rule is pinned by a unit test instead of by
98
+ * inspecting rendered markup.
99
+ */
100
+ export declare function syncStatusDotVariant(health: AppSyncHealth): SyncStatusDotVariant;
@@ -0,0 +1,3 @@
1
+ export * from './constants';
2
+ export * from './get-sync-status';
3
+ export * from './sync-status-pill';
@@ -0,0 +1,42 @@
1
+ import { NetworkById } from '../../types/footer';
2
+ import { SdkAttachStatus, SyncSourceInput } from './get-sync-status';
3
+ import type * as React from "react";
4
+ export interface SyncStatusPillProps {
5
+ onchain: SyncSourceInput;
6
+ offchain: SyncSourceInput;
7
+ attach?: SdkAttachStatus;
8
+ /** A read is in flight — the dot pulses. Orthogonal to health. */
9
+ isSyncing?: boolean;
10
+ /** Only for chain labels in the tooltip. */
11
+ networkById: NetworkById;
12
+ }
13
+ /**
14
+ * SyncStatusPill — data-freshness indicator for an app footer: one dot, and
15
+ * every word in the tooltip.
16
+ *
17
+ * Takes the SDK's own `DataResponse.meta` for each source, untouched, and
18
+ * derives the status itself. The predecessor (`BlockSyncPill`) owned a bespoke
19
+ * `BlockByChain` shape that each client had to hand-translate SDK state into;
20
+ * the format outlived its source and the translators rotted.
21
+ *
22
+ * Two deliberate constraints:
23
+ *
24
+ * - **The palette is green and muted grey only** — never red or amber, at any
25
+ * input. See {@link syncStatusDotVariant}.
26
+ * - **No visible label.** Colour therefore separates only two of the four
27
+ * states, which makes the tooltip the status rather than a nicety — and it is
28
+ * mirrored into `aria-label`, because a screen reader cannot see colour.
29
+ *
30
+ * Colour tracks `onchain` alone; see {@link getSyncStatus} for why.
31
+ *
32
+ * @example
33
+ * ```tsx
34
+ * <SyncStatusPill
35
+ * onchain={{ meta: onchainResponse?.meta, updatedAt: onchainUpdatedAt }}
36
+ * offchain={{ meta: offchainResponse?.meta, updatedAt: offchainUpdatedAt }}
37
+ * attach="ready"
38
+ * networkById={{ 1: "Ethereum" }}
39
+ * />
40
+ * ```
41
+ */
42
+ export declare function SyncStatusPill({ onchain, offchain, attach, isSyncing, networkById, }: SyncStatusPillProps): React.ReactElement;
@@ -9,7 +9,6 @@ export * from './components/assets-list-cell';
9
9
  export * from './components/badge';
10
10
  export * from './components/base-link';
11
11
  export * from './components/base-table';
12
- export * from './components/block-sync-pill';
13
12
  export * from './components/breadcrumbs';
14
13
  export * from './components/breakpoint-provider';
15
14
  export * from './components/buttons';
@@ -72,6 +71,7 @@ export * from './components/spinner-loader';
72
71
  export * from './components/split-list';
73
72
  export * from './components/status-dot';
74
73
  export * from './components/status-elements';
74
+ export * from './components/sync-status-pill';
75
75
  export * from './components/tab-control';
76
76
  export * from './components/table';
77
77
  export * from './components/tabs';
@@ -96,7 +96,7 @@ export * from './configs/variants';
96
96
  export * from './hooks';
97
97
  export type { LocaleKeys } from './locale';
98
98
  export * from './locale';
99
- export type { BlockSyncProps } from './types/footer';
99
+ export type { NetworkById } from './types/footer';
100
100
  export { EnglishLocale };
101
101
  export * from './utils';
102
102
  export * from './utils/a11y';
@@ -20,9 +20,14 @@ declare const _default: {
20
20
  "components.footer.built": "Built at ETHGlobal MarketMake hackathon.",
21
21
  "components.footer.version": "Version {value}",
22
22
 
23
- "components.blockSyncPill.syncError": ", sync error",
24
- "components.blockSyncPill.syncedAt": ", synced at {time}",
25
- "components.blockSyncPill.syncing": ", syncing…",
23
+ "components.syncStatus.live": "Live",
24
+ "components.syncStatus.starting": "Syncing…",
25
+ "components.syncStatus.cached": "Cached",
26
+ "components.syncStatus.none": "No data",
27
+ "components.syncStatus.unavailable": "Unavailable",
28
+ "components.syncStatus.ago": "{age} ago",
29
+ "components.syncStatus.sourceOutdated": "{source} data outdated",
30
+ "components.syncStatus.sourceMissing": "no {source} data",
26
31
 
27
32
  "components.creditAccountHistory.table.protocol": "Protocol",
28
33
  "components.creditAccountHistory.table.operation": "Operation",
@@ -1,51 +1,7 @@
1
1
  /**
2
2
  * Types for Footer component
3
3
  */
4
- export interface LastSyncBlock {
5
- block: bigint;
6
- /** Local wall-clock time (ms) of the last successful sync for this chain. */
7
- localTimestamp: number;
8
- }
9
- /**
10
- * Per-chain sync info: the last synced block plus the live refresh state, so the
11
- * sync indicator can reflect whether the refresh loop is alive / failing.
12
- */
13
- export interface ChainSyncInfo {
14
- lastSyncBlock: LastSyncBlock;
15
- /** True while a refresh for this chain is currently in flight. */
16
- loading?: boolean;
17
- /** Human-readable error if the last refresh for this chain failed. */
18
- error?: string;
19
- }
20
- export interface BlockByChain {
21
- [chainId: number]: ChainSyncInfo | undefined;
22
- }
23
- export interface ExplorerInfo {
24
- url: string;
25
- name?: string;
26
- }
27
- export interface ExplorerAddresses {
28
- [chainId: number]: string | ExplorerInfo;
29
- }
4
+ /** Chain id → display name, for footer status rows. */
30
5
  export interface NetworkById {
31
6
  [chainId: number]: string;
32
7
  }
33
- export interface BlockSyncProps {
34
- blockByChain: BlockByChain | undefined;
35
- explorerAddresses: ExplorerAddresses;
36
- networkById: NetworkById;
37
- /**
38
- * Expected refresh cadence in ms. The indicator is considered stale only after
39
- * `syncIntervalMs * staleMultiplier`, so a healthy loop never flips to red.
40
- * Defaults to 5 minutes.
41
- */
42
- syncIntervalMs?: number;
43
- /**
44
- * Stale-threshold multiplier applied to {@link BlockSyncProps.syncIntervalMs}.
45
- * Defaults to 2.5 (allows for missed/throttled cycles before flagging stale).
46
- */
47
- staleMultiplier?: number;
48
- }
49
- export interface FooterProps {
50
- blockSyncProps?: BlockSyncProps;
51
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gearbox-protocol/ui-kit",
3
- "version": "4.0.0-next.13",
3
+ "version": "4.0.0-next.14",
4
4
  "description": "Internal UI components",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1 +0,0 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),o=require("react"),E=require("../tooltip/simple-tooltip.cjs");require("../tooltip/tooltip.cjs");const M=require("../typed-intl/index.cjs"),g=require("../../utils/cn.cjs");require("../accordion/accordion.cjs");require("../alert-badge/alert-badge.cjs");require("../alert-dialog/alert-dialog.cjs");require("../alert-modal/alert-modal.cjs");require("@gearbox-protocol/static");require("../buttons/back-button/back-button.cjs");require("../buttons/button/button.cjs");require("../buttons/copy-button/copy-button.cjs");require("../buttons/external-button/external-button.cjs");require("../buttons/filter-button/filter-button.cjs");require("../buttons/navigation-button/navigation-button.cjs");require("@gearbox-protocol/sdk");require("sonner");require("@gearbox-protocol/sdk/common-utils");require("luxon");require("../../utils/z-index.cjs");require("../buttons/tab-button/tab-button.cjs");require("@fortawesome/free-solid-svg-icons");require("../icons/fa-icon.cjs");require("../token-icon/token-icon.cjs");require("../badge/badge.cjs");require("../badge/chip.cjs");require("../base/amount-input/amount-input.cjs");require("../confirm-menu/confirm-menu.cjs");require("react-intl");require("../table/grid-table.cjs");require("../skeleton/skeleton.cjs");require("../table/table.cjs");require("../token-symbol/token-symbol.cjs");require("../simple-accordion/simple-accordion.cjs");require("../tip-card/tip-card.cjs");require("../typography/typography.cjs");require("../points-icon/points-icon.cjs");require("../vertical-indicator/vertical-indicator.cjs");require("../base/page-title/page-title.cjs");require("../detailed-page-title/detailed-page-title-copy-button.cjs");require("../detailed-page-title/detailed-page-title-explorer-dropdown.cjs");require("../base/details-view/detail-aside/detail-aside-scope.cjs");require("../layout/main-aside-layout/main-aside-layout.cjs");require("../base/details-view/details-view.cjs");require("../fadeout-loading/fadeout-loading.cjs");require("../health-factor/health-factor.cjs");require("@gearbox-protocol/sdk/model");require("../checkbox/checkbox.cjs");require("../checkbox/checkbox-labeled.cjs");require("../select/select.cjs");require("../breakpoint-provider.cjs");require("lightweight-charts");require("../graph/formatters.cjs");require("../graph/graph-view.cjs");require("../base/info-portal/info-portal.cjs");require("../edit-input/edit-input.cjs");require("../slider/slider.cjs");require("../slider/slider-context.cjs");require("../text-button/text-button.cjs");require("../with-title/with-title.cjs");require("react-router-dom");require("../status-elements/status-elements.cjs");require("../client-adapters/styled-dialog-container/styled-dialog-container.cjs");require("../modal/modal.cjs");require("@radix-ui/react-dialog");require("../dialog/dialog-container.cjs");require("../dialog/dialog-content.cjs");require("../dialog/dialog-description.cjs");require("../dialog/dialog-footer.cjs");require("../dialog/dialog-header.cjs");require("../dialog/dialog-title.cjs");require("../dialog/dialog-modal-container.cjs");require("../dialog/dialog-overlay.cjs");require("viem");require("../input/input.cjs");require("../label/label.cjs");require("../simple-dropdown/simple-dropdown.cjs");require("../simple-dropdown/simple-dropdown-menu-item.cjs");require("../base/tabber/tabber.cjs");require("../link/link.cjs");require("../rounded-image/rounded-image.cjs");require("../network-icon/network-indicator.cjs");require("../base-link/base-link.cjs");require("../help-tip/help-tip.cjs");require("../base-table/base-table-line.cjs");require("../navigation-context/navigation-context.cjs");require("../cards/card/card.cjs");require("../checkbox-item/checkbox-item.cjs");require("../client-adapters/styled-button/styled-button.cjs");require("../client-adapters/styled-rounded-image/styled-rounded-image.cjs");require("../composites/tx-preview/confirm/amounts/TransactionConfirmAmountField.cjs");require("../composites/tx-preview/confirm/dialog/constants.cjs");require("../spinner/spinner.cjs");require("../spinner-loader/spinner-loader.cjs");require("../detailed-apy/detailed-apy.cjs");require("../dropdown-menu/dropdown-menu.cjs");require("../dropdown-menu/dropdown-menu-checkbox-item.cjs");require("../dropdown-menu/dropdown-menu-content.cjs");require("../dropdown-menu/dropdown-menu-item.cjs");require("../dropdown-menu/dropdown-menu-label.cjs");require("../dropdown-menu/dropdown-menu-radio-item.cjs");require("../dropdown-menu/dropdown-menu-separator.cjs");require("../dropdown-menu/dropdown-menu-shortcut.cjs");require("../dropdown-menu/dropdown-menu-sub-content.cjs");require("../dropdown-menu/dropdown-menu-sub-trigger.cjs");require("../filter/filter-block.cjs");require("../filter/filter-checkbox-item.cjs");require("../filter/filter-chip.cjs");require("../filter/filter-chips.cjs");require("../filter/filter-dropdown-item.cjs");require("../filter/filter-group.cjs");require("../filter/filter-label.cjs");require("../search-line/search-line.cjs");require("../tabs/tabs.cjs");require("../filter/filter-modal-item.cjs");require("../filter/filter-radio-item.cjs");require("../filter/filter-separator.cjs");require("../filter/variants.cjs");require("../icon-button/icon-button.cjs");require("../layout/app-footer/app-footer.cjs");require("../layout/app-header/app-header.cjs");require("../layout/col/col.cjs");require("../layout/container/container.cjs");require("../layout/grid/grid.cjs");require("../layout/layout/layout.cjs");require("../legal-agreement/legal-agreement-theme.cjs");require("../loader/loader.cjs");require("../loading/loading.cjs");require("../loading-bar/loading-bar.cjs");require("../segmented-control/segmented-control.cjs");require("../separate-line/separate-line.cjs");require("../split-list/split-list.cjs");const R=require("../status-dot/status-dot.cjs");require("../textarea/textarea.cjs");require("../theme-provider.cjs");const U=require("./block-sync-status.cjs"),$=300*1e3,F=2.5;function O({blockByChain:n,explorerAddresses:y,networkById:f,syncIntervalMs:b=$,staleMultiplier:j=F,statusLabel:T="Status",className:_}){const{formatMessage:s}=M.useIntlTyped(),[c,k]=o.useState(Date.now());o.useEffect(()=>{const t=()=>{k(Date.now())},r=window.setInterval(t,1e3);return()=>{clearInterval(r)}},[]);const l=b*j,{state:u,isSyncing:h}=o.useMemo(()=>U.getBlockSyncStatus({blockByChain:n,now:c,staleAfterMs:l}),[n,c,l]),v=u==="error"?"alert":u==="loading"?"default":"success",L=u==="loading"||u==="synced"&&h,a=()=>e.jsxs("span",{className:g.cn("inline-flex items-center gap-1.5 rounded-md border border-border px-2 py-1 text-xs",u==="error"?"text-destructive":"text-muted-foreground",_),children:[e.jsx(R.StatusDot,{variant:v,size:"xs",pulse:L}),T]}),d=(()=>{if(!n)return null;const t=Object.entries(n).filter(r=>!!r[1]);return t.length===0?null:e.jsx("div",{className:"flex flex-col text-left text-xs",children:t.map(([r,N],P)=>{const{lastSyncBlock:p,error:w}=N,q=p?.block||0n,m=p?.localTimestamp,i=y[Number(r)],x=typeof i=="string"?i:i&&typeof i=="object"&&"url"in i?i.url:void 0,D=f[Number(r)]||"Unknown",S=`[${q.toString()}]`,I=P===t.length-1,A=q>0n&&!!m;return e.jsxs("div",{className:g.cn(!I&&"mb-2"),children:[e.jsxs("span",{children:[D," "]}),x&&q>0?e.jsx("a",{href:`${x}/block/${q}`,target:"_blank",rel:"noopener noreferrer",className:"underline hover:text-foreground",children:S}):e.jsx("span",{children:S}),w?e.jsx("span",{className:"text-destructive",children:s({messageId:"components.blockSyncPill.syncError"})}):A?e.jsx("span",{children:s({messageId:"components.blockSyncPill.syncedAt"},{time:new Date(m).toLocaleTimeString()})}):e.jsx("span",{children:s({messageId:"components.blockSyncPill.syncing"})})]},r)})})})();return d?e.jsx(E.SimpleTooltip,{title:a(),children:d}):e.jsx("div",{className:"select-none",children:a()})}exports.BlockSyncPill=O;
@@ -1 +0,0 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function m({blockByChain:l,now:o,staleAfterMs:a}){const s=l?Object.values(l).filter(t=>!!t):[],i=s.some(t=>!!t.error),r=s.some(t=>!!t.loading),n=s.filter(t=>t.lastSyncBlock.block>0n&&t.lastSyncBlock.localTimestamp).map(t=>t.lastSyncBlock.localTimestamp),c=n.length?Math.min(...n):0,S=c>0&&o-c>a;let e;return i||S?e="error":c===0?e="loading":e="synced",{state:e,isSyncing:r}}exports.getBlockSyncStatus=m;
@@ -1 +0,0 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const l=require("./block-sync-pill.cjs");exports.BlockSyncPill=l.BlockSyncPill;
@@ -1,211 +0,0 @@
1
- import { jsx as t, jsxs as s } from "react/jsx-runtime";
2
- import { useState as U, useEffect as j, useMemo as R } from "react";
3
- import { SimpleTooltip as $ } from "../tooltip/simple-tooltip.js";
4
- import "../tooltip/tooltip.js";
5
- import { useIntlTyped as F } from "../typed-intl/index.js";
6
- import { cn as S } from "../../utils/cn.js";
7
- import "../accordion/accordion.js";
8
- import "../alert-badge/alert-badge.js";
9
- import "../alert-dialog/alert-dialog.js";
10
- import "../alert-modal/alert-modal.js";
11
- import "@gearbox-protocol/static";
12
- import "../buttons/back-button/back-button.js";
13
- import "../buttons/button/button.js";
14
- import "../buttons/copy-button/copy-button.js";
15
- import "../buttons/external-button/external-button.js";
16
- import "../buttons/filter-button/filter-button.js";
17
- import "../buttons/navigation-button/navigation-button.js";
18
- import "@gearbox-protocol/sdk";
19
- import "sonner";
20
- import "@gearbox-protocol/sdk/common-utils";
21
- import "luxon";
22
- import "../../utils/z-index.js";
23
- import "../buttons/tab-button/tab-button.js";
24
- import "@fortawesome/free-solid-svg-icons";
25
- import "../icons/fa-icon.js";
26
- import "../token-icon/token-icon.js";
27
- import "../badge/badge.js";
28
- import "../badge/chip.js";
29
- import "../base/amount-input/amount-input.js";
30
- import "../confirm-menu/confirm-menu.js";
31
- import "react-intl";
32
- import "../table/grid-table.js";
33
- import "../skeleton/skeleton.js";
34
- import "../table/table.js";
35
- import "../token-symbol/token-symbol.js";
36
- import "../simple-accordion/simple-accordion.js";
37
- import "../tip-card/tip-card.js";
38
- import "../typography/typography.js";
39
- import "../points-icon/points-icon.js";
40
- import "../vertical-indicator/vertical-indicator.js";
41
- import "../base/page-title/page-title.js";
42
- import "../detailed-page-title/detailed-page-title-copy-button.js";
43
- import "../detailed-page-title/detailed-page-title-explorer-dropdown.js";
44
- import "../base/details-view/detail-aside/detail-aside-scope.js";
45
- import "../layout/main-aside-layout/main-aside-layout.js";
46
- import "../base/details-view/details-view.js";
47
- import "../fadeout-loading/fadeout-loading.js";
48
- import "../health-factor/health-factor.js";
49
- import "@gearbox-protocol/sdk/model";
50
- import "../checkbox/checkbox.js";
51
- import "../checkbox/checkbox-labeled.js";
52
- import "../select/select.js";
53
- import "../breakpoint-provider.js";
54
- import "lightweight-charts";
55
- import "../graph/formatters.js";
56
- import "../graph/graph-view.js";
57
- import "../base/info-portal/info-portal.js";
58
- import "../edit-input/edit-input.js";
59
- import "../slider/slider.js";
60
- import "../slider/slider-context.js";
61
- import "../text-button/text-button.js";
62
- import "../with-title/with-title.js";
63
- import "react-router-dom";
64
- import "../status-elements/status-elements.js";
65
- import "../client-adapters/styled-dialog-container/styled-dialog-container.js";
66
- import "../modal/modal.js";
67
- import "@radix-ui/react-dialog";
68
- import "../dialog/dialog-container.js";
69
- import "../dialog/dialog-content.js";
70
- import "../dialog/dialog-description.js";
71
- import "../dialog/dialog-footer.js";
72
- import "../dialog/dialog-header.js";
73
- import "../dialog/dialog-title.js";
74
- import "../dialog/dialog-modal-container.js";
75
- import "../dialog/dialog-overlay.js";
76
- import "viem";
77
- import "../input/input.js";
78
- import "../label/label.js";
79
- import "../simple-dropdown/simple-dropdown.js";
80
- import "../simple-dropdown/simple-dropdown-menu-item.js";
81
- import "../base/tabber/tabber.js";
82
- import "../link/link.js";
83
- import "../rounded-image/rounded-image.js";
84
- import "../network-icon/network-indicator.js";
85
- import "../base-link/base-link.js";
86
- import "../help-tip/help-tip.js";
87
- import "../base-table/base-table-line.js";
88
- import "../navigation-context/navigation-context.js";
89
- import "../cards/card/card.js";
90
- import "../checkbox-item/checkbox-item.js";
91
- import "../client-adapters/styled-button/styled-button.js";
92
- import "../client-adapters/styled-rounded-image/styled-rounded-image.js";
93
- import "../composites/tx-preview/confirm/amounts/TransactionConfirmAmountField.js";
94
- import "../composites/tx-preview/confirm/dialog/constants.js";
95
- import "../spinner/spinner.js";
96
- import "../spinner-loader/spinner-loader.js";
97
- import "../detailed-apy/detailed-apy.js";
98
- import "../dropdown-menu/dropdown-menu.js";
99
- import "../dropdown-menu/dropdown-menu-checkbox-item.js";
100
- import "../dropdown-menu/dropdown-menu-content.js";
101
- import "../dropdown-menu/dropdown-menu-item.js";
102
- import "../dropdown-menu/dropdown-menu-label.js";
103
- import "../dropdown-menu/dropdown-menu-radio-item.js";
104
- import "../dropdown-menu/dropdown-menu-separator.js";
105
- import "../dropdown-menu/dropdown-menu-shortcut.js";
106
- import "../dropdown-menu/dropdown-menu-sub-content.js";
107
- import "../dropdown-menu/dropdown-menu-sub-trigger.js";
108
- import "../filter/filter-block.js";
109
- import "../filter/filter-checkbox-item.js";
110
- import "../filter/filter-chip.js";
111
- import "../filter/filter-chips.js";
112
- import "../filter/filter-dropdown-item.js";
113
- import "../filter/filter-group.js";
114
- import "../filter/filter-label.js";
115
- import "../search-line/search-line.js";
116
- import "../tabs/tabs.js";
117
- import "../filter/filter-modal-item.js";
118
- import "../filter/filter-radio-item.js";
119
- import "../filter/filter-separator.js";
120
- import "../filter/variants.js";
121
- import "../icon-button/icon-button.js";
122
- import "../layout/app-footer/app-footer.js";
123
- import "../layout/app-header/app-header.js";
124
- import "../layout/col/col.js";
125
- import "../layout/container/container.js";
126
- import "../layout/grid/grid.js";
127
- import "../layout/layout/layout.js";
128
- import "../legal-agreement/legal-agreement-theme.js";
129
- import "../loader/loader.js";
130
- import "../loading/loading.js";
131
- import "../loading-bar/loading-bar.js";
132
- import "../segmented-control/segmented-control.js";
133
- import "../separate-line/separate-line.js";
134
- import "../split-list/split-list.js";
135
- import { StatusDot as V } from "../status-dot/status-dot.js";
136
- import "../textarea/textarea.js";
137
- import "../theme-provider.js";
138
- import { getBlockSyncStatus as z } from "./block-sync-status.js";
139
- const C = 300 * 1e3, O = 2.5;
140
- function ho({
141
- blockByChain: p,
142
- explorerAddresses: y,
143
- networkById: T,
144
- syncIntervalMs: b = C,
145
- staleMultiplier: h = O,
146
- statusLabel: k = "Status",
147
- className: v
148
- }) {
149
- const { formatMessage: n } = F(), [c, L] = U(Date.now());
150
- j(() => {
151
- const m = () => {
152
- L(Date.now());
153
- }, r = window.setInterval(m, 1e3);
154
- return () => {
155
- clearInterval(r);
156
- };
157
- }, []);
158
- const l = b * h, { state: i, isSyncing: N } = R(
159
- () => z({ blockByChain: p, now: c, staleAfterMs: l }),
160
- [p, c, l]
161
- ), w = i === "error" ? "alert" : i === "loading" ? "default" : "success", A = i === "loading" || i === "synced" && N, a = () => /* @__PURE__ */ s(
162
- "span",
163
- {
164
- className: S(
165
- "inline-flex items-center gap-1.5 rounded-md border border-border px-2 py-1 text-xs",
166
- i === "error" ? "text-destructive" : "text-muted-foreground",
167
- v
168
- ),
169
- children: [
170
- /* @__PURE__ */ t(V, { variant: w, size: "xs", pulse: A }),
171
- k
172
- ]
173
- }
174
- ), d = (() => {
175
- if (!p) return null;
176
- const m = Object.entries(p).filter(
177
- (r) => !!r[1]
178
- );
179
- return m.length === 0 ? null : /* @__PURE__ */ t("div", { className: "flex flex-col text-left text-xs", children: m.map(([r, E], I) => {
180
- const { lastSyncBlock: u, error: D } = E, e = u?.block || 0n, f = u?.localTimestamp, o = y[Number(r)], g = typeof o == "string" ? o : o && typeof o == "object" && "url" in o ? o.url : void 0, P = T[Number(r)] || "Unknown", x = `[${e.toString()}]`, _ = I === m.length - 1, M = e > 0n && !!f;
181
- return /* @__PURE__ */ s("div", { className: S(!_ && "mb-2"), children: [
182
- /* @__PURE__ */ s("span", { children: [
183
- P,
184
- " "
185
- ] }),
186
- g && e > 0 ? /* @__PURE__ */ t(
187
- "a",
188
- {
189
- href: `${g}/block/${e}`,
190
- target: "_blank",
191
- rel: "noopener noreferrer",
192
- className: "underline hover:text-foreground",
193
- children: x
194
- }
195
- ) : /* @__PURE__ */ t("span", { children: x }),
196
- D ? /* @__PURE__ */ t("span", { className: "text-destructive", children: n({
197
- messageId: "components.blockSyncPill.syncError"
198
- }) }) : M ? /* @__PURE__ */ t("span", { children: n(
199
- { messageId: "components.blockSyncPill.syncedAt" },
200
- { time: new Date(f).toLocaleTimeString() }
201
- ) }) : /* @__PURE__ */ t("span", { children: n({
202
- messageId: "components.blockSyncPill.syncing"
203
- }) })
204
- ] }, r);
205
- }) });
206
- })();
207
- return d ? /* @__PURE__ */ t($, { title: a(), children: d }) : /* @__PURE__ */ t("div", { className: "select-none", children: a() });
208
- }
209
- export {
210
- ho as BlockSyncPill
211
- };
@@ -1,12 +0,0 @@
1
- function S({
2
- blockByChain: c,
3
- now: o,
4
- staleAfterMs: a
5
- }) {
6
- const e = c ? Object.values(c).filter((t) => !!t) : [], i = e.some((t) => !!t.error), r = e.some((t) => !!t.loading), l = e.filter((t) => t.lastSyncBlock.block > 0n && t.lastSyncBlock.localTimestamp).map((t) => t.lastSyncBlock.localTimestamp), n = l.length ? Math.min(...l) : 0, m = n > 0 && o - n > a;
7
- let s;
8
- return i || m ? s = "error" : n === 0 ? s = "loading" : s = "synced", { state: s, isSyncing: r };
9
- }
10
- export {
11
- S as getBlockSyncStatus
12
- };
@@ -1,4 +0,0 @@
1
- import { BlockSyncPill as c } from "./block-sync-pill.js";
2
- export {
3
- c as BlockSyncPill
4
- };
@@ -1,35 +0,0 @@
1
- import { BlockSyncProps } from '../../types/footer';
2
- import type * as React from "react";
3
- export interface BlockSyncPillProps extends BlockSyncProps {
4
- /**
5
- * Label rendered inside the status pill. Defaults to `Status`.
6
- */
7
- statusLabel?: React.ReactNode;
8
- /**
9
- * Additional class names applied to the pill wrapper.
10
- */
11
- className?: string;
12
- }
13
- /**
14
- * BlockSyncPill — backend-sync status rendered as an {@link AppFooter}-style
15
- * pill: a thin bordered, rounded badge with a status dot and a label.
16
- *
17
- * Reflects the **liveness** of the data-refresh loop rather than raw block age:
18
- * - **loading** (no successful sync yet): the dot is a pulsing neutral gray;
19
- * - **synced** (within `syncIntervalMs * staleMultiplier`, no errors): green,
20
- * pulsing while a refresh is in flight;
21
- * - **error / stalled**: red.
22
- *
23
- * Hovering reveals per-chain block numbers and the local time of the last sync.
24
- *
25
- * @example
26
- * ```tsx
27
- * <BlockSyncPill
28
- * blockByChain={blockByChain}
29
- * explorerAddresses={explorerAddresses}
30
- * networkById={networkById}
31
- * syncIntervalMs={300000}
32
- * />
33
- * ```
34
- */
35
- export declare function BlockSyncPill({ blockByChain, explorerAddresses, networkById, syncIntervalMs, staleMultiplier, statusLabel, className, }: BlockSyncPillProps): React.ReactElement;
@@ -1,29 +0,0 @@
1
- import { BlockByChain } from '../../types/footer';
2
- /**
3
- * Liveness state of the data-refresh loop, derived from per-chain sync info:
4
- * - `loading` — nothing has successfully synced yet (initial state);
5
- * - `synced` — at least one chain synced and none are stale/failing;
6
- * - `error` — a chain reported an error, or the oldest sync is stale.
7
- */
8
- export type BlockSyncState = "loading" | "synced" | "error";
9
- export interface BlockSyncStatus {
10
- state: BlockSyncState;
11
- /** True while any chain has a refresh in flight. */
12
- isSyncing: boolean;
13
- }
14
- export interface GetBlockSyncStatusProps {
15
- blockByChain: BlockByChain | undefined;
16
- /** Reference "now" timestamp (ms) used to evaluate staleness. */
17
- now: number;
18
- /** Age (ms) after which the oldest sync is considered stale. */
19
- staleAfterMs: number;
20
- }
21
- /**
22
- * Pure derivation of the {@link BlockSyncState} from chain sync info.
23
- *
24
- * A chain counts as "synced" only when it has both a real block (`> 0`) and a
25
- * local sync timestamp. Staleness is measured against the **oldest** synced
26
- * chain, so a single lagging chain is enough to flag the whole indicator.
27
- * Errors take priority over everything else.
28
- */
29
- export declare function getBlockSyncStatus({ blockByChain, now, staleAfterMs, }: GetBlockSyncStatusProps): BlockSyncStatus;
@@ -1 +0,0 @@
1
- export * from './block-sync-pill';