@humanforest/nuxt-layer 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +67 -0
- package/README.md +208 -0
- package/app.config.ts +10 -0
- package/assets/icons/forest/bike-asterisk.svg +21 -0
- package/assets/icons/forest/bike-down.svg +19 -0
- package/assets/icons/forest/bike-off.svg +20 -0
- package/assets/icons/forest/bike-up.svg +19 -0
- package/assets/icons/forest/bike-x.svg +19 -0
- package/assets/icons/forest/bike.svg +18 -0
- package/assets/icons/forest/mark-fill.svg +6 -0
- package/assets/icons/forest/mark.svg +6 -0
- package/assets/icons/forest/parking.svg +3 -0
- package/assets/icons/forest/star-fill.svg +4 -0
- package/assets/icons/forest/star.svg +4 -0
- package/assets/icons/forest/traffic-light-caution.svg +8 -0
- package/assets/icons/forest/traffic-light-go.svg +8 -0
- package/assets/icons/forest/traffic-light-lit.svg +8 -0
- package/assets/icons/forest/traffic-light-stop.svg +8 -0
- package/assets/icons/forest/traffic-light.svg +8 -0
- package/nuxt.config.ts +81 -0
- package/package.json +35 -0
- package/packages/charts/src/FBoroughShape.vue +52 -0
- package/packages/charts/src/FCalendarGrid.vue +332 -0
- package/packages/charts/src/FCellLegend.vue +54 -0
- package/packages/charts/src/FChartFrame.vue +201 -0
- package/packages/charts/src/FChartFrameSingle.vue +91 -0
- package/packages/charts/src/FChartLegend.vue +26 -0
- package/packages/charts/src/FDistributionBar.vue +224 -0
- package/packages/charts/src/FDottedMap.vue +335 -0
- package/packages/charts/src/FLondonMap.vue +399 -0
- package/packages/charts/src/FSparkline.vue +198 -0
- package/packages/charts/src/FStatusTrack.vue +151 -0
- package/packages/charts/src/cellSize.ts +46 -0
- package/packages/charts/src/distribution.ts +99 -0
- package/packages/charts/src/engine.ts +15 -0
- package/packages/charts/src/forestTooltip.ts +110 -0
- package/packages/charts/src/fromCategories.ts +43 -0
- package/packages/charts/src/index.ts +87 -0
- package/packages/charts/src/londonAreas.ts +75 -0
- package/packages/charts/src/londonGrid.ts +67 -0
- package/packages/charts/src/motionDuration.ts +49 -0
- package/packages/charts/src/presets.ts +143 -0
- package/packages/charts/src/resolveVar.ts +21 -0
- package/packages/charts/src/snapTooltip.ts +154 -0
- package/packages/charts/src/tooltipStandIn.ts +58 -0
- package/packages/charts/src/useCellScale.ts +155 -0
- package/packages/charts/src/useChartPalette.ts +61 -0
- package/packages/charts/src/useChartRepaintKey.ts +22 -0
- package/packages/charts/src/useThemeVersion.ts +17 -0
- package/packages/frames/src/FBrowserFrame.vue +53 -0
- package/packages/frames/src/FDeviceFrame.vue +242 -0
- package/packages/frames/src/FFitFrame.vue +83 -0
- package/packages/frames/src/art/iphone-11.svg +124 -0
- package/packages/frames/src/art/iphone-12.svg +601 -0
- package/packages/frames/src/art/iphone-13-pro.svg +820 -0
- package/packages/frames/src/art/iphone-13.svg +849 -0
- package/packages/frames/src/art/iphone-14.svg +681 -0
- package/packages/frames/src/art/iphone-15-pro.svg +633 -0
- package/packages/frames/src/art/iphone-15.svg +713 -0
- package/packages/frames/src/art/iphone-16-plus.svg +685 -0
- package/packages/frames/src/art/iphone-16-pro-max.svg +611 -0
- package/packages/frames/src/art/iphone-16-pro.svg +631 -0
- package/packages/frames/src/art/iphone-16.svg +713 -0
- package/packages/frames/src/art/iphone-17-pro-max.svg +355 -0
- package/packages/frames/src/art/iphone-17.svg +305 -0
- package/packages/frames/src/art/iphone-air.svg +389 -0
- package/packages/frames/src/art/iphone-se-3.svg +473 -0
- package/packages/frames/src/art/iphone-x.svg +652 -0
- package/packages/frames/src/art/pixel-10-pro-xl.svg +32 -0
- package/packages/frames/src/art/pixel-10-pro.svg +32 -0
- package/packages/frames/src/art/pixel-10.svg +32 -0
- package/packages/frames/src/art/pixel-8-pro.svg +208 -0
- package/packages/frames/src/art/pixel-8.svg +208 -0
- package/packages/frames/src/art/pixel-8a.svg +8 -0
- package/packages/frames/src/art/pixel-9-pro-xl.svg +32 -0
- package/packages/frames/src/art/pixel-9-pro.svg +34 -0
- package/packages/frames/src/art/pixel-9a.svg +25 -0
- package/packages/frames/src/art.ts +29 -0
- package/packages/frames/src/devices.ts +337 -0
- package/packages/frames/src/index.ts +15 -0
- package/packages/tokens/geo/london-boroughs.json +1 -0
- package/packages/tokens/geo/london-dots-coarse.json +1 -0
- package/packages/tokens/geo/london-dots-fine.json +1 -0
- package/packages/tokens/geo/london-dots-medium.json +1 -0
- package/packages/tokens/geo/london-dots-ultra.json +1 -0
- package/packages/tokens/geo/london-wards.json +1 -0
- package/packages/tokens/glyphs/glyphs.json +1 -0
- package/packages/tokens/logos/f-rot.svg +4 -0
- package/packages/tokens/logos/f.svg +4 -0
- package/packages/tokens/logos/favicon-c2w.svg +9 -0
- package/packages/tokens/logos/favicon-console.svg +9 -0
- package/packages/tokens/logos/favicon-ds.svg +9 -0
- package/packages/tokens/logos/favicon-fleet.svg +9 -0
- package/packages/tokens/logos/favicon-flex.svg +9 -0
- package/packages/tokens/logos/favicon-plus.svg +9 -0
- package/packages/tokens/logos/favicon-radar.svg +9 -0
- package/packages/tokens/logos/favicon-tower.svg +9 -0
- package/packages/tokens/logos/lockup-horizontal.svg +17 -0
- package/packages/tokens/logos/lockup-vertical.svg +17 -0
- package/packages/tokens/logos/mark-mono.svg +4 -0
- package/packages/tokens/logos/mark.svg +7 -0
- package/packages/tokens/logos/wordmark.svg +9 -0
- package/packages/tokens/src/colourEngine.ts +604 -0
- package/packages/tokens/src/glyphs.ts +127 -0
- package/packages/tokens/src/logo.ts +50 -0
- package/packages/tokens/src/subbrands.ts +44 -0
- package/packages/tokens/src/sublogo.ts +80 -0
- package/packages/ui/icons/forest/map/forest-place-count.svg +21 -0
- package/packages/ui/icons/forest/map/forest-place-marker.svg +17 -0
- package/packages/ui/icons/forest/map/forest-vehicle-marker.svg +73 -0
- package/packages/ui/icons/forest/map/manifest.json +373 -0
- package/packages/ui/icons/forest/map/place-count-manifest.json +83 -0
- package/packages/ui/icons/forest/map/place-manifest.json +74 -0
- package/packages/ui/src/address/address.ts +125 -0
- package/packages/ui/src/address/autofill.ts +78 -0
- package/packages/ui/src/address/geocode.ts +55 -0
- package/packages/ui/src/address/index.ts +31 -0
- package/packages/ui/src/address/validate.ts +91 -0
- package/packages/ui/src/canvas/FCanvasCard.vue +97 -0
- package/packages/ui/src/canvas/FCanvasControl.vue +121 -0
- package/packages/ui/src/canvas/FCanvasControlGroup.vue +100 -0
- package/packages/ui/src/canvas/FCanvasFlyout.vue +216 -0
- package/packages/ui/src/canvas/FCanvasHud.vue +402 -0
- package/packages/ui/src/canvas/canvasSurface.ts +39 -0
- package/packages/ui/src/canvas/index.ts +17 -0
- package/packages/ui/src/contexts/ForestContext.vue +53 -0
- package/packages/ui/src/contexts/contexts.ts +153 -0
- package/packages/ui/src/contexts/index.ts +23 -0
- package/packages/ui/src/contexts/marketing-theme.ts +214 -0
- package/packages/ui/src/contexts/mobile-theme.ts +402 -0
- package/packages/ui/src/filter/FFilterBar.vue +72 -0
- package/packages/ui/src/filter/FFilterChip.vue +362 -0
- package/packages/ui/src/filter/FFilterClauses.vue +104 -0
- package/packages/ui/src/filter/FFilterTrigger.vue +183 -0
- package/packages/ui/src/filter/filter.theme.ts +27 -0
- package/packages/ui/src/filter/filterClause.ts +265 -0
- package/packages/ui/src/filter/filterPredicate.ts +134 -0
- package/packages/ui/src/filter/filterSchema.ts +82 -0
- package/packages/ui/src/filter/index.ts +36 -0
- package/packages/ui/src/forest-preset.ts +68 -0
- package/packages/ui/src/icons.ts +79 -0
- package/packages/ui/src/index.ts +17 -0
- package/packages/ui/src/kpi/FKpi.vue +253 -0
- package/packages/ui/src/kpi/FKpiGroup.vue +161 -0
- package/packages/ui/src/kpi/index.ts +20 -0
- package/packages/ui/src/kpi/kpi.theme.ts +168 -0
- package/packages/ui/src/kpi/kpiDelta.ts +82 -0
- package/packages/ui/src/kpi/kpiFormat.ts +72 -0
- package/packages/ui/src/kpi/kpiGroupState.ts +63 -0
- package/packages/ui/src/logo/FLogo.vue +52 -0
- package/packages/ui/src/logo/FSubLogo.vue +214 -0
- package/packages/ui/src/logo/index.ts +8 -0
- package/packages/ui/src/map/FMap.vue +355 -0
- package/packages/ui/src/map/FMapCompass.vue +154 -0
- package/packages/ui/src/map/FMapCoordinates.vue +134 -0
- package/packages/ui/src/map/FMapDrawTools.vue +116 -0
- package/packages/ui/src/map/FMapFullscreen.vue +92 -0
- package/packages/ui/src/map/FMapGeolocate.vue +208 -0
- package/packages/ui/src/map/FMapLayers.vue +75 -0
- package/packages/ui/src/map/FMapLegend.vue +45 -0
- package/packages/ui/src/map/FMapPopup.vue +129 -0
- package/packages/ui/src/map/FMapRamp.vue +69 -0
- package/packages/ui/src/map/FMapRegion.vue +77 -0
- package/packages/ui/src/map/FMapScale.vue +107 -0
- package/packages/ui/src/map/FMapSearch.vue +362 -0
- package/packages/ui/src/map/FMapTilt.vue +87 -0
- package/packages/ui/src/map/FMapTools.vue +61 -0
- package/packages/ui/src/map/FMapZoom.vue +81 -0
- package/packages/ui/src/map/FMarker.vue +218 -0
- package/packages/ui/src/map/FTrackScrubber.vue +94 -0
- package/packages/ui/src/map/FVehicleLegend.vue +72 -0
- package/packages/ui/src/map/FVehicleMark.vue +166 -0
- package/packages/ui/src/map/bin.ts +196 -0
- package/packages/ui/src/map/coordinates.ts +83 -0
- package/packages/ui/src/map/density.ts +401 -0
- package/packages/ui/src/map/draw.ts +356 -0
- package/packages/ui/src/map/ease.ts +67 -0
- package/packages/ui/src/map/fullscreen.ts +59 -0
- package/packages/ui/src/map/heat.ts +160 -0
- package/packages/ui/src/map/index.ts +351 -0
- package/packages/ui/src/map/liveMarkers.ts +60 -0
- package/packages/ui/src/map/map.css +116 -0
- package/packages/ui/src/map/mapColor.ts +31 -0
- package/packages/ui/src/map/mapContext.ts +12 -0
- package/packages/ui/src/map/mapLayers.ts +55 -0
- package/packages/ui/src/map/mapLegend.ts +70 -0
- package/packages/ui/src/map/mapSearch.theme.ts +79 -0
- package/packages/ui/src/map/mapSearch.ts +279 -0
- package/packages/ui/src/map/mapUrl.ts +117 -0
- package/packages/ui/src/map/markerAsset.ts +3 -0
- package/packages/ui/src/map/padding.ts +39 -0
- package/packages/ui/src/map/place.ts +192 -0
- package/packages/ui/src/map/ramp.ts +95 -0
- package/packages/ui/src/map/regionSelect.ts +163 -0
- package/packages/ui/src/map/route.ts +167 -0
- package/packages/ui/src/map/scale.ts +329 -0
- package/packages/ui/src/map/scaleBar.ts +67 -0
- package/packages/ui/src/map/selectionModel.ts +50 -0
- package/packages/ui/src/map/sources/coordinates.ts +58 -0
- package/packages/ui/src/map/sources/mapboxPlaces.ts +71 -0
- package/packages/ui/src/map/style.ts +14 -0
- package/packages/ui/src/map/track.ts +279 -0
- package/packages/ui/src/map/useFeatureSelect.ts +230 -0
- package/packages/ui/src/map/useLiveMarkers.ts +84 -0
- package/packages/ui/src/map/useMap.ts +150 -0
- package/packages/ui/src/map/useMapDraw.ts +776 -0
- package/packages/ui/src/map/useMapOverlay.ts +421 -0
- package/packages/ui/src/map/useMapTableSelection.ts +101 -0
- package/packages/ui/src/map/useMapUrlState.ts +155 -0
- package/packages/ui/src/map/useMarkerSource.ts +163 -0
- package/packages/ui/src/map/useRegionSelect.ts +309 -0
- package/packages/ui/src/map/useTokenMode.ts +61 -0
- package/packages/ui/src/map/useTrackPlayback.ts +131 -0
- package/packages/ui/src/map/useVehicleFilter.ts +51 -0
- package/packages/ui/src/map/useVisibleFeatures.ts +124 -0
- package/packages/ui/src/map/vehicle.ts +807 -0
- package/packages/ui/src/map/vehicleFilter.ts +284 -0
- package/packages/ui/src/map/vehicleLegend.ts +155 -0
- package/packages/ui/src/map/visibleFeatures.ts +192 -0
- package/packages/ui/src/map/zone.ts +291 -0
- package/packages/ui/src/shell/FAppNavbar.vue +68 -0
- package/packages/ui/src/shell/FAppShell.vue +163 -0
- package/packages/ui/src/shell/FAppSidebar.vue +215 -0
- package/packages/ui/src/shell/FPageToolbar.vue +126 -0
- package/packages/ui/src/shell/FScrollShadow.vue +43 -0
- package/packages/ui/src/shell/FSidePanel.vue +258 -0
- package/packages/ui/src/shell/FToolbarActions.vue +32 -0
- package/packages/ui/src/shell/index.ts +33 -0
- package/packages/ui/src/shell/nav.ts +37 -0
- package/packages/ui/src/shell/pageToolbar.ts +69 -0
- package/packages/ui/src/shell/shell.ts +74 -0
- package/packages/ui/src/shell/sidePanel.ts +115 -0
- package/packages/ui/src/themes/accordion.ts +36 -0
- package/packages/ui/src/themes/alert.ts +16 -0
- package/packages/ui/src/themes/auth-form.ts +19 -0
- package/packages/ui/src/themes/avatar-group.ts +9 -0
- package/packages/ui/src/themes/avatar.ts +18 -0
- package/packages/ui/src/themes/badge.ts +51 -0
- package/packages/ui/src/themes/banner.ts +21 -0
- package/packages/ui/src/themes/breadcrumb.ts +10 -0
- package/packages/ui/src/themes/button.mechanics.ts +191 -0
- package/packages/ui/src/themes/button.ts +136 -0
- package/packages/ui/src/themes/calendar.ts +29 -0
- package/packages/ui/src/themes/card.ts +67 -0
- package/packages/ui/src/themes/carousel.ts +41 -0
- package/packages/ui/src/themes/checkbox-group.ts +20 -0
- package/packages/ui/src/themes/checkbox.ts +47 -0
- package/packages/ui/src/themes/chip.ts +50 -0
- package/packages/ui/src/themes/collapsible.ts +26 -0
- package/packages/ui/src/themes/color-picker.ts +13 -0
- package/packages/ui/src/themes/command-palette.ts +37 -0
- package/packages/ui/src/themes/container.ts +14 -0
- package/packages/ui/src/themes/context-menu.ts +38 -0
- package/packages/ui/src/themes/dashboard-group.ts +11 -0
- package/packages/ui/src/themes/dashboard-navbar.ts +10 -0
- package/packages/ui/src/themes/dashboard-panel.ts +18 -0
- package/packages/ui/src/themes/dashboard-resize-handle.ts +35 -0
- package/packages/ui/src/themes/dashboard-sidebar-collapse.ts +7 -0
- package/packages/ui/src/themes/dashboard-sidebar-toggle.ts +7 -0
- package/packages/ui/src/themes/dashboard-sidebar.ts +58 -0
- package/packages/ui/src/themes/dashboard-toolbar.ts +15 -0
- package/packages/ui/src/themes/drawer.ts +45 -0
- package/packages/ui/src/themes/dropdown-menu.ts +38 -0
- package/packages/ui/src/themes/empty.ts +43 -0
- package/packages/ui/src/themes/field-group.ts +52 -0
- package/packages/ui/src/themes/file-upload.ts +38 -0
- package/packages/ui/src/themes/footer-columns.ts +13 -0
- package/packages/ui/src/themes/footer.ts +9 -0
- package/packages/ui/src/themes/form-field.ts +25 -0
- package/packages/ui/src/themes/form.ts +22 -0
- package/packages/ui/src/themes/header.ts +12 -0
- package/packages/ui/src/themes/icon.ts +61 -0
- package/packages/ui/src/themes/index.ts +99 -0
- package/packages/ui/src/themes/input-date.ts +73 -0
- package/packages/ui/src/themes/input-menu.ts +66 -0
- package/packages/ui/src/themes/input-number.ts +58 -0
- package/packages/ui/src/themes/input-rating.ts +25 -0
- package/packages/ui/src/themes/input-tags.ts +63 -0
- package/packages/ui/src/themes/input-time.ts +70 -0
- package/packages/ui/src/themes/input.ts +148 -0
- package/packages/ui/src/themes/kbd.ts +27 -0
- package/packages/ui/src/themes/link.ts +27 -0
- package/packages/ui/src/themes/listbox.ts +48 -0
- package/packages/ui/src/themes/main.ts +6 -0
- package/packages/ui/src/themes/marquee.ts +45 -0
- package/packages/ui/src/themes/modal.ts +46 -0
- package/packages/ui/src/themes/navigation-menu.ts +80 -0
- package/packages/ui/src/themes/page-anchors.ts +7 -0
- package/packages/ui/src/themes/page-aside.ts +7 -0
- package/packages/ui/src/themes/page-card.ts +22 -0
- package/packages/ui/src/themes/page-cta.ts +18 -0
- package/packages/ui/src/themes/page-feature.ts +13 -0
- package/packages/ui/src/themes/page-header.ts +16 -0
- package/packages/ui/src/themes/page-hero.ts +15 -0
- package/packages/ui/src/themes/page-links.ts +12 -0
- package/packages/ui/src/themes/page-section.ts +14 -0
- package/packages/ui/src/themes/page.ts +7 -0
- package/packages/ui/src/themes/pagination.ts +17 -0
- package/packages/ui/src/themes/pin-input.ts +73 -0
- package/packages/ui/src/themes/popover.ts +30 -0
- package/packages/ui/src/themes/progress.ts +29 -0
- package/packages/ui/src/themes/prose.ts +45 -0
- package/packages/ui/src/themes/radio-group.ts +39 -0
- package/packages/ui/src/themes/scroll-area.ts +23 -0
- package/packages/ui/src/themes/select-menu.ts +71 -0
- package/packages/ui/src/themes/select.ts +72 -0
- package/packages/ui/src/themes/separator.ts +28 -0
- package/packages/ui/src/themes/sidebar.ts +127 -0
- package/packages/ui/src/themes/skeleton.ts +15 -0
- package/packages/ui/src/themes/slideover.ts +87 -0
- package/packages/ui/src/themes/slider.ts +48 -0
- package/packages/ui/src/themes/stepper.ts +41 -0
- package/packages/ui/src/themes/switch.ts +63 -0
- package/packages/ui/src/themes/table.ts +34 -0
- package/packages/ui/src/themes/tabs.ts +74 -0
- package/packages/ui/src/themes/textarea.ts +77 -0
- package/packages/ui/src/themes/timeline.ts +25 -0
- package/packages/ui/src/themes/toast.ts +29 -0
- package/packages/ui/src/themes/toaster.ts +66 -0
- package/packages/ui/src/themes/tooltip.ts +31 -0
- package/packages/ui/src/themes/tree.ts +41 -0
- package/packages/ui/src/themes/user.ts +28 -0
- package/styles.cdn.gen.css +2047 -0
- package/theme.gen.json +1 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Generated by scripts/icons.ts — do not edit.
|
|
2
|
+
// Source: packages/ui/icons/forest/*.svg + @iconify-json/lucide.
|
|
3
|
+
|
|
4
|
+
/** Structural match for Iconify's IconifyJSON — avoids a runtime dep on @iconify/types here. */
|
|
5
|
+
export type ForestIconCollection = {
|
|
6
|
+
prefix: string;
|
|
7
|
+
icons: Record<string, { body: string }>;
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Forest's own icons, drawn to Lucide's conventions (24×24, stroke-width 2, round caps and joins,
|
|
14
|
+
* currentColor). Most are line drawings with no fill; a few are FILLED shapes — the solid variants, and
|
|
15
|
+
* `parking`, which is the letter P in Mohr Black Italic converted to outlines. They exist only where
|
|
16
|
+
* Lucide has no glyph for something Forest needs.
|
|
17
|
+
* Register with addCollection() from @iconify/vue before mounting, then use as `i-forest-<name>`.
|
|
18
|
+
*/
|
|
19
|
+
export const forestIcons: ForestIconCollection = {
|
|
20
|
+
"prefix": "forest",
|
|
21
|
+
"icons": {
|
|
22
|
+
"bike-asterisk": {
|
|
23
|
+
"body": "<g fill=\"none\" stroke=\"currentColor\"><path d=\"M18.5 19C20.433 19 22 17.433 22 15.5C22 13.567 20.433 12 18.5 12C16.567 12 15 13.567 15 15.5C15 17.433 16.567 19 18.5 19Z\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" /><path d=\"M5.5 19C7.433 19 9 17.433 9 15.5C9 13.567 7.433 12 5.5 12C3.567 12 2 13.567 2 15.5C2 17.433 3.567 19 5.5 19Z\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" /><path d=\"M6 15.5H11.3602C11.7502 15.5 12.1047 15.2732 12.2681 14.9191L15 9\" stroke-width=\"2\" stroke-linecap=\"round\" /><path d=\"M8 9H10\" stroke-width=\"2\" stroke-linecap=\"round\" /><path d=\"M9 9L12 15\" stroke-width=\"2\" stroke-linecap=\"round\" /><path d=\"M12 5H12.8686C13.2543 5 13.6056 5.22183 13.7715 5.57007L18.5 15.5\" stroke-width=\"2\" stroke-linecap=\"round\" /><path d=\"M15 6H18.6126C19.2951 6 19.7771 6.6687 19.5613 7.31623L19.2279 8.31623C19.0918 8.72457 18.7097 9 18.2792 9H16\" stroke-width=\"2\" stroke-linecap=\"round\" /><path d=\"M5.5 12H3\" stroke-width=\"2\" stroke-linecap=\"round\" /><path d=\"M5 3V7.5\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" /><path d=\"M7 4L3 6.5\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" /><path d=\"M3 4L7 6.5\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" /></g>"
|
|
24
|
+
},
|
|
25
|
+
"bike-down": {
|
|
26
|
+
"body": "<g fill=\"none\" stroke=\"currentColor\"><path d=\"M18.5 19C20.433 19 22 17.433 22 15.5C22 13.567 20.433 12 18.5 12C16.567 12 15 13.567 15 15.5C15 17.433 16.567 19 18.5 19Z\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" /><path d=\"M5.5 19C7.433 19 9 17.433 9 15.5C9 13.567 7.433 12 5.5 12C3.567 12 2 13.567 2 15.5C2 17.433 3.567 19 5.5 19Z\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" /><path d=\"M6 15.5H11.3602C11.7502 15.5 12.1047 15.2732 12.2681 14.9191L15 9\" stroke-width=\"2\" stroke-linecap=\"round\" /><path d=\"M8 9H10\" stroke-width=\"2\" stroke-linecap=\"round\" /><path d=\"M9 9L12 15\" stroke-width=\"2\" stroke-linecap=\"round\" /><path d=\"M12 5H12.8686C13.2543 5 13.6056 5.22183 13.7715 5.57007L18.5 15.5\" stroke-width=\"2\" stroke-linecap=\"round\" /><path d=\"M15 6H18.6126C19.2951 6 19.7771 6.6687 19.5613 7.31623L19.2279 8.31623C19.0918 8.72457 18.7097 9 18.2792 9H16\" stroke-width=\"2\" stroke-linecap=\"round\" /><path d=\"M5.5 12H3\" stroke-width=\"2\" stroke-linecap=\"round\" /><path d=\"M4 8L4 2.5M4 8L2 5.5M4 8L6 5.5\" stroke-width=\"2\" stroke-linecap=\"round\" /></g>"
|
|
27
|
+
},
|
|
28
|
+
"bike-off": {
|
|
29
|
+
"body": "<g fill=\"none\" stroke=\"currentColor\"><path d=\"M18.5 18C20.433 18 22 17.433 22 15.5C22 13.567 20.433 12 18.5 12C16.567 12 15 13.567 15 15.5C15 17.433 16.567 18 18.5 18Z\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" /><path d=\"M5.5 18C7.433 18 9 17.433 9 15.5C9 13.567 7.433 12 5.5 12C3.567 12 2 13.567 2 15.5C2 17.433 3.567 18 5.5 18Z\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" /><path d=\"M6 15.5H11.3602C11.7502 15.5 12.1047 15.2732 12.2681 14.9191L15 9\" stroke-width=\"2\" stroke-linecap=\"round\" /><path d=\"M8 9H10\" stroke-width=\"2\" stroke-linecap=\"round\" /><path d=\"M9 9L12 15\" stroke-width=\"2\" stroke-linecap=\"round\" /><path d=\"M12 5H12.8686C13.2543 5 13.6056 5.22183 13.7715 5.57007L18.5 15.5\" stroke-width=\"2\" stroke-linecap=\"round\" /><path d=\"M15 6H18.6126C19.2951 6 19.7771 6.6687 19.5613 7.31623L19.2279 8.31623C19.0918 8.72457 18.7097 9 18.2792 9H16\" stroke-width=\"2\" stroke-linecap=\"round\" /><path d=\"M5.5 12H3\" stroke-width=\"2\" stroke-linecap=\"round\" /><path d=\"M8 18H3\" stroke-width=\"2\" stroke-linecap=\"round\" /><path d=\"M21 18H16\" stroke-width=\"2\" stroke-linecap=\"round\" /></g>"
|
|
30
|
+
},
|
|
31
|
+
"bike-up": {
|
|
32
|
+
"body": "<g fill=\"none\" stroke=\"currentColor\"><path d=\"M18.5 19C20.433 19 22 17.433 22 15.5C22 13.567 20.433 12 18.5 12C16.567 12 15 13.567 15 15.5C15 17.433 16.567 19 18.5 19Z\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" /><path d=\"M5.5 19C7.433 19 9 17.433 9 15.5C9 13.567 7.433 12 5.5 12C3.567 12 2 13.567 2 15.5C2 17.433 3.567 19 5.5 19Z\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" /><path d=\"M6 15.5H11.3602C11.7502 15.5 12.1047 15.2732 12.2681 14.9191L15 9\" stroke-width=\"2\" stroke-linecap=\"round\" /><path d=\"M8 9H10\" stroke-width=\"2\" stroke-linecap=\"round\" /><path d=\"M9 9L12 15\" stroke-width=\"2\" stroke-linecap=\"round\" /><path d=\"M12 5H12.8686C13.2543 5 13.6056 5.22183 13.7715 5.57007L18.5 15.5\" stroke-width=\"2\" stroke-linecap=\"round\" /><path d=\"M15 6H18.6126C19.2951 6 19.7771 6.6687 19.5613 7.31623L19.2279 8.31623C19.0918 8.72457 18.7097 9 18.2792 9H16\" stroke-width=\"2\" stroke-linecap=\"round\" /><path d=\"M5.5 12H3\" stroke-width=\"2\" stroke-linecap=\"round\" /><path d=\"M4 2.5V8M4 2.5L6 5M4 2.5L2 5\" stroke-width=\"2\" stroke-linecap=\"round\" /></g>"
|
|
33
|
+
},
|
|
34
|
+
"bike-x": {
|
|
35
|
+
"body": "<g fill=\"none\" stroke=\"currentColor\"><path d=\"M18.5 19C20.433 19 22 17.433 22 15.5C22 13.567 20.433 12 18.5 12C16.567 12 15 13.567 15 15.5C15 17.433 16.567 19 18.5 19Z\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" /><path d=\"M5.5 19C7.433 19 9 17.433 9 15.5C9 13.567 7.433 12 5.5 12C3.567 12 2 13.567 2 15.5C2 17.433 3.567 19 5.5 19Z\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" /><path d=\"M6 15.5H11.3602C11.7502 15.5 12.1047 15.2732 12.2681 14.9191L15 9\" stroke-width=\"2\" stroke-linecap=\"round\" /><path d=\"M8 9H10\" stroke-width=\"2\" stroke-linecap=\"round\" /><path d=\"M9 9L12 15\" stroke-width=\"2\" stroke-linecap=\"round\" /><path d=\"M12 5H12.8686C13.2543 5 13.6056 5.22183 13.7715 5.57007L18.5 15.5\" stroke-width=\"2\" stroke-linecap=\"round\" /><path d=\"M15 6H18.6126C19.2951 6 19.7771 6.6687 19.5613 7.31623L19.2279 8.31623C19.0918 8.72457 18.7097 9 18.2792 9H16\" stroke-width=\"2\" stroke-linecap=\"round\" /><path d=\"M5.5 12H3\" stroke-width=\"2\" stroke-linecap=\"round\" /><path d=\"M3 7L7 3M7 7L3 3\" stroke-width=\"2\" stroke-linecap=\"round\" /></g>"
|
|
36
|
+
},
|
|
37
|
+
"bike": {
|
|
38
|
+
"body": "<g fill=\"none\" stroke=\"currentColor\"><path d=\"M18.5 19C20.433 19 22 17.433 22 15.5C22 13.567 20.433 12 18.5 12C16.567 12 15 13.567 15 15.5C15 17.433 16.567 19 18.5 19Z\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" /><path d=\"M5.5 19C7.433 19 9 17.433 9 15.5C9 13.567 7.433 12 5.5 12C3.567 12 2 13.567 2 15.5C2 17.433 3.567 19 5.5 19Z\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" /><path d=\"M6 15.5H11.3602C11.7502 15.5 12.1047 15.2732 12.2681 14.9191L15 9\" stroke-width=\"2\" stroke-linecap=\"round\" /><path d=\"M8 9H10\" stroke-width=\"2\" stroke-linecap=\"round\" /><path d=\"M9 9L12 15\" stroke-width=\"2\" stroke-linecap=\"round\" /><path d=\"M12 5H12.8686C13.2543 5 13.6056 5.22183 13.7715 5.57007L18.5 15.5\" stroke-width=\"2\" stroke-linecap=\"round\" /><path d=\"M15 6H18.6126C19.2951 6 19.7771 6.6687 19.5613 7.31623L19.2279 8.31623C19.0918 8.72457 18.7097 9 18.2792 9H16\" stroke-width=\"2\" stroke-linecap=\"round\" /><path d=\"M5.5 12H3\" stroke-width=\"2\" stroke-linecap=\"round\" /></g>"
|
|
39
|
+
},
|
|
40
|
+
"mark-fill": {
|
|
41
|
+
"body": "<g fill=\"currentColor\" stroke=\"currentColor\"><path d=\"M12 2L17.2 12.4H15.4L17.2 16H6.8L8.6 12.4H6.8L12 2Z\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" /><path d=\"M10.2 16V22H8.6\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" /><path d=\"M13.8 16V22H15.4\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" /></g>"
|
|
42
|
+
},
|
|
43
|
+
"mark": {
|
|
44
|
+
"body": "<g fill=\"none\" stroke=\"currentColor\"><path d=\"M12 2L17.2 12.4H15.4L17.2 16H6.8L8.6 12.4H6.8L12 2Z\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" /><path d=\"M10.2 16V22H8.6\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" /><path d=\"M13.8 16V22H15.4\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" /></g>"
|
|
45
|
+
},
|
|
46
|
+
"parking": {
|
|
47
|
+
"body": "<g fill=\"currentColor\"><path d=\"M4.68 20.5 6.48 3.5H12.99Q16.15 3.5 17.78 5.02Q19.41 6.53 19.32 9.35Q19.24 12.66 17.19 14.43Q15.14 16.2 11.62 16.2H10.22L9.78 20.5ZM10.7 11.79H11.95Q13 11.79 13.57 11.26Q14.14 10.73 14.2 9.74Q14.23 8.88 13.77 8.4Q13.3 7.91 12.38 7.91H11.1Z\" /></g>"
|
|
48
|
+
},
|
|
49
|
+
"star-fill": {
|
|
50
|
+
"body": "<g fill=\"currentColor\" stroke=\"currentColor\"><path d=\"M 11.5251 2.2969 a 0.5299 0.5299 0 0 1 0.9498 0 l 2.3095 4.6781 a 2.1196 2.1196 0 0 0 1.5947 1.1598 l 5.165 0.7558 a 0.5299 0.5299 0 0 1 0.2939 0.9038 l -3.7353 3.6373 a 2.1196 2.1196 0 0 0 -0.6109 1.8776 l 0.8818 5.139 a 0.5299 0.5299 0 0 1 -0.7708 0.5599 l -4.6171 -2.4275 a 2.1196 2.1196 0 0 0 -1.9726 0 L 6.3971 21.0082 a 0.5299 0.5299 0 0 1 -0.7698 -0.5599 l 0.8808 -5.138 a 2.1196 2.1196 0 0 0 -0.6109 -1.8786 L 2.162 9.7954 a 0.5299 0.5299 0 0 1 0.2939 -0.9058 l 5.164 -0.7548 a 2.1196 2.1196 0 0 0 1.5967 -1.1598 z\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" /></g>"
|
|
51
|
+
},
|
|
52
|
+
"star": {
|
|
53
|
+
"body": "<g fill=\"none\" stroke=\"currentColor\"><path d=\"M 11.5251 2.2969 a 0.5299 0.5299 0 0 1 0.9498 0 l 2.3095 4.6781 a 2.1196 2.1196 0 0 0 1.5947 1.1598 l 5.165 0.7558 a 0.5299 0.5299 0 0 1 0.2939 0.9038 l -3.7353 3.6373 a 2.1196 2.1196 0 0 0 -0.6109 1.8776 l 0.8818 5.139 a 0.5299 0.5299 0 0 1 -0.7708 0.5599 l -4.6171 -2.4275 a 2.1196 2.1196 0 0 0 -1.9726 0 L 6.3971 21.0082 a 0.5299 0.5299 0 0 1 -0.7698 -0.5599 l 0.8808 -5.138 a 2.1196 2.1196 0 0 0 -0.6109 -1.8786 L 2.162 9.7954 a 0.5299 0.5299 0 0 1 0.2939 -0.9058 l 5.164 -0.7548 a 2.1196 2.1196 0 0 0 1.5967 -1.1598 z\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" /></g>"
|
|
54
|
+
},
|
|
55
|
+
"traffic-light-caution": {
|
|
56
|
+
"body": "<g fill=\"none\" stroke=\"currentColor\"><rect x=\"7\" y=\"2\" width=\"10\" height=\"18\" rx=\"2\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" /><path d=\"M12 20v2\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" /><circle cx=\"12\" cy=\"6\" r=\"2\" fill=\"currentColor\" stroke=\"none\" /><circle cx=\"12\" cy=\"11\" r=\"2\" fill=\"var(--ui-warning, currentColor)\" stroke=\"none\" /><circle cx=\"12\" cy=\"16\" r=\"2\" fill=\"currentColor\" stroke=\"none\" /></g>"
|
|
57
|
+
},
|
|
58
|
+
"traffic-light-go": {
|
|
59
|
+
"body": "<g fill=\"none\" stroke=\"currentColor\"><rect x=\"7\" y=\"2\" width=\"10\" height=\"18\" rx=\"2\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" /><path d=\"M12 20v2\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" /><circle cx=\"12\" cy=\"6\" r=\"2\" fill=\"currentColor\" stroke=\"none\" /><circle cx=\"12\" cy=\"11\" r=\"2\" fill=\"currentColor\" stroke=\"none\" /><circle cx=\"12\" cy=\"16\" r=\"2\" fill=\"var(--ui-success, currentColor)\" stroke=\"none\" /></g>"
|
|
60
|
+
},
|
|
61
|
+
"traffic-light-lit": {
|
|
62
|
+
"body": "<g fill=\"none\" stroke=\"currentColor\"><rect x=\"7\" y=\"2\" width=\"10\" height=\"18\" rx=\"2\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" /><path d=\"M12 20v2\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" /><circle cx=\"12\" cy=\"6\" r=\"2\" fill=\"var(--ui-error, currentColor)\" stroke=\"none\" /><circle cx=\"12\" cy=\"11\" r=\"2\" fill=\"var(--ui-warning, currentColor)\" stroke=\"none\" /><circle cx=\"12\" cy=\"16\" r=\"2\" fill=\"var(--ui-success, currentColor)\" stroke=\"none\" /></g>"
|
|
63
|
+
},
|
|
64
|
+
"traffic-light-stop": {
|
|
65
|
+
"body": "<g fill=\"none\" stroke=\"currentColor\"><rect x=\"7\" y=\"2\" width=\"10\" height=\"18\" rx=\"2\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" /><path d=\"M12 20v2\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" /><circle cx=\"12\" cy=\"6\" r=\"2\" fill=\"var(--ui-error, currentColor)\" stroke=\"none\" /><circle cx=\"12\" cy=\"11\" r=\"2\" fill=\"currentColor\" stroke=\"none\" /><circle cx=\"12\" cy=\"16\" r=\"2\" fill=\"currentColor\" stroke=\"none\" /></g>"
|
|
66
|
+
},
|
|
67
|
+
"traffic-light": {
|
|
68
|
+
"body": "<g fill=\"none\" stroke=\"currentColor\"><rect x=\"7\" y=\"2\" width=\"10\" height=\"18\" rx=\"2\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" /><path d=\"M12 20v2\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" /><circle cx=\"12\" cy=\"6\" r=\"2\" fill=\"currentColor\" stroke=\"none\" /><circle cx=\"12\" cy=\"11\" r=\"2\" fill=\"currentColor\" stroke=\"none\" /><circle cx=\"12\" cy=\"16\" r=\"2\" fill=\"currentColor\" stroke=\"none\" /></g>"
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"width": 24,
|
|
72
|
+
"height": 24
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/** Every icon in the Forest set, e.g. `bike-off` → `i-forest-bike-off`. */
|
|
76
|
+
export const FOREST_ICON_NAMES: readonly string[] = ["bike-asterisk","bike-down","bike-off","bike-up","bike-x","bike","mark-fill","mark","parking","star-fill","star","traffic-light-caution","traffic-light-go","traffic-light-lit","traffic-light-stop","traffic-light"];
|
|
77
|
+
|
|
78
|
+
/** Every Lucide icon name, for the docs catalogue search. Names only — no path data. */
|
|
79
|
+
export const LUCIDE_ICON_NAMES: readonly string[] = ["a-arrow-down","a-arrow-up","a-large-small","accessibility","activity","air-vent","airplay","alarm-clock","alarm-clock-check","alarm-clock-minus","alarm-clock-off","alarm-clock-plus","alarm-smoke","album","align-center","align-center-horizontal","align-center-vertical","align-end-horizontal","align-end-vertical","align-horizontal-distribute-center","align-horizontal-distribute-end","align-horizontal-distribute-start","align-horizontal-justify-center","align-horizontal-justify-end","align-horizontal-justify-start","align-horizontal-space-around","align-horizontal-space-between","align-justify","align-left","align-right","align-start-horizontal","align-start-vertical","align-vertical-distribute-center","align-vertical-distribute-end","align-vertical-distribute-start","align-vertical-justify-center","align-vertical-justify-end","align-vertical-justify-start","align-vertical-space-around","align-vertical-space-between","ambulance","ampersand","ampersands","amphora","anchor","angry","annoyed","antenna","anvil","aperture","app-window","app-window-mac","apple","archive","archive-restore","archive-x","area-chart","armchair","arrow-big-down","arrow-big-down-dash","arrow-big-left","arrow-big-left-dash","arrow-big-right","arrow-big-right-dash","arrow-big-up","arrow-big-up-dash","arrow-down","arrow-down-0-1","arrow-down-1-0","arrow-down-a-z","arrow-down-from-line","arrow-down-left","arrow-down-narrow-wide","arrow-down-right","arrow-down-to-dot","arrow-down-to-line","arrow-down-up","arrow-down-wide-narrow","arrow-down-z-a","arrow-left","arrow-left-from-line","arrow-left-right","arrow-left-to-line","arrow-right","arrow-right-from-line","arrow-right-left","arrow-right-to-line","arrow-up","arrow-up-0-1","arrow-up-1-0","arrow-up-a-z","arrow-up-down","arrow-up-from-dot","arrow-up-from-line","arrow-up-left","arrow-up-narrow-wide","arrow-up-right","arrow-up-to-line","arrow-up-wide-narrow","arrow-up-z-a","arrows-up-from-line","asterisk","astroid","at-sign","atom","audio-lines","audio-waveform","award","axe","axis-3d","baby","backpack","badge","badge-alert","badge-cent","badge-check","badge-dollar-sign","badge-euro","badge-indian-rupee","badge-info","badge-japanese-yen","badge-minus","badge-percent","badge-plus","badge-pound-sterling","badge-question-mark","badge-russian-ruble","badge-swiss-franc","badge-turkish-lira","badge-x","baggage-claim","balloon","ban","banana","bandage","banknote","banknote-arrow-down","banknote-arrow-up","banknote-x","bar-chart-3","bar-chart-4","bar-chart-big","bar-chart-horizontal","bar-chart-horizontal-big","barcode","barrel","baseline","bath","battery","battery-charging","battery-full","battery-low","battery-medium","battery-plus","battery-warning","beaker","bean","bean-off","bed","bed-double","bed-single","beef","beef-off","beer","beer-off","bell","bell-check","bell-dot","bell-electric","bell-minus","bell-off","bell-plus","bell-ring","between-horizontal-end","between-horizontal-start","between-vertical-end","between-vertical-start","biceps-flexed","bike","binary","binoculars","biohazard","bird","birdhouse","bitcoin","blend","blender","blinds","blocks","bluetooth","bluetooth-connected","bluetooth-off","bluetooth-searching","bold","bolt","bomb","bone","book","book-a","book-alert","book-audio","book-check","book-copy","book-dashed","book-down","book-headphones","book-heart","book-image","book-key","book-lock","book-marked","book-minus","book-open","book-open-check","book-open-text","book-plus","book-search","book-text","book-type","book-up","book-up-2","book-user","book-x","bookmark","bookmark-check","bookmark-minus","bookmark-off","bookmark-plus","bookmark-x","boom-box","bot","bot-message-square","bot-off","bottle-wine","bow-arrow","box","boxes","braces","brackets","brain","brain-circuit","brain-cog","brick-wall","brick-wall-fire","brick-wall-shield","briefcase","briefcase-business","briefcase-conveyor-belt","briefcase-medical","bring-to-front","broccoli","brush","brush-cleaning","bubbles","bug","bug-off","bug-play","building","building-2","bus","bus-front","cable","cable-car","cake","cake-slice","calculator","calendar","calendar-1","calendar-arrow-down","calendar-arrow-up","calendar-check","calendar-check-2","calendar-clock","calendar-cog","calendar-days","calendar-fold","calendar-heart","calendar-minus","calendar-minus-2","calendar-off","calendar-plus","calendar-plus-2","calendar-range","calendar-search","calendar-sync","calendar-x","calendar-x-2","calendars","camera","camera-off","candlestick-chart","candy","candy-cane","candy-off","cannabis","cannabis-off","captions","captions-off","car","car-front","car-taxi-front","caravan","card-sim","carrot","case-lower","case-sensitive","case-upper","cassette-tape","cast","castle","cat","cctv","cctv-off","chart-area","chart-bar","chart-bar-big","chart-bar-decreasing","chart-bar-increasing","chart-bar-stacked","chart-candlestick","chart-column","chart-column-big","chart-column-decreasing","chart-column-increasing","chart-column-stacked","chart-gantt","chart-line","chart-network","chart-no-axes-column","chart-no-axes-column-decreasing","chart-no-axes-column-increasing","chart-no-axes-combined","chart-no-axes-gantt","chart-pie","chart-scatter","chart-spline","check","check-check","check-line","chef-hat","cherry","chess-bishop","chess-king","chess-knight","chess-pawn","chess-queen","chess-rook","chevron-down","chevron-first","chevron-last","chevron-left","chevron-right","chevron-up","chevrons-down","chevrons-down-up","chevrons-left","chevrons-left-right","chevrons-left-right-ellipsis","chevrons-right","chevrons-right-left","chevrons-up","chevrons-up-down","chrome","chromium","church","cigarette","cigarette-off","circle","circle-alert","circle-arrow-down","circle-arrow-left","circle-arrow-out-down-left","circle-arrow-out-down-right","circle-arrow-out-up-left","circle-arrow-out-up-right","circle-arrow-right","circle-arrow-up","circle-check","circle-check-big","circle-chevron-down","circle-chevron-left","circle-chevron-right","circle-chevron-up","circle-dashed","circle-divide","circle-dollar-sign","circle-dot","circle-dot-dashed","circle-ellipsis","circle-equal","circle-fading-arrow-up","circle-fading-plus","circle-gauge","circle-minus","circle-off","circle-parking","circle-parking-off","circle-pause","circle-percent","circle-pile","circle-play","circle-plus","circle-pound-sterling","circle-power","circle-question-mark","circle-slash","circle-slash-2","circle-small","circle-star","circle-stop","circle-user","circle-user-round","circle-x","circuit-board","citrus","clapperboard","clipboard","clipboard-check","clipboard-clock","clipboard-copy","clipboard-list","clipboard-minus","clipboard-paste","clipboard-pen","clipboard-pen-line","clipboard-plus","clipboard-type","clipboard-x","clock","clock-1","clock-10","clock-11","clock-12","clock-2","clock-3","clock-4","clock-5","clock-6","clock-7","clock-8","clock-9","clock-alert","clock-arrow-down","clock-arrow-up","clock-check","clock-fading","clock-plus","closed-caption","cloud","cloud-alert","cloud-backup","cloud-check","cloud-cog","cloud-download","cloud-drizzle","cloud-fog","cloud-hail","cloud-lightning","cloud-moon","cloud-moon-rain","cloud-off","cloud-rain","cloud-rain-wind","cloud-snow","cloud-sun","cloud-sun-rain","cloud-sync","cloud-upload","cloudy","clover","club","code","code-xml","codepen","codesandbox","coffee","cog","coins","columns-2","columns-3","columns-3-cog","columns-4","combine","command","compass","component","computer","concierge-bell","cone","construction","contact","contact-round","container","contrast","cookie","cooking-pot","copy","copy-check","copy-minus","copy-plus","copy-slash","copy-x","copyleft","copyright","corner-down-left","corner-down-right","corner-left-down","corner-left-up","corner-right-down","corner-right-up","corner-up-left","corner-up-right","cpu","creative-commons","credit-card","croissant","crop","cross","crosshair","crown","cuboid","cup-soda","currency","cylinder","dam","database","database-backup","database-search","database-zap","decimals-arrow-left","decimals-arrow-right","delete","dessert","diameter","diamond","diamond-minus","diamond-percent","diamond-plus","dice-1","dice-2","dice-3","dice-4","dice-5","dice-6","dices","diff","disc","disc-2","disc-3","disc-album","divide","dna","dna-off","dock","dog","dollar-sign","donut","door-closed","door-closed-locked","door-open","dot","download","drafting-compass","drama","dribbble","drill","drone","droplet","droplet-off","droplets","drum","drumstick","dumbbell","ear","ear-off","earth","earth-lock","eclipse","egg","egg-fried","egg-off","ellipse","ellipsis","ellipsis-vertical","equal","equal-approximately","equal-not","eraser","ethernet-port","euro","ev-charger","expand","external-link","eye","eye-closed","eye-off","facebook","factory","fan","fast-forward","feather","fence","ferris-wheel","figma","file","file-archive","file-audio","file-audio-2","file-axis-3d","file-badge","file-badge-2","file-box","file-braces","file-braces-corner","file-chart-column","file-chart-column-increasing","file-chart-line","file-chart-pie","file-check","file-check-2","file-check-corner","file-clock","file-code","file-code-2","file-code-corner","file-cog","file-diff","file-digit","file-down","file-exclamation-point","file-headphone","file-heart","file-image","file-input","file-json","file-json-2","file-key","file-key-2","file-lock","file-lock-2","file-minus","file-minus-2","file-minus-corner","file-music","file-output","file-pen","file-pen-line","file-pie-chart","file-play","file-plus","file-plus-2","file-plus-corner","file-question-mark","file-scan","file-search","file-search-2","file-search-corner","file-signal","file-sliders","file-spreadsheet","file-stack","file-symlink","file-terminal","file-text","file-type","file-type-2","file-type-corner","file-up","file-user","file-video-camera","file-volume","file-volume-2","file-warning","file-x","file-x-2","file-x-corner","files","film","filter","filter-x","fingerprint-pattern","fire-extinguisher","fish","fish-off","fish-symbol","fishing-hook","fishing-rod","flag","flag-off","flag-triangle-left","flag-triangle-right","flame","flame-kindling","flashlight","flashlight-off","flask-conical","flask-conical-off","flask-round","flip-horizontal-2","flip-vertical-2","flower","flower-2","focus","fold-horizontal","fold-vertical","folder","folder-archive","folder-bookmark","folder-check","folder-clock","folder-closed","folder-code","folder-cog","folder-dot","folder-down","folder-git","folder-git-2","folder-heart","folder-input","folder-kanban","folder-key","folder-lock","folder-minus","folder-open","folder-open-dot","folder-output","folder-pen","folder-plus","folder-root","folder-search","folder-search-2","folder-symlink","folder-sync","folder-tree","folder-up","folder-x","folders","footprints","forklift","form","forward","frame","framer","frown","fuel","fullscreen","funnel","funnel-plus","funnel-x","gallery-horizontal","gallery-horizontal-end","gallery-thumbnails","gallery-vertical","gallery-vertical-end","gamepad","gamepad-2","gamepad-directional","gauge","gavel","gem","georgian-lari","ghost","gift","git-branch","git-branch-minus","git-branch-plus","git-commit-horizontal","git-commit-vertical","git-compare","git-compare-arrows","git-fork","git-graph","git-merge","git-merge-conflict","git-pull-request","git-pull-request-arrow","git-pull-request-closed","git-pull-request-create","git-pull-request-create-arrow","git-pull-request-draft","github","gitlab","glass-water","glasses","globe","globe-check","globe-lock","globe-off","globe-x","goal","gpu","graduation-cap","grape","grid-2x2","grid-2x2-check","grid-2x2-plus","grid-2x2-x","grid-3x2","grid-3x3","grip","grip-horizontal","grip-vertical","group","guitar","ham","hamburger","hammer","hand","hand-coins","hand-fist","hand-grab","hand-heart","hand-helping","hand-metal","hand-platter","handbag","handshake","hard-drive","hard-drive-download","hard-drive-upload","hard-hat","hash","hat-glasses","haze","hd","hdmi-port","heading","heading-1","heading-2","heading-3","heading-4","heading-5","heading-6","headphone-off","headphones","headset","heart","heart-crack","heart-handshake","heart-minus","heart-off","heart-plus","heart-pulse","heart-x","heater","helicopter","hexagon","highlighter","history","hop","hop-off","hospital","hotel","hourglass","house","house-heart","house-plug","house-plus","house-wifi","ice-cream-bowl","ice-cream-cone","id-card","id-card-lanyard","image","image-down","image-minus","image-off","image-play","image-plus","image-up","image-upscale","images","import","inbox","indent-decrease","indent-increase","indian-rupee","infinity","info","inspection-panel","instagram","italic","iteration-ccw","iteration-cw","japanese-yen","joystick","kanban","kayak","key","key-round","key-square","keyboard","keyboard-music","keyboard-off","lamp","lamp-ceiling","lamp-desk","lamp-floor","lamp-wall-down","lamp-wall-up","land-plot","landmark","languages","laptop","laptop-minimal","laptop-minimal-check","lasso","lasso-select","laugh","layers","layers-2","layers-3","layers-minus","layers-plus","layout-dashboard","layout-grid","layout-list","layout-panel-left","layout-panel-top","layout-template","leaf","leafy-green","lectern","lens-concave","lens-convex","letter-text","library","library-big","life-buoy","ligature","lightbulb","lightbulb-off","line-chart","line-dot-right-horizontal","line-squiggle","line-style","link","link-2","link-2-off","linkedin","list","list-check","list-checks","list-chevrons-down-up","list-chevrons-up-down","list-collapse","list-end","list-filter","list-filter-plus","list-indent-decrease","list-indent-increase","list-minus","list-music","list-ordered","list-plus","list-restart","list-start","list-todo","list-tree","list-video","list-x","loader","loader-circle","loader-pinwheel","locate","locate-fixed","locate-off","lock","lock-keyhole","lock-keyhole-open","lock-open","log-in","log-out","logs","lollipop","luggage","magnet","mail","mail-check","mail-minus","mail-open","mail-plus","mail-question-mark","mail-search","mail-warning","mail-x","mailbox","mails","map","map-minus","map-pin","map-pin-check","map-pin-check-inside","map-pin-house","map-pin-minus","map-pin-minus-inside","map-pin-off","map-pin-pen","map-pin-plus","map-pin-plus-inside","map-pin-search","map-pin-x","map-pin-x-inside","map-pinned","map-plus","mars","mars-stroke","martini","maximize","maximize-2","medal","megaphone","megaphone-off","meh","memory-stick","menu","merge","message-circle","message-circle-check","message-circle-code","message-circle-dashed","message-circle-heart","message-circle-more","message-circle-off","message-circle-plus","message-circle-question-mark","message-circle-reply","message-circle-warning","message-circle-x","message-square","message-square-check","message-square-code","message-square-dashed","message-square-diff","message-square-dot","message-square-heart","message-square-lock","message-square-more","message-square-off","message-square-plus","message-square-quote","message-square-reply","message-square-share","message-square-text","message-square-warning","message-square-x","messages-square","metronome","mic","mic-off","mic-vocal","microchip","microscope","microwave","milestone","milk","milk-off","minimize","minimize-2","minus","mirror-rectangular","mirror-round","monitor","monitor-check","monitor-cloud","monitor-cog","monitor-dot","monitor-down","monitor-off","monitor-pause","monitor-play","monitor-smartphone","monitor-speaker","monitor-stop","monitor-up","monitor-x","moon","moon-star","motorbike","mountain","mountain-snow","mouse","mouse-left","mouse-off","mouse-pointer","mouse-pointer-2","mouse-pointer-2-off","mouse-pointer-ban","mouse-pointer-click","mouse-right","move","move-3d","move-diagonal","move-diagonal-2","move-down","move-down-left","move-down-right","move-horizontal","move-left","move-right","move-up","move-up-left","move-up-right","move-vertical","music","music-2","music-3","music-4","navigation","navigation-2","navigation-2-off","navigation-off","network","newspaper","nfc","non-binary","notebook","notebook-pen","notebook-tabs","notebook-text","notepad-text","notepad-text-dashed","nut","nut-off","octagon","octagon-alert","octagon-minus","octagon-pause","octagon-x","omega","option","orbit","origami","package","package-2","package-check","package-minus","package-open","package-plus","package-search","package-x","paint-bucket","paint-roller","paintbrush","paintbrush-vertical","palette","panda","panel-bottom","panel-bottom-close","panel-bottom-dashed","panel-bottom-open","panel-left","panel-left-close","panel-left-dashed","panel-left-open","panel-left-right-dashed","panel-right","panel-right-close","panel-right-dashed","panel-right-open","panel-top","panel-top-bottom-dashed","panel-top-close","panel-top-dashed","panel-top-open","panels-left-bottom","panels-right-bottom","panels-top-left","paperclip","parasol","parentheses","parking-meter","party-popper","pause","paw-print","pc-case","pen","pen-line","pen-off","pen-tool","pencil","pencil-line","pencil-off","pencil-ruler","pentagon","percent","person-standing","philippine-peso","phone","phone-call","phone-forwarded","phone-incoming","phone-missed","phone-off","phone-outgoing","pi","piano","pickaxe","picture-in-picture","picture-in-picture-2","pie-chart","piggy-bank","pilcrow","pilcrow-left","pilcrow-right","pill","pill-bottle","pin","pin-off","pipette","pizza","plane","plane-landing","plane-takeoff","play","play-off","plug","plug-2","plug-zap","plus","pocket","pocket-knife","podcast","pointer","pointer-off","popcorn","popsicle","pound-sterling","power","power-off","presentation","printer","printer-check","printer-x","projector","proportions","puzzle","pyramid","qr-code","quote","rabbit","radar","radiation","radical","radio","radio-off","radio-receiver","radio-tower","radius","rail-symbol","rainbow","rat","ratio","receipt","receipt-cent","receipt-euro","receipt-indian-rupee","receipt-japanese-yen","receipt-pound-sterling","receipt-russian-ruble","receipt-swiss-franc","receipt-text","receipt-turkish-lira","rectangle-circle","rectangle-ellipsis","rectangle-goggles","rectangle-horizontal","rectangle-vertical","recycle","redo","redo-2","redo-dot","refresh-ccw","refresh-ccw-dot","refresh-cw","refresh-cw-off","refrigerator","regex","remove-formatting","repeat","repeat-1","repeat-2","repeat-off","replace","replace-all","reply","reply-all","rewind","ribbon","road","rocket","rocking-chair","roller-coaster","rose","rotate-3d","rotate-ccw","rotate-ccw-key","rotate-ccw-square","rotate-cw","rotate-cw-square","route","route-off","router","rows-2","rows-3","rows-4","rss","ruler","ruler-dimension-line","russian-ruble","sailboat","salad","sandwich","satellite","satellite-dish","saudi-riyal","save","save-all","save-off","scale","scale-3d","scaling","scan","scan-barcode","scan-eye","scan-face","scan-heart","scan-line","scan-qr-code","scan-search","scan-text","scatter-chart","school","scissors","scissors-line-dashed","scooter","screen-share","screen-share-off","scroll","scroll-text","search","search-alert","search-check","search-code","search-large","search-slash","search-x","section","send","send-horizontal","send-to-back","separator-horizontal","separator-vertical","server","server-cog","server-crash","server-off","settings","settings-2","shapes","share","share-2","sheet","shell","shelving-unit","shield","shield-alert","shield-ban","shield-check","shield-cog","shield-cog-corner","shield-ellipsis","shield-half","shield-minus","shield-off","shield-plus","shield-question-mark","shield-user","shield-x","ship","ship-wheel","shirt","shopping-bag","shopping-basket","shopping-cart","shovel","shower-head","shredder","shrimp","shrink","shrub","shuffle","sigma","signal","signal-high","signal-low","signal-medium","signal-zero","signature","signpost","signpost-big","siren","skip-back","skip-forward","skull","slack","slash","slice","sliders-horizontal","sliders-vertical","smartphone","smartphone-charging","smartphone-nfc","smile","smile-plus","snail","snowflake","soap-dispenser-droplet","sofa","solar-panel","soup","space","spade","sparkle","sparkles","speaker","speech","spell-check","spell-check-2","spline","spline-pointer","split","spool","sport-shoe","spotlight","spray-can","sprout","square","square-activity","square-arrow-down","square-arrow-down-left","square-arrow-down-right","square-arrow-left","square-arrow-out-down-left","square-arrow-out-down-right","square-arrow-out-up-left","square-arrow-out-up-right","square-arrow-right","square-arrow-right-enter","square-arrow-right-exit","square-arrow-up","square-arrow-up-left","square-arrow-up-right","square-asterisk","square-bottom-dashed-scissors","square-centerline-dashed-horizontal","square-centerline-dashed-vertical","square-chart-gantt","square-check","square-check-big","square-chevron-down","square-chevron-left","square-chevron-right","square-chevron-up","square-code","square-dashed","square-dashed-bottom","square-dashed-bottom-code","square-dashed-kanban","square-dashed-mouse-pointer","square-dashed-text","square-dashed-top-solid","square-divide","square-dot","square-equal","square-function","square-kanban","square-library","square-m","square-menu","square-minus","square-mouse-pointer","square-parking","square-parking-off","square-pause","square-pen","square-percent","square-pi","square-pilcrow","square-play","square-plus","square-power","square-radical","square-round-corner","square-scissors","square-sigma","square-slash","square-split-horizontal","square-split-vertical","square-square","square-stack","square-star","square-stop","square-terminal","square-user","square-user-round","square-x","squares-exclude","squares-intersect","squares-subtract","squares-unite","squircle","squircle-dashed","squirrel","stamp","star","star-half","star-off","step-back","step-forward","stethoscope","sticker","sticky-note","sticky-note-check","sticky-note-minus","sticky-note-off","sticky-note-plus","sticky-note-x","sticky-notes","stone","store","stretch-horizontal","stretch-vertical","strikethrough","subscript","sun","sun-dim","sun-medium","sun-moon","sun-snow","sunrise","sunset","superscript","swatch-book","swiss-franc","switch-camera","sword","swords","syringe","table","table-2","table-cells-merge","table-cells-split","table-columns-split","table-of-contents","table-properties","table-rows-split","tablet","tablet-smartphone","tablets","tag","tags","tally-1","tally-2","tally-3","tally-4","tally-5","tangent","target","telescope","tent","tent-tree","terminal","test-tube","test-tube-diagonal","test-tubes","text","text-align-center","text-align-end","text-align-justify","text-align-start","text-cursor","text-cursor-input","text-initial","text-quote","text-search","text-wrap","theater","thermometer","thermometer-snowflake","thermometer-sun","thumbs-down","thumbs-up","ticket","ticket-check","ticket-minus","ticket-percent","ticket-plus","ticket-slash","ticket-x","tickets","tickets-plane","timeline","timer","timer-off","timer-reset","toggle-left","toggle-right","toilet","tool-case","toolbox","tornado","torus","touchpad","touchpad-off","towel-rack","tower-control","toy-brick","tractor","traffic-cone","train-front","train-front-tunnel","train-track","tram-front","transgender","trash","trash-2","tree-deciduous","tree-palm","tree-pine","trees","trello","trending-down","trending-up","trending-up-down","triangle","triangle-alert","triangle-dashed","triangle-right","trophy","truck","truck-electric","turkish-lira","turntable","turtle","tv","tv-minimal","tv-minimal-play","twitch","twitter","type","type-outline","umbrella","umbrella-off","underline","undo","undo-2","undo-dot","unfold-horizontal","unfold-vertical","ungroup","university","unlink","unlink-2","unplug","upload","usb","user","user-check","user-cog","user-key","user-lock","user-minus","user-pen","user-plus","user-round","user-round-check","user-round-cog","user-round-key","user-round-minus","user-round-pen","user-round-plus","user-round-search","user-round-x","user-search","user-star","user-x","users","users-round","utensils","utensils-crossed","utility-pole","van","variable","vault","vector-square","vegan","venetian-mask","venus","venus-and-mars","vibrate","vibrate-off","video","video-off","videotape","view","voicemail","volleyball","volume","volume-1","volume-2","volume-off","volume-x","vote","wallet","wallet-cards","wallet-minimal","wallpaper","wand","wand-sparkles","warehouse","washing-machine","watch","waves","waves-arrow-down","waves-arrow-up","waves-horizontal","waves-ladder","waves-vertical","waypoints","webcam","webhook","webhook-off","weight","weight-tilde","wheat","wheat-off","whole-word","wifi","wifi-cog","wifi-high","wifi-low","wifi-off","wifi-pen","wifi-sync","wifi-zero","wind","wind-arrow-down","wine","wine-off","workflow","worm","wrap-text","wrench","x","x-line-top","youtube","zap","zap-off","zodiac-aquarius","zodiac-aries","zodiac-cancer","zodiac-capricorn","zodiac-gemini","zodiac-leo","zodiac-libra","zodiac-ophiuchus","zodiac-pisces","zodiac-sagittarius","zodiac-scorpio","zodiac-taurus","zodiac-virgo","zoom-in","zoom-out"];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export { forestPreset, default, PRESET_STRUCTURAL_MARKERS } from './forest-preset';
|
|
2
|
+
// The three exports below import from ./contexts/* directly rather than the ./contexts barrel: that
|
|
3
|
+
// barrel also re-exports ForestContext.vue (an SFC), and this root barrel must stay SFC-free.
|
|
4
|
+
// scripts/layer.ts imports this file under bun; bun does not error on a .vue import, it loads the SFC
|
|
5
|
+
// as a plain string — so routing through ./contexts would degrade silently rather than throw.
|
|
6
|
+
export { forestMobileTheme, forestMobileProps, forestMobileManifest } from './contexts/mobile-theme';
|
|
7
|
+
export { forestMarketingTheme, forestMarketingProps, forestMarketingManifest } from './contexts/marketing-theme';
|
|
8
|
+
export {
|
|
9
|
+
forestContexts,
|
|
10
|
+
CONTEXT_REGISTER_ROWS,
|
|
11
|
+
HOOK_CLASSES,
|
|
12
|
+
deriveChannels,
|
|
13
|
+
themeTokens,
|
|
14
|
+
stampedMarkers,
|
|
15
|
+
consumedMarkers,
|
|
16
|
+
} from './contexts/contexts';
|
|
17
|
+
export type { ContextManifest, ContextChannel, ForestContextName } from './contexts/contexts';
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// A KPI tile: one figure, what it is, and optionally how it has moved.
|
|
3
|
+
//
|
|
4
|
+
// NAMING. `value` is the tile's stable KEY — what a group's `modelValue` matches on. The number is
|
|
5
|
+
// `metric`. `key` is reserved in Vue templates and `id` implies a DOM id, so the obvious spellings
|
|
6
|
+
// were both taken.
|
|
7
|
+
//
|
|
8
|
+
// The tile is a UCard, so it inherits the card surface and moves when cards move. Inside a group it
|
|
9
|
+
// registers itself, spans four subgrid rows, and may become a tab or a toggle.
|
|
10
|
+
import { computed, inject, onBeforeUnmount, onMounted, useId, useSlots } from 'vue';
|
|
11
|
+
import { kpiTheme, KPI_GROUP_KEY, type KpiSlot } from './kpi.theme';
|
|
12
|
+
import { formatMetric, type KpiFormat } from './kpiFormat';
|
|
13
|
+
import { resolveDelta, type KpiDelta, type KpiSentiment } from './kpiDelta';
|
|
14
|
+
|
|
15
|
+
const props = withDefaults(
|
|
16
|
+
defineProps<{
|
|
17
|
+
label: string;
|
|
18
|
+
/** Stable key. Required when the tile sits in a selectable group. */
|
|
19
|
+
value?: string;
|
|
20
|
+
/** The number. Pre-formatted text goes through the `metric` slot instead. */
|
|
21
|
+
metric?: number | null;
|
|
22
|
+
|
|
23
|
+
format?: KpiFormat;
|
|
24
|
+
precision?: number;
|
|
25
|
+
locale?: string;
|
|
26
|
+
unit?: string;
|
|
27
|
+
noValue?: string;
|
|
28
|
+
|
|
29
|
+
/** Required for an aggregate ("last 7 days"); omit for an instantaneous reading. */
|
|
30
|
+
period?: string;
|
|
31
|
+
asOf?: Date | string;
|
|
32
|
+
/** Rendered, and wired to the value with aria-describedby. Never tooltip-only. */
|
|
33
|
+
caveat?: string;
|
|
34
|
+
/** Info tooltip. Ignored on a selectable tile, which may not hold interactive children. */
|
|
35
|
+
hint?: string;
|
|
36
|
+
|
|
37
|
+
/** Forces the figure's face. Unset lets the context's --fx-metric traits decide. */
|
|
38
|
+
face?: 'tabular' | 'display';
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* How the `visual` slot meets the card. `bleed` runs the graphic to the side and bottom edges —
|
|
42
|
+
* right for a sparkline, which is data and nothing else. `inset` keeps the card's padding and
|
|
43
|
+
* top-aligns, for a graphic carrying text such as FDistributionBar's legend. Ignored without a
|
|
44
|
+
* visual.
|
|
45
|
+
*/
|
|
46
|
+
visualFit?: 'bleed' | 'inset';
|
|
47
|
+
|
|
48
|
+
sentiment?: KpiSentiment;
|
|
49
|
+
status?: 'ok' | 'warn' | 'critical';
|
|
50
|
+
emphasis?: 'value' | 'surface';
|
|
51
|
+
partial?: boolean;
|
|
52
|
+
|
|
53
|
+
delta?: KpiDelta;
|
|
54
|
+
|
|
55
|
+
to?: string;
|
|
56
|
+
linkTarget?: string;
|
|
57
|
+
|
|
58
|
+
state?: 'ok' | 'loading' | 'refreshing' | 'empty' | 'error';
|
|
59
|
+
reason?: 'no-data' | 'permission' | 'system' | 'not-configured';
|
|
60
|
+
|
|
61
|
+
ui?: Partial<Record<KpiSlot, string>>;
|
|
62
|
+
}>(),
|
|
63
|
+
{
|
|
64
|
+
sentiment: 'neutral',
|
|
65
|
+
status: 'ok',
|
|
66
|
+
emphasis: 'value',
|
|
67
|
+
state: 'ok',
|
|
68
|
+
noValue: '—',
|
|
69
|
+
// Bleed is what every visual did before there was a choice, so the default keeps sparkline
|
|
70
|
+
// tiles exactly as they were.
|
|
71
|
+
visualFit: 'bleed',
|
|
72
|
+
},
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const slots = useSlots();
|
|
76
|
+
const group = inject(KPI_GROUP_KEY, undefined);
|
|
77
|
+
const uid = useId();
|
|
78
|
+
const key = computed(() => props.value ?? `${uid}`);
|
|
79
|
+
|
|
80
|
+
onMounted(() => {
|
|
81
|
+
if (import.meta.env.DEV && group?.selectable && !props.value)
|
|
82
|
+
console.warn(`[FKpi] "${props.label}" is in a selectable group and needs a \`value\` to be selectable.`);
|
|
83
|
+
group?.register(key.value);
|
|
84
|
+
});
|
|
85
|
+
onBeforeUnmount(() => group?.unregister(key.value));
|
|
86
|
+
|
|
87
|
+
const selectable = computed(() => !!group?.selectable && !!props.value);
|
|
88
|
+
const selected = computed(() => !!group?.isSelected(key.value));
|
|
89
|
+
|
|
90
|
+
const format = computed(() => props.format ?? group?.format);
|
|
91
|
+
const precision = computed(() => props.precision ?? group?.precision);
|
|
92
|
+
|
|
93
|
+
const isLoading = computed(() => props.state === 'loading');
|
|
94
|
+
const isBlank = computed(() => props.state === 'empty' || props.state === 'error');
|
|
95
|
+
|
|
96
|
+
const REASON_COPY: Record<string, string> = {
|
|
97
|
+
'no-data': 'No data for this period',
|
|
98
|
+
permission: 'You do not have access to this metric',
|
|
99
|
+
system: 'Could not load this metric',
|
|
100
|
+
'not-configured': 'Not configured yet',
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const metricText = computed(() =>
|
|
104
|
+
formatMetric(props.metric, {
|
|
105
|
+
format: format.value,
|
|
106
|
+
precision: precision.value,
|
|
107
|
+
locale: props.locale,
|
|
108
|
+
noValue: props.noValue,
|
|
109
|
+
}),
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const delta = computed(() =>
|
|
113
|
+
resolveDelta(props.delta, {
|
|
114
|
+
sentiment: props.sentiment,
|
|
115
|
+
locale: props.locale,
|
|
116
|
+
precision: precision.value,
|
|
117
|
+
}),
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
// Period first, then the baseline it was measured against — "last 7 days · vs previous 7 days".
|
|
121
|
+
// The other order reads as one repeated phrase.
|
|
122
|
+
const captionParts = computed(() =>
|
|
123
|
+
[
|
|
124
|
+
props.period,
|
|
125
|
+
props.delta ? `vs ${props.delta.comparisonLabel}` : undefined,
|
|
126
|
+
props.partial ? 'Period in progress' : undefined,
|
|
127
|
+
].filter(Boolean),
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const asOfText = computed(() => {
|
|
131
|
+
if (!props.asOf) return undefined;
|
|
132
|
+
const d = props.asOf instanceof Date ? props.asOf : new Date(props.asOf);
|
|
133
|
+
return Number.isNaN(d.getTime())
|
|
134
|
+
? undefined
|
|
135
|
+
: `Updated ${new Intl.DateTimeFormat(props.locale ?? 'en-GB', { timeStyle: 'short' }).format(d)}`;
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const caveatId = computed(() => (props.caveat ? `${uid}-caveat` : undefined));
|
|
139
|
+
|
|
140
|
+
const styles = computed(() =>
|
|
141
|
+
kpiTheme({
|
|
142
|
+
naked: group?.naked ?? false,
|
|
143
|
+
aligned: !!group,
|
|
144
|
+
selectable: selectable.value,
|
|
145
|
+
selected: selected.value,
|
|
146
|
+
hovered: selectable.value && !selected.value,
|
|
147
|
+
// Only when there is a visual: `bleed` zeroes the tile's bottom padding, which a tile with
|
|
148
|
+
// nothing in that row must keep.
|
|
149
|
+
visualFit: slots.visual ? props.visualFit : undefined,
|
|
150
|
+
intent: delta.value?.intent,
|
|
151
|
+
face: props.face,
|
|
152
|
+
status: props.status,
|
|
153
|
+
emphasis: props.emphasis,
|
|
154
|
+
dimmed: isBlank.value,
|
|
155
|
+
}),
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
const cls = (slot: KpiSlot) => styles.value[slot]({ class: props.ui?.[slot] });
|
|
159
|
+
|
|
160
|
+
const onKeydown = (e: KeyboardEvent) => {
|
|
161
|
+
if (!selectable.value || !group) return;
|
|
162
|
+
if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {
|
|
163
|
+
e.preventDefault();
|
|
164
|
+
group.focusStep(key.value, 1);
|
|
165
|
+
} else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {
|
|
166
|
+
e.preventDefault();
|
|
167
|
+
group.focusStep(key.value, -1);
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
</script>
|
|
171
|
+
|
|
172
|
+
<template>
|
|
173
|
+
<UCard
|
|
174
|
+
:as="selectable ? 'button' : undefined"
|
|
175
|
+
:to="selectable ? undefined : to"
|
|
176
|
+
:target="selectable ? undefined : linkTarget"
|
|
177
|
+
:type="selectable ? 'button' : undefined"
|
|
178
|
+
:data-kpi="key"
|
|
179
|
+
:role="selectable && !group?.multiple ? 'tab' : undefined"
|
|
180
|
+
:aria-selected="selectable && !group?.multiple ? selected : undefined"
|
|
181
|
+
:aria-pressed="selectable && group?.multiple ? selected : undefined"
|
|
182
|
+
:aria-controls="selectable ? group?.controls : undefined"
|
|
183
|
+
:tabindex="selectable ? (group?.isFocusable(key) ? 0 : -1) : undefined"
|
|
184
|
+
:class="cls('root')"
|
|
185
|
+
:ui="{ body: 'contents' }"
|
|
186
|
+
@click="selectable && group?.select(key)"
|
|
187
|
+
@keydown="onKeydown"
|
|
188
|
+
>
|
|
189
|
+
<div :class="cls('header')">
|
|
190
|
+
<slot name="leading" />
|
|
191
|
+
<dt :class="cls('label')">
|
|
192
|
+
<slot name="label">{{ label }}</slot>
|
|
193
|
+
</dt>
|
|
194
|
+
<span v-if="hint && !selectable" :class="cls('hint')">
|
|
195
|
+
<UTooltip :text="hint">
|
|
196
|
+
<UIcon name="i-lucide-info" class="size-3.5" />
|
|
197
|
+
</UTooltip>
|
|
198
|
+
</span>
|
|
199
|
+
</div>
|
|
200
|
+
|
|
201
|
+
<dd :class="cls('body')">
|
|
202
|
+
<USkeleton v-if="isLoading" class="h-8 w-24" aria-hidden="true" />
|
|
203
|
+
<span v-else-if="isBlank" :class="cls('metric')">{{ noValue }}</span>
|
|
204
|
+
<template v-else>
|
|
205
|
+
<span v-if="$slots.prefix" :class="cls('prefix')"><slot name="prefix" /></span>
|
|
206
|
+
<span :class="cls('metric')" :aria-describedby="caveatId">
|
|
207
|
+
<slot name="metric">{{ metricText }}</slot>
|
|
208
|
+
</span>
|
|
209
|
+
<span v-if="unit || $slots.suffix" :class="cls('suffix')">
|
|
210
|
+
<slot name="suffix">{{ unit }}</slot>
|
|
211
|
+
</span>
|
|
212
|
+
</template>
|
|
213
|
+
|
|
214
|
+
<slot name="delta">
|
|
215
|
+
<!-- Not a UBadge: a filled chip reads as a second object beside the figure, and shrinking
|
|
216
|
+
it out of the way costs its legibility (xs resolves to 8px type). Bare text sizes off
|
|
217
|
+
the figure through the same container query. The arrow is aria-hidden — direction and
|
|
218
|
+
baseline are stated in words by the visually hidden sentence below. -->
|
|
219
|
+
<span v-if="delta && !delta.hidden && !isLoading && !isBlank" :class="cls('delta')">
|
|
220
|
+
<UIcon
|
|
221
|
+
:name="
|
|
222
|
+
delta.direction === 'flat'
|
|
223
|
+
? 'i-lucide-minus'
|
|
224
|
+
: delta.direction === 'up'
|
|
225
|
+
? 'i-lucide-trending-up'
|
|
226
|
+
: 'i-lucide-trending-down'
|
|
227
|
+
"
|
|
228
|
+
:class="cls('deltaIcon')"
|
|
229
|
+
aria-hidden="true"
|
|
230
|
+
/>
|
|
231
|
+
{{ delta.text }}
|
|
232
|
+
</span>
|
|
233
|
+
</slot>
|
|
234
|
+
<span v-if="delta" class="sr-only">{{ delta.sentence }}</span>
|
|
235
|
+
</dd>
|
|
236
|
+
|
|
237
|
+
<p :class="cls('caption')">
|
|
238
|
+
<slot name="caption">
|
|
239
|
+
<span v-if="isBlank && reason">{{ REASON_COPY[reason] }}</span>
|
|
240
|
+
<span v-else>{{ captionParts.join(' · ') }}</span>
|
|
241
|
+
</slot>
|
|
242
|
+
<span v-if="asOfText" class="ms-1">{{ asOfText }}</span>
|
|
243
|
+
<span v-if="caveat" :id="caveatId" class="ms-1">{{ caveat }}</span>
|
|
244
|
+
</p>
|
|
245
|
+
|
|
246
|
+
<div v-if="$slots.visual" :class="cls('visual')"><slot name="visual" /></div>
|
|
247
|
+
<slot name="insight" />
|
|
248
|
+
<div v-if="$slots.links || $slots.footer" :class="cls('footer')">
|
|
249
|
+
<slot name="links" />
|
|
250
|
+
<slot name="footer" />
|
|
251
|
+
</div>
|
|
252
|
+
</UCard>
|
|
253
|
+
</template>
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// A row of KPI tiles.
|
|
3
|
+
//
|
|
4
|
+
// The row is a four-row grid and each tile spans all four with grid-template-rows: subgrid, so
|
|
5
|
+
// labels, values, captions and visuals align across the row however long any one label runs.
|
|
6
|
+
// Without it, a two-line label pushes its own value down and nothing lines up.
|
|
7
|
+
//
|
|
8
|
+
// Selection: single mode is a tablist with manual activation, pointing at the chart it controls;
|
|
9
|
+
// multiple mode is a set of aria-pressed toggles, because more than one selected tile is not a tab
|
|
10
|
+
// row. One live region for the whole row — a per-tile region turns a refresh into a chorus.
|
|
11
|
+
import { computed, provide, reactive, ref, watch } from 'vue';
|
|
12
|
+
import { KPI_GROUP_KEY, type KpiGroupContext } from './kpi.theme';
|
|
13
|
+
import type { KpiFormat } from './kpiFormat';
|
|
14
|
+
import {
|
|
15
|
+
createKpiRegistry,
|
|
16
|
+
isSelected as isSelectedIn,
|
|
17
|
+
nextKey,
|
|
18
|
+
resolveInitial,
|
|
19
|
+
toggleSelection,
|
|
20
|
+
} from './kpiGroupState';
|
|
21
|
+
|
|
22
|
+
const props = withDefaults(
|
|
23
|
+
defineProps<{
|
|
24
|
+
orientation?: 'horizontal' | 'vertical';
|
|
25
|
+
/** auto-fit floor for the columns. */
|
|
26
|
+
min?: string;
|
|
27
|
+
/** Maximum columns. A wallboard fits eleven tiles across, which nobody wants. */
|
|
28
|
+
columns?: number;
|
|
29
|
+
divided?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Drops the divided row's own outer border and radius, so it can sit flush at the top of a card
|
|
32
|
+
* that already draws them. The row keeps the hairlines between tiles and the one along its
|
|
33
|
+
* bottom edge, which is the edge whatever it drives hangs off.
|
|
34
|
+
*/
|
|
35
|
+
flush?: boolean;
|
|
36
|
+
format?: KpiFormat;
|
|
37
|
+
precision?: number;
|
|
38
|
+
selectable?: boolean;
|
|
39
|
+
multiple?: boolean;
|
|
40
|
+
modelValue?: string | string[];
|
|
41
|
+
defaultValue?: string;
|
|
42
|
+
/** id of the chart container the row drives. */
|
|
43
|
+
controls?: string;
|
|
44
|
+
announce?: 'off' | 'polite';
|
|
45
|
+
label?: string;
|
|
46
|
+
}>(),
|
|
47
|
+
{
|
|
48
|
+
orientation: 'horizontal',
|
|
49
|
+
min: '10rem',
|
|
50
|
+
announce: 'off',
|
|
51
|
+
},
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const emit = defineEmits<{ 'update:modelValue': [value: string | string[]] }>();
|
|
55
|
+
|
|
56
|
+
const registry = createKpiRegistry();
|
|
57
|
+
const keys = ref<string[]>([]);
|
|
58
|
+
const focused = ref<string | undefined>(undefined);
|
|
59
|
+
|
|
60
|
+
const selection = computed(() => resolveInitial(keys.value, props.modelValue, props.defaultValue));
|
|
61
|
+
|
|
62
|
+
watch(keys, (list) => {
|
|
63
|
+
if (!props.selectable || !list.length) return;
|
|
64
|
+
const resolved = resolveInitial(list, props.modelValue, props.defaultValue);
|
|
65
|
+
if (resolved !== undefined && resolved !== props.modelValue) emit('update:modelValue', resolved);
|
|
66
|
+
if (focused.value === undefined || !list.includes(focused.value)) focused.value = list[0];
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const context = reactive({
|
|
70
|
+
register(key: string) {
|
|
71
|
+
registry.register(key);
|
|
72
|
+
keys.value = registry.keys();
|
|
73
|
+
},
|
|
74
|
+
unregister(key: string) {
|
|
75
|
+
registry.unregister(key);
|
|
76
|
+
keys.value = registry.keys();
|
|
77
|
+
},
|
|
78
|
+
select(key: string) {
|
|
79
|
+
focused.value = key;
|
|
80
|
+
emit('update:modelValue', toggleSelection(props.modelValue, key, !!props.multiple));
|
|
81
|
+
},
|
|
82
|
+
focusStep(key: string, step: 1 | -1) {
|
|
83
|
+
const target = nextKey(keys.value, key, step);
|
|
84
|
+
if (!target) return;
|
|
85
|
+
focused.value = target;
|
|
86
|
+
document.querySelector<HTMLElement>(`[data-kpi="${CSS.escape(target)}"]`)?.focus();
|
|
87
|
+
},
|
|
88
|
+
// Gated on `selectable`: resolveInitial falls back to the first tile so a selectable row is never
|
|
89
|
+
// empty, and without this gate that fallback paints the selected treatment on the first tile of
|
|
90
|
+
// every plain row, where selection means nothing.
|
|
91
|
+
isSelected: (key: string) => !!props.selectable && isSelectedIn(selection.value, key),
|
|
92
|
+
isFocusable: (key: string) => (focused.value ?? keys.value[0]) === key,
|
|
93
|
+
get selectable() {
|
|
94
|
+
return !!props.selectable;
|
|
95
|
+
},
|
|
96
|
+
get multiple() {
|
|
97
|
+
return !!props.multiple;
|
|
98
|
+
},
|
|
99
|
+
get controls() {
|
|
100
|
+
return props.controls;
|
|
101
|
+
},
|
|
102
|
+
get format() {
|
|
103
|
+
return props.format;
|
|
104
|
+
},
|
|
105
|
+
get precision() {
|
|
106
|
+
return props.precision;
|
|
107
|
+
},
|
|
108
|
+
get naked() {
|
|
109
|
+
return !!props.divided;
|
|
110
|
+
},
|
|
111
|
+
}) as KpiGroupContext;
|
|
112
|
+
|
|
113
|
+
provide(KPI_GROUP_KEY, context);
|
|
114
|
+
|
|
115
|
+
const gridStyle = computed(() =>
|
|
116
|
+
props.orientation === 'vertical'
|
|
117
|
+
? { gridTemplateColumns: 'minmax(0, 1fr)' }
|
|
118
|
+
: {
|
|
119
|
+
gridTemplateColumns: `repeat(auto-fit, minmax(${props.min}, 1fr))`,
|
|
120
|
+
...(props.columns ? { maxWidth: `calc(${props.columns} * 20rem)` } : {}),
|
|
121
|
+
},
|
|
122
|
+
);
|
|
123
|
+
</script>
|
|
124
|
+
|
|
125
|
+
<template>
|
|
126
|
+
<div>
|
|
127
|
+
<!-- Four content-sized rows. Not 1fr on the last one: the group is sized by its content, so 1fr
|
|
128
|
+
resolves to whatever is left over — nothing — and a visual placed in that row overflows onto
|
|
129
|
+
the caption above it. -->
|
|
130
|
+
<div
|
|
131
|
+
:role="selectable && !multiple ? 'tablist' : selectable ? 'group' : undefined"
|
|
132
|
+
:aria-label="label"
|
|
133
|
+
:aria-orientation="selectable && !multiple ? orientation : undefined"
|
|
134
|
+
class="grid grid-rows-[auto_auto_auto_auto] items-stretch"
|
|
135
|
+
:class="[
|
|
136
|
+
divided ? 'gap-px bg-[var(--ui-border)]' : 'gap-4',
|
|
137
|
+
// Ring outset, not inset: `divided` rules are this element's background through 1px gaps,
|
|
138
|
+
// so the tiles sit flush to its border box and an inset stroke lands on them.
|
|
139
|
+
divided && !flush && 'overflow-hidden rounded-lg ring-1 ring-default',
|
|
140
|
+
divided && flush && 'border-b border-default',
|
|
141
|
+
]"
|
|
142
|
+
:style="gridStyle"
|
|
143
|
+
>
|
|
144
|
+
<!-- The dl is display:contents so the tiles join the grid directly, which also means it is
|
|
145
|
+
the group's ONLY element child — a `divide-x` on the grid would target the dl and never
|
|
146
|
+
reach a tile. The hairlines are the grid's own 1px gaps showing the container's colour
|
|
147
|
+
through, which is the one treatment that stays correct when the row wraps: a `divide-*`
|
|
148
|
+
rule would draw a stray edge on the first tile of every wrapped row.
|
|
149
|
+
|
|
150
|
+
That colour is --ui-border, not bg-accented: these gaps read as the same rules as the
|
|
151
|
+
ring around the row, and accented sits several steps lighter, so the separators inside
|
|
152
|
+
the group came out brighter than the border enclosing them. -->
|
|
153
|
+
<dl class="contents">
|
|
154
|
+
<slot />
|
|
155
|
+
</dl>
|
|
156
|
+
</div>
|
|
157
|
+
<div v-if="announce === 'polite'" class="sr-only" role="status" aria-live="polite">
|
|
158
|
+
<slot name="announcement" />
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
</template>
|