Skip to content

Commit 281eccb

Browse files
authored
feat: ref details popover [STU-185] (#24)
* feat: ref details popover * feat: onGoToRef handler
1 parent 8790c96 commit 281eccb

File tree

5 files changed

+87
-70
lines changed

5 files changed

+87
-70
lines changed
Lines changed: 59 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as React from 'react';
22

33
import { State, Store } from '@sambego/storybook-state';
4+
import { action } from '@storybook/addon-actions';
45
import { boolean, number, object, text, withKnobs } from '@storybook/addon-knobs';
56
import { storiesOf } from '@storybook/react';
67
import { JsonSchemaViewer } from '../components';
@@ -16,16 +17,16 @@ import { Wrapper } from './utils/Wrapper';
1617

1718
storiesOf('JsonSchemaViewer', module)
1819
.addDecorator(withKnobs)
20+
.addDecorator(storyFn => <Wrapper>{storyFn()}</Wrapper>)
1921
.add('default', () => (
20-
<Wrapper>
21-
<JsonSchemaViewer
22-
name={text('name', 'my schema')}
23-
schema={schema as JSONSchema4}
24-
defaultExpandedDepth={number('defaultExpandedDepth', 2)}
25-
expanded={boolean('expanded', false)}
26-
hideTopBar={boolean('hideTopBar', false)}
27-
/>
28-
</Wrapper>
22+
<JsonSchemaViewer
23+
name={text('name', 'my schema')}
24+
schema={schema as JSONSchema4}
25+
defaultExpandedDepth={number('defaultExpandedDepth', 2)}
26+
expanded={boolean('expanded', false)}
27+
hideTopBar={boolean('hideTopBar', false)}
28+
onGoToRef={action('onGoToRef')}
29+
/>
2930
))
3031
.add('with dereferenced schema', () => {
3132
const store = new Store<{ selected: string[] }>({
@@ -34,74 +35,68 @@ storiesOf('JsonSchemaViewer', module)
3435

3536
return (
3637
<State store={store}>
37-
<Wrapper>
38-
<JsonSchemaViewer
39-
name={text('name', 'name')}
40-
schema={schemaWithRefs as JSONSchema4}
41-
dereferencedSchema={dereferencedSchema as JSONSchema4}
42-
defaultExpandedDepth={number('defaultExpandedDepth', 2)}
43-
expanded={boolean('expanded', true)}
44-
hideTopBar={boolean('hideTopBar', false)}
45-
/>
46-
</Wrapper>
38+
<JsonSchemaViewer
39+
name={text('name', 'name')}
40+
schema={schemaWithRefs as JSONSchema4}
41+
dereferencedSchema={dereferencedSchema as JSONSchema4}
42+
defaultExpandedDepth={number('defaultExpandedDepth', 2)}
43+
expanded={boolean('expanded', true)}
44+
hideTopBar={boolean('hideTopBar', false)}
45+
onGoToRef={action('onGoToRef')}
46+
/>
4747
</State>
4848
);
4949
})
5050
.add('custom schema', () => (
51-
<Wrapper>
52-
<JsonSchemaViewer
53-
name={text('name', 'my schema')}
54-
schema={object('schema', {})}
55-
expanded={boolean('expanded', true)}
56-
hideTopBar={boolean('hideTopBar', false)}
57-
/>
58-
</Wrapper>
51+
<JsonSchemaViewer
52+
name={text('name', 'my schema')}
53+
schema={object('schema', {})}
54+
expanded={boolean('expanded', true)}
55+
hideTopBar={boolean('hideTopBar', false)}
56+
onGoToRef={action('onGoToRef')}
57+
/>
5958
))
6059
.add('stress-test schema', () => (
61-
<Wrapper>
62-
<JsonSchemaViewer
63-
name={text('name', 'my stress schema')}
64-
schema={stressSchema as JSONSchema4}
65-
defaultExpandedDepth={number('defaultExpandedDepth', 2)}
66-
expanded={boolean('expanded', false)}
67-
hideTopBar={boolean('hideTopBar', false)}
68-
/>
69-
</Wrapper>
60+
<JsonSchemaViewer
61+
name={text('name', 'my stress schema')}
62+
schema={stressSchema as JSONSchema4}
63+
defaultExpandedDepth={number('defaultExpandedDepth', 2)}
64+
expanded={boolean('expanded', false)}
65+
hideTopBar={boolean('hideTopBar', false)}
66+
onGoToRef={action('onGoToRef')}
67+
/>
7068
))
7169
.add('allOf-schema', () => (
72-
<Wrapper>
73-
<JsonSchemaViewer
74-
schema={allOfSchema as JSONSchema4}
75-
dereferencedSchema={allOfSchemaResolved as JSONSchema4}
76-
defaultExpandedDepth={number('defaultExpandedDepth', 2)}
77-
expanded={boolean('expanded', false)}
78-
hideTopBar={boolean('hideTopBar', false)}
79-
/>
80-
</Wrapper>
70+
<JsonSchemaViewer
71+
schema={allOfSchema as JSONSchema4}
72+
dereferencedSchema={allOfSchemaResolved as JSONSchema4}
73+
defaultExpandedDepth={number('defaultExpandedDepth', 2)}
74+
expanded={boolean('expanded', false)}
75+
hideTopBar={boolean('hideTopBar', false)}
76+
onGoToRef={action('onGoToRef')}
77+
/>
8178
))
8279
.add('error boundary', () => (
83-
<Wrapper>
80+
<JsonSchemaViewer
81+
name={text('name', 'throw me an error!')}
82+
// @ts-ignore
83+
schema={null}
84+
onError={(error: any) => console.log('You can hook into the onError handler too!', error)}
85+
expanded={boolean('expanded', false)}
86+
defaultExpandedDepth={number('defaultExpandedDepth', 2)}
87+
hideTopBar={boolean('hideTopBar', false)}
88+
onGoToRef={action('onGoToRef')}
89+
/>
90+
))
91+
.add('dark', () => (
92+
<div style={{ height: '100vh' }} className="bp3-dark bg-gray-8">
8493
<JsonSchemaViewer
85-
name={text('name', 'throw me an error!')}
86-
// @ts-ignore
87-
schema={null}
88-
onError={(error: any) => console.log('You can hook into the onError handler too!', error)}
89-
expanded={boolean('expanded', false)}
94+
name={text('name', 'my stress schema')}
95+
schema={stressSchema as JSONSchema4}
9096
defaultExpandedDepth={number('defaultExpandedDepth', 2)}
97+
expanded={boolean('expanded', false)}
9198
hideTopBar={boolean('hideTopBar', false)}
99+
onGoToRef={action('onGoToRef')}
92100
/>
93-
</Wrapper>
94-
))
95-
.add('dark', () => (
96-
<div style={{ height: '100vh' }} className="bp3-dark bg-gray-8">
97-
<Wrapper>
98-
<JsonSchemaViewer
99-
name={text('name', 'my stress schema')}
100-
schema={stressSchema as JSONSchema4}
101-
defaultExpandedDepth={number('defaultExpandedDepth', 2)}
102-
expanded={boolean('expanded', false)}
103-
hideTopBar={boolean('hideTopBar', false)}
104-
/>
105-
</Wrapper>
106101
</div>
107102
));

src/components/JsonSchemaViewer.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import * as React from 'react';
55
import ErrorBoundary, { ErrorBoundaryProps, FallbackProps } from 'react-error-boundary';
66

77
import { JSONSchema4 } from 'json-schema';
8+
import { GoToRefHandler } from '../types';
89
import { isSchemaViewerEmpty, renderSchema } from '../utils';
910
import { SchemaTree } from './SchemaTree';
1011

@@ -19,6 +20,7 @@ export interface IJsonSchemaViewer extends ErrorBoundaryProps {
1920
name?: string;
2021
hideTopBar?: boolean;
2122
maxRows?: number;
23+
onGoToRef?: GoToRefHandler;
2224
}
2325

2426
export class JsonSchemaViewerComponent extends React.PureComponent<IJsonSchemaViewer> {

src/components/SchemaRow.tsx

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { MarkdownViewer } from '@stoplight/markdown-viewer';
2-
import { IRowRendererOptions, ITreeListNode, TreeStore } from '@stoplight/tree-list';
2+
import { IRowRendererOptions, TreeStore } from '@stoplight/tree-list';
33
import { Colors, Icon, Popover } from '@stoplight/ui-kit';
44
import * as cn from 'classnames';
55
import * as React from 'react';
@@ -8,21 +8,22 @@ import get = require('lodash/get');
88
import map = require('lodash/map');
99
import size = require('lodash/size');
1010

11-
import { SchemaNodeWithMeta } from '../types';
11+
import { GoToRefHandler, SchemaNodeWithMeta, SchemaTreeListNode } from '../types';
1212
import { isCombiner, isRef } from '../utils';
1313
import { Types } from './';
1414

1515
export interface ISchemaRow {
16-
node: ITreeListNode<object>;
16+
node: SchemaTreeListNode;
1717
rowOptions: IRowRendererOptions;
1818
treeStore: TreeStore;
19+
onGoToRef?: GoToRefHandler;
1920
}
2021

2122
const ICON_SIZE = 12;
2223
const ICON_DIMENSION = 20;
2324
const ROW_OFFSET = 7;
2425

25-
export const SchemaRow: React.FunctionComponent<ISchemaRow> = ({ node, treeStore }) => {
26+
export const SchemaRow: React.FunctionComponent<ISchemaRow> = ({ node, treeStore, onGoToRef }) => {
2627
const schemaNode = node.metadata as SchemaNodeWithMeta;
2728
const { name, $ref, subtype, required } = schemaNode;
2829

@@ -37,6 +38,14 @@ export const SchemaRow: React.FunctionComponent<ISchemaRow> = ({ node, treeStore
3738
...get(schemaNode, 'validations', {}),
3839
};
3940
const validationCount = Object.keys(nodeValidations).length;
41+
const handleGoToRef = React.useCallback<React.MouseEventHandler>(
42+
() => {
43+
if (onGoToRef) {
44+
onGoToRef($ref!, node);
45+
}
46+
},
47+
[onGoToRef, node, $ref],
48+
);
4049

4150
const requiredElem = (
4251
<div className={cn('ml-2', required ? 'font-medium' : 'text-darken-7 dark:text-lighten-6')}>
@@ -85,6 +94,12 @@ export const SchemaRow: React.FunctionComponent<ISchemaRow> = ({ node, treeStore
8594
{type === '$ref' ? `[${$ref}]` : null}
8695
</Types>
8796

97+
{type === '$ref' && onGoToRef ? (
98+
<a role="button" className="text-blue-4 ml-2" onClick={handleGoToRef}>
99+
(go to ref)
100+
</a>
101+
) : null}
102+
88103
{node.canHaveChildren && <div className="ml-2 text-darken-7 dark:text-lighten-6">{`{${childrenCount}}`}</div>}
89104

90105
{'pattern' in schemaNode && schemaNode.pattern ? (

src/components/SchemaTree.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { JSONSchema4 } from 'json-schema';
44
import { observer } from 'mobx-react-lite';
55
import * as React from 'react';
66

7+
import { GoToRefHandler } from '../types';
78
import { SchemaRow } from './';
89

910
export interface ISchemaTree {
@@ -15,23 +16,25 @@ export interface ISchemaTree {
1516
hideTopBar?: boolean;
1617
expanded?: boolean;
1718
maxRows?: number;
19+
onGoToRef?: GoToRefHandler;
1820
}
1921

2022
const canDrag = () => false;
2123

2224
export const SchemaTree = observer<ISchemaTree>(props => {
23-
const { hideTopBar, name, treeStore, maxRows, className } = props;
25+
const { hideTopBar, name, treeStore, maxRows, className, onGoToRef } = props;
2426

2527
treeStore.on(TreeListEvents.NodeClick, (e, node) => treeStore.toggleExpand(node));
2628

2729
const itemData = {
2830
treeStore,
2931
count: treeStore.nodes.length,
32+
onGoToRef,
3033
};
3134

3235
const rowRenderer = React.useCallback(
3336
(node, rowOptions) => <SchemaRow node={node} rowOptions={rowOptions} {...itemData} />,
34-
[itemData],
37+
[itemData.count],
3538
);
3639

3740
return (

src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,5 @@ export interface ITreeNodeMeta {
6767
export type SchemaNodeWithMeta = SchemaNode & ITreeNodeMeta;
6868

6969
export type SchemaTreeListNode = TreeListNode<SchemaNodeWithMeta>;
70+
71+
export type GoToRefHandler = (path: string, node: SchemaTreeListNode) => void;

0 commit comments

Comments
 (0)