pgo-uiux2 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/.env +1 -0
  2. package/.env.production +1 -0
  3. package/.prettierrc +13 -0
  4. package/.vscode/extensions.json +3 -0
  5. package/BUTTON_GUIDE.md +257 -0
  6. package/README.md +49 -0
  7. package/THEME_REFERENCE.md +310 -0
  8. package/eslint.config.ts +27 -0
  9. package/index.html +13 -0
  10. package/package.json +85 -0
  11. package/public/favicon.ico +0 -0
  12. package/src/App.vue +368 -0
  13. package/src/assets/fonts/Faruma.ttf +0 -0
  14. package/src/components/examples/AppBarExample.vue +101 -0
  15. package/src/components/examples/AvatarExample.vue +47 -0
  16. package/src/components/examples/BannerExample.vue +287 -0
  17. package/src/components/examples/BaseInputExample.vue +25 -0
  18. package/src/components/examples/BreadcrumbExample.vue +53 -0
  19. package/src/components/examples/CardExample.vue +77 -0
  20. package/src/components/examples/ChipExample.vue +225 -0
  21. package/src/components/examples/DatePickerExample.vue +31 -0
  22. package/src/components/examples/DropdownExample.vue +84 -0
  23. package/src/components/examples/EditorExample.vue +200 -0
  24. package/src/components/examples/ExpansionPanelExample.vue +42 -0
  25. package/src/components/examples/FileUploadExample.vue +40 -0
  26. package/src/components/examples/FormExample.vue +121 -0
  27. package/src/components/examples/HugeTest.vue +8 -0
  28. package/src/components/examples/LayoutContainerExample.vue +80 -0
  29. package/src/components/examples/ModalExample.vue +82 -0
  30. package/src/components/examples/NavDrawerExample.vue +170 -0
  31. package/src/components/examples/NumberFieldExample.vue +145 -0
  32. package/src/components/examples/RadioButtonExample.vue +161 -0
  33. package/src/components/examples/SearchExample.vue +322 -0
  34. package/src/components/examples/SelectExample.vue +121 -0
  35. package/src/components/examples/StackedTableViewExample.vue +53 -0
  36. package/src/components/examples/TabExample.vue +336 -0
  37. package/src/components/examples/TableExample.vue +228 -0
  38. package/src/components/examples/TextFieldExample.vue +181 -0
  39. package/src/components/examples/TextareaExample.vue +173 -0
  40. package/src/components/examples/ThemeToggle.vue +50 -0
  41. package/src/components/examples/TimelineExample.vue +66 -0
  42. package/src/components/examples/TipTapEditorExample.vue +20 -0
  43. package/src/components/examples/TooltipExample.vue +53 -0
  44. package/src/components/examples/VueDatePickerShowcase.vue +214 -0
  45. package/src/components/examples/_DatePickerExample.vue +33 -0
  46. package/src/components/examples/__FormExample.vue +77 -0
  47. package/src/components/index.ts +25 -0
  48. package/src/components/pgo/AppBar.vue +347 -0
  49. package/src/components/pgo/Avatar.vue +139 -0
  50. package/src/components/pgo/Banner.vue +300 -0
  51. package/src/components/pgo/Breadcrumb.vue +101 -0
  52. package/src/components/pgo/Button.vue +171 -0
  53. package/src/components/pgo/Card.vue +178 -0
  54. package/src/components/pgo/ConfirmationModel.vue +32 -0
  55. package/src/components/pgo/DataTable.vue +845 -0
  56. package/src/components/pgo/DatePicker/CalendarPanel.vue +43 -0
  57. package/src/components/pgo/DatePicker/__DatePicker.vue +122 -0
  58. package/src/components/pgo/DatePicker/types.ts +11 -0
  59. package/src/components/pgo/DatePicker/useCalendar.ts +39 -0
  60. package/src/components/pgo/DatePicker/useDatePicker.ts +31 -0
  61. package/src/components/pgo/Deprecated/ToastContainer.vue +51 -0
  62. package/src/components/pgo/Deprecated/ToastItem.vue +55 -0
  63. package/src/components/pgo/Dropdown.vue +296 -0
  64. package/src/components/pgo/DropdownItem.vue +40 -0
  65. package/src/components/pgo/Editor.vue +511 -0
  66. package/src/components/pgo/ExpansionPanel.vue +185 -0
  67. package/src/components/pgo/Footer.vue +39 -0
  68. package/src/components/pgo/HeroIcon.vue +124 -0
  69. package/src/components/pgo/InputSearch.vue +194 -0
  70. package/src/components/pgo/LayoutContainer.vue +104 -0
  71. package/src/components/pgo/Main.vue +37 -0
  72. package/src/components/pgo/Modal.vue +273 -0
  73. package/src/components/pgo/NavDrawer.vue +127 -0
  74. package/src/components/pgo/NavDrawerItem.vue +161 -0
  75. package/src/components/pgo/NavigationDrawer.vue +849 -0
  76. package/src/components/pgo/OLDNavDrawer.vue +661 -0
  77. package/src/components/pgo/OldAppBar.vue +223 -0
  78. package/src/components/pgo/PApp.vue +102 -0
  79. package/src/components/pgo/Pagination.vue +242 -0
  80. package/src/components/pgo/Search copy.vue +310 -0
  81. package/src/components/pgo/Search.vue +411 -0
  82. package/src/components/pgo/StackedTableView.vue +167 -0
  83. package/src/components/pgo/Tab.vue +617 -0
  84. package/src/components/pgo/TestInput.vue +395 -0
  85. package/src/components/pgo/Timeline.vue +367 -0
  86. package/src/components/pgo/TimelineItem.vue +80 -0
  87. package/src/components/pgo/TipTapEditor.vue +315 -0
  88. package/src/components/pgo/Tooltip.NOTES.md +12 -0
  89. package/src/components/pgo/Tooltip.PROPS.md +21 -0
  90. package/src/components/pgo/Tooltip.vue +281 -0
  91. package/src/components/pgo/base/Base.vue +444 -0
  92. package/src/components/pgo/buttons/Chip.vue +324 -0
  93. package/src/components/pgo/buttons/ChipGroup.vue +224 -0
  94. package/src/components/pgo/buttons/Radio.vue +424 -0
  95. package/src/components/pgo/filters/FilterSection.vue +188 -0
  96. package/src/components/pgo/filters/Searchbar.vue +216 -0
  97. package/src/components/pgo/forms/DynamicForm.vue +45 -0
  98. package/src/components/pgo/forms/Form.vue +132 -0
  99. package/src/components/pgo/index.ts +15 -0
  100. package/src/components/pgo/inputs/Checkbox.vue +320 -0
  101. package/src/components/pgo/inputs/DatePicker.vue +395 -0
  102. package/src/components/pgo/inputs/FileUpload.vue +326 -0
  103. package/src/components/pgo/inputs/NumberField.vue +243 -0
  104. package/src/components/pgo/inputs/Radio.vue +162 -0
  105. package/src/components/pgo/inputs/RadioGroup.vue +188 -0
  106. package/src/components/pgo/inputs/Select.vue +535 -0
  107. package/src/components/pgo/inputs/TextField.vue +194 -0
  108. package/src/components/pgo/inputs/Textarea.vue +181 -0
  109. package/src/main.js +12 -0
  110. package/src/pgo-components/_index.js +31 -0
  111. package/src/pgo-components/assets/fonts/Faruma.ttf +0 -0
  112. package/src/pgo-components/assets/fonts/logo.png +0 -0
  113. package/src/pgo-components/composables/useTheme.js +10 -0
  114. package/src/pgo-components/directives/tooltip-directive.ts +393 -0
  115. package/src/pgo-components/index.js +96 -0
  116. package/src/pgo-components/lib/componentConfig.js +147 -0
  117. package/src/pgo-components/lib/core/composables/_useCalendar.ts +127 -0
  118. package/src/pgo-components/lib/core/composables/useDefaults.ts +15 -0
  119. package/src/pgo-components/lib/core/composables/useLanguageSelect.js +0 -0
  120. package/src/pgo-components/lib/core/composables/useRtl.ts +12 -0
  121. package/src/pgo-components/lib/core/defaults/createDefaults.ts +5 -0
  122. package/src/pgo-components/lib/core/defaults/defaults.ts +7 -0
  123. package/src/pgo-components/lib/core/rtl/rtl.ts +3 -0
  124. package/src/pgo-components/lib/core/rtl/setRtl.ts +19 -0
  125. package/src/pgo-components/lib/drawerState.ts +3 -0
  126. package/src/pgo-components/lib/i18n/defaultLables.js +71 -0
  127. package/src/pgo-components/lib/i18n/i18nPlugin.js +52 -0
  128. package/src/pgo-components/lib/i18n/useI18n.js +35 -0
  129. package/src/pgo-components/lib/index.ts +38 -0
  130. package/src/pgo-components/pages/Component.vue +7 -0
  131. package/src/pgo-components/pages/ComponentRenderer.vue +85 -0
  132. package/src/pgo-components/pages/Home.vue +130 -0
  133. package/src/pgo-components/pages/ListView.vue +370 -0
  134. package/src/pgo-components/pages/Page1.vue +296 -0
  135. package/src/pgo-components/pages/_Page1.vue +180 -0
  136. package/src/pgo-components/plugins/SnackBar.vue +251 -0
  137. package/src/pgo-components/plugins/SnackBarContainer.vue +53 -0
  138. package/src/pgo-components/plugins/SnackBarPlugin.ts +136 -0
  139. package/src/pgo-components/plugins/theme-plugin.js +114 -0
  140. package/src/pgo-components/plugins/types.ts +46 -0
  141. package/src/pgo-components/plugins/useSnackBar.js +11 -0
  142. package/src/pgo-components/plugins/useSnackBar.ts +21 -0
  143. package/src/pgo-components/plugins/validation-plugin.js +11 -0
  144. package/src/pgo-components/services/Entry.json +813 -0
  145. package/src/pgo-components/services/axios.js +54 -0
  146. package/src/pgo-components/services/data.json +90 -0
  147. package/src/pgo-components/services/person.json +260 -0
  148. package/src/pgo-components/services/toast.ts +44 -0
  149. package/src/pgo-components/styles/global.css +234 -0
  150. package/src/pgo-components/styles/reset.css +96 -0
  151. package/src/pgo-components/styles/tokens.css +18 -0
  152. package/src/pgo-components/styles/utilities/border-radius.css +57 -0
  153. package/src/pgo-components/styles/utilities/borders.css +85 -0
  154. package/src/pgo-components/styles/utilities/colors.css +38 -0
  155. package/src/pgo-components/styles/utilities/cursor.css +19 -0
  156. package/src/pgo-components/styles/utilities/display.css +78 -0
  157. package/src/pgo-components/styles/utilities/elevation.css +33 -0
  158. package/src/pgo-components/styles/utilities/flex.css +403 -0
  159. package/src/pgo-components/styles/utilities/float.css +41 -0
  160. package/src/pgo-components/styles/utilities/hover.css +9 -0
  161. package/src/pgo-components/styles/utilities/index.css +18 -0
  162. package/src/pgo-components/styles/utilities/opacity.css +27 -0
  163. package/src/pgo-components/styles/utilities/overflow.css +26 -0
  164. package/src/pgo-components/styles/utilities/palette.css +515 -0
  165. package/src/pgo-components/styles/utilities/position.css +14 -0
  166. package/src/pgo-components/styles/utilities/sizing.css +70 -0
  167. package/src/pgo-components/styles/utilities/spacing.css +578 -0
  168. package/src/pgo-components/styles/utilities/transitions.css +58 -0
  169. package/src/pgo-components/styles/utilities/typography.css +91 -0
  170. package/src/pgo-components/styles/utilities/z-index.css +11 -0
  171. package/src/pgo-components/tokens/index.js +337 -0
  172. package/src/router/index.js +88 -0
  173. package/src/shims-vue.d.ts +14 -0
  174. package/src/validations/validationRules.js +50 -0
  175. package/tailwind.config.js +73 -0
  176. package/test.php +5 -0
  177. package/tsconfig.json +25 -0
  178. package/ui +31 -0
  179. package/ui.pgo.mv.conf +18 -0
  180. package/vite.config.js +42 -0
@@ -0,0 +1,511 @@
1
+ <template>
2
+ <div class="rte-root border border-gray-300 dark:border-gray-600 rounded-lg overflow-hidden bg-white dark:bg-gray-900" data-component="rich-text-editor">
3
+ <!-- Toolbar -->
4
+ <div
5
+ v-if="editor"
6
+ class="rte-toolbar flex flex-wrap gap-1 p-2 border-b border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800"
7
+ :class="{ 'sticky top-0 z-10': stickyToolbar }"
8
+ >
9
+ <!-- Core formatting -->
10
+ <ToolbarButton
11
+ icon="x-mark"
12
+ title="Clear content"
13
+ @click="clearContent"
14
+ />
15
+
16
+ <ToolbarButton
17
+ icon="bold"
18
+ title="Bold"
19
+ :active="editor.isActive('bold')"
20
+ @click="cmd(() => editor.chain().toggleBold().run())"
21
+ />
22
+
23
+ <ToolbarButton
24
+ icon="underline"
25
+ title="Underline"
26
+ :active="editor.isActive('underline')"
27
+ @click="cmd(() => editor.chain().toggleUnderline().run())"
28
+ />
29
+
30
+ <div class="w-px bg-gray-300 dark:bg-gray-600 mx-1" />
31
+
32
+ <ToolbarButton
33
+ title="LTR"
34
+ :active="isLTR"
35
+ @click="setTextDirection('ltr')"
36
+ >
37
+ LTR
38
+ </ToolbarButton>
39
+
40
+ <ToolbarButton
41
+ title="RTL"
42
+ :active="isRTL"
43
+ @click="setTextDirection('rtl')"
44
+ >
45
+ RTL
46
+ </ToolbarButton>
47
+
48
+ <div class="w-px bg-gray-300 dark:bg-gray-600 mx-1" />
49
+
50
+ <!-- Text alignment -->
51
+ <ToolbarButton
52
+ icon="align-left"
53
+ title="Align Left"
54
+ @click="cmd(() => editor.chain().setTextAlign('left').run())"
55
+ />
56
+ <ToolbarButton
57
+ icon="align-center"
58
+ title="Align Center"
59
+ @click="cmd(() => editor.chain().setTextAlign('center').run())"
60
+ />
61
+ <ToolbarButton
62
+ icon="align-right"
63
+ title="Align Right"
64
+ @click="cmd(() => editor.chain().setTextAlign('right').run())"
65
+ />
66
+
67
+ <ToolbarButton
68
+ icon="list-bullet"
69
+ title="Bullet list"
70
+ :active="editor.isActive('bulletList')"
71
+ @click="cmd(() => editor.chain().toggleBulletList().run())"
72
+ />
73
+
74
+ <ToolbarButton
75
+ icon="numbered-list"
76
+ title="Ordered list"
77
+ :active="editor.isActive('orderedList')"
78
+ @click="cmd(() => editor.chain().toggleOrderedList().run())"
79
+ />
80
+
81
+ <ToolbarButton
82
+ icon="arrow-right"
83
+ title="Indent"
84
+ @click="cmd(() => handleIndent())"
85
+ />
86
+
87
+ <ToolbarButton
88
+ icon="arrow-left"
89
+ title="Outdent"
90
+ @click="cmd(() => handleOutdent())"
91
+ />
92
+
93
+ <div class="w-px bg-gray-300 dark:bg-gray-600 mx-1" />
94
+
95
+ <ToolbarButton
96
+ icon="table-cells"
97
+ title="Insert table"
98
+ @click="cmd(insertTable)"
99
+ />
100
+
101
+ <!-- Table width controls -->
102
+ <ToolbarButton
103
+ v-if="editor.isActive('table')"
104
+ title="Table 100%"
105
+ @click="setTableWidth('100%')"
106
+ >
107
+ 100%
108
+ </ToolbarButton>
109
+ <ToolbarButton
110
+ v-if="editor.isActive('table')"
111
+ title="Table 600px"
112
+ @click="setTableWidth('600px')"
113
+ >
114
+ 600px
115
+ </ToolbarButton>
116
+
117
+ <ToolbarButton
118
+ v-if="editor.isActive('table')"
119
+ icon="trash"
120
+ title="Delete table"
121
+ @click="cmd(() => editor.chain().deleteTable().run())"
122
+ />
123
+
124
+ <!-- Table manipulation buttons -->
125
+ <template v-if="editor.isActive('table')">
126
+ <div class="w-px bg-gray-300 dark:bg-gray-600 mx-1" />
127
+ <ToolbarButton
128
+ icon="arrow-up"
129
+ title="Add row above"
130
+ @click="cmd(() => editor.chain().addRowBefore().run())"
131
+ />
132
+ <ToolbarButton
133
+ icon="arrow-down"
134
+ title="Add row below"
135
+ @click="cmd(() => editor.chain().addRowAfter().run())"
136
+ />
137
+ <ToolbarButton
138
+ icon="trash"
139
+ title="Delete row"
140
+ @click="cmd(() => editor.chain().deleteRow().run())"
141
+ />
142
+ <ToolbarButton
143
+ icon="arrow-left"
144
+ title="Add column left"
145
+ @click="cmd(() => editor.chain().addColumnBefore().run())"
146
+ />
147
+ <ToolbarButton
148
+ icon="arrow-right"
149
+ title="Add column right"
150
+ @click="cmd(() => editor.chain().addColumnAfter().run())"
151
+ />
152
+ <ToolbarButton
153
+ icon="trash"
154
+ title="Delete column"
155
+ @click="cmd(() => editor.chain().deleteColumn().run())"
156
+ />
157
+ </template>
158
+
159
+ <ToolbarButton
160
+ v-if="canEditHtml"
161
+ icon="code-bracket"
162
+ title="Toggle HTML"
163
+ :active="isHtmlMode"
164
+ @click="toggleHtmlMode"
165
+ />
166
+ </div>
167
+
168
+ <!-- Editor Content -->
169
+ <div v-if="!isHtmlMode" class="rte-editor p-4 min-h-64 max-w-none">
170
+ <EditorContent :editor="editor" />
171
+ </div>
172
+
173
+ <!-- HTML mode -->
174
+ <div v-else class="rte-html p-4">
175
+ <textarea
176
+ v-model="rawHtml"
177
+ rows="10"
178
+ class="w-full p-2 border border-gray-300 dark:border-gray-600 rounded font-mono text-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100"
179
+ />
180
+ <button
181
+ type="button"
182
+ @click="applyHtml"
183
+ class="mt-2 px-3 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded font-medium transition"
184
+ >
185
+ Apply HTML
186
+ </button>
187
+ </div>
188
+
189
+ <!-- Footer -->
190
+ <div class="rte-footer flex items-center justify-between p-2 border-t border-gray-300 dark:border-gray-600 text-sm text-gray-600 dark:text-gray-400">
191
+ <span>{{ charCount }} characters</span>
192
+ <span v-if="isOverLimit" class="text-red-600 dark:text-red-400 font-medium">⚠ Limit reached</span>
193
+ </div>
194
+ </div>
195
+ </template>
196
+
197
+ <style scoped>
198
+ /* Editor content styling */
199
+ .rte-editor :deep(ul),
200
+ .rte-editor :deep(ol) {
201
+ margin: 1rem 0;
202
+ padding-left: 2rem;
203
+ }
204
+
205
+ .rte-editor :deep(li) {
206
+ margin: 0.5rem 0;
207
+ }
208
+
209
+ .rte-editor :deep(ul li) {
210
+ list-style-type: disc;
211
+ }
212
+
213
+ .rte-editor :deep(ol li) {
214
+ list-style-type: decimal;
215
+ }
216
+
217
+ .rte-editor :deep(p) {
218
+ margin: 0.5rem 0;
219
+ line-height: 1.6;
220
+ }
221
+
222
+ /* Table styling */
223
+ .rte-editor :deep(table) {
224
+ border-collapse: collapse;
225
+ width: 100%;
226
+ margin: 1rem 0;
227
+ border: 2px solid #e5e7eb;
228
+ }
229
+
230
+ .rte-editor :deep(td),
231
+ .rte-editor :deep(th) {
232
+ border: 1px solid #d1d5db;
233
+ padding: 0.75rem;
234
+ min-width: 50px;
235
+ word-break: break-word;
236
+ }
237
+
238
+ .rte-editor :deep(th) {
239
+ background-color: #f3f4f6;
240
+ font-weight: 600;
241
+ text-align: left;
242
+ }
243
+
244
+ .rte-editor :deep(tr:hover td) {
245
+ background-color: #f9fafb;
246
+ }
247
+
248
+ /* Dark mode table styling */
249
+ .dark .rte-editor :deep(table) {
250
+ border-color: #4b5563;
251
+ }
252
+
253
+ .dark .rte-editor :deep(td),
254
+ .dark .rte-editor :deep(th) {
255
+ border-color: #374151;
256
+ }
257
+
258
+ .dark .rte-editor :deep(th) {
259
+ background-color: #374151;
260
+ }
261
+
262
+ .dark .rte-editor :deep(tr:hover td) {
263
+ background-color: #ebebeb;
264
+ }
265
+
266
+ /* Text direction */
267
+ .rte-editor :deep([dir="rtl"]) {
268
+ direction: rtl;
269
+ text-align: right;
270
+ }
271
+
272
+ .rte-editor :deep([dir="ltr"]) {
273
+ direction: ltr;
274
+ text-align: left;
275
+ }
276
+
277
+ /* Focus style for table cells */
278
+ .rte-editor :deep(.selectedCell) {
279
+ background-color: #dbeafe;
280
+ }
281
+
282
+ .dark .rte-editor :deep(.selectedCell) {
283
+ background-color: #1e40af;
284
+ }
285
+ </style>
286
+
287
+ <script setup lang="ts">
288
+ import { ref, watch, computed, onBeforeUnmount, onMounted, defineComponent, h } from 'vue'
289
+ import { Editor, EditorContent } from '@tiptap/vue-3'
290
+ import StarterKit from '@tiptap/starter-kit'
291
+ import Underline from '@tiptap/extension-underline'
292
+ import CharacterCount from '@tiptap/extension-character-count'
293
+ import { Table } from '@tiptap/extension-table'
294
+ import { TableRow } from '@tiptap/extension-table-row'
295
+ import { TableHeader } from '@tiptap/extension-table-header'
296
+ import { TableCell } from '@tiptap/extension-table-cell'
297
+ import TextAlign from '@tiptap/extension-text-align'
298
+ import * as HeroIconsOutline from '@heroicons/vue/24/outline'
299
+
300
+ // Icon mapping
301
+ const iconMap: { [key: string]: any } = {
302
+ 'x-mark': HeroIconsOutline.XMarkIcon,
303
+ 'bold': HeroIconsOutline.PencilIcon,
304
+ 'underline': HeroIconsOutline.UnderlineIcon,
305
+ 'list-bullet': HeroIconsOutline.ListBulletIcon,
306
+ 'numbered-list': HeroIconsOutline.ListBulletIcon,
307
+ 'arrow-right': HeroIconsOutline.ArrowRightIcon,
308
+ 'arrow-left': HeroIconsOutline.ArrowLeftIcon,
309
+ 'arrow-up': HeroIconsOutline.ArrowUpIcon,
310
+ 'arrow-down': HeroIconsOutline.ArrowDownIcon,
311
+ 'align-left': HeroIconsOutline.ArrowLeftIcon,
312
+ 'align-center': HeroIconsOutline.Bars3Icon,
313
+ 'align-right': HeroIconsOutline.ArrowRightIcon,
314
+ 'table-cells': HeroIconsOutline.TableCellsIcon,
315
+ 'trash': HeroIconsOutline.TrashIcon,
316
+ 'code-bracket': HeroIconsOutline.CodeBracketIcon,
317
+ }
318
+
319
+ // ToolbarButton component
320
+ const ToolbarButton = defineComponent({
321
+ name: 'ToolbarButton',
322
+ props: {
323
+ icon: String,
324
+ title: String,
325
+ active: Boolean,
326
+ disabled: Boolean
327
+ },
328
+ emits: ['click'],
329
+ setup(props, { slots, emit }) {
330
+ return () => {
331
+ const IconComponent = props.icon ? iconMap[props.icon] : null
332
+
333
+ return h(
334
+ 'button',
335
+ {
336
+ type: 'button',
337
+ title: props.title,
338
+ disabled: props.disabled,
339
+ class: [
340
+ 'p-2 rounded hover:bg-gray-200 dark:hover:bg-gray-700 transition',
341
+ 'disabled:opacity-50 disabled:cursor-not-allowed',
342
+ props.active ? 'bg-blue-200 dark:bg-blue-900 text-blue-700 dark:text-blue-300' : ''
343
+ ].filter(Boolean).join(' '),
344
+ onClick: () => emit('click')
345
+ },
346
+ props.icon && IconComponent ? h(IconComponent, { class: 'w-5 h-5' }) : slots.default?.()
347
+ )
348
+ }
349
+ }
350
+ })
351
+
352
+ const props = defineProps<{
353
+ modelValue: string
354
+ maximumLength?: number | null
355
+ canEditHtml?: boolean
356
+ stickyToolbar?: boolean
357
+ }>()
358
+
359
+ const emit = defineEmits<{
360
+ (e: 'update:modelValue', value: string): void
361
+ (e: 'limitReached'): void
362
+ (e: 'limitOk'): void
363
+ }>()
364
+
365
+ const isHtmlMode = ref(false)
366
+ const rawHtml = ref('')
367
+ const isOverLimit = ref(false)
368
+ const editor = ref<any>(null)
369
+ const isLTR = ref(false)
370
+ const isRTL = ref(false)
371
+
372
+ onMounted(() => {
373
+ editor.value = new Editor({
374
+ content: props.modelValue,
375
+ injectCSS: true,
376
+ parseOptions: { preserveWhitespace: 'full' },
377
+ editable: true,
378
+ extensions: [
379
+ StarterKit.configure({
380
+ heading: false,
381
+ blockquote: false,
382
+ horizontalRule: false,
383
+ hardBreak: false,
384
+ paragraph: {
385
+ HTMLAttributes: {
386
+ dir: 'ltr'
387
+ }
388
+ }
389
+ }),
390
+ TextAlign.configure({ types: ['heading', 'paragraph'] }),
391
+ Underline,
392
+ Table.configure({
393
+ resizable: true,
394
+ handleWidth: 4,
395
+ cellMinWidth: 50,
396
+ lastColumnResizable: true,
397
+ colgroup: true
398
+ }),
399
+ TableRow,
400
+ TableHeader,
401
+ TableCell,
402
+ CharacterCount.configure(props.maximumLength ? { limit: props.maximumLength } : {})
403
+ ],
404
+ onUpdate({ editor: ed }) {
405
+ const html = ed.getHTML()
406
+ emit('update:modelValue', html)
407
+ checkLimit()
408
+ updateDirectionState()
409
+ }
410
+ })
411
+ updateDirectionState()
412
+ })
413
+
414
+ const charCount = computed(() => {
415
+ if (!editor.value) return 0
416
+ return editor.value.storage.characterCount?.characters() ?? editor.value.state.doc.textContent.length
417
+ })
418
+
419
+ function checkLimit() {
420
+ if (!editor.value || !props.maximumLength) return
421
+ if (charCount.value >= props.maximumLength) {
422
+ if (!isOverLimit.value) emit('limitReached')
423
+ isOverLimit.value = true
424
+ editor.value.setOptions({ editable: false })
425
+ } else {
426
+ if (isOverLimit.value) emit('limitOk')
427
+ isOverLimit.value = false
428
+ editor.value.setOptions({ editable: true })
429
+ }
430
+ }
431
+
432
+ function updateDirectionState() {
433
+ if (!editor.value) return
434
+ const attrs = editor.value.getAttributes('paragraph')
435
+ const currentDir = attrs?.dir || 'ltr'
436
+ isLTR.value = currentDir === 'ltr'
437
+ isRTL.value = currentDir === 'rtl'
438
+ }
439
+
440
+ function cmd(fn: () => void) {
441
+ if (!editor.value) return
442
+ editor.value.chain().focus()
443
+ fn()
444
+ }
445
+
446
+ function clearContent() {
447
+ cmd(() => editor.value?.chain().clearContent().run())
448
+ }
449
+
450
+ function insertTable() {
451
+ if (!editor.value) return
452
+ editor.value.chain().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run()
453
+ }
454
+
455
+ function toggleHtmlMode() {
456
+ if (!isHtmlMode.value && editor.value) {
457
+ rawHtml.value = editor.value.getHTML()
458
+ }
459
+ isHtmlMode.value = !isHtmlMode.value
460
+ }
461
+
462
+ function applyHtml() {
463
+ if (!editor.value) return
464
+ editor.value.commands.setContent(rawHtml.value, false)
465
+ isHtmlMode.value = false
466
+ }
467
+
468
+ function setTextDirection(direction: string) {
469
+ if (!editor.value) return
470
+ editor.value.chain()
471
+ .focus()
472
+ .updateAttributes('paragraph', { dir: direction })
473
+ .run()
474
+ updateDirectionState()
475
+ }
476
+
477
+ function handleIndent() {
478
+ if (!editor.value) return
479
+ // Try to indent list items
480
+ if (editor.value.isActive('bulletList') || editor.value.isActive('orderedList')) {
481
+ editor.value.chain().focus().sinkListItem('listItem').run()
482
+ }
483
+ }
484
+
485
+ function handleOutdent() {
486
+ if (!editor.value) return
487
+ // Try to outdent list items
488
+ if (editor.value.isActive('bulletList') || editor.value.isActive('orderedList')) {
489
+ editor.value.chain().focus().liftListItem('listItem').run()
490
+ }
491
+ }
492
+
493
+ function setTableWidth(pxOrPercent: string) {
494
+ if (!editor.value) return
495
+ editor.value.chain().focus().updateAttributes('table', { style: `width: ${pxOrPercent}` }).run()
496
+ }
497
+
498
+ watch(
499
+ () => props.modelValue,
500
+ value => {
501
+ if (!editor.value || value === editor.value.getHTML()) return
502
+ editor.value.commands.setContent(value, false)
503
+ }
504
+ )
505
+
506
+ onBeforeUnmount(() => {
507
+ if (editor.value) {
508
+ editor.value.destroy()
509
+ }
510
+ })
511
+ </script>
@@ -0,0 +1,185 @@
1
+ <template>
2
+ <div class="expansion-panels vts-d-flex vts-flex-column vts-ga-2">
3
+ <div
4
+ class="expansion-panel vts-overflow-hidden vts-elevation-0 vts-border vts-border-thin vts-bg-surface vts-transition vts-transition-all vts-duration-3 vts-ease-standard"
5
+ :class="{ 'is-collapsed': panel.collapsed, 'is-disabled': panel.disabled, 'is-readonly': panel.readonly }"
6
+ v-for="(panel, index) in panels"
7
+ :key="index"
8
+ >
9
+ <!-- Panel Header -->
10
+ <div
11
+ class="panel-header vts-d-flex vts-justify-space-between vts-align-center vts-pa-3 vts-font-weight-bold vts-bg-surface vts-text vts-transition vts-transition-colors vts-duration-2 vts-ease-standard"
12
+ :class="{
13
+ 'vts-bg-border-light': panel.disabled,
14
+ 'vts-text-secondary': panel.disabled || panel.readonly,
15
+ 'vts-cursor-not-allowed': panel.disabled || panel.readonly,
16
+ 'vts-cursor-pointer': !panel.disabled && !panel.readonly
17
+ }"
18
+ @click="togglePanel(index)"
19
+ >
20
+ <span>{{ panel.title }}</span>
21
+ <span v-if="panel.icon" class="vts-text-h6">{{ panel.icon }}</span>
22
+ </div>
23
+
24
+ <!-- Panel Body -->
25
+ <transition
26
+ @before-enter="beforeEnter"
27
+ @enter="enter"
28
+ @after-enter="afterEnter"
29
+ @before-leave="beforeLeave"
30
+ @leave="leave"
31
+ @after-leave="afterLeave"
32
+ >
33
+ <div
34
+ class="panel-body vts-pa-3 vts-bg-surface-elevated vts-text vts-overflow-hidden vts-transition vts-transition-size-opacity vts-will-change-size-opacity vts-duration-2 vts-ease-standard"
35
+ v-show="!panel.collapsed"
36
+ >
37
+ <slot :name="'panel-' + index">{{ panel.content }}</slot>
38
+ </div>
39
+ </transition>
40
+ </div>
41
+
42
+ <!-- Control Buttons (Collapse All, None) -->
43
+ <div class="control-buttons vts-d-flex vts-ga-3 vts-mt-3 vts-justify-start">
44
+ <button class="vts-rounded vts-pt-2 vts-pb-2 vts-pl-3 vts-pr-3 vts-cursor-pointer vts-border vts-bg-primary vts-on-primary vts-transition vts-transition-colors vts-duration-2 vts-ease-standard" @click="collapseAll(true)">Collapse All</button>
45
+ <button class="vts-rounded vts-pt-2 vts-pb-2 vts-pl-3 vts-pr-3 vts-cursor-pointer vts-border vts-bg-primary vts-on-primary vts-transition vts-transition-colors vts-duration-2 vts-ease-standard" @click="collapseAll(false)">Expand All</button>
46
+ </div>
47
+ </div>
48
+ </template>
49
+
50
+ <script lang="ts">
51
+ import { defineComponent, PropType, ref } from 'vue'
52
+
53
+ type PanelItem = {
54
+ title?: string
55
+ icon?: string
56
+ content?: string
57
+ collapsed?: boolean
58
+ readonly?: boolean
59
+ disabled?: boolean
60
+ }
61
+
62
+ export default defineComponent({
63
+ name: 'ExpansionPanel',
64
+
65
+ props: {
66
+ panelsData: {
67
+ type: Array as PropType<PanelItem[]>,
68
+ default: () => []
69
+ },
70
+ allowMultiple: {
71
+ type: Boolean,
72
+ default: false
73
+ },
74
+ readOnly: {
75
+ type: Boolean,
76
+ default: false
77
+ },
78
+ disable: {
79
+ type: Boolean,
80
+ default: false
81
+ }
82
+ },
83
+
84
+ setup(props) {
85
+ // Track the state of each panel (collapsed or expanded)
86
+ const panels = ref<PanelItem[]>(
87
+ props.panelsData.map(panel => ({
88
+ ...panel,
89
+ collapsed: true,
90
+ readonly: props.readOnly || panel.readonly,
91
+ disabled: props.disable || panel.disabled
92
+ }))
93
+ )
94
+
95
+ // Toggle the collapse state of a specific panel
96
+ const togglePanel = (index: number) => {
97
+ if (panels.value[index].disabled || panels.value[index].readonly) return
98
+
99
+ if (props.allowMultiple) {
100
+ // If multiple panels are allowed, just toggle the current panel
101
+ panels.value[index].collapsed = !panels.value[index].collapsed
102
+ } else {
103
+ // If only one panel can be open, toggle the clicked panel
104
+ // and collapse all others
105
+ panels.value.forEach((panel, idx) => {
106
+ if (idx === index) {
107
+ panel.collapsed = !panel.collapsed // Toggle the clicked panel
108
+ } else {
109
+ panel.collapsed = true // Collapse all other panels
110
+ }
111
+ })
112
+ }
113
+ }
114
+
115
+ // Collapse or expand all panels
116
+ const collapseAll = (collapse: boolean) => {
117
+ panels.value.forEach(panel => {
118
+ panel.collapsed = collapse
119
+ })
120
+ }
121
+
122
+ // height/opacity collapse transition hooks
123
+ const beforeEnter = (el: Element) => {
124
+ const e = el as HTMLElement
125
+ e.style.height = '0px'
126
+ e.style.opacity = '0'
127
+ e.style.overflow = 'hidden'
128
+ }
129
+
130
+ const enter = (el: Element) => {
131
+ const e = el as HTMLElement
132
+ // schedule height/opacity change on next frame to avoid stutter
133
+ requestAnimationFrame(() => {
134
+ e.style.height = `${e.scrollHeight}px`
135
+ e.style.opacity = '1'
136
+ })
137
+ }
138
+
139
+ const afterEnter = (el: Element) => {
140
+ const e = el as HTMLElement
141
+ e.style.height = 'auto'
142
+ e.style.opacity = ''
143
+ e.style.overflow = ''
144
+ }
145
+
146
+ const beforeLeave = (el: Element) => {
147
+ const e = el as HTMLElement
148
+ e.style.height = `${e.scrollHeight}px`
149
+ e.style.opacity = '1'
150
+ e.style.overflow = 'hidden'
151
+ }
152
+
153
+ const leave = (el: Element) => {
154
+ const e = el as HTMLElement
155
+ // schedule collapse on next frame for smooth start
156
+ requestAnimationFrame(() => {
157
+ e.style.height = '0px'
158
+ e.style.opacity = '0'
159
+ })
160
+ }
161
+
162
+ const afterLeave = (el: Element) => {
163
+ const e = el as HTMLElement
164
+ e.style.height = ''
165
+ e.style.opacity = ''
166
+ e.style.overflow = ''
167
+ }
168
+
169
+ return {
170
+ panels,
171
+ togglePanel,
172
+ collapseAll,
173
+ beforeEnter,
174
+ enter,
175
+ afterEnter,
176
+ beforeLeave,
177
+ leave,
178
+ afterLeave
179
+ }
180
+ }
181
+ })
182
+ </script>
183
+
184
+ <style scoped>
185
+ </style>