mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2025-09-01 08:12:32 +00:00
toggle additional data on elevation profile
This commit is contained in:
@@ -397,6 +397,22 @@ export class TrackPoint {
|
|||||||
getCoordinates(): Coordinates {
|
getCoordinates(): Coordinates {
|
||||||
return this.attributes;
|
return this.attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getHeartRate(): number {
|
||||||
|
return this.extensions && this.extensions['gpxtpx:TrackPointExtension'] && this.extensions['gpxtpx:TrackPointExtension']['gpxtpx:hr'] ? this.extensions['gpxtpx:TrackPointExtension']['gpxtpx:hr'] : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCadence(): number {
|
||||||
|
return this.extensions && this.extensions['gpxtpx:TrackPointExtension'] && this.extensions['gpxtpx:TrackPointExtension']['gpxtpx:cad'] ? this.extensions['gpxtpx:TrackPointExtension']['gpxtpx:cad'] : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTemperature(): number {
|
||||||
|
return this.extensions && this.extensions['gpxtpx:TrackPointExtension'] && this.extensions['gpxtpx:TrackPointExtension']['gpxtpx:atemp'] ? this.extensions['gpxtpx:TrackPointExtension']['gpxtpx:atemp'] : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
getPower(): number {
|
||||||
|
return this.extensions && this.extensions["gpxpx:PowerExtension"] && this.extensions["gpxpx:PowerExtension"]["gpxpx:PowerInWatts"] ? this.extensions["gpxpx:PowerExtension"]["gpxpx:PowerInWatts"] : undefined;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Waypoint {
|
export class Waypoint {
|
||||||
|
8
website/package-lock.json
generated
8
website/package-lock.json
generated
@@ -9,7 +9,7 @@
|
|||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mapbox/mapbox-gl-geocoder": "^5.0.2",
|
"@mapbox/mapbox-gl-geocoder": "^5.0.2",
|
||||||
"bits-ui": "^0.21.3",
|
"bits-ui": "^0.21.4",
|
||||||
"chart.js": "^4.4.2",
|
"chart.js": "^4.4.2",
|
||||||
"clsx": "^2.1.0",
|
"clsx": "^2.1.0",
|
||||||
"gpx": "file:../gpx",
|
"gpx": "file:../gpx",
|
||||||
@@ -1828,9 +1828,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/bits-ui": {
|
"node_modules/bits-ui": {
|
||||||
"version": "0.21.3",
|
"version": "0.21.4",
|
||||||
"resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-0.21.3.tgz",
|
"resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-0.21.4.tgz",
|
||||||
"integrity": "sha512-VMQVXwYIjYmDoudIRm2ZlS2guy97lUQk73DwSfTnaS0dhldImbDFMATNxjLSLsTDj8FqJ8Dv78wSctdxcloIbQ==",
|
"integrity": "sha512-IL+7s19GW561jwkeYk23dwkTfQ9606I062qqv2AtjCdhhIdoOEJNVBX0kjP5xefSaS6ojL0HGG54att0aRTcAQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@internationalized/date": "^3.5.1",
|
"@internationalized/date": "^3.5.1",
|
||||||
"@melt-ui/svelte": "0.76.2",
|
"@melt-ui/svelte": "0.76.2",
|
||||||
|
@@ -42,7 +42,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mapbox/mapbox-gl-geocoder": "^5.0.2",
|
"@mapbox/mapbox-gl-geocoder": "^5.0.2",
|
||||||
"bits-ui": "^0.21.3",
|
"bits-ui": "^0.21.4",
|
||||||
"chart.js": "^4.4.2",
|
"chart.js": "^4.4.2",
|
||||||
"clsx": "^2.1.0",
|
"clsx": "^2.1.0",
|
||||||
"gpx": "file:../gpx",
|
"gpx": "file:../gpx",
|
||||||
|
@@ -1,88 +1,127 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import * as ToggleGroup from '$lib/components/ui/toggle-group';
|
||||||
|
import Tooltip from '$lib/components/Tooltip.svelte';
|
||||||
|
import { Separator } from '$lib/components/ui/separator';
|
||||||
|
|
||||||
import Chart from 'chart.js/auto';
|
import Chart from 'chart.js/auto';
|
||||||
|
|
||||||
import { selectedFiles } from '$lib/stores';
|
import { selectedFiles } from '$lib/stores';
|
||||||
|
|
||||||
import { onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
|
import {
|
||||||
|
BrickWall,
|
||||||
|
TriangleRight,
|
||||||
|
HeartPulse,
|
||||||
|
Orbit,
|
||||||
|
SquareActivity,
|
||||||
|
Thermometer,
|
||||||
|
Zap
|
||||||
|
} from 'lucide-svelte';
|
||||||
|
|
||||||
let canvas: HTMLCanvasElement;
|
let canvas: HTMLCanvasElement;
|
||||||
let chart: Chart;
|
let chart: Chart;
|
||||||
|
|
||||||
Chart.defaults.font.family =
|
Chart.defaults.font.family =
|
||||||
'ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"'; // Tailwind CSS font
|
'ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"'; // Tailwind CSS font
|
||||||
|
|
||||||
|
let elevationFill: string;
|
||||||
|
let additionalDatasets: string[];
|
||||||
|
|
||||||
|
let options = {
|
||||||
|
animation: false,
|
||||||
|
parsing: false,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
type: 'linear',
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Distance (km)',
|
||||||
|
padding: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
type: 'linear',
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Elevation (m)',
|
||||||
|
padding: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
datasets: {
|
||||||
|
line: {
|
||||||
|
pointRadius: 0,
|
||||||
|
tension: 0.4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
interaction: {
|
||||||
|
mode: 'nearest',
|
||||||
|
axis: 'x',
|
||||||
|
intersect: false
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: false
|
||||||
|
},
|
||||||
|
decimation: {
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
stacked: false
|
||||||
|
};
|
||||||
|
|
||||||
|
let datasets = {
|
||||||
|
speed: {
|
||||||
|
id: 'speed',
|
||||||
|
label: 'Speed',
|
||||||
|
units: 'km/h'
|
||||||
|
},
|
||||||
|
hr: {
|
||||||
|
id: 'hr',
|
||||||
|
label: 'Heart Rate',
|
||||||
|
units: 'bpm'
|
||||||
|
},
|
||||||
|
cad: {
|
||||||
|
id: 'cad',
|
||||||
|
label: 'Cadence',
|
||||||
|
units: 'rpm'
|
||||||
|
},
|
||||||
|
atemp: {
|
||||||
|
id: 'atemp',
|
||||||
|
label: 'Temperature',
|
||||||
|
units: '°C'
|
||||||
|
},
|
||||||
|
power: {
|
||||||
|
id: 'power',
|
||||||
|
label: 'Power',
|
||||||
|
units: 'W'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let [id, dataset] of Object.entries(datasets)) {
|
||||||
|
options.scales[`y${id}`] = {
|
||||||
|
type: 'linear',
|
||||||
|
position: 'right',
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: dataset.label + ' (' + dataset.units + ')',
|
||||||
|
padding: 0
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
display: false
|
||||||
|
},
|
||||||
|
display: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
chart = new Chart(canvas, {
|
chart = new Chart(canvas, {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: {
|
data: {
|
||||||
datasets: []
|
datasets: []
|
||||||
},
|
},
|
||||||
options: {
|
options
|
||||||
animation: false,
|
|
||||||
parsing: false,
|
|
||||||
maintainAspectRatio: false,
|
|
||||||
scales: {
|
|
||||||
x: {
|
|
||||||
type: 'linear',
|
|
||||||
title: {
|
|
||||||
display: true,
|
|
||||||
text: 'Distance (km)',
|
|
||||||
padding: 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
type: 'linear',
|
|
||||||
title: {
|
|
||||||
display: true,
|
|
||||||
text: 'Elevation (m)',
|
|
||||||
padding: 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
y1: {
|
|
||||||
type: 'linear',
|
|
||||||
position: 'right',
|
|
||||||
title: {
|
|
||||||
display: true,
|
|
||||||
text: 'Speed (km/h)',
|
|
||||||
padding: 0
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
display: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
y2: {
|
|
||||||
type: 'linear',
|
|
||||||
position: 'right',
|
|
||||||
title: {
|
|
||||||
display: true,
|
|
||||||
text: 'Slope (%)',
|
|
||||||
padding: 0
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
display: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
datasets: {
|
|
||||||
line: {
|
|
||||||
pointRadius: 0,
|
|
||||||
tension: 0.4
|
|
||||||
}
|
|
||||||
},
|
|
||||||
interaction: {
|
|
||||||
mode: 'nearest',
|
|
||||||
axis: 'x',
|
|
||||||
intersect: false
|
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
legend: {
|
|
||||||
display: false
|
|
||||||
},
|
|
||||||
decimation: {
|
|
||||||
enabled: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
stacked: false
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -102,7 +141,7 @@
|
|||||||
fill: true
|
fill: true
|
||||||
};
|
};
|
||||||
chart.data.datasets[1] = {
|
chart.data.datasets[1] = {
|
||||||
label: 'Speed',
|
label: datasets.speed.label,
|
||||||
data: trackPointsAndStatistics.points.map((point, index) => {
|
data: trackPointsAndStatistics.points.map((point, index) => {
|
||||||
return {
|
return {
|
||||||
x: trackPointsAndStatistics.statistics.distance[index],
|
x: trackPointsAndStatistics.statistics.distance[index],
|
||||||
@@ -110,18 +149,56 @@
|
|||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
normalized: true,
|
normalized: true,
|
||||||
yAxisID: 'y1'
|
yAxisID: `y${datasets.speed.id}`,
|
||||||
|
hidden: true
|
||||||
};
|
};
|
||||||
chart.data.datasets[2] = {
|
chart.data.datasets[2] = {
|
||||||
label: 'Slope',
|
label: datasets.hr.label,
|
||||||
data: trackPointsAndStatistics.points.map((point, index) => {
|
data: trackPointsAndStatistics.points.map((point, index) => {
|
||||||
return {
|
return {
|
||||||
x: trackPointsAndStatistics.statistics.distance[index],
|
x: trackPointsAndStatistics.statistics.distance[index],
|
||||||
y: trackPointsAndStatistics.statistics.slope[index]
|
y: point.getHeartRate()
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
normalized: true,
|
normalized: true,
|
||||||
yAxisID: 'y2'
|
yAxisID: `y${datasets.hr.id}`,
|
||||||
|
hidden: true
|
||||||
|
};
|
||||||
|
chart.data.datasets[3] = {
|
||||||
|
label: datasets.cad.label,
|
||||||
|
data: trackPointsAndStatistics.points.map((point, index) => {
|
||||||
|
return {
|
||||||
|
x: trackPointsAndStatistics.statistics.distance[index],
|
||||||
|
y: point.getCadence()
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
normalized: true,
|
||||||
|
yAxisID: `y${datasets.cad.id}`,
|
||||||
|
hidden: true
|
||||||
|
};
|
||||||
|
chart.data.datasets[4] = {
|
||||||
|
label: datasets.atemp.label,
|
||||||
|
data: trackPointsAndStatistics.points.map((point, index) => {
|
||||||
|
return {
|
||||||
|
x: trackPointsAndStatistics.statistics.distance[index],
|
||||||
|
y: point.getTemperature()
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
normalized: true,
|
||||||
|
yAxisID: `y${datasets.atemp.id}`,
|
||||||
|
hidden: true
|
||||||
|
};
|
||||||
|
chart.data.datasets[5] = {
|
||||||
|
label: datasets.power.label,
|
||||||
|
data: trackPointsAndStatistics.points.map((point, index) => {
|
||||||
|
return {
|
||||||
|
x: trackPointsAndStatistics.statistics.distance[index],
|
||||||
|
y: point.getPower()
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
normalized: true,
|
||||||
|
yAxisID: `y${datasets.power.id}`,
|
||||||
|
hidden: true
|
||||||
};
|
};
|
||||||
chart.options.scales.x['min'] = 0;
|
chart.options.scales.x['min'] = 0;
|
||||||
chart.options.scales.x['max'] = file.statistics.distance.total;
|
chart.options.scales.x['max'] = file.statistics.distance.total;
|
||||||
@@ -129,8 +206,87 @@
|
|||||||
chart.update();
|
chart.update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: console.log(elevationFill);
|
||||||
|
$: if (additionalDatasets && chart) {
|
||||||
|
let includeSpeed = additionalDatasets.includes('speed');
|
||||||
|
let includeHeartRate = additionalDatasets.includes('hr');
|
||||||
|
let includeCadence = additionalDatasets.includes('cad');
|
||||||
|
let includeTemperature = additionalDatasets.includes('atemp');
|
||||||
|
let includePower = additionalDatasets.includes('power');
|
||||||
|
if (chart.data.datasets.length > 0) {
|
||||||
|
chart.data.datasets[1].hidden = !includeSpeed;
|
||||||
|
chart.data.datasets[2].hidden = !includeHeartRate;
|
||||||
|
chart.data.datasets[3].hidden = !includeCadence;
|
||||||
|
chart.data.datasets[4].hidden = !includeTemperature;
|
||||||
|
chart.data.datasets[5].hidden = !includePower;
|
||||||
|
}
|
||||||
|
chart.options.scales[`y${datasets.speed.id}`].display = includeSpeed;
|
||||||
|
chart.options.scales[`y${datasets.hr.id}`].display = includeHeartRate;
|
||||||
|
chart.options.scales[`y${datasets.cad.id}`].display = includeCadence;
|
||||||
|
chart.options.scales[`y${datasets.atemp.id}`].display = includeTemperature;
|
||||||
|
chart.options.scales[`y${datasets.power.id}`].display = includePower;
|
||||||
|
chart.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
if (chart) {
|
||||||
|
chart.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="h-full grow min-w-0 py-4">
|
<div class="h-full grow min-w-0 flex flex-row items-center">
|
||||||
<canvas bind:this={canvas}> </canvas>
|
<div class="h-full grow min-w-0 py-4">
|
||||||
|
<canvas bind:this={canvas}> </canvas>
|
||||||
|
</div>
|
||||||
|
<div class="h-fit flex flex-col m-2 border rounded">
|
||||||
|
<ToggleGroup.Root class="flex-col gap-0" type="single" bind:value={elevationFill}>
|
||||||
|
<ToggleGroup.Item class="p-0 w-8 h-8" value="slope">
|
||||||
|
<Tooltip side="left">
|
||||||
|
<TriangleRight slot="data" size="16" />
|
||||||
|
<span slot="tooltip">Show slope</span>
|
||||||
|
</Tooltip>
|
||||||
|
</ToggleGroup.Item>
|
||||||
|
<ToggleGroup.Item class="p-0 w-8 h-8" value="surface">
|
||||||
|
<Tooltip side="left">
|
||||||
|
<BrickWall slot="data" size="16" />
|
||||||
|
<span slot="tooltip">Show surface</span>
|
||||||
|
</Tooltip>
|
||||||
|
</ToggleGroup.Item>
|
||||||
|
</ToggleGroup.Root>
|
||||||
|
<Separator />
|
||||||
|
<ToggleGroup.Root class="flex-col gap-0" type="multiple" bind:value={additionalDatasets}>
|
||||||
|
<ToggleGroup.Item class="p-0 w-8 h-8" value="speed">
|
||||||
|
<Tooltip side="left">
|
||||||
|
<Zap slot="data" size="16" />
|
||||||
|
<span slot="tooltip">Show speed</span>
|
||||||
|
</Tooltip>
|
||||||
|
</ToggleGroup.Item>
|
||||||
|
<ToggleGroup.Item class="p-0 w-8 h-8" value="hr">
|
||||||
|
<Tooltip side="left">
|
||||||
|
<HeartPulse slot="data" size="16" />
|
||||||
|
<span slot="tooltip">Show heart rate</span>
|
||||||
|
</Tooltip>
|
||||||
|
</ToggleGroup.Item>
|
||||||
|
<ToggleGroup.Item class="p-0 w-8 h-8" value="cad">
|
||||||
|
<Tooltip side="left">
|
||||||
|
<Orbit slot="data" size="16" />
|
||||||
|
<span slot="tooltip">Show cadence</span>
|
||||||
|
</Tooltip>
|
||||||
|
</ToggleGroup.Item>
|
||||||
|
<ToggleGroup.Item class="p-0 w-8 h-8" value="atemp">
|
||||||
|
<Tooltip side="left">
|
||||||
|
<Thermometer slot="data" size="16" />
|
||||||
|
<span slot="tooltip">Show temperature</span>
|
||||||
|
</Tooltip>
|
||||||
|
</ToggleGroup.Item>
|
||||||
|
<ToggleGroup.Item class="p-0 w-8 h-8" value="power">
|
||||||
|
<Tooltip side="left">
|
||||||
|
<SquareActivity slot="data" size="16" />
|
||||||
|
<span slot="tooltip">Show power</span>
|
||||||
|
</Tooltip>
|
||||||
|
</ToggleGroup.Item>
|
||||||
|
</ToggleGroup.Root>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import * as Card from '$lib/components/ui/card';
|
import * as Card from '$lib/components/ui/card';
|
||||||
import GPXDataItem from '$lib/components/GPXDataItem.svelte';
|
import Tooltip from '$lib/components/Tooltip.svelte';
|
||||||
|
|
||||||
import { GPXStatistics } from 'gpx';
|
import { GPXStatistics } from 'gpx';
|
||||||
|
|
||||||
@@ -30,14 +30,14 @@
|
|||||||
|
|
||||||
<Card.Root class="h-full overflow-hidden border-none">
|
<Card.Root class="h-full overflow-hidden border-none">
|
||||||
<Card.Content class="h-full flex flex-col flex-wrap gap-4 justify-center">
|
<Card.Content class="h-full flex flex-col flex-wrap gap-4 justify-center">
|
||||||
<GPXDataItem>
|
<Tooltip>
|
||||||
<span slot="data" class="flex flex-row items-center">
|
<span slot="data" class="flex flex-row items-center">
|
||||||
<Ruler size="18" class="mr-1" />
|
<Ruler size="18" class="mr-1" />
|
||||||
{gpxData.distance.total.toFixed(2)} km
|
{gpxData.distance.total.toFixed(2)} km
|
||||||
</span>
|
</span>
|
||||||
<span slot="tooltip">Distance</span>
|
<span slot="tooltip">Distance</span>
|
||||||
</GPXDataItem>
|
</Tooltip>
|
||||||
<GPXDataItem>
|
<Tooltip>
|
||||||
<span slot="data" class="flex flex-row items-center">
|
<span slot="data" class="flex flex-row items-center">
|
||||||
<MoveUpRight size="18" class="mr-1" />
|
<MoveUpRight size="18" class="mr-1" />
|
||||||
{gpxData.elevation.gain.toFixed(0)} m
|
{gpxData.elevation.gain.toFixed(0)} m
|
||||||
@@ -45,20 +45,20 @@
|
|||||||
{gpxData.elevation.loss.toFixed(0)} m
|
{gpxData.elevation.loss.toFixed(0)} m
|
||||||
</span>
|
</span>
|
||||||
<span slot="tooltip">Elevation</span>
|
<span slot="tooltip">Elevation</span>
|
||||||
</GPXDataItem>
|
</Tooltip>
|
||||||
<GPXDataItem>
|
<Tooltip>
|
||||||
<span slot="data" class="flex flex-row items-center">
|
<span slot="data" class="flex flex-row items-center">
|
||||||
<Zap size="18" class="mr-1" />
|
<Zap size="18" class="mr-1" />
|
||||||
{gpxData.speed.moving.toFixed(2)} km/h
|
{gpxData.speed.moving.toFixed(2)} km/h
|
||||||
</span>
|
</span>
|
||||||
<span slot="tooltip">Time</span>
|
<span slot="tooltip">Time</span>
|
||||||
</GPXDataItem>
|
</Tooltip>
|
||||||
<GPXDataItem>
|
<Tooltip>
|
||||||
<span slot="data" class="flex flex-row items-center">
|
<span slot="data" class="flex flex-row items-center">
|
||||||
<Timer size="18" class="mr-1" />
|
<Timer size="18" class="mr-1" />
|
||||||
{toHHMMSS(gpxData.time.moving)} / {toHHMMSS(gpxData.time.total)}
|
{toHHMMSS(gpxData.time.moving)} / {toHHMMSS(gpxData.time.total)}
|
||||||
</span>
|
</span>
|
||||||
<span slot="tooltip">Moving time / Total time</span>
|
<span slot="tooltip">Moving time / Total time</span>
|
||||||
</GPXDataItem>
|
</Tooltip>
|
||||||
</Card.Content>
|
</Card.Content>
|
||||||
</Card.Root>
|
</Card.Root>
|
||||||
|
@@ -11,8 +11,6 @@
|
|||||||
Trash2,
|
Trash2,
|
||||||
HeartHandshake,
|
HeartHandshake,
|
||||||
Upload,
|
Upload,
|
||||||
CloudDownload,
|
|
||||||
CloudUpload,
|
|
||||||
Cloud
|
Cloud
|
||||||
} from 'lucide-svelte';
|
} from 'lucide-svelte';
|
||||||
|
|
||||||
|
@@ -1,12 +1,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import * as Tooltip from '$lib/components/ui/tooltip/index.js';
|
import * as Tooltip from '$lib/components/ui/tooltip/index.js';
|
||||||
|
|
||||||
|
export let side: 'top' | 'right' | 'bottom' | 'left' = 'top';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Tooltip.Root>
|
<Tooltip.Root>
|
||||||
<Tooltip.Trigger>
|
<Tooltip.Trigger>
|
||||||
<slot name="data" />
|
<slot name="data" />
|
||||||
</Tooltip.Trigger>
|
</Tooltip.Trigger>
|
||||||
<Tooltip.Content side="top">
|
<Tooltip.Content {side}>
|
||||||
<slot name="tooltip" />
|
<slot name="tooltip" />
|
||||||
</Tooltip.Content>
|
</Tooltip.Content>
|
||||||
</Tooltip.Root>
|
</Tooltip.Root>
|
23
website/src/lib/components/ui/toggle-group/index.ts
Normal file
23
website/src/lib/components/ui/toggle-group/index.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import type { VariantProps } from "tailwind-variants";
|
||||||
|
import { getContext, setContext } from "svelte";
|
||||||
|
import Root from "./toggle-group.svelte";
|
||||||
|
import Item from "./toggle-group-item.svelte";
|
||||||
|
import type { toggleVariants } from "$lib/components/ui/toggle/index.js";
|
||||||
|
|
||||||
|
export type ToggleVariants = VariantProps<typeof toggleVariants>;
|
||||||
|
|
||||||
|
export function setToggleGroupCtx(props: ToggleVariants) {
|
||||||
|
setContext("toggleGroup", props);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getToggleGroupCtx() {
|
||||||
|
return getContext<ToggleVariants>("toggleGroup");
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
Item,
|
||||||
|
//
|
||||||
|
Root as ToggleGroup,
|
||||||
|
Item as ToggleGroupItem,
|
||||||
|
};
|
@@ -0,0 +1,31 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { ToggleGroup as ToggleGroupPrimitive } from "bits-ui";
|
||||||
|
import { type ToggleVariants, getToggleGroupCtx } from "./index.js";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
import { toggleVariants } from "$lib/components/ui/toggle/index.js";
|
||||||
|
|
||||||
|
type $$Props = ToggleGroupPrimitive.ItemProps & ToggleVariants;
|
||||||
|
|
||||||
|
let className: string | undefined | null = undefined;
|
||||||
|
|
||||||
|
export { className as class };
|
||||||
|
export let variant: $$Props["variant"] = "default";
|
||||||
|
export let size: $$Props["size"] = "default";
|
||||||
|
export let value: $$Props["value"];
|
||||||
|
|
||||||
|
const ctx = getToggleGroupCtx();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ToggleGroupPrimitive.Item
|
||||||
|
class={cn(
|
||||||
|
toggleVariants({
|
||||||
|
variant: ctx.variant || variant,
|
||||||
|
size: ctx.size || size,
|
||||||
|
}),
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{value}
|
||||||
|
{...$$restProps}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</ToggleGroupPrimitive.Item>
|
@@ -0,0 +1,30 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { VariantProps } from "tailwind-variants";
|
||||||
|
import { ToggleGroup as ToggleGroupPrimitive } from "bits-ui";
|
||||||
|
import { setToggleGroupCtx } from "./index.js";
|
||||||
|
import type { toggleVariants } from "$lib/components/ui/toggle/index.js";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
type T = $$Generic<"single" | "multiple">;
|
||||||
|
type $$Props = ToggleGroupPrimitive.Props<T> & VariantProps<typeof toggleVariants>;
|
||||||
|
|
||||||
|
let className: string | undefined | null = undefined;
|
||||||
|
export { className as class };
|
||||||
|
export let variant: $$Props["variant"] = "default";
|
||||||
|
export let size: $$Props["size"] = "default";
|
||||||
|
export let value: $$Props["value"] = undefined;
|
||||||
|
|
||||||
|
setToggleGroupCtx({
|
||||||
|
variant,
|
||||||
|
size,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ToggleGroupPrimitive.Root
|
||||||
|
class={cn("flex items-center justify-center gap-1", className)}
|
||||||
|
bind:value
|
||||||
|
{...$$restProps}
|
||||||
|
let:builder
|
||||||
|
>
|
||||||
|
<slot {builder} />
|
||||||
|
</ToggleGroupPrimitive.Root>
|
31
website/src/lib/components/ui/toggle/index.ts
Normal file
31
website/src/lib/components/ui/toggle/index.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { type VariantProps, tv } from "tailwind-variants";
|
||||||
|
import Root from "./toggle.svelte";
|
||||||
|
|
||||||
|
export const toggleVariants = tv({
|
||||||
|
base: "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground",
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "bg-transparent",
|
||||||
|
outline:
|
||||||
|
"border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
default: "h-10 px-3",
|
||||||
|
sm: "h-9 px-2.5",
|
||||||
|
lg: "h-11 px-5",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
size: "default",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export type Variant = VariantProps<typeof toggleVariants>["variant"];
|
||||||
|
export type Size = VariantProps<typeof toggleVariants>["size"];
|
||||||
|
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
//
|
||||||
|
Root as Toggle,
|
||||||
|
};
|
26
website/src/lib/components/ui/toggle/toggle.svelte
Normal file
26
website/src/lib/components/ui/toggle/toggle.svelte
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Toggle as TogglePrimitive } from "bits-ui";
|
||||||
|
import { type Size, type Variant, toggleVariants } from "./index.js";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
type $$Props = TogglePrimitive.Props & {
|
||||||
|
variant?: Variant;
|
||||||
|
size?: Size;
|
||||||
|
};
|
||||||
|
type $$Events = TogglePrimitive.Events;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export let variant: $$Props["variant"] = "default";
|
||||||
|
export let size: $$Props["size"] = "default";
|
||||||
|
export let pressed: $$Props["pressed"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<TogglePrimitive.Root
|
||||||
|
bind:pressed
|
||||||
|
class={cn(toggleVariants({ variant, size, className }))}
|
||||||
|
{...$$restProps}
|
||||||
|
on:click
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</TogglePrimitive.Root>
|
Reference in New Issue
Block a user