@nexxtmove/ui 1.1.5 → 1.1.7

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": "1.1.5",
4
+ "version": "1.1.7",
5
5
  "exports": {
6
6
  ".": {
7
7
  "types": "./dist/index.d.ts",
@@ -8,6 +8,7 @@ interface NexxtProgressBarProps {
8
8
  currentStep: number
9
9
  progressLabel?: string
10
10
  stepTitles?: string[]
11
+ maxInlineSteps?: number
11
12
  }
12
13
 
13
14
  defineOptions({
@@ -22,6 +23,7 @@ const {
22
23
  stepTitles,
23
24
  steps,
24
25
  currentStep,
26
+ maxInlineSteps = 7,
25
27
  } = defineProps<NexxtProgressBarProps>()
26
28
 
27
29
  const isPercentage = computed(() => variant === 'percentage')
@@ -45,6 +47,14 @@ const percentage = computed(() => {
45
47
 
46
48
  const hasTitles = computed(() => !isPercentage.value && Boolean(stepTitles?.length))
47
49
 
50
+ // Reserve the current-step number at the width of the total, so a 9 -> 10
51
+ // transition doesn't shift the "/x" that follows it.
52
+ const stepDigits = computed(() => String(Math.max(steps, 0)).length)
53
+
54
+ // When there are more titles than can comfortably fit side by side, fall back to
55
+ // the compact treatment (current title + "x/y" counter) at every breakpoint.
56
+ const isCompact = computed(() => hasTitles.value && (stepTitles?.length ?? 0) > maxInlineSteps)
57
+
48
58
  watchEffect(() => {
49
59
  if (stepTitles && stepTitles.length !== steps) {
50
60
  console.warn(
@@ -89,24 +99,37 @@ watchEffect(() => {
89
99
  </div>
90
100
  <div
91
101
  v-if="hasTitles"
92
- class="flex items-center small-normal tabular-nums @md:hidden"
102
+ class="flex items-center gap-0.5 small-normal tabular-nums"
103
+ :class="isCompact ? '' : '@md:hidden'"
93
104
  aria-hidden="true"
94
105
  >
95
- <AnimatedNumber :value="displayStep" />
96
- <span>/{{ steps }}</span>
106
+ <span class="flex justify-end" :style="{ minWidth: `${stepDigits}ch` }">
107
+ <AnimatedNumber :value="displayStep" />
108
+ </span>
109
+ <span>/</span>
110
+ <span>{{ steps }}</span>
97
111
  </div>
98
112
  </div>
99
113
  <ol
100
114
  v-if="hasTitles"
101
- class="flex justify-between @max-md:order-first @max-md:justify-center"
115
+ class="flex @max-md:order-first"
116
+ :class="isCompact ? 'order-first justify-center' : 'justify-between @max-md:justify-center'"
102
117
  aria-label="Progress steps"
103
118
  >
104
119
  <li
105
120
  v-for="(title, index) in stepTitles"
106
121
  :key="title"
107
- class="rounded-xs transition-colors duration-500 focus-visible:outline-3 @max-md:hidden @max-md:body-normal @min-md:small-normal"
122
+ class="rounded-xs transition-colors duration-500 focus-visible:outline-3"
108
123
  :class="[
109
- index === clampedStep ? 'text-sapphire-500 @max-md:inline' : '',
124
+ isCompact
125
+ ? index === clampedStep
126
+ ? 'body-normal'
127
+ : 'hidden'
128
+ : [
129
+ '@max-md:body-normal @min-md:small-normal',
130
+ index === clampedStep ? '@max-md:inline' : '@max-md:hidden',
131
+ ],
132
+ index === clampedStep ? 'text-sapphire-500' : '',
110
133
  index < clampedStep ? 'cursor-pointer duration-200! hover:text-sapphire-400' : '',
111
134
  ]"
112
135
  :aria-current="index === clampedStep ? 'step' : undefined"
@@ -31,22 +31,36 @@ export interface ProposalReference {
31
31
  pricePerSquareMeter: string | null
32
32
  }
33
33
 
34
+ export type NexxtReferenceCardMessage = 'selected' | 'unselected' | 'details' | 'energyLabel'
35
+
34
36
  export interface NexxtReferenceCardProps {
35
37
  reference: ProposalReference
36
- selectedLabel?: string
37
- unselectedLabel?: string
38
- detailsLabel?: string
38
+ messages?: Partial<Record<NexxtReferenceCardMessage, string>>
39
+ locale?: string
40
+ currency?: string
41
+ }
42
+
43
+ const DEFAULT_MESSAGES: Record<NexxtReferenceCardMessage, string> = {
44
+ selected: 'Meegenomen in advies',
45
+ unselected: 'Selecteer deze woning',
46
+ details: 'Meer details',
47
+ energyLabel: 'Energielabel',
39
48
  }
40
49
 
41
50
  defineOptions({ name: 'NexxtReferenceCard' })
42
51
 
43
52
  const {
44
53
  reference,
45
- selectedLabel = 'Meegenomen in advies',
46
- unselectedLabel = 'Selecteer deze woning',
47
- detailsLabel = 'Meer details',
54
+ messages = {},
55
+ locale = 'nl-NL',
56
+ currency = 'EUR',
48
57
  } = defineProps<NexxtReferenceCardProps>()
49
58
 
59
+ const label = computed<Record<NexxtReferenceCardMessage, string>>(() => ({
60
+ ...DEFAULT_MESSAGES,
61
+ ...messages,
62
+ }))
63
+
50
64
  const selected = defineModel<boolean>({ default: false })
51
65
 
52
66
  defineEmits<{ details: [reference: ProposalReference] }>()
@@ -57,6 +71,19 @@ const location = computed(() => `${reference.postalCode} ${reference.city}`)
57
71
 
58
72
  const energyLabel = computed(() => (reference.energyLabel ?? null) as EnergyLabelLetter | null)
59
73
 
74
+ const salePrice = computed(() => {
75
+ if (!reference.salePrice) return null
76
+
77
+ const amount = Number(reference.salePrice)
78
+ if (Number.isNaN(amount)) return reference.salePrice
79
+
80
+ return new Intl.NumberFormat(locale, {
81
+ style: 'currency',
82
+ currency,
83
+ maximumFractionDigits: 0,
84
+ }).format(amount)
85
+ })
86
+
60
87
  const saleDate = computed(() => {
61
88
  if (!reference.saleDate) return null
62
89
 
@@ -80,7 +107,9 @@ const saleDate = computed(() => {
80
107
  :class="[
81
108
  '@container flex w-full cursor-pointer flex-col overflow-hidden rounded-lg border transition-colors duration-200',
82
109
  'focus-visible:ring-2 focus-visible:ring-cornflower-blue-400 focus-visible:ring-offset-2 focus-visible:outline-none',
83
- selected ? 'border-cornflower-blue-500' : 'border-gray-200',
110
+ selected
111
+ ? 'border-cornflower-blue-500 hover:border-cornflower-blue-600'
112
+ : 'border-gray-200 hover:border-cornflower-blue-300',
84
113
  ]"
85
114
  @click="selected = !selected"
86
115
  @keydown.space.prevent="selected = !selected"
@@ -88,16 +117,16 @@ const saleDate = computed(() => {
88
117
  >
89
118
  <div class="flex flex-col items-stretch bg-white @lg:flex-row">
90
119
  <div
91
- class="relative h-40 w-full self-stretch overflow-hidden bg-gray-100 @lg:h-auto @lg:w-48 @lg:shrink-0"
120
+ class="relative h-40 w-full self-stretch overflow-hidden bg-gray-100 @lg:h-auto @lg:min-h-32 @lg:w-48 @lg:shrink-0"
92
121
  >
93
122
  <img
94
123
  v-if="reference.mainImageUrl"
95
124
  :src="reference.mainImageUrl"
96
125
  :alt="address"
97
126
  loading="lazy"
98
- class="h-full w-full object-cover"
127
+ class="absolute inset-0 size-full object-cover"
99
128
  />
100
- <div v-else class="flex h-full min-h-32 w-full items-center justify-center">
129
+ <div v-else class="absolute inset-0 flex items-center justify-center">
101
130
  <NexxtIcon name="house" class="text-4xl text-gray-500" />
102
131
  </div>
103
132
  </div>
@@ -107,25 +136,25 @@ const saleDate = computed(() => {
107
136
  >
108
137
  <p class="truncate heading-4">{{ address }}</p>
109
138
  <p class="truncate body-normal text-gray-800">{{ location }}</p>
110
- <div v-if="energyLabel" class="flex items-center justify-between gap-2">
111
- <span class="body-normal text-gray-800">Energielabel</span>
139
+ <div v-if="energyLabel" class="mt-2 flex items-center justify-between gap-2">
140
+ <span class="small-normal text-gray-700">{{ label.energyLabel }}</span>
112
141
  <NexxtEnergyLabel :label="energyLabel" />
113
142
  </div>
114
143
  </div>
115
144
 
116
145
  <div
146
+ v-if="salePrice"
117
147
  class="flex flex-col justify-center gap-1 border-t border-gray-200 p-4 text-left @lg:min-w-56 @lg:shrink-0 @lg:border-t-0 @lg:border-l"
118
148
  >
119
- <p v-if="reference.salePrice" class="heading-4 whitespace-nowrap">
120
- {{ reference.salePrice }}
121
- </p>
149
+ <p class="heading-4 whitespace-nowrap">{{ salePrice }}</p>
122
150
  <p v-if="saleDate" class="body-normal text-gray-800">{{ saleDate }}</p>
123
151
  </div>
124
152
  </div>
125
153
 
126
154
  <div
127
155
  :class="[
128
- 'flex flex-wrap items-center justify-between gap-x-4 gap-y-2 border-t px-4 py-2',
156
+ 'flex flex-col-reverse gap-3 border-t px-4 py-3',
157
+ '@lg:flex-row @lg:flex-wrap @lg:items-center @lg:justify-between @lg:gap-x-4 @lg:gap-y-2 @lg:py-2',
129
158
  selected
130
159
  ? 'border-cornflower-blue-500 bg-cornflower-blue-50'
131
160
  : 'border-gray-200 bg-gray-50',
@@ -134,12 +163,16 @@ const saleDate = computed(() => {
134
163
  <div aria-hidden="true" class="pointer-events-none flex min-w-0 items-center gap-3">
135
164
  <NexxtCheckbox :model-value="selected" tabindex="-1" />
136
165
  <span class="truncate body-normal">
137
- {{ selected ? selectedLabel : unselectedLabel }}
166
+ {{ selected ? label.selected : label.unselected }}
138
167
  </span>
139
168
  </div>
140
169
 
141
- <NexxtButton variant="secondary" @click.stop="$emit('details', reference)">
142
- {{ detailsLabel }}
170
+ <NexxtButton
171
+ variant="secondary"
172
+ class="w-full @lg:w-auto"
173
+ @click.stop="$emit('details', reference)"
174
+ >
175
+ {{ label.details }}
143
176
  </NexxtButton>
144
177
  </div>
145
178
  </div>