0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-03-31 22:51:25 -05:00

feat(console): add line number and disable line wrapper ()

---------

Co-authored-by: Charles Zhao <charleszhao@silverhand.io>
This commit is contained in:
Darcy Ye 2023-03-20 23:24:44 +08:00 committed by GitHub
parent 80015f22fd
commit aee1fbfb6d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 98 additions and 20 deletions
packages/console/src
components
pages/SignInExperience/tabs/Branding

View file

@ -1,11 +1,12 @@
@use '@/scss/underscore' as _;
.container {
padding: _.unit(6);
padding: _.unit(6) _.unit(6) _.unit(6) _.unit(4);
border-radius: 16px;
// Force dark theme on the code editor
background: #34353f;
position: relative;
overflow-y: auto;
.placeholder {
position: absolute;
@ -36,6 +37,7 @@
.editor {
position: relative;
overflow-x: auto;
textarea {
width: 100%;
@ -46,15 +48,15 @@
font-size: 14px;
line-height: 1.5;
font-family: 'Roboto Mono', monospace;
white-space: pre-wrap;
word-break: break-all;
white-space: nowrap;
word-break: normal;
position: absolute;
inset: 0;
resize: none;
color: #fefefe;
overflow: hidden;
-webkit-text-fill-color: transparent;
outline: none;
overflow: hidden;
}
textarea,
@ -63,6 +65,10 @@
min-height: 80px;
}
}
.editor::-webkit-scrollbar {
display: none;
}
}
.errorMessage {

View file

@ -1,12 +1,13 @@
import classNames from 'classnames';
import type { ChangeEvent, KeyboardEvent } from 'react';
import { useMemo, useRef } from 'react';
import { useLayoutEffect, useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { PrismAsyncLight as SyntaxHighlighter } from 'react-syntax-highlighter';
import { a11yDark as theme } from 'react-syntax-highlighter/dist/esm/styles/prism';
import { a11yDark as a11yDarkTheme } from 'react-syntax-highlighter/dist/esm/styles/prism';
import CopyToClipboard from '../CopyToClipboard';
import * as styles from './index.module.scss';
import { lineNumberContainerStyle, lineNumberStyle, customStyle } from './utils';
type Props = {
className?: string;
@ -32,8 +33,19 @@ const CodeEditor = ({
placeholder,
}: Props) => {
const textareaRef = useRef<HTMLTextAreaElement>(null);
const editorRef = useRef<HTMLDivElement>(null);
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
useLayoutEffect(() => {
// Update textarea width according to its scroll width
const { current } = textareaRef;
if (current && current.style.width !== `${current.scrollWidth}px`) {
// eslint-disable-next-line @silverhand/fp/no-mutation
current.style.width = `${current.scrollWidth}px`;
}
}, [value]);
const handleChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
const { value } = event.currentTarget;
onChange?.(value);
@ -54,6 +66,15 @@ const CodeEditor = ({
onChange?.(newText);
}
/**
* Since lineNumber container could cover leftmost part of the editor,
* when user is clicking on "Enter", should manually scroll to the leftmost.
*/
if (event.key === 'Enter' && editorRef.current && editorRef.current.scrollLeft !== 0) {
// eslint-disable-next-line @silverhand/fp/no-mutation
editorRef.current.scrollLeft = 0;
}
};
// TODO @sijie temp solution for required error (the errorMessage is an empty string)
@ -65,12 +86,15 @@ const CodeEditor = ({
return t('general.required');
}, [errorMessage, t]);
const maxLineNumberDigits = ((value ?? '').split('\n').length + 1).toString().length;
const isShowingPlaceholder = !value;
return (
<>
<div className={classNames(styles.container, className)}>
{!value && <div className={styles.placeholder}>{placeholder}</div>}
{isShowingPlaceholder && <div className={styles.placeholder}>{placeholder}</div>}
<CopyToClipboard value={value ?? ''} variant="icon" className={styles.copy} />
<div className={styles.editor}>
<div ref={editorRef} className={styles.editor}>
{/* SyntaxHighlighter is a readonly component, so a transparent <textarea> layer is needed
in order to support user interactions, such as code editing, copy-pasting, etc. */}
<textarea
@ -82,6 +106,14 @@ const CodeEditor = ({
readOnly={isReadonly}
spellCheck="false"
value={value}
style={
isShowingPlaceholder
? { marginLeft: '8px', width: 'calc(100% - 8px)' }
: {
marginLeft: `calc(${maxLineNumberDigits}ch + 20px)`,
width: `calc(100% - ${maxLineNumberDigits}ch - 20px)`,
}
}
onChange={handleChange}
onKeyDown={handleKeydown}
/>
@ -89,23 +121,19 @@ const CodeEditor = ({
inline-styles by default. Therefore, We can only use inline styles to customize them.
Some styles have to be applied multiple times to each of them for the sake of consistency. */}
<SyntaxHighlighter
wrapLongLines
showInlineLineNumbers
showLineNumbers={!isShowingPlaceholder}
width={textareaRef.current?.scrollWidth ?? 0}
lineNumberContainerStyle={lineNumberContainerStyle()}
lineNumberStyle={lineNumberStyle(maxLineNumberDigits)}
codeTagProps={{
style: {
fontFamily: "'Roboto Mono', monospace", // Override default font-family of <code>
},
}}
customStyle={{
background: 'transparent',
fontSize: '14px',
margin: '0',
padding: '0',
borderRadius: '0',
wordBreak: 'break-all',
fontFamily: "'Roboto Mono', monospace", // Override default font-family of <pre>
}}
customStyle={customStyle(textareaRef.current?.scrollWidth)}
language={language}
style={theme}
style={a11yDarkTheme}
>
{value ?? ''}
</SyntaxHighlighter>

View file

@ -0,0 +1,44 @@
import type { CSSProperties } from 'react';
export const lineNumberContainerStyle = (): CSSProperties => {
return {
display: 'flex',
flexDirection: 'column',
textAlign: 'right',
paddingLeft: '0px',
paddingRight: '0px',
};
};
export const lineNumberStyle = (numberOfLines: number): CSSProperties => {
return {
minWidth: `calc(${numberOfLines}ch + 20px)`,
marginLeft: '0px',
paddingRight: '20px',
paddingLeft: '0px',
display: 'inline-flex',
justifyContent: 'flex-end',
counterIncrement: 'line',
lineHeight: '1.5',
flexShrink: 0,
fontFamily: "'Roboto Mono', monospace",
fontSize: '14px',
position: 'sticky',
background: '#34353f', // Stick to code editor container
left: 0,
};
};
export const customStyle = (width?: number): CSSProperties => {
return {
width: `${width ?? 0}px`,
background: 'transparent',
fontSize: '14px',
margin: '0',
padding: '0',
borderRadius: '0',
wordBreak: 'break-all',
overflow: 'unset',
fontFamily: "'Roboto Mono', monospace", // Override default font-family of <pre>
};
};

View file

@ -39,4 +39,5 @@
.form {
flex-grow: 1;
overflow: hidden;
}

View file

@ -1,5 +1,4 @@
.customCssCodeEditor {
max-height: calc(100vh - 260px);
min-height: 132px; // min-height to show three lines of code
overflow-y: auto;
}