@asd20/ui-next 2.0.29 → 2.2.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ # [2.2.0](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.1.0...ui-next-v2.2.0) (2026-04-07)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * fix plublishing contract regressions ([9ce287b](https://github.com/academydistrict20/asd20-ui-next/commit/9ce287b8f5719db6fdf1467fe4423fc60bbbc19c))
9
+
10
+
11
+ ### Features
12
+
13
+ * add pagination to file list template ([cf9829c](https://github.com/academydistrict20/asd20-ui-next/commit/cf9829cd4776e2fd0c24cee52092397cdc6a16e8))
14
+
15
+ # [2.1.0](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.0.29...ui-next-v2.1.0) (2026-04-06)
16
+
17
+
18
+ ### Features
19
+
20
+ * revise file list template and componets to add search ([661b500](https://github.com/academydistrict20/asd20-ui-next/commit/661b500259a263285b37adf497156417fe983430))
21
+
3
22
  ## [2.0.29](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.0.28...ui-next-v2.0.29) (2026-04-06)
4
23
 
5
24
  ## [2.0.28](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.0.27...ui-next-v2.0.28) (2026-04-03)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asd20/ui-next",
3
- "version": "2.0.29",
3
+ "version": "2.2.0",
4
4
  "private": false,
5
5
  "description": "ASD20 UI component library for Vue 3.",
6
6
  "license": "MIT",
@@ -118,6 +118,27 @@ import {
118
118
  shouldShowExternalIcon,
119
119
  } from '../../../helpers/linkPolicy'
120
120
 
121
+ function resolveRouter(vm) {
122
+ const internal = vm && vm.$
123
+ const context = internal && internal.ctx
124
+ if (context && context.$router) {
125
+ return context.$router
126
+ }
127
+
128
+ const parentContext = internal && internal.parent && internal.parent.ctx
129
+ if (parentContext && parentContext.$router) {
130
+ return parentContext.$router
131
+ }
132
+
133
+ return (
134
+ internal &&
135
+ internal.appContext &&
136
+ internal.appContext.config &&
137
+ internal.appContext.config.globalProperties &&
138
+ internal.appContext.config.globalProperties.$router
139
+ )
140
+ }
141
+
121
142
  export default {
122
143
  name: 'Asd20ListItem',
123
144
 
@@ -183,7 +204,8 @@ export default {
183
204
  return this.link || this.to
184
205
  },
185
206
  usesRouterNavigation() {
186
- if (!this.$router || !this.to) return false
207
+ const router = resolveRouter(this)
208
+ if (!router || !this.to) return false
187
209
  if (typeof this.to === 'string') return this.to.startsWith('/')
188
210
  return typeof this.to === 'object'
189
211
  },
@@ -206,9 +228,11 @@ export default {
206
228
 
207
229
  methods: {
208
230
  onClick(e) {
209
- if (this.usesRouterNavigation) {
231
+ const router = resolveRouter(this)
232
+
233
+ if (this.usesRouterNavigation && router && typeof router.push === 'function') {
210
234
  e.preventDefault()
211
- this.$router.push(this.to)
235
+ router.push(this.to)
212
236
  }
213
237
  this.$emit('click', e)
214
238
  },
@@ -28,10 +28,10 @@
28
28
  @input="input"
29
29
  />
30
30
  <div
31
- v-if="extra || !!$slots.default"
31
+ v-if="extra"
32
32
  class="asd20-search-field__extra"
33
33
  >
34
- {{ extra }}<slot />
34
+ {{ extra }}
35
35
  </div>
36
36
  </div>
37
37
  </template>
@@ -141,35 +141,14 @@ export default {
141
141
  opacity: 1;
142
142
  color: var(--color__primary-t50);
143
143
  }
144
- &:focus + .asd20-search-field__extra {
145
- background: var(--website-page__alternate-background-t40);
146
- }
147
144
  }
148
145
 
149
- .asd20-search-field__extra {
150
- transition: background asd20-speed(1) ease;
151
- align-self: stretch;
152
- display: flex;
153
- align-items: center;
154
- justify-content: center;
155
- font-size: 0.875rem;
146
+ &__extra {
147
+ flex-shrink: 0;
148
+ padding: 0 space(0.5);
149
+ color: var(--color__primary);
150
+ font-size: space(0.5);
156
151
  white-space: nowrap;
157
- color: var(--color__primary-t50);
158
- box-shadow: -1px 0 0 0 var(--color__tertiary);
159
- }
160
- &--large {
161
- & > .asd20-icon {
162
- width: space(1.5);
163
- height: space(1.5);
164
- margin: 0;
165
- }
166
- input {
167
- margin-left: space(0);
168
- font-size: 2rem;
169
- line-height: 1.5;
170
- padding: 0 space(0.25) 0 space(0.5);
171
- height: space(3);
172
- }
173
152
  }
174
153
  }
175
154
  </style>
@@ -10,6 +10,63 @@
10
10
  :icon="icon"
11
11
  :column-width="640"
12
12
  >
13
+ <template #header>
14
+ <div
15
+ v-if="searchable"
16
+ class="asd20-file-list__toolbar"
17
+ >
18
+ <asd20-search-field
19
+ v-model="searchQuery"
20
+ class="asd20-file-list__search"
21
+ :placeholder="searchPlaceholder"
22
+ :extra="resultSummary"
23
+ :id-tag="searchIdTag"
24
+ />
25
+ </div>
26
+ </template>
27
+ <template #after-header>
28
+ <div
29
+ v-if="showTopPagination"
30
+ class="asd20-file-list__pagination"
31
+ >
32
+ <p class="asd20-file-list__pagination-summary">
33
+ {{ paginationSummary }}
34
+ </p>
35
+ <div
36
+ v-if="paginate && totalPages > 1"
37
+ class="asd20-file-list__pagination-actions"
38
+ >
39
+ <asd20-button
40
+ class="asd20-file-list__pagination-button"
41
+ icon="chevron"
42
+ label="Prev"
43
+ :icon-angle="-90"
44
+ size="md"
45
+ horizontal
46
+ transparent
47
+ centered
48
+ opposite
49
+ :disabled="currentPage === 1"
50
+ @click="prevPage"
51
+ />
52
+ <p class="asd20-file-list__pagination-page">
53
+ Page {{ currentPage }} of {{ totalPages }}
54
+ </p>
55
+ <asd20-button
56
+ class="asd20-file-list__pagination-button"
57
+ icon="chevron"
58
+ label="Next"
59
+ :icon-angle="90"
60
+ size="md"
61
+ horizontal
62
+ transparent
63
+ centered
64
+ :disabled="currentPage === totalPages"
65
+ @click="nextPage"
66
+ />
67
+ </div>
68
+ </div>
69
+ </template>
13
70
  <div
14
71
  v-for="category in categorizedFileItems"
15
72
  :key="category.name"
@@ -22,6 +79,36 @@
22
79
  v-bind="item"
23
80
  />
24
81
  </div>
82
+ <div
83
+ v-if="!categorizedFileItems.length"
84
+ class="asd20-file-list__empty"
85
+ >
86
+ {{ emptyStateMessage }}
87
+ </div>
88
+ <template #footer>
89
+ <div
90
+ v-if="shouldShowFooter"
91
+ class="asd20-file-list__footer"
92
+ >
93
+ <div class="asd20-file-list__footer-summary">
94
+ {{ footerSummary }}
95
+ </div>
96
+ <div class="asd20-file-list__footer-actions">
97
+ <asd20-button
98
+ v-if="canShowMore"
99
+ :label="showMoreLabel"
100
+ text-only
101
+ @click="showMore"
102
+ />
103
+ <asd20-button
104
+ v-if="canShowAll"
105
+ label="Show all"
106
+ text-only
107
+ @click="showAll"
108
+ />
109
+ </div>
110
+ </div>
111
+ </template>
25
112
  </asd20-list>
26
113
  </template>
27
114
 
@@ -30,10 +117,27 @@ import Asd20List from '../../../components/organisms/Asd20WidgetList'
30
117
  import mapFilesToListItems from '../../../helpers/mapFilesToListItems'
31
118
  import Asd20ListItem from '../../../components/molecules/Asd20ListItem'
32
119
  import Asd20ListCategory from '../../../components/molecules/Asd20ListCategory'
120
+ import Asd20Button from '../../../components/atoms/Asd20Button'
121
+ import Asd20SearchField from '../../../components/molecules/Asd20SearchField'
122
+
123
+ function normalizeSearchText(value = '') {
124
+ return String(value || '')
125
+ .toLowerCase()
126
+ .replace(/\.[a-z0-9]+$/i, '')
127
+ .replace(/[^a-z0-9]+/g, ' ')
128
+ .trim()
129
+ }
130
+
33
131
  export default {
34
132
  name: 'Asd20FileList',
35
133
 
36
- components: { Asd20List, Asd20ListItem, Asd20ListCategory },
134
+ components: {
135
+ Asd20Button,
136
+ Asd20List,
137
+ Asd20ListItem,
138
+ Asd20ListCategory,
139
+ Asd20SearchField,
140
+ },
37
141
 
38
142
  props: {
39
143
  files: { type: Array, default: () => [] },
@@ -46,10 +150,19 @@ export default {
46
150
  groupByTag: { type: Boolean, default: false },
47
151
  groupByDate: { type: Boolean, default: false },
48
152
  maxHeight: { type: String, default: '320px' },
153
+ searchable: { type: Boolean, default: false },
154
+ searchPlaceholder: { type: String, default: 'Search files' },
155
+ paginate: { type: Boolean, default: false },
156
+ pageSize: { type: Number, default: 50 },
157
+ initialVisibleCount: { type: Number, default: 0 },
158
+ visibleCountStep: { type: Number, default: 50 },
49
159
  },
50
160
 
51
161
  data: () => ({
52
162
  loadedFiles: [],
163
+ searchQuery: '',
164
+ currentPage: 1,
165
+ visibleCount: 0,
53
166
  }),
54
167
 
55
168
  computed: {
@@ -57,6 +170,180 @@ export default {
57
170
  return (this.files || []).concat(this.loadedFiles)
58
171
  },
59
172
 
173
+ normalizedSearchQuery() {
174
+ return normalizeSearchText(this.searchQuery)
175
+ },
176
+
177
+ searchTerms() {
178
+ return this.normalizedSearchQuery
179
+ ? this.normalizedSearchQuery.split(/\s+/).filter(Boolean)
180
+ : []
181
+ },
182
+
183
+ filteredFiles() {
184
+ if (!this.searchTerms.length) return this.computedFiles
185
+
186
+ return this.computedFiles.filter(file => this.matchesSearch(file))
187
+ },
188
+
189
+ totalFilesCount() {
190
+ return this.computedFiles.length
191
+ },
192
+
193
+ filteredFilesCount() {
194
+ return this.filteredFiles.length
195
+ },
196
+
197
+ resolvedPageSize() {
198
+ return this.pageSize > 0 ? this.pageSize : 50
199
+ },
200
+
201
+ totalPages() {
202
+ if (!this.paginate || !this.filteredFilesCount) return 1
203
+ return Math.max(1, Math.ceil(this.filteredFilesCount / this.resolvedPageSize))
204
+ },
205
+
206
+ paginationStart() {
207
+ if (!this.filteredFilesCount) return 0
208
+ return (this.currentPage - 1) * this.resolvedPageSize + 1
209
+ },
210
+
211
+ paginationEnd() {
212
+ if (!this.filteredFilesCount) return 0
213
+ return Math.min(this.currentPage * this.resolvedPageSize, this.filteredFilesCount)
214
+ },
215
+
216
+ hasActiveSearch() {
217
+ return this.searchTerms.length > 0
218
+ },
219
+
220
+ effectiveVisibleCount() {
221
+ if (!this.initialVisibleCount) return this.filteredFilesCount
222
+ if (!this.visibleCount) return this.initialVisibleCount
223
+
224
+ return this.visibleCount
225
+ },
226
+
227
+ visibleFiles() {
228
+ if (!this.initialVisibleCount) return this.filteredFiles
229
+
230
+ return this.filteredFiles.slice(0, this.effectiveVisibleCount)
231
+ },
232
+
233
+ paginatedFiles() {
234
+ if (!this.paginate) return this.visibleFiles
235
+
236
+ const startIndex = (this.currentPage - 1) * this.resolvedPageSize
237
+ return this.filteredFiles.slice(startIndex, startIndex + this.resolvedPageSize)
238
+ },
239
+
240
+ canShowMore() {
241
+ return (
242
+ !!this.initialVisibleCount &&
243
+ this.visibleFiles.length < this.filteredFilesCount
244
+ )
245
+ },
246
+
247
+ canShowAll() {
248
+ return this.canShowMore && this.filteredFilesCount > this.nextVisibleCount
249
+ },
250
+
251
+ nextVisibleCount() {
252
+ return Math.min(
253
+ this.filteredFilesCount,
254
+ this.effectiveVisibleCount + this.resolvedVisibleCountStep
255
+ )
256
+ },
257
+
258
+ resolvedVisibleCountStep() {
259
+ return this.visibleCountStep > 0
260
+ ? this.visibleCountStep
261
+ : this.initialVisibleCount || 50
262
+ },
263
+
264
+ resultSummary() {
265
+ if (!this.searchable) return ''
266
+ if (!this.totalFilesCount) return '0 files'
267
+ if (this.hasActiveSearch) {
268
+ return `${this.filteredFilesCount} of ${this.totalFilesCount}`
269
+ }
270
+
271
+ return `${this.totalFilesCount} files`
272
+ },
273
+
274
+ footerSummary() {
275
+ if (!this.totalFilesCount) return ''
276
+
277
+ if (this.canShowMore) {
278
+ return `Showing ${this.visibleFiles.length} of ${this.filteredFilesCount} files`
279
+ }
280
+
281
+ if (this.hasActiveSearch) {
282
+ return `${this.filteredFilesCount} matching files`
283
+ }
284
+
285
+ return `Showing all ${this.filteredFilesCount} files`
286
+ },
287
+
288
+ hasLargeResultSet() {
289
+ return (
290
+ !!this.initialVisibleCount &&
291
+ this.filteredFilesCount > this.initialVisibleCount
292
+ )
293
+ },
294
+
295
+ shouldShowFooter() {
296
+ return (
297
+ !this.paginate &&
298
+ this.totalFilesCount > 0 &&
299
+ (this.canShowMore || this.hasActiveSearch || this.hasLargeResultSet)
300
+ )
301
+ },
302
+
303
+ showMoreLabel() {
304
+ const increment = this.nextVisibleCount - this.visibleFiles.length
305
+ return `Show ${increment} more`
306
+ },
307
+
308
+ searchIdTag() {
309
+ return normalizeSearchText(this.title).replace(/\s+/g, '-')
310
+ },
311
+
312
+ emptyStateMessage() {
313
+ if (!this.totalFilesCount) return 'No files available.'
314
+ if (this.hasActiveSearch) return 'No files match your search.'
315
+
316
+ return 'No files available.'
317
+ },
318
+
319
+ showTopPagination() {
320
+ return this.paginate || this.hasActiveSearch || !!this.initialVisibleCount
321
+ },
322
+
323
+ paginationSummary() {
324
+ if (!this.filteredFilesCount) {
325
+ return this.hasActiveSearch ? 'Showing 0 matching files.' : 'Showing 0 files.'
326
+ }
327
+
328
+ if (this.paginate) {
329
+ if (this.hasActiveSearch) {
330
+ return `Showing ${this.paginationStart}-${this.paginationEnd} of ${this.filteredFilesCount} matching files.`
331
+ }
332
+
333
+ return `Showing ${this.paginationStart}-${this.paginationEnd} of ${this.filteredFilesCount} files.`
334
+ }
335
+
336
+ if (this.canShowMore) {
337
+ return `Showing ${this.visibleFiles.length} of ${this.filteredFilesCount} files.`
338
+ }
339
+
340
+ if (this.hasActiveSearch) {
341
+ return `Showing ${this.filteredFilesCount} matching files.`
342
+ }
343
+
344
+ return `Showing ${this.filteredFilesCount} files.`
345
+ },
346
+
60
347
  categorizedFileItems() {
61
348
  if (this.groupByDate) {
62
349
  return this.fileItemsGroupedByDate
@@ -71,7 +358,7 @@ export default {
71
358
  },
72
359
 
73
360
  fileItemsGroupedByCategory() {
74
- return this.computedFiles
361
+ return this.paginatedFiles
75
362
  .reduce((a, c) => {
76
363
  let categories =
77
364
  c.categories && c.categories.length > 0
@@ -87,7 +374,7 @@ export default {
87
374
  return {
88
375
  name: c,
89
376
  items: mapFilesToListItems(
90
- this.computedFiles
377
+ this.paginatedFiles
91
378
  .map(f => ({
92
379
  ...f,
93
380
  categories:
@@ -112,7 +399,7 @@ export default {
112
399
  },
113
400
 
114
401
  fileItemsGroupedByCategoryDescending() {
115
- return this.computedFiles
402
+ return this.paginatedFiles
116
403
  .reduce((a, c) => {
117
404
  let categories =
118
405
  c.categories && c.categories.length > 0
@@ -128,7 +415,7 @@ export default {
128
415
  return {
129
416
  name: c,
130
417
  items: mapFilesToListItems(
131
- this.computedFiles
418
+ this.paginatedFiles
132
419
  .map(f => ({
133
420
  ...f,
134
421
  categories:
@@ -153,7 +440,7 @@ export default {
153
440
  },
154
441
 
155
442
  fileItemsGroupedByTag() {
156
- return this.computedFiles
443
+ return this.paginatedFiles
157
444
  .reduce((a, t) => {
158
445
  let tags = t.tags && t.tags.length > 0 ? t.tags : ['Untagged']
159
446
  for (const tag of tags) {
@@ -166,7 +453,7 @@ export default {
166
453
  return {
167
454
  name: t,
168
455
  items: mapFilesToListItems(
169
- this.computedFiles
456
+ this.paginatedFiles
170
457
  .map(f => ({
171
458
  ...f,
172
459
  tags: f.tags && f.tags.length > 0 ? f.tags : ['Untagged'],
@@ -188,7 +475,7 @@ export default {
188
475
  },
189
476
 
190
477
  fileItemsGroupedByDate() {
191
- return this.computedFiles
478
+ return this.paginatedFiles
192
479
  .reduce((a, c) => {
193
480
  let date = new Date(c.lastModifiedDateTime).toLocaleDateString()
194
481
  if (a.indexOf(date) === -1) a.push(date)
@@ -199,7 +486,7 @@ export default {
199
486
  name: d,
200
487
  unix: new Date(d).valueOf(),
201
488
  items: mapFilesToListItems(
202
- this.computedFiles.filter(
489
+ this.paginatedFiles.filter(
203
490
  f => new Date(f.lastModifiedDateTime).toLocaleDateString() === d
204
491
  )
205
492
  )
@@ -221,7 +508,7 @@ export default {
221
508
  fileItemsGroupedByOwner() {
222
509
  return [
223
510
  {
224
- items: mapFilesToListItems(this.computedFiles).map(t => ({
511
+ items: mapFilesToListItems(this.paginatedFiles).map(t => ({
225
512
  ...t,
226
513
  bordered: false,
227
514
  alignTop: false,
@@ -236,6 +523,8 @@ export default {
236
523
  watch: {
237
524
  files: {
238
525
  handler() {
526
+ this.currentPage = 1
527
+ this.resetVisibleCount()
239
528
  this.$nextTick(() => {
240
529
  // console.log('Change in Files detected:', this.files)
241
530
  this.checkForOverflow() // Ensure overflow check happens after file changes
@@ -245,13 +534,56 @@ export default {
245
534
  deep: true,
246
535
  immediate: true,
247
536
  },
537
+ searchQuery() {
538
+ this.currentPage = 1
539
+ this.resetVisibleCount()
540
+ },
248
541
  },
249
542
 
250
543
  mounted() {
544
+ this.currentPage = 1
545
+ this.resetVisibleCount()
251
546
  if (this.url) this.loadFiles()
252
547
  },
253
548
 
254
549
  methods: {
550
+ nextPage() {
551
+ this.currentPage = Math.min(this.currentPage + 1, this.totalPages)
552
+ },
553
+ prevPage() {
554
+ this.currentPage = Math.max(this.currentPage - 1, 1)
555
+ },
556
+ resetVisibleCount() {
557
+ this.visibleCount = this.initialVisibleCount > 0
558
+ ? this.initialVisibleCount
559
+ : 0
560
+ },
561
+ showMore() {
562
+ this.visibleCount = this.nextVisibleCount
563
+ },
564
+ showAll() {
565
+ this.visibleCount = this.filteredFilesCount
566
+ },
567
+ getFileSearchTokens(file = {}) {
568
+ return [
569
+ file.name,
570
+ file.filename,
571
+ file.description,
572
+ file.slug,
573
+ file.createdBy,
574
+ file.lastModifiedBy,
575
+ ...(Array.isArray(file.categories) ? file.categories : []),
576
+ ...(Array.isArray(file.owners) ? file.owners : []),
577
+ ...(Array.isArray(file.tags) ? file.tags : []),
578
+ ]
579
+ .map(normalizeSearchText)
580
+ .filter(Boolean)
581
+ .join(' ')
582
+ },
583
+ matchesSearch(file) {
584
+ const haystack = this.getFileSearchTokens(file)
585
+ return this.searchTerms.every(term => haystack.includes(term))
586
+ },
255
587
  // Expose the checkForOverflow method from the asd20viewport component
256
588
  // Expose the handleResize method from the asd20list component
257
589
  checkForOverflow() {
@@ -280,6 +612,29 @@ export default {
280
612
  <style lang="scss" scoped>
281
613
  @use '../../../design/component-stack' as *;
282
614
 
615
+ .asd20-file-list__toolbar {
616
+ display: flex;
617
+ align-items: center;
618
+ justify-content: flex-end;
619
+ }
620
+
621
+ .asd20-file-list__search {
622
+ min-width: min(20rem, 100%);
623
+ margin-left: auto;
624
+
625
+ :deep(.asd20-icon) {
626
+ margin-left: space(0.5);
627
+ }
628
+
629
+ :deep(input) {
630
+ border: 2px solid var(--color__accent);
631
+ border-radius: var(--website-shape__radius-s);
632
+ font-family: var(--website-typography__font-family-headlines);
633
+ font-size: 1rem;
634
+ min-height: 40px;
635
+ }
636
+ }
637
+
283
638
  .asd20-file-list__category-group {
284
639
  display: contents;
285
640
  }
@@ -289,4 +644,82 @@ export default {
289
644
  margin-top: space(0.5);
290
645
  }
291
646
  }
647
+
648
+ .asd20-file-list__pagination {
649
+ display: flex;
650
+ align-items: center;
651
+ justify-content: space-between;
652
+ flex-wrap: wrap;
653
+ margin: 0;
654
+ }
655
+
656
+ .asd20-file-list__pagination-summary {
657
+ margin: 0;
658
+ }
659
+
660
+ .asd20-file-list__pagination-actions {
661
+ display: flex;
662
+ flex-wrap: wrap;
663
+ align-items: center;
664
+ gap: space(0.5);
665
+ }
666
+
667
+ .asd20-file-list__pagination-page {
668
+ margin: 0;
669
+ }
670
+
671
+ .asd20-file-list__empty {
672
+ width: 100%;
673
+ padding: space(0.75) 0;
674
+ color: var(--color__primary);
675
+ }
676
+
677
+ .asd20-file-list__footer {
678
+ display: flex;
679
+ flex-wrap: wrap;
680
+ align-items: center;
681
+ justify-content: center;
682
+ gap: space(0.5);
683
+ padding-top: space(0.75);
684
+ }
685
+
686
+ .asd20-file-list__footer-summary {
687
+ color: var(--color__primary);
688
+ font-size: 0.875rem;
689
+ }
690
+
691
+ .asd20-file-list__footer-actions {
692
+ display: flex;
693
+ flex-wrap: wrap;
694
+ gap: space(0.5);
695
+ }
696
+
697
+
698
+ @media (max-width: 767px) {
699
+ .asd20-file-list__toolbar {
700
+ display: block;
701
+ width: 100%;
702
+ }
703
+ .asd20-file-list__pagination {
704
+ justify-content: center;
705
+ }
706
+
707
+ .asd20-file-list__search {
708
+ min-width: 100%;
709
+ margin-left: 0;
710
+ }
711
+ }
712
+
713
+ @media (min-width: 1024px) {
714
+ // .asd20-file-list__search {
715
+ // margin: space(0.5) space(0.5);
716
+
717
+ // :deep(.asd20-icon) {
718
+ // margin-left: 0;
719
+ // }
720
+ // }
721
+ .asd20-file-list__pagination {
722
+ justify-content: space-between;
723
+ }
724
+ }
292
725
  </style>
@@ -8,18 +8,29 @@
8
8
  v-if="$slots.header || headline"
9
9
  class="asd20-list__header"
10
10
  >
11
- <asd20-icon
12
- v-if="icon"
13
- :name="icon"
14
- :size="iconSize"
15
- />
16
- <component
17
- :is="headlineTag"
18
- class="asd20-list__headline"
19
- v-html="headline"
20
- ></component>
11
+ <div
12
+ v-if="icon || headline"
13
+ class="asd20-list__headline-group"
14
+ >
15
+ <asd20-icon
16
+ v-if="icon"
17
+ :name="icon"
18
+ :size="iconSize"
19
+ />
20
+ <component
21
+ :is="headlineTag"
22
+ class="asd20-list__headline"
23
+ v-html="headline"
24
+ ></component>
25
+ </div>
21
26
  <slot name="header" />
22
27
  </div>
28
+ <div
29
+ v-if="$slots['after-header']"
30
+ class="asd20-list__after-header"
31
+ >
32
+ <slot name="after-header" />
33
+ </div>
23
34
  <asd20-viewport
24
35
  scrollable
25
36
  :max-height="maxHeight"
@@ -123,7 +134,17 @@ export default {
123
134
  display: flex;
124
135
  flex-direction: row;
125
136
  align-items: center;
137
+ justify-content: space-between;
126
138
  margin-bottom: space(0.5);
139
+ gap: space(0.5);
140
+ }
141
+
142
+ .asd20-list__headline-group {
143
+ display: flex;
144
+ flex-direction: row;
145
+ align-items: center;
146
+ min-width: 0;
147
+
127
148
  .asd20-icon {
128
149
  margin-right: space(0.5);
129
150
  --line-color: var(--website-icon__line-color);
@@ -136,6 +157,10 @@ export default {
136
157
  color: var(--color__primary);
137
158
  }
138
159
 
160
+ .asd20-list__after-header {
161
+ margin-bottom: space(0.5);
162
+ }
163
+
139
164
  .asd20-list--multi-column {
140
165
  .asd20-list__items {
141
166
  list-style: none;
@@ -154,6 +179,18 @@ export default {
154
179
  }
155
180
  }
156
181
 
182
+ @media (max-width: 767px) {
183
+ .asd20-list__header {
184
+ flex-direction: column;
185
+ justify-content: flex-start;
186
+ align-items: stretch;
187
+ }
188
+
189
+ .asd20-list__headline-group {
190
+ width: 100%;
191
+ }
192
+ }
193
+
157
194
  /* @media (min-width: 767px) {*/
158
195
  /* .asd20-list--multi-column {*/
159
196
  /* &::v-deep .asd20-list-item {*/
@@ -8,18 +8,29 @@
8
8
  v-if="$slots.header || headline"
9
9
  class="asd20-list__header"
10
10
  >
11
- <asd20-icon
12
- v-if="icon"
13
- :name="icon"
14
- :size="iconSize"
15
- />
16
- <component
17
- :is="headlineTag"
18
- class="asd20-list__headline"
19
- v-html="headline"
20
- ></component>
11
+ <div
12
+ v-if="icon || headline"
13
+ class="asd20-list__headline-group"
14
+ >
15
+ <asd20-icon
16
+ v-if="icon"
17
+ :name="icon"
18
+ :size="iconSize"
19
+ />
20
+ <component
21
+ :is="headlineTag"
22
+ class="asd20-list__headline"
23
+ v-html="headline"
24
+ ></component>
25
+ </div>
21
26
  <slot name="header" />
22
27
  </div>
28
+ <div
29
+ v-if="$slots['after-header']"
30
+ class="asd20-list__after-header"
31
+ >
32
+ <slot name="after-header" />
33
+ </div>
23
34
  <asd20-viewport
24
35
  ref="viewport"
25
36
  scrollable
@@ -128,7 +139,17 @@ export default {
128
139
  display: flex;
129
140
  flex-direction: row;
130
141
  align-items: center;
142
+ justify-content: space-between;
131
143
  margin-bottom: space(0.5);
144
+ gap: space(0.5);
145
+ }
146
+
147
+ .asd20-list__headline-group {
148
+ display: flex;
149
+ flex-direction: row;
150
+ align-items: center;
151
+ min-width: 0;
152
+
132
153
  .asd20-icon {
133
154
  margin-right: space(0.5);
134
155
  --line-color: var(--website-icon__line-color);
@@ -141,6 +162,10 @@ export default {
141
162
  color: var(--color__primary);
142
163
  }
143
164
 
165
+ .asd20-list__after-header {
166
+ margin-bottom: space(0.5);
167
+ }
168
+
144
169
  .asd20-list--multi-column {
145
170
  .asd20-list__items {
146
171
  list-style: none;
@@ -159,6 +184,18 @@ export default {
159
184
  }
160
185
  }
161
186
 
187
+ @media (max-width: 767px) {
188
+ .asd20-list__header {
189
+ flex-direction: column;
190
+ justify-content: flex-start;
191
+ align-items: stretch;
192
+ }
193
+
194
+ .asd20-list__headline-group {
195
+ width: 100%;
196
+ }
197
+ }
198
+
162
199
  /* @media (min-width: 767px) {*/
163
200
  /* .asd20-list--multi-column {*/
164
201
  /* &::v-deep .asd20-list-item {*/
@@ -79,6 +79,10 @@
79
79
  :files="files"
80
80
  v-bind="filesFeedProps"
81
81
  :multi-column="true"
82
+ searchable
83
+ paginate
84
+ :page-size="50"
85
+ search-placeholder="Search files"
82
86
  max-height="auto"
83
87
  @files-in-view="$emit('files-in-view')"
84
88
  />
@@ -22,6 +22,33 @@ function hasIncomingProp(vm, propName) {
22
22
  )
23
23
  }
24
24
 
25
+ function resolveStore(vm) {
26
+ if (!vm) return null
27
+
28
+ const internal = vm.$
29
+ const context = internal && internal.ctx
30
+ if (context && context.$store) {
31
+ return context.$store
32
+ }
33
+
34
+ if (vm.$options && vm.$options.store) {
35
+ return vm.$options.store
36
+ }
37
+
38
+ const parentContext = internal && internal.parent && internal.parent.ctx
39
+ if (parentContext && parentContext.$store) {
40
+ return parentContext.$store
41
+ }
42
+
43
+ return (
44
+ internal &&
45
+ internal.appContext &&
46
+ internal.appContext.config &&
47
+ internal.appContext.config.globalProperties &&
48
+ internal.appContext.config.globalProperties.$store
49
+ )
50
+ }
51
+
25
52
  export default function(variableName, propOptions, storeModuleName) {
26
53
  const pascalCaseName = variableName[0].toUpperCase() + variableName.slice(1)
27
54
  const resolvedName = `resolved${pascalCaseName}`
@@ -41,14 +68,15 @@ export default function(variableName, propOptions, storeModuleName) {
41
68
  return this[variableName]
42
69
  }
43
70
 
44
- try {
45
- const storeValue = storeModuleName
46
- ? this.$store['state'][storeModuleName][variableName]
47
- : this.$store.state[variableName]
48
- return storeValue !== undefined ? storeValue : this[localStateName]
49
- } catch {
50
- return this[localStateName]
51
- }
71
+ const store = resolveStore(this)
72
+ const state = store && store.state
73
+ const storeState = storeModuleName && state ? state[storeModuleName] : state
74
+ const storeValue =
75
+ storeState && Object.prototype.hasOwnProperty.call(storeState, variableName)
76
+ ? storeState[variableName]
77
+ : undefined
78
+
79
+ return storeValue !== undefined ? storeValue : this[localStateName]
52
80
  },
53
81
  // emit an update event and value and call method to set value in store.
54
82
  set(value) {
@@ -68,9 +96,10 @@ export default function(variableName, propOptions, storeModuleName) {
68
96
  },
69
97
  }
70
98
  mixin.methods[`set${pascalCaseName}`] = function(value) {
71
- if (!this.$store || typeof this.$store.dispatch !== 'function') return false
99
+ const store = resolveStore(this)
100
+ if (!store || typeof store.dispatch !== 'function') return false
72
101
 
73
- this.$store.dispatch(
102
+ store.dispatch(
74
103
  `${storeModuleName ? storeModuleName + '/' : ''}set${pascalCaseName}`,
75
104
  value
76
105
  )