Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Addon-controls: Add JSON tree editor for Object/Array Type args #12824

Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
c9f8a8f
[dependencies][lib/components] Add react-editable-json-tree
hydrosquall Oct 20, 2020
718299b
(feat:controls) Add React-editable-tree for Object type
hydrosquall Oct 20, 2020
87149e8
(feat:controls) Update documentation examples for object controls to …
hydrosquall Oct 20, 2020
c93b353
Merge branch 'next' into pr/12824
shilman Dec 2, 2020
d591865
Merge branch 'next' into pr/12824
shilman Dec 2, 2020
866f4b2
Merge branch 'next' into cameron.yick/feature/ui-editable-json-tree-knob
ghengeveld Feb 18, 2021
f30528e
Merge remote-tracking branch 'origin/next' into cameron.yick/feature/…
ghengeveld Feb 18, 2021
e3e2415
Fix lockfile
ghengeveld Feb 18, 2021
cba8343
Many styling tweaks
ghengeveld Feb 19, 2021
91d51d3
More tweaks
ghengeveld Feb 19, 2021
d9c58c2
Design tweaks
ghengeveld Feb 22, 2021
51e6b81
Copy react-editable-json-tree into the project
ghengeveld Feb 22, 2021
adf06ce
Replace react-hotkeys with own key listeners.
ghengeveld Feb 22, 2021
ed2eced
Fix first-child warning
ghengeveld Feb 22, 2021
c8967b5
Replace componentWillReceiveProps with getDerivedStateFromProps
ghengeveld Feb 22, 2021
063af6a
Many tweaks and fixes
ghengeveld Feb 22, 2021
af7cb07
Delete objects and arrays rather than nullifying them
ghengeveld Feb 22, 2021
55d3df4
Dark theme tweaks
ghengeveld Feb 22, 2021
609b901
Tweaks
ghengeveld Feb 22, 2021
5c03dbb
Cleanup
ghengeveld Feb 22, 2021
2f2896a
Drop prop-types and comment blocks
ghengeveld Feb 22, 2021
fa10ff5
Improve addon-controls story
ghengeveld Feb 22, 2021
ca34d99
Use text control for unknown arg types
ghengeveld Feb 22, 2021
9168b45
Add support for entering raw JSON
ghengeveld Feb 22, 2021
5bec7e0
Use JSON editor for arrays
ghengeveld Feb 22, 2021
0472b6c
Fix standalone style
ghengeveld Feb 22, 2021
3a72d71
Remove unused import
ghengeveld Feb 22, 2021
ca4ccb9
Fix data sync
ghengeveld Feb 22, 2021
26ec6b2
Restore propTypes and fix string value editing
ghengeveld Feb 22, 2021
63d3284
Merge branch 'next' into cameron.yick/feature/ui-editable-json-tree-knob
ghengeveld Feb 22, 2021
10dc235
Support removing the root node
ghengeveld Feb 23, 2021
3d7f7a7
Add RAW toggle and fix menu icon hover style
ghengeveld Feb 23, 2021
c3e79d4
Style tweaks
ghengeveld Feb 23, 2021
e13c905
Fix null handling
ghengeveld Feb 23, 2021
6a400ff
Don't show RAW toggle when there's no data
ghengeveld Feb 23, 2021
9ef5b3a
Fix dark style
ghengeveld Feb 23, 2021
5c20661
Merge remote-tracking branch 'origin/next' into cameron.yick/feature/…
ghengeveld Feb 23, 2021
7a1601a
Fix textarea height
ghengeveld Feb 23, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ function setupAngularJestPreset() {
// for emission of the TS decorations like 'design:paramtypes'
try {
jest.requireActual('jest-preset-angular/build/setupJest');
} catch(e) {
} catch (e) {
jest.requireActual('jest-preset-angular/build/setup-jest');
}
}
Expand Down
19 changes: 16 additions & 3 deletions examples/official-storybook/stories/addon-controls.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,35 @@ export default {
},
};

const Template = (args) => <Button {...args} />;
const DEFAULT_NESTED_OBJECT = { a: 4, b: { c: 'hello', d: [1, 2, 3] } };

const Template = (args) => (
<div>
<Button type={args.type}>{args.children}</Button>
{args.somethingElse && JSON.stringify(args.somethingElse)}
</div>
);

export const Basic = Template.bind({});
Basic.args = {
children: 'basic',
somethingElse: { a: 2 },
somethingElse: DEFAULT_NESTED_OBJECT,
};

export const Action = Template.bind({});
Action.args = {
children: 'hmmm',
type: 'action',
somethingElse: { a: 4 },
somethingElse: DEFAULT_NESTED_OBJECT,
};

export const CustomControls = Template.bind({});
CustomControls.args = {
children: 'hmmm',
type: 'action',
somethingElse: DEFAULT_NESTED_OBJECT,
};

CustomControls.argTypes = {
children: { table: { disable: true } },
type: { control: { disable: true } },
Expand Down
1 change: 1 addition & 0 deletions lib/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"overlayscrollbars": "^1.10.2",
"polished": "^3.4.4",
"react-color": "^2.17.0",
"react-editable-json-tree": "^2.2.1",
"react-popper-tooltip": "^3.1.0",
"react-syntax-highlighter": "^13.5.0",
"react-textarea-autosize": "^8.1.1",
Expand Down
171 changes: 116 additions & 55 deletions lib/components/src/controls/Object.tsx
Original file line number Diff line number Diff line change
@@ -1,72 +1,133 @@
import React, { FC, ChangeEvent, useState, useCallback, useEffect } from 'react';
import { styled } from '@storybook/theming';
import React, { useCallback, useMemo } from 'react';
import { styled, useTheme, Theme } from '@storybook/theming';

import deepEqual from 'fast-deep-equal';
import { Form } from '../form';
import { ControlProps, ObjectValue, ObjectConfig } from './types';
import { ArgType } from '../blocks';

const format = (value: any) => (value ? JSON.stringify(value) : '');

const parse = (value: string) => {
const trimmed = value && value.trim();
return trimmed ? JSON.parse(trimmed) : {};
};

const validate = (value: any, argType: ArgType) => {
if (argType && argType.type.name === 'array') {
return Array.isArray(value);
}
return true;
};
import { JsonTree, JsonTreeProps } from 'react-editable-json-tree';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we should lazy-load these components?

import type { ControlProps, ObjectValue, ObjectConfig } from './types';

const Wrapper = styled.label({
display: 'flex',
});

export type ObjectProps = ControlProps<ObjectValue> & ObjectConfig;
export const ObjectControl: FC<ObjectProps> = ({
name,
argType,
value,
onChange,
onBlur,
onFocus,
}) => {
const [valid, setValid] = useState(true);
const [text, setText] = useState(format(value));
export type ObjectProps = ControlProps<ObjectValue> &
ObjectConfig & {
theme: any; // TODO: is there a type for this?
};

const getCustomStyleFunction: (theme: Theme) => JsonTreeProps['getStyle'] = (theme) => (
keyName,
data,
keyPath,
deep,
dataType
) => {
const DEFAULT_FONT_SIZE = '13px';
const DEFAULT_LINE_HEIGHT = '18px';
const DEFAULT_PLUS_COLOR = theme.color.ancillary;
const DEFAULT_MINUS_COLOR = theme.color.negative;
const DEFAULT_TEXT_COLOR = theme.color.defaultText;
const DEFAULT_COLLAPSED_COLOR = theme.color.dark;
const DEFAULT_KEY_COLOR = theme.color.secondary; // Bright to invite clicking

// Based on default styles provided by the library
// https://github.com/oxyno-zeta/react-editable-json-tree/blob/master/src/utils/styles.js
const objectStyle = {
minus: {
color: DEFAULT_MINUS_COLOR,
},
plus: {
color: DEFAULT_PLUS_COLOR,
},
collapsed: {
color: DEFAULT_COLLAPSED_COLOR,
},
delimiter: {},
ul: {
padding: '0px',
margin: '0 0 0 25px',
listStyle: 'none',
},
name: {
color: DEFAULT_KEY_COLOR,
fontSize: DEFAULT_FONT_SIZE,
lineHeight: DEFAULT_LINE_HEIGHT,
},
addForm: {},
};
const arrayStyle = {
minus: {
color: DEFAULT_MINUS_COLOR,
},
plus: {
color: DEFAULT_PLUS_COLOR,
},
collapsed: {
color: DEFAULT_COLLAPSED_COLOR,
},
delimiter: {},
ul: {
padding: '0px',
margin: '0 0 0 25px',
listStyle: 'none',
},
name: {
color: DEFAULT_TEXT_COLOR,
fontSize: DEFAULT_FONT_SIZE,
lineHeight: DEFAULT_LINE_HEIGHT,
},
addForm: {},
};
const valueStyle = {
minus: {
color: DEFAULT_MINUS_COLOR,
},
editForm: {},
value: {
color: theme.color.ultraviolet, // something colorful that invites clicking
fontSize: DEFAULT_FONT_SIZE,
lineHeight: DEFAULT_LINE_HEIGHT,
},
li: {
minHeight: '22px',
lineHeight: '22px',
outline: '0px',
},
name: {
color: DEFAULT_KEY_COLOR,
},
};

useEffect(() => {
const newText = format(value);
if (text !== newText) setText(newText);
}, [value]);
switch (dataType) {
case 'Object':
case 'Error':
return objectStyle;
case 'Array':
return arrayStyle;
default:
return valueStyle;
}
};

export const ObjectControl: React.FC<ObjectProps> = ({ name, value = {}, onChange }) => {
const handleChange = useCallback(
(e: ChangeEvent<HTMLTextAreaElement>) => {
try {
const newVal = parse(e.target.value);
const newValid = validate(newVal, argType);
if (newValid && !deepEqual(value, newVal)) {
onChange(newVal);
}
setValid(newValid);
} catch (err) {
setValid(false);
}
setText(e.target.value);
(data: ObjectValue) => {
onChange(data);
},
[onChange, setValid]
[onChange]
);

const theme = useTheme() as Theme;

const customStyleFunction = useMemo(() => getCustomStyleFunction(theme), [theme]);

return (
<Wrapper>
<Form.Textarea
valid={valid ? undefined : 'error'}
value={text}
onChange={handleChange}
size="flex"
placeholder="Adjust object dynamically"
{...{ name, onBlur, onFocus }}
<JsonTree
data={value}
onFullyUpdate={handleChange}
rootName="root"
getStyle={customStyleFunction}
cancelButtonElement={<button type="submit">cancel</button>}
editButtonElement={<button type="submit">edit</button>}
/>
</Wrapper>
);
Expand Down
60 changes: 60 additions & 0 deletions lib/components/src/typings.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,63 @@ declare module 'global';
declare module 'markdown-to-jsx';
declare module '*.md';
declare module '@storybook/semver';

declare module 'react-editable-json-tree' {
type JsonTreeData = object | Array;

type CssStyleObject = object; // Keys = CSS properties

// https://github.com/oxyno-zeta/react-editable-json-tree/blob/master/src/types/dataTypes.js
export enum DATA_TYPES {
ERROR = 'Error',
OBJECT = 'Object',
ARRAY = 'Array',
STRING = 'String',
NUMBER = 'Number',
BOOLEAN = 'Boolean',
DATE = 'Date',
NULL = 'Null',
UNDEFINED = 'Undefined',
FUNCTION = 'Function',
SYMBOL = 'Symbol',
}

// This interface is incomplete and only includes the props used by storybook.
// More are available by visiting the project README.md
interface JsonTreeProps {
// Data to be displayed/edited
data: JsonTreeData;
// Function called each time an update is done and give the updated data
onFullyUpdate?: (d: JsonTree) => void;
// Root name for first object
rootName?: string;

// Get style (CSS keys)
// Example responses:
// https://github.com/oxyno-zeta/react-editable-json-tree/blob/master/src/utils/styles.js
getStyle?: (
keyName: string, // key name of currently selected node/value
data: any, // data at the currently selected node/value
keyPath: string[], // Array of keyNames to reach current node
deep: number, // Depth of node within current tree
dataType: DATA_TYPES, // Data type of selected node
) => {
minus?: CssStyleObject;
plus?: CssStyleObject;
collapsed?: CssStyleObject;
delimiter?: CssStyleObject;
ul?: CssStyleObject;
value?: CssStyleObject;
name?: CssStyleObject;
editForm?: CssStyleObject;
}

// Optional control overrides
addButtonElement?: JSX.Element;
cancelButtonElement?: JSX.Element;
buttonElement?: JSX.Element;
editButtonElement?: JSX.Element;

}
export const JsonTree: React.FC<JsonTreeProps>;
}
27 changes: 25 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -11331,7 +11331,7 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7:
safe-buffer "^5.0.1"
sha.js "^2.4.8"

create-react-class@^15.6.0:
create-react-class@^15.5.2, create-react-class@^15.6.0:
version "15.7.0"
resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.7.0.tgz#7499d7ca2e69bb51d13faf59bd04f0c65a1d6c1e"
integrity sha512-QZv4sFWG9S5RUvkTYWbflxeZX+JG7Cz0Tn33rQBJ+WFQTqTfUTjMjiv9tnfXazjsO5r0KhPs+AqCjyrQX6h2ng==
Expand Down Expand Up @@ -21591,7 +21591,7 @@ [email protected]:
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==

[email protected], "lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.0.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1, lodash@^4.5.1, lodash@~4.17.10, lodash@~4.17.19:
[email protected], "lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.0.1, lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1, lodash@^4.5.1, lodash@~4.17.10, lodash@~4.17.19:
version "4.17.20"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
Expand Down Expand Up @@ -22767,6 +22767,11 @@ mount-point@^3.0.0:
pify "^2.3.0"
pinkie-promise "^2.0.1"

mousetrap@^1.5.2:
version "1.6.5"
resolved "https://registry.yarnpkg.com/mousetrap/-/mousetrap-1.6.5.tgz#8a766d8c272b08393d5f56074e0b5ec183485bf9"
integrity sha512-QNo4kEepaIBwiT8CDhP98umTetp+JNfQYBWvC1pc6/OAibuXtRcxZ58Qz8skvEHYvURne/7R8T5VoOI7rDsEUA==

mout@^1.0.0:
version "1.2.2"
resolved "https://registry.yarnpkg.com/mout/-/mout-1.2.2.tgz#c9b718a499806a0632cede178e80f436259e777d"
Expand Down Expand Up @@ -26880,6 +26885,14 @@ react-draggable@^4.0.3:
classnames "^2.2.5"
prop-types "^15.6.0"

react-editable-json-tree@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/react-editable-json-tree/-/react-editable-json-tree-2.2.1.tgz#1ae3f13a4b4358c3cf058e47c200baaf690bfacb"
integrity sha512-GwhhcvU5ptg9rbaMWr+hTGwfxX6JNfV+fwdrNaqIAlEQu+GJKi4PKICRrlvibKNl2z5DOmL6x3ppEBSusY1K+g==
dependencies:
prop-types "^15.6.0"
react-hotkeys "^0.10.0"

react-element-to-jsx-string@^14.3.1:
version "14.3.2"
resolved "https://registry.yarnpkg.com/react-element-to-jsx-string/-/react-element-to-jsx-string-14.3.2.tgz#c0000ed54d1f8b4371731b669613f2d4e0f63d5c"
Expand Down Expand Up @@ -26928,6 +26941,16 @@ [email protected]:
dependencies:
prop-types "^15.6.1"

react-hotkeys@^0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/react-hotkeys/-/react-hotkeys-0.10.0.tgz#d1e78bd63f16d6db58d550d33c8eb071f35d94fb"
integrity sha1-0eeL1j8W1ttY1VDTPI6wcfNdlPs=
dependencies:
create-react-class "^15.5.2"
lodash "^4.13.1"
mousetrap "^1.5.2"
prop-types "^15.5.8"

react-input-autosize@^2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-2.2.2.tgz#fcaa7020568ec206bc04be36f4eb68e647c4d8c2"
Expand Down