@kvass/widgets 1.0.18 → 1.1.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/_plugin-vue_export-helper.js +1 -0
- package/dist/contact.js +8 -8
- package/dist/img-comparison-slider.js +2 -0
- package/dist/project-portal.js +6 -0
- package/index.html +20 -0
- package/package.json +9 -8
- package/src/contact/README.md +1 -0
- package/src/contact/components/Field.ce.vue +5 -2
- package/src/contact/components/Form.ce.vue +7 -3
- package/src/img-comparison-slider/README.md +69 -0
- package/src/img-comparison-slider/components/ImgComparisonSlider.ce.vue +139 -0
- package/src/img-comparison-slider/main.js +7 -0
- package/src/project-portal/App.ce.vue +308 -0
- package/src/project-portal/api.js +48 -0
- package/src/project-portal/assets/logo.png +0 -0
- package/src/project-portal/assets/map-pin-solid.svg +1 -0
- package/src/project-portal/components/Card.ce.vue +110 -0
- package/src/project-portal/components/Category.ce.vue +87 -0
- package/src/project-portal/components/CategorySelector.ce.vue +43 -0
- package/src/project-portal/components/ProjectTypeSelector.ce.vue +70 -0
- package/src/project-portal/main.js +16 -0
- package/src/project-portal/styles/_variables.scss +19 -0
- package/src/project-portal/styles/components/_card.scss +178 -0
- package/vite.config.js +9 -7
|
@@ -6,7 +6,7 @@ export default {
|
|
|
6
6
|
</script>
|
|
7
7
|
|
|
8
8
|
<script setup>
|
|
9
|
-
import { computed, ref } from 'vue'
|
|
9
|
+
import { computed, ref } from 'vue';
|
|
10
10
|
|
|
11
11
|
const emit = defineEmits(['update:modelValue'])
|
|
12
12
|
const props = defineProps({
|
|
@@ -76,9 +76,11 @@ function onBlur() {
|
|
|
76
76
|
margin-left: var(--kvass-contact-label-transform, 0);
|
|
77
77
|
font-weight: var(--kvass-contact-label-weight, initial);
|
|
78
78
|
}
|
|
79
|
+
|
|
79
80
|
input {
|
|
80
81
|
-webkit-appearance: none;
|
|
81
82
|
}
|
|
83
|
+
|
|
82
84
|
&__element {
|
|
83
85
|
padding: calc(
|
|
84
86
|
var(--kvass-contact-spacing, var(--kvass-contact-default-spacing)) / 2
|
|
@@ -106,6 +108,7 @@ function onBlur() {
|
|
|
106
108
|
);
|
|
107
109
|
resize: vertical;
|
|
108
110
|
font: inherit;
|
|
111
|
+
color: inherit;
|
|
109
112
|
background-color: var(
|
|
110
113
|
--kvass-contact-input-background,
|
|
111
114
|
var(--kvass-contact-default-input-background)
|
|
@@ -126,7 +129,7 @@ function onBlur() {
|
|
|
126
129
|
}
|
|
127
130
|
|
|
128
131
|
&--required {
|
|
129
|
-
.kvass-contact-field__label
|
|
132
|
+
.kvass-contact-field__label::after {
|
|
130
133
|
content: '*';
|
|
131
134
|
color: var(--kvass-contact-error, var(--kvass-contact-default-error));
|
|
132
135
|
margin-left: 0.3rem;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { onMounted, reactive, ref,
|
|
2
|
+
import { computed, onMounted, reactive, ref, useAttrs } from 'vue'
|
|
3
3
|
import { createLead, getProjects } from '../api'
|
|
4
4
|
import { Capitalize } from '../utils'
|
|
5
5
|
|
|
6
|
+
import Checkbox from './Checkbox.ce.vue'
|
|
6
7
|
import Field from './Field.ce.vue'
|
|
7
8
|
import Fieldset from './Fieldset.ce.vue'
|
|
8
|
-
import Checkbox from './Checkbox.ce.vue'
|
|
9
9
|
|
|
10
10
|
const Labels = {
|
|
11
11
|
nb: {
|
|
@@ -248,6 +248,10 @@ onMounted(() => {
|
|
|
248
248
|
|
|
249
249
|
--kvass-contact-default-background: #ffffff;
|
|
250
250
|
--kvass-contact-default-spacing: 2rem;
|
|
251
|
+
--kvass-contact-default-padding: var(
|
|
252
|
+
--kvass-contact-spacing,
|
|
253
|
+
var(--kvass-contact-default-spacing)
|
|
254
|
+
);
|
|
251
255
|
--kvass-contact-default-border-radius: 4px;
|
|
252
256
|
--kvass-contact-default-border-color: #eaeaea;
|
|
253
257
|
--kvass-contact-default-border-width: 1px;
|
|
@@ -275,7 +279,7 @@ onMounted(() => {
|
|
|
275
279
|
--kvass-contact-background,
|
|
276
280
|
var(--kvass-contact-default-background)
|
|
277
281
|
);
|
|
278
|
-
padding: var(--kvass-contact-
|
|
282
|
+
padding: var(--kvass-contact-padding, var(--kvass-contact-default-padding));
|
|
279
283
|
border-radius: var(
|
|
280
284
|
--kvass-contact-border-radius,
|
|
281
285
|
var(--kvass-contact-default-border-radius)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# kvass-img-comparison-slider
|
|
2
|
+
|
|
3
|
+
A simple, embeddable Web Component to compare images.
|
|
4
|
+
|
|
5
|
+
`https://unpkg.com/@kvass/widgets@latest/dist/img-comparison-slider.js`
|
|
6
|
+
|
|
7
|
+
## Develop
|
|
8
|
+
|
|
9
|
+
To run in development mode, first install the neccessary packages.
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
npm install
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Then, run in development mode.
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
npm run dev
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Open `localhost:3000` in the browser of your choice, and you will see the form widget.
|
|
22
|
+
|
|
23
|
+
## Build
|
|
24
|
+
|
|
25
|
+
To build the widget for production, run `build` instead of `dev`.
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
npm run build
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
To use the widget, use the `<kvass-img-comparison-slider />` element as shown here.
|
|
32
|
+
|
|
33
|
+
```html
|
|
34
|
+
<kvass-img-comparison-slider
|
|
35
|
+
first-image="https://example.com/first-image,First image"
|
|
36
|
+
second-image="https://example.com/second-image,Second image"
|
|
37
|
+
options="direction:vertical,keyboard:disabled"
|
|
38
|
+
></kvass-img-comparison-slider>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Props
|
|
42
|
+
|
|
43
|
+
The component has several props for easy configuration.
|
|
44
|
+
|
|
45
|
+
| Name | Type | Description | Enums |
|
|
46
|
+
| :---------- | :------ | :-------------------------------------------------------- | :---------------------- |
|
|
47
|
+
| **options** | String | key:value pairs separated by comma | The following options: |
|
|
48
|
+
| value | Number | Position of the divider in percents. | `0...100` |
|
|
49
|
+
| hover | Boolean | Automatically slide on mouse over. | `false`, `true` |
|
|
50
|
+
| direction | String | Set slider direction. | `horiontal`, `vertical` |
|
|
51
|
+
| nonce | | Define nonce which gets passed to inline style. | |
|
|
52
|
+
| keyboard | String | Enable/disable slider position control with the keyboard. | `enabled`, `disabled` |
|
|
53
|
+
| handle | Boolean | Enable/disable dragging by handle only. | `false`, `true` |
|
|
54
|
+
| | | | |
|
|
55
|
+
| first-image | String | Image url and text, separated by a comma | |
|
|
56
|
+
| second-mage | String | Image url and text, separated by a comma | |
|
|
57
|
+
| handle-svg | String | The svg on the slider handle | |
|
|
58
|
+
|
|
59
|
+
## Styling
|
|
60
|
+
|
|
61
|
+
The widget's styles are based on CSS custom properties, and can be overwritten.
|
|
62
|
+
These are the available CSS variables.
|
|
63
|
+
|
|
64
|
+
| Name | Description | Default |
|
|
65
|
+
| :------------------------ | :--------------------------------------------------------------------------------------- | :------ |
|
|
66
|
+
| `--divider-width` | Width of the vertical line separating both images | `1px` |
|
|
67
|
+
| `--divider-color` | Color of the vertical line separating the two images | `#fff` |
|
|
68
|
+
| `--divider-shadow` | Shadow cast by the vertical line separating the two images | `none` |
|
|
69
|
+
| `--handle-position-start` | Handle position on the divider axis. In case the handle must be displaced off the center | `50%` |
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { ImgComparisonSlider } from '@img-comparison-slider/vue';
|
|
3
|
+
import { computed } from 'vue';
|
|
4
|
+
|
|
5
|
+
const defaultOptions = {
|
|
6
|
+
value: 50,
|
|
7
|
+
hover: false,
|
|
8
|
+
direction: 'horizontal',
|
|
9
|
+
keyboard: 'enabled',
|
|
10
|
+
handle: false,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function createImageObject(imageString) {
|
|
14
|
+
if (!imageString) return {}
|
|
15
|
+
|
|
16
|
+
const split = imageString.split(',')
|
|
17
|
+
return {
|
|
18
|
+
url: split[0],
|
|
19
|
+
description: split[1]
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const props = defineProps({
|
|
24
|
+
firstImage: {
|
|
25
|
+
type: String,
|
|
26
|
+
default: 'https://assets.kvass.no/641c0b087c49867b0b1065ec,Første bilde'
|
|
27
|
+
},
|
|
28
|
+
secondImage: {
|
|
29
|
+
type: String,
|
|
30
|
+
default: 'https://assets.kvass.no/641c0a8c7c49867b0b106570,Andre bilde'
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
options: {
|
|
34
|
+
type: String,
|
|
35
|
+
default: '',
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
handleSvg: {
|
|
39
|
+
type: String,
|
|
40
|
+
default:
|
|
41
|
+
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="-8 -3 16 6"><path stroke="#fff" d="M -3 -2 L -5 0 L -3 2 M -3 -2 L -3 2 M 3 -2 L 5 0 L 3 2 M 3 -2 L 3 2" stroke-width="1" fill="#fff" vector-effect="non-scaling-stroke"></path></svg>',
|
|
42
|
+
},
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
// images
|
|
46
|
+
const firstImage = createImageObject(props.firstImage)
|
|
47
|
+
const firstImageCaption = computed(() => firstImage.description)
|
|
48
|
+
|
|
49
|
+
const secondImage = createImageObject(props.secondImage)
|
|
50
|
+
const secondImageCaption = computed(() => secondImage.description)
|
|
51
|
+
|
|
52
|
+
// options
|
|
53
|
+
const options = computed(() => {
|
|
54
|
+
const entries = props.options.split(',').map(entry => entry.split(':'))
|
|
55
|
+
return Object.fromEntries(entries)
|
|
56
|
+
})
|
|
57
|
+
</script>
|
|
58
|
+
|
|
59
|
+
<template>
|
|
60
|
+
<ImgComparisonSlider
|
|
61
|
+
:style="`--first-image-caption: ${firstImageCaption}; --second-image-caption: ${secondImageCaption}`" tabindex="0"
|
|
62
|
+
class="img-comparison-slider" v-bind="{
|
|
63
|
+
...defaultOptions,
|
|
64
|
+
...options,
|
|
65
|
+
}">
|
|
66
|
+
<img slot="first" class="img-comparison-slider__image" :src="firstImage.url" />
|
|
67
|
+
<img slot="second" class="img-comparison-slider__image" :src="secondImage.url" />
|
|
68
|
+
|
|
69
|
+
<div slot="handle" class="handle">
|
|
70
|
+
<p class="handle__caption handle__caption--left">
|
|
71
|
+
{{ firstImage.description }}
|
|
72
|
+
</p>
|
|
73
|
+
<div class="handle__svg" v-html="handleSvg"></div>
|
|
74
|
+
<p class="handle__caption handle__caption--right">
|
|
75
|
+
{{ secondImage.description }}
|
|
76
|
+
</p>
|
|
77
|
+
</div>
|
|
78
|
+
</ImgComparisonSlider>
|
|
79
|
+
</template>
|
|
80
|
+
|
|
81
|
+
<style lang="scss">
|
|
82
|
+
.img-comparison-slider {
|
|
83
|
+
width: 100%;
|
|
84
|
+
height: 100%;
|
|
85
|
+
|
|
86
|
+
--divider-width: 4px;
|
|
87
|
+
--divider-color: black;
|
|
88
|
+
|
|
89
|
+
// aspect-ratio: var(--kvass-img-comparison-slider-aspect-ratio, $aspect-ratio);
|
|
90
|
+
// aspect-ratio: var(
|
|
91
|
+
// --kvass-img-comparison-slider-aspect-ratio,
|
|
92
|
+
// $aspect-ratio
|
|
93
|
+
// );
|
|
94
|
+
|
|
95
|
+
&__image {
|
|
96
|
+
width: 100%;
|
|
97
|
+
height: 100%;
|
|
98
|
+
object-fit: cover;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.handle {
|
|
102
|
+
display: flex;
|
|
103
|
+
flex-direction: row;
|
|
104
|
+
|
|
105
|
+
justify-content: center;
|
|
106
|
+
align-items: center;
|
|
107
|
+
flex-wrap: nowrap;
|
|
108
|
+
gap: 1rem;
|
|
109
|
+
min-width: 800px;
|
|
110
|
+
|
|
111
|
+
font-size: 1rem;
|
|
112
|
+
|
|
113
|
+
@media (max-width: 600px) {
|
|
114
|
+
font-size: 0.75rem;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
&__caption {
|
|
118
|
+
color: white;
|
|
119
|
+
word-wrap: wrap;
|
|
120
|
+
width: 100%;
|
|
121
|
+
|
|
122
|
+
&--left {
|
|
123
|
+
text-align: right;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
&__svg {
|
|
128
|
+
padding: 0.3em;
|
|
129
|
+
background-color: black;
|
|
130
|
+
border-radius: 100%;
|
|
131
|
+
|
|
132
|
+
svg {
|
|
133
|
+
width: 2.5em;
|
|
134
|
+
height: 2.5em;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
</style>
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<!-- <Loader :value="promise"> -->
|
|
3
|
+
<div class="project-selector" :class="`project-selector--theme-${theme}`">
|
|
4
|
+
<div
|
|
5
|
+
v-if="!disableNav || !navItems.length"
|
|
6
|
+
class="project-selector__navigation"
|
|
7
|
+
>
|
|
8
|
+
<CategorySelector
|
|
9
|
+
class="project-selector__navigation-category"
|
|
10
|
+
v-if="navItems.length"
|
|
11
|
+
:items="navItems"
|
|
12
|
+
:value="category"
|
|
13
|
+
@input="
|
|
14
|
+
($ev) => {
|
|
15
|
+
category = $ev
|
|
16
|
+
filterItems()
|
|
17
|
+
}
|
|
18
|
+
"
|
|
19
|
+
/>
|
|
20
|
+
|
|
21
|
+
<ProjectTypeSelector
|
|
22
|
+
v-if="projectTypes.length > 1"
|
|
23
|
+
class="project-selector__navigation-project-type"
|
|
24
|
+
:items="projectTypes"
|
|
25
|
+
:value="projectType"
|
|
26
|
+
@input="
|
|
27
|
+
($ev) => {
|
|
28
|
+
projectType = $ev.target.value
|
|
29
|
+
filterItems()
|
|
30
|
+
}
|
|
31
|
+
"
|
|
32
|
+
/>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<transition-group
|
|
36
|
+
v-if="items && items.length"
|
|
37
|
+
tag="div"
|
|
38
|
+
name="list"
|
|
39
|
+
appear
|
|
40
|
+
class="project-selector__card"
|
|
41
|
+
>
|
|
42
|
+
<Card
|
|
43
|
+
v-for="(item, index) in items"
|
|
44
|
+
:disable-label="disableNav"
|
|
45
|
+
:key="index"
|
|
46
|
+
:item="item"
|
|
47
|
+
theme="border"
|
|
48
|
+
/>
|
|
49
|
+
</transition-group>
|
|
50
|
+
|
|
51
|
+
<div class="project-selector__no-result" v-else>Ingen resultater</div>
|
|
52
|
+
</div>
|
|
53
|
+
<!-- </Loader> -->
|
|
54
|
+
</template>
|
|
55
|
+
|
|
56
|
+
<script>
|
|
57
|
+
export default {
|
|
58
|
+
created() {
|
|
59
|
+
this.fetch()
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
</script>
|
|
63
|
+
|
|
64
|
+
<script setup>
|
|
65
|
+
import { ref, computed } from 'vue'
|
|
66
|
+
|
|
67
|
+
import Card from './components/Card.ce.vue'
|
|
68
|
+
import CategorySelector from './components/CategorySelector.ce.vue'
|
|
69
|
+
import ProjectTypeSelector from './components/ProjectTypeSelector.ce.vue'
|
|
70
|
+
// import { LoaderComponent as Loader } from 'vue-elder-loader'
|
|
71
|
+
|
|
72
|
+
import { getProjects } from './api'
|
|
73
|
+
|
|
74
|
+
function getSortValue(type, value) {
|
|
75
|
+
switch (type) {
|
|
76
|
+
case 'status':
|
|
77
|
+
switch (value[type]) {
|
|
78
|
+
case 'sale':
|
|
79
|
+
return 2
|
|
80
|
+
case 'upcoming':
|
|
81
|
+
return 1
|
|
82
|
+
default:
|
|
83
|
+
return 0
|
|
84
|
+
}
|
|
85
|
+
case 'name':
|
|
86
|
+
return value[type]
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const props = defineProps({
|
|
91
|
+
source: {
|
|
92
|
+
type: String,
|
|
93
|
+
default: 'https://feature.kvass.no',
|
|
94
|
+
},
|
|
95
|
+
startCategory: {
|
|
96
|
+
type: String,
|
|
97
|
+
default: 'all',
|
|
98
|
+
enums: ['all', 'sale', 'upcoming', 'development', 'sold'],
|
|
99
|
+
},
|
|
100
|
+
enabledCategories: {
|
|
101
|
+
type: String,
|
|
102
|
+
default: 'all,sale,upcoming,development,sold',
|
|
103
|
+
},
|
|
104
|
+
theme: {
|
|
105
|
+
type: String,
|
|
106
|
+
enum: ['default', 'tiles'],
|
|
107
|
+
default: 'default',
|
|
108
|
+
},
|
|
109
|
+
sortOn: {
|
|
110
|
+
type: String,
|
|
111
|
+
enum: ['status', 'name'],
|
|
112
|
+
default: 'status',
|
|
113
|
+
},
|
|
114
|
+
// triggerLabel: {
|
|
115
|
+
// type: String,
|
|
116
|
+
// default: 'Velg type',
|
|
117
|
+
// },
|
|
118
|
+
disableNav: {
|
|
119
|
+
type: Boolean,
|
|
120
|
+
default: false,
|
|
121
|
+
},
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
const category = ref('')
|
|
125
|
+
const projectType = ref('none')
|
|
126
|
+
const items = ref([])
|
|
127
|
+
var allItems = []
|
|
128
|
+
const promise = ref(null)
|
|
129
|
+
|
|
130
|
+
const navItems = computed(() => {
|
|
131
|
+
return [
|
|
132
|
+
...props.enabledCategories.split(',').filter((i) => {
|
|
133
|
+
if (i === 'all') return true
|
|
134
|
+
return allItems.find((k) => k.status.includes(i))
|
|
135
|
+
}),
|
|
136
|
+
]
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
const projectTypes = computed(() => {
|
|
140
|
+
let types = ['none'].concat(
|
|
141
|
+
allItems.map((i) => {
|
|
142
|
+
if (i.customFields && i.customFields['project-type'])
|
|
143
|
+
return i.customFields['project-type']
|
|
144
|
+
}),
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
return [...new Set(types || [])].filter(Boolean)
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
function filterItems() {
|
|
151
|
+
items.value = allItems
|
|
152
|
+
.filter((i) => {
|
|
153
|
+
if (category.value === 'all') return true
|
|
154
|
+
return i.status.includes(category.value)
|
|
155
|
+
})
|
|
156
|
+
.filter((i) => {
|
|
157
|
+
if (!projectTypes.length || projectType.value === 'none') return true
|
|
158
|
+
if (!i.customFields || !i.customFields['project-type']) return
|
|
159
|
+
return i.customFields['project-type'].includes(projectType.value)
|
|
160
|
+
})
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function getFromSource() {
|
|
164
|
+
if (props.source) return getProjects(props.source)
|
|
165
|
+
return Promise.resolve()
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function getStatus(item) {
|
|
169
|
+
if (item.status) return item.status
|
|
170
|
+
let total = item.stats.total
|
|
171
|
+
if (item.isPublished && total && !item.stats.sale) return 'sold'
|
|
172
|
+
if (item.isPublished && total) return 'sale'
|
|
173
|
+
if (item.isPublished && !total) return 'upcoming'
|
|
174
|
+
if (!item.isPublished) return 'development'
|
|
175
|
+
return 'development'
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function fetch() {
|
|
179
|
+
promise.value = getFromSource()
|
|
180
|
+
.then(async (data) => {
|
|
181
|
+
let items = []
|
|
182
|
+
//read from global customItems
|
|
183
|
+
if (typeof customItems !== 'undefined') {
|
|
184
|
+
items =
|
|
185
|
+
typeof customItems === 'function' ? await customItems() : customItems
|
|
186
|
+
}
|
|
187
|
+
//remove kvass projects that is defined in customItems
|
|
188
|
+
let kvassProjects = data
|
|
189
|
+
? data.Projects.filter((item) => {
|
|
190
|
+
if (items.find((i) => (i.id ? i.id.includes(item.id) : undefined)))
|
|
191
|
+
return
|
|
192
|
+
return item
|
|
193
|
+
})
|
|
194
|
+
: []
|
|
195
|
+
|
|
196
|
+
allItems = [...kvassProjects, ...items]
|
|
197
|
+
|
|
198
|
+
return (allItems = allItems
|
|
199
|
+
.map((item) => {
|
|
200
|
+
item.status = getStatus(item)
|
|
201
|
+
return {
|
|
202
|
+
intro: item.customFields ? item.customFields['project-intro'] : '',
|
|
203
|
+
...item,
|
|
204
|
+
sortVale: getSortValue(props.sortOn, item),
|
|
205
|
+
url: item.url,
|
|
206
|
+
}
|
|
207
|
+
})
|
|
208
|
+
.sort((a, b) => {
|
|
209
|
+
switch (props.sortOn) {
|
|
210
|
+
case 'status':
|
|
211
|
+
if (a.sortVale < b.sortVale) return 1
|
|
212
|
+
if (a.sortVale > b.sortVale) return -1
|
|
213
|
+
}
|
|
214
|
+
})
|
|
215
|
+
.filter((i) => props.enabledCategories.split(',').includes(i.status)))
|
|
216
|
+
})
|
|
217
|
+
.then(() => {
|
|
218
|
+
category.value = props.startCategory
|
|
219
|
+
items.value = allItems
|
|
220
|
+
})
|
|
221
|
+
}
|
|
222
|
+
</script>
|
|
223
|
+
|
|
224
|
+
<style lang="scss">
|
|
225
|
+
@import './styles/_variables';
|
|
226
|
+
.project-selector {
|
|
227
|
+
$gap: 1.5rem;
|
|
228
|
+
&__navigation {
|
|
229
|
+
display: flex;
|
|
230
|
+
justify-content: var(--kvass-project-selector-nav-position, center);
|
|
231
|
+
padding: 0 2rem;
|
|
232
|
+
padding-bottom: 3rem;
|
|
233
|
+
gap: $gap;
|
|
234
|
+
@media (max-width: $kvass-project-selector-resposive) {
|
|
235
|
+
flex-direction: column-reverse;
|
|
236
|
+
justify-content: center;
|
|
237
|
+
gap: $gap - 1rem;
|
|
238
|
+
}
|
|
239
|
+
&-category {
|
|
240
|
+
display: flex;
|
|
241
|
+
justify-content: center;
|
|
242
|
+
gap: $gap;
|
|
243
|
+
@media (max-width: $kvass-project-selector-resposive) {
|
|
244
|
+
flex-direction: column;
|
|
245
|
+
gap: $gap - 1rem;
|
|
246
|
+
align-items: center;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
&__card {
|
|
251
|
+
position: relative;
|
|
252
|
+
display: grid;
|
|
253
|
+
grid-template-columns: repeat(
|
|
254
|
+
var(--kvass-project-selector-grid-columns, 4),
|
|
255
|
+
1fr
|
|
256
|
+
);
|
|
257
|
+
gap: var(--kvass-project-selector-grid-gap, 2rem);
|
|
258
|
+
@media (max-width: $kvass-project-selector-resposive) {
|
|
259
|
+
grid-template-columns: 1fr;
|
|
260
|
+
padding-top: 2rem;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
&__no-result {
|
|
264
|
+
font-size: 1.2em;
|
|
265
|
+
text-align: center;
|
|
266
|
+
display: flex;
|
|
267
|
+
justify-content: center;
|
|
268
|
+
align-items: center;
|
|
269
|
+
min-height: 200px;
|
|
270
|
+
margin: 2rem 0;
|
|
271
|
+
background-color: GetVariable('light-grey');
|
|
272
|
+
@media (max-width: $kvass-project-selector-resposive) {
|
|
273
|
+
min-height: 100px;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
.list {
|
|
277
|
+
&-leave-active {
|
|
278
|
+
position: absolute;
|
|
279
|
+
}
|
|
280
|
+
&-move,
|
|
281
|
+
&-enter-active,
|
|
282
|
+
&-leave-active {
|
|
283
|
+
transition: all 500ms ease;
|
|
284
|
+
}
|
|
285
|
+
&-enter {
|
|
286
|
+
transform: scale(0.95);
|
|
287
|
+
}
|
|
288
|
+
&-enter,
|
|
289
|
+
&-leave-to {
|
|
290
|
+
opacity: 0;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
// tiles theme
|
|
295
|
+
.project-selector--theme-tiles {
|
|
296
|
+
.project-selector__card {
|
|
297
|
+
grid-template-columns: repeat(
|
|
298
|
+
var(--kvass-project-selector-grid-columns, 2),
|
|
299
|
+
1fr
|
|
300
|
+
);
|
|
301
|
+
gap: var(--kvass-project-selector-grid-gap, 0rem);
|
|
302
|
+
@media (max-width: $kvass-project-selector-resposive) {
|
|
303
|
+
grid-template-columns: 1fr;
|
|
304
|
+
padding-top: 2rem;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
</style>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
function getProjects(url) {
|
|
2
|
+
return fetch(`${url}/api/graphql`, {
|
|
3
|
+
method: 'POST',
|
|
4
|
+
headers: {
|
|
5
|
+
'Content-Type': 'application/json',
|
|
6
|
+
},
|
|
7
|
+
body: JSON.stringify({
|
|
8
|
+
query: `
|
|
9
|
+
query {
|
|
10
|
+
Projects {
|
|
11
|
+
id
|
|
12
|
+
name
|
|
13
|
+
url
|
|
14
|
+
isPublished
|
|
15
|
+
media {
|
|
16
|
+
cover {
|
|
17
|
+
url
|
|
18
|
+
type
|
|
19
|
+
}
|
|
20
|
+
gallery {
|
|
21
|
+
url
|
|
22
|
+
type
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
address {
|
|
26
|
+
street
|
|
27
|
+
city
|
|
28
|
+
county
|
|
29
|
+
postcode
|
|
30
|
+
location {
|
|
31
|
+
coordinates
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
stats {
|
|
35
|
+
total
|
|
36
|
+
sale
|
|
37
|
+
}
|
|
38
|
+
customFields(keys: ["project-intro", "project-type"])
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
`,
|
|
42
|
+
}),
|
|
43
|
+
})
|
|
44
|
+
.then((res) => res.json())
|
|
45
|
+
.then((res) => res.data)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export { getProjects }
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="map-pin" class="svg-inline--fa fa-map-pin fa-w-9" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 288 512"><path fill="currentColor" d="M112 316.94v156.69l22.02 33.02c4.75 7.12 15.22 7.12 19.97 0L176 473.63V316.94c-10.39 1.92-21.06 3.06-32 3.06s-21.61-1.14-32-3.06zM144 0C64.47 0 0 64.47 0 144s64.47 144 144 144 144-64.47 144-144S223.53 0 144 0zm0 76c-37.5 0-68 30.5-68 68 0 6.62-5.38 12-12 12s-12-5.38-12-12c0-50.73 41.28-92 92-92 6.62 0 12 5.38 12 12s-5.38 12-12 12z"></path></svg>
|