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', () => { defineStep('I can visit the post page', () => {
cy.contains('Fake news').click() cy.contains('Fake news').click()
cy.location('pathname').should('contain', '/post') 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' import { defineStep } from '@badeball/cypress-cucumber-preprocessor'
defineStep('I click on "Report Post" from the content menu of the post', () => { 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"]') .find('[data-test="content-menu-button"]')
.click() .click()

View File

@ -2,7 +2,7 @@ import { defineStep } from '@badeball/cypress-cucumber-preprocessor'
defineStep('the post was saved successfully', () => { defineStep('the post was saved successfully', () => {
cy.task('getValue', 'lastPost').then(lastPost => { 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) cy.get('.content').should('contain', lastPost.content)
}) })
}) })

View File

@ -1,9 +1,7 @@
import { defineStep } from '@badeball/cypress-cucumber-preprocessor' import { defineStep } from '@badeball/cypress-cucumber-preprocessor'
defineStep('the first image should not be displayed anymore', () => { defineStep('the first image should not be displayed anymore', () => {
cy.get('.hero-image') cy.get('.os-card__hero-image > .image')
.children()
.get('.hero-image > .image')
.should('have.length', 1) .should('have.length', 1)
.and('have.attr', 'src') .and('have.attr', 'src')
}) })

View File

@ -1,7 +1,7 @@
import { defineStep } from '@badeball/cypress-cucumber-preprocessor' import { defineStep } from '@badeball/cypress-cucumber-preprocessor'
defineStep('the post was saved successfully with the {string} teaser image', condition => { 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') .should('contain', condition === 'updated' ? 'to be updated' : 'new post')
.get('.content') .get('.content')
.should('contain', condition === 'updated' ? 'successfully updated' : 'new post content') .should('contain', condition === 'updated' ? 'successfully updated' : 'new post content')

View File

@ -1,12 +1,12 @@
import { defineStep } from '@badeball/cypress-cucumber-preprocessor' import { defineStep } from '@badeball/cypress-cucumber-preprocessor'
defineStep('the {string} post was saved successfully without a teaser image', condition => { 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') .should("contain", condition === 'updated' ? 'to be updated' : 'new post')
.get(".content") .get(".content")
.should("contain", condition === 'updated' ? 'successfully updated' : 'new post content') .should("contain", condition === 'updated' ? 'successfully updated' : 'new post content')
.get('.post-page') .get('.post-page')
.should('exist') .should('exist')
.get('.hero-image > .image') .get('.os-card__hero-image > .image')
.should('not.exist') .should('not.exist')
}) })

View File

@ -1,7 +1,7 @@
import { defineStep } from '@badeball/cypress-cucumber-preprocessor' import { defineStep } from '@badeball/cypress-cucumber-preprocessor'
defineStep('the post shows up on the newsfeed at position {int}', index => { 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', 'previously created post')
cy.get(selector).should('contain', 'with some content') cy.get(selector).should('contain', 'with some content')
}) })

View File

@ -1,6 +1,6 @@
import { defineStep } from '@badeball/cypress-cucumber-preprocessor' import { defineStep } from '@badeball/cypress-cucumber-preprocessor'
defineStep('I should not see {string} button', button => { defineStep('I should not see {string} button', button => {
cy.get('.base-card .action-buttons') cy.get('.os-card .action-buttons')
.should('have.length', 1) .should('have.length', 1)
}) })

View File

@ -1,6 +1,6 @@
import { defineStep } from '@badeball/cypress-cucumber-preprocessor' import { defineStep } from '@badeball/cypress-cucumber-preprocessor'
defineStep('I should see the {string} button', button => { 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) .should('contain', button)
}) })

View File

@ -1,5 +1,5 @@
import { defineStep } from '@badeball/cypress-cucumber-preprocessor' import { defineStep } from '@badeball/cypress-cucumber-preprocessor'
defineStep('they should not see the comment form', () => { 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' import { defineStep } from '@badeball/cypress-cucumber-preprocessor'
defineStep('the list of posts of this user is empty', () => { 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') cy.get('.main-container').find('.ds-space.hc-empty')
}) })

View File

@ -1,7 +1,7 @@
import { defineStep } from '@badeball/cypress-cucumber-preprocessor' import { defineStep } from '@badeball/cypress-cucumber-preprocessor'
defineStep('I cannot upload a picture', () => { defineStep('I cannot upload a picture', () => {
cy.get('.base-card') cy.get('.os-card')
.children() .children()
.should('not.have.id', 'customdropzone') .should('not.have.id', 'customdropzone')
.should('have.class', 'profile-avatar') .should('have.class', 'profile-avatar')

View File

@ -1,8 +1,8 @@
import { defineStep } from '@badeball/cypress-cucumber-preprocessor' import { defineStep } from '@badeball/cypress-cucumber-preprocessor'
defineStep('I get removed from his follower collection', () => { defineStep('I get removed from his follower collection', () => {
cy.get('.base-card') cy.get('.os-card')
.not('.post-link') .not('.post-link')
cy.get('.main-container') 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 1: ██████████ 100% (6/6 Aufgaben) ✅
Phase 2: ██████████ 100% (26/26 Aufgaben) ✅ Phase 2: ██████████ 100% (26/26 Aufgaben) ✅
Phase 3: ██████████ 100% (24/24 Aufgaben) ✅ - Webapp-Integration komplett 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) Phase 5: ░░░░░░░░░░ 0% (0/7 Aufgaben)
─────────────────────────────────────── ───────────────────────────────────────
Gesamt: ████████░░ 79% (68/86 Aufgaben) Gesamt: ████████░░ 81% (70/86 Aufgaben)
``` ```
### Katalogisierung (Details in KATALOG.md) ### Katalogisierung (Details in KATALOG.md)
@ -142,6 +142,23 @@ OsSpinner:
├─ vue-compat: ✅ h() Render-Function mit isVue2 ├─ vue-compat: ✅ h() Render-Function mit isVue2
└─ webapp: ✅ 4 Spinner migriert (DsSpinner + LoadingSpinner → OsSpinner) └─ 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: ✅ DsSpinner/LoadingSpinner → OsSpinner Webapp-Migration: ✅
├─ ImageUploader.vue: LoadingSpinner → OsSpinner (size="lg") ├─ ImageUploader.vue: LoadingSpinner → OsSpinner (size="lg")
├─ pages/profile: ds-spinner → os-spinner (size="lg") ├─ pages/profile: ds-spinner → os-spinner (size="lg")
@ -166,11 +183,30 @@ BaseIcon → OsIcon Webapp-Migration: ✅
## Aktueller Stand ## 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] OsButton refactored: nutzt `h(OsSpinner, { 'aria-hidden': 'true' })` statt Inline-SVG
- [x] OsSpinner: Decorative-Modus (`aria-hidden="true"` unterdrückt role/aria-label) - [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 - [x] `ButtonSize` Type exportiert (sm/md/lg/xl), `types.d.ts` Kommentar aktualisiert
@ -263,8 +299,8 @@ BaseIcon → OsIcon Webapp-Migration: ✅
**Nächste Schritte:** **Nächste Schritte:**
- [x] OsSpinner Webapp-Migration (DsSpinner + LoadingSpinner → OsSpinner) ✅ - [x] OsSpinner Webapp-Migration (DsSpinner + LoadingSpinner → OsSpinner) ✅
- [ ] OsCard Komponente (vereint DsCard + BaseCard) - [x] OsCard Komponente + BaseCard → OsCard Webapp-Migration ✅
- [ ] Weitere Tier 1 Komponenten - [ ] 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) - [ ] 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):** **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 (vereint DsSpinner + LoadingSpinner) ✅ OsButton nutzt OsSpinner als Komponente
- [x] OsSpinner Webapp-Migration ✅ 4 Spinner migriert, LoadingSpinner gelöscht, Admin ApolloQuery→$apollo.loading - [x] OsSpinner Webapp-Migration ✅ 4 Spinner migriert, LoadingSpinner gelöscht, Admin ApolloQuery→$apollo.loading
- [x] OsButton (vereint DsButton + BaseButton) ✅ Entwickelt in Phase 2 - [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** **Tier 2: Layout & Feedback**
- [ ] OsModal (Basis: DsModal) - [ ] 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 | **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 | **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-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": [ "size-limit": [
{ {
"path": "dist/index.mjs", "path": "dist/index.mjs",
"limit": "15 kB", "limit": "20 kB",
"brotli": true "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, type SystemIconName,
} from './OsIcon' } from './OsIcon'
export { OsSpinner, SPINNER_SIZES } from './OsSpinner' export { OsSpinner, SPINNER_SIZES } from './OsSpinner'
export { OsCard } from './OsCard'

View File

@ -151,7 +151,7 @@ body.dropdown-open {
overflow: hidden; overflow: hidden;
} }
.base-card > .ds-section { .os-card > .ds-section {
padding: 0; padding: 0;
margin: -$space-base; 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'] { [class$='menu-popover'] {
min-width: 130px; min-width: 130px;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,7 @@
import { storiesOf } from '@storybook/vue' import { storiesOf } from '@storybook/vue'
import { withA11y } from '@storybook/addon-a11y' import { withA11y } from '@storybook/addon-a11y'
import HcEditor from '~/components/Editor/Editor.vue' import HcEditor from '~/components/Editor/Editor.vue'
import { OsCard } from '@ocelot-social/ui'
import helpers from '~/storybook/helpers' import helpers from '~/storybook/helpers'
import Vue from 'vue' import Vue from 'vue'
@ -37,11 +38,11 @@ storiesOf('Editor', module)
.addDecorator((storyFn) => { .addDecorator((storyFn) => {
const ctx = storyFn() const ctx = storyFn()
return { return {
components: { ctx }, components: { ctx, OsCard },
template: ` template: `
<base-card style="width: 50%; min-width: 500px; margin: 0 auto;"> <os-card style="width: 50%; min-width: 500px; margin: 0 auto;">
<ctx /> <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" class="group-teaser"
:to="{ name: 'groups-id-slug', params: { id: group.id, slug: group.slug } }" :to="{ name: 'groups-id-slug', params: { id: group.id, slug: group.slug } }"
> >
<base-card <os-card
:class="{ :class="{
'disabled-content': group.disabled, 'disabled-content': group.disabled,
}" }"
@ -74,12 +74,12 @@
</div> </div>
</div> </div>
</footer> </footer>
</base-card> </os-card>
</nuxt-link> </nuxt-link>
</template> </template>
<script> <script>
import { OsIcon } from '@ocelot-social/ui' import { OsCard, OsIcon } from '@ocelot-social/ui'
import { iconRegistry } from '~/utils/iconRegistry' import { iconRegistry } from '~/utils/iconRegistry'
import Category from '~/components/Category' import Category from '~/components/Category'
import GroupContentMenu from '~/components/ContentMenu/GroupContentMenu' import GroupContentMenu from '~/components/ContentMenu/GroupContentMenu'
@ -91,6 +91,7 @@ export default {
components: { components: {
Category, Category,
GroupContentMenu, GroupContentMenu,
OsCard,
OsIcon, OsIcon,
}, },
props: { props: {
@ -130,7 +131,7 @@ export default {
} }
} }
.group-teaser > .base-card { .group-teaser > .os-card {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;

View File

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

View File

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

View File

@ -4,58 +4,60 @@
<p>{{ $t('quotes.african.quote') }}</p> <p>{{ $t('quotes.african.quote') }}</p>
<b>- {{ $t('quotes.african.author') }}</b> <b>- {{ $t('quotes.african.author') }}</b>
</blockquote> </blockquote>
<base-card> <os-card class="--columns">
<template #imageColumn> <aside class="image-column" :aria-label="$t('login.moreInfo', metadata)">
<page-params-link :pageParams="links.ORGANIZATION" :title="$t('login.moreInfo', metadata)"> <page-params-link :pageParams="links.ORGANIZATION" :title="$t('login.moreInfo', metadata)">
<logo logoType="welcome" /> <logo logoType="welcome" />
</page-params-link> </page-params-link>
</template> </aside>
<h2 class="title">{{ $t('login.login') }}</h2> <section class="content-column">
<form :disabled="pending" @submit.prevent="onSubmit"> <h2 class="title">{{ $t('login.login') }}</h2>
<ds-input <form :disabled="pending" @submit.prevent="onSubmit">
v-model="form.email"
:disabled="pending"
:placeholder="$t('login.email')"
type="email"
name="email"
icon="envelope"
/>
<div class="password-wrapper">
<ds-input <ds-input
v-model="form.password" v-model="form.email"
:disabled="pending" :disabled="pending"
:placeholder="$t('login.password')" :placeholder="$t('login.email')"
icon="lock" type="email"
name="password" name="email"
class="password-field" icon="envelope"
ref="password"
:type="showPassword ? 'text' : 'password'"
/> />
<show-password @show-password="toggleShowPassword" :icon="passwordIcon" /> <div class="password-wrapper">
</div> <ds-input
<nuxt-link to="/password-reset/request"> v-model="form.password"
{{ $t('login.forgotPassword') }} :disabled="pending"
</nuxt-link> :placeholder="$t('login.password')"
<os-button icon="lock"
:loading="pending" name="password"
variant="primary" class="password-field"
appearance="filled" ref="password"
full-width :type="showPassword ? 'text' : 'password'"
name="submit" />
type="submit" <show-password @show-password="toggleShowPassword" :icon="passwordIcon" />
> </div>
<template #icon><os-icon :icon="icons.signIn" /></template> <nuxt-link to="/password-reset/request">
{{ $t('login.login') }} {{ $t('login.forgotPassword') }}
</os-button> </nuxt-link>
<p> <os-button
{{ $t('login.no-account') }} :loading="pending"
<nuxt-link to="/registration">{{ $t('login.register') }}</nuxt-link> variant="primary"
</p> appearance="filled"
</form> full-width
<template #topMenu> 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" /> <locale-switch offset="5" />
</template> </aside>
</base-card> </os-card>
</section> </section>
</template> </template>
@ -66,7 +68,7 @@ import PageParamsLink from '~/components/_new/features/PageParamsLink/PageParams
import LocaleSwitch from '~/components/LocaleSwitch/LocaleSwitch' import LocaleSwitch from '~/components/LocaleSwitch/LocaleSwitch'
import Logo from '~/components/Logo/Logo' import Logo from '~/components/Logo/Logo'
import ShowPassword from '../ShowPassword/ShowPassword.vue' 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 { iconRegistry } from '~/utils/iconRegistry'
import { mapGetters, mapMutations } from 'vuex' import { mapGetters, mapMutations } from 'vuex'
@ -75,6 +77,7 @@ export default {
LocaleSwitch, LocaleSwitch,
Logo, Logo,
OsButton, OsButton,
OsCard,
OsIcon, OsIcon,
PageParamsLink, PageParamsLink,
ShowPassword, ShowPassword,
@ -135,7 +138,7 @@ export default {
toggleShowPassword() { toggleShowPassword() {
this.showPassword = !this.showPassword this.showPassword = !this.showPassword
this.$nextTick(() => { this.$nextTick(() => {
this.$refs.password.$el.children[1].children[1].focus() this.$refs.password.$el.querySelector('input').focus()
this.$emit('focus') this.$emit('focus')
}) })
}, },

View File

@ -18,74 +18,66 @@
<ds-grid-item> <ds-grid-item>
<ds-flex class="user-section"> <ds-flex class="user-section">
<ds-flex-item> <ds-flex-item>
<base-card :wide-content="true"> <user-teaser
<client-only> :user="
<user-teaser isGroup(notification.from) ? notification.relatedUser : notification.from.author
:user=" "
isGroup(notification.from) :class="{ 'notification-status': notification.read }"
? notification.relatedUser :date-time="notification.from.createdAt"
: notification.from.author :injected-text="$t(`notifications.reason.${notification.reason}`)"
" :injected-date="true"
:class="{ 'notification-status': notification.read }" :show-popover="showPopover"
:date-time="notification.from.createdAt" />
:injected-text="$t(`notifications.reason.${notification.reason}`)"
:injected-date="true"
:show-popover="showPopover"
/>
</client-only>
</base-card>
</ds-flex-item> </ds-flex-item>
</ds-flex> </ds-flex>
</ds-grid-item> </ds-grid-item>
<ds-grid-item> <ds-grid-item>
<base-card :wide-content="true"> <div class="notification-container">
<div class="notification-container"> <!-- Icon with responsive sizing -->
<!-- Icon with responsive sizing --> <div class="notification-icon">
<div class="notification-icon"> <os-icon
<os-icon v-if="notification.from.post"
v-if="notification.from.post" :icon="icons.comment"
:icon="icons.comment" v-tooltip="{ content: $t('notifications.comment'), placement: 'right' }"
v-tooltip="{ content: $t('notifications.comment'), placement: 'right' }" />
/> <os-icon
<os-icon v-else
v-else :icon="icons.bookmark"
:icon="icons.bookmark" v-tooltip="{ content: $t('notifications.post'), placement: 'right' }"
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> </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-item>
</ds-grid> </ds-grid>
</ds-grid-item> </ds-grid-item>
@ -98,7 +90,6 @@ import { OsIcon } from '@ocelot-social/ui'
import { iconRegistry } from '~/utils/iconRegistry' import { iconRegistry } from '~/utils/iconRegistry'
import UserTeaser from '~/components/UserTeaser/UserTeaser' import UserTeaser from '~/components/UserTeaser/UserTeaser'
import HcEmpty from '~/components/Empty/Empty' import HcEmpty from '~/components/Empty/Empty'
import BaseCard from '../_new/generic/BaseCard/BaseCard.vue'
import mobile from '~/mixins/mobile' import mobile from '~/mixins/mobile'
const maxMobileWidth = 768 // at this point the table breaks down const maxMobileWidth = 768 // at this point the table breaks down
@ -109,7 +100,6 @@ export default {
OsIcon, OsIcon,
UserTeaser, UserTeaser,
HcEmpty, HcEmpty,
BaseCard,
}, },
props: { props: {
notifications: { type: Array, default: () => [] }, notifications: { type: Array, default: () => [] },
@ -181,11 +171,6 @@ export default {
.notification-grid .content-section { .notification-grid .content-section {
flex-wrap: nowrap; flex-wrap: nowrap;
} }
.notification-grid .base-card {
border-radius: 0;
box-shadow: none;
padding: 16px 4px;
}
/* dirty fix to override broken styleguide inline-styles */ /* dirty fix to override broken styleguide inline-styles */
.notification-grid .ds-grid { .notification-grid .ds-grid {
grid-template-columns: 5fr 6fr !important; grid-template-columns: 5fr 6fr !important;
@ -201,9 +186,9 @@ export default {
&:nth-child(odd) { &:nth-child(odd) {
background-color: $color-neutral-90; background-color: $color-neutral-90;
} }
.base-card {
> .ds-grid > div {
padding: 8px 0; padding: 8px 0;
background-color: unset !important;
} }
} }
@media screen and (max-width: 768px) { @media screen and (max-width: 768px) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -62,25 +62,25 @@
<!-- users --> <!-- users -->
<template v-if="activeTab === 'User'"> <template v-if="activeTab === 'User'">
<ds-grid-item v-for="user in activeResources" :key="user.id" :row-span="2"> <ds-grid-item v-for="user in activeResources" :key="user.id" :row-span="2">
<base-card :wideContent="true"> <os-card>
<user-teaser :user="user" /> <user-teaser :user="user" />
</base-card> </os-card>
</ds-grid-item> </ds-grid-item>
</template> </template>
<!-- groups --> <!-- groups -->
<template v-if="activeTab === 'Group'"> <template v-if="activeTab === 'Group'">
<ds-grid-item v-for="group in activeResources" :key="group.id" :row-span="2"> <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 }" /> <group-teaser :group="{ ...group, name: group.groupName }" />
</base-card> </os-card>
</ds-grid-item> </ds-grid-item>
</template> </template>
<!-- hashtags --> <!-- hashtags -->
<template v-if="activeTab === 'Hashtag'"> <template v-if="activeTab === 'Hashtag'">
<ds-grid-item v-for="hashtag in activeResources" :key="hashtag.id" :row-span="2"> <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" /> <hc-hashtag :id="hashtag.id" />
</base-card> </os-card>
</ds-grid-item> </ds-grid-item>
</template> </template>
@ -115,6 +115,7 @@
</template> </template>
<script> <script>
import { OsCard } from '@ocelot-social/ui'
import postListActions from '~/mixins/postListActions' import postListActions from '~/mixins/postListActions'
import { searchPosts, searchUsers, searchGroups, searchHashtags } from '~/graphql/Search.js' import { searchPosts, searchUsers, searchGroups, searchHashtags } from '~/graphql/Search.js'
import HcEmpty from '~/components/Empty/Empty' import HcEmpty from '~/components/Empty/Empty'
@ -130,6 +131,7 @@ import HcHashtag from '~/components/Hashtag/Hashtag'
export default { export default {
name: 'SearchResults', name: 'SearchResults',
components: { components: {
OsCard,
TabNavigation, TabNavigation,
HcEmpty, HcEmpty,
MasonryGrid, 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> <template>
<ds-grid-item class="tab-navigation" :row-span="tabs.length" column-span="fullWidth"> <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"> <ul class="Tabs">
<li <li
v-for="tab in tabs" v-for="tab in tabs"
@ -24,15 +24,17 @@
</a> </a>
</li> </li>
</ul> </ul>
</base-card> </os-card>
</ds-grid-item> </ds-grid-item>
</template> </template>
<script> <script>
import { OsCard } from '@ocelot-social/ui'
import HcCountTo from '~/components/CountTo.vue' import HcCountTo from '~/components/CountTo.vue'
export default { export default {
components: { components: {
OsCard,
HcCountTo, HcCountTo,
}, },
props: { props: {
@ -42,6 +44,7 @@ export default {
}, },
activeTab: { activeTab: {
type: String, type: String,
default: null,
}, },
}, },
methods: { methods: {
@ -93,8 +96,8 @@ export default {
top: 53px; top: 53px;
z-index: 2; z-index: 2;
} }
.ds-tab-nav.base-card { .ds-tab-nav.os-card {
padding: 0; padding: 0 !important;
.ds-tab-nav-item { .ds-tab-nav-item {
&.ds-tab-nav-item-active { &.ds-tab-nav-item-active {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="admin-users"> <div class="admin-users">
<base-card> <os-card>
<h2 class="title">{{ $t('admin.users.name') }}</h2> <h2 class="title">{{ $t('admin.users.name') }}</h2>
<ds-form v-model="form" @submit="submit"> <ds-form v-model="form" @submit="submit">
<ds-flex gutter="small"> <ds-flex gutter="small">
@ -25,8 +25,8 @@
</ds-flex-item> </ds-flex-item>
</ds-flex> </ds-flex>
</ds-form> </ds-form>
</base-card> </os-card>
<base-card v-if="User && User.length"> <os-card v-if="User && User.length">
<ds-table :data="User" :fields="fields" condensed> <ds-table :data="User" :fields="fields" condensed>
<template #index="scope">{{ scope.row.index + 1 }}.</template> <template #index="scope">{{ scope.row.index + 1 }}.</template>
<template #name="scope"> <template #name="scope">
@ -89,15 +89,15 @@
</template> </template>
</ds-table> </ds-table>
<pagination-buttons :hasNext="hasNext" :hasPrevious="hasPrevious" @next="next" @back="back" /> <pagination-buttons :hasNext="hasNext" :hasPrevious="hasPrevious" @next="next" @back="back" />
</base-card> </os-card>
<base-card v-else> <os-card v-else>
<ds-placeholder>{{ $t('admin.users.empty') }}</ds-placeholder> <ds-placeholder>{{ $t('admin.users.empty') }}</ds-placeholder>
</base-card> </os-card>
</div> </div>
</template> </template>
<script> <script>
import { OsButton, OsIcon } from '@ocelot-social/ui' import { OsButton, OsCard, OsIcon } from '@ocelot-social/ui'
import { iconRegistry } from '~/utils/iconRegistry' import { iconRegistry } from '~/utils/iconRegistry'
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import { isEmail } from 'validator' import { isEmail } from 'validator'
@ -108,6 +108,7 @@ import { FetchAllRoles, updateUserRole } from '~/graphql/admin/Roles'
export default { export default {
components: { components: {
OsButton, OsButton,
OsCard,
OsIcon, OsIcon,
PaginationButtons, PaginationButtons,
}, },
@ -241,7 +242,7 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
.admin-users > .base-card:first-child { .admin-users > .os-card:first-child {
margin-bottom: $space-small; 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" class="ds-flex-item"
style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;" style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;"
> >
<article <div
class="base-card" 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;" style="position: relative; height: auto; overflow: visible;"
> >
<div <div
@ -566,9 +566,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
</span> </span>
</div> </div>
</div> </div>
</div>
<!---->
</article>
<div <div
class="ds-space" class="ds-space"
@ -584,8 +582,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
</h3> </h3>
<article <div
class="profile-list base-card" 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 <h5
class="title spacer-x-small" class="title spacer-x-small"
@ -927,9 +925,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
<!----> <!---->
<!----> <!---->
</div>
<!---->
</article>
</div> </div>
<div <div
@ -940,8 +936,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
class="ds-space" class="ds-space"
style="margin-bottom: 32px;" style="margin-bottom: 32px;"
> >
<article <div
class="group-description base-card" 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 <div
class="content hyphenate-text" class="content hyphenate-text"
@ -979,9 +975,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
</span> </span>
</button> </button>
</div>
<!---->
</article>
</div> </div>
<div <div
@ -1159,8 +1153,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
class="ds-flex-item" class="ds-flex-item"
style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;" style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;"
> >
<article <div
class="base-card" 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;" style="position: relative; height: auto; overflow: visible;"
> >
<div <div
@ -1465,9 +1459,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
</span> </span>
</div> </div>
</div> </div>
</div>
<!---->
</article>
<div <div
class="ds-space" class="ds-space"
@ -1483,17 +1475,15 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
</h3> </h3>
<article <div
class="profile-list base-card" 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 <p
class="nobody-message" class="nobody-message"
> >
group.membersListTitleNotAllowedSeeingGroupMembers group.membersListTitleNotAllowedSeeingGroupMembers
</p> </p>
</div>
<!---->
</article>
</div> </div>
<div <div
@ -1504,8 +1494,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
class="ds-space" class="ds-space"
style="margin-bottom: 32px;" style="margin-bottom: 32px;"
> >
<article <div
class="group-description base-card" 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 <div
class="content hyphenate-text" class="content hyphenate-text"
@ -1543,9 +1533,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
</span> </span>
</button> </button>
</div>
<!---->
</article>
</div> </div>
<!----> <!---->
@ -1688,8 +1676,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
class="ds-flex-item" class="ds-flex-item"
style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;" style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;"
> >
<article <div
class="base-card" 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;" style="position: relative; height: auto; overflow: visible;"
> >
<div <div
@ -2014,9 +2002,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
</span> </span>
</div> </div>
</div> </div>
</div>
<!---->
</article>
<div <div
class="ds-space" class="ds-space"
@ -2032,17 +2018,15 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
</h3> </h3>
<article <div
class="profile-list base-card" 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 <p
class="nobody-message" class="nobody-message"
> >
group.membersListTitleNotAllowedSeeingGroupMembers group.membersListTitleNotAllowedSeeingGroupMembers
</p> </p>
</div>
<!---->
</article>
</div> </div>
<div <div
@ -2053,8 +2037,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
class="ds-space" class="ds-space"
style="margin-bottom: 32px;" style="margin-bottom: 32px;"
> >
<article <div
class="group-description base-card" 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 <div
class="content hyphenate-text" class="content hyphenate-text"
@ -2092,9 +2076,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
</span> </span>
</button> </button>
</div>
<!---->
</article>
</div> </div>
<!----> <!---->
@ -2237,8 +2219,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
class="ds-flex-item" class="ds-flex-item"
style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;" style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;"
> >
<article <div
class="base-card" 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;" style="position: relative; height: auto; overflow: visible;"
> >
<div <div
@ -2696,9 +2678,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
</span> </span>
</div> </div>
</div> </div>
</div>
<!---->
</article>
<div <div
class="ds-space" class="ds-space"
@ -2714,8 +2694,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
</h3> </h3>
<article <div
class="profile-list base-card" 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 <h5
class="title spacer-x-small" class="title spacer-x-small"
@ -3057,9 +3037,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
<!----> <!---->
<!----> <!---->
</div>
<!---->
</article>
</div> </div>
<div <div
@ -3070,8 +3048,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
class="ds-space" class="ds-space"
style="margin-bottom: 32px;" style="margin-bottom: 32px;"
> >
<article <div
class="group-description base-card" 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 <div
class="content hyphenate-text" class="content hyphenate-text"
@ -3109,9 +3087,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a close
</span> </span>
</button> </button>
</div>
<!---->
</article>
</div> </div>
<div <div
@ -3289,8 +3265,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
class="ds-flex-item" class="ds-flex-item"
style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;" style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;"
> >
<article <div
class="base-card" 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;" style="position: relative; height: auto; overflow: visible;"
> >
<div <div
@ -3762,9 +3738,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
</div> </div>
<!----> <!---->
</div>
<!---->
</article>
<div <div
class="ds-space" class="ds-space"
@ -3780,8 +3754,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
</h3> </h3>
<article <div
class="profile-list base-card" 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 <h5
class="title spacer-x-small" class="title spacer-x-small"
@ -4123,9 +4097,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
<!----> <!---->
<!----> <!---->
</div>
<!---->
</article>
</div> </div>
<div <div
@ -4136,8 +4108,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
class="ds-space" class="ds-space"
style="margin-bottom: 32px;" style="margin-bottom: 32px;"
> >
<article <div
class="group-description base-card" 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 <div
class="content hyphenate-text" class="content hyphenate-text"
@ -4180,9 +4152,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
</span> </span>
</button> </button>
</div>
<!---->
</article>
</div> </div>
<div <div
@ -4360,8 +4330,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
class="ds-flex-item" class="ds-flex-item"
style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;" style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;"
> >
<article <div
class="base-card" 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;" style="position: relative; height: auto; overflow: visible;"
> >
<div <div
@ -4629,9 +4599,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
</div> </div>
<!----> <!---->
</div>
<!---->
</article>
<div <div
class="ds-space" class="ds-space"
@ -4647,8 +4615,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
</h3> </h3>
<article <div
class="profile-list base-card" 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 <h5
class="title spacer-x-small" class="title spacer-x-small"
@ -4990,9 +4958,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
<!----> <!---->
<!----> <!---->
</div>
<!---->
</article>
</div> </div>
<div <div
@ -5003,8 +4969,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
class="ds-space" class="ds-space"
style="margin-bottom: 32px;" style="margin-bottom: 32px;"
> >
<article <div
class="group-description base-card" 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 <div
class="content hyphenate-text" class="content hyphenate-text"
@ -5047,9 +5013,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
</span> </span>
</button> </button>
</div>
<!---->
</article>
</div> </div>
<!----> <!---->
@ -5192,8 +5156,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
class="ds-flex-item" class="ds-flex-item"
style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;" style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;"
> >
<article <div
class="base-card" 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;" style="position: relative; height: auto; overflow: visible;"
> >
<div <div
@ -5481,9 +5445,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
</div> </div>
<!----> <!---->
</div>
<!---->
</article>
<div <div
class="ds-space" class="ds-space"
@ -5499,8 +5461,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
</h3> </h3>
<article <div
class="profile-list base-card" 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 <h5
class="title spacer-x-small" class="title spacer-x-small"
@ -5842,9 +5804,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
<!----> <!---->
<!----> <!---->
</div>
<!---->
</article>
</div> </div>
<div <div
@ -5855,8 +5815,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
class="ds-space" class="ds-space"
style="margin-bottom: 32px;" style="margin-bottom: 32px;"
> >
<article <div
class="group-description base-card" 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 <div
class="content hyphenate-text" class="content hyphenate-text"
@ -5899,9 +5859,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
</span> </span>
</button> </button>
</div>
<!---->
</article>
</div> </div>
<!----> <!---->
@ -6044,8 +6002,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
class="ds-flex-item" class="ds-flex-item"
style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;" style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;"
> >
<article <div
class="base-card" 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;" style="position: relative; height: auto; overflow: visible;"
> >
<div <div
@ -6428,9 +6386,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
</div> </div>
<!----> <!---->
</div>
<!---->
</article>
<div <div
class="ds-space" class="ds-space"
@ -6446,8 +6402,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
</h3> </h3>
<article <div
class="profile-list base-card" 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 <h5
class="title spacer-x-small" class="title spacer-x-small"
@ -6789,9 +6745,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
<!----> <!---->
<!----> <!---->
</div>
<!---->
</article>
</div> </div>
<div <div
@ -6802,8 +6756,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
class="ds-space" class="ds-space"
style="margin-bottom: 32px;" style="margin-bottom: 32px;"
> >
<article <div
class="group-description base-card" 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 <div
class="content hyphenate-text" class="content hyphenate-text"
@ -6846,9 +6800,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a curre
</span> </span>
</button> </button>
</div>
<!---->
</article>
</div> </div>
<div <div
@ -7026,8 +6978,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
class="ds-flex-item" class="ds-flex-item"
style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;" style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;"
> >
<article <div
class="base-card" 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;" style="position: relative; height: auto; overflow: visible;"
> >
<div <div
@ -7574,9 +7526,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
</span> </span>
</div> </div>
</div> </div>
</div>
<!---->
</article>
<div <div
class="ds-space" class="ds-space"
@ -7592,8 +7542,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
</h3> </h3>
<article <div
class="profile-list base-card" 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 <h5
class="title spacer-x-small" class="title spacer-x-small"
@ -7935,9 +7885,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
<!----> <!---->
<!----> <!---->
</div>
<!---->
</article>
</div> </div>
<div <div
@ -7948,8 +7896,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
class="ds-space" class="ds-space"
style="margin-bottom: 32px;" style="margin-bottom: 32px;"
> >
<article <div
class="group-description base-card" 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 <div
class="content hyphenate-text" class="content hyphenate-text"
@ -8004,9 +7952,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
</span> </span>
</button> </button>
</div>
<!---->
</article>
</div> </div>
<div <div
@ -8196,8 +8142,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
class="ds-flex-item" class="ds-flex-item"
style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;" style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;"
> >
<article <div
class="base-card" 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;" style="position: relative; height: auto; overflow: visible;"
> >
<div <div
@ -8655,9 +8601,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
</span> </span>
</div> </div>
</div> </div>
</div>
<!---->
</article>
<div <div
class="ds-space" class="ds-space"
@ -8673,8 +8617,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
</h3> </h3>
<article <div
class="profile-list base-card" 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 <h5
class="title spacer-x-small" class="title spacer-x-small"
@ -9016,9 +8960,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
<!----> <!---->
<!----> <!---->
</div>
<!---->
</article>
</div> </div>
<div <div
@ -9029,8 +8971,8 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
class="ds-space" class="ds-space"
style="margin-bottom: 32px;" style="margin-bottom: 32px;"
> >
<article <div
class="group-description base-card" 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 <div
class="content hyphenate-text" class="content hyphenate-text"
@ -9085,9 +9027,7 @@ exports[`GroupProfileSlug given a puplic group "yoga-practice" given a hidde
</span> </span>
</button> </button>
</div>
<!---->
</article>
</div> </div>
<div <div

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,7 +15,7 @@
<ds-space margin="large" /> <ds-space margin="large" />
<ds-flex gutter="small"> <ds-flex gutter="small">
<ds-flex-item :width="{ base: '100%', sm: 2, md: 2, lg: 1 }"> <ds-flex-item :width="{ base: '100%', sm: 2, md: 2, lg: 1 }">
<base-card <os-card
v-if="post && ready" v-if="post && ready"
:lang="post.language" :lang="post.language"
:class="{ :class="{
@ -170,7 +170,7 @@
</hc-empty> </hc-empty>
</ds-placeholder> </ds-placeholder>
</ds-section> </ds-section>
</base-card> </os-card>
</ds-flex-item> </ds-flex-item>
<ds-flex-item :width="{ base: '200px' }"> <ds-flex-item :width="{ base: '200px' }">
<ds-menu :routes="routes" class="post-side-navigation" /> <ds-menu :routes="routes" class="post-side-navigation" />
@ -181,7 +181,7 @@
</template> </template>
<script> <script>
import { OsButton, OsIcon } from '@ocelot-social/ui' import { OsButton, OsCard, OsIcon } from '@ocelot-social/ui'
import { iconRegistry } from '~/utils/iconRegistry' import { iconRegistry } from '~/utils/iconRegistry'
import ContentViewer from '~/components/Editor/ContentViewer' import ContentViewer from '~/components/Editor/ContentViewer'
import CommentForm from '~/components/CommentForm/CommentForm' import CommentForm from '~/components/CommentForm/CommentForm'
@ -218,6 +218,7 @@ export default {
mode: 'out-in', mode: 'out-in',
}, },
components: { components: {
OsCard,
OsButton, OsButton,
OsIcon, OsIcon,
CommentForm, CommentForm,
@ -426,14 +427,14 @@ export default {
z-index: 2; z-index: 2;
} }
.post-page { .post-page {
> .hero-image { > .os-card__hero-image {
position: relative; position: relative;
/* The padding top makes sure the correct height is set (according to the /* The padding top makes sure the correct height is set (according to the
hero image aspect ratio) before the hero image loads so hero image aspect ratio) before the hero image loads so
the autoscroll works correctly when following a comment link. 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 /* Letting the image fill the container, since the container
is the one determining height is the one determining height
*/ */
@ -446,13 +447,13 @@ export default {
} }
} }
> .menu { .menu {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
} }
&.--blur-image > .hero-image > .image { &.--blur-image > .os-card__hero-image > .image {
filter: blur($blur-radius); 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" class="ds-flex-item"
style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;" style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;"
> >
<article <div
class="base-card" 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;" style="position: relative; height: auto; overflow: visible;"
> >
<div <div
@ -435,9 +435,7 @@ exports[`ProfileSlug given an authenticated user given another profile user and
</div> </div>
<!----> <!---->
</div>
<!---->
</article>
<div <div
class="ds-space" class="ds-space"
@ -483,8 +481,8 @@ exports[`ProfileSlug given an authenticated user given another profile user and
class="tab-navigation" class="tab-navigation"
style="grid-row-end: span 3; grid-column-start: 1; grid-column-end: -1;" style="grid-row-end: span 3; grid-column-start: 1; grid-column-end: -1;"
> >
<article <div
class="ds-tab-nav base-card" 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 <ul
class="Tabs" class="Tabs"
@ -628,9 +626,7 @@ exports[`ProfileSlug given an authenticated user given another profile user and
</a> </a>
</li> </li>
</ul> </ul>
</div>
<!---->
</article>
</div> </div>
<!----> <!---->
@ -765,8 +761,8 @@ exports[`ProfileSlug given an authenticated user given another profile user and
class="ds-flex-item" class="ds-flex-item"
style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;" style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;"
> >
<article <div
class="base-card" 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;" style="position: relative; height: auto; overflow: visible;"
> >
<div <div
@ -1221,9 +1217,7 @@ exports[`ProfileSlug given an authenticated user given another profile user and
</div> </div>
<!----> <!---->
</div>
<!---->
</article>
<div <div
class="ds-space" class="ds-space"
@ -1269,8 +1263,8 @@ exports[`ProfileSlug given an authenticated user given another profile user and
class="tab-navigation" class="tab-navigation"
style="grid-row-end: span 3; grid-column-start: 1; grid-column-end: -1;" style="grid-row-end: span 3; grid-column-start: 1; grid-column-end: -1;"
> >
<article <div
class="ds-tab-nav base-card" 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 <ul
class="Tabs" class="Tabs"
@ -1414,9 +1408,7 @@ exports[`ProfileSlug given an authenticated user given another profile user and
</a> </a>
</li> </li>
</ul> </ul>
</div>
<!---->
</article>
</div> </div>
<!----> <!---->
@ -1551,8 +1543,8 @@ exports[`ProfileSlug given an authenticated user given the logged in user as pro
class="ds-flex-item" class="ds-flex-item"
style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;" style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;"
> >
<article <div
class="base-card" 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;" style="position: relative; height: auto; overflow: visible;"
> >
<div <div
@ -1830,9 +1822,7 @@ exports[`ProfileSlug given an authenticated user given the logged in user as pro
<!----> <!---->
<!----> <!---->
</div>
<!---->
</article>
<div <div
class="ds-space" class="ds-space"
@ -1878,8 +1868,8 @@ exports[`ProfileSlug given an authenticated user given the logged in user as pro
class="tab-navigation" class="tab-navigation"
style="grid-row-end: span 3; grid-column-start: 1; grid-column-end: -1;" style="grid-row-end: span 3; grid-column-start: 1; grid-column-end: -1;"
> >
<article <div
class="ds-tab-nav base-card" 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 <ul
class="Tabs" class="Tabs"
@ -2023,9 +2013,7 @@ exports[`ProfileSlug given an authenticated user given the logged in user as pro
</a> </a>
</li> </li>
</ul> </ul>
</div>
<!---->
</article>
</div> </div>
<div <div
@ -2198,8 +2186,8 @@ exports[`ProfileSlug given an authenticated user given the logged in user as pro
class="ds-flex-item" class="ds-flex-item"
style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;" style="flex-basis: 100%; width: 100%; padding-left: 12px; padding-right: 12px; margin-bottom: 24px;"
> >
<article <div
class="base-card" 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;" style="position: relative; height: auto; overflow: visible;"
> >
<div <div
@ -2519,9 +2507,7 @@ exports[`ProfileSlug given an authenticated user given the logged in user as pro
<!----> <!---->
<!----> <!---->
</div>
<!---->
</article>
<div <div
class="ds-space" class="ds-space"
@ -2567,8 +2553,8 @@ exports[`ProfileSlug given an authenticated user given the logged in user as pro
class="tab-navigation" class="tab-navigation"
style="grid-row-end: span 3; grid-column-start: 1; grid-column-end: -1;" style="grid-row-end: span 3; grid-column-start: 1; grid-column-end: -1;"
> >
<article <div
class="ds-tab-nav base-card" 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 <ul
class="Tabs" class="Tabs"
@ -2712,9 +2698,7 @@ exports[`ProfileSlug given an authenticated user given the logged in user as pro
</a> </a>
</li> </li>
</ul> </ul>
</div>
<!---->
</article>
</div> </div>
<div <div

View File

@ -3,7 +3,7 @@
<ds-space /> <ds-space />
<ds-flex v-if="user" :width="{ base: '100%' }" gutter="base"> <ds-flex v-if="user" :width="{ base: '100%' }" gutter="base">
<ds-flex-item :width="{ base: '100%', sm: 2, md: 2, lg: 1 }"> <ds-flex-item :width="{ base: '100%', sm: 2, md: 2, lg: 1 }">
<base-card <os-card
:class="{ 'disabled-content': user.disabled }" :class="{ 'disabled-content': user.disabled }"
style="position: relative; height: auto; overflow: visible" style="position: relative; height: auto; overflow: visible"
> >
@ -117,7 +117,7 @@
</ds-text> </ds-text>
</ds-space> </ds-space>
</template> </template>
</base-card> </os-card>
<ds-space /> <ds-space />
<ds-heading tag="h3" soft style="text-align: center; margin-bottom: 10px"> <ds-heading tag="h3" soft style="text-align: center; margin-bottom: 10px">
{{ $t('profile.network.title') }} {{ $t('profile.network.title') }}
@ -212,7 +212,7 @@
</template> </template>
<script> <script>
import { OsButton, OsIcon, OsSpinner } from '@ocelot-social/ui' import { OsButton, OsCard, OsIcon, OsSpinner } from '@ocelot-social/ui'
import { iconRegistry } from '~/utils/iconRegistry' import { iconRegistry } from '~/utils/iconRegistry'
import uniqBy from 'lodash/uniqBy' import uniqBy from 'lodash/uniqBy'
import { mapGetters, mapMutations } from 'vuex' import { mapGetters, mapMutations } from 'vuex'
@ -247,6 +247,7 @@ const tabToFilterMapping = ({ tab, id }) => {
export default { export default {
components: { components: {
OsCard,
OsButton, OsButton,
OsIcon, OsIcon,
OsSpinner, 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`] = ` exports[`badge settings with badges more badges available selecting an empty slot shows list with available badges 1`] = `
<div> <div>
<article <div
class="base-card" class="os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6"
> >
<h2 <h2
class="title" class="title"
@ -129,16 +129,14 @@ exports[`badge settings with badges more badges available selecting an empty slo
</div> </div>
</div> </div>
</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`] = ` exports[`badge settings with badges no more badges available selecting an empty slot shows no more badges available message 1`] = `
<div> <div>
<article <div
class="base-card" class="os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6"
> >
<h2 <h2
class="title" class="title"
@ -211,16 +209,14 @@ exports[`badge settings with badges no more badges available selecting an empty
<!----> <!---->
</div> </div>
</div>
<!---->
</article>
</div> </div>
`; `;
exports[`badge settings with badges renders 1`] = ` exports[`badge settings with badges renders 1`] = `
<div> <div>
<article <div
class="base-card" class="os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6"
> >
<h2 <h2
class="title" class="title"
@ -295,92 +291,14 @@ exports[`badge settings with badges renders 1`] = `
<!----> <!---->
</div> </div>
</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`] = ` exports[`badge settings without badges renders 1`] = `
<div> <div>
<article <div
class="base-card" class="os-card relative rounded-[5px] break-words bg-white shadow-[0px_12px_26px_-4px_rgba(0,0,0,0.1)] p-6"
> >
<h2 <h2
class="title" class="title"
@ -422,8 +340,6 @@ exports[`badge settings without badges renders 1`] = `
<!----> <!---->
</div> </div>
</div>
<!---->
</article>
</div> </div>
`; `;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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