@humanforest/ui 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 (235) hide show
  1. package/LICENSE +67 -0
  2. package/icons/forest/bike-asterisk.svg +21 -0
  3. package/icons/forest/bike-down.svg +19 -0
  4. package/icons/forest/bike-off.svg +20 -0
  5. package/icons/forest/bike-up.svg +19 -0
  6. package/icons/forest/bike-x.svg +19 -0
  7. package/icons/forest/bike.svg +18 -0
  8. package/icons/forest/map/forest-place-count.svg +21 -0
  9. package/icons/forest/map/forest-place-marker.svg +17 -0
  10. package/icons/forest/map/forest-vehicle-marker.svg +73 -0
  11. package/icons/forest/map/manifest.json +373 -0
  12. package/icons/forest/map/place-count-manifest.json +83 -0
  13. package/icons/forest/map/place-manifest.json +74 -0
  14. package/icons/forest/mark-fill.svg +6 -0
  15. package/icons/forest/mark.svg +6 -0
  16. package/icons/forest/parking.svg +3 -0
  17. package/icons/forest/star-fill.svg +4 -0
  18. package/icons/forest/star.svg +4 -0
  19. package/icons/forest/traffic-light-caution.svg +8 -0
  20. package/icons/forest/traffic-light-go.svg +8 -0
  21. package/icons/forest/traffic-light-lit.svg +8 -0
  22. package/icons/forest/traffic-light-stop.svg +8 -0
  23. package/icons/forest/traffic-light.svg +8 -0
  24. package/package.json +64 -0
  25. package/src/address/address.ts +125 -0
  26. package/src/address/autofill.ts +78 -0
  27. package/src/address/geocode.ts +55 -0
  28. package/src/address/index.ts +31 -0
  29. package/src/address/validate.ts +91 -0
  30. package/src/canvas/FCanvasCard.vue +97 -0
  31. package/src/canvas/FCanvasControl.vue +121 -0
  32. package/src/canvas/FCanvasControlGroup.vue +100 -0
  33. package/src/canvas/FCanvasFlyout.vue +216 -0
  34. package/src/canvas/FCanvasHud.vue +402 -0
  35. package/src/canvas/canvasSurface.ts +39 -0
  36. package/src/canvas/index.ts +17 -0
  37. package/src/contexts/ForestContext.vue +53 -0
  38. package/src/contexts/contexts.ts +153 -0
  39. package/src/contexts/index.ts +23 -0
  40. package/src/contexts/marketing-theme.ts +214 -0
  41. package/src/contexts/mobile-theme.ts +402 -0
  42. package/src/filter/FFilterBar.vue +72 -0
  43. package/src/filter/FFilterChip.vue +362 -0
  44. package/src/filter/FFilterClauses.vue +104 -0
  45. package/src/filter/FFilterTrigger.vue +183 -0
  46. package/src/filter/filter.theme.ts +27 -0
  47. package/src/filter/filterClause.ts +265 -0
  48. package/src/filter/filterPredicate.ts +134 -0
  49. package/src/filter/filterSchema.ts +82 -0
  50. package/src/filter/index.ts +36 -0
  51. package/src/forest-preset.ts +68 -0
  52. package/src/icons.ts +79 -0
  53. package/src/index.ts +17 -0
  54. package/src/kpi/FKpi.vue +253 -0
  55. package/src/kpi/FKpiGroup.vue +161 -0
  56. package/src/kpi/index.ts +20 -0
  57. package/src/kpi/kpi.theme.ts +168 -0
  58. package/src/kpi/kpiDelta.ts +82 -0
  59. package/src/kpi/kpiFormat.ts +72 -0
  60. package/src/kpi/kpiGroupState.ts +63 -0
  61. package/src/logo/FLogo.vue +52 -0
  62. package/src/logo/FSubLogo.vue +214 -0
  63. package/src/logo/index.ts +8 -0
  64. package/src/map/FMap.vue +355 -0
  65. package/src/map/FMapCompass.vue +154 -0
  66. package/src/map/FMapCoordinates.vue +134 -0
  67. package/src/map/FMapDrawTools.vue +116 -0
  68. package/src/map/FMapFullscreen.vue +92 -0
  69. package/src/map/FMapGeolocate.vue +208 -0
  70. package/src/map/FMapLayers.vue +75 -0
  71. package/src/map/FMapLegend.vue +45 -0
  72. package/src/map/FMapPopup.vue +129 -0
  73. package/src/map/FMapRamp.vue +69 -0
  74. package/src/map/FMapRegion.vue +77 -0
  75. package/src/map/FMapScale.vue +107 -0
  76. package/src/map/FMapSearch.vue +362 -0
  77. package/src/map/FMapTilt.vue +87 -0
  78. package/src/map/FMapTools.vue +61 -0
  79. package/src/map/FMapZoom.vue +81 -0
  80. package/src/map/FMarker.vue +218 -0
  81. package/src/map/FTrackScrubber.vue +94 -0
  82. package/src/map/FVehicleLegend.vue +72 -0
  83. package/src/map/FVehicleMark.vue +166 -0
  84. package/src/map/bin.ts +196 -0
  85. package/src/map/coordinates.ts +83 -0
  86. package/src/map/density.ts +401 -0
  87. package/src/map/draw.ts +356 -0
  88. package/src/map/ease.ts +67 -0
  89. package/src/map/fullscreen.ts +59 -0
  90. package/src/map/heat.ts +160 -0
  91. package/src/map/index.ts +351 -0
  92. package/src/map/liveMarkers.ts +60 -0
  93. package/src/map/map.css +116 -0
  94. package/src/map/mapColor.ts +31 -0
  95. package/src/map/mapContext.ts +12 -0
  96. package/src/map/mapLayers.ts +55 -0
  97. package/src/map/mapLegend.ts +70 -0
  98. package/src/map/mapSearch.theme.ts +79 -0
  99. package/src/map/mapSearch.ts +279 -0
  100. package/src/map/mapUrl.ts +117 -0
  101. package/src/map/markerAsset.ts +3 -0
  102. package/src/map/padding.ts +39 -0
  103. package/src/map/place.ts +192 -0
  104. package/src/map/ramp.ts +95 -0
  105. package/src/map/regionSelect.ts +163 -0
  106. package/src/map/route.ts +167 -0
  107. package/src/map/scale.ts +329 -0
  108. package/src/map/scaleBar.ts +67 -0
  109. package/src/map/selectionModel.ts +50 -0
  110. package/src/map/sources/coordinates.ts +58 -0
  111. package/src/map/sources/mapboxPlaces.ts +71 -0
  112. package/src/map/style.ts +14 -0
  113. package/src/map/track.ts +279 -0
  114. package/src/map/useFeatureSelect.ts +230 -0
  115. package/src/map/useLiveMarkers.ts +84 -0
  116. package/src/map/useMap.ts +150 -0
  117. package/src/map/useMapDraw.ts +776 -0
  118. package/src/map/useMapOverlay.ts +421 -0
  119. package/src/map/useMapTableSelection.ts +101 -0
  120. package/src/map/useMapUrlState.ts +155 -0
  121. package/src/map/useMarkerSource.ts +163 -0
  122. package/src/map/useRegionSelect.ts +309 -0
  123. package/src/map/useTokenMode.ts +61 -0
  124. package/src/map/useTrackPlayback.ts +131 -0
  125. package/src/map/useVehicleFilter.ts +51 -0
  126. package/src/map/useVisibleFeatures.ts +124 -0
  127. package/src/map/vehicle.ts +807 -0
  128. package/src/map/vehicleFilter.ts +284 -0
  129. package/src/map/vehicleLegend.ts +155 -0
  130. package/src/map/visibleFeatures.ts +192 -0
  131. package/src/map/zone.ts +291 -0
  132. package/src/shell/FAppNavbar.vue +68 -0
  133. package/src/shell/FAppShell.vue +163 -0
  134. package/src/shell/FAppSidebar.vue +215 -0
  135. package/src/shell/FPageToolbar.vue +126 -0
  136. package/src/shell/FScrollShadow.vue +43 -0
  137. package/src/shell/FSidePanel.vue +258 -0
  138. package/src/shell/FToolbarActions.vue +32 -0
  139. package/src/shell/index.ts +33 -0
  140. package/src/shell/nav.ts +37 -0
  141. package/src/shell/pageToolbar.ts +69 -0
  142. package/src/shell/shell.ts +74 -0
  143. package/src/shell/sidePanel.ts +115 -0
  144. package/src/styles/forest.css +852 -0
  145. package/src/styles/index.css +39 -0
  146. package/src/themes/accordion.ts +36 -0
  147. package/src/themes/alert.ts +16 -0
  148. package/src/themes/auth-form.ts +19 -0
  149. package/src/themes/avatar-group.ts +9 -0
  150. package/src/themes/avatar.ts +18 -0
  151. package/src/themes/badge.ts +51 -0
  152. package/src/themes/banner.ts +21 -0
  153. package/src/themes/breadcrumb.ts +10 -0
  154. package/src/themes/button.mechanics.ts +191 -0
  155. package/src/themes/button.ts +136 -0
  156. package/src/themes/calendar.ts +29 -0
  157. package/src/themes/card.ts +67 -0
  158. package/src/themes/carousel.ts +41 -0
  159. package/src/themes/checkbox-group.ts +20 -0
  160. package/src/themes/checkbox.ts +47 -0
  161. package/src/themes/chip.ts +50 -0
  162. package/src/themes/collapsible.ts +26 -0
  163. package/src/themes/color-picker.ts +13 -0
  164. package/src/themes/command-palette.ts +37 -0
  165. package/src/themes/container.ts +14 -0
  166. package/src/themes/context-menu.ts +38 -0
  167. package/src/themes/dashboard-group.ts +11 -0
  168. package/src/themes/dashboard-navbar.ts +10 -0
  169. package/src/themes/dashboard-panel.ts +18 -0
  170. package/src/themes/dashboard-resize-handle.ts +35 -0
  171. package/src/themes/dashboard-sidebar-collapse.ts +7 -0
  172. package/src/themes/dashboard-sidebar-toggle.ts +7 -0
  173. package/src/themes/dashboard-sidebar.ts +58 -0
  174. package/src/themes/dashboard-toolbar.ts +15 -0
  175. package/src/themes/drawer.ts +45 -0
  176. package/src/themes/dropdown-menu.ts +38 -0
  177. package/src/themes/empty.ts +43 -0
  178. package/src/themes/field-group.ts +52 -0
  179. package/src/themes/file-upload.ts +38 -0
  180. package/src/themes/footer-columns.ts +13 -0
  181. package/src/themes/footer.ts +9 -0
  182. package/src/themes/form-field.ts +25 -0
  183. package/src/themes/form.ts +22 -0
  184. package/src/themes/header.ts +12 -0
  185. package/src/themes/icon.ts +61 -0
  186. package/src/themes/index.ts +99 -0
  187. package/src/themes/input-date.ts +73 -0
  188. package/src/themes/input-menu.ts +66 -0
  189. package/src/themes/input-number.ts +58 -0
  190. package/src/themes/input-rating.ts +25 -0
  191. package/src/themes/input-tags.ts +63 -0
  192. package/src/themes/input-time.ts +70 -0
  193. package/src/themes/input.ts +148 -0
  194. package/src/themes/kbd.ts +27 -0
  195. package/src/themes/link.ts +27 -0
  196. package/src/themes/listbox.ts +48 -0
  197. package/src/themes/main.ts +6 -0
  198. package/src/themes/marquee.ts +45 -0
  199. package/src/themes/modal.ts +46 -0
  200. package/src/themes/navigation-menu.ts +80 -0
  201. package/src/themes/page-anchors.ts +7 -0
  202. package/src/themes/page-aside.ts +7 -0
  203. package/src/themes/page-card.ts +22 -0
  204. package/src/themes/page-cta.ts +18 -0
  205. package/src/themes/page-feature.ts +13 -0
  206. package/src/themes/page-header.ts +16 -0
  207. package/src/themes/page-hero.ts +15 -0
  208. package/src/themes/page-links.ts +12 -0
  209. package/src/themes/page-section.ts +14 -0
  210. package/src/themes/page.ts +7 -0
  211. package/src/themes/pagination.ts +17 -0
  212. package/src/themes/pin-input.ts +73 -0
  213. package/src/themes/popover.ts +30 -0
  214. package/src/themes/progress.ts +29 -0
  215. package/src/themes/prose.ts +45 -0
  216. package/src/themes/radio-group.ts +39 -0
  217. package/src/themes/scroll-area.ts +23 -0
  218. package/src/themes/select-menu.ts +71 -0
  219. package/src/themes/select.ts +72 -0
  220. package/src/themes/separator.ts +28 -0
  221. package/src/themes/sidebar.ts +127 -0
  222. package/src/themes/skeleton.ts +15 -0
  223. package/src/themes/slideover.ts +87 -0
  224. package/src/themes/slider.ts +48 -0
  225. package/src/themes/stepper.ts +41 -0
  226. package/src/themes/switch.ts +63 -0
  227. package/src/themes/table.ts +34 -0
  228. package/src/themes/tabs.ts +74 -0
  229. package/src/themes/textarea.ts +77 -0
  230. package/src/themes/timeline.ts +25 -0
  231. package/src/themes/toast.ts +29 -0
  232. package/src/themes/toaster.ts +66 -0
  233. package/src/themes/tooltip.ts +31 -0
  234. package/src/themes/tree.ts +41 -0
  235. package/src/themes/user.ts +28 -0
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Forest theme fragment for UPageFeature.
3
+ *
4
+ * `text-[15px]` on the description is an arbitrary value — not a step on any scale, Tailwind's or
5
+ * Forest's. Both slots move onto the ladder.
6
+ */
7
+
8
+ export const pageFeatureTheme = {
9
+ slots: {
10
+ title: 'type-heading-xs',
11
+ description: 'type-body',
12
+ },
13
+ };
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Forest theme fragment for UPageHeader.
3
+ *
4
+ * Stock builds a whole type ladder of its own — `text-3xl sm:text-4xl` for the title, `text-lg` for
5
+ * the description, `text-sm font-semibold` for the headline. Forest already has those three steps
6
+ * named, and a second ladder drifts the moment the real one moves. Retyped onto the ladder; the
7
+ * responsive step is dropped with it, because `.type-display` owns its own scaling.
8
+ */
9
+
10
+ export const pageHeaderTheme = {
11
+ slots: {
12
+ headline: 'type-overline',
13
+ title: 'type-display',
14
+ description: 'type-body',
15
+ },
16
+ };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Forest theme fragment for UPageHero.
3
+ *
4
+ * `text-5xl sm:text-7xl` is the largest of stock's off-ladder sizes and the one most likely to be
5
+ * noticed — a hero title two steps above anything Forest publishes. `.type-display` is the top of the
6
+ * ladder and is where a hero belongs; a louder treatment is the marketing context's job, not a
7
+ * bigger literal.
8
+ */
9
+
10
+ export const pageHeroTheme = {
11
+ slots: {
12
+ title: 'type-display',
13
+ description: 'type-subtitle',
14
+ },
15
+ };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Forest theme fragment for UPageLinks.
3
+ *
4
+ * One delta: the group title is `text-sm font-semibold`, off-ladder. `.type-label` is the step for a
5
+ * small heading over a list of controls.
6
+ */
7
+
8
+ export const pageLinksTheme = {
9
+ slots: {
10
+ title: 'type-label',
11
+ },
12
+ };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Forest theme fragment for UPageSection.
3
+ *
4
+ * Stock steps `text-3xl sm:text-4xl lg:text-5xl` across three breakpoints. Forest's ladder handles
5
+ * its own scaling, so the section title takes `.type-headline` — one step below the page title, which
6
+ * is the relationship a section heading should have to the page it sits in.
7
+ */
8
+
9
+ export const pageSectionTheme = {
10
+ slots: {
11
+ title: 'type-headline',
12
+ description: 'type-body',
13
+ },
14
+ };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Reviewed, no delta. UPage and its structural children (body, grid, columns, list) are flex/grid and
3
+ * spacing only — no type, colour or radius. They also set no width of their own, so unlike the
4
+ * page-CHROME family they nest inside a well safely.
5
+ */
6
+
7
+ export const pageTheme = {};
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Forest theme fragment — UPagination. Deltas over stock (stock `label: min-w-5 text-center`,
3
+ * `item: ''`). The page controls render as UButton, so their colour, size, radius and focus ring
4
+ * ride the button theme — nothing needed here for those, or for the touch (coarse) size step-up.
5
+ */
6
+
7
+ export const paginationTheme = {
8
+ slots: {
9
+ // Bold page numbers so the active page reads clearly on its solid face.
10
+ label: 'font-bold',
11
+ // Softer rounded-lg face on each page item.
12
+ item: 'rounded-lg',
13
+ // Ellipsis renders as an outline UButton (inherits the item variant); drop its ring so the
14
+ // gap marker reads as a plain glyph, not a bordered button. `as-child` lands this on the button.
15
+ ellipsis: 'ring-0',
16
+ },
17
+ } as const
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Forest theme fragment — UPinInput (OTP / code entry). Deltas only; everything else inherited from
3
+ * stock Nuxt UI.
4
+ *
5
+ * A pin input is an input split into square cells, so it wears the SAME Forest identity as UInput
6
+ * (themes/input.ts): the recessed depth line, the colour-tracking focus halo, the per-size radius
7
+ * ladder, and the touch ladder. Stock UPinInput shares UInput's variant/colour/highlight
8
+ * set and the same responsive `md:text-*` shrink, so the deltas match — only the size axis differs
9
+ * (a square `size-*` cell rather than px/py), so the coarse step bumps the cell size + type.
10
+ *
11
+ * Slots touched: root, base.
12
+ * Slot names sourced from: apps/docs/node_modules/.nuxt-ui/ui/pin-input.ts
13
+ */
14
+
15
+ export const pinInputTheme = {
16
+ slots: {
17
+ // gap-2 → 8px between cells (stock gap-1.5) — a warmer, less grid-like row cadence.
18
+ root: 'gap-2',
19
+ // font-bold so a single digit fills the cell; plus the input identity: the box-shadow transition
20
+ // and the recessed depth line at rest + the depth line & soft colour halo on focus (same shared
21
+ // --forest-input-* / --forest-halo-* tokens as UInput). The old `focus:outline-none` is dropped —
22
+ // it suppressed the very focus outline UInput keeps; parity restores the shared treatment.
23
+ base: 'font-bold 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-[inset_0_var(--forest-input-depth)_0_0_var(--forest-input-recess),0_0_0_3px_var(--forest-input-halo,var(--forest-halo-primary))]',
24
+ },
25
+ // Touch ladder + per-size radius/depth — mirrors input.ts. Under a coarse pointer each
26
+ // cell grows one size up (a bigger square + type), so OTP entry gets a real tap target. The type
27
+ // bump targets the next size's RESPONSIVE-EFFECTIVE value (stock's `md:text-*` shrink, else the
28
+ // ladder inverts): sm→md `touch:text-sm`, lg→xl `touch:text-base`. sm also rounds up to rounded-md
29
+ // tracking its size-up. xl has a touch rung of its own (52). Depth line scales per size
30
+ // (2/2/3/3/4). PER-SIZE RADIUS:
31
+ // xs/sm rounded-sm (8px), md/lg/xl rounded-md (12px, inherited from stock base — not restated).
32
+ // The mobile cut needs no escape — it lands on the same rung a coarse pointer would.
33
+ variants: {
34
+ size: {
35
+ // touch 28: bigger cell (text/radius/depth unchanged)
36
+ xs: { base: 'rounded-sm [--forest-input-depth:2px] touch:size-7' },
37
+ // touch 36: bigger cell, larger type (md's effective 14), rounder, deeper recess
38
+ sm: { base: 'rounded-sm [--forest-input-depth:2px] touch:size-9 touch:text-sm touch:rounded-md touch:[--forest-input-depth:3px]' },
39
+ // touch 40: bigger cell
40
+ md: { base: '[--forest-input-depth:3px] touch:size-10 touch:text-sm' },
41
+ // touch 44: bigger cell, larger type (16), deepest recess
42
+ lg: { base: '[--forest-input-depth:3px] touch:size-11 touch:text-base touch:rounded-lg touch:[--forest-input-depth:4px]' },
43
+ // touch 52
44
+ xl: { base: '[--forest-input-depth:4px] touch:size-13 touch:text-lg touch:rounded-lg touch:[--forest-input-depth:4px]' },
45
+ },
46
+ // Focus halo tracks `color` — FLAT strings (stock's colour variant is flat; a `{ base: … }` shape
47
+ // would be dropped by the merge). Each colour re-points --forest-input-halo at its own 200 step.
48
+ color: {
49
+ primary: '[--forest-input-halo:var(--forest-halo-primary)]',
50
+ secondary: '[--forest-input-halo:var(--forest-halo-secondary)]',
51
+ success: '[--forest-input-halo:var(--forest-halo-success)]',
52
+ info: '[--forest-input-halo:var(--forest-halo-info)]',
53
+ warning: '[--forest-input-halo:var(--forest-halo-warning)]',
54
+ error: '[--forest-input-halo:var(--forest-halo-error)]',
55
+ forest: '[--forest-input-halo:var(--forest-halo-forest)]',
56
+ maple: '[--forest-input-halo:var(--forest-halo-maple)]',
57
+ river: '[--forest-input-halo:var(--forest-halo-river)]',
58
+ warm: '[--forest-input-halo:var(--forest-halo-warm)]',
59
+ acid: '[--forest-input-halo:var(--forest-halo-acid)]',
60
+ neutral: '[--forest-input-halo:var(--forest-halo-neutral)]',
61
+ },
62
+ },
63
+ // Ghost drops the resting depth line (no fill/ring = nothing to recess into); it takes the line on
64
+ // hover/focus with the elevated fill. Same as input.
65
+ compoundVariants: [
66
+ {
67
+ variant: 'ghost',
68
+ class: {
69
+ base: 'shadow-none hover:shadow-[inset_0_calc(var(--forest-input-depth)_+_var(--forest-field-edge,0px))_0_0_var(--forest-input-recess)]',
70
+ },
71
+ },
72
+ ],
73
+ } as const;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Forest theme fragment — UPopover (floating contextual panel).
3
+ *
4
+ * Forest opinions applied:
5
+ *
6
+ * content — radius bumped from stock `rounded-md` to `rounded-xl` (the brand's warm, soft
7
+ * register — one step above the tooltip's `rounded-lg` since a popover carries richer
8
+ * content and warrants the extra breathing room). The shadow is stepped up from
9
+ * `shadow-lg` to `shadow-xl` to give the floating panel appropriate depth against the
10
+ * page surface.
11
+ *
12
+ * arrow — fill-bg already tracks the content background token; no Forest opinion needed beyond
13
+ * confirming stroke-default resolves correctly in both modes. Class unchanged.
14
+ *
15
+ * Result: a warmer, more tactile popover that sits confidently above the canvas without competing
16
+ * with deeper modals.
17
+ *
18
+ * Slots sourced from: apps/docs/node_modules/.nuxt-ui/ui/popover.ts
19
+ * Slots touched: content
20
+ * Slots NOT touched: arrow — stock values are semantically correct; no Forest override required.
21
+ */
22
+
23
+ export const popoverTheme = {
24
+ slots: {
25
+ // Softer brand radius + deeper shadow for comfortable floating-panel presence.
26
+ // All other classes (animation, origin, focus outline, pointer-events, ring) are inherited
27
+ // from the Nuxt UI base and are not restated here.
28
+ content: 'shadow-xl rounded-xl 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)]',
29
+ },
30
+ } as const;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Forest theme fragment — UProgress (linear progress bar).
3
+ *
4
+ * Forest opinions applied:
5
+ *
6
+ * indicator — adds `will-change-transform` so the translate transition composites on the GPU
7
+ * across all browsers, giving a smooth scrub during rapid model-value updates (e.g. live
8
+ * telemetry feeds from the fleet API).
9
+ *
10
+ * Slots touched: `indicator`.
11
+ * Slot names sourced from: apps/docs/node_modules/.nuxt-ui/ui/progress.ts
12
+ *
13
+ * We do NOT override:
14
+ * - `base` (track) — stock `bg-accented` fill carries the groove; the coloured indicator is the
15
+ * signal. No border token reads consistently against the track in both modes, so no ring.
16
+ * - Colour variants — `bg-primary`, `bg-error`, etc. are already wired to the Forest OKLCH ramps
17
+ * via docs.css / forest-preset.ts; no per-slot colour class needed.
18
+ * - Size compound variants — the 2xs–2xl height/width ladder is suitable; Forest has no density
19
+ * opinion here beyond what `--ui-radius` (0.5 rem) already gives.
20
+ * - `status`, `steps`, `step` slots — stock text-dimmed + transition-[width] treatment is correct.
21
+ * - `root` — flex layout is correct for both orientations; no override needed.
22
+ */
23
+
24
+ export const progressTheme = {
25
+ slots: {
26
+ // Indicator: GPU-composited translate for smooth live-telemetry scrubbing.
27
+ indicator: 'duration-(--motion-slow) ease-(--motion-ease-out) will-change-transform',
28
+ },
29
+ } as const;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Prose components (`ui.prose.*`) — the ones a docs surface actually renders.
3
+ *
4
+ * These run stock: `border-muted` around every code surface, `bg-default` on a filename header,
5
+ * `bg-muted` in the code body, `my-5` for rhythm. Only a measurable defect warrants an entry here.
6
+ *
7
+ * TWO CONSTRAINTS ON ANY ENTRY:
8
+ *
9
+ * 1. THE RAMP COLLAPSES A DIFFERENT PAIR IN EACH MODE. Light: `border-default` == `border-muted`
10
+ * (p3 0.903) == `bg-accented`. Dark: `border-muted` == `border-accented` (0.255), and
11
+ * `border-default` equals `bg-muted` — the code fill, which leaves a block no edge. A `dark:`
12
+ * split therefore holds in one mode and loses the edge in the other. Frame tokens take one
13
+ * token across both modes; never split them.
14
+ *
15
+ * 2. `codeIcon` STAYS STOCK. It maps a file to `i-vscode-icons-file-type-<ext>`, the brand marks an
16
+ * editor shows, which is what makes a file tree readable at a glance. They live in
17
+ * `@iconify-json/vscode-icons`, a devDependency alongside lucide; without that collection a tab
18
+ * with a filename renders an EMPTY slot. Lucide cannot tell a .ts from a .vue, so it is not a
19
+ * substitute. A file rendering blank means the extension is missing from stock's map
20
+ * (`UCodeIcon` resolves the FULL FILENAME first, then the extension) — add that one key.
21
+ */
22
+ export const proseTheme = {
23
+ // `ed-demo` is a marker, not styling. The docs editorial grid opens a new section whenever a block
24
+ // contains a heading, and a specimen renders headings of its own (a UEmpty title, a dialog title).
25
+ // The marker excludes them, so section rhythm does not depend on which state a demo is showing.
26
+ codePreview: {
27
+ slots: {
28
+ preview: 'ed-demo',
29
+ },
30
+ },
31
+
32
+ // Stock's collapsed root is `rounded-b-md` over an opaque `bg-muted` fill, so its square top
33
+ // corners paint into the 12px corners a filename header rounds — a hard right angle at each top
34
+ // corner, visible in dark where the fill clears the page. Rounding the closed root clips the
35
+ // children correctly: the root already carries `overflow-hidden`.
36
+ codeCollapse: {
37
+ variants: {
38
+ open: {
39
+ false: {
40
+ root: 'rounded-md',
41
+ },
42
+ },
43
+ },
44
+ },
45
+ };
@@ -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;