@ulu/frontend-vue 0.1.0-beta.3 → 0.1.0-beta.31

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 (151) hide show
  1. package/README.md +113 -2
  2. package/dist/{breakpoints-Cq2oSdYS.js → breakpoints-BJNvnXsD.js} +1 -1
  3. package/dist/frontend-vue.css +1 -1
  4. package/dist/frontend-vue.js +78 -72
  5. package/dist/index-BjwifaTk.js +6946 -0
  6. package/lib/components/collapsible/UluAccordion.vue +1 -1
  7. package/lib/components/collapsible/UluModal.vue +4 -5
  8. package/lib/components/collapsible/UluOverflowPopover.vue +1 -1
  9. package/lib/components/elements/UluAlert.vue +1 -2
  10. package/lib/components/elements/UluBadge.vue +27 -28
  11. package/lib/components/elements/UluBadgeStack.vue +8 -13
  12. package/lib/components/elements/UluButton.vue +2 -2
  13. package/lib/components/elements/UluButtonVerbose.vue +119 -0
  14. package/lib/components/elements/UluCard.vue +1 -1
  15. package/lib/components/elements/UluDefinitionList.vue +14 -17
  16. package/lib/components/elements/UluExternalLink.vue +22 -29
  17. package/lib/components/elements/UluIcon.vue +22 -17
  18. package/lib/components/elements/UluList.vue +53 -55
  19. package/lib/components/elements/UluSpokeSpinner.vue +12 -18
  20. package/lib/components/elements/UluTag.vue +35 -35
  21. package/lib/components/forms/UluFileDisplay.vue +40 -31
  22. package/lib/components/forms/UluFormFile.vue +22 -24
  23. package/lib/components/forms/UluFormMessage.vue +7 -10
  24. package/lib/components/forms/UluFormSelect.vue +16 -16
  25. package/lib/components/forms/UluFormText.vue +15 -15
  26. package/lib/components/forms/UluSearchForm.vue +8 -10
  27. package/lib/components/forms/UluSelectableMenu.vue +78 -0
  28. package/lib/components/index.js +2 -2
  29. package/lib/components/layout/UluAdaptiveLayout.vue +3 -5
  30. package/lib/components/layout/UluTitleRail.vue +9 -5
  31. package/lib/components/layout/UluWhenBreakpoint.vue +71 -77
  32. package/lib/components/navigation/UluBreadcrumb.vue +10 -4
  33. package/lib/components/navigation/UluMenu.vue +3 -3
  34. package/lib/components/navigation/UluPager.vue +102 -0
  35. package/lib/components/systems/facets/ExampleFacetsWithPagination.vue +119 -0
  36. package/lib/components/systems/facets/UluFacetsFilterLists.vue +84 -0
  37. package/lib/components/systems/facets/UluFacetsFilterPopovers.vue +114 -0
  38. package/lib/components/systems/facets/UluFacetsFilterSelects.vue +71 -0
  39. package/lib/components/systems/facets/UluFacetsHeaderLayout.vue +24 -0
  40. package/lib/components/systems/facets/UluFacetsList.vue +61 -33
  41. package/lib/components/systems/facets/UluFacetsResults.vue +63 -0
  42. package/lib/components/systems/facets/UluFacetsSearch.vue +26 -49
  43. package/lib/components/systems/facets/UluFacetsSidebarLayout.vue +31 -0
  44. package/lib/components/systems/facets/UluFacetsSort.vue +45 -0
  45. package/lib/components/systems/facets/_facets.scss +2 -3
  46. package/lib/components/systems/facets/_mock-data.js +40 -0
  47. package/lib/components/systems/facets/useFacets.js +229 -0
  48. package/lib/components/systems/index.js +13 -2
  49. package/lib/components/systems/scroll-anchors/UluScrollAnchors.vue +2 -1
  50. package/lib/components/systems/skeleton/UluShowSkeleton.vue +9 -8
  51. package/lib/components/systems/skeleton/UluSkeletonContent.vue +39 -43
  52. package/lib/components/systems/skeleton/UluSkeletonMedia.vue +4 -6
  53. package/lib/components/systems/skeleton/UluSkeletonText.vue +27 -0
  54. package/lib/components/systems/slider/UluImageSlideShow.vue +1 -1
  55. package/lib/components/systems/slider/UluSlideShow.vue +8 -3
  56. package/lib/components/systems/table-sticky/UluTableSticky.vue +8 -8
  57. package/lib/components/systems/table-sticky/UluTableStickyTable.vue +3 -3
  58. package/lib/composables/index.js +4 -1
  59. package/lib/composables/useDocumentTitle.js +61 -0
  60. package/lib/composables/usePagination.js +122 -0
  61. package/lib/composables/useRequiredInject.js +26 -0
  62. package/lib/index.js +1 -1
  63. package/lib/meta.js +14 -0
  64. package/lib/plugins/core/index.js +91 -0
  65. package/lib/plugins/index.js +1 -0
  66. package/lib/plugins/popovers/UluPopover.vue +3 -1
  67. package/lib/plugins/toast/UluToast.vue +2 -2
  68. package/lib/utils/index.js +2 -0
  69. package/lib/utils/{vue-router.js → router.js} +114 -30
  70. package/package.json +38 -13
  71. package/types/components/index.d.ts +2 -0
  72. package/types/components/index.d.ts.map +1 -0
  73. package/types/components/systems/facets/_mock-data.d.ts +18 -0
  74. package/types/components/systems/facets/_mock-data.d.ts.map +1 -0
  75. package/types/components/systems/facets/useFacets.d.ts +39 -0
  76. package/types/components/systems/facets/useFacets.d.ts.map +1 -0
  77. package/types/components/systems/index.d.ts +2 -0
  78. package/types/components/systems/index.d.ts.map +1 -0
  79. package/types/components/systems/scroll-anchors/symbols.d.ts +7 -0
  80. package/types/components/systems/scroll-anchors/symbols.d.ts.map +1 -0
  81. package/types/composables/index.d.ts +8 -0
  82. package/types/composables/index.d.ts.map +1 -0
  83. package/types/composables/useBreakpointManager.d.ts +8 -0
  84. package/types/composables/useBreakpointManager.d.ts.map +1 -0
  85. package/types/composables/useDocumentTitle.d.ts +22 -0
  86. package/types/composables/useDocumentTitle.d.ts.map +1 -0
  87. package/types/composables/useIcon.d.ts +6 -0
  88. package/types/composables/useIcon.d.ts.map +1 -0
  89. package/types/composables/useModifiers.d.ts +69 -0
  90. package/types/composables/useModifiers.d.ts.map +1 -0
  91. package/types/composables/usePageTitle.d.ts +19 -0
  92. package/types/composables/usePageTitle.d.ts.map +1 -0
  93. package/types/composables/usePagination.d.ts +25 -0
  94. package/types/composables/usePagination.d.ts.map +1 -0
  95. package/types/composables/useRequiredInject.d.ts +8 -0
  96. package/types/composables/useRequiredInject.d.ts.map +1 -0
  97. package/types/composables/useWindowResize.d.ts +6 -0
  98. package/types/composables/useWindowResize.d.ts.map +1 -0
  99. package/types/index.d.ts +5 -0
  100. package/types/index.d.ts.map +1 -0
  101. package/types/meta.d.ts +10 -0
  102. package/types/meta.d.ts.map +1 -0
  103. package/types/plugins/breakpoints/index.d.ts +2 -0
  104. package/types/plugins/breakpoints/index.d.ts.map +1 -0
  105. package/types/plugins/core/index.d.ts +3 -0
  106. package/types/plugins/core/index.d.ts.map +1 -0
  107. package/types/plugins/index.d.ts +6 -0
  108. package/types/plugins/index.d.ts.map +1 -0
  109. package/types/plugins/modals/api.d.ts +34 -0
  110. package/types/plugins/modals/api.d.ts.map +1 -0
  111. package/types/plugins/modals/index.d.ts +28 -0
  112. package/types/plugins/modals/index.d.ts.map +1 -0
  113. package/types/plugins/modals/useModals.d.ts +2 -0
  114. package/types/plugins/modals/useModals.d.ts.map +1 -0
  115. package/types/plugins/popovers/defaults.d.ts +14 -0
  116. package/types/plugins/popovers/defaults.d.ts.map +1 -0
  117. package/types/plugins/popovers/directive.d.ts +8 -0
  118. package/types/plugins/popovers/directive.d.ts.map +1 -0
  119. package/types/plugins/popovers/index.d.ts +7 -0
  120. package/types/plugins/popovers/index.d.ts.map +1 -0
  121. package/types/plugins/popovers/manager.d.ts +52 -0
  122. package/types/plugins/popovers/manager.d.ts.map +1 -0
  123. package/types/plugins/popovers/useFollow.d.ts +31 -0
  124. package/types/plugins/popovers/useFollow.d.ts.map +1 -0
  125. package/types/plugins/popovers/utils.d.ts +2 -0
  126. package/types/plugins/popovers/utils.d.ts.map +1 -0
  127. package/types/plugins/toast/defaults.d.ts +15 -0
  128. package/types/plugins/toast/defaults.d.ts.map +1 -0
  129. package/types/plugins/toast/index.d.ts +5 -0
  130. package/types/plugins/toast/index.d.ts.map +1 -0
  131. package/types/plugins/toast/store.d.ts +22 -0
  132. package/types/plugins/toast/store.d.ts.map +1 -0
  133. package/types/plugins/toast/useToast.d.ts +2 -0
  134. package/types/plugins/toast/useToast.d.ts.map +1 -0
  135. package/types/utils/dom.d.ts +8 -0
  136. package/types/utils/dom.d.ts.map +1 -0
  137. package/types/utils/index.d.ts +3 -0
  138. package/types/utils/index.d.ts.map +1 -0
  139. package/types/utils/placeholder.d.ts +8 -0
  140. package/types/utils/placeholder.d.ts.map +1 -0
  141. package/types/utils/router.d.ts +144 -0
  142. package/types/utils/router.d.ts.map +1 -0
  143. package/types/utils/vue-router.d.ts +122 -0
  144. package/types/utils/vue-router.d.ts.map +1 -0
  145. package/dist/index-CMGxe_M1.js +0 -6466
  146. package/lib/components/forms/UluCheckboxMenu.vue +0 -36
  147. package/lib/components/forms/UluFormDropzone.vue +0 -62
  148. package/lib/components/systems/facets/UluFacets.vue +0 -380
  149. package/lib/components/systems/skeleton/UluSkeletonTextInline.vue +0 -9
  150. package/lib/settings.js +0 -119
  151. package/lib/utils/placeholder.js +0 -6
@@ -4,9 +4,10 @@
4
4
  aria-label="Breadcrumb"
5
5
  v-if="items.length"
6
6
  >
7
- <ul :class="classes.list">
7
+ <ol :class="classes.list">
8
8
  <li v-for="(item, index) in items" :key="index" :class="classes.item">
9
9
  <router-link
10
+ v-if="!item.current"
10
11
  :to="item.to"
11
12
  :class="classes.link"
12
13
  :aria-current="item.current ? 'page' : null"
@@ -15,17 +16,21 @@
15
16
  {{ item.title }}
16
17
  </slot>
17
18
  </router-link>
19
+ <span v-else :class="item.current">
20
+ <slot :item="item">
21
+ {{ item.title }}
22
+ </slot>
23
+ </span>
18
24
  <template v-if="index < items.length - 1">
19
25
  <slot name="separator">
20
26
  <UluIcon
21
27
  :class="classes.separator"
22
- type="pathSeparator"
23
- :definition="separatorIcon"
28
+ :icon="separatorIcon || 'type:pathSeparator'"
24
29
  />
25
30
  </slot>
26
31
  </template>
27
32
  </li>
28
- </ul>
33
+ </ol>
29
34
  </nav>
30
35
  </template>
31
36
 
@@ -64,6 +69,7 @@
64
69
  list: "breadcrumb__list",
65
70
  item: "breadcrumb__item",
66
71
  link: "breadcrumb__link",
72
+ current: "breadcrumb__current",
67
73
  separator: "breadcrumb__separator"
68
74
  })
69
75
  }
@@ -26,7 +26,7 @@
26
26
  <slot :item="item" :index="index">
27
27
  <UluIcon
28
28
  v-if="item.icon"
29
- :definition="item.icon"
29
+ :icon="item.icon"
30
30
  :class="[classes.linkIcon, item?.classes?.linkIcon]"
31
31
  />
32
32
  <span :class="[classes.linkText, item?.classes?.linkText]">{{ item.title }}</span>
@@ -60,7 +60,7 @@
60
60
  /**
61
61
  * Fired anytime a item is clicked
62
62
  */
63
- "itemClick"
63
+ "item-click"
64
64
  ],
65
65
  props: {
66
66
  /**
@@ -98,7 +98,7 @@
98
98
  if (item.click) {
99
99
  item.click(event);
100
100
  }
101
- this.$emit("itemClick", { item, event });
101
+ this.$emit("item-click", { item, event });
102
102
  }
103
103
  }
104
104
  };
@@ -0,0 +1,102 @@
1
+ <template>
2
+ <nav v-if="items" class="pager" role="navigation" :aria-labelledby="headingId">
3
+ <component :is="titleElement" :id="headingId" class="hidden-visually">Pagination</component>
4
+ <ul class="pager__items js-pager__items">
5
+ <!-- First page link -->
6
+ <li v-if="items.first" class="pager__item pager__item--first">
7
+ <router-link :to="items.first.href" title="Go to first page" v-bind="items.first.attributes">
8
+ <span class="hidden-visually">First page</span>
9
+ <UluIcon icon="fas fa-angle-double-left" aria-hidden="true" />
10
+ </router-link>
11
+ </li>
12
+
13
+ <!-- Previous page link -->
14
+ <li v-if="items.previous" class="pager__item pager__item--previous">
15
+ <router-link :to="items.previous.href" title="Go to previous page" rel="prev" v-bind="items.previous.attributes">
16
+ <span class="hidden-visually">Previous page</span>
17
+ <UluIcon icon="fas fa-angle-left" aria-hidden="true" />
18
+ </router-link>
19
+ </li>
20
+
21
+ <!-- Ellipsis for previous pages -->
22
+ <li v-if="ellipses.previous" class="pager__item pager__item--ellipsis" role="presentation">&hellip;</li>
23
+
24
+ <!-- Page number links -->
25
+ <li v-for="(item, key) in items.pages" :key="key" :class="['pager__item', { 'is-active': current == key }]">
26
+ <router-link :to="item.href" :title="getPageTitle(key)" v-bind="item.attributes">
27
+ <span class="hidden-visually">
28
+ {{ current == key ? 'Current page' : 'Page' }}
29
+ </span>
30
+ {{ key }}
31
+ </router-link>
32
+ </li>
33
+
34
+ <!-- Ellipsis for next pages -->
35
+ <li v-if="ellipses.next" class="pager__item pager__item--ellipsis" role="presentation">&hellip;</li>
36
+
37
+ <!-- Next page link -->
38
+ <li v-if="items.next" class="pager__item pager__item--next">
39
+ <router-link :to="items.next.href" title="Go to next page" rel="next" v-bind="items.next.attributes">
40
+ <span class="hidden-visually">Next page</span>
41
+ <UluIcon icon="fas fa-angle-right" aria-hidden="true" />
42
+ </router-link>
43
+ </li>
44
+
45
+ <!-- Last page link -->
46
+ <li v-if="items.last" class="pager__item pager__item--last">
47
+ <router-link :to="items.last.href" title="Go to last page" v-bind="items.last.attributes">
48
+ <span class="hidden-visually">Last page</span>
49
+ <UluIcon icon="fas fa-angle-double-right" aria-hidden="true" />
50
+ </router-link>
51
+ </li>
52
+ </ul>
53
+ </nav>
54
+ </template>
55
+
56
+ <script setup>
57
+ import UluIcon from '../elements/UluIcon.vue';
58
+
59
+ let pagerCounter = 0;
60
+
61
+ const props = defineProps({
62
+ /**
63
+ * The HTML element to use for the visually hidden title.
64
+ */
65
+ titleElement: {
66
+ type: String,
67
+ default: 'h4'
68
+ },
69
+ /**
70
+ * List of pager items.
71
+ */
72
+ items: {
73
+ type: Object,
74
+ default: () => ({})
75
+ },
76
+ /**
77
+ * The page number of the current page.
78
+ */
79
+ current: {
80
+ type: Number,
81
+ default: 1
82
+ },
83
+ /**
84
+ * Ellipses configuration.
85
+ */
86
+ ellipses: {
87
+ type: Object,
88
+ default: () => ({})
89
+ }
90
+ });
91
+
92
+ const headingId = `ulu-pager-${ pagerCounter++ }`;
93
+
94
+ /**
95
+ * Generates the title for a page link.
96
+ * @param {string|number} key - The page number.
97
+ * @returns {string} The title for the page link.
98
+ */
99
+ function getPageTitle(key) {
100
+ return props.current == key ? 'Current page' : `Go to page ${key}`;
101
+ }
102
+ </script>
@@ -0,0 +1,119 @@
1
+ <template>
2
+ <div class="BibliographyList">
3
+ <LayoutListPage title="Bibliography" icon="type:bibliography">
4
+ <template #intro>
5
+ <AppContent uid="bibliographyIntroduction" />
6
+ </template>
7
+ <template #default>
8
+ <UluFacetsSidebarLayout>
9
+ <template #sidebar>
10
+ <UluFacetsSearch v-model="searchValue" />
11
+ <UluFacetsSort v-model="selectedSort" :sort-types="sortTypes" />
12
+ <UluFacetsFilterLists :facets="facets" @facet-change="handleFacetChange" />
13
+ </template>
14
+ <template #main>
15
+ <UluFacetsResults :items="paginatedItems">
16
+ <template #item="{ item }">
17
+ <div class="source-item">
18
+ <h3>{{ item.title || "NO TITLE" }}</h3>
19
+ <div>
20
+ <PortableText v-if="item.citation" :value="item.citation" />
21
+ </div>
22
+ <small v-if="item.publicationDate">Published on: {{ item.publicationDate }}</small>
23
+ </div>
24
+ </template>
25
+ </UluFacetsResults>
26
+ <UluPager
27
+ v-if="totalPages > 1"
28
+ :items="pagerItems"
29
+ :current="currentPage"
30
+ :ellipses="pagerEllipses"
31
+ class="mt-4"
32
+ />
33
+ </template>
34
+ </UluFacetsSidebarLayout>
35
+ </template>
36
+ </LayoutListPage>
37
+ </div>
38
+ </template>
39
+
40
+ <script setup>
41
+ import { ref } from "vue";
42
+ import { PortableText } from "@portabletext/vue";
43
+ import sources from "@/api/virtual/sources.js?virtual-module";
44
+ import {
45
+ useFacets,
46
+ usePagination,
47
+ UluFacetsSidebarLayout,
48
+ UluFacetsFilterLists,
49
+ UluFacetsSort,
50
+ UluFacetsSearch,
51
+ UluFacetsResults
52
+ } from "@ulu/frontend-vue";
53
+
54
+ const sorterDateLatest = (a, b) => new Date(b.publicationDate) - new Date(a.publicationDate);
55
+
56
+ const config = {
57
+ facetFields: [
58
+ { name: "Chapters", uid: "chapters", open: false, getValue: item => item.chapters?.map(c => c.uuid) },
59
+ { name: "Types", uid: "types", open: true },
60
+ { name: "Topics", uid: "topics", open: true },
61
+ { name: "Citation Type", uid: "citationType", open: true },
62
+ { name: "Source Type", uid: "sourceType", open: true },
63
+ { name: "Authors", uid: "authors", open: false },
64
+ { name: "Source Name", uid: "sourceName", open: false }
65
+ ],
66
+ extraSortTypes: {
67
+ newest: {
68
+ text: "Date (Newest)",
69
+ sort: items => [...items].sort(sorterDateLatest)
70
+ },
71
+ oldest: {
72
+ text: "Date (Oldest)",
73
+ sort: items => [...items].sort(sorterDateLatest).reverse()
74
+ }
75
+ },
76
+ initialSortType: "az",
77
+ // Remove quotes and stuff from beginning when sorting
78
+ getSortValue: item => item.title ? item.title.replace(/^[^A-Za-z0-9]+/, "") : "",
79
+ searchOptions: {
80
+ keys: ["title", "authors", "sourceName", "topics", "types", "citation"]
81
+ }
82
+ };
83
+
84
+ const itemsPerPage = 20;
85
+
86
+ const {
87
+ facets,
88
+ searchValue,
89
+ selectedSort,
90
+ sortTypes,
91
+ displayItems,
92
+ handleFacetChange,
93
+ } = useFacets(ref(sources), config);
94
+
95
+ const {
96
+ currentPage,
97
+ totalPages,
98
+ paginatedItems,
99
+ pagerItems,
100
+ pagerEllipses
101
+ } = usePagination(displayItems, itemsPerPage);
102
+ </script>
103
+
104
+ <style lang="scss">
105
+ // Add some basic styling for the item display
106
+ .source-item {
107
+ padding: 1rem;
108
+ border-bottom: 1px solid #eee;
109
+
110
+ h3 {
111
+ margin: 0 0 0.5rem;
112
+ }
113
+
114
+ p {
115
+ margin: 0 0 0.5rem;
116
+ font-style: italic;
117
+ }
118
+ }
119
+ </style>
@@ -0,0 +1,84 @@
1
+ <template>
2
+ <div class="UluFacetsFilters">
3
+ <UluCollapsibleRegion
4
+ class="UluFacets__group"
5
+ :class="classes.group"
6
+ :classToggle="['UluFacets__group-toggle', classes.groupToggle]"
7
+ :classContent="['UluFacets__group-content', classes.groupContent]"
8
+ v-for="group in facets"
9
+ :key="group.uid"
10
+ :group="group"
11
+ :startOpen="group.open"
12
+ :clickOutsideCloses="false"
13
+ :closeOnEscape="false"
14
+ :transitionHeight="true"
15
+ >
16
+ <template #toggle="{ isOpen }">
17
+ <slot name="groupToggle" :group="group" :isOpen="isOpen">
18
+ {{ group.name }}
19
+ </slot>
20
+ </template>
21
+ <template #default>
22
+ <UluFacetsList
23
+ :children="group.children.slice(0, maxVisible)"
24
+ :groupUid="group.uid"
25
+ :groupName="group.name"
26
+ :type="group.multiple ? 'checkbox' : 'radio'"
27
+ :model-value="selectedUids(group)"
28
+ @facet-change="emit('facet-change', $event)"
29
+ />
30
+ <UluCollapsibleRegion
31
+ v-if="group.children.length > maxVisible"
32
+ class="UluFacets__more-facets"
33
+ :class="classes.moreFacets"
34
+ :clickOutsideCloses="false"
35
+ :closeOnEscape="false"
36
+ :transitionHeight="true"
37
+ >
38
+ <template #toggle="{ isOpen }">
39
+ {{ isOpen ? "- Less" : "+ More" }}
40
+ </template>
41
+ <template #default>
42
+ <UluFacetsList
43
+ :children="group.children.slice(maxVisible)"
44
+ :groupUid="group.uid"
45
+ :groupName="group.name"
46
+ :type="group.multiple ? 'checkbox' : 'radio'"
47
+ :model-value="selectedUids(group)"
48
+ @facet-change="emit('facet-change', $event)"
49
+ />
50
+ </template>
51
+ </UluCollapsibleRegion>
52
+ </template>
53
+ </UluCollapsibleRegion>
54
+ </div>
55
+ </template>
56
+
57
+ <script setup>
58
+ import UluFacetsList from "./UluFacetsList.vue";
59
+ import UluCollapsibleRegion from "../../collapsible/UluCollapsibleRegion.vue";
60
+
61
+ defineProps({
62
+ classes: {
63
+ type: Object,
64
+ default: () => ({})
65
+ },
66
+ maxVisible: {
67
+ type: Number,
68
+ default: 5
69
+ },
70
+ facets: {
71
+ type: Array,
72
+ default: () => []
73
+ }
74
+ });
75
+
76
+ const emit = defineEmits(['facet-change']);
77
+
78
+ const selectedUids = (group) => {
79
+ if (group.multiple) {
80
+ return group.children.filter(c => c.selected).map(c => c.uid);
81
+ }
82
+ return group.children.find(c => c.selected)?.uid || '';
83
+ };
84
+ </script>
@@ -0,0 +1,114 @@
1
+ <template>
2
+ <div :class="classes.container">
3
+ <div v-for="group in facets" :key="group.uid" :class="classes.group">
4
+ <UluPopover
5
+ :classes="{
6
+ trigger: classes.trigger,
7
+ content: classes.content
8
+ }
9
+ ">
10
+ <template #trigger>
11
+ <slot name="trigger" :group="group" :label="selectedLabel(group)">
12
+ <span>{{ group.name }}: <strong>{{ selectedLabel(group) }}</strong></span>
13
+ </slot>
14
+ <UluIcon :class="classes.triggerIcon" icon="fas fa-chevron-down" />
15
+ </template>
16
+ <template #content="{ close }">
17
+ <UluSelectableMenu
18
+ :legend="group.name"
19
+ :type="group.multiple ? 'checkbox' : 'radio'"
20
+ :options="getMenuOptions(group)"
21
+ :model-value="selectedUids(group)"
22
+ @update:model-value="onFilterChange(group, $event, close)"
23
+ />
24
+ </template>
25
+ </UluPopover>
26
+ </div>
27
+ </div>
28
+ </template>
29
+
30
+ <script setup>
31
+ import UluPopover from '../../../plugins/popovers/UluPopover.vue';
32
+ import UluSelectableMenu from '../../forms/UluSelectableMenu.vue';
33
+ import UluIcon from '../../elements/UluIcon.vue';
34
+
35
+ const props = defineProps({
36
+ facets: {
37
+ type: Array,
38
+ default: () => []
39
+ },
40
+ classes: {
41
+ type: Object,
42
+ default: () => ({
43
+ trigger: "button",
44
+ triggerIcon: "button__icon"
45
+ // content: null,
46
+ // container: null,
47
+ // group: null
48
+ })
49
+ }
50
+ });
51
+
52
+ const emit = defineEmits(['facet-change']);
53
+
54
+ const getMenuOptions = (group) => {
55
+ if (group.multiple) {
56
+ return group.children;
57
+ }
58
+ return [{ label: `All ${group.name}s`, uid: '' }, ...group.children];
59
+ };
60
+
61
+ const selectedUids = (group) => {
62
+ if (group.multiple) {
63
+ return group.children.filter(c => c.selected).map(c => c.uid);
64
+ }
65
+ return group.children.find(c => c.selected)?.uid || '';
66
+ };
67
+
68
+ const selectedLabel = (group) => {
69
+ const selectedItems = group.children.filter(c => c.selected);
70
+ const count = selectedItems.length;
71
+
72
+ if (count === 0) {
73
+ return 'All';
74
+ }
75
+
76
+ if (group.multiple) {
77
+ if (count === 1) {
78
+ return selectedItems[0].label;
79
+ }
80
+ return `${count} selected`;
81
+ }
82
+
83
+ return selectedItems[0].label;
84
+ };
85
+
86
+ function onFilterChange(group, value, closePopover) {
87
+ if (group.multiple) {
88
+ const selectedUids = new Set(value);
89
+ group.children.forEach(facet => {
90
+ const shouldBeSelected = selectedUids.has(facet.uid);
91
+ if (facet.selected !== shouldBeSelected) {
92
+ emit('facet-change', {
93
+ groupUid: group.uid,
94
+ facetUid: facet.uid,
95
+ selected: shouldBeSelected
96
+ });
97
+ }
98
+ });
99
+ } else {
100
+ const selectedUid = value;
101
+ group.children.forEach(facet => {
102
+ const shouldBeSelected = facet.uid === selectedUid;
103
+ if (facet.selected !== shouldBeSelected) {
104
+ emit('facet-change', {
105
+ groupUid: group.uid,
106
+ facetUid: facet.uid,
107
+ selected: shouldBeSelected
108
+ });
109
+ }
110
+ });
111
+ closePopover();
112
+ }
113
+ }
114
+ </script>
@@ -0,0 +1,71 @@
1
+ <template>
2
+ <div class="UluFacetsDropdownFilters">
3
+ <div
4
+ class="UluFacetsDropdownFilters__group"
5
+ v-for="group in facets"
6
+ :key="group.uid"
7
+ >
8
+ <label :for="`facet-dropdown-${group.uid}`" class="UluFacetsDropdownFilters__label">
9
+ {{ group.name }}
10
+ </label>
11
+ <select
12
+ :id="`facet-dropdown-${group.uid}`"
13
+ class="UluFacetsDropdownFilters__select"
14
+ @change="onFilterChange(group, $event)"
15
+ >
16
+ <option value="">All {{ group.name }}s</option>
17
+ <option
18
+ v-for="option in group.children"
19
+ :key="option.uid"
20
+ :value="option.uid"
21
+ :selected="option.selected"
22
+ >
23
+ {{ option.label }}
24
+ </option>
25
+ </select>
26
+ </div>
27
+ </div>
28
+ </template>
29
+
30
+ <script setup>
31
+ const props = defineProps({
32
+ facets: {
33
+ type: Array,
34
+ default: () => []
35
+ }
36
+ });
37
+
38
+ const emit = defineEmits(['facet-change']);
39
+
40
+ function onFilterChange(group, event) {
41
+ const selectedUid = event.target.value;
42
+
43
+ const currentlySelected = group.children.find(c => c.selected);
44
+ if (currentlySelected?.uid === selectedUid) return;
45
+
46
+ group.children.forEach(facet => {
47
+ const shouldBeSelected = facet.uid === selectedUid;
48
+ if (facet.selected !== shouldBeSelected) {
49
+ emit('facet-change', {
50
+ groupUid: group.uid,
51
+ facetUid: facet.uid,
52
+ selected: shouldBeSelected
53
+ });
54
+ }
55
+ });
56
+ }
57
+ </script>
58
+
59
+ <style lang="scss">
60
+ .UluFacetsDropdownFilters {
61
+ display: flex;
62
+ gap: 1rem;
63
+ align-items: center;
64
+ flex-wrap: wrap;
65
+ }
66
+ .UluFacetsDropdownFilters__group {
67
+ display: flex;
68
+ gap: 0.5rem;
69
+ align-items: center;
70
+ }
71
+ </style>
@@ -0,0 +1,24 @@
1
+ <template>
2
+ <div class="UluFacetsHeaderLayout">
3
+ <div class="UluFacetsHeaderLayout__header">
4
+ <slot name="header"></slot>
5
+ </div>
6
+ <div class="UluFacetsHeaderLayout__main">
7
+ <slot name="main"></slot>
8
+ </div>
9
+ </div>
10
+ </template>
11
+
12
+ <script setup>
13
+ // This component is purely for layout, no logic needed.
14
+ </script>
15
+
16
+ <style lang="scss">
17
+ .UluFacetsHeaderLayout__header {
18
+ display: flex;
19
+ gap: 1rem;
20
+ align-items: center;
21
+ margin-bottom: 1.5rem;
22
+ flex-wrap: wrap;
23
+ }
24
+ </style>
@@ -1,39 +1,67 @@
1
1
  <template>
2
- <ul class="UluFacets__facet-list">
3
- <li
4
- class="UluFacets__facet"
5
- :class="classFacet"
6
- v-for="facet in children"
7
- :key="facet.uid"
8
- >
9
- <input
10
- class="UluFacets__facet-checkbox"
11
- :id="facetCheckboxId(facet)"
12
- type="checkbox"
13
- v-model="facet.selected"
14
- >
15
- <label
16
- class="UluFacets__facet-label"
17
- :for="facetCheckboxId(facet)"
18
- >
19
- {{ facet.label }}
20
- </label>
21
- </li>
22
- </ul>
2
+ <UluSelectableMenu
3
+ class="UluFacets__facet-list"
4
+ :legend="groupUid"
5
+ :type="type"
6
+ :options="menuOptions"
7
+ :model-value="modelValue"
8
+ @update:model-value="handleChange"
9
+ >
10
+ <template #default="{ option }">
11
+ {{ option.label }}
12
+ </template>
13
+ </UluSelectableMenu>
23
14
  </template>
24
15
 
25
- <script>
26
- export default {
27
- name: 'UluFacetsList',
28
- props: {
29
- groupUid: String,
30
- children: Array,
31
- classFacet: String
32
- },
33
- methods: {
34
- facetCheckboxId(facet) {
35
- return `facet-${ this.groupUid }-${ facet.uid }`;
16
+ <script setup>
17
+ import { computed } from 'vue';
18
+ import UluSelectableMenu from '../../forms/UluSelectableMenu.vue';
19
+
20
+ const props = defineProps({
21
+ groupUid: String,
22
+ groupName: String,
23
+ children: Array,
24
+ type: {
25
+ type: String,
26
+ default: 'checkbox',
27
+ },
28
+ modelValue: [String, Array],
29
+ });
30
+
31
+ const emit = defineEmits(['facet-change']);
32
+
33
+ const menuOptions = computed(() => {
34
+ if (props.type === 'radio') {
35
+ return [{ label: `All ${props.groupName}s`, uid: '' }, ...props.children];
36
+ }
37
+ return props.children;
38
+ });
39
+
40
+ function handleChange(value) {
41
+ if (props.type === 'radio') {
42
+ const selectedUid = value;
43
+ props.children.forEach(facet => {
44
+ const shouldBeSelected = facet.uid === selectedUid;
45
+ if (facet.selected !== shouldBeSelected) {
46
+ emit('facet-change', {
47
+ groupUid: props.groupUid,
48
+ facetUid: facet.uid,
49
+ selected: shouldBeSelected
50
+ });
51
+ }
52
+ });
53
+ } else {
54
+ const selectedUids = new Set(value);
55
+ props.children.forEach(facet => {
56
+ const shouldBeSelected = selectedUids.has(facet.uid);
57
+ if (facet.selected !== shouldBeSelected) {
58
+ emit('facet-change', {
59
+ groupUid: props.groupUid,
60
+ facetUid: facet.uid,
61
+ selected: shouldBeSelected
62
+ });
36
63
  }
37
- }
64
+ });
38
65
  }
66
+ }
39
67
  </script>