feat(panel): render icons in switcher

This commit is contained in:
dragonwocky 2023-08-09 23:59:18 +10:00
parent 681d5c13f6
commit 8745dc9313
Signed by: dragonwocky
GPG Key ID: 7998D08F7D7BD7A8
4 changed files with 84 additions and 49 deletions

View File

@ -12,7 +12,7 @@ import { fileURLToPath } from "node:url";
const dependencies = { const dependencies = {
"htm.min.js": "https://unpkg.com/htm@3.1.1/mini/index.js", "htm.min.js": "https://unpkg.com/htm@3.1.1/mini/index.js",
"twind.min.js": "https://unpkg.com/@twind/cdn@1.0.8/cdn.global.js", "twind.min.js": "https://unpkg.com/@twind/cdn@1.0.8/cdn.global.js",
"lucide.min.js": "https://unpkg.com/lucide@0.263.0/dist/umd/lucide.min.js", "lucide.min.js": "https://unpkg.com/lucide@0.264.0/dist/umd/lucide.min.js",
"coloris.min.js": "coloris.min.js":
"https://cdn.jsdelivr.net/gh/mdbassit/Coloris@v0.21.0/dist/coloris.min.js", "https://cdn.jsdelivr.net/gh/mdbassit/Coloris@v0.21.0/dist/coloris.min.js",
"coloris.min.css": "coloris.min.css":

View File

@ -7,12 +7,7 @@
import { Tooltip } from "./Tooltip.mjs"; import { Tooltip } from "./Tooltip.mjs";
import { Select } from "../menu/islands/Select.mjs"; import { Select } from "../menu/islands/Select.mjs";
function PanelView(props) { function View(props) {
const { html } = globalThis.__enhancerApi;
return html``;
}
function PanelSwitcher(props) {
const { html } = globalThis.__enhancerApi; const { html } = globalThis.__enhancerApi;
return html``; return html``;
} }
@ -37,13 +32,36 @@ function Panel({
duration-[${transitionDuration}ms] group/panel`, duration-[${transitionDuration}ms] group/panel`,
}); });
const $resizeHandle = html`<div const values = [
{
icon: html`<i class="i-type h-[16px] w-[16px]" />`,
value: "word counter",
},
{
// prettier-ignore
icon: html`<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24">
<circle cx="5" cy="7" r="2.8"/>
<circle cx="5" cy="17" r="2.79"/>
<path d="M21,5.95H11c-0.55,0-1-0.45-1-1v0c0-0.55,0.45-1,1-1h10c0.55,0,1,0.45,1,1v0C22,5.5,21.55,5.95,21,5.95z"/>
<path d="M17,10.05h-6c-0.55,0-1-0.45-1-1v0c0-0.55,0.45-1,1-1h6c0.55,0,1,0.45,1,1v0C18,9.6,17.55,10.05,17,10.05z"/>
<path d="M21,15.95H11c-0.55,0-1-0.45-1-1v0c0-0.55,0.45-1,1-1h10c0.55,0,1,0.45,1,1v0C22,15.5,21.55,15.95,21,15.95z" />
<path d="M17,20.05h-6c-0.55,0-1-0.45-1-1v0c0-0.55,0.45-1,1-1h6c0.55,0,1,0.45,1,1v0C18,19.6,17.55,20.05,17,20.05z"/>
</svg>`,
value: "outliner",
},
],
_get = () => useState(["panelView"])[0],
_set = (value) => {
setState({ panelView: value, rerender: true });
};
const $resize = html`<div
class="absolute h-full w-[3px] left-[-3px] class="absolute h-full w-[3px] left-[-3px]
z-10 transition duration-300 hover:(cursor-col-resize z-10 transition duration-300 hover:(cursor-col-resize
shadow-[var(--theme--fg-border)_-2px_0px_0px_0px_inset]) shadow-[var(--theme--fg-border)_-2px_0px_0px_0px_inset])
active:cursor-text group-not-[open]/panel:hidden" active:cursor-text group-not-[open]/panel:hidden"
></div>`, ></div>`,
$chevronClose = html`<button $close = html`<button
aria-label="Close side panel" aria-label="Close side panel"
class="user-select-none h-[24px] w-[24px] duration-[20ms] class="user-select-none h-[24px] w-[24px] duration-[20ms]
transition inline-flex items-center justify-center mr-[10px] transition inline-flex items-center justify-center mr-[10px]
@ -53,33 +71,29 @@ function Panel({
class="i-chevrons-right w-[20px] h-[20px] class="i-chevrons-right w-[20px] h-[20px]
text-[color:var(--theme--fg-secondary)]" text-[color:var(--theme--fg-secondary)]"
/> />
</div>`; </div>`,
$switcher = html`<div
const values = ["default", "outliner", "word counter"], class="relative flex items-center
_get = () => useState(["panelView"])[0], font-medium p-[8.5px] ml-[4px] grow"
_set = (value) => {
setState({ panelView: value, rerender: true });
};
const $panel = html`<aside ...${props}>
${$resizeHandle}
<div
class="flex justify-between items-center
border-(b [color:var(--theme--fg-border)])"
> >
<${Select}
popupMode="dropdown"
maxWidth="${maxWidth}"
class="w-full text-left"
...${{ _get, _set, values, maxWidth: maxWidth - 56 }}
/>
</div>`,
$view = html`<div class="h-full overflow-y-auto"></div>`,
$panel = html`<aside ...${props}>
${$resize}
<div <div
class="relative flex grow font-medium items-center p-[8.5px] ml-[4px]" class="flex justify-between items-center
border-(b [color:var(--theme--fg-border)])"
> >
<${Select} ${$switcher}${$close}
popupMode="dropdown"
maxWidth="${maxWidth}"
class="w-full text-left"
...${{ _get, _set, values, maxWidth: maxWidth - 56 }}
/>
</div> </div>
${$chevronClose} ${$view}
</div> </aside>`;
</aside>`;
let preDragWidth, let preDragWidth,
dragStartX = 0; dragStartX = 0;
@ -103,7 +117,7 @@ function Panel({
// trigger panel close if not resized // trigger panel close if not resized
if (dragStartX - event.clientX === 0) $panel.close(); if (dragStartX - event.clientX === 0) $panel.close();
}; };
$resizeHandle.addEventListener("mousedown", startDrag); $resize.addEventListener("mousedown", startDrag);
const $tooltip = html`<${Tooltip}> const $tooltip = html`<${Tooltip}>
<span>Drag</span> to resize<br /> <span>Drag</span> to resize<br />
@ -112,16 +126,18 @@ function Panel({
showTooltip = (event) => { showTooltip = (event) => {
setTimeout(() => { setTimeout(() => {
const panelOpen = $panel.hasAttribute("open"), const panelOpen = $panel.hasAttribute("open"),
handleHovered = $resizeHandle.matches(":hover"); handleHovered = $resize.matches(":hover");
if (!panelOpen || !handleHovered) return; if (!panelOpen || !handleHovered) return;
const { x } = $resizeHandle.getBoundingClientRect(); const { x } = $resize.getBoundingClientRect();
$tooltip.show(x, event.clientY); $tooltip.show(x, event.clientY);
}, 200); }, 200);
}; };
$resizeHandle.addEventListener("mouseover", showTooltip); $resize.addEventListener("mouseover", showTooltip);
$resizeHandle.addEventListener("mouseout", () => $tooltip.hide()); $resize.addEventListener("mouseout", () => $tooltip.hide());
$chevronClose.addEventListener("click", () => $panel.close()); $close.addEventListener("click", () => $panel.close());
// normally would place outside of an island, but in
// this case is necessary for syncing up animations
const notionHelp = ".notion-help-button", const notionHelp = ".notion-help-button",
repositionHelp = async () => { repositionHelp = async () => {
const $notionHelp = document.querySelector(notionHelp); const $notionHelp = document.querySelector(notionHelp);

View File

@ -6,12 +6,12 @@
import { Popup } from "./Popup.mjs"; import { Popup } from "./Popup.mjs";
function Option({ value, _get, _set }) { function Option({ icon = "", value = "", _get, _set }) {
const { html, useState } = globalThis.__enhancerApi, const { html, useState } = globalThis.__enhancerApi,
$selected = html`<i class="ml-auto i-check w-[16px] h-[16px]"></i>`, $selected = html`<i class="ml-auto i-check w-[16px] h-[16px]"></i>`,
$option = html`<div $option = html`<div
tabindex="0" tabindex="0"
role="button" role="option"
class="select-none cursor-pointer rounded-[3px] class="select-none cursor-pointer rounded-[3px]
flex items-center w-full h-[28px] px-[12px] leading-[1.2] flex items-center w-full h-[28px] px-[12px] leading-[1.2]
transition duration-[20ms] hover:bg-[color:var(--theme--bg-hover)]" transition duration-[20ms] hover:bg-[color:var(--theme--bg-hover)]"
@ -20,8 +20,11 @@ function Option({ value, _get, _set }) {
if (event.key === "Enter") _set?.(value); if (event.key === "Enter") _set?.(value);
}} }}
> >
<div class="mr-[6px] text-[14px] text-ellipsis overflow-hidden"> <div
${value} class="mr-[6px] inline-flex items-center gap-[6px]
text-[14px] text-ellipsis overflow-hidden"
>
${icon}<span>${value}</span>
</div> </div>
</div>`; </div>`;
useState(["rerender"], async () => { useState(["rerender"], async () => {
@ -41,7 +44,6 @@ function Select({
maxWidth = 256, maxWidth = 256,
...props ...props
}) { }) {
let _initialValue;
const { html, extendProps, setState, useState } = globalThis.__enhancerApi, const { html, extendProps, setState, useState } = globalThis.__enhancerApi,
// dir="rtl" overflows to the left during transition // dir="rtl" overflows to the left during transition
$select = html`<div $select = html`<div
@ -53,9 +55,24 @@ function Select({
max-w-[${maxWidth}px] pl-[8px] pr-[28px] transition max-w-[${maxWidth}px] pl-[8px] pr-[28px] transition
duration-[20ms] hover:bg-[color:var(--theme--bg-hover)]" duration-[20ms] hover:bg-[color:var(--theme--bg-hover)]"
></div>`; ></div>`;
let _initialValue;
values = values.map((value) => {
value = typeof value === "string" ? { value } : value;
if (typeof value.icon === "string" && value.icon) {
value.icon = html`<i class="i-${value.icon} h-[16px] w-[16px]" />`;
} else value.icon ??= "";
value.value ??= "";
return value;
});
useState(["rerender"], async () => { useState(["rerender"], async () => {
const value = (await _get?.()) ?? ($select.innerText || values[0]); const value = (await _get?.()) ?? ($select.innerText || values[0].value),
$select.innerText = value; icon = values.find((v) => v.value === value)?.icon;
$select.innerHTML = "";
// swap icon/value order for correct display when dir="rtl"
$select.append(html`<div class="inline-flex items-center gap-[6px]">
<span>${value}</span>${icon?.cloneNode?.(true) || ""}
</div>`);
if (_requireReload) { if (_requireReload) {
_initialValue ??= value; _initialValue ??= value;
if (value !== _initialValue) setState({ databaseUpdated: true }); if (value !== _initialValue) setState({ databaseUpdated: true });
@ -76,7 +93,9 @@ function Select({
$select.style.width = ""; $select.style.width = "";
$select.style.background = ""; $select.style.background = "";
}} }}
>${values.map((value) => html`<${Option} ...${{ value, _get, _set }} />`)} >${values.map((value) => {
return html`<${Option} ...${{ ...value, _get, _set }} />`;
})}
<//> <//>
<i <i
class="i-chevron-down pointer-events-none class="i-chevron-down pointer-events-none

File diff suppressed because one or more lines are too long