Skip to content

Commit eec98ba

Browse files
authored
feat(Codebytes): add simple monaco editor disc 353 (#16)
* working version * update to include gamut styles * add simple code editor * add editor container styles * fix prettier issues * pass onCopy from parent * use isIframeProp * pass down snippets base url from parent * simple monaco editor * update tslint for scale * incorporate review feedback * fix prettier issues * default snippets endpoint to empty string * use rem * update example text * use language prop * use colors gray * remove env file * run prettier * use monaco-editor/react package * update monaco editor package version * remove comments * fix typings * remove gitignore * use theme navy * fix linting issues * update dependencies * rename env
1 parent db6be3e commit eec98ba

File tree

14 files changed

+251
-30
lines changed

14 files changed

+251
-30
lines changed

.gitignore

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
.DS_Store
22

3-
# Env
4-
.env.*
5-
63
# Logs
74
logs
85
*.log

packages/codebytes/package.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@
2525
"@codecademy/gamut-styles": "*",
2626
"@codecademy/variance": "*",
2727
"@emotion/react": "^11.4.0",
28-
"@emotion/styled": "^11.3.0"
28+
"@emotion/styled": "^11.3.0",
29+
"@monaco-editor/react": "4.3.1",
30+
"react-resize-observer": "1.1.1",
31+
"monaco-editor": ">= 0.25.0 < 1"
2932
},
3033
"scripts": {
3134
"verify": "tsc --noEmit",
@@ -39,7 +42,8 @@
3942
"devDependencies": {
4043
"@emotion/jest": "^11.3.0",
4144
"@testing-library/dom": "^7.31.2",
42-
"@testing-library/react": "^11.0.4"
45+
"@testing-library/react": "^11.0.4",
46+
"@types/loadable__component": "^5.13.2"
4347
},
4448
"publishConfig": {
4549
"access": "public"
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// DO NOT CHANGE ANYTHING HERE
2+
// This file is part of the Codebytes MVP and only includes basic configuration around theming for the SimpleMonacoEditor component
3+
// We are working on a monaco package in client-modules that has more configuration around themes and languages
4+
// Monaco as a shared package RFC https://www.notion.so/codecademy/Monaco-editor-as-a-shared-package-1f4484db165b4abc8394c3556451ef6a
5+
6+
import { colors, editorColors } from '@codecademy/gamut-styles';
7+
8+
const darkTheme = {
9+
blue: editorColors.blue,
10+
deepPurple: editorColors.deepPurple,
11+
gray: editorColors.gray,
12+
green: editorColors.green,
13+
orange: editorColors.orange,
14+
purple: editorColors.purple,
15+
red: editorColors.red,
16+
white: colors.white,
17+
yellow: editorColors.yellow,
18+
};
19+
20+
export const syntax = {
21+
attribute: darkTheme.green,
22+
annotation: darkTheme.red,
23+
atom: darkTheme.deepPurple,
24+
basic: darkTheme.white,
25+
comment: darkTheme.gray,
26+
constant: darkTheme.orange,
27+
decoration: darkTheme.red,
28+
invalid: darkTheme.red,
29+
key: darkTheme.blue,
30+
keyword: darkTheme.purple,
31+
number: darkTheme.red,
32+
operator: darkTheme.red,
33+
predefined: darkTheme.white,
34+
property: darkTheme.red,
35+
regexp: darkTheme.green,
36+
string: darkTheme.yellow,
37+
tag: darkTheme.red,
38+
text: darkTheme.orange,
39+
value: darkTheme.yellow,
40+
variable: darkTheme.green,
41+
};
42+
43+
export const ui = {
44+
background: '#211E2F',
45+
text: darkTheme.white,
46+
indent: {
47+
active: '#393b41',
48+
inactive: '#494b51',
49+
},
50+
};
51+
52+
export type SyntaxColors = typeof syntax;
53+
54+
export type UIColors = typeof ui;
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// DO NOT CHANGE ANYTHING HERE
2+
// This component is part of the Codebytes MVP and only includes basic configuration around theming
3+
// We are working on a monaco package in client-modules that has more configuration around themes and languages
4+
// Monaco as a shared package RFC https://www.notion.so/codecademy/Monaco-editor-as-a-shared-package-1f4484db165b4abc8394c3556451ef6a
5+
6+
import ReactMonacoEditor, { OnMount } from '@monaco-editor/react';
7+
import { editor } from 'monaco-editor/esm/vs/editor/editor.api';
8+
import React, { useCallback, useRef } from 'react';
9+
import ResizeObserver from 'react-resize-observer';
10+
11+
import { dark } from './theme';
12+
import { Monaco } from './types';
13+
14+
export type SimpleMonacoEditorProps = {
15+
value: string;
16+
language: string;
17+
onChange?: (value: string) => void;
18+
};
19+
20+
type ThemedEditor = Parameters<OnMount>[0];
21+
22+
export const SimpleMonacoEditor: React.FC<SimpleMonacoEditorProps> = ({
23+
value,
24+
language,
25+
onChange,
26+
}) => {
27+
const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
28+
const editorWillMount = useCallback(
29+
(editor: ThemedEditor, monaco: Monaco) => {
30+
editorRef.current = editor;
31+
monaco.editor.defineTheme('dark', dark);
32+
monaco.editor.setTheme('dark');
33+
},
34+
[]
35+
);
36+
return (
37+
<>
38+
<ResizeObserver
39+
onResize={({ height, width }) => {
40+
editorRef.current?.layout({
41+
height,
42+
width,
43+
});
44+
}}
45+
/>
46+
<ReactMonacoEditor
47+
onMount={editorWillMount}
48+
onChange={onChange}
49+
options={{ minimap: { enabled: false } }}
50+
theme="vs-dark"
51+
value={value}
52+
language={language}
53+
/>
54+
</>
55+
);
56+
};
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// DO NOT CHANGE ANYTHING HERE
2+
// This file is part of the Codebytes MVP and only includes basic configuration around theming for the SimpleMonacoEditor component
3+
// We are working on a monaco package in client-modules that has more configuration around themes and languages
4+
// Monaco as a shared package RFC https://www.notion.so/codecademy/Monaco-editor-as-a-shared-package-1f4484db165b4abc8394c3556451ef6a
5+
6+
import type * as monaco from 'monaco-editor';
7+
8+
import * as darkColors from './colorsDark';
9+
10+
const c = (color: string) => color.substr(1);
11+
12+
const theme = ({
13+
ui,
14+
syntax,
15+
}: {
16+
ui: darkColors.UIColors;
17+
syntax: darkColors.SyntaxColors;
18+
}): monaco.editor.IStandaloneThemeData => ({
19+
base: 'vs-dark',
20+
inherit: true,
21+
rules: [
22+
// Base
23+
{ token: '', foreground: c(syntax.basic) },
24+
{ token: 'regexp', foreground: c(syntax.regexp) },
25+
{ token: 'annotation', foreground: c(syntax.annotation) },
26+
{ token: 'type', foreground: c(syntax.annotation) },
27+
{ token: 'doctype', foreground: c(syntax.comment) },
28+
{ token: 'delimiter', foreground: c(syntax.decoration) },
29+
{ token: 'invalid', foreground: c(syntax.invalid) },
30+
{ token: 'emphasis', fontStyle: 'italic' },
31+
{ token: 'strong', fontStyle: 'bold' },
32+
{ token: 'variable', foreground: c(syntax.variable) },
33+
{ token: 'variable.predefined', foreground: c(syntax.variable) },
34+
{ token: 'constant', foreground: c(syntax.constant) },
35+
{ token: 'comment', foreground: c(syntax.comment) },
36+
{ token: 'number', foreground: c(syntax.number) },
37+
{ token: 'number.hex', foreground: c(syntax.number) },
38+
{ token: 'keyword.directive', foreground: c(syntax.comment) },
39+
{ token: 'include', foreground: c(syntax.comment) },
40+
{ token: 'key', foreground: c(syntax.property) },
41+
{ token: 'attribute.name', foreground: c(syntax.attribute) },
42+
{ token: 'attribute.name-numeric', foreground: c(syntax.string) },
43+
{ token: 'attribute.value', foreground: c(syntax.property) },
44+
{ token: 'attribute.value.number', foreground: c(syntax.number) },
45+
{ token: 'string', foreground: c(syntax.string) },
46+
{ token: 'string.yaml', foreground: c(syntax.string) },
47+
{ token: 'tag', foreground: c(syntax.tag) },
48+
{ token: 'tag.id.jade', foreground: c(syntax.tag) },
49+
{ token: 'tag.class.jade', foreground: c(syntax.tag) },
50+
{ token: 'metatag', foreground: c(syntax.comment) },
51+
{ token: 'attribute.value.unit', foreground: c(syntax.string) },
52+
{ token: 'keyword', foreground: c(syntax.keyword) },
53+
{ token: 'keyword.flow', foreground: c(syntax.keyword) },
54+
],
55+
colors: {
56+
'editor.background': ui.background,
57+
'editor.foreground': ui.text,
58+
'editorIndentGuide.background': ui.indent.inactive,
59+
'editorIndentGuide.activeBackground': ui.indent.active,
60+
'editorWhitespace.foreground': syntax.comment,
61+
},
62+
});
63+
64+
export const dark = theme(darkColors);
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export type Monaco = typeof import('monaco-editor');

packages/codebytes/src/editor.tsx

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ import {
66
ToolTip,
77
} from '@codecademy/gamut';
88
import { CopyIcon } from '@codecademy/gamut-icons';
9-
import { theme } from '@codecademy/gamut-styles';
109
import styled from '@emotion/styled';
1110
import React, { useState } from 'react';
1211

1312
import { postSnippet } from './api';
1413
import type { languageOption } from './consts';
1514
import { Drawers } from './drawers';
15+
import { SimpleMonacoEditor } from './MonacoEditor';
1616

1717
const Output = styled.pre<{ hasError: boolean }>`
1818
width: 100%;
@@ -22,9 +22,9 @@ const Output = styled.pre<{ hasError: boolean }>`
2222
font-family: Monaco;
2323
font-size: 0.875rem;
2424
overflow: auto;
25-
${({ hasError }) => `
25+
${({ hasError, theme }) => `
2626
color: ${hasError ? theme.colors.orange : theme.colors.white};
27-
background-color: ${theme.colors['gray-900']};
27+
background-color: ${theme.colors['navy-900']};
2828
`}
2929
`;
3030

@@ -38,10 +38,7 @@ type EditorProps = {
3838
hideCopyButton: boolean;
3939
language: languageOption;
4040
text: string;
41-
// eslint-disable-next-line react/no-unused-prop-types
42-
onChange: (
43-
text: string
44-
) => void /* TODO: Add onChange behavior in DISC-353 */;
41+
onChange: (text: string) => void;
4542
onCopy?: (text: string, language: string) => void;
4643
snippetsBaseUrl?: string;
4744
};
@@ -51,6 +48,7 @@ export const Editor: React.FC<EditorProps> = ({
5148
text,
5249
hideCopyButton,
5350
onCopy,
51+
onChange,
5452
snippetsBaseUrl,
5553
}) => {
5654
const [output, setOutput] = useState('');
@@ -105,7 +103,13 @@ export const Editor: React.FC<EditorProps> = ({
105103
return (
106104
<>
107105
<Drawers
108-
leftChild={<div>{text}</div>}
106+
leftChild={
107+
<SimpleMonacoEditor
108+
value={text}
109+
language={language}
110+
onChange={onChange}
111+
/>
112+
}
109113
rightChild={
110114
<Output hasError={status === 'error'} aria-live="polite">
111115
{output}

packages/codebytes/src/index.tsx

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@ import React, { useState } from 'react';
88
import { languageOption } from './consts';
99
import { Editor } from './editor';
1010

11+
export interface CodeByteEditorProps {
12+
text: string;
13+
language: languageOption;
14+
hideCopyButton: boolean;
15+
onCopy?: (text: string, language: string) => void;
16+
isIFrame?: boolean;
17+
snippetsBaseUrl?: string /* TODO in DISC-353: Determine best way to host and include snippets endpoint for both staging and production in both the monolith and next.js repo. */;
18+
onTextChange?: (text: string) => void;
19+
}
20+
1121
const editorStates = states({
1222
isIFrame: { height: '100vh' },
1323
});
@@ -30,23 +40,13 @@ const EditorContainer = styled(Background)<EditorStyleProps>(
3040
editorStates
3141
);
3242

33-
export interface CodeByteEditorProps {
34-
text: string;
35-
language: languageOption;
36-
hideCopyButton: boolean;
37-
onCopy?: (text: string, language: string) => void;
38-
isIFrame?: boolean;
39-
snippetsBaseUrl?: string /* TODO in DISC-353: Determine best way to host and include snippets endpoint for both staging and production in both the monolith and next.js repo. */;
40-
onTextChange?: (text: string) => void;
41-
}
42-
4343
export const CodeByteEditor: React.FC<CodeByteEditorProps> = ({
4444
text: initialText,
4545
language,
4646
hideCopyButton,
4747
onCopy,
4848
isIFrame = false,
49-
snippetsBaseUrl = '',
49+
snippetsBaseUrl = process.env.CONTAINER_API_BASE,
5050
onTextChange,
5151
}) => {
5252
const [text, setText] = useState<string>(initialText);

packages/codebytes/src/theme.d.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { CoreTheme } from '@codecademy/gamut-styles';
2-
32
declare module '@emotion/react' {
43
export interface Theme extends CoreTheme {}
54
}

packages/styleguide/.env

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
CONTAINER_API_BASE=propeller.cc-le-cf.com

0 commit comments

Comments
 (0)