Skip to content

Commit 80d67e5

Browse files
committed
feat: add examples/templates when adding a new tab
1 parent 8f3052d commit 80d67e5

File tree

3 files changed

+154
-31
lines changed

3 files changed

+154
-31
lines changed
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
export default [
2+
{
3+
name: "counter",
4+
content: `/**
5+
* This component leverages Marko's controllable pattern!
6+
*
7+
* To hoist the value out when using it, you can optionally
8+
* bind a value like this:
9+
* \`\`\`
10+
* <let/myCount=0>
11+
* <counter value:=myCount/>
12+
* \`\`\`
13+
*/
14+
15+
<let/count:=input.value>
16+
<button onClick() { count++ }>
17+
\${count}
18+
</button>
19+
`,
20+
},
21+
{
22+
name: "let-localstorage",
23+
content: `/**
24+
* This can be used just like any other \`<let>\` tag, except
25+
* the value is synced over \`localStorage\` so it stays
26+
* constant after a page reload!
27+
*
28+
* Just add a \`key=\` attribute:
29+
* \`\`\`
30+
* <let-localstorage/count=0 key="my-count"/>
31+
* \`\`\`
32+
*/
33+
34+
<let/internalValue=input.value>
35+
36+
<script>
37+
const existingValue = localStorage.getItem(input.key);
38+
if (existingValue) {
39+
internalValue = JSON.parse(existingValue);
40+
}
41+
const handleStorageChange = (e) => {
42+
if (e.key === input.key) {
43+
internalValue = JSON.parse(e.newValue ?? "");
44+
}
45+
};
46+
window.addEventListener(
47+
"local-storage-update",
48+
(e) => handleStorageChange(e.detail),
49+
{
50+
signal: $signal,
51+
},
52+
);
53+
</script>
54+
55+
<return=internalValue valueChange(newValue) {
56+
const stringified = JSON.stringify(newValue);
57+
localStorage.setItem(input.key, stringified);
58+
window.dispatchEvent(
59+
new CustomEvent("local-storage-update", {
60+
detail: {
61+
key: input.key,
62+
newValue: stringified,
63+
storageArea: localStorage,
64+
},
65+
}),
66+
);
67+
}>
68+
`,
69+
},
70+
];
Lines changed: 62 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { faPlus } from "@fortawesome/free-solid-svg-icons";
22
import type { File } from "app/util/workspace";
33
import * as styles from "./tabs.style.module.scss";
4+
import EXAMPLE_TEMPLATES from "./examples"
45
export interface Input {
56
files: File[];
67
filesChange: (files: File[]) => void;
@@ -10,53 +11,53 @@ export interface Input {
1011

1112
let/tabs:=input.files
1213
let/selected:=input.selected
14+
let/editingIndex=-1
1315

1416
div class=styles.tabs
1517
div class=styles.files
1618
for|tab, i| of=tabs
1719
const/isSelected=selected === i
1820
const/isEditable=i !== 0
19-
let/editing=false valueChange=(isEditable ? undefined : () => {})
21+
const/isEditing=editingIndex === i
2022

2123
div
2224
,aria-selected=isSelected && "true"
2325
,tabindex=0
2426
,role="button"
2527
,onClick() {
26-
if (isSelected) {
27-
editing = true;
28-
} else {
28+
if (isSelected && !isEditing) {
29+
editingIndex = i;
30+
} else if (!isEditing) {
2931
selected = i;
3032
}
3133
}
3234
,onKeyPress(e, el) {
3335
if (e.target === el && (e.key === " " || e.key === "Enter")) {
34-
if (isSelected) {
35-
editing = true;
36-
} else {
36+
if (isSelected && !isEditing) {
37+
editingIndex = i;
38+
} else if (!isEditing) {
3739
selected = i;
3840
}
3941
}
4042
}
4143
,class=styles.tab
42-
if=editing
44+
if=isEditing
4345
let/length=(tab.path.length)
4446
input/$input
4547
,value=tab.path
4648
,size=length
47-
,onInput() {
48-
length = $input().value.length;
49+
,onInput(_, el) {
50+
length = el.value.length;
4951
}
50-
,onBlur() {
51-
tabs = tabs.toSpliced(i, 1, { ...tab, path: $input().value });
52-
editing = false;
52+
,onBlur(_, el) {
53+
tabs = tabs.toSpliced(i, 1, { ...tab, path: el.value });
54+
editingIndex = -1;
5355
}
5456
,onKeyPress(e) {
5557
if (e.key === "Enter") {
56-
$input().blur();
57-
editing = false;
58+
document.querySelector<HTMLElement>(".cm-content")?.focus();
5859
} else if (e.key === "Escape") {
59-
editing = false;
60+
editingIndex = -1;
6061
}
6162
}
6263
script --
@@ -77,21 +78,53 @@ div class=styles.tabs
7778
,class=styles.close
7879
-- ×
7980

81+
id/popoverId
82+
8083
button
8184
,title="Add file"
82-
,onClick() {
83-
let filename = prompt(
84-
"What filename (extension optional)?",
85-
tabs.some((tab) => tab.path === "Tag.marko")
86-
? `Tag${tabs.length}`
87-
: "Tag",
88-
);
85+
,popovertarget=popoverId
86+
,class=styles.add
87+
,onClick(_, btn) {
88+
const { top, right } = btn.getBoundingClientRect();
89+
$popover().style.top = `${top}px`;
90+
$popover().style.right = `${innerWidth - right}px`;
91+
}
92+
fa-icon=faPlus
8993

90-
if (filename) {
91-
if (!filename.includes(".")) filename += ".marko";
92-
tabs = [...tabs, { path: filename, content: "" }];
93-
selected = tabs.length - 1;
94+
div/$popover
95+
,id=popoverId
96+
,class=styles.popover
97+
,popover
98+
,role="menu"
99+
,onFocusOut(e) {
100+
if (!$popover().contains(e.relatedTarget as Node)) {
101+
$popover().hidePopover();
94102
}
95103
}
96-
,class=styles.add
97-
fa-icon=faPlus
104+
105+
button
106+
,role="menuitem"
107+
,autofocus
108+
,onClick() {
109+
const newIndex = tabs.length;
110+
tabs = [...tabs, {
111+
path: `Tag${tabs.filter(t => /^Tag\d*\.marko$/.test(t.path)).length || ""}.marko`,
112+
content: ""
113+
}];
114+
selected = newIndex;
115+
editingIndex = newIndex;
116+
}
117+
,class=styles.templateButton
118+
-- Blank
119+
120+
for|template| of=EXAMPLE_TEMPLATES
121+
if=!tabs.some(({ path }) => path === template.name + ".marko")
122+
button
123+
,role="menuitem"
124+
,onClick() {
125+
selected = tabs.length;
126+
tabs = [...tabs, { path: template.name + ".marko", content: template.content }];
127+
document.querySelector<HTMLElement>(".cm-content")?.focus();
128+
}
129+
,class=styles.templateButton
130+
-- ${`<${template.name}>`}

src/routes/playground/tags/playground/tags/editor/tags/tabs/tabs.style.module.scss

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,9 @@
4343
color: var(--color-blue);
4444
background-color: var(--color-gray-dim);
4545
align-items: center;
46-
border: 1px solid transparent;
46+
4747
&:hover {
4848
background-color: var(--color-gray-alt-dim);
49-
// color: var(--color-gray-dim);
5049
}
5150

5251
svg {
@@ -56,3 +55,24 @@
5655
}
5756
}
5857
}
58+
59+
.popover {
60+
inset: unset;
61+
max-width: 95vw;
62+
background-color: var(--color-gray-alt-dim);
63+
border: 1px solid var(--color-gray-alt);
64+
border-radius: 0.25rem;
65+
}
66+
67+
.templateButton {
68+
width: 100%;
69+
padding: 0.6rem 0.75rem;
70+
background-color: transparent;
71+
border: none;
72+
text-align: left;
73+
74+
&:hover,
75+
&:focus-visible {
76+
background-color: var(--color-gray-dim);
77+
}
78+
}

0 commit comments

Comments
 (0)