diff --git a/pages/src/common/code.dark.less b/pages/src/common/code.dark.less
new file mode 100644
index 00000000..fde80a77
--- /dev/null
+++ b/pages/src/common/code.dark.less
@@ -0,0 +1,82 @@
+.uip-root.dark-theme .uip-editor {
+ .editor-content {
+ color: #ccc;
+ text-shadow: none;
+ background-color: @dark-theme;
+ }
+
+ .token.block-comment,
+ .token.cdata,
+ .token.comment,
+ .token.doctype,
+ .token.prolog {
+ color: #999;
+ }
+
+ .token.punctuation {
+ color: #ccc;
+ }
+
+ .token.attr-name,
+ .token.deleted,
+ .token.namespace,
+ .token.tag {
+ color: #e2777a;
+ }
+
+ .token.function-name {
+ color: #6196cc;
+ }
+
+ .token.boolean,
+ .token.function,
+ .token.number {
+ color: #f08d49;
+ }
+
+ .token.class-name,
+ .token.constant,
+ .token.property,
+ .token.symbol {
+ color: #f8c555;
+ }
+
+ .token.atrule,
+ .token.builtin,
+ .token.important,
+ .token.keyword,
+ .token.selector {
+ color: #cc99cd;
+ }
+
+ .token.attr-value,
+ .token.char,
+ .token.regex,
+ .token.string,
+ .token.variable {
+ color: #7ec699;
+ }
+
+ .token.entity,
+ .token.operator,
+ .token.url {
+ color: #67cdcc;
+ }
+
+ .token.bold,
+ .token.important {
+ font-weight: 700;
+ }
+
+ .token.italic {
+ font-style: italic;
+ }
+
+ .token.entity {
+ cursor: help;
+ }
+
+ .token.inserted {
+ color: green;
+ }
+}
diff --git a/pages/src/common/code.less b/pages/src/common/code.less
index 00fe0733..e2c64596 100644
--- a/pages/src/common/code.less
+++ b/pages/src/common/code.less
@@ -4,3 +4,5 @@ pre {
margin-bottom: 1rem;
overflow: auto;
}
+
+@import './code.dark.less';
diff --git a/pages/views/examples/example/form.njk b/pages/views/examples/example/form.njk
index 10c2051a..9720c9d7 100644
--- a/pages/views/examples/example/form.njk
+++ b/pages/views/examples/example/form.njk
@@ -28,5 +28,5 @@ title: Example with form
-
+
diff --git a/src/core/base/plugin.tsx b/src/core/base/plugin.tsx
index c8d5f9e5..0f003eeb 100644
--- a/src/core/base/plugin.tsx
+++ b/src/core/base/plugin.tsx
@@ -30,9 +30,9 @@ export abstract class UIPPlugin extends ESLBaseElement {
/** {@link UIPPlugin} section wrapper */
@memoize()
- protected get $inner(): HTMLDivElement {
+ protected get $inner(): HTMLElement {
const pluginType = this.constructor as typeof UIPPlugin;
- return
as HTMLDivElement;
+ return as HTMLElement;
}
protected connectedCallback(): void {
diff --git a/src/core/base/root.less b/src/core/base/root.less
index 484f679c..1209c1a0 100644
--- a/src/core/base/root.less
+++ b/src/core/base/root.less
@@ -4,6 +4,7 @@
.uip-root {
position: relative;
+ overflow: hidden;
display: grid;
align-content: start;
grid-template-columns: minmax(calc(100% - 250px), 100%) auto;
@@ -25,15 +26,6 @@
'editor';
}
- &[editor-collapsed] > .uip-editor {
- max-height: 0;
-
- &-inner {
- visibility: hidden;
- transition: visibility 0s 0.3s;
- }
- }
-
&[settings-collapsed] > .uip-settings {
max-width: 0;
max-height: 0;
diff --git a/src/plugins/editor/editor.header.less b/src/plugins/editor/editor.header.less
new file mode 100644
index 00000000..fdcaf04e
--- /dev/null
+++ b/src/plugins/editor/editor.header.less
@@ -0,0 +1,93 @@
+html {
+ --uip-editor-divider: #ccc;
+ --uip-editor-header-bg: #ccc;
+ --uip-editor-header-fg: #000;
+}
+
+.uip-editor-header {
+ position: relative;
+ display: flex;
+ align-items: center;
+
+ color: var(--uip-editor-header-fg, #000);
+ background: var(--uip-editor-header-bg, transparent);
+ border-bottom: 1px solid var(--uip-editor-divider, transparent);
+
+ &-title {
+ margin-inline-end: auto;
+ }
+
+ &-title:not(:empty) {
+ padding: 5px;
+ }
+
+ &-copy {
+ position: relative;
+ width: 25px;
+ height: 25px;
+ z-index: 2;
+ }
+
+ &-trigger {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ z-index: 1;
+
+ background: transparent;
+ border: none;
+ box-shadow: none;
+ appearance: none;
+ }
+
+ &.no-label {
+ background: transparent;
+ justify-content: center;
+ }
+
+ &.no-label &-title {
+ display: none;
+ }
+
+ &.no-label &-trigger {
+ left: 50%;
+ width: 100px;
+ transform: translateX(-50%);
+
+ background: var(--uip-editor-header-bg, transparent);
+ border-radius: 50px 50px 0 0;
+ opacity: 0.5;
+ transition: opacity 0.3s ease-in-out;
+
+ &:hover {
+ opacity: 1;
+ }
+ }
+
+ [collapsible] &::after {
+ content: '';
+ position: relative;
+ margin: 5px 10px;
+ top: -2px;
+ width: 7px;
+ height: 7px;
+ z-index: 1;
+ pointer-events: none;
+
+ border-left: 2px solid currentColor;
+ border-top: 2px solid currentColor;
+
+ transform: rotate(225deg);
+ transform-origin: center;
+ transition:
+ transform 0.5s ease-in-out,
+ top 0.5s ease-in-out;
+ }
+
+ [collapsible][collapsed] &::after {
+ top: 2px;
+ transform: rotate(45deg);
+ }
+}
diff --git a/src/plugins/editor/editor.less b/src/plugins/editor/editor.less
index 117ed11e..813ad240 100644
--- a/src/plugins/editor/editor.less
+++ b/src/plugins/editor/editor.less
@@ -1,108 +1,64 @@
@import (reference) '../../common/variables.less';
+html {
+ --uip-editor-bg: #f5f2f0;
+}
+
.uip-editor {
- position: relative;
- grid-area: editor;
display: block;
- max-height: 325px;
- overflow: auto;
- transition: max-height 0.3s ease-in-out;
+ grid-area: editor;
&-inner {
- height: 100%;
- border-top: 1px solid @section-border;
- padding: 0;
+ display: flex;
+ position: relative;
+ height: auto;
+ overflow: hidden;
+ max-height: 325px;
+ transition:
+ padding-top 0.3s linear,
+ padding-bottom 0.3s linear,
+ max-height 0.3s linear,
+ visibility 0s linear;
+
+ padding: 1em;
+ background: var(--uip-editor-bg);
}
- .editor-content {
- margin: 0;
+ &[collapsed] &-inner {
+ padding-top: 0;
+ padding-bottom: 0;
+ max-height: 0;
+ visibility: hidden;
+ transition:
+ padding-top 0.3s linear,
+ padding-bottom 0.3s linear,
+ max-height 0.3s linear,
+ visibility 0.3s linear;
}
- .codejar-linenumbers {
- // Override default width set by withLineNumbers
- width: unset !important;
+ &-container {
+ flex: 1 1 auto;
+ }
+ &-container &-code {
+ margin: 0;
+ padding: 0;
}
- .uip-root.dark-theme & {
- .editor-content {
- color: #ccc;
- text-shadow: none;
- background-color: @dark-theme;
- }
-
- .token.block-comment,
- .token.cdata,
- .token.comment,
- .token.doctype,
- .token.prolog {
- color: #999;
- }
-
- .token.punctuation {
- color: #ccc;
- }
-
- .token.attr-name,
- .token.deleted,
- .token.namespace,
- .token.tag {
- color: #e2777a;
- }
-
- .token.function-name {
- color: #6196cc;
- }
-
- .token.boolean,
- .token.function,
- .token.number {
- color: #f08d49;
- }
-
- .token.class-name,
- .token.constant,
- .token.property,
- .token.symbol {
- color: #f8c555;
- }
-
- .token.atrule,
- .token.builtin,
- .token.important,
- .token.keyword,
- .token.selector {
- color: #cc99cd;
- }
-
- .token.attr-value,
- .token.char,
- .token.regex,
- .token.string,
- .token.variable {
- color: #7ec699;
- }
-
- .token.entity,
- .token.operator,
- .token.url {
- color: #67cdcc;
- }
-
- .token.bold,
- .token.important {
- font-weight: 700;
- }
-
- .token.italic {
- font-style: italic;
- }
+ &-scrollbar {
+ flex: 0 0 auto;
+ order: -1;
+ opacity: 1;
+ transition: opacity 0.3s ease-in-out;
+ }
- .token.entity {
- cursor: help;
- }
+ &[collapsed] &-scrollbar {
+ opacity: 0;
+ }
- .token.inserted {
- color: green;
- }
+ .codejar-linenumbers {
+ // Override default width set by withLineNumbers
+ width: unset !important;
}
}
+
+@import "./editor.header.less";
diff --git a/src/plugins/editor/editor.tsx b/src/plugins/editor/editor.tsx
index 71194ec7..99d7e30c 100644
--- a/src/plugins/editor/editor.tsx
+++ b/src/plugins/editor/editor.tsx
@@ -8,30 +8,69 @@ import Prism from 'prismjs';
import {CodeJar} from 'codejar';
import {debounce} from '@exadel/esl/modules/esl-utils/async/debounce';
-import {bind, decorate, memoize} from '@exadel/esl/modules/esl-utils/decorators';
+import {bind, boolAttr, decorate, listen, memoize} from '@exadel/esl/modules/esl-utils/decorators';
import {UIPPlugin} from '../../core/base/plugin';
+import {UIPOptionIcons} from '../header/options/option-icons';
/**
* Editor {@link UIPPlugin} custom element definition
* Uses Codejar code editor to provide an ability to modify UIP state markup
- * @extends UIPPlugin
*/
export class UIPEditor extends UIPPlugin {
public static override is = 'uip-editor';
+ public static override observedAttributes = ['collapsible', 'copy', 'label'];
/** Highlight method declaration */
public static highlight = (editor: HTMLElement): void => Prism.highlightElement(editor, false);
- /** Wrapped {@link https://medv.io/codejar/ CodeJar} editor instance */
+ /** Marker to collapse editor area */
+ @boolAttr() public collapsed: boolean;
+
+ /** Marker to make enable toggle collapse action for section header */
+ @boolAttr() public collapsible: boolean;
+
+ /** Marker to display copy widget */
+ @boolAttr({name: 'copy'}) public showCopy: boolean;
+
+ /** Header section block */
@memoize()
- protected get editor(): CodeJar {
- return CodeJar(this.$code, UIPEditor.highlight, { tab: '\t' });
+ protected get $header(): HTMLElement {
+ const type = this.constructor as typeof UIPEditor;
+ return (
+
+ {this.label}
+ {this.showCopy ? {UIPOptionIcons.copySVG} : ''}
+ {this.collapsible ? : ''}
+
+ ) as HTMLElement;
}
+ /** {@link UIPEditor} section wrapper */
+ @memoize()
+ protected get $inner(): HTMLDivElement {
+ const type = this.constructor as typeof UIPPlugin;
+ return (
+
+ ) as HTMLDivElement;
+ }
+
+ /** Code block */
@memoize()
protected get $code(): HTMLElement {
- return (
) as HTMLElement;
+ const type = this.constructor as typeof UIPEditor;
+ return (
) as HTMLElement;
+ }
+
+ /** Wrapped {@link https://medv.io/codejar/ CodeJar} editor instance */
+ @memoize()
+ protected get editor(): CodeJar {
+ return CodeJar(this.$code, UIPEditor.highlight, {tab: '\t'});
}
/** @returns editor's content */
@@ -49,10 +88,8 @@ export class UIPEditor extends UIPPlugin {
this.innerHTML = '';
// Prefill content
+ this.appendChild(this.$header);
this.appendChild(this.$inner);
- this.$inner.classList.add('esl-scrollable-content');
- this.$inner.append();
- this.$inner.append(this.$code);
// Initial update
this._onRootStateChange();
@@ -66,6 +103,20 @@ export class UIPEditor extends UIPPlugin {
super.disconnectedCallback();
}
+ protected override attributeChangedCallback(attrName: string, oldVal: string, newVal: string): void {
+ this.$header.remove();
+ memoize.clear(this, '$header');
+ this.insertAdjacentElement('afterbegin', this.$header);
+ }
+
+ @listen({
+ event: 'click',
+ selector: `.${UIPEditor.is}-header-trigger`,
+ })
+ protected _onClick(): void {
+ if (this.collapsible) this.collapsed = !this.collapsed;
+ }
+
/** Callback to call on editor's content changes */
@decorate(debounce, 1000)
protected _onChange(): void {