feat: show additional info for autocomplete results (#28564)

## Description
Adds another popover against a selected autocomplete result that shows
relevant information beforehand to the user. Info includes a brief
description, example if applicable and a link to docs.
>
#### PR fixes following issue(s)
Fixes #23381 
>
#### Media
<img width="580" alt="Screenshot 2023-11-07 at 13 00 25"
src="https://github.com/appsmithorg/appsmith/assets/32433245/3a9d254d-29cf-4ad4-8033-c4763431a215">

#### Type of change
- New feature (non-breaking change which adds functionality)
>
## Testing
>
#### How Has This Been Tested?
- [x] Manual
- [x] Cypress
>
>
#### Test Plan
> Add Testsmith test cases links that relate to this PR
>
>
#### Issues raised during DP testing
> Link issues raised during DP testing for better visiblity and tracking
(copy link from comments dropped on this PR)
>
## Checklist:
#### Dev activity
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my own code
- [x] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] PR is being merged under a feature flag


#### QA activity:
- [ ] [Speedbreak
features](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#speedbreakers-)
have been covered
- [ ] Test plan covers all impacted features and [areas of
interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans#areas-of-interest-)
- [ ] Test plan has been peer reviewed by project stakeholders and other
QA members
- [ ] Manually tested functionality on DP
- [ ] We had an implementation alignment call with stakeholders post QA
Round 2
- [ ] Cypress test cases have been added and approved by SDET/manual QA
- [ ] Added `Test Plan Approved` label after Cypress tests were reviewed
- [ ] Added `Test Plan Approved` label after JUnit tests were reviewed

---------

Co-authored-by: Aishwarya UR <aishwarya@appsmith.com>
This commit is contained in:
arunvjn 2023-11-10 13:36:10 +05:30 committed by GitHub
parent f99ab85ee0
commit 863dbddb7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 351 additions and 107 deletions

View File

@ -97,4 +97,12 @@ describe("Autocomplete tests for setters", () => {
);
agHelper.AssertElementAbsence(locators._evalValuePopover);
});
it("4. function description tooltip shows up", () => {
entityExplorer.DragDropWidgetNVerify(draggableWidgets.BUTTON, 100, 100);
entityExplorer.SelectEntityByName("Button1");
propPane.EnterJSContext("onClick", "{{showAlert", true, false);
agHelper.GetElementsNAssertTextPresence(locators._hints, "showAlert");
agHelper.AssertElementExist(locators._tern_doc);
});
});

View File

@ -201,6 +201,7 @@ export class CommonLocators {
`//p[text()='${fieldName}']/parent::div//following-sibling::div//input[@type='checkbox']`;
_deployedPage = `.t--page-switch-tab`;
_hints = "ul.CodeMirror-hints li";
_tern_doc = ".t--tern-doc";
_argHintFnName = ".CodeMirror-Tern-tooltip .CodeMirror-Tern-fname";
_cancelActionExecution = ".t--cancel-action-button";
_widgetPane = "[data-testid='widget-sidebar-scrollable-wrapper']";

View File

@ -19,16 +19,65 @@ export const entityDefinitions = {
) {
return {
...generatedTypeDef,
"!doc":
"A global object that provides access to information and functionalities within an application",
"!url": "https://docs.appsmith.com/reference/appsmith-framework",
store: {
...(generatedTypeDef.store as Def),
"!doc":
"Object to access any app-level data or temporary state that is stored on the user's browser",
"!url":
"https://docs.appsmith.com/reference/appsmith-framework/context-object#store-object",
},
user: {
...(generatedTypeDef.user as Def),
"!doc":
"Object that contains the data of the currently authenticated user.",
"!url":
"https://docs.appsmith.com/reference/appsmith-framework/context-object#user-object",
},
URL: {
...(generatedTypeDef.URL as Def),
"!doc": "Object containing all the attributes of the current URL",
"!url":
"https://docs.appsmith.com/reference/appsmith-framework/context-object#url-object",
},
theme: {
...(generatedTypeDef.theme as Def),
"!doc":
"Object containing the details of the theme properties applied to the application",
"!url":
"https://docs.appsmith.com/reference/appsmith-framework/context-object#theme-object",
},
mode: {
"!type": generatedTypeDef.mode as Def,
"!doc":
"An enum that contains whether the app runs in view or edit mode. It takes the values VIEW or EDIT",
"!url":
"https://docs.appsmith.com/reference/appsmith-framework/context-object#mode-enum",
},
geolocation: {
...generatedTypeDef.geolocation,
"!doc":
"The user's geo location information. Only available when requested",
"Object containing functions that allow you to retrieve the current user's location and the coordinates received from the user's device using the Geolocation API.",
"!url":
"https://docs.appsmith.com/reference/appsmith-framework/context-object#geolocation-object",
getCurrentPosition:
"fn(onSuccess: fn() -> void, onError: fn() -> void, options: object) -> void",
watchPosition: "fn(options: object) -> void",
clearWatch: "fn() -> void",
getCurrentPosition: {
"!type":
"fn(onSuccess: fn() -> void, onError: fn() -> void, options: object) -> +Promise|void",
"!url":
"https://docs.appsmith.com/reference/appsmith-framework/context-object#geolocationgetcurrentposition",
},
watchPosition: {
"!type": "fn(options: object) -> void",
"!url":
"https://docs.appsmith.com/reference/appsmith-framework/context-object#geolocationwatchposition",
},
clearWatch: {
"!type": "fn() -> +Promise|void",
"!url":
"https://docs.appsmith.com/reference/appsmith-framework/context-object#geolocationclearwatch",
},
},
};
}
@ -36,29 +85,59 @@ export const entityDefinitions = {
},
ACTION: (entity: ActionEntity, extraDefsToDefine: ExtraDef) => {
const dataDef = generateTypeDef(entity.data, extraDefsToDefine);
let responseMetaDef = generateTypeDef(
entity.responseMeta,
extraDefsToDefine,
);
let data: Def = {
"!doc": "The response of the action",
if (_.isString(responseMetaDef)) {
responseMetaDef = {
"!type": responseMetaDef,
};
}
let dataCustomDef: Def = {
"!doc":
"A read-only property that contains the response body from the last successful execution of this query.",
"!url":
"https://docs.appsmith.com/reference/appsmith-framework/query-object#data-array",
};
if (_.isString(dataDef)) {
data["!type"] = dataDef;
dataCustomDef["!type"] = dataDef;
} else {
data = { ...data, ...dataDef };
dataCustomDef = { ...dataCustomDef, ...dataDef };
}
return {
"!doc":
"Actions allow you to connect your widgets to your backend data in a secure manner.",
"Object that contains the properties required to run queries and access the query data.",
"!url":
"https://docs.appsmith.com/reference/appsmith-framework/query-object",
isLoading: "bool",
data,
responseMeta: {
"!doc": "The response meta of the action",
"!type": "?",
isLoading: {
"!type": "bool",
"!doc":
"Boolean that indicates whether the query is currently being executed.",
},
data: dataCustomDef,
responseMeta: {
"!doc":
"Object that contains details about the response, such as the status code, headers, and other relevant information related to the query's execution and the server's response.",
"!url":
"https://docs.appsmith.com/reference/appsmith-framework/query-object#responsemeta-object",
...responseMetaDef,
},
run: {
"!type": "fn(params: ?) -> +Promise",
"!url":
"https://docs.appsmith.com/reference/appsmith-framework/query-object#queryrun",
"!doc": "Executes the query with the given parameters.",
},
clear: {
"!type": "fn() -> +Promise",
"!url":
"https://docs.appsmith.com/reference/appsmith-framework/query-object#queryclear",
"!doc": "Clears the query data.",
},
run: "fn(params: ?) -> +Promise",
clear: "fn() -> +Promise",
};
},
};
@ -99,57 +178,89 @@ export const GLOBAL_DEFS = {
export const GLOBAL_FUNCTIONS = {
"!name": "DATA_TREE.APPSMITH.FUNCTIONS",
navigateTo: {
"!doc": "Action to navigate the user to another page or url",
"!url":
"https://docs.appsmith.com/reference/appsmith-framework/widget-actions/navigate-to",
"!doc":
"Enables navigation between the internal pages of the App or to an external URL.",
"!type":
"fn(pageNameOrUrl: string, params: {}, target?: string) -> +Promise",
},
showAlert: {
"!doc": "Show a temporary notification style message to the user",
"!url":
"https://docs.appsmith.com/reference/appsmith-framework/widget-actions/show-alert",
"!doc":
"Displays a temporary toast-style alert message to the user for precisely 5 seconds. The duration of the alert message can't be modified.",
"!type": "fn(message: string, style: string) -> +Promise",
},
showModal: {
"!doc": "Open a modal",
"!url":
"https://docs.appsmith.com/reference/appsmith-framework/widget-actions/show-modal",
"!doc":
"Opens an existing Modal widget and bring it into focus on the page",
"!type": "fn(modalName: string) -> +Promise",
},
closeModal: {
"!url":
"https://docs.appsmith.com/reference/appsmith-framework/widget-actions/close-modal",
"!doc": "Close a modal",
"!type": "fn(modalName: string) -> +Promise",
},
storeValue: {
"!doc": "Store key value data locally",
"!type": "fn(key: string, value: any) -> +Promise",
"!url":
"https://docs.appsmith.com/reference/appsmith-framework/widget-actions/store-value",
"!doc":
"Stores the data in the browser's local storage as key-value pairs that represent storage objects and can be later accessed anywhere in the application via <code>appsmith.store</code>.",
"!type": "fn(key: string, value: any, persist?: bool) -> +Promise",
},
removeValue: {
"!url":
"https://docs.appsmith.com/reference/appsmith-framework/widget-actions/remove-value",
"!doc": "Remove key value data locally",
"!type": "fn(key: string) -> +Promise",
},
clearStore: {
"!url":
"https://docs.appsmith.com/reference/appsmith-framework/widget-actions/clear-store",
"!doc": "Clear all key value data locally",
"!type": "fn() -> +Promise",
},
download: {
"!doc": "Download anything as a file",
"!url":
"https://docs.appsmith.com/reference/appsmith-framework/widget-actions/download",
"!doc":
"Download any data as a file, leveraging the capabilities of the downloadjs library.",
"!type":
"fn(data: string|+Blob, fileName: string, fileType?: string) -> +Promise",
},
copyToClipboard: {
"!doc": "Copy text to clipboard",
"!url":
"https://docs.appsmith.com/reference/appsmith-framework/widget-actions/copy-to-clipboard",
"!doc": "Copies the given text to clipboard",
"!type": "fn(data: string, options: object) -> +Promise",
},
resetWidget: {
"!doc": "Reset widget values",
"!url":
"https://docs.appsmith.com/reference/appsmith-framework/widget-actions/reset-widget",
"!doc":
"Resets a widget to its default state. All user input changes are reverted and its properties' default values are applied.",
"!type": "fn(widgetName: string, resetChildren: bool) -> +Promise",
},
setInterval: {
"!doc": "Execute triggers at a given interval",
"!url":
"https://docs.appsmith.com/reference/appsmith-framework/widget-actions/intervals-time-events",
"!doc": "Executes a function at a given interval",
"!type":
"fn(callback: fn() -> void, interval: number, id?: string) -> number",
},
clearInterval: {
"!url":
"https://docs.appsmith.com/reference/appsmith-framework/widget-actions/clear-interval",
"!doc": "Stop executing a setInterval with id",
"!type": "fn(id: string) -> void",
},
postWindowMessage: {
"!url":
"https://docs.appsmith.com/reference/appsmith-framework/widget-actions/post-message",
"!doc":
"Establish cross-origin communication between Window objects/page and iframes",
"!type": "fn(message: unknown, source: string, targetOrigin: string)",
@ -180,4 +291,55 @@ export const getPropsForJSActionEntity = ({
return properties;
};
export const ternDocsInfo: Record<string, any> = {
showAlert: {
exampleArgs: [
"'This is a success message', 'success'",
"'This is an error message', 'error'",
],
},
showModal: {
exampleArgs: ["'Modal1'"],
},
closeModal: {
exampleArgs: ["'Modal1'"],
},
navigateTo: {
exampleArgs: [
"'Page1', { id: 1 }",
"'https://appsmith.com', {}, 'NEW_WINDOW'",
],
},
copyToClipboard: {
exampleArgs: ["'Hello'"],
},
download: {
exampleArgs: [
"'Hello World', 'hello.txt', 'text/plain'",
"FilePicker1.files[0].data, 'data.json'",
],
},
storeValue: {
exampleArgs: ["'key', 'value'"],
},
removeValue: {
exampleArgs: ["'key'"],
},
clearStore: {
exampleArgs: [""],
},
resetWidget: {
exampleArgs: ["'Table1', false"],
},
setInterval: {
exampleArgs: ["() => showAlert('Hello'), 1000, 'id'"],
},
clearInterval: {
exampleArgs: ["'id'"],
},
postWindowMessage: {
exampleArgs: ["message, 'Iframe1', '*'"],
},
};
export type EntityDefinitionsOptions = keyof typeof entityDefinitions;

View File

@ -37,6 +37,7 @@ export const CodeEditorColors = {
NUMBER: "#555",
COMMENT: "var(--ads-v2-color-gray-400)",
FUNCTION_ARGS: "hsl(288, 44%, 44%)",
TOOLTIP_FN_ARGS: "#DB6E33",
};
export const EditorWrapper = styled.div<{

View File

@ -245,12 +245,12 @@
"setTimeout": {
"!type": "fn(f: fn(), ms: number) -> number",
"!url": "https://developer.mozilla.org/en/docs/DOM/window.setTimeout",
"!doc": "Calls a function or executes a code snippet after specified delay."
"!doc": "Calls a function or executes a code snippet after specified delay. Returns a number that can be used to clear the timeout with <code>clearTimeout()</code>."
},
"clearTimeout": {
"!type": "fn(timeout: number)",
"!type": "fn(timerId: number)",
"!url": "https://developer.mozilla.org/en/docs/DOM/window.clearTimeout",
"!doc": "Clears the delay set by window.setTimeout()."
"!doc": "Clears the function scheduled to run by <code>setTimeout()</code>."
},
"Response": {
"!type": "fn()",

View File

@ -1461,8 +1461,7 @@
"!url": "https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/JSON/stringify",
"!doc": "Convert a value to JSON, optionally replacing values if a replacer function is specified, or optionally including only the specified properties if a replacer array is specified."
},
"!url": "https://developer.mozilla.org/en-US/docs/JSON",
"!doc": "JSON (JavaScript Object Notation) is a data-interchange format. It closely resembles a subset of JavaScript syntax, although it is not a strict subset. (See JSON in the JavaScript Reference for full details.) It is useful when writing any kind of JavaScript-based application, including websites and browser extensions. For example, you might store user information in JSON format in a cookie, or you might store extension preferences in JSON in a string-valued browser preference."
"!url": "https://developer.mozilla.org/en-US/docs/JSON"
},
"ArrayBuffer": {
"!type": "fn(length: number)",

View File

@ -2,6 +2,7 @@ import { createGlobalStyle } from "styled-components";
import type { EditorTheme } from "components/editorComponents/CodeEditor/EditorConfig";
import type { Theme } from "constants/DefaultTheme";
import { LINT_TOOLTIP_JUSTIFIED_LEFT_CLASS } from "components/editorComponents/CodeEditor/constants";
import { CodeEditorColors } from "components/editorComponents/CodeEditor/styledComponents";
export const CodemirrorHintStyles = createGlobalStyle<{
editorTheme: EditorTheme;
@ -14,15 +15,13 @@ export const CodemirrorHintStyles = createGlobalStyle<{
z-index: 20;
overflow: hidden;
list-style: none;
margin-top: ${(props) => props.theme.spaces[3]}px;
padding: 0px 0px;
font-family: monospace;
padding: 0px;
font-family: ${(props) => props.theme.fonts.code};
max-height: 25em;
overflow-y: auto;
background: var(--ads-v2-color-bg);
box-shadow: var(--ads-v2-shadow-popovers);
border: 1px solid var(--ads-v2-color-border);
border-radius: var(--ads-v2-border-radius);
}
.CodeMirror-hint {
@ -38,7 +37,6 @@ export const CodemirrorHintStyles = createGlobalStyle<{
letter-spacing: -0.24px;
&:hover {
background: var(--ads-v2-color-bg-subtle);
border-radius: 0px;
color: var(--ads-v2-color-fg);
&:after {
color: var(--ads-v2-color-fg);
@ -82,9 +80,6 @@ export const CodemirrorHintStyles = createGlobalStyle<{
font-family: ${(props) => props.theme.fonts.text};
font-size:14px;
margin: 0 4px;
&:hover {
border-radius: var(--ads-v2-border-radius);
}
&.CodeMirror-hint-active {
.magic {
path {
@ -172,7 +167,6 @@ export const CodemirrorHintStyles = createGlobalStyle<{
bottom: 6px;
height: 12px;
width: 12px;
border-radius: var(--ads-v2-border-radius);
font-size: 10px;
line-height: 12px;
font-weight: normal;
@ -238,7 +232,6 @@ export const CodemirrorHintStyles = createGlobalStyle<{
}
li.CodeMirror-hint-active {
background-color: var(--ads-v2-color-bg-muted);
border-radius: var(--ads-v2-border-radius);
color: var(--ads-v2-color-fg);
&:after {
color: var(--ads-v2-color-fg);
@ -261,22 +254,43 @@ export const CodemirrorHintStyles = createGlobalStyle<{
background: var(--ads-v2-color-bg);
box-shadow: var(--ads-v2-shadow-popovers);
border: 1px solid var(--ads-v2-color-border);
border-radius: var(--ads-v2-border-radius);
z-index: 15px;
font-weight: 500;
max-width: none;
white-space: nowrap;
.CodeMirror-Tern-fname {
color: #304EAA;
color: ${CodeEditorColors.KEYWORD};
}
.CodeMirror-Tern-farg {
color: #DB6E33;
color: ${CodeEditorColors.TOOLTIP_FN_ARGS};
&.CodeMirror-Tern-farg-current {
color: #DB6E33;
color: ${CodeEditorColors.TOOLTIP_FN_ARGS};
font-weight: 600;
}
}
.CodeMirror-Tern-type {
color: #364252;
}
&.CodeMirror-Tern-hint-doc {
display: block;
background: var(--ads-v2-color-bg);
box-shadow: var(--ads-v2-shadow-popovers);
border: 1px solid var(--ads-v2-color-border);
color: var(--ads-v2-color-fg);
max-height: 150px;
max-width: 350px;
overflow: auto;
font-size: 11px;
padding: 0 !important;
.doc-link > span {
font-size: var(--ads-v2-font-size-2);
}
code {
background: var(--ads-v2-color-bg-subtle);
padding: 2px 4px;
border-radius: var(--ads-v2-border-radius);
}
}
}
}
@ -299,7 +313,6 @@ export const CodemirrorHintStyles = createGlobalStyle<{
bottom: 6px;
height: 12px;
width: 12px;
border-radius: var(--ads-v2-border-radius);
font-size: 10px;
line-height: 12px;
font-weight: normal;
@ -339,26 +352,9 @@ export const CodemirrorHintStyles = createGlobalStyle<{
}
}
.CodeMirror-Tern-hint-doc {
display: none;
&.visible {
display: block;
background-color: var(--ads-v2-color-bg) !important;
color: var(--ads-v2-color-fg) !important;
max-height: 150px;
width: 250px;
font-size: 12px;
padding: 5px !important;
border: 1px solid !important;
border: 1px solid var(--ads-v2-color-border) !important;
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.12) !important;
overflow: scroll;
}
}
.CodeMirror-lint-tooltip {
&& {
border: 1px solid var(--ads-v2-color-border) !important;
border-radius: var(--ads-v2-border-radius);
background: var(--ads-v2-color-bg) !important;
box-shadow: 0px 12px 28px -6px rgba(0, 0, 0, 0.32);
padding: 4px;

View File

@ -10,27 +10,25 @@ export const createObjectPeekData = (
parentKey: string,
) => {
Object.keys(defs).forEach((key: string) => {
if (key.indexOf("!") === -1) {
const childKeyPathArray = [parentKey, key];
if (isObject(defs[key])) {
if (Object.keys(defs[key]).length > 0) {
peekData[key] = {};
const result = createObjectPeekData(
defs[key],
data[key],
peekData[key],
key,
);
_.set(peekData, childKeyPathArray, result.peekData);
} else {
peekData[key] = data[key];
}
} else {
peekData[key] = isTernFunctionDef(defs[key])
? // eslint-disable-next-line @typescript-eslint/no-empty-function
function () {} // tern inference required here
: data[key];
}
if (key.startsWith("!")) return;
const childKeyPathArray = [parentKey, key];
if (
isObject(defs[key]) &&
Object.keys(defs[key]).filter((k) => !k.startsWith("!")).length > 0
) {
peekData[key] = {};
const result = createObjectPeekData(
defs[key],
data[key],
peekData[key],
key,
);
_.set(peekData, childKeyPathArray, result.peekData);
} else {
peekData[key] = isTernFunctionDef(defs[key])
? // eslint-disable-next-line @typescript-eslint/no-empty-function
function () {} // tern inference required here
: data[key];
}
});
return { peekData };

View File

@ -313,6 +313,9 @@ class BlockAsyncFnsInDataFieldRule implements AutocompleteRule {
"clearTimeout",
"setInterval",
"clearInterval",
"postWindowMessage",
"windowMessageListener",
"watchPosition",
];
computeScore(
completion: Completion<TernCompletionResult>,

View File

@ -20,6 +20,7 @@ import {
} from "../getCodeMirrorNamespace";
import AnalyticsUtil from "utils/AnalyticsUtil";
import { findIndex, isString } from "lodash";
import { renderTernTooltipContent } from "./ternDocTooltip";
const bigDoc = 250;
const cls = "CodeMirror-Tern-";
@ -558,32 +559,33 @@ class CodeMirrorTernService {
CodeMirror.on(
obj,
"select",
(cur: { data: { doc: string } }, node: any) => {
(cur: Completion<TernCompletionResult>, node: any) => {
this.active = cur;
this.remove(tooltip);
const content = cur.data.doc;
if (content) {
tooltip = this.makeTooltip(
node.parentNode.getBoundingClientRect().right + window.pageXOffset,
node.getBoundingClientRect().top + window.pageYOffset,
content,
cm,
);
tooltip.className += " " + cls + "hint-doc";
CodeMirror.on(
cm,
"keyup",
(cm: CodeMirror.Editor, keyboardEvent: KeyboardEvent) => {
if (
keyboardEvent.code === "Space" &&
keyboardEvent.ctrlKey &&
tooltip
) {
tooltip.className += " visible";
}
},
);
}
if (!content) return;
const docTooltipContainer = this.elt("div", "flex flex-col pb-1");
renderTernTooltipContent(docTooltipContainer, cur);
tooltip = this.makeTooltip(
node.parentNode.getBoundingClientRect().right + window.pageXOffset,
node.getBoundingClientRect().top + window.pageYOffset + 2,
docTooltipContainer,
cm,
cls + "hint-doc",
);
CodeMirror.on(
cm,
"keyup",
(cm: CodeMirror.Editor, keyboardEvent: KeyboardEvent) => {
if (
keyboardEvent.code === "Space" &&
keyboardEvent.ctrlKey &&
tooltip
) {
tooltip.className += " visible";
}
},
);
},
);
resolve(obj);

View File

@ -0,0 +1,74 @@
import React from "react";
import ReactDOM from "react-dom";
import { ternDocsInfo } from "@appsmith/utils/autocomplete/EntityDefinitions";
import type { Completion, TernCompletionResult } from "./CodemirrorTernService";
import { CodeEditorColors } from "components/editorComponents/CodeEditor/styledComponents";
import { Link } from "design-system";
export function renderTernTooltipContent(
element: HTMLElement,
completion: Completion<TernCompletionResult>,
) {
ReactDOM.render(<TernDocToolTip completion={completion} />, element);
}
export function TernDocToolTip(props: {
completion: Completion<TernCompletionResult>;
}) {
const { completion } = props;
const {
data: { doc, url },
displayText,
} = completion;
if (!doc || !displayText) return null;
const examples =
displayText in ternDocsInfo ? ternDocsInfo[displayText].exampleArgs : null;
return (
<div className="flex flex-col pb-1 t--tern-doc">
<div className="flex items-center justify-between px-2 p-1 border-b border-mercury text-sm font-semibold">
{displayText}
{url && (
<Link
className="text-xs doc-link"
kind="primary"
target="_blank"
to={url}
>
[docs]
</Link>
)}
</div>
<pre
className="px-2 p-1 text-xs whitespace-normal"
dangerouslySetInnerHTML={{ __html: doc }}
/>
{examples && (
<div className="flex px-2 py-[2px] text-xs font-semibold">Example</div>
)}
{examples && (
<div className="px-2">
{examples.map((example: string) => {
const fnName = displayText;
const args = example;
return (
<span
className="flex items-center justify-start py-[2px] text-xs whitespace-nowrap"
key={example}
style={{ color: CodeEditorColors.KEYWORD }}
>
{`${fnName}(`}
<span style={{ color: CodeEditorColors.TOOLTIP_FN_ARGS }}>
{args}
</span>
{")"}
</span>
);
})}
</div>
)}
</div>
);
}