feat: design revamp

This commit is contained in:
Anoop M D
2025-11-28 05:10:29 +05:30
parent 7ee366eb81
commit fa94efaa24
11 changed files with 406 additions and 80 deletions

View File

@@ -18,6 +18,28 @@ const StyledWrapper = styled.div`
flex-direction: column-reverse;
}
.CodeMirror-linenumber {
text-align: left !important;
padding-left: 3px !important;
}
/* Override default lint highlight background when emphasizing the gutter */
.CodeMirror-lint-line-error,
.CodeMirror-lint-line-warning {
background: none !important;
}
/* Style line numbers when there's a lint issue */
.CodeMirror-lint-line-error .CodeMirror-linenumber {
color: #d32f2f !important;
text-decoration: underline;
}
.CodeMirror-lint-line-warning .CodeMirror-linenumber {
color: #f57c00 !important;
text-decoration: underline;
}
/* Removes the glow outline around the folded json */
.CodeMirror-foldmarker {
text-shadow: none;
@@ -73,41 +95,48 @@ const StyledWrapper = styled.div`
}
}
.cm-s-monokai span.cm-property,
.cm-s-monokai span.cm-attribute {
color: #9cdcfe !important;
}
.cm-s-monokai span.cm-string {
color: #ce9178 !important;
}
.cm-s-monokai span.cm-number {
color: #b5cea8 !important;
}
.cm-s-monokai span.cm-atom {
color: #569cd6 !important;
.cm-s-default, .cm-s-monokai {
span.cm-def {
color: ${(props) => props.theme.codemirror.tokens.definition} !important;
}
span.cm-property {
color: ${(props) => props.theme.codemirror.tokens.property} !important;
}
span.cm-string {
color: ${(props) => props.theme.codemirror.tokens.string} !important;
}
span.cm-number {
color: ${(props) => props.theme.codemirror.tokens.number} !important;
}
span.cm-atom {
color: ${(props) => props.theme.codemirror.tokens.atom} !important;
}
span.cm-variable {
color: ${(props) => props.theme.codemirror.tokens.variable} !important;
}
span.cm-keyword {
color: ${(props) => props.theme.codemirror.tokens.keyword} !important;
}
span.cm-comment {
color: ${(props) => props.theme.codemirror.tokens.comment} !important;
}
span.cm-operator {
color: ${(props) => props.theme.codemirror.tokens.operator} !important;
}
}
/* Variable validation colors */
.cm-variable-valid {
color: green;
color: #5fad89 !important; /* Soft sage */
}
.cm-variable-invalid {
color: red;
color: #d17b7b !important; /* Soft coral */
}
.CodeMirror-search-hint {
display: inline;
}
.cm-s-default span.cm-property {
color: #1f61a0 !important;
}
.cm-s-default span.cm-variable {
color: #397d13 !important;
}
//matching bracket fix
.CodeMirror-matchingbracket {
@@ -126,6 +155,31 @@ const StyledWrapper = styled.div`
.cm-search-current {
background: rgba(255, 193, 7, 0.4);
}
.lint-error-tooltip {
position: fixed;
z-index: 10000;
background: ${(props) => props.theme.codemirror.bg};
border-radius: ${(props) => props.theme.border.radius.base};
padding: 8px 12px;
max-width: 400px;
box-shadow: ${(props) => props.theme.shadow.sm};
font-size: ${(props) => props.theme.font.size.xs};
line-height: 1.5;
pointer-events: none;
.lint-tooltip-message {
padding: 2px 0;
}
.lint-tooltip-message.error {
color: ${(props) => props.theme.colors.text.danger};
}
.lint-tooltip-message.warning {
color: ${(props) => props.theme.colors.text.warning};
}
}
`;
export default StyledWrapper;

View File

@@ -15,6 +15,7 @@ import { JSHINT } from 'jshint';
import stripJsonComments from 'strip-json-comments';
import { getAllVariables } from 'utils/collections';
import { setupLinkAware } from 'utils/codemirror/linkAware';
import { setupLintErrorTooltip } from 'utils/codemirror/lint-errors';
import CodeMirrorSearch from 'components/CodeMirrorSearch';
const CodeMirror = require('codemirror');
@@ -37,7 +38,8 @@ export default class CodeEditor extends React.Component {
this.lintOptions = {
esversion: 11,
expr: true,
asi: true
asi: true,
highlightLines: true
};
this.state = {
@@ -64,7 +66,7 @@ export default class CodeEditor extends React.Component {
matchBrackets: true,
showCursorWhenSelecting: true,
foldGutter: true,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'CodeMirror-lint-markers'],
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
lint: this.lintOptions,
readOnly: this.props.readOnly,
scrollbarStyle: 'overlay',
@@ -207,6 +209,9 @@ export default class CodeEditor extends React.Component {
);
setupLinkAware(editor);
// Setup lint error tooltip on line number hover
this.cleanupLintErrorTooltip = setupLintErrorTooltip(editor);
}
}
@@ -272,6 +277,10 @@ export default class CodeEditor extends React.Component {
this.editor?._destroyLinkAware?.();
this.editor.off('change', this._onEdit);
this.editor.off('scroll', this.onScroll);
// Clean up lint error tooltip
this.cleanupLintErrorTooltip?.();
this.editor = null;
}
}

View File

@@ -12,8 +12,8 @@ const Wrapper = styled.div`
font-size: ${(props) => props.theme.font.size.base};
color: ${(props) => props.theme.dropdown.color};
background-color: ${(props) => props.theme.dropdown.bg};
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(0, 0, 0, 0.05);
border-radius: 10px;
box-shadow: ${(props) => props.theme.shadow.sm};
border-radius: ${(props) => props.theme.border.radius.base};
max-height: 90vh;
overflow-y: auto;
max-width: unset !important;

View File

@@ -2,17 +2,19 @@ import styled from 'styled-components';
const Wrapper = styled.div`
height: 2.3rem;
border: ${(props) => props.theme.requestTabPanel.url.border};
border-radius: ${(props) => props.theme.border.radius.base};
.method-selector-container {
background-color: ${(props) => props.theme.requestTabPanel.url.bg};
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
border-top-left-radius: ${(props) => props.theme.border.radius.base};
border-bottom-left-radius: ${(props) => props.theme.border.radius.base};
}
.input-container {
background-color: ${(props) => props.theme.requestTabPanel.url.bg};
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
border-top-right-radius: ${(props) => props.theme.border.radius.base};
border-bottom-right-radius: ${(props) => props.theme.border.radius.base};
input {
background-color: ${(props) => props.theme.requestTabPanel.url.bg};

View File

@@ -27,7 +27,7 @@ const Icon = forwardRef(function IconComponent(
<input
ref={inputRef}
type="text"
className="font-medium px-2 w-full focus:bg-transparent"
className="px-2 w-full focus:bg-transparent"
value={inputValue}
onChange={handleInputChange}
onBlur={handleBlur}
@@ -46,7 +46,7 @@ const Icon = forwardRef(function IconComponent(
className="cursor-pointer flex items-center text-left w-full"
>
<span
className="font-medium px-2 truncate method-span"
className="px-2 truncate method-span"
id="create-new-request-method"
title={inputValue}
>

View File

@@ -2,17 +2,19 @@ import styled from 'styled-components';
const Wrapper = styled.div`
height: 2.3rem;
border: ${(props) => props.theme.requestTabPanel.url.border};
border-radius: ${(props) => props.theme.border.radius.base};
div.method-selector-container {
background-color: ${(props) => props.theme.requestTabPanel.url.bg};
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
border-top-left-radius: ${(props) => props.theme.border.radius.base};
border-bottom-left-radius: ${(props) => props.theme.border.radius.base};
}
div.input-container {
background-color: ${(props) => props.theme.requestTabPanel.url.bg};
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
border-top-right-radius: ${(props) => props.theme.border.radius.base};
border-bottom-right-radius: ${(props) => props.theme.border.radius.base};
input {
background-color: ${(props) => props.theme.requestTabPanel.url.bg};

View File

@@ -3,11 +3,12 @@ import styled from 'styled-components';
const StyledWrapper = styled.div`
height: 2.3rem;
position: relative;
border: ${(props) => props.theme.requestTabPanel.url.border};
border-radius: ${(props) => props.theme.border.radius.base};
.input-container {
background-color: ${(props) => props.theme.requestTabPanel.url.bg};
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
border-radius: ${(props) => props.theme.border.radius.base};
input {
background-color: ${(props) => props.theme.requestTabPanel.url.bg};

View File

@@ -53,10 +53,16 @@ const Wrapper = styled.div`
right: -3px;
transition: opacity 0.2s ease;
&:hover div.drag-request-border {
width: 2px;
div.drag-request-border {
width: 1px;
height: 100%;
border-left: solid 1px ${(props) => props.theme.sidebar.dragbar};
border-left: solid 1px ${(props) => props.theme.sidebar.dragbar.border};
}
&:hover div.drag-request-border {
width: 1px;
height: 100%;
border-left: solid 1px ${(props) => props.theme.sidebar.dragbar.activeBorder};
}
}
`;

View File

@@ -1,8 +1,31 @@
const colors = {
BRAND: '#546de5',
TEXT: '#d4d4d4',
TEXT_LINK: '#569cd6',
BACKGROUND: '#1e1e1e',
GRAY_1: '#666666',
GRAY_2: '#444444',
GRAY_3: '#252526',
CODEMIRROR_TOKENS: {
DEFINITION: '#9ccc9c', // Softer, brighter sage — better contrast
PROPERTY: '#7dcfff', // Soft sky blue, high clarity without being loud
STRING: '#d7ba7d', // VSCode-like warm string tone
NUMBER: '#4ec9b0', // Standard teal with higher clarity
ATOM: '#c586c0', // Brighter lavender, matches VSCode purple
VARIABLE: '#4fc1ff', // Clear aqua-blue (used widely in dark themes)
KEYWORD: '#c58679', // Coral-ish but muted to avoid eye strain
COMMENT: '#6a9955', // Greenish-slate — very readable & subtle
OPERATOR: '#d4d4d4' // Light gray — consistent with dark mode operators
}
};
const darkTheme = {
brand: '#546de5',
text: '#d4d4d4',
textLink: '#569cd6',
bg: '#1e1e1e',
brand: colors.BRAND,
text: colors.TEXT,
textLink: colors.TEXT_LINK,
bg: colors.BACKGROUND,
font: {
size: {
@@ -15,13 +38,29 @@ const darkTheme = {
}
},
shadow: {
sm: '0 1px 3px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(0, 0, 0, 0.3)',
md: '0 2px 8px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(0, 0, 0, 0.4)',
lg: '0 2px 12px rgba(0, 0, 0, 0.7), 0 0 0 1px rgba(0, 0, 0, 0.4)'
},
border: {
radius: {
sm: '4px',
base: '6px',
md: '8px',
lg: '10px',
xl: '12px'
}
},
colors: {
text: {
green: 'rgb(11 178 126)',
danger: '#f06f57',
muted: '#9d9d9d',
purple: '#cd56d6',
yellow: '#f59e0b'
yellow: '#d9a342'
},
bg: {
danger: '#d03544'
@@ -57,8 +96,11 @@ const darkTheme = {
sidebar: {
color: '#ccc',
muted: '#9d9d9d',
bg: '#252526',
dragbar: '#666666',
bg: colors.GRAY_3,
dragbar: {
border: 'transparent',
activeBorder: colors.GRAY_1
},
badge: {
bg: '#3D3D3D'
@@ -98,8 +140,8 @@ const darkTheme = {
shadow: 'rgb(0 0 0 / 36%) 0px 2px 8px',
separator: '#444',
labelBg: '#4a4949',
selectedBg: '#F59E0B14',
selectedColor: '#F59E0B',
selectedBg: '#d9a34214',
selectedColor: '#d9a342',
mutedText: '#9B9B9B',
primaryText: '#D4D4D4',
secondaryText: '#9CA3AF',
@@ -118,16 +160,17 @@ const darkTheme = {
head: '#d69956'
},
grpc: '#6366f1',
ws: '#f59e0b',
ws: '#d9a342',
gql: '#e535ab'
},
requestTabPanel: {
url: {
bg: '#3D3D3D',
bg: colors.BACKGROUND,
icon: 'rgb(204, 204, 204)',
iconDanger: '#fa5343',
errorHoverBg: '#4a2a2a'
errorHoverBg: '#4a2a2a',
border: `solid 1px ${colors.GRAY_2}`
},
dragbar: {
border: '#444',
@@ -252,7 +295,7 @@ const darkTheme = {
tabs: {
active: {
color: '#CCCCCC',
border: '#F59E0B'
border: '#d9a342'
},
secondary: {
active: {
@@ -287,14 +330,14 @@ const darkTheme = {
},
codemirror: {
bg: '#1e1e1e',
border: '#373737',
bg: colors.BACKGROUND,
border: colors.BACKGROUND,
placeholder: {
color: '#a2a2a2',
opacity: 0.5
},
gutter: {
bg: '#262626'
bg: colors.BACKGROUND
},
variable: {
valid: 'rgb(11 178 126)',
@@ -313,6 +356,17 @@ const darkTheme = {
editorBorder: '#3D3D3D'
}
},
tokens: {
definition: colors.CODEMIRROR_TOKENS.DEFINITION,
property: colors.CODEMIRROR_TOKENS.PROPERTY,
string: colors.CODEMIRROR_TOKENS.STRING,
number: colors.CODEMIRROR_TOKENS.NUMBER,
atom: colors.CODEMIRROR_TOKENS.ATOM,
variable: colors.CODEMIRROR_TOKENS.VARIABLE,
keyword: colors.CODEMIRROR_TOKENS.KEYWORD,
comment: colors.CODEMIRROR_TOKENS.COMMENT,
operator: colors.CODEMIRROR_TOKENS.OPERATOR
},
searchLineHighlightCurrent: 'rgba(120,120,120,0.18)',
searchMatch: '#FFD700',
searchMatchActive: '#FFFF00'
@@ -346,7 +400,7 @@ const darkTheme = {
tooltip: {
bg: '#1f1f1f',
color: '#ffffff',
shortcutColor: '#f59e0b'
shortcutColor: '#d9a342'
},
infoTip: {
@@ -463,7 +517,7 @@ const darkTheme = {
hoverBg: 'rgba(255, 255, 255, 0.05)',
selected: {
bg: 'rgba(245, 158, 11, 0.2)',
border: '#f59e0b'
border: '#d9a342'
},
text: '#d4d4d4',
secondaryText: '#9d9d9d',
@@ -492,8 +546,8 @@ const darkTheme = {
},
examples: {
buttonBg: '#F59E0B1A',
buttonColor: '#F59E0B',
buttonBg: '#d9a3421A',
buttonColor: '#d9a342',
buttonText: '#fff',
buttonIconColor: '#fff',
border: '#444',

View File

@@ -1,8 +1,33 @@
const colors = {
BRAND: '#546de5',
TEXT: 'rgb(52, 52, 52)',
TEXT_LINK: '#1663bb',
BACKGROUND: '#fff',
WHITE: '#fff',
BLACK: '#000',
GRAY_1: '#f8f8f8',
GRAY_2: '#eaeaea',
GRAY_3: '#e5e5e5',
GRAY_4: '#cbcbcb',
CODEMIRROR_TOKENS: {
DEFINITION: '#566f4e', // Deep moss
PROPERTY: '#4b7bbb', // Muted azure
STRING: '#a06e3b', // Warm bronze
NUMBER: '#3d8b7c', // Muted jade
ATOM: '#8169ad', // Soft plum
VARIABLE: '#3f7b6f', // Deep teal
KEYWORD: '#b95d6a', // Muted ruby
COMMENT: '#8997aa', // Cool gray
OPERATOR: '#6b7a8f' // Slate blue
}
};
const lightTheme = {
brand: '#546de5',
text: 'rgb(52, 52, 52)',
textLink: '#1663bb',
bg: '#fff',
brand: colors.BRAND,
text: colors.TEXT,
textLink: colors.TEXT_LINK,
bg: colors.BACKGROUND,
font: {
size: {
@@ -15,10 +40,27 @@ const lightTheme = {
}
},
shadow: {
sm: '0 1px 3px rgba(0, 0, 0, 0.12), 0 0 0 1px rgba(0, 0, 0, 0.05)',
md: '0 2px 8px rgba(0, 0, 0, 0.14), 0 0 0 1px rgba(0, 0, 0, 0.06)',
lg: '0 2px 12px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(0, 0, 0, 0.05)'
},
border: {
radius: {
sm: '4px',
base: '6px',
md: '8px',
lg: '10px',
xl: '12px'
}
},
colors: {
text: {
green: '#047857',
danger: '#B91C1C',
warning: '#f57c00',
muted: '#838383',
purple: '#8e44ad',
yellow: '#d97706'
@@ -57,11 +99,14 @@ const lightTheme = {
sidebar: {
color: 'rgb(52, 52, 52)',
muted: '#4b5563',
bg: '#F3F3F3',
dragbar: 'rgb(200, 200, 200)',
bg: colors.GRAY_1,
dragbar: {
border: colors.GRAY_3,
activeBorder: colors.GRAY_4
},
badge: {
bg: '#e1e1e1'
bg: '#eaeaea'
},
search: {
@@ -71,11 +116,11 @@ const lightTheme = {
collection: {
item: {
bg: '#e1e1e1',
hoverBg: '#e7e7e7',
indentBorder: 'solid 1px #e1e1e1',
bg: colors.GRAY_2,
hoverBg: colors.GRAY_2,
indentBorder: `solid 1px ${colors.GRAY_3}`,
active: {
indentBorder: 'solid 1px #d0d0d0'
indentBorder: `solid 1px ${colors.GRAY_3}`
}
}
},
@@ -124,10 +169,11 @@ const lightTheme = {
requestTabPanel: {
url: {
bg: '#f3f3f3',
bg: colors.WHITE,
icon: '#515151',
iconDanger: '#d91f11',
errorHoverBg: '#fef2f2'
errorHoverBg: '#fef2f2',
border: `solid 1px ${colors.GRAY_3}`
},
dragbar: {
border: '#efefef',
@@ -288,14 +334,14 @@ const lightTheme = {
},
codemirror: {
bg: 'white',
border: '#efefef',
bg: colors.WHITE,
border: colors.WHITE,
placeholder: {
color: '#a2a2a2',
opacity: 0.75
},
gutter: {
bg: '#f3f3f3'
bg: colors.WHITE
},
variable: {
valid: '#047857',
@@ -314,6 +360,17 @@ const lightTheme = {
editorBorder: '#EFEFEF'
}
},
tokens: {
definition: colors.CODEMIRROR_TOKENS.DEFINITION,
property: colors.CODEMIRROR_TOKENS.PROPERTY,
string: colors.CODEMIRROR_TOKENS.STRING,
number: colors.CODEMIRROR_TOKENS.NUMBER,
atom: colors.CODEMIRROR_TOKENS.ATOM,
variable: colors.CODEMIRROR_TOKENS.VARIABLE,
keyword: colors.CODEMIRROR_TOKENS.KEYWORD,
comment: colors.CODEMIRROR_TOKENS.COMMENT,
operator: colors.CODEMIRROR_TOKENS.OPERATOR
},
searchLineHighlightCurrent: 'rgba(120,120,120,0.10)',
searchMatch: '#B8860B',
searchMatchActive: '#DAA520'

View File

@@ -0,0 +1,141 @@
/**
* Lint Error Tooltip for CodeMirror
* Shows lint errors in a popover when hovering over line numbers
*/
let activeTooltip = null;
/**
* Get lint errors for a specific line from the editor's lint state
* @param {CodeMirror} editor - The CodeMirror editor instance
* @param {number} lineNumber - The 0-indexed line number
* @returns {Array} Array of lint error annotations
*/
function getLintErrorsForLine(editor, lineNumber) {
if (!editor) return [];
const errors = [];
const lintState = editor.state.lint;
if (lintState && lintState.marked) {
lintState.marked.forEach((mark) => {
if (mark.__annotation) {
// Use annotation's from position directly (mark.find() can return null if lines array is empty)
const annotationLine = mark.__annotation.from?.line;
if (annotationLine === lineNumber) {
// Avoid duplicate messages
if (!errors.find((e) => e.message === mark.__annotation.message)) {
errors.push(mark.__annotation);
}
}
}
});
}
return errors;
}
/**
* Show the lint error tooltip next to the target element
* @param {Array} errors - Array of lint error annotations
* @param {HTMLElement} targetElement - The element to position the tooltip near
* @param {HTMLElement} container - The container to append the tooltip to
*/
function showLintTooltip(errors, targetElement, container) {
hideLintTooltip();
const tooltip = document.createElement('div');
tooltip.className = 'lint-error-tooltip';
errors.forEach((error, index) => {
const errorDiv = document.createElement('div');
errorDiv.className = `lint-tooltip-message ${error.severity || 'error'}`;
errorDiv.textContent = error.message;
tooltip.appendChild(errorDiv);
});
container.appendChild(tooltip);
activeTooltip = tooltip;
// Position the tooltip
const rect = targetElement.getBoundingClientRect();
tooltip.style.left = `${rect.right + 8}px`;
tooltip.style.top = `${rect.top + (rect.height / 2)}px`;
tooltip.style.transform = 'translateY(-50%)';
}
/**
* Hide and remove the active lint error tooltip
*/
function hideLintTooltip() {
if (activeTooltip) {
activeTooltip.remove();
activeTooltip = null;
}
}
/**
* Setup lint error tooltip functionality for a CodeMirror editor
* Shows lint errors when hovering over line numbers
*
* @param {CodeMirror} editor - The CodeMirror editor instance
* @returns {Function} Cleanup function to remove event listeners
*/
export function setupLintErrorTooltip(editor) {
const wrapper = editor.getWrapperElement();
// Get the StyledWrapper container (parent of CodeMirror wrapper)
const container = wrapper.closest('.graphiql-container') || wrapper.parentElement;
const handleMouseOver = (e) => {
const target = e.target;
// Check if hovering over a line number element
if (target.classList.contains('CodeMirror-linenumber')) {
const lineNumber = parseInt(target.textContent, 10) - 1; // 0-indexed
if (isNaN(lineNumber) || lineNumber < 0) {
hideLintTooltip();
return;
}
const lintErrors = getLintErrorsForLine(editor, lineNumber);
if (lintErrors.length > 0) {
showLintTooltip(lintErrors, target, container);
} else {
hideLintTooltip();
}
} else if (!target.closest('.lint-error-tooltip')) {
hideLintTooltip();
}
};
const handleMouseOut = (e) => {
const relatedTarget = e.relatedTarget;
// Don't hide if moving to another line number or the tooltip
if (relatedTarget
&& (relatedTarget.classList?.contains('CodeMirror-linenumber')
|| relatedTarget.closest?.('.lint-error-tooltip'))) {
return;
}
hideLintTooltip();
};
const handleScroll = () => {
hideLintTooltip();
};
// Add event listeners
wrapper.addEventListener('mouseover', handleMouseOver);
wrapper.addEventListener('mouseout', handleMouseOut);
editor.on('scroll', handleScroll);
// Return cleanup function
return () => {
wrapper.removeEventListener('mouseover', handleMouseOver);
wrapper.removeEventListener('mouseout', handleMouseOut);
editor.off('scroll', handleScroll);
hideLintTooltip();
};
}