1
0
Fork 0
forked from 2sys/phonograph
phonograph/components/src/lens-controls-shell.ts
2025-07-08 14:37:03 -07:00

214 lines
5.1 KiB
TypeScript

import { css, html, LitElement } from "lit";
import { classMap } from "lit/directives/class-map.js";
import { createRef, ref } from "lit/directives/ref.js";
import { customElement, property, state } from "lit/decorators.js";
import "./entrypoints/custom-icon.ts";
export type TabItem = {
icon: string;
label: string;
value: string;
};
export class SelectTabEvent extends Event {
value: string;
constructor(value: string) {
super("select-tab");
this.value = value;
}
}
@customElement("lens-controls-shell")
export class LensControlsShell extends LitElement {
@property({ attribute: true, type: Array })
tabs: TabItem[] = [];
@property({ attribute: "active-tab" })
activeTab?: string;
@state()
private _isOpen = false;
private _controlPanelRef = createRef<HTMLDivElement>();
static override styles = css`
:host {
--shadow: 0 0.5rem 0.5rem #3333;
--background: #fff;
--border-color: #ccc;
--border-radius: 0.5rem;
}
#container-positioner {
position: fixed;
bottom: 2rem;
display: flex;
justify-content: center;
align-items: flex-end;
overflow: visible;
width: 100%;
height: 0;
}
#container {
display: grid;
grid-template-columns: max-content max-content 1rem max-content;
filter: drop-shadow(var(--shadow));
}
#tab-box {
height: 2rem;
display: flex;
align-items: center;
list-style-type: none;
padding: 0.5rem;
margin: 0;
border: solid 1px var(--border-color);
border-right: none;
border-top-left-radius: var(--border-radius);
border-bottom-left-radius: var(--border-radius);
background: var(--background);
grid-row: 2;
& button {
appearance: none;
background: none;
border: none;
font-size: inherit;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
font-family: inherit;
cursor: pointer;
height: 2rem;
}
& button.active {
background: #39f3;
}
}
#control-bar {
height: 3rem;
flex-shrink: 0;
border: solid 1px var(--border-color);
border-top-right-radius: var(--border-radius);
border-bottom-right-radius: var(--border-radius);
overflow: hidden;
width: 40rem;
grid-row: 2;
background: var(--background);
&.open {
border-top-right-radius: 0;
}
}
#control-buttons {
height: 3rem;
grid-row: 2;
grid-column: 4;
height: 100%;
}
#control-panel-positioner {
grid-template-columns: subgrid;
grid-column: 2;
grid-row: 1;
position: relative;
overflow: visible;
/* Flexbox positioning is required for Safari */
display: flex;
align-items: flex-end;
anchor-name: --control-bar;
}
#control-panel-container:popover-open {
inset: unset;
border: solid 1px var(--border-color);
border-bottom: none;
border-top-left-radius: var(--border-radius);
border-top-right-radius: var(--border-radius);
margin: 0;
position: fixed;
display: block;
width: 40rem;
/* Anchor positioning is required for Chromium */
position-anchor: --control-bar;
position-area: top;
padding: 0;
background: var(--background);
box-shadow: var(--shadow);
/* Clip drop shadow */
clip-path: polygon(
-100% -100%,
200% -100%,
200% 200%,
100% 200%,
100% 100%,
-100% 100%
);
}
#control-panel {
padding: 0.5rem;
overflow: auto;
max-height: 8rem;
}
`;
open(): void {
this._controlPanelRef.value?.showPopover();
}
protected override render() {
return html`
<div id="container-positioner">
<div id="container">
<ul id="tab-box" @click="${this.open}">
${this.tabs.map(({ icon, label, value }) =>
html`
<li>
<button type="button" class="${classMap({
active: this.activeTab === value,
})}" @click="${() => {
this.dispatchEvent(new SelectTabEvent(value));
}}">
${icon
? html`<custom-icon name=${icon} alt=${label}></custom-label>`
: label}
</button>
</li>
`
)}
</ul>
<div id="control-bar" class="${classMap({
open: this._isOpen,
})}" @click="${this.open}">
<slot name="control-bar"></slot>
</div>
<div id="control-buttons">
<slot name="control-buttons"></slot>
</div>
<div id="control-panel-positioner">
<div
id="control-panel-container"
popover="auto"
${ref(
this._controlPanelRef,
)}
@toggle="${(ev: ToggleEvent) => {
this._isOpen = ev.newState === "open";
}}"
>
<div id="control-panel">
<slot name="control-panel"></slot>
</div>
</div>
</div>
</div>
</div>
`;
}
}