+
{nodeTypeIndicator}
);
@@ -137,9 +176,15 @@ export default function JSONNestedNode(props: Props) {
data,
itemType,
createItemString(data, collectionLimit),
- keyPath
+ keyPath,
+ expandedRef.current,
);
- const stylingArgs = [keyPath, nodeType, expanded, expandable] as const;
+ const stylingArgs = [
+ keyPath,
+ nodeType,
+ expandedRef.current,
+ expandable,
+ ] as const;
return hideRoot ? (
@@ -153,7 +198,7 @@ export default function JSONNestedNode(props: Props) {
)}
diff --git a/packages/react-json-tree/src/createStylingFromTheme.ts b/packages/react-json-tree/src/createStylingFromTheme.ts
index 5c776c7e..2be9fe2f 100644
--- a/packages/react-json-tree/src/createStylingFromTheme.ts
+++ b/packages/react-json-tree/src/createStylingFromTheme.ts
@@ -46,6 +46,7 @@ const getDefaultThemeStyling = (theme: Base16Theme): StylingConfig => {
return {
tree: {
+ position: 'relative',
border: 0,
padding: 0,
marginTop: '0.5em',
@@ -58,6 +59,19 @@ const getDefaultThemeStyling = (theme: Base16Theme): StylingConfig => {
backgroundColor: colors.BACKGROUND_COLOR,
},
+ expandCollapseAll: {
+ color: colors.TEXT_COLOR,
+ backgroundColor: colors.BACKGROUND_COLOR,
+ position: 'absolute',
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ gap: '1rem',
+ top: '1rem',
+ right: '1rem',
+ cursor: 'pointer',
+ },
+
value: ({ style }, nodeType, keyPath) => ({
style: {
...style,
diff --git a/packages/react-json-tree/src/expandCollapseButtons.tsx b/packages/react-json-tree/src/expandCollapseButtons.tsx
new file mode 100644
index 00000000..bcaab8f9
--- /dev/null
+++ b/packages/react-json-tree/src/expandCollapseButtons.tsx
@@ -0,0 +1,129 @@
+import {
+ faArrowDown,
+ faArrowRight,
+ faUndo,
+} from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import React, { ReactNode } from 'react';
+import { ExpandCollapseAll } from '.';
+import { useExpandCollapseAllContext } from './expandCollapseContext';
+import { StylingFunction } from 'react-base16-styling';
+
+interface Props {
+ expandCollapseAll: ExpandCollapseAll;
+ styling: StylingFunction;
+}
+
+interface ExpandButtonProps {
+ expandableDefaultValue?: 'expand' | 'collapse';
+ expandIcon?: ReactNode;
+}
+
+interface CollapseButtonProps {
+ expandableDefaultValue?: 'expand' | 'collapse';
+ collapseIcon?: ReactNode;
+}
+
+interface DefaultButtonProps {
+ defaultIcon?: ReactNode;
+}
+
+function ExpandCollapseButtons({ expandCollapseAll, styling }: Props) {
+ const { enableDefaultButton } = useExpandCollapseAllContext();
+
+ const expandableDefaultValue = expandCollapseAll?.defaultValue || 'expand';
+
+ return (
+
+ {enableDefaultButton && (
+
+ )}
+
+
+
+
+
+ );
+}
+
+function ExpandButton({
+ expandableDefaultValue,
+ expandIcon,
+}: ExpandButtonProps) {
+ const { expandAllState, setExpandAllState, setEnableDefaultButton } =
+ useExpandCollapseAllContext();
+
+ const onExpand = () => {
+ setExpandAllState('expand');
+ setEnableDefaultButton(true);
+ };
+
+ const isDefault = !expandAllState || expandAllState === 'default';
+
+ if (
+ expandAllState === 'collapse' ||
+ (isDefault && expandableDefaultValue === 'expand')
+ ) {
+ return (
+
+ {expandIcon || }
+
+ );
+ }
+
+ return <>>;
+}
+
+function CollapseButton({
+ expandableDefaultValue,
+ collapseIcon,
+}: CollapseButtonProps) {
+ const { expandAllState, setExpandAllState, setEnableDefaultButton } =
+ useExpandCollapseAllContext();
+
+ const onCollapse = () => {
+ setExpandAllState('collapse');
+ setEnableDefaultButton(true);
+ };
+
+ const isDefault = !expandAllState || expandAllState === 'default';
+
+ if (
+ expandAllState === 'expand' ||
+ (isDefault && expandableDefaultValue === 'collapse')
+ ) {
+ return (
+
+ {collapseIcon || }
+
+ );
+ }
+
+ return <>>;
+}
+
+function DefaultButton({ defaultIcon }: DefaultButtonProps) {
+ const { setExpandAllState, setEnableDefaultButton } =
+ useExpandCollapseAllContext();
+
+ const onDefaultCollapse = () => {
+ setExpandAllState('default');
+ setEnableDefaultButton(false);
+ };
+
+ return (
+
+ {defaultIcon || }
+
+ );
+
+ return <>>;
+}
+
+export default ExpandCollapseButtons;
diff --git a/packages/react-json-tree/src/expandCollapseContext.tsx b/packages/react-json-tree/src/expandCollapseContext.tsx
new file mode 100644
index 00000000..d1258ef5
--- /dev/null
+++ b/packages/react-json-tree/src/expandCollapseContext.tsx
@@ -0,0 +1,61 @@
+import React, {
+ ReactNode,
+ createContext,
+ useContext,
+ useMemo,
+ useState,
+} from 'react';
+import { ExpandCollapseAll } from '.';
+import ExpandCollapseButtons from './expandCollapseButtons';
+import { StylingFunction } from 'react-base16-styling';
+
+interface Context {
+ enableDefaultButton: boolean;
+ setEnableDefaultButton: any;
+ expandAllState?: 'expand' | 'collapse' | 'default';
+ setExpandAllState: any;
+}
+
+interface Props {
+ children: ReactNode;
+ expandCollapseAll?: ExpandCollapseAll;
+ styling: StylingFunction;
+}
+
+const ExpandCollapseAllContext = createContext({} as Context);
+
+function ExpandCollapseAllContextProvider({
+ expandCollapseAll,
+ children,
+ styling,
+}: Props) {
+ const [enableDefaultButton, setEnableDefaultButton] = useState(false);
+ const [expandAllState, setExpandAllState] = useState();
+
+ const value = useMemo(
+ () => ({
+ enableDefaultButton,
+ setEnableDefaultButton,
+ expandAllState,
+ setExpandAllState,
+ }),
+ [enableDefaultButton, expandAllState]
+ );
+
+ return (
+
+ {children}
+ {expandCollapseAll && (
+
+ )}
+
+ );
+}
+
+export const useExpandCollapseAllContext = () =>
+ useContext(ExpandCollapseAllContext);
+
+export default ExpandCollapseAllContextProvider;
diff --git a/packages/react-json-tree/src/index.tsx b/packages/react-json-tree/src/index.tsx
index 26fab0fc..0af44436 100644
--- a/packages/react-json-tree/src/index.tsx
+++ b/packages/react-json-tree/src/index.tsx
@@ -3,11 +3,13 @@
// Dave Vedder http://www.eskimospy.com/
// port by Daniele Zannotti http://www.github.com/dzannotti
-import React, { useMemo } from 'react';
+import React, { ReactNode, useMemo } 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 ExpandCollapseAllButtonsContext from './expandCollapseContext';
+
import type {
CommonExternalProps,
GetItemString,
@@ -20,6 +22,14 @@ interface Props extends Partial {
data: unknown;
theme?: Theme;
invertTheme?: boolean;
+ expandCollapseAll?: ExpandCollapseAll;
+}
+
+interface ExpandCollapseAll {
+ defaultValue?: 'expand' | 'collapse';
+ expandIcon?: ReactNode;
+ collapseIcon?: ReactNode;
+ defaultIcon?: ReactNode;
}
const identity = (value: any) => value;
@@ -41,6 +51,7 @@ export function JSONTree({
labelRenderer = defaultLabelRenderer,
valueRenderer = identity,
shouldExpandNodeInitially = expandRootNode,
+ expandCollapseAll,
hideRoot = false,
getItemString = defaultItemString,
postprocessValue = identity,
@@ -56,20 +67,25 @@ export function JSONTree({
return (
);
}
@@ -87,4 +103,4 @@ export type {
Styling,
CommonExternalProps,
} from './types';
-export type { StylingValue };
+export type { ExpandCollapseAll, StylingValue };
diff --git a/packages/react-json-tree/src/types.ts b/packages/react-json-tree/src/types.ts
index 6a67f376..357979ec 100644
--- a/packages/react-json-tree/src/types.ts
+++ b/packages/react-json-tree/src/types.ts
@@ -10,7 +10,8 @@ export type GetItemString = (
data: unknown,
itemType: React.ReactNode,
itemString: string,
- keyPath: KeyPath
+ keyPath: KeyPath,
+ isExpanded: boolean,
) => React.ReactNode;
export type LabelRenderer = (
diff --git a/packages/react-json-tree/test/index.spec.tsx b/packages/react-json-tree/test/index.spec.tsx
index 99661291..136b64a9 100644
--- a/packages/react-json-tree/test/index.spec.tsx
+++ b/packages/react-json-tree/test/index.spec.tsx
@@ -2,7 +2,7 @@ import React from 'react';
import { createRenderer } from 'react-test-renderer/shallow';
import { JSONTree } from '../src/index';
-import JSONNode from '../src/JSONNode';
+import ExpandCollapseAllContext from '../src/expandCollapseContext';
const BASIC_DATA = { a: 1, b: 'c' };
@@ -17,6 +17,6 @@ describe('JSONTree', () => {
const result = render();
expect(result.type).toBe('ul');
- expect(result.props.children.type.name).toBe(JSONNode.name);
+ expect(result.props.children.type.name).toBe(ExpandCollapseAllContext.name);
});
});