Files
dees-catalog/ts_web/elements/00group-media/dees-tile-note/component.ts

232 lines
6.2 KiB
TypeScript
Raw Normal View History

import {
property,
state,
html,
customElement,
css,
cssManager,
type TemplateResult,
type CSSResult,
} from '@design.estate/dees-element';
import { DeesTileBase } from '../dees-tile-shared/DeesTileBase.js';
import { tileBaseStyles } from '../dees-tile-shared/styles.js';
import { demo } from './demo.js';
declare global {
interface HTMLElementTagNameMap {
'dees-tile-note': DeesTileNote;
}
}
@customElement('dees-tile-note')
export class DeesTileNote extends DeesTileBase {
public static demo = demo;
public static demoGroups = ['Media'];
public static styles = [
...tileBaseStyles,
css`
.note-content {
position: relative;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
background: ${cssManager.bdTheme('#ffffff', 'hsl(60 5% 96%)')};
overflow: hidden;
}
.note-header {
padding: 12px 14px 8px;
flex-shrink: 0;
}
.note-title {
font-size: 12px;
font-weight: 700;
color: ${cssManager.bdTheme('hsl(215 20% 20%)', 'hsl(215 20% 20%)')};
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1.3;
}
.note-body {
flex: 1;
padding: 0 14px 14px;
position: relative;
overflow: hidden;
}
.note-text {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
font-size: 10px;
line-height: 1.5;
color: ${cssManager.bdTheme('hsl(215 10% 40%)', 'hsl(215 10% 35%)')};
white-space: pre-wrap;
word-wrap: break-word;
overflow: hidden;
margin: 0;
}
.note-fade {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 60px;
background: linear-gradient(
transparent,
${cssManager.bdTheme('#ffffff', 'hsl(60 5% 96%)')}
);
pointer-events: none;
}
.note-language {
position: absolute;
top: 8px;
right: 8px;
padding: 2px 6px;
background: ${cssManager.bdTheme('hsl(215 20% 92%)', 'hsl(215 20% 88%)')};
color: ${cssManager.bdTheme('hsl(215 16% 50%)', 'hsl(215 16% 40%)')};
border-radius: 3px;
font-size: 9px;
font-weight: 600;
text-transform: uppercase;
z-index: 5;
}
.note-lines {
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 34px;
border-right: 1px solid ${cssManager.bdTheme('hsl(0 70% 85%)', 'hsl(0 50% 80%)')};
display: flex;
flex-direction: column;
padding-top: 12px;
}
.line-number {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
font-size: 9px;
line-height: 15px; /* matches 10px * 1.5 line-height */
color: ${cssManager.bdTheme('hsl(215 10% 75%)', 'hsl(215 10% 70%)')};
text-align: right;
padding-right: 6px;
}
.note-line-indicator {
position: absolute;
bottom: 8px;
right: 8px;
padding: 3px 8px;
background: rgba(0, 0, 0, 0.6);
color: white;
border-radius: 4px;
font-size: 9px;
font-weight: 600;
font-variant-numeric: tabular-nums;
backdrop-filter: blur(8px);
z-index: 10;
pointer-events: none;
}
`,
] as any;
@property({ type: String })
accessor title: string = '';
@property({ type: String })
accessor content: string = '';
@property({ type: String })
accessor language: string = '';
@state()
accessor isHovering: boolean = false;
private noteBodyElement: HTMLElement | null = null;
protected renderTileContent(): TemplateResult {
const lines = this.content.split('\n');
return html`
<div class="note-content">
${this.language ? html`
<div class="note-language">${this.language}</div>
` : ''}
${this.title ? html`
<div class="note-header">
<div class="note-title">${this.title}</div>
</div>
` : ''}
<div class="note-body">
<pre class="note-text">${lines.join('\n')}</pre>
${!this.isHovering ? html`<div class="note-fade"></div>` : ''}
</div>
${this.isHovering && lines.length > 12 ? html`
<div class="note-line-indicator">
Line ${this.getVisibleLineRange(lines.length)}
</div>
` : ''}
</div>
${this.clickable ? html`
<div class="tile-overlay">
<dees-icon icon="lucide:FileText"></dees-icon>
<span>Open Note</span>
</div>
` : ''}
`;
}
protected getTileClickDetail(): Record<string, unknown> {
return {
title: this.title,
content: this.content,
language: this.language,
};
}
protected onTileMouseEnter(): void {
this.isHovering = true;
if (!this.noteBodyElement) {
this.noteBodyElement = this.shadowRoot?.querySelector('.note-body') as HTMLElement;
}
}
protected onTileMouseLeave(): void {
this.isHovering = false;
if (this.noteBodyElement) {
this.noteBodyElement.scrollTop = 0;
}
}
protected onTileMouseMove(e: MouseEvent): void {
if (!this.isHovering || !this.noteBodyElement) return;
const totalLines = this.content.split('\n').length;
if (totalLines <= 12) return;
const rect = this.getBoundingClientRect();
const x = e.clientX - rect.left;
const percentage = Math.max(0, Math.min(1, x / rect.width));
const maxScroll = this.noteBodyElement.scrollHeight - this.noteBodyElement.clientHeight;
this.noteBodyElement.scrollTop = percentage * maxScroll;
}
private getVisibleLineRange(totalLines: number): string {
if (!this.noteBodyElement) return `112 of ${totalLines}`;
const lineHeight = 15; // 10px font × 1.5 line-height
const firstLine = Math.floor(this.noteBodyElement.scrollTop / lineHeight) + 1;
const visibleCount = Math.floor(this.noteBodyElement.clientHeight / lineHeight);
const lastLine = Math.min(firstLine + visibleCount - 1, totalLines);
return `${firstLine}${lastLine} of ${totalLines}`;
}
}