refactor(webapp): ds-grid (#9248)

This commit is contained in:
Ulf Gebhardt 2026-02-20 00:57:58 +01:00 committed by GitHub
parent 5ef4fecf99
commit c269e971f2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 165 additions and 130 deletions

View File

@ -194,11 +194,32 @@ Tier A ds-* → Plain HTML + CSS: ✅
## Aktueller Stand ## Aktueller Stand
**Letzte Aktualisierung:** 2026-02-19 (Session 27) **Letzte Aktualisierung:** 2026-02-19 (Session 28)
**Aktuelle Phase:** Phase 4 - OsIcon ✅, BaseIcon → OsIcon Migration ✅, OsSpinner ✅, Spinner Webapp-Migration ✅, OsCard ✅, BaseCard → OsCard Migration ✅, Tier A ds-* → Plain HTML ✅ **Aktuelle Phase:** Phase 4 - OsIcon ✅, BaseIcon → OsIcon Migration ✅, OsSpinner ✅, Spinner Webapp-Migration ✅, OsCard ✅, BaseCard → OsCard Migration ✅, Tier A ds-* → Plain HTML ✅
**Zuletzt abgeschlossen (Session 27 - Tier A: ds-* Komponenten → Plain HTML):** **Zuletzt abgeschlossen (Session 28 - CodeRabbit Review Fixes für Tier A PR):**
- [x] GroupForm.vue: Fehlende `buttons`-Klasse auf Button-Container ergänzt (CSS-Regel `.group-form > .buttons` griff nicht)
- [x] ComponentSlider.vue: `<h1>``<h3>` (Original war `<ds-heading size="h3">`, falsch zu h1 migriert)
- [x] SearchHeading.vue: `<h1>``<h5>` (Original war `<ds-heading size="h5">`, falsch zu h1 migriert)
- [x] MySomethingList.vue: Redundantes verschachteltes `<div class="ds-mt-base ds-mb-large">` entfernt (Überbleibsel von verschachtelten ds-space)
- [x] Empty.vue: Prop-Type `[String, Object]``String` (Object-Support war ds-space-Relikt, nicht implementiert)
- [x] ChangePassword.vue: Inline-Spans → separate `<p class="ds-text">` Elemente (visuelle Regression: fehlende Block-Level-Trennung)
- [x] RegistrationSlideEmail.vue: Label `for="checkbox0"``for="sendEmailAgain"` passend zu `id="sendEmailAgain"` (A11y WCAG 1.3.1/4.1.2)
- [x] Signup.vue: `margin="large"` (totes ds-space-Attribut auf div) → `class="ds-my-large"` + `<style>``<style scoped>`
- [x] SocialMedia.vue: `:key="link.id"``:key="link.url"` (link.id war immer undefined, socialMediaLinks() gibt kein id zurück)
- [x] logout.vue: Hardcodierter "Logging out..." → `$t('login.loggingOut')` mit Übersetzungen in allen 9 Sprachen
- [x] maintenance/index.vue: `alt="Under maintenance"``:alt="$t('maintenance.title', metadata)"` (i18n-Konsistenz) + `<style scoped>`
- [x] admin/index.vue: `33%``33.333%` (Rundungsfehler bei 3-Spalten-Layout)
- [x] profile/_id/_slug.vue: Inline-Style `margin-bottom: 10px; text-align: center``ds-text-center ds-mb-x-small` Utility-Klassen
- [x] post/edit/_id.vue: `&nbsp;` aus Aside-Div entfernt (Screenreader-Problem) + zwei Style-Blöcke zu einem scoped Block gemergt
- [x] post/create/_type.vue: Zwei Style-Blöcke (scoped + unscoped) zu einem scoped Block gemergt
- [x] _ds-compat.scss: `display: flex` zu `.ds-space-centered` hinzugefügt (justify-content/align-items wirkten ohne display:flex nicht)
- [x] blocked-users.vue: Unnötiges Wrapper-Div `ds-mb-large` am Kartenende entfernt
- [x] Cypress SocialMedia: `cy.get('.ds-list-item a')``cy.get('.ds-list-item a[href="${link}"]')` (nutzt link-Parameter)
- [x] Als "nicht in diesem PR" bewertet: pre-existing Code-Style Nitpicks (required:false, Ternary-Syntax, Prop-Validierung, Spacer-Divs, flex:1 0 0 Einheit, v-if="user", CSS-Nesting, Dark-Mode SVG-Farbe)
**Zuvor abgeschlossen (Session 27 - Tier A: ds-* Komponenten → Plain HTML):**
- [x] `_ds-compat.scss` erstellt (Utility-Klassen für Margins, Flex, Centered) - [x] `_ds-compat.scss` erstellt (Utility-Klassen für Margins, Flex, Centered)
- [x] `ds-section``<section class="ds-section">` (5 Dateien) - [x] `ds-section``<section class="ds-section">` (5 Dateien)
- [x] `ds-placeholder``<div class="ds-placeholder">` (4 Dateien) - [x] `ds-placeholder``<div class="ds-placeholder">` (4 Dateien)
@ -1737,6 +1758,13 @@ Bei der Migration werden:
| 2026-02-19 | **ds-flex/ds-flex-item** | 103 Nutzungen in 29 Dateien → Plain HTML + CSS `@media` Queries; JS window.innerWidth → CSS Media Queries; `gap` statt negative-margin/padding | | 2026-02-19 | **ds-flex/ds-flex-item** | 103 Nutzungen in 29 Dateien → Plain HTML + CSS `@media` Queries; JS window.innerWidth → CSS Media Queries; `gap` statt negative-margin/padding |
| 2026-02-19 | **HTML-Validierung Bugfix** | `<p>` mit Block-Level-Kindern → `<div>` in DateTimeRange.vue und verify.vue | | 2026-02-19 | **HTML-Validierung Bugfix** | `<p>` mit Block-Level-Kindern → `<div>` in DateTimeRange.vue und verify.vue |
| 2026-02-19 | **Test-Fix** | Empty.spec.js: `attributes().margin``classes().toContain('ds-my-xxx-small')` | | 2026-02-19 | **Test-Fix** | Empty.spec.js: `attributes().margin``classes().toContain('ds-my-xxx-small')` |
| 2026-02-19 | **Review Fixes (Session 28)** | ~20 CodeRabbit Review-Kommentare für Tier A PR bearbeitet; Bugfixes, A11y, i18n, Scoping |
| 2026-02-19 | **Heading-Semantik** | ComponentSlider h1→h3, SearchHeading h1→h5 (ds-heading size-Prop wurde falsch zu h1 migriert) |
| 2026-02-19 | **A11y Fixes** | Label-for Mismatch (RegistrationSlideEmail), &nbsp; in Aside entfernt, link.id→link.url Key |
| 2026-02-19 | **i18n** | logout.vue "Logging out..." → $t() mit 9 Sprachen; maintenance alt-Text → $t() |
| 2026-02-19 | **CSS Fixes** | .buttons Klasse ergänzt (GroupForm), display:flex auf .ds-space-centered, 33%→33.333%, inline-style→Utilities |
| 2026-02-19 | **Scoping** | Signup.vue, maintenance/index.vue, post/create/_type.vue, post/edit/_id.vue: unscoped → scoped Style-Blöcke |
| 2026-02-19 | **Migration-Artefakte** | Redundante Wrapper-Divs, tote Attribute (margin="large"), Span→P Block-Level Regression |
--- ---

View File

@ -53,6 +53,9 @@
background-size: 0.6em; background-size: 0.6em;
} }
// ds-grid Ersatz
.ds-grid { display: grid; }
// ds-flex Ersatz // ds-flex Ersatz
.ds-flex { display: flex; flex-wrap: wrap; } .ds-flex { display: flex; flex-wrap: wrap; }
.ds-flex-no-wrap { flex-wrap: nowrap; } .ds-flex-no-wrap { flex-wrap: nowrap; }

View File

@ -58,8 +58,8 @@
<div v-if="createEvent" class="eventDatas"> <div v-if="createEvent" class="eventDatas">
<hr /> <hr />
<div class="ds-mt-x-small ds-mb-large"></div> <div class="ds-mt-x-small ds-mb-large"></div>
<ds-grid> <div class="ds-grid event-date-grid">
<ds-grid-item class="event-grid-item"> <div class="event-grid-item">
<!-- <label>Beginn</label> --> <!-- <label>Beginn</label> -->
<div class="event-grid-item-z-helper"> <div class="event-grid-item-z-helper">
<date-picker <date-picker
@ -85,8 +85,8 @@
<os-icon :icon="icons.warning" /> <os-icon :icon="icons.warning" />
</ds-chip> </ds-chip>
</div> </div>
</ds-grid-item> </div>
<ds-grid-item class="event-grid-item"> <div class="event-grid-item">
<!-- <label>Ende (optional)</label> --> <!-- <label>Ende (optional)</label> -->
<date-picker <date-picker
@ -104,10 +104,10 @@
:show-second="false" :show-second="false"
@change="changeEventEnd($event)" @change="changeEventEnd($event)"
></date-picker> ></date-picker>
</ds-grid-item> </div>
</ds-grid> </div>
<ds-grid class="event-location-grid"> <div class="ds-grid event-location-grid">
<ds-grid-item class="event-grid-item"> <div class="event-grid-item">
<ds-input <ds-input
model="eventVenue" model="eventVenue"
name="eventVenue" name="eventVenue"
@ -119,8 +119,8 @@
<os-icon v-if="errors && errors.eventVenue" :icon="icons.warning" /> <os-icon v-if="errors && errors.eventVenue" :icon="icons.warning" />
</ds-chip> </ds-chip>
</div> </div>
</ds-grid-item> </div>
<ds-grid-item v-if="showEventLocationName" class="event-grid-item"> <div v-if="showEventLocationName" class="event-grid-item">
<ds-input <ds-input
model="eventLocationName" model="eventLocationName"
name="eventLocationName" name="eventLocationName"
@ -132,8 +132,8 @@
<os-icon v-if="errors && errors.eventLocationName" :icon="icons.warning" /> <os-icon v-if="errors && errors.eventLocationName" :icon="icons.warning" />
</ds-chip> </ds-chip>
</div> </div>
</ds-grid-item> </div>
</ds-grid> </div>
<div> <div>
<input <input
@ -520,14 +520,15 @@ export default {
margin-top: -10px; margin-top: -10px;
} }
} }
// style override to handle dynamic inputs .event-date-grid,
.event-location-grid { .event-location-grid {
grid-template-columns: repeat(2, 1fr) !important; grid-template-columns: repeat(2, 1fr);
grid-auto-rows: auto;
gap: $space-small;
} }
.event-grid-item { .event-grid-item {
// important needed because of component inline style grid-row-end: span 3;
grid-row-end: span 3 !important;
} }
.event-grid-item-z-helper { .event-grid-item-z-helper {
z-index: 20; z-index: 20;

View File

@ -15,7 +15,6 @@
</os-button> </os-button>
</div> </div>
<div class="category-filter-list"> <div class="category-filter-list">
<!-- <ds-space margin="small" /> -->
<os-button <os-button
v-for="category in sortCategories(categories)" v-for="category in sortCategories(categories)"
:key="category.id" :key="category.id"

View File

@ -6,11 +6,9 @@ const localVue = global.localVue
describe('MasonryGrid', () => { describe('MasonryGrid', () => {
let wrapper let wrapper
let masonryGridItem
beforeEach(() => { beforeEach(() => {
wrapper = mount(MasonryGrid, { localVue }) wrapper = mount(MasonryGrid, { localVue })
masonryGridItem = wrapper.vm.$children[0]
}) })
it('adds the "reset-grid-height" class when itemsCalculating is more than 0', async () => { it('adds the "reset-grid-height" class when itemsCalculating is more than 0', async () => {
@ -25,16 +23,16 @@ describe('MasonryGrid', () => {
expect(wrapper.classes()).not.toContain('reset-grid-height') expect(wrapper.classes()).not.toContain('reset-grid-height')
}) })
it('adds 1 to itemsCalculating when a child emits "calculating-item-height"', async () => { it('adds 1 to itemsCalculating when "calculating-item-height" is emitted', async () => {
wrapper.setData({ itemsCalculating: 0 }) wrapper.setData({ itemsCalculating: 0 })
masonryGridItem.$emit('calculating-item-height') wrapper.vm.$emit('calculating-item-height')
await Vue.nextTick() await Vue.nextTick()
expect(wrapper.vm.itemsCalculating).toBe(1) expect(wrapper.vm.itemsCalculating).toBe(1)
}) })
it('subtracts 1 from itemsCalculating when a child emits "finished-calculating-item-height"', async () => { it('subtracts 1 from itemsCalculating when "finished-calculating-item-height" is emitted', async () => {
wrapper.setData({ itemsCalculating: 2 }) wrapper.setData({ itemsCalculating: 2 })
masonryGridItem.$emit('finished-calculating-item-height') wrapper.vm.$emit('finished-calculating-item-height')
await Vue.nextTick() await Vue.nextTick()
expect(wrapper.vm.itemsCalculating).toBe(1) expect(wrapper.vm.itemsCalculating).toBe(1)
}) })

View File

@ -1,11 +1,11 @@
<template> <template>
<ds-grid <div
v-on:calculating-item-height="startCalculation" class="ds-grid"
v-on:finished-calculating-item-height="endCalculation" :style="{ gridAutoRows: '20px', rowGap: '16px', columnGap: '16px' }"
:class="[itemsCalculating ? 'reset-grid-height' : '']" :class="[itemsCalculating ? 'reset-grid-height' : '']"
> >
<slot></slot> <slot></slot>
</ds-grid> </div>
</template> </template>
<script> <script>
@ -15,6 +15,14 @@ export default {
itemsCalculating: 0, itemsCalculating: 0,
} }
}, },
created() {
this.$on('calculating-item-height', this.startCalculation)
this.$on('finished-calculating-item-height', this.endCalculation)
},
beforeDestroy() {
this.$off('calculating-item-height', this.startCalculation)
this.$off('finished-calculating-item-height', this.endCalculation)
},
methods: { methods: {
startCalculation() { startCalculation() {
this.itemsCalculating += 1 this.itemsCalculating += 1
@ -27,11 +35,8 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
/* dirty fix to override broken styleguide inline-styles */
.ds-grid { .ds-grid {
grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr)) !important; grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
gap: 16px !important;
grid-auto-rows: 20px;
} }
.reset-grid-height { .reset-grid-height {

View File

@ -3,44 +3,40 @@ import MasonryGridItem from './MasonryGridItem'
const localVue = global.localVue const localVue = global.localVue
const stubs = {
'ds-grid-item': true,
}
describe('MasonryGridItem', () => { describe('MasonryGridItem', () => {
let wrapper let wrapper
describe('given an imageAspectRatio', () => { describe('given an imageAspectRatio', () => {
it('sets the initial rowSpan to 13 when the ratio is higher than 1.3', () => { it('sets the initial rowSpan to 13 when the ratio is higher than 1.3', () => {
const propsData = { imageAspectRatio: 2 } const propsData = { imageAspectRatio: 2 }
wrapper = mount(MasonryGridItem, { localVue, propsData, stubs }) wrapper = mount(MasonryGridItem, { localVue, propsData })
expect(wrapper.vm.rowSpan).toBe(13) expect(wrapper.vm.rowSpan).toBe(13)
}) })
it('sets the initial rowSpan to 15 when the ratio is between 1.3 and 1', () => { it('sets the initial rowSpan to 15 when the ratio is between 1.3 and 1', () => {
const propsData = { imageAspectRatio: 1.1 } const propsData = { imageAspectRatio: 1.1 }
wrapper = mount(MasonryGridItem, { localVue, propsData, stubs }) wrapper = mount(MasonryGridItem, { localVue, propsData })
expect(wrapper.vm.rowSpan).toBe(15) expect(wrapper.vm.rowSpan).toBe(15)
}) })
it('sets the initial rowSpan to 18 when the ratio is between 1 and 0.7', () => { it('sets the initial rowSpan to 18 when the ratio is between 1 and 0.7', () => {
const propsData = { imageAspectRatio: 0.7 } const propsData = { imageAspectRatio: 0.7 }
wrapper = mount(MasonryGridItem, { localVue, propsData, stubs }) wrapper = mount(MasonryGridItem, { localVue, propsData })
expect(wrapper.vm.rowSpan).toBe(18) expect(wrapper.vm.rowSpan).toBe(18)
}) })
it('sets the initial rowSpan to 25 when the ratio is lower than 0.7', () => { it('sets the initial rowSpan to 25 when the ratio is lower than 0.7', () => {
const propsData = { imageAspectRatio: 0.3 } const propsData = { imageAspectRatio: 0.3 }
wrapper = mount(MasonryGridItem, { localVue, propsData, stubs }) wrapper = mount(MasonryGridItem, { localVue, propsData })
expect(wrapper.vm.rowSpan).toBe(25) expect(wrapper.vm.rowSpan).toBe(25)
}) })
describe('given no aspect ratio', () => { describe('given no aspect ratio', () => {
it('sets the initial rowSpan to 8 when not given an imageAspectRatio', () => { it('sets the initial rowSpan to 8 when not given an imageAspectRatio', () => {
wrapper = mount(MasonryGridItem, { localVue, stubs }) wrapper = mount(MasonryGridItem, { localVue })
expect(wrapper.vm.rowSpan).toBe(8) expect(wrapper.vm.rowSpan).toBe(8)
}) })
}) })

View File

@ -1,7 +1,7 @@
<template> <template>
<ds-grid-item :rowSpan="rowSpan"> <div :style="{ gridRowEnd: 'span ' + rowSpan }">
<slot></slot> <slot></slot>
</ds-grid-item> </div>
</template> </template>
<script> <script>

View File

@ -1,21 +1,21 @@
<template> <template>
<div class="notification-grid" v-if="notifications && notifications.length"> <div class="notification-grid" v-if="notifications && notifications.length">
<ds-grid> <div class="ds-grid">
<ds-grid-item v-if="!isMobile" column-span="fullWidth"> <div v-if="!isMobile" style="grid-column: 1 / -1">
<ds-grid class="header-grid"> <div class="ds-grid header-grid">
<ds-grid-item v-for="field in fields" :key="field.label" class="ds-table-head-col"> <div v-for="field in fields" :key="field.label" class="ds-table-head-col">
{{ field.label }} {{ field.label }}
</ds-grid-item> </div>
</ds-grid> </div>
</ds-grid-item> </div>
<ds-grid-item <div
v-for="notification in notifications" v-for="notification in notifications"
:key="notification.id" :key="notification.id"
column-span="fullWidth" style="grid-column: 1 / -1"
class="notification-grid-row" class="notification-grid-row"
> >
<ds-grid> <div class="ds-grid">
<ds-grid-item> <div>
<div class="ds-flex user-section"> <div class="ds-flex user-section">
<div class="ds-flex-item"> <div class="ds-flex-item">
<user-teaser <user-teaser
@ -30,8 +30,8 @@
/> />
</div> </div>
</div> </div>
</ds-grid-item> </div>
<ds-grid-item> <div>
<div class="notification-container"> <div class="notification-container">
<!-- Icon with responsive sizing --> <!-- Icon with responsive sizing -->
<div class="notification-icon"> <div class="notification-icon">
@ -78,10 +78,10 @@
</p> </p>
</div> </div>
</div> </div>
</ds-grid-item> </div>
</ds-grid> </div>
</ds-grid-item> </div>
</ds-grid> </div>
</div> </div>
<hc-empty v-else icon="alert" :message="$t('notifications.empty')" /> <hc-empty v-else icon="alert" :message="$t('notifications.empty')" />
</template> </template>
@ -171,7 +171,6 @@ export default {
.notification-grid .content-section { .notification-grid .content-section {
flex-wrap: nowrap; flex-wrap: nowrap;
} }
/* dirty fix to override broken styleguide inline-styles */
.notification-grid .ds-grid { .notification-grid .ds-grid {
grid-template-columns: 5fr 6fr !important; grid-template-columns: 5fr 6fr !important;
grid-auto-rows: auto !important; grid-auto-rows: auto !important;

View File

@ -3,14 +3,14 @@
<div class="search-results__content"> <div class="search-results__content">
<masonry-grid> <masonry-grid>
<!-- search text --> <!-- search text -->
<ds-grid-item class="grid-total-search-results" :row-span="1" column-span="fullWidth"> <div class="grid-total-search-results" style="grid-row-end: span 1; grid-column: 1 / -1">
<div class="ds-mb-xxx-small ds-mt-xxx-small ds-space-centered"> <div class="ds-mb-xxx-small ds-mt-xxx-small ds-space-centered">
<p class="ds-text total-search-results"> <p class="ds-text total-search-results">
{{ $t('search.for') }} {{ $t('search.for') }}
<strong>{{ '"' + (search || '') + '"' }}</strong> <strong>{{ '"' + (search || '') + '"' }}</strong>
</p> </p>
</div> </div>
</ds-grid-item> </div>
<!-- tabs --> <!-- tabs -->
<tab-navigation :tabs="tabOptions" :activeTab="activeTab" @switch-tab="switchTab" /> <tab-navigation :tabs="tabOptions" :activeTab="activeTab" @switch-tab="switchTab" />
@ -19,7 +19,10 @@
<template v-if="!(!activeResourceCount || searchCount === 0)"> <template v-if="!(!activeResourceCount || searchCount === 0)">
<!-- pagination buttons --> <!-- pagination buttons -->
<ds-grid-item v-if="activeResourceCount > pageSize" :row-span="2" column-span="fullWidth"> <div
v-if="activeResourceCount > pageSize"
style="grid-row-end: span 2; grid-column: 1 / -1"
>
<div class="ds-mb-large ds-space-centered"> <div class="ds-mb-large ds-space-centered">
<pagination-buttons <pagination-buttons
:hasNext="hasNext" :hasNext="hasNext"
@ -33,7 +36,7 @@
@next="nextResults" @next="nextResults"
/> />
</div> </div>
</ds-grid-item> </div>
<!-- posts --> <!-- posts -->
<template v-if="activeTab === 'Post'"> <template v-if="activeTab === 'Post'">
@ -61,31 +64,34 @@
</template> </template>
<!-- users --> <!-- users -->
<template v-if="activeTab === 'User'"> <template v-if="activeTab === 'User'">
<ds-grid-item v-for="user in activeResources" :key="user.id" :row-span="2"> <div v-for="user in activeResources" :key="user.id" style="grid-row-end: span 2">
<os-card> <os-card>
<user-teaser :user="user" /> <user-teaser :user="user" />
</os-card> </os-card>
</ds-grid-item> </div>
</template> </template>
<!-- groups --> <!-- groups -->
<template v-if="activeTab === 'Group'"> <template v-if="activeTab === 'Group'">
<ds-grid-item v-for="group in activeResources" :key="group.id" :row-span="2"> <div v-for="group in activeResources" :key="group.id" style="grid-row-end: span 2">
<os-card class="group-teaser-card-wrapper"> <os-card class="group-teaser-card-wrapper">
<group-teaser :group="{ ...group, name: group.groupName }" /> <group-teaser :group="{ ...group, name: group.groupName }" />
</os-card> </os-card>
</ds-grid-item> </div>
</template> </template>
<!-- hashtags --> <!-- hashtags -->
<template v-if="activeTab === 'Hashtag'"> <template v-if="activeTab === 'Hashtag'">
<ds-grid-item v-for="hashtag in activeResources" :key="hashtag.id" :row-span="2"> <div v-for="hashtag in activeResources" :key="hashtag.id" style="grid-row-end: span 2">
<os-card> <os-card>
<hc-hashtag :id="hashtag.id" /> <hc-hashtag :id="hashtag.id" />
</os-card> </os-card>
</ds-grid-item> </div>
</template> </template>
<!-- pagination buttons --> <!-- pagination buttons -->
<ds-grid-item v-if="activeResourceCount > pageSize" :row-span="2" column-span="fullWidth"> <div
v-if="activeResourceCount > pageSize"
style="grid-row-end: span 2; grid-column: 1 / -1"
>
<div class="ds-mb-large ds-space-centered"> <div class="ds-mb-large ds-space-centered">
<pagination-buttons <pagination-buttons
:hasNext="hasNext" :hasNext="hasNext"
@ -100,15 +106,15 @@
@next="nextResults" @next="nextResults"
/> />
</div> </div>
</ds-grid-item> </div>
</template> </template>
<!-- no results --> <!-- no results -->
<ds-grid-item v-else :row-span="7" column-span="fullWidth"> <div v-else style="grid-row-end: span 7; grid-column: 1 / -1">
<div class="ds-mb-large ds-space-centered"> <div class="ds-mb-large ds-space-centered">
<hc-empty icon="tasks" :message="$t('search.no-results', { search })" /> <hc-empty icon="tasks" :message="$t('search.no-results', { search })" />
</div> </div>
</ds-grid-item> </div>
</masonry-grid> </masonry-grid>
</div> </div>
</div> </div>

View File

@ -1,5 +1,5 @@
<template> <template>
<ds-grid-item class="tab-navigation" :row-span="tabs.length" column-span="fullWidth"> <div class="tab-navigation" :style="{ gridRowEnd: 'span ' + tabs.length, gridColumn: '1 / -1' }">
<os-card class="ds-tab-nav"> <os-card class="ds-tab-nav">
<ul class="Tabs"> <ul class="Tabs">
<li <li
@ -25,7 +25,7 @@
</li> </li>
</ul> </ul>
</os-card> </os-card>
</ds-grid-item> </div>
</template> </template>
<script> <script>

View File

@ -161,28 +161,28 @@ storiesOf('TabNavigator', module)
</template> </template>
<!-- users --> <!-- users -->
<template v-if="activeTab === 'User'"> <template v-if="activeTab === 'User'">
<ds-grid-item v-for="user in activeResources" :key="user.id" :row-span="2"> <div v-for="user in activeResources" :key="user.id" style="grid-row-end: span 2;">
<os-card> <os-card>
<user-teaser :user="user" /> <user-teaser :user="user" />
</os-card> </os-card>
</ds-grid-item> </div>
</template> </template>
<!-- hashtags --> <!-- hashtags -->
<template v-if="activeTab === 'Hashtag'"> <template v-if="activeTab === 'Hashtag'">
<ds-grid-item v-for="hashtag in activeResources" :key="hashtag.id" :row-span="2"> <div v-for="hashtag in activeResources" :key="hashtag.id" style="grid-row-end: span 2;">
<os-card> <os-card>
<hc-hashtag :id="hashtag.id" /> <hc-hashtag :id="hashtag.id" />
</os-card> </os-card>
</ds-grid-item> </div>
</template> </template>
</template> </template>
<!-- no results --> <!-- no results -->
<ds-grid-item v-else :row-span="7" column-span="fullWidth"> <div v-else style="grid-row-end: span 7; grid-column: 1 / -1;">
<div class="ds-mb-large ds-space-centered"> <div class="ds-mb-large ds-space-centered">
<hc-empty icon="tasks" :message="$t('search.no-results', { search })" /> <hc-empty icon="tasks" :message="$t('search.no-results', { search })" />
</div> </div>
</ds-grid-item> </div>
</masonry-grid> </masonry-grid>
</ds-flex-item> </ds-flex-item>
</div> </div>

View File

@ -1001,10 +1001,10 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
<div <div
class="ds-grid" class="ds-grid"
style="grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); grid-auto-rows: 20px;" style="grid-auto-rows: 20px; row-gap: 16px; column-gap: 16px;"
> >
<div <div
style="grid-row-end: span 4; grid-column-start: 1; grid-column-end: -1;" style="grid-row-end: span 4; grid-column: 1 / -1;"
> >
<div <div
data-test="icon-empty" data-test="icon-empty"
@ -1509,10 +1509,10 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
<div <div
class="ds-grid" class="ds-grid"
style="grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); grid-auto-rows: 20px;" style="grid-auto-rows: 20px; row-gap: 16px; column-gap: 16px;"
> >
<div <div
style="grid-row-end: span 4; grid-column-start: 1; grid-column-end: -1;" style="grid-row-end: span 4; grid-column: 1 / -1;"
> >
<div <div
data-test="icon-empty" data-test="icon-empty"
@ -2037,10 +2037,10 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
<div <div
class="ds-grid" class="ds-grid"
style="grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); grid-auto-rows: 20px;" style="grid-auto-rows: 20px; row-gap: 16px; column-gap: 16px;"
> >
<div <div
style="grid-row-end: span 4; grid-column-start: 1; grid-column-end: -1;" style="grid-row-end: span 4; grid-column: 1 / -1;"
> >
<div <div
data-test="icon-empty" data-test="icon-empty"
@ -3066,10 +3066,10 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
<div <div
class="ds-grid" class="ds-grid"
style="grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); grid-auto-rows: 20px;" style="grid-auto-rows: 20px; row-gap: 16px; column-gap: 16px;"
> >
<div <div
style="grid-row-end: span 4; grid-column-start: 1; grid-column-end: -1;" style="grid-row-end: span 4; grid-column: 1 / -1;"
> >
<div <div
data-test="icon-empty" data-test="icon-empty"
@ -4115,10 +4115,10 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
<div <div
class="ds-grid" class="ds-grid"
style="grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); grid-auto-rows: 20px;" style="grid-auto-rows: 20px; row-gap: 16px; column-gap: 16px;"
> >
<div <div
style="grid-row-end: span 4; grid-column-start: 1; grid-column-end: -1;" style="grid-row-end: span 4; grid-column: 1 / -1;"
> >
<div <div
data-test="icon-empty" data-test="icon-empty"
@ -4926,10 +4926,10 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
<div <div
class="ds-grid" class="ds-grid"
style="grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); grid-auto-rows: 20px;" style="grid-auto-rows: 20px; row-gap: 16px; column-gap: 16px;"
> >
<div <div
style="grid-row-end: span 4; grid-column-start: 1; grid-column-end: -1;" style="grid-row-end: span 4; grid-column: 1 / -1;"
> >
<div <div
data-test="icon-empty" data-test="icon-empty"
@ -5757,10 +5757,10 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
<div <div
class="ds-grid" class="ds-grid"
style="grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); grid-auto-rows: 20px;" style="grid-auto-rows: 20px; row-gap: 16px; column-gap: 16px;"
> >
<div <div
style="grid-row-end: span 4; grid-column-start: 1; grid-column-end: -1;" style="grid-row-end: span 4; grid-column: 1 / -1;"
> >
<div <div
data-test="icon-empty" data-test="icon-empty"
@ -6717,10 +6717,10 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
<div <div
class="ds-grid" class="ds-grid"
style="grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); grid-auto-rows: 20px;" style="grid-auto-rows: 20px; row-gap: 16px; column-gap: 16px;"
> >
<div <div
style="grid-row-end: span 4; grid-column-start: 1; grid-column-end: -1;" style="grid-row-end: span 4; grid-column: 1 / -1;"
> >
<div <div
data-test="icon-empty" data-test="icon-empty"
@ -7851,10 +7851,10 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
<div <div
class="ds-grid" class="ds-grid"
style="grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); grid-auto-rows: 20px;" style="grid-auto-rows: 20px; row-gap: 16px; column-gap: 16px;"
> >
<div <div
style="grid-row-end: span 4; grid-column-start: 1; grid-column-end: -1;" style="grid-row-end: span 4; grid-column: 1 / -1;"
> >
<div <div
data-test="icon-empty" data-test="icon-empty"
@ -8908,10 +8908,10 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
<div <div
class="ds-grid" class="ds-grid"
style="grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); grid-auto-rows: 20px;" style="grid-auto-rows: 20px; row-gap: 16px; column-gap: 16px;"
> >
<div <div
style="grid-row-end: span 4; grid-column-start: 1; grid-column-end: -1;" style="grid-row-end: span 4; grid-column: 1 / -1;"
> >
<div <div
data-test="icon-empty" data-test="icon-empty"

View File

@ -270,16 +270,16 @@
</masonry-grid-item> </masonry-grid-item>
</template> </template>
<template v-else-if="$apollo.loading"> <template v-else-if="$apollo.loading">
<ds-grid-item column-span="fullWidth"> <div style="grid-row-end: span 4; grid-column: 1 / -1">
<div style="text-align: center; padding: 48px 0"> <div style="text-align: center; padding: 48px 0">
<os-spinner size="lg" /> <os-spinner size="lg" />
</div> </div>
</ds-grid-item> </div>
</template> </template>
<template v-else> <template v-else>
<ds-grid-item column-span="fullWidth"> <div style="grid-row-end: span 4; grid-column: 1 / -1">
<empty margin="xx-large" icon="file" data-test="icon-empty" /> <empty margin="xx-large" icon="file" data-test="icon-empty" />
</ds-grid-item> </div>
</template> </template>
</masonry-grid> </masonry-grid>
<client-only> <client-only>

View File

@ -136,11 +136,11 @@
</masonry-grid-item> </masonry-grid-item>
</template> </template>
<template v-else> <template v-else>
<ds-grid-item :row-span="2" column-span="fullWidth"> <div style="grid-row-end: span 2; grid-column: 1 / -1">
<hc-empty icon="docs" /> <hc-empty icon="docs" />
<p class="ds-text ds-text-center">{{ $t('index.no-results') }}</p> <p class="ds-text ds-text-center">{{ $t('index.no-results') }}</p>
<p class="ds-text ds-text-center">{{ $t('index.change-filter-settings') }}</p> <p class="ds-text ds-text-center">{{ $t('index.change-filter-settings') }}</p>
</ds-grid-item> </div>
</template> </template>
</masonry-grid> </masonry-grid>

View File

@ -465,11 +465,11 @@ exports[`ProfileSlug given an authenticated user given another profile user and
> >
<div <div
class="ds-grid" class="ds-grid"
style="grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); grid-auto-rows: 20px;" style="grid-auto-rows: 20px; row-gap: 16px; column-gap: 16px;"
> >
<div <div
class="tab-navigation" class="tab-navigation"
style="grid-row-end: span 3; grid-column-start: 1; grid-column-end: -1;" style="grid-row-end: span 3; grid-column: 1 / -1;"
> >
<div <div
class="ds-tab-nav os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6 ds-tab-nav" class="ds-tab-nav os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6 ds-tab-nav"
@ -619,7 +619,7 @@ exports[`ProfileSlug given an authenticated user given another profile user and
<!----> <!---->
<div <div
style="grid-row-end: span 4; grid-column-start: 1; grid-column-end: -1;" style="grid-row-end: span 4; grid-column: 1 / -1;"
> >
<div> <div>
<div <div
@ -1232,11 +1232,11 @@ exports[`ProfileSlug given an authenticated user given another profile user and
> >
<div <div
class="ds-grid" class="ds-grid"
style="grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); grid-auto-rows: 20px;" style="grid-auto-rows: 20px; row-gap: 16px; column-gap: 16px;"
> >
<div <div
class="tab-navigation" class="tab-navigation"
style="grid-row-end: span 3; grid-column-start: 1; grid-column-end: -1;" style="grid-row-end: span 3; grid-column: 1 / -1;"
> >
<div <div
class="ds-tab-nav os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6 ds-tab-nav" class="ds-tab-nav os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6 ds-tab-nav"
@ -1386,7 +1386,7 @@ exports[`ProfileSlug given an authenticated user given another profile user and
<!----> <!---->
<div <div
style="grid-row-end: span 4; grid-column-start: 1; grid-column-end: -1;" style="grid-row-end: span 4; grid-column: 1 / -1;"
> >
<div> <div>
<div <div
@ -1823,11 +1823,11 @@ exports[`ProfileSlug given an authenticated user given the logged in user as pro
> >
<div <div
class="ds-grid" class="ds-grid"
style="grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); grid-auto-rows: 20px;" style="grid-auto-rows: 20px; row-gap: 16px; column-gap: 16px;"
> >
<div <div
class="tab-navigation" class="tab-navigation"
style="grid-row-end: span 3; grid-column-start: 1; grid-column-end: -1;" style="grid-row-end: span 3; grid-column: 1 / -1;"
> >
<div <div
class="ds-tab-nav os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6 ds-tab-nav" class="ds-tab-nav os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6 ds-tab-nav"
@ -1975,7 +1975,7 @@ exports[`ProfileSlug given an authenticated user given the logged in user as pro
</div> </div>
<div <div
style="grid-row-end: span 2; grid-column-start: 1; grid-column-end: -1;" style="grid-row-end: span 2; grid-column: 1 / -1;"
> >
<div <div
class="profile-post-add-button-container" class="profile-post-add-button-container"
@ -2015,7 +2015,7 @@ exports[`ProfileSlug given an authenticated user given the logged in user as pro
</div> </div>
<div <div
style="grid-row-end: span 4; grid-column-start: 1; grid-column-end: -1;" style="grid-row-end: span 4; grid-column: 1 / -1;"
> >
<div> <div>
<div <div
@ -2493,11 +2493,11 @@ exports[`ProfileSlug given an authenticated user given the logged in user as pro
> >
<div <div
class="ds-grid" class="ds-grid"
style="grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); grid-auto-rows: 20px;" style="grid-auto-rows: 20px; row-gap: 16px; column-gap: 16px;"
> >
<div <div
class="tab-navigation" class="tab-navigation"
style="grid-row-end: span 3; grid-column-start: 1; grid-column-end: -1;" style="grid-row-end: span 3; grid-column: 1 / -1;"
> >
<div <div
class="ds-tab-nav os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6 ds-tab-nav" class="ds-tab-nav os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6 ds-tab-nav"
@ -2645,7 +2645,7 @@ exports[`ProfileSlug given an authenticated user given the logged in user as pro
</div> </div>
<div <div
style="grid-row-end: span 2; grid-column-start: 1; grid-column-end: -1;" style="grid-row-end: span 2; grid-column: 1 / -1;"
> >
<div <div
class="profile-post-add-button-container" class="profile-post-add-button-container"
@ -2685,7 +2685,7 @@ exports[`ProfileSlug given an authenticated user given the logged in user as pro
</div> </div>
<div <div
style="grid-row-end: span 4; grid-column-start: 1; grid-column-end: -1;" style="grid-row-end: span 4; grid-column: 1 / -1;"
> >
<div> <div>
<div <div

View File

@ -144,7 +144,7 @@
<tab-navigation :tabs="tabOptions" :activeTab="tabActive" @switch-tab="handleTab" /> <tab-navigation :tabs="tabOptions" :activeTab="tabActive" @switch-tab="handleTab" />
<!-- feed --> <!-- feed -->
<ds-grid-item v-if="myProfile" :row-span="2" column-span="fullWidth"> <div v-if="myProfile" style="grid-row-end: span 2; grid-column: 1 / -1">
<div class="profile-post-add-button-container"> <div class="profile-post-add-button-container">
<os-button <os-button
as="nuxt-link" as="nuxt-link"
@ -164,7 +164,7 @@
</template> </template>
</os-button> </os-button>
</div> </div>
</ds-grid-item> </div>
<template v-if="posts.length"> <template v-if="posts.length">
<masonry-grid-item <masonry-grid-item
@ -189,16 +189,16 @@
</masonry-grid-item> </masonry-grid-item>
</template> </template>
<template v-else-if="$apollo.loading"> <template v-else-if="$apollo.loading">
<ds-grid-item column-span="fullWidth"> <div style="grid-row-end: span 4; grid-column: 1 / -1">
<div style="text-align: center; padding: 48px 0"> <div style="text-align: center; padding: 48px 0">
<os-spinner size="lg" /> <os-spinner size="lg" />
</div> </div>
</ds-grid-item> </div>
</template> </template>
<template v-else> <template v-else>
<ds-grid-item column-span="fullWidth"> <div style="grid-row-end: span 4; grid-column: 1 / -1">
<hc-empty margin="xx-large" icon="file" /> <hc-empty margin="xx-large" icon="file" />
</ds-grid-item> </div>
</template> </template>
</masonry-grid> </masonry-grid>
<client-only> <client-only>