@lark-apaas/coding-template-nestjs-react-fullstack 0.1.0-alpha.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 (250) hide show
  1. package/LICENSE +13 -0
  2. package/README.md +41 -0
  3. package/hooks/after-install.mjs +3 -0
  4. package/hooks/after-render.mjs +3 -0
  5. package/hooks/before-render.mjs +3 -0
  6. package/package.json +36 -0
  7. package/template/.codebase/pipelines/ci.yaml +34 -0
  8. package/template/.codebase/pipelines/coco-code-review.yaml +21 -0
  9. package/template/.prettierrc +8 -0
  10. package/template/.spark_project +16 -0
  11. package/template/.stylelintrc.js +12 -0
  12. package/template/_env.local +38 -0
  13. package/template/_gitignore +64 -0
  14. package/template/_npmrc +1 -0
  15. package/template/client/index.html +23 -0
  16. package/template/client/public/favicon.svg +40 -0
  17. package/template/client/src/api/index.ts +19 -0
  18. package/template/client/src/app.tsx +22 -0
  19. package/template/client/src/components/Layout.tsx +11 -0
  20. package/template/client/src/components/business-ui/README.md +519 -0
  21. package/template/client/src/components/business-ui/api/chats/queries.ts +39 -0
  22. package/template/client/src/components/business-ui/api/chats/service.ts +29 -0
  23. package/template/client/src/components/business-ui/api/departments/queries.ts +42 -0
  24. package/template/client/src/components/business-ui/api/departments/service.ts +18 -0
  25. package/template/client/src/components/business-ui/api/files/service.ts +28 -0
  26. package/template/client/src/components/business-ui/api/user-profiles/queries.ts +42 -0
  27. package/template/client/src/components/business-ui/api/user-profiles/service.ts +23 -0
  28. package/template/client/src/components/business-ui/api/users/queries.ts +34 -0
  29. package/template/client/src/components/business-ui/api/users/service.ts +40 -0
  30. package/template/client/src/components/business-ui/chat-select/README.md +128 -0
  31. package/template/client/src/components/business-ui/chat-select/chat-item.tsx +85 -0
  32. package/template/client/src/components/business-ui/chat-select/chat-select-tag.tsx +83 -0
  33. package/template/client/src/components/business-ui/chat-select/chat-select.tsx +197 -0
  34. package/template/client/src/components/business-ui/chat-select/index.tsx +12 -0
  35. package/template/client/src/components/business-ui/chat-select/types.ts +102 -0
  36. package/template/client/src/components/business-ui/chat-select/use-chat-value.ts +180 -0
  37. package/template/client/src/components/business-ui/chat-select/utils.ts +81 -0
  38. package/template/client/src/components/business-ui/department-select/department-item.tsx +55 -0
  39. package/template/client/src/components/business-ui/department-select/department-select-field.tsx +86 -0
  40. package/template/client/src/components/business-ui/department-select/department-select-tag.tsx +56 -0
  41. package/template/client/src/components/business-ui/department-select/department-select.tsx +168 -0
  42. package/template/client/src/components/business-ui/department-select/icon-department.tsx +22 -0
  43. package/template/client/src/components/business-ui/department-select/index.tsx +7 -0
  44. package/template/client/src/components/business-ui/department-select/types.ts +15 -0
  45. package/template/client/src/components/business-ui/department-select/utils.ts +17 -0
  46. package/template/client/src/components/business-ui/entity-combobox/base-combobox-content.tsx +35 -0
  47. package/template/client/src/components/business-ui/entity-combobox/base-combobox-empty.tsx +28 -0
  48. package/template/client/src/components/business-ui/entity-combobox/base-combobox-error.tsx +35 -0
  49. package/template/client/src/components/business-ui/entity-combobox/base-combobox-item.tsx +55 -0
  50. package/template/client/src/components/business-ui/entity-combobox/base-combobox-list.tsx +174 -0
  51. package/template/client/src/components/business-ui/entity-combobox/base-combobox-loading.tsx +19 -0
  52. package/template/client/src/components/business-ui/entity-combobox/base-combobox-search.tsx +31 -0
  53. package/template/client/src/components/business-ui/entity-combobox/base-combobox-trigger.tsx +281 -0
  54. package/template/client/src/components/business-ui/entity-combobox/base-combobox.tsx +132 -0
  55. package/template/client/src/components/business-ui/entity-combobox/context.tsx +19 -0
  56. package/template/client/src/components/business-ui/entity-combobox/entity-combobox.tsx +145 -0
  57. package/template/client/src/components/business-ui/entity-combobox/highlight-text.tsx +52 -0
  58. package/template/client/src/components/business-ui/entity-combobox/hooks.tsx +21 -0
  59. package/template/client/src/components/business-ui/entity-combobox/index.tsx +31 -0
  60. package/template/client/src/components/business-ui/entity-combobox/item-pill.tsx +179 -0
  61. package/template/client/src/components/business-ui/entity-combobox/popover-wrapper.tsx +149 -0
  62. package/template/client/src/components/business-ui/entity-combobox/search-trigger.tsx +262 -0
  63. package/template/client/src/components/business-ui/entity-combobox/shared-types.ts +296 -0
  64. package/template/client/src/components/business-ui/entity-combobox/size-variants.tsx +322 -0
  65. package/template/client/src/components/business-ui/entity-combobox/types.ts +246 -0
  66. package/template/client/src/components/business-ui/entity-combobox/use-fetch-data.tsx +81 -0
  67. package/template/client/src/components/business-ui/entity-combobox/use-infinite-scroll.tsx +99 -0
  68. package/template/client/src/components/business-ui/entity-combobox/use-popover-outside-click.tsx +32 -0
  69. package/template/client/src/components/business-ui/form/checkbox-field.tsx +83 -0
  70. package/template/client/src/components/business-ui/form/context.tsx +38 -0
  71. package/template/client/src/components/business-ui/form/field-layout.tsx +91 -0
  72. package/template/client/src/components/business-ui/form/form.tsx +54 -0
  73. package/template/client/src/components/business-ui/form/hooks/form-context.tsx +4 -0
  74. package/template/client/src/components/business-ui/form/hooks/form-utils.ts +23 -0
  75. package/template/client/src/components/business-ui/form/hooks/form.tsx +40 -0
  76. package/template/client/src/components/business-ui/form/index.tsx +4 -0
  77. package/template/client/src/components/business-ui/form/input-field.tsx +74 -0
  78. package/template/client/src/components/business-ui/form/radio-group-field.tsx +138 -0
  79. package/template/client/src/components/business-ui/form/select-field.tsx +155 -0
  80. package/template/client/src/components/business-ui/form/switch-field.tsx +66 -0
  81. package/template/client/src/components/business-ui/form/textarea-field.tsx +72 -0
  82. package/template/client/src/components/business-ui/form/types.ts +32 -0
  83. package/template/client/src/components/business-ui/tiptap-editor/README.md +324 -0
  84. package/template/client/src/components/business-ui/tiptap-editor/components/attachment-toolbar-button.tsx +74 -0
  85. package/template/client/src/components/business-ui/tiptap-editor/components/blockquote-toolbar-button.tsx +41 -0
  86. package/template/client/src/components/business-ui/tiptap-editor/components/code-block-toolbar-button.tsx +41 -0
  87. package/template/client/src/components/business-ui/tiptap-editor/components/color-highlight-toolbar-button.tsx +141 -0
  88. package/template/client/src/components/business-ui/tiptap-editor/components/heading-toolbar-button.tsx +99 -0
  89. package/template/client/src/components/business-ui/tiptap-editor/components/horizontal-rule-toolbar-button.tsx +39 -0
  90. package/template/client/src/components/business-ui/tiptap-editor/components/image-upload-toolbar-button.tsx +65 -0
  91. package/template/client/src/components/business-ui/tiptap-editor/components/link-edit-form.tsx +127 -0
  92. package/template/client/src/components/business-ui/tiptap-editor/components/link-hover-toolbar.tsx +380 -0
  93. package/template/client/src/components/business-ui/tiptap-editor/components/link-toolbar-button.tsx +96 -0
  94. package/template/client/src/components/business-ui/tiptap-editor/components/list-toolbar-button.tsx +75 -0
  95. package/template/client/src/components/business-ui/tiptap-editor/components/mark-toolbar-button.tsx +118 -0
  96. package/template/client/src/components/business-ui/tiptap-editor/components/text-align-toolbar-button.tsx +84 -0
  97. package/template/client/src/components/business-ui/tiptap-editor/components/undo-redo-toolbar-button.tsx +54 -0
  98. package/template/client/src/components/business-ui/tiptap-editor/extensions/attachment.tsx +539 -0
  99. package/template/client/src/components/business-ui/tiptap-editor/extensions/code-block-shiki.tsx +236 -0
  100. package/template/client/src/components/business-ui/tiptap-editor/extensions/complete-kit.ts +260 -0
  101. package/template/client/src/components/business-ui/tiptap-editor/extensions/image.tsx +456 -0
  102. package/template/client/src/components/business-ui/tiptap-editor/hooks/use-tiptap-editor.ts +47 -0
  103. package/template/client/src/components/business-ui/tiptap-editor/index.ts +38 -0
  104. package/template/client/src/components/business-ui/tiptap-editor/tiptap-editor-complete.tsx +112 -0
  105. package/template/client/src/components/business-ui/tiptap-editor/tiptap-editor.tsx +169 -0
  106. package/template/client/src/components/business-ui/types/user.ts +74 -0
  107. package/template/client/src/components/business-ui/user-display/index.tsx +2 -0
  108. package/template/client/src/components/business-ui/user-display/overflow-tooltip-text.tsx +90 -0
  109. package/template/client/src/components/business-ui/user-display/type.ts +27 -0
  110. package/template/client/src/components/business-ui/user-display/user-display.tsx +99 -0
  111. package/template/client/src/components/business-ui/user-display/user-with-avatar.tsx +158 -0
  112. package/template/client/src/components/business-ui/user-display/utils.ts +37 -0
  113. package/template/client/src/components/business-ui/user-profile/error-image.tsx +302 -0
  114. package/template/client/src/components/business-ui/user-profile/user-external-script.ts +73 -0
  115. package/template/client/src/components/business-ui/user-profile/user-profile.tsx +370 -0
  116. package/template/client/src/components/business-ui/user-select/index.tsx +28 -0
  117. package/template/client/src/components/business-ui/user-select/types.ts +109 -0
  118. package/template/client/src/components/business-ui/user-select/use-user-value.ts +160 -0
  119. package/template/client/src/components/business-ui/user-select/user-item.tsx +98 -0
  120. package/template/client/src/components/business-ui/user-select/user-pill.tsx +140 -0
  121. package/template/client/src/components/business-ui/user-select/user-select-tag.tsx +82 -0
  122. package/template/client/src/components/business-ui/user-select/user-select.tsx +359 -0
  123. package/template/client/src/components/business-ui/user-select/utils.tsx +188 -0
  124. package/template/client/src/components/business-ui/utils/user.ts +72 -0
  125. package/template/client/src/components/ui/README.md +134 -0
  126. package/template/client/src/components/ui/accordion.tsx +66 -0
  127. package/template/client/src/components/ui/alert-dialog.tsx +157 -0
  128. package/template/client/src/components/ui/alert.tsx +71 -0
  129. package/template/client/src/components/ui/aspect-ratio.tsx +11 -0
  130. package/template/client/src/components/ui/avatar.tsx +53 -0
  131. package/template/client/src/components/ui/badge.tsx +42 -0
  132. package/template/client/src/components/ui/breadcrumb.tsx +109 -0
  133. package/template/client/src/components/ui/button-group.tsx +83 -0
  134. package/template/client/src/components/ui/button.tsx +69 -0
  135. package/template/client/src/components/ui/calendar.tsx +213 -0
  136. package/template/client/src/components/ui/card.tsx +82 -0
  137. package/template/client/src/components/ui/carousel.tsx +241 -0
  138. package/template/client/src/components/ui/chart.tsx +357 -0
  139. package/template/client/src/components/ui/checkbox.tsx +32 -0
  140. package/template/client/src/components/ui/collapsible.tsx +33 -0
  141. package/template/client/src/components/ui/command.tsx +208 -0
  142. package/template/client/src/components/ui/context-menu.tsx +324 -0
  143. package/template/client/src/components/ui/dialog.tsx +143 -0
  144. package/template/client/src/components/ui/drawer.tsx +135 -0
  145. package/template/client/src/components/ui/dropdown-menu.tsx +329 -0
  146. package/template/client/src/components/ui/empty.tsx +104 -0
  147. package/template/client/src/components/ui/field.tsx +248 -0
  148. package/template/client/src/components/ui/form.tsx +167 -0
  149. package/template/client/src/components/ui/hover-card.tsx +44 -0
  150. package/template/client/src/components/ui/icons/file-ae-colorful-icon.tsx +21 -0
  151. package/template/client/src/components/ui/icons/file-ai-colorful-icon.tsx +36 -0
  152. package/template/client/src/components/ui/icons/file-android-colorful-icon.tsx +33 -0
  153. package/template/client/src/components/ui/icons/file-audio-colorful-icon.tsx +21 -0
  154. package/template/client/src/components/ui/icons/file-code-colorful-icon.tsx +28 -0
  155. package/template/client/src/components/ui/icons/file-csv-colorful-icon.tsx +21 -0
  156. package/template/client/src/components/ui/icons/file-eml-colorful-icon.tsx +29 -0
  157. package/template/client/src/components/ui/icons/file-ios-colorful-icon.tsx +25 -0
  158. package/template/client/src/components/ui/icons/file-keynote-colorful-icon.tsx +29 -0
  159. package/template/client/src/components/ui/icons/file-pages-colorful-icon.tsx +29 -0
  160. package/template/client/src/components/ui/icons/file-ps-colorful-icon.tsx +21 -0
  161. package/template/client/src/components/ui/icons/file-sketch-colorful-icon.tsx +21 -0
  162. package/template/client/src/components/ui/icons/file-slide-colorful-icon.tsx +21 -0
  163. package/template/client/src/components/ui/icons/file-vcf-colorful-icon.tsx +29 -0
  164. package/template/client/src/components/ui/icons/file-wiki-excel-colorful-icon.tsx +23 -0
  165. package/template/client/src/components/ui/icons/file-wiki-image-colorful-icon.tsx +27 -0
  166. package/template/client/src/components/ui/icons/file-wiki-pdf-colorful-icon.tsx +20 -0
  167. package/template/client/src/components/ui/icons/file-wiki-ppt-colorful-icon.tsx +21 -0
  168. package/template/client/src/components/ui/icons/file-wiki-text-colorful-icon.tsx +12 -0
  169. package/template/client/src/components/ui/icons/file-wiki-unknown-colorful-icon.tsx +14 -0
  170. package/template/client/src/components/ui/icons/file-wiki-video-colorful-icon.tsx +23 -0
  171. package/template/client/src/components/ui/icons/file-wiki-word-colorful-icon.tsx +38 -0
  172. package/template/client/src/components/ui/icons/file-wiki-zip-colorful-icon.tsx +21 -0
  173. package/template/client/src/components/ui/image.tsx +183 -0
  174. package/template/client/src/components/ui/input-group.tsx +166 -0
  175. package/template/client/src/components/ui/input-otp.tsx +77 -0
  176. package/template/client/src/components/ui/input.tsx +21 -0
  177. package/template/client/src/components/ui/item.tsx +193 -0
  178. package/template/client/src/components/ui/kbd.tsx +28 -0
  179. package/template/client/src/components/ui/label.tsx +24 -0
  180. package/template/client/src/components/ui/menubar.tsx +348 -0
  181. package/template/client/src/components/ui/native-select.tsx +48 -0
  182. package/template/client/src/components/ui/navigation-menu.tsx +168 -0
  183. package/template/client/src/components/ui/pagination.tsx +127 -0
  184. package/template/client/src/components/ui/popover.tsx +48 -0
  185. package/template/client/src/components/ui/progress.tsx +31 -0
  186. package/template/client/src/components/ui/radio-group.tsx +45 -0
  187. package/template/client/src/components/ui/resizable.tsx +56 -0
  188. package/template/client/src/components/ui/scroll-area.tsx +58 -0
  189. package/template/client/src/components/ui/select.tsx +243 -0
  190. package/template/client/src/components/ui/separator.tsx +28 -0
  191. package/template/client/src/components/ui/sheet.tsx +139 -0
  192. package/template/client/src/components/ui/sidebar.tsx +727 -0
  193. package/template/client/src/components/ui/skeleton.tsx +13 -0
  194. package/template/client/src/components/ui/slider.tsx +87 -0
  195. package/template/client/src/components/ui/sonner.tsx +67 -0
  196. package/template/client/src/components/ui/spinner.tsx +16 -0
  197. package/template/client/src/components/ui/streamdown.tsx +186 -0
  198. package/template/client/src/components/ui/switch.tsx +31 -0
  199. package/template/client/src/components/ui/table.tsx +116 -0
  200. package/template/client/src/components/ui/tabs.tsx +66 -0
  201. package/template/client/src/components/ui/textarea.tsx +18 -0
  202. package/template/client/src/components/ui/toggle-group.tsx +83 -0
  203. package/template/client/src/components/ui/toggle.tsx +47 -0
  204. package/template/client/src/components/ui/tooltip.tsx +61 -0
  205. package/template/client/src/hooks/use-example.ts +52 -0
  206. package/template/client/src/hooks/use-mobile.ts +19 -0
  207. package/template/client/src/index.css +7 -0
  208. package/template/client/src/index.tsx +36 -0
  209. package/template/client/src/lib/shiki.ts +92 -0
  210. package/template/client/src/lib/utils.ts +6 -0
  211. package/template/client/src/pages/ExamplePage/ExamplePage.tsx +37 -0
  212. package/template/client/src/pages/NotFound/NotFound.tsx +11 -0
  213. package/template/client/src/tailwind-theme.css +257 -0
  214. package/template/client/src/types/common.ts +34 -0
  215. package/template/client/src/types/global.d.ts +53 -0
  216. package/template/client/src/types/index.ts +4 -0
  217. package/template/client/src/typography.css +41 -0
  218. package/template/client/src/utils/img-resources/avatar-placeholders.ts +12 -0
  219. package/template/client/src/utils/img-resources/banner-placeholders.ts +29 -0
  220. package/template/client/src/utils/img-resources/cover-placeholders.ts +47 -0
  221. package/template/components.json +21 -0
  222. package/template/eslint.config.js +50 -0
  223. package/template/nest-cli.json +25 -0
  224. package/template/package-lock.json +29195 -0
  225. package/template/package.json +202 -0
  226. package/template/postcss.config.js +10 -0
  227. package/template/scripts/build.sh +120 -0
  228. package/template/scripts/dev.js +295 -0
  229. package/template/scripts/dev.sh +2 -0
  230. package/template/scripts/lint.js +150 -0
  231. package/template/scripts/prune-smart.js +281 -0
  232. package/template/scripts/run.sh +5 -0
  233. package/template/server/app.module.ts +27 -0
  234. package/template/server/common/constants/api_response_code.ts +54 -0
  235. package/template/server/common/filters/exception.filter.ts +78 -0
  236. package/template/server/common/interfaces/api_response.interface.ts +21 -0
  237. package/template/server/common/interfaces/exception.interface.ts +19 -0
  238. package/template/server/database/schema.ts +90 -0
  239. package/template/server/main.ts +31 -0
  240. package/template/server/modules/hello/hello.controller.ts +16 -0
  241. package/template/server/modules/hello/hello.module.ts +10 -0
  242. package/template/server/modules/hello/hello.service.ts +28 -0
  243. package/template/server/modules/view/view.controller.ts +17 -0
  244. package/template/server/modules/view/view.module.ts +8 -0
  245. package/template/shared/api.interface.ts +1 -0
  246. package/template/tailwind.config.ts +9 -0
  247. package/template/tsconfig.app.json +23 -0
  248. package/template/tsconfig.json +8 -0
  249. package/template/tsconfig.node.json +16 -0
  250. package/template/vite.config.ts +10 -0
@@ -0,0 +1,168 @@
1
+ 'use client';
2
+
3
+ import { useMemo } from 'react';
4
+
5
+ import { searchDepartments } from '@client/src/components/business-ui/api/departments/service';
6
+ import { DepartmentItem } from '@client/src/components/business-ui/department-select/department-item';
7
+ import { DepartmentSelectTag } from '@client/src/components/business-ui/department-select/department-select-tag';
8
+ import type {
9
+ Department,
10
+ DepartmentSelectProps,
11
+ } from '@client/src/components/business-ui/department-select/types';
12
+ import { departmentInfoToDepartment } from '@client/src/components/business-ui/department-select/utils';
13
+ import { BaseCombobox } from '@client/src/components/business-ui/entity-combobox/base-combobox';
14
+ import { useEntityComboboxContext } from '@client/src/components/business-ui/entity-combobox/context';
15
+
16
+ function createDepartmentsFetcher(pageSize = 100) {
17
+ return async (search: string) => {
18
+ const response = await searchDepartments({ query: search, pageSize });
19
+ const departmentList = response?.data?.departmentList || [];
20
+
21
+ return {
22
+ items: departmentList.map(departmentInfoToDepartment),
23
+ };
24
+ };
25
+ }
26
+
27
+ // 内部包装组件用于获取 context 中的 size
28
+ const DepartmentItemWrapper = ({
29
+ departmentValue,
30
+ isSelected,
31
+ className,
32
+ disabled,
33
+ }: {
34
+ departmentValue: Department;
35
+ isSelected: boolean;
36
+ className?: string;
37
+ disabled?: boolean;
38
+ }) => {
39
+ const { size, searchValue } = useEntityComboboxContext();
40
+ return (
41
+ <DepartmentItem
42
+ departmentValue={departmentValue}
43
+ isSelected={isSelected}
44
+ className={className}
45
+ size={size}
46
+ searchKeyword={searchValue}
47
+ disabled={disabled}
48
+ />
49
+ );
50
+ };
51
+
52
+ const DepartmentSelectTagWrapper = ({
53
+ departmentValue,
54
+ onClose,
55
+ className,
56
+ disabled,
57
+ }: {
58
+ departmentValue: Department;
59
+ onClose: (value: Department, e: React.MouseEvent) => void;
60
+ className?: string;
61
+ disabled?: boolean;
62
+ }) => {
63
+ const { size } = useEntityComboboxContext();
64
+ return (
65
+ <DepartmentSelectTag
66
+ departmentValue={departmentValue}
67
+ onClose={onClose}
68
+ className={className}
69
+ size={size}
70
+ disabled={disabled}
71
+ />
72
+ );
73
+ };
74
+
75
+ export const DepartmentSelect = (props: DepartmentSelectProps) => {
76
+ const {
77
+ size = 'medium',
78
+ triggerType = 'button',
79
+ renderTrigger,
80
+ multiple,
81
+ value,
82
+ defaultValue,
83
+ onChange,
84
+ defaultOpen,
85
+ disabled,
86
+ autoFocus,
87
+ required,
88
+ name,
89
+ className,
90
+ classNames,
91
+ placeholder = '请选择部门',
92
+ emptyText = '没有匹配结果,换个关键词试试吧',
93
+ tagClosable,
94
+ maxTagCount,
95
+ onSelect,
96
+ onDeselect,
97
+ onClear,
98
+ onFocus,
99
+ onBlur,
100
+ slotProps,
101
+ getOptionDisabled,
102
+ } = props;
103
+
104
+ const fetchFn = useMemo(() => createDepartmentsFetcher(), []);
105
+
106
+ return (
107
+ <BaseCombobox
108
+ classNames={classNames}
109
+ fetchFn={fetchFn}
110
+ size={size}
111
+ triggerType={triggerType}
112
+ renderTrigger={renderTrigger}
113
+ multiple={multiple}
114
+ value={value}
115
+ defaultValue={defaultValue}
116
+ onChange={onChange}
117
+ defaultOpen={defaultOpen}
118
+ disabled={disabled}
119
+ autoFocus={autoFocus}
120
+ required={required}
121
+ name={name}
122
+ className={className}
123
+ placeholder={placeholder}
124
+ emptyText={emptyText}
125
+ tagClosable={tagClosable}
126
+ maxTagCount={maxTagCount}
127
+ onSelect={onSelect}
128
+ onDeselect={onDeselect}
129
+ onClear={onClear}
130
+ onFocus={onFocus}
131
+ onBlur={onBlur}
132
+ showSearch
133
+ searchPlaceholder=""
134
+ debounce={300}
135
+ getItemValue={(departmentValue) => departmentValue}
136
+ getItemLabel={(departmentValue) => departmentValue.name}
137
+ getOptionDisabled={getOptionDisabled}
138
+ renderItem={(
139
+ departmentValue,
140
+ isSelected,
141
+ itemClassName,
142
+ itemDisabled,
143
+ ) => (
144
+ <DepartmentItemWrapper
145
+ key={departmentValue.id}
146
+ departmentValue={departmentValue}
147
+ isSelected={isSelected}
148
+ className={itemClassName}
149
+ disabled={itemDisabled}
150
+ />
151
+ )}
152
+ renderTag={(departmentValue, onClose, tagDisabled) => (
153
+ <DepartmentSelectTagWrapper
154
+ key={departmentValue.id}
155
+ departmentValue={departmentValue}
156
+ onClose={onClose}
157
+ disabled={tagDisabled}
158
+ />
159
+ )}
160
+ slotProps={slotProps}
161
+ />
162
+ );
163
+ };
164
+
165
+ export type {
166
+ DepartmentInfo,
167
+ Department,
168
+ } from '@client/src/components/business-ui/department-select/types';
@@ -0,0 +1,22 @@
1
+ 'use client';
2
+
3
+ import type { SVGProps } from 'react';
4
+
5
+ export const IconDepartment = (props: SVGProps<SVGSVGElement>) => {
6
+ return (
7
+ <svg
8
+ width="20"
9
+ height="20"
10
+ viewBox="0 0 20 20"
11
+ fill="none"
12
+ xmlns="http://www.w3.org/2000/svg"
13
+ {...props}
14
+ >
15
+ <rect width="20" height="20" rx="10" fill="#1456F0" />
16
+ <path
17
+ d="M7.6267 11.8751H12.3734C12.4775 11.8126 12.5896 11.7626 12.7084 11.7272L12.7084 10.833C12.7084 10.7225 12.6645 10.6165 12.5863 10.5384C12.5082 10.4602 12.4022 10.4163 12.2917 10.4163H7.70838C7.59787 10.4163 7.49189 10.4602 7.41375 10.5384C7.33561 10.6165 7.29171 10.7225 7.29171 10.833L7.2917 11.7272C7.41045 11.7626 7.52274 11.8126 7.6267 11.8751ZM6.45837 11.7272L6.45838 10.833C6.45838 10.5015 6.59008 10.1835 6.8245 9.94911C7.05892 9.71469 7.37686 9.58299 7.70838 9.58299H9.58338V8.69716C9.1906 8.59574 8.8483 8.35456 8.62063 8.01882C8.39296 7.68308 8.29555 7.27583 8.34666 6.87341C8.39778 6.47099 8.5939 6.10102 8.89828 5.83286C9.20266 5.56469 9.59439 5.41675 10 5.41675C10.4057 5.41675 10.7974 5.56469 11.1018 5.83286C11.4062 6.10102 11.6023 6.47099 11.6534 6.87341C11.7045 7.27583 11.6071 7.68308 11.3795 8.01882C11.1518 8.35456 10.8095 8.59574 10.4167 8.69716V9.58299H12.2917C12.6232 9.58299 12.9412 9.71469 13.1756 9.94911C13.41 10.1835 13.5417 10.5015 13.5417 10.833L13.5417 11.7272C13.8623 11.8227 14.1403 12.0256 14.329 12.3018C14.5178 12.5779 14.6059 12.9106 14.5786 13.244C14.5512 13.5774 14.41 13.8913 14.1788 14.133C13.9475 14.3747 13.6402 14.5295 13.3083 14.5715C12.9764 14.6135 12.6402 14.5402 12.356 14.3637C12.0718 14.1873 11.8568 13.9185 11.7472 13.6025C11.6377 13.2864 11.6401 12.9423 11.7541 12.6278C11.8681 12.3133 12.0867 12.0475 12.3734 11.8751H7.6267C7.91336 12.0475 8.13201 12.3133 8.24601 12.6278C8.36 12.9423 8.36241 13.2864 8.25282 13.6025C8.14324 13.9185 7.92832 14.1873 7.64411 14.3637C7.35989 14.5402 7.02366 14.6135 6.69179 14.5715C6.35992 14.5295 6.05258 14.3747 5.8213 14.133C5.59003 13.8913 5.44887 13.5774 5.42151 13.244C5.39415 12.9106 5.48225 12.5779 5.67102 12.3018C5.8598 12.0256 6.13779 11.8227 6.45837 11.7272ZM10 7.91632C10.2211 7.91632 10.433 7.82853 10.5893 7.67225C10.7456 7.51597 10.8334 7.304 10.8334 7.08299C10.8334 6.86198 10.7456 6.65002 10.5893 6.49374C10.433 6.33746 10.2211 6.24966 10 6.24966C9.77903 6.24966 9.56707 6.33746 9.41079 6.49374C9.25451 6.65002 9.16671 6.86198 9.16671 7.08299C9.16671 7.304 9.25451 7.51597 9.41079 7.67225C9.56707 7.82853 9.77903 7.91632 10 7.91632ZM13.125 13.7501C13.2908 13.7501 13.4498 13.6842 13.567 13.567C13.6842 13.4498 13.75 13.2908 13.75 13.1251C13.75 12.9593 13.6842 12.8004 13.567 12.6831C13.4498 12.5659 13.2908 12.5001 13.125 12.5001C12.9593 12.5001 12.8003 12.5659 12.6831 12.6831C12.5659 12.8004 12.5 12.9593 12.5 13.1251C12.5 13.2908 12.5659 13.4498 12.6831 13.567C12.8003 13.6842 12.9593 13.7501 13.125 13.7501ZM6.87504 13.7501C7.0408 13.7501 7.19977 13.6842 7.31698 13.567C7.43419 13.4498 7.50004 13.2908 7.50004 13.1251C7.50004 12.9593 7.43419 12.8004 7.31698 12.6831C7.19977 12.5659 7.0408 12.5001 6.87504 12.5001C6.70928 12.5001 6.5503 12.5659 6.43309 12.6831C6.31588 12.8004 6.25004 12.9593 6.25004 13.1251C6.25004 13.2908 6.31588 13.4498 6.43309 13.567C6.5503 13.6842 6.70928 13.7501 6.87504 13.7501Z"
18
+ fill="white"
19
+ />
20
+ </svg>
21
+ );
22
+ };
@@ -0,0 +1,7 @@
1
+ export { DepartmentSelect } from '@client/src/components/business-ui/department-select/department-select';
2
+ export { DepartmentSelectField } from '@client/src/components/business-ui/department-select/department-select-field';
3
+ export type {
4
+ DepartmentInfo as Department,
5
+ DepartmentSelectProps,
6
+ Department as DepartmentValue,
7
+ } from '@client/src/components/business-ui/department-select/types';
@@ -0,0 +1,15 @@
1
+ import type {
2
+ BaseEntitySelectProps,
3
+ ItemValue,
4
+ } from '@client/src/components/business-ui/entity-combobox/shared-types';
5
+ import type { I18nText } from '@client/src/components/business-ui/entity-combobox/types';
6
+
7
+ export type DepartmentInfo = {
8
+ departmentID: string;
9
+ larkDepartmentID: string;
10
+ name: I18nText;
11
+ };
12
+
13
+ export type Department = ItemValue<DepartmentInfo>;
14
+
15
+ export type DepartmentSelectProps = BaseEntitySelectProps<Department>;
@@ -0,0 +1,17 @@
1
+ import type { DepartmentInfo } from '@lark-apaas/client-toolkit/tools/services';
2
+
3
+ import type { Department } from './types';
4
+
5
+ export function getDepartmentDisplayName(name: DepartmentInfo['name']): string {
6
+ return name?.zh_cn || name?.en_us || '';
7
+ }
8
+
9
+ export function departmentInfoToDepartment(
10
+ departmentInfo: DepartmentInfo,
11
+ ): Department {
12
+ return {
13
+ id: departmentInfo.departmentID,
14
+ name: getDepartmentDisplayName(departmentInfo.name),
15
+ raw: departmentInfo,
16
+ };
17
+ }
@@ -0,0 +1,35 @@
1
+ 'use client';
2
+
3
+ import { type PopoverSlotProps } from '@client/src/components/business-ui/entity-combobox/shared-types';
4
+ import { cn } from '@/lib/utils';
5
+ import { Command } from '@client/src/components/ui/command';
6
+ import { PopoverContent } from '@client/src/components/ui/popover';
7
+
8
+ export const BaseComboboxContent = ({
9
+ children,
10
+ className,
11
+ side,
12
+ align,
13
+ sideOffset,
14
+ popoverProps,
15
+ }: {
16
+ children: React.ReactNode;
17
+ className?: string;
18
+ side?: 'top' | 'bottom' | 'left' | 'right';
19
+ align?: 'start' | 'center' | 'end';
20
+ sideOffset?: number;
21
+ popoverProps?: PopoverSlotProps['popover'];
22
+ }) => {
23
+ return (
24
+ <PopoverContent
25
+ className={cn('w-[270px] p-0', className)}
26
+ side={side}
27
+ align={align}
28
+ sideOffset={sideOffset}
29
+ forceMount
30
+ {...popoverProps}
31
+ >
32
+ <Command shouldFilter={false}>{children}</Command>
33
+ </PopoverContent>
34
+ );
35
+ };
@@ -0,0 +1,28 @@
1
+ 'use client';
2
+
3
+ import { CommandEmpty } from 'cmdk';
4
+
5
+ import { useEntityComboboxContext } from '@client/src/components/business-ui/entity-combobox/context';
6
+ import { cn } from '@/lib/utils';
7
+
8
+ export const BaseComboboxEmpty = ({
9
+ text = '没有匹配结果,换个关键词试试吧',
10
+ className,
11
+ }: {
12
+ text?: string;
13
+ className?: string;
14
+ }) => {
15
+ const { size } = useEntityComboboxContext();
16
+
17
+ return (
18
+ <CommandEmpty
19
+ className={cn(
20
+ 'px-2 text-sm leading-normal text-muted-foreground',
21
+ size === 'xs' ? 'pt-3' : 'pt-4',
22
+ className,
23
+ )}
24
+ >
25
+ {text}
26
+ </CommandEmpty>
27
+ );
28
+ };
@@ -0,0 +1,35 @@
1
+ 'use client';
2
+
3
+ import { cn } from '@/lib/utils';
4
+ import { Button } from '@client/src/components/ui/button';
5
+
6
+ export type BaseComboboxErrorProps = {
7
+ onRetry?: () => void;
8
+ className?: string;
9
+ };
10
+
11
+ export const BaseComboboxError = ({
12
+ onRetry,
13
+ className,
14
+ }: BaseComboboxErrorProps) => {
15
+ return (
16
+ <div
17
+ className={cn(
18
+ 'flex gap-2 px-2 pt-4 text-sm leading-normal text-muted-foreground',
19
+ className,
20
+ )}
21
+ >
22
+ <div className="max-w-full truncate text-muted-foreground">
23
+ 加载失败,请检查网络后重试
24
+ </div>
25
+ <Button
26
+ variant="ghost"
27
+ size="sm"
28
+ onClick={onRetry}
29
+ className="h-auto px-0 text-primary"
30
+ >
31
+ 刷新
32
+ </Button>
33
+ </div>
34
+ );
35
+ };
@@ -0,0 +1,55 @@
1
+ 'use client';
2
+
3
+ import { Check } from 'lucide-react';
4
+
5
+ import { useEntityComboboxContext } from '@client/src/components/business-ui/entity-combobox/context';
6
+ import type { ItemValue } from '@client/src/components/business-ui/entity-combobox/types';
7
+ import { cn } from '@/lib/utils';
8
+ import { CommandItem } from '@client/src/components/ui/command';
9
+
10
+ export type BaseComboboxItemProps<T = unknown> = {
11
+ item: T;
12
+ getItemValue: (item: T) => ItemValue;
13
+ isSelected: boolean;
14
+ children: React.ReactNode;
15
+ onSelect?: () => void;
16
+ className?: string;
17
+ /**
18
+ * 是否禁用该选项
19
+ * 禁用时不能改变选中状态
20
+ */
21
+ disabled?: boolean;
22
+ };
23
+
24
+ export function BaseComboboxItem<T>({
25
+ item,
26
+ getItemValue,
27
+ isSelected,
28
+ children,
29
+ onSelect,
30
+ className,
31
+ disabled,
32
+ }: BaseComboboxItemProps<T>) {
33
+ const { handleSelect } = useEntityComboboxContext();
34
+ const itemValue = getItemValue(item);
35
+
36
+ return (
37
+ <CommandItem
38
+ value={itemValue.id}
39
+ disabled={disabled}
40
+ onSelect={() => {
41
+ if (disabled) return;
42
+ handleSelect(itemValue);
43
+ onSelect?.();
44
+ }}
45
+ className={cn(
46
+ 'flex items-center gap-2',
47
+ disabled && 'cursor-not-allowed opacity-50',
48
+ className,
49
+ )}
50
+ >
51
+ {children}
52
+ {isSelected && <Check className="ml-auto h-4 w-4 text-primary" />}
53
+ </CommandItem>
54
+ );
55
+ }
@@ -0,0 +1,174 @@
1
+ 'use client';
2
+
3
+ import React, { useCallback } from 'react';
4
+
5
+ import { BaseComboboxEmpty } from '@client/src/components/business-ui/entity-combobox/base-combobox-empty';
6
+ import { BaseComboboxError } from '@client/src/components/business-ui/entity-combobox/base-combobox-error';
7
+ import { BaseComboboxLoading } from '@client/src/components/business-ui/entity-combobox/base-combobox-loading';
8
+ import { useEntityComboboxContext } from '@client/src/components/business-ui/entity-combobox/context';
9
+ import {
10
+ skeletonAvatarVariants,
11
+ skeletonItemVariants,
12
+ } from '@client/src/components/business-ui/entity-combobox/size-variants';
13
+ import type { ItemValue } from '@client/src/components/business-ui/entity-combobox/types';
14
+ import { CommandGroup, CommandList } from '@client/src/components/ui/command';
15
+ import { ScrollArea } from '@client/src/components/ui/scroll-area';
16
+ import { Skeleton } from '@client/src/components/ui/skeleton';
17
+
18
+ export type BaseComboboxListProps<T = unknown> = {
19
+ renderItem: (
20
+ item: T,
21
+ isSelected: boolean,
22
+ itemClassName?: string,
23
+ disabled?: boolean,
24
+ ) => React.ReactNode;
25
+ getItemValue: (item: T) => ItemValue;
26
+ /**
27
+ * 判断选项是否禁用
28
+ */
29
+ getOptionDisabled?: (value: ItemValue) => boolean;
30
+ emptyText?: string;
31
+ loadingText?: string;
32
+ classNames?: {
33
+ list?: string;
34
+ listItem?: string;
35
+ empty?: string;
36
+ loading?: string;
37
+ error?: string;
38
+ };
39
+ };
40
+
41
+ export function BaseComboboxList<T>({
42
+ renderItem,
43
+ getItemValue,
44
+ getOptionDisabled,
45
+ emptyText,
46
+ loadingText,
47
+ classNames = {},
48
+ }: BaseComboboxListProps<T>) {
49
+ const {
50
+ data,
51
+ isFetching,
52
+ selectedValue,
53
+ debouncedSearch,
54
+ isError,
55
+ refetch,
56
+ isSuccess,
57
+ size,
58
+ } = useEntityComboboxContext();
59
+
60
+ const isItemSelected = useCallback(
61
+ (item: T) => {
62
+ const itemValue = getItemValue(item);
63
+ if (Array.isArray(selectedValue)) {
64
+ return selectedValue.some((v) => v.id === itemValue.id);
65
+ }
66
+ return selectedValue?.id === itemValue.id;
67
+ },
68
+ [getItemValue, selectedValue],
69
+ );
70
+
71
+ const isItemDisabled = useCallback(
72
+ (item: T) => {
73
+ if (!getOptionDisabled) return false;
74
+ const itemValue = getItemValue(item);
75
+ return getOptionDisabled(itemValue);
76
+ },
77
+ [getItemValue, getOptionDisabled],
78
+ );
79
+
80
+ const hasSearchQuery = debouncedSearch.trim() !== '';
81
+
82
+ // 1. 没有搜索词时显示 placeholder
83
+ const shouldShowPlaceHolder = !hasSearchQuery;
84
+ // 2. 搜索出错时显示错误状态
85
+ const shouldShowError = isError && hasSearchQuery;
86
+ // 3. 首次搜索无已有数据时显示骨架屏
87
+ const shouldShowSkeleton =
88
+ isFetching && (data === undefined || data?.length === 0) && hasSearchQuery;
89
+ // 4. 有已有内容触发搜索时显示蒙层 loading
90
+ const shouldShowSpinner =
91
+ isFetching && data !== undefined && data.length > 0 && hasSearchQuery;
92
+ // 5. 搜索完成结果为空时显示空状态
93
+ const shouldShowEmpty =
94
+ !isFetching && isSuccess && data?.length === 0 && hasSearchQuery;
95
+
96
+ const content = (() => {
97
+ if (shouldShowPlaceHolder) {
98
+ return (
99
+ <BaseComboboxEmpty text={'请输入关键词'} className={classNames.empty} />
100
+ );
101
+ }
102
+
103
+ if (shouldShowError) {
104
+ return (
105
+ <BaseComboboxError onRetry={refetch} className={classNames.error} />
106
+ );
107
+ }
108
+
109
+ if (shouldShowSpinner) {
110
+ return (
111
+ <>
112
+ <div className="absolute inset-0 z-50 flex items-center justify-center bg-popover opacity-40">
113
+ <BaseComboboxLoading
114
+ text={loadingText}
115
+ className={classNames.loading}
116
+ />
117
+ </div>
118
+ {(data as T[]).map((item) =>
119
+ renderItem(
120
+ item,
121
+ isItemSelected(item),
122
+ classNames.listItem,
123
+ isItemDisabled(item),
124
+ ),
125
+ )}
126
+ </>
127
+ );
128
+ }
129
+
130
+ if (shouldShowSkeleton) {
131
+ return (
132
+ <div className="absolute inset-0 overflow-hidden! bg-background p-1 py-1.5">
133
+ {Array.from({ length: 5 }).map((_, index) => (
134
+ <div key={index} className={skeletonItemVariants({ size })}>
135
+ <Skeleton className={skeletonAvatarVariants({ size })} />
136
+ <div className="flex-1 space-y-1">
137
+ <Skeleton className="h-4 w-full" />
138
+ <Skeleton className="h-3 w-3/4" />
139
+ </div>
140
+ </div>
141
+ ))}
142
+ </div>
143
+ );
144
+ }
145
+
146
+ if (shouldShowEmpty) {
147
+ return (
148
+ <BaseComboboxEmpty text={emptyText} className={classNames.empty} />
149
+ );
150
+ }
151
+
152
+ return ((data as T[]) ?? []).map((item) =>
153
+ renderItem(
154
+ item,
155
+ isItemSelected(item),
156
+ classNames.listItem,
157
+ isItemDisabled(item),
158
+ ),
159
+ );
160
+ })();
161
+
162
+ return (
163
+ <CommandList className={classNames.list} autoFocus>
164
+ <div className="relative">
165
+ <ScrollArea
166
+ className={`max-h-[300px] [&>[data-radix-scroll-area-viewport]>div]:block! ${size === 'xs' ? 'min-h-[54px]' : 'min-h-[58px]'}`}
167
+ onWheel={(e: React.WheelEvent<HTMLDivElement>) => e.stopPropagation()}
168
+ >
169
+ <CommandGroup className="w-full">{content}</CommandGroup>
170
+ </ScrollArea>
171
+ </div>
172
+ </CommandList>
173
+ );
174
+ }
@@ -0,0 +1,19 @@
1
+ 'use client';
2
+
3
+ import { cn } from '@/lib/utils';
4
+ import { Spinner } from '@client/src/components/ui/spinner';
5
+
6
+ export const BaseComboboxLoading = ({
7
+ className,
8
+ }: {
9
+ text?: string;
10
+ className?: string;
11
+ }) => {
12
+ return (
13
+ <div
14
+ className={cn('flex items-center justify-center gap-2 py-6', className)}
15
+ >
16
+ <Spinner className="size-4.5 text-primary" />
17
+ </div>
18
+ );
19
+ };
@@ -0,0 +1,31 @@
1
+ 'use client';
2
+
3
+ import { useEntityComboboxContext } from '@client/src/components/business-ui/entity-combobox/context';
4
+ import { searchInputVariants } from '@client/src/components/business-ui/entity-combobox/size-variants';
5
+ import { cn } from '@/lib/utils';
6
+ import { CommandInput } from '@client/src/components/ui/command';
7
+
8
+ export type BaseComboboxSearchProps = {
9
+ placeholder?: string;
10
+ className?: string;
11
+ ref?: React.Ref<HTMLInputElement>;
12
+ };
13
+
14
+ export const BaseComboboxSearch = ({
15
+ placeholder = '搜索...',
16
+ className,
17
+ ref,
18
+ }: BaseComboboxSearchProps) => {
19
+ const { searchValue, setSearchValue, size } = useEntityComboboxContext();
20
+
21
+ return (
22
+ <CommandInput
23
+ ref={ref}
24
+ autoFocus
25
+ value={searchValue}
26
+ onValueChange={setSearchValue}
27
+ placeholder={placeholder}
28
+ className={cn(searchInputVariants({ size }), className)}
29
+ />
30
+ );
31
+ };