fixed circle charts and added a component collection

This commit is contained in:
CodingPhoenixx
2026-02-13 18:03:57 +01:00
parent 4f5ae60f5e
commit 67f7f4dd68
3 changed files with 289 additions and 27 deletions
+185
View File
@@ -0,0 +1,185 @@
# Component Library
## ui-button
| Attribute | Type | Description |
|------------|-----------|----------------------------|
| `disabled` | `boolean` | Disables the button |
| `full` | `boolean` | Full-width layout |
| Slot | Description |
|-----------|---------------------|
| `default` | Button label text |
| `icon` | Leading SVG icon |
---
## ui-button-secondary
| Attribute | Type | Description |
|------------|-----------|----------------------------|
| `disabled` | `boolean` | Disables the button |
| `full` | `boolean` | Full-width layout |
| Slot | Description |
|-----------|---------------------|
| `default` | Button label text |
| `icon` | Leading SVG icon |
---
## ui-badge
| Attribute | Type | Values |
|-----------|----------|------------------------------------------------------|
| `variant` | `string` | `accent` · `success` · `warning` · `error` · `muted` |
| Slot | Description |
|-----------|-------------------|
| `default` | Badge label text |
| `icon` | Leading SVG icon |
---
## notify-bar
| Attribute | Type | Values / Description |
|---------------|-----------|-----------------------------------------|
| `type` | `string` | `success` · `warning` · `error` |
| `message` | `string` | Notification text |
| `dismissible` | `boolean` | Whether the bar can be dismissed (default `true`) |
---
## ui-link
| Attribute | Type | Description |
|-----------|-----------|--------------------------------|
| `href` | `string` | Navigation target |
| `active` | `boolean` | Renders in active/highlighted state |
| Slot | Description |
|-----------|-------------|
| `default` | Link text |
---
## form-input
| Attribute | Type | Description |
|---------------|----------|------------------------------|
| `label` | `string` | Field label |
| `type` | `string` | `text` · `email` · `password` |
| `placeholder` | `string` | Placeholder text |
| `value` | `string` | Current input value |
| Event | Detail | Description |
|-----------------|----------------|-------------------------|
| `value-changed` | `{ value: string }` | Fires on input change |
---
## stat-card
| Attribute | Type | Description |
|-----------|----------|----------------------|
| `value` | `string` | Displayed metric |
| `label` | `string` | Metric description |
---
## icon-card
| Attribute | Type | Description |
|---------------|----------|--------------------|
| `heading` | `string` | Card title |
| `description` | `string` | Card body text |
| Slot | Description |
|--------|----------------|
| `icon` | Leading SVG icon |
---
## ui-card
| Attribute | Type | Description |
|------------|-----------|-----------------------------|
| `centered` | `boolean` | Centers all child content |
| Slot | Description |
|-----------|----------------------|
| `default` | Arbitrary content |
---
## card-header
| Attribute | Type | Description |
|--------------|----------|---------------|
| `heading` | `string` | Title text |
| `subheading` | `string` | Subtitle text |
---
## card-backdrop
_No attributes or slots observed in usage._
---
## horizontal-divider
_No attributes. Renders a visual separator line._
---
## loading-bar
| Attribute | Type | Values / Description |
|-----------------|-----------|-----------------------------------------------|
| `label` | `string` | Text label next to the bar |
| `value` | `number` | Progress percentage (`0``100`) |
| `variant` | `string` | _(default)_ · `success` · `warning` · `error` |
| `size` | `string` | `sm` · `md` · `lg` |
| `indeterminate` | `boolean` | Infinite animation, no fixed value |
| `hideValue` | `boolean` | Hides the percentage label |
---
## line-chart
| Attribute | Type | Description |
|------------|-----------|------------------------------------|
| `heading` | `string` | Chart title |
| `subtitle` | `string` | Chart subtitle |
| `xLabel` | `string` | X-axis label |
| `yLabel` | `string` | Y-axis label |
| `showArea` | `boolean` | Fill area beneath lines |
| `series` | `Array` | Array of series objects (see below) |
**Series object:**
| Key | Type | Description |
|----------|----------|------------------------------------------|
| `label` | `string` | Legend label |
| `color` | `string` | CSS color value |
| `points` | `Array` | Array of `{ x: number, y: number }` |
---
## circle-chart
| Attribute | Type | Description |
|--------------|----------|--------------------------------------|
| `heading` | `string` | Chart title |
| `centerText` | `string` | Text rendered in the donut center |
| `segments` | `Array` | Array of segment objects (see below) |
**Segment object:**
| Key | Type | Description |
|---------|----------|--------------------|
| `label` | `string` | Legend label |
| `value` | `number` | Segment value |
| `color` | `string` | CSS color value |
+95 -18
View File
@@ -1,10 +1,10 @@
import { LitElement, html, css, svg } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { customElement, property, state } from 'lit/decorators.js';
export interface CircleSegment {
label: string;
value: number;
color?: string;
color: string;
}
@customElement('circle-chart')
@@ -98,6 +98,46 @@ export class CircleChart extends LitElement {
filter: brightness(1.15);
}
.tooltip {
position: absolute;
pointer-events: none;
background: var(--color-bg-nav, #1a1a2e);
border: 1px solid var(--color-border, #333);
border-radius: 0.5rem;
padding: 0.35rem 0.65rem;
display: flex;
align-items: center;
gap: 0.4rem;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
transform: translate(-50%, -120%);
z-index: 10;
white-space: nowrap;
opacity: 0;
transition: opacity 0.15s ease;
}
.tooltip.visible {
opacity: 1;
}
.tooltip-dot {
width: 0.45rem;
height: 0.45rem;
border-radius: 50%;
flex-shrink: 0;
}
.tooltip-label {
font-size: 0.75rem;
color: color-mix(in srgb, var(--color-text) 65%, transparent);
}
.tooltip-value {
font-size: 0.75rem;
font-weight: 700;
color: var(--color-text);
}
.legend {
display: flex;
flex-direction: column;
@@ -137,14 +177,33 @@ export class CircleChart extends LitElement {
@property() centerText = 'Total';
@property({ type: Array }) segments: CircleSegment[] = [];
private defaultColors = [
'var(--color-accent)',
'#30a46c',
'#e79d13',
'#e5484d',
'#6e56cf',
'#0091ff',
];
@state() private _hoveredIndex = -1;
@state() private _tooltipX = 0;
@state() private _tooltipY = 0;
private _onArcEnter(e: MouseEvent, index: number) {
this._hoveredIndex = index;
this._updateTooltipPos(e);
}
private _onArcMove(e: MouseEvent) {
if (this._hoveredIndex < 0) return;
this._updateTooltipPos(e);
}
private _onArcLeave() {
this._hoveredIndex = -1;
}
private _updateTooltipPos(e: MouseEvent) {
const container = this.shadowRoot!.querySelector(
'.svg-container'
) as HTMLElement;
if (!container) return;
const rect = container.getBoundingClientRect();
this._tooltipX = e.clientX - rect.left;
this._tooltipY = e.clientY - rect.top;
}
render() {
const total = this.segments.reduce((s, d) => s + d.value, 0);
@@ -160,8 +219,6 @@ export class CircleChart extends LitElement {
const pct = total > 0 ? seg.value / total : 0;
const dash = pct * usable;
const gap = circumference - dash;
const color =
seg.color || this.defaultColors[i % this.defaultColors.length];
const currentOffset = offset;
offset += dash + gapSize;
@@ -169,14 +226,22 @@ export class CircleChart extends LitElement {
<circle
cx="80" cy="80" r="${radius}"
fill="none"
stroke="${color}"
stroke="${seg.color}"
stroke-width="16"
stroke-dasharray="${dash} ${gap}"
stroke-dashoffset="${-currentOffset}"
@mouseenter=${(e: MouseEvent) => this._onArcEnter(e, i)}
@mousemove=${(e: MouseEvent) => this._onArcMove(e)}
@mouseleave=${() => this._onArcLeave()}
/>
`;
});
const hovered =
this._hoveredIndex >= 0
? this.segments[this._hoveredIndex]
: null;
return html`
<div class="chart-wrapper">
${this.heading
@@ -189,17 +254,29 @@ export class CircleChart extends LitElement {
<span class="center-value">${total}</span>
<span class="center-text">${this.centerText}</span>
</div>
<div
class="tooltip ${hovered ? 'visible' : ''}"
style="left:${this._tooltipX}px;top:${this._tooltipY}px"
>
${hovered
? html`
<span
class="tooltip-dot"
style="background:${hovered.color}"
></span>
<span class="tooltip-label">${hovered.label}</span>
<span class="tooltip-value">${hovered.value}</span>
`
: null}
</div>
</div>
<div class="legend">
${this.segments.map(
(seg, i) => html`
(seg) => html`
<div class="legend-item">
<span
class="legend-dot"
style="background: ${seg.color ||
this.defaultColors[
i % this.defaultColors.length
]}"
style="background:${seg.color}"
></span>
<span class="legend-label">${seg.label}</span>
<span class="legend-value">${seg.value}</span>
+5 -5
View File
@@ -667,12 +667,12 @@ export class DevPage extends LitElement {
heading="Pilots by Category"
centerText="Pilots"
.segments=${[
{ label: 'Sport', value: 124 },
{ label: 'Serial', value: 89 },
{ label: 'Open', value: 47 },
{ label: 'Tandem', value: 18 },
{ label: 'Sport', value: 124, color: 'var(--color-accent)' },
{ label: 'Serial', value: 89, color: '#30a46c' },
{ label: 'Open', value: 47, color: '#e79d13' },
{ label: 'Tandem', value: 18, color: '#e5484d' },
]}
></circle-chart>
></circle-chart>
</div>
</div>
</div>