@zap-wunschlachen/wl-shared-components 1.0.88 → 1.0.89

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.
@@ -0,0 +1,97 @@
1
+ name: Unit Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [develop]
6
+ paths:
7
+ - "src/**"
8
+ - "tests/unit/**"
9
+ - "package.json"
10
+ - "package-lock.json"
11
+ - "tsconfig.json"
12
+ - "vitest.config.ts"
13
+ - "vite.config.ts"
14
+ - ".github/workflows/unit.yml"
15
+ pull_request:
16
+ branches: [develop]
17
+ paths:
18
+ - "src/**"
19
+ - "tests/unit/**"
20
+ - "package.json"
21
+ - "package-lock.json"
22
+ - "tsconfig.json"
23
+ - "vitest.config.ts"
24
+ - "vite.config.ts"
25
+ - ".github/workflows/unit.yml"
26
+ workflow_dispatch:
27
+
28
+ permissions:
29
+ contents: read
30
+ checks: write
31
+
32
+ concurrency:
33
+ group: unit-${{ github.head_ref || github.ref_name }}
34
+ cancel-in-progress: true
35
+
36
+ jobs:
37
+ unit-tests:
38
+ name: Unit Tests
39
+ runs-on: ubuntu-latest
40
+ timeout-minutes: 15
41
+
42
+ steps:
43
+ - uses: actions/checkout@v4
44
+
45
+ - uses: actions/setup-node@v4
46
+ with:
47
+ node-version: '20'
48
+ cache: 'npm'
49
+
50
+ - name: Install dependencies
51
+ run: npm ci
52
+
53
+ - name: Run unit tests
54
+ run: npx vitest run --reporter=dot --reporter=github-actions --reporter=junit --reporter=json --outputFile.junit=test-results/vitest-junit.xml --outputFile.json=test-results/vitest-results.json
55
+
56
+ - name: Test Report
57
+ uses: dorny/test-reporter@v2
58
+ if: always()
59
+ with:
60
+ name: Vitest Results
61
+ path: test-results/vitest-junit.xml
62
+ reporter: java-junit
63
+
64
+ - name: Upload test results
65
+ uses: actions/upload-artifact@v4
66
+ if: always()
67
+ with:
68
+ name: test-results
69
+ path: test-results/
70
+ retention-days: 7
71
+
72
+ coverage:
73
+ name: Coverage
74
+ runs-on: ubuntu-latest
75
+ timeout-minutes: 15
76
+
77
+ steps:
78
+ - uses: actions/checkout@v4
79
+
80
+ - uses: actions/setup-node@v4
81
+ with:
82
+ node-version: '20'
83
+ cache: 'npm'
84
+
85
+ - name: Install dependencies
86
+ run: npm ci
87
+
88
+ - name: Run tests with coverage
89
+ run: npx vitest run --coverage
90
+
91
+ - name: Upload coverage report
92
+ uses: actions/upload-artifact@v4
93
+ if: always()
94
+ with:
95
+ name: coverage
96
+ path: coverage/
97
+ retention-days: 7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zap-wunschlachen/wl-shared-components",
3
- "version": "1.0.88",
3
+ "version": "1.0.89",
4
4
  "type": "module",
5
5
  "module": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -31,6 +31,7 @@
31
31
  :aria-invalid="error || undefined"
32
32
  :aria-label="ariaLabel || undefined"
33
33
  :aria-describedby="ariaDescribedby || undefined"
34
+ :hide-no-data="hideNoData"
34
35
  :menu-props="computedMenuProps"
35
36
  v-model="internalValue"
36
37
  @click:append="onClickAppend"
@@ -61,6 +62,12 @@
61
62
  <slot name="append-inner"></slot>
62
63
  </template>
63
64
 
65
+ <template #no-data>
66
+ <slot name="no-data">
67
+ <v-list-item :title="resolvedNoDataText" />
68
+ </slot>
69
+ </template>
70
+
64
71
  <template v-if="$slots['item']" #item="slotProps">
65
72
  <slot name="item" v-bind="slotProps"></slot>
66
73
  </template>
@@ -76,6 +83,9 @@ import './Select.css';
76
83
  import { ref, watch, nextTick, computed, inject } from 'vue';
77
84
  import { defineProps, defineEmits } from 'vue';
78
85
  import { siteColors } from "../../utils/index";
86
+ import { useI18n } from 'vue-i18n';
87
+
88
+ const { t } = useI18n();
79
89
 
80
90
  // Inject theme colors from ThemeProvider, fallback to global siteColors
81
91
  const injectedThemeColors = inject('themeColors', null);
@@ -306,6 +316,25 @@ const props = defineProps({
306
316
  type: String,
307
317
  default: undefined,
308
318
  },
319
+
320
+ /**
321
+ * When true, hides the dropdown menu when there are no items.
322
+ * Set to false to allow the menu to open and show a "no data" message.
323
+ * Default is false — the menu opens and shows noDataText.
324
+ */
325
+ hideNoData: {
326
+ type: Boolean,
327
+ default: false,
328
+ },
329
+
330
+ /**
331
+ * Text shown in the dropdown when there are no items and hideNoData is false.
332
+ * Default is 'No data available'.
333
+ */
334
+ noDataText: {
335
+ type: String,
336
+ default: undefined,
337
+ },
309
338
  });
310
339
 
311
340
  // Define events that can be emitted by the component
@@ -319,6 +348,8 @@ const emits = defineEmits([
319
348
  'update:search', // Emit search updates for filtering items
320
349
  ]);
321
350
 
351
+ const resolvedNoDataText = computed(() => props.noDataText ?? t('wl.select.no_data'));
352
+
322
353
  // Define a reactive reference for internal value based on the modelValue prop
323
354
  const internalValue = ref(props.modelValue);
324
355
 
@@ -32,6 +32,9 @@
32
32
  "fill_form_title": "Anamnesebogen benötigt!",
33
33
  "fill_form_description": "Bitte füllen Sie diesen vor ihren Termin aus",
34
34
  "anamnese_form": "Anamnesebogen"
35
+ },
36
+ "select": {
37
+ "no_data": "Keine Daten verfügbar"
35
38
  }
36
39
  }
37
40
  }
@@ -32,6 +32,9 @@
32
32
  "fill_form_title": "Medical history form required!",
33
33
  "fill_form_description": "Please fill this out before your appointment",
34
34
  "anamnese_form": "Medical history form"
35
+ },
36
+ "select": {
37
+ "no_data": "No data available"
35
38
  }
36
39
  }
37
40
  }
@@ -117,6 +117,119 @@
117
117
  </div>
118
118
  </div>
119
119
 
120
+ <!-- No Data / Empty State -->
121
+ <div class="section">
122
+ <h3 class="section-title">No Data / Empty State</h3>
123
+ <p class="section-description">When items are empty, the dropdown opens and shows a configurable message (hideNoData defaults to false)</p>
124
+ <div class="examples-grid">
125
+ <div class="example-item">
126
+ <h4>Default (No Data Available)</h4>
127
+ <Select
128
+ v-model="noDataDefault"
129
+ label="Select an option"
130
+ :items="[]"
131
+ />
132
+ <p class="value-display">Empty items — click to see default message</p>
133
+ </div>
134
+
135
+ <div class="example-item">
136
+ <h4>Custom No Data Text</h4>
137
+ <Select
138
+ v-model="noDataCustomText"
139
+ label="Select a relationship"
140
+ :items="[]"
141
+ no-data-text="Failed to load relationships"
142
+ />
143
+ <p class="value-display">Simulates API failure with custom message</p>
144
+ </div>
145
+
146
+ <div class="example-item">
147
+ <h4>Hidden No Data (old behavior)</h4>
148
+ <Select
149
+ v-model="noDataHidden"
150
+ label="Select an option"
151
+ :items="[]"
152
+ :hide-no-data="true"
153
+ />
154
+ <p class="value-display">hideNoData=true — dropdown won't open</p>
155
+ </div>
156
+
157
+ <div class="example-item">
158
+ <h4>Custom No Data Slot</h4>
159
+ <Select
160
+ v-model="noDataCustomSlot"
161
+ label="Select an option"
162
+ :items="[]"
163
+ >
164
+ <template #no-data>
165
+ <div class="custom-no-data">
166
+ <p class="custom-no-data-title">Nothing here yet</p>
167
+ <p class="custom-no-data-desc">Try a different search or add a new item.</p>
168
+ <button class="custom-no-data-btn" @click="onNoDataAction">Add new item</button>
169
+ </div>
170
+ </template>
171
+ </Select>
172
+ <p class="value-display">Uses #no-data slot for custom content</p>
173
+ </div>
174
+ </div>
175
+ </div>
176
+
177
+ <!-- No Data - SelectAutocomplete -->
178
+ <div class="section">
179
+ <h3 class="section-title">No Data — SelectAutocomplete</h3>
180
+ <p class="section-description">Same no-data behavior applies to the autocomplete variant</p>
181
+ <div class="examples-grid">
182
+ <div class="example-item">
183
+ <h4>Default (No Data Available)</h4>
184
+ <SelectAutocomplete
185
+ v-model="noDataAutocompleteDefault"
186
+ label="Search..."
187
+ :items="[]"
188
+ />
189
+ <p class="value-display">Empty items — type to see default message</p>
190
+ </div>
191
+
192
+ <div class="example-item">
193
+ <h4>Custom No Data Text</h4>
194
+ <SelectAutocomplete
195
+ v-model="noDataAutocompleteCustom"
196
+ label="Search a patient"
197
+ :items="[]"
198
+ no-data-text="Kein Patient gefunden"
199
+ />
200
+ <p class="value-display">Custom text via noDataText prop</p>
201
+ </div>
202
+
203
+ <div class="example-item">
204
+ <h4>Custom No Data Slot</h4>
205
+ <SelectAutocomplete
206
+ v-model="noDataAutocompleteSlot"
207
+ label="Search..."
208
+ :items="[]"
209
+ >
210
+ <template #no-data>
211
+ <div class="custom-no-data">
212
+ <p class="custom-no-data-title">No results found</p>
213
+ <p class="custom-no-data-desc">Try adjusting your search terms.</p>
214
+ </div>
215
+ </template>
216
+ </SelectAutocomplete>
217
+ <p class="value-display">Uses #no-data slot for custom content</p>
218
+ </div>
219
+
220
+ <div class="example-item">
221
+ <h4>Hidden No Data</h4>
222
+ <SelectAutocomplete
223
+ v-model="noDataAutocompleteHidden"
224
+ label="Search..."
225
+ :items="[]"
226
+ :hide-no-data="true"
227
+ />
228
+ <p class="value-display">hideNoData=true — menu stays closed</p>
229
+ </div>
230
+ </div>
231
+ </div>
232
+
120
233
  <!-- Multiple Selection -->
121
234
  <div class="section">
122
235
  <h3 class="section-title">Multiple Selection</h3>
@@ -835,6 +948,20 @@ const disabledValue = ref({ title: 'Apple', value: 'apple' });
835
948
  const errorValue = ref(null);
836
949
  const preselectedValue = ref({ title: 'Cherry', value: 'cherry' });
837
950
 
951
+ // No data / empty state
952
+ const noDataDefault = ref(null);
953
+ const noDataCustomText = ref(null);
954
+ const noDataHidden = ref(null);
955
+ const noDataCustomSlot = ref(null);
956
+ const noDataAutocompleteDefault = ref(null);
957
+ const noDataAutocompleteCustom = ref(null);
958
+ const noDataAutocompleteSlot = ref(null);
959
+ const noDataAutocompleteHidden = ref(null);
960
+
961
+ const onNoDataAction = () => {
962
+ alert('Add new item clicked');
963
+ };
964
+
838
965
  // Multiple selection
839
966
  const multipleValue = ref([]);
840
967
  const chipsValue = ref([]);
@@ -1299,4 +1426,43 @@ const removeSelection = (index: number) => {
1299
1426
  .patient-edit-btn:hover {
1300
1427
  background-color: rgba(23, 39, 116, 0.05);
1301
1428
  }
1429
+
1430
+ /* Custom no-data slot styles */
1431
+ .custom-no-data {
1432
+ display: flex;
1433
+ flex-direction: column;
1434
+ align-items: center;
1435
+ padding: 1.25rem 1rem;
1436
+ text-align: center;
1437
+ }
1438
+
1439
+ .custom-no-data-title {
1440
+ font-size: 0.9375rem;
1441
+ font-weight: 600;
1442
+ color: #333;
1443
+ margin: 0 0 0.375rem 0;
1444
+ }
1445
+
1446
+ .custom-no-data-desc {
1447
+ font-size: 0.8125rem;
1448
+ color: #888;
1449
+ margin: 0 0 0.75rem 0;
1450
+ line-height: 1.4;
1451
+ }
1452
+
1453
+ .custom-no-data-btn {
1454
+ background: none;
1455
+ border: 1.5px solid #4caf50;
1456
+ color: #4caf50;
1457
+ border-radius: 999px;
1458
+ padding: 0.375rem 1rem;
1459
+ font-size: 0.8125rem;
1460
+ font-weight: 500;
1461
+ cursor: pointer;
1462
+ transition: background-color 0.2s;
1463
+ }
1464
+
1465
+ .custom-no-data-btn:hover {
1466
+ background-color: rgba(76, 175, 80, 0.08);
1467
+ }
1302
1468
  </style>
@@ -7,7 +7,7 @@ import { siteColors } from '@/utils';
7
7
  import AccordionGroup from '@components/Accordion/AccordionGroup.vue';
8
8
  import AccordionItem from '@components/Accordion/AccordionItem.vue';
9
9
  import Button from '@components/Button/Button.vue';
10
- import Checkbox from '@components/Checkbox/Checkbox.vue';
10
+ import Checkbox from '@components/CheckBox/Checkbox.vue';
11
11
  import DateInput from '@components/DateInput/DateInput.vue';
12
12
  import Dialog from '@components/Dialog/Dialog.vue';
13
13
  import EditField from '@components/EditField/EditField.vue';
@@ -1,6 +1,6 @@
1
1
  import { describe, it, expect, vi } from 'vitest';
2
2
  import { mount } from '@vue/test-utils';
3
- import Calendar from '@components/Icons/calendar.vue';
3
+ import Calendar from '@components/Icons/Calendar.vue';
4
4
 
5
5
  // Mock siteColors
6
6
  vi.mock('@/utils/index', () => ({
@@ -1,6 +1,6 @@
1
1
  import { describe, it, expect, vi } from 'vitest';
2
2
  import { mount } from '@vue/test-utils';
3
- import Play from '@components/Icons/play.vue';
3
+ import Play from '@components/Icons/Play.vue';
4
4
 
5
5
  // Mock siteColors
6
6
  vi.mock('@/utils/index', () => ({