Integrate expand/collapse buttons into the react-json-tree

This commit is contained in:
lucataglia 2023-05-19 17:45:35 +02:00
parent f0b8a64713
commit decde458b1
6 changed files with 142 additions and 11 deletions

View File

@ -46,6 +46,9 @@
},
"dependencies": {
"@babel/runtime": "^7.21.0",
"@fortawesome/fontawesome-svg-core": "^6.4.0",
"@fortawesome/free-solid-svg-icons": "^6.4.0",
"@fortawesome/react-fontawesome": "^0.2.0",
"@types/lodash": "^4.14.194",
"react-base16-styling": "^0.9.1"
},

View File

@ -119,13 +119,16 @@ export default function JSONNestedNode(props: Props) {
isCircular ? false : shouldExpandNodeInitially(keyPath, data, level)
);
useEffect(() => {
if (shouldExpandNode === undefined) {
return;
}
const defaultExpanded = shouldExpandNodeInitially(keyPath, data, level);
setExpanded(shouldExpandNode);
}, [shouldExpandNode]);
useEffect(() => {
switch(shouldExpandNode){
case 'expand': setExpanded(true); break;
case'collapse': setExpanded(false); break;
case'default': setExpanded(defaultExpanded); break;
default:
}
}, [defaultExpanded, shouldExpandNode]);
const handleClick = useCallback(() => {
if (expandable) setExpanded(!expanded);

View File

@ -0,0 +1,106 @@
import React, { ReactNode } from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faArrowDown, faArrowRight, faUndo } from '@fortawesome/free-solid-svg-icons';
import { Expandable } from '.';
interface Props {
expandable?: Expandable;
expandableDefaultValue?: 'expand' | 'collapse';
shouldExpandNode?: 'expand' | 'collapse' | 'default';
setShouldExpandNode: any;
}
interface ExpandButtonProps {
expandableDefaultValue?: 'expand' | 'collapse';
expandIcon?: ReactNode;
shouldExpandNode?: 'expand' | 'collapse' | 'default';
setShouldExpandNode: any;
}
interface CollapseButtonProps {
expandableDefaultValue?: 'expand' | 'collapse';
collapseIcon?: ReactNode;
shouldExpandNode?: 'expand' | 'collapse' | 'default';
setShouldExpandNode: any;
}
interface DefaultButtonProps {
defaultIcon?: ReactNode;
setShouldExpandNode: any;
}
function ExpandableButtons({
expandable,
expandableDefaultValue,
setShouldExpandNode,
shouldExpandNode
}: Props){
if(!expandable){
return <></>
}
return (
<div style={{position: 'absolute', display: 'flex', justifyContent:'center', alignItems: 'center', gap:'1rem', top: '1rem', right: '1rem', cursor: 'pointer'}}>
<DefaultButton
defaultIcon={expandable?.defaultIcon}
setShouldExpandNode={setShouldExpandNode}
/>
<ExpandButton
expandableDefaultValue={expandableDefaultValue}
expandIcon={expandable?.expandIcon}
setShouldExpandNode={setShouldExpandNode}
shouldExpandNode={shouldExpandNode}
/>
<CollapseButton
expandableDefaultValue={expandable?.defaultValue}
collapseIcon={expandable?.collapseIcon}
setShouldExpandNode={setShouldExpandNode}
shouldExpandNode={shouldExpandNode}
/>
</div>
)
}
function ExpandButton({ expandableDefaultValue, expandIcon, shouldExpandNode, setShouldExpandNode }: ExpandButtonProps) {
const onExpand = () => setShouldExpandNode('expand');
if (shouldExpandNode === 'collapse' || (shouldExpandNode === 'default' && expandableDefaultValue === 'collapse')) {
return (
<div role="presentation" onClick={onExpand}>
{expandIcon || <FontAwesomeIcon icon={faArrowRight} />}
</div>
);
}
return <></>;
}
function CollapseButton({ expandableDefaultValue, collapseIcon, shouldExpandNode, setShouldExpandNode }: CollapseButtonProps) {
const onCollapse = () => setShouldExpandNode('collapse');
if (shouldExpandNode === 'expand' ||(shouldExpandNode === 'default' && expandableDefaultValue === 'expand')) {
return (
<div role="presentation" onClick={onCollapse}>
{collapseIcon || <FontAwesomeIcon icon={faArrowDown} />}
</div>
);
}
return <></>;
}
function DefaultButton({defaultIcon, setShouldExpandNode }:DefaultButtonProps) {
const onDefaultCollapse = () => setShouldExpandNode('default');
return (
<div role="presentation" onClick={onDefaultCollapse}>
{defaultIcon || <FontAwesomeIcon icon={faUndo} />}
</div>
);
return <></>;
}
export default ExpandableButtons

View File

@ -3,11 +3,12 @@
// Dave Vedder <veddermatic@gmail.com> http://www.eskimospy.com/
// port by Daniele Zannotti http://www.github.com/dzannotti <dzannotti@me.com>
import React, { useMemo } from 'react';
import React, { ReactNode, useMemo, useState } from 'react';
import JSONNode from './JSONNode';
import createStylingFromTheme from './createStylingFromTheme';
import { invertTheme } from 'react-base16-styling';
import type { StylingValue, Theme } from 'react-base16-styling';
import ExpandableButtons from './expandableButtons'
import type {
CommonExternalProps,
GetItemString,
@ -20,6 +21,14 @@ interface Props extends Partial<CommonExternalProps> {
data: unknown;
theme?: Theme;
invertTheme?: boolean;
expandable?: Expandable;
}
interface Expandable {
defaultValue?: 'expand' | 'collapse';
expandIcon?: ReactNode;
collapseIcon?: ReactNode;
defaultIcon?: ReactNode;
}
const identity = (value: any) => value;
@ -41,7 +50,7 @@ export function JSONTree({
labelRenderer = defaultLabelRenderer,
valueRenderer = identity,
shouldExpandNodeInitially = expandRootNode,
shouldExpandNode,
expandable,
hideRoot = false,
getItemString = defaultItemString,
postprocessValue = identity,
@ -49,6 +58,9 @@ export function JSONTree({
collectionLimit = 50,
sortObjectKeys = false,
}: Props) {
const expandableDefaultValue = expandable?.defaultValue || 'expand'
const [shouldExpandNode, setShouldExpandNode] = useState(expandableDefaultValue);
const styling = useMemo(
() =>
createStylingFromTheme(shouldInvertTheme ? invertTheme(theme) : theme),
@ -72,6 +84,13 @@ export function JSONTree({
collectionLimit={collectionLimit}
sortObjectKeys={sortObjectKeys}
/>
<ExpandableButtons
expandable={expandable}
expandableDefaultValue={expandableDefaultValue}
shouldExpandNode={shouldExpandNode}
setShouldExpandNode={setShouldExpandNode}
/>
</ul>
);
}
@ -89,4 +108,4 @@ export type {
Styling,
CommonExternalProps,
} from './types';
export type { StylingValue };
export type { Expandable, StylingValue };

View File

@ -47,7 +47,6 @@ export interface CommonExternalProps {
labelRenderer: LabelRenderer;
valueRenderer: ValueRenderer;
shouldExpandNodeInitially: ShouldExpandNodeInitially;
shouldExpandNode: boolean | undefined;
hideRoot: boolean;
getItemString: GetItemString;
postprocessValue: PostprocessValue;
@ -61,4 +60,5 @@ export interface CommonInternalProps extends CommonExternalProps {
circularCache?: CircularCache;
level?: number;
isCircular?: boolean;
shouldExpandNode?: 'expand' | 'collapse' | 'default';
}

View File

@ -17,6 +17,6 @@ describe('JSONTree', () => {
const result = render(<JSONTree data={BASIC_DATA} />);
expect(result.type).toBe('ul');
expect(result.props.children.type.name).toBe(JSONNode.name);
expect(result.props.children[0].type.name).toBe(JSONNode.name);
});
});