@godxjp/ui 5.0.2 → 6.0.0

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 (298) hide show
  1. package/README.md +101 -142
  2. package/package.json +124 -128
  3. package/scripts/ui-audit.mjs +179 -0
  4. package/src/app/__tests__/app-provider.test.tsx +232 -0
  5. package/src/app/__tests__/date-format-labels.test.ts +36 -0
  6. package/src/app/__tests__/date-formats.test.ts +44 -0
  7. package/src/app/__tests__/timezones.test.ts +65 -0
  8. package/src/app/app-provider.tsx +227 -0
  9. package/src/app/date-format-labels.ts +21 -0
  10. package/src/app/date-formats.ts +30 -0
  11. package/src/app/index.ts +40 -0
  12. package/src/app/locales.ts +32 -0
  13. package/src/app/request-headers.ts +31 -0
  14. package/src/app/storage.ts +44 -0
  15. package/src/app/time-format-labels.ts +19 -0
  16. package/src/app/time-formats.ts +15 -0
  17. package/src/app/timezones.ts +208 -0
  18. package/src/app/types.ts +39 -0
  19. package/src/app/use-formatting.ts +47 -0
  20. package/src/components/__tests__/accessibility-primitives.test.tsx +65 -0
  21. package/src/components/__tests__/docs-parity.test.ts +41 -0
  22. package/src/components/__tests__/shadcn-release-guardrails.test.ts +71 -0
  23. package/src/components/__tests__/theme-axes-integration.test.tsx +242 -0
  24. package/src/components/admin/index.ts +76 -0
  25. package/src/components/data-display/__tests__/card-table.test.tsx +328 -0
  26. package/src/components/data-display/__tests__/data-display.test.tsx +73 -0
  27. package/src/components/data-display/__tests__/data-table.test.tsx +84 -0
  28. package/src/components/data-display/__tests__/popover.test.tsx +92 -0
  29. package/src/components/data-display/__tests__/scroll-area-collapsible.test.tsx +66 -0
  30. package/src/components/data-display/badge.tsx +27 -0
  31. package/src/components/data-display/card.tsx +194 -0
  32. package/src/components/data-display/code-badge.tsx +28 -0
  33. package/src/components/data-display/collapsible.tsx +5 -0
  34. package/src/components/data-display/data-table.tsx +476 -0
  35. package/src/components/data-display/empty-state.tsx +22 -0
  36. package/src/components/data-display/index.ts +41 -0
  37. package/src/components/data-display/key-value-grid.tsx +46 -0
  38. package/src/components/data-display/popover.tsx +62 -0
  39. package/src/components/data-display/progress-meter.tsx +20 -0
  40. package/src/components/data-display/scan-panel.tsx +16 -0
  41. package/src/components/data-display/scroll-area.tsx +42 -0
  42. package/src/components/data-display/status-badge.tsx +83 -0
  43. package/src/components/data-display/table.tsx +59 -0
  44. package/src/components/data-display/timeline.tsx +42 -0
  45. package/src/components/data-display/tree-list.tsx +42 -0
  46. package/src/components/data-entry/__fixtures__/tree-options.ts +80 -0
  47. package/src/components/data-entry/__tests__/cascader-tree-transfer.test.tsx +417 -0
  48. package/src/components/data-entry/__tests__/checkbox-group.test.tsx +40 -0
  49. package/src/components/data-entry/__tests__/checkbox.test.tsx +20 -0
  50. package/src/components/data-entry/__tests__/date-autocomplete.test.tsx +94 -0
  51. package/src/components/data-entry/__tests__/form-field.test.tsx +49 -0
  52. package/src/components/data-entry/__tests__/input-textarea.test.tsx +38 -0
  53. package/src/components/data-entry/__tests__/label-select.test.tsx +62 -0
  54. package/src/components/data-entry/__tests__/pickers.test.tsx +74 -0
  55. package/src/components/data-entry/__tests__/radio.test.tsx +46 -0
  56. package/src/components/data-entry/__tests__/search-input.test.tsx +32 -0
  57. package/src/components/data-entry/__tests__/switch-field.test.tsx +52 -0
  58. package/src/components/data-entry/__tests__/upload.test.tsx +125 -0
  59. package/src/components/data-entry/autocomplete.tsx +91 -0
  60. package/src/components/data-entry/calendar.tsx +90 -0
  61. package/src/components/data-entry/cascader.tsx +305 -0
  62. package/src/components/data-entry/checkbox-group.tsx +90 -0
  63. package/src/components/data-entry/checkbox.tsx +30 -0
  64. package/src/components/data-entry/choice-field.tsx +27 -0
  65. package/src/components/data-entry/choice-option.ts +20 -0
  66. package/src/components/data-entry/color-picker.tsx +75 -0
  67. package/src/components/data-entry/command.tsx +56 -0
  68. package/src/components/data-entry/country-select.tsx +88 -0
  69. package/src/components/data-entry/date-picker.tsx +69 -0
  70. package/src/components/data-entry/date-range-picker.tsx +75 -0
  71. package/src/components/data-entry/form-field.tsx +59 -0
  72. package/src/components/data-entry/index.ts +62 -0
  73. package/src/components/data-entry/input.tsx +26 -0
  74. package/src/components/data-entry/label.tsx +25 -0
  75. package/src/components/data-entry/radio.tsx +109 -0
  76. package/src/components/data-entry/search-input.tsx +103 -0
  77. package/src/components/data-entry/select.tsx +149 -0
  78. package/src/components/data-entry/slider.tsx +38 -0
  79. package/src/components/data-entry/switch-field.tsx +91 -0
  80. package/src/components/data-entry/switch.tsx +24 -0
  81. package/src/components/data-entry/textarea.tsx +12 -0
  82. package/src/components/data-entry/time-picker.tsx +214 -0
  83. package/src/components/data-entry/transfer.tsx +231 -0
  84. package/src/components/data-entry/tree-select-strategy.ts +6 -0
  85. package/src/components/data-entry/tree-select.tsx +279 -0
  86. package/src/components/data-entry/tree-utils.ts +221 -0
  87. package/src/components/data-entry/upload-crop-dialog.tsx +109 -0
  88. package/src/components/data-entry/upload-types.ts +86 -0
  89. package/src/components/data-entry/upload.tsx +498 -0
  90. package/src/components/data-entry/use-upload-draft.ts +93 -0
  91. package/src/components/feedback/__tests__/alert.test.tsx +127 -0
  92. package/src/components/feedback/__tests__/dialog.test.tsx +290 -0
  93. package/src/components/feedback/__tests__/sheet.test.tsx +94 -0
  94. package/src/components/feedback/__tests__/skeleton.test.tsx +25 -0
  95. package/src/components/feedback/__tests__/toast.test.tsx +52 -0
  96. package/src/components/feedback/alert.tsx +167 -0
  97. package/src/components/feedback/dialog.tsx +325 -0
  98. package/src/components/feedback/index.ts +53 -0
  99. package/src/components/feedback/sheet.tsx +130 -0
  100. package/src/components/feedback/skeleton.tsx +95 -0
  101. package/src/components/feedback/sonner.tsx +54 -0
  102. package/src/components/feedback/toaster.tsx +1 -0
  103. package/src/components/feedback/use-toast.ts +62 -0
  104. package/src/components/general/__tests__/button.test.tsx +71 -0
  105. package/src/components/general/button.tsx +61 -0
  106. package/src/components/general/index.ts +2 -0
  107. package/src/components/layout/__tests__/page-container.test.tsx +69 -0
  108. package/src/components/layout/__tests__/page-inset.test.tsx +14 -0
  109. package/src/components/layout/__tests__/stack-inline.test.tsx +39 -0
  110. package/src/components/layout/app-shell.tsx +42 -0
  111. package/src/components/layout/breadcrumb.tsx +35 -0
  112. package/src/components/layout/index.ts +31 -0
  113. package/src/components/layout/inline.tsx +13 -0
  114. package/src/components/layout/menu.tsx +34 -0
  115. package/src/components/layout/mobile-frame.tsx +57 -0
  116. package/src/components/layout/page-container.tsx +81 -0
  117. package/src/components/layout/page-inset.tsx +16 -0
  118. package/src/components/layout/responsive-grid.tsx +14 -0
  119. package/src/components/layout/shell-app.tsx +30 -0
  120. package/src/components/layout/sidebar.tsx +98 -0
  121. package/src/components/layout/split-pane.tsx +16 -0
  122. package/src/components/layout/stack.tsx +13 -0
  123. package/src/components/layout/topbar.tsx +108 -0
  124. package/src/components/navigation/__tests__/app-pickers.test.tsx +118 -0
  125. package/src/components/navigation/__tests__/dropdown-menu.test.tsx +104 -0
  126. package/src/components/navigation/__tests__/navigation.test.tsx +61 -0
  127. package/src/components/navigation/__tests__/pagination-steps-tabs.test.tsx +76 -0
  128. package/src/components/navigation/date-format-picker.tsx +55 -0
  129. package/src/components/navigation/dropdown-menu.tsx +190 -0
  130. package/src/components/navigation/filter-bar.tsx +38 -0
  131. package/src/components/navigation/index.ts +28 -0
  132. package/src/components/navigation/locale-picker.tsx +49 -0
  133. package/src/components/navigation/page-header.tsx +50 -0
  134. package/src/components/navigation/pagination-utils.ts +35 -0
  135. package/src/components/navigation/pagination.tsx +168 -0
  136. package/src/components/navigation/steps.tsx +163 -0
  137. package/src/components/navigation/tabs-items.tsx +69 -0
  138. package/src/components/navigation/tabs.tsx +67 -0
  139. package/src/components/navigation/time-format-picker.tsx +55 -0
  140. package/src/components/navigation/timezone-picker.tsx +63 -0
  141. package/src/components/query/__tests__/data-state.test.tsx +214 -0
  142. package/src/components/query/__tests__/infinite-prefetch.test.tsx +105 -0
  143. package/src/components/query/__tests__/query-helpers.test.tsx +61 -0
  144. package/src/components/query/data-state.tsx +58 -0
  145. package/src/components/query/index.ts +10 -0
  146. package/src/components/query/infinite-query-state.tsx +99 -0
  147. package/src/components/query/mutation-feedback.tsx +31 -0
  148. package/src/components/query/prefetch-link.tsx +45 -0
  149. package/src/components/query/query-refetch-button.tsx +41 -0
  150. package/src/components/ui/alert-dialog.tsx +1 -0
  151. package/src/components/ui/alert.tsx +1 -0
  152. package/src/components/ui/autocomplete.tsx +1 -0
  153. package/src/components/ui/badge.tsx +1 -0
  154. package/src/components/ui/button.tsx +1 -0
  155. package/src/components/ui/calendar.tsx +1 -0
  156. package/src/components/ui/card.tsx +1 -0
  157. package/src/components/ui/checkbox.tsx +1 -0
  158. package/src/components/ui/color-picker.tsx +1 -0
  159. package/src/components/ui/command.tsx +1 -0
  160. package/src/components/ui/date-picker.tsx +1 -0
  161. package/src/components/ui/date-range-picker.tsx +1 -0
  162. package/src/components/ui/dialog.tsx +1 -0
  163. package/src/components/ui/dropdown-menu.tsx +1 -0
  164. package/src/components/ui/index.tsx +31 -0
  165. package/src/components/ui/input.tsx +1 -0
  166. package/src/components/ui/label.tsx +1 -0
  167. package/src/components/ui/pagination.tsx +1 -0
  168. package/src/components/ui/popover.tsx +1 -0
  169. package/src/components/ui/radio.tsx +1 -0
  170. package/src/components/ui/scroll-area.tsx +1 -0
  171. package/src/components/ui/select.tsx +1 -0
  172. package/src/components/ui/sheet.tsx +1 -0
  173. package/src/components/ui/slider.tsx +1 -0
  174. package/src/components/ui/sonner.tsx +1 -0
  175. package/src/components/ui/switch.tsx +1 -0
  176. package/src/components/ui/table.tsx +1 -0
  177. package/src/components/ui/tabs-items.tsx +1 -0
  178. package/src/components/ui/tabs.tsx +1 -0
  179. package/src/components/ui/textarea.tsx +1 -0
  180. package/src/components/ui/time-picker.tsx +1 -0
  181. package/src/components/ui/upload.tsx +1 -0
  182. package/src/form/__tests__/use-zod-form.test.tsx +97 -0
  183. package/src/form/form-field-control.tsx +44 -0
  184. package/src/form/form-root.tsx +29 -0
  185. package/src/form/index.ts +7 -0
  186. package/src/form/use-zod-form.ts +29 -0
  187. package/src/i18n/__tests__/translate.test.ts +23 -0
  188. package/src/i18n/index.ts +9 -0
  189. package/src/i18n/messages/en.json +171 -0
  190. package/src/i18n/messages/ja.json +171 -0
  191. package/src/i18n/messages/vi.json +171 -0
  192. package/src/i18n/translate.ts +74 -0
  193. package/src/i18n/use-translation.ts +53 -0
  194. package/src/index.ts +3 -0
  195. package/src/lib/__tests__/control-styles.test.ts +78 -0
  196. package/src/lib/__tests__/datetime.test.ts +77 -0
  197. package/src/lib/__tests__/format-date.test.ts +97 -0
  198. package/src/lib/__tests__/format.test.ts +62 -0
  199. package/src/lib/__tests__/theme-tokens-audit.test.ts +176 -0
  200. package/src/lib/__tests__/theme-tokens-css.test.ts +118 -0
  201. package/src/lib/__tests__/token-governance.test.ts +191 -0
  202. package/src/lib/__tests__/variants.test.ts +18 -0
  203. package/src/lib/control-styles.ts +33 -0
  204. package/src/lib/datetime/detect.ts +25 -0
  205. package/src/lib/datetime/format-date.ts +100 -0
  206. package/src/lib/datetime/format.ts +140 -0
  207. package/src/lib/datetime/index.ts +25 -0
  208. package/src/lib/datetime/parse.ts +51 -0
  209. package/src/lib/datetime/sync.ts +48 -0
  210. package/src/lib/format.ts +114 -0
  211. package/src/lib/hooks.ts +54 -0
  212. package/src/lib/utils.ts +6 -0
  213. package/src/lib/variants.ts +40 -0
  214. package/src/props/components/app.prop.ts +99 -0
  215. package/src/props/components/data-display.prop.ts +73 -0
  216. package/src/props/components/data-entry.prop.ts +334 -0
  217. package/src/props/components/feedback.prop.ts +80 -0
  218. package/src/props/components/form.prop.ts +46 -0
  219. package/src/props/components/general.prop.ts +18 -0
  220. package/src/props/components/index.ts +99 -0
  221. package/src/props/components/layout.prop.ts +130 -0
  222. package/src/props/components/navigation.prop.ts +88 -0
  223. package/src/props/components/query.prop.ts +94 -0
  224. package/src/props/index.ts +17 -0
  225. package/src/props/registry.ts +603 -0
  226. package/src/props/vocabulary/content.prop.ts +35 -0
  227. package/src/props/vocabulary/data.prop.ts +46 -0
  228. package/src/props/vocabulary/index.ts +73 -0
  229. package/src/props/vocabulary/interaction.prop.ts +42 -0
  230. package/src/props/vocabulary/layout.prop.ts +25 -0
  231. package/src/props/vocabulary/navigation.prop.ts +19 -0
  232. package/src/props/vocabulary/shared.prop.ts +59 -0
  233. package/src/styles/alert-layout.css +191 -0
  234. package/src/styles/badge-layout.css +22 -0
  235. package/src/styles/card-layout.css +373 -0
  236. package/src/styles/control.css +504 -0
  237. package/src/styles/data-display-layout.css +246 -0
  238. package/src/styles/density.css +43 -0
  239. package/src/styles/dialog-layout.css +84 -0
  240. package/src/styles/index.css +105 -0
  241. package/src/styles/layout.css +479 -0
  242. package/src/styles/shell-layout.css +604 -0
  243. package/src/styles/table-layout.css +109 -0
  244. package/src/test/__tests__/render-loop-guard.test.tsx +38 -0
  245. package/src/test/jest-dom.d.ts +4 -0
  246. package/src/test/render-loop-guard.tsx +50 -0
  247. package/src/test/render.tsx +29 -0
  248. package/src/test/theme-globals.test.ts +77 -0
  249. package/src/test/theme-globals.ts +134 -0
  250. package/src/test/theme-test-utils.tsx +67 -0
  251. package/src/theme/example.service.css +37 -0
  252. package/src/tokens/base.css +13 -0
  253. package/src/tokens/foundation.css +151 -0
  254. package/src/tokens/primitives/badge.css +13 -0
  255. package/src/tokens/primitives/card.css +29 -0
  256. package/src/tokens/primitives/control.css +55 -0
  257. package/src/tokens/primitives/feedback.css +17 -0
  258. package/src/tokens/primitives/layout.css +20 -0
  259. package/src/tokens/primitives/navigation.css +13 -0
  260. package/src/tokens/primitives/table.css +10 -0
  261. package/BRAND.md +0 -296
  262. package/CHANGELOG.md +0 -668
  263. package/config/eslint.js +0 -54
  264. package/config/prettier.cjs +0 -20
  265. package/config/tsconfig.base.json +0 -22
  266. package/config/vitest.base.ts +0 -26
  267. package/dist/MiniMonth-YAmPGEpC.d.ts +0 -143
  268. package/dist/Table.types-BbsxoIYE.d.ts +0 -352
  269. package/dist/color-DO0qqUAb.d.ts +0 -38
  270. package/dist/components/composites.d.ts +0 -963
  271. package/dist/components/composites.js +0 -7343
  272. package/dist/components/composites.js.map +0 -1
  273. package/dist/components/primitives.d.ts +0 -2744
  274. package/dist/components/primitives.js +0 -7356
  275. package/dist/components/primitives.js.map +0 -1
  276. package/dist/components/shell.d.ts +0 -182
  277. package/dist/components/shell.js +0 -774
  278. package/dist/components/shell.js.map +0 -1
  279. package/dist/hooks.d.ts +0 -100
  280. package/dist/hooks.js +0 -558
  281. package/dist/hooks.js.map +0 -1
  282. package/dist/i18n.d.ts +0 -61
  283. package/dist/i18n.js +0 -860
  284. package/dist/i18n.js.map +0 -1
  285. package/dist/index.d.ts +0 -33
  286. package/dist/index.js +0 -13062
  287. package/dist/index.js.map +0 -1
  288. package/dist/padding-DY0JV5Ja.d.ts +0 -16
  289. package/dist/preferences.d.ts +0 -132
  290. package/dist/preferences.js +0 -262
  291. package/dist/preferences.js.map +0 -1
  292. package/dist/props.d.ts +0 -86
  293. package/dist/props.js +0 -16
  294. package/dist/props.js.map +0 -1
  295. package/dist/size-CQwNvOWd.d.ts +0 -19
  296. package/dist/types-LTj-2bl-.d.ts +0 -30
  297. package/dist/useTableViews-D5NIAJ7h.d.ts +0 -154
  298. package/src/tokens/tailwind.css +0 -158
@@ -0,0 +1,171 @@
1
+ {
2
+ "common": {
3
+ "cancel": "キャンセル",
4
+ "continue": "続行",
5
+ "delete": "削除",
6
+ "error": "エラーが発生しました",
7
+ "working": "処理中…",
8
+ "retry": "再試行",
9
+ "search": "検索",
10
+ "clearSearch": "検索をクリア",
11
+ "clearFilters": "フィルターをクリア",
12
+ "first": "最初",
13
+ "next": "次へ →",
14
+ "selectedCount": "{count} 件選択",
15
+ "typeToConfirm": "確認のため {phrase} と入力"
16
+ },
17
+ "dataEntry": {
18
+ "datePicker": {
19
+ "placeholder": "日付を選択"
20
+ },
21
+ "dateRangePicker": {
22
+ "placeholder": "期間を選択"
23
+ },
24
+ "timePicker": {
25
+ "placeholder": "時刻を選択",
26
+ "hour": "時",
27
+ "minute": "分",
28
+ "typeLabel": "HH:mm を入力"
29
+ },
30
+ "searchInput": {
31
+ "placeholder": "検索…"
32
+ },
33
+ "autocomplete": {
34
+ "placeholder": "検索または選択…",
35
+ "empty": "結果が見つかりません"
36
+ },
37
+ "colorPicker": {
38
+ "ariaLabel": "色を選択"
39
+ },
40
+ "upload": {
41
+ "dropzoneLabel": "ファイルをアップロード",
42
+ "dropzoneHint": "ここにドラッグ&ドロップ、またはクリックして選択",
43
+ "dropzoneMeta": "一時ストレージに保存 — フォーム保存時に promote",
44
+ "buttonLabel": "ファイルを選択",
45
+ "addImage": "画像を追加",
46
+ "uploading": "アップロード中…",
47
+ "pendingReplace": "新しい画像",
48
+ "pendingReplaceHint": "新しい画像を保留中 — 保存で反映",
49
+ "avatarLabel": "プロフィール写真",
50
+ "change": "変更",
51
+ "removeAvatar": "写真を削除",
52
+ "markedForDelete": "保存時に写真が削除されます",
53
+ "undo": "元に戻す",
54
+ "cropTitle": "写真を切り抜き",
55
+ "cropZoom": "ズーム",
56
+ "cropConfirm": "この写真を使用"
57
+ },
58
+ "cascader": {
59
+ "placeholder": "選択…",
60
+ "searchPlaceholder": "検索…",
61
+ "empty": "結果なし"
62
+ },
63
+ "treeSelect": {
64
+ "placeholder": "選択…",
65
+ "searchPlaceholder": "検索…",
66
+ "empty": "データなし",
67
+ "expand": "展開",
68
+ "collapse": "折りたたむ"
69
+ },
70
+ "transfer": {
71
+ "source": "ソース",
72
+ "target": "ターゲット",
73
+ "searchPlaceholder": "検索…",
74
+ "empty": "データなし",
75
+ "moveRight": "ターゲットへ移動",
76
+ "moveLeft": "ソースへ戻す"
77
+ }
78
+ },
79
+ "feedback": {
80
+ "errorPanel": {
81
+ "title": "このセクションを読み込めませんでした"
82
+ },
83
+ "genericError": "問題が発生しました。もう一度お試しください。"
84
+ },
85
+ "navigation": {
86
+ "localePicker": {
87
+ "ariaLabel": "言語"
88
+ },
89
+ "timezonePicker": {
90
+ "ariaLabel": "タイムゾーン"
91
+ },
92
+ "timeFormatPicker": {
93
+ "ariaLabel": "時刻形式"
94
+ },
95
+ "dateFormatPicker": {
96
+ "ariaLabel": "日付形式"
97
+ },
98
+ "pagination": {
99
+ "ariaLabel": "ページネーション",
100
+ "prev": "前のページ",
101
+ "next": "次のページ",
102
+ "page": "ページ {page}",
103
+ "pageSize": "ページサイズ",
104
+ "pageSizeOption": "{size} / ページ",
105
+ "total": "全 {total} 件"
106
+ }
107
+ },
108
+ "locale": {
109
+ "vi": "Tiếng Việt",
110
+ "en": "English",
111
+ "ja": "日本語"
112
+ },
113
+ "timeFormat": {
114
+ "24h": "24時間",
115
+ "12h": "12時間 (午前/午後)"
116
+ },
117
+ "dateFormat": {
118
+ "iso": "YYYY-MM-DD(年-月-日)",
119
+ "dmy": "日/月/年",
120
+ "mdy": "月/日/年"
121
+ },
122
+ "timezone": {
123
+ "Asia/Ho_Chi_Minh": "ベトナム(ホーチミン)",
124
+ "Asia/Tokyo": "日本(東京)",
125
+ "Asia/Bangkok": "タイ(バンコク)",
126
+ "Asia/Singapore": "シンガポール",
127
+ "Asia/Seoul": "韓国(ソウル)",
128
+ "Asia/Shanghai": "中国(上海)",
129
+ "Asia/Taipei": "台湾(台北)",
130
+ "Asia/Hong_Kong": "香港",
131
+ "UTC": "UTC",
132
+ "Europe/London": "英国(ロンドン)",
133
+ "America/Los_Angeles": "米国(ロサンゼルス)",
134
+ "America/New_York": "米国(ニューヨーク)"
135
+ },
136
+ "dataTable": {
137
+ "selectAll": "すべての行を選択",
138
+ "selectRow": "行 {id} を選択",
139
+ "bulkActions": "一括操作",
140
+ "densityCompact": "コンパクト",
141
+ "densityComfortable": "快適",
142
+ "densitySwitch": "{density} 表示に切り替え",
143
+ "rowActions": "行の操作"
144
+ },
145
+ "status": {
146
+ "active": "有効",
147
+ "completed": "完了",
148
+ "delivered": "配達済み",
149
+ "done": "完了",
150
+ "permanent": "永久",
151
+ "succeeded": "成功",
152
+ "draft": "下書き",
153
+ "pending": "保留",
154
+ "scheduled": "予定",
155
+ "sending": "送信中",
156
+ "temporary": "一時",
157
+ "bounced": "バウンス",
158
+ "cancelled": "キャンセル",
159
+ "deleted": "削除済み",
160
+ "failed": "失敗",
161
+ "private": "非公開",
162
+ "internal": "内部",
163
+ "public": "公開",
164
+ "ASSIGNMENT_STATUS_ACTIVE": "有効",
165
+ "ASSIGNMENT_STATUS_SUSPENDED": "停止",
166
+ "ASSIGNMENT_STATUS_TERMINATED": "終了"
167
+ },
168
+ "query": {
169
+ "loadMore": "さらに読み込む"
170
+ }
171
+ }
@@ -0,0 +1,171 @@
1
+ {
2
+ "common": {
3
+ "cancel": "Hủy",
4
+ "continue": "Tiếp tục",
5
+ "delete": "Xóa",
6
+ "error": "Đã xảy ra lỗi",
7
+ "working": "Đang xử lý…",
8
+ "retry": "Thử lại",
9
+ "search": "Tìm kiếm",
10
+ "clearSearch": "Xóa tìm kiếm",
11
+ "clearFilters": "Xóa bộ lọc",
12
+ "first": "Đầu",
13
+ "next": "Tiếp →",
14
+ "selectedCount": "{count} đã chọn",
15
+ "typeToConfirm": "Nhập {phrase} để xác nhận"
16
+ },
17
+ "dataEntry": {
18
+ "datePicker": {
19
+ "placeholder": "Chọn ngày"
20
+ },
21
+ "dateRangePicker": {
22
+ "placeholder": "Chọn khoảng ngày"
23
+ },
24
+ "timePicker": {
25
+ "placeholder": "Chọn giờ",
26
+ "hour": "Giờ",
27
+ "minute": "Phút",
28
+ "typeLabel": "Nhập giờ HH:mm"
29
+ },
30
+ "searchInput": {
31
+ "placeholder": "Tìm kiếm…"
32
+ },
33
+ "autocomplete": {
34
+ "placeholder": "Tìm hoặc chọn…",
35
+ "empty": "Không tìm thấy kết quả"
36
+ },
37
+ "colorPicker": {
38
+ "ariaLabel": "Chọn màu"
39
+ },
40
+ "upload": {
41
+ "dropzoneLabel": "Tải file lên",
42
+ "dropzoneHint": "Kéo thả file vào đây, hoặc bấm để chọn",
43
+ "dropzoneMeta": "Upload vào temp — promote khi lưu form",
44
+ "buttonLabel": "Chọn file",
45
+ "addImage": "Thêm ảnh",
46
+ "uploading": "Đang tải lên…",
47
+ "pendingReplace": "Ảnh mới",
48
+ "pendingReplaceHint": "Ảnh mới đang chờ — lưu form để áp dụng",
49
+ "avatarLabel": "Ảnh đại diện",
50
+ "change": "Đổi",
51
+ "removeAvatar": "Xóa ảnh",
52
+ "markedForDelete": "Ảnh sẽ bị xóa khi bạn lưu",
53
+ "undo": "Hoàn tác",
54
+ "cropTitle": "Cắt ảnh",
55
+ "cropZoom": "Thu phóng",
56
+ "cropConfirm": "Dùng ảnh này"
57
+ },
58
+ "cascader": {
59
+ "placeholder": "Chọn…",
60
+ "searchPlaceholder": "Tìm kiếm…",
61
+ "empty": "Không có kết quả"
62
+ },
63
+ "treeSelect": {
64
+ "placeholder": "Chọn…",
65
+ "searchPlaceholder": "Tìm kiếm…",
66
+ "empty": "Không có dữ liệu",
67
+ "expand": "Mở rộng",
68
+ "collapse": "Thu gọn"
69
+ },
70
+ "transfer": {
71
+ "source": "Nguồn",
72
+ "target": "Đích",
73
+ "searchPlaceholder": "Tìm kiếm…",
74
+ "empty": "Không có dữ liệu",
75
+ "moveRight": "Chuyển sang đích",
76
+ "moveLeft": "Chuyển về nguồn"
77
+ }
78
+ },
79
+ "feedback": {
80
+ "errorPanel": {
81
+ "title": "Không tải được nội dung này"
82
+ },
83
+ "genericError": "Đã xảy ra lỗi. Vui lòng thử lại."
84
+ },
85
+ "navigation": {
86
+ "localePicker": {
87
+ "ariaLabel": "Ngôn ngữ"
88
+ },
89
+ "timezonePicker": {
90
+ "ariaLabel": "Múi giờ"
91
+ },
92
+ "timeFormatPicker": {
93
+ "ariaLabel": "Định dạng giờ"
94
+ },
95
+ "dateFormatPicker": {
96
+ "ariaLabel": "Định dạng ngày"
97
+ },
98
+ "pagination": {
99
+ "ariaLabel": "Phân trang",
100
+ "prev": "Trang trước",
101
+ "next": "Trang sau",
102
+ "page": "Trang {page}",
103
+ "pageSize": "Số dòng mỗi trang",
104
+ "pageSizeOption": "{size} / trang",
105
+ "total": "Tổng {total} mục"
106
+ }
107
+ },
108
+ "locale": {
109
+ "vi": "Tiếng Việt",
110
+ "en": "English",
111
+ "ja": "日本語"
112
+ },
113
+ "timeFormat": {
114
+ "24h": "24 giờ",
115
+ "12h": "12 giờ (SA/CH)"
116
+ },
117
+ "dateFormat": {
118
+ "iso": "ISO (yyyy-MM-dd)",
119
+ "dmy": "Ngày / Tháng / Năm",
120
+ "mdy": "Tháng / Ngày / Năm"
121
+ },
122
+ "timezone": {
123
+ "Asia/Ho_Chi_Minh": "Việt Nam (HCM)",
124
+ "Asia/Tokyo": "Nhật Bản (Tokyo)",
125
+ "Asia/Bangkok": "Thái Lan (Bangkok)",
126
+ "Asia/Singapore": "Singapore",
127
+ "Asia/Seoul": "Hàn Quốc (Seoul)",
128
+ "Asia/Shanghai": "Trung Quốc (Shanghai)",
129
+ "Asia/Taipei": "Đài Loan (Taipei)",
130
+ "Asia/Hong_Kong": "Hồng Kông",
131
+ "UTC": "UTC",
132
+ "Europe/London": "Anh (London)",
133
+ "America/Los_Angeles": "Mỹ (Los Angeles)",
134
+ "America/New_York": "Mỹ (New York)"
135
+ },
136
+ "dataTable": {
137
+ "selectAll": "Chọn tất cả các dòng",
138
+ "selectRow": "Chọn dòng {id}",
139
+ "bulkActions": "Thao tác hàng loạt",
140
+ "densityCompact": "Gọn",
141
+ "densityComfortable": "Thoải mái",
142
+ "densitySwitch": "Chuyển sang mật độ {density}",
143
+ "rowActions": "Thao tác dòng"
144
+ },
145
+ "status": {
146
+ "active": "Đang hoạt động",
147
+ "completed": "Hoàn thành",
148
+ "delivered": "Đã giao",
149
+ "done": "Xong",
150
+ "permanent": "Vĩnh viễn",
151
+ "succeeded": "Thành công",
152
+ "draft": "Nháp",
153
+ "pending": "Chờ xử lý",
154
+ "scheduled": "Đã lên lịch",
155
+ "sending": "Đang gửi",
156
+ "temporary": "Tạm thời",
157
+ "bounced": "Bị trả lại",
158
+ "cancelled": "Đã hủy",
159
+ "deleted": "Đã xóa",
160
+ "failed": "Thất bại",
161
+ "private": "Riêng tư",
162
+ "internal": "Nội bộ",
163
+ "public": "Công khai",
164
+ "ASSIGNMENT_STATUS_ACTIVE": "Đang hoạt động",
165
+ "ASSIGNMENT_STATUS_SUSPENDED": "Tạm ngưng",
166
+ "ASSIGNMENT_STATUS_TERMINATED": "Chấm dứt"
167
+ },
168
+ "query": {
169
+ "loadMore": "Tải thêm"
170
+ }
171
+ }
@@ -0,0 +1,74 @@
1
+ import type { AppLocale } from "../app/types";
2
+ import en from "./messages/en.json";
3
+ import ja from "./messages/ja.json";
4
+ import vi from "./messages/vi.json";
5
+
6
+ export type Messages = typeof vi;
7
+
8
+ export const MESSAGE_CATALOG: Record<AppLocale, Messages> = {
9
+ vi,
10
+ en,
11
+ ja,
12
+ };
13
+
14
+ export type MessageKey = string;
15
+
16
+ export type TranslateParams = Record<string, string | number>;
17
+
18
+ function getNested(obj: Record<string, unknown>, path: string): string | undefined {
19
+ const parts = path.split(".");
20
+ let current: unknown = obj;
21
+ for (const part of parts) {
22
+ if (current == null || typeof current !== "object") return undefined;
23
+ current = (current as Record<string, unknown>)[part];
24
+ }
25
+ return typeof current === "string" ? current : undefined;
26
+ }
27
+
28
+ function interpolate(template: string, params?: TranslateParams): string {
29
+ if (!params) return template;
30
+ return Object.entries(params).reduce(
31
+ (text, [key, value]) => text.replaceAll(`{${key}}`, String(value)),
32
+ template,
33
+ );
34
+ }
35
+
36
+ /** Resolve a dot-path message with fallback locale chain. */
37
+ export function translate(
38
+ locale: AppLocale,
39
+ fallbackLocale: AppLocale,
40
+ key: MessageKey,
41
+ params?: TranslateParams,
42
+ ): string {
43
+ const primary = getNested(MESSAGE_CATALOG[locale], key);
44
+ const fallback = getNested(MESSAGE_CATALOG[fallbackLocale], key);
45
+ const text = primary ?? fallback ?? key;
46
+ return interpolate(text, params);
47
+ }
48
+
49
+ /** Non-React translate using module-synced locale (see `syncI18nLocale`). */
50
+ export function translateCurrent(key: MessageKey, params?: TranslateParams): string {
51
+ return translate(getSyncedLocale(), getSyncedFallbackLocale(), key, params);
52
+ }
53
+
54
+ let syncedLocale: AppLocale = "vi";
55
+ let syncedFallbackLocale: AppLocale = "en";
56
+
57
+ export function syncI18nLocale(locale: AppLocale, fallbackLocale: AppLocale): void {
58
+ syncedLocale = locale;
59
+ syncedFallbackLocale = fallbackLocale;
60
+ }
61
+
62
+ export function getSyncedLocale(): AppLocale {
63
+ return syncedLocale;
64
+ }
65
+
66
+ export function getSyncedFallbackLocale(): AppLocale {
67
+ return syncedFallbackLocale;
68
+ }
69
+
70
+ /** Reset for tests. */
71
+ export function resetI18nLocale(): void {
72
+ syncedLocale = "vi";
73
+ syncedFallbackLocale = "en";
74
+ }
@@ -0,0 +1,53 @@
1
+ import type { DayPickerProps } from "react-day-picker";
2
+ import { useMemo } from "react";
3
+ import { useOptionalAppContext } from "../app/app-provider";
4
+ import { getDateFnsLocale, getDayPickerLocale } from "../app/locales";
5
+ import type { AppLocale } from "../app/types";
6
+ import { translate, type MessageKey, type TranslateParams } from "./translate";
7
+
8
+ const DEFAULT_LOCALE: AppLocale = "vi";
9
+ const DEFAULT_FALLBACK: AppLocale = "en";
10
+
11
+ type DayPickerLocale = NonNullable<DayPickerProps["locale"]>;
12
+
13
+ export function useTranslation() {
14
+ const ctx = useOptionalAppContext();
15
+ const locale = ctx?.locale ?? DEFAULT_LOCALE;
16
+ const fallbackLocale = ctx?.fallbackLocale ?? DEFAULT_FALLBACK;
17
+
18
+ return useMemo(
19
+ () => ({
20
+ locale,
21
+ fallbackLocale,
22
+ t: (key: MessageKey, params?: TranslateParams) =>
23
+ translate(locale, fallbackLocale, key, params),
24
+ }),
25
+ [locale, fallbackLocale],
26
+ );
27
+ }
28
+
29
+ /** date-fns + react-day-picker locales + datetime prefs from AppProvider. */
30
+ export function usePickerLocales(dayPickerOverride?: DayPickerLocale) {
31
+ const ctx = useOptionalAppContext();
32
+ const locale = ctx?.locale ?? DEFAULT_LOCALE;
33
+
34
+ return useMemo(
35
+ () => ({
36
+ locale,
37
+ timezone: ctx?.timezone ?? "Asia/Ho_Chi_Minh",
38
+ timeFormat: ctx?.timeFormat ?? "24h",
39
+ dateFormat: ctx?.dateFormat ?? "dmy",
40
+ dateFnsLocale: ctx?.dateFnsLocale ?? getDateFnsLocale(locale),
41
+ dayPickerLocale: dayPickerOverride ?? ctx?.dayPickerLocale ?? getDayPickerLocale(locale),
42
+ }),
43
+ [
44
+ ctx?.dateFnsLocale,
45
+ ctx?.dayPickerLocale,
46
+ ctx?.timeFormat,
47
+ ctx?.dateFormat,
48
+ ctx?.timezone,
49
+ dayPickerOverride,
50
+ locale,
51
+ ],
52
+ );
53
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from "./components/admin";
2
+ export * from "./form";
3
+ export { cn } from "./lib/utils";
@@ -0,0 +1,78 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ controlFieldClass,
4
+ controlIconClass,
5
+ controlIconSmClass,
6
+ controlMultilineClass,
7
+ controlTriggerClass,
8
+ tableCellPaddingClass,
9
+ tableHeadHeightClass,
10
+ tableRowHeightClass,
11
+ toneDestructiveClass,
12
+ toneInfoClass,
13
+ toneNeutralClass,
14
+ toneSuccessClass,
15
+ toneWarningClass,
16
+ } from "../control-styles";
17
+
18
+ describe("control-styles (token wiring)", () => {
19
+ describe("form controls", () => {
20
+ it("controlFieldClass uses ui-control + ring token", () => {
21
+ expect(controlFieldClass).toContain("ui-control");
22
+ expect(controlFieldClass).toContain("border-input");
23
+ expect(controlFieldClass).toContain("focus-visible:ring-ring");
24
+ });
25
+
26
+ it("controlMultilineClass uses ui-control-multiline", () => {
27
+ expect(controlMultilineClass).toContain("ui-control-multiline");
28
+ expect(controlMultilineClass).toContain("border-input");
29
+ });
30
+
31
+ it("controlTriggerClass uses ui-control flex layout", () => {
32
+ expect(controlTriggerClass).toContain("ui-control");
33
+ expect(controlTriggerClass).toContain("flex");
34
+ expect(controlTriggerClass).toContain("items-center");
35
+ expect(controlTriggerClass).not.toMatch(/\bh-9\b/);
36
+ });
37
+ });
38
+
39
+ describe("density-aware sizing", () => {
40
+ it("icon classes reference --control-height", () => {
41
+ expect(controlIconClass).toContain("var(--control-height)");
42
+ expect(controlIconSmClass).toContain("var(--control-height)");
43
+ });
44
+
45
+ it("table classes reference density row/cell tokens", () => {
46
+ expect(tableRowHeightClass).toContain("var(--table-row-height)");
47
+ expect(tableHeadHeightClass).toContain("var(--table-row-height)");
48
+ expect(tableCellPaddingClass).toContain("var(--table-cell-padding-y)");
49
+ });
50
+ });
51
+
52
+ describe("semantic tone classes (no raw palette)", () => {
53
+ const tones = {
54
+ success: toneSuccessClass,
55
+ warning: toneWarningClass,
56
+ info: toneInfoClass,
57
+ destructive: toneDestructiveClass,
58
+ neutral: toneNeutralClass,
59
+ };
60
+
61
+ it.each(Object.entries(tones))("tone %s avoids raw Tailwind palette", (_name, cls) => {
62
+ expect(cls).not.toMatch(/green-|blue-|amber-|red-|slate-/);
63
+ expect(cls.length).toBeGreaterThan(10);
64
+ });
65
+
66
+ it("success tone uses success token", () => {
67
+ expect(toneSuccessClass).toContain("success");
68
+ });
69
+
70
+ it("info tone uses info token", () => {
71
+ expect(toneInfoClass).toContain("info");
72
+ });
73
+
74
+ it("warning tone uses warning token", () => {
75
+ expect(toneWarningClass).toContain("warning");
76
+ });
77
+ });
78
+ });
@@ -0,0 +1,77 @@
1
+ import { describe, expect, it, beforeEach } from "vitest";
2
+ import { getDateFnsLocale } from "../../app/locales";
3
+ import {
4
+ formatAppDateTime,
5
+ formatCalendarDate,
6
+ formatTimeOfDay,
7
+ normalizeHhmm,
8
+ resetDatetimeContextForTests,
9
+ syncDatetimeContext,
10
+ } from "../datetime";
11
+
12
+ describe("datetime (timezone-aware)", () => {
13
+ beforeEach(() => {
14
+ resetDatetimeContextForTests();
15
+ });
16
+
17
+ it("formatAppDateTime shifts instant by timezone", () => {
18
+ syncDatetimeContext({
19
+ locale: "en",
20
+ timezone: "Asia/Ho_Chi_Minh",
21
+ timeFormat: "24h",
22
+ dateFormat: "iso",
23
+ dateFnsLocale: getDateFnsLocale("en"),
24
+ });
25
+ // 2026-05-01T14:30:00Z → 21:30 in Ho Chi Minh (UTC+7)
26
+ expect(formatAppDateTime("2026-05-01T14:30:00Z")).toMatch(/2026-05-01 21:30/);
27
+ });
28
+
29
+ it("formatAppDateTime differs across timezones", () => {
30
+ const iso = "2026-05-01T14:30:00Z";
31
+ syncDatetimeContext({
32
+ locale: "en",
33
+ timezone: "Asia/Tokyo",
34
+ timeFormat: "24h",
35
+ dateFormat: "iso",
36
+ dateFnsLocale: getDateFnsLocale("en"),
37
+ });
38
+ expect(formatAppDateTime(iso)).toMatch(/2026-05-01 23:30/);
39
+
40
+ syncDatetimeContext({
41
+ locale: "en",
42
+ timezone: "Asia/Ho_Chi_Minh",
43
+ timeFormat: "24h",
44
+ dateFormat: "iso",
45
+ dateFnsLocale: getDateFnsLocale("en"),
46
+ });
47
+ expect(formatAppDateTime(iso)).toMatch(/2026-05-01 21:30/);
48
+ });
49
+
50
+ it("formatCalendarDate uses calendar parts in app timezone", () => {
51
+ syncDatetimeContext({
52
+ locale: "vi",
53
+ timezone: "Asia/Tokyo",
54
+ timeFormat: "24h",
55
+ dateFormat: "iso",
56
+ dateFnsLocale: getDateFnsLocale("vi"),
57
+ });
58
+ const picked = new Date(2026, 4, 2); // May 2 local — treated as calendar date
59
+ expect(formatCalendarDate(picked)).toBe("2026-05-02");
60
+ });
61
+
62
+ it("formatTimeOfDay respects 12h format", () => {
63
+ syncDatetimeContext({
64
+ locale: "en",
65
+ timezone: "Asia/Ho_Chi_Minh",
66
+ timeFormat: "12h",
67
+ dateFormat: "iso",
68
+ dateFnsLocale: getDateFnsLocale("en"),
69
+ });
70
+ expect(formatTimeOfDay("14:30")).toMatch(/2:30 PM/);
71
+ });
72
+
73
+ it("normalizeHhmm pads single-digit hours", () => {
74
+ expect(normalizeHhmm("9:30")).toBe("09:30");
75
+ expect(normalizeHhmm("25:00")).toBeNull();
76
+ });
77
+ });