mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2025-08-31 23:53:25 +00:00
beginning of map layer control
This commit is contained in:
24
website/package-lock.json
generated
24
website/package-lock.json
generated
@@ -11,6 +11,8 @@
|
|||||||
"@fortawesome/free-brands-svg-icons": "^6.5.2",
|
"@fortawesome/free-brands-svg-icons": "^6.5.2",
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.5.2",
|
"@fortawesome/free-solid-svg-icons": "^6.5.2",
|
||||||
"@mapbox/mapbox-gl-geocoder": "^5.0.2",
|
"@mapbox/mapbox-gl-geocoder": "^5.0.2",
|
||||||
|
"@types/mapbox__mapbox-gl-geocoder": "^5.0.0",
|
||||||
|
"@types/mapbox-gl": "^3.1.0",
|
||||||
"bits-ui": "^0.21.2",
|
"bits-ui": "^0.21.2",
|
||||||
"clsx": "^2.1.0",
|
"clsx": "^2.1.0",
|
||||||
"gpx": "file:../gpx",
|
"gpx": "file:../gpx",
|
||||||
@@ -1370,6 +1372,11 @@
|
|||||||
"integrity": "sha512-trOc4AAUThEz9hapPtSd7wf5tiQKvTtu5b371UxXdTuqzIh0ArcRspRP0i0Viu+LXstIQ1z96t1nsPxT9ol01g==",
|
"integrity": "sha512-trOc4AAUThEz9hapPtSd7wf5tiQKvTtu5b371UxXdTuqzIh0ArcRspRP0i0Viu+LXstIQ1z96t1nsPxT9ol01g==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/geojson": {
|
||||||
|
"version": "7946.0.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.14.tgz",
|
||||||
|
"integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg=="
|
||||||
|
},
|
||||||
"node_modules/@types/http-cache-semantics": {
|
"node_modules/@types/http-cache-semantics": {
|
||||||
"version": "4.0.4",
|
"version": "4.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz",
|
||||||
@@ -1389,6 +1396,23 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/mapbox__mapbox-gl-geocoder": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/mapbox__mapbox-gl-geocoder/-/mapbox__mapbox-gl-geocoder-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-eGBWdFiP2QgmwndPyhwK6eBeOfyB8vRscp2C6Acqasx5dH8FvTo/VgXWCrCKFR3zkWek/H4w4/CwmBFOs7OLBA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/geojson": "*",
|
||||||
|
"@types/mapbox-gl": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/mapbox-gl": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/mapbox-gl/-/mapbox-gl-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-hI6cQDjw1bkJw7MC/eHMqq5TWUamLwsujnUUeiIX2KDRjxRNSYMjnHz07+LATz9I9XIsKumOtUz4gRYnZOJ/FA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/geojson": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/minimist": {
|
"node_modules/@types/minimist": {
|
||||||
"version": "1.2.5",
|
"version": "1.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz",
|
||||||
|
@@ -40,6 +40,8 @@
|
|||||||
"@fortawesome/free-brands-svg-icons": "^6.5.2",
|
"@fortawesome/free-brands-svg-icons": "^6.5.2",
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.5.2",
|
"@fortawesome/free-solid-svg-icons": "^6.5.2",
|
||||||
"@mapbox/mapbox-gl-geocoder": "^5.0.2",
|
"@mapbox/mapbox-gl-geocoder": "^5.0.2",
|
||||||
|
"@types/mapbox__mapbox-gl-geocoder": "^5.0.0",
|
||||||
|
"@types/mapbox-gl": "^3.1.0",
|
||||||
"bits-ui": "^0.21.2",
|
"bits-ui": "^0.21.2",
|
||||||
"clsx": "^2.1.0",
|
"clsx": "^2.1.0",
|
||||||
"gpx": "file:../gpx",
|
"gpx": "file:../gpx",
|
||||||
|
85
website/src/lib/assets/layers.ts
Normal file
85
website/src/lib/assets/layers.ts
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import { type AnySourceData, type Style } from 'mapbox-gl';
|
||||||
|
|
||||||
|
export const basemaps: { [key: string]: string | Style; } = {
|
||||||
|
mapboxOutdoors: 'mapbox://styles/mapbox/outdoors-v12',
|
||||||
|
mapboxSatellite: 'mapbox://styles/mapbox/satellite-v9',
|
||||||
|
openStreetMap: {
|
||||||
|
version: 8,
|
||||||
|
sources: {
|
||||||
|
openStreetMap: {
|
||||||
|
type: 'raster',
|
||||||
|
tiles: ['https://a.tile.openstreetmap.org/{z}/{x}/{y}.png', 'https://b.tile.openstreetmap.org/{z}/{x}/{y}.png', 'https://c.tile.openstreetmap.org/{z}/{x}/{y}.png'],
|
||||||
|
tileSize: 256,
|
||||||
|
maxzoom: 18,
|
||||||
|
attribution: 'Map tiles by <a target="_top" rel="noopener" href="https://tile.openstreetmap.org/">OpenStreetMap tile servers</a>, under the <a target="_top" rel="noopener" href="https://operations.osmfoundation.org/policies/tiles/">tile usage policy</a>. Data by <a target="_top" rel="noopener" href="http://openstreetmap.org">OpenStreetMap</a>'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
layers: [{
|
||||||
|
id: 'openStreetMap',
|
||||||
|
type: 'raster',
|
||||||
|
source: 'openStreetMap',
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
openTopoMap: {
|
||||||
|
version: 8,
|
||||||
|
sources: {
|
||||||
|
openTopoMap: {
|
||||||
|
type: 'raster',
|
||||||
|
tiles: ['https://tile.opentopomap.org/{z}/{x}/{y}.png'],
|
||||||
|
tileSize: 256,
|
||||||
|
maxzoom: 17,
|
||||||
|
attribution: '© <a href="https://www.opentopomap.org" target="_blank">OpenTopoMap</a> © <a href="https://www.openstreetmap.org/copyright" target="_blank">OpenStreetMap</a>'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
layers: [{
|
||||||
|
id: 'openTopoMap',
|
||||||
|
type: 'raster',
|
||||||
|
source: 'openTopoMap',
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
openHikingMap: {
|
||||||
|
version: 8,
|
||||||
|
sources: {
|
||||||
|
openHikingMap: {
|
||||||
|
type: 'raster',
|
||||||
|
tiles: ['https://maps.refuges.info/hiking/{z}/{x}/{y}.png'],
|
||||||
|
tileSize: 256,
|
||||||
|
maxzoom: 18,
|
||||||
|
attribution: '© <a href="https://wiki.openstreetmap.org/wiki/Hiking/mri" target="_blank">sly</a> © <a href="https://www.openstreetmap.org/copyright" target="_blank">OpenStreetMap</a>'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
layers: [{
|
||||||
|
id: 'openHikingMap',
|
||||||
|
type: 'raster',
|
||||||
|
source: 'openHikingMap',
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
cyclOSM: {
|
||||||
|
version: 8,
|
||||||
|
sources: {
|
||||||
|
cyclOSM: {
|
||||||
|
type: 'raster',
|
||||||
|
tiles: ['https://a.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png', 'https://b.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png', 'https://c.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png'],
|
||||||
|
tileSize: 256,
|
||||||
|
maxzoom: 17,
|
||||||
|
attribution: '© <a href="https://github.com/cyclosm/cyclosm-cartocss-style/releases" title="CyclOSM - Open Bicycle render">CyclOSM</a> © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
layers: [{
|
||||||
|
id: 'cyclOSM',
|
||||||
|
type: 'raster',
|
||||||
|
source: 'cyclOSM',
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
linz: 'https://basemaps.linz.govt.nz/v1/tiles/topographic/EPSG:3857/style/topographic.json?api=d01fbtg0ar23gctac5m0jgyy2ds'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const overlays: { [key: string]: AnySourceData; } = {
|
||||||
|
cyclOSMlite: {
|
||||||
|
type: 'raster',
|
||||||
|
tiles: ['https://a.tile-cyclosm.openstreetmap.fr/cyclosm-lite/{z}/{x}/{y}.png', 'https://b.tile-cyclosm.openstreetmap.fr/cyclosm-lite/{z}/{x}/{y}.png', 'https://c.tile-cyclosm.openstreetmap.fr/cyclosm-lite/{z}/{x}/{y}.png'],
|
||||||
|
tileSize: 256,
|
||||||
|
maxzoom: 17,
|
||||||
|
attribution: '© <a href="https://github.com/cyclosm/cyclosm-cartocss-style/releases" title="CyclOSM - Open Bicycle render">CyclOSM</a> © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
||||||
|
},
|
||||||
|
};
|
63
website/src/lib/components/LayerControl.svelte
Normal file
63
website/src/lib/components/LayerControl.svelte
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import mapboxgl from 'mapbox-gl';
|
||||||
|
import CustomControl from '$lib/components/custom-control/CustomControl.svelte';
|
||||||
|
|
||||||
|
import Fa from 'svelte-fa';
|
||||||
|
import { faLayerGroup } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
|
import { Label } from '$lib/components/ui/label';
|
||||||
|
import * as RadioGroup from '$lib/components/ui/radio-group';
|
||||||
|
import { Checkbox } from '$lib/components/ui/checkbox';
|
||||||
|
|
||||||
|
import { basemaps, overlays } from '$lib/assets/layers';
|
||||||
|
|
||||||
|
export let map: mapboxgl.Map | null;
|
||||||
|
|
||||||
|
$: if (map) {
|
||||||
|
map?.setStyle(basemaps['mapboxOutdoors']);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<CustomControl {map} class="group">
|
||||||
|
<div class="flex flex-row justify-center items-center w-[29px] h-[29px] group-hover:hidden">
|
||||||
|
<Fa icon={faLayerGroup} size="1.4x" />
|
||||||
|
</div>
|
||||||
|
<div class="hidden group-hover:block p-2">
|
||||||
|
<RadioGroup.Root
|
||||||
|
value="mapboxOutdoors"
|
||||||
|
onValueChange={(id) => {
|
||||||
|
map.setStyle(basemaps[id]);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{#each Object.keys(basemaps) as id}
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<RadioGroup.Item value={id} {id} />
|
||||||
|
<Label for={id}>{id}</Label>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</RadioGroup.Root>
|
||||||
|
<div>
|
||||||
|
{#each Object.keys(overlays) as id}
|
||||||
|
<Checkbox
|
||||||
|
{id}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
console.log('onCheckedChange', map?.isStyleLoaded());
|
||||||
|
if (checked) {
|
||||||
|
if (!map.getSource(id)) {
|
||||||
|
map.addSource(id, overlays[id]);
|
||||||
|
}
|
||||||
|
map.addLayer({
|
||||||
|
id,
|
||||||
|
type: overlays[id].type === 'raster' ? 'raster' : 'line',
|
||||||
|
source: id
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
map.removeLayer(id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Label for={id}>{id}</Label>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CustomControl>
|
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
import MapboxGeocoder from '@mapbox/mapbox-gl-geocoder';
|
import MapboxGeocoder from '@mapbox/mapbox-gl-geocoder';
|
||||||
import '@mapbox/mapbox-gl-geocoder/dist/mapbox-gl-geocoder.css';
|
import '@mapbox/mapbox-gl-geocoder/dist/mapbox-gl-geocoder.css';
|
||||||
|
import LayerControl from './LayerControl.svelte';
|
||||||
|
|
||||||
mapboxgl.accessToken =
|
mapboxgl.accessToken =
|
||||||
'pk.eyJ1IjoiZ3B4c3R1ZGlvIiwiYSI6ImNrdHVoM2pjNTBodmUycG1yZTNwcnJ3MzkifQ.YZnNs9s9oCQPzoXAWs_SLg';
|
'pk.eyJ1IjoiZ3B4c3R1ZGlvIiwiYSI6ImNrdHVoM2pjNTBodmUycG1yZTNwcnJ3MzkifQ.YZnNs9s9oCQPzoXAWs_SLg';
|
||||||
@@ -17,7 +18,7 @@
|
|||||||
onMount(() => {
|
onMount(() => {
|
||||||
map = new mapboxgl.Map({
|
map = new mapboxgl.Map({
|
||||||
container: 'map',
|
container: 'map',
|
||||||
style: 'mapbox://styles/mapbox/outdoors-v12',
|
style: { version: 8, sources: {}, layers: [] },
|
||||||
projection: 'mercator',
|
projection: 'mercator',
|
||||||
hash: true,
|
hash: true,
|
||||||
language: 'auto',
|
language: 'auto',
|
||||||
@@ -57,10 +58,18 @@
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
if (map) {
|
||||||
|
map.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div {...$$restProps}>
|
<div {...$$restProps}>
|
||||||
<div id="map" class="h-full"></div>
|
<div id="map" class="h-full">
|
||||||
|
<LayerControl {map} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="postcss">
|
<style lang="postcss">
|
||||||
|
@@ -0,0 +1,25 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import CustomControl from './CustomControl';
|
||||||
|
import mapboxgl from 'mapbox-gl';
|
||||||
|
|
||||||
|
export let map: mapboxgl.Map | null;
|
||||||
|
export let position: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' = 'top-right';
|
||||||
|
|
||||||
|
let container: HTMLDivElement | null = null;
|
||||||
|
|
||||||
|
$: if (map && container) {
|
||||||
|
if (position.includes('right')) container.classList.add('float-right');
|
||||||
|
else container.classList.add('float-left');
|
||||||
|
container.classList.remove('hidden');
|
||||||
|
let control = new CustomControl(container);
|
||||||
|
map.addControl(control, position);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
bind:this={container}
|
||||||
|
class="{$$props.class ||
|
||||||
|
''} clear-both translate-0 m-[10px] pointer-events-auto bg-background rounded shadow-md hidden"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
20
website/src/lib/components/custom-control/CustomControl.ts
Normal file
20
website/src/lib/components/custom-control/CustomControl.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { type Map, type IControl } from 'mapbox-gl';
|
||||||
|
|
||||||
|
export default class CustomControl implements IControl {
|
||||||
|
_map: Map | undefined;
|
||||||
|
_container: HTMLElement;
|
||||||
|
|
||||||
|
constructor(container: HTMLElement) {
|
||||||
|
this._container = container;
|
||||||
|
}
|
||||||
|
|
||||||
|
onAdd(map: Map): HTMLElement {
|
||||||
|
this._map = map;
|
||||||
|
return this._container;
|
||||||
|
}
|
||||||
|
|
||||||
|
onRemove() {
|
||||||
|
this._container?.parentNode?.removeChild(this._container);
|
||||||
|
this._map = undefined;
|
||||||
|
}
|
||||||
|
}
|
35
website/src/lib/components/ui/checkbox/checkbox.svelte
Normal file
35
website/src/lib/components/ui/checkbox/checkbox.svelte
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Checkbox as CheckboxPrimitive } from "bits-ui";
|
||||||
|
import Check from "lucide-svelte/icons/check";
|
||||||
|
import Minus from "lucide-svelte/icons/minus";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
type $$Props = CheckboxPrimitive.Props;
|
||||||
|
type $$Events = CheckboxPrimitive.Events;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export let checked: $$Props["checked"] = false;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<CheckboxPrimitive.Root
|
||||||
|
class={cn(
|
||||||
|
"peer box-content h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[disabled=true]:cursor-not-allowed data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[disabled=true]:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
bind:checked
|
||||||
|
{...$$restProps}
|
||||||
|
on:click
|
||||||
|
>
|
||||||
|
<CheckboxPrimitive.Indicator
|
||||||
|
class={cn("flex h-4 w-4 items-center justify-center text-current")}
|
||||||
|
let:isChecked
|
||||||
|
let:isIndeterminate
|
||||||
|
>
|
||||||
|
{#if isChecked}
|
||||||
|
<Check class="h-3.5 w-3.5" />
|
||||||
|
{:else if isIndeterminate}
|
||||||
|
<Minus class="h-3.5 w-3.5" />
|
||||||
|
{/if}
|
||||||
|
</CheckboxPrimitive.Indicator>
|
||||||
|
</CheckboxPrimitive.Root>
|
6
website/src/lib/components/ui/checkbox/index.ts
Normal file
6
website/src/lib/components/ui/checkbox/index.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import Root from "./checkbox.svelte";
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
//
|
||||||
|
Root as Checkbox,
|
||||||
|
};
|
7
website/src/lib/components/ui/label/index.ts
Normal file
7
website/src/lib/components/ui/label/index.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import Root from "./label.svelte";
|
||||||
|
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
//
|
||||||
|
Root as Label,
|
||||||
|
};
|
21
website/src/lib/components/ui/label/label.svelte
Normal file
21
website/src/lib/components/ui/label/label.svelte
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Label as LabelPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
type $$Props = LabelPrimitive.Props;
|
||||||
|
type $$Events = LabelPrimitive.Events;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<LabelPrimitive.Root
|
||||||
|
class={cn(
|
||||||
|
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...$$restProps}
|
||||||
|
on:mousedown
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</LabelPrimitive.Root>
|
15
website/src/lib/components/ui/radio-group/index.ts
Normal file
15
website/src/lib/components/ui/radio-group/index.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { RadioGroup as RadioGroupPrimitive } from "bits-ui";
|
||||||
|
|
||||||
|
import Root from "./radio-group.svelte";
|
||||||
|
import Item from "./radio-group-item.svelte";
|
||||||
|
const Input = RadioGroupPrimitive.Input;
|
||||||
|
|
||||||
|
export {
|
||||||
|
Root,
|
||||||
|
Input,
|
||||||
|
Item,
|
||||||
|
//
|
||||||
|
Root as RadioGroup,
|
||||||
|
Input as RadioGroupInput,
|
||||||
|
Item as RadioGroupItem,
|
||||||
|
};
|
@@ -0,0 +1,28 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { RadioGroup as RadioGroupPrimitive } from "bits-ui";
|
||||||
|
import Circle from "lucide-svelte/icons/circle";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
type $$Props = RadioGroupPrimitive.ItemProps;
|
||||||
|
type $$Events = RadioGroupPrimitive.ItemEvents;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export let value: $$Props["value"];
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<RadioGroupPrimitive.Item
|
||||||
|
{value}
|
||||||
|
class={cn(
|
||||||
|
"aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...$$restProps}
|
||||||
|
on:click
|
||||||
|
>
|
||||||
|
<div class="flex items-center justify-center">
|
||||||
|
<RadioGroupPrimitive.ItemIndicator>
|
||||||
|
<Circle class="h-2.5 w-2.5 fill-current text-current" />
|
||||||
|
</RadioGroupPrimitive.ItemIndicator>
|
||||||
|
</div>
|
||||||
|
</RadioGroupPrimitive.Item>
|
14
website/src/lib/components/ui/radio-group/radio-group.svelte
Normal file
14
website/src/lib/components/ui/radio-group/radio-group.svelte
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { RadioGroup as RadioGroupPrimitive } from "bits-ui";
|
||||||
|
import { cn } from "$lib/utils.js";
|
||||||
|
|
||||||
|
type $$Props = RadioGroupPrimitive.Props;
|
||||||
|
|
||||||
|
let className: $$Props["class"] = undefined;
|
||||||
|
export let value: $$Props["value"] = undefined;
|
||||||
|
export { className as class };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<RadioGroupPrimitive.Root bind:value class={cn("grid gap-2", className)} {...$$restProps}>
|
||||||
|
<slot />
|
||||||
|
</RadioGroupPrimitive.Root>
|
Reference in New Issue
Block a user