@paris-ias/list 1.2.2 → 1.3.0

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/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@paris-ias/list",
3
3
  "configKey": "list",
4
- "version": "1.2.2",
4
+ "version": "1.3.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "0.8.4",
7
7
  "unbuild": "2.0.0"
@@ -49,12 +49,15 @@
49
49
  'list.filters.fellowships.fellowshipType.' +
50
50
  item.fellowshipType,
51
51
  ),
52
- ...(props.item && props.item.disciplines
53
- ? props.item.disciplines.map((discipline) => discipline.name)
54
- : []),
55
52
  ]"
56
53
  class="mt-2"
57
54
  />
55
+ <!-- DISCIPLINES -->
56
+ <MiscMoleculesDisciplinesTags
57
+ :disciplines="item.disciplines"
58
+ justify="center"
59
+ class="mt-4"
60
+ />
58
61
  <div class="mt-5">
59
62
  <FellowshipsBadges :item="item" :view="view" :loading="loading" />
60
63
  </div>
@@ -152,58 +155,43 @@
152
155
 
153
156
  <script setup>
154
157
  import { useDisplay } from "vuetify"
155
- import { ref } from "#imports"
158
+ import { computed, ref } from "#imports"
156
159
  const { name } = useDisplay()
157
160
  const accordeon = ref(-1)
158
161
  const props = defineProps({
162
+ // null while the resource item is loading (see useI18nResourceItem)
159
163
  item: {
160
164
  type: Object,
161
- required: true,
165
+ default: null,
162
166
  },
163
167
  loading: {
164
168
  type: Boolean,
165
169
  default: false,
166
- required: true,
167
170
  },
168
171
  })
169
172
 
170
173
  const view = ref(true)
171
174
 
172
- const renderedDetails = {
173
- ...(props.item?.fellowshipDetails?.type && {
174
- type: props.item?.fellowshipDetails?.type,
175
- }),
176
- ...(props.item?.fellowshipDetails?.fundingPeriod && {
177
- fundingPeriod: props.item?.fellowshipDetails?.fundingPeriod,
178
- }),
179
- ...(props.item?.fellowshipDetails?.profile && {
180
- profile: props.item?.fellowshipDetails?.profile,
181
- }),
182
- ...(props.item?.fellowshipDetails?.tasks && {
183
- tasks: props.item?.fellowshipDetails?.tasks,
184
- }),
185
- ...(props.item?.fellowshipDetails?.location && {
186
- location: props.item?.fellowshipDetails?.location,
187
- }),
188
- ...(props.item?.fellowshipDetails?.funding && {
189
- funding: props.item?.fellowshipDetails?.funding,
190
- }),
191
- ...(props.item?.fellowshipDetails?.housing && {
192
- housing: props.item?.fellowshipDetails?.housing,
193
- }),
194
- ...(props.item?.fellowshipDetails?.meals && {
195
- meals: props.item?.fellowshipDetails?.meals,
196
- }),
197
- ...(props.item?.fellowshipDetails?.applicationMaterials && {
198
- applicationMaterials: props.item?.fellowshipDetails?.applicationMaterials,
199
- }),
200
- ...(props.item?.fellowshipDetails?.selectionProcess && {
201
- selectionProcess: props.item?.fellowshipDetails?.selectionProcess,
202
- }),
203
- ...(props.item?.fellowshipDetails?.researchProcess && {
204
- researchProcess: props.item?.fellowshipDetails?.researchProcess,
205
- }),
206
- }
175
+ // computed (not a one-shot setup value) so it picks up `item` once it loads in
176
+ const renderedDetails = computed(() => {
177
+ const d = props.item?.fellowshipDetails
178
+ if (!d) return {}
179
+ return {
180
+ ...(d.type && { type: d.type }),
181
+ ...(d.fundingPeriod && { fundingPeriod: d.fundingPeriod }),
182
+ ...(d.profile && { profile: d.profile }),
183
+ ...(d.tasks && { tasks: d.tasks }),
184
+ ...(d.location && { location: d.location }),
185
+ ...(d.funding && { funding: d.funding }),
186
+ ...(d.housing && { housing: d.housing }),
187
+ ...(d.meals && { meals: d.meals }),
188
+ ...(d.applicationMaterials && {
189
+ applicationMaterials: d.applicationMaterials,
190
+ }),
191
+ ...(d.selectionProcess && { selectionProcess: d.selectionProcess }),
192
+ ...(d.researchProcess && { researchProcess: d.researchProcess }),
193
+ }
194
+ })
207
195
  </script>
208
196
 
209
197
  <style lang="scss" scoped></style>
@@ -62,28 +62,31 @@ const getItems = (name) => {
62
62
  return []
63
63
  }
64
64
 
65
+ // Disciplines and thematics are shared enums used across most list types.
66
+ // They live in their own top-level i18n namespace (list.filters.disciplines
67
+ // / list.filters.thematics) and are resolved from there for every type,
68
+ // independently of filters.json, to avoid duplicating them per type.
69
+ if (["disciplines", "thematics"].includes(name)) {
70
+ return Object.keys(messages.value[locale.value].list.filters?.[name])
71
+ .filter((key) => key !== "label")
72
+ .map((item) => ({
73
+ title: i18n.t(`list.filters.${name}.${item}`),
74
+ value: item,
75
+ }))
76
+ .sort((a, b) => a.title.localeCompare(b.title))
77
+ }
78
+
65
79
  if ($filters?.[props.type]?.[name]) {
66
- // Disciplines and thematics have their own namespace for translations
67
- if (["disciplines", "thematics"].includes(name)) {
68
- return Object.keys(messages.value[locale.value].list.filters?.[name])
69
- .filter((key) => key !== "label")
70
- .map((item) => ({
71
- title: i18n.t(`list.filters.${name}.${item}`),
72
- value: item,
73
- }))
74
- .sort((a, b) => a.title.localeCompare(b.title))
75
- } else {
76
- return $filters[props.type][name]
77
- .filter((key) => key !== "label")
78
- .map((item) => ({
79
- title: i18n.t(
80
- props.type === "people" && name === "vintage"
81
- ? item
82
- : `list.filters.${props.type}.${name}.${item}`,
83
- ),
84
- value: item,
85
- }))
86
- }
80
+ return $filters[props.type][name]
81
+ .filter((key) => key !== "label")
82
+ .map((item) => ({
83
+ title: i18n.t(
84
+ props.type === "people" && name === "vintage"
85
+ ? item
86
+ : `list.filters.${props.type}.${name}.${item}`,
87
+ ),
88
+ value: item,
89
+ }))
87
90
  }
88
91
 
89
92
  if (!messages.value[locale.value].list.filters[props.type]?.[name]) {
@@ -0,0 +1,79 @@
1
+ <template>
2
+ <v-skeleton-loader
3
+ v-if="loading"
4
+ type="chip@3"
5
+ class="discipline-skeleton mt-2"
6
+ />
7
+ <div
8
+ v-else-if="disciplines.length"
9
+ class="d-flex flex-wrap"
10
+ :class="justify === 'center' ? 'justify-center' : ''"
11
+ style="gap: 6px"
12
+ >
13
+ <v-chip
14
+ v-for="(d, i) in shownDisciplines"
15
+ :key="d + i"
16
+ size="small"
17
+ tile
18
+ variant="flat"
19
+ color="grey-lighten-3"
20
+ class="discipline-chip"
21
+ >
22
+ {{ disciplineLabel(d) }}
23
+ </v-chip>
24
+ <v-chip
25
+ v-if="extraDisciplines"
26
+ size="small"
27
+ variant="flat"
28
+ tile
29
+ color="grey-lighten-3"
30
+ class="discipline-chip"
31
+ >
32
+ +{{ extraDisciplines }}
33
+ </v-chip>
34
+ </div>
35
+ </template>
36
+
37
+ <script setup>
38
+ import { computed, useI18n } from "#imports"
39
+
40
+ const { t } = useI18n()
41
+
42
+ const props = defineProps({
43
+ // scalar enum array (e.g. item.disciplines)
44
+ disciplines: {
45
+ type: Array,
46
+ default: () => [],
47
+ },
48
+ loading: {
49
+ type: Boolean,
50
+ default: false,
51
+ },
52
+ // cap the number of chips; 0 = show all (used by detail views)
53
+ max: {
54
+ type: Number,
55
+ default: 0,
56
+ },
57
+ // 'start' (dense list) | 'center' (detail views)
58
+ justify: {
59
+ type: String,
60
+ default: "start",
61
+ },
62
+ })
63
+
64
+ // normalise to an array regardless of what the caller passes
65
+ const disciplines = computed(() =>
66
+ Array.isArray(props.disciplines) ? props.disciplines : [],
67
+ )
68
+ const shownDisciplines = computed(() =>
69
+ props.max > 0 ? disciplines.value.slice(0, props.max) : disciplines.value,
70
+ )
71
+ const extraDisciplines = computed(() =>
72
+ props.max > 0 ? Math.max(0, disciplines.value.length - props.max) : 0,
73
+ )
74
+ const disciplineLabel = (d) => t("list.filters.disciplines." + d)
75
+ </script>
76
+
77
+ <style scoped>
78
+ .discipline-chip{border-radius:6px;color:rgba(0,0,0,.78);font-weight:500}.discipline-skeleton :deep(.v-skeleton-loader__chip){margin:0 6px 0 0}
79
+ </style>
@@ -73,37 +73,35 @@
73
73
  <div class="mt-6 align-self-center">
74
74
  <PeopleBadges v-if="item && item.groups" :item="item" />
75
75
  </div>
76
-
76
+ <!-- DIVIDERS -->
77
+ <v-responsive class="mx-auto my-6" width="120">
78
+ <v-divider class="mb-1" />
79
+ <v-divider />
80
+ </v-responsive>
77
81
  <!-- FELLOWSHIP -->
78
- <div v-if="fellowName || fellowTheme || fellowDates" class="mt-6">
79
- <div v-if="fellowName" class="text-h6 font-weight-regular">
80
- {{ fellowName }}
81
- </div>
82
-
83
- <!-- DISCIPLINES -->
82
+ <div
83
+ v-if="fellowshipProjectName || fellowTheme || fellowDates"
84
+ class=""
85
+ >
84
86
  <div
85
- v-if="disciplines.length"
86
- class="mt-4 d-flex flex-wrap justify-center"
87
- style="gap: 6px"
87
+ v-if="fellowshipProjectName"
88
+ class="text-h6 font-weight-regular"
88
89
  >
89
- <v-chip
90
- v-for="(d, i) in disciplines"
91
- :key="d + i"
92
- size="small"
93
- variant="flat"
94
- tile
95
- color="grey-lighten-3"
96
- class="discipline-chip"
97
- >
98
- {{ disciplineLabel(d) }}
99
- </v-chip>
90
+ {{ fellowshipProjectName }}
100
91
  </div>
101
- <div v-if="fellowTheme" class="text-body-1 mt-1 font-italic">
92
+
93
+ <!-- DISCIPLINES -->
94
+ <MiscMoleculesDisciplinesTags
95
+ :disciplines="item.disciplines"
96
+ justify="center"
97
+ class="mt-4"
98
+ />
99
+ <div v-if="fellowTheme" class="text-body-1 my-3 font-italic">
102
100
  {{ fellowTheme }}
103
101
  </div>
104
102
  <div
105
103
  v-if="fellowDates"
106
- class="text-body-2 mt-1 text-medium-emphasis"
104
+ class="text-body-2 my-3 text-medium-emphasis"
107
105
  >
108
106
  {{ fellowDates }}
109
107
  </div>
@@ -217,22 +215,17 @@ const { t, locale } = useI18n()
217
215
  const { $stores } = useNuxtApp()
218
216
  const { name, mdAndUp } = useDisplay()
219
217
  const props = defineProps({
220
- item: { type: Object, required: true },
218
+ // null while the resource item is loading (see useI18nResourceItem)
219
+ item: { type: Object, default: null },
221
220
  loading: { type: Boolean, default: false },
222
221
  })
223
222
  $stores.people.loading = false
224
223
 
225
- // disciplines: scalar enum array → translated labels
226
- const disciplines = computed(() =>
227
- Array.isArray(props.item?.disciplines) ? props.item.disciplines : [],
228
- )
229
- const disciplineLabel = (d) => t("list.filters.disciplines." + d)
230
-
231
224
  // latest is a union Vintage | Position; the fellowship program lives on the
232
225
  // Vintage branch (name = program name, theme = fellowship theme) and carries the
233
226
  // arrival/departure dates (start/stop). Guard by field presence.
234
227
  const latest = computed(() => props.item?.latest ?? null)
235
- const fellowName = computed(() => latest.value?.name ?? null)
228
+ const fellowshipProjectName = computed(() => latest.value?.name ?? null)
236
229
  const fellowTheme = computed(() => latest.value?.theme ?? null)
237
230
 
238
231
  // "present" = currently in residence/post: started on or before today and not
@@ -259,5 +252,5 @@ const fellowDates = computed(() => {
259
252
  </script>
260
253
 
261
254
  <style scoped>
262
- .discipline-chip{border-radius:6px;color:rgba(0,0,0,.78);font-weight:500}.present-pill{align-items:center;background-color:rgba(0,0,0,.06);border-radius:999px;color:rgba(0,0,0,.7);display:inline-flex;font-size:.6875rem;font-weight:500;gap:5px;letter-spacing:.04em;line-height:1;padding:4px 10px;text-transform:uppercase}.present-dot{background-color:#4caf50;border-radius:50%;height:6px;width:6px}
255
+ .present-pill{align-items:center;background-color:rgba(0,0,0,.06);border-radius:999px;color:rgba(0,0,0,.7);display:inline-flex;font-size:.6875rem;font-weight:500;gap:5px;letter-spacing:.04em;line-height:1;padding:4px 10px;text-transform:uppercase}.present-dot{background-color:#4caf50;border-radius:50%;height:6px;width:6px}
263
256
  </style>
@@ -45,6 +45,13 @@
45
45
  >
46
46
  <MDC v-if="item && item.subtitle" :value="item.subtitle" />
47
47
  </div>
48
+ <!-- DISCIPLINES -->
49
+ <MiscMoleculesDisciplinesTags
50
+ v-if="item"
51
+ :disciplines="item.disciplines"
52
+ justify="center"
53
+ class="mt-4"
54
+ />
48
55
  <MiscMoleculesChipContainer
49
56
  v-if="item && item.tags"
50
57
  :items="item.tags"
@@ -120,13 +127,14 @@ import { useNuxtApp } from "#imports"
120
127
  const { $stores } = useNuxtApp()
121
128
  const { name } = useDisplay()
122
129
  const props = defineProps({
130
+ // null while the resource item is loading (see useI18nResourceItem)
123
131
  item: {
124
132
  type: Object,
125
- required: true,
133
+ default: null,
126
134
  },
127
135
  loading: {
128
136
  type: Boolean,
129
- required: false,
137
+ default: false,
130
138
  },
131
139
  })
132
140
  </script>
@@ -40,6 +40,12 @@
40
40
  <div class="overline my-2">
41
41
  {{ formatDateValue(item.date, locale) }}
42
42
  </div>
43
+ <!-- DISCIPLINES -->
44
+ <MiscMoleculesDisciplinesTags
45
+ :disciplines="item.disciplines"
46
+ justify="center"
47
+ class="mt-4"
48
+ />
43
49
  <MiscMoleculesChipContainer
44
50
  v-if="item.tags && item.tags.length"
45
51
  :items="item.tags"
@@ -119,13 +125,13 @@ const { $stores } = useNuxtApp()
119
125
  const { name } = useDisplay()
120
126
  const { locale } = useI18n()
121
127
  const props = defineProps({
128
+ // null while the resource item is loading (see useI18nResourceItem)
122
129
  item: {
123
130
  type: Object,
124
- required: true,
131
+ default: null,
125
132
  },
126
133
  loading: {
127
134
  type: Boolean,
128
- required: false,
129
135
  default: false,
130
136
  },
131
137
  })
@@ -234,7 +234,7 @@
234
234
  "type": {
235
235
  "label": "Type",
236
236
  "INITIATIVE": "Initiative",
237
- "PROJECTS": "Project",
237
+ "PROJECT": "Project",
238
238
  "TOOL": "Tool"
239
239
  }
240
240
  },
@@ -224,7 +224,7 @@
224
224
  "type": {
225
225
  "label": "Type",
226
226
  "INITIATIVE": "Initiative",
227
- "PROJECTS": "Projet",
227
+ "PROJECT": "Projet",
228
228
  "TOOL": "Outil"
229
229
  }
230
230
  },
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "license": "AGPL-3.0-only",
3
3
  "main": "./dist/module.mjs",
4
- "version": "1.2.2",
4
+ "version": "1.3.0",
5
5
  "name": "@paris-ias/list",
6
6
  "repository": {
7
7
  "url": "git+https://github.com/IEA-Paris/list.git",