@nexxtmove/ui 0.1.37 → 0.1.39

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nexxtmove/ui",
3
3
  "type": "module",
4
- "version": "0.1.37",
4
+ "version": "0.1.39",
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 { computed, ref, watch } from 'vue'
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
- easing = 'ease-out',
11
+ delay = 0,
14
12
  } = defineProps<{
15
13
  value: number
16
14
  duration?: number
17
- easing?: EasingFunction
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
- <TransitionGroup
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,17 +1,22 @@
1
1
  <script lang="ts" setup>
2
2
  import NexxtIcon from '../Icon/Icon.vue'
3
- import { computed } from 'vue'
3
+ import NexxtAnimatedNumber from '../AnimatedNumber/AnimatedNumber.vue'
4
+ import { computed, onMounted, ref } from 'vue'
5
+ import { PHASE_MAP } from '../TimelineEvent/TimelineTypes'
4
6
 
5
- type Phase = 'Latent' | 'Oriëntatie' | 'Actief zoeken' | 'Selectie' | 'Transactie' | 'Wonen'
7
+ type Phase = (typeof PHASE_MAP)[keyof typeof PHASE_MAP]['label']
6
8
 
7
9
  interface NexxtCustomerJourneyTileProps {
10
+ as?: keyof HTMLElementTagNameMap
8
11
  title?: string
9
12
  amount?: number
13
+ animated?: boolean
14
+ delay?: number
10
15
  percentage?: number
11
16
  percentageExplanaition?: string
12
17
  }
13
18
 
14
- const PHASE_ICON_MAP: Record<Phase, string> = {
19
+ const PHASE_ICON_MAP: Partial<Record<Phase, string>> = {
15
20
  Latent: 'eye-closed',
16
21
  Oriëntatie: 'building-magnifying-glass',
17
22
  'Actief zoeken': 'house-person-return',
@@ -24,8 +29,21 @@ defineOptions({
24
29
  name: 'NexxtCustomerJourneyTile',
25
30
  })
26
31
 
27
- const { title, amount, percentage, percentageExplanaition } =
28
- defineProps<NexxtCustomerJourneyTileProps>()
32
+ const {
33
+ as = 'div',
34
+ title,
35
+ amount,
36
+ animated = false,
37
+ delay,
38
+ percentage,
39
+ percentageExplanaition,
40
+ } = defineProps<NexxtCustomerJourneyTileProps>()
41
+
42
+ const displayAmount = ref(animated ? 0 : (amount ?? 0))
43
+
44
+ onMounted(() => {
45
+ if (animated && amount !== undefined) displayAmount.value = amount
46
+ })
29
47
 
30
48
  const icon = computed(() => {
31
49
  if (!title) return null
@@ -46,10 +64,10 @@ const ariaLabel = computed(() => {
46
64
  </script>
47
65
 
48
66
  <template>
49
- <button
50
- type="button"
67
+ <component
68
+ :is="as"
51
69
  :aria-label="ariaLabel"
52
- class="flex cursor-pointer rounded-xl bg-white outline outline-gray-200 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-cornflower-blue-500"
70
+ 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
71
  :class="icon ? 'gap-6 p-1.5' : 'p-4'"
54
72
  >
55
73
  <div
@@ -63,19 +81,26 @@ const ariaLabel = computed(() => {
63
81
  <span
64
82
  class="heading-1"
65
83
  :class="icon ? 'text-purple-500' : 'text-sapphire-900'"
66
- v-if="amount"
67
- >{{ amount }}</span
84
+ v-if="amount !== undefined"
68
85
  >
69
- <div v-if="percentage" class="flex gap-0.5">
86
+ <NexxtAnimatedNumber :value="displayAmount" :delay="delay" />
87
+ </span>
88
+ <div v-if="percentage !== undefined" class="flex gap-0.5">
70
89
  <span
71
90
  class="extra-small-semibold"
72
- :class="percentage > 0 ? 'text-green-500' : 'text-brick-500'"
91
+ :class="
92
+ percentage > 0
93
+ ? 'text-green-500'
94
+ : percentage === 0
95
+ ? 'text-orange-500'
96
+ : 'text-brick-500'
97
+ "
73
98
  ><template v-if="percentage > 0">+</template>{{ percentage }}%</span
74
99
  >
75
- <span class="extra-small-normal" v-if="percentageExplanaition">{{
100
+ <span class="ml-0.5 text-left extra-small-normal" v-if="percentageExplanaition">{{
76
101
  percentageExplanaition
77
102
  }}</span>
78
103
  </div>
79
104
  </div>
80
- </button>
105
+ </component>
81
106
  </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
- export interface NexxtDropdownButtonProps extends NexxtDropdownButton {
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="flex cursor-pointer items-center gap-2 px-4 py-2 transition-colors duration-200"
111
- :class="buttonClasses"
112
- @click="handleAction(action)"
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="chevron-down"
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>
@@ -140,7 +151,7 @@ const transitionClasses = computed(() => {
140
151
  <div
141
152
  v-if="isOpen"
142
153
  role="menu"
143
- class="absolute top-full z-10 mt-2.5 min-w-full rounded-md border border-gray-200 bg-white"
154
+ class="absolute top-full z-10 mt-2.5 min-w-full rounded-md border border-gray-200 bg-white whitespace-nowrap"
144
155
  :class="placement === 'right' ? 'right-0' : 'left-0'"
145
156
  >
146
157
  <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 heading-4 text-sapphire-500">
34
+ <span class="block truncate text-sapphire-500" :class="headingType">
33
35
  {{ title }}
34
36
  </span>
35
37
  </div>
@@ -1,9 +1,8 @@
1
1
  <script setup lang="ts">
2
- import { ref, computed } from 'vue'
3
2
  import Icon from '../Icon/Icon.vue'
4
3
 
5
4
  const generateId = () => 'input-field-' + Math.random().toString(36).slice(2, 10)
6
- const inputId = ref(generateId())
5
+ const inputId = generateId()
7
6
 
8
7
  defineOptions({
9
8
  name: 'NexxtInputField',
@@ -14,71 +13,58 @@ interface NexxtInputFieldProps {
14
13
  label?: string
15
14
  error?: boolean
16
15
  errorMessage?: string
17
- focused?: boolean
18
16
  leftIcon?: InstanceType<typeof Icon>['name']
19
17
  rightIcon?: InstanceType<typeof Icon>['name']
20
18
  }
21
19
 
22
- const props = defineProps<NexxtInputFieldProps>()
20
+ defineProps<NexxtInputFieldProps>()
23
21
 
24
- // ✅ Correct usage
25
22
  const modelValue = defineModel<string>()
26
-
27
- const isFocused = ref(false)
28
-
29
- const wrapperClasses = computed(() => {
30
- if (props.error) {
31
- return 'border-brick-400'
32
- }
33
- if (props.focused || isFocused.value) {
34
- return 'border-cornflower-blue-500'
35
- }
36
- return 'border-gray-200'
37
- })
38
-
39
- const onFocus = () => {
40
- isFocused.value = true
41
- }
42
-
43
- const onBlur = () => {
44
- isFocused.value = false
45
- }
46
23
  </script>
47
24
 
48
25
  <template>
49
- <div class="flex w-full flex-col gap-1">
26
+ <div class="inline-flex flex-col gap-1">
50
27
  <label
51
- v-if="props.label"
28
+ v-if="label"
52
29
  class="text-color-gray-700 justify-start body-normal text-sm"
53
30
  :for="inputId"
54
31
  >
55
- {{ props.label }}
32
+ {{ label }}
56
33
  </label>
57
34
 
58
35
  <div
59
- class="flex items-center gap-2 rounded-lg border px-3 py-2 transition-colors"
60
- :class="wrapperClasses"
36
+ class="flex items-center gap-2 rounded-sm px-3 ring transition-colors ease-out focus-within:ring-cornflower-blue-500"
37
+ :class="error ? 'ring-brick-400' : 'ring-gray-200'"
61
38
  >
62
- <span v-if="props.leftIcon" class="flex items-center">
63
- <Icon :name="props.leftIcon" />
39
+ <span v-if="leftIcon" class="flex items-center">
40
+ <Icon :name="leftIcon" />
64
41
  </span>
65
42
 
66
43
  <input
67
44
  :id="inputId"
68
45
  v-model="modelValue"
69
- :placeholder="props.placeholder"
70
- @focus="onFocus"
71
- @blur="onBlur"
72
- class="w-full bg-transparent text-sm text-gray-900 placeholder-gray-400 focus:outline-none"
46
+ :placeholder="placeholder"
47
+ v-bind="$attrs"
48
+ class="h-10 min-w-60 bg-transparent text-sm text-gray-900 placeholder-gray-400 focus:outline-none"
73
49
  />
74
-
75
- <span v-if="props.rightIcon" class="flex items-center">
76
- <Icon :name="props.rightIcon" />
77
- </span>
50
+ <slot name="right">
51
+ <span v-if="rightIcon" class="flex items-center">
52
+ <Icon :name="rightIcon" />
53
+ </span>
54
+ </slot>
78
55
  </div>
79
56
 
80
- <p v-if="props.errorMessage" class="text-sm text-brick-400">
81
- {{ props.errorMessage }}
82
- </p>
57
+ <Transition
58
+ enter-active-class="transition-all duration-200 ease-out"
59
+ enter-from-class="opacity-0 -translate-y-1"
60
+ enter-to-class="opacity-100 translate-y-0"
61
+ leave-active-class="transition-all duration-150 ease-in"
62
+ leave-from-class="opacity-100 translate-y-0"
63
+ leave-to-class="opacity-0 -translate-y-1"
64
+ >
65
+ <p v-if="error && errorMessage" class="text-sm text-brick-400">
66
+ {{ errorMessage }}
67
+ </p>
68
+ </Transition>
83
69
  </div>
84
70
  </template>
@@ -1,4 +1,4 @@
1
- <script lang="ts" setup generic="TKey extends string">
1
+ <script lang="ts" setup generic="TKey extends string, TRow extends Record<TKey, unknown>">
2
2
  import { computed, ref } from 'vue'
3
3
  import Icon from '../Icon/Icon.vue'
4
4
  import Checkbox from '../Checkbox/Checkbox.vue'
@@ -8,24 +8,35 @@ 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
- export interface NexxtTableProps<TKey extends string = string> {
14
+ export interface NexxtTableProps<
15
+ TKey extends string = string,
16
+ TRow extends Record<TKey, unknown> = Record<TKey, unknown>,
17
+ > {
14
18
  columns: TableColumn<TKey>[]
15
- rows: Record<TKey, unknown>[]
19
+ rows: TRow[]
16
20
  selectable?: boolean
21
+ loading?: boolean
17
22
  }
18
23
 
19
24
  const INTERACTIVE = ['a', 'button', 'input', 'select', 'textarea', 'label']
20
25
 
21
26
  defineOptions({ name: 'NexxtTable' })
22
27
 
23
- const props = defineProps<NexxtTableProps<TKey>>()
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
+
34
+ const props = defineProps<NexxtTableProps<TKey, TRow>>()
24
35
  const emit = defineEmits<{
25
- 'row-click': [row: Record<TKey, unknown>]
36
+ 'row-click': [row: TRow]
26
37
  }>()
27
38
 
28
- const selected = defineModel<Record<TKey, unknown>[]>({ default: () => [] })
39
+ const selected = defineModel<TRow[]>({ default: () => [] })
29
40
  const sortKey = defineModel<TKey>('sortKey')
30
41
  const sortDirection = defineModel<'asc' | 'desc'>('sortDirection', { default: 'asc' })
31
42
 
@@ -38,20 +49,22 @@ const someSelected = computed(
38
49
  () => selected.value.length > 0 && selected.value.length < props.rows.length,
39
50
  )
40
51
 
41
- const isRowSelected = (row: Record<TKey, unknown>) => selected.value.includes(row)
52
+ const isRowSelected = (row: TRow) => selected.value.includes(row)
42
53
 
43
54
  const toggleAll = () => {
44
55
  selected.value = allSelected.value ? [] : [...props.rows]
45
56
  }
46
57
 
47
- const toggleRow = (row: Record<TKey, unknown>) => {
58
+ const toggleRow = (row: TRow) => {
48
59
  selected.value = isRowSelected(row)
49
60
  ? selected.value.filter((r) => r !== row)
50
61
  : [...selected.value, row]
51
62
  }
52
63
 
53
64
  const columnTemplate = computed(() =>
54
- props.columns.map((col) => `minmax(100px, ${col.width ?? '1fr'})`).join(' '),
65
+ props.columns
66
+ .map((col) => `minmax(${col.minCellWidth ?? 150}px, ${col.width ?? '1fr'})`)
67
+ .join(' '),
55
68
  )
56
69
 
57
70
  const gridTemplate = computed(() =>
@@ -84,6 +97,9 @@ const getRowRoundedClass = (rowIndex: number) => {
84
97
  return ''
85
98
  }
86
99
 
100
+ const rowValue = (row: TRow, key: TKey): TRow[TKey] => row[key]
101
+ const cellSlotName = (key: TKey): string => `cell-${key}`
102
+
87
103
  const handleRowKeydown = (event: KeyboardEvent, rowIndex: number) => {
88
104
  if (event.key === 'ArrowDown') {
89
105
  event.preventDefault()
@@ -97,12 +113,12 @@ const handleRowKeydown = (event: KeyboardEvent, rowIndex: number) => {
97
113
 
98
114
  <template>
99
115
  <div class="flex flex-col gap-3">
100
- <div class="overflow-auto">
116
+ <div class="overflow-x-auto">
101
117
  <div
102
118
  role="grid"
103
119
  :aria-colcount="columns.length + (selectable ? 1 : 0)"
104
120
  :aria-rowcount="rows.length + 1"
105
- class="w-full min-w-max"
121
+ class="min-w-full"
106
122
  >
107
123
  <div class="sticky top-0 z-10 mb-0.5">
108
124
  <div v-if="$slots.toolbar">
@@ -151,7 +167,9 @@ const handleRowKeydown = (event: KeyboardEvent, rowIndex: number) => {
151
167
  @keydown.enter.prevent="handleSort(column)"
152
168
  @keydown.space.prevent="handleSort(column)"
153
169
  >
154
- {{ column.label }}
170
+ <span :class="sortKey === column.key ? 'text-cornflower-blue-600' : ''">{{
171
+ column.label
172
+ }}</span>
155
173
  <Icon
156
174
  v-if="column.sortable"
157
175
  :name="
@@ -168,7 +186,7 @@ const handleRowKeydown = (event: KeyboardEvent, rowIndex: number) => {
168
186
  </div>
169
187
  </div>
170
188
 
171
- <div role="rowgroup">
189
+ <div role="rowgroup" class="relative">
172
190
  <div
173
191
  v-for="(row, rowIndex) in rows"
174
192
  :key="rowIndex"
@@ -210,8 +228,8 @@ const handleRowKeydown = (event: KeyboardEvent, rowIndex: number) => {
210
228
  role="gridcell"
211
229
  :aria-colindex="colIndex + (selectable ? 2 : 1)"
212
230
  >
213
- <slot :name="`cell-${column.key}`" :row="row" :value="row[column.key]">
214
- {{ row[column.key] }}
231
+ <slot :name="cellSlotName(column.key)" :row="row" :value="rowValue(row, column.key)">
232
+ {{ rowValue(row, column.key) }}
215
233
  </slot>
216
234
  </div>
217
235
  </div>
@@ -219,6 +237,23 @@ const handleRowKeydown = (event: KeyboardEvent, rowIndex: number) => {
219
237
  <div v-if="!rows.length" class="px-4 py-8 text-center text-sm text-slate-400" role="row">
220
238
  <slot name="empty">Geen data beschikbaar</slot>
221
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>
222
257
  </div>
223
258
  </div>
224
259
  </div>
@@ -186,7 +186,7 @@ export const PHASE_MAP: Record<number, PhaseConfig> = {
186
186
  textColor: 'text-gray-700',
187
187
  },
188
188
  1: {
189
- label: 'Latente woonfase',
189
+ label: 'Latent',
190
190
  color: 'border-amber-500',
191
191
  bgColor: 'bg-amber-500',
192
192
  fromColor: 'from-amber-500',
@@ -202,7 +202,7 @@ export const PHASE_MAP: Record<number, PhaseConfig> = {
202
202
  textColor: 'text-white',
203
203
  },
204
204
  3: {
205
- label: 'Actieve zoektocht',
205
+ label: 'Actief zoeken',
206
206
  color: 'border-yellow-500',
207
207
  bgColor: 'bg-yellow-500',
208
208
  fromColor: 'from-yellow-500',