@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.
- package/CLAUDE.md +35 -0
- package/README.md +1125 -3
- package/account/account-address.js +127 -127
- package/account/available-balance.js +22 -22
- package/account/identicon.js +90 -90
- package/account/signer-key.js +65 -64
- package/api/explorer-api-hooks.js +209 -202
- package/api/explorer-api-paginated-list-hooks.js +441 -440
- package/api/explorer-batch-info-loader.js +111 -85
- package/api/explorer-tx-api.js +28 -28
- package/api/ledger-stream.js +15 -0
- package/asset/amount.js +56 -56
- package/asset/asset-icon.js +41 -41
- package/asset/asset-issuer.js +21 -21
- package/asset/asset-link.js +107 -107
- package/asset/asset-list-hooks.js +59 -59
- package/asset/asset-meta-hooks.js +88 -88
- package/asset/asset-selector.js +72 -71
- package/claimable-balance/claimable-balance-claimants.js +23 -18
- package/contract/contract-api.js +15 -0
- package/contract/invocation-info-view.js +10 -2
- package/contract/sc-val.js +131 -107
- package/controls/button-group.js +25 -19
- package/controls/button.js +93 -78
- package/controls/code-block.js +42 -34
- package/controls/dropdown.js +318 -287
- package/controls/external-link.js +10 -4
- package/controls/info-tooltip.js +23 -16
- package/controls/slider.js +29 -19
- package/controls/tabs.js +94 -94
- package/controls/tooltip.js +244 -240
- package/controls/update-highlighter.js +32 -27
- package/date/date-selector.js +56 -54
- package/date/elapsed-time.js +28 -21
- package/dex/price-dynamic.js +53 -44
- package/directory/directory-hooks.js +109 -97
- package/effect/effect-description.js +346 -344
- package/errors/error-boundary.js +110 -97
- package/horizon/horizon-account-helpers.js +104 -104
- package/horizon/horizon-ledger-helpers.js +35 -35
- package/horizon/horizon-trades-helper.js +88 -88
- package/horizon/horizon-transaction-helpers.js +36 -36
- package/index.d.ts +1241 -0
- package/interaction/accordion.js +43 -35
- package/interaction/autofocus.js +13 -9
- package/interaction/block-select.js +64 -53
- package/interaction/copy-to-clipboard.js +25 -18
- package/interaction/inline-progress.js +20 -15
- package/interaction/qr-code.js +34 -34
- package/interaction/responsive.js +20 -20
- package/interaction/spoiler.js +52 -39
- package/interaction/theme-selector.js +13 -10
- package/ledger/ledger-entry-href-formatter.js +27 -26
- package/ledger/ledger-entry-link.js +93 -58
- package/ledger/ledger-info-parser.js +46 -18
- package/meta/page-meta-tags.js +243 -238
- package/module/dynamic-module.js +47 -47
- package/package.json +41 -40
- package/state/on-screen-hooks.js +22 -22
- package/state/page-visibility-helpers.js +29 -16
- package/state/page-visibility-hooks.js +13 -11
- package/state/screen-orientation-hooks.js +19 -15
- package/state/state-hooks.js +76 -76
- package/state/stellar-network-hooks.js +56 -44
- package/state/theme.js +29 -28
- package/stellar/key-type.js +92 -91
- package/stellar/ledger-generic-id.js +39 -39
- package/stellar/signature-hint-utils.js +65 -65
- package/toast/toast-notifications-block.js +59 -59
- package/tx/op-description-view.js +945 -942
- package/tx/op-icon.js +98 -98
- package/tx/parser/op-balance-changes.js +66 -66
- package/tx/parser/op-descriptor.js +51 -48
- package/tx/parser/tx-details-parser.js +81 -81
- package/tx/parser/tx-matcher.js +371 -371
- package/tx/parser/type-filter-matcher.js +126 -126
- package/tx/tx-list-hooks.js +32 -18
- package/tx/tx-operations-list.js +117 -116
package/state/on-screen-hooks.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import {useEffect, useState} from 'react'
|
|
2
|
-
|
|
3
|
-
//TODO consider using a single instance of an IntersectionObserver to track all changes instead of creating many instances
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Hook for determining the visibility of the element inside the scroll context using the Intersection Observer API
|
|
7
|
-
* @param {MutableRefObject<Element>} root - Scroll parent ref
|
|
8
|
-
* @param {
|
|
9
|
-
* @returns {
|
|
10
|
-
*/
|
|
11
|
-
export function useOnScreen(root, rootMargin) {
|
|
12
|
-
const [isVisible, setVisibility] = useState(false)
|
|
13
|
-
|
|
14
|
-
useEffect(() => {
|
|
15
|
-
if (!root.current)
|
|
16
|
-
return
|
|
17
|
-
const observer = new IntersectionObserver(([entry]) => setVisibility(entry.isIntersecting), {rootMargin})
|
|
18
|
-
observer.observe(root.current)
|
|
19
|
-
return () => observer.disconnect()
|
|
20
|
-
}, [root.current])
|
|
21
|
-
|
|
22
|
-
return isVisible
|
|
1
|
+
import {useEffect, useState} from 'react'
|
|
2
|
+
|
|
3
|
+
//TODO consider using a single instance of an IntersectionObserver to track all changes instead of creating many instances
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Hook for determining the visibility of the element inside the scroll context using the Intersection Observer API
|
|
7
|
+
* @param {MutableRefObject<Element>} root - Scroll parent ref
|
|
8
|
+
* @param {string} [rootMargin] - Visibility margin, in pixels or percents (default 0)
|
|
9
|
+
* @returns {boolean} - Whether an element is visible or not
|
|
10
|
+
*/
|
|
11
|
+
export function useOnScreen(root, rootMargin) {
|
|
12
|
+
const [isVisible, setVisibility] = useState(false)
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (!root.current)
|
|
16
|
+
return
|
|
17
|
+
const observer = new IntersectionObserver(([entry]) => setVisibility(entry.isIntersecting), {rootMargin})
|
|
18
|
+
observer.observe(root.current)
|
|
19
|
+
return () => observer.disconnect()
|
|
20
|
+
}, [root.current])
|
|
21
|
+
|
|
22
|
+
return isVisible
|
|
23
23
|
}
|
|
@@ -1,17 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
document.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Whether the Page Visibility API is supported by the browser
|
|
3
|
+
* @type {boolean}
|
|
4
|
+
*/
|
|
5
|
+
export const isDocumentVisibilitySupported = 'hidden' in document
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Check whether the document is currently visible (tab is active)
|
|
9
|
+
* @return {boolean}
|
|
10
|
+
*/
|
|
11
|
+
export function isDocumentVisible() {
|
|
12
|
+
if (!isDocumentVisibilitySupported) return true
|
|
13
|
+
return !document.hidden
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Register a listener for document visibility changes
|
|
18
|
+
* @param {function(boolean): void} listener - Callback invoked with `true` when visible, `false` when hidden
|
|
19
|
+
* @return {function(): void} Unsubscribe function
|
|
20
|
+
*/
|
|
21
|
+
export function addVisibilityChangeListener(listener) {
|
|
22
|
+
function wrappedListener() {
|
|
23
|
+
listener(isDocumentVisible())
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
document.addEventListener('visibilitychange', wrappedListener)
|
|
27
|
+
return function () {
|
|
28
|
+
document.removeEventListener('visibilitychange', wrappedListener)
|
|
29
|
+
}
|
|
17
30
|
}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import {useState, useEffect} from 'react'
|
|
2
|
-
import {isDocumentVisible, addVisibilityChangeListener, isDocumentVisibilitySupported} from './page-visibility-helpers'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
*
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
import {useState, useEffect} from 'react'
|
|
2
|
+
import {isDocumentVisible, addVisibilityChangeListener, isDocumentVisibilitySupported} from './page-visibility-helpers'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Hook that tracks whether the current browser tab/page is visible
|
|
6
|
+
* @returns {boolean} - True if the page is currently visible or false otherwise
|
|
7
|
+
*/
|
|
8
|
+
export function usePageVisibility() {
|
|
9
|
+
const [visible, setVisible] = useState(isDocumentVisible)
|
|
10
|
+
if (!isDocumentVisibilitySupported || !document.addEventListener)
|
|
11
|
+
return true
|
|
12
|
+
useEffect(() => addVisibilityChangeListener(() => setVisible(isDocumentVisible())))
|
|
13
|
+
return visible
|
|
12
14
|
}
|
|
@@ -1,16 +1,20 @@
|
|
|
1
|
-
import {useState, useEffect} from 'react'
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
window.
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
1
|
+
import {useState, useEffect} from 'react'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* React hook that tracks and returns the current screen orientation type
|
|
5
|
+
* @return {OrientationType} Screen orientation (e.g., "portrait-primary", "landscape-primary")
|
|
6
|
+
*/
|
|
7
|
+
export function useScreenOrientation() {
|
|
8
|
+
const [orientation, setOrientation] = useState(window.screen.orientation.type)
|
|
9
|
+
|
|
10
|
+
function onChange() {
|
|
11
|
+
setOrientation(window.screen.orientation.type)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
window.addEventListener('orientationchange', onChange)
|
|
16
|
+
return () => window.removeEventListener('orientationchange', onChange)
|
|
17
|
+
}, [])
|
|
18
|
+
|
|
19
|
+
return orientation
|
|
16
20
|
}
|
package/state/state-hooks.js
CHANGED
|
@@ -1,77 +1,77 @@
|
|
|
1
|
-
import {useState, useEffect, useRef} from 'react'
|
|
2
|
-
import isEqual from 'react-fast-compare'
|
|
3
|
-
|
|
4
|
-
function ensureDependencies(dependencies) {
|
|
5
|
-
if (!(dependencies instanceof Array))
|
|
6
|
-
throw new Error('Parameter dependencies is required for useDependantState hook')
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* React hook that automatically re-inits state when one of the dependencies changed
|
|
11
|
-
* @param {
|
|
12
|
-
* @param {Array} dependencies
|
|
13
|
-
* @param {
|
|
14
|
-
* @returns {[Object,
|
|
15
|
-
*/
|
|
16
|
-
export function useDependantState(stateInitializer, dependencies, finalizer) {
|
|
17
|
-
//ensureDependencies(dependencies)
|
|
18
|
-
const [state, updateState] = useState(function () {
|
|
19
|
-
return typeof stateInitializer === 'function' ? stateInitializer(dependencies) : stateInitializer
|
|
20
|
-
})
|
|
21
|
-
//pin dependencies object to invoke effect update only if dependencies changed
|
|
22
|
-
const pinnedDeps = useRef(dependencies)
|
|
23
|
-
let dependenciesChanged = !isEqual(dependencies, pinnedDeps.current)
|
|
24
|
-
//check that dependencies really changed
|
|
25
|
-
if (dependenciesChanged) {
|
|
26
|
-
pinnedDeps.current = dependencies
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
//effect invokes the initializer each time dependencies changed
|
|
30
|
-
useEffect(function () {
|
|
31
|
-
//check that dependencies really changed
|
|
32
|
-
if (dependenciesChanged) {
|
|
33
|
-
//re-initialize state when any of the dependencies changed
|
|
34
|
-
updateState(typeof stateInitializer === 'function' ? stateInitializer(dependencies, state) : stateInitializer)
|
|
35
|
-
}
|
|
36
|
-
return finalizer || undefined
|
|
37
|
-
}, pinnedDeps.current)
|
|
38
|
-
|
|
39
|
-
return [state, function (newState) {
|
|
40
|
-
//use deep compare - as React used to in good old times
|
|
41
|
-
updateState(current => {
|
|
42
|
-
if (typeof newState === 'function') {
|
|
43
|
-
newState = newState(current)
|
|
44
|
-
}
|
|
45
|
-
if (isEqual(current, newState)) return current
|
|
46
|
-
return newState
|
|
47
|
-
})
|
|
48
|
-
}]
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Simple force update hook.
|
|
53
|
-
* @return {
|
|
54
|
-
*/
|
|
55
|
-
export function useForceUpdate() {
|
|
56
|
-
const [, updateNonce] = useState(0)
|
|
57
|
-
return function () {
|
|
58
|
-
updateNonce(nonce => nonce + 1)
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* React hook that automatically reacts on dependency changes using deep comparison
|
|
64
|
-
* @param {
|
|
65
|
-
* @param {Array} dependencies
|
|
66
|
-
*/
|
|
67
|
-
export function useDeepEffect(effect, dependencies) {
|
|
68
|
-
ensureDependencies(dependencies)
|
|
69
|
-
//pin dependencies object to invoke effect update only if dependencies changed
|
|
70
|
-
const pinnedDeps = useRef([])
|
|
71
|
-
//check that dependencies really changed
|
|
72
|
-
if (!isEqual(dependencies, pinnedDeps.current)) {
|
|
73
|
-
pinnedDeps.current = dependencies
|
|
74
|
-
}
|
|
75
|
-
//effect invokes the initializer each time dependencies changed
|
|
76
|
-
useEffect(effect, pinnedDeps.current)
|
|
1
|
+
import {useState, useEffect, useRef} from 'react'
|
|
2
|
+
import isEqual from 'react-fast-compare'
|
|
3
|
+
|
|
4
|
+
function ensureDependencies(dependencies) {
|
|
5
|
+
if (!(dependencies instanceof Array))
|
|
6
|
+
throw new Error('Parameter dependencies is required for useDependantState hook')
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* React hook that automatically re-inits state when one of the dependencies changed
|
|
11
|
+
* @param {function|any} stateInitializer
|
|
12
|
+
* @param {Array} dependencies
|
|
13
|
+
* @param {function} [finalizer]
|
|
14
|
+
* @returns {[Object, function]}
|
|
15
|
+
*/
|
|
16
|
+
export function useDependantState(stateInitializer, dependencies, finalizer) {
|
|
17
|
+
//ensureDependencies(dependencies)
|
|
18
|
+
const [state, updateState] = useState(function () {
|
|
19
|
+
return typeof stateInitializer === 'function' ? stateInitializer(dependencies) : stateInitializer
|
|
20
|
+
})
|
|
21
|
+
//pin dependencies object to invoke effect update only if dependencies changed
|
|
22
|
+
const pinnedDeps = useRef(dependencies)
|
|
23
|
+
let dependenciesChanged = !isEqual(dependencies, pinnedDeps.current)
|
|
24
|
+
//check that dependencies really changed
|
|
25
|
+
if (dependenciesChanged) {
|
|
26
|
+
pinnedDeps.current = dependencies
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
//effect invokes the initializer each time dependencies changed
|
|
30
|
+
useEffect(function () {
|
|
31
|
+
//check that dependencies really changed
|
|
32
|
+
if (dependenciesChanged) {
|
|
33
|
+
//re-initialize state when any of the dependencies changed
|
|
34
|
+
updateState(typeof stateInitializer === 'function' ? stateInitializer(dependencies, state) : stateInitializer)
|
|
35
|
+
}
|
|
36
|
+
return finalizer || undefined
|
|
37
|
+
}, pinnedDeps.current)
|
|
38
|
+
|
|
39
|
+
return [state, function (newState) {
|
|
40
|
+
//use deep compare - as React used to in good old times
|
|
41
|
+
updateState(current => {
|
|
42
|
+
if (typeof newState === 'function') {
|
|
43
|
+
newState = newState(current)
|
|
44
|
+
}
|
|
45
|
+
if (isEqual(current, newState)) return current
|
|
46
|
+
return newState
|
|
47
|
+
})
|
|
48
|
+
}]
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Simple force update hook.
|
|
53
|
+
* @return {function}
|
|
54
|
+
*/
|
|
55
|
+
export function useForceUpdate() {
|
|
56
|
+
const [, updateNonce] = useState(0)
|
|
57
|
+
return function () {
|
|
58
|
+
updateNonce(nonce => nonce + 1)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* React hook that automatically reacts on dependency changes using deep comparison
|
|
64
|
+
* @param {function} effect
|
|
65
|
+
* @param {Array} dependencies
|
|
66
|
+
*/
|
|
67
|
+
export function useDeepEffect(effect, dependencies) {
|
|
68
|
+
ensureDependencies(dependencies)
|
|
69
|
+
//pin dependencies object to invoke effect update only if dependencies changed
|
|
70
|
+
const pinnedDeps = useRef([])
|
|
71
|
+
//check that dependencies really changed
|
|
72
|
+
if (!isEqual(dependencies, pinnedDeps.current)) {
|
|
73
|
+
pinnedDeps.current = dependencies
|
|
74
|
+
}
|
|
75
|
+
//effect invokes the initializer each time dependencies changed
|
|
76
|
+
useEffect(effect, pinnedDeps.current)
|
|
77
77
|
}
|
|
@@ -1,45 +1,57 @@
|
|
|
1
|
-
import {useState, useEffect} from 'react'
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
*
|
|
19
|
-
* @
|
|
20
|
-
*/
|
|
21
|
-
export function
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
1
|
+
import {useState, useEffect} from 'react'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {'public'|'testnet'} StellarNetwork
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const listeners = []
|
|
8
|
+
let currentNetwork = 'public'
|
|
9
|
+
|
|
10
|
+
setStellarNetwork(currentNetwork)
|
|
11
|
+
|
|
12
|
+
function removeListener(callback) {
|
|
13
|
+
const idx = listeners.indexOf(callback)
|
|
14
|
+
if (~idx) listeners.splice(idx, 1)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Get current Stellar network identifier
|
|
19
|
+
* @return {StellarNetwork}
|
|
20
|
+
*/
|
|
21
|
+
export function getCurrentStellarNetwork() {
|
|
22
|
+
return currentNetwork
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Set current Stellar network
|
|
27
|
+
* @param {StellarNetwork} network
|
|
28
|
+
*/
|
|
29
|
+
export function setStellarNetwork(network) {
|
|
30
|
+
if (currentNetwork === network) return
|
|
31
|
+
currentNetwork = network
|
|
32
|
+
for (const listener of listeners) {
|
|
33
|
+
listener(network)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Subscribe to Stellar network change events
|
|
39
|
+
* @param {function(StellarNetwork): void} onChange - Callback invoked when the network changes
|
|
40
|
+
*/
|
|
41
|
+
export function subscribeToStellarNetworkChange(onChange) {
|
|
42
|
+
removeListener(onChange)
|
|
43
|
+
listeners.push(onChange)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* React hook that returns the current Stellar network and re-renders on network changes
|
|
48
|
+
* @return {StellarNetwork}
|
|
49
|
+
*/
|
|
50
|
+
export function useStellarNetwork() {
|
|
51
|
+
const [state, updateState] = useState(currentNetwork)
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
subscribeToStellarNetworkChange(updateState)
|
|
54
|
+
return () => removeListener(updateState)
|
|
55
|
+
}, [])
|
|
56
|
+
return state
|
|
45
57
|
}
|
package/state/theme.js
CHANGED
|
@@ -1,29 +1,30 @@
|
|
|
1
|
-
import {useState} from 'react'
|
|
2
|
-
|
|
3
|
-
function setGlobalTheme(theme) {
|
|
4
|
-
localStorage.setItem('preferred-color-theme', theme)
|
|
5
|
-
document.documentElement.attributes['data-theme'].value = theme
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
const preferredTheme = localStorage.getItem('preferred-color-theme')
|
|
9
|
-
if (preferredTheme) {
|
|
10
|
-
setGlobalTheme(preferredTheme)
|
|
11
|
-
} else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
12
|
-
setGlobalTheme('night')
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
*
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
1
|
+
import {useState} from 'react'
|
|
2
|
+
|
|
3
|
+
function setGlobalTheme(theme) {
|
|
4
|
+
localStorage.setItem('preferred-color-theme', theme)
|
|
5
|
+
document.documentElement.attributes['data-theme'].value = theme
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const preferredTheme = localStorage.getItem('preferred-color-theme')
|
|
9
|
+
if (preferredTheme) {
|
|
10
|
+
setGlobalTheme(preferredTheme)
|
|
11
|
+
} else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
12
|
+
setGlobalTheme('night')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Hook that manages the current color theme (day/night) with localStorage persistence
|
|
17
|
+
* @returns {[colorTheme: string, setTheme: function]}
|
|
18
|
+
*/
|
|
19
|
+
export function useTheme() {
|
|
20
|
+
const [theme, setTheme] = useState(localStorage.getItem('preferred-color-theme') || 'day')
|
|
21
|
+
return [theme, function (newTheme) {
|
|
22
|
+
setTheme(current => {
|
|
23
|
+
if (typeof newTheme === 'function') {
|
|
24
|
+
newTheme = newTheme(current)
|
|
25
|
+
}
|
|
26
|
+
setGlobalTheme(newTheme)
|
|
27
|
+
return newTheme
|
|
28
|
+
})
|
|
29
|
+
}]
|
|
29
30
|
}
|