Skip to content

add scroll-x to graphiql tabs area #3936

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/early-camels-protect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@graphiql/react': patch
'graphiql': patch
---

add scroll-x to graphiql tabs area
17 changes: 13 additions & 4 deletions packages/graphiql-react/src/ui/tabs.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@
padding: 0;
margin: 0;
list-style: none;
overflow: auto;
border-top-left-radius: var(--border-radius-8);
}

/* Hide scrollbar */
.no-scrollbar {
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE and Edge */

&::-webkit-scrollbar {
@apply x:hidden; /* Chrome, Safari and Opera */
}
}

/* trick to shrink multiple tabs, instead of overflow container */
Expand All @@ -21,7 +33,7 @@
background: hsla(var(--color-neutral), var(--alpha-background-light));
position: relative;
display: flex;
max-width: 140px;
flex-shrink: 0;

/* disable shrinking while changing the operation name */
&:not(:focus-within) {
Expand All @@ -41,8 +53,6 @@

.graphiql-tab-button {
border-radius: var(--border-radius-8) var(--border-radius-8) 0 0;
overflow: hidden;
text-overflow: ellipsis;
padding: var(--px-4) 28px var(--px-4) var(--px-8);

&:hover {
Expand All @@ -57,7 +67,6 @@
transform: translateY(-50%);
display: none;
background: var(--bg);
box-shadow: -10px 0 10px 0 var(--bg);
padding: var(--px-6);
line-height: 0;

Expand Down
6 changes: 6 additions & 0 deletions packages/graphiql-react/src/utility/resize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,12 @@ export function useDragResize({
direction === 'horizontal' ? 'clientWidth' : 'clientHeight';

function handleMouseDown(downEvent: MouseEvent) {
const isClickOnCurrentElement =
downEvent.target === downEvent.currentTarget;
if (!isClickOnCurrentElement) {
return;
}

downEvent.preventDefault();

// Distance between the start of the drag bar and the exact point where
Expand Down
9 changes: 4 additions & 5 deletions packages/graphiql/src/GraphiQL.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ describe('GraphiQL', () => {

await waitFor(() => {
// 700 / (900 - 700) = 3.5
expect(editors.parentElement!.style.flex).toEqual('3.5');
expect(editors.style.flex).toEqual('3.5');
});

clientWidthSpy.mockRestore();
Expand Down Expand Up @@ -351,10 +351,9 @@ describe('GraphiQL', () => {

await waitFor(() => {
// 797 / (1200 - 797) = 1.977667493796526
expect(
container.querySelector('.graphiql-plugin')!.parentElement!.style
.flex,
).toBe('1.977667493796526');
expect(container.querySelector('.graphiql-plugin')!.style.flex).toBe(
'1.977667493796526',
);
});

clientWidthSpy.mockRestore();
Expand Down
246 changes: 123 additions & 123 deletions packages/graphiql/src/GraphiQL.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -519,15 +519,14 @@ export const GraphiQLInterface: FC<GraphiQLInterfaceProps> = props => {
<div className="graphiql-main">
<div
ref={pluginResize.firstRef}
className="graphiql-plugin"
style={{
// Make sure the container shrinks when containing long
// non-breaking texts
minWidth: '200px',
}}
>
<div className="graphiql-plugin">
{PluginContent ? <PluginContent /> : null}
</div>
{PluginContent ? <PluginContent /> : null}
</div>
{pluginContext?.visiblePlugin && (
<div
Expand All @@ -541,6 +540,7 @@ export const GraphiQLInterface: FC<GraphiQLInterfaceProps> = props => {
values={editorContext.tabs}
onReorder={handleReorder}
aria-label="Select active operation"
className="no-scrollbar"
>
{editorContext.tabs.map((tab, index, tabs) => (
<Tab
Expand Down Expand Up @@ -577,141 +577,136 @@ export const GraphiQLInterface: FC<GraphiQLInterfaceProps> = props => {
id="graphiql-session" // used by aria-controls="graphiql-session"
aria-labelledby={`${TAB_CLASS_PREFIX}${editorContext.activeTabIndex}`}
>
<div ref={editorResize.firstRef}>
<div className="graphiql-editors">
<div ref={editorToolsResize.firstRef}>
<section
className="graphiql-query-editor"
aria-label="Query Editor"
>
<QueryEditor
editorTheme={props.editorTheme}
keyMap={props.keyMap}
onClickReference={onClickReference}
onCopyQuery={props.onCopyQuery}
onPrettifyQuery={props.onPrettifyQuery}
onEdit={props.onEditQuery}
readOnly={props.readOnly}
/>
<div
className="graphiql-toolbar"
role="toolbar"
aria-label="Editor Commands"
>
<ExecuteButton />
{toolbar}
</div>
</section>
<div className="graphiql-editors" ref={editorResize.firstRef}>
<section
className="graphiql-query-editor"
aria-label="Query Editor"
ref={editorToolsResize.firstRef}
>
<QueryEditor
editorTheme={props.editorTheme}
keyMap={props.keyMap}
onClickReference={onClickReference}
onCopyQuery={props.onCopyQuery}
onPrettifyQuery={props.onPrettifyQuery}
onEdit={props.onEditQuery}
readOnly={props.readOnly}
/>
<div
className="graphiql-toolbar"
role="toolbar"
aria-label="Editor Commands"
>
<ExecuteButton />
{toolbar}
</div>
</section>

<div ref={editorToolsResize.dragBarRef}>
<div className="graphiql-editor-tools">
<UnStyledButton
type="button"
className={cn(
activeSecondaryEditor === 'variables' &&
editorToolsResize.hiddenElement !== 'second' &&
'active',
)}
onClick={handleToolsTabClick}
data-name="variables"
>
Variables
</UnStyledButton>
{isHeadersEditorEnabled && (
<UnStyledButton
type="button"
className={cn(
activeSecondaryEditor === 'headers' &&
editorToolsResize.hiddenElement !== 'second' &&
'active',
)}
onClick={handleToolsTabClick}
data-name="headers"
>
Headers
</UnStyledButton>
<div
ref={editorToolsResize.dragBarRef}
className="graphiql-editor-tools"
>
<UnStyledButton
type="button"
className={cn(
activeSecondaryEditor === 'variables' &&
editorToolsResize.hiddenElement !== 'second' &&
'active',
)}
onClick={handleToolsTabClick}
data-name="variables"
>
Variables
</UnStyledButton>
{isHeadersEditorEnabled && (
<UnStyledButton
type="button"
className={cn(
activeSecondaryEditor === 'headers' &&
editorToolsResize.hiddenElement !== 'second' &&
'active',
)}
onClick={handleToolsTabClick}
data-name="headers"
>
Headers
</UnStyledButton>
)}

<Tooltip
label={
editorToolsResize.hiddenElement === 'second'
? 'Show editor tools'
: 'Hide editor tools'
}
>
<UnStyledButton
type="button"
onClick={toggleEditorTools}
aria-label={
editorToolsResize.hiddenElement === 'second'
? 'Show editor tools'
: 'Hide editor tools'
}
className="graphiql-toggle-editor-tools"
>
{editorToolsResize.hiddenElement === 'second' ? (
<ChevronUpIcon
className="graphiql-chevron-icon"
aria-hidden="true"
/>
) : (
<ChevronDownIcon
className="graphiql-chevron-icon"
aria-hidden="true"
/>
)}
</UnStyledButton>
</Tooltip>
</div>
</div>

<div ref={editorToolsResize.secondRef}>
<section
className="graphiql-editor-tool"
<Tooltip
label={
editorToolsResize.hiddenElement === 'second'
? 'Show editor tools'
: 'Hide editor tools'
}
>
<UnStyledButton
type="button"
onClick={toggleEditorTools}
aria-label={
activeSecondaryEditor === 'variables'
? 'Variables'
: 'Headers'
editorToolsResize.hiddenElement === 'second'
? 'Show editor tools'
: 'Hide editor tools'
}
className="graphiql-toggle-editor-tools"
>
<VariableEditor
editorTheme={props.editorTheme}
isHidden={activeSecondaryEditor !== 'variables'}
keyMap={props.keyMap}
onEdit={props.onEditVariables}
onClickReference={onClickReference}
readOnly={props.readOnly}
/>
{isHeadersEditorEnabled && (
<HeaderEditor
editorTheme={props.editorTheme}
isHidden={activeSecondaryEditor !== 'headers'}
keyMap={props.keyMap}
onEdit={props.onEditHeaders}
readOnly={props.readOnly}
{editorToolsResize.hiddenElement === 'second' ? (
<ChevronUpIcon
className="graphiql-chevron-icon"
aria-hidden="true"
/>
) : (
<ChevronDownIcon
className="graphiql-chevron-icon"
aria-hidden="true"
/>
)}
</section>
</div>
</UnStyledButton>
</Tooltip>
</div>

<section
className="graphiql-editor-tool"
aria-label={
activeSecondaryEditor === 'variables'
? 'Variables'
: 'Headers'
}
ref={editorToolsResize.secondRef}
>
<VariableEditor
editorTheme={props.editorTheme}
isHidden={activeSecondaryEditor !== 'variables'}
keyMap={props.keyMap}
onEdit={props.onEditVariables}
onClickReference={onClickReference}
readOnly={props.readOnly}
/>
{isHeadersEditorEnabled && (
<HeaderEditor
editorTheme={props.editorTheme}
isHidden={activeSecondaryEditor !== 'headers'}
keyMap={props.keyMap}
onEdit={props.onEditHeaders}
readOnly={props.readOnly}
/>
)}
</section>
</div>

<div
className="graphiql-horizontal-drag-bar"
ref={editorResize.dragBarRef}
/>

<div ref={editorResize.secondRef}>
<div className="graphiql-response">
{executionContext.isFetching ? <Spinner /> : null}
<ResponseEditor
editorTheme={props.editorTheme}
responseTooltip={props.responseTooltip}
keyMap={props.keyMap}
/>
{footer}
</div>
<div className="graphiql-response" ref={editorResize.secondRef}>
{executionContext.isFetching ? <Spinner /> : null}
<ResponseEditor
editorTheme={props.editorTheme}
responseTooltip={props.responseTooltip}
keyMap={props.keyMap}
/>
{footer}
</div>
</div>
</div>
Expand All @@ -727,7 +722,7 @@ export const GraphiQLInterface: FC<GraphiQLInterfaceProps> = props => {
<Dialog.Close />
</div>
<div className="graphiql-dialog-section">
<ShortKeys keyMap={props.keyMap || 'sublime'} />
<ShortKeys keyMap={props.keyMap} />
</div>
</Dialog>
<Dialog
Expand Down Expand Up @@ -856,7 +851,12 @@ const SHORT_KEYS = Object.entries({
'Re-fetch schema using introspection': ['Ctrl', 'Shift', 'R'],
});

const ShortKeys: FC<{ keyMap: string }> = ({ keyMap }) => {
interface ShortKeysProps {
/** @default 'sublime' */
keyMap?: string;
}

const ShortKeys: FC<ShortKeysProps> = ({ keyMap = 'sublime' }) => {
return (
<div>
<table className="graphiql-table">
Expand Down
Loading