@opencode-ai/ui 0.0.0-dev-19220 → 0.0.0-dev-202609070344

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 (299) hide show
  1. package/dist/{data-display/accordion → components}/accordion.d.ts +6 -6
  2. package/dist/components/avatar.d.ts +9 -0
  3. package/dist/components/button.d.ts +9 -0
  4. package/dist/components/card.d.ts +1 -1
  5. package/dist/{forms/checkbox → components}/checkbox.d.ts +2 -2
  6. package/dist/components/dialog.d.ts +12 -0
  7. package/dist/{data-display/diff-changes → components}/diff-changes.d.ts +1 -2
  8. package/dist/components/dropdown-menu.d.ts +80 -0
  9. package/dist/components/icon-button.d.ts +10 -0
  10. package/dist/{icons/icon/additional-icons.d.ts → components/icon.d.ts} +8 -8
  11. package/dist/components/keybind.d.ts +6 -0
  12. package/dist/components/list.d.ts +1 -1
  13. package/dist/{feedback/progress-circle → components}/progress-circle.d.ts +0 -2
  14. package/dist/components/radio-group.d.ts +16 -0
  15. package/dist/components/scroll-view.d.ts +4 -14
  16. package/dist/components/select.d.ts +21 -0
  17. package/dist/{forms/switch → components}/switch.d.ts +2 -4
  18. package/dist/{navigation/tabs → components}/tabs.d.ts +8 -15
  19. package/dist/components/tag.d.ts +5 -0
  20. package/dist/{typography/text-shimmer → components}/text-shimmer.d.ts +0 -1
  21. package/dist/components/toast.d.ts +56 -0
  22. package/dist/components/tooltip.d.ts +17 -0
  23. package/dist/context/i18n.d.ts +7 -10
  24. package/dist/context/marked-parser.d.ts +2 -1
  25. package/dist/hooks/use-filtered-list.d.ts +0 -9
  26. package/dist/i18n/ar.d.ts +2 -0
  27. package/dist/i18n/bg.d.ts +2 -0
  28. package/dist/i18n/br.d.ts +2 -0
  29. package/dist/i18n/bs.d.ts +2 -0
  30. package/dist/i18n/da.d.ts +2 -0
  31. package/dist/i18n/de.d.ts +2 -0
  32. package/dist/i18n/en.d.ts +1 -242
  33. package/dist/i18n/es.d.ts +2 -0
  34. package/dist/i18n/fr.d.ts +2 -0
  35. package/dist/i18n/ja.d.ts +2 -0
  36. package/dist/i18n/km.d.ts +2 -0
  37. package/dist/i18n/ko.d.ts +2 -0
  38. package/dist/i18n/lo.d.ts +2 -0
  39. package/dist/i18n/mk.d.ts +2 -0
  40. package/dist/i18n/mn.d.ts +2 -0
  41. package/dist/i18n/my.d.ts +2 -0
  42. package/dist/i18n/pl.d.ts +2 -0
  43. package/dist/i18n/ru.d.ts +2 -0
  44. package/dist/i18n/sr.d.ts +2 -0
  45. package/dist/i18n/tg.d.ts +2 -0
  46. package/dist/i18n/th.d.ts +2 -0
  47. package/dist/i18n/tr.d.ts +2 -0
  48. package/dist/i18n/zh.d.ts +2 -0
  49. package/dist/i18n/zht.d.ts +2 -0
  50. package/dist/v2/components/accordion-v2.d.ts +26 -0
  51. package/dist/{data-display/avatar/avatar.d.ts → v2/components/avatar-v2.d.ts} +1 -1
  52. package/dist/v2/components/badge-v2.d.ts +6 -0
  53. package/dist/v2/components/button-v2.d.ts +10 -0
  54. package/dist/v2/components/checkbox-v2.d.ts +10 -0
  55. package/dist/{overlays/dialog/dialog.d.ts → v2/components/dialog-v2.d.ts} +2 -1
  56. package/dist/v2/components/diff-changes-v2.d.ts +11 -0
  57. package/dist/v2/components/divider-v2.d.ts +5 -0
  58. package/dist/{forms/field/field.d.ts → v2/components/field-v2.d.ts} +10 -4
  59. package/dist/v2/components/icon-button-v2.d.ts +11 -0
  60. package/dist/{icons/icon → v2/components}/icon.d.ts +1 -52
  61. package/dist/v2/components/inline-input-v2.d.ts +21 -0
  62. package/dist/v2/components/keybind-v2.d.ts +7 -0
  63. package/dist/{data-display/line-comment/line-comment.d.ts → v2/components/line-comment-v2.d.ts} +9 -9
  64. package/dist/v2/components/loader-v2.d.ts +3 -0
  65. package/dist/v2/components/menu-v2.d.ts +52 -0
  66. package/dist/v2/components/progress-circle-v2.d.ts +8 -0
  67. package/dist/{data-display/project-avatar/project-avatar.d.ts → v2/components/project-avatar-v2.d.ts} +1 -1
  68. package/dist/v2/components/radio-v2.d.ts +16 -0
  69. package/dist/{navigation/segmented-control/segmented-control.d.ts → v2/components/segmented-control-v2.d.ts} +5 -5
  70. package/dist/{forms/select/select.d.ts → v2/components/select-v2.d.ts} +6 -5
  71. package/dist/v2/components/split-button-v2.d.ts +5 -0
  72. package/dist/v2/components/switch-v2.d.ts +7 -0
  73. package/dist/v2/components/tabs-v2.d.ts +34 -0
  74. package/dist/{forms/text-input/text-input.d.ts → v2/components/text-input-v2.d.ts} +3 -3
  75. package/dist/v2/components/text-shimmer-v2.d.ts +9 -0
  76. package/dist/v2/components/textarea-v2.d.ts +7 -0
  77. package/dist/v2/components/toast-v2.d.ts +57 -0
  78. package/dist/v2/components/tooltip-v2.d.ts +13 -0
  79. package/dist/v2/components/wordmark-v2.d.ts +2 -0
  80. package/package.json +35 -141
  81. package/src/{data-display/accordion → components}/accordion.css +33 -7
  82. package/src/{data-display/accordion → components}/accordion.tsx +14 -14
  83. package/src/components/animated-number.css +11 -29
  84. package/src/components/animated-number.tsx +2 -2
  85. package/src/components/avatar.css +49 -0
  86. package/src/components/avatar.tsx +55 -0
  87. package/src/components/button.css +194 -0
  88. package/src/{actions/button → components}/button.tsx +11 -19
  89. package/src/components/card.css +23 -2
  90. package/src/components/card.tsx +4 -4
  91. package/src/{forms/checkbox → components}/checkbox.css +7 -1
  92. package/src/{forms/checkbox → components}/checkbox.tsx +13 -13
  93. package/src/components/collapsible.tsx +2 -2
  94. package/src/components/dialog.css +181 -0
  95. package/src/components/dialog.tsx +72 -0
  96. package/src/{data-display/diff-changes → components}/diff-changes.css +19 -22
  97. package/src/components/diff-changes.tsx +115 -0
  98. package/src/components/dock-surface.css +9 -2
  99. package/src/components/dropdown-menu.css +135 -0
  100. package/src/components/dropdown-menu.tsx +308 -0
  101. package/src/components/icon-button.css +181 -0
  102. package/src/components/icon-button.tsx +29 -0
  103. package/src/components/icon.css +39 -0
  104. package/src/{icons/icon/additional-icons.ts → components/icon.tsx} +74 -8
  105. package/src/components/image-preview.tsx +2 -3
  106. package/src/components/keybind.css +18 -0
  107. package/src/components/keybind.tsx +20 -0
  108. package/src/components/list.css +5 -5
  109. package/src/components/list.tsx +5 -7
  110. package/src/components/popover.tsx +2 -3
  111. package/src/components/progress-circle.css +16 -0
  112. package/src/components/progress-circle.tsx +64 -0
  113. package/src/components/radio-group.css +187 -0
  114. package/src/components/radio-group.tsx +83 -0
  115. package/src/components/scroll-view.css +4 -36
  116. package/src/components/scroll-view.tsx +88 -153
  117. package/src/components/select.css +201 -0
  118. package/src/components/select.tsx +174 -0
  119. package/src/components/sticky-accordion-header.tsx +1 -1
  120. package/src/components/switch.css +136 -0
  121. package/src/components/switch.tsx +29 -0
  122. package/src/{navigation/tabs → components}/tabs.css +232 -53
  123. package/src/components/tabs.tsx +126 -0
  124. package/src/components/tag.css +37 -0
  125. package/src/components/tag.tsx +22 -0
  126. package/src/components/text-field.tsx +4 -12
  127. package/src/{typography/text-shimmer → components}/text-shimmer.css +1 -2
  128. package/src/{typography/text-shimmer → components}/text-shimmer.tsx +0 -1
  129. package/src/components/toast.css +236 -0
  130. package/src/components/toast.tsx +185 -0
  131. package/src/components/tooltip.css +74 -0
  132. package/src/components/tooltip.tsx +163 -0
  133. package/src/context/dialog.tsx +0 -1
  134. package/src/context/i18n.tsx +14 -17
  135. package/src/context/marked-parser.tsx +13 -3
  136. package/src/hooks/create-auto-scroll.tsx +0 -20
  137. package/src/i18n/am.ts +9 -7
  138. package/src/i18n/ar.ts +9 -7
  139. package/src/i18n/az.ts +9 -7
  140. package/src/i18n/bg.ts +9 -7
  141. package/src/i18n/bn.ts +9 -7
  142. package/src/i18n/br.ts +9 -7
  143. package/src/i18n/bs.ts +9 -7
  144. package/src/i18n/ca.ts +9 -7
  145. package/src/i18n/cs.ts +9 -7
  146. package/src/i18n/da.ts +9 -7
  147. package/src/i18n/de.ts +3 -1
  148. package/src/i18n/dv.ts +9 -7
  149. package/src/i18n/dz.ts +7 -5
  150. package/src/i18n/el.ts +9 -7
  151. package/src/i18n/en.ts +12 -59
  152. package/src/i18n/es.ts +8 -6
  153. package/src/i18n/et.ts +9 -7
  154. package/src/i18n/fa.ts +9 -7
  155. package/src/i18n/fi.ts +9 -7
  156. package/src/i18n/fo.ts +9 -7
  157. package/src/i18n/fr.ts +8 -6
  158. package/src/i18n/hi.ts +9 -7
  159. package/src/i18n/hr.ts +9 -7
  160. package/src/i18n/hu.ts +9 -7
  161. package/src/i18n/hy.ts +9 -7
  162. package/src/i18n/id.ts +9 -7
  163. package/src/i18n/is.ts +9 -7
  164. package/src/i18n/it.ts +9 -7
  165. package/src/i18n/ja.ts +9 -7
  166. package/src/i18n/ka.ts +9 -7
  167. package/src/i18n/km.ts +9 -7
  168. package/src/i18n/ko.ts +9 -7
  169. package/src/i18n/lo.ts +9 -7
  170. package/src/i18n/lt.ts +9 -7
  171. package/src/i18n/lv.ts +9 -7
  172. package/src/i18n/mk.ts +9 -7
  173. package/src/i18n/mn.ts +9 -7
  174. package/src/i18n/ms.ts +9 -7
  175. package/src/i18n/my.ts +9 -7
  176. package/src/i18n/ne.ts +9 -7
  177. package/src/i18n/nl.ts +9 -7
  178. package/src/i18n/no.ts +9 -7
  179. package/src/i18n/pa.ts +9 -7
  180. package/src/i18n/pl.ts +9 -7
  181. package/src/i18n/ro.ts +9 -7
  182. package/src/i18n/ru.ts +9 -7
  183. package/src/i18n/si.ts +9 -7
  184. package/src/i18n/sk.ts +9 -7
  185. package/src/i18n/sl.ts +9 -7
  186. package/src/i18n/sq.ts +9 -7
  187. package/src/i18n/sr.ts +9 -7
  188. package/src/i18n/sv.ts +9 -7
  189. package/src/i18n/tg.ts +9 -7
  190. package/src/i18n/th.ts +9 -7
  191. package/src/i18n/tk.ts +9 -7
  192. package/src/i18n/tr.ts +9 -7
  193. package/src/i18n/uk.ts +9 -7
  194. package/src/i18n/ur.ts +9 -7
  195. package/src/i18n/uz.ts +9 -7
  196. package/src/i18n/vi.ts +9 -7
  197. package/src/i18n/zh.ts +9 -7
  198. package/src/i18n/zht.ts +9 -7
  199. package/src/styles/base.css +0 -5
  200. package/src/styles/index.css +20 -4
  201. package/src/styles/tailwind/colors.css +0 -2
  202. package/src/styles/tailwind/index.css +1 -18
  203. package/src/styles/theme.css +2 -2
  204. package/src/theme/context.tsx +11 -19
  205. package/src/theme/themes/oc-2.json +19 -50
  206. package/src/theme/v2/default-primitives.ts +8 -8
  207. package/src/theme/v2/foreground.ts +0 -1
  208. package/src/theme/v2/mapping.ts +1 -7
  209. package/src/v2/components/accordion-v2.css +139 -0
  210. package/src/v2/components/accordion-v2.tsx +86 -0
  211. package/src/{data-display/avatar/avatar.tsx → v2/components/avatar-v2.tsx} +1 -1
  212. package/src/{data-display/badge/badge.css → v2/components/badge-v2.css} +0 -19
  213. package/src/{data-display/badge/badge.tsx → v2/components/badge-v2.tsx} +4 -6
  214. package/src/{actions/button/button.css → v2/components/button-v2.css} +0 -30
  215. package/src/v2/components/button-v2.tsx +35 -0
  216. package/src/v2/components/checkbox-v2.css +184 -0
  217. package/src/v2/components/checkbox-v2.tsx +65 -0
  218. package/src/{overlays/dialog/dialog.css → v2/components/dialog-v2.css} +0 -3
  219. package/src/{overlays/dialog/dialog.tsx → v2/components/dialog-v2.tsx} +13 -11
  220. package/src/v2/components/diff-changes-v2.css +26 -0
  221. package/src/{data-display/diff-changes/diff-changes.tsx → v2/components/diff-changes-v2.tsx} +2 -7
  222. package/src/{layout/divider/divider.tsx → v2/components/divider-v2.tsx} +3 -3
  223. package/src/{forms/field/field.css → v2/components/field-v2.css} +1 -1
  224. package/src/{forms/field/field.tsx → v2/components/field-v2.tsx} +11 -9
  225. package/src/{styles/file-tree.css → v2/components/file-tree-v2.css} +3 -64
  226. package/src/{actions/icon-button/icon-button.css → v2/components/icon-button-v2.css} +9 -27
  227. package/src/v2/components/icon-button-v2.tsx +37 -0
  228. package/src/{icons/icon → v2/components}/icon.tsx +12 -88
  229. package/src/v2/components/inline-input-v2.css +225 -0
  230. package/src/v2/components/inline-input-v2.tsx +107 -0
  231. package/src/{data-display/keybind/keybind.css → v2/components/keybind-v2.css} +1 -1
  232. package/src/{data-display/keybind/keybind.tsx → v2/components/keybind-v2.tsx} +3 -3
  233. package/src/{data-display/line-comment/line-comment.css → v2/components/line-comment-v2.css} +4 -4
  234. package/src/{data-display/line-comment/line-comment.tsx → v2/components/line-comment-v2.tsx} +15 -17
  235. package/src/{feedback/loader/loader.tsx → v2/components/loader-v2.tsx} +2 -2
  236. package/src/{navigation/menu/menu.css → v2/components/menu-v2.css} +2 -124
  237. package/src/{navigation/menu/menu.tsx → v2/components/menu-v2.tsx} +32 -96
  238. package/src/v2/components/progress-circle-v2.css +13 -0
  239. package/src/v2/components/progress-circle-v2.tsx +52 -0
  240. package/src/{data-display/project-avatar/project-avatar.css → v2/components/project-avatar-v2.css} +4 -4
  241. package/src/{data-display/project-avatar/project-avatar.tsx → v2/components/project-avatar-v2.tsx} +1 -1
  242. package/src/{forms/radio/radio.tsx → v2/components/radio-v2.tsx} +20 -30
  243. package/src/{navigation/segmented-control/segmented-control.css → v2/components/segmented-control-v2.css} +1 -1
  244. package/src/{navigation/segmented-control/segmented-control.tsx → v2/components/segmented-control-v2.tsx} +8 -9
  245. package/src/{forms/select/select.css → v2/components/select-v2.css} +8 -2
  246. package/src/{forms/select/select.tsx → v2/components/select-v2.tsx} +32 -42
  247. package/src/{actions/split-button/split-button.css → v2/components/split-button-v2.css} +6 -2
  248. package/src/{actions/split-button/split-button.tsx → v2/components/split-button-v2.tsx} +4 -4
  249. package/src/{forms/switch/switch.css → v2/components/switch-v2.css} +0 -110
  250. package/src/v2/components/switch-v2.tsx +28 -0
  251. package/src/{navigation/tabs/tabs-current.css → v2/components/tabs-v2.css} +10 -25
  252. package/src/v2/components/tabs-v2.tsx +149 -0
  253. package/src/{forms/text-input/text-input.css → v2/components/text-input-v2.css} +3 -3
  254. package/src/{forms/text-input/text-input.tsx → v2/components/text-input-v2.tsx} +4 -4
  255. package/src/v2/components/text-shimmer-v2.css +125 -0
  256. package/src/v2/components/text-shimmer-v2.tsx +63 -0
  257. package/src/{forms/textarea/textarea.tsx → v2/components/textarea-v2.tsx} +3 -3
  258. package/src/{feedback/toast/toast.tsx → v2/components/toast-v2.tsx} +58 -60
  259. package/src/{overlays/tooltip/tooltip.css → v2/components/tooltip-v2.css} +0 -33
  260. package/src/{overlays/tooltip/tooltip.tsx → v2/components/tooltip-v2.tsx} +17 -24
  261. package/src/v2/components/wordmark-v2.tsx +71 -0
  262. package/src/{styles/tokens → v2/styles}/colors.css +8 -8
  263. package/src/{styles/tokens → v2/styles}/theme.css +16 -23
  264. package/dist/actions/button/button.d.ts +0 -10
  265. package/dist/actions/icon-button/icon-button.d.ts +0 -11
  266. package/dist/actions/split-button/split-button.d.ts +0 -5
  267. package/dist/context/marked-base.d.ts +0 -3
  268. package/dist/data-display/badge/badge.d.ts +0 -7
  269. package/dist/data-display/keybind/keybind.d.ts +0 -7
  270. package/dist/feedback/loader/loader.d.ts +0 -3
  271. package/dist/feedback/toast/toast.d.ts +0 -57
  272. package/dist/forms/radio/radio.d.ts +0 -16
  273. package/dist/forms/textarea/textarea.d.ts +0 -7
  274. package/dist/i18n/he.d.ts +0 -197
  275. package/dist/layout/divider/divider.d.ts +0 -5
  276. package/dist/navigation/menu/menu.d.ts +0 -59
  277. package/dist/overlays/tooltip/tooltip.d.ts +0 -15
  278. package/dist/typography/wordmark/wordmark.d.ts +0 -5
  279. package/src/actions/icon-button/icon-button.tsx +0 -32
  280. package/src/context/marked-base.tsx +0 -27
  281. package/src/feedback/progress-circle/progress-circle.css +0 -28
  282. package/src/feedback/progress-circle/progress-circle.tsx +0 -64
  283. package/src/forms/switch/switch.tsx +0 -34
  284. package/src/i18n/he.ts +0 -199
  285. package/src/icons/icon/icon.css +0 -3
  286. package/src/navigation/tabs/tabs.tsx +0 -194
  287. package/src/typography/wordmark/wordmark.tsx +0 -39
  288. /package/dist/{forms/inline-input → components}/inline-input.d.ts +0 -0
  289. /package/dist/{navigation → v2/components}/tab-state-indicator.d.ts +0 -0
  290. /package/src/{forms/inline-input → components}/inline-input.css +0 -0
  291. /package/src/{forms/inline-input → components}/inline-input.tsx +0 -0
  292. /package/src/{data-display/avatar/avatar.css → v2/components/avatar-v2.css} +0 -0
  293. /package/src/{layout/divider/divider.css → v2/components/divider-v2.css} +0 -0
  294. /package/src/{feedback/loader/loader.css → v2/components/loader-v2.css} +0 -0
  295. /package/src/{forms/radio/radio.css → v2/components/radio-v2.css} +0 -0
  296. /package/src/{navigation → v2/components}/tab-state-indicator.tsx +0 -0
  297. /package/src/{forms/textarea/textarea.css → v2/components/textarea-v2.css} +0 -0
  298. /package/src/{feedback/toast/toast.css → v2/components/toast-v2.css} +0 -0
  299. /package/src/{styles/tokens/index.css → v2/styles/tailwind.css} +0 -0
@@ -0,0 +1,187 @@
1
+ [data-component="radio-group"] {
2
+ --radio-group-height: 28px;
3
+ --radio-group-gap: 4px;
4
+ --radio-group-padding: 2px;
5
+
6
+ display: inline-flex;
7
+
8
+ [data-slot="radio-group-wrapper"] {
9
+ all: unset;
10
+ background-color: var(--surface-inset-base);
11
+ border-radius: var(--radius-sm);
12
+ box-shadow: var(--shadow-xxs-border);
13
+ display: inline-flex;
14
+ height: var(--radio-group-height);
15
+ margin: 0;
16
+ overflow: visible;
17
+ padding: 0;
18
+ position: relative;
19
+ width: fit-content;
20
+ }
21
+
22
+ &[data-fill] [data-slot="radio-group-wrapper"] {
23
+ width: 100%;
24
+ }
25
+
26
+ [data-slot="radio-group-items"] {
27
+ display: inline-flex;
28
+ flex-direction: row;
29
+ gap: var(--radio-group-gap);
30
+ height: 100%;
31
+ list-style: none;
32
+ position: relative;
33
+ z-index: 1;
34
+ }
35
+
36
+ &[data-fill] [data-slot="radio-group-items"] {
37
+ width: 100%;
38
+ }
39
+
40
+ [data-slot="radio-group-indicator"] {
41
+ background: var(--button-secondary-base);
42
+ border-radius: var(--radius-sm);
43
+ box-shadow: var(--shadow-xs-border-base);
44
+ content: "";
45
+ opacity: var(--indicator-opacity, 1);
46
+ pointer-events: none;
47
+ position: absolute;
48
+ transition:
49
+ opacity 200ms ease-out,
50
+ box-shadow 100ms ease-in-out,
51
+ width 200ms cubic-bezier(0.22, 1.2, 0.36, 1),
52
+ height 200ms cubic-bezier(0.22, 1.2, 0.36, 1),
53
+ transform 300ms cubic-bezier(0.22, 1.2, 0.36, 1);
54
+ will-change: transform;
55
+ z-index: 0;
56
+ }
57
+
58
+ [data-slot="radio-group-item"] {
59
+ display: flex;
60
+ height: 100%;
61
+ min-width: 0;
62
+ position: relative;
63
+ }
64
+
65
+ &[data-fill] [data-slot="radio-group-item"] {
66
+ flex: 1;
67
+ }
68
+
69
+ [data-slot="radio-group-item-input"] {
70
+ border-width: 0;
71
+ clip: rect(0 0 0 0);
72
+ height: 1px;
73
+ margin: -1px;
74
+ overflow: hidden;
75
+ padding: 0;
76
+ position: absolute;
77
+ white-space: nowrap;
78
+ width: 1px;
79
+ }
80
+
81
+ [data-slot="radio-group-item-label"] {
82
+ color: var(--text-weak);
83
+ cursor: default;
84
+ display: flex;
85
+ align-items: center;
86
+ justify-content: center;
87
+ font-family: var(--font-family-sans);
88
+ font-size: var(--font-size-small);
89
+ font-weight: var(--font-weight-medium);
90
+ flex: 1;
91
+ height: 100%;
92
+ line-height: 1;
93
+ padding: var(--radio-group-padding);
94
+ position: relative;
95
+ transition:
96
+ color 200ms ease-out,
97
+ opacity 200ms ease-out;
98
+ user-select: none;
99
+ }
100
+
101
+ [data-slot="radio-group-item-control"] {
102
+ align-items: center;
103
+ border-radius: var(--radius-xs);
104
+ display: inline-flex;
105
+ height: 100%;
106
+ justify-content: center;
107
+ min-width: 0;
108
+ padding: var(--radio-group-control-padding, 0 10px);
109
+ transition: background-color 200ms ease-out;
110
+ width: 100%;
111
+ }
112
+
113
+ &[data-pad="none"] {
114
+ --radio-group-control-padding: 0;
115
+ }
116
+
117
+ &[data-pad="normal"] {
118
+ --radio-group-control-padding: 0 10px;
119
+ }
120
+
121
+ /* Checked state */
122
+ [data-slot="radio-group-item-input"][data-checked] + [data-slot="radio-group-item-label"] {
123
+ color: var(--text-strong);
124
+ }
125
+
126
+ /* Hover state: match the inset background (adds subtle density) */
127
+ [data-slot="radio-group-item-input"]:not([data-checked], [data-disabled])
128
+ + [data-slot="radio-group-item-label"]:hover
129
+ [data-slot="radio-group-item-control"] {
130
+ background-color: var(--surface-inset-base);
131
+ }
132
+
133
+ /* Do not overlay hover on the active segment */
134
+ [data-slot="radio-group-item-input"][data-checked]
135
+ + [data-slot="radio-group-item-label"]:hover
136
+ [data-slot="radio-group-item-control"] {
137
+ background-color: transparent;
138
+ }
139
+
140
+ /* Disabled state */
141
+ [data-slot="radio-group-item-input"][data-disabled] + [data-slot="radio-group-item-label"] {
142
+ cursor: not-allowed;
143
+ opacity: 0.5;
144
+ }
145
+
146
+ /* Focus state */
147
+ [data-slot="radio-group-wrapper"]:has([data-slot="radio-group-item-input"]:focus-visible)
148
+ [data-slot="radio-group-indicator"] {
149
+ box-shadow: var(--shadow-xs-border-focus);
150
+ }
151
+
152
+ /* Hide indicator when nothing is checked */
153
+ [data-slot="radio-group-wrapper"]:not(:has([data-slot="radio-group-item-input"][data-checked]))
154
+ [data-slot="radio-group-indicator"] {
155
+ --indicator-opacity: 0;
156
+ }
157
+
158
+ /* Vertical orientation */
159
+ &[aria-orientation="vertical"] [data-slot="radio-group-items"] {
160
+ flex-direction: column;
161
+ }
162
+
163
+ /* Small size variant */
164
+ &[data-size="small"] {
165
+ --radio-group-height: 24px;
166
+ --radio-group-gap: 3px;
167
+ --radio-group-padding: 2px;
168
+
169
+ [data-slot="radio-group-item-label"] {
170
+ font-size: 12px;
171
+ }
172
+
173
+ [data-slot="radio-group-item-control"] {
174
+ padding: var(--radio-group-control-padding, 0 8px);
175
+ }
176
+ }
177
+
178
+ &[data-size="small"][data-pad="normal"] {
179
+ --radio-group-control-padding: 0 8px;
180
+ }
181
+
182
+ /* Disabled root state */
183
+ &[data-disabled] {
184
+ opacity: 0.5;
185
+ cursor: not-allowed;
186
+ }
187
+ }
@@ -0,0 +1,83 @@
1
+ import { SegmentedControl as Kobalte } from "@kobalte/core/segmented-control"
2
+ import { For, splitProps } from "solid-js"
3
+ import type { ComponentProps, JSX } from "solid-js"
4
+
5
+ export type RadioGroupProps<T> = Omit<
6
+ ComponentProps<typeof Kobalte>,
7
+ "value" | "defaultValue" | "onChange" | "children"
8
+ > & {
9
+ options: T[]
10
+ current?: T
11
+ defaultValue?: T
12
+ value?: (x: T) => string
13
+ label?: (x: T) => JSX.Element | string
14
+ onSelect?: (value: T | undefined) => void
15
+ class?: ComponentProps<"div">["class"]
16
+ classList?: ComponentProps<"div">["classList"]
17
+ size?: "small" | "medium"
18
+ fill?: boolean
19
+ pad?: "none" | "normal"
20
+ }
21
+
22
+ export function RadioGroup<T>(props: RadioGroupProps<T>) {
23
+ const [local, others] = splitProps(props, [
24
+ "class",
25
+ "classList",
26
+ "options",
27
+ "current",
28
+ "defaultValue",
29
+ "value",
30
+ "label",
31
+ "onSelect",
32
+ "size",
33
+ "fill",
34
+ "pad",
35
+ ])
36
+
37
+ const getValue = (item: T): string => {
38
+ if (local.value) return local.value(item)
39
+ return String(item)
40
+ }
41
+
42
+ const getLabel = (item: T): JSX.Element | string => {
43
+ if (local.label) return local.label(item)
44
+ return String(item)
45
+ }
46
+
47
+ const findOption = (v: string): T | undefined => {
48
+ return local.options.find((opt) => getValue(opt) === v)
49
+ }
50
+
51
+ return (
52
+ <Kobalte
53
+ {...others}
54
+ data-component="radio-group"
55
+ data-size={local.size ?? "medium"}
56
+ data-fill={local.fill ? "" : undefined}
57
+ data-pad={local.pad ?? "normal"}
58
+ classList={{
59
+ ...local.classList,
60
+ [local.class ?? ""]: !!local.class,
61
+ }}
62
+ value={local.current ? getValue(local.current) : undefined}
63
+ defaultValue={local.defaultValue ? getValue(local.defaultValue) : undefined}
64
+ onChange={(v) => local.onSelect?.(findOption(v))}
65
+ >
66
+ <div role="presentation" data-slot="radio-group-wrapper">
67
+ <Kobalte.Indicator data-slot="radio-group-indicator" />
68
+ <div role="presentation" data-slot="radio-group-items">
69
+ <For each={local.options}>
70
+ {(option) => (
71
+ <Kobalte.Item value={getValue(option)} data-slot="radio-group-item" data-value={getValue(option)}>
72
+ <Kobalte.ItemInput data-slot="radio-group-item-input" />
73
+ <Kobalte.ItemLabel data-slot="radio-group-item-label">
74
+ <span data-slot="radio-group-item-control">{getLabel(option)}</span>
75
+ </Kobalte.ItemLabel>
76
+ </Kobalte.Item>
77
+ )}
78
+ </For>
79
+ </div>
80
+ </div>
81
+ </Kobalte>
82
+ )
83
+ }
@@ -15,21 +15,15 @@
15
15
  outline: none;
16
16
  }
17
17
 
18
- .scroll-view[data-orientation="horizontal"] .scroll-view__viewport {
19
- overflow-x: auto;
20
- overflow-y: hidden;
21
- }
22
-
23
- .scroll-view[data-orientation="both"] .scroll-view__viewport {
24
- overflow: auto;
25
- }
26
-
27
18
  .scroll-view__viewport::-webkit-scrollbar {
28
19
  display: none;
29
20
  }
30
21
 
31
22
  .scroll-view__thumb {
32
23
  position: absolute;
24
+ inset-inline-end: 0;
25
+ top: 0;
26
+ width: 12px;
33
27
  transition: opacity 200ms ease;
34
28
  cursor: default;
35
29
  user-select: none;
@@ -37,19 +31,7 @@
37
31
  opacity: 0;
38
32
  }
39
33
 
40
- .scroll-view__thumb[data-orientation="vertical"] {
41
- inset-inline-end: 0;
42
- top: 0;
43
- width: 12px;
44
- }
45
-
46
- .scroll-view__thumb[data-orientation="horizontal"] {
47
- left: 0;
48
- bottom: 0;
49
- height: 12px;
50
- }
51
-
52
- .scroll-view__thumb[data-orientation="vertical"]::after {
34
+ .scroll-view__thumb::after {
53
35
  content: "";
54
36
  position: absolute;
55
37
  left: 50%;
@@ -63,20 +45,6 @@
63
45
  transition: background-color 150ms ease;
64
46
  }
65
47
 
66
- .scroll-view__thumb[data-orientation="horizontal"]::after {
67
- content: "";
68
- position: absolute;
69
- left: 0;
70
- right: 0;
71
- top: 50%;
72
- height: 4px;
73
- transform: translateY(-50%);
74
- border-radius: 9999px;
75
- background-color: var(--border-weak-base);
76
- backdrop-filter: blur(4px);
77
- transition: background-color 150ms ease;
78
- }
79
-
80
48
  .scroll-view__thumb:hover::after,
81
49
  .scroll-view__thumb[data-dragging="true"]::after {
82
50
  background-color: var(--border-strong-base);
@@ -1,4 +1,14 @@
1
- import { createEffect, mergeProps, onCleanup, onMount, Show, splitProps, type ComponentProps } from "solid-js"
1
+ import {
2
+ createEffect,
3
+ createMemo,
4
+ mergeProps,
5
+ onCleanup,
6
+ onMount,
7
+ Show,
8
+ splitProps,
9
+ type Accessor,
10
+ type ComponentProps,
11
+ } from "solid-js"
2
12
  import { Portal } from "solid-js/web"
3
13
  import { createResizeObserver } from "@solid-primitives/resize-observer"
4
14
  import { createStore } from "solid-js/store"
@@ -8,7 +18,7 @@ export type ScrollViewThumbVisibility = "hover" | "scroll"
8
18
 
9
19
  export interface ScrollViewProps extends ComponentProps<"div"> {
10
20
  viewportRef?: (el: HTMLDivElement) => void
11
- orientation?: "vertical" | "horizontal" | "both"
21
+ orientation?: "vertical" | "horizontal" // currently only vertical is fully implemented for thumb
12
22
  /**
13
23
  * `hover`: show while hovered or scrolling. `scroll`: show only while scrolling.
14
24
  *
@@ -17,9 +27,9 @@ export interface ScrollViewProps extends ComponentProps<"div"> {
17
27
  * */
18
28
  thumbVisibility?: ScrollViewThumbVisibility
19
29
  /** Mount the thumb into an external track. Scroll metrics still come from this ScrollView. */
20
- thumbContainer?: HTMLElement
30
+ thumbContainer?: HTMLElement | Accessor<HTMLElement | undefined>
21
31
  /** Element whose hover reveals the thumb. Defaults to the ScrollView root when unset. */
22
- thumbHoverTarget?: HTMLElement
32
+ thumbHoverTarget?: HTMLElement | Accessor<HTMLElement | undefined>
23
33
  }
24
34
 
25
35
  export const scrollKey = (event: Pick<KeyboardEvent, "key" | "altKey" | "ctrlKey" | "metaKey" | "shiftKey">) => {
@@ -78,37 +88,12 @@ export function scrollTopFromThumbPointer(input: {
78
88
  thumbHeight: number
79
89
  /** Viewport height used for max scroll. Defaults to `clientHeight` (track == viewport). */
80
90
  scrollClientHeight?: number
81
- }) {
82
- return scrollOffsetFromThumbPointer({
83
- pointer: input.pointer,
84
- viewportStart: input.viewportTop,
85
- grabOffset: input.grabOffset,
86
- clientSize: input.clientHeight,
87
- scrollSize: input.scrollHeight,
88
- thumbSize: input.thumbHeight,
89
- scrollClientSize: input.scrollClientHeight,
90
- })
91
- }
92
-
93
- export function scrollOffsetFromThumbPointer(input: {
94
- pointer: number
95
- viewportStart: number
96
- grabOffset: number
97
- clientSize: number
98
- scrollSize: number
99
- thumbSize: number
100
- scrollClientSize?: number
101
- reverse?: boolean
102
91
  }) {
103
92
  const padding = 8
104
- const maxThumbStart = input.clientSize - padding * 2 - input.thumbSize
105
- if (maxThumbStart <= 0) return 0
106
- const thumbStart = Math.max(
107
- 0,
108
- Math.min(input.pointer - input.viewportStart - padding - input.grabOffset, maxThumbStart),
109
- )
110
- const progress = input.reverse ? 1 - thumbStart / maxThumbStart : thumbStart / maxThumbStart
111
- return progress * Math.max(0, input.scrollSize - (input.scrollClientSize ?? input.clientSize))
93
+ const maxThumbTop = input.clientHeight - padding * 2 - input.thumbHeight
94
+ if (maxThumbTop <= 0) return 0
95
+ const thumbTop = Math.max(0, Math.min(input.pointer - input.viewportTop - padding - input.grabOffset, maxThumbTop))
96
+ return (thumbTop / maxThumbTop) * Math.max(0, input.scrollHeight - (input.scrollClientHeight ?? input.clientHeight))
112
97
  }
113
98
 
114
99
  export function ScrollView(props: ScrollViewProps) {
@@ -141,29 +126,31 @@ export function ScrollView(props: ScrollViewProps) {
141
126
 
142
127
  let rootRef!: HTMLDivElement
143
128
  let viewportRef!: HTMLDivElement
144
- let verticalThumbRef!: HTMLDivElement
145
- let horizontalThumbRef!: HTMLDivElement
129
+ let thumbRef!: HTMLDivElement
130
+
131
+ const resolveEl = (value: HTMLElement | Accessor<HTMLElement | undefined> | undefined) => {
132
+ if (typeof value === "function") return value()
133
+ return value
134
+ }
146
135
 
147
- const thumbMount = () => local.thumbContainer
148
- const thumbHover = () => local.thumbHoverTarget
136
+ const thumbMount = createMemo(() => resolveEl(local.thumbContainer))
137
+ const thumbHover = createMemo(() => resolveEl(local.thumbHoverTarget))
149
138
  const hoverRoot = () => !local.thumbHoverTarget && !local.thumbContainer
150
139
 
151
140
  const [state, setState] = createStore({
152
141
  isHovered: false,
153
- dragging: undefined as "vertical" | "horizontal" | undefined,
142
+ isDragging: false,
154
143
  isScrolling: false,
155
- verticalThumbSize: 0,
156
- verticalThumbStart: 0,
157
- showVerticalThumb: false,
158
- horizontalThumbSize: 0,
159
- horizontalThumbStart: 0,
160
- showHorizontalThumb: false,
144
+ thumbHeight: 0,
145
+ thumbTop: 0,
146
+ showThumb: false,
161
147
  })
162
148
  const isHovered = () => state.isHovered
163
- const isDragging = () => state.dragging !== undefined
149
+ const isDragging = () => state.isDragging
164
150
  const isScrolling = () => state.isScrolling
165
- const vertical = () => local.orientation === "vertical" || local.orientation === "both"
166
- const horizontal = () => local.orientation === "horizontal" || local.orientation === "both"
151
+ const thumbHeight = () => state.thumbHeight
152
+ const thumbTop = () => state.thumbTop
153
+ const showThumb = () => state.showThumb
167
154
 
168
155
  let scrollIdleTimer: ReturnType<typeof setTimeout> | undefined
169
156
 
@@ -185,42 +172,33 @@ export function ScrollView(props: ScrollViewProps) {
185
172
 
186
173
  const updateThumb = () => {
187
174
  if (!viewportRef) return
188
- const trackPadding = 8
189
- const minThumbSize = 32
190
-
191
- if (vertical()) {
192
- const trackSize = Math.max(0, (thumbMount()?.clientHeight || viewportRef.clientHeight) - trackPadding * 2)
193
- const size = trackSize
194
- ? Math.min(trackSize, Math.max((viewportRef.clientHeight / viewportRef.scrollHeight) * trackSize, minThumbSize))
195
- : 0
196
- const maxScroll = viewportRef.scrollHeight - viewportRef.clientHeight
197
- const maxStart = trackSize - size
198
- setState("showVerticalThumb", maxScroll > 0)
199
- setState("verticalThumbSize", size)
200
- setState(
201
- "verticalThumbStart",
202
- trackPadding + (maxScroll > 0 ? (viewportRef.scrollTop / maxScroll) * maxStart : 0),
203
- )
204
- } else {
205
- setState("showVerticalThumb", false)
206
- }
175
+ const { scrollTop, scrollHeight, clientHeight } = viewportRef
207
176
 
208
- if (horizontal()) {
209
- const trackSize = Math.max(0, (thumbMount()?.clientWidth || viewportRef.clientWidth) - trackPadding * 2)
210
- const size = trackSize
211
- ? Math.min(trackSize, Math.max((viewportRef.clientWidth / viewportRef.scrollWidth) * trackSize, minThumbSize))
212
- : 0
213
- const maxScroll = viewportRef.scrollWidth - viewportRef.clientWidth
214
- const maxStart = trackSize - size
215
- const rtl = getComputedStyle(viewportRef).direction === "rtl"
216
- const offset = Math.max(0, Math.min(rtl ? -viewportRef.scrollLeft : viewportRef.scrollLeft, maxScroll))
217
- const start = maxScroll > 0 ? (offset / maxScroll) * maxStart : 0
218
- setState("showHorizontalThumb", maxScroll > 0)
219
- setState("horizontalThumbSize", size)
220
- setState("horizontalThumbStart", trackPadding + (rtl ? maxStart - start : start))
221
- } else {
222
- setState("showHorizontalThumb", false)
177
+ if (scrollHeight <= clientHeight || scrollHeight === 0) {
178
+ setState("showThumb", false)
179
+ return
223
180
  }
181
+
182
+ setState("showThumb", true)
183
+ const trackPadding = 8
184
+ const trackClientHeight = thumbMount()?.clientHeight || clientHeight
185
+ const trackHeight = trackClientHeight - trackPadding * 2
186
+
187
+ const minThumbHeight = 32
188
+ // Calculate raw thumb height based on ratio
189
+ let height = (clientHeight / scrollHeight) * trackHeight
190
+ height = Math.max(height, minThumbHeight)
191
+
192
+ const maxScrollTop = scrollHeight - clientHeight
193
+ const maxThumbTop = trackHeight - height
194
+
195
+ const top = maxScrollTop > 0 ? (scrollTop / maxScrollTop) * maxThumbTop : 0
196
+
197
+ // Ensure thumb stays within bounds (shouldn't be necessary due to math above, but good for safety)
198
+ const boundedTop = trackPadding + Math.max(0, Math.min(top, maxThumbTop))
199
+
200
+ setState("thumbHeight", height)
201
+ setState("thumbTop", boundedTop)
224
202
  }
225
203
 
226
204
  onMount(() => {
@@ -241,13 +219,6 @@ export function ScrollView(props: ScrollViewProps) {
241
219
  updateThumb()
242
220
  })
243
221
 
244
- createEffect(() => {
245
- if (!horizontal() || !viewportRef) return
246
- const observer = new MutationObserver(updateThumb)
247
- observer.observe(viewportRef, { childList: true, subtree: true, characterData: true })
248
- onCleanup(() => observer.disconnect())
249
- })
250
-
251
222
  createEffect(() => {
252
223
  const target = thumbHover()
253
224
  if (!target) return
@@ -263,88 +234,58 @@ export function ScrollView(props: ScrollViewProps) {
263
234
  })
264
235
  })
265
236
 
266
- const onThumbPointerDown = (axis: "vertical" | "horizontal", e: PointerEvent) => {
237
+ const onThumbPointerDown = (e: PointerEvent) => {
267
238
  e.preventDefault()
268
239
  e.stopPropagation()
269
- setState("dragging", axis)
270
- const thumb = axis === "vertical" ? verticalThumbRef : horizontalThumbRef
271
- const grabOffset =
272
- axis === "vertical"
273
- ? e.clientY - thumb.getBoundingClientRect().top
274
- : e.clientX - thumb.getBoundingClientRect().left
240
+ setState("isDragging", true)
241
+ const grabOffset = e.clientY - thumbRef.getBoundingClientRect().top
275
242
  const track = thumbMount() ?? viewportRef
276
243
 
277
- thumb.setPointerCapture(e.pointerId)
244
+ thumbRef.setPointerCapture(e.pointerId)
278
245
 
279
246
  const onPointerMove = (e: PointerEvent) => {
280
- const vertical = axis === "vertical"
281
- const rtl = !vertical && getComputedStyle(viewportRef).direction === "rtl"
282
- const offset = scrollOffsetFromThumbPointer({
283
- pointer: vertical ? e.clientY : e.clientX,
284
- viewportStart: vertical ? track.getBoundingClientRect().top : track.getBoundingClientRect().left,
247
+ const { scrollHeight, clientHeight } = viewportRef
248
+ viewportRef.scrollTop = scrollTopFromThumbPointer({
249
+ pointer: e.clientY,
250
+ viewportTop: track.getBoundingClientRect().top,
285
251
  grabOffset,
286
- clientSize: vertical ? track.clientHeight : track.clientWidth,
287
- scrollClientSize: vertical ? viewportRef.clientHeight : viewportRef.clientWidth,
288
- scrollSize: vertical ? viewportRef.scrollHeight : viewportRef.scrollWidth,
289
- thumbSize: vertical ? state.verticalThumbSize : state.horizontalThumbSize,
290
- reverse: rtl,
252
+ clientHeight: track.clientHeight,
253
+ scrollClientHeight: clientHeight,
254
+ scrollHeight,
255
+ thumbHeight: thumbHeight(),
291
256
  })
292
- if (vertical) {
293
- viewportRef.scrollTop = offset
294
- return
295
- }
296
- viewportRef.scrollLeft = rtl ? -offset : offset
297
257
  }
298
258
 
299
259
  const done = (e: PointerEvent) => {
300
- setState("dragging", undefined)
301
- thumb.releasePointerCapture(e.pointerId)
302
- thumb.removeEventListener("pointermove", onPointerMove)
303
- thumb.removeEventListener("pointerup", done)
304
- thumb.removeEventListener("pointercancel", done)
260
+ setState("isDragging", false)
261
+ thumbRef.releasePointerCapture(e.pointerId)
262
+ thumbRef.removeEventListener("pointermove", onPointerMove)
263
+ thumbRef.removeEventListener("pointerup", done)
264
+ thumbRef.removeEventListener("pointercancel", done)
305
265
  }
306
266
 
307
- thumb.addEventListener("pointermove", onPointerMove)
308
- thumb.addEventListener("pointerup", done)
309
- thumb.addEventListener("pointercancel", done)
267
+ thumbRef.addEventListener("pointermove", onPointerMove)
268
+ thumbRef.addEventListener("pointerup", done)
269
+ thumbRef.addEventListener("pointercancel", done)
310
270
  }
311
271
 
312
- const renderVerticalThumb = () => (
272
+ const renderThumb = () => (
313
273
  <div
314
274
  ref={(el) => {
315
- verticalThumbRef = el
275
+ thumbRef = el
316
276
  }}
317
- onPointerDown={(event) => onThumbPointerDown("vertical", event)}
277
+ onPointerDown={onThumbPointerDown}
318
278
  class="scroll-view__thumb"
319
- data-orientation="vertical"
320
279
  data-visible={thumbVisible()}
321
- data-dragging={state.dragging === "vertical"}
280
+ data-dragging={isDragging()}
322
281
  style={{
323
- height: `${state.verticalThumbSize}px`,
324
- transform: `translateY(${state.verticalThumbStart}px)`,
282
+ height: `${thumbHeight()}px`,
283
+ transform: `translateY(${thumbTop()}px)`,
325
284
  "z-index": 100, // ensure it displays over content
326
285
  }}
327
286
  />
328
287
  )
329
288
 
330
- const renderHorizontalThumb = () => (
331
- <div
332
- ref={(el) => {
333
- horizontalThumbRef = el
334
- }}
335
- onPointerDown={(event) => onThumbPointerDown("horizontal", event)}
336
- class="scroll-view__thumb"
337
- data-orientation="horizontal"
338
- data-visible={thumbVisible()}
339
- data-dragging={state.dragging === "horizontal"}
340
- style={{
341
- width: `${state.horizontalThumbSize}px`,
342
- transform: `translateX(${state.horizontalThumbStart}px)`,
343
- "z-index": 100,
344
- }}
345
- />
346
- )
347
-
348
289
  // Keybinds implementation
349
290
  // We ensure the viewport has a tabindex so it can receive focus
350
291
  // We can also explicitly catch PageUp/Down if we want smooth scroll or specific behavior,
@@ -394,7 +335,6 @@ export function ScrollView(props: ScrollViewProps) {
394
335
  <div
395
336
  ref={rootRef}
396
337
  class={`scroll-view ${local.class || ""}`}
397
- data-orientation={local.orientation}
398
338
  style={local.style}
399
339
  onPointerEnter={() => {
400
340
  if (hoverRoot()) setState("isHovered", true)
@@ -438,14 +378,9 @@ export function ScrollView(props: ScrollViewProps) {
438
378
  </div>
439
379
 
440
380
  {/* Thumb Overlay — optionally portaled into an external track */}
441
- <Show when={state.showVerticalThumb}>
442
- <Show when={thumbMount()} fallback={renderVerticalThumb()}>
443
- {(mount) => <Portal mount={mount()}>{renderVerticalThumb()}</Portal>}
444
- </Show>
445
- </Show>
446
- <Show when={state.showHorizontalThumb}>
447
- <Show when={thumbMount()} fallback={renderHorizontalThumb()}>
448
- {(mount) => <Portal mount={mount()}>{renderHorizontalThumb()}</Portal>}
381
+ <Show when={showThumb()}>
382
+ <Show when={thumbMount()} fallback={renderThumb()}>
383
+ {(mount) => <Portal mount={mount()}>{renderThumb()}</Portal>}
449
384
  </Show>
450
385
  </Show>
451
386
  </div>