diff --git a/hyperglass/ui/components/debugger.tsx b/hyperglass/ui/components/debugger.tsx
index 9255ece..5218779 100644
--- a/hyperglass/ui/components/debugger.tsx
+++ b/hyperglass/ui/components/debugger.tsx
@@ -12,11 +12,8 @@ import {
useDisclosure,
ModalCloseButton,
} from '@chakra-ui/react';
-import { HiOutlineDownload as RefreshIcon } from '@meronex/icons/hi';
-import { IosColorPalette as ThemeIcon } from '@meronex/icons/ios';
-import { MdcCodeJson as ConfigIcon } from '@meronex/icons/mdc';
import { useConfig, useColorValue, useBreakpointValue } from '~/context';
-import { CodeBlock } from '~/components';
+import { CodeBlock, DynamicIcon } from '~/components';
import { useHyperglassConfig } from '~/hooks';
import type { UseDisclosureReturn } from '@chakra-ui/react';
@@ -75,16 +72,26 @@ export const Debugger: React.FC = () => {
{colorMode.toUpperCase()}
- } colorScheme="cyan" onClick={onConfigOpen}>
+ }
+ >
View Config
- } colorScheme="blue" onClick={onThemeOpen}>
+ }
+ colorScheme="blue"
+ onClick={onThemeOpen}
+ >
View Theme
}
+ leftIcon={}
onClick={() => refetch()}
>
Reload Config
diff --git a/hyperglass/ui/components/footer/button.tsx b/hyperglass/ui/components/footer/button.tsx
index ae2a238..a253e90 100644
--- a/hyperglass/ui/components/footer/button.tsx
+++ b/hyperglass/ui/components/footer/button.tsx
@@ -40,6 +40,7 @@ export const FooterButton: React.FC = (props: TFooterButton) => {
as={Button}
size={size}
variant="ghost"
+ lineHeight={0}
aria-label={typeof title === 'string' ? title : undefined}
>
{title}
diff --git a/hyperglass/ui/components/footer/colorMode.tsx b/hyperglass/ui/components/footer/colorMode.tsx
index feee0dd..09dab80 100644
--- a/hyperglass/ui/components/footer/colorMode.tsx
+++ b/hyperglass/ui/components/footer/colorMode.tsx
@@ -1,15 +1,11 @@
import { forwardRef } from 'react';
-import dynamic from 'next/dynamic';
-import { Button, Icon, Tooltip } from '@chakra-ui/react';
-import { If } from '~/components';
+import { Button, Tooltip } from '@chakra-ui/react';
+import { DynamicIcon, If } from '~/components';
import { useColorMode, useColorValue, useBreakpointValue } from '~/context';
import { useOpposingColor } from '~/hooks';
import type { TColorModeToggle } from './types';
-const Sun = dynamic(() => import('@meronex/icons/hi').then(i => i.HiSun));
-const Moon = dynamic(() => import('@meronex/icons/hi').then(i => i.HiMoon));
-
export const ColorModeToggle = forwardRef(
(props: TColorModeToggle, ref) => {
const { size = '1.5rem', ...rest } = props;
@@ -34,10 +30,10 @@ export const ColorModeToggle = forwardRef(
{...rest}
>
-
+
-
+
diff --git a/hyperglass/ui/components/footer/footer.tsx b/hyperglass/ui/components/footer/footer.tsx
index f38d664..72de28b 100644
--- a/hyperglass/ui/components/footer/footer.tsx
+++ b/hyperglass/ui/components/footer/footer.tsx
@@ -1,7 +1,6 @@
import { useMemo } from 'react';
-import dynamic from 'next/dynamic';
-import { Flex, Icon, HStack, useToken } from '@chakra-ui/react';
-import { If } from '~/components';
+import { Flex, HStack, useToken } from '@chakra-ui/react';
+import { DynamicIcon, If } from '~/components';
import { useConfig, useMobile, useColorValue, useBreakpointValue } from '~/context';
import { useStrf } from '~/hooks';
import { FooterButton } from './button';
@@ -12,9 +11,6 @@ import { isLink, isMenu } from './types';
import type { ButtonProps, LinkProps } from '@chakra-ui/react';
import type { Link, Menu } from '~/types';
-const CodeIcon = dynamic(() => import('@meronex/icons/fi').then(i => i.FiCode));
-const ExtIcon = dynamic(() => import('@meronex/icons/go').then(i => i.GoLinkExternal));
-
function buildItems(links: Link[], menus: Menu[]): [(Link | Menu)[], (Link | Menu)[]] {
const leftLinks = links.filter(link => link.side === 'left');
const leftMenus = menus.filter(menu => menu.side === 'left');
@@ -61,7 +57,7 @@ export const Footer: React.FC = () => {
const icon: Partial = {};
if (item.showIcon) {
- icon.rightIcon = ;
+ icon.rightIcon = ;
}
return ;
} else if (isMenu(item)) {
@@ -77,7 +73,7 @@ export const Footer: React.FC = () => {
const icon: Partial = {};
if (item.showIcon) {
- icon.rightIcon = ;
+ icon.rightIcon = ;
}
return ;
} else if (isMenu(item)) {
@@ -91,7 +87,7 @@ export const Footer: React.FC = () => {
key="credit"
side="right"
content={content.credit}
- title={}
+ title={}
/>
diff --git a/hyperglass/ui/components/form/resolvedTarget.tsx b/hyperglass/ui/components/form/resolvedTarget.tsx
index 030ce77..d7ee705 100644
--- a/hyperglass/ui/components/form/resolvedTarget.tsx
+++ b/hyperglass/ui/components/form/resolvedTarget.tsx
@@ -1,20 +1,12 @@
import { useMemo } from 'react';
-import dynamic from 'next/dynamic';
-import { Button, chakra, Stack, Text, VStack } from '@chakra-ui/react';
+import { Button, Stack, Text, VStack } from '@chakra-ui/react';
+import { DynamicIcon } from '~/components';
import { useConfig, useColorValue } from '~/context';
import { useStrf, useDNSQuery, useFormState } from '~/hooks';
import type { DnsOverHttps } from '~/types';
import type { ResolvedTargetProps } from './types';
-const RightArrow = chakra(
- dynamic(() => import('@meronex/icons/fa').then(i => i.FaArrowCircleRight)),
-);
-
-const LeftArrow = chakra(
- dynamic(() => import('@meronex/icons/fa').then(i => i.FaArrowCircleLeft)),
-);
-
function findAnswer(data: DnsOverHttps.Response | undefined): string {
let answer = '';
if (typeof data !== 'undefined') {
@@ -68,16 +60,12 @@ export const ResolvedTarget = (props: ResolvedTargetProps): JSX.Element => {
}
const hasAnswer = useMemo(
- () => (!isError4 || !isError6) && (answer4 !== '' || answer6 !== ''),
+ () => (!isError4 || !isError6) && (answer4 || answer6),
[answer4, answer6, isError4, isError6],
);
- const showA = useMemo(
- () => !isLoading4 && !isError4 && answer4 !== '',
- [isLoading4, isError4, answer4],
- );
-
+ const showA = useMemo(() => !isLoading4 && !isError4 && answer4, [isLoading4, isError4, answer4]);
const showAAAA = useMemo(
- () => !isLoading6 && !isError6 && answer6 !== '',
+ () => !isLoading6 && !isError6 && answer6,
[isLoading6, isError6, answer6],
);
@@ -102,7 +90,7 @@ export const ResolvedTarget = (props: ResolvedTargetProps): JSX.Element => {
colorScheme="primary"
justifyContent="space-between"
onClick={() => selectTarget(answer4)}
- rightIcon={}
+ rightIcon={}
>
{answer4}
@@ -116,7 +104,7 @@ export const ResolvedTarget = (props: ResolvedTargetProps): JSX.Element => {
colorScheme="secondary"
justifyContent="space-between"
onClick={() => selectTarget(answer6)}
- rightIcon={}
+ rightIcon={}
>
{answer6}
@@ -135,7 +123,7 @@ export const ResolvedTarget = (props: ResolvedTargetProps): JSX.Element => {
variant="outline"
size="sm"
onClick={errorClose}
- leftIcon={}
+ leftIcon={}
>
{web.text.fqdnErrorButton}
diff --git a/hyperglass/ui/components/form/userIP.tsx b/hyperglass/ui/components/form/userIP.tsx
index edbcea1..25f9390 100644
--- a/hyperglass/ui/components/form/userIP.tsx
+++ b/hyperglass/ui/components/form/userIP.tsx
@@ -1,16 +1,11 @@
import { useMemo } from 'react';
-import dynamic from 'next/dynamic';
-import { Button, chakra, Stack, Text, VStack, useDisclosure } from '@chakra-ui/react';
-import { Prompt } from '~/components';
+import { Button, Stack, Text, VStack, useDisclosure } from '@chakra-ui/react';
+import { DynamicIcon, Prompt } from '~/components';
import { useConfig, useColorValue } from '~/context';
import { useStrf, useWtf } from '~/hooks';
import type { UserIPProps } from './types';
-const RightArrow = chakra(
- dynamic(() => import('@meronex/icons/fa').then(i => i.FaArrowCircleRight)),
-);
-
export const UserIP = (props: UserIPProps): JSX.Element => {
const { setTarget } = props;
const { onOpen, ...disclosure } = useDisclosure();
@@ -67,7 +62,7 @@ export const UserIP = (props: UserIPProps): JSX.Element => {
ipv4?.data?.ip && setTarget(ipv4.data.ip);
disclosure.onClose();
}}
- rightIcon={}
+ rightIcon={}
>
{ipv4?.data?.ip ?? noIPv4}
@@ -85,7 +80,7 @@ export const UserIP = (props: UserIPProps): JSX.Element => {
ipv6?.data?.ip && setTarget(ipv6.data.ip);
disclosure.onClose();
}}
- rightIcon={}
+ rightIcon={}
>
{ipv6?.data?.ip ?? noIPv6}
diff --git a/hyperglass/ui/components/help/modal.tsx b/hyperglass/ui/components/help/modal.tsx
index faff30a..3d59bba 100644
--- a/hyperglass/ui/components/help/modal.tsx
+++ b/hyperglass/ui/components/help/modal.tsx
@@ -1,4 +1,3 @@
-import dynamic from 'next/dynamic';
import {
Modal,
ScaleFade,
@@ -10,14 +9,12 @@ import {
useDisclosure,
ModalCloseButton,
} from '@chakra-ui/react';
-import { Markdown } from '~/components';
+import { DynamicIcon, Markdown } from '~/components';
import { useColorValue } from '~/context';
import { isQueryContent } from '~/types';
import type { THelpModal } from './types';
-const Info = dynamic(() => import('@meronex/icons/fi').then(i => i.FiInfo));
-
export const HelpModal: React.FC = (props: THelpModal) => {
const { visible, item, name, ...rest } = props;
const { isOpen, onOpen, onClose } = useDisclosure();
@@ -36,7 +33,7 @@ export const HelpModal: React.FC = (props: THelpModal) => {
minW={3}
size="md"
variant="link"
- icon={}
+ icon={}
onClick={onOpen}
colorScheme="blue"
aria-label={`${name}_help`}
diff --git a/hyperglass/ui/components/layout/resetButton.tsx b/hyperglass/ui/components/layout/resetButton.tsx
index ce1eee7..1b7f8ee 100644
--- a/hyperglass/ui/components/layout/resetButton.tsx
+++ b/hyperglass/ui/components/layout/resetButton.tsx
@@ -1,14 +1,11 @@
-import dynamic from 'next/dynamic';
-import { Flex, Icon, IconButton } from '@chakra-ui/react';
+import { Flex, IconButton } from '@chakra-ui/react';
import { AnimatePresence } from 'framer-motion';
-import { AnimatedDiv } from '~/components';
+import { AnimatedDiv, DynamicIcon } from '~/components';
import { useColorValue } from '~/context';
import { useOpposingColor, useFormState } from '~/hooks';
import type { TResetButton } from './types';
-const LeftArrow = dynamic(() => import('@meronex/icons/fa').then(i => i.FaAngleLeft));
-
export const ResetButton = (props: TResetButton): JSX.Element => {
const { developerMode, resetForm, ...rest } = props;
const status = useFormState(s => s.status);
@@ -34,11 +31,12 @@ export const ResetButton = (props: TResetButton): JSX.Element => {
>
}
+ icon={}
/>
diff --git a/hyperglass/ui/components/output/fields.tsx b/hyperglass/ui/components/output/fields.tsx
index 0e275af..eff8061 100644
--- a/hyperglass/ui/components/output/fields.tsx
+++ b/hyperglass/ui/components/output/fields.tsx
@@ -1,15 +1,9 @@
import { forwardRef } from 'react';
-import { Icon, Text, Box, Tooltip, Menu, MenuButton, MenuList, Link } from '@chakra-ui/react';
-import { CgMoreO as More } from '@meronex/icons/cg';
-import { BisError as Warning } from '@meronex/icons/bi';
-import { MdCancel as NotAllowed } from '@meronex/icons/md';
-import { RiHome2Fill as End } from '@meronex/icons/ri';
-import { BsQuestionCircleFill as Question } from '@meronex/icons/bs';
-import { FaCheckCircle as Check, FaChevronRight as ChevronRight } from '@meronex/icons/fa';
+import { Text, Box, Tooltip, Menu, MenuButton, MenuList, Link } from '@chakra-ui/react';
import dayjs from 'dayjs';
import relativeTimePlugin from 'dayjs/plugin/relativeTime';
import utcPlugin from 'dayjs/plugin/utc';
-import { If } from '~/components';
+import { If, DynamicIcon } from '~/components';
import { useConfig, useColorValue } from '~/context';
import { useOpposingColor } from '~/hooks';
@@ -41,10 +35,10 @@ export const Active: React.FC = (props: TActive) => {
return (
<>
-
+
-
+
>
);
@@ -86,7 +80,7 @@ export const ASPath: React.FC = (props: TASPath) => {
);
if (path.length === 0) {
- return ;
+ return ;
}
const paths = [] as JSX.Element[];
@@ -95,7 +89,13 @@ export const ASPath: React.FC = (props: TASPath) => {
const asnStr = String(asn);
i !== 0 &&
paths.push(
- ,
+ ,
);
paths.push(
@@ -117,14 +117,14 @@ export const Communities: React.FC = (props: TCommunities) => {
-
+
-
+
diff --git a/hyperglass/ui/components/results/requeryButton.tsx b/hyperglass/ui/components/results/requeryButton.tsx
index 1a691a2..9724659 100644
--- a/hyperglass/ui/components/results/requeryButton.tsx
+++ b/hyperglass/ui/components/results/requeryButton.tsx
@@ -1,11 +1,9 @@
import { forwardRef } from 'react';
-import dynamic from 'next/dynamic';
-import { Button, Icon, Tooltip } from '@chakra-ui/react';
+import { Button, Tooltip } from '@chakra-ui/react';
+import { DynamicIcon } from '~/components';
import type { TRequeryButton } from './types';
-const Repeat = dynamic(() => import('@meronex/icons/fi').then(i => i.FiRepeat));
-
const _RequeryButton: React.ForwardRefRenderFunction = (
props: TRequeryButton,
ref,
@@ -25,7 +23,7 @@ const _RequeryButton: React.ForwardRefRenderFunction
-
+
);
diff --git a/hyperglass/ui/components/submit/submit.tsx b/hyperglass/ui/components/submit/submit.tsx
index 223c410..d5b21cd 100644
--- a/hyperglass/ui/components/submit/submit.tsx
+++ b/hyperglass/ui/components/submit/submit.tsx
@@ -13,9 +13,8 @@ import {
ModalCloseButton,
PopoverCloseButton,
} from '@chakra-ui/react';
-import { FiSearch } from '@meronex/icons/fi';
import { useFormContext } from 'react-hook-form';
-import { If, ResolvedTarget } from '~/components';
+import { DynamicIcon, If, ResolvedTarget } from '~/components';
import { useMobile, useColorValue } from '~/context';
import { useFormState } from '~/hooks';
@@ -33,7 +32,7 @@ const _SubmitIcon: React.ForwardRefRenderFunction<
size="lg"
width={16}
type="submit"
- icon={}
+ icon={}
title="Submit Query"
colorScheme="primary"
isLoading={isLoading}
diff --git a/hyperglass/ui/components/table/main.tsx b/hyperglass/ui/components/table/main.tsx
index e7d4969..461dfa7 100644
--- a/hyperglass/ui/components/table/main.tsx
+++ b/hyperglass/ui/components/table/main.tsx
@@ -1,11 +1,9 @@
// This rule isn't needed because react-table does this for us, for better or worse.
/* eslint react/jsx-key: 0 */
-
-import dynamic from 'next/dynamic';
-import { Flex, Icon, Text } from '@chakra-ui/react';
+import { Flex, Text } from '@chakra-ui/react';
import { usePagination, useSortBy, useTable } from 'react-table';
import { useMobile } from '~/context';
-import { CardBody, CardFooter, CardHeader, If } from '~/components';
+import { CardBody, CardFooter, CardHeader, DynamicIcon, If } from '~/components';
import { TableMain } from './table';
import { TableCell } from './cell';
import { TableHead } from './head';
@@ -18,25 +16,6 @@ import type { TableOptions, PluginHook } from 'react-table';
import type { TCellRender } from '~/types';
import type { TTable } from './types';
-const ChevronRight = dynamic(() =>
- import('@meronex/icons/fa').then(i => i.FaChevronRight),
-);
-
-const ChevronLeft = dynamic(() =>
- import('@meronex/icons/fa').then(i => i.FaChevronLeft),
-);
-
-const ChevronDown = dynamic(() =>
- import('@meronex/icons/fa').then(i => i.FaChevronDown),
-);
-
-const DoubleChevronRight = dynamic(() =>
- import('@meronex/icons/fi').then(i => i.FiChevronsRight),
-);
-const DoubleChevronLeft = dynamic(() =>
- import('@meronex/icons/fi').then(i => i.FiChevronsLeft),
-);
-
export const Table: React.FC = (props: TTable) => {
const {
data,
@@ -112,10 +91,10 @@ export const Table: React.FC = (props: TTable) => {
-
+
-
+
{''}
@@ -163,13 +142,13 @@ export const Table: React.FC = (props: TTable) => {
mr={2}
onClick={() => gotoPage(0)}
isDisabled={!canPreviousPage}
- icon={}
+ icon={}
/>
previousPage()}
isDisabled={!canPreviousPage}
- icon={}
+ icon={}
/>
@@ -193,12 +172,12 @@ export const Table: React.FC = (props: TTable) => {
ml={2}
onClick={nextPage}
isDisabled={!canNextPage}
- icon={}
+ icon={}
/>
}
+ icon={}
onClick={() => gotoPage(pageCount ? pageCount - 1 : 1)}
/>
diff --git a/hyperglass/ui/components/util/dynamic-icon.tsx b/hyperglass/ui/components/util/dynamic-icon.tsx
new file mode 100644
index 0000000..2a91845
--- /dev/null
+++ b/hyperglass/ui/components/util/dynamic-icon.tsx
@@ -0,0 +1,181 @@
+/* eslint-disable react-hooks/exhaustive-deps */
+/* eslint-disable react-hooks/rules-of-hooks */
+import { memo, useMemo } from 'react';
+import dynamic from 'next/dynamic';
+import { chakra, Icon as ChakraIcon } from '@chakra-ui/react';
+import isEqual from 'react-fast-compare';
+
+import type { IconProps as ChakraIconProps, TooltipProps } from '@chakra-ui/react';
+
+interface IconMap {
+ [library: string]: string;
+}
+
+interface DynamicIconProps extends Omit {
+ icon: IconMap;
+}
+
+interface ErrorIconProps {
+ message: string;
+}
+
+interface IconErrorConstructor {
+ original: IconMap;
+ library: string;
+ iconName: string;
+}
+
+/**
+ * Extend builtin `Error` for easier handling of icon rendering errors.
+ */
+class IconError extends Error {
+ /**
+ * Original family → icon mapping object.
+ */
+ original: IconMap;
+ /**
+ * Determined family/icon library.
+ */
+ library: string;
+ /**
+ * Determined icon name.
+ */
+ iconName: string;
+
+ constructor({ original, library, iconName }: IconErrorConstructor) {
+ super();
+ this.original = original;
+ this.library = library;
+ this.iconName = iconName;
+ this.stack = this.stack + `\nOriginal object: '${JSON.stringify(this.original)}'`;
+ }
+
+ get message(): string {
+ return `No icon matches 'react-icons/${this.library}/${this.iconName}'`;
+ }
+}
+
+/**
+ * Derive `react-icons` icon family → icon name mapping with proper capitalization. Also handles
+ * existence (or not) of the family prefix.
+ * @param iconObj Family to icon name mapping.
+ *
+ * @example
+ * ```js
+ * iconPath({ fa: 'FaPlus' });
+ * iconPath({ fa: 'faplus' });
+ * iconPath({ fa: 'plus' });
+ * // all return → ['fa', 'FaPlus']
+ * ```
+ * @returns
+ */
+function iconPath(iconObj: IconMap): [string, string] {
+ // Capitalize the first character of a string.
+ const capitalizeFirst = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);
+
+ // Get the first object key.
+ const [familyKey] = Object.keys(iconObj);
+ // Capitalize the family name.
+ const family = capitalizeFirst(familyKey!);
+ // Get the icon name.
+ const initialName = iconObj[familyKey!];
+ // Capitalize the icon name. If `faplus` is provided, it will now be `Faplus`.
+ let name = capitalizeFirst(initialName!);
+ // Create a regex pattern to determine if the family name is in the icon name. If `name` is
+ // `Faplus`, this will be true.
+ const familyPattern = new RegExp(`^${family}`, 'g');
+
+ if (name.match(familyPattern)) {
+ // If the icon name contains the family name, remove it and capitalize the result. If `name`
+ // was `Faplus`, it is now `Plus`.
+ name = capitalizeFirst(name.replace(familyPattern, ''));
+ }
+ // Return a tuple of [family, icon name], i.e. [fa, FaPlus].
+ return [family.toLowerCase(), `${family}${name}`];
+}
+
+/**
+ * Generic error icon to indicate that there was a problem dynamically importing or otherwise
+ * rendering the dynamic icon. Wraps generic icon in a tooltip that provides more detail. This
+ * is dynamically imported at render time in an effort to reduce load times.
+ *
+ * @param props Error message to be displayed.
+ */
+const ErrorIcon = (props: ErrorIconProps): JSX.Element => {
+ const Tooltip = dynamic(() => import('@chakra-ui/react').then(m => m.Tooltip));
+ return (
+
+
+ ⚠
+
+
+ );
+};
+
+const _DynamicIcon = (props: DynamicIconProps): JSX.Element => {
+ const { icon: iconObj, ...rest } = props;
+ // Create a string representation of the icon family and name mapping for memoization.
+ const key = Object.entries(iconObj).flat().join('--');
+ try {
+ const [library, iconName] = useMemo(() => {
+ return iconPath(iconObj);
+ }, [key]);
+
+ if (!library || !iconName) {
+ // If either the library or icon name are falsy, error out.
+ throw new IconError({ original: iconObj, iconName, library });
+ }
+ // Create a memoized version of the imported component, to update only when the computed
+ // family/icon names are changed. Attempt to dynamically import icon from formatted
+ // library/icon name.
+
+ const Component = useMemo(
+ () =>
+ dynamic(() =>
+ import(`react-icons/${library}/index.js`)
+ .then(i => {
+ if (!(iconName in i)) {
+ // If the icon name doesn't exist in the module, error out.
+ throw new IconError({ original: iconObj, iconName, library });
+ }
+ // Otherwise, return the imported icon.
+ return i[iconName as keyof typeof i];
+ })
+ .catch(error => {
+ // Handle any error that occurs during dynamic import.
+ console.error(error);
+ const CaughtError = (): JSX.Element => ;
+ return CaughtError;
+ }),
+ ),
+ [library, iconName],
+ );
+
+ // Return a Chakra-UI icon instance with the imported icon.
+ return ;
+ } catch (error) {
+ // Handle any other uncaught errors.
+ console.error(error);
+ return ;
+ }
+};
+
+/**
+ * Dynamically import a `react-icons` icon by name and wrap it in a Chakra-UI icon component.
+ *
+ * @param props Icon family to icon name mapping.
+ *
+ * @throws An error icon is produced if there is any error during the dynamic import process. A
+ * console message is also displayed with additional details.
+ *
+ * @example
+ * ```js
+ * <>
+ *
+ * // This also works:
+ *
+ * >
+ * ```
+ */
+export const DynamicIcon = memo(_DynamicIcon, isEqual);
+export default DynamicIcon;
diff --git a/hyperglass/ui/components/util/index.ts b/hyperglass/ui/components/util/index.ts
index d7a1522..56b40ca 100644
--- a/hyperglass/ui/components/util/index.ts
+++ b/hyperglass/ui/components/util/index.ts
@@ -1,2 +1,3 @@
export * from './animated';
+export * from './dynamic-icon';
export * from './if';
diff --git a/hyperglass/ui/hooks/useFormState.ts b/hyperglass/ui/hooks/useFormState.ts
index c163f62..432df4f 100644
--- a/hyperglass/ui/hooks/useFormState.ts
+++ b/hyperglass/ui/hooks/useFormState.ts
@@ -1,6 +1,6 @@
import { useMemo } from 'react';
import create from 'zustand';
-import { intersectionWith } from 'lodash';
+import intersectionWith from 'lodash/intersectionWith';
import plur from 'plur';
import isEqual from 'react-fast-compare';
import { all, andJoin, dedupObjectArray, withDev } from '~/util';
diff --git a/hyperglass/ui/package.json b/hyperglass/ui/package.json
index 6c1ee90..0dd075a 100644
--- a/hyperglass/ui/package.json
+++ b/hyperglass/ui/package.json
@@ -21,7 +21,6 @@
"@emotion/styled": "^11.6.0",
"@hookform/devtools": "^4.0.1",
"@hookform/resolvers": "^2.8.4",
- "@meronex/icons": "^4.0.0",
"dagre": "^0.8.5",
"dayjs": "^1.10.4",
"framer-motion": "^5.4.1",
@@ -38,6 +37,7 @@
"react-flow-renderer": "^9.6.0",
"react-ga": "^3.3.0",
"react-hook-form": "^7.21.0",
+ "react-icons": "^4.3.1",
"react-markdown": "^5.0.3",
"react-query": "^3.16.0",
"react-select": "^5.2.1",
@@ -45,13 +45,14 @@
"remark-gfm": "^1.0.0",
"string-format": "^2.0.0",
"vest": "^3.2.8",
- "zustand": "^3.5.10"
+ "zustand": "^3.6.6"
},
"devDependencies": {
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^12.1.0",
"@types/dagre": "^0.7.44",
"@types/express": "^4.17.13",
+ "@types/lodash": "^4.14.177",
"@types/node": "^14.14.41",
"@types/react": "^17.0.3",
"@types/react-table": "^7.7.1",
diff --git a/hyperglass/ui/util/state.ts b/hyperglass/ui/util/state.ts
index 40a70b9..3fac87d 100644
--- a/hyperglass/ui/util/state.ts
+++ b/hyperglass/ui/util/state.ts
@@ -1,6 +1,6 @@
import { devtools } from 'zustand/middleware';
-import type { StateCreator } from 'zustand';
+import type { StateCreator, SetState, GetState, StoreApi } from 'zustand';
/**
* Wrap a zustand state function with devtools, if applicable.
@@ -14,7 +14,7 @@ export function withDev(
name: string,
): StateCreator {
if (typeof window !== 'undefined' && process.env.NODE_ENV === 'development') {
- return devtools(store, { name });
+ return devtools, GetState, StoreApi>(store, { name });
}
return store;
}
diff --git a/hyperglass/ui/yarn.lock b/hyperglass/ui/yarn.lock
index ddbe287..e7a284b 100644
--- a/hyperglass/ui/yarn.lock
+++ b/hyperglass/ui/yarn.lock
@@ -1514,14 +1514,6 @@
"@types/yargs" "^16.0.0"
chalk "^4.0.0"
-"@meronex/icons@^4.0.0":
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/@meronex/icons/-/icons-4.0.0.tgz#26e089a8a4ec176a5b6778fd54fcdd25b4746c67"
- integrity sha512-WnoxUT02qawZSvsoPSwe7YOqOk0APysIHugiD3dYdc/QNeoigN4PD8mmmtmZFKlv8/Z7eERub0BmPkWcJ1BI+w==
- dependencies:
- camelcase "^5.0.0"
- ncp "^2.0.0"
-
"@napi-rs/triples@1.0.3":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@napi-rs/triples/-/triples-1.0.3.tgz#76d6d0c3f4d16013c61e45dfca5ff1e6c31ae53c"
@@ -2094,6 +2086,11 @@
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.170.tgz#0d67711d4bf7f4ca5147e9091b847479b87925d6"
integrity sha512-bpcvu/MKHHeYX+qeEN8GE7DIravODWdACVA1ctevD8CN24RhPZIKMn9ntfAsrvLfSX3cR5RrBKAbYm9bGs0A+Q==
+"@types/lodash@^4.14.177":
+ version "4.14.177"
+ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.177.tgz#f70c0d19c30fab101cad46b52be60363c43c4578"
+ integrity sha512-0fDwydE2clKe9MNfvXHBHF9WEahRuj+msTuQqOmAApNORFvhMYZKNGGJdCzuhheVjMps/ti0Ak/iJPACMaevvw==
+
"@types/mdast@^3.0.0", "@types/mdast@^3.0.3":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.3.tgz#2d7d671b1cd1ea3deb306ea75036c2a0407d2deb"
@@ -2944,7 +2941,7 @@ callsites@^3.0.0:
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
-camelcase@^5.0.0, camelcase@^5.3.1:
+camelcase@^5.3.1:
version "5.3.1"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
@@ -6175,11 +6172,6 @@ natural-compare@^1.4.0:
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
-ncp@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3"
- integrity sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=
-
negotiator@0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
@@ -6957,6 +6949,11 @@ react-hook-form@^7.21.0:
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.21.0.tgz#f6f0311295b83ca768873ca9f2a787ff1525f591"
integrity sha512-aekCf+dedYFIg+7nCK2acMvZ+s6Ohw2I7UNQ+zNIadBl1SoXow2Tl6c3F49xF8GFCdn5jeK43JHH26rmtdRyLQ==
+react-icons@^4.3.1:
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-4.3.1.tgz#2fa92aebbbc71f43d2db2ed1aed07361124e91ca"
+ integrity sha512-cB10MXLTs3gVuXimblAdI71jrJx8njrJZmNMEMC+sQu5B/BIOmlsAjskdqpn81y8UBVEGuHODd7/ci5DvoSzTQ==
+
react-is@17.0.2, "react-is@^16.12.0 || ^17.0.0", react-is@^17.0.1, react-is@^17.0.2:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
@@ -8433,10 +8430,10 @@ yocto-queue@^0.1.0:
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
-zustand@^3.5.10:
- version "3.5.10"
- resolved "https://registry.yarnpkg.com/zustand/-/zustand-3.5.10.tgz#d2622efd64530ffda285ee5b13ff645b68ab0faf"
- integrity sha512-upluvSRWrlCiExu2UbkuMIPJ9AigyjRFoO7O9eUossIj7rPPq7pcJ0NKk6t2P7KF80tg/UdPX6/pNKOSbs9DEg==
+zustand@^3.6.6:
+ version "3.6.6"
+ resolved "https://registry.yarnpkg.com/zustand/-/zustand-3.6.6.tgz#3b7473a15813f7af9784233abd052c3b4560bbcc"
+ integrity sha512-y4755cIzJHQFEHgTQ5cHrlHdmXMxm5N3DU05Q27yT6rK4lKs2336t5IsAz5q9/GRaoEz6o8SiCOPDhZd5BnneA==
zwitch@^1.0.0:
version "1.0.5"