@open-mercato/ui 0.4.2-canary-c02407ff85

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 (319) hide show
  1. package/build.mjs +62 -0
  2. package/dist/backend/AppShell.js +902 -0
  3. package/dist/backend/AppShell.js.map +7 -0
  4. package/dist/backend/ConfirmDialog.js +17 -0
  5. package/dist/backend/ConfirmDialog.js.map +7 -0
  6. package/dist/backend/ContextHelp.js +31 -0
  7. package/dist/backend/ContextHelp.js.map +7 -0
  8. package/dist/backend/CrudForm.js +2028 -0
  9. package/dist/backend/CrudForm.js.map +7 -0
  10. package/dist/backend/DataTable.js +1363 -0
  11. package/dist/backend/DataTable.js.map +7 -0
  12. package/dist/backend/EmptyState.js +52 -0
  13. package/dist/backend/EmptyState.js.map +7 -0
  14. package/dist/backend/FilterBar.js +140 -0
  15. package/dist/backend/FilterBar.js.map +7 -0
  16. package/dist/backend/FilterOverlay.js +279 -0
  17. package/dist/backend/FilterOverlay.js.map +7 -0
  18. package/dist/backend/FlashMessages.js +66 -0
  19. package/dist/backend/FlashMessages.js.map +7 -0
  20. package/dist/backend/JsonBuilder.js +322 -0
  21. package/dist/backend/JsonBuilder.js.map +7 -0
  22. package/dist/backend/JsonDisplay.js +203 -0
  23. package/dist/backend/JsonDisplay.js.map +7 -0
  24. package/dist/backend/Page.js +27 -0
  25. package/dist/backend/Page.js.map +7 -0
  26. package/dist/backend/PerspectiveSidebar.js +282 -0
  27. package/dist/backend/PerspectiveSidebar.js.map +7 -0
  28. package/dist/backend/RowActions.js +148 -0
  29. package/dist/backend/RowActions.js.map +7 -0
  30. package/dist/backend/TruncatedCell.js +92 -0
  31. package/dist/backend/TruncatedCell.js.map +7 -0
  32. package/dist/backend/UserMenu.js +107 -0
  33. package/dist/backend/UserMenu.js.map +7 -0
  34. package/dist/backend/ValueIcons.js +34 -0
  35. package/dist/backend/ValueIcons.js.map +7 -0
  36. package/dist/backend/custom-fields/FieldDefinitionsEditor.js +1264 -0
  37. package/dist/backend/custom-fields/FieldDefinitionsEditor.js.map +7 -0
  38. package/dist/backend/custom-fields/FieldDefinitionsManager.js +332 -0
  39. package/dist/backend/custom-fields/FieldDefinitionsManager.js.map +7 -0
  40. package/dist/backend/dashboard/DashboardScreen.js +578 -0
  41. package/dist/backend/dashboard/DashboardScreen.js.map +7 -0
  42. package/dist/backend/dashboard/index.js +5 -0
  43. package/dist/backend/dashboard/index.js.map +7 -0
  44. package/dist/backend/dashboard/widgetRegistry.js +55 -0
  45. package/dist/backend/dashboard/widgetRegistry.js.map +7 -0
  46. package/dist/backend/detail/ActivitiesSection.js +962 -0
  47. package/dist/backend/detail/ActivitiesSection.js.map +7 -0
  48. package/dist/backend/detail/AddressEditor.js +413 -0
  49. package/dist/backend/detail/AddressEditor.js.map +7 -0
  50. package/dist/backend/detail/AddressTiles.js +437 -0
  51. package/dist/backend/detail/AddressTiles.js.map +7 -0
  52. package/dist/backend/detail/AddressesSection.js +264 -0
  53. package/dist/backend/detail/AddressesSection.js.map +7 -0
  54. package/dist/backend/detail/AttachmentDeleteDialog.js +41 -0
  55. package/dist/backend/detail/AttachmentDeleteDialog.js.map +7 -0
  56. package/dist/backend/detail/AttachmentMetadataDialog.js +517 -0
  57. package/dist/backend/detail/AttachmentMetadataDialog.js.map +7 -0
  58. package/dist/backend/detail/AttachmentsSection.js +367 -0
  59. package/dist/backend/detail/AttachmentsSection.js.map +7 -0
  60. package/dist/backend/detail/CustomDataSection.js +433 -0
  61. package/dist/backend/detail/CustomDataSection.js.map +7 -0
  62. package/dist/backend/detail/DetailFieldsSection.js +75 -0
  63. package/dist/backend/detail/DetailFieldsSection.js.map +7 -0
  64. package/dist/backend/detail/ErrorMessage.js +28 -0
  65. package/dist/backend/detail/ErrorMessage.js.map +7 -0
  66. package/dist/backend/detail/InlineEditors.js +681 -0
  67. package/dist/backend/detail/InlineEditors.js.map +7 -0
  68. package/dist/backend/detail/LoadingMessage.js +14 -0
  69. package/dist/backend/detail/LoadingMessage.js.map +7 -0
  70. package/dist/backend/detail/NotesSection.js +1032 -0
  71. package/dist/backend/detail/NotesSection.js.map +7 -0
  72. package/dist/backend/detail/TabEmptyState.js +25 -0
  73. package/dist/backend/detail/TabEmptyState.js.map +7 -0
  74. package/dist/backend/detail/TagsSection.js +254 -0
  75. package/dist/backend/detail/TagsSection.js.map +7 -0
  76. package/dist/backend/detail/addressFormat.js +77 -0
  77. package/dist/backend/detail/addressFormat.js.map +7 -0
  78. package/dist/backend/detail/index.js +34 -0
  79. package/dist/backend/detail/index.js.map +7 -0
  80. package/dist/backend/fields/registry.generated.js +8 -0
  81. package/dist/backend/fields/registry.generated.js.map +7 -0
  82. package/dist/backend/fields/registry.js +29 -0
  83. package/dist/backend/fields/registry.js.map +7 -0
  84. package/dist/backend/indexes/PartialIndexBanner.js +58 -0
  85. package/dist/backend/indexes/PartialIndexBanner.js.map +7 -0
  86. package/dist/backend/indexes/store.js +62 -0
  87. package/dist/backend/indexes/store.js.map +7 -0
  88. package/dist/backend/injection/InjectionSpot.js +179 -0
  89. package/dist/backend/injection/InjectionSpot.js.map +7 -0
  90. package/dist/backend/injection/PageInjectionBoundary.js +26 -0
  91. package/dist/backend/injection/PageInjectionBoundary.js.map +7 -0
  92. package/dist/backend/injection/helpers.js +26 -0
  93. package/dist/backend/injection/helpers.js.map +7 -0
  94. package/dist/backend/injection/widgetRegistry.js +55 -0
  95. package/dist/backend/injection/widgetRegistry.js.map +7 -0
  96. package/dist/backend/inputs/ComboboxInput.js +225 -0
  97. package/dist/backend/inputs/ComboboxInput.js.map +7 -0
  98. package/dist/backend/inputs/LookupSelect.js +191 -0
  99. package/dist/backend/inputs/LookupSelect.js.map +7 -0
  100. package/dist/backend/inputs/PhoneNumberField.js +100 -0
  101. package/dist/backend/inputs/PhoneNumberField.js.map +7 -0
  102. package/dist/backend/inputs/SwitchableMarkdownInput.js +92 -0
  103. package/dist/backend/inputs/SwitchableMarkdownInput.js.map +7 -0
  104. package/dist/backend/inputs/TagsInput.js +222 -0
  105. package/dist/backend/inputs/TagsInput.js.map +7 -0
  106. package/dist/backend/inputs/index.js +6 -0
  107. package/dist/backend/inputs/index.js.map +7 -0
  108. package/dist/backend/operations/LastOperationBanner.js +80 -0
  109. package/dist/backend/operations/LastOperationBanner.js.map +7 -0
  110. package/dist/backend/operations/store.js +183 -0
  111. package/dist/backend/operations/store.js.map +7 -0
  112. package/dist/backend/schedule/ScheduleAgenda.js +107 -0
  113. package/dist/backend/schedule/ScheduleAgenda.js.map +7 -0
  114. package/dist/backend/schedule/ScheduleGrid.js +107 -0
  115. package/dist/backend/schedule/ScheduleGrid.js.map +7 -0
  116. package/dist/backend/schedule/ScheduleToolbar.js +166 -0
  117. package/dist/backend/schedule/ScheduleToolbar.js.map +7 -0
  118. package/dist/backend/schedule/ScheduleView.js +165 -0
  119. package/dist/backend/schedule/ScheduleView.js.map +7 -0
  120. package/dist/backend/schedule/index.js +6 -0
  121. package/dist/backend/schedule/index.js.map +7 -0
  122. package/dist/backend/schedule/recurrence.js +83 -0
  123. package/dist/backend/schedule/recurrence.js.map +7 -0
  124. package/dist/backend/schedule/types.js +1 -0
  125. package/dist/backend/schedule/types.js.map +7 -0
  126. package/dist/backend/upgrades/UpgradeActionBanner.js +91 -0
  127. package/dist/backend/upgrades/UpgradeActionBanner.js.map +7 -0
  128. package/dist/backend/utils/api.js +127 -0
  129. package/dist/backend/utils/api.js.map +7 -0
  130. package/dist/backend/utils/apiCall.js +48 -0
  131. package/dist/backend/utils/apiCall.js.map +7 -0
  132. package/dist/backend/utils/crud.js +126 -0
  133. package/dist/backend/utils/crud.js.map +7 -0
  134. package/dist/backend/utils/customFieldColumns.js +56 -0
  135. package/dist/backend/utils/customFieldColumns.js.map +7 -0
  136. package/dist/backend/utils/customFieldDefs.js +143 -0
  137. package/dist/backend/utils/customFieldDefs.js.map +7 -0
  138. package/dist/backend/utils/customFieldFilters.js +126 -0
  139. package/dist/backend/utils/customFieldFilters.js.map +7 -0
  140. package/dist/backend/utils/customFieldForms.js +162 -0
  141. package/dist/backend/utils/customFieldForms.js.map +7 -0
  142. package/dist/backend/utils/customFieldValues.js +26 -0
  143. package/dist/backend/utils/customFieldValues.js.map +7 -0
  144. package/dist/backend/utils/flash.js +16 -0
  145. package/dist/backend/utils/flash.js.map +7 -0
  146. package/dist/backend/utils/nav.js +185 -0
  147. package/dist/backend/utils/nav.js.map +7 -0
  148. package/dist/backend/utils/serverErrors.js +230 -0
  149. package/dist/backend/utils/serverErrors.js.map +7 -0
  150. package/dist/frontend/AuthFooter.js +23 -0
  151. package/dist/frontend/AuthFooter.js.map +7 -0
  152. package/dist/frontend/LanguageSwitcher.js +57 -0
  153. package/dist/frontend/LanguageSwitcher.js.map +7 -0
  154. package/dist/frontend/Layout.js +14 -0
  155. package/dist/frontend/Layout.js.map +7 -0
  156. package/dist/index.js +32 -0
  157. package/dist/index.js.map +7 -0
  158. package/dist/primitives/DataLoader.js +67 -0
  159. package/dist/primitives/DataLoader.js.map +7 -0
  160. package/dist/primitives/ErrorNotice.js +20 -0
  161. package/dist/primitives/ErrorNotice.js.map +7 -0
  162. package/dist/primitives/alert.js +38 -0
  163. package/dist/primitives/alert.js.map +7 -0
  164. package/dist/primitives/badge.js +28 -0
  165. package/dist/primitives/badge.js.map +7 -0
  166. package/dist/primitives/button.js +44 -0
  167. package/dist/primitives/button.js.map +7 -0
  168. package/dist/primitives/card.js +91 -0
  169. package/dist/primitives/card.js.map +7 -0
  170. package/dist/primitives/checkbox.js +28 -0
  171. package/dist/primitives/checkbox.js.map +7 -0
  172. package/dist/primitives/dialog.js +90 -0
  173. package/dist/primitives/dialog.js.map +7 -0
  174. package/dist/primitives/input.js +22 -0
  175. package/dist/primitives/input.js.map +7 -0
  176. package/dist/primitives/label.js +21 -0
  177. package/dist/primitives/label.js.map +7 -0
  178. package/dist/primitives/separator.js +9 -0
  179. package/dist/primitives/separator.js.map +7 -0
  180. package/dist/primitives/spinner.js +24 -0
  181. package/dist/primitives/spinner.js.map +7 -0
  182. package/dist/primitives/switch.js +80 -0
  183. package/dist/primitives/switch.js.map +7 -0
  184. package/dist/primitives/table.js +29 -0
  185. package/dist/primitives/table.js.map +7 -0
  186. package/dist/primitives/tabs.js +87 -0
  187. package/dist/primitives/tabs.js.map +7 -0
  188. package/dist/primitives/textarea.js +21 -0
  189. package/dist/primitives/textarea.js.map +7 -0
  190. package/dist/primitives/tooltip.js +60 -0
  191. package/dist/primitives/tooltip.js.map +7 -0
  192. package/dist/theme/QueryProvider.js +44 -0
  193. package/dist/theme/QueryProvider.js.map +7 -0
  194. package/dist/theme/ThemeProvider.js +95 -0
  195. package/dist/theme/ThemeProvider.js.map +7 -0
  196. package/dist/theme/ThemeToggle.js +88 -0
  197. package/dist/theme/ThemeToggle.js.map +7 -0
  198. package/dist/theme/index.js +10 -0
  199. package/dist/theme/index.js.map +7 -0
  200. package/dist/types/react-big-calendar.d.js +1 -0
  201. package/dist/types/react-big-calendar.d.js.map +7 -0
  202. package/jest.config.cjs +23 -0
  203. package/jest.setup.ts +55 -0
  204. package/package.json +105 -0
  205. package/src/backend/AppShell.tsx +1096 -0
  206. package/src/backend/ConfirmDialog.tsx +19 -0
  207. package/src/backend/ContextHelp.tsx +38 -0
  208. package/src/backend/CrudForm.tsx +2503 -0
  209. package/src/backend/DataTable.tsx +1730 -0
  210. package/src/backend/EmptyState.tsx +65 -0
  211. package/src/backend/FilterBar.tsx +161 -0
  212. package/src/backend/FilterOverlay.tsx +328 -0
  213. package/src/backend/FlashMessages.tsx +82 -0
  214. package/src/backend/JsonBuilder.tsx +362 -0
  215. package/src/backend/JsonDisplay.tsx +254 -0
  216. package/src/backend/Page.tsx +30 -0
  217. package/src/backend/PerspectiveSidebar.tsx +337 -0
  218. package/src/backend/RowActions.tsx +151 -0
  219. package/src/backend/TruncatedCell.tsx +133 -0
  220. package/src/backend/UserMenu.tsx +118 -0
  221. package/src/backend/ValueIcons.tsx +48 -0
  222. package/src/backend/__tests__/AppShell.test.tsx +115 -0
  223. package/src/backend/__tests__/CrudForm.render.test.tsx +30 -0
  224. package/src/backend/__tests__/DataTable.render.test.tsx +48 -0
  225. package/src/backend/__tests__/custom-field-filters.test.ts +72 -0
  226. package/src/backend/__tests__/custom-field-forms.test.ts +54 -0
  227. package/src/backend/__tests__/serverErrors.test.ts +83 -0
  228. package/src/backend/custom-fields/FieldDefinitionsEditor.tsx +1292 -0
  229. package/src/backend/custom-fields/FieldDefinitionsManager.tsx +381 -0
  230. package/src/backend/dashboard/DashboardScreen.tsx +684 -0
  231. package/src/backend/dashboard/__tests__/DashboardScreen.test.tsx +112 -0
  232. package/src/backend/dashboard/index.ts +1 -0
  233. package/src/backend/dashboard/widgetRegistry.ts +68 -0
  234. package/src/backend/detail/ActivitiesSection.tsx +1284 -0
  235. package/src/backend/detail/AddressEditor.tsx +472 -0
  236. package/src/backend/detail/AddressTiles.tsx +587 -0
  237. package/src/backend/detail/AddressesSection.tsx +346 -0
  238. package/src/backend/detail/AttachmentDeleteDialog.tsx +56 -0
  239. package/src/backend/detail/AttachmentMetadataDialog.tsx +672 -0
  240. package/src/backend/detail/AttachmentsSection.tsx +414 -0
  241. package/src/backend/detail/CustomDataSection.tsx +530 -0
  242. package/src/backend/detail/DetailFieldsSection.tsx +147 -0
  243. package/src/backend/detail/ErrorMessage.tsx +32 -0
  244. package/src/backend/detail/InlineEditors.tsx +877 -0
  245. package/src/backend/detail/LoadingMessage.tsx +14 -0
  246. package/src/backend/detail/NotesSection.tsx +1275 -0
  247. package/src/backend/detail/TabEmptyState.tsx +48 -0
  248. package/src/backend/detail/TagsSection.tsx +314 -0
  249. package/src/backend/detail/addressFormat.tsx +121 -0
  250. package/src/backend/detail/index.ts +44 -0
  251. package/src/backend/fields/registry.generated.ts +8 -0
  252. package/src/backend/fields/registry.ts +38 -0
  253. package/src/backend/indexes/PartialIndexBanner.tsx +68 -0
  254. package/src/backend/indexes/store.ts +88 -0
  255. package/src/backend/injection/InjectionSpot.tsx +236 -0
  256. package/src/backend/injection/PageInjectionBoundary.tsx +31 -0
  257. package/src/backend/injection/helpers.ts +35 -0
  258. package/src/backend/injection/widgetRegistry.ts +68 -0
  259. package/src/backend/inputs/ComboboxInput.tsx +269 -0
  260. package/src/backend/inputs/LookupSelect.tsx +247 -0
  261. package/src/backend/inputs/PhoneNumberField.tsx +129 -0
  262. package/src/backend/inputs/SwitchableMarkdownInput.tsx +128 -0
  263. package/src/backend/inputs/TagsInput.tsx +259 -0
  264. package/src/backend/inputs/index.ts +5 -0
  265. package/src/backend/operations/LastOperationBanner.tsx +85 -0
  266. package/src/backend/operations/__tests__/LastOperationBanner.test.tsx +99 -0
  267. package/src/backend/operations/store.ts +230 -0
  268. package/src/backend/schedule/ScheduleAgenda.tsx +136 -0
  269. package/src/backend/schedule/ScheduleGrid.tsx +136 -0
  270. package/src/backend/schedule/ScheduleToolbar.tsx +178 -0
  271. package/src/backend/schedule/ScheduleView.tsx +198 -0
  272. package/src/backend/schedule/index.ts +5 -0
  273. package/src/backend/schedule/recurrence.ts +99 -0
  274. package/src/backend/schedule/types.ts +26 -0
  275. package/src/backend/upgrades/UpgradeActionBanner.tsx +128 -0
  276. package/src/backend/utils/__tests__/apiCall.test.ts +109 -0
  277. package/src/backend/utils/__tests__/crud.test.ts +87 -0
  278. package/src/backend/utils/__tests__/customFieldDefs.test.ts +25 -0
  279. package/src/backend/utils/__tests__/customFieldValues.test.ts +35 -0
  280. package/src/backend/utils/api.ts +149 -0
  281. package/src/backend/utils/apiCall.ts +96 -0
  282. package/src/backend/utils/crud.ts +174 -0
  283. package/src/backend/utils/customFieldColumns.ts +71 -0
  284. package/src/backend/utils/customFieldDefs.ts +245 -0
  285. package/src/backend/utils/customFieldFilters.ts +145 -0
  286. package/src/backend/utils/customFieldForms.ts +196 -0
  287. package/src/backend/utils/customFieldValues.ts +41 -0
  288. package/src/backend/utils/flash.ts +17 -0
  289. package/src/backend/utils/nav.ts +238 -0
  290. package/src/backend/utils/serverErrors.ts +302 -0
  291. package/src/frontend/AuthFooter.tsx +29 -0
  292. package/src/frontend/LanguageSwitcher.tsx +66 -0
  293. package/src/frontend/Layout.tsx +13 -0
  294. package/src/index.ts +32 -0
  295. package/src/primitives/DataLoader.tsx +92 -0
  296. package/src/primitives/ErrorNotice.tsx +26 -0
  297. package/src/primitives/alert.tsx +52 -0
  298. package/src/primitives/badge.tsx +31 -0
  299. package/src/primitives/button.tsx +47 -0
  300. package/src/primitives/card.tsx +92 -0
  301. package/src/primitives/checkbox.tsx +28 -0
  302. package/src/primitives/dialog.tsx +110 -0
  303. package/src/primitives/input.tsx +20 -0
  304. package/src/primitives/label.tsx +18 -0
  305. package/src/primitives/separator.tsx +7 -0
  306. package/src/primitives/spinner.tsx +27 -0
  307. package/src/primitives/switch.tsx +86 -0
  308. package/src/primitives/table.tsx +27 -0
  309. package/src/primitives/tabs.tsx +128 -0
  310. package/src/primitives/textarea.tsx +20 -0
  311. package/src/primitives/tooltip.tsx +85 -0
  312. package/src/theme/QueryProvider.tsx +46 -0
  313. package/src/theme/ThemeProvider.tsx +120 -0
  314. package/src/theme/ThemeToggle.tsx +88 -0
  315. package/src/theme/index.ts +3 -0
  316. package/src/types/react-big-calendar.d.ts +16 -0
  317. package/tsconfig.build.json +11 -0
  318. package/tsconfig.json +9 -0
  319. package/watch.mjs +6 -0
@@ -0,0 +1,128 @@
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import { cn } from '@open-mercato/shared/lib/utils'
5
+
6
+ type TabsContextValue = {
7
+ value: string
8
+ onValueChange: (value: string) => void
9
+ }
10
+
11
+ const TabsContext = React.createContext<TabsContextValue | undefined>(undefined)
12
+
13
+ function useTabsContext() {
14
+ const context = React.useContext(TabsContext)
15
+ if (!context) {
16
+ throw new Error('Tabs components must be used within a Tabs provider')
17
+ }
18
+ return context
19
+ }
20
+
21
+ export type TabsProps = {
22
+ value?: string
23
+ defaultValue?: string
24
+ onValueChange?: (value: string) => void
25
+ children: React.ReactNode
26
+ className?: string
27
+ }
28
+
29
+ export function Tabs({
30
+ value: controlledValue,
31
+ defaultValue,
32
+ onValueChange,
33
+ children,
34
+ className,
35
+ }: TabsProps) {
36
+ const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultValue ?? '')
37
+ const isControlled = controlledValue !== undefined
38
+ const value = isControlled ? controlledValue : uncontrolledValue
39
+
40
+ const handleValueChange = React.useCallback(
41
+ (newValue: string) => {
42
+ if (!isControlled) {
43
+ setUncontrolledValue(newValue)
44
+ }
45
+ onValueChange?.(newValue)
46
+ },
47
+ [isControlled, onValueChange],
48
+ )
49
+
50
+ return (
51
+ <TabsContext.Provider value={{ value, onValueChange: handleValueChange }}>
52
+ <div className={className}>{children}</div>
53
+ </TabsContext.Provider>
54
+ )
55
+ }
56
+
57
+ export type TabsListProps = {
58
+ children: React.ReactNode
59
+ className?: string
60
+ }
61
+
62
+ export function TabsList({ children, className }: TabsListProps) {
63
+ return (
64
+ <div
65
+ className={cn(
66
+ 'inline-flex h-9 items-center justify-start rounded-lg bg-muted p-1 text-muted-foreground',
67
+ className,
68
+ )}
69
+ role="tablist"
70
+ >
71
+ {children}
72
+ </div>
73
+ )
74
+ }
75
+
76
+ export type TabsTriggerProps = {
77
+ value: string
78
+ children: React.ReactNode
79
+ className?: string
80
+ disabled?: boolean
81
+ }
82
+
83
+ export function TabsTrigger({ value, children, className, disabled }: TabsTriggerProps) {
84
+ const { value: selectedValue, onValueChange } = useTabsContext()
85
+ const isSelected = selectedValue === value
86
+
87
+ return (
88
+ <button
89
+ type="button"
90
+ role="tab"
91
+ aria-selected={isSelected}
92
+ disabled={disabled}
93
+ onClick={() => onValueChange(value)}
94
+ className={cn(
95
+ 'inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
96
+ isSelected
97
+ ? 'bg-background text-foreground shadow'
98
+ : 'hover:bg-background/50 hover:text-foreground',
99
+ className,
100
+ )}
101
+ >
102
+ {children}
103
+ </button>
104
+ )
105
+ }
106
+
107
+ export type TabsContentProps = {
108
+ value: string
109
+ children: React.ReactNode
110
+ className?: string
111
+ }
112
+
113
+ export function TabsContent({ value, children, className }: TabsContentProps) {
114
+ const { value: selectedValue } = useTabsContext()
115
+
116
+ if (selectedValue !== value) {
117
+ return null
118
+ }
119
+
120
+ return (
121
+ <div
122
+ role="tabpanel"
123
+ className={cn('mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2', className)}
124
+ >
125
+ {children}
126
+ </div>
127
+ )
128
+ }
@@ -0,0 +1,20 @@
1
+ import * as React from 'react'
2
+
3
+ import { cn } from '@open-mercato/shared/lib/utils'
4
+
5
+ type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>
6
+
7
+ export const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
8
+ ({ className, ...props }, ref) => (
9
+ <textarea
10
+ ref={ref}
11
+ className={cn(
12
+ 'flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
13
+ className
14
+ )}
15
+ {...props}
16
+ />
17
+ )
18
+ )
19
+
20
+ Textarea.displayName = 'Textarea'
@@ -0,0 +1,85 @@
1
+ "use client"
2
+
3
+ import * as React from 'react'
4
+ import * as TooltipPrimitive from '@radix-ui/react-tooltip'
5
+ import { cn } from '@open-mercato/shared/lib/utils'
6
+
7
+ export const TooltipProvider = TooltipPrimitive.Provider
8
+
9
+ export const Tooltip = TooltipPrimitive.Root
10
+
11
+ export const TooltipTrigger = TooltipPrimitive.Trigger
12
+
13
+ export const TooltipContent = React.forwardRef<
14
+ React.ElementRef<typeof TooltipPrimitive.Content>,
15
+ React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
16
+ >(({ className, sideOffset = 4, ...props }, ref) => (
17
+ <TooltipPrimitive.Portal>
18
+ <TooltipPrimitive.Content
19
+ ref={ref}
20
+ sideOffset={sideOffset}
21
+ className={cn(
22
+ 'z-50 overflow-hidden rounded-md bg-slate-900 px-3 py-1.5 text-xs text-slate-50 animate-in fade-in-0 zoom-in-95',
23
+ 'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
24
+ 'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2',
25
+ 'data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
26
+ 'max-w-xs break-words',
27
+ className
28
+ )}
29
+ {...props}
30
+ />
31
+ </TooltipPrimitive.Portal>
32
+ ))
33
+ TooltipContent.displayName = TooltipPrimitive.Content.displayName
34
+
35
+ export type TooltipProps = {
36
+ content: React.ReactNode
37
+ children: React.ReactNode
38
+ delayDuration?: number
39
+ side?: 'top' | 'right' | 'bottom' | 'left'
40
+ align?: 'start' | 'center' | 'end'
41
+ open?: boolean
42
+ onOpenChange?: (open: boolean) => void
43
+ disabled?: boolean
44
+ }
45
+
46
+ /**
47
+ * Simple tooltip wrapper component for common use cases.
48
+ *
49
+ * @example
50
+ * <SimpleTooltip content="Full text here">
51
+ * <span>Truncated...</span>
52
+ * </SimpleTooltip>
53
+ */
54
+ export function SimpleTooltip({
55
+ content,
56
+ children,
57
+ delayDuration = 300,
58
+ side = 'top',
59
+ align = 'center',
60
+ open,
61
+ onOpenChange,
62
+ disabled = false,
63
+ }: TooltipProps) {
64
+ // If disabled or no content, just render children without tooltip
65
+ const isDisabled = disabled || !content
66
+
67
+ if (isDisabled) {
68
+ return <>{children}</>
69
+ }
70
+
71
+ return (
72
+ <Tooltip
73
+ open={open}
74
+ onOpenChange={onOpenChange}
75
+ delayDuration={delayDuration}
76
+ >
77
+ <TooltipTrigger asChild>
78
+ {children}
79
+ </TooltipTrigger>
80
+ <TooltipContent side={side} align={align}>
81
+ {content}
82
+ </TooltipContent>
83
+ </Tooltip>
84
+ )
85
+ }
@@ -0,0 +1,46 @@
1
+ "use client"
2
+ import * as React from 'react'
3
+ import { MutationCache, QueryCache, QueryClient, QueryClientProvider } from '@tanstack/react-query'
4
+ import { redirectToSessionRefresh, redirectToForbiddenLogin, UnauthorizedError, ForbiddenError, apiFetch, setAuthRedirectConfig } from '../backend/utils/api'
5
+
6
+ // Ensure global fetch calls also respect our redirect-on-401/403 policy.
7
+ function ensureGlobalFetchInterception() {
8
+ if (typeof window === 'undefined') return
9
+ const w = window as any
10
+ if (w.__omFetchPatched) return
11
+ w.__omFetchPatched = true
12
+ w.__omOriginalFetch = window.fetch
13
+ window.fetch = ((input: RequestInfo | URL, init?: RequestInit) => apiFetch(input, init)) as any
14
+ }
15
+
16
+ const client = new QueryClient({
17
+ queryCache: new QueryCache({
18
+ onError: (error) => {
19
+ if (error instanceof UnauthorizedError) redirectToSessionRefresh()
20
+ else if (error instanceof ForbiddenError) redirectToForbiddenLogin()
21
+ // As a fallback, try to detect common cases
22
+ else if ((error as any)?.status === 401) redirectToSessionRefresh()
23
+ else if ((error as any)?.status === 403) redirectToForbiddenLogin()
24
+ },
25
+ }),
26
+ mutationCache: new MutationCache({
27
+ onError: (error) => {
28
+ if (error instanceof UnauthorizedError) redirectToSessionRefresh()
29
+ else if (error instanceof ForbiddenError) redirectToForbiddenLogin()
30
+ else if ((error as any)?.status === 401) redirectToSessionRefresh()
31
+ else if ((error as any)?.status === 403) redirectToForbiddenLogin()
32
+ },
33
+ }),
34
+ })
35
+
36
+ type QueryProviderProps = { children: React.ReactNode; defaultForbiddenRoles?: string[] }
37
+
38
+ export function QueryProvider({ children, defaultForbiddenRoles }: QueryProviderProps) {
39
+ React.useEffect(() => {
40
+ ensureGlobalFetchInterception()
41
+ if (defaultForbiddenRoles && defaultForbiddenRoles.length) {
42
+ setAuthRedirectConfig({ defaultForbiddenRoles })
43
+ }
44
+ }, [])
45
+ return <QueryClientProvider client={client}>{children}</QueryClientProvider>
46
+ }
@@ -0,0 +1,120 @@
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+
5
+ export type Theme = 'light' | 'dark' | 'system'
6
+
7
+ type ThemeContextValue = {
8
+ theme: Theme
9
+ resolvedTheme: 'light' | 'dark'
10
+ setTheme: (theme: Theme) => void
11
+ }
12
+
13
+ const ThemeContext = React.createContext<ThemeContextValue | undefined>(undefined)
14
+
15
+ const THEME_STORAGE_KEY = 'om-theme'
16
+
17
+ function getSystemTheme(): 'light' | 'dark' {
18
+ if (typeof window === 'undefined') return 'light'
19
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
20
+ }
21
+
22
+ function getStoredTheme(): Theme {
23
+ if (typeof window === 'undefined') return 'system'
24
+ try {
25
+ const stored = localStorage.getItem(THEME_STORAGE_KEY)
26
+ if (stored === 'light' || stored === 'dark' || stored === 'system') {
27
+ return stored
28
+ }
29
+ } catch (error) {
30
+ // localStorage may be unavailable in private browsing, iframes, or restricted contexts
31
+ // Theme will default to system preference - this is expected graceful degradation
32
+ if (process.env.NODE_ENV === 'development') {
33
+ console.warn('[ThemeProvider] localStorage read failed:', error)
34
+ }
35
+ }
36
+ return 'system'
37
+ }
38
+
39
+ function applyTheme(resolvedTheme: 'light' | 'dark') {
40
+ const root = document.documentElement
41
+ if (resolvedTheme === 'dark') {
42
+ root.classList.add('dark')
43
+ } else {
44
+ root.classList.remove('dark')
45
+ }
46
+ }
47
+
48
+ export function ThemeProvider({ children }: { children: React.ReactNode }) {
49
+ const [theme, setThemeState] = React.useState<Theme>('system')
50
+ const [resolvedTheme, setResolvedTheme] = React.useState<'light' | 'dark'>('light')
51
+ const [mounted, setMounted] = React.useState(false)
52
+
53
+ // Initialize theme from localStorage on mount
54
+ React.useEffect(() => {
55
+ const stored = getStoredTheme()
56
+ setThemeState(stored)
57
+ const resolved = stored === 'system' ? getSystemTheme() : stored
58
+ setResolvedTheme(resolved)
59
+ applyTheme(resolved)
60
+ setMounted(true)
61
+ }, [])
62
+
63
+ // Listen for system theme changes
64
+ React.useEffect(() => {
65
+ if (typeof window === 'undefined') return
66
+
67
+ const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
68
+ const handleChange = () => {
69
+ if (theme === 'system') {
70
+ const newResolved = getSystemTheme()
71
+ setResolvedTheme(newResolved)
72
+ applyTheme(newResolved)
73
+ }
74
+ }
75
+
76
+ mediaQuery.addEventListener('change', handleChange)
77
+ return () => mediaQuery.removeEventListener('change', handleChange)
78
+ }, [theme])
79
+
80
+ const setTheme = React.useCallback((newTheme: Theme) => {
81
+ setThemeState(newTheme)
82
+ try {
83
+ localStorage.setItem(THEME_STORAGE_KEY, newTheme)
84
+ } catch (error) {
85
+ // localStorage may be unavailable - theme still works for this session, just won't persist
86
+ if (process.env.NODE_ENV === 'development') {
87
+ console.warn('[ThemeProvider] localStorage write failed:', error)
88
+ }
89
+ }
90
+ const resolved = newTheme === 'system' ? getSystemTheme() : newTheme
91
+ setResolvedTheme(resolved)
92
+ applyTheme(resolved)
93
+ }, [])
94
+
95
+ const value = React.useMemo(
96
+ () => ({ theme, resolvedTheme, setTheme }),
97
+ [theme, resolvedTheme, setTheme]
98
+ )
99
+
100
+ // Prevent flash of wrong theme during hydration
101
+ if (!mounted) {
102
+ return <>{children}</>
103
+ }
104
+
105
+ return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
106
+ }
107
+
108
+ export function useTheme(): ThemeContextValue {
109
+ const context = React.useContext(ThemeContext)
110
+ if (context === undefined) {
111
+ // Return safe defaults when not in provider (e.g., server render)
112
+ return {
113
+ theme: 'system',
114
+ resolvedTheme: 'light',
115
+ setTheme: () => {},
116
+ }
117
+ }
118
+ return context
119
+ }
120
+
@@ -0,0 +1,88 @@
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import { Moon, Sun } from 'lucide-react'
5
+ import { useTheme } from './ThemeProvider'
6
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
7
+ import { cn } from '@open-mercato/shared/lib/utils'
8
+
9
+ type ThemeToggleProps = {
10
+ className?: string
11
+ }
12
+
13
+ export function ThemeToggle({ className }: ThemeToggleProps) {
14
+ const { resolvedTheme, setTheme } = useTheme()
15
+ const t = useT()
16
+ const [mounted, setMounted] = React.useState(false)
17
+
18
+ React.useEffect(() => {
19
+ setMounted(true)
20
+ }, [])
21
+
22
+ const isDark = resolvedTheme === 'dark'
23
+ const toggleLabel = t('common.theme.toggle', 'Toggle theme')
24
+
25
+ const toggle = () => {
26
+ setTheme(isDark ? 'light' : 'dark')
27
+ }
28
+
29
+ // Render placeholder during SSR to prevent hydration mismatch
30
+ if (!mounted) {
31
+ return (
32
+ <div
33
+ className={cn(
34
+ 'relative flex h-7 w-14 items-center rounded-full bg-muted p-1',
35
+ className
36
+ )}
37
+ aria-hidden="true"
38
+ >
39
+ <div className="flex w-full justify-between px-1">
40
+ <Sun className="size-3.5 text-muted-foreground" />
41
+ <Moon className="size-3.5 text-muted-foreground" />
42
+ </div>
43
+ </div>
44
+ )
45
+ }
46
+
47
+ return (
48
+ <button
49
+ type="button"
50
+ role="switch"
51
+ aria-checked={isDark}
52
+ aria-label={toggleLabel}
53
+ onClick={toggle}
54
+ className={cn(
55
+ 'relative flex h-7 w-14 cursor-pointer items-center rounded-full p-1 transition-colors',
56
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
57
+ isDark ? 'bg-primary' : 'bg-muted',
58
+ className
59
+ )}
60
+ >
61
+ {/* Sliding indicator */}
62
+ <span
63
+ className={cn(
64
+ 'absolute size-5 rounded-full bg-background shadow-sm transition-transform duration-200 motion-reduce:transition-none',
65
+ isDark ? 'translate-x-7' : 'translate-x-0'
66
+ )}
67
+ />
68
+ {/* Icons */}
69
+ <span className="relative flex w-full justify-between px-0.5">
70
+ <Sun
71
+ className={cn(
72
+ 'size-3.5 transition-colors motion-reduce:transition-none',
73
+ isDark ? 'text-muted-foreground' : 'text-amber-500'
74
+ )}
75
+ />
76
+ <Moon
77
+ className={cn(
78
+ 'size-3.5 transition-colors motion-reduce:transition-none',
79
+ isDark ? 'text-primary-foreground' : 'text-muted-foreground'
80
+ )}
81
+ />
82
+ </span>
83
+ <span className="sr-only">
84
+ {isDark ? t('common.theme.dark', 'Dark') : t('common.theme.light', 'Light')}
85
+ </span>
86
+ </button>
87
+ )
88
+ }
@@ -0,0 +1,3 @@
1
+ export { ThemeProvider, useTheme } from './ThemeProvider'
2
+ export { ThemeToggle } from './ThemeToggle'
3
+ export { QueryProvider } from './QueryProvider'
@@ -0,0 +1,16 @@
1
+ declare module 'react-big-calendar' {
2
+ import * as React from 'react'
3
+
4
+ export type View = 'month' | 'week' | 'day' | 'agenda' | string
5
+
6
+ export type SlotInfo = {
7
+ start: Date
8
+ end: Date
9
+ slots: Date[]
10
+ action: 'select' | 'click' | 'doubleClick'
11
+ }
12
+
13
+ export function dateFnsLocalizer(_config: Record<string, unknown>): unknown
14
+
15
+ export const Calendar: React.ComponentType<Record<string, unknown>>
16
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "extends": "../../tsconfig.json",
4
+ "compilerOptions": {
5
+ "noEmit": false,
6
+ "declaration": false,
7
+ "outDir": "./dist",
8
+ "rootDir": "./src"
9
+ },
10
+ "include": ["src/**/*"]
11
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "extends": "../../tsconfig.base.json",
4
+ "compilerOptions": {
5
+ "noEmit": true
6
+ },
7
+ "include": ["src/**/*"],
8
+ "exclude": ["node_modules", "dist", "**/__tests__/**"]
9
+ }
package/watch.mjs ADDED
@@ -0,0 +1,6 @@
1
+ import { watch } from '../../scripts/watch.mjs'
2
+ import { dirname } from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+
5
+ const __dirname = dirname(fileURLToPath(import.meta.url))
6
+ watch(__dirname)