This is an automated email from the ASF dual-hosted git repository. young pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/apisix-dashboard.git
The following commit(s) were added to refs/heads/master by this push: new a8c4e73cb fix: toc box rendering when form items changed (#3063) a8c4e73cb is described below commit a8c4e73cba86a92451cdac011c736cad46ce1315 Author: YYYoung <isk...@outlook.com> AuthorDate: Mon May 19 09:22:19 2025 +0800 fix: toc box rendering when form items changed (#3063) --- src/components/form-slice/FormSection/index.tsx | 153 +++++++++++++++--------- 1 file changed, 95 insertions(+), 58 deletions(-) diff --git a/src/components/form-slice/FormSection/index.tsx b/src/components/form-slice/FormSection/index.tsx index ca6fa8bff..fba331b51 100644 --- a/src/components/form-slice/FormSection/index.tsx +++ b/src/components/form-slice/FormSection/index.tsx @@ -19,14 +19,17 @@ import { type FieldsetProps, Group, TableOfContents, + type TableOfContentsProps, } from '@mantine/core'; +import { useShallowEffect } from '@mantine/hooks'; +import clsx from 'clsx'; +import { debounce } from 'rambdax'; import { createContext, - type FC, type PropsWithChildren, type ReactNode, + useCallback, useContext, - useEffect, useMemo, useRef, } from 'react'; @@ -44,49 +47,109 @@ const tocSelector = 'form-section'; const tocValue = 'data-label'; const tocDepth = 'data-depth'; +const FormTOCCtx = createContext<{ + refreshTOC: () => void; +}>({ + refreshTOC: () => {}, +}); + export type FormSectionProps = Omit<FieldsetProps, 'form'> & { extra?: ReactNode; }; -export const FormSection: FC<FormSectionProps> = (props) => { - const { className, legend, extra, ...restProps } = props; +const LegendGroup = ({ + legend, + extra, +}: { + legend: ReactNode; + extra?: ReactNode; +}) => { + if (!legend && !extra) { + return null; + } + return ( + <Group> + {legend} + {extra} + </Group> + ); +}; + +export const FormSection = (props: FormSectionProps) => { + const { className, legend, extra, children, ...restProps } = props; const parentDepth = useContext(SectionDepthCtx); + const { refreshTOC } = useContext(FormTOCCtx); const depth = useMemo(() => parentDepth + 1, [parentDepth]); + const dataAttrs = useMemo( + () => ({ + [tocValue]: legend, + [tocDepth]: depth, + }), + [legend, depth] + ); + + // refresh TOC when children changes + useShallowEffect(refreshTOC, [children]); - const newClass = `${tocSelector} ${classes.root} ${className || ''}`; return ( <SectionDepthProvider value={depth}> <Fieldset - className={newClass} - {...((legend || extra) && { - legend: ( - <Group> - {legend} - {extra} - </Group> - ), - })} + className={clsx(tocSelector, classes.root, className)} + legend={<LegendGroup legend={legend} extra={extra} />} {...restProps} - {...{ - [tocValue]: legend, - [tocDepth]: depth, - }} - /> + {...dataAttrs} + > + {children} + </Fieldset> </SectionDepthProvider> ); }; -export type FormTOCBoxProps = PropsWithChildren & { - deps?: unknown[]; +const TOC = (props: Pick<TableOfContentsProps, 'reinitializeRef'>) => { + return ( + <TableOfContents + variant="light" + color="blue" + size="sm" + radius="sm" + style={{ + flexShrink: 0, + position: 'sticky', + top: APPSHELL_HEADER_HEIGHT + 20, + }} + w={200} + mt={10} + minDepthToOffset={0} + depthOffset={20} + scrollSpyOptions={{ + selector: `.${tocSelector}`, + getDepth: (el) => Number(el.getAttribute(tocDepth)), + getValue: (el) => el.getAttribute(tocValue) || '', + }} + getControlProps={({ data }) => ({ + onClick: () => { + return data.getNode().scrollIntoView({ + behavior: 'smooth', + block: 'end', + inline: 'end', + }); + }, + children: data.value, + })} + {...props} + /> + ); }; +export type FormTOCBoxProps = PropsWithChildren; + export const FormTOCBox = (props: FormTOCBoxProps) => { - const { children, deps } = props; + const { children } = props; const reinitializeRef = useRef(() => {}); - - useEffect(() => { - reinitializeRef.current(); - }, [deps]); + const refreshTOC = useCallback( + () => debounce(reinitializeRef.current, 200), + [] + ); return ( <Group @@ -96,38 +159,12 @@ export const FormTOCBox = (props: FormTOCBoxProps) => { gap={30} style={{ paddingInlineEnd: '10%', position: 'relative' }} > - <TableOfContents - reinitializeRef={reinitializeRef} - variant="light" - color="blue" - size="sm" - radius="sm" - style={{ - flexShrink: 0, - position: 'sticky', - top: APPSHELL_HEADER_HEIGHT + 20, - }} - w={200} - mt={10} - minDepthToOffset={0} - depthOffset={20} - scrollSpyOptions={{ - selector: `.${tocSelector}`, - getDepth: (el) => Number(el.getAttribute(tocDepth)), - getValue: (el) => el.getAttribute(tocValue) || '', - }} - getControlProps={({ data }) => ({ - onClick: () => { - return data.getNode().scrollIntoView({ - behavior: 'smooth', - block: 'end', - inline: 'end', - }); - }, - children: data.value, - })} - /> - <div style={{ width: '80%' }}>{children}</div> + <TOC reinitializeRef={reinitializeRef} /> + <div style={{ width: '80%' }}> + <FormTOCCtx.Provider value={{ refreshTOC }}> + {children} + </FormTOCCtx.Provider> + </div> </Group> ); };