forked from mirrors/thatmattlove-hyperglass
parent
18e0b3e7e7
commit
ee7d8752f8
40 changed files with 260 additions and 331 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,5 +1,5 @@
|
||||||
# Project
|
# Project
|
||||||
hyperglass/hyperglass/static
|
static/
|
||||||
TODO*
|
TODO*
|
||||||
.env
|
.env
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,7 @@ def init_ui_params(*, params: "Params", devices: "Devices") -> "UIParameters":
|
||||||
_ui_params = params.frontend()
|
_ui_params = params.frontend()
|
||||||
_ui_params["web"]["logo"]["light_format"] = params.web.logo.light.suffix
|
_ui_params["web"]["logo"]["light_format"] = params.web.logo.light.suffix
|
||||||
_ui_params["web"]["logo"]["dark_format"] = params.web.logo.dark.suffix
|
_ui_params["web"]["logo"]["dark_format"] = params.web.logo.dark.suffix
|
||||||
|
_ui_params["web"]["logo"]["footer_format"] = params.web.logo.footer.suffix
|
||||||
|
|
||||||
return UIParameters(
|
return UIParameters(
|
||||||
**_ui_params,
|
**_ui_params,
|
||||||
|
|
|
||||||
|
|
@ -203,7 +203,7 @@ def migrate_images(app_path: Path, params: "UIParameters"):
|
||||||
src_files = ()
|
src_files = ()
|
||||||
dst_files = ()
|
dst_files = ()
|
||||||
|
|
||||||
for image in ("light", "dark", "favicon"):
|
for image in ("light", "dark", "favicon", "footer"):
|
||||||
src: Path = getattr(params.web.logo, image)
|
src: Path = getattr(params.web.logo, image)
|
||||||
dst = images_dir / f"{image + src.suffix}"
|
dst = images_dir / f"{image + src.suffix}"
|
||||||
src_files += (src,)
|
src_files += (src,)
|
||||||
|
|
|
||||||
|
|
@ -47,12 +47,12 @@ class ParamsPublic(HyperglassModel):
|
||||||
description="Your network's primary ASN. This field is used to set some useful defaults such as the subtitle and PeeringDB URL.",
|
description="Your network's primary ASN. This field is used to set some useful defaults such as the subtitle and PeeringDB URL.",
|
||||||
)
|
)
|
||||||
org_name: str = Field(
|
org_name: str = Field(
|
||||||
"Beloved Hyperglass User",
|
"Beloved Looking Glass User",
|
||||||
title="Organization Name",
|
title="Organization Name",
|
||||||
description="Your organization's name. This field is used in the UI & API documentation to set fields such as `<meta/>` HTML tags for SEO and the terms & conditions footer component.",
|
description="Your organization's name. This field is used in the UI & API documentation to set fields such as `<meta/>` HTML tags for SEO and the terms & conditions footer component.",
|
||||||
)
|
)
|
||||||
site_title: str = Field(
|
site_title: str = Field(
|
||||||
"hyperglass",
|
"Looking Glass",
|
||||||
title="Site Title",
|
title="Site Title",
|
||||||
description="The name of your hyperglass site. This field is used in the UI & API documentation to set fields such as the `<title/>` HTML tag, and the terms & conditions footer component.",
|
description="The name of your hyperglass site. This field is used in the UI & API documentation to set fields such as the `<title/>` HTML tag, and the terms & conditions footer component.",
|
||||||
)
|
)
|
||||||
|
|
@ -131,8 +131,8 @@ class Params(ParamsPublic, HyperglassModel):
|
||||||
|
|
||||||
for menu in web.menus:
|
for menu in web.menus:
|
||||||
menu.content = menu.content.format(
|
menu.content = menu.content.format(
|
||||||
site_title=info.data.get("site_title", "hyperglass"),
|
site_title=info.data.get("site_title", "Looking Glass"),
|
||||||
org_name=info.data.get("org_name", "hyperglass"),
|
org_name=info.data.get("org_name", "Looking Glass"),
|
||||||
version=__version__,
|
version=__version__,
|
||||||
)
|
)
|
||||||
return web
|
return web
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,7 @@ class Logo(HyperglassModel):
|
||||||
light: FilePath = DEFAULT_IMAGES / "hyperglass-light.svg"
|
light: FilePath = DEFAULT_IMAGES / "hyperglass-light.svg"
|
||||||
dark: FilePath = DEFAULT_IMAGES / "hyperglass-dark.svg"
|
dark: FilePath = DEFAULT_IMAGES / "hyperglass-dark.svg"
|
||||||
favicon: FilePath = DEFAULT_IMAGES / "hyperglass-icon.svg"
|
favicon: FilePath = DEFAULT_IMAGES / "hyperglass-icon.svg"
|
||||||
|
footer: FilePath = DEFAULT_IMAGES / "hyperglass-icon.svg"
|
||||||
width: str = Field(default="50%", pattern=PERCENTAGE_PATTERN)
|
width: str = Field(default="50%", pattern=PERCENTAGE_PATTERN)
|
||||||
height: t.Optional[str] = Field(default=None, pattern=PERCENTAGE_PATTERN)
|
height: t.Optional[str] = Field(default=None, pattern=PERCENTAGE_PATTERN)
|
||||||
|
|
||||||
|
|
@ -95,13 +96,13 @@ class LogoPublic(Logo):
|
||||||
|
|
||||||
light_format: str
|
light_format: str
|
||||||
dark_format: str
|
dark_format: str
|
||||||
|
footer_format: str
|
||||||
|
|
||||||
class Text(HyperglassModel):
|
class Text(HyperglassModel):
|
||||||
"""Validation model for params.branding.text."""
|
"""Validation model for params.branding.text."""
|
||||||
|
|
||||||
title_mode: TitleMode = "logo_only"
|
title_mode: TitleMode = "logo_only"
|
||||||
title: str = Field(default="hyperglass", max_length=32)
|
title: str = Field(default="Looking Glass", max_length=32)
|
||||||
subtitle: str = Field(default="Network Looking Glass", max_length=32)
|
subtitle: str = Field(default="Network Looking Glass", max_length=32)
|
||||||
query_location: str = "Location"
|
query_location: str = "Location"
|
||||||
query_type: str = "Query Type"
|
query_type: str = "Query Type"
|
||||||
|
|
@ -132,7 +133,7 @@ class Text(HyperglassModel):
|
||||||
class ThemeColors(HyperglassModel):
|
class ThemeColors(HyperglassModel):
|
||||||
"""Validation model for theme colors."""
|
"""Validation model for theme colors."""
|
||||||
|
|
||||||
black: Color = "#000000"
|
black: Color = "#2d3635"
|
||||||
white: Color = "#ffffff"
|
white: Color = "#ffffff"
|
||||||
dark: Color = "#010101"
|
dark: Color = "#010101"
|
||||||
light: Color = "#f5f6f7"
|
light: Color = "#f5f6f7"
|
||||||
|
|
@ -169,7 +170,7 @@ class ThemeColors(HyperglassModel):
|
||||||
class ThemeFonts(HyperglassModel):
|
class ThemeFonts(HyperglassModel):
|
||||||
"""Validation model for theme fonts."""
|
"""Validation model for theme fonts."""
|
||||||
|
|
||||||
body: str = "Nunito"
|
body: str = "Inter"
|
||||||
mono: str = "Fira Code"
|
mono: str = "Fira Code"
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -226,6 +227,7 @@ class HighlightPattern(HyperglassModel):
|
||||||
class Web(HyperglassModel):
|
class Web(HyperglassModel):
|
||||||
"""Validation model for all web/browser-related configuration."""
|
"""Validation model for all web/browser-related configuration."""
|
||||||
|
|
||||||
|
copyright: t.Optional[str] = "All rights reserved"
|
||||||
credit: Credit = Credit()
|
credit: Credit = Credit()
|
||||||
dns_provider: DnsOverHttps = DnsOverHttps()
|
dns_provider: DnsOverHttps = DnsOverHttps()
|
||||||
links: t.Sequence[Link] = [
|
links: t.Sequence[Link] = [
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,12 @@ import { useMemo } from 'react';
|
||||||
import { Button, Menu, MenuButton, MenuList } from '@chakra-ui/react';
|
import { Button, Menu, MenuButton, MenuList } from '@chakra-ui/react';
|
||||||
import { useConfig } from '~/context';
|
import { useConfig } from '~/context';
|
||||||
import { Markdown } from '~/elements';
|
import { Markdown } from '~/elements';
|
||||||
import { useColorValue, useBreakpointValue, useOpposingColor, useStrf } from '~/hooks';
|
import { useStrf } from '~/hooks';
|
||||||
|
|
||||||
import type { MenuListProps } from '@chakra-ui/react';
|
import type { MenuListProps } from '@chakra-ui/react';
|
||||||
import type { Config } from '~/types';
|
import type { Config } from '~/types';
|
||||||
|
|
||||||
interface FooterButtonProps extends Omit<MenuListProps, 'title'> {
|
interface FooterButtonProps extends Omit<MenuListProps, 'title'> {
|
||||||
side: 'left' | 'right';
|
|
||||||
title?: MenuListProps['children'];
|
title?: MenuListProps['children'];
|
||||||
content: string;
|
content: string;
|
||||||
}
|
}
|
||||||
|
|
@ -27,26 +26,26 @@ function getConfigFmt(config: Config): Record<string, string> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FooterButton = (props: FooterButtonProps): JSX.Element => {
|
export const FooterButton = (props: FooterButtonProps): JSX.Element => {
|
||||||
const { content, title, side, ...rest } = props;
|
const { content, title, ...rest } = props;
|
||||||
|
|
||||||
const config = useConfig();
|
const config = useConfig();
|
||||||
const strF = useStrf();
|
const strF = useStrf();
|
||||||
const fmt = useMemo(() => getConfigFmt(config), [config]);
|
const fmt = useMemo(() => getConfigFmt(config), [config]);
|
||||||
const fmtContent = useMemo(() => strF(content, fmt), [fmt, content, strF]);
|
const fmtContent = useMemo(() => strF(content, fmt), [fmt, content, strF]);
|
||||||
|
|
||||||
const placement = side === 'left' ? 'top' : side === 'right' ? 'top-end' : undefined;
|
|
||||||
const bg = useColorValue('white', 'gray.900');
|
|
||||||
const color = useOpposingColor(bg);
|
|
||||||
const size = useBreakpointValue({ base: 'xs', lg: 'sm' });
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu placement={placement} preventOverflow isLazy>
|
<Menu
|
||||||
|
placement="top"
|
||||||
|
preventOverflow
|
||||||
|
isLazy
|
||||||
|
>
|
||||||
<MenuButton
|
<MenuButton
|
||||||
zIndex={2}
|
zIndex={2}
|
||||||
|
py={1}
|
||||||
|
fontWeight="normal"
|
||||||
as={Button}
|
as={Button}
|
||||||
size={size}
|
variant="link"
|
||||||
variant="ghost"
|
colorScheme="transparent"
|
||||||
lineHeight={0}
|
|
||||||
aria-label={typeof title === 'string' ? title : undefined}
|
aria-label={typeof title === 'string' ? title : undefined}
|
||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
|
|
@ -54,10 +53,10 @@ export const FooterButton = (props: FooterButtonProps): JSX.Element => {
|
||||||
<MenuList
|
<MenuList
|
||||||
px={6}
|
px={6}
|
||||||
py={4}
|
py={4}
|
||||||
bg={bg}
|
bg="white"
|
||||||
// Ensure the height doesn't overtake the viewport, especially on mobile. See overflow also.
|
// Ensure the height doesn't overtake the viewport, especially on mobile. See overflow also.
|
||||||
maxH="50vh"
|
maxH="50vh"
|
||||||
color={color}
|
color="black"
|
||||||
boxShadow="2xl"
|
boxShadow="2xl"
|
||||||
textAlign="left"
|
textAlign="left"
|
||||||
overflowY="auto"
|
overflowY="auto"
|
||||||
|
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
import { forwardRef } from 'react';
|
|
||||||
import { Button, Tooltip } from '@chakra-ui/react';
|
|
||||||
import { Switch, Case } from 'react-if';
|
|
||||||
import { DynamicIcon } from '~/elements';
|
|
||||||
import { useOpposingColor, useColorMode, useColorValue, useBreakpointValue } from '~/hooks';
|
|
||||||
|
|
||||||
import type { ButtonProps } from '@chakra-ui/react';
|
|
||||||
|
|
||||||
interface ColorModeToggleProps extends Omit<ButtonProps, 'size'> {
|
|
||||||
size?: string | number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ColorModeToggle = forwardRef<HTMLButtonElement, ColorModeToggleProps>(
|
|
||||||
(props: ColorModeToggleProps, ref) => {
|
|
||||||
const { size = '1.5rem', ...rest } = props;
|
|
||||||
const { colorMode, toggleColorMode } = useColorMode();
|
|
||||||
|
|
||||||
const bg = useColorValue('primary.500', 'yellow.300');
|
|
||||||
const color = useOpposingColor(bg);
|
|
||||||
const label = useColorValue('Switch to dark mode', 'Switch to light mode');
|
|
||||||
const btnSize = useBreakpointValue({ base: 'xs', lg: 'sm' });
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tooltip hasArrow placement="top-end" label={label} bg={bg} color={color}>
|
|
||||||
<Button
|
|
||||||
ref={ref}
|
|
||||||
size={btnSize}
|
|
||||||
title={label}
|
|
||||||
variant="ghost"
|
|
||||||
aria-label={label}
|
|
||||||
_hover={{ color: bg }}
|
|
||||||
color="currentColor"
|
|
||||||
onClick={toggleColorMode}
|
|
||||||
{...rest}
|
|
||||||
>
|
|
||||||
<Switch>
|
|
||||||
<Case condition={colorMode === 'light'}>
|
|
||||||
<DynamicIcon icon={{ hi: 'HiMoon' }} boxSize={size} />
|
|
||||||
</Case>
|
|
||||||
<Case condition={colorMode === 'dark'}>
|
|
||||||
<DynamicIcon icon={{ hi: 'HiSun' }} boxSize={size} />
|
|
||||||
</Case>
|
|
||||||
</Switch>
|
|
||||||
</Button>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
import { Flex, HStack, useToken } from '@chakra-ui/react';
|
import { Box, Container, Flex, Grid, GridItem } from '@chakra-ui/react';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useConfig } from '~/context';
|
import { useConfig } from '~/context';
|
||||||
import { DynamicIcon } from '~/elements';
|
import { DynamicIcon, Markdown } from '~/elements';
|
||||||
import { useBreakpointValue, useColorValue, useMobile } from '~/hooks';
|
import { useMobile } from '~/hooks';
|
||||||
import { isLink, isMenu } from '~/types';
|
import { isLink, isMenu } from '~/types';
|
||||||
import { FooterButton } from './button';
|
import { FooterButton } from './button';
|
||||||
import { ColorModeToggle } from './color-mode';
|
|
||||||
import { FooterLink } from './link';
|
import { FooterLink } from './link';
|
||||||
|
|
||||||
import type { ButtonProps, LinkProps } from '@chakra-ui/react';
|
import type { ButtonProps, LinkProps } from '@chakra-ui/react';
|
||||||
import type { Link, Menu } from '~/types';
|
import type { Link, Menu } from '~/types';
|
||||||
|
import { Logo } from './logo';
|
||||||
|
|
||||||
type MenuItems = (Link | Menu)[];
|
type MenuItems = (Link | Menu)[];
|
||||||
|
|
||||||
|
|
@ -24,7 +24,7 @@ function buildItems(links: Link[], menus: Menu[]): [MenuItems, MenuItems] {
|
||||||
return [left, right];
|
return [left, right];
|
||||||
}
|
}
|
||||||
|
|
||||||
const LinkOnSide = (props: { item: ArrayElement<MenuItems>; side: 'left' | 'right' }) => {
|
const NavLink = (props: { item: ArrayElement<MenuItems> }) => {
|
||||||
const { item, side } = props;
|
const { item, side } = props;
|
||||||
if (isLink(item)) {
|
if (isLink(item)) {
|
||||||
const icon: Partial<ButtonProps & LinkProps> = {};
|
const icon: Partial<ButtonProps & LinkProps> = {};
|
||||||
|
|
@ -40,49 +40,55 @@ const LinkOnSide = (props: { item: ArrayElement<MenuItems>; side: 'left' | 'righ
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Footer = (): JSX.Element => {
|
export const Footer = (): JSX.Element => {
|
||||||
const { web, content } = useConfig();
|
const { web } = useConfig();
|
||||||
|
|
||||||
const footerBg = useColorValue('blackAlpha.50', 'whiteAlpha.100');
|
|
||||||
const footerColor = useColorValue('black', 'white');
|
|
||||||
|
|
||||||
const size = useBreakpointValue({ base: useToken('sizes', 4), lg: useToken('sizes', 6) });
|
|
||||||
|
|
||||||
const isMobile = useMobile();
|
|
||||||
|
|
||||||
const [left, right] = useMemo(() => buildItems(web.links, web.menus), [web.links, web.menus]);
|
const [left, right] = useMemo(() => buildItems(web.links, web.menus), [web.links, web.menus]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HStack
|
<Box
|
||||||
|
w="100%"
|
||||||
|
bg="#005e8a"
|
||||||
|
color="#ffffff"
|
||||||
|
fontSize=".945rem"
|
||||||
|
>
|
||||||
|
<Container maxW="8xl">
|
||||||
|
<Grid
|
||||||
px={6}
|
px={6}
|
||||||
py={4}
|
py={4}
|
||||||
w="100%"
|
w="100%"
|
||||||
zIndex={1}
|
zIndex={1}
|
||||||
as="footer"
|
as="footer"
|
||||||
bg={footerBg}
|
|
||||||
whiteSpace="nowrap"
|
whiteSpace="nowrap"
|
||||||
color={footerColor}
|
templateColumns="repeat(4, 1fr)"
|
||||||
spacing={{ base: 8, lg: 6 }}
|
gap={6}
|
||||||
display={{ base: 'inline-block', lg: 'flex' }}
|
|
||||||
overflowY={{ base: 'auto', lg: 'unset' }}
|
|
||||||
justifyContent={{ base: 'center', lg: 'space-between' }}
|
|
||||||
>
|
>
|
||||||
{left.map(item => (
|
<GridItem colSpan={{base: 4, md: 2}}>
|
||||||
<LinkOnSide key={item.title} item={item} side="left" />
|
<Flex
|
||||||
))}
|
flex="1 0 auto"
|
||||||
{!isMobile && <Flex p={0} flex="1 0 auto" maxWidth="100%" mr="auto" />}
|
|
||||||
{right.map(item => (
|
|
||||||
<LinkOnSide key={item.title} item={item} side="right" />
|
|
||||||
))}
|
|
||||||
{web.credit.enable && (
|
|
||||||
<FooterButton
|
|
||||||
key="credit"
|
key="credit"
|
||||||
side="right"
|
side="left"
|
||||||
content={content.credit}
|
maxW="50%"
|
||||||
title={<DynamicIcon icon={{ fi: 'FiCode' }} boxSize={size} />}
|
>
|
||||||
/>
|
<Box w="50px" maxW="15vw" my="2" mr="4">
|
||||||
)}
|
<Logo />
|
||||||
|
</Box>
|
||||||
<ColorModeToggle size={size} />
|
<Markdown content={web.copyright} />
|
||||||
</HStack>
|
</Flex>
|
||||||
|
</GridItem>
|
||||||
|
<GridItem colSpan={{base: 4, md: 1}}>
|
||||||
|
<Flex flexDirection="column" alignItems="start">
|
||||||
|
{left.map(item => (
|
||||||
|
<NavLink key={item.title} item={item} />
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
</GridItem>
|
||||||
|
<GridItem colSpan={{base: 4, md: 1}}>
|
||||||
|
{right.map(item => (
|
||||||
|
<NavLink key={item.title} item={item} />
|
||||||
|
))}
|
||||||
|
</GridItem>
|
||||||
|
</Grid>
|
||||||
|
</Container>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { Button, Link } from '@chakra-ui/react';
|
import { Button, Link } from '@chakra-ui/react';
|
||||||
import { useBreakpointValue } from '~/hooks';
|
|
||||||
|
|
||||||
import type { ButtonProps, LinkProps } from '@chakra-ui/react';
|
import type { ButtonProps, LinkProps } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
|
@ -7,9 +6,17 @@ type FooterLinkProps = ButtonProps & LinkProps & { title: string };
|
||||||
|
|
||||||
export const FooterLink = (props: FooterLinkProps): JSX.Element => {
|
export const FooterLink = (props: FooterLinkProps): JSX.Element => {
|
||||||
const { title } = props;
|
const { title } = props;
|
||||||
const btnSize = useBreakpointValue({ base: 'xs', lg: 'sm' });
|
|
||||||
return (
|
return (
|
||||||
<Button as={Link} isExternal size={btnSize} variant="ghost" aria-label={title} {...props}>
|
<Button
|
||||||
|
as={Link}
|
||||||
|
isExternal
|
||||||
|
py={1}
|
||||||
|
fontWeight="normal"
|
||||||
|
variant="link"
|
||||||
|
colorScheme="transparent"
|
||||||
|
aria-label={title}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
{title}
|
{title}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
37
hyperglass/ui/components/footer/logo.tsx
Normal file
37
hyperglass/ui/components/footer/logo.tsx
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { Image, Skeleton } from '@chakra-ui/react';
|
||||||
|
import { useConfig } from '~/context';
|
||||||
|
|
||||||
|
import type { ImageProps } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
export const Logo = (props: ImageProps): JSX.Element => {
|
||||||
|
const { web } = useConfig();
|
||||||
|
const { footerFormat } = web.logo;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Image
|
||||||
|
src={`/images/footer${footerFormat}`}
|
||||||
|
alt={web.text.title}
|
||||||
|
width="100%"
|
||||||
|
css={{
|
||||||
|
objectFit: 'contain',
|
||||||
|
objectPosition: 'top',
|
||||||
|
userDrag: 'none',
|
||||||
|
userSelect: 'none',
|
||||||
|
msUserSelect: 'none',
|
||||||
|
MozUserSelect: 'none',
|
||||||
|
WebkitUserDrag: 'none',
|
||||||
|
WebkitUserSelect: 'none',
|
||||||
|
}}
|
||||||
|
fallback={
|
||||||
|
<Skeleton
|
||||||
|
isLoaded={false}
|
||||||
|
borderRadius="md"
|
||||||
|
endColor="light.500"
|
||||||
|
startColor="whiteSolid.100"
|
||||||
|
width="100%"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -2,7 +2,7 @@ import { Flex, FormControl, FormErrorMessage, FormLabel } from '@chakra-ui/react
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useFormContext } from 'react-hook-form';
|
import { useFormContext } from 'react-hook-form';
|
||||||
import { If, Then } from 'react-if';
|
import { If, Then } from 'react-if';
|
||||||
import { useBooleanValue, useColorValue } from '~/hooks';
|
import { useBooleanValue } from '~/hooks';
|
||||||
|
|
||||||
import type { FormControlProps } from '@chakra-ui/react';
|
import type { FormControlProps } from '@chakra-ui/react';
|
||||||
import type { FieldError } from 'react-hook-form';
|
import type { FieldError } from 'react-hook-form';
|
||||||
|
|
@ -18,8 +18,8 @@ interface FormFieldProps extends FormControlProps {
|
||||||
|
|
||||||
export const FormField = (props: FormFieldProps): JSX.Element => {
|
export const FormField = (props: FormFieldProps): JSX.Element => {
|
||||||
const { name, label, children, labelAddOn, fieldAddOn, hiddenLabels = false, ...rest } = props;
|
const { name, label, children, labelAddOn, fieldAddOn, hiddenLabels = false, ...rest } = props;
|
||||||
const labelColor = useColorValue('blackAlpha.700', 'whiteAlpha.700');
|
const labelColor = 'blackAlpha.700';
|
||||||
const errorColor = useColorValue('red.500', 'red.300');
|
const errorColor = 'red.500';
|
||||||
const opacity = useBooleanValue(hiddenLabels, 0, undefined);
|
const opacity = useBooleanValue(hiddenLabels, 0, undefined);
|
||||||
|
|
||||||
const { formState } = useFormContext<FormData>();
|
const { formState } = useFormContext<FormData>();
|
||||||
|
|
@ -47,7 +47,6 @@ export const FormField = (props: FormFieldProps): JSX.Element => {
|
||||||
>
|
>
|
||||||
<FormLabel
|
<FormLabel
|
||||||
pr={0}
|
pr={0}
|
||||||
mb={{ lg: 4 }}
|
|
||||||
htmlFor={name}
|
htmlFor={name}
|
||||||
display="flex"
|
display="flex"
|
||||||
opacity={opacity}
|
opacity={opacity}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import {
|
||||||
import { If, Then } from 'react-if';
|
import { If, Then } from 'react-if';
|
||||||
import { Markdown } from '~/elements';
|
import { Markdown } from '~/elements';
|
||||||
import { useConfig } from '~/context';
|
import { useConfig } from '~/context';
|
||||||
import { useGreeting, useColorValue, useOpposingColor } from '~/hooks';
|
import { useGreeting } from '~/hooks';
|
||||||
|
|
||||||
import type { ModalContentProps } from '@chakra-ui/react';
|
import type { ModalContentProps } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
|
@ -20,9 +20,6 @@ export const Greeting = (props: ModalContentProps): JSX.Element => {
|
||||||
const { web, content } = useConfig();
|
const { web, content } = useConfig();
|
||||||
const { isAck, isOpen, open, ack } = useGreeting();
|
const { isAck, isOpen, open, ack } = useGreeting();
|
||||||
|
|
||||||
const bg = useColorValue('white', 'gray.800');
|
|
||||||
const color = useOpposingColor(bg);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!web.greeting.enable && !web.greeting.required) {
|
if (!web.greeting.enable && !web.greeting.required) {
|
||||||
ack(true, false);
|
ack(true, false);
|
||||||
|
|
@ -44,8 +41,8 @@ export const Greeting = (props: ModalContentProps): JSX.Element => {
|
||||||
<ModalOverlay />
|
<ModalOverlay />
|
||||||
<ModalContent
|
<ModalContent
|
||||||
py={4}
|
py={4}
|
||||||
bg={bg}
|
bg="white"
|
||||||
color={color}
|
color="black"
|
||||||
borderRadius="md"
|
borderRadius="md"
|
||||||
maxW={{ base: '95%', md: '75%' }}
|
maxW={{ base: '95%', md: '75%' }}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
import { Flex, ScaleFade } from '@chakra-ui/react';
|
import { Container, Flex, ScaleFade } from '@chakra-ui/react';
|
||||||
import { motionChakra } from '~/elements';
|
import { motionChakra } from '~/elements';
|
||||||
import { useBooleanValue, useBreakpointValue, useFormInteractive } from '~/hooks';
|
import { useBooleanValue, useBreakpointValue, useFormInteractive } from '~/hooks';
|
||||||
import { Title } from './title';
|
import { Title } from './title';
|
||||||
|
|
||||||
const Wrapper = motionChakra('header', {
|
const Wrapper = motionChakra('header');
|
||||||
baseStyle: { display: 'flex', px: 4, pt: 6, minH: 16, w: 'full', flex: '0 1 auto' },
|
|
||||||
});
|
|
||||||
|
|
||||||
export const Header = (): JSX.Element => {
|
export const Header = (): JSX.Element => {
|
||||||
const formInteractive = useFormInteractive();
|
const formInteractive = useFormInteractive();
|
||||||
|
|
@ -18,6 +16,14 @@ export const Header = (): JSX.Element => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper layout="position">
|
<Wrapper layout="position">
|
||||||
|
<Container
|
||||||
|
maxW="8xl"
|
||||||
|
display="flex"
|
||||||
|
px={4}
|
||||||
|
pt={6}
|
||||||
|
minH={16}
|
||||||
|
flex="0 1 auto"
|
||||||
|
>
|
||||||
<ScaleFade in initialScale={0.5} style={{ width: '100%' }}>
|
<ScaleFade in initialScale={0.5} style={{ width: '100%' }}>
|
||||||
<Flex
|
<Flex
|
||||||
height="100%"
|
height="100%"
|
||||||
|
|
@ -29,6 +35,7 @@ export const Header = (): JSX.Element => {
|
||||||
<Title />
|
<Title />
|
||||||
</Flex>
|
</Flex>
|
||||||
</ScaleFade>
|
</ScaleFade>
|
||||||
|
</Container>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,46 +1,17 @@
|
||||||
import { Image, Skeleton } from '@chakra-ui/react';
|
import { Image, Skeleton } from '@chakra-ui/react';
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
|
||||||
import { useConfig } from '~/context';
|
import { useConfig } from '~/context';
|
||||||
import { useColorValue } from '~/hooks';
|
import { useColorValue } from '~/hooks';
|
||||||
|
|
||||||
import type { ImageProps } from '@chakra-ui/react';
|
import type { ImageProps } from '@chakra-ui/react';
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom hook to handle loading the user's logo, errors loading the logo, and color mode changes.
|
|
||||||
*/
|
|
||||||
function useLogo(): [string, () => void] {
|
|
||||||
const { web } = useConfig();
|
|
||||||
const { darkFormat, lightFormat } = web.logo;
|
|
||||||
|
|
||||||
const src = useColorValue(`/images/light${darkFormat}`, `/images/dark${lightFormat}`);
|
|
||||||
|
|
||||||
// Use the hyperglass logo if the user's logo can't be loaded for whatever reason.
|
|
||||||
const [fallback, setSource] = useState<string | null>(null);
|
|
||||||
|
|
||||||
// If the user image cannot be loaded, log an error to the console and set the fallback image.
|
|
||||||
const setFallback = useCallback(() => {
|
|
||||||
console.warn(`Error loading image from '${src}'`);
|
|
||||||
setSource('https://res.cloudinary.com/hyperglass/image/upload/v1593916013/logo-light.svg');
|
|
||||||
}, [src]);
|
|
||||||
|
|
||||||
// Only return the fallback image if it's been set.
|
|
||||||
return useMemo(() => [fallback ?? src, setFallback], [fallback, setFallback, src]);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Logo = (props: ImageProps): JSX.Element => {
|
export const Logo = (props: ImageProps): JSX.Element => {
|
||||||
const { web } = useConfig();
|
const { web } = useConfig();
|
||||||
const { width } = web.logo;
|
const { lightFormat, width } = web.logo;
|
||||||
|
|
||||||
const skeletonA = useColorValue('whiteSolid.100', 'blackSolid.800');
|
|
||||||
const skeletonB = useColorValue('light.500', 'dark.500');
|
|
||||||
|
|
||||||
const [source, setFallback] = useLogo();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Image
|
<Image
|
||||||
src={source}
|
src={`/images/light${lightFormat}`}
|
||||||
alt={web.text.title}
|
alt={web.text.title}
|
||||||
onError={setFallback}
|
|
||||||
maxW={{ base: '100%', md: width }}
|
maxW={{ base: '100%', md: width }}
|
||||||
width="auto"
|
width="auto"
|
||||||
css={{
|
css={{
|
||||||
|
|
@ -55,8 +26,8 @@ export const Logo = (props: ImageProps): JSX.Element => {
|
||||||
<Skeleton
|
<Skeleton
|
||||||
isLoaded={false}
|
isLoaded={false}
|
||||||
borderRadius="md"
|
borderRadius="md"
|
||||||
endColor={skeletonB}
|
endColor="light.500"
|
||||||
startColor={skeletonA}
|
startColor="whiteSolid.100"
|
||||||
width={{ base: 64, lg: 80 }}
|
width={{ base: 64, lg: 80 }}
|
||||||
height={{ base: 12, lg: 16 }}
|
height={{ base: 12, lg: 16 }}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Flex } from '@chakra-ui/react';
|
import { Container, Flex } from '@chakra-ui/react';
|
||||||
import { useCallback, useRef } from 'react';
|
import { useCallback, useRef } from 'react';
|
||||||
import { isSafari } from 'react-device-detect';
|
import { isSafari } from 'react-device-detect';
|
||||||
import { If, Then } from 'react-if';
|
import { If, Then } from 'react-if';
|
||||||
|
|
@ -53,6 +53,7 @@ export const Layout = (props: FlexProps): JSX.Element => {
|
||||||
minHeight={isSafari ? '-webkit-fill-available' : '100vh'}
|
minHeight={isSafari ? '-webkit-fill-available' : '100vh'}
|
||||||
>
|
>
|
||||||
<Header />
|
<Header />
|
||||||
|
<Container maxW="8xl" display="flex" flex="1 1 auto">
|
||||||
<Main
|
<Main
|
||||||
layout
|
layout
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
|
@ -62,6 +63,7 @@ export const Layout = (props: FlexProps): JSX.Element => {
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
</Main>
|
</Main>
|
||||||
|
</Container>
|
||||||
<Footer />
|
<Footer />
|
||||||
<If condition={developerMode}>
|
<If condition={developerMode}>
|
||||||
<Then>
|
<Then>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { Flex, Avatar, chakra } from '@chakra-ui/react';
|
import { Flex, Avatar, chakra } from '@chakra-ui/react';
|
||||||
import { motionChakra } from '~/elements';
|
import { motionChakra } from '~/elements';
|
||||||
import { useColorValue, useOpposingColor } from '~/hooks';
|
|
||||||
|
|
||||||
import type { SingleOption } from '~/types';
|
import type { SingleOption } from '~/types';
|
||||||
import type { LocationOption } from './query-location';
|
import type { LocationOption } from './query-location';
|
||||||
|
|
@ -43,11 +42,9 @@ export const LocationCard = (props: LocationCardProps): JSX.Element => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const bg = useColorValue('white', 'blackSolid.600');
|
const imageBorder = 'gray.600';
|
||||||
const imageBorder = useColorValue('gray.600', 'whiteAlpha.800');
|
const checkedBorder = 'brand.500';
|
||||||
const fg = useOpposingColor(bg);
|
const errorBorder = 'red.500';
|
||||||
const checkedBorder = useColorValue('blue.400', 'blue.300');
|
|
||||||
const errorBorder = useColorValue('red.500', 'red.300');
|
|
||||||
|
|
||||||
const borderColor = useMemo(
|
const borderColor = useMemo(
|
||||||
() =>
|
() =>
|
||||||
|
|
@ -64,7 +61,7 @@ export const LocationCard = (props: LocationCardProps): JSX.Element => {
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<LocationCardWrapper
|
<LocationCardWrapper
|
||||||
bg={bg}
|
bg="white"
|
||||||
key={label}
|
key={label}
|
||||||
whileHover={{ scale: 1.05 }}
|
whileHover={{ scale: 1.05 }}
|
||||||
borderColor={borderColor}
|
borderColor={borderColor}
|
||||||
|
|
@ -76,7 +73,6 @@ export const LocationCard = (props: LocationCardProps): JSX.Element => {
|
||||||
<>
|
<>
|
||||||
<Flex justifyContent="space-between" alignItems="center">
|
<Flex justifyContent="space-between" alignItems="center">
|
||||||
<chakra.h2
|
<chakra.h2
|
||||||
color={fg}
|
|
||||||
fontWeight="bold"
|
fontWeight="bold"
|
||||||
mt={{ base: 2, md: 0 }}
|
mt={{ base: 2, md: 0 }}
|
||||||
fontSize={{ base: 'lg', md: 'xl' }}
|
fontSize={{ base: 'lg', md: 'xl' }}
|
||||||
|
|
@ -84,7 +80,7 @@ export const LocationCard = (props: LocationCardProps): JSX.Element => {
|
||||||
{label}
|
{label}
|
||||||
</chakra.h2>
|
</chakra.h2>
|
||||||
<Avatar
|
<Avatar
|
||||||
color={fg}
|
color="black"
|
||||||
name={label}
|
name={label}
|
||||||
boxSize={12}
|
boxSize={12}
|
||||||
rounded="full"
|
rounded="full"
|
||||||
|
|
@ -97,7 +93,7 @@ export const LocationCard = (props: LocationCardProps): JSX.Element => {
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
{option?.data?.description && (
|
{option?.data?.description && (
|
||||||
<chakra.p mt={2} color={fg} opacity={0.6} fontSize="sm">
|
<chakra.p mt={2} opacity={0.6} fontSize="sm">
|
||||||
{option.data.description as string}
|
{option.data.description as string}
|
||||||
</chakra.p>
|
</chakra.p>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -173,7 +173,7 @@ export const LookingGlassForm = (): JSX.Element => {
|
||||||
w="100%"
|
w="100%"
|
||||||
mx="auto"
|
mx="auto"
|
||||||
textAlign="left"
|
textAlign="left"
|
||||||
maxW={{ base: '100%', lg: '75%' }}
|
maxW="100%"
|
||||||
onSubmit={handleSubmit(submitHandler)}
|
onSubmit={handleSubmit(submitHandler)}
|
||||||
>
|
>
|
||||||
<FormRow>
|
<FormRow>
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ export const Meta = (): JSX.Element => {
|
||||||
const [location, setLocation] = useState('/');
|
const [location, setLocation] = useState('/');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
siteTitle: title = 'hyperglass',
|
siteTitle: title = 'Looking Glass',
|
||||||
siteDescription: description = 'Network Looking Glass',
|
siteDescription: description = 'Network Looking Glass',
|
||||||
} = useConfig();
|
} = useConfig();
|
||||||
|
|
||||||
|
|
@ -23,16 +23,13 @@ export const Meta = (): JSX.Element => {
|
||||||
<Head>
|
<Head>
|
||||||
<title key="title">{title}</title>
|
<title key="title">{title}</title>
|
||||||
<meta name="url" content={location} />
|
<meta name="url" content={location} />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="og:title" content={title} />
|
<meta name="og:title" content={title} />
|
||||||
<meta name="og:url" content={location} />
|
<meta name="og:url" content={location} />
|
||||||
<meta name="description" content={description} />
|
<meta name="description" content={description} />
|
||||||
<meta property="og:image:alt" content={siteName} />
|
<meta property="og:image:alt" content={siteName} />
|
||||||
<meta name="og:description" content={description} />
|
<meta name="og:description" content={description} />
|
||||||
<meta name="hyperglass-version" content={config.version} />
|
<meta name="lg-version" content={config.version} />
|
||||||
<meta
|
|
||||||
name="viewport"
|
|
||||||
content="width=device-width, initial-scale=1, user-scalable=no, maximum-scale=1.0, minimum-scale=1.0"
|
|
||||||
/>
|
|
||||||
</Head>
|
</Head>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -147,8 +147,6 @@ export const ASPath = (props: ASPathProps): JSX.Element => {
|
||||||
export const Communities = (props: CommunitiesProps): JSX.Element => {
|
export const Communities = (props: CommunitiesProps): JSX.Element => {
|
||||||
const { communities } = props;
|
const { communities } = props;
|
||||||
const { web } = useConfig();
|
const { web } = useConfig();
|
||||||
const bg = useColorValue('white', 'gray.900');
|
|
||||||
const color = useOpposingColor(bg);
|
|
||||||
return (
|
return (
|
||||||
<If condition={communities.length === 0}>
|
<If condition={communities.length === 0}>
|
||||||
<Then>
|
<Then>
|
||||||
|
|
@ -165,10 +163,10 @@ export const Communities = (props: CommunitiesProps): JSX.Element => {
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
<MenuList
|
<MenuList
|
||||||
p={3}
|
p={3}
|
||||||
bg={bg}
|
bg="white"
|
||||||
minW={32}
|
minW={32}
|
||||||
width="unset"
|
width="unset"
|
||||||
color={color}
|
color="black"
|
||||||
boxShadow="2xl"
|
boxShadow="2xl"
|
||||||
textAlign="left"
|
textAlign="left"
|
||||||
fontFamily="mono"
|
fontFamily="mono"
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ export const PathButton = (props: PathButtonProps): JSX.Element => {
|
||||||
const { onOpen } = props;
|
const { onOpen } = props;
|
||||||
return (
|
return (
|
||||||
<Tooltip hasArrow label="View AS Path" placement="top">
|
<Tooltip hasArrow label="View AS Path" placement="top">
|
||||||
<Button as="a" mx={1} size="sm" variant="ghost" onClick={onOpen} colorScheme="secondary">
|
<Button as="a" mx={1} size="sm" variant="ghost" onClick={onOpen} colorScheme="primary">
|
||||||
<DynamicIcon icon={{ bi: 'BiNetworkChart' }} boxSize="16px" />
|
<DynamicIcon icon={{ bi: 'BiNetworkChart' }} boxSize="16px" />
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
|
||||||
|
|
@ -6,19 +6,17 @@ import {
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverCloseButton,
|
PopoverCloseButton,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { useColorValue } from '~/hooks';
|
|
||||||
|
|
||||||
import type { PromptProps } from './types';
|
import type { PromptProps } from './types';
|
||||||
|
|
||||||
export const DesktopPrompt = (props: PromptProps): JSX.Element => {
|
export const DesktopPrompt = (props: PromptProps): JSX.Element => {
|
||||||
const { trigger, children, ...disclosure } = props;
|
const { trigger, children, ...disclosure } = props;
|
||||||
const bg = useColorValue('white', 'gray.900');
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover closeOnBlur={false} {...disclosure}>
|
<Popover closeOnBlur={false} {...disclosure}>
|
||||||
<PopoverTrigger>{trigger}</PopoverTrigger>
|
<PopoverTrigger>{trigger}</PopoverTrigger>
|
||||||
<PopoverContent bg={bg}>
|
<PopoverContent bg="white">
|
||||||
<PopoverArrow bg={bg} />
|
<PopoverArrow bg="white" />
|
||||||
<PopoverCloseButton />
|
<PopoverCloseButton />
|
||||||
<PopoverBody p={6}>{children}</PopoverBody>
|
<PopoverBody p={6}>{children}</PopoverBody>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
import { Modal, ModalBody, ModalOverlay, ModalContent, ModalCloseButton } from '@chakra-ui/react';
|
import { Modal, ModalBody, ModalOverlay, ModalContent, ModalCloseButton } from '@chakra-ui/react';
|
||||||
import { useColorValue } from '~/hooks';
|
|
||||||
|
|
||||||
import type { PromptProps } from './types';
|
import type { PromptProps } from './types';
|
||||||
|
|
||||||
export const MobilePrompt = (props: PromptProps): JSX.Element => {
|
export const MobilePrompt = (props: PromptProps): JSX.Element => {
|
||||||
const { children, trigger, ...disclosure } = props;
|
const { children, trigger, ...disclosure } = props;
|
||||||
const bg = useColorValue('white', 'gray.900');
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{trigger}
|
{trigger}
|
||||||
|
|
@ -18,7 +16,7 @@ export const MobilePrompt = (props: PromptProps): JSX.Element => {
|
||||||
{...disclosure}
|
{...disclosure}
|
||||||
>
|
>
|
||||||
<ModalOverlay />
|
<ModalOverlay />
|
||||||
<ModalContent bg={bg}>
|
<ModalContent bg="white">
|
||||||
<ModalCloseButton />
|
<ModalCloseButton />
|
||||||
<ModalBody px={4} py={10}>
|
<ModalBody px={4} py={10}>
|
||||||
{children}
|
{children}
|
||||||
|
|
|
||||||
|
|
@ -120,10 +120,10 @@ export const QueryLocation = (props: QueryLocationProps): JSX.Element => {
|
||||||
<>
|
<>
|
||||||
{options.length === 1 ? (
|
{options.length === 1 ? (
|
||||||
<Wrap
|
<Wrap
|
||||||
p={{ lg: 4 }}
|
p={{ lg: 2 }}
|
||||||
align="flex-start"
|
align="flex-start"
|
||||||
shouldWrapChildren
|
shouldWrapChildren
|
||||||
spacing={{ base: 4, lg: 8 }}
|
spacing={{ base: 4, lg: 6 }}
|
||||||
justify={{ base: 'center', lg: 'center' }}
|
justify={{ base: 'center', lg: 'center' }}
|
||||||
>
|
>
|
||||||
{options[0].options.map(opt => {
|
{options[0].options.map(opt => {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import { useDirective, useFormState } from '~/hooks';
|
||||||
import { isSelectDirective } from '~/types';
|
import { isSelectDirective } from '~/types';
|
||||||
import { UserIP } from './user-ip';
|
import { UserIP } from './user-ip';
|
||||||
|
|
||||||
import { type UseFormRegister, useForm } from 'react-hook-form';
|
import { type UseFormRegister } from 'react-hook-form';
|
||||||
import type { GroupBase, OptionProps } from 'react-select';
|
import type { GroupBase, OptionProps } from 'react-select';
|
||||||
import type { SelectOnChange } from '~/components/select';
|
import type { SelectOnChange } from '~/components/select';
|
||||||
import type { Directive, FormData, OnChangeArgs, SingleOption } from '~/types';
|
import type { Directive, FormData, OnChangeArgs, SingleOption } from '~/types';
|
||||||
|
|
@ -87,7 +87,7 @@ export const QueryTarget = (props: QueryTargetProps): JSX.Element => {
|
||||||
<InputGroup size="lg">
|
<InputGroup size="lg">
|
||||||
<Input
|
<Input
|
||||||
bg="white"
|
bg="white"
|
||||||
color="gray.400"
|
color="black"
|
||||||
borderRadius="md"
|
borderRadius="md"
|
||||||
borderColor="gray.100"
|
borderColor="gray.100"
|
||||||
value={displayTarget}
|
value={displayTarget}
|
||||||
|
|
@ -96,12 +96,6 @@ export const QueryTarget = (props: QueryTargetProps): JSX.Element => {
|
||||||
name="queryTargetDisplay"
|
name="queryTargetDisplay"
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
_placeholder={{ color: 'gray.600' }}
|
_placeholder={{ color: 'gray.600' }}
|
||||||
_dark={{
|
|
||||||
bg: 'blackSolid.800',
|
|
||||||
color: 'whiteAlpha.800',
|
|
||||||
borderColor: 'whiteAlpha.50',
|
|
||||||
_placeholder: { color: 'whiteAlpha.700' },
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<InputRightElement w="max-content" pr={2}>
|
<InputRightElement w="max-content" pr={2}>
|
||||||
<UserIP setTarget={handleUserIPChange} />
|
<UserIP setTarget={handleUserIPChange} />
|
||||||
|
|
|
||||||
|
|
@ -13,18 +13,16 @@ interface ResetButtonProps extends FlexProps {
|
||||||
export const ResetButton = (props: ResetButtonProps): JSX.Element => {
|
export const ResetButton = (props: ResetButtonProps): JSX.Element => {
|
||||||
const { developerMode, resetForm, ...rest } = props;
|
const { developerMode, resetForm, ...rest } = props;
|
||||||
const status = useFormState(s => s.status);
|
const status = useFormState(s => s.status);
|
||||||
const bg = useColorValue('primary.500', 'primary.300');
|
|
||||||
const color = useOpposingColor(bg);
|
|
||||||
return (
|
return (
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{status === 'results' && (
|
{status === 'results' && (
|
||||||
<AnimatedDiv
|
<AnimatedDiv
|
||||||
bg={bg}
|
bg="brand.500"
|
||||||
left={0}
|
left={0}
|
||||||
zIndex={4}
|
zIndex={4}
|
||||||
bottom={24}
|
bottom={24}
|
||||||
boxSize={12}
|
boxSize={12}
|
||||||
color={color}
|
color="white"
|
||||||
position="fixed"
|
position="fixed"
|
||||||
animate={{ x: 0 }}
|
animate={{ x: 0 }}
|
||||||
exit={{ x: '-100%' }}
|
exit={{ x: '-100%' }}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { useMemo } from 'react';
|
||||||
import { Button, Stack, Text, VStack } from '@chakra-ui/react';
|
import { Button, Stack, Text, VStack } from '@chakra-ui/react';
|
||||||
import { useConfig } from '~/context';
|
import { useConfig } from '~/context';
|
||||||
import { DynamicIcon } from '~/elements';
|
import { DynamicIcon } from '~/elements';
|
||||||
import { useStrf, useColorValue, useDNSQuery, useFormState } from '~/hooks';
|
import { useStrf, useDNSQuery, useFormState } from '~/hooks';
|
||||||
|
|
||||||
import type { DnsOverHttps } from '~/types';
|
import type { DnsOverHttps } from '~/types';
|
||||||
|
|
||||||
|
|
@ -28,8 +28,8 @@ export const ResolvedTarget = (props: ResolvedTargetProps): JSX.Element => {
|
||||||
const displayTarget = useFormState(s => s.target.display);
|
const displayTarget = useFormState(s => s.target.display);
|
||||||
const setFormValue = useFormState(s => s.setFormValue);
|
const setFormValue = useFormState(s => s.setFormValue);
|
||||||
|
|
||||||
const color = useColorValue('secondary.500', 'secondary.300');
|
const color = 'secondary.500';
|
||||||
const errorColor = useColorValue('red.500', 'red.300');
|
const errorColor = 'red.500';
|
||||||
|
|
||||||
const tooltip4 = strF(web.text.fqdnTooltip, { protocol: 'IPv4' });
|
const tooltip4 = strF(web.text.fqdnTooltip, { protocol: 'IPv4' });
|
||||||
const tooltip6 = strF(web.text.fqdnTooltip, { protocol: 'IPv6' });
|
const tooltip6 = strF(web.text.fqdnTooltip, { protocol: 'IPv6' });
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ export const CopyButton = (props: CopyButtonProps): JSX.Element => {
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={onCopy}
|
onClick={onCopy}
|
||||||
colorScheme="secondary"
|
colorScheme="primary"
|
||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
<DynamicIcon icon={{ fi: hasCopied ? 'FiCheck' : 'FiCopy' }} boxSize="16px" />
|
<DynamicIcon icon={{ fi: hasCopied ? 'FiCheck' : 'FiCopy' }} boxSize="16px" />
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { AccordionIcon, Box, HStack, Spinner, Text, Tooltip } from '@chakra-ui/r
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useConfig } from '~/context';
|
import { useConfig } from '~/context';
|
||||||
import { DynamicIcon } from '~/elements';
|
import { DynamicIcon } from '~/elements';
|
||||||
import { useColorValue, useOpposingColor, useStrf } from '~/hooks';
|
import { useOpposingColor, useStrf } from '~/hooks';
|
||||||
|
|
||||||
import type { ErrorLevels } from '~/types';
|
import type { ErrorLevels } from '~/types';
|
||||||
|
|
||||||
|
|
@ -26,9 +26,9 @@ const runtimeText = (runtime: number, text: string): string => {
|
||||||
export const ResultHeader = (props: ResultHeaderProps): JSX.Element => {
|
export const ResultHeader = (props: ResultHeaderProps): JSX.Element => {
|
||||||
const { title, loading, isError, errorMsg, errorLevel, runtime } = props;
|
const { title, loading, isError, errorMsg, errorLevel, runtime } = props;
|
||||||
|
|
||||||
const status = useColorValue('primary.500', 'primary.300');
|
const status = 'primary.500';
|
||||||
const warning = useColorValue(`${errorLevel}.500`, `${errorLevel}.300`);
|
const warning = `${errorLevel}.500`;
|
||||||
const defaultStatus = useColorValue('success.500', 'success.300');
|
const defaultStatus = 'success.500';
|
||||||
|
|
||||||
const { web } = useConfig();
|
const { web } = useConfig();
|
||||||
const strF = useStrf();
|
const strF = useStrf();
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ const AccordionHeaderWrapper = chakra('div', {
|
||||||
baseStyle: {
|
baseStyle: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
|
bg: 'white',
|
||||||
_hover: { bg: 'blackAlpha.50' },
|
_hover: { bg: 'blackAlpha.50' },
|
||||||
_focus: { boxShadow: 'outline' },
|
_focus: { boxShadow: 'outline' },
|
||||||
},
|
},
|
||||||
|
|
@ -223,6 +224,7 @@ const _Result: React.ForwardRefRenderFunction<HTMLDivElement, ResultProps> = (
|
||||||
</AccordionHeaderWrapper>
|
</AccordionHeaderWrapper>
|
||||||
<AccordionPanel
|
<AccordionPanel
|
||||||
pb={4}
|
pb={4}
|
||||||
|
bg="white"
|
||||||
overflowX="auto"
|
overflowX="auto"
|
||||||
css={{
|
css={{
|
||||||
WebkitOverflowScrolling: 'touch',
|
WebkitOverflowScrolling: 'touch',
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ const _RequeryButton: React.ForwardRefRenderFunction<HTMLButtonElement, RequeryB
|
||||||
zIndex="1"
|
zIndex="1"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={requery as Get<RequeryButtonProps, 'onClick'>}
|
onClick={requery as Get<RequeryButtonProps, 'onClick'>}
|
||||||
colorScheme="secondary"
|
colorScheme="primary"
|
||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
<DynamicIcon icon={{ fi: 'FiRepeat' }} boxSize="16px" />
|
<DynamicIcon icon={{ fi: 'FiRepeat' }} boxSize="16px" />
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ export const Option = <Opt extends SingleOption, IsMulti extends boolean>(
|
||||||
display={{ base: 'flex', lg: 'inline-flex' }}
|
display={{ base: 'flex', lg: 'inline-flex' }}
|
||||||
>
|
>
|
||||||
{tags.map(tag => (
|
{tags.map(tag => (
|
||||||
<Badge fontSize="xs" variant="subtle" key={tag} colorScheme="gray" textTransform="none">
|
<Badge fontSize="xs" variant="subtle" key={tag} colorScheme="primary" textTransform="none">
|
||||||
{tag}
|
{tag}
|
||||||
</Badge>
|
</Badge>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,6 @@ import { mergeWith } from '@chakra-ui/utils';
|
||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import {
|
import {
|
||||||
useColorToken,
|
|
||||||
useColorValue,
|
|
||||||
useMobile,
|
useMobile,
|
||||||
useOpposingColor,
|
useOpposingColor,
|
||||||
useOpposingColorCallback,
|
useOpposingColorCallback,
|
||||||
|
|
@ -30,26 +28,20 @@ export const useControlStyle = <Opt extends SingleOption, IsMulti extends boolea
|
||||||
|
|
||||||
const { isError } = useSelectContext();
|
const { isError } = useSelectContext();
|
||||||
|
|
||||||
const minHeight = useToken('space', 12);
|
const focusBorder = useToken('colors', 'brand.500');
|
||||||
const borderRadius = useToken('radii', 'md');
|
const invalidBorder = useToken('colors', 'red.500');
|
||||||
const color = useColorToken('colors', 'black', 'whiteAlpha.800');
|
const borderHover = useToken('colors', 'gray.300');
|
||||||
const focusBorder = useColorToken('colors', 'blue.500', 'blue.300');
|
|
||||||
const invalidBorder = useColorToken('colors', 'red.500', 'red.300');
|
|
||||||
// const borderColor = useColorToken('colors', 'gray.200', 'whiteAlpha.300');
|
|
||||||
const borderColor = useColorToken('colors', 'gray.100', 'whiteAlpha.50');
|
|
||||||
const borderHover = useColorToken('colors', 'gray.300', 'whiteAlpha.400');
|
|
||||||
const backgroundColor = useColorToken('colors', 'white', 'blackSolid.800');
|
|
||||||
|
|
||||||
return useCallback(
|
return useCallback(
|
||||||
(base, state) => {
|
(base, state) => {
|
||||||
const { isFocused } = state;
|
const { isFocused } = state;
|
||||||
const styles = {
|
const styles = {
|
||||||
backgroundColor,
|
backgroundColor: useToken('colors', 'white'),
|
||||||
borderRadius,
|
borderRadius: useToken('radii', 'md'),
|
||||||
color,
|
color: useToken('colors', 'black'),
|
||||||
minHeight,
|
minHeight: useToken('space', 12),
|
||||||
transition: 'all 0.2s',
|
transition: 'all 0.2s',
|
||||||
borderColor: isError ? invalidBorder : isFocused ? focusBorder : borderColor,
|
borderColor: isError ? invalidBorder : isFocused ? focusBorder : useToken('gray.100'),
|
||||||
boxShadow: isError
|
boxShadow: isError
|
||||||
? `0 0 0 1px ${invalidBorder}`
|
? `0 0 0 1px ${invalidBorder}`
|
||||||
: isFocused
|
: isFocused
|
||||||
|
|
@ -72,9 +64,7 @@ export const useMenuStyle = <Opt extends SingleOption, IsMulti extends boolean>(
|
||||||
const { colorMode } = props;
|
const { colorMode } = props;
|
||||||
|
|
||||||
const { isOpen } = useSelectContext();
|
const { isOpen } = useSelectContext();
|
||||||
|
const styles = { backgroundColor: useToken('white'), zIndex: 1500 };
|
||||||
const backgroundColor = useColorToken('colors', 'white', 'blackSolid.700');
|
|
||||||
const styles = { backgroundColor, zIndex: 1500 };
|
|
||||||
|
|
||||||
return useCallback(base => mergeWith({}, base, styles), [colorMode, isOpen]);
|
return useCallback(base => mergeWith({}, base, styles), [colorMode, isOpen]);
|
||||||
};
|
};
|
||||||
|
|
@ -86,14 +76,12 @@ export const useMenuListStyle = <Opt extends SingleOption, IsMulti extends boole
|
||||||
|
|
||||||
const { isOpen } = useSelectContext();
|
const { isOpen } = useSelectContext();
|
||||||
|
|
||||||
const borderRadius = useToken('radii', 'md');
|
const scrollbarTrack = useToken('colors', 'blackAlpha.50');
|
||||||
const backgroundColor = useColorToken('colors', 'white', 'blackSolid.700');
|
const scrollbarThumb = useToken('colors', 'blackAlpha.300');
|
||||||
const scrollbarTrack = useColorToken('colors', 'blackAlpha.50', 'whiteAlpha.50');
|
const scrollbarThumbHover = useToken('colors', 'blackAlpha.400');
|
||||||
const scrollbarThumb = useColorToken('colors', 'blackAlpha.300', 'whiteAlpha.300');
|
|
||||||
const scrollbarThumbHover = useColorToken('colors', 'blackAlpha.400', 'whiteAlpha.400');
|
|
||||||
const styles = {
|
const styles = {
|
||||||
borderRadius,
|
borderRadius: useToken('radii', 'md'),
|
||||||
backgroundColor,
|
backgroundColor: useToken('colors', 'white'),
|
||||||
'&::-webkit-scrollbar': { width: '5px' },
|
'&::-webkit-scrollbar': { width: '5px' },
|
||||||
'&::-webkit-scrollbar-track': { backgroundColor: scrollbarTrack },
|
'&::-webkit-scrollbar-track': { backgroundColor: scrollbarTrack },
|
||||||
'&::-webkit-scrollbar-thumb': { backgroundColor: scrollbarThumb },
|
'&::-webkit-scrollbar-thumb': { backgroundColor: scrollbarThumb },
|
||||||
|
|
@ -113,12 +101,11 @@ export const useOptionStyle = <Opt extends SingleOption, IsMulti extends boolean
|
||||||
|
|
||||||
const fontSize = useToken('fontSizes', 'lg');
|
const fontSize = useToken('fontSizes', 'lg');
|
||||||
const disabled = useToken('colors', 'whiteAlpha.400');
|
const disabled = useToken('colors', 'whiteAlpha.400');
|
||||||
const active = useColorToken('colors', 'primary.600', 'primary.400');
|
const active = useToken('colors', 'brand.600');
|
||||||
const focused = useColorToken('colors', 'primary.500', 'primary.300');
|
const focused = useToken('colors', 'brand.500');
|
||||||
const selected = useColorToken('colors', 'blackAlpha.400', 'whiteAlpha.400');
|
const selected = useToken('colors', 'brand.600');
|
||||||
|
|
||||||
const activeColor = useOpposingColor(active);
|
const color = useToken('colors', 'white');
|
||||||
const getColor = useOpposingColorCallback();
|
|
||||||
|
|
||||||
return useCallback(
|
return useCallback(
|
||||||
(base, state) => {
|
(base, state) => {
|
||||||
|
|
@ -136,12 +123,11 @@ export const useOptionStyle = <Opt extends SingleOption, IsMulti extends boolean
|
||||||
backgroundColor = focused;
|
backgroundColor = focused;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
const color = getColor(backgroundColor);
|
|
||||||
|
|
||||||
const styles = {
|
const styles = {
|
||||||
color: backgroundColor === 'transparent' ? 'currentColor' : color,
|
color: backgroundColor === 'transparent' ? 'currentColor' : color,
|
||||||
'&:active': { backgroundColor: active, color: activeColor },
|
'&:active': { backgroundColor: active, color: color },
|
||||||
'&:focus': { backgroundColor: active, color: activeColor },
|
'&:focus': { backgroundColor: active, color: color },
|
||||||
backgroundColor,
|
backgroundColor,
|
||||||
fontSize,
|
fontSize,
|
||||||
};
|
};
|
||||||
|
|
@ -156,8 +142,7 @@ export const useIndicatorSeparatorStyle = <Opt extends SingleOption, IsMulti ext
|
||||||
props: RSStyleCallbackProps,
|
props: RSStyleCallbackProps,
|
||||||
): RSStyleFunction<'indicatorSeparator', Opt, IsMulti> => {
|
): RSStyleFunction<'indicatorSeparator', Opt, IsMulti> => {
|
||||||
const { colorMode } = props;
|
const { colorMode } = props;
|
||||||
const backgroundColor = useColorToken('colors', 'gray.200', 'whiteAlpha.300');
|
const styles = { backgroundColor: useToken('colors', 'gray.200') };
|
||||||
const styles = { backgroundColor };
|
|
||||||
|
|
||||||
return useCallback(base => mergeWith({}, base, styles), [colorMode]);
|
return useCallback(base => mergeWith({}, base, styles), [colorMode]);
|
||||||
};
|
};
|
||||||
|
|
@ -167,10 +152,12 @@ export const usePlaceholderStyle = <Opt extends SingleOption, IsMulti extends bo
|
||||||
): RSStyleFunction<'placeholder', Opt, IsMulti> => {
|
): RSStyleFunction<'placeholder', Opt, IsMulti> => {
|
||||||
const { colorMode } = props;
|
const { colorMode } = props;
|
||||||
|
|
||||||
const color = useColorToken('colors', 'gray.600', 'whiteAlpha.700');
|
const styles = {
|
||||||
const fontSize = useToken('fontSizes', 'lg');
|
color: useToken('colors', 'gray.600'),
|
||||||
|
fontSize: useToken('fontSizes', 'lg')
|
||||||
|
}
|
||||||
|
|
||||||
return useCallback(base => mergeWith({}, base, { color, fontSize }), [colorMode]);
|
return useCallback(base => mergeWith({}, base, styles), [colorMode]);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useSingleValueStyle = <Opt extends SingleOption, IsMulti extends boolean>(
|
export const useSingleValueStyle = <Opt extends SingleOption, IsMulti extends boolean>(
|
||||||
|
|
@ -178,9 +165,11 @@ export const useSingleValueStyle = <Opt extends SingleOption, IsMulti extends bo
|
||||||
): RSStyleFunction<'singleValue', Opt, IsMulti> => {
|
): RSStyleFunction<'singleValue', Opt, IsMulti> => {
|
||||||
const { colorMode } = props;
|
const { colorMode } = props;
|
||||||
|
|
||||||
const color = useColorValue('black', 'whiteAlpha.800');
|
const color = useToken('colors', 'black');
|
||||||
const fontSize = useToken('fontSizes', 'lg');
|
const styles = {
|
||||||
const styles = { color, fontSize };
|
color,
|
||||||
|
fontSize: useToken('fontSizes', 'lg')
|
||||||
|
};
|
||||||
|
|
||||||
return useCallback(base => mergeWith({}, base, styles), [color, colorMode]);
|
return useCallback(base => mergeWith({}, base, styles), [color, colorMode]);
|
||||||
};
|
};
|
||||||
|
|
@ -190,10 +179,12 @@ export const useMultiValueStyle = <Opt extends SingleOption, IsMulti extends boo
|
||||||
): RSStyleFunction<'multiValue', Opt, IsMulti> => {
|
): RSStyleFunction<'multiValue', Opt, IsMulti> => {
|
||||||
const { colorMode } = props;
|
const { colorMode } = props;
|
||||||
|
|
||||||
const backgroundColor = useColorToken('colors', 'primary.500', 'primary.300');
|
const backgroundColor = useToken('colors', 'brand.500');
|
||||||
const color = useOpposingColor(backgroundColor);
|
const styles = {
|
||||||
const borderRadius = useToken('radii', 'md');
|
backgroundColor: backgroundColor,
|
||||||
const styles = { backgroundColor, color, borderRadius };
|
color: useToken('colors', 'white'),
|
||||||
|
borderRadius: useToken('radii', 'md')
|
||||||
|
};
|
||||||
|
|
||||||
return useCallback(base => mergeWith({}, base, styles), [backgroundColor, colorMode]);
|
return useCallback(base => mergeWith({}, base, styles), [backgroundColor, colorMode]);
|
||||||
};
|
};
|
||||||
|
|
@ -203,9 +194,7 @@ export const useMultiValueLabelStyle = <Opt extends SingleOption, IsMulti extend
|
||||||
): RSStyleFunction<'multiValueLabel', Opt, IsMulti> => {
|
): RSStyleFunction<'multiValueLabel', Opt, IsMulti> => {
|
||||||
const { colorMode } = props;
|
const { colorMode } = props;
|
||||||
|
|
||||||
const backgroundColor = useColorToken('colors', 'primary.500', 'primary.300');
|
const styles = { color: useToken('colors', 'white') };
|
||||||
const color = useOpposingColor(backgroundColor);
|
|
||||||
const styles = { color };
|
|
||||||
|
|
||||||
return useCallback(base => mergeWith({}, base, styles), [colorMode]);
|
return useCallback(base => mergeWith({}, base, styles), [colorMode]);
|
||||||
};
|
};
|
||||||
|
|
@ -215,10 +204,9 @@ export const useMultiValueRemoveStyle = <Opt extends SingleOption, IsMulti exten
|
||||||
): RSStyleFunction<'multiValueRemove', Opt, IsMulti> => {
|
): RSStyleFunction<'multiValueRemove', Opt, IsMulti> => {
|
||||||
const { colorMode } = props;
|
const { colorMode } = props;
|
||||||
|
|
||||||
const backgroundColor = useColorToken('colors', 'primary.500', 'primary.300');
|
const color = useToken('colors', 'white');
|
||||||
const color = useOpposingColor(backgroundColor);
|
|
||||||
const styles = {
|
const styles = {
|
||||||
color,
|
color: color,
|
||||||
'&:hover': { backgroundColor: 'transparent', color, opacity: 0.8 },
|
'&:hover': { backgroundColor: 'transparent', color, opacity: 0.8 },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import { useFormContext } from 'react-hook-form';
|
||||||
import { Else, If, Then } from 'react-if';
|
import { Else, If, Then } from 'react-if';
|
||||||
import { ResolvedTarget } from '~/components';
|
import { ResolvedTarget } from '~/components';
|
||||||
import { DynamicIcon } from '~/elements';
|
import { DynamicIcon } from '~/elements';
|
||||||
import { useColorValue, useFormState, useMobile } from '~/hooks';
|
import { useFormState, useMobile } from '~/hooks';
|
||||||
|
|
||||||
import type { IconButtonProps } from '@chakra-ui/react';
|
import type { IconButtonProps } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
|
@ -42,7 +42,8 @@ const _SubmitIcon: React.ForwardRefRenderFunction<
|
||||||
type="submit"
|
type="submit"
|
||||||
icon={<DynamicIcon icon={{ fi: 'FiSearch' }} />}
|
icon={<DynamicIcon icon={{ fi: 'FiSearch' }} />}
|
||||||
title="Submit Query"
|
title="Submit Query"
|
||||||
colorScheme="primary"
|
variant="solid"
|
||||||
|
colorScheme="brand"
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
aria-label="Submit Query"
|
aria-label="Submit Query"
|
||||||
{...rest}
|
{...rest}
|
||||||
|
|
@ -56,7 +57,6 @@ const SubmitIcon = forwardRef<HTMLButtonElement, Omit<IconButtonProps, 'aria-lab
|
||||||
*/
|
*/
|
||||||
const MSubmitButton = (props: ResponsiveSubmitButtonProps): JSX.Element => {
|
const MSubmitButton = (props: ResponsiveSubmitButtonProps): JSX.Element => {
|
||||||
const { children, isOpen, onClose } = props;
|
const { children, isOpen, onClose } = props;
|
||||||
const bg = useColorValue('white', 'gray.900');
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{children}
|
{children}
|
||||||
|
|
@ -70,7 +70,7 @@ const MSubmitButton = (props: ResponsiveSubmitButtonProps): JSX.Element => {
|
||||||
motionPreset="slideInBottom"
|
motionPreset="slideInBottom"
|
||||||
>
|
>
|
||||||
<ModalOverlay />
|
<ModalOverlay />
|
||||||
<ModalContent bg={bg}>
|
<ModalContent bg="white">
|
||||||
<ModalCloseButton />
|
<ModalCloseButton />
|
||||||
<ModalBody px={4} py={10}>
|
<ModalBody px={4} py={10}>
|
||||||
{isOpen && <ResolvedTarget errorClose={onClose} />}
|
{isOpen && <ResolvedTarget errorClose={onClose} />}
|
||||||
|
|
@ -86,12 +86,11 @@ const MSubmitButton = (props: ResponsiveSubmitButtonProps): JSX.Element => {
|
||||||
*/
|
*/
|
||||||
const DSubmitButton = (props: ResponsiveSubmitButtonProps): JSX.Element => {
|
const DSubmitButton = (props: ResponsiveSubmitButtonProps): JSX.Element => {
|
||||||
const { children, isOpen, onClose } = props;
|
const { children, isOpen, onClose } = props;
|
||||||
const bg = useColorValue('white', 'gray.900');
|
|
||||||
return (
|
return (
|
||||||
<Popover isOpen={isOpen} onClose={onClose} closeOnBlur={false}>
|
<Popover isOpen={isOpen} onClose={onClose} closeOnBlur={false}>
|
||||||
<PopoverTrigger>{children}</PopoverTrigger>
|
<PopoverTrigger>{children}</PopoverTrigger>
|
||||||
<PopoverContent bg={bg}>
|
<PopoverContent bg="white">
|
||||||
<PopoverArrow bg={bg} />
|
<PopoverArrow bg="white" />
|
||||||
<PopoverCloseButton />
|
<PopoverCloseButton />
|
||||||
<PopoverBody p={6}>{isOpen && <ResolvedTarget errorClose={onClose} />}</PopoverBody>
|
<PopoverBody p={6}>{isOpen && <ResolvedTarget errorClose={onClose} />}</PopoverBody>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ export const UserIP = (props: UserIPProps): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<Prompt
|
<Prompt
|
||||||
trigger={
|
trigger={
|
||||||
<Button size="sm" onClick={handleOpen}>
|
<Button size="sm" onClick={handleOpen} colorScheme="brand">
|
||||||
{web.text.ipButton}
|
{web.text.ipButton}
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { Flex } from '@chakra-ui/react';
|
import { Flex } from '@chakra-ui/react';
|
||||||
import { useColorValue } from '~/hooks';
|
|
||||||
|
|
||||||
import type { FlexProps } from '@chakra-ui/react';
|
import type { FlexProps } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
|
@ -9,14 +8,12 @@ interface CardBodyProps extends Omit<FlexProps, 'onClick'> {
|
||||||
|
|
||||||
export const CardBody = (props: CardBodyProps): JSX.Element => {
|
export const CardBody = (props: CardBodyProps): JSX.Element => {
|
||||||
const { onClick, ...rest } = props;
|
const { onClick, ...rest } = props;
|
||||||
const bg = useColorValue('white', 'dark.500');
|
|
||||||
const color = useColorValue('dark.500', 'white');
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
bg={bg}
|
bg="white"
|
||||||
w="100%"
|
w="100%"
|
||||||
rounded="md"
|
rounded="md"
|
||||||
color={color}
|
color="dark.500"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
overflow="hidden"
|
overflow="hidden"
|
||||||
borderWidth="1px"
|
borderWidth="1px"
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,17 @@
|
||||||
import { Box } from '@chakra-ui/react';
|
import { Box } from '@chakra-ui/react';
|
||||||
import { useColorValue } from '~/hooks';
|
|
||||||
|
|
||||||
import type { BoxProps } from '@chakra-ui/react';
|
import type { BoxProps } from '@chakra-ui/react';
|
||||||
|
|
||||||
export const CodeBlock = (props: BoxProps): JSX.Element => {
|
export const CodeBlock = (props: BoxProps): JSX.Element => {
|
||||||
const bg = useColorValue('blackAlpha.100', 'gray.800');
|
|
||||||
const color = useColorValue('black', 'white');
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
p={3}
|
p={3}
|
||||||
mt={5}
|
mt={5}
|
||||||
bg={bg}
|
bg="blackAlpha.100"
|
||||||
as="pre"
|
as="pre"
|
||||||
border="1px"
|
border="1px"
|
||||||
rounded="md"
|
rounded="md"
|
||||||
color={color}
|
color="black"
|
||||||
fontSize="sm"
|
fontSize="sm"
|
||||||
fontFamily="mono"
|
fontFamily="mono"
|
||||||
borderColor="inherit"
|
borderColor="inherit"
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ const Renderer = (props: RendererProps): JSX.Element => {
|
||||||
const time = [zeroPad(seconds)];
|
const time = [zeroPad(seconds)];
|
||||||
minutes !== 0 && time.unshift(zeroPad(minutes));
|
minutes !== 0 && time.unshift(zeroPad(minutes));
|
||||||
hours !== 0 && time.unshift(zeroPad(hours));
|
hours !== 0 && time.unshift(zeroPad(hours));
|
||||||
const bg = useColorValue('black', 'white');
|
|
||||||
return (
|
return (
|
||||||
<If condition={completed}>
|
<If condition={completed}>
|
||||||
<Then>
|
<Then>
|
||||||
|
|
@ -28,7 +27,7 @@ const Renderer = (props: RendererProps): JSX.Element => {
|
||||||
<Else>
|
<Else>
|
||||||
<Text fontSize="xs" color="gray.500">
|
<Text fontSize="xs" color="gray.500">
|
||||||
{text}
|
{text}
|
||||||
<chakra.span fontSize="xs" color={bg}>
|
<chakra.span fontSize="xs">
|
||||||
{time.join(':')}
|
{time.join(':')}
|
||||||
</chakra.span>
|
</chakra.span>
|
||||||
</Text>
|
</Text>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import { ColorModeScript } from '@chakra-ui/react';
|
|
||||||
import Document, { Html, Head, Main, NextScript } from 'next/document';
|
import Document, { Html, Head, Main, NextScript } from 'next/document';
|
||||||
import { CustomHtml, CustomJavascript, Favicon } from '~/elements';
|
import { CustomHtml, CustomJavascript, Favicon } from '~/elements';
|
||||||
import { googleFontUrl } from '~/util';
|
import { googleFontUrl } from '~/util';
|
||||||
|
|
@ -42,7 +41,7 @@ class MyDocument extends Document<DocumentExtra> {
|
||||||
// } = await getHyperglassConfig(hyperglassUrl);
|
// } = await getHyperglassConfig(hyperglassUrl);
|
||||||
|
|
||||||
fonts = {
|
fonts = {
|
||||||
body: googleFontUrl(config.web.theme.fonts.body),
|
body: "https://assets.witine.com/fonts/inter.css",
|
||||||
mono: googleFontUrl(config.web.theme.fonts.mono),
|
mono: googleFontUrl(config.web.theme.fonts.mono),
|
||||||
};
|
};
|
||||||
defaultColorMode = config.web.theme.defaultColorMode;
|
defaultColorMode = config.web.theme.defaultColorMode;
|
||||||
|
|
@ -61,9 +60,6 @@ class MyDocument extends Document<DocumentExtra> {
|
||||||
<meta name="og:image" content="/images/opengraph.jpg" />
|
<meta name="og:image" content="/images/opengraph.jpg" />
|
||||||
<meta property="og:image:width" content="1200" />
|
<meta property="og:image:width" content="1200" />
|
||||||
<meta property="og:image:height" content="630" />
|
<meta property="og:image:height" content="630" />
|
||||||
<link rel="dns-prefetch" href="//fonts.gstatic.com" />
|
|
||||||
<link rel="dns-prefetch" href="//fonts.googleapis.com" />
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="" />
|
|
||||||
<link href={this.props.fonts.mono} rel="stylesheet" />
|
<link href={this.props.fonts.mono} rel="stylesheet" />
|
||||||
<link href={this.props.fonts.body} rel="stylesheet" />
|
<link href={this.props.fonts.body} rel="stylesheet" />
|
||||||
{favicons.map(favicon => (
|
{favicons.map(favicon => (
|
||||||
|
|
@ -72,7 +68,6 @@ class MyDocument extends Document<DocumentExtra> {
|
||||||
<CustomJavascript>{this.props.customJs}</CustomJavascript>
|
<CustomJavascript>{this.props.customJs}</CustomJavascript>
|
||||||
</Head>
|
</Head>
|
||||||
<body>
|
<body>
|
||||||
<ColorModeScript initialColorMode={this.props.defaultColorMode ?? 'system'} />
|
|
||||||
<Main />
|
<Main />
|
||||||
<CustomHtml>{this.props.customHtml}</CustomHtml>
|
<CustomHtml>{this.props.customHtml}</CustomHtml>
|
||||||
<NextScript />
|
<NextScript />
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,7 @@ interface _Logo {
|
||||||
height: string | null;
|
height: string | null;
|
||||||
light_format: string;
|
light_format: string;
|
||||||
dark_format: string;
|
dark_format: string;
|
||||||
|
footer_format: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface _Link {
|
interface _Link {
|
||||||
|
|
@ -87,6 +88,7 @@ interface _Highlight {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface _Web {
|
interface _Web {
|
||||||
|
copyright: string;
|
||||||
credit: _Credit;
|
credit: _Credit;
|
||||||
dns_provider: { name: string; url: string };
|
dns_provider: { name: string; url: string };
|
||||||
links: _Link[];
|
links: _Link[];
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ function importFonts(userFonts: Theme.Fonts): ChakraTheme['fonts'] {
|
||||||
|
|
||||||
function importColors(userColors: ThemeConfig['colors']): Theme.Colors {
|
function importColors(userColors: ThemeConfig['colors']): Theme.Colors {
|
||||||
const initial: Pick<Theme.Colors, 'blackSolid' | 'whiteSolid' | 'transparent' | 'current'> = {
|
const initial: Pick<Theme.Colors, 'blackSolid' | 'whiteSolid' | 'transparent' | 'current'> = {
|
||||||
|
brand: generatePalette('#00BCA9'),
|
||||||
blackSolid: {
|
blackSolid: {
|
||||||
50: '#444444',
|
50: '#444444',
|
||||||
100: '#3c3c3c',
|
100: '#3c3c3c',
|
||||||
|
|
@ -92,24 +93,14 @@ export function makeTheme(
|
||||||
colors,
|
colors,
|
||||||
config,
|
config,
|
||||||
fontWeights,
|
fontWeights,
|
||||||
semanticTokens: {
|
|
||||||
colors: {
|
|
||||||
'body-bg': {
|
|
||||||
default: 'light.500',
|
|
||||||
_dark: 'dark.500',
|
|
||||||
},
|
|
||||||
'body-fg': {
|
|
||||||
default: 'dark.500',
|
|
||||||
_dark: 'light.500',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
styles: {
|
styles: {
|
||||||
global: {
|
global: {
|
||||||
html: { scrollBehavior: 'smooth', height: '-webkit-fill-available' },
|
html: { scrollBehavior: 'smooth', height: '-webkit-fill-available' },
|
||||||
body: {
|
body: {
|
||||||
background: 'body-bg',
|
background: 'repeating-linear-gradient(225deg,#f5fffe,#effdff,#eff9ff,#f5f4ff,#ffeefe,#ffeefe)',
|
||||||
color: 'body-fg',
|
color: '#2D3635',
|
||||||
|
fontSize: '1.05rem',
|
||||||
|
lineHeight: '1.5',
|
||||||
overflowX: 'hidden',
|
overflowX: 'hidden',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue