Skip to content

Commit 783d621

Browse files
committed
feat: add wide modal in trasform
1 parent 138455d commit 783d621

File tree

11 files changed

+301
-0
lines changed

11 files changed

+301
-0
lines changed

src/js/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ import './polyfill';
44
import './code';
55
import './cut';
66
import './term';
7+
import './wide-mode';

src/js/wide-mode/apply.ts

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import {WIDE_ELEMENTS_SELECTOR} from './constants';
2+
import expand from './icons/expand';
3+
import {modal} from './modal';
4+
5+
const wrap = (target: HTMLElement) => {
6+
if (target.parentElement?.classList.contains('wide-element-wrapper')) {
7+
return;
8+
}
9+
10+
target.classList.add('wide-inner-element');
11+
12+
const handler = document.createElement('div');
13+
14+
handler.innerHTML = expand;
15+
handler.classList.add('wide-content-viewer');
16+
handler.addEventListener('click', () => modal.render(target));
17+
18+
const container = document.createElement('div');
19+
20+
container.classList.add('wide-element-wrapper');
21+
22+
target.before(container);
23+
24+
container.appendChild(handler);
25+
container.appendChild(target);
26+
};
27+
28+
export const applyWideMode = () => {
29+
const allWideElements = Array.from(
30+
document.querySelectorAll(WIDE_ELEMENTS_SELECTOR),
31+
) as HTMLElement[];
32+
33+
allWideElements.forEach(wrap);
34+
};

src/js/wide-mode/constants.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const WIDE_ELEMENTS_SELECTOR = '[wide-content]';

src/js/wide-mode/globals.d.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export {};
2+
3+
type Template = {
4+
label(name?: string);
5+
content(target?: Element);
6+
};
7+
8+
declare global {
9+
interface Window {
10+
wideTemplate?: HTMLDivElement & Template;
11+
}
12+
}

src/js/wide-mode/icons/close.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default `<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg" class="close-action"><path fillRule="evenodd" clipRule="evenodd" d="M9.46967 9.46967C9.76256 9.17678 10.2374 9.17678 10.5303 9.46967L14 12.9393L17.4697 9.46967C17.7626 9.17678 18.2374 9.17678 18.5303 9.46967C18.8232 9.76256 18.8232 10.2374 18.5303 10.5303L15.0607 14L18.5303 17.4697C18.8232 17.7626 18.8232 18.2374 18.5303 18.5303C18.2374 18.8232 17.7626 18.8232 17.4697 18.5303L14 15.0607L10.5303 18.5303C10.2374 18.8232 9.76256 18.8232 9.46967 18.5303C9.17678 18.2374 9.17678 17.7626 9.46967 17.4697L12.9393 14L9.46967 10.5303C9.17678 10.2374 9.17678 9.76256 9.46967 9.46967Z" fill="var(--g-color-text-primary)" fillOpacity="0.85"/></svg>`;

src/js/wide-mode/icons/expand.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 16 16"><path fill="currentColor" fill-rule="evenodd" d="M7.754 2.004a.75.75 0 0 0 0 1.5h4.75v4.742a.75.75 0 0 0 1.5 0V2.754a.75.75 0 0 0-.75-.75zm.492 11.992a.75.75 0 0 0 0-1.5h-4.75V7.754a.75.75 0 0 0-1.5 0v5.492a.75.75 0 0 0 .75.75z" clip-rule="evenodd"/></svg>`;

src/js/wide-mode/index.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import {applyWideMode} from './apply';
2+
3+
if (typeof document !== 'undefined') {
4+
window.addEventListener('load', applyWideMode);
5+
}

src/js/wide-mode/modal.ts

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import closeIcon from './icons/close';
2+
3+
const remove = () => {
4+
if (!window.wideTemplate) {
5+
return;
6+
}
7+
8+
window.wideTemplate.style.display = 'none';
9+
window.wideTemplate.content(undefined);
10+
};
11+
12+
const tbodyOf = (node: HTMLElement) => {
13+
if (node.tagName !== 'TABLE') {
14+
return undefined;
15+
}
16+
17+
const elements = Array.from(node.children);
18+
19+
const thead = elements.find((child) => child.tagName === 'THEAD');
20+
21+
if (thead) {
22+
return undefined;
23+
}
24+
25+
const tbody = elements.find((child) => child.tagName === 'TBODY');
26+
27+
return tbody;
28+
};
29+
30+
type ModalContainer = NonNullable<typeof window.wideTemplate>;
31+
32+
/* @todo refactor to markup with innerHTML @v8tenko */
33+
34+
const container = () => {
35+
if (window.wideTemplate) {
36+
return window.wideTemplate;
37+
}
38+
39+
const template = document.createElement('div') as ModalContainer;
40+
template.classList.add('dc-doc-page', 'wide-container');
41+
42+
const overlay = document.createElement('div');
43+
overlay.classList.add('wide-content-overlay');
44+
overlay.addEventListener('click', remove);
45+
46+
const wrapper = document.createElement('div');
47+
wrapper.classList.add('yfm', 'wide-content-wrapper');
48+
49+
const toolbar = document.createElement('div');
50+
toolbar.classList.add('wide-toolbar');
51+
52+
const close = document.createElement('div');
53+
close.classList.add('wide-actions');
54+
close.addEventListener('click', remove);
55+
close.innerHTML = closeIcon;
56+
57+
const title = document.createElement('p');
58+
title.classList.add('wide-entity-name');
59+
60+
template.label = (content: string) => {
61+
title.innerHTML = content;
62+
};
63+
64+
const content = document.createElement('div');
65+
content.classList.add('wide-content');
66+
67+
template.content = (target?: Element) => {
68+
if (typeof target === 'undefined') {
69+
content.innerHTML = '';
70+
71+
return;
72+
}
73+
74+
const cloned = target.cloneNode(true) as HTMLElement;
75+
76+
const tbody = tbodyOf(cloned);
77+
78+
/* used to stretch single tbody to 100% */
79+
tbody?.classList?.add('wide-thead-content');
80+
81+
content.replaceChildren(cloned);
82+
};
83+
84+
toolbar.append(title, close);
85+
wrapper.append(toolbar, content);
86+
template.append(overlay, wrapper);
87+
88+
template.style.display = 'none';
89+
document.body.appendChild(template);
90+
91+
window.wideTemplate = template;
92+
93+
return template;
94+
};
95+
96+
const render = (content: HTMLElement) => {
97+
const template = container();
98+
99+
if (content.title) {
100+
template.label(content.title);
101+
}
102+
103+
template.content(content);
104+
105+
template.style.display = 'flex';
106+
};
107+
108+
export const modal = {
109+
render,
110+
remove,
111+
};

src/scss/_modal.scss

+123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
.wide-container {
2+
position: fixed;
3+
width: 100vw;
4+
height: 100vh;
5+
display: flex;
6+
justify-content: center;
7+
align-items: center;
8+
top: 0px;
9+
left: 0px;
10+
z-index: 200;
11+
12+
&.dc-doc-page {
13+
max-width: 100% !important;
14+
}
15+
16+
.wide-content-viewer {
17+
visibility: hidden;
18+
}
19+
20+
.wide-content-wrapper {
21+
z-index: 200;
22+
background-color: var(--g-color-base-background);
23+
height: 90vh;
24+
width: 70vw;
25+
border-radius: 10px;
26+
display: flex;
27+
flex-direction: column;
28+
29+
.wide-content {
30+
height: 95%;
31+
width: 100%;
32+
33+
.wide-inner-element {
34+
max-width: 100%;
35+
width: 100%;
36+
height: 100%;
37+
}
38+
39+
table {
40+
border-radius: 0px;
41+
}
42+
43+
.wide-thead-content {
44+
display: table;
45+
width: 100%;
46+
}
47+
}
48+
49+
.wide-toolbar {
50+
display: flex;
51+
flex-direction: row;
52+
width: 100%;
53+
justify-content: space-between;
54+
align-items: center;
55+
padding: 10px 12px;
56+
}
57+
58+
.wide-entity-name {
59+
margin: 0;
60+
font-size: 18px;
61+
}
62+
63+
.wide-actions {
64+
align-self: flex-end;
65+
66+
.close-action {
67+
cursor: pointer;
68+
border-radius: 3px;
69+
70+
transition: background 300ms;
71+
72+
&:hover {
73+
background: var(--g-color-base-simple-hover);
74+
}
75+
}
76+
}
77+
}
78+
}
79+
80+
.wide-element-wrapper {
81+
display: inline-block;
82+
position: relative;
83+
padding-right: 30px;
84+
85+
&:hover > .wide-content-viewer {
86+
visibility: visible;
87+
}
88+
89+
90+
.wide-inner-element {
91+
max-width: 700px;
92+
}
93+
}
94+
95+
.wide-content-viewer {
96+
position: absolute;
97+
visibility: hidden;
98+
width: 30px;
99+
height: 30px;
100+
z-index: 100;
101+
padding: 6px;
102+
right: -20px;
103+
box-sizing: content-box;
104+
color: var(--g-color-text-primary);
105+
cursor: pointer;
106+
107+
& > svg {
108+
width: 20px;
109+
height: 20px;
110+
}
111+
}
112+
113+
114+
.wide-content-overlay {
115+
z-index: 100;
116+
width: 100vw;
117+
height: 100vh;
118+
position: fixed;
119+
top: 0;
120+
left: 0;
121+
background-color: black;
122+
opacity: 0.6;
123+
}

src/scss/yfm.scss

+1
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@
77
@import 'file';
88
@import 'term';
99
@import 'table';
10+
@import 'modal';
1011

1112
@import '@diplodoc/tabs-extension/runtime';

test/table/attrs.test.ts

+11
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,17 @@ describe('attrs parser tests', () => {
2222
});
2323
});
2424

25+
it('parses wide mode', () => {
26+
const attrs = new AttrsParser();
27+
28+
const result = attrs.parse('{wide-view wide-name="short table"}');
29+
30+
expect(result).toEqual({
31+
attr: ['wide-view'],
32+
'wide-name': ['short table'],
33+
});
34+
});
35+
2536
it('parses full attrs', () => {
2637
const attrs = new AttrsParser();
2738

0 commit comments

Comments
 (0)