|
6 | 6 | * Side Public License, v 1. |
7 | 7 | */ |
8 | 8 |
|
9 | | -import React from 'react'; |
| 9 | +import React, { useEffect, useState } from 'react'; |
10 | 10 | import type { Meta, StoryObj, ReactRenderer } from '@storybook/react'; |
11 | 11 | import type { PlayFunctionContext } from '@storybook/csf'; |
| 12 | +import { css } from '@emotion/react'; |
12 | 13 |
|
13 | 14 | import { within } from '../../../.storybook/test'; |
14 | 15 | import { LOKI_SELECTORS } from '../../../.storybook/loki'; |
| 16 | +import { mathWithUnits } from '../../global_styling'; |
15 | 17 |
|
16 | 18 | import { EuiCodeBlock, EuiCodeBlockProps } from './code_block'; |
17 | 19 | import { expect, userEvent } from '@storybook/test'; |
| 20 | +import { EuiButton } from '../button'; |
| 21 | +import { EuiFlexGroup, EuiFlexItem } from '../flex'; |
18 | 22 |
|
19 | 23 | const meta: Meta<EuiCodeBlockProps> = { |
20 | 24 | title: 'Editors & Syntax/EuiCodeBlock', |
@@ -148,3 +152,97 @@ export const HighContrast: Story = { |
148 | 152 | ],`, |
149 | 153 | }, |
150 | 154 | }; |
| 155 | + |
| 156 | +/* This Story verifies that updated data in a virtualized code block is |
| 157 | +rendered correctly and not cut-off after scrolling */ |
| 158 | +export const VirtualizedCodeBlockScrolling: Story = { |
| 159 | + tags: ['vrt-only'], |
| 160 | + parameters: { |
| 161 | + loki: { |
| 162 | + chromeSelector: LOKI_SELECTORS.portal, |
| 163 | + }, |
| 164 | + codeSnippet: { |
| 165 | + skip: true, |
| 166 | + }, |
| 167 | + }, |
| 168 | + // use dark mode to better visualize container boundaries |
| 169 | + globals: { colorMode: 'dark' }, |
| 170 | + render: function Render() { |
| 171 | + const [response, setResponse] = useState('{}'); |
| 172 | + const [isLoading, setIsLoading] = useState(false); |
| 173 | + |
| 174 | + const handleSubmit = async () => { |
| 175 | + setIsLoading(true); |
| 176 | + |
| 177 | + try { |
| 178 | + const res = await fetch('https://jsonplaceholder.typicode.com/posts'); |
| 179 | + const data = await res.json(); |
| 180 | + |
| 181 | + setResponse(JSON.stringify(data, null, 2)); |
| 182 | + } catch (error) { |
| 183 | + console.error(error); |
| 184 | + } finally { |
| 185 | + // delay to keep VRT images more stable |
| 186 | + setTimeout(() => { |
| 187 | + setIsLoading(false); |
| 188 | + }); |
| 189 | + } |
| 190 | + }; |
| 191 | + |
| 192 | + useEffect(() => { |
| 193 | + handleSubmit(); |
| 194 | + }, []); |
| 195 | + |
| 196 | + useEffect(() => { |
| 197 | + // scroll the code block after response updates to test if virtualization |
| 198 | + // calculates the scroll height and position correctly |
| 199 | + const pre = document.querySelector('.euiCodeBlock__pre'); |
| 200 | + if (pre) { |
| 201 | + pre.scrollBy({ |
| 202 | + top: 75, |
| 203 | + }); |
| 204 | + |
| 205 | + // trigger a second load after scroll to simulate potential issues |
| 206 | + setTimeout(() => { |
| 207 | + handleSubmit(); |
| 208 | + }, 10); |
| 209 | + } |
| 210 | + }, [response]); |
| 211 | + |
| 212 | + return ( |
| 213 | + <EuiFlexGroup |
| 214 | + direction="row" |
| 215 | + gutterSize="m" |
| 216 | + css={({ euiTheme }) => css` |
| 217 | + block-size: calc( |
| 218 | + 100vh - ${mathWithUnits(euiTheme.size.base, (x) => x * 2)} |
| 219 | + ); |
| 220 | + `} |
| 221 | + > |
| 222 | + <EuiFlexItem grow={false}> |
| 223 | + <EuiButton |
| 224 | + type="submit" |
| 225 | + iconType="sortRight" |
| 226 | + isLoading={isLoading} |
| 227 | + onClick={handleSubmit} |
| 228 | + > |
| 229 | + Submit |
| 230 | + </EuiButton> |
| 231 | + </EuiFlexItem> |
| 232 | + <EuiFlexItem grow={2}> |
| 233 | + <EuiCodeBlock |
| 234 | + language="json" |
| 235 | + fontSize="s" |
| 236 | + paddingSize="m" |
| 237 | + isCopyable={true} |
| 238 | + lineNumbers |
| 239 | + isVirtualized |
| 240 | + overflowHeight="100%" |
| 241 | + > |
| 242 | + {response} |
| 243 | + </EuiCodeBlock> |
| 244 | + </EuiFlexItem> |
| 245 | + </EuiFlexGroup> |
| 246 | + ); |
| 247 | + }, |
| 248 | +}; |
0 commit comments