@nexxtmove/ui 0.1.44 → 0.1.46

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.44",
4
+ "version": "0.1.46",
5
5
  "exports": {
6
6
  ".": {
7
7
  "types": "./dist/index.d.ts",
@@ -46,7 +46,26 @@ const emit = defineEmits<{
46
46
 
47
47
  const isOpen = ref(false)
48
48
  const wrapperRef = ref<HTMLElement | null>(null)
49
- const checkedItems = ref<Record<string, boolean>>({})
49
+ const flatItems = computed<NexxtDropdownButton[]>(() =>
50
+ options.length > 0 && 'items' in options[0]
51
+ ? (options as NexxtDropdownButtonGroup[]).flatMap((g) => g.items)
52
+ : (options as NexxtDropdownButton[]),
53
+ )
54
+
55
+ const toggledItems = ref<Record<string, boolean>>({})
56
+
57
+ const checkedItems = computed(() =>
58
+ Object.fromEntries(
59
+ flatItems.value
60
+ .filter((item) => item.checkbox)
61
+ .map((item) => [
62
+ item.label,
63
+ item.label in toggledItems.value
64
+ ? toggledItems.value[item.label]
65
+ : (item.selected ?? false),
66
+ ]),
67
+ ),
68
+ )
50
69
 
51
70
  const handleAction = (action: string | (() => void)) => {
52
71
  if (typeof action === 'function') {
@@ -63,7 +82,7 @@ const toggleDropdown = () => {
63
82
  const handleOptionClick = (option: NexxtDropdownButton) => {
64
83
  if (option.checkbox) {
65
84
  const newValue = !checkedItems.value[option.label]
66
- checkedItems.value[option.label] = newValue
85
+ toggledItems.value[option.label] = newValue
67
86
  emit('checkboxChange', option.label, newValue)
68
87
  } else {
69
88
  isOpen.value = false
@@ -181,8 +200,8 @@ const transitionClasses = computed(() => {
181
200
  <template v-for="(group, groupIndex) in groupedOptions" :key="group.title || groupIndex">
182
201
  <div
183
202
  v-if="group.title"
184
- class="border-b border-gray-100 px-4 py-1.5 extra-small-normal text-gray-600"
185
- :class="groupIndex > 0 ? 'mt-1 border-t pt-2' : ''"
203
+ class="border-b border-gray-100 px-6 py-3 extra-small-normal text-gray-600"
204
+ :class="groupIndex > 0 ? 'border-t pt-2' : ''"
186
205
  >
187
206
  {{ group.title }}
188
207
  </div>
@@ -191,7 +210,7 @@ const transitionClasses = computed(() => {
191
210
  :key="option.label"
192
211
  :role="option.checkbox ? 'menuitemcheckbox' : 'menuitem'"
193
212
  :aria-checked="option.checkbox ? (checkedItems[option.label] ?? false) : undefined"
194
- 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"
213
+ class="flex w-full min-w-42 cursor-pointer items-center gap-2 px-6 py-3 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"
195
214
  :class="!option.checkbox && option.selected ? 'bg-cornflower-blue-100' : ''"
196
215
  @click="handleOptionClick(option)"
197
216
  >
@@ -113,148 +113,145 @@ const handleRowKeydown = (event: KeyboardEvent, rowIndex: number) => {
113
113
 
114
114
  <template>
115
115
  <div class="flex flex-col gap-3">
116
- <div class="overflow-x-auto">
117
- <div
118
- role="grid"
119
- :aria-colcount="columns.length + (selectable ? 1 : 0)"
120
- :aria-rowcount="rows.length + 1"
121
- class="min-w-full"
122
- >
123
- <div class="sticky top-0 z-10 mb-0.5">
124
- <div v-if="$slots.toolbar">
125
- <slot name="toolbar" />
116
+ <div
117
+ role="grid"
118
+ :aria-colcount="columns.length + (selectable ? 1 : 0)"
119
+ :aria-rowcount="rows.length + 1"
120
+ class="min-w-full"
121
+ >
122
+ <div class="sticky top-0 z-10 mb-0.5">
123
+ <div v-if="$slots.toolbar">
124
+ <slot name="toolbar" />
125
+ </div>
126
+ <div
127
+ class="grid rounded-md bg-gray-50"
128
+ :style="{ gridTemplateColumns: gridTemplate }"
129
+ role="row"
130
+ aria-rowindex="1"
131
+ >
132
+ <div
133
+ v-if="selectable"
134
+ class="flex items-center justify-center px-2 py-3"
135
+ role="columnheader"
136
+ aria-colindex="1"
137
+ >
138
+ <Checkbox
139
+ :model-value="allSelected"
140
+ :indeterminate="someSelected"
141
+ aria-label="Select all rows"
142
+ @update:model-value="toggleAll"
143
+ />
126
144
  </div>
127
145
  <div
128
- class="grid rounded-md bg-gray-50"
129
- :style="{ gridTemplateColumns: gridTemplate }"
130
- role="row"
131
- aria-rowindex="1"
146
+ v-for="(column, colIndex) in columns"
147
+ :key="column.key"
148
+ class="flex items-center gap-1.5 px-4 py-3 heading-4 text-slate-700"
149
+ :class="{
150
+ 'cursor-pointer select-none hover:text-slate-900 focus-visible:ring-2 focus-visible:ring-cornflower-blue-500 focus-visible:outline-none focus-visible:ring-inset':
151
+ column.sortable,
152
+ }"
153
+ role="columnheader"
154
+ :aria-colindex="colIndex + (selectable ? 2 : 1)"
155
+ :aria-sort="
156
+ column.sortable
157
+ ? sortKey === column.key
158
+ ? sortDirection === 'asc'
159
+ ? 'ascending'
160
+ : 'descending'
161
+ : 'none'
162
+ : undefined
163
+ "
164
+ :tabindex="column.sortable ? 0 : undefined"
165
+ @click="handleSort(column)"
166
+ @keydown.enter.prevent="handleSort(column)"
167
+ @keydown.space.prevent="handleSort(column)"
132
168
  >
133
- <div
134
- v-if="selectable"
135
- class="flex items-center justify-center px-2 py-3"
136
- role="columnheader"
137
- aria-colindex="1"
138
- >
139
- <Checkbox
140
- :model-value="allSelected"
141
- :indeterminate="someSelected"
142
- aria-label="Select all rows"
143
- @update:model-value="toggleAll"
144
- />
145
- </div>
146
- <div
147
- v-for="(column, colIndex) in columns"
148
- :key="column.key"
149
- class="flex items-center gap-1.5 px-4 py-3 heading-4 text-slate-700"
150
- :class="{
151
- 'cursor-pointer select-none hover:text-slate-900 focus-visible:ring-2 focus-visible:ring-cornflower-blue-500 focus-visible:outline-none focus-visible:ring-inset':
152
- column.sortable,
153
- }"
154
- role="columnheader"
155
- :aria-colindex="colIndex + (selectable ? 2 : 1)"
156
- :aria-sort="
157
- column.sortable
158
- ? sortKey === column.key
159
- ? sortDirection === 'asc'
160
- ? 'ascending'
161
- : 'descending'
162
- : 'none'
163
- : undefined
169
+ <span :class="sortKey === column.key ? 'text-cornflower-blue-600' : ''">{{
170
+ column.label
171
+ }}</span>
172
+ <Icon
173
+ v-if="column.sortable"
174
+ :name="
175
+ sortKey === column.key
176
+ ? sortDirection === 'asc'
177
+ ? 'angle-down'
178
+ : 'angle-up'
179
+ : 'angles-up-down'
164
180
  "
165
- :tabindex="column.sortable ? 0 : undefined"
166
- @click="handleSort(column)"
167
- @keydown.enter.prevent="handleSort(column)"
168
- @keydown.space.prevent="handleSort(column)"
169
- >
170
- <span :class="sortKey === column.key ? 'text-cornflower-blue-600' : ''">{{
171
- column.label
172
- }}</span>
173
- <Icon
174
- v-if="column.sortable"
175
- :name="
176
- sortKey === column.key
177
- ? sortDirection === 'asc'
178
- ? 'angle-down'
179
- : 'angle-up'
180
- : 'angles-up-down'
181
- "
182
- class="shrink-0 text-xs"
183
- :class="sortKey === column.key ? 'text-cornflower-blue-600' : 'text-slate-400'"
184
- />
185
- </div>
181
+ class="shrink-0 text-xs"
182
+ :class="sortKey === column.key ? 'text-cornflower-blue-600' : 'text-slate-400'"
183
+ />
186
184
  </div>
187
185
  </div>
186
+ </div>
188
187
 
189
- <div role="rowgroup" class="relative">
188
+ <div role="rowgroup" class="relative">
189
+ <div
190
+ v-for="(row, rowIndex) in rows"
191
+ :key="rowIndex"
192
+ ref="rowRefs"
193
+ class="grid border-b border-gray-100 transition-colors duration-150 hover:bg-cornflower-blue-50 focus-visible:bg-slate-50 focus-visible:ring-2 focus-visible:ring-cornflower-blue-500 focus-visible:outline-none focus-visible:ring-inset"
194
+ :class="[
195
+ {
196
+ 'hover:rounded-md': !isRowSelected(rows[rowIndex]),
197
+ 'border-white/80 bg-cornflower-blue-100': selectable && isRowSelected(rows[rowIndex]),
198
+ 'border-transparent': selectable && isRowSelected(rows[rowIndex + 1]),
199
+ },
200
+ getRowRoundedClass(rowIndex),
201
+ ]"
202
+ :style="{ gridTemplateColumns: gridTemplate }"
203
+ role="row"
204
+ :aria-rowindex="rowIndex + 2"
205
+ :aria-selected="selectable ? isRowSelected(row) : undefined"
206
+ tabindex="0"
207
+ @click="handleRowClick($event, rowIndex)"
208
+ @keydown="handleRowKeydown($event, rowIndex)"
209
+ >
190
210
  <div
191
- v-for="(row, rowIndex) in rows"
192
- :key="rowIndex"
193
- ref="rowRefs"
194
- class="grid border-b border-gray-100 transition-colors duration-150 hover:bg-cornflower-blue-50 focus-visible:bg-slate-50 focus-visible:ring-2 focus-visible:ring-cornflower-blue-500 focus-visible:outline-none focus-visible:ring-inset"
195
- :class="[
196
- {
197
- 'hover:rounded-md': !isRowSelected(rows[rowIndex]),
198
- 'border-white/80 bg-cornflower-blue-100':
199
- selectable && isRowSelected(rows[rowIndex]),
200
- 'border-transparent': selectable && isRowSelected(rows[rowIndex + 1]),
201
- },
202
- getRowRoundedClass(rowIndex),
203
- ]"
204
- :style="{ gridTemplateColumns: gridTemplate }"
205
- role="row"
206
- :aria-rowindex="rowIndex + 2"
207
- :aria-selected="selectable ? isRowSelected(row) : undefined"
208
- tabindex="0"
209
- @click="handleRowClick($event, rowIndex)"
210
- @keydown="handleRowKeydown($event, rowIndex)"
211
+ v-if="selectable"
212
+ class="flex items-center justify-center px-2 py-4"
213
+ role="gridcell"
214
+ :aria-colindex="1"
211
215
  >
212
- <div
213
- v-if="selectable"
214
- class="flex items-center justify-center px-2 py-4"
215
- role="gridcell"
216
- :aria-colindex="1"
217
- >
218
- <Checkbox
219
- :model-value="isRowSelected(row)"
220
- :aria-label="`Select row ${rowIndex + 1}`"
221
- @update:model-value="toggleRow(row)"
222
- />
223
- </div>
224
- <div
225
- v-for="(column, colIndex) in columns"
226
- :key="column.key"
227
- class="flex items-center truncate px-4 py-4 extra-small-normal text-slate-700"
228
- role="gridcell"
229
- :aria-colindex="colIndex + (selectable ? 2 : 1)"
230
- >
231
- <slot :name="cellSlotName(column.key)" :row="row" :value="rowValue(row, column.key)">
232
- {{ rowValue(row, column.key) }}
233
- </slot>
234
- </div>
216
+ <Checkbox
217
+ :model-value="isRowSelected(row)"
218
+ :aria-label="`Select row ${rowIndex + 1}`"
219
+ @update:model-value="toggleRow(row)"
220
+ />
235
221
  </div>
236
-
237
- <div v-if="!rows.length" class="px-4 py-8 text-center text-sm text-slate-400" role="row">
238
- <slot name="empty">Geen data beschikbaar</slot>
222
+ <div
223
+ v-for="(column, colIndex) in columns"
224
+ :key="column.key"
225
+ class="flex items-center truncate px-4 py-4 extra-small-normal text-slate-700"
226
+ role="gridcell"
227
+ :aria-colindex="colIndex + (selectable ? 2 : 1)"
228
+ >
229
+ <slot :name="cellSlotName(column.key)" :row="row" :value="rowValue(row, column.key)">
230
+ {{ rowValue(row, column.key) }}
231
+ </slot>
239
232
  </div>
233
+ </div>
240
234
 
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>
235
+ <div v-if="!rows.length" class="px-4 py-8 text-center text-sm text-slate-400" role="row">
236
+ <slot name="empty">Geen data beschikbaar</slot>
257
237
  </div>
238
+
239
+ <Transition
240
+ enter-active-class="transition-opacity duration-200"
241
+ leave-active-class="transition-opacity duration-200"
242
+ enter-from-class="opacity-0"
243
+ leave-to-class="opacity-0"
244
+ >
245
+ <div
246
+ v-if="loading"
247
+ data-testid="loading-overlay"
248
+ class="absolute inset-0 flex items-center justify-center rounded-md bg-white/40 backdrop-blur-sm"
249
+ >
250
+ <slot name="loading"
251
+ ><Icon name="spinner-third" class="fa-spin text-3xl text-purple-500"
252
+ /></slot>
253
+ </div>
254
+ </Transition>
258
255
  </div>
259
256
  </div>
260
257
  </div>
@@ -117,6 +117,10 @@
117
117
  @apply bg-social-media-template-info-bg;
118
118
  }
119
119
 
120
+ .card {
121
+ @apply border-card-border border border-solid;
122
+ }
123
+
120
124
  .info-block {
121
125
  @apply p-(--info-block-padding);
122
126
  }
@@ -159,6 +159,7 @@
159
159
  --color-progress-bar-border: var(--color-gray-200);
160
160
  --color-header-border-bottom: var(--color-gray-200);
161
161
  --color-social-media-template-info-bg: var(--color-cornflower-blue-500);
162
+ --color-card-border: var(--color-gray-200);
162
163
  --text-xs: 0.75rem;
163
164
  --text-base: 1rem;
164
165
  --text-lg: 1.125rem;
@@ -220,12 +221,13 @@
220
221
  --button-padding-y: var(--spacing-2);
221
222
  --button-padding-link-x: var(--spacing-0\.5);
222
223
  --button-padding-link-y: var(--spacing-0\.5);
223
- --button-gap: var(--spacing-1);
224
+ --button-gap: var(--spacing-2);
224
225
  --button-border-radius: var(--radius-lg);
225
226
  --header-height: var(--spacing-24);
226
227
  --header-padding-x: var(--spacing-10);
227
228
  --header-gap: var(--spacing-6);
228
229
  --info-block-padding: var(--spacing-1);
230
+ --card-border-radius: var(--radius-lg);
229
231
  --font-sans: 'Poppins', ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
230
232
  --font-serif: 'Playfair Display', ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
231
233
  --font-mono: 'Space Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;