@stellar-expert/ui-framework 1.16.8 → 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 (78) 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/claimable-balance/claimable-balance-claimants.js +23 -18
  20. package/contract/contract-api.js +15 -0
  21. package/contract/invocation-info-view.js +10 -2
  22. package/contract/sc-val.js +131 -107
  23. package/controls/button-group.js +25 -19
  24. package/controls/button.js +93 -78
  25. package/controls/code-block.js +42 -34
  26. package/controls/dropdown.js +318 -287
  27. package/controls/external-link.js +10 -4
  28. package/controls/info-tooltip.js +23 -16
  29. package/controls/slider.js +29 -19
  30. package/controls/tabs.js +94 -94
  31. package/controls/tooltip.js +244 -240
  32. package/controls/update-highlighter.js +32 -27
  33. package/date/date-selector.js +56 -54
  34. package/date/elapsed-time.js +28 -21
  35. package/dex/price-dynamic.js +53 -44
  36. package/directory/directory-hooks.js +109 -97
  37. package/effect/effect-description.js +346 -344
  38. package/errors/error-boundary.js +110 -97
  39. package/horizon/horizon-account-helpers.js +104 -104
  40. package/horizon/horizon-ledger-helpers.js +35 -35
  41. package/horizon/horizon-trades-helper.js +88 -88
  42. package/horizon/horizon-transaction-helpers.js +36 -36
  43. package/index.d.ts +1241 -0
  44. package/interaction/accordion.js +43 -35
  45. package/interaction/autofocus.js +13 -9
  46. package/interaction/block-select.js +64 -53
  47. package/interaction/copy-to-clipboard.js +25 -18
  48. package/interaction/inline-progress.js +20 -15
  49. package/interaction/qr-code.js +34 -34
  50. package/interaction/responsive.js +20 -20
  51. package/interaction/spoiler.js +52 -39
  52. package/interaction/theme-selector.js +13 -10
  53. package/ledger/ledger-entry-href-formatter.js +27 -26
  54. package/ledger/ledger-entry-link.js +93 -58
  55. package/ledger/ledger-info-parser.js +46 -18
  56. package/meta/page-meta-tags.js +243 -238
  57. package/module/dynamic-module.js +47 -47
  58. package/package.json +41 -40
  59. package/state/on-screen-hooks.js +22 -22
  60. package/state/page-visibility-helpers.js +29 -16
  61. package/state/page-visibility-hooks.js +13 -11
  62. package/state/screen-orientation-hooks.js +19 -15
  63. package/state/state-hooks.js +76 -76
  64. package/state/stellar-network-hooks.js +56 -44
  65. package/state/theme.js +29 -28
  66. package/stellar/key-type.js +92 -91
  67. package/stellar/ledger-generic-id.js +39 -39
  68. package/stellar/signature-hint-utils.js +65 -65
  69. package/toast/toast-notifications-block.js +59 -59
  70. package/tx/op-description-view.js +945 -942
  71. package/tx/op-icon.js +98 -98
  72. package/tx/parser/op-balance-changes.js +66 -66
  73. package/tx/parser/op-descriptor.js +51 -48
  74. package/tx/parser/tx-details-parser.js +81 -81
  75. package/tx/parser/tx-matcher.js +371 -371
  76. package/tx/parser/type-filter-matcher.js +126 -126
  77. package/tx/tx-list-hooks.js +32 -18
  78. package/tx/tx-operations-list.js +117 -116
@@ -1,36 +1,44 @@
1
- import React, {useCallback, useEffect, useState} from 'react'
2
- import cn from 'classnames'
3
- import './accordion.scss'
4
-
5
- /**
6
- * Group of collapsible panels
7
- * @param {{key: String, title: String|JSX.Element, content:*}[]} options - Accordion options
8
- * @param {String} [collapsedSymbol] - Prefix to show for collapsed panels
9
- * @param {String} [expandedSymbol] - Prefix to show for expanded panel
10
- */
11
- export function Accordion({options, collapsedSymbol = '+', expandedSymbol = '-', ...otherProps}) {
12
- const [selectedOption, setSelectedOption] = useState()
13
- useEffect(() => {
14
- const firstOption = options[0]
15
- setSelectedOption(firstOption.key || firstOption.title)
16
- }, [options])
17
- const changeSelected = useCallback(e => setSelectedOption(e.currentTarget.dataset.key), [options])
18
- return <div className="accordion" {...otherProps}>
19
- {options.map(({key, title, content}) => {
20
- const expanded = key ?
21
- selectedOption === key :
22
- selectedOption === title
23
- return <div key={key || title} className={cn('option', {expanded})}>
24
- <div className="accordion-header" data-collapsed={collapsedSymbol} data-expanded={expandedSymbol} data-key={key || title}
25
- onClick={changeSelected}>
26
- {title || key}
27
- </div>
28
- <div className="accordion-collapse">
29
- <div className="accordion-body">
30
- {content}
31
- </div>
32
- </div>
33
- </div>
34
- })}
35
- </div>
1
+ import React, {useCallback, useEffect, useState} from 'react'
2
+ import cn from 'classnames'
3
+ import './accordion.scss'
4
+
5
+ /**
6
+ * @typedef {Object} AccordionOption
7
+ * @property {string} [key] - Unique key for the panel (falls back to title)
8
+ * @property {string|JSX.Element} title - Panel header content
9
+ * @property {*} content - Panel body content
10
+ */
11
+
12
+ /**
13
+ * Group of collapsible panels where only one panel is expanded at a time
14
+ * @param {Object} props
15
+ * @param {AccordionOption[]} props.options - Accordion panel definitions
16
+ * @param {string} [props.collapsedSymbol='+'] - Prefix shown for collapsed panels
17
+ * @param {string} [props.expandedSymbol='-'] - Prefix shown for the expanded panel
18
+ */
19
+ export function Accordion({options, collapsedSymbol = '+', expandedSymbol = '-', ...otherProps}) {
20
+ const [selectedOption, setSelectedOption] = useState()
21
+ useEffect(() => {
22
+ const firstOption = options[0]
23
+ setSelectedOption(firstOption.key || firstOption.title)
24
+ }, [options])
25
+ const changeSelected = useCallback(e => setSelectedOption(e.currentTarget.dataset.key), [options])
26
+ return <div className="accordion" {...otherProps}>
27
+ {options.map(({key, title, content}) => {
28
+ const expanded = key ?
29
+ selectedOption === key :
30
+ selectedOption === title
31
+ return <div key={key || title} className={cn('option', {expanded})}>
32
+ <div className="accordion-header" data-collapsed={collapsedSymbol} data-expanded={expandedSymbol} data-key={key || title}
33
+ onClick={changeSelected}>
34
+ {title || key}
35
+ </div>
36
+ <div className="accordion-collapse">
37
+ <div className="accordion-body">
38
+ {content}
39
+ </div>
40
+ </div>
41
+ </div>
42
+ })}
43
+ </div>
36
44
  }
@@ -1,9 +1,13 @@
1
- export function useAutoFocusRef(inputRef) {
2
- if (!inputRef)
3
- return
4
- setTimeout(() => {
5
- if (document.body.contains(inputRef)) {
6
- inputRef.focus()
7
- }
8
- }, 200)
9
- }
1
+ /**
2
+ * Auto-focuses the given DOM element after a short delay (ref callback)
3
+ * @param {HTMLElement} inputRef - DOM element to focus
4
+ */
5
+ export function useAutoFocusRef(inputRef) {
6
+ if (!inputRef)
7
+ return
8
+ setTimeout(() => {
9
+ if (document.body.contains(inputRef)) {
10
+ inputRef.focus()
11
+ }
12
+ }, 200)
13
+ }
@@ -1,54 +1,65 @@
1
- import React from 'react'
2
- import PropTypes from 'prop-types'
3
- import cn from 'classnames'
4
- import './block-select.scss'
5
-
6
- function select(target) {
7
- //handle textarea and inputs
8
- if (target.nodeName.match(/^(INPUT|TEXTAREA)$/i)) {
9
- target.focus()
10
- target.select()
11
- return
12
- }
13
- //handle selection inside text elements
14
- const range = document.createRange()
15
- range.selectNodeContents(target)
16
- const sel = window.getSelection()
17
- if (typeof sel.setBaseAndExtent === 'function') {
18
- // Safari
19
- sel.setBaseAndExtent(target, 0, target, 1)
20
- }
21
- sel.removeAllRanges()
22
- sel.addRange(range)
23
- }
24
-
25
- export const BlockSelect = React.memo(function BlockSelect({as = 'span', children, title, className, wrap, inline, style, ...op}) {
26
- const props = {
27
- className: cn('block-select', className),
28
- onFocus: e => select(e.target),
29
- tabIndex: '-1',
30
- style,
31
- title,
32
- ...op
33
- }
34
- if (wrap) {
35
- props.style = {...props.style, whiteSpace: 'normal', overflow: 'visible'}
36
- }
37
- if (wrap === false) {
38
- props.style = {...props.style, whiteSpace: 'nowrap', overflow: 'hidden'}
39
- }
40
- if (inline) {
41
- props.style = {...props.style, display: 'inline'}
42
- }
43
- return React.createElement(as, props, children)
44
- })
45
-
46
- BlockSelect.propTypes = {
47
- children: PropTypes.any.isRequired,
48
- title: PropTypes.string,
49
- className: PropTypes.string,
50
- style: PropTypes.object,
51
- wrap: PropTypes.bool,
52
- inline: PropTypes.bool,
53
- as: PropTypes.string
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import cn from 'classnames'
4
+ import './block-select.scss'
5
+
6
+ function select(target) {
7
+ //handle textarea and inputs
8
+ if (target.nodeName.match(/^(INPUT|TEXTAREA)$/i)) {
9
+ target.focus()
10
+ target.select()
11
+ return
12
+ }
13
+ //handle selection inside text elements
14
+ const range = document.createRange()
15
+ range.selectNodeContents(target)
16
+ const sel = window.getSelection()
17
+ if (typeof sel.setBaseAndExtent === 'function') {
18
+ // Safari
19
+ sel.setBaseAndExtent(target, 0, target, 1)
20
+ }
21
+ sel.removeAllRanges()
22
+ sel.addRange(range)
23
+ }
24
+
25
+ /**
26
+ * Block that selects all its text content on focus/click
27
+ * @param {Object} props
28
+ * @param {string} [props.as='span'] - HTML tag to render
29
+ * @param {*} props.children - Content to display
30
+ * @param {string} [props.title] - HTML title attribute
31
+ * @param {string} [props.className] - Additional CSS classes
32
+ * @param {boolean} [props.wrap] - Enable text wrapping (true) or disable it (false)
33
+ * @param {boolean} [props.inline] - Force inline display
34
+ * @param {Object} [props.style] - Inline styles
35
+ */
36
+ export const BlockSelect = React.memo(function BlockSelect({as = 'span', children, title, className, wrap, inline, style, ...op}) {
37
+ const props = {
38
+ className: cn('block-select', className),
39
+ onFocus: e => select(e.target),
40
+ tabIndex: '-1',
41
+ style,
42
+ title,
43
+ ...op
44
+ }
45
+ if (wrap) {
46
+ props.style = {...props.style, whiteSpace: 'normal', overflow: 'visible'}
47
+ }
48
+ if (wrap === false) {
49
+ props.style = {...props.style, whiteSpace: 'nowrap', overflow: 'hidden'}
50
+ }
51
+ if (inline) {
52
+ props.style = {...props.style, display: 'inline'}
53
+ }
54
+ return React.createElement(as, props, children)
55
+ })
56
+
57
+ BlockSelect.propTypes = {
58
+ children: PropTypes.any.isRequired,
59
+ title: PropTypes.string,
60
+ className: PropTypes.string,
61
+ style: PropTypes.object,
62
+ wrap: PropTypes.bool,
63
+ inline: PropTypes.bool,
64
+ as: PropTypes.string
54
65
  }
@@ -1,19 +1,26 @@
1
- import React from 'react'
2
- import PropTypes from 'prop-types'
3
- import {CopyToClipboard as Copy} from 'react-copy-to-clipboard'
4
-
5
- export const CopyToClipboard = React.memo(function CopyToClipboard({text, children, title}) {
6
- return <Copy text={text}>
7
- {children ? children : <a href="#" className="icon-copy active-icon" title={title}/>}
8
- </Copy>
9
- })
10
-
11
- CopyToClipboard.defaultProps = {
12
- title: 'Copy to clipboard'
13
- }
14
-
15
- CopyToClipboard.propTypes = {
16
- text: PropTypes.string.isRequired,
17
- title: PropTypes.string,
18
- children: PropTypes.any
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import {CopyToClipboard as Copy} from 'react-copy-to-clipboard'
4
+
5
+ /**
6
+ * Copy-to-clipboard wrapper; shows a default copy icon if no children are provided
7
+ * @param {Object} props
8
+ * @param {string} props.text - Text to copy to clipboard
9
+ * @param {*} [props.children] - Custom trigger element (defaults to a copy icon)
10
+ * @param {string} [props.title='Copy to clipboard'] - Tooltip text
11
+ */
12
+ export const CopyToClipboard = React.memo(function CopyToClipboard({text, children, title}) {
13
+ return <Copy text={text}>
14
+ {children ? children : <a href="#" className="icon-copy active-icon" title={title}/>}
15
+ </Copy>
16
+ })
17
+
18
+ CopyToClipboard.defaultProps = {
19
+ title: 'Copy to clipboard'
20
+ }
21
+
22
+ CopyToClipboard.propTypes = {
23
+ text: PropTypes.string.isRequired,
24
+ title: PropTypes.string,
25
+ children: PropTypes.any
19
26
  }
@@ -1,16 +1,21 @@
1
- import React, {useEffect, useRef, useState} from 'react'
2
- import PropTypes from 'prop-types'
3
-
4
- export const InlineProgress = React.memo(function InlineProgress({dots = 5}) {
5
- const [progress, setProgress] = useState(0),
6
- intervalRef = useRef(0)
7
- useEffect(() => {
8
- intervalRef.current = setInterval(() => setProgress(p => ++p), 500)
9
- return () => clearInterval(intervalRef.current)
10
- }, [])
11
- return <span>{'.'.repeat(progress % (dots + 1))}</span>
12
- })
13
-
14
- InlineProgress.propTypes = {
15
- dots: PropTypes.number
1
+ import React, {useEffect, useRef, useState} from 'react'
2
+ import PropTypes from 'prop-types'
3
+
4
+ /**
5
+ * Animated inline progress indicator that cycles through dots
6
+ * @param {Object} props
7
+ * @param {number} [props.dots=5] - Maximum number of dots to display
8
+ */
9
+ export const InlineProgress = React.memo(function InlineProgress({dots = 5}) {
10
+ const [progress, setProgress] = useState(0),
11
+ intervalRef = useRef(0)
12
+ useEffect(() => {
13
+ intervalRef.current = setInterval(() => setProgress(p => ++p), 500)
14
+ return () => clearInterval(intervalRef.current)
15
+ }, [])
16
+ return <span>{'.'.repeat(progress % (dots + 1))}</span>
17
+ })
18
+
19
+ InlineProgress.propTypes = {
20
+ dots: PropTypes.number
16
21
  }
@@ -1,35 +1,35 @@
1
- import React, {useMemo, useRef} from 'react'
2
- import {QRCodeCanvas} from 'qrcode.react'
3
-
4
- /**
5
- * QrCode renderer
6
- * @param {String} value - Value to encode
7
- * @param {String} [caption] - Additional caption under QR code
8
- * @param {Number} [size] - Width|height fo the rendered QR code image
9
- * @param {String} [embeddedImage] - Optional logo to render in the center of QR code
10
- * @param {Number} [embeddedSize] - Embedded logo size (by default 10% of QR code size)
11
- * @return {JSX.Element}
12
- */
13
- export const QrCode = React.memo(function QrCode({value, caption, size = 320, embeddedImage, embeddedSize}) {
14
- const foreground = useMemo(() => getComputedStyle(document.documentElement).getPropertyValue('--color-primary'))
15
- const containerRef = useRef()
16
- return <div className="text-center" ref={containerRef}>
17
- <QRCodeCanvas value={value} size={256} level="Q" includeMargin imageSettings={embedImage(embeddedImage, embeddedSize, size)}
18
- fgColor={foreground} style={{width: size + 'px', height: size + 'px', display: 'block', margin: 'auto'}}/>
19
- {!!caption && <div className="text-small dimmed condensed word-break">{caption}</div>}
20
- </div>
21
- })
22
-
23
- function embedImage(src, size, qrSize) {
24
- if (!src)
25
- return undefined
26
- if (!size) {
27
- size = qrSize * 0.1
28
- }
29
- return {
30
- src,
31
- height: size,
32
- width: size,
33
- excavate: true
34
- }
1
+ import React, {useMemo, useRef} from 'react'
2
+ import {QRCodeCanvas} from 'qrcode.react'
3
+
4
+ /**
5
+ * QrCode renderer
6
+ * @param {string} value - Value to encode
7
+ * @param {string} [caption] - Additional caption under QR code
8
+ * @param {number} [size] - Width|height fo the rendered QR code image
9
+ * @param {string} [embeddedImage] - Optional logo to render in the center of QR code
10
+ * @param {number} [embeddedSize] - Embedded logo size (by default 10% of QR code size)
11
+ * @return {JSX.Element}
12
+ */
13
+ export const QrCode = React.memo(function QrCode({value, caption, size = 320, embeddedImage, embeddedSize}) {
14
+ const foreground = useMemo(() => getComputedStyle(document.documentElement).getPropertyValue('--color-primary'))
15
+ const containerRef = useRef()
16
+ return <div className="text-center" ref={containerRef}>
17
+ <QRCodeCanvas value={value} size={256} level="Q" includeMargin imageSettings={embedImage(embeddedImage, embeddedSize, size)}
18
+ fgColor={foreground} style={{width: size + 'px', height: size + 'px', display: 'block', margin: 'auto'}}/>
19
+ {!!caption && <div className="text-small dimmed condensed word-break">{caption}</div>}
20
+ </div>
21
+ })
22
+
23
+ function embedImage(src, size, qrSize) {
24
+ if (!src)
25
+ return undefined
26
+ if (!size) {
27
+ size = qrSize * 0.1
28
+ }
29
+ return {
30
+ src,
31
+ height: size,
32
+ width: size,
33
+ excavate: true
34
+ }
35
35
  }
@@ -1,21 +1,21 @@
1
- import {useState, useEffect} from 'react'
2
- import {throttle} from 'throttle-debounce'
3
-
4
- /**
5
- * Measures window client width
6
- * @return {Number}
7
- */
8
- export function useWindowWidth() {
9
- const [width, setWidth] = useState(window.innerWidth)
10
-
11
- const onResize = throttle(100, function () {
12
- setWidth(window.innerWidth)
13
- })
14
-
15
- useEffect(() => {
16
- window.addEventListener('resize', onResize)
17
- return () => window.removeEventListener('resize', onResize)
18
- }, [])
19
-
20
- return width
1
+ import {useState, useEffect} from 'react'
2
+ import {throttle} from 'throttle-debounce'
3
+
4
+ /**
5
+ * Measures window client width
6
+ * @return {number}
7
+ */
8
+ export function useWindowWidth() {
9
+ const [width, setWidth] = useState(window.innerWidth)
10
+
11
+ const onResize = throttle(100, function () {
12
+ setWidth(window.innerWidth)
13
+ })
14
+
15
+ useEffect(() => {
16
+ window.addEventListener('resize', onResize)
17
+ return () => window.removeEventListener('resize', onResize)
18
+ }, [])
19
+
20
+ return width
21
21
  }
@@ -1,40 +1,53 @@
1
- import React, {useCallback, useEffect, useState} from 'react'
2
- import PropTypes from 'prop-types'
3
- import cn from 'classnames'
4
- import './spoiler.scss'
5
-
6
- export const Spoiler = React.memo(function Spoiler({expanded, showMore = 'Show more', showLess = 'Show less', onChange, className, micro, style, active, children}) {
7
- const [expandedState, setExpandedState] = useState(expanded || false)
8
- useEffect(() => {
9
- setExpandedState(expanded)
10
- }, [expanded])
11
-
12
- const toggle = useCallback(() => {
13
- setExpandedState(prevState => {
14
- const newState = !prevState
15
- setExpandedState(newState)
16
- onChange && onChange({expanded: newState})
17
- })
18
- }, [onChange])
19
-
20
- const text = expandedState ? showLess : showMore
21
- return <>
22
- <span className={cn('spoiler', className)}>
23
- <a href="#" className={!active ? 'dimmed' : undefined} title={micro ? text : undefined} style={style} onClick={toggle}>
24
- {!micro && <span className="spoiler-text">{text}</span>}
25
- <i className={`icon ${expandedState ? 'icon-less' : 'icon-more'}`}/>
26
- </a>
27
- </span>
28
- {expandedState ? children : null}
29
- </>
30
- })
31
-
32
- Spoiler.propTypes = {
33
- expanded: PropTypes.bool,
34
- micro: PropTypes.bool,
35
- onChange: PropTypes.func,
36
- showMore: PropTypes.string,
37
- showLess: PropTypes.string,
38
- active: PropTypes.bool,
39
- style: PropTypes.object
1
+ import React, {useCallback, useEffect, useState} from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import cn from 'classnames'
4
+ import './spoiler.scss'
5
+
6
+ /**
7
+ * Expandable/collapsible content toggle
8
+ * @param {Object} props
9
+ * @param {boolean} [props.expanded] - Whether the content is initially expanded
10
+ * @param {string} [props.showMore='Show more'] - Label for the expand action
11
+ * @param {string} [props.showLess='Show less'] - Label for the collapse action
12
+ * @param {function({expanded: boolean}): void} [props.onChange] - Callback invoked on toggle
13
+ * @param {string} [props.className] - Additional CSS classes
14
+ * @param {boolean} [props.micro] - Show only an icon without text label
15
+ * @param {Object} [props.style] - Inline styles
16
+ * @param {boolean} [props.active] - Highlight the toggle link
17
+ * @param {*} [props.children] - Content revealed when expanded
18
+ */
19
+ export const Spoiler = React.memo(function Spoiler({expanded, showMore = 'Show more', showLess = 'Show less', onChange, className, micro, style, active, children}) {
20
+ const [expandedState, setExpandedState] = useState(expanded || false)
21
+ useEffect(() => {
22
+ setExpandedState(expanded)
23
+ }, [expanded])
24
+
25
+ const toggle = useCallback(() => {
26
+ setExpandedState(prevState => {
27
+ const newState = !prevState
28
+ setExpandedState(newState)
29
+ onChange && onChange({expanded: newState})
30
+ })
31
+ }, [onChange])
32
+
33
+ const text = expandedState ? showLess : showMore
34
+ return <>
35
+ <span className={cn('spoiler', className)}>
36
+ <a href="#" className={!active ? 'dimmed' : undefined} title={micro ? text : undefined} style={style} onClick={toggle}>
37
+ {!micro && <span className="spoiler-text">{text}</span>}
38
+ <i className={`icon ${expandedState ? 'icon-less' : 'icon-more'}`}/>
39
+ </a>
40
+ </span>
41
+ {expandedState ? children : null}
42
+ </>
43
+ })
44
+
45
+ Spoiler.propTypes = {
46
+ expanded: PropTypes.bool,
47
+ micro: PropTypes.bool,
48
+ onChange: PropTypes.func,
49
+ showMore: PropTypes.string,
50
+ showLess: PropTypes.string,
51
+ active: PropTypes.bool,
52
+ style: PropTypes.object
40
53
  }
@@ -1,11 +1,14 @@
1
- import React from 'react'
2
- import {useTheme} from '../state/theme'
3
-
4
- export const ThemeSelector = React.memo(function ThemeSelector() {
5
- const [theme, setTheme] = useTheme()
6
- return <a href="#" onClick={() => setTheme(current => current === 'day' ? 'night' : 'day')}>
7
- {theme === 'day' ?
8
- <><i className="icon icon-night"/> Dark theme</> :
9
- <><i className="icon icon-day"/> Light theme</>}
10
- </a>
1
+ import React from 'react'
2
+ import {useTheme} from '../state/theme'
3
+
4
+ /**
5
+ * Theme toggle button that switches between day (light) and night (dark) themes
6
+ */
7
+ export const ThemeSelector = React.memo(function ThemeSelector() {
8
+ const [theme, setTheme] = useTheme()
9
+ return <a href="#" onClick={() => setTheme(current => current === 'day' ? 'night' : 'day')}>
10
+ {theme === 'day' ?
11
+ <><i className="icon icon-night"/> Dark theme</> :
12
+ <><i className="icon icon-day"/> Light theme</>}
13
+ </a>
11
14
  })
@@ -1,27 +1,28 @@
1
- import {getCurrentStellarNetwork} from '../state/stellar-network-hooks'
2
-
3
- /**
4
- * @param {'account'|'asset'|'ledger'|'tx'|'op'|'offer'|'contract'|'liquidity-pool'|'claimable-balance'} type
5
- * @param {String|Number} id
6
- * @param {String} [network]
7
- * @return {String}
8
- */
9
- export function formatExplorerLink(type, id, network = null) {
10
- if (typeof window.explorerLinkFormatter === 'function') {
11
- const link = window.explorerLinkFormatter(type, id, network)
12
- if (link)
13
- return link
14
- }
15
- const segments = [
16
- explorerFrontendOrigin !== window.origin ? explorerFrontendOrigin : '',
17
- 'explorer',
18
- network || getCurrentStellarNetwork()
19
- ]
20
- if (type) {
21
- segments.push(type)
22
- }
23
- if (id) {
24
- segments.push(id)
25
- }
26
- return segments.join('/')
1
+ import {getCurrentStellarNetwork} from '../state/stellar-network-hooks'
2
+
3
+ /**
4
+ * Build URL path to StellarExpert explorer page for given ledger entry
5
+ * @param {'account'|'asset'|'ledger'|'tx'|'op'|'offer'|'contract'|'liquidity-pool'|'claimable-balance'} type
6
+ * @param {string|number} id
7
+ * @param {string} [network]
8
+ * @return {string}
9
+ */
10
+ export function formatExplorerLink(type, id, network = null) {
11
+ if (typeof window.explorerLinkFormatter === 'function') {
12
+ const link = window.explorerLinkFormatter(type, id, network)
13
+ if (link)
14
+ return link
15
+ }
16
+ const segments = [
17
+ explorerFrontendOrigin !== window.origin ? explorerFrontendOrigin : '',
18
+ 'explorer',
19
+ network || getCurrentStellarNetwork()
20
+ ]
21
+ if (type) {
22
+ segments.push(type)
23
+ }
24
+ if (id) {
25
+ segments.push(id)
26
+ }
27
+ return segments.join('/')
27
28
  }