Compare commits

..

236 Commits

Author SHA1 Message Date
Paul Valerie GOMA
299bae8d3d 1.0.27 -> 1.0.28 2025-08-11 19:35:46 +02:00
Paul Valerie GOMA
ef93944618 1.0.27 -> 1.0.28 2025-08-11 19:35:28 +02:00
Paul Valerie GOMA
cff17612de 📝 docs: Updated CHANGELOG file 2025-08-11 19:32:15 +02:00
Paul Valerie GOMA
6af37991a4 🎉 add README 2025-08-11 19:31:34 +02:00
Paul Valerie GOMA
6a0e9e566b 💄 Updating the style of the primevue datatable component 2025-08-11 19:28:00 +02:00
Paul Valerie GOMA
b06accb14f feature: Data table component updated 2025-08-11 19:27:21 +02:00
Paul Valerie GOMA
9e3067627c feature: Menu bar component updated 2025-08-11 18:07:18 +02:00
Paul Valerie GOMA
fd143ea7a5 feature: Select component improved 2025-08-11 17:32:09 +02:00
Paul Valerie GOMA
5f5c532d8e 🐛 fix: bugs fixed 2025-08-11 16:20:56 +02:00
Paul Valerie GOMA
e20626296e 1.0.26 -> 1.0.27 2025-08-07 15:30:13 +02:00
Paul Valerie GOMA
96fa281b11 1.0.26 -> 1.0.27 2025-08-07 15:29:55 +02:00
Paul Valerie GOMA
8d4bb9b762 📝 docs: README file updated 2025-08-07 15:25:30 +02:00
Paul Valerie GOMA
dcf54bf4b9 💄 Updating the style of the primevue fileupload component 2025-08-07 15:24:25 +02:00
Paul Valerie GOMA
6caad49ff8 💄 Updating the style of the primevue datatable component 2025-08-07 15:23:43 +02:00
Paul Valerie GOMA
24b1018f7a 1.0.25 -> 1.0.26 2025-08-05 15:30:26 +02:00
Paul Valerie GOMA
501e9cb3b8 1.0.25 -> 1.0.26 2025-08-05 15:30:06 +02:00
Paul Valerie GOMA
8071f3565c 🐛 fix: bug fixed 2025-08-05 15:21:02 +02:00
Paul Valerie GOMA
fff3f5e647 feature: Select component improved 2025-08-05 15:20:11 +02:00
Paul Valerie GOMA
1ca5f60449 📝 docs: README file updated 2025-08-05 15:13:11 +02:00
Paul Valerie GOMA
07aed8835e 📝 docs: Updated CHANGELOG file 2025-08-05 15:12:44 +02:00
Paul Valerie GOMA
3c3956da68 🔥 deleted files 2025-08-05 15:11:51 +02:00
Paul Valerie GOMA
65a2f9028a 💄 Updating the style of the primevue datatable component 2025-08-05 15:09:22 +02:00
Paul Valerie GOMA
2bd148274f 1.0.24 -> 1.0.25 2025-08-05 00:39:53 +02:00
Paul Valerie GOMA
ae4955f00c 📝 docs: Updated CHANGELOG file 2025-08-05 00:30:54 +02:00
Paul Valerie GOMA
8bfd244be2 📝 docs: README file updated 2025-08-05 00:29:59 +02:00
Paul Valerie GOMA
921667d7e9 1.0.24 -> 1.0.25 2025-08-05 00:29:27 +02:00
Paul Valerie GOMA
ce70f81113 1.0.24 -> 1.0.25 2025-08-05 00:29:12 +02:00
Paul Valerie GOMA
504118e92e Display datatable component 2025-08-05 00:27:37 +02:00
Paul Valerie GOMA
073ddc9d7c feature: Data table component updated 2025-08-05 00:26:53 +02:00
Paul Valerie GOMA
994237e98e feature: Select component improved 2025-08-05 00:26:21 +02:00
Paul Valerie GOMA
625189077b ♻️ refactor: component updated 2025-08-05 00:25:41 +02:00
Paul Valerie GOMA
dc288d42bb ♻️ refactor: useConfirmModal composable updated 2025-08-03 04:28:31 +02:00
Paul Valerie GOMA
ef57a78a03 ♻️ refactor: useAlert composable updated 2025-08-03 04:28:04 +02:00
Paul Valerie GOMA
38dfcfe794 Display confirm modal component 2025-08-03 04:27:34 +02:00
Paul Valerie GOMA
f8dfcfee1f 1.0.23 -> 1.0.24 2025-08-03 04:26:19 +02:00
Paul Valerie GOMA
34a00841b1 1.0.23 -> 1.0.24 2025-08-03 04:25:49 +02:00
Paul Valerie GOMA
8f77be03dc 🎉 add README 2025-08-03 04:24:51 +02:00
Paul Valerie GOMA
7749fdc8e2 📝 docs: Updated CHANGELOG file 2025-08-03 04:24:35 +02:00
Paul Valerie GOMA
1fb81ec69f 🔧 test environment setup updated 2025-08-02 23:24:38 +02:00
Paul Valerie GOMA
738f5ae2ad 1.0.22 -> 1.0.23 2025-08-02 23:24:03 +02:00
Paul Valerie GOMA
a5ef570311 1.0.22 -> 1.0.23 2025-08-02 23:23:48 +02:00
Paul Valerie GOMA
1964852b1c Display menu bar component 2025-08-02 22:52:56 +02:00
Paul Valerie GOMA
3c833bacb1 1.0.21 -> 1.0.22 2025-08-02 22:52:34 +02:00
Paul Valerie GOMA
e960c0d56d 1.0.22 -> 1.0.22 2025-08-02 22:52:06 +02:00
Paul Valerie GOMA
b78de670e9 📝 docs: README file updated 2025-08-02 22:50:43 +02:00
Paul Valerie GOMA
edeb8b105d 📝 docs: Updated CHANGELOG file 2025-08-02 22:50:20 +02:00
Paul Valerie GOMA
cf208cd03b 1.0.20 -> 1.0.21 2025-07-31 13:35:07 +02:00
Paul Valerie GOMA
fee74298c0 1.0.20 -> 1.0.21 2025-07-31 13:34:53 +02:00
Paul Valerie GOMA
45c3945627 🏷️ Menu bar component interface file updated 2025-07-31 13:28:52 +02:00
Paul Valerie GOMA
efc442316c feature: Menu bar component updated 2025-07-31 13:28:05 +02:00
Paul Valerie GOMA
6bb72c9147 📝 docs: README file updated 2025-07-31 11:14:38 +02:00
Paul Valerie GOMA
d8b80c506d 📝 docs: Updated CHANGELOG file 2025-07-31 11:14:12 +02:00
Paul Valerie GOMA
b490b2a720 1.0.19 -> 1.0.20 2025-07-31 11:13:09 +02:00
Paul Valerie GOMA
6b8d621217 1.0.19 -> 1.0.20 2025-07-31 11:12:53 +02:00
Paul Valerie GOMA
95a1699f2f ♻️ refactor: useAlert composable updated 2025-07-31 11:11:26 +02:00
Paul Valerie GOMA
2dda891c43 ♻️ refactor: useConfirmModal composable updated 2025-07-31 11:10:18 +02:00
Paul Valerie GOMA
5f8a7ae4c2 test: added test file 2025-07-31 11:09:47 +02:00
Paul Valerie GOMA
184417f484 test: added test file 2025-07-31 11:09:12 +02:00
Paul Valerie GOMA
e4915e618b 📝 docs: README file updated 2025-07-31 03:28:58 +02:00
Paul Valerie GOMA
7bae04e7c0 📝 docs: Updated CHANGELOG file 2025-07-31 03:28:29 +02:00
Paul Valerie GOMA
96f8465d8c 1.0.18 -> 1.0.19 2025-07-31 03:27:25 +02:00
Paul Valerie GOMA
649c59ecb4 1.0.18 -> 1.0.19 2025-07-31 03:27:11 +02:00
Paul Valerie GOMA
c0d36a8c33 💄 Style component added 2025-07-31 03:24:38 +02:00
Paul Valerie GOMA
2c4651800d 📝 docs: README file updated 2025-07-31 02:30:18 +02:00
Paul Valerie GOMA
7d5fa0100c 📝 docs: Updated CHANGELOG file 2025-07-31 02:29:56 +02:00
Paul Valerie GOMA
52d7f57276 1.0.17 -> 1.0.18 2025-07-31 02:29:27 +02:00
Paul Valerie GOMA
26e6288529 1.0.17 -> 1.0.18 2025-07-31 02:29:11 +02:00
Paul Valerie GOMA
eed44a1214 📝 docs: Updated CHANGELOG file 2025-07-31 02:01:24 +02:00
Paul Valerie GOMA
dbf30cc066 🎉 add README 2025-07-31 02:00:31 +02:00
Paul Valerie GOMA
b02ce1bd7e 1.0.16 -> 1.0.17 2025-07-31 02:00:04 +02:00
Paul Valerie GOMA
853b6cc4b6 1.0.16 -> 1.0.17 2025-07-31 01:59:49 +02:00
Paul Valerie GOMA
3ab504e262 file updated 2025-07-31 01:54:58 +02:00
Paul Valerie GOMA
f05f4f0ee5 🏷️ Components export file configuration updated 2025-07-31 01:54:02 +02:00
Paul Valerie GOMA
4d3febf19b configuration file added 2025-07-31 01:53:36 +02:00
Paul Valerie GOMA
d36eec1d5e configuration file added 2025-07-31 01:52:44 +02:00
Paul Valerie GOMA
8cfb13d1b3 file added 2025-07-31 01:51:38 +02:00
Paul Valerie GOMA
18fa9df5bf 🔧 test environment setup updated 2025-07-31 01:50:55 +02:00
Paul Valerie GOMA
521206a589 file updated 2025-07-31 01:50:36 +02:00
Paul Valerie GOMA
b61eb6fe4a 🏷️ type configuration file added 2025-07-31 01:49:18 +02:00
Paul Valerie GOMA
19c8de7350 🐛 fix: bugs fixed 2025-07-31 01:45:51 +02:00
Paul Valerie GOMA
c21d7e5c78 🐛 fix: bugs fixed 2025-07-30 19:27:59 +02:00
Paul Valerie GOMA
2ab992a321 Display menu bar component 2025-07-30 19:22:30 +02:00
Paul Valerie GOMA
534be360a1 🐛 fix: fixed bugs 2025-07-30 19:22:06 +02:00
Paul Valerie GOMA
de3ce627ba 🐛 fix: bugs fixed 2025-07-30 19:14:53 +02:00
Paul Valerie GOMA
779cc5146a 🎉 add README 2025-07-30 18:52:04 +02:00
Paul Valerie GOMA
34cdb4c12e 📝 docs: Updated CHANGELOG file 2025-07-30 18:51:37 +02:00
Paul Valerie GOMA
e5a2843c9f 🔧 test environment setup updated 2025-07-30 18:50:32 +02:00
Paul Valerie GOMA
ef777d539f 1.0.15 -> 1.0.16 2025-07-30 18:48:57 +02:00
Paul Valerie GOMA
bca12f60b8 1.0.15 -> 1.0.16 2025-07-30 18:48:42 +02:00
Paul Valerie GOMA
a84d47e0fc Merge branch 'menu' into 'main'
Menu

See merge request cellule-financiere-pmo/design-system/visua-vue!14
2025-07-30 15:16:07 +00:00
Paul Valerie GOMA
8eaa8db835 Corrige la casse du nom de fichier pour la compatibilité Linux 2025-07-30 17:11:03 +02:00
Paul Valerie GOMA
0f2d287cf7 🔧 test environment updated 2025-07-30 16:39:02 +02:00
Paul Valerie GOMA
6c62dfe9b5 1.0.14 -> 1.0.15 2025-07-30 16:38:19 +02:00
Paul Valerie GOMA
60384e5957 1.0.14 -> 1.0.15 2025-07-30 16:29:18 +02:00
Paul Valerie GOMA
d731e8213e 1.0.14 -> 1.0.15 2025-07-30 16:29:00 +02:00
Paul Valerie GOMA
ba7e8d4639 feat: added @visua as allias for visua style file paths 2025-07-30 16:19:19 +02:00
Paul Valerie GOMA
2041974712 📝 docs: README file updated 2025-07-30 16:18:35 +02:00
Paul Valerie GOMA
359c2cba12 📝 docs: Updated CHANGELOG file 2025-07-30 16:18:16 +02:00
Paul Valerie GOMA
b06172f72f 🐛 fix: fixed bugs 2025-07-30 16:15:36 +02:00
Paul Valerie GOMA
76d85039e3 Display menu bar component 2025-07-30 16:15:13 +02:00
Paul Valerie GOMA
42c2d8d70d feature: Menu bar component updated 2025-07-30 16:14:39 +02:00
Paul Valerie GOMA
430876cfbf 💄 Update style dependency files 2025-07-30 16:12:11 +02:00
Paul Valerie GOMA
7014804a25 💄 Updating the style of the primevue navigation component 2025-07-30 16:10:19 +02:00
Paul Valerie GOMA
feb54ca613 💄 Updating the style of the primevue menubar component 2025-07-30 16:05:15 +02:00
Paul Valerie GOMA
c57cf6f96b 💄 update common styles to components 2025-07-30 16:04:34 +02:00
Paul Valerie GOMA
674475984e 💄 update common styles to components 2025-07-30 11:27:39 +02:00
Paul Valerie GOMA
b335bae79c 💄 Update the primevue configuration file 2025-07-30 11:27:08 +02:00
Paul Valerie GOMA
dc01f4694d 💄 Adding the style of the primevue menubar component 2025-07-30 11:26:47 +02:00
Paul Valerie GOMA
4a86b1cd57 💄 Adding the style of the primevue navigation component 2025-07-30 11:26:02 +02:00
Paul Valerie GOMA
564f036030 🏷️ Components export file configuration updated 2025-07-30 10:52:28 +02:00
Paul Valerie GOMA
b2a353f109 🏷️ Types export file configuration updated 2025-07-30 10:50:34 +02:00
Paul Valerie GOMA
b998522b05 🏷️ Menu bar component interface file updated 2025-07-30 10:48:33 +02:00
Paul Valerie GOMA
93cd17aef5 feature: Menu bar component added 2025-07-30 10:44:52 +02:00
Paul Valerie GOMA
e46ba8127d 🏷️ Menu bar component interface file added 2025-07-30 10:43:18 +02:00
Paul Valerie GOMA
0ebc29b3df 💚 fix: package publication error fixed 2025-07-30 02:14:21 +02:00
Paul Valerie GOMA
d99331c9ee 📝 docs: Updated CHANGELOG file 2025-07-30 02:07:42 +02:00
Paul Valerie GOMA
8a99e698f4 🔧 test environment setup 2025-07-30 02:04:49 +02:00
Paul Valerie GOMA
80e2c6e358 🔧 test environment setup updated 2025-07-30 02:03:51 +02:00
Paul Valerie GOMA
717c5f8e6e 🐛 fix: bugs fixed 2025-07-30 01:48:45 +02:00
Paul Valerie GOMA
072aa34050 1.0.13 -> 1.0.14 2025-07-30 01:46:28 +02:00
Paul Valerie GOMA
8f9c0716b1 1.0.13 -> 1.0.14 2025-07-30 01:46:14 +02:00
Paul Valerie GOMA
1c4b59b246 🏷️ Composables export file configuration 2025-07-30 01:43:40 +02:00
Paul Valerie GOMA
a066efea10 🏷️ Types export file configuration 2025-07-30 01:42:17 +02:00
Paul Valerie GOMA
1d5a3839c7 🏷️ Components export file configuration 2025-07-30 01:40:58 +02:00
Paul Valerie GOMA
72bcd63466 💚 fix: package publication error fixed 2025-07-29 11:37:02 +02:00
Paul Valerie GOMA
e01dd93629 💚 fix: package publication error fixed 2025-07-29 11:23:08 +02:00
Paul Valerie GOMA
d202205e5e 1.0.13 -> 1.0.14 2025-07-29 11:01:03 +02:00
Paul Valerie GOMA
385233ea8b 1.0.13 -> 1.0.14 2025-07-29 11:00:46 +02:00
Paul Valerie GOMA
e2db26d340 📝 docs: README file updated 2025-07-29 10:59:52 +02:00
Paul Valerie GOMA
1530b66396 📝 docs: Updated CHANGELOG file 2025-07-29 10:58:42 +02:00
Paul Valerie GOMA
92d3564654 💚 fix: package publication error fixed 2025-07-29 10:55:45 +02:00
Paul Valerie GOMA
bfe699150e Merge branch 'datatable' into 'main'
Datatable

See merge request cellule-financiere-pmo/design-system/visua-vue!13
2025-07-29 04:26:18 +00:00
Paul Valerie GOMA
c3073c1219 1.0.12 -> 1.0.13 2025-07-29 06:23:52 +02:00
Paul Valerie GOMA
4c76522d52 1.0.12 -> 1.0.13 2025-07-29 06:23:36 +02:00
Paul Valerie GOMA
3c64a187f7 📝 docs: README file updated 2025-07-29 06:22:17 +02:00
Paul Valerie GOMA
254119edc9 📝 docs: Updated CHANGELOG file 2025-07-29 06:21:43 +02:00
Paul Valerie GOMA
76d75d6756 Display datatable component 2025-07-29 06:17:27 +02:00
Paul Valerie GOMA
a3a8fa05b9 feature: Data table component updated 2025-07-29 06:16:44 +02:00
Paul Valerie GOMA
7ee1b3fefd feature: Select component improved 2025-07-29 06:15:56 +02:00
Paul Valerie GOMA
c46f2889cd 💄 Adding the style of the primevue list component 2025-07-29 06:14:55 +02:00
Paul Valerie GOMA
46d9d12781 💄 Updating the style of the primevue datatable component 2025-07-29 06:14:03 +02:00
Paul Valerie GOMA
b0b6a35906 💄 Updating the style of the primevue fileupload component 2025-07-29 06:13:13 +02:00
Paul Valerie GOMA
4e42d0256c 💄 Updating the style of the primevue toast component 2025-07-29 06:12:20 +02:00
Paul Valerie GOMA
2a03550d09 💄 Updating the style of various component 2025-07-29 06:11:34 +02:00
Paul Valerie GOMA
c73d2e0892 💄 Updating the style of the primevue overlay component 2025-07-29 06:10:50 +02:00
Paul Valerie GOMA
43472332dd 💄 update common styles to components 2025-07-29 06:09:58 +02:00
Paul Valerie GOMA
c035a7dce5 💄 Update the primevue configuration file 2025-07-29 06:09:12 +02:00
Paul Valerie GOMA
80825394b8 🔥 deleted file 2025-07-28 18:03:27 +02:00
Paul Valerie GOMA
ae0cd7a5b0 🔥 deleted file 2025-07-28 18:03:09 +02:00
Paul Valerie GOMA
fad2a30a6b 🔥 deleted file 2025-07-28 18:02:55 +02:00
Paul Valerie GOMA
3d6b27c13c feature: Data table component added 2025-07-28 17:03:11 +02:00
Paul Valerie GOMA
a4bbfeb9ad feature: Column row component updated 2025-07-28 16:38:17 +02:00
Paul Valerie GOMA
5612d5d6f4 feature: Column row component added 2025-07-28 16:34:39 +02:00
Paul Valerie GOMA
0c8540511e feature: Column group component added 2025-07-28 16:32:42 +02:00
Paul Valerie GOMA
43ea5c5fdd feature: Column component added 2025-07-28 16:22:49 +02:00
Paul Valerie GOMA
94611cef94 💄 Update the primevue configuration file 2025-07-28 11:48:47 +02:00
Paul Valerie GOMA
825b0aaeab 💄 Adding* the style of the primevue paginator component 2025-07-28 11:48:04 +02:00
Paul Valerie GOMA
b2045352ac 💄 Adding* the style of the primevue datatable component 2025-07-28 11:46:32 +02:00
Paul Valerie GOMA
88f6ee8fef Merge branch 'modal' into 'main'
Modal

See merge request cellule-financiere-pmo/design-system/visua-vue!12
2025-07-28 09:36:36 +00:00
Paul Valerie GOMA
07371b4eea 1.0.11 -> 1.0.12 2025-07-28 11:33:47 +02:00
Paul Valerie GOMA
d1eb29caa7 1.0.11 -> 1.0.12 2025-07-28 11:33:27 +02:00
Paul Valerie GOMA
bb43b55576 📝 docs: README file updated 2025-07-28 11:32:06 +02:00
Paul Valerie GOMA
0becd4a459 feature: Confirm modal component updated 2025-07-28 11:29:25 +02:00
Paul Valerie GOMA
0245e4df4a 📝 docs: Updated CHANGELOG file 2025-07-28 11:25:32 +02:00
Paul Valerie GOMA
0732261793 ♻️ refactor: useConfirmModal composable updated 2025-07-28 11:22:30 +02:00
Paul Valerie GOMA
d332fab643 Display some confirm modal components 2025-07-28 11:21:01 +02:00
Paul Valerie GOMA
28abd098cf 🐛 fix: fixed bugs 2025-07-28 11:20:09 +02:00
Paul Valerie GOMA
d1138efb5a feature: Confirm modal component added 2025-07-28 11:19:38 +02:00
Paul Valerie GOMA
88fd6ec0d2 feature: useConfirmModal composable added 2025-07-28 09:11:29 +02:00
Paul Valerie GOMA
77f4e5460a 💄 Adding* the style of the primevue confirm dialog component 2025-07-28 08:26:35 +02:00
Paul Valerie GOMA
58450e70ee 💄 Update the primevue configuration file 2025-07-28 08:25:35 +02:00
Paul Valerie GOMA
22e473b87c test: added test file 2025-07-28 01:22:49 +02:00
Paul Valerie GOMA
0fd7e1ed1f 🏷️ Modal component interface fileupdated 2025-07-28 01:01:17 +02:00
Paul Valerie GOMA
24ea6cb51b ♻️ refactor: modal component improved 2025-07-28 00:58:31 +02:00
Paul Valerie GOMA
80dc6e4161 Display some modal components 2025-07-28 00:12:07 +02:00
Paul Valerie GOMA
f1f5e1f36a 🐛 fix: bug fixed 2025-07-28 00:11:26 +02:00
Paul Valerie GOMA
dd5bc2fdb4 feature: Modal component added 2025-07-27 23:55:47 +02:00
Paul Valerie GOMA
99d31824c0 🏷️ Modal component interface file added 2025-07-27 23:46:24 +02:00
Paul Valerie GOMA
255e7f5021 💄 Update the primevue configuration file 2025-07-27 23:37:34 +02:00
Paul Valerie GOMA
cbfe7726ca 💄 Adding* the style of the primevue dialog component 2025-07-27 23:36:32 +02:00
Paul Valerie GOMA
06e61bd809 Merge branch 'alert' into 'main'
Alert

See merge request cellule-financiere-pmo/design-system/visua-vue!11
2025-07-27 21:28:17 +00:00
Paul Valerie GOMA
dcc7caa217 📝 docs: README file updated 2025-07-27 23:25:44 +02:00
Paul Valerie GOMA
2dda1e7ff5 1.0.10 -> 1.0.11 2025-07-27 23:25:16 +02:00
Paul Valerie GOMA
0195750392 1.0.10 -> 1.0.11 2025-07-27 23:24:47 +02:00
Paul Valerie GOMA
f0a5774b00 📝 docs: Updated CHANGELOG file 2025-07-27 23:23:42 +02:00
Paul Valerie GOMA
81e875dc48 feature: Alert component added 2025-07-27 23:21:06 +02:00
Paul Valerie GOMA
2dd55342d9 🐛 fix: fixed bugs 2025-07-27 23:18:42 +02:00
Paul Valerie GOMA
34e83970d9 Display some alert components 2025-07-27 23:17:56 +02:00
Paul Valerie GOMA
b3fe8a2f00 test: added test file 2025-07-27 23:17:04 +02:00
Paul Valerie GOMA
d38cdce28c 💄 Updating the style of the primevue toast component 2025-07-27 18:33:14 +02:00
Paul Valerie GOMA
0f9d219eb6 feature: useAlert composable added 2025-07-27 09:46:45 +02:00
Paul Valerie GOMA
355802ed6b 🏷️ Alert component interface file added 2025-07-27 04:33:33 +02:00
Paul Valerie GOMA
880e3d0f15 💄 Adding the style of the primevue toast component 2025-07-27 04:24:24 +02:00
Paul Valerie GOMA
7627870c07 💄 Update the primevue configuration file 2025-07-27 04:22:24 +02:00
Paul Valerie GOMA
2c91cfb38c Merge branch 'file' into 'main'
File

See merge request cellule-financiere-pmo/design-system/visua-vue!10
2025-07-27 02:11:25 +00:00
Paul Valerie GOMA
43411d7e7a 1.0.9 -> 1.0.10 2025-07-27 04:06:07 +02:00
Paul Valerie GOMA
1b2b343e2d 1.0.9 -> 1.0.10 2025-07-27 04:05:01 +02:00
Paul Valerie GOMA
d0e8163764 📝 docs: README file updated 2025-07-27 04:03:36 +02:00
Paul Valerie GOMA
4b6ef133fc 📝 docs: Updated CHANGELOG file 2025-07-27 04:02:41 +02:00
Paul Valerie GOMA
2938e3bd2b test: added test file 2025-07-27 03:58:56 +02:00
Paul Valerie GOMA
80a0cb656a 🐛 fix: bugs fixed 2025-07-27 03:58:07 +02:00
Paul Valerie GOMA
b807657583 🐛 fix: bugs fixed 2025-07-27 02:43:09 +02:00
Paul Valerie GOMA
605f6af3b9 feature: label error proxy added 2025-07-27 00:30:34 +02:00
Paul Valerie GOMA
f15fdb8e18 🐛 fix: bugs fixed 2025-07-27 00:27:20 +02:00
Paul Valerie GOMA
fbfa6e33fc 🐛 fix: bugs fixed 2025-07-26 15:07:48 +02:00
Paul Valerie GOMA
0ca59dbc59 🐛 fix: File upload component updated 2025-07-26 14:20:47 +02:00
Paul Valerie GOMA
6f1a6b930c ♻️ refactor: Scroll panel component updated 2025-07-26 14:19:48 +02:00
Paul Valerie GOMA
2c6c693ceb feature: Scroll panel component added 2025-07-26 12:54:39 +02:00
Paul Valerie GOMA
e9247149fe 💄 Update the primevue configuration file 2025-07-26 12:50:08 +02:00
Paul Valerie GOMA
bcc2e33d0d 💄 Adding the style of the primevue scrollpanel component 2025-07-26 12:49:43 +02:00
Paul Valerie GOMA
4d343e7f87 🐛 fix: File component updated 2025-07-25 18:31:48 +02:00
Paul Valerie GOMA
fc64beda13 🐛 fix: File upload component updated 2025-07-25 18:30:52 +02:00
Paul Valerie GOMA
0d925df378 🏷️ FileUpload component interface file updated 2025-07-25 18:29:22 +02:00
Paul Valerie GOMA
d72f0b67d2 🏷️ FileUpload component interface file updated 2025-07-25 11:19:21 +02:00
Paul Valerie GOMA
ea2a8a9bd1 Display some file upload components 2025-07-24 19:22:03 +02:00
Paul Valerie GOMA
1acd65a704 File upload component added 2025-07-24 19:21:25 +02:00
Paul Valerie GOMA
16f0617df2 File component added 2025-07-24 19:20:37 +02:00
Paul Valerie GOMA
abae860b58 🏷️ FileUpload component interface file updated 2025-07-24 03:48:59 +02:00
Paul Valerie GOMA
bf13fec2eb 💄 Adding the style of the primevue fileupload component 2025-07-24 03:35:53 +02:00
Paul Valerie GOMA
4d07c97c7c 💄 Update the primevue configuration file 2025-07-24 03:33:30 +02:00
Paul Valerie GOMA
db5d9b0dae 🏷️ FileUpload component interface file added 2025-07-24 03:32:49 +02:00
Paul Valerie GOMA
9da0952de8 Merge branch 'message' into 'main'
Message

See merge request cellule-financiere-pmo/design-system/visua-vue!9
2025-07-24 00:57:35 +00:00
Paul Valerie GOMA
94c7dd092f 📝 docs: Updated CHANGELOG file 2025-07-24 02:51:27 +02:00
Paul Valerie GOMA
938a8a3457 📝 docs: README file updated 2025-07-24 02:49:01 +02:00
Paul Valerie GOMA
b34c86d8e2 1.0.8 -> 1.0.9 2025-07-24 02:47:54 +02:00
Paul Valerie GOMA
b034c1a587 1.0.8 -> 1.0.9 2025-07-24 02:47:32 +02:00
Paul Valerie GOMA
83f9097970 test: added test file 2025-07-24 02:45:57 +02:00
Paul Valerie GOMA
e05b10a34b 🏷️ Message component interface file updated 2025-07-24 02:44:51 +02:00
Paul Valerie GOMA
4b50cef7fb ♻️ refactor: Message component Updated 2025-07-24 02:43:39 +02:00
Paul Valerie GOMA
a4c47617d8 Display some message components 2025-07-24 02:34:32 +02:00
Paul Valerie GOMA
55c47947e4 Message component added 2025-07-24 02:33:52 +02:00
Paul Valerie GOMA
574d2da025 🐛 fix: bug fixed 2025-07-24 02:32:35 +02:00
Paul Valerie GOMA
8a43684027 🏷️ Message component interface file updated 2025-07-24 00:41:30 +02:00
Paul Valerie GOMA
1aa7a142c6 🏷️ Message component interface file updated 2025-07-24 00:15:57 +02:00
Paul Valerie GOMA
54eb389c57 🏷️ Message component interface file added 2025-07-24 00:14:16 +02:00
Paul Valerie GOMA
b8458d6c2f Merge branch 'progress' into 'main'
Progress

See merge request cellule-financiere-pmo/design-system/visua-vue!8
2025-07-23 17:21:48 +00:00
66 changed files with 4106 additions and 641 deletions

View File

@ -5,6 +5,69 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.0.28] - 2025-08-11
### fixed
- bug fixed
## [1.0.24] - 2025-08-02
### fixed
- Composable error fixed
## [1.0.22] - 2025-08-02
### fixed
- Fixed bugs related to dependencies
## [1.0.20] - 2025-07-31
### fixed
- Composable error fixed
## [1.0.19] - 2025-07-31
### Added
- Style component added
## [1.0.18] - 2025-07-31
### fixed
- Package publication error fixed
- Component export file configuration
## [1.0.15] - 2025-07-30
### Added
- Menu bar compoenent
## [1.0.14] - 2025-07-29
### fixed
- Package publication error fixed
- Component export file configuration
## [1.0.13] - 2025-07-29
### Added
- Data table compoenent
- List style
## [1.0.12] - 2025-07-28
### Added
- Modal compoenent
- Confirm modal component
- useConfirmModal composable
## [1.0.11] - 2025-07-27
### Added
- Alert compoenent
- useAlert composable
## [1.0.10] - 2025-07-27
### Added
- File compoenent
- File upload component
- Scroll panel component
## [1.0.9] - 2025-07-24
### Added
- Message component
### Changed
- Badge compoent icon
## [1.0.8] - 2025-07-23 ## [1.0.8] - 2025-07-23
### Added ### Added
- ProgressBar component - ProgressBar component

View File

@ -1,3 +1,3 @@
# visua-vue # visua-vue
**Current version: v1.0.8** **Current version: v1.0.28**

8
env.d.ts vendored
View File

@ -1 +1,9 @@
/// <reference types="vite/client" /> /// <reference types="vite/client" />
declare module '*.vue' {
import { DefineComponent } from 'vue';
const component: DefineComponent<Record<string, unknown>, Record<string, unknown>, unknown>;
export default component;
}

1342
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,21 +1,41 @@
{ {
"name": "@cellule-financiere-pmo/visua-vue", "name": "@cellule-financiere-pmo/visua-vue",
"version": "1.0.8", "version": "1.0.28",
"type": "module", "type": "module",
"description": "Vue.js components of the Visua Design System.",
"main": "./dist/visua-vue.umd.cjs",
"module": "./dist/visua-vue.es.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"require": "./dist/visua-vue.umd.cjs",
"import": "./dist/visua-vue.es.js"
}
},
"sideEffects": false,
"files": [
"dist",
"README.md"
],
"scripts": { "scripts": {
"clean": "rimraf dist",
"build": "npm-run-all clean build:assets build:types",
"build:assets": "vite build",
"build:types": "vue-tsc --emitDeclarationOnly --outDir dist && tsc --project tsconfig.types.json",
"prepare": "npm run build",
"dev": "vite", "dev": "vite",
"build": "run-p type-check \"build-only {@}\" --",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --build", "type-check": "vue-tsc --build",
"lint": "eslint . --fix", "lint": "eslint . --fix",
"test:unit": "vitest run --coverage" "test": "vitest --config vitest.config.ts",
"test:unit": "vitest run --coverage",
"preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@cellule-financiere-pmo/visua": "1.1.3", "@cellule-financiere-pmo/visua": "1.1.3"
"jsdom": "^26.1.0", },
"peerDependencies": {
"primevue": "^4.3.6", "primevue": "^4.3.6",
"vite-plugin-inspect": "^11.3.0",
"vue": "^3.5.17", "vue": "^3.5.17",
"vue-router": "^4.5.1" "vue-router": "^4.5.1"
}, },
@ -30,28 +50,37 @@
"eslint": "^9.29.0", "eslint": "^9.29.0",
"eslint-plugin-vue": "~10.2.0", "eslint-plugin-vue": "~10.2.0",
"jiti": "^2.4.2", "jiti": "^2.4.2",
"jsdom": "^26.1.0",
"npm-run-all2": "^8.0.4", "npm-run-all2": "^8.0.4",
"rimraf": "^5.0.10",
"typescript": "~5.8.0", "typescript": "~5.8.0",
"vite": "^7.0.0", "vite": "^7.0.0",
"vite-plugin-inspect": "^11.3.0",
"vite-plugin-vue-devtools": "^7.7.7", "vite-plugin-vue-devtools": "^7.7.7",
"vitest": "^3.2.4", "vitest": "^3.2.4",
"vue": "^3.5.17",
"vue-router": "^4.5.1",
"vue-tsc": "^2.2.10" "vue-tsc": "^2.2.10"
}, },
"description": "**Current version: v0.0.0**",
"main": "index.js",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://gitlab.com/cellule-financiere-pmo/design-system/visua-vue.git" "url": "https://gitlab.com/cellule-financiere-pmo/design-system/visua-vue.git"
}, },
"keywords": [],
"author": "",
"license": "ISC",
"bugs": { "bugs": {
"url": "https://gitlab.com/cellule-financiere-pmo/design-system/visua-vue/issues" "url": "https://gitlab.com/cellule-financiere-pmo/design-system/visua-vue/issues"
}, },
"homepage": "https://gitlab.com/cellule-financiere-pmo/design-system/visua-vue#readme", "homepage": "https://gitlab.com/cellule-financiere-pmo/design-system/visua-vue#readme",
"files": [ "keywords": [
"src/components/*", "vue",
"src/assets/style/*" "typescript",
] "design-system",
"components",
"vite",
"library"
],
"author": {
"name": "CGI",
"url": "https://www.cgi.com"
},
"license": "ISC"
} }

View File

@ -7,18 +7,42 @@
// import VCheckboxView from '../template/VCheckboxView.vue'; // import VCheckboxView from '../template/VCheckboxView.vue';
// import VBadgeView from '../template/VBadgeView.vue'; // import VBadgeView from '../template/VBadgeView.vue';
// import VSelectView from '../template/VSelectView.vue'; // import VSelectView from '../template/VSelectView.vue';
import VProgressBarView from '../template/VProgressBarView.vue'; // import VProgressBarView from '../template/VProgressBarView.vue';
// import VMessageView from '../template/VMessageView.vue';
// import VFileUploadView from '../template/VFileUploadView.vue';
// import VAlertView from '../template/VAlertView.vue';
// import VModalView from '../template/VModalView.vue';
// import VConfirmModalView from '../template/VConfirmModalView.vue';
import VDataTableView from '../template/VDataTableView.vue';
// import VMenuBarView from '../template/VMenuBarView.vue'
// import VDataTable from './components/table/VDataTable.vue';
</script> </script>
<template> <template>
<!-- <VButtonView/> --> <!-- <VButtonView/> -->
<!-- <VButtonGroupView/> --> <!-- <VButtonGroupView/> -->
<!-- <VLinkView/> --> <!-- <VLinkView/> -->
<!-- <VAccordionView/> --> <!-- <VAccordionView/> -->
<!-- <VInputView/> --> <!-- <VInputView/> -->
<!-- <VCheckboxView/> --> <!-- <VCheckboxView/> -->
<!-- <VBadgeView/> --> <!-- <VBadgeView/>-->
<!-- <VSelectView/> --> <!-- <VSelectView/> -->
<VProgressBarView/> <!-- <VProgressBarView/> -->
<!-- <VMessageView/> -->
<!-- <VFileUploadView/> -->
<!-- <VAlertView/> -->
<!-- <VModalView/> -->
<!-- <VConfirmModalView/> -->
<VDataTableView/>
<!-- <RouterView/> -->
<!-- <VMenuBarView/> -->
<!-- <VDataTable/> -->
</template> </template>
<style lang="css" scoped>
*{
padding: 0px;
margin: 0px;
width: 100%;
}
</style>

View File

@ -8,4 +8,6 @@ html {
font-size: 16px; font-size: 16px;
font-family: var(--font-family-primary); font-family: var(--font-family-primary);
font-style: normal; font-style: normal;
padding: 0px;
margin: 0px;
} }

View File

@ -22,7 +22,7 @@
--input-padding-y: 0.5rem; --input-padding-y: 0.5rem;
--input-border-raduis: 0.25rem 0.25rem 0px 0px; --input-border-raduis: 0.25rem 0.25rem 0px 0px;
/* datatable */ /* datatable */
--datatable-background: var(--background-contrast-grey); --datatable-background: var(--background-default-grey);
--datatable-alt-background: var(--background-alt-grey); --datatable-alt-background: var(--background-alt-grey);
--datatable-hover-background: var(--background-alt-grey-hover); --datatable-hover-background: var(--background-alt-grey-hover);
--datatable-active-background: var(--background-alt-grey-active); --datatable-active-background: var(--background-alt-grey-active);
@ -31,7 +31,7 @@
/* menu */ /* menu */
--menu-container-border-color: var(--border-default-grey); --menu-container-border-color: var(--border-default-grey);
--menu-border-width: 0px 0px 0px var(--large-border-width); --menu-border-width: 0px 0px 0px var(--large-border-width);
--menu-default-background: var(--background-transparent); --menu-default-background: var(--background-default-grey);
--menu-default-color: var(--text-default-grey); --menu-default-color: var(--text-default-grey);
--menu-active-color: var(--text-active-blue-france); --menu-active-color: var(--text-active-blue-france);
--menu-hover-background: var(--background-transparent-active); --menu-hover-background: var(--background-transparent-active);
@ -41,4 +41,5 @@
--menu-indent: 1rem; --menu-indent: 1rem;
--menu-border-color: transparent; --menu-border-color: transparent;
--menu-border-color-active: var(--border-active-blue-france); --menu-border-color-active: var(--border-active-blue-france);
--menu-border-radius: 0px;
} }

View File

@ -13,3 +13,13 @@
@import './primevue-style/overlay.css'; @import './primevue-style/overlay.css';
@import './primevue-style/iconfield.css'; @import './primevue-style/iconfield.css';
@import './primevue-style/progressbar.css'; @import './primevue-style/progressbar.css';
@import './primevue-style/fileupload.css';
@import './primevue-style/scrollpanel.css';
@import './primevue-style/toast.css';
@import './primevue-style/dialog.css';
@import './primevue-style/confirmdialog.css';
@import './primevue-style/datatable.css';
@import './primevue-style/paginator.css';
@import './primevue-style/list.css';
@import './primevue-style/menubar.css';
@import './primevue-style/navigation.css';

View File

@ -0,0 +1,5 @@
:root {
--p-confirmdialog-content-gap: 1rem;
--p-confirmdialog-icon-size: var(--titles-H6-XXS-size);
--p-confirmdialog-icon-color: var(--text-title-grey);
}

View File

@ -0,0 +1,110 @@
:root{
--p-datatable-paginator-bottom-border-color: var(--p-datatable-border-color);
--p-datatable-paginator-bottom-border-width: 0 0 1px 0;
--p-datatable-paginator-top-border-color: var(--p-datatable-border-color);
--p-datatable-paginator-top-border-width: 0 0 1px 0;
--p-datatable-filter-inline-gap: 0.5rem;
--p-datatable-filter-constraint-focus-background: var(--p-list-option-focus-background);
--p-datatable-filter-constraint-selected-background: var(--p-list-option-selected-background);
--p-datatable-filter-constraint-selected-focus-background: var(--p-list-option-selected-focus-background);
--p-datatable-filter-constraint-color: var(--p-list-option-color);
--p-datatable-filter-constraint-focus-color: var(--p-list-option-focus-color);
--p-datatable-filter-constraint-selected-color: var(--p-list-option-selected-color);
--p-datatable-filter-constraint-selected-focus-color: var(--p-list-option-selected-focus-color);
--p-datatable-filter-constraint-padding: var(--p-list-option-padding);
--p-datatable-filter-constraint-border-radius: var(--p-list-option-border-radius);
--p-datatable-filter-constraint-separator-border-color: var(--p-content-border-color);
--p-datatable-filter-constraint-list-padding: var(--p-list-padding);
--p-datatable-filter-constraint-list-gap: var(--p-list-gap);
--p-datatable-filter-rule-border-color: var(--p-content-border-color);
--p-datatable-filter-overlay-popover-background: var(--p-overlay-popover-background);
--p-datatable-filter-overlay-popover-border-color: var(--p-overlay-popover-border-color);
--p-datatable-filter-overlay-popover-border-radius: var(--p-overlay-popover-border-radius);
--p-datatable-filter-overlay-popover-color: var(--p-overlay-popover-color);
--p-datatable-filter-overlay-popover-shadow: var(--p-overlay-popover-shadow);
--p-datatable-filter-overlay-popover-padding: var(--p-overlay-popover-padding);
--p-datatable-filter-overlay-popover-gap: 0.5rem;
--p-datatable-filter-overlay-select-background: var(--p-overlay-select-background);
--p-datatable-filter-overlay-select-border-color: var(--p-overlay-select-border-color);
--p-datatable-filter-overlay-select-border-radius: var(--p-overlay-select-border-radius);
--p-datatable-filter-overlay-select-color: var(--p-overlay-select-color);
--p-datatable-filter-overlay-select-shadow: var(--p-overlay-select-shadow);
--p-datatable-row-toggle-button-hover-background: var(--datatable-hover-background);
--p-datatable-row-toggle-button-selected-hover-background: var(--datatable-background);
--p-datatable-row-toggle-button-color: var(--datatable-row-color);
--p-datatable-row-toggle-button-hover-color: var(--datatable-row-color);
--p-datatable-row-toggle-button-selected-hover-color: var(--datatable-row-color);
--p-datatable-row-toggle-button-size: 1.75rem;
--p-datatable-row-toggle-button-border-radius: 0px;
--p-datatable-row-toggle-button-focus-ring-width: var(--focus-width);
--p-datatable-row-toggle-button-focus-ring-style: var(--focus-style);
--p-datatable-row-toggle-button-focus-ring-color: var(--focus-color);
--p-datatable-row-toggle-button-focus-ring-offset: var(--focus-offset);
/* --p-datatable-row-toggle-button-focus-ring-shadow: var(--p-focus-ring-shadow); */
--p-datatable-loading-icon-size: 2rem;
--p-datatable-sort-icon-color: var(--datatable-row-color);
--p-datatable-sort-icon-hover-color: var(--datatable-row-color);
--p-datatable-sort-icon-size: 0.875rem;
--p-datatable-resize-indicator-width: 1px;
--p-datatable-resize-indicator-color: var(--p-primary-color);
--p-datatable-column-resizer-width: 0.5rem;
--p-datatable-drop-point-color: var(--p-primary-color);
--p-datatable-footer-background: var(--datatable-background);
--p-datatable-footer-border-color: var(--p-datatable-border-color);
--p-datatable-footer-color: var(--p-content-color);
--p-datatable-footer-border-width: 0 0 1px 0;
--p-datatable-footer-padding: 0.75rem;
--p-datatable-footer-lg-padding: 1rem;
--p-datatable-footer-sm-padding: 0.5rem;
--p-datatable-column-footer-font-weight: 600;
--p-datatable-footer-cell-background: var(--datatable-background);
--p-datatable-footer-cell-border-color: var(--p-datatable-border-color);
--p-datatable-footer-cell-color: var(--p-content-color);
--p-datatable-footer-cell-padding: 0.75rem;
--p-datatable-footer-cell-lg-padding: 1rem;
--p-datatable-footer-cell-sm-padding: 0.5rem;
--p-datatable-body-cell-border-color: var(--p-datatable-border-color);
--p-datatable-body-cell-padding: 0.75rem;
--p-datatable-body-cell-lg-padding: 1rem;
--p-datatable-body-cell-sm-padding: 0.5rem;
--p-datatable-row-background: var(--datatable-background);
--p-datatable-row-hover-background: var(--datatable-hover-background);
--p-datatable-row-selected-background: var(--datatable-active-background);
--p-datatable-row-color: var(--datatable-row-color);
--p-datatable-row-hover-color: var(--datatable-row-color);
--p-datatable-row-selected-color: var(--datatable-row-color);
--p-datatable-row-focus-ring-width: var(--focus-width);
--p-datatable-row-focus-ring-style: var(--focus-style);
--p-datatable-row-focus-ring-color: var(--focus-color);
--p-datatable-row-focus-ring-offset: -1px;
/* --p-datatable-row-focus-ring-shadow: var(--p-focus-ring-shadow); */
--p-datatable-column-title-font-weight: var(--text-body-SM-detail-text-Medium-weight);
--p-datatable-header-cell-background: var(--primary-color-850-blue-france-default);
--p-datatable-header-cell-hover-background: var(--primary-color-850-blue-france-hover);
--p-datatable-header-cell-selected-background: var(--primary-color-850-blue-france-active);
--p-datatable-header-cell-border-color: var(--border-plain-grey);
--p-datatable-header-cell-color: var(--datatable-header-cell-color);
--p-datatable-header-cell-hover-color: var(--datatable-header-cell-color);
--p-datatable-header-cell-selected-color: var(--datatable-header-cell-color);
--p-datatable-header-cell-gap: 0.5rem;
--p-datatable-header-cell-padding: 0.75rem;
--p-datatable-header-cell-lg-padding: 1rem;
--p-datatable-header-cell-sm-padding: 0.5rem;
--p-datatable-header-cell-focus-ring-width: var(--focus-width);
--p-datatable-header-cell-focus-ring-style: var(--focus-style);
--p-datatable-header-cell-focus-ring-color: var(--focus-color);
--p-datatable-header-cell-focus-ring-offset: -1px;
/* --p-datatable-header-cell-focus-ring-shadow: var(--p-focus-ring-shadow); */
--p-datatable-header-background: var(--datatable-background);
--p-datatable-header-border-color: transparent;
--p-datatable-header-color: var(--datatable-header-cell-color);
--p-datatable-header-border-width: 0 0 1px 0;
--p-datatable-header-padding: 0.75rem;
--p-datatable-header-lg-padding: 1rem;
--p-datatable-header-sm-padding: 0.5rem;
/* --p-datatable-transition-duration: var(--p-transition-duration); */
--p-datatable-body-cell-selected-border-color: var(--datatable-active-background);
--p-datatable-row-striped-background: var(--datatable-background);
--p-datatable-border-color: var(--p-content-border-color);
}

View File

@ -0,0 +1,14 @@
:root{
--p-dialog-background: var(--background-lifted-grey);
--p-dialog-border-color: var(--border-default-grey);
--p-dialog-color: var(--text-title-grey);
--p-dialog-border-radius: 0px;
--p-dialog-shadow: var(--shadow);
--p-dialog-header-padding: 1rem;
--p-dialog-header-gap: 0.5rem;
--p-dialog-title-font-size: var(--titles-H4-SM-size);
--p-dialog-title-font-weight: var( --titles-H4-SM-weight);
--p-dialog-content-padding: 0 1rem 1rem 1rem;
--p-dialog-footer-padding: 1rem;
--p-dialog-footer-gap: 1rem;
}

View File

@ -0,0 +1,24 @@
:root {
--p-fileupload-basic-gap: 0.5rem;
--p-fileupload-progressbar-height: 0.25rem;
--p-fileupload-file-list-gap: 0.5rem;
--p-fileupload-file-padding: 1rem;
--p-fileupload-file-gap: 1rem;
--p-fileupload-file-border-color: var(--border-default-grey);
--p-fileupload-file-info-gap: 0.5rem;
--p-fileupload-content-highlight-border-color: var(--border-action-high-grey);
--p-fileupload-content-padding: 0rem;
--p-fileupload-content-gap: 1rem;
--p-fileupload-header-background: transparent;
--p-fileupload-header-color: var(--text-default-grey);
--p-fileupload-header-padding: 0rem;
--p-fileupload-header-border-color: unset;
--p-fileupload-header-border-width: 0;
--p-fileupload-header-border-radius: 0;
--p-fileupload-header-gap: 0.5rem;
--p-fileupload-background: var(--background-default-grey);
--p-fileupload-border-color: var(--border-default-grey);
--p-fileupload-color: var(--text-default-grey);
--p-fileupload-border-radius: 0px;
--p-fileupload-transition-duration: var(--transition-duration);
}

View File

@ -0,0 +1,20 @@
:root{
--p-list-option-group-padding: 0.25rem;
--p-list-option-group-font-weight: var(--text-body-MD-standard-text-Regular-weight);
--p-list-option-padding: 0.25rem;
--p-list-option-border-radius: 0px;
--p-list-option-group-background: var(--input-background);
--p-list-option-group-color: var(--input-color);
--p-list-option-focus-background: var(--background-transparent-active);
--p-list-option-selected-background: var(--background-active-blue-france);
--p-list-option-selected-focus-background: var(--background-active-blue-france);
--p-list-option-color: var(--input-color);
--p-list-option-focus-color: var(--input-color);
--p-list-option-selected-color: var(--text-inverted-blue-france);
--p-list-option-selected-focus-color: var(--text-inverted-blue-france);
--p-list-option-icon-color: var(--input-color);
--p-list-option-icon-focus-color: var(--input-color);
--p-list-padding: 0.25rem;
--p-list-gap: 0.125rem;
--p-list-header-padding: 0.5rem 1rem;
}

View File

@ -0,0 +1,44 @@
:root {
--p-menubar-mobile-button-border-radius: var(--menu-border-radius);
--p-menubar-mobile-button-size: 1.75rem;
--p-menubar-mobile-button-color: var(--text-active-blue-france);
--p-menubar-mobile-button-hover-color: var(--text-active-blue-france);
--p-menubar-mobile-button-hover-background: var(--background-transparent-active);
--p-menubar-mobile-button-focus-ring-width: var(--focus-width);
--p-menubar-mobile-button-focus-ring-style: var(--focus-style);
--p-menubar-mobile-button-focus-ring-color: var(--focus-color);
--p-menubar-mobile-button-focus-ring-offset: var(--focus-offset);
/* --p-menubar-mobile-button-focus-ring-shadow: */
--p-menubar-separator-border-color: var(--border-default-grey);
--p-menubar-submenu-padding: var(--p-navigation-list-padding);
--p-menubar-submenu-gap: var(--p-navigation-list-gap);
--p-menubar-submenu-background: var(--background-transparent);
--p-menubar-submenu-border-color: var(--border-default-grey);
--p-menubar-submenu-border-radius: var(--menu-border-radius);
--p-menubar-submenu-shadow: var(--shadow);
--p-menubar-submenu-mobile-indent: 1rem;
--p-menubar-submenu-icon-size: var(--p-navigation-submenu-icon-size);
--p-menubar-submenu-icon-color: var(--p-navigation-submenu-icon-color);
--p-menubar-submenu-icon-focus-color: var(--p-navigation-submenu-icon-focus-color);
--p-menubar-submenu-icon-active-color: var(--p-navigation-submenu-icon-active-color);
--p-menubar-item-focus-background: var(--p-navigation-item-focus-background);
--p-menubar-item-active-background: var(--p-navigation-item-active-background);
--p-menubar-item-color: var(--p-navigation-item-color);
--p-menubar-item-focus-color: var(--p-navigation-item-focus-color);
--p-menubar-item-active-color: var(--p-navigation-item-active-color);
--p-menubar-item-padding: var(--p-navigation-item-padding);
--p-menubar-item-border-radius: var(--p-navigation-item-border-radius);
--p-menubar-item-gap: var(--p-navigation-item-gap);
--p-menubar-item-icon-color: var(--p-navigation-item-icon-color);
--p-menubar-item-icon-focus-color: var(--p-navigation-item-icon-focus-color);
--p-menubar-item-icon-active-color: var(--p-navigation-item-icon-active-color);
--p-menubar-base-item-border-radius: var(--menu-border-radius);
--p-menubar-base-item-padding: var(--p-navigation-item-padding);
--p-menubar-background: var(--background-transparent);
--p-menubar-border-color: var(--border-default-grey);
--p-menubar-border-radius: var(--menu-border-radius);
--p-menubar-color: var(--menu-default-color);
--p-menubar-gap: 0.5rem;
--p-menubar-padding: var(--menu-padding);
--p-menubar-transition-duration: var(--transition-duration);
}

View File

@ -0,0 +1,23 @@
:root{
--p-navigation-submenu-icon-size: 0.875rem;
--p-navigation-submenu-label-padding: var(--menu-padding);
--p-navigation-submenu-label-font-weight: var( --titles-H4-SM-weight);
--p-navigation-item-padding: calc(var(--menu-padding) * 3);
--p-navigation-item-border-radius: var(--p-border-radius-sm);
--p-navigation-item-gap: var(--menu-item-gap);
--p-navigation-list-padding: var(--menu-padding);
--p-navigation-list-gap: var(--menu-gap);
--p-navigation-submenu-icon-color: var(--menu-default-color);
--p-navigation-submenu-icon-focus-color: var(--menu-active-color);
--p-navigation-submenu-icon-active-color: var(--menu-active-color);
--p-navigation-submenu-label-background: var(--menu-default-background);
--p-navigation-submenu-label-color: var(--menu-default-color);
--p-navigation-item-focus-background: var(--menu-default-background);
--p-navigation-item-active-background: var(--menu-default-background);
--p-navigation-item-color: var(--menu-default-color);
--p-navigation-item-focus-color: var(--menu-active-color);
--p-navigation-item-active-color: var(--menu-active-color);
--p-navigation-item-icon-color: var(--menu-default-color);
--p-navigation-item-icon-focus-color: var(--menu-active-color);
--p-navigation-item-icon-active-color: var(--menu-active-color);
}

View File

@ -14,4 +14,7 @@
--p-overlay-popover-shadow: var(--shadow); --p-overlay-popover-shadow: var(--shadow);
--p-overlay-select-border-radius: 0px; --p-overlay-select-border-radius: 0px;
--p-overlay-select-shadow: var(--shadow); --p-overlay-select-shadow: var(--shadow);
--p-overlay-select-background: var(--input-background);
--p-overlay-select-border-color: var(--input-border-color);
--p-overlay-select-color: var(--input-color);
} }

View File

@ -0,0 +1,24 @@
:root{
--p-paginator-jump-to-page-input-max-width: 2.5rem;
--p-paginator-current-page-report-color: var(--text-action-high-grey);
--p-paginator-nav-button-background: transparent;
--p-paginator-nav-button-hover-background: var(--background-transparent-hover);
--p-paginator-nav-button-selected-background: var(--background-action-high-blue-france);
--p-paginator-nav-button-color: var(--text-action-high-grey);
--p-paginator-nav-button-hover-color: var(--text-action-high-grey);
--p-paginator-nav-button-selected-color: var(--text-inverted-blue-france);
--p-paginator-nav-button-width: 2rem;
--p-paginator-nav-button-height: 2rem;
--p-paginator-nav-button-border-radius: 0px;
--p-paginator-nav-button-focus-ring-width: var(--focus-width);
--p-paginator-nav-button-focus-ring-style: var(--focus-style);
--p-paginator-nav-button-focus-ring-color: var(--focus-color);
--p-paginator-nav-button-focus-ring-offset: var(--focus-offset);
/* --p-paginator-nav-button-focus-ring-shadow: var(--p-focus-ring-shadow); */
--p-paginator-padding: 0px;
--p-paginator-gap: 1rem;
--p-paginator-border-radius: 0px;
--p-paginator-background: transparent;
--p-paginator-color: var(--text-action-high-grey);
--p-paginator-transition-duration: var(--transition-duration);
}

View File

@ -0,0 +1,11 @@
:root {
--p-scrollpanel-bar-size: 0.5rem;
--p-scrollpanel-bar-border-radius: 0.25rem;
--p-scrollpanel-bar-focus-ring-width: var(--focus-width);
--p-scrollpanel-bar-focus-ring-style: var(--focus-style);
--p-scrollpanel-bar-focus-ring-color: var(--focus-color);
--p-scrollpanel-bar-focus-ring-offset: var(--focus-offset);
--p-scrollpanel-bar-focus-ring-shadow: none;
--p-scrollpanel-transition-duration: var(--transition-duration);
--p-scrollpanel-bar-background: var(--background-overlay-grey);
}

View File

@ -0,0 +1,37 @@
:root{
--p-toast-width: 25rem;
--p-toast-border-radius: 0px;
--p-toast-border-width: var(--large-border-width);
--p-toast-icon-size: 1.5rem;
--p-toast-content-padding: 0.25rem;
--p-toast-content-gap: 0.5rem;
--p-toast-text-gap: 0.5rem;
--p-toast-summary-font-weight: var( --text-body-MD-standard-text-Medium-weight);
--p-toast-summary-font-size: var(--text-body-MD-standard-text-Medium-size);
--p-toast-detail-font-weight: var( --text-body-MD-standard-text-Regular-weight);
--p-toast-detail-font-size: var(--text-body-MD-standard-text-Regular-size);
/* info */
--p-toast-info-background: var(--background-contrast-info);
--p-toast-info-border-color: var(--border-plain-info);
--p-toast-info-color: var(--text-default-info);
--p-toast-info-detail-color: var(--input-color);
--p-toast-info-shadow: var(--shadow);
/* success */
--p-toast-success-background: var(--background-contrast-success);
--p-toast-success-border-color: var(--border-plain-success);
--p-toast-success-color: var(--text-default-success);
--p-toast-success-detail-color: var(--input-color);
--p-toast-success-shadow: var(--shadow);
/* warn */
--p-toast-warn-background: var(--background-contrast-warning);
--p-toast-warn-border-color: var(--border-plain-warning);
--p-toast-warn-color: var(--text-default-warning);
--p-toast-warn-detail-color: var(--input-color);
--p-toast-warn-shadow: var(--shadow);
/* error */
--p-toast-error-background: var(--background-contrast-error);
--p-toast-error-border-color: var(--border-plain-error);
--p-toast-error-color: var(--text-default-error);
--p-toast-error-detail-color: var(--input-color);
--p-toast-error-shadow: var(--shadow);
}

View File

@ -3,4 +3,11 @@
--p-text-muted-color: var(--text-action-high-grey); --p-text-muted-color: var(--text-action-high-grey);
--p-transition-duration: var(--transition-duration); --p-transition-duration: var(--transition-duration);
--p-anchor-gutter: 2px; --p-anchor-gutter: 2px;
--p-content-background: var(--input-background);
--p-content-hover-background: var(--background-transparent-active);
--p-content-border-color: var(--border-action-high-grey);
--p-content-color: var(--text-action-high-grey);
--p-content-hover-color: var(--text-action-high-grey);
--p-content-border-radius: 0px;
--p-primary-color: var(--background-action-high-blue-france);
} }

View File

@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import Accordion from 'primevue/accordion'; import Accordion from 'primevue/accordion';
import VAccordionChild from './VAccordionChild.vue'; import VAccordionChild from './VAccordionChild.vue';
import type IVAccordion from './IVAccordion.type'; import type IVAccordion from './IVAccordion.type.js';
import { ref, watch } from 'vue'; import { ref, watch } from 'vue';
const props = withDefaults(defineProps<IVAccordion>(), { const props = withDefaults(defineProps<IVAccordion>(), {

View File

@ -2,7 +2,7 @@
import AccordionPanel from 'primevue/accordionpanel'; import AccordionPanel from 'primevue/accordionpanel';
import AccordionHeader from 'primevue/accordionheader'; import AccordionHeader from 'primevue/accordionheader';
import AccordionContent from 'primevue/accordioncontent'; import AccordionContent from 'primevue/accordioncontent';
import type IVAccordion from './IVAccordion.type'; import type IVAccordion from './IVAccordion.type.js';
import { useId } from 'vue'; import { useId } from 'vue';
const props = withDefaults(defineProps<IVAccordion>(), { const props = withDefaults(defineProps<IVAccordion>(), {

View File

@ -0,0 +1,62 @@
/**
* Interface representing the properties of an Alert component.
*/
export default interface IVAlert {
/**
* Determines if the alert should be visible.
* @default false
*/
alert?: boolean;
/**
* Indicates whether the alert has been closed.
* @default false
*/
closed?: boolean;
/**
* Specifies if the alert can be closed by the user.
* @default false
*/
closeable?: boolean;
/**
* Unique identifier for the alert instance.
*/
id?: string;
/**
* Title displayed at the top of the alert.
*/
title?: string;
/**
* Detailed description or message content of the alert.
*/
description?: string;
/**
* Determines if a smaller variant of the alert should be displayed.
* @default false
*/
small?: boolean;
/**
* Type of alert, affecting its visual style and icon.
* - `"success"`: Indicates a successful or positive action.
* - `"error"`: Indicates an error or critical issue.
* - `"info"`: Provides general information.
* - `"warn"`: Indicates a warning or potential issue.
*/
type?: "success" | "error" | "info" | "warn" | undefined;
/**
* Label for the close button, useful for accessibility.
*/
closeButtonLabel?: string;
/**
* Time in milliseconds after which the alert automatically disappears.
*/
lifeTime?: number;
}

View File

@ -0,0 +1,106 @@
<script setup lang="ts">
import Toast from 'primevue/toast';
import VButton from '../button/VButton.vue';
import type { ToastProps } from 'primevue/toast';
import styles from '@visua/typography.module.css';
const props = withDefaults(defineProps<ToastProps>(), {
group: undefined,
position: 'bottom-center',
breakpoints: undefined
});
const emit = defineEmits([
'close',
'life-end'
]);
const getIconColor = (type?: string) => {
switch (type) {
case 'error': return 'var(--text-default-error)';
case 'warn' : return 'var(--text-default-warning)';
case 'success' : return 'var(--text-default-success)';
case 'info': return 'var(--text-default-info)';
default:
return 'var(--text-default-success)';
}
};
const getIconClass = (type?: string) => {
switch (type) {
case 'error': return 'ri-spam-fill';
case 'warn' : return 'ri-alert-fill';
case 'success' : return 'ri-checkbox-circle-fill';
case 'info': return 'ri-information-fill';
default:
return 'ri-information-fill';
}
};
</script>
<template>
<Toast
:position=props.position
:group="props.group"
:breakpoints="props.breakpoints"
class="p-toast"
role="alert"
aria-live="assertive"
aria-atomic="true"
@close="emit('close', $event)"
@life-end="emit('life-end', $event)"
>
<template #container="{message, closeCallback}">
<div class="header">
<i
style="font-size: var(--p-message-icon-lg-size);"
:class="getIconClass(message.severity)"
:style="{color: getIconColor(message.severity)}"
/>
<span
:class="[styles['text-body-MD-standard-text-Medium']]"
:style="{color: getIconColor(message.severity)}"
style="width: 100%;"
>
{{ message.summary }}
</span>
<VButton
title="Fermer le message"
tertiary
no-outline
size="sm"
icon-only
aria-label="Fermer"
icon="ri-close-line"
type="button"
@click="closeCallback"
style="height: 2rem;"
/>
</div>
<div
v-if="!!message.detail"
:class="['content', styles['text-body-MD-standard-text-Regular']]"
>
<span>{{ message.detail }}</span>
<slot name="footer"/>
</div>
</template>
</Toast>
</template>
<style lang="css" scoped>
.header{
width: 100%;
display: flex;
flex-direction: row;
gap: 0.25rem;
padding: var(--p-toast-text-gap);
color: var(--text-title-grey);
align-items: center;
}
.content{
padding: 0rem 0.825rem 0.5rem 0.825rem;
color: var(--text-default-grey);
}
</style>

View File

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import type IVBadge from './IVBadge.type'; import type IVBadge from './IVBadge.type.js';
import Tag from 'primevue/tag'; import Tag from 'primevue/tag';
import { computed } from 'vue'; import { computed } from 'vue';
@ -23,11 +23,11 @@ const severity = computed(() => {
const icon = computed(() => { const icon = computed(() => {
switch (props.type) { switch (props.type) {
case 'error': return 'ri-close-circle-line'; case 'error': return 'ri-spam-fill';
case 'warning' : return 'ri-alert-line'; case 'warning' : return 'ri-alert-fill';
case 'success' : return 'ri-checkbox-circle-line'; case 'success' : return 'ri-checkbox-circle-fill';
case 'info': return 'ri-information-line'; case 'info': return 'ri-information-fill';
case 'new': return 'ri-flashlight-line'; case 'new': return 'ri-flashlight-fill';
default: default:
return undefined; return undefined;
} }
@ -45,7 +45,7 @@ const limit = computed(() => props.maxWidth);
class="p-tag" class="p-tag"
:class="{'small': props.small}" :class="{'small': props.small}"
> >
<i v-if="!props.noIcon || props.type === undefined" :class="icon"></i> <i v-if="!props.noIcon || props.type === undefined" :class="icon" style="font-weight: 100;"></i>
<span :class="{'limit': props.maxWidth}">{{ props.label }}</span> <span :class="{'limit': props.maxWidth}">{{ props.label }}</span>
</Tag> </Tag>
</template> </template>

View File

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import Button from 'primevue/button'; import Button from 'primevue/button';
import type IVButton from './IVButton.type'; import type IVButton from './IVButton.type.js';
import { computed } from 'vue'; import { computed } from 'vue';
import styles from '@visua/typography.module.css'; import styles from '@visua/typography.module.css';

View File

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import VButton from './VButton.vue'; import VButton from './VButton.vue';
import type IVButtonGroup from './IVButton.type.ts'; import type IVButtonGroup from './IVButton.type.js';
import { computed, ref, onMounted } from 'vue'; import { computed, ref, onMounted } from 'vue';
const props = withDefaults(defineProps<IVButtonGroup>(), { const props = withDefaults(defineProps<IVButtonGroup>(), {

View File

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import Button from 'primevue/button'; import Button from 'primevue/button';
import type IVLink from '@/components/button/IVLink.type'; import type IVLink from '@/components/button/IVLink.type.js';
import { computed } from 'vue'; import { computed } from 'vue';
import styles from '@visua/typography.module.css'; import styles from '@visua/typography.module.css';

View File

@ -2,7 +2,7 @@
import Checkbox from 'primevue/checkbox'; import Checkbox from 'primevue/checkbox';
import VLabel from '../label/VLabel.vue'; import VLabel from '../label/VLabel.vue';
import VHint from '../hint/VHint.vue'; import VHint from '../hint/VHint.vue';
import type IVCheckBox from './IVCheckbox.type'; import type IVCheckBox from './IVCheckbox.type.js';
import { useId, computed, watch, ref } from 'vue'; import { useId, computed, watch, ref } from 'vue';
const props = withDefaults(defineProps<IVCheckBox>(), { const props = withDefaults(defineProps<IVCheckBox>(), {

View File

@ -0,0 +1,2 @@
export * from './useAlert.js';
export * from './useConfirmModal.js';

View File

@ -0,0 +1,24 @@
import { useToast } from "primevue/usetoast";
import type IVAlert from "../alert/IVAlert.type.js";
export function useAlert() {
const toast = useToast();
const showAlert = ({
title = '',
description = '',
type = 'info',
closeable = true,
lifeTime,
}: IVAlert) => {
toast.add({
severity: type,
summary: title,
detail: description,
life: lifeTime,
closable: closeable,
})
}
return { showAlert}
}

View File

@ -0,0 +1,42 @@
import type { ConfirmationOptions } from "primevue/confirmationoptions";
import { useConfirm } from "primevue";
import VButton from "../button/VButton.vue";
export function useConfirmModal() {
const confirm = useConfirm();
const showConfirmModal = ({
acceptProps = VButton,
rejectProps = VButton,
group = '',
header = '',
message = '',
icon = '',
accept = Function,
reject = Function,
onHide = Function,
onShow = Function,
modal = true,
blockScroll = true,
position = 'center',
appendTo = 'body',
}: ConfirmationOptions) => {
confirm.require({
group,
header,
message,
icon,
rejectProps,
acceptProps,
accept,
reject,
onHide,
onShow,
modal,
blockScroll,
position,
appendTo,
})
}
return {showConfirmModal}
}

View File

@ -0,0 +1,198 @@
import type { HintedString } from '@primevue/core';
/**
* Interface representing the properties of a FileUpload component.
*/
export interface IFile {
/**
* Optional unique identifier for the file input.
*/
id?: string;
/**
* Optional label displayed above or beside the file input.
*/
label?: string;
/**
* Specifies the types of files that the input accepts.
* Can be a single MIME type string or an array of MIME types.
* Example: "image/*" or ["image/png", "application/pdf"]
*/
accept?: string | string[];
/**
* Optional helper text displayed below the input to guide the user.
*/
hint?: string;
/**
* Optional error message displayed when validation fails.
*/
error?: string;
/**
* Optional message displayed when the input is valid.
*/
validMessage?: string;
/**
* If true, disables the file input.
*/
disabled?: boolean;
/**
* The current value of the file input, typically a file path or name.
*/
modelValue?: string;
}
export interface FileUploadProps {
/**
* Name of the request parameter to identify the files at backend.
*/
name?: string | undefined;
/**
* Remote url to upload the files.
*/
url?: string | undefined;
/**
* Defines the UI of the component, possible values are 'advanced' and 'basic'.
* @defaultValue advanced
*/
mode?: HintedString<'advanced' | 'basic'> | undefined;
/**
* Used to select multiple files at once from file dialog.
* @defaultValue false
*/
multiple?: boolean | undefined;
/**
* Pattern to restrict the allowed file types such as 'image/*'.
*/
accept?: string | undefined;
/**
* Disables the upload functionality.
* @defaultValue false
*/
disabled?: boolean | undefined;
/**
* When enabled, upload begins automatically after selection is completed.
* @defaultValue false
*/
auto?: boolean | undefined;
/**
* Maximum file size allowed in bytes.
*/
maxFileSize?: number | undefined;
/**
* Message of the invalid fize size.
* @defaultValue {0}: Invalid file size, file size should be smaller than {1.}
*/
invalidFileSizeMessage?: string | undefined;
/**
* Message to display when number of files to be uploaded exceeeds the limit.
* @defaultValue Maximum number of files to be uploaded is {0.}
*/
invalidFileLimitMessage?: string | undefined;
/**
* Message of the invalid fize type.
* @defaultValue '{0}: Invalid file type.'
*/
invalidFileTypeMessage?: string | undefined;
/**
* Maximum number of files that can be uploaded.
*/
fileLimit?: number | undefined;
/**
* Cross-site Access-Control requests should be made using credentials such as cookies, authorization headers or TLS client certificates.
* @defaultValue false
*/
withCredentials?: boolean | undefined;
/**
* Width of the image thumbnail in pixels.
* @defaultValue 50
*/
previewWidth?: number | undefined;
/**
* Label of the choose button. Defaults to PrimeVue Locale configuration.
*/
chooseLabel?: string | undefined;
/**
* Label of the upload button. Defaults to PrimeVue Locale configuration.
*/
uploadLabel?: string | undefined;
/**
* Label of the cancel button. Defaults to PrimeVue Locale configuration.
* @defaultValue Cancel
*/
cancelLabel?: string | undefined;
/**
* Whether to use the default upload or a manual implementation defined in uploadHandler callback. Defaults to PrimeVue Locale configuration.
*/
customUpload?: boolean | undefined;
/**
* Whether to show the upload button.
* @defaultValue true
*/
showUploadButton?: boolean | undefined;
/**
* Whether to show the cancel button.
* @defaultValue true
*/
showCancelButton?: boolean | undefined;
/**
* Icon of the choose button.
*/
chooseIcon?: string | undefined;
/**
* Icon of the upload button.
*/
uploadIcon?: string | undefined;
/**
* Icon of the cancel button.
*/
cancelIcon?: string | undefined;
/**
* Inline style of the component.
*/
style?: unknown;
/**
* Style class of the component.
*/
class?: unknown;
/**
* Used to pass all properties of the ButtonProps to the choose button inside the component.
* @type {ButtonProps}
* @defaultValue null
*/
chooseButtonProps?: object | undefined;
/**
* Used to pass all properties of the ButtonProps to the upload button inside the component.
* @type {ButtonProps}
* @defaultValue { severity: 'secondary' }
*/
uploadButtonProps?: object | undefined;
/**
* Used to pass all properties of the ButtonProps to the cancel button inside the component.
* @type {ButtonProps}
* @defaultValue { severity: 'secondary' }
*/
cancelButtonProps?: object | undefined;
}
/**
* Extended interface for a customizable FileUpload component.
*
* Combines selected properties from IFile and FileUploadProps,
* while omitting and overriding specific ones for more control.
*/
export default interface IVFileUpload extends
Partial<Omit<IFile, 'accept' | 'error'>>,
Partial<Omit<FileUploadProps, 'auto' | 'mode' | 'multiple'>> {
/**
* If true, enables the advanced mode of the file upload component,
* which may include features like drag-and-drop, file previews, etc.
*/
advanced?: boolean;
}

View File

@ -0,0 +1,229 @@
<script setup lang="ts">
import VBadge from '../badge/VBadge.vue';
import VButton from '../button/VButton.vue';
import VProgressBar from '../progressbar/VProgressBar.vue';
import { usePrimeVue } from 'primevue';
import { ref, computed } from 'vue';
import styles from '@visua/typography.module.css';
export interface IVFile {
file: File
advanced?: boolean
index?: number
removeFileCallback?: (index: number) => void
status?: 'pending' | 'completed' | 'error'
progress?: number
}
const props = withDefaults(defineProps<IVFile>(), {
advanced: false,
index: 0,
removeFileCallback: () => {},
status: 'pending',
progress: 0,
})
const badgeType = computed(() => {
switch (props.status) {
case 'pending': return 'warning'
case 'completed': return 'success'
case 'error': return 'error'
default:
return undefined;
}
})
const badgeLabel = computed(() => {
switch (props.status) {
case 'pending': return 'En attente'
case 'completed': return 'Terminé'
case 'error': return 'Annulé'
default:
return '';
}
})
const $primevue = usePrimeVue();
const totalSize = ref(0);
const totalSizePercent = ref(0);
const formatSize = (bytes: number) => {
const k = 1024;
const dm = 3;
const sizes = $primevue.config.locale?.fileSizeTypes ?? ['B', 'KB', 'MB', 'GB', 'TB'];;
if (bytes === 0) {
return `0 ${ sizes[0]}`;
}
const i = Math.floor(Math.log(bytes) / Math.log(k));
const formattedSize = parseFloat((bytes / k ** i).toFixed(dm));
return `${formattedSize} ${sizes[i]}`;
};
const onRemoveTemplatingFile = (file: File, removeFileCallback: (index: number) => void, index: number) => {
removeFileCallback(index);
totalSize.value -= parseInt(formatSize(file.size));
totalSizePercent.value = totalSize.value / 10;
};
const getFileIconClass = (file: File) => {
const type = file?.name.toLowerCase();
if (type?.endsWith('.pdf')) return 'ri-file-pdf-2-line';
if (type?.endsWith('.doc') || type?.endsWith('.docx')) return 'ri-file-word-2-line';
if (type?.endsWith('.xls') || type?.endsWith('.xlsx')) return 'ri-file-excel-2-line';
if (type?.endsWith('.txt')) return 'ri-file-text-line';
if (type?.endsWith('.zip') || type?.endsWith('.rar')) return 'ri-file-zip-line';
return 'ri-file-line'; // icône générique
};
const getFileIconColor = (file: File) => {
const type = file?.name.toLowerCase();
if (type?.endsWith('.pdf')) return 'var(--border-plain-error)'; // rouge
if (type?.endsWith('.doc') || type?.endsWith('.docx')) return 'var(--border-plain-blue-france)'; // bleu
if (type?.endsWith('.xls') || type?.endsWith('.xlsx')) return 'var(--border-plain-success)'; // vert
if (type?.endsWith('.txt')) return 'var(--border-plain-grey)'; // gris
if (type?.endsWith('.zip') || type?.endsWith('.rar')) return 'var(--illustration-color-850-tournesol-default)'; // jaune
return 'var(--border-plain-blue-france)'; // bleu marine
};
const borderColor = computed(() => {
switch (props.status) {
case 'pending': return '2px solid var(--system-color-950-warning-active)';
case 'completed': return '2px solid var(--system-color-950-success-active)';
case 'error': return '2px solid var(--system-color-950-success-active)';
default:
return undefined;
}
})
const getImagePreview = (file: File) => {
return URL.createObjectURL(file);
};
</script>
<template>
<div
v-if="props.advanced"
class="advanced-file"
:title="`Fichier : ${props.file.name}`"
:style="{border: borderColor}"
:class="[styles['text-body-XS-mention-text-Medium']]"
>
<VButton
tertiary
noOutline
size="sm"
icon="ri-close-line"
@click="onRemoveTemplatingFile(props.file, props.removeFileCallback, props.index)"
title='Spprimer le fichier'
ref="clearfile"
aria-controls="clear-file"
type="button"
class="button"
/>
<div>
<img
v-if="file?.type.startsWith('image/')"
role="presentation"
:alt="props.file?.name"
:src="getImagePreview(props.file)"
/>
<i
v-else
:class="getFileIconClass(props.file)"
:style="{color: getFileIconColor(props.file), fontSize: '4rem', fontWeight: 500}"
/>
</div>
<span :title="props.file?.name" class="file-name">
{{ props.file?.name }}
</span>
<span>{{ formatSize(props.file?.size ?? 0) }}</span>
<VBadge
v-if="advanced"
:label="badgeLabel"
:type="badgeType"
small
no-icon
/>
<VProgressBar
v-if="advanced && status === 'pending'"
:value="props.progress"
:show-value="false"
style="width: 6rem; margin-top: 0.5rem;"
/>
</div>
<!-- Simple file -->
<div v-else class="simple-file" >
<div>
<img
v-if="file?.type.startsWith('image/')"
role="presentation"
:alt="props.file.name"
:src="getImagePreview(props.file)"
/>
<i
v-else
:class="getFileIconClass(props.file)"
:style="{color: getFileIconColor(props.file), fontSize: '2.5rem', padding: '0px'}"
/>
</div>
<div class="file-info" :class="[styles['text-body-SM-detail-text-Regular']]">
<span>{{ props.file?.name }}</span>
<span>{{ formatSize(props.file?.size ?? 0) }}</span>
</div>
<VBadge
:label="badgeLabel"
:type="badgeType"
small
no-icon
/>
</div>
</template>
<style lang="css" scoped>
img{
width: 2.5rem;
height: auto;
}
.button{
display: flex;
align-self: self-end;
}
.advanced-file{
border-radius: 3px;
width: fit-content;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
padding-bottom: 0.25rem;
}
.simple-file{
display: flex;
flex-direction: row;
align-items: center;
gap: calc(var(--p-fileupload-content-gap) / 4);
}
.file-name{
width: 6rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin: 0rem 0.5rem;
text-align: center;
}
.file-info{
display: flex;
flex-direction: column;
justify-content: center;
align-items: start;
gap: 0.025rem;
}
</style>

View File

@ -0,0 +1,377 @@
<script setup lang="ts">
import FileUpload from 'primevue/fileupload';
import VHint from '../hint/VHint.vue';
import VLabel from '../label/VLabel.vue';
import VButton from '../button/VButton.vue';
import VButtonGroup from '../button/VButtonGroup.vue';
import VFile from './VFile.vue';
import VMessage from '../message/VMessage.vue';
import VScrollPanel from '../scrollpanel/VScrollPanel.vue';
import VLabelErrorProxy from './VLabelErrorProxy.vue';
import type IVFileUpload from './IVFileUpload.type.js';
import type { FileUploadErrorEvent, FileUploadProgressEvent, FileUploadRemoveEvent, FileUploadSelectEvent, FileUploadUploadEvent } from 'primevue/fileupload';
import { computed, useId, ref } from 'vue';
import styles from '@visua/typography.module.css';
const fileUploadRef = ref();
const fileProgressMap = ref<Record<string, number>>({});
const hasValidationError = ref(false);
const hasSystemError = ref(false);
const systemErrorMessage = ref('');
defineOptions({
inheritAttrs: false,
})
const props = withDefaults(defineProps<IVFileUpload>(), {
id: () => useId(),
label: 'Ajouter un fichier',
validMessage: '',
hint: '',
disabled: false,
invalidFileSizeMessage: 'Taille de fichier {0} invalide, la taille du fichier doit être plus petite que {1}',
invalidFileLimitMessage: 'Nombre maximal des fichiers atteint, le nombre maximal est {0.}',
invalidFileTypeMessage: 'Type de fichier invalide, le type de fichier {0} n\'est pas pris en charge',
accept: undefined,
chooseLabel: 'Choisir',
name: undefined,
url: undefined,
maxFileSize: undefined,
fileLimit: undefined,
withCredentials: false,
previewWidth: 50,
uploadLabel: 'Téléverser',
cancelLabel: 'Annuler',
showCancelButton: true,
showUploadButton: true,
advanced: false
})
const emit = defineEmits([
'select',
'before-upload',
'progress',
'upload',
'error',
'before-send',
'clear',
'remove',
'removeUploadFile',
'uploader',
]);
defineExpose({
upload: () => fileUploadRef.value.upload()
});
const handleSelect = (event: FileUploadSelectEvent) => {
emit('select', event);
if (!props.advanced && event.files.length > 0 && fileUploadRef.value) {
fileUploadRef.value.files = [event.files.at(-1)];
fileUploadRef.value.uploadedFiles = [];
}
};
const handleClear = () => {
emit('clear');
};
const handleUpload = (event: FileUploadUploadEvent) => {
emit('upload', event);
};
const handleRemove = (event: FileUploadRemoveEvent) => {
emit('remove', event);
};
const handleProgress = (event: FileUploadProgressEvent) => {
emit('progress', event);
const files = fileUploadRef.value?.files || [];
files.forEach((file: File) => {
fileProgressMap.value[file.name] = event.progress;
});
};
const handleError = (event: FileUploadErrorEvent) => {
emit('error', event);
hasSystemError.value = true;
const status = event.xhr?.status;
const statusText = event.xhr?.statusText || 'Erreur inconnue';
const fileNames = Array.isArray(event.files)
? event.files.map(f => f.name).join(', ')
: event.files.name;
systemErrorMessage.value = `Echec de téléversement du fichier ${fileNames} {Status : ${status} | Message : <${statusText}>}`;
if (!props.advanced && fileUploadRef.value) {
fileUploadRef.value.uploadedFiles = [];
}
};
const lastSelectedFile = computed(() => {
const files = fileUploadRef.value?.files || [];
return files.at(-1) ?? null;
});
const uploadEvent = (callback: () => void) => {
callback();
};
const padding = computed(() => props.advanced ? '1.125rem' : '0rem')
const borderColor = computed(() => props.advanced ? 'var(--border-default-grey)' : 'transparent');
const labelState = computed(() => {
if ((hasValidationError.value || hasSystemError.value) && !props.disabled) return 'error';
return 'default';
});
type MessageType = 'alert' | 'warning' | 'success' | 'info';
const globalStatusMessage = computed<{
type: MessageType;
title: string;
} | null>(() => {
const files = fileUploadRef.value?.files || [];
const uploaded = fileUploadRef.value?.uploadedFiles || [];
const hasError = files.some((f: { status: string; }) => f.status === 'error');
const hasPending = files.length > 0;
if (hasError) return { type: 'alert', title: 'Erreur lors du téléversement' };
if (hasPending) return { type: 'warning', title: 'En attente de téléversement' };
if (uploaded.length > 0) return { type: 'success', title: 'Tous les fichiers ont été téléversés' };
return null;
});
</script>
<template>
<div class="container" data-testid="file-label">
<VLabel
:for="`file-upload-${props.id}`"
:label="props.label"
:disabled="props.disabled"
:type="labelState"
:hint="props.hint"
/>
<FileUpload
ref="fileUploadRef"
:id="`file-upload-${props.id}`"
:multiple="props.advanced"
:invalid-file-limit-message="props.invalidFileLimitMessage"
:invalid-file-size-message="props.invalidFileSizeMessage"
:invalid-file-type-message="props.invalidFileTypeMessage"
mode="advanced"
:accept="props.accept"
:disabled="props.disabled"
v-bind="$attrs"
:auto="!props.advanced"
:choose-label="props.chooseLabel"
:upload-label="props.uploadLabel"
:cancel-label="props.cancelLabel"
:name="props.name"
:url="props.url"
:max-file-size="props.maxFileSize"
:file-limit="props.fileLimit"
:with-credentials="props.withCredentials"
:preview-width="props.previewWidth"
:show-cancel-button="props.showCancelButton"
:show-upload-button="props.showUploadButton"
@select="handleSelect($event)"
@before-send="emit('before-send', $event)"
@progress="handleProgress($event)"
@before-upload="emit('before-upload', $event)"
@uploader="emit('uploader', $event)"
@upload="handleUpload($event)"
@error="handleError($event)"
@clear="handleClear()"
@remove="handleRemove($event)"
@remove-uploaded-file="emit('removeUploadFile', $event)"
class="p-fileupload"
>
<template #header="slotProps">
<VButtonGroup
v-if="props.advanced"
:buttons="[
{
label: 'Parcourir...',
onClick: slotProps.chooseCallback,
title: 'parcourir les fichiers',
},
{
label: 'Téléverser',
icon: 'ri-upload-cloud-line',
onClick: () => uploadEvent(slotProps.uploadCallback),
secondary: true,
disabled: !slotProps.files || slotProps.files.length === 0,
title: 'televerser les fichiers'
},
{
label: 'Supprimer',
tertiary: true,
onClick: slotProps.clearCallback,
disabled: !slotProps.files || slotProps.files.length === 0,
title: 'suprimer les fichiers'
}
]"
size="sm"
:disabled="props.disabled"
inlineLayoutWhen="always"
title="boutons de gestion des fichiers"
/>
<div v-else class="simple">
<VButton
label="Parcourir..."
:disabled="props.disabled"
size="sm"
@click="slotProps.chooseCallback"
title="parcourir les fichiers"
/>
<span
v-if="(!slotProps.files || slotProps.files.length === 0) && (!slotProps.uploadedFiles || slotProps.uploadedFiles.length === 0) && !(hasSystemError || hasValidationError)"
:class="[styles['text-body-SM-detail-text-Regular']]"
>
Aucun fichier sélectionné
</span>
</div>
</template>
<template #empty v-if="props.advanced && !(hasSystemError || hasValidationError)">
<div class="fileupload-empty" :class="[styles['text-body-SM-detail-text-Regular']]">
<i class="ri-upload-cloud-line upload-cloud-icon"></i>
<p>Glissez-déposez les fichiers ici pour les téléverser.</p>
</div>
</template>
<template #content="slotProps">
<div v-if="props.advanced && !(hasValidationError || hasSystemError)" style="margin-top: 0.75rem;" class="file-content">
<VMessage
v-if="globalStatusMessage"
:type="globalStatusMessage.type"
:title="globalStatusMessage.title"
/>
<VScrollPanel height="20rem">
<div class="file-area">
<div v-if="slotProps.files.length > 0" class="file-non-uploaded-area">
<VFile
v-for="(file, index) in slotProps.files"
:key="file.name + file.type + file.size"
:file="file"
:index="index"
:remove-file-callback="slotProps.removeFileCallback"
:progress="fileProgressMap[file.name] || 0"
status="pending"
advanced
/>
</div>
<div v-if="slotProps.uploadedFiles.length > 0" class="file-uploaded-area">
<VFile
v-for="(file, index) in slotProps.uploadedFiles"
:key="file.name + file.type + file.size"
:file="file"
:index="index"
:remove-file-callback="slotProps.removeFileCallback"
:progress="100"
status="completed"
advanced
/>
</div>
</div>
</VScrollPanel>
</div>
<div v-if="!props.advanced && !(hasValidationError || hasSystemError)">
<VFile
v-if="lastSelectedFile"
:file="lastSelectedFile"
:remove-file-callback="slotProps.removeFileCallback"
status="pending"
/>
<VFile
v-if="slotProps.uploadedFiles.length > 0 && slotProps.uploadedFiles.at(-1)"
:file="slotProps.uploadedFiles.at(-1)!"
:remove-file-callback="slotProps.removeFileCallback"
status="completed"
/>
</div>
<div
role="alert"
aria-live="polite"
>
<VHint
v-for="message of [...(slotProps.messages ?? []), ...(hasSystemError ? [systemErrorMessage] : [])]"
:key="message"
:title="message"
type="alert"
icon
/>
</div>
<VLabelErrorProxy
:hasError="(slotProps.messages ?? []).length > 0"
@update:error="hasValidationError = $event"
/>
</template>
</FileUpload>
</div>
</template>
<style lang="css" scoped>
*{
margin: 0px;
padding: 0px;
}
.container {
width: 100%;
height: auto;
display: flex;
flex-direction: column;
align-items: start;
gap: 0.5rem;
}
.p-fileupload{
width: 100%;
--p-fileupload-border-color: v-bind(borderColor);
padding: v-bind(padding);
}
.simple{
display: flex;
flex-direction: row;
align-items: center;
gap: var(--p-fileupload-file-gap);
}
.fileupload-empty{
display: flex;
flex-direction: column;
align-items: center;
margin-top: 1rem;
gap: 1.5rem;
}
.upload-cloud-icon{
font-size: 5rem;
color: var(--border-contrast-grey);
}
.file-content,
.file-area{
display: flex;
flex-direction: column;
}
.file-content{gap: 0.75rem;}
.file-area{gap: 0.5rem;}
.file-non-uploaded-area,
.file-uploaded-area {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 0.25rem;
}
</style>

View File

@ -0,0 +1,27 @@
<script setup lang="ts">
import { onMounted, onUpdated, watch } from 'vue';
const props = defineProps<{
hasError: boolean
}>();
const emit = defineEmits<{
(e: 'update:error', value: boolean): void
}>();
onMounted(() => {
emit('update:error', props.hasError);
});
onUpdated(() => {
emit('update:error', props.hasError);
});
watch(() => props.hasError, (val) => {
emit('update:error', val);
});
</script>
<template>
<slot/>
</template>

View File

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import type IVGroup from './IVGroup.type'; import type IVGroup from './IVGroup.type.js';
const props = withDefaults(defineProps<IVGroup>(), { const props = withDefaults(defineProps<IVGroup>(), {
type: 'default', type: 'default',

View File

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import Message from 'primevue/message'; import Message from 'primevue/message';
import type IVHint from './IVHint.type'; import type IVHint from './IVHint.type.js';
import { computed } from 'vue'; import { computed } from 'vue';
import styles from '@visua/typography.module.css'; import styles from '@visua/typography.module.css';

View File

@ -4,7 +4,7 @@ import Textarea from 'primevue/textarea';
import VHint from '../hint/VHint.vue'; import VHint from '../hint/VHint.vue';
import VDivider from '../divider/VDivider.vue'; import VDivider from '../divider/VDivider.vue';
import VLabel from '../label/VLabel.vue'; import VLabel from '../label/VLabel.vue';
import type IVInput from './IVInput.type'; import type IVInput from './IVInput.type.js';
import { computed, useAttrs, useId, ref, watch } from 'vue'; import { computed, useAttrs, useId, ref, watch } from 'vue';
import styles from '@visua/typography.module.css'; import styles from '@visua/typography.module.css';
import Password from 'primevue/password'; import Password from 'primevue/password';
@ -70,7 +70,7 @@ const labelState = computed(() => {
v-if="props.labelVisible" v-if="props.labelVisible"
:for="props.id" :for="props.id"
:label="props.label" :label="props.label"
:required="!props.disabled" :required="!props.disabled && props.required"
:disabled="props.disabled" :disabled="props.disabled"
:type="labelState" :type="labelState"
:hint="props.hint" :hint="props.hint"

View File

@ -0,0 +1,18 @@
export type { default as IVAccordion } from '../accordion/IVAccordion.type.js';
export type { default as IVAlert } from '../alert/IVAlert.type.js';
export type { default as IVBadge } from '../badge/IVBadge.type.js';
export type { default as IVButton } from '../button/IVButton.type.js';
export type { default as IVButtonGroup } from '../button/IVButton.type.js';
export type { default as IVCheckbox } from '../checkbox/IVCheckbox.type.js';
export type { default as IVLink } from '../button/IVLink.type.js';
export type { default as IVFileUpload } from '../file/IVFileUpload.type.js';
export type { default as IVGroup } from '../group/IVGroup.type.js';
export type { default as IVHint } from '../hint/IVHint.type.js';
export type { default as IVInput } from '../input/IVInput.type.js';
export type { default as IVLabel } from '../label/IVLabel.type.js';
export type { default as IVMessage } from '../message/IVMessage.type.js';
export type { default as IVModal } from '../modal/IVModal.type.js';
export type { default as IVProgressBar } from '../progressbar/IVProgressBar.type.js';
export type { default as IVSelect } from '../select/IVSelect.type.js';
export type { default as IVMenuBar } from '../menu/IVMenuBar.type.js';
export type { default as IVMenuBarItem } from '../menu/IVMenuBar.type.js';

View File

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import type IVLabel from './IVLabel.type'; import type IVLabel from './IVLabel.type.js';
import styles from '@visua/typography.module.css'; import styles from '@visua/typography.module.css';
import VHint from '../hint/VHint.vue'; import VHint from '../hint/VHint.vue';
@ -14,6 +14,7 @@ const props = withDefaults(defineProps<IVLabel>(), {
<template> <template>
<label <label
:for="props.for" :for="props.for"
class="label-container"
:class="[styles['text-body-MD-standard-text-Regular'], { :class="[styles['text-body-MD-standard-text-Regular'], {
'label': props.type === 'default', 'label': props.type === 'default',
'success': props.type === 'success', 'success': props.type === 'success',
@ -23,12 +24,14 @@ const props = withDefaults(defineProps<IVLabel>(), {
:aria-label="props.label" :aria-label="props.label"
:aria-disabled="props.disabled" :aria-disabled="props.disabled"
> >
<span>
{{ props.label }} {{ props.label }}
<template v-if="props.required"> <template v-if="props.required">
<span v-if="props.required" :class="{ 'required': !props.disabled}"> <span v-if="props.required" :class="{ 'required': !props.disabled}">
<slot name="required-tip">*</slot> <slot name="required-tip">*</slot>
</span> </span>
</template> </template>
</span>
<VHint <VHint
v-if="props.hint" v-if="props.hint"
:title="props.hint" :title="props.hint"
@ -38,6 +41,18 @@ const props = withDefaults(defineProps<IVLabel>(), {
</template> </template>
<style lang="css" scoped> <style lang="css" scoped>
*{
padding: 0px;
margin: 0px;
}
.label-container{
display: flex;
flex-direction: column;
align-items: start;
gap: 0.25rem;
}
.label {color: var(--text-label-grey);} .label {color: var(--text-label-grey);}
.success {color: var(--text-default-success);} .success {color: var(--text-default-success);}
.error {color: var(--text-default-error);} .error {color: var(--text-default-error);}

View File

@ -0,0 +1,93 @@
import type { HTMLAttributes } from "vue"
import type { RouteLocationRaw } from "vue-router"
import type { MenuItem } from "primevue/menuitem"
/**
* Describes a link or button item in a menu bar.
*/
export interface IVMenuBarLinks {
/** Whether the item should be rendered as a button */
button?: boolean;
/** Name of the icon to display on the left */
icon?: string;
/** Whether the icon appears on the right side */
iconRight?: boolean;
/** Text label for the item */
label?: string;
/** Target URL if rendered as an anchor (<a>) */
target?: string;
/** Callback executed when the item is clicked */
onClick?: ($event: MouseEvent) => void;
/** Navigation route */
to?: RouteLocationRaw;
}
/**
* Extends PrimeVue's MenuItem with optional routing capabilities.
* Excludes 'style' and 'class' to avoid conflicts with Vue attributes.
*/
export interface IVMenuBarItem
extends Partial<Omit<MenuItem, 'style' | 'class'>>,
Partial<Pick<IVMenuBarLinks, 'to'>> {}
/**
* Interface for configuring a customizable menu bar component.
*/
export default interface IVMenuBar {
/** HTML id for the searchbar input */
searchbarId?: string;
/** Title of the service or application */
serviceTitle?: string;
/** Description of the service */
serviceDescription?: string;
/** Text displayed beside the logo (can be multiline) */
logoText?: string | string[];
/** Whether to show the logo */
logo?: boolean;
/** Bound model value for the search input */
modelValue?: string;
/** Placeholder text for the search input */
placeholder?: string;
/** List of quick link items with additional HTML attributes */
quickLinks?: (IVMenuBarItem & HTMLAttributes)[];
/** Label for the search input (accessibility) */
searchLabel?: string;
/** ARIA label for the quick links section */
quickLinksAriaLabel?: string;
/** Whether to display the search bar */
showSearch?: boolean;
/** Label describing the search bar visibility */
showSearchLabel?: string;
/** Label for the main menu toggle button */
menuLabel?: string;
/** Label used in modal menus */
menuModalLabel?: string;
/** Label for closing the modal menu */
closeMenuModalLabel?: string;
/** Responsive breakpoints (e.g. Tailwind-style classes) */
breakpoints?: string;
logoPath?: string;
}

View File

@ -0,0 +1,122 @@
<script setup lang="ts">
import Menubar from 'primevue/menubar';
import type IVMenuBar from './IVMenuBar.type.js';
import styles from '@visua/typography.module.css'
import { useRoute } from 'vue-router';
const props = withDefaults(defineProps<IVMenuBar>(), {
searchbarId: 'searchbar-header',
serviceTitle: undefined,
serviceDescription: undefined,
logo: false,
logoText: undefined,
breakpoints: '960px',
quickLinks: undefined,
menuLabel: undefined,
logoPath: '/home',
})
const route = useRoute();
</script>
<template>
<Menubar
role="banner"
:model="props.quickLinks"
:breakpoint="props.breakpoints"
class="p-menubar"
>
<template #start>
<RouterLink :to=props.logoPath class="logo-container">
<div v-if="$slots.logo" class="logo">
<slot name="logo"/>
</div>
<span
v-if="!!props.serviceTitle"
:class="[styles['titles-H6-XXS']]"
style="color: var(--text-title-grey);"
>
{{ props.serviceTitle }}
</span>
</RouterLink>
</template>
<template #end>
<slot name="end"/>
</template>
<template #item="{ item, props, hasSubmenu }">
<component
v-if="hasSubmenu || 'label' in item || 'icon' in item"
:is="item.to ? 'RouterLink' : item.url ? 'a' : 'div'"
:to="item.to"
:href="item.url"
:target="item.target"
:tabindex="0"
v-bind="props.action"
class="item"
:class="[styles['text-body-LG-article-text-Regular'],
item.to === route.path ? 'active' : ''
]"
>
<slot name="itemicon" :item="item" v-if="'icon' in item">
<i :class="[item.icon]"></i>
</slot>
<span v-if="'label' in item" style="width: 100%;">{{ item.label }}</span>
<slot name="submenuicon" v-if="hasSubmenu">
<i class="ri-arrow-down-s-line"/>
</slot>
</component>
</template>
<template #button="{id, toggleCallback}">
<slot name="button" :id="id" :toggleCallback="toggleCallback"/>
</template>
<template #buttonicon>
<slot name="buttonicon"/>
</template>
</Menubar>
</template>
<style lang="css" scoped>
*{
margin: 0px;
padding: 0px;
}
a{
color: var(--p-menubar-color);
text-decoration: none;
}
.logo{
display: block;
width: 25%;
height: auto;
padding: 0px;
margin: 0px;
}
.logo-container{
display: flex;
flex-direction: row;
width: fit-content;
gap: var(--p-menubar-gap);
align-items: center;
}
.p-menubar{
width: 100%;
height: 4.5rem;
border-width: 0px 0px var(--border-width) 0px;
border-style: solid;
border-color: var(--p-menubar-border-color);
}
.item:hover,
.item.active{
color: var(--menu-active-color);
}
.item.active {
height: 100%;
font-weight: bold;
}
</style>

View File

@ -0,0 +1,25 @@
/**
* Interface representing the structure of a message notice component.
*/
export default interface IVMessage {
/**
* The title or main text of the notice.
*/
title: string;
/**
* The type of notice, which affects its visual style and semantics.
* - `info`: Informational message
* - `warning`: Warning message
* - `alert`: Error or critical alert
* - `success`: Confirmation or success message
* - undefined: No specific type
*/
type?: 'info' | 'warning' | 'alert' | 'success' | undefined;
/**
* If true, the notice can be closed by the user.
* @default false
*/
closable?: boolean;
}

View File

@ -0,0 +1,115 @@
<script setup lang="ts">
import type IVMessage from './IVMessage.type.js';
import Message from 'primevue/message';
import VButton from '../button/VButton.vue';
import { computed } from 'vue';
import styles from '@visua/typography.module.css';
const props = withDefaults(defineProps<IVMessage>(), {
type: undefined,
closable: false,
})
const emit = defineEmits(['close']);
const iconClass = computed(() => {
switch (props.type) {
case 'alert': return 'ri-spam-fill';
case 'warning' : return 'ri-alert-fill';
case 'success' : return 'ri-checkbox-circle-fill';
case 'info': return 'ri-information-fill';
default:
return undefined;
}
});
const severity = computed(() => {
switch (props.type) {
case 'alert': return 'error';
case 'warning' : return 'warn';
case 'success' : return 'success';
case 'info' : return 'info';
default:
return undefined;
}
});
const iconColor = computed(() => {
switch (props.type) {
case 'alert': return 'var(--text-default-error)';
case 'warning' : return 'var(--text-default-warning)';
case 'success' : return 'var(--text-default-success)';
case 'info': return 'var(--text-default-info)';
default:
return 'var(--text-mention-grey)';
}
});
</script>
<template>
<Message
role="alert"
:closable="props.closable"
:severity="severity"
size="large"
class="p-message"
@close="emit('close', $event)"
>
<template #container="slotProps">
<div class="container">
<div class="header">
<i :class="[iconClass]" :style="{color: iconColor}" style="font-size: var(--p-message-icon-lg-size);"></i>
<span class="title" :class="[styles['text-body-MD-standard-text-Regular']]" :style="{color: iconColor}">
{{ props.title }}
</span>
<VButton
v-if="props.closable"
title="Fermer le message"
tertiary
no-outline
size="sm"
icon-only
aria-label="Fermer"
icon="ri-close-line"
type="button"
@click="slotProps.closeCallback"
/>
</div>
<div v-if="$slots.content" class="content">
<slot name="content"/>
</div>
</div>
</template>
</Message>
</template>
<style lang="css" scoped>
.p-message{
width: 100%;
height: fit-content;
margin: 0px;
}
.container{
padding: 0.5rem 0.75rem;
display: flex;
flex-direction: column;
gap: var(--p-message-content-gap);
}
.header{
display: flex;
flex-direction: row;
gap: var(--p-message-content-gap);
align-items: baseline;
}
.title{
width: 100%;
}
.content{
display: block;
padding: 0rem 0.825rem 0.5rem 0.825rem;
}
</style>

View File

@ -0,0 +1,205 @@
import type { HTMLAttributes } from 'vue';
import type IVButton from '../button/IVButton.type.js';
import { type DialogBreakpoints } from 'primevue';
import type { HintedString } from '@primevue/core';
/**
* Interface representing the properties for a Modal component.
*/
export interface IModal {
/**
* Optional unique identifier for the modal element.
*/
modalId?: string
/**
* Flag indicating whether the modal is currently open.
*/
opened?: boolean
/**
* List of actions (buttons) displayed in the modal.
*/
actions?: IVButton[]
/**
* Determines if the modal should behave like an alert dialog.
*/
isAlert?: boolean
/**
* Reference to the originating element that triggered the modal,
* used to return focus when the modal is closed.
*/
origin?: { focus: () => void }
/**
* Title displayed at the top of the modal.
*/
title: string
/**
* Optional icon name or path to render next to the title.
*/
icon?: string
/**
* Defines the modal size: 'sm' = small, 'md' = medium, 'lg' = large.
*/
size?: 'sm' | 'md' | 'lg'
/**
* Accessible label (aria-label) for the close button.
*/
closeButtonLabel?: string
/**
* Tooltip (title attribute) shown when hovering over the close button.
*/
closeButtonTitle?: string
}
/**
* Defines valid properties in Dialog component.
*/
export interface DialogProps {
/**
* Title content of the dialog.
*/
header?: string | undefined;
/**
* Footer content of the dialog.
*/
footer?: string | undefined;
/**
* Specifies the visibility of the dialog.
* @defaultValue false
*/
visible?: boolean | undefined;
/**
* Defines if background should be blocked when dialog is displayed.
* @defaultValue false
*/
modal?: boolean | undefined;
/**
* Style of the content section.
*/
contentStyle?: unknown;
/**
* Style class of the content section.
*/
contentClass?: unknown;
/**
* Used to pass all properties of the HTMLDivElement to the overlay Dialog inside the component.
*/
contentProps?: HTMLAttributes | undefined;
/**
* Adds a close icon to the header to hide the dialog.
* @defaultValue true
*/
closable?: boolean | undefined;
/**
* Specifies if clicking the modal background should hide the dialog.
* @defaultValue false
*/
dismissableMask?: boolean | undefined;
/**
* Specifies if pressing escape key should hide the dialog.
* @defaultValue true
*/
closeOnEscape?: boolean | undefined;
/**
* Whether to show the header or not.
* @defaultValue true
*/
showHeader?: boolean | undefined;
/**
* Whether background scroll should be blocked when dialog is visible.
* @defaultValue false
*/
blockScroll?: boolean | undefined;
/**
* Base zIndex value to use in layering.
* @defaultValue 0
*/
baseZIndex?: number | undefined;
/**
* Whether to automatically manage layering.
* @defaultValue true
*/
autoZIndex?: boolean | undefined;
/**
* Position of the dialog.
* @defaultValue center
*/
position?: HintedString<'center' | 'top' | 'bottom' | 'left' | 'right' | 'topleft' | 'topright' | 'bottomleft' | 'bottomright'> | undefined;
/**
* Whether the dialog can be displayed full screen.
* @defaultValue false
*/
maximizable?: boolean | undefined;
/**
* Object literal to define widths per screen size.
*/
breakpoints?: DialogBreakpoints;
/**
* Enables dragging to change the position using header.
* @defaultValue true
*/
draggable?: boolean | undefined;
/**
* Keeps dialog in the viewport when dragging.
* @defaultValue true
*/
keepInViewport?: boolean | undefined;
/**
* Minimum value for the left coordinate of dialog in dragging.
* @defaultValue 0.
*/
minX?: number | undefined;
/**
* Minimum value for the top coordinate of dialog in dragging.
* @defaultValue 0
*/
minY?: number | undefined;
/**
* A valid query selector or an HTMLElement to specify where the dialog gets attached.
* @defaultValue body
*/
appendTo?: HintedString<'body' | 'self'> | undefined | HTMLElement;
/**
* Icon to display in the dialog close button.
*/
closeIcon?: string | undefined;
/**
* Icon to display in the dialog maximize button when dialog is not maximized.
*/
maximizeIcon?: string | undefined;
/**
* Icon to display in the dialog maximize button when dialog is minimized.
*/
minimizeIcon?: string | undefined;
/**
* Used to pass all properties of the ButtonProps to the Button component.
* @type {ButtonProps}
* @defaultValue { severity: 'secondary', rounded: true, text: true }
*/
closeButtonProps?: object | undefined;
/**
* Used to pass all properties of the ButtonProps to the Button component.
* @type {ButtonProps}
* @defaultValue { severity: 'secondary', rounded: true, text: true }
*/
maximizeButtonProps?: object | undefined;
}
/**
* Extended Modal interface that includes configuration for responsive breakpoints.
*/
export default interface IVModal extends Partial<Omit<IModal, 'opened'>>, Partial<DialogProps> {
/**
* Breakpoints used to control responsive modal behavior.
* Compatible with PrimeVue Dialog breakpoints format.
*/
breakpoints?: DialogBreakpoints
}

View File

@ -0,0 +1,68 @@
<script setup lang="ts">
import ConfirmDialog from 'primevue/confirmdialog';
import type { ConfirmDialogProps } from 'primevue/confirmdialog';
import VButtonGroup from '../button/VButtonGroup.vue';
import styles from '@visua/typography.module.css';
const props = withDefaults(defineProps<ConfirmDialogProps>(), {
group: '',
draggable: true,
breakpoints: undefined,
})
</script>
<template>
<ConfirmDialog
:group="props.group"
:draggable="props.draggable"
:breakpoints="props.breakpoints"
role="alert"
>
<template #container="slotProps">
<div class="container">
<div
class='header'
:class="[styles['titles-H6-XXS']]"
:style="('danger' in slotProps.message.acceptProps) ? {color: 'var(--text-default-error)'} : {color: 'var(--text-default-warning)'}"
>
<i :class="slotProps.message.icon" style="font-weight:lighter;"></i>
<span style="width: 100%;">{{ slotProps.message.header }}</span>
</div>
<span>{{ slotProps.message.message }}</span>
<VButtonGroup
:title="slotProps.message.group"
inline-layout-when="always"
size="sm"
align="right"
:buttons="[
{
...slotProps.message.acceptProps,
onclick: slotProps.acceptCallback
},
{
...slotProps.message.rejectProps,
onclick: slotProps.rejectCallback
}
]"
/>
</div>
</template>
</ConfirmDialog>
</template>
<style lang="css" scoped>
.container{
padding: 1rem;
display: flex;
flex-direction: column;
gap: 1rem;
}
.header{
display: flex;
flex-direction: row;
gap: var(--p-dialog-header-gap);
align-items: baseline;
}
</style>

View File

@ -0,0 +1,137 @@
<script setup lang="ts">
import Dialog from 'primevue/dialog';
import type IVModal from './IVModal.type';
import { useId, computed, watch, ref } from 'vue';
import VButtonGroup from '../button/VButtonGroup.vue';
import VButton from '../button/VButton.vue';
import styles from '@visua/typography.module.css';
const props = withDefaults(defineProps<IVModal>(), {
modalId: () => useId(),
actions: () => [],
icon: undefined,
size: 'md',
closeButtonLabel: 'Fermer',
closeButtonTitle: 'Fermer la fenêtre modale',
title: undefined,
visible: false,
isAlert: false,
breakpoints: undefined,
modal: true,
dismissableMask: false,
blockScroll: true,
position: 'center',
maximizable: false,
draggable: true,
showHeader: true,
closeOnEscape: true,
})
const role = computed(() => {
return props.isAlert ? 'alertdialog' : 'dialog'
})
const modalSize = computed(() => {
switch (props.size) {
case 'lg':
return `width: 49.5rem;`
case 'md':
return `width: 36.75rem;`
case 'sm':
return `width: 24rem;`
default:
return `width: 36.75rem;`
}
})
const emit = defineEmits([
'update:visible',
'hide',
'after-hide',
'show',
'maximize',
'unmaximize',
'dragstart',
'dragend'
]);
const localVisible = ref(props.visible);
watch(() => props.visible, (newVal) => {
if(localVisible.value !== newVal){
localVisible.value = newVal;
}
})
watch(localVisible, (newVal) => {
if(props.visible !== newVal){
emit('update:visible', newVal);
}
});
</script>
<template>
<Dialog
:header="props.title"
id="modal"
:modal="props.modal"
:dismissable-mask="props.dismissableMask"
:role="role"
:aria-model="true"
:aria-labelledby="`modal-${props.modalId}-dialog`"
ref="modal"
:style="modalSize"
:breakpoints="props.breakpoints"
:block-scroll="props.blockScroll"
:position="props.position"
:maximizable="props.maximizable"
:draggable="props.draggable"
:show-header="props.showHeader"
:close-on-escape="props.closeOnEscape"
v-model:visible="localVisible"
@update:visible="emit('update:visible', $event)"
@after-hide="emit('after-hide', $event)"
@dragend="emit('dragend', $event)"
@dragstart="emit('dragstart', $event)"
@hide="emit('hide', $event)"
@maximize="emit('maximize', $event)"
@unmaximize="emit('unmaximize', $event)"
@show="emit('show', $event)"
>
<template #header>
<slot name="header" v-if="props.icon !== undefined">
<span :class="[styles['titles-H6-XXS']]">
<i :class="props.icon"></i>
{{ props.title }}
</span>
</slot>
</template>
<slot name="content"/>
<template #footer>
<slot name="footer">
<VButtonGroup
v-if="props.actions?.length"
inline-layout-when="always"
align="right"
:buttons="props.actions"
size="sm"
title="groupe de boutons"
/>
</slot>
</template>
<template #closebutton="{closeCallback}">
<VButton
tertiary
noOutline
size="sm"
icon="ri-close-line"
iconRight
@click="closeCallback"
:label="closeButtonLabel"
:title="closeButtonTitle"
ref="closeBtn"
aria-controls="modal-1"
type="button"
/>
</template>
</Dialog>
</template>

View File

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import ProgressBar from 'primevue/progressbar'; import ProgressBar from 'primevue/progressbar';
import type IVProgressBar from './IVProgressBar.type'; import type IVProgressBar from './IVProgressBar.type.js';
import { computed } from 'vue'; import { computed } from 'vue';
const props = withDefaults(defineProps<IVProgressBar>(), { const props = withDefaults(defineProps<IVProgressBar>(), {

View File

@ -0,0 +1,35 @@
<script setup lang="ts">
import ScrollPanel from 'primevue/scrollpanel';
const props = withDefaults(defineProps<{
step?: number
height: string
width?: string
}>(), {
step: 5,
width: '100%'
});
</script>
<template>
<ScrollPanel
:step="props.step"
class="p-scrollpanel-content"
:style="{width: props.width, maxHeight: props.height}"
>
<slot/>
</ScrollPanel>
</template>
<style lang="css" scoped>
*{
margin: 0px;
padding: 0px;
}
.p-scrollpanel {
height: fit-content;
overflow-y: auto;
box-sizing: content-box;
}
</style>

View File

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import Select from 'primevue/select'; import Select, { type SelectSlots } from 'primevue/select';
import type IVSelect from './IVSelect.type'; import type IVSelect from './IVSelect.type.js';
import { useId, computed, watch, ref } from 'vue'; import { useId, computed, watch, ref } from 'vue';
import VLabel from '../label/VLabel.vue'; import VLabel from '../label/VLabel.vue';
import VHint from '../hint/VHint.vue'; import VHint from '../hint/VHint.vue';
@ -29,9 +29,7 @@ const props = withDefaults(defineProps<IVSelect>(), {
selectionMessage: 'Elements sélectionnés', selectionMessage: 'Elements sélectionnés',
emptySelectionMessage: 'Aucun élément sélectionné', emptySelectionMessage: 'Aucun élément sélectionné',
emptyFilterMessage: 'Aucun résultat trouvé', emptyFilterMessage: 'Aucun résultat trouvé',
emptyMessage: 'Aucune option disponible', emptyMessage: 'Aucune option disponible'
optionTemplate: false,
}) })
const emit = defineEmits([ const emit = defineEmits([
@ -78,20 +76,48 @@ const labelState = computed(() => {
else if(props.errorMessage && !props.successMessage && !props.disabled) return 'error'; else if(props.errorMessage && !props.successMessage && !props.disabled) return 'error';
else return undefined else return undefined
}) })
type VSelectSlots = SelectSlots & {
required?: (props: Record<string, unknown>) => unknown
};
const slots = defineSlots<VSelectSlots>();
const selectSlotKeys = [
'value',
'header',
'footer',
'option',
'optiongroup',
'emptyfilter',
'empty',
'content',
'loader',
'clearicon',
'dropdownicon',
'loadingicon',
'filtericon'
] as const;
const availableSlots = computed(() =>
selectSlotKeys.filter((key) => !!slots[key]).map((key) => [key, slots[key]])
);
</script> </script>
<template> <template>
<div class="main-container"> <div class="main-container">
<VLabel <VLabel
v-if="props.label"
:for="props.selectId" :for="props.selectId"
:label="props.label" :label="props.label"
:required="!props.disabled" :required="props.required && !props.disabled"
:disabled="props.disabled" :disabled="props.disabled"
:type="labelState" :type="labelState"
:hint="props.hint" :hint="props.hint"
> >
<template #required-type v-if="props.required"> <template #required-type v-if="props.required">
<slot name="required-type"/> <slot name="required"/>
</template> </template>
</VLabel> </VLabel>
<Select <Select
@ -140,8 +166,12 @@ const labelState = computed(() => {
} }
]" ]"
> >
<template v-if="props.optionTemplate" #option="{option, selected, index}"> <template
<slot name="option" :option="option" :selected="selected" :index="index"/> v-for="([name]) in availableSlots"
:key="name"
v-slot:[name]="slotProps"
>
<slot :name="name" v-bind="slotProps" />
</template> </template>
</Select> </Select>
<div <div
@ -173,6 +203,8 @@ const labelState = computed(() => {
--p-select-dropdown-color: var(--text-disabled-grey); --p-select-dropdown-color: var(--text-disabled-grey);
} }
.p-select.error, .p-select.success{border-width: var(--large-border-width);}
.p-select.error{ .p-select.error{
--p-select-border-color: var(--border-plain-error); --p-select-border-color: var(--border-plain-error);
--p-select-hover-border-color: var(--border-plain-error); --p-select-hover-border-color: var(--border-plain-error);

View File

@ -0,0 +1,446 @@
<script setup lang="ts">
import DataTable from 'primevue/datatable';
import type { DataTableProps, DataTableSlots } from 'primevue/datatable';
import { useId, ref, watch, computed } from 'vue';
import styles from '@visua/typography.module.css';
export interface IVDataTable extends Partial<Omit<DataTableProps, 'pt' | 'dt' | 'ptOptions' | 'unstyled'>>{
id?: string
title?: string
}
const props = withDefaults(defineProps<IVDataTable>(), {
id: () => useId(),
title: '',
selection: undefined,
paginator: false,
rowsPerPageOptions: undefined,
rows: 0,
value: undefined,
dataKey: undefined,
showGridlines: false,
stripedRows: false,
first: 0,
totalRecords: 0,
pageLinkSize: 5,
paginatorPosition: 'bottom',
paginatorTemplate: undefined,
alwaysShowPaginator:true,
currentPageReportTemplate: '({currentPage} de {totalPages})',
lazy: false,
loading: false,
sortField: undefined,
sortOrder: undefined,
nullSortOrder: 1,
defaultSortOrder: 1,
sortMode: 'single',
removableSort: false,
filters: undefined,
filterDisplay: undefined,
globalFilterFields: undefined,
filterLocale: undefined,
selectionMode: undefined,
compareSelectionBy: 'deepEquals',
metaKeySelection: false,
contextMenu: false,
contextMenuSelection: undefined,
selectAll: undefined,
rowHover: false,
csvSeparator: ',',
exportFilename: 'download',
exportFunction: undefined,
resizableColumns: false,
columnResizeMode: 'fit',
reorderableColumns: false,
expandedRows: undefined,
expandedRowIcon: undefined,
collapsedRowIcon: undefined,
rowGroupMode: undefined,
groupRowsBy: undefined,
expandableRowGroups: false,
expandedRowGroups: undefined,
stateStorage: 'session',
stateKey: undefined,
editMode: undefined,
editingRows: undefined,
rowClass: undefined,
rowStyle: undefined,
scrollable: false,
scrollHeight: undefined,
virtualScrollerOptions: undefined,
frozenValue: undefined,
breakpoint: '960px',
showHeaders: true,
highlightOnSelect: false,
size: undefined,
tableClass: undefined,
tableProps: undefined,
tableStyle: undefined,
filterButtonProps: undefined,
editButtonProps: undefined,
multiSortMeta: undefined,
})
type VDataTableSlots = DataTableSlots & {
default?: (props: Record<string, unknown>) => unknown
};
const slots = defineSlots<VDataTableSlots>();
const dataTableSlotKeys = [
'header',
'footer',
'empty',
'groupheader',
'groupfooter',
'loading',
'expansion',
'loadingicon',
'rowreorderindicatorupicon',
'rowreorderindicatordownicon',
'rowgrouptogglericon',
'paginatorcontainer',
'paginatorstart',
'paginatorend',
'paginatorfirstpagelinkicon',
'paginatorprevpagelinkicon',
'paginatornextpagelinkicon',
'paginatorlastpagelinkicon',
'paginatorrowsperpagedropdownicon',
'paginatorjumptopagedropdownicon'
] as const;
const availableSlots = computed(() =>
dataTableSlotKeys.filter((key) => !!slots[key]).map((key) => [key, slots[key]])
);
const dataTableRef = ref();
defineExpose({
exportCSV: () => dataTableRef.value.exportCSV()
});
const emit = defineEmits([
'update:selection',
'update:rows',
'update:sortField',
'update:sortOrder',
'update:multiSortMeta',
'update:contextMenuSelection',
'update:expandedRows',
'update:expandedRowGroups',
'update:filters',
'update:editingRows',
'update:first',
'page',
'sort',
'row-select',
'row-unselect',
'filter',
'value-change',
'row-click',
'row-dbclick',
'row-contextmenu',
'row-select-all',
'row-unselect-all',
'select-all-change',
'column-resize-end',
'column-reorder',
'row-reorder',
'row-expand',
'row-collapse',
'rowgroup-expand',
'rowgroup-collapse',
'cell-edit-init',
'cell-edit-complete',
'cell-edit-cancel',
'row-edit-init',
'row-edit-save',
'row-edit-cancel',
'state-restore',
'state-save',
]);
const localSelection = ref(props.selection);
watch(() => props.selection, (newVal) => {
if(localSelection.value !== newVal) {
localSelection.value = newVal;
}
});
watch(localSelection, (newVal) => {
if(props.selection !== newVal){
emit('update:selection', newVal);
}
});
const localRows = ref(props.rows);
watch(() => props.rows, (newVal) => {
if(localRows.value !== newVal){
localRows.value = newVal;
}
})
watch(localRows, (newVal) => {
if(props.rows !== newVal){
emit('update:rows', newVal);
}
});
const localFirst = ref(props.first);
watch(() => props.first, (newVal) => {
if(localFirst.value !== newVal){
localFirst.value = newVal;
}
})
watch(localFirst, (newVal) => {
if(props.first !== newVal){
emit('update:first', newVal);
}
});
const localSortField = ref(props.sortField);
watch(() => props.sortField, (newVal) => {
if(localSortField.value !== newVal){
localSortField.value = newVal;
}
})
watch(localSortField, (newVal) => {
if(props.sortField !== newVal){
emit('update:sortField', newVal);
}
});
const localSortOrder = ref(props.sortOrder);
watch(() => props.sortOrder, (newVal) => {
if(localSortOrder.value !== newVal){
localSortOrder.value = newVal;
}
})
watch(localSortOrder, (newVal) => {
if(props.sortOrder !== newVal){
emit('update:sortOrder', newVal);
}
});
const localMultiSortMeta = ref(props.multiSortMeta);
watch(() => props.multiSortMeta, (newVal) => {
if(localMultiSortMeta.value !== newVal){
localMultiSortMeta.value = newVal;
}
})
watch(localMultiSortMeta, (newVal) => {
if(props.multiSortMeta !== newVal){
emit('update:multiSortMeta', newVal);
}
});
const localContextMenuSelection = ref(props.contextMenuSelection);
watch(() => props.contextMenuSelection, (newVal) => {
if(localContextMenuSelection.value !== newVal){
localContextMenuSelection.value = newVal;
}
})
watch(localContextMenuSelection, (newVal) => {
if(props.contextMenuSelection){
emit('update:contextMenuSelection', newVal);
}
});
const localExpandedRows = ref(props.expandedRows);
watch(() => props.expandedRows, (newVal) => {
if(localExpandedRows.value !== newVal){
localExpandedRows.value = newVal;
}
})
watch(localExpandedRows, (newVal) => {
if(props.expandedRows !== newVal){
emit('update:expandedRows', newVal);
}
});
const localExpandedRowGroups = ref(props.expandedRowGroups);
watch(() => props.expandedRowGroups, (newVal) => {
if(localExpandedRowGroups.value !== newVal){
localExpandedRowGroups.value = newVal;
}
})
watch(localExpandedRowGroups, (newVal) => {
if(props.expandedRowGroups !== newVal){
emit('update:expandedRowGroups', newVal);
}
});
const localFilters = ref(props.filters);
watch(() => props.filters, (newVal) => {
if(localFilters.value !== newVal){
localFilters.value = newVal;
}
})
watch(localFilters, (newVal) => {
if(props.filters !== newVal){
emit('update:filters', newVal);
}
});
const localEditingRows = ref(props.editingRows);
watch(() => props.editingRows, (newVal) => {
if(localEditingRows.value = newVal){
localEditingRows.value = newVal;
}
})
watch(localEditingRows, (newVal) => {
if(props.editingRows !== newVal){
emit('update:editingRows', newVal);
}
});
</script>
<template>
<DataTable
:id="`datatable-${props.id}`"
role="table"
ref="dataTableRef"
:value="props.value"
:paginator="props.paginator"
:rowsPerPageOptions="props.rowsPerPageOptions"
:data-key="props.dataKey"
:show-gridlines="props.showGridlines"
:striped-rows="props.stripedRows"
:total-records="props.totalRecords"
:page-link-size="props.pageLinkSize"
:paginator-position="props.paginatorPosition"
:paginator-template="props.paginatorTemplate"
:always-show-paginator="props.alwaysShowPaginator"
:current-page-report-template="props.currentPageReportTemplate"
:lazy="props.lazy"
:loading="props.loading"
:nullSortOrder="props.nullSortOrder"
:defaultSortOrder="props.defaultSortOrder"
:sortMode="props.sortMode"
:removableSort="props.removableSort"
:filterDisplay="props.filterDisplay"
:globalFilterFields="props.globalFilterFields"
:filterLocale="props.filterLocale"
:selectionMode="props.selectionMode"
:compareSelectionBy="props.compareSelectionBy"
:metaKeySelection="props.metaKeySelection"
:contextMenu="props.contextMenu"
:selectAll="props.selectAll"
:rowHover="props.rowHover"
:csvSeparator="props.csvSeparator"
:exportFilename="props.exportFilename"
:exportFunction="props.exportFunction"
:resizableColumns="props.resizableColumns"
:columnResizeMode="props.columnResizeMode"
:reorderableColumns="props.reorderableColumns"
:expandedRowIcon="props.expandedRowIcon"
:collapsedRowIcon="props.collapsedRowIcon"
:rowGroupMode="props.rowGroupMode"
:groupRowsBy="props.groupRowsBy"
:expandableRowGroups="props.expandableRowGroups"
:stateStorage="props.stateStorage"
:stateKey="props.stateKey"
:editMode="props.editMode"
:rowClass="props.rowClass"
:rowStyle="props.rowStyle"
:scrollable="props.scrollable"
:scrollHeight="props.scrollHeight"
:virtualScrollerOptions="props.virtualScrollerOptions"
:frozenValue="props.frozenValue"
:breakpoint="props.breakpoint"
:showHeaders="props.showHeaders"
:highlightOnSelect="props.highlightOnSelect"
:size="props.size"
:tableClass="props.tableClass"
:tableProps="props.tableProps"
:tableStyle="props.tableStyle"
:filterButtonProps="props.filterButtonProps"
:editButtonProps="props.editButtonProps"
style="width: 100%; height: fit-content;"
v-model:selection="localSelection"
v-model:rows="localRows"
v-model:first="localFirst"
v-model:sortField="localSortField"
v-model:sortOrder="localSortOrder"
v-model:filters="localFilters"
v-model:contextMenuSelection="localContextMenuSelection"
v-model:expandedRows="localExpandedRows"
v-model:expandedRowGroups="localExpandedRowGroups"
v-model:editingRows="localEditingRows"
v-model:multi-sort-meta="localMultiSortMeta"
@update:selection="emit('update:selection', $event)"
@update:context-menu-selection="emit('update:contextMenuSelection', $event)"
@update:editing-rows="emit('update:editingRows', $event)"
@update:expanded-row-groups="emit('update:expandedRowGroups', $event)"
@update:filters="emit('update:filters', $event)"
@update:first="emit('update:first', $event)"
@update:multi-sort-meta="emit('update:multiSortMeta', $event)"
@update:rows="emit('update:rows', $event)"
@update:sort-field="emit('update:sortField', $event)"
@update:sort-order="emit('update:sortOrder', $event)"
@page="emit('page', $event)"
@sort="emit('sort', $event)"
@rowSelect="emit('row-select', $event)"
@rowUnselect="emit('row-unselect', $event)"
@filter="emit('filter', $event)"
@valueChange="emit('value-change', $event)"
@rowClick="emit('row-click', $event)"
@rowCollapse="emit('row-collapse', $event)"
@rowContextmenu="emit('row-contextmenu', $event)"
@rowDblclick="emit('row-dbclick', $event)"
@rowEditCancel="emit('row-edit-cancel', $event)"
@rowEditInit="emit('row-edit-init', $event)"
@rowEditSave="emit('row-edit-save', $event)"
@rowExpand="emit('row-expand', $event)"
@rowReorder="emit('row-reorder', $event)"
@rowSelectAll="emit('row-select-all', $event)"
@rowUnselectAll="emit('row-unselect-all', $event)"
@rowgroupCollapse="emit('rowgroup-collapse', $event)"
@rowgroupExpand="emit('rowgroup-expand', $event)"
@columnReorder="emit('column-reorder', $event)"
@columnResizeEnd="emit('column-resize-end', $event)"
@cellEditCancel="emit('cell-edit-cancel', $event)"
@cellEditComplete="emit('cell-edit-complete', $event)"
@cellEditInit="emit('cell-edit-init', $event)"
@stateRestore="emit('state-restore', $event)"
@stateSave="emit('state-save', $event)"
@selectAllChange="emit('select-all-change', $event)"
class="p-datatable"
>
<template
v-for="([name]) in availableSlots"
:key="name"
v-slot:[name]="slotProps"
>
<slot :name="name" v-bind="slotProps" />
</template>
<slot></slot>
<template #empty>
<div class="datatable-empty" :class="[styles['text-body-SM-detail-text-Regular']]">
<i class="ri-database-line database-icon"></i>
<span> Aucune donnée trouvée. </span>
</div>
</template>
</DataTable>
</template>
<style lang="css" scoped>
.datatable-empty{
height: fit-content;
display: flex;
flex-direction: column;
align-items: center;
box-sizing: border-box;
gap: 1.5rem;
margin-top: 1.5rem;
}
.database-icon{
padding: 0px;
margin: 0px;
display: block;
color: var(--border-contrast-grey);
font-size: 5rem;
}
</style>

5
src/env.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
declare module '*.vue' {
import { DefineComponent } from 'vue';
const component: DefineComponent<Record<string, unknown>, Record<string, unknown>, unknown>;
export default component;
}

121
src/index.ts Normal file
View File

@ -0,0 +1,121 @@
/**
* Visua Design System Vue Component Library
*
* This file serves as the entry point for the library, exporting components, utilities,
* and styles that make up the Visua Vue ecosystem.
*
* @module visua-vue
*/
//////////////////////////
// Component Exports //
//////////////////////////
/**
* Accordion components
*/
export { default as VAccordion } from './components/accordion/VAccordion.vue';
export { default as VAccordionChild } from './components/accordion/VAccordionChild.vue';
/**
* Alert and notification components
*/
export { default as VAlert } from './components/alert/VAlert.vue';
export { default as VBadge } from './components/badge/VBadge.vue';
export { default as VMessage } from './components/message/VMessage.vue';
/**
* Button and link components
*/
export { default as VButton } from './components/button/VButton.vue';
export { default as VButtonGroup } from './components/button/VButtonGroup.vue';
export { default as VLink } from './components/button/VLink.vue';
/**
* Form input and control components
*/
export { default as VCheckbox } from './components/checkbox/VCheckbox.vue';
export { default as VInput } from './components/input/VInput.vue';
export { default as VSelect } from './components/select/VSelect.vue';
export { default as VFile } from './components/file/VFile.vue';
export { default as VFileUpload } from './components/file/VFileUpload.vue';
export { default as VLabelErrorProxy } from './components/file/VLabelErrorProxy.vue';
export { default as VHint } from './components/hint/VHint.vue';
export { default as VLabel } from './components/label/VLabel.vue';
/**
* Layout and grouping components
*/
export { default as VDivider } from './components/divider/VDivider.vue';
export { default as VGroup } from './components/group/VGroup.vue';
export { default as VScrollPanel } from './components/scrollpanel/VScrollPanel.vue';
/**
* Navigation and menu components
*/
export { default as VMenuBar } from './components/menu/VMenuBar.vue';
/**
* Modal and overlay components
*/
export { default as VModal } from './components/modal/VModal.vue';
export { default as VConfirmModal } from './components/modal/VConfirmModal.vue';
/**
* Visual feedback components
*/
export { default as VProgressBar } from './components/progressbar/VProgressBar.vue';
export { default as VDataTable } from './components/table/VDataTable.vue';
//////////////////////////////////////
// Composables & Interfaces //
//////////////////////////////////////
/**
* Composable functions (hooks)
*/
export * from './components/composable/index.js';
/**
* Custom TypeScript interfaces
*/
export * from './components/interface/index.js';
//////////////////////
// Global Styles //
//////////////////////
/**
* Global and layout styles
*/
import './assets/style/global.css';
import './assets/style/primevue-configuration.css';
/**
* PrimeVue component-specific style overrides
*/
import './assets/style/primevue-style/accordion.css';
import './assets/style/primevue-style/button.css';
import './assets/style/primevue-style/checkbox.css';
import './assets/style/primevue-style/confirmdialog.css';
import './assets/style/primevue-style/datatable.css';
import './assets/style/primevue-style/dialog.css';
import './assets/style/primevue-style/divider.css';
import './assets/style/primevue-style/fileupload.css';
import './assets/style/primevue-style/form.css';
import './assets/style/primevue-style/iconfield.css';
import './assets/style/primevue-style/input.css';
import './assets/style/primevue-style/list.css';
import './assets/style/primevue-style/menubar.css';
import './assets/style/primevue-style/message.css';
import './assets/style/primevue-style/navigation.css';
import './assets/style/primevue-style/overlay.css';
import './assets/style/primevue-style/paginator.css';
import './assets/style/primevue-style/password.css';
import './assets/style/primevue-style/progressbar.css';
import './assets/style/primevue-style/scrollpanel.css';
import './assets/style/primevue-style/select.css';
import './assets/style/primevue-style/tag.css';
import './assets/style/primevue-style/textarea.css';
import './assets/style/primevue-style/toast.css';
import './assets/style/primevue-style/various.css';

View File

@ -2,9 +2,15 @@ import './assets/main.css'
import { createApp } from 'vue' import { createApp } from 'vue'
import App from './App.vue' import App from './App.vue'
import primeVue from 'primevue/config' import primeVue from 'primevue/config'
import ToastService from 'primevue/toastservice'
import ConfirmationService from 'primevue/confirmationservice'
// import router from '../template/router'
const app = createApp(App) const app = createApp(App)
app.use(primeVue) app.use(primeVue)
app.use(ToastService)
app.use(ConfirmationService)
// app.use(router)
app.mount('#app') app.mount('#app')

60
test/VAlert.spec.ts Normal file
View File

@ -0,0 +1,60 @@
import { mount, flushPromises } from '@vue/test-utils';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import VAlert from '../src/components/alert/VAlert.vue';
import Toast from 'primevue/toast';
import ToastService from 'primevue/toastservice';
vi.mock('primevue/toastservice', () => ({
default: {
install: () => {},
add: vi.fn()
}
}));
describe('VAlert.vue', () => {
let wrapper: ReturnType<typeof mount>;
beforeEach(() => {
const container = document.createElement('div');
container.id = 'app';
document.body.appendChild(container);
wrapper = mount(VAlert, {
attachTo: '#app',
global: {
components: { Toast },
plugins: [ToastService]
},
props: {
position: 'top-right'
}
});
});
// 💡 Helper pour injecter un toast et attendre le rendu
const renderToast = async (payload: { severity: string; summary: string; detail: string; life: number }) => {
wrapper.vm.$toast?.add(payload);
await new Promise(resolve => setTimeout(resolve, 50));
await flushPromises();
};
it('renders the Toast container via teleport', async () => {
await renderToast({
severity: 'success',
summary: 'Test Title',
detail: 'Test description',
life: 3000
});
const toast = document.querySelector('.p-toast');
expect(toast).toBeTruthy();
});
it('responds to "close" and "life-end" events', async () => {
await wrapper.vm.$emit('close', { id: 'test-alert' });
await wrapper.vm.$emit('life-end', { id: 'test-alert' });
expect(wrapper.emitted()).toHaveProperty('close');
expect(wrapper.emitted()).toHaveProperty('life-end');
});
});

45
test/VFileUpload.spec.ts Normal file
View File

@ -0,0 +1,45 @@
import { mount } from '@vue/test-utils';
import { describe, it, expect, vi } from 'vitest';
import VFileUpload from '../src/components/file/VFileUpload.vue'
describe('VFileUpload emits', () => {
it('doit émettre les événements personnalisés', async () => {
// Création des mocks
const onSelect = vi.fn();
const onUpload = vi.fn();
const onError = vi.fn();
const onRemove = vi.fn();
const onClear = vi.fn();
const wrapper = mount(VFileUpload, {
props: {
advanced: true,
onSelect,
onUpload,
onError,
onRemove,
onClear
}
});
// Simule une sélection de fichier
await wrapper.vm.$emit('select', { files: [{ name: 'test.png' }] });
expect(onSelect).toHaveBeenCalledWith({ files: [{ name: 'test.png' }] });
// Simule une erreur
await wrapper.vm.$emit('error', { message: 'Erreur test' });
expect(onError).toHaveBeenCalledWith({ message: 'Erreur test' });
// Simule un upload
await wrapper.vm.$emit('upload', { files: ['fichier1'] });
expect(onUpload).toHaveBeenCalledWith({ files: ['fichier1'] });
// Simule une suppression
await wrapper.vm.$emit('remove', { file: 'test.png' });
expect(onRemove).toHaveBeenCalledWith({ file: 'test.png' });
// Simule un clear
await wrapper.vm.$emit('clear');
expect(onClear).toHaveBeenCalled();
});
});

37
test/VMessage.spec.ts Normal file
View File

@ -0,0 +1,37 @@
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import VMessage from '../src/components/message/VMessage.vue'
describe('VMessage.vue', () => {
it('renders the title', () => {
const wrapper = mount(VMessage, {
props: { title: 'Test Message' }
})
expect(wrapper.text()).toContain('Test Message')
})
it('shows the correct icon class for type "alert"', () => {
const wrapper = mount(VMessage, {
props: { title: 'Alert Message', type: 'alert' }
})
const icon = wrapper.find('i')
expect(icon.classes()).toContain('ri-spam-fill')
})
it('shows the close button if closable is true', () => {
const wrapper = mount(VMessage, {
props: { title: 'Closable Message', closable: true }
})
const button = wrapper.find('button[aria-label="Fermer"]')
expect(button.exists()).toBe(true)
})
it('emits close event when close button is clicked', async () => {
const wrapper = mount(VMessage, {
props: { title: 'Closable Message', closable: true }
})
const button = wrapper.find('button[aria-label="Fermer"]')
await button.trigger('click')
expect(wrapper.emitted()).toHaveProperty('close')
})
})

69
test/VModal.spec.ts Normal file
View File

@ -0,0 +1,69 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { mount } from '@vue/test-utils';
import { nextTick } from 'vue';
import VModal from '../src/components/modal/VModal.vue';
import type IVButton from '../src/components/button/IVButton.type';
describe('VModal.vue', () => {
let actions: IVButton[];
let visible: boolean;
beforeEach(() => {
visible = true;
actions = [
{
label: 'Bouton primaire',
onClick: vi.fn(),
title: 'primaire',
},
{
label: 'Bouton secondaire',
secondary: true,
onClick: vi.fn(),
title: 'secondaire',
},
{
label: 'Bouton tertiaire',
tertiary: true,
onClick: vi.fn(),
title: 'tertiaire',
},
];
});
it('emits update:visible when internal visibility changes', async () => {
const wrapper = mount(VModal, {
props: {
visible: true,
actions,
},
});
// Simulation dun changement interne
await wrapper.vm.$emit('update:visible', false);
await nextTick();
const emitted = wrapper.emitted('update:visible');
expect(emitted).toBeTruthy();
expect(emitted?.[0]).toEqual([false]);
});
it('emits lifecycle events (show/hide)', async () => {
const wrapper = mount(VModal, {
props: {
visible,
actions,
},
});
wrapper.vm.$emit('show');
wrapper.vm.$emit('hide');
await nextTick();
expect(wrapper.emitted('show')).toBeTruthy();
expect(wrapper.emitted('hide')).toBeTruthy();
});
});

View File

@ -1,10 +1,13 @@
{ {
"extends": "@vue/tsconfig/tsconfig.dom.json", "extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue", "test/VButton.spec.ts"], "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"], "exclude": ["src/**/__tests__/*", "src/App.vue", "src/main.ts"],
"compilerOptions": { "compilerOptions": {
"declaration": true,
"emitDeclarationOnly": true,
"outDir": "dist",
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"lib": ["es2022", "dom"],
"paths": { "paths": {
"@/*": ["./src/*"], "@/*": ["./src/*"],
"@visua/*": ["./node_modules/@cellule-financiere-pmo/visua/output/*"] "@visua/*": ["./node_modules/@cellule-financiere-pmo/visua/output/*"]

View File

@ -1,14 +1,20 @@
{ {
"compilerOptions": {
"types": ["vitest"],
"emitDeclarationOnly": true,
"declaration": true,
"outDir": "dist"
},
"files": [], "files": [],
"references": [ "references": [
{
"path": "./tsconfig.app.json"
},
{ {
"path": "./tsconfig.node.json" "path": "./tsconfig.node.json"
}, },
{ {
"path": "./tsconfig.app.json" "path": "./tsconfig.types.json"
} }
], ]
"compilerOptions": {
"types": ["vitest"]
}
} }

22
tsconfig.types.json Normal file
View File

@ -0,0 +1,22 @@
{
"compilerOptions": {
"declaration": true,
"emitDeclarationOnly": true,
"outDir": "./dist",
"baseUrl": ".",
"module": "NodeNext",
"target": "ESNext",
"moduleResolution": "nodenext",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"allowJs": true,
"lib": ["es2022", "dom"],
"paths": {
"@/*": ["src/*"],
"@visua/*": ["node_modules/@cellule-financiere-pmo/visua/output/*"]
}
},
"include": ["src", "src/**/*.vue", "env.d.ts"],
"exclude": ["src/**/__tests__/*", "src/App.vue", "src/main.ts"]
}

View File

@ -1,24 +1,38 @@
// vite.config.ts
import { fileURLToPath, URL } from 'node:url' import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vitest/config' import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools' import vueDevTools from 'vite-plugin-vue-devtools'
import path from 'path' import path from 'path'
// https://vite.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
vue(), vue(),
vueDevTools(), vueDevTools()
], ],
resolve: { resolve: {
alias: { alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)), '@': fileURLToPath(new URL('./src', import.meta.url)),
'@visua': path.resolve(__dirname, './node_modules/@cellule-financiere-pmo/visua/output') '@visua': path.resolve(__dirname, './node_modules/@cellule-financiere-pmo/visua/output'),
},
},
build: {
lib: {
entry: path.resolve(__dirname, 'src/index.ts'),
name: 'VisuaVue',
fileName: (format) => format === 'es' ? 'visua-vue.es.js' : 'visua-vue.umd.cjs',
formats: ['es', 'umd'],
},
rollupOptions: {
external: ['vue', 'primevue', 'vue-router', 'primeicons'],
output: {
globals: {
vue: 'Vue',
primevue: 'PrimeVue',
'vue-router': 'VueRouter',
primeicons: 'primeicons'
},
},
}, },
}, },
test: {
globals: true,
environment: 'jsdom',
include: ['test/**/*.spec.ts'],
}
}) })

20
vitest.config.ts Normal file
View File

@ -0,0 +1,20 @@
// vitest.config.ts
import { fileURLToPath, URL } from 'node:url';
import { defineConfig } from 'vitest/config';
import path from 'path';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
'@visua': path.resolve(__dirname, './node_modules/@cellule-financiere-pmo/visua/output'),
},
},
test: {
globals: true,
environment: 'jsdom',
include: ['test/**/*.spec.ts'],
},
});

4
vue-shim.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
declare module '*.vue' {
const component: unknown;
export default component;
}