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 { LitElement, html, css, svg } from 'lit';
|
||||||
import { customElement, property } from 'lit/decorators.js';
|
import { customElement, property, state } from 'lit/decorators.js';
|
||||||
|
|
||||||
export interface CircleSegment {
|
export interface CircleSegment {
|
||||||
label: string;
|
label: string;
|
||||||
value: number;
|
value: number;
|
||||||
color?: string;
|
color: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@customElement('circle-chart')
|
@customElement('circle-chart')
|
||||||
@@ -98,6 +98,46 @@ export class CircleChart extends LitElement {
|
|||||||
filter: brightness(1.15);
|
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 {
|
.legend {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -137,14 +177,33 @@ export class CircleChart extends LitElement {
|
|||||||
@property() centerText = 'Total';
|
@property() centerText = 'Total';
|
||||||
@property({ type: Array }) segments: CircleSegment[] = [];
|
@property({ type: Array }) segments: CircleSegment[] = [];
|
||||||
|
|
||||||
private defaultColors = [
|
@state() private _hoveredIndex = -1;
|
||||||
'var(--color-accent)',
|
@state() private _tooltipX = 0;
|
||||||
'#30a46c',
|
@state() private _tooltipY = 0;
|
||||||
'#e79d13',
|
|
||||||
'#e5484d',
|
private _onArcEnter(e: MouseEvent, index: number) {
|
||||||
'#6e56cf',
|
this._hoveredIndex = index;
|
||||||
'#0091ff',
|
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() {
|
render() {
|
||||||
const total = this.segments.reduce((s, d) => s + d.value, 0);
|
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 pct = total > 0 ? seg.value / total : 0;
|
||||||
const dash = pct * usable;
|
const dash = pct * usable;
|
||||||
const gap = circumference - dash;
|
const gap = circumference - dash;
|
||||||
const color =
|
|
||||||
seg.color || this.defaultColors[i % this.defaultColors.length];
|
|
||||||
const currentOffset = offset;
|
const currentOffset = offset;
|
||||||
offset += dash + gapSize;
|
offset += dash + gapSize;
|
||||||
|
|
||||||
@@ -169,14 +226,22 @@ export class CircleChart extends LitElement {
|
|||||||
<circle
|
<circle
|
||||||
cx="80" cy="80" r="${radius}"
|
cx="80" cy="80" r="${radius}"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="${color}"
|
stroke="${seg.color}"
|
||||||
stroke-width="16"
|
stroke-width="16"
|
||||||
stroke-dasharray="${dash} ${gap}"
|
stroke-dasharray="${dash} ${gap}"
|
||||||
stroke-dashoffset="${-currentOffset}"
|
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`
|
return html`
|
||||||
<div class="chart-wrapper">
|
<div class="chart-wrapper">
|
||||||
${this.heading
|
${this.heading
|
||||||
@@ -189,17 +254,29 @@ export class CircleChart extends LitElement {
|
|||||||
<span class="center-value">${total}</span>
|
<span class="center-value">${total}</span>
|
||||||
<span class="center-text">${this.centerText}</span>
|
<span class="center-text">${this.centerText}</span>
|
||||||
</div>
|
</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>
|
||||||
<div class="legend">
|
<div class="legend">
|
||||||
${this.segments.map(
|
${this.segments.map(
|
||||||
(seg, i) => html`
|
(seg) => html`
|
||||||
<div class="legend-item">
|
<div class="legend-item">
|
||||||
<span
|
<span
|
||||||
class="legend-dot"
|
class="legend-dot"
|
||||||
style="background: ${seg.color ||
|
style="background:${seg.color}"
|
||||||
this.defaultColors[
|
|
||||||
i % this.defaultColors.length
|
|
||||||
]}"
|
|
||||||
></span>
|
></span>
|
||||||
<span class="legend-label">${seg.label}</span>
|
<span class="legend-label">${seg.label}</span>
|
||||||
<span class="legend-value">${seg.value}</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-label">Example</div>
|
||||||
<div class="component-preview col">
|
<div class="component-preview col">
|
||||||
<circle-chart
|
<circle-chart
|
||||||
heading="Pilots by Category"
|
heading="Pilots by Category"
|
||||||
centerText="Pilots"
|
centerText="Pilots"
|
||||||
.segments=${[
|
.segments=${[
|
||||||
{ label: 'Sport', value: 124 },
|
{ label: 'Sport', value: 124, color: 'var(--color-accent)' },
|
||||||
{ label: 'Serial', value: 89 },
|
{ label: 'Serial', value: 89, color: '#30a46c' },
|
||||||
{ label: 'Open', value: 47 },
|
{ label: 'Open', value: 47, color: '#e79d13' },
|
||||||
{ label: 'Tandem', value: 18 },
|
{ label: 'Tandem', value: 18, color: '#e5484d' },
|
||||||
]}
|
]}
|
||||||
></circle-chart>
|
></circle-chart>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user