mirror of
https://github.com/checktheroads/hyperglass
synced 2024-05-11 05:55:08 +00:00
update UI deps, migrate from yup to vest for form validation, migrate to palette-by-numbers for theming
This commit is contained in:
@@ -4,7 +4,8 @@ import { If } from '~/components';
|
||||
import { useColorValue } from '~/context';
|
||||
import { useBooleanValue } from '~/hooks';
|
||||
|
||||
import { TField, TFormError } from './types';
|
||||
import type { FieldError } from 'react-hook-form';
|
||||
import type { TField } from './types';
|
||||
|
||||
export const FormField: React.FC<TField> = (props: TField) => {
|
||||
const { name, label, children, labelAddOn, fieldAddOn, hiddenLabels = false, ...rest } = props;
|
||||
@@ -14,10 +15,10 @@ export const FormField: React.FC<TField> = (props: TField) => {
|
||||
|
||||
const { errors } = useFormContext();
|
||||
|
||||
const error = name in errors && (errors[name] as TFormError);
|
||||
const error = name in errors && (errors[name] as FieldError);
|
||||
|
||||
if (error !== false) {
|
||||
console.warn(`${label} Error: ${error.message}`);
|
||||
console.warn(`Error on field '${label}': ${error.message}`);
|
||||
}
|
||||
|
||||
return (
|
||||
|
@@ -1,9 +1,6 @@
|
||||
import type { FormControlProps } from '@chakra-ui/react';
|
||||
import type { Control } from 'react-hook-form';
|
||||
import type { TDeviceVrf, TBGPCommunity, OnChangeArgs } from '~/types';
|
||||
import type { ValidationError } from 'yup';
|
||||
|
||||
export type TFormError = Pick<ValidationError, 'message' | 'type'>;
|
||||
|
||||
export interface TField extends FormControlProps {
|
||||
name: string;
|
||||
|
@@ -2,8 +2,8 @@ import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { intersectionWith } from 'lodash';
|
||||
import * as yup from 'yup';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { vestResolver } from '@hookform/resolvers/vest';
|
||||
import vest, { test, enforce } from 'vest';
|
||||
import {
|
||||
If,
|
||||
FormRow,
|
||||
@@ -51,15 +51,29 @@ export const LookingGlass: React.FC = () => {
|
||||
const noQueryLoc = useStrf(messages.no_input, { field: web.text.query_location });
|
||||
const noQueryTarget = useStrf(messages.no_input, { field: web.text.query_target });
|
||||
|
||||
const formSchema = yup.object().shape({
|
||||
query_location: yup.array().of(yup.string()).required(noQueryLoc),
|
||||
query_target: yup.string().required(noQueryTarget),
|
||||
query_type: yup.string().required(noQueryType),
|
||||
query_vrf: yup.string(),
|
||||
const formSchema = vest.create((data: TFormData = {} as TFormData) => {
|
||||
test('query_location', noQueryLoc, () => {
|
||||
enforce(data.query_location).isArrayOf(enforce.isString()).isNotEmpty();
|
||||
});
|
||||
test('query_target', noQueryTarget, () => {
|
||||
enforce(data.query_target).longerThan(1);
|
||||
});
|
||||
test('query_type', noQueryType, () => {
|
||||
enforce(data.query_type).anyOf(
|
||||
enforce.equals('bgp_route'),
|
||||
enforce.equals('bgp_community'),
|
||||
enforce.equals('bgp_aspath'),
|
||||
enforce.equals('ping'),
|
||||
enforce.equals('traceroute'),
|
||||
);
|
||||
});
|
||||
test('query_vrf', 'Query VRF is empty', () => {
|
||||
enforce(data.query_vrf).isString();
|
||||
});
|
||||
});
|
||||
|
||||
const formInstance = useForm<TFormData>({
|
||||
resolver: yupResolver(formSchema),
|
||||
resolver: vestResolver(formSchema),
|
||||
defaultValues: { query_vrf: 'default', query_target: '', query_location: [], query_type: '' },
|
||||
});
|
||||
|
||||
|
19
hyperglass/ui/package.json
vendored
19
hyperglass/ui/package.json
vendored
@@ -18,19 +18,19 @@
|
||||
},
|
||||
"browserslist": "> 0.25%, not dead",
|
||||
"dependencies": {
|
||||
"@chakra-ui/react": "^1.1.6",
|
||||
"@emotion/react": "^11.1.4",
|
||||
"@emotion/styled": "^11.0.0",
|
||||
"@hookform/resolvers": "^1.2.0",
|
||||
"@hookstate/core": "^3.0.3",
|
||||
"@chakra-ui/react": "^1.3.3",
|
||||
"@emotion/react": "^11.1.5",
|
||||
"@emotion/styled": "^11.1.5",
|
||||
"@hookform/resolvers": "1.3.0",
|
||||
"@hookstate/core": "^3.0.6",
|
||||
"@hookstate/persistence": "^3.0.0",
|
||||
"@meronex/icons": "^4.0.0",
|
||||
"color2k": "^1.1.1",
|
||||
"dagre": "^0.8.5",
|
||||
"dayjs": "^1.8.25",
|
||||
"framer-motion": "^3.2.2-rc.1",
|
||||
"lodash": "^4.17.15",
|
||||
"next": "^10.0.5",
|
||||
"next": "^10.0.7",
|
||||
"palette-by-numbers": "^0.1.5",
|
||||
"react": "^17.0.1",
|
||||
"react-countdown": "^2.2.1",
|
||||
"react-device-detect": "^1.15.0",
|
||||
@@ -38,13 +38,13 @@
|
||||
"react-fast-compare": "^3.2.0",
|
||||
"react-flow-renderer": "^8.2.3",
|
||||
"react-ga": "^3.3.0",
|
||||
"react-hook-form": "^6.14.2",
|
||||
"react-hook-form": "^6.15.4",
|
||||
"react-markdown": "^5.0.3",
|
||||
"react-query": "^3.6.0",
|
||||
"react-select": "^3.1.1",
|
||||
"react-table": "^7.6.2",
|
||||
"string-format": "^2.0.0",
|
||||
"yup": "^0.32.8"
|
||||
"vest": "^3.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hookstate/devtools": "^3.0.0",
|
||||
@@ -54,7 +54,6 @@
|
||||
"@types/react-select": "^3.0.28",
|
||||
"@types/react-table": "^7.0.25",
|
||||
"@types/string-format": "^2.0.0",
|
||||
"@types/yup": "^0.29.9",
|
||||
"@typescript-eslint/eslint-plugin": "^4.11.1",
|
||||
"@typescript-eslint/parser": "^4.11.1",
|
||||
"@upstatement/eslint-config": "^0.4.3",
|
||||
|
@@ -1,21 +1,7 @@
|
||||
import type { Theme as DefaultTheme } from '@chakra-ui/theme';
|
||||
import type { ColorHues } from '@chakra-ui/theme/dist/types/foundations/colors';
|
||||
import type { ChakraTheme } from '@chakra-ui/theme';
|
||||
|
||||
export namespace Theme {
|
||||
type ExtraColors = {
|
||||
dark: ColorHues;
|
||||
light: ColorHues;
|
||||
error: ColorHues;
|
||||
danger: ColorHues;
|
||||
primary: ColorHues;
|
||||
success: ColorHues;
|
||||
warning: ColorHues;
|
||||
secondary: ColorHues;
|
||||
blackSolid: ColorHues;
|
||||
whiteSolid: ColorHues;
|
||||
};
|
||||
|
||||
export type Colors = ExtraColors & DefaultTheme['colors'];
|
||||
export type Colors = ChakraTheme['colors'];
|
||||
|
||||
export type ColorNames = keyof Colors;
|
||||
|
||||
@@ -24,9 +10,9 @@ export namespace Theme {
|
||||
mono: string;
|
||||
};
|
||||
|
||||
export type FontWeights = Partial<DefaultTheme['fontWeights']>;
|
||||
export type FontWeights = Partial<ChakraTheme['fontWeights']>;
|
||||
|
||||
export interface Full extends Omit<DefaultTheme, 'colors'> {
|
||||
export interface Full extends Omit<ChakraTheme, 'colors'> {
|
||||
colors: Colors;
|
||||
}
|
||||
}
|
||||
|
@@ -1,115 +1,27 @@
|
||||
import {
|
||||
hsla,
|
||||
saturate,
|
||||
desaturate,
|
||||
parseToHsla,
|
||||
transparentize,
|
||||
readableColorIsBlack,
|
||||
} from 'color2k';
|
||||
import { extendTheme } from '@chakra-ui/react';
|
||||
import { mode } from '@chakra-ui/theme-tools';
|
||||
import { generateFontFamily, generatePalette } from 'palette-by-numbers';
|
||||
|
||||
import type { Theme as ChakraTheme } from '@chakra-ui/react';
|
||||
import type { ChakraTheme } from '@chakra-ui/react';
|
||||
import type { IConfigTheme, Theme } from '~/types';
|
||||
|
||||
const defaultBodyFonts = [
|
||||
'-apple-system',
|
||||
'BlinkMacSystemFont',
|
||||
'"Segoe UI"',
|
||||
'Helvetica',
|
||||
'Arial',
|
||||
'sans-serif',
|
||||
'"Apple Color Emoji"',
|
||||
'"Segoe UI Emoji"',
|
||||
'"Segoe UI Symbol"',
|
||||
];
|
||||
function importFonts(userFonts: Theme.Fonts): ChakraTheme['fonts'] {
|
||||
const { body: userBody, mono: userMono } = userFonts;
|
||||
|
||||
const defaultMonoFonts = [
|
||||
'SFMono-Regular',
|
||||
'Melno',
|
||||
'Monaco',
|
||||
'Consolas',
|
||||
'"Liberation Mono"',
|
||||
'"Courier New"',
|
||||
'monospace',
|
||||
];
|
||||
|
||||
export function isLight(color: string): boolean {
|
||||
return readableColorIsBlack(color);
|
||||
}
|
||||
|
||||
export function isDark(color: string): boolean {
|
||||
return !readableColorIsBlack(color);
|
||||
}
|
||||
|
||||
function alphaColors(color: string) {
|
||||
return {
|
||||
50: transparentize(color, Number((1 - 0.04).toFixed(2))),
|
||||
100: transparentize(color, Number((1 - 0.08).toFixed(2))),
|
||||
200: transparentize(color, Number((1 - 0.12).toFixed(2))),
|
||||
300: transparentize(color, Number((1 - 0.16).toFixed(2))),
|
||||
400: transparentize(color, Number((1 - 0.24).toFixed(2))),
|
||||
500: transparentize(color, Number((1 - 0.38).toFixed(2))),
|
||||
600: transparentize(color, Number((1 - 0.48).toFixed(2))),
|
||||
700: transparentize(color, Number((1 - 0.6).toFixed(2))),
|
||||
800: transparentize(color, Number((1 - 0.8).toFixed(2))),
|
||||
900: transparentize(color, Number((1 - 0.92).toFixed(2))),
|
||||
body: generateFontFamily('sans-serif', userBody),
|
||||
heading: generateFontFamily('sans-serif', userBody),
|
||||
mono: generateFontFamily('monospace', userMono),
|
||||
};
|
||||
}
|
||||
|
||||
function generateColors(colorInput: string) {
|
||||
const colorMap = Object();
|
||||
|
||||
const lightnessMap = [0.95, 0.85, 0.75, 0.65, 0.55, 0.45, 0.35, 0.25, 0.15, 0.05];
|
||||
const saturationMap = [0.32, 0.16, 0.08, 0.04, 0, 0, 0.04, 0.08, 0.16, 0.32];
|
||||
|
||||
const colorHsla = parseToHsla(colorInput);
|
||||
const lightnessGoal = colorHsla[2];
|
||||
|
||||
const closestLightness = lightnessMap.reduce((prev, curr) =>
|
||||
Math.abs(curr - lightnessGoal) < Math.abs(prev - lightnessGoal) ? curr : prev,
|
||||
);
|
||||
|
||||
const baseColorIndex = lightnessMap.findIndex(l => l === closestLightness);
|
||||
|
||||
const colors = lightnessMap
|
||||
.map(l => {
|
||||
const [h, s, _, a] = colorHsla;
|
||||
return hsla(h, s, l, a);
|
||||
})
|
||||
.map((color, i) => {
|
||||
const saturationDelta = saturationMap[i] - saturationMap[baseColorIndex];
|
||||
return saturationDelta >= 0
|
||||
? saturate(color, saturationDelta)
|
||||
: desaturate(color, saturationDelta * -1);
|
||||
});
|
||||
|
||||
const getColorNumber = (index: number) => (index === 0 ? 50 : index * 100);
|
||||
|
||||
colors.map((color, i) => {
|
||||
const colorIndex = getColorNumber(i);
|
||||
if (colorIndex === 500) {
|
||||
colorMap[500] = colorInput;
|
||||
} else {
|
||||
colorMap[colorIndex] = color;
|
||||
}
|
||||
});
|
||||
return colorMap;
|
||||
}
|
||||
|
||||
function generatePalette(palette: IConfigTheme['colors']): Theme.Colors {
|
||||
const generatedPalette = Object();
|
||||
|
||||
for (const color of Object.keys(palette)) {
|
||||
if (!['black', 'white'].includes(color)) {
|
||||
generatedPalette[color] = generateColors(palette[color]);
|
||||
} else {
|
||||
generatedPalette[color] = palette[color];
|
||||
generatedPalette[`${color}Alpha`] = alphaColors(palette[color]);
|
||||
}
|
||||
function importColors(userColors: IConfigTheme['colors']): Theme.Colors {
|
||||
const generatedColors = {} as Theme.Colors;
|
||||
for (const [k, v] of Object.entries(userColors)) {
|
||||
generatedColors[k] = generatePalette(v);
|
||||
}
|
||||
|
||||
generatedPalette.blackSolid = {
|
||||
generatedColors.blackSolid = {
|
||||
50: '#444444',
|
||||
100: '#3c3c3c',
|
||||
200: '#353535',
|
||||
@@ -121,7 +33,7 @@ function generatePalette(palette: IConfigTheme['colors']): Theme.Colors {
|
||||
800: '#080808',
|
||||
900: '#000000',
|
||||
};
|
||||
generatedPalette.whiteSolid = {
|
||||
generatedColors.whiteSolid = {
|
||||
50: '#ffffff',
|
||||
100: '#f7f7f7',
|
||||
200: '#f0f0f0',
|
||||
@@ -133,38 +45,6 @@ function generatePalette(palette: IConfigTheme['colors']): Theme.Colors {
|
||||
800: '#c3c3c3',
|
||||
900: '#bbbbbb',
|
||||
};
|
||||
return generatedPalette;
|
||||
}
|
||||
|
||||
function formatFont(font: string): string {
|
||||
const fontList = font.split(' ');
|
||||
const fontFmt = fontList.length >= 2 ? `'${fontList.join(' ')}'` : fontList.join(' ');
|
||||
return fontFmt;
|
||||
}
|
||||
|
||||
function importFonts(userFonts: Theme.Fonts): [ChakraTheme['fonts'], Theme.FontWeights] {
|
||||
const [body, mono] = [defaultBodyFonts, defaultMonoFonts];
|
||||
const { body: userBody, mono: userMono, ...fontWeights } = userFonts;
|
||||
const bodyFmt = formatFont(userBody);
|
||||
const monoFmt = formatFont(userMono);
|
||||
if (userFonts.body && !body.includes(bodyFmt)) {
|
||||
body.unshift(bodyFmt);
|
||||
}
|
||||
if (userFonts.mono && !mono.includes(monoFmt)) {
|
||||
mono.unshift(monoFmt);
|
||||
}
|
||||
return [
|
||||
{
|
||||
body: body.join(', '),
|
||||
heading: body.join(', '),
|
||||
mono: mono.join(', '),
|
||||
},
|
||||
fontWeights,
|
||||
];
|
||||
}
|
||||
|
||||
function importColors(userColors: IConfigTheme['colors']): Theme.Colors {
|
||||
const generatedColors = generatePalette(userColors);
|
||||
|
||||
return {
|
||||
...generatedColors,
|
||||
@@ -177,9 +57,20 @@ export function makeTheme(
|
||||
userTheme: IConfigTheme,
|
||||
defaultColorMode: 'dark' | 'light' | null,
|
||||
): Theme.Full {
|
||||
const [fonts, fontWeights] = importFonts(userTheme.fonts);
|
||||
const fonts = importFonts(userTheme.fonts);
|
||||
const colors = importColors(userTheme.colors);
|
||||
const config = {} as Theme.Full['config'];
|
||||
const fontWeights = {
|
||||
hairline: 300,
|
||||
thin: 300,
|
||||
light: 300,
|
||||
normal: 400,
|
||||
medium: 400,
|
||||
semibold: 700,
|
||||
bold: 700,
|
||||
extrabold: 700,
|
||||
black: 700,
|
||||
} as ChakraTheme['fontWeights'];
|
||||
|
||||
switch (defaultColorMode) {
|
||||
case null:
|
||||
|
3034
hyperglass/ui/yarn.lock
vendored
3034
hyperglass/ui/yarn.lock
vendored
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user