@stellar-expert/ui-framework 1.16.7 → 1.17.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.
Files changed (92) hide show
  1. package/CLAUDE.md +35 -0
  2. package/README.md +1125 -3
  3. package/account/account-address.js +127 -127
  4. package/account/available-balance.js +22 -22
  5. package/account/identicon.js +90 -90
  6. package/account/signer-key.js +65 -64
  7. package/api/explorer-api-hooks.js +209 -202
  8. package/api/explorer-api-paginated-list-hooks.js +441 -440
  9. package/api/explorer-batch-info-loader.js +111 -85
  10. package/api/explorer-tx-api.js +28 -28
  11. package/api/ledger-stream.js +15 -0
  12. package/asset/amount.js +56 -56
  13. package/asset/asset-icon.js +41 -41
  14. package/asset/asset-issuer.js +21 -21
  15. package/asset/asset-link.js +107 -107
  16. package/asset/asset-list-hooks.js +59 -59
  17. package/asset/asset-meta-hooks.js +88 -88
  18. package/asset/asset-selector.js +72 -71
  19. package/basic-styles/base.scss +50 -50
  20. package/basic-styles/grid.scss +1 -1
  21. package/basic-styles/table.scss +1 -1
  22. package/claimable-balance/claimable-balance-claimants.js +5 -0
  23. package/contract/contract-api.js +15 -0
  24. package/contract/invocation-info-view.js +10 -2
  25. package/contract/sc-val.js +24 -0
  26. package/controls/button-group.js +25 -19
  27. package/controls/button-group.scss +46 -46
  28. package/controls/button.js +93 -78
  29. package/controls/button.scss +173 -173
  30. package/controls/code-block.js +42 -34
  31. package/controls/code-block.scss +71 -71
  32. package/controls/dropdown.js +318 -287
  33. package/controls/dropdown.scss +159 -159
  34. package/controls/external-link.js +10 -4
  35. package/controls/info-tooltip.js +23 -16
  36. package/controls/slider.js +29 -19
  37. package/controls/tabs.js +94 -94
  38. package/controls/tabs.scss +70 -70
  39. package/controls/tooltip.js +244 -240
  40. package/controls/tooltip.scss +116 -116
  41. package/controls/update-highlighter.js +32 -27
  42. package/controls/update-highlighter.scss +7 -7
  43. package/date/date-selector.js +56 -54
  44. package/date/elapsed-time.js +28 -21
  45. package/dex/price-dynamic.js +53 -44
  46. package/dex/price-dynamic.scss +33 -33
  47. package/directory/directory-hooks.js +109 -97
  48. package/effect/effect-description.js +5 -3
  49. package/errors/error-boundary.js +110 -97
  50. package/horizon/horizon-account-helpers.js +104 -104
  51. package/horizon/horizon-ledger-helpers.js +35 -35
  52. package/horizon/horizon-trades-helper.js +88 -88
  53. package/horizon/horizon-transaction-helpers.js +36 -36
  54. package/index.d.ts +1241 -0
  55. package/interaction/accordion.js +43 -35
  56. package/interaction/autofocus.js +13 -9
  57. package/interaction/block-select.js +64 -53
  58. package/interaction/block-select.scss +21 -21
  59. package/interaction/copy-to-clipboard.js +25 -18
  60. package/interaction/inline-progress.js +20 -15
  61. package/interaction/qr-code.js +34 -34
  62. package/interaction/responsive.js +20 -20
  63. package/interaction/spoiler.js +52 -39
  64. package/interaction/spoiler.scss +6 -6
  65. package/interaction/theme-selector.js +13 -10
  66. package/ledger/ledger-entry-href-formatter.js +4 -3
  67. package/ledger/ledger-entry-link.js +93 -58
  68. package/ledger/ledger-info-parser.js +28 -0
  69. package/meta/page-meta-tags.js +243 -238
  70. package/module/dynamic-module.js +47 -47
  71. package/package.json +3 -2
  72. package/state/on-screen-hooks.js +22 -22
  73. package/state/page-visibility-helpers.js +29 -16
  74. package/state/page-visibility-hooks.js +13 -11
  75. package/state/screen-orientation-hooks.js +19 -15
  76. package/state/state-hooks.js +76 -76
  77. package/state/stellar-network-hooks.js +56 -44
  78. package/state/theme.js +29 -28
  79. package/stellar/key-type.js +92 -91
  80. package/stellar/ledger-generic-id.js +39 -39
  81. package/stellar/signature-hint-utils.js +65 -65
  82. package/toast/toast-notifications-block.js +59 -59
  83. package/toast/toast-notifications.scss +1 -1
  84. package/tx/op-description-view.js +84 -81
  85. package/tx/op-icon.js +98 -98
  86. package/tx/parser/op-balance-changes.js +66 -66
  87. package/tx/parser/op-descriptor.js +51 -48
  88. package/tx/parser/tx-details-parser.js +81 -81
  89. package/tx/parser/tx-matcher.js +371 -371
  90. package/tx/parser/type-filter-matcher.js +126 -126
  91. package/tx/tx-list-hooks.js +32 -18
  92. package/tx/tx-operations-list.js +117 -116
package/README.md CHANGED
@@ -1,3 +1,1125 @@
1
- # @stellar-expert/ui-framework
2
-
3
- ### StellarExpert shared UI components library
1
+ # @stellar-expert/ui-framework
2
+
3
+ > Shared UI components library for StellarExpert apps.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add @stellar-expert/ui-framework
9
+ ```
10
+
11
+ Peer dependencies must be installed separately — see `peerDependencies` in `package.json`.
12
+
13
+ ## Quick Start
14
+
15
+ ```js
16
+ import {
17
+ setStellarNetwork,
18
+ createToastNotificationsContainer,
19
+ initMeta
20
+ } from '@stellar-expert/ui-framework'
21
+
22
+ // Set the active Stellar network
23
+ setStellarNetwork('public')
24
+
25
+ // Initialize page meta tags
26
+ initMeta({
27
+ serviceTitle: '| StellarExpert',
28
+ origin: 'https://stellar.expert',
29
+ description: 'Stellar blockchain explorer',
30
+ image: 'https://stellar.expert/img/og-image.png'
31
+ })
32
+
33
+ // Initialize toast notifications (call once at app startup)
34
+ createToastNotificationsContainer()
35
+ ```
36
+
37
+ ## Configuration
38
+
39
+ Global variables:
40
+
41
+ | Global | Default | Description |
42
+ |--------|---------|-------------|
43
+ | `window.explorerFrontendOrigin` | `https://stellar.expert` | StellarExpert frontend URL |
44
+ | `window.explorerApiOrigin` | `https://api.stellar.expert` | StellarExpert API URL |
45
+ | `window.horizonOrigin` | `https://horizon.stellar.org` | Stellar Horizon API URL |
46
+
47
+ Override them before importing components if needed.
48
+
49
+ ---
50
+
51
+ ## API Reference
52
+
53
+ ### Modules
54
+
55
+ #### `DynamicModule`
56
+
57
+ Dynamically loadable module with error boundary wrapping and caching.
58
+
59
+ | Prop | Type | Required | Description |
60
+ |------|------|----------|-------------|
61
+ | `load` | `() => Promise` | Yes | Dynamic import function |
62
+ | `module` | `string` | No | Unique module name for caching |
63
+
64
+ ```jsx
65
+ import {DynamicModule} from '@stellar-expert/ui-framework'
66
+
67
+ <DynamicModule module="docs" load={() => import('./docs/docs-view')}/>
68
+ ```
69
+
70
+ ---
71
+
72
+ ### State Management
73
+
74
+ #### `useDependantState(stateInitializer, dependencies, finalizer?)`
75
+
76
+ React hook that auto-reinitializes state when dependencies change using deep comparison.
77
+
78
+ | Param | Type | Description |
79
+ |-------|------|-------------|
80
+ | `stateInitializer` | `function\|any` | Initial state or initializer function receiving `(dependencies, prevState)` |
81
+ | `dependencies` | `any[]` | Dependencies array (deep compared) |
82
+ | `finalizer` | `function` | Optional cleanup function |
83
+
84
+ Returns `[state, setState]` — setState also uses deep comparison.
85
+
86
+ ```jsx
87
+ import {useDependantState} from '@stellar-expert/ui-framework'
88
+
89
+ const [transfer] = useDependantState(
90
+ () => new TransferSettings(network),
91
+ [network, accountLedgerData.address]
92
+ )
93
+
94
+ const [result, setResult] = useDependantState(() => {
95
+ loadIssuedAssets(address)
96
+ .then(data => setResult({loaded: true, data}))
97
+ return {loaded: false, data: []}
98
+ }, [address])
99
+ ```
100
+
101
+ #### `useForceUpdate()`
102
+
103
+ Returns a function that forces a component re-render.
104
+
105
+ ```jsx
106
+ const forceUpdate = useForceUpdate()
107
+ ```
108
+
109
+ #### `useDeepEffect(effect, dependencies)`
110
+
111
+ Like `useEffect` but uses deep comparison for dependencies.
112
+
113
+ ```jsx
114
+ import {useDeepEffect} from '@stellar-expert/ui-framework'
115
+
116
+ useDeepEffect(() => {
117
+ // runs when options deeply change
118
+ }, [options, modules, type])
119
+ ```
120
+
121
+ #### `useOnScreen(root, rootMargin?)`
122
+
123
+ Hook for determining element visibility using IntersectionObserver.
124
+
125
+ | Param | Type | Description |
126
+ |-------|------|-------------|
127
+ | `root` | `RefObject<Element>` | Scroll parent ref |
128
+ | `rootMargin` | `string` | Visibility margin (e.g., `"100px"`) |
129
+
130
+ Returns `boolean`.
131
+
132
+ #### `getCurrentStellarNetwork()`
133
+
134
+ Returns the current Stellar network (`'public'` or `'testnet'`).
135
+
136
+ #### `setStellarNetwork(network)`
137
+
138
+ Sets the active Stellar network and notifies all subscribers.
139
+
140
+ ```jsx
141
+ import {setStellarNetwork} from '@stellar-expert/ui-framework'
142
+
143
+ setStellarNetwork('testnet')
144
+ ```
145
+
146
+ #### `subscribeToStellarNetworkChange(onChange)`
147
+
148
+ Subscribes to network change events.
149
+
150
+ ```jsx
151
+ import {subscribeToStellarNetworkChange} from '@stellar-expert/ui-framework'
152
+
153
+ subscribeToStellarNetworkChange(network => {
154
+ window.horizonOrigin = appSettings.horizonUrl
155
+ })
156
+ ```
157
+
158
+ #### `useStellarNetwork()`
159
+
160
+ React hook that returns the current network and re-renders on change.
161
+
162
+ ```jsx
163
+ const network = useStellarNetwork()
164
+ ```
165
+
166
+ #### `useScreenOrientation()`
167
+
168
+ Returns the current screen orientation type (e.g., `"portrait-primary"`).
169
+
170
+ #### `isDocumentVisibilitySupported`
171
+
172
+ Boolean indicating Page Visibility API support.
173
+
174
+ #### `isDocumentVisible()`
175
+
176
+ Returns `true` if the document tab is currently visible.
177
+
178
+ #### `addVisibilityChangeListener(listener)`
179
+
180
+ Registers a visibility change listener. Returns an unsubscribe function.
181
+
182
+ #### `useTheme()`
183
+
184
+ Returns `[theme, setTheme]` for managing `'day'`/`'night'` theme.
185
+
186
+ ---
187
+
188
+ ### Meta Tags
189
+
190
+ #### `initMeta(appMetaProps)`
191
+
192
+ Initialize default page meta properties. Call once at app startup.
193
+
194
+ | Param | Type | Required | Description |
195
+ |-------|------|----------|-------------|
196
+ | `appMetaProps.serviceTitle` | `string` | Yes | Service title suffix |
197
+ | `appMetaProps.description` | `string` | Yes | Default description |
198
+ | `appMetaProps.origin` | `string` | Yes | Site origin URL |
199
+ | `appMetaProps.image` | `string` | No | Default OG image |
200
+ | `appMetaProps.imageEndpoint` | `string` | No | Thumbnail generation endpoint |
201
+
202
+ ```jsx
203
+ initMeta({
204
+ serviceTitle: '| StellarExpert',
205
+ origin: metaOrigin,
206
+ description: 'StellarExpert - Stellar blockchain explorer',
207
+ image: metaOrigin + '/img/og-image.png',
208
+ imageEndpoint: metaOrigin + '/thumbnail'
209
+ })
210
+ ```
211
+
212
+ #### `usePageMetadata(meta)`
213
+
214
+ React hook for setting page title, description, and image.
215
+
216
+ ```jsx
217
+ import {usePageMetadata} from '@stellar-expert/ui-framework'
218
+
219
+ usePageMetadata({
220
+ title: `Account ${address}`,
221
+ description: `Explore properties of account ${address}`
222
+ })
223
+ ```
224
+
225
+ #### `setPageNoIndex(noIndex)`
226
+
227
+ Add or remove the `robots` noindex meta tag.
228
+
229
+ ---
230
+
231
+ ### Explorer API
232
+
233
+ #### `ExplorerApiResult`
234
+
235
+ Class representing an API response.
236
+
237
+ | Property | Type | Description |
238
+ |----------|------|-------------|
239
+ | `data` | `any` | Response data |
240
+ | `error` | `string` | Error message if any |
241
+ | `status` | `number` | HTTP status code |
242
+ | `loaded` | `boolean` | Whether response has been received |
243
+ | `fetchedAt` | `number` | Response timestamp |
244
+
245
+ #### `useExplorerApi(apiEndpoint, options?)`
246
+
247
+ React hook for fetching data from the StellarExpert API with caching and auto-refresh.
248
+
249
+ | Option | Type | Default | Description |
250
+ |--------|------|---------|-------------|
251
+ | `refreshInterval` | `number` | — | Auto-refresh interval in seconds |
252
+ | `ttl` | `number` | `60` | Cache TTL in seconds |
253
+ | `processResult` | `function` | — | Transform the response data |
254
+ | `allowStaleDataTransition` | `boolean` | `false` | Show stale data during URL transitions |
255
+
256
+ ```jsx
257
+ import {useExplorerApi} from '@stellar-expert/ui-framework'
258
+
259
+ const {data, loaded, error} = useExplorerApi('account/' + address)
260
+
261
+ const {data: valueInfo} = useExplorerApi(`account/${address}/value`)
262
+
263
+ const accountInfo = useExplorerApi('account/' + address, {
264
+ processResult(data) { /* transform */ return data }
265
+ })
266
+ ```
267
+
268
+ #### `fetchData(url, ttl?, processResult?)`
269
+
270
+ Low-level function to fetch from the Explorer API with caching.
271
+
272
+ #### `useExplorerPaginatedApi(apiEndpoint, options?, dependencies?)`
273
+
274
+ React hook for paginated API data with cursor-based navigation.
275
+
276
+ | Option | Type | Default | Description |
277
+ |--------|------|---------|-------------|
278
+ | `ttl` | `number` | `30` | Cache TTL |
279
+ | `limit` | `number` | `20` | Rows per page |
280
+ | `autoReverseRecordsOrder` | `boolean` | `false` | Reverse order for grid display |
281
+ | `defaultSortOrder` | `'asc'\|'desc'` | `'desc'` | Default sort order |
282
+ | `autoLoadLastPage` | `boolean` | `true` | Load last page if empty |
283
+ | `includeNetwork` | `boolean` | `true` | Prefix endpoint with network |
284
+ | `dataProcessingCallback` | `function` | — | Post-process records |
285
+ | `autoLoad` | `boolean` | `true` | Auto-load on mount |
286
+ | `updateLocation` | `boolean\|function` | `true` | Update browser query string |
287
+
288
+ Returns `ExplorerApiListResponse` with `data`, `loaded`, `loading`, `load(page)`, `reset()`, `canLoadNextPage`, `canLoadPrevPage`.
289
+
290
+ ```jsx
291
+ import {useExplorerPaginatedApi} from '@stellar-expert/ui-framework'
292
+
293
+ const assets = useExplorerPaginatedApi(
294
+ {path: 'asset/', query: {sort, search: navigation.query.search}},
295
+ {autoReverseRecordsOrder: true, defaultSortOrder: order, limit: 20}
296
+ )
297
+ ```
298
+
299
+ #### `loadTransaction(txHashOrId)`
300
+
301
+ Load a single transaction by hash or ID. Returns a Promise.
302
+
303
+ #### `loadLedgerTransactions(sequence)`
304
+
305
+ Load all transactions for a given ledger sequence.
306
+
307
+ #### `ExplorerBatchInfoLoader`
308
+
309
+ Batches multiple individual load requests into single API calls.
310
+
311
+ ```jsx
312
+ const loader = new ExplorerBatchInfoLoader(
313
+ batch => fetchExplorerApi('directory' + stringifyQuery({address: batch})),
314
+ entry => ({key: entry.address, info: entry})
315
+ )
316
+ const result = await loader.loadEntry(address)
317
+ ```
318
+
319
+ #### `ledgerStream`
320
+
321
+ Singleton for subscribing to new ledger notifications via long-polling.
322
+
323
+ | Method | Description |
324
+ |--------|-------------|
325
+ | `on(listener)` | Subscribe to new ledgers |
326
+ | `off(listener)` | Unsubscribe |
327
+ | `getLast()` | Fetch most recent ledger |
328
+ | `getLastSequence()` | Fetch most recent ledger sequence |
329
+
330
+ ---
331
+
332
+ ### Horizon API
333
+
334
+ #### `initHorizon()`
335
+
336
+ Create a new Horizon.Server instance using `window.horizonOrigin`.
337
+
338
+ #### `loadAllHorizonRecords(query)`
339
+
340
+ Load all records from a Horizon query using cursor-based pagination.
341
+
342
+ #### `applyListQueryParameters(query, params?)`
343
+
344
+ Apply `cursor`, `order`, and `limit` to a Horizon query builder.
345
+
346
+ #### `loadLedgers(queryParams?)`, `loadLedger(sequence)`, `streamLedgers(cursor, onNewLedger)`
347
+
348
+ Ledger-related Horizon API helpers.
349
+
350
+ #### `loadTransactions(queryParams?)`, `streamTransactions(cursor, onNewTx, includeFailed?)`, `submitTransaction(tx)`
351
+
352
+ Transaction-related Horizon API helpers.
353
+
354
+ #### `loadAccount(address)`, `loadIssuedAssets(account, queryParams?)`, `loadAccountOffers(account, queryParams?)`, `loadAccountClaimableBalances(account)`
355
+
356
+ Account-related Horizon API helpers.
357
+
358
+ #### `getAccountLockStatus(account)`
359
+
360
+ Returns `'unlocked'`, `'locked'`, or `'partially locked'`.
361
+
362
+ #### `getAccountBalance(account, asset?)`
363
+
364
+ Returns `{total, available, asset}` for a given balance.
365
+
366
+ #### `loadOrderbook(selling, buying, queryParams?)`
367
+
368
+ Load orderbook from Horizon.
369
+
370
+ #### `loadMarketTrades(baseAsset, counterAsset, queryParams?)`, `streamMarketTrades(cursor, baseAsset, counterAsset, onNewTrade)`, `streamTrades(cursor, onNewTrade)`, `loadTradesAggregation(params)`
371
+
372
+ DEX trades Horizon API helpers.
373
+
374
+ ---
375
+
376
+ ### UI Controls
377
+
378
+ #### `Button`
379
+
380
+ Versatile button rendered as `<button>` or `<a>`.
381
+
382
+ | Prop | Type | Default | Description |
383
+ |------|------|---------|-------------|
384
+ | `href` | `string` | — | Renders as `<a>` tag |
385
+ | `onClick` | `function` | — | Click handler |
386
+ | `block` | `boolean` | `false` | Block-level |
387
+ | `outline` | `boolean` | `false` | Outline style |
388
+ | `clear` | `boolean` | `false` | Text only |
389
+ | `small` | `boolean` | `false` | Small size |
390
+ | `disabled` | `boolean` | `false` | Disabled state |
391
+ | `loading` | `boolean` | `false` | Loading animation |
392
+ | `className` | `string` | — | CSS classes |
393
+ | `children` | `ReactNode` | — | Button content |
394
+
395
+ ```jsx
396
+ import {Button} from '@stellar-expert/ui-framework'
397
+
398
+ <Button block outline href="/signup">Create new account</Button>
399
+ <Button block disabled={disabled} loading={inProgress} onClick={proceed}>Submit</Button>
400
+ ```
401
+
402
+ #### `ButtonGroup`
403
+
404
+ Groups buttons with consistent spacing.
405
+
406
+ | Prop | Type | Description |
407
+ |------|------|-------------|
408
+ | `inline` | `boolean` | Render as `<span>` |
409
+ | `children` | `ReactNode` | Nested buttons |
410
+
411
+ #### `Dropdown`
412
+
413
+ Customizable dropdown select component.
414
+
415
+ | Prop | Type | Default | Description |
416
+ |------|------|---------|-------------|
417
+ | `options` | `Array<DropdownOption\|string>` | — | Available options |
418
+ | `value` | `string\|number` | — | Selected value |
419
+ | `onChange` | `function` | — | Change handler |
420
+ | `title` | `any` | — | Display title |
421
+ | `disabled` | `boolean` | `false` | Disabled state |
422
+ | `showToggle` | `boolean` | `true` | Toggle arrow |
423
+ | `solo` | `boolean` | `false` | Centered dialog mode |
424
+ | `maxHeight` | `string` | `'35em'` | Max list height |
425
+
426
+ ```jsx
427
+ import {Dropdown} from '@stellar-expert/ui-framework'
428
+
429
+ <Dropdown options={orderOptions} onChange={setSort} value={sort}/>
430
+ <Dropdown options={networks} value={network} onChange={updateNetwork}/>
431
+ ```
432
+
433
+ #### `Tabs`
434
+
435
+ Tabbed interface with optional URL query parameter sync.
436
+
437
+ | Prop | Type | Description |
438
+ |------|------|-------------|
439
+ | `tabs` | `TabDescriptor[]` | Tab definitions (`{name, title, render?, isDefault?}`) |
440
+ | `selectedTab` | `string` | Controlled selected tab |
441
+ | `onChange` | `function` | Tab change handler |
442
+ | `queryParam` | `string` | Query parameter for URL sync |
443
+ | `right` | `boolean` | Right-align tabs |
444
+
445
+ ```jsx
446
+ import {Tabs} from '@stellar-expert/ui-framework'
447
+
448
+ <Tabs right queryParam="type"
449
+ tabs={[
450
+ {name: 'all', title: 'All', isDefault: true},
451
+ {name: 'trade', title: 'Trades'}
452
+ ]}
453
+ selectedTab={type}
454
+ onChange={tabName => setType(tabName)}/>
455
+ ```
456
+
457
+ #### `Tooltip`
458
+
459
+ Tooltip with automatic positioning.
460
+
461
+ | Prop | Type | Default | Description |
462
+ |------|------|---------|-------------|
463
+ | `trigger` | `ReactElement` | — | Element that triggers the tooltip |
464
+ | `desiredPlace` | `'top'\|'bottom'\|'left'\|'right'` | `'top'` | Preferred position |
465
+ | `activation` | `'hover'\|'click'` | `'hover'` | Activation mode |
466
+ | `maxWidth` | `string` | — | Max tooltip width |
467
+ | `children` | `ReactNode` | — | Tooltip content |
468
+
469
+ #### `InfoTooltip`
470
+
471
+ Help icon with tooltip and optional "Read more" link.
472
+
473
+ | Prop | Type | Description |
474
+ |------|------|-------------|
475
+ | `children` | `ReactNode` | Tooltip content |
476
+ | `link` | `string` | "Read more" URL |
477
+ | `icon` | `string` | Icon CSS class |
478
+
479
+ ```jsx
480
+ import {InfoTooltip} from '@stellar-expert/ui-framework'
481
+
482
+ <InfoTooltip link="https://example.com/docs">
483
+ Explanation text shown on hover.
484
+ </InfoTooltip>
485
+ ```
486
+
487
+ #### `CodeBlock`
488
+
489
+ Syntax-highlighted code block.
490
+
491
+ | Prop | Type | Description |
492
+ |------|------|-------------|
493
+ | `children` | `string` | Source code |
494
+ | `lang` | `'js'\|'json'\|'html'\|'xml'\|'toml'\|'rust'\|'plain'` | Language |
495
+
496
+ ```jsx
497
+ import {CodeBlock} from '@stellar-expert/ui-framework'
498
+
499
+ <CodeBlock lang="rust">{contractSourceCode}</CodeBlock>
500
+ ```
501
+
502
+ #### `UpdateHighlighter`
503
+
504
+ Briefly highlights content with animation when children change.
505
+
506
+ ```jsx
507
+ <UpdateHighlighter>{formatPrice(1 / market.price, 4)}</UpdateHighlighter>
508
+ ```
509
+
510
+ #### `Slider`
511
+
512
+ Range input slider with optional category labels.
513
+
514
+ | Prop | Type | Default | Description |
515
+ |------|------|---------|-------------|
516
+ | `value` | `number` | `min` | Current value |
517
+ | `onChange` | `function` | — | Change handler (throttled) |
518
+ | `min` | `number` | `0` | Minimum |
519
+ | `max` | `number` | `100` | Maximum |
520
+ | `step` | `number` | `1` | Step |
521
+
522
+ #### `ExternalLink`
523
+
524
+ Link opening in a new tab with `rel="noreferrer noopener"`.
525
+
526
+ ```jsx
527
+ <ExternalLink href="https://example.com">Read more</ExternalLink>
528
+ ```
529
+
530
+ ---
531
+
532
+ ### Interaction
533
+
534
+ #### `BlockSelect`
535
+
536
+ Block that auto-selects all its text content on focus.
537
+
538
+ | Prop | Type | Default | Description |
539
+ |------|------|---------|-------------|
540
+ | `as` | `string` | `'span'` | HTML tag |
541
+ | `wrap` | `boolean` | — | Enable/disable text wrapping |
542
+ | `inline` | `boolean` | — | Inline display |
543
+
544
+ ```jsx
545
+ import {BlockSelect} from '@stellar-expert/ui-framework'
546
+
547
+ <BlockSelect>{originalAddress}</BlockSelect>
548
+ <BlockSelect inline className="condensed">{transaction.sequence}</BlockSelect>
549
+ ```
550
+
551
+ #### `CopyToClipboard`
552
+
553
+ Copy-to-clipboard wrapper with default copy icon.
554
+
555
+ | Prop | Type | Description |
556
+ |------|------|-------------|
557
+ | `text` | `string` | Text to copy |
558
+ | `children` | `ReactNode` | Custom trigger (defaults to copy icon) |
559
+ | `title` | `string` | Tooltip text |
560
+
561
+ ```jsx
562
+ import {CopyToClipboard} from '@stellar-expert/ui-framework'
563
+
564
+ <CopyToClipboard text={publicKey}>
565
+ <a href="#" className="icon-copy active-icon" title="Copy account address"/>
566
+ </CopyToClipboard>
567
+ ```
568
+
569
+ #### `Spoiler`
570
+
571
+ Expandable/collapsible content toggle.
572
+
573
+ | Prop | Type | Default | Description |
574
+ |------|------|---------|-------------|
575
+ | `expanded` | `boolean` | `false` | Initial state |
576
+ | `showMore` | `string` | `'Show more'` | Expand label |
577
+ | `showLess` | `string` | `'Show less'` | Collapse label |
578
+ | `onChange` | `function` | — | Toggle callback |
579
+ | `micro` | `boolean` | — | Icon-only mode |
580
+ | `children` | `ReactNode` | — | Hidden content |
581
+
582
+ ```jsx
583
+ import {Spoiler} from '@stellar-expert/ui-framework'
584
+
585
+ <Spoiler expanded={extended}
586
+ onChange={e => setExtended(e.expanded)}
587
+ showMore="Show extended info"
588
+ showLess="Hide extended info"/>
589
+ ```
590
+
591
+ #### `Accordion`
592
+
593
+ Collapsible panel group (one panel open at a time).
594
+
595
+ | Prop | Type | Default | Description |
596
+ |------|------|---------|-------------|
597
+ | `options` | `AccordionOption[]` | — | Panel definitions (`{key, title, content}`) |
598
+ | `collapsedSymbol` | `string` | `'+'` | Collapsed prefix |
599
+ | `expandedSymbol` | `string` | `'-'` | Expanded prefix |
600
+
601
+ ```jsx
602
+ import {Accordion} from '@stellar-expert/ui-framework'
603
+
604
+ <Accordion options={[
605
+ {key: 'overview', title: 'Overview', content: <OverviewPanel/>},
606
+ {key: 'details', title: 'Details', content: <DetailsPanel/>}
607
+ ]}/>
608
+ ```
609
+
610
+ #### `ThemeSelector`
611
+
612
+ Theme toggle button switching between day (light) and night (dark) themes.
613
+
614
+ ```jsx
615
+ <ThemeSelector/>
616
+ ```
617
+
618
+ #### `InlineProgress`
619
+
620
+ Animated dots progress indicator.
621
+
622
+ | Prop | Type | Default | Description |
623
+ |------|------|---------|-------------|
624
+ | `dots` | `number` | `5` | Max dots |
625
+
626
+ #### `useWindowWidth()`
627
+
628
+ React hook that tracks window width (throttled).
629
+
630
+ ```jsx
631
+ const width = useWindowWidth()
632
+ ```
633
+
634
+ #### `QrCode`
635
+
636
+ QR code renderer with optional embedded logo.
637
+
638
+ | Prop | Type | Default | Description |
639
+ |------|------|---------|-------------|
640
+ | `value` | `string` | — | Value to encode |
641
+ | `size` | `number` | `320` | QR code size |
642
+ | `caption` | `string` | — | Caption below QR |
643
+ | `embeddedImage` | `string` | — | Logo URL |
644
+ | `embeddedSize` | `number` | 10% of size | Logo size |
645
+
646
+ ```jsx
647
+ import {QrCode} from '@stellar-expert/ui-framework'
648
+
649
+ <QrCode value={accountAddress} size={240}
650
+ embeddedImage="/img/logo-square.svg" embeddedSize={36}/>
651
+ ```
652
+
653
+ #### `Dialog`
654
+
655
+ Modal dialog overlay.
656
+
657
+ | Prop | Type | Description |
658
+ |------|------|-------------|
659
+ | `dialogOpen` | `boolean` | Whether dialog is visible |
660
+ | `children` | `ReactNode` | Dialog content |
661
+
662
+ ```jsx
663
+ <Dialog dialogOpen={isOpen}><AuthRequestView/></Dialog>
664
+ ```
665
+
666
+ #### `SystemDialog`
667
+
668
+ Replaces built-in `alert()` and `confirm()` with styled dialog versions.
669
+
670
+ ```jsx
671
+ <SystemDialog/>
672
+ ```
673
+
674
+ #### `useAutoFocusRef`
675
+
676
+ Ref callback that auto-focuses an element after a short delay.
677
+
678
+ ```jsx
679
+ const inputRef = useAutoFocusRef()
680
+ return <input ref={inputRef} />
681
+ ```
682
+
683
+ ---
684
+
685
+ ### Date Components
686
+
687
+ #### `UtcTimestamp`
688
+
689
+ Displays a formatted UTC timestamp.
690
+
691
+ | Prop | Type | Description |
692
+ |------|------|-------------|
693
+ | `date` | `string\|number\|Date` | Timestamp to format |
694
+ | `dateOnly` | `boolean` | Show date without time |
695
+
696
+ ```jsx
697
+ import {UtcTimestamp} from '@stellar-expert/ui-framework'
698
+
699
+ <UtcTimestamp date={created} dateOnly/>
700
+ <UtcTimestamp date={offer.created}/>
701
+ ```
702
+
703
+ #### `ElapsedTime`
704
+
705
+ Displays relative time (e.g., "3m ago").
706
+
707
+ | Prop | Type | Description |
708
+ |------|------|-------------|
709
+ | `ts` | `Date\|string\|number` | Timestamp |
710
+ | `suffix` | `string` | Appended text |
711
+
712
+ ```jsx
713
+ import {ElapsedTime} from '@stellar-expert/ui-framework'
714
+
715
+ <ElapsedTime ts={new Date(tx.createdAt)} suffix=" ago"/>
716
+ ```
717
+
718
+ #### `DateSelector`
719
+
720
+ Date/time input selector.
721
+
722
+ | Prop | Type | Description |
723
+ |------|------|-------------|
724
+ | `value` | `string\|number\|Date` | Current date value |
725
+ | `onChange` | `function` | Returns Unix timestamp or null |
726
+ | `min` | `string` | Min date |
727
+ | `max` | `string` | Max date |
728
+
729
+ #### `trimIsoDateSeconds(date)`
730
+
731
+ Trims seconds from an ISO date string. Returns a string like `"2024-01-15T10:30"`.
732
+
733
+ ---
734
+
735
+ ### Ledger Components
736
+
737
+ #### `TxLink`, `OpLink`, `LedgerLink`, `OfferLink`, `PoolLink`
738
+
739
+ Links to StellarExpert ledger entries.
740
+
741
+ | Component | ID Prop | Type |
742
+ |-----------|---------|------|
743
+ | `TxLink` | `tx` | `string` |
744
+ | `OpLink` | `op` | `string` |
745
+ | `LedgerLink` | `sequence` | `number` |
746
+ | `OfferLink` | `offer` | `string` |
747
+ | `PoolLink` | `pool` | `string` |
748
+
749
+ All accept optional `network` and `children` props.
750
+
751
+ ```jsx
752
+ import {TxLink, OfferLink} from '@stellar-expert/ui-framework'
753
+
754
+ <TxLink tx={tx.txHash}>
755
+ <ElapsedTime ts={new Date(tx.createdAt)} suffix=" ago"/>
756
+ </TxLink>
757
+ <OfferLink offer={offer.id}/>
758
+ ```
759
+
760
+ #### `formatExplorerLink(type, id, network?)`
761
+
762
+ Generate an explorer URL for a given entry type and ID.
763
+
764
+ ```jsx
765
+ const url = formatExplorerLink('contract', contract.address + '/versions')
766
+ ```
767
+
768
+ #### `retrieveLedgerInfo(data)`
769
+
770
+ Parse raw ledger data (including XDR header) into structured `LedgerInfo`.
771
+
772
+ ---
773
+
774
+ ### Account Components
775
+
776
+ #### `AccountAddress`
777
+
778
+ Explorer link for Stellar addresses with identicon and directory info.
779
+
780
+ | Prop | Type | Default | Description |
781
+ |------|------|---------|-------------|
782
+ | `account` | `string` | — | StrKey-encoded address |
783
+ | `chars` | `number\|'all'` | `8` | Visible characters |
784
+ | `name` | `string\|false` | — | Display name override |
785
+ | `link` | `string\|false` | — | Link override |
786
+ | `icon` | `boolean` | `true` | Show identicon |
787
+ | `network` | `string` | — | Network override |
788
+
789
+ ```jsx
790
+ import {AccountAddress} from '@stellar-expert/ui-framework'
791
+
792
+ <AccountAddress account={address} chars={12}/>
793
+ <AccountAddress account={address} name={false} network={network} chars={12}/>
794
+ <AccountAddress account={address} className="plain" link={false} chars="all"/>
795
+ ```
796
+
797
+ #### `AccountIdenticon`
798
+
799
+ Renders an account-specific identicon.
800
+
801
+ | Prop | Type | Description |
802
+ |------|------|-------------|
803
+ | `address` | `string` | StrKey-encoded address |
804
+ | `size` | `number` | Display size |
805
+
806
+ #### `drawIdenticon(address, size?)`
807
+
808
+ Generate an SVG identicon string for a Stellar address.
809
+
810
+ #### `SignerKey`
811
+
812
+ Account signer key display with weight.
813
+
814
+ | Prop | Type | Description |
815
+ |------|------|-------------|
816
+ | `signer` | `Object` | Signer object (raw XDR or parsed) |
817
+ | `showWeight` | `boolean` | Show weight (default `true`) |
818
+
819
+ #### `calculateAvailableBalance(account, balance, additionalReserves?, decimals?)`
820
+
821
+ Calculate available balance for an account trustline, accounting for reserves and liabilities.
822
+
823
+ ---
824
+
825
+ ### Asset Components
826
+
827
+ #### `AssetLink`
828
+
829
+ Explorer link for assets with icon and issuer display.
830
+
831
+ | Prop | Type | Default | Description |
832
+ |------|------|---------|-------------|
833
+ | `asset` | `string\|AssetDescriptor` | — | Asset descriptor |
834
+ | `link` | `string\|false` | — | Link override |
835
+ | `issuer` | `boolean` | `true` | Show issuer |
836
+ | `icon` | `boolean` | `true` | Show icon |
837
+
838
+ ```jsx
839
+ import {AssetLink} from '@stellar-expert/ui-framework'
840
+
841
+ <AssetLink asset={descriptor}/>
842
+ <AssetLink asset={xrf} issuer={false} icon={false}/>
843
+ ```
844
+
845
+ #### `AssetIssuer`
846
+
847
+ Inline asset issuer display (domain or shortened address).
848
+
849
+ #### `AssetIcon`
850
+
851
+ Asset icon from metadata or identicon fallback.
852
+
853
+ #### `AssetSelector`
854
+
855
+ Asset search and selection dropdown.
856
+
857
+ | Prop | Type | Description |
858
+ |------|------|-------------|
859
+ | `onChange` | `function` | Selection callback |
860
+ | `value` | `string` | Selected asset |
861
+ | `predefinedAssets` | `string[]` | Assets shown at top |
862
+ | `restricted` | `boolean` | Limit to predefined list |
863
+ | `title` | `string` | Dropdown title |
864
+
865
+ ```jsx
866
+ import {AssetSelector} from '@stellar-expert/ui-framework'
867
+
868
+ <AssetSelector value={asset}
869
+ onChange={select}
870
+ title="Choose an asset"/>
871
+ ```
872
+
873
+ #### `Amount`
874
+
875
+ Formatted token amount with optional asset link.
876
+
877
+ | Prop | Type | Description |
878
+ |------|------|-------------|
879
+ | `amount` | `string\|number` | Token amount |
880
+ | `asset` | `string\|AssetDescriptor` | Asset descriptor |
881
+ | `decimals` | `number\|'auto'` | Decimal precision |
882
+ | `adjust` | `boolean` | Treat as raw Int64 stroops |
883
+ | `round` | `boolean\|'floor'` | Round the amount |
884
+ | `issuer` | `boolean` | Show issuer |
885
+ | `icon` | `boolean` | Show icon |
886
+
887
+ ```jsx
888
+ import {Amount} from '@stellar-expert/ui-framework'
889
+
890
+ <Amount amount={supply} adjust round/>
891
+ <Amount asset={offer.selling} amount={offer.amount} adjust/>
892
+ <Amount amount={total} asset={xrf} adjust issuer={false}/>
893
+ ```
894
+
895
+ #### `useAssetMeta(asset)`
896
+
897
+ React hook that fetches and caches asset metadata. Returns `AssetMeta` or null.
898
+
899
+ #### `useAssetList(params?)`
900
+
901
+ React hook for fetching a paginated, searchable asset list. Returns `{assets, loadPage, loading}`.
902
+
903
+ ---
904
+
905
+ ### DEX Components
906
+
907
+ #### `PriceDynamic`
908
+
909
+ Price change percentage indicator with positive/negative styling.
910
+
911
+ | Prop | Type | Description |
912
+ |------|------|-------------|
913
+ | `change` | `number` | Pre-calculated percentage |
914
+ | `current` | `number` | Current price |
915
+ | `prev` | `number` | Previous price |
916
+ | `standalone` | `boolean` | Standalone styling |
917
+ | `allowZero` | `boolean` | Show 0% |
918
+
919
+ ---
920
+
921
+ ### Directory
922
+
923
+ #### `getDirectoryEntry(address, options?)`
924
+
925
+ Async function to fetch a directory entry.
926
+
927
+ | Option | Type | Default | Description |
928
+ |--------|------|---------|-------------|
929
+ | `forceRefresh` | `boolean` | `false` | Bypass cache |
930
+ | `extended` | `boolean` | `false` | Extended info |
931
+
932
+ #### `useDirectory(address, options?)`
933
+
934
+ React hook for directory info. Returns the directory entry or null.
935
+
936
+ ```jsx
937
+ const directoryInfo = useDirectory(address)
938
+ ```
939
+
940
+ #### `useDirectoryTags()`
941
+
942
+ React hook that fetches all available directory tags.
943
+
944
+ ---
945
+
946
+ ### Transaction & Operations
947
+
948
+ #### `TxOperationsList`
949
+
950
+ Renders a list of transaction operations with optional effects.
951
+
952
+ | Prop | Type | Default | Description |
953
+ |------|------|---------|-------------|
954
+ | `parsedTx` | `ParsedTxDetails` | — | Parsed transaction |
955
+ | `filter` | `function` | — | Filter operations |
956
+ | `showFees` | `boolean` | `true` | Show fee effects |
957
+ | `compact` | `boolean` | `false` | Compact view |
958
+ | `showEffects` | `boolean` | — | Show effects toggle |
959
+
960
+ ```jsx
961
+ import {TxOperationsList} from '@stellar-expert/ui-framework'
962
+
963
+ <TxOperationsList parsedTx={parsedTx}/>
964
+ <TxOperationsList parsedTx={tx} compact showFees={false}/>
965
+ ```
966
+
967
+ #### `parseTxDetails(params)`
968
+
969
+ Parse transaction details from raw XDR.
970
+
971
+ | Param | Type | Required | Description |
972
+ |-------|------|----------|-------------|
973
+ | `network` | `string` | Yes | Network passphrase |
974
+ | `txEnvelope` | `string` | Yes | Base64 tx envelope XDR |
975
+ | `result` | `string` | No | Base64 tx result |
976
+ | `meta` | `string` | No | Base64 tx meta |
977
+ | `context` | `TxFiltersContext` | No | Filter context |
978
+ | `createdAt` | `string` | No | Timestamp |
979
+ | `protocol` | `number` | No | Protocol version |
980
+
981
+ ```jsx
982
+ import {parseTxDetails} from '@stellar-expert/ui-framework'
983
+
984
+ const parsedTx = parseTxDetails({
985
+ network: appSettings.networkPassphrase,
986
+ txEnvelope: txResponse.body,
987
+ result: txResponse.result,
988
+ meta: txResponse.meta,
989
+ createdAt: txResponse.ts,
990
+ context: {},
991
+ protocol: txResponse.protocol
992
+ })
993
+ ```
994
+
995
+ #### `useTxHistory(params)`
996
+
997
+ React hook for paginated transaction history.
998
+
999
+ ```jsx
1000
+ const historyModel = useTxHistory({
1001
+ filters: {account: [address]},
1002
+ order: 'desc',
1003
+ rows: 50,
1004
+ updateLocation: false
1005
+ })
1006
+ ```
1007
+
1008
+ #### `useTxInfo(idOrHash)`
1009
+
1010
+ React hook for transaction details by ID or hash.
1011
+
1012
+ ---
1013
+
1014
+ ### Effects
1015
+
1016
+ #### `EffectDescription`
1017
+
1018
+ Renders a human-readable description of a transaction effect.
1019
+
1020
+ | Prop | Type | Description |
1021
+ |------|------|-------------|
1022
+ | `effect` | `Object` | Parsed effect with `type` field |
1023
+ | `operation` | `Object` | Parent operation context |
1024
+
1025
+ ---
1026
+
1027
+ ### Contract / Soroban
1028
+
1029
+ #### `useContractInfo(address)`
1030
+
1031
+ React hook for fetching contract information. Returns `ExplorerApiResult`.
1032
+
1033
+ #### `generateContractSourceLink(hash)`
1034
+
1035
+ Generate a URL to download contract WASM source.
1036
+
1037
+ #### `useContractSource(hash)`
1038
+
1039
+ React hook for fetching contract WASM binary. Returns `ArrayBuffer`, `null`, or `undefined` (loading).
1040
+
1041
+ #### `ScVal`
1042
+
1043
+ Renders Stellar smart contract values in human-readable format.
1044
+
1045
+ | Prop | Type | Default | Description |
1046
+ |------|------|---------|-------------|
1047
+ | `value` | `string\|xdr.ScVal\|Array` | — | ScVal (base64 XDR, parsed, or array) |
1048
+ | `indent` | `boolean` | `false` | Block-level indentation |
1049
+
1050
+ ```jsx
1051
+ import {ScVal} from '@stellar-expert/ui-framework'
1052
+
1053
+ <ScVal value={entry.key} indent/>
1054
+ <ScVal value={entry.value} indent/>
1055
+ ```
1056
+
1057
+ #### `parseScValValue(value)`
1058
+
1059
+ Parse a base64-encoded XDR ScVal string into an `xdr.ScVal` object.
1060
+
1061
+ #### `ScValStruct`
1062
+
1063
+ Structural wrapper for ScVal elements (handles indentation and separators).
1064
+
1065
+ #### `primitiveTypes`
1066
+
1067
+ `Set<string>` of primitive ScVal type identifiers.
1068
+
1069
+ ---
1070
+
1071
+ ### Stellar Utilities
1072
+
1073
+ #### `parseStellarGenericId(id)`
1074
+
1075
+ Parse a Stellar generic ID into components. Returns `{type, id, ledger, tx, operationOrder}`.
1076
+
1077
+ ```jsx
1078
+ const {tx: txid, operationOrder} = parseStellarGenericId(term)
1079
+ ```
1080
+
1081
+ #### `decodeKeyType(key)`
1082
+
1083
+ Decode a Stellar key into its type and components. Returns `{address, type, muxedId?, publicKey?, payload?}` or `null`.
1084
+
1085
+ #### `parseMuxedAccount(muxedAddress)`
1086
+
1087
+ Parse a multiplexed address. Returns `{address, muxedId}`.
1088
+
1089
+ #### `encodeMuxedAccount(address, muxedId)`
1090
+
1091
+ Encode an address and BigInt ID into a multiplexed address.
1092
+
1093
+ #### `signatureHintToMask(hint)`
1094
+
1095
+ Convert a signature hint Buffer to a StrKey mask string.
1096
+
1097
+ #### `formatSignatureHint(hint)`
1098
+
1099
+ Format a signature hint for display.
1100
+
1101
+ #### `singatureHintMatchesKey(hint, key)`
1102
+
1103
+ Check if a hint matches a key. Note: function name contains a typo ("singature").
1104
+
1105
+ #### `findKeyBySignatureHint(hint, allKeys)`
1106
+
1107
+ Find a key matching a signature hint.
1108
+
1109
+ #### `findSignatureByKey(key, allSignatures?)`
1110
+
1111
+ Find a transaction signature for a given signer key.
1112
+
1113
+ #### `findKeysBySignatureHint(signature, keys)`
1114
+
1115
+ Find all keys matching a signature's hint.
1116
+
1117
+ ```jsx
1118
+ const possibleSigners = findKeysBySignatureHint(signature, potentialSigners)
1119
+ ```
1120
+
1121
+ ---
1122
+
1123
+ ## License
1124
+
1125
+ MIT