@conduction/nextcloud-vue 0.1.0-beta.2 → 0.1.0-beta.4

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 (153) hide show
  1. package/README.md +226 -226
  2. package/css/index.css +5 -0
  3. package/dist/nextcloud-vue.cjs.js +60455 -8755
  4. package/dist/nextcloud-vue.cjs.js.map +1 -1
  5. package/dist/nextcloud-vue.css +2062 -528
  6. package/dist/nextcloud-vue.esm.js +60411 -8731
  7. package/dist/nextcloud-vue.esm.js.map +1 -1
  8. package/package.json +75 -61
  9. package/src/components/CnActionsBar/CnActionsBar.vue +235 -225
  10. package/src/components/CnActionsBar/index.js +1 -1
  11. package/src/components/CnAdvancedFormDialog/CnAdvancedFormDialog.vue +579 -0
  12. package/src/components/CnAdvancedFormDialog/CnDataTab.vue +217 -0
  13. package/src/components/CnAdvancedFormDialog/CnMetadataTab.vue +121 -0
  14. package/src/components/CnAdvancedFormDialog/CnPropertiesTab.vue +418 -0
  15. package/src/components/CnAdvancedFormDialog/CnPropertyValueCell.vue +247 -0
  16. package/src/components/CnAdvancedFormDialog/index.js +1 -0
  17. package/src/components/CnCardGrid/CnCardGrid.vue +152 -152
  18. package/src/components/CnCardGrid/index.js +1 -1
  19. package/src/components/CnCellRenderer/CnCellRenderer.vue +132 -132
  20. package/src/components/CnCellRenderer/index.js +1 -1
  21. package/src/components/CnChartWidget/CnChartWidget.vue +320 -0
  22. package/src/components/CnChartWidget/index.js +1 -0
  23. package/src/components/CnConfigurationCard/CnConfigurationCard.vue +77 -77
  24. package/src/components/CnConfigurationCard/index.js +1 -1
  25. package/src/components/CnDashboardGrid/CnDashboardGrid.vue +225 -0
  26. package/src/components/CnDashboardGrid/index.js +1 -0
  27. package/src/components/CnDashboardPage/CnDashboardPage.vue +390 -0
  28. package/src/components/CnDashboardPage/index.js +1 -0
  29. package/src/components/CnDataTable/CnDataTable.vue +349 -349
  30. package/src/components/CnDataTable/index.js +1 -1
  31. package/src/components/CnDetailCard/CnDetailCard.vue +214 -0
  32. package/src/components/CnDetailCard/index.js +1 -0
  33. package/src/components/CnDetailPage/CnDetailPage.vue +281 -0
  34. package/src/components/CnDetailPage/index.js +1 -0
  35. package/src/components/CnFacetSidebar/CnFacetSidebar.vue +231 -223
  36. package/src/components/CnFacetSidebar/index.js +1 -1
  37. package/src/components/CnFilterBar/CnFilterBar.vue +152 -152
  38. package/src/components/CnFilterBar/index.js +1 -1
  39. package/src/components/CnIcon/CnIcon.vue +89 -89
  40. package/src/components/CnIcon/index.js +1 -1
  41. package/src/components/CnIndexPage/CnIndexPage.vue +874 -816
  42. package/src/components/CnIndexPage/index.js +1 -1
  43. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +503 -484
  44. package/src/components/CnIndexSidebar/index.js +1 -1
  45. package/src/components/CnItemCard/CnItemCard.vue +132 -0
  46. package/src/components/CnItemCard/index.js +1 -0
  47. package/src/components/CnKpiGrid/CnKpiGrid.vue +89 -89
  48. package/src/components/CnKpiGrid/index.js +1 -1
  49. package/src/components/CnMassActionBar/CnMassActionBar.vue +160 -160
  50. package/src/components/CnMassActionBar/index.js +1 -1
  51. package/src/components/CnMassCopyDialog/CnMassCopyDialog.vue +320 -320
  52. package/src/components/CnMassCopyDialog/index.js +1 -1
  53. package/src/components/CnMassDeleteDialog/CnMassDeleteDialog.vue +238 -238
  54. package/src/components/CnMassDeleteDialog/index.js +1 -1
  55. package/src/components/CnMassExportDialog/CnMassExportDialog.vue +190 -190
  56. package/src/components/CnMassExportDialog/index.js +1 -1
  57. package/src/components/CnMassImportDialog/CnMassImportDialog.vue +491 -491
  58. package/src/components/CnMassImportDialog/index.js +1 -1
  59. package/src/components/CnNoteCard/CnNoteCard.vue +149 -0
  60. package/src/components/CnNoteCard/index.js +1 -0
  61. package/src/components/CnNotesCard/CnNotesCard.vue +413 -0
  62. package/src/components/CnNotesCard/index.js +1 -0
  63. package/src/components/CnObjectCard/CnObjectCard.vue +292 -292
  64. package/src/components/CnObjectCard/index.js +1 -1
  65. package/src/components/CnObjectSidebar/CnObjectSidebar.vue +876 -0
  66. package/src/components/CnObjectSidebar/index.js +1 -0
  67. package/src/components/CnPageHeader/CnPageHeader.vue +57 -57
  68. package/src/components/CnPageHeader/index.js +1 -1
  69. package/src/components/CnPagination/CnPagination.vue +252 -252
  70. package/src/components/CnPagination/index.js +1 -1
  71. package/src/components/CnRowActions/CnRowActions.vue +73 -73
  72. package/src/components/CnRowActions/index.js +1 -1
  73. package/src/components/CnSchemaFormDialog/CnSchemaConfigurationTab.vue +226 -0
  74. package/src/components/CnSchemaFormDialog/CnSchemaFormDialog.vue +787 -0
  75. package/src/components/CnSchemaFormDialog/CnSchemaPropertiesTab.vue +305 -0
  76. package/src/components/CnSchemaFormDialog/CnSchemaPropertyActions.vue +1398 -0
  77. package/src/components/CnSchemaFormDialog/CnSchemaSecurityTab.vue +236 -0
  78. package/src/components/CnSchemaFormDialog/index.js +1 -0
  79. package/src/components/CnSettingsCard/CnSettingsCard.vue +92 -92
  80. package/src/components/CnSettingsCard/index.js +1 -1
  81. package/src/components/CnSettingsSection/CnSettingsSection.vue +266 -266
  82. package/src/components/CnSettingsSection/index.js +1 -1
  83. package/src/components/CnStatsBlock/CnStatsBlock.vue +420 -366
  84. package/src/components/CnStatsBlock/index.js +1 -1
  85. package/src/components/CnStatusBadge/CnStatusBadge.vue +77 -77
  86. package/src/components/CnStatusBadge/index.js +1 -1
  87. package/src/components/CnTabbedFormDialog/CnTabbedFormDialog.vue +540 -0
  88. package/src/components/CnTabbedFormDialog/index.js +1 -0
  89. package/src/components/CnTasksCard/CnTasksCard.vue +373 -0
  90. package/src/components/CnTasksCard/index.js +1 -0
  91. package/src/components/CnTileWidget/CnTileWidget.vue +159 -0
  92. package/src/components/CnTileWidget/index.js +1 -0
  93. package/src/components/CnTimelineStages/CnTimelineStages.vue +292 -0
  94. package/src/components/CnTimelineStages/index.js +1 -0
  95. package/src/components/CnUserActionMenu/CnUserActionMenu.vue +435 -0
  96. package/src/components/CnUserActionMenu/index.js +1 -0
  97. package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +312 -312
  98. package/src/components/CnVersionInfoCard/index.js +1 -1
  99. package/src/components/CnWidgetRenderer/CnWidgetRenderer.vue +180 -0
  100. package/src/components/CnWidgetRenderer/index.js +1 -0
  101. package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +211 -0
  102. package/src/components/CnWidgetWrapper/index.js +1 -0
  103. package/src/components/index.js +43 -29
  104. package/src/composables/index.js +4 -3
  105. package/src/composables/useDashboardView.js +240 -0
  106. package/src/composables/useDetailView.js +289 -132
  107. package/src/composables/useListView.js +363 -153
  108. package/src/composables/useSubResource.js +142 -142
  109. package/src/constants/metadata.js +30 -30
  110. package/src/css/CnSchemaFormDialog.css +546 -0
  111. package/src/css/__sample_nextcloud_tokens.css +110 -0
  112. package/src/css/actions-bar.css +48 -48
  113. package/src/css/badge.css +51 -51
  114. package/src/css/card.css +128 -128
  115. package/src/css/dashboard.css +70 -0
  116. package/src/css/detail-page.css +168 -0
  117. package/src/css/detail.css +68 -68
  118. package/src/css/index-page.css +44 -32
  119. package/src/css/index-sidebar.css +193 -187
  120. package/src/css/index.css +16 -12
  121. package/src/css/layout.css +90 -90
  122. package/src/css/page-header.css +33 -33
  123. package/src/css/pagination.css +72 -72
  124. package/src/css/table.css +142 -142
  125. package/src/css/timeline-stages.css +218 -0
  126. package/src/css/utilities.css +46 -46
  127. package/src/index.js +72 -53
  128. package/src/store/createSubResourcePlugin.js +135 -135
  129. package/src/store/index.js +3 -3
  130. package/src/store/plugins/auditTrails.js +17 -17
  131. package/src/store/plugins/files.js +250 -186
  132. package/src/store/plugins/index.js +7 -5
  133. package/src/store/plugins/lifecycle.js +180 -180
  134. package/src/store/plugins/relations.js +68 -68
  135. package/src/store/plugins/search.js +372 -0
  136. package/src/store/plugins/selection.js +104 -0
  137. package/src/store/useObjectStore.js +829 -686
  138. package/src/types/auditTrail.d.ts +32 -32
  139. package/src/types/file.d.ts +23 -23
  140. package/src/types/index.d.ts +35 -35
  141. package/src/types/notification.d.ts +36 -36
  142. package/src/types/object.d.ts +40 -40
  143. package/src/types/organisation.d.ts +41 -41
  144. package/src/types/register.d.ts +25 -25
  145. package/src/types/schema.d.ts +39 -39
  146. package/src/types/shared.d.ts +79 -79
  147. package/src/types/source.d.ts +14 -14
  148. package/src/types/task.d.ts +31 -31
  149. package/src/utils/errors.js +96 -96
  150. package/src/utils/headers.js +68 -50
  151. package/src/utils/id.js +13 -0
  152. package/src/utils/index.js +3 -3
  153. package/src/utils/schema.js +422 -419
@@ -0,0 +1,373 @@
1
+ <!--
2
+ CnTasksCard — Inline tasks card for detail pages.
3
+
4
+ Displays up to 5 tasks with status indicators, assignee, and due date.
5
+ Integrates CnUserActionMenu on assignee names. Highlights overdue tasks.
6
+ Wraps CnDetailCard for consistent styling.
7
+ -->
8
+ <template>
9
+ <CnDetailCard :title="titleLabel" :icon="CheckboxMarkedOutline" :collapsible="collapsible">
10
+ <div class="cn-tasks-card">
11
+ <!-- Loading state -->
12
+ <NcLoadingIcon v-if="loading" />
13
+
14
+ <!-- Empty state -->
15
+ <div v-else-if="allTasks.length === 0" class="cn-tasks-card__empty">
16
+ {{ noTasksLabel }}
17
+ </div>
18
+
19
+ <!-- Tasks list -->
20
+ <div v-else class="cn-tasks-card__list">
21
+ <div
22
+ v-for="task in displayedTasks"
23
+ :key="task.id"
24
+ class="cn-tasks-card__task">
25
+ <!-- Status icon -->
26
+ <div class="cn-tasks-card__status-icon">
27
+ <CheckboxMarkedOutline
28
+ v-if="task.status === 'completed'"
29
+ :size="20"
30
+ class="cn-tasks-card__icon--completed" />
31
+ <ProgressClock
32
+ v-else-if="task.status === 'active' || task.status === 'in-process'"
33
+ :size="20"
34
+ class="cn-tasks-card__icon--active" />
35
+ <CloseCircleOutline
36
+ v-else-if="task.status === 'terminated'"
37
+ :size="20"
38
+ class="cn-tasks-card__icon--terminated" />
39
+ <CheckboxBlankOutline
40
+ v-else
41
+ :size="20"
42
+ class="cn-tasks-card__icon--available" />
43
+ </div>
44
+
45
+ <!-- Task content -->
46
+ <div class="cn-tasks-card__content">
47
+ <span class="cn-tasks-card__title">{{ task.title || task.name }}</span>
48
+ <div class="cn-tasks-card__meta">
49
+ <!-- Assignee -->
50
+ <span v-if="hasAssignee(task)" class="cn-tasks-card__assignee">
51
+ <CnUserActionMenu
52
+ v-if="!isCurrentUser(task.assignee)"
53
+ :user-id="task.assignee"
54
+ :display-name="task.assignee">
55
+ <span class="cn-tasks-card__assignee-name">{{ task.assignee }}</span>
56
+ </CnUserActionMenu>
57
+ <span v-else class="cn-tasks-card__assignee-name cn-tasks-card__assignee-name--self">
58
+ {{ task.assignee }}
59
+ </span>
60
+ </span>
61
+ <span v-else class="cn-tasks-card__unassigned">
62
+ {{ unassignedLabel }}
63
+ </span>
64
+
65
+ <!-- Due date -->
66
+ <span
67
+ v-if="task.dueDate"
68
+ class="cn-tasks-card__due-date"
69
+ :class="{ 'cn-tasks-card__due-date--overdue': isOverdue(task) }">
70
+ {{ formatDate(task.dueDate) }}
71
+ </span>
72
+ </div>
73
+ </div>
74
+ </div>
75
+ </div>
76
+ </div>
77
+
78
+ <!-- Footer: "Show all" link -->
79
+ <template v-if="allTasks.length > maxDisplay" #footer>
80
+ <button
81
+ class="cn-tasks-card__show-all"
82
+ @click="$emit('show-all')">
83
+ {{ showAllLabel }} ({{ allTasks.length }})
84
+ </button>
85
+ </template>
86
+ </CnDetailCard>
87
+ </template>
88
+
89
+ <script>
90
+ import { NcLoadingIcon } from '@nextcloud/vue'
91
+ import CheckboxMarkedOutline from 'vue-material-design-icons/CheckboxMarkedOutline.vue'
92
+ import CheckboxBlankOutline from 'vue-material-design-icons/CheckboxBlankOutline.vue'
93
+ import ProgressClock from 'vue-material-design-icons/ProgressClock.vue'
94
+ import CloseCircleOutline from 'vue-material-design-icons/CloseCircleOutline.vue'
95
+
96
+ import CnDetailCard from '../CnDetailCard/CnDetailCard.vue'
97
+ import CnUserActionMenu from '../CnUserActionMenu/CnUserActionMenu.vue'
98
+ import { buildHeaders } from '../../utils/index.js'
99
+
100
+ /**
101
+ * CnTasksCard — Inline tasks widget for detail pages.
102
+ *
103
+ * Shows up to 5 tasks sorted by due date with status indicators.
104
+ * Highlights overdue tasks and integrates CnUserActionMenu on assignees.
105
+ *
106
+ * @example Basic usage
107
+ * <CnTasksCard
108
+ * register-id="uuid-register"
109
+ * schema-id="uuid-schema"
110
+ * object-id="uuid-object" />
111
+ *
112
+ * @example With sidebar sync
113
+ * <CnTasksCard
114
+ * register-id="reg"
115
+ * schema-id="schema"
116
+ * object-id="obj"
117
+ * @show-all="openSidebarTasksTab" />
118
+ */
119
+ export default {
120
+ name: 'CnTasksCard',
121
+
122
+ components: {
123
+ CnDetailCard,
124
+ CnUserActionMenu,
125
+ NcLoadingIcon,
126
+ CheckboxMarkedOutline,
127
+ CheckboxBlankOutline,
128
+ ProgressClock,
129
+ CloseCircleOutline,
130
+ },
131
+
132
+ props: {
133
+ /** OpenRegister register ID */
134
+ registerId: {
135
+ type: String,
136
+ required: true,
137
+ },
138
+ /** OpenRegister schema ID */
139
+ schemaId: {
140
+ type: String,
141
+ required: true,
142
+ },
143
+ /** Object UUID */
144
+ objectId: {
145
+ type: String,
146
+ required: true,
147
+ },
148
+ /** Base API URL for OpenRegister */
149
+ apiBase: {
150
+ type: String,
151
+ default: '/apps/openregister/api',
152
+ },
153
+ /** Maximum number of tasks to display */
154
+ maxDisplay: {
155
+ type: Number,
156
+ default: 5,
157
+ },
158
+ /** Whether the card is collapsible */
159
+ collapsible: {
160
+ type: Boolean,
161
+ default: false,
162
+ },
163
+
164
+ // --- Pre-translated labels ---
165
+ titleLabel: { type: String, default: 'Tasks' },
166
+ noTasksLabel: { type: String, default: 'No tasks' },
167
+ showAllLabel: { type: String, default: 'Show all' },
168
+ unassignedLabel: { type: String, default: 'Unassigned' },
169
+ },
170
+
171
+ emits: ['show-all'],
172
+
173
+ data() {
174
+ return {
175
+ CheckboxMarkedOutline,
176
+ allTasks: [],
177
+ loading: false,
178
+ }
179
+ },
180
+
181
+ computed: {
182
+ displayedTasks() {
183
+ // Sort by due date (soonest first), then limit
184
+ const sorted = [...this.allTasks].sort((a, b) => {
185
+ const dateA = a.dueDate ? new Date(a.dueDate) : new Date('9999-12-31')
186
+ const dateB = b.dueDate ? new Date(b.dueDate) : new Date('9999-12-31')
187
+ return dateA - dateB
188
+ })
189
+ return sorted.slice(0, this.maxDisplay)
190
+ },
191
+ },
192
+
193
+ watch: {
194
+ objectId: {
195
+ immediate: true,
196
+ handler(newId) {
197
+ if (newId && this.registerId && this.schemaId) {
198
+ this.fetchTasks()
199
+ }
200
+ },
201
+ },
202
+ },
203
+
204
+ methods: {
205
+ hasAssignee(task) {
206
+ return task.assignee && task.assignee.trim() !== ''
207
+ },
208
+
209
+ isCurrentUser(userId) {
210
+ const currentUser = typeof OC !== 'undefined' ? OC?.currentUser : null
211
+ return userId === currentUser
212
+ },
213
+
214
+ isOverdue(task) {
215
+ if (!task.dueDate || task.status === 'completed') return false
216
+ try {
217
+ return new Date(task.dueDate) < new Date()
218
+ } catch {
219
+ return false
220
+ }
221
+ },
222
+
223
+ async fetchTasks() {
224
+ if (!this.registerId || !this.schemaId || !this.objectId) return
225
+ this.loading = true
226
+ try {
227
+ const url = `${this.apiBase}/objects/${this.registerId}/${this.schemaId}/${this.objectId}/tasks`
228
+ const response = await fetch(url, { headers: buildHeaders() })
229
+ if (response.ok) {
230
+ const data = await response.json()
231
+ this.allTasks = data.results || data || []
232
+ }
233
+ } catch (err) {
234
+ console.error('CnTasksCard: Failed to fetch tasks', err)
235
+ } finally {
236
+ this.loading = false
237
+ }
238
+ },
239
+
240
+ formatDate(dateStr) {
241
+ if (!dateStr) return ''
242
+ try {
243
+ return new Date(dateStr).toLocaleDateString(undefined, {
244
+ year: 'numeric',
245
+ month: 'short',
246
+ day: 'numeric',
247
+ })
248
+ } catch {
249
+ return dateStr
250
+ }
251
+ },
252
+ },
253
+ }
254
+ </script>
255
+
256
+ <style scoped>
257
+ .cn-tasks-card__empty {
258
+ text-align: center;
259
+ padding: 16px 12px;
260
+ color: var(--color-text-maxcontrast);
261
+ font-size: 13px;
262
+ }
263
+
264
+ .cn-tasks-card__list {
265
+ display: flex;
266
+ flex-direction: column;
267
+ }
268
+
269
+ .cn-tasks-card__task {
270
+ display: flex;
271
+ align-items: flex-start;
272
+ gap: 10px;
273
+ padding: 8px 0;
274
+ border-bottom: 1px solid var(--color-border);
275
+ }
276
+
277
+ .cn-tasks-card__task:last-child {
278
+ border-bottom: none;
279
+ }
280
+
281
+ .cn-tasks-card__status-icon {
282
+ flex-shrink: 0;
283
+ padding-top: 1px;
284
+ }
285
+
286
+ .cn-tasks-card__icon--completed {
287
+ color: var(--color-success);
288
+ }
289
+
290
+ .cn-tasks-card__icon--active {
291
+ color: var(--color-primary-element);
292
+ }
293
+
294
+ .cn-tasks-card__icon--terminated {
295
+ color: var(--color-error);
296
+ }
297
+
298
+ .cn-tasks-card__icon--available {
299
+ color: var(--color-text-maxcontrast);
300
+ }
301
+
302
+ .cn-tasks-card__content {
303
+ flex: 1;
304
+ min-width: 0;
305
+ }
306
+
307
+ .cn-tasks-card__title {
308
+ display: block;
309
+ font-size: 13px;
310
+ font-weight: 500;
311
+ white-space: nowrap;
312
+ overflow: hidden;
313
+ text-overflow: ellipsis;
314
+ }
315
+
316
+ .cn-tasks-card__meta {
317
+ display: flex;
318
+ align-items: center;
319
+ gap: 8px;
320
+ margin-top: 2px;
321
+ font-size: 12px;
322
+ color: var(--color-text-maxcontrast);
323
+ }
324
+
325
+ .cn-tasks-card__assignee-name {
326
+ color: var(--color-primary-element);
327
+ font-weight: 500;
328
+ cursor: pointer;
329
+ }
330
+
331
+ .cn-tasks-card__assignee-name:hover {
332
+ text-decoration: underline;
333
+ }
334
+
335
+ .cn-tasks-card__assignee-name--self {
336
+ color: var(--color-text-maxcontrast);
337
+ cursor: default;
338
+ }
339
+
340
+ .cn-tasks-card__assignee-name--self:hover {
341
+ text-decoration: none;
342
+ }
343
+
344
+ .cn-tasks-card__unassigned {
345
+ font-style: italic;
346
+ color: var(--color-text-maxcontrast);
347
+ }
348
+
349
+ .cn-tasks-card__due-date {
350
+ white-space: nowrap;
351
+ }
352
+
353
+ .cn-tasks-card__due-date--overdue {
354
+ color: var(--color-error);
355
+ font-weight: 500;
356
+ }
357
+
358
+ .cn-tasks-card__show-all {
359
+ background: none;
360
+ border: none;
361
+ color: var(--color-primary-element);
362
+ font-size: 13px;
363
+ font-weight: 500;
364
+ cursor: pointer;
365
+ padding: 0;
366
+ width: 100%;
367
+ text-align: center;
368
+ }
369
+
370
+ .cn-tasks-card__show-all:hover {
371
+ text-decoration: underline;
372
+ }
373
+ </style>
@@ -0,0 +1 @@
1
+ export { default as CnTasksCard } from './CnTasksCard.vue'
@@ -0,0 +1,159 @@
1
+ <!--
2
+ CnTileWidget — Quick-access tile with icon and link.
3
+
4
+ A simple, colorful tile for app shortcuts or external links.
5
+ Supports SVG paths, CSS icon classes, image URLs, and emoji icons.
6
+ -->
7
+ <template>
8
+ <div
9
+ v-if="tile"
10
+ class="cn-tile-widget"
11
+ :style="tileStyles">
12
+ <a
13
+ :href="tileUrl"
14
+ class="cn-tile-widget__link"
15
+ :target="tile.linkType === 'url' ? '_blank' : '_self'"
16
+ rel="noopener noreferrer">
17
+ <!-- SVG icon -->
18
+ <svg
19
+ v-if="tile.iconType === 'svg'"
20
+ class="cn-tile-widget__icon cn-tile-widget__icon--svg"
21
+ :style="{ fill: tile.textColor || '#ffffff' }"
22
+ viewBox="0 0 24 24">
23
+ <path :d="tile.icon" />
24
+ </svg>
25
+ <!-- Other icon types -->
26
+ <div v-else class="cn-tile-widget__icon">
27
+ <span v-if="tile.iconType === 'class'" :class="['icon', tile.icon]" />
28
+ <img v-else-if="tile.iconType === 'url'" :src="tile.icon" alt="">
29
+ <span v-else-if="tile.iconType === 'emoji'" class="cn-tile-widget__emoji">{{ tile.icon }}</span>
30
+ </div>
31
+ <div class="cn-tile-widget__title" :style="{ color: tile.textColor || '#ffffff' }">
32
+ {{ tile.title }}
33
+ </div>
34
+ </a>
35
+ </div>
36
+ </template>
37
+
38
+ <script>
39
+ import { generateUrl } from '@nextcloud/router'
40
+
41
+ /**
42
+ * CnTileWidget — Quick-access tile with icon and link.
43
+ *
44
+ * @example
45
+ * <CnTileWidget :tile="{
46
+ * title: 'Files',
47
+ * icon: 'M12,2C6.48,...',
48
+ * iconType: 'svg',
49
+ * backgroundColor: '#0082c9',
50
+ * textColor: '#ffffff',
51
+ * linkType: 'app',
52
+ * linkValue: 'files',
53
+ * }" />
54
+ */
55
+ export default {
56
+ name: 'CnTileWidget',
57
+
58
+ props: {
59
+ /**
60
+ * Tile configuration object.
61
+ * @type {{ title: string, icon: string, iconType: 'svg'|'class'|'url'|'emoji', backgroundColor?: string, textColor?: string, linkType: 'app'|'url', linkValue: string }}
62
+ */
63
+ tile: {
64
+ type: Object,
65
+ required: true,
66
+ },
67
+ },
68
+
69
+ computed: {
70
+ tileUrl() {
71
+ if (this.tile.linkType === 'app') {
72
+ return generateUrl('/apps/' + this.tile.linkValue)
73
+ }
74
+ return this.tile.linkValue || '#'
75
+ },
76
+
77
+ tileStyles() {
78
+ return {
79
+ '--cn-tile-bg': this.tile.backgroundColor || '#0082c9',
80
+ '--cn-tile-text': this.tile.textColor || '#ffffff',
81
+ }
82
+ },
83
+ },
84
+ }
85
+ </script>
86
+
87
+ <style scoped>
88
+ .cn-tile-widget {
89
+ height: 100%;
90
+ width: 100%;
91
+ position: absolute;
92
+ top: 0;
93
+ left: 0;
94
+ overflow: hidden;
95
+ background-color: var(--cn-tile-bg);
96
+ }
97
+
98
+ .cn-tile-widget__link {
99
+ display: flex;
100
+ flex-direction: column;
101
+ align-items: center;
102
+ justify-content: center;
103
+ height: 100%;
104
+ width: 100%;
105
+ text-decoration: none;
106
+ padding: 20px;
107
+ gap: 12px;
108
+ transition: transform 0.2s ease, opacity 0.2s ease;
109
+ background-color: var(--cn-tile-bg);
110
+ color: var(--cn-tile-text);
111
+ }
112
+
113
+ .cn-tile-widget__link:hover {
114
+ transform: scale(1.02);
115
+ opacity: 0.95;
116
+ }
117
+
118
+ .cn-tile-widget__icon {
119
+ font-size: 48px;
120
+ width: 48px;
121
+ height: 48px;
122
+ display: flex;
123
+ align-items: center;
124
+ justify-content: center;
125
+ flex-shrink: 0;
126
+ }
127
+
128
+ .cn-tile-widget__icon--svg {
129
+ width: 48px;
130
+ height: 48px;
131
+ }
132
+
133
+ .cn-tile-widget__icon span.icon {
134
+ display: inline-block;
135
+ width: 48px;
136
+ height: 48px;
137
+ background-size: 48px;
138
+ filter: brightness(0) invert(1);
139
+ }
140
+
141
+ .cn-tile-widget__icon img {
142
+ width: 100%;
143
+ height: 100%;
144
+ object-fit: contain;
145
+ }
146
+
147
+ .cn-tile-widget__emoji {
148
+ font-size: 48px;
149
+ }
150
+
151
+ .cn-tile-widget__title {
152
+ font-size: 16px;
153
+ font-weight: 700;
154
+ text-align: center;
155
+ word-break: break-word;
156
+ line-height: 1.3;
157
+ color: var(--cn-tile-text);
158
+ }
159
+ </style>
@@ -0,0 +1 @@
1
+ export { default as CnTileWidget } from './CnTileWidget.vue'