Skip to content

Commit 1c47b76

Browse files
authored
Add horizontal scrolling to IDE component on small viewports (#897)
* added horizontal scroll to editor content * added horizontal scroll to editor tabs * scroll to active tab when active tab changes * mock scrollIntoView * update snapshots * add changeset * only scroll in inline direction * remove debug line
1 parent e6dd060 commit 1c47b76

13 files changed

+51
-22
lines changed

.changeset/thirty-swans-yawn.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@primer/react-brand': patch
3+
---
4+
5+
Added horizontal scrolling to `IDE` component when viewed on small viewports

packages/react/src/IDE/IDE.module.css

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.IDE__inner {
2+
--brand-IDE-lineNumber-width: 45px;
23
background: var(--brand-IDE-default-bgColor);
34
border: var(--brand-borderWidth-thin) solid var(--brand-IDE-borderColor);
45
}
@@ -170,10 +171,11 @@
170171
}
171172

172173
.IDE--default .IDE__Editor {
173-
margin-left: var(--base-size-20);
174+
padding-left: var(--base-size-20);
174175
}
175176

176177
.IDE__Editor-tabs {
178+
overflow-x: auto;
177179
display: inline-flex;
178180
background: var(--brand-IDE-default-editor-tabs-bgColor);
179181
}
@@ -187,6 +189,7 @@
187189
}
188190

189191
.IDE__Editor-tab {
192+
flex-shrink: 0;
190193
display: flex;
191194
padding: var(--base-size-8) var(--base-size-12);
192195
align-items: center;
@@ -262,6 +265,7 @@ img.IDE__Editor-tab-icon {
262265
.IDE__Editor-content {
263266
height: 100%;
264267
padding-top: var(--base-size-16);
268+
overflow-x: auto;
265269
}
266270

267271
.IDE--default .IDE__Editor-content {
@@ -281,6 +285,14 @@ img.IDE__Editor-tab-icon {
281285
margin: 0;
282286
}
283287

288+
.IDE__Editor-content-wrapper {
289+
display: flex;
290+
}
291+
292+
.IDE__Editor-content-inner {
293+
flex: 1;
294+
}
295+
284296
.IDE__Editor--small .IDE__Editor-pane,
285297
.IDE__Editor--small .IDE__Editor-lineNumbers {
286298
font-size: calc(var(--base-size-12) - 1px) !important; /* workaround dotcom specificity */
@@ -300,18 +312,19 @@ img.IDE__Editor-tab-icon {
300312
}
301313

302314
.IDE__Editor-pane--suggested {
315+
margin-left: calc(-1 * var(--base-size-64));
316+
padding-left: calc((var(--base-size-64) * 2) - var(--brand-IDE-lineNumber-width)) !important;
303317
background-color: var(--brand-IDE-autoSuggestLine-bgColor);
304318
}
305319

306320
.IDE__Editor-lineNumbers {
307-
position: absolute;
308321
z-index: 1;
309322
font-family: var(--brand-fontStack-monospace);
310323
font-weight: var(--brand-text-weight-200);
311324
font-feature-settings: 'liga' 0, 'calt' 0;
312325
font-variation-settings: normal;
313326
letter-spacing: 0;
314-
width: 45px;
327+
flex: 0 0 var(--brand-IDE-lineNumber-width);
315328
}
316329

317330
.IDE__Editor-lineNumber {
@@ -341,20 +354,17 @@ img.IDE__Editor-tab-icon {
341354
.IDE__Editor pre {
342355
color: var(--brand-color-text-default);
343356
font-family: var(--brand-fontStack-monospace);
344-
padding-left: var(--base-size-64);
357+
padding-left: calc(var(--base-size-64) - var(--brand-IDE-lineNumber-width));
345358
}
346359

347360
pre.IDE__Chat-copilot-indicator {
348361
background-color: var(--brand-IDE-autoSuggest-bgColor);
349362
padding-left: 0;
350-
position: absolute;
351363
display: inline-flex;
352364
padding: var(--base-size-8) var(--base-size-24);
353365
border-radius: var(--brand-borderRadius-small);
354366
gap: var(--base-size-16);
355-
margin: 0;
356-
margin-left: 60px;
357-
margin-top: var(--base-size-4);
367+
margin: var(--base-size-4) 0 0 var(--base-size-16);
358368

359369
box-shadow: -9px 10px 39px 0px rgba(0, 0, 0, 0.25);
360370
}

packages/react/src/IDE/IDE.module.css.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ declare const styles: {
2323
readonly "IDE__Editor-tab-icon": string;
2424
readonly "IDE__Editor-content": string;
2525
readonly "IDE__Editor-pane": string;
26+
readonly "IDE__Editor-content-wrapper": string;
27+
readonly "IDE__Editor-content-inner": string;
2628
readonly "IDE__Editor--small": string;
2729
readonly "IDE__Editor-lineNumbers": string;
2830
readonly "IDE__Editor--medium": string;

packages/react/src/IDE/IDE.test.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,14 @@ describe('IDE', () => {
3939
{name: 'File 3', alternativeText: 'Alt for File 3', code: 'Code for File 3'},
4040
]
4141

42-
afterEach(cleanup)
42+
beforeEach(() => {
43+
Element.prototype.scrollIntoView = jest.fn()
44+
})
45+
46+
afterEach(() => {
47+
cleanup()
48+
jest.clearAllMocks()
49+
})
4350

4451
it('renders main elements correctly into the document', () => {
4552
const {getByTestId} = render(

packages/react/src/IDE/IDE.tsx

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import React, {
1616

1717
import {Avatar, Button, Text, TextInput} from '..'
1818
import type {BaseProps} from '../component-helpers'
19-
import {useTabs} from '../hooks/useTabs'
19+
import {useTabs, type OnTabActivate} from '../hooks/useTabs'
2020

2121
/**
2222
* Design tokens
@@ -404,9 +404,13 @@ const _Editor = memo(
404404
setTimeouts(prev => [...prev, animationEndTimeout])
405405
}, [hasAnimated, isAnimating])
406406

407-
const onTabActivate = useCallback(() => {
408-
resetAnimation()
409-
}, [resetAnimation])
407+
const onTabActivate = useCallback<OnTabActivate>(
408+
(_, activeTabRef) => {
409+
activeTabRef?.scrollIntoView({behavior: 'smooth', block: 'nearest', inline: 'start'})
410+
resetAnimation()
411+
},
412+
[resetAnimation],
413+
)
410414

411415
const tabs = useTabs({
412416
defaultTab: activeTab.toString(),
@@ -454,7 +458,7 @@ const _Editor = memo(
454458
{files.map((file, fileIndex) => (
455459
<div {...tabs.getTabPanelProps(fileIndex.toString())} key={file.name}>
456460
<span className="visually-hidden">{file.alternativeText}</span>
457-
<div aria-hidden="true">
461+
<div className={styles['IDE__Editor-content-wrapper']} aria-hidden="true">
458462
{showLineNumbers && (
459463
<div className={styles['IDE__Editor-lineNumbers']}>
460464
{(Array.isArray(file.code) ? file.code : file.code.split('\n')).map((_, index) => (
@@ -467,6 +471,7 @@ const _Editor = memo(
467471
<div
468472
ref={tabs.activeTab === fileIndex.toString() ? presRef : null}
469473
data-testid={testIds.editorContent}
474+
className={styles['IDE__Editor-content-inner']}
470475
>
471476
{Array.isArray(file.code) ? (
472477
(file.code as string[]).map((line, index) => {
29 Bytes
Loading
29 Bytes
Loading

packages/react/src/hooks/useTabs.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
11
import {useCallback, useState, useRef, type HTMLAttributes, type KeyboardEvent} from 'react'
22
import {useId} from '../hooks/useId'
33

4-
type TabOrientation = 'horizontal' | 'vertical'
4+
export type OnTabActivate = (id: string, activeTabRef?: HTMLElement) => void
55

6-
type UseTabsOptions = {
6+
export type UseTabsOptions = {
77
defaultTab?: string
88
autoActivate?: boolean
9-
onTabActivate?: (id: string) => void
10-
orientation?: TabOrientation
9+
onTabActivate?: OnTabActivate
10+
orientation?: 'horizontal' | 'vertical'
1111
}
1212

13-
type TabState = {
13+
export type TabState = {
1414
activeTab: string | null
1515
focusedTab: string | null
1616
tabs: Set<string>
1717
}
1818

19-
type TabListProps = {
19+
export type TabListProps = {
2020
label?: string
2121
labelledBy?: string
2222
}
2323

24-
type UseTabs = {
24+
export type UseTabs = {
2525
activeTab: string | null
2626
focusedTab: string | null
2727
activateTab: (id: string) => void
@@ -50,7 +50,7 @@ export const useTabs = ({
5050
const nextState = updater(prev)
5151

5252
if (nextState.activeTab && nextState.activeTab !== prev.activeTab) {
53-
onTabActivate?.(nextState.activeTab)
53+
onTabActivate?.(nextState.activeTab, tabRefs.current.get(nextState.activeTab))
5454
}
5555

5656
return nextState

0 commit comments

Comments
 (0)