@nexxtmove/ui 1.2.5 → 1.2.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.2.5",
4
+ "version": "1.2.7",
5
5
  "exports": {
6
6
  ".": {
7
7
  "types": "./dist/index.d.ts",
@@ -10,7 +10,7 @@
10
10
  "./nuxt": "./dist/nuxt.js",
11
11
  "./ui.css": "./dist/ui.css",
12
12
  "./dist/ui.css": "./dist/ui.css",
13
- "./theme": "./tailwind/nexxtmove.tailwind.theme.css",
13
+ "./theme": "./tailwind/theme.css",
14
14
  "./components.json": "./src/components.json",
15
15
  "./components/*": "./src/components/*"
16
16
  },
@@ -36,12 +36,12 @@
36
36
  "@eslint/js": "^10.0.1",
37
37
  "@nuxt/kit": "^4.5.2",
38
38
  "@nuxt/schema": "^4.5.2",
39
- "@storybook/addon-a11y": "^10.5.7",
39
+ "@storybook/addon-a11y": "^10.5.10",
40
40
  "@storybook/addon-designs": "^11.1.4",
41
- "@storybook/addon-docs": "^10.5.7",
42
- "@storybook/addon-vitest": "^10.5.7",
43
- "@storybook/builder-vite": "^10.5.7",
44
- "@storybook/vue3-vite": "^10.5.7",
41
+ "@storybook/addon-docs": "^10.5.10",
42
+ "@storybook/addon-vitest": "^10.5.10",
43
+ "@storybook/builder-vite": "^10.5.10",
44
+ "@storybook/vue3-vite": "^10.5.10",
45
45
  "@tailwindcss/vite": "^4.3.3",
46
46
  "@types/node": "^26.2.0",
47
47
  "@types/react": "^19.2.18",
@@ -55,7 +55,7 @@
55
55
  "eslint": "^10.8.1",
56
56
  "eslint-config-prettier": "^10.1.8",
57
57
  "eslint-plugin-prettier": "^5.5.6",
58
- "eslint-plugin-storybook": "10.5.7",
58
+ "eslint-plugin-storybook": "10.5.10",
59
59
  "eslint-plugin-vue": "^10.10.0",
60
60
  "flag-icons": "^7.5.0",
61
61
  "globals": "^17.11.0",
@@ -65,7 +65,7 @@
65
65
  "react": "^19.2.8",
66
66
  "react-dom": "^19.2.8",
67
67
  "remark-frontmatter": "^5.0.0",
68
- "storybook": "^10.5.7",
68
+ "storybook": "^10.5.10",
69
69
  "typescript-eslint": "^8.67.0",
70
70
  "vite": "^8.2.1",
71
71
  "vite-plugin-dts": "^5.0.3",
@@ -58,8 +58,7 @@ onMounted(async () => {
58
58
  <!-- Scrollable drum -->
59
59
  <div
60
60
  ref="yearScrollerRef"
61
- class="relative h-full snap-y snap-mandatory overflow-y-scroll [&::-webkit-scrollbar]:hidden"
62
- style="scrollbar-width: none"
61
+ class="relative scrollbar-none h-full snap-y snap-mandatory overflow-y-scroll"
63
62
  >
64
63
  <div class="h-18" />
65
64
  <button
@@ -208,7 +208,7 @@ defineExpose({ focus })
208
208
  role="listbox"
209
209
  aria-multiselectable="true"
210
210
  tabindex="0"
211
- class="max-h-56 overflow-y-auto [&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-cornflower-blue-200 [&::-webkit-scrollbar-thumb:hover]:bg-gray-300 [&::-webkit-scrollbar-track]:bg-transparent"
211
+ class="scrollbar-thin max-h-56 overflow-y-auto"
212
212
  >
213
213
  <li
214
214
  v-for="option in filteredOptions"
@@ -298,11 +298,7 @@ defineExpose({ focus })
298
298
  </button>
299
299
  </TransitionGroup>
300
300
  </Transition>
301
- <ul
302
- role="listbox"
303
- aria-multiselectable="true"
304
- class="max-h-56 overflow-y-auto [&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-cornflower-blue-200 [&::-webkit-scrollbar-thumb:hover]:bg-gray-300 [&::-webkit-scrollbar-track]:bg-transparent"
305
- >
301
+ <ul role="listbox" aria-multiselectable="true" class="scrollbar-thin max-h-56 overflow-y-auto">
306
302
  <li
307
303
  v-for="option in filteredOptions"
308
304
  :key="getValue(option)"
@@ -125,10 +125,7 @@ const onOpen = async () => {
125
125
  placeholder="Zoeken..."
126
126
  />
127
127
  </div>
128
- <ul
129
- role="listbox"
130
- class="max-h-60 overflow-y-auto [&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-cornflower-blue-200 [&::-webkit-scrollbar-thumb:hover]:bg-gray-300 [&::-webkit-scrollbar-track]:bg-transparent"
131
- >
128
+ <ul role="listbox" class="scrollbar-thin max-h-60 overflow-y-auto">
132
129
  <li
133
130
  v-for="country in filteredCountries"
134
131
  :key="country.code"
@@ -1,6 +1,8 @@
1
1
  <script setup lang="ts">
2
2
  import { computed, watchEffect } from 'vue'
3
3
  import AnimatedNumber from '../AnimatedNumber/AnimatedNumber.vue'
4
+ import Icon from '../Icon/Icon.vue'
5
+ import FloatingPanel from '../FloatingPanel/FloatingPanel.vue'
4
6
 
5
7
  interface NexxtProgressBarProps {
6
8
  variant?: 'stepper' | 'percentage'
@@ -15,7 +17,7 @@ defineOptions({
15
17
  name: 'NexxtProgressBar',
16
18
  })
17
19
 
18
- defineEmits(['update:currentStep'])
20
+ const emit = defineEmits(['update:currentStep'])
19
21
 
20
22
  const {
21
23
  variant = 'stepper',
@@ -55,6 +57,11 @@ const stepDigits = computed(() => String(Math.max(steps, 0)).length)
55
57
  // the compact treatment (current title + "x/y" counter) at every breakpoint.
56
58
  const isCompact = computed(() => hasTitles.value && (stepTitles?.length ?? 0) > maxInlineSteps)
57
59
 
60
+ const selectStep = (index: number, close: () => void) => {
61
+ close()
62
+ if (index < clampedStep.value) emit('update:currentStep', index)
63
+ }
64
+
58
65
  watchEffect(() => {
59
66
  if (stepTitles && stepTitles.length !== steps) {
60
67
  console.warn(
@@ -110,10 +117,64 @@ watchEffect(() => {
110
117
  <span>{{ steps }}</span>
111
118
  </div>
112
119
  </div>
120
+ <div v-if="isCompact" class="order-first flex justify-center">
121
+ <FloatingPanel placement="bottom">
122
+ <template #trigger="{ isOpen, toggle }">
123
+ <button
124
+ type="button"
125
+ class="flex cursor-pointer items-center gap-1.5 rounded-xs body-normal text-sapphire-500 transition-colors duration-200 hover:text-sapphire-400 focus-visible:outline-3"
126
+ aria-haspopup="menu"
127
+ :aria-expanded="isOpen"
128
+ :aria-label="`Stap ${displayStep} van ${steps}: ${stepTitles?.[clampedStep]}. Kies een stap`"
129
+ @click="toggle"
130
+ >
131
+ {{ stepTitles?.[clampedStep] }}
132
+ <Icon
133
+ name="chevron-down"
134
+ class="text-xs transition-transform duration-200"
135
+ :class="{ 'rotate-180': isOpen }"
136
+ aria-hidden="true"
137
+ />
138
+ </button>
139
+ </template>
140
+
141
+ <template #content="{ close }">
142
+ <ol
143
+ role="menu"
144
+ class="scrollbar-thin max-h-72 min-w-52 overflow-y-auto rounded-lg border border-gray-200 bg-white py-1"
145
+ aria-label="Progress steps"
146
+ >
147
+ <li v-for="(title, index) in stepTitles" :key="title">
148
+ <button
149
+ type="button"
150
+ role="menuitem"
151
+ class="flex w-full items-center gap-2 p-3 text-left small-normal whitespace-nowrap transition-colors duration-200 focus:outline-none"
152
+ :class="
153
+ index > clampedStep
154
+ ? 'cursor-default text-gray-400'
155
+ : 'cursor-pointer text-gray-700 hover:bg-gray-100 hover:text-sapphire-400 focus-visible:bg-gray-100'
156
+ "
157
+ :disabled="index > clampedStep"
158
+ :aria-current="index === clampedStep ? 'step' : undefined"
159
+ @click="selectStep(index, close)"
160
+ >
161
+ <span
162
+ class="w-(--step-number-width) shrink-0 text-right tabular-nums"
163
+ :style="{ '--step-number-width': `${stepDigits}ch` }"
164
+ aria-hidden="true"
165
+ >
166
+ {{ index + 1 }}
167
+ </span>
168
+ <span :class="index === clampedStep ? 'text-sapphire-500' : ''">{{ title }}</span>
169
+ </button>
170
+ </li>
171
+ </ol>
172
+ </template>
173
+ </FloatingPanel>
174
+ </div>
113
175
  <ol
114
- v-if="hasTitles"
115
- class="flex @max-md:order-first"
116
- :class="isCompact ? 'order-first justify-center' : 'justify-between @max-md:justify-center'"
176
+ v-else-if="hasTitles"
177
+ class="flex justify-between @max-md:order-first @max-md:justify-center"
117
178
  aria-label="Progress steps"
118
179
  >
119
180
  <li
@@ -121,15 +182,8 @@ watchEffect(() => {
121
182
  :key="title"
122
183
  class="rounded-xs transition-colors duration-500 focus-visible:outline-3"
123
184
  :class="[
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' : '',
185
+ '@max-md:body-normal @min-md:small-normal',
186
+ index === clampedStep ? 'text-sapphire-500 @max-md:inline' : '@max-md:hidden',
133
187
  index < clampedStep ? 'cursor-pointer duration-200! hover:text-sapphire-400' : '',
134
188
  ]"
135
189
  :aria-current="index === clampedStep ? 'step' : undefined"
@@ -10,6 +10,7 @@ export interface ProposalReference {
10
10
  postalCode: string
11
11
  streetName: string
12
12
  houseNumber: number
13
+ houseNumberExt?: string | number | null
13
14
  houseType: string
14
15
  city: string
15
16
  mainImageUrl: string | null
@@ -65,7 +66,16 @@ const selected = defineModel<boolean>({ default: false })
65
66
 
66
67
  defineEmits<{ details: [reference: ProposalReference] }>()
67
68
 
68
- const address = computed(() => `${reference.streetName} ${reference.houseNumber}`)
69
+ const formattedHouseNumberExtension = (value: string | number | null | undefined) => {
70
+ const extension = String(value ?? '').trim()
71
+
72
+ return extension ? (extension.startsWith('-') ? extension : `-${extension}`) : ''
73
+ }
74
+
75
+ const address = computed(
76
+ () =>
77
+ `${reference.streetName} ${reference.houseNumber}${formattedHouseNumberExtension(reference.houseNumberExt)}`,
78
+ )
69
79
 
70
80
  const location = computed(() => `${reference.postalCode} ${reference.city}`)
71
81
 
@@ -74,7 +84,13 @@ const energyLabel = computed(() => (reference.energyLabel ?? null) as EnergyLabe
74
84
  const salePrice = computed(() => {
75
85
  if (!reference.salePrice) return null
76
86
 
77
- const amount = Number(reference.salePrice)
87
+ const normalized = String(reference.salePrice)
88
+ .replace(/[^\d,-]/g, '')
89
+ .replace(/\./g, '')
90
+ .replace(',', '.')
91
+ if (!normalized) return reference.salePrice
92
+
93
+ const amount = Number(normalized)
78
94
  if (Number.isNaN(amount)) return reference.salePrice
79
95
 
80
96
  return new Intl.NumberFormat(locale, {
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Hand-written utilities.
3
+ *
4
+ * The other files in this folder are generated by the Figma design-tokens
5
+ * plugin and get overwritten on every token sync, so anything we author by
6
+ * hand lives here. It is pulled in through `tailwind/theme.css`, the entry
7
+ * behind the `@nexxtmove/ui/theme` export.
8
+ */
9
+
10
+ /* Slim, branded scrollbar for scrollable panels (dropdowns, lists, drawers). */
11
+ @utility scrollbar-thin {
12
+ scrollbar-width: thin;
13
+ scrollbar-color: var(--color-cornflower-blue-200) transparent;
14
+
15
+ &::-webkit-scrollbar {
16
+ @apply h-1.5 w-1.5;
17
+ }
18
+
19
+ &::-webkit-scrollbar-track {
20
+ @apply bg-transparent;
21
+ }
22
+
23
+ &::-webkit-scrollbar-thumb {
24
+ @apply rounded-full bg-cornflower-blue-200 transition-colors duration-200;
25
+ }
26
+
27
+ &::-webkit-scrollbar-thumb:hover {
28
+ @apply bg-gray-300;
29
+ }
30
+ }
31
+
32
+ /* Scrollable, but without a visible scrollbar. */
33
+ @utility scrollbar-none {
34
+ scrollbar-width: none;
35
+
36
+ &::-webkit-scrollbar {
37
+ display: none;
38
+ }
39
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Entry point behind the `@nexxtmove/ui/theme` export.
3
+ *
4
+ * Keep this file free of anything generated: `nexxtmove.tailwind.theme.css`
5
+ * (and the utilities/components it imports) is overwritten by the Figma
6
+ * design-tokens sync, so hand-written CSS belongs in the custom file below.
7
+ */
8
+
9
+ @import './nexxtmove.tailwind.theme.css';
10
+ @import './nexxtmove.tailwind.custom.css';