@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,37 @@
1
+ /**
2
+ * Forest theme fragment — UCommandPalette (searchable command list).
3
+ *
4
+ * Deltas only (the preset appends to stock Nuxt UI):
5
+ * - root — rounded-xl: a soft card boundary at the palette container; stock has no radius
6
+ * (flat edges, since the palette usually lives inside a modal/drawer).
7
+ * - label — font-bold + tracking-wide: group headings get brand confidence; stock is
8
+ * font-semibold with no tracking.
9
+ *
10
+ * The `item` hover/active slab is stock before:rounded-md (12px) — the same shape the sidebar's nav
11
+ * rows carry, since UNavigationMenu's `link` takes no Forest radius delta either. A row is a row on
12
+ * both surfaces.
13
+ *
14
+ * Everything else (density ladder, match-mark highlight [&>mark]:text-primary, focus, close/back
15
+ * icons, transitions) is stock on the Forest ramps. Slot names sourced from:
16
+ * apps/docs/node_modules/.nuxt-ui/ui/command-palette.ts
17
+ */
18
+
19
+ export const commandPaletteTheme = {
20
+ slots: {
21
+ root: 'rounded-xl',
22
+ label: 'font-bold tracking-wide',
23
+ },
24
+
25
+ // Touch: each result row promotes ONE size up under a coarse pointer (borrows the next size's
26
+ // padding / gap / type / icon) — same ladder as the menu family. The search `input` slot already
27
+ // ships explicit h-12+ heights so it needs no bump. xl is the ceiling; deltas only. `coarse:`
28
+ // carries the :not(.cut-fixed) escape so a baked mobile cut opts out.
29
+ variants: {
30
+ size: {
31
+ xs: { item: 'coarse:p-1.5 coarse:gap-1.5', label: 'coarse:p-1.5 coarse:gap-1.5' },
32
+ sm: { item: 'coarse:text-sm', label: 'coarse:text-xs', itemLeadingIcon: 'coarse:size-5', itemTrailingIcon: 'coarse:size-5', itemTrailingHighlightedIcon: 'coarse:size-5' },
33
+ md: { item: 'coarse:p-2 coarse:gap-2', label: 'coarse:p-2 coarse:gap-2' },
34
+ lg: { item: 'coarse:text-base', label: 'coarse:text-sm', itemLeadingIcon: 'coarse:size-6', itemTrailingIcon: 'coarse:size-6', itemTrailingHighlightedIcon: 'coarse:size-6' },
35
+ },
36
+ },
37
+ } as const;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Forest theme fragment for UContainer — deliberately EMPTY.
3
+ *
4
+ * UContainer is the WELL: the only thing in the system that caps page width. Stock's recipe is
5
+ * already correct for Forest — `max-w-(--ui-container) mx-auto px-4 sm:px-6 lg:px-8`, where the cap
6
+ * rides the `--ui-container` lever (80rem) and the gutters land on 2rem at `lg`, which is what the
7
+ * docs shell was already hand-typing. So there is no delta to record, only a review.
8
+ *
9
+ * The three well modes are NOT variants here: UContainer exposes no `width` prop, so a Tailwind
10
+ * Variants variant would have nothing to drive it. They ship instead as utilities that re-point
11
+ * `--ui-container` in scope — `well-reading` / `well-full` / `well-flush` in styles/forest.css.
12
+ */
13
+
14
+ export const containerTheme = {} as const
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Forest theme fragment — UContextMenu (right-click contextual menu).
3
+ *
4
+ * Forest opinions applied:
5
+ * - `item` — before:rounded-sm (8 px) — concentric one step inside the 12 px panel, giving
6
+ * the hover/active slab a natural inset. Stock is before:rounded-md.
7
+ * - `label` — font-bold + tracking-wide gives section headings confident hierarchy over item
8
+ * rows; stock is font-semibold with no tracking. Mirrors the CommandPalette label
9
+ * treatment for visual consistency across Forest menu surfaces.
10
+ *
11
+ * Slots touched: content, item, label.
12
+ * Slot names sourced from: apps/docs/node_modules/.nuxt-ui/ui/context-menu.ts
13
+ */
14
+
15
+ export const contextMenuTheme = {
16
+ slots: {
17
+ content: 'data-[state=open]:animate-[scale-in_var(--motion-base)_var(--motion-ease-out)] data-[state=closed]:animate-[scale-out_var(--motion-fast)_var(--motion-ease-out)]',
18
+
19
+ // Item slab: before:rounded-sm (8 px) — concentric inside the 12 px panel.
20
+ item: 'before:rounded-sm',
21
+
22
+ // Section labels: bold weight + wide tracking — confident group hierarchy, not just slightly-bolder body.
23
+ label: 'font-bold tracking-wide',
24
+ },
25
+
26
+ // Touch: each row promotes ONE size up under a coarse pointer (borrows the next size's padding /
27
+ // gap / type / icon), scaling the tap slab as a unit — same ladder as DropdownMenu and the
28
+ // input/button family. xl is the ceiling; deltas only. `coarse:` carries the :not(.cut-fixed)
29
+ // escape so a baked mobile cut opts out.
30
+ variants: {
31
+ size: {
32
+ xs: { item: 'coarse:p-1.5 coarse:gap-1.5', label: 'coarse:p-1.5 coarse:gap-1.5' },
33
+ sm: { item: 'coarse:text-sm', label: 'coarse:text-sm', itemLeadingIcon: 'coarse:size-5', itemTrailingIcon: 'coarse:size-5' },
34
+ md: { item: 'coarse:p-2 coarse:gap-2', label: 'coarse:p-2 coarse:gap-2' },
35
+ lg: { item: 'coarse:text-base', label: 'coarse:text-base', itemLeadingIcon: 'coarse:size-6', itemTrailingIcon: 'coarse:size-6' },
36
+ },
37
+ },
38
+ } as const;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Forest theme fragment for UDashboardGroup.
3
+ *
4
+ * The group is the muted page shell the inset panels float on: bg-muted in light, the deep
5
+ * neutral-950 in dark (bg-muted collapses into the card surface there — the explicit step keeps
6
+ * shell and card separable). text-default seats the base ink for everything inside.
7
+ */
8
+
9
+ export const dashboardGroupTheme = {
10
+ base: 'bg-muted dark:bg-(--ui-color-neutral-950) text-default',
11
+ } as const
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Forest theme fragment for UDashboardNavbar. No delta: the stock recipe is correct as-is.
3
+ *
4
+ * - Height rides --ui-header-height, the hairline is border-default, the title weight is
5
+ * font-semibold (bold via the two-weight re-map) — all already on Forest tokens.
6
+ * - The docs shell composes its crumb and actions through the navbar's slots; that is usage,
7
+ * not theme.
8
+ */
9
+
10
+ export const dashboardNavbarTheme = {} as const
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Forest theme fragment for UDashboardPanel.
3
+ *
4
+ * The Forest dashboard panel is an INSET CARD, not stock's flat full-bleed column: the panel
5
+ * floats on the group's muted shell with a margin (--forest-panel-inset, which the app shell's bands
6
+ * read too so their edges track the card's), soft corners and the relative hairline
7
+ * (--forest-card-ring, the same translucent edge cards use). `lg:ms-0` hugs the panel against
8
+ * the sidebar rail on desktop so the shell reads as one composition, not two islands.
9
+ * `min-h-0` counters stock's min-h-svh — inside the group's fixed viewport the margins would
10
+ * otherwise push the panel past the bottom edge. `overflow-hidden` clips content to the
11
+ * rounded corners.
12
+ */
13
+
14
+ export const dashboardPanelTheme = {
15
+ slots: {
16
+ root: 'bg-default overflow-hidden m-(--forest-panel-inset) lg:ms-0 rounded-lg ring ring-[var(--forest-card-ring)] min-h-0',
17
+ },
18
+ } as const
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Forest theme fragment for UDashboardResizeHandle (flat component, single `base` slot).
3
+ *
4
+ * A grab pill in the middle of the gutter, on hover and while dragging. Hidden at rest: the gutter is a
5
+ * seam between two surfaces, and anything permanent sitting in it reads as a divider rather than as a
6
+ * control — which is what the full-height hairline this replaces did.
7
+ *
8
+ * Short, and it fades rather than moves: the pill has to say "grab here", not draw the edge.
9
+ *
10
+ * It sits CLEAR of the panel's edge rather than centred on the handle. The handle's own box is zero-wide
11
+ * and lands exactly on the seam, so a centred pill straddles the card's rounded corner and reads as part
12
+ * of it; `right-1` backs it off into the shell where it reads as a separate control.
13
+ *
14
+ * Stock draws nothing and carries `cursor-ew-resize`, `touch-none` and a ±6px `before:` hit area. The
15
+ * paint is added here and the hit area is widened to hold it — see below.
16
+ */
17
+
18
+ export const dashboardResizeHandleTheme = {
19
+ base: [
20
+ // ★ THE PILL HAS TO SIT INSIDE THE ZONE THAT REVEALS IT. Drawn to the shell side of the seam against
21
+ // stock's ±6px area, it fell outside: it appeared where the cursor was not, and vanished on approach.
22
+ // Widened leftwards only — the sidebar's links and its scroller's padding both stop 16px short of the
23
+ // seam and its scrollbar is `width: none`, so this reaches nothing. The right stays at stock.
24
+ 'before:-left-3.5',
25
+ 'after:absolute after:top-1/2 after:right-1 after:-translate-y-1/2',
26
+ 'after:h-16 after:w-1 after:rounded-full after:bg-(--ui-text-dimmed)',
27
+ 'after:opacity-0 after:transition-opacity after:duration-(--motion-fast)',
28
+ // ★ ONE-WAY DWELL. The delay rides the HOVER state only, so the pill waits half a second before it
29
+ // appears and leaves the instant the pointer does — the base carries no delay to revert to. A cursor
30
+ // crossing the seam on its way into the sidebar never summons it. `active` clears the wait so a drag
31
+ // that starts before the pill has arrived still shows what is being dragged.
32
+ 'hover:after:opacity-100 hover:after:delay-(--motion-dwell)',
33
+ 'active:after:opacity-100 active:after:delay-0',
34
+ ].join(' '),
35
+ } as const
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Forest theme fragment for UDashboardSidebarCollapse. No delta: the stock recipe is correct
3
+ * as-is — a desktop-only (hidden lg:flex) neutral ghost button whose panel-open/close icon and
4
+ * behaviour come from the dashboard context.
5
+ */
6
+
7
+ export const dashboardSidebarCollapseTheme = {} as const
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Forest theme fragment for UDashboardSidebarToggle. No delta: the stock recipe is correct
3
+ * as-is — a mobile-only (lg:hidden) neutral ghost button whose menu/close icon and behaviour
4
+ * come from the dashboard context.
5
+ */
6
+
7
+ export const dashboardSidebarToggleTheme = {} as const
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Forest theme fragment for UDashboardSidebar.
3
+ *
4
+ * The sidebar sits directly on the group's muted shell with no divider — the inset panel's own
5
+ * hairline does the separating, so stock's border-e (side=left variant) is zeroed at the same
6
+ * variant path. The rail sizing vars and the mobile slideover are stock and ride the tokens.
7
+ *
8
+ * The rest is the COLLAPSED RAIL, which stock does not size for. These are properties of the
9
+ * component at collapsed-size=4, not of any one app, so they belong here rather than as a `:ui` on
10
+ * each consumer — every app would otherwise rediscover them by eye:
11
+ *
12
+ * · Stock spends 16px a side, leaving 32px of content in a 64px rail — too little for an icon-sized
13
+ * row to be square or centred, and narrower than the 41px logo badge. Halved here.
14
+ * · The scrollbar claims another 11px, offsetting every row and the logo from the rail's centre
15
+ * line; expanded, the same bar draws hard against the inset panel's edge and reads as a seam
16
+ * between two surfaces meant to be one. Hidden in BOTH states: a nav column shows its overflow
17
+ * by fading, not by a bar. Never via ::-webkit-scrollbar, which forces the classic wide
18
+ * bar back — see the scrollbar note in styles/forest.css. The `!` is load-bearing: forest.css
19
+ * sets `scrollbar-width: thin` on `*`, which otherwise wins.
20
+ *
21
+ * `data-collapsed` lives on the ROOT and the padding belongs to its children, so the root carries a
22
+ * `group` for header, body and footer to hang off — a footer carries account chrome, which collapses
23
+ * to an avatar and needs the same centre line as the rows above it. Nothing here touches the resize
24
+ * handle: it is a sibling element and keeps its own hit area, cursor and drag.
25
+ *
26
+ * ── The collapse travel ──
27
+ * Stock ships this component with no transition at all: the root is `w-(--width)` and nothing else, so
28
+ * 16rem → 4rem lands in one frame. USidebar DOES carry a `transition` variant, and themes/sidebar.ts
29
+ * re-points its timing at the motion tokens — but that is a different ui key and a different component,
30
+ * and the app rail never reads it. The width travel is re-declared here so the shell obeys the same
31
+ * doctrine: the largest movement the shell makes, starting and ending in view, so --motion-slow on
32
+ * --motion-ease-in-out rather than the near-element pair.
33
+ *
34
+ * ★ DRAGGING MUST OPT OUT. The resize handle writes `--width` on every pointermove, so a transition
35
+ * left on would run the column 240ms behind the pointer for the whole drag. Stock puts `data-dragging`
36
+ * on the root for exactly this, and the attribute selector out-specifies the bare utility, so the
37
+ * override holds whatever order Tailwind emits the two in.
38
+ *
39
+ * Reduced motion needs no gate here: the floor in the motion tokens re-declares transition-property as
40
+ * an allow-list of colour and opacity properties, and `width` is not in it, so the rail snaps.
41
+ */
42
+
43
+ export const dashboardSidebarTheme = {
44
+ slots: {
45
+ root: [
46
+ 'group transition-[width] duration-(--motion-slow) ease-(--motion-ease-in-out)',
47
+ 'data-[dragging=true]:transition-none',
48
+ ].join(' '),
49
+ header: 'group-data-[collapsed=true]:px-2 group-data-[collapsed=true]:justify-center',
50
+ body: '[scrollbar-width:none]! group-data-[collapsed=true]:px-2',
51
+ footer: 'group-data-[collapsed=true]:px-2 group-data-[collapsed=true]:justify-center',
52
+ },
53
+ variants: {
54
+ side: {
55
+ left: { root: 'border-e-0' },
56
+ },
57
+ },
58
+ } as const
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Forest theme fragment for UDashboardToolbar.
3
+ *
4
+ * One delta: the height. Stock hard-codes min-h-[49px], which tracks no token and sits a pixel proud
5
+ * of the navbar's h-(--ui-header-height). A floor rather than a fixed height, because a wrapping
6
+ * filter bar has to grow.
7
+ *
8
+ * Slots sourced from test/fixture/node_modules/.nuxt-ui/ui/dashboard-toolbar.ts.
9
+ */
10
+
11
+ export const dashboardToolbarTheme = {
12
+ slots: {
13
+ root: 'min-h-(--ui-header-height)',
14
+ },
15
+ } as const
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Forest theme fragment — UDrawer (drag-dismissable edge sheet).
3
+ *
4
+ * Deltas only (the preset APPENDS to stock Nuxt UI); slots touched: title, content radius.
5
+ *
6
+ * content radius — rounder edge corners than stock's rounded-*-lg. CRITICAL: stock sets the edge
7
+ * radius in COMPOUND VARIANTS (per direction + inset, e.g. `{direction:bottom, inset:false}
8
+ * → content: rounded-t-lg`), which OUTRANK a base `slots.content` class in tv. A flat
9
+ * `slots: { content: 'rounded-t-xl' }` was silently overridden (rendered rounded-t-lg).
10
+ * So the override must live in matching compoundVariants — appended after stock's, they win
11
+ * the merge. rounded-*-2xl (32px) on the four non-inset directions; the inset (floating)
12
+ * cut steps stock's rounded-lg up to rounded-xl.
13
+ *
14
+ * handle — untouched: stock's neutral `!bg-accented` affordance. Only mobile re-colours it (warm,
15
+ * to sit on that context's peach surface — mobile-theme.ts). NOTE for anyone overriding
16
+ * it: stock's class is IMPORTANT and tw-merge keeps an important class alongside a plain
17
+ * one of the same bucket, so an override has to be important too.
18
+ *
19
+ * title — text-base + tracking-tight, added to stock's font-semibold. Polishes the built-in
20
+ * `title`-prop path; the #header slot (used in the docs demos) is free-form.
21
+ *
22
+ * Recipe shape sourced from apps/docs/node_modules/.nuxt-ui/ui/drawer.ts (radius = compoundVariants).
23
+ */
24
+
25
+ export const drawerTheme = {
26
+ slots: {
27
+ title: 'text-base tracking-tight',
28
+ },
29
+ variants: {
30
+ // Inset (floating) cut rounds all four corners; read the same trait var.
31
+ inset: { true: { content: 'rounded-[var(--forest-drawer-radius)]' } },
32
+ },
33
+ // Edge radius via the --forest-drawer-radius TRAIT (forest.css :root = 1.5rem / xl, standard). The
34
+ // radius MUST live in compoundVariants — stock sets it there per direction+inset, beating any base
35
+ // slot class. But a per-CONTEXT override can't add compoundVariants (context UTheme merges only
36
+ // slots, and those lose to compoundVariants anyway). So the compoundVariant reads a VAR, and mobile/
37
+ // marketing re-point that var with a `[--forest-drawer-radius:2rem]` slot class — which rides the
38
+ // portal and never collides in tw-merge. Same trick as --fx-title-* on the card title.
39
+ compoundVariants: [
40
+ { direction: 'bottom', inset: false, class: { content: 'rounded-t-[var(--forest-drawer-radius)]' } },
41
+ { direction: 'top', inset: false, class: { content: 'rounded-b-[var(--forest-drawer-radius)]' } },
42
+ { direction: 'left', inset: false, class: { content: 'rounded-r-[var(--forest-drawer-radius)]' } },
43
+ { direction: 'right', inset: false, class: { content: 'rounded-l-[var(--forest-drawer-radius)]' } },
44
+ ],
45
+ } as const;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Forest theme fragment — UDropdownMenu (action menu, groups + nesting).
3
+ *
4
+ * Forest opinions applied:
5
+ * - `item` — before:rounded-sm (8 px) — concentric one step inside the 12 px panel, giving the
6
+ * hover/active slab a natural inset. Stock is before:rounded-md.
7
+ * - `label` — font-bold tracking-wide (group + section headings read with brand confidence;
8
+ * stock is font-semibold with no tracking).
9
+ *
10
+ * Slots touched: content · item · label
11
+ * Slot names sourced from: apps/docs/node_modules/.nuxt-ui/ui/dropdown-menu.ts
12
+ */
13
+
14
+ export const dropdownMenuTheme = {
15
+ slots: {
16
+ content: 'data-[state=open]:animate-[scale-in_var(--motion-base)_var(--motion-ease-out)] data-[state=closed]:animate-[scale-out_var(--motion-fast)_var(--motion-ease-out)]',
17
+
18
+ // Item slab: before:rounded-sm (8 px) — concentric inside the 12 px panel.
19
+ item: 'before:rounded-sm',
20
+
21
+ // Group headings: bold weight + wide tracking — confident, readable at small size.
22
+ label: 'font-bold tracking-wide',
23
+ },
24
+
25
+ // Touch: under a coarse pointer each row promotes ONE size up — it borrows the next size's
26
+ // padding / gap / type / icon, so the tap slab scales as a unit (mirrors the input/button ladder).
27
+ // xl is the ceiling. Deltas only: each size lists just the tokens that differ from its next step
28
+ // up (e.g. sm→md changes only type + icon, so no padding delta). `coarse:` carries the
29
+ // :not(.cut-fixed) escape, so a baked mobile cut opts out instead of promoting again.
30
+ variants: {
31
+ size: {
32
+ xs: { item: 'coarse:p-1.5 coarse:gap-1.5', label: 'coarse:p-1.5 coarse:gap-1.5' },
33
+ sm: { item: 'coarse:text-sm', label: 'coarse:text-sm', itemLeadingIcon: 'coarse:size-5', itemTrailingIcon: 'coarse:size-5' },
34
+ md: { item: 'coarse:p-2 coarse:gap-2', label: 'coarse:p-2 coarse:gap-2' },
35
+ lg: { item: 'coarse:text-base', label: 'coarse:text-base', itemLeadingIcon: 'coarse:size-6', itemTrailingIcon: 'coarse:size-6' },
36
+ },
37
+ },
38
+ } as const;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Forest theme fragment — UEmpty (empty-state placeholder).
3
+ *
4
+ * Forest opinions applied:
5
+ * - `root`: radius bumped to rounded-2xl for the spacious, card-like Forest feel; padding
6
+ * stepped up to p-8 sm:p-10 lg:p-12 to give the empty state breathing room — it is the
7
+ * centrepiece of a blank surface, not a tucked-away widget.
8
+ * - `title`: font-display (MohrAlt — always uppercase + black weight) to carry
9
+ * the brand voice; the display cut renders all-caps naturally so no text-transform needed.
10
+ * Bumped two steps up per size (stock caps the title at text-sm..text-lg, undersized for a
11
+ * display face) so the MohrAlt title reads with presence: xs/sm text-lg, md/lg text-xl,
12
+ * xl text-2xl. Overridden on the SIZE VARIANT (stock sets title size there, so a plain base
13
+ * class would lose the ordering race — see the per-size-radius-hook rule).
14
+ * - `description`: max-w-xs tightens the measure to ~12 words per line for comfortable reading in
15
+ * the centred layout. The colour is deliberately NOT set here — stock already assigns the right
16
+ * per-variant description colour (outline text-muted, soft/subtle text-toned, solid text-dimmed
17
+ * so it stays legible on the inverted surface), and the variant always wins the merge, so a
18
+ * slot-base colour here would just be dead weight. Leave it to stock.
19
+ * - `actions`: gap-3 (12 px) — a touch more breath between CTA buttons than the stock gap-2.
20
+ *
21
+ * Slots touched: root, title, description, actions.
22
+ * Slot names sourced from: apps/docs/node_modules/.nuxt-ui/ui/empty.ts
23
+ */
24
+
25
+ export const emptyTheme = {
26
+ slots: {
27
+ root: 'rounded-2xl p-8 sm:p-10 lg:p-12',
28
+ title: 'font-display',
29
+ description: 'max-w-xs',
30
+ actions: 'gap-3',
31
+ },
32
+ variants: {
33
+ // One step up from stock (xs/sm text-sm, md/lg text-base, xl text-lg) so the display title
34
+ // carries presence. Set on the size variant because stock puts the title size here.
35
+ size: {
36
+ xs: { title: 'text-lg' },
37
+ sm: { title: 'text-lg' },
38
+ md: { title: 'text-xl' },
39
+ lg: { title: 'text-xl' },
40
+ xl: { title: 'text-2xl' },
41
+ },
42
+ },
43
+ } as const;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Forest theme fragment — UFieldGroup (joined input/button cluster).
3
+ *
4
+ * Forest opinions applied:
5
+ * - `base`: radius set to `rounded-lg` (0.5 rem, matching --ui-radius) so the outer corners of
6
+ * the joined bar read with the same soft radius used across Forest controls (input, button,
7
+ * card). Stock Nuxt UI's FieldGroup has no explicit radius on the wrapper — the outer corners
8
+ * are therefore whatever the first/last child uses. Pinning `rounded-lg` on the wrapper ensures
9
+ * the group's clip boundary matches the brand standard even when children vary.
10
+ *
11
+ * STRUCTURE (do not wrap in `slots`): UFieldGroup is a SINGLE-SLOT component — its stock theme is
12
+ * FLAT (`{ base: 'relative', variants: { size, orientation } }`), where `base` is the root's class
13
+ * string and `variants.orientation.horizontal` carries the load-bearing `inline-flex -space-x-px`
14
+ * (the 1px negative gap that overlaps children so adjacent inset rings/borders collapse to one).
15
+ * A previous version wrapped this in `{ slots: { base } }` (multi-slot shape) — that mismatched the
16
+ * flat recipe, broke tv() resolution (root rendered literal class "base", display:block), and DROPPED
17
+ * the orientation variant → no overlap → ring variants (subtle/outline) double-bordered at every join.
18
+ * Keep this fragment FLAT so the top-level `base` merges and the orientation/size variants survive.
19
+ *
20
+ * With the overlap intact, inner-corner stripping + ring collapse are handled automatically by Nuxt
21
+ * (fieldGroup variant on each child + `-space-x-px` here), so ring variants join cleanly. The one
22
+ * caveat is the KEYCAP lip variants (keycap/pad/framed): their ::before lip slab reads lumpy when
23
+ * segments butt together — for a segmented control prefer solid (active) + soft/subtle (inactive).
24
+ *
25
+ * Slot sourced from: apps/docs/node_modules/.nuxt-ui/ui/field-group.ts
26
+ */
27
+
28
+ export const fieldGroupTheme = {
29
+ // FLAT top-level `base` (single-slot component) — merges with stock's `relative` base and keeps the
30
+ // size/orientation variants (incl. the load-bearing `-space-x-px` overlap). rounded-md = the joined
31
+ // bar's outer corner; the wrapper itself paints nothing, so the VISIBLE corner is the child's —
32
+ // capped to the same value by the orientation variants below.
33
+ base: 'rounded-md',
34
+
35
+ // OUTER-CORNER CAP. A joined bar's end segments must not keep their own standalone corner: the
36
+ // mobile cut gives every button `rounded-full`, which lands after the field-group corner-strip and
37
+ // domed the bar's ends (a 36px segment clamped to an 18px half-round — overshooting the group's own
38
+ // radius). A segmented bar is one slab, not a row of pills, so the group pins its end corners to
39
+ // rounded-md in EVERY context.
40
+ //
41
+ // `[&>*:first-child]` is (0,2,0), so it beats a child's plain `rounded-full`/`rounded-lg` (0,1,0),
42
+ // while Nuxt's inner-corner strip (`not-only:first:rounded-e-none`, higher specificity still) keeps
43
+ // stripping the shared edges. Flat STRINGS here to match stock's orientation variant shape — a
44
+ // `{ base: … }` object would mismatch the flat recipe and drop the overlap (the 2026-07-17 bug).
45
+ // Trade-off: inside a group the per-size radius ramp flattens to one corner, which is the point.
46
+ variants: {
47
+ orientation: {
48
+ horizontal: '[&>*:first-child]:rounded-s-md [&>*:last-child]:rounded-e-md',
49
+ vertical: '[&>*:first-child]:rounded-t-md [&>*:last-child]:rounded-b-md',
50
+ },
51
+ },
52
+ } as const;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Forest theme fragment — UFileUpload (drop area / button upload).
3
+ *
4
+ * Deltas only — every class here overrides stock and actually lands (verified against the merged
5
+ * tv() output). Forest opinions:
6
+ * - `base`: radius lifted from stock `rounded-lg` to `rounded-xl` (the Forest card tier, 24 px on
7
+ * the scaled --ui-radius) so the drop-zone shell reads at the card/modal level, not the tighter
8
+ * input level. The transition is widened
9
+ * to include `border-color` (on the Forest `--motion-fast` duration) so the drag-highlight
10
+ * border animates in smoothly alongside the background.
11
+ * - `label`: `font-bold` — a step above stock `font-medium`, giving the call-to-action label a
12
+ * confident hierarchy over the description.
13
+ *
14
+ * Deliberately NOT set (would be inert — stock overrides them in a later-merged variant, so the
15
+ * class is silently dropped by tailwind-merge; documenting the trap so nobody re-adds them):
16
+ * - a `focus-visible:outline-*` on base is clobbered by the per-colour compoundVariant
17
+ * (`focus-visible:outline-3`); the focus ring stays stock.
18
+ * - an `icon` size on the base slot is clobbered by the size variant (`size-4/5/6`); a bigger
19
+ * empty-state glyph would need per-size `variants.size.{…}.icon` overrides.
20
+ * - a `file` radius on the base slot is clobbered by `variants.layout.list.file` (`rounded-md`);
21
+ * softer list rows would need to override that layout variant.
22
+ * - `fileLeadingAvatar` `rounded-lg` in the grid layout is already stock — nothing to add.
23
+ *
24
+ * Focus rings and the drag-highlight border colour are Nuxt UI compoundVariants keyed on the
25
+ * semantic colour vars (`--ui-color-<family>-*`), wired to the Forest OKLCH ramps in docs.css —
26
+ * they resolve to the correct brand values with no Forest override.
27
+ */
28
+
29
+ export const fileUploadTheme = {
30
+ slots: {
31
+ // rounded-xl — Forest card/modal radius (24 px scaled; stock base is rounded-lg). border-color added
32
+ // to the transition so the drag-highlight border animates on the Forest fast duration.
33
+ base: 'rounded-xl transition-[background,border-color] duration-(--motion-fast)',
34
+
35
+ // font-bold — lifts the call-to-action above stock font-medium.
36
+ label: 'font-bold',
37
+ },
38
+ } as const;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Forest theme fragment for UFooterColumns.
3
+ *
4
+ * One delta: the column label is `text-sm font-semibold`, a size off Tailwind's default scale. A
5
+ * footer column heading is an overline in Forest's vocabulary — small, spaced, quiet — so it takes
6
+ * `.type-overline`, which carries size, tracking and case together.
7
+ */
8
+
9
+ export const footerColumnsTheme = {
10
+ slots: {
11
+ label: 'type-overline',
12
+ },
13
+ };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Reviewed, no delta. UFooter is pure layout — every slot is flex/grid/spacing and it sets no type,
3
+ * colour or radius of its own. The type that appears in a footer comes from UFooterColumns.
4
+ *
5
+ * ★ It DOES cap its own width: the container slot embeds a UContainer, so a footer must not be
6
+ * nested inside a well. Registered on /layout/well.
7
+ */
8
+
9
+ export const footerTheme = {};
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Forest theme fragment — UFormField (field layout: label, help, error).
3
+ *
4
+ * Forest opinions applied:
5
+ *
6
+ * `label`:
7
+ * Lifted from stock `font-medium` to `font-bold` — clearly above the field content,
8
+ * giving the label a clear hierarchy. Matches the legend treatment in checkboxGroupTheme /
9
+ * radioGroupTheme.
10
+ *
11
+ * Everything else is stock. Text size belongs to `variants.size`, which drives the `root` slot and
12
+ * lets the secondary slots (description, error, hint, help) inherit from it — a flat `text-*` here
13
+ * would pin one rung of a five-rung ladder. The secondary slots keep stock's `text-muted`, so the
14
+ * label carries the hierarchy on weight alone.
15
+ *
16
+ * Slots touched: label.
17
+ * Slot names sourced from: apps/docs/node_modules/.nuxt-ui/ui/form-field.ts
18
+ */
19
+
20
+ export const formFieldTheme = {
21
+ slots: {
22
+ // font-bold — clearly above the field content.
23
+ label: 'font-bold',
24
+ },
25
+ } as const;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Forest theme fragment — UForm + UFormField (validation wrapper + field layout).
3
+ *
4
+ * Forest opinions applied:
5
+ *
6
+ * UForm (`base`):
7
+ * No visual chrome of its own — UForm is a pure behaviour wrapper (validation, loading state,
8
+ * submit gate). The only Forest opinion is a comfortable `space-y-5` vertical rhythm between
9
+ * FormFields, replacing the stock empty string. This gives Forest forms a consistent,
10
+ * breathing density without each page needing to repeat the spacing utility.
11
+ *
12
+ * Slots touched (form): base.
13
+ * Slot names sourced from: apps/docs/node_modules/.nuxt-ui/ui/form.ts
14
+ *
15
+ * The UFormField opinions (label, description, error, hint) live in their own sibling fragment
16
+ * (form-field.ts), matching the one-file-per-component convention (cf. radio-group.ts).
17
+ */
18
+
19
+ export const formTheme = {
20
+ // space-y-5: comfortable Forest density between FormFields (stock: empty string — no spacing).
21
+ base: 'space-y-5',
22
+ } as const;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Forest theme fragment for UHeader.
3
+ *
4
+ * One delta: the title takes `text-xl` off Tailwind's default scale. Retyped onto `.type-title` so
5
+ * the site wordmark sits on Forest's ladder rather than a parallel one.
6
+ */
7
+
8
+ export const headerTheme = {
9
+ slots: {
10
+ title: 'type-title',
11
+ },
12
+ };
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Forest theme fragment — UIcon
3
+ *
4
+ * UIcon is a thin pass-through to @nuxt/icon — it has no Tailwind Variants theme slots,
5
+ * no compound variants, and no class-generating machinery of its own. There is nothing to
6
+ * override in the Tailwind Variants sense.
7
+ *
8
+ * The Forest opinion for icons therefore lives entirely at the USAGE layer:
9
+ *
10
+ * Sizing — always use Tailwind `size-*` utilities, never the `:size` prop.
11
+ * The `:size` prop inlines a `font-size` style which misaligns SVG icons in
12
+ * flex/grid contexts. `size-*` sets both `width` and `height` as layout properties.
13
+ *
14
+ * size-3 (12 px) — inline annotation, caption badge icon
15
+ * size-3.5 (14 px) — UBadge leading/trailing icon slot
16
+ * size-4 (16 px) — UButton icon · sidebar nav item ← default
17
+ * size-5 (20 px) — list row · prominent inline icon
18
+ * size-6 (24 px) — card icon · empty-state accent block
19
+ * size-8 (32 px) — avatar-scale accent
20
+ * size-10 (40 px) — hero / illustration icon
21
+ *
22
+ * Colour — always use semantic `text-*` classes so dark mode adapts automatically.
23
+ * Never use raw palette classes (text-forest-500, text-maple-600, …) on icons.
24
+ *
25
+ * text-primary — brand accent (forest)
26
+ * text-success — active / healthy states
27
+ * text-warning — low battery / caution
28
+ * text-error — fault / critical
29
+ * text-info — location / informational
30
+ * text-muted — secondary / inactive
31
+ * text-dimmed — decorative / lowest emphasis
32
+ * text-highlighted — high-contrast foreground
33
+ *
34
+ * Flex rows — always add `shrink-0` when UIcon sits beside text in a flex row;
35
+ * omitting it causes the icon to be squeezed on narrow viewports.
36
+ *
37
+ * Icon family — two prefixes, one drawing contract. `i-lucide-*` is the base set
38
+ * (@iconify-json/lucide) and resolves over the Iconify API. `i-forest-*` is
39
+ * Forest's own (packages/ui/icons/forest, packed by scripts/icons.ts) and must be
40
+ * registered from the bundle with addCollection() — there is no API behind it.
41
+ * Draw a Forest icon only where Lucide's glyph would misstate the operation, and
42
+ * draw it to Lucide's conventions: 24×24, fill none, stroke currentColor,
43
+ * stroke-width 2, round caps and joins. Full doctrine: /foundations/icons.
44
+ *
45
+ * Accessibility — decorative icons (beside a label): add `aria-hidden="true"`.
46
+ * Standalone icon controls: use <UButton icon="…" aria-label="…" />.
47
+ * Meaningful icons without visible label: add <span class="sr-only">.
48
+ *
49
+ * Render mode — keep the default mode="svg" for crisp rendering at all pixel densities;
50
+ * do not switch to mode="css" in Forest UIs.
51
+ *
52
+ * Slots touched: none — UIcon has no Tailwind Variants theme.
53
+ *
54
+ * This file exists as a Forest convention record and a named export so the theme registry
55
+ * can include it without breaking the import graph. The exported object is intentionally
56
+ * empty; the real Forest opinion is documented above and enforced at the usage layer.
57
+ */
58
+ export const iconTheme = {
59
+ // UIcon has no Tailwind Variants slots.
60
+ // Forest opinions are usage conventions documented in the comment above.
61
+ } as const;