diff --git a/CHANGELOG.md b/CHANGELOG.md index 6446084df96..f33aba07049 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Updated `EuiText`s `color` prop to accept `inherit` and custom colors. Updated the `size` prop to accept `relative` ([#4663](https://github.com/elastic/eui/pull/4663)) - Updated `EuiText`s `blockquote` font-size/line-height to match the base font-size/line-height which is the same as paragraphs ([#4663](https://github.com/elastic/eui/pull/4663)) - Added `markdownFormatProps` prop to `EuiMarkdownEditor` to extend the props passed to the rendered `EuiMarkdownFormat` ([#4663](https://github.com/elastic/eui/pull/4663)) +- Added optional virtualized line rendering to `EuiCodeBlock` ([#4952](https://github.com/elastic/eui/pull/4952)) ## [`36.0.0`](https://github.com/elastic/eui/tree/v36.0.0) diff --git a/src-docs/src/views/code/code_example.js b/src-docs/src/views/code/code_example.js index a41b0e056ad..0e93e848dba 100644 --- a/src-docs/src/views/code/code_example.js +++ b/src-docs/src/views/code/code_example.js @@ -1,6 +1,5 @@ import React from 'react'; - -import { renderToHtml } from '../../services'; +import { Link } from 'react-router-dom'; import { GuideSectionTypes } from '../../components'; @@ -14,20 +13,27 @@ import { codeBlockConfig, codeConfig } from './playground'; import Code from './code'; const codeSource = require('!!raw-loader!./code'); -const codeHtml = renderToHtml(Code); const codeSnippet = 'Text to be formatted'; import CodeBlock from './code_block'; const codeBlockSource = require('!!raw-loader!./code_block'); -const codeBlockHtml = renderToHtml(CodeBlock); const codeBlockSnippet = ` { \`

Title

\` }
`; +import CodeBlockVirtualized from './virtualized'; +const codeBlockVirtualizedSource = require('!!raw-loader!./virtualized'); +const codeBlockVirtualizedSnippet = ` +{ \`{}\` } + +`; + +import CodeBlockVirtualizedFlyout from './virtualized_flyout'; +const codeBlockVirtualizedFlyoutSource = require('!!raw-loader!./virtualized_flyout'); + import CodeBlockPre from './code_block_pre'; const codeBlockPreSource = require('!!raw-loader!./code_block_pre'); -const codeBlockPreHtml = renderToHtml(CodeBlockPre); export const CodeExample = { title: 'Code', @@ -37,7 +43,7 @@ export const CodeExample = {

The EuiCode and EuiCodeBlock{' '} components support{' '} - + all language syntaxes {' '} supported by the @@ -67,10 +73,6 @@ export const CodeExample = { type: GuideSectionTypes.JS, code: codeSource, }, - { - type: GuideSectionTypes.HTML, - code: codeHtml, - }, ], text: (

@@ -90,10 +92,6 @@ export const CodeExample = { type: GuideSectionTypes.JS, code: codeBlockSource, }, - { - type: GuideSectionTypes.HTML, - code: codeBlockHtml, - }, ], text: (

@@ -109,15 +107,48 @@ export const CodeExample = { playground: codeBlockConfig, }, { - title: 'Code block and white-space', + title: 'Code block virtualization', source: [ { type: GuideSectionTypes.JS, - code: codeBlockPreSource, + code: codeBlockVirtualizedSource, + }, + ], + text: ( +

+ For large blocks of code, add isVirtualized to + reduce the number of rendered rows and improve load times. Note that{' '} + overflowHeight is required when using this + configuration. +

+ ), + props: { EuiCodeBlock }, + snippet: codeBlockVirtualizedSnippet, + demo: , + }, + { + source: [ + { + type: GuideSectionTypes.JS, + code: codeBlockVirtualizedFlyoutSource, }, + ], + text: ( +

+ In places like flyouts, you can use{' '} + {'overflowHeight="100%"'} to stretch + the code block to fill the space. Just be sure that it's parent + container is also {'height: 100%'}. +

+ ), + demo: , + }, + { + title: 'Code block and white-space', + source: [ { - type: GuideSectionTypes.HTML, - code: codeBlockPreHtml, + type: GuideSectionTypes.JS, + code: codeBlockPreSource, }, ], text: ( diff --git a/src-docs/src/views/code/virtualized.js b/src-docs/src/views/code/virtualized.js new file mode 100644 index 00000000000..f5a25a93c20 --- /dev/null +++ b/src-docs/src/views/code/virtualized.js @@ -0,0 +1,824 @@ +import React from 'react'; + +import { EuiCodeBlock } from '../../../../src/components'; + +export default () => ( +
+ + {`{ + "id": "1", + "rawResponse": { + "took": 19, + "timed_out": false, + "_shards": { + "total": 1, + "successful": 1, + "skipped": 0, + "failed": 0 + }, + "hits": { + "total": 7, + "max_score": null, + "hits": [ + { + "_index": "kibana_sample_data_flights", + "_id": "i5-sr3oB9JvwH6mY-m2Q", + "_version": 1, + "_score": null, + "fields": { + "Origin": [ + "Wichita Mid Continent Airport" + ], + "OriginLocation": [ + { + "coordinates": [ + -97.43309784, + 37.64989853 + ], + "type": "Point" + } + ], + "FlightNum": [ + "Q4UQIF3" + ], + "DestLocation": [ + { + "coordinates": [ + 8.54917, + 47.464699 + ], + "type": "Point" + } + ], + "FlightDelay": [ + true + ], + "DistanceMiles": [ + 5013.5835 + ], + "FlightTimeMin": [ + 822.3817 + ], + "OriginWeather": [ + "Cloudy" + ], + "dayOfWeek": [ + 4 + ], + "AvgTicketPrice": [ + 276.2003 + ], + "Carrier": [ + "JetBeats" + ], + "FlightDelayMin": [ + 150 + ], + "OriginRegion": [ + "US-KS" + ], + "DestAirportID": [ + "ZRH" + ], + "FlightDelayType": [ + "Carrier Delay" + ], + "hour_of_day": [ + 14 + ], + "timestamp": [ + "2021-07-16T14:15:29.000Z" + ], + "Dest": [ + "Zurich Airport" + ], + "FlightTimeHour": [ + "13.706362235285443" + ], + "Cancelled": [ + false + ], + "DistanceKilometers": [ + 8068.5806 + ], + "OriginCityName": [ + "Wichita" + ], + "DestWeather": [ + "Rain" + ], + "OriginCountry": [ + "US" + ], + "DestCountry": [ + "CH" + ], + "DestRegion": [ + "CH-ZH" + ], + "OriginAirportID": [ + "ICT" + ], + "DestCityName": [ + "Zurich" + ] + }, + "sort": [ + 1626444929000 + ] + }, + { + "_index": "kibana_sample_data_flights", + "_id": "AZ-sr3oB9JvwH6mY-m2P", + "_version": 1, + "_score": null, + "fields": { + "Origin": [ + "Turin Airport" + ], + "OriginLocation": [ + { + "coordinates": [ + 7.64963, + 45.200802 + ], + "type": "Point" + } + ], + "FlightNum": [ + "WR15PZZ" + ], + "DestLocation": [ + { + "coordinates": [ + 20.96710014, + 52.16569901 + ], + "type": "Point" + } + ], + "FlightDelay": [ + false + ], + "DistanceMiles": [ + 774.4176 + ], + "FlightTimeMin": [ + 95.86956 + ], + "OriginWeather": [ + "Clear" + ], + "dayOfWeek": [ + 4 + ], + "AvgTicketPrice": [ + 360.27106 + ], + "Carrier": [ + "Kibana Airlines" + ], + "FlightDelayMin": [ + 0 + ], + "OriginRegion": [ + "IT-21" + ], + "DestAirportID": [ + "WAW" + ], + "FlightDelayType": [ + "No Delay" + ], + "hour_of_day": [ + 14 + ], + "timestamp": [ + "2021-07-16T14:14:06.000Z" + ], + "Dest": [ + "Warsaw Chopin Airport" + ], + "FlightTimeHour": [ + "1.597826006729792" + ], + "Cancelled": [ + false + ], + "DistanceKilometers": [ + 1246.3043 + ], + "OriginCityName": [ + "Torino" + ], + "DestWeather": [ + "Thunder & Lightning" + ], + "OriginCountry": [ + "IT" + ], + "DestCountry": [ + "PL" + ], + "DestRegion": [ + "PL-MZ" + ], + "OriginAirportID": [ + "TO11" + ], + "DestCityName": [ + "Warsaw" + ] + }, + "sort": [ + 1626444846000 + ] + }, + { + "_index": "kibana_sample_data_flights", + "_id": "LJ-sr3oB9JvwH6mY-m6Q", + "_version": 1, + "_score": null, + "fields": { + "Origin": [ + "Chhatrapati Shivaji International Airport" + ], + "OriginLocation": [ + { + "coordinates": [ + 72.86789703, + 19.08869934 + ], + "type": "Point" + } + ], + "FlightNum": [ + "VZNTLIZ" + ], + "DestLocation": [ + { + "coordinates": [ + 33.46390152, + 68.15180206 + ], + "type": "Point" + } + ], + "FlightDelay": [ + false + ], + "DistanceMiles": [ + 3791.431 + ], + "FlightTimeMin": [ + 305.08582 + ], + "OriginWeather": [ + "Cloudy" + ], + "dayOfWeek": [ + 4 + ], + "AvgTicketPrice": [ + 845.4707 + ], + "Carrier": [ + "JetBeats" + ], + "FlightDelayMin": [ + 0 + ], + "OriginRegion": [ + "SE-BD" + ], + "DestAirportID": [ + "XLMO" + ], + "FlightDelayType": [ + "No Delay" + ], + "hour_of_day": [ + 14 + ], + "timestamp": [ + "2021-07-16T14:05:44.000Z" + ], + "Dest": [ + "Olenya Air Base" + ], + "FlightTimeHour": [ + "5.084763808440013" + ], + "Cancelled": [ + false + ], + "DistanceKilometers": [ + 6101.717 + ], + "OriginCityName": [ + "Mumbai" + ], + "DestWeather": [ + "Heavy Fog" + ], + "OriginCountry": [ + "IN" + ], + "DestCountry": [ + "RU" + ], + "DestRegion": [ + "RU-MUR" + ], + "OriginAirportID": [ + "BOM" + ], + "DestCityName": [ + "Olenegorsk" + ] + }, + "sort": [ + 1626444344000 + ] + }, + { + "_index": "kibana_sample_data_flights", + "_id": "Hp-sr3oB9JvwH6mY-m2P", + "_version": 1, + "_score": null, + "fields": { + "Origin": [ + "Buffalo Niagara International Airport" + ], + "OriginLocation": [ + { + "coordinates": [ + -78.73220062, + 42.94049835 + ], + "type": "Point" + } + ], + "FlightNum": [ + "QAXVRPQ" + ], + "DestLocation": [ + { + "coordinates": [ + -78.3575, + -0.129166667 + ], + "type": "Point" + } + ], + "FlightDelay": [ + false + ], + "DistanceMiles": [ + 2964.2756 + ], + "FlightTimeMin": [ + 227.16853 + ], + "OriginWeather": [ + "Sunny" + ], + "dayOfWeek": [ + 4 + ], + "AvgTicketPrice": [ + 719.76935 + ], + "Carrier": [ + "JetBeats" + ], + "FlightDelayMin": [ + 0 + ], + "OriginRegion": [ + "US-NY" + ], + "DestAirportID": [ + "UIO" + ], + "FlightDelayType": [ + "No Delay" + ], + "hour_of_day": [ + 13 + ], + "timestamp": [ + "2021-07-16T13:57:30.000Z" + ], + "Dest": [ + "Mariscal Sucre International Airport" + ], + "FlightTimeHour": [ + "3.7861423240197563" + ], + "Cancelled": [ + false + ], + "DistanceKilometers": [ + 4770.5396 + ], + "OriginCityName": [ + "Buffalo" + ], + "DestWeather": [ + "Clear" + ], + "OriginCountry": [ + "US" + ], + "DestCountry": [ + "EC" + ], + "DestRegion": [ + "EC-P" + ], + "OriginAirportID": [ + "BUF" + ], + "DestCityName": [ + "Quito" + ] + }, + "sort": [ + 1626443850000 + ] + }, + { + "_index": "kibana_sample_data_flights", + "_id": "U5-sr3oB9JvwH6mY-m2Q", + "_version": 1, + "_score": null, + "fields": { + "Origin": [ + "Dubai International Airport" + ], + "OriginLocation": [ + { + "coordinates": [ + 55.36439896, + 25.25279999 + ], + "type": "Point" + } + ], + "FlightNum": [ + "TJQKCKN" + ], + "DestLocation": [ + { + "coordinates": [ + -73.74079895, + 45.47060013 + ], + "type": "Point" + } + ], + "FlightDelay": [ + false + ], + "DistanceMiles": [ + 6611.2646 + ], + "FlightTimeMin": [ + 709.31995 + ], + "OriginWeather": [ + "Rain" + ], + "dayOfWeek": [ + 4 + ], + "AvgTicketPrice": [ + 756.61444 + ], + "Carrier": [ + "ES-Air" + ], + "FlightDelayMin": [ + 0 + ], + "OriginRegion": [ + "SE-BD" + ], + "DestAirportID": [ + "YUL" + ], + "FlightDelayType": [ + "No Delay" + ], + "hour_of_day": [ + 13 + ], + "timestamp": [ + "2021-07-16T13:51:52.000Z" + ], + "Dest": [ + "Montreal / Pierre Elliott Trudeau International Airport" + ], + "FlightTimeHour": [ + "11.821998598483413" + ], + "Cancelled": [ + false + ], + "DistanceKilometers": [ + 10639.799 + ], + "OriginCityName": [ + "Dubai" + ], + "DestWeather": [ + "Clear" + ], + "OriginCountry": [ + "AE" + ], + "DestCountry": [ + "CA" + ], + "DestRegion": [ + "CA-QC" + ], + "OriginAirportID": [ + "DXB" + ], + "DestCityName": [ + "Montreal" + ] + }, + "sort": [ + 1626443512000 + ] + }, + { + "_index": "kibana_sample_data_flights", + "_id": "TJ-sr3oB9JvwH6mY-m2Q", + "_version": 1, + "_score": null, + "fields": { + "Origin": [ + "Jorge Chavez International Airport" + ], + "OriginLocation": [ + { + "coordinates": [ + -77.114304, + -12.0219 + ], + "type": "Point" + } + ], + "FlightNum": [ + "8B6BGMO" + ], + "DestLocation": [ + { + "coordinates": [ + 128.445007, + 51.169997 + ], + "type": "Point" + } + ], + "FlightDelay": [ + true + ], + "DistanceMiles": [ + 9375.942 + ], + "FlightTimeMin": [ + 824.16406 + ], + "OriginWeather": [ + "Cloudy" + ], + "dayOfWeek": [ + 4 + ], + "AvgTicketPrice": [ + 463.068 + ], + "Carrier": [ + "Logstash Airways" + ], + "FlightDelayMin": [ + 30 + ], + "OriginRegion": [ + "SE-BD" + ], + "DestAirportID": [ + "XHBU" + ], + "FlightDelayType": [ + "Late Aircraft Delay" + ], + "hour_of_day": [ + 13 + ], + "timestamp": [ + "2021-07-16T13:50:55.000Z" + ], + "Dest": [ + "Ukrainka Air Base" + ], + "FlightTimeHour": [ + "13.736067768266615" + ], + "Cancelled": [ + false + ], + "DistanceKilometers": [ + 15089.117 + ], + "OriginCityName": [ + "Lima" + ], + "DestWeather": [ + "Clear" + ], + "OriginCountry": [ + "PE" + ], + "DestCountry": [ + "RU" + ], + "DestRegion": [ + "RU-AMU" + ], + "OriginAirportID": [ + "LIM" + ], + "DestCityName": [ + "Belogorsk" + ] + }, + "sort": [ + 1626443455000 + ] + }, + { + "_index": "kibana_sample_data_flights", + "_id": "3J-sr3oB9JvwH6mY-m2Q", + "_version": 1, + "_score": null, + "fields": { + "Origin": [ + "Sydney Kingsford Smith International Airport" + ], + "OriginLocation": [ + { + "coordinates": [ + 151.177002, + -33.94609833 + ], + "type": "Point" + } + ], + "FlightNum": [ + "PASAN8N" + ], + "DestLocation": [ + { + "coordinates": [ + 8.54917, + 47.464699 + ], + "type": "Point" + } + ], + "FlightDelay": [ + false + ], + "DistanceMiles": [ + 10293.209 + ], + "FlightTimeMin": [ + 1380.4429 + ], + "OriginWeather": [ + "Sunny" + ], + "dayOfWeek": [ + 4 + ], + "AvgTicketPrice": [ + 380.29593 + ], + "Carrier": [ + "Logstash Airways" + ], + "FlightDelayMin": [ + 0 + ], + "OriginRegion": [ + "SE-BD" + ], + "DestAirportID": [ + "ZRH" + ], + "FlightDelayType": [ + "No Delay" + ], + "hour_of_day": [ + 13 + ], + "timestamp": [ + "2021-07-16T13:49:20.000Z" + ], + "Dest": [ + "Zurich Airport" + ], + "FlightTimeHour": [ + "23.007380215402044" + ], + "Cancelled": [ + false + ], + "DistanceKilometers": [ + 16565.314 + ], + "OriginCityName": [ + "Sydney" + ], + "DestWeather": [ + "Rain" + ], + "OriginCountry": [ + "AU" + ], + "DestCountry": [ + "CH" + ], + "DestRegion": [ + "CH-ZH" + ], + "OriginAirportID": [ + "SYD" + ], + "DestCityName": [ + "Zurich" + ] + }, + "sort": [ + 1626443360000 + ] + } + ] + }, + "aggregations": { + "2": { + "buckets": [ + { + "key_as_string": "2021-07-16T08:49:00.000-05:00", + "key": 1626443340000, + "doc_count": 1 + }, + { + "key_as_string": "2021-07-16T08:50:30.000-05:00", + "key": 1626443430000, + "doc_count": 1 + }, + { + "key_as_string": "2021-07-16T08:51:30.000-05:00", + "key": 1626443490000, + "doc_count": 1 + }, + { + "key_as_string": "2021-07-16T08:57:30.000-05:00", + "key": 1626443850000, + "doc_count": 1 + }, + { + "key_as_string": "2021-07-16T09:05:30.000-05:00", + "key": 1626444330000, + "doc_count": 1 + }, + { + "key_as_string": "2021-07-16T09:14:00.000-05:00", + "key": 1626444840000, + "doc_count": 1 + }, + { + "key_as_string": "2021-07-16T09:15:00.000-05:00", + "key": 1626444900000, + "doc_count": 1 + } + ] + } + } + }, + "isPartial": false, + "isRunning": false, + "total": 1, + "loaded": 1, + "isRestored": false +}`} + +
+); diff --git a/src-docs/src/views/code/virtualized_flyout.js b/src-docs/src/views/code/virtualized_flyout.js new file mode 100644 index 00000000000..8b0ece97bec --- /dev/null +++ b/src-docs/src/views/code/virtualized_flyout.js @@ -0,0 +1,861 @@ +import React, { useState } from 'react'; + +import { + EuiFlyout, + EuiFlyoutHeader, + EuiButton, + EuiTitle, + EuiCodeBlock, +} from '../../../../src/components'; + +export default () => { + const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); + + let flyout; + + if (isFlyoutVisible) { + flyout = ( + setIsFlyoutVisible(false)} + aria-labelledby="flyoutTitle"> + + +

A flyout with just code

+
+
+
+ + {`{ + "id": "1", + "rawResponse": { + "took": 19, + "timed_out": false, + "_shards": { + "total": 1, + "successful": 1, + "skipped": 0, + "failed": 0 + }, + "hits": { + "total": 7, + "max_score": null, + "hits": [ + { + "_index": "kibana_sample_data_flights", + "_id": "i5-sr3oB9JvwH6mY-m2Q", + "_version": 1, + "_score": null, + "fields": { + "Origin": [ + "Wichita Mid Continent Airport" + ], + "OriginLocation": [ + { + "coordinates": [ + -97.43309784, + 37.64989853 + ], + "type": "Point" + } + ], + "FlightNum": [ + "Q4UQIF3" + ], + "DestLocation": [ + { + "coordinates": [ + 8.54917, + 47.464699 + ], + "type": "Point" + } + ], + "FlightDelay": [ + true + ], + "DistanceMiles": [ + 5013.5835 + ], + "FlightTimeMin": [ + 822.3817 + ], + "OriginWeather": [ + "Cloudy" + ], + "dayOfWeek": [ + 4 + ], + "AvgTicketPrice": [ + 276.2003 + ], + "Carrier": [ + "JetBeats" + ], + "FlightDelayMin": [ + 150 + ], + "OriginRegion": [ + "US-KS" + ], + "DestAirportID": [ + "ZRH" + ], + "FlightDelayType": [ + "Carrier Delay" + ], + "hour_of_day": [ + 14 + ], + "timestamp": [ + "2021-07-16T14:15:29.000Z" + ], + "Dest": [ + "Zurich Airport" + ], + "FlightTimeHour": [ + "13.706362235285443" + ], + "Cancelled": [ + false + ], + "DistanceKilometers": [ + 8068.5806 + ], + "OriginCityName": [ + "Wichita" + ], + "DestWeather": [ + "Rain" + ], + "OriginCountry": [ + "US" + ], + "DestCountry": [ + "CH" + ], + "DestRegion": [ + "CH-ZH" + ], + "OriginAirportID": [ + "ICT" + ], + "DestCityName": [ + "Zurich" + ] + }, + "sort": [ + 1626444929000 + ] + }, + { + "_index": "kibana_sample_data_flights", + "_id": "AZ-sr3oB9JvwH6mY-m2P", + "_version": 1, + "_score": null, + "fields": { + "Origin": [ + "Turin Airport" + ], + "OriginLocation": [ + { + "coordinates": [ + 7.64963, + 45.200802 + ], + "type": "Point" + } + ], + "FlightNum": [ + "WR15PZZ" + ], + "DestLocation": [ + { + "coordinates": [ + 20.96710014, + 52.16569901 + ], + "type": "Point" + } + ], + "FlightDelay": [ + false + ], + "DistanceMiles": [ + 774.4176 + ], + "FlightTimeMin": [ + 95.86956 + ], + "OriginWeather": [ + "Clear" + ], + "dayOfWeek": [ + 4 + ], + "AvgTicketPrice": [ + 360.27106 + ], + "Carrier": [ + "Kibana Airlines" + ], + "FlightDelayMin": [ + 0 + ], + "OriginRegion": [ + "IT-21" + ], + "DestAirportID": [ + "WAW" + ], + "FlightDelayType": [ + "No Delay" + ], + "hour_of_day": [ + 14 + ], + "timestamp": [ + "2021-07-16T14:14:06.000Z" + ], + "Dest": [ + "Warsaw Chopin Airport" + ], + "FlightTimeHour": [ + "1.597826006729792" + ], + "Cancelled": [ + false + ], + "DistanceKilometers": [ + 1246.3043 + ], + "OriginCityName": [ + "Torino" + ], + "DestWeather": [ + "Thunder & Lightning" + ], + "OriginCountry": [ + "IT" + ], + "DestCountry": [ + "PL" + ], + "DestRegion": [ + "PL-MZ" + ], + "OriginAirportID": [ + "TO11" + ], + "DestCityName": [ + "Warsaw" + ] + }, + "sort": [ + 1626444846000 + ] + }, + { + "_index": "kibana_sample_data_flights", + "_id": "LJ-sr3oB9JvwH6mY-m6Q", + "_version": 1, + "_score": null, + "fields": { + "Origin": [ + "Chhatrapati Shivaji International Airport" + ], + "OriginLocation": [ + { + "coordinates": [ + 72.86789703, + 19.08869934 + ], + "type": "Point" + } + ], + "FlightNum": [ + "VZNTLIZ" + ], + "DestLocation": [ + { + "coordinates": [ + 33.46390152, + 68.15180206 + ], + "type": "Point" + } + ], + "FlightDelay": [ + false + ], + "DistanceMiles": [ + 3791.431 + ], + "FlightTimeMin": [ + 305.08582 + ], + "OriginWeather": [ + "Cloudy" + ], + "dayOfWeek": [ + 4 + ], + "AvgTicketPrice": [ + 845.4707 + ], + "Carrier": [ + "JetBeats" + ], + "FlightDelayMin": [ + 0 + ], + "OriginRegion": [ + "SE-BD" + ], + "DestAirportID": [ + "XLMO" + ], + "FlightDelayType": [ + "No Delay" + ], + "hour_of_day": [ + 14 + ], + "timestamp": [ + "2021-07-16T14:05:44.000Z" + ], + "Dest": [ + "Olenya Air Base" + ], + "FlightTimeHour": [ + "5.084763808440013" + ], + "Cancelled": [ + false + ], + "DistanceKilometers": [ + 6101.717 + ], + "OriginCityName": [ + "Mumbai" + ], + "DestWeather": [ + "Heavy Fog" + ], + "OriginCountry": [ + "IN" + ], + "DestCountry": [ + "RU" + ], + "DestRegion": [ + "RU-MUR" + ], + "OriginAirportID": [ + "BOM" + ], + "DestCityName": [ + "Olenegorsk" + ] + }, + "sort": [ + 1626444344000 + ] + }, + { + "_index": "kibana_sample_data_flights", + "_id": "Hp-sr3oB9JvwH6mY-m2P", + "_version": 1, + "_score": null, + "fields": { + "Origin": [ + "Buffalo Niagara International Airport" + ], + "OriginLocation": [ + { + "coordinates": [ + -78.73220062, + 42.94049835 + ], + "type": "Point" + } + ], + "FlightNum": [ + "QAXVRPQ" + ], + "DestLocation": [ + { + "coordinates": [ + -78.3575, + -0.129166667 + ], + "type": "Point" + } + ], + "FlightDelay": [ + false + ], + "DistanceMiles": [ + 2964.2756 + ], + "FlightTimeMin": [ + 227.16853 + ], + "OriginWeather": [ + "Sunny" + ], + "dayOfWeek": [ + 4 + ], + "AvgTicketPrice": [ + 719.76935 + ], + "Carrier": [ + "JetBeats" + ], + "FlightDelayMin": [ + 0 + ], + "OriginRegion": [ + "US-NY" + ], + "DestAirportID": [ + "UIO" + ], + "FlightDelayType": [ + "No Delay" + ], + "hour_of_day": [ + 13 + ], + "timestamp": [ + "2021-07-16T13:57:30.000Z" + ], + "Dest": [ + "Mariscal Sucre International Airport" + ], + "FlightTimeHour": [ + "3.7861423240197563" + ], + "Cancelled": [ + false + ], + "DistanceKilometers": [ + 4770.5396 + ], + "OriginCityName": [ + "Buffalo" + ], + "DestWeather": [ + "Clear" + ], + "OriginCountry": [ + "US" + ], + "DestCountry": [ + "EC" + ], + "DestRegion": [ + "EC-P" + ], + "OriginAirportID": [ + "BUF" + ], + "DestCityName": [ + "Quito" + ] + }, + "sort": [ + 1626443850000 + ] + }, + { + "_index": "kibana_sample_data_flights", + "_id": "U5-sr3oB9JvwH6mY-m2Q", + "_version": 1, + "_score": null, + "fields": { + "Origin": [ + "Dubai International Airport" + ], + "OriginLocation": [ + { + "coordinates": [ + 55.36439896, + 25.25279999 + ], + "type": "Point" + } + ], + "FlightNum": [ + "TJQKCKN" + ], + "DestLocation": [ + { + "coordinates": [ + -73.74079895, + 45.47060013 + ], + "type": "Point" + } + ], + "FlightDelay": [ + false + ], + "DistanceMiles": [ + 6611.2646 + ], + "FlightTimeMin": [ + 709.31995 + ], + "OriginWeather": [ + "Rain" + ], + "dayOfWeek": [ + 4 + ], + "AvgTicketPrice": [ + 756.61444 + ], + "Carrier": [ + "ES-Air" + ], + "FlightDelayMin": [ + 0 + ], + "OriginRegion": [ + "SE-BD" + ], + "DestAirportID": [ + "YUL" + ], + "FlightDelayType": [ + "No Delay" + ], + "hour_of_day": [ + 13 + ], + "timestamp": [ + "2021-07-16T13:51:52.000Z" + ], + "Dest": [ + "Montreal / Pierre Elliott Trudeau International Airport" + ], + "FlightTimeHour": [ + "11.821998598483413" + ], + "Cancelled": [ + false + ], + "DistanceKilometers": [ + 10639.799 + ], + "OriginCityName": [ + "Dubai" + ], + "DestWeather": [ + "Clear" + ], + "OriginCountry": [ + "AE" + ], + "DestCountry": [ + "CA" + ], + "DestRegion": [ + "CA-QC" + ], + "OriginAirportID": [ + "DXB" + ], + "DestCityName": [ + "Montreal" + ] + }, + "sort": [ + 1626443512000 + ] + }, + { + "_index": "kibana_sample_data_flights", + "_id": "TJ-sr3oB9JvwH6mY-m2Q", + "_version": 1, + "_score": null, + "fields": { + "Origin": [ + "Jorge Chavez International Airport" + ], + "OriginLocation": [ + { + "coordinates": [ + -77.114304, + -12.0219 + ], + "type": "Point" + } + ], + "FlightNum": [ + "8B6BGMO" + ], + "DestLocation": [ + { + "coordinates": [ + 128.445007, + 51.169997 + ], + "type": "Point" + } + ], + "FlightDelay": [ + true + ], + "DistanceMiles": [ + 9375.942 + ], + "FlightTimeMin": [ + 824.16406 + ], + "OriginWeather": [ + "Cloudy" + ], + "dayOfWeek": [ + 4 + ], + "AvgTicketPrice": [ + 463.068 + ], + "Carrier": [ + "Logstash Airways" + ], + "FlightDelayMin": [ + 30 + ], + "OriginRegion": [ + "SE-BD" + ], + "DestAirportID": [ + "XHBU" + ], + "FlightDelayType": [ + "Late Aircraft Delay" + ], + "hour_of_day": [ + 13 + ], + "timestamp": [ + "2021-07-16T13:50:55.000Z" + ], + "Dest": [ + "Ukrainka Air Base" + ], + "FlightTimeHour": [ + "13.736067768266615" + ], + "Cancelled": [ + false + ], + "DistanceKilometers": [ + 15089.117 + ], + "OriginCityName": [ + "Lima" + ], + "DestWeather": [ + "Clear" + ], + "OriginCountry": [ + "PE" + ], + "DestCountry": [ + "RU" + ], + "DestRegion": [ + "RU-AMU" + ], + "OriginAirportID": [ + "LIM" + ], + "DestCityName": [ + "Belogorsk" + ] + }, + "sort": [ + 1626443455000 + ] + }, + { + "_index": "kibana_sample_data_flights", + "_id": "3J-sr3oB9JvwH6mY-m2Q", + "_version": 1, + "_score": null, + "fields": { + "Origin": [ + "Sydney Kingsford Smith International Airport" + ], + "OriginLocation": [ + { + "coordinates": [ + 151.177002, + -33.94609833 + ], + "type": "Point" + } + ], + "FlightNum": [ + "PASAN8N" + ], + "DestLocation": [ + { + "coordinates": [ + 8.54917, + 47.464699 + ], + "type": "Point" + } + ], + "FlightDelay": [ + false + ], + "DistanceMiles": [ + 10293.209 + ], + "FlightTimeMin": [ + 1380.4429 + ], + "OriginWeather": [ + "Sunny" + ], + "dayOfWeek": [ + 4 + ], + "AvgTicketPrice": [ + 380.29593 + ], + "Carrier": [ + "Logstash Airways" + ], + "FlightDelayMin": [ + 0 + ], + "OriginRegion": [ + "SE-BD" + ], + "DestAirportID": [ + "ZRH" + ], + "FlightDelayType": [ + "No Delay" + ], + "hour_of_day": [ + 13 + ], + "timestamp": [ + "2021-07-16T13:49:20.000Z" + ], + "Dest": [ + "Zurich Airport" + ], + "FlightTimeHour": [ + "23.007380215402044" + ], + "Cancelled": [ + false + ], + "DistanceKilometers": [ + 16565.314 + ], + "OriginCityName": [ + "Sydney" + ], + "DestWeather": [ + "Rain" + ], + "OriginCountry": [ + "AU" + ], + "DestCountry": [ + "CH" + ], + "DestRegion": [ + "CH-ZH" + ], + "OriginAirportID": [ + "SYD" + ], + "DestCityName": [ + "Zurich" + ] + }, + "sort": [ + 1626443360000 + ] + } + ] + }, + "aggregations": { + "2": { + "buckets": [ + { + "key_as_string": "2021-07-16T08:49:00.000-05:00", + "key": 1626443340000, + "doc_count": 1 + }, + { + "key_as_string": "2021-07-16T08:50:30.000-05:00", + "key": 1626443430000, + "doc_count": 1 + }, + { + "key_as_string": "2021-07-16T08:51:30.000-05:00", + "key": 1626443490000, + "doc_count": 1 + }, + { + "key_as_string": "2021-07-16T08:57:30.000-05:00", + "key": 1626443850000, + "doc_count": 1 + }, + { + "key_as_string": "2021-07-16T09:05:30.000-05:00", + "key": 1626444330000, + "doc_count": 1 + }, + { + "key_as_string": "2021-07-16T09:14:00.000-05:00", + "key": 1626444840000, + "doc_count": 1 + }, + { + "key_as_string": "2021-07-16T09:15:00.000-05:00", + "key": 1626444900000, + "doc_count": 1 + } + ] + } + } + }, + "isPartial": false, + "isRunning": false, + "total": 1, + "loaded": 1, + "isRestored": false +}`} + +
+
+ ); + } + + return ( +
+ setIsFlyoutVisible(true)}> + Show flyout example + + {flyout} +
+ ); +}; diff --git a/src/components/code/__snapshots__/_code_block.test.tsx.snap b/src/components/code/__snapshots__/_code_block.test.tsx.snap index 644a2d0e1e0..9c684ffb5e5 100644 --- a/src/components/code/__snapshots__/_code_block.test.tsx.snap +++ b/src/components/code/__snapshots__/_code_block.test.tsx.snap @@ -73,6 +73,33 @@ exports[`EuiCodeBlockImpl block renders a pre block tag with a css class modifie `; +exports[`EuiCodeBlockImpl block renders a virtualized code block 1`] = ` +
+
+
+ +
+
+`; + exports[`EuiCodeBlockImpl block renders with transparent background 1`] = `
+
+
+ +
+
+`; + exports[`EuiCodeBlock dynamic content updates DOM when input changes 1`] = ` "
diff --git a/src/components/code/_code_block.scss b/src/components/code/_code_block.scss index 73407b1dba7..0dc9cbc04f0 100644 --- a/src/components/code/_code_block.scss +++ b/src/components/code/_code_block.scss @@ -20,6 +20,11 @@ white-space: pre-wrap; } + // Necessary for virtualized code blocks to have appropriate padding + .euiCodeBlock__pre--isVirtualized { + position: relative; + } + .euiCodeBlock__code { @include euiCodeFont; display: block; diff --git a/src/components/code/_code_block.test.tsx b/src/components/code/_code_block.test.tsx index ca8c5088324..da29ac57276 100644 --- a/src/components/code/_code_block.test.tsx +++ b/src/components/code/_code_block.test.tsx @@ -87,5 +87,18 @@ describe('EuiCodeBlockImpl', () => { ); expect(component).toMatchSnapshot(); }); + + test('renders a virtualized code block', () => { + const component = render( + + {code} + + ); + expect(component).toMatchSnapshot(); + }); }); }); diff --git a/src/components/code/_code_block.tsx b/src/components/code/_code_block.tsx index 9ff85f59e89..a3b1af5f3fb 100644 --- a/src/components/code/_code_block.tsx +++ b/src/components/code/_code_block.tsx @@ -8,18 +8,24 @@ import React, { CSSProperties, + HTMLAttributes, FunctionComponent, KeyboardEvent, + ReactElement, ReactNode, + memo, + forwardRef, useEffect, useMemo, useState, } from 'react'; import classNames from 'classnames'; -import { highlight, AST, RefractorNode, listLanguages } from 'refractor'; +import { highlight, RefractorNode, listLanguages } from 'refractor'; +import { FixedSizeList, ListChildComponentProps } from 'react-window'; +import AutoSizer from 'react-virtualized-auto-sizer'; import { keys, useCombinedRefs } from '../../services'; import { EuiButtonIcon } from '../button'; -import { keysOf } from '../common'; +import { keysOf, CommonProps, ExclusiveUnion } from '../common'; import { EuiCopy } from '../copy'; import { EuiFocusTrap } from '../focus_trap'; import { EuiI18n } from '../i18n'; @@ -27,117 +33,43 @@ import { useInnerText } from '../inner_text'; import { useMutationObserver } from '../observer/mutation_observer'; import { useResizeObserver } from '../observer/resize_observer'; import { EuiOverlayMask } from '../overlay_mask'; +import { highlightByLine, nodeToHtml } from './utils'; -type ExtendedRefractorNode = RefractorNode & { - lineStart?: number; - lineEnd?: number; -}; - -const SUPPORTED_LANGUAGES = listLanguages(); -const DEFAULT_LANGUAGE = 'text'; - -const isAstElement = (node: RefractorNode): node is AST.Element => - node.hasOwnProperty('type') && node.type === 'element'; - -const nodeToHtml = ( - node: RefractorNode, - idx: number, - nodes: RefractorNode[], - depth: number = 0 -): ReactNode => { - if (isAstElement(node)) { - const { properties, tagName, children } = node; - - return React.createElement( - tagName, - { - ...properties, - key: `node-${depth}-${idx}`, - className: classNames(properties.className), - }, - children && children.map((el, i) => nodeToHtml(el, i, nodes, depth + 1)) - ); - } - - return node.value; -}; - -const addLineData = ( - nodes: ExtendedRefractorNode[], - data = { lineNumber: 1 } -): ExtendedRefractorNode[] => { - return nodes.reduce((result, node) => { - const lineStart = data.lineNumber; - if (node.type === 'text') { - if (!node.value.match(/\r\n?|\n/)) { - node.lineStart = lineStart; - node.lineEnd = lineStart; - result.push(node); - } else { - const lines = node.value.split(/\r\n?|\n/); - lines.forEach((line, i) => { - const num = i === 0 ? data.lineNumber : ++data.lineNumber; - result.push({ - type: 'text', - value: i === lines.length - 1 ? line : `${line}\n`, - lineStart: num, - lineEnd: num, - }); - }); - } - return result; - } +// eslint-disable-next-line local/forward-ref +const virtualizedOuterElement = ({ + className, +}: HTMLAttributes) => + memo( + forwardRef((props, ref) => ( +
+    ))
+  );
 
-    if (node.children && node.children.length) {
-      const children = addLineData(node.children, data);
-      const first = children[0];
-      const last = children[children.length - 1];
-      const start = first.lineStart ?? lineStart;
-      const end = last.lineEnd ?? lineStart;
-      if (start !== end) {
-        children.forEach((node) => {
-          result.push(node);
-        });
-      } else {
-        node.lineStart = start;
-        node.lineEnd = end;
-        node.children = children;
-        result.push(node);
-      }
-      return result;
-    }
+// eslint-disable-next-line local/forward-ref
+const virtualizedInnerElement = ({
+  className,
+  onKeyDown,
+}: HTMLAttributes) =>
+  memo(
+    forwardRef((props, ref) => (
+      
+    ))
+  );
 
-    result.push(node);
-    return result;
-  }, []);
+const ListRow = ({ data, index, style }: ListChildComponentProps) => {
+  const row = data[index];
+  row.properties.style = style;
+  return nodeToHtml(row, index, data, 0);
 };
 
-function wrapLines(nodes: ExtendedRefractorNode[]) {
-  const grouped: ExtendedRefractorNode[][] = [];
-  nodes.forEach((node) => {
-    const lineStart = node.lineStart! - 1;
-    if (grouped[lineStart]) {
-      grouped[lineStart].push(node);
-    } else {
-      grouped[lineStart] = [node];
-    }
-  });
-  const wrapped: RefractorNode[] = [];
-  grouped.forEach((node) => {
-    wrapped.push({
-      type: 'element',
-      tagName: 'span',
-      properties: {
-        className: ['euiCodeBlock__line'],
-      },
-      children: node,
-    });
-  });
-  return wrapped;
-}
+const SUPPORTED_LANGUAGES = listLanguages();
+const DEFAULT_LANGUAGE = 'text';
 
-const highlightByLine = (children: string, language: string) => {
-  return wrapLines(addLineData(highlight(children, language)));
+// Based on observed line height for non-virtualized code blocks
+const fontSizeToRowHeightMap = {
+  s: 16,
+  m: 19,
+  l: 21,
 };
 
 const fontSizeToClassNameMap = {
@@ -160,7 +92,29 @@ const paddingSizeToClassNameMap: { [paddingSize in PaddingSize]: string } = {
 
 export const PADDING_SIZES = keysOf(paddingSizeToClassNameMap);
 
-export interface EuiCodeBlockImplProps {
+// overflowHeight is required when using virtualization
+type VirtualizedOptionProps = ExclusiveUnion<
+  {
+    /**
+     * Renders code block lines virtually.
+     * Useful for improving load times of large code blocks.
+     * `overflowHeight` is required when using this configuration.
+     */
+    isVirtualized: true;
+    /**
+     * Sets the maximum container height.
+     * Accepts a pixel value (`300`) or a percentage (`'100%'`)
+     * Ensure the container has calcuable height when using a percentage
+     */
+    overflowHeight: number | string;
+  },
+  {
+    isVirtualized?: boolean;
+    overflowHeight?: number | string;
+  }
+>;
+
+export type EuiCodeBlockImplProps = CommonProps & {
   className?: string;
   fontSize?: FontSize;
 
@@ -176,11 +130,10 @@ export interface EuiCodeBlockImplProps {
 
   /**
    * Sets the syntax highlighting for a specific language
-   * @see https://github.com/wooorm/refractor#syntaxes
+   * @see https://prismjs.com/#supported-languages
    * for options
    */
   language?: string;
-  overflowHeight?: number;
   paddingSize?: PaddingSize;
   transparentBackground?: boolean;
   /**
@@ -189,7 +142,7 @@ export interface EuiCodeBlockImplProps {
    * `pre-wrap` respects line breaks/white space but does force them to wrap the line when necessary.
    */
   whiteSpace?: 'pre' | 'pre-wrap';
-}
+} & VirtualizedOptionProps;
 
 /**
  * This is the base component extended by EuiCode and EuiCodeBlock.
@@ -206,6 +159,7 @@ export const EuiCodeBlockImpl: FunctionComponent = ({
   children,
   className,
   overflowHeight,
+  isVirtualized: _isVirtualized,
   ...rest
 }) => {
   const language: string = useMemo(
@@ -226,17 +180,31 @@ export const EuiCodeBlockImpl: FunctionComponent = ({
     setWrapperRef,
   ]);
   const { width, height } = useResizeObserver(wrapperRef);
+  const rowHeight = useMemo(() => fontSizeToRowHeightMap[fontSize], [fontSize]);
 
-  const content = useMemo(() => {
+  // Used by `FixedSizeList` when `isVirtualized=true` or `children` is parsable (`isVirtualized=true`)
+  const data: RefractorNode[] = useMemo(() => {
     if (typeof children !== 'string') {
-      return children;
+      return [];
     }
-    const nodes = inline
+    return inline
       ? highlight(children, language)
       : highlightByLine(children, language);
-    return nodes.length === 0 ? children : nodes.map(nodeToHtml);
   }, [children, language, inline]);
 
+  const isVirtualized = useMemo(() => _isVirtualized && Array.isArray(data), [
+    _isVirtualized,
+    data,
+  ]);
+
+  // Used by `pre` when `isVirtualized=false` or `children` is not parsable (`isVirtualized=false`)
+  const content: ReactElement[] | ReactNode = useMemo(() => {
+    if (!Array.isArray(data) || data.length < 1) {
+      return children;
+    }
+    return data.map(nodeToHtml);
+  }, [data, children]);
+
   const doesOverflow = () => {
     if (!wrapperRef) return;
 
@@ -291,12 +259,15 @@ export const EuiCodeBlockImpl: FunctionComponent = ({
   const preClasses = classNames('euiCodeBlock__pre', {
     'euiCodeBlock__pre--whiteSpacePre': whiteSpace === 'pre',
     'euiCodeBlock__pre--whiteSpacePreWrap': whiteSpace === 'pre-wrap',
+    'euiCodeBlock__pre--isVirtualized': isVirtualized,
   });
 
   const optionalStyles: CSSProperties = {};
 
   if (overflowHeight) {
-    optionalStyles.maxHeight = overflowHeight;
+    const property =
+      typeof overflowHeight === 'string' ? 'height' : 'maxHeight';
+    optionalStyles[property] = overflowHeight;
   }
 
   const codeSnippet = (
@@ -314,8 +285,10 @@ export const EuiCodeBlockImpl: FunctionComponent = ({
     return {codeSnippet};
   }
 
-  const getCopyButton = (textToCopy?: string) => {
+  const getCopyButton = (_textToCopy?: string) => {
     let copyButton: JSX.Element | undefined;
+    // Fallback to `children` in the case of virtualized blocks.
+    const textToCopy = _textToCopy || `${children}`;
 
     if (isCopyable && textToCopy) {
       copyButton = (
@@ -397,11 +370,33 @@ export const EuiCodeBlockImpl: FunctionComponent = ({
         
           
             
-
-                
-                  {content}
-                
-              
+ {isVirtualized ? ( + + {({ height, width }) => ( + + {ListRow} + + )} + + ) : ( +
+                  
+                    {content}
+                  
+                
+ )} {codeBlockControls}
@@ -416,13 +411,35 @@ export const EuiCodeBlockImpl: FunctionComponent = ({ const codeBlockControls = getCodeBlockControls(innerText); return (
-
-        {codeSnippet}
-      
+ {isVirtualized ? ( + + {({ height, width }) => ( + + {ListRow} + + )} + + ) : ( +
+          {codeSnippet}
+        
+ )} {/* If the below fullScreen code renders, it actually attaches to the body because of EuiOverlayMask's React portal usage. diff --git a/src/components/code/code_block.test.tsx b/src/components/code/code_block.test.tsx index 5af6ebfd1e1..d867afe7541 100644 --- a/src/components/code/code_block.test.tsx +++ b/src/components/code/code_block.test.tsx @@ -154,5 +154,17 @@ describe('EuiCodeBlock', () => { 'const value = "hello"' ); }); + + test('renders a virtualized code block', () => { + const component = render( + + {code} + + ); + expect(component).toMatchSnapshot(); + }); }); }); diff --git a/src/components/code/utils.tsx b/src/components/code/utils.tsx new file mode 100644 index 00000000000..4c784074bc0 --- /dev/null +++ b/src/components/code/utils.tsx @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { createElement, ReactElement } from 'react'; +import { highlight, AST, RefractorNode } from 'refractor'; +import classNames from 'classnames'; + +type ExtendedRefractorNode = RefractorNode & { + lineStart?: number; + lineEnd?: number; +}; + +const isAstElement = (node: RefractorNode): node is AST.Element => + node.hasOwnProperty('type') && node.type === 'element'; + +const addLineData = ( + nodes: ExtendedRefractorNode[], + data = { lineNumber: 1 } +): ExtendedRefractorNode[] => { + return nodes.reduce((result, node) => { + const lineStart = data.lineNumber; + if (node.type === 'text') { + if (!node.value.match(/\r\n?|\n/)) { + node.lineStart = lineStart; + node.lineEnd = lineStart; + result.push(node); + } else { + const lines = node.value.split(/\r\n?|\n/); + lines.forEach((line, i) => { + const num = i === 0 ? data.lineNumber : ++data.lineNumber; + result.push({ + type: 'text', + value: i === lines.length - 1 ? line : `${line}\n`, + lineStart: num, + lineEnd: num, + }); + }); + } + return result; + } + + if (node.children && node.children.length) { + const children = addLineData(node.children, data); + const first = children[0]; + const last = children[children.length - 1]; + const start = first.lineStart ?? lineStart; + const end = last.lineEnd ?? lineStart; + if (start !== end) { + children.forEach((node) => { + result.push(node); + }); + } else { + node.lineStart = start; + node.lineEnd = end; + node.children = children; + result.push(node); + } + return result; + } + + result.push(node); + return result; + }, []); +}; + +function wrapLines(nodes: ExtendedRefractorNode[]) { + const grouped: ExtendedRefractorNode[][] = []; + nodes.forEach((node) => { + const lineStart = node.lineStart! - 1; + if (grouped[lineStart]) { + grouped[lineStart].push(node); + } else { + grouped[lineStart] = [node]; + } + }); + const wrapped: RefractorNode[] = []; + grouped.forEach((node) => { + wrapped.push({ + type: 'element', + tagName: 'span', + properties: { + className: ['euiCodeBlock__line'], + }, + children: node, + }); + }); + return wrapped; +} + +export const nodeToHtml = ( + node: RefractorNode, + idx: number, + nodes: RefractorNode[], + depth: number = 0 +): ReactElement => { + const key = `node-${depth}-${idx}`; + + if (isAstElement(node)) { + const { properties, tagName, children } = node; + + return createElement( + tagName, + { + ...properties, + key, + className: classNames(properties.className), + }, + children && children.map((el, i) => nodeToHtml(el, i, nodes, depth + 1)) + ); + } + + return {node.value}; +}; + +export const highlightByLine = (children: string, language: string) => { + return wrapLines(addLineData(highlight(children, language))); +};