@spaceinvoices/react-ui 0.4.8 → 0.4.11

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 (276) hide show
  1. package/README.md +24 -8
  2. package/cli/dist/index.js +89 -26
  3. package/package.json +4 -1
  4. package/spaceinvoices.schema.json +6 -1
  5. package/src/common/autocomplete.tsx +69 -6
  6. package/src/components/advance-invoices/create/create-advance-invoice-form.tsx +124 -285
  7. package/src/components/advance-invoices/list/list-table.tsx +10 -3
  8. package/src/components/advance-invoices/list/locales/de.ts +2 -0
  9. package/src/components/advance-invoices/list/locales/en.ts +1 -0
  10. package/src/components/advance-invoices/list/locales/es.ts +1 -0
  11. package/src/components/advance-invoices/list/locales/fr.ts +1 -0
  12. package/src/components/advance-invoices/list/locales/hr.ts +1 -0
  13. package/src/components/advance-invoices/list/locales/it.ts +1 -0
  14. package/src/components/advance-invoices/list/locales/nl.ts +1 -0
  15. package/src/components/advance-invoices/list/locales/pl.ts +1 -0
  16. package/src/components/advance-invoices/list/locales/pt.ts +1 -0
  17. package/src/components/advance-invoices/list/locales/sl.ts +1 -0
  18. package/src/components/advance-invoices/list/use-advance-invoice-download.ts +1 -12
  19. package/src/components/credit-notes/create/create-credit-note-form.tsx +116 -238
  20. package/src/components/credit-notes/list/list-table.tsx +6 -3
  21. package/src/components/credit-notes/list/use-credit-note-download.ts +1 -12
  22. package/src/components/customers/customer-autocomplete.tsx +64 -11
  23. package/src/components/customers/customer-list-table/customer-list-table.tsx +3 -2
  24. package/src/components/dashboard/collection-rate-card/collection-rate-card.tsx +9 -1
  25. package/src/components/dashboard/collection-rate-card/locales/bg.ts +3 -0
  26. package/src/components/dashboard/collection-rate-card/locales/cs.ts +3 -0
  27. package/src/components/dashboard/collection-rate-card/locales/et.ts +3 -0
  28. package/src/components/dashboard/collection-rate-card/locales/fi.ts +3 -0
  29. package/src/components/dashboard/collection-rate-card/locales/is.ts +3 -0
  30. package/src/components/dashboard/collection-rate-card/locales/nb.ts +3 -0
  31. package/src/components/dashboard/collection-rate-card/locales/sk.ts +3 -0
  32. package/src/components/dashboard/collection-rate-card/locales/sv.ts +3 -0
  33. package/src/components/dashboard/invoice-status-chart/invoice-status-chart.tsx +10 -2
  34. package/src/components/dashboard/invoice-status-chart/locales/bg.ts +10 -0
  35. package/src/components/dashboard/invoice-status-chart/locales/cs.ts +10 -0
  36. package/src/components/dashboard/invoice-status-chart/locales/de.ts +1 -0
  37. package/src/components/dashboard/invoice-status-chart/locales/es.ts +1 -0
  38. package/src/components/dashboard/invoice-status-chart/locales/et.ts +10 -0
  39. package/src/components/dashboard/invoice-status-chart/locales/fi.ts +10 -0
  40. package/src/components/dashboard/invoice-status-chart/locales/fr.ts +1 -0
  41. package/src/components/dashboard/invoice-status-chart/locales/hr.ts +1 -0
  42. package/src/components/dashboard/invoice-status-chart/locales/is.ts +10 -0
  43. package/src/components/dashboard/invoice-status-chart/locales/it.ts +1 -0
  44. package/src/components/dashboard/invoice-status-chart/locales/nb.ts +10 -0
  45. package/src/components/dashboard/invoice-status-chart/locales/nl.ts +1 -0
  46. package/src/components/dashboard/invoice-status-chart/locales/pl.ts +1 -0
  47. package/src/components/dashboard/invoice-status-chart/locales/pt.ts +1 -0
  48. package/src/components/dashboard/invoice-status-chart/locales/sk.ts +10 -0
  49. package/src/components/dashboard/invoice-status-chart/locales/sl.ts +1 -0
  50. package/src/components/dashboard/invoice-status-chart/locales/sv.ts +10 -0
  51. package/src/components/dashboard/payment-methods-chart/locales/bg.ts +12 -0
  52. package/src/components/dashboard/payment-methods-chart/locales/cs.ts +12 -0
  53. package/src/components/dashboard/payment-methods-chart/locales/et.ts +12 -0
  54. package/src/components/dashboard/payment-methods-chart/locales/fi.ts +12 -0
  55. package/src/components/dashboard/payment-methods-chart/locales/is.ts +12 -0
  56. package/src/components/dashboard/payment-methods-chart/locales/nb.ts +12 -0
  57. package/src/components/dashboard/payment-methods-chart/locales/sk.ts +12 -0
  58. package/src/components/dashboard/payment-methods-chart/locales/sv.ts +12 -0
  59. package/src/components/dashboard/payment-methods-chart/payment-methods-chart.tsx +9 -1
  60. package/src/components/dashboard/payment-trend-chart/locales/bg.ts +6 -0
  61. package/src/components/dashboard/payment-trend-chart/locales/cs.ts +6 -0
  62. package/src/components/dashboard/payment-trend-chart/locales/de.ts +1 -0
  63. package/src/components/dashboard/payment-trend-chart/locales/es.ts +1 -0
  64. package/src/components/dashboard/payment-trend-chart/locales/et.ts +6 -0
  65. package/src/components/dashboard/payment-trend-chart/locales/fi.ts +6 -0
  66. package/src/components/dashboard/payment-trend-chart/locales/fr.ts +1 -0
  67. package/src/components/dashboard/payment-trend-chart/locales/hr.ts +1 -0
  68. package/src/components/dashboard/payment-trend-chart/locales/is.ts +6 -0
  69. package/src/components/dashboard/payment-trend-chart/locales/it.ts +1 -0
  70. package/src/components/dashboard/payment-trend-chart/locales/nb.ts +6 -0
  71. package/src/components/dashboard/payment-trend-chart/locales/nl.ts +1 -0
  72. package/src/components/dashboard/payment-trend-chart/locales/pl.ts +1 -0
  73. package/src/components/dashboard/payment-trend-chart/locales/pt.ts +1 -0
  74. package/src/components/dashboard/payment-trend-chart/locales/sk.ts +6 -0
  75. package/src/components/dashboard/payment-trend-chart/locales/sl.ts +1 -0
  76. package/src/components/dashboard/payment-trend-chart/locales/sv.ts +6 -0
  77. package/src/components/dashboard/payment-trend-chart/payment-trend-chart.tsx +15 -8
  78. package/src/components/dashboard/revenue-trend-chart/locales/bg.ts +6 -0
  79. package/src/components/dashboard/revenue-trend-chart/locales/cs.ts +6 -0
  80. package/src/components/dashboard/revenue-trend-chart/locales/de.ts +1 -0
  81. package/src/components/dashboard/revenue-trend-chart/locales/es.ts +1 -0
  82. package/src/components/dashboard/revenue-trend-chart/locales/et.ts +6 -0
  83. package/src/components/dashboard/revenue-trend-chart/locales/fi.ts +6 -0
  84. package/src/components/dashboard/revenue-trend-chart/locales/fr.ts +1 -0
  85. package/src/components/dashboard/revenue-trend-chart/locales/hr.ts +1 -0
  86. package/src/components/dashboard/revenue-trend-chart/locales/is.ts +6 -0
  87. package/src/components/dashboard/revenue-trend-chart/locales/it.ts +1 -0
  88. package/src/components/dashboard/revenue-trend-chart/locales/nb.ts +6 -0
  89. package/src/components/dashboard/revenue-trend-chart/locales/nl.ts +1 -0
  90. package/src/components/dashboard/revenue-trend-chart/locales/pl.ts +1 -0
  91. package/src/components/dashboard/revenue-trend-chart/locales/pt.ts +1 -0
  92. package/src/components/dashboard/revenue-trend-chart/locales/sk.ts +6 -0
  93. package/src/components/dashboard/revenue-trend-chart/locales/sl.ts +1 -0
  94. package/src/components/dashboard/revenue-trend-chart/locales/sv.ts +6 -0
  95. package/src/components/dashboard/revenue-trend-chart/revenue-trend-chart.tsx +15 -8
  96. package/src/components/dashboard/tax-collected-card/locales.ts +110 -0
  97. package/src/components/dashboard/tax-collected-card/tax-collected-card.tsx +8 -2
  98. package/src/components/dashboard/tax-collected-card/use-tax-collected.ts +4 -4
  99. package/src/components/dashboard/top-customers-chart/locales/bg.ts +7 -0
  100. package/src/components/dashboard/top-customers-chart/locales/cs.ts +7 -0
  101. package/src/components/dashboard/top-customers-chart/locales/de.ts +2 -0
  102. package/src/components/dashboard/top-customers-chart/locales/es.ts +2 -0
  103. package/src/components/dashboard/top-customers-chart/locales/et.ts +7 -0
  104. package/src/components/dashboard/top-customers-chart/locales/fi.ts +7 -0
  105. package/src/components/dashboard/top-customers-chart/locales/fr.ts +2 -0
  106. package/src/components/dashboard/top-customers-chart/locales/hr.ts +2 -0
  107. package/src/components/dashboard/top-customers-chart/locales/is.ts +7 -0
  108. package/src/components/dashboard/top-customers-chart/locales/it.ts +2 -0
  109. package/src/components/dashboard/top-customers-chart/locales/nb.ts +7 -0
  110. package/src/components/dashboard/top-customers-chart/locales/nl.ts +2 -0
  111. package/src/components/dashboard/top-customers-chart/locales/pl.ts +2 -0
  112. package/src/components/dashboard/top-customers-chart/locales/pt.ts +2 -0
  113. package/src/components/dashboard/top-customers-chart/locales/sk.ts +7 -0
  114. package/src/components/dashboard/top-customers-chart/locales/sl.ts +2 -0
  115. package/src/components/dashboard/top-customers-chart/locales/sv.ts +7 -0
  116. package/src/components/dashboard/top-customers-chart/top-customers-chart.tsx +23 -12
  117. package/src/components/delivery-notes/create/create-delivery-note-form.tsx +33 -20
  118. package/src/components/delivery-notes/list/list-table.tsx +22 -13
  119. package/src/components/delivery-notes/list/locales/de.ts +2 -0
  120. package/src/components/delivery-notes/list/locales/en.ts +1 -0
  121. package/src/components/delivery-notes/list/locales/es.ts +1 -0
  122. package/src/components/delivery-notes/list/locales/fr.ts +1 -0
  123. package/src/components/delivery-notes/list/locales/hr.ts +1 -0
  124. package/src/components/delivery-notes/list/locales/it.ts +1 -0
  125. package/src/components/delivery-notes/list/locales/nl.ts +1 -0
  126. package/src/components/delivery-notes/list/locales/pl.ts +1 -0
  127. package/src/components/delivery-notes/list/locales/pt.ts +1 -0
  128. package/src/components/delivery-notes/list/locales/sl.ts +1 -0
  129. package/src/components/delivery-notes/list/use-delivery-note-download.ts +1 -12
  130. package/src/components/documents/create/document-add-item-form.tsx +28 -16
  131. package/src/components/documents/create/document-add-item-tax-rate-field.tsx +12 -2
  132. package/src/components/documents/create/document-items-section.tsx +70 -39
  133. package/src/components/documents/create/document-recipient-section.tsx +10 -1
  134. package/src/components/documents/create/live-preview.tsx +113 -15
  135. package/src/components/documents/create/prepare-document-submission.ts +35 -16
  136. package/src/components/documents/create/use-document-customer-form.ts +14 -3
  137. package/src/components/documents/documents.hooks.ts +7 -2
  138. package/src/components/documents/shared/document-preview-display.tsx +136 -67
  139. package/src/components/documents/shared/scaled-document-preview.tsx +45 -5
  140. package/src/components/documents/view/document-actions-bar.tsx +284 -182
  141. package/src/components/documents/view/document-activities-list.tsx +3 -0
  142. package/src/components/documents/view/document-payments-list.tsx +3 -0
  143. package/src/components/documents/view/locales/de.ts +8 -0
  144. package/src/components/documents/view/locales/es.ts +8 -0
  145. package/src/components/documents/view/locales/fr.ts +8 -0
  146. package/src/components/documents/view/locales/hr.ts +8 -0
  147. package/src/components/documents/view/locales/it.ts +8 -0
  148. package/src/components/documents/view/locales/nl.ts +8 -0
  149. package/src/components/documents/view/locales/pl.ts +8 -0
  150. package/src/components/documents/view/locales/pt.ts +8 -0
  151. package/src/components/documents/view/locales/sl.ts +8 -0
  152. package/src/components/documents/view/use-document-download.ts +14 -25
  153. package/src/components/entities/create-entity-form.tsx +101 -16
  154. package/src/components/entities/entity-settings-form/entity-settings-form.tsx +10 -10
  155. package/src/components/entities/entity-settings-form/locales/de.ts +10 -0
  156. package/src/components/entities/entity-settings-form/locales/es.ts +10 -0
  157. package/src/components/entities/entity-settings-form/locales/fr.ts +10 -0
  158. package/src/components/entities/entity-settings-form/locales/hr.ts +10 -0
  159. package/src/components/entities/entity-settings-form/locales/it.ts +10 -0
  160. package/src/components/entities/entity-settings-form/locales/nl.ts +10 -0
  161. package/src/components/entities/entity-settings-form/locales/pl.ts +10 -0
  162. package/src/components/entities/entity-settings-form/locales/pt.ts +10 -0
  163. package/src/components/entities/entity-settings-form/locales/sl.ts +10 -0
  164. package/src/components/entities/fina-settings-form/fina-operator-required-dialog.tsx +3 -3
  165. package/src/components/entities/fina-settings-form/fina-settings-form.tsx +78 -124
  166. package/src/components/entities/fina-settings-form/sections/certificate-settings-section.tsx +8 -1
  167. package/src/components/entities/fina-settings-form/sections/premises-management-section.tsx +14 -2
  168. package/src/components/entities/fina-settings-form/sections/register-premise-dialog.tsx +7 -2
  169. package/src/components/entities/furs-settings-form/furs-settings-form.tsx +56 -130
  170. package/src/components/entities/furs-settings-form/sections/certificate-settings-section.tsx +8 -1
  171. package/src/components/entities/furs-settings-form/sections/enable-fiscalization-section.tsx +1 -0
  172. package/src/components/entities/furs-settings-form/sections/general-settings-section.tsx +15 -2
  173. package/src/components/entities/furs-settings-form/sections/premises-management-section.tsx +20 -3
  174. package/src/components/entities/furs-settings-form/sections/register-premise-dialog.tsx +38 -12
  175. package/src/components/entities/settings/defaults-settings-form.tsx +6 -6
  176. package/src/components/entities/settings/eslog-settings-form.tsx +13 -1
  177. package/src/components/entities/settings/pdf-template-selector/demo-invoice-data.ts +3 -22
  178. package/src/components/entities/shared/fiscalization-step-flow.ts +77 -0
  179. package/src/components/entities/shared/fiscalization-step-tabs.tsx +71 -0
  180. package/src/components/estimates/create/create-estimate-form.tsx +34 -21
  181. package/src/components/estimates/list/list-table.tsx +23 -14
  182. package/src/components/estimates/list/locales/de.ts +2 -0
  183. package/src/components/estimates/list/locales/en.ts +1 -0
  184. package/src/components/estimates/list/locales/es.ts +1 -0
  185. package/src/components/estimates/list/locales/fr.ts +1 -0
  186. package/src/components/estimates/list/locales/hr.ts +1 -0
  187. package/src/components/estimates/list/locales/it.ts +1 -0
  188. package/src/components/estimates/list/locales/nl.ts +1 -0
  189. package/src/components/estimates/list/locales/pl.ts +1 -0
  190. package/src/components/estimates/list/locales/pt.ts +1 -0
  191. package/src/components/estimates/list/locales/sl.ts +1 -0
  192. package/src/components/estimates/list/use-estimate-download.ts +1 -12
  193. package/src/components/export/document-export-form.tsx +33 -7
  194. package/src/components/export/sales-per-item-export-form.tsx +23 -7
  195. package/src/components/invoices/create/create-invoice-form.tsx +295 -329
  196. package/src/components/invoices/create/prepare-invoice-submission.ts +0 -8
  197. package/src/components/invoices/list/list-table.tsx +7 -4
  198. package/src/components/invoices/list/use-invoice-download.ts +1 -11
  199. package/src/components/invoices/send-email-dialog/locales/de.ts +20 -0
  200. package/src/components/invoices/send-email-dialog/locales/es.ts +20 -0
  201. package/src/components/invoices/send-email-dialog/locales/fr.ts +20 -0
  202. package/src/components/invoices/send-email-dialog/locales/hr.ts +20 -0
  203. package/src/components/invoices/send-email-dialog/locales/it.ts +20 -0
  204. package/src/components/invoices/send-email-dialog/locales/nl.ts +20 -0
  205. package/src/components/invoices/send-email-dialog/locales/pl.ts +20 -0
  206. package/src/components/invoices/send-email-dialog/locales/pt.ts +20 -0
  207. package/src/components/invoices/send-email-dialog/locales/sl.ts +20 -0
  208. package/src/components/invoices/send-email-dialog/send-email-dialog.tsx +77 -8
  209. package/src/components/invoices/view/eslog-info-display.tsx +17 -1
  210. package/src/components/invoices/view/fiscalization-status-card.tsx +7 -3
  211. package/src/components/items/item-combobox.tsx +26 -6
  212. package/src/components/items/item-list-table/item-list-table.tsx +5 -2
  213. package/src/components/payments/create-payment-form/index.ts +1 -0
  214. package/src/components/payments/list/list-table.tsx +14 -4
  215. package/src/components/recurring-invoices/list/list-table.tsx +7 -4
  216. package/src/components/request-logs/locales.ts +412 -0
  217. package/src/components/request-logs/request-log-detail.tsx +37 -21
  218. package/src/components/request-logs/request-log-list-table.tsx +57 -11
  219. package/src/components/table/data-table.tsx +5 -2
  220. package/src/components/table/date-cell.tsx +3 -1
  221. package/src/components/table/filter-bar.tsx +14 -2
  222. package/src/components/table/hooks/use-table-query.ts +1 -1
  223. package/src/components/table/locales.ts +1116 -0
  224. package/src/components/table/search-input.tsx +12 -3
  225. package/src/components/table/selection-toolbar.tsx +23 -6
  226. package/src/components/table/table-empty-state.tsx +43 -3
  227. package/src/components/table/table-no-results.tsx +3 -3
  228. package/src/components/table/table-pagination.tsx +4 -3
  229. package/src/components/table/types.ts +1 -0
  230. package/src/components/tax-reports/index.ts +1 -0
  231. package/src/components/tax-reports/kir-export-form.tsx +46 -8
  232. package/src/components/tax-reports/slovenia-tax-profile-step.tsx +191 -0
  233. package/src/components/tax-reports/slovenia-yearly-export-form.tsx +509 -0
  234. package/src/components/tax-reports/slovenia-yearly-review-step.tsx +253 -0
  235. package/src/components/tax-reports/slovenia-yearly-summary.tsx +19 -0
  236. package/src/components/taxes/tax-list-table/tax-list-table.tsx +3 -2
  237. package/src/components/ui/sidebar.tsx +3 -2
  238. package/src/components/ui/sticky-form-footer.tsx +7 -1
  239. package/src/components/webhook-logs/index.ts +6 -0
  240. package/src/components/webhook-logs/locales.ts +392 -0
  241. package/src/components/webhook-logs/webhook-delivery-detail.tsx +255 -0
  242. package/src/components/webhook-logs/webhook-delivery-list-table.tsx +278 -0
  243. package/src/components/wl-subscription/index.ts +1 -0
  244. package/src/components/wl-subscription/locked-feature.tsx +1 -0
  245. package/src/components/wl-subscription/paywall.tsx +193 -0
  246. package/src/components/wl-subscription/upgrade-modal.tsx +93 -29
  247. package/src/generate-schemas.ts +12 -7
  248. package/src/generated/schemas/customer.ts +2 -0
  249. package/src/generated/schemas/entity.ts +134 -0
  250. package/src/generated/schemas/exportsloveniayearlynormiranireport_body.ts +27 -0
  251. package/src/generated/schemas/index.ts +2 -0
  252. package/src/generated/schemas/me.ts +20 -1
  253. package/src/generated/schemas/renderadvanceinvoicepreview_body.ts +40 -34
  254. package/src/generated/schemas/rendercreditnotepreview_body.ts +42 -36
  255. package/src/generated/schemas/renderdeliverynotepreview_body.ts +23 -13
  256. package/src/generated/schemas/renderestimatepreview_body.ts +23 -13
  257. package/src/generated/schemas/renderinvoicepreview_body.ts +40 -34
  258. package/src/generated/schemas/sendemail_body.ts +44 -0
  259. package/src/generated/schemas/sloveniataxprofile.ts +42 -0
  260. package/src/generated/schemas/startpdfexport_body.ts +91 -1
  261. package/src/generated/schemas/webhook.ts +10 -0
  262. package/src/hooks/use-duplicate-document.ts +51 -13
  263. package/src/hooks/use-eslog-validation.ts +59 -0
  264. package/src/hooks/use-premise-selection.ts +186 -0
  265. package/src/lib/browser-cookies.ts +4 -4
  266. package/src/lib/date-fns-locale.ts +48 -0
  267. package/src/lib/fiscalization-options.ts +81 -0
  268. package/src/lib/locale.ts +38 -0
  269. package/src/lib/template-variables.tsx +1 -1
  270. package/src/lib/translation.ts +14 -3
  271. package/src/providers/entities-context.tsx +1 -0
  272. package/src/providers/entities-provider.tsx +102 -3
  273. package/src/providers/form-footer-context.tsx +37 -4
  274. package/src/providers/sdk-provider.tsx +7 -2
  275. package/src/providers/white-label-provider.tsx +4 -1
  276. package/src/providers/wl-subscription-provider.tsx +90 -3
package/README.md CHANGED
@@ -1,19 +1,25 @@
1
- # @spaceinvoices/react-ui - Space Invoices Component Library
1
+ # @spaceinvoices/react-ui - Space Invoices React UI Kit
2
2
 
3
- > **Pre-built React components for the Space Invoices API**
3
+ > **Copy-paste React UI for the Space Invoices API**
4
4
 
5
- 70+ components for building invoicing applications with the Space Invoices API. Includes forms, tables, dashboard charts, and more - all designed to be copied into your project for full customization.
5
+ 50+ installable components for building invoicing applications with the Space Invoices API. Includes forms, tables, dashboard charts, and more - all designed to be copied into your project for full customization.
6
6
 
7
7
  ## Philosophy
8
8
 
9
- These components are **designed to be copied**, not installed as a package:
9
+ This package is a **CLI installer and source registry**, not a conventional runtime component library. The intended flow is:
10
+
11
+ 1. initialize your app with the CLI
12
+ 2. add only the components you need
13
+ 3. own the copied source inside your app
14
+
15
+ These components are **designed to be copied**, not imported from `node_modules` at runtime:
10
16
 
11
17
  - **Full ownership** - Modify freely without breaking changes
12
18
  - **Complete customization** - Change behavior and structure
13
19
  - **No version conflicts** - You control when to update
14
20
  - **Zero lock-in** - Components work standalone in your codebase
15
21
 
16
- **Note**: Components are built on **shadcn/ui primitives**. Bring your own shadcn/ui components or use any alternative. The `components/ui/` folder is included as reference only.
22
+ **Note**: Components are built on **shadcn/ui primitives**. Bring your own shadcn/ui components or use any alternative. The CLI can copy compatible `components/ui/` primitives when needed, but teams using their own design system can swap those imports after install.
17
23
 
18
24
  ## Quick Start (CLI - Recommended)
19
25
 
@@ -36,6 +42,7 @@ The CLI will:
36
42
  - Create `spaceinvoices.json` config with your preferred paths
37
43
  - Install required dependencies (`@spaceinvoices/js-sdk`, `@tanstack/react-query`)
38
44
  - Copy essential files (SDK provider, utilities)
45
+ - Copy generated Zod schemas when installed components depend on them
39
46
  - Transform import paths to match your project structure
40
47
  - Install npm dependencies for each component
41
48
 
@@ -101,7 +108,10 @@ cp -r react-ui/src/components/customers/ your-project/src/components/space-invoi
101
108
  cp -r react-ui/src/components/table/ your-project/src/components/space-invoices/
102
109
  # ... add more as needed
103
110
 
104
- # DON'T copy components/ui/ - use shadcn/ui instead
111
+ # Include generated schemas if you use form components
112
+ cp -r react-ui/src/generated/ your-project/src/generated/
113
+
114
+ # Optional: skip components/ui/ if you already use shadcn/ui or your own primitives
105
115
  ```
106
116
 
107
117
  ### 4. Update Import Paths
@@ -158,9 +168,13 @@ src/components/
158
168
  └── table/ # Generic data table infrastructure
159
169
  ```
160
170
 
161
- ### UI Primitives (Reference Only)
171
+ ### UI Primitives (Optional)
172
+
173
+ The `components/ui/` folder contains shadcn/ui-style components. You can:
162
174
 
163
- The `components/ui/` folder contains shadcn/ui-style components. **Don't copy these** - use shadcn/ui directly:
175
+ - let the CLI install compatible primitives for you
176
+ - use shadcn/ui directly
177
+ - swap to your own component library after copying the feature components
164
178
 
165
179
  ```bash
166
180
  npx shadcn@latest add button input form table dialog select
@@ -175,6 +189,8 @@ src/
175
189
  ├── providers/
176
190
  │ ├── sdk-provider.tsx # SDK context (required)
177
191
  │ └── entities-provider.tsx # Active entity context (required)
192
+ ├── generated/
193
+ │ └── schemas/ # Generated Zod schemas used by form components
178
194
  ├── hooks/
179
195
  │ └── *.ts # Shared hooks
180
196
  └── lib/
package/cli/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import { Command } from "commander";
5
5
 
6
6
  // cli/src/commands/init.ts
7
7
  import fs4 from "fs";
8
- import path4 from "path";
8
+ import path5 from "path";
9
9
  import prompts from "prompts";
10
10
 
11
11
  // cli/src/utils/config.ts
@@ -19,7 +19,8 @@ var DEFAULT_CONFIG = {
19
19
  ui: "@/components/ui",
20
20
  lib: "@/lib",
21
21
  hooks: "@/hooks",
22
- providers: "@/providers"
22
+ providers: "@/providers",
23
+ generated: "@/generated"
23
24
  }
24
25
  };
25
26
  function getConfigPath(cwd = process.cwd()) {
@@ -44,6 +45,20 @@ function writeConfig(config, cwd = process.cwd()) {
44
45
  const configPath = getConfigPath(cwd);
45
46
  fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
46
47
  }
48
+ function resolveAliasPath(alias, cwd = process.cwd()) {
49
+ if (path.isAbsolute(alias)) {
50
+ return alias;
51
+ }
52
+ if (alias.startsWith("@/")) {
53
+ const srcPath = path.join(cwd, "src", alias.slice(2));
54
+ const rootPath = path.join(cwd, alias.slice(2));
55
+ if (fs.existsSync(path.join(cwd, "src"))) {
56
+ return srcPath;
57
+ }
58
+ return rootPath;
59
+ }
60
+ return path.join(cwd, alias);
61
+ }
47
62
 
48
63
  // cli/src/utils/installer.ts
49
64
  import { spawn } from "child_process";
@@ -268,6 +283,7 @@ function getComponentsByCategory(registry) {
268
283
  }
269
284
 
270
285
  // cli/src/utils/transformer.ts
286
+ import path4 from "path";
271
287
  function transformImports(source, config) {
272
288
  let result = source;
273
289
  const replacements = [
@@ -280,7 +296,9 @@ function transformImports(source, config) {
280
296
  // Lib utilities: @/ui/lib/ -> config.aliases.lib/
281
297
  [/@\/ui\/lib\//g, `${config.aliases.lib}/`],
282
298
  // Hooks: @/ui/hooks/ -> config.aliases.hooks/
283
- [/@\/ui\/hooks\//g, `${config.aliases.hooks}/`]
299
+ [/@\/ui\/hooks\//g, `${config.aliases.hooks}/`],
300
+ // Generated helpers: @/ui/generated/ -> config.aliases.generated/
301
+ [/@\/ui\/generated\//g, `${config.aliases.generated}/`]
284
302
  ];
285
303
  for (const [pattern, replacement] of replacements) {
286
304
  result = result.replace(pattern, replacement);
@@ -318,6 +336,12 @@ function getDestinationPath(sourcePath, config) {
318
336
  category: "hooks"
319
337
  };
320
338
  }
339
+ if (sourcePath.startsWith("generated/")) {
340
+ return {
341
+ destPath: sourcePath.replace("generated/", ""),
342
+ category: "generated"
343
+ };
344
+ }
321
345
  return {
322
346
  destPath: sourcePath,
323
347
  category: "components"
@@ -326,13 +350,7 @@ function getDestinationPath(sourcePath, config) {
326
350
  function getFullDestinationPath(sourcePath, config, basePath) {
327
351
  const { destPath, category } = getDestinationPath(sourcePath, config);
328
352
  const alias = config.aliases[category];
329
- let relativePath;
330
- if (alias.startsWith("@/")) {
331
- relativePath = alias.slice(2);
332
- } else {
333
- relativePath = alias;
334
- }
335
- return `${basePath}/src/${relativePath}/${destPath}`;
353
+ return path4.join(resolveAliasPath(alias, basePath), destPath);
336
354
  }
337
355
 
338
356
  // cli/src/commands/init.ts
@@ -397,6 +415,12 @@ async function init(options = {}) {
397
415
  name: "providers",
398
416
  message: "Where should providers be installed?",
399
417
  initial: DEFAULT_CONFIG.aliases.providers
418
+ },
419
+ {
420
+ type: "text",
421
+ name: "generated",
422
+ message: "Where should generated helpers (schemas) be installed?",
423
+ initial: DEFAULT_CONFIG.aliases.generated
400
424
  }
401
425
  ]);
402
426
  if (!answers.components) {
@@ -410,7 +434,8 @@ async function init(options = {}) {
410
434
  ui: answers.ui,
411
435
  lib: answers.lib,
412
436
  hooks: answers.hooks,
413
- providers: answers.providers
437
+ providers: answers.providers,
438
+ generated: answers.generated
414
439
  }
415
440
  };
416
441
  }
@@ -431,16 +456,11 @@ async function init(options = {}) {
431
456
  config.aliases.ui,
432
457
  config.aliases.lib,
433
458
  config.aliases.hooks,
434
- config.aliases.providers
459
+ config.aliases.providers,
460
+ config.aliases.generated
435
461
  ];
436
462
  for (const alias of directories) {
437
- let dirPath;
438
- if (alias.startsWith("@/")) {
439
- dirPath = path4.join(cwd, "src", alias.slice(2));
440
- } else {
441
- dirPath = path4.join(cwd, alias);
442
- }
443
- fs4.mkdirSync(dirPath, { recursive: true });
463
+ fs4.mkdirSync(resolveAliasPath(alias, cwd), { recursive: true });
444
464
  }
445
465
  dirSpinner.succeed("Created directories");
446
466
  } catch (error) {
@@ -454,7 +474,7 @@ async function init(options = {}) {
454
474
  const content = await fetchFile(filePath);
455
475
  const transformed = transformImports(content, config);
456
476
  const destPath = getFullDestinationPath(filePath, config, cwd);
457
- fs4.mkdirSync(path4.dirname(destPath), { recursive: true });
477
+ fs4.mkdirSync(path5.dirname(destPath), { recursive: true });
458
478
  fs4.writeFileSync(destPath, transformed);
459
479
  }
460
480
  filesSpinner.succeed(`Copied ${ESSENTIAL_FILES.length} essential files`);
@@ -488,7 +508,7 @@ async function init(options = {}) {
488
508
 
489
509
  // cli/src/commands/add.ts
490
510
  import fs5 from "fs";
491
- import path5 from "path";
511
+ import path6 from "path";
492
512
  import prompts2 from "prompts";
493
513
 
494
514
  // cli/src/utils/resolver.ts
@@ -614,6 +634,31 @@ function getInstallSummary(resolved) {
614
634
  };
615
635
  }
616
636
 
637
+ // cli/src/utils/file-dependencies.ts
638
+ var GENERATED_IMPORT_PATTERN = /from\s+["']@\/ui\/generated\/schemas(?:\/([^"']+))?["']/g;
639
+ var GENERATED_EXPORT_PATTERN = /from\s+['"]\.\/([^'"]+)['"]/g;
640
+ function normalizeGeneratedPath(subpath) {
641
+ return `generated/schemas/${subpath.replace(/\.ts$/, "")}.ts`;
642
+ }
643
+ function getRegistryManagedDependencies(filePath, source) {
644
+ const dependencies = /* @__PURE__ */ new Set();
645
+ if (filePath === "generated/schemas/index.ts") {
646
+ for (const match of source.matchAll(GENERATED_EXPORT_PATTERN)) {
647
+ dependencies.add(normalizeGeneratedPath(match[1]));
648
+ }
649
+ return [...dependencies];
650
+ }
651
+ for (const match of source.matchAll(GENERATED_IMPORT_PATTERN)) {
652
+ const subpath = match[1];
653
+ if (subpath) {
654
+ dependencies.add(normalizeGeneratedPath(subpath));
655
+ continue;
656
+ }
657
+ dependencies.add("generated/schemas/index.ts");
658
+ }
659
+ return [...dependencies];
660
+ }
661
+
617
662
  // cli/src/commands/add.ts
618
663
  async function add(componentNames, options = {}) {
619
664
  const cwd = options.cwd ?? process.cwd();
@@ -674,6 +719,7 @@ async function add(componentNames, options = {}) {
674
719
  resolveSpinner.fail("Failed to resolve dependencies");
675
720
  throw error;
676
721
  }
722
+ const fileContents = await collectRegistryFiles(resolved.allFiles);
677
723
  const summary = getInstallSummary(resolved);
678
724
  logger.break();
679
725
  logger.info("The following will be installed:");
@@ -692,7 +738,7 @@ async function add(componentNames, options = {}) {
692
738
  ` ${highlight("npm packages:")} ${summary.npmPackages.join(", ")}`
693
739
  );
694
740
  }
695
- logger.log(` ${highlight("Files:")} ${summary.fileCount} files`);
741
+ logger.log(` ${highlight("Files:")} ${fileContents.size} files`);
696
742
  if (!options.yes) {
697
743
  logger.break();
698
744
  const { proceed } = await prompts2({
@@ -712,12 +758,11 @@ async function add(componentNames, options = {}) {
712
758
  const existingFiles = [];
713
759
  const filesToWrite = [];
714
760
  try {
715
- for (const filePath of resolved.allFiles) {
761
+ for (const [filePath, content] of fileContents) {
716
762
  const destPath = getFullDestinationPath(filePath, config, cwd);
717
763
  if (fs5.existsSync(destPath)) {
718
764
  existingFiles.push(destPath);
719
765
  }
720
- const content = await fetchFile(filePath);
721
766
  const transformed = transformImports(content, config);
722
767
  filesToWrite.push({ path: destPath, content: transformed });
723
768
  }
@@ -753,7 +798,7 @@ async function add(componentNames, options = {}) {
753
798
  }
754
799
  try {
755
800
  for (const { path: filePath, content } of filesToWrite) {
756
- fs5.mkdirSync(path5.dirname(filePath), { recursive: true });
801
+ fs5.mkdirSync(path6.dirname(filePath), { recursive: true });
757
802
  fs5.writeFileSync(filePath, content);
758
803
  }
759
804
  filesSpinner.succeed(`Installed ${filesToWrite.length} files`);
@@ -780,6 +825,24 @@ async function add(componentNames, options = {}) {
780
825
  logger.success("Components installed successfully!");
781
826
  logger.break();
782
827
  }
828
+ async function collectRegistryFiles(initialFiles) {
829
+ const pending = [...initialFiles];
830
+ const contents = /* @__PURE__ */ new Map();
831
+ while (pending.length > 0) {
832
+ const filePath = pending.shift();
833
+ if (!filePath || contents.has(filePath)) {
834
+ continue;
835
+ }
836
+ const content = await fetchFile(filePath);
837
+ contents.set(filePath, content);
838
+ for (const dependency of getRegistryManagedDependencies(filePath, content)) {
839
+ if (!contents.has(dependency)) {
840
+ pending.push(dependency);
841
+ }
842
+ }
843
+ }
844
+ return contents;
845
+ }
783
846
  async function selectComponents(registry) {
784
847
  const byCategory = getComponentsByCategory(registry);
785
848
  const choices = [];
@@ -870,7 +933,7 @@ async function list(options = {}) {
870
933
 
871
934
  // cli/src/index.ts
872
935
  var program = new Command();
873
- program.name("spaceinvoices-ui").description("CLI for adding Space Invoices React UI components to your project").version("0.4.8");
936
+ program.name("spaceinvoices-ui").description("CLI for adding Space Invoices React UI components to your project").version("0.4.11");
874
937
  program.option("--local <path>", "Use local registry from specified path (for development)");
875
938
  program.command("init").description("Initialize Space Invoices UI in your project").option("-y, --yes", "Skip prompts and use defaults").option("-f, --force", "Overwrite existing configuration").option("--cwd <path>", "Working directory (defaults to current directory)").action(async (options) => {
876
939
  const globalOpts = program.opts();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@spaceinvoices/react-ui",
3
3
  "type": "module",
4
- "version": "0.4.8",
4
+ "version": "0.4.11",
5
5
  "private": false,
6
6
  "license": "MIT",
7
7
  "description": "Space Invoices UI components - copy-paste distribution with CLI support",
@@ -78,6 +78,9 @@
78
78
  "zod": "^4.1.12"
79
79
  },
80
80
  "devDependencies": {
81
+ "@happy-dom/global-registrator": "^20.8.3",
82
+ "@testing-library/jest-dom": "^6.9.1",
83
+ "@testing-library/react": "^16.3.2",
81
84
  "@types/bun": "latest",
82
85
  "@types/prompts": "^2.4.9",
83
86
  "@types/react": "^19.1.0",
@@ -12,7 +12,7 @@
12
12
  "aliases": {
13
13
  "type": "object",
14
14
  "description": "Path aliases for component installation",
15
- "required": ["components", "ui", "lib", "hooks", "providers"],
15
+ "required": ["components", "ui", "lib", "hooks", "providers", "generated"],
16
16
  "properties": {
17
17
  "components": {
18
18
  "type": "string",
@@ -38,6 +38,11 @@
38
38
  "type": "string",
39
39
  "description": "Path alias for React providers (e.g., @/providers)",
40
40
  "default": "@/providers"
41
+ },
42
+ "generated": {
43
+ "type": "string",
44
+ "description": "Path alias for generated helpers such as Zod schemas (e.g., @/generated)",
45
+ "default": "@/generated"
41
46
  }
42
47
  },
43
48
  "additionalProperties": false
@@ -14,6 +14,9 @@ type AutocompleteProps = {
14
14
  value?: string;
15
15
  onValueChange?: (value: string) => void;
16
16
  onBlur?: (event: React.FocusEvent<HTMLInputElement>) => void; // Added onBlur prop
17
+ onCommitUnselectedInput?: (value: string) => void;
18
+ commitUnselectedOnBlur?: boolean;
19
+ committedDisplayValue?: string;
17
20
  placeholder?: string;
18
21
  emptyText?: string;
19
22
  className?: string;
@@ -22,6 +25,8 @@ type AutocompleteProps = {
22
25
  onSearch?: (value: string) => void;
23
26
  searchValue?: string;
24
27
  displayValue?: string;
28
+ inputTestId?: string;
29
+ inputRef?: React.Ref<HTMLInputElement>;
25
30
  };
26
31
 
27
32
  export function Autocomplete({
@@ -29,6 +34,9 @@ export function Autocomplete({
29
34
  value,
30
35
  onValueChange,
31
36
  onBlur: onBlurProp, // Destructure the new onBlur prop
37
+ onCommitUnselectedInput,
38
+ commitUnselectedOnBlur = false,
39
+ committedDisplayValue,
32
40
  placeholder = "Type to search...",
33
41
  emptyText = "No results found.",
34
42
  className,
@@ -37,10 +45,16 @@ export function Autocomplete({
37
45
  onSearch,
38
46
  searchValue: externalSearchValue,
39
47
  displayValue,
48
+ inputTestId,
49
+ inputRef: externalInputRef,
40
50
  }: AutocompleteProps) {
41
51
  const [open, setOpen] = React.useState(false);
42
52
  const [internalSearchValue, setInternalSearchValue] = React.useState("");
43
53
  const inputRef = React.useRef<HTMLInputElement>(null);
54
+ const suppressAutoOpenRef = React.useRef(false);
55
+ const blurCloseRef = React.useRef(false);
56
+ const focusOpenRef = React.useRef(false);
57
+ const selectedValueRef = React.useRef<string | null>(null);
44
58
 
45
59
  const searchValue = externalSearchValue ?? internalSearchValue;
46
60
  // Show displayValue when not typing, otherwise show what user is typing
@@ -48,6 +62,7 @@ export function Autocomplete({
48
62
 
49
63
  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
50
64
  const value = e.target.value;
65
+ suppressAutoOpenRef.current = false;
51
66
  setInternalSearchValue(value);
52
67
  onSearch?.(value);
53
68
 
@@ -59,22 +74,38 @@ export function Autocomplete({
59
74
 
60
75
  // Close popover when options become empty, open when they appear
61
76
  React.useEffect(() => {
77
+ if (!displayValue && !value) {
78
+ suppressAutoOpenRef.current = false;
79
+ }
80
+
62
81
  if (options.length === 0) {
63
82
  setOpen(false);
83
+ } else if (suppressAutoOpenRef.current) {
84
+ return;
64
85
  } else if (document.activeElement === inputRef.current && searchValue) {
65
86
  setOpen(true);
66
87
  }
67
- }, [options.length, searchValue]);
88
+ }, [displayValue, options.length, searchValue, value]);
68
89
 
69
90
  const handleSelect = (selectedValue: string) => {
91
+ selectedValueRef.current = selectedValue;
92
+ suppressAutoOpenRef.current = true;
70
93
  onValueChange?.(selectedValue);
71
94
  setOpen(false);
72
95
  };
73
96
 
74
97
  const handleInputFocus = () => {
98
+ if (!displayValue) {
99
+ suppressAutoOpenRef.current = false;
100
+ }
101
+ if (suppressAutoOpenRef.current) return;
75
102
  // Only open popover on focus if there's no displayValue (no customer selected)
76
103
  if (!displayValue && options.length > 0) {
104
+ focusOpenRef.current = true;
77
105
  setOpen(true);
106
+ requestAnimationFrame(() => {
107
+ focusOpenRef.current = false;
108
+ });
78
109
  }
79
110
  };
80
111
 
@@ -84,8 +115,30 @@ export function Autocomplete({
84
115
  if (relatedTarget?.closest('[role="dialog"]')) {
85
116
  return;
86
117
  }
87
- // Close after a short delay to allow click events to fire
88
- setTimeout(() => setOpen(false), 200);
118
+
119
+ const typedValue = searchValue?.trim();
120
+ const shouldCommitTypedValue =
121
+ !!typedValue &&
122
+ typedValue !== displayValue &&
123
+ selectedValueRef.current == null &&
124
+ commitUnselectedOnBlur &&
125
+ !!onCommitUnselectedInput;
126
+
127
+ if (shouldCommitTypedValue) {
128
+ onCommitUnselectedInput(typedValue);
129
+ } else if (selectedValueRef.current == null) {
130
+ const restoredValue = committedDisplayValue ?? displayValue ?? "";
131
+ setInternalSearchValue("");
132
+ onSearch?.(restoredValue);
133
+ }
134
+
135
+ blurCloseRef.current = true;
136
+ suppressAutoOpenRef.current = false;
137
+ setOpen(false);
138
+ requestAnimationFrame(() => {
139
+ blurCloseRef.current = false;
140
+ selectedValueRef.current = null;
141
+ });
89
142
 
90
143
  onBlurProp?.(e); // Call the passed onBlur prop after internal logic
91
144
  };
@@ -98,8 +151,10 @@ export function Autocomplete({
98
151
 
99
152
  // Handle popover open/close - prevent closing when input is focused
100
153
  const handleOpenChange = (newOpen: boolean) => {
101
- if (!newOpen && document.activeElement === inputRef.current) {
102
- // Don't close if the input still has focus (user clicked on input)
154
+ if (!newOpen && focusOpenRef.current) {
155
+ return;
156
+ }
157
+ if (!newOpen && document.activeElement === inputRef.current && !blurCloseRef.current) {
103
158
  return;
104
159
  }
105
160
  setOpen(newOpen);
@@ -108,7 +163,14 @@ export function Autocomplete({
108
163
  return (
109
164
  <Popover.Root open={open} onOpenChange={handleOpenChange}>
110
165
  <Input
111
- ref={inputRef}
166
+ ref={(node) => {
167
+ inputRef.current = node;
168
+ if (typeof externalInputRef === "function") {
169
+ externalInputRef(node);
170
+ } else if (externalInputRef) {
171
+ (externalInputRef as React.MutableRefObject<HTMLInputElement | null>).current = node;
172
+ }
173
+ }}
112
174
  value={inputValue}
113
175
  onChange={handleInputChange}
114
176
  onFocus={handleInputFocus}
@@ -118,6 +180,7 @@ export function Autocomplete({
118
180
  className={className}
119
181
  disabled={disabled}
120
182
  autoComplete="off"
183
+ data-testid={inputTestId}
121
184
  />
122
185
  <Popover.Portal>
123
186
  <Popover.Positioner anchor={inputRef} align="start" sideOffset={4} className="isolate z-50">