diff --git a/hyperglass/ui/components/buttons/colorMode.tsx b/hyperglass/ui/components/Footer/colorMode.tsx similarity index 100% rename from hyperglass/ui/components/buttons/colorMode.tsx rename to hyperglass/ui/components/Footer/colorMode.tsx diff --git a/hyperglass/ui/components/Footer/footer.tsx b/hyperglass/ui/components/Footer/footer.tsx index e9529dd..950af99 100644 --- a/hyperglass/ui/components/Footer/footer.tsx +++ b/hyperglass/ui/components/Footer/footer.tsx @@ -1,9 +1,10 @@ import dynamic from 'next/dynamic'; -import { Button, Flex, Link, HStack, useToken } from '@chakra-ui/react'; -import { If, ColorModeToggle } from '~/components'; +import { Button, Flex, Link, Icon, HStack, useToken } from '@chakra-ui/react'; +import { If } from '~/components'; import { useConfig, useMobile, useColorValue, useBreakpointValue } from '~/context'; import { useStrf } from '~/hooks'; import { FooterButton } from './button'; +import { ColorModeToggle } from './colorMode'; const CodeIcon = dynamic(() => import('@meronex/icons/fi').then(i => i.FiCode)); const ExtIcon = dynamic(() => import('@meronex/icons/go').then(i => i.GoLinkExternal)); @@ -52,7 +53,11 @@ export const Footer = () => { {!isMobile && } - } /> + } + /> diff --git a/hyperglass/ui/components/Footer/types.ts b/hyperglass/ui/components/Footer/types.ts index 4695ed7..ddd71bd 100644 --- a/hyperglass/ui/components/Footer/types.ts +++ b/hyperglass/ui/components/Footer/types.ts @@ -1,4 +1,4 @@ -import type { MenuListProps } from '@chakra-ui/react'; +import type { ButtonProps, MenuListProps } from '@chakra-ui/react'; type TFooterSide = 'left' | 'right'; @@ -9,3 +9,7 @@ export interface TFooterButton extends Omit { } export type TFooterItems = 'help' | 'credit' | 'terms'; + +export interface TColorModeToggle extends ButtonProps { + size?: string; +} diff --git a/hyperglass/ui/components/HyperglassForm.js b/hyperglass/ui/components/HyperglassForm.js deleted file mode 100644 index 7f568f0..0000000 --- a/hyperglass/ui/components/HyperglassForm.js +++ /dev/null @@ -1,251 +0,0 @@ -import * as React from 'react'; -import { forwardRef, useState, useEffect } from 'react'; -import { Box, Flex } from '@chakra-ui/core'; -import { useForm } from 'react-hook-form'; -import { intersectionWith, isEqual } from 'lodash'; -import * as yup from 'yup'; -import format from 'string-format'; -import { - FormField, - HelpModal, - QueryLocation, - QueryType, - QueryTarget, - CommunitySelect, - QueryVrf, - ResolvedTarget, - SubmitButton, -} from 'app/components'; -import { useConfig } from 'app/context'; - -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 FormRow = ({ children, ...props }) => ( - - {children} - -); - -export const HyperglassForm = 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: '' }, - }); - - 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 intersecting = intersectionWith(...allVrfs, isEqual); - setAvailVrfs(intersecting); - !intersecting.includes(queryVrf) && queryVrf !== 'default' && setQueryVrf('default'); - - 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 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 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 ( - - - - - - }> - - - - - {availVrfs.length > 1 && ( - - - - )} - - ) - }> - {queryType === 'bgp_community' && config.queries.bgp_community.mode === 'select' ? ( - - ) : ( - - )} - - - - - - - - - ); - }, -); diff --git a/hyperglass/ui/components/buttons/index.ts b/hyperglass/ui/components/buttons/index.ts deleted file mode 100644 index 460b183..0000000 --- a/hyperglass/ui/components/buttons/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from './colorMode'; -export * from './copy'; -export * from './path'; -export * from './requery'; -export * from './reset'; -export * from './submit'; diff --git a/hyperglass/ui/components/buttons/reset.tsx b/hyperglass/ui/components/buttons/reset.tsx deleted file mode 100644 index 6d197ef..0000000 --- a/hyperglass/ui/components/buttons/reset.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { forwardRef } from 'react'; -import dynamic from 'next/dynamic'; -import { Button, Icon } from '@chakra-ui/react'; -import { useLGState } from '~/hooks'; - -import type { ButtonProps } from '@chakra-ui/react'; - -const ChevronLeft = dynamic(() => - import('@meronex/icons/fi').then(i => i.FiChevronLeft), -); - -export const ResetButton = forwardRef((props, ref) => { - const { isSubmitting } = useLGState(); - return ( - - ); -}); diff --git a/hyperglass/ui/components/buttons/types.ts b/hyperglass/ui/components/buttons/types.ts deleted file mode 100644 index 50b12d9..0000000 --- a/hyperglass/ui/components/buttons/types.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { IconButtonProps, ButtonProps } from '@chakra-ui/react'; -import type { OnChangeArgs } from '~/types'; - -export interface TCopyButton extends ButtonProps { - copyValue: string; -} - -export interface TColorModeToggle extends ButtonProps { - size?: string; -} - -export interface TSubmitButton extends Omit { - handleChange(e: OnChangeArgs): void; -} - -export interface TRequeryButton extends ButtonProps { - requery(): void; -} - -export interface TRSubmitButton { - isOpen: boolean; - onClose(): void; - onChange(e: OnChangeArgs): void; - children: React.ReactNode; -} - -export interface TPathButton { - onOpen(): void; -} diff --git a/hyperglass/ui/components/header/context.ts b/hyperglass/ui/components/header/context.ts deleted file mode 100644 index 4f81c2b..0000000 --- a/hyperglass/ui/components/header/context.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { createContext, useContext } from 'react'; -import { createState, useState } from '@hookstate/core'; -import type { THeaderCtx, THeaderState } from './types'; - -const HeaderCtx = createContext({ - showSubtitle: true, - titleRef: {} as React.MutableRefObject, -}); - -export const HeaderProvider = HeaderCtx.Provider; -export const useHeaderCtx = (): THeaderCtx => useContext(HeaderCtx); - -const HeaderState = createState({ fontSize: '' }); -export const useHeader = () => useState(HeaderState); diff --git a/hyperglass/ui/components/header/header.tsx b/hyperglass/ui/components/header/header.tsx index 64b9792..1971b2a 100644 --- a/hyperglass/ui/components/header/header.tsx +++ b/hyperglass/ui/components/header/header.tsx @@ -1,9 +1,7 @@ import { useRef } from 'react'; import { Flex, ScaleFade } from '@chakra-ui/react'; -import { ColorModeToggle } from '~/components'; import { useColorValue, useBreakpointValue } from '~/context'; import { useBooleanValue, useLGState } from '~/hooks'; -import { HeaderProvider } from './context'; import { Title } from './title'; import type { THeader } from './types'; @@ -26,35 +24,28 @@ export const Header = (props: THeader) => { const justify = useBreakpointValue({ base: 'flex-start', lg: 'center' }); return ( - - - - - - </Flex> - </ScaleFade> - {/* <Flex pos="absolute" right={0} top={0} m={4}> - <ColorModeToggle /> - </Flex> */} - </Flex> - </HeaderProvider> + <Flex + px={4} + pt={6} + bg={bg} + minH={16} + zIndex={4} + as="header" + width="full" + flex="0 1 auto" + color="gray.500" + {...rest}> + <ScaleFade in initialScale={0.5} style={{ width: '100%' }}> + <Flex + height="100%" + ref={titleRef} + maxW={titleWidth} + // This is here for the logo + justifyContent={justify} + mx={{ base: 0, lg: 'auto' }}> + <Title /> + </Flex> + </ScaleFade> + </Flex> ); }; diff --git a/hyperglass/ui/components/header/title.tsx b/hyperglass/ui/components/header/title.tsx index 49ac1fe..9d84a7b 100644 --- a/hyperglass/ui/components/header/title.tsx +++ b/hyperglass/ui/components/header/title.tsx @@ -5,7 +5,6 @@ import { useConfig, useMobile } from '~/context'; import { useBooleanValue, useLGState } from '~/hooks'; import { Logo } from './logo'; import { TitleOnly } from './titleOnly'; -import { useHeaderCtx } from './context'; import { SubtitleOnly } from './subtitleOnly'; import type { StackProps } from '@chakra-ui/react'; @@ -22,13 +21,13 @@ const MWrapper = (props: StackProps) => <VStack spacing={1} alignItems="flex-sta * Title wrapper for desktop devices, breakpoints lg & xl. */ const DWrapper = (props: TDWrapper) => { - const { showSubtitle } = useHeaderCtx(); + const { isSubmitting } = useLGState(); return ( <AnimatedVStack spacing={1} initial="main" alignItems="center" - animate={showSubtitle ? 'main' : 'submitting'} + animate={isSubmitting.value ? 'submitting' : 'main'} transition={{ damping: 15, type: 'spring', stiffness: 100 }} variants={{ submitting: { scale: 0.5 }, main: { scale: 1 } }} {...props} diff --git a/hyperglass/ui/components/header/titleOnly.tsx b/hyperglass/ui/components/header/titleOnly.tsx index dda7d63..ace22f9 100644 --- a/hyperglass/ui/components/header/titleOnly.tsx +++ b/hyperglass/ui/components/header/titleOnly.tsx @@ -1,14 +1,13 @@ import { Heading } from '@chakra-ui/react'; import { useConfig } from '~/context'; -import { useBooleanValue } from '~/hooks'; -import { useHeaderCtx } from './context'; +import { useBooleanValue, useLGState } from '~/hooks'; import { useTitleSize } from './useTitleSize'; export const TitleOnly = () => { - const { showSubtitle } = useHeaderCtx(); const { web } = useConfig(); + const { isSubmitting } = useLGState(); - const margin = useBooleanValue(showSubtitle, 2, 0); + const margin = useBooleanValue(isSubmitting.value, 0, 2); const sizeSm = useTitleSize(web.text.title, '2xl', []); return ( diff --git a/hyperglass/ui/components/header/types.ts b/hyperglass/ui/components/header/types.ts index 67e3359..fb2b53c 100644 --- a/hyperglass/ui/components/header/types.ts +++ b/hyperglass/ui/components/header/types.ts @@ -25,7 +25,3 @@ export interface THeaderCtx { showSubtitle: boolean; titleRef: React.MutableRefObject<HTMLHeadingElement>; } - -export interface THeaderState { - fontSize: string; -} diff --git a/hyperglass/ui/components/index.ts b/hyperglass/ui/components/index.ts index d4146af..17216b1 100644 --- a/hyperglass/ui/components/index.ts +++ b/hyperglass/ui/components/index.ts @@ -1,4 +1,3 @@ -export * from './buttons'; export * from './card'; export * from './codeBlock'; export * from './countdown'; @@ -18,5 +17,6 @@ export * from './output'; export * from './path'; export * from './results'; export * from './select'; +export * from './submit'; export * from './table'; export * from './util'; diff --git a/hyperglass/ui/components/buttons/path.tsx b/hyperglass/ui/components/path/button.tsx similarity index 100% rename from hyperglass/ui/components/buttons/path.tsx rename to hyperglass/ui/components/path/button.tsx diff --git a/hyperglass/ui/components/path/chart.tsx b/hyperglass/ui/components/path/chart.tsx index 4b84ce8..eabc753 100644 --- a/hyperglass/ui/components/path/chart.tsx +++ b/hyperglass/ui/components/path/chart.tsx @@ -15,7 +15,7 @@ export const Chart = (props: TChart) => { const { data } = props; const { primary_asn, org_name } = useConfig(); - const dots = useColorToken('blackAlpha.500', 'whiteAlpha.400'); + const dots = useColorToken('colors', 'blackAlpha.500', 'whiteAlpha.400'); const flowProps = useBreakpointValue<Omit<ReactFlowProps, 'elements'>>({ base: { defaultPosition: [0, 300], defaultZoom: 0 }, diff --git a/hyperglass/ui/components/path/path.tsx b/hyperglass/ui/components/path/path.tsx index 7e4596c..6fd69bb 100644 --- a/hyperglass/ui/components/path/path.tsx +++ b/hyperglass/ui/components/path/path.tsx @@ -8,9 +8,9 @@ import { Skeleton, ModalCloseButton, } from '@chakra-ui/react'; -import { PathButton } from '~/components'; import { useColorValue } from '~/context'; import { useLGState } from '~/hooks'; +import { PathButton } from './button'; import { Chart } from './chart'; import type { TPath } from './types'; diff --git a/hyperglass/ui/components/path/types.ts b/hyperglass/ui/components/path/types.ts index 975614c..067b065 100644 --- a/hyperglass/ui/components/path/types.ts +++ b/hyperglass/ui/components/path/types.ts @@ -23,3 +23,7 @@ export interface BasePath { asn: string; name: string; } + +export interface TPathButton { + onOpen(): void; +} diff --git a/hyperglass/ui/components/buttons/copy.tsx b/hyperglass/ui/components/results/copyButton.tsx similarity index 100% rename from hyperglass/ui/components/buttons/copy.tsx rename to hyperglass/ui/components/results/copyButton.tsx diff --git a/hyperglass/ui/components/results/individual.tsx b/hyperglass/ui/components/results/individual.tsx index a001b0b..af54bc2 100644 --- a/hyperglass/ui/components/results/individual.tsx +++ b/hyperglass/ui/components/results/individual.tsx @@ -12,11 +12,13 @@ import { import { motion } from 'framer-motion'; import { BsLightningFill } from '@meronex/icons/bs'; import { startCase } from 'lodash'; -import { BGPTable, Countdown, CopyButton, RequeryButton, TextOutput, If, Path } from '~/components'; +import { BGPTable, Countdown, TextOutput, If, Path } from '~/components'; import { useColorValue, useConfig, useMobile } from '~/context'; import { useStrf, useLGQuery, useLGState, useTableToString } from '~/hooks'; import { isStructuredOutput, isStringOutput } from '~/types'; import { isStackError, isFetchError, isLGError } from './guards'; +import { RequeryButton } from './requeryButton'; +import { CopyButton } from './copyButton'; import { FormattedError } from './error'; import { ResultHeader } from './header'; diff --git a/hyperglass/ui/components/buttons/requery.tsx b/hyperglass/ui/components/results/requeryButton.tsx similarity index 93% rename from hyperglass/ui/components/buttons/requery.tsx rename to hyperglass/ui/components/results/requeryButton.tsx index 88bed26..3eb0c81 100644 --- a/hyperglass/ui/components/buttons/requery.tsx +++ b/hyperglass/ui/components/results/requeryButton.tsx @@ -18,7 +18,7 @@ export const RequeryButton = forwardRef<HTMLButtonElement, TRequeryButton>((prop size="sm" zIndex="1" variant="ghost" - onClick={requery} + onClick={requery as TRequeryButton['onClick']} colorScheme="secondary" {...rest}> <Icon as={Repeat} boxSize="16px" /> diff --git a/hyperglass/ui/components/results/types.ts b/hyperglass/ui/components/results/types.ts index ab9b547..74c9c31 100644 --- a/hyperglass/ui/components/results/types.ts +++ b/hyperglass/ui/components/results/types.ts @@ -1,4 +1,5 @@ -import type { FlexProps } from '@chakra-ui/react'; +import type { ButtonProps, FlexProps } from '@chakra-ui/react'; +import type { QueryResultBase } from 'react-query'; import type { TDevice, TQueryTypes } from '~/types'; export interface TResultHeader { @@ -31,3 +32,11 @@ export interface TResult { } export type TErrorLevels = 'success' | 'warning' | 'error'; + +export interface TCopyButton extends ButtonProps { + copyValue: string; +} + +export interface TRequeryButton extends ButtonProps { + requery: QueryResultBase<TQueryResponse>['refetch']; +} diff --git a/hyperglass/ui/components/submit/index.ts b/hyperglass/ui/components/submit/index.ts new file mode 100644 index 0000000..e9be2e3 --- /dev/null +++ b/hyperglass/ui/components/submit/index.ts @@ -0,0 +1 @@ +export * from './submit'; diff --git a/hyperglass/ui/components/buttons/submit.tsx b/hyperglass/ui/components/submit/submit.tsx similarity index 100% rename from hyperglass/ui/components/buttons/submit.tsx rename to hyperglass/ui/components/submit/submit.tsx diff --git a/hyperglass/ui/components/submit/types.ts b/hyperglass/ui/components/submit/types.ts new file mode 100644 index 0000000..91ca90b --- /dev/null +++ b/hyperglass/ui/components/submit/types.ts @@ -0,0 +1,13 @@ +import type { IconButtonProps } from '@chakra-ui/react'; +import type { OnChangeArgs } from '~/types'; + +export interface TSubmitButton extends Omit<IconButtonProps, 'aria-label'> { + handleChange(e: OnChangeArgs): void; +} + +export interface TRSubmitButton { + isOpen: boolean; + onClose(): void; + onChange(e: OnChangeArgs): void; + children: React.ReactNode; +} diff --git a/hyperglass/ui/context/GlobalState.ts b/hyperglass/ui/context/GlobalState.ts index 200be92..f89acf7 100644 --- a/hyperglass/ui/context/GlobalState.ts +++ b/hyperglass/ui/context/GlobalState.ts @@ -1,31 +1,6 @@ import { createState, useState } from '@hookstate/core'; import type { TGlobalState, TUseGlobalState } from './types'; -// const StateContext = createContext(null); - -// export const StateProvider = ({ children }) => { -// const [isSubmitting, setSubmitting] = useState(false); -// const [formData, setFormData] = useState({}); -// const [greetingAck, setGreetingAck] = useSessionStorage('hyperglass-greeting-ack', false); -// const resetForm = layoutRef => { -// layoutRef.current.scrollIntoView({ behavior: 'smooth', block: 'start' }); -// setSubmitting(false); -// setFormData({}); -// }; -// const value = useMemo(() => ({ -// isSubmitting, -// setSubmitting, -// formData, -// setFormData, -// greetingAck, -// setGreetingAck, -// resetForm, -// })); -// return <StateContext.Provider value={value}>{children}</StateContext.Provider>; -// }; - -// export const useHyperglassState = () => useContext(StateContext); - const defaultFormData = { query_location: [], query_target: '', diff --git a/hyperglass/ui/context/HyperglassProvider.tsx b/hyperglass/ui/context/HyperglassProvider.tsx index 6efbe13..99f3196 100644 --- a/hyperglass/ui/context/HyperglassProvider.tsx +++ b/hyperglass/ui/context/HyperglassProvider.tsx @@ -31,8 +31,11 @@ export const useTheme = (): ITheme => useChakraTheme(); export const useMobile = (): boolean => useBreakpointValue({ base: true, md: true, lg: false, xl: false }) ?? true; -export const useColorToken = (light: string, dark: string): string => - useColorModeValue(useToken('colors', light), useToken('colors', dark)); +export const useColorToken = <L extends string, D extends string>( + token: keyof ITheme, + light: L, + dark: D, +): L | D => useColorModeValue(useToken(token, light), useToken(token, dark)); export { useColorMode, diff --git a/hyperglass/ui/hooks/index.ts b/hyperglass/ui/hooks/index.ts index f5861ca..05b99d6 100644 --- a/hyperglass/ui/hooks/index.ts +++ b/hyperglass/ui/hooks/index.ts @@ -5,7 +5,6 @@ export * from './useGreeting'; export * from './useLGQuery'; export * from './useLGState'; export * from './useOpposingColor'; -export * from './useSessionStorage'; export * from './useStrf'; export * from './useTableToString'; export * from './useScaledText'; diff --git a/hyperglass/ui/hooks/useSessionStorage.js b/hyperglass/ui/hooks/useSessionStorage.js deleted file mode 100644 index e3b7344..0000000 --- a/hyperglass/ui/hooks/useSessionStorage.js +++ /dev/null @@ -1,42 +0,0 @@ -/* -react-use: useSessionStorage -https://github.com/streamich/react-use/blob/master/src/useSessionStorage.ts -*/ - -import { useEffect, useState } from 'react'; - -export const useSessionStorage = (key, initialValue, raw) => { - const isClient = typeof window === 'object'; - if (!isClient) { - return [initialValue, () => {}]; - } - - const [state, setState] = useState(() => { - try { - const sessionStorageValue = sessionStorage.getItem(key); - if (typeof sessionStorageValue !== 'string') { - sessionStorage.setItem(key, raw ? String(initialValue) : JSON.stringify(initialValue)); - return initialValue; - } else { - return raw ? sessionStorageValue : JSON.parse(sessionStorageValue || 'null'); - } - } catch { - // If user is in private mode or has storage restriction - // sessionStorage can throw. JSON.parse and JSON.stringify - // cat throw, too. - return initialValue; - } - }); - - useEffect(() => { - try { - const serializedState = raw ? String(state) : JSON.stringify(state); - sessionStorage.setItem(key, serializedState); - } catch { - // If user is in private mode or has storage restriction - // sessionStorage can throw. Also JSON.stringify can throw. - } - }); - - return [state, setState]; -}; diff --git a/hyperglass/ui/types/react-textfit.d.ts b/hyperglass/ui/types/react-textfit.d.ts deleted file mode 100644 index e712e2f..0000000 --- a/hyperglass/ui/types/react-textfit.d.ts +++ /dev/null @@ -1,29 +0,0 @@ -declare module 'react-textfit' { - type RenderFunction = (text: string) => React.ReactNode; - interface TextfitProps { - children: React.ReactNode | RenderFunction; - text?: string; - /** - * @default number 1 - */ - min?: number; - /** - * @default number 100 - */ - max?: number; - /** - * @default single|multi multi - */ - mode?: 'single' | 'multi'; - /** - * @default boolean true - */ - forceSingleModeWidth?: boolean; - /** - * @default number 50 - */ - throttle?: number; - onReady?: (mid: number) => void; - } - class Textfit extends React.Component<TextfitProps> {} -}