@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,96 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { Link as LinkIcon } from 'lucide-react';
5
+
6
+ import { LinkEditForm } from '@/components/business-ui/tiptap-editor/components/link-edit-form';
7
+ import { useTiptapEditor } from '@/components/business-ui/tiptap-editor/hooks/use-tiptap-editor';
8
+ import { Button } from '@/components/ui/button';
9
+ import {
10
+ Popover,
11
+ PopoverContent,
12
+ PopoverTrigger,
13
+ } from '@/components/ui/popover';
14
+ import {
15
+ Tooltip,
16
+ TooltipContent,
17
+ TooltipProvider,
18
+ TooltipTrigger,
19
+ } from '@/components/ui/tooltip';
20
+
21
+ export function LinkToolbarButton() {
22
+ const { editor } = useTiptapEditor();
23
+ const [open, setOpen] = React.useState(false);
24
+ const [initialText, setInitialText] = React.useState('');
25
+ const [initialHref, setInitialHref] = React.useState('');
26
+
27
+ if (!editor) return null;
28
+
29
+ const resetForm = () => {
30
+ setInitialText('');
31
+ setInitialHref('');
32
+ };
33
+
34
+ const openDialog = () => {
35
+ const { from, to } = editor.state.selection;
36
+ const selectedText = editor.state.doc.textBetween(from, to, ' ');
37
+ const currentHref = (editor.getAttributes('link')?.href as string) || '';
38
+
39
+ setInitialText(selectedText);
40
+ setInitialHref(currentHref);
41
+ setOpen(true);
42
+ };
43
+
44
+ const trigger = (
45
+ <Button
46
+ variant="ghost"
47
+ size="sm"
48
+ className="size-6 px-0"
49
+ onClick={openDialog}
50
+ disabled={!editor.can().toggleLink({ href: '' })}
51
+ >
52
+ <LinkIcon className="size-4" />
53
+ <span className="sr-only">链接</span>
54
+ </Button>
55
+ );
56
+
57
+ return (
58
+ <Popover
59
+ open={open}
60
+ onOpenChange={(nextOpen) => {
61
+ setOpen(nextOpen);
62
+ if (!nextOpen) resetForm();
63
+ }}
64
+ >
65
+ <TooltipProvider>
66
+ <Tooltip delayDuration={700}>
67
+ <TooltipTrigger asChild>
68
+ <PopoverTrigger asChild>{trigger}</PopoverTrigger>
69
+ </TooltipTrigger>
70
+ {!open && (
71
+ <TooltipContent>
72
+ <p>链接</p>
73
+ </TooltipContent>
74
+ )}
75
+ </Tooltip>
76
+ </TooltipProvider>
77
+
78
+ <PopoverContent
79
+ className="w-105 p-5 shadow-lg"
80
+ align="start"
81
+ sideOffset={6}
82
+ >
83
+ <LinkEditForm
84
+ open={open}
85
+ initialText={initialText}
86
+ initialHref={initialHref}
87
+ autoFocusHref
88
+ onDone={() => {
89
+ setOpen(false);
90
+ resetForm();
91
+ }}
92
+ />
93
+ </PopoverContent>
94
+ </Popover>
95
+ );
96
+ }
@@ -0,0 +1,75 @@
1
+ 'use client';
2
+
3
+ import { Check, ChevronDown, List, ListOrdered, ListTodo } from 'lucide-react';
4
+
5
+ import { useTiptapEditor } from '@/components/business-ui/tiptap-editor/hooks/use-tiptap-editor';
6
+ import { cn } from '@/lib/utils';
7
+ import { Button } from '@/components/ui/button';
8
+ import {
9
+ DropdownMenu,
10
+ DropdownMenuContent,
11
+ DropdownMenuItem,
12
+ DropdownMenuTrigger,
13
+ } from '@/components/ui/dropdown-menu';
14
+
15
+ const LIST_OPTIONS = [
16
+ { type: 'bulletList', label: '无序列表', icon: List },
17
+ { type: 'orderedList', label: '有序列表', icon: ListOrdered },
18
+ { type: 'taskList', label: '任务列表', icon: ListTodo },
19
+ ] as const;
20
+
21
+ export function ListToolbarButton() {
22
+ const { editor } = useTiptapEditor();
23
+
24
+ if (!editor) return null;
25
+
26
+ const handleSelect = (type: string) => {
27
+ switch (type) {
28
+ case 'bulletList':
29
+ editor.chain().focus().toggleBulletList().run();
30
+ break;
31
+ case 'orderedList':
32
+ editor.chain().focus().toggleOrderedList().run();
33
+ break;
34
+ case 'taskList':
35
+ editor.chain().focus().toggleTaskList().run();
36
+ break;
37
+ }
38
+ };
39
+
40
+ return (
41
+ <DropdownMenu>
42
+ <DropdownMenuTrigger asChild>
43
+ <Button variant="ghost" size="sm" className="h-6 gap-0.5 px-2">
44
+ <List className="size-4" />
45
+ <ChevronDown className="size-3.5 text-muted-foreground" />
46
+ <span className="sr-only">列表</span>
47
+ </Button>
48
+ </DropdownMenuTrigger>
49
+ <DropdownMenuContent
50
+ align="start"
51
+ className="w-50"
52
+ onCloseAutoFocus={(e) => e.preventDefault()}
53
+ >
54
+ {LIST_OPTIONS.map((option) => {
55
+ const Icon = option.icon;
56
+ const active = editor.isActive(option.type);
57
+
58
+ return (
59
+ <DropdownMenuItem
60
+ key={option.type}
61
+ onClick={() => handleSelect(option.type)}
62
+ className={cn('justify-between', active && 'bg-accent')}
63
+ >
64
+ <span className="flex items-center gap-2">
65
+ <Icon className="size-4" />
66
+ {option.label}
67
+ </span>
68
+ {active && <Check className="size-4" />}
69
+ </DropdownMenuItem>
70
+ );
71
+ })}
72
+ </DropdownMenuContent>
73
+ </DropdownMenu>
74
+ );
75
+ }
@@ -0,0 +1,118 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { Bold, Code, Italic, Strikethrough, Underline } from 'lucide-react';
5
+
6
+ import { useTiptapEditor } from '@/components/business-ui/tiptap-editor/hooks/use-tiptap-editor';
7
+ import { Toggle } from '@/components/ui/toggle';
8
+ import {
9
+ Tooltip,
10
+ TooltipContent,
11
+ TooltipProvider,
12
+ TooltipTrigger,
13
+ } from '@/components/ui/tooltip';
14
+
15
+ interface MarkToolbarButtonProps {
16
+ format: 'bold' | 'italic' | 'underline' | 'strike' | 'code';
17
+ icon?: React.ReactNode;
18
+ tooltip?: string;
19
+ }
20
+
21
+ export function MarkToolbarButton({
22
+ format,
23
+ icon,
24
+ tooltip,
25
+ }: MarkToolbarButtonProps) {
26
+ const { editor } = useTiptapEditor();
27
+
28
+ if (!editor) return null;
29
+
30
+ const isActive = editor.isActive(format);
31
+
32
+ let disabled = false;
33
+
34
+ switch (format) {
35
+ case 'bold':
36
+ disabled = !editor.can().toggleBold();
37
+ break;
38
+ case 'italic':
39
+ disabled = !editor.can().toggleItalic();
40
+ break;
41
+ case 'underline':
42
+ disabled = !editor.can().toggleUnderline();
43
+ break;
44
+ case 'strike':
45
+ disabled = !editor.can().toggleStrike();
46
+ break;
47
+ case 'code':
48
+ disabled = !editor.can().toggleCode();
49
+ break;
50
+ }
51
+
52
+ const toggle = () => {
53
+ switch (format) {
54
+ case 'bold':
55
+ editor.chain().focus().toggleBold().run();
56
+ break;
57
+ case 'italic':
58
+ editor.chain().focus().toggleItalic().run();
59
+ break;
60
+ case 'underline':
61
+ editor.chain().focus().toggleUnderline().run();
62
+ break;
63
+ case 'strike':
64
+ editor.chain().focus().toggleStrike().run();
65
+ break;
66
+ case 'code':
67
+ editor.chain().focus().toggleCode().run();
68
+ break;
69
+ }
70
+ };
71
+
72
+ const icons = {
73
+ bold: <Bold className="size-4" />,
74
+ italic: <Italic className="size-4" />,
75
+ underline: <Underline className="size-4" />,
76
+ strike: <Strikethrough className="size-4" />,
77
+ code: <Code className="size-4" />,
78
+ };
79
+
80
+ const labels: Record<typeof format, string> = {
81
+ bold: '粗体',
82
+ italic: '斜体',
83
+ underline: '下划线',
84
+ strike: '删除线',
85
+ code: '行内代码',
86
+ };
87
+
88
+ const tooltips: Record<typeof format, string> = {
89
+ bold: '粗体(⌘+B)',
90
+ italic: '斜体(⌘+I)',
91
+ underline: '下划线(⌘+U)',
92
+ strike: '删除线(⌘+Shift+X)',
93
+ code: '行内代码(⌘+E)',
94
+ };
95
+
96
+ return (
97
+ <TooltipProvider>
98
+ <Tooltip delayDuration={700}>
99
+ <TooltipTrigger>
100
+ <Toggle
101
+ size="sm"
102
+ pressed={isActive}
103
+ onPressedChange={toggle}
104
+ disabled={disabled}
105
+ aria-label={labels[format]}
106
+ className="size-6 p-0"
107
+ asChild
108
+ >
109
+ <div>{icon || icons[format]}</div>
110
+ </Toggle>
111
+ </TooltipTrigger>
112
+ <TooltipContent>
113
+ <p>{tooltip || tooltips[format]}</p>
114
+ </TooltipContent>
115
+ </Tooltip>
116
+ </TooltipProvider>
117
+ );
118
+ }
@@ -0,0 +1,84 @@
1
+ 'use client';
2
+
3
+ import {
4
+ AlignCenter,
5
+ AlignLeft,
6
+ AlignRight,
7
+ Check,
8
+ ChevronDown,
9
+ } from 'lucide-react';
10
+
11
+ import { useTiptapEditor } from '@/components/business-ui/tiptap-editor/hooks/use-tiptap-editor';
12
+ import { cn } from '@/lib/utils';
13
+ import { Button } from '@/components/ui/button';
14
+ import {
15
+ DropdownMenu,
16
+ DropdownMenuContent,
17
+ DropdownMenuItem,
18
+ DropdownMenuTrigger,
19
+ } from '@/components/ui/dropdown-menu';
20
+
21
+ const ALIGN_OPTIONS = [
22
+ { value: 'left', label: '左对齐', icon: AlignLeft },
23
+ { value: 'center', label: '居中对齐', icon: AlignCenter },
24
+ { value: 'right', label: '右对齐', icon: AlignRight },
25
+ ] as const;
26
+
27
+ export function TextAlignToolbarButton() {
28
+ const { editor } = useTiptapEditor();
29
+
30
+ if (!editor) return null;
31
+
32
+ const getCurrentAlignment = () => {
33
+ if (editor.isActive({ textAlign: 'center' })) return 'center';
34
+ if (editor.isActive({ textAlign: 'right' })) return 'right';
35
+ return 'left';
36
+ };
37
+
38
+ const isDefaultLeft =
39
+ !editor.isActive({ textAlign: 'center' }) &&
40
+ !editor.isActive({ textAlign: 'right' }) &&
41
+ !editor.isActive({ textAlign: 'justify' });
42
+
43
+ const alignment = getCurrentAlignment();
44
+ const currentOption = ALIGN_OPTIONS.find((opt) => opt.value === alignment);
45
+ const Icon = currentOption?.icon ?? AlignLeft;
46
+
47
+ return (
48
+ <DropdownMenu>
49
+ <DropdownMenuTrigger asChild>
50
+ <Button variant="ghost" size="sm" className="h-6 gap-0.5 px-2">
51
+ <Icon className="size-4" />
52
+ <ChevronDown className="size-3.5 text-muted-foreground" />
53
+ <span className="sr-only">文字对齐</span>
54
+ </Button>
55
+ </DropdownMenuTrigger>
56
+ <DropdownMenuContent align="start" className="w-50">
57
+ {ALIGN_OPTIONS.map((option) => {
58
+ const OptionIcon = option.icon;
59
+ const active =
60
+ option.value === 'left'
61
+ ? editor.isActive({ textAlign: 'left' }) || isDefaultLeft
62
+ : editor.isActive({ textAlign: option.value });
63
+
64
+ return (
65
+ <DropdownMenuItem
66
+ key={option.value}
67
+ onClick={() =>
68
+ editor.chain().focus().setTextAlign(option.value).run()
69
+ }
70
+ disabled={!editor.can().setTextAlign(option.value)}
71
+ className={cn('justify-between', active && 'bg-accent')}
72
+ >
73
+ <span className="flex items-center gap-2">
74
+ <OptionIcon className="size-4" />
75
+ {option.label}
76
+ </span>
77
+ {active && <Check className="size-4" />}
78
+ </DropdownMenuItem>
79
+ );
80
+ })}
81
+ </DropdownMenuContent>
82
+ </DropdownMenu>
83
+ );
84
+ }
@@ -0,0 +1,54 @@
1
+ 'use client';
2
+
3
+ import { Redo, Undo } from 'lucide-react';
4
+
5
+ import { useTiptapEditor } from '@/components/business-ui/tiptap-editor/hooks/use-tiptap-editor';
6
+ import { Button } from '@/components/ui/button';
7
+ import {
8
+ Tooltip,
9
+ TooltipContent,
10
+ TooltipProvider,
11
+ TooltipTrigger,
12
+ } from '@/components/ui/tooltip';
13
+
14
+ interface UndoRedoToolbarButtonProps {
15
+ action: 'undo' | 'redo';
16
+ }
17
+
18
+ export function UndoRedoToolbarButton({ action }: UndoRedoToolbarButtonProps) {
19
+ const { editor } = useTiptapEditor();
20
+
21
+ if (!editor) return null;
22
+
23
+ const canExecute =
24
+ action === 'undo' ? editor.can().undo() : editor.can().redo();
25
+ const Icon = action === 'undo' ? Undo : Redo;
26
+
27
+ return (
28
+ <TooltipProvider>
29
+ <Tooltip delayDuration={700}>
30
+ <TooltipTrigger asChild>
31
+ <Button
32
+ variant="ghost"
33
+ size="sm"
34
+ onClick={() =>
35
+ action === 'undo'
36
+ ? editor.chain().focus().undo().run()
37
+ : editor.chain().focus().redo().run()
38
+ }
39
+ disabled={!canExecute}
40
+ className="size-6 px-0"
41
+ >
42
+ <Icon className="size-4" />
43
+ <span className="sr-only">
44
+ {action === 'undo' ? '撤销' : '重做'}
45
+ </span>
46
+ </Button>
47
+ </TooltipTrigger>
48
+ <TooltipContent>
49
+ <p>{action === 'undo' ? '撤销(⌘+Z)' : '重做(⌘+Shift+Z)'}</p>
50
+ </TooltipContent>
51
+ </Tooltip>
52
+ </TooltipProvider>
53
+ );
54
+ }