diff --git a/.changeset/five-pillows-fail.md b/.changeset/five-pillows-fail.md index 8fda40b2884..b0bf36e27bb 100644 --- a/.changeset/five-pillows-fail.md +++ b/.changeset/five-pillows-fail.md @@ -3,7 +3,7 @@ --- Add new components: -- UI components (`Button`, `Dropdown`, `Spinner`, `Tab`, `TabAddButton`, `Tabs`, `UnStyledButton` and lots of icon components) +- UI components (`Button`, `Dropdown`, `Spinner`, `Tab`, `Tabs`, `UnStyledButton` and lots of icon components) - Editor components (`QueryEditor`, `VariableEditor`, `HeaderEditor` and `ResponseEditor`) - Toolbar components (`ExecuteButton` and `ToolbarButton`) - Docs components (`Argument`, `DefaultValue`, `DeprecationReason`, `Directive`, `DocExplorer`, `ExplorerSection`, `FieldDocumentation`, `FieldLink`, `SchemaDocumentation`, `Search`, `TypeDocumentation` and `TypeLink`) diff --git a/.changeset/spotty-fans-lie.md b/.changeset/spotty-fans-lie.md new file mode 100644 index 00000000000..c34b598a104 --- /dev/null +++ b/.changeset/spotty-fans-lie.md @@ -0,0 +1,11 @@ +--- +'graphiql': major +--- + +BREAKING: Tabs are now always enabled. The `tabs` prop has therefore been replaced with a prop `onTabChange`. If you used the `tabs` prop before to pass this function you can change your implementation like so: +```diff + {/* do something */} }} ++ onTabChange={(tabState) => {/* do something */}} +/> +``` diff --git a/packages/graphiql-react/src/icons/index.tsx b/packages/graphiql-react/src/icons/index.tsx index aaadb85fd1a..cc336ed8561 100644 --- a/packages/graphiql-react/src/icons/index.tsx +++ b/packages/graphiql-react/src/icons/index.tsx @@ -18,6 +18,7 @@ import _MagnifyingGlassIcon from './magnifying-glass.svg'; import _MergeIcon from './merge.svg'; import _PenIcon from './pen.svg'; import _PlayIcon from './play.svg'; +import _PlusIcon from './plus.svg'; import _PrettifyIcon from './prettify.svg'; import _RootTypeIcon from './root-type.svg'; import _SettingsIcon from './settings.svg'; @@ -67,6 +68,7 @@ export const MagnifyingGlassIcon = generateIcon( export const MergeIcon = generateIcon(_MergeIcon, 'merge icon'); export const PenIcon = generateIcon(_PenIcon, 'pen icon'); export const PlayIcon = generateIcon(_PlayIcon, 'play icon'); +export const PlusIcon = generateIcon(_PlusIcon, 'plus icon'); export const PrettifyIcon = generateIcon(_PrettifyIcon, 'prettify icon'); export const RootTypeIcon = generateIcon(_RootTypeIcon, 'root type icon'); export const SettingsIcon = generateIcon(_SettingsIcon, 'settings icon'); diff --git a/packages/graphiql-react/src/icons/plus.svg b/packages/graphiql-react/src/icons/plus.svg new file mode 100644 index 00000000000..5d02b3c4a5f --- /dev/null +++ b/packages/graphiql-react/src/icons/plus.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/graphiql-react/src/style/root.css b/packages/graphiql-react/src/style/root.css index 3465c504a70..6de75b9dbe3 100644 --- a/packages/graphiql-react/src/style/root.css +++ b/packages/graphiql-react/src/style/root.css @@ -61,6 +61,7 @@ /* Layout */ --sidebar-width: 44px; --toolbar-width: 40px; + --session-header-height: 51px; } .graphiql-container, diff --git a/packages/graphiql-react/src/ui/tabs.css b/packages/graphiql-react/src/ui/tabs.css new file mode 100644 index 00000000000..5c792816c77 --- /dev/null +++ b/packages/graphiql-react/src/ui/tabs.css @@ -0,0 +1,44 @@ +.graphiql-tabs { + display: flex; + padding: var(--px-12); + + & > :not(:first-child) { + margin-left: var(--px-12); + } +} + +.graphiql-tab { + align-items: stretch; + border-radius: var(--border-radius-8); + color: var(--color-neutral-60); + display: flex; + + & > button.graphiql-tab-close { + visibility: hidden; + } + &.graphiql-tab-active > button.graphiql-tab-close, + &:hover > button.graphiql-tab-close, + &:focus-within > button.graphiql-tab-close { + visibility: unset; + } + + &.graphiql-tab-active { + background-color: var(--color-neutral-15); + color: var(--color-neutral-100); + } +} + +button.graphiql-tab-button { + padding: var(--px-4) 0 var(--px-4) var(--px-8); +} + +button.graphiql-tab-close { + align-items: center; + display: flex; + padding: var(--px-4) var(--px-8); + + & > svg { + height: var(--px-8); + width: var(--px-8); + } +} diff --git a/packages/graphiql-react/src/ui/tabs.tsx b/packages/graphiql-react/src/ui/tabs.tsx index e7628337740..7e4c5658118 100644 --- a/packages/graphiql-react/src/ui/tabs.tsx +++ b/packages/graphiql-react/src/ui/tabs.tsx @@ -1,67 +1,66 @@ -function TabCloseButton(props: { onClick: () => void }) { - return ( -
{ - ev.stopPropagation(); - props.onClick(); - }} - /> - ); -} +import { CloseIcon } from '../icons'; +import { compose } from '../utility/compose'; +import { UnStyledButton } from './button'; + +import './tabs.css'; type TabProps = { - isActive: boolean; - title: string; - isCloseable: boolean; - onSelect: () => void; - onClose: () => void; - tabProps?: React.ButtonHTMLAttributes<{}>; + isActive?: boolean; }; -/** - * Generic tab component that implements wai-aria tab spec - */ -export function Tab(props: TabProps): React.ReactElement { +export function Tab({ + isActive, + ...props +}: TabProps & JSX.IntrinsicElements['div']) { return ( -
+ ); +} + +function TabButton(props: JSX.IntrinsicElements['button']) { + return ( + - {props.title} - {props.isCloseable ? ( - props.onClose()} /> - ) : null} - + className={compose('graphiql-tab-button', props.className)}> + {props.children} + ); } -export function TabAddButton(props: { onClick: () => void }) { +Tab.Button = TabButton; + +function TabClose(props: JSX.IntrinsicElements['button']) { return ( - + + + ); } -type TabsProps = { - children: Array; - tabsProps?: React.HTMLAttributes<{}>; -}; -/** - * Generic tablist component that implements wai-aria tab spec - */ -export function Tabs(props: TabsProps) { +Tab.Close = TabClose; + +export function Tabs(props: JSX.IntrinsicElements['div']) { return ( -
+
{props.children}
); diff --git a/packages/graphiql-react/src/utility/compose.ts b/packages/graphiql-react/src/utility/compose.ts new file mode 100644 index 00000000000..151f47ba2a2 --- /dev/null +++ b/packages/graphiql-react/src/utility/compose.ts @@ -0,0 +1,9 @@ +export function compose(...classes: (string | null | undefined)[]) { + let result = ''; + for (const c of classes) { + if (c) { + result += (result ? ' ' : '') + c; + } + } + return result; +} diff --git a/packages/graphiql/__mocks__/@graphiql/react.tsx b/packages/graphiql/__mocks__/@graphiql/react.tsx index bfbead202f0..4a8c0dca407 100644 --- a/packages/graphiql/__mocks__/@graphiql/react.tsx +++ b/packages/graphiql/__mocks__/@graphiql/react.tsx @@ -55,6 +55,7 @@ export { onHasCompletion, PenIcon, PlayIcon, + PlusIcon, PrettifyIcon, RootTypeIcon, SchemaContext, @@ -69,7 +70,6 @@ export { StorageContext, StorageContextProvider, Tab, - TabAddButton, Tabs, ToolbarButton, TypeDocumentation, diff --git a/packages/graphiql/cypress/integration/init.spec.ts b/packages/graphiql/cypress/integration/init.spec.ts index 040548bbfc7..98e5856cbf9 100644 --- a/packages/graphiql/cypress/integration/init.spec.ts +++ b/packages/graphiql/cypress/integration/init.spec.ts @@ -31,8 +31,7 @@ describe('GraphiQL On Initialization', () => { const containers = [ '#graphiql', '.graphiql-container', - '.topBarWrap', - '.editorWrap', + '.graphiql-sessions', '.graphiql-editors', '.graphiql-response', '.graphiql-editor-tool', diff --git a/packages/graphiql/cypress/integration/tabs.spec.ts b/packages/graphiql/cypress/integration/tabs.spec.ts index 5112c8e05ce..03c4fc9da41 100644 --- a/packages/graphiql/cypress/integration/tabs.spec.ts +++ b/packages/graphiql/cypress/integration/tabs.spec.ts @@ -1,25 +1,26 @@ describe('Tabs', () => { it('Should store editor contents when switching between tabs', () => { cy.visit('/?query='); - cy.get('#session-tab-0').should('have.text', ''); + + // Assert that no tab visible when there's only one session + cy.get('#graphiql-session-tab-0').should('not.exist'); // Enter a query without operation name cy.get('.graphiql-query-editor textarea') .type('{id', { force: true }) .wait(500); - cy.get('#session-tab-0').should('have.text', ''); // Run the query cy.clickExecuteQuery().wait(500); // Open a new tab - cy.get('.tab-add').click(); + cy.get('.graphiql-tab-add').click(); // Enter a query cy.get('.graphiql-query-editor textarea') .type('query Foo {image', { force: true }) .wait(500); - cy.get('#session-tab-1').should('have.text', 'Foo'); + cy.get('#graphiql-session-tab-1').should('have.text', 'Foo'); // Enter variables cy.get('.graphiql-editor-tool textarea') @@ -36,11 +37,11 @@ describe('Tabs', () => { cy.clickExecuteQuery().wait(500); // Switch back to the first tab - cy.get('#session-tab-0').click(); + cy.get('#graphiql-session-tab-0').click(); // Assert tab titles - cy.get('#session-tab-0').should('have.text', ''); - cy.get('#session-tab-1').should('have.text', 'Foo'); + cy.get('#graphiql-session-tab-0').should('have.text', ''); + cy.get('#graphiql-session-tab-1').should('have.text', 'Foo'); // Assert editor values cy.assertHasValues({ @@ -51,11 +52,11 @@ describe('Tabs', () => { }); // Switch back to the second tab - cy.get('#session-tab-1').click(); + cy.get('#graphiql-session-tab-1').click(); // Assert tab titles - cy.get('#session-tab-0').should('have.text', ''); - cy.get('#session-tab-1').should('have.text', 'Foo'); + cy.get('#graphiql-session-tab-0').should('have.text', ''); + cy.get('#graphiql-session-tab-1').should('have.text', 'Foo'); // Assert editor values cy.assertHasValues({ @@ -66,10 +67,10 @@ describe('Tabs', () => { }); // Close tab - cy.get('#session-tab-1 .close').click(); + cy.get('#graphiql-session-tab-1 + .graphiql-tab-close').click(); - // Assert tab titles - cy.get('#session-tab-0').should('have.text', ''); + // Assert that no tab visible when there's only one session + cy.get('#graphiql-session-tab-0').should('not.exist'); // Assert editor values cy.assertHasValues({ diff --git a/packages/graphiql/src/cdn.ts b/packages/graphiql/src/cdn.ts index 134762717d4..70d782e0d43 100644 --- a/packages/graphiql/src/cdn.ts +++ b/packages/graphiql/src/cdn.ts @@ -13,9 +13,6 @@ import '@graphiql/react/font/fira-code.css'; import '@graphiql/react/dist/style.css'; import './style.css'; -// Legacy styles -import './css/app.css'; - import { GraphiQL } from './components/GraphiQL'; // add the static function here for CDN only. otherwise, doing this in the component could // add unwanted dependencies to the bundle. diff --git a/packages/graphiql/src/components/GraphiQL.tsx b/packages/graphiql/src/components/GraphiQL.tsx index 868a109260b..e98f523389d 100644 --- a/packages/graphiql/src/components/GraphiQL.tsx +++ b/packages/graphiql/src/components/GraphiQL.tsx @@ -38,6 +38,7 @@ import { HistoryIcon, KeyboardShortcutIcon, MergeIcon, + PlusIcon, PrettifyIcon, QueryEditor, ResponseEditor, @@ -46,7 +47,6 @@ import { Spinner, StorageContextProvider, Tab, - TabAddButton, Tabs, ToolbarButton, UnStyledButton, @@ -292,23 +292,11 @@ export type GraphiQLProps = { * Callback that is invoked once a remote schema has been fetched. */ onSchemaChange?: (schema: GraphQLSchema) => void; - /** - * Content to place before the top bar (logo). - */ - beforeTopBarContent?: React.ReactElement | null; /** - * Whether tabs should be enabled. - * default: false + * Callback that is invoked onTabChange. */ - tabs?: - | boolean - | { - /** - * Callback that is invoked onTabChange. - */ - onTabChange?: (tab: TabsState) => void; - }; + onTabChange?: (tab: TabsState) => void; children?: ReactNode; }; @@ -445,6 +433,7 @@ const GraphiQLProviders: ForwardRefExoticComponent< introspectionQueryName, maxHistoryLength, onSchemaChange, + onTabChange, onToggleHistory, onToggleDocs, storage, @@ -467,9 +456,7 @@ const GraphiQLProviders: ForwardRefExoticComponent< @@ -514,6 +501,7 @@ type GraphiQLWithContextProviderProps = Omit< | 'introspectionQueryName' | 'maxHistoryLength' | 'onSchemaChange' + | 'onTabChange' | 'onToggleDocs' | 'onToggleHistory' | 'query' @@ -772,54 +760,75 @@ class GraphiQLWithContext extends React.Component< ) : null}
-
-
- {this.props.beforeTopBarContent} -
{logo}
-
- {this.props.tabs ? ( - - {this.props.editorContext.tabs.map((tab, index) => ( - 1} - onSelect={() => { - this.props.executionContext.stop(); - this.props.editorContext.changeTab(index); - }} - onClose={() => { - if (this.props.editorContext.activeTabIndex === index) { - this.props.executionContext.stop(); - } - this.props.editorContext.closeTab(index); - }} - tabProps={{ - 'aria-controls': 'graphiql-session', - id: `session-tab-${index}`, - }} - /> - ))} - { - this.props.editorContext.addTab(); - }} - /> +
+
+ + {this.props.editorContext.tabs.length > 1 ? ( + <> + {this.props.editorContext.tabs.map((tab, index) => ( + + { + this.props.executionContext.stop(); + this.props.editorContext.changeTab(index); + }}> + {tab.title} + + { + if ( + this.props.editorContext.activeTabIndex === + index + ) { + this.props.executionContext.stop(); + } + this.props.editorContext.closeTab(index); + }} + /> + + ))} + { + this.props.editorContext.addTab(); + }}> + + + + ) : null} - ) : null} +
+ {this.props.editorContext.tabs.length === 1 ? ( + { + this.props.editorContext.addTab(); + }}> + + + ) : null} +
{logo}
+
+
+ aria-labelledby={`graphiql-session-tab-${this.props.editorContext.activeTabIndex}`}>
-
+
{ }); describe('Tabs', () => { - it('not enabled by default', () => { + it('show tabs if there are more than one', () => { const { container } = render(); - expect(container.querySelector('.tabs')).not.toBeInTheDocument(); - }); - it('enable tabs via "tabs" property boolean', () => { - const { container } = render(); - expect(container.querySelector('.tabs')).toBeInTheDocument(); - }); - it('enable tabs via "tabs" property object', () => { - const { container } = render( - , - ); - expect(container.querySelector('.tabs')).toBeInTheDocument(); - }); - it('only one tab is open by default', () => { - const { container } = render(); - expect(container.querySelectorAll('.tabs .tab')).toHaveLength(1); - }); - it('single tab has no close button', () => { - const { container } = render(); - expect(container.querySelector('.tab .close')).not.toBeInTheDocument(); - }); - it('open multiple tabs', () => { - const { container } = render(); - expect(container.querySelectorAll('.tabs .tab')).toHaveLength(1); - fireEvent.click(container.querySelector('.tab-add')); - expect(container.querySelectorAll('.tabs .tab')).toHaveLength(2); - fireEvent.click(container.querySelector('.tab-add')); - expect(container.querySelectorAll('.tabs .tab')).toHaveLength(3); + + expect( + container.querySelectorAll('.graphiql-tabs .graphiql-tab'), + ).toHaveLength(0); + + fireEvent.click(container.querySelector('.graphiql-tab-add')); + expect( + container.querySelectorAll('.graphiql-tabs .graphiql-tab'), + ).toHaveLength(2); + + fireEvent.click(container.querySelector('.graphiql-tab-add')); + expect( + container.querySelectorAll('.graphiql-tabs .graphiql-tab'), + ).toHaveLength(3); }); it('each tab has a close button when multiple tabs are open', () => { - const { container } = render(); - expect(container.querySelectorAll('.tab .close')).toHaveLength(0); - fireEvent.click(container.querySelector('.tab-add')); - expect(container.querySelectorAll('.tab .close')).toHaveLength(2); - fireEvent.click(container.querySelector('.tab-add')); - expect(container.querySelectorAll('.tab .close')).toHaveLength(3); + const { container } = render(); + + expect( + container.querySelectorAll('.graphiql-tab .graphiql-tab-close'), + ).toHaveLength(0); + + fireEvent.click(container.querySelector('.graphiql-tab-add')); + expect( + container.querySelectorAll('.graphiql-tab .graphiql-tab-close'), + ).toHaveLength(2); + + fireEvent.click(container.querySelector('.graphiql-tab-add')); + expect( + container.querySelectorAll('.graphiql-tab .graphiql-tab-close'), + ).toHaveLength(3); }); it('close button removes a tab', () => { - const { container } = render(); - fireEvent.click(container.querySelector('.tab-add')); - expect(container.querySelectorAll('.tab .close')).toHaveLength(2); - fireEvent.click(container.querySelector('.tab .close')); - expect(container.querySelectorAll('.tabs .tab')).toHaveLength(1); - expect(container.querySelectorAll('.tab .close')).toHaveLength(0); + const { container } = render(); + + fireEvent.click(container.querySelector('.graphiql-tab-add')); + + expect( + container.querySelectorAll('.graphiql-tab .graphiql-tab-close'), + ).toHaveLength(2); + + fireEvent.click( + container.querySelector('.graphiql-tab .graphiql-tab-close'), + ); + expect( + container.querySelectorAll('.graphiql-tabs .graphiql-tab'), + ).toHaveLength(0); + expect( + container.querySelectorAll('.graphiql-tab .graphiql-tab-close'), + ).toHaveLength(0); }); }); }); diff --git a/packages/graphiql/src/css/app.css b/packages/graphiql/src/css/app.css deleted file mode 100644 index 06fb746a320..00000000000 --- a/packages/graphiql/src/css/app.css +++ /dev/null @@ -1,287 +0,0 @@ -.graphiql-container .editorWrap { - display: flex; - flex-direction: column; - flex: 1; - overflow-x: hidden; -} - -.graphiql-container .title { - font-size: 18px; -} - -.graphiql-container .title em { - font-size: 19px; -} - -.graphiql-container .topBarWrap { - display: flex; - flex-direction: row; -} - -.graphiql-container .topBar { - align-items: center; - background: linear-gradient(#f7f7f7, #e2e2e2); - border-bottom: 1px solid #d0d0d0; - cursor: default; - display: flex; - flex-direction: row; - flex: 1; - height: 34px; - overflow-y: visible; - padding: 7px 14px 6px; - user-select: none; -} - -.graphiql-container .toolbar { - overflow-x: visible; - display: flex; -} - -.graphiql-container .docExplorerShow, -.graphiql-container .historyShow { - background: linear-gradient(#f7f7f7, #e2e2e2); - border-radius: 0; - border-bottom: 1px solid #d0d0d0; - border-right: none; - border-top: none; - color: #3b5998; - cursor: pointer; - font-size: 14px; - margin: 0; - padding: 2px 20px 0 18px; -} - -.graphiql-container .docExplorerShow { - border-left: 1px solid rgba(0, 0, 0, 0.2); -} - -.graphiql-container .historyShow { - border-right: 1px solid rgba(0, 0, 0, 0.2); - border-left: 0; -} - -.graphiql-container .docExplorerShow:before { - border-left: 2px solid #3b5998; - border-top: 2px solid #3b5998; - content: ''; - display: inline-block; - height: 9px; - margin: 0 3px -1px 0; - position: relative; - transform: rotate(-45deg); - width: 9px; -} - -.graphiql-container .historyPaneWrap { - min-width: 230px; - z-index: 5; -} - -.graphiql-container .docExplorerHide { - cursor: pointer; - font-size: 18px; - margin: -7px -8px -6px 0; - padding: 18px 16px 15px 12px; - background: 0; - border: 0; - line-height: 14px; -} - -.graphiql-container .result-window { - --editor-background: var(--color-neutral-7); - flex: 1; - height: 100%; - position: relative; -} - -.graphiql-container .toolbar-menu, -.graphiql-container .toolbar-select { - position: relative; -} - -.graphiql-container .toolbar-menu-items, -.graphiql-container .toolbar-select-options { - background: #fff; - box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.25); - margin: 0; - padding: 6px 0; - position: absolute; - z-index: 100; -} - -.graphiql-container .toolbar-menu-items { - left: 1px; - margin-top: -1px; - min-width: 110%; - top: 100%; - visibility: hidden; -} - -.graphiql-container .toolbar-menu-items.open { - visibility: visible; -} - -.graphiql-container .toolbar-select-options { - left: 0; - min-width: 100%; - top: -5px; - visibility: hidden; -} - -.graphiql-container .toolbar-select-options.open { - visibility: visible; -} - -.graphiql-container .toolbar-menu-items > li, -.graphiql-container .toolbar-select-options > li { - cursor: pointer; - display: block; - margin: none; - max-width: 300px; - overflow: hidden; - padding: 2px 20px 4px 11px; - white-space: nowrap; -} - -.graphiql-container .toolbar-menu-items > li.hover, -.graphiql-container .toolbar-menu-items > li:active, -.graphiql-container .toolbar-menu-items > li:hover, -.graphiql-container .toolbar-select-options > li.hover, -.graphiql-container .toolbar-select-options > li:active, -.graphiql-container .toolbar-select-options > li:hover { - background: #e10098; - color: #fff; -} - -.graphiql-container .toolbar-select-options > li > svg { - display: inline; - fill: #666; - margin: 0 -6px 0 6px; - pointer-events: none; - vertical-align: middle; -} - -.graphiql-container .toolbar-select-options > li.hover > svg, -.graphiql-container .toolbar-select-options > li:active > svg, -.graphiql-container .toolbar-select-options > li:hover > svg { - fill: #fff; -} - -.graphiql-container .tabs { - height: 42px; - background-image: linear-gradient(#f7f7f7, #e2e2e2); - display: flex; - align-items: center; -} - -.graphiql-container .tab { - position: relative; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - padding-top: 0; - padding-right: 6px; - padding-left: 14px; - height: 100%; - color: rgba(0, 0, 0, 0.6); - - border-left: 1px solid lightgray; - /* - Needed for `button` components. - */ - border-top-style: none; - border-bottom-style: none; - border-right-style: none; -} - -/* - If it's only one tab, we don't have the X button, so we want more padding. - In the .tabs container, we have one more child - the plus button. - So if this tab is first child and the second last at the same time, - this is the case we want to target. -*/ - -.graphiql-container .tab:first-child:nth-last-child(2) { - padding-right: 14px; -} - -.graphiql-container .tab:hover { - background-image: linear-gradient( - rgba(245, 245, 245, 0.7), - rgba(215, 215, 215, 1) - ); - color: rgba(0, 0, 0, 0.8); -} - -.graphiql-container .tab.active { - background-image: linear-gradient( - rgba(233, 233, 233, 0.7), - rgba(205, 205, 205, 1) - ); - color: black; -} - -/* { - background-image: linear-gradient( - rgba(223, 223, 223, 0.5), - rgba(196, 196, 196, 1) - ); -} */ - -.graphiql-container .tab .close { - display: inline-block; - cursor: pointer; - border: none; - background: transparent; - margin-left: 6px; - padding: 3px 6px; - border-radius: 4px; -} - -.graphiql-container .tab:hover .close, -.graphiql-container .tab.active .close { - opacity: 1; -} - -.graphiql-container .tab .close::before { - content: '✕'; - display: inline-block; - font-weight: bold; - font-size: 12px; - color: rgba(0, 0, 0, 0.7); - height: 14px; -} - -.graphiql-container .tab .close:hover { - background: rgba(0, 0, 0, 0.08); -} - -.graphiql-container .tab .close:active { - background: rgba(0, 0, 0, 0.12); -} - -.graphiql-container .tab-add { - display: flex; - align-items: center; - justify-content: center; - border: none; - background: transparent; - line-height: 1; - font-size: 26px; - padding: 0 8px; - height: 30px; - border-radius: 4px; - color: rgba(0, 0, 0, 0.5); - padding-bottom: 3px; - margin-left: 6px; - cursor: pointer; -} - -.graphiql-container .tab-add:hover { - background: rgba(0, 0, 0, 0.06); -} - -.graphiql-container .tab-add:active { - background: rgba(0, 0, 0, 0.1); -} diff --git a/packages/graphiql/src/style.css b/packages/graphiql/src/style.css index 6abf23bb2cc..3c1560aebd3 100644 --- a/packages/graphiql/src/style.css +++ b/packages/graphiql/src/style.css @@ -39,15 +39,57 @@ flex: 1; } -/* The whole session, i.e. editors and response */ -.graphiql-container .graphiql-session { +/* The current session and tabs */ +.graphiql-container .graphiql-sessions { background-color: var(--color-neutral-7); /* Adding the 8px of padding to the inner border radius of the query editor */ border-radius: calc(var(--border-radius-12) + var(--px-8)); display: flex; + flex-direction: column; flex: 1; max-height: 100%; - padding: var(--px-8); + margin: var(--px-16); + margin-left: 0; +} + +/* The session header containing tabs and the logo */ +.graphiql-container .graphiql-session-header { + align-items: center; + display: flex; + justify-content: space-between; + height: var(--session-header-height); +} + +/* The button to add a new tab */ +button.graphiql-tab-add { + margin-left: var(--px-12); +} +button.graphiql-tab-add > svg { + color: var(--color-neutral-60); + display: block; + height: var(--px-16); + width: var(--px-16); +} + +/* The right-hand-side of the session header */ +.graphiql-container .graphiql-session-header-right { + align-items: center; + display: flex; +} + +/* The GraphiQL logo */ +.graphiql-container .graphiql-logo { + color: var(--color-neutral-60); + font-size: var(--font-size-h4); + font-weight: var(--font-weight-medium); + padding: var(--px-12) var(--px-16); +} + +/* The editor of the session */ +.graphiql-container .graphiql-session { + display: flex; + flex: 1; + padding: 0 var(--px-8) var(--px-8); } /* All editors (query, variable, headers) */ @@ -58,6 +100,9 @@ flex: 1; flex-direction: column; } +.graphiql-container .graphiql-editors.full-height { + margin-top: calc(var(--px-8) - var(--session-header-height)); +} /* The query editor and the toolbar */ .graphiql-container .graphiql-query-editor { @@ -126,6 +171,7 @@ /* The response view */ .graphiql-container .graphiql-response { + --editor-background: var(--color-neutral-7); display: flex; flex: 1; flex-direction: column;