diff --git a/demo/index.tsx b/demo/index.tsx index 46518b9..dae0d01 100644 --- a/demo/index.tsx +++ b/demo/index.tsx @@ -1,113 +1,115 @@ -import { StrictMode, useState } from "react"; -import { createRoot } from "react-dom/client"; +import React, { StrictMode, useState } from 'react' +import { createRoot } from 'react-dom/client' -import { ThemeProvider } from "@edgeandnode/components"; +import { ThemeProvider, Toast } from '@edgeandnode/components' -import { GraphProtocolGraphiQL } from "../src"; -import { SavedQuery } from "../src/SavedQueriesToolbar/types"; +import { GraphProtocolGraphiQL } from '../src' +import { SavedQuery } from '../src/SavedQueriesToolbar/types' // #region utils const counter = (() => { - let value = 0; - return () => value++; -})(); + let value = 0 + return () => value++ +})() // #endregion utils -const url = - "https://api.thegraph.com/subgraphs/name/graphprotocol/graph-network-mainnet-staging"; +const url = 'https://api.thegraph.com/subgraphs/name/graphprotocol/graph-network-mainnet-staging' const DEFAULT_QUERY_STR = `\ { subgraphs(first: 5, orderBy: createdAt, orderDirection: desc) { displayName } -}`; +}` const SCHEMA_TYPES_QUERY_STR = `\ query GetSchemaTypes { __schema { queryType { name } } -}`; +}` const initialQueries = [ { - name: "Subgraph Names", + name: 'Subgraph Names', query: DEFAULT_QUERY_STR, isDefault: true, }, { - name: "Schema Types", + name: 'Schema Types', query: SCHEMA_TYPES_QUERY_STR, }, ].map((x, i) => ({ id: `${i}`, ...x, -})); +})) function Demo() { - const [currentQueryId, setCurrentQueryId] = useState( - initialQueries[0].id - ); - const [savedQueries, setSavedQueries] = - useState(initialQueries); + const [currentQueryId, setCurrentQueryId] = useState(initialQueries[0].id) + const [savedQueries, setSavedQueries] = useState(initialQueries) + const [toast, setToast] = useState() return ( - { - setCurrentQueryId(queryId); - }} - onSaveAsNewQuery={async ({ name, query }) => { - const newQuery: SavedQuery = { - id: counter(), - name, - query, - }; + <> + { + setCurrentQueryId(queryId) + }} + onSaveAsNewQuery={async ({ name, query }) => { + const newQuery: SavedQuery = { + id: counter(), + name, + query, + } - setSavedQueries((queries) => [...queries, newQuery]); - }} - onDeleteQuery={async () => { - setCurrentQueryId( - savedQueries.find((x) => x.isDefault)?.id ?? - savedQueries[0]?.id ?? - null - ); - setSavedQueries((queries) => - queries.filter((x) => x.id !== currentQueryId) - ); - }} - onSetQueryAsDefault={async () => { - setSavedQueries((queries) => - queries.map((q) => ({ ...q, isDefault: q.id === currentQueryId })) - ); - }} - onUpdateQuery={async ({ name, query }) => { - setSavedQueries((qs) => - qs.map((q) => - q.id === currentQueryId ? { ...q, name, query } : q - ) - ); - }} - showActions - /> - } - /> - ); + setSavedQueries((queries) => [...queries, newQuery]) + setCurrentQueryId(newQuery.id) + }} + onDeleteQuery={async () => { + const newCurrent = savedQueries.find((x) => x.isDefault)?.id ?? savedQueries[0]?.id ?? null + setSavedQueries((queries) => queries.filter((x) => x.id !== currentQueryId)) + setCurrentQueryId(newCurrent) + }} + onSetQueryAsDefault={async () => { + setSavedQueries((queries) => queries.map((q) => ({ ...q, isDefault: q.id === currentQueryId }))) + }} + onUpdateQuery={async ({ name, query }) => { + setSavedQueries((qs) => qs.map((q) => (q.id === currentQueryId ? { ...q, name, query } : q))) + }} + showActions + /> + } + /> + {/* TODO: I'm not quite sure if this is the best public API. */} + { + toast?.onClose?.() + setToast(undefined) + }} + /> + + ) } -createRoot(document.getElementById("root")!).render( +createRoot(document.getElementById('root')!).render( - -); + , +) diff --git a/package.json b/package.json index 45be6ef..7dc8598 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@edgeandnode/graphiql-playground", - "version": "0.1.13", + "version": "0.1.15", "type": "commonjs", "main": "./dist/index.cjs", "module": "./dist/index.mjs", @@ -39,7 +39,7 @@ "@graphiql/toolkit": "0.7.3" }, "devDependencies": { - "@edgeandnode/components": "^23.5.10", + "@edgeandnode/components": "^23.6.3-rc.0", "@edgeandnode/eslint-config": "^1.0.3", "@emotion/react": ">=11", "@types/node": "^18.7.13", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a0056bf..042e10c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,7 +1,7 @@ lockfileVersion: 5.4 specifiers: - '@edgeandnode/components': ^23.5.10 + '@edgeandnode/components': ^23.6.3-rc.0 '@edgeandnode/eslint-config': ^1.0.3 '@emotion/react': '>=11' '@graphiql/plugin-explorer': 0.1.0 @@ -30,7 +30,7 @@ dependencies: '@graphiql/toolkit': 0.7.3_@types+node@18.7.13 devDependencies: - '@edgeandnode/components': 23.5.10_gat5vmbjqpr4oh7ojdv6zbqawe + '@edgeandnode/components': 23.6.3-rc.0_3lp5xpj6l2qrmy37j4k465pe7y '@edgeandnode/eslint-config': 1.0.3_4rv7y5c6xz3vfxwhbrcxxi73bq '@emotion/react': 11.10.0_tu23i5xxn6kbev62oblybgbdem '@types/node': 18.7.13 @@ -392,8 +392,8 @@ packages: - utf-8-validate dev: true - /@edgeandnode/components/23.5.10_gat5vmbjqpr4oh7ojdv6zbqawe: - resolution: {integrity: sha512-bdv0eUmRdIQxNrxZFefZOxi+sznI6wAr7BUj7S6oePqfUQDgeEmBUWDaH283hLS8bGUL544+x6kV1dWGvE03TQ==} + /@edgeandnode/components/23.6.3-rc.0_3lp5xpj6l2qrmy37j4k465pe7y: + resolution: {integrity: sha512-4nvgZ043zB/JceUEsUvE71jRDK2dRGyFefS6PNHrifhJTBTCQMhaTytjaYysOLzN3/njXfiRx3Ms8E9iXJY0BQ==} peerDependencies: '@emotion/react': ^11.10.4 '@mdx-js/react': ^1.6.22 @@ -411,10 +411,11 @@ packages: '@radix-ui/react-label': 1.0.0_biqbaboplfbrettd7655fr4n2y '@radix-ui/react-popover': 1.0.0_zxljzmqdrxwnuenbkrz77w74uy '@radix-ui/react-switch': 1.0.0_biqbaboplfbrettd7655fr4n2y + '@radix-ui/react-toast': 1.0.0_biqbaboplfbrettd7655fr4n2y '@radix-ui/react-visually-hidden': 1.0.0_biqbaboplfbrettd7655fr4n2y '@reach/auto-id': 0.17.0_biqbaboplfbrettd7655fr4n2y '@tanem/react-nprogress': 5.0.14_biqbaboplfbrettd7655fr4n2y - '@theme-ui/match-media': 0.14.7_frz6mbdq5bzq5cuwzan55ym4wa + '@theme-ui/match-media': 0.15.1_kfg4paaq5hoelbqmqnjlpkuiai '@tippyjs/react': 4.2.6_biqbaboplfbrettd7655fr4n2y '@xstate/react': 3.0.1_zwxkkoki7zbtgmygiremsdka3q classnames: 2.3.2 @@ -1264,7 +1265,7 @@ packages: react: ^16.8 || ^17.0 || ^18.0 || 18 react-dom: ^16.8 || ^17.0 || ^18.0 || 18 dependencies: - '@babel/runtime': 7.18.9 + '@babel/runtime': 7.19.0 '@radix-ui/primitive': 1.0.0 '@radix-ui/react-compose-refs': 1.0.0_react@18.2.0 '@radix-ui/react-context': 1.0.0_react@18.2.0 @@ -1354,7 +1355,7 @@ packages: peerDependencies: react: ^16.8 || ^17.0 || ^18.0 || 18 dependencies: - '@babel/runtime': 7.18.9 + '@babel/runtime': 7.19.0 react: 18.2.0 dev: true @@ -1380,7 +1381,7 @@ packages: react: ^16.8 || ^17.0 || ^18.0 || 18 react-dom: ^16.8 || ^17.0 || ^18.0 || 18 dependencies: - '@babel/runtime': 7.18.9 + '@babel/runtime': 7.19.0 '@radix-ui/primitive': 1.0.0 '@radix-ui/react-compose-refs': 1.0.0_react@18.2.0 '@radix-ui/react-context': 1.0.0_react@18.2.0 @@ -1433,7 +1434,7 @@ packages: react: ^16.8 || ^17.0 || ^18.0 || 18 react-dom: ^16.8 || ^17.0 || ^18.0 || 18 dependencies: - '@babel/runtime': 7.18.9 + '@babel/runtime': 7.19.0 '@radix-ui/react-compose-refs': 1.0.0_react@18.2.0 '@radix-ui/react-context': 1.0.0_react@18.2.0 '@radix-ui/react-id': 1.0.0_react@18.2.0 @@ -1479,7 +1480,7 @@ packages: react: ^16.8 || ^17.0 || ^18.0 || 18 react-dom: ^16.8 || ^17.0 || ^18.0 || 18 dependencies: - '@babel/runtime': 7.18.9 + '@babel/runtime': 7.19.0 '@radix-ui/primitive': 1.0.0 '@radix-ui/react-compose-refs': 1.0.0_react@18.2.0 '@radix-ui/react-context': 1.0.0_react@18.2.0 @@ -1596,7 +1597,7 @@ packages: react: ^16.8 || ^17.0 || ^18.0 || 18 react-dom: ^16.8 || ^17.0 || ^18.0 || 18 dependencies: - '@babel/runtime': 7.18.9 + '@babel/runtime': 7.19.0 '@radix-ui/primitive': 1.0.0 '@radix-ui/react-compose-refs': 1.0.0_react@18.2.0 '@radix-ui/react-context': 1.0.0_react@18.2.0 @@ -1609,6 +1610,29 @@ packages: react-dom: 18.2.0_react@18.2.0 dev: true + /@radix-ui/react-toast/1.0.0_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-mdoF6rahgushdev0OX+9a7JKoH0xZAZBo2Ktf/s779S7EnkZeL3/MFiRIV5LpRP5CtASmfdSD3FLnEvG1RHRtQ==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 || 18 + react-dom: ^16.8 || ^17.0 || ^18.0 || 18 + dependencies: + '@babel/runtime': 7.19.0 + '@radix-ui/primitive': 1.0.0 + '@radix-ui/react-collection': 1.0.0_biqbaboplfbrettd7655fr4n2y + '@radix-ui/react-compose-refs': 1.0.0_react@18.2.0 + '@radix-ui/react-context': 1.0.0_react@18.2.0 + '@radix-ui/react-dismissable-layer': 1.0.0_biqbaboplfbrettd7655fr4n2y + '@radix-ui/react-portal': 1.0.0_biqbaboplfbrettd7655fr4n2y + '@radix-ui/react-presence': 1.0.0_biqbaboplfbrettd7655fr4n2y + '@radix-ui/react-primitive': 1.0.0_biqbaboplfbrettd7655fr4n2y + '@radix-ui/react-use-callback-ref': 1.0.0_react@18.2.0 + '@radix-ui/react-use-controllable-state': 1.0.0_react@18.2.0 + '@radix-ui/react-use-layout-effect': 1.0.0_react@18.2.0 + '@radix-ui/react-visually-hidden': 1.0.0_biqbaboplfbrettd7655fr4n2y + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: true + /@radix-ui/react-use-callback-ref/1.0.0_react@18.2.0: resolution: {integrity: sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==} peerDependencies: @@ -1682,7 +1706,7 @@ packages: react: ^16.8 || ^17.0 || ^18.0 || 18 react-dom: ^16.8 || ^17.0 || ^18.0 || 18 dependencies: - '@babel/runtime': 7.18.9 + '@babel/runtime': 7.19.0 '@radix-ui/react-primitive': 1.0.0_biqbaboplfbrettd7655fr4n2y react: 18.2.0 react-dom: 18.2.0_react@18.2.0 @@ -2140,6 +2164,18 @@ packages: react: 18.2.0 dev: true + /@theme-ui/core/0.15.1_g566eayvhbit5eqxocdac5mhdm: + resolution: {integrity: sha512-RogzL8+HonM+4gP9JBlABp+TbdeXR1SEScAXvBrXN0sM5VnUS4OWPjDxHRsR9uL4DTTM4+LzjAY76WJmM2FuWA==} + peerDependencies: + '@emotion/react': ^11 + react: '>=18 || 18' + dependencies: + '@emotion/react': 11.10.0_tu23i5xxn6kbev62oblybgbdem + '@theme-ui/css': 0.15.1_@emotion+react@11.10.0 + deepmerge: 4.2.2 + react: 18.2.0 + dev: true + /@theme-ui/css/0.14.7_@emotion+react@11.10.0: resolution: {integrity: sha512-DbGw0T4MrTjiRs6lnyYgSD2TV/LvFErzDDpm0ICPL2KqDwqgHX4mFpDafj8/DkyCO9wx2KEiUuE+0NtOlR3i4w==} peerDependencies: @@ -2149,15 +2185,24 @@ packages: csstype: 3.1.0 dev: true - /@theme-ui/match-media/0.14.7_frz6mbdq5bzq5cuwzan55ym4wa: - resolution: {integrity: sha512-Kt4q/Ebl5cBZUnoN0uQfzOFnivfumEiDAQWYcQedP5IJanDPnskAIscz1BM6zQ7GeYWX3ZYIJmTgQTid5so2eA==} + /@theme-ui/css/0.15.1_@emotion+react@11.10.0: + resolution: {integrity: sha512-Ab0t6cnLC09OzfO+SaxzUJWRr4RGfzVm/gpSjZP6CK4UETwWkQ3S2wQdeIBqD4nss0Df6PQkscpGe3eVPjXcdQ==} peerDependencies: - '@theme-ui/core': '>=0.6.0' - '@theme-ui/css': '>=0.6.0' - react: '>16 || 18' + '@emotion/react': ^11 dependencies: - '@theme-ui/core': 0.14.7_g566eayvhbit5eqxocdac5mhdm - '@theme-ui/css': 0.14.7_@emotion+react@11.10.0 + '@emotion/react': 11.10.0_tu23i5xxn6kbev62oblybgbdem + csstype: 3.1.0 + dev: true + + /@theme-ui/match-media/0.15.1_kfg4paaq5hoelbqmqnjlpkuiai: + resolution: {integrity: sha512-tBRZnSoU015neFXvfgAYs2znnY968jROCPMhJYSfEjfg1L2QSOQt4lnFgh5x2A3jSTNx10kR4L7Cmva4zWUoZw==} + peerDependencies: + '@theme-ui/core': ^0.15.1 + '@theme-ui/css': ^0.15.1 + react: '>=18 || 18' + dependencies: + '@theme-ui/core': 0.15.1_g566eayvhbit5eqxocdac5mhdm + '@theme-ui/css': 0.15.1_@emotion+react@11.10.0 react: 18.2.0 dev: true diff --git a/src/SavedQueriesToolbar/SavedQueriesActionButtons.tsx b/src/SavedQueriesToolbar/SavedQueriesActionButtons.tsx index c47c7d2..c3bdbe3 100644 --- a/src/SavedQueriesToolbar/SavedQueriesActionButtons.tsx +++ b/src/SavedQueriesToolbar/SavedQueriesActionButtons.tsx @@ -2,7 +2,7 @@ import { Flex, NewGDSButton as Button, Spacing } from '@edgeandnode/components' -import { SnackbarMessageType } from './SavedQueriesSnackbar' +import { SnackbarMessageType } from './messages' import { SavedQuery } from './types' import { validateQuery, ValidationStatus } from './validation' diff --git a/src/SavedQueriesToolbar/SavedQueriesSnackbar.tsx b/src/SavedQueriesToolbar/SavedQueriesSnackbar.tsx deleted file mode 100644 index 97895ab..0000000 --- a/src/SavedQueriesToolbar/SavedQueriesSnackbar.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { Icon, Text } from '@edgeandnode/components' - -import { ValidationError } from './validation' - -export type SnackbarMessageType = - | ValidationError - | 'success-create' - | 'success-update' - | 'success-setDefault' - | 'success-share' - | 'success-delete' - | 'error-create' - | 'error-update' - | 'error-default' - | 'error-deleteDefault' - -const snackbarMessages: Record = { - 'success-update': 'Query updated', - 'success-create': 'Query created', - 'success-setDefault': 'Default query set', - 'success-share': 'URL copied to clipboard', - 'success-delete': 'Query successfully deleted', - 'error-nameEmpty': "Name can't be empty", - 'error-nameTaken': 'Name is already taken', - 'error-queryEmpty': "Query can't be empty", - 'error-queryInvalid': 'Query is invalid', - 'error-create': 'Unable to create query (duplicate)', - 'error-update': 'Unable to update query (duplicate)', - 'error-default': 'Unable to set the default query', - 'error-deleteDefault': "Default query can't be deleted", -} - -export interface SavedQueriesSnackbarProps { - messageType: SnackbarMessageType | undefined - - onUndoDelete: () => void - onClose: () => void -} - -export function SavedQueriesSnackbar({ messageType, onUndoDelete, onClose }: SavedQueriesSnackbarProps) { - const isDeleteMessage = messageType === 'success-delete' - const autoHideDurationMs = isDeleteMessage ? 5000 : 2000 - - const isSuccess = messageType?.startsWith('success') - const isError = messageType?.startsWith('error') - - return ( - - {isDeleteMessage && } - {messageType && {snackbarMessages[messageType]}} - {isDeleteMessage &&
Undo
} - - } - onClose={onClose} - resumeHideDuration={0} - status={isSuccess ? 'success' : isError ? 'error' : undefined} - /> - ) -} - -// TODO -function Snackbar(props: any) { - console.log('Snackbar', props) - return null -} diff --git a/src/SavedQueriesToolbar/SavedQueriesToolbar.tsx b/src/SavedQueriesToolbar/SavedQueriesToolbar.tsx index 78946b8..aa7d65d 100644 --- a/src/SavedQueriesToolbar/SavedQueriesToolbar.tsx +++ b/src/SavedQueriesToolbar/SavedQueriesToolbar.tsx @@ -1,13 +1,13 @@ /** @jsxImportSource theme-ui */ -import { useState } from 'react' +import { useRef, useState } from 'react' import { Flex, Spacing } from '@edgeandnode/components' import { ActionsMenu } from './ActionsMenu' +import { SnackbarMessageType, ToastMessage, toToastMessage } from './messages' import { SavedQueriesActionButtons, SavedQueriesActionButtonsProps } from './SavedQueriesActionButtons' import { useSavedQueriesContext } from './SavedQueriesContext' -import { SavedQueriesSnackbar, SnackbarMessageType } from './SavedQueriesSnackbar' import { SavedQuerySelect } from './SavedQuerySelect' import type { SavedQuery } from './types' @@ -34,6 +34,7 @@ export interface SavedQueriesToolbarProps isMobile: boolean className?: string actionButtonsClassName?: string + onToast: (message: ToastMessage) => void } export function SavedQueriesToolbar(props: SavedQueriesToolbarProps) { @@ -49,8 +50,19 @@ export function SavedQueriesToolbar(props: SavedQueri // potential rename state for existing queries, name for new queries. const [queryNameDraft, setQueryNameDraft] = useState(currentQuery ? currentQuery.name : '') - const [isQueryDeletionPending, setQueryDeletionPending] = useState(false) - const [snackbarMessage, setSnackbarMessage] = useState() + const isQueryDeletionPending = useRef(false) + + const setSnackbarMessage = (message: SnackbarMessageType) => + props.onToast( + toToastMessage(message, { + confirmDelete: () => { + if (isQueryDeletionPending.current) void props.onDeleteQuery() + }, + undoDelete: () => { + isQueryDeletionPending.current = false + }, + }), + ) const handleActionSelected = async (action: QueryAction) => { switch (action) { @@ -74,7 +86,7 @@ export function SavedQueriesToolbar(props: SavedQueri setSnackbarMessage('error-deleteDefault') return } - setQueryDeletionPending(true) + isQueryDeletionPending.current = true setSnackbarMessage('success-delete') return @@ -140,15 +152,6 @@ export function SavedQueriesToolbar(props: SavedQueri void handleActionSelected('New query')}>New query )} - setQueryDeletionPending(false)} - onClose={() => { - // The snackbar is used as a confirmation prompt here. - if (isQueryDeletionPending) void props.onDeleteQuery() - setSnackbarMessage(undefined) - }} - /> ) } diff --git a/src/SavedQueriesToolbar/index.ts b/src/SavedQueriesToolbar/index.ts index 5e941d3..2faf77e 100644 --- a/src/SavedQueriesToolbar/index.ts +++ b/src/SavedQueriesToolbar/index.ts @@ -1,2 +1,3 @@ +export * from './messages' export * from './SavedQueriesContext' export * from './SavedQueriesToolbar' diff --git a/src/SavedQueriesToolbar/messages.tsx b/src/SavedQueriesToolbar/messages.tsx new file mode 100644 index 0000000..a1a0cb4 --- /dev/null +++ b/src/SavedQueriesToolbar/messages.tsx @@ -0,0 +1,62 @@ +/** @jsxImportSource theme-ui */ +import { Text, ToastProps } from '@edgeandnode/components' + +import { ValidationError } from './validation' + +export type SnackbarMessageType = + | ValidationError + | 'success-create' + | 'success-update' + | 'success-setDefault' + | 'success-share' + | 'success-delete' + | 'error-create' + | 'error-update' + | 'error-default' + | 'error-deleteDefault' + +const snackbarMessages: Record = { + 'success-update': 'Query updated', + 'success-create': 'Query created', + 'success-setDefault': 'Default query set', + 'success-share': 'URL copied to clipboard', + 'success-delete': 'Query successfully deleted', + 'error-nameEmpty': "Name can't be empty", + 'error-nameTaken': 'Name is already taken', + 'error-queryEmpty': "Query can't be empty", + 'error-queryInvalid': 'Query is invalid', + 'error-create': 'Unable to create query (duplicate)', + 'error-update': 'Unable to update query (duplicate)', + 'error-default': 'Unable to set the default query', + 'error-deleteDefault': "Default query can't be deleted", +} + +export type ToastMessage = Pick + +export const toToastMessage = ( + message: SnackbarMessageType, + callbacks: { + undoDelete: () => void + confirmDelete: () => void + }, +): ToastMessage => { + const title = snackbarMessages[message] + const severity = message.startsWith('error') ? 'error' : 'success' + + let res: ToastMessage = { title, severity } + + if (message === 'success-delete') { + res = { + ...res, + action: { + altText: 'Undo', + children: Undo, + onClick: callbacks.undoDelete, + }, + onClose: callbacks.confirmDelete, + duration: 5000, + } + } + + return res +} diff --git a/src/index.tsx b/src/index.tsx index 9da69ac..8c1278b 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -5,7 +5,12 @@ import { ReactNode, useEffect, useState } from 'react' import { SavedQuery } from './SavedQueriesToolbar/types' import { GraphiQLInterface, GraphiQLToolbar } from './GraphiQLInterface' -import { SavedQueriesContext, SavedQueriesContextProvider, SavedQueriesToolbar } from './SavedQueriesToolbar' +import { + SavedQueriesContext, + SavedQueriesContextProvider, + SavedQueriesToolbar, + ToastMessage as _ToastMessage, +} from './SavedQueriesToolbar' import '@graphiql/react/font/fira-code.css' import '@graphiql/plugin-explorer/dist/style.css' @@ -93,6 +98,7 @@ GraphProtocolGraphiQL.SavedQueriesToolbar = SavedQueriesToolbar export declare namespace GraphProtocolGraphiQL { export interface FetcherOptions extends CreateFetcherOptions {} export interface Storage extends GraphiQLStorage {} + export interface ToastMessage extends _ToastMessage {} } export * from './SavedQueriesToolbar/types'