@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.
Files changed (324) hide show
  1. package/LICENSE +67 -0
  2. package/README.md +208 -0
  3. package/app.config.ts +10 -0
  4. package/assets/icons/forest/bike-asterisk.svg +21 -0
  5. package/assets/icons/forest/bike-down.svg +19 -0
  6. package/assets/icons/forest/bike-off.svg +20 -0
  7. package/assets/icons/forest/bike-up.svg +19 -0
  8. package/assets/icons/forest/bike-x.svg +19 -0
  9. package/assets/icons/forest/bike.svg +18 -0
  10. package/assets/icons/forest/mark-fill.svg +6 -0
  11. package/assets/icons/forest/mark.svg +6 -0
  12. package/assets/icons/forest/parking.svg +3 -0
  13. package/assets/icons/forest/star-fill.svg +4 -0
  14. package/assets/icons/forest/star.svg +4 -0
  15. package/assets/icons/forest/traffic-light-caution.svg +8 -0
  16. package/assets/icons/forest/traffic-light-go.svg +8 -0
  17. package/assets/icons/forest/traffic-light-lit.svg +8 -0
  18. package/assets/icons/forest/traffic-light-stop.svg +8 -0
  19. package/assets/icons/forest/traffic-light.svg +8 -0
  20. package/nuxt.config.ts +81 -0
  21. package/package.json +35 -0
  22. package/packages/charts/src/FBoroughShape.vue +52 -0
  23. package/packages/charts/src/FCalendarGrid.vue +332 -0
  24. package/packages/charts/src/FCellLegend.vue +54 -0
  25. package/packages/charts/src/FChartFrame.vue +201 -0
  26. package/packages/charts/src/FChartFrameSingle.vue +91 -0
  27. package/packages/charts/src/FChartLegend.vue +26 -0
  28. package/packages/charts/src/FDistributionBar.vue +224 -0
  29. package/packages/charts/src/FDottedMap.vue +335 -0
  30. package/packages/charts/src/FLondonMap.vue +399 -0
  31. package/packages/charts/src/FSparkline.vue +198 -0
  32. package/packages/charts/src/FStatusTrack.vue +151 -0
  33. package/packages/charts/src/cellSize.ts +46 -0
  34. package/packages/charts/src/distribution.ts +99 -0
  35. package/packages/charts/src/engine.ts +15 -0
  36. package/packages/charts/src/forestTooltip.ts +110 -0
  37. package/packages/charts/src/fromCategories.ts +43 -0
  38. package/packages/charts/src/index.ts +87 -0
  39. package/packages/charts/src/londonAreas.ts +75 -0
  40. package/packages/charts/src/londonGrid.ts +67 -0
  41. package/packages/charts/src/motionDuration.ts +49 -0
  42. package/packages/charts/src/presets.ts +143 -0
  43. package/packages/charts/src/resolveVar.ts +21 -0
  44. package/packages/charts/src/snapTooltip.ts +154 -0
  45. package/packages/charts/src/tooltipStandIn.ts +58 -0
  46. package/packages/charts/src/useCellScale.ts +155 -0
  47. package/packages/charts/src/useChartPalette.ts +61 -0
  48. package/packages/charts/src/useChartRepaintKey.ts +22 -0
  49. package/packages/charts/src/useThemeVersion.ts +17 -0
  50. package/packages/frames/src/FBrowserFrame.vue +53 -0
  51. package/packages/frames/src/FDeviceFrame.vue +242 -0
  52. package/packages/frames/src/FFitFrame.vue +83 -0
  53. package/packages/frames/src/art/iphone-11.svg +124 -0
  54. package/packages/frames/src/art/iphone-12.svg +601 -0
  55. package/packages/frames/src/art/iphone-13-pro.svg +820 -0
  56. package/packages/frames/src/art/iphone-13.svg +849 -0
  57. package/packages/frames/src/art/iphone-14.svg +681 -0
  58. package/packages/frames/src/art/iphone-15-pro.svg +633 -0
  59. package/packages/frames/src/art/iphone-15.svg +713 -0
  60. package/packages/frames/src/art/iphone-16-plus.svg +685 -0
  61. package/packages/frames/src/art/iphone-16-pro-max.svg +611 -0
  62. package/packages/frames/src/art/iphone-16-pro.svg +631 -0
  63. package/packages/frames/src/art/iphone-16.svg +713 -0
  64. package/packages/frames/src/art/iphone-17-pro-max.svg +355 -0
  65. package/packages/frames/src/art/iphone-17.svg +305 -0
  66. package/packages/frames/src/art/iphone-air.svg +389 -0
  67. package/packages/frames/src/art/iphone-se-3.svg +473 -0
  68. package/packages/frames/src/art/iphone-x.svg +652 -0
  69. package/packages/frames/src/art/pixel-10-pro-xl.svg +32 -0
  70. package/packages/frames/src/art/pixel-10-pro.svg +32 -0
  71. package/packages/frames/src/art/pixel-10.svg +32 -0
  72. package/packages/frames/src/art/pixel-8-pro.svg +208 -0
  73. package/packages/frames/src/art/pixel-8.svg +208 -0
  74. package/packages/frames/src/art/pixel-8a.svg +8 -0
  75. package/packages/frames/src/art/pixel-9-pro-xl.svg +32 -0
  76. package/packages/frames/src/art/pixel-9-pro.svg +34 -0
  77. package/packages/frames/src/art/pixel-9a.svg +25 -0
  78. package/packages/frames/src/art.ts +29 -0
  79. package/packages/frames/src/devices.ts +337 -0
  80. package/packages/frames/src/index.ts +15 -0
  81. package/packages/tokens/geo/london-boroughs.json +1 -0
  82. package/packages/tokens/geo/london-dots-coarse.json +1 -0
  83. package/packages/tokens/geo/london-dots-fine.json +1 -0
  84. package/packages/tokens/geo/london-dots-medium.json +1 -0
  85. package/packages/tokens/geo/london-dots-ultra.json +1 -0
  86. package/packages/tokens/geo/london-wards.json +1 -0
  87. package/packages/tokens/glyphs/glyphs.json +1 -0
  88. package/packages/tokens/logos/f-rot.svg +4 -0
  89. package/packages/tokens/logos/f.svg +4 -0
  90. package/packages/tokens/logos/favicon-c2w.svg +9 -0
  91. package/packages/tokens/logos/favicon-console.svg +9 -0
  92. package/packages/tokens/logos/favicon-ds.svg +9 -0
  93. package/packages/tokens/logos/favicon-fleet.svg +9 -0
  94. package/packages/tokens/logos/favicon-flex.svg +9 -0
  95. package/packages/tokens/logos/favicon-plus.svg +9 -0
  96. package/packages/tokens/logos/favicon-radar.svg +9 -0
  97. package/packages/tokens/logos/favicon-tower.svg +9 -0
  98. package/packages/tokens/logos/lockup-horizontal.svg +17 -0
  99. package/packages/tokens/logos/lockup-vertical.svg +17 -0
  100. package/packages/tokens/logos/mark-mono.svg +4 -0
  101. package/packages/tokens/logos/mark.svg +7 -0
  102. package/packages/tokens/logos/wordmark.svg +9 -0
  103. package/packages/tokens/src/colourEngine.ts +604 -0
  104. package/packages/tokens/src/glyphs.ts +127 -0
  105. package/packages/tokens/src/logo.ts +50 -0
  106. package/packages/tokens/src/subbrands.ts +44 -0
  107. package/packages/tokens/src/sublogo.ts +80 -0
  108. package/packages/ui/icons/forest/map/forest-place-count.svg +21 -0
  109. package/packages/ui/icons/forest/map/forest-place-marker.svg +17 -0
  110. package/packages/ui/icons/forest/map/forest-vehicle-marker.svg +73 -0
  111. package/packages/ui/icons/forest/map/manifest.json +373 -0
  112. package/packages/ui/icons/forest/map/place-count-manifest.json +83 -0
  113. package/packages/ui/icons/forest/map/place-manifest.json +74 -0
  114. package/packages/ui/src/address/address.ts +125 -0
  115. package/packages/ui/src/address/autofill.ts +78 -0
  116. package/packages/ui/src/address/geocode.ts +55 -0
  117. package/packages/ui/src/address/index.ts +31 -0
  118. package/packages/ui/src/address/validate.ts +91 -0
  119. package/packages/ui/src/canvas/FCanvasCard.vue +97 -0
  120. package/packages/ui/src/canvas/FCanvasControl.vue +121 -0
  121. package/packages/ui/src/canvas/FCanvasControlGroup.vue +100 -0
  122. package/packages/ui/src/canvas/FCanvasFlyout.vue +216 -0
  123. package/packages/ui/src/canvas/FCanvasHud.vue +402 -0
  124. package/packages/ui/src/canvas/canvasSurface.ts +39 -0
  125. package/packages/ui/src/canvas/index.ts +17 -0
  126. package/packages/ui/src/contexts/ForestContext.vue +53 -0
  127. package/packages/ui/src/contexts/contexts.ts +153 -0
  128. package/packages/ui/src/contexts/index.ts +23 -0
  129. package/packages/ui/src/contexts/marketing-theme.ts +214 -0
  130. package/packages/ui/src/contexts/mobile-theme.ts +402 -0
  131. package/packages/ui/src/filter/FFilterBar.vue +72 -0
  132. package/packages/ui/src/filter/FFilterChip.vue +362 -0
  133. package/packages/ui/src/filter/FFilterClauses.vue +104 -0
  134. package/packages/ui/src/filter/FFilterTrigger.vue +183 -0
  135. package/packages/ui/src/filter/filter.theme.ts +27 -0
  136. package/packages/ui/src/filter/filterClause.ts +265 -0
  137. package/packages/ui/src/filter/filterPredicate.ts +134 -0
  138. package/packages/ui/src/filter/filterSchema.ts +82 -0
  139. package/packages/ui/src/filter/index.ts +36 -0
  140. package/packages/ui/src/forest-preset.ts +68 -0
  141. package/packages/ui/src/icons.ts +79 -0
  142. package/packages/ui/src/index.ts +17 -0
  143. package/packages/ui/src/kpi/FKpi.vue +253 -0
  144. package/packages/ui/src/kpi/FKpiGroup.vue +161 -0
  145. package/packages/ui/src/kpi/index.ts +20 -0
  146. package/packages/ui/src/kpi/kpi.theme.ts +168 -0
  147. package/packages/ui/src/kpi/kpiDelta.ts +82 -0
  148. package/packages/ui/src/kpi/kpiFormat.ts +72 -0
  149. package/packages/ui/src/kpi/kpiGroupState.ts +63 -0
  150. package/packages/ui/src/logo/FLogo.vue +52 -0
  151. package/packages/ui/src/logo/FSubLogo.vue +214 -0
  152. package/packages/ui/src/logo/index.ts +8 -0
  153. package/packages/ui/src/map/FMap.vue +355 -0
  154. package/packages/ui/src/map/FMapCompass.vue +154 -0
  155. package/packages/ui/src/map/FMapCoordinates.vue +134 -0
  156. package/packages/ui/src/map/FMapDrawTools.vue +116 -0
  157. package/packages/ui/src/map/FMapFullscreen.vue +92 -0
  158. package/packages/ui/src/map/FMapGeolocate.vue +208 -0
  159. package/packages/ui/src/map/FMapLayers.vue +75 -0
  160. package/packages/ui/src/map/FMapLegend.vue +45 -0
  161. package/packages/ui/src/map/FMapPopup.vue +129 -0
  162. package/packages/ui/src/map/FMapRamp.vue +69 -0
  163. package/packages/ui/src/map/FMapRegion.vue +77 -0
  164. package/packages/ui/src/map/FMapScale.vue +107 -0
  165. package/packages/ui/src/map/FMapSearch.vue +362 -0
  166. package/packages/ui/src/map/FMapTilt.vue +87 -0
  167. package/packages/ui/src/map/FMapTools.vue +61 -0
  168. package/packages/ui/src/map/FMapZoom.vue +81 -0
  169. package/packages/ui/src/map/FMarker.vue +218 -0
  170. package/packages/ui/src/map/FTrackScrubber.vue +94 -0
  171. package/packages/ui/src/map/FVehicleLegend.vue +72 -0
  172. package/packages/ui/src/map/FVehicleMark.vue +166 -0
  173. package/packages/ui/src/map/bin.ts +196 -0
  174. package/packages/ui/src/map/coordinates.ts +83 -0
  175. package/packages/ui/src/map/density.ts +401 -0
  176. package/packages/ui/src/map/draw.ts +356 -0
  177. package/packages/ui/src/map/ease.ts +67 -0
  178. package/packages/ui/src/map/fullscreen.ts +59 -0
  179. package/packages/ui/src/map/heat.ts +160 -0
  180. package/packages/ui/src/map/index.ts +351 -0
  181. package/packages/ui/src/map/liveMarkers.ts +60 -0
  182. package/packages/ui/src/map/map.css +116 -0
  183. package/packages/ui/src/map/mapColor.ts +31 -0
  184. package/packages/ui/src/map/mapContext.ts +12 -0
  185. package/packages/ui/src/map/mapLayers.ts +55 -0
  186. package/packages/ui/src/map/mapLegend.ts +70 -0
  187. package/packages/ui/src/map/mapSearch.theme.ts +79 -0
  188. package/packages/ui/src/map/mapSearch.ts +279 -0
  189. package/packages/ui/src/map/mapUrl.ts +117 -0
  190. package/packages/ui/src/map/markerAsset.ts +3 -0
  191. package/packages/ui/src/map/padding.ts +39 -0
  192. package/packages/ui/src/map/place.ts +192 -0
  193. package/packages/ui/src/map/ramp.ts +95 -0
  194. package/packages/ui/src/map/regionSelect.ts +163 -0
  195. package/packages/ui/src/map/route.ts +167 -0
  196. package/packages/ui/src/map/scale.ts +329 -0
  197. package/packages/ui/src/map/scaleBar.ts +67 -0
  198. package/packages/ui/src/map/selectionModel.ts +50 -0
  199. package/packages/ui/src/map/sources/coordinates.ts +58 -0
  200. package/packages/ui/src/map/sources/mapboxPlaces.ts +71 -0
  201. package/packages/ui/src/map/style.ts +14 -0
  202. package/packages/ui/src/map/track.ts +279 -0
  203. package/packages/ui/src/map/useFeatureSelect.ts +230 -0
  204. package/packages/ui/src/map/useLiveMarkers.ts +84 -0
  205. package/packages/ui/src/map/useMap.ts +150 -0
  206. package/packages/ui/src/map/useMapDraw.ts +776 -0
  207. package/packages/ui/src/map/useMapOverlay.ts +421 -0
  208. package/packages/ui/src/map/useMapTableSelection.ts +101 -0
  209. package/packages/ui/src/map/useMapUrlState.ts +155 -0
  210. package/packages/ui/src/map/useMarkerSource.ts +163 -0
  211. package/packages/ui/src/map/useRegionSelect.ts +309 -0
  212. package/packages/ui/src/map/useTokenMode.ts +61 -0
  213. package/packages/ui/src/map/useTrackPlayback.ts +131 -0
  214. package/packages/ui/src/map/useVehicleFilter.ts +51 -0
  215. package/packages/ui/src/map/useVisibleFeatures.ts +124 -0
  216. package/packages/ui/src/map/vehicle.ts +807 -0
  217. package/packages/ui/src/map/vehicleFilter.ts +284 -0
  218. package/packages/ui/src/map/vehicleLegend.ts +155 -0
  219. package/packages/ui/src/map/visibleFeatures.ts +192 -0
  220. package/packages/ui/src/map/zone.ts +291 -0
  221. package/packages/ui/src/shell/FAppNavbar.vue +68 -0
  222. package/packages/ui/src/shell/FAppShell.vue +163 -0
  223. package/packages/ui/src/shell/FAppSidebar.vue +215 -0
  224. package/packages/ui/src/shell/FPageToolbar.vue +126 -0
  225. package/packages/ui/src/shell/FScrollShadow.vue +43 -0
  226. package/packages/ui/src/shell/FSidePanel.vue +258 -0
  227. package/packages/ui/src/shell/FToolbarActions.vue +32 -0
  228. package/packages/ui/src/shell/index.ts +33 -0
  229. package/packages/ui/src/shell/nav.ts +37 -0
  230. package/packages/ui/src/shell/pageToolbar.ts +69 -0
  231. package/packages/ui/src/shell/shell.ts +74 -0
  232. package/packages/ui/src/shell/sidePanel.ts +115 -0
  233. package/packages/ui/src/themes/accordion.ts +36 -0
  234. package/packages/ui/src/themes/alert.ts +16 -0
  235. package/packages/ui/src/themes/auth-form.ts +19 -0
  236. package/packages/ui/src/themes/avatar-group.ts +9 -0
  237. package/packages/ui/src/themes/avatar.ts +18 -0
  238. package/packages/ui/src/themes/badge.ts +51 -0
  239. package/packages/ui/src/themes/banner.ts +21 -0
  240. package/packages/ui/src/themes/breadcrumb.ts +10 -0
  241. package/packages/ui/src/themes/button.mechanics.ts +191 -0
  242. package/packages/ui/src/themes/button.ts +136 -0
  243. package/packages/ui/src/themes/calendar.ts +29 -0
  244. package/packages/ui/src/themes/card.ts +67 -0
  245. package/packages/ui/src/themes/carousel.ts +41 -0
  246. package/packages/ui/src/themes/checkbox-group.ts +20 -0
  247. package/packages/ui/src/themes/checkbox.ts +47 -0
  248. package/packages/ui/src/themes/chip.ts +50 -0
  249. package/packages/ui/src/themes/collapsible.ts +26 -0
  250. package/packages/ui/src/themes/color-picker.ts +13 -0
  251. package/packages/ui/src/themes/command-palette.ts +37 -0
  252. package/packages/ui/src/themes/container.ts +14 -0
  253. package/packages/ui/src/themes/context-menu.ts +38 -0
  254. package/packages/ui/src/themes/dashboard-group.ts +11 -0
  255. package/packages/ui/src/themes/dashboard-navbar.ts +10 -0
  256. package/packages/ui/src/themes/dashboard-panel.ts +18 -0
  257. package/packages/ui/src/themes/dashboard-resize-handle.ts +35 -0
  258. package/packages/ui/src/themes/dashboard-sidebar-collapse.ts +7 -0
  259. package/packages/ui/src/themes/dashboard-sidebar-toggle.ts +7 -0
  260. package/packages/ui/src/themes/dashboard-sidebar.ts +58 -0
  261. package/packages/ui/src/themes/dashboard-toolbar.ts +15 -0
  262. package/packages/ui/src/themes/drawer.ts +45 -0
  263. package/packages/ui/src/themes/dropdown-menu.ts +38 -0
  264. package/packages/ui/src/themes/empty.ts +43 -0
  265. package/packages/ui/src/themes/field-group.ts +52 -0
  266. package/packages/ui/src/themes/file-upload.ts +38 -0
  267. package/packages/ui/src/themes/footer-columns.ts +13 -0
  268. package/packages/ui/src/themes/footer.ts +9 -0
  269. package/packages/ui/src/themes/form-field.ts +25 -0
  270. package/packages/ui/src/themes/form.ts +22 -0
  271. package/packages/ui/src/themes/header.ts +12 -0
  272. package/packages/ui/src/themes/icon.ts +61 -0
  273. package/packages/ui/src/themes/index.ts +99 -0
  274. package/packages/ui/src/themes/input-date.ts +73 -0
  275. package/packages/ui/src/themes/input-menu.ts +66 -0
  276. package/packages/ui/src/themes/input-number.ts +58 -0
  277. package/packages/ui/src/themes/input-rating.ts +25 -0
  278. package/packages/ui/src/themes/input-tags.ts +63 -0
  279. package/packages/ui/src/themes/input-time.ts +70 -0
  280. package/packages/ui/src/themes/input.ts +148 -0
  281. package/packages/ui/src/themes/kbd.ts +27 -0
  282. package/packages/ui/src/themes/link.ts +27 -0
  283. package/packages/ui/src/themes/listbox.ts +48 -0
  284. package/packages/ui/src/themes/main.ts +6 -0
  285. package/packages/ui/src/themes/marquee.ts +45 -0
  286. package/packages/ui/src/themes/modal.ts +46 -0
  287. package/packages/ui/src/themes/navigation-menu.ts +80 -0
  288. package/packages/ui/src/themes/page-anchors.ts +7 -0
  289. package/packages/ui/src/themes/page-aside.ts +7 -0
  290. package/packages/ui/src/themes/page-card.ts +22 -0
  291. package/packages/ui/src/themes/page-cta.ts +18 -0
  292. package/packages/ui/src/themes/page-feature.ts +13 -0
  293. package/packages/ui/src/themes/page-header.ts +16 -0
  294. package/packages/ui/src/themes/page-hero.ts +15 -0
  295. package/packages/ui/src/themes/page-links.ts +12 -0
  296. package/packages/ui/src/themes/page-section.ts +14 -0
  297. package/packages/ui/src/themes/page.ts +7 -0
  298. package/packages/ui/src/themes/pagination.ts +17 -0
  299. package/packages/ui/src/themes/pin-input.ts +73 -0
  300. package/packages/ui/src/themes/popover.ts +30 -0
  301. package/packages/ui/src/themes/progress.ts +29 -0
  302. package/packages/ui/src/themes/prose.ts +45 -0
  303. package/packages/ui/src/themes/radio-group.ts +39 -0
  304. package/packages/ui/src/themes/scroll-area.ts +23 -0
  305. package/packages/ui/src/themes/select-menu.ts +71 -0
  306. package/packages/ui/src/themes/select.ts +72 -0
  307. package/packages/ui/src/themes/separator.ts +28 -0
  308. package/packages/ui/src/themes/sidebar.ts +127 -0
  309. package/packages/ui/src/themes/skeleton.ts +15 -0
  310. package/packages/ui/src/themes/slideover.ts +87 -0
  311. package/packages/ui/src/themes/slider.ts +48 -0
  312. package/packages/ui/src/themes/stepper.ts +41 -0
  313. package/packages/ui/src/themes/switch.ts +63 -0
  314. package/packages/ui/src/themes/table.ts +34 -0
  315. package/packages/ui/src/themes/tabs.ts +74 -0
  316. package/packages/ui/src/themes/textarea.ts +77 -0
  317. package/packages/ui/src/themes/timeline.ts +25 -0
  318. package/packages/ui/src/themes/toast.ts +29 -0
  319. package/packages/ui/src/themes/toaster.ts +66 -0
  320. package/packages/ui/src/themes/tooltip.ts +31 -0
  321. package/packages/ui/src/themes/tree.ts +41 -0
  322. package/packages/ui/src/themes/user.ts +28 -0
  323. package/styles.cdn.gen.css +2047 -0
  324. 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;