@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,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forest theme fragment — URadioGroup (single-choice radios). Deltas only; everything else inherited
|
|
3
|
+
* from stock Nuxt UI.
|
|
4
|
+
*
|
|
5
|
+
* Forest opinions applied:
|
|
6
|
+
* `legend`: font-bold (stock font-medium) so the group legend reads as a confident field label,
|
|
7
|
+
* matching checkboxGroupTheme and formFieldTheme.
|
|
8
|
+
*
|
|
9
|
+
* The description keeps stock's `text-muted`, matching formFieldTheme — the legend carries the
|
|
10
|
+
* hierarchy on weight alone.
|
|
11
|
+
*
|
|
12
|
+
* The radio circle (rounded-full), focus ring colour, and every variant (list/card/table) are stock —
|
|
13
|
+
* colour-keyed compounds resolve to the Forest OKLCH ramps via the alias bridge. The card variant is
|
|
14
|
+
* left at stock radius, matching the checkbox decision.
|
|
15
|
+
*
|
|
16
|
+
* Slots touched: legend.
|
|
17
|
+
* Slot names sourced from: apps/docs/node_modules/.nuxt-ui/ui/radio-group.ts
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export const radioGroupTheme = {
|
|
21
|
+
slots: {
|
|
22
|
+
// Step legend up to font-bold — Forest field-label convention.
|
|
23
|
+
legend: 'font-bold',
|
|
24
|
+
},
|
|
25
|
+
// Touch: a radio's tap target is the whole item ROW, short at md (~20px). Under a coarse (finger)
|
|
26
|
+
// pointer each size grows one size up as a unit — circle (base), dot (indicator), row height
|
|
27
|
+
// (container), item + legend text, and the fieldset gap — so the row is a taller, easier target. No
|
|
28
|
+
// sliding part, so the bump is clean. xl is the ceiling; each size lists only its deltas up.
|
|
29
|
+
// `coarse:` carries the `:not(.cut-fixed)` escape so the mobile cut (baked size lg) opts out.
|
|
30
|
+
variants: {
|
|
31
|
+
size: {
|
|
32
|
+
xs: { base: 'coarse:size-3.5' },
|
|
33
|
+
sm: { base: 'coarse:size-4', item: 'coarse:text-sm', legend: 'coarse:text-sm', container: 'coarse:h-5', fieldset: 'coarse:gap-y-1', indicator: 'coarse:after:size-1.5' },
|
|
34
|
+
md: { base: 'coarse:size-4.5' },
|
|
35
|
+
lg: { base: 'coarse:size-5', item: 'coarse:text-base', legend: 'coarse:text-base', container: 'coarse:h-6', fieldset: 'coarse:gap-y-1.5', indicator: 'coarse:after:size-2' },
|
|
36
|
+
// xl: ceiling
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
} as const;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forest theme fragment — UScrollArea (custom scrollbar wrapper)
|
|
3
|
+
*
|
|
4
|
+
* Delta over stock lives on one slot, `root`:
|
|
5
|
+
* - `rounded-lg` clips the scroll container to the Forest radius baseline so it sits flush with
|
|
6
|
+
* the surfaces around it (stock ships no explicit rounding).
|
|
7
|
+
* - `scrollbar-color` paints the thumb in the brand green (`--forest-400`, readable in both modes)
|
|
8
|
+
* over a TRANSPARENT track. This is the one place in the system that colours a scrollbar on
|
|
9
|
+
* purpose, so the thumb is loud; the track is not, because an opaque lane is most of what reads
|
|
10
|
+
* as weight and it stays visible whether or not anything is scrolling. The global scrollbar
|
|
11
|
+
* treatment in forest.css supplies the thinning — that lever is `scrollbar-width`, which
|
|
12
|
+
* `scrollbar-color` cannot do (a coloured scrollbar measures the same 15px as an untouched one).
|
|
13
|
+
*
|
|
14
|
+
* `viewport` (flex axis, driven by the orientation variant) and `item` (empty) stay stock.
|
|
15
|
+
*
|
|
16
|
+
* Slot names sourced from: apps/docs/node_modules/.nuxt-ui/ui/scroll-area.ts
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export const scrollAreaTheme = {
|
|
20
|
+
slots: {
|
|
21
|
+
root: 'rounded-lg [scrollbar-color:var(--forest-400)_transparent] hover:[scrollbar-color:var(--forest-500)_transparent]',
|
|
22
|
+
},
|
|
23
|
+
} as const;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forest theme fragment — USelectMenu (searchable select trigger + dropdown panel).
|
|
3
|
+
*
|
|
4
|
+
* Forest opinions applied:
|
|
5
|
+
* - base (trigger): the recessed depth line ported from UInput (inset top edge at rest, dropped on
|
|
6
|
+
* focus) so the field family shares one pressed-into-the-page identity; the brand forest-500 focus
|
|
7
|
+
* ring on outline/subtle; focus:outline-none suppresses the native ring.
|
|
8
|
+
* - touch ladder: each size takes its touch rung (min-h 28/36/40/44/52) under a coarse
|
|
9
|
+
* pointer or the mobile cut — box, gap,
|
|
10
|
+
* effective text, trigger icons); px held (absolute leading/trailing insets). The base carries a
|
|
11
|
+
* responsive `md:text-*` shrink, so the coarse type bump targets the next size's EFFECTIVE value.
|
|
12
|
+
* - recess tint: an explicit intent tints the depth line to its ring colour, but only in `highlight`.
|
|
13
|
+
* - label (group heading): font-bold — lifts headings above GT Haptik body weight (stock font-semibold).
|
|
14
|
+
* - item (list row): before:rounded-sm (8px) — concentric one step inside the 12px panel.
|
|
15
|
+
* - content (panel): the trigger width is a floor rather than a fixed width, so option labels are
|
|
16
|
+
* not truncated by a trigger that is narrower than its own list.
|
|
17
|
+
*
|
|
18
|
+
* Slots touched: base, label, item, content; size variant (depth + touch); variant (focus ring).
|
|
19
|
+
* Slot names sourced from: apps/docs/node_modules/.nuxt-ui/ui/select-menu.ts
|
|
20
|
+
* Trigger chevron and selected check are stock appConfig icon defaults.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export const selectMenuTheme = {
|
|
24
|
+
slots: {
|
|
25
|
+
base: 'transition-[color,box-shadow] duration-(--motion-fast) shadow-[inset_0_calc(var(--forest-input-depth)_+_var(--forest-field-edge,0px))_0_0_var(--forest-input-recess)] focus-visible:shadow-none focus:outline-none',
|
|
26
|
+
// Group label: font-bold so section headings stand out against GT Haptik body weight.
|
|
27
|
+
label: 'font-bold',
|
|
28
|
+
// Item slab: before:rounded-sm (8px) — concentric inside the 12px panel.
|
|
29
|
+
item: 'before:rounded-sm',
|
|
30
|
+
// Stock pins the panel to the trigger width, which truncates option labels whenever the trigger
|
|
31
|
+
// is narrower than its own list — a filter chip sizes to its summary, not to its options. The
|
|
32
|
+
// trigger width becomes a floor instead, so the panel still lines up with the field it belongs
|
|
33
|
+
// to but grows to fit its labels, capped so a long label cannot run past the viewport edge.
|
|
34
|
+
content: 'w-auto min-w-(--reka-combobox-trigger-width) max-w-[min(28rem,var(--reka-combobox-content-available-width))]',
|
|
35
|
+
},
|
|
36
|
+
variants: {
|
|
37
|
+
variant: {
|
|
38
|
+
// Brand forest-500 focus ring — consistent with UInput / USelect.
|
|
39
|
+
outline: 'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--forest-500)]',
|
|
40
|
+
subtle: 'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--forest-500)]',
|
|
41
|
+
},
|
|
42
|
+
// Per-size recessed depth (2/2/3/3/4) + the touch ladder. Object-shaped; deltas only.
|
|
43
|
+
// `touch:` = the shared TOUCH ladder (styles/forest.css) — height+type twin of button.ts/input.ts
|
|
44
|
+
// so a joined <UFieldGroup> matches. Fired by a coarse pointer or by the mobile cut's
|
|
45
|
+
// baked marker — same numbers either way, so a real phone can't step twice.
|
|
46
|
+
size: {
|
|
47
|
+
xs: { base: '[--forest-input-depth:2px] touch:min-h-7 touch:gap-1.5' }, // touch 28
|
|
48
|
+
sm: { base: '[--forest-input-depth:2px] touch:min-h-9 touch:text-sm touch:rounded-md touch:[--forest-input-depth:3px]', leadingIcon: 'touch:size-5', trailingIcon: 'touch:size-5' }, // touch 36
|
|
49
|
+
md: { base: '[--forest-input-depth:3px] touch:min-h-10 touch:text-sm touch:gap-2' }, // touch 40
|
|
50
|
+
lg: { base: '[--forest-input-depth:3px] touch:min-h-11 touch:text-base touch:rounded-lg touch:[--forest-input-depth:4px]', leadingIcon: 'touch:size-6', trailingIcon: 'touch:size-6' }, // touch 44
|
|
51
|
+
xl: { base: '[--forest-input-depth:4px] touch:min-h-13 touch:text-lg touch:rounded-lg touch:[--forest-input-depth:4px]', leadingIcon: 'touch:size-7', trailingIcon: 'touch:size-7' }, // touch 52
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
compoundVariants: [
|
|
55
|
+
// ghost / none carry no fill, so a resting depth line floats with nothing to recess into.
|
|
56
|
+
{ variant: 'ghost', class: { base: 'shadow-none hover:shadow-[inset_0_calc(var(--forest-input-depth)_+_var(--forest-field-edge,0px))_0_0_var(--forest-input-recess)]' } },
|
|
57
|
+
{ variant: 'none', class: { base: 'shadow-none' } },
|
|
58
|
+
// Recess tint gated to `highlight`; a plain rest field keeps the neutral line. neutral omitted.
|
|
59
|
+
{ color: 'primary', highlight: true, class: { base: '[--forest-input-recess:var(--forest-recess-primary)]' } },
|
|
60
|
+
{ color: 'secondary', highlight: true, class: { base: '[--forest-input-recess:var(--forest-recess-secondary)]' } },
|
|
61
|
+
{ color: 'success', highlight: true, class: { base: '[--forest-input-recess:var(--forest-recess-success)]' } },
|
|
62
|
+
{ color: 'info', highlight: true, class: { base: '[--forest-input-recess:var(--forest-recess-info)]' } },
|
|
63
|
+
{ color: 'warning', highlight: true, class: { base: '[--forest-input-recess:var(--forest-recess-warning)]' } },
|
|
64
|
+
{ color: 'error', highlight: true, class: { base: '[--forest-input-recess:var(--forest-recess-error)]' } },
|
|
65
|
+
{ color: 'forest', highlight: true, class: { base: '[--forest-input-recess:var(--forest-recess-forest)]' } },
|
|
66
|
+
{ color: 'maple', highlight: true, class: { base: '[--forest-input-recess:var(--forest-recess-maple)]' } },
|
|
67
|
+
{ color: 'river', highlight: true, class: { base: '[--forest-input-recess:var(--forest-recess-river)]' } },
|
|
68
|
+
{ color: 'warm', highlight: true, class: { base: '[--forest-input-recess:var(--forest-recess-warm)]' } },
|
|
69
|
+
{ color: 'acid', highlight: true, class: { base: '[--forest-input-recess:var(--forest-recess-acid)]' } },
|
|
70
|
+
],
|
|
71
|
+
} as const;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forest theme fragment — USelect (select trigger + dropdown panel, grouped items).
|
|
3
|
+
*
|
|
4
|
+
* Forest opinions applied:
|
|
5
|
+
* base (trigger): the recessed depth line ported from UInput (inset top edge at rest, dropped on
|
|
6
|
+
* focus) so the field family shares one pressed-into-the-page identity; the brand forest-500 focus
|
|
7
|
+
* ring on outline/subtle (matching UInput / UTextarea); focus:outline-none suppresses the native
|
|
8
|
+
* ring. Radius/layout inherited.
|
|
9
|
+
* touch ladder: each size takes its touch rung (min-h 28/36/40/44/52) under a coarse
|
|
10
|
+
* pointer or the mobile cut — box, gap,
|
|
11
|
+
* effective text, trigger icons). Horizontal px is held (the absolute leading/trailing insets would
|
|
12
|
+
* be clobbered). The base carries a responsive `md:text-*` shrink, so the coarse type bump targets
|
|
13
|
+
* the next size's EFFECTIVE value (sm→md `touch:text-sm`, lg→xl `touch:text-base`) or the ladder
|
|
14
|
+
* inverts. xl has a touch rung of its own (52).
|
|
15
|
+
* recess tint: an explicit intent tints the depth line to its ring colour, but only in `highlight`.
|
|
16
|
+
* item: before:rounded-sm (8px) — concentric one step inside the 12px panel.
|
|
17
|
+
* label (group heading): uppercase, dimmed, tight tracking — the Forest section-heading style.
|
|
18
|
+
*
|
|
19
|
+
* Slots touched: base, item, label; size variant (depth + touch); variant (focus ring).
|
|
20
|
+
* Slot names sourced from: apps/docs/node_modules/.nuxt-ui/ui/select.ts
|
|
21
|
+
* Trigger chevron (trailingIcon → appConfig chevronDown) and selected check are stock defaults.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
export const selectTheme = {
|
|
25
|
+
slots: {
|
|
26
|
+
// Recessed depth line + focus:outline-none. Depth scales per size below; recess colour is
|
|
27
|
+
// mode-aware (forest.css) and tints to the intent in the highlight state.
|
|
28
|
+
base: 'transition-[color,box-shadow] duration-(--motion-fast) shadow-[inset_0_calc(var(--forest-input-depth)_+_var(--forest-field-edge,0px))_0_0_var(--forest-input-recess)] focus-visible:shadow-none focus:outline-none',
|
|
29
|
+
// Item slab: before:rounded-sm (8px) — concentric inside the 12px panel.
|
|
30
|
+
item: 'before:rounded-sm',
|
|
31
|
+
// Forest group-heading style: uppercase, dimmed, tight tracking — matches page section headings.
|
|
32
|
+
label: 'text-dimmed uppercase tracking-wider',
|
|
33
|
+
},
|
|
34
|
+
variants: {
|
|
35
|
+
variant: {
|
|
36
|
+
// Brand forest-500 focus ring — consistent with UInput / UTextarea.
|
|
37
|
+
outline: 'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--forest-500)]',
|
|
38
|
+
subtle: 'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--forest-500)]',
|
|
39
|
+
},
|
|
40
|
+
// Per-size recessed depth (2/2/3/3/4) + the touch ladder. Object-shaped (stock's size variant
|
|
41
|
+
// is per-slot objects); each size lists only its deltas up.
|
|
42
|
+
// `touch:` = the shared TOUCH ladder (styles/forest.css) — height+type twin of button.ts/input.ts
|
|
43
|
+
// so a joined <UFieldGroup> matches. Fired by a coarse pointer or by the mobile cut's
|
|
44
|
+
// baked marker — same numbers either way, so a real phone can't step twice.
|
|
45
|
+
size: {
|
|
46
|
+
xs: { base: '[--forest-input-depth:2px] touch:min-h-7 touch:gap-1.5' }, // touch 28
|
|
47
|
+
sm: { base: '[--forest-input-depth:2px] touch:min-h-9 touch:text-sm touch:rounded-md touch:[--forest-input-depth:3px]', leadingIcon: 'touch:size-5', trailingIcon: 'touch:size-5' }, // touch 36
|
|
48
|
+
md: { base: '[--forest-input-depth:3px] touch:min-h-10 touch:text-sm touch:gap-2' }, // touch 40
|
|
49
|
+
lg: { base: '[--forest-input-depth:3px] touch:min-h-11 touch:text-base touch:rounded-lg touch:[--forest-input-depth:4px]', leadingIcon: 'touch:size-6', trailingIcon: 'touch:size-6' }, // touch 44
|
|
50
|
+
xl: { base: '[--forest-input-depth:4px] touch:min-h-13 touch:text-lg touch:rounded-lg touch:[--forest-input-depth:4px]', leadingIcon: 'touch:size-7', trailingIcon: 'touch:size-7' }, // touch 52
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
compoundVariants: [
|
|
54
|
+
// ghost / none carry no fill, so a resting depth line floats with nothing to recess into. ghost
|
|
55
|
+
// picks it up on hover (elevated fill arrives); none stays flat.
|
|
56
|
+
{ variant: 'ghost', class: { base: 'shadow-none hover:shadow-[inset_0_calc(var(--forest-input-depth)_+_var(--forest-field-edge,0px))_0_0_var(--forest-input-recess)]' } },
|
|
57
|
+
{ variant: 'none', class: { base: 'shadow-none' } },
|
|
58
|
+
// Recess tint gated to `highlight` (the persistent coloured-ring state); a plain rest field keeps
|
|
59
|
+
// the neutral line. neutral omitted (its highlight ring is the inverted tone).
|
|
60
|
+
{ color: 'primary', highlight: true, class: { base: '[--forest-input-recess:var(--forest-recess-primary)]' } },
|
|
61
|
+
{ color: 'secondary', highlight: true, class: { base: '[--forest-input-recess:var(--forest-recess-secondary)]' } },
|
|
62
|
+
{ color: 'success', highlight: true, class: { base: '[--forest-input-recess:var(--forest-recess-success)]' } },
|
|
63
|
+
{ color: 'info', highlight: true, class: { base: '[--forest-input-recess:var(--forest-recess-info)]' } },
|
|
64
|
+
{ color: 'warning', highlight: true, class: { base: '[--forest-input-recess:var(--forest-recess-warning)]' } },
|
|
65
|
+
{ color: 'error', highlight: true, class: { base: '[--forest-input-recess:var(--forest-recess-error)]' } },
|
|
66
|
+
{ color: 'forest', highlight: true, class: { base: '[--forest-input-recess:var(--forest-recess-forest)]' } },
|
|
67
|
+
{ color: 'maple', highlight: true, class: { base: '[--forest-input-recess:var(--forest-recess-maple)]' } },
|
|
68
|
+
{ color: 'river', highlight: true, class: { base: '[--forest-input-recess:var(--forest-recess-river)]' } },
|
|
69
|
+
{ color: 'warm', highlight: true, class: { base: '[--forest-input-recess:var(--forest-recess-warm)]' } },
|
|
70
|
+
{ color: 'acid', highlight: true, class: { base: '[--forest-input-recess:var(--forest-recess-acid)]' } },
|
|
71
|
+
],
|
|
72
|
+
} as const;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forest theme fragment — USeparator (divider with optional label/icon).
|
|
3
|
+
*
|
|
4
|
+
* Forest opinions applied:
|
|
5
|
+
* - `container`: label receives `font-bold` — heavier than the stock `font-medium`
|
|
6
|
+
* so the label reads with confident hierarchy when placed between two rule halves, without
|
|
7
|
+
* competing with nearby headings. The `text-default` colour and `text-sm` size are left as
|
|
8
|
+
* stock (already correct via the semantic surface token).
|
|
9
|
+
*
|
|
10
|
+
* Slots NOT overridden:
|
|
11
|
+
* - `root` — stock `flex items-center align-center text-center` is correct; no Forest opinion.
|
|
12
|
+
* - `border` — thickness is handled by compoundVariants (size × orientation); colour is driven by
|
|
13
|
+
* the `color` prop variants already wired to the brand ramps via docs.css. No override.
|
|
14
|
+
* - `icon` — stock `shrink-0 size-5` is the right size for inline icon landmarks.
|
|
15
|
+
* - `avatar` / `avatarSize` — defer to the avatarTheme; no separator-specific opinion.
|
|
16
|
+
* - `label` — thin wrapper; no Forest opinion beyond what `container` already provides.
|
|
17
|
+
*
|
|
18
|
+
* Slot names sourced from: apps/docs/node_modules/.nuxt-ui/ui/separator.ts
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
export const separatorTheme = {
|
|
22
|
+
slots: {
|
|
23
|
+
// Delta only: heavier label weight (stock is font-medium) — confident hierarchy at rest,
|
|
24
|
+
// readable at xs. `text-default flex` are stock and NOT restated here (the preset appends to
|
|
25
|
+
// stock, so a copy would only invite drift).
|
|
26
|
+
container: 'font-bold',
|
|
27
|
+
},
|
|
28
|
+
} as const;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forest theme fragment — USidebar (the persistent navigation shell).
|
|
3
|
+
*
|
|
4
|
+
* Deltas over stock (the preset APPENDS to Nuxt UI's recipe):
|
|
5
|
+
* - `title`: font-display renders the shell heading in MohrAlt (Black, uppercase) — the same
|
|
6
|
+
* treatment UModal and USlideover give their titles. It also closes a breakpoint seam: below
|
|
7
|
+
* lg the sidebar forwards `title` to its mobile menu (a USlideover by default), whose own
|
|
8
|
+
* fragment already stamps font-display, so without this the same string changed face at
|
|
9
|
+
* 1024px.
|
|
10
|
+
* Not cap-trimmed. Mohr's cap band is centred in its own box by the @font-face metric overrides
|
|
11
|
+
* in packages/fonts/forest-fonts.css, so a heading needs no box-level treatment.
|
|
12
|
+
* - `variants.variant.floating.inner`: `bg-default shadow-none`. Two changes to one slot.
|
|
13
|
+
* Stock lifts the floating panel with a `shadow-lg` — Forest surfaces separate by ring and
|
|
14
|
+
* tint, not by drop shadow. And stock gives the panel no background at all, so it showed the
|
|
15
|
+
* shell bed through its own ring; `bg-default` makes it the same surface as the content panel
|
|
16
|
+
* beside it, which is what "two floating parts on one bed" has to mean to read.
|
|
17
|
+
* ★ The variant lifts the COLUMN only. The content panel beside it is a usage-site concern,
|
|
18
|
+
* and its START margin must follow `open` — while the column is out, the gap on the panel's
|
|
19
|
+
* leading edge is the column's own p-4, and nothing supplies it once the column slides away.
|
|
20
|
+
* ★ FLOATING IS OPT-IN, and the default is stock's flush `sidebar`. This fragment used to flip
|
|
21
|
+
* that, on the reasoning that Forest shells float their parts on a muted bed — but the shell's
|
|
22
|
+
* float comes from dashboardPanel and dashboardSidebar, which are different ui keys and never
|
|
23
|
+
* read this one. The population a default here actually governs is a bare `<USidebar>`, and the
|
|
24
|
+
* app rail is not in it: FAppSidebar wraps UDashboardSidebar. What is left is the static column
|
|
25
|
+
* — inside a modal, a settings pane, a card — where floating is usually wrong twice over: it
|
|
26
|
+
* draws a card inside a card, and its `collapsible="none"` compound puts `p-4` on the root,
|
|
27
|
+
* which is border-box and `w-(--sidebar-width)`, so the padding comes out of the column's own
|
|
28
|
+
* width. Floating is a claim about what lies UNDERNEATH the column, and only the usage site can
|
|
29
|
+
* see that. FSidePanel passes it explicitly for its overlay cut; a shell column should too.
|
|
30
|
+
* - `variants.collapsible.icon`: the collapsed rail halves its side padding across header, body and
|
|
31
|
+
* footer, and centres the two regions that hold a single control. Mirrors dashboardSidebar, which
|
|
32
|
+
* carries the same pair — see the note on the variant.
|
|
33
|
+
* - `variants.transition.true`: the collapse/expand timing moves onto the shared motion tokens —
|
|
34
|
+
* --motion-slow / --motion-ease-in-out on the width travel, the near-element pair on the rail.
|
|
35
|
+
*
|
|
36
|
+
* Slots touched by the transition variant: gap, container, rail.
|
|
37
|
+
*
|
|
38
|
+
* Everything else is stock and already rides the Forest ramps:
|
|
39
|
+
* - Every colour and border in the recipe is a semantic token (divide-default, text-highlighted,
|
|
40
|
+
* --ui-border-accented on the rail, --ui-header-height on the header).
|
|
41
|
+
* - The width vars (--sidebar-width 16rem, --sidebar-width-icon 4rem) and the
|
|
42
|
+
* side × collapsible × variant matrix are structure, set at the usage site.
|
|
43
|
+
* - The docs shell's inset look is the `variant="inset"` prop at the usage site, not a theme
|
|
44
|
+
* concern.
|
|
45
|
+
*
|
|
46
|
+
* Slots touched: title, description, header/body/footer under the icon collapse, and inner under the
|
|
47
|
+
* floating variant.
|
|
48
|
+
* Stock recipe: apps/docs/node_modules/.nuxt-ui/ui/sidebar.ts
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
export const sidebarTheme = {
|
|
52
|
+
slots: {
|
|
53
|
+
title: 'font-display',
|
|
54
|
+
// Stock gives USidebar no gap between its title and description, where it gives UCard's
|
|
55
|
+
// description an `mt-1`. Same idiom here rather than a `space-y-*` on the wrapper: the gap belongs
|
|
56
|
+
// to the description in both components, so the two read the same way when someone compares them.
|
|
57
|
+
description: 'mt-1',
|
|
58
|
+
},
|
|
59
|
+
variants: {
|
|
60
|
+
// ★ The collapsed rail halves its side padding in EVERY region, because stock's leaves its own
|
|
61
|
+
// controls no room. Stock keeps `p-4` at any width, which spends 32px of a rail that is 56–64px
|
|
62
|
+
// wide; the nav item is `size-8` (32px) and a footer button's own `px-3.5` makes it 28px at
|
|
63
|
+
// minimum. At stock's default 4rem the nav item fits with exactly zero slack, and at any narrower
|
|
64
|
+
// rail everything overflows — measured at 3.5rem: nav icons 4.5px off centre and hanging past the
|
|
65
|
+
// trailing edge, the footer's collapse button 2.5px off.
|
|
66
|
+
//
|
|
67
|
+
// ★ AND CENTRING CANNOT FIX IT. `mx-auto` and `justify-center` distribute LEFTOVER space, so they
|
|
68
|
+
// do nothing once the item is wider than the box it sits in — the item overflows and the centring
|
|
69
|
+
// silently stops applying. The box has to be wide enough first; that is what this is.
|
|
70
|
+
//
|
|
71
|
+
// Halved rather than zeroed: at `px-0` the controls would centre just as well, but a ghost
|
|
72
|
+
// button's hover fill would run edge to edge across the rail. 8px keeps the fill inset and still
|
|
73
|
+
// clears a 32px control at 3.5rem, with room to spare at stock's 4rem.
|
|
74
|
+
//
|
|
75
|
+
// ★ `justify-center` on the two REGIONS that hold a single control, because room alone only
|
|
76
|
+
// centres a child that stretches to fill it. The body's rows carry their own `mx-auto`, but a
|
|
77
|
+
// header's mark and a footer's avatar are content-width in a flex row that stays `flex-start` —
|
|
78
|
+
// measured, a 28px avatar sat 5.5px off the line every row below it shares. This mirrors
|
|
79
|
+
// themes/dashboard-sidebar.ts, which has carried the same pair since the docs rail hit it; the
|
|
80
|
+
// two components are separate ui keys, so the fix does not travel on its own.
|
|
81
|
+
collapsible: {
|
|
82
|
+
icon: {
|
|
83
|
+
header: 'group-data-[state=collapsed]/sidebar:px-2 group-data-[state=collapsed]/sidebar:justify-center',
|
|
84
|
+
body: 'group-data-[state=collapsed]/sidebar:px-2',
|
|
85
|
+
footer: 'group-data-[state=collapsed]/sidebar:px-2 group-data-[state=collapsed]/sidebar:justify-center',
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
// Collapse / expand is the largest on-screen travel the shell performs, and it both starts and
|
|
89
|
+
// ends in view — the exact case the doctrine reserves --motion-slow + --motion-ease-in-out for
|
|
90
|
+
// (it names the nav viewport resize as the precedent). Stock runs a flat duration-200 on the
|
|
91
|
+
// built-in `ease-out`, which the doctrine calls too weak to read as intentional. The rail is a
|
|
92
|
+
// thin hover affordance, not travel, so it takes the near-element pair instead.
|
|
93
|
+
transition: {
|
|
94
|
+
true: {
|
|
95
|
+
gap: 'duration-(--motion-slow) ease-(--motion-ease-in-out)',
|
|
96
|
+
container: 'duration-(--motion-slow) ease-(--motion-ease-in-out)',
|
|
97
|
+
rail: 'duration-(--motion-fast) ease-(--motion-ease-out)',
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
variant: {
|
|
101
|
+
floating: {
|
|
102
|
+
inner: 'bg-default shadow-none',
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
// Kills the hairline stock leaves hanging in the gutter of a floating sidebar.
|
|
107
|
+
//
|
|
108
|
+
// Stock MEANT to remove it — `variant.floating.container` carries `border-transparent` against
|
|
109
|
+
// the `border-e border-default` that `side.left` sets. But tv applies the `side` variant AFTER
|
|
110
|
+
// `variant`, so tailwind-merge resolves the pair the wrong way round and `border-default` is the
|
|
111
|
+
// one that survives (measured: the container ships `p-4 … border-e border-default`, no
|
|
112
|
+
// `border-transparent` in sight). The result is a 1px line in the gap between the floating
|
|
113
|
+
// column and the content panel, running the container's full height — so it overshoots both
|
|
114
|
+
// rounded cards by the 16px of container padding and reads as a stray rule rather than an edge.
|
|
115
|
+
//
|
|
116
|
+
// Restating `border-transparent` at the variant path would lose the same race. compoundVariants
|
|
117
|
+
// are applied after all variants, so this is the seam that actually wins. Colour, not width:
|
|
118
|
+
// stock keeps the 1px so the floating and flush cuts measure the same.
|
|
119
|
+
compoundVariants: [
|
|
120
|
+
{
|
|
121
|
+
variant: 'floating',
|
|
122
|
+
class: {
|
|
123
|
+
container: 'border-transparent',
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
} as const
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forest theme fragment — USkeleton (loading placeholder).
|
|
3
|
+
*
|
|
4
|
+
* Delta only (single-element component, one `base`): `rounded-sm` over stock's `rounded-md`. The
|
|
5
|
+
* base radius is mostly the TEXT-LINE / generic-box look (avatars and pills override to rounded-full
|
|
6
|
+
* per instance, card images to rounded-xl), so it stays tight — rounded-sm reads as a clean bar, not
|
|
7
|
+
* a pill, and matches Forest's item-slab radius tier. The fill stays stock `bg-elevated`: measured
|
|
8
|
+
* against the page/card surface it has ~2× the contrast of bg-muted (which is nearly invisible on
|
|
9
|
+
* bg-default), so the placeholder actually reads. `animate-pulse` and bg-elevated both carry through
|
|
10
|
+
* from stock — the preset appends, nothing restated.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export const skeletonTheme = {
|
|
14
|
+
base: 'rounded-sm',
|
|
15
|
+
} as const;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forest theme fragment — USlideover (edge side panel).
|
|
3
|
+
*
|
|
4
|
+
* Deltas over stock (the preset appends to Nuxt UI's recipe):
|
|
5
|
+
* - `inset` defaults to TRUE, in every context — standard, mobile and marketing. The panel floats
|
|
6
|
+
* 1 rem off each viewport edge instead of welding itself to it, so it reads as a surface sitting
|
|
7
|
+
* on the app rather than a second chrome, and the corners it earns put it in the same shape
|
|
8
|
+
* family as the card and the drawer. Authors opt back out with `:inset="false"` for the flush cut.
|
|
9
|
+
* - `inset` radius: xl / 24px (stock rounded-lg / 16px). A floating slideover is a full-surface
|
|
10
|
+
* panel, so it takes the card step. Set on the `inset` VARIANT, not the base `content` slot —
|
|
11
|
+
* stock puts its radius on that same variant, so a base override loses the tailwind-merge race
|
|
12
|
+
* (the per-variant seam — see the modal and badge fragments).
|
|
13
|
+
* - `content`: shadow-xl lifts the panel one step above stock's sm:shadow-lg.
|
|
14
|
+
* - `title`: font-display renders the heading in MohrAlt (uppercase, black weight) —
|
|
15
|
+
* the same heading treatment as every other Forest panel/modal.
|
|
16
|
+
* - `header` / `footer`: matching border separators (stock leaves both borderless).
|
|
17
|
+
* - `transition`: the panel slide and the overlay fade move onto the shared motion tokens. A
|
|
18
|
+
* slideover is the longest travel in the overlay family — a full panel crossing in from off
|
|
19
|
+
* screen — so it enters on --motion-slow and leaves on --motion-fast. Both directions ease-out:
|
|
20
|
+
* stock's `ease-in-out` belongs to movement that starts and ends on screen (the tabs indicator,
|
|
21
|
+
* a nav resize), not to something arriving from outside the viewport. The overlay exit also drops
|
|
22
|
+
* stock's banned `ease-in`. The per-side keyframes live in compoundVariants because that's where
|
|
23
|
+
* stock puts them; a variant-level override would never reach them.
|
|
24
|
+
*
|
|
25
|
+
* Everything else — side offsets, inset offsets, dividers — is stock.
|
|
26
|
+
* Slots touched: content, title, header, footer, overlay.
|
|
27
|
+
* Stock recipe: apps/docs/node_modules/.nuxt-ui/ui/slideover.ts
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
export const slideoverTheme = {
|
|
31
|
+
slots: {
|
|
32
|
+
content: 'shadow-xl',
|
|
33
|
+
title: 'font-display',
|
|
34
|
+
header: 'border-b border-default',
|
|
35
|
+
footer: 'border-t border-default',
|
|
36
|
+
},
|
|
37
|
+
variants: {
|
|
38
|
+
inset: {
|
|
39
|
+
true: { content: 'rounded-xl' },
|
|
40
|
+
},
|
|
41
|
+
transition: {
|
|
42
|
+
true: {
|
|
43
|
+
overlay:
|
|
44
|
+
'data-[state=open]:animate-[fade-in_var(--motion-base)_var(--motion-ease-out)] data-[state=closed]:animate-[fade-out_var(--motion-fast)_var(--motion-ease-out)]',
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
// Panel slide, one entry per side — same keyframes as stock, retimed onto the tokens. Compounds
|
|
49
|
+
// apply last, so these win the tailwind-merge over stock's flat 200ms ease-in-out pair.
|
|
50
|
+
compoundVariants: [
|
|
51
|
+
{
|
|
52
|
+
transition: true,
|
|
53
|
+
side: 'top',
|
|
54
|
+
class: {
|
|
55
|
+
content:
|
|
56
|
+
'data-[state=open]:animate-[slide-in-from-top_var(--motion-slow)_var(--motion-ease-out)] data-[state=closed]:animate-[slide-out-to-top_var(--motion-fast)_var(--motion-ease-out)]',
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
transition: true,
|
|
61
|
+
side: 'right',
|
|
62
|
+
class: {
|
|
63
|
+
content:
|
|
64
|
+
'data-[state=open]:animate-[slide-in-from-right_var(--motion-slow)_var(--motion-ease-out)] data-[state=closed]:animate-[slide-out-to-right_var(--motion-fast)_var(--motion-ease-out)]',
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
transition: true,
|
|
69
|
+
side: 'bottom',
|
|
70
|
+
class: {
|
|
71
|
+
content:
|
|
72
|
+
'data-[state=open]:animate-[slide-in-from-bottom_var(--motion-slow)_var(--motion-ease-out)] data-[state=closed]:animate-[slide-out-to-bottom_var(--motion-fast)_var(--motion-ease-out)]',
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
transition: true,
|
|
77
|
+
side: 'left',
|
|
78
|
+
class: {
|
|
79
|
+
content:
|
|
80
|
+
'data-[state=open]:animate-[slide-in-from-left_var(--motion-slow)_var(--motion-ease-out)] data-[state=closed]:animate-[slide-out-to-left_var(--motion-fast)_var(--motion-ease-out)]',
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
defaultVariants: {
|
|
85
|
+
inset: true,
|
|
86
|
+
},
|
|
87
|
+
} as const;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forest theme fragment — USlider (range slider, single / multi thumb).
|
|
3
|
+
*
|
|
4
|
+
* Forest opinions applied:
|
|
5
|
+
*
|
|
6
|
+
* thumb — Forest's soft-radius vocabulary uses `rounded-full` (stock already does this — no
|
|
7
|
+
* change there). We add `shadow-sm` for a subtle lift that reinforces the tactile brand feel
|
|
8
|
+
* (consistent with the keycap button metaphor), and `transition-[box-shadow,ring-color]` so the
|
|
9
|
+
* thumb reacts smoothly when it enters the focused / hovered state. We also add
|
|
10
|
+
* `cursor-grab active:cursor-grabbing` so the drag affordance is immediately clear on pointer
|
|
11
|
+
* devices — the stock cursor is the default arrow.
|
|
12
|
+
*
|
|
13
|
+
* Slots touched: `thumb` (resting look + coarse size-up).
|
|
14
|
+
* Slot names sourced from: apps/docs/node_modules/.nuxt-ui/ui/slider.ts
|
|
15
|
+
*
|
|
16
|
+
* We do NOT override:
|
|
17
|
+
* - Colour variants — `bg-primary`, `ring-primary`, `focus-visible:outline-primary/50` etc. are
|
|
18
|
+
* already wired to the Forest OKLCH ramps via docs.css / forest-preset.ts.
|
|
19
|
+
* - Track height ladder — the xs–xl per-size groove height is left stock (only the thumb takes the
|
|
20
|
+
* coarse touch step-up below).
|
|
21
|
+
* - `track` — stock `bg-accented` groove is the shape; the coloured range is the signal. No
|
|
22
|
+
* border token reads consistently against the track in both modes, so no ring.
|
|
23
|
+
* - `range` slot — `rounded-full` + colour is correct; no additional opinion needed.
|
|
24
|
+
* - `root` slot — layout is correct for both orientations.
|
|
25
|
+
* - `disabled` variant — stock `opacity-75 cursor-not-allowed` is appropriate.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
export const sliderTheme = {
|
|
29
|
+
slots: {
|
|
30
|
+
// Thumb: shadow lift for tactile feel; grab cursor; smooth transition on focus/hover.
|
|
31
|
+
thumb: 'focus-visible:outline-2 shadow-sm cursor-grab active:cursor-grabbing transition-[box-shadow,ring-color] duration-(--motion-fast)',
|
|
32
|
+
},
|
|
33
|
+
// Touch: the thumb is the whole drag target and it is small (md 16px). Under a coarse (finger)
|
|
34
|
+
// pointer the thumb grows one size up — a bigger, easier-to-grab handle — the same proportional
|
|
35
|
+
// step-up the radio circle takes. Only the THUMB bumps: the track is a thin visual groove the thumb
|
|
36
|
+
// overhangs, not a hit surface, so its stock per-size height is left alone. xl is the ceiling; each
|
|
37
|
+
// size lists only its delta up. `coarse:` carries the `:not(.cut-fixed)` escape so the mobile cut
|
|
38
|
+
// (baked size lg) opts out and holds instead of stepping again.
|
|
39
|
+
variants: {
|
|
40
|
+
size: {
|
|
41
|
+
xs: { thumb: 'coarse:size-3.5' },
|
|
42
|
+
sm: { thumb: 'coarse:size-4' },
|
|
43
|
+
md: { thumb: 'coarse:size-4.5' },
|
|
44
|
+
lg: { thumb: 'coarse:size-5' },
|
|
45
|
+
// xl: ceiling
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
} as const;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forest theme fragment — UStepper (multi-step progress).
|
|
3
|
+
*
|
|
4
|
+
* Forest opinions applied:
|
|
5
|
+
*
|
|
6
|
+
* title — lifted to `font-bold` (stock: `font-medium`) so step labels hold hierarchy over
|
|
7
|
+
* the description text. Matches Forest's general heading-density opinion: bold labels, muted
|
|
8
|
+
* descriptions.
|
|
9
|
+
*
|
|
10
|
+
* separator — kept `rounded-full` (stock). Forest opinion: a subtle `opacity-60` on the base
|
|
11
|
+
* so pending separators recede without disappearing; completed separators override via the
|
|
12
|
+
* colour variant's `group-data-[state=completed]:bg-{color}` which restores full opacity.
|
|
13
|
+
*
|
|
14
|
+
* Slots touched: `trigger`, `title`, `separator`.
|
|
15
|
+
* Slot names sourced from: apps/docs/node_modules/.nuxt-ui/ui/stepper.ts
|
|
16
|
+
*
|
|
17
|
+
* We do NOT override:
|
|
18
|
+
* - `root`, `header`, `item`, `container`, `wrapper`, `content` — stock layout is correct for
|
|
19
|
+
* both horizontal and vertical orientations; no Forest density opinion required.
|
|
20
|
+
* - `indicator`, `icon` — stock flex/size utilities are correct.
|
|
21
|
+
* - `description` — `text-muted text-wrap` is the right voice; no override needed.
|
|
22
|
+
* - Colour variants — active/completed bg and focus-visible:outline already resolve to the
|
|
23
|
+
* Forest OKLCH ramps via the Nuxt UI colour-alias bridge in docs.css.
|
|
24
|
+
* - Size compound variants — the xs–xl trigger/text ladder is suitable; no Forest density
|
|
25
|
+
* adjustment needed.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
export const stepperTheme = {
|
|
29
|
+
slots: {
|
|
30
|
+
// Pending trigger: stock shape and state classes; bg-elevated carries the pending fill.
|
|
31
|
+
trigger:
|
|
32
|
+
'focus-visible:outline-2 focus-visible:outline-offset-2',
|
|
33
|
+
|
|
34
|
+
// Step label: font-bold (stock: font-medium) — Forest heading-density opinion.
|
|
35
|
+
title: 'font-bold',
|
|
36
|
+
|
|
37
|
+
// Pending separator: opacity-60 so incomplete connectors recede; completed/active state
|
|
38
|
+
// overrides via colour variant class (group-data-[state=completed]:bg-{color}).
|
|
39
|
+
separator: 'opacity-60 group-data-[state=completed]:opacity-100',
|
|
40
|
+
},
|
|
41
|
+
} as const;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forest theme fragment — USwitch.
|
|
3
|
+
*
|
|
4
|
+
* Forest opinions applied:
|
|
5
|
+
* - `base` (the track): the stock component is already `rounded-full` — that's correct for a
|
|
6
|
+
* toggle track and we preserve it. The Forest opinion here is on the focus ring: we pin
|
|
7
|
+
* `focus-visible:outline-[var(--forest-500)]` so that regardless of which semantic colour the
|
|
8
|
+
* switch is rendered in, the keyboard ring is the brand green (consistent with button / checkbox
|
|
9
|
+
* behaviour). The stock outline class (`focus-visible:outline-primary` etc.) is colour-keyed and
|
|
10
|
+
* resolves correctly via the colour-alias bridge, so for `color="primary"` it already renders
|
|
11
|
+
* forest-500. For the other colours (error, warning, neutral…) the focus ring follows that
|
|
12
|
+
* colour — a deliberate product choice to deviate: neutral's outline-inverted is near-black in
|
|
13
|
+
* light mode and hard to distinguish from the track; pinning brand-500 is safer there.
|
|
14
|
+
* NOTE: the base focus-visible outline is OVERRIDDEN per-colour via the `color` variant entries
|
|
15
|
+
* (see switch.ts generated theme). We patch only the default — callers can still pass
|
|
16
|
+
* `ui="{ base: 'focus-visible:outline-forest' }"` to pin it explicitly.
|
|
17
|
+
* - `thumb`: `shadow-md` replaces the stock `shadow-lg` — the Forest surface vocabulary is
|
|
18
|
+
* tactile but not deeply raised. A softer shadow keeps the thumb readable without adding
|
|
19
|
+
* skeuomorphic depth.
|
|
20
|
+
* - `label`: `font-medium` is already stock; we add nothing — the Nuxt UI default is correct.
|
|
21
|
+
* - `description`: `text-muted` is already stock.
|
|
22
|
+
*
|
|
23
|
+
* Under a coarse (touch) pointer the switch grows one size up — a genuine tap target — via the size
|
|
24
|
+
* variant below; the mobile cut bakes size lg and opts out.
|
|
25
|
+
*
|
|
26
|
+
* KNOWN LIMIT: the thumb's ON position is a discrete `data-[state=checked]:translate-x-*`, and a
|
|
27
|
+
* stacked `coarse:data-[state=checked]:translate-x-*` loses the cascade to stock's same-property rule
|
|
28
|
+
* (both emit; stock wins on source order), so under the ADAPTIVE coarse bump the thumb undershoots
|
|
29
|
+
* the enlarged track by ~2px. Accepted as a minor cosmetic tradeoff. The MOBILE bake is unaffected —
|
|
30
|
+
* a real `size` prop applies the whole lg variant (track + translate together) so its thumb is flush.
|
|
31
|
+
*
|
|
32
|
+
* Slots touched: base, thumb, and the size variant (base/container/thumb/wrapper) for the coarse bump.
|
|
33
|
+
* Slot names sourced from:
|
|
34
|
+
* apps/docs/node_modules/.nuxt-ui/ui/switch.ts
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
export const switchTheme = {
|
|
38
|
+
slots: {
|
|
39
|
+
// Softer focus ring anchored to the brand green regardless of switch colour intent.
|
|
40
|
+
// shadow-sm on the thumb: Forest tactile but not skeuomorphic.
|
|
41
|
+
base: 'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--forest-500)] duration-(--motion-base)',
|
|
42
|
+
thumb: 'shadow-md duration-(--motion-base) ease-(--motion-ease-out)',
|
|
43
|
+
},
|
|
44
|
+
// Touch: a switch's whole control IS the tap target (unlike a checkbox, whose target is the label
|
|
45
|
+
// row), so under a coarse (finger) primary pointer each size grows one size up — the full size
|
|
46
|
+
// bundle: track width (base), track height (container), thumb size + its checked translate, and the
|
|
47
|
+
// label text (wrapper). The thumb's `data-[state=checked]:translate-x-*` must bump in lockstep with
|
|
48
|
+
// the track width or the thumb would overshoot/undershoot the ON position. xl is the ceiling.
|
|
49
|
+
// `coarse:` carries the `:not(.cut-fixed)` escape so the mobile cut (baked size lg) opts out.
|
|
50
|
+
variants: {
|
|
51
|
+
size: {
|
|
52
|
+
// → sm: wider track + bigger thumb (height/text unchanged)
|
|
53
|
+
xs: { base: 'coarse:w-8', thumb: 'coarse:size-3.5 coarse:data-[state=checked]:translate-x-3.5' },
|
|
54
|
+
// → md: wider track, taller, bigger thumb, larger text
|
|
55
|
+
sm: { base: 'coarse:w-9', container: 'coarse:h-5', thumb: 'coarse:size-4 coarse:data-[state=checked]:translate-x-4', wrapper: 'coarse:text-sm' },
|
|
56
|
+
// → lg: wider track + bigger thumb (height/text unchanged)
|
|
57
|
+
md: { base: 'coarse:w-10', thumb: 'coarse:size-4.5 coarse:data-[state=checked]:translate-x-4.5' },
|
|
58
|
+
// → xl: wider track, taller, bigger thumb, larger text
|
|
59
|
+
lg: { base: 'coarse:w-11', container: 'coarse:h-6', thumb: 'coarse:size-5 coarse:data-[state=checked]:translate-x-5', wrapper: 'coarse:text-base' },
|
|
60
|
+
// xl: ceiling — no bump
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
} as const;
|