fixed circle charts and added a component collection
This commit is contained in:
@@ -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 |
|
||||
@@ -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>
|
||||
|
||||
@@ -664,15 +664,15 @@ export class DevPage extends LitElement {
|
||||
<div class="component-label">Example</div>
|
||||
<div class="component-preview col">
|
||||
<circle-chart
|
||||
heading="Pilots by Category"
|
||||
centerText="Pilots"
|
||||
.segments=${[
|
||||
{ label: 'Sport', value: 124 },
|
||||
{ label: 'Serial', value: 89 },
|
||||
{ label: 'Open', value: 47 },
|
||||
{ label: 'Tandem', value: 18 },
|
||||
]}
|
||||
></circle-chart>
|
||||
heading="Pilots by Category"
|
||||
centerText="Pilots"
|
||||
.segments=${[
|
||||
{ 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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user