@nexxtmove/ui 0.1.38 → 0.1.40
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/index.d.ts +71 -25
- package/dist/index.js +2304 -1772
- package/package.json +2 -1
- package/src/components/AnimatedNumber/AnimatedNumber.vue +4 -48
- package/src/components/CustomerJourneyTile/CustomerJourneyTile.vue +39 -24
- package/src/components/DropdownButton/DropdownButton.vue +31 -19
- package/src/components/Header/Header.vue +3 -1
- package/src/components/InputField/InputField.vue +6 -5
- package/src/components/Table/Table.vue +37 -7
- package/src/components/TimelineEvent/TimelineTypes.ts +10 -2
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nexxtmove/ui",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.40",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"tailwind"
|
|
21
21
|
],
|
|
22
22
|
"dependencies": {
|
|
23
|
+
"@number-flow/vue": "^0.5.0",
|
|
23
24
|
"date-fns": "^4.1.0",
|
|
24
25
|
"date-fns-tz": "^3.2.0",
|
|
25
26
|
"reka-ui": "^2.9.2"
|
|
@@ -1,65 +1,21 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import
|
|
2
|
+
import NumberFlow from '@number-flow/vue'
|
|
3
3
|
|
|
4
4
|
defineOptions({
|
|
5
5
|
name: 'NexxtAnimatedNumber',
|
|
6
6
|
})
|
|
7
7
|
|
|
8
|
-
type EasingFunction = 'ease-linear' | 'ease-in' | 'ease-out' | 'ease-in-out'
|
|
9
|
-
|
|
10
8
|
const {
|
|
11
9
|
value,
|
|
12
10
|
duration = 500,
|
|
13
|
-
|
|
11
|
+
delay = 0,
|
|
14
12
|
} = defineProps<{
|
|
15
13
|
value: number
|
|
16
14
|
duration?: number
|
|
17
|
-
|
|
15
|
+
delay?: number
|
|
18
16
|
}>()
|
|
19
|
-
|
|
20
|
-
const direction = ref<'up' | 'down'>('up')
|
|
21
|
-
const digits = computed(() => String(value).split(''))
|
|
22
|
-
const transitionDuration = computed(() => `duration-${duration}`)
|
|
23
|
-
|
|
24
|
-
watch(
|
|
25
|
-
() => value,
|
|
26
|
-
(newVal, oldVal) => {
|
|
27
|
-
direction.value = newVal > oldVal ? 'up' : 'down'
|
|
28
|
-
},
|
|
29
|
-
)
|
|
30
17
|
</script>
|
|
31
18
|
|
|
32
19
|
<template>
|
|
33
|
-
<
|
|
34
|
-
tag="div"
|
|
35
|
-
class="relative flex shrink-0 items-center tabular-nums"
|
|
36
|
-
:aria-label="value"
|
|
37
|
-
:enter-active-class="`transition-all ${transitionDuration} ${easing}`"
|
|
38
|
-
:leave-active-class="`absolute transition-all ${transitionDuration} ${easing}`"
|
|
39
|
-
:enter-from-class="
|
|
40
|
-
direction === 'up' ? 'translate-y-full opacity-0' : '-translate-y-full opacity-0'
|
|
41
|
-
"
|
|
42
|
-
:leave-to-class="
|
|
43
|
-
direction === 'up' ? '-translate-y-full opacity-0' : 'translate-y-full opacity-0'
|
|
44
|
-
"
|
|
45
|
-
move-class="transition-all duration-500 ease-in-out"
|
|
46
|
-
>
|
|
47
|
-
<div
|
|
48
|
-
v-for="(digit, i) in digits"
|
|
49
|
-
:key="i"
|
|
50
|
-
class="relative inline-flex h-[1.5em] w-[1ch] items-center justify-center overflow-hidden"
|
|
51
|
-
aria-hidden="true"
|
|
52
|
-
>
|
|
53
|
-
<Transition
|
|
54
|
-
:enter-active-class="`transition-transform ${transitionDuration} ${easing}`"
|
|
55
|
-
:leave-active-class="`absolute inset-0 transition-transform ${transitionDuration} ${easing}`"
|
|
56
|
-
:enter-from-class="direction === 'up' ? 'translate-y-full' : '-translate-y-full'"
|
|
57
|
-
:leave-to-class="direction === 'up' ? '-translate-y-full' : 'translate-y-full'"
|
|
58
|
-
>
|
|
59
|
-
<span :key="digit" class="flex h-full w-full items-center justify-center">
|
|
60
|
-
{{ digit }}
|
|
61
|
-
</span>
|
|
62
|
-
</Transition>
|
|
63
|
-
</div>
|
|
64
|
-
</TransitionGroup>
|
|
20
|
+
<NumberFlow :value="value" :transform-timing="{ duration, delay }" />
|
|
65
21
|
</template>
|
|
@@ -1,35 +1,43 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
2
|
import NexxtIcon from '../Icon/Icon.vue'
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
import NexxtAnimatedNumber from '../AnimatedNumber/AnimatedNumber.vue'
|
|
4
|
+
import { computed, onMounted, ref } from 'vue'
|
|
5
|
+
import { PHASE_MAP } from '../TimelineEvent/TimelineTypes'
|
|
6
6
|
|
|
7
7
|
interface NexxtCustomerJourneyTileProps {
|
|
8
|
+
as?: keyof HTMLElementTagNameMap
|
|
8
9
|
title?: string
|
|
9
10
|
amount?: number
|
|
11
|
+
animated?: boolean
|
|
12
|
+
delay?: number
|
|
10
13
|
percentage?: number
|
|
11
14
|
percentageExplanaition?: string
|
|
12
15
|
}
|
|
13
16
|
|
|
14
|
-
const PHASE_ICON_MAP: Record<Phase, string> = {
|
|
15
|
-
Latent: 'eye-closed',
|
|
16
|
-
Oriëntatie: 'building-magnifying-glass',
|
|
17
|
-
'Actief zoeken': 'house-person-return',
|
|
18
|
-
Selectie: 'handshake',
|
|
19
|
-
Transactie: 'pencil-mechanical',
|
|
20
|
-
Wonen: 'house-heart',
|
|
21
|
-
}
|
|
22
|
-
|
|
23
17
|
defineOptions({
|
|
24
18
|
name: 'NexxtCustomerJourneyTile',
|
|
25
19
|
})
|
|
26
20
|
|
|
27
|
-
const {
|
|
28
|
-
|
|
21
|
+
const {
|
|
22
|
+
as = 'div',
|
|
23
|
+
title,
|
|
24
|
+
amount,
|
|
25
|
+
animated = false,
|
|
26
|
+
delay,
|
|
27
|
+
percentage,
|
|
28
|
+
percentageExplanaition,
|
|
29
|
+
} = defineProps<NexxtCustomerJourneyTileProps>()
|
|
30
|
+
|
|
31
|
+
const displayAmount = ref(animated ? 0 : (amount ?? 0))
|
|
32
|
+
|
|
33
|
+
onMounted(() => {
|
|
34
|
+
if (animated && amount !== undefined) displayAmount.value = amount
|
|
35
|
+
})
|
|
29
36
|
|
|
30
37
|
const icon = computed(() => {
|
|
31
38
|
if (!title) return null
|
|
32
|
-
|
|
39
|
+
const phase = Object.values(PHASE_MAP).find((p) => p.label === title)
|
|
40
|
+
return (phase?.icon as InstanceType<typeof NexxtIcon>['name']) || null
|
|
33
41
|
})
|
|
34
42
|
|
|
35
43
|
const ariaLabel = computed(() => {
|
|
@@ -46,10 +54,10 @@ const ariaLabel = computed(() => {
|
|
|
46
54
|
</script>
|
|
47
55
|
|
|
48
56
|
<template>
|
|
49
|
-
<
|
|
50
|
-
|
|
57
|
+
<component
|
|
58
|
+
:is="as"
|
|
51
59
|
:aria-label="ariaLabel"
|
|
52
|
-
class="flex
|
|
60
|
+
class="flex rounded-xl bg-white outline outline-gray-200 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-cornflower-blue-500"
|
|
53
61
|
:class="icon ? 'gap-6 p-1.5' : 'p-4'"
|
|
54
62
|
>
|
|
55
63
|
<div
|
|
@@ -63,19 +71,26 @@ const ariaLabel = computed(() => {
|
|
|
63
71
|
<span
|
|
64
72
|
class="heading-1"
|
|
65
73
|
:class="icon ? 'text-purple-500' : 'text-sapphire-900'"
|
|
66
|
-
v-if="amount"
|
|
67
|
-
>{{ amount }}</span
|
|
74
|
+
v-if="amount !== undefined"
|
|
68
75
|
>
|
|
69
|
-
|
|
76
|
+
<NexxtAnimatedNumber :value="displayAmount" :delay="delay" />
|
|
77
|
+
</span>
|
|
78
|
+
<div v-if="percentage !== undefined" class="flex gap-0.5">
|
|
70
79
|
<span
|
|
71
80
|
class="extra-small-semibold"
|
|
72
|
-
:class="
|
|
81
|
+
:class="
|
|
82
|
+
percentage > 0
|
|
83
|
+
? 'text-green-500'
|
|
84
|
+
: percentage === 0
|
|
85
|
+
? 'text-orange-500'
|
|
86
|
+
: 'text-brick-500'
|
|
87
|
+
"
|
|
73
88
|
><template v-if="percentage > 0">+</template>{{ percentage }}%</span
|
|
74
89
|
>
|
|
75
|
-
<span class="extra-small-normal" v-if="percentageExplanaition">{{
|
|
90
|
+
<span class="ml-0.5 text-left extra-small-normal" v-if="percentageExplanaition">{{
|
|
76
91
|
percentageExplanaition
|
|
77
92
|
}}</span>
|
|
78
93
|
</div>
|
|
79
94
|
</div>
|
|
80
|
-
</
|
|
95
|
+
</component>
|
|
81
96
|
</template>
|
|
@@ -6,14 +6,21 @@ interface NexxtDropdownButton {
|
|
|
6
6
|
label: string
|
|
7
7
|
action: string | (() => void)
|
|
8
8
|
icon?: InstanceType<typeof NexxtIcon>['name']
|
|
9
|
+
selected?: boolean
|
|
9
10
|
}
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
type NexxtDropdownButtonBase = {
|
|
12
13
|
variant?: 'primary' | 'secondary'
|
|
13
14
|
options: NexxtDropdownButton[]
|
|
14
15
|
placement?: 'left' | 'right'
|
|
16
|
+
triggerIcon?: InstanceType<typeof NexxtIcon>['name']
|
|
17
|
+
icon?: InstanceType<typeof NexxtIcon>['name']
|
|
15
18
|
}
|
|
16
19
|
|
|
20
|
+
export type NexxtDropdownButtonProps =
|
|
21
|
+
| (NexxtDropdownButtonBase & { iconOnly?: false; label: string; action: string | (() => void) })
|
|
22
|
+
| (NexxtDropdownButtonBase & { iconOnly: true; label?: string; action?: string | (() => void) })
|
|
23
|
+
|
|
17
24
|
const {
|
|
18
25
|
variant = 'primary',
|
|
19
26
|
options,
|
|
@@ -21,6 +28,8 @@ const {
|
|
|
21
28
|
action,
|
|
22
29
|
icon,
|
|
23
30
|
placement = 'right',
|
|
31
|
+
iconOnly = false,
|
|
32
|
+
triggerIcon = 'chevron-down',
|
|
24
33
|
} = defineProps<NexxtDropdownButtonProps>()
|
|
25
34
|
|
|
26
35
|
const emit = defineEmits<{
|
|
@@ -106,31 +115,33 @@ const transitionClasses = computed(() => {
|
|
|
106
115
|
:class="placement === 'right' ? 'items-end' : 'items-start'"
|
|
107
116
|
>
|
|
108
117
|
<div class="inline-flex items-center overflow-hidden rounded-md" :class="componentClasses">
|
|
118
|
+
<template v-if="!iconOnly">
|
|
119
|
+
<button
|
|
120
|
+
class="flex cursor-pointer items-center gap-2 px-4 py-2 transition-colors duration-200"
|
|
121
|
+
:class="buttonClasses"
|
|
122
|
+
@click="action && handleAction(action)"
|
|
123
|
+
>
|
|
124
|
+
<NexxtIcon v-if="icon" :name="icon" aria-hidden="true" />
|
|
125
|
+
{{ label }}
|
|
126
|
+
</button>
|
|
127
|
+
<span
|
|
128
|
+
class="h-7 w-px rounded-sm"
|
|
129
|
+
:class="variant === 'primary' ? 'bg-white' : 'bg-gray-200'"
|
|
130
|
+
aria-hidden="true"
|
|
131
|
+
></span>
|
|
132
|
+
</template>
|
|
109
133
|
<button
|
|
110
|
-
class="
|
|
111
|
-
:class="buttonClasses"
|
|
112
|
-
@click="
|
|
113
|
-
>
|
|
114
|
-
<NexxtIcon v-if="icon" :name="icon" aria-hidden="true" />
|
|
115
|
-
{{ label }}
|
|
116
|
-
</button>
|
|
117
|
-
<span
|
|
118
|
-
class="h-7 w-px rounded-sm"
|
|
119
|
-
:class="variant === 'primary' ? 'bg-white' : 'bg-gray-200'"
|
|
120
|
-
aria-hidden="true"
|
|
121
|
-
></span>
|
|
122
|
-
<button
|
|
123
|
-
class="cursor-pointer px-2 py-2 transition-colors duration-200"
|
|
124
|
-
:class="buttonClasses"
|
|
125
|
-
@click="toggleDropdown"
|
|
134
|
+
class="w-10 shrink-0 cursor-pointer transition-colors duration-200"
|
|
135
|
+
:class="[buttonClasses, iconOnly ? 'p-2' : 'px-2 py-2']"
|
|
136
|
+
@click="iconOnly ? toggleDropdown() : toggleDropdown()"
|
|
126
137
|
aria-haspopup="menu"
|
|
127
138
|
:aria-expanded="isOpen"
|
|
128
139
|
:aria-label="`Open ${label} menu`"
|
|
129
140
|
>
|
|
130
141
|
<NexxtIcon
|
|
131
|
-
name="
|
|
142
|
+
:name="triggerIcon"
|
|
132
143
|
class="transition-transform duration-200"
|
|
133
|
-
:class="{ 'rotate-180': isOpen }"
|
|
144
|
+
:class="{ 'rotate-180': isOpen && triggerIcon === 'chevron-down' }"
|
|
134
145
|
aria-hidden="true"
|
|
135
146
|
/>
|
|
136
147
|
</button>
|
|
@@ -148,6 +159,7 @@ const transitionClasses = computed(() => {
|
|
|
148
159
|
:key="option.label"
|
|
149
160
|
role="menuitem"
|
|
150
161
|
class="flex w-full min-w-42 cursor-pointer items-center gap-2 px-4 py-2 text-left text-sm whitespace-nowrap text-gray-700 transition-colors duration-200 hover:bg-gray-100 hover:text-purple-600 focus:bg-gray-100 focus:outline-none"
|
|
162
|
+
:class="option.selected ? 'bg-cornflower-blue-100' : ''"
|
|
151
163
|
@click="handleOptionClick(option.action)"
|
|
152
164
|
>
|
|
153
165
|
<NexxtIcon v-if="option.icon" :name="option.icon" aria-hidden="true" />
|
|
@@ -3,6 +3,7 @@ import Button from '../Button/Button.vue'
|
|
|
3
3
|
|
|
4
4
|
interface NexxtHeaderProps {
|
|
5
5
|
title: string
|
|
6
|
+
headingType?: 'heading-1' | 'heading-2' | 'heading-3' | 'heading-4'
|
|
6
7
|
backButtonText?: string
|
|
7
8
|
backButtonAction?: () => void
|
|
8
9
|
}
|
|
@@ -12,6 +13,7 @@ defineOptions({
|
|
|
12
13
|
})
|
|
13
14
|
|
|
14
15
|
const {
|
|
16
|
+
headingType = 'heading-4',
|
|
15
17
|
backButtonText,
|
|
16
18
|
backButtonAction = () => {
|
|
17
19
|
window.history.back()
|
|
@@ -29,7 +31,7 @@ const {
|
|
|
29
31
|
@click="backButtonAction"
|
|
30
32
|
>{{ backButtonText }}</Button
|
|
31
33
|
>
|
|
32
|
-
<span class="block truncate
|
|
34
|
+
<span class="block truncate text-sapphire-500" :class="headingType">
|
|
33
35
|
{{ title }}
|
|
34
36
|
</span>
|
|
35
37
|
</div>
|
|
@@ -33,7 +33,7 @@ const modelValue = defineModel<string>()
|
|
|
33
33
|
</label>
|
|
34
34
|
|
|
35
35
|
<div
|
|
36
|
-
class="flex items-center gap-2 rounded-
|
|
36
|
+
class="flex items-center gap-2 rounded-sm px-3 ring transition-colors ease-out focus-within:ring-cornflower-blue-500"
|
|
37
37
|
:class="error ? 'ring-brick-400' : 'ring-gray-200'"
|
|
38
38
|
>
|
|
39
39
|
<span v-if="leftIcon" class="flex items-center">
|
|
@@ -47,10 +47,11 @@ const modelValue = defineModel<string>()
|
|
|
47
47
|
v-bind="$attrs"
|
|
48
48
|
class="h-10 min-w-60 bg-transparent text-sm text-gray-900 placeholder-gray-400 focus:outline-none"
|
|
49
49
|
/>
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
<slot name="right">
|
|
51
|
+
<span v-if="rightIcon" class="flex items-center">
|
|
52
|
+
<Icon :name="rightIcon" />
|
|
53
|
+
</span>
|
|
54
|
+
</slot>
|
|
54
55
|
</div>
|
|
55
56
|
|
|
56
57
|
<Transition
|
|
@@ -8,6 +8,7 @@ export interface TableColumn<K extends string = string> {
|
|
|
8
8
|
label: string
|
|
9
9
|
sortable?: boolean
|
|
10
10
|
width?: string
|
|
11
|
+
minCellWidth?: number
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export interface NexxtTableProps<
|
|
@@ -17,12 +18,19 @@ export interface NexxtTableProps<
|
|
|
17
18
|
columns: TableColumn<TKey>[]
|
|
18
19
|
rows: TRow[]
|
|
19
20
|
selectable?: boolean
|
|
21
|
+
loading?: boolean
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
const INTERACTIVE = ['a', 'button', 'input', 'select', 'textarea', 'label']
|
|
23
25
|
|
|
24
26
|
defineOptions({ name: 'NexxtTable' })
|
|
25
27
|
|
|
28
|
+
defineSlots<
|
|
29
|
+
{ [K in TKey as `cell-${K}`]?: (props: { row: TRow; value: TRow[K] }) => unknown } & {
|
|
30
|
+
[key: string]: ((props: Record<string, unknown>) => unknown) | undefined
|
|
31
|
+
}
|
|
32
|
+
>()
|
|
33
|
+
|
|
26
34
|
const props = defineProps<NexxtTableProps<TKey, TRow>>()
|
|
27
35
|
const emit = defineEmits<{
|
|
28
36
|
'row-click': [row: TRow]
|
|
@@ -54,7 +62,9 @@ const toggleRow = (row: TRow) => {
|
|
|
54
62
|
}
|
|
55
63
|
|
|
56
64
|
const columnTemplate = computed(() =>
|
|
57
|
-
props.columns
|
|
65
|
+
props.columns
|
|
66
|
+
.map((col) => `minmax(${col.minCellWidth ?? 150}px, ${col.width ?? '1fr'})`)
|
|
67
|
+
.join(' '),
|
|
58
68
|
)
|
|
59
69
|
|
|
60
70
|
const gridTemplate = computed(() =>
|
|
@@ -87,7 +97,8 @@ const getRowRoundedClass = (rowIndex: number) => {
|
|
|
87
97
|
return ''
|
|
88
98
|
}
|
|
89
99
|
|
|
90
|
-
const rowValue = (row: TRow, key: TKey) =>
|
|
100
|
+
const rowValue = (row: TRow, key: TKey): TRow[TKey] => row[key]
|
|
101
|
+
const cellSlotName = (key: TKey): string => `cell-${key}`
|
|
91
102
|
|
|
92
103
|
const handleRowKeydown = (event: KeyboardEvent, rowIndex: number) => {
|
|
93
104
|
if (event.key === 'ArrowDown') {
|
|
@@ -102,12 +113,12 @@ const handleRowKeydown = (event: KeyboardEvent, rowIndex: number) => {
|
|
|
102
113
|
|
|
103
114
|
<template>
|
|
104
115
|
<div class="flex flex-col gap-3">
|
|
105
|
-
<div class="overflow-auto">
|
|
116
|
+
<div class="overflow-x-auto">
|
|
106
117
|
<div
|
|
107
118
|
role="grid"
|
|
108
119
|
:aria-colcount="columns.length + (selectable ? 1 : 0)"
|
|
109
120
|
:aria-rowcount="rows.length + 1"
|
|
110
|
-
class="
|
|
121
|
+
class="min-w-full"
|
|
111
122
|
>
|
|
112
123
|
<div class="sticky top-0 z-10 mb-0.5">
|
|
113
124
|
<div v-if="$slots.toolbar">
|
|
@@ -156,7 +167,9 @@ const handleRowKeydown = (event: KeyboardEvent, rowIndex: number) => {
|
|
|
156
167
|
@keydown.enter.prevent="handleSort(column)"
|
|
157
168
|
@keydown.space.prevent="handleSort(column)"
|
|
158
169
|
>
|
|
159
|
-
|
|
170
|
+
<span :class="sortKey === column.key ? 'text-cornflower-blue-600' : ''">{{
|
|
171
|
+
column.label
|
|
172
|
+
}}</span>
|
|
160
173
|
<Icon
|
|
161
174
|
v-if="column.sortable"
|
|
162
175
|
:name="
|
|
@@ -173,7 +186,7 @@ const handleRowKeydown = (event: KeyboardEvent, rowIndex: number) => {
|
|
|
173
186
|
</div>
|
|
174
187
|
</div>
|
|
175
188
|
|
|
176
|
-
<div role="rowgroup">
|
|
189
|
+
<div role="rowgroup" class="relative">
|
|
177
190
|
<div
|
|
178
191
|
v-for="(row, rowIndex) in rows"
|
|
179
192
|
:key="rowIndex"
|
|
@@ -215,7 +228,7 @@ const handleRowKeydown = (event: KeyboardEvent, rowIndex: number) => {
|
|
|
215
228
|
role="gridcell"
|
|
216
229
|
:aria-colindex="colIndex + (selectable ? 2 : 1)"
|
|
217
230
|
>
|
|
218
|
-
<slot :name="
|
|
231
|
+
<slot :name="cellSlotName(column.key)" :row="row" :value="rowValue(row, column.key)">
|
|
219
232
|
{{ rowValue(row, column.key) }}
|
|
220
233
|
</slot>
|
|
221
234
|
</div>
|
|
@@ -224,6 +237,23 @@ const handleRowKeydown = (event: KeyboardEvent, rowIndex: number) => {
|
|
|
224
237
|
<div v-if="!rows.length" class="px-4 py-8 text-center text-sm text-slate-400" role="row">
|
|
225
238
|
<slot name="empty">Geen data beschikbaar</slot>
|
|
226
239
|
</div>
|
|
240
|
+
|
|
241
|
+
<Transition
|
|
242
|
+
enter-active-class="transition-opacity duration-200"
|
|
243
|
+
leave-active-class="transition-opacity duration-200"
|
|
244
|
+
enter-from-class="opacity-0"
|
|
245
|
+
leave-to-class="opacity-0"
|
|
246
|
+
>
|
|
247
|
+
<div
|
|
248
|
+
v-if="loading"
|
|
249
|
+
data-testid="loading-overlay"
|
|
250
|
+
class="absolute inset-0 flex items-center justify-center rounded-md bg-white/40 backdrop-blur-sm"
|
|
251
|
+
>
|
|
252
|
+
<slot name="loading"
|
|
253
|
+
><Icon name="spinner-third" class="fa-spin text-3xl text-purple-500"
|
|
254
|
+
/></slot>
|
|
255
|
+
</div>
|
|
256
|
+
</Transition>
|
|
227
257
|
</div>
|
|
228
258
|
</div>
|
|
229
259
|
</div>
|
|
@@ -34,6 +34,7 @@ export interface TimelineResponse {
|
|
|
34
34
|
export interface PhaseConfig {
|
|
35
35
|
label: string
|
|
36
36
|
color: string
|
|
37
|
+
icon: string
|
|
37
38
|
bgColor: string
|
|
38
39
|
fromColor: string
|
|
39
40
|
toColor: string
|
|
@@ -180,14 +181,16 @@ export const PHASE_MAP: Record<number, PhaseConfig> = {
|
|
|
180
181
|
0: {
|
|
181
182
|
label: 'Inactief',
|
|
182
183
|
color: 'border-slate-300',
|
|
184
|
+
icon: 'eye-closed',
|
|
183
185
|
bgColor: 'bg-slate-300',
|
|
184
186
|
fromColor: 'from-slate-300',
|
|
185
187
|
toColor: 'to-slate-300',
|
|
186
188
|
textColor: 'text-gray-700',
|
|
187
189
|
},
|
|
188
190
|
1: {
|
|
189
|
-
label: '
|
|
191
|
+
label: 'Latent',
|
|
190
192
|
color: 'border-amber-500',
|
|
193
|
+
icon: 'eye-closed',
|
|
191
194
|
bgColor: 'bg-amber-500',
|
|
192
195
|
fromColor: 'from-amber-500',
|
|
193
196
|
toColor: 'to-amber-500',
|
|
@@ -196,14 +199,16 @@ export const PHASE_MAP: Record<number, PhaseConfig> = {
|
|
|
196
199
|
2: {
|
|
197
200
|
label: 'Oriëntatie',
|
|
198
201
|
color: 'border-purple-500',
|
|
202
|
+
icon: 'building-magnifying-glass',
|
|
199
203
|
bgColor: 'bg-purple-500',
|
|
200
204
|
fromColor: 'from-purple-500',
|
|
201
205
|
toColor: 'to-purple-500',
|
|
202
206
|
textColor: 'text-white',
|
|
203
207
|
},
|
|
204
208
|
3: {
|
|
205
|
-
label: '
|
|
209
|
+
label: 'Actief zoeken',
|
|
206
210
|
color: 'border-yellow-500',
|
|
211
|
+
icon: 'house-person-return',
|
|
207
212
|
bgColor: 'bg-yellow-500',
|
|
208
213
|
fromColor: 'from-yellow-500',
|
|
209
214
|
toColor: 'to-yellow-500',
|
|
@@ -212,6 +217,7 @@ export const PHASE_MAP: Record<number, PhaseConfig> = {
|
|
|
212
217
|
4: {
|
|
213
218
|
label: 'Selectie',
|
|
214
219
|
color: 'border-blue-500',
|
|
220
|
+
icon: 'handshake',
|
|
215
221
|
bgColor: 'bg-blue-500',
|
|
216
222
|
fromColor: 'from-blue-500',
|
|
217
223
|
toColor: 'to-blue-500',
|
|
@@ -220,6 +226,7 @@ export const PHASE_MAP: Record<number, PhaseConfig> = {
|
|
|
220
226
|
5: {
|
|
221
227
|
label: 'Transactie',
|
|
222
228
|
color: 'border-emerald-500',
|
|
229
|
+
icon: 'pencil-mechanical',
|
|
223
230
|
bgColor: 'bg-emerald-500',
|
|
224
231
|
fromColor: 'from-emerald-500',
|
|
225
232
|
toColor: 'to-emerald-500',
|
|
@@ -228,6 +235,7 @@ export const PHASE_MAP: Record<number, PhaseConfig> = {
|
|
|
228
235
|
6: {
|
|
229
236
|
label: 'Wonen',
|
|
230
237
|
color: 'border-green-500',
|
|
238
|
+
icon: 'house-heart',
|
|
231
239
|
bgColor: 'bg-green-500',
|
|
232
240
|
fromColor: 'from-green-500',
|
|
233
241
|
toColor: 'to-green-500',
|