@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,54 @@
1
+ import React from 'react';
2
+ import type { AppFieldExtendedReactFormApi } from '@tanstack/react-form';
3
+
4
+ import { FormProvider } from '@client/src/components/business-ui/form/context';
5
+ import { FieldGroup } from '@client/src/components/ui/field';
6
+
7
+ interface FormProps {
8
+ children: React.ReactNode;
9
+ form: AppFieldExtendedReactFormApi<
10
+ any,
11
+ any,
12
+ any,
13
+ any,
14
+ any,
15
+ any,
16
+ any,
17
+ any,
18
+ any,
19
+ any,
20
+ any,
21
+ any,
22
+ any,
23
+ any
24
+ >;
25
+ className?: string;
26
+ style?: React.CSSProperties;
27
+ layout?: 'vertical' | 'responsive' | 'horizontal';
28
+ }
29
+
30
+ const ForceForm: React.FC<FormProps> = (props) => {
31
+ const { children, form, className, style, layout = 'vertical' } = props;
32
+ return (
33
+ <FormProvider layout={layout}>
34
+ <form
35
+ data-testid="tanstack-form"
36
+ onSubmit={(e) => {
37
+ e.preventDefault();
38
+ e.stopPropagation();
39
+ form.handleSubmit();
40
+ }}
41
+ >
42
+ <form.AppForm>
43
+ <FieldGroup className={className} style={style}>
44
+ {children}
45
+ </FieldGroup>
46
+ </form.AppForm>
47
+ </form>
48
+ </FormProvider>
49
+ );
50
+ };
51
+
52
+ const Form = ForceForm;
53
+
54
+ export { Form };
@@ -0,0 +1,4 @@
1
+ import { createFormHookContexts } from '@tanstack/react-form';
2
+
3
+ export const { fieldContext, formContext, useFieldContext, useFormContext } =
4
+ createFormHookContexts();
@@ -0,0 +1,23 @@
1
+ import {
2
+ useFieldContext,
3
+ useFormContext,
4
+ } from '@client/src/components/business-ui/form/hooks/form-context';
5
+
6
+ export const useFieldValidation = <T>() => {
7
+ const field = useFieldContext<T>();
8
+ const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid;
9
+ const errorMessage = isInvalid
10
+ ? (field.state.meta.errors[0].message ?? field.state.meta.errors[0])
11
+ : '';
12
+ return {
13
+ isInvalid,
14
+ errorMessage,
15
+ };
16
+ };
17
+
18
+ export const useFieldName = () => {
19
+ const form = useFormContext();
20
+ const field = useFieldContext();
21
+ const name = `${form.formId}-${field.name}`;
22
+ return name;
23
+ };
@@ -0,0 +1,40 @@
1
+ import { createFormHook } from '@tanstack/react-form';
2
+
3
+ import { CheckboxField } from '@client/src/components/business-ui/form/checkbox-field';
4
+ import {
5
+ fieldContext,
6
+ formContext,
7
+ } from '@client/src/components/business-ui/form/hooks/form-context';
8
+ import { InputField } from '@client/src/components/business-ui/form/input-field';
9
+ import { RadioGroupField } from '@client/src/components/business-ui/form/radio-group-field';
10
+ import { SelectField } from '@client/src/components/business-ui/form/select-field';
11
+ import { SwitchField } from '@client/src/components/business-ui/form/switch-field';
12
+ import { TextareaField } from '@client/src/components/business-ui/form/textarea-field';
13
+
14
+ interface CreateFormHookOptions {
15
+ fieldComponents: Record<string, React.FC>;
16
+ formComponents: Record<string, React.FC>;
17
+ }
18
+
19
+ const COMMON_FORM_COMPONENTS = {
20
+ Input: InputField,
21
+ Switch: SwitchField,
22
+ RadioGroup: RadioGroupField,
23
+ Checkbox: CheckboxField,
24
+ Select: SelectField,
25
+ Textarea: TextareaField,
26
+ };
27
+
28
+ export function createAppFormHook(options?: CreateFormHookOptions) {
29
+ const { fieldComponents = {}, formComponents = {} } = options ?? {};
30
+
31
+ return createFormHook({
32
+ fieldComponents: {
33
+ ...COMMON_FORM_COMPONENTS,
34
+ ...fieldComponents,
35
+ },
36
+ formComponents: formComponents,
37
+ fieldContext,
38
+ formContext,
39
+ });
40
+ }
@@ -0,0 +1,4 @@
1
+ export { useFormGlobalProps } from '@client/src/components/business-ui/form/context';
2
+ export { FieldLayout } from '@client/src/components/business-ui/form/field-layout';
3
+ export { Form } from '@client/src/components/business-ui/form/form';
4
+ export { createAppFormHook } from '@client/src/components/business-ui/form/hooks/form';
@@ -0,0 +1,74 @@
1
+ import { useFormGlobalProps } from '@client/src/components/business-ui/form/context';
2
+ import { FieldLayout } from '@client/src/components/business-ui/form/field-layout';
3
+ import { useFieldContext } from '@client/src/components/business-ui/form/hooks/form-context';
4
+ import {
5
+ useFieldName,
6
+ useFieldValidation,
7
+ } from '@client/src/components/business-ui/form/hooks/form-utils';
8
+ import type {
9
+ CommonFieldProps,
10
+ FieldLayoutClassNames,
11
+ FieldLayoutStyles,
12
+ } from '@client/src/components/business-ui/form/types';
13
+ import { Input } from '@client/src/components/ui/input';
14
+
15
+ type InputFieldProps = CommonFieldProps &
16
+ React.ComponentProps<typeof Input> & {
17
+ classNames?: Partial<
18
+ FieldLayoutClassNames & {
19
+ input: string;
20
+ }
21
+ >;
22
+ styles?: Partial<
23
+ FieldLayoutStyles & {
24
+ input: React.CSSProperties;
25
+ }
26
+ >;
27
+ };
28
+
29
+ export function InputField({
30
+ label,
31
+ description,
32
+ disabled,
33
+ required,
34
+ layout = 'vertical',
35
+ classNames,
36
+ readOnly,
37
+ styles,
38
+ ...rest
39
+ }: InputFieldProps) {
40
+ const field = useFieldContext<string>();
41
+ const { isInvalid, errorMessage } = useFieldValidation();
42
+ const { layout: formLayout } = useFormGlobalProps();
43
+ const name = useFieldName();
44
+ const { input: inputClassName, ...fieldLayoutClassNames } = classNames ?? {};
45
+ const { input: inputStyle, ...fieldLayoutStyles } = styles ?? {};
46
+
47
+ return (
48
+ <FieldLayout
49
+ label={label}
50
+ layout={layout ?? formLayout ?? 'vertical'}
51
+ description={description}
52
+ isInvalid={isInvalid}
53
+ required={required ?? false}
54
+ classNames={fieldLayoutClassNames ?? {}}
55
+ styles={fieldLayoutStyles ?? {}}
56
+ errorMessage={errorMessage}
57
+ name={name}
58
+ >
59
+ <Input
60
+ {...rest}
61
+ className={inputClassName ?? ''}
62
+ style={inputStyle ?? {}}
63
+ disabled={disabled ?? false}
64
+ readOnly={readOnly ?? false}
65
+ id={name}
66
+ name={name}
67
+ value={field.state.value}
68
+ onBlur={field.handleBlur}
69
+ onChange={(e) => field.handleChange(e.target.value)}
70
+ aria-invalid={isInvalid}
71
+ />
72
+ </FieldLayout>
73
+ );
74
+ }
@@ -0,0 +1,138 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ import { useFormGlobalProps } from '@client/src/components/business-ui/form/context';
4
+ import { FieldLayout } from '@client/src/components/business-ui/form/field-layout';
5
+ import { useFieldContext } from '@client/src/components/business-ui/form/hooks/form-context';
6
+ import {
7
+ useFieldName,
8
+ useFieldValidation,
9
+ } from '@client/src/components/business-ui/form/hooks/form-utils';
10
+ import type {
11
+ CommonFieldProps,
12
+ FieldLayoutClassNames,
13
+ FieldLayoutStyles,
14
+ } from '@client/src/components/business-ui/form/types';
15
+ import { cn } from '@/lib/utils';
16
+ import { Label } from '@client/src/components/ui/label';
17
+ import {
18
+ RadioGroup,
19
+ RadioGroupItem,
20
+ } from '@client/src/components/ui/radio-group';
21
+
22
+ type RadioOption = {
23
+ label: string;
24
+ value: string;
25
+ disabled?: boolean;
26
+ description?: string;
27
+ };
28
+
29
+ type RadioGroupFieldProps = CommonFieldProps & {
30
+ children?: ReactNode;
31
+ options?: RadioOption[];
32
+ orientation?: 'horizontal' | 'vertical';
33
+ classNames?: Partial<
34
+ FieldLayoutClassNames & {
35
+ radioGroup: string;
36
+ }
37
+ >;
38
+ styles?: Partial<
39
+ FieldLayoutStyles & {
40
+ radioGroup: React.CSSProperties;
41
+ }
42
+ >;
43
+ };
44
+
45
+ function renderRadioOptions(
46
+ options: RadioOption[],
47
+ fieldName: string,
48
+ disabled: boolean,
49
+ ): ReactNode {
50
+ return options.map((option, index) => {
51
+ const optionId = `${fieldName}-${index}`;
52
+
53
+ return (
54
+ <div key={option.value} className="flex items-center space-x-2">
55
+ <RadioGroupItem
56
+ id={optionId}
57
+ value={option.value}
58
+ disabled={disabled || option.disabled}
59
+ />
60
+ <Label
61
+ htmlFor={optionId}
62
+ className={cn(
63
+ `${option.disabled ? 'cursor-not-allowed opacity-70' : 'cursor-pointer'}`,
64
+ 'font-normal',
65
+ )}
66
+ >
67
+ {option.label}
68
+ </Label>
69
+ {option.description && (
70
+ <span className="text-sm text-muted-foreground">
71
+ {option.description}
72
+ </span>
73
+ )}
74
+ </div>
75
+ );
76
+ });
77
+ }
78
+
79
+ export function RadioGroupField(props: RadioGroupFieldProps) {
80
+ const {
81
+ children,
82
+ description,
83
+ label,
84
+ options,
85
+ orientation = 'vertical',
86
+ required,
87
+ layout,
88
+ disabled,
89
+ classNames,
90
+ styles,
91
+ } = props;
92
+ const field = useFieldContext<string>();
93
+ const { isInvalid, errorMessage } = useFieldValidation();
94
+ const { layout: formLayout } = useFormGlobalProps();
95
+ const name = useFieldName();
96
+ const { radioGroup: radioGroupClassName, ...fieldLayoutClassNames } =
97
+ classNames ?? {};
98
+ const { radioGroup: radioGroupStyle, ...fieldLayoutStyles } = styles ?? {};
99
+
100
+ const hasContent = children || options;
101
+
102
+ return (
103
+ <FieldLayout
104
+ label={label}
105
+ description={description}
106
+ isInvalid={isInvalid}
107
+ required={required ?? false}
108
+ layout={layout ?? formLayout ?? 'vertical'}
109
+ classNames={fieldLayoutClassNames}
110
+ styles={fieldLayoutStyles}
111
+ errorMessage={errorMessage}
112
+ name={name}
113
+ >
114
+ <RadioGroup
115
+ id={name}
116
+ name={name}
117
+ orientation={orientation}
118
+ aria-invalid={isInvalid}
119
+ value={field.state.value}
120
+ disabled={disabled}
121
+ onValueChange={(value) => field.handleChange(value)}
122
+ className={cn(
123
+ 'flex gap-3',
124
+ orientation === 'horizontal' ? 'flex-row' : 'flex-col',
125
+ radioGroupClassName,
126
+ )}
127
+ style={radioGroupStyle ?? {}}
128
+ >
129
+ {hasContent ? (
130
+ children ||
131
+ (options && renderRadioOptions(options, name, disabled ?? false))
132
+ ) : (
133
+ <div className="text-sm text-muted-foreground">暂无选项</div>
134
+ )}
135
+ </RadioGroup>
136
+ </FieldLayout>
137
+ );
138
+ }
@@ -0,0 +1,155 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ import { useFormGlobalProps } from '@client/src/components/business-ui/form/context';
4
+ import { FieldLayout } from '@client/src/components/business-ui/form/field-layout';
5
+ import { useFieldContext } from '@client/src/components/business-ui/form/hooks/form-context';
6
+ import {
7
+ useFieldName,
8
+ useFieldValidation,
9
+ } from '@client/src/components/business-ui/form/hooks/form-utils';
10
+ import type {
11
+ CommonFieldProps,
12
+ FieldLayoutClassNames,
13
+ FieldLayoutStyles,
14
+ } from '@client/src/components/business-ui/form/types';
15
+ import { cn } from '@/lib/utils';
16
+ import {
17
+ Select,
18
+ SelectContent,
19
+ SelectGroup,
20
+ SelectItem,
21
+ SelectLabel,
22
+ SelectSeparator,
23
+ SelectTrigger,
24
+ SelectValue,
25
+ } from '@client/src/components/ui/select';
26
+
27
+ type SelectOption = {
28
+ label: string;
29
+ value: string;
30
+ disabled?: boolean;
31
+ };
32
+
33
+ type SelectGroupOption = {
34
+ label: string;
35
+ options: SelectOption[];
36
+ };
37
+
38
+ type SelectFieldProps = CommonFieldProps & {
39
+ placeholder?: string;
40
+ children?: ReactNode;
41
+ options?: SelectOption[] | SelectGroupOption[];
42
+ classNames?: Partial<
43
+ FieldLayoutClassNames & {
44
+ Select: string;
45
+ }
46
+ >;
47
+ styles?: Partial<
48
+ FieldLayoutStyles & {
49
+ Select: React.CSSProperties;
50
+ }
51
+ >;
52
+ };
53
+
54
+ function renderOptions(
55
+ options: SelectOption[] | SelectGroupOption[],
56
+ ): ReactNode {
57
+ // 检查是否是分组选项
58
+ const isGrouped = options.length > 0 && options[0] && 'options' in options[0];
59
+
60
+ if (isGrouped) {
61
+ return (options as SelectGroupOption[]).map((group, groupIndex) => (
62
+ <SelectGroup key={group.label}>
63
+ <SelectLabel>{group.label}</SelectLabel>
64
+ {group.options.map((option) => (
65
+ <SelectItem
66
+ key={option.value}
67
+ value={option.value}
68
+ disabled={option.disabled ?? false}
69
+ >
70
+ {option.label}
71
+ </SelectItem>
72
+ ))}
73
+ {groupIndex < options.length - 1 && <SelectSeparator />}
74
+ </SelectGroup>
75
+ ));
76
+ } else {
77
+ return (options as SelectOption[]).map((option) => (
78
+ <SelectItem
79
+ key={option.value}
80
+ value={option.value}
81
+ disabled={option.disabled ?? false}
82
+ >
83
+ {option.label}
84
+ </SelectItem>
85
+ ));
86
+ }
87
+ }
88
+
89
+ export function SelectField(props: SelectFieldProps) {
90
+ const {
91
+ label,
92
+ description,
93
+ placeholder,
94
+ children,
95
+ options,
96
+ required,
97
+ disabled,
98
+ classNames,
99
+ layout = 'vertical',
100
+ styles,
101
+ } = props;
102
+ const field = useFieldContext<string>();
103
+ const { isInvalid, errorMessage } = useFieldValidation();
104
+ const { layout: formLayout } = useFormGlobalProps();
105
+ const name = useFieldName();
106
+
107
+ const { Select: selectClassName, ...fieldLayoutClassNames } =
108
+ classNames ?? {};
109
+ const { Select: selectStyle, ...fieldLayoutStyles } = styles ?? {};
110
+
111
+ const hasContent = children || options;
112
+
113
+ return (
114
+ <FieldLayout
115
+ label={label}
116
+ description={description}
117
+ isInvalid={isInvalid}
118
+ required={required ?? false}
119
+ layout={layout ?? formLayout ?? 'vertical'}
120
+ classNames={fieldLayoutClassNames ?? {}}
121
+ styles={fieldLayoutStyles ?? {}}
122
+ errorMessage={errorMessage}
123
+ name={name}
124
+ >
125
+ <Select
126
+ disabled={disabled ?? false}
127
+ aria-invalid={isInvalid}
128
+ value={field.state.value}
129
+ onValueChange={(value) => {
130
+ field.handleChange(value);
131
+ }}
132
+ >
133
+ <SelectTrigger
134
+ style={selectStyle}
135
+ className={cn(selectClassName, 'w-full')}
136
+ id={name}
137
+ name={name}
138
+ aria-invalid={isInvalid}
139
+ >
140
+ <SelectValue placeholder={placeholder || '请选择'} />
141
+ </SelectTrigger>
142
+ <SelectContent>
143
+ {hasContent ? (
144
+ children || (options && renderOptions(options))
145
+ ) : (
146
+ // eslint-disable-next-line no-restricted-syntax
147
+ <SelectItem disabled value={''}>
148
+ 暂无选项
149
+ </SelectItem>
150
+ )}
151
+ </SelectContent>
152
+ </Select>
153
+ </FieldLayout>
154
+ );
155
+ }
@@ -0,0 +1,66 @@
1
+ import { FieldLayout } from '@client/src/components/business-ui/form/field-layout';
2
+ import { useFieldContext } from '@client/src/components/business-ui/form/hooks/form-context';
3
+ import {
4
+ useFieldName,
5
+ useFieldValidation,
6
+ } from '@client/src/components/business-ui/form/hooks/form-utils';
7
+ import type {
8
+ CommonFieldProps,
9
+ FieldLayoutClassNames,
10
+ FieldLayoutStyles,
11
+ } from '@client/src/components/business-ui/form/types';
12
+ import { Switch } from '@client/src/components/ui/switch';
13
+
14
+ type SwitchFieldProps = CommonFieldProps &
15
+ React.ComponentProps<typeof Switch> & {
16
+ classNames?: Partial<FieldLayoutClassNames & Record<'switch', string>>;
17
+ styles?: Partial<FieldLayoutStyles & Record<'switch', React.CSSProperties>>;
18
+ };
19
+
20
+ export function SwitchField(props: SwitchFieldProps) {
21
+ const {
22
+ label,
23
+ description,
24
+ disabled,
25
+ required,
26
+ classNames,
27
+ layout = 'vertical',
28
+ styles,
29
+ ...rest
30
+ } = props;
31
+ const field = useFieldContext<boolean>();
32
+ const { isInvalid, errorMessage } = useFieldValidation();
33
+ const name = useFieldName();
34
+ const { switch: switchClassName, ...fieldLayoutClassNames } =
35
+ classNames ?? {};
36
+ const { switch: switchStyle, ...fieldLayoutStyles } = styles ?? {};
37
+
38
+ return (
39
+ <FieldLayout
40
+ label={label}
41
+ description={description}
42
+ isInvalid={isInvalid}
43
+ required={required ?? false}
44
+ layout={layout}
45
+ classNames={fieldLayoutClassNames}
46
+ styles={fieldLayoutStyles}
47
+ errorMessage={errorMessage}
48
+ name={name}
49
+ >
50
+ <Switch
51
+ {...rest}
52
+ id={name}
53
+ className={switchClassName ?? ''}
54
+ style={switchStyle ?? {}}
55
+ name={name}
56
+ disabled={disabled ?? false}
57
+ checked={field.state.value}
58
+ onBlur={field.handleBlur}
59
+ onCheckedChange={(checked) => {
60
+ field.handleChange(checked);
61
+ }}
62
+ aria-invalid={isInvalid}
63
+ />
64
+ </FieldLayout>
65
+ );
66
+ }
@@ -0,0 +1,72 @@
1
+ import { useFormGlobalProps } from '@client/src/components/business-ui/form/context';
2
+ import { FieldLayout } from '@client/src/components/business-ui/form/field-layout';
3
+ import { useFieldContext } from '@client/src/components/business-ui/form/hooks/form-context';
4
+ import {
5
+ useFieldName,
6
+ useFieldValidation,
7
+ } from '@client/src/components/business-ui/form/hooks/form-utils';
8
+ import type {
9
+ CommonFieldProps,
10
+ FieldLayoutClassNames,
11
+ FieldLayoutStyles,
12
+ } from '@client/src/components/business-ui/form/types';
13
+ import { Textarea } from '@client/src/components/ui/textarea';
14
+
15
+ type TextareaFieldProps = CommonFieldProps &
16
+ React.ComponentProps<typeof Textarea> & {
17
+ classNames?: Partial<FieldLayoutClassNames & Record<'textarea', string>>;
18
+ styles?: Partial<
19
+ FieldLayoutStyles & Record<'textarea', React.CSSProperties>
20
+ >;
21
+ };
22
+
23
+ export function TextareaField({
24
+ label,
25
+ description,
26
+ disabled,
27
+ required,
28
+ classNames,
29
+ layout = 'vertical',
30
+ readOnly,
31
+ styles,
32
+ ...rest
33
+ }: TextareaFieldProps) {
34
+ const field = useFieldContext<string>();
35
+ const { isInvalid, errorMessage } = useFieldValidation();
36
+ const { layout: formLayout } = useFormGlobalProps();
37
+ const name = useFieldName();
38
+
39
+ const { textarea: textareaClassName, ...fieldLayoutClassNames } =
40
+ classNames ?? {};
41
+ const { textarea: textareaStyle, ...fieldLayoutStyles } = styles ?? {};
42
+
43
+ return (
44
+ <FieldLayout
45
+ label={label}
46
+ description={description}
47
+ isInvalid={isInvalid}
48
+ required={required ?? false}
49
+ layout={layout ?? formLayout ?? 'vertical'}
50
+ classNames={fieldLayoutClassNames}
51
+ styles={fieldLayoutStyles}
52
+ errorMessage={errorMessage}
53
+ name={name}
54
+ >
55
+ <Textarea
56
+ {...rest}
57
+ disabled={disabled ?? false}
58
+ readOnly={readOnly ?? false}
59
+ id={name}
60
+ name={name}
61
+ className={textareaClassName ?? ''}
62
+ style={textareaStyle ?? {}}
63
+ value={field.state.value}
64
+ onBlur={field.handleBlur}
65
+ onChange={(e) => {
66
+ field.handleChange(e.target.value);
67
+ }}
68
+ aria-invalid={isInvalid}
69
+ />
70
+ </FieldLayout>
71
+ );
72
+ }
@@ -0,0 +1,32 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export type FieldLayoutStyles = Record<
4
+ 'root' | 'label' | 'description' | 'error',
5
+ React.CSSProperties
6
+ >;
7
+ export type FieldLayoutClassNames = Record<
8
+ 'root' | 'label' | 'description' | 'error',
9
+ string
10
+ >;
11
+
12
+ export interface FieldLayoutProps {
13
+ label?: ReactNode;
14
+ placeholder?: string;
15
+ description?: string | undefined;
16
+ isInvalid?: boolean;
17
+ errorMessage?: string;
18
+ layout?: 'horizontal' | 'vertical' | 'responsive';
19
+ name?: string;
20
+ required?: boolean;
21
+ classNames?: Partial<FieldLayoutClassNames>; // 自定义样式
22
+ styles?: Partial<FieldLayoutStyles>; // 内联样式
23
+ }
24
+
25
+ export interface CommonFieldProps {
26
+ label?: ReactNode;
27
+ placeholder?: string;
28
+ description?: string;
29
+ disabled?: boolean;
30
+ required?: boolean;
31
+ layout?: 'horizontal' | 'vertical' | 'responsive';
32
+ }