@justanarthur/modo-ui 0.6.0-canary.6 → 0.6.0-canary.8

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 (164) hide show
  1. package/DESIGN.md +8 -2
  2. package/README.md +87 -13
  3. package/components/AI/input-message/examples.mdx +34 -28
  4. package/components/AI/input-message/index.tsx +2 -0
  5. package/components/accordion/examples.mdx +41 -35
  6. package/components/badge/examples.mdx +30 -26
  7. package/components/button/examples.mdx +47 -43
  8. package/components/card/examples.mdx +135 -129
  9. package/components/copy/examples.mdx +50 -44
  10. package/components/inputs/checkbox/examples.mdx +34 -30
  11. package/components/inputs/color-picker/examples.mdx +39 -31
  12. package/components/inputs/combobox/examples.mdx +135 -125
  13. package/components/inputs/date-time/date-picker/examples.mdx +107 -97
  14. package/components/inputs/date-time/date-range-picker/examples.mdx +37 -33
  15. package/components/inputs/field/examples.mdx +58 -54
  16. package/components/inputs/file-dropzone/examples.mdx +15 -13
  17. package/components/inputs/input/examples.mdx +44 -13
  18. package/components/inputs/number-input/examples.mdx +18 -14
  19. package/components/inputs/select/examples.mdx +94 -84
  20. package/components/inputs/slider/examples.mdx +13 -11
  21. package/components/inputs/switch/examples.mdx +33 -31
  22. package/components/inputs/textarea/examples.mdx +39 -33
  23. package/components/navigation/pagination/examples.mdx +22 -16
  24. package/components/navigation/sidebar/examples.mdx +14 -8
  25. package/components/navigation/tabs/index.tsx +1 -1
  26. package/components/overlays/dropdown/examples.mdx +176 -168
  27. package/components/overlays/popover/examples.mdx +46 -40
  28. package/components/table/examples.mdx +45 -41
  29. package/dist/base.css +44 -0
  30. package/dist/blocks/sidebar-app/index.js +12 -11
  31. package/dist/components/AI/input-message/index.js +5 -4
  32. package/dist/components/accordion/index.js +3 -2
  33. package/dist/components/avatar/index.js +3 -2
  34. package/dist/components/badge/index.js +3 -2
  35. package/dist/components/button/index.js +3 -2
  36. package/dist/components/card/index.js +3 -2
  37. package/dist/components/copy/index.js +4 -3
  38. package/dist/components/inputs/checkbox/index.js +3 -2
  39. package/dist/components/inputs/color-picker/index.js +5 -4
  40. package/dist/components/inputs/combobox/index.js +5 -4
  41. package/dist/components/inputs/date-time/calendar/index.js +4 -3
  42. package/dist/components/inputs/date-time/date-picker/index.js +12 -11
  43. package/dist/components/inputs/date-time/date-range-picker/index.js +12 -11
  44. package/dist/components/inputs/date-time/duration-picker/index.js +11 -10
  45. package/dist/components/inputs/date-time/month-picker/index.js +10 -9
  46. package/dist/components/inputs/date-time/time-picker/index.js +11 -10
  47. package/dist/components/inputs/field/index.js +5 -4
  48. package/dist/components/inputs/file-dropzone/index.js +5 -4
  49. package/dist/components/inputs/input/index.js +4 -3
  50. package/dist/components/inputs/number-input/index.js +5 -4
  51. package/dist/components/inputs/radio/index.js +3 -2
  52. package/dist/components/inputs/search/index.js +7 -6
  53. package/dist/components/inputs/select/index.js +5 -4
  54. package/dist/components/inputs/slider/index.js +3 -2
  55. package/dist/components/inputs/switch/index.js +3 -2
  56. package/dist/components/inputs/textarea/index.js +4 -3
  57. package/dist/components/navigation/navigation-menu/index.js +4 -3
  58. package/dist/components/navigation/pagination/index.js +9 -8
  59. package/dist/components/navigation/sidebar/index.js +9 -8
  60. package/dist/components/navigation/tabs/index.js +3 -2
  61. package/dist/components/navigation/tabs-subtle/index.js +3 -2
  62. package/dist/components/overlays/alert-dialog/index.js +5 -4
  63. package/dist/components/overlays/command/index.js +6 -5
  64. package/dist/components/overlays/context-menu/index.js +6 -5
  65. package/dist/components/overlays/dialog/index.js +4 -3
  66. package/dist/components/overlays/dropdown/index.js +5 -4
  67. package/dist/components/overlays/popover/index.js +3 -2
  68. package/dist/components/overlays/preview-card/index.js +3 -2
  69. package/dist/components/overlays/sheet/index.js +5 -4
  70. package/dist/components/overlays/toast/index.js +4 -3
  71. package/dist/components/overlays/tooltip/index.js +3 -2
  72. package/dist/docs/index.d.ts +83 -0
  73. package/dist/docs/index.js +288 -0
  74. package/dist/docs/vite.d.ts +31 -0
  75. package/dist/docs/vite.js +45 -0
  76. package/dist/index.js +56 -55
  77. package/dist/lib/field-classes.d.ts +6 -1
  78. package/dist/lib/field-classes.js +3 -2
  79. package/dist/lib/focus-ring.js +3 -2
  80. package/dist/lib/scroll-column.js +3 -2
  81. package/dist/lib/shape-context.js +2 -1
  82. package/dist/lib/shape-radius.d.ts +19 -0
  83. package/dist/lib/shape-radius.js +6 -0
  84. package/dist/preset.d.ts +1 -0
  85. package/dist/preset.js +1 -0
  86. package/dist/primitives/code/index.js +4 -3
  87. package/dist/primitives/craft/index.js +4 -3
  88. package/dist/primitives/fluid-hover/index.js +3 -2
  89. package/dist/primitives/morph/index.js +4 -3
  90. package/dist/primitives/motion/index.js +4 -3
  91. package/dist/primitives/scroll-area/index.js +3 -2
  92. package/dist/primitives/skeleton/index.js +3 -2
  93. package/dist/shared/{components-59my5pwr.js → components-1kab3j44.js} +4 -4
  94. package/dist/shared/{components-47226105.js → components-2c64r8c9.js} +5 -5
  95. package/dist/shared/{components-vbh98t9d.js → components-2syq4mwc.js} +3 -3
  96. package/dist/shared/{components-dg6s5gjg.js → components-3ydr2fqe.js} +3 -3
  97. package/dist/shared/{components-z9n19fyk.js → components-4nck8acq.js} +1 -1
  98. package/dist/shared/{components-q4z71n18.js → components-5012r6gr.js} +2 -2
  99. package/dist/shared/{components-621xezcz.js → components-5sxjeh6h.js} +2 -2
  100. package/dist/shared/{components-pmfszv7a.js → components-5tzhqbcw.js} +2 -2
  101. package/dist/shared/{components-60atskhp.js → components-5y5shqfd.js} +1 -1
  102. package/dist/shared/{components-kr40s3z0.js → components-6b0zb8z3.js} +1 -1
  103. package/dist/shared/{components-c96k4gfs.js → components-6gk118w3.js} +2 -2
  104. package/dist/shared/{components-78x40gee.js → components-71nbt03m.js} +14 -1
  105. package/dist/shared/{components-52ngsgca.js → components-7zkhz3r8.js} +1 -1
  106. package/dist/shared/{components-02c81nsv.js → components-8mt5bsv6.js} +1 -1
  107. package/dist/shared/{components-nd1baxp1.js → components-8rb25gbe.js} +2 -2
  108. package/dist/shared/{components-ep3d57kp.js → components-8v644a0s.js} +4 -4
  109. package/dist/shared/{components-1edyddfg.js → components-9j9abkct.js} +3 -3
  110. package/dist/shared/{components-4swzve2d.js → components-9sttq78t.js} +1 -1
  111. package/dist/shared/{components-2c0qcfx4.js → components-av5t26rc.js} +1 -1
  112. package/dist/shared/{components-k57dtvgp.js → components-b94t6qtj.js} +3 -3
  113. package/dist/shared/{components-2z48g4fp.js → components-ctqmge8t.js} +2 -1
  114. package/dist/shared/{components-vqqapw72.js → components-cwg1d0w6.js} +1 -1
  115. package/dist/shared/{components-mpw667rb.js → components-dyae0y7h.js} +1 -1
  116. package/dist/shared/{components-35tmfjtw.js → components-ej4r7wmj.js} +2 -2
  117. package/dist/shared/{components-bwy8775y.js → components-eqze3y30.js} +5 -3
  118. package/dist/shared/{components-3qssegme.js → components-fejdzvqc.js} +5 -5
  119. package/dist/shared/{components-pd42emw3.js → components-ft3zdc79.js} +2 -2
  120. package/dist/shared/components-gx4h5rbb.js +40 -0
  121. package/dist/shared/{components-c6cz9nw6.js → components-gyt7my41.js} +3 -3
  122. package/dist/shared/{components-6zng858q.js → components-hdzrqqpx.js} +3 -1
  123. package/dist/shared/{components-zj640a90.js → components-jymmaevv.js} +3 -3
  124. package/dist/shared/{components-aj7f4v4v.js → components-khspp26b.js} +2 -2
  125. package/dist/shared/{components-b92nqk44.js → components-km7fhgbk.js} +1 -1
  126. package/dist/shared/{components-y1w3a6wx.js → components-mattt5jp.js} +2 -2
  127. package/dist/shared/{components-43brg0gv.js → components-mpa2y6x8.js} +4 -4
  128. package/dist/shared/{components-wktrrb4m.js → components-pt5ftm4p.js} +1 -1
  129. package/dist/shared/{components-t7c6c4t4.js → components-px2zm7dc.js} +1 -1
  130. package/dist/shared/{components-v0nz1m4t.js → components-qqcmtrbv.js} +1 -1
  131. package/dist/shared/{components-0rpek9d8.js → components-r7q1zm7r.js} +2 -2
  132. package/dist/shared/{components-n155gkhr.js → components-rpw4md2b.js} +2 -2
  133. package/dist/shared/{components-72w4ge68.js → components-rtadhd9j.js} +1 -1
  134. package/dist/shared/{components-nv4awn05.js → components-s8yqk2dc.js} +1 -1
  135. package/dist/shared/{components-wwd363me.js → components-skfd7kff.js} +1 -1
  136. package/dist/shared/{components-daqy7ydp.js → components-t2a3qmy9.js} +2 -2
  137. package/dist/shared/{components-030n1m1v.js → components-t50hrswb.js} +2 -2
  138. package/dist/shared/{components-jkctvwz9.js → components-t5pk5nmc.js} +1 -1
  139. package/dist/shared/{components-49bsynbw.js → components-vc864c51.js} +1 -1
  140. package/dist/shared/{components-d7nhksd0.js → components-vkwq2k96.js} +1 -1
  141. package/dist/shared/{components-64c8kvmt.js → components-w0xq6q9k.js} +3 -3
  142. package/dist/shared/{components-8nwsqjn8.js → components-wbbckbw8.js} +1 -1
  143. package/dist/shared/{components-hx4a4cvm.js → components-wqrvqq5p.js} +3 -3
  144. package/dist/shared/{components-96sd8h9m.js → components-xjkdvnbg.js} +1 -1
  145. package/dist/shared/{components-hpe3avtw.js → components-xqszvkj4.js} +3 -3
  146. package/dist/shared/{components-tjtjmjjr.js → components-y1qvbawa.js} +1 -1
  147. package/dist/shared/{components-9n43qf8y.js → components-z0bqtvcp.js} +5 -5
  148. package/dist/shared/{components-khcnzj3g.js → components-z6dfqtyt.js} +3 -3
  149. package/dist/styles.css +46 -1
  150. package/dist/tailwind.css +1 -0
  151. package/dist/tokens/colors.css +6 -0
  152. package/dist/tokens.css +6 -0
  153. package/docs/chrome.css +120 -0
  154. package/docs/chrome.tsx +283 -0
  155. package/docs/index.ts +9 -0
  156. package/docs/vite.ts +85 -0
  157. package/lib/field-classes.ts +7 -1
  158. package/lib/shape-context.tsx +16 -2
  159. package/lib/shape-radius.ts +67 -0
  160. package/package.json +26 -2
  161. package/preset.ts +1 -0
  162. package/primitives/craft/examples.mdx +22 -18
  163. package/primitives/morph/examples.mdx +11 -7
  164. package/primitives/scroll-area/index.tsx +4 -0
package/DESIGN.md CHANGED
@@ -67,6 +67,8 @@ colors:
67
67
  image-edge-dark: "rgb(255 255 255 / 0.1)"
68
68
  progress-track: "color-mix(in oklab, oklch(0.205 0 0) 10%, transparent)"
69
69
  progress-track-dark: "color-mix(in oklab, oklch(0.97 0 0) 10%, transparent)"
70
+ well: "oklch(0.967 0.001 286.375)"
71
+ well-dark: "oklch(0 0 0 / 0.25)"
70
72
  surface-1: "oklch(0.985 0 0)"
71
73
  surface-2: "oklch(0.991 0 0)"
72
74
  surface-3: "oklch(1 0 0)"
@@ -284,7 +286,7 @@ components:
284
286
  padding: 24px
285
287
  width: 400px
286
288
  tabs-list:
287
- backgroundColor: "{colors.muted}"
289
+ backgroundColor: "{colors.well}"
288
290
  rounded: "{rounded.xl}"
289
291
  height: "{spacing.control}"
290
292
  padding: 4px
@@ -357,7 +359,11 @@ Two text colors, a greyscale ground, one blue.
357
359
  button (until `--primary-foreground` is re-pointed) and the tooltip (`text-background`).
358
360
  - **Surfaces (`bg-surface-1` … `bg-surface-8`)** — the elevation ladder (see Elevation & Depth). Prefer a
359
361
  surface level over `card`/`background` for anything that floats or nests.
360
- - **Muted (`bg-muted`)** — a recessed track: the segmented tabs' well, a sunken field at rest.
362
+ - **Muted (`bg-muted`)** — a sunken fill: a field's hover wash, a queued row. In dark it is surface 2, so
363
+ it never carries a track: it would vanish on a card or the inset.
364
+ - **Well (`bg-well`)** — the recessed track under a segmented control's indicator (the tabs list). Light is
365
+ muted; dark is black at 25%, which darkens whatever it sits on, so the track reads on the page, a card
366
+ and the inset alike.
361
367
  - **Accent (`bg-accent`)** — the secondary button and the switch's off track. `selected` is a firmer
362
368
  grey for persistent selection fills.
363
369
  - **Hover / Active (`bg-hover`, `bg-active`)** — translucent overlays (black 4% / 7% in light, white 6% /
package/README.md CHANGED
@@ -106,7 +106,7 @@ Every vendored file opens with a plain `/* */` header. The header names the
106
106
  upstream path and commit, carries the MIT notice, and lists every local
107
107
  modification. The vendored code below the header keeps upstream's formatting.
108
108
  Files written for this port (`preset.ts`, `uno.config.ts`, `vite.ts`, `index.ts`,
109
- `scripts/build-css.ts`, `modo.components.tsx`,
109
+ `scripts/build-css.ts`, `docs/*`,
110
110
  `lib/use-controllable-state.ts`, `primitives/code/index.tsx`, `primitives/craft/*`, the morph files
111
111
  in `lib/` (`use-morph.ts`, `reduced-motion.ts`, `morph-layers.tsx`, `morph-part.tsx`, `goo-indicator.tsx`,
112
112
  `slot.ts`) and `primitives/morph/index.tsx`, `blocks/sidebar-app/index.tsx`,
@@ -137,13 +137,13 @@ positioning) are ported.
137
137
  | `DESIGN.md` | The design language in Google's [DESIGN.md](https://github.com/google-labs-code/design.md) format: the tokens as front matter, the rules as prose. Agents building here follow the `modo-ui` skill (`.claude/skills/modo-ui` at the repo root). |
138
138
  | `tokens/*.css` | Colors, typography (`--font-sans`, `--text-*`), motion (`--duration-*`), radius and spacing. Shipped per group as `dist/tokens/*.css` (`./tokens/*`). |
139
139
  | `base.css` | Theme switching, base styles, focus fallback, scrollbars, shimmer/spinner keyframes and `.scroll-fade`. Shipped in the package. The inline `code` chip applies only inside the docs shell, `data-modo="prose"` and `.modo-prose`. |
140
- | `global.css` | The docs site's stylesheet (`modo.config.ts` `css`): imports `base.css`, then the modo-only focus revert and the docs chrome. |
140
+ | `global.css` | The docs site's stylesheet (`modo.config.ts` `css`): imports `base.css`, then `docs/chrome.css`. |
141
141
  | `preset.ts` | The UnoCSS preset (theme and token utilities), shipped as `@justanarthur/modo-ui/unocss`. |
142
- | `uno.config.ts`, `vite.ts` | The UnoCSS setup of the docs site and the prebuilt CSS (see below). |
142
+ | `uno.config.ts`, `vite.ts` | The UnoCSS setup of the docs site and the prebuilt CSS (see below); `vite.ts` also applies the docs chrome's `docsTheme` plugin. |
143
143
  | `index.ts`, `bunup.config.ts`, `scripts/build-css.ts` | The package build: `bun run build` writes the components, their types and the stylesheets to `dist/`. The docs site builds with `bun run build:docs` into `docs-dist/`. |
144
144
  | `scripts/exports.ts`, `scripts/use-client.ts`, `scripts/check-singletons.ts` | Packaging around that build. `exports.ts` (first step of `bun run build`) writes one `./lib/<name>` export per `lib/` module into `package.json`, plus `./components/*/variants` and `./blocks/*/variants` for the items' server-callable `variants.ts` entries; commit the `package.json` it leaves. `use-client.ts` (after bunup) puts `"use client"` on the emitted modules that render, call a hook or create a context, or use a binding from one; pure helpers (`cn`, class tables, springs, `variants.ts`) and re-export entries stay directive-free so a React Server Component can call them (`--list` prints the split). `check-singletons.ts` (`bun run check:dist`) checks that every React context and the `toast()` manager exist once in `dist/`, and that every entry has its `.d.ts`. `typesVersions` in `package.json` maps every public subpath to `dist/**/*.d.ts`, so `moduleResolution: node` consumers do not type-check the shipped source. |
145
145
  | `lib/` | FF's shared system files (upstream `lib/`, `hooks/` and the fluid-hover highlight, flat, upstream names), imported relatively by items. modo only scans the tier folders, so it never mistakes them for items. |
146
- | `modo.components.tsx` | Components only the docs site uses: the `Icon`, `Select` and `DocsNav` shell-slot adapters and the `ThemeSwitcher` panel item, picked in `modo.config.ts` by export name (`./modo.components.tsx#Select`). |
146
+ | `docs/` | The docs chrome, shipped: `chrome.tsx` (the `Button`, `Link`, `Icon`, `Select` and `DocsNav` shell-slot adapters and the `ThemeSwitcher` panel item, re-exported by `index.ts` as `@justanarthur/modo-ui/docs`), `chrome.css` (the inset layout and the look of the chrome, `@justanarthur/modo-ui/docs/chrome.css`) and `vite.ts` (`docsTheme`, the pre-paint theme controller, `@justanarthur/modo-ui/docs/vite`). `modo.config.ts` picks the slots by export name (`./docs#DocsNav`); another design system documented with modo points at the same module by package specifier (see **The docs chrome, for another design system**). The barrel (`index.ts`) never imports it, `bun run check:dist` fails if it does. |
147
147
  | `primitives/<id>/` | FF's "System" pages: `index.tsx` (component + TSDoc prose) and `examples.mdx`, pulled in by `@example {@include ./examples.mdx}`. |
148
148
  | `components/[<group>/]<id>/` | FF's "Components" pages, same shape. The `AI/`, `inputs/` (with `inputs/date-time/`), `navigation/` and `overlays/` group folders become sidebar sub-groups and overview sections; the core display items stay ungrouped. |
149
149
  | `blocks/<id>/` | Compositions of several items, same shape: `sidebar-app`, FF's sidebar preset `sa1FQfCxH6` as an app shell, with the header and footer blocks it is built from beside it. |
@@ -158,7 +158,8 @@ positioning) are ported.
158
158
  | `lib/springs.ts` | `lib/springs.ts` | `goo` tier added (the morph engine's goo: 250ms to land, bounce 0.15, a 200ms spring back); `exitFallbackMs` also reads a spring exit's `visualDuration` |
159
159
  | `lib/font-weight.ts` | — | not ported: its `fontWeights` became the `--weight-*` tokens in `tokens/typography.css` (the `weight-*` utilities) |
160
160
  | `lib/popup.ts` | `lib/popup.ts` | `popupMotionClass` dropped (unused since the popups morph) |
161
- | `lib/shape-context.tsx` | `lib/shape-context.tsx` | `"use client"` dropped |
161
+ | `lib/shape-context.tsx` | `lib/shape-context.tsx` | `"use client"` dropped; `--shape-input-radius` is published through `claimShapeRadius` and given back on unmount |
162
+ | — (new) | `lib/shape-radius.ts` | Local addition: the stack of `ShapeProvider` claims on `--shape-input-radius` (`<html>`), so an unmounting provider restores the one below it or the page's own value; plain data, tested without a DOM |
162
163
  | `lib/size-context.tsx` | `lib/size-context.tsx` | `"use client"` dropped; `SizeProvider` moved to `primitives/sizes` (the documented item), with `SizeContext` exported for it; `sizeMap`, `typeScale` and their types moved to `size-scale.ts` and re-exported |
163
164
  | `lib/size-context.tsx` (`sizeMap`, `typeScale`) | `lib/size-scale.ts` | The ladder's plain data, split out so server code can read it (no JSX, hooks or contexts); `sizeMap` text classes → `text-body` / `text-body-compact` |
164
165
  | `lib/icon-context.tsx` | `lib/icon-context.tsx` | `"use client"` dropped; `size`/`strokeWidth` widened to `number \| string` for lucide v1 + React 18 types |
@@ -292,17 +293,17 @@ only what is specific to it:
292
293
  | — | `registry/blocks/sidebar-app/inset-topbar.tsx` | `blocks/sidebar-app/inset-topbar.tsx` | the trigger's peek-time fade dropped (the sidebar opens on hover in flow, so the trigger stays) |
293
294
  | — | `registry/default/lib/sidebar-menu-grid.ts` | `blocks/sidebar-app/menu-grid.ts` | the width reads `--anchor-width` only; the `-ml-1` shift dropped (it would move the morph surface's content off its background) |
294
295
 
295
- `Select` in `modo.components.tsx` is not an item: modo's docs chrome renders its Select slot
296
+ `Select` in `docs/chrome.tsx` is not an item: modo's docs chrome renders its Select slot
296
297
  with a flat `value` / `onChange` / `options` contract
297
298
  (`lib/src/lib/slots.tsx`), and this adapter maps it onto
298
299
  `Select.Trigger` / `Select.Content` / `Select.Item` so the chrome runs on the
299
300
  design system's own Select. The
300
301
  `components/inputs/select` item keeps the upstream API untouched.
301
302
 
302
- `DocsNav` in `modo.components.tsx` is the chrome's Sidebar slot, pinned as
303
+ `DocsNav` in `docs/chrome.tsx` is the chrome's Sidebar slot, pinned as
303
304
  `shell.Sidebar` (by name alone modo would adopt the documented `Sidebar`, an
304
305
  app shell that needs its provider). Each aside is an inset `Sidebar` with its
305
- own provider, and `global.css` turns the content between them into the inset
306
+ own provider, and `docs/chrome.css` turns the content between them into the inset
306
307
  card (the lib's grid columns become `auto`, so they follow the sidebars' width
307
308
  springs). Each provider has `openOnHover`, and its `Sidebar.Trigger` sits in
308
309
  the card's corner: the trigger, `[` for the nav and `]` for the panel, switch
@@ -315,7 +316,7 @@ below xl. A section of links is a collapsible `Sidebar.Group` with one
315
316
  highlight melt from link to link; a panel section (Theme) keeps its control
316
317
  under the group label.
317
318
 
318
- `ThemeSwitcher` in `modo.components.tsx` is not an item either: it is the `Theme` entry in
319
+ `ThemeSwitcher` in `docs/chrome.tsx` is not an item either: it is the `Theme` entry in
319
320
  `modo.config.ts` `panel.items`, a `Select` of Light / Dark / System (each row
320
321
  with its icon, the trigger showing the active one) that reads and sets
321
322
  `window.__uiTheme` and re-reads it on `ui:themechange`. All switching logic lives in the pre-paint controller
@@ -323,6 +324,75 @@ described under **Tailwind → UnoCSS**, so the control holds no theme state of
323
324
  its own. `demo/scripts/run-design-systems.ts` appends its own `Switcher` panel
324
325
  item to whatever a design system declares, so both show up under the runner.
325
326
 
327
+ The chrome's own elements (the corner triggers, the panel section's gutter) are
328
+ styled in `docs/chrome.css` through `data-modo-ui` attributes, not utility
329
+ classes: nothing in `docs/` needs a UnoCSS or Tailwind scan, so a host with
330
+ either generator loads the same file. `Button` and `Link` are pinned too, so a
331
+ design system with items of those names but another prop shape is not matched
332
+ by name (modo would warn on every start).
333
+
334
+ ### The docs chrome, for another design system
335
+
336
+ A design system documented with modo (an `@utegsk/ui`-style package that
337
+ depends on `@justanarthur/modo-ui`) gets the same sidebars, inset card layout,
338
+ panel and Theme switcher as this site by pointing its `modo.config.ts` at the
339
+ shipped module. A `shell` / `panel` value may be a package specifier (the
340
+ viewer resolves it from the project root through the package's `exports`, the
341
+ `source` file first), which needs an `@justanarthur/modo` release after
342
+ 0.9.0-canary.0; with an older viewer keep a one-line local re-export instead
343
+ (`export * from '@justanarthur/modo-ui/docs'` in a `docs/shell.tsx`, and point
344
+ the slots at `./docs/shell.tsx#DocsNav`).
345
+
346
+ ```ts
347
+ // modo.config.ts
348
+ import { defineConfig } from '@justanarthur/modo/config'
349
+
350
+ export default defineConfig({
351
+ name: 'My DS',
352
+ css: './docs.css',
353
+ vite: './vite.mjs',
354
+ shell: {
355
+ Button: '@justanarthur/modo-ui/docs#Button',
356
+ Link: '@justanarthur/modo-ui/docs#Link',
357
+ Select: '@justanarthur/modo-ui/docs#Select',
358
+ Icon: '@justanarthur/modo-ui/docs#Icon',
359
+ Sidebar: '@justanarthur/modo-ui/docs#DocsNav',
360
+ },
361
+ panel: { items: [{ label: 'Theme', component: '@justanarthur/modo-ui/docs#ThemeSwitcher' }] },
362
+ })
363
+ ```
364
+
365
+ ```css
366
+ /* docs.css: the package stylesheet (or base.css with your own utilities), then the chrome */
367
+ @import "@justanarthur/modo-ui/styles.css";
368
+ @import "@justanarthur/modo-ui/docs/chrome.css";
369
+ ```
370
+
371
+ ```js
372
+ // vite.mjs: plain JS, Node loads it as is. Add your own plugins (Tailwind, UnoCSS) beside docsTheme.
373
+ import { docsTheme } from '@justanarthur/modo-ui/docs/vite'
374
+
375
+ export default config => {
376
+ config.plugins = [...(config.plugins ?? []), docsTheme({ storageKey: 'my-ds-theme' })]
377
+ return config
378
+ }
379
+ ```
380
+
381
+ - `chrome.css` also loads by itself, as the stylesheet beside the slot module,
382
+ when modo bundles `@justanarthur/modo-ui/docs` from its source; the explicit
383
+ `@import` fixes where it sits in your cascade and is the one to rely on.
384
+ - `docsTheme` puts `.light` / `.dark` on `<html>` before first paint and
385
+ exposes `window.__uiTheme` for `ThemeSwitcher`; `storageKey` (default
386
+ `modo-ui-theme`) keeps two sites on one origin apart.
387
+ - The panel's narrow-screen behavior (below xl the panel is a drawer, opened on
388
+ hover or with `]`, never a strip above the content) comes with `chrome.css`.
389
+ A stylesheet that re-implements the layout instead of importing it can
390
+ recreate the lib's `grid-template-rows: auto` strip: a viewport-tall blank
391
+ row with the Theme select behind it.
392
+ - `lucide-react` (the `Icon` slot) is already a dependency of the package;
393
+ the docs entry adds no other. `@justanarthur/modo-ui` itself must be a
394
+ dependency of the host: modo resolves the specifier from the project root.
395
+
326
396
  ## Tier mapping
327
397
 
328
398
  The tiers mirror the FF docs navigation. FF's "System" pages become
@@ -371,7 +441,7 @@ No ported file imports any of them.
371
441
 
372
442
  The files only they, or the FF docs site's nav, use are left out too:
373
443
  `mobile-drawer`, `nav-item`, `nav-menu`, `lib/theme-context.tsx` (the
374
- `ThemeSwitcher` panel item and vite.ts's pre-paint controller take its
444
+ `ThemeSwitcher` panel item and docs/vite.ts's pre-paint controller take its
375
445
  place) and the blocks other than the sidebar's (`queued-stack`,
376
446
  `dialog-sidebar`). Inside kept files, the same rule removed
377
447
  `SURFACE_HOVER_BG`, `SURFACE_HOVER_SHADOW` and `surfaceHoverClasses` from
@@ -398,7 +468,8 @@ prebuilt `styles.css`, and turns `preset.ts` into `tailwind.css`.
398
468
 
399
469
  - **Dark mode.** `dark: 'class'` produces `.dark .dark\:x`, the same "inside a
400
470
  `.dark` ancestor" contract as FF's `@custom-variant dark (&:is(.dark *))`.
401
- modo ships no theme of its own, so `vite.ts` injects a small controller into
471
+ modo ships no theme of its own, so `docsTheme` (`docs/vite.ts`, applied by
472
+ `vite.ts`) injects a small controller into
402
473
  `index.html` (`transformIndexHtml`, `head-prepend`). Before first paint it
403
474
  puts `light` or `dark` on `<html>` — the stored preference, or the OS one
404
475
  under `system` — which is also what flips every `light-dark()` token through
@@ -411,7 +482,10 @@ prebuilt `styles.css`, and turns `preset.ts` into `tailwind.css`.
411
482
  `global.css` (and the `base.css` it imports) and fixes the order of the
412
483
  layer its `@layer base` rules join.
413
484
  `vite.ts` is loaded by Node with type stripping, so it may only use erasable
414
- TypeScript and hands `uno.config.ts` to UnoCSS by absolute path.
485
+ TypeScript, names the `.ts` extension of the one relative module it imports
486
+ (`./docs/vite.ts`) and hands `uno.config.ts` to UnoCSS by absolute path.
487
+ `docs/vite.ts` ships built (`dist/docs/vite.js`): Node never strips types
488
+ from a file under `node_modules`.
415
489
  - **Colors.** Theme colors point at the CSS variables directly (for example
416
490
  `accent: 'var(--accent)'`, `surface.1..8`). wind4 applies opacity modifiers
417
491
  to `var()` colors with `color-mix(in srgb, …)`, so `bg-accent/12` works on
@@ -435,7 +509,7 @@ prebuilt `styles.css`, and turns `preset.ts` into `tailwind.css`.
435
509
  after the reset, so utilities (in `default`) still win. This is the same
436
510
  arrangement as Tailwind's base/utilities split.
437
511
  - **Content.** UnoCSS scans `{lib,primitives,components,blocks}/**/*.{ts,tsx,mdx}`
438
- and `modo.components.tsx` from disk (`content.filesystem`, and the same
512
+ from disk (`content.filesystem`, and the same
439
513
  globs in `content.pipeline.include`, since the default pipeline skips
440
514
  `.ts`). Two reasons:
441
515
  - modo serves items pre-bundled, so the sources never pass through Vite's
@@ -2,7 +2,9 @@ import InputMessage from './index'
2
2
  import Button from '../../button'
3
3
  import { ChevronDown, Plus } from 'lucide-react'
4
4
 
5
- # Basic
5
+ # Basics
6
+
7
+ ## Basic
6
8
 
7
9
  No props at all: the composer keeps its own draft, grows as you type and
8
10
  clears itself on Enter.
@@ -11,7 +13,31 @@ clears itself on Enter.
11
13
  <InputMessage />
12
14
  </div>
13
15
 
14
- # Suggestions
16
+ ## Send Handler
17
+
18
+ `onSend` receives the trimmed text and the attached files; an uncontrolled
19
+ composer clears its own draft afterwards. The handler here is an empty
20
+ arrow — in an app it is where the message joins the transcript.
21
+
22
+ <div className="w-full max-w-xl">
23
+ <InputMessage
24
+ onSend={() => {}}
25
+ placeholder="Press Enter to send. Shift+Enter for newline."
26
+ />
27
+ </div>
28
+
29
+ ## Disabled
30
+
31
+ Dims the panel to 50% and drops pointer events — textarea, send button and
32
+ drag-and-drop all go inert.
33
+
34
+ <div className="w-full max-w-xl">
35
+ <InputMessage defaultValue="This composer is disabled" disabled />
36
+ </div>
37
+
38
+ # Content
39
+
40
+ ## Suggestions
15
41
 
16
42
  The ghost placeholder renders with a Tab keycap — Tab fills it into the
17
43
  composer. ArrowDown/ArrowUp walk the list below (focus stays in the
@@ -30,7 +56,7 @@ shows a ↓ hint until a row is highlighted. Typing collapses the list.
30
56
  />
31
57
  </div>
32
58
 
33
- # Attachments
59
+ ## Attachments
34
60
 
35
61
  `defaultFiles` seeds the composer and turns attachment behavior on: drop a
36
62
  PNG, JPEG or PDF anywhere on the panel, or use the render-fn slot's
@@ -52,7 +78,7 @@ for its × button.
52
78
  />
53
79
  </div>
54
80
 
55
- # Queued messages
81
+ ## Queued messages
56
82
 
57
83
  `defaultStatus="streaming"` plus `defaultQueue` runs the composer as if a
58
84
  response were in flight: the send button is a Queue action, so a typed
@@ -71,7 +97,9 @@ response, and that streaming→idle edge dispatches the row at the top.
71
97
  />
72
98
  </div>
73
99
 
74
- # Left Slot Only
100
+ # Slots
101
+
102
+ ## Left Slot Only
75
103
 
76
104
  `leftSlot` takes the bottom-left action area — an attach button, a model
77
105
  switcher, a mode toggle.
@@ -86,7 +114,7 @@ switcher, a mode toggle.
86
114
  />
87
115
  </div>
88
116
 
89
- # Right Slot Only
117
+ ## Right Slot Only
90
118
 
91
119
  `rightSlot` sits in the bottom-right, just before the built-in send
92
120
  button.
@@ -100,25 +128,3 @@ button.
100
128
  }
101
129
  />
102
130
  </div>
103
-
104
- # Send Handler
105
-
106
- `onSend` receives the trimmed text and the attached files; an uncontrolled
107
- composer clears its own draft afterwards. The handler here is an empty
108
- arrow — in an app it is where the message joins the transcript.
109
-
110
- <div className="w-full max-w-xl">
111
- <InputMessage
112
- onSend={() => {}}
113
- placeholder="Press Enter to send. Shift+Enter for newline."
114
- />
115
- </div>
116
-
117
- # Disabled
118
-
119
- Dims the panel to 50% and drops pointer events — textarea, send button and
120
- drag-and-drop all go inert.
121
-
122
- <div className="w-full max-w-xl">
123
- <InputMessage defaultValue="This composer is disabled" disabled />
124
- </div>
@@ -924,6 +924,8 @@ const InputMessage = forwardRef<HTMLDivElement, InputMessageProps>(
924
924
  if (!clickToFocus || disabled) return
925
925
  const target = e.target as HTMLElement
926
926
  if (target === textareaRef.current) return
927
+ // A press in a popup portalled out of the box reaches it through the React tree only.
928
+ if (!e.currentTarget.contains(target)) return
927
929
  if (target.closest('button, a, input, select, textarea, [contenteditable], [role="button"], [data-im-queue]')) {
928
930
  return
929
931
  }
@@ -2,7 +2,9 @@ import Accordion from './index'
2
2
  import Button from '../button'
3
3
  import { ArrowRight } from 'lucide-react'
4
4
 
5
- # Standalone
5
+ # Basics
6
+
7
+ ## Standalone
6
8
 
7
9
  A single collapsible item with its own hover state.
8
10
 
@@ -15,7 +17,7 @@ A single collapsible item with its own hover state.
15
17
  </Accordion.Item>
16
18
  </Accordion>
17
19
 
18
- # Single Expand
20
+ ## Single Expand
19
21
 
20
22
  Multiple items with fluid hover — only one can be expanded at a time.
21
23
 
@@ -50,7 +52,7 @@ Multiple items with fluid hover — only one can be expanded at a time.
50
52
  </Accordion.Item>
51
53
  </Accordion.Group>
52
54
 
53
- # Multi Expand
55
+ ## Multi Expand
54
56
 
55
57
  Multiple items with fluid hover — several can be expanded at once.
56
58
 
@@ -75,34 +77,7 @@ Multiple items with fluid hover — several can be expanded at once.
75
77
  </Accordion.Item>
76
78
  </Accordion.Group>
77
79
 
78
- # Row highlight
79
-
80
- With `highlight="trigger"` an expanded item holds no tint — the fill
81
- scopes to the row and waits for hover. Suits dense panels, where a block
82
- per open item reads as a second layer.
83
-
84
- <Accordion.Group type="single" collapsible defaultValue="item-1" highlight="trigger">
85
- <Accordion.Item value="item-1" index={0}>
86
- <Accordion.Trigger>Expanded, but not tinted</Accordion.Trigger>
87
- <Accordion.Content>
88
- The panel reads as part of the page rather than a filled block.
89
- </Accordion.Content>
90
- </Accordion.Item>
91
- <Accordion.Item value="item-2" index={1}>
92
- <Accordion.Trigger>Hover any row</Accordion.Trigger>
93
- <Accordion.Content>
94
- The fill follows your cursor instead of marking a state.
95
- </Accordion.Content>
96
- </Accordion.Item>
97
- <Accordion.Item value="item-3" index={2}>
98
- <Accordion.Trigger>Compare with the sections above</Accordion.Trigger>
99
- <Accordion.Content>
100
- Those hold a block across the row and its panel while open.
101
- </Accordion.Content>
102
- </Accordion.Item>
103
- </Accordion.Group>
104
-
105
- # Morph
80
+ ## Morph
106
81
 
107
82
  Each panel drips out of the row where it was pressed (its middle, from the keyboard) and widens into the item, its content revealed by the same shape; closing folds it back into the row. The rows below ride its bottom edge.
108
83
 
@@ -130,7 +105,36 @@ Each panel drips out of the row where it was pressed (its middle, from the keybo
130
105
  </Accordion.Item>
131
106
  </Accordion.Group>
132
107
 
133
- # Without the goo
108
+ # Variants
109
+
110
+ ## Row highlight
111
+
112
+ With `highlight="trigger"` an expanded item holds no tint — the fill
113
+ scopes to the row and waits for hover. Suits dense panels, where a block
114
+ per open item reads as a second layer.
115
+
116
+ <Accordion.Group type="single" collapsible defaultValue="item-1" highlight="trigger">
117
+ <Accordion.Item value="item-1" index={0}>
118
+ <Accordion.Trigger>Expanded, but not tinted</Accordion.Trigger>
119
+ <Accordion.Content>
120
+ The panel reads as part of the page rather than a filled block.
121
+ </Accordion.Content>
122
+ </Accordion.Item>
123
+ <Accordion.Item value="item-2" index={1}>
124
+ <Accordion.Trigger>Hover any row</Accordion.Trigger>
125
+ <Accordion.Content>
126
+ The fill follows your cursor instead of marking a state.
127
+ </Accordion.Content>
128
+ </Accordion.Item>
129
+ <Accordion.Item value="item-3" index={2}>
130
+ <Accordion.Trigger>Compare with the sections above</Accordion.Trigger>
131
+ <Accordion.Content>
132
+ Those hold a block across the row and its panel while open.
133
+ </Accordion.Content>
134
+ </Accordion.Item>
135
+ </Accordion.Group>
136
+
137
+ ## Without the goo
134
138
 
135
139
  `effect="morph"` grows the same shape with no liquid neck to its row; `tier="moderate"` runs the goo on the quicker, critically damped spring.
136
140
 
@@ -149,7 +153,7 @@ Each panel drips out of the row where it was pressed (its middle, from the keybo
149
153
  </Accordion.Item>
150
154
  </Accordion.Group>
151
155
 
152
- # Without the chevron
156
+ ## Without the chevron
153
157
 
154
158
  `icon={false}` drops the glyph at the row's end, for a row whose own content says it opens. A component passed as `icon` replaces the chevron and turns a quarter turn on open, so give it a right-pointing glyph.
155
159
 
@@ -174,7 +178,9 @@ Each panel drips out of the row where it was pressed (its middle, from the keybo
174
178
  </Accordion.Item>
175
179
  </Accordion.Group>
176
180
 
177
- # Rich headers
181
+ # Composition
182
+
183
+ ## Rich headers
178
184
 
179
185
  By default the trigger renders its label twice: a hidden semibold copy reserves the width, so the label gets heavier on open without moving. A header made of components would be duplicated in the DOM, so `weightShift={false}` renders it once and leaves the weight to its content. `contentClassName` is merged over the inner row's inset, padding and muted body text: the first panel below turns its text to full strength, the second takes a wider inset and the caption size. The label lives inside the trigger `<button>`, so build it from phrasing content (spans, as below), never a `div`-based header. Text that fixes its own colour stays that colour when the item opens: the chevron, the highlight and the open tint are what signal the state.
180
186
 
@@ -203,7 +209,7 @@ By default the trigger renders its label twice: a hidden semibold copy reserves
203
209
  </Accordion.Item>
204
210
  </Accordion.Group>
205
211
 
206
- # Your own trigger
212
+ ## Your own trigger
207
213
 
208
214
  `asChild` (or `render`) makes your element the trigger, so a Button, a link-styled control or anything else can open the item. It is rendered as it is: the row's label copy, the chevron and the row padding are not added, so the element brings its own content and look. Base UI still wires `aria-expanded`, `aria-controls`, `data-panel-open` and the keys onto it, so style the open state with `data-[panel-open]`.
209
215
 
@@ -1,7 +1,9 @@
1
1
  import Badge from './index'
2
2
  import ChangeDemo from './change-demo'
3
3
 
4
- # Solid
4
+ # Variants
5
+
6
+ ## Solid
5
7
 
6
8
  The default variant: a tinted background in the badge's color.
7
9
 
@@ -13,7 +15,7 @@ The default variant: a tinted background in the badge's color.
13
15
  <Badge color="rose">Poetry</Badge>
14
16
  </div>
15
17
 
16
- # Dot
18
+ ## Dot
17
19
 
18
20
  `variant="dot"` keeps a neutral bordered badge and shows the color as an
19
21
  indicator dot.
@@ -26,7 +28,7 @@ indicator dot.
26
28
  <Badge variant="dot" color="rose">Poetry</Badge>
27
29
  </div>
28
30
 
29
- # Outline
31
+ ## Outline
30
32
 
31
33
  `variant="outline"` is the dot badge's neutral frame without the dot: a label
32
34
  that carries no status.
@@ -37,22 +39,7 @@ that carries no status.
37
39
  <Badge variant="outline" size="compact">Archived</Badge>
38
40
  </div>
39
41
 
40
- # Status mapping
41
-
42
- Hosts with a shadcn-style badge map its variants onto a hue: default and
43
- secondary → `gray`, destructive → `red`, success → `green`, outline →
44
- `variant="outline"`. The tint stays a wash with foreground text, never a solid
45
- fill.
46
-
47
- <div className="flex flex-wrap items-center gap-2">
48
- <Badge color="gray">Default</Badge>
49
- <Badge color="gray">Secondary</Badge>
50
- <Badge color="red">Destructive</Badge>
51
- <Badge color="green">Success</Badge>
52
- <Badge variant="outline">Outline</Badge>
53
- </div>
54
-
55
- # Colors
42
+ ## Colors
56
43
 
57
44
  All seventeen palette colors, solid and dot.
58
45
 
@@ -73,18 +60,24 @@ All seventeen palette colors, solid and dot.
73
60
  </div>
74
61
  </div>
75
62
 
76
- # Compact
63
+ # Usage
77
64
 
78
- `size="compact"` steps the badge down the size ladder (20px tall, 11px
79
- text) for dense rows and table cells.
65
+ ## Status mapping
66
+
67
+ Hosts with a shadcn-style badge map its variants onto a hue: default and
68
+ secondary → `gray`, destructive → `red`, success → `green`, outline →
69
+ `variant="outline"`. The tint stays a wash with foreground text, never a solid
70
+ fill.
80
71
 
81
72
  <div className="flex flex-wrap items-center gap-2">
82
- <Badge size="compact" color="blue">History</Badge>
83
- <Badge size="compact" variant="dot" color="green">Philosophy</Badge>
84
- <Badge size="compact">Draft</Badge>
73
+ <Badge color="gray">Default</Badge>
74
+ <Badge color="gray">Secondary</Badge>
75
+ <Badge color="red">Destructive</Badge>
76
+ <Badge color="green">Success</Badge>
77
+ <Badge variant="outline">Outline</Badge>
85
78
  </div>
86
79
 
87
- # Changes
80
+ ## Changes
88
81
 
89
82
  Press a button. A new label springs the badge to its new width while the
90
83
  text crossfades, a new color fades in, and the dot slides in or out with
@@ -118,3 +111,14 @@ the variant. `ChangeDemo` is a docs stage: a Badge that steps through its
118
111
  ]}
119
112
  />
120
113
  </div>
114
+
115
+ ## Compact
116
+
117
+ `size="compact"` steps the badge down the size ladder (20px tall, 11px
118
+ text) for dense rows and table cells.
119
+
120
+ <div className="flex flex-wrap items-center gap-2">
121
+ <Badge size="compact" color="blue">History</Badge>
122
+ <Badge size="compact" variant="dot" color="green">Philosophy</Badge>
123
+ <Badge size="compact">Draft</Badge>
124
+ </div>