From 77a1e0964b5ff234d41621757d48581d844bbd7f Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Tue, 17 Feb 2026 23:40:51 +0100 Subject: [PATCH] refactor(webapp): migrate icons (#9238) --- packages/ui/PROJEKT.md | 66 +- packages/ui/package.json | 10 +- .../src/components/OsButton/OsButton.spec.ts | 18 +- .../ui/src/components/OsButton/OsButton.vue | 18 +- .../src/components/OsIcon/OsIcon.stories.ts | 2 +- .../components/OsIcon/OsIcon.visual.spec.ts | 8 +- packages/ui/src/components/OsIcon/OsIcon.vue | 17 +- .../{all-system-icons.png => all-icons.png} | Bin .../chromium/inherit-color.png | Bin 2226 -> 1069 bytes .../src/ocelot/icons/OcelotIcons.stories.ts | 29 +- .../__screenshots__/chromium/all-icons.png | Bin 9187 -> 77772 bytes packages/ui/src/ocelot/icons/index.spec.ts | 26 +- packages/ui/src/ocelot/icons/index.ts | 25 +- .../ui/src/ocelot}/icons/svgs/angle-up.svg | 0 .../ui/src/ocelot}/icons/svgs/arrow-down.svg | 0 .../ui/src/ocelot}/icons/svgs/arrow-left.svg | 0 .../ui/src/ocelot}/icons/svgs/arrow-right.svg | 0 .../src/ocelot}/icons/svgs/balance-scale.svg | 0 .../ui/src/ocelot}/icons/svgs/ban.svg | 0 .../ui/src/ocelot}/icons/svgs/bars.svg | 0 .../ui/src/ocelot/icons/svgs/bell-slashed.svg | 4 + .../ui/src/ocelot}/icons/svgs/bell.svg | 0 .../ui/src/ocelot}/icons/svgs/bold.svg | 0 packages/ui/src/ocelot/icons/svgs/book.svg | 1 + .../ui/src/ocelot}/icons/svgs/bookmark.svg | 0 .../ui/src/ocelot}/icons/svgs/calendar.svg | 0 .../ui/src/ocelot}/icons/svgs/chat-bubble.svg | 0 .../ui/src/ocelot}/icons/svgs/clock.svg | 0 .../ui/src/ocelot}/icons/svgs/cogs.svg | 0 .../ui/src/ocelot}/icons/svgs/comment.svg | 0 .../ui/src/ocelot/icons/svgs/comments.svg | 1 + .../ui/src/ocelot}/icons/svgs/copy.svg | 0 .../ui/src/ocelot}/icons/svgs/download.svg | 0 .../ui/src/ocelot}/icons/svgs/edit.svg | 0 .../ui/src/ocelot}/icons/svgs/ellipsis-v.svg | 0 .../ocelot}/icons/svgs/exclamation-circle.svg | 0 .../ui/src/ocelot}/icons/svgs/expand.svg | 0 .../ui/src/ocelot}/icons/svgs/eye-slash.svg | 0 .../ui/src/ocelot}/icons/svgs/eye.svg | 0 .../ui/src/ocelot}/icons/svgs/filter.svg | 0 .../ui/src/ocelot}/icons/svgs/flag.svg | 0 .../src/ocelot}/icons/svgs/globe-detailed.svg | 0 .../ui/src/ocelot}/icons/svgs/globe.svg | 0 .../ui/src/ocelot/icons/svgs/hand-pointer.svg | 1 + packages/ui/src/ocelot/icons/svgs/heart-o.svg | 1 + .../ui/src/ocelot}/icons/svgs/home.svg | 0 .../ui/src/ocelot}/icons/svgs/image.svg | 0 .../ui/src/ocelot}/icons/svgs/italic.svg | 0 .../ui/src/ocelot}/icons/svgs/level-down.svg | 0 .../ui/src/ocelot}/icons/svgs/link.svg | 0 .../ui/src/ocelot}/icons/svgs/list-ol.svg | 0 .../ui/src/ocelot}/icons/svgs/list-ul.svg | 0 .../ui/src/ocelot}/icons/svgs/map-marker.svg | 0 .../ocelot}/icons/svgs/microphone-slash.svg | 0 .../ui/src/ocelot}/icons/svgs/microphone.svg | 0 .../ui/src/ocelot}/icons/svgs/minus.svg | 0 .../ui/src/ocelot}/icons/svgs/paragraph.svg | 0 .../ui/src/ocelot}/icons/svgs/pencil.svg | 0 .../ocelot}/icons/svgs/question-circle.svg | 0 .../ui/src/ocelot}/icons/svgs/quote-right.svg | 0 .../ui/src/ocelot}/icons/svgs/save.svg | 0 .../ui/src/ocelot}/icons/svgs/search.svg | 0 .../ui/src/ocelot}/icons/svgs/shield.svg | 0 .../src/ocelot/icons/svgs/shopping-cart.svg | 1 + .../ui/src/ocelot}/icons/svgs/sign-in.svg | 0 .../ui/src/ocelot}/icons/svgs/sign-out.svg | 0 .../ocelot}/icons/svgs/sort-amount-asc.svg | 0 .../ocelot}/icons/svgs/sort-amount-desc.svg | 0 .../ui/src/ocelot}/icons/svgs/trash.svg | 0 .../ui/src/ocelot}/icons/svgs/underline.svg | 0 .../ui/src/ocelot}/icons/svgs/unlink.svg | 0 .../ui/src/ocelot}/icons/svgs/user-plus.svg | 0 .../ui/src/ocelot}/icons/svgs/user-times.svg | 0 .../ui/src/ocelot}/icons/svgs/user.svg | 0 .../ui/src/ocelot}/icons/svgs/users.svg | 0 .../ui/src/ocelot}/icons/svgs/volume-off.svg | 0 .../ui/src/ocelot}/icons/svgs/volume-up.svg | 0 .../ui/src/ocelot}/icons/svgs/warning.svg | 0 packages/ui/src/plugins/vite-svg-icon.ts | 58 +- packages/ui/src/test/setup.ts | 9 + packages/ui/vite.config.ts | 1 + webapp/assets/_new/icons/svgs/.gitkeep | 0 webapp/assets/_new/icons/svgs/angle-down.svg | 5 - .../assets/_new/icons/svgs/bell-slashed.svg | 1 - webapp/assets/_new/icons/svgs/book.svg | 5 - webapp/assets/_new/icons/svgs/bullhorn.svg | 6 +- webapp/assets/_new/icons/svgs/camera.svg | 6 +- webapp/assets/_new/icons/svgs/child.svg | 6 +- webapp/assets/_new/icons/svgs/comments.svg | 5 - webapp/assets/_new/icons/svgs/credit-card.svg | 6 +- webapp/assets/_new/icons/svgs/cubes.svg | 6 +- webapp/assets/_new/icons/svgs/culture.svg | 21 +- webapp/assets/_new/icons/svgs/energy.svg | 15 +- webapp/assets/_new/icons/svgs/finance.svg | 14 +- .../assets/_new/icons/svgs/graduation-cap.svg | 6 +- .../assets/_new/icons/svgs/hand-pointer.svg | 5 - webapp/assets/_new/icons/svgs/health.svg | 8 +- webapp/assets/_new/icons/svgs/heart-o.svg | 5 - .../assets/_new/icons/svgs/location-arrow.svg | 6 +- webapp/assets/_new/icons/svgs/media.svg | 8 +- .../assets/_new/icons/svgs/miscellaneous.svg | 14 +- webapp/assets/_new/icons/svgs/mobility.svg | 12 +- webapp/assets/_new/icons/svgs/movement.svg | 20 +- webapp/assets/_new/icons/svgs/music.svg | 6 +- webapp/assets/_new/icons/svgs/nature.svg | 19 +- webapp/assets/_new/icons/svgs/networking.svg | 23 +- webapp/assets/_new/icons/svgs/peace.svg | 9 +- webapp/assets/_new/icons/svgs/pie-chart.svg | 6 +- webapp/assets/_new/icons/svgs/politics.svg | 13 +- webapp/assets/_new/icons/svgs/psyche.svg | 9 +- webapp/assets/_new/icons/svgs/recycle.svg | 6 +- webapp/assets/_new/icons/svgs/rocket.svg | 6 +- webapp/assets/_new/icons/svgs/science.svg | 26 +- webapp/assets/_new/icons/svgs/share.svg | 6 +- .../assets/_new/icons/svgs/shopping-cart.svg | 5 - .../assets/_new/icons/svgs/spirituality.svg | 14 +- .../assets/_new/icons/svgs/video-camera.svg | 6 +- webapp/assets/styles/main.scss | 2 +- webapp/components/ActionButton.spec.js | 22 +- webapp/components/ActionButton.vue | 8 +- webapp/components/AvatarMenu/AvatarMenu.vue | 28 +- webapp/components/Button/FollowButton.vue | 14 +- webapp/components/Button/JoinLeaveButton.vue | 20 +- .../CategoriesSelect/CategoriesSelect.vue | 10 +- webapp/components/Category/index.spec.js | 10 +- webapp/components/Category/index.vue | 13 +- .../Chat/AddChatRoomByUserSearch.vue | 6 +- webapp/components/Chat/Chat.vue | 8 +- .../ChatNotificationMenu.vue | 6 +- webapp/components/CommentCard/CommentCard.vue | 15 +- webapp/components/CommentList/CommentList.vue | 8 +- .../ComponentSlider/ComponentSlider.spec.js | 5 +- .../ComponentSlider/ComponentSlider.vue | 6 +- webapp/components/ContentMenu/ContentMenu.vue | 53 +- .../ContentMenu/GroupContentMenu.vue | 23 +- .../GroupContentMenu.spec.js.snap | 130 +++- .../ContributionForm/ContributionForm.vue | 20 +- .../DateTimeRange/DateTimeRange.vue | 21 +- webapp/components/DeleteData/DeleteData.vue | 14 +- .../DropdownFilter/DropdownFilter.vue | 6 +- webapp/components/Editor/MenuBar.vue | 31 +- webapp/components/Editor/MenuLegend.vue | 39 +- webapp/components/Embed/EmbedComponent.vue | 4 +- .../Empty/CallToAction/CtaUnblockAuthor.vue | 11 +- .../CtaJoinLeaveGroup.spec.js.snap | 2 +- .../FilterMenu/CategoriesFilter.vue | 11 +- .../components/FilterMenu/EventsByFilter.vue | 8 +- webapp/components/FilterMenu/FilterMenu.vue | 10 +- .../FilterMenu/FilterMenuComponent.vue | 6 +- .../components/FilterMenu/FollowingFilter.vue | 10 +- webapp/components/FilterMenu/HeaderButton.vue | 6 +- .../components/FilterMenu/OrderByFilter.vue | 11 +- .../components/FilterMenu/PostTypeFilter.vue | 10 +- webapp/components/Group/GroupForm.vue | 23 +- webapp/components/Group/GroupTeaser.vue | 9 +- .../HashtagsFilter/HashtagsFilter.vue | 6 +- webapp/components/HeaderMenu/HeaderMenu.vue | 25 +- .../components/InviteButton/InviteButton.vue | 10 +- .../components/LocaleSwitch/LocaleSwitch.vue | 9 +- .../components/LocationInfo/LocationInfo.vue | 9 +- .../__snapshots__/LocationInfo.spec.js.snap | 52 +- .../LocationTeaser/LocationTeaser.vue | 9 +- webapp/components/LoginButton/LoginButton.vue | 11 +- webapp/components/LoginForm/LoginForm.spec.js | 6 +- webapp/components/LoginForm/LoginForm.vue | 15 +- webapp/components/Modal/ConfirmModal.vue | 7 +- webapp/components/Modal/DeleteUserModal.vue | 17 +- webapp/components/Modal/DisableModal.vue | 10 +- webapp/components/Modal/ReportModal.vue | 22 +- .../NotificationMenu/NotificationMenu.vue | 8 +- .../NotificationsTable/NotificationsTable.vue | 16 +- webapp/components/ObserveButton.vue | 6 +- webapp/components/PostTeaser/PostTeaser.vue | 12 +- .../Registration/RegistrationSlideCreate.vue | 16 +- .../components/ReleaseModal/ReleaseModal.vue | 10 +- webapp/components/Select/LocationSelect.vue | 4 +- webapp/components/ShoutButton.vue | 6 +- .../ShowPassword/ShowPassword.spec.js | 13 +- .../components/ShowPassword/ShowPassword.vue | 13 +- webapp/components/Uploader/AvatarUploader.vue | 8 +- webapp/components/Uploader/ImageUploader.vue | 14 +- .../UserTeaser/UserTeaserNonAnonymous.vue | 16 +- .../__snapshots__/UserTeaser.spec.js.snap | 52 +- .../__snapshots__/ActionButton.spec.js.snap | 26 +- .../__snapshots__/ObserveButton.spec.js.snap | 26 +- .../__snapshots__/ShoutButton.spec.js.snap | 52 +- .../features/Invitations/CreateInvitation.vue | 8 +- .../_new/features/Invitations/Invitation.vue | 13 +- .../CreateInvitation.spec.js.snap | 4 +- .../__snapshots__/Invitation.spec.js.snap | 52 +- .../__snapshots__/InvitationList.spec.js.snap | 56 +- .../MySomethingList/MySomethingList.spec.js | 7 +- .../MySomethingList/MySomethingList.vue | 14 +- .../_new/generic/BaseIcon/BaseIcon.story.js | 53 -- .../_new/generic/BaseIcon/BaseIcon.vue | 67 -- .../generic/CounterIcon/CounterIcon.spec.js | 15 +- .../_new/generic/CounterIcon/CounterIcon.vue | 7 +- .../LabeledButton/LabeledButton.story.js | 4 +- .../generic/LabeledButton/LabeledButton.vue | 8 +- .../PaginationButtons/PaginationButtons.vue | 12 +- .../ProfileAvatar/ProfileAvatar.spec.js | 8 +- .../generic/ProfileAvatar/ProfileAvatar.vue | 14 +- .../features/ReportList/ReportList.vue | 8 +- .../features/ReportRow/ReportRow.spec.js | 23 +- .../features/ReportRow/ReportRow.vue | 23 +- .../generic/SearchPost/SearchPost.vue | 14 +- .../SearchableInput/SearchableInput.vue | 6 +- webapp/components/utils/PostHelpers.js | 5 +- webapp/jest.config.js | 2 + webapp/pages/admin/categories.vue | 10 +- .../users/__snapshots__/index.spec.js.snap | 104 ++- webapp/pages/admin/users/index.vue | 11 +- .../_id/__snapshots__/_slug.spec.js.snap | 676 ++++++++++++------ webapp/pages/groups/_id/_slug.vue | 9 +- .../_id/__snapshots__/invites.spec.js.snap | 28 +- webapp/pages/groups/index.vue | 6 +- webapp/pages/index.vue | 10 +- webapp/pages/post/_id/_slug/index.vue | 9 +- .../_id/__snapshots__/_slug.spec.js.snap | 296 ++++++-- webapp/pages/profile/_id/_slug.vue | 9 +- webapp/pages/settings/blocked-users.vue | 9 +- webapp/pages/settings/data-download.vue | 9 +- webapp/pages/settings/index.vue | 6 +- webapp/pages/settings/muted-users.vue | 9 +- .../settings/my-email-address/enter-nonce.vue | 6 +- .../pages/settings/my-email-address/index.vue | 6 +- webapp/pages/settings/my-social-media.vue | 15 +- webapp/pages/terms-and-conditions-confirm.vue | 12 +- webapp/test/__mocks__/iconRegistry.js | 25 + webapp/utils/iconRegistry.js | 29 + 230 files changed, 2217 insertions(+), 1275 deletions(-) rename packages/ui/src/components/OsIcon/__screenshots__/chromium/{all-system-icons.png => all-icons.png} (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/angle-up.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/arrow-down.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/arrow-left.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/arrow-right.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/balance-scale.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/ban.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/bars.svg (100%) create mode 100644 packages/ui/src/ocelot/icons/svgs/bell-slashed.svg rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/bell.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/bold.svg (100%) create mode 100644 packages/ui/src/ocelot/icons/svgs/book.svg rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/bookmark.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/calendar.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/chat-bubble.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/clock.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/cogs.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/comment.svg (100%) create mode 100755 packages/ui/src/ocelot/icons/svgs/comments.svg rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/copy.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/download.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/edit.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/ellipsis-v.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/exclamation-circle.svg (100%) mode change 100755 => 100644 rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/expand.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/eye-slash.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/eye.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/filter.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/flag.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/globe-detailed.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/globe.svg (100%) create mode 100644 packages/ui/src/ocelot/icons/svgs/hand-pointer.svg create mode 100755 packages/ui/src/ocelot/icons/svgs/heart-o.svg rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/home.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/image.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/italic.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/level-down.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/link.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/list-ol.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/list-ul.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/map-marker.svg (100%) mode change 100755 => 100644 rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/microphone-slash.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/microphone.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/minus.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/paragraph.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/pencil.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/question-circle.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/quote-right.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/save.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/search.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/shield.svg (100%) create mode 100755 packages/ui/src/ocelot/icons/svgs/shopping-cart.svg rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/sign-in.svg (100%) mode change 100755 => 100644 rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/sign-out.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/sort-amount-asc.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/sort-amount-desc.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/trash.svg (100%) mode change 100755 => 100644 rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/underline.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/unlink.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/user-plus.svg (100%) mode change 100755 => 100644 rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/user-times.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/user.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/users.svg (100%) mode change 100755 => 100644 rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/volume-off.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/volume-up.svg (100%) rename {webapp/assets/_new => packages/ui/src/ocelot}/icons/svgs/warning.svg (100%) mode change 100755 => 100644 create mode 100644 packages/ui/src/test/setup.ts create mode 100644 webapp/assets/_new/icons/svgs/.gitkeep delete mode 100755 webapp/assets/_new/icons/svgs/angle-down.svg delete mode 100644 webapp/assets/_new/icons/svgs/bell-slashed.svg delete mode 100644 webapp/assets/_new/icons/svgs/book.svg delete mode 100755 webapp/assets/_new/icons/svgs/comments.svg delete mode 100644 webapp/assets/_new/icons/svgs/hand-pointer.svg delete mode 100755 webapp/assets/_new/icons/svgs/heart-o.svg delete mode 100755 webapp/assets/_new/icons/svgs/shopping-cart.svg delete mode 100644 webapp/components/_new/generic/BaseIcon/BaseIcon.story.js delete mode 100644 webapp/components/_new/generic/BaseIcon/BaseIcon.vue create mode 100644 webapp/test/__mocks__/iconRegistry.js create mode 100644 webapp/utils/iconRegistry.js diff --git a/packages/ui/PROJEKT.md b/packages/ui/PROJEKT.md index f3a992e48..874b40636 100644 --- a/packages/ui/PROJEKT.md +++ b/packages/ui/PROJEKT.md @@ -81,10 +81,10 @@ Phase 0: ██████████ 100% (6/6 Aufgaben) ✅ Phase 1: ██████████ 100% (6/6 Aufgaben) ✅ Phase 2: ██████████ 100% (26/26 Aufgaben) ✅ Phase 3: ██████████ 100% (24/24 Aufgaben) ✅ - Webapp-Integration komplett -Phase 4: ██░░░░░░░░ 18% (3/17 Aufgaben) - OsButton ✅, OsIcon ✅, System-Icons ✅ +Phase 4: ██░░░░░░░░ 24% (4/17 Aufgaben) - OsButton ✅, OsIcon ✅, System-Icons ✅, BaseIcon→OsIcon Migration ✅ Phase 5: ░░░░░░░░░░ 0% (0/7 Aufgaben) ─────────────────────────────────────── -Gesamt: ████████░░ 76% (65/86 Aufgaben) +Gesamt: ████████░░ 77% (66/86 Aufgaben) ``` ### Katalogisierung (Details in KATALOG.md) @@ -131,31 +131,53 @@ System-Icons: └─ plus.svg (Plus/Add) Ocelot-Icons (separates Entry-Point): -└─ angle-down.svg (Dropdown-Pfeil) +└─ 82 Icons (Feature-Icons + Kategorie-Icons aus Webapp migriert) + +BaseIcon → OsIcon Webapp-Migration: ✅ +├─ 131 in 70+ Dateien → +├─ 82 SVGs in ocelot/icons/svgs/ (inkl. 17 Kategorie-Icons) +├─ vite-svg-icon Plugin erweitert (rect, circle, polygon, polyline, ellipse, line) +├─ Kategorie-Icons: DB-String → toCamelCase() → ocelotIcons Lookup +├─ Jest Mocks: @ocelot-social/ui/ocelot für ocelotIcons in Tests +├─ Tests aktualisiert: 911/939 Tests bestanden (3 pre-existing failures) +└─ 0 base-icon/BaseIcon Referenzen verbleibend ``` --- ## Aktueller Stand -**Letzte Aktualisierung:** 2026-02-15 (Session 21) +**Letzte Aktualisierung:** 2026-02-15 (Session 22) -**Aktuelle Phase:** Phase 4 - OsIcon ✅ implementiert, System-Icons eingerichtet +**Aktuelle Phase:** Phase 4 - OsIcon ✅, BaseIcon → OsIcon Webapp-Migration ✅ -**Zuletzt abgeschlossen (Session 21 - OsIcon Komponente, System-Icons, Ocelot-Umbenennung):** +**Zuletzt abgeschlossen (Session 22 - BaseIcon → OsIcon Webapp-Migration):** +- [x] 131 `` Nutzungen in 70+ Dateien → `` migriert +- [x] 82 Ocelot-Icons in `packages/ui/src/ocelot/icons/svgs/` (von 1 auf 82) +- [x] 17 Kategorie-Icons aus Webapp kopiert (networking, energy, psyche, movement, finance, child, mobility, shopping-cart, peace, politics, nature, science, health, media, spirituality, culture, miscellaneous) +- [x] vite-svg-icon Plugin erweitert: unterstützt ``, ``, ``, ``, ``, `` (war path-only) +- [x] Alle neuen SVGs auf Single-Line minifiziert (Multiline brach JS-String-Literale) +- [x] Kategorie-Icons: DB-String → `toCamelCase()` → `ocelotIcons[key]` Lookup (Category/index.vue, CategoriesFilter.vue, CategoriesSelect.vue, admin/categories.vue) +- [x] `created() { this.icons = ocelotIcons }` Pattern in allen Komponenten (non-reactive) +- [x] MenuLegend.vue: `legendItems` von `data()` → `computed` (data() läuft vor created(), this.icons undefined) +- [x] HeaderMenu.vue: Map-Button Icon-Größe korrigiert (`size="xl"` + negative Margin) +- [x] ShowPassword.vue: `:data-test="iconName"` entfernt (Icon ist jetzt Render-Function, kein String) +- [x] Jest-Tests aktualisiert: OsIcon + ocelotIcons statt BaseIcon + String-Namen + - Category/index.spec.js, ProfileAvatar.spec.js, CounterIcon.spec.js, ReportRow.spec.js + - ActionButton.spec.js, ComponentSlider.spec.js, ShowPassword.spec.js, LoginForm.spec.js +- [x] 8 stale Snapshot-Dateien gelöscht +- [x] Jest Mock: `test/__mocks__/@ocelot-social/ui/ocelot.js` für ocelotIcons in Tests +- [x] CSS: `.base-icon` → `.os-icon` in main.scss und Category/index.vue +- [x] 0 `base-icon`/`BaseIcon` Referenzen verbleibend in gesamter Webapp +- [x] 911/939 Tests bestanden (3 pre-existing Jest worker crashes) + +**Zuvor abgeschlossen (Session 21 - OsIcon Komponente, System-Icons, Ocelot-Umbenennung):** - [x] OsIcon Komponente implementiert (name, icon, size Props; Vue 2/3 via vue-demi h()) - [x] System-Icons: check, close, plus (SVG, viewBox 0 0 32 32, stroke-basiert) - [x] Custom vite-svg-icon Plugin: SVG → Vue Render-Function via `?icon` Query -- [x] Icon-Größen: xs(0.75em), sm(0.875em), md(1.2em), lg(1.5em), xl(2em), 2xl(2.5em) -- [x] A11y: decorative (aria-hidden, default) / semantic (role="img" + aria-label) -- [x] fill-current für Farbvererbung vom Parent -- [x] OsButton nutzt OsIcon statt inline SVG für Icon-Rendering - [x] Ocelot-Icons: separates Entry-Point (ocelot.mjs) mit dynamischem Loading via import.meta.glob - [x] `src/webapp/` → `src/ocelot/` umbenannt (konsistentes Naming) -- [x] check-completeness erweitert: unterstützt ocelot/ Verzeichnis -- [x] OsIcon: 211 Zeilen Unit-Tests, Visual Tests mit checkA11y(), Keyboard A11y - [x] 100% Test-Coverage für OsIcon -- [x] OsButton Stories bereinigt (OsIcon statt Inline-SVGs) **Zuvor abgeschlossen (Session 20 - `as`-Prop + nuxt-link Migration):** - [x] OsButton: `as` Prop implementiert (polymorphe Komponente: `button`, `a`, `nuxt-link`, `router-link`, Custom-Komponenten) @@ -200,8 +222,7 @@ Ocelot-Icons (separates Entry-Point): - [ ] OsSpinner Komponente (vereint DsSpinner + LoadingSpinner) - [ ] OsCard Komponente (vereint DsCard + BaseCard) - [ ] Weitere Tier 1 Komponenten -- [ ] BaseIcon → OsIcon Webapp-Migration (131 Nutzungen) -- [ ] Snapshots/Tests aktualisieren +- [ ] Browser-Fehler untersuchen: `TypeError: Cannot read properties of undefined (reading 'heartO')` (ocelotIcons undefined im Browser trotz korrekter Webpack-Aliase) **Manuelle Setup-Aufgaben (außerhalb Code):** - [ ] `NPM_TOKEN` als GitHub Secret einrichten (für npm publish in ui-release.yml) @@ -475,6 +496,7 @@ Jeder migrierte Button muss manuell geprüft werden: Normal, Hover, Focus, Activ **Infrastruktur** - [x] System-Icons einrichten ✅ vite-svg-icon Plugin, 3 System-Icons, Ocelot-Icons Entry-Point +- [x] BaseIcon → OsIcon Webapp-Migration ✅ 131 Nutzungen, 82 Ocelot-Icons, 0 BaseIcon verbleibend - [ ] CI docs-check Workflow (JSDoc-Coverage, README-Aktualität) ### Phase 5: Finalisierung @@ -768,7 +790,7 @@ import { ocelotIcons } from '@ocelot-social/ui/ocelot' | Styleguide (_all) | 616 | Nicht übernehmen (FontAwesome 4 komplett) | | Webapp (svgs) | 238 | Feature-Icons, bleiben in Webapp | | **Library (system)** | **3** | ✅ check, close, plus | -| **Ocelot-Icons** | **1** | ✅ angle-down (separates Entry-Point) | +| **Ocelot-Icons** | **82** | ✅ Feature-Icons + Kategorie-Icons (separates Entry-Point) | --- @@ -1543,6 +1565,18 @@ Bei der Migration werden: | 2026-02-15 | **OsButton Stories** | Bereinigt: Inline-SVG-Komponenten durch OsIcon ersetzt; WithAriaLabel-Story entfernt; InheritColor-Story vereinfacht | | 2026-02-15 | **check-completeness** | Erweitert für ocelot/ Verzeichnis; unterstützt OsIcon-Patterns | | 2026-02-15 | **svg-icon.d.ts** | TypeScript-Deklaration für `?icon` Import-Query (Component-Typ) | +| 2026-02-15 | **BaseIcon → OsIcon Migration** | 131 `` in 70+ Dateien → ``, 0 BaseIcon verbleibend | +| 2026-02-15 | **82 Ocelot-Icons** | Von 1 auf 82 Icons: Feature-Icons + 17 Kategorie-Icons aus Webapp kopiert | +| 2026-02-15 | **vite-svg-icon erweitert** | Unterstützt jetzt ``, ``, ``, ``, ``, `` (war path-only) | +| 2026-02-15 | **SVG-Minifizierung** | Alle 21 neuen SVGs auf Single-Line minifiziert (Multiline brach JS-String-Literale im Plugin) | +| 2026-02-15 | **Kategorie-Icons** | DB-String → `toCamelCase()` → `ocelotIcons[key]` Lookup in Category, CategoriesFilter, CategoriesSelect, admin/categories | +| 2026-02-15 | **MenuLegend.vue Fix** | `legendItems` von `data()` → `computed` (`data()` läuft vor `created()`, `this.icons` undefined) | +| 2026-02-15 | **HeaderMenu.vue Fix** | Map-Button Icon-Größe: `size="xl"` + negative Margin (OsIcon md=1.2em vs BaseIcon --large=2.2em) | +| 2026-02-15 | **ShowPassword.vue Fix** | `:data-test="iconName"` entfernt (Icon ist jetzt Render-Function statt String) | +| 2026-02-15 | **Test-Updates (8 Specs)** | Category, ProfileAvatar, CounterIcon, ReportRow, ActionButton, ComponentSlider, ShowPassword, LoginForm: BaseIcon → OsIcon + ocelotIcons | +| 2026-02-15 | **8 Snapshots gelöscht** | Stale Snapshot-Dateien entfernt nach BaseIcon → OsIcon Migration | +| 2026-02-15 | **CSS Migration** | `.base-icon` → `.os-icon` in main.scss und Category/index.vue | +| 2026-02-15 | **Jest Mock ocelot** | `test/__mocks__/@ocelot-social/ui/ocelot.js` für ocelotIcons in Jest-Umgebung | --- diff --git a/packages/ui/package.json b/packages/ui/package.json index 452861fa6..140724e9f 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -144,21 +144,23 @@ "size-limit": [ { "path": "dist/index.mjs", - "limit": "16 kB", + "limit": "15 kB", "brotli": true }, { "path": "dist/tailwind.preset.mjs", - "limit": "2 kB" + "limit": "1 kB", + "brotli": true }, { "path": "dist/ocelot.mjs", - "limit": "5 kB", + "limit": "45 kB", "brotli": true }, { "path": "dist/style.css", - "limit": "10 kB" + "limit": "5 kB", + "brotli": true } ] } diff --git a/packages/ui/src/components/OsButton/OsButton.spec.ts b/packages/ui/src/components/OsButton/OsButton.spec.ts index f4f4fe335..d8e983241 100644 --- a/packages/ui/src/components/OsButton/OsButton.spec.ts +++ b/packages/ui/src/components/OsButton/OsButton.spec.ts @@ -1,6 +1,6 @@ import { mount } from '@vue/test-utils' import { describe, expect, it } from 'vitest' -import { defineComponent, h } from 'vue-demi' +import { defineComponent, h, markRaw } from 'vue-demi' import OsButton from './OsButton.vue' @@ -570,16 +570,18 @@ describe('osButton', () => { }) it('renders a component passed as as', () => { - const FakeLink = defineComponent({ - props: { to: { type: String, default: undefined } }, - setup(props, { slots }) { - return () => h('a', { href: props.to }, slots.default?.()) - }, - }) + const FakeLink = markRaw( + defineComponent({ + props: { to: { type: String, default: undefined } }, + setup(props, { slots }) { + return () => h('a', { href: props.to }, slots.default?.()) + }, + }), + ) const wrapper = mount(OsButton, { props: { as: FakeLink }, attrs: { to: '/groups' }, - slots: { default: 'Groups' }, + slots: { default: () => 'Groups' }, }) expect((wrapper.element as HTMLElement).tagName).toBe('A') diff --git a/packages/ui/src/components/OsButton/OsButton.vue b/packages/ui/src/components/OsButton/OsButton.vue index f552d03bc..8a47072a5 100644 --- a/packages/ui/src/components/OsButton/OsButton.vue +++ b/packages/ui/src/components/OsButton/OsButton.vue @@ -237,15 +237,15 @@ /* v8 ignore stop */ const { class: attrClass, ...restAttrs } = attrs as Record - return h( - tag, - { - ...buttonData, - class: cn(buttonClass, attrClass || ''), - ...restAttrs, - }, - children, - ) + const nodeProps = { + ...buttonData, + class: cn(buttonClass, attrClass || ''), + ...restAttrs, + } + // Components expect slot functions; HTML elements accept VNode arrays + return typeof tag === 'string' + ? h(tag, nodeProps, children) + : h(tag, nodeProps, { default: () => children }) } }, }) diff --git a/packages/ui/src/components/OsIcon/OsIcon.stories.ts b/packages/ui/src/components/OsIcon/OsIcon.stories.ts index 30e3a0051..962dcdcf6 100644 --- a/packages/ui/src/components/OsIcon/OsIcon.stories.ts +++ b/packages/ui/src/components/OsIcon/OsIcon.stories.ts @@ -62,7 +62,7 @@ export const Playground: StoryObj = { }), } -export const AllSystemIcons: Story = { +export const AllIcons: Story = { render: () => ({ components: { OsIcon }, setup() { diff --git a/packages/ui/src/components/OsIcon/OsIcon.visual.spec.ts b/packages/ui/src/components/OsIcon/OsIcon.visual.spec.ts index 54727b8ea..f1d141a23 100644 --- a/packages/ui/src/components/OsIcon/OsIcon.visual.spec.ts +++ b/packages/ui/src/components/OsIcon/OsIcon.visual.spec.ts @@ -32,7 +32,7 @@ async function checkA11y(page: Page) { test.describe('OsIcon keyboard accessibility', () => { test('decorative icons are not focusable', async ({ page }) => { - await page.goto(`${STORY_URL}--all-system-icons&viewMode=story`) + await page.goto(`${STORY_URL}--all-icons&viewMode=story`) const root = page.locator(STORY_ROOT) await root.waitFor() @@ -50,13 +50,13 @@ test.describe('OsIcon keyboard accessibility', () => { }) test.describe('OsIcon visual regression', () => { - test('all system icons', async ({ page }) => { - await page.goto(`${STORY_URL}--all-system-icons&viewMode=story`) + test('all icons', async ({ page }) => { + await page.goto(`${STORY_URL}--all-icons&viewMode=story`) const root = page.locator(STORY_ROOT) await root.waitFor() await waitForFonts(page) - await expect(root.locator('.grid')).toHaveScreenshot('all-system-icons.png') + await expect(root.locator('.grid')).toHaveScreenshot('all-icons.png') await checkA11y(page) }) diff --git a/packages/ui/src/components/OsIcon/OsIcon.vue b/packages/ui/src/components/OsIcon/OsIcon.vue index ba39b3074..9fe02c059 100644 --- a/packages/ui/src/components/OsIcon/OsIcon.vue +++ b/packages/ui/src/components/OsIcon/OsIcon.vue @@ -49,12 +49,19 @@ const sizeClass = ICON_SIZES[props.size] // Vue 2's h() cannot handle plain arrow functions as components (only - // constructor functions or option objects). SYSTEM_ICONS entries are - // arrow functions that return VNodes, so call them directly. + // constructor functions or option objects). Icon render functions are + // arrow functions that accept optional (h, isVue2) and return VNodes. // eslint-disable-next-line @typescript-eslint/no-explicit-any const isRenderFn = typeof iconComponent === 'function' && !(iconComponent as any).cid + // In Vue 2, pass $createElement (bound to this instance) so icons avoid + // the globally-imported h() which requires currentInstance in Vue 2.7. + const createElement = /* v8 ignore next -- Vue 2 only */ isVue2 + ? // eslint-disable-next-line @typescript-eslint/no-explicit-any + (instance?.proxy as any)?.$createElement + : h + const iconVNode = isRenderFn - ? (iconComponent as () => ReturnType)() + ? (iconComponent as (...args: unknown[]) => ReturnType)(createElement, isVue2) : h(iconComponent) /* v8 ignore start -- Vue 2 branch tested in webapp Jest tests */ @@ -75,7 +82,7 @@ { class: [ cn( - 'os-icon inline-flex items-center shrink-0', + 'os-icon inline-flex items-center align-bottom shrink-0', sizeClass, '[&>svg]:h-full [&>svg]:w-auto [&>svg]:fill-current', ), @@ -103,7 +110,7 @@ 'span', { class: cn( - 'os-icon inline-flex items-center shrink-0', + 'os-icon inline-flex items-center align-bottom shrink-0', sizeClass, '[&>svg]:h-full [&>svg]:w-auto [&>svg]:fill-current', attrClass as ClassValue, diff --git a/packages/ui/src/components/OsIcon/__screenshots__/chromium/all-system-icons.png b/packages/ui/src/components/OsIcon/__screenshots__/chromium/all-icons.png similarity index 100% rename from packages/ui/src/components/OsIcon/__screenshots__/chromium/all-system-icons.png rename to packages/ui/src/components/OsIcon/__screenshots__/chromium/all-icons.png diff --git a/packages/ui/src/components/OsIcon/__screenshots__/chromium/inherit-color.png b/packages/ui/src/components/OsIcon/__screenshots__/chromium/inherit-color.png index 4058719bf3d7856fa6d8e10f917d0d01e71f692c..6c480e7b4e4560547bfedc01667e4e5a72e2e660 100644 GIT binary patch literal 1069 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 ({ components: { OsIcon }, setup() { - return { systemEntries, ocelotEntries } + return { allEntries } }, template: ` -
-
-

Library Icons

-
-
- - {{ name }} -
-
-
-
-

Ocelot Icons

-
-
- - {{ name }} -
+
+
+
+ + {{ name }}
diff --git a/packages/ui/src/ocelot/icons/__screenshots__/chromium/all-icons.png b/packages/ui/src/ocelot/icons/__screenshots__/chromium/all-icons.png index 976455790af82626b317e958089bab763a9cd5ad..a751e0d959f0f6813c421b4fb87c7b3f1813c54c 100644 GIT binary patch literal 77772 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%0A^{{9xQo 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 literal 9187 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 diff --git a/packages/ui/src/ocelot/icons/index.spec.ts b/packages/ui/src/ocelot/icons/index.spec.ts index 167aec430..36febfc11 100644 --- a/packages/ui/src/ocelot/icons/index.spec.ts +++ b/packages/ui/src/ocelot/icons/index.spec.ts @@ -1,13 +1,12 @@ import { mount } from '@vue/test-utils' import { describe, expect, expectTypeOf, it } from 'vitest' -import { h } from 'vue' import OsIcon from '#src/components/OsIcon/OsIcon.vue' import { ocelotIcons } from './index' describe('ocelot icons', () => { - const IconAngleDown = ocelotIcons.IconAngleDown + const angleDown = ocelotIcons.angleDown describe('exports', () => { it('exports ocelotIcons as a record of functions', () => { @@ -17,20 +16,21 @@ describe('ocelot icons', () => { }) it('auto-discovers all SVGs in svgs/ directory', () => { - expect(ocelotIcons).toHaveProperty('IconAngleDown') + expect(ocelotIcons).toHaveProperty('angleDown') + expect(angleDown).toBeTypeOf('function') + }) - expectTypeOf(IconAngleDown).toBeFunction() + it('includes system icons (check, close, plus)', () => { + expect(ocelotIcons).toHaveProperty('check') + expect(ocelotIcons).toHaveProperty('close') + expect(ocelotIcons).toHaveProperty('plus') }) }) - describe('iconAngleDown', () => { + describe('angleDown', () => { it('renders an SVG with correct viewBox', () => { - const vnode = IconAngleDown() - - expect(vnode).toBeDefined() - - const wrapper = mount({ - render: () => h('div', [IconAngleDown()]), + const wrapper = mount(OsIcon, { + props: { icon: angleDown }, }) const svg = wrapper.find('svg') @@ -40,7 +40,7 @@ describe('ocelot icons', () => { it('works with OsIcon :icon prop', () => { const wrapper = mount(OsIcon, { - props: { icon: IconAngleDown }, + props: { icon: angleDown }, }) expect(wrapper.find('.os-icon').exists()).toBe(true) @@ -52,7 +52,7 @@ describe('ocelot icons', () => { describe('keyboard accessibility', () => { it('icon via :icon prop is not focusable (decorative element)', () => { const wrapper = mount(OsIcon, { - props: { icon: IconAngleDown }, + props: { icon: angleDown }, }) expect(wrapper.attributes('tabindex')).toBeUndefined() diff --git a/packages/ui/src/ocelot/icons/index.ts b/packages/ui/src/ocelot/icons/index.ts index cfaaf16e9..d484848be 100644 --- a/packages/ui/src/ocelot/icons/index.ts +++ b/packages/ui/src/ocelot/icons/index.ts @@ -1,24 +1,19 @@ -import type { VNode } from 'vue-demi' +import { SYSTEM_ICONS } from '#src/components/OsIcon/icons' -const modules = import.meta.glob<() => VNode>('./svgs/*.svg', { +import type { Component } from 'vue-demi' + +const modules = import.meta.glob('./svgs/*.svg', { query: '?icon', eager: true, import: 'default', }) function toName(path: string): string { - return ( - 'Icon' + - path - .replace('./svgs/', '') - .replace('.svg', '') - .split('-') - .filter(Boolean) - .map((s) => s[0].toUpperCase() + s.slice(1)) - .join('') - ) + const parts = path.replace('./svgs/', '').replace('.svg', '').split('-').filter(Boolean) + return parts.map((s, i) => (i === 0 ? s : s[0].toUpperCase() + s.slice(1))).join('') } -export const ocelotIcons: Record VNode> = Object.fromEntries( - Object.entries(modules).map(([path, icon]) => [toName(path), icon]), -) +export const ocelotIcons: Record = { + ...SYSTEM_ICONS, + ...Object.fromEntries(Object.entries(modules).map(([path, icon]) => [toName(path), icon])), +} diff --git a/webapp/assets/_new/icons/svgs/angle-up.svg b/packages/ui/src/ocelot/icons/svgs/angle-up.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/angle-up.svg rename to packages/ui/src/ocelot/icons/svgs/angle-up.svg diff --git a/webapp/assets/_new/icons/svgs/arrow-down.svg b/packages/ui/src/ocelot/icons/svgs/arrow-down.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/arrow-down.svg rename to packages/ui/src/ocelot/icons/svgs/arrow-down.svg diff --git a/webapp/assets/_new/icons/svgs/arrow-left.svg b/packages/ui/src/ocelot/icons/svgs/arrow-left.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/arrow-left.svg rename to packages/ui/src/ocelot/icons/svgs/arrow-left.svg diff --git a/webapp/assets/_new/icons/svgs/arrow-right.svg b/packages/ui/src/ocelot/icons/svgs/arrow-right.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/arrow-right.svg rename to packages/ui/src/ocelot/icons/svgs/arrow-right.svg diff --git a/webapp/assets/_new/icons/svgs/balance-scale.svg b/packages/ui/src/ocelot/icons/svgs/balance-scale.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/balance-scale.svg rename to packages/ui/src/ocelot/icons/svgs/balance-scale.svg diff --git a/webapp/assets/_new/icons/svgs/ban.svg b/packages/ui/src/ocelot/icons/svgs/ban.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/ban.svg rename to packages/ui/src/ocelot/icons/svgs/ban.svg diff --git a/webapp/assets/_new/icons/svgs/bars.svg b/packages/ui/src/ocelot/icons/svgs/bars.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/bars.svg rename to packages/ui/src/ocelot/icons/svgs/bars.svg diff --git a/packages/ui/src/ocelot/icons/svgs/bell-slashed.svg b/packages/ui/src/ocelot/icons/svgs/bell-slashed.svg new file mode 100644 index 000000000..532779bae --- /dev/null +++ b/packages/ui/src/ocelot/icons/svgs/bell-slashed.svg @@ -0,0 +1,4 @@ + + + + diff --git a/webapp/assets/_new/icons/svgs/bell.svg b/packages/ui/src/ocelot/icons/svgs/bell.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/bell.svg rename to packages/ui/src/ocelot/icons/svgs/bell.svg diff --git a/webapp/assets/_new/icons/svgs/bold.svg b/packages/ui/src/ocelot/icons/svgs/bold.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/bold.svg rename to packages/ui/src/ocelot/icons/svgs/bold.svg diff --git a/packages/ui/src/ocelot/icons/svgs/book.svg b/packages/ui/src/ocelot/icons/svgs/book.svg new file mode 100644 index 000000000..7527fa203 --- /dev/null +++ b/packages/ui/src/ocelot/icons/svgs/book.svg @@ -0,0 +1 @@ +book diff --git a/webapp/assets/_new/icons/svgs/bookmark.svg b/packages/ui/src/ocelot/icons/svgs/bookmark.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/bookmark.svg rename to packages/ui/src/ocelot/icons/svgs/bookmark.svg diff --git a/webapp/assets/_new/icons/svgs/calendar.svg b/packages/ui/src/ocelot/icons/svgs/calendar.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/calendar.svg rename to packages/ui/src/ocelot/icons/svgs/calendar.svg diff --git a/webapp/assets/_new/icons/svgs/chat-bubble.svg b/packages/ui/src/ocelot/icons/svgs/chat-bubble.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/chat-bubble.svg rename to packages/ui/src/ocelot/icons/svgs/chat-bubble.svg diff --git a/webapp/assets/_new/icons/svgs/clock.svg b/packages/ui/src/ocelot/icons/svgs/clock.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/clock.svg rename to packages/ui/src/ocelot/icons/svgs/clock.svg diff --git a/webapp/assets/_new/icons/svgs/cogs.svg b/packages/ui/src/ocelot/icons/svgs/cogs.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/cogs.svg rename to packages/ui/src/ocelot/icons/svgs/cogs.svg diff --git a/webapp/assets/_new/icons/svgs/comment.svg b/packages/ui/src/ocelot/icons/svgs/comment.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/comment.svg rename to packages/ui/src/ocelot/icons/svgs/comment.svg diff --git a/packages/ui/src/ocelot/icons/svgs/comments.svg b/packages/ui/src/ocelot/icons/svgs/comments.svg new file mode 100755 index 000000000..cb332ae26 --- /dev/null +++ b/packages/ui/src/ocelot/icons/svgs/comments.svg @@ -0,0 +1 @@ +comments diff --git a/webapp/assets/_new/icons/svgs/copy.svg b/packages/ui/src/ocelot/icons/svgs/copy.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/copy.svg rename to packages/ui/src/ocelot/icons/svgs/copy.svg diff --git a/webapp/assets/_new/icons/svgs/download.svg b/packages/ui/src/ocelot/icons/svgs/download.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/download.svg rename to packages/ui/src/ocelot/icons/svgs/download.svg diff --git a/webapp/assets/_new/icons/svgs/edit.svg b/packages/ui/src/ocelot/icons/svgs/edit.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/edit.svg rename to packages/ui/src/ocelot/icons/svgs/edit.svg diff --git a/webapp/assets/_new/icons/svgs/ellipsis-v.svg b/packages/ui/src/ocelot/icons/svgs/ellipsis-v.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/ellipsis-v.svg rename to packages/ui/src/ocelot/icons/svgs/ellipsis-v.svg diff --git a/webapp/assets/_new/icons/svgs/exclamation-circle.svg b/packages/ui/src/ocelot/icons/svgs/exclamation-circle.svg old mode 100755 new mode 100644 similarity index 100% rename from webapp/assets/_new/icons/svgs/exclamation-circle.svg rename to packages/ui/src/ocelot/icons/svgs/exclamation-circle.svg diff --git a/webapp/assets/_new/icons/svgs/expand.svg b/packages/ui/src/ocelot/icons/svgs/expand.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/expand.svg rename to packages/ui/src/ocelot/icons/svgs/expand.svg diff --git a/webapp/assets/_new/icons/svgs/eye-slash.svg b/packages/ui/src/ocelot/icons/svgs/eye-slash.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/eye-slash.svg rename to packages/ui/src/ocelot/icons/svgs/eye-slash.svg diff --git a/webapp/assets/_new/icons/svgs/eye.svg b/packages/ui/src/ocelot/icons/svgs/eye.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/eye.svg rename to packages/ui/src/ocelot/icons/svgs/eye.svg diff --git a/webapp/assets/_new/icons/svgs/filter.svg b/packages/ui/src/ocelot/icons/svgs/filter.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/filter.svg rename to packages/ui/src/ocelot/icons/svgs/filter.svg diff --git a/webapp/assets/_new/icons/svgs/flag.svg b/packages/ui/src/ocelot/icons/svgs/flag.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/flag.svg rename to packages/ui/src/ocelot/icons/svgs/flag.svg diff --git a/webapp/assets/_new/icons/svgs/globe-detailed.svg b/packages/ui/src/ocelot/icons/svgs/globe-detailed.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/globe-detailed.svg rename to packages/ui/src/ocelot/icons/svgs/globe-detailed.svg diff --git a/webapp/assets/_new/icons/svgs/globe.svg b/packages/ui/src/ocelot/icons/svgs/globe.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/globe.svg rename to packages/ui/src/ocelot/icons/svgs/globe.svg diff --git a/packages/ui/src/ocelot/icons/svgs/hand-pointer.svg b/packages/ui/src/ocelot/icons/svgs/hand-pointer.svg new file mode 100644 index 000000000..c3191558e --- /dev/null +++ b/packages/ui/src/ocelot/icons/svgs/hand-pointer.svg @@ -0,0 +1 @@ +hand-pointer-o diff --git a/packages/ui/src/ocelot/icons/svgs/heart-o.svg b/packages/ui/src/ocelot/icons/svgs/heart-o.svg new file mode 100755 index 000000000..e505b625e --- /dev/null +++ b/packages/ui/src/ocelot/icons/svgs/heart-o.svg @@ -0,0 +1 @@ +heart-o diff --git a/webapp/assets/_new/icons/svgs/home.svg b/packages/ui/src/ocelot/icons/svgs/home.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/home.svg rename to packages/ui/src/ocelot/icons/svgs/home.svg diff --git a/webapp/assets/_new/icons/svgs/image.svg b/packages/ui/src/ocelot/icons/svgs/image.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/image.svg rename to packages/ui/src/ocelot/icons/svgs/image.svg diff --git a/webapp/assets/_new/icons/svgs/italic.svg b/packages/ui/src/ocelot/icons/svgs/italic.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/italic.svg rename to packages/ui/src/ocelot/icons/svgs/italic.svg diff --git a/webapp/assets/_new/icons/svgs/level-down.svg b/packages/ui/src/ocelot/icons/svgs/level-down.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/level-down.svg rename to packages/ui/src/ocelot/icons/svgs/level-down.svg diff --git a/webapp/assets/_new/icons/svgs/link.svg b/packages/ui/src/ocelot/icons/svgs/link.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/link.svg rename to packages/ui/src/ocelot/icons/svgs/link.svg diff --git a/webapp/assets/_new/icons/svgs/list-ol.svg b/packages/ui/src/ocelot/icons/svgs/list-ol.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/list-ol.svg rename to packages/ui/src/ocelot/icons/svgs/list-ol.svg diff --git a/webapp/assets/_new/icons/svgs/list-ul.svg b/packages/ui/src/ocelot/icons/svgs/list-ul.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/list-ul.svg rename to packages/ui/src/ocelot/icons/svgs/list-ul.svg diff --git a/webapp/assets/_new/icons/svgs/map-marker.svg b/packages/ui/src/ocelot/icons/svgs/map-marker.svg old mode 100755 new mode 100644 similarity index 100% rename from webapp/assets/_new/icons/svgs/map-marker.svg rename to packages/ui/src/ocelot/icons/svgs/map-marker.svg diff --git a/webapp/assets/_new/icons/svgs/microphone-slash.svg b/packages/ui/src/ocelot/icons/svgs/microphone-slash.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/microphone-slash.svg rename to packages/ui/src/ocelot/icons/svgs/microphone-slash.svg diff --git a/webapp/assets/_new/icons/svgs/microphone.svg b/packages/ui/src/ocelot/icons/svgs/microphone.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/microphone.svg rename to packages/ui/src/ocelot/icons/svgs/microphone.svg diff --git a/webapp/assets/_new/icons/svgs/minus.svg b/packages/ui/src/ocelot/icons/svgs/minus.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/minus.svg rename to packages/ui/src/ocelot/icons/svgs/minus.svg diff --git a/webapp/assets/_new/icons/svgs/paragraph.svg b/packages/ui/src/ocelot/icons/svgs/paragraph.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/paragraph.svg rename to packages/ui/src/ocelot/icons/svgs/paragraph.svg diff --git a/webapp/assets/_new/icons/svgs/pencil.svg b/packages/ui/src/ocelot/icons/svgs/pencil.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/pencil.svg rename to packages/ui/src/ocelot/icons/svgs/pencil.svg diff --git a/webapp/assets/_new/icons/svgs/question-circle.svg b/packages/ui/src/ocelot/icons/svgs/question-circle.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/question-circle.svg rename to packages/ui/src/ocelot/icons/svgs/question-circle.svg diff --git a/webapp/assets/_new/icons/svgs/quote-right.svg b/packages/ui/src/ocelot/icons/svgs/quote-right.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/quote-right.svg rename to packages/ui/src/ocelot/icons/svgs/quote-right.svg diff --git a/webapp/assets/_new/icons/svgs/save.svg b/packages/ui/src/ocelot/icons/svgs/save.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/save.svg rename to packages/ui/src/ocelot/icons/svgs/save.svg diff --git a/webapp/assets/_new/icons/svgs/search.svg b/packages/ui/src/ocelot/icons/svgs/search.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/search.svg rename to packages/ui/src/ocelot/icons/svgs/search.svg diff --git a/webapp/assets/_new/icons/svgs/shield.svg b/packages/ui/src/ocelot/icons/svgs/shield.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/shield.svg rename to packages/ui/src/ocelot/icons/svgs/shield.svg diff --git a/packages/ui/src/ocelot/icons/svgs/shopping-cart.svg b/packages/ui/src/ocelot/icons/svgs/shopping-cart.svg new file mode 100755 index 000000000..27275aa77 --- /dev/null +++ b/packages/ui/src/ocelot/icons/svgs/shopping-cart.svg @@ -0,0 +1 @@ +shopping-cart diff --git a/webapp/assets/_new/icons/svgs/sign-in.svg b/packages/ui/src/ocelot/icons/svgs/sign-in.svg old mode 100755 new mode 100644 similarity index 100% rename from webapp/assets/_new/icons/svgs/sign-in.svg rename to packages/ui/src/ocelot/icons/svgs/sign-in.svg diff --git a/webapp/assets/_new/icons/svgs/sign-out.svg b/packages/ui/src/ocelot/icons/svgs/sign-out.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/sign-out.svg rename to packages/ui/src/ocelot/icons/svgs/sign-out.svg diff --git a/webapp/assets/_new/icons/svgs/sort-amount-asc.svg b/packages/ui/src/ocelot/icons/svgs/sort-amount-asc.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/sort-amount-asc.svg rename to packages/ui/src/ocelot/icons/svgs/sort-amount-asc.svg diff --git a/webapp/assets/_new/icons/svgs/sort-amount-desc.svg b/packages/ui/src/ocelot/icons/svgs/sort-amount-desc.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/sort-amount-desc.svg rename to packages/ui/src/ocelot/icons/svgs/sort-amount-desc.svg diff --git a/webapp/assets/_new/icons/svgs/trash.svg b/packages/ui/src/ocelot/icons/svgs/trash.svg old mode 100755 new mode 100644 similarity index 100% rename from webapp/assets/_new/icons/svgs/trash.svg rename to packages/ui/src/ocelot/icons/svgs/trash.svg diff --git a/webapp/assets/_new/icons/svgs/underline.svg b/packages/ui/src/ocelot/icons/svgs/underline.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/underline.svg rename to packages/ui/src/ocelot/icons/svgs/underline.svg diff --git a/webapp/assets/_new/icons/svgs/unlink.svg b/packages/ui/src/ocelot/icons/svgs/unlink.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/unlink.svg rename to packages/ui/src/ocelot/icons/svgs/unlink.svg diff --git a/webapp/assets/_new/icons/svgs/user-plus.svg b/packages/ui/src/ocelot/icons/svgs/user-plus.svg old mode 100755 new mode 100644 similarity index 100% rename from webapp/assets/_new/icons/svgs/user-plus.svg rename to packages/ui/src/ocelot/icons/svgs/user-plus.svg diff --git a/webapp/assets/_new/icons/svgs/user-times.svg b/packages/ui/src/ocelot/icons/svgs/user-times.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/user-times.svg rename to packages/ui/src/ocelot/icons/svgs/user-times.svg diff --git a/webapp/assets/_new/icons/svgs/user.svg b/packages/ui/src/ocelot/icons/svgs/user.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/user.svg rename to packages/ui/src/ocelot/icons/svgs/user.svg diff --git a/webapp/assets/_new/icons/svgs/users.svg b/packages/ui/src/ocelot/icons/svgs/users.svg old mode 100755 new mode 100644 similarity index 100% rename from webapp/assets/_new/icons/svgs/users.svg rename to packages/ui/src/ocelot/icons/svgs/users.svg diff --git a/webapp/assets/_new/icons/svgs/volume-off.svg b/packages/ui/src/ocelot/icons/svgs/volume-off.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/volume-off.svg rename to packages/ui/src/ocelot/icons/svgs/volume-off.svg diff --git a/webapp/assets/_new/icons/svgs/volume-up.svg b/packages/ui/src/ocelot/icons/svgs/volume-up.svg similarity index 100% rename from webapp/assets/_new/icons/svgs/volume-up.svg rename to packages/ui/src/ocelot/icons/svgs/volume-up.svg diff --git a/webapp/assets/_new/icons/svgs/warning.svg b/packages/ui/src/ocelot/icons/svgs/warning.svg old mode 100755 new mode 100644 similarity index 100% rename from webapp/assets/_new/icons/svgs/warning.svg rename to packages/ui/src/ocelot/icons/svgs/warning.svg diff --git a/packages/ui/src/plugins/vite-svg-icon.ts b/packages/ui/src/plugins/vite-svg-icon.ts index 38730d400..0bbfd0941 100644 --- a/packages/ui/src/plugins/vite-svg-icon.ts +++ b/packages/ui/src/plugins/vite-svg-icon.ts @@ -7,9 +7,28 @@ const SUFFIX = '?icon' /** Escape a string for safe embedding in a single-quoted JS literal */ function escapeJS(str: string): string { - return str.replace(/\\/g, '\\\\').replace(/'/g, "\\'") + return str + .replace(/\\/g, '\\\\') + .replace(/'/g, "\\'") + .replace(/\n/g, '\\n') + .replace(/\r/g, '\\r') + .replace(/\u2028/g, '\\u2028') + .replace(/\u2029/g, '\\u2029') } +const SUPPORTED_ELEMENTS = ['path', 'circle', 'rect', 'polygon', 'polyline', 'ellipse', 'line'] +const ELEM_PATTERN = SUPPORTED_ELEMENTS.join('|') +// Built from constant array above — safe to use in RegExp +// eslint-disable-next-line security/detect-non-literal-regexp +const ELEM_REGEX = new RegExp(`<(${ELEM_PATTERN})(?:\\s([^>]*?))?\\/?>`, 'g') + +// Elements silently ignored (container without visual effect) +const IGNORED_ELEMENTS = ['g', 'title'] +const KNOWN_ELEMENTS = ['svg', ...SUPPORTED_ELEMENTS, ...IGNORED_ELEMENTS] +// Built from constant arrays above — safe to use in RegExp +// eslint-disable-next-line security/detect-non-literal-regexp +const UNSUPPORTED_REGEX = new RegExp(`<(?!\\/|(?:${KNOWN_ELEMENTS.join('|')})\\b)(\\w+)[\\s>]`, 'g') + export default function svgIcon(): Plugin { return { name: 'svg-icon', @@ -32,32 +51,43 @@ export default function svgIcon(): Plugin { const viewBoxMatch = viewBoxRegex.exec(svg) const viewBox = viewBoxMatch ? viewBoxMatch[1] : '0 0 32 32' - const unsupported = svg.match(/<(?:circle|rect|polygon|polyline|ellipse|line)\s/g) + const unsupported = svg.match(UNSUPPORTED_REGEX) if (unsupported) { this.warn( `${filePath}: unsupported SVG elements will be ignored: ${[...new Set(unsupported.map((s) => s.trim()))].join(', ')}`, ) } - const paths: string[] = [] - const pathRegex = /]*?\bd="([^"]+)"/g + const children: string[] = [] + ELEM_REGEX.lastIndex = 0 let match: RegExpExecArray | null - while ((match = pathRegex.exec(svg)) !== null) { - paths.push(match[1]) + while ((match = ELEM_REGEX.exec(svg)) !== null) { + const tag = match[1] + const attrString = match[2] || '' + const attrs: Record = {} + const attrRegex = /(\w[\w-]*)=(["'])([^"']*)\2/g + let attrMatch: RegExpExecArray | null + while ((attrMatch = attrRegex.exec(attrString)) !== null) { + attrs[attrMatch[1]] = attrMatch[3] + } + const attrEntries = Object.entries(attrs) + .map(([k, v]) => `'${escapeJS(k)}': '${escapeJS(v)}'`) + .join(', ') + children.push(`_h('${tag}', _v2 ? { attrs: { ${attrEntries} } } : { ${attrEntries} })`) } - const pathElements = paths - .map((d) => { - const escaped = escapeJS(d) - return `h('path', isVue2 ? { attrs: { d: '${escaped}' } } : { d: '${escaped}' })` - }) - .join(', ') + const pathElements = children.join(', ') const safeViewBox = escapeJS(viewBox) - return `import { h, isVue2 } from 'vue-demi' + // Icon functions accept optional (h, v2) from OsIcon. When OsIcon passes + // Vue 2's $createElement, we use it directly — avoiding the globally-imported + // h() which requires currentInstance in Vue 2.7. + // When used as a standalone Vue 3 component (e.g. in Storybook), h/v2 are not + // functions/booleans, so we fall back to the imported _hImport / _v2Import. + return `import { h as _hImport, isVue2 as _v2Import } from 'vue-demi' const svgAttrs = { xmlns: 'http://www.w3.org/2000/svg', viewBox: '${safeViewBox}', fill: 'currentColor' } -export default () => h('svg', isVue2 ? { attrs: svgAttrs } : svgAttrs, [${pathElements}]) +export default (h, v2) => { const _h = typeof h === 'function' ? h : _hImport; const _v2 = typeof v2 === 'boolean' ? v2 : _v2Import; return _h('svg', _v2 ? { attrs: svgAttrs } : svgAttrs, [${pathElements}]) } ` }, } diff --git a/packages/ui/src/test/setup.ts b/packages/ui/src/test/setup.ts new file mode 100644 index 000000000..c0d826cc3 --- /dev/null +++ b/packages/ui/src/test/setup.ts @@ -0,0 +1,9 @@ +import { config } from '@vue/test-utils' + +config.global.config.warnHandler = (msg) => { + throw new Error(`[Vue warn]: ${msg}`) +} + +config.global.config.errorHandler = (err) => { + throw err instanceof Error ? err : new Error(`[Vue error]: ${String(err)}`) +} diff --git a/packages/ui/vite.config.ts b/packages/ui/vite.config.ts index 8d37025c8..14d0610cc 100644 --- a/packages/ui/vite.config.ts +++ b/packages/ui/vite.config.ts @@ -68,6 +68,7 @@ export default defineConfig({ test: { globals: true, environment: 'jsdom', + setupFiles: ['src/test/setup.ts'], include: ['src/**/*.{test,spec}.{js,ts}'], exclude: ['src/**/*.visual.spec.ts', 'src/plugins/**'], coverage: { diff --git a/webapp/assets/_new/icons/svgs/.gitkeep b/webapp/assets/_new/icons/svgs/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/webapp/assets/_new/icons/svgs/angle-down.svg b/webapp/assets/_new/icons/svgs/angle-down.svg deleted file mode 100755 index aa6f763c8..000000000 --- a/webapp/assets/_new/icons/svgs/angle-down.svg +++ /dev/null @@ -1,5 +0,0 @@ - - -angle-down - - diff --git a/webapp/assets/_new/icons/svgs/bell-slashed.svg b/webapp/assets/_new/icons/svgs/bell-slashed.svg deleted file mode 100644 index 0aae3ff97..000000000 --- a/webapp/assets/_new/icons/svgs/bell-slashed.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/webapp/assets/_new/icons/svgs/book.svg b/webapp/assets/_new/icons/svgs/book.svg deleted file mode 100644 index 305e367ac..000000000 --- a/webapp/assets/_new/icons/svgs/book.svg +++ /dev/null @@ -1,5 +0,0 @@ - - -book - - diff --git a/webapp/assets/_new/icons/svgs/bullhorn.svg b/webapp/assets/_new/icons/svgs/bullhorn.svg index 95e0d21d3..1cdc6b452 100755 --- a/webapp/assets/_new/icons/svgs/bullhorn.svg +++ b/webapp/assets/_new/icons/svgs/bullhorn.svg @@ -1,5 +1 @@ - - -bullhorn - - +bullhorn diff --git a/webapp/assets/_new/icons/svgs/camera.svg b/webapp/assets/_new/icons/svgs/camera.svg index 793620544..59a02f655 100755 --- a/webapp/assets/_new/icons/svgs/camera.svg +++ b/webapp/assets/_new/icons/svgs/camera.svg @@ -1,5 +1 @@ - - -camera - - +camera diff --git a/webapp/assets/_new/icons/svgs/child.svg b/webapp/assets/_new/icons/svgs/child.svg index fcb5651f0..353c8cbe1 100644 --- a/webapp/assets/_new/icons/svgs/child.svg +++ b/webapp/assets/_new/icons/svgs/child.svg @@ -1,5 +1 @@ - - -child - - +child diff --git a/webapp/assets/_new/icons/svgs/comments.svg b/webapp/assets/_new/icons/svgs/comments.svg deleted file mode 100755 index 551f10e4e..000000000 --- a/webapp/assets/_new/icons/svgs/comments.svg +++ /dev/null @@ -1,5 +0,0 @@ - - -comments - - diff --git a/webapp/assets/_new/icons/svgs/credit-card.svg b/webapp/assets/_new/icons/svgs/credit-card.svg index 29c1fb96f..3dc714732 100755 --- a/webapp/assets/_new/icons/svgs/credit-card.svg +++ b/webapp/assets/_new/icons/svgs/credit-card.svg @@ -1,5 +1 @@ - - -credit-card - - +credit-card diff --git a/webapp/assets/_new/icons/svgs/cubes.svg b/webapp/assets/_new/icons/svgs/cubes.svg index aeb3d66d1..09b320bc3 100755 --- a/webapp/assets/_new/icons/svgs/cubes.svg +++ b/webapp/assets/_new/icons/svgs/cubes.svg @@ -1,5 +1 @@ - - -cubes - - +cubes diff --git a/webapp/assets/_new/icons/svgs/culture.svg b/webapp/assets/_new/icons/svgs/culture.svg index d63e38cb4..54aeb6f87 100644 --- a/webapp/assets/_new/icons/svgs/culture.svg +++ b/webapp/assets/_new/icons/svgs/culture.svg @@ -1,20 +1 @@ - - - - - - - - + diff --git a/webapp/assets/_new/icons/svgs/energy.svg b/webapp/assets/_new/icons/svgs/energy.svg index 5035a5586..324563323 100644 --- a/webapp/assets/_new/icons/svgs/energy.svg +++ b/webapp/assets/_new/icons/svgs/energy.svg @@ -1,14 +1 @@ - - - + diff --git a/webapp/assets/_new/icons/svgs/finance.svg b/webapp/assets/_new/icons/svgs/finance.svg index 74081bc6a..4273ae836 100644 --- a/webapp/assets/_new/icons/svgs/finance.svg +++ b/webapp/assets/_new/icons/svgs/finance.svg @@ -1,13 +1 @@ - - - - + diff --git a/webapp/assets/_new/icons/svgs/graduation-cap.svg b/webapp/assets/_new/icons/svgs/graduation-cap.svg index 5d35226d3..a3fad9224 100755 --- a/webapp/assets/_new/icons/svgs/graduation-cap.svg +++ b/webapp/assets/_new/icons/svgs/graduation-cap.svg @@ -1,5 +1 @@ - - -graduation-cap - - +graduation-cap diff --git a/webapp/assets/_new/icons/svgs/hand-pointer.svg b/webapp/assets/_new/icons/svgs/hand-pointer.svg deleted file mode 100644 index e74339724..000000000 --- a/webapp/assets/_new/icons/svgs/hand-pointer.svg +++ /dev/null @@ -1,5 +0,0 @@ - - -hand-pointer-o - - diff --git a/webapp/assets/_new/icons/svgs/health.svg b/webapp/assets/_new/icons/svgs/health.svg index acf50d7c1..4567b74d9 100644 --- a/webapp/assets/_new/icons/svgs/health.svg +++ b/webapp/assets/_new/icons/svgs/health.svg @@ -1,7 +1 @@ - - - - + diff --git a/webapp/assets/_new/icons/svgs/heart-o.svg b/webapp/assets/_new/icons/svgs/heart-o.svg deleted file mode 100755 index 6605b96ac..000000000 --- a/webapp/assets/_new/icons/svgs/heart-o.svg +++ /dev/null @@ -1,5 +0,0 @@ - - -heart-o - - diff --git a/webapp/assets/_new/icons/svgs/location-arrow.svg b/webapp/assets/_new/icons/svgs/location-arrow.svg index 942a37fa5..bf2fe7b0e 100755 --- a/webapp/assets/_new/icons/svgs/location-arrow.svg +++ b/webapp/assets/_new/icons/svgs/location-arrow.svg @@ -1,5 +1 @@ - - -location-arrow - - +location-arrow diff --git a/webapp/assets/_new/icons/svgs/media.svg b/webapp/assets/_new/icons/svgs/media.svg index d63c98610..8257ffff8 100644 --- a/webapp/assets/_new/icons/svgs/media.svg +++ b/webapp/assets/_new/icons/svgs/media.svg @@ -1,7 +1 @@ - - - - + diff --git a/webapp/assets/_new/icons/svgs/miscellaneous.svg b/webapp/assets/_new/icons/svgs/miscellaneous.svg index 07f8dbe3f..e180aafb0 100644 --- a/webapp/assets/_new/icons/svgs/miscellaneous.svg +++ b/webapp/assets/_new/icons/svgs/miscellaneous.svg @@ -1,13 +1 @@ - - - - + diff --git a/webapp/assets/_new/icons/svgs/mobility.svg b/webapp/assets/_new/icons/svgs/mobility.svg index 9e36ec21e..a6236957b 100644 --- a/webapp/assets/_new/icons/svgs/mobility.svg +++ b/webapp/assets/_new/icons/svgs/mobility.svg @@ -1,11 +1 @@ - - - + diff --git a/webapp/assets/_new/icons/svgs/movement.svg b/webapp/assets/_new/icons/svgs/movement.svg index 81052875d..93dd83411 100644 --- a/webapp/assets/_new/icons/svgs/movement.svg +++ b/webapp/assets/_new/icons/svgs/movement.svg @@ -1,19 +1 @@ - - - - + diff --git a/webapp/assets/_new/icons/svgs/music.svg b/webapp/assets/_new/icons/svgs/music.svg index b84b87800..cdba78fac 100644 --- a/webapp/assets/_new/icons/svgs/music.svg +++ b/webapp/assets/_new/icons/svgs/music.svg @@ -1,5 +1 @@ - - -music - - +music diff --git a/webapp/assets/_new/icons/svgs/nature.svg b/webapp/assets/_new/icons/svgs/nature.svg index d40250af4..1075be693 100644 --- a/webapp/assets/_new/icons/svgs/nature.svg +++ b/webapp/assets/_new/icons/svgs/nature.svg @@ -1,18 +1 @@ - - - - + diff --git a/webapp/assets/_new/icons/svgs/networking.svg b/webapp/assets/_new/icons/svgs/networking.svg index b8d35da69..57049a7e4 100644 --- a/webapp/assets/_new/icons/svgs/networking.svg +++ b/webapp/assets/_new/icons/svgs/networking.svg @@ -1,22 +1 @@ - - - - - + diff --git a/webapp/assets/_new/icons/svgs/peace.svg b/webapp/assets/_new/icons/svgs/peace.svg index 408601cae..5b055461c 100644 --- a/webapp/assets/_new/icons/svgs/peace.svg +++ b/webapp/assets/_new/icons/svgs/peace.svg @@ -1,8 +1 @@ - - - + diff --git a/webapp/assets/_new/icons/svgs/pie-chart.svg b/webapp/assets/_new/icons/svgs/pie-chart.svg index 1d942226e..6b6eb90a9 100755 --- a/webapp/assets/_new/icons/svgs/pie-chart.svg +++ b/webapp/assets/_new/icons/svgs/pie-chart.svg @@ -1,5 +1 @@ - - -pie-chart - - +pie-chart diff --git a/webapp/assets/_new/icons/svgs/politics.svg b/webapp/assets/_new/icons/svgs/politics.svg index 35322097d..ef439c074 100644 --- a/webapp/assets/_new/icons/svgs/politics.svg +++ b/webapp/assets/_new/icons/svgs/politics.svg @@ -1,12 +1 @@ - - - - - + diff --git a/webapp/assets/_new/icons/svgs/psyche.svg b/webapp/assets/_new/icons/svgs/psyche.svg index 8c285d5ca..64334602d 100644 --- a/webapp/assets/_new/icons/svgs/psyche.svg +++ b/webapp/assets/_new/icons/svgs/psyche.svg @@ -1,8 +1 @@ - - - + diff --git a/webapp/assets/_new/icons/svgs/recycle.svg b/webapp/assets/_new/icons/svgs/recycle.svg index 9bbdb3ad3..adf0deb05 100755 --- a/webapp/assets/_new/icons/svgs/recycle.svg +++ b/webapp/assets/_new/icons/svgs/recycle.svg @@ -1,5 +1 @@ - - -recycle - - +recycle diff --git a/webapp/assets/_new/icons/svgs/rocket.svg b/webapp/assets/_new/icons/svgs/rocket.svg index f83674f15..fd9f12049 100755 --- a/webapp/assets/_new/icons/svgs/rocket.svg +++ b/webapp/assets/_new/icons/svgs/rocket.svg @@ -1,5 +1 @@ - - -rocket - - +rocket diff --git a/webapp/assets/_new/icons/svgs/science.svg b/webapp/assets/_new/icons/svgs/science.svg index 9d3211223..b444fa897 100644 --- a/webapp/assets/_new/icons/svgs/science.svg +++ b/webapp/assets/_new/icons/svgs/science.svg @@ -1,25 +1 @@ - - - - + diff --git a/webapp/assets/_new/icons/svgs/share.svg b/webapp/assets/_new/icons/svgs/share.svg index b2fee7622..b1b4c98bb 100755 --- a/webapp/assets/_new/icons/svgs/share.svg +++ b/webapp/assets/_new/icons/svgs/share.svg @@ -1,5 +1 @@ - - -share - - +share diff --git a/webapp/assets/_new/icons/svgs/shopping-cart.svg b/webapp/assets/_new/icons/svgs/shopping-cart.svg deleted file mode 100755 index 9ca3c5e13..000000000 --- a/webapp/assets/_new/icons/svgs/shopping-cart.svg +++ /dev/null @@ -1,5 +0,0 @@ - - -shopping-cart - - diff --git a/webapp/assets/_new/icons/svgs/spirituality.svg b/webapp/assets/_new/icons/svgs/spirituality.svg index 0c2757071..ec6e36e50 100644 --- a/webapp/assets/_new/icons/svgs/spirituality.svg +++ b/webapp/assets/_new/icons/svgs/spirituality.svg @@ -1,13 +1 @@ - - - - - - - - - + diff --git a/webapp/assets/_new/icons/svgs/video-camera.svg b/webapp/assets/_new/icons/svgs/video-camera.svg index b6a9cc0df..27e1634ad 100755 --- a/webapp/assets/_new/icons/svgs/video-camera.svg +++ b/webapp/assets/_new/icons/svgs/video-camera.svg @@ -1,5 +1 @@ - - -video-camera - - +video-camera diff --git a/webapp/assets/styles/main.scss b/webapp/assets/styles/main.scss index fdf5c4240..9c43a3e65 100644 --- a/webapp/assets/styles/main.scss +++ b/webapp/assets/styles/main.scss @@ -168,7 +168,7 @@ body.dropdown-open { align-content: center; align-items: center; - .base-icon { + .os-icon { padding-right: $space-xx-small; } } diff --git a/webapp/components/ActionButton.spec.js b/webapp/components/ActionButton.spec.js index 3889d26db..9da1ae6ea 100644 --- a/webapp/components/ActionButton.spec.js +++ b/webapp/components/ActionButton.spec.js @@ -1,6 +1,7 @@ import { render, screen, fireEvent } from '@testing-library/vue' import '@testing-library/jest-dom' import ActionButton from './ActionButton.vue' +import { ocelotIcons } from '@ocelot-social/ui/ocelot' const localVue = global.localVue @@ -19,7 +20,7 @@ describe('ActionButton.vue', () => { mocks, localVue, propsData: { - icon: 'heart', + icon: ocelotIcons.heartO, text: 'Click me', count: 7, disabled: isDisabled, @@ -27,13 +28,12 @@ describe('ActionButton.vue', () => { }) } - beforeEach(() => { - wrapper = Wrapper() - }) - describe('when not disabled', () => { + beforeEach(() => { + wrapper = Wrapper() + }) + it('renders', () => { - const wrapper = Wrapper() expect(wrapper.container).toMatchSnapshot() }) @@ -50,15 +50,17 @@ describe('ActionButton.vue', () => { }) describe('when disabled', () => { + beforeEach(() => { + wrapper = Wrapper({ isDisabled: true }) + }) + it('renders', () => { - const wrapper = Wrapper({ isDisabled: true }) expect(wrapper.container).toMatchSnapshot() }) - it('button does not emit click event', async () => { + it('button is disabled', () => { const button = screen.getByRole('button') - await fireEvent.click(button) - expect(wrapper.emitted().click).toEqual([[]]) + expect(button).toBeDisabled() }) }) }) diff --git a/webapp/components/ActionButton.vue b/webapp/components/ActionButton.vue index 20eb021a4..827538cf5 100644 --- a/webapp/components/ActionButton.vue +++ b/webapp/components/ActionButton.vue @@ -10,7 +10,7 @@ @click="click" >

{{ count }}
@@ -18,14 +18,14 @@ diff --git a/webapp/components/Category/index.spec.js b/webapp/components/Category/index.spec.js index 22a85f6f1..3f11bebac 100644 --- a/webapp/components/Category/index.spec.js +++ b/webapp/components/Category/index.spec.js @@ -1,4 +1,6 @@ import { mount } from '@vue/test-utils' +import { OsIcon } from '@ocelot-social/ui' +import { resolveIcon } from '~/utils/iconRegistry' import Category from './index' @@ -20,7 +22,7 @@ describe('Category', () => { describe('given Strings for Icon and Name', () => { beforeEach(() => { - icon = 'mouse-pointer' + icon = 'home' name = 'Peter' }) @@ -28,8 +30,10 @@ describe('Category', () => { expect(Wrapper().text()).toContain('Peter') }) - it('shows base icon', () => { - expect(Wrapper().find('.base-icon').exists()).toBe(true) + it('shows icon', () => { + const wrapper = Wrapper() + expect(wrapper.findComponent(OsIcon).exists()).toBe(true) + expect(wrapper.findComponent(OsIcon).props().icon).toBe(resolveIcon('home')) }) }) }) diff --git a/webapp/components/Category/index.vue b/webapp/components/Category/index.vue index ebbaae584..48006370b 100644 --- a/webapp/components/Category/index.vue +++ b/webapp/components/Category/index.vue @@ -1,18 +1,27 @@ @@ -24,7 +33,7 @@ export default { float: right; } - > .base-icon { + > .os-icon { margin-right: $space-xx-small; font-size: $font-size-base; } diff --git a/webapp/components/Chat/AddChatRoomByUserSearch.vue b/webapp/components/Chat/AddChatRoomByUserSearch.vue index 8e71dad13..e59587852 100644 --- a/webapp/components/Chat/AddChatRoomByUserSearch.vue +++ b/webapp/components/Chat/AddChatRoomByUserSearch.vue @@ -12,7 +12,7 @@ @click="closeUserSearch" > @@ -25,6 +25,7 @@ diff --git a/webapp/components/DeleteData/DeleteData.vue b/webapp/components/DeleteData/DeleteData.vue index f77a056df..fb6c83515 100644 --- a/webapp/components/DeleteData/DeleteData.vue +++ b/webapp/components/DeleteData/DeleteData.vue @@ -1,7 +1,7 @@ diff --git a/webapp/components/Editor/MenuLegend.vue b/webapp/components/Editor/MenuLegend.vue index 786b24eb2..e7561aace 100644 --- a/webapp/components/Editor/MenuLegend.vue +++ b/webapp/components/Editor/MenuLegend.vue @@ -11,7 +11,7 @@ @click="toggleMenu" > @@ -28,7 +28,7 @@
{{ item.label }} @@ -42,33 +42,38 @@ diff --git a/webapp/components/Embed/EmbedComponent.vue b/webapp/components/Embed/EmbedComponent.vue index 5e4f7b286..38e7b4a26 100644 --- a/webapp/components/Embed/EmbedComponent.vue +++ b/webapp/components/Embed/EmbedComponent.vue @@ -57,7 +57,7 @@ @click.prevent="removeEmbed()" > @@ -65,6 +65,7 @@ diff --git a/webapp/components/Empty/CallToAction/__snapshots__/CtaJoinLeaveGroup.spec.js.snap b/webapp/components/Empty/CallToAction/__snapshots__/CtaJoinLeaveGroup.spec.js.snap index 8e8a61916..135fd1229 100644 --- a/webapp/components/Empty/CallToAction/__snapshots__/CtaJoinLeaveGroup.spec.js.snap +++ b/webapp/components/Empty/CallToAction/__snapshots__/CtaJoinLeaveGroup.spec.js.snap @@ -11,7 +11,7 @@ exports[`CtaJoinLeaveGroup.vue mount renders 1`] = `

contribution.comment.commenting-disabled.no-group-member.call-to-action -

diff --git a/webapp/components/FilterMenu/CategoriesFilter.vue b/webapp/components/FilterMenu/CategoriesFilter.vue index 9e3e4d0ee..57468f478 100644 --- a/webapp/components/FilterMenu/CategoriesFilter.vue +++ b/webapp/components/FilterMenu/CategoriesFilter.vue @@ -9,7 +9,7 @@ @click="setResetCategories" > {{ $t('filter-menu.all') }} @@ -29,7 +29,7 @@ }" > {{ $t(`contribution.category.name.${category.slug}`) }} @@ -40,6 +40,7 @@ diff --git a/webapp/components/FilterMenu/EventsByFilter.vue b/webapp/components/FilterMenu/EventsByFilter.vue index 5f1309d50..033a9657b 100644 --- a/webapp/components/FilterMenu/EventsByFilter.vue +++ b/webapp/components/FilterMenu/EventsByFilter.vue @@ -11,7 +11,7 @@ data-test="all-button" > {{ $t('filter-menu.ended.all.label') }} @@ -26,7 +26,7 @@ data-test="not-ended-button" > {{ $t('filter-menu.ended.onlyEnded.label') }} @@ -37,6 +37,7 @@ diff --git a/webapp/components/FilterMenu/FilterMenuComponent.vue b/webapp/components/FilterMenu/FilterMenuComponent.vue index 012a87fe4..db2909d49 100644 --- a/webapp/components/FilterMenu/FilterMenuComponent.vue +++ b/webapp/components/FilterMenu/FilterMenuComponent.vue @@ -7,7 +7,7 @@
@@ -28,6 +28,7 @@ diff --git a/webapp/components/InviteButton/InviteButton.vue b/webapp/components/InviteButton/InviteButton.vue index 4633c1c02..2a7f07be2 100644 --- a/webapp/components/InviteButton/InviteButton.vue +++ b/webapp/components/InviteButton/InviteButton.vue @@ -13,7 +13,7 @@ @click.prevent="toggleMenu" > @@ -36,7 +36,8 @@ diff --git a/webapp/components/LocationInfo/__snapshots__/LocationInfo.spec.js.snap b/webapp/components/LocationInfo/__snapshots__/LocationInfo.spec.js.snap index ef5030b3b..1c25e01ff 100644 --- a/webapp/components/LocationInfo/__snapshots__/LocationInfo.spec.js.snap +++ b/webapp/components/LocationInfo/__snapshots__/LocationInfo.spec.js.snap @@ -9,9 +9,18 @@ exports[`LocationInfo distance renders with distance 1`] = ` class="location" > Paris @@ -38,9 +47,18 @@ exports[`LocationInfo distance renders without distance 1`] = ` class="location" > Paris @@ -61,9 +79,18 @@ exports[`LocationInfo size renders in base size 1`] = ` class="location" > Paris @@ -84,9 +111,18 @@ exports[`LocationInfo size renders in small size 1`] = ` class="location" > Paris diff --git a/webapp/components/LocationTeaser/LocationTeaser.vue b/webapp/components/LocationTeaser/LocationTeaser.vue index ede0457ab..a6e64e60a 100644 --- a/webapp/components/LocationTeaser/LocationTeaser.vue +++ b/webapp/components/LocationTeaser/LocationTeaser.vue @@ -1,6 +1,6 @@ diff --git a/webapp/components/LoginButton/LoginButton.vue b/webapp/components/LoginButton/LoginButton.vue index b73a71fa1..5c6c7451d 100644 --- a/webapp/components/LoginButton/LoginButton.vue +++ b/webapp/components/LoginButton/LoginButton.vue @@ -10,14 +10,14 @@ @click.prevent="toggleMenu" > diff --git a/webapp/components/LoginForm/LoginForm.spec.js b/webapp/components/LoginForm/LoginForm.spec.js index e2e2d4530..451dc5e04 100644 --- a/webapp/components/LoginForm/LoginForm.spec.js +++ b/webapp/components/LoginForm/LoginForm.spec.js @@ -1,8 +1,10 @@ import Vue from 'vue' import LoginForm from './LoginForm.vue' +import ShowPassword from '../ShowPassword/ShowPassword.vue' import Styleguide from '@@/' import Vuex from 'vuex' import { mount, createLocalVue } from '@vue/test-utils' +import { ocelotIcons } from '@ocelot-social/ui/ocelot' const localVue = createLocalVue() localVue.use(Vuex) @@ -131,13 +133,13 @@ describe('LoginForm', () => { describe('Click on show password icon, icon change', () => { const wrapper = Wrapper() it('shows eye icon by default', () => { - expect(wrapper.find('span.icon-wrapper').attributes('data-test')).toEqual('eye') + expect(wrapper.findComponent(ShowPassword).props().icon).toBe(ocelotIcons.eye) }) it('shows the slash-eye icon after click', async () => { wrapper.find('span.click-wrapper').trigger('click') await Vue.nextTick() - await expect(wrapper.find('span.icon-wrapper').attributes('data-test')).toEqual('eye-slash') + expect(wrapper.findComponent(ShowPassword).props().icon).toBe(ocelotIcons.eyeSlash) }) }) diff --git a/webapp/components/LoginForm/LoginForm.vue b/webapp/components/LoginForm/LoginForm.vue index 957c17c16..32065aa2c 100644 --- a/webapp/components/LoginForm/LoginForm.vue +++ b/webapp/components/LoginForm/LoginForm.vue @@ -31,7 +31,7 @@ ref="password" :type="showPassword ? 'text' : 'password'" /> - +
{{ $t('login.forgotPassword') }} @@ -44,7 +44,7 @@ name="submit" type="submit" > - + {{ $t('login.login') }}

@@ -66,7 +66,8 @@ import PageParamsLink from '~/components/_new/features/PageParamsLink/PageParams import LocaleSwitch from '~/components/LocaleSwitch/LocaleSwitch' import Logo from '~/components/Logo/Logo' import ShowPassword from '../ShowPassword/ShowPassword.vue' -import { OsButton } from '@ocelot-social/ui' +import { OsButton, OsIcon } from '@ocelot-social/ui' +import { ocelotIcons } from '@ocelot-social/ui/ocelot' import { mapGetters, mapMutations } from 'vuex' export default { @@ -74,6 +75,7 @@ export default { LocaleSwitch, Logo, OsButton, + OsIcon, PageParamsLink, ShowPassword, }, @@ -92,14 +94,17 @@ export default { pending() { return this.$store.getters['auth/pending'] }, - iconName() { - return this.showPassword ? 'eye-slash' : 'eye' + passwordIcon() { + return this.showPassword ? this.icons.eyeSlash : this.icons.eye }, ...mapGetters({ currentUser: 'auth/user', categories: 'categories/categories', }), }, + created() { + this.icons = ocelotIcons + }, methods: { ...mapMutations({ toggleCategory: 'posts/TOGGLE_CATEGORY', diff --git a/webapp/components/Modal/ConfirmModal.vue b/webapp/components/Modal/ConfirmModal.vue index 13032532b..affc1a516 100644 --- a/webapp/components/Modal/ConfirmModal.vue +++ b/webapp/components/Modal/ConfirmModal.vue @@ -18,7 +18,7 @@ data-test="cancel-button" > {{ $t(modalData.buttons.cancel.textIdent) }} @@ -32,7 +32,7 @@ data-test="confirm-button" > {{ $t(modalData.buttons.confirm.textIdent) }} @@ -41,13 +41,14 @@ - - diff --git a/webapp/components/_new/generic/CounterIcon/CounterIcon.spec.js b/webapp/components/_new/generic/CounterIcon/CounterIcon.spec.js index 584a971c4..001c9f1cf 100644 --- a/webapp/components/_new/generic/CounterIcon/CounterIcon.spec.js +++ b/webapp/components/_new/generic/CounterIcon/CounterIcon.spec.js @@ -1,6 +1,7 @@ import { mount } from '@vue/test-utils' import CounterIcon from './CounterIcon' -import BaseIcon from '../BaseIcon/BaseIcon' +import { OsIcon } from '@ocelot-social/ui' +import { ocelotIcons } from '@ocelot-social/ui/ocelot' const localVue = global.localVue @@ -11,15 +12,15 @@ describe('CounterIcon.vue', () => { return mount(CounterIcon, { propsData, localVue }) } - describe('given a valid icon name and count below 100', () => { + describe('given a valid icon and count below 100', () => { beforeEach(() => { - propsData = { icon: 'comments', count: 42 } + propsData = { icon: ocelotIcons.comments, count: 42 } wrapper = Wrapper() count = wrapper.find('.count') }) it('renders the icon', () => { - expect(wrapper.findComponent(BaseIcon).exists()).toBe(true) + expect(wrapper.findComponent(OsIcon).exists()).toBe(true) }) it('renders the count', () => { @@ -27,15 +28,15 @@ describe('CounterIcon.vue', () => { }) }) - describe('given a valid icon name and count above 100', () => { + describe('given a valid icon and count above 100', () => { beforeEach(() => { - propsData = { icon: 'comments', count: 750 } + propsData = { icon: ocelotIcons.comments, count: 750 } wrapper = Wrapper() count = wrapper.find('.count') }) it('renders the icon', () => { - expect(wrapper.findComponent(BaseIcon).exists()).toBe(true) + expect(wrapper.findComponent(OsIcon).exists()).toBe(true) }) it('renders the capped count with a plus', () => { diff --git a/webapp/components/_new/generic/CounterIcon/CounterIcon.vue b/webapp/components/_new/generic/CounterIcon/CounterIcon.vue index 221f0b599..9c3ced1fe 100644 --- a/webapp/components/_new/generic/CounterIcon/CounterIcon.vue +++ b/webapp/components/_new/generic/CounterIcon/CounterIcon.vue @@ -1,14 +1,17 @@ diff --git a/webapp/components/_new/generic/ProfileAvatar/ProfileAvatar.spec.js b/webapp/components/_new/generic/ProfileAvatar/ProfileAvatar.spec.js index 10af4663d..8d94ffab5 100644 --- a/webapp/components/_new/generic/ProfileAvatar/ProfileAvatar.spec.js +++ b/webapp/components/_new/generic/ProfileAvatar/ProfileAvatar.spec.js @@ -1,6 +1,6 @@ import { mount } from '@vue/test-utils' import ProfileAvatar from './ProfileAvatar' -import BaseIcon from '~/components/_new/generic/BaseIcon/BaseIcon' +import { OsIcon } from '@ocelot-social/ui' const localVue = global.localVue @@ -20,7 +20,7 @@ describe('ProfileAvatar', () => { }) it('renders an icon', () => { - expect(wrapper.findComponent(BaseIcon).exists()).toBe(true) + expect(wrapper.findComponent(OsIcon).exists()).toBe(true) }) describe('given a profile', () => { @@ -38,7 +38,7 @@ describe('ProfileAvatar', () => { it('renders an icon', () => { propsData = { profile: { name: null } } wrapper = Wrapper() - expect(wrapper.findComponent(BaseIcon).exists()).toBe(true) + expect(wrapper.findComponent(OsIcon).exists()).toBe(true) }) }) @@ -46,7 +46,7 @@ describe('ProfileAvatar', () => { it('renders an icon', () => { propsData = { profile: { name: 'Anonymous' } } wrapper = Wrapper() - expect(wrapper.findComponent(BaseIcon).exists()).toBe(true) + expect(wrapper.findComponent(OsIcon).exists()).toBe(true) }) }) diff --git a/webapp/components/_new/generic/ProfileAvatar/ProfileAvatar.vue b/webapp/components/_new/generic/ProfileAvatar/ProfileAvatar.vue index 67cd6824d..27f6fa8b4 100644 --- a/webapp/components/_new/generic/ProfileAvatar/ProfileAvatar.vue +++ b/webapp/components/_new/generic/ProfileAvatar/ProfileAvatar.vue @@ -1,8 +1,8 @@