UI improvements

This commit is contained in:
checktheroads 2020-01-20 00:37:04 -07:00
parent 22973cd21b
commit ebe848bbf9
15 changed files with 491 additions and 272 deletions

View file

@ -1,11 +1,19 @@
import React from "react";
import { Button, Icon, Tooltip, useClipboard } from "@chakra-ui/core";
export default ({ bg = "secondary", copyValue }) => {
export default ({ bg = "secondary", copyValue, ...props }) => {
const { onCopy, hasCopied } = useClipboard(copyValue);
return (
<Tooltip hasArrow label="Copy Output" placement="top">
<Button size="sm" variantColor={bg} zIndex="1" onClick={onCopy} mx={1}>
<Button
as="a"
size="sm"
variantColor={bg}
zIndex="dropdown"
onClick={onCopy}
mx={1}
{...props}
>
{hasCopied ? <Icon name="check" size="16px" /> : <Icon name="copy" size="16px" />}
</Button>
</Tooltip>

View file

@ -71,7 +71,7 @@ export default ({ general, help, extLink, credit, terms, content }) => {
/>
)}
<Flex
py={2}
py={[4, 4, 2, 2]}
px={6}
w="100%"
as="footer"

View file

@ -1,32 +1,28 @@
import React from "react";
import {
Flex,
FormControl,
FormLabel,
FormErrorMessage,
useTheme,
useColorMode
} from "@chakra-ui/core";
import { Flex, FormControl, FormLabel, FormErrorMessage, useColorMode } from "@chakra-ui/core";
import HelpModal from "~/components/HelpModal";
export default ({ label, name, error, hiddenLabels, helpIcon, children, ...props }) => {
const theme = useTheme();
const { colorMode } = useColorMode();
const labelColor =
colorMode === "dark" ? theme.colors.whiteAlpha[600] : theme.colors.blackAlpha[600];
const labelColor = { dark: "whiteAlpha.600", light: "blackAlpha.600" };
return (
<FormControl
as={Flex}
flexDirection="column"
flexGrow={1}
flexBasis={0}
flex={["1 0 100%", "1 0 100%", "1 0 33.33%", "1 0 33.33%"]}
w="100%"
maxW="100%"
mx={2}
my={[2, 2, 4, 4]}
isInvalid={error && error.message}
{...props}
>
<FormLabel htmlFor={name} color={labelColor} pl={1} opacity={hiddenLabels ? 0 : null}>
<FormLabel
htmlFor={name}
color={labelColor[colorMode]}
pl={1}
opacity={hiddenLabels ? 0 : null}
>
{label}
{helpIcon?.enable && <HelpModal item={helpIcon} name={name} />}
</FormLabel>

View file

@ -1,44 +1,123 @@
import React from "react";
import { Flex, IconButton, useColorMode, useTheme } from "@chakra-ui/core";
import { motion } from "framer-motion";
import { Flex, IconButton, useColorMode } from "@chakra-ui/core";
import { motion, AnimatePresence } from "framer-motion";
import ResetButton from "~/components/ResetButton";
import useMedia from "~/components/MediaProvider";
import Title from "~/components/Title";
const AnimatedFlex = motion.custom(Flex);
const AnimatedResetButton = motion.custom(ResetButton);
export default () => {
const theme = useTheme();
const titleVariants = {
sm: {
fullSize: { scale: 1, marginLeft: 0 },
small: { marginLeft: "auto" }
},
md: {
fullSize: { scale: 1 },
small: { scale: 1 }
},
lg: {
fullSize: { scale: 1 },
small: { scale: 1 }
},
xl: {
fullSize: { scale: 1 },
small: { scale: 1 }
}
};
const icon = { light: "moon", dark: "sun" };
const bg = { light: "white", dark: "black" };
const colorSwitch = { dark: "Switch to light mode", light: "Switch to dark mode" };
const headerTransition = { type: "spring", ease: "anticipate", damping: 15, stiffness: 100 };
export default ({ height, isSubmitting, handleFormReset, ...props }) => {
const { colorMode, toggleColorMode } = useColorMode();
const bg = { light: theme.colors.white, dark: theme.colors.black };
const icon = { light: "moon", dark: "sun" };
const { mediaSize } = useMedia();
const resetButton = (
<AnimatePresence key="resetButton">
<AnimatedFlex
layoutTransition={headerTransition}
initial={{ opacity: 0, x: -50 }}
animate={{ opacity: 1, x: 0, width: "unset" }}
exit={{ opacity: 0, x: -50 }}
alignItems="center"
mb={[null, "auto"]}
>
<AnimatedResetButton isSubmitting={isSubmitting} onClick={handleFormReset} />
</AnimatedFlex>
</AnimatePresence>
);
const title = (
<AnimatedFlex
key="title"
px={1}
alignItems={isSubmitting ? "center" : ["center", "center", "flex-end", "flex-end"]}
positionTransition={headerTransition}
initial={{ scale: 0.5 }}
animate={isSubmitting ? "small" : "fullSize"}
variants={titleVariants[mediaSize]}
justifyContent="center"
mb={[null, isSubmitting ? "auto" : null]}
mt={[null, isSubmitting ? null : "auto"]}
>
<Title isSubmitting={isSubmitting} onClick={handleFormReset} />
</AnimatedFlex>
);
const colorModeToggle = (
<AnimatedFlex
layoutTransition={headerTransition}
key="colorModeToggle"
alignItems="center"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
mb={[null, "auto"]}
>
<IconButton
aria-label={colorSwitch[colorMode]}
variant="ghost"
color="current"
ml={2}
pl={0}
fontSize="20px"
onClick={toggleColorMode}
icon={icon[colorMode]}
/>
</AnimatedFlex>
);
const layout = {
false: {
sm: [title, resetButton, colorModeToggle],
md: [resetButton, title, colorModeToggle],
lg: [resetButton, title, colorModeToggle],
xl: [resetButton, title, colorModeToggle]
},
true: {
sm: [resetButton, colorModeToggle, title],
md: [resetButton, title, colorModeToggle],
lg: [resetButton, title, colorModeToggle],
xl: [resetButton, title, colorModeToggle]
}
};
return (
<Flex
position="fixed"
as="header"
px={2}
top="0"
zIndex="4"
bg={bg[colorMode]}
color={theme.colors.gray[500]}
left="0"
right="0"
zIndex="4"
as="header"
width="full"
height="4rem"
flex="1 0 auto"
position="fixed"
bg={bg[colorMode]}
color="gray.500"
height={height}
{...props}
>
<Flex w="100%" mx="auto" px={6} justifyContent="flex-end">
<AnimatedFlex
align="center"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.6 }}
>
<IconButton
aria-label={`Switch to ${colorMode === "light" ? "dark" : "light"} mode`}
variant="ghost"
color="current"
ml="2"
fontSize="20px"
onClick={toggleColorMode}
icon={icon[colorMode]}
/>
</AnimatedFlex>
<Flex w="100%" mx="auto" py={6} justify="space-between" alignItems="center">
{layout[isSubmitting][mediaSize]}
</Flex>
</Flex>
);

View file

@ -10,11 +10,10 @@ import QueryType from "~/components/QueryType";
import QueryTarget from "~/components/QueryTarget";
import QueryVrf from "~/components/QueryVrf";
import SubmitButton from "~/components/SubmitButton";
import useConfig from "~/components/HyperglassProvider";
format.extend(String.prototype, {});
const all = (...items) => [...items].every(i => (i ? true : false));
const formSchema = config =>
yup.object().shape({
query_location: yup
@ -33,13 +32,20 @@ const formSchema = config =>
});
const FormRow = ({ children, ...props }) => (
<Flex flexDirection="row" flexWrap="wrap" w="100%" my={4} {...props}>
<Flex
flexDirection="row"
flexWrap="wrap"
w="100%"
justifyContent={["center", "center", "space-between", "space-between"]}
{...props}
>
{children}
</Flex>
);
export default React.forwardRef(
({ config, isSubmitting, setSubmitting, setFormData, ...props }, ref) => {
const HyperglassForm = React.forwardRef(
({ isSubmitting, setSubmitting, setFormData, ...props }, ref) => {
const config = useConfig();
const { handleSubmit, register, setValue, errors } = useForm({
validationSchema: formSchema(config)
});
@ -47,8 +53,6 @@ export default React.forwardRef(
const [queryType, setQueryType] = useState("");
const [queryVrf, setQueryVrf] = useState("");
const [availVrfs, setAvailVrfs] = useState([]);
// const [showHelpIcon, setShowHelpIcon] = useState(false);
const onSubmit = values => {
setFormData(values);
setSubmitting(true);
@ -138,17 +142,25 @@ export default React.forwardRef(
register={register}
/>
</FormField>
<FormField
flexGrow={0}
label="Submit"
error={errors.query_target}
hiddenLabels
</FormRow>
<FormRow mt={0} justifyContent="flex-end">
<Flex
w="100%"
maxW="100%"
ml="auto"
my={2}
mr={[0, 0, 2, 2]}
flexDirection="column"
flex="0 0 0"
>
<SubmitButton isLoading={isSubmitting} />
</FormField>
</Flex>
</FormRow>
</form>
</Box>
);
}
);
HyperglassForm.displayName = "HyperglassForm";
export default HyperglassForm;

View file

@ -2,20 +2,27 @@ import React from "react";
import { Flex, useColorMode, useTheme } from "@chakra-ui/core";
export default React.forwardRef(
({ value, label, labelBg, labelColor, valueBg, valueColor }, ref) => {
({ value, label, labelColor, valueBg, valueColor, ...props }, ref) => {
const theme = useTheme();
const { colorMode } = useColorMode();
const _labelBg = { light: theme.colors.black, dark: theme.colors.gray[200] };
const _labelColor = { light: theme.colors.white, dark: theme.colors.white };
const _labelColor = { dark: "whiteAlpha.700", light: "blackAlpha.700" };
const _valueBg = { light: theme.colors.primary[600], dark: theme.colors.primary[600] };
const _valueColor = { light: theme.colors.white, dark: theme.colors.white };
const _valueColor = { light: "white", dark: "white" };
return (
<Flex ref={ref} flexWrap="wrap" alignItems="center" justifyContent="flex-start" mx={2}>
<Flex
ref={ref}
flexWrap="nowrap"
alignItems="center"
justifyContent="flex-start"
mx={[1, 2, 2, 2]}
my={2}
{...props}
>
<Flex
display="inline-flex"
justifyContent="center"
lineHeight="1.5"
px={3}
px={[1, 3, 3, 3]}
whiteSpace="nowrap"
mb={2}
mr={0}
@ -25,8 +32,8 @@ export default React.forwardRef(
borderTopLeftRadius={4}
borderBottomRightRadius={0}
borderTopRightRadius={0}
fontSize="sm"
fontWeight="bold"
fontSize={["xs", "sm", "sm", "sm"]}
>
{value}
</Flex>
@ -39,13 +46,13 @@ export default React.forwardRef(
mb={2}
ml={0}
mr={0}
bg={labelBg || _labelBg[colorMode]}
boxShadow={`inset 0px 0px 0px 1px ${valueBg || _valueBg[colorMode]}`}
color={labelColor || _labelColor[colorMode]}
borderBottomRightRadius={4}
borderTopRightRadius={4}
borderBottomLeftRadius={0}
borderTopLeftRadius={0}
fontSize="sm"
fontSize={["xs", "sm", "sm", "sm"]}
>
{label}
</Flex>

View file

@ -1,67 +1,67 @@
import React, { useState } from "react";
import { Flex, useColorMode, useTheme } from "@chakra-ui/core";
import React, { useRef, useState } from "react";
import { Flex, useColorMode } from "@chakra-ui/core";
import { motion, AnimatePresence } from "framer-motion";
import ResetButton from "~/components/ResetButton";
import HyperglassForm from "~/components/HyperglassForm";
import Results from "~/components/Results";
import Header from "~/components/Header";
import Footer from "~/components/Footer";
import Title from "~/components/Title";
import Meta from "~/components/Meta";
import useConfig from "~/components/HyperglassProvider";
import Debugger from "~/components/Debugger";
const AnimatedForm = motion.custom(HyperglassForm);
const AnimatedTitle = motion.custom(Title);
const AnimatedResetButton = motion.custom(ResetButton);
export default ({ config }) => {
const theme = useTheme();
const bg = { light: "white", dark: "black" };
const color = { light: "black", dark: "white" };
const headerHeightDefault = { true: [16, 16, 16, 16], false: [24, 64, 64, 64] };
const headerHeightAll = { true: [32, 32, 32, 32], false: [48, "20rem", "20rem", "20rem"] };
const Layout = () => {
const config = useConfig();
const { colorMode } = useColorMode();
const bg = { light: theme.colors.white, dark: theme.colors.black };
const color = { light: theme.colors.black, dark: theme.colors.white };
const [isSubmitting, setSubmitting] = useState(false);
const [formData, setFormData] = useState({});
const containerRef = useRef(null);
const handleFormReset = () => {
containerRef.current.scrollIntoView({ behavior: "smooth", block: "start" });
setSubmitting(false);
};
const headerHeight =
config.branding.text.title_mode === "all"
? headerHeightAll[isSubmitting]
: headerHeightDefault[isSubmitting];
return (
<>
<Meta config={config} />
<Meta />
<Flex
flexDirection="column"
minHeight="100vh"
w="100%"
ref={containerRef}
minHeight="100vh"
bg={bg[colorMode]}
flexDirection="column"
color={color[colorMode]}
>
<Header />
<Flex px={2} flex="1 1 auto" flexGrow={0} flexDirection="column">
<Header
isSubmitting={isSubmitting}
handleFormReset={handleFormReset}
height={headerHeight}
/>
</Flex>
<Flex
as="main"
w="100%"
flexGrow={1}
flexShrink={1}
flexBasis="auto"
alignItems="center"
justifyContent="start"
textAlign="center"
flexDirection="column"
px={2}
py={0}
mt={["5%", "5%", "5%", "10%"]}
w="100%"
as="main"
mt={headerHeight}
flex="1 1 auto"
textAlign="center"
alignItems="center"
justifyContent="start"
flexDirection="column"
>
<AnimatePresence>
<AnimatedTitle
initial={{ opacity: 0, y: -300 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
exit={{ opacity: 0, y: -300 }}
text={config.branding.text}
logo={config.branding.logo}
resetForm={handleFormReset}
/>
</AnimatePresence>
{isSubmitting && formData && (
<Results
config={config}
queryLocation={formData.query_location}
queryType={formData.query_type}
queryVrf={formData.query_vrf}
@ -76,7 +76,6 @@ export default ({ config }) => {
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
exit={{ opacity: 0, x: -300 }}
config={config}
isSubmitting={isSubmitting}
setSubmitting={setSubmitting}
setFormData={setFormData}
@ -84,18 +83,7 @@ export default ({ config }) => {
)}
</AnimatePresence>
</Flex>
<AnimatePresence>
{isSubmitting && (
<AnimatedResetButton
initial={{ opacity: 0, x: -50 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.3 }}
exit={{ opacity: 0, x: -50 }}
isSubmitting={isSubmitting}
onClick={handleFormReset}
/>
)}
</AnimatePresence>
{config.general.debug && <Debugger />}
<Footer
general={config.general}
content={config.content}
@ -108,3 +96,6 @@ export default ({ config }) => {
</>
);
};
Layout.displayName = "HyperglassLayout";
export default Layout;

View file

@ -1,9 +1,11 @@
import React, { useEffect, useState } from "react";
import Head from "next/head";
import { useTheme } from "@chakra-ui/core";
import useConfig from "~/components/HyperglassProvider";
import { googleFontUrl } from "~/util";
export default ({ config }) => {
export default () => {
const config = useConfig();
const theme = useTheme();
const [location, setLocation] = useState({});
const title = config?.general.org_name || "hyperglass";

View file

@ -1,21 +1,18 @@
import React from "react";
import { Button, Flex, Heading, Spinner, useTheme, useColorMode } from "@chakra-ui/core";
import {
Button,
ColorModeProvider,
CSSReset,
Flex,
Heading,
Spinner,
ThemeProvider,
useTheme,
useColorMode
} from "@chakra-ui/core";
import { defaultTheme } from "~/theme";
const ErrorMsg = ({ title }) => (
<>
<Heading mb={4} color="danger.500" as="h1" fontSize="2xl">
{title}
</Heading>
</>
);
const ErrorBtn = ({ text, onClick }) => (
<Button variant="outline" variantColor="danger" onClick={onClick}>
{text}
</Button>
);
export default ({ loading, error, refresh }) => {
const PreConfig = ({ loading, error, refresh }) => {
const theme = useTheme();
const { colorMode } = useColorMode();
const bg = { light: theme.colors.white, dark: theme.colors.dark };
@ -45,15 +42,26 @@ export default ({ loading, error, refresh }) => {
{loading && <Spinner color="primary.500" w="6rem" h="6rem" />}
{!loading && error && (
<>
<ErrorMsg
title={
error.response?.data?.output || error.message || "An Error Occurred"
}
/>
<ErrorBtn text="Retry" onClick={refresh} />
<Heading mb={4} color="danger.500" as="h1" fontSize="2xl">
{error.response?.data?.output || error.message || "An Error Occurred"}
</Heading>
<Button variant="outline" variantColor="danger" onClick={refresh}>
Retry
</Button>
</>
)}
</Flex>
</Flex>
);
};
export default ({ loading, error, refresh }) => {
return (
<ThemeProvider theme={defaultTheme}>
<ColorModeProvider>
<CSSReset />
<PreConfig loading={loading} error={error} refresh={refresh} />
</ColorModeProvider>
</ThemeProvider>
);
};

View file

@ -1,11 +1,19 @@
import React from "react";
import { Button, Icon, Spinner, Tooltip } from "@chakra-ui/core";
import { Button, Icon, Tooltip } from "@chakra-ui/core";
export default ({ isLoading, requery, bg = "secondary" }) => {
export default ({ requery, bg = "secondary", ...props }) => {
return (
<Tooltip hasArrow label="Reload Query" placement="top">
<Button size="sm" variantColor={bg} zIndex="1" onClick={requery} mx={1}>
{isLoading ? <Spinner size="sm" /> : <Icon size="16px" name="repeat" />}
<Button
as="a"
size="sm"
variantColor={bg}
zIndex="1"
onClick={requery}
mx={1}
{...props}
>
<Icon size="16px" name="repeat" />
</Button>
</Tooltip>
);

View file

@ -1,11 +1,17 @@
import React from "react";
import { Box, Button } from "@chakra-ui/core";
import { Button } from "@chakra-ui/core";
import { FiChevronLeft } from "react-icons/fi";
export default React.forwardRef(({ isSubmitting, onClick }, ref) => (
<Box ref={ref} position="fixed" bottom={16} left={8} opacity={isSubmitting ? 1 : 0}>
<Button variantColor="primary" variant="outline" p={2} onClick={onClick}>
<FiChevronLeft size={24} />
</Button>
</Box>
<Button
ref={ref}
aria-label="Reset Form"
opacity={isSubmitting ? 1 : 0}
variant="ghost"
color="current"
onClick={onClick}
pl={0}
>
<FiChevronLeft size={24} />
</Button>
));

View file

@ -3,10 +3,10 @@ import {
AccordionItem,
AccordionHeader,
AccordionPanel,
AccordionIcon,
Alert,
Box,
ButtonGroup,
css,
Flex,
Text,
useTheme,
@ -15,26 +15,35 @@ import {
import styled from "@emotion/styled";
import useAxios from "axios-hooks";
import strReplace from "react-string-replace";
import useConfig from "~/components/HyperglassProvider";
import CopyButton from "~/components/CopyButton";
import RequeryButton from "~/components/RequeryButton";
import ResultHeader from "~/components/ResultHeader";
const PreBox = styled(Box)`
&::selection {
background-color: ${props => props.selectionBg};
color: ${props => props.selectionColor};
}
`;
const FormattedError = ({ keywords, message }) => {
const patternStr = `(${keywords.join("|")})`;
const pattern = new RegExp(patternStr, "gi");
const errorFmt = strReplace(message, pattern, match => <Text as="strong">{match}</Text>);
const errorFmt = strReplace(message, pattern, match => (
<Text key={match} as="strong">
{match}
</Text>
));
return <Text>{errorFmt}</Text>;
};
export default React.forwardRef(
({ config, device, timeout, queryLocation, queryType, queryVrf, queryTarget }, ref) => {
const AccordionHeaderWrapper = styled(Flex)`
justify-content: space-between;
&:hover {
background-color: ${props => props.hoverBg};
}
&:focus {
box-shadow: "outline";
}
`;
const Result = React.forwardRef(
({ device, timeout, queryLocation, queryType, queryVrf, queryTarget }, ref) => {
const config = useConfig();
const theme = useTheme();
const { colorMode } = useColorMode();
const bg = { dark: theme.colors.gray[800], light: theme.colors.blackAlpha[100] };
@ -68,60 +77,68 @@ export default React.forwardRef(
<AccordionItem
isDisabled={loading}
ref={ref}
css={{
css={css({
"&:last-of-type": { borderBottom: "none" },
"&:first-of-type": { borderTop: "none" }
}}
})}
>
<AccordionHeader justifyContent="space-between">
<ResultHeader
config={config}
title={device.display_name}
loading={loading}
error={error}
/>
<Flex>
<AccordionIcon />
<AccordionHeaderWrapper hoverBg={theme.colors.blackAlpha[50]}>
<AccordionHeader flex="1 0 auto" py={2} _hover={{}} _focus={{}} w="unset">
<ResultHeader title={device.display_name} loading={loading} error={error} />
</AccordionHeader>
<ButtonGroup px={3} py={2}>
<CopyButton copyValue={cleanOutput} variant="ghost" />
<RequeryButton requery={refetch} variant="ghost" />
</ButtonGroup>
</AccordionHeaderWrapper>
<AccordionPanel
pb={4}
overflowX="auto"
css={css({ WebkitOverflowScrolling: "touch" })}
>
<Flex direction="row" flexWrap="wrap">
<Flex direction="column" flex="1 0 auto">
{data && (
<Box
fontFamily="mono"
mt={5}
mx={2}
p={3}
border="1px"
borderColor="inherit"
rounded="md"
bg={bg[colorMode]}
color={color[colorMode]}
fontSize="sm"
whiteSpace="pre-wrap"
as="pre"
css={css({
"&::selection": {
backgroundColor: selectionBg[colorMode],
color: selectionColor[colorMode]
}
})}
>
{cleanOutput}
</Box>
)}
{error && (
<Alert
rounded="lg"
my={2}
py={4}
status={error.response?.data?.alert || "error"}
>
<FormattedError keywords={errorKw} message={errorMsg} />
</Alert>
)}
</Flex>
</Flex>
</AccordionHeader>
<AccordionPanel pb={4}>
<Box position="relative">
{data && (
<PreBox
fontFamily="mono"
mt={5}
p={3}
border="1px"
borderColor="inherit"
rounded="md"
bg={bg[colorMode]}
color={color[colorMode]}
fontSize="sm"
whiteSpace="pre-wrap"
as="pre"
selectionBg={selectionBg[colorMode]}
selectionColor={selectionColor[colorMode]}
>
{cleanOutput}
</PreBox>
)}
{error && (
<Alert
rounded="lg"
my={2}
py={4}
status={error.response?.data?.alert || "error"}
>
<FormattedError keywords={errorKw} message={errorMsg} />
</Alert>
)}
<ButtonGroup position="absolute" top={0} right={5} py={3} spacing={4}>
<CopyButton copyValue={cleanOutput} />
<RequeryButton isLoading={loading} requery={refetch} />
</ButtonGroup>
</Box>
</AccordionPanel>
</AccordionItem>
);
}
);
Result.displayName = "HyperglassQueryResult";
export default Result;

View file

@ -1,18 +1,29 @@
import React from "react";
import { Icon, Spinner, Stack, Text, Tooltip, useColorMode, useTheme } from "@chakra-ui/core";
import {
AccordionIcon,
Icon,
Spinner,
Stack,
Text,
Tooltip,
useColorMode,
useTheme
} from "@chakra-ui/core";
import useConfig from "~/components/HyperglassProvider";
export default React.forwardRef(({ config, title, loading, error }, ref) => {
export default React.forwardRef(({ title, loading, error }, ref) => {
const config = useConfig();
const theme = useTheme();
const { colorMode } = useColorMode();
const statusColor = { dark: theme.colors.primary[300], light: theme.colors.primary[500] };
const defaultWarningColor = { dark: theme.colors.danger[300], light: theme.colors.danger[500] };
const statusColor = { dark: "primary.300", light: "primary.500" };
const defaultWarningColor = { dark: "danger.300", light: "danger.500" };
const warningColor = { dark: 300, light: 500 };
const defaultStatusColor = {
dark: theme.colors.success[300],
light: theme.colors.success[500]
dark: "success.300",
light: "success.500"
};
return (
<Stack ref={ref} isInline alignItems="center">
<Stack ref={ref} isInline alignItems="center" w="100%">
{loading ? (
<Spinner size="sm" mr={4} color={statusColor[colorMode]} />
) : error ? (
@ -36,6 +47,7 @@ export default React.forwardRef(({ config, title, loading, error }, ref) => {
<Icon name="check" color={defaultStatusColor[colorMode]} mr={4} size={6} />
)}
<Text fontSize="lg">{title}</Text>
<AccordionIcon ml="auto" />
</Stack>
);
});

View file

@ -1,25 +1,60 @@
import React from "react";
import { Accordion, Box, Stack, useColorMode, useTheme } from "@chakra-ui/core";
import { Accordion, Box, Stack, useTheme } from "@chakra-ui/core";
import { motion, AnimatePresence } from "framer-motion";
import Label from "~/components/Label";
import Result from "~/components/Result";
import useConfig from "~/components/HyperglassProvider";
import useMedia from "~/components/MediaProvider";
const AnimatedResult = motion.custom(Result);
const AnimatedLabel = motion.custom(Label);
export default ({
config,
queryLocation,
queryType,
queryVrf,
queryTarget,
setSubmitting,
...props
}) => {
const labelInitial = {
left: {
sm: { opacity: 0, x: -100 },
md: { opacity: 0, x: -100 },
lg: { opacity: 0, x: -100 },
xl: { opacity: 0, x: -100 }
},
center: {
sm: { opacity: 0 },
md: { opacity: 0 },
lg: { opacity: 0 },
xl: { opacity: 0 }
},
right: {
sm: { opacity: 0, x: 100 },
md: { opacity: 0, x: 100 },
lg: { opacity: 0, x: 100 },
xl: { opacity: 0, x: 100 }
}
};
const labelAnimate = {
left: {
sm: { opacity: 1, x: 0 },
md: { opacity: 1, x: 0 },
lg: { opacity: 1, x: 0 },
xl: { opacity: 1, x: 0 }
},
center: {
sm: { opacity: 1 },
md: { opacity: 1 },
lg: { opacity: 1 },
xl: { opacity: 1 }
},
right: {
sm: { opacity: 1, x: 0 },
md: { opacity: 1, x: 0 },
lg: { opacity: 1, x: 0 },
xl: { opacity: 1, x: 0 }
}
};
const Results = ({ queryLocation, queryType, queryVrf, queryTarget, setSubmitting, ...props }) => {
const config = useConfig();
const theme = useTheme();
const { colorMode } = useColorMode();
const { mediaSize } = useMedia();
const matchedVrf = config.vrfs.filter(v => v.id === queryVrf)[0];
const labelColor = { light: theme.colors.white, dark: theme.colors.black };
return (
<>
<Box
@ -31,39 +66,39 @@ export default ({
textAlign="left"
{...props}
>
<Stack isInline align="center" justify="center" mt={4}>
<Stack isInline align="center" justify="center" mt={4} flexWrap="wrap">
<AnimatePresence>
{queryLocation && (
<>
<AnimatedLabel
initial={{ opacity: 0, x: -100 }}
animate={{ opacity: 1, x: 0 }}
initial={labelInitial.left[mediaSize]}
animate={labelAnimate.left[mediaSize]}
transition={{ duration: 0.3, delay: 0.3 }}
exit={{ opacity: 0, x: -100 }}
label={config.branding.text.query_type}
value={config.branding.text[queryType]}
valueBg={theme.colors.cyan[500]}
labelColor={labelColor[colorMode]}
fontSize={["xs", "sm", "sm", "sm"]}
/>
<AnimatedLabel
initial={{ opacity: 0, scale: 0.5 }}
animate={{ opacity: 1, scale: 1 }}
initial={labelInitial.center[mediaSize]}
animate={labelAnimate.center[mediaSize]}
transition={{ duration: 0.3, delay: 0.3 }}
exit={{ opacity: 0, scale: 0.5 }}
label={config.branding.text.query_target}
value={queryTarget}
valueBg={theme.colors.teal[600]}
labelColor={labelColor[colorMode]}
fontSize={["xs", "sm", "sm", "sm"]}
/>
<AnimatedLabel
initial={{ opacity: 0, x: 100 }}
animate={{ opacity: 1, x: 0 }}
initial={labelInitial.right[mediaSize]}
animate={labelAnimate.right[mediaSize]}
transition={{ duration: 0.3, delay: 0.3 }}
exit={{ opacity: 0, x: 100 }}
label={config.branding.text.query_vrf}
value={matchedVrf.display_name}
valueBg={theme.colors.blue[500]}
labelColor={labelColor[colorMode]}
fontSize={["xs", "sm", "sm", "sm"]}
/>
</>
)}
@ -71,7 +106,7 @@ export default ({
</Stack>
</Box>
<Box
maxW={["100%", "100%", "75%", "50%"]}
maxW={["100%", "100%", "75%", "75%"]}
w="100%"
p={0}
mx="auto"
@ -91,7 +126,6 @@ export default ({
{queryLocation &&
queryLocation.map((loc, i) => (
<AnimatedResult
config={config}
initial={{ opacity: 0, y: 300 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, delay: i * 0.3 }}
@ -112,3 +146,6 @@ export default ({
</>
);
};
Results.displayName = "HyperglassResults";
export default Results;

View file

@ -1,62 +1,98 @@
import React from "react";
import { Button, Flex, Heading, Image, Stack, useColorMode } from "@chakra-ui/core";
import { Button, Heading, Image, Stack, useColorMode } from "@chakra-ui/core";
import { motion, AnimatePresence } from "framer-motion";
import useConfig from "~/components/HyperglassProvider";
import useMedia from "~/components/MediaProvider";
const TitleOnly = ({ text }) => (
<Heading as="h1" size="2xl">
const subtitleAnimation = {
transition: { duration: 0.2, type: "tween" },
initial: { opacity: 1, scale: 1 },
animate: { opacity: 1, scale: 1 },
exit: { opacity: 0, scale: 0.3 }
};
const titleSize = { true: "2xl", false: "lg" };
const titleMargin = { true: 2, false: 0 };
const TitleOnly = ({ text, showSubtitle }) => (
<Heading as="h1" mb={titleMargin[showSubtitle]} size={titleSize[showSubtitle]}>
{text}
</Heading>
);
const SubtitleOnly = ({ text }) => (
<Heading as="h3" size="md">
const SubtitleOnly = React.forwardRef(({ text, size = "md", ...props }, ref) => (
<Heading ref={ref} as="h3" size={size} {...props}>
{text}
</Heading>
);
));
const TextOnly = ({ text }) => (
<Stack spacing={2}>
<TitleOnly text={text.title} />
<SubtitleOnly text={text.subtitle} />
const AnimatedSubtitle = motion.custom(SubtitleOnly);
const textAlignment = { false: ["right", "center"], true: ["left", "center"] };
const TextOnly = ({ text, mediaSize, showSubtitle, ...props }) => (
<Stack spacing={2} textAlign={textAlignment[showSubtitle]} {...props}>
<TitleOnly text={text.title} showSubtitle={showSubtitle} />
<AnimatePresence>
{showSubtitle && <AnimatedSubtitle text={text.subtitle} {...subtitleAnimation} />}
</AnimatePresence>
</Stack>
);
const LogoOnly = ({ text, logo }) => {
const Logo = ({ text, logo }) => {
const { colorMode } = useColorMode();
const logoColor = { light: logo.dark, dark: logo.light };
const logoPath = logoColor[colorMode];
return (
<Image
src={`http://localhost:8001${logoPath}`}
alt={text.title}
w={logo.width}
h={logo.height || null}
/>
);
return <Image src={logoPath} alt={text.title} />;
};
const LogoTitle = ({ text, logo }) => (
const LogoTitle = ({ text, logo, showSubtitle }) => (
<>
<LogoOnly text={text} logo={logo} />
<SubtitleOnly text={text.title} />
<Logo text={text} logo={logo} />
<AnimatePresence>
{showSubtitle && (
<AnimatedSubtitle mt={2} text={text.subtitle} {...subtitleAnimation} />
)}
</AnimatePresence>
</>
);
const All = ({ text, logo }) => (
const All = ({ text, logo, mediaSize, showSubtitle }) => (
<>
<LogoOnly text={text} logo={logo} />
<TextOnly text={text} />
<Logo text={text} logo={logo} />
<TextOnly mediaSize={mediaSize} showSubtitle={showSubtitle} mt={2} text={text} />
</>
);
const modeMap = { text_only: TextOnly, logo_only: LogoOnly, logo_title: LogoTitle, all: All };
const modeMap = { text_only: TextOnly, logo_only: Logo, logo_title: LogoTitle, all: All };
export default React.forwardRef(({ text, logo, resetForm }, ref) => {
const MatchedMode = modeMap[text.title_mode];
const btnJustify = {
true: ["flex-end", "center"],
false: ["flex-start", "center"]
};
export default React.forwardRef(({ onClick, isSubmitting, ...props }, ref) => {
const { branding } = useConfig();
const { mediaSize } = useMedia();
const titleMode = branding.text.title_mode;
const MatchedMode = modeMap[titleMode];
return (
<Button variant="link" onClick={resetForm} _focus={{ boxShadow: "non" }}>
<Flex ref={ref}>
<MatchedMode text={text} logo={logo} />
</Flex>
<Button
ref={ref}
variant="link"
onClick={onClick}
flexWrap="wrap"
_focus={{ boxShadow: "none" }}
_hover={{ textDecoration: "none" }}
justifyContent={btnJustify[isSubmitting]}
px={0}
{...props}
>
<MatchedMode
mediaSize={mediaSize}
showSubtitle={!isSubmitting}
text={branding.text}
logo={branding.logo}
/>
</Button>
);
});