refactor(package/ui): os-card (#9246)

This commit is contained in:
Ulf Gebhardt 2026-02-19 09:06:48 +01:00 committed by GitHub
parent daafde24b0
commit 4f4f2e4696
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
117 changed files with 1315 additions and 1059 deletions

View File

@ -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')
})

View File

@ -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()

View File

@ -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)
})
})

View File

@ -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')
})

View File

@ -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')

View File

@ -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')
})

View File

@ -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')
})

View File

@ -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)
})

View File

@ -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)
})

View File

@ -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')
})

View File

@ -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')
})

View File

@ -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')

View File

@ -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')
})

View File

@ -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: <base-card><os-card> (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: <base-card><os-card> 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 <base-card> 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: `<base-card>``<os-card>` 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: `<base-card>``<os-card>` 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 `<client-only>` 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** | `<ApolloQuery>``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: `<base-card>``<os-card>` 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 |
---

View File

@ -144,7 +144,7 @@
"size-limit": [
{
"path": "dist/index.mjs",
"limit": "15 kB",
"limit": "20 kB",
"brotli": true
},
{

View File

@ -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: '<p>Card content</p>' },
})
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: '<img src="/test.jpg" alt="Hero" />',
default: '<p>Content</p>',
}
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: '<p>Content</p>' },
})
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()
})
})
})

View File

@ -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<typeof OsCard> = {
title: 'Components/OsCard',
component: OsCard,
tags: ['autodocs'],
}
export default meta
type Story = StoryObj<typeof OsCard>
interface PlaygroundArgs {
as: string
highlight: boolean
heroImage: boolean
content: string
}
export const Playground: StoryObj<PlaygroundArgs> = {
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: `
<div style="max-width: 400px">
<OsCard v-bind="cardProps">
<template v-if="showHero" #heroImage>
<img :src="HERO_SVG" alt="Hero placeholder" style="display: block; width: 100%; height: auto" />
</template>
<p style="margin: 0">{{ content }}</p>
</OsCard>
</div>
`,
}),
}
export const SimpleWrapper: Story = {
render: () => ({
components: { OsCard },
template: `
<div data-testid="simple-wrapper" class="flex flex-col gap-4" style="max-width: 400px">
<OsCard>
<h3 style="margin: 0 0 8px; font-weight: 600">Card Title</h3>
<p style="margin: 0; color: #666">Some card content goes here. Cards provide a contained surface for related information.</p>
</OsCard>
<OsCard>
<p style="margin: 0">A minimal card with just text.</p>
</OsCard>
<OsCard>
<h3 style="margin: 0 0 8px; font-weight: 600">Another Card</h3>
<p style="margin: 0; color: #666">Cards stack naturally in a flex column layout.</p>
</OsCard>
</div>
`,
}),
}
export const CustomClass: Story = {
render: () => ({
components: { OsCard },
template: `
<div data-testid="custom-class" style="max-width: 400px">
<OsCard class="text-center">
<p style="margin: 0">Centered content via custom class.</p>
</OsCard>
</div>
`,
}),
}
export const Highlight: Story = {
render: () => ({
components: { OsCard },
template: `
<div data-testid="highlight" class="flex flex-col gap-4" style="max-width: 400px">
<OsCard :highlight="true">
<h3 style="margin: 0 0 8px; font-weight: 600">Pinned Post</h3>
<p style="margin: 0; color: #666">This card is highlighted with a colored border, used for pinned or featured content.</p>
</OsCard>
<OsCard>
<h3 style="margin: 0 0 8px; font-weight: 600">Normal Post</h3>
<p style="margin: 0; color: #666">This card has no highlight for comparison.</p>
</OsCard>
</div>
`,
}),
}
export const HeroImage: Story = {
render: () => ({
components: { OsCard },
setup() {
return { HERO_SVG }
},
template: `
<div data-testid="hero-image" class="flex flex-col gap-4" style="max-width: 400px">
<OsCard as="article" :highlight="true">
<template #heroImage>
<img
:src="HERO_SVG"
alt="Hero placeholder"
style="display: block; width: 100%; height: auto"
/>
</template>
<h3 style="margin: 0 0 8px; font-weight: 600">Pinned Post with Image</h3>
<p style="margin: 0; color: #666">Combines hero image, highlight border, and article semantics.</p>
</OsCard>
<OsCard>
<template #heroImage>
<img
:src="HERO_SVG"
alt="Hero placeholder"
style="display: block; width: 100%; height: auto"
/>
</template>
<h3 style="margin: 0 0 8px; font-weight: 600">Post with Hero Image</h3>
<p style="margin: 0; color: #666">The image spans the full card width. Content below has its own padding.</p>
</OsCard>
<OsCard>
<h3 style="margin: 0 0 8px; font-weight: 600">Card without Image</h3>
<p style="margin: 0; color: #666">A regular card for comparison.</p>
</OsCard>
</div>
`,
}),
}

View File

@ -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)
})
})

View File

@ -0,0 +1,104 @@
<script lang="ts">
import { defineComponent, getCurrentInstance, h, isVue2 } from 'vue-demi'
import { cn } from '#src/utils'
import type { ClassValue } from 'clsx'
import type { PropType } from 'vue-demi'
const CARD_BASE =
'os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)]'
const HIGHLIGHT_CLASS = 'outline outline-1 outline-[var(--color-warning)]'
const HERO_IMAGE_CLASS = 'os-card__hero-image overflow-hidden'
const CONTENT_CLASS = 'os-card__content p-6'
/**
* Content card container with rounded corners, background, and shadow.
* Renders as `<div>` by default. Use `as="article"` for self-contained
* content like posts or comments.
*
* When `heroImage` slot is provided, the card uses a two-section layout:
* the hero image spans full width (no padding), followed by padded content.
* Without `heroImage`, the card applies padding directly.
*
* @slot default - Card content
* @slot heroImage - Full-width image at the top of the card
*/
export default defineComponent({
name: 'OsCard',
inheritAttrs: false,
props: {
/**
* HTML element to render as. Use `article` for self-contained content
* (posts, comments), `section` for thematic groups, or `div` (default)
* for generic containers.
*/
as: {
type: String as PropType<'div' | 'article' | 'section' | 'aside'>,
default: 'div',
},
/**
* Adds a colored border to highlight the card (e.g. for pinned posts).
* Uses `--color-warning` CSS variable for border color.
*/
highlight: {
type: Boolean,
default: false,
},
},
setup(props, { slots, attrs }) {
/* v8 ignore start -- Vue 2 only */
const instance = isVue2 ? getCurrentInstance() : null
/* v8 ignore stop */
return () => {
const defaultContent = slots.default?.()
const heroImageContent = slots.heroImage?.()
const hasHeroImage = heroImageContent && heroImageContent.length > 0
const cardClass = cn(CARD_BASE, !hasHeroImage && 'p-6', props.highlight && HIGHLIGHT_CLASS)
const children = hasHeroImage
? [
h('div', { class: HERO_IMAGE_CLASS }, heroImageContent),
h('div', { class: CONTENT_CLASS }, defaultContent),
]
: defaultContent
const tag = props.as
/* v8 ignore start -- Vue 2 branch tested in webapp Jest tests */
if (isVue2) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const proxy = instance?.proxy as any
const parentClass = proxy?.$vnode?.data?.staticClass || ''
const parentDynClass = proxy?.$vnode?.data?.class
const parentAttrs = proxy?.$vnode?.data?.attrs || {}
return h(
tag,
{
class: cn(cardClass, parentClass, parentDynClass),
attrs: { ...parentAttrs, ...attrs },
},
children,
)
}
/* v8 ignore stop */
const { class: attrClass, ...restAttrs } = attrs as Record<string, unknown>
return h(
tag,
{
class: cn(cardClass, attrClass as ClassValue),
...restAttrs,
},
children,
)
}
},
})
</script>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -0,0 +1 @@
export { default as OsCard } from './OsCard.vue'

View File

@ -18,3 +18,4 @@ export {
type SystemIconName,
} from './OsIcon'
export { OsSpinner, SPINNER_SIZES } from './OsSpinner'
export { OsCard } from './OsCard'

View File

@ -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;

View File

@ -1,11 +1,11 @@
<template>
<base-card v-if="isUnavailable" class="comment-card">
<os-card as="article" v-if="isUnavailable" class="comment-card">
<p>
<os-icon :icon="icons.ban" />
{{ $t('comment.content.unavailable-placeholder') }}
</p>
</base-card>
<base-card v-else :class="commentClass" :id="anchor">
</os-card>
<os-card as="article" v-else :class="commentClass" :id="anchor">
<header class="header">
<user-teaser :user="comment.author" :date-time="comment.createdAt">
<template v-if="wasEdited" #dateTime>
@ -70,11 +70,11 @@
</template>
</os-button>
</div>
</base-card>
</os-card>
</template>
<script>
import { OsButton, OsIcon } from '@ocelot-social/ui'
import { OsButton, OsCard, OsIcon } from '@ocelot-social/ui'
import { iconRegistry } from '~/utils/iconRegistry'
import { mapGetters } from 'vuex'
import { COMMENT_MAX_UNTRUNCATED_LENGTH, COMMENT_TRUNCATE_TO_LENGTH } from '~/constants/comment'
@ -89,6 +89,7 @@ import scrollToAnchor from '~/mixins/scrollToAnchor.js'
export default {
components: {
OsButton,
OsCard,
OsIcon,
UserTeaser,
ContentMenu,

View File

@ -1,7 +1,7 @@
<template>
<ds-form v-model="form" @submit="handleSubmit" class="comment-form">
<template #default="{ errors }">
<base-card>
<os-card>
<hc-editor ref="editor" :users="users" :value="form.content" @input="updateEditorContent" />
<div class="buttons">
<os-button
@ -26,13 +26,13 @@
{{ $t('post.comment.submit') }}
</os-button>
</div>
</base-card>
</os-card>
</template>
</ds-form>
</template>
<script>
import { OsButton, OsIcon } from '@ocelot-social/ui'
import { OsButton, OsCard, OsIcon } from '@ocelot-social/ui'
import { iconRegistry } from '~/utils/iconRegistry'
import HcEditor from '~/components/Editor/Editor'
import { COMMENT_MIN_LENGTH } from '~/constants/comment'
@ -42,6 +42,7 @@ import CommentMutations from '~/graphql/CommentMutations'
export default {
components: {
OsButton,
OsCard,
OsIcon,
HcEditor,
},

View File

@ -8,7 +8,7 @@
@submit="submit"
>
<template #default="{ errors }">
<base-card>
<os-card>
<template #heroImage>
<img
v-if="formData.image"
@ -196,13 +196,13 @@
</os-button>
</ds-flex-item>
</ds-flex>
</base-card>
</os-card>
</template>
</ds-form>
</div>
</template>
<script>
import { OsButton, OsIcon } from '@ocelot-social/ui'
import { OsButton, OsCard, OsIcon } from '@ocelot-social/ui'
import { iconRegistry } from '~/utils/iconRegistry'
import gql from 'graphql-tag'
import { mapGetters } from 'vuex'
@ -224,6 +224,7 @@ export default {
Editor,
ImageUploader,
OsButton,
OsCard,
OsIcon,
PageParamsLink,
},
@ -540,15 +541,18 @@ export default {
}
}
.contribution-form > .base-card {
.contribution-form > .os-card {
display: flex;
flex-direction: column;
> .hero-image {
> .os-card__hero-image {
position: relative;
max-height: $size-image-max-height;
overflow: hidden;
> .image {
max-height: $size-image-max-height;
width: 100%;
object-fit: contain;
}
}
@ -556,41 +560,46 @@ export default {
filter: blur($blur-radius);
}
> .ds-form-item {
margin: 0;
}
> .os-card__content {
display: flex;
flex-direction: column;
> .ds-chip {
align-self: flex-end;
margin: $space-xx-small 0 $space-base;
cursor: default;
}
> .select-field {
align-self: flex-end;
}
> .buttons-footer {
justify-content: flex-end;
align-self: flex-end;
width: 100%;
margin-top: $space-base;
> .action-buttons-group {
margin-left: auto;
display: flex;
justify-content: flex-end;
> button {
margin-left: 1em;
min-width: fit-content;
}
> .ds-form-item {
margin: 0;
}
> .buttons-footer-helper {
margin-right: 16px;
// important needed because of component inline style
margin-bottom: 6px !important;
> .ds-chip {
align-self: flex-end;
margin: $space-xx-small 0 $space-base;
cursor: default;
}
> .select-field {
align-self: flex-end;
}
> .buttons-footer {
justify-content: flex-end;
align-self: flex-end;
width: 100%;
margin-top: $space-base;
> .action-buttons-group {
margin-left: auto;
display: flex;
justify-content: flex-end;
> button {
margin-left: 1em;
min-width: fit-content;
}
}
> .buttons-footer-helper {
margin-right: 16px;
// important needed because of component inline style
margin-bottom: 6px !important;
}
}
}
@ -604,7 +613,7 @@ export default {
}
@media screen and (max-width: 656px) {
> .buttons-footer {
> .os-card__content > .buttons-footer {
flex-direction: column;
margin-top: 5px;
@ -617,7 +626,7 @@ export default {
}
@media screen and (max-width: 280px) {
> .buttons-footer {
> .os-card__content > .buttons-footer {
> .action-buttons-group {
flex-direction: column;

View File

@ -1,5 +1,5 @@
<template>
<base-card class="delete-data">
<os-card class="delete-data">
<h2 class="title">
<os-icon :icon="icons.warning" />
{{ $t('settings.deleteUserAccount.name') }}
@ -55,11 +55,11 @@
</template>
{{ $t('settings.deleteUserAccount.name') }}
</os-button>
</base-card>
</os-card>
</template>
<script>
import { OsButton, OsIcon } from '@ocelot-social/ui'
import { OsButton, OsCard, OsIcon } from '@ocelot-social/ui'
import { iconRegistry } from '~/utils/iconRegistry'
import { mapActions, mapGetters } from 'vuex'
import gql from 'graphql-tag'
@ -67,7 +67,7 @@ import { currentUserCountQuery } from '~/graphql/User'
export default {
name: 'DeleteData',
components: { OsButton, OsIcon },
components: { OsButton, OsCard, OsIcon },
data() {
return {
deleteContributions: false,

View File

@ -1,6 +1,7 @@
import { storiesOf } from '@storybook/vue'
import { withA11y } from '@storybook/addon-a11y'
import ContentViewer from '~/components/Editor/ContentViewer.vue'
import { OsCard } from '@ocelot-social/ui'
import helpers from '~/storybook/helpers'
helpers.init()
@ -10,11 +11,11 @@ storiesOf('ContentViewer', module)
.addDecorator((storyFn) => {
const ctx = storyFn()
return {
components: { ctx },
components: { ctx, OsCard },
template: `
<base-card style="width: 50%; min-width: 500px; margin: 0 auto;">
<os-card style="width: 50%; min-width: 500px; margin: 0 auto;">
<ctx />
</base-card>
</os-card>
`,
}
})

View File

@ -1,6 +1,7 @@
import { storiesOf } from '@storybook/vue'
import { withA11y } from '@storybook/addon-a11y'
import HcEditor from '~/components/Editor/Editor.vue'
import { OsCard } from '@ocelot-social/ui'
import helpers from '~/storybook/helpers'
import Vue from 'vue'
@ -37,11 +38,11 @@ storiesOf('Editor', module)
.addDecorator((storyFn) => {
const ctx = storyFn()
return {
components: { ctx },
components: { ctx, OsCard },
template: `
<base-card style="width: 50%; min-width: 500px; margin: 0 auto;">
<os-card style="width: 50%; min-width: 500px; margin: 0 auto;">
<ctx />
</base-card>
</os-card>
`,
}
})

View File

@ -1,85 +0,0 @@
import { mount } from '@vue/test-utils'
// import Vuex from 'vuex'
// import locales from '~/locales'
// import orderBy from 'lodash/orderBy'
import LanguagesFilter from './LanguagesFilter'
const localVue = global.localVue
// let wrapper, englishButton, spanishButton
// const languages = orderBy(locales, 'name')
describe('mount', () => {
let wrapper
const Wrapper = () => {
return mount(LanguagesFilter, { localVue })
}
beforeEach(() => {
wrapper = Wrapper()
})
it('renders button DIV', () => {
expect(wrapper.find('div').exists()).toBe(true)
})
})
// describe('LanguagesFilter.vue', () => {
// const mutations = {
// 'posts/TOGGLE_LANGUAGE': jest.fn(),
// 'posts/RESET_LANGUAGES': jest.fn(),
// }
// const getters = {
// 'posts/filteredLanguageCodes': jest.fn(() => []),
// }
// const mocks = {
// $t: jest.fn((string) => string),
// }
// const Wrapper = () => {
// const store = new Vuex.Store({ mutations, getters })
// return mount(LanguagesFilter, { mocks, localVue, store })
// }
// beforeEach(() => {
// wrapper = Wrapper()
// })
// describe('mount', () => {
// it('starts with all categories button active', () => {
// const allLanguagesButton = wrapper.find('.languages-filter .sidebar button')
// expect(allLanguagesButton.attributes().class).toContain('--filled')
// })
// it('sets language button attribute `filled` when corresponding language is filtered', () => {
// getters['posts/filteredLanguageCodes'] = jest.fn(() => ['es'])
// const wrapper = Wrapper()
// spanishButton = wrapper
// .findAll('.languages-filter .item button')
// .at(languages.findIndex((l) => l.code === 'es'))
// expect(spanishButton.attributes().class).toContain('--filled')
// })
// describe('click on an "language-button" button', () => {
// it('calls TOGGLE_LANGUAGE when clicked', () => {
// const wrapper = Wrapper()
// englishButton = wrapper
// .findAll('.languages-filter .item button')
// .at(languages.findIndex((l) => l.code === 'en'))
// englishButton.trigger('click')
// expect(mutations['posts/TOGGLE_LANGUAGE']).toHaveBeenCalledWith({}, 'en')
// })
// })
// describe('clears filter', () => {
// it('when all button is clicked', async () => {
// getters['posts/filteredLanguageCodes'] = jest.fn(() => ['en'])
// wrapper = await Wrapper()
// const allLanguagesButton = wrapper.find('.languages-filter .sidebar button')
// allLanguagesButton.trigger('click')
// expect(mutations['posts/RESET_LANGUAGES']).toHaveBeenCalledTimes(1)
// })
// })
// })
// })

View File

@ -1,57 +0,0 @@
<template>
<div>
<!-- <filter-menu-section :title="$t('filter-menu.languages')" class="languages-filter">
<template>
<labeled-button
class="filter-languages"
:filled="!filteredLanguageCodes.length"
:label="$t('filter-menu.all')"
icon="check"
@click="resetLanguages"
/>
</template>
<template #filter-list>
<li v-for="language in locales" :key="language.code" class="item">
<base-button
:filled="filteredLanguageCodes.includes(language.code)"
circle
@click="toggleLanguage(language.code)"
>
{{ language.code.toUpperCase() }}
</base-button>
</li>
</template>
</filter-menu-section> -->
</div>
</template>
<script>
// import { mapGetters, mapMutations } from 'vuex'
// import orderBy from 'lodash/orderBy'
// import locales from '~/locales'
// import FilterMenuSection from '~/components/FilterMenu/FilterMenuSection'
// import LabeledButton from '~/components/_new/generic/LabeledButton/LabeledButton'
// export default {
// components: {
// FilterMenuSection,
// LabeledButton,
// },
// computed: {
// ...mapGetters({
// filteredLanguageCodes: 'posts/filteredLanguageCodes',
// }),
// },
// methods: {
// ...mapMutations({
// resetLanguages: 'posts/RESET_LANGUAGES',
// toggleLanguage: 'posts/TOGGLE_LANGUAGE',
// }),
// },
// data() {
// return {
// locales: orderBy(locales, 'name'),
// }
// },
// }
</script>

View File

@ -3,7 +3,7 @@
class="group-teaser"
:to="{ name: 'groups-id-slug', params: { id: group.id, slug: group.slug } }"
>
<base-card
<os-card
:class="{
'disabled-content': group.disabled,
}"
@ -74,12 +74,12 @@
</div>
</div>
</footer>
</base-card>
</os-card>
</nuxt-link>
</template>
<script>
import { OsIcon } from '@ocelot-social/ui'
import { OsCard, OsIcon } from '@ocelot-social/ui'
import { iconRegistry } from '~/utils/iconRegistry'
import Category from '~/components/Category'
import GroupContentMenu from '~/components/ContentMenu/GroupContentMenu'
@ -91,6 +91,7 @@ export default {
components: {
Category,
GroupContentMenu,
OsCard,
OsIcon,
},
props: {
@ -130,7 +131,7 @@ export default {
}
}
.group-teaser > .base-card {
.group-teaser > .os-card {
display: flex;
flex-direction: column;
height: 100%;

View File

@ -28,8 +28,7 @@ describe('HashtagsFilter.vue', () => {
})
it('renders a card', () => {
wrapper = Wrapper()
expect(wrapper.classes('base-card')).toBe(true)
expect(wrapper.classes('os-card')).toBe(true)
})
describe('click clear search button', () => {

View File

@ -1,5 +1,5 @@
<template>
<base-card class="hashtags-filter">
<os-card class="hashtags-filter">
<h2>{{ $t('hashtags-filter.hashtag-search', { hashtag }) }}</h2>
<os-button
data-test="clear-search-button"
@ -14,15 +14,15 @@
<os-icon :icon="icons.close" />
</template>
</os-button>
</base-card>
</os-card>
</template>
<script>
import { OsButton, OsIcon } from '@ocelot-social/ui'
import { OsButton, OsCard, OsIcon } from '@ocelot-social/ui'
import { iconRegistry } from '~/utils/iconRegistry'
export default {
components: { OsButton, OsIcon },
components: { OsButton, OsCard, OsIcon },
props: {
hashtag: {
type: String,
@ -41,10 +41,10 @@ export default {
</script>
<style lang="scss">
.hashtags-filter.base-card {
.hashtags-filter.os-card {
display: flex;
justify-content: space-between;
align-items: center;
padding: $space-x-small $space-base;
padding: $space-x-small $space-base !important;
}
</style>

View File

@ -4,58 +4,60 @@
<p>{{ $t('quotes.african.quote') }}</p>
<b>- {{ $t('quotes.african.author') }}</b>
</blockquote>
<base-card>
<template #imageColumn>
<os-card class="--columns">
<aside class="image-column" :aria-label="$t('login.moreInfo', metadata)">
<page-params-link :pageParams="links.ORGANIZATION" :title="$t('login.moreInfo', metadata)">
<logo logoType="welcome" />
</page-params-link>
</template>
<h2 class="title">{{ $t('login.login') }}</h2>
<form :disabled="pending" @submit.prevent="onSubmit">
<ds-input
v-model="form.email"
:disabled="pending"
:placeholder="$t('login.email')"
type="email"
name="email"
icon="envelope"
/>
<div class="password-wrapper">
</aside>
<section class="content-column">
<h2 class="title">{{ $t('login.login') }}</h2>
<form :disabled="pending" @submit.prevent="onSubmit">
<ds-input
v-model="form.password"
v-model="form.email"
:disabled="pending"
:placeholder="$t('login.password')"
icon="lock"
name="password"
class="password-field"
ref="password"
:type="showPassword ? 'text' : 'password'"
:placeholder="$t('login.email')"
type="email"
name="email"
icon="envelope"
/>
<show-password @show-password="toggleShowPassword" :icon="passwordIcon" />
</div>
<nuxt-link to="/password-reset/request">
{{ $t('login.forgotPassword') }}
</nuxt-link>
<os-button
:loading="pending"
variant="primary"
appearance="filled"
full-width
name="submit"
type="submit"
>
<template #icon><os-icon :icon="icons.signIn" /></template>
{{ $t('login.login') }}
</os-button>
<p>
{{ $t('login.no-account') }}
<nuxt-link to="/registration">{{ $t('login.register') }}</nuxt-link>
</p>
</form>
<template #topMenu>
<div class="password-wrapper">
<ds-input
v-model="form.password"
:disabled="pending"
:placeholder="$t('login.password')"
icon="lock"
name="password"
class="password-field"
ref="password"
:type="showPassword ? 'text' : 'password'"
/>
<show-password @show-password="toggleShowPassword" :icon="passwordIcon" />
</div>
<nuxt-link to="/password-reset/request">
{{ $t('login.forgotPassword') }}
</nuxt-link>
<os-button
:loading="pending"
variant="primary"
appearance="filled"
full-width
name="submit"
type="submit"
>
<template #icon><os-icon :icon="icons.signIn" /></template>
{{ $t('login.login') }}
</os-button>
<p>
{{ $t('login.no-account') }}
<nuxt-link to="/registration">{{ $t('login.register') }}</nuxt-link>
</p>
</form>
</section>
<aside class="top-menu" :aria-label="$t('localeSwitch.tooltip')">
<locale-switch offset="5" />
</template>
</base-card>
</aside>
</os-card>
</section>
</template>
@ -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')
})
},

View File

@ -18,74 +18,66 @@
<ds-grid-item>
<ds-flex class="user-section">
<ds-flex-item>
<base-card :wide-content="true">
<client-only>
<user-teaser
:user="
isGroup(notification.from)
? notification.relatedUser
: notification.from.author
"
:class="{ 'notification-status': notification.read }"
:date-time="notification.from.createdAt"
:injected-text="$t(`notifications.reason.${notification.reason}`)"
:injected-date="true"
:show-popover="showPopover"
/>
</client-only>
</base-card>
<user-teaser
:user="
isGroup(notification.from) ? notification.relatedUser : notification.from.author
"
:class="{ 'notification-status': notification.read }"
:date-time="notification.from.createdAt"
:injected-text="$t(`notifications.reason.${notification.reason}`)"
:injected-date="true"
:show-popover="showPopover"
/>
</ds-flex-item>
</ds-flex>
</ds-grid-item>
<ds-grid-item>
<base-card :wide-content="true">
<div class="notification-container">
<!-- Icon with responsive sizing -->
<div class="notification-icon">
<os-icon
v-if="notification.from.post"
:icon="icons.comment"
v-tooltip="{ content: $t('notifications.comment'), placement: 'right' }"
/>
<os-icon
v-else
:icon="icons.bookmark"
v-tooltip="{ content: $t('notifications.post'), placement: 'right' }"
/>
</div>
<!-- Content section with title and description -->
<div class="notification-content">
<nuxt-link
class="notification-mention-post"
:class="{ 'notification-status': notification.read }"
:to="{
name: isGroup(notification.from) ? 'groups-id-slug' : 'post-id-slug',
params: params(notification.from),
hash: hashParam(notification.from),
}"
@click.native.prevent="handleNotificationClick(notification)"
>
<b>
{{
notification.from.title ||
notification.from.groupName ||
notification.from.post.title | truncate(50)
}}
</b>
</nuxt-link>
<p
class="notification-description"
:class="{ 'notification-status': notification.read }"
>
{{
notification.from.contentExcerpt ||
notification.from.descriptionExcerpt | removeHtml
}}
</p>
</div>
<div class="notification-container">
<!-- Icon with responsive sizing -->
<div class="notification-icon">
<os-icon
v-if="notification.from.post"
:icon="icons.comment"
v-tooltip="{ content: $t('notifications.comment'), placement: 'right' }"
/>
<os-icon
v-else
:icon="icons.bookmark"
v-tooltip="{ content: $t('notifications.post'), placement: 'right' }"
/>
</div>
</base-card>
<!-- Content section with title and description -->
<div class="notification-content">
<nuxt-link
class="notification-mention-post"
:class="{ 'notification-status': notification.read }"
:to="{
name: isGroup(notification.from) ? 'groups-id-slug' : 'post-id-slug',
params: params(notification.from),
hash: hashParam(notification.from),
}"
@click.native.prevent="handleNotificationClick(notification)"
>
<b>
{{
notification.from.title ||
notification.from.groupName ||
(notification.from.post && notification.from.post.title) | truncate(50)
}}
</b>
</nuxt-link>
<p
class="notification-description"
:class="{ 'notification-status': notification.read }"
>
{{
notification.from.contentExcerpt ||
notification.from.descriptionExcerpt | removeHtml
}}
</p>
</div>
</div>
</ds-grid-item>
</ds-grid>
</ds-grid-item>
@ -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) {

View File

@ -3,7 +3,7 @@
class="post-teaser"
:to="{ name: 'post-id-slug', params: { id: post.id, slug: post.slug } }"
>
<base-card
<os-card
:lang="post.language"
:class="{
'disabled-content': post.disabled,
@ -138,12 +138,12 @@
</span>
</div>
</client-only>
</base-card>
</os-card>
</nuxt-link>
</template>
<script>
import { OsIcon } from '@ocelot-social/ui'
import { OsCard, OsIcon } from '@ocelot-social/ui'
import { iconRegistry } from '~/utils/iconRegistry'
import Category from '~/components/Category'
import ContentMenu from '~/components/ContentMenu/ContentMenu'
@ -165,6 +165,7 @@ export default {
components: {
Category,
ContentMenu,
OsCard,
OsIcon,
CounterIcon,
DateTimeRange,
@ -197,7 +198,7 @@ export default {
if (!image) return
const width = this.$el.offsetWidth
const height = Math.min(width / image.aspectRatio, 2000)
const imageElement = this.$el.querySelector('.hero-image')
const imageElement = this.$el.querySelector('.os-card__hero-image')
if (imageElement) {
imageElement.style.height = `${height}px`
}
@ -301,40 +302,46 @@ export default {
position: absolute;
// 14px (~height of ribbon element) + 24px(=margin of hero image)
top: -38px;
// 7px+24px(=padding of parent)
right: -31px;
// 7px+24px(=padding of parent) - 1px adjustment
right: -29px;
}
> .post-ribbon {
position: absolute;
// 14px (~height of ribbon element) + 24px(=margin of hero image)
top: -24px;
// 7px(=offset)+24px(=margin of parent)
right: -31px;
// 7px(=offset)+24px(=margin of parent) - 2px adjustment
right: -29px;
}
}
.post-teaser > .base-card {
.post-teaser > .os-card {
display: flex;
flex-direction: column;
overflow: visible;
height: 100%;
padding-bottom: $space-x-small;
> .hero-image {
> .os-card__hero-image {
border-top-left-radius: 5px;
border-top-right-radius: 5px;
}
&.--blur-image > .hero-image > .image {
&.--blur-image > .os-card__hero-image .image {
filter: blur($blur-radius);
}
> .content {
> .os-card__content {
display: flex;
flex-direction: column;
flex-grow: 1;
}
.content {
flex-grow: 1;
margin-bottom: $space-small;
}
> .footer {
.footer {
display: flex;
justify-content: space-between;
align-items: center;
@ -363,7 +370,8 @@ export default {
.user-teaser {
margin-bottom: $space-small;
}
> .date-row {
.date-row {
display: flex;
justify-content: flex-end;
margin-top: $space-small;

View File

@ -3,45 +3,46 @@
<div v-if="registrationType !== 'no-public-registration'" class="back-link" left>
<nuxt-link :to="loginLink">{{ $t('site.back-to-login') }}</nuxt-link>
</div>
<base-card>
<template #imageColumn>
<os-card class="--columns">
<aside class="image-column" :aria-label="$t('login.moreInfo', metadata)">
<page-params-link :pageParams="links.ORGANIZATION" :title="$t('login.moreInfo', metadata)">
<logo logoType="signup" />
</page-params-link>
</template>
</aside>
<section class="content-column">
<component-slider :sliderData="sliderData">
<template #no-public-registration>
<registration-slide-no-public :sliderData="sliderData" />
</template>
<component-slider :sliderData="sliderData">
<template #no-public-registration>
<registration-slide-no-public :sliderData="sliderData" />
</template>
<template #enter-invite>
<registration-slide-invite :sliderData="sliderData" />
</template>
<template #enter-invite>
<registration-slide-invite :sliderData="sliderData" />
</template>
<template #enter-email>
<registration-slide-email :sliderData="sliderData" :invitation="false" />
</template>
<template #enter-email>
<registration-slide-email :sliderData="sliderData" :invitation="false" />
</template>
<template #enter-nonce>
<registration-slide-nonce :sliderData="sliderData" />
</template>
<template #enter-nonce>
<registration-slide-nonce :sliderData="sliderData" />
</template>
<template #create-user-account>
<registration-slide-create :sliderData="sliderData" />
</template>
</component-slider>
<template #topMenu>
<template #create-user-account>
<registration-slide-create :sliderData="sliderData" />
</template>
</component-slider>
</section>
<aside class="top-menu" :aria-label="$t('localeSwitch.tooltip')">
<locale-switch offset="5" />
</template>
</base-card>
</aside>
</os-card>
</section>
</template>
<script>
import links from '~/constants/links.js'
import metadata from '~/constants/metadata.js'
import { OsCard } from '@ocelot-social/ui'
import ComponentSlider from '~/components/ComponentSlider/ComponentSlider'
import LocaleSwitch from '~/components/LocaleSwitch/LocaleSwitch'
import Logo from '~/components/Logo/Logo'
@ -58,6 +59,7 @@ export default {
ComponentSlider,
LocaleSwitch,
Logo,
OsCard,
PageParamsLink,
RegistrationSlideCreate,
RegistrationSlideEmail,

View File

@ -1,6 +1,6 @@
<template>
<ds-space v-if="user.socialMedia && user.socialMedia.length" margin="large">
<base-card class="social-media-bc">
<os-card class="social-media-bc">
<ds-space margin="x-small">
<ds-text tag="h5" color="soft" data-test="social-media-list-headline">
{{ $t('profile.socialMedia') }} {{ userName | truncate(15) }}?
@ -14,16 +14,19 @@
</ds-space>
</template>
</ds-space>
</base-card>
</os-card>
</ds-space>
</template>
<script>
import { OsCard } from '@ocelot-social/ui'
export default {
name: 'social-media',
components: { OsCard },
props: {
userName: {},
user: {},
userName: { type: String, required: true },
user: { type: Object, required: true },
},
methods: {
socialMediaLinks() {

View File

@ -8,7 +8,7 @@
@vdropzone-file-added="fileAdded"
>
<os-spinner v-if="isLoadingImage" size="lg" />
<os-icon v-else-if="!hasImage" :icon="icons.image" />
<os-icon v-else-if="!hasImage" size="lg" :icon="icons.image" />
<div v-if="!hasImage" class="supported-formats">
{{ $t('contribution.teaserImage.supportedFormats') }}
</div>
@ -113,7 +113,9 @@ export default {
const supportedFormats = ['image/jpg', 'image/jpeg', 'image/png', 'image/gif']
if (supportedFormats.indexOf(file.type) < 0) {
this.onUnSupportedFormat(this.$t('contribution.teaserImage.errors.unSupported-file-format'))
this.$nextTick((this.isLoadingImage = false))
this.$nextTick(() => {
this.isLoadingImage = false
})
return null
}
const imageURL = URL.createObjectURL(file)
@ -126,7 +128,9 @@ export default {
this.saveImage(aspectRatio, file, file.type)
this.file = file
if (this.file.type === 'image/jpeg') this.imageCanBeCropped = true
this.$nextTick((this.isLoadingImage = false))
this.$nextTick(() => {
this.isLoadingImage = false
})
},
initCropper() {
this.showCropper = true
@ -138,7 +142,9 @@ export default {
this.isLoadingImage = true
const onCropComplete = (aspectRatio, imageFile, imageType) => {
this.saveImage(aspectRatio, imageFile, imageType)
this.$nextTick((this.isLoadingImage = false))
this.$nextTick(() => {
this.isLoadingImage = false
})
this.closeCropper()
}
if (this.file.type === 'image/jpeg') {

View File

@ -10,24 +10,26 @@
<br />
<div v-html="$t(pageParams.internalPage.htmlIdent)" />
</div>
<base-card v-else>
<os-card v-else>
<div v-html="$t(pageParams.internalPage.htmlIdent)" />
</base-card>
</os-card>
</ds-container>
<div v-else-if="!pageParams.internalPage.hasBaseCard">
<br />
<div v-html="$t(pageParams.internalPage.htmlIdent)" />
</div>
<base-card v-else>
<os-card v-else>
<div v-html="$t(pageParams.internalPage.htmlIdent)" />
</base-card>
</os-card>
</div>
</template>
<script>
import { OsCard } from '@ocelot-social/ui'
import { PageParams } from '~/components/utils/PageParams.js'
export default {
components: { OsCard },
name: 'InternalPage',
props: {
pageParams: { type: Object, required: true },
@ -49,8 +51,8 @@ export default {
padding-left: 0 !important;
padding-right: 0 !important;
.base-card {
padding: 16px !important;
.os-card {
padding: $space-small !important;
}
}
}

View File

@ -62,25 +62,25 @@
<!-- users -->
<template v-if="activeTab === 'User'">
<ds-grid-item v-for="user in activeResources" :key="user.id" :row-span="2">
<base-card :wideContent="true">
<os-card>
<user-teaser :user="user" />
</base-card>
</os-card>
</ds-grid-item>
</template>
<!-- groups -->
<template v-if="activeTab === 'Group'">
<ds-grid-item v-for="group in activeResources" :key="group.id" :row-span="2">
<base-card :wideContent="true" class="group-teaser-card-wrapper">
<os-card class="group-teaser-card-wrapper">
<group-teaser :group="{ ...group, name: group.groupName }" />
</base-card>
</os-card>
</ds-grid-item>
</template>
<!-- hashtags -->
<template v-if="activeTab === 'Hashtag'">
<ds-grid-item v-for="hashtag in activeResources" :key="hashtag.id" :row-span="2">
<base-card :wideContent="true">
<os-card>
<hc-hashtag :id="hashtag.id" />
</base-card>
</os-card>
</ds-grid-item>
</template>
@ -115,6 +115,7 @@
</template>
<script>
import { OsCard } from '@ocelot-social/ui'
import postListActions from '~/mixins/postListActions'
import { searchPosts, searchUsers, searchGroups, searchHashtags } from '~/graphql/Search.js'
import HcEmpty from '~/components/Empty/Empty'
@ -130,6 +131,7 @@ import HcHashtag from '~/components/Hashtag/Hashtag'
export default {
name: 'SearchResults',
components: {
OsCard,
TabNavigation,
HcEmpty,
MasonryGrid,

View File

@ -1,133 +0,0 @@
<template>
<article :class="classNames">
<template v-if="$slots.imageColumn">
<aside class="image-column">
<slot name="imageColumn" />
</aside>
<section class="content-column">
<slot />
</section>
</template>
<template v-else-if="$slots.heroImage">
<section class="hero-image">
<slot name="heroImage" />
</section>
<slot />
</template>
<slot v-else />
<aside v-if="$slots.topMenu" class="top-menu">
<slot name="topMenu" />
</aside>
</article>
</template>
<script>
export default {
props: {
highlight: {
type: Boolean,
default: false,
},
wideContent: {
type: Boolean,
default: false,
},
},
computed: {
classNames() {
let classNames = 'base-card'
if (this.$slots.imageColumn) classNames += ' --columns'
if (this.highlight) classNames += ' --highlight'
if (this.wideContent) classNames += ' --wide-content'
return classNames
},
},
}
</script>
<style lang="scss">
.base-card {
position: relative;
padding: $space-base;
border-radius: $border-radius-x-large;
overflow: hidden;
overflow-wrap: break-word;
background-color: $color-neutral-100;
box-shadow: $box-shadow-base;
&.--columns {
display: flex;
}
&.--highlight {
border: $border-size-base solid $color-ribbon-announcement;
}
&.--wide-content {
padding: $space-small;
> .hero-image {
width: calc(100% + (2 * #{$space-small}));
margin: -$space-small;
margin-bottom: $space-small;
}
}
> .title,
> .content-column > .title {
font-size: $font-size-large;
margin-bottom: $space-x-small;
}
> .hero-image {
width: calc(100% + (2 * #{$space-base}));
max-height: $size-image-max-height;
margin: -$space-base;
margin-bottom: $space-base;
overflow: hidden;
> .image {
width: 100%;
object-fit: contain;
}
}
> .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) {
.base-card.--columns {
flex-direction: column;
> .image-column {
padding-right: 0;
margin-bottom: $space-base;
}
}
}
</style>

View File

@ -1,6 +1,6 @@
<template>
<ds-grid-item class="tab-navigation" :row-span="tabs.length" column-span="fullWidth">
<base-card class="ds-tab-nav">
<os-card class="ds-tab-nav">
<ul class="Tabs">
<li
v-for="tab in tabs"
@ -24,15 +24,17 @@
</a>
</li>
</ul>
</base-card>
</os-card>
</ds-grid-item>
</template>
<script>
import { OsCard } from '@ocelot-social/ui'
import HcCountTo from '~/components/CountTo.vue'
export default {
components: {
OsCard,
HcCountTo,
},
props: {
@ -42,6 +44,7 @@ export default {
},
activeTab: {
type: String,
default: null,
},
},
methods: {
@ -93,8 +96,8 @@ export default {
top: 53px;
z-index: 2;
}
.ds-tab-nav.base-card {
padding: 0;
.ds-tab-nav.os-card {
padding: 0 !important;
.ds-tab-nav-item {
&.ds-tab-nav-item-active {

View File

@ -1,6 +1,7 @@
import { faker } from '@faker-js/faker'
import { storiesOf } from '@storybook/vue'
import { withA11y } from '@storybook/addon-a11y'
import { OsCard } from '@ocelot-social/ui'
import HcEmpty from '~/components/Empty/Empty'
import MasonryGrid from '~/components/MasonryGrid/MasonryGrid'
import MasonryGridItem from '~/components/MasonryGrid/MasonryGridItem'
@ -72,6 +73,7 @@ storiesOf('TabNavigator', module)
PostTeaser,
UserTeaser,
HcHashtag,
OsCard,
},
store: helpers.store,
data: () => ({
@ -160,17 +162,17 @@ storiesOf('TabNavigator', module)
<!-- users -->
<template v-if="activeTab === 'User'">
<ds-grid-item v-for="user in activeResources" :key="user.id" :row-span="2">
<base-card :wideContent="true">
<os-card>
<user-teaser :user="user" />
</base-card>
</os-card>
</ds-grid-item>
</template>
<!-- hashtags -->
<template v-if="activeTab === 'Hashtag'">
<ds-grid-item v-for="hashtag in activeResources" :key="hashtag.id" :row-span="2">
<base-card :wideContent="true">
<os-card>
<hc-hashtag :id="hashtag.id" />
</base-card>
</os-card>
</ds-grid-item>
</template>
</template>

View File

@ -1,5 +1,5 @@
<template>
<base-card class="profile-list">
<os-card class="profile-list">
<template v-if="profiles.length">
<h5 class="title spacer-x-small">
{{ title }}
@ -60,11 +60,11 @@
</os-button>
</template>
<p v-else-if="titleNobody" class="nobody-message">{{ titleNobody }}</p>
</base-card>
</os-card>
</template>
<script>
import { OsButton } from '@ocelot-social/ui'
import { OsButton, OsCard } from '@ocelot-social/ui'
import { escape } from 'xregexp/xregexp-all.js'
// @ts-ignore
import { RecycleScroller } from 'vue-virtual-scroller'
@ -78,6 +78,7 @@ export default {
name: 'ProfileList',
components: {
OsButton,
OsCard,
UserTeaser,
RecycleScroller,
},
@ -172,7 +173,7 @@ export default {
</script>
<style lang="scss">
.profile-list {
.profile-list.os-card {
display: flex;
flex-direction: column;
position: relative;

View File

@ -1,6 +1,7 @@
import { storiesOf } from '@storybook/vue'
import { withA11y } from '@storybook/addon-a11y'
import { action } from '@storybook/addon-actions'
import { OsCard } from '@ocelot-social/ui'
import { post } from '~/components/PostTeaser/PostTeaser.story.js'
import { user } from '~/components/UserTeaser/UserTeaser.story.js'
import helpers from '~/storybook/helpers'
@ -172,7 +173,7 @@ storiesOf('ReportList', module)
.addDecorator(withA11y)
.addDecorator(helpers.layout)
.add('with reports', () => ({
components: { ReportList, DropdownFilter, ReportsTable },
components: { ReportList, DropdownFilter, ReportsTable, OsCard },
store: helpers.store,
data: () => ({
filterOptions,
@ -183,11 +184,11 @@ storiesOf('ReportList', module)
openModal: action('openModal'),
filter: action('filter'),
},
template: `<base-card>
template: `<os-card>
<div class="reports-header">
<h3 class="title">Reports</h3>
<dropdown-filter @filter="filter" :filterOptions="filterOptions" :selected="selected" />
</div>
<reports-table :reports="reports" @confirm="openModal" />
</base-card>`,
</os-card>`,
}))

View File

@ -1,5 +1,5 @@
<template>
<base-card>
<os-card>
<div class="reports-header">
<h3 class="title">{{ $t('moderation.reports.name') }}</h3>
<client-only>
@ -8,10 +8,11 @@
</div>
<reports-table :reports="reports" @confirm="openModal" />
<pagination-buttons :hasNext="hasNext" :hasPrevious="hasPrevious" @back="back" @next="next" />
</base-card>
</os-card>
</template>
<script>
import { OsCard } from '@ocelot-social/ui'
import { iconRegistry } from '~/utils/iconRegistry'
import { mapMutations } from 'vuex'
import DropdownFilter from '~/components/DropdownFilter/DropdownFilter'
@ -21,6 +22,7 @@ import PaginationButtons from '~/components/_new/generic/PaginationButtons/Pagin
export default {
components: {
OsCard,
DropdownFilter,
ReportsTable,
PaginationButtons,

View File

@ -38,7 +38,6 @@ export default {
styleResources,
plugins: [
{ src: '~/plugins/base-components.js', ssr: true },
{ src: `~/plugins/styleguide.js`, ssr: true },
{ src: '~/plugins/i18n.js', ssr: true },
{ src: '~/plugins/v-tooltip.js', ssr: false },

View File

@ -1,7 +1,7 @@
<template>
<transition name="fade" appear>
<ds-container width="medium">
<base-card>
<os-card>
<ds-space>
<locale-switch class="login-locale-switch" offset="5" />
</ds-space>
@ -38,12 +38,13 @@
</ds-flex-item>
</ds-flex-item>
</ds-flex>
</base-card>
</os-card>
</ds-container>
</transition>
</template>
<script>
import { OsCard } from '@ocelot-social/ui'
import emails from '~/constants/emails.js'
// import links from '~/constants/links.js'
import metadata from '~/constants/metadata.js'
@ -53,6 +54,7 @@ import LocaleSwitch from '~/components/LocaleSwitch/LocaleSwitch'
export default {
layout: 'blank',
components: {
OsCard,
LocaleSwitch,
// Logo,
},

View File

@ -109,7 +109,6 @@ export default {
** Plugins to load before mounting the App
*/
plugins: [
{ src: '~/plugins/base-components.js', ssr: true },
{
src: `~/plugins/styleguide.js`,
ssr: true,

View File

@ -19,13 +19,11 @@ exports[`_static mount renders 1`] = `
<div
class="ds-container ds-container-x-large"
>
<article
class="base-card"
<div
class="os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6"
>
<div />
<!---->
</article>
</div>
</div>
</div>
</div>

View File

@ -26,7 +26,7 @@ describe('categories.vue', () => {
})
it('renders', () => {
expect(wrapper.classes('base-card')).toBe(true)
expect(wrapper.classes('os-card')).toBe(true)
})
})
})

View File

@ -1,21 +1,21 @@
<template>
<base-card>
<os-card>
<h2 class="title">{{ $t('admin.categories.name') }}</h2>
<ds-table :data="Category" :fields="fields" condensed>
<template #icon="scope">
<os-icon :icon="resolveIcon(scope.row.icon)" />
</template>
</ds-table>
</base-card>
</os-card>
</template>
<script>
import { OsIcon } from '@ocelot-social/ui'
import { OsCard, OsIcon } from '@ocelot-social/ui'
import { resolveIcon } from '~/utils/iconRegistry'
import gql from 'graphql-tag'
export default {
components: { OsIcon },
components: { OsCard, OsIcon },
data() {
return {
Category: [],

View File

@ -57,7 +57,7 @@ describe('donations.vue', () => {
})
it('renders', () => {
expect(wrapper.classes('base-card')).toBe(true)
expect(wrapper.classes('os-card')).toBe(true)
})
describe('displays', () => {

View File

@ -1,5 +1,5 @@
<template>
<base-card>
<os-card>
<h2 class="title">{{ $t('admin.donations.name') }}</h2>
<ds-form v-model="formData" @submit="submit">
<ds-text class="show-donations-checkbox">
@ -32,15 +32,15 @@
{{ $t('actions.save') }}
</os-button>
</ds-form>
</base-card>
</os-card>
</template>
<script>
import { OsButton } from '@ocelot-social/ui'
import { OsButton, OsCard } from '@ocelot-social/ui'
import { DonationsQuery, UpdateDonations } from '~/graphql/Donations'
export default {
components: { OsButton },
components: { OsButton, OsCard },
data() {
return {
formData: {

View File

@ -26,7 +26,7 @@ describe('hashtags.vue', () => {
})
it('renders', () => {
expect(wrapper.classes('base-card')).toBe(true)
expect(wrapper.classes('os-card')).toBe(true)
})
})
})

View File

@ -1,5 +1,5 @@
<template>
<base-card>
<os-card>
<h2 class="title">{{ $t('admin.hashtags.name') }}</h2>
<ds-table :data="Tag" :fields="fields" condensed>
<template #index="scope">{{ scope.index + 1 }}.</template>
@ -9,13 +9,15 @@
</nuxt-link>
</template>
</ds-table>
</base-card>
</os-card>
</template>
<script>
import { OsCard } from '@ocelot-social/ui'
import gql from 'graphql-tag'
export default {
components: { OsCard },
data() {
return {
Tag: [],

View File

@ -1,5 +1,5 @@
<template>
<base-card>
<os-card>
<template v-if="$apollo.loading">
<div style="text-align: center; padding: 48px 0">
<os-spinner size="xl" />
@ -34,16 +34,17 @@
</ds-text>
</ds-space>
</template>
</base-card>
</os-card>
</template>
<script>
import { OsSpinner } from '@ocelot-social/ui'
import { OsCard, OsSpinner } from '@ocelot-social/ui'
import HcCountTo from '~/components/CountTo.vue'
import { Statistics } from '~/graphql/admin/Statistics'
export default {
components: {
OsCard,
OsSpinner,
HcCountTo,
},

View File

@ -4,17 +4,19 @@
<ds-heading size="h3">{{ $t('admin.invites.title') }}</ds-heading>
<ds-text>{{ $t('admin.invites.description') }}</ds-text>
</ds-space>
<base-card>
<os-card>
<signup :invitation="true" />
</base-card>
</os-card>
</ds-section>
</template>
<script>
import { OsCard } from '@ocelot-social/ui'
import Signup from '~/components/Registration/Signup'
export default {
components: {
OsCard,
Signup,
},
}

View File

@ -29,7 +29,7 @@ describe('notifications.vue', () => {
})
it('renders', () => {
expect(wrapper.classes('base-card')).toBe(true)
expect(wrapper.classes('os-card')).toBe(true)
})
})
})

View File

@ -1,15 +1,17 @@
<template>
<base-card>
<os-card>
<h2 class="title">{{ $t('admin.notifications.name') }}</h2>
<hc-empty icon="tasks" message="Coming Soon…" />
</base-card>
</os-card>
</template>
<script>
import { OsCard } from '@ocelot-social/ui'
import HcEmpty from '~/components/Empty/Empty'
export default {
components: {
OsCard,
HcEmpty,
},
}

View File

@ -29,7 +29,7 @@ describe('organizations.vue', () => {
})
it('renders', () => {
expect(wrapper.classes('base-card')).toBe(true)
expect(wrapper.classes('os-card')).toBe(true)
})
})
})

View File

@ -1,15 +1,17 @@
<template>
<base-card>
<os-card>
<h2 class="title">{{ $t('admin.organizations.name') }}</h2>
<hc-empty icon="tasks" message="Coming Soon…" />
</base-card>
</os-card>
</template>
<script>
import { OsCard } from '@ocelot-social/ui'
import HcEmpty from '~/components/Empty/Empty'
export default {
components: {
OsCard,
HcEmpty,
},
}

View File

@ -29,7 +29,7 @@ describe('pages.vue', () => {
})
it('renders', () => {
expect(wrapper.classes('base-card')).toBe(true)
expect(wrapper.classes('os-card')).toBe(true)
})
})
})

View File

@ -1,15 +1,17 @@
<template>
<base-card>
<os-card>
<h2 class="title">{{ $t('admin.pages.name') }}</h2>
<hc-empty icon="tasks" message="Coming Soon…" />
</base-card>
</os-card>
</template>
<script>
import { OsCard } from '@ocelot-social/ui'
import HcEmpty from '~/components/Empty/Empty'
export default {
components: {
OsCard,
HcEmpty,
},
}

View File

@ -29,7 +29,7 @@ describe('settings.vue', () => {
})
it('renders', () => {
expect(wrapper.classes('base-card')).toBe(true)
expect(wrapper.classes('os-card')).toBe(true)
})
})
})

View File

@ -1,15 +1,17 @@
<template>
<base-card>
<os-card>
<h2 class="title">{{ $t('admin.settings.name') }}</h2>
<hc-empty icon="tasks" message="Coming Soon…" />
</base-card>
</os-card>
</template>
<script>
import { OsCard } from '@ocelot-social/ui'
import HcEmpty from '~/components/Empty/Empty'
export default {
components: {
OsCard,
HcEmpty,
},
}

View File

@ -33,8 +33,8 @@ exports[`.vue renders 1`] = `
</p>
</div>
<article
class="base-card"
<div
class="os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6"
>
<div
class="badge-section"
@ -93,9 +93,7 @@ exports[`.vue renders 1`] = `
</button>
</div>
</div>
<!---->
</article>
</div>
</div>
</div>
</section>

View File

@ -4,8 +4,8 @@ exports[`Users given badges are disabled renders 1`] = `
<div
class="admin-users"
>
<article
class="base-card"
<div
class="os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6"
>
<h2
class="title"
@ -119,12 +119,10 @@ exports[`Users given badges are disabled renders 1`] = `
</div>
</div>
</form>
<!---->
</article>
</div>
<article
class="base-card"
<div
class="os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6"
>
<div
class="ds-table-wrap"
@ -452,9 +450,7 @@ exports[`Users given badges are disabled renders 1`] = `
</span>
</button>
</div>
<!---->
</article>
</div>
</div>
`;
@ -462,8 +458,8 @@ exports[`Users given badges are enabled renders 1`] = `
<div
class="admin-users"
>
<article
class="base-card"
<div
class="os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6"
>
<h2
class="title"
@ -577,12 +573,10 @@ exports[`Users given badges are enabled renders 1`] = `
</div>
</div>
</form>
<!---->
</article>
</div>
<article
class="base-card"
<div
class="os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6"
>
<div
class="ds-table-wrap"
@ -988,8 +982,6 @@ exports[`Users given badges are enabled renders 1`] = `
</span>
</button>
</div>
<!---->
</article>
</div>
</div>
`;

View File

@ -8,7 +8,7 @@
</ds-heading>
<ds-text>{{ $t('admin.badges.description') }}</ds-text>
</ds-space>
<base-card v-if="!isLoadingBadges">
<os-card v-if="!isLoadingBadges">
<badges-section
:title="$t('admin.badges.verificationBadges')"
:badges="verificationBadges"
@ -19,11 +19,12 @@
:badges="trophyBadges"
@toggleBadge="toggleBadge"
/>
</base-card>
</os-card>
</ds-section>
</template>
<script>
import { OsCard } from '@ocelot-social/ui'
import BadgesSection from '~/components/_new/features/Admin/Badges/BadgesSection.vue'
import {
queryBadges,
@ -35,6 +36,7 @@ import { adminUserBadgesQuery } from '~/graphql/User'
export default {
components: {
OsCard,
BadgesSection,
},
data() {

View File

@ -1,6 +1,6 @@
<template>
<div class="admin-users">
<base-card>
<os-card>
<h2 class="title">{{ $t('admin.users.name') }}</h2>
<ds-form v-model="form" @submit="submit">
<ds-flex gutter="small">
@ -25,8 +25,8 @@
</ds-flex-item>
</ds-flex>
</ds-form>
</base-card>
<base-card v-if="User && User.length">
</os-card>
<os-card v-if="User && User.length">
<ds-table :data="User" :fields="fields" condensed>
<template #index="scope">{{ scope.row.index + 1 }}.</template>
<template #name="scope">
@ -89,15 +89,15 @@
</template>
</ds-table>
<pagination-buttons :hasNext="hasNext" :hasPrevious="hasPrevious" @next="next" @back="back" />
</base-card>
<base-card v-else>
</os-card>
<os-card v-else>
<ds-placeholder>{{ $t('admin.users.empty') }}</ds-placeholder>
</base-card>
</os-card>
</div>
</template>
<script>
import { OsButton, OsIcon } from '@ocelot-social/ui'
import { OsButton, OsCard, OsIcon } from '@ocelot-social/ui'
import { iconRegistry } from '~/utils/iconRegistry'
import { mapGetters } from 'vuex'
import { isEmail } from 'validator'
@ -108,6 +108,7 @@ import { FetchAllRoles, updateUserRole } from '~/graphql/admin/Roles'
export default {
components: {
OsButton,
OsCard,
OsIcon,
PaginationButtons,
},
@ -241,7 +242,7 @@ export default {
</script>
<style lang="scss">
.admin-users > .base-card:first-child {
.admin-users > .os-card:first-child {
margin-bottom: $space-small;
}

View File

@ -18,8 +18,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
class="ds-flex-item"
style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;"
>
<article
class="base-card"
<div
class="os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6"
style="position: relative; height: auto; overflow: visible;"
>
<div
@ -566,9 +566,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
</span>
</div>
</div>
<!---->
</article>
</div>
<div
class="ds-space"
@ -584,8 +582,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
</h3>
<article
class="profile-list base-card"
<div
class="profile-list os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6 profile-list"
>
<h5
class="title spacer-x-small"
@ -927,9 +925,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
<!---->
<!---->
<!---->
</article>
</div>
</div>
<div
@ -940,8 +936,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
class="ds-space"
style="margin-bottom: 32px;"
>
<article
class="group-description base-card"
<div
class="group-description os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6 group-description"
>
<div
class="content hyphenate-text"
@ -979,9 +975,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
</span>
</button>
<!---->
</article>
</div>
</div>
<div
@ -1159,8 +1153,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
class="ds-flex-item"
style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;"
>
<article
class="base-card"
<div
class="os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6"
style="position: relative; height: auto; overflow: visible;"
>
<div
@ -1465,9 +1459,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
</span>
</div>
</div>
<!---->
</article>
</div>
<div
class="ds-space"
@ -1483,17 +1475,15 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
</h3>
<article
class="profile-list base-card"
<div
class="profile-list os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6 profile-list"
>
<p
class="nobody-message"
>
group.membersListTitleNotAllowedSeeingGroupMembers
</p>
<!---->
</article>
</div>
</div>
<div
@ -1504,8 +1494,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
class="ds-space"
style="margin-bottom: 32px;"
>
<article
class="group-description base-card"
<div
class="group-description os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6 group-description"
>
<div
class="content hyphenate-text"
@ -1543,9 +1533,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
</span>
</button>
<!---->
</article>
</div>
</div>
<!---->
@ -1688,8 +1676,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
class="ds-flex-item"
style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;"
>
<article
class="base-card"
<div
class="os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6"
style="position: relative; height: auto; overflow: visible;"
>
<div
@ -2014,9 +2002,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
</span>
</div>
</div>
<!---->
</article>
</div>
<div
class="ds-space"
@ -2032,17 +2018,15 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
</h3>
<article
class="profile-list base-card"
<div
class="profile-list os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6 profile-list"
>
<p
class="nobody-message"
>
group.membersListTitleNotAllowedSeeingGroupMembers
</p>
<!---->
</article>
</div>
</div>
<div
@ -2053,8 +2037,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
class="ds-space"
style="margin-bottom: 32px;"
>
<article
class="group-description base-card"
<div
class="group-description os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6 group-description"
>
<div
class="content hyphenate-text"
@ -2092,9 +2076,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
</span>
</button>
<!---->
</article>
</div>
</div>
<!---->
@ -2237,8 +2219,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
class="ds-flex-item"
style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;"
>
<article
class="base-card"
<div
class="os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6"
style="position: relative; height: auto; overflow: visible;"
>
<div
@ -2696,9 +2678,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
</span>
</div>
</div>
<!---->
</article>
</div>
<div
class="ds-space"
@ -2714,8 +2694,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
</h3>
<article
class="profile-list base-card"
<div
class="profile-list os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6 profile-list"
>
<h5
class="title spacer-x-small"
@ -3057,9 +3037,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
<!---->
<!---->
<!---->
</article>
</div>
</div>
<div
@ -3070,8 +3048,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
class="ds-space"
style="margin-bottom: 32px;"
>
<article
class="group-description base-card"
<div
class="group-description os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6 group-description"
>
<div
class="content hyphenate-text"
@ -3109,9 +3087,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
</span>
</button>
<!---->
</article>
</div>
</div>
<div
@ -3289,8 +3265,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
class="ds-flex-item"
style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;"
>
<article
class="base-card"
<div
class="os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6"
style="position: relative; height: auto; overflow: visible;"
>
<div
@ -3762,9 +3738,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
</div>
<!---->
<!---->
</article>
</div>
<div
class="ds-space"
@ -3780,8 +3754,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
</h3>
<article
class="profile-list base-card"
<div
class="profile-list os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6 profile-list"
>
<h5
class="title spacer-x-small"
@ -4123,9 +4097,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
<!---->
<!---->
<!---->
</article>
</div>
</div>
<div
@ -4136,8 +4108,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
class="ds-space"
style="margin-bottom: 32px;"
>
<article
class="group-description base-card"
<div
class="group-description os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6 group-description"
>
<div
class="content hyphenate-text"
@ -4180,9 +4152,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
</span>
</button>
<!---->
</article>
</div>
</div>
<div
@ -4360,8 +4330,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
class="ds-flex-item"
style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;"
>
<article
class="base-card"
<div
class="os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6"
style="position: relative; height: auto; overflow: visible;"
>
<div
@ -4629,9 +4599,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
</div>
<!---->
<!---->
</article>
</div>
<div
class="ds-space"
@ -4647,8 +4615,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
</h3>
<article
class="profile-list base-card"
<div
class="profile-list os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6 profile-list"
>
<h5
class="title spacer-x-small"
@ -4990,9 +4958,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
<!---->
<!---->
<!---->
</article>
</div>
</div>
<div
@ -5003,8 +4969,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
class="ds-space"
style="margin-bottom: 32px;"
>
<article
class="group-description base-card"
<div
class="group-description os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6 group-description"
>
<div
class="content hyphenate-text"
@ -5047,9 +5013,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
</span>
</button>
<!---->
</article>
</div>
</div>
<!---->
@ -5192,8 +5156,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
class="ds-flex-item"
style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;"
>
<article
class="base-card"
<div
class="os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6"
style="position: relative; height: auto; overflow: visible;"
>
<div
@ -5481,9 +5445,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
</div>
<!---->
<!---->
</article>
</div>
<div
class="ds-space"
@ -5499,8 +5461,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
</h3>
<article
class="profile-list base-card"
<div
class="profile-list os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6 profile-list"
>
<h5
class="title spacer-x-small"
@ -5842,9 +5804,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
<!---->
<!---->
<!---->
</article>
</div>
</div>
<div
@ -5855,8 +5815,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
class="ds-space"
style="margin-bottom: 32px;"
>
<article
class="group-description base-card"
<div
class="group-description os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6 group-description"
>
<div
class="content hyphenate-text"
@ -5899,9 +5859,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
</span>
</button>
<!---->
</article>
</div>
</div>
<!---->
@ -6044,8 +6002,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
class="ds-flex-item"
style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;"
>
<article
class="base-card"
<div
class="os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6"
style="position: relative; height: auto; overflow: visible;"
>
<div
@ -6428,9 +6386,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
</div>
<!---->
<!---->
</article>
</div>
<div
class="ds-space"
@ -6446,8 +6402,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
</h3>
<article
class="profile-list base-card"
<div
class="profile-list os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6 profile-list"
>
<h5
class="title spacer-x-small"
@ -6789,9 +6745,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
<!---->
<!---->
<!---->
</article>
</div>
</div>
<div
@ -6802,8 +6756,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
class="ds-space"
style="margin-bottom: 32px;"
>
<article
class="group-description base-card"
<div
class="group-description os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6 group-description"
>
<div
class="content hyphenate-text"
@ -6846,9 +6800,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
</span>
</button>
<!---->
</article>
</div>
</div>
<div
@ -7026,8 +6978,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
class="ds-flex-item"
style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;"
>
<article
class="base-card"
<div
class="os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6"
style="position: relative; height: auto; overflow: visible;"
>
<div
@ -7574,9 +7526,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
</span>
</div>
</div>
<!---->
</article>
</div>
<div
class="ds-space"
@ -7592,8 +7542,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
</h3>
<article
class="profile-list base-card"
<div
class="profile-list os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6 profile-list"
>
<h5
class="title spacer-x-small"
@ -7935,9 +7885,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
<!---->
<!---->
<!---->
</article>
</div>
</div>
<div
@ -7948,8 +7896,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
class="ds-space"
style="margin-bottom: 32px;"
>
<article
class="group-description base-card"
<div
class="group-description os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6 group-description"
>
<div
class="content hyphenate-text"
@ -8004,9 +7952,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
</span>
</button>
<!---->
</article>
</div>
</div>
<div
@ -8196,8 +8142,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
class="ds-flex-item"
style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;"
>
<article
class="base-card"
<div
class="os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6"
style="position: relative; height: auto; overflow: visible;"
>
<div
@ -8655,9 +8601,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
</span>
</div>
</div>
<!---->
</article>
</div>
<div
class="ds-space"
@ -8673,8 +8617,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
</h3>
<article
class="profile-list base-card"
<div
class="profile-list os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6 profile-list"
>
<h5
class="title spacer-x-small"
@ -9016,9 +8960,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
<!---->
<!---->
<!---->
</article>
</div>
</div>
<div
@ -9029,8 +8971,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
class="ds-space"
style="margin-bottom: 32px;"
>
<article
class="group-description base-card"
<div
class="group-description os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6 group-description"
>
<div
class="content hyphenate-text"
@ -9085,9 +9027,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
</span>
</button>
<!---->
</article>
</div>
</div>
<div

View File

@ -3,7 +3,7 @@
<ds-space />
<ds-flex v-if="group" :width="{ base: '100%' }" gutter="base">
<ds-flex-item :width="{ base: '100%', sm: 2, md: 2, lg: 1 }">
<base-card
<os-card
:class="{ 'disabled-content': group.disabled }"
style="position: relative; height: auto; overflow: visible"
>
@ -169,7 +169,7 @@
</div>
</ds-space>
</template>
</base-card>
</os-card>
<ds-space />
<ds-heading tag="h3" soft style="text-align: center; margin-bottom: 10px">
{{ $t('profile.network.title') }}
@ -196,7 +196,7 @@
<ds-flex-item :width="{ base: '100%', sm: 3, md: 5, lg: 3 }">
<!-- Group description -->
<ds-space>
<base-card class="group-description">
<os-card class="group-description">
<!-- TODO: replace editor content with tiptap render view -->
<!-- eslint-disable-next-line vue/no-v-html -->
<div
@ -214,7 +214,7 @@
>
{{ isDescriptionCollapsed ? $t('comment.show.more') : $t('comment.show.less') }}
</os-button>
</base-card>
</os-card>
</ds-space>
<ds-space v-if="isGroupMemberNonePending" centered>
<os-button
@ -290,7 +290,7 @@
</template>
<script>
import { OsButton, OsIcon, OsSpinner } from '@ocelot-social/ui'
import { OsButton, OsCard, OsIcon, OsSpinner } from '@ocelot-social/ui'
import { iconRegistry } from '~/utils/iconRegistry'
import uniqBy from 'lodash/uniqBy'
import { profilePagePosts } from '~/graphql/PostQuery'
@ -327,6 +327,7 @@ import GetCategories from '~/mixins/getCategoriesMixin.js'
export default {
components: {
OsCard,
OsButton,
OsIcon,
OsSpinner,
@ -673,17 +674,18 @@ export default {
.chip {
margin-bottom: $space-x-small;
}
.group-description > .base-card {
.group-description.os-card {
display: flex;
flex-direction: column;
height: 100%;
padding-bottom: $space-base !important;
> .content {
.content {
flex-grow: 1;
margin-bottom: $space-small;
}
}
.collaps-button {
float: right;
align-self: flex-end;
}
</style>

View File

@ -5,7 +5,7 @@
</ds-space>
<ds-space margin="large" />
<ds-container>
<base-card>
<os-card>
<ds-space margin="large">
<ds-flex :width="{ base: '100%' }" gutter="base">
<ds-flex-item :width="{ base: '100%', md: 5 }">
@ -16,17 +16,19 @@
<ds-flex-item :width="{ base: '100%', md: 1 }">&nbsp;</ds-flex-item>
</ds-flex>
</ds-space>
</base-card>
</os-card>
</ds-container>
</div>
</template>
<script>
import { OsCard } from '@ocelot-social/ui'
import GroupForm from '~/components/Group/GroupForm'
import { createGroupMutation } from '~/graphql/groups.js'
export default {
components: {
OsCard,
GroupForm,
},
data() {

View File

@ -3,8 +3,8 @@
exports[`invites.vue renders 1`] = `
<div>
<div>
<article
class="base-card"
<div
class="os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6"
>
<h3
class="ds-heading ds-heading-h3"
@ -207,9 +207,7 @@ exports[`invites.vue renders 1`] = `
</form>
</div>
</div>
<!---->
</article>
</div>
</div>
</div>
`;

View File

@ -1,19 +1,21 @@
<template>
<div>
<base-card>
<os-card>
<ds-heading tag="h3">{{ $t('group.general') }}</ds-heading>
<ds-space margin="large" />
<group-form :group="group" :update="true" @updateGroup="updateGroup" />
</base-card>
</os-card>
</div>
</template>
<script>
import { OsCard } from '@ocelot-social/ui'
import GroupForm from '~/components/Group/GroupForm'
import { updateGroupMutation } from '~/graphql/groups.js'
export default {
components: {
OsCard,
GroupForm,
},
props: {

View File

@ -1,6 +1,6 @@
<template>
<div>
<base-card>
<os-card>
<ds-heading tag="h3">{{ $t('invite-codes.group-invite-links') }}</ds-heading>
<ds-space margin="large" />
<invitation-list
@ -18,16 +18,18 @@
})
"
/>
</base-card>
</os-card>
</div>
</template>
<script>
import { OsCard } from '@ocelot-social/ui'
import InvitationList from '~/components/_new/features/Invitations/InvitationList.vue'
import { generateGroupInviteCode, invalidateInviteCode } from '~/graphql/InviteCode'
export default {
components: {
OsCard,
InvitationList,
},
props: {

View File

@ -6,23 +6,25 @@
@loadGroupMembers="loadGroupMembers"
/>
<ds-space margin-bottom="small" />
<base-card>
<os-card>
<group-member
:groupId="group.id"
:groupMembers="groupMembers"
@loadGroupMembers="loadGroupMembers"
/>
</base-card>
</os-card>
</div>
</template>
<script>
import { OsCard } from '@ocelot-social/ui'
import GroupMember from '~/components/Group/GroupMember'
import AddGroupMember from '~/components/Group/AddGroupMember'
import { groupMembersQuery } from '~/graphql/groups.js'
export default {
components: {
OsCard,
GroupMember,
AddGroupMember,
},

View File

@ -27,7 +27,7 @@ describe('moderation/index.vue', () => {
})
it('renders', () => {
expect(wrapper.classes('base-card')).toBe(true)
expect(wrapper.classes('os-card')).toBe(true)
})
})
})

View File

@ -1,5 +1,5 @@
<template>
<base-card>
<os-card>
<ds-flex class="notifications-page-flex">
<ds-flex-item :width="{ lg: '85%' }">
<ds-heading tag="h3">{{ $t('notifications.title') }}</ds-heading>
@ -38,11 +38,11 @@
/>
</ds-flex-item>
</ds-flex>
</base-card>
</os-card>
</template>
<script>
import { OsButton } from '@ocelot-social/ui'
import { OsButton, OsCard } from '@ocelot-social/ui'
import NotificationsTable from '~/components/NotificationsTable/NotificationsTable'
import DropdownFilter from '~/components/DropdownFilter/DropdownFilter'
import PaginationButtons from '~/components/_new/generic/PaginationButtons/PaginationButtons'
@ -50,6 +50,7 @@ import { notificationQuery, markAsReadMutation, markAllAsReadMutation } from '~/
export default {
components: {
OsCard,
OsButton,
DropdownFilter,
NotificationsTable,

View File

@ -4,17 +4,19 @@
<nuxt-link to="/login">{{ $t('site.back-to-login') }}</nuxt-link>
</div>
<base-card>
<template #imageColumn>
<os-card class="--columns">
<aside class="image-column" :aria-label="$t('login.moreInfo', metadata)">
<page-params-link :pageParams="links.ORGANIZATION" :title="$t('login.moreInfo', metadata)">
<logo logoType="passwordReset" />
</page-params-link>
</template>
<nuxt-child />
<template #topMenu>
</aside>
<section class="content-column">
<nuxt-child />
</section>
<aside class="top-menu" :aria-label="$t('localeSwitch.tooltip')">
<locale-switch offset="5" />
</template>
</base-card>
</aside>
</os-card>
</ds-container>
</template>
@ -22,6 +24,7 @@
import links from '~/constants/links.js'
import metadata from '~/constants/metadata.js'
import loginConstants from '~/constants/loginBranded.js'
import { OsCard } from '@ocelot-social/ui'
import LocaleSwitch from '~/components/LocaleSwitch/LocaleSwitch'
import Logo from '~/components/Logo/Logo'
import PageParamsLink from '~/components/_new/features/PageParamsLink/PageParamsLink.vue'
@ -30,6 +33,7 @@ export default {
components: {
LocaleSwitch,
Logo,
OsCard,
PageParamsLink,
},
layout: loginConstants.LAYOUT,

View File

@ -15,7 +15,7 @@
<ds-space margin="large" />
<ds-flex gutter="small">
<ds-flex-item :width="{ base: '100%', sm: 2, md: 2, lg: 1 }">
<base-card
<os-card
v-if="post && ready"
:lang="post.language"
:class="{
@ -170,7 +170,7 @@
</hc-empty>
</ds-placeholder>
</ds-section>
</base-card>
</os-card>
</ds-flex-item>
<ds-flex-item :width="{ base: '200px' }">
<ds-menu :routes="routes" class="post-side-navigation" />
@ -181,7 +181,7 @@
</template>
<script>
import { OsButton, OsIcon } from '@ocelot-social/ui'
import { OsButton, OsCard, OsIcon } from '@ocelot-social/ui'
import { iconRegistry } from '~/utils/iconRegistry'
import ContentViewer from '~/components/Editor/ContentViewer'
import CommentForm from '~/components/CommentForm/CommentForm'
@ -218,6 +218,7 @@ export default {
mode: 'out-in',
},
components: {
OsCard,
OsButton,
OsIcon,
CommentForm,
@ -426,14 +427,14 @@ export default {
z-index: 2;
}
.post-page {
> .hero-image {
> .os-card__hero-image {
position: relative;
/* The padding top makes sure the correct height is set (according to the
hero image aspect ratio) before the hero image loads so
the autoscroll works correctly when following a comment link.
*/
padding-top: calc(var(--hero-image-aspect-ratio) * (100% + 48px));
padding-top: calc(var(--hero-image-aspect-ratio) * 100%);
/* Letting the image fill the container, since the container
is the one determining height
*/
@ -446,13 +447,13 @@ export default {
}
}
> .menu {
.menu {
display: flex;
justify-content: space-between;
align-items: center;
}
&.--blur-image > .hero-image > .image {
&.--blur-image > .os-card__hero-image > .image {
filter: blur($blur-radius);
}

View File

@ -16,8 +16,8 @@ exports[`ProfileSlug given an authenticated user given another profile user and
class="ds-flex-item"
style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;"
>
<article
class="base-card"
<div
class="os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6"
style="position: relative; height: auto; overflow: visible;"
>
<div
@ -435,9 +435,7 @@ exports[`ProfileSlug given an authenticated user given another profile user and
</div>
<!---->
<!---->
</article>
</div>
<div
class="ds-space"
@ -483,8 +481,8 @@ exports[`ProfileSlug given an authenticated user given another profile user and
class="tab-navigation"
style="grid-row-end: span 3; grid-column-start: 1; grid-column-end: -1;"
>
<article
class="ds-tab-nav base-card"
<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"
>
<ul
class="Tabs"
@ -628,9 +626,7 @@ exports[`ProfileSlug given an authenticated user given another profile user and
</a>
</li>
</ul>
<!---->
</article>
</div>
</div>
<!---->
@ -765,8 +761,8 @@ exports[`ProfileSlug given an authenticated user given another profile user and
class="ds-flex-item"
style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;"
>
<article
class="base-card"
<div
class="os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6"
style="position: relative; height: auto; overflow: visible;"
>
<div
@ -1221,9 +1217,7 @@ exports[`ProfileSlug given an authenticated user given another profile user and
</div>
<!---->
<!---->
</article>
</div>
<div
class="ds-space"
@ -1269,8 +1263,8 @@ exports[`ProfileSlug given an authenticated user given another profile user and
class="tab-navigation"
style="grid-row-end: span 3; grid-column-start: 1; grid-column-end: -1;"
>
<article
class="ds-tab-nav base-card"
<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"
>
<ul
class="Tabs"
@ -1414,9 +1408,7 @@ exports[`ProfileSlug given an authenticated user given another profile user and
</a>
</li>
</ul>
<!---->
</article>
</div>
</div>
<!---->
@ -1551,8 +1543,8 @@ exports[`ProfileSlug given an authenticated user given the logged in user as pro
class="ds-flex-item"
style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;"
>
<article
class="base-card"
<div
class="os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6"
style="position: relative; height: auto; overflow: visible;"
>
<div
@ -1830,9 +1822,7 @@ exports[`ProfileSlug given an authenticated user given the logged in user as pro
<!---->
<!---->
<!---->
</article>
</div>
<div
class="ds-space"
@ -1878,8 +1868,8 @@ exports[`ProfileSlug given an authenticated user given the logged in user as pro
class="tab-navigation"
style="grid-row-end: span 3; grid-column-start: 1; grid-column-end: -1;"
>
<article
class="ds-tab-nav base-card"
<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"
>
<ul
class="Tabs"
@ -2023,9 +2013,7 @@ exports[`ProfileSlug given an authenticated user given the logged in user as pro
</a>
</li>
</ul>
<!---->
</article>
</div>
</div>
<div
@ -2198,8 +2186,8 @@ exports[`ProfileSlug given an authenticated user given the logged in user as pro
class="ds-flex-item"
style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;"
>
<article
class="base-card"
<div
class="os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6"
style="position: relative; height: auto; overflow: visible;"
>
<div
@ -2519,9 +2507,7 @@ exports[`ProfileSlug given an authenticated user given the logged in user as pro
<!---->
<!---->
<!---->
</article>
</div>
<div
class="ds-space"
@ -2567,8 +2553,8 @@ exports[`ProfileSlug given an authenticated user given the logged in user as pro
class="tab-navigation"
style="grid-row-end: span 3; grid-column-start: 1; grid-column-end: -1;"
>
<article
class="ds-tab-nav base-card"
<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"
>
<ul
class="Tabs"
@ -2712,9 +2698,7 @@ exports[`ProfileSlug given an authenticated user given the logged in user as pro
</a>
</li>
</ul>
<!---->
</article>
</div>
</div>
<div

View File

@ -3,7 +3,7 @@
<ds-space />
<ds-flex v-if="user" :width="{ base: '100%' }" gutter="base">
<ds-flex-item :width="{ base: '100%', sm: 2, md: 2, lg: 1 }">
<base-card
<os-card
:class="{ 'disabled-content': user.disabled }"
style="position: relative; height: auto; overflow: visible"
>
@ -117,7 +117,7 @@
</ds-text>
</ds-space>
</template>
</base-card>
</os-card>
<ds-space />
<ds-heading tag="h3" soft style="text-align: center; margin-bottom: 10px">
{{ $t('profile.network.title') }}
@ -212,7 +212,7 @@
</template>
<script>
import { OsButton, OsIcon, OsSpinner } from '@ocelot-social/ui'
import { OsButton, OsCard, OsIcon, OsSpinner } from '@ocelot-social/ui'
import { iconRegistry } from '~/utils/iconRegistry'
import uniqBy from 'lodash/uniqBy'
import { mapGetters, mapMutations } from 'vuex'
@ -247,6 +247,7 @@ const tabToFilterMapping = ({ tab, id }) => {
export default {
components: {
OsCard,
OsButton,
OsIcon,
OsSpinner,

View File

@ -2,8 +2,8 @@
exports[`badge settings with badges more badges available selecting an empty slot shows list with available badges 1`] = `
<div>
<article
class="base-card"
<div
class="os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6"
>
<h2
class="title"
@ -129,16 +129,14 @@ exports[`badge settings with badges more badges available selecting an empty slo
</div>
</div>
</div>
<!---->
</article>
</div>
</div>
`;
exports[`badge settings with badges no more badges available selecting an empty slot shows no more badges available message 1`] = `
<div>
<article
class="base-card"
<div
class="os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6"
>
<h2
class="title"
@ -211,16 +209,14 @@ exports[`badge settings with badges no more badges available selecting an empty
<!---->
</div>
<!---->
</article>
</div>
</div>
`;
exports[`badge settings with badges renders 1`] = `
<div>
<article
class="base-card"
<div
class="os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6"
>
<h2
class="title"
@ -295,92 +291,14 @@ exports[`badge settings with badges renders 1`] = `
<!---->
</div>
<!---->
</article>
</div>
`;
exports[`badge settings with badges selecting a used badge clicking remove badge button with successful server request removes the badge 1`] = `
<div>
<article
class="base-card"
>
<h2
class="title"
>
settings.badges.name
</h2>
<p>
settings.badges.description
</p>
<div
class="ds-space"
>
<div
class="presenterContainer"
>
<div
class="hc-badges"
style="transform: scale(2);"
>
<button
class="hc-badge-container"
>
<img
class="hc-badge"
src="/api/verification/icon"
title="Verification description"
/>
</button>
<button
class="hc-badge-container selectable"
>
<img
class="hc-badge"
src="/api/path/to/some/icon"
title="Some description"
/>
</button>
<button
class="hc-badge-container selectable"
>
<img
class="hc-badge"
src="/api/path/to/empty/icon"
title="Empty"
/>
</button>
<button
class="hc-badge-container selectable"
>
<img
class="hc-badge"
src="/api/path/to/third/icon"
title="Third description"
/>
</button>
</div>
</div>
<!---->
<!---->
<!---->
</div>
<!---->
</article>
</div>
</div>
`;
exports[`badge settings without badges renders 1`] = `
<div>
<article
class="base-card"
<div
class="os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6"
>
<h2
class="title"
@ -422,8 +340,6 @@ exports[`badge settings without badges renders 1`] = `
<!---->
</div>
<!---->
</article>
</div>
</div>
`;

View File

@ -2,8 +2,8 @@
exports[`notifications.vue mount renders 1`] = `
<div>
<article
class="base-card"
<div
class="os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6"
>
<h2
class="title"
@ -206,8 +206,6 @@ exports[`notifications.vue mount renders 1`] = `
</span>
</button>
</div>
<!---->
</article>
</div>
</div>
`;

View File

@ -1,5 +1,5 @@
<template>
<base-card>
<os-card>
<h2 class="title">{{ $t('settings.badges.name') }}</h2>
<p>{{ $t('settings.badges.description') }}</p>
<ds-space centered margin-bottom="small" margin-top="base">
@ -50,11 +50,11 @@
/>
</div>
</ds-space>
</base-card>
</os-card>
</template>
<script>
import { OsButton } from '@ocelot-social/ui'
import { OsButton, OsCard } from '@ocelot-social/ui'
import { mapGetters, mapMutations } from 'vuex'
import { setTrophyBadgeSelected } from '~/graphql/User'
import scrollToContent from './scroll-to-content.js'
@ -62,7 +62,7 @@ import Badges from '../../components/Badges.vue'
import BadgeSelection from '../../components/BadgeSelection.vue'
export default {
components: { OsButton, BadgeSelection, Badges },
components: { OsButton, OsCard, BadgeSelection, Badges },
mixins: [scrollToContent],
data() {
return {

View File

@ -1,7 +1,7 @@
<template>
<div>
<ds-space>
<base-card>
<os-card>
<h2 class="title">{{ $t('settings.blocked-users.name') }}</h2>
<ds-text>
{{ $t('settings.blocked-users.explanation.intro') }}
@ -17,9 +17,9 @@
{{ $t('settings.blocked-users.explanation.notifications') }}
</ds-list-item>
</ds-list>
</base-card>
</os-card>
</ds-space>
<base-card v-if="blockedUsers && blockedUsers.length">
<os-card v-if="blockedUsers && blockedUsers.length">
<ds-table :data="blockedUsers" :fields="fields" condensed>
<template #avatar="scope">
<nuxt-link
@ -67,8 +67,8 @@
</os-button>
</template>
</ds-table>
</base-card>
<base-card v-else>
</os-card>
<os-card v-else>
<ds-space>
<ds-placeholder>
{{ $t('settings.blocked-users.empty') }}
@ -79,12 +79,12 @@
{{ $t('settings.blocked-users.how-to') }}
</ds-text>
</ds-space>
</base-card>
</os-card>
</div>
</template>
<script>
import { OsButton, OsIcon } from '@ocelot-social/ui'
import { OsButton, OsCard, OsIcon } from '@ocelot-social/ui'
import { iconRegistry } from '~/utils/iconRegistry'
import { blockedUsers, unblockUser } from '~/graphql/settings/BlockedUsers'
import ProfileAvatar from '~/components/_new/generic/ProfileAvatar/ProfileAvatar'
@ -94,6 +94,7 @@ export default {
mixins: [scrollToContent],
components: {
OsButton,
OsCard,
OsIcon,
ProfileAvatar,
},

View File

@ -26,7 +26,7 @@ describe('data-download.vue', () => {
})
it('renders', () => {
expect(wrapper.classes('base-card')).toBe(true)
expect(wrapper.classes('os-card')).toBe(true)
})
})
})

View File

@ -1,5 +1,5 @@
<template>
<base-card>
<os-card>
<h2 class="title">{{ $t('settings.download.name') }}</h2>
<os-button
variant="primary"
@ -14,15 +14,15 @@
<ds-space margin="large" />
<ds-text>{{ $t('settings.download.description') }}</ds-text>
<ds-space margin="large" />
<base-card v-for="image in imageList" :key="image.key">
<os-card v-for="image in imageList" :key="image.key">
<a :href="image.url" target="_blank" rel="noopener noreferrer">{{ image.title }}</a>
<ds-space margin="xxx-small" />
</base-card>
</base-card>
</os-card>
</os-card>
</template>
<script>
import { OsButton, OsIcon } from '@ocelot-social/ui'
import { OsButton, OsCard, OsIcon } from '@ocelot-social/ui'
import { iconRegistry } from '~/utils/iconRegistry'
import { mapGetters } from 'vuex'
import { userDataQuery } from '~/graphql/User'
@ -33,6 +33,7 @@ export default {
mixins: [scrollToContent],
components: {
OsButton,
OsCard,
OsIcon,
},
created() {

View File

@ -36,7 +36,7 @@ describe('delete-account.vue', () => {
})
it('renders', () => {
expect(wrapper.classes('base-card')).toBe(true)
expect(wrapper.classes('os-card')).toBe(true)
})
})
})

View File

@ -36,7 +36,7 @@ describe('embeds.vue', () => {
})
it('renders', () => {
expect(wrapper.classes('base-card')).toBe(true)
expect(wrapper.classes('os-card')).toBe(true)
})
})
})

View File

@ -1,5 +1,5 @@
<template>
<base-card>
<os-card>
<h2 class="title">{{ $t('settings.embeds.name') }}</h2>
<ds-section>
<ds-text>
@ -51,18 +51,18 @@
</ul>
</ds-space>
</ds-section>
</base-card>
</os-card>
</template>
<script>
import { OsButton } from '@ocelot-social/ui'
import { OsButton, OsCard } from '@ocelot-social/ui'
import axios from 'axios'
import { mapGetters, mapMutations } from 'vuex'
import { updateUserMutation } from '~/graphql/User.js'
import scrollToContent from './scroll-to-content.js'
export default {
components: { OsButton },
components: { OsButton, OsCard },
mixins: [scrollToContent],
head() {
return {

View File

@ -1,7 +1,7 @@
<template>
<ds-form class="settings-form" v-model="formData" :schema="formSchema" @submit="submit">
<template #default="{ errors }">
<base-card>
<os-card>
<h2 class="title">{{ $t('settings.data.name') }}</h2>
<ds-input
id="name"
@ -39,13 +39,13 @@
<template #icon><os-icon :icon="icons.check" /></template>
{{ $t('actions.save') }}
</os-button>
</base-card>
</os-card>
</template>
</ds-form>
</template>
<script>
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'
import UniqueSlugForm from '~/components/utils/UniqueSlugForm'
@ -58,6 +58,7 @@ export default {
name: 'Settings',
components: {
OsButton,
OsCard,
OsIcon,
LocationSelect,
},

View File

@ -26,7 +26,7 @@ describe('invites.vue', () => {
})
it('renders', () => {
expect(wrapper.classes('base-card')).toBe(true)
expect(wrapper.classes('os-card')).toBe(true)
})
})
})

View File

@ -1,17 +1,19 @@
<template>
<base-card>
<os-card>
<h2 class="title">{{ $t('settings.invites.name') }}</h2>
<hc-empty icon="tasks" message="Coming Soon…" />
</base-card>
</os-card>
</template>
<script>
import { OsCard } from '@ocelot-social/ui'
import HcEmpty from '~/components/Empty/Empty'
import scrollToContent from './scroll-to-content.js'
export default {
mixins: [scrollToContent],
components: {
OsCard,
HcEmpty,
},
}

Some files were not shown because too many files have changed in this diff Show More