diff --git a/components/pages/example/Knob/Knob.styles.ts b/components/pages/example/KnobsTable/Knob/Knob.styles.ts similarity index 100% rename from components/pages/example/Knob/Knob.styles.ts rename to components/pages/example/KnobsTable/Knob/Knob.styles.ts diff --git a/components/pages/example/Knob/Knob.tsx b/components/pages/example/KnobsTable/Knob/Knob.tsx similarity index 100% rename from components/pages/example/Knob/Knob.tsx rename to components/pages/example/KnobsTable/Knob/Knob.tsx diff --git a/components/pages/example/Knob/RawKnob.tsx b/components/pages/example/KnobsTable/Knob/RawKnob.tsx similarity index 100% rename from components/pages/example/Knob/RawKnob.tsx rename to components/pages/example/KnobsTable/Knob/RawKnob.tsx diff --git a/components/pages/example/Knob/types.ts b/components/pages/example/KnobsTable/Knob/types.ts similarity index 83% rename from components/pages/example/Knob/types.ts rename to components/pages/example/KnobsTable/Knob/types.ts index 31e82196..9a4861d8 100644 --- a/components/pages/example/Knob/types.ts +++ b/components/pages/example/KnobsTable/Knob/types.ts @@ -1,6 +1,6 @@ import { HTMLElementProps } from '@leafygreen-ui/lib'; -import { KnobOptionType, TypeString } from '../types'; +import { KnobOptionType, TypeString } from '../../types'; export interface KnobProps extends HTMLElementProps<'input'> { propName: string; diff --git a/components/pages/example/KnobRow/KnobRow.tsx b/components/pages/example/KnobsTable/KnobRow.tsx similarity index 83% rename from components/pages/example/KnobRow/KnobRow.tsx rename to components/pages/example/KnobsTable/KnobRow.tsx index 37523273..9a63f8f4 100644 --- a/components/pages/example/KnobRow/KnobRow.tsx +++ b/components/pages/example/KnobsTable/KnobRow.tsx @@ -1,4 +1,5 @@ import { kebabCase } from 'lodash'; +import { isRequired } from 'utils/tsdoc.utils'; import { css } from '@leafygreen-ui/emotion'; import { useDarkMode } from '@leafygreen-ui/leafygreen-provider'; @@ -6,11 +7,12 @@ import { HTMLElementProps } from '@leafygreen-ui/lib'; import { palette } from '@leafygreen-ui/palette'; import { spacing } from '@leafygreen-ui/tokens'; import Tooltip from '@leafygreen-ui/tooltip'; -import { Body } from '@leafygreen-ui/typography'; +import { Body, Disclaimer } from '@leafygreen-ui/typography'; -import { Knob } from '../Knob/Knob'; import { KnobType } from '../types'; +import { Knob } from './Knob/Knob'; + const knobRowWrapperStyle = (darkMode: boolean) => css` display: flex; width: 100%; @@ -31,6 +33,13 @@ const knobControlStyle = css` justify-content: end; `; +const requiredFlagStyle = css` + display: inline; + padding-left: 1ch; + color: ${palette.red.base}; + text-transform: uppercase; +`; + interface KnobRowProps extends HTMLElementProps<'div'> { knob: KnobType; knobValue?: any; @@ -66,6 +75,9 @@ export const KnobRow = ({ knob, knobValue, setKnobValue }: KnobRowProps) => { id={`${kebabCase()}-knob-${name}`} > {name} + {isRequired(knob) && ( + (required) + )} {args?.disabled && args?.description ? ( diff --git a/components/pages/example/KnobsTable/KnobsTable.styles.ts b/components/pages/example/KnobsTable/KnobsTable.styles.ts new file mode 100644 index 00000000..fb21382b --- /dev/null +++ b/components/pages/example/KnobsTable/KnobsTable.styles.ts @@ -0,0 +1,10 @@ +import { css } from '@leafygreen-ui/emotion'; + +export const exampleCodeButtonRowStyle = css` + text-align: right; + padding: 16px 24px; +`; + +export const exampleCodeButtonStyle = css` + white-space: nowrap; +`; diff --git a/components/pages/example/KnobsTable/KnobsTable.tsx b/components/pages/example/KnobsTable/KnobsTable.tsx new file mode 100644 index 00000000..b07dbec2 --- /dev/null +++ b/components/pages/example/KnobsTable/KnobsTable.tsx @@ -0,0 +1,61 @@ +import { MouseEventHandler } from 'react'; + +import Button from '@leafygreen-ui/button'; +import Icon from '@leafygreen-ui/icon'; + +import { + LiveExampleContext, + LiveExampleStateReturnValue, +} from '../useLiveExampleState'; + +import { KnobRow } from './KnobRow'; +import { + exampleCodeButtonRowStyle, + exampleCodeButtonStyle, +} from './KnobsTable.styles'; + +interface KnobsTableProps { + showCode: boolean; + codeExampleEnabled: boolean; + handleShowCodeClick: MouseEventHandler; + knobsArray: Required['knobsArray']; + knobValues: Required['knobValues']; + updateKnobValue: LiveExampleStateReturnValue['updateKnobValue']; +} + +export const KnobsTable = ({ + showCode, + codeExampleEnabled, + handleShowCodeClick, + knobsArray, + knobValues, + updateKnobValue, +}: KnobsTableProps) => { + return ( +
+ {codeExampleEnabled && ( +
+ +
+ )} + {knobsArray.map(knob => ( + + ))} +
+ ); +}; diff --git a/components/pages/example/LiveExample.styles.ts b/components/pages/example/LiveExample.styles.ts index 359870e5..d188b6c2 100644 --- a/components/pages/example/LiveExample.styles.ts +++ b/components/pages/example/LiveExample.styles.ts @@ -85,12 +85,3 @@ export const codeStyle = css` height: 100%; overflow: auto; `; - -export const exampleCodeButtonRowStyle = css` - text-align: right; - padding: 16px 24px; -`; - -export const exampleCodeButtonStyle = css` - white-space: nowrap; -`; diff --git a/components/pages/example/LiveExample.tsx b/components/pages/example/LiveExample.tsx index a7554466..30b3e64a 100644 --- a/components/pages/example/LiveExample.tsx +++ b/components/pages/example/LiveExample.tsx @@ -1,7 +1,6 @@ import { useEffect, useRef, useState } from 'react'; import { CustomComponentDoc } from 'utils/tsdoc.utils'; -import Button from '@leafygreen-ui/button'; import Card from '@leafygreen-ui/card'; import { css, cx } from '@leafygreen-ui/emotion'; import { usePrevious } from '@leafygreen-ui/hooks'; @@ -9,16 +8,11 @@ import LeafyGreenProvider, { useDarkMode, } from '@leafygreen-ui/leafygreen-provider'; -import { KnobRow } from './KnobRow/KnobRow'; -import { - assertCompleteContext, - isStateReady, -} from './useLiveExampleState/utils'; +import { KnobsTable } from './KnobsTable/KnobsTable'; +import { isStateReady } from './useLiveExampleState/utils'; import { CodeExample } from './CodeExample'; import { blockContainerStyle, - exampleCodeButtonRowStyle, - exampleCodeButtonStyle, liveExampleWrapperStyle, storyContainerStyle, } from './LiveExample.styles'; @@ -28,8 +22,8 @@ import { LiveExampleLoading, LiveExampleNotFound, } from './LiveExampleStateComponents'; -import { LiveExampleContext, useLiveExampleState } from './useLiveExampleState'; -import { getStoryCode } from './utils'; +import { useLiveExampleState } from './useLiveExampleState'; +import { useStoryCode } from './useStoryCode'; // Use standard block flow for these packages const useBlockWrapperFor = [ @@ -50,7 +44,6 @@ export const LiveExample = ({ }) => { const prevComponentName = usePrevious(componentName); const [showCode, setShowCode] = useState(false); - const [storyCode, setCode] = useState('No code found'); const storyContainerRef = useRef(null); const storyWrapperRef = useRef(null); @@ -72,23 +65,11 @@ export const LiveExample = ({ } }, [componentName, prevComponentName, tsDoc, resetContext, setErrorState]); - /** Re-generates story example code from context */ - const regenerateStoryCode = (context: Partial) => { - const code = assertCompleteContext(context) - ? getStoryCode(context) ?? 'No code found' - : 'No code found'; - setCode(code); - }; - - /** re-generate example code when the context changes */ - useEffect(() => { - regenerateStoryCode(context); - }, [context]); + const storyCode = useStoryCode(context, showCode); /** Triggered on button click */ - const handleShowCodeClick = () => { + const toggleShowCode = () => { setShowCode(sc => !sc); - regenerateStoryCode(context); }; const storyWrapperStyle = context.meta?.parameters?.wrapperStyle; @@ -104,6 +85,8 @@ export const LiveExample = ({ // should match the total height of the story container const exampleCodeHeight = storyContainerHeight + 48; + const codeExampleEnabled = !disableCodeExampleFor.includes(componentName); + return ( )} - {!disableCodeExampleFor.includes(componentName) && ( + {codeExampleEnabled && ( )} -
- {!disableCodeExampleFor.includes(componentName) && ( -
- -
- )} - {isStateReady(context) && - context.knobsArray.map(knob => ( - - ))} -
+ {isStateReady(context) && ( + + )}
); diff --git a/components/pages/example/useLiveExampleState/LiveExampleState.types.ts b/components/pages/example/useLiveExampleState/LiveExampleState.types.ts index 61a15fac..cb070ce8 100644 --- a/components/pages/example/useLiveExampleState/LiveExampleState.types.ts +++ b/components/pages/example/useLiveExampleState/LiveExampleState.types.ts @@ -19,7 +19,7 @@ export interface LiveExampleContext { componentName?: string; tsDoc?: Array | null; meta?: Meta; - StoryFn?: ComponentStoryFn; + StoryFn?: ComponentStoryFn & React.FunctionComponent; knobValues?: { [arg: string]: any }; knobsArray?: Array; errorMessage?: string; @@ -51,3 +51,11 @@ export type LiveExampleAction = type: LiveExampleActionType.NOT_FOUND; componentName: string; }; + +export interface LiveExampleStateReturnValue { + context: LiveExampleContext; + updateKnobValue: (prop: string, val: any) => void; + resetContext: (name: string, tsDoc: Array) => void; + setErrorState: (msg: string) => void; + isState: (state: LiveExampleState) => boolean; +} diff --git a/components/pages/example/useLiveExampleState/index.ts b/components/pages/example/useLiveExampleState/index.ts index c4726a71..4ca0a67c 100644 --- a/components/pages/example/useLiveExampleState/index.ts +++ b/components/pages/example/useLiveExampleState/index.ts @@ -1,2 +1,5 @@ -export type { LiveExampleContext } from './LiveExampleState.types'; +export type { + LiveExampleContext, + LiveExampleStateReturnValue, +} from './LiveExampleState.types'; export { useLiveExampleState } from './useLiveExampleState'; diff --git a/components/pages/example/useLiveExampleState/useLiveExampleState.ts b/components/pages/example/useLiveExampleState/useLiveExampleState.ts index 7b88b21b..6152ccc8 100644 --- a/components/pages/example/useLiveExampleState/useLiveExampleState.ts +++ b/components/pages/example/useLiveExampleState/useLiveExampleState.ts @@ -1,5 +1,6 @@ import { useReducer } from 'react'; import { kebabCase, merge } from 'lodash'; +import pascalcase from 'pascalcase'; import { getComponentStories, ModuleType } from 'utils/getComponentStories'; import { CustomComponentDoc } from 'utils/tsdoc.utils'; @@ -14,6 +15,7 @@ import { import { LiveExampleActionType, LiveExampleState, + LiveExampleStateReturnValue, } from './LiveExampleState.types'; import { liveExampleStateReducer } from './LiveExampleStateReducer'; import { assertContext, defaultLiveExampleContext } from './utils'; @@ -21,7 +23,7 @@ import { assertContext, defaultLiveExampleContext } from './utils'; export function useLiveExampleState( componentName: string, tsDoc?: Array | null, -) { +): LiveExampleStateReturnValue { const initialState = merge( { componentName, tsDoc }, defaultLiveExampleContext, @@ -51,6 +53,7 @@ export function useLiveExampleState( }); } + /** Log an error */ function setErrorState(message: string) { dispatch({ type: LiveExampleActionType.ERROR, @@ -70,6 +73,7 @@ export function useLiveExampleState( function parse(module: ModuleType) { const { default: meta, ...stories } = module; const StoryFn = getDefaultStoryFn(meta, stories); + StoryFn.displayName = pascalcase(componentName) + 'Story'; if (assertContext(context, ['state', 'componentName', 'tsDoc'])) { const knobsArray = getKnobsArray({ diff --git a/components/pages/example/useStoryCode.ts b/components/pages/example/useStoryCode.ts new file mode 100644 index 00000000..1dc348be --- /dev/null +++ b/components/pages/example/useStoryCode.ts @@ -0,0 +1,23 @@ +import { useEffect, useState } from 'react'; + +import { isStateReady } from './useLiveExampleState/utils'; +import { getStoryCode } from './utils/getStoryCode'; +import { LiveExampleContext } from './useLiveExampleState'; + +const emptyStateCode = 'No code found'; + +export const useStoryCode = ( + context: LiveExampleContext, + showCode: boolean, +) => { + const [storyCode, setCode] = useState(emptyStateCode); + + useEffect(() => { + if (showCode && isStateReady(context)) { + const code = getStoryCode(context) ?? emptyStateCode; + setCode(code); + } + }, [context, showCode]); + + return storyCode; +}; diff --git a/components/pages/example/utils/getStoryCode.ts b/components/pages/example/utils/getStoryCode.ts new file mode 100644 index 00000000..d317b517 --- /dev/null +++ b/components/pages/example/utils/getStoryCode.ts @@ -0,0 +1,98 @@ +import React, { FunctionComponentElement, ReactElement } from 'react'; +import reactElementToJSXString from 'react-element-to-jsx-string'; +import { isElement } from 'react-is'; +import prepass from 'react-ssr-prepass'; // lets us traverse the react tree +import pascalcase from 'pascalcase'; +import { + getDefaultValueValue, + getPropsArrayForComponentName, +} from 'utils/tsdoc.utils'; + +import { LiveExampleContext } from '../useLiveExampleState'; +import { assertCompleteContext } from '../useLiveExampleState/utils'; + +import { ignoreProps } from '.'; + +/** + * Returns example code for the given component data. + * + * Does not generate prop code for props that have the same value as the + * documented default (in TSDoc). + */ +export function getStoryCode(context: LiveExampleContext): string | undefined { + // Some components have highly complex props, which crashes the browser when attempting to parse them + const ignoreAllPropsForComponents = ['table']; + + if (assertCompleteContext(context)) { + const { componentName, StoryFn, tsDoc, knobValues } = context; + + const renderedStory: FunctionComponentElement = React.createElement( + StoryFn, + { ...knobValues }, + ); + + const componentRoot = getComponentRoot(renderedStory, componentName); + + if (componentRoot) { + const TSPropsArray = getPropsArrayForComponentName(componentName, tsDoc); + const storyCode = reactElementToJSXString(componentRoot, { + showFunctions: true, + showDefaultProps: false, + useBooleanShorthandSyntax: false, + useFragmentShortSyntax: true, + filterProps: (value, name) => { + const tsProp = TSPropsArray.find(p => p.name === name); + const tsDefault = tsProp ? getDefaultValueValue(tsProp) : null; + + const excludeProp = + ignoreAllPropsForComponents.includes(componentName) || + ignoreProps.includes(name) || + value === tsDefault; + + // Filter out explicitly ignored props + // and props that have the same value as the documented default + return !excludeProp; + }, + }); + + return storyCode; + } + } + + /** Returns the component root we want to generate source code for */ + function getComponentRoot( + renderedStory: FunctionComponentElement, + componentName: string, + ) { + /** Treat these packages differently. We use the entire story code, not just the component JSX */ + const packageNameDoesNotMatchComponent = ['typography']; + + let isRootSet = false; + let componentRoot: ReactElement = renderedStory; + + // @ts-expect-error - prepass callback args are incorrectly typed (see FormidableLabs/react-ssr-prepass#86). Need to explicitly re-type them here + prepass(renderedStory, (element: ReactElement) => { + if (!isRootSet && isFunctionComponentElement(element)) { + if (packageNameDoesNotMatchComponent.includes(componentName)) { + // We take the first element that is not the Story element + if (!element.type.displayName?.includes('Story')) { + componentRoot = element; + isRootSet = true; + } + } else if (element.type.displayName === pascalcase(componentName)) { + componentRoot = element; + isRootSet = true; + } + } + }); + + return componentRoot; + } +} + +/** Returns whether a React Element is a Component vs just an intrinsic element */ +function isFunctionComponentElement( + node: React.ReactElement, +): node is React.FunctionComponentElement> { + return isElement(node) && typeof node.type === 'function'; +} diff --git a/components/pages/example/utils.ts b/components/pages/example/utils/index.ts similarity index 80% rename from components/pages/example/utils.ts rename to components/pages/example/utils/index.ts index 13b73b5b..573dac21 100644 --- a/components/pages/example/utils.ts +++ b/components/pages/example/utils/index.ts @@ -1,6 +1,4 @@ -import React, { ReactNode } from 'react'; import { PropItem } from 'react-docgen-typescript'; -import reactElementToJSXString from 'react-element-to-jsx-string'; import { InputType } from '@storybook/csf'; import { ComponentStoryFn, Meta } from '@storybook/react'; import { @@ -12,22 +10,26 @@ import { pickBy, snakeCase, } from 'lodash'; -import pascalcase from 'pascalcase'; import { CustomComponentDoc, - findComponentDoc, - getComponentPropsArray as getTSDocPropsArray, getDefaultValueValue, + getPropsArrayForComponentName, + sortPropItems, } from 'utils/tsdoc.utils'; -import { assertCompleteContext } from './useLiveExampleState/utils'; -import { KnobOptionType, KnobType, MetadataSources, TypeString } from './types'; -import { LiveExampleContext } from './useLiveExampleState'; +import { + KnobOptionType, + KnobType, + MetadataSources, + TypeString, +} from '../types'; +import { LiveExampleContext } from '../useLiveExampleState'; /** * A list of Prop names that should not appear in Knobs */ export const ignoreProps = [ + 'key', 'className', 'tooltipClassName', 'contentClassName', @@ -378,75 +380,12 @@ export function getKnobDescription({ ); } -/** - * Returns example code for the given component data - */ -export function getStoryCode(context: LiveExampleContext): string | undefined { - /** Skip generation, and just use the story source code for these packages */ - const useStorySourceForComponents = ['typography']; - - if (assertCompleteContext(context)) { - const { componentName, meta, StoryFn, knobValues } = context; - - /** - * If this is the Typography component, - * we use the original story code, - * otherwise we convert the component to JSX - */ - if (useStorySourceForComponents.includes(componentName)) { - return getStorySourceCode(meta); - } else { - const renderedStory = React.createElement(StoryFn, { ...knobValues }); - return getStoryJSX(renderedStory, componentName); - } - } - - /** `getStoryCode` utility that returns a JSX string */ - function getStoryJSX(element: ReactNode, displayName: string) { - if (element) { - return reactElementToJSXString(element, { - displayName: (child: ReactNode) => - // @ts-expect-error - correct type for `child` is too verbose - child?.type?.displayName ?? pascalcase(displayName), - showFunctions: true, - showDefaultProps: true, - useBooleanShorthandSyntax: false, - useFragmentShortSyntax: true, - }); - } - } - - /** Extracts the story code from the meta `storySource` */ - function getStorySourceCode(meta?: Meta) { - if (meta && meta.parameters) { - const { - parameters: { default: defaultStoryName, storySource }, - } = meta; - - if (storySource) { - const locationsMap = defaultStoryName - ? storySource.locationsMap[defaultStoryName] - : Object.values(storySource?.locationsMap)[0]; - const lines = (storySource.source as string).match(/^.*$/gm); - - const storyCode = lines - ?.slice( - locationsMap?.startLoc?.line - 1, - locationsMap?.endLoc?.line - 1, - ) - .join('\n'); - return storyCode; - } - } - } -} - /** * Gets the default story from the meta */ export function getDefaultStoryFn( - meta: Meta, - stories: { [key: string]: ComponentStoryFn }, + meta: Required['meta'], + stories: { [key: string]: Required['StoryFn'] }, ) { const defaultStoryName = meta.parameters?.default ?? Object.keys(stories)[0]; return defaultStoryName @@ -468,8 +407,9 @@ export function getKnobsArray({ StoryFn: ComponentStoryFn; tsDoc: Array | null; }) { - const TSPropsArray: Array = getTSDocPropsArray( - findComponentDoc(componentName, tsDoc), + const TSPropsArray: Array = getPropsArrayForComponentName( + componentName, + tsDoc, ) // Filter out component props we don't want knobs for. // i.e. `@ignore` tags, excluded in SB.parameters.controls @@ -487,7 +427,7 @@ export function getKnobsArray({ // Convert SB InputType to KnobType .map(mapSBArgTypeToKnobType); - const knobsArray = [...TSPropsArray, ...SBArgsArray]; + const knobsArray = [...TSPropsArray, ...SBArgsArray].sort(sortPropItems); return knobsArray; } diff --git a/next.config.js b/next.config.js index 8f379a37..1940b296 100644 --- a/next.config.js +++ b/next.config.js @@ -38,18 +38,6 @@ const nextConfig = { type: 'javascript/auto', }); - config.module.rules.push({ - test: /\.+(stories|story)\.tsx?$/, - use: [ - { - loader: require.resolve('@storybook/source-loader'), - options: { parser: 'typescript' }, - }, - ], - include: isPathInLeafygreen, - enforce: 'pre', - }); - // Allow to dynamically import the svg files config.module.rules.push({ test: /\.svg$/, diff --git a/package.json b/package.json index 88e17d70..ac57dab4 100644 --- a/package.json +++ b/package.json @@ -98,13 +98,16 @@ "eslint-plugin-react-hooks": "^4.6.0", "gray-matter": "4.0.3", "next": "^12.3.1", + "pascalcase": "^2.0.0", "polished": "^4.1.3", "prettier": "^2.7.1", "react": "^17.0.2", "react-docgen-typescript": "^2.2.2", "react-dom": "^17.0.2", "react-element-to-jsx-string": "^15.0.0", + "react-is": "^18.2.0", "react-markdown": "^8.0.3", + "react-ssr-prepass": "^1.5.0", "rehype-autolink-headings": "^6.1.1", "rehype-slug": "^5.0.1", "remark": "^14.0.2", diff --git a/utils/tsdoc.utils.ts b/utils/tsdoc.utils.ts index 138efb3a..37bc08fe 100644 --- a/utils/tsdoc.utils.ts +++ b/utils/tsdoc.utils.ts @@ -41,11 +41,23 @@ export const isPropItem = (obj: any): obj is PropItem => { ); }; +/** + * Whether a given prop item is required + */ export function isRequired(prop: PropItem): boolean { // @ts-expect-error return prop.required || !isUndefined(prop.tags?.required); } +/** + * Sorts prop items with required props first, then the rest alphabetically + */ +export function sortPropItems(a: PropItem, z: PropItem): number { + if (isRequired(a) && !isRequired(z)) return -1; + if (isRequired(z)) return 1; + return a.name.localeCompare(z.name); +} + /** * Finds the appropriate ComponentDoc given a componentName. * Useful when there are multiple docs for one component @@ -77,11 +89,14 @@ export function getComponentPropsArray( return Object.values(omitBy(props, isInheritableGroup)) .flatMap(Object.values) - .sort((a, z) => { - if (isRequired(a) && !isRequired(z)) return -1; - if (isRequired(z)) return 1; - return a.name.localeCompare(z.name); - }); + .sort(sortPropItems); +} + +export function getPropsArrayForComponentName( + componentName: string, + tsDoc: Array | null, +) { + return getComponentPropsArray(findComponentDoc(componentName, tsDoc)); } export const getInheritedProps = (props: PropCategories): Array => { diff --git a/yarn.lock b/yarn.lock index 94dc51ad..a1329d9d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4686,7 +4686,7 @@ camelcase@^5.3.1: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -camelcase@^6.2.0: +camelcase@^6.2.0, camelcase@^6.2.1: version "6.3.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== @@ -9864,6 +9864,13 @@ pascalcase@^0.1.1: resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" integrity sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw== +pascalcase@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-2.0.0.tgz#13515fcbfa76eddff9282827f59f7868e3cc9250" + integrity sha512-DHpENy5Qm/FaX+x3iBLoMLG/XHNCTgL+yErm1TwuVaj6u4fiOSkYkf60vGtITk7hrKHOO4uCl9vRrD4hqjNKjg== + dependencies: + camelcase "^6.2.1" + path-browserify@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" @@ -10433,7 +10440,7 @@ react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.6: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -react-is@^18.0.0: +react-is@^18.0.0, react-is@^18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== @@ -10471,6 +10478,11 @@ react-refresh@^0.11.0: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046" integrity sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A== +react-ssr-prepass@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/react-ssr-prepass/-/react-ssr-prepass-1.5.0.tgz#bc4ca7fcb52365e6aea11cc254a3d1bdcbd030c5" + integrity sha512-yFNHrlVEReVYKsLI5lF05tZoHveA5pGzjFbFJY/3pOqqjGOmMmqx83N4hIjN2n6E1AOa+eQEUxs3CgRnPmT0RQ== + react-transition-group@^4.4.1, react-transition-group@^4.4.5: version "4.4.5" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1"