@stellar-expert/ui-framework 1.9.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 (124) hide show
  1. package/.npmrc +1 -0
  2. package/LICENSE +21 -0
  3. package/README.md +3 -0
  4. package/account/account-address.js +127 -0
  5. package/account/account-address.scss +20 -0
  6. package/account/available-balance.js +21 -0
  7. package/account/identicon.js +91 -0
  8. package/account/identicon.scss +6 -0
  9. package/account/signer-key.js +65 -0
  10. package/api/api-cache.js +5 -0
  11. package/api/explorer-api-call.js +37 -0
  12. package/api/explorer-api-hooks.js +202 -0
  13. package/api/explorer-api-paginated-list-hooks.js +439 -0
  14. package/api/explorer-batch-info-loader.js +85 -0
  15. package/api/explorer-tx-api.js +29 -0
  16. package/asset/amount.js +53 -0
  17. package/asset/asset-icon.js +34 -0
  18. package/asset/asset-issuer.js +22 -0
  19. package/asset/asset-link.js +93 -0
  20. package/asset/asset-link.scss +26 -0
  21. package/asset/asset-list-hooks.js +60 -0
  22. package/asset/asset-meta-hooks.js +78 -0
  23. package/asset/asset-selector.js +72 -0
  24. package/asset/asset-selector.scss +41 -0
  25. package/basic-styles/base.scss +53 -0
  26. package/basic-styles/form.scss +90 -0
  27. package/basic-styles/grid.scss +388 -0
  28. package/basic-styles/link.scss +17 -0
  29. package/basic-styles/lists.scss +68 -0
  30. package/basic-styles/loader.scss +22 -0
  31. package/basic-styles/responsive.scss +29 -0
  32. package/basic-styles/scrollbars.scss +19 -0
  33. package/basic-styles/spacing.scss +50 -0
  34. package/basic-styles/table.scss +104 -0
  35. package/basic-styles/themes.scss +59 -0
  36. package/basic-styles/typography.scss +191 -0
  37. package/basic-styles/variables.scss +26 -0
  38. package/claimable-balance/claimable-balance-claimants.js +12 -0
  39. package/contract/sc-val.js +70 -0
  40. package/contract/wasm-section-parser.js +159 -0
  41. package/contract/wasm-section-reader.js +79 -0
  42. package/contract/xdr-reader.js +148 -0
  43. package/controls/active-icon.scss +23 -0
  44. package/controls/button-group.js +20 -0
  45. package/controls/button-group.scss +48 -0
  46. package/controls/button.js +74 -0
  47. package/controls/button.scss +148 -0
  48. package/controls/code-block.js +35 -0
  49. package/controls/code-block.scss +74 -0
  50. package/controls/dropdown.js +281 -0
  51. package/controls/dropdown.scss +161 -0
  52. package/controls/external-link.js +5 -0
  53. package/controls/info-tooltip.js +17 -0
  54. package/controls/info-tooltip.scss +4 -0
  55. package/controls/slider.js +20 -0
  56. package/controls/slider.scss +22 -0
  57. package/controls/tabs.js +94 -0
  58. package/controls/tabs.scss +73 -0
  59. package/controls/tooltip.js +214 -0
  60. package/controls/tooltip.scss +97 -0
  61. package/controls/update-highlighter.js +28 -0
  62. package/controls/update-highlighter.scss +10 -0
  63. package/date/date-selector.js +44 -0
  64. package/date/elapsed-time.js +22 -0
  65. package/date/utc-timestamp.js +23 -0
  66. package/dex/price-dynamic.js +45 -0
  67. package/dex/price-dynamic.scss +36 -0
  68. package/directory/directory-hooks.js +89 -0
  69. package/effect/effect-description.js +289 -0
  70. package/effect/op-effects-view.js +11 -0
  71. package/effect/op-effects.scss +8 -0
  72. package/errors/error-boundary.js +98 -0
  73. package/fonts/font.scss +23 -0
  74. package/fonts/icons/config.json +1858 -0
  75. package/fonts/icons/icons-embedded.scss +644 -0
  76. package/horizon/horizon-account-helpers.js +104 -0
  77. package/horizon/horizon-client-helpers.js +52 -0
  78. package/horizon/horizon-ledger-helpers.js +36 -0
  79. package/horizon/horizon-orderbook-helpers.js +14 -0
  80. package/horizon/horizon-trades-helper.js +89 -0
  81. package/horizon/horizon-transaction-helpers.js +36 -0
  82. package/index.js +85 -0
  83. package/index.scss +16 -0
  84. package/interaction/autofocus.js +9 -0
  85. package/interaction/block-select.js +54 -0
  86. package/interaction/block-select.scss +24 -0
  87. package/interaction/copy-to-clipboard.js +19 -0
  88. package/interaction/inline-progress.js +16 -0
  89. package/interaction/qr-code.js +45 -0
  90. package/interaction/responsive.js +21 -0
  91. package/interaction/spoiler.js +40 -0
  92. package/interaction/spoiler.scss +9 -0
  93. package/interaction/theme-selector.js +11 -0
  94. package/ledger/ledger-entry-href-formatter.js +22 -0
  95. package/ledger/ledger-entry-link.js +59 -0
  96. package/meta/page-meta-tags.js +229 -0
  97. package/module/dynamic-module.js +48 -0
  98. package/package.json +41 -0
  99. package/state/on-screen-hooks.js +23 -0
  100. package/state/page-visibility-helpers.js +17 -0
  101. package/state/page-visibility-hooks.js +12 -0
  102. package/state/screen-orientation-hooks.js +16 -0
  103. package/state/state-hooks.js +77 -0
  104. package/state/stellar-network-hooks.js +45 -0
  105. package/state/theme.js +29 -0
  106. package/stellar/key-type.js +92 -0
  107. package/stellar/ledger-generic-id.js +40 -0
  108. package/stellar/signature-hint-utils.js +65 -0
  109. package/toast/toast-notification-instance.js +43 -0
  110. package/toast/toast-notification.js +46 -0
  111. package/toast/toast-notifications-block.js +55 -0
  112. package/toast/toast-notifications.scss +166 -0
  113. package/tx/op-accounting-changes.js +27 -0
  114. package/tx/op-description-view.js +925 -0
  115. package/tx/op-description.scss +98 -0
  116. package/tx/op-icon.js +93 -0
  117. package/tx/parser/op-balance-changes.js +62 -0
  118. package/tx/parser/op-descriptor.js +49 -0
  119. package/tx/parser/tx-details-parser.js +75 -0
  120. package/tx/parser/tx-matcher.js +372 -0
  121. package/tx/parser/type-filter-matcher.js +127 -0
  122. package/tx/tx-fee-effect.js +14 -0
  123. package/tx/tx-list-hooks.js +19 -0
  124. package/tx/tx-operations-list.js +105 -0
@@ -0,0 +1,93 @@
1
+ import React from 'react'
2
+ import cn from 'classnames'
3
+ import {shortenString} from '@stellar-expert/formatter'
4
+ import {AssetDescriptor} from '@stellar-expert/asset-descriptor'
5
+ import {useStellarNetwork} from '../state/stellar-network-hooks'
6
+ import {formatExplorerLink} from '../ledger/ledger-entry-href-formatter'
7
+ import {AccountAddress} from '../account/account-address'
8
+ import {useAssetMeta} from './asset-meta-hooks'
9
+ import {AssetIcon} from './asset-icon'
10
+ import {AssetIssuer} from './asset-issuer'
11
+ import './asset-link.scss'
12
+
13
+ /**
14
+ * Explorer asset link
15
+ * @param {String|AssetDescriptor|Asset} asset - Asset name/descriptor
16
+ * @param {Boolean|String} [link] - Reference link
17
+ * @param {Boolean} [issuer] - Whether to show asset issuer
18
+ * @param {Boolean} [icon] - Wheter to show asset icon
19
+ * @param {String} [className] - Optional CSS class name
20
+ * @param {{}} [style] - Optional CSS style
21
+ * @param {*} [children] - Optional inner link text
22
+ * @constructor
23
+ */
24
+ export const AssetLink = React.memo(function AssetLink({asset, link, issuer, icon, className, style, children: innerText}) {
25
+ if (!(asset instanceof AssetDescriptor)) {
26
+ asset = AssetDescriptor.parse(asset)
27
+ }
28
+ useStellarNetwork()
29
+ const meta = useAssetMeta(asset)
30
+
31
+ if (!asset)
32
+ return null
33
+
34
+ let children = innerText
35
+ if (!innerText) {
36
+ if (asset.poolId) {
37
+ if (meta) {
38
+ const [assetA, assetB] = meta.assets.map(a => AssetDescriptor.parse(a.asset))
39
+ children = <span title={'Liquidity pool ' + asset.poolId} className="nowrap">
40
+ <span>
41
+ <AssetWarningStatus meta={assetA}/>
42
+ {icon !== false && <AssetIcon asset={assetA}/>}
43
+ {assetA.code}
44
+ {issuer === true && <AssetIssuer asset={assetA}/>}
45
+ </span>
46
+ <span style={{fontSize: '0.7em'}}>&nbsp;<i className="icon icon-plus dimmed"/>&nbsp;</span>
47
+ <span>
48
+ <AssetWarningStatus meta={assetB}/>
49
+ {icon !== false && <AssetIcon asset={assetB}/>}
50
+ {assetB.code}
51
+ {issuer === true && <AssetIssuer asset={assetB}/>}
52
+ </span>
53
+ </span>
54
+ } else {
55
+ children = <>{shortenString(asset.poolId)}</>
56
+ }
57
+ } else {
58
+ children = <>
59
+ <AssetWarningStatus meta={meta}/>
60
+ {icon !== false && <AssetIcon asset={asset}/>}
61
+ {!!asset.code && asset.code}
62
+ {!!asset.isContract && <AccountAddress account={asset.contract} chars={8} link={false} icon={false}/>}
63
+ {issuer !== false && <AssetIssuer asset={asset}/>}
64
+ </>
65
+ }
66
+ }
67
+
68
+ const props = {
69
+ 'aria-label': asset.toString(),
70
+ className: cn('asset-link', className),
71
+ style,
72
+ children
73
+ }
74
+ if (link === false)
75
+ return <span {...props}/>
76
+ if (typeof link === 'string') {
77
+ props.href = link
78
+ } else {
79
+ props.href = asset.poolId ?
80
+ formatExplorerLink('liquidity-pool', asset.poolId) :
81
+ formatExplorerLink('asset', asset.toString())
82
+ if (window.origin !== explorerFrontendOrigin) {
83
+ props.target = '_blank'
84
+ }
85
+ }
86
+ return <a {...props}/>
87
+ })
88
+
89
+ function AssetWarningStatus({meta}) {
90
+ if (!meta?.unsafe)
91
+ return null
92
+ return <i className="icon icon-warning color-warning" title="Warning: asset reported as unsafe"/>
93
+ }
@@ -0,0 +1,26 @@
1
+ .asset-link {
2
+ white-space: nowrap;
3
+
4
+ .asset-issuer {
5
+ color: var(--color-dimmed);
6
+ font-size: 0.65em;
7
+ padding-left: 0.1em;
8
+ }
9
+
10
+ .asset-icon {
11
+ display: inline-block;
12
+ color: var(--color-text);
13
+ width: 1.1em;
14
+ height: 1.1em;
15
+ vertical-align: middle;
16
+ margin-right: .1em;
17
+ line-height: 1.1;
18
+ background: transparent center center no-repeat;
19
+ background-size: contain;
20
+ margin-top: -0.2em;
21
+
22
+ &.icon {
23
+ margin-top: 0;
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,60 @@
1
+ import React, {useState} from 'react'
2
+ import {throttle} from 'throttle-debounce'
3
+ import equal from 'react-fast-compare'
4
+ import {stringifyQuery} from '@stellar-expert/navigation'
5
+ import {fetchExplorerApi} from '../api/explorer-api-call'
6
+ import {useDeepEffect} from '../state/state-hooks'
7
+ import {getCurrentStellarNetwork} from '../state/stellar-network-hooks'
8
+
9
+ const defaults = {
10
+ limit: 20,
11
+ order: 'desc',
12
+ sort: 'rating'
13
+ }
14
+
15
+ function getCurrentCursor(data) {
16
+ if (!data || !data.length) return undefined
17
+ return data[data.length - 1].paging_token
18
+ }
19
+
20
+ /**
21
+ *
22
+ * @param {Object} params
23
+ * @return {{assets: Array<Object>, loadPage: Function, loading: Boolean}}
24
+ */
25
+ export function useAssetList(params) {
26
+ const [loading, setLoading] = useState(false),
27
+ [assets, setAssets] = useState({data: [], params}),
28
+ loadPage = throttle(1000, function () {
29
+ if (loading) return
30
+ const requestParams = {
31
+ ...defaults, ...params
32
+ }
33
+ if (equal(params, assets.params)) {
34
+ requestParams.cursor = getCurrentCursor(assets.data)
35
+ }
36
+ const endpoint = getCurrentStellarNetwork() + '/asset' + stringifyQuery(requestParams)
37
+ setLoading(true)
38
+ fetchExplorerApi(endpoint)
39
+ .then(res => {
40
+ const {records} = res._embedded
41
+ setAssets(existing => {
42
+ let data = records
43
+ if (equal(params, existing.params)) {
44
+ data = [...existing.data, ...records]
45
+ }
46
+ return {data, params}
47
+ })
48
+ setLoading(false)
49
+ })
50
+ .catch(e => console.error(e))
51
+ .finally(() => {
52
+ setLoading(false)
53
+ })
54
+ })
55
+ useDeepEffect(() => {
56
+ loadPage()
57
+ }, [params])
58
+
59
+ return {assets: assets.data, loadPage, loading}
60
+ }
@@ -0,0 +1,78 @@
1
+ import {useEffect, useState} from 'react'
2
+ import {AssetDescriptor} from '@stellar-expert/asset-descriptor'
3
+ import {stringifyQuery} from '@stellar-expert/navigation'
4
+ import {InMemoryClientCache} from '@stellar-expert/client-cache'
5
+ import {fetchExplorerApi} from '../api/explorer-api-call'
6
+ import {ExplorerBatchInfoLoader} from '../api/explorer-batch-info-loader'
7
+ import {getCurrentStellarNetwork} from '../state/stellar-network-hooks'
8
+
9
+ /**
10
+ * @typedef AssetBasicTomlInfo
11
+ * @property {String} name
12
+ * @property {String} orgName
13
+ * @property {String} image
14
+ * @property {Number} decimals
15
+ */
16
+
17
+ /**
18
+ * @typedef AssetMeta
19
+ * @property {String} name
20
+ * @property {String} domain
21
+ * @property {AssetBasicTomlInfo} toml_info
22
+ */
23
+
24
+ const cache = new InMemoryClientCache()
25
+
26
+ const loader = new ExplorerBatchInfoLoader(batch => {
27
+ return fetchExplorerApi(getCurrentStellarNetwork() + '/asset/meta' + stringifyQuery({asset: batch, origin: window.location.origin}))
28
+ }, entry => {
29
+ cache.set(entry.name, entry)
30
+ return {
31
+ key: entry.name,
32
+ info: entry
33
+ }
34
+ })
35
+
36
+ function retrieveFromCache(asset) {
37
+ //try to load from the shared cache
38
+ const cachedEntry = cache.get(asset)
39
+ if (cachedEntry && !cachedEntry.isStale) {
40
+ if (!cachedEntry.isExpired)
41
+ return cachedEntry.data//everything is up to date - no need to re-fetch
42
+ }
43
+ }
44
+
45
+ function normalizeAssetName(asset) {
46
+ if (!asset)
47
+ return null
48
+ if (typeof asset === 'string' && asset.length === 56)
49
+ return asset //contract id
50
+ return AssetDescriptor.parse(asset).toFQAN()
51
+ }
52
+
53
+ /**
54
+ *
55
+ * @param {AssetDescriptor|String} asset
56
+ * @return {AssetMeta}
57
+ */
58
+ export function useAssetMeta(asset) {
59
+ asset = normalizeAssetName(asset)
60
+ const [assetInfo, setAssetInfo] = useState(retrieveFromCache(asset))
61
+ useEffect(() => {
62
+ if (!asset)
63
+ return
64
+ const cached = retrieveFromCache(asset)
65
+ setAssetInfo(cached)
66
+ if (cached)
67
+ return
68
+ let unloaded = false
69
+ //load from the server
70
+ loader.loadEntry(asset)
71
+ .then(a => !unloaded && setAssetInfo(a))
72
+ return () => {
73
+ unloaded = true
74
+ }
75
+ }, [asset])
76
+
77
+ return assetInfo
78
+ }
@@ -0,0 +1,72 @@
1
+ import React, {useCallback, useRef, useState} from 'react'
2
+ import {Dropdown} from '../controls/dropdown'
3
+ import {AssetLink} from './asset-link'
4
+ import {useAssetList} from './asset-list-hooks'
5
+ import './asset-selector.scss'
6
+
7
+ /**
8
+ * @param {AssetSelectorOnAssetChanged} onChange - On asset selected callback
9
+ * @param {String} [value] - Selected asset
10
+ * @param {String[]} [predefinedAssets] - Optional lists of predefined assets that should be shown at the top of the dropdown list
11
+ * @param {Boolean} [restricted] - If set, the selector is limited to the predefined assets list only
12
+ * @param {String} [title] - Dropdown selector title
13
+ * @param {String} [expanded] - Expanded by default
14
+ * @return {JSX.Element}
15
+ * @constructor
16
+ */
17
+ export function AssetSelector({value, predefinedAssets, onChange, restricted, title, expanded}) {
18
+ const [search, setSearch] = useState('')
19
+ const searchRef = useRef()
20
+ const options = []
21
+
22
+ const focusSearch = useCallback(() => {
23
+ setTimeout(() => searchRef.current?.focus(), 200)
24
+ })
25
+
26
+ if (predefinedAssets) {
27
+ for (const asset of predefinedAssets) {
28
+ options.push({
29
+ value: asset,
30
+ title: <AssetLink link={false} asset={asset}/>,
31
+ hidden: search && !asset.split('-')[0].toLowerCase().includes(search.toLowerCase())
32
+ })
33
+ }
34
+ }
35
+
36
+ let loadNextPage
37
+ if (!restricted) {
38
+ const {assets, loadPage, loading} = useAssetList({search: search?.trim() || undefined})
39
+ for (let {asset} of assets) {
40
+ if (!predefinedAssets || !predefinedAssets.includes(asset)) {
41
+ options.push({value: asset, title: <AssetLink link={false} asset={asset}/>})
42
+ }
43
+ }
44
+ if (!options.filter(opt => !opt.hidden).length) {
45
+ if (loading) {
46
+ options.push({value: '...', disabled: true, title: <div className="loader"/>})
47
+ } else {
48
+ options.push({
49
+ value: 'no',
50
+ disabled: true,
51
+ title: <div className="dimmed text-center text-small">(not found)</div>
52
+ })
53
+ }
54
+ }
55
+ loadNextPage = loadPage
56
+ }
57
+
58
+ return <Dropdown solo className="asset-selector" options={options} value={value} onOpen={focusSearch} title={title} expanded={expanded}
59
+ showToggle={!title} onChange={onChange} onScroll={e => e.rel === 'bottom' && loadNextPage?.call(this)} header={<>
60
+ <h3>Select an asset</h3>
61
+ <div className="relative">
62
+ <input type="text" value={search} ref={searchRef} onChange={e => setSearch(e.target.value)}
63
+ placeholder="Search by asset code or website"/>
64
+ <i className="icon-search dimmed"/>
65
+ </div>
66
+ </>}/>
67
+ }
68
+
69
+ /**
70
+ * @callback AssetSelectorOnAssetChanged
71
+ * @param {String} value
72
+ */
@@ -0,0 +1,41 @@
1
+ .asset-selector {
2
+ .dd-header {
3
+ .asset-issuer {
4
+ display: none;
5
+ }
6
+ }
7
+
8
+ .dd-list {
9
+ width: 22em;
10
+ max-width: 94vw;
11
+ }
12
+
13
+ .dd-list-header {
14
+ min-width: 16em;
15
+
16
+ h3 {
17
+ margin: 0;
18
+ }
19
+
20
+ .icon-search {
21
+ position: absolute;
22
+ right: 0.3em;
23
+ top: 0.5em;
24
+ opacity: 0.5;
25
+ font-size: $font-size-base;
26
+ }
27
+
28
+ input {
29
+ margin-bottom: 0;
30
+
31
+ &:not(:placeholder-shown) + .icon-search {
32
+ display: none;
33
+ }
34
+
35
+ &::placeholder {
36
+ font-size: 0.85em;
37
+ line-height: 1;
38
+ }
39
+ }
40
+ }
41
+ }
@@ -0,0 +1,53 @@
1
+ *,
2
+ *:after,
3
+ *:before {
4
+ box-sizing: border-box;
5
+ }
6
+
7
+ * {
8
+ @include scrollbars(6px, var(--color-dimmed), var(--color-contrast-border));
9
+ }
10
+
11
+ html {
12
+ font-size: 62.5%;
13
+ height: 100%;
14
+ -ms-text-size-adjust: 100%;
15
+ -webkit-text-size-adjust: 100%;
16
+ scroll-behavior: smooth;
17
+ }
18
+
19
+ body {
20
+ color: var(--color-text);
21
+ background: var(--color-bg);
22
+ font-family: $font-face-primary;
23
+ font-size: $font-size-base;
24
+ font-weight: $font-weight-base;
25
+ letter-spacing: 0.01em;
26
+ line-height: $font-line-height-base;
27
+ height: 100%;
28
+ overflow-x: hidden;
29
+ min-width: 320px;
30
+ margin: 0;
31
+ font-smoothing: antialiased;
32
+ -webkit-font-smoothing: antialiased;
33
+ }
34
+
35
+ @include scrollbars(6px, var(--color-dimmed), var(--color-contrast-border));
36
+
37
+ ::selection {
38
+ background-color: transparentize($color-primary, 0.8) !important;
39
+ color: var(--color-text);
40
+ }
41
+
42
+ .page-container {
43
+ min-height: calc(100vh - 8rem);
44
+ display: flex;
45
+ flex-direction: column;
46
+ /*@media (min-width: $responsive-mobile-browser-width) {
47
+ justify-content: center;
48
+ }*/
49
+
50
+ > .no-center {
51
+ margin-bottom: auto;
52
+ }
53
+ }
@@ -0,0 +1,90 @@
1
+ input:not([type=checkbox],[type=radio],[type=range]),
2
+ textarea,
3
+ select,
4
+ .input-like {
5
+ appearance: none;
6
+ background-color: transparent !important;
7
+ border: none !important;
8
+ border-bottom: 1px solid var(--color-contrast-border) !important;
9
+ box-shadow: none;
10
+ box-sizing: inherit;
11
+ height: 4rem;
12
+ font-size: $font-size-base;
13
+ font-family: $font-face-primary;
14
+ color: var(--color-text);
15
+ padding: 0.6rem;
16
+ width: 100%;
17
+ transition: border-bottom-color 0.2s;
18
+
19
+ &:focus {
20
+ background-color: var(--color-alt-bg) !important;
21
+ border-bottom-color: var(--color-highlight) !important;
22
+ outline: 0;
23
+ }
24
+ }
25
+
26
+ input[type=range] {
27
+ height: 2px;
28
+ width: 100%;
29
+ margin: 0;
30
+ filter: grayscale(100%);
31
+ }
32
+
33
+ input[type='checkbox'],
34
+ input[type='radio'] {
35
+ display: inline;
36
+ margin-left: 0;
37
+ filter: grayscale(100%);
38
+ }
39
+
40
+ input[type=checkbox] {
41
+ vertical-align: bottom;
42
+ position: relative;
43
+ top: -0.3em;
44
+ padding: 0;
45
+ margin: 0;
46
+ }
47
+
48
+ input[type='text'].key {
49
+ letter-spacing: -0.05em;
50
+ font-family: $font-face-mono;
51
+ @media (max-width: 519px) {
52
+ font-size: 0.9em;
53
+ }
54
+
55
+ &::placeholder {
56
+ letter-spacing: 0;
57
+ font-size: $font-size-base;
58
+ font-family: $font-face-primary;
59
+ }
60
+ }
61
+
62
+ .input-like {
63
+ min-height: 4rem;
64
+ height: auto;
65
+ }
66
+
67
+ textarea {
68
+ min-height: 4em;
69
+
70
+ &.no-resize {
71
+ resize: none;
72
+ }
73
+ }
74
+
75
+ label,
76
+ legend {
77
+ display: block;
78
+ font-size: $font-size-base;
79
+ margin-bottom: $space-nano;
80
+ }
81
+
82
+ .label-inline {
83
+ display: inline-block;
84
+ font-weight: normal;
85
+ margin-left: $space-nano;
86
+ }
87
+
88
+ ::placeholder {
89
+ color: var(--color-dimmed);
90
+ }