Add Witine customisations (#2)

Reviewed-on: #2
This commit is contained in:
Andrew Ying 2024-11-15 18:40:53 +00:00
parent 18e0b3e7e7
commit ee7d8752f8
40 changed files with 260 additions and 331 deletions

2
.gitignore vendored
View file

@ -1,5 +1,5 @@
# Project # Project
hyperglass/hyperglass/static static/
TODO* TODO*
.env .env

View file

@ -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,

View file

@ -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,)

View file

@ -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

View file

@ -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] = [

View file

@ -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"

View file

@ -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>
);
},
);

View file

@ -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>
); );
}; };

View file

@ -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>
); );

View 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}
/>
);
};

View file

@ -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}

View file

@ -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}

View file

@ -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>
); );
}; };

View file

@ -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 }}
/> />

View file

@ -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>

View file

@ -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>
)} )}

View file

@ -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>

View file

@ -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>
); );
}; };

View file

@ -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"

View file

@ -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>

View file

@ -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>

View file

@ -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}

View file

@ -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 => {

View file

@ -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} />

View file

@ -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%' }}

View file

@ -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' });

View file

@ -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" />

View file

@ -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();

View file

@ -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',

View file

@ -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" />

View file

@ -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>
))} ))}

View file

@ -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 },
}; };

View file

@ -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>

View file

@ -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>
} }

View file

@ -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"

View file

@ -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"

View file

@ -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>

View file

@ -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 />

View file

@ -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[];

View file

@ -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',
}, },
}, },