diff --git a/scopes/api-reference/renderers/api-node-details/api-node-details.tsx b/scopes/api-reference/renderers/api-node-details/api-node-details.tsx index ac3a4e94bf35..039020b8f372 100644 --- a/scopes/api-reference/renderers/api-node-details/api-node-details.tsx +++ b/scopes/api-reference/renderers/api-node-details/api-node-details.tsx @@ -1,6 +1,6 @@ import React, { useState, useRef, useEffect, useCallback } from 'react'; import { H6 } from '@teambit/documenter.ui.heading'; -import { CodeEditor } from '@teambit/code.ui.code-editor'; +import Editor from '@monaco-editor/react'; import { useLocation } from '@teambit/base-react.navigation.link'; import { defaultCodeEditorOptions } from '@teambit/api-reference.utils.code-editor-options'; import classnames from 'classnames'; @@ -10,8 +10,6 @@ import { APIRefQueryParams } from '@teambit/api-reference.hooks.use-api-ref-url' import { useNavigate } from 'react-router-dom'; import { APINode } from '@teambit/api-reference.models.api-reference-model'; import { SchemaNodesIndex } from '@teambit/api-reference.renderers.schema-nodes-index'; -import { OnMount, Monaco } from '@monaco-editor/react'; -import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import styles from './api-node-details.module.scss'; @@ -44,41 +42,33 @@ export function APINodeDetails({ const routerLocation = useLocation(); const query = useQuery(); const navigate = useNavigate(); - - const signatureEditorRef = useRef(); - const signatureMonacoRef = useRef(); - - const exampleEditorRef = useRef(); - const exampleMonacoRef = useRef(); - + const editorRef = useRef(); + const monacoRef = useRef(); const routeToAPICmdId = useRef(null); const apiUrlToRoute = useRef(null); - const hoverProviderDispose = useRef(); - const rootRef = useRef() as React.MutableRefObject; const apiRef = useRef(null); - - const signatureContainerRef = useRef(null); - const exampleContainerRef = useRef(null); - - const [signatureHeight, setSignatureHeight] = useState(); - const [exampleHeight, setExampleHeight] = useState(); - - const [containerSize] = useState<{ width?: number; height?: number }>({ + const currentQueryParams = query.toString(); + const [containerSize, setContainerSize] = useState<{ width?: number; height?: number }>({ width: undefined, height: undefined, }); - - const currentQueryParams = query.toString(); - const signatureHeightStyle = (!!signatureHeight && `calc(${signatureHeight} + 16px)`) || '250px'; - const exampleHeightStyle = (!!exampleHeight && `calc(${exampleHeight} + 16px)`) || '250px'; - const indexHidden = (containerSize.width ?? 0) < INDEX_THRESHOLD_WIDTH; const example = (doc?.tags || []).find((tag) => tag.tagName === 'example'); const comment = doc?.comment; const signature = displaySignature || defaultSignature; + /** + * @HACK + * Make Monaco responsive + * default line height: 18px; + * totalHeight: (no of lines * default line height) + */ + const exampleHeight = (example?.comment?.split('\n').length || 0) * 18; + const defaultSignatureHeight = 36 + ((signature?.split('\n').length || 0) - 1) * 18; + + const [signatureHeight, setSignatureHeight] = useState(defaultSignatureHeight); const [isMounted, setIsMounted] = useState(false); const getAPINodeUrl = useCallback((queryParams: APIRefQueryParams) => { @@ -107,158 +97,54 @@ export function APINodeDetails({ useEffect(() => { if (isMounted && signature) { - signatureMonacoRef.current?.languages.typescript.typescriptDefaults.setCompilerOptions({ - jsx: signatureMonacoRef.current.languages.typescript.JsxEmit.Preserve, - target: signatureMonacoRef.current.languages.typescript.ScriptTarget.ES2020, + monacoRef.current.languages.typescript.typescriptDefaults.setCompilerOptions({ + jsx: monacoRef.current.languages.typescript.JsxEmit.Preserve, + target: monacoRef.current.languages.typescript.ScriptTarget.ES2020, esModuleInterop: true, }); - signatureMonacoRef.current?.languages.typescript.typescriptDefaults.setDiagnosticsOptions({ + ``; + monacoRef.current.languages.typescript.typescriptDefaults.setDiagnosticsOptions({ noSemanticValidation: true, noSyntaxValidation: true, }); - - routeToAPICmdId.current = - signatureEditorRef.current?.addCommand(0, () => { - apiUrlToRoute.current && navigate(apiUrlToRoute.current); - }) ?? null; - + const container = editorRef.current.getDomNode(); + editorRef.current.onDidContentSizeChange(({ contentHeight }) => { + if (container && isMounted && signature) { + const updatedHeight = Math.min(200, contentHeight + 18); + setSignatureHeight(updatedHeight); + } + }); + routeToAPICmdId.current = editorRef.current.addCommand(0, () => { + apiUrlToRoute.current && navigate(apiUrlToRoute.current); + }); if (!hoverProviderDispose.current) { - hoverProviderDispose.current = signatureMonacoRef.current?.languages.registerHoverProvider('typescript', { + hoverProviderDispose.current = monacoRef.current.languages.registerHoverProvider('typescript', { provideHover: hoverProvider, }); } } }, [isMounted]); - const getDisplayedLineCount = (editorInstance, containerWidth) => { - if (!signatureMonacoRef.current) return 0; - - const model = editorInstance.getModel(); - - if (!model) { - return 0; - } - - const lineCount = model.getLineCount(); - - let displayedLines = 0; - - const lineWidth = editorInstance.getOption(signatureMonacoRef.current.editor.EditorOption.wordWrapColumn); - const fontWidthApproximation = 8; - - for (let lineNumber = 1; lineNumber <= lineCount; lineNumber += 1) { - const line = model.getLineContent(lineNumber); - const length = line.length || 1; - const lineFitsContainer = length * fontWidthApproximation <= containerWidth; - const wrappedLineCount = (lineFitsContainer ? 1 : Math.ceil(length / lineWidth)) || 1; - displayedLines += wrappedLineCount; - } - - return displayedLines; - }; - - const updateEditorHeight = - ( - setHeight: React.Dispatch>, - editorRef: React.MutableRefObject - ) => - () => { - if (!signatureMonacoRef.current) return undefined; - - const editor = editorRef.current; - - if (!editor) { - return undefined; - } - - const lineHeight = editor.getOption(signatureMonacoRef.current.editor.EditorOption.lineHeight); - - const paddingTop = editor.getOption(signatureMonacoRef.current.editor.EditorOption.padding)?.top || 0; - const paddingBottom = editor.getOption(signatureMonacoRef.current.editor.EditorOption.padding)?.bottom || 0; - const glyphMargin = editor.getOption(signatureMonacoRef.current.editor.EditorOption.glyphMargin); - const lineNumbers = editor.getOption(signatureMonacoRef.current.editor.EditorOption.lineNumbers); - - const glyphMarginHeight = glyphMargin ? lineHeight : 0; - const lineNumbersHeight = lineNumbers.renderType !== 0 ? lineHeight : 0; - - const containerWidth = editor.getLayoutInfo().contentWidth; - const displayedLines = getDisplayedLineCount(editor, containerWidth); - - const contentHeight = - displayedLines * lineHeight + paddingTop + paddingBottom + glyphMarginHeight + lineNumbersHeight; - - const domNode = editor.getDomNode()?.parentElement; - - if (!domNode) { - return undefined; - } - - domNode.style.height = `${contentHeight}px`; - signatureEditorRef.current?.layout(); - setHeight(() => `${contentHeight}px`); - return undefined; - }; - - const handleEditorDidMount: ( - monacoRef: React.MutableRefObject, - editorRef: React.MutableRefObject, - containerRef: React.MutableRefObject, - setHeight: React.Dispatch> - ) => OnMount = (monacoRef, editorRef, containerRef, setHeight) => (editor, _monaco) => { - /** - * disable syntax check - * ts cant validate all types because imported files aren't available to the editor - */ - monacoRef.current = _monaco; - editorRef.current = editor; - - monacoRef.current.languages?.typescript?.typescriptDefaults?.setDiagnosticsOptions({ - noSemanticValidation: true, - noSyntaxValidation: true, - }); - - monaco.editor.defineTheme('bit', { - base: 'vs-dark', - inherit: true, - rules: [], - colors: { - 'scrollbar.shadow': '#222222', - 'diffEditor.insertedTextBackground': '#1C4D2D', - 'diffEditor.removedTextBackground': '#761E24', - 'editor.selectionBackground': '#5A5A5A', - 'editor.overviewRulerBorder': '#6a57fd', - 'editor.lineHighlightBorder': '#6a57fd', - }, + const handleSize = useCallback(() => { + setContainerSize({ + width: rootRef.current.offsetWidth, + height: rootRef.current.offsetHeight, }); - monaco.editor.setTheme('bit'); - editor.onDidChangeModelDecorations(updateEditorHeight(setHeight, editorRef)); - editor.onDidChangeModelDecorations(updateEditorHeight(setHeight, editorRef)); - const containerElement = containerRef.current; - let resizeObserver: ResizeObserver | undefined; - - if (containerElement) { - resizeObserver = new ResizeObserver(() => { - setTimeout(() => updateEditorHeight(setHeight, editorRef)); - }); - resizeObserver.observe(containerElement); - } - - return () => containerElement && resizeObserver?.unobserve(containerElement); - }; + }, []); useEffect(() => { - updateEditorHeight(setSignatureHeight, signatureEditorRef)(); - updateEditorHeight(setExampleHeight, exampleEditorRef)(); - + if (window) window.addEventListener('resize', handleSize); + // Call handler right away so state gets updated with initial container size + handleSize(); return () => { hoverProviderDispose.current?.dispose(); + if (window) window.removeEventListener('resize', handleSize); setIsMounted(false); }; }, []); - React.useLayoutEffect(() => { - updateEditorHeight(setSignatureHeight, signatureEditorRef)(); - updateEditorHeight(setExampleHeight, exampleEditorRef)(); + useEffect(() => { + handleSize(); }, [rootRef?.current?.offsetHeight, rootRef?.current?.offsetWidth]); return ( @@ -278,53 +164,38 @@ export function APINodeDetails({
- { - signatureMonacoRef.current = _monaco; + beforeMount={(monaco) => { + monacoRef.current = monaco; + }} + onMount={(editor) => { + editorRef.current = editor; + const signatureContent = editorRef.current.getValue(); + const updatedSignatureHeight = 36 + ((signatureContent?.split('\n').length || 0) - 1) * 18; + setIsMounted(true); + setSignatureHeight(updatedSignatureHeight); }} - handleEditorDidMount={handleEditorDidMount( - signatureMonacoRef, - signatureEditorRef, - signatureContainerRef, - setSignatureHeight - )} + theme={'vs-dark'} />
)} {example && example.comment && (
Example
-
- +
diff --git a/scopes/api-reference/renderers/schema-node-member-summary/function-node-summary.module.scss b/scopes/api-reference/renderers/schema-node-member-summary/function-node-summary.module.scss index 243c235480f9..bdbb6843e151 100644 --- a/scopes/api-reference/renderers/schema-node-member-summary/function-node-summary.module.scss +++ b/scopes/api-reference/renderers/schema-node-member-summary/function-node-summary.module.scss @@ -23,7 +23,6 @@ .row { grid-template-columns: 1fr 1fr 2fr; - &.isHovering { background-color: #f4f5f6; cursor: pointer; @@ -45,7 +44,6 @@ .signatureDetails { background-color: var(--bit-bg-heavy, #f4f5f6); - margin-left: 8px; } .returnContainer, diff --git a/scopes/api-reference/renderers/type-ref/type-ref.renderer.tsx b/scopes/api-reference/renderers/type-ref/type-ref.renderer.tsx index c4636780560b..cd958d4d3a57 100644 --- a/scopes/api-reference/renderers/type-ref/type-ref.renderer.tsx +++ b/scopes/api-reference/renderers/type-ref/type-ref.renderer.tsx @@ -64,7 +64,7 @@ function TypeRefComponent(props: APINodeRenderProps) { if (typeArgRenderer) { return ( - + + {typeArg.toString()} {(typeArgs?.length ?? 0) > 1 && index !== (typeArgs?.length ?? 0) - 1 ? ', ' : null} @@ -148,7 +148,7 @@ function getExportedTypeUrlFromAnotherComp({ componentId: ComponentID; selectedAPI: string; }) { - const componentUrl = ComponentUrl.toUrl(componentId, { useLocationOrigin: true }); + const componentUrl = ComponentUrl.toUrl(componentId); const [componentIdUrl, versionQuery] = componentUrl.split('?'); const exportedTypeUrl = `${componentIdUrl}/~api-reference?selectedAPI=${encodeURIComponent( diff --git a/scopes/api-reference/sections/api-reference-page/api-reference-page.tsx b/scopes/api-reference/sections/api-reference-page/api-reference-page.tsx index 52c25494a3a1..4b85b8f44be7 100644 --- a/scopes/api-reference/sections/api-reference-page/api-reference-page.tsx +++ b/scopes/api-reference/sections/api-reference-page/api-reference-page.tsx @@ -4,7 +4,7 @@ import classNames from 'classnames'; import { ComponentContext } from '@teambit/component'; import { H1 } from '@teambit/documenter.ui.heading'; import { useIsMobile } from '@teambit/ui-foundation.ui.hooks.use-is-mobile'; -import { Link } from '@teambit/base-react.navigation.link'; +import { useLocation, Link } from '@teambit/base-react.navigation.link'; import { useQuery } from '@teambit/ui-foundation.ui.react-router.use-query'; import { Collapser } from '@teambit/ui-foundation.ui.buttons.collapser'; import { HoverSplitter } from '@teambit/base-ui.surfaces.split-pane.hover-splitter'; @@ -35,9 +35,7 @@ export function APIRefPage({ host, rendererSlot, className }: APIRefPageProps) { const sidebarOpenness = isSidebarOpen ? Layout.row : Layout.left; const selectedAPIFromUrl = useAPIRefParam('selectedAPI'); - React.useEffect(() => { - window.scrollTo(0, 0); - }, [selectedAPIFromUrl]); + const apiNodes = (apiModel && flatten(Array.from(apiModel.apiByType.values())).sort(sortAPINodes)) || []; const isEmpty = apiNodes.length === 0; @@ -60,7 +58,7 @@ export function APIRefPage({ host, rendererSlot, className }: APIRefPageProps) { (selectedAPINode && `${selectedAPINode?.renderer?.nodeType}/${selectedAPINode?.api.name}`) || apiTree[0]; const SelectedAPIComponent = selectedAPINode && selectedAPINode.renderer.Component; - // const location = useLocation(); + const location = useLocation(); const query = useQuery(); if (loading) { @@ -74,14 +72,18 @@ export function APIRefPage({ host, rendererSlot, className }: APIRefPageProps) { if (!apiModel || isEmpty) { return ; } + const icon = selectedAPINode.renderer.icon; const name = selectedAPINode.api.name; const componentVersionFromUrl = query.get('version'); const filePath = selectedAPINode.api.location.filePath; - const pathname = ComponentUrl.toUrl(component.id, { includeVersion: false, useLocationOrigin: true }); + const pathname = + location?.pathname && window?.location?.hostname?.startsWith('localhost') + ? location?.pathname + : `${ComponentUrl.toUrl(component.id, { includeVersion: false })}/`; const componentUrlWithoutVersion = pathname?.split('~')[0]; - const locationUrl = `${componentUrlWithoutVersion}/~code/${filePath}${ + const locationUrl = `${componentUrlWithoutVersion}~code/${filePath}${ componentVersionFromUrl ? `?version=${componentVersionFromUrl}` : '' }`; diff --git a/scopes/api-reference/utils/code-editor-options/code-editor-options.ts b/scopes/api-reference/utils/code-editor-options/code-editor-options.ts index 4ea7666d42f5..72f32f0f465a 100644 --- a/scopes/api-reference/utils/code-editor-options/code-editor-options.ts +++ b/scopes/api-reference/utils/code-editor-options/code-editor-options.ts @@ -21,7 +21,6 @@ export const defaultCodeEditorOptions: monaco.editor.IStandaloneEditorConstructi enabled: false, }, renderLineHighlight: 'none', - lineHeight: 20, - padding: { top: 8, bottom: 8 }, - cursorBlinking: 'smooth', + lineHeight: 18, + padding: { top: 8 }, }; diff --git a/scopes/code/ui/code-editor/code-editor.tsx b/scopes/code/ui/code-editor/code-editor.tsx index 02fe06f29dbf..b66dd3dc454e 100644 --- a/scopes/code/ui/code-editor/code-editor.tsx +++ b/scopes/code/ui/code-editor/code-editor.tsx @@ -1,6 +1,6 @@ import React from 'react'; import classnames from 'classnames'; -import Editor, { OnMount, BeforeMount } from '@monaco-editor/react'; +import Editor, { OnMount, BeforeMount, OnChange } from '@monaco-editor/react'; import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import { darkMode } from '@teambit/base-ui.theme.dark-theme'; @@ -8,12 +8,13 @@ export type CodeEditorProps = { filePath?: string; fileContent?: string; language?: string; - handleEditorDidMount?: OnMount; - handleEditorBeforeMount?: BeforeMount; - Loader?: React.ReactNode; - options?: monaco.editor.IStandaloneEditorConstructionOptions; - className?: string; height?: string; + className?: string; + options?: monaco.editor.IStandaloneEditorConstructionOptions; + beforeMount?: BeforeMount; + onMount?: OnMount; + onChange?: OnChange; + Loader?: React.ReactNode; }; export const DEFAULT_EDITOR_OPTIONS: monaco.editor.IStandaloneEditorConstructionOptions = { @@ -29,7 +30,7 @@ export const DEFAULT_EDITOR_OPTIONS: monaco.editor.IStandaloneEditorConstruction fixedOverflowWidgets: true, renderLineHighlight: 'none', lineHeight: 20, - padding: { top: 8 }, + padding: { top: 8, bottom: 8 }, hover: { enabled: false }, cursorBlinking: 'smooth', }; @@ -48,8 +49,9 @@ export function CodeEditor({ fileContent, filePath, language, - handleEditorBeforeMount, - handleEditorDidMount, + beforeMount, + onMount, + onChange, Loader, options, className, @@ -67,10 +69,11 @@ export function CodeEditor({ value={fileContent || undefined} language={language || defaultLang} height={height || '100%'} - onMount={handleEditorDidMount} + onMount={onMount} + beforeMount={beforeMount} + onChange={onChange} className={classnames(darkMode, className)} theme={'vs-dark'} - beforeMount={handleEditorBeforeMount} options={options || DEFAULT_EDITOR_OPTIONS} loading={Loader} />