From 4f4f2e469685d975438a33122576f378c1d12cfd Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Thu, 19 Feb 2026 09:06:48 +0100 Subject: [PATCH] refactor(package/ui): os-card (#9246) --- .../I_can_visit_the_post_page.js | 2 +- ..._Post_from_the_content_menu_of_the_post.js | 2 +- .../the_post_was_saved_successfully.js | 2 +- ...t_image_should_not_be_displayed_anymore.js | 4 +- ...essfully_with_the_{string}_teaser_image.js | 2 +- ...ved_successfully_without_a_teaser_image.js | 4 +- ...ws_up_on_the_newsfeed_at_position_{int}.js | 2 +- .../I_should_not_see_{string}_button.js | 2 +- .../I_should_see_the_{string}_button.js | 2 +- .../they_should_not_see_the_comment_form.js | 2 +- ...the_list_of_posts_of_this_user_is_empty.js | 2 +- .../I_cannot_upload_a_picture.js | 2 +- ...et_removed_from_his_follower_collection.js | 4 +- packages/ui/PROJEKT.md | 58 ++++- packages/ui/package.json | 2 +- .../ui/src/components/OsCard/OsCard.spec.ts | 191 ++++++++++++++ .../src/components/OsCard/OsCard.stories.ts | 161 ++++++++++++ .../components/OsCard/OsCard.visual.spec.ts | 88 +++++++ packages/ui/src/components/OsCard/OsCard.vue | 104 ++++++++ .../__screenshots__/chromium/custom-class.png | Bin 0 -> 2741 bytes .../__screenshots__/chromium/hero-image.png | Bin 0 -> 23518 bytes .../__screenshots__/chromium/highlight.png | Bin 0 -> 10364 bytes .../chromium/simple-wrapper.png | Bin 0 -> 14271 bytes packages/ui/src/components/OsCard/index.ts | 1 + packages/ui/src/components/index.ts | 1 + webapp/assets/styles/main.scss | 53 +++- webapp/components/CommentCard/CommentCard.vue | 11 +- webapp/components/CommentForm/CommentForm.vue | 7 +- .../ContributionForm/ContributionForm.vue | 89 ++++--- webapp/components/DeleteData/DeleteData.vue | 8 +- .../components/Editor/ContentViewer.story.js | 7 +- webapp/components/Editor/Editor.story.js | 7 +- .../FilterMenu/LanguagesFilter.spec.js.old | 85 ------- .../FilterMenu/LanguagesFilter.vue.old | 57 ----- webapp/components/Group/GroupTeaser.vue | 9 +- .../HashtagsFilter/HashtagsFilter.spec.js | 3 +- .../HashtagsFilter/HashtagsFilter.vue | 12 +- webapp/components/LoginForm/LoginForm.vue | 97 +++---- .../NotificationsTable/NotificationsTable.vue | 129 +++++----- webapp/components/PostTeaser/PostTeaser.vue | 36 ++- .../Registration/RegistrationSlider.vue | 52 ++-- webapp/components/SocialMedia/SocialMedia.vue | 11 +- webapp/components/Uploader/ImageUploader.vue | 14 +- .../features/InternalPage/InternalPage.vue | 14 +- .../features/SearchResults/SearchResults.vue | 14 +- .../_new/generic/BaseCard/BaseCard.vue | 133 ---------- .../generic/TabNavigation/TabNavigation.vue | 11 +- .../TabNavigation/TabNavigator.story.js | 10 +- .../features/ProfileList/ProfileList.vue | 9 +- .../features/ReportList/ReportList.story.js | 7 +- .../features/ReportList/ReportList.vue | 6 +- .../source/nuxt.config.maintenance.js | 1 - webapp/maintenance/source/pages/index.vue | 6 +- webapp/nuxt.config.js | 1 - .../pages/__snapshots__/_static.spec.js.snap | 8 +- webapp/pages/admin/categories.spec.js | 2 +- webapp/pages/admin/categories.vue | 8 +- webapp/pages/admin/donations.spec.js | 2 +- webapp/pages/admin/donations.vue | 8 +- webapp/pages/admin/hashtags.spec.js | 2 +- webapp/pages/admin/hashtags.vue | 6 +- webapp/pages/admin/index.vue | 7 +- webapp/pages/admin/invite.vue | 6 +- webapp/pages/admin/notifications.spec.js | 2 +- webapp/pages/admin/notifications.vue | 6 +- webapp/pages/admin/organizations.spec.js | 2 +- webapp/pages/admin/organizations.vue | 6 +- webapp/pages/admin/pages.spec.js | 2 +- webapp/pages/admin/pages.vue | 6 +- webapp/pages/admin/settings.spec.js | 2 +- webapp/pages/admin/settings.vue | 6 +- .../users/__snapshots__/_id.spec.js.snap | 8 +- .../users/__snapshots__/index.spec.js.snap | 32 +-- webapp/pages/admin/users/_id.vue | 6 +- webapp/pages/admin/users/index.vue | 17 +- .../_id/__snapshots__/_slug.spec.js.snap | 240 +++++++----------- webapp/pages/groups/_id/_slug.vue | 18 +- webapp/pages/groups/create.vue | 6 +- .../_id/__snapshots__/invites.spec.js.snap | 8 +- webapp/pages/groups/edit/_id/index.vue | 6 +- webapp/pages/groups/edit/_id/invites.vue | 6 +- webapp/pages/groups/edit/_id/members.vue | 6 +- webapp/pages/moderation/index.spec.js | 2 +- webapp/pages/notifications/index.vue | 7 +- webapp/pages/password-reset.vue | 18 +- webapp/pages/post/_id/_slug/index.vue | 15 +- .../_id/__snapshots__/_slug.spec.js.snap | 64 ++--- webapp/pages/profile/_id/_slug.vue | 7 +- .../__snapshots__/badges.spec.js.snap | 108 +------- .../__snapshots__/notifications.spec.js.snap | 8 +- webapp/pages/settings/badges.vue | 8 +- webapp/pages/settings/blocked-users.vue | 15 +- webapp/pages/settings/data-download.spec.js | 2 +- webapp/pages/settings/data-download.vue | 11 +- webapp/pages/settings/delete-account.spec.js | 2 +- webapp/pages/settings/embeds.spec.js | 2 +- webapp/pages/settings/embeds.vue | 8 +- webapp/pages/settings/index.vue | 7 +- webapp/pages/settings/invites.spec.js | 2 +- webapp/pages/settings/invites.vue | 6 +- webapp/pages/settings/languages.spec.js | 2 +- webapp/pages/settings/languages.vue | 6 +- webapp/pages/settings/muted-users.vue | 15 +- .../settings/my-email-address/enter-nonce.vue | 8 +- .../pages/settings/my-email-address/index.vue | 11 +- .../settings/my-email-address/verify.vue | 6 +- .../pages/settings/my-organizations.spec.js | 2 +- webapp/pages/settings/my-organizations.vue | 6 +- webapp/pages/settings/my-social-media.vue | 6 +- webapp/pages/settings/notifications.vue | 8 +- webapp/pages/settings/privacy.spec.js | 2 +- webapp/pages/settings/privacy.vue | 8 +- webapp/pages/settings/security.spec.js | 2 +- webapp/pages/settings/security.vue | 6 +- webapp/pages/terms-and-conditions-confirm.vue | 10 +- webapp/plugins/base-components.js | 11 - webapp/test/testSetup.js | 2 - 117 files changed, 1315 insertions(+), 1059 deletions(-) create mode 100644 packages/ui/src/components/OsCard/OsCard.spec.ts create mode 100644 packages/ui/src/components/OsCard/OsCard.stories.ts create mode 100644 packages/ui/src/components/OsCard/OsCard.visual.spec.ts create mode 100644 packages/ui/src/components/OsCard/OsCard.vue create mode 100644 packages/ui/src/components/OsCard/__screenshots__/chromium/custom-class.png create mode 100644 packages/ui/src/components/OsCard/__screenshots__/chromium/hero-image.png create mode 100644 packages/ui/src/components/OsCard/__screenshots__/chromium/highlight.png create mode 100644 packages/ui/src/components/OsCard/__screenshots__/chromium/simple-wrapper.png create mode 100644 packages/ui/src/components/OsCard/index.ts delete mode 100644 webapp/components/FilterMenu/LanguagesFilter.spec.js.old delete mode 100644 webapp/components/FilterMenu/LanguagesFilter.vue.old delete mode 100644 webapp/components/_new/generic/BaseCard/BaseCard.vue delete mode 100644 webapp/plugins/base-components.js diff --git a/cypress/support/step_definitions/Moderation.ReportContent/I_can_visit_the_post_page.js b/cypress/support/step_definitions/Moderation.ReportContent/I_can_visit_the_post_page.js index 2986a8fc8..a9b8ad33c 100644 --- a/cypress/support/step_definitions/Moderation.ReportContent/I_can_visit_the_post_page.js +++ b/cypress/support/step_definitions/Moderation.ReportContent/I_can_visit_the_post_page.js @@ -3,5 +3,5 @@ import { defineStep } from '@badeball/cypress-cucumber-preprocessor' defineStep('I can visit the post page', () => { cy.contains('Fake news').click() cy.location('pathname').should('contain', '/post') - .get('.base-card .title').should('contain', 'Fake news') + .get('.os-card .title').should('contain', 'Fake news') }) diff --git a/cypress/support/step_definitions/Moderation.ReportContent/I_click_on_Report_Post_from_the_content_menu_of_the_post.js b/cypress/support/step_definitions/Moderation.ReportContent/I_click_on_Report_Post_from_the_content_menu_of_the_post.js index 810bf52b8..7f7eb8664 100644 --- a/cypress/support/step_definitions/Moderation.ReportContent/I_click_on_Report_Post_from_the_content_menu_of_the_post.js +++ b/cypress/support/step_definitions/Moderation.ReportContent/I_click_on_Report_Post_from_the_content_menu_of_the_post.js @@ -1,7 +1,7 @@ import { defineStep } from '@badeball/cypress-cucumber-preprocessor' defineStep('I click on "Report Post" from the content menu of the post', () => { - cy.contains('.base-card', 'The Truth about the Holocaust') + cy.contains('.os-card', 'The Truth about the Holocaust') .find('[data-test="content-menu-button"]') .click() diff --git a/cypress/support/step_definitions/Post.Create/the_post_was_saved_successfully.js b/cypress/support/step_definitions/Post.Create/the_post_was_saved_successfully.js index 4850ab432..49769af9d 100644 --- a/cypress/support/step_definitions/Post.Create/the_post_was_saved_successfully.js +++ b/cypress/support/step_definitions/Post.Create/the_post_was_saved_successfully.js @@ -2,7 +2,7 @@ import { defineStep } from '@badeball/cypress-cucumber-preprocessor' defineStep('the post was saved successfully', () => { cy.task('getValue', 'lastPost').then(lastPost => { - cy.get('.base-card > .title').should('contain', lastPost.title) + cy.get('.os-card .title').should('contain', lastPost.title) cy.get('.content').should('contain', lastPost.content) }) }) diff --git a/cypress/support/step_definitions/Post.Images/the_first_image_should_not_be_displayed_anymore.js b/cypress/support/step_definitions/Post.Images/the_first_image_should_not_be_displayed_anymore.js index f2188a28a..1f8a87d35 100644 --- a/cypress/support/step_definitions/Post.Images/the_first_image_should_not_be_displayed_anymore.js +++ b/cypress/support/step_definitions/Post.Images/the_first_image_should_not_be_displayed_anymore.js @@ -1,9 +1,7 @@ import { defineStep } from '@badeball/cypress-cucumber-preprocessor' defineStep('the first image should not be displayed anymore', () => { - cy.get('.hero-image') - .children() - .get('.hero-image > .image') + cy.get('.os-card__hero-image > .image') .should('have.length', 1) .and('have.attr', 'src') }) diff --git a/cypress/support/step_definitions/Post.Images/the_post_was_saved_successfully_with_the_{string}_teaser_image.js b/cypress/support/step_definitions/Post.Images/the_post_was_saved_successfully_with_the_{string}_teaser_image.js index fdfb1c84a..e3d0b5bee 100644 --- a/cypress/support/step_definitions/Post.Images/the_post_was_saved_successfully_with_the_{string}_teaser_image.js +++ b/cypress/support/step_definitions/Post.Images/the_post_was_saved_successfully_with_the_{string}_teaser_image.js @@ -1,7 +1,7 @@ import { defineStep } from '@badeball/cypress-cucumber-preprocessor' defineStep('the post was saved successfully with the {string} teaser image', condition => { - cy.get('.base-card > .title') + cy.get('.os-card .title') .should('contain', condition === 'updated' ? 'to be updated' : 'new post') .get('.content') .should('contain', condition === 'updated' ? 'successfully updated' : 'new post content') diff --git a/cypress/support/step_definitions/Post.Images/the_{string}_post_was_saved_successfully_without_a_teaser_image.js b/cypress/support/step_definitions/Post.Images/the_{string}_post_was_saved_successfully_without_a_teaser_image.js index 39947d029..a5a7a68b3 100644 --- a/cypress/support/step_definitions/Post.Images/the_{string}_post_was_saved_successfully_without_a_teaser_image.js +++ b/cypress/support/step_definitions/Post.Images/the_{string}_post_was_saved_successfully_without_a_teaser_image.js @@ -1,12 +1,12 @@ import { defineStep } from '@badeball/cypress-cucumber-preprocessor' defineStep('the {string} post was saved successfully without a teaser image', condition => { - cy.get(".base-card > .title") + cy.get(".os-card > .title") .should("contain", condition === 'updated' ? 'to be updated' : 'new post') .get(".content") .should("contain", condition === 'updated' ? 'successfully updated' : 'new post content') .get('.post-page') .should('exist') - .get('.hero-image > .image') + .get('.os-card__hero-image > .image') .should('not.exist') }) diff --git a/cypress/support/step_definitions/Post/the_post_shows_up_on_the_newsfeed_at_position_{int}.js b/cypress/support/step_definitions/Post/the_post_shows_up_on_the_newsfeed_at_position_{int}.js index 59484591f..dbe68184d 100644 --- a/cypress/support/step_definitions/Post/the_post_shows_up_on_the_newsfeed_at_position_{int}.js +++ b/cypress/support/step_definitions/Post/the_post_shows_up_on_the_newsfeed_at_position_{int}.js @@ -1,7 +1,7 @@ import { defineStep } from '@badeball/cypress-cucumber-preprocessor' defineStep('the post shows up on the newsfeed at position {int}', index => { - const selector = `.post-teaser:nth-child(${index}) > .base-card` + const selector = `.post-teaser:nth-child(${index}) > .os-card` cy.get(selector).should('contain', 'previously created post') cy.get(selector).should('contain', 'with some content') }) diff --git a/cypress/support/step_definitions/User.Block/I_should_not_see_{string}_button.js b/cypress/support/step_definitions/User.Block/I_should_not_see_{string}_button.js index ae47405f3..72665d1a0 100644 --- a/cypress/support/step_definitions/User.Block/I_should_not_see_{string}_button.js +++ b/cypress/support/step_definitions/User.Block/I_should_not_see_{string}_button.js @@ -1,6 +1,6 @@ import { defineStep } from '@badeball/cypress-cucumber-preprocessor' defineStep('I should not see {string} button', button => { - cy.get('.base-card .action-buttons') + cy.get('.os-card .action-buttons') .should('have.length', 1) }) diff --git a/cypress/support/step_definitions/User.Block/I_should_see_the_{string}_button.js b/cypress/support/step_definitions/User.Block/I_should_see_the_{string}_button.js index 88d331fa3..3362062e6 100644 --- a/cypress/support/step_definitions/User.Block/I_should_see_the_{string}_button.js +++ b/cypress/support/step_definitions/User.Block/I_should_see_the_{string}_button.js @@ -1,6 +1,6 @@ import { defineStep } from '@badeball/cypress-cucumber-preprocessor' defineStep('I should see the {string} button', button => { - cy.get('.base-card .action-buttons button') + cy.get('.os-card .action-buttons button') .should('contain', button) }) diff --git a/cypress/support/step_definitions/User.Block/they_should_not_see_the_comment_form.js b/cypress/support/step_definitions/User.Block/they_should_not_see_the_comment_form.js index b9dff833d..89216f623 100644 --- a/cypress/support/step_definitions/User.Block/they_should_not_see_the_comment_form.js +++ b/cypress/support/step_definitions/User.Block/they_should_not_see_the_comment_form.js @@ -1,5 +1,5 @@ import { defineStep } from '@badeball/cypress-cucumber-preprocessor' defineStep('they should not see the comment form', () => { - cy.get('.base-card').children().should('not.have.class', 'comment-form') + cy.get('.os-card').children().should('not.have.class', 'comment-form') }) diff --git a/cypress/support/step_definitions/User.Mute/the_list_of_posts_of_this_user_is_empty.js b/cypress/support/step_definitions/User.Mute/the_list_of_posts_of_this_user_is_empty.js index 7a2f3d7df..970994fc3 100644 --- a/cypress/support/step_definitions/User.Mute/the_list_of_posts_of_this_user_is_empty.js +++ b/cypress/support/step_definitions/User.Mute/the_list_of_posts_of_this_user_is_empty.js @@ -1,6 +1,6 @@ import { defineStep } from '@badeball/cypress-cucumber-preprocessor' defineStep('the list of posts of this user is empty', () => { - cy.get('.base-card').not('.post-link') + cy.get('.os-card').not('.post-link') cy.get('.main-container').find('.ds-space.hc-empty') }) diff --git a/cypress/support/step_definitions/UserProfile.Avatar/I_cannot_upload_a_picture.js b/cypress/support/step_definitions/UserProfile.Avatar/I_cannot_upload_a_picture.js index 792c6462c..756548952 100644 --- a/cypress/support/step_definitions/UserProfile.Avatar/I_cannot_upload_a_picture.js +++ b/cypress/support/step_definitions/UserProfile.Avatar/I_cannot_upload_a_picture.js @@ -1,7 +1,7 @@ import { defineStep } from '@badeball/cypress-cucumber-preprocessor' defineStep('I cannot upload a picture', () => { - cy.get('.base-card') + cy.get('.os-card') .children() .should('not.have.id', 'customdropzone') .should('have.class', 'profile-avatar') diff --git a/cypress/support/step_definitions/common/I_get_removed_from_his_follower_collection.js b/cypress/support/step_definitions/common/I_get_removed_from_his_follower_collection.js index 36ef62a54..0042b1874 100644 --- a/cypress/support/step_definitions/common/I_get_removed_from_his_follower_collection.js +++ b/cypress/support/step_definitions/common/I_get_removed_from_his_follower_collection.js @@ -1,8 +1,8 @@ import { defineStep } from '@badeball/cypress-cucumber-preprocessor' defineStep('I get removed from his follower collection', () => { - cy.get('.base-card') + cy.get('.os-card') .not('.post-link') cy.get('.main-container') - .contains('.base-card','is not followed by anyone') + .contains('.os-card','is not followed by anyone') }) diff --git a/packages/ui/PROJEKT.md b/packages/ui/PROJEKT.md index 4ff7c5493..8810d1c59 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: ████░░░░░░ 35% (6/17 Aufgaben) - OsButton ✅, OsIcon ✅, System-Icons ✅, BaseIcon→OsIcon Migration ✅, OsSpinner ✅, Spinner Webapp-Migration ✅ +Phase 4: █████░░░░░ 47% (8/17 Aufgaben) - OsButton ✅, OsIcon ✅, System-Icons ✅, BaseIcon→OsIcon Migration ✅, OsSpinner ✅, Spinner Webapp-Migration ✅, OsCard ✅, BaseCard→OsCard Migration ✅ Phase 5: ░░░░░░░░░░ 0% (0/7 Aufgaben) ─────────────────────────────────────── -Gesamt: ████████░░ 79% (68/86 Aufgaben) +Gesamt: ████████░░ 81% (70/86 Aufgaben) ``` ### Katalogisierung (Details in KATALOG.md) @@ -142,6 +142,23 @@ OsSpinner: ├─ vue-compat: ✅ h() Render-Function mit isVue2 └─ webapp: ✅ 4 Spinner migriert (DsSpinner + LoadingSpinner → OsSpinner) +BaseCard → OsCard Webapp-Migration: ✅ +├─ ~30 Webapp-Dateien: (lokale Imports) +├─ 3 Template-Dateien: #imageColumn/#topMenu Slots → inline Layout mit --columns CSS +├─ 16 Spec-Dateien: wrapper.classes('base-card') → wrapper.classes('os-card') +├─ 4 Story-Dateien: mit Import +├─ 12 Cypress E2E-Dateien: .base-card → .os-card Selektoren +├─ 2 Cypress-Dateien: .hero-image → .os-card__hero-image +├─ BaseCard.vue Komponente gelöscht +├─ base-components.js Plugin gelöscht (keine Base*.vue mehr) +├─ nuxt.config.js, maintenance config, testSetup.js bereinigt +├─ main.scss: .os-card Regeln (title, ds-section, hero-image, --columns Layout) +├─ CSS Fixes: Tailwind p-6 Override (!important), outline statt border (highlight), +│ child selectors → descendant selectors (hero-image content wrapper) +├─ ContributionForm: Media-Query Selektoren auf .os-card__content korrigiert +├─ ProfileList: .profile-list.os-card Spezifität erhöht (0,3,0 vs 0,2,0) +└─ 0 Template-Nutzungen verbleibend + DsSpinner/LoadingSpinner → OsSpinner Webapp-Migration: ✅ ├─ ImageUploader.vue: LoadingSpinner → OsSpinner (size="lg") ├─ pages/profile: ds-spinner → os-spinner (size="lg") @@ -166,11 +183,30 @@ BaseIcon → OsIcon Webapp-Migration: ✅ ## Aktueller Stand -**Letzte Aktualisierung:** 2026-02-18 (Session 24) +**Letzte Aktualisierung:** 2026-02-19 (Session 25) -**Aktuelle Phase:** Phase 4 - OsIcon ✅, BaseIcon → OsIcon Migration ✅, OsSpinner ✅, Spinner Webapp-Migration ✅ +**Aktuelle Phase:** Phase 4 - OsIcon ✅, BaseIcon → OsIcon Migration ✅, OsSpinner ✅, Spinner Webapp-Migration ✅, OsCard ✅, BaseCard → OsCard Migration ✅ -**Zuletzt abgeschlossen (Session 24 - OsSpinner Webapp-Migration + Refactoring):** +**Zuletzt abgeschlossen (Session 25 - BaseCard → OsCard Webapp-Migration):** +- [x] ~30 Webapp-Dateien: `` → `` mit lokalen Imports +- [x] 3 Template-Dateien mit #imageColumn/#topMenu Slots → inline Layout (LoginForm, RegistrationSlider, password-reset) +- [x] CSS: `.os-card.--columns` Layout in main.scss (flex, image-column, content-column, top-menu, responsive) +- [x] 16 Spec-Dateien: `wrapper.classes('base-card')` → `wrapper.classes('os-card')` +- [x] 4 Story-Dateien: `` → `` mit OsCard-Import +- [x] 12 Cypress E2E Step-Definitions: `.base-card` → `.os-card` Selektoren +- [x] 2 Cypress-Dateien: `.hero-image` → `.os-card__hero-image` +- [x] BaseCard.vue Komponente gelöscht +- [x] `base-components.js` Plugin gelöscht (keine Base*.vue Komponenten mehr) +- [x] Plugin-Referenzen entfernt: nuxt.config.js, nuxt.config.maintenance.js, testSetup.js +- [x] main.scss bereinigt: `.base-card > .ds-section` entfernt, `.os-card` Regeln hinzugefügt +- [x] CSS-Fixes: Tailwind `p-6` Override (`!important`), `outline` statt `border` (highlight), child → descendant selectors +- [x] ContributionForm: Media-Query Selektoren auf `.os-card__content > .buttons-footer` korrigiert +- [x] ProfileList: Spezifität `.profile-list.os-card` erhöht (0,3,0 vs 0,2,0) +- [x] OsCard highlight Tests: `border` → `outline-1` (twMerge), Testnamen aktualisiert +- [x] Kleinere Verbesserungen: SocialMedia Props typisiert, LoginForm querySelector statt fragiler DOM-Traversierung, redundante `` entfernt, NotificationsTable optional chaining +- [x] `hasBaseCard` Property verbleibt in 4 Dateien (rein semantisch, kein Komponentenbezug) + +**Zuvor abgeschlossen (Session 24 - OsSpinner Webapp-Migration + Refactoring):** - [x] OsButton refactored: nutzt `h(OsSpinner, { 'aria-hidden': 'true' })` statt Inline-SVG - [x] OsSpinner: Decorative-Modus (`aria-hidden="true"` unterdrückt role/aria-label) - [x] `ButtonSize` Type exportiert (sm/md/lg/xl), `types.d.ts` Kommentar aktualisiert @@ -263,8 +299,8 @@ BaseIcon → OsIcon Webapp-Migration: ✅ **Nächste Schritte:** - [x] OsSpinner Webapp-Migration (DsSpinner + LoadingSpinner → OsSpinner) ✅ -- [ ] OsCard Komponente (vereint DsCard + BaseCard) -- [ ] Weitere Tier 1 Komponenten +- [x] OsCard Komponente + BaseCard → OsCard Webapp-Migration ✅ +- [ ] Weitere Tier 2 Komponenten (OsModal, OsDropdown, OsAvatar, OsInput) - [ ] 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):** @@ -519,7 +555,7 @@ Jeder migrierte Button muss manuell geprüft werden: Normal, Hover, Focus, Activ - [x] OsSpinner (vereint DsSpinner + LoadingSpinner) ✅ OsButton nutzt OsSpinner als Komponente - [x] OsSpinner Webapp-Migration ✅ 4 Spinner migriert, LoadingSpinner gelöscht, Admin ApolloQuery→$apollo.loading - [x] OsButton (vereint DsButton + BaseButton) ✅ Entwickelt in Phase 2 -- [ ] OsCard (vereint DsCard + BaseCard) +- [x] OsCard (vereint DsCard + BaseCard) ✅ Webapp-Migration abgeschlossen, BaseCard gelöscht **Tier 2: Layout & Feedback** - [ ] OsModal (Basis: DsModal) @@ -1629,6 +1665,12 @@ Bei der Migration werden: | 2026-02-18 | **Admin Spinner Fix** | `` → `apollo`-Option + `$apollo.loading`; SSR-Prefetch verhinderte Loading-State im Client | | 2026-02-18 | **filterStatistics Fix** | `delete data.__typename` → Destructuring `{ __typename, ...rest }` (keine Mutation des Originalobjekts) | | 2026-02-18 | **infinite-loading Spinner-Slot** | OsSpinner im `spinner`-Slot von vue-infinite-loading in 3 Seiten (index, profile, groups); einheitliches Spinner-Design | +| 2026-02-19 | **BaseCard → OsCard Migration** | ~30 Webapp-Dateien: `` → `` mit lokalen Imports; CSS-Fixes für Tailwind p-6 Override, outline highlight, child→descendant selectors | +| 2026-02-19 | **#imageColumn/#topMenu inline** | LoginForm, RegistrationSlider, password-reset: BaseCard-Slots → inline Layout mit `.os-card.--columns` CSS in main.scss | +| 2026-02-19 | **Tests & Stories migriert** | 16 Spec-Dateien, 4 Story-Dateien, 12+2 Cypress E2E Step-Definitions: base-card → os-card Selektoren | +| 2026-02-19 | **BaseCard gelöscht** | BaseCard.vue Komponente + base-components.js Plugin entfernt; nuxt.config, maintenance config, testSetup bereinigt | +| 2026-02-19 | **CSS-Fixes** | ContributionForm Media-Query Selektoren, ProfileList Spezifität, InternalPage $space-small, OsCard highlight outline-1 Tests | +| 2026-02-19 | **Code-Quality** | SocialMedia Props typisiert, LoginForm querySelector, redundante client-only entfernt, NotificationsTable optional chaining, HashtagsFilter doppeltes Mounting | --- diff --git a/packages/ui/package.json b/packages/ui/package.json index 140724e9f..c11476594 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -144,7 +144,7 @@ "size-limit": [ { "path": "dist/index.mjs", - "limit": "15 kB", + "limit": "20 kB", "brotli": true }, { diff --git a/packages/ui/src/components/OsCard/OsCard.spec.ts b/packages/ui/src/components/OsCard/OsCard.spec.ts new file mode 100644 index 000000000..92b411d13 --- /dev/null +++ b/packages/ui/src/components/OsCard/OsCard.spec.ts @@ -0,0 +1,191 @@ +import { mount } from '@vue/test-utils' +import { describe, expect, it } from 'vitest' + +import OsCard from './OsCard.vue' + +describe('osCard', () => { + describe('rendering', () => { + it('renders as div element by default', () => { + const wrapper = mount(OsCard) + + expect((wrapper.element as HTMLElement).tagName).toBe('DIV') + }) + + it('renders default slot content', () => { + const wrapper = mount(OsCard, { + slots: { default: '

Card content

' }, + }) + + expect(wrapper.find('p').text()).toBe('Card content') + }) + + it('renders without content', () => { + const wrapper = mount(OsCard) + + expect(wrapper.exists()).toBe(true) + expect(wrapper.text()).toBe('') + }) + }) + + describe('as prop', () => { + it('renders as article when as="article"', () => { + const wrapper = mount(OsCard, { + props: { as: 'article' }, + }) + + expect((wrapper.element as HTMLElement).tagName).toBe('ARTICLE') + }) + + it('renders as section when as="section"', () => { + const wrapper = mount(OsCard, { + props: { as: 'section' }, + }) + + expect((wrapper.element as HTMLElement).tagName).toBe('SECTION') + }) + + it('renders as aside when as="aside"', () => { + const wrapper = mount(OsCard, { + props: { as: 'aside' }, + }) + + expect((wrapper.element as HTMLElement).tagName).toBe('ASIDE') + }) + }) + + describe('css', () => { + it('has os-card class', () => { + const wrapper = mount(OsCard) + + expect(wrapper.classes()).toContain('os-card') + }) + + it('has padding when no heroImage slot', () => { + const wrapper = mount(OsCard) + + expect(wrapper.classes()).toContain('p-6') + }) + + it('merges custom classes', () => { + const wrapper = mount(OsCard, { + attrs: { class: 'my-custom-class' }, + }) + + expect(wrapper.classes()).toContain('os-card') + expect(wrapper.classes()).toContain('my-custom-class') + }) + + it('passes through attributes', () => { + const wrapper = mount(OsCard, { + attrs: { 'data-testid': 'my-card' }, + }) + + expect(wrapper.attributes('data-testid')).toBe('my-card') + }) + }) + + describe('highlight', () => { + it('does not have outline class by default', () => { + const wrapper = mount(OsCard) + + expect(wrapper.classes()).not.toContain('outline-1') + }) + + it('adds outline class when highlight is true', () => { + const wrapper = mount(OsCard, { + props: { highlight: true }, + }) + + expect(wrapper.classes()).toContain('outline-1') + }) + + it('does not add outline class when highlight is false', () => { + const wrapper = mount(OsCard, { + props: { highlight: false }, + }) + + expect(wrapper.classes()).not.toContain('outline-1') + }) + }) + + describe('heroImage slot', () => { + const heroSlots = { + heroImage: 'Hero', + default: '

Content

', + } + + it('renders heroImage slot content', () => { + const wrapper = mount(OsCard, { slots: heroSlots }) + + expect(wrapper.find('img').exists()).toBe(true) + expect(wrapper.find('img').attributes('alt')).toBe('Hero') + }) + + it('wraps heroImage in os-card__hero-image div', () => { + const wrapper = mount(OsCard, { slots: heroSlots }) + + const heroDiv = wrapper.find('.os-card__hero-image') + + expect(heroDiv.exists()).toBe(true) + expect(heroDiv.find('img').exists()).toBe(true) + }) + + it('wraps default content in os-card__content div', () => { + const wrapper = mount(OsCard, { slots: heroSlots }) + + const contentDiv = wrapper.find('.os-card__content') + + expect(contentDiv.exists()).toBe(true) + expect(contentDiv.find('p').text()).toBe('Content') + }) + + it('does not have padding on card when heroImage is present', () => { + const wrapper = mount(OsCard, { slots: heroSlots }) + + expect(wrapper.classes()).not.toContain('p-6') + }) + + it('content wrapper has padding when heroImage is present', () => { + const wrapper = mount(OsCard, { slots: heroSlots }) + + expect(wrapper.find('.os-card__content').classes()).toContain('p-6') + }) + + it('does not create wrapper divs without heroImage slot', () => { + const wrapper = mount(OsCard, { + slots: { default: '

Content

' }, + }) + + expect(wrapper.find('.os-card__hero-image').exists()).toBe(false) + expect(wrapper.find('.os-card__content').exists()).toBe(false) + }) + + it('renders heroImage before content', () => { + const wrapper = mount(OsCard, { slots: heroSlots }) + + const heroImage = wrapper.find('.os-card__hero-image') + const content = wrapper.find('.os-card__content') + + expect(heroImage.exists()).toBe(true) + expect(content.exists()).toBe(true) + expect( + heroImage.element.compareDocumentPosition(content.element) & + Node.DOCUMENT_POSITION_FOLLOWING, + ).not.toBe(0) + }) + }) + + describe('keyboard accessibility', () => { + it('is not focusable (non-interactive element)', () => { + const wrapper = mount(OsCard) + + expect(wrapper.attributes('tabindex')).toBeUndefined() + }) + + it('has no interactive role', () => { + const wrapper = mount(OsCard) + + expect(wrapper.attributes('role')).toBeUndefined() + }) + }) +}) diff --git a/packages/ui/src/components/OsCard/OsCard.stories.ts b/packages/ui/src/components/OsCard/OsCard.stories.ts new file mode 100644 index 000000000..012d625f7 --- /dev/null +++ b/packages/ui/src/components/OsCard/OsCard.stories.ts @@ -0,0 +1,161 @@ +import { computed } from 'vue' + +import OsCard from './OsCard.vue' + +import type { Meta, StoryObj } from '@storybook/vue3-vite' + +const HERO_SVG = + "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='200'%3E%3Crect fill='%234a5a9e' width='400' height='200'/%3E%3Ctext x='200' y='105' text-anchor='middle' fill='white' font-size='20' font-family='sans-serif'%3EHero Image%3C/text%3E%3C/svg%3E" + +const meta: Meta = { + title: 'Components/OsCard', + component: OsCard, + tags: ['autodocs'], +} + +export default meta +type Story = StoryObj + +interface PlaygroundArgs { + as: string + highlight: boolean + heroImage: boolean + content: string +} + +export const Playground: StoryObj = { + argTypes: { + as: { + control: 'select', + options: ['div', 'article', 'section', 'aside'], + }, + highlight: { + control: 'boolean', + }, + heroImage: { + control: 'boolean', + }, + content: { + control: 'text', + }, + }, + args: { + as: 'div', + highlight: false, + heroImage: false, + content: 'This is a card with customizable props. Try toggling the controls below.', + }, + render: (args) => ({ + components: { OsCard }, + setup() { + const cardProps = computed(() => ({ + as: args.as, + highlight: args.highlight, + })) + const showHero = computed(() => args.heroImage) + const content = computed(() => args.content) + return { cardProps, showHero, content, HERO_SVG } + }, + template: ` +
+ + +

{{ content }}

+
+
+ `, + }), +} + +export const SimpleWrapper: Story = { + render: () => ({ + components: { OsCard }, + template: ` +
+ +

Card Title

+

Some card content goes here. Cards provide a contained surface for related information.

+
+ +

A minimal card with just text.

+
+ +

Another Card

+

Cards stack naturally in a flex column layout.

+
+
+ `, + }), +} + +export const CustomClass: Story = { + render: () => ({ + components: { OsCard }, + template: ` +
+ +

Centered content via custom class.

+
+
+ `, + }), +} + +export const Highlight: Story = { + render: () => ({ + components: { OsCard }, + template: ` +
+ +

Pinned Post

+

This card is highlighted with a colored border, used for pinned or featured content.

+
+ +

Normal Post

+

This card has no highlight for comparison.

+
+
+ `, + }), +} + +export const HeroImage: Story = { + render: () => ({ + components: { OsCard }, + setup() { + return { HERO_SVG } + }, + template: ` +
+ + +

Pinned Post with Image

+

Combines hero image, highlight border, and article semantics.

+
+ + +

Post with Hero Image

+

The image spans the full card width. Content below has its own padding.

+
+ +

Card without Image

+

A regular card for comparison.

+
+
+ `, + }), +} diff --git a/packages/ui/src/components/OsCard/OsCard.visual.spec.ts b/packages/ui/src/components/OsCard/OsCard.visual.spec.ts new file mode 100644 index 000000000..dd073f8e0 --- /dev/null +++ b/packages/ui/src/components/OsCard/OsCard.visual.spec.ts @@ -0,0 +1,88 @@ +import { AxeBuilder } from '@axe-core/playwright' +import { expect, test } from '@playwright/test' + +import type { Page } from '@playwright/test' + +const STORY_URL = '/iframe.html?id=components-oscard' +const STORY_ROOT = '#storybook-root' + +async function waitForReady(page: Page) { + await page.evaluate(async () => document.fonts.ready) +} + +async function checkA11y(page: Page) { + const results = await new AxeBuilder({ page }).include(STORY_ROOT).analyze() + + expect(results.violations).toEqual([]) +} + +test.describe('OsCard keyboard accessibility', () => { + test('card is not focusable (non-interactive element)', async ({ page }) => { + await page.goto(`${STORY_URL}--simple-wrapper&viewMode=story`) + const root = page.locator(STORY_ROOT) + await root.waitFor() + + const cards = root.locator('.os-card') + const count = await cards.count() + + expect(count).toBeGreaterThan(0) + + for (let i = 0; i < count; i++) { + await expect(cards.nth(i)).not.toHaveAttribute('tabindex') + await expect(cards.nth(i)).not.toHaveAttribute('role') + } + + await page.keyboard.press('Tab') + for (let i = 0; i < count; i++) { + await expect(cards.nth(i)).not.toBeFocused() + } + }) +}) + +test.describe('OsCard visual regression', () => { + test('simple wrapper', async ({ page }) => { + await page.goto(`${STORY_URL}--simple-wrapper&viewMode=story`) + const root = page.locator(STORY_ROOT) + await root.waitFor() + await waitForReady(page) + + await expect(root.locator('[data-testid="simple-wrapper"]')).toHaveScreenshot( + 'simple-wrapper.png', + ) + + await checkA11y(page) + }) + + test('custom class', async ({ page }) => { + await page.goto(`${STORY_URL}--custom-class&viewMode=story`) + const root = page.locator(STORY_ROOT) + await root.waitFor() + await waitForReady(page) + + await expect(root.locator('[data-testid="custom-class"]')).toHaveScreenshot('custom-class.png') + + await checkA11y(page) + }) + + test('highlight', async ({ page }) => { + await page.goto(`${STORY_URL}--highlight&viewMode=story`) + const root = page.locator(STORY_ROOT) + await root.waitFor() + await waitForReady(page) + + await expect(root.locator('[data-testid="highlight"]')).toHaveScreenshot('highlight.png') + + await checkA11y(page) + }) + + test('hero image', async ({ page }) => { + await page.goto(`${STORY_URL}--hero-image&viewMode=story`) + const root = page.locator(STORY_ROOT) + await root.waitFor() + await waitForReady(page) + + await expect(root.locator('[data-testid="hero-image"]')).toHaveScreenshot('hero-image.png') + + await checkA11y(page) + }) +}) diff --git a/packages/ui/src/components/OsCard/OsCard.vue b/packages/ui/src/components/OsCard/OsCard.vue new file mode 100644 index 000000000..ceebadcd9 --- /dev/null +++ b/packages/ui/src/components/OsCard/OsCard.vue @@ -0,0 +1,104 @@ + diff --git a/packages/ui/src/components/OsCard/__screenshots__/chromium/custom-class.png b/packages/ui/src/components/OsCard/__screenshots__/chromium/custom-class.png new file mode 100644 index 0000000000000000000000000000000000000000..0b2fe00803cb0590b031becd9a7646eb1fb5bc21 GIT binary patch literal 2741 zcmc&$`8(8W8@FbRC1W2Z8U|??WyW$Wkr*ZvvX=dbEHNVMNVbfOV@lZ#Mn{TNM;dFk zF=Uyk6i(KmB3UN8Z11=0{U6@}r-K=b~<0+>F{9pm=QVzG3cArWA{Y3jU1_@DHHG zPDM-eh)YP+BSLhGupqel(ey630uScA6dZ9?83X=DstjUAR2q%+$`mtm&|iN6g7=wr zD5aj@<mi7Y9pg zGz1P+27P)uSZd<6i>6YkaqIKt`y1{Jc(|%6cDI?+-#|e7*8O|ZxrQiVv1_?A_Qp-Q z*0VKkWzsk+zdcA{cz0{1NH5IuiNoK|uFxtkl*p-&p8_$nNB8IX`E6 z>|U+Su>x3>*6>-z(ilzVYXvszq+^v`*BuePkWu^`v*f;t^@aRX@GGxFKtmTw3^o=% zAu+Wc&6^L@FB!?by{Fu>Jki{rtBDK~UY=}izS^IgDWfH=q7{iO8|o9h+~gQWKA+NFf;%y_C&n+ZJNnnxmHb9wP4%WzqgNEQ)dKQXP5~hhJ)I z8IN<-M2y!5y8uvL`jrSRMePC^j;uEuARdQ^J3q3zr)Y9(ePt?far7k>%-nfjVQHVd zN~fF01OQmf{%}W|=RvjosxCdg0949IE`vzSH;&hJeqF%O2_B}W`yRF>>`H%s-O!+4 z@g9!9UD6ma=ew4t<)yHU%aqo9K`*I3{=hB=<ul*u&n3#jT#^5<)CSqN#l=xCP-$Ij_Z;>g@w$^a2k)?OWK2qIXB>9PtZ+ba| z;%dJ67!$U|GO?5X)9p!4PaQibsbn?_XbxKNh-jvGftF^V#bnb>n_WJ*5Z&9;QKos68(tkhrGG%Q(bD9cK&0_;*SDi}vkdU$te@;VEG^J$#rlSV2y z*1AKR`H`NEaKgK4e4O%wQCe8(K~x*5p9FCsf4IXRzBnu8akJWajCjTu zHdqu(j^<^HQ`@@Ik9LK8eiaCJvJ;>&zM?Zuc~wdE=BOQoA&n31%1pTj*rn^hx3T+r zXjH$qdPk>rCtP(ay?Z1(rFYZB;qUWlWcJ37ktRrMcp)Z`b9W3ImNk75on;|qCO!v0 zE*&uz0T*FreRUcYW(LjVg%_Nj0Xu31FpGbTJnun!{v?Ty_!ATEh#tGUlh0$*LKxRPa6PrB1dj$ z@UYi_pwQ>nX15dlnZ-{E7)a&&j7})sZDCxY@d{1xqWHjf*6c@7PgU02}w9x}RKXyOls2My!>o;O^87z(*=gx>jzplCxdwgFZdJ zSV}f9Eem2a97P8*YLKhAQwwbc;ciE&1)aB+C+>N**WyuhCy02Cejq^sqn4UFSyhjR*r!v^z@p|dGeN<NQ?1{_|SOAStBD1&heC z*CA|kK2^IYC+&Y5lyA@_@~mUht4lH@)Mg}0&;g>khLKR2D1Gap#}~DpdRsc4-N5_s z8TNJuQ8=;gSnRlNd%X+5>^<3v*)B%Sx?(1ZB|5>oOW~c%H-C{lw}czgV$6S1q%svH6rvfpJncb#4I)x zGYmHSh>uR!-o^J|P=bC$@(koUX>mFVR;Y9fZ#lpeekxdw`o^EC*v#fUabbeQ7 ztbNDAyY|pZ2|9Wi0)goJ)1P1T7Ng~0nU#JDW@cvi>?GN!g^y3N4-r9IT>6dzIB>NLs@$>W7dQ*F7+rg?6g%5vI6ENc@s826zydh>a z0*CR?|Bop8uO!<9_FNXA)r`cD@dQH4Z%wB_d^o|z+tmRktXGnL($q)=f#Bog8(oSj z8mxqvS@m%#rCHfkNqzio0%*>|!($tR5rA>zx9a5Ox}D;uk;&aO0C>1?78lHG&AgNU E1B%E1U;qFB literal 0 HcmV?d00001 diff --git a/packages/ui/src/components/OsCard/__screenshots__/chromium/hero-image.png b/packages/ui/src/components/OsCard/__screenshots__/chromium/hero-image.png new file mode 100644 index 0000000000000000000000000000000000000000..e73fb40d1def902dcc145cfd323975b800f5aab4 GIT binary patch literal 23518 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 rul*}j&0l}C>re*u4$ke`4@ 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 literal 0 HcmV?d00001 diff --git a/packages/ui/src/components/OsCard/index.ts b/packages/ui/src/components/OsCard/index.ts new file mode 100644 index 000000000..1414d1bb5 --- /dev/null +++ b/packages/ui/src/components/OsCard/index.ts @@ -0,0 +1 @@ +export { default as OsCard } from './OsCard.vue' diff --git a/packages/ui/src/components/index.ts b/packages/ui/src/components/index.ts index f416e68f3..fad180acf 100644 --- a/packages/ui/src/components/index.ts +++ b/packages/ui/src/components/index.ts @@ -18,3 +18,4 @@ export { type SystemIconName, } from './OsIcon' export { OsSpinner, SPINNER_SIZES } from './OsSpinner' +export { OsCard } from './OsCard' diff --git a/webapp/assets/styles/main.scss b/webapp/assets/styles/main.scss index 9c43a3e65..4099d811d 100644 --- a/webapp/assets/styles/main.scss +++ b/webapp/assets/styles/main.scss @@ -151,7 +151,7 @@ body.dropdown-open { overflow: hidden; } -.base-card > .ds-section { +.os-card > .ds-section { padding: 0; margin: -$space-base; @@ -160,6 +160,57 @@ body.dropdown-open { } } +.os-card { + > .title, + > .content-column > .title { + font-size: $font-size-large; + margin-bottom: $space-x-small; + } + + > .os-card__hero-image > .image { + width: 100%; + object-fit: contain; + } + + &.--columns { + display: flex; + + > .image-column { + flex-basis: 50%; + display: flex; + justify-content: center; + align-items: center; + padding-right: $space-base; + + .image { + width: 100%; + max-width: 200px; + } + } + + > .content-column { + flex-basis: 50%; + } + + > .top-menu { + position: absolute; + top: $space-small; + left: $space-small; + } + } +} + +@media (max-width: 565px) { + .os-card.--columns { + flex-direction: column; + + > .image-column { + padding-right: 0; + margin-bottom: $space-base; + } + } +} + [class$='menu-popover'] { min-width: 130px; diff --git a/webapp/components/CommentCard/CommentCard.vue b/webapp/components/CommentCard/CommentCard.vue index aaa64dba4..f59eb1347 100644 --- a/webapp/components/CommentCard/CommentCard.vue +++ b/webapp/components/CommentCard/CommentCard.vue @@ -1,11 +1,11 @@ diff --git a/webapp/components/Group/GroupTeaser.vue b/webapp/components/Group/GroupTeaser.vue index 7f98ad3e3..7d4143f1d 100644 --- a/webapp/components/Group/GroupTeaser.vue +++ b/webapp/components/Group/GroupTeaser.vue @@ -3,7 +3,7 @@ class="group-teaser" :to="{ name: 'groups-id-slug', params: { id: group.id, slug: group.slug } }" > - - +
diff --git a/webapp/components/LoginForm/LoginForm.vue b/webapp/components/LoginForm/LoginForm.vue index 3bbff1454..6a7117943 100644 --- a/webapp/components/LoginForm/LoginForm.vue +++ b/webapp/components/LoginForm/LoginForm.vue @@ -4,58 +4,60 @@

{{ $t('quotes.african.quote') }}

- {{ $t('quotes.african.author') }} - - -

{{ $t('login.login') }}

-
- -
+ +
+

{{ $t('login.login') }}

+ - -
- - {{ $t('login.forgotPassword') }} - - - - {{ $t('login.login') }} - -

- {{ $t('login.no-account') }} - {{ $t('login.register') }} -

- - -
+ +
@@ -66,7 +68,7 @@ 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, OsIcon } from '@ocelot-social/ui' +import { OsButton, OsCard, OsIcon } from '@ocelot-social/ui' import { iconRegistry } from '~/utils/iconRegistry' import { mapGetters, mapMutations } from 'vuex' @@ -75,6 +77,7 @@ export default { LocaleSwitch, Logo, OsButton, + OsCard, OsIcon, PageParamsLink, ShowPassword, @@ -135,7 +138,7 @@ export default { toggleShowPassword() { this.showPassword = !this.showPassword this.$nextTick(() => { - this.$refs.password.$el.children[1].children[1].focus() + this.$refs.password.$el.querySelector('input').focus() this.$emit('focus') }) }, diff --git a/webapp/components/NotificationsTable/NotificationsTable.vue b/webapp/components/NotificationsTable/NotificationsTable.vue index fc1430d8b..f448334fd 100644 --- a/webapp/components/NotificationsTable/NotificationsTable.vue +++ b/webapp/components/NotificationsTable/NotificationsTable.vue @@ -18,74 +18,66 @@ - - - - - + - -
- -
- - -
- - -
- - - {{ - notification.from.title || - notification.from.groupName || - notification.from.post.title | truncate(50) - }} - - -

- {{ - notification.from.contentExcerpt || - notification.from.descriptionExcerpt | removeHtml - }} -

-
+
+ +
+ +
- + + +
+ + + {{ + notification.from.title || + notification.from.groupName || + (notification.from.post && notification.from.post.title) | truncate(50) + }} + + +

+ {{ + notification.from.contentExcerpt || + notification.from.descriptionExcerpt | removeHtml + }} +

+
+
@@ -98,7 +90,6 @@ import { OsIcon } from '@ocelot-social/ui' import { iconRegistry } from '~/utils/iconRegistry' import UserTeaser from '~/components/UserTeaser/UserTeaser' import HcEmpty from '~/components/Empty/Empty' -import BaseCard from '../_new/generic/BaseCard/BaseCard.vue' import mobile from '~/mixins/mobile' const maxMobileWidth = 768 // at this point the table breaks down @@ -109,7 +100,6 @@ export default { OsIcon, UserTeaser, HcEmpty, - BaseCard, }, props: { notifications: { type: Array, default: () => [] }, @@ -181,11 +171,6 @@ export default { .notification-grid .content-section { flex-wrap: nowrap; } -.notification-grid .base-card { - border-radius: 0; - box-shadow: none; - padding: 16px 4px; -} /* dirty fix to override broken styleguide inline-styles */ .notification-grid .ds-grid { grid-template-columns: 5fr 6fr !important; @@ -201,9 +186,9 @@ export default { &:nth-child(odd) { background-color: $color-neutral-90; } - .base-card { + + > .ds-grid > div { padding: 8px 0; - background-color: unset !important; } } @media screen and (max-width: 768px) { diff --git a/webapp/components/PostTeaser/PostTeaser.vue b/webapp/components/PostTeaser/PostTeaser.vue index 738f2a591..6d92b2126 100644 --- a/webapp/components/PostTeaser/PostTeaser.vue +++ b/webapp/components/PostTeaser/PostTeaser.vue @@ -3,7 +3,7 @@ class="post-teaser" :to="{ name: 'post-id-slug', params: { id: post.id, slug: post.slug } }" > - {{ $t('site.back-to-login') }}
- - + +
+ + - - + - + - + - - - - - - - + +
- - diff --git a/webapp/components/_new/generic/TabNavigation/TabNavigation.vue b/webapp/components/_new/generic/TabNavigation/TabNavigation.vue index ff06c42d9..7ff4cda8f 100644 --- a/webapp/components/_new/generic/TabNavigation/TabNavigation.vue +++ b/webapp/components/_new/generic/TabNavigation/TabNavigation.vue @@ -1,6 +1,6 @@ diff --git a/webapp/pages/groups/create.vue b/webapp/pages/groups/create.vue index 88350041f..f4ea5b7c3 100644 --- a/webapp/pages/groups/create.vue +++ b/webapp/pages/groups/create.vue @@ -5,7 +5,7 @@ - + @@ -16,17 +16,19 @@   - +