@eifi1/ui-kit 0.6.1 → 0.7.1

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 (271) hide show
  1. package/README.md +69 -33
  2. package/dist/chart.d.ts +1 -0
  3. package/dist/components/account-settings.js +3 -2
  4. package/dist/components/account-settings.js.map +1 -1
  5. package/dist/components/amount-input.d.ts +5 -0
  6. package/dist/components/amount-input.js +6 -4
  7. package/dist/components/amount-input.js.map +1 -1
  8. package/dist/components/autocomplete.d.ts +33 -6
  9. package/dist/components/autocomplete.js +62 -16
  10. package/dist/components/autocomplete.js.map +1 -1
  11. package/dist/components/calculator.d.ts +5 -0
  12. package/dist/components/calculator.js +2 -1
  13. package/dist/components/calculator.js.map +1 -1
  14. package/dist/components/chart-zoom.js +2 -1
  15. package/dist/components/chart-zoom.js.map +1 -1
  16. package/dist/components/chart.d.ts +33 -2
  17. package/dist/components/chart.js +20 -8
  18. package/dist/components/chart.js.map +1 -1
  19. package/dist/components/chip.d.ts +34 -14
  20. package/dist/components/chip.js +63 -28
  21. package/dist/components/chip.js.map +1 -1
  22. package/dist/components/choice-card.d.ts +18 -1
  23. package/dist/components/choice-card.js +21 -8
  24. package/dist/components/choice-card.js.map +1 -1
  25. package/dist/components/combobox-core.d.ts +61 -2
  26. package/dist/components/combobox-core.js +68 -17
  27. package/dist/components/combobox-core.js.map +1 -1
  28. package/dist/components/combobox.d.ts +11 -7
  29. package/dist/components/combobox.js +51 -28
  30. package/dist/components/combobox.js.map +1 -1
  31. package/dist/components/currency-select.js +3 -3
  32. package/dist/components/currency-select.js.map +1 -1
  33. package/dist/components/danger-confirm.d.ts +30 -6
  34. package/dist/components/danger-confirm.js +51 -32
  35. package/dist/components/danger-confirm.js.map +1 -1
  36. package/dist/components/data-table-filter-popover.d.ts +1 -1
  37. package/dist/components/data-table-filters.d.ts +1 -1
  38. package/dist/components/data-table-pagination.d.ts +6 -3
  39. package/dist/components/data-table-pagination.js +6 -5
  40. package/dist/components/data-table-pagination.js.map +1 -1
  41. package/dist/components/data-table.d.ts +1 -1
  42. package/dist/components/data-table.js +332 -248
  43. package/dist/components/data-table.js.map +1 -1
  44. package/dist/components/date-picker.js +17 -7
  45. package/dist/components/date-picker.js.map +1 -1
  46. package/dist/components/dialog-frame.d.ts +28 -3
  47. package/dist/components/dialog-frame.js +47 -11
  48. package/dist/components/dialog-frame.js.map +1 -1
  49. package/dist/components/disclosure.d.ts +22 -2
  50. package/dist/components/disclosure.js +41 -8
  51. package/dist/components/disclosure.js.map +1 -1
  52. package/dist/components/dropdown.d.ts +7 -3
  53. package/dist/components/dropdown.js +6 -2
  54. package/dist/components/dropdown.js.map +1 -1
  55. package/dist/components/entity-combobox.d.ts +17 -8
  56. package/dist/components/entity-combobox.js +7 -5
  57. package/dist/components/entity-combobox.js.map +1 -1
  58. package/dist/components/file-button.d.ts +38 -5
  59. package/dist/components/file-button.js +23 -2
  60. package/dist/components/file-button.js.map +1 -1
  61. package/dist/components/file-dropzone.d.ts +11 -5
  62. package/dist/components/file-dropzone.js +14 -19
  63. package/dist/components/file-dropzone.js.map +1 -1
  64. package/dist/components/grouped-picker.js +2 -2
  65. package/dist/components/grouped-picker.js.map +1 -1
  66. package/dist/components/hover-menu.d.ts +8 -3
  67. package/dist/components/hover-menu.js +88 -4
  68. package/dist/components/hover-menu.js.map +1 -1
  69. package/dist/components/icon-picker.d.ts +10 -1
  70. package/dist/components/icon-picker.js +5 -1
  71. package/dist/components/icon-picker.js.map +1 -1
  72. package/dist/components/measured-grid.d.ts +165 -0
  73. package/dist/components/measured-grid.js +559 -0
  74. package/dist/components/measured-grid.js.map +1 -0
  75. package/dist/components/mini-calendar.js +7 -8
  76. package/dist/components/mini-calendar.js.map +1 -1
  77. package/dist/components/month-picker.js +13 -9
  78. package/dist/components/month-picker.js.map +1 -1
  79. package/dist/components/multi-entity-combobox.js +2 -2
  80. package/dist/components/multi-entity-combobox.js.map +1 -1
  81. package/dist/components/multi-select.d.ts +9 -1
  82. package/dist/components/multi-select.js +22 -8
  83. package/dist/components/multi-select.js.map +1 -1
  84. package/dist/components/number-field.d.ts +5 -0
  85. package/dist/components/number-input.d.ts +5 -0
  86. package/dist/components/number-input.js +3 -3
  87. package/dist/components/number-input.js.map +1 -1
  88. package/dist/components/numpad-sheet.d.ts +5 -0
  89. package/dist/components/numpad-sheet.js +3 -3
  90. package/dist/components/numpad-sheet.js.map +1 -1
  91. package/dist/components/picker-sheet.js +1 -1
  92. package/dist/components/picker-sheet.js.map +1 -1
  93. package/dist/components/popover.js +11 -2
  94. package/dist/components/popover.js.map +1 -1
  95. package/dist/components/series-chart-ticks.d.ts +13 -0
  96. package/dist/components/series-chart-ticks.js +29 -0
  97. package/dist/components/series-chart-ticks.js.map +1 -0
  98. package/dist/components/series-chart.js +33 -27
  99. package/dist/components/series-chart.js.map +1 -1
  100. package/dist/components/settings-fields.d.ts +2 -0
  101. package/dist/components/swatch-picker.d.ts +10 -1
  102. package/dist/components/swatch-picker.js +2 -0
  103. package/dist/components/swatch-picker.js.map +1 -1
  104. package/dist/components/swipeable-row.d.ts +11 -2
  105. package/dist/components/swipeable-row.js +5 -3
  106. package/dist/components/swipeable-row.js.map +1 -1
  107. package/dist/components/tile-radio.js +5 -7
  108. package/dist/components/tile-radio.js.map +1 -1
  109. package/dist/components/time-input.d.ts +2 -0
  110. package/dist/components/toggle-group.d.ts +13 -3
  111. package/dist/components/toggle-group.js +21 -1
  112. package/dist/components/toggle-group.js.map +1 -1
  113. package/dist/components/toggle-legend.js +1 -2
  114. package/dist/components/toggle-legend.js.map +1 -1
  115. package/dist/components/tooltip.d.ts +10 -4
  116. package/dist/components/tooltip.js +23 -6
  117. package/dist/components/tooltip.js.map +1 -1
  118. package/dist/components/treemap.d.ts +6 -1
  119. package/dist/components/treemap.js +16 -6
  120. package/dist/components/treemap.js.map +1 -1
  121. package/dist/components/ui.d.ts +36 -8
  122. package/dist/components/ui.js +27 -19
  123. package/dist/components/ui.js.map +1 -1
  124. package/dist/components/use-anchor-dir.d.ts +15 -0
  125. package/dist/components/use-anchor-dir.js +14 -0
  126. package/dist/components/use-anchor-dir.js.map +1 -0
  127. package/dist/components/use-mobile-reveal.d.ts +8 -1
  128. package/dist/components/use-mobile-reveal.js +3 -2
  129. package/dist/components/use-mobile-reveal.js.map +1 -1
  130. package/dist/components/use-table-state.d.ts +4 -4
  131. package/dist/components/use-table-state.js +12 -5
  132. package/dist/components/use-table-state.js.map +1 -1
  133. package/dist/{data-table-filters-DM1auUfH.d.ts → data-table-filters-noy0Abvi.d.ts} +40 -7
  134. package/dist/data-table.d.ts +1 -1
  135. package/dist/data-table.js.map +1 -1
  136. package/dist/feedback/feedback-attachment.d.ts +22 -3
  137. package/dist/feedback/feedback-attachment.js +15 -7
  138. package/dist/feedback/feedback-attachment.js.map +1 -1
  139. package/dist/feedback/feedback-dialog.d.ts +3 -2
  140. package/dist/feedback/feedback-dialog.js.map +1 -1
  141. package/dist/feedback.d.ts +1 -1
  142. package/dist/hooks/use-windowed-rows.d.ts +29 -0
  143. package/dist/hooks/use-windowed-rows.js +31 -0
  144. package/dist/hooks/use-windowed-rows.js.map +1 -0
  145. package/dist/i18n/defaults.d.ts +5 -0
  146. package/dist/i18n/defaults.js +5 -1
  147. package/dist/i18n/defaults.js.map +1 -1
  148. package/dist/i18n/kit-labels.d.ts +7 -0
  149. package/dist/i18n/kit-labels.js.map +1 -1
  150. package/dist/i18n/locales/de-CH.d.ts +45 -0
  151. package/dist/i18n/locales/de-CH.js +21 -0
  152. package/dist/i18n/locales/de-CH.js.map +1 -0
  153. package/dist/i18n/locales/de.d.ts +45 -0
  154. package/dist/i18n/locales/de.js +344 -0
  155. package/dist/i18n/locales/de.js.map +1 -0
  156. package/dist/i18n/locales/es.d.ts +45 -0
  157. package/dist/i18n/locales/es.js +339 -0
  158. package/dist/i18n/locales/es.js.map +1 -0
  159. package/dist/i18n/locales/fr.d.ts +45 -0
  160. package/dist/i18n/locales/fr.js +344 -0
  161. package/dist/i18n/locales/fr.js.map +1 -0
  162. package/dist/i18n/locales/hu.d.ts +45 -0
  163. package/dist/i18n/locales/hu.js +343 -0
  164. package/dist/i18n/locales/hu.js.map +1 -0
  165. package/dist/i18n/locales/it.d.ts +45 -0
  166. package/dist/i18n/locales/it.js +342 -0
  167. package/dist/i18n/locales/it.js.map +1 -0
  168. package/dist/i18n/locales/zh.d.ts +45 -0
  169. package/dist/i18n/locales/zh.js +338 -0
  170. package/dist/i18n/locales/zh.js.map +1 -0
  171. package/dist/index.d.ts +7 -4
  172. package/dist/index.js +4 -1
  173. package/dist/index.js.map +1 -1
  174. package/dist/lib/direction.d.ts +24 -0
  175. package/dist/lib/direction.js +19 -0
  176. package/dist/lib/direction.js.map +1 -0
  177. package/dist/rhf/form.d.ts +2 -0
  178. package/dist/rhf/form.js.map +1 -1
  179. package/dist/rhf.d.ts +2 -0
  180. package/dist/search/command-palette.d.ts +5 -0
  181. package/dist/search/command-palette.js +80 -50
  182. package/dist/search/command-palette.js.map +1 -1
  183. package/dist/shell/app-shell.d.ts +13 -1
  184. package/dist/shell/app-shell.js +134 -85
  185. package/dist/shell/app-shell.js.map +1 -1
  186. package/dist/shell/topbar-action-menu.js +1 -1
  187. package/dist/shell/topbar-action-menu.js.map +1 -1
  188. package/dist/shell/topbar-controls.d.ts +7 -3
  189. package/dist/shell/topbar-controls.js +38 -31
  190. package/dist/shell/topbar-controls.js.map +1 -1
  191. package/dist/theme/chart-palette.d.ts +7 -1
  192. package/dist/theme/chart-palette.js +2 -1
  193. package/dist/theme/chart-palette.js.map +1 -1
  194. package/dist/tour/tour.d.ts +11 -5
  195. package/dist/tour/tour.js +124 -58
  196. package/dist/tour/tour.js.map +1 -1
  197. package/dist/wizard/use-wizard.js +30 -13
  198. package/dist/wizard/use-wizard.js.map +1 -1
  199. package/dist/wizard/wizard-summary.js +1 -1
  200. package/dist/wizard/wizard-summary.js.map +1 -1
  201. package/package.json +12 -2
  202. package/src/components/account-settings.tsx +6 -2
  203. package/src/components/amount-input.tsx +10 -8
  204. package/src/components/autocomplete.tsx +110 -24
  205. package/src/components/calculator.tsx +5 -1
  206. package/src/components/chart-zoom.tsx +8 -1
  207. package/src/components/chart.tsx +68 -7
  208. package/src/components/chip.tsx +131 -49
  209. package/src/components/choice-card.tsx +77 -36
  210. package/src/components/combobox-core.tsx +147 -20
  211. package/src/components/combobox.tsx +100 -44
  212. package/src/components/currency-select.tsx +6 -6
  213. package/src/components/danger-confirm.tsx +90 -36
  214. package/src/components/data-table-pagination.tsx +32 -19
  215. package/src/components/data-table.tsx +198 -36
  216. package/src/components/date-picker.tsx +24 -7
  217. package/src/components/dialog-frame.tsx +97 -22
  218. package/src/components/disclosure.tsx +75 -13
  219. package/src/components/dropdown.tsx +17 -5
  220. package/src/components/entity-combobox.tsx +26 -13
  221. package/src/components/file-button.tsx +74 -5
  222. package/src/components/file-dropzone.tsx +44 -35
  223. package/src/components/grouped-picker.tsx +2 -2
  224. package/src/components/hover-menu.tsx +139 -10
  225. package/src/components/icon-picker.tsx +14 -1
  226. package/src/components/measured-grid.tsx +852 -0
  227. package/src/components/mini-calendar.tsx +11 -8
  228. package/src/components/month-picker.tsx +18 -11
  229. package/src/components/multi-entity-combobox.tsx +2 -2
  230. package/src/components/multi-select.tsx +34 -7
  231. package/src/components/number-input.tsx +4 -4
  232. package/src/components/numpad-sheet.tsx +5 -3
  233. package/src/components/picker-sheet.tsx +1 -1
  234. package/src/components/popover.tsx +18 -3
  235. package/src/components/series-chart-ticks.ts +52 -0
  236. package/src/components/series-chart.tsx +39 -31
  237. package/src/components/swatch-picker.tsx +11 -0
  238. package/src/components/swipeable-row.tsx +21 -6
  239. package/src/components/tile-radio.tsx +5 -7
  240. package/src/components/toggle-group.tsx +39 -3
  241. package/src/components/toggle-legend.tsx +5 -2
  242. package/src/components/tooltip.tsx +40 -12
  243. package/src/components/treemap.tsx +28 -5
  244. package/src/components/ui.tsx +79 -33
  245. package/src/components/use-anchor-dir.ts +20 -0
  246. package/src/components/use-mobile-reveal.ts +10 -2
  247. package/src/components/use-table-state.ts +44 -7
  248. package/src/data-table.ts +1 -0
  249. package/src/feedback/feedback-attachment.tsx +36 -9
  250. package/src/feedback/feedback-dialog.tsx +3 -2
  251. package/src/hooks/use-windowed-rows.ts +69 -0
  252. package/src/i18n/defaults.ts +4 -0
  253. package/src/i18n/kit-labels.tsx +4 -0
  254. package/src/i18n/locales/de-CH.ts +30 -0
  255. package/src/i18n/locales/de.ts +365 -0
  256. package/src/i18n/locales/es.ts +358 -0
  257. package/src/i18n/locales/fr.ts +371 -0
  258. package/src/i18n/locales/hu.ts +359 -0
  259. package/src/i18n/locales/it.ts +360 -0
  260. package/src/i18n/locales/zh.ts +348 -0
  261. package/src/index.ts +10 -1
  262. package/src/lib/direction.ts +37 -0
  263. package/src/rhf/form.tsx +21 -0
  264. package/src/search/command-palette.tsx +77 -19
  265. package/src/shell/app-shell.tsx +201 -130
  266. package/src/shell/topbar-action-menu.tsx +1 -1
  267. package/src/shell/topbar-controls.tsx +19 -8
  268. package/src/theme/chart-palette.ts +9 -2
  269. package/src/tour/tour.tsx +119 -18
  270. package/src/wizard/use-wizard.ts +55 -17
  271. package/src/wizard/wizard-summary.tsx +1 -1
@@ -0,0 +1,348 @@
1
+ import { formatFileSize } from "../kit-labels";
2
+ import type { UiKitLabels } from "../kit-labels";
3
+
4
+ /**
5
+ * The kit's words in Chinese (Simplified): every namespace of {@link UiKitLabels}, for
6
+ * `<UiKitProvider labels={UI_KIT_LABELS_ZH}>`.
7
+ *
8
+ * Self-contained on purpose — nothing but the kit's own `formatFileSize` — so it works
9
+ * as a starting point to copy and adjust. Counts and sizes are formatted with
10
+ * `zh-CN` digits; {@link uiKitLabelsZh} takes another number locale
11
+ * (e.g. `"de-CH"` for 1’234) without touching the words.
12
+ */
13
+ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
14
+ const num = new Intl.NumberFormat(numberLocale);
15
+ const n = (value: number) => num.format(value);
16
+
17
+ return {
18
+ feedbackAttachment: {
19
+ attachmentAdd: "附加图片",
20
+ attachmentCapture: "截取屏幕",
21
+ attachmentPaste: "……或从剪贴板粘贴截图。",
22
+ attachmentRemove: "移除附件",
23
+ },
24
+ measuredGrid: {
25
+ view: "表格视图",
26
+ cellsView: "单元格",
27
+ textView: "文本",
28
+ addRow: "添加行",
29
+ removeRow: (row) => `删除第 ${row} 行`,
30
+ clear: "清空表格",
31
+ pasteHint: "可将电子表格中的一块数据粘贴到任意单元格",
32
+ cell: (column, row) => `${column},第 ${row} 行`,
33
+ rowNumber: "行",
34
+ rowActions: "行操作",
35
+ keyboardHint:
36
+ "方向键在单元格之间移动。直接输入替换单元格内容,F2 编辑,Esc 撤销编辑。回车向下移动,并在末尾添加一行。",
37
+ lineError: (line) => `无法读取第 ${line} 行`,
38
+ points: (count) => `${count} 个点`,
39
+ problems: (count) => `${count} 个单元格不是数字`,
40
+ },
41
+ pageContents: { title: "本页目录" },
42
+ common: {
43
+ close: "关闭",
44
+ clear: "清除",
45
+ search: "搜索",
46
+ done: "完成",
47
+ cancel: "取消",
48
+ save: "保存",
49
+ back: "上一步",
50
+ next: "下一步",
51
+ remove: "移除",
52
+ loading: "加载中…",
53
+ noResults: "无结果",
54
+ // Full-width colon, no space after it.
55
+ fieldValue: (field, value) => `${field}:${value}`,
56
+ },
57
+ dataTable: {
58
+ columns: "列",
59
+ selectAllRows: "全选所有行",
60
+ sortHint: "点击排序 · 按住 Shift 点击可添加排序",
61
+ filter: "筛选",
62
+ close: "关闭",
63
+ selectRow: "选择行",
64
+ autoSize: "自动调整列宽",
65
+ loading: "加载中…",
66
+ filters: "筛选条件",
67
+ clearAll: "全部清除",
68
+ done: "完成",
69
+ pageSize: "每页行数",
70
+ pageSizeAll: "全部",
71
+ prevPage: "上一页",
72
+ nextPage: "下一页",
73
+ clearFilter: "清除筛选",
74
+ filterPlaceholder: "筛选…",
75
+ selectFilter: "选择",
76
+ selectAll: "全部",
77
+ selectNone: "无",
78
+ dateFrom: "从",
79
+ dateTo: "至",
80
+ numberMin: "最小值",
81
+ numberMax: "最大值",
82
+ numberAbs: "绝对值",
83
+ presets: {
84
+ today: "今天",
85
+ yesterday: "昨天",
86
+ this_week: "本周",
87
+ last_week: "上周",
88
+ last_7_days: "最近 7 天",
89
+ last_30_days: "最近 30 天",
90
+ this_month: "本月",
91
+ last_month: "上月",
92
+ last_3_months: "最近 3 个月",
93
+ ytd: "今年至今",
94
+ last_year: "去年",
95
+ },
96
+ table: "数据表格",
97
+ filterResults: (shown, total) => `显示 ${n(shown)} 行,共 ${n(total)} 行`,
98
+ sortedAscending: (column) => `已按“${column}”升序排序`,
99
+ sortedDescending: (column) => `已按“${column}”降序排序`,
100
+ sortCleared: (column) => `已取消“${column}”的排序`,
101
+ pageChanged: (page, totalPages) => `第 ${n(page)} 页,共 ${n(totalPages)} 页`,
102
+ pageRange: (from, to, total) => `${n(from)}–${n(to)} / ${n(total)}`,
103
+ rowCount: (total) => n(total),
104
+ columnsCount: (visible, total) => `列(${n(visible)}/${n(total)})`,
105
+ },
106
+ miniCalendar: {
107
+ previousMonth: "上个月",
108
+ nextMonth: "下个月",
109
+ // Already formatted in the provider's locale ("2026年9月14日星期一").
110
+ day: (date) => date,
111
+ chooseStart: "请选择开始日期",
112
+ chooseEnd: "请选择结束日期",
113
+ startSelected: (date) => `已选择 ${date} 为开始日期。请选择结束日期。`,
114
+ rangeSelected: (from, to) => `已选择 ${from} 至 ${to}。如需重新选择,请先选择开始日期。`,
115
+ },
116
+ monthPicker: {
117
+ previousYear: "上一年",
118
+ nextYear: "下一年",
119
+ panel: "选择月份",
120
+ month: (monthYear) => monthYear,
121
+ },
122
+ datePicker: {
123
+ panel: "选择日期",
124
+ rangePanel: "选择日期范围",
125
+ clear: "清除",
126
+ previousDay: "前一天",
127
+ nextDay: "后一天",
128
+ today: "今天",
129
+ },
130
+ popover: {
131
+ panel: "弹出框",
132
+ },
133
+ combobox: {
134
+ search: "搜索",
135
+ noResults: "无结果",
136
+ clear: "清除",
137
+ loading: "加载中…",
138
+ create: (query) => `创建“${query}”`,
139
+ selectedCount: (count) => `已选 ${n(count)} 项`,
140
+ loadError: "无法加载结果",
141
+ resultCount: (count) => `${n(count)} 个结果`,
142
+ minChars: (count) => `请至少输入 ${n(count)} 个字符`,
143
+ },
144
+ multiSelect: {
145
+ search: "搜索",
146
+ selectAll: "全选",
147
+ clear: "清除",
148
+ all: "全部",
149
+ // The bare count, as in English: the trigger has always shown just the number.
150
+ selectedCount: (count) => n(count),
151
+ },
152
+ calculator: {
153
+ open: "打开计算器",
154
+ panel: "计算器",
155
+ calculation: "算式",
156
+ backspace: "退格",
157
+ clear: "清除",
158
+ equals: "等于",
159
+ done: "完成",
160
+ plus: "加",
161
+ minus: "减",
162
+ times: "乘",
163
+ divide: "除以",
164
+ decimal: "小数点",
165
+ },
166
+ currency: {
167
+ currency: "货币",
168
+ search: "搜索货币",
169
+ },
170
+ chipInput: {
171
+ added: (value) => `已添加“${value}”`,
172
+ removed: (value) => `已移除“${value}”`,
173
+ remove: "移除",
174
+ atLimit: (max) => `已达上限(${n(max)} 项)`,
175
+ duplicate: (value) => `“${value}”已在列表中`,
176
+ },
177
+ swatchPicker: {
178
+ none: "无颜色",
179
+ mixed: "混合:所选项目的颜色各不相同",
180
+ },
181
+ iconPicker: {
182
+ none: "无图标",
183
+ mixed: "混合:所选项目的图标各不相同",
184
+ search: "搜索图标",
185
+ noResults: "没有匹配的图标",
186
+ resultCount: (count) => `${n(count)} 个图标`,
187
+ },
188
+ fieldSync: {
189
+ synced: "已保存",
190
+ edited: "有未保存的更改",
191
+ pending: "正在保存…",
192
+ error: "保存失败",
193
+ retry: "重试",
194
+ },
195
+ passwordReveal: {
196
+ show: "显示密码",
197
+ hide: "隐藏密码",
198
+ },
199
+ dangerConfirm: {
200
+ arm: "删除…",
201
+ confirm: "删除",
202
+ cancel: "取消",
203
+ prompt: "此操作无法撤销。",
204
+ password: "密码",
205
+ phrase: (phrase) => `输入“${phrase}”以确认`,
206
+ },
207
+ tabs: {
208
+ add: "添加标签页",
209
+ // Quoted, so the spacing rule holds whether the tab's name is Han or Latin.
210
+ remove: (tab) => `移除“${tab}”`,
211
+ },
212
+ appShell: {
213
+ collapse: "收起侧边栏",
214
+ expand: "展开侧边栏",
215
+ toggleGroup: (groupLabel) => `${groupLabel}:页面`,
216
+ },
217
+ topBar: {
218
+ theme: "切换主题",
219
+ palette: "外观预设",
220
+ language: "语言",
221
+ switchRole: "切换角色",
222
+ role: (value) => `角色:${value}`,
223
+ },
224
+ pickerSheet: {
225
+ close: "关闭",
226
+ },
227
+ dialogFrame: {
228
+ close: "关闭",
229
+ },
230
+ swipeableRow: {
231
+ actions: "行操作",
232
+ },
233
+ file: {
234
+ // The kit's own `Intl` unit formatting, pinned to this locale.
235
+ size: (bytes) => formatFileSize(bytes, numberLocale),
236
+ },
237
+ filePicker: {
238
+ rejectedPick: (count) => (count === 1 ? "未添加该文件" : `${count} 个文件均未添加`),
239
+ rejectedType: (name) => `“${name}”的文件类型不受支持`,
240
+ rejectedTypeOnly: (accept) => `仅限 ${accept} 文件`,
241
+ rejectedSize: (name, maxSize) => `“${name}”超过 ${maxSize}`,
242
+ rejectedCount: (name, maxFiles) => `未添加“${name}”:最多 ${n(maxFiles)} 个文件`,
243
+ rejectedInvalid: (name) => `“${name}”无法在此使用`,
244
+ rejectedMany: (count) => `${n(count)} 个文件未添加`,
245
+ selected: (count, firstName) =>
246
+ count === 1 ? `已选择“${firstName}”` : `已选择 ${n(count)} 个文件`,
247
+ remove: (name) => `移除“${name}”`,
248
+ clearAll: "移除所有文件",
249
+ removed: (name) => `已移除“${name}”`,
250
+ cleared: "已移除所有文件",
251
+ },
252
+ wizard: {
253
+ cancel: "取消",
254
+ back: "上一步",
255
+ next: "下一步",
256
+ skip: "跳过",
257
+ finish: "完成",
258
+ submitting: "正在创建…",
259
+ steps: "步骤",
260
+ step: (current, total) => `第 ${n(current)} 步,共 ${n(total)} 步`,
261
+ cancelTitle: "放弃此表单?",
262
+ confirmCancel: "已填写的内容将会丢失。",
263
+ cancelConfirmLabel: "放弃",
264
+ cancelDismissLabel: "继续编辑",
265
+ reviewTitle: "确认信息",
266
+ edit: "编辑",
267
+ missingRequired: "请填写所有必填字段。",
268
+ genericError: "发生错误",
269
+ },
270
+ tour: {
271
+ next: "下一步",
272
+ back: "上一步",
273
+ skip: "跳过",
274
+ done: "完成",
275
+ awaitClickHint: "点击高亮的元素以继续",
276
+ step: (current, total) => `${n(current)} / ${n(total)}`,
277
+ },
278
+ commandPalette: {
279
+ placeholder: "搜索…",
280
+ empty: "无结果",
281
+ loading: "搜索中…",
282
+ dialog: "搜索",
283
+ error: "搜索失败,请重试。",
284
+ },
285
+ sparkline: {
286
+ rising: (first, last) => `从 ${first} 上升到 ${last}`,
287
+ falling: (first, last) => `从 ${first} 下降到 ${last}`,
288
+ flat: (value) => `保持在 ${value} 不变`,
289
+ single: (value) => `仅一个值:${value}`,
290
+ noData: "无数据",
291
+ named: (name, summary) => `${name}:${summary}`,
292
+ },
293
+ statTile: {
294
+ increase: (amount) => `上升 ${amount}`,
295
+ decrease: (amount) => `下降 ${amount}`,
296
+ unchanged: "无变化",
297
+ better: (change) => `${change}(向好)`,
298
+ worse: (change) => `${change}(变差)`,
299
+ noValue: "无数据",
300
+ loading: "加载中…",
301
+ },
302
+ signaturePad: {
303
+ label: "签名",
304
+ instructions: "请使用鼠标、手指或触控笔在框内签名。",
305
+ typedFallbackHint: "如果无法手写,可以改为输入姓名。",
306
+ empty: "尚未签名",
307
+ signed: "已签名",
308
+ undo: "撤销上一笔",
309
+ clear: "清除",
310
+ save: "保存签名",
311
+ useTyped: "改为输入姓名",
312
+ useDrawn: "改为手写",
313
+ typedName: "姓名",
314
+ cleared: "签名已清除",
315
+ undone: "已撤销上一笔",
316
+ viewEmpty: "未签名",
317
+ viewDrawn: "手写签名",
318
+ // Quoted, so the spacing rule holds whether the name is Han or Latin.
319
+ viewTyped: (name) => `以输入的姓名“${name}”签名`,
320
+ },
321
+ passwordStrength: {
322
+ tooShort: "太短",
323
+ weak: "弱",
324
+ fair: "一般",
325
+ good: "良好",
326
+ strong: "强",
327
+ announcement: (level) => `密码强度:${level}`,
328
+ // Unformatted, as in the English default.
329
+ ruleLength: (minLength) => `至少 ${minLength} 个字符`,
330
+ ruleCase: "包含大写和小写字母",
331
+ ruleDigit: "包含数字",
332
+ ruleSymbol: "包含符号",
333
+ optional: (rule) => `${rule}(可选)`,
334
+ met: "已满足:",
335
+ notMet: "未满足:",
336
+ tooLong: (maxBytes) => `最多 ${maxBytes} 个字符(带重音的字母和表情符号按多个字符计算)。`,
337
+ },
338
+ seriesChart: {
339
+ resetZoom: "重置缩放",
340
+ zoomHint:
341
+ "拖动以缩放:近似正方形的选区同时缩放两条坐标轴,细长的选区只缩放其所沿的那条轴。双击可重置。",
342
+ empty: "无数据",
343
+ legend: "数据系列",
344
+ },
345
+ };
346
+ }
347
+
348
+ export const UI_KIT_LABELS_ZH: UiKitLabels = uiKitLabelsZh();
package/src/index.ts CHANGED
@@ -87,6 +87,9 @@ export * from "./components/swatch-picker";
87
87
  export * from "./components/icon-picker";
88
88
  export * from "./components/choice-card";
89
89
  export * from "./components/autocomplete";
90
+ export * from "./components/measured-grid";
91
+ export { useWindowedRows } from "./hooks/use-windowed-rows";
92
+ export type { WindowedRows } from "./hooks/use-windowed-rows";
90
93
  // Named, not `export *`: file-button.tsx also holds the screening helpers the
91
94
  // dropzone shares, which are internal.
92
95
  export { FileButton, useFilePicker, matchesAccept, DEFAULT_FILE_PICKER_LABELS } from "./components/file-button";
@@ -131,7 +134,13 @@ export { FilterPopover } from "./components/data-table-filter-popover";
131
134
  // data-table re-exports SortState internally; export the rest explicitly to
132
135
  // avoid a duplicate SortState star-export (it comes from data-table-sort).
133
136
  export { DataTable } from "./components/data-table";
134
- export type { DataTableColumn, DataTableProps, ServerPagination, FilterState } from "./components/data-table";
137
+ export type {
138
+ DataTableColumn,
139
+ DataTableProps,
140
+ ServerPagination,
141
+ FilterState,
142
+ MobileSwipeActions,
143
+ } from "./components/data-table";
135
144
 
136
145
  // ── shell (composable app chrome) ────────────────────────────────────────────
137
146
  export * from "./shell/topbar-controls";
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Reading direction, as the DOM has it — internal, not exported from the barrel.
3
+ *
4
+ * The kit lays itself out with logical classes (`ps-*`, `end-*`, `text-start`), which
5
+ * need no JavaScript. These helpers are for the two things CSS cannot do:
6
+ *
7
+ * - **Arrow keys.** ←/→ mean previous/next in LTR and the reverse in RTL. Resolve the
8
+ * key through {@link horizontalStep} instead of comparing to "ArrowRight".
9
+ * - **Portals.** A panel portalled to `document.body` leaves the subtree whose `dir`
10
+ * it inherited. Read the anchor's direction with {@link dirOf} and put it on the
11
+ * portalled root, and position it from the anchor's START edge, not `rect.left`.
12
+ */
13
+
14
+ export type Direction = "ltr" | "rtl";
15
+
16
+ /** The effective direction at `el`: its nearest `[dir]` ancestor (or itself), else the
17
+ * document's, else LTR. Attribute-based on purpose — jsdom computes no `direction`. */
18
+ export function dirOf(el: Element | null | undefined): Direction {
19
+ const source =
20
+ el?.closest("[dir]") ??
21
+ (typeof document !== "undefined" ? document.documentElement : null);
22
+ return source?.getAttribute("dir") === "rtl" ? "rtl" : "ltr";
23
+ }
24
+
25
+ export function isRtl(el: Element | null | undefined): boolean {
26
+ return dirOf(el) === "rtl";
27
+ }
28
+
29
+ /**
30
+ * +1 for a key that moves FORWARD along the reading direction, -1 for backward, 0 for
31
+ * any other key. ArrowRight is forward in LTR and backward in RTL.
32
+ */
33
+ export function horizontalStep(key: string, el: Element | null | undefined): 1 | -1 | 0 {
34
+ if (key !== "ArrowRight" && key !== "ArrowLeft") return 0;
35
+ const forward = (key === "ArrowRight") !== isRtl(el);
36
+ return forward ? 1 : -1;
37
+ }
package/src/rhf/form.tsx CHANGED
@@ -51,6 +51,27 @@
51
51
  * `<Input {...field} invalid={fieldState.invalid} />`. `FormControl` does not inject
52
52
  * `invalid` itself because its child may be a native element, which would print it
53
53
  * into the DOM as an unknown attribute.
54
+ *
55
+ * ## Clearing a picker: `null`, unless the picker is told otherwise
56
+ *
57
+ * Nothing here touches a value. `FormField` is `Controller`, so what a control hands
58
+ * `field.onChange` is exactly what lands in the form — and what the entity pickers
59
+ * hand it on a clear is `null`: `EntityCombobox`'s "×", `InlineEntityCombobox`'s "×"
60
+ * and its emptied text. (`MultiEntityCombobox` and `MultiSelect` clear to `[]`.)
61
+ *
62
+ * A zod schema that spells "no choice" as `""` (`z.string()`, or `z.literal("")` in a
63
+ * union) then fails on the `null`. Say so on the PICKER, not here:
64
+ *
65
+ * ```tsx
66
+ * <EntityCombobox clearValue="" value={field.value} onChange={field.onChange} … />
67
+ * ```
68
+ *
69
+ * `clearValue` also types `onChange` (`(v: V | "") => void` rather than
70
+ * `V | null`) and reads `""` back as "nothing selected", so an initial `defaultValues`
71
+ * of `""` shows the placeholder rather than an unresolvable id. The adapter does not
72
+ * coerce because it cannot see the schema: mapping `null` to `""` for every field
73
+ * would break every schema that wants `null` (`z.string().nullable()`), which is the
74
+ * pickers' default for a reason — it is the one value no option can have.
54
75
  */
55
76
  import {
56
77
  Children,
@@ -1,9 +1,11 @@
1
- import { useEffect, useMemo, useRef, useState } from "react";
1
+ import { useEffect, useId, useMemo, useRef, useState } from "react";
2
2
  import type { ReactNode } from "react";
3
3
  import { createPortal } from "react-dom";
4
4
  import { Search } from "lucide-react";
5
5
  import { cn } from "../lib/cn";
6
6
  import { useOverlayHistory } from "../hooks/use-overlay-history";
7
+ import { useFocusTrap } from "../hooks/use-focus-trap";
8
+ import { useBodyScrollLock } from "../hooks/use-body-scroll-lock";
7
9
  import { useKitLabels } from "../i18n/kit-labels";
8
10
 
9
11
  export interface CommandItem {
@@ -42,6 +44,11 @@ export interface CommandPaletteLabels {
42
44
  * `CommandPaletteLabels` keeps compiling; the default always carries it.
43
45
  */
44
46
  dialog?: string;
47
+ /**
48
+ * Shown in place of the results when the `search` provider throws or its promise
49
+ * rejects. Optional for the same reason as `dialog`; the default always carries it.
50
+ */
51
+ error?: string;
45
52
  }
46
53
 
47
54
  export const DEFAULT_COMMAND_PALETTE_LABELS: CommandPaletteLabels = {
@@ -49,6 +56,7 @@ export const DEFAULT_COMMAND_PALETTE_LABELS: CommandPaletteLabels = {
49
56
  empty: "No results",
50
57
  loading: "Searching…",
51
58
  dialog: "Search",
59
+ error: "Search failed. Try again.",
52
60
  };
53
61
 
54
62
  /** The defaults minus `dialog`, so a resolved `dialog` means someone SUPPLIED one —
@@ -124,7 +132,16 @@ export function CommandPalette({
124
132
  const [query, setQuery] = useState("");
125
133
  const [results, setResults] = useState<CommandItem[]>([]);
126
134
  const [loading, setLoading] = useState(false);
135
+ const [failed, setFailed] = useState(false);
127
136
  const [active, setActive] = useState(0);
137
+ // Per instance: these were the literal "command-palette-list" / "command-item-<id>",
138
+ // so two palettes on one page (an app-wide one and a scoped one) pointed each
139
+ // other's `aria-controls` and `aria-activedescendant` at the wrong list.
140
+ const uid = useId();
141
+ const listId = `${uid}-list`;
142
+ const itemId = (id: string) => `${uid}-item-${id}`;
143
+ const groupId = (index: number) => `${uid}-group-${index}`;
144
+ const dialogRef = useRef<HTMLDivElement>(null);
128
145
  const inputRef = useRef<HTMLInputElement>(null);
129
146
  const listRef = useRef<HTMLUListElement>(null);
130
147
  const searchRef = useRef(search);
@@ -137,13 +154,17 @@ export function CommandPalette({
137
154
  // mounting-while-open is what lets it sit here.
138
155
  useOverlayHistory(open, onClose);
139
156
 
140
- // Reset + focus when opened.
157
+ // A modal like any other: Tab stays inside, the page behind does not scroll, and
158
+ // focus goes back to whatever opened it — the palette used to drop it on <body>.
159
+ // Initial focus is the field, not the container: typing is the only reason to open it.
160
+ useFocusTrap(dialogRef, { active: open, initialFocus: () => inputRef.current });
161
+ useBodyScrollLock(open);
162
+
163
+ // Reset when opened.
141
164
  useEffect(() => {
142
165
  if (!open) return;
143
166
  setQuery("");
144
167
  setActive(0);
145
- const id = requestAnimationFrame(() => inputRef.current?.focus());
146
- return () => cancelAnimationFrame(id);
147
168
  }, [open]);
148
169
 
149
170
  // Debounced, race-safe search.
@@ -156,8 +177,18 @@ export function CommandPalette({
156
177
  const r = await Promise.resolve(searchRef.current(query));
157
178
  if (reqId.current === id) {
158
179
  setResults(r);
180
+ setFailed(false);
181
+ setActive(0);
182
+ }
183
+ } catch (err) {
184
+ // There was no catch: the previous query's results stayed on screen as if they
185
+ // answered this one, and the rejection went unhandled. Clear them and say so.
186
+ if (reqId.current === id) {
187
+ setResults([]);
188
+ setFailed(true);
159
189
  setActive(0);
160
190
  }
191
+ console.error("CommandPalette: search failed", err);
161
192
  } finally {
162
193
  if (reqId.current === id) setLoading(false);
163
194
  }
@@ -202,7 +233,13 @@ export function CommandPalette({
202
233
  } else if (e.key === "Enter") {
203
234
  e.preventDefault();
204
235
  choose(flat[active]);
205
- } else if (e.key === "Escape") {
236
+ }
237
+ };
238
+
239
+ // Escape on the whole dialog, not only the field: once Tab has moved focus to a row
240
+ // (or a footer control), Escape used to do nothing at all.
241
+ const onDialogKeyDown = (e: React.KeyboardEvent) => {
242
+ if (e.key === "Escape") {
206
243
  e.preventDefault();
207
244
  onClose();
208
245
  }
@@ -224,11 +261,17 @@ export function CommandPalette({
224
261
  if (e.target === e.currentTarget) onClose();
225
262
  }}
226
263
  >
264
+ {/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- Escape from anywhere in the dialog */}
227
265
  <div
266
+ ref={dialogRef}
228
267
  role="dialog"
229
268
  aria-modal="true"
230
269
  aria-label={dialogName}
231
- className="flex max-h-[70vh] w-full max-w-xl flex-col overflow-hidden rounded-xl border border-[var(--border)] bg-[var(--bg-surface)] shadow-2xl"
270
+ // The trap's fallback target when the field is not there to take focus.
271
+ tabIndex={-1}
272
+ // Escape bubbling up from anything inside — a dialog is where it belongs.
273
+ onKeyDown={onDialogKeyDown}
274
+ className="flex max-h-[70vh] w-full max-w-xl flex-col overflow-hidden rounded-xl border border-[var(--border)] bg-[var(--bg-surface)] shadow-2xl outline-none"
232
275
  >
233
276
  <div className="flex items-center gap-2 border-b border-[var(--border)] px-3">
234
277
  <Search className="size-4 shrink-0 text-[var(--text-placeholder)]" />
@@ -236,8 +279,8 @@ export function CommandPalette({
236
279
  ref={inputRef}
237
280
  role="combobox"
238
281
  aria-expanded
239
- aria-controls="command-palette-list"
240
- aria-activedescendant={flat[active] ? `command-item-${flat[active].id}` : undefined}
282
+ aria-controls={listId}
283
+ aria-activedescendant={flat[active] ? itemId(flat[active].id) : undefined}
241
284
  value={query}
242
285
  onChange={(e) => setQuery(e.target.value)}
243
286
  onKeyDown={onKeyDown}
@@ -247,16 +290,31 @@ export function CommandPalette({
247
290
  {loading && <span className="shrink-0 text-[11px] text-[var(--text-placeholder)]">{l.loading}</span>}
248
291
  </div>
249
292
 
250
- <ul id="command-palette-list" ref={listRef} role="listbox" className="min-h-0 flex-1 overflow-y-auto py-1">
251
- {flat.length === 0 && !loading && (
252
- <li className="px-3 py-6 text-center text-sm text-[var(--text-muted)]">{l.empty}</li>
253
- )}
254
- {groups.map(({ group, items }) => (
255
- <li key={group}>
256
- <div className="px-3 pb-0.5 pt-2 text-[10px] font-semibold uppercase tracking-wide text-[var(--text-placeholder)]">
293
+ {/* The status lines sit outside the listbox: a listbox owns options and groups,
294
+ and "No results" is neither. */}
295
+ {failed && !loading && (
296
+ <div role="alert" className="px-3 py-6 text-center text-sm text-[var(--text-muted)]">
297
+ {l.error ?? DEFAULT_COMMAND_PALETTE_LABELS.error}
298
+ </div>
299
+ )}
300
+ {!failed && flat.length === 0 && !loading && (
301
+ <div className="px-3 py-6 text-center text-sm text-[var(--text-muted)]">{l.empty}</div>
302
+ )}
303
+ {/* Listbox grouping: each group is a `role="group"` named by its heading, and
304
+ the list markup in between is presentational so the listbox's children are
305
+ groups and options only — as a plain <li>/<ul> tree a reader announced
306
+ "list, 2 items" between options and never tied the heading to its rows. */}
307
+ <ul id={listId} ref={listRef} role="listbox" aria-label={dialogName} className="min-h-0 flex-1 overflow-y-auto py-1">
308
+ {groups.map(({ group, items }, groupIndex) => (
309
+ <li key={group} role="presentation">
310
+ <div
311
+ id={groupId(groupIndex)}
312
+ role="presentation"
313
+ className="px-3 pb-0.5 pt-2 text-[10px] font-semibold uppercase tracking-wide text-[var(--text-placeholder)]"
314
+ >
257
315
  {group}
258
316
  </div>
259
- <ul>
317
+ <ul role="group" aria-labelledby={groupId(groupIndex)}>
260
318
  {items.map((item) => {
261
319
  flatIndex += 1;
262
320
  const idx = flatIndex;
@@ -266,13 +324,13 @@ export function CommandPalette({
266
324
  // `role="option"` on an <a href> is fine: the browser's middle-click
267
325
  // behaviour keys off the element, not the ARIA role.
268
326
  const shared = {
269
- id: `command-item-${item.id}`,
327
+ id: itemId(item.id),
270
328
  role: "option" as const,
271
329
  "aria-selected": isActive,
272
330
  "data-index": idx,
273
331
  onMouseMove: () => setActive(idx),
274
332
  className: cn(
275
- "flex w-full items-center gap-2.5 px-3 py-2 text-left text-sm",
333
+ "flex w-full items-center gap-2.5 px-3 py-2 text-start text-sm",
276
334
  isActive
277
335
  ? "bg-[var(--bg-active)] text-[var(--text-primary)]"
278
336
  : "text-[var(--text-secondary)]",
@@ -292,7 +350,7 @@ export function CommandPalette({
292
350
  </>
293
351
  );
294
352
  return (
295
- <li key={item.id}>
353
+ <li key={item.id} role="presentation">
296
354
  {item.href ? (
297
355
  <a
298
356
  {...shared}