@sanity/ui 4.2.0 → 5.0.0-alpha.10

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 (339) hide show
  1. package/README.md +194 -28
  2. package/bin/sanity-ui.js +14 -0
  3. package/dist/_chunks-es/LazyRefractor.js +20 -0
  4. package/dist/_chunks-es/LazyRefractor.js.map +1 -0
  5. package/dist/cli/checks.js +148 -0
  6. package/dist/cli/config.js +23 -0
  7. package/dist/cli/detect.js +181 -0
  8. package/dist/cli/doctor.js +49 -0
  9. package/dist/cli/index.js +69 -0
  10. package/dist/cli/init.js +189 -0
  11. package/dist/cli/install.js +79 -0
  12. package/dist/cli/log.js +66 -0
  13. package/dist/cli/selfPackage.js +11 -0
  14. package/dist/cli/styles.js +185 -0
  15. package/dist/cli/test-utils.js +8 -0
  16. package/dist/cli/tsconfig.js +236 -0
  17. package/dist/cli/types.js +1 -0
  18. package/dist/cli/versions.js +45 -0
  19. package/dist/index.d.ts +1203 -1469
  20. package/dist/index.js +2123 -1989
  21. package/dist/index.js.map +1 -1
  22. package/dist/polyfills.d.ts +1 -0
  23. package/dist/polyfills.js +5 -0
  24. package/dist/polyfills.js.map +1 -0
  25. package/dist/styles.css +1 -547
  26. package/package.json +57 -58
  27. package/src/cli/checks.test.ts +38 -0
  28. package/src/cli/checks.ts +200 -0
  29. package/src/cli/config.ts +44 -0
  30. package/src/cli/detect.test.ts +191 -0
  31. package/src/cli/detect.ts +205 -0
  32. package/src/cli/doctor.test.ts +52 -0
  33. package/src/cli/doctor.ts +65 -0
  34. package/src/cli/index.ts +77 -0
  35. package/src/cli/init.ts +225 -0
  36. package/src/cli/install.test.ts +52 -0
  37. package/src/cli/install.ts +108 -0
  38. package/src/cli/log.ts +79 -0
  39. package/src/cli/selfPackage.ts +14 -0
  40. package/src/cli/styles.test.ts +130 -0
  41. package/src/cli/styles.ts +205 -0
  42. package/src/cli/test-utils.ts +10 -0
  43. package/src/cli/tsconfig.test.ts +174 -0
  44. package/src/cli/tsconfig.ts +282 -0
  45. package/src/cli/types.ts +77 -0
  46. package/src/cli/versions.test.ts +35 -0
  47. package/src/cli/versions.ts +41 -0
  48. package/src/components/badge/Badge.tsx +53 -0
  49. package/src/components/badge/badge.css +6 -0
  50. package/src/components/badge/badge.props.ts +31 -0
  51. package/src/components/box/Box.tsx +30 -0
  52. package/src/components/box/box.css +4 -0
  53. package/src/components/box/box.props.ts +29 -0
  54. package/src/components/button/Button.tsx +78 -0
  55. package/src/components/button/button.css +72 -0
  56. package/src/components/button/button.props.ts +122 -0
  57. package/src/components/card/Card.tsx +31 -0
  58. package/src/components/card/card.css +4 -0
  59. package/src/components/card/card.props.ts +58 -0
  60. package/src/components/checkbox/Checkbox.tsx +64 -0
  61. package/src/components/checkbox/checkbox.css +100 -0
  62. package/src/components/checkbox/checkbox.props.ts +33 -0
  63. package/src/components/code/Code.tsx +38 -0
  64. package/src/components/code/LazyRefractor.tsx +9 -0
  65. package/src/components/code/code.css +40 -0
  66. package/src/components/code/code.props.ts +44 -0
  67. package/src/components/container/Container.tsx +30 -0
  68. package/src/components/container/container.css +4 -0
  69. package/src/components/container/container.props.ts +28 -0
  70. package/src/components/dialog/Dialog.tsx +139 -0
  71. package/src/components/dialog/dialog.css +49 -0
  72. package/src/components/dialog/dialog.props.ts +45 -0
  73. package/src/components/divider/Divider.tsx +23 -0
  74. package/src/components/divider/divider.props.ts +9 -0
  75. package/src/components/eyebrow/Eyebrow.tsx +54 -0
  76. package/src/components/eyebrow/eyebrow.css +11 -0
  77. package/src/components/eyebrow/eyebrow.props.ts +28 -0
  78. package/src/components/flex/Flex.tsx +30 -0
  79. package/src/components/flex/flex.css +4 -0
  80. package/src/components/flex/flex.props.ts +33 -0
  81. package/src/components/grid/Grid.tsx +30 -0
  82. package/src/components/grid/grid.css +4 -0
  83. package/src/components/grid/grid.props.ts +33 -0
  84. package/src/components/h-stack/HStack.tsx +34 -0
  85. package/src/components/h-stack/hStack.props.ts +17 -0
  86. package/src/components/heading/Heading.tsx +54 -0
  87. package/src/components/heading/heading.css +9 -0
  88. package/src/components/heading/heading.props.ts +31 -0
  89. package/src/components/icon/Icon.tsx +24 -0
  90. package/src/components/icon/icon.css +17 -0
  91. package/src/components/icon/icon.props.ts +35 -0
  92. package/src/components/icon-button/IconButton.tsx +32 -0
  93. package/src/components/icon-button/iconButton.props.ts +31 -0
  94. package/src/components/index.css +28 -0
  95. package/src/components/indicator/Indicator.tsx +42 -0
  96. package/src/components/indicator/indicator.css +4 -0
  97. package/src/components/indicator/indicator.props.ts +24 -0
  98. package/src/components/indicator-stack/IndicatorStack.tsx +31 -0
  99. package/src/components/indicator-stack/indicator-stack.css +7 -0
  100. package/src/components/indicator-stack/indicatorStack.props.ts +15 -0
  101. package/src/components/inline/Inline.tsx +38 -0
  102. package/src/components/inline/inline.css +4 -0
  103. package/src/components/inline/inline.props.ts +100 -0
  104. package/src/components/label/Label.tsx +34 -0
  105. package/src/components/label/label.css +3 -0
  106. package/src/components/label/label.props.ts +25 -0
  107. package/src/components/link/Link.tsx +29 -0
  108. package/src/components/link/link.css +12 -0
  109. package/src/components/link/link.props.ts +23 -0
  110. package/src/components/list/List.tsx +167 -0
  111. package/src/components/list/list.css +25 -0
  112. package/src/components/list/list.props.ts +130 -0
  113. package/src/components/popover/Popover.tsx +85 -0
  114. package/src/components/popover/popover.css +21 -0
  115. package/src/components/popover/popover.props.ts +38 -0
  116. package/src/components/press-area/PressArea.tsx +29 -0
  117. package/src/components/press-area/press-area.css +22 -0
  118. package/src/components/press-area/pressArea.props.ts +16 -0
  119. package/src/components/radio/Radio.tsx +48 -0
  120. package/src/components/radio/radio.css +97 -0
  121. package/src/components/radio/radio.props.ts +25 -0
  122. package/src/components/skip-to-content/SkipToContent.tsx +35 -0
  123. package/src/components/skip-to-content/skip-to-content.css +9 -0
  124. package/src/components/skip-to-content/skipToContent.props.ts +25 -0
  125. package/src/components/spinner/Spinner.tsx +28 -0
  126. package/src/components/spinner/spinner.css +10 -0
  127. package/src/components/spinner/spinner.props.ts +19 -0
  128. package/src/components/switch/Switch.tsx +48 -0
  129. package/src/components/switch/switch.css +119 -0
  130. package/src/components/switch/switch.props.ts +25 -0
  131. package/src/components/text/Text.tsx +53 -0
  132. package/src/components/text/text.css +9 -0
  133. package/src/components/text/text.props.ts +28 -0
  134. package/src/components/tooltip/Tooltip.tsx +97 -0
  135. package/src/components/tooltip/tooltip.css +31 -0
  136. package/src/components/tooltip/tooltip.props.ts +36 -0
  137. package/src/components/tooltip-group/TooltipGroup.tsx +64 -0
  138. package/src/components/tooltip-group/tooltipGroup.props.ts +15 -0
  139. package/src/components/v-stack/VStack.tsx +32 -0
  140. package/src/components/v-stack/vStack.props.ts +17 -0
  141. package/src/components/visually-hidden/VisuallyHidden.tsx +29 -0
  142. package/src/components/visually-hidden/visually-hidden.css +9 -0
  143. package/src/components/visually-hidden/visuallyHidden.props.ts +23 -0
  144. package/src/css/classes/dynamic/flex-child.css +5 -0
  145. package/src/css/classes/dynamic/grid-child.css +8 -0
  146. package/src/css/classes/dynamic/grid-parent.css +6 -0
  147. package/src/css/classes/dynamic/height.css +5 -0
  148. package/src/css/classes/dynamic/index.css +7 -0
  149. package/src/css/classes/dynamic/line-clamp.css +5 -0
  150. package/src/css/classes/dynamic/readme.md +3 -0
  151. package/src/css/classes/dynamic/width.css +7 -0
  152. package/src/css/classes/dynamic/z-index.css +3 -0
  153. package/src/css/classes/generic/display.css +10 -0
  154. package/src/css/classes/generic/flex.css +24 -0
  155. package/src/css/classes/generic/grid.css +7 -0
  156. package/src/css/classes/generic/index.css +7 -0
  157. package/src/css/classes/generic/overflow.css +19 -0
  158. package/src/css/classes/generic/placement.css +202 -0
  159. package/src/css/classes/generic/position.css +7 -0
  160. package/src/css/classes/generic/readme.md +3 -0
  161. package/src/css/classes/generic/text-align.css +5 -0
  162. package/src/css/classes/index.css +4 -0
  163. package/src/css/classes/local/animation.css +4 -0
  164. package/src/css/classes/local/border.css +20 -0
  165. package/src/css/classes/local/index.css +6 -0
  166. package/src/css/classes/local/readme.md +3 -0
  167. package/src/css/classes/local/text-overflow.css +5 -0
  168. package/src/css/classes/local/text-trim.css +45 -0
  169. package/src/css/classes/local/tone.css +75 -0
  170. package/src/css/classes/local/top-anchor.css +4 -0
  171. package/src/css/classes/system/color.css +223 -0
  172. package/src/css/classes/system/container.css +8 -0
  173. package/src/css/classes/system/font-family.css +2 -0
  174. package/src/css/classes/system/font-weight.css +4 -0
  175. package/src/css/classes/system/gap.css +37 -0
  176. package/src/css/classes/system/index.css +10 -0
  177. package/src/css/classes/system/inset.css +56 -0
  178. package/src/css/classes/system/margin.css +148 -0
  179. package/src/css/classes/system/padding.css +78 -0
  180. package/src/css/classes/system/radius.css +10 -0
  181. package/src/css/classes/system/readme.md +3 -0
  182. package/src/css/classes/system/shadow.css +6 -0
  183. package/src/css/classes/system/typography.css +31 -0
  184. package/src/css/global/font-face.css +20 -0
  185. package/src/css/global/index.css +2 -0
  186. package/src/css/global/reset.css +76 -0
  187. package/src/css/tokens/index.css +3 -0
  188. package/src/css/tokens/opacity.css +6 -0
  189. package/src/css/tokens/semantic.css +25 -0
  190. package/src/css/tokens/surface.css +27 -0
  191. package/src/hooks/useIsClient.ts +10 -0
  192. package/src/index.css +18 -0
  193. package/src/index.ts +65 -0
  194. package/src/polyfills.ts +15 -0
  195. package/src/props/border.ts +51 -0
  196. package/src/props/flexChild.ts +29 -0
  197. package/src/props/flexParent.ts +47 -0
  198. package/src/props/gap.ts +31 -0
  199. package/src/props/gridChild.ts +50 -0
  200. package/src/props/gridParent.ts +45 -0
  201. package/src/props/height.ts +29 -0
  202. package/src/props/layout.ts +44 -0
  203. package/src/props/margin.ts +59 -0
  204. package/src/props/overflow.ts +30 -0
  205. package/src/props/padding.ts +59 -0
  206. package/src/props/placement.ts +16 -0
  207. package/src/props/position.ts +52 -0
  208. package/src/props/shadow.ts +15 -0
  209. package/src/props/tone.ts +15 -0
  210. package/src/props/typography.ts +62 -0
  211. package/src/props/width.ts +29 -0
  212. package/src/props/zIndex.ts +15 -0
  213. package/src/styles.css.d.ts +1 -0
  214. package/src/styles.css.node.js +2 -0
  215. package/src/styles.css.test.ts +24 -0
  216. package/src/types/Button.ts +8 -0
  217. package/src/types/Code.ts +6 -0
  218. package/src/types/Container.ts +3 -0
  219. package/src/types/Density.ts +3 -0
  220. package/src/types/Display.ts +11 -0
  221. package/src/types/Eyebrow.ts +3 -0
  222. package/src/types/Flex.ts +22 -0
  223. package/src/types/FontWeight.ts +3 -0
  224. package/src/types/Grid.ts +3 -0
  225. package/src/types/Heading.ts +6 -0
  226. package/src/types/Icon.ts +3 -0
  227. package/src/types/Interactive.ts +8 -0
  228. package/src/types/List.ts +2 -0
  229. package/src/types/Overflow.ts +3 -0
  230. package/src/types/Placement.ts +16 -0
  231. package/src/types/Position.ts +3 -0
  232. package/src/types/PropDef.ts +73 -0
  233. package/src/types/Radius.ts +3 -0
  234. package/src/types/Responsive.ts +10 -0
  235. package/src/types/Shadow.ts +3 -0
  236. package/src/types/Space.ts +15 -0
  237. package/src/types/Text.ts +3 -0
  238. package/src/types/TextAlign.ts +3 -0
  239. package/src/types/Tone.ts +3 -0
  240. package/src/utils/getProps.test.ts +156 -0
  241. package/src/utils/getProps.ts +156 -0
  242. package/src/utils/mergeTriggerProps.ts +66 -0
  243. package/src/utils/renderPortal.tsx +14 -0
  244. package/src/utils/suffixClassName.ts +5 -0
  245. package/src/version.ts +3 -0
  246. package/LICENSE +0 -21
  247. package/dist/_system-CTe2lb5l.d.ts +0 -40
  248. package/dist/_system-CTe2lb5l.d.ts.map +0 -1
  249. package/dist/arrow-BmX6qbAS.js +0 -170
  250. package/dist/arrow-BmX6qbAS.js.map +0 -1
  251. package/dist/autocomplete.d.ts +0 -140
  252. package/dist/autocomplete.d.ts.map +0 -1
  253. package/dist/autocomplete.js +0 -470
  254. package/dist/autocomplete.js.map +0 -1
  255. package/dist/breadcrumbs.d.ts +0 -18
  256. package/dist/breadcrumbs.d.ts.map +0 -1
  257. package/dist/breadcrumbs.js +0 -119
  258. package/dist/breadcrumbs.js.map +0 -1
  259. package/dist/button-BNyKSDyA.js +0 -180
  260. package/dist/button-BNyKSDyA.js.map +0 -1
  261. package/dist/button-dnqMFkWg.d.ts +0 -76
  262. package/dist/button-dnqMFkWg.d.ts.map +0 -1
  263. package/dist/card-C0a0wQTc.d.ts +0 -7
  264. package/dist/card-C0a0wQTc.d.ts.map +0 -1
  265. package/dist/code.d.ts +0 -20
  266. package/dist/code.d.ts.map +0 -1
  267. package/dist/code.js +0 -90
  268. package/dist/code.js.map +0 -1
  269. package/dist/codeFontStyle-84SMhFtI.js +0 -11
  270. package/dist/codeFontStyle-84SMhFtI.js.map +0 -1
  271. package/dist/component-DVlwaWB-.d.ts +0 -25
  272. package/dist/component-DVlwaWB-.d.ts.map +0 -1
  273. package/dist/element-BYgCE_iK.js +0 -51
  274. package/dist/element-BYgCE_iK.js.map +0 -1
  275. package/dist/factory-BzXTX8_Q.js +0 -3056
  276. package/dist/factory-BzXTX8_Q.js.map +0 -1
  277. package/dist/flex-DmbDG2ks.js +0 -76
  278. package/dist/flex-DmbDG2ks.js.map +0 -1
  279. package/dist/font-EtfxKPUz.d.ts +0 -49
  280. package/dist/font-EtfxKPUz.d.ts.map +0 -1
  281. package/dist/getElementRef-Jw7gY0eV.js +0 -102
  282. package/dist/getElementRef-Jw7gY0eV.js.map +0 -1
  283. package/dist/getScopedTheme-C-sZsyAz.js +0 -267
  284. package/dist/getScopedTheme-C-sZsyAz.js.map +0 -1
  285. package/dist/getTheme_v2-y09KqtEb.js +0 -487
  286. package/dist/getTheme_v2-y09KqtEb.js.map +0 -1
  287. package/dist/grid-6JSgMj1O.js +0 -115
  288. package/dist/grid-6JSgMj1O.js.map +0 -1
  289. package/dist/helpers-C8xxXAQC.js +0 -63
  290. package/dist/helpers-C8xxXAQC.js.map +0 -1
  291. package/dist/hotkeys-CaHsQlsO.js +0 -168
  292. package/dist/hotkeys-CaHsQlsO.js.map +0 -1
  293. package/dist/index.d.ts.map +0 -1
  294. package/dist/layer-Bl5R3uNR.js +0 -61
  295. package/dist/layer-Bl5R3uNR.js.map +0 -1
  296. package/dist/layer-ChlP9Ik6.d.ts +0 -242
  297. package/dist/layer-ChlP9Ik6.d.ts.map +0 -1
  298. package/dist/menu.d.ts +0 -146
  299. package/dist/menu.d.ts.map +0 -1
  300. package/dist/menu.js +0 -626
  301. package/dist/menu.js.map +0 -1
  302. package/dist/popover-B7-28L8y.js +0 -408
  303. package/dist/popover-B7-28L8y.js.map +0 -1
  304. package/dist/popover-CMlQeKn0.d.ts +0 -96
  305. package/dist/popover-CMlQeKn0.d.ts.map +0 -1
  306. package/dist/popover.d.ts +0 -2
  307. package/dist/popover.js +0 -3
  308. package/dist/refractor-CC48DwHi.js +0 -20
  309. package/dist/refractor-CC48DwHi.js.map +0 -1
  310. package/dist/responsiveFont-CilwaORG.js +0 -55
  311. package/dist/responsiveFont-CilwaORG.js.map +0 -1
  312. package/dist/rgba-BSYaEUW6.d.ts +0 -846
  313. package/dist/rgba-BSYaEUW6.d.ts.map +0 -1
  314. package/dist/selectable-B0GUIL74.d.ts +0 -7
  315. package/dist/selectable-B0GUIL74.d.ts.map +0 -1
  316. package/dist/stack-rsNFPq2-.js +0 -112
  317. package/dist/stack-rsNFPq2-.js.map +0 -1
  318. package/dist/styles-css.d.ts +0 -2
  319. package/dist/styles-css.js +0 -4
  320. package/dist/textInput-pSfQjkmx.js +0 -313
  321. package/dist/textInput-pSfQjkmx.js.map +0 -1
  322. package/dist/theme.d.ts +0 -280
  323. package/dist/theme.d.ts.map +0 -1
  324. package/dist/theme.js +0 -55
  325. package/dist/theme.js.map +0 -1
  326. package/dist/toast.d.ts +0 -73
  327. package/dist/toast.d.ts.map +0 -1
  328. package/dist/toast.js +0 -302
  329. package/dist/toast.js.map +0 -1
  330. package/dist/tooltip.d.ts +0 -88
  331. package/dist/tooltip.d.ts.map +0 -1
  332. package/dist/tooltip.js +0 -435
  333. package/dist/tooltip.js.map +0 -1
  334. package/dist/useClickOutsideEvent-wGLGWBZN.js +0 -36
  335. package/dist/useClickOutsideEvent-wGLGWBZN.js.map +0 -1
  336. package/dist/useElementSize-BJvbGHq8.js +0 -51
  337. package/dist/useElementSize-BJvbGHq8.js.map +0 -1
  338. package/dist/useLayer-D1usxIE0.js +0 -712
  339. package/dist/useLayer-D1usxIE0.js.map +0 -1
package/README.md CHANGED
@@ -1,49 +1,215 @@
1
- # @sanity/ui
1
+ # Sanity UI 5
2
2
 
3
- The Sanity UI components.
3
+ The next version of Sanity's component library. Faster, simpler, and built on CSS instead of styled-components.
4
4
 
5
- ```sh
6
- npm install @sanity/ui
5
+ ## What's different from v3
6
+
7
+ - **CSS classes instead of styled-components.** No runtime style generation. Smaller bundles, faster renders.
8
+ - **Direct props for layout.** Width, height, position, border, and overflow are first-class props — no more `style={{ ... }}` escape hatches.
9
+ - **Works alongside v3.** Both versions run in the same app, in the same component tree. No forced migration.
10
+
11
+ Requires React 19.2+ and Node >=20.19 <22 || >=22.12.
12
+
13
+ ## For new apps
7
14
 
8
- # Install peer dependencies
9
- npm install react react-dom styled-components
15
+ ### Install
16
+
17
+ #### Recommended: the setup CLI
18
+
19
+ ```sh
20
+ npx @sanity/ui@alpha init
10
21
  ```
11
22
 
12
- [![npm version](https://img.shields.io/npm/v/@sanity/ui.svg?style=flat-square)](https://www.npmjs.com/package/@sanity/ui)
23
+ `init` detects your framework and package manager, checks your React and Node versions, then installs the package along with `@sanity/icons`, adds the stylesheet import to your entry file, reconciles `tsconfig.json`, and writes a `sanity-ui.json`. It prints a plan and asks before changing anything.
13
24
 
14
- ## Usage
25
+ `@sanity/icons` is a dependency of this package, so your package manager installs it either way. `init` also adds it to your own `package.json` because you import icons directly to pass to components, and under pnpm an import that isn't declared in your project doesn't resolve.
15
26
 
16
- ```jsx
17
- import {Button, ThemeProvider} from '@sanity/ui'
18
- import {buildTheme} from '@sanity/ui/theme'
19
- import {createRoot} from 'react-dom/client'
27
+ - `--dry` print the plan and exit without changing anything
28
+ - `--yes` accept every prompt with its default (non-interactive, for CI and agents)
29
+ - `--cwd <dir>` run against another directory
20
30
 
21
- // Static styles are shipped as a plain stylesheet (not injected at runtime) —
22
- // import it once, wherever your app loads global CSS
31
+ #### Manual
32
+
33
+ ```sh
34
+ pnpm add @sanity/ui@alpha
35
+ ```
36
+
37
+ Using npm or yarn? Use `npm add` or `yarn add` instead. (`init` above picks the right one for you.)
38
+
39
+ Then import the stylesheet at your app entry point. Without it, components render as unstyled HTML.
40
+
41
+ ```tsx
23
42
  import '@sanity/ui/styles.css'
43
+ ```
24
44
 
25
- const root = createRoot(document.getElementById('root'))
26
- const theme = buildTheme()
45
+ ### Usage
46
+
47
+ ```tsx
48
+ import {Box, Flex, Card, Heading, Text, Button} from '@sanity/ui'
49
+ import {AddIcon} from '@sanity/icons/Add'
50
+
51
+ export default function App() {
52
+ return (
53
+ <Flex minHeight="100vh">
54
+ <Box as="nav" aria-label="Main" padding={3} borderRight width="240px">
55
+ <Heading as="h2" size={1}>
56
+ My App
57
+ </Heading>
58
+ </Box>
59
+ <Box as="main" padding={4} flexGrow={1}>
60
+ <Flex alignItems="center" justifyContent="space-between" flexWrap="wrap" gap={2}>
61
+ <Heading as="h1" size={2}>
62
+ Documents
63
+ </Heading>
64
+ <Button iconStart={AddIcon} text="New" />
65
+ </Flex>
66
+ <Box marginTop={3}>
67
+ <Card density="loose">
68
+ <Text size={1}>First document</Text>
69
+ <Text size={1} muted>
70
+ Edited 2 hours ago
71
+ </Text>
72
+ </Card>
73
+ </Box>
74
+ </Box>
75
+ </Flex>
76
+ )
77
+ }
78
+ ```
27
79
 
28
- root.render(
29
- <ThemeProvider theme={theme}>
30
- <Button text="Hello, world" />
31
- </ThemeProvider>,
32
- )
80
+ ### Checking your setup
81
+
82
+ Run `doctor` at any time to verify an install and get a one-line fix for anything broken:
83
+
84
+ ```sh
85
+ npx @sanity/ui@alpha doctor
33
86
  ```
34
87
 
35
- ## Contributing and releasing new versions to npm
88
+ It checks that the package and `@sanity/icons` are installed, the stylesheet resolves and is imported, `tsconfig.json` has the options the components need, and your React and Node versions are supported.
89
+
90
+ ## For apps using Sanity UI v3
36
91
 
37
- This package lives in the [`sanity-io/ui` monorepo](https://github.com/sanity-io/ui) and uses [Changesets](https://github.com/changesets/changesets) to manage versioning and publishing.
92
+ ### Install
38
93
 
39
- When you make a change that should be released, add a changeset to your pull request:
94
+ v3 and v5 are the same npm package at different majors, so an app that wants both needs a package alias to tell them apart.
95
+
96
+ In apps still using v3 components, we recommend installing v5 with a package alias. This way, your existing `@sanity/ui` imports keep resolving to v3, so nothing you have already written needs to change.
40
97
 
41
98
  ```sh
42
- pnpm changeset
99
+ pnpm add ui5@npm:@sanity/ui@alpha
100
+ ```
101
+
102
+ This gives you:
103
+
104
+ ```json
105
+ {
106
+ "dependencies": {
107
+ "@sanity/ui": "^3.5.1",
108
+ "ui5": "npm:@sanity/ui@alpha"
109
+ }
110
+ }
43
111
  ```
44
112
 
45
- Once pull requests with changesets are merged into the `main` branch, a "Version Packages" pull request is opened (and kept up to date) that bumps the affected package versions and updates their changelogs. Merging that pull request publishes the packages to npm through the [`Release` workflow](https://github.com/sanity-io/ui/actions/workflows/release.yml), which uses npm [Trusted Publishing](https://docs.npmjs.com/trusted-publishers) (OIDC).
113
+ Now `@sanity/ui` means v3 everywhere in your codebase and `ui5` means v5. You migrate one import at a time, at whatever pace suits you, instead of rewriting every file before anything runs.
114
+
115
+ The alias name is yours to choose. `ui5` is what we use internally. npm, pnpm, and yarn all support the `npm:` protocol.
116
+
117
+ ### Setup
118
+
119
+ v5 has no `ThemeProvider`, and it does not read v3's theme. Keep your existing provider setup exactly as it is: v3 components still need it, and it has no effect on v5.
120
+
121
+ With v5 installed under an alias, the stylesheet comes from the alias too.
122
+
123
+ ```tsx
124
+ import {ThemeProvider, studioTheme, ToastProvider} from '@sanity/ui' // v3
125
+ import 'ui5/styles.css' // v5
126
+ import App from './App'
127
+
128
+ createRoot(document.getElementById('root')!).render(
129
+ <ThemeProvider theme={studioTheme}>
130
+ <ToastProvider>
131
+ <App />
132
+ </ToastProvider>
133
+ </ThemeProvider>,
134
+ )
135
+ ```
136
+
137
+ ### Usage
138
+
139
+ With v5 aliased, which version you get is just which specifier you import from. Anything without a v5 equivalent keeps coming from `@sanity/ui`, and the two sit next to each other in the same tree.
140
+
141
+ ```tsx
142
+ import {Dialog} from '@sanity/ui' // v3
143
+ import {Button, Card, Text} from 'ui5' // v5
144
+ ```
145
+
146
+ ## Components
147
+
148
+ ### Layout
149
+
150
+ | Component | What it does |
151
+ | ----------- | ----------------------------------------------------------------------------------- |
152
+ | `Box` | General container. Padding, margin, border, overflow, and position as direct props. |
153
+ | `Flex` | Flexbox layout. `alignItems`, `justifyContent`, `flexDirection`, `gap`. |
154
+ | `Grid` | CSS grid. Use `gridTemplateColumns` with a CSS string (e.g. `"repeat(3, 1fr)"`). |
155
+ | `Container` | Max-width content wrapper. |
156
+ | `HStack` | Horizontal stack. Accepts `gap` and `as` only — use `Flex` for alignment control. |
157
+ | `VStack` | Vertical stack. Accepts `gap` and `as` only — use `Flex` for alignment control. |
158
+ | `Inline` | Inline flow layout with wrapping and gap. |
159
+
160
+ ### Typography
161
+
162
+ | Component | What it does |
163
+ | --------- | ----------------------------------------------------------------------- |
164
+ | `Heading` | Semantic headings (`h1`–`h6`). Always set `as` to match the level. |
165
+ | `Text` | Body copy, captions, labels. Props: `size`, `weight`, `muted`, `align`. |
166
+ | `Label` | Form input label. Use only with form elements. |
167
+ | `Code` | Inline or block code. Uses the system monospace font. |
168
+
169
+ ### Interactive
170
+
171
+ | Component | What it does |
172
+ | ---------- | ------------------------------------------------------------------------------------------------------------------------ |
173
+ | `Button` | `level` (primary/secondary/tertiary), `tone` (neutral/critical), `iconStart`, `iconEnd`, `text`, `fullWidth`, `loading`. |
174
+ | `Checkbox` | Controlled checkbox. Requires `label` (string). |
175
+ | `Radio` | Controlled radio button. |
176
+ | `Switch` | Toggle control. Requires `label` (string). |
177
+ | `Link` | Anchor element styled as a link. |
178
+
179
+ ### Display
180
+
181
+ | Component | What it does |
182
+ | ---------------- | -------------------------------------------------------------------------------------------------------------------- |
183
+ | `Card` | Raised surface with background and `tone`. Does not accept padding, margin, or layout props — wrap content in `Box`. |
184
+ | `Divider` | Horizontal rule between content sections. |
185
+ | `Icon` | Renders a `@sanity/icons` icon component. Always set `aria-label` or `aria-hidden`. |
186
+ | `Indicator` | Status dot with `tone`. |
187
+ | `IndicatorStack` | Grouped `Indicator` elements. |
188
+ | `Spinner` | Loading indicator. |
189
+
190
+ ### Lists
191
+
192
+ | Component | What it does |
193
+ | --------- | --------------------------------------------------- |
194
+ | `List` | Semantic list. Use `List.Item` and `List.ItemText`. |
195
+
196
+ ### Accessibility
197
+
198
+ | Component | What it does |
199
+ | ---------------- | ------------------------------------------------------------------------------------------------ |
200
+ | `SkipToContent` | Visually hidden skip-nav link. Must be the first focusable element. Requires `href` and `label`. |
201
+ | `VisuallyHidden` | Hides content visually while keeping it in the accessibility tree. |
202
+
203
+ ### Still only in v3
204
+
205
+ Menu, Dialog, TextInput, Select, Stack, Badge, ThemeProvider, ToastProvider, and other components not yet migrated to v5. Import these from `@sanity/ui` exactly as you do today.
206
+
207
+ ## Contributing
208
+
209
+ We welcome feedback and contributions. Start here:
46
210
 
47
- ## License
211
+ 1. **Try the components** and report what breaks, what confuses, or what's missing.
212
+ 2. **Read the contribution model** — build a recipe with existing building block components, then propose graduating it.
213
+ 3. **Open a PR** following the branch naming and checklist in the developer contribution docs.
48
214
 
49
- MIT-licensed. See LICENSE.
215
+ During preview, the most useful contribution is using the components and telling us what happens.
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+
3
+ import process from 'node:process'
4
+
5
+ import {main} from '../dist/cli/index.js'
6
+ import {errorLine} from '../dist/cli/log.js'
7
+
8
+ try {
9
+ const code = await main(process.argv.slice(2))
10
+ process.exitCode = code ?? 0
11
+ } catch (error) {
12
+ errorLine(error instanceof Error ? error.message : String(error))
13
+ process.exitCode = 1
14
+ }
@@ -0,0 +1,20 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { c } from "react/compiler-runtime";
4
+ import { hasLanguage, Refractor } from "react-refractor";
5
+ function LazyRefractor(t0) {
6
+ const $ = c(5), {
7
+ language,
8
+ value
9
+ } = t0;
10
+ if (!hasLanguage(language)) {
11
+ let t12;
12
+ return $[0] !== value ? (t12 = /* @__PURE__ */ jsx("code", { children: value }), $[0] = value, $[1] = t12) : t12 = $[1], t12;
13
+ }
14
+ let t1;
15
+ return $[2] !== language || $[3] !== value ? (t1 = /* @__PURE__ */ jsx(Refractor, { inline: !0, language, value }), $[2] = language, $[3] = value, $[4] = t1) : t1 = $[4], t1;
16
+ }
17
+ export {
18
+ LazyRefractor as default
19
+ };
20
+ //# sourceMappingURL=LazyRefractor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LazyRefractor.js","sources":["../../src/components/code/LazyRefractor.tsx"],"sourcesContent":["import {hasLanguage, Refractor, type RefractorProps} from 'react-refractor'\n\nexport default function LazyRefractor({language, value}: RefractorProps) {\n if (!hasLanguage(language)) {\n return <code>{value}</code>\n }\n\n return <Refractor inline language={language} value={value} />\n}\n"],"names":["LazyRefractor","t0","$","_c","language","value","hasLanguage","t1"],"mappings":";;;;AAEA,SAAeA,cAAAC,IAAA;AAAA,QAAAC,IAAAC,EAAA,CAAA,GAAuB;AAAA,IAAAC;AAAAA,IAAAC;AAAAA,EAAAA,IAAAJ;AACpC,MAAI,CAACK,YAAYF,QAAQ,GAAC;AAAA,QAAAG;AAAA,WAAAL,SAAAG,SACjBE,MAAA,oBAAA,QAAA,EAAOF,iBAAM,GAAOH,OAAAG,OAAAH,OAAAK,OAAAA,MAAAL,EAAA,CAAA,GAApBK;AAAAA,EAAoB;AAC5B,MAAAA;AAAA,SAAAL,EAAA,CAAA,MAAAE,YAAAF,SAAAG,SAEME,yBAAC,WAAA,EAAU,YAAiBH,UAAiBC,MAAAA,CAAK,GAAIH,OAAAE,UAAAF,OAAAG,OAAAH,OAAAK,MAAAA,KAAAL,EAAA,CAAA,GAAtDK;AAAsD;"}
@@ -0,0 +1,148 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { createRequire } from 'node:module';
3
+ import { dirname, join } from 'node:path';
4
+ import process from 'node:process';
5
+ import { detect, isPackageInstalled, isLegacyUiVersion, PACKAGE_NAME, readInstalledPackageVersion, } from './detect.js';
6
+ import { inspectStylesheet, STYLES_SPECIFIER } from './styles.js';
7
+ import { inspectTsconfig } from './tsconfig.js';
8
+ import { meetsNode, meetsReact, NODE_REQUIREMENT, REACT_MIN } from './versions.js';
9
+ const ICONS = '@sanity/icons';
10
+ /** Resolves the built stylesheet on disk (not the Node no-op shim). */
11
+ function resolveStylesheet(cwd) {
12
+ try {
13
+ const require = createRequire(join(cwd, 'noop.js'));
14
+ const pkgJsonPath = require.resolve(`${PACKAGE_NAME}/package.json`);
15
+ const path = join(dirname(pkgJsonPath), 'dist/styles.css');
16
+ return existsSync(path) ? path : null;
17
+ }
18
+ catch {
19
+ return null;
20
+ }
21
+ }
22
+ /**
23
+ * Without a browser, "a component renders with a computed background" is
24
+ * verified by confirming the resolved stylesheet ships the component class and
25
+ * a background declaration for it. If both are present and the import is wired,
26
+ * the component renders styled.
27
+ */
28
+ function stylesheetHasComponentStyles(path) {
29
+ try {
30
+ const css = readFileSync(path, 'utf8');
31
+ return /\.sui-Card\b/.test(css) && /background/.test(css);
32
+ }
33
+ catch {
34
+ return false;
35
+ }
36
+ }
37
+ function check(id, ok, meta) {
38
+ return {
39
+ id,
40
+ title: meta.title,
41
+ ok,
42
+ status: meta.status ?? (ok ? 'pass' : 'fail'),
43
+ detail: meta.detail,
44
+ fix: meta.fix,
45
+ };
46
+ }
47
+ /**
48
+ * Runs every verification and returns structured results. `doctor` prints them
49
+ * all; `init` reuses a subset on exit. Pure read-only.
50
+ */
51
+ export function collectChecks(cwd) {
52
+ const detected = detect(cwd);
53
+ const checks = [];
54
+ const installedVersion = readInstalledPackageVersion(cwd, PACKAGE_NAME);
55
+ checks.push(check('install', Boolean(installedVersion), {
56
+ title: 'Package installed',
57
+ detail: installedVersion ? `${PACKAGE_NAME}@${installedVersion}` : 'not installed',
58
+ fix: `Run \`npx ${PACKAGE_NAME}@alpha init\` or install it with your package manager`,
59
+ }));
60
+ if (installedVersion) {
61
+ const legacyInstalled = isLegacyUiVersion(installedVersion);
62
+ checks.push(check('version', !legacyInstalled, {
63
+ title: 'Package version',
64
+ detail: legacyInstalled
65
+ ? `${PACKAGE_NAME}@${installedVersion} is pre-v5 (expected v5)`
66
+ : `${PACKAGE_NAME}@${installedVersion}`,
67
+ fix: `Remove the existing @sanity/ui version, or install v5 with a package alias — see README "For apps using Sanity UI v3"`,
68
+ }));
69
+ }
70
+ const iconsInstalled = isPackageInstalled(cwd, ICONS);
71
+ checks.push(check('icons', iconsInstalled, {
72
+ title: 'Icons installed',
73
+ detail: iconsInstalled ? `${ICONS} present` : `${ICONS} missing`,
74
+ fix: `Install it: add ${ICONS}`,
75
+ }));
76
+ const stylesheetPath = resolveStylesheet(cwd);
77
+ const stylesheetResolves = Boolean(stylesheetPath);
78
+ checks.push(check('stylesheet-resolves', stylesheetResolves, {
79
+ title: 'Stylesheet resolves',
80
+ detail: stylesheetResolves ? `${PACKAGE_NAME}/styles.css` : 'cannot resolve styles.css',
81
+ fix: 'Reinstall the package, or build it if working inside this monorepo',
82
+ }));
83
+ const stylesheet = inspectStylesheet(cwd, detected.entry);
84
+ checks.push(check('stylesheet-wired', stylesheet.imported, {
85
+ title: 'Stylesheet imported',
86
+ detail: stylesheet.imported
87
+ ? `imported in ${stylesheet.importedIn ?? detected.entry}`
88
+ : stylesheet.exists
89
+ ? `missing from ${detected.entry}`
90
+ : `entry file ${detected.entry} not found`,
91
+ fix: `Add \`import '${STYLES_SPECIFIER}'\` to ${detected.entry}`,
92
+ }));
93
+ if (detected.typescript) {
94
+ const tsconfig = inspectTsconfig(cwd);
95
+ const loadBearing = tsconfig.changes.filter((c) => c.load);
96
+ // An unreadable file has no changes to report, so guard against it explicitly
97
+ // rather than letting an empty change list read as "all good".
98
+ const ok = tsconfig.exists && !tsconfig.unreadable && loadBearing.length === 0;
99
+ checks.push(check('tsconfig', ok, {
100
+ title: 'tsconfig options',
101
+ detail: !tsconfig.exists
102
+ ? 'tsconfig.json not found'
103
+ : tsconfig.unreadable
104
+ ? 'tsconfig.json could not be parsed'
105
+ : loadBearing.length === 0
106
+ ? tsconfig.changes.length === 0
107
+ ? 'all required options set'
108
+ : 'required options set (some optional additions available)'
109
+ : `needs: ${loadBearing.map((c) => `${c.key}: ${String(c.to)}`).join(', ')}`,
110
+ fix: tsconfig.unreadable
111
+ ? 'Fix the JSON syntax in tsconfig.json'
112
+ : `Run \`npx ${PACKAGE_NAME}@alpha init\` to reconcile tsconfig.json`,
113
+ }));
114
+ }
115
+ const reactVersion = detected.react.version;
116
+ const reactOk = reactVersion ? meetsReact(reactVersion) : false;
117
+ checks.push(check('react', reactOk, {
118
+ title: 'React version',
119
+ status: reactVersion ? (reactOk ? 'pass' : 'fail') : 'warn',
120
+ detail: reactVersion ? `react ${reactVersion}` : 'react not detected',
121
+ fix: `Upgrade React to >= ${REACT_MIN}`,
122
+ }));
123
+ const nodeVersion = process.versions.node;
124
+ checks.push(check('node', meetsNode(nodeVersion), {
125
+ title: 'Node version',
126
+ detail: `node ${nodeVersion} (requires ${NODE_REQUIREMENT})`,
127
+ fix: `Use Node ${NODE_REQUIREMENT}`,
128
+ }));
129
+ return { detected, checks, stylesheetPath };
130
+ }
131
+ /** The subset `init` verifies on exit, plus the render probe. */
132
+ export function verifyAfterInit(cwd) {
133
+ const { checks, stylesheetPath } = collectChecks(cwd);
134
+ const ids = new Set(['stylesheet-resolves', 'stylesheet-wired', 'tsconfig']);
135
+ const subset = checks.filter((c) => ids.has(c.id));
136
+ const probeOk = stylesheetPath ? stylesheetHasComponentStyles(stylesheetPath) : false;
137
+ subset.push({
138
+ id: 'probe',
139
+ title: 'Probe renders with background',
140
+ ok: probeOk,
141
+ status: probeOk ? 'pass' : 'fail',
142
+ detail: stylesheetPath
143
+ ? 'Card ships a background declaration'
144
+ : 'stylesheet not resolved, cannot probe',
145
+ fix: 'Ensure the package is installed and built',
146
+ });
147
+ return subset;
148
+ }
@@ -0,0 +1,23 @@
1
+ import { existsSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ export const CONFIG_FILE = 'sanity-ui.json';
4
+ /**
5
+ * Builds the sanity-ui.json contents: a small record of the detected setup
6
+ * (framework, entry) written to the project root for tooling to read.
7
+ */
8
+ export function buildConfig({ framework, entry, }) {
9
+ return {
10
+ $schema: 'https://sanity-ui.sanity.dev/schema.json',
11
+ framework,
12
+ entry,
13
+ };
14
+ }
15
+ export function configExists(cwd) {
16
+ return existsSync(join(cwd, CONFIG_FILE));
17
+ }
18
+ export function writeConfig(cwd, config, { dryRun = false } = {}) {
19
+ const file = join(cwd, CONFIG_FILE);
20
+ if (!dryRun)
21
+ writeFileSync(file, `${JSON.stringify(config, null, 2)}\n`);
22
+ return { file, wrote: !dryRun };
23
+ }
@@ -0,0 +1,181 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { parseVersion } from './versions.js';
4
+ export const PACKAGE_NAME = '@sanity/ui';
5
+ export const LEGACY_COEXISTENCE_HINT = 'To add v5 alongside an existing @sanity/ui install, use a package alias — see the README section "For apps using Sanity UI v3".';
6
+ /**
7
+ * Framework ids map 1:1 to the `framework` field written into sanity-ui.json.
8
+ * Each carries the ordered list of candidate entry files where the stylesheet
9
+ * import belongs.
10
+ */
11
+ const FRAMEWORK_ENTRIES = {
12
+ 'next-app': ['app/layout.tsx', 'app/layout.jsx', 'src/app/layout.tsx', 'src/app/layout.jsx'],
13
+ 'next-pages': ['pages/_app.tsx', 'pages/_app.jsx', 'src/pages/_app.tsx', 'src/pages/_app.jsx'],
14
+ 'react-router': ['app/root.tsx', 'app/root.jsx'],
15
+ 'remix': ['app/root.tsx', 'app/root.jsx'],
16
+ 'vite': ['src/main.tsx', 'src/main.jsx', 'src/main.ts'],
17
+ 'astro': [],
18
+ 'react': ['src/index.tsx', 'src/main.tsx', 'src/index.jsx', 'src/main.jsx'],
19
+ };
20
+ export function readPackageJson(cwd) {
21
+ const file = join(cwd, 'package.json');
22
+ if (!existsSync(file))
23
+ return null;
24
+ try {
25
+ return JSON.parse(readFileSync(file, 'utf8'));
26
+ }
27
+ catch {
28
+ return null;
29
+ }
30
+ }
31
+ export function allDeps(pkg) {
32
+ if (!pkg)
33
+ return {};
34
+ return { ...pkg.dependencies, ...pkg.devDependencies, ...pkg.peerDependencies };
35
+ }
36
+ export function detectPackageManager(cwd) {
37
+ if (existsSync(join(cwd, 'bun.lockb')) || existsSync(join(cwd, 'bun.lock')))
38
+ return 'bun';
39
+ if (existsSync(join(cwd, 'pnpm-lock.yaml')))
40
+ return 'pnpm';
41
+ if (existsSync(join(cwd, 'yarn.lock')))
42
+ return 'yarn';
43
+ if (existsSync(join(cwd, 'package-lock.json')))
44
+ return 'npm';
45
+ const declared = readPackageJson(cwd)?.packageManager;
46
+ if (typeof declared === 'string') {
47
+ const name = declared.split('@')[0];
48
+ if (name === 'pnpm' || name === 'yarn' || name === 'npm' || name === 'bun')
49
+ return name;
50
+ }
51
+ return 'npm';
52
+ }
53
+ export function detectFramework(cwd, pkg = readPackageJson(cwd)) {
54
+ const deps = allDeps(pkg);
55
+ const has = (name) => Boolean(deps[name]);
56
+ const dir = (relative) => existsSync(join(cwd, relative));
57
+ if (has('next')) {
58
+ if (dir('app') || dir('src/app'))
59
+ return 'next-app';
60
+ if (dir('pages') || dir('src/pages'))
61
+ return 'next-pages';
62
+ return 'next-app';
63
+ }
64
+ if (has('@remix-run/react') || has('@remix-run/dev'))
65
+ return 'remix';
66
+ if (has('@react-router/dev') || has('react-router'))
67
+ return 'react-router';
68
+ if (has('astro'))
69
+ return 'astro';
70
+ if (has('vite'))
71
+ return 'vite';
72
+ return 'react';
73
+ }
74
+ export function detectTypeScript(cwd, pkg = readPackageJson(cwd)) {
75
+ return existsSync(join(cwd, 'tsconfig.json')) || Boolean(allDeps(pkg)['typescript']);
76
+ }
77
+ /**
78
+ * Returns the first existing candidate entry file, otherwise the first
79
+ * candidate as the place to create one. Astro has no fixed entry, so we look
80
+ * for a layout component.
81
+ */
82
+ export function detectEntry(cwd, framework) {
83
+ if (framework === 'astro')
84
+ return detectAstroLayout(cwd);
85
+ const candidates = FRAMEWORK_ENTRIES[framework];
86
+ const existing = candidates.find((rel) => existsSync(join(cwd, rel)));
87
+ return { entry: existing ?? candidates[0] ?? 'src/index.tsx', exists: Boolean(existing) };
88
+ }
89
+ function detectAstroLayout(cwd) {
90
+ const candidates = [
91
+ 'src/layouts/Layout.astro',
92
+ 'src/layouts/BaseLayout.astro',
93
+ 'src/layouts/index.astro',
94
+ 'src/pages/index.astro',
95
+ ];
96
+ const existing = candidates.find((rel) => existsSync(join(cwd, rel)));
97
+ return { entry: existing ?? candidates[0] ?? 'src/layouts/Layout.astro', exists: Boolean(existing) };
98
+ }
99
+ /**
100
+ * Resolves the installed React version from node_modules, falling back to the
101
+ * declared range in package.json (carets/tildes stripped).
102
+ */
103
+ export function detectReactVersion(cwd, pkg = readPackageJson(cwd)) {
104
+ const installed = readInstalledVersion(cwd, 'react');
105
+ if (installed)
106
+ return { version: installed, source: 'installed' };
107
+ const declared = allDeps(pkg)['react'];
108
+ if (typeof declared === 'string') {
109
+ return { version: declared.replace(/^[\^~>=<\s]+/, ''), source: 'declared' };
110
+ }
111
+ return { version: null, source: 'missing' };
112
+ }
113
+ function readInstalledVersion(cwd, name) {
114
+ const file = join(cwd, 'node_modules', name, 'package.json');
115
+ if (!existsSync(file))
116
+ return null;
117
+ try {
118
+ const pkg = JSON.parse(readFileSync(file, 'utf8'));
119
+ return pkg.version ?? null;
120
+ }
121
+ catch {
122
+ return null;
123
+ }
124
+ }
125
+ export function isPackageInstalled(cwd, name) {
126
+ return existsSync(join(cwd, 'node_modules', name, 'package.json'));
127
+ }
128
+ export function readInstalledPackageVersion(cwd, name) {
129
+ return readInstalledVersion(cwd, name);
130
+ }
131
+ export function isLegacyUiVersion(version) {
132
+ const major = parseVersion(version)?.[0];
133
+ return major !== undefined && major < 5;
134
+ }
135
+ function detectAliasedInstall(deps) {
136
+ for (const [name, spec] of Object.entries(deps)) {
137
+ if (typeof spec === 'string' && /^npm:@sanity\/ui@(5(?:\..*)?|alpha)$/.test(spec))
138
+ return name;
139
+ }
140
+ return null;
141
+ }
142
+ /** Reads the major from a declared range, skipping workspace/catalog/link specs. */
143
+ function declaredMajor(spec) {
144
+ if (typeof spec !== 'string')
145
+ return null;
146
+ if (/^(workspace:|catalog:|link:|file:)/.test(spec))
147
+ return null;
148
+ const npmAlias = spec.match(/^npm:@sanity\/ui@(\d+)/);
149
+ if (npmAlias)
150
+ return Number(npmAlias[1]);
151
+ return parseVersion(spec.replace(/^[\^~>=<\s]+/, ''))?.[0] ?? null;
152
+ }
153
+ function detectLegacyInstall(cwd, deps) {
154
+ const installed = readInstalledPackageVersion(cwd, PACKAGE_NAME);
155
+ if (installed && isLegacyUiVersion(installed)) {
156
+ return installed;
157
+ }
158
+ const declared = deps[PACKAGE_NAME];
159
+ const major = declaredMajor(declared);
160
+ if (major !== null && major < 5) {
161
+ return declared ?? 'unknown';
162
+ }
163
+ return null;
164
+ }
165
+ export function detect(cwd) {
166
+ const pkg = readPackageJson(cwd);
167
+ const deps = allDeps(pkg);
168
+ const framework = detectFramework(cwd, pkg);
169
+ const entry = detectEntry(cwd, framework);
170
+ const typescript = detectTypeScript(cwd, pkg) || (entry.exists && /\.tsx?$/.test(entry.entry));
171
+ return {
172
+ pkg,
173
+ packageManager: detectPackageManager(cwd),
174
+ framework,
175
+ typescript,
176
+ ...entry,
177
+ react: detectReactVersion(cwd, pkg),
178
+ aliasedInstall: detectAliasedInstall(deps),
179
+ legacyInstall: detectLegacyInstall(cwd, deps),
180
+ };
181
+ }