feat: toast package (#1073)

This commit is contained in:
Mo 2022-06-07 13:19:45 -05:00 committed by GitHub
parent de94fb69cf
commit 6d0b6e9018
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 524 additions and 23 deletions

3
babel.config.js Normal file
View File

@ -0,0 +1,3 @@
module.exports = {
babelrcRoots: ['.', './packages/*'],
}

View File

@ -11,7 +11,7 @@
"../../node_modules/@standardnotes/config/src/.eslintrc"
],
"parser": "@typescript-eslint/parser",
"ignorePatterns": ["test", "scripts", ".eslintrc", "tsconfig.json", "node_modules"],
"ignorePatterns": ["test", "scripts", ".eslintrc", "tsconfig.json", "node_modules", "*.webpack.*.js"],
"rules": {
/** Style */
"quotes": ["error", "single", { "avoidEscape": true }],

View File

@ -0,0 +1,2 @@
node_modules
dist

3
packages/toast/.eslintrc Normal file
View File

@ -0,0 +1,3 @@
{
"extends": ["../../node_modules/@standardnotes/config/src/.eslintrc"]
}

1
packages/toast/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
dist

View File

@ -0,0 +1,3 @@
{
"extends": "../../node_modules/@standardnotes/config/src/linter.tsconfig.json"
}

View File

@ -0,0 +1,32 @@
{
"name": "@standardnotes/toast",
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"publishConfig": {
"access": "public"
},
"scripts": {
"build": "yarn run tsc",
"clean": "rm -fr dist",
"prestart": "yarn clean",
"start": "tsc -p tsconfig.json --watch",
"prebuild": "yarn clean",
"lint": "eslint ./src"
},
"dependencies": {
"@nanostores/react": "^0.2.0",
"@standardnotes/config": "^2.4.3",
"@standardnotes/icons": "^1.1.7",
"nanoid": "^3.3.4",
"nanostores": "^0.5.12"
},
"devDependencies": {
"@babel/preset-env": "^7.18.0",
"@babel/preset-react": "^7.17.12",
"@babel/preset-typescript": "^7.17.12"
},
"peerDependencies": {
"react": "17"
}
}

View File

@ -0,0 +1,133 @@
import type { Toast as ToastPropType } from './types'
import { CheckCircleFilledIcon, ClearCircleFilledIcon } from '@standardnotes/icons'
import { dismissToast } from './toastStore'
import { ToastType } from './enums'
import { ForwardedRef, forwardRef, RefObject, useEffect } from 'react'
const prefersReducedMotion = () => {
const mediaQuery = matchMedia('(prefers-reduced-motion: reduce)')
return mediaQuery.matches
}
const colorForToastType = (type: ToastType) => {
switch (type) {
case ToastType.Success:
return 'color-success'
case ToastType.Error:
return 'color-danger'
default:
return 'color-info'
}
}
const iconForToastType = (type: ToastType) => {
switch (type) {
case ToastType.Success:
return <CheckCircleFilledIcon className={colorForToastType(type)} />
case ToastType.Error:
return <ClearCircleFilledIcon className={colorForToastType(type)} />
case ToastType.Progress:
case ToastType.Loading:
return <div className="sk-spinner w-4 h-4 spinner-info" />
default:
return null
}
}
type Props = {
toast: ToastPropType
index: number
}
export const Toast = forwardRef(({ toast, index }: Props, ref: ForwardedRef<HTMLDivElement>) => {
const icon = iconForToastType(toast.type)
const hasActions = toast.actions && toast.actions.length > 0
const hasProgress = toast.type === ToastType.Progress && toast.progress !== undefined && toast.progress > -1
const shouldReduceMotion = prefersReducedMotion()
const enterAnimation = shouldReduceMotion ? 'fade-in-animation' : 'slide-in-right-animation'
const exitAnimation = shouldReduceMotion ? 'fade-out-animation' : 'slide-out-left-animation'
const currentAnimation = toast.dismissed ? exitAnimation : enterAnimation
useEffect(() => {
if (!ref) {
return
}
const element = (ref as RefObject<HTMLDivElement>).current
if (element && toast.dismissed) {
const { scrollHeight, style } = element
requestAnimationFrame(() => {
style.minHeight = 'initial'
style.height = scrollHeight + 'px'
style.transition = 'all 200ms'
requestAnimationFrame(() => {
style.height = '0'
style.padding = '0'
style.margin = '0'
})
})
}
}, [ref, toast.dismissed])
return (
<div
data-index={index}
role="status"
className={`flex flex-col bg-passive-5 rounded opacity-0 animation-fill-forwards select-none min-w-max relative mt-3 ${currentAnimation}`}
style={{
boxShadow: '0px 4px 12px rgba(0, 0, 0, 0.16)',
transition: shouldReduceMotion ? undefined : 'all 0.2s ease',
animationDelay: !toast.dismissed ? '50ms' : undefined,
}}
onClick={() => {
if (!hasActions && toast.type !== ToastType.Loading && toast.type !== ToastType.Progress) {
dismissToast(toast.id)
}
}}
ref={ref}
>
<div className={`flex items-center w-full ${hasActions ? 'p-2 pl-3' : hasProgress ? 'px-3 py-2.5' : 'p-3'}`}>
{icon ? <div className="flex flex-shrink-0 items-center justify-center sn-icon mr-2">{icon}</div> : null}
<div className="text-sm">{toast.message}</div>
{hasActions && (
<div className="ml-4">
{toast.actions?.map((action, index) => (
<button
style={{
paddingLeft: '0.45rem',
paddingRight: '0.45rem',
}}
className={`py-1 border-0 bg-transparent cursor-pointer font-semibold text-sm hover:bg-passive-3 rounded ${colorForToastType(
toast.type,
)} ${index !== 0 ? 'ml-2' : ''}`}
onClick={() => {
action.handler(toast.id)
}}
key={index}
>
{action.label}
</button>
))}
</div>
)}
</div>
{hasProgress && (
<div className="toast-progress-bar">
<div
className="toast-progress-bar__value"
role="progressbar"
style={{
width: `${toast.progress}%`,
...(toast.progress === 100 ? { borderTopRightRadius: 0 } : {}),
}}
aria-valuenow={toast.progress}
/>
</div>
)}
</div>
)
})

View File

@ -0,0 +1,20 @@
import { FunctionComponent } from 'react'
import { useStore } from '@nanostores/react'
import { toastStore } from './toastStore'
import { ToastTimer } from './ToastTimer'
export const ToastContainer: FunctionComponent = () => {
const toasts = useStore(toastStore)
if (!toasts.length) {
return null
}
return (
<div className="flex flex-col items-end fixed z-index-toast bottom-6 right-6">
{toasts.map((toast, index) => (
<ToastTimer toast={toast} index={index} key={toast.id} />
))}
</div>
)
}

View File

@ -0,0 +1,115 @@
import { useCallback, useEffect, useRef, FunctionComponent } from 'react'
import { Toast } from './Toast'
import { Toast as ToastPropType } from './types'
import { ToastType } from './enums'
import { dismissToast } from './toastStore'
type Props = {
toast: ToastPropType
index: number
}
const getDefaultForAutoClose = (hasActions: boolean, type: ToastType) => {
return !hasActions && ![ToastType.Loading, ToastType.Progress].includes(type)
}
const getDefaultToastDuration = (type: ToastType) => (type === ToastType.Error ? 8000 : 4000)
export const ToastTimer: FunctionComponent<Props> = ({ toast, index }) => {
const toastElementRef = useRef<HTMLDivElement>(null)
const toastTimerIdRef = useRef<number>()
const hasActions = Boolean(toast.actions?.length)
const shouldAutoClose = toast.autoClose ?? getDefaultForAutoClose(hasActions, toast.type)
const duration = toast.duration ?? getDefaultToastDuration(toast.type)
const startTimeRef = useRef(duration)
const remainingTimeRef = useRef(duration)
const dismissToastOnEnd = useCallback(() => {
dismissToast(toast.id)
}, [toast.id])
const clearTimer = useCallback(() => {
if (toastTimerIdRef.current) {
clearTimeout(toastTimerIdRef.current)
}
}, [])
const pauseTimer = useCallback(() => {
clearTimer()
remainingTimeRef.current -= Date.now() - startTimeRef.current
}, [clearTimer])
const resumeTimer = useCallback(() => {
startTimeRef.current = Date.now()
clearTimer()
toastTimerIdRef.current = window.setTimeout(dismissToastOnEnd, remainingTimeRef.current)
}, [clearTimer, dismissToastOnEnd])
const handleMouseEnter = useCallback(() => {
pauseTimer()
}, [pauseTimer])
const handleMouseLeave = useCallback(() => {
resumeTimer()
}, [resumeTimer])
const handlePageVisibility = useCallback(() => {
if (document.visibilityState === 'hidden') {
pauseTimer()
} else {
resumeTimer()
}
}, [pauseTimer, resumeTimer])
const handlePageFocus = useCallback(() => {
resumeTimer()
}, [resumeTimer])
const handlePageBlur = useCallback(() => {
pauseTimer()
}, [pauseTimer])
useEffect(() => {
clearTimer()
if (shouldAutoClose) {
resumeTimer()
}
const toastElement = toastElementRef.current
if (toastElement) {
toastElement.addEventListener('mouseenter', handleMouseEnter)
toastElement.addEventListener('mouseleave', handleMouseLeave)
}
document.addEventListener('visibilitychange', handlePageVisibility)
window.addEventListener('focus', handlePageFocus)
window.addEventListener('blur', handlePageBlur)
return () => {
clearTimer()
if (toastElement) {
toastElement.removeEventListener('mouseenter', handleMouseEnter)
toastElement.removeEventListener('mouseleave', handleMouseLeave)
}
document.removeEventListener('visibilitychange', handlePageVisibility)
window.removeEventListener('focus', handlePageFocus)
window.removeEventListener('blur', handlePageBlur)
}
}, [
clearTimer,
dismissToastOnEnd,
duration,
handleMouseEnter,
handleMouseLeave,
handlePageBlur,
handlePageFocus,
handlePageVisibility,
resumeTimer,
shouldAutoClose,
toast.id,
])
return <Toast toast={toast} index={index} ref={toastElementRef} />
}

View File

@ -0,0 +1,35 @@
import { addToast, dismissToast, updateToast } from './toastStore'
import { ToastOptions } from './types'
type InitialToastOptions = Omit<ToastOptions, 'message'> & {
message: (timeRemainingInSeconds: number) => string
}
export const addTimedToast = (
initialOptions: InitialToastOptions,
callback: () => void,
timeInSeconds: number,
): [string, number] => {
let timeRemainingInSeconds = timeInSeconds
const intervalId = window.setInterval(() => {
timeRemainingInSeconds--
if (timeRemainingInSeconds > 0) {
updateToast(toastId, {
message: initialOptions.message(timeRemainingInSeconds),
})
} else {
dismissToast(toastId)
clearInterval(intervalId)
callback()
}
}, 1000)
const toastId = addToast({
...initialOptions,
message: initialOptions.message(timeRemainingInSeconds),
autoClose: false,
})
return [toastId, intervalId]
}

View File

@ -0,0 +1,7 @@
export enum ToastType {
Regular = 'regular',
Success = 'success',
Error = 'error',
Loading = 'loading',
Progress = 'progress',
}

View File

@ -0,0 +1,5 @@
export { ToastContainer } from './ToastContainer'
export { addToast, updateToast, dismissToast } from './toastStore'
export { ToastType } from './enums'
export { addTimedToast } from './addTimedToast'
export type { Toast, ToastAction, ToastOptions } from './types'

View File

@ -0,0 +1,73 @@
import { nanoid } from 'nanoid'
import { action, atom, WritableAtom } from 'nanostores'
import { Toast, ToastOptions, ToastUpdateOptions } from './types'
export const toastStore = atom<Toast[]>([])
export const updateToast = action(
toastStore,
'updateToast',
(store: WritableAtom<Toast[]>, toastId: Toast['id'], options: ToastUpdateOptions) => {
const existingToasts = store.get()
store.set(
existingToasts.map((toast) => {
if (toast.id === toastId) {
return {
...toast,
...options,
}
} else {
return toast
}
}),
)
},
)
const removeToast = action(toastStore, 'removeToast', (store: WritableAtom<Toast[]>, toastId: Toast['id']) => {
const existingToasts = store.get()
store.set(existingToasts.filter((toast) => toast.id !== toastId))
})
const DelayBeforeRemovingToast = 175
export const dismissToast = action(toastStore, 'dismissToast', (store: WritableAtom<Toast[]>, toastId: Toast['id']) => {
const existingToasts = store.get()
store.set(
existingToasts.map((toast) => {
if (toast.id === toastId) {
return {
...toast,
dismissed: true,
}
} else {
return toast
}
}),
)
setTimeout(() => {
removeToast(toastId)
}, DelayBeforeRemovingToast)
})
export const addToast = action(toastStore, 'addToast', (store: WritableAtom<Toast[]>, options: ToastOptions) => {
const existingToasts = store.get()
const isToastIdDuplicate = existingToasts.findIndex((toast) => toast.id === options.id) > -1
const id = options.id && !isToastIdDuplicate ? options.id : nanoid()
if (isToastIdDuplicate) {
console.warn(`Generated new ID for toast instead of overriding toast of ID "${options.id}".
If you want to update an existing toast, use the \`updateToast()\` function instead.`)
}
const toast = {
...options,
id,
dismissed: false,
}
store.set([...existingToasts, toast])
return id
})

View File

@ -0,0 +1,30 @@
import { ToastType } from './enums'
export type ToastAction = {
label: string
handler: (toastId: Toast['id']) => void
}
type CommonToastProperties = {
type: ToastType
message: string
actions?: ToastAction[]
progress?: number
autoClose?: boolean
duration?: number
}
export type Toast = CommonToastProperties & {
id: string
dismissed: boolean
}
export type ToastOptions = CommonToastProperties & {
id?: string
}
export type ToastUpdateOptions = Omit<Partial<ToastOptions>, 'id'>
export type ToastState = {
toasts: Toast[]
}

View File

@ -0,0 +1,13 @@
{
"extends": "../../node_modules/@standardnotes/config/src/tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"rootDir": "src",
"outDir": "dist",
"jsx": "react-jsx",
"skipLibCheck": true,
"module": "es2022"
},
"include": ["src"],
"exclude": ["dist", "node_modules"]
}

View File

@ -9,7 +9,7 @@
"../../node_modules/@standardnotes/config/src/.eslintrc"
],
"plugins": ["@typescript-eslint", "react", "react-hooks"],
"ignorePatterns": [".eslintrc.js", "webpack.*.js", "webpack-defaults.js", "jest.config.js", "__mocks__"],
"ignorePatterns": [".eslintrc.js", "*.webpack.*.js", "webpack-defaults.js", "jest.config.js", "__mocks__"],
"rules": {
"standard/no-callback-literal": 0, // Disable this as we have too many callbacks relying on literals
"no-throw-literal": 0,

View File

@ -1,4 +1,4 @@
const pathsToModuleNameMapper = require('ts-jest/utils').pathsToModuleNameMapper
const pathsToModuleNameMapper = require('ts-jest').pathsToModuleNameMapper
const tsConfig = require('./tsconfig.json')
const pathsFromTsconfig = tsConfig.compilerOptions.paths
@ -12,23 +12,14 @@ module.exports = {
prefix: '<rootDir>',
}),
'\\.(css|less|scss|sass)$': 'identity-obj-proxy',
'@standardnotes/toast': 'identity-obj-proxy',
},
globals: {
__WEB_VERSION__: '1.0.0',
__DESKTOP__: false,
__WEB__: true,
self: {}, // fixes error happening on `import { SKAlert } from 'sn-stylekit'`
},
transform: {
'^.+\\.(ts|tsx)?$': 'ts-jest',
'\\.svg$': 'svg-jest',
},
coverageThreshold: {
global: {
branches: 3,
functions: 5,
lines: 21,
statements: 22,
},
},
}

View File

@ -73,6 +73,7 @@
"@standardnotes/sncrypto-web": "1.10.1",
"@standardnotes/snjs": "^2.115.6",
"@standardnotes/stylekit": "5.29.3",
"@standardnotes/toast": "1.0.0",
"@zip.js/zip.js": "^2.4.10",
"mobx": "^6.5.0",
"mobx-react-lite": "^3.3.0",

View File

@ -18,7 +18,7 @@ import RevisionHistoryModalWrapper from '@/Components/RevisionHistoryModal/Revis
import PremiumModalProvider from '@/Hooks/usePremiumModal'
import ConfirmSignoutContainer from '@/Components/ConfirmSignoutModal/ConfirmSignoutModal'
import TagsContextMenuWrapper from '@/Components/Tags/TagContextMenu'
import { ToastContainer } from '@standardnotes/stylekit'
import { ToastContainer } from '@standardnotes/toast'
import FilePreviewModalWrapper from '@/Components/FilePreview/FilePreviewModal'
import ContentListView from '@/Components/ContentListView/ContentListView'
import FileContextMenuWrapper from '@/Components/FileContextMenu/FileContextMenu'

View File

@ -7,7 +7,7 @@ import { FunctionComponent, useCallback, useEffect, useRef, useState } from 'rea
import Icon from '@/Components/Icon/Icon'
import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur'
import { FileItem, SNNote } from '@standardnotes/snjs'
import { addToast, ToastType } from '@standardnotes/stylekit'
import { addToast, ToastType } from '@standardnotes/toast'
import { StreamingFileReader } from '@standardnotes/filepicker'
import AttachedFilesPopover from './AttachedFilesPopover'
import { usePremiumModal } from '@/Hooks/usePremiumModal'

View File

@ -8,7 +8,7 @@ import ChangeEditorOption from './ChangeEditorOption'
import { BYTES_IN_ONE_MEGABYTE } from '@/Constants/Constants'
import ListedActionsOption from './ListedActionsOption'
import AddTagOption from './AddTagOption'
import { addToast, dismissToast, ToastType } from '@standardnotes/stylekit'
import { addToast, dismissToast, ToastType } from '@standardnotes/toast'
import { NotesOptionsProps } from './NotesOptionsProps'
import { NotesController } from '@/Controllers/NotesController'

View File

@ -15,7 +15,7 @@ import {
parseFileName,
} from '@standardnotes/filepicker'
import { ChallengeReason, ClientDisplayableError, ContentType, FileItem, InternalEventBus } from '@standardnotes/snjs'
import { addToast, dismissToast, ToastType, updateToast } from '@standardnotes/stylekit'
import { addToast, dismissToast, ToastType, updateToast } from '@standardnotes/toast'
import { action, makeObservable, observable, reaction } from 'mobx'
import { WebApplication } from '../Application/Application'
import { AbstractViewController } from './Abstract/AbstractViewController'

View File

@ -14,7 +14,7 @@ import {
PayloadEmitSource,
LocalStorageDecryptedContextualPayload,
} from '@standardnotes/snjs'
import { dismissToast, ToastType, addTimedToast } from '@standardnotes/stylekit'
import { dismissToast, ToastType, addTimedToast } from '@standardnotes/toast'
const CachedThemesKey = 'cachedThemes'
const TimeBeforeApplyingColorScheme = 5

View File

@ -22,7 +22,7 @@ module.exports = (env, argv) => {
],
devServer: {
hot: 'only',
static: '../../web-server/public',
static: '../web-server/public',
port,
devMiddleware: {
writeToDisk: argv.writeToDisk,

View File

@ -731,7 +731,21 @@
dependencies:
"@babel/helper-plugin-utils" "^7.16.7"
"@babel/plugin-transform-react-jsx@^7.17.3":
"@babel/plugin-transform-react-display-name@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.16.7.tgz#7b6d40d232f4c0f550ea348593db3b21e2404340"
integrity sha512-qgIg8BcZgd0G/Cz916D5+9kqX0c7nPZyXaP8R2tLNN5tkyIZdG5fEwBrxwplzSnjC1jvQmyMNVwUCZPcbGY7Pg==
dependencies:
"@babel/helper-plugin-utils" "^7.16.7"
"@babel/plugin-transform-react-jsx-development@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.16.7.tgz#43a00724a3ed2557ed3f276a01a929e6686ac7b8"
integrity sha512-RMvQWvpla+xy6MlBpPlrKZCMRs2AGiHOGHY3xRwl0pEeim348dDyxeH4xBsMPbIMhujeq7ihE702eM2Ew0Wo+A==
dependencies:
"@babel/plugin-transform-react-jsx" "^7.16.7"
"@babel/plugin-transform-react-jsx@^7.16.7", "@babel/plugin-transform-react-jsx@^7.17.12", "@babel/plugin-transform-react-jsx@^7.17.3":
version "7.17.12"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.17.12.tgz#2aa20022709cd6a3f40b45d60603d5f269586dba"
integrity sha512-Lcaw8bxd1DKht3thfD4A12dqo1X16he1Lm8rIv8sTwjAYNInRS1qHa9aJoqvzpscItXvftKDCfaEQzwoVyXpEQ==
@ -742,6 +756,14 @@
"@babel/plugin-syntax-jsx" "^7.17.12"
"@babel/types" "^7.17.12"
"@babel/plugin-transform-react-pure-annotations@^7.16.7":
version "7.18.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.18.0.tgz#ef82c8e310913f3522462c9ac967d395092f1954"
integrity sha512-6+0IK6ouvqDn9bmEG7mEyF/pwlJXVj5lwydybpyyH3D0A7Hftk+NCTdYjnLNZksn261xaOV5ksmp20pQEmc2RQ==
dependencies:
"@babel/helper-annotate-as-pure" "^7.16.7"
"@babel/helper-plugin-utils" "^7.17.12"
"@babel/plugin-transform-regenerator@^7.18.0":
version "7.18.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.0.tgz#44274d655eb3f1af3f3a574ba819d3f48caf99d5"
@ -817,7 +839,7 @@
"@babel/helper-create-regexp-features-plugin" "^7.16.7"
"@babel/helper-plugin-utils" "^7.16.7"
"@babel/preset-env@^7.16.11", "@babel/preset-env@^7.17.10":
"@babel/preset-env@^7.16.11", "@babel/preset-env@^7.17.10", "@babel/preset-env@^7.18.0":
version "7.18.2"
resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.18.2.tgz#f47d3000a098617926e674c945d95a28cb90977a"
integrity sha512-PfpdxotV6afmXMU47S08F9ZKIm2bJIQ0YbAAtDfIENX7G1NUAXigLREh69CWDjtgUy7dYn7bsMzkgdtAlmS68Q==
@ -909,7 +931,19 @@
"@babel/types" "^7.4.4"
esutils "^2.0.2"
"@babel/preset-typescript@^7.16.7":
"@babel/preset-react@^7.17.12":
version "7.17.12"
resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.17.12.tgz#62adbd2d1870c0de3893095757ed5b00b492ab3d"
integrity sha512-h5U+rwreXtZaRBEQhW1hOJLMq8XNJBQ/9oymXiCXTuT/0uOwpbT0gUt+sXeOqoXBgNuUKI7TaObVwoEyWkpFgA==
dependencies:
"@babel/helper-plugin-utils" "^7.17.12"
"@babel/helper-validator-option" "^7.16.7"
"@babel/plugin-transform-react-display-name" "^7.16.7"
"@babel/plugin-transform-react-jsx" "^7.17.12"
"@babel/plugin-transform-react-jsx-development" "^7.16.7"
"@babel/plugin-transform-react-pure-annotations" "^7.16.7"
"@babel/preset-typescript@^7.16.7", "@babel/preset-typescript@^7.17.12":
version "7.17.12"
resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.17.12.tgz#40269e0a0084d56fc5731b6c40febe1c9a4a3e8c"
integrity sha512-S1ViF8W2QwAKUGJXxP9NAfNaqGDdEBJKpYkxHf5Yy2C4NPPzXGeR3Lhk7G8xJaaLcFTRfNjVbtbVtm8Gb0mqvg==
@ -2837,7 +2871,7 @@
"@standardnotes/services" "^1.13.11"
"@standardnotes/utils" "^1.6.10"
"@standardnotes/icons@^1.1.8":
"@standardnotes/icons@^1.1.7", "@standardnotes/icons@^1.1.8":
version "1.1.8"
resolved "https://registry.yarnpkg.com/@standardnotes/icons/-/icons-1.1.8.tgz#958b73cc3dd68c7fe31dcceb8ee48627093ab468"
integrity sha512-RhNzHEbSYFVwVz5+BqDAC5wJZ8DkQlboofwPxuTLSrmezjBeNi9kOw9metoC1Sf82u3bXJr5fgXAC8DEYXYKTg==