|
1 | | -import { MarkdownViewer } from '@stoplight/markdown-viewer'; |
2 | 1 | import { IRowRendererOptions } from '@stoplight/tree-list'; |
3 | | -import { Icon, Popover } from '@stoplight/ui-kit'; |
4 | | -import * as cn from 'classnames'; |
| 2 | +import cn from 'classnames'; |
5 | 3 | import * as React from 'react'; |
| 4 | +import { Divider } from './shared/Divider'; |
6 | 5 |
|
7 | 6 | import get = require('lodash/get'); |
8 | | -import map = require('lodash/map'); |
9 | | -import size = require('lodash/size'); |
10 | 7 |
|
11 | | -import { GoToRefHandler, IExtendableRenderers, SchemaNodeWithMeta, SchemaTreeListNode } from '../types'; |
12 | | -import { isCombiner, isRef } from '../utils'; |
13 | | -import { Types } from './'; |
| 8 | +import { GoToRefHandler, SchemaNodeWithMeta, SchemaTreeListNode } from '../types'; |
| 9 | +import { Caret } from './shared/Caret'; |
| 10 | +import { Description } from './shared/Description'; |
| 11 | +import { Property } from './shared/Property'; |
| 12 | +import { Validations } from './shared/Validations'; |
14 | 13 |
|
15 | | -export interface ISchemaRow extends IExtendableRenderers { |
| 14 | +export interface ISchemaRow { |
| 15 | + className?: string; |
16 | 16 | node: SchemaTreeListNode; |
17 | 17 | rowOptions: IRowRendererOptions; |
18 | 18 | onGoToRef?: GoToRefHandler; |
19 | | - toggleExpand: () => void; |
20 | 19 | } |
21 | 20 |
|
22 | 21 | const ICON_SIZE = 12; |
23 | 22 | const ICON_DIMENSION = 20; |
| 23 | +const ROW_OFFSET = 7; |
24 | 24 |
|
25 | | -export const SchemaRow: React.FunctionComponent<ISchemaRow> = ({ |
26 | | - node, |
27 | | - rowOptions, |
28 | | - onGoToRef, |
29 | | - rowRendererRight, |
30 | | - toggleExpand, |
31 | | -}) => { |
| 25 | +export const SchemaRow: React.FunctionComponent<ISchemaRow> = ({ className, node, rowOptions, onGoToRef }) => { |
32 | 26 | const schemaNode = node.metadata as SchemaNodeWithMeta; |
33 | | - const { name, $ref, subtype, required } = schemaNode; |
34 | | - |
35 | | - const type = isRef(schemaNode) ? '$ref' : isCombiner(schemaNode) ? schemaNode.combiner : schemaNode.type; |
36 | 27 | const description = get(schemaNode, 'annotations.description'); |
37 | | - const childrenCount = |
38 | | - type === 'object' |
39 | | - ? size(get(schemaNode, 'properties')) |
40 | | - : subtype === 'object' |
41 | | - ? size(get(schemaNode, 'items.properties')) |
42 | | - : size(get(schemaNode, 'items')); |
43 | | - |
44 | | - const nodeValidations = { |
45 | | - ...('annotations' in schemaNode && schemaNode.annotations.default |
46 | | - ? { default: schemaNode.annotations.default } |
47 | | - : {}), |
48 | | - ...get(schemaNode, 'validations', {}), |
49 | | - }; |
50 | | - const validationCount = Object.keys(nodeValidations).length; |
51 | | - const handleGoToRef = React.useCallback<React.MouseEventHandler>( |
52 | | - () => { |
53 | | - if (onGoToRef) { |
54 | | - onGoToRef($ref!, node); |
55 | | - } |
56 | | - }, |
57 | | - [onGoToRef, node, $ref], |
58 | | - ); |
59 | | - |
60 | | - const requiredElem = ( |
61 | | - <div className={cn('ml-2', required ? 'font-medium' : 'text-darken-7 dark:text-lighten-6')}> |
62 | | - {required ? 'required' : 'optional'} |
63 | | - {validationCount ? `+${validationCount}` : ''} |
64 | | - </div> |
65 | | - ); |
66 | 28 |
|
67 | | - const combinerOffset = ICON_DIMENSION * node.level; |
68 | 29 | return ( |
69 | | - <div onClick={toggleExpand} className="px-6 flex-1 w-full"> |
70 | | - {/* Do not set position: relative. Divider must be relative to the parent container in order to avoid bugs related to this container calculated height changes. */} |
| 30 | + <div className={cn('px-2 flex-1 w-full', className)}> |
71 | 31 | <div |
72 | | - className="flex items-center text-sm" |
| 32 | + className="flex items-center text-sm relative" |
73 | 33 | style={{ |
74 | | - marginLeft: combinerOffset, |
| 34 | + marginLeft: ICON_DIMENSION * node.level, // offset for spacing |
75 | 35 | }} |
76 | 36 | > |
77 | 37 | {node.canHaveChildren && |
78 | 38 | node.level > 0 && ( |
79 | | - <div |
80 | | - className="absolute flex justify-center cursor-pointer p-1 rounded hover:bg-darken-3" |
| 39 | + <Caret |
| 40 | + isExpanded={!!rowOptions.isExpanded} |
81 | 41 | style={{ |
82 | | - left: combinerOffset, |
| 42 | + left: ICON_DIMENSION * -1 + ROW_OFFSET / -2, |
83 | 43 | width: ICON_DIMENSION, |
84 | 44 | height: ICON_DIMENSION, |
85 | 45 | }} |
86 | | - > |
87 | | - <Icon |
88 | | - iconSize={ICON_SIZE} |
89 | | - icon={rowOptions.isExpanded ? 'caret-down' : 'caret-right'} |
90 | | - className="text-darken-9 dark:text-lighten-9" |
91 | | - /> |
92 | | - </div> |
| 46 | + size={ICON_SIZE} |
| 47 | + /> |
93 | 48 | )} |
94 | 49 |
|
95 | | - {schemaNode.divider && ( |
96 | | - <div |
97 | | - className="flex items-center absolute" |
98 | | - style={{ |
99 | | - top: 0, |
100 | | - height: 1, |
101 | | - width: `calc(100% - ${combinerOffset}px - 1.5rem)`, |
102 | | - }} |
103 | | - > |
104 | | - <div className="text-darken-7 dark:text-lighten-8 uppercase text-xs pr-2 -ml-4">{schemaNode.divider}</div> |
105 | | - <div className="flex-1 bg-darken-5 dark:bg-lighten-5" style={{ height: 1 }} /> |
106 | | - </div> |
107 | | - )} |
| 50 | + {schemaNode.divider && <Divider>{schemaNode.divider}</Divider>} |
108 | 51 |
|
109 | 52 | <div className="flex-1 flex truncate"> |
110 | | - {name && <div className="mr-2">{name}</div>} |
111 | | - |
112 | | - <Types type={type} subtype={subtype}> |
113 | | - {type === '$ref' ? `[${$ref}]` : null} |
114 | | - </Types> |
115 | | - |
116 | | - {type === '$ref' && onGoToRef ? ( |
117 | | - <a role="button" className="text-blue-4 ml-2" onClick={handleGoToRef}> |
118 | | - (go to ref) |
119 | | - </a> |
120 | | - ) : null} |
121 | | - |
122 | | - {node.canHaveChildren && <div className="ml-2 text-darken-7 dark:text-lighten-7">{`{${childrenCount}}`}</div>} |
123 | | - |
124 | | - {'pattern' in schemaNode && schemaNode.pattern ? ( |
125 | | - <div className="ml-2 text-darken-7 dark:text-lighten-7 truncate">(pattern property)</div> |
126 | | - ) : null} |
127 | | - |
128 | | - {description && ( |
129 | | - <Popover |
130 | | - boundary="window" |
131 | | - interactionKind="hover" |
132 | | - className="ml-2 flex-1 truncate flex items-baseline" |
133 | | - target={<div className="text-darken-7 dark:text-lighten-7 w-full truncate">{description}</div>} |
134 | | - targetClassName="text-darken-7 dark:text-lighten-6 w-full truncate" |
135 | | - content={ |
136 | | - <div className="p-5" style={{ maxHeight: 500, maxWidth: 400 }}> |
137 | | - <MarkdownViewer markdown={description} /> |
138 | | - </div> |
139 | | - } |
140 | | - /> |
141 | | - )} |
| 53 | + <Property node={schemaNode} onGoToRef={onGoToRef} /> |
| 54 | + {description && <Description value={description} />} |
142 | 55 | </div> |
143 | 56 |
|
144 | | - {validationCount ? ( |
145 | | - <Popover |
146 | | - boundary="window" |
147 | | - interactionKind="hover" |
148 | | - content={ |
149 | | - <div className="p-5" style={{ maxHeight: 500, maxWidth: 400 }}> |
150 | | - {map(Object.keys(nodeValidations), (key, index) => { |
151 | | - const validation = nodeValidations[key]; |
152 | | - |
153 | | - let elem = null; |
154 | | - if (Array.isArray(validation)) { |
155 | | - elem = validation.map((v, i) => ( |
156 | | - <div key={i} className="mt-1 mr-1 flex items-center"> |
157 | | - <div className="px-1 bg-gray-2 dark:bg-gray-8 font-bold text-sm rounded">{String(v)}</div> |
158 | | - {i < validation.length - 1 ? <div>,</div> : null} |
159 | | - </div> |
160 | | - )); |
161 | | - } else if (typeof validation === 'object') { |
162 | | - elem = ( |
163 | | - <div className="m-1 px-1 bg-gray-2 dark:bg-gray-8 font-bold text-sm rounded" key={index}> |
164 | | - {'{...}'} |
165 | | - </div> |
166 | | - ); |
167 | | - } else { |
168 | | - elem = ( |
169 | | - <div className="m-1 px-1 bg-gray-2 dark:bg-gray-8 font-bold text-sm rounded" key={index}> |
170 | | - {JSON.stringify(validation)} |
171 | | - </div> |
172 | | - ); |
173 | | - } |
174 | | - |
175 | | - return ( |
176 | | - <div key={index} className="py-1 flex items-baseline"> |
177 | | - <div className="font-medium pr-2 w-24">{key}:</div> |
178 | | - <div className="flex-1 flex flex-wrap text-center">{elem}</div> |
179 | | - </div> |
180 | | - ); |
181 | | - })} |
182 | | - </div> |
183 | | - } |
184 | | - target={requiredElem} |
185 | | - /> |
186 | | - ) : ( |
187 | | - requiredElem |
188 | | - )} |
189 | | - {rowRendererRight && <div className="ml-2">{rowRendererRight(node)}</div>} |
| 57 | + <Validations |
| 58 | + required={!!schemaNode.required} |
| 59 | + validations={{ |
| 60 | + ...('annotations' in schemaNode && |
| 61 | + schemaNode.annotations.default && { default: schemaNode.annotations.default }), |
| 62 | + ...('validations' in schemaNode && schemaNode.validations), |
| 63 | + }} |
| 64 | + /> |
190 | 65 | </div> |
191 | 66 | </div> |
192 | 67 | ); |
|
0 commit comments