@orz/ui 0.0.0-stage → 0.0.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 (270) hide show
  1. package/LICENSE +29 -0
  2. package/README.md +541 -2
  3. package/dist/components/avatar.d.ts +14 -0
  4. package/dist/components/avatar.d.ts.map +1 -0
  5. package/dist/components/avatar.js +23 -0
  6. package/dist/components/avatar.js.map +1 -0
  7. package/dist/components/badge.d.ts +10 -0
  8. package/dist/components/badge.d.ts.map +1 -0
  9. package/dist/components/badge.js +26 -0
  10. package/dist/components/badge.js.map +1 -0
  11. package/dist/components/button.d.ts +11 -0
  12. package/dist/components/button.d.ts.map +1 -0
  13. package/dist/components/button.js +40 -0
  14. package/dist/components/button.js.map +1 -0
  15. package/dist/components/card.d.ts +12 -0
  16. package/dist/components/card.d.ts.map +1 -0
  17. package/dist/components/card.js +25 -0
  18. package/dist/components/card.js.map +1 -0
  19. package/dist/components/color-picker.d.ts +58 -0
  20. package/dist/components/color-picker.d.ts.map +1 -0
  21. package/dist/components/color-picker.js +950 -0
  22. package/dist/components/color-picker.js.map +1 -0
  23. package/dist/components/confirm-dialog.d.ts +26 -0
  24. package/dist/components/confirm-dialog.d.ts.map +1 -0
  25. package/dist/components/confirm-dialog.js +18 -0
  26. package/dist/components/confirm-dialog.js.map +1 -0
  27. package/dist/components/dialog.d.ts +26 -0
  28. package/dist/components/dialog.d.ts.map +1 -0
  29. package/dist/components/dialog.js +149 -0
  30. package/dist/components/dialog.js.map +1 -0
  31. package/dist/components/dropdown-menu.d.ts +30 -0
  32. package/dist/components/dropdown-menu.d.ts.map +1 -0
  33. package/dist/components/dropdown-menu.js +100 -0
  34. package/dist/components/dropdown-menu.js.map +1 -0
  35. package/dist/components/filter-menu.d.ts +98 -0
  36. package/dist/components/filter-menu.d.ts.map +1 -0
  37. package/dist/components/filter-menu.js +745 -0
  38. package/dist/components/filter-menu.js.map +1 -0
  39. package/dist/components/highlighted-input.d.ts +40 -0
  40. package/dist/components/highlighted-input.d.ts.map +1 -0
  41. package/dist/components/highlighted-input.js +95 -0
  42. package/dist/components/highlighted-input.js.map +1 -0
  43. package/dist/components/hover-card.d.ts +7 -0
  44. package/dist/components/hover-card.d.ts.map +1 -0
  45. package/dist/components/hover-card.js +22 -0
  46. package/dist/components/hover-card.js.map +1 -0
  47. package/dist/components/input.d.ts +4 -0
  48. package/dist/components/input.d.ts.map +1 -0
  49. package/dist/components/input.js +7 -0
  50. package/dist/components/input.js.map +1 -0
  51. package/dist/components/label.d.ts +5 -0
  52. package/dist/components/label.d.ts.map +1 -0
  53. package/dist/components/label.js +8 -0
  54. package/dist/components/label.js.map +1 -0
  55. package/dist/components/link-chip.d.ts +67 -0
  56. package/dist/components/link-chip.d.ts.map +1 -0
  57. package/dist/components/link-chip.js +71 -0
  58. package/dist/components/link-chip.js.map +1 -0
  59. package/dist/components/load-failure.d.ts +24 -0
  60. package/dist/components/load-failure.d.ts.map +1 -0
  61. package/dist/components/load-failure.js +27 -0
  62. package/dist/components/load-failure.js.map +1 -0
  63. package/dist/components/load-more.d.ts +15 -0
  64. package/dist/components/load-more.d.ts.map +1 -0
  65. package/dist/components/load-more.js +25 -0
  66. package/dist/components/load-more.js.map +1 -0
  67. package/dist/components/media-controls.d.ts +50 -0
  68. package/dist/components/media-controls.d.ts.map +1 -0
  69. package/dist/components/media-controls.js +71 -0
  70. package/dist/components/media-controls.js.map +1 -0
  71. package/dist/components/popover.d.ts +9 -0
  72. package/dist/components/popover.d.ts.map +1 -0
  73. package/dist/components/popover.js +82 -0
  74. package/dist/components/popover.js.map +1 -0
  75. package/dist/components/query-box.d.ts +78 -0
  76. package/dist/components/query-box.d.ts.map +1 -0
  77. package/dist/components/query-box.js +332 -0
  78. package/dist/components/query-box.js.map +1 -0
  79. package/dist/components/query-highlight.d.ts +14 -0
  80. package/dist/components/query-highlight.d.ts.map +1 -0
  81. package/dist/components/query-highlight.js +132 -0
  82. package/dist/components/query-highlight.js.map +1 -0
  83. package/dist/components/select.d.ts +19 -0
  84. package/dist/components/select.d.ts.map +1 -0
  85. package/dist/components/select.js +38 -0
  86. package/dist/components/select.js.map +1 -0
  87. package/dist/components/separator.d.ts +5 -0
  88. package/dist/components/separator.d.ts.map +1 -0
  89. package/dist/components/separator.js +9 -0
  90. package/dist/components/separator.js.map +1 -0
  91. package/dist/components/sheet.d.ts +16 -0
  92. package/dist/components/sheet.d.ts.map +1 -0
  93. package/dist/components/sheet.js +41 -0
  94. package/dist/components/sheet.js.map +1 -0
  95. package/dist/components/skeleton.d.ts +3 -0
  96. package/dist/components/skeleton.d.ts.map +1 -0
  97. package/dist/components/skeleton.js +7 -0
  98. package/dist/components/skeleton.js.map +1 -0
  99. package/dist/components/slider.d.ts +8 -0
  100. package/dist/components/slider.d.ts.map +1 -0
  101. package/dist/components/slider.js +14 -0
  102. package/dist/components/slider.js.map +1 -0
  103. package/dist/components/sonner.d.ts +4 -0
  104. package/dist/components/sonner.d.ts.map +1 -0
  105. package/dist/components/sonner.js +26 -0
  106. package/dist/components/sonner.js.map +1 -0
  107. package/dist/components/switch.d.ts +5 -0
  108. package/dist/components/switch.d.ts.map +1 -0
  109. package/dist/components/switch.js +8 -0
  110. package/dist/components/switch.js.map +1 -0
  111. package/dist/components/table.d.ts +11 -0
  112. package/dist/components/table.d.ts.map +1 -0
  113. package/dist/components/table.js +28 -0
  114. package/dist/components/table.js.map +1 -0
  115. package/dist/components/tabs.d.ts +8 -0
  116. package/dist/components/tabs.d.ts.map +1 -0
  117. package/dist/components/tabs.js +18 -0
  118. package/dist/components/tabs.js.map +1 -0
  119. package/dist/components/textarea.d.ts +4 -0
  120. package/dist/components/textarea.d.ts.map +1 -0
  121. package/dist/components/textarea.js +7 -0
  122. package/dist/components/textarea.js.map +1 -0
  123. package/dist/components/theme-editor/draft.d.ts +235 -0
  124. package/dist/components/theme-editor/draft.d.ts.map +1 -0
  125. package/dist/components/theme-editor/draft.js +638 -0
  126. package/dist/components/theme-editor/draft.js.map +1 -0
  127. package/dist/components/theme-editor/export-panel.d.ts +14 -0
  128. package/dist/components/theme-editor/export-panel.d.ts.map +1 -0
  129. package/dist/components/theme-editor/export-panel.js +39 -0
  130. package/dist/components/theme-editor/export-panel.js.map +1 -0
  131. package/dist/components/theme-editor/form.d.ts +69 -0
  132. package/dist/components/theme-editor/form.d.ts.map +1 -0
  133. package/dist/components/theme-editor/form.js +350 -0
  134. package/dist/components/theme-editor/form.js.map +1 -0
  135. package/dist/components/theme-editor/import-panel.d.ts +21 -0
  136. package/dist/components/theme-editor/import-panel.d.ts.map +1 -0
  137. package/dist/components/theme-editor/import-panel.js +117 -0
  138. package/dist/components/theme-editor/import-panel.js.map +1 -0
  139. package/dist/components/theme-editor/theme-list.d.ts +23 -0
  140. package/dist/components/theme-editor/theme-list.d.ts.map +1 -0
  141. package/dist/components/theme-editor/theme-list.js +42 -0
  142. package/dist/components/theme-editor/theme-list.js.map +1 -0
  143. package/dist/components/theme-editor/token-list.d.ts +30 -0
  144. package/dist/components/theme-editor/token-list.d.ts.map +1 -0
  145. package/dist/components/theme-editor/token-list.js +124 -0
  146. package/dist/components/theme-editor/token-list.js.map +1 -0
  147. package/dist/components/theme-editor/tokens.d.ts +37 -0
  148. package/dist/components/theme-editor/tokens.d.ts.map +1 -0
  149. package/dist/components/theme-editor/tokens.js +155 -0
  150. package/dist/components/theme-editor/tokens.js.map +1 -0
  151. package/dist/components/theme-editor.d.ts +26 -0
  152. package/dist/components/theme-editor.d.ts.map +1 -0
  153. package/dist/components/theme-editor.js +467 -0
  154. package/dist/components/theme-editor.js.map +1 -0
  155. package/dist/components/theme-menu.d.ts +28 -0
  156. package/dist/components/theme-menu.d.ts.map +1 -0
  157. package/dist/components/theme-menu.js +308 -0
  158. package/dist/components/theme-menu.js.map +1 -0
  159. package/dist/components/tooltip.d.ts +11 -0
  160. package/dist/components/tooltip.d.ts.map +1 -0
  161. package/dist/components/tooltip.js +18 -0
  162. package/dist/components/tooltip.js.map +1 -0
  163. package/dist/components/unsaved-changes-dialog.d.ts +39 -0
  164. package/dist/components/unsaved-changes-dialog.d.ts.map +1 -0
  165. package/dist/components/unsaved-changes-dialog.js +46 -0
  166. package/dist/components/unsaved-changes-dialog.js.map +1 -0
  167. package/dist/hooks/scroll-insets.d.ts +89 -0
  168. package/dist/hooks/scroll-insets.d.ts.map +1 -0
  169. package/dist/hooks/scroll-insets.js +265 -0
  170. package/dist/hooks/scroll-insets.js.map +1 -0
  171. package/dist/hooks/use-element-height.d.ts +11 -0
  172. package/dist/hooks/use-element-height.d.ts.map +1 -0
  173. package/dist/hooks/use-element-height.js +33 -0
  174. package/dist/hooks/use-element-height.js.map +1 -0
  175. package/dist/hooks/use-media-query.d.ts +22 -0
  176. package/dist/hooks/use-media-query.d.ts.map +1 -0
  177. package/dist/hooks/use-media-query.js +38 -0
  178. package/dist/hooks/use-media-query.js.map +1 -0
  179. package/dist/hooks/use-overlay-scrollbars.d.ts +31 -0
  180. package/dist/hooks/use-overlay-scrollbars.d.ts.map +1 -0
  181. package/dist/hooks/use-overlay-scrollbars.js +80 -0
  182. package/dist/hooks/use-overlay-scrollbars.js.map +1 -0
  183. package/dist/hooks/use-paged-append.d.ts +7 -0
  184. package/dist/hooks/use-paged-append.d.ts.map +1 -0
  185. package/dist/hooks/use-paged-append.js +53 -0
  186. package/dist/hooks/use-paged-append.js.map +1 -0
  187. package/dist/hooks/use-query-completion.d.ts +27 -0
  188. package/dist/hooks/use-query-completion.d.ts.map +1 -0
  189. package/dist/hooks/use-query-completion.js +100 -0
  190. package/dist/hooks/use-query-completion.js.map +1 -0
  191. package/dist/hooks/use-slash-shortcut.d.ts +9 -0
  192. package/dist/hooks/use-slash-shortcut.d.ts.map +1 -0
  193. package/dist/hooks/use-slash-shortcut.js +38 -0
  194. package/dist/hooks/use-slash-shortcut.js.map +1 -0
  195. package/dist/hooks/viewport-chrome.d.ts +50 -0
  196. package/dist/hooks/viewport-chrome.d.ts.map +1 -0
  197. package/dist/hooks/viewport-chrome.js +170 -0
  198. package/dist/hooks/viewport-chrome.js.map +1 -0
  199. package/dist/index.d.ts +2 -0
  200. package/dist/index.d.ts.map +1 -0
  201. package/dist/index.js +2 -0
  202. package/dist/index.js.map +1 -0
  203. package/dist/lib/cell-width-table.d.ts +3 -0
  204. package/dist/lib/cell-width-table.d.ts.map +1 -0
  205. package/dist/lib/cell-width-table.js +6 -0
  206. package/dist/lib/cell-width-table.js.map +1 -0
  207. package/dist/lib/cell-width.d.ts +7 -0
  208. package/dist/lib/cell-width.d.ts.map +1 -0
  209. package/dist/lib/cell-width.js +50 -0
  210. package/dist/lib/cell-width.js.map +1 -0
  211. package/dist/lib/clipboard.d.ts +8 -0
  212. package/dist/lib/clipboard.d.ts.map +1 -0
  213. package/dist/lib/clipboard.js +19 -0
  214. package/dist/lib/clipboard.js.map +1 -0
  215. package/dist/lib/color.d.ts +40 -0
  216. package/dist/lib/color.d.ts.map +1 -0
  217. package/dist/lib/color.js +177 -0
  218. package/dist/lib/color.js.map +1 -0
  219. package/dist/lib/scroll-map.d.ts +20 -0
  220. package/dist/lib/scroll-map.d.ts.map +1 -0
  221. package/dist/lib/scroll-map.js +46 -0
  222. package/dist/lib/scroll-map.js.map +1 -0
  223. package/dist/lib/text-metrics.d.ts +33 -0
  224. package/dist/lib/text-metrics.d.ts.map +1 -0
  225. package/dist/lib/text-metrics.js +89 -0
  226. package/dist/lib/text-metrics.js.map +1 -0
  227. package/dist/lib/theme-file.d.ts +51 -0
  228. package/dist/lib/theme-file.d.ts.map +1 -0
  229. package/dist/lib/theme-file.js +511 -0
  230. package/dist/lib/theme-file.js.map +1 -0
  231. package/dist/lib/unsaved-guard.d.ts +33 -0
  232. package/dist/lib/unsaved-guard.d.ts.map +1 -0
  233. package/dist/lib/unsaved-guard.js +37 -0
  234. package/dist/lib/unsaved-guard.js.map +1 -0
  235. package/dist/lib/utils.d.ts +3 -0
  236. package/dist/lib/utils.d.ts.map +1 -0
  237. package/dist/lib/utils.js +6 -0
  238. package/dist/lib/utils.js.map +1 -0
  239. package/dist/theme/custom.d.ts +8 -0
  240. package/dist/theme/custom.d.ts.map +1 -0
  241. package/dist/theme/custom.js +56 -0
  242. package/dist/theme/custom.js.map +1 -0
  243. package/dist/theme/index.d.ts +6 -0
  244. package/dist/theme/index.d.ts.map +1 -0
  245. package/dist/theme/index.js +5 -0
  246. package/dist/theme/index.js.map +1 -0
  247. package/dist/theme/paint-guard.d.ts +20 -0
  248. package/dist/theme/paint-guard.d.ts.map +1 -0
  249. package/dist/theme/paint-guard.js +120 -0
  250. package/dist/theme/paint-guard.js.map +1 -0
  251. package/dist/theme/picks.d.ts +22 -0
  252. package/dist/theme/picks.d.ts.map +1 -0
  253. package/dist/theme/picks.js +43 -0
  254. package/dist/theme/picks.js.map +1 -0
  255. package/dist/theme/pref.d.ts +39 -0
  256. package/dist/theme/pref.d.ts.map +1 -0
  257. package/dist/theme/pref.js +129 -0
  258. package/dist/theme/pref.js.map +1 -0
  259. package/dist/theme/provider.d.ts +41 -0
  260. package/dist/theme/provider.d.ts.map +1 -0
  261. package/dist/theme/provider.js +103 -0
  262. package/dist/theme/provider.js.map +1 -0
  263. package/dist/theme/store.d.ts +104 -0
  264. package/dist/theme/store.d.ts.map +1 -0
  265. package/dist/theme/store.js +509 -0
  266. package/dist/theme/store.js.map +1 -0
  267. package/package.json +94 -3
  268. package/styles.css +79 -0
  269. package/theme.css +186 -0
  270. package/vendor/shadcn-tailwind.css +664 -0
package/LICENSE ADDED
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright 2026 the orzui team
4
+ Copyright 2026 the todou team
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package/README.md CHANGED
@@ -1,3 +1,542 @@
1
- # Temporary Holding Version
1
+ # @orz/ui
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ React components, hooks and a theme store, built on shadcn's radix-nova primitives and Tailwind
4
+ CSS v4. The theme data itself lives in `@orz/themes`.
5
+
6
+ ## Install
7
+
8
+ ```bash
9
+ pnpm add @orz/ui react react-dom radix-ui lucide-react sonner
10
+ # only for @orz/ui/hooks/use-overlay-scrollbars:
11
+ pnpm add overlayscrollbars
12
+ ```
13
+
14
+ `radix-ui`, `sonner`, `lucide-react` and React are peer dependencies. They have to be the app's
15
+ own copies, because radix's dismissable-layer and focus stacks and sonner's toast store are module
16
+ state. If a second copy sat beside the app's, Escape and outside clicks would stop working between
17
+ a dialog from one copy and a menu from the other, and `toast()` would not reach `<Toaster />`.
18
+
19
+ ## CSS
20
+
21
+ ```css
22
+ @import "tailwindcss";
23
+ @import "@orz/ui/styles.css";
24
+ ```
25
+
26
+ `styles.css` brings the baseline tokens, the base layer, `tw-animate-css` and shadcn's
27
+ Tailwind variants. It also registers `@source "./dist"` on its own, so Tailwind finds the classes
28
+ used inside the package without any `@source` line from you.
29
+
30
+ If you keep your own copy of the tokens and do not import `styles.css`, point Tailwind at the
31
+ package yourself:
32
+
33
+ ```css
34
+ @source "../node_modules/@orz/ui/dist";
35
+ ```
36
+
37
+ `styles.css` is `theme.css` plus the page-level rules: the base layer (`* { border-color }`,
38
+ `body` colours, `html` font), the overlayscrollbars theme, `revealBlock`'s `.anchor-flash` and
39
+ `@source "./dist"`. A surface that
40
+ shares a document with someone else's UI, and must not restyle it, imports
41
+ `@orz/ui/theme.css` instead: the tokens, `.dark`, `tw-animate-css` and shadcn's variants, nothing
42
+ global. `theme.css` carries no `@source`, so that surface names the files Tailwind scans itself.
43
+
44
+ `styles.css` does not load a font. `--font-sans` names `'Geist Variable'` first and falls back to
45
+ the system UI font, so install `@fontsource-variable/geist` (or define `--font-sans`) if you want it.
46
+ `useOverlayScrollbars` needs the library's own stylesheet, which you import yourself:
47
+ `@import "overlayscrollbars/overlayscrollbars.css";`.
48
+
49
+ ## Theme
50
+
51
+ Importing anything from `@orz/ui` has no side effects. Creating a theme store does: it reads the
52
+ stored pref, paints it, and starts following the OS and other tabs. The store takes its themes as
53
+ input, so an app ships only the themes it passes.
54
+
55
+ ```tsx
56
+ import { builtinThemes } from "@orz/themes";
57
+ import { Toaster } from "@orz/ui/components/sonner";
58
+ import { createThemeStore, ThemeProvider } from "@orz/ui/theme";
59
+
60
+ const themes = createThemeStore({
61
+ storageKey: "app-theme",
62
+ themes: builtinThemes,
63
+ });
64
+
65
+ createRoot(root).render(
66
+ <ThemeProvider store={themes}>
67
+ <App />
68
+ <Toaster />
69
+ </ThemeProvider>,
70
+ );
71
+ ```
72
+
73
+ The store has two slots, and an app enables either or both by handing over a list for it:
74
+
75
+ - `themes` is the UI slot. Its pick paints the page.
76
+ - `terminalThemes` is the terminal slot. It may also hold terminal-only themes from
77
+ `@orz/themes`. Without a terminal pick, the terminal follows the UI pick: same theme, color
78
+ scheme and variant.
79
+
80
+ A store with only a UI slot still writes the UI pick's terminal tokens, so an ANSI-escape renderer
81
+ anywhere on the page works without a slot of its own. A store with only a terminal slot writes the
82
+ terminal tokens alone and leaves `color-scheme`, `.dark` and `data-theme` to the page.
83
+
84
+ The pick is one theme, a mode and an optional variant per slot:
85
+
86
+ ```ts
87
+ store.setPref({
88
+ ui: { theme: "github", mode: "light" },
89
+ terminal: { theme: "dracula", mode: "dark", variant: "amoled" },
90
+ });
91
+ ```
92
+
93
+ - `mode` is `"system"`, `"light"` or `"dark"`. On a theme with one palette it is always that
94
+ palette's side.
95
+ - `variant` names one variant for the whole theme. A palette that does not define it shows its
96
+ default, so AMOLED Solarized under `system` is default Solarized Light by day and AMOLED Solarized
97
+ Dark at night.
98
+ - Every read and write normalizes the pref against the current lists: an unknown theme falls back
99
+ to the first theme of the list the app passed, under `system`, or to following for a terminal
100
+ pick.
101
+ - `setThemes(list)` and `setTerminalThemes(list)` replace those lists at runtime. A pick whose theme
102
+ leaves the list comes back when the theme returns.
103
+
104
+ The store paints through one `<style data-orz-theme>` element in `<head>`, holding a single
105
+ `:root { … }` rule rendered from the data. It also toggles `.dark` and sets `data-theme`,
106
+ `data-variant` and `data-terminal-theme` on the root. Given a `root` other than `<html>`, the store
107
+ keeps an element of its own, sets `data-orz-theme-root` on that root to an id unique to the store,
108
+ and scopes the rule to it, so stores on different roots never repaint each other. The rule has
109
+ plain `:root` specificity, so a stylesheet loaded later, or a `:root:root` selector, can still
110
+ retune a token for every theme.
111
+
112
+ - `useTheme()` returns `{ pref, themes, theme, colorScheme, variant, setPref }`: the UI slot's
113
+ normalized pref and list, the resolved theme object, the color scheme on screen, and the variant
114
+ that side shows. Outside a provider it reports the default theme, light, and ignores `setPref`.
115
+ - `useTerminalTheme()` returns `{ following, themes, theme, colorScheme, variant, tokens }` for the
116
+ terminal slot.
117
+ - Both also return `preview`, `customThemes` and `setCustomThemes` (below). Outside a provider the
118
+ list is empty and the two calls are ignored.
119
+ - `useThemeSlots()` returns `{ ui, terminal }`: which slots the store has. The two hooks fall back
120
+ where theirs is missing, so they cannot tell you.
121
+
122
+ ### Theme menu
123
+
124
+ `<ThemeMenu />` from `@orz/ui/components/theme-menu` is a ready-made picker: an icon button opening
125
+ a filter box, an Auto / Light / Dark switch, and the themes, each with its variants in a submenu.
126
+ Whatever is highlighted previews on the whole page after resting 80 ms, and a pick commits it.
127
+
128
+ ```tsx
129
+ <ThemeMenu /> // the UI pick
130
+ <ThemeMenu slot="terminal" /> // the terminal pick, e.g. on a terminal pane
131
+ <ThemeMenu onCustomize={(id, slot) => openEditor(id, slot)} />
132
+ ```
133
+
134
+ - `slot` is `"ui"` (default) or `"terminal"`. A picker whose slot the store lacks renders nothing.
135
+ The terminal picker adds a "Same as UI" row and a Terminal only group.
136
+ - `onCustomize` adds a "Custom themes…" row, which calls it with no id, and an edit button on each
137
+ custom theme's row, which calls it with that theme's id. The second argument is the menu's slot,
138
+ so a new theme opened from the terminal menu is a terminal theme; a callback that takes only the
139
+ id still fits. The app brings the editor, such as `ThemeEditor` below.
140
+
141
+ `trigger` replaces the icon button, for a toolbar with buttons of its own look. It gets the icon
142
+ the default button would show (it follows the mode in the UI picker) and the menu's name, "Theme"
143
+ or "Terminal theme", which the element should carry as its accessible name. The element goes
144
+ through Radix's `PopoverTrigger asChild`, so it must take a ref and spread the props it is given
145
+ onto a DOM element.
146
+
147
+ ```tsx
148
+ <ThemeMenu
149
+ trigger={({ Icon, label }) => (
150
+ <button type="button" aria-label={label} title={label} className="…">
151
+ <Icon className="size-3.5" />
152
+ </button>
153
+ )}
154
+ />
155
+ ```
156
+
157
+ ### Custom themes
158
+
159
+ The store keeps the user's custom themes from `@orz/themes` in one list of two kinds, and lists the
160
+ ones that compose ahead of each slot's own list, in list order:
161
+
162
+ - a `CustomTheme` (no `kind`) is composed with `composeTheme` and goes into both slots. In a store with no UI slot it stays in the list and shows nowhere;
163
+ - a `CustomTerminalTheme` (`kind: "terminal"`), made of terminal colours alone, is composed with
164
+ `composeTerminalTheme` and goes into the terminal slot only. In a store with no terminal slot it
165
+ stays in the list and shows nowhere.
166
+
167
+ ```ts
168
+ const { customThemes, setCustomThemes } = useTheme();
169
+ setCustomThemes([...customThemes, draft]);
170
+ ```
171
+
172
+ - `getCustomThemes()` / `setCustomThemes(list)` on the store, typed `AnyCustomTheme`.
173
+ `setCustomThemes` throws on an id that repeats across both kinds or that a theme in either list
174
+ already has.
175
+ - `setThemes` and `setTerminalThemes` replace the app's own lists; the custom themes are composed
176
+ again against them.
177
+ - A custom theme whose bases are gone stays in `getCustomThemes()` but out of the lists, so an
178
+ editor can still show and repair it.
179
+ - The list is stored as JSON under `customStorageKey` (default `` `${storageKey}-custom` ``) and
180
+ follows other tabs. Entries that do not have the shape of either kind, or carry a `kind` this
181
+ version does not know, are dropped on read, and the key is only written by `setCustomThemes`.
182
+ - `persistCustomThemes: false` keeps the store away from that key altogether. The app then loads its
183
+ list with `setCustomThemes` and saves it from `subscribe` and `getCustomThemes`, for example to its
184
+ server.
185
+ - `persistPref: false` does the same for the pref under `storageKey`: the store never writes the
186
+ pref and ignores other tabs' writes to it. The app then commits picks itself with `setPref`, for
187
+ example from a URL. Until it does, the store shows the pick the paint cache last painted (the
188
+ first theme when there is none), so it does not repaint over the [paint guard](#paint-guard).
189
+ An app whose source holds no pick, such as a bare URL, commits its default with `setPref` rather
190
+ than relying on the store's. The paint cache and the custom themes are unaffected.
191
+
192
+ ### Custom theme editor
193
+
194
+ `<ThemeEditor />` from `@orz/ui/components/theme-editor` is the editor `ThemeMenu`'s
195
+ `onCustomize` opens: a panel docked to the right, with the page left visible and repainted with the
196
+ draft as it changes. Each side of a theme is based on a theme from the store's lists with
197
+ overrides, fully custom, or absent; its terminal colours follow the base or are edited the same
198
+ way.
199
+
200
+ A store with a terminal slot also gets terminal themes, in a layout of their own: Light and Dark
201
+ tabs, each side None, based on a palette (with its scheme and variant) or fully custom, over the
202
+ terminal colours. A terminal theme with one side serves both modes. A terminal-only store edits
203
+ terminal themes only; a store with both slots lists the two kinds in two groups, and **+ New** asks
204
+ which to make.
205
+
206
+ ```tsx
207
+ const [editing, setEditing] = useState<{ id?: string; slot: "ui" | "terminal" } | null>(null);
208
+
209
+ <ThemeMenu onCustomize={(id, slot) => setEditing({ id, slot })} />
210
+ <ThemeMenu slot="terminal" onCustomize={(id, slot) => setEditing({ id, slot })} />
211
+ <ThemeEditor
212
+ open={editing !== null}
213
+ themeId={editing?.id}
214
+ kind={editing?.slot}
215
+ onOpenChange={(open) => { if (!open) setEditing(null); }}
216
+ />
217
+ ```
218
+
219
+ - `themeId` opens that custom theme; without one, with an id the store does not hold, or with a
220
+ theme of a kind the store cannot show, the editor starts a new theme from what the page shows (a
221
+ terminal theme from what the terminal shows).
222
+ - `kind` is the kind of that new theme. Unset, or naming a slot the store lacks, it is a UI theme
223
+ when the store has a UI slot, else a terminal theme.
224
+ - It reads and writes the store's custom list (`customThemes` / `setCustomThemes`), so the themes
225
+ are stored wherever the store keeps that list: under `customStorageKey`, or wherever the app saves
226
+ it with `persistCustomThemes: false`. A kind the store cannot show is left out of the editor and
227
+ written back untouched. Save also makes a UI theme the UI pick, and a terminal theme the terminal
228
+ pick, leaving the UI pick alone.
229
+ - `onOpenChange(false)` comes after Save, or after the unsaved-changes prompt resolves to Discard.
230
+ While the draft has unsaved edits it is registered with `@orz/ui/lib/unsaved-guard`, so the app's
231
+ leave-page guard covers it.
232
+
233
+ Import… and Export carry themes as JSON files (`format: "orz-themes"`, `version: 1`). UI themes go
234
+ in `themes`, always written; terminal themes in `terminalThemes`, written only when there is one. An
235
+ entry is a custom theme without its id or kind, and every based side or terminal also carries the
236
+ colours it showed at export, so a theme whose base the importing app lacks arrives as fully custom
237
+ instead of being lost. Import follows the store's slots: a terminal theme is refused where there is
238
+ no terminal, and a UI theme arrives in a terminal-only store as a terminal theme made from the
239
+ terminal colours each side shows. `serializeThemeFile`, `parseThemeFile` and `uniqueLabel` in `@orz/ui/lib/theme-file` read and
240
+ write the format with no React or DOM, for example to validate uploads on a server.
241
+
242
+ ### Preview
243
+
244
+ `preview(next)` shows a pick without committing it, and `preview(null)` shows the committed pick
245
+ again. It is what `ThemeMenu` calls on a highlight.
246
+
247
+ ```ts
248
+ store.preview({ ui: { theme: "dracula", mode: "dark" } });
249
+ store.preview({ terminal: "follow" }); // the terminal following the UI
250
+ store.preview({ ui: { theme: composeTheme(draft, themes), mode } }); // an unsaved theme
251
+ ```
252
+
253
+ - A slot left out of `next` shows its committed pick. A theme passed as an object need not be in
254
+ any list; for `ui` it must be a UI theme (`isUiTheme`), or `preview` throws.
255
+ - It paints exactly what `setPref` paints, and writes neither `storageKey` nor the paint cache, so
256
+ other tabs and a reload see only the committed pick.
257
+ - While it is active, `getResolved()`, `getTerminal()` and the hooks report what is shown, so
258
+ everything themed follows the preview; `getPref()` and the hooks' `pref` stay the committed pick.
259
+ - `setPref` and `dispose` clear it. A previewed theme that leaves its list falls back to the
260
+ committed pick.
261
+
262
+ ### Terminals
263
+
264
+ The terminal tokens are CSS variables (`--terminal-background`, `--terminal-foreground`,
265
+ `--terminal-cursor`, `--terminal-selection`, and `--ansi-black` through `--ansi-bright-white`), and
266
+ Tailwind classes such as `bg-terminal-background` and `text-ansi-red`. An ANSI-escape renderer can
267
+ use the classes directly. xterm.js takes the resolved tokens:
268
+
269
+ ```tsx
270
+ import { xtermTheme } from "@orz/themes";
271
+ import { useTerminalTheme } from "@orz/ui/theme";
272
+
273
+ const { tokens } = useTerminalTheme();
274
+ useEffect(() => {
275
+ terminal.options.theme = xtermTheme(tokens);
276
+ }, [terminal, tokens]);
277
+ ```
278
+
279
+ ### Storage
280
+
281
+ `storageKey` holds the pref as JSON:
282
+ `{"ui":{"theme":"github","mode":"light"},"terminal":{"theme":"dracula","mode":"dark"}}`. A default
283
+ variant and a following terminal are left out.
284
+
285
+ Prefs stored by earlier versions of `@orz/ui` are read and rewritten in this shape on the next
286
+ write. A plain slug such as `solarized-dark` becomes `{ theme: "solarized", mode: "dark" }`, and
287
+ `system`, `light` and `dark` map onto the `default` theme. An app that keeps its old `storageKey`
288
+ keeps its users' themes.
289
+
290
+ When the app keeps the pick on its server, keep `persistPref` on and hand the server's answer to
291
+ `setPref` when it arrives. Until then the store shows the copy in `storageKey`, so a reload paints
292
+ the last pick at once, and the server's answer repaints only when another device changed it. Save
293
+ the user's picks from `subscribe` and `getPref`. `persistPref: false` is for an app whose pick must
294
+ not have a second source, such as one held in the URL.
295
+
296
+ ### Paint guard
297
+
298
+ Without a guard, a reload in a dark theme flashes white before the bundle runs. The store caches
299
+ the declarations it painted under `paintKey` (default `` `${storageKey}-paint` ``), one per side the
300
+ slot's mode can show. `themePaintGuardScript()` returns an inline script that replays them before
301
+ the bundle loads, choosing the side from the OS when both are cached, so neither the guard nor the
302
+ page needs a theme list. The store adopts the guard's `<style>` element when it starts. Give the
303
+ guard the same keys as the store:
304
+
305
+ ```ts
306
+ // vite.config.ts
307
+ import { themePaintGuardScript } from "@orz/ui/theme";
308
+
309
+ export default defineConfig({
310
+ plugins: [
311
+ {
312
+ name: "theme-paint-guard",
313
+ transformIndexHtml: () => [
314
+ {
315
+ tag: "script",
316
+ children: themePaintGuardScript({ storageKey: "app-theme" }),
317
+ injectTo: "head-prepend",
318
+ },
319
+ ],
320
+ },
321
+ ],
322
+ });
323
+ ```
324
+
325
+ On the first load after an upgrade there is no cache yet. The guard then sets `.dark` from the
326
+ `` `${storageKey}-kind` `` value earlier versions stored, and the store writes the cache.
327
+
328
+ Under a UI pick, the guard's rule also sets `background-color: var(--background)` on `<html>`, so
329
+ frames painted before the stylesheet loads already show the theme's background. The store's rule
330
+ leaves it out, so once the store starts, `body`'s background reaches the canvas as usual.
331
+
332
+ A page that cannot run an inline script, under a Content Security Policy without
333
+ `'unsafe-inline'` for example, can have its server paint the background instead. Given
334
+ `paintCookie: "app-theme-paint"`, the store also writes the committed UI pick's background for each
335
+ side it can show to that cookie (path `/`, one year, `SameSite=Lax`). `themePaintCookieStyle(value)`
336
+ turns the cookie's value, URL-encoded or not, into CSS for a `<style>` in `<head>`: the background
337
+ and `color-scheme` on `html`, with the dark side under `@media (prefers-color-scheme: dark)` when
338
+ both are stored. A side whose colour could break out of the rule is dropped, and a missing or
339
+ malformed value gives `""`.
340
+
341
+ ```ts
342
+ // a server template
343
+ const css = themePaintCookieStyle(request.cookies["app-theme-paint"]);
344
+ html`<head><style>${css}</style>…</head>`;
345
+ ```
346
+
347
+ ## Query input
348
+
349
+ A search box that colours its own query and completes it, driven by an `@orz/query` schema.
350
+ `@orz/query` is a dependency of this package, so it is installed with it; import the schema
351
+ helpers from it directly.
352
+
353
+ | Import | Contents |
354
+ |---|---|
355
+ | `@orz/ui/components/highlighted-input` | `HighlightedInput`: a one-line input whose text is painted by a mirror layer underneath, so it can be coloured while selection, undo, autofill and IME composition stay native |
356
+ | `@orz/ui/components/query-highlight` | `highlightQuery(text, parts, schema, providers)`: the spans `HighlightedInput` paints, with keys, punctuation and values coloured by verdict |
357
+ | `@orz/ui/hooks/use-query-completion` | `useQueryCompletion({ schema, value, caret, providers, enabled })`: key and value completions at the caret; a `search` provider is asked after 150 ms of quiet, and its answers are cached per prefix |
358
+ | `@orz/ui/components/query-box` | `QueryBox`: the three above as a combobox, with the app's own rows (history, jumps) as `RowSource`s |
359
+
360
+ ```tsx
361
+ import { defineQuerySchema } from "@orz/query";
362
+ import { QueryBox } from "@orz/ui/components/query-box";
363
+ import { SearchIcon } from "lucide-react";
364
+
365
+ const schema = defineQuerySchema({
366
+ qualifiers: {
367
+ is: { value: { kind: "enum", values: ["open", "closed"] }, hint: "open or closed" },
368
+ label: { value: { kind: "named", source: "label" }, hint: "a label name" },
369
+ },
370
+ });
371
+
372
+ <QueryBox
373
+ schema={schema}
374
+ providers={{ label: { options: labels?.map((name) => ({ value: name })) } }}
375
+ value={value}
376
+ onValueChange={setValue}
377
+ onSubmit={(query) => navigate(`/search?q=${encodeURIComponent(query)}`)}
378
+ renderSubmitRow={(query, option) => <div {...option}>Search for “{query}”</div>}
379
+ leading={<SearchIcon />}
380
+ inputProps={{ "aria-label": "Search", placeholder: "Search…" }}
381
+ />;
382
+ ```
383
+
384
+ Nothing in the panel is preselected, so Enter submits the query as typed unless the reader arrowed
385
+ onto a row. Tab takes the highlighted completion, or the first one. Escape closes the panel, and a
386
+ second Escape goes to `onEscape`. Pass `options: undefined` for a list that has not loaded: its
387
+ values paint as valid rather than flashing amber, and `onOpenChange` tells you when the box is in
388
+ use, so loading can wait until then.
389
+
390
+ ## Filter menu
391
+
392
+ `@orz/ui/components/filter-menu` exports `FilterMenu`: a popover with a filter box over a list of
393
+ sections, for a pick from more rows than a menu should make anyone scroll through. Focus stays in the
394
+ box, so typing always filters; ↑↓, Home and End move a highlight that `aria-activedescendant`
395
+ follows, Enter picks, Escape closes, and Tab closes and moves on. The caller does the filtering and
396
+ passes the sections in, so it decides what a match means (aliases, ids, other fields);
397
+ `matchText(text, query)` and `MatchedText` fold case and diacritics, and mark the hit.
398
+
399
+ ```tsx
400
+ import { FilterMenu, MatchedText, matchText } from "@orz/ui/components/filter-menu";
401
+
402
+ const [query, setQuery] = useState("");
403
+ const rows = FRUIT.filter((fruit) => matchText(fruit.label, query));
404
+
405
+ <FilterMenu
406
+ query={query}
407
+ onQueryChange={setQuery}
408
+ sections={[{ id: "fruit", label: "Fruit", rows }]}
409
+ selectedId={fruit}
410
+ onSelect={(row) => setFruit(row.id)}
411
+ renderRow={(row) => <MatchedText text={row.label} query={query} />}
412
+ inputLabel="Filter fruit"
413
+ >
414
+ <Button variant="outline">{label}</Button>
415
+ </FilterMenu>;
416
+ ```
417
+
418
+ On opening, the query is cleared and the highlight rests on `selectedId`. `onHighlight(row)` reports
419
+ each highlight the user moves to, and `onHighlight(null)` a return to the committed state (the
420
+ highlight resting on `selectedId`, the pointer leaving the list, the menu closing), which is what a
421
+ live preview needs. A control in `header` that commits at once puts the highlight back through the
422
+ `ref`'s `resetHighlight()`.
423
+
424
+ Rows can carry a submenu: `getSubmenu(row)` returns its rows (ids unique across the whole menu), and
425
+ `selectedSubmenuId` checks one of them. The submenu opens beside a row the user highlights, by
426
+ pointer or keys, but not for the highlight resting on `selectedId` or on the query's first hit. →
427
+ with the caret at the end of the query enters it, ← and Escape come back, and `onHighlight` and
428
+ `onSelect` receive its rows like any other. The pointer can cut across other rows on its way to the
429
+ submenu without moving the highlight. A row with a submenu is a split button: clicking the row picks
430
+ it, and clicking its chevron, past the divider, opens the submenu instead. `submenuLabel(row)` names
431
+ the submenu's listbox.
432
+
433
+ ## Colour picker
434
+
435
+ `ColorPicker` is the panel, rendered inline; `ColorPickerPopover` puts it behind a trigger that
436
+ shows the value, or behind your own trigger passed as `children`. Both come from
437
+ `@orz/ui/components/color-picker`. The colour maths they use is `@orz/ui/lib/color`: `parseColor`,
438
+ `formatColor`, `gamutOf`, `mapToSrgb`, `maxChroma` and `contrastRatio`, on oklch values.
439
+ `maxChroma(l, h, gamut)` is the largest chroma at a lightness and hue that stays inside `"srgb"` or
440
+ `"p3"`.
441
+
442
+ ```tsx
443
+ import { ColorPickerPopover } from "@orz/ui/components/color-picker";
444
+
445
+ <ColorPickerPopover
446
+ value={token}
447
+ onValueChange={preview}
448
+ onValueCommit={save}
449
+ format="oklch"
450
+ contrastWith={background}
451
+ contrastLabel="background"
452
+ swatches={[{ label: "This theme", colors: tokens }]}
453
+ />;
454
+ ```
455
+
456
+ - `value` is any CSS colour. The panel follows it, except that the parent echoing the panel's own
457
+ output changes nothing, so the panel keeps full precision between strings.
458
+ - `onValueChange` fires on every drag frame and every keystroke that parses; `onValueCommit` when
459
+ an interaction ends (pointer release, a keyboard step, Enter or blur, a swatch, the eyedropper).
460
+ Preview on change, save on commit.
461
+ - The output keeps the incoming value's format when that is oklch, hex, rgb or hsl, and is oklch
462
+ otherwise; `format` pins it. hex, rgb and hsl cannot leave sRGB, so they are gamut-mapped.
463
+ `alpha={false}` makes every output opaque.
464
+ - Three picking surfaces, all at the current hue:
465
+ - `oklch`: lightness up, and across, chroma as a share of the largest chroma the `gamut` holds at
466
+ that lightness, so every point is a colour inside it. A colour beyond the gamut sits pinned to
467
+ the right edge with a dashed ring, and keeps its value until the area is touched.
468
+ - `absolute`: lightness up, and across, chroma itself, the axis fitted to the hue's Display P3
469
+ wedge. The sRGB and P3 edges are drawn, and past P3 is a faded veil. It is the surface for exact
470
+ chroma and for colours outside sRGB.
471
+ - `hsv`: the HSV square.
472
+
473
+ The default is `oklch` when the output is oklch and `hsv` otherwise; `surfaces` limits them, and
474
+ `surface` with `onSurfaceChange` controls the choice if you want to persist it.
475
+ - `gamut` (`"srgb"`, the default, or `"p3"`) bounds the `oklch` surface and the chroma lock.
476
+ - `resetValue` shows a reset button beside the text field that commits that value. The prop is not
477
+ `defaultValue`, which React reads as an uncontrolled input's initial value.
478
+ - The chroma lock, a toggle beside the hue slider on the oklch surfaces, keeps chroma's share of
479
+ the gamut's maximum while the hue slider or the L and H fields move, so a vivid colour stays
480
+ vivid instead of falling out of the gamut. `chromaLock` / `defaultChromaLock` /
481
+ `onChromaLockChange` control it.
482
+ - `extra` renders at the bottom of the panel, for a consumer's own controls.
483
+
484
+ Importing either entry point brings in [culori](https://culorijs.org) (about 9 KB gzipped, only the
485
+ modes the picker uses); nothing else in `@orz/ui` does.
486
+
487
+ ## Media controls
488
+
489
+ `MediaControls`, from `@orz/ui/components/media-controls`, is the control bar under a media frame:
490
+ play, time, seek, volume, speed, picture-in-picture and fullscreen, in that order. It plays nothing
491
+ itself; the player passes the values in and acts on the callbacks. `@orz/ui-asciinema`'s cast
492
+ player and `@orz/ui-content`'s `VideoPlayer` and `AudioPlayer` draw it.
493
+
494
+ ```tsx
495
+ import { MediaControls } from "@orz/ui/components/media-controls";
496
+
497
+ <MediaControls
498
+ playing={playing}
499
+ time={time}
500
+ duration={duration}
501
+ speed={speed}
502
+ onToggle={toggle}
503
+ onSeek={seek}
504
+ onSpeed={setSpeed}
505
+ buffered={[[0, 42]]}
506
+ volume={{ level, muted, onLevel: setLevel, onMute: toggleMute }}
507
+ fullscreen={{ active: fullscreen, onToggle: toggleFullscreen }}
508
+ portal={fullscreen ? frame : undefined}
509
+ />;
510
+ ```
511
+
512
+ - `time` and `duration` are seconds. A `duration` that is not finite, as a recorded WebM reports
513
+ until it has been read to the end, disables the seek track and shows the time alone.
514
+ - Dragging the seek track moves only the time shown; `onSeek` fires once, on release, and a value
515
+ within a step of the end snaps to the end.
516
+ - `markers` are ticks over the track that seek to their time; `buffered` ranges are shaded behind
517
+ the played range.
518
+ - `volume`, `fullscreen` and `pictureInPicture` are each one object of value and handler; leave one
519
+ out and its control is not drawn. `onLevel` only reports a level: whether dragging a muted
520
+ volume unmutes is the player's call.
521
+ - From 24rem wide, volume is a mute button beside a level slider; below that, a single button opens
522
+ a popover with a vertical slider and the mute button. A container query makes the switch, so
523
+ nothing is measured.
524
+ - `portal` is where the popover, the speed list and the tooltips render: the frame, while it is
525
+ fullscreen, or they would open outside it. `MEDIA_SPEEDS` lists the speeds offered, and a speed
526
+ outside it joins the list. `formatTime(seconds, long?)` writes `m:ss`, or `h:mm:ss`.
527
+
528
+ ## Entry points
529
+
530
+ | Import | Contents |
531
+ |---|---|
532
+ | `@orz/ui` | `cn` |
533
+ | `@orz/ui/components/<name>` | `avatar`, `badge`, `button`, `card`, `color-picker`, `confirm-dialog`, `dialog`, `dropdown-menu`, `filter-menu`, `highlighted-input`, `hover-card`, `input`, `label`, `link-chip`, `load-failure`, `load-more`, `media-controls`, `popover`, `query-box`, `query-highlight`, `select`, `separator`, `sheet`, `skeleton`, `sonner`, `switch`, `table`, `tabs`, `textarea`, `theme-editor`, `theme-menu`, `tooltip`, `unsaved-changes-dialog` |
534
+ | `@orz/ui/hooks/<name>` | `scroll-insets`, `use-element-height`, `use-media-query`, `use-overlay-scrollbars`, `use-paged-append`, `use-query-completion`, `use-slash-shortcut` |
535
+ | `@orz/ui/lib/<name>` | `cell-width`, `clipboard`, `color`, `scroll-map`, `text-metrics`, `theme-file`, `unsaved-guard`, `utils` |
536
+ | `@orz/ui/theme` | `createThemeStore`, `ThemeProvider`, `useTheme`, `useTerminalTheme`, `useThemeSlots`, `themePaintGuardScript`, `normalizePref`, `parseStoredPref`, types (the theme data types re-exported from `@orz/themes`) |
537
+ | `@orz/ui/styles.css` | baseline tokens (the `default` theme), base layer |
538
+ | `@orz/ui/theme.css` | baseline tokens and custom variants, without the base layer or @source |
539
+
540
+ `UnsavedChangesDialog` only renders the prompt, and the blocker belongs to your router. Its JSDoc
541
+ shows the TanStack Router wiring. Register the work that can be lost with `useDirtySource()` from
542
+ `@orz/ui/lib/unsaved-guard`.
@@ -0,0 +1,14 @@
1
+ import { Avatar as AvatarPrimitive } from "radix-ui";
2
+ import type * as React from "react";
3
+ declare function Avatar({ className, size, shape, ...props }: React.ComponentProps<typeof AvatarPrimitive.Root> & {
4
+ size?: "default" | "sm" | "lg";
5
+ /** Square for marks that are not a person; people stay round. */
6
+ shape?: "circle" | "square";
7
+ }): React.JSX.Element;
8
+ declare function AvatarImage({ className, ...props }: React.ComponentProps<typeof AvatarPrimitive.Image>): React.JSX.Element;
9
+ declare function AvatarFallback({ className, ...props }: React.ComponentProps<typeof AvatarPrimitive.Fallback>): React.JSX.Element;
10
+ declare function AvatarBadge({ className, ...props }: React.ComponentProps<"span">): React.JSX.Element;
11
+ declare function AvatarGroup({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
12
+ declare function AvatarGroupCount({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
13
+ export { Avatar, AvatarBadge, AvatarFallback, AvatarGroup, AvatarGroupCount, AvatarImage, };
14
+ //# sourceMappingURL=avatar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"avatar.d.ts","sourceRoot":"","sources":["../../src/components/avatar.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,eAAe,EAAE,MAAM,UAAU,CAAC;AACrD,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;AAIpC,iBAAS,MAAM,CAAC,EACd,SAAS,EACT,IAAgB,EAChB,KAAgB,EAChB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,IAAI,CAAC,GAAG;IACrD,IAAI,CAAC,EAAE,SAAS,GAAG,IAAI,GAAG,IAAI,CAAC;IAC/B,iEAAiE;IACjE,KAAK,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;CAC7B,qBAcA;AAED,iBAAS,WAAW,CAAC,EACnB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,KAAK,CAAC,qBAWpD;AAED,iBAAS,cAAc,CAAC,EACtB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,QAAQ,CAAC,qBAWvD;AAED,iBAAS,WAAW,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,qBAczE;AAED,iBAAS,WAAW,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,qBAWxE;AAED,iBAAS,gBAAgB,CAAC,EACxB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,qBAW7B;AAED,OAAO,EACL,MAAM,EACN,WAAW,EACX,cAAc,EACd,WAAW,EACX,gBAAgB,EAChB,WAAW,GACZ,CAAC"}
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Avatar as AvatarPrimitive } from "radix-ui";
3
+ import { cn } from "../lib/utils.js";
4
+ function Avatar({ className, size = "default", shape = "circle", ...props }) {
5
+ return (_jsx(AvatarPrimitive.Root, { "data-slot": "avatar", "data-size": size, "data-shape": shape, className: cn("group/avatar relative flex size-8 shrink-0 select-none after:absolute after:inset-0 after:border after:border-border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten", "data-[shape=circle]:rounded-full data-[shape=circle]:after:rounded-full data-[shape=square]:rounded-md data-[shape=square]:after:rounded-md", className), ...props }));
6
+ }
7
+ function AvatarImage({ className, ...props }) {
8
+ return (_jsx(AvatarPrimitive.Image, { "data-slot": "avatar-image", className: cn("aspect-square size-full rounded-[inherit] object-cover", className), ...props }));
9
+ }
10
+ function AvatarFallback({ className, ...props }) {
11
+ return (_jsx(AvatarPrimitive.Fallback, { "data-slot": "avatar-fallback", className: cn("flex size-full items-center justify-center rounded-[inherit] bg-muted text-sm text-muted-foreground group-data-[size=sm]/avatar:text-xs", className), ...props }));
12
+ }
13
+ function AvatarBadge({ className, ...props }) {
14
+ return (_jsx("span", { "data-slot": "avatar-badge", className: cn("absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background select-none", "group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden", "group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2", "group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2", className), ...props }));
15
+ }
16
+ function AvatarGroup({ className, ...props }) {
17
+ return (_jsx("div", { "data-slot": "avatar-group", className: cn("group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background", className), ...props }));
18
+ }
19
+ function AvatarGroupCount({ className, ...props }) {
20
+ return (_jsx("div", { "data-slot": "avatar-group-count", className: cn("relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-sm text-muted-foreground ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3", className), ...props }));
21
+ }
22
+ export { Avatar, AvatarBadge, AvatarFallback, AvatarGroup, AvatarGroupCount, AvatarImage, };
23
+ //# sourceMappingURL=avatar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"avatar.js","sourceRoot":"","sources":["../../src/components/avatar.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,MAAM,IAAI,eAAe,EAAE,MAAM,UAAU,CAAC;AAGrD,OAAO,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAC;AAErC,SAAS,MAAM,CAAC,EACd,SAAS,EACT,IAAI,GAAG,SAAS,EAChB,KAAK,GAAG,QAAQ,EAChB,GAAG,KAAK,EAKT;IACC,OAAO,CACL,KAAC,eAAe,CAAC,IAAI,iBACT,QAAQ,eACP,IAAI,gBACH,KAAK,EACjB,SAAS,EAAE,EAAE,CACX,uNAAuN,EACvN,6IAA6I,EAC7I,SAAS,CACV,KACG,KAAK,GACT,CACH,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,EACnB,SAAS,EACT,GAAG,KAAK,EAC2C;IACnD,OAAO,CACL,KAAC,eAAe,CAAC,KAAK,iBACV,cAAc,EACxB,SAAS,EAAE,EAAE,CACX,wDAAwD,EACxD,SAAS,CACV,KACG,KAAK,GACT,CACH,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,EACtB,SAAS,EACT,GAAG,KAAK,EAC8C;IACtD,OAAO,CACL,KAAC,eAAe,CAAC,QAAQ,iBACb,iBAAiB,EAC3B,SAAS,EAAE,EAAE,CACX,yIAAyI,EACzI,SAAS,CACV,KACG,KAAK,GACT,CACH,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAgC;IACxE,OAAO,CACL,4BACY,cAAc,EACxB,SAAS,EAAE,EAAE,CACX,0KAA0K,EAC1K,+EAA+E,EAC/E,2FAA2F,EAC3F,+EAA+E,EAC/E,SAAS,CACV,KACG,KAAK,GACT,CACH,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAA+B;IACvE,OAAO,CACL,2BACY,cAAc,EACxB,SAAS,EAAE,EAAE,CACX,qGAAqG,EACrG,SAAS,CACV,KACG,KAAK,GACT,CACH,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,EACxB,SAAS,EACT,GAAG,KAAK,EACoB;IAC5B,OAAO,CACL,2BACY,oBAAoB,EAC9B,SAAS,EAAE,EAAE,CACX,0VAA0V,EAC1V,SAAS,CACV,KACG,KAAK,GACT,CACH,CAAC;AACJ,CAAC;AAED,OAAO,EACL,MAAM,EACN,WAAW,EACX,cAAc,EACd,WAAW,EACX,gBAAgB,EAChB,WAAW,GACZ,CAAC"}
@@ -0,0 +1,10 @@
1
+ import { type VariantProps } from "class-variance-authority";
2
+ import type * as React from "react";
3
+ declare const badgeVariants: (props?: ({
4
+ variant?: "default" | "destructive" | "ghost" | "link" | "outline" | "secondary" | null | undefined;
5
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
6
+ declare function Badge({ className, variant, asChild, ...props }: React.ComponentProps<"span"> & VariantProps<typeof badgeVariants> & {
7
+ asChild?: boolean;
8
+ }): React.JSX.Element;
9
+ export { Badge, badgeVariants };
10
+ //# sourceMappingURL=badge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"badge.d.ts","sourceRoot":"","sources":["../../src/components/badge.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAElE,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAC;AAIpC,QAAA,MAAM,aAAa;;8EAsBlB,CAAC;AAEF,iBAAS,KAAK,CAAC,EACb,SAAS,EACT,OAAmB,EACnB,OAAe,EACf,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,GAC7B,YAAY,CAAC,OAAO,aAAa,CAAC,GAAG;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,qBAW3D;AAED,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC"}