1
0
mirror of https://github.com/checktheroads/hyperglass synced 2024-05-11 05:55:08 +00:00

accessibility improvements

This commit is contained in:
checktheroads
2020-05-02 16:09:03 -07:00
parent 51d3804d07
commit 07508b4b02
16 changed files with 2482 additions and 2485 deletions

View File

@@ -58,8 +58,8 @@ const ChakraSelect = React.forwardRef(
);
const selectedDisabled = theme.colors.whiteAlpha[400];
const placeholderColor = {
dark: theme.colors.whiteAlpha[400],
light: theme.colors.gray[400]
dark: theme.colors.whiteAlpha[700],
light: theme.colors.gray[600]
};
const menuBg = { dark: theme.colors.black, light: theme.colors.white };
const menuColor = {

View File

@@ -10,119 +10,135 @@ import FooterContent from "~/components/FooterContent";
format.extend(String.prototype, {});
const Footer = () => {
const theme = useTheme();
const config = useConfig();
const { colorMode } = useColorMode();
const footerBg = { light: theme.colors.blackAlpha[50], dark: theme.colors.whiteAlpha[100] };
const footerColor = { light: theme.colors.black, dark: theme.colors.white };
const contentBorder = {
light: theme.colors.blackAlpha[100],
dark: theme.colors.whiteAlpha[200]
};
const [helpVisible, showHelp] = useState(false);
const [termsVisible, showTerms] = useState(false);
const [creditVisible, showCredit] = useState(false);
const extUrl = config.web.external_link.url.includes("{primary_asn}")
? config.web.external_link.url.format({ primary_asn: config.primary_asn })
: config.web.external_link.url || "/";
const handleCollapse = i => {
if (i === "help") {
showTerms(false);
showCredit(false);
showHelp(!helpVisible);
} else if (i === "credit") {
showTerms(false);
showHelp(false);
showCredit(!creditVisible);
} else if (i === "terms") {
showHelp(false);
showCredit(false);
showTerms(!termsVisible);
}
};
return (
<>
{config.web.help_menu.enable && (
<FooterContent
isOpen={helpVisible}
content={config.content.help_menu}
title={config.web.help_menu.title}
bg={footerBg[colorMode]}
borderColor={contentBorder[colorMode]}
side="left"
/>
)}
{config.web.terms.enable && (
<FooterContent
isOpen={termsVisible}
content={config.content.terms}
title={config.web.terms.title}
bg={footerBg[colorMode]}
borderColor={contentBorder[colorMode]}
side="left"
/>
)}
{config.web.credit.enable && (
<FooterContent
isOpen={creditVisible}
content={config.content.credit}
title={config.web.credit.title}
bg={footerBg[colorMode]}
borderColor={contentBorder[colorMode]}
side="right"
/>
)}
<Flex
py={[4, 4, 2, 2]}
px={6}
w="100%"
as="footer"
flexWrap="wrap"
textAlign="center"
alignItems="center"
bg={footerBg[colorMode]}
color={footerColor[colorMode]}
justifyContent="space-between"
>
{config.web.terms.enable && (
<FooterButton side="left" onClick={() => handleCollapse("terms")}>
{config.web.terms.title}
</FooterButton>
)}
{config.web.help_menu.enable && (
<FooterButton side="left" onClick={() => handleCollapse("help")}>
{config.web.help_menu.title}
</FooterButton>
)}
<Flex
flexBasis="auto"
flexGrow={0}
flexShrink={0}
maxWidth="100%"
marginRight="auto"
p={0}
/>
{config.web.credit.enable && (
<FooterButton side="right" onClick={() => handleCollapse("credit")}>
<FiCode />
</FooterButton>
)}
{config.web.external_link.enable && (
<FooterButton
as="a"
href={extUrl}
target="_blank"
rel="noopener noreferrer"
variant="ghost"
rightIcon={GoLinkExternal}
size="xs"
>
{config.web.external_link.title}
</FooterButton>
)}
</Flex>
</>
);
const theme = useTheme();
const config = useConfig();
const { colorMode } = useColorMode();
const footerBg = {
light: theme.colors.blackAlpha[50],
dark: theme.colors.whiteAlpha[100]
};
const footerColor = { light: theme.colors.black, dark: theme.colors.white };
const contentBorder = {
light: theme.colors.blackAlpha[100],
dark: theme.colors.whiteAlpha[200]
};
const [helpVisible, showHelp] = useState(false);
const [termsVisible, showTerms] = useState(false);
const [creditVisible, showCredit] = useState(false);
const extUrl = config.web.external_link.url.includes("{primary_asn}")
? config.web.external_link.url.format({ primary_asn: config.primary_asn })
: config.web.external_link.url || "/";
const handleCollapse = i => {
if (i === "help") {
showTerms(false);
showCredit(false);
showHelp(!helpVisible);
} else if (i === "credit") {
showTerms(false);
showHelp(false);
showCredit(!creditVisible);
} else if (i === "terms") {
showHelp(false);
showCredit(false);
showTerms(!termsVisible);
}
};
return (
<>
{config.web.help_menu.enable && (
<FooterContent
isOpen={helpVisible}
content={config.content.help_menu}
title={config.web.help_menu.title}
bg={footerBg[colorMode]}
borderColor={contentBorder[colorMode]}
side="left"
/>
)}
{config.web.terms.enable && (
<FooterContent
isOpen={termsVisible}
content={config.content.terms}
title={config.web.terms.title}
bg={footerBg[colorMode]}
borderColor={contentBorder[colorMode]}
side="left"
/>
)}
{config.web.credit.enable && (
<FooterContent
isOpen={creditVisible}
content={config.content.credit}
title={config.web.credit.title}
bg={footerBg[colorMode]}
borderColor={contentBorder[colorMode]}
side="right"
/>
)}
<Flex
py={[4, 4, 2, 2]}
px={6}
w="100%"
as="footer"
flexWrap="wrap"
textAlign="center"
alignItems="center"
bg={footerBg[colorMode]}
color={footerColor[colorMode]}
justifyContent="space-between"
>
{config.web.terms.enable && (
<FooterButton
side="left"
onClick={() => handleCollapse("terms")}
aria-label={config.web.terms.title}
>
{config.web.terms.title}
</FooterButton>
)}
{config.web.help_menu.enable && (
<FooterButton
side="left"
onClick={() => handleCollapse("help")}
aria-label={config.web.help_menu.title}
>
{config.web.help_menu.title}
</FooterButton>
)}
<Flex
flexBasis="auto"
flexGrow={0}
flexShrink={0}
maxWidth="100%"
marginRight="auto"
p={0}
/>
{config.web.credit.enable && (
<FooterButton
side="right"
onClick={() => handleCollapse("credit")}
aria-label="Powered by hyperglass"
>
<FiCode />
</FooterButton>
)}
{config.web.external_link.enable && (
<FooterButton
as="a"
href={extUrl}
aria-label={config.web.external_link.title}
target="_blank"
rel="noopener noreferrer"
variant="ghost"
rightIcon={GoLinkExternal}
size="xs"
>
{config.web.external_link.title}
</FooterButton>
)}
</Flex>
</>
);
};
Footer.displayName = "Footer";

View File

@@ -4,24 +4,29 @@ import { motion } from "framer-motion";
const AnimatedFlex = motion.custom(Flex);
export default React.forwardRef(({ onClick, side, children, ...props }, ref) => {
const FooterButton = React.forwardRef(
({ onClick, side, children, ...props }, ref) => {
return (
<AnimatedFlex
p={0}
w="auto"
ref={ref}
flexGrow={0}
float={side}
flexShrink={0}
maxWidth="100%"
flexBasis="auto"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.6 }}
>
<Button size="xs" variant="ghost" onClick={onClick} {...props}>
{children}
</Button>
</AnimatedFlex>
<AnimatedFlex
p={0}
w="auto"
ref={ref}
flexGrow={0}
float={side}
flexShrink={0}
maxWidth="100%"
flexBasis="auto"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.6 }}
>
<Button size="xs" variant="ghost" onClick={onClick} {...props}>
{children}
</Button>
</AnimatedFlex>
);
});
}
);
FooterButton.displayName = "FooterButton";
export default FooterButton;

View File

@@ -1,55 +1,61 @@
import React from "react";
import { Flex, FormControl, FormLabel, FormErrorMessage, useColorMode } from "@chakra-ui/core";
import {
Flex,
FormControl,
FormLabel,
FormErrorMessage,
useColorMode
} from "@chakra-ui/core";
export default ({
label,
name,
error,
hiddenLabels,
helpIcon,
targetInfo,
setTarget,
labelAddOn,
fieldAddOn,
children,
...props
label,
name,
error,
hiddenLabels,
helpIcon,
targetInfo,
setTarget,
labelAddOn,
fieldAddOn,
children,
...props
}) => {
const { colorMode } = useColorMode();
const labelColor = { dark: "whiteAlpha.600", light: "blackAlpha.600" };
return (
<FormControl
as={Flex}
flexDirection="column"
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[colorMode]}
pl={1}
opacity={hiddenLabels ? 0 : null}
display="flex"
alignItems="center"
justifyContent="space-between"
pr={0}
>
{label}
{labelAddOn || null}
</FormLabel>
{children}
{fieldAddOn && (
<Flex justifyContent="flex-end" pt={3}>
{fieldAddOn}
</Flex>
)}
<FormErrorMessage opacity={hiddenLabels ? 0 : null}>
{error && error.message}
</FormErrorMessage>
</FormControl>
);
const { colorMode } = useColorMode();
const labelColor = { dark: "whiteAlpha.700", light: "blackAlpha.700" };
return (
<FormControl
as={Flex}
flexDirection="column"
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[colorMode]}
pl={1}
opacity={hiddenLabels ? 0 : null}
display="flex"
alignItems="center"
justifyContent="space-between"
pr={0}
>
{label}
{labelAddOn || null}
</FormLabel>
{children}
{fieldAddOn && (
<Flex justifyContent="flex-end" pt={3}>
{fieldAddOn}
</Flex>
)}
<FormErrorMessage opacity={hiddenLabels ? 0 : null}>
{error && error.message}
</FormErrorMessage>
</FormControl>
);
};

View File

@@ -18,236 +18,265 @@ import useConfig from "~/components/HyperglassProvider";
format.extend(String.prototype, {});
const formSchema = (config) =>
yup.object().shape({
query_location: yup
.array()
.of(yup.string())
.required(config.messages.no_input.format({ field: config.web.text.query_location })),
query_type: yup
.string()
.required(config.messages.no_input.format({ field: config.web.text.query_type })),
query_vrf: yup.string(),
query_target: yup
.string()
.required(config.messages.no_input.format({ field: config.web.text.query_target })),
});
const formSchema = config =>
yup.object().shape({
query_location: yup
.array()
.of(yup.string())
.required(
config.messages.no_input.format({
field: config.web.text.query_location
})
),
query_type: yup
.string()
.required(
config.messages.no_input.format({ field: config.web.text.query_type })
),
query_vrf: yup.string(),
query_target: yup
.string()
.required(
config.messages.no_input.format({ field: config.web.text.query_target })
)
});
const FormRow = ({ children, ...props }) => (
<Flex
flexDirection="row"
flexWrap="wrap"
w="100%"
justifyContent={["center", "center", "space-between", "space-between"]}
{...props}
>
{children}
</Flex>
<Flex
flexDirection="row"
flexWrap="wrap"
w="100%"
justifyContent={["center", "center", "space-between", "space-between"]}
{...props}
>
{children}
</Flex>
);
const HyperglassForm = React.forwardRef(
({ isSubmitting, setSubmitting, setFormData, greetingAck, setGreetingAck, ...props }, ref) => {
const config = useConfig();
const { handleSubmit, register, unregister, setValue, errors } = useForm({
validationSchema: formSchema(config),
defaultValues: { query_vrf: "default", query_target: "" },
(
{
isSubmitting,
setSubmitting,
setFormData,
greetingAck,
setGreetingAck,
...props
},
ref
) => {
const config = useConfig();
const { handleSubmit, register, unregister, setValue, errors } = useForm({
validationSchema: formSchema(config),
defaultValues: { query_vrf: "default", query_target: "" }
});
const [queryLocation, setQueryLocation] = useState([]);
const [queryType, setQueryType] = useState("");
const [queryVrf, setQueryVrf] = useState("");
const [queryTarget, setQueryTarget] = useState("");
const [availVrfs, setAvailVrfs] = useState([]);
const [fqdnTarget, setFqdnTarget] = useState("");
const [displayTarget, setDisplayTarget] = useState("");
const [families, setFamilies] = useState([]);
const onSubmit = values => {
if (!greetingAck && config.web.greeting.required) {
window.location.reload(false);
setGreetingAck(false);
} else {
setFormData(values);
setSubmitting(true);
}
};
const handleLocChange = locObj => {
setQueryLocation(locObj.value);
const allVrfs = [];
const deviceVrfs = [];
locObj.value.map(loc => {
const locVrfs = [];
config.devices[loc].vrfs.map(vrf => {
locVrfs.push({
label: vrf.display_name,
value: vrf.id
});
deviceVrfs.push([{ id: vrf.id, ipv4: vrf.ipv4, ipv6: vrf.ipv6 }]);
});
allVrfs.push(locVrfs);
});
const [queryLocation, setQueryLocation] = useState([]);
const [queryType, setQueryType] = useState("");
const [queryVrf, setQueryVrf] = useState("");
const [queryTarget, setQueryTarget] = useState("");
const [availVrfs, setAvailVrfs] = useState([]);
const [fqdnTarget, setFqdnTarget] = useState("");
const [displayTarget, setDisplayTarget] = useState("");
const [families, setFamilies] = useState([]);
const onSubmit = (values) => {
if (!greetingAck && config.web.greeting.required) {
window.location.reload(false);
setGreetingAck(false);
} else {
setFormData(values);
setSubmitting(true);
}
};
const intersecting = lodash.intersectionWith(...allVrfs, lodash.isEqual);
setAvailVrfs(intersecting);
!intersecting.includes(queryVrf) &&
queryVrf !== "default" &&
setQueryVrf("default");
const handleLocChange = (locObj) => {
setQueryLocation(locObj.value);
const allVrfs = [];
const deviceVrfs = [];
locObj.value.map((loc) => {
const locVrfs = [];
config.devices[loc].vrfs.map((vrf) => {
locVrfs.push({
label: vrf.display_name,
value: vrf.id,
});
deviceVrfs.push([{ id: vrf.id, ipv4: vrf.ipv4, ipv6: vrf.ipv6 }]);
});
allVrfs.push(locVrfs);
});
let ipv4 = 0;
let ipv6 = 0;
deviceVrfs.length !== 0 &&
intersecting.length !== 0 &&
deviceVrfs
.filter(v => intersecting.every(i => i.id === v.id))
.reduce((a, b) => a.concat(b))
.filter(v => v.id === "default")
.map(v => {
v.ipv4 === true && ipv4++;
v.ipv6 === true && ipv6++;
});
if (ipv4 !== 0 && ipv4 === ipv6) {
setFamilies([4, 6]);
} else if (ipv4 > ipv6) {
setFamilies([4]);
} else if (ipv4 < ipv6) {
setFamilies([6]);
} else {
setFamilies([]);
}
};
const intersecting = lodash.intersectionWith(...allVrfs, lodash.isEqual);
setAvailVrfs(intersecting);
!intersecting.includes(queryVrf) && queryVrf !== "default" && setQueryVrf("default");
const handleChange = e => {
setValue(e.field, e.value);
e.field === "query_location"
? handleLocChange(e)
: e.field === "query_type"
? setQueryType(e.value)
: e.field === "query_vrf"
? setQueryVrf(e.value)
: e.field === "query_target"
? setQueryTarget(e.value)
: null;
};
let ipv4 = 0;
let ipv6 = 0;
deviceVrfs.length !== 0 &&
intersecting.length !== 0 &&
deviceVrfs
.filter((v) => intersecting.every((i) => i.id === v.id))
.reduce((a, b) => a.concat(b))
.filter((v) => v.id === "default")
.map((v) => {
v.ipv4 === true && ipv4++;
v.ipv6 === true && ipv6++;
});
if (ipv4 !== 0 && ipv4 === ipv6) {
setFamilies([4, 6]);
} else if (ipv4 > ipv6) {
setFamilies([4]);
} else if (ipv4 < ipv6) {
setFamilies([6]);
} else {
setFamilies([]);
}
};
const vrfContent = config.content.vrf[queryVrf]?.[queryType];
const handleChange = (e) => {
setValue(e.field, e.value);
e.field === "query_location"
? handleLocChange(e)
: e.field === "query_type"
? setQueryType(e.value)
: e.field === "query_vrf"
? setQueryVrf(e.value)
: e.field === "query_target"
? setQueryTarget(e.value)
: null;
};
const validFqdnQueryType =
["ping", "traceroute", "bgp_route"].includes(queryType) &&
fqdnTarget &&
queryVrf === "default"
? fqdnTarget
: null;
const vrfContent = config.content.vrf[queryVrf]?.[queryType];
const validFqdnQueryType =
["ping", "traceroute", "bgp_route"].includes(queryType) &&
fqdnTarget &&
queryVrf === "default"
? fqdnTarget
: null;
useEffect(() => {
register({ name: "query_location" });
register({ name: "query_type" });
register({ name: "query_vrf" });
}, [register]);
Object.keys(errors).length >= 1 && console.error(errors);
return (
<Box
maxW={["100%", "100%", "75%", "75%"]}
w="100%"
p={0}
mx="auto"
my={4}
textAlign="left"
ref={ref}
{...props}
useEffect(() => {
register({ name: "query_location" });
register({ name: "query_type" });
register({ name: "query_vrf" });
}, [register]);
Object.keys(errors).length >= 1 && console.error(errors);
return (
<Box
maxW={["100%", "100%", "75%", "75%"]}
w="100%"
p={0}
mx="auto"
my={4}
textAlign="left"
ref={ref}
{...props}
>
<form onSubmit={handleSubmit(onSubmit)}>
<FormRow>
<FormField
label={config.web.text.query_location}
name="query_location"
error={errors.query_location}
>
<form onSubmit={handleSubmit(onSubmit)}>
<FormRow>
<FormField
label={config.web.text.query_location}
name="query_location"
error={errors.query_location}
>
<QueryLocation onChange={handleChange} locations={config.networks} />
</FormField>
<FormField
label={config.web.text.query_type}
name="query_type"
error={errors.query_type}
labelAddOn={
vrfContent && <HelpModal item={vrfContent} name="query_type" />
}
>
<QueryType onChange={handleChange} queryTypes={config.queries.list} />
</FormField>
</FormRow>
<FormRow>
{availVrfs.length > 1 && (
<FormField
label={config.web.text.query_vrf}
name="query_vrf"
error={errors.query_vrf}
>
<QueryVrf
placeholder={config.web.text.query_vrf}
vrfs={availVrfs}
onChange={handleChange}
/>
</FormField>
)}
<FormField
label={config.web.text.query_target}
name="query_target"
error={errors.query_target}
fieldAddOn={
queryLocation.length !== 0 &&
validFqdnQueryType && (
<ResolvedTarget
queryTarget={queryTarget}
fqdnTarget={validFqdnQueryType}
setTarget={handleChange}
families={families}
availVrfs={availVrfs}
/>
)
}
>
{queryType === "bgp_community" &&
config.queries.bgp_community.mode === "select" ? (
<CommunitySelect
name="query_target"
register={register}
unregister={unregister}
onChange={handleChange}
communities={config.queries.bgp_community.communities}
/>
) : (
<QueryTarget
name="query_target"
placeholder={config.web.text.query_target}
register={register}
unregister={unregister}
resolveTarget={["ping", "traceroute", "bgp_route"].includes(
queryType
)}
value={queryTarget}
setFqdn={setFqdnTarget}
setTarget={handleChange}
displayValue={displayTarget}
setDisplayValue={setDisplayTarget}
/>
)}
</FormField>
</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} />
</Flex>
</FormRow>
</form>
</Box>
);
}
<QueryLocation
onChange={handleChange}
locations={config.networks}
label={config.web.text.query_location}
/>
</FormField>
<FormField
label={config.web.text.query_type}
name="query_type"
error={errors.query_type}
labelAddOn={
vrfContent && <HelpModal item={vrfContent} name="query_type" />
}
>
<QueryType
onChange={handleChange}
queryTypes={config.queries.list}
label={config.web.text.query_type}
/>
</FormField>
</FormRow>
<FormRow>
{availVrfs.length > 1 && (
<FormField
label={config.web.text.query_vrf}
name="query_vrf"
error={errors.query_vrf}
>
<QueryVrf
label={config.web.text.query_vrf}
vrfs={availVrfs}
onChange={handleChange}
/>
</FormField>
)}
<FormField
label={config.web.text.query_target}
name="query_target"
error={errors.query_target}
fieldAddOn={
queryLocation.length !== 0 &&
validFqdnQueryType && (
<ResolvedTarget
queryTarget={queryTarget}
fqdnTarget={validFqdnQueryType}
setTarget={handleChange}
families={families}
availVrfs={availVrfs}
/>
)
}
>
{queryType === "bgp_community" &&
config.queries.bgp_community.mode === "select" ? (
<CommunitySelect
label={config.queries.bgp_community.display_name}
name="query_target"
register={register}
unregister={unregister}
onChange={handleChange}
communities={config.queries.bgp_community.communities}
/>
) : (
<QueryTarget
name="query_target"
placeholder={config.web.text.query_target}
register={register}
unregister={unregister}
resolveTarget={["ping", "traceroute", "bgp_route"].includes(
queryType
)}
value={queryTarget}
setFqdn={setFqdnTarget}
setTarget={handleChange}
displayValue={displayTarget}
setDisplayValue={setDisplayTarget}
/>
)}
</FormField>
</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} />
</Flex>
</FormRow>
</form>
</Box>
);
}
);
HyperglassForm.displayName = "HyperglassForm";

View File

@@ -1,40 +1,44 @@
import React from "react";
import ChakraSelect from "~/components/ChakraSelect";
const buildLocations = (networks) => {
const locations = [];
networks.map((net) => {
const netLocations = [];
net.locations.map((loc) => {
netLocations.push({
label: loc.display_name,
value: loc.name,
group: net.display_name,
});
});
locations.push({ label: net.display_name, options: netLocations });
const buildLocations = networks => {
const locations = [];
networks.map(net => {
const netLocations = [];
net.locations.map(loc => {
netLocations.push({
label: loc.display_name,
value: loc.name,
group: net.display_name
});
});
return locations;
locations.push({ label: net.display_name, options: netLocations });
});
return locations;
};
export default ({ locations, onChange }) => {
const options = buildLocations(locations);
const handleChange = (e) => {
const selected = [];
e &&
e.map((sel) => {
selected.push(sel.value);
});
onChange({ field: "query_location", value: selected });
};
return (
<ChakraSelect
size="lg"
name="query_location"
onChange={handleChange}
options={options}
isMulti
closeMenuOnSelect={false}
/>
);
const QueryLocation = ({ locations, onChange, label }) => {
const options = buildLocations(locations);
const handleChange = e => {
const selected = [];
e &&
e.map(sel => {
selected.push(sel.value);
});
onChange({ field: "query_location", value: selected });
};
return (
<ChakraSelect
size="lg"
name="query_location"
onChange={handleChange}
options={options}
isMulti
closeMenuOnSelect={false}
aria-label={label}
/>
);
};
QueryLocation.displayName = "QueryLocation";
export default QueryLocation;

View File

@@ -1,74 +1,71 @@
import React, { useEffect } from "react";
import styled from "@emotion/styled";
import * as React from "react";
import { useEffect } from "react";
import { Input, useColorMode } from "@chakra-ui/core";
const StyledInput = styled(Input)`
&::placeholder {
color: ${(props) => props.placeholderColor};
}
`;
const fqdnPattern = /^(?!:\/\/)([a-zA-Z0-9]+\.)?[a-zA-Z0-9][a-zA-Z0-9-]+\.[a-zA-Z]{2,6}?$/gim;
const bg = { dark: "whiteAlpha.100", light: "white" };
const color = { dark: "whiteAlpha.800", light: "gray.400" };
const border = { dark: "whiteAlpha.50", light: "gray.100" };
const placeholderColor = { dark: "whiteAlpha.400", light: "gray.400" };
const placeholderColor = { dark: "whiteAlpha.700", light: "gray.600" };
const QueryTarget = ({
placeholder,
register,
unregister,
setFqdn,
name,
value,
setTarget,
resolveTarget,
displayValue,
setDisplayValue,
placeholder,
register,
unregister,
setFqdn,
name,
value,
setTarget,
resolveTarget,
displayValue,
setDisplayValue
}) => {
const { colorMode } = useColorMode();
const { colorMode } = useColorMode();
const handleBlur = () => {
if (resolveTarget && displayValue && fqdnPattern.test(displayValue)) {
setFqdn(displayValue);
} else if (resolveTarget && !displayValue) {
setFqdn(false);
}
};
const handleChange = (e) => {
setDisplayValue(e.target.value);
setTarget({ field: name, value: e.target.value });
};
const handleKeyDown = (e) => {
if ([9, 13].includes(e.keyCode)) {
handleBlur();
}
};
useEffect(() => {
register({ name });
return () => unregister(name);
}, [register, unregister, name]);
return (
<>
<input hidden readOnly name={name} ref={register} value={value} />
<StyledInput
size="lg"
name="query_target_display"
bg={bg[colorMode]}
onBlur={handleBlur}
onFocus={handleBlur}
onKeyDown={handleKeyDown}
value={displayValue}
borderRadius="0.25rem"
onChange={handleChange}
color={color[colorMode]}
placeholder={placeholder}
borderColor={border[colorMode]}
placeholderColor={placeholderColor[colorMode]}
/>
</>
);
const handleBlur = () => {
if (resolveTarget && displayValue && fqdnPattern.test(displayValue)) {
setFqdn(displayValue);
} else if (resolveTarget && !displayValue) {
setFqdn(false);
}
};
const handleChange = e => {
setDisplayValue(e.target.value);
setTarget({ field: name, value: e.target.value });
};
const handleKeyDown = e => {
if ([9, 13].includes(e.keyCode)) {
handleBlur();
}
};
useEffect(() => {
register({ name });
return () => unregister(name);
}, [register, unregister, name]);
return (
<>
<input hidden readOnly name={name} ref={register} value={value} />
<Input
size="lg"
aria-label={placeholder}
name="query_target_display"
bg={bg[colorMode]}
onBlur={handleBlur}
onFocus={handleBlur}
onKeyDown={handleKeyDown}
value={displayValue}
borderRadius="0.25rem"
onChange={handleChange}
color={color[colorMode]}
placeholder={placeholder}
borderColor={border[colorMode]}
_placeholder={{
color: placeholderColor[colorMode]
}}
/>
</>
);
};
QueryTarget.displayName = "QueryTarget";

View File

@@ -1,18 +1,22 @@
import React from "react";
import ChakraSelect from "~/components/ChakraSelect";
export default ({ queryTypes, onChange }) => {
const queries = queryTypes
.filter(q => q.enable === true)
.map(q => {
return { value: q.name, label: q.display_name };
});
return (
<ChakraSelect
size="lg"
name="query_type"
onChange={e => onChange({ field: "query_type", value: e.value })}
options={queries}
/>
);
const QueryType = ({ queryTypes, onChange, label }) => {
const queries = queryTypes
.filter(q => q.enable === true)
.map(q => {
return { value: q.name, label: q.display_name };
});
return (
<ChakraSelect
size="lg"
name="query_type"
onChange={e => onChange({ field: "query_type", value: e.value })}
options={queries}
aria-label={label}
/>
);
};
QueryType.displayName = "QueryType";
export default QueryType;

View File

@@ -1,13 +1,17 @@
import React from "react";
import ChakraSelect from "~/components/ChakraSelect";
export default ({ vrfs, onChange }) => {
return (
<ChakraSelect
size="lg"
onChange={e => onChange({ field: "query_vrf", value: e.value })}
name="query_vrf"
options={vrfs}
/>
);
const QueryVrf = ({ vrfs, onChange, label }) => {
return (
<ChakraSelect
size="lg"
onChange={e => onChange({ field: "query_vrf", value: e.value })}
name="query_vrf"
options={vrfs}
aria-label={label}
/>
);
};
QueryVrf.displayName = "QueryVrf";
export default QueryVrf;

View File

@@ -1,111 +1,130 @@
import React from "react";
import { Box, PseudoBox, Spinner, useColorMode, useTheme } from "@chakra-ui/core";
import {
Box,
PseudoBox,
Spinner,
useColorMode,
useTheme
} from "@chakra-ui/core";
import { FiSearch } from "react-icons/fi";
import { opposingColor } from "~/util";
const btnProps = {
display: "inline-flex",
appearance: "none",
alignItems: "center",
justifyContent: "center",
transition: "all 250ms",
userSelect: "none",
position: "relative",
whiteSpace: "nowrap",
verticalAlign: "middle",
lineHeight: "1.2",
outline: "none",
as: "button",
type: "submit",
borderRadius: "md",
fontWeight: "semibold"
display: "inline-flex",
appearance: "none",
alignItems: "center",
justifyContent: "center",
transition: "all 250ms",
userSelect: "none",
position: "relative",
whiteSpace: "nowrap",
verticalAlign: "middle",
lineHeight: "1.2",
outline: "none",
as: "button",
type: "submit",
borderRadius: "md",
fontWeight: "semibold"
};
const btnSizeMap = {
lg: {
height: 12,
minWidth: 12,
fontSize: "lg",
px: 6
},
md: {
height: 10,
minWidth: 10,
fontSize: "md",
px: 4
},
sm: {
height: 8,
minWidth: 8,
fontSize: "sm",
px: 3
},
xs: {
height: 6,
minWidth: 6,
fontSize: "xs",
px: 2
}
lg: {
height: 12,
minWidth: 12,
fontSize: "lg",
px: 6
},
md: {
height: 10,
minWidth: 10,
fontSize: "md",
px: 4
},
sm: {
height: 8,
minWidth: 8,
fontSize: "sm",
px: 3
},
xs: {
height: 6,
minWidth: 6,
fontSize: "xs",
px: 2
}
};
export default React.forwardRef(
(
{
isLoading = false,
isDisabled = false,
isActive = false,
isFullWidth = false,
size = "lg",
loadingText,
children,
...props
},
ref
) => {
const _isDisabled = isDisabled || isLoading;
const { colorMode } = useColorMode();
const theme = useTheme();
const btnBg = { dark: theme.colors.primary[300], light: theme.colors.primary[500] };
const btnBgActive = { dark: theme.colors.primary[400], light: theme.colors.primary[600] };
const btnBgHover = { dark: theme.colors.primary[200], light: theme.colors.primary[400] };
const btnColor = opposingColor(theme, btnBg[colorMode]);
const btnColorActive = opposingColor(theme, btnBgActive[colorMode]);
const btnColorHover = opposingColor(theme, btnBgHover[colorMode]);
const btnSize = btnSizeMap[size];
return (
<PseudoBox
ref={ref}
disabled={_isDisabled}
aria-disabled={_isDisabled}
width={isFullWidth ? "full" : undefined}
data-active={isActive ? "true" : undefined}
bg={btnBg[colorMode]}
color={btnColor}
_active={{ bg: btnBgActive[colorMode], color: btnColorActive }}
_hover={{ bg: btnBgHover[colorMode], color: btnColorHover }}
_focus={{ boxShadow: theme.shadows.outline }}
{...btnProps}
{...btnSize}
{...props}
>
{isLoading ? (
<Spinner
position={loadingText ? "relative" : "absolute"}
mr={loadingText ? 2 : 0}
color="currentColor"
size="1em"
/>
) : (
<FiSearch color={btnColor} />
)}
{isLoading
? loadingText || (
<Box as="span" opacity="0">
{children}
</Box>
)
: children}
</PseudoBox>
);
}
const SubmitButton = React.forwardRef(
(
{
isLoading = false,
isDisabled = false,
isActive = false,
isFullWidth = false,
size = "lg",
loadingText,
children,
...props
},
ref
) => {
const _isDisabled = isDisabled || isLoading;
const { colorMode } = useColorMode();
const theme = useTheme();
const btnBg = {
dark: theme.colors.primary[300],
light: theme.colors.primary[500]
};
const btnBgActive = {
dark: theme.colors.primary[400],
light: theme.colors.primary[600]
};
const btnBgHover = {
dark: theme.colors.primary[200],
light: theme.colors.primary[400]
};
const btnColor = opposingColor(theme, btnBg[colorMode]);
const btnColorActive = opposingColor(theme, btnBgActive[colorMode]);
const btnColorHover = opposingColor(theme, btnBgHover[colorMode]);
const btnSize = btnSizeMap[size];
return (
<PseudoBox
ref={ref}
disabled={_isDisabled}
aria-disabled={_isDisabled}
aria-label="Submit Query"
width={isFullWidth ? "full" : undefined}
data-active={isActive ? "true" : undefined}
bg={btnBg[colorMode]}
color={btnColor}
_active={{ bg: btnBgActive[colorMode], color: btnColorActive }}
_hover={{ bg: btnBgHover[colorMode], color: btnColorHover }}
_focus={{ boxShadow: theme.shadows.outline }}
{...btnProps}
{...btnSize}
{...props}
>
{isLoading ? (
<Spinner
position={loadingText ? "relative" : "absolute"}
mr={loadingText ? 2 : 0}
color="currentColor"
size="1em"
/>
) : (
<FiSearch color={btnColor} />
)}
{isLoading
? loadingText || (
<Box as="span" opacity="0">
{children}
</Box>
)
: children}
</PseudoBox>
);
}
);
SubmitButton.displayName = "SubmitButton";
export default SubmitButton;

View File

@@ -1,35 +0,0 @@
import { generate } from "namor";
const range = (len) => {
const arr = [];
for (let i = 0; i < len; i++) {
arr.push(i);
}
return arr;
};
export const newPerson = () => {
const statusChance = Math.random();
return {
name: generate({ words: 2, numbers: 0 }),
age: Math.floor(Math.random() * 30),
visits: Math.floor(Math.random() * 100),
progress: Math.floor(Math.random() * 100),
status:
statusChance > 0.66 ? "relationship" : statusChance > 0.33 ? "complicated" : "single",
};
};
export default function makeData(...lens) {
const makeDataLevel = (depth = 0) => {
const len = lens[depth];
return range(len).map((d) => {
return {
...newPerson(),
subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined,
};
});
};
return makeDataLevel();
}