mirror of
https://github.com/checktheroads/hyperglass
synced 2024-05-11 05:55:08 +00:00
continue typescript & chakra v1 migrations [skip ci]
This commit is contained in:
@@ -1,190 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { Text, useColorMode, useTheme } from '@chakra-ui/core';
|
||||
import Select from 'react-select';
|
||||
import { opposingColor } from 'app/util';
|
||||
|
||||
export const ChakraSelect = React.forwardRef(
|
||||
({ placeholder = 'Select...', isFullWidth, size, children, ...props }, ref) => {
|
||||
const theme = useTheme();
|
||||
const { colorMode } = useColorMode();
|
||||
const sizeMap = {
|
||||
lg: { height: theme.space[12] },
|
||||
md: { height: theme.space[10] },
|
||||
sm: { height: theme.space[8] },
|
||||
};
|
||||
const colorSetPrimaryBg = {
|
||||
dark: theme.colors.primary[300],
|
||||
light: theme.colors.primary[500],
|
||||
};
|
||||
const colorSetPrimaryColor = opposingColor(theme, colorSetPrimaryBg[colorMode]);
|
||||
const bg = {
|
||||
dark: theme.colors.whiteAlpha[100],
|
||||
light: theme.colors.white,
|
||||
};
|
||||
const color = {
|
||||
dark: theme.colors.whiteAlpha[800],
|
||||
light: theme.colors.black,
|
||||
};
|
||||
const borderFocused = theme.colors.secondary[500];
|
||||
const borderDisabled = theme.colors.whiteAlpha[100];
|
||||
const border = {
|
||||
dark: theme.colors.whiteAlpha[50],
|
||||
light: theme.colors.gray[100],
|
||||
};
|
||||
const borderRadius = theme.space[1];
|
||||
const hoverColor = {
|
||||
dark: theme.colors.whiteAlpha[200],
|
||||
light: theme.colors.gray[300],
|
||||
};
|
||||
const { height } = sizeMap[size];
|
||||
const optionBgActive = {
|
||||
dark: theme.colors.primary[400],
|
||||
light: theme.colors.primary[600],
|
||||
};
|
||||
const optionBgColor = opposingColor(theme, optionBgActive[colorMode]);
|
||||
const optionSelectedBg = {
|
||||
dark: theme.colors.whiteAlpha[400],
|
||||
light: theme.colors.blackAlpha[400],
|
||||
};
|
||||
const optionSelectedColor = opposingColor(theme, optionSelectedBg[colorMode]);
|
||||
const selectedDisabled = theme.colors.whiteAlpha[400];
|
||||
const placeholderColor = {
|
||||
dark: theme.colors.whiteAlpha[700],
|
||||
light: theme.colors.gray[600],
|
||||
};
|
||||
const menuBg = {
|
||||
dark: theme.colors.blackFaded[800],
|
||||
light: theme.colors.whiteFaded[50],
|
||||
};
|
||||
const menuColor = {
|
||||
dark: theme.colors.white,
|
||||
light: theme.colors.blackAlpha[800],
|
||||
};
|
||||
const scrollbar = {
|
||||
dark: theme.colors.whiteAlpha[300],
|
||||
light: theme.colors.blackAlpha[300],
|
||||
};
|
||||
const scrollbarHover = {
|
||||
dark: theme.colors.whiteAlpha[400],
|
||||
light: theme.colors.blackAlpha[400],
|
||||
};
|
||||
const scrollbarBg = {
|
||||
dark: theme.colors.whiteAlpha[50],
|
||||
light: theme.colors.blackAlpha[50],
|
||||
};
|
||||
return (
|
||||
<Select
|
||||
ref={ref}
|
||||
styles={{
|
||||
container: base => ({
|
||||
...base,
|
||||
minHeight: height,
|
||||
borderRadius: borderRadius,
|
||||
width: '100%',
|
||||
}),
|
||||
control: (base, state) => ({
|
||||
...base,
|
||||
minHeight: height,
|
||||
backgroundColor: bg[colorMode],
|
||||
color: color[colorMode],
|
||||
borderColor: state.isDisabled
|
||||
? borderDisabled
|
||||
: state.isFocused
|
||||
? borderFocused
|
||||
: border[colorMode],
|
||||
borderRadius: borderRadius,
|
||||
'&:hover': {
|
||||
borderColor: hoverColor[colorMode],
|
||||
},
|
||||
}),
|
||||
menu: base => ({
|
||||
...base,
|
||||
backgroundColor: menuBg[colorMode],
|
||||
borderRadius: borderRadius,
|
||||
}),
|
||||
menuList: base => ({
|
||||
...base,
|
||||
'&::-webkit-scrollbar': { width: '5px' },
|
||||
'&::-webkit-scrollbar-track': {
|
||||
backgroundColor: scrollbarBg[colorMode],
|
||||
},
|
||||
'&::-webkit-scrollbar-thumb': {
|
||||
backgroundColor: scrollbar[colorMode],
|
||||
},
|
||||
'&::-webkit-scrollbar-thumb:hover': {
|
||||
backgroundColor: scrollbarHover[colorMode],
|
||||
},
|
||||
|
||||
'-ms-overflow-style': { display: 'none' },
|
||||
}),
|
||||
option: (base, state) => ({
|
||||
...base,
|
||||
backgroundColor: state.isDisabled
|
||||
? selectedDisabled
|
||||
: state.isSelected
|
||||
? optionSelectedBg[colorMode]
|
||||
: state.isFocused
|
||||
? colorSetPrimaryBg[colorMode]
|
||||
: 'transparent',
|
||||
color: state.isDisabled
|
||||
? selectedDisabled
|
||||
: state.isFocused
|
||||
? colorSetPrimaryColor
|
||||
: state.isSelected
|
||||
? optionSelectedColor
|
||||
: menuColor[colorMode],
|
||||
fontSize: theme.fontSizes[size],
|
||||
'&:active': {
|
||||
backgroundColor: optionBgActive[colorMode],
|
||||
color: optionBgColor,
|
||||
},
|
||||
}),
|
||||
indicatorSeparator: base => ({
|
||||
...base,
|
||||
backgroundColor: placeholderColor[colorMode],
|
||||
}),
|
||||
dropdownIndicator: base => ({
|
||||
...base,
|
||||
color: placeholderColor[colorMode],
|
||||
'&:hover': {
|
||||
color: color[colorMode],
|
||||
},
|
||||
}),
|
||||
valueContainer: base => ({
|
||||
...base,
|
||||
paddingLeft: theme.space[4],
|
||||
paddingRight: theme.space[4],
|
||||
}),
|
||||
multiValue: base => ({
|
||||
...base,
|
||||
backgroundColor: colorSetPrimaryBg[colorMode],
|
||||
}),
|
||||
multiValueLabel: base => ({
|
||||
...base,
|
||||
color: colorSetPrimaryColor,
|
||||
}),
|
||||
multiValueRemove: base => ({
|
||||
...base,
|
||||
color: colorSetPrimaryColor,
|
||||
'&:hover': {
|
||||
color: colorSetPrimaryColor,
|
||||
backgroundColor: 'inherit',
|
||||
},
|
||||
}),
|
||||
singleValue: base => ({
|
||||
...base,
|
||||
color: color[colorMode],
|
||||
fontSize: theme.fontSizes[size],
|
||||
}),
|
||||
}}
|
||||
placeholder={
|
||||
<Text color={placeholderColor[colorMode]} fontSize={size} fontFamily={theme.fonts.body}>
|
||||
{placeholder}
|
||||
</Text>
|
||||
}
|
||||
{...props}>
|
||||
{children}
|
||||
</Select>
|
||||
);
|
||||
},
|
||||
);
|
@@ -1,66 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
IconButton,
|
||||
Modal,
|
||||
ModalOverlay,
|
||||
ModalContent,
|
||||
ModalHeader,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
useDisclosure,
|
||||
useColorMode,
|
||||
useTheme,
|
||||
} from '@chakra-ui/core';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { Markdown } from 'app/components';
|
||||
|
||||
const AnimatedIcon = motion.custom(IconButton);
|
||||
|
||||
export const HelpModal = ({ item, name }) => {
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const { colors } = useTheme();
|
||||
const { colorMode } = useColorMode();
|
||||
const bg = { light: 'whiteFaded.50', dark: 'blackFaded.800' };
|
||||
const color = { light: 'black', dark: 'white' };
|
||||
const iconColor = {
|
||||
light: colors.primary[500],
|
||||
dark: colors.primary[300],
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<AnimatePresence>
|
||||
<AnimatedIcon
|
||||
initial={{ opacity: 0, scale: 0.3, color: colors.gray[500] }}
|
||||
animate={{ opacity: 1, scale: 1, color: iconColor[colorMode] }}
|
||||
transition={{ duration: 0.2 }}
|
||||
exit={{ opacity: 0, scale: 0.3 }}
|
||||
variantColor="primary"
|
||||
aria-label={`${name}_help`}
|
||||
icon="info-outline"
|
||||
variant="link"
|
||||
size="sm"
|
||||
h="unset"
|
||||
w={3}
|
||||
minW={3}
|
||||
maxW={3}
|
||||
h={3}
|
||||
minH={3}
|
||||
maxH={3}
|
||||
ml={1}
|
||||
mb={1}
|
||||
onClick={onOpen}
|
||||
/>
|
||||
</AnimatePresence>
|
||||
<Modal isOpen={isOpen} onClose={onClose} size="xl">
|
||||
<ModalOverlay />
|
||||
<ModalContent bg={bg[colorMode]} color={color[colorMode]} py={4} borderRadius="md">
|
||||
<ModalHeader>{item.params.title}</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody>
|
||||
<Markdown content={item.content} />
|
||||
</ModalBody>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
@@ -1,46 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { Layout, HyperglassForm, Results } from 'app/components';
|
||||
import { useHyperglassState } from 'app/context';
|
||||
|
||||
const AnimatedForm = motion.custom(HyperglassForm);
|
||||
|
||||
export const LookingGlass = () => {
|
||||
const {
|
||||
isSubmitting,
|
||||
setSubmitting,
|
||||
formData,
|
||||
setFormData,
|
||||
greetingAck,
|
||||
setGreetingAck,
|
||||
} = useHyperglassState();
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
{isSubmitting && formData && (
|
||||
<Results
|
||||
queryLocation={formData.query_location}
|
||||
queryType={formData.query_type}
|
||||
queryVrf={formData.query_vrf}
|
||||
queryTarget={formData.query_target}
|
||||
setSubmitting={setSubmitting}
|
||||
/>
|
||||
)}
|
||||
<AnimatePresence>
|
||||
{!isSubmitting && (
|
||||
<AnimatedForm
|
||||
initial={{ opacity: 0, y: 300 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
exit={{ opacity: 0, x: -300 }}
|
||||
isSubmitting={isSubmitting}
|
||||
setSubmitting={setSubmitting}
|
||||
setFormData={setFormData}
|
||||
greetingAck={greetingAck}
|
||||
setGreetingAck={setGreetingAck}
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</Layout>
|
||||
);
|
||||
};
|
@@ -1,41 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { ChakraSelect } from 'app/components';
|
||||
|
||||
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 });
|
||||
});
|
||||
return locations;
|
||||
};
|
||||
|
||||
export 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
|
||||
isMulti
|
||||
size="lg"
|
||||
options={options}
|
||||
aria-label={label}
|
||||
name="query_location"
|
||||
onChange={handleChange}
|
||||
closeMenuOnSelect={false}
|
||||
/>
|
||||
);
|
||||
};
|
@@ -1,115 +0,0 @@
|
||||
/** @jsx jsx */
|
||||
import { jsx } from '@emotion/core';
|
||||
import { forwardRef } from 'react';
|
||||
import { Button, Heading, Image, Stack, useColorMode } from '@chakra-ui/core';
|
||||
import { useConfig, useMedia } from 'app/context';
|
||||
|
||||
const titleSize = { true: ['2xl', '2xl', '5xl', '5xl'], false: '2xl' };
|
||||
const titleMargin = { true: 2, false: 0 };
|
||||
const textAlignment = { false: ['right', 'center'], true: ['left', 'center'] };
|
||||
const logoName = { light: 'dark', dark: 'light' };
|
||||
const justifyMap = {
|
||||
true: ['flex-end', 'center', 'center', 'center'],
|
||||
false: ['flex-start', 'center', 'center', 'center'],
|
||||
};
|
||||
|
||||
const logoFallback = {
|
||||
light: 'https://res.cloudinary.com/hyperglass/image/upload/v1593916013/logo-dark.svg',
|
||||
dark: 'https://res.cloudinary.com/hyperglass/image/upload/v1593916013/logo-light.svg',
|
||||
};
|
||||
|
||||
const TitleOnly = ({ text, showSubtitle }) => (
|
||||
<Heading as="h1" mb={titleMargin[showSubtitle]} fontSize={titleSize[showSubtitle]}>
|
||||
{text}
|
||||
</Heading>
|
||||
);
|
||||
|
||||
const SubtitleOnly = ({ text, mediaSize, ...props }) => (
|
||||
<Heading
|
||||
as="h3"
|
||||
fontSize={['md', 'md', 'xl', 'xl']}
|
||||
whiteSpace="break-spaces"
|
||||
textAlign={['left', 'left', 'center', 'center']}
|
||||
{...props}>
|
||||
{text}
|
||||
</Heading>
|
||||
);
|
||||
|
||||
const TextOnly = ({ text, mediaSize, showSubtitle, ...props }) => (
|
||||
<Stack spacing={2} maxW="100%" textAlign={textAlignment[showSubtitle]} {...props}>
|
||||
<TitleOnly text={text.title} showSubtitle={showSubtitle} />
|
||||
{showSubtitle && <SubtitleOnly text={text.subtitle} mediaSize={mediaSize} />}
|
||||
</Stack>
|
||||
);
|
||||
|
||||
const Logo = ({ text, logo }) => {
|
||||
const { colorMode } = useColorMode();
|
||||
const { width, dark_format, light_format } = logo;
|
||||
const logoExt = { light: dark_format, dark: light_format };
|
||||
return (
|
||||
<Image
|
||||
css={{
|
||||
userDrag: 'none',
|
||||
userSelect: 'none',
|
||||
msUserSelect: 'none',
|
||||
MozUserSelect: 'none',
|
||||
WebkitUserDrag: 'none',
|
||||
WebkitUserSelect: 'none',
|
||||
}}
|
||||
alt={text.title}
|
||||
width={width ?? 'auto'}
|
||||
fallbackSrc={logoFallback[colorMode]}
|
||||
src={`/images/${logoName[colorMode]}${logoExt[colorMode]}`}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const LogoSubtitle = ({ text, logo, mediaSize }) => (
|
||||
<>
|
||||
<Logo text={text} logo={logo} mediaSize={mediaSize} />
|
||||
<SubtitleOnly mt={6} text={text.subtitle} />
|
||||
</>
|
||||
);
|
||||
|
||||
const All = ({ text, logo, mediaSize, showSubtitle }) => (
|
||||
<>
|
||||
<Logo text={text} logo={logo} />
|
||||
<TextOnly mediaSize={mediaSize} showSubtitle={showSubtitle} mt={2} text={text} />
|
||||
</>
|
||||
);
|
||||
|
||||
const modeMap = {
|
||||
text_only: TextOnly,
|
||||
logo_only: Logo,
|
||||
logo_subtitle: LogoSubtitle,
|
||||
all: All,
|
||||
};
|
||||
|
||||
export const Title = forwardRef(({ onClick, isSubmitting, ...props }, ref) => {
|
||||
const { web } = useConfig();
|
||||
const { mediaSize } = useMedia();
|
||||
const titleMode = web.text.title_mode;
|
||||
const MatchedMode = modeMap[titleMode];
|
||||
return (
|
||||
<Button
|
||||
px={0}
|
||||
w="100%"
|
||||
ref={ref}
|
||||
variant="link"
|
||||
flexWrap="wrap"
|
||||
flexDir="column"
|
||||
onClick={onClick}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
_hover={{ textDecoration: 'none' }}
|
||||
justifyContent={justifyMap[isSubmitting]}
|
||||
alignItems={['flex-start', 'flex-start', 'center']}
|
||||
{...props}>
|
||||
<MatchedMode
|
||||
mediaSize={mediaSize}
|
||||
showSubtitle={!isSubmitting}
|
||||
text={web.text}
|
||||
logo={web.logo}
|
||||
/>
|
||||
</Button>
|
||||
);
|
||||
});
|
46
hyperglass/ui/components/form/queryLocation.tsx
Normal file
46
hyperglass/ui/components/form/queryLocation.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { useMemo } from 'react';
|
||||
import { Select } from '~/components';
|
||||
|
||||
import type { TNetwork } from '~/types';
|
||||
import type { TQueryLocation, OnChangeArgs } from './types';
|
||||
|
||||
function isOnChangeArgsArray(e: OnChangeArgs | OnChangeArgs[]): e is OnChangeArgs[] {
|
||||
return Array.isArray(e);
|
||||
}
|
||||
|
||||
function buildOptions(networks: TNetwork[]) {
|
||||
return networks.map(net => {
|
||||
const label = net.display_name;
|
||||
const options = net.locations.map(loc => ({
|
||||
label: loc.display_name,
|
||||
value: loc.name,
|
||||
group: net.display_name,
|
||||
}));
|
||||
return { label, options };
|
||||
});
|
||||
}
|
||||
|
||||
export const QueryLocation = (props: TQueryLocation) => {
|
||||
const { locations, onChange, label } = props;
|
||||
|
||||
const options = useMemo(() => buildOptions(locations), [locations.length]);
|
||||
|
||||
function handleChange(e: OnChangeArgs | OnChangeArgs[]): void {
|
||||
if (isOnChangeArgsArray(e)) {
|
||||
const value = e.map(sel => sel.value as string);
|
||||
onChange({ label: 'query_location', value });
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Select
|
||||
isMulti
|
||||
size="lg"
|
||||
options={options}
|
||||
aria-label={label}
|
||||
name="query_location"
|
||||
onChange={handleChange}
|
||||
closeMenuOnSelect={false}
|
||||
/>
|
||||
);
|
||||
};
|
@@ -1,5 +1,6 @@
|
||||
import type { FormControlProps } from '@chakra-ui/react';
|
||||
import type { FieldError } from 'react-hook-form';
|
||||
import type { TNetwork } from '~/types';
|
||||
|
||||
export interface TField extends FormControlProps {
|
||||
name: string;
|
||||
@@ -9,3 +10,11 @@ export interface TField extends FormControlProps {
|
||||
labelAddOn?: React.ReactNode;
|
||||
fieldAddOn?: React.ReactNode;
|
||||
}
|
||||
|
||||
export type OnChangeArgs = { label: string; value: string | string[] };
|
||||
|
||||
export interface TQueryLocation {
|
||||
locations: TNetwork[];
|
||||
onChange(f: OnChangeArgs | OnChangeArgs[]): void;
|
||||
label: string;
|
||||
}
|
||||
|
1
hyperglass/ui/components/help/index.ts
Normal file
1
hyperglass/ui/components/help/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './modal';
|
63
hyperglass/ui/components/help/modal.tsx
Normal file
63
hyperglass/ui/components/help/modal.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import dynamic from 'next/dynamic';
|
||||
import {
|
||||
IconButton,
|
||||
Modal,
|
||||
ModalOverlay,
|
||||
ModalContent,
|
||||
ModalHeader,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
useDisclosure,
|
||||
} from '@chakra-ui/react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { Markdown } from '~/components';
|
||||
import { useColorValue } from '~/context';
|
||||
|
||||
import type { THelpModal } from './types';
|
||||
|
||||
const Info = dynamic<MeronexIcon>(() => import('@meronex/icons/fi').then(i => i.FiInfo));
|
||||
|
||||
export const HelpModal = (props: THelpModal) => {
|
||||
const { item, name, ...rest } = props;
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const bg = useColorValue('whiteFaded.50', 'blackFaded.800');
|
||||
const color = useColorValue('black', 'white');
|
||||
return (
|
||||
<>
|
||||
<AnimatePresence>
|
||||
<motion.div
|
||||
transition={{ duration: 0.2 }}
|
||||
exit={{ opacity: 0, scale: 0.3 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
initial={{ opacity: 0, scale: 0.3 }}>
|
||||
<IconButton
|
||||
h={3}
|
||||
w={3}
|
||||
mb={1}
|
||||
ml={1}
|
||||
maxH={3}
|
||||
maxW={3}
|
||||
minH={3}
|
||||
minW={3}
|
||||
size="sm"
|
||||
variant="link"
|
||||
icon={<Info />}
|
||||
onClick={onOpen}
|
||||
colorScheme="primary"
|
||||
aria-label={`${name}_help`}
|
||||
/>
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
<Modal isOpen={isOpen} onClose={onClose} size="xl">
|
||||
<ModalOverlay />
|
||||
<ModalContent bg={bg} color={color} py={4} borderRadius="md" {...rest}>
|
||||
<ModalHeader>{item.params.title}</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody>
|
||||
<Markdown content={item.content} />
|
||||
</ModalBody>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
7
hyperglass/ui/components/help/types.ts
Normal file
7
hyperglass/ui/components/help/types.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { ModalContentProps } from '@chakra-ui/react';
|
||||
import type { TQueryContent, TQueryFields } from '~/types';
|
||||
|
||||
export interface THelpModal extends ModalContentProps {
|
||||
item: TQueryContent;
|
||||
name: TQueryFields;
|
||||
}
|
@@ -1,6 +1,5 @@
|
||||
export * from './buttons';
|
||||
export * from './card';
|
||||
export * from './ChakraSelect';
|
||||
export * from './codeBlock';
|
||||
export * from './CommunitySelect';
|
||||
export * from './countdown';
|
||||
@@ -9,21 +8,20 @@ export * from './footer';
|
||||
export * from './form';
|
||||
export * from './greeting';
|
||||
export * from './header';
|
||||
export * from './HelpModal';
|
||||
export * from './help';
|
||||
export * from './HyperglassForm';
|
||||
export * from './label';
|
||||
export * from './layout';
|
||||
export * from './loading';
|
||||
export * from './LookingGlass';
|
||||
export * from './markdown';
|
||||
export * from './meta';
|
||||
export * from './output';
|
||||
export * from './QueryLocation';
|
||||
export * from './QueryTarget';
|
||||
export * from './QueryType';
|
||||
export * from './QueryVrf';
|
||||
export * from './ResolvedTarget';
|
||||
export * from './results';
|
||||
export * from './select';
|
||||
export * from './table';
|
||||
export * from './Title';
|
||||
export * from './title';
|
||||
export * from './util';
|
||||
|
@@ -4,7 +4,9 @@ import { useConfig, useColorValue, useGlobalState } from '~/context';
|
||||
import { If, Debugger, Greeting, Footer, Header } from '~/components';
|
||||
import { useGreeting } from '~/hooks';
|
||||
|
||||
export const Layout: React.FC = props => {
|
||||
import type { TFrame } from './types';
|
||||
|
||||
export const Frame = (props: TFrame) => {
|
||||
const { web, developer_mode } = useConfig();
|
||||
const { isSubmitting, formData } = useGlobalState();
|
||||
const [greetingAck, setGreetingAck] = useGreeting();
|
||||
@@ -27,9 +29,10 @@ export const Layout: React.FC = props => {
|
||||
bg={bg}
|
||||
w="100%"
|
||||
color={color}
|
||||
flexDir="column"
|
||||
minHeight="100vh"
|
||||
ref={containerRef}
|
||||
flexDirection="column">
|
||||
{...props}>
|
||||
<Flex px={2} flex="0 1 auto" flexDirection="column">
|
||||
<Header resetForm={resetForm} />
|
||||
</Flex>
|
2
hyperglass/ui/components/layout/index.ts
Normal file
2
hyperglass/ui/components/layout/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './frame';
|
||||
export * from './layout';
|
42
hyperglass/ui/components/layout/layout.tsx
Normal file
42
hyperglass/ui/components/layout/layout.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { If, HyperglassForm, Results } from '~/components';
|
||||
import { useGlobalState } from '~/context';
|
||||
import { all } from '~/util';
|
||||
import { Frame } from './frame';
|
||||
|
||||
export const Layout: React.FC = () => {
|
||||
const { isSubmitting, formData } = useGlobalState();
|
||||
|
||||
return (
|
||||
<Frame>
|
||||
<If
|
||||
c={
|
||||
isSubmitting.value &&
|
||||
all(
|
||||
formData.query_location.value,
|
||||
formData.query_target.value,
|
||||
formData.query_type.value,
|
||||
formData.query_vrf.value,
|
||||
)
|
||||
}>
|
||||
<Results
|
||||
queryLocation={formData.query_location.value}
|
||||
queryType={formData.query_type.value}
|
||||
queryVrf={formData.query_vrf.value}
|
||||
queryTarget={formData.query_target.value}
|
||||
/>
|
||||
</If>
|
||||
<AnimatePresence>
|
||||
<If c={!isSubmitting.value}>
|
||||
<motion.div
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
exit={{ opacity: 0, x: -300 }}
|
||||
initial={{ opacity: 0, y: 300 }}>
|
||||
<HyperglassForm />
|
||||
</motion.div>
|
||||
</If>
|
||||
</AnimatePresence>
|
||||
</Frame>
|
||||
);
|
||||
};
|
3
hyperglass/ui/components/layout/types.ts
Normal file
3
hyperglass/ui/components/layout/types.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import type { FlexProps } from '@chakra-ui/react';
|
||||
|
||||
export interface TFrame extends FlexProps {}
|
@@ -8,7 +8,7 @@ import { Result } from './individual';
|
||||
import type { TResults } from './types';
|
||||
|
||||
export const Results = (props: TResults) => {
|
||||
const { queryLocation, queryType, queryVrf, queryTarget, setSubmitting, ...rest } = props;
|
||||
const { queryLocation, queryType, queryVrf, queryTarget, ...rest } = props;
|
||||
const { request_timeout, devices, queries, vrfs, web } = useConfig();
|
||||
const targetBg = useToken('colors', 'teal.600');
|
||||
const queryBg = useToken('colors', 'cyan.500');
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import dynamic from 'next/dynamic';
|
||||
import { forwardRef, useMemo } from 'react';
|
||||
import { AccordionIcon, Icon, Spinner, Stack, Text, Tooltip, useColorMode } from '@chakra-ui/react';
|
||||
import { AccordionIcon, Icon, Spinner, Stack, Text, Tooltip } from '@chakra-ui/react';
|
||||
import { useConfig, useColorValue } from '~/context';
|
||||
import { useStrf } from '~/hooks';
|
||||
|
||||
|
@@ -32,7 +32,6 @@ export interface TResult {
|
||||
}
|
||||
|
||||
export interface TResults extends BoxProps {
|
||||
setSubmitting(v: boolean): boolean;
|
||||
queryType: TQueryTypes;
|
||||
queryLocation: string[];
|
||||
queryTarget: string;
|
||||
|
1
hyperglass/ui/components/select/index.ts
Normal file
1
hyperglass/ui/components/select/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './select';
|
81
hyperglass/ui/components/select/select.tsx
Normal file
81
hyperglass/ui/components/select/select.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import { createContext, useContext, useMemo, useState } from 'react';
|
||||
import ReactSelect from 'react-select';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { useColorMode } from '~/context';
|
||||
import {
|
||||
useRSTheme,
|
||||
useMenuStyle,
|
||||
useMenuPortal,
|
||||
useOptionStyle,
|
||||
useControlStyle,
|
||||
useMenuListStyle,
|
||||
useMultiValueStyle,
|
||||
usePlaceholderStyle,
|
||||
useSingleValueStyle,
|
||||
useMultiValueLabelStyle,
|
||||
useMultiValueRemoveStyle,
|
||||
useIndicatorSeparatorStyle,
|
||||
} from './styles';
|
||||
|
||||
import type { TSelect, TSelectOption, TSelectContext, TBoxAsReactSelect } from './types';
|
||||
|
||||
const SelectContext = createContext<TSelectContext>(Object());
|
||||
export const useSelectContext = () => useContext(SelectContext);
|
||||
|
||||
const ReactSelectAsBox = (props: TBoxAsReactSelect) => <Box as={ReactSelect} {...props} />;
|
||||
|
||||
export const Select = (props: TSelect) => {
|
||||
const { ctl, options, multi, onSelect, ...rest } = props;
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
const { colorMode } = useColorMode();
|
||||
|
||||
const selectContext = useMemo<TSelectContext>(() => ({ colorMode, isOpen }), [colorMode, isOpen]);
|
||||
|
||||
const handleChange = (changed: TSelectOption | TSelectOption[]) => {
|
||||
if (!Array.isArray(changed)) {
|
||||
changed = [changed];
|
||||
}
|
||||
if (typeof onSelect === 'function') {
|
||||
onSelect(changed);
|
||||
}
|
||||
};
|
||||
|
||||
const multiValue = useMultiValueStyle({ colorMode });
|
||||
const multiValueLabel = useMultiValueLabelStyle({ colorMode });
|
||||
const multiValueRemove = useMultiValueRemoveStyle({ colorMode });
|
||||
const menuPortal = useMenuPortal({ colorMode });
|
||||
const rsTheme = useRSTheme({ colorMode });
|
||||
|
||||
return (
|
||||
<SelectContext.Provider value={selectContext}>
|
||||
<ReactSelectAsBox
|
||||
as={ReactSelect}
|
||||
options={options}
|
||||
isMulti={multi}
|
||||
onChange={handleChange}
|
||||
ref={ctl}
|
||||
onMenuClose={() => {
|
||||
isOpen && setIsOpen(false);
|
||||
}}
|
||||
onMenuOpen={() => {
|
||||
!isOpen && setIsOpen(true);
|
||||
}}
|
||||
theme={rsTheme}
|
||||
styles={{
|
||||
menuPortal,
|
||||
multiValue,
|
||||
multiValueLabel,
|
||||
multiValueRemove,
|
||||
menu: useMenuStyle,
|
||||
option: useOptionStyle,
|
||||
control: useControlStyle,
|
||||
menuList: useMenuListStyle,
|
||||
singleValue: useSingleValueStyle,
|
||||
placeholder: usePlaceholderStyle,
|
||||
indicatorSeparator: useIndicatorSeparatorStyle,
|
||||
}}
|
||||
{...rest}
|
||||
/>
|
||||
</SelectContext.Provider>
|
||||
);
|
||||
};
|
221
hyperglass/ui/components/select/styles.tsx
Normal file
221
hyperglass/ui/components/select/styles.tsx
Normal file
@@ -0,0 +1,221 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useToken } from '@chakra-ui/react';
|
||||
import { mergeWith } from '@chakra-ui/utils';
|
||||
import { useOpposingColor } from '~/hooks';
|
||||
import { useColorValue, useMobile } from '~/context';
|
||||
import { useSelectContext } from './select';
|
||||
|
||||
import type {
|
||||
TControl,
|
||||
TIndicator,
|
||||
TMenu,
|
||||
TMenuList,
|
||||
TMultiValueState,
|
||||
TOption,
|
||||
TPlaceholder,
|
||||
TStyles,
|
||||
TRSTheme,
|
||||
TMultiValue,
|
||||
} from './types';
|
||||
|
||||
export const useControlStyle = (base: TStyles, state: TControl): TStyles => {
|
||||
const { isFocused } = state;
|
||||
const { colorMode } = useSelectContext();
|
||||
const borderHover = useColorValue(
|
||||
useToken('colors', 'gray.300'),
|
||||
useToken('colors', 'whiteAlpha.200'),
|
||||
);
|
||||
const focusBorder = useToken('colors', 'secondary.500');
|
||||
const invalidBorder = useColorValue(useToken('colors', 'red.500'), useToken('colors', 'red.300'));
|
||||
|
||||
const borderRadius = useToken('radii', 'md');
|
||||
const minHeight = useToken('sizes', 'lg');
|
||||
const color = useColorValue(useToken('colors', 'black'), useToken('colors', 'whiteAlpha.800'));
|
||||
const backgroundColor = useColorValue(
|
||||
useToken('colors', 'white'),
|
||||
useToken('colors', 'whiteAlpha.100'),
|
||||
);
|
||||
const styles = {
|
||||
backgroundColor,
|
||||
borderRadius,
|
||||
color,
|
||||
minHeight,
|
||||
'&:hover': { borderColor: isFocused ? focusBorder : borderHover },
|
||||
'&:hover > div > span': { backgroundColor: borderHover },
|
||||
|
||||
'&.invalid': { borderColor: invalidBorder, boxShadow: `0 0 0 1px ${invalidBorder}` },
|
||||
};
|
||||
return useMemo(() => mergeWith({}, base, styles), [colorMode, isFocused]);
|
||||
};
|
||||
|
||||
export const useMenuStyle = (base: TStyles, state: TMenu): TStyles => {
|
||||
const { colorMode, isOpen } = useSelectContext();
|
||||
const backgroundColor = useColorValue(
|
||||
useToken('colors', 'white'),
|
||||
useToken('colors', 'whiteFaded.50'),
|
||||
);
|
||||
const borderRadius = useToken('radii', 'md');
|
||||
const styles = { borderRadius, backgroundColor };
|
||||
return useMemo(() => mergeWith({}, base, styles), [colorMode, isOpen]);
|
||||
};
|
||||
|
||||
export const useMenuListStyle = (base: TStyles, state: TMenuList): TStyles => {
|
||||
const { colorMode, isOpen } = useSelectContext();
|
||||
|
||||
const scrollbarTrack = useColorValue(
|
||||
useToken('colors', 'blackAlpha.50'),
|
||||
useToken('colors', 'whiteAlpha.50'),
|
||||
);
|
||||
const scrollbarThumb = useColorValue(
|
||||
useToken('colors', 'blackAlpha.300'),
|
||||
useToken('colors', 'whiteAlpha.300'),
|
||||
);
|
||||
|
||||
const scrollbarThumbHover = useColorValue(
|
||||
useToken('colors', 'blackAlpha.400'),
|
||||
useToken('colors', 'whiteAlpha.400'),
|
||||
);
|
||||
|
||||
const styles = {
|
||||
'&::-webkit-scrollbar': { width: '5px' },
|
||||
'&::-webkit-scrollbar-track': { backgroundColor: scrollbarTrack },
|
||||
'&::-webkit-scrollbar-thumb': { backgroundColor: scrollbarThumb },
|
||||
'&::-webkit-scrollbar-thumb:hover': { backgroundColor: scrollbarThumbHover },
|
||||
'-ms-overflow-style': { display: 'none' },
|
||||
};
|
||||
return useMemo(() => mergeWith({}, base, styles), [colorMode, isOpen]);
|
||||
};
|
||||
|
||||
export const useOptionStyle = (base: TStyles, state: TOption): TStyles => {
|
||||
const { isFocused } = state;
|
||||
const { colorMode, isOpen } = useSelectContext();
|
||||
const fontSize = useToken('fontSizes', 'lg');
|
||||
const disabled = useToken('colors', 'whiteAlpha.400');
|
||||
const selected = useColorValue(
|
||||
useToken('colors', 'blackAlpha.400'),
|
||||
useToken('colors', 'whiteAlpha.400'),
|
||||
);
|
||||
const focused = useColorValue(
|
||||
useToken('colors', 'primary.500'),
|
||||
useToken('colors', 'primary.300'),
|
||||
);
|
||||
const active = useColorValue(
|
||||
useToken('colors', 'primary.600'),
|
||||
useToken('colors', 'primary.400'),
|
||||
);
|
||||
const disabledColor = useOpposingColor(disabled);
|
||||
const selectedColor = useOpposingColor(selected);
|
||||
const focusedColor = useOpposingColor(focused);
|
||||
const activeColor = useOpposingColor(active);
|
||||
|
||||
const styles = {
|
||||
backgroundColor: state.isDisabled
|
||||
? disabled
|
||||
: state.isSelected
|
||||
? selected
|
||||
: state.isFocused
|
||||
? focused
|
||||
: 'transparent',
|
||||
color: state.isDisabled
|
||||
? disabledColor
|
||||
: state.isSelected
|
||||
? selectedColor
|
||||
: state.isFocused
|
||||
? focusedColor
|
||||
: 'transparent',
|
||||
fontSize,
|
||||
'&:focus': { backgroundColor: active, color: activeColor },
|
||||
'&:active': { backgroundColor: active, color: activeColor },
|
||||
};
|
||||
return useMemo(() => mergeWith({}, base, styles), [colorMode, isFocused, isOpen]);
|
||||
};
|
||||
|
||||
export const useIndicatorSeparatorStyle = (base: TStyles, state: TIndicator): TStyles => {
|
||||
const { colorMode } = useSelectContext();
|
||||
const backgroundColor = useColorValue(
|
||||
useToken('colors', 'whiteAlpha.700'),
|
||||
useToken('colors', 'gray.600'),
|
||||
);
|
||||
const styles = { backgroundColor };
|
||||
return useMemo(() => mergeWith({}, base, styles), [colorMode]);
|
||||
};
|
||||
|
||||
export const usePlaceholderStyle = (base: TStyles, state: TPlaceholder): TStyles => {
|
||||
const { colorMode } = useSelectContext();
|
||||
const color = useColorValue(useToken('colors', 'whiteAlpha.700'), useToken('colors', 'gray.600'));
|
||||
return useMemo(() => mergeWith({}, base, { color }), [colorMode]);
|
||||
};
|
||||
|
||||
export const useSingleValueStyle = (props: TStyles) => {
|
||||
const { colorMode } = useSelectContext();
|
||||
|
||||
const color = useColorValue('black', 'whiteAlpha.800');
|
||||
const fontSize = useToken('fontSizes', 'lg');
|
||||
|
||||
const styles = { color, fontSize };
|
||||
return useCallback((base: TStyles, state: TMultiValueState) => mergeWith({}, base, styles), [
|
||||
color,
|
||||
colorMode,
|
||||
]);
|
||||
};
|
||||
|
||||
export const useMultiValueStyle = (props: TMultiValue) => {
|
||||
const { colorMode } = props;
|
||||
|
||||
const backgroundColor = useColorValue(
|
||||
useToken('colors', 'primary.500'),
|
||||
useToken('colors', 'primary.300'),
|
||||
);
|
||||
const color = useOpposingColor(backgroundColor);
|
||||
const styles = { backgroundColor, color };
|
||||
|
||||
return useCallback((base: TStyles, state: TMultiValueState) => mergeWith({}, base, styles), [
|
||||
backgroundColor,
|
||||
colorMode,
|
||||
]);
|
||||
};
|
||||
|
||||
export const useMultiValueLabelStyle = (props: TMultiValue) => {
|
||||
const { colorMode } = props;
|
||||
const backgroundColor = useColorValue(
|
||||
useToken('colors', 'primary.500'),
|
||||
useToken('colors', 'primary.300'),
|
||||
);
|
||||
const color = useOpposingColor(backgroundColor);
|
||||
const styles = { color };
|
||||
|
||||
return useCallback((base: TStyles, state: TMultiValueState) => mergeWith({}, base, styles), [
|
||||
colorMode,
|
||||
]);
|
||||
};
|
||||
|
||||
export const useMultiValueRemoveStyle = (props: TMultiValue) => {
|
||||
const { colorMode } = props;
|
||||
const backgroundColor = useColorValue(
|
||||
useToken('colors', 'primary.500'),
|
||||
useToken('colors', 'primary.300'),
|
||||
);
|
||||
const color = useOpposingColor(backgroundColor);
|
||||
const styles = {
|
||||
color,
|
||||
'&:hover': { backgroundColor: 'inherit', color, opacity: 0.7 },
|
||||
};
|
||||
return useCallback((base: TStyles, state: TMultiValueState) => mergeWith({}, base, styles), [
|
||||
colorMode,
|
||||
]);
|
||||
};
|
||||
|
||||
export const useRSTheme = (props: TMultiValue) => {
|
||||
const borderRadius = useToken('radii', 'md');
|
||||
return useCallback((t: TRSTheme): TRSTheme => ({ ...t, borderRadius }), []);
|
||||
};
|
||||
|
||||
export const useMenuPortal = (props: TMultiValue) => {
|
||||
const isMobile = useMobile();
|
||||
const styles = {
|
||||
zIndex: isMobile ? 1500 : 1,
|
||||
};
|
||||
return useCallback((base: TStyles, state: TMultiValueState) => mergeWith({}, base, styles), [
|
||||
isMobile,
|
||||
]);
|
||||
};
|
81
hyperglass/ui/components/select/types.ts
Normal file
81
hyperglass/ui/components/select/types.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import type {
|
||||
Props as IReactSelect,
|
||||
ControlProps,
|
||||
MenuProps,
|
||||
MenuListComponentProps,
|
||||
OptionProps,
|
||||
MultiValueProps,
|
||||
IndicatorProps,
|
||||
Theme,
|
||||
PlaceholderProps,
|
||||
} from 'react-select';
|
||||
import type { BoxProps } from '@chakra-ui/react';
|
||||
import type { ColorNames } from '~/types';
|
||||
|
||||
export interface TSelectState {
|
||||
[k: string]: string[];
|
||||
}
|
||||
|
||||
export type TSelectOption = {
|
||||
label: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type TSelectOptionGroup = {
|
||||
label: string;
|
||||
options: TSelectOption[];
|
||||
};
|
||||
|
||||
export type TOptions = Array<TSelectOptionGroup | TSelectOption>;
|
||||
|
||||
export type TBoxAsReactSelect = Omit<IReactSelect, 'isMulti' | 'onSelect' | 'onChange'> &
|
||||
Omit<BoxProps, 'onChange' | 'onSelect'>;
|
||||
|
||||
export interface TSelect extends TBoxAsReactSelect {
|
||||
options: TOptions;
|
||||
name: string;
|
||||
required?: boolean;
|
||||
multi?: boolean;
|
||||
onSelect?: (v: TSelectOption[]) => void;
|
||||
onChange?: (c: TSelectOption | TSelectOption[]) => void;
|
||||
colorScheme?: ColorNames;
|
||||
}
|
||||
|
||||
export interface TSelectContext {
|
||||
colorMode: 'light' | 'dark';
|
||||
isOpen: boolean;
|
||||
}
|
||||
|
||||
export interface TMultiValueRemoveProps {
|
||||
children: Node;
|
||||
data: any;
|
||||
innerProps: {
|
||||
className: string;
|
||||
onTouchEnd: (e: any) => void;
|
||||
onClick: (e: any) => void;
|
||||
onMouseDown: (e: any) => void;
|
||||
};
|
||||
selectProps: any;
|
||||
}
|
||||
|
||||
export interface TRSTheme extends Omit<Theme, 'borderRadius'> {
|
||||
borderRadius: string | number;
|
||||
}
|
||||
|
||||
export type TControl = ControlProps<TOptions>;
|
||||
|
||||
export type TMenu = MenuProps<TOptions>;
|
||||
|
||||
export type TMenuList = MenuListComponentProps<TOptions>;
|
||||
|
||||
export type TOption = OptionProps<TOptions>;
|
||||
|
||||
export type TMultiValueState = MultiValueProps<TOptions>;
|
||||
|
||||
export type TIndicator = IndicatorProps<TOptions>;
|
||||
|
||||
export type TPlaceholder = PlaceholderProps<TOptions>;
|
||||
|
||||
export type TMultiValue = Pick<TSelectContext, 'colorMode'>;
|
||||
|
||||
export type { Styles as TStyles } from 'react-select';
|
1
hyperglass/ui/components/title/index.ts
Normal file
1
hyperglass/ui/components/title/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './title';
|
33
hyperglass/ui/components/title/logo.tsx
Normal file
33
hyperglass/ui/components/title/logo.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Image } from '@chakra-ui/react';
|
||||
import { useColorValue, useConfig } from '~/context';
|
||||
|
||||
import type { TLogo } from './types';
|
||||
|
||||
export const Logo = (props: TLogo) => {
|
||||
const { web } = useConfig();
|
||||
const { width, dark_format, light_format } = web.logo;
|
||||
|
||||
const src = useColorValue(`/images/dark${dark_format}`, `/images/light${light_format}`);
|
||||
const fallbackSrc = useColorValue(
|
||||
'https://res.cloudinary.com/hyperglass/image/upload/v1593916013/logo-dark.svg',
|
||||
'https://res.cloudinary.com/hyperglass/image/upload/v1593916013/logo-light.svg',
|
||||
);
|
||||
|
||||
return (
|
||||
<Image
|
||||
css={{
|
||||
userDrag: 'none',
|
||||
userSelect: 'none',
|
||||
msUserSelect: 'none',
|
||||
MozUserSelect: 'none',
|
||||
WebkitUserDrag: 'none',
|
||||
WebkitUserSelect: 'none',
|
||||
}}
|
||||
fallbackSrc={fallbackSrc}
|
||||
width={width ?? 'auto'}
|
||||
alt={web.text.title}
|
||||
src={src}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
18
hyperglass/ui/components/title/subtitleOnly.tsx
Normal file
18
hyperglass/ui/components/title/subtitleOnly.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Heading } from '@chakra-ui/react';
|
||||
import { useConfig } from '~/context';
|
||||
|
||||
import type { TSubtitleOnly } from './types';
|
||||
|
||||
export const SubtitleOnly = (props: TSubtitleOnly) => {
|
||||
const { web } = useConfig();
|
||||
return (
|
||||
<Heading
|
||||
as="h3"
|
||||
whiteSpace="break-spaces"
|
||||
fontSize={{ base: 'md', lg: 'xl' }}
|
||||
textAlign={{ base: 'left', xl: 'center' }}
|
||||
{...props}>
|
||||
{web.text.subtitle}
|
||||
</Heading>
|
||||
);
|
||||
};
|
85
hyperglass/ui/components/title/title.tsx
Normal file
85
hyperglass/ui/components/title/title.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
import { forwardRef } from 'react';
|
||||
import { Button, Stack } from '@chakra-ui/react';
|
||||
import { If } from '~/components';
|
||||
import { useConfig, useGlobalState } from '~/context';
|
||||
import { useBooleanValue } from '~/hooks';
|
||||
import { TitleOnly } from './titleOnly';
|
||||
import { SubtitleOnly } from './subtitleOnly';
|
||||
import { Logo } from './logo';
|
||||
|
||||
import type { TTitle, TTextOnly } from './types';
|
||||
|
||||
const TextOnly = (props: TTextOnly) => {
|
||||
const { showSubtitle, ...rest } = props;
|
||||
const textAlign = useBooleanValue(
|
||||
showSubtitle,
|
||||
{ base: 'right', md: 'center' },
|
||||
{ base: 'left', md: 'center' },
|
||||
);
|
||||
return (
|
||||
<Stack spacing={2} maxW="100%" textAlign={textAlign} {...rest}>
|
||||
<TitleOnly showSubtitle={showSubtitle} />
|
||||
<If c={showSubtitle}>
|
||||
<SubtitleOnly />
|
||||
</If>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
const LogoSubtitle = () => (
|
||||
<>
|
||||
<Logo />
|
||||
<SubtitleOnly mt={6} />
|
||||
</>
|
||||
);
|
||||
|
||||
const All = (props: TTextOnly) => {
|
||||
const { showSubtitle, ...rest } = props;
|
||||
return (
|
||||
<>
|
||||
<Logo />
|
||||
<TextOnly showSubtitle={showSubtitle} mt={2} {...rest} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const Title = forwardRef<HTMLButtonElement, TTitle>((props, ref) => {
|
||||
const { web } = useConfig();
|
||||
const titleMode = web.text.title_mode;
|
||||
|
||||
const { isSubmitting } = useGlobalState();
|
||||
|
||||
const justify = useBooleanValue(
|
||||
isSubmitting.value,
|
||||
{ base: 'flex-end', md: 'center' },
|
||||
{ base: 'flex-start', md: 'center' },
|
||||
);
|
||||
|
||||
return (
|
||||
<Button
|
||||
px={0}
|
||||
w="100%"
|
||||
ref={ref}
|
||||
variant="link"
|
||||
flexWrap="wrap"
|
||||
flexDir="column"
|
||||
justifyContent={justify}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
_hover={{ textDecoration: 'none' }}
|
||||
alignItems={{ base: 'flex-start', lg: 'center' }}
|
||||
{...props}>
|
||||
<If c={titleMode === 'text_only'}>
|
||||
<TextOnly showSubtitle={!isSubmitting.value} />
|
||||
</If>
|
||||
<If c={titleMode === 'logo_only'}>
|
||||
<Logo />
|
||||
</If>
|
||||
<If c={titleMode === 'logo_subtitle'}>
|
||||
<LogoSubtitle />
|
||||
</If>
|
||||
<If c={titleMode === 'all'}>
|
||||
<All showSubtitle={!isSubmitting.value} />
|
||||
</If>
|
||||
</Button>
|
||||
);
|
||||
});
|
17
hyperglass/ui/components/title/titleOnly.tsx
Normal file
17
hyperglass/ui/components/title/titleOnly.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Heading } from '@chakra-ui/react';
|
||||
import { useConfig } from '~/context';
|
||||
import { useBooleanValue } from '~/hooks';
|
||||
|
||||
import type { TTitleOnly } from './types';
|
||||
|
||||
export const TitleOnly = (props: TTitleOnly) => {
|
||||
const { showSubtitle, ...rest } = props;
|
||||
const { web } = useConfig();
|
||||
const fontSize = useBooleanValue(showSubtitle, { base: '2xl', lg: '5xl' }, '2xl');
|
||||
const margin = useBooleanValue(showSubtitle, 2, 0);
|
||||
return (
|
||||
<Heading as="h1" mb={margin} fontSize={fontSize} {...rest}>
|
||||
{web.text.title}
|
||||
</Heading>
|
||||
);
|
||||
};
|
15
hyperglass/ui/components/title/types.ts
Normal file
15
hyperglass/ui/components/title/types.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import type { ButtonProps, HeadingProps, ImageProps, StackProps } from '@chakra-ui/react';
|
||||
|
||||
export interface TTitle extends ButtonProps {}
|
||||
|
||||
export interface TTitleOnly extends HeadingProps {
|
||||
showSubtitle: boolean;
|
||||
}
|
||||
|
||||
export interface TSubtitleOnly extends HeadingProps {}
|
||||
|
||||
export interface TLogo extends ImageProps {}
|
||||
|
||||
export interface TTextOnly extends StackProps {
|
||||
showSubtitle: boolean;
|
||||
}
|
@@ -1,41 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { createContext, useContext, useMemo } from 'react';
|
||||
import { useMediaLayout } from 'use-media';
|
||||
|
||||
const MediaContext = createContext(null);
|
||||
|
||||
export const MediaProvider = ({ theme, children }) => {
|
||||
const { sm, md, lg, xl } = theme.breakpoints;
|
||||
const isSm = useMediaLayout({ maxWidth: md });
|
||||
const isMd = useMediaLayout({ minWidth: md, maxWidth: lg });
|
||||
const isLg = useMediaLayout({ minWidth: lg, maxWidth: xl });
|
||||
const isXl = useMediaLayout({ minWidth: xl });
|
||||
let mediaSize = false;
|
||||
switch (true) {
|
||||
case isSm:
|
||||
mediaSize = 'sm';
|
||||
break;
|
||||
case isMd:
|
||||
mediaSize = 'md';
|
||||
break;
|
||||
case isLg:
|
||||
mediaSize = 'lg';
|
||||
break;
|
||||
case isXl:
|
||||
mediaSize = 'xl';
|
||||
break;
|
||||
}
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
isSm: isSm,
|
||||
isMd: isMd,
|
||||
isLg: isLg,
|
||||
isXl: isXl,
|
||||
mediaSize: mediaSize,
|
||||
}),
|
||||
[isSm, isMd, isLg, isXl, mediaSize],
|
||||
);
|
||||
return <MediaContext.Provider value={value}>{children}</MediaContext.Provider>;
|
||||
};
|
||||
|
||||
export const useMedia = () => useContext(MediaContext);
|
@@ -2,12 +2,10 @@ import * as React from 'react';
|
||||
import Head from 'next/head';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { Meta, Loading } from 'app/components';
|
||||
const LookingGlass = dynamic(
|
||||
() => import('app/components/LookingGlass').then(i => i.LookingGlass),
|
||||
{
|
||||
loading: Loading,
|
||||
},
|
||||
);
|
||||
|
||||
const Layout = dynamic(() => import('~/components').then(i => i.Layout), {
|
||||
loading: Loading,
|
||||
});
|
||||
|
||||
const Index = ({ faviconComponents }) => {
|
||||
return (
|
||||
@@ -18,7 +16,7 @@ const Index = ({ faviconComponents }) => {
|
||||
))}
|
||||
</Head>
|
||||
<Meta />
|
||||
<LookingGlass />
|
||||
<Layout />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@@ -1,5 +1,7 @@
|
||||
import { Colors, Fonts } from './theme';
|
||||
|
||||
export type TQueryFields = 'query_type' | 'query_target' | 'query_location' | 'query_vrf';
|
||||
|
||||
export interface IConfigMessages {
|
||||
no_input: string;
|
||||
acl_denied: string;
|
||||
@@ -49,13 +51,20 @@ export interface TConfigGreeting {
|
||||
required: boolean;
|
||||
}
|
||||
|
||||
export interface TConfigWebLogo {
|
||||
width: string;
|
||||
height: string | null;
|
||||
light_format: string;
|
||||
dark_format: string;
|
||||
}
|
||||
|
||||
export interface IConfigWeb {
|
||||
credit: { enable: boolean };
|
||||
dns_provider: { name: string; url: string };
|
||||
external_link: { enable: boolean; title: string; url: string };
|
||||
greeting: TConfigGreeting;
|
||||
help_menu: { enable: boolean; title: string };
|
||||
logo: { width: string; height: string | null; light_format: string; dark_format: string };
|
||||
logo: TConfigWebLogo;
|
||||
terms: { enable: boolean; title: string };
|
||||
text: IConfigWebText;
|
||||
theme: IConfigTheme;
|
||||
@@ -111,18 +120,18 @@ export interface TDevice extends TDeviceBase {
|
||||
vrfs: IDeviceVrf[];
|
||||
}
|
||||
|
||||
export interface INetworkLocation extends TDeviceBase {
|
||||
export interface TNetworkLocation extends TDeviceBase {
|
||||
vrfs: IDeviceVrfBase[];
|
||||
}
|
||||
|
||||
export interface INetwork {
|
||||
export interface TNetwork {
|
||||
display_name: string;
|
||||
locations: INetworkLocation[];
|
||||
locations: TNetworkLocation[];
|
||||
}
|
||||
|
||||
export type TParsedDataField = [string, keyof TRoute, 'left' | 'right' | 'center' | null];
|
||||
|
||||
export interface IQueryContent {
|
||||
export interface TQueryContent {
|
||||
content: string;
|
||||
enable: boolean;
|
||||
params: {
|
||||
@@ -141,11 +150,11 @@ export interface IConfigContent {
|
||||
greeting: string;
|
||||
vrf: {
|
||||
[k: string]: {
|
||||
bgp_route: IQueryContent;
|
||||
bgp_community: IQueryContent;
|
||||
bgp_aspath: IQueryContent;
|
||||
ping: IQueryContent;
|
||||
traceroute: IQueryContent;
|
||||
bgp_route: TQueryContent;
|
||||
bgp_community: TQueryContent;
|
||||
bgp_aspath: TQueryContent;
|
||||
ping: TQueryContent;
|
||||
traceroute: TQueryContent;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -166,6 +175,7 @@ export interface IConfig {
|
||||
hyperglass_version: string;
|
||||
queries: IConfigQueries;
|
||||
devices: TDevice[];
|
||||
networks: TNetwork[];
|
||||
vrfs: IDeviceVrfBase[];
|
||||
parsed_data_fields: TParsedDataField[];
|
||||
content: IConfigContent;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
export type TQueryTypes = 'bgp_route' | 'bgp_community' | 'bgp_aspath' | 'ping' | 'traceroute';
|
||||
export type TQueryTypes = '' | 'bgp_route' | 'bgp_community' | 'bgp_aspath' | 'ping' | 'traceroute';
|
||||
|
||||
export interface IFormData {
|
||||
query_location: string[];
|
||||
|
2
hyperglass/ui/types/globals.d.ts
vendored
2
hyperglass/ui/types/globals.d.ts
vendored
@@ -62,5 +62,5 @@ declare global {
|
||||
type Animated<T> = Omit<T, keyof MotionProps> &
|
||||
Omit<MotionProps, keyof T> & { transition?: MotionProps['transition'] };
|
||||
|
||||
type MeronexIcon = import('@meronex/icons').IconType;
|
||||
type MeronexIcon = import('@meronex/icons').IconBaseProps;
|
||||
}
|
||||
|
@@ -35,10 +35,10 @@ interface CustomColors {
|
||||
}
|
||||
|
||||
type AllColors = CustomColors & ChakraColors;
|
||||
type ColorKey = keyof AllColors;
|
||||
export type ColorNames = keyof AllColors;
|
||||
|
||||
export interface Colors extends AllColors {
|
||||
original: { [key in ColorKey]: string };
|
||||
original: { [key in ColorNames]: string };
|
||||
}
|
||||
|
||||
export interface Fonts {
|
||||
|
8
hyperglass/ui/util/common.ts
Normal file
8
hyperglass/ui/util/common.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export function all(...iter: any[]) {
|
||||
for (let i of iter) {
|
||||
if (!i) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
@@ -1,2 +1,3 @@
|
||||
export * from './common';
|
||||
export * from './formatters';
|
||||
export * from './theme';
|
||||
|
Reference in New Issue
Block a user