@ulu/frontend-vue 0.1.0-beta.9 → 0.1.1-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{breakpoints-BbkGNxxt.js → breakpoints-DM-CBTtb.js} +1 -1
- package/dist/frontend-vue.css +1 -1
- package/dist/frontend-vue.js +79 -68
- package/dist/index-BNRZ3Apw.js +7541 -0
- package/lib/components/collapsible/UluAccordion.vue +71 -53
- package/lib/components/collapsible/UluAccordionGroup.vue +54 -0
- package/lib/components/collapsible/UluCollapsible.vue +144 -0
- package/lib/components/collapsible/UluDropdown.vue +29 -29
- package/lib/components/collapsible/UluOverflowPopover.vue +1 -1
- package/lib/components/elements/UluBadge.vue +51 -28
- package/lib/components/elements/UluBadgeStack.vue +8 -13
- package/lib/components/elements/UluButtonVerbose.vue +119 -0
- package/lib/components/elements/UluCard.vue +1 -1
- package/lib/components/elements/UluDefinitionList.vue +14 -17
- package/lib/components/elements/UluExternalLink.vue +21 -27
- package/lib/components/elements/UluIcon.vue +11 -1
- package/lib/components/elements/UluList.vue +53 -55
- package/lib/components/elements/UluSpokeSpinner.vue +12 -18
- package/lib/components/elements/UluTag.vue +35 -35
- package/lib/components/forms/UluFileDisplay.vue +49 -31
- package/lib/components/forms/UluFormFile.vue +37 -24
- package/lib/components/forms/UluFormMessage.vue +13 -10
- package/lib/components/forms/UluFormSelect.vue +28 -16
- package/lib/components/forms/UluFormText.vue +24 -15
- package/lib/components/forms/UluSearchForm.vue +11 -10
- package/lib/components/forms/UluSelectableMenu.vue +99 -0
- package/lib/components/index.js +4 -3
- package/lib/components/layout/UluTitleRail.vue +18 -0
- package/lib/components/layout/UluWhenBreakpoint.vue +9 -0
- package/lib/components/navigation/UluBreadcrumb.vue +9 -2
- package/lib/components/navigation/UluMenu.vue +8 -3
- package/lib/components/navigation/UluMenuStack.vue +3 -1
- package/lib/components/navigation/UluPager.vue +102 -0
- package/lib/components/systems/facets/ExampleFacetsWithPagination.vue +119 -0
- package/lib/components/systems/facets/UluFacetsFilterLists.vue +91 -0
- package/lib/components/systems/facets/UluFacetsFilterPopovers.vue +125 -0
- package/lib/components/systems/facets/UluFacetsFilterSelects.vue +71 -0
- package/lib/components/systems/facets/UluFacetsHeaderLayout.vue +24 -0
- package/lib/components/systems/facets/UluFacetsList.vue +62 -34
- package/lib/components/systems/facets/UluFacetsResults.vue +63 -0
- package/lib/components/systems/facets/UluFacetsSearch.vue +27 -50
- package/lib/components/systems/facets/UluFacetsSidebarLayout.vue +70 -0
- package/lib/components/systems/facets/UluFacetsSort.vue +45 -0
- package/lib/components/systems/facets/_facets.scss +2 -3
- package/lib/components/systems/facets/_mock-data.js +40 -0
- package/lib/components/systems/facets/useFacets.js +268 -0
- package/lib/components/systems/index.js +13 -2
- package/lib/components/systems/scroll-anchors/UluScrollAnchors.vue +2 -1
- package/lib/components/systems/skeleton/UluShowSkeleton.vue +9 -8
- package/lib/components/systems/skeleton/UluSkeletonContent.vue +39 -43
- package/lib/components/systems/skeleton/UluSkeletonMedia.vue +4 -6
- package/lib/components/systems/skeleton/UluSkeletonText.vue +27 -0
- package/lib/components/systems/slider/UluImageSlideShow.vue +1 -1
- package/lib/components/systems/slider/UluSlideShow.vue +8 -3
- package/lib/components/systems/table-sticky/UluTableSticky.vue +7 -7
- package/lib/components/systems/table-sticky/UluTableStickyTable.vue +3 -3
- package/lib/components/visualizations/UluAnimateNumber.vue +7 -1
- package/lib/components/visualizations/UluProgressBar.vue +99 -68
- package/lib/components/visualizations/UluProgressCircle.vue +146 -0
- package/lib/components/visualizations/progress-bar-examples.html +175 -0
- package/lib/composables/index.js +3 -1
- package/lib/composables/useDocumentTitle.js +61 -0
- package/lib/composables/usePagination.js +122 -0
- package/lib/index.js +1 -0
- package/lib/plugins/core/index.js +6 -1
- package/lib/plugins/popovers/UluPopover.vue +8 -3
- package/lib/plugins/toast/UluToast.vue +1 -1
- package/lib/plugins/toast/UluToastDisplay.vue +19 -2
- package/lib/utils/dom.js +12 -0
- package/lib/utils/index.js +2 -0
- package/lib/utils/{vue-router.js → router.js} +114 -30
- package/package.json +17 -11
- package/types/components/systems/facets/_mock-data.d.ts +18 -0
- package/types/components/systems/facets/_mock-data.d.ts.map +1 -0
- package/types/components/systems/facets/useFacets.d.ts +39 -0
- package/types/components/systems/facets/useFacets.d.ts.map +1 -0
- package/types/components/systems/index.d.ts +1 -1
- package/types/composables/index.d.ts +2 -0
- package/types/composables/useDocumentTitle.d.ts +22 -0
- package/types/composables/useDocumentTitle.d.ts.map +1 -0
- package/types/composables/usePageTitle.d.ts +19 -0
- package/types/composables/usePageTitle.d.ts.map +1 -0
- package/types/composables/usePagination.d.ts +25 -0
- package/types/composables/usePagination.d.ts.map +1 -0
- package/types/index.d.ts +1 -0
- package/types/plugins/core/index.d.ts.map +1 -1
- package/types/utils/dom.d.ts +1 -0
- package/types/utils/dom.d.ts.map +1 -1
- package/types/utils/index.d.ts +3 -0
- package/types/utils/index.d.ts.map +1 -0
- package/types/utils/router.d.ts +144 -0
- package/types/utils/router.d.ts.map +1 -0
- package/dist/index-D3Uc6T5M.js +0 -6469
- package/lib/components/collapsible/UluCollapsibleRegion.vue +0 -278
- package/lib/components/forms/UluCheckboxMenu.vue +0 -36
- package/lib/components/systems/facets/UluFacets.vue +0 -380
- package/lib/components/systems/skeleton/UluSkeletonTextInline.vue +0 -9
- package/lib/components/visualizations/UluProgressDonut.vue +0 -97
- package/lib/utils/placeholder.js +0 -6
|
@@ -0,0 +1,125 @@
|
|
|
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 #default="{ 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
|
+
:hideInputs="hideInputs"
|
|
24
|
+
/>
|
|
25
|
+
</template>
|
|
26
|
+
</UluPopover>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
</template>
|
|
30
|
+
|
|
31
|
+
<script setup>
|
|
32
|
+
import UluPopover from '../../../plugins/popovers/UluPopover.vue';
|
|
33
|
+
import UluSelectableMenu from '../../forms/UluSelectableMenu.vue';
|
|
34
|
+
import UluIcon from '../../elements/UluIcon.vue';
|
|
35
|
+
|
|
36
|
+
const props = defineProps({
|
|
37
|
+
/**
|
|
38
|
+
* An array of facet groups to display.
|
|
39
|
+
*/
|
|
40
|
+
facets: {
|
|
41
|
+
type: Array,
|
|
42
|
+
default: () => []
|
|
43
|
+
},
|
|
44
|
+
/**
|
|
45
|
+
* An object of classes to apply to the component.
|
|
46
|
+
*/
|
|
47
|
+
classes: {
|
|
48
|
+
type: Object,
|
|
49
|
+
default: () => ({
|
|
50
|
+
trigger: "button",
|
|
51
|
+
triggerIcon: "button__icon"
|
|
52
|
+
// content: null,
|
|
53
|
+
// container: null,
|
|
54
|
+
// group: null
|
|
55
|
+
})
|
|
56
|
+
},
|
|
57
|
+
/**
|
|
58
|
+
* If true, the input elements will be visually hidden.
|
|
59
|
+
*/
|
|
60
|
+
hideInputs: Boolean
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const emit = defineEmits(['facet-change']);
|
|
64
|
+
|
|
65
|
+
const getMenuOptions = (group) => {
|
|
66
|
+
if (group.multiple) {
|
|
67
|
+
return group.children;
|
|
68
|
+
}
|
|
69
|
+
return [{ label: `All ${group.name}s`, uid: '' }, ...group.children];
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const selectedUids = (group) => {
|
|
73
|
+
if (group.multiple) {
|
|
74
|
+
return group.children.filter(c => c.selected).map(c => c.uid);
|
|
75
|
+
}
|
|
76
|
+
return group.children.find(c => c.selected)?.uid || '';
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const selectedLabel = (group) => {
|
|
80
|
+
const selectedItems = group.children.filter(c => c.selected);
|
|
81
|
+
const count = selectedItems.length;
|
|
82
|
+
|
|
83
|
+
if (count === 0) {
|
|
84
|
+
return 'All';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (group.multiple) {
|
|
88
|
+
if (count === 1) {
|
|
89
|
+
return selectedItems[0].label;
|
|
90
|
+
}
|
|
91
|
+
return `${count} selected`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return selectedItems[0].label;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
function onFilterChange(group, value, closePopover) {
|
|
98
|
+
if (group.multiple) {
|
|
99
|
+
const selectedUids = new Set(value);
|
|
100
|
+
group.children.forEach(facet => {
|
|
101
|
+
const shouldBeSelected = selectedUids.has(facet.uid);
|
|
102
|
+
if (facet.selected !== shouldBeSelected) {
|
|
103
|
+
emit('facet-change', {
|
|
104
|
+
groupUid: group.uid,
|
|
105
|
+
facetUid: facet.uid,
|
|
106
|
+
selected: shouldBeSelected
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
} else {
|
|
111
|
+
const selectedUid = value;
|
|
112
|
+
group.children.forEach(facet => {
|
|
113
|
+
const shouldBeSelected = facet.uid === selectedUid;
|
|
114
|
+
if (facet.selected !== shouldBeSelected) {
|
|
115
|
+
emit('facet-change', {
|
|
116
|
+
groupUid: group.uid,
|
|
117
|
+
facetUid: facet.uid,
|
|
118
|
+
selected: shouldBeSelected
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
closePopover();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
</script>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="facets-dropdown-filters">
|
|
3
|
+
<div
|
|
4
|
+
class="facets-dropdown-filters__group"
|
|
5
|
+
v-for="group in facets"
|
|
6
|
+
:key="group.uid"
|
|
7
|
+
>
|
|
8
|
+
<label :for="`facet-dropdown-${group.uid}`" class="facets-dropdown-filters__label">
|
|
9
|
+
{{ group.name }}
|
|
10
|
+
</label>
|
|
11
|
+
<select
|
|
12
|
+
:id="`facet-dropdown-${group.uid}`"
|
|
13
|
+
class="facets-dropdown-filters__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
|
+
.facets-dropdown-filters {
|
|
61
|
+
display: flex;
|
|
62
|
+
gap: 1rem;
|
|
63
|
+
align-items: center;
|
|
64
|
+
flex-wrap: wrap;
|
|
65
|
+
}
|
|
66
|
+
.facets-dropdown-filters__group {
|
|
67
|
+
display: flex;
|
|
68
|
+
gap: 0.5rem;
|
|
69
|
+
align-items: center;
|
|
70
|
+
}
|
|
71
|
+
</style>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="facets-header-layout">
|
|
3
|
+
<div class="facets-header-layout__header">
|
|
4
|
+
<slot name="header"></slot>
|
|
5
|
+
</div>
|
|
6
|
+
<div class="facets-header-layout__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
|
+
.facets-header-layout__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
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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="ulu-facets__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 }} <span v-if="option.count !== undefined">({{ option.count }})</span>
|
|
12
|
+
</template>
|
|
13
|
+
</UluSelectableMenu>
|
|
23
14
|
</template>
|
|
24
15
|
|
|
25
|
-
<script>
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
}
|
|
39
|
-
|
|
66
|
+
}
|
|
67
|
+
</script>
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="facets-results">
|
|
3
|
+
<transition-group
|
|
4
|
+
v-if="items.length"
|
|
5
|
+
:tag="tag"
|
|
6
|
+
:name="transitionName"
|
|
7
|
+
class="facets-results__list"
|
|
8
|
+
:class="classes.list"
|
|
9
|
+
>
|
|
10
|
+
<li
|
|
11
|
+
class="facets-results__item"
|
|
12
|
+
:class="classes.item"
|
|
13
|
+
v-for="(item, index) in items"
|
|
14
|
+
:key="item.id || index"
|
|
15
|
+
>
|
|
16
|
+
<slot name="item" :item="item" :index="index"></slot>
|
|
17
|
+
</li>
|
|
18
|
+
</transition-group>
|
|
19
|
+
<div v-else class="facets-results__empty">
|
|
20
|
+
<slot name="empty">
|
|
21
|
+
<p>No matching items found.</p>
|
|
22
|
+
</slot>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<script setup>
|
|
28
|
+
defineProps({
|
|
29
|
+
items: {
|
|
30
|
+
type: Array,
|
|
31
|
+
required: true
|
|
32
|
+
},
|
|
33
|
+
tag: {
|
|
34
|
+
type: String,
|
|
35
|
+
default: 'ul'
|
|
36
|
+
},
|
|
37
|
+
transitionName: {
|
|
38
|
+
type: String,
|
|
39
|
+
default: 'facets-fade'
|
|
40
|
+
},
|
|
41
|
+
classes: {
|
|
42
|
+
type: Object,
|
|
43
|
+
default: () => ({})
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<style lang="scss">
|
|
49
|
+
.facets-results__list {
|
|
50
|
+
list-style: none;
|
|
51
|
+
padding: 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.facets-fade-enter-active,
|
|
55
|
+
.facets-fade-leave-active {
|
|
56
|
+
transition: opacity 0.25s ease;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.facets-fade-enter-from,
|
|
60
|
+
.facets-fade-leave-to {
|
|
61
|
+
opacity: 0;
|
|
62
|
+
}
|
|
63
|
+
</style>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="
|
|
2
|
+
<div class="ulu-facets__keyword-search">
|
|
3
3
|
<label :class="classes.searchLabel" :for="id">
|
|
4
4
|
<strong>Search</strong>
|
|
5
5
|
</label>
|
|
@@ -10,58 +10,35 @@
|
|
|
10
10
|
type="text"
|
|
11
11
|
:placeholder="placeholder"
|
|
12
12
|
>
|
|
13
|
-
<!-- <button
|
|
14
|
-
v-if="value"
|
|
15
|
-
:class="classes.searchClear"
|
|
16
|
-
@click="clear"
|
|
17
|
-
:aria-label="classes.searchClearIcon ? 'Clear Search' : false"
|
|
18
|
-
type="button"
|
|
19
|
-
>
|
|
20
|
-
<span
|
|
21
|
-
v-if="classes.searchClearIcon"
|
|
22
|
-
:class="classes.searchClearIcon"
|
|
23
|
-
aria-hidden="true"
|
|
24
|
-
></span>
|
|
25
|
-
<span v-else>
|
|
26
|
-
Clear
|
|
27
|
-
</span>
|
|
28
|
-
</button> -->
|
|
29
13
|
</div>
|
|
30
14
|
</template>
|
|
31
15
|
|
|
32
|
-
<script>
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
placeholder: {
|
|
40
|
-
type: String,
|
|
41
|
-
default: "Keywords…"
|
|
42
|
-
}
|
|
43
|
-
},
|
|
44
|
-
data() {
|
|
45
|
-
return {
|
|
46
|
-
id: `facet-view-keyword-${ ++uid }`
|
|
47
|
-
}
|
|
16
|
+
<script setup>
|
|
17
|
+
import { computed } from 'vue';
|
|
18
|
+
|
|
19
|
+
const props = defineProps({
|
|
20
|
+
classes: {
|
|
21
|
+
type: Object,
|
|
22
|
+
default: () => ({})
|
|
48
23
|
},
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
24
|
+
modelValue: String,
|
|
25
|
+
placeholder: {
|
|
26
|
+
type: String,
|
|
27
|
+
default: "Keywords…"
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const emit = defineEmits(['update:modelValue']);
|
|
32
|
+
|
|
33
|
+
let uid = 0;
|
|
34
|
+
const id = `facet-view-keyword-${++uid}`;
|
|
35
|
+
|
|
36
|
+
const localValue = computed({
|
|
37
|
+
get() {
|
|
38
|
+
return props.modelValue;
|
|
58
39
|
},
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
// this.value = null;
|
|
62
|
-
// this.applied = false;
|
|
63
|
-
// this.$emit("search", null);
|
|
64
|
-
}
|
|
40
|
+
set(val) {
|
|
41
|
+
emit('update:modelValue', val);
|
|
65
42
|
}
|
|
66
|
-
}
|
|
67
|
-
</script>
|
|
43
|
+
});
|
|
44
|
+
</script>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="facets-sidebar-layout"
|
|
4
|
+
:class="{ 'facets-sidebar-layout--filters-hidden' : isMobile }"
|
|
5
|
+
>
|
|
6
|
+
<div class="facets-sidebar-layout__header">
|
|
7
|
+
<slot name="header"></slot>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<div v-show="isMobile" class="facets-sidebar-layout__mobile-controls">
|
|
11
|
+
<button class="button" @click="showMobileFilters = true">Filters & Sort</button>
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<div class="facets-sidebar-layout__body">
|
|
15
|
+
<!-- Desktop container for the sidebar -->
|
|
16
|
+
<div v-show="!isMobile" class="facets-sidebar-layout__sidebar" ref="desktopTarget"></div>
|
|
17
|
+
|
|
18
|
+
<div class="facets-sidebar-layout__main">
|
|
19
|
+
<slot name="main"></slot>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<UluModal
|
|
24
|
+
v-if="isMobile"
|
|
25
|
+
v-model="showMobileFilters"
|
|
26
|
+
position="right"
|
|
27
|
+
title="Filters & Sort"
|
|
28
|
+
allowResize
|
|
29
|
+
>
|
|
30
|
+
<!-- Mobile container for the sidebar -->
|
|
31
|
+
<div class="facets-sidebar-layout__sidebar" ref="mobileTarget"></div>
|
|
32
|
+
</UluModal>
|
|
33
|
+
|
|
34
|
+
<teleport :to="teleportTarget" v-if="teleportTarget">
|
|
35
|
+
<slot name="sidebar"></slot>
|
|
36
|
+
</teleport>
|
|
37
|
+
</div>
|
|
38
|
+
</template>
|
|
39
|
+
|
|
40
|
+
<script setup>
|
|
41
|
+
import { ref, inject, computed } from 'vue';
|
|
42
|
+
import UluModal from '../../collapsible/UluModal.vue';
|
|
43
|
+
|
|
44
|
+
const showMobileFilters = ref(false);
|
|
45
|
+
const isMobile = inject("uluIsMobile");
|
|
46
|
+
|
|
47
|
+
const desktopTarget = ref(null);
|
|
48
|
+
const mobileTarget = ref(null);
|
|
49
|
+
|
|
50
|
+
const teleportTarget = computed(() => {
|
|
51
|
+
if (isMobile.value) {
|
|
52
|
+
return mobileTarget.value;
|
|
53
|
+
} else {
|
|
54
|
+
return desktopTarget.value;
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
</script>
|
|
58
|
+
|
|
59
|
+
<style lang="scss">
|
|
60
|
+
.facets-sidebar-layout__body {
|
|
61
|
+
display: grid;
|
|
62
|
+
grid-template-columns: 250px 1fr;
|
|
63
|
+
gap: 2rem;
|
|
64
|
+
}
|
|
65
|
+
.facets-sidebar-layout--filters-hidden {
|
|
66
|
+
.facets-sidebar-layout__body {
|
|
67
|
+
grid-template-columns: 1fr;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
</style>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="facets-sort" :class="classes.sortForm">
|
|
3
|
+
<label
|
|
4
|
+
:for="sortId"
|
|
5
|
+
:class="classes.sortFormLabel"
|
|
6
|
+
>
|
|
7
|
+
<slot>Sort:</slot>
|
|
8
|
+
</label>
|
|
9
|
+
<select
|
|
10
|
+
:value="modelValue"
|
|
11
|
+
@change="emit('update:modelValue', $event.target.value)"
|
|
12
|
+
:id="sortId"
|
|
13
|
+
:class="classes.sortFormSelect"
|
|
14
|
+
>
|
|
15
|
+
<option v-for="(item, key) in sortTypes" :value="key" :key="key">
|
|
16
|
+
{{ item.text }}
|
|
17
|
+
</option>
|
|
18
|
+
</select>
|
|
19
|
+
</div>
|
|
20
|
+
</template>
|
|
21
|
+
|
|
22
|
+
<script setup>
|
|
23
|
+
import { ref } from 'vue';
|
|
24
|
+
|
|
25
|
+
let idCounter = 0;
|
|
26
|
+
|
|
27
|
+
defineProps({
|
|
28
|
+
classes: {
|
|
29
|
+
type: Object,
|
|
30
|
+
default: () => ({})
|
|
31
|
+
},
|
|
32
|
+
sortTypes: {
|
|
33
|
+
type: Object,
|
|
34
|
+
default: () => ({})
|
|
35
|
+
},
|
|
36
|
+
modelValue: {
|
|
37
|
+
type: String,
|
|
38
|
+
default: ''
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const emit = defineEmits(['update:modelValue']);
|
|
43
|
+
|
|
44
|
+
const sortId = ref(`ulu-facet-sort-${++idCounter}`);
|
|
45
|
+
</script>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export const initialMockFacets = [
|
|
2
|
+
{
|
|
3
|
+
name: 'Category',
|
|
4
|
+
uid: 'category',
|
|
5
|
+
open: true,
|
|
6
|
+
children: [
|
|
7
|
+
{ uid: 'cat1', label: 'Design' },
|
|
8
|
+
{ uid: 'cat2', label: 'Development' },
|
|
9
|
+
{ uid: 'cat3', label: 'Marketing' },
|
|
10
|
+
{ uid: 'cat4', label: 'Business' },
|
|
11
|
+
{ uid: 'cat5', label: 'Lifestyle' },
|
|
12
|
+
{ uid: 'cat6', label: 'Technology' },
|
|
13
|
+
]
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: 'Author',
|
|
17
|
+
uid: 'author',
|
|
18
|
+
open: true,
|
|
19
|
+
children: [
|
|
20
|
+
{ uid: 'jane-doe', label: 'Jane Doe' },
|
|
21
|
+
{ uid: 'john-smith', label: 'John Smith' },
|
|
22
|
+
{ uid: 'peter-jones', label: 'Peter Jones' },
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
export const mockItems = [
|
|
28
|
+
{ id: 1, title: 'The Art of UI Design', description: 'A deep dive into creating beautiful user interfaces.', category: ['cat1', 'cat2'], author: ['jane-doe'], date: new Date(2023, 5, 15) },
|
|
29
|
+
{ id: 2, title: 'Vue.js for Beginners', description: 'Getting started with the popular JavaScript framework.', category: ['cat2', 'cat6'], author: ['john-smith'], date: new Date(2023, 8, 22) },
|
|
30
|
+
{ id: 3, title: 'Content Marketing Strategies', description: 'How to attract and retain customers with great content.', category: ['cat3'], author: ['peter-jones'], date: new Date(2022, 11, 10) },
|
|
31
|
+
{ id: 4, title: 'Startup Funding 101', description: 'A guide to raising capital for your new venture.', category: ['cat4'], author: ['jane-doe'], date: new Date(2024, 1, 5) },
|
|
32
|
+
{ id: 5, title: 'Minimalist Living', description: 'Declutter your life and find more happiness.', category: ['cat5'], author: ['john-smith'], date: new Date(2023, 3, 30) },
|
|
33
|
+
{ id: 6, title: 'The Future of AI', description: 'Exploring the impact of artificial intelligence on society.', category: ['cat6'], author: ['peter-jones'], date: new Date(2024, 0, 1) },
|
|
34
|
+
{ id: 7, title: 'Advanced CSS Techniques', description: 'Take your styling skills to the next level.', category: ['cat1', 'cat2'], author: ['jane-doe'], date: new Date(2023, 10, 18) },
|
|
35
|
+
{ id: 8, title: 'Building a Scalable API', description: 'Best practices for designing and implementing APIs.', category: ['cat2', 'cat6'], author: ['john-smith'], date: new Date(2023, 7, 3) },
|
|
36
|
+
{ id: 9, title: 'Social Media for Business', description: 'Leveraging social platforms for growth.', category: ['cat3'], author: ['peter-jones'], date: new Date(2022, 9, 14) },
|
|
37
|
+
{ id: 10, title: 'Negotiation and Deal Making', description: 'Master the art of getting what you want.', category: ['cat4'], author: ['jane-doe'], date: new Date(2023, 6, 25) },
|
|
38
|
+
{ id: 11, title: 'Healthy Eating Habits', description: 'A guide to a balanced and nutritious diet.', category: ['cat5'], author: ['john-smith'], date: new Date(2024, 2, 12) },
|
|
39
|
+
{ id: 12, title: 'Quantum Computing Explained', description: 'A simple introduction to a complex topic.', category: ['cat6'], author: ['peter-jones'], date: new Date(2023, 9, 9) },
|
|
40
|
+
];
|