Files
FlightScore-Frontend/src/pages/dev-page.ts
T
2026-04-12 16:27:52 +02:00

631 lines
20 KiB
TypeScript

import { LitElement, html, css } from "lit";
import { customElement, state } from "lit/decorators.js";
import "../components/ui-button";
import "../components/ui-button-secondary";
import "../components/ui-badge";
import "../components/ui-card";
import "../components/card-backdrop";
import "../components/card-header";
import "../components/auth-card";
import "../components/stat-card";
import "../components/icon-card";
import "../components/form-input";
import "../components/horizontal-divider";
import "../components/notify-bar";
import "../components/ui-link";
import "../components/circle-chart";
import "../components/loading-bar";
import "../components/line-chart";
import { Icons } from "../components/icons";
@customElement("dev-page")
export class DevPage extends LitElement {
static styles = css`
:host {
flex: 1;
display: flex;
flex-direction: column;
background: var(--color-bg);
}
.container {
max-width: 1100px;
width: 100%;
margin: 0 auto;
padding: 2.5rem 1.5rem 4rem;
display: flex;
flex-direction: column;
gap: 3rem;
}
.page-header {
display: flex;
flex-direction: column;
gap: 0.35rem;
padding-bottom: 1.5rem;
border-bottom: 1px solid var(--color-border);
}
.page-header h1 {
margin: 0;
font-size: 2rem;
font-weight: 800;
letter-spacing: -0.03em;
color: var(--color-text);
}
.page-header p {
margin: 0;
font-size: 0.95rem;
color: color-mix(in srgb, var(--color-text) 55%, transparent);
}
.section {
display: flex;
flex-direction: column;
gap: 1.25rem;
}
.section-head {
display: flex;
flex-direction: column;
gap: 0.2rem;
}
.section-head h2 {
margin: 0;
font-size: 1.25rem;
font-weight: 700;
letter-spacing: -0.01em;
color: var(--color-text);
}
.section-head p {
margin: 0;
font-size: 0.85rem;
color: color-mix(in srgb, var(--color-text) 50%, transparent);
}
.component-group {
background: var(--color-bg-nav);
border: 1px solid var(--color-border);
border-radius: 0.75rem;
overflow: hidden;
}
.component-label {
padding: 0.6rem 1.25rem;
font-size: 0.75rem;
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
color: color-mix(in srgb, var(--color-text) 45%, transparent);
background: color-mix(in srgb, var(--color-text) 4%, transparent);
border-bottom: 1px solid var(--color-border);
}
.component-preview {
padding: 1.5rem 1.25rem;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.75rem;
}
.component-preview.col {
flex-direction: column;
align-items: stretch;
}
.component-preview.grid-2 {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
.component-preview.grid-4 {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
}
.component-preview + .component-label {
border-top: 1px solid var(--color-border);
}
.spacer {
width: 1px;
height: 1.5rem;
background: var(--color-border);
margin: 0 0.25rem;
}
.input-row {
max-width: 360px;
width: 100%;
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.card-demo {
display: flex;
justify-content: center;
padding: 2rem;
background:
radial-gradient(
ellipse at 30% 20%,
color-mix(in srgb, var(--color-accent) 8%, transparent) 0%,
transparent 50%
),
var(--color-bg);
border-radius: 0 0 0.75rem 0.75rem;
}
.toggle-row {
display: flex;
align-items: center;
gap: 0.5rem;
}
.toggle-label {
font-size: 0.85rem;
color: color-mix(in srgb, var(--color-text) 60%, transparent);
}
`;
@state() inputValue = "";
@state() notifyVisible = true;
render() {
return html`
<div class="container">
<div class="page-header">
<h1>Component Library</h1>
<p>All available UI components with variants and states</p>
</div>
<div class="section">
<div class="section-head">
<h2>Buttons</h2>
<p>Primary and secondary action buttons</p>
</div>
<div class="component-group">
<div class="component-label">Primary</div>
<div class="component-preview">
<ui-button>Default</ui-button>
<ui-button>
<svg
slot="icon"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<line x1="12" y1="5" x2="12" y2="19" />
<line x1="5" y1="12" x2="19" y2="12" />
</svg>
With Icon
</ui-button>
<ui-button disabled>Disabled</ui-button>
</div>
<div class="component-label">Primary Full Width</div>
<div class="component-preview col">
<ui-button full>Full Width Button</ui-button>
</div>
<div class="component-label">Secondary</div>
<div class="component-preview">
<ui-button-secondary>Default</ui-button-secondary>
<ui-button-secondary>
<svg
slot="icon"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<line x1="19" y1="12" x2="5" y2="12" />
<polyline points="12 19 5 12 12 5" />
</svg>
With Icon
</ui-button-secondary>
<ui-button-secondary disabled> Disabled </ui-button-secondary>
</div>
<div class="component-label">Secondary Full Width</div>
<div class="component-preview col">
<ui-button-secondary full>
Full Width Secondary
</ui-button-secondary>
</div>
</div>
</div>
<div class="section">
<div class="section-head">
<h2>Badges</h2>
<p>Status and category indicators</p>
</div>
<div class="component-group">
<div class="component-label">Variants</div>
<div class="component-preview">
<ui-badge variant="accent">Accent</ui-badge>
<ui-badge variant="success">Success</ui-badge>
<ui-badge variant="warning">Warning</ui-badge>
<ui-badge variant="error">Error</ui-badge>
<ui-badge variant="muted">Muted</ui-badge>
</div>
<div class="component-label">With Icon</div>
<div class="component-preview">
<ui-badge variant="accent">
<span slot="icon">${Icons.icons.stack}</span>
Platform
</ui-badge>
<ui-badge variant="success">
<span slot="icon">${Icons.icons.success}</span>
Live
</ui-badge>
<ui-badge variant="error">
<span slot="icon">${Icons.icons.error}</span>
Offline
</ui-badge>
</div>
</div>
</div>
<div class="section">
<div class="section-head">
<h2>Notification Bars</h2>
<p>Inline feedback messages</p>
</div>
<div class="component-group">
<div class="component-label">Types</div>
<div class="component-preview col">
<notify-bar
type="info"
message="Competition is currently active."
></notify-bar>
<notify-bar
type="success"
message="Competition successfully created."
></notify-bar>
<notify-bar
type="warning"
message="GPS data incomplete. Some scores may be inaccurate."
></notify-bar>
<notify-bar
type="error"
message="Login failed. Please check your credentials."
></notify-bar>
</div>
<div class="component-label">Non-Dismissible</div>
<div class="component-preview col">
<notify-bar
type="success"
message="This notification cannot be dismissed."
.dismissible=${false}
></notify-bar>
</div>
</div>
</div>
<div class="section">
<div class="section-head">
<h2>Links</h2>
<p>Navigation links with underline animation</p>
</div>
<div class="component-group">
<div class="component-label">States</div>
<div class="component-preview">
<ui-link href="#">Default Link</ui-link>
<div class="spacer"></div>
<ui-link href="#" active>Active Link</ui-link>
</div>
</div>
</div>
<div class="section">
<div class="section-head">
<h2>Form Inputs</h2>
<p>Text input fields with labels</p>
</div>
<div class="component-group">
<div class="component-label">Types</div>
<div class="component-preview col">
<div class="input-row">
<form-input
label="Text"
placeholder="Enter some text"
.value=${this.inputValue}
@value-changed=${(e: CustomEvent) =>
(this.inputValue = e.detail.value)}
></form-input>
<form-input
label="Email"
type="email"
placeholder="you@example.com"
></form-input>
<form-input
label="Password"
type="password"
placeholder="Enter your password"
></form-input>
</div>
</div>
<div class="component-label">Live Value</div>
<div class="component-preview">
<span
style="font-size:0.85rem;color:color-mix(in srgb,var(--color-text) 60%,transparent)"
>
Current value: "${this.inputValue}"
</span>
</div>
</div>
</div>
<div class="section">
<div class="section-head">
<h2>Stat Cards</h2>
<p>Metric display cards</p>
</div>
<div class="component-group">
<div class="component-label">Grid</div>
<div class="component-preview grid-4">
<stat-card value="42" label="Competitions"></stat-card>
<stat-card value="318" label="Pilots"></stat-card>
<stat-card value="1,240" label="Tasks"></stat-card>
<stat-card value="12" label="Countries"></stat-card>
</div>
</div>
</div>
<div class="section">
<div class="section-head">
<h2>Icon Cards</h2>
<p>Feature highlight cards</p>
</div>
<div class="component-group">
<div class="component-label">Grid</div>
<div class="component-preview grid-2">
<icon-card
heading="Real-Time Scoring"
description="Scores update live as judges submit results."
>
${Icons.icons.clock}
</icon-card>
<icon-card
heading="GPS Integration"
description="Import GPS tracks and calculate distances automatically."
>
${Icons.icons.map_pin}
</icon-card>
</div>
</div>
</div>
<div class="section">
<div class="section-head">
<h2>Cards</h2>
<p>Container cards and layout primitives</p>
</div>
<div class="component-group">
<div class="component-label">ui-card (default)</div>
<div class="card-demo">
<ui-card>
<card-header
heading="Card Title"
subheading="A subtitle goes here"
></card-header>
<p
style="margin:0;font-size:0.9rem;color:color-mix(in srgb,var(--color-text) 60%,transparent)"
>
This is the default card layout with a header component
inside.
</p>
<horizontal-divider></horizontal-divider>
<ui-button full>Action</ui-button>
</ui-card>
</div>
<div class="component-label">ui-card (centered)</div>
<div class="card-demo">
<ui-card centered>
<p
style="margin:0;font-size:3rem;font-weight:800;letter-spacing:-0.03em;color:var(--color-accent)"
>
404
</p>
<card-header
heading="Centered Content"
subheading="Used for error pages and similar layouts"
></card-header>
<horizontal-divider></horizontal-divider>
<ui-button>Action</ui-button>
</ui-card>
</div>
</div>
</div>
<div class="section">
<div class="section-head">
<h2>Divider</h2>
<p>Visual separator for content sections</p>
</div>
<div class="component-group">
<div class="component-label">horizontal-divider</div>
<div class="component-preview col">
<p
style="margin:0;font-size:0.85rem;color:color-mix(in srgb,var(--color-text) 60%,transparent)"
>
Content above
</p>
<horizontal-divider></horizontal-divider>
<p
style="margin:0;font-size:0.85rem;color:color-mix(in srgb,var(--color-text) 60%,transparent)"
>
Content below
</p>
</div>
</div>
</div>
<div class="section">
<div class="section-head">
<h2>Composed: Auth Card</h2>
<p>Pre-composed card for authentication flows</p>
</div>
<div class="component-group">
<div class="component-label">Login Example</div>
<div class="card-demo">
<ui-card>
<card-header
heading="Welcome back"
subheading="Sign in to your FlightScore account"
></card-header>
<form-input
label="Email"
type="email"
placeholder="you@example.com"
></form-input>
<form-input
label="Password"
type="password"
placeholder="Enter your password"
></form-input>
<notify-bar
type="error"
message="Invalid email or password."
></notify-bar>
<ui-button full>Sign in</ui-button>
<horizontal-divider></horizontal-divider>
<p
style="margin:0;text-align:center;font-size:0.875rem;color:color-mix(in srgb,var(--color-text) 60%,transparent)"
>
No account?
<ui-link href="#">Create one</ui-link>
</p>
</ui-card>
</div>
</div>
</div>
<div class="section">
<div class="section-head">
<h2>Loading Bars</h2>
<p>Progress indicators with variants and states</p>
</div>
<div class="component-group">
<div class="component-label">Variants</div>
<div class="component-preview col">
<loading-bar label="Accent" value="72"></loading-bar>
<loading-bar
label="Success"
value="100"
variant="success"
></loading-bar>
<loading-bar
label="Warning"
value="45"
variant="warning"
></loading-bar>
<loading-bar
label="Error"
value="18"
variant="error"
></loading-bar>
</div>
<div class="component-label">Sizes</div>
<div class="component-preview col">
<loading-bar label="Small" value="60" size="sm"></loading-bar>
<loading-bar label="Medium" value="60" size="md"></loading-bar>
<loading-bar label="Large" value="60" size="lg"></loading-bar>
</div>
<div class="component-label">Indeterminate</div>
<div class="component-preview col">
<loading-bar
label="Loading…"
indeterminate
hideValue
></loading-bar>
</div>
</div>
</div>
<div class="section">
<div class="section-head">
<h2>Line Chart</h2>
<p>Trend lines with optional area fill</p>
</div>
<div class="component-group">
<div class="component-label">Multi-Series</div>
<div class="component-preview col">
<line-chart
heading="Score Progression"
subtitle="Last 6 Rounds"
xLabel="Round"
yLabel="Score"
showArea
.series=${[
{
label: "Pilot A",
color: "var(--color-accent)",
points: [
{ x: 0, y: 20 },
{ x: 1, y: 420 },
{ x: 2, y: 580 },
{ x: 3, y: 540 },
{ x: 4, y: 710 },
{ x: 5, y: 690 },
{ x: 6, y: 820 },
],
},
{
label: "Pilot B",
color: "#30a46c",
points: [
{ x: 1, y: 380 },
{ x: 2, y: 450 },
{ x: 3, y: 620 },
{ x: 4, y: 590 },
{ x: 5, y: 750 },
{ x: 6, y: 780 },
{ x: 7, y: 780 },
{ x: 8, y: 780 },
{ x: 9, y: 780 },
{ x: 10, y: 780 },
{ x: 11, y: 780 },
{ x: 12, y: 780 },
],
},
]}
></line-chart>
</div>
</div>
</div>
<div class="section">
<div class="section-head">
<h2>Circle Chart</h2>
<p>Donut chart for proportional data</p>
</div>
<div class="component-group">
<div class="component-label">Example</div>
<div class="component-preview col">
<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>
</div>
`;
}
}