@parto-system-design/ui 1.1.16 → 1.1.18

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 (294) hide show
  1. package/AGENTS.md +2 -2
  2. package/README.md +1 -1
  3. package/dist/components/brand/parto-logo.cjs +0 -2
  4. package/dist/components/brand/parto-logo.js +0 -2
  5. package/dist/components/charts/PartoAreaChart.cjs +2 -4
  6. package/dist/components/charts/PartoAreaChart.d.cts +1 -1
  7. package/dist/components/charts/PartoAreaChart.d.ts +1 -1
  8. package/dist/components/charts/PartoAreaChart.js +2 -4
  9. package/dist/components/charts/PartoBarChart.cjs +2 -4
  10. package/dist/components/charts/PartoBarChart.d.cts +1 -1
  11. package/dist/components/charts/PartoBarChart.d.ts +1 -1
  12. package/dist/components/charts/PartoBarChart.js +2 -4
  13. package/dist/components/charts/PartoLineChart.cjs +2 -4
  14. package/dist/components/charts/PartoLineChart.d.cts +1 -1
  15. package/dist/components/charts/PartoLineChart.d.ts +1 -1
  16. package/dist/components/charts/PartoLineChart.js +2 -4
  17. package/dist/components/charts/PartoPieChart.cjs +2 -4
  18. package/dist/components/charts/PartoPieChart.d.cts +1 -1
  19. package/dist/components/charts/PartoPieChart.d.ts +1 -1
  20. package/dist/components/charts/PartoPieChart.js +2 -4
  21. package/dist/components/ui/accordion.cjs +0 -2
  22. package/dist/components/ui/accordion.d.cts +4 -5
  23. package/dist/components/ui/accordion.d.ts +4 -5
  24. package/dist/components/ui/accordion.js +0 -2
  25. package/dist/components/ui/alert-dialog.cjs +0 -2
  26. package/dist/components/ui/alert-dialog.d.cts +9 -10
  27. package/dist/components/ui/alert-dialog.d.ts +9 -10
  28. package/dist/components/ui/alert-dialog.js +1 -3
  29. package/dist/components/ui/alert-rule-card.cjs +149 -12
  30. package/dist/components/ui/alert-rule-card.d.cts +2 -2
  31. package/dist/components/ui/alert-rule-card.d.ts +2 -2
  32. package/dist/components/ui/alert-rule-card.js +149 -12
  33. package/dist/components/ui/alert.cjs +3 -5
  34. package/dist/components/ui/alert.js +3 -5
  35. package/dist/components/ui/app-bar.cjs +0 -2
  36. package/dist/components/ui/app-bar.js +0 -2
  37. package/dist/components/ui/avatar.cjs +0 -2
  38. package/dist/components/ui/avatar.d.cts +3 -4
  39. package/dist/components/ui/avatar.d.ts +3 -4
  40. package/dist/components/ui/avatar.js +0 -2
  41. package/dist/components/ui/badge.cjs +0 -2
  42. package/dist/components/ui/badge.js +0 -2
  43. package/dist/components/ui/breadcrumb.cjs +0 -2
  44. package/dist/components/ui/breadcrumb.d.cts +7 -8
  45. package/dist/components/ui/breadcrumb.d.ts +7 -8
  46. package/dist/components/ui/breadcrumb.js +0 -2
  47. package/dist/components/ui/button.cjs +0 -2
  48. package/dist/components/ui/button.js +1 -3
  49. package/dist/components/ui/calendar.cjs +0 -2
  50. package/dist/components/ui/calendar.d.cts +1 -2
  51. package/dist/components/ui/calendar.d.ts +1 -2
  52. package/dist/components/ui/calendar.js +1 -3
  53. package/dist/components/ui/card.cjs +0 -2
  54. package/dist/components/ui/card.js +0 -2
  55. package/dist/components/ui/checkbox.cjs +0 -2
  56. package/dist/components/ui/checkbox.js +0 -2
  57. package/dist/components/ui/concept-card.cjs +179 -61
  58. package/dist/components/ui/concept-card.d.cts +3 -3
  59. package/dist/components/ui/concept-card.d.ts +3 -3
  60. package/dist/components/ui/concept-card.js +179 -61
  61. package/dist/components/ui/data-table.cjs +129 -3
  62. package/dist/components/ui/data-table.d.cts +1 -1
  63. package/dist/components/ui/data-table.d.ts +1 -1
  64. package/dist/components/ui/data-table.js +130 -4
  65. package/dist/components/ui/dialog.cjs +0 -2
  66. package/dist/components/ui/dialog.d.cts +2 -3
  67. package/dist/components/ui/dialog.d.ts +2 -3
  68. package/dist/components/ui/dialog.js +0 -2
  69. package/dist/components/ui/dropdown-menu.cjs +0 -2
  70. package/dist/components/ui/dropdown-menu.d.cts +15 -16
  71. package/dist/components/ui/dropdown-menu.d.ts +15 -16
  72. package/dist/components/ui/dropdown-menu.js +0 -2
  73. package/dist/components/ui/filter-provider.cjs +0 -2
  74. package/dist/components/ui/filter-provider.d.cts +1 -2
  75. package/dist/components/ui/filter-provider.d.ts +1 -2
  76. package/dist/components/ui/filter-provider.js +0 -2
  77. package/dist/components/ui/form.cjs +0 -2
  78. package/dist/components/ui/form.d.cts +7 -8
  79. package/dist/components/ui/form.d.ts +7 -8
  80. package/dist/components/ui/form.js +0 -2
  81. package/dist/components/ui/input.cjs +0 -2
  82. package/dist/components/ui/input.js +0 -2
  83. package/dist/components/ui/iran-province-heat.cjs +1 -3
  84. package/dist/components/ui/iran-province-heat.d.cts +1 -1
  85. package/dist/components/ui/iran-province-heat.d.ts +1 -1
  86. package/dist/components/ui/iran-province-heat.js +1 -3
  87. package/dist/components/ui/label.cjs +0 -2
  88. package/dist/components/ui/label.d.cts +1 -2
  89. package/dist/components/ui/label.d.ts +1 -2
  90. package/dist/components/ui/label.js +0 -2
  91. package/dist/components/ui/page-card.cjs +170 -33
  92. package/dist/components/ui/page-card.d.cts +3 -3
  93. package/dist/components/ui/page-card.d.ts +3 -3
  94. package/dist/components/ui/page-card.js +170 -33
  95. package/dist/components/ui/page-header.cjs +128 -2
  96. package/dist/components/ui/page-header.d.cts +1 -1
  97. package/dist/components/ui/page-header.d.ts +1 -1
  98. package/dist/components/ui/page-header.js +128 -2
  99. package/dist/components/ui/password-input.cjs +0 -2
  100. package/dist/components/ui/password-input.js +0 -2
  101. package/dist/components/ui/popover.cjs +0 -2
  102. package/dist/components/ui/popover.js +0 -2
  103. package/dist/components/ui/progress.cjs +1 -3
  104. package/dist/components/ui/progress.js +1 -3
  105. package/dist/components/ui/radio-card.cjs +0 -2
  106. package/dist/components/ui/radio-card.js +0 -2
  107. package/dist/components/ui/radio-group.cjs +0 -2
  108. package/dist/components/ui/radio-group.js +0 -2
  109. package/dist/components/ui/saved-query-card.cjs +0 -2
  110. package/dist/components/ui/saved-query-card.d.cts +1 -1
  111. package/dist/components/ui/saved-query-card.d.ts +1 -1
  112. package/dist/components/ui/saved-query-card.js +1 -3
  113. package/dist/components/ui/scroll-area.cjs +0 -2
  114. package/dist/components/ui/scroll-area.d.cts +2 -3
  115. package/dist/components/ui/scroll-area.d.ts +2 -3
  116. package/dist/components/ui/scroll-area.js +0 -2
  117. package/dist/components/ui/select.cjs +0 -2
  118. package/dist/components/ui/select.d.cts +1 -2
  119. package/dist/components/ui/select.d.ts +1 -2
  120. package/dist/components/ui/select.js +0 -2
  121. package/dist/components/ui/separator.cjs +0 -2
  122. package/dist/components/ui/separator.js +0 -2
  123. package/dist/components/ui/sheet.cjs +0 -2
  124. package/dist/components/ui/sheet.d.cts +9 -10
  125. package/dist/components/ui/sheet.d.ts +9 -10
  126. package/dist/components/ui/sheet.js +0 -2
  127. package/dist/components/ui/skeleton.cjs +0 -2
  128. package/dist/components/ui/skeleton.d.cts +8 -9
  129. package/dist/components/ui/skeleton.d.ts +8 -9
  130. package/dist/components/ui/skeleton.js +0 -2
  131. package/dist/components/ui/slider.cjs +0 -2
  132. package/dist/components/ui/slider.d.cts +1 -2
  133. package/dist/components/ui/slider.d.ts +1 -2
  134. package/dist/components/ui/slider.js +0 -2
  135. package/dist/components/ui/social-platform-badge.cjs +0 -2
  136. package/dist/components/ui/social-platform-badge.js +1 -3
  137. package/dist/components/ui/sonner.cjs +4 -6
  138. package/dist/components/ui/sonner.d.cts +2 -2
  139. package/dist/components/ui/sonner.d.ts +2 -2
  140. package/dist/components/ui/sonner.js +4 -6
  141. package/dist/components/ui/sparkline.cjs +3 -5
  142. package/dist/components/ui/sparkline.d.cts +1 -1
  143. package/dist/components/ui/sparkline.d.ts +1 -1
  144. package/dist/components/ui/sparkline.js +3 -5
  145. package/dist/components/ui/switch.cjs +0 -2
  146. package/dist/components/ui/switch.js +0 -2
  147. package/dist/components/ui/table.cjs +0 -2
  148. package/dist/components/ui/table.d.cts +9 -10
  149. package/dist/components/ui/table.d.ts +9 -10
  150. package/dist/components/ui/table.js +0 -2
  151. package/dist/components/ui/tabs.cjs +0 -2
  152. package/dist/components/ui/tabs.js +0 -2
  153. package/dist/components/ui/textarea.cjs +0 -2
  154. package/dist/components/ui/textarea.js +0 -2
  155. package/dist/components/ui/toggle-group.cjs +0 -2
  156. package/dist/components/ui/toggle-group.d.cts +1 -2
  157. package/dist/components/ui/toggle-group.d.ts +1 -2
  158. package/dist/components/ui/toggle-group.js +0 -2
  159. package/dist/components/ui/tooltip.cjs +0 -2
  160. package/dist/components/ui/tooltip.js +0 -2
  161. package/dist/{concept-card-BoJ5gIJD.d.ts → concept-card-BU8JL-gj.d.cts} +5 -5
  162. package/dist/{concept-card-BXra9mr0.d.cts → concept-card-D7PfDkNR.d.ts} +5 -5
  163. package/dist/hooks/use-hotkey-registry.cjs +0 -2
  164. package/dist/hooks/use-hotkey-registry.d.cts +1 -2
  165. package/dist/hooks/use-hotkey-registry.d.ts +1 -2
  166. package/dist/hooks/use-hotkey-registry.js +0 -2
  167. package/dist/hooks/use-hotkeys.cjs +0 -2
  168. package/dist/hooks/use-hotkeys.js +0 -2
  169. package/dist/{i18n-BfRhV5aw.d.cts → i18n-DD3DMY8O.d.cts} +94 -5
  170. package/dist/{i18n-ewyqbKM-.d.ts → i18n-UEClNsBy.d.ts} +94 -5
  171. package/dist/index.cjs +1321 -1169
  172. package/dist/index.css +8010 -5445
  173. package/dist/index.d.cts +245 -991
  174. package/dist/index.d.ts +245 -991
  175. package/dist/index.js +1283 -1149
  176. package/dist/{page-card-C9XXXOVr.d.cts → page-card-DjztZrFM.d.cts} +4 -4
  177. package/dist/{page-card-DAnbez_f.d.ts → page-card-sIE4lvnb.d.ts} +4 -4
  178. package/dist/server-FTUA8opZ.d.cts +829 -0
  179. package/dist/server-m6tiB6DB.d.ts +829 -0
  180. package/dist/server.cjs +1565 -0
  181. package/dist/server.d.cts +8 -0
  182. package/dist/server.d.ts +8 -0
  183. package/dist/server.js +1483 -0
  184. package/dist/theme.css +50 -0
  185. package/dist/{toggle-group-B8r4LOQw.d.cts → toggle-group-Bis6suuR.d.cts} +3 -4
  186. package/dist/{toggle-group-B8r4LOQw.d.ts → toggle-group-Bis6suuR.d.ts} +3 -4
  187. package/dist/{utils-DlXWmDZ-.d.cts → utils-Czyp5Ned.d.cts} +1 -1
  188. package/dist/{utils-DlXWmDZ-.d.ts → utils-Czyp5Ned.d.ts} +1 -1
  189. package/package.json +455 -187
  190. package/tailwind.config.ts +184 -183
  191. package/dist/components/brand/parto-logo.cjs.map +0 -1
  192. package/dist/components/brand/parto-logo.js.map +0 -1
  193. package/dist/components/charts/PartoAreaChart.cjs.map +0 -1
  194. package/dist/components/charts/PartoAreaChart.js.map +0 -1
  195. package/dist/components/charts/PartoBarChart.cjs.map +0 -1
  196. package/dist/components/charts/PartoBarChart.js.map +0 -1
  197. package/dist/components/charts/PartoLineChart.cjs.map +0 -1
  198. package/dist/components/charts/PartoLineChart.js.map +0 -1
  199. package/dist/components/charts/PartoPieChart.cjs.map +0 -1
  200. package/dist/components/charts/PartoPieChart.js.map +0 -1
  201. package/dist/components/ui/accordion.cjs.map +0 -1
  202. package/dist/components/ui/accordion.js.map +0 -1
  203. package/dist/components/ui/alert-dialog.cjs.map +0 -1
  204. package/dist/components/ui/alert-dialog.js.map +0 -1
  205. package/dist/components/ui/alert-rule-card.cjs.map +0 -1
  206. package/dist/components/ui/alert-rule-card.js.map +0 -1
  207. package/dist/components/ui/alert.cjs.map +0 -1
  208. package/dist/components/ui/alert.js.map +0 -1
  209. package/dist/components/ui/app-bar.cjs.map +0 -1
  210. package/dist/components/ui/app-bar.js.map +0 -1
  211. package/dist/components/ui/avatar.cjs.map +0 -1
  212. package/dist/components/ui/avatar.js.map +0 -1
  213. package/dist/components/ui/badge.cjs.map +0 -1
  214. package/dist/components/ui/badge.js.map +0 -1
  215. package/dist/components/ui/breadcrumb.cjs.map +0 -1
  216. package/dist/components/ui/breadcrumb.js.map +0 -1
  217. package/dist/components/ui/button.cjs.map +0 -1
  218. package/dist/components/ui/button.js.map +0 -1
  219. package/dist/components/ui/calendar.cjs.map +0 -1
  220. package/dist/components/ui/calendar.js.map +0 -1
  221. package/dist/components/ui/card.cjs.map +0 -1
  222. package/dist/components/ui/card.js.map +0 -1
  223. package/dist/components/ui/checkbox.cjs.map +0 -1
  224. package/dist/components/ui/checkbox.js.map +0 -1
  225. package/dist/components/ui/concept-card.cjs.map +0 -1
  226. package/dist/components/ui/concept-card.js.map +0 -1
  227. package/dist/components/ui/data-table.cjs.map +0 -1
  228. package/dist/components/ui/data-table.js.map +0 -1
  229. package/dist/components/ui/dialog.cjs.map +0 -1
  230. package/dist/components/ui/dialog.js.map +0 -1
  231. package/dist/components/ui/dropdown-menu.cjs.map +0 -1
  232. package/dist/components/ui/dropdown-menu.js.map +0 -1
  233. package/dist/components/ui/filter-provider.cjs.map +0 -1
  234. package/dist/components/ui/filter-provider.js.map +0 -1
  235. package/dist/components/ui/form.cjs.map +0 -1
  236. package/dist/components/ui/form.js.map +0 -1
  237. package/dist/components/ui/input.cjs.map +0 -1
  238. package/dist/components/ui/input.js.map +0 -1
  239. package/dist/components/ui/iran-province-heat.cjs.map +0 -1
  240. package/dist/components/ui/iran-province-heat.js.map +0 -1
  241. package/dist/components/ui/label.cjs.map +0 -1
  242. package/dist/components/ui/label.js.map +0 -1
  243. package/dist/components/ui/page-card.cjs.map +0 -1
  244. package/dist/components/ui/page-card.js.map +0 -1
  245. package/dist/components/ui/page-header.cjs.map +0 -1
  246. package/dist/components/ui/page-header.js.map +0 -1
  247. package/dist/components/ui/password-input.cjs.map +0 -1
  248. package/dist/components/ui/password-input.js.map +0 -1
  249. package/dist/components/ui/popover.cjs.map +0 -1
  250. package/dist/components/ui/popover.js.map +0 -1
  251. package/dist/components/ui/progress.cjs.map +0 -1
  252. package/dist/components/ui/progress.js.map +0 -1
  253. package/dist/components/ui/radio-card.cjs.map +0 -1
  254. package/dist/components/ui/radio-card.js.map +0 -1
  255. package/dist/components/ui/radio-group.cjs.map +0 -1
  256. package/dist/components/ui/radio-group.js.map +0 -1
  257. package/dist/components/ui/saved-query-card.cjs.map +0 -1
  258. package/dist/components/ui/saved-query-card.js.map +0 -1
  259. package/dist/components/ui/scroll-area.cjs.map +0 -1
  260. package/dist/components/ui/scroll-area.js.map +0 -1
  261. package/dist/components/ui/select.cjs.map +0 -1
  262. package/dist/components/ui/select.js.map +0 -1
  263. package/dist/components/ui/separator.cjs.map +0 -1
  264. package/dist/components/ui/separator.js.map +0 -1
  265. package/dist/components/ui/sheet.cjs.map +0 -1
  266. package/dist/components/ui/sheet.js.map +0 -1
  267. package/dist/components/ui/skeleton.cjs.map +0 -1
  268. package/dist/components/ui/skeleton.js.map +0 -1
  269. package/dist/components/ui/slider.cjs.map +0 -1
  270. package/dist/components/ui/slider.js.map +0 -1
  271. package/dist/components/ui/social-platform-badge.cjs.map +0 -1
  272. package/dist/components/ui/social-platform-badge.js.map +0 -1
  273. package/dist/components/ui/sonner.cjs.map +0 -1
  274. package/dist/components/ui/sonner.js.map +0 -1
  275. package/dist/components/ui/sparkline.cjs.map +0 -1
  276. package/dist/components/ui/sparkline.js.map +0 -1
  277. package/dist/components/ui/switch.cjs.map +0 -1
  278. package/dist/components/ui/switch.js.map +0 -1
  279. package/dist/components/ui/table.cjs.map +0 -1
  280. package/dist/components/ui/table.js.map +0 -1
  281. package/dist/components/ui/tabs.cjs.map +0 -1
  282. package/dist/components/ui/tabs.js.map +0 -1
  283. package/dist/components/ui/textarea.cjs.map +0 -1
  284. package/dist/components/ui/textarea.js.map +0 -1
  285. package/dist/components/ui/toggle-group.cjs.map +0 -1
  286. package/dist/components/ui/toggle-group.js.map +0 -1
  287. package/dist/components/ui/tooltip.cjs.map +0 -1
  288. package/dist/components/ui/tooltip.js.map +0 -1
  289. package/dist/hooks/use-hotkey-registry.cjs.map +0 -1
  290. package/dist/hooks/use-hotkey-registry.js.map +0 -1
  291. package/dist/hooks/use-hotkeys.cjs.map +0 -1
  292. package/dist/hooks/use-hotkeys.js.map +0 -1
  293. package/dist/index.cjs.map +0 -1
  294. package/dist/index.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../src/lib/utils.ts","../../../src/lib/i18n.ts","../../../src/components/ui/page-header.tsx"],"names":["twMerge","clsx","React","jsxs","jsx","ArrowRight"],"mappings":"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","file":"page-header.cjs","sourcesContent":["import { type ClassValue, clsx } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\nimport { formatJalaliDate } from '@/lib/jalali-utils'\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n\nexport type SupportedLocale = 'fa' | 'ar' | 'en'\n\n/**\n * Convert digits in a string to Persian/Arabic numerals based on locale.\n * @example convertToLocalNumbers('123', 'fa') => '۱۲۳'\n * @example convertToLocalNumbers('123', 'en') => '123'\n */\nexport function convertToLocalNumbers(text: string | number, locale: SupportedLocale): string {\n if (locale === 'fa' || locale === 'ar') {\n const persianDigits = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']\n return String(text).replace(/\\d/g, (digit) => persianDigits[parseInt(digit)])\n }\n return String(text)\n}\n\n/**\n * Format large numbers with locale-aware suffixes (K/M/B).\n * @example formatLargeNumber(1500, 'fa') => '۱.۵ هزار'\n * @example formatLargeNumber(1500, 'en') => '1.5K'\n */\nexport function formatLargeNumber(num: number, locale: SupportedLocale): string {\n if (num >= 1_000_000_000) {\n const formatted = (num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'B' : ' میلیارد')\n }\n if (num >= 1_000_000) {\n const formatted = (num / 1_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'M' : ' میلیون')\n }\n if (num >= 1_000) {\n const formatted = (num / 1_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'K' : ' هزار')\n }\n return convertToLocalNumbers(num.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ','), locale)\n}\n\n/**\n * Format number to Instagram-style short format (English only).\n * @example formatNumber(123456, 'short') => '123K'\n * @example formatNumber(123456, 'exact') => '123,456'\n */\nexport function formatNumber(num: number | undefined, format: 'exact' | 'short' = 'exact'): string {\n if (num === undefined || num === null) return '0'\n\n if (format === 'exact') {\n return num.toLocaleString('en-US')\n }\n\n // Short format (Instagram style)\n if (num >= 1_000_000_000) {\n return `${(num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')}B`\n }\n if (num >= 1_000_000) {\n return `${(num / 1_000_000).toFixed(1).replace(/\\.0$/, '')}M`\n }\n if (num >= 1_000) {\n return `${(num / 1_000).toFixed(1).replace(/\\.0$/, '')}K`\n }\n return num.toString()\n}\n\n/**\n * Format date to relative time with absolute on hover (Persian)\n * @example formatRelativeTime(new Date()) => '۲ ساعت پیش'\n */\nexport function formatRelativeTime(date: Date | string | number): string {\n const now = new Date()\n const then = new Date(date)\n const diffInSeconds = Math.floor((now.getTime() - then.getTime()) / 1000)\n\n if (diffInSeconds < 60) {\n return 'همین الان'\n }\n\n const diffInMinutes = Math.floor(diffInSeconds / 60)\n if (diffInMinutes < 60) {\n return `${convertToLocalNumbers(diffInMinutes, 'fa')} دقیقه پیش`\n }\n\n const diffInHours = Math.floor(diffInMinutes / 60)\n if (diffInHours < 24) {\n return `${convertToLocalNumbers(diffInHours, 'fa')} ساعت پیش`\n }\n\n const diffInDays = Math.floor(diffInHours / 24)\n if (diffInDays < 7) {\n return `${convertToLocalNumbers(diffInDays, 'fa')} روز پیش`\n }\n\n const diffInWeeks = Math.floor(diffInDays / 7)\n if (diffInWeeks < 4) {\n return `${convertToLocalNumbers(diffInWeeks, 'fa')} هفته پیش`\n }\n\n const diffInMonths = Math.floor(diffInDays / 30)\n if (diffInMonths < 12) {\n return `${convertToLocalNumbers(diffInMonths, 'fa')} ماه پیش`\n }\n\n const diffInYears = Math.floor(diffInDays / 365)\n return `${convertToLocalNumbers(diffInYears, 'fa')} سال پیش`\n}\n\n/**\n * Format date to absolute format (Persian / Jalali)\n * Uses date-fns-jalali for accurate Jalali conversion.\n * @example formatAbsoluteTime(new Date()) => '۱۵ دی ۱۴۰۳، ۱۵:۳۰'\n */\nexport function formatAbsoluteTime(date: Date | string | number): string {\n const d = new Date(date)\n return formatJalaliDate(d, 'd MMMM yyyy، HH:mm')\n}\n","import type { SupportedLocale } from './utils'\n\n// Re-export SupportedLocale as Locale for public API convenience.\n// SupportedLocale is the canonical type; Locale is an alias.\nexport type Locale = SupportedLocale\n\n/**\n * Returns true for RTL locales (fa, ar).\n */\nexport function isRTL(locale: SupportedLocale): boolean {\n return locale === 'fa' || locale === 'ar'\n}\n\n// NOTE: TIER_LABELS and GROUP_LABELS already live in engagement-utils.ts.\n// This file only houses UI-layer translations that are not part of the domain model.\n\n/**\n * UI string translations used by the EngagementRate component.\n * Centralised here to avoid duplication between components.\n */\nexport const engagementUiTranslations: Record<\n SupportedLocale,\n {\n yourCategory: string\n influencer: string\n followers: string\n over: string\n to: string\n and: string\n lessThan: string\n you: string\n criteria: string\n }\n> = {\n fa: {\n yourCategory: 'دسته شما:',\n influencer: 'اینفلوئنسر',\n followers: 'فالوور',\n over: 'بیش از',\n to: 'تا',\n and: 'و بالاتر',\n lessThan: 'کمتر از',\n you: 'شما',\n criteria: 'معیارهای دسته',\n },\n ar: {\n yourCategory: 'فئتك:',\n influencer: 'مؤثر',\n followers: 'متابع',\n over: 'أكثر من',\n to: 'إلى',\n and: 'وأعلى',\n lessThan: 'أقل من',\n you: 'أنت',\n criteria: 'معايير فئة المؤثر',\n },\n en: {\n yourCategory: 'Your Category:',\n influencer: 'Influencer',\n followers: 'followers',\n over: 'Over',\n to: 'to',\n and: 'and above',\n lessThan: 'Less than',\n you: 'You',\n criteria: 'Category Criteria',\n },\n}\n\n/**\n * Sentiment label translations used by the SentimentBadge component.\n */\nexport const sentimentLabels: Record<\n SupportedLocale,\n {\n positive: string\n negative: string\n neutral: string\n mixed: string\n }\n> = {\n fa: { positive: 'مثبت', negative: 'منفی', neutral: 'خنثی', mixed: 'ترکیبی' },\n ar: { positive: 'إيجابي', negative: 'سلبي', neutral: 'محايد', mixed: 'مختلط' },\n en: { positive: 'Positive', negative: 'Negative', neutral: 'Neutral', mixed: 'Mixed' },\n}\n\n/**\n * Canonical emotion keys — 9-class advanced sentiment scale used in افکارسنجی\n * deep analysis. Coexists with the simple 3-class `Sentiment` above; they are\n * NOT interchangeable. Choose the scale based on view density.\n */\nexport type EmotionKey =\n | 'anger'\n | 'anticipation'\n | 'joy'\n | 'trust'\n | 'fear'\n | 'surprise'\n | 'sadness'\n | 'disgust'\n | 'neutral'\n\nexport const EMOTION_KEYS: readonly EmotionKey[] = [\n 'anger',\n 'anticipation',\n 'joy',\n 'trust',\n 'fear',\n 'surprise',\n 'sadness',\n 'disgust',\n 'neutral',\n] as const\n\n/** Emotion label translations used by EmotionBadge / EmotionDistribution. */\nexport const emotionLabels: Record<SupportedLocale, Record<EmotionKey, string>> = {\n fa: {\n anger: 'خشم',\n anticipation: 'انتظار',\n joy: 'شادی',\n trust: 'اعتماد',\n fear: 'ترس',\n surprise: 'شگفتی',\n sadness: 'غم',\n disgust: 'انزجار',\n neutral: 'خنثی',\n },\n ar: {\n anger: 'غضب',\n anticipation: 'ترقّب',\n joy: 'فرح',\n trust: 'ثقة',\n fear: 'خوف',\n surprise: 'دهشة',\n sadness: 'حزن',\n disgust: 'اشمئزاز',\n neutral: 'محايد',\n },\n en: {\n anger: 'Anger',\n anticipation: 'Anticipation',\n joy: 'Joy',\n trust: 'Trust',\n fear: 'Fear',\n surprise: 'Surprise',\n sadness: 'Sadness',\n disgust: 'Disgust',\n neutral: 'Neutral',\n },\n}\n\n/**\n * Stance keys — 5-class audience-stance classification used in clustering.\n * A concept / post / page belongs to exactly one primary stance, but usually\n * also has a secondary composition expressed as percentages. Keys are kept\n * short and historical; display labels are framed as audience stance toward\n * a tracked topic/brand (supporter ↔ detractor + undecided).\n */\nexport type FlowKey = 'pro-gov' | 'internal-critic' | 'internal-opponent' | 'external-opponent' | 'grey'\n\nexport const FLOW_KEYS: readonly FlowKey[] = [\n 'pro-gov',\n 'internal-critic',\n 'internal-opponent',\n 'external-opponent',\n 'grey',\n] as const\n\nexport const flowLabels: Record<SupportedLocale, Record<FlowKey, string>> = {\n fa: {\n 'pro-gov': 'حامی',\n 'internal-critic': 'منتقد سازنده',\n 'internal-opponent': 'مخالف',\n 'external-opponent': 'مخالف جدی',\n grey: 'خنثی',\n },\n ar: {\n 'pro-gov': 'مؤيد',\n 'internal-critic': 'منتقد بنّاء',\n 'internal-opponent': 'معارض',\n 'external-opponent': 'معارض شديد',\n grey: 'محايد',\n },\n en: {\n 'pro-gov': 'Supporter',\n 'internal-critic': 'Constructive Critic',\n 'internal-opponent': 'Detractor',\n 'external-opponent': 'Strong Detractor',\n grey: 'Undecided',\n },\n}\n\n/** Operational status — visual state of a tracked entity. */\nexport type StatusKey = 'critical' | 'warning' | 'normal'\n\nexport const statusLabels: Record<SupportedLocale, Record<StatusKey, string>> = {\n fa: { critical: 'بحرانی', warning: 'هشدار', normal: 'عادی' },\n ar: { critical: 'حرج', warning: 'تحذير', normal: 'طبيعي' },\n en: { critical: 'Critical', warning: 'Warning', normal: 'Normal' },\n}\n\n/** Severity — urgency level. Independent from status. */\nexport type SeverityKey = 'urgent' | 'high' | 'medium' | 'low'\n\nexport const severityLabels: Record<SupportedLocale, Record<SeverityKey, string>> = {\n fa: { urgent: 'فوری', high: 'بالا', medium: 'متوسط', low: 'پایین' },\n ar: { urgent: 'عاجل', high: 'مرتفع', medium: 'متوسط', low: 'منخفض' },\n en: { urgent: 'Urgent', high: 'High', medium: 'Medium', low: 'Low' },\n}\n\n/**\n * Job lifecycle — canonical states for async work (analyses, campaigns,\n * evaluation runs, imports, exports, report generation).\n *\n * Terminal states: completed / failed / cancelled.\n * Active states: queued / running / paused.\n */\nexport type JobStatusKey = 'queued' | 'running' | 'paused' | 'completed' | 'failed' | 'cancelled'\n\nexport const JOB_STATUS_KEYS: readonly JobStatusKey[] = [\n 'queued',\n 'running',\n 'paused',\n 'completed',\n 'failed',\n 'cancelled',\n] as const\n\nexport const jobStatusLabels: Record<SupportedLocale, Record<JobStatusKey, string>> = {\n fa: {\n queued: 'در صف',\n running: 'در حال اجرا',\n paused: 'متوقف',\n completed: 'تکمیل شد',\n failed: 'ناموفق',\n cancelled: 'لغو شد',\n },\n ar: {\n queued: 'في الطابور',\n running: 'قيد التنفيذ',\n paused: 'متوقف',\n completed: 'مكتمل',\n failed: 'فشل',\n cancelled: 'ملغى',\n },\n en: {\n queued: 'Queued',\n running: 'Running',\n paused: 'Paused',\n completed: 'Completed',\n failed: 'Failed',\n cancelled: 'Cancelled',\n },\n}\n\n/** True iff a job is still active (not in a terminal state). */\nexport function isActiveJobStatus(s: JobStatusKey): boolean {\n return s === 'queued' || s === 'running' || s === 'paused'\n}\n\n/**\n * Action status — discrete outcome for a single completed (or in-flight)\n * action in an activity feed / audit timeline. Distinct from the richer\n * job/stage lifecycles above: each event is atomic, so no \"paused\" here.\n */\nexport type ActionStatusKey = 'success' | 'failed' | 'pending' | 'warning' | 'info'\n\nexport const ACTION_STATUS_KEYS: readonly ActionStatusKey[] = [\n 'success',\n 'failed',\n 'pending',\n 'warning',\n 'info',\n] as const\n\nexport const actionStatusLabels: Record<SupportedLocale, Record<ActionStatusKey, string>> = {\n fa: {\n success: 'موفق',\n failed: 'ناموفق',\n pending: 'در حال انجام',\n warning: 'هشدار',\n info: 'اطلاع',\n },\n ar: {\n success: 'ناجح',\n failed: 'فشل',\n pending: 'قيد التنفيذ',\n warning: 'تحذير',\n info: 'معلومات',\n },\n en: {\n success: 'Success',\n failed: 'Failed',\n pending: 'Pending',\n warning: 'Warning',\n info: 'Info',\n },\n}\n\n/**\n * Locale-aware relative time label producer. Used by ActionTimeline and any\n * other component that wants trilingual \"n minutes ago\" strings. For absolute\n * formatting see `formatAbsoluteTime`/`formatJalaliDate` in lib/utils.ts.\n */\nexport function formatRelativeLocaleTime(date: Date | string | number, locale: SupportedLocale): string {\n const then = new Date(date).getTime()\n const now = Date.now()\n const diffSec = Math.floor((now - then) / 1000)\n\n const fmt = (n: number) => (locale === 'en' ? String(n) : n.toLocaleString(locale === 'ar' ? 'ar-EG' : 'fa-IR'))\n\n if (diffSec < 0) {\n // Future time — rare in feeds. Fall through to absolute below.\n return new Date(then).toISOString()\n }\n if (diffSec < 60) {\n if (locale === 'fa') return 'همین الان'\n if (locale === 'ar') return 'الآن'\n return 'just now'\n }\n const diffMin = Math.floor(diffSec / 60)\n if (diffMin < 60) {\n if (locale === 'fa') return `${fmt(diffMin)} دقیقه پیش`\n if (locale === 'ar') return `قبل ${fmt(diffMin)} دقيقة`\n return `${diffMin}m ago`\n }\n const diffHr = Math.floor(diffMin / 60)\n if (diffHr < 24) {\n if (locale === 'fa') return `${fmt(diffHr)} ساعت پیش`\n if (locale === 'ar') return `قبل ${fmt(diffHr)} ساعة`\n return `${diffHr}h ago`\n }\n const diffDay = Math.floor(diffHr / 24)\n if (diffDay < 7) {\n if (locale === 'fa') return `${fmt(diffDay)} روز پیش`\n if (locale === 'ar') return `قبل ${fmt(diffDay)} يوم`\n return `${diffDay}d ago`\n }\n const diffWk = Math.floor(diffDay / 7)\n if (diffWk < 4) {\n if (locale === 'fa') return `${fmt(diffWk)} هفته پیش`\n if (locale === 'ar') return `قبل ${fmt(diffWk)} أسبوع`\n return `${diffWk}w ago`\n }\n const diffMon = Math.floor(diffDay / 30)\n if (diffMon < 12) {\n if (locale === 'fa') return `${fmt(diffMon)} ماه پیش`\n if (locale === 'ar') return `قبل ${fmt(diffMon)} شهر`\n return `${diffMon}mo ago`\n }\n const diffYr = Math.floor(diffDay / 365)\n if (locale === 'fa') return `${fmt(diffYr)} سال پیش`\n if (locale === 'ar') return `قبل ${fmt(diffYr)} سنة`\n return `${diffYr}y ago`\n}\n\n/**\n * Locale-aware \"time remaining\" label — forward-looking companion to\n * `formatRelativeLocaleTime`. Used by RateLimitBanner and any countdown UI.\n *\n * Input can be a Date, ISO string, or epoch ms. Past/zero remaining returns\n * the locale-specific \"now\" / \"any moment\" marker.\n */\nexport function formatTimeRemaining(target: Date | string | number, locale: SupportedLocale): string {\n const then = new Date(target).getTime()\n const now = Date.now()\n const diffSec = Math.max(0, Math.floor((then - now) / 1000))\n\n const fmt = (n: number) => (locale === 'en' ? String(n) : n.toLocaleString(locale === 'ar' ? 'ar-EG' : 'fa-IR'))\n\n if (diffSec <= 0) {\n if (locale === 'fa') return 'هم‌اکنون'\n if (locale === 'ar') return 'الآن'\n return 'any moment'\n }\n if (diffSec < 60) {\n if (locale === 'fa') return `${fmt(diffSec)} ثانیه دیگر`\n if (locale === 'ar') return `بعد ${fmt(diffSec)} ثانية`\n return `in ${diffSec}s`\n }\n const totalMin = Math.floor(diffSec / 60)\n if (totalMin < 60) {\n if (locale === 'fa') return `${fmt(totalMin)} دقیقه دیگر`\n if (locale === 'ar') return `بعد ${fmt(totalMin)} دقيقة`\n return `in ${totalMin}m`\n }\n const hours = Math.floor(totalMin / 60)\n const mins = totalMin % 60\n if (hours < 24) {\n if (mins === 0) {\n if (locale === 'fa') return `${fmt(hours)} ساعت دیگر`\n if (locale === 'ar') return `بعد ${fmt(hours)} ساعة`\n return `in ${hours}h`\n }\n if (locale === 'fa') return `${fmt(hours)} ساعت و ${fmt(mins)} دقیقه دیگر`\n if (locale === 'ar') return `بعد ${fmt(hours)} ساعة و${fmt(mins)} دقيقة`\n return `in ${hours}h ${mins}m`\n }\n const days = Math.floor(hours / 24)\n const remHours = hours % 24\n if (remHours === 0) {\n if (locale === 'fa') return `${fmt(days)} روز دیگر`\n if (locale === 'ar') return `بعد ${fmt(days)} يوم`\n return `in ${days}d`\n }\n if (locale === 'fa') return `${fmt(days)} روز و ${fmt(remHours)} ساعت دیگر`\n if (locale === 'ar') return `بعد ${fmt(days)} يوم و${fmt(remHours)} ساعة`\n return `in ${days}d ${remHours}h`\n}\n\n/**\n * Stage status — for multi-stage pipeline visualizations (StatusFlow).\n *\n * Distinct from `JobStatusKey`: a job has ONE status; a pipeline of stages has\n * a status PER stage. A `running` stage means that stage is currently active;\n * a `warning` stage completed but with caveats; `skipped` means it was bypassed.\n */\nexport type StageStatusKey = 'pending' | 'running' | 'completed' | 'failed' | 'skipped' | 'warning' | 'paused'\n\nexport const STAGE_STATUS_KEYS: readonly StageStatusKey[] = [\n 'pending',\n 'running',\n 'completed',\n 'failed',\n 'skipped',\n 'warning',\n 'paused',\n] as const\n\nexport const stageStatusLabels: Record<SupportedLocale, Record<StageStatusKey, string>> = {\n fa: {\n pending: 'در انتظار',\n running: 'در حال اجرا',\n completed: 'تکمیل شد',\n failed: 'ناموفق',\n skipped: 'رد شد',\n warning: 'با هشدار',\n paused: 'متوقف',\n },\n ar: {\n pending: 'في الانتظار',\n running: 'قيد التنفيذ',\n completed: 'مكتمل',\n failed: 'فشل',\n skipped: 'تم تخطيه',\n warning: 'مع تحذير',\n paused: 'متوقف',\n },\n en: {\n pending: 'Pending',\n running: 'Running',\n completed: 'Completed',\n failed: 'Failed',\n skipped: 'Skipped',\n warning: 'Warning',\n paused: 'Paused',\n },\n}\n\n/**\n * Action type — canonical set of social-media actions a worker/user can\n * execute. Maps 1:1 to a CSS token family (`--action-type-*`) so each action\n * has a distinct recognizable colour + icon across activity feeds, audit\n * timelines, and rate-limit dashboards.\n *\n * The 7 canonical keys cover current افکارسنجی + Booster + future SRR use:\n * - `like` / `comment` / `save` / `follow` / `unfollow` — Booster OPERATION_TYPES\n * - `dm` — direct message (future bulk-DM, notification replies)\n * - `share` — cross-post / retweet (future multi-platform)\n */\nexport type ActionTypeKey = 'like' | 'comment' | 'save' | 'follow' | 'unfollow' | 'dm' | 'share'\n\nexport const ACTION_TYPE_KEYS: readonly ActionTypeKey[] = [\n 'like',\n 'comment',\n 'save',\n 'follow',\n 'unfollow',\n 'dm',\n 'share',\n] as const\n\nexport const actionTypeLabels: Record<SupportedLocale, Record<ActionTypeKey, string>> = {\n fa: {\n like: 'پسندیدن',\n comment: 'نظر',\n save: 'ذخیره',\n follow: 'دنبال کردن',\n unfollow: 'لغو دنبال',\n dm: 'پیام مستقیم',\n share: 'اشتراک‌گذاری',\n },\n ar: {\n like: 'إعجاب',\n comment: 'تعليق',\n save: 'حفظ',\n follow: 'متابعة',\n unfollow: 'إلغاء المتابعة',\n dm: 'رسالة مباشرة',\n share: 'مشاركة',\n },\n en: {\n like: 'Like',\n comment: 'Comment',\n save: 'Save',\n follow: 'Follow',\n unfollow: 'Unfollow',\n dm: 'Direct Message',\n share: 'Share',\n },\n}\n\n/**\n * Short-form verb labels for past-tense activity phrasing (\"worker X پسندید Y\").\n * When composing \"subject action object\" sentences in ActivityFeed / ActionTimeline,\n * prefer these over `actionTypeLabels` which are noun-form.\n */\nexport const actionTypeVerbs: Record<SupportedLocale, Record<ActionTypeKey, string>> = {\n fa: {\n like: 'پسندید',\n comment: 'نظر گذاشت',\n save: 'ذخیره کرد',\n follow: 'دنبال کرد',\n unfollow: 'لغو دنبال کرد',\n dm: 'پیام داد',\n share: 'اشتراک گذاشت',\n },\n ar: {\n like: 'أعجب',\n comment: 'علّق على',\n save: 'حفظ',\n follow: 'تابع',\n unfollow: 'ألغى متابعة',\n dm: 'راسل',\n share: 'شارك',\n },\n en: {\n like: 'liked',\n comment: 'commented on',\n save: 'saved',\n follow: 'followed',\n unfollow: 'unfollowed',\n dm: 'DMd',\n share: 'shared',\n },\n}\n\n/**\n * Entity health — 6-tier severity narrative for long-lived entities like\n * workers, tracked pages, data sources, monitored feeds. Ordered most-critical\n * to most-positive so components can sort \"worst first\" deterministically.\n *\n * `banned` — entity is permanently disabled / blocked upstream.\n * `pending` — not yet evaluated (new, awaiting verification, in setup).\n * `at-risk` — immediate intervention needed (failed checks, violations).\n * `warning` — degrading signals, needs monitoring.\n * `growing` — improving trajectory but not yet healthy.\n * `healthy` — stable, all checks pass.\n *\n * Distinct from `StatusKey` (critical/warning/normal — short-lived operational)\n * and `SeverityKey` (urgent/high/medium/low — urgency of an action item).\n */\nexport type EntityHealthKey = 'banned' | 'pending' | 'at-risk' | 'warning' | 'growing' | 'healthy'\n\nexport const ENTITY_HEALTH_KEYS: readonly EntityHealthKey[] = [\n 'banned',\n 'pending',\n 'at-risk',\n 'warning',\n 'growing',\n 'healthy',\n] as const\n\nexport const entityHealthLabels: Record<SupportedLocale, Record<EntityHealthKey, string>> = {\n fa: {\n banned: 'مسدود',\n pending: 'در انتظار بررسی',\n 'at-risk': 'در خطر',\n warning: 'هشدار',\n growing: 'در حال بهبود',\n healthy: 'سالم',\n },\n ar: {\n banned: 'محظور',\n pending: 'قيد الانتظار',\n 'at-risk': 'في خطر',\n warning: 'تحذير',\n growing: 'ينمو',\n healthy: 'صحي',\n },\n en: {\n banned: 'Banned',\n pending: 'Pending',\n 'at-risk': 'At Risk',\n warning: 'Warning',\n growing: 'Growing',\n healthy: 'Healthy',\n },\n}\n\n/**\n * Priority rank of each entity health state — higher number = more severe.\n * Use to sort entity lists \"worst first\" without ad-hoc comparisons.\n */\nexport const entityHealthPriority: Record<EntityHealthKey, number> = {\n banned: 5,\n 'at-risk': 4,\n warning: 3,\n pending: 2,\n growing: 1,\n healthy: 0,\n}\n\n/** True iff the entity needs immediate human attention. */\nexport function isCriticalEntityHealth(s: EntityHealthKey): boolean {\n return s === 'banned' || s === 'at-risk'\n}\n\n/**\n * Default UI strings for components that need localized text.\n * Persian (fa) is the default locale for backward compatibility.\n */\nexport const UI_STRINGS = {\n fa: {\n // Autocomplete\n noResults: 'نتیجه‌ای یافت نشد',\n suggestions: 'پیشنهادها',\n\n // MultiSelect\n select: 'انتخاب کنید',\n noOptionsFound: 'موردی یافت نشد',\n search: 'جستجو...',\n clearAll: 'پاک کردن همه',\n\n // DatePicker / DateRangePicker\n selectDate: 'انتخاب تاریخ',\n selectDateRange: 'انتخاب بازه تاریخ',\n\n // ErrorState\n errorLoadingData: 'خطا در بارگذاری داده‌ها',\n retry: 'تلاش مجدد',\n\n // FilterChip\n remove: 'حذف',\n\n // UserAutocomplete\n noUsersFound: 'کاربری یافت نشد',\n\n // Pagination\n goToPreviousPage: 'رفتن به صفحه قبلی',\n goToNextPage: 'رفتن به صفحه بعدی',\n previous: 'قبلی',\n next: 'بعدی',\n\n // ProfileInfo\n actions: 'عملیات',\n\n // CopyButton\n copy: 'کپی',\n copied: 'کپی شد',\n\n // ConfirmDialog\n confirm: 'تایید',\n cancel: 'انصراف',\n areYouSure: 'آیا مطمئن هستید؟',\n\n // Stepper\n stepperLabel: 'مراحل',\n stepCompleted: 'تکمیل شده',\n stepCurrent: 'مرحله فعلی',\n stepPending: 'در انتظار',\n\n // FilterBar / FilterPanel\n clearFilters: 'پاک کردن فیلترها',\n activeFilters: 'فیلترهای فعال',\n filters: 'فیلترها',\n applyFilters: 'اعمال',\n resetFilters: 'پیش‌فرض',\n noFiltersActive: 'فیلتر فعالی نیست',\n clearSection: 'پاک کردن این بخش',\n\n // PageHeader\n goBack: 'بازگشت',\n\n // ViewToggle\n gridView: 'نمای شبکه‌ای',\n listView: 'نمای لیستی',\n\n // PageLoader\n loading: 'در حال بارگذاری...',\n\n // ErrorBoundary\n unexpectedError: 'خطای غیرمنتظره‌ای رخ داد',\n\n // DataTable\n selectAll: 'انتخاب همه',\n selectRow: 'انتخاب ردیف',\n noDataToDisplay: 'داده‌ای برای نمایش وجود ندارد',\n\n // InstagramPost\n likesCount: 'تعداد لایک',\n commentsCount: 'تعداد کامنت',\n viewsCount: 'تعداد بازدید',\n sharesCount: 'تعداد اشتراک',\n postTypeLabel: 'نوع پست',\n postTypePhoto: 'عکس',\n postTypeVideo: 'ویدیو',\n postTypeCarousel: 'اسلاید',\n showMore: 'بیشتر',\n showLess: 'کمتر',\n noMediaAvailable: 'رسانه‌ای موجود نیست',\n commentAnalysis: 'تحلیل کامنت‌ها',\n commentAnalysisDisabled: 'تحلیل کامنت‌ها (غیرفعال)',\n booster: 'بوستر',\n boosterDisabled: 'بوستر (غیرفعال)',\n aiAnalysis: 'تحلیل هوش مصنوعی',\n aiAnalysisDisabled: 'تحلیل هوش مصنوعی (غیرفعال)',\n openOnInstagram: 'باز کردن در اینستاگرام',\n openOnInstagramDisabled: 'باز کردن در اینستاگرام (غیرفعال)',\n },\n ar: {\n noResults: 'لم يتم العثور على نتائج',\n suggestions: 'اقتراحات',\n select: 'اختر',\n noOptionsFound: 'لم يتم العثور على خيارات',\n search: 'بحث...',\n clearAll: 'مسح الكل',\n selectDate: 'اختر تاريخ',\n selectDateRange: 'اختر نطاق التاريخ',\n errorLoadingData: 'خطأ في تحميل البيانات',\n retry: 'إعادة المحاولة',\n remove: 'حذف',\n noUsersFound: 'لم يتم العثور على مستخدمين',\n goToPreviousPage: 'الانتقال إلى الصفحة السابقة',\n goToNextPage: 'الانتقال إلى الصفحة التالية',\n previous: 'السابق',\n next: 'التالي',\n actions: 'إجراءات',\n\n // CopyButton\n copy: 'نسخ',\n copied: 'تم النسخ',\n\n // ConfirmDialog\n confirm: 'تأكيد',\n cancel: 'إلغاء',\n areYouSure: 'هل أنت متأكد؟',\n\n // Stepper\n stepperLabel: 'الخطوات',\n stepCompleted: 'مكتمل',\n stepCurrent: 'الخطوة الحالية',\n stepPending: 'قيد الانتظار',\n\n // FilterBar / FilterPanel\n clearFilters: 'مسح الفلاتر',\n activeFilters: 'الفلاتر النشطة',\n filters: 'الفلاتر',\n applyFilters: 'تطبيق',\n resetFilters: 'الافتراضي',\n noFiltersActive: 'لا توجد فلاتر نشطة',\n clearSection: 'مسح هذا القسم',\n\n // PageHeader\n goBack: 'رجوع',\n\n // ViewToggle\n gridView: 'عرض شبكي',\n listView: 'عرض قائمة',\n\n // PageLoader\n loading: 'جارٍ التحميل...',\n\n // ErrorBoundary\n unexpectedError: 'حدث خطأ غير متوقع',\n\n // DataTable\n selectAll: 'تحديد الكل',\n selectRow: 'تحديد الصف',\n noDataToDisplay: 'لا توجد بيانات للعرض',\n\n // InstagramPost\n likesCount: 'عدد الإعجابات',\n commentsCount: 'عدد التعليقات',\n viewsCount: 'عدد المشاهدات',\n sharesCount: 'عدد المشاركات',\n postTypeLabel: 'نوع المنشور',\n postTypePhoto: 'صورة',\n postTypeVideo: 'فيديو',\n postTypeCarousel: 'شرائح',\n showMore: 'المزيد',\n showLess: 'أقل',\n noMediaAvailable: 'لا توجد وسائط',\n commentAnalysis: 'تحليل التعليقات',\n commentAnalysisDisabled: 'تحليل التعليقات (معطل)',\n booster: 'تعزيز',\n boosterDisabled: 'تعزيز (معطل)',\n aiAnalysis: 'تحليل الذكاء الاصطناعي',\n aiAnalysisDisabled: 'تحليل الذكاء الاصطناعي (معطل)',\n openOnInstagram: 'فتح في إنستاجرام',\n openOnInstagramDisabled: 'فتح في إنستاجرام (معطل)',\n },\n en: {\n noResults: 'No results found',\n suggestions: 'Suggestions',\n select: 'Select',\n noOptionsFound: 'No options found',\n search: 'Search...',\n clearAll: 'Clear all',\n selectDate: 'Select date',\n selectDateRange: 'Select date range',\n errorLoadingData: 'Error loading data',\n retry: 'Retry',\n remove: 'Remove',\n noUsersFound: 'No users found',\n goToPreviousPage: 'Go to previous page',\n goToNextPage: 'Go to next page',\n previous: 'Previous',\n next: 'Next',\n actions: 'Actions',\n\n // CopyButton\n copy: 'Copy',\n copied: 'Copied',\n\n // ConfirmDialog\n confirm: 'Confirm',\n cancel: 'Cancel',\n areYouSure: 'Are you sure?',\n\n // Stepper\n stepperLabel: 'Steps',\n stepCompleted: 'Completed',\n stepCurrent: 'Current step',\n stepPending: 'Pending',\n\n // FilterBar / FilterPanel\n clearFilters: 'Clear filters',\n activeFilters: 'Active filters',\n filters: 'Filters',\n applyFilters: 'Apply',\n resetFilters: 'Reset',\n noFiltersActive: 'No active filters',\n clearSection: 'Clear this section',\n\n // PageHeader\n goBack: 'Go back',\n\n // ViewToggle\n gridView: 'Grid view',\n listView: 'List view',\n\n // PageLoader\n loading: 'Loading...',\n\n // ErrorBoundary\n unexpectedError: 'An unexpected error occurred',\n\n // DataTable\n selectAll: 'Select all',\n selectRow: 'Select row',\n noDataToDisplay: 'No data to display',\n\n // InstagramPost\n likesCount: 'Likes',\n commentsCount: 'Comments',\n viewsCount: 'Views',\n sharesCount: 'Shares',\n postTypeLabel: 'Post type',\n postTypePhoto: 'Photo',\n postTypeVideo: 'Video',\n postTypeCarousel: 'Carousel',\n showMore: 'More',\n showLess: 'Less',\n noMediaAvailable: 'No media available',\n commentAnalysis: 'Comment Analysis',\n commentAnalysisDisabled: 'Comment Analysis (disabled)',\n booster: 'Booster',\n boosterDisabled: 'Booster (disabled)',\n aiAnalysis: 'AI Analysis',\n aiAnalysisDisabled: 'AI Analysis (disabled)',\n openOnInstagram: 'Open on Instagram',\n openOnInstagramDisabled: 'Open on Instagram (disabled)',\n },\n} as const\n\nexport type UIStringKeys = keyof (typeof UI_STRINGS)['fa']\nexport type UIStrings = Record<UIStringKeys, string>\n\n/**\n * Get UI strings for a given locale.\n * Falls back to 'fa' for unknown locales.\n */\nexport function getUIStrings(locale: SupportedLocale = 'fa'): UIStrings {\n return (UI_STRINGS[locale] ?? UI_STRINGS.fa) as UIStrings\n}\n","'use client'\n\nimport * as React from 'react'\nimport { ArrowRight } from 'lucide-react'\nimport { cn, type SupportedLocale } from '@/lib/utils'\nimport { getUIStrings } from '@/lib/i18n'\n\nexport interface PageHeaderProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Page title */\n title: string\n /** Optional description below title */\n description?: string\n /** Back button callback. When provided, shows a back arrow. */\n onBack?: () => void\n /** Actions slot (buttons, etc.) rendered at inline-end */\n actions?: React.ReactNode\n /** Breadcrumb slot rendered above the title */\n breadcrumbs?: React.ReactNode\n /** Locale @default \"fa\" */\n locale?: SupportedLocale\n}\n\nconst PageHeader = React.forwardRef<HTMLDivElement, PageHeaderProps>(\n ({ className, title, description, onBack, actions, breadcrumbs, locale = 'fa', ...props }, ref) => {\n const strings = getUIStrings(locale)\n\n return (\n <div ref={ref} data-slot=\"page-header\" className={cn('flex flex-col gap-1', className)} {...props}>\n {breadcrumbs && (\n <div data-slot=\"page-header-breadcrumbs\" className=\"mb-2\">\n {breadcrumbs}\n </div>\n )}\n <div className=\"flex items-center justify-between gap-4\">\n <div className=\"flex items-center gap-3 min-w-0\">\n {onBack && (\n <button\n type=\"button\"\n onClick={onBack}\n aria-label={strings.goBack}\n className={cn(\n 'flex items-center justify-center shrink-0 rounded-md',\n 'size-8 text-foreground-lighter hover:text-foreground hover:bg-surface-200',\n 'transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring'\n )}\n >\n <ArrowRight className=\"size-5 ltr:rotate-180\" />\n </button>\n )}\n <div className=\"min-w-0\">\n <h1 data-slot=\"page-header-title\" className=\"text-xl font-bold text-foreground truncate sm:text-2xl\">\n {title}\n </h1>\n {description && (\n <p data-slot=\"page-header-description\" className=\"text-sm text-foreground-lighter mt-1 line-clamp-2\">\n {description}\n </p>\n )}\n </div>\n </div>\n {actions && (\n <div data-slot=\"page-header-actions\" className=\"flex items-center gap-2 shrink-0\">\n {actions}\n </div>\n )}\n </div>\n </div>\n )\n }\n)\nPageHeader.displayName = 'PageHeader'\n\nexport { PageHeader }\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../src/lib/utils.ts","../../../src/lib/i18n.ts","../../../src/components/ui/page-header.tsx"],"names":[],"mappings":"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","file":"page-header.js","sourcesContent":["import { type ClassValue, clsx } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\nimport { formatJalaliDate } from '@/lib/jalali-utils'\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n\nexport type SupportedLocale = 'fa' | 'ar' | 'en'\n\n/**\n * Convert digits in a string to Persian/Arabic numerals based on locale.\n * @example convertToLocalNumbers('123', 'fa') => '۱۲۳'\n * @example convertToLocalNumbers('123', 'en') => '123'\n */\nexport function convertToLocalNumbers(text: string | number, locale: SupportedLocale): string {\n if (locale === 'fa' || locale === 'ar') {\n const persianDigits = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']\n return String(text).replace(/\\d/g, (digit) => persianDigits[parseInt(digit)])\n }\n return String(text)\n}\n\n/**\n * Format large numbers with locale-aware suffixes (K/M/B).\n * @example formatLargeNumber(1500, 'fa') => '۱.۵ هزار'\n * @example formatLargeNumber(1500, 'en') => '1.5K'\n */\nexport function formatLargeNumber(num: number, locale: SupportedLocale): string {\n if (num >= 1_000_000_000) {\n const formatted = (num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'B' : ' میلیارد')\n }\n if (num >= 1_000_000) {\n const formatted = (num / 1_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'M' : ' میلیون')\n }\n if (num >= 1_000) {\n const formatted = (num / 1_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'K' : ' هزار')\n }\n return convertToLocalNumbers(num.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ','), locale)\n}\n\n/**\n * Format number to Instagram-style short format (English only).\n * @example formatNumber(123456, 'short') => '123K'\n * @example formatNumber(123456, 'exact') => '123,456'\n */\nexport function formatNumber(num: number | undefined, format: 'exact' | 'short' = 'exact'): string {\n if (num === undefined || num === null) return '0'\n\n if (format === 'exact') {\n return num.toLocaleString('en-US')\n }\n\n // Short format (Instagram style)\n if (num >= 1_000_000_000) {\n return `${(num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')}B`\n }\n if (num >= 1_000_000) {\n return `${(num / 1_000_000).toFixed(1).replace(/\\.0$/, '')}M`\n }\n if (num >= 1_000) {\n return `${(num / 1_000).toFixed(1).replace(/\\.0$/, '')}K`\n }\n return num.toString()\n}\n\n/**\n * Format date to relative time with absolute on hover (Persian)\n * @example formatRelativeTime(new Date()) => '۲ ساعت پیش'\n */\nexport function formatRelativeTime(date: Date | string | number): string {\n const now = new Date()\n const then = new Date(date)\n const diffInSeconds = Math.floor((now.getTime() - then.getTime()) / 1000)\n\n if (diffInSeconds < 60) {\n return 'همین الان'\n }\n\n const diffInMinutes = Math.floor(diffInSeconds / 60)\n if (diffInMinutes < 60) {\n return `${convertToLocalNumbers(diffInMinutes, 'fa')} دقیقه پیش`\n }\n\n const diffInHours = Math.floor(diffInMinutes / 60)\n if (diffInHours < 24) {\n return `${convertToLocalNumbers(diffInHours, 'fa')} ساعت پیش`\n }\n\n const diffInDays = Math.floor(diffInHours / 24)\n if (diffInDays < 7) {\n return `${convertToLocalNumbers(diffInDays, 'fa')} روز پیش`\n }\n\n const diffInWeeks = Math.floor(diffInDays / 7)\n if (diffInWeeks < 4) {\n return `${convertToLocalNumbers(diffInWeeks, 'fa')} هفته پیش`\n }\n\n const diffInMonths = Math.floor(diffInDays / 30)\n if (diffInMonths < 12) {\n return `${convertToLocalNumbers(diffInMonths, 'fa')} ماه پیش`\n }\n\n const diffInYears = Math.floor(diffInDays / 365)\n return `${convertToLocalNumbers(diffInYears, 'fa')} سال پیش`\n}\n\n/**\n * Format date to absolute format (Persian / Jalali)\n * Uses date-fns-jalali for accurate Jalali conversion.\n * @example formatAbsoluteTime(new Date()) => '۱۵ دی ۱۴۰۳، ۱۵:۳۰'\n */\nexport function formatAbsoluteTime(date: Date | string | number): string {\n const d = new Date(date)\n return formatJalaliDate(d, 'd MMMM yyyy، HH:mm')\n}\n","import type { SupportedLocale } from './utils'\n\n// Re-export SupportedLocale as Locale for public API convenience.\n// SupportedLocale is the canonical type; Locale is an alias.\nexport type Locale = SupportedLocale\n\n/**\n * Returns true for RTL locales (fa, ar).\n */\nexport function isRTL(locale: SupportedLocale): boolean {\n return locale === 'fa' || locale === 'ar'\n}\n\n// NOTE: TIER_LABELS and GROUP_LABELS already live in engagement-utils.ts.\n// This file only houses UI-layer translations that are not part of the domain model.\n\n/**\n * UI string translations used by the EngagementRate component.\n * Centralised here to avoid duplication between components.\n */\nexport const engagementUiTranslations: Record<\n SupportedLocale,\n {\n yourCategory: string\n influencer: string\n followers: string\n over: string\n to: string\n and: string\n lessThan: string\n you: string\n criteria: string\n }\n> = {\n fa: {\n yourCategory: 'دسته شما:',\n influencer: 'اینفلوئنسر',\n followers: 'فالوور',\n over: 'بیش از',\n to: 'تا',\n and: 'و بالاتر',\n lessThan: 'کمتر از',\n you: 'شما',\n criteria: 'معیارهای دسته',\n },\n ar: {\n yourCategory: 'فئتك:',\n influencer: 'مؤثر',\n followers: 'متابع',\n over: 'أكثر من',\n to: 'إلى',\n and: 'وأعلى',\n lessThan: 'أقل من',\n you: 'أنت',\n criteria: 'معايير فئة المؤثر',\n },\n en: {\n yourCategory: 'Your Category:',\n influencer: 'Influencer',\n followers: 'followers',\n over: 'Over',\n to: 'to',\n and: 'and above',\n lessThan: 'Less than',\n you: 'You',\n criteria: 'Category Criteria',\n },\n}\n\n/**\n * Sentiment label translations used by the SentimentBadge component.\n */\nexport const sentimentLabels: Record<\n SupportedLocale,\n {\n positive: string\n negative: string\n neutral: string\n mixed: string\n }\n> = {\n fa: { positive: 'مثبت', negative: 'منفی', neutral: 'خنثی', mixed: 'ترکیبی' },\n ar: { positive: 'إيجابي', negative: 'سلبي', neutral: 'محايد', mixed: 'مختلط' },\n en: { positive: 'Positive', negative: 'Negative', neutral: 'Neutral', mixed: 'Mixed' },\n}\n\n/**\n * Canonical emotion keys — 9-class advanced sentiment scale used in افکارسنجی\n * deep analysis. Coexists with the simple 3-class `Sentiment` above; they are\n * NOT interchangeable. Choose the scale based on view density.\n */\nexport type EmotionKey =\n | 'anger'\n | 'anticipation'\n | 'joy'\n | 'trust'\n | 'fear'\n | 'surprise'\n | 'sadness'\n | 'disgust'\n | 'neutral'\n\nexport const EMOTION_KEYS: readonly EmotionKey[] = [\n 'anger',\n 'anticipation',\n 'joy',\n 'trust',\n 'fear',\n 'surprise',\n 'sadness',\n 'disgust',\n 'neutral',\n] as const\n\n/** Emotion label translations used by EmotionBadge / EmotionDistribution. */\nexport const emotionLabels: Record<SupportedLocale, Record<EmotionKey, string>> = {\n fa: {\n anger: 'خشم',\n anticipation: 'انتظار',\n joy: 'شادی',\n trust: 'اعتماد',\n fear: 'ترس',\n surprise: 'شگفتی',\n sadness: 'غم',\n disgust: 'انزجار',\n neutral: 'خنثی',\n },\n ar: {\n anger: 'غضب',\n anticipation: 'ترقّب',\n joy: 'فرح',\n trust: 'ثقة',\n fear: 'خوف',\n surprise: 'دهشة',\n sadness: 'حزن',\n disgust: 'اشمئزاز',\n neutral: 'محايد',\n },\n en: {\n anger: 'Anger',\n anticipation: 'Anticipation',\n joy: 'Joy',\n trust: 'Trust',\n fear: 'Fear',\n surprise: 'Surprise',\n sadness: 'Sadness',\n disgust: 'Disgust',\n neutral: 'Neutral',\n },\n}\n\n/**\n * Stance keys — 5-class audience-stance classification used in clustering.\n * A concept / post / page belongs to exactly one primary stance, but usually\n * also has a secondary composition expressed as percentages. Keys are kept\n * short and historical; display labels are framed as audience stance toward\n * a tracked topic/brand (supporter ↔ detractor + undecided).\n */\nexport type FlowKey = 'pro-gov' | 'internal-critic' | 'internal-opponent' | 'external-opponent' | 'grey'\n\nexport const FLOW_KEYS: readonly FlowKey[] = [\n 'pro-gov',\n 'internal-critic',\n 'internal-opponent',\n 'external-opponent',\n 'grey',\n] as const\n\nexport const flowLabels: Record<SupportedLocale, Record<FlowKey, string>> = {\n fa: {\n 'pro-gov': 'حامی',\n 'internal-critic': 'منتقد سازنده',\n 'internal-opponent': 'مخالف',\n 'external-opponent': 'مخالف جدی',\n grey: 'خنثی',\n },\n ar: {\n 'pro-gov': 'مؤيد',\n 'internal-critic': 'منتقد بنّاء',\n 'internal-opponent': 'معارض',\n 'external-opponent': 'معارض شديد',\n grey: 'محايد',\n },\n en: {\n 'pro-gov': 'Supporter',\n 'internal-critic': 'Constructive Critic',\n 'internal-opponent': 'Detractor',\n 'external-opponent': 'Strong Detractor',\n grey: 'Undecided',\n },\n}\n\n/** Operational status — visual state of a tracked entity. */\nexport type StatusKey = 'critical' | 'warning' | 'normal'\n\nexport const statusLabels: Record<SupportedLocale, Record<StatusKey, string>> = {\n fa: { critical: 'بحرانی', warning: 'هشدار', normal: 'عادی' },\n ar: { critical: 'حرج', warning: 'تحذير', normal: 'طبيعي' },\n en: { critical: 'Critical', warning: 'Warning', normal: 'Normal' },\n}\n\n/** Severity — urgency level. Independent from status. */\nexport type SeverityKey = 'urgent' | 'high' | 'medium' | 'low'\n\nexport const severityLabels: Record<SupportedLocale, Record<SeverityKey, string>> = {\n fa: { urgent: 'فوری', high: 'بالا', medium: 'متوسط', low: 'پایین' },\n ar: { urgent: 'عاجل', high: 'مرتفع', medium: 'متوسط', low: 'منخفض' },\n en: { urgent: 'Urgent', high: 'High', medium: 'Medium', low: 'Low' },\n}\n\n/**\n * Job lifecycle — canonical states for async work (analyses, campaigns,\n * evaluation runs, imports, exports, report generation).\n *\n * Terminal states: completed / failed / cancelled.\n * Active states: queued / running / paused.\n */\nexport type JobStatusKey = 'queued' | 'running' | 'paused' | 'completed' | 'failed' | 'cancelled'\n\nexport const JOB_STATUS_KEYS: readonly JobStatusKey[] = [\n 'queued',\n 'running',\n 'paused',\n 'completed',\n 'failed',\n 'cancelled',\n] as const\n\nexport const jobStatusLabels: Record<SupportedLocale, Record<JobStatusKey, string>> = {\n fa: {\n queued: 'در صف',\n running: 'در حال اجرا',\n paused: 'متوقف',\n completed: 'تکمیل شد',\n failed: 'ناموفق',\n cancelled: 'لغو شد',\n },\n ar: {\n queued: 'في الطابور',\n running: 'قيد التنفيذ',\n paused: 'متوقف',\n completed: 'مكتمل',\n failed: 'فشل',\n cancelled: 'ملغى',\n },\n en: {\n queued: 'Queued',\n running: 'Running',\n paused: 'Paused',\n completed: 'Completed',\n failed: 'Failed',\n cancelled: 'Cancelled',\n },\n}\n\n/** True iff a job is still active (not in a terminal state). */\nexport function isActiveJobStatus(s: JobStatusKey): boolean {\n return s === 'queued' || s === 'running' || s === 'paused'\n}\n\n/**\n * Action status — discrete outcome for a single completed (or in-flight)\n * action in an activity feed / audit timeline. Distinct from the richer\n * job/stage lifecycles above: each event is atomic, so no \"paused\" here.\n */\nexport type ActionStatusKey = 'success' | 'failed' | 'pending' | 'warning' | 'info'\n\nexport const ACTION_STATUS_KEYS: readonly ActionStatusKey[] = [\n 'success',\n 'failed',\n 'pending',\n 'warning',\n 'info',\n] as const\n\nexport const actionStatusLabels: Record<SupportedLocale, Record<ActionStatusKey, string>> = {\n fa: {\n success: 'موفق',\n failed: 'ناموفق',\n pending: 'در حال انجام',\n warning: 'هشدار',\n info: 'اطلاع',\n },\n ar: {\n success: 'ناجح',\n failed: 'فشل',\n pending: 'قيد التنفيذ',\n warning: 'تحذير',\n info: 'معلومات',\n },\n en: {\n success: 'Success',\n failed: 'Failed',\n pending: 'Pending',\n warning: 'Warning',\n info: 'Info',\n },\n}\n\n/**\n * Locale-aware relative time label producer. Used by ActionTimeline and any\n * other component that wants trilingual \"n minutes ago\" strings. For absolute\n * formatting see `formatAbsoluteTime`/`formatJalaliDate` in lib/utils.ts.\n */\nexport function formatRelativeLocaleTime(date: Date | string | number, locale: SupportedLocale): string {\n const then = new Date(date).getTime()\n const now = Date.now()\n const diffSec = Math.floor((now - then) / 1000)\n\n const fmt = (n: number) => (locale === 'en' ? String(n) : n.toLocaleString(locale === 'ar' ? 'ar-EG' : 'fa-IR'))\n\n if (diffSec < 0) {\n // Future time — rare in feeds. Fall through to absolute below.\n return new Date(then).toISOString()\n }\n if (diffSec < 60) {\n if (locale === 'fa') return 'همین الان'\n if (locale === 'ar') return 'الآن'\n return 'just now'\n }\n const diffMin = Math.floor(diffSec / 60)\n if (diffMin < 60) {\n if (locale === 'fa') return `${fmt(diffMin)} دقیقه پیش`\n if (locale === 'ar') return `قبل ${fmt(diffMin)} دقيقة`\n return `${diffMin}m ago`\n }\n const diffHr = Math.floor(diffMin / 60)\n if (diffHr < 24) {\n if (locale === 'fa') return `${fmt(diffHr)} ساعت پیش`\n if (locale === 'ar') return `قبل ${fmt(diffHr)} ساعة`\n return `${diffHr}h ago`\n }\n const diffDay = Math.floor(diffHr / 24)\n if (diffDay < 7) {\n if (locale === 'fa') return `${fmt(diffDay)} روز پیش`\n if (locale === 'ar') return `قبل ${fmt(diffDay)} يوم`\n return `${diffDay}d ago`\n }\n const diffWk = Math.floor(diffDay / 7)\n if (diffWk < 4) {\n if (locale === 'fa') return `${fmt(diffWk)} هفته پیش`\n if (locale === 'ar') return `قبل ${fmt(diffWk)} أسبوع`\n return `${diffWk}w ago`\n }\n const diffMon = Math.floor(diffDay / 30)\n if (diffMon < 12) {\n if (locale === 'fa') return `${fmt(diffMon)} ماه پیش`\n if (locale === 'ar') return `قبل ${fmt(diffMon)} شهر`\n return `${diffMon}mo ago`\n }\n const diffYr = Math.floor(diffDay / 365)\n if (locale === 'fa') return `${fmt(diffYr)} سال پیش`\n if (locale === 'ar') return `قبل ${fmt(diffYr)} سنة`\n return `${diffYr}y ago`\n}\n\n/**\n * Locale-aware \"time remaining\" label — forward-looking companion to\n * `formatRelativeLocaleTime`. Used by RateLimitBanner and any countdown UI.\n *\n * Input can be a Date, ISO string, or epoch ms. Past/zero remaining returns\n * the locale-specific \"now\" / \"any moment\" marker.\n */\nexport function formatTimeRemaining(target: Date | string | number, locale: SupportedLocale): string {\n const then = new Date(target).getTime()\n const now = Date.now()\n const diffSec = Math.max(0, Math.floor((then - now) / 1000))\n\n const fmt = (n: number) => (locale === 'en' ? String(n) : n.toLocaleString(locale === 'ar' ? 'ar-EG' : 'fa-IR'))\n\n if (diffSec <= 0) {\n if (locale === 'fa') return 'هم‌اکنون'\n if (locale === 'ar') return 'الآن'\n return 'any moment'\n }\n if (diffSec < 60) {\n if (locale === 'fa') return `${fmt(diffSec)} ثانیه دیگر`\n if (locale === 'ar') return `بعد ${fmt(diffSec)} ثانية`\n return `in ${diffSec}s`\n }\n const totalMin = Math.floor(diffSec / 60)\n if (totalMin < 60) {\n if (locale === 'fa') return `${fmt(totalMin)} دقیقه دیگر`\n if (locale === 'ar') return `بعد ${fmt(totalMin)} دقيقة`\n return `in ${totalMin}m`\n }\n const hours = Math.floor(totalMin / 60)\n const mins = totalMin % 60\n if (hours < 24) {\n if (mins === 0) {\n if (locale === 'fa') return `${fmt(hours)} ساعت دیگر`\n if (locale === 'ar') return `بعد ${fmt(hours)} ساعة`\n return `in ${hours}h`\n }\n if (locale === 'fa') return `${fmt(hours)} ساعت و ${fmt(mins)} دقیقه دیگر`\n if (locale === 'ar') return `بعد ${fmt(hours)} ساعة و${fmt(mins)} دقيقة`\n return `in ${hours}h ${mins}m`\n }\n const days = Math.floor(hours / 24)\n const remHours = hours % 24\n if (remHours === 0) {\n if (locale === 'fa') return `${fmt(days)} روز دیگر`\n if (locale === 'ar') return `بعد ${fmt(days)} يوم`\n return `in ${days}d`\n }\n if (locale === 'fa') return `${fmt(days)} روز و ${fmt(remHours)} ساعت دیگر`\n if (locale === 'ar') return `بعد ${fmt(days)} يوم و${fmt(remHours)} ساعة`\n return `in ${days}d ${remHours}h`\n}\n\n/**\n * Stage status — for multi-stage pipeline visualizations (StatusFlow).\n *\n * Distinct from `JobStatusKey`: a job has ONE status; a pipeline of stages has\n * a status PER stage. A `running` stage means that stage is currently active;\n * a `warning` stage completed but with caveats; `skipped` means it was bypassed.\n */\nexport type StageStatusKey = 'pending' | 'running' | 'completed' | 'failed' | 'skipped' | 'warning' | 'paused'\n\nexport const STAGE_STATUS_KEYS: readonly StageStatusKey[] = [\n 'pending',\n 'running',\n 'completed',\n 'failed',\n 'skipped',\n 'warning',\n 'paused',\n] as const\n\nexport const stageStatusLabels: Record<SupportedLocale, Record<StageStatusKey, string>> = {\n fa: {\n pending: 'در انتظار',\n running: 'در حال اجرا',\n completed: 'تکمیل شد',\n failed: 'ناموفق',\n skipped: 'رد شد',\n warning: 'با هشدار',\n paused: 'متوقف',\n },\n ar: {\n pending: 'في الانتظار',\n running: 'قيد التنفيذ',\n completed: 'مكتمل',\n failed: 'فشل',\n skipped: 'تم تخطيه',\n warning: 'مع تحذير',\n paused: 'متوقف',\n },\n en: {\n pending: 'Pending',\n running: 'Running',\n completed: 'Completed',\n failed: 'Failed',\n skipped: 'Skipped',\n warning: 'Warning',\n paused: 'Paused',\n },\n}\n\n/**\n * Action type — canonical set of social-media actions a worker/user can\n * execute. Maps 1:1 to a CSS token family (`--action-type-*`) so each action\n * has a distinct recognizable colour + icon across activity feeds, audit\n * timelines, and rate-limit dashboards.\n *\n * The 7 canonical keys cover current افکارسنجی + Booster + future SRR use:\n * - `like` / `comment` / `save` / `follow` / `unfollow` — Booster OPERATION_TYPES\n * - `dm` — direct message (future bulk-DM, notification replies)\n * - `share` — cross-post / retweet (future multi-platform)\n */\nexport type ActionTypeKey = 'like' | 'comment' | 'save' | 'follow' | 'unfollow' | 'dm' | 'share'\n\nexport const ACTION_TYPE_KEYS: readonly ActionTypeKey[] = [\n 'like',\n 'comment',\n 'save',\n 'follow',\n 'unfollow',\n 'dm',\n 'share',\n] as const\n\nexport const actionTypeLabels: Record<SupportedLocale, Record<ActionTypeKey, string>> = {\n fa: {\n like: 'پسندیدن',\n comment: 'نظر',\n save: 'ذخیره',\n follow: 'دنبال کردن',\n unfollow: 'لغو دنبال',\n dm: 'پیام مستقیم',\n share: 'اشتراک‌گذاری',\n },\n ar: {\n like: 'إعجاب',\n comment: 'تعليق',\n save: 'حفظ',\n follow: 'متابعة',\n unfollow: 'إلغاء المتابعة',\n dm: 'رسالة مباشرة',\n share: 'مشاركة',\n },\n en: {\n like: 'Like',\n comment: 'Comment',\n save: 'Save',\n follow: 'Follow',\n unfollow: 'Unfollow',\n dm: 'Direct Message',\n share: 'Share',\n },\n}\n\n/**\n * Short-form verb labels for past-tense activity phrasing (\"worker X پسندید Y\").\n * When composing \"subject action object\" sentences in ActivityFeed / ActionTimeline,\n * prefer these over `actionTypeLabels` which are noun-form.\n */\nexport const actionTypeVerbs: Record<SupportedLocale, Record<ActionTypeKey, string>> = {\n fa: {\n like: 'پسندید',\n comment: 'نظر گذاشت',\n save: 'ذخیره کرد',\n follow: 'دنبال کرد',\n unfollow: 'لغو دنبال کرد',\n dm: 'پیام داد',\n share: 'اشتراک گذاشت',\n },\n ar: {\n like: 'أعجب',\n comment: 'علّق على',\n save: 'حفظ',\n follow: 'تابع',\n unfollow: 'ألغى متابعة',\n dm: 'راسل',\n share: 'شارك',\n },\n en: {\n like: 'liked',\n comment: 'commented on',\n save: 'saved',\n follow: 'followed',\n unfollow: 'unfollowed',\n dm: 'DMd',\n share: 'shared',\n },\n}\n\n/**\n * Entity health — 6-tier severity narrative for long-lived entities like\n * workers, tracked pages, data sources, monitored feeds. Ordered most-critical\n * to most-positive so components can sort \"worst first\" deterministically.\n *\n * `banned` — entity is permanently disabled / blocked upstream.\n * `pending` — not yet evaluated (new, awaiting verification, in setup).\n * `at-risk` — immediate intervention needed (failed checks, violations).\n * `warning` — degrading signals, needs monitoring.\n * `growing` — improving trajectory but not yet healthy.\n * `healthy` — stable, all checks pass.\n *\n * Distinct from `StatusKey` (critical/warning/normal — short-lived operational)\n * and `SeverityKey` (urgent/high/medium/low — urgency of an action item).\n */\nexport type EntityHealthKey = 'banned' | 'pending' | 'at-risk' | 'warning' | 'growing' | 'healthy'\n\nexport const ENTITY_HEALTH_KEYS: readonly EntityHealthKey[] = [\n 'banned',\n 'pending',\n 'at-risk',\n 'warning',\n 'growing',\n 'healthy',\n] as const\n\nexport const entityHealthLabels: Record<SupportedLocale, Record<EntityHealthKey, string>> = {\n fa: {\n banned: 'مسدود',\n pending: 'در انتظار بررسی',\n 'at-risk': 'در خطر',\n warning: 'هشدار',\n growing: 'در حال بهبود',\n healthy: 'سالم',\n },\n ar: {\n banned: 'محظور',\n pending: 'قيد الانتظار',\n 'at-risk': 'في خطر',\n warning: 'تحذير',\n growing: 'ينمو',\n healthy: 'صحي',\n },\n en: {\n banned: 'Banned',\n pending: 'Pending',\n 'at-risk': 'At Risk',\n warning: 'Warning',\n growing: 'Growing',\n healthy: 'Healthy',\n },\n}\n\n/**\n * Priority rank of each entity health state — higher number = more severe.\n * Use to sort entity lists \"worst first\" without ad-hoc comparisons.\n */\nexport const entityHealthPriority: Record<EntityHealthKey, number> = {\n banned: 5,\n 'at-risk': 4,\n warning: 3,\n pending: 2,\n growing: 1,\n healthy: 0,\n}\n\n/** True iff the entity needs immediate human attention. */\nexport function isCriticalEntityHealth(s: EntityHealthKey): boolean {\n return s === 'banned' || s === 'at-risk'\n}\n\n/**\n * Default UI strings for components that need localized text.\n * Persian (fa) is the default locale for backward compatibility.\n */\nexport const UI_STRINGS = {\n fa: {\n // Autocomplete\n noResults: 'نتیجه‌ای یافت نشد',\n suggestions: 'پیشنهادها',\n\n // MultiSelect\n select: 'انتخاب کنید',\n noOptionsFound: 'موردی یافت نشد',\n search: 'جستجو...',\n clearAll: 'پاک کردن همه',\n\n // DatePicker / DateRangePicker\n selectDate: 'انتخاب تاریخ',\n selectDateRange: 'انتخاب بازه تاریخ',\n\n // ErrorState\n errorLoadingData: 'خطا در بارگذاری داده‌ها',\n retry: 'تلاش مجدد',\n\n // FilterChip\n remove: 'حذف',\n\n // UserAutocomplete\n noUsersFound: 'کاربری یافت نشد',\n\n // Pagination\n goToPreviousPage: 'رفتن به صفحه قبلی',\n goToNextPage: 'رفتن به صفحه بعدی',\n previous: 'قبلی',\n next: 'بعدی',\n\n // ProfileInfo\n actions: 'عملیات',\n\n // CopyButton\n copy: 'کپی',\n copied: 'کپی شد',\n\n // ConfirmDialog\n confirm: 'تایید',\n cancel: 'انصراف',\n areYouSure: 'آیا مطمئن هستید؟',\n\n // Stepper\n stepperLabel: 'مراحل',\n stepCompleted: 'تکمیل شده',\n stepCurrent: 'مرحله فعلی',\n stepPending: 'در انتظار',\n\n // FilterBar / FilterPanel\n clearFilters: 'پاک کردن فیلترها',\n activeFilters: 'فیلترهای فعال',\n filters: 'فیلترها',\n applyFilters: 'اعمال',\n resetFilters: 'پیش‌فرض',\n noFiltersActive: 'فیلتر فعالی نیست',\n clearSection: 'پاک کردن این بخش',\n\n // PageHeader\n goBack: 'بازگشت',\n\n // ViewToggle\n gridView: 'نمای شبکه‌ای',\n listView: 'نمای لیستی',\n\n // PageLoader\n loading: 'در حال بارگذاری...',\n\n // ErrorBoundary\n unexpectedError: 'خطای غیرمنتظره‌ای رخ داد',\n\n // DataTable\n selectAll: 'انتخاب همه',\n selectRow: 'انتخاب ردیف',\n noDataToDisplay: 'داده‌ای برای نمایش وجود ندارد',\n\n // InstagramPost\n likesCount: 'تعداد لایک',\n commentsCount: 'تعداد کامنت',\n viewsCount: 'تعداد بازدید',\n sharesCount: 'تعداد اشتراک',\n postTypeLabel: 'نوع پست',\n postTypePhoto: 'عکس',\n postTypeVideo: 'ویدیو',\n postTypeCarousel: 'اسلاید',\n showMore: 'بیشتر',\n showLess: 'کمتر',\n noMediaAvailable: 'رسانه‌ای موجود نیست',\n commentAnalysis: 'تحلیل کامنت‌ها',\n commentAnalysisDisabled: 'تحلیل کامنت‌ها (غیرفعال)',\n booster: 'بوستر',\n boosterDisabled: 'بوستر (غیرفعال)',\n aiAnalysis: 'تحلیل هوش مصنوعی',\n aiAnalysisDisabled: 'تحلیل هوش مصنوعی (غیرفعال)',\n openOnInstagram: 'باز کردن در اینستاگرام',\n openOnInstagramDisabled: 'باز کردن در اینستاگرام (غیرفعال)',\n },\n ar: {\n noResults: 'لم يتم العثور على نتائج',\n suggestions: 'اقتراحات',\n select: 'اختر',\n noOptionsFound: 'لم يتم العثور على خيارات',\n search: 'بحث...',\n clearAll: 'مسح الكل',\n selectDate: 'اختر تاريخ',\n selectDateRange: 'اختر نطاق التاريخ',\n errorLoadingData: 'خطأ في تحميل البيانات',\n retry: 'إعادة المحاولة',\n remove: 'حذف',\n noUsersFound: 'لم يتم العثور على مستخدمين',\n goToPreviousPage: 'الانتقال إلى الصفحة السابقة',\n goToNextPage: 'الانتقال إلى الصفحة التالية',\n previous: 'السابق',\n next: 'التالي',\n actions: 'إجراءات',\n\n // CopyButton\n copy: 'نسخ',\n copied: 'تم النسخ',\n\n // ConfirmDialog\n confirm: 'تأكيد',\n cancel: 'إلغاء',\n areYouSure: 'هل أنت متأكد؟',\n\n // Stepper\n stepperLabel: 'الخطوات',\n stepCompleted: 'مكتمل',\n stepCurrent: 'الخطوة الحالية',\n stepPending: 'قيد الانتظار',\n\n // FilterBar / FilterPanel\n clearFilters: 'مسح الفلاتر',\n activeFilters: 'الفلاتر النشطة',\n filters: 'الفلاتر',\n applyFilters: 'تطبيق',\n resetFilters: 'الافتراضي',\n noFiltersActive: 'لا توجد فلاتر نشطة',\n clearSection: 'مسح هذا القسم',\n\n // PageHeader\n goBack: 'رجوع',\n\n // ViewToggle\n gridView: 'عرض شبكي',\n listView: 'عرض قائمة',\n\n // PageLoader\n loading: 'جارٍ التحميل...',\n\n // ErrorBoundary\n unexpectedError: 'حدث خطأ غير متوقع',\n\n // DataTable\n selectAll: 'تحديد الكل',\n selectRow: 'تحديد الصف',\n noDataToDisplay: 'لا توجد بيانات للعرض',\n\n // InstagramPost\n likesCount: 'عدد الإعجابات',\n commentsCount: 'عدد التعليقات',\n viewsCount: 'عدد المشاهدات',\n sharesCount: 'عدد المشاركات',\n postTypeLabel: 'نوع المنشور',\n postTypePhoto: 'صورة',\n postTypeVideo: 'فيديو',\n postTypeCarousel: 'شرائح',\n showMore: 'المزيد',\n showLess: 'أقل',\n noMediaAvailable: 'لا توجد وسائط',\n commentAnalysis: 'تحليل التعليقات',\n commentAnalysisDisabled: 'تحليل التعليقات (معطل)',\n booster: 'تعزيز',\n boosterDisabled: 'تعزيز (معطل)',\n aiAnalysis: 'تحليل الذكاء الاصطناعي',\n aiAnalysisDisabled: 'تحليل الذكاء الاصطناعي (معطل)',\n openOnInstagram: 'فتح في إنستاجرام',\n openOnInstagramDisabled: 'فتح في إنستاجرام (معطل)',\n },\n en: {\n noResults: 'No results found',\n suggestions: 'Suggestions',\n select: 'Select',\n noOptionsFound: 'No options found',\n search: 'Search...',\n clearAll: 'Clear all',\n selectDate: 'Select date',\n selectDateRange: 'Select date range',\n errorLoadingData: 'Error loading data',\n retry: 'Retry',\n remove: 'Remove',\n noUsersFound: 'No users found',\n goToPreviousPage: 'Go to previous page',\n goToNextPage: 'Go to next page',\n previous: 'Previous',\n next: 'Next',\n actions: 'Actions',\n\n // CopyButton\n copy: 'Copy',\n copied: 'Copied',\n\n // ConfirmDialog\n confirm: 'Confirm',\n cancel: 'Cancel',\n areYouSure: 'Are you sure?',\n\n // Stepper\n stepperLabel: 'Steps',\n stepCompleted: 'Completed',\n stepCurrent: 'Current step',\n stepPending: 'Pending',\n\n // FilterBar / FilterPanel\n clearFilters: 'Clear filters',\n activeFilters: 'Active filters',\n filters: 'Filters',\n applyFilters: 'Apply',\n resetFilters: 'Reset',\n noFiltersActive: 'No active filters',\n clearSection: 'Clear this section',\n\n // PageHeader\n goBack: 'Go back',\n\n // ViewToggle\n gridView: 'Grid view',\n listView: 'List view',\n\n // PageLoader\n loading: 'Loading...',\n\n // ErrorBoundary\n unexpectedError: 'An unexpected error occurred',\n\n // DataTable\n selectAll: 'Select all',\n selectRow: 'Select row',\n noDataToDisplay: 'No data to display',\n\n // InstagramPost\n likesCount: 'Likes',\n commentsCount: 'Comments',\n viewsCount: 'Views',\n sharesCount: 'Shares',\n postTypeLabel: 'Post type',\n postTypePhoto: 'Photo',\n postTypeVideo: 'Video',\n postTypeCarousel: 'Carousel',\n showMore: 'More',\n showLess: 'Less',\n noMediaAvailable: 'No media available',\n commentAnalysis: 'Comment Analysis',\n commentAnalysisDisabled: 'Comment Analysis (disabled)',\n booster: 'Booster',\n boosterDisabled: 'Booster (disabled)',\n aiAnalysis: 'AI Analysis',\n aiAnalysisDisabled: 'AI Analysis (disabled)',\n openOnInstagram: 'Open on Instagram',\n openOnInstagramDisabled: 'Open on Instagram (disabled)',\n },\n} as const\n\nexport type UIStringKeys = keyof (typeof UI_STRINGS)['fa']\nexport type UIStrings = Record<UIStringKeys, string>\n\n/**\n * Get UI strings for a given locale.\n * Falls back to 'fa' for unknown locales.\n */\nexport function getUIStrings(locale: SupportedLocale = 'fa'): UIStrings {\n return (UI_STRINGS[locale] ?? UI_STRINGS.fa) as UIStrings\n}\n","'use client'\n\nimport * as React from 'react'\nimport { ArrowRight } from 'lucide-react'\nimport { cn, type SupportedLocale } from '@/lib/utils'\nimport { getUIStrings } from '@/lib/i18n'\n\nexport interface PageHeaderProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Page title */\n title: string\n /** Optional description below title */\n description?: string\n /** Back button callback. When provided, shows a back arrow. */\n onBack?: () => void\n /** Actions slot (buttons, etc.) rendered at inline-end */\n actions?: React.ReactNode\n /** Breadcrumb slot rendered above the title */\n breadcrumbs?: React.ReactNode\n /** Locale @default \"fa\" */\n locale?: SupportedLocale\n}\n\nconst PageHeader = React.forwardRef<HTMLDivElement, PageHeaderProps>(\n ({ className, title, description, onBack, actions, breadcrumbs, locale = 'fa', ...props }, ref) => {\n const strings = getUIStrings(locale)\n\n return (\n <div ref={ref} data-slot=\"page-header\" className={cn('flex flex-col gap-1', className)} {...props}>\n {breadcrumbs && (\n <div data-slot=\"page-header-breadcrumbs\" className=\"mb-2\">\n {breadcrumbs}\n </div>\n )}\n <div className=\"flex items-center justify-between gap-4\">\n <div className=\"flex items-center gap-3 min-w-0\">\n {onBack && (\n <button\n type=\"button\"\n onClick={onBack}\n aria-label={strings.goBack}\n className={cn(\n 'flex items-center justify-center shrink-0 rounded-md',\n 'size-8 text-foreground-lighter hover:text-foreground hover:bg-surface-200',\n 'transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring'\n )}\n >\n <ArrowRight className=\"size-5 ltr:rotate-180\" />\n </button>\n )}\n <div className=\"min-w-0\">\n <h1 data-slot=\"page-header-title\" className=\"text-xl font-bold text-foreground truncate sm:text-2xl\">\n {title}\n </h1>\n {description && (\n <p data-slot=\"page-header-description\" className=\"text-sm text-foreground-lighter mt-1 line-clamp-2\">\n {description}\n </p>\n )}\n </div>\n </div>\n {actions && (\n <div data-slot=\"page-header-actions\" className=\"flex items-center gap-2 shrink-0\">\n {actions}\n </div>\n )}\n </div>\n </div>\n )\n }\n)\nPageHeader.displayName = 'PageHeader'\n\nexport { PageHeader }\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../src/lib/constants.ts","../../../src/lib/utils.ts","../../../src/components/ui/input.tsx","../../../src/components/ui/password-input.tsx"],"names":["twMerge","clsx","cva","React","jsx","React2","EyeOff","Eye"],"mappings":"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","file":"password-input.cjs","sourcesContent":["/**\n * Standard size scale used across the design system.\n * Components should accept these values for their `size` prop.\n */\nexport type StandardSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'\n\n/** @deprecated Use `StandardSize` instead — legacy names have been removed */\nexport type LegacySize = StandardSize\n\n/** @deprecated Use `StandardSize` instead */\nexport type SizeWithLegacy = StandardSize\n\n/**\n * Normalizes a size value to standard names.\n * @deprecated All sizes are standard now — this function is a no-op passthrough.\n */\nexport function normalizeSize(size: StandardSize): StandardSize {\n return size\n}\n\nexport const SIZE = {\n text: {\n xs: 'text-xs',\n sm: 'text-sm leading-4',\n md: 'text-sm',\n lg: 'text-base',\n xl: 'text-base',\n },\n padding: {\n xs: 'px-2.5 py-1',\n sm: 'px-3 py-2',\n md: 'px-4 py-2',\n lg: 'px-4 py-2',\n xl: 'px-6 py-3',\n },\n height: {\n xs: 'h-[26px]',\n sm: 'h-[34px]',\n md: 'h-[38px]',\n lg: 'h-[42px]',\n xl: 'h-[50px]',\n },\n}\n\n/** Inner sizes for nested elements (badges inside buttons, multi-select items, etc.) */\nexport const SIZE_INNER = {\n text: {\n xs: 'text-xs',\n sm: 'text-sm leading-4',\n md: 'text-sm',\n lg: 'text-base',\n xl: 'text-base',\n },\n padding: {\n xs: 'px-2.5 py-1',\n sm: 'px-3 py-2',\n md: 'px-4 py-2',\n lg: 'px-4 py-2',\n xl: 'px-6 py-3',\n },\n height: {\n xs: 'h-[24px]',\n sm: 'h-[28px]',\n md: 'h-[32px]',\n lg: 'h-[36px]',\n xl: 'h-[44px]',\n },\n}\n\nexport const SIZE_VARIANTS = {\n xs: `${SIZE.text['xs']} ${SIZE.padding['xs']} ${SIZE.height['xs']}`,\n sm: `${SIZE.text['sm']} ${SIZE.padding['sm']} ${SIZE.height['sm']}`,\n md: `${SIZE.text['md']} ${SIZE.padding['md']} ${SIZE.height['md']}`,\n lg: `${SIZE.text['lg']} ${SIZE.padding['lg']} ${SIZE.height['lg']}`,\n xl: `${SIZE.text['xl']} ${SIZE.padding['xl']} ${SIZE.height['xl']}`,\n}\n\nexport const SIZE_VARIANTS_INNER = {\n xs: `${SIZE.text['xs']} ${SIZE.padding['xs']} ${SIZE_INNER.height['xs']}`,\n sm: `${SIZE.text['sm']} ${SIZE.padding['sm']} ${SIZE_INNER.height['sm']}`,\n md: `${SIZE.text['md']} ${SIZE.padding['md']} ${SIZE_INNER.height['md']}`,\n lg: `${SIZE.text['lg']} ${SIZE.padding['lg']} ${SIZE_INNER.height['lg']}`,\n xl: `${SIZE.text['xl']} ${SIZE.padding['xl']} ${SIZE_INNER.height['xl']}`,\n}\n\n/** @deprecated Use 'sm' instead */\nexport const SIZE_VARIANTS_DEFAULT = 'sm'\n","import { type ClassValue, clsx } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\nimport { formatJalaliDate } from '@/lib/jalali-utils'\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n\nexport type SupportedLocale = 'fa' | 'ar' | 'en'\n\n/**\n * Convert digits in a string to Persian/Arabic numerals based on locale.\n * @example convertToLocalNumbers('123', 'fa') => '۱۲۳'\n * @example convertToLocalNumbers('123', 'en') => '123'\n */\nexport function convertToLocalNumbers(text: string | number, locale: SupportedLocale): string {\n if (locale === 'fa' || locale === 'ar') {\n const persianDigits = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']\n return String(text).replace(/\\d/g, (digit) => persianDigits[parseInt(digit)])\n }\n return String(text)\n}\n\n/**\n * Format large numbers with locale-aware suffixes (K/M/B).\n * @example formatLargeNumber(1500, 'fa') => '۱.۵ هزار'\n * @example formatLargeNumber(1500, 'en') => '1.5K'\n */\nexport function formatLargeNumber(num: number, locale: SupportedLocale): string {\n if (num >= 1_000_000_000) {\n const formatted = (num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'B' : ' میلیارد')\n }\n if (num >= 1_000_000) {\n const formatted = (num / 1_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'M' : ' میلیون')\n }\n if (num >= 1_000) {\n const formatted = (num / 1_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'K' : ' هزار')\n }\n return convertToLocalNumbers(num.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ','), locale)\n}\n\n/**\n * Format number to Instagram-style short format (English only).\n * @example formatNumber(123456, 'short') => '123K'\n * @example formatNumber(123456, 'exact') => '123,456'\n */\nexport function formatNumber(num: number | undefined, format: 'exact' | 'short' = 'exact'): string {\n if (num === undefined || num === null) return '0'\n\n if (format === 'exact') {\n return num.toLocaleString('en-US')\n }\n\n // Short format (Instagram style)\n if (num >= 1_000_000_000) {\n return `${(num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')}B`\n }\n if (num >= 1_000_000) {\n return `${(num / 1_000_000).toFixed(1).replace(/\\.0$/, '')}M`\n }\n if (num >= 1_000) {\n return `${(num / 1_000).toFixed(1).replace(/\\.0$/, '')}K`\n }\n return num.toString()\n}\n\n/**\n * Format date to relative time with absolute on hover (Persian)\n * @example formatRelativeTime(new Date()) => '۲ ساعت پیش'\n */\nexport function formatRelativeTime(date: Date | string | number): string {\n const now = new Date()\n const then = new Date(date)\n const diffInSeconds = Math.floor((now.getTime() - then.getTime()) / 1000)\n\n if (diffInSeconds < 60) {\n return 'همین الان'\n }\n\n const diffInMinutes = Math.floor(diffInSeconds / 60)\n if (diffInMinutes < 60) {\n return `${convertToLocalNumbers(diffInMinutes, 'fa')} دقیقه پیش`\n }\n\n const diffInHours = Math.floor(diffInMinutes / 60)\n if (diffInHours < 24) {\n return `${convertToLocalNumbers(diffInHours, 'fa')} ساعت پیش`\n }\n\n const diffInDays = Math.floor(diffInHours / 24)\n if (diffInDays < 7) {\n return `${convertToLocalNumbers(diffInDays, 'fa')} روز پیش`\n }\n\n const diffInWeeks = Math.floor(diffInDays / 7)\n if (diffInWeeks < 4) {\n return `${convertToLocalNumbers(diffInWeeks, 'fa')} هفته پیش`\n }\n\n const diffInMonths = Math.floor(diffInDays / 30)\n if (diffInMonths < 12) {\n return `${convertToLocalNumbers(diffInMonths, 'fa')} ماه پیش`\n }\n\n const diffInYears = Math.floor(diffInDays / 365)\n return `${convertToLocalNumbers(diffInYears, 'fa')} سال پیش`\n}\n\n/**\n * Format date to absolute format (Persian / Jalali)\n * Uses date-fns-jalali for accurate Jalali conversion.\n * @example formatAbsoluteTime(new Date()) => '۱۵ دی ۱۴۰۳، ۱۵:۳۰'\n */\nexport function formatAbsoluteTime(date: Date | string | number): string {\n const d = new Date(date)\n return formatJalaliDate(d, 'd MMMM yyyy، HH:mm')\n}\n","'use client'\n\nimport { VariantProps, cva } from 'class-variance-authority'\nimport * as React from 'react'\nimport { SIZE_VARIANTS } from '@/lib/constants'\nimport { cn } from '@/lib/utils'\n\nexport const inputVariants = cva(\n cn(\n // 1.1.16 — bg-control (now 19% in dark) instead of foreground/2.6%\n // opacity overlay, so the input is visibly distinct from any Card\n // it sits inside. text-start makes text-align follow the input's\n // own `dir` (set explicitly or auto for LTR types), so an email\n // field with `dir=\"ltr\"` is left-aligned even in an RTL form.\n 'flex w-full rounded-md border border-control read-only:border-button bg-control text-sm text-foreground text-start file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-foreground-lighter read-only:text-foreground-light',\n 'transition-colors duration-150',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50',\n 'aria-[invalid=true]:bg-destructive-200 dark:aria-[invalid=true]:bg-destructive/5 aria-[invalid=true]:border-destructive-400 dark:aria-[invalid=true]:border-destructive-default aria-[invalid=true]:focus:border-destructive aria-[invalid=true]:focus-visible:border-destructive'\n ),\n {\n variants: {\n size: {\n ...SIZE_VARIANTS,\n },\n },\n defaultVariants: {\n size: 'sm',\n },\n }\n)\n\n/** @deprecated Use inputVariants instead */\nexport const InputVariants = inputVariants\n\nexport interface InputProps\n extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'>, VariantProps<typeof inputVariants> {\n /**\n * Node rendered at the start of the input (left in LTR, right in RTL).\n * Common use: a search icon, a country flag, a currency symbol.\n * The adornment wrapper is `pointer-events-none` by default so it doesn't\n * steal focus from the input — interactive elements inside (e.g. a\n * `<button>` for a password show/hide toggle) get `pointer-events-auto`\n * back automatically.\n */\n startAdornment?: React.ReactNode\n /**\n * Node rendered at the end of the input (right in LTR, left in RTL).\n * Common use: a password show/hide toggle, a clear button, a unit suffix.\n * See `startAdornment` for the pointer-events behavior.\n */\n endAdornment?: React.ReactNode\n}\n\nconst LTR_INPUT_TYPES = new Set(['email', 'url', 'tel', 'number', 'password'])\n\nconst Input = React.forwardRef<HTMLInputElement, InputProps>(\n ({ className, type, size = 'sm', dir, startAdornment, endAdornment, ...props }, ref) => {\n // LTR content types: auto-set dir=\"ltr\" unless caller explicitly overrides\n const resolvedDir = dir ?? (type && LTR_INPUT_TYPES.has(type) ? 'ltr' : undefined)\n\n // Fast path: no adornments → render the bare <input>. Zero wrapper cost,\n // identical to the pre-1.1.14 behavior for backward compatibility.\n if (!startAdornment && !endAdornment) {\n return (\n <input\n type={type}\n ref={ref}\n data-slot=\"input\"\n dir={resolvedDir}\n {...props}\n className={cn(inputVariants({ size }), className)}\n />\n )\n }\n\n // Adornment path: wrap in a relative container that inherits the input's\n // OWN direction. This is the key bug fix: when a password input declares\n // `dir=\"ltr\"` inside an `dir=\"rtl\"` page, the adornment must be positioned\n // relative to the input's flow (right side for LTR), not the page's flow\n // (which would put it under the typed characters).\n return (\n <div data-slot=\"input-wrapper\" dir={resolvedDir} className=\"relative w-full\">\n {startAdornment && (\n <span\n data-slot=\"input-start-adornment\"\n className=\"pointer-events-none absolute start-3 top-1/2 flex -translate-y-1/2 items-center text-foreground-light [&_button]:pointer-events-auto\"\n >\n {startAdornment}\n </span>\n )}\n <input\n type={type}\n ref={ref}\n data-slot=\"input\"\n dir={resolvedDir}\n {...props}\n className={cn(inputVariants({ size }), startAdornment && 'ps-10', endAdornment && 'pe-10', className)}\n />\n {endAdornment && (\n <span\n data-slot=\"input-end-adornment\"\n className=\"pointer-events-none absolute end-3 top-1/2 flex -translate-y-1/2 items-center text-foreground-light [&_button]:pointer-events-auto\"\n >\n {endAdornment}\n </span>\n )}\n </div>\n )\n }\n)\n\nInput.displayName = 'Input'\n\nexport { Input }\n","'use client'\n\nimport * as React from 'react'\nimport { Eye, EyeOff } from 'lucide-react'\nimport { Input, type InputProps } from './input'\n\n/**\n * Password input with built-in show/hide toggle.\n *\n * Why this exists as a dedicated component rather than asking consumers to\n * compose Input + endAdornment:\n *\n * 1. **Stable direction across visibility toggles.** A naive composition\n * flips the `type` between `password` and `text` on click. The base\n * `Input` auto-resolves `dir=\"ltr\"` only for the `password` type — so\n * when type becomes `text`, the input wrapper drops back to the page's\n * `rtl` direction and the eye-icon adornment visibly jumps to the\n * opposite side mid-interaction. This component forces `dir=\"ltr\"`\n * once for the whole lifecycle.\n *\n * 2. **Standard pattern.** Mantine `<PasswordInput>`, Chakra `<InputGroup>`,\n * MUI `<TextField type=\"password\" endAdornment>` — every major DS\n * surfaces this as a first-class component because the composition is\n * fiddly and easy to get wrong.\n *\n * 3. **Persian aria labels by default.** \"نمایش رمز\" / \"مخفی کردن رمز\"\n * so consumers don't have to remember to localize the toggle.\n */\nexport interface PasswordInputProps extends Omit<InputProps, 'type' | 'endAdornment'> {\n /**\n * Show the eye toggle button. Defaults to true. Set to false for cases\n * where you want a pure password field (e.g. forms with a separate\n * \"show password\" checkbox).\n */\n enableVisibilityToggle?: boolean\n /**\n * Aria-label for the toggle when the password is currently hidden.\n * Defaults to \"نمایش رمز\".\n */\n showLabel?: string\n /**\n * Aria-label for the toggle when the password is currently visible.\n * Defaults to \"مخفی کردن رمز\".\n */\n hideLabel?: string\n}\n\nconst PasswordInput = React.forwardRef<HTMLInputElement, PasswordInputProps>(\n ({ enableVisibilityToggle = true, showLabel = 'نمایش رمز', hideLabel = 'مخفی کردن رمز', dir, ...props }, ref) => {\n const [visible, setVisible] = React.useState(false)\n\n return (\n <Input\n ref={ref}\n type={visible ? 'text' : 'password'}\n // Force ltr so the adornment stays on the same side when the type\n // toggles between password and text. Consumer can still override by\n // explicitly passing `dir`.\n dir={dir ?? 'ltr'}\n data-slot=\"password-input\"\n endAdornment={\n enableVisibilityToggle ? (\n <button\n type=\"button\"\n onClick={() => setVisible((v) => !v)}\n // tabIndex=-1: keep keyboard flow on submit, not on the toggle.\n tabIndex={-1}\n className=\"text-foreground-light hover:text-foreground transition-colors\"\n aria-label={visible ? hideLabel : showLabel}\n aria-pressed={visible}\n >\n {visible ? <EyeOff size={15} aria-hidden=\"true\" /> : <Eye size={15} aria-hidden=\"true\" />}\n </button>\n ) : undefined\n }\n {...props}\n />\n )\n }\n)\n\nPasswordInput.displayName = 'PasswordInput'\n\nexport { PasswordInput }\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../src/lib/constants.ts","../../../src/lib/utils.ts","../../../src/components/ui/input.tsx","../../../src/components/ui/password-input.tsx"],"names":["React","jsx"],"mappings":";;;;;;;;AAoBO,IAAM,IAAA,GAAO;AAAA,EAClB,IAAA,EAAM;AAAA,IACJ,EAAA,EAAI,SAAA;AAAA,IACJ,EAAA,EAAI,mBAAA;AAAA,IACJ,EAAA,EAAI,SAAA;AAAA,IACJ,EAAA,EAAI,WAAA;AAAA,IACJ,EAAA,EAAI;AAAA,GACN;AAAA,EACA,OAAA,EAAS;AAAA,IACP,EAAA,EAAI,aAAA;AAAA,IACJ,EAAA,EAAI,WAAA;AAAA,IACJ,EAAA,EAAI,WAAA;AAAA,IACJ,EAAA,EAAI,WAAA;AAAA,IACJ,EAAA,EAAI;AAAA,GACN;AAAA,EACA,MAAA,EAAQ;AAAA,IACN,EAAA,EAAI,UAAA;AAAA,IACJ,EAAA,EAAI,UAAA;AAAA,IACJ,EAAA,EAAI,UAAA;AAAA,IACJ,EAAA,EAAI,UAAA;AAAA,IACJ,EAAA,EAAI;AAAA;AAER,CAAA;AA2BO,IAAM,aAAA,GAAgB;AAAA,EAC3B,EAAA,EAAI,CAAA,EAAG,IAAA,CAAK,IAAA,CAAK,IAAI,CAAC,CAAA,CAAA,EAAI,IAAA,CAAK,OAAA,CAAQ,IAAI,CAAC,CAAA,CAAA,EAAI,IAAA,CAAK,MAAA,CAAO,IAAI,CAAC,CAAA,CAAA;AAAA,EACjE,EAAA,EAAI,CAAA,EAAG,IAAA,CAAK,IAAA,CAAK,IAAI,CAAC,CAAA,CAAA,EAAI,IAAA,CAAK,OAAA,CAAQ,IAAI,CAAC,CAAA,CAAA,EAAI,IAAA,CAAK,MAAA,CAAO,IAAI,CAAC,CAAA,CAAA;AAAA,EACjE,EAAA,EAAI,CAAA,EAAG,IAAA,CAAK,IAAA,CAAK,IAAI,CAAC,CAAA,CAAA,EAAI,IAAA,CAAK,OAAA,CAAQ,IAAI,CAAC,CAAA,CAAA,EAAI,IAAA,CAAK,MAAA,CAAO,IAAI,CAAC,CAAA,CAAA;AAAA,EACjE,EAAA,EAAI,CAAA,EAAG,IAAA,CAAK,IAAA,CAAK,IAAI,CAAC,CAAA,CAAA,EAAI,IAAA,CAAK,OAAA,CAAQ,IAAI,CAAC,CAAA,CAAA,EAAI,IAAA,CAAK,MAAA,CAAO,IAAI,CAAC,CAAA,CAAA;AAAA,EACjE,EAAA,EAAI,CAAA,EAAG,IAAA,CAAK,IAAA,CAAK,IAAI,CAAC,CAAA,CAAA,EAAI,IAAA,CAAK,OAAA,CAAQ,IAAI,CAAC,CAAA,CAAA,EAAI,IAAA,CAAK,MAAA,CAAO,IAAI,CAAC,CAAA;AACnE,CAAA;ACvEO,SAAS,MAAM,MAAA,EAAsB;AAC1C,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;ACCO,IAAM,aAAA,GAAgB,GAAA;AAAA,EAC3B,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAME,6QAAA;AAAA,IACA,gCAAA;AAAA,IACA,0LAAA;AAAA,IACA;AAAA,GACF;AAAA,EACA;AAAA,IACE,QAAA,EAAU;AAAA,MACR,IAAA,EAAM;AAAA,QACJ,GAAG;AAAA;AACL,KACF;AAAA,IACA,eAAA,EAAiB;AAAA,MACf,IAAA,EAAM;AAAA;AACR;AAEJ,CAAA;AAwBA,IAAM,eAAA,uBAAsB,GAAA,CAAI,CAAC,SAAS,KAAA,EAAO,KAAA,EAAO,QAAA,EAAU,UAAU,CAAC,CAAA;AAE7E,IAAM,KAAA,GAAcA,MAAA,CAAA,UAAA;AAAA,EAClB,CAAC,EAAE,SAAA,EAAW,IAAA,EAAM,IAAA,GAAO,IAAA,EAAM,GAAA,EAAK,cAAA,EAAgB,YAAA,EAAc,GAAG,KAAA,EAAM,EAAG,GAAA,KAAQ;AAEtF,IAAA,MAAM,cAAc,GAAA,KAAQ,IAAA,IAAQ,gBAAgB,GAAA,CAAI,IAAI,IAAI,KAAA,GAAQ,MAAA,CAAA;AAIxE,IAAA,IAAI,CAAC,cAAA,IAAkB,CAAC,YAAA,EAAc;AACpC,MAAA,uBACE,GAAA;AAAA,QAAC,OAAA;AAAA,QAAA;AAAA,UACC,IAAA;AAAA,UACA,GAAA;AAAA,UACA,WAAA,EAAU,OAAA;AAAA,UACV,GAAA,EAAK,WAAA;AAAA,UACJ,GAAG,KAAA;AAAA,UACJ,WAAW,EAAA,CAAG,aAAA,CAAc,EAAE,IAAA,EAAM,GAAG,SAAS;AAAA;AAAA,OAClD;AAAA,IAEJ;AAOA,IAAA,4BACG,KAAA,EAAA,EAAI,WAAA,EAAU,iBAAgB,GAAA,EAAK,WAAA,EAAa,WAAU,iBAAA,EACxD,QAAA,EAAA;AAAA,MAAA,cAAA,oBACC,GAAA;AAAA,QAAC,MAAA;AAAA,QAAA;AAAA,UACC,WAAA,EAAU,uBAAA;AAAA,UACV,SAAA,EAAU,sIAAA;AAAA,UAET,QAAA,EAAA;AAAA;AAAA,OACH;AAAA,sBAEF,GAAA;AAAA,QAAC,OAAA;AAAA,QAAA;AAAA,UACC,IAAA;AAAA,UACA,GAAA;AAAA,UACA,WAAA,EAAU,OAAA;AAAA,UACV,GAAA,EAAK,WAAA;AAAA,UACJ,GAAG,KAAA;AAAA,UACJ,SAAA,EAAW,EAAA,CAAG,aAAA,CAAc,EAAE,IAAA,EAAM,CAAA,EAAG,cAAA,IAAkB,OAAA,EAAS,YAAA,IAAgB,OAAA,EAAS,SAAS;AAAA;AAAA,OACtG;AAAA,MACC,YAAA,oBACC,GAAA;AAAA,QAAC,MAAA;AAAA,QAAA;AAAA,UACC,WAAA,EAAU,qBAAA;AAAA,UACV,SAAA,EAAU,oIAAA;AAAA,UAET,QAAA,EAAA;AAAA;AAAA;AACH,KAAA,EAEJ,CAAA;AAAA,EAEJ;AACF,CAAA;AAEA,KAAA,CAAM,WAAA,GAAc,OAAA;AChEpB,IAAM,aAAA,GAAsB,MAAA,CAAA,UAAA;AAAA,EAC1B,CAAC,EAAE,sBAAA,GAAyB,IAAA,EAAM,SAAA,GAAY,mDAAA,EAAa,SAAA,GAAY,sEAAA,EAAiB,GAAA,EAAK,GAAG,KAAA,EAAM,EAAG,GAAA,KAAQ;AAC/G,IAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAU,gBAAS,KAAK,CAAA;AAElD,IAAA,uBACEC,GAAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,GAAA;AAAA,QACA,IAAA,EAAM,UAAU,MAAA,GAAS,UAAA;AAAA,QAIzB,KAAK,GAAA,IAAO,KAAA;AAAA,QACZ,WAAA,EAAU,gBAAA;AAAA,QACV,YAAA,EACE,yCACEA,GAAAA;AAAA,UAAC,QAAA;AAAA,UAAA;AAAA,YACC,IAAA,EAAK,QAAA;AAAA,YACL,SAAS,MAAM,UAAA,CAAW,CAAC,CAAA,KAAM,CAAC,CAAC,CAAA;AAAA,YAEnC,QAAA,EAAU,EAAA;AAAA,YACV,SAAA,EAAU,+DAAA;AAAA,YACV,YAAA,EAAY,UAAU,SAAA,GAAY,SAAA;AAAA,YAClC,cAAA,EAAc,OAAA;AAAA,YAEb,QAAA,EAAA,OAAA,mBAAUA,GAAAA,CAAC,MAAA,EAAA,EAAO,MAAM,EAAA,EAAI,aAAA,EAAY,MAAA,EAAO,CAAA,mBAAKA,GAAAA,CAAC,GAAA,EAAA,EAAI,IAAA,EAAM,EAAA,EAAI,eAAY,MAAA,EAAO;AAAA;AAAA,SACzF,GACE,MAAA;AAAA,QAEL,GAAG;AAAA;AAAA,KACN;AAAA,EAEJ;AACF;AAEA,aAAA,CAAc,WAAA,GAAc,eAAA","file":"password-input.js","sourcesContent":["/**\n * Standard size scale used across the design system.\n * Components should accept these values for their `size` prop.\n */\nexport type StandardSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'\n\n/** @deprecated Use `StandardSize` instead — legacy names have been removed */\nexport type LegacySize = StandardSize\n\n/** @deprecated Use `StandardSize` instead */\nexport type SizeWithLegacy = StandardSize\n\n/**\n * Normalizes a size value to standard names.\n * @deprecated All sizes are standard now — this function is a no-op passthrough.\n */\nexport function normalizeSize(size: StandardSize): StandardSize {\n return size\n}\n\nexport const SIZE = {\n text: {\n xs: 'text-xs',\n sm: 'text-sm leading-4',\n md: 'text-sm',\n lg: 'text-base',\n xl: 'text-base',\n },\n padding: {\n xs: 'px-2.5 py-1',\n sm: 'px-3 py-2',\n md: 'px-4 py-2',\n lg: 'px-4 py-2',\n xl: 'px-6 py-3',\n },\n height: {\n xs: 'h-[26px]',\n sm: 'h-[34px]',\n md: 'h-[38px]',\n lg: 'h-[42px]',\n xl: 'h-[50px]',\n },\n}\n\n/** Inner sizes for nested elements (badges inside buttons, multi-select items, etc.) */\nexport const SIZE_INNER = {\n text: {\n xs: 'text-xs',\n sm: 'text-sm leading-4',\n md: 'text-sm',\n lg: 'text-base',\n xl: 'text-base',\n },\n padding: {\n xs: 'px-2.5 py-1',\n sm: 'px-3 py-2',\n md: 'px-4 py-2',\n lg: 'px-4 py-2',\n xl: 'px-6 py-3',\n },\n height: {\n xs: 'h-[24px]',\n sm: 'h-[28px]',\n md: 'h-[32px]',\n lg: 'h-[36px]',\n xl: 'h-[44px]',\n },\n}\n\nexport const SIZE_VARIANTS = {\n xs: `${SIZE.text['xs']} ${SIZE.padding['xs']} ${SIZE.height['xs']}`,\n sm: `${SIZE.text['sm']} ${SIZE.padding['sm']} ${SIZE.height['sm']}`,\n md: `${SIZE.text['md']} ${SIZE.padding['md']} ${SIZE.height['md']}`,\n lg: `${SIZE.text['lg']} ${SIZE.padding['lg']} ${SIZE.height['lg']}`,\n xl: `${SIZE.text['xl']} ${SIZE.padding['xl']} ${SIZE.height['xl']}`,\n}\n\nexport const SIZE_VARIANTS_INNER = {\n xs: `${SIZE.text['xs']} ${SIZE.padding['xs']} ${SIZE_INNER.height['xs']}`,\n sm: `${SIZE.text['sm']} ${SIZE.padding['sm']} ${SIZE_INNER.height['sm']}`,\n md: `${SIZE.text['md']} ${SIZE.padding['md']} ${SIZE_INNER.height['md']}`,\n lg: `${SIZE.text['lg']} ${SIZE.padding['lg']} ${SIZE_INNER.height['lg']}`,\n xl: `${SIZE.text['xl']} ${SIZE.padding['xl']} ${SIZE_INNER.height['xl']}`,\n}\n\n/** @deprecated Use 'sm' instead */\nexport const SIZE_VARIANTS_DEFAULT = 'sm'\n","import { type ClassValue, clsx } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\nimport { formatJalaliDate } from '@/lib/jalali-utils'\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n\nexport type SupportedLocale = 'fa' | 'ar' | 'en'\n\n/**\n * Convert digits in a string to Persian/Arabic numerals based on locale.\n * @example convertToLocalNumbers('123', 'fa') => '۱۲۳'\n * @example convertToLocalNumbers('123', 'en') => '123'\n */\nexport function convertToLocalNumbers(text: string | number, locale: SupportedLocale): string {\n if (locale === 'fa' || locale === 'ar') {\n const persianDigits = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']\n return String(text).replace(/\\d/g, (digit) => persianDigits[parseInt(digit)])\n }\n return String(text)\n}\n\n/**\n * Format large numbers with locale-aware suffixes (K/M/B).\n * @example formatLargeNumber(1500, 'fa') => '۱.۵ هزار'\n * @example formatLargeNumber(1500, 'en') => '1.5K'\n */\nexport function formatLargeNumber(num: number, locale: SupportedLocale): string {\n if (num >= 1_000_000_000) {\n const formatted = (num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'B' : ' میلیارد')\n }\n if (num >= 1_000_000) {\n const formatted = (num / 1_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'M' : ' میلیون')\n }\n if (num >= 1_000) {\n const formatted = (num / 1_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'K' : ' هزار')\n }\n return convertToLocalNumbers(num.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ','), locale)\n}\n\n/**\n * Format number to Instagram-style short format (English only).\n * @example formatNumber(123456, 'short') => '123K'\n * @example formatNumber(123456, 'exact') => '123,456'\n */\nexport function formatNumber(num: number | undefined, format: 'exact' | 'short' = 'exact'): string {\n if (num === undefined || num === null) return '0'\n\n if (format === 'exact') {\n return num.toLocaleString('en-US')\n }\n\n // Short format (Instagram style)\n if (num >= 1_000_000_000) {\n return `${(num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')}B`\n }\n if (num >= 1_000_000) {\n return `${(num / 1_000_000).toFixed(1).replace(/\\.0$/, '')}M`\n }\n if (num >= 1_000) {\n return `${(num / 1_000).toFixed(1).replace(/\\.0$/, '')}K`\n }\n return num.toString()\n}\n\n/**\n * Format date to relative time with absolute on hover (Persian)\n * @example formatRelativeTime(new Date()) => '۲ ساعت پیش'\n */\nexport function formatRelativeTime(date: Date | string | number): string {\n const now = new Date()\n const then = new Date(date)\n const diffInSeconds = Math.floor((now.getTime() - then.getTime()) / 1000)\n\n if (diffInSeconds < 60) {\n return 'همین الان'\n }\n\n const diffInMinutes = Math.floor(diffInSeconds / 60)\n if (diffInMinutes < 60) {\n return `${convertToLocalNumbers(diffInMinutes, 'fa')} دقیقه پیش`\n }\n\n const diffInHours = Math.floor(diffInMinutes / 60)\n if (diffInHours < 24) {\n return `${convertToLocalNumbers(diffInHours, 'fa')} ساعت پیش`\n }\n\n const diffInDays = Math.floor(diffInHours / 24)\n if (diffInDays < 7) {\n return `${convertToLocalNumbers(diffInDays, 'fa')} روز پیش`\n }\n\n const diffInWeeks = Math.floor(diffInDays / 7)\n if (diffInWeeks < 4) {\n return `${convertToLocalNumbers(diffInWeeks, 'fa')} هفته پیش`\n }\n\n const diffInMonths = Math.floor(diffInDays / 30)\n if (diffInMonths < 12) {\n return `${convertToLocalNumbers(diffInMonths, 'fa')} ماه پیش`\n }\n\n const diffInYears = Math.floor(diffInDays / 365)\n return `${convertToLocalNumbers(diffInYears, 'fa')} سال پیش`\n}\n\n/**\n * Format date to absolute format (Persian / Jalali)\n * Uses date-fns-jalali for accurate Jalali conversion.\n * @example formatAbsoluteTime(new Date()) => '۱۵ دی ۱۴۰۳، ۱۵:۳۰'\n */\nexport function formatAbsoluteTime(date: Date | string | number): string {\n const d = new Date(date)\n return formatJalaliDate(d, 'd MMMM yyyy، HH:mm')\n}\n","'use client'\n\nimport { VariantProps, cva } from 'class-variance-authority'\nimport * as React from 'react'\nimport { SIZE_VARIANTS } from '@/lib/constants'\nimport { cn } from '@/lib/utils'\n\nexport const inputVariants = cva(\n cn(\n // 1.1.16 — bg-control (now 19% in dark) instead of foreground/2.6%\n // opacity overlay, so the input is visibly distinct from any Card\n // it sits inside. text-start makes text-align follow the input's\n // own `dir` (set explicitly or auto for LTR types), so an email\n // field with `dir=\"ltr\"` is left-aligned even in an RTL form.\n 'flex w-full rounded-md border border-control read-only:border-button bg-control text-sm text-foreground text-start file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-foreground-lighter read-only:text-foreground-light',\n 'transition-colors duration-150',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50',\n 'aria-[invalid=true]:bg-destructive-200 dark:aria-[invalid=true]:bg-destructive/5 aria-[invalid=true]:border-destructive-400 dark:aria-[invalid=true]:border-destructive-default aria-[invalid=true]:focus:border-destructive aria-[invalid=true]:focus-visible:border-destructive'\n ),\n {\n variants: {\n size: {\n ...SIZE_VARIANTS,\n },\n },\n defaultVariants: {\n size: 'sm',\n },\n }\n)\n\n/** @deprecated Use inputVariants instead */\nexport const InputVariants = inputVariants\n\nexport interface InputProps\n extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'>, VariantProps<typeof inputVariants> {\n /**\n * Node rendered at the start of the input (left in LTR, right in RTL).\n * Common use: a search icon, a country flag, a currency symbol.\n * The adornment wrapper is `pointer-events-none` by default so it doesn't\n * steal focus from the input — interactive elements inside (e.g. a\n * `<button>` for a password show/hide toggle) get `pointer-events-auto`\n * back automatically.\n */\n startAdornment?: React.ReactNode\n /**\n * Node rendered at the end of the input (right in LTR, left in RTL).\n * Common use: a password show/hide toggle, a clear button, a unit suffix.\n * See `startAdornment` for the pointer-events behavior.\n */\n endAdornment?: React.ReactNode\n}\n\nconst LTR_INPUT_TYPES = new Set(['email', 'url', 'tel', 'number', 'password'])\n\nconst Input = React.forwardRef<HTMLInputElement, InputProps>(\n ({ className, type, size = 'sm', dir, startAdornment, endAdornment, ...props }, ref) => {\n // LTR content types: auto-set dir=\"ltr\" unless caller explicitly overrides\n const resolvedDir = dir ?? (type && LTR_INPUT_TYPES.has(type) ? 'ltr' : undefined)\n\n // Fast path: no adornments → render the bare <input>. Zero wrapper cost,\n // identical to the pre-1.1.14 behavior for backward compatibility.\n if (!startAdornment && !endAdornment) {\n return (\n <input\n type={type}\n ref={ref}\n data-slot=\"input\"\n dir={resolvedDir}\n {...props}\n className={cn(inputVariants({ size }), className)}\n />\n )\n }\n\n // Adornment path: wrap in a relative container that inherits the input's\n // OWN direction. This is the key bug fix: when a password input declares\n // `dir=\"ltr\"` inside an `dir=\"rtl\"` page, the adornment must be positioned\n // relative to the input's flow (right side for LTR), not the page's flow\n // (which would put it under the typed characters).\n return (\n <div data-slot=\"input-wrapper\" dir={resolvedDir} className=\"relative w-full\">\n {startAdornment && (\n <span\n data-slot=\"input-start-adornment\"\n className=\"pointer-events-none absolute start-3 top-1/2 flex -translate-y-1/2 items-center text-foreground-light [&_button]:pointer-events-auto\"\n >\n {startAdornment}\n </span>\n )}\n <input\n type={type}\n ref={ref}\n data-slot=\"input\"\n dir={resolvedDir}\n {...props}\n className={cn(inputVariants({ size }), startAdornment && 'ps-10', endAdornment && 'pe-10', className)}\n />\n {endAdornment && (\n <span\n data-slot=\"input-end-adornment\"\n className=\"pointer-events-none absolute end-3 top-1/2 flex -translate-y-1/2 items-center text-foreground-light [&_button]:pointer-events-auto\"\n >\n {endAdornment}\n </span>\n )}\n </div>\n )\n }\n)\n\nInput.displayName = 'Input'\n\nexport { Input }\n","'use client'\n\nimport * as React from 'react'\nimport { Eye, EyeOff } from 'lucide-react'\nimport { Input, type InputProps } from './input'\n\n/**\n * Password input with built-in show/hide toggle.\n *\n * Why this exists as a dedicated component rather than asking consumers to\n * compose Input + endAdornment:\n *\n * 1. **Stable direction across visibility toggles.** A naive composition\n * flips the `type` between `password` and `text` on click. The base\n * `Input` auto-resolves `dir=\"ltr\"` only for the `password` type — so\n * when type becomes `text`, the input wrapper drops back to the page's\n * `rtl` direction and the eye-icon adornment visibly jumps to the\n * opposite side mid-interaction. This component forces `dir=\"ltr\"`\n * once for the whole lifecycle.\n *\n * 2. **Standard pattern.** Mantine `<PasswordInput>`, Chakra `<InputGroup>`,\n * MUI `<TextField type=\"password\" endAdornment>` — every major DS\n * surfaces this as a first-class component because the composition is\n * fiddly and easy to get wrong.\n *\n * 3. **Persian aria labels by default.** \"نمایش رمز\" / \"مخفی کردن رمز\"\n * so consumers don't have to remember to localize the toggle.\n */\nexport interface PasswordInputProps extends Omit<InputProps, 'type' | 'endAdornment'> {\n /**\n * Show the eye toggle button. Defaults to true. Set to false for cases\n * where you want a pure password field (e.g. forms with a separate\n * \"show password\" checkbox).\n */\n enableVisibilityToggle?: boolean\n /**\n * Aria-label for the toggle when the password is currently hidden.\n * Defaults to \"نمایش رمز\".\n */\n showLabel?: string\n /**\n * Aria-label for the toggle when the password is currently visible.\n * Defaults to \"مخفی کردن رمز\".\n */\n hideLabel?: string\n}\n\nconst PasswordInput = React.forwardRef<HTMLInputElement, PasswordInputProps>(\n ({ enableVisibilityToggle = true, showLabel = 'نمایش رمز', hideLabel = 'مخفی کردن رمز', dir, ...props }, ref) => {\n const [visible, setVisible] = React.useState(false)\n\n return (\n <Input\n ref={ref}\n type={visible ? 'text' : 'password'}\n // Force ltr so the adornment stays on the same side when the type\n // toggles between password and text. Consumer can still override by\n // explicitly passing `dir`.\n dir={dir ?? 'ltr'}\n data-slot=\"password-input\"\n endAdornment={\n enableVisibilityToggle ? (\n <button\n type=\"button\"\n onClick={() => setVisible((v) => !v)}\n // tabIndex=-1: keep keyboard flow on submit, not on the toggle.\n tabIndex={-1}\n className=\"text-foreground-light hover:text-foreground transition-colors\"\n aria-label={visible ? hideLabel : showLabel}\n aria-pressed={visible}\n >\n {visible ? <EyeOff size={15} aria-hidden=\"true\" /> : <Eye size={15} aria-hidden=\"true\" />}\n </button>\n ) : undefined\n }\n {...props}\n />\n )\n }\n)\n\nPasswordInput.displayName = 'PasswordInput'\n\nexport { PasswordInput }\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../src/lib/utils.ts","../../../src/components/ui/popover.tsx"],"names":["twMerge","clsx","PopoverPrimitive","React","jsx"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAIO,SAAS,MAAM,MAAA,EAAsB;AAC1C,EAAA,OAAOA,qBAAA,CAAQC,SAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;ACCA,IAAM,OAAA,GAA2BC,2BAAA,CAAA;AAEjC,IAAM,cAAA,GAAkCA,2BAAA,CAAA;AAExC,IAAM,aAAA,GAAiCA,2BAAA,CAAA;AAEvC,IAAM,cAAA,GAAuBC,gBAAA,CAAA,UAAA,CAG3B,CAAC,EAAE,WAAW,KAAA,GAAQ,QAAA,EAAU,UAAA,GAAa,CAAA,EAAG,GAAG,KAAA,EAAM,EAAG,GAAA,qBAC5DC,cAAA,CAAkBF,oCAAjB,EACC,QAAA,kBAAAE,cAAA;AAAA,EAAkBF,2BAAA,CAAA,OAAA;AAAA,EAAjB;AAAA,IACC,GAAA;AAAA,IACA,WAAA,EAAU,iBAAA;AAAA,IACV,KAAA;AAAA,IACA,UAAA;AAAA,IACA,SAAA,EAAW,EAAA;AAAA,MACT,geAAA;AAAA,MACA;AAAA,KACF;AAAA,IACC,GAAG;AAAA;AACN,CAAA,EACF,CACD;AACD,cAAA,CAAe,cAA+BA,2BAAA,CAAA,OAAA,CAAQ,WAAA","file":"popover.cjs","sourcesContent":["import { type ClassValue, clsx } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\nimport { formatJalaliDate } from '@/lib/jalali-utils'\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n\nexport type SupportedLocale = 'fa' | 'ar' | 'en'\n\n/**\n * Convert digits in a string to Persian/Arabic numerals based on locale.\n * @example convertToLocalNumbers('123', 'fa') => '۱۲۳'\n * @example convertToLocalNumbers('123', 'en') => '123'\n */\nexport function convertToLocalNumbers(text: string | number, locale: SupportedLocale): string {\n if (locale === 'fa' || locale === 'ar') {\n const persianDigits = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']\n return String(text).replace(/\\d/g, (digit) => persianDigits[parseInt(digit)])\n }\n return String(text)\n}\n\n/**\n * Format large numbers with locale-aware suffixes (K/M/B).\n * @example formatLargeNumber(1500, 'fa') => '۱.۵ هزار'\n * @example formatLargeNumber(1500, 'en') => '1.5K'\n */\nexport function formatLargeNumber(num: number, locale: SupportedLocale): string {\n if (num >= 1_000_000_000) {\n const formatted = (num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'B' : ' میلیارد')\n }\n if (num >= 1_000_000) {\n const formatted = (num / 1_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'M' : ' میلیون')\n }\n if (num >= 1_000) {\n const formatted = (num / 1_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'K' : ' هزار')\n }\n return convertToLocalNumbers(num.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ','), locale)\n}\n\n/**\n * Format number to Instagram-style short format (English only).\n * @example formatNumber(123456, 'short') => '123K'\n * @example formatNumber(123456, 'exact') => '123,456'\n */\nexport function formatNumber(num: number | undefined, format: 'exact' | 'short' = 'exact'): string {\n if (num === undefined || num === null) return '0'\n\n if (format === 'exact') {\n return num.toLocaleString('en-US')\n }\n\n // Short format (Instagram style)\n if (num >= 1_000_000_000) {\n return `${(num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')}B`\n }\n if (num >= 1_000_000) {\n return `${(num / 1_000_000).toFixed(1).replace(/\\.0$/, '')}M`\n }\n if (num >= 1_000) {\n return `${(num / 1_000).toFixed(1).replace(/\\.0$/, '')}K`\n }\n return num.toString()\n}\n\n/**\n * Format date to relative time with absolute on hover (Persian)\n * @example formatRelativeTime(new Date()) => '۲ ساعت پیش'\n */\nexport function formatRelativeTime(date: Date | string | number): string {\n const now = new Date()\n const then = new Date(date)\n const diffInSeconds = Math.floor((now.getTime() - then.getTime()) / 1000)\n\n if (diffInSeconds < 60) {\n return 'همین الان'\n }\n\n const diffInMinutes = Math.floor(diffInSeconds / 60)\n if (diffInMinutes < 60) {\n return `${convertToLocalNumbers(diffInMinutes, 'fa')} دقیقه پیش`\n }\n\n const diffInHours = Math.floor(diffInMinutes / 60)\n if (diffInHours < 24) {\n return `${convertToLocalNumbers(diffInHours, 'fa')} ساعت پیش`\n }\n\n const diffInDays = Math.floor(diffInHours / 24)\n if (diffInDays < 7) {\n return `${convertToLocalNumbers(diffInDays, 'fa')} روز پیش`\n }\n\n const diffInWeeks = Math.floor(diffInDays / 7)\n if (diffInWeeks < 4) {\n return `${convertToLocalNumbers(diffInWeeks, 'fa')} هفته پیش`\n }\n\n const diffInMonths = Math.floor(diffInDays / 30)\n if (diffInMonths < 12) {\n return `${convertToLocalNumbers(diffInMonths, 'fa')} ماه پیش`\n }\n\n const diffInYears = Math.floor(diffInDays / 365)\n return `${convertToLocalNumbers(diffInYears, 'fa')} سال پیش`\n}\n\n/**\n * Format date to absolute format (Persian / Jalali)\n * Uses date-fns-jalali for accurate Jalali conversion.\n * @example formatAbsoluteTime(new Date()) => '۱۵ دی ۱۴۰۳، ۱۵:۳۰'\n */\nexport function formatAbsoluteTime(date: Date | string | number): string {\n const d = new Date(date)\n return formatJalaliDate(d, 'd MMMM yyyy، HH:mm')\n}\n","'use client'\n\nimport * as React from 'react'\nimport * as PopoverPrimitive from '@radix-ui/react-popover'\n\nimport { cn } from '@/lib/utils'\n\nconst Popover = PopoverPrimitive.Root\n\nconst PopoverTrigger = PopoverPrimitive.Trigger\n\nconst PopoverAnchor = PopoverPrimitive.Anchor\n\nconst PopoverContent = React.forwardRef<\n React.ElementRef<typeof PopoverPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>\n>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (\n <PopoverPrimitive.Portal>\n <PopoverPrimitive.Content\n ref={ref}\n data-slot=\"popover-content\"\n align={align}\n sideOffset={sideOffset}\n className={cn(\n 'z-50 rounded-md border border-overlay bg-overlay p-4 text-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-popover-content-transform-origin]',\n className\n )}\n {...props}\n />\n </PopoverPrimitive.Portal>\n))\nPopoverContent.displayName = PopoverPrimitive.Content.displayName\n\nexport { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../src/lib/utils.ts","../../../src/components/ui/popover.tsx"],"names":[],"mappings":";;;;;;AAIO,SAAS,MAAM,MAAA,EAAsB;AAC1C,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;ACCA,IAAM,OAAA,GAA2B,gBAAA,CAAA;AAEjC,IAAM,cAAA,GAAkC,gBAAA,CAAA;AAExC,IAAM,aAAA,GAAiC,gBAAA,CAAA;AAEvC,IAAM,cAAA,GAAuB,KAAA,CAAA,UAAA,CAG3B,CAAC,EAAE,WAAW,KAAA,GAAQ,QAAA,EAAU,UAAA,GAAa,CAAA,EAAG,GAAG,KAAA,EAAM,EAAG,GAAA,qBAC5D,GAAA,CAAkB,yBAAjB,EACC,QAAA,kBAAA,GAAA;AAAA,EAAkB,gBAAA,CAAA,OAAA;AAAA,EAAjB;AAAA,IACC,GAAA;AAAA,IACA,WAAA,EAAU,iBAAA;AAAA,IACV,KAAA;AAAA,IACA,UAAA;AAAA,IACA,SAAA,EAAW,EAAA;AAAA,MACT,geAAA;AAAA,MACA;AAAA,KACF;AAAA,IACC,GAAG;AAAA;AACN,CAAA,EACF,CACD;AACD,cAAA,CAAe,cAA+B,gBAAA,CAAA,OAAA,CAAQ,WAAA","file":"popover.js","sourcesContent":["import { type ClassValue, clsx } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\nimport { formatJalaliDate } from '@/lib/jalali-utils'\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n\nexport type SupportedLocale = 'fa' | 'ar' | 'en'\n\n/**\n * Convert digits in a string to Persian/Arabic numerals based on locale.\n * @example convertToLocalNumbers('123', 'fa') => '۱۲۳'\n * @example convertToLocalNumbers('123', 'en') => '123'\n */\nexport function convertToLocalNumbers(text: string | number, locale: SupportedLocale): string {\n if (locale === 'fa' || locale === 'ar') {\n const persianDigits = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']\n return String(text).replace(/\\d/g, (digit) => persianDigits[parseInt(digit)])\n }\n return String(text)\n}\n\n/**\n * Format large numbers with locale-aware suffixes (K/M/B).\n * @example formatLargeNumber(1500, 'fa') => '۱.۵ هزار'\n * @example formatLargeNumber(1500, 'en') => '1.5K'\n */\nexport function formatLargeNumber(num: number, locale: SupportedLocale): string {\n if (num >= 1_000_000_000) {\n const formatted = (num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'B' : ' میلیارد')\n }\n if (num >= 1_000_000) {\n const formatted = (num / 1_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'M' : ' میلیون')\n }\n if (num >= 1_000) {\n const formatted = (num / 1_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'K' : ' هزار')\n }\n return convertToLocalNumbers(num.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ','), locale)\n}\n\n/**\n * Format number to Instagram-style short format (English only).\n * @example formatNumber(123456, 'short') => '123K'\n * @example formatNumber(123456, 'exact') => '123,456'\n */\nexport function formatNumber(num: number | undefined, format: 'exact' | 'short' = 'exact'): string {\n if (num === undefined || num === null) return '0'\n\n if (format === 'exact') {\n return num.toLocaleString('en-US')\n }\n\n // Short format (Instagram style)\n if (num >= 1_000_000_000) {\n return `${(num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')}B`\n }\n if (num >= 1_000_000) {\n return `${(num / 1_000_000).toFixed(1).replace(/\\.0$/, '')}M`\n }\n if (num >= 1_000) {\n return `${(num / 1_000).toFixed(1).replace(/\\.0$/, '')}K`\n }\n return num.toString()\n}\n\n/**\n * Format date to relative time with absolute on hover (Persian)\n * @example formatRelativeTime(new Date()) => '۲ ساعت پیش'\n */\nexport function formatRelativeTime(date: Date | string | number): string {\n const now = new Date()\n const then = new Date(date)\n const diffInSeconds = Math.floor((now.getTime() - then.getTime()) / 1000)\n\n if (diffInSeconds < 60) {\n return 'همین الان'\n }\n\n const diffInMinutes = Math.floor(diffInSeconds / 60)\n if (diffInMinutes < 60) {\n return `${convertToLocalNumbers(diffInMinutes, 'fa')} دقیقه پیش`\n }\n\n const diffInHours = Math.floor(diffInMinutes / 60)\n if (diffInHours < 24) {\n return `${convertToLocalNumbers(diffInHours, 'fa')} ساعت پیش`\n }\n\n const diffInDays = Math.floor(diffInHours / 24)\n if (diffInDays < 7) {\n return `${convertToLocalNumbers(diffInDays, 'fa')} روز پیش`\n }\n\n const diffInWeeks = Math.floor(diffInDays / 7)\n if (diffInWeeks < 4) {\n return `${convertToLocalNumbers(diffInWeeks, 'fa')} هفته پیش`\n }\n\n const diffInMonths = Math.floor(diffInDays / 30)\n if (diffInMonths < 12) {\n return `${convertToLocalNumbers(diffInMonths, 'fa')} ماه پیش`\n }\n\n const diffInYears = Math.floor(diffInDays / 365)\n return `${convertToLocalNumbers(diffInYears, 'fa')} سال پیش`\n}\n\n/**\n * Format date to absolute format (Persian / Jalali)\n * Uses date-fns-jalali for accurate Jalali conversion.\n * @example formatAbsoluteTime(new Date()) => '۱۵ دی ۱۴۰۳، ۱۵:۳۰'\n */\nexport function formatAbsoluteTime(date: Date | string | number): string {\n const d = new Date(date)\n return formatJalaliDate(d, 'd MMMM yyyy، HH:mm')\n}\n","'use client'\n\nimport * as React from 'react'\nimport * as PopoverPrimitive from '@radix-ui/react-popover'\n\nimport { cn } from '@/lib/utils'\n\nconst Popover = PopoverPrimitive.Root\n\nconst PopoverTrigger = PopoverPrimitive.Trigger\n\nconst PopoverAnchor = PopoverPrimitive.Anchor\n\nconst PopoverContent = React.forwardRef<\n React.ElementRef<typeof PopoverPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>\n>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (\n <PopoverPrimitive.Portal>\n <PopoverPrimitive.Content\n ref={ref}\n data-slot=\"popover-content\"\n align={align}\n sideOffset={sideOffset}\n className={cn(\n 'z-50 rounded-md border border-overlay bg-overlay p-4 text-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-popover-content-transform-origin]',\n className\n )}\n {...props}\n />\n </PopoverPrimitive.Portal>\n))\nPopoverContent.displayName = PopoverPrimitive.Content.displayName\n\nexport { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../src/lib/utils.ts","../../../src/components/ui/progress.tsx"],"names":["twMerge","clsx","cva","React","jsxs","jsx","ProgressPrimitive"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAIO,SAAS,MAAM,MAAA,EAAsB;AAC1C,EAAA,OAAOA,qBAAA,CAAQC,SAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;ACEA,IAAM,gBAAA,GAAmBC,2BAAI,6DAAA,EAA+D;AAAA,EAC1F,QAAA,EAAU;AAAA,IACR,IAAA,EAAM;AAAA,MACJ,EAAA,EAAI,KAAA;AAAA,MACJ,EAAA,EAAI,KAAA;AAAA,MACJ,EAAA,EAAI;AAAA;AACN,GACF;AAAA,EACA,eAAA,EAAiB;AAAA,IACf,IAAA,EAAM;AAAA;AAEV,CAAC,CAAA;AAED,IAAM,yBAAA,GAA4BA,2BAAI,kEAAA,EAAoE;AAAA,EACxG,QAAA,EAAU;AAAA,IACR,OAAA,EAAS;AAAA,MACP,OAAA,EAAS,gCAAA;AAAA,MACT,SAAA,EAAW,eAAA;AAAA,MACX,OAAA,EAAS,uBAAA;AAAA,MACT,OAAA,EAAS,gBAAA;AAAA,MACT,WAAA,EAAa;AAAA;AACf,GACF;AAAA,EACA,eAAA,EAAiB;AAAA,IACf,OAAA,EAAS;AAAA;AAEb,CAAC,CAAA;AAWD,IAAM,QAAA,GAAiBC,gBAAA,CAAA,UAAA;AAAA,EACrB,CAAC,EAAE,SAAA,EAAW,KAAA,EAAO,IAAA,EAAM,OAAA,EAAS,KAAA,EAAO,SAAA,GAAY,KAAA,EAAO,GAAG,KAAA,EAAM,EAAG,GAAA,KAAQ;AAChF,IAAA,MAAM,YAAA,GAAe,KAAK,GAAA,CAAI,CAAA,EAAG,KAAK,GAAA,CAAI,GAAA,EAAK,KAAA,IAAS,CAAC,CAAC,CAAA;AAC1D,IAAA,MAAM,YAAA,GAAe,CAAA,EAAG,IAAA,CAAK,KAAA,CAAM,YAAY,CAAC,CAAA,CAAA,CAAA;AAEhD,IAAA,uBACEC,eAAA,CAAC,KAAA,EAAA,EAAI,WAAA,EAAU,kBAAA,EAAmB,WAAU,kBAAA,EACxC,QAAA,EAAA;AAAA,MAAA,CAAA,KAAA,IAAS,SAAA,qBACTA,eAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,2CAAA,EACZ,QAAA,EAAA;AAAA,QAAA,KAAA,oBAASC,cAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,iBAAA,EAAmB,QAAA,EAAA,KAAA,EAAM,CAAA;AAAA,QAClD,SAAA,oBAAaA,cAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,qCAAqC,QAAA,EAAA,YAAA,EAAa;AAAA,OAAA,EAClF,CAAA;AAAA,sBAEFA,cAAA;AAAA,QAAmBC,4BAAA,CAAA,IAAA;AAAA,QAAlB;AAAA,UACC,GAAA;AAAA,UACA,WAAA,EAAU,UAAA;AAAA,UACV,WAAW,EAAA,CAAG,gBAAA,CAAiB,EAAE,IAAA,EAAM,GAAG,SAAS,CAAA;AAAA,UACnD,KAAA;AAAA,UACC,GAAG,KAAA;AAAA,UAEJ,QAAA,kBAAAD,cAAA;AAAA,YAAmBC,4BAAA,CAAA,SAAA;AAAA,YAAlB;AAAA,cACC,WAAA,EAAU,oBAAA;AAAA,cACV,WAAW,EAAA,CAAG,yBAAA,CAA0B,EAAE,OAAA,EAAS,CAAC,CAAA;AAAA,cACpD,OAAO,EAAE,SAAA,EAAW,CAAA,OAAA,EAAU,YAAA,GAAe,GAAG,CAAA,CAAA,CAAA;AAAI;AAAA;AACtD;AAAA;AACF,KAAA,EACF,CAAA;AAAA,EAEJ;AACF;AAEA,QAAA,CAAS,WAAA,GAAc,UAAA","file":"progress.cjs","sourcesContent":["import { type ClassValue, clsx } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\nimport { formatJalaliDate } from '@/lib/jalali-utils'\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n\nexport type SupportedLocale = 'fa' | 'ar' | 'en'\n\n/**\n * Convert digits in a string to Persian/Arabic numerals based on locale.\n * @example convertToLocalNumbers('123', 'fa') => '۱۲۳'\n * @example convertToLocalNumbers('123', 'en') => '123'\n */\nexport function convertToLocalNumbers(text: string | number, locale: SupportedLocale): string {\n if (locale === 'fa' || locale === 'ar') {\n const persianDigits = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']\n return String(text).replace(/\\d/g, (digit) => persianDigits[parseInt(digit)])\n }\n return String(text)\n}\n\n/**\n * Format large numbers with locale-aware suffixes (K/M/B).\n * @example formatLargeNumber(1500, 'fa') => '۱.۵ هزار'\n * @example formatLargeNumber(1500, 'en') => '1.5K'\n */\nexport function formatLargeNumber(num: number, locale: SupportedLocale): string {\n if (num >= 1_000_000_000) {\n const formatted = (num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'B' : ' میلیارد')\n }\n if (num >= 1_000_000) {\n const formatted = (num / 1_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'M' : ' میلیون')\n }\n if (num >= 1_000) {\n const formatted = (num / 1_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'K' : ' هزار')\n }\n return convertToLocalNumbers(num.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ','), locale)\n}\n\n/**\n * Format number to Instagram-style short format (English only).\n * @example formatNumber(123456, 'short') => '123K'\n * @example formatNumber(123456, 'exact') => '123,456'\n */\nexport function formatNumber(num: number | undefined, format: 'exact' | 'short' = 'exact'): string {\n if (num === undefined || num === null) return '0'\n\n if (format === 'exact') {\n return num.toLocaleString('en-US')\n }\n\n // Short format (Instagram style)\n if (num >= 1_000_000_000) {\n return `${(num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')}B`\n }\n if (num >= 1_000_000) {\n return `${(num / 1_000_000).toFixed(1).replace(/\\.0$/, '')}M`\n }\n if (num >= 1_000) {\n return `${(num / 1_000).toFixed(1).replace(/\\.0$/, '')}K`\n }\n return num.toString()\n}\n\n/**\n * Format date to relative time with absolute on hover (Persian)\n * @example formatRelativeTime(new Date()) => '۲ ساعت پیش'\n */\nexport function formatRelativeTime(date: Date | string | number): string {\n const now = new Date()\n const then = new Date(date)\n const diffInSeconds = Math.floor((now.getTime() - then.getTime()) / 1000)\n\n if (diffInSeconds < 60) {\n return 'همین الان'\n }\n\n const diffInMinutes = Math.floor(diffInSeconds / 60)\n if (diffInMinutes < 60) {\n return `${convertToLocalNumbers(diffInMinutes, 'fa')} دقیقه پیش`\n }\n\n const diffInHours = Math.floor(diffInMinutes / 60)\n if (diffInHours < 24) {\n return `${convertToLocalNumbers(diffInHours, 'fa')} ساعت پیش`\n }\n\n const diffInDays = Math.floor(diffInHours / 24)\n if (diffInDays < 7) {\n return `${convertToLocalNumbers(diffInDays, 'fa')} روز پیش`\n }\n\n const diffInWeeks = Math.floor(diffInDays / 7)\n if (diffInWeeks < 4) {\n return `${convertToLocalNumbers(diffInWeeks, 'fa')} هفته پیش`\n }\n\n const diffInMonths = Math.floor(diffInDays / 30)\n if (diffInMonths < 12) {\n return `${convertToLocalNumbers(diffInMonths, 'fa')} ماه پیش`\n }\n\n const diffInYears = Math.floor(diffInDays / 365)\n return `${convertToLocalNumbers(diffInYears, 'fa')} سال پیش`\n}\n\n/**\n * Format date to absolute format (Persian / Jalali)\n * Uses date-fns-jalali for accurate Jalali conversion.\n * @example formatAbsoluteTime(new Date()) => '۱۵ دی ۱۴۰۳، ۱۵:۳۰'\n */\nexport function formatAbsoluteTime(date: Date | string | number): string {\n const d = new Date(date)\n return formatJalaliDate(d, 'd MMMM yyyy، HH:mm')\n}\n","'use client'\n\nimport * as React from 'react'\nimport * as ProgressPrimitive from '@radix-ui/react-progress'\nimport { cva, type VariantProps } from 'class-variance-authority'\n\nimport { cn } from '@/lib/utils'\n\nconst progressVariants = cva('relative w-full overflow-hidden rounded-full bg-surface-300', {\n variants: {\n size: {\n sm: 'h-1',\n md: 'h-2',\n lg: 'h-3',\n },\n },\n defaultVariants: {\n size: 'md',\n },\n})\n\nconst progressIndicatorVariants = cva('h-full w-full flex-1 transition-all origin-left rtl:origin-right', {\n variants: {\n variant: {\n primary: 'bg-[hsl(var(--brand-default))]',\n secondary: 'bg-foreground',\n success: 'bg-sentiment-positive',\n warning: 'bg-warning-500',\n destructive: 'bg-destructive-500',\n },\n },\n defaultVariants: {\n variant: 'primary',\n },\n})\n\nexport interface ProgressProps\n extends\n React.ComponentProps<typeof ProgressPrimitive.Root>,\n VariantProps<typeof progressVariants>,\n VariantProps<typeof progressIndicatorVariants> {\n label?: string\n showValue?: boolean\n}\n\nconst Progress = React.forwardRef<React.ElementRef<typeof ProgressPrimitive.Root>, ProgressProps>(\n ({ className, value, size, variant, label, showValue = false, ...props }, ref) => {\n const clampedValue = Math.max(0, Math.min(100, value ?? 0))\n const displayValue = `${Math.round(clampedValue)}%`\n\n return (\n <div data-slot=\"progress-wrapper\" className=\"w-full space-y-2\">\n {(label || showValue) && (\n <div className=\"flex items-center justify-between text-sm\">\n {label && <span className=\"text-foreground\">{label}</span>}\n {showValue && <span className=\"text-foreground-light font-medium\">{displayValue}</span>}\n </div>\n )}\n <ProgressPrimitive.Root\n ref={ref}\n data-slot=\"progress\"\n className={cn(progressVariants({ size }), className)}\n value={value}\n {...props}\n >\n <ProgressPrimitive.Indicator\n data-slot=\"progress-indicator\"\n className={cn(progressIndicatorVariants({ variant }))}\n style={{ transform: `scaleX(${clampedValue / 100})` }}\n />\n </ProgressPrimitive.Root>\n </div>\n )\n }\n)\n\nProgress.displayName = 'Progress'\n\nexport { Progress }\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../src/lib/utils.ts","../../../src/components/ui/progress.tsx"],"names":[],"mappings":";;;;;;;AAIO,SAAS,MAAM,MAAA,EAAsB;AAC1C,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;ACEA,IAAM,gBAAA,GAAmB,IAAI,6DAAA,EAA+D;AAAA,EAC1F,QAAA,EAAU;AAAA,IACR,IAAA,EAAM;AAAA,MACJ,EAAA,EAAI,KAAA;AAAA,MACJ,EAAA,EAAI,KAAA;AAAA,MACJ,EAAA,EAAI;AAAA;AACN,GACF;AAAA,EACA,eAAA,EAAiB;AAAA,IACf,IAAA,EAAM;AAAA;AAEV,CAAC,CAAA;AAED,IAAM,yBAAA,GAA4B,IAAI,kEAAA,EAAoE;AAAA,EACxG,QAAA,EAAU;AAAA,IACR,OAAA,EAAS;AAAA,MACP,OAAA,EAAS,gCAAA;AAAA,MACT,SAAA,EAAW,eAAA;AAAA,MACX,OAAA,EAAS,uBAAA;AAAA,MACT,OAAA,EAAS,gBAAA;AAAA,MACT,WAAA,EAAa;AAAA;AACf,GACF;AAAA,EACA,eAAA,EAAiB;AAAA,IACf,OAAA,EAAS;AAAA;AAEb,CAAC,CAAA;AAWD,IAAM,QAAA,GAAiB,KAAA,CAAA,UAAA;AAAA,EACrB,CAAC,EAAE,SAAA,EAAW,KAAA,EAAO,IAAA,EAAM,OAAA,EAAS,KAAA,EAAO,SAAA,GAAY,KAAA,EAAO,GAAG,KAAA,EAAM,EAAG,GAAA,KAAQ;AAChF,IAAA,MAAM,YAAA,GAAe,KAAK,GAAA,CAAI,CAAA,EAAG,KAAK,GAAA,CAAI,GAAA,EAAK,KAAA,IAAS,CAAC,CAAC,CAAA;AAC1D,IAAA,MAAM,YAAA,GAAe,CAAA,EAAG,IAAA,CAAK,KAAA,CAAM,YAAY,CAAC,CAAA,CAAA,CAAA;AAEhD,IAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,WAAA,EAAU,kBAAA,EAAmB,WAAU,kBAAA,EACxC,QAAA,EAAA;AAAA,MAAA,CAAA,KAAA,IAAS,SAAA,qBACT,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,2CAAA,EACZ,QAAA,EAAA;AAAA,QAAA,KAAA,oBAAS,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,iBAAA,EAAmB,QAAA,EAAA,KAAA,EAAM,CAAA;AAAA,QAClD,SAAA,oBAAa,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,qCAAqC,QAAA,EAAA,YAAA,EAAa;AAAA,OAAA,EAClF,CAAA;AAAA,sBAEF,GAAA;AAAA,QAAmB,iBAAA,CAAA,IAAA;AAAA,QAAlB;AAAA,UACC,GAAA;AAAA,UACA,WAAA,EAAU,UAAA;AAAA,UACV,WAAW,EAAA,CAAG,gBAAA,CAAiB,EAAE,IAAA,EAAM,GAAG,SAAS,CAAA;AAAA,UACnD,KAAA;AAAA,UACC,GAAG,KAAA;AAAA,UAEJ,QAAA,kBAAA,GAAA;AAAA,YAAmB,iBAAA,CAAA,SAAA;AAAA,YAAlB;AAAA,cACC,WAAA,EAAU,oBAAA;AAAA,cACV,WAAW,EAAA,CAAG,yBAAA,CAA0B,EAAE,OAAA,EAAS,CAAC,CAAA;AAAA,cACpD,OAAO,EAAE,SAAA,EAAW,CAAA,OAAA,EAAU,YAAA,GAAe,GAAG,CAAA,CAAA,CAAA;AAAI;AAAA;AACtD;AAAA;AACF,KAAA,EACF,CAAA;AAAA,EAEJ;AACF;AAEA,QAAA,CAAS,WAAA,GAAc,UAAA","file":"progress.js","sourcesContent":["import { type ClassValue, clsx } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\nimport { formatJalaliDate } from '@/lib/jalali-utils'\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n\nexport type SupportedLocale = 'fa' | 'ar' | 'en'\n\n/**\n * Convert digits in a string to Persian/Arabic numerals based on locale.\n * @example convertToLocalNumbers('123', 'fa') => '۱۲۳'\n * @example convertToLocalNumbers('123', 'en') => '123'\n */\nexport function convertToLocalNumbers(text: string | number, locale: SupportedLocale): string {\n if (locale === 'fa' || locale === 'ar') {\n const persianDigits = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']\n return String(text).replace(/\\d/g, (digit) => persianDigits[parseInt(digit)])\n }\n return String(text)\n}\n\n/**\n * Format large numbers with locale-aware suffixes (K/M/B).\n * @example formatLargeNumber(1500, 'fa') => '۱.۵ هزار'\n * @example formatLargeNumber(1500, 'en') => '1.5K'\n */\nexport function formatLargeNumber(num: number, locale: SupportedLocale): string {\n if (num >= 1_000_000_000) {\n const formatted = (num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'B' : ' میلیارد')\n }\n if (num >= 1_000_000) {\n const formatted = (num / 1_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'M' : ' میلیون')\n }\n if (num >= 1_000) {\n const formatted = (num / 1_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'K' : ' هزار')\n }\n return convertToLocalNumbers(num.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ','), locale)\n}\n\n/**\n * Format number to Instagram-style short format (English only).\n * @example formatNumber(123456, 'short') => '123K'\n * @example formatNumber(123456, 'exact') => '123,456'\n */\nexport function formatNumber(num: number | undefined, format: 'exact' | 'short' = 'exact'): string {\n if (num === undefined || num === null) return '0'\n\n if (format === 'exact') {\n return num.toLocaleString('en-US')\n }\n\n // Short format (Instagram style)\n if (num >= 1_000_000_000) {\n return `${(num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')}B`\n }\n if (num >= 1_000_000) {\n return `${(num / 1_000_000).toFixed(1).replace(/\\.0$/, '')}M`\n }\n if (num >= 1_000) {\n return `${(num / 1_000).toFixed(1).replace(/\\.0$/, '')}K`\n }\n return num.toString()\n}\n\n/**\n * Format date to relative time with absolute on hover (Persian)\n * @example formatRelativeTime(new Date()) => '۲ ساعت پیش'\n */\nexport function formatRelativeTime(date: Date | string | number): string {\n const now = new Date()\n const then = new Date(date)\n const diffInSeconds = Math.floor((now.getTime() - then.getTime()) / 1000)\n\n if (diffInSeconds < 60) {\n return 'همین الان'\n }\n\n const diffInMinutes = Math.floor(diffInSeconds / 60)\n if (diffInMinutes < 60) {\n return `${convertToLocalNumbers(diffInMinutes, 'fa')} دقیقه پیش`\n }\n\n const diffInHours = Math.floor(diffInMinutes / 60)\n if (diffInHours < 24) {\n return `${convertToLocalNumbers(diffInHours, 'fa')} ساعت پیش`\n }\n\n const diffInDays = Math.floor(diffInHours / 24)\n if (diffInDays < 7) {\n return `${convertToLocalNumbers(diffInDays, 'fa')} روز پیش`\n }\n\n const diffInWeeks = Math.floor(diffInDays / 7)\n if (diffInWeeks < 4) {\n return `${convertToLocalNumbers(diffInWeeks, 'fa')} هفته پیش`\n }\n\n const diffInMonths = Math.floor(diffInDays / 30)\n if (diffInMonths < 12) {\n return `${convertToLocalNumbers(diffInMonths, 'fa')} ماه پیش`\n }\n\n const diffInYears = Math.floor(diffInDays / 365)\n return `${convertToLocalNumbers(diffInYears, 'fa')} سال پیش`\n}\n\n/**\n * Format date to absolute format (Persian / Jalali)\n * Uses date-fns-jalali for accurate Jalali conversion.\n * @example formatAbsoluteTime(new Date()) => '۱۵ دی ۱۴۰۳، ۱۵:۳۰'\n */\nexport function formatAbsoluteTime(date: Date | string | number): string {\n const d = new Date(date)\n return formatJalaliDate(d, 'd MMMM yyyy، HH:mm')\n}\n","'use client'\n\nimport * as React from 'react'\nimport * as ProgressPrimitive from '@radix-ui/react-progress'\nimport { cva, type VariantProps } from 'class-variance-authority'\n\nimport { cn } from '@/lib/utils'\n\nconst progressVariants = cva('relative w-full overflow-hidden rounded-full bg-surface-300', {\n variants: {\n size: {\n sm: 'h-1',\n md: 'h-2',\n lg: 'h-3',\n },\n },\n defaultVariants: {\n size: 'md',\n },\n})\n\nconst progressIndicatorVariants = cva('h-full w-full flex-1 transition-all origin-left rtl:origin-right', {\n variants: {\n variant: {\n primary: 'bg-[hsl(var(--brand-default))]',\n secondary: 'bg-foreground',\n success: 'bg-sentiment-positive',\n warning: 'bg-warning-500',\n destructive: 'bg-destructive-500',\n },\n },\n defaultVariants: {\n variant: 'primary',\n },\n})\n\nexport interface ProgressProps\n extends\n React.ComponentProps<typeof ProgressPrimitive.Root>,\n VariantProps<typeof progressVariants>,\n VariantProps<typeof progressIndicatorVariants> {\n label?: string\n showValue?: boolean\n}\n\nconst Progress = React.forwardRef<React.ElementRef<typeof ProgressPrimitive.Root>, ProgressProps>(\n ({ className, value, size, variant, label, showValue = false, ...props }, ref) => {\n const clampedValue = Math.max(0, Math.min(100, value ?? 0))\n const displayValue = `${Math.round(clampedValue)}%`\n\n return (\n <div data-slot=\"progress-wrapper\" className=\"w-full space-y-2\">\n {(label || showValue) && (\n <div className=\"flex items-center justify-between text-sm\">\n {label && <span className=\"text-foreground\">{label}</span>}\n {showValue && <span className=\"text-foreground-light font-medium\">{displayValue}</span>}\n </div>\n )}\n <ProgressPrimitive.Root\n ref={ref}\n data-slot=\"progress\"\n className={cn(progressVariants({ size }), className)}\n value={value}\n {...props}\n >\n <ProgressPrimitive.Indicator\n data-slot=\"progress-indicator\"\n className={cn(progressIndicatorVariants({ variant }))}\n style={{ transform: `scaleX(${clampedValue / 100})` }}\n />\n </ProgressPrimitive.Root>\n </div>\n )\n }\n)\n\nProgress.displayName = 'Progress'\n\nexport { Progress }\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../src/lib/utils.ts","../../../src/components/ui/radio-card.tsx"],"names":["twMerge","clsx","React","jsx","RadioGroupPrimitive"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAIO,SAAS,MAAM,MAAA,EAAsB;AAC1C,EAAA,OAAOA,qBAAA,CAAQC,SAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;ACCA,IAAM,UAAA,GAAmBC,gBAAA,CAAA,UAAA,CAMvB,CAAC,EAAE,WAAW,OAAA,GAAU,CAAA,EAAG,GAAA,GAAM,KAAA,EAAO,KAAA,EAAO,QAAA,EAAU,GAAG,KAAA,IAAS,GAAA,KAAQ;AAG7E,EAAA,MAAM,SAAA,GACJ,OAAO,OAAA,KAAY,QAAA,GAAW,EAAE,qBAAqB,CAAA,OAAA,EAAU,OAAO,CAAA,iBAAA,CAAA,EAAoB,GAAI,EAAC;AAEjG,EAAA,uBACEC,cAAA;AAAA,IAAqBC,8BAAA,CAAA,IAAA;AAAA,IAApB;AAAA,MACC,GAAA;AAAA,MACA,WAAA,EAAU,aAAA;AAAA,MACV,GAAA;AAAA,MACA,SAAA,EAAW,EAAA,CAAG,YAAA,EAAc,SAAS,CAAA;AAAA,MACrC,KAAA,EAAO,EAAE,GAAG,SAAA,EAAW,GAAG,KAAA,EAAM;AAAA,MAC/B,GAAG,KAAA;AAAA,MAEH;AAAA;AAAA,GACH;AAEJ,CAAC;AACD,UAAA,CAAW,WAAA,GAAc,YAAA;AAEzB,IAAM,aAAA,GAAsBF,4BAG1B,CAAC,EAAE,WAAW,QAAA,EAAU,GAAG,KAAA,EAAM,EAAG,GAAA,KAAQ;AAC5C,EAAA,uBACEC,cAAA;AAAA,IAAqBC,8BAAA,CAAA,IAAA;AAAA,IAApB;AAAA,MACC,GAAA;AAAA,MACA,WAAA,EAAU,iBAAA;AAAA,MACV,SAAA,EAAW,EAAA;AAAA,QACT,uGAAA;AAAA,QACA,4CAAA;AAAA,QACA,sGAAA;AAAA,QACA,mEAAA;AAAA,QACA,iDAAA;AAAA,QACA;AAAA,OACF;AAAA,MACC,GAAG,KAAA;AAAA,MAEH;AAAA;AAAA,GACH;AAEJ,CAAC;AACD,aAAA,CAAc,WAAA,GAAc,eAAA;AAE5B,IAAM,cAAA,GAAuBF,gBAAA,CAAA,UAAA;AAAA,EAC3B,CAAC,EAAE,SAAA,EAAW,GAAG,KAAA,IAAS,GAAA,qBACxBC,cAAA,CAAC,IAAA,EAAA,EAAG,GAAA,EAAU,WAAW,EAAA,CAAG,oCAAA,EAAsC,SAAS,CAAA,EAAI,GAAG,KAAA,EAAO;AAE7F;AACA,cAAA,CAAe,WAAA,GAAc,gBAAA;AAE7B,IAAM,oBAAA,GAA6BD,gBAAA,CAAA,UAAA;AAAA,EACjC,CAAC,EAAE,SAAA,EAAW,GAAG,KAAA,IAAS,GAAA,qBACxBC,cAAA,CAAC,GAAA,EAAA,EAAE,GAAA,EAAU,WAAW,EAAA,CAAG,+BAAA,EAAiC,SAAS,CAAA,EAAI,GAAG,KAAA,EAAO;AAEvF;AACA,oBAAA,CAAqB,WAAA,GAAc,sBAAA","file":"radio-card.cjs","sourcesContent":["import { type ClassValue, clsx } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\nimport { formatJalaliDate } from '@/lib/jalali-utils'\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n\nexport type SupportedLocale = 'fa' | 'ar' | 'en'\n\n/**\n * Convert digits in a string to Persian/Arabic numerals based on locale.\n * @example convertToLocalNumbers('123', 'fa') => '۱۲۳'\n * @example convertToLocalNumbers('123', 'en') => '123'\n */\nexport function convertToLocalNumbers(text: string | number, locale: SupportedLocale): string {\n if (locale === 'fa' || locale === 'ar') {\n const persianDigits = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']\n return String(text).replace(/\\d/g, (digit) => persianDigits[parseInt(digit)])\n }\n return String(text)\n}\n\n/**\n * Format large numbers with locale-aware suffixes (K/M/B).\n * @example formatLargeNumber(1500, 'fa') => '۱.۵ هزار'\n * @example formatLargeNumber(1500, 'en') => '1.5K'\n */\nexport function formatLargeNumber(num: number, locale: SupportedLocale): string {\n if (num >= 1_000_000_000) {\n const formatted = (num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'B' : ' میلیارد')\n }\n if (num >= 1_000_000) {\n const formatted = (num / 1_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'M' : ' میلیون')\n }\n if (num >= 1_000) {\n const formatted = (num / 1_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'K' : ' هزار')\n }\n return convertToLocalNumbers(num.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ','), locale)\n}\n\n/**\n * Format number to Instagram-style short format (English only).\n * @example formatNumber(123456, 'short') => '123K'\n * @example formatNumber(123456, 'exact') => '123,456'\n */\nexport function formatNumber(num: number | undefined, format: 'exact' | 'short' = 'exact'): string {\n if (num === undefined || num === null) return '0'\n\n if (format === 'exact') {\n return num.toLocaleString('en-US')\n }\n\n // Short format (Instagram style)\n if (num >= 1_000_000_000) {\n return `${(num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')}B`\n }\n if (num >= 1_000_000) {\n return `${(num / 1_000_000).toFixed(1).replace(/\\.0$/, '')}M`\n }\n if (num >= 1_000) {\n return `${(num / 1_000).toFixed(1).replace(/\\.0$/, '')}K`\n }\n return num.toString()\n}\n\n/**\n * Format date to relative time with absolute on hover (Persian)\n * @example formatRelativeTime(new Date()) => '۲ ساعت پیش'\n */\nexport function formatRelativeTime(date: Date | string | number): string {\n const now = new Date()\n const then = new Date(date)\n const diffInSeconds = Math.floor((now.getTime() - then.getTime()) / 1000)\n\n if (diffInSeconds < 60) {\n return 'همین الان'\n }\n\n const diffInMinutes = Math.floor(diffInSeconds / 60)\n if (diffInMinutes < 60) {\n return `${convertToLocalNumbers(diffInMinutes, 'fa')} دقیقه پیش`\n }\n\n const diffInHours = Math.floor(diffInMinutes / 60)\n if (diffInHours < 24) {\n return `${convertToLocalNumbers(diffInHours, 'fa')} ساعت پیش`\n }\n\n const diffInDays = Math.floor(diffInHours / 24)\n if (diffInDays < 7) {\n return `${convertToLocalNumbers(diffInDays, 'fa')} روز پیش`\n }\n\n const diffInWeeks = Math.floor(diffInDays / 7)\n if (diffInWeeks < 4) {\n return `${convertToLocalNumbers(diffInWeeks, 'fa')} هفته پیش`\n }\n\n const diffInMonths = Math.floor(diffInDays / 30)\n if (diffInMonths < 12) {\n return `${convertToLocalNumbers(diffInMonths, 'fa')} ماه پیش`\n }\n\n const diffInYears = Math.floor(diffInDays / 365)\n return `${convertToLocalNumbers(diffInYears, 'fa')} سال پیش`\n}\n\n/**\n * Format date to absolute format (Persian / Jalali)\n * Uses date-fns-jalali for accurate Jalali conversion.\n * @example formatAbsoluteTime(new Date()) => '۱۵ دی ۱۴۰۳، ۱۵:۳۰'\n */\nexport function formatAbsoluteTime(date: Date | string | number): string {\n const d = new Date(date)\n return formatJalaliDate(d, 'd MMMM yyyy، HH:mm')\n}\n","'use client'\n\nimport * as React from 'react'\nimport * as RadioGroupPrimitive from '@radix-ui/react-radio-group'\n\nimport { cn } from '@/lib/utils'\n\nconst RadioCards = React.forwardRef<\n React.ElementRef<typeof RadioGroupPrimitive.Root>,\n React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root> & {\n columns?: number\n dir?: 'rtl' | 'ltr'\n }\n>(({ className, columns = 1, dir = 'rtl', style, children, ...props }, ref) => {\n // Build grid-template-columns via inline style — Tailwind JIT can't handle\n // dynamic class names like `grid-cols-${n}` (they are purged at build time)\n const gridStyle: React.CSSProperties =\n typeof columns === 'number' ? { gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))` } : {}\n\n return (\n <RadioGroupPrimitive.Root\n ref={ref}\n data-slot=\"radio-cards\"\n dir={dir}\n className={cn('grid gap-3', className)}\n style={{ ...gridStyle, ...style }}\n {...props}\n >\n {children}\n </RadioGroupPrimitive.Root>\n )\n})\nRadioCards.displayName = 'RadioCards'\n\nconst RadioCardItem = React.forwardRef<\n React.ElementRef<typeof RadioGroupPrimitive.Item>,\n React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>\n>(({ className, children, ...props }, ref) => {\n return (\n <RadioGroupPrimitive.Item\n ref={ref}\n data-slot=\"radio-card-item\"\n className={cn(\n 'relative flex cursor-pointer rounded-lg border border-control bg-surface-100 px-4 py-3 transition-all',\n 'hover:border-brand/40 hover:bg-surface-200',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand focus-visible:ring-offset-2',\n 'data-[state=checked]:border-brand data-[state=checked]:bg-brand/5',\n 'disabled:cursor-not-allowed disabled:opacity-50',\n className\n )}\n {...props}\n >\n {children}\n </RadioGroupPrimitive.Item>\n )\n})\nRadioCardItem.displayName = 'RadioCardItem'\n\nconst RadioCardTitle = React.forwardRef<HTMLHeadingElement, React.HTMLAttributes<HTMLHeadingElement>>(\n ({ className, ...props }, ref) => (\n <h4 ref={ref} className={cn('text-sm font-semibold leading-none', className)} {...props} />\n )\n)\nRadioCardTitle.displayName = 'RadioCardTitle'\n\nconst RadioCardDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(\n ({ className, ...props }, ref) => (\n <p ref={ref} className={cn('text-sm text-foreground-muted', className)} {...props} />\n )\n)\nRadioCardDescription.displayName = 'RadioCardDescription'\n\nexport { RadioCards, RadioCardItem, RadioCardTitle, RadioCardDescription }\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../src/lib/utils.ts","../../../src/components/ui/radio-card.tsx"],"names":[],"mappings":";;;;;;AAIO,SAAS,MAAM,MAAA,EAAsB;AAC1C,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;ACCA,IAAM,UAAA,GAAmB,KAAA,CAAA,UAAA,CAMvB,CAAC,EAAE,WAAW,OAAA,GAAU,CAAA,EAAG,GAAA,GAAM,KAAA,EAAO,KAAA,EAAO,QAAA,EAAU,GAAG,KAAA,IAAS,GAAA,KAAQ;AAG7E,EAAA,MAAM,SAAA,GACJ,OAAO,OAAA,KAAY,QAAA,GAAW,EAAE,qBAAqB,CAAA,OAAA,EAAU,OAAO,CAAA,iBAAA,CAAA,EAAoB,GAAI,EAAC;AAEjG,EAAA,uBACE,GAAA;AAAA,IAAqB,mBAAA,CAAA,IAAA;AAAA,IAApB;AAAA,MACC,GAAA;AAAA,MACA,WAAA,EAAU,aAAA;AAAA,MACV,GAAA;AAAA,MACA,SAAA,EAAW,EAAA,CAAG,YAAA,EAAc,SAAS,CAAA;AAAA,MACrC,KAAA,EAAO,EAAE,GAAG,SAAA,EAAW,GAAG,KAAA,EAAM;AAAA,MAC/B,GAAG,KAAA;AAAA,MAEH;AAAA;AAAA,GACH;AAEJ,CAAC;AACD,UAAA,CAAW,WAAA,GAAc,YAAA;AAEzB,IAAM,aAAA,GAAsB,iBAG1B,CAAC,EAAE,WAAW,QAAA,EAAU,GAAG,KAAA,EAAM,EAAG,GAAA,KAAQ;AAC5C,EAAA,uBACE,GAAA;AAAA,IAAqB,mBAAA,CAAA,IAAA;AAAA,IAApB;AAAA,MACC,GAAA;AAAA,MACA,WAAA,EAAU,iBAAA;AAAA,MACV,SAAA,EAAW,EAAA;AAAA,QACT,uGAAA;AAAA,QACA,4CAAA;AAAA,QACA,sGAAA;AAAA,QACA,mEAAA;AAAA,QACA,iDAAA;AAAA,QACA;AAAA,OACF;AAAA,MACC,GAAG,KAAA;AAAA,MAEH;AAAA;AAAA,GACH;AAEJ,CAAC;AACD,aAAA,CAAc,WAAA,GAAc,eAAA;AAE5B,IAAM,cAAA,GAAuB,KAAA,CAAA,UAAA;AAAA,EAC3B,CAAC,EAAE,SAAA,EAAW,GAAG,KAAA,IAAS,GAAA,qBACxB,GAAA,CAAC,IAAA,EAAA,EAAG,GAAA,EAAU,WAAW,EAAA,CAAG,oCAAA,EAAsC,SAAS,CAAA,EAAI,GAAG,KAAA,EAAO;AAE7F;AACA,cAAA,CAAe,WAAA,GAAc,gBAAA;AAE7B,IAAM,oBAAA,GAA6B,KAAA,CAAA,UAAA;AAAA,EACjC,CAAC,EAAE,SAAA,EAAW,GAAG,KAAA,IAAS,GAAA,qBACxB,GAAA,CAAC,GAAA,EAAA,EAAE,GAAA,EAAU,WAAW,EAAA,CAAG,+BAAA,EAAiC,SAAS,CAAA,EAAI,GAAG,KAAA,EAAO;AAEvF;AACA,oBAAA,CAAqB,WAAA,GAAc,sBAAA","file":"radio-card.js","sourcesContent":["import { type ClassValue, clsx } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\nimport { formatJalaliDate } from '@/lib/jalali-utils'\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n\nexport type SupportedLocale = 'fa' | 'ar' | 'en'\n\n/**\n * Convert digits in a string to Persian/Arabic numerals based on locale.\n * @example convertToLocalNumbers('123', 'fa') => '۱۲۳'\n * @example convertToLocalNumbers('123', 'en') => '123'\n */\nexport function convertToLocalNumbers(text: string | number, locale: SupportedLocale): string {\n if (locale === 'fa' || locale === 'ar') {\n const persianDigits = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']\n return String(text).replace(/\\d/g, (digit) => persianDigits[parseInt(digit)])\n }\n return String(text)\n}\n\n/**\n * Format large numbers with locale-aware suffixes (K/M/B).\n * @example formatLargeNumber(1500, 'fa') => '۱.۵ هزار'\n * @example formatLargeNumber(1500, 'en') => '1.5K'\n */\nexport function formatLargeNumber(num: number, locale: SupportedLocale): string {\n if (num >= 1_000_000_000) {\n const formatted = (num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'B' : ' میلیارد')\n }\n if (num >= 1_000_000) {\n const formatted = (num / 1_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'M' : ' میلیون')\n }\n if (num >= 1_000) {\n const formatted = (num / 1_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'K' : ' هزار')\n }\n return convertToLocalNumbers(num.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ','), locale)\n}\n\n/**\n * Format number to Instagram-style short format (English only).\n * @example formatNumber(123456, 'short') => '123K'\n * @example formatNumber(123456, 'exact') => '123,456'\n */\nexport function formatNumber(num: number | undefined, format: 'exact' | 'short' = 'exact'): string {\n if (num === undefined || num === null) return '0'\n\n if (format === 'exact') {\n return num.toLocaleString('en-US')\n }\n\n // Short format (Instagram style)\n if (num >= 1_000_000_000) {\n return `${(num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')}B`\n }\n if (num >= 1_000_000) {\n return `${(num / 1_000_000).toFixed(1).replace(/\\.0$/, '')}M`\n }\n if (num >= 1_000) {\n return `${(num / 1_000).toFixed(1).replace(/\\.0$/, '')}K`\n }\n return num.toString()\n}\n\n/**\n * Format date to relative time with absolute on hover (Persian)\n * @example formatRelativeTime(new Date()) => '۲ ساعت پیش'\n */\nexport function formatRelativeTime(date: Date | string | number): string {\n const now = new Date()\n const then = new Date(date)\n const diffInSeconds = Math.floor((now.getTime() - then.getTime()) / 1000)\n\n if (diffInSeconds < 60) {\n return 'همین الان'\n }\n\n const diffInMinutes = Math.floor(diffInSeconds / 60)\n if (diffInMinutes < 60) {\n return `${convertToLocalNumbers(diffInMinutes, 'fa')} دقیقه پیش`\n }\n\n const diffInHours = Math.floor(diffInMinutes / 60)\n if (diffInHours < 24) {\n return `${convertToLocalNumbers(diffInHours, 'fa')} ساعت پیش`\n }\n\n const diffInDays = Math.floor(diffInHours / 24)\n if (diffInDays < 7) {\n return `${convertToLocalNumbers(diffInDays, 'fa')} روز پیش`\n }\n\n const diffInWeeks = Math.floor(diffInDays / 7)\n if (diffInWeeks < 4) {\n return `${convertToLocalNumbers(diffInWeeks, 'fa')} هفته پیش`\n }\n\n const diffInMonths = Math.floor(diffInDays / 30)\n if (diffInMonths < 12) {\n return `${convertToLocalNumbers(diffInMonths, 'fa')} ماه پیش`\n }\n\n const diffInYears = Math.floor(diffInDays / 365)\n return `${convertToLocalNumbers(diffInYears, 'fa')} سال پیش`\n}\n\n/**\n * Format date to absolute format (Persian / Jalali)\n * Uses date-fns-jalali for accurate Jalali conversion.\n * @example formatAbsoluteTime(new Date()) => '۱۵ دی ۱۴۰۳، ۱۵:۳۰'\n */\nexport function formatAbsoluteTime(date: Date | string | number): string {\n const d = new Date(date)\n return formatJalaliDate(d, 'd MMMM yyyy، HH:mm')\n}\n","'use client'\n\nimport * as React from 'react'\nimport * as RadioGroupPrimitive from '@radix-ui/react-radio-group'\n\nimport { cn } from '@/lib/utils'\n\nconst RadioCards = React.forwardRef<\n React.ElementRef<typeof RadioGroupPrimitive.Root>,\n React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root> & {\n columns?: number\n dir?: 'rtl' | 'ltr'\n }\n>(({ className, columns = 1, dir = 'rtl', style, children, ...props }, ref) => {\n // Build grid-template-columns via inline style — Tailwind JIT can't handle\n // dynamic class names like `grid-cols-${n}` (they are purged at build time)\n const gridStyle: React.CSSProperties =\n typeof columns === 'number' ? { gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))` } : {}\n\n return (\n <RadioGroupPrimitive.Root\n ref={ref}\n data-slot=\"radio-cards\"\n dir={dir}\n className={cn('grid gap-3', className)}\n style={{ ...gridStyle, ...style }}\n {...props}\n >\n {children}\n </RadioGroupPrimitive.Root>\n )\n})\nRadioCards.displayName = 'RadioCards'\n\nconst RadioCardItem = React.forwardRef<\n React.ElementRef<typeof RadioGroupPrimitive.Item>,\n React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>\n>(({ className, children, ...props }, ref) => {\n return (\n <RadioGroupPrimitive.Item\n ref={ref}\n data-slot=\"radio-card-item\"\n className={cn(\n 'relative flex cursor-pointer rounded-lg border border-control bg-surface-100 px-4 py-3 transition-all',\n 'hover:border-brand/40 hover:bg-surface-200',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand focus-visible:ring-offset-2',\n 'data-[state=checked]:border-brand data-[state=checked]:bg-brand/5',\n 'disabled:cursor-not-allowed disabled:opacity-50',\n className\n )}\n {...props}\n >\n {children}\n </RadioGroupPrimitive.Item>\n )\n})\nRadioCardItem.displayName = 'RadioCardItem'\n\nconst RadioCardTitle = React.forwardRef<HTMLHeadingElement, React.HTMLAttributes<HTMLHeadingElement>>(\n ({ className, ...props }, ref) => (\n <h4 ref={ref} className={cn('text-sm font-semibold leading-none', className)} {...props} />\n )\n)\nRadioCardTitle.displayName = 'RadioCardTitle'\n\nconst RadioCardDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(\n ({ className, ...props }, ref) => (\n <p ref={ref} className={cn('text-sm text-foreground-muted', className)} {...props} />\n )\n)\nRadioCardDescription.displayName = 'RadioCardDescription'\n\nexport { RadioCards, RadioCardItem, RadioCardTitle, RadioCardDescription }\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../src/lib/utils.ts","../../../src/components/ui/radio-group.tsx"],"names":["twMerge","clsx","React","jsx","RadioGroupPrimitive","CircleIcon"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAIO,SAAS,MAAM,MAAA,EAAsB;AAC1C,EAAA,OAAOA,qBAAA,CAAQC,SAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;ACEA,IAAM,UAAA,GAAmBC,4BAGvB,CAAC,EAAE,WAAW,GAAG,KAAA,EAAM,EAAG,GAAA,qBAC1BC,cAAA,CAAqBC,8BAAA,CAAA,IAAA,EAApB,EAAyB,GAAA,EAAU,WAAA,EAAU,eAAc,SAAA,EAAW,EAAA,CAAG,cAAc,SAAS,CAAA,EAAI,GAAG,KAAA,EAAO,CAChH;AAED,IAAM,cAAA,GAAuBF,4BAG3B,CAAC,EAAE,WAAW,GAAG,KAAA,IAAS,GAAA,qBAC1BC,cAAA;AAAA,EAAqBC,8BAAA,CAAA,IAAA;AAAA,EAApB;AAAA,IACC,GAAA;AAAA,IACA,WAAA,EAAU,kBAAA;AAAA,IACV,SAAA,EAAW,EAAA;AAAA,MACT,mXAAA;AAAA,MACA;AAAA,KACF;AAAA,IACC,GAAG,KAAA;AAAA,IAEJ,QAAA,kBAAAD,cAAA;AAAA,MAAqBC,8BAAA,CAAA,SAAA;AAAA,MAApB;AAAA,QACC,WAAA,EAAU,uBAAA;AAAA,QACV,SAAA,EAAU,2CAAA;AAAA,QAEV,QAAA,kBAAAD,cAAA,CAACE,sBAAA,EAAA,EAAW,SAAA,EAAU,oGAAA,EAAqG;AAAA;AAAA;AAC7H;AACF,CACD;AAED,UAAA,CAAW,WAAA,GAAc,YAAA;AACzB,cAAA,CAAe,WAAA,GAAc,gBAAA","file":"radio-group.cjs","sourcesContent":["import { type ClassValue, clsx } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\nimport { formatJalaliDate } from '@/lib/jalali-utils'\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n\nexport type SupportedLocale = 'fa' | 'ar' | 'en'\n\n/**\n * Convert digits in a string to Persian/Arabic numerals based on locale.\n * @example convertToLocalNumbers('123', 'fa') => '۱۲۳'\n * @example convertToLocalNumbers('123', 'en') => '123'\n */\nexport function convertToLocalNumbers(text: string | number, locale: SupportedLocale): string {\n if (locale === 'fa' || locale === 'ar') {\n const persianDigits = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']\n return String(text).replace(/\\d/g, (digit) => persianDigits[parseInt(digit)])\n }\n return String(text)\n}\n\n/**\n * Format large numbers with locale-aware suffixes (K/M/B).\n * @example formatLargeNumber(1500, 'fa') => '۱.۵ هزار'\n * @example formatLargeNumber(1500, 'en') => '1.5K'\n */\nexport function formatLargeNumber(num: number, locale: SupportedLocale): string {\n if (num >= 1_000_000_000) {\n const formatted = (num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'B' : ' میلیارد')\n }\n if (num >= 1_000_000) {\n const formatted = (num / 1_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'M' : ' میلیون')\n }\n if (num >= 1_000) {\n const formatted = (num / 1_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'K' : ' هزار')\n }\n return convertToLocalNumbers(num.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ','), locale)\n}\n\n/**\n * Format number to Instagram-style short format (English only).\n * @example formatNumber(123456, 'short') => '123K'\n * @example formatNumber(123456, 'exact') => '123,456'\n */\nexport function formatNumber(num: number | undefined, format: 'exact' | 'short' = 'exact'): string {\n if (num === undefined || num === null) return '0'\n\n if (format === 'exact') {\n return num.toLocaleString('en-US')\n }\n\n // Short format (Instagram style)\n if (num >= 1_000_000_000) {\n return `${(num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')}B`\n }\n if (num >= 1_000_000) {\n return `${(num / 1_000_000).toFixed(1).replace(/\\.0$/, '')}M`\n }\n if (num >= 1_000) {\n return `${(num / 1_000).toFixed(1).replace(/\\.0$/, '')}K`\n }\n return num.toString()\n}\n\n/**\n * Format date to relative time with absolute on hover (Persian)\n * @example formatRelativeTime(new Date()) => '۲ ساعت پیش'\n */\nexport function formatRelativeTime(date: Date | string | number): string {\n const now = new Date()\n const then = new Date(date)\n const diffInSeconds = Math.floor((now.getTime() - then.getTime()) / 1000)\n\n if (diffInSeconds < 60) {\n return 'همین الان'\n }\n\n const diffInMinutes = Math.floor(diffInSeconds / 60)\n if (diffInMinutes < 60) {\n return `${convertToLocalNumbers(diffInMinutes, 'fa')} دقیقه پیش`\n }\n\n const diffInHours = Math.floor(diffInMinutes / 60)\n if (diffInHours < 24) {\n return `${convertToLocalNumbers(diffInHours, 'fa')} ساعت پیش`\n }\n\n const diffInDays = Math.floor(diffInHours / 24)\n if (diffInDays < 7) {\n return `${convertToLocalNumbers(diffInDays, 'fa')} روز پیش`\n }\n\n const diffInWeeks = Math.floor(diffInDays / 7)\n if (diffInWeeks < 4) {\n return `${convertToLocalNumbers(diffInWeeks, 'fa')} هفته پیش`\n }\n\n const diffInMonths = Math.floor(diffInDays / 30)\n if (diffInMonths < 12) {\n return `${convertToLocalNumbers(diffInMonths, 'fa')} ماه پیش`\n }\n\n const diffInYears = Math.floor(diffInDays / 365)\n return `${convertToLocalNumbers(diffInYears, 'fa')} سال پیش`\n}\n\n/**\n * Format date to absolute format (Persian / Jalali)\n * Uses date-fns-jalali for accurate Jalali conversion.\n * @example formatAbsoluteTime(new Date()) => '۱۵ دی ۱۴۰۳، ۱۵:۳۰'\n */\nexport function formatAbsoluteTime(date: Date | string | number): string {\n const d = new Date(date)\n return formatJalaliDate(d, 'd MMMM yyyy، HH:mm')\n}\n","'use client'\n\nimport * as React from 'react'\nimport * as RadioGroupPrimitive from '@radix-ui/react-radio-group'\nimport { CircleIcon } from 'lucide-react'\n\nimport { cn } from '@/lib/utils'\n\nconst RadioGroup = React.forwardRef<\n React.ElementRef<typeof RadioGroupPrimitive.Root>,\n React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>\n>(({ className, ...props }, ref) => (\n <RadioGroupPrimitive.Root ref={ref} data-slot=\"radio-group\" className={cn('grid gap-3', className)} {...props} />\n))\n\nconst RadioGroupItem = React.forwardRef<\n React.ElementRef<typeof RadioGroupPrimitive.Item>,\n React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>\n>(({ className, ...props }, ref) => (\n <RadioGroupPrimitive.Item\n ref={ref}\n data-slot=\"radio-group-item\"\n className={cn(\n 'border-control bg-control data-[state=checked]:border-brand focus-visible:border-brand-default focus-visible:ring-brand-default/50 aria-invalid:ring-destructive/20 aria-invalid:border-destructive aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-all duration-150 outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',\n className\n )}\n {...props}\n >\n <RadioGroupPrimitive.Indicator\n data-slot=\"radio-group-indicator\"\n className=\"relative flex items-center justify-center\"\n >\n <CircleIcon className=\"fill-brand absolute top-1/2 start-1/2 size-2 -translate-x-1/2 -translate-y-1/2 rtl:translate-x-1/2\" />\n </RadioGroupPrimitive.Indicator>\n </RadioGroupPrimitive.Item>\n))\n\nRadioGroup.displayName = 'RadioGroup'\nRadioGroupItem.displayName = 'RadioGroupItem'\n\nexport { RadioGroup, RadioGroupItem }\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../src/lib/utils.ts","../../../src/components/ui/radio-group.tsx"],"names":[],"mappings":";;;;;;;AAIO,SAAS,MAAM,MAAA,EAAsB;AAC1C,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;ACEA,IAAM,UAAA,GAAmB,iBAGvB,CAAC,EAAE,WAAW,GAAG,KAAA,EAAM,EAAG,GAAA,qBAC1B,GAAA,CAAqB,mBAAA,CAAA,IAAA,EAApB,EAAyB,GAAA,EAAU,WAAA,EAAU,eAAc,SAAA,EAAW,EAAA,CAAG,cAAc,SAAS,CAAA,EAAI,GAAG,KAAA,EAAO,CAChH;AAED,IAAM,cAAA,GAAuB,iBAG3B,CAAC,EAAE,WAAW,GAAG,KAAA,IAAS,GAAA,qBAC1B,GAAA;AAAA,EAAqB,mBAAA,CAAA,IAAA;AAAA,EAApB;AAAA,IACC,GAAA;AAAA,IACA,WAAA,EAAU,kBAAA;AAAA,IACV,SAAA,EAAW,EAAA;AAAA,MACT,mXAAA;AAAA,MACA;AAAA,KACF;AAAA,IACC,GAAG,KAAA;AAAA,IAEJ,QAAA,kBAAA,GAAA;AAAA,MAAqB,mBAAA,CAAA,SAAA;AAAA,MAApB;AAAA,QACC,WAAA,EAAU,uBAAA;AAAA,QACV,SAAA,EAAU,2CAAA;AAAA,QAEV,QAAA,kBAAA,GAAA,CAAC,UAAA,EAAA,EAAW,SAAA,EAAU,oGAAA,EAAqG;AAAA;AAAA;AAC7H;AACF,CACD;AAED,UAAA,CAAW,WAAA,GAAc,YAAA;AACzB,cAAA,CAAe,WAAA,GAAc,gBAAA","file":"radio-group.js","sourcesContent":["import { type ClassValue, clsx } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\nimport { formatJalaliDate } from '@/lib/jalali-utils'\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n\nexport type SupportedLocale = 'fa' | 'ar' | 'en'\n\n/**\n * Convert digits in a string to Persian/Arabic numerals based on locale.\n * @example convertToLocalNumbers('123', 'fa') => '۱۲۳'\n * @example convertToLocalNumbers('123', 'en') => '123'\n */\nexport function convertToLocalNumbers(text: string | number, locale: SupportedLocale): string {\n if (locale === 'fa' || locale === 'ar') {\n const persianDigits = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹']\n return String(text).replace(/\\d/g, (digit) => persianDigits[parseInt(digit)])\n }\n return String(text)\n}\n\n/**\n * Format large numbers with locale-aware suffixes (K/M/B).\n * @example formatLargeNumber(1500, 'fa') => '۱.۵ هزار'\n * @example formatLargeNumber(1500, 'en') => '1.5K'\n */\nexport function formatLargeNumber(num: number, locale: SupportedLocale): string {\n if (num >= 1_000_000_000) {\n const formatted = (num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'B' : ' میلیارد')\n }\n if (num >= 1_000_000) {\n const formatted = (num / 1_000_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'M' : ' میلیون')\n }\n if (num >= 1_000) {\n const formatted = (num / 1_000).toFixed(1).replace(/\\.0$/, '')\n return convertToLocalNumbers(formatted, locale) + (locale === 'en' ? 'K' : ' هزار')\n }\n return convertToLocalNumbers(num.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, ','), locale)\n}\n\n/**\n * Format number to Instagram-style short format (English only).\n * @example formatNumber(123456, 'short') => '123K'\n * @example formatNumber(123456, 'exact') => '123,456'\n */\nexport function formatNumber(num: number | undefined, format: 'exact' | 'short' = 'exact'): string {\n if (num === undefined || num === null) return '0'\n\n if (format === 'exact') {\n return num.toLocaleString('en-US')\n }\n\n // Short format (Instagram style)\n if (num >= 1_000_000_000) {\n return `${(num / 1_000_000_000).toFixed(1).replace(/\\.0$/, '')}B`\n }\n if (num >= 1_000_000) {\n return `${(num / 1_000_000).toFixed(1).replace(/\\.0$/, '')}M`\n }\n if (num >= 1_000) {\n return `${(num / 1_000).toFixed(1).replace(/\\.0$/, '')}K`\n }\n return num.toString()\n}\n\n/**\n * Format date to relative time with absolute on hover (Persian)\n * @example formatRelativeTime(new Date()) => '۲ ساعت پیش'\n */\nexport function formatRelativeTime(date: Date | string | number): string {\n const now = new Date()\n const then = new Date(date)\n const diffInSeconds = Math.floor((now.getTime() - then.getTime()) / 1000)\n\n if (diffInSeconds < 60) {\n return 'همین الان'\n }\n\n const diffInMinutes = Math.floor(diffInSeconds / 60)\n if (diffInMinutes < 60) {\n return `${convertToLocalNumbers(diffInMinutes, 'fa')} دقیقه پیش`\n }\n\n const diffInHours = Math.floor(diffInMinutes / 60)\n if (diffInHours < 24) {\n return `${convertToLocalNumbers(diffInHours, 'fa')} ساعت پیش`\n }\n\n const diffInDays = Math.floor(diffInHours / 24)\n if (diffInDays < 7) {\n return `${convertToLocalNumbers(diffInDays, 'fa')} روز پیش`\n }\n\n const diffInWeeks = Math.floor(diffInDays / 7)\n if (diffInWeeks < 4) {\n return `${convertToLocalNumbers(diffInWeeks, 'fa')} هفته پیش`\n }\n\n const diffInMonths = Math.floor(diffInDays / 30)\n if (diffInMonths < 12) {\n return `${convertToLocalNumbers(diffInMonths, 'fa')} ماه پیش`\n }\n\n const diffInYears = Math.floor(diffInDays / 365)\n return `${convertToLocalNumbers(diffInYears, 'fa')} سال پیش`\n}\n\n/**\n * Format date to absolute format (Persian / Jalali)\n * Uses date-fns-jalali for accurate Jalali conversion.\n * @example formatAbsoluteTime(new Date()) => '۱۵ دی ۱۴۰۳، ۱۵:۳۰'\n */\nexport function formatAbsoluteTime(date: Date | string | number): string {\n const d = new Date(date)\n return formatJalaliDate(d, 'd MMMM yyyy، HH:mm')\n}\n","'use client'\n\nimport * as React from 'react'\nimport * as RadioGroupPrimitive from '@radix-ui/react-radio-group'\nimport { CircleIcon } from 'lucide-react'\n\nimport { cn } from '@/lib/utils'\n\nconst RadioGroup = React.forwardRef<\n React.ElementRef<typeof RadioGroupPrimitive.Root>,\n React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>\n>(({ className, ...props }, ref) => (\n <RadioGroupPrimitive.Root ref={ref} data-slot=\"radio-group\" className={cn('grid gap-3', className)} {...props} />\n))\n\nconst RadioGroupItem = React.forwardRef<\n React.ElementRef<typeof RadioGroupPrimitive.Item>,\n React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>\n>(({ className, ...props }, ref) => (\n <RadioGroupPrimitive.Item\n ref={ref}\n data-slot=\"radio-group-item\"\n className={cn(\n 'border-control bg-control data-[state=checked]:border-brand focus-visible:border-brand-default focus-visible:ring-brand-default/50 aria-invalid:ring-destructive/20 aria-invalid:border-destructive aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-all duration-150 outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',\n className\n )}\n {...props}\n >\n <RadioGroupPrimitive.Indicator\n data-slot=\"radio-group-indicator\"\n className=\"relative flex items-center justify-center\"\n >\n <CircleIcon className=\"fill-brand absolute top-1/2 start-1/2 size-2 -translate-x-1/2 -translate-y-1/2 rtl:translate-x-1/2\" />\n </RadioGroupPrimitive.Indicator>\n </RadioGroupPrimitive.Item>\n))\n\nRadioGroup.displayName = 'RadioGroup'\nRadioGroupItem.displayName = 'RadioGroupItem'\n\nexport { RadioGroup, RadioGroupItem }\n"]}