mirror of
https://github.com/IT4Change/Ocelot-Social.git
synced 2026-04-03 08:05:33 +00:00
feat(package/ui): os-menu (#9431)
This commit is contained in:
parent
cadd0d0286
commit
75c1232860
@ -1,7 +1,7 @@
|
||||
import { defineStep } from '@badeball/cypress-cucumber-preprocessor'
|
||||
|
||||
defineStep('there is no button to pin a post', () => {
|
||||
cy.get('a.ds-menu-item-link')
|
||||
cy.get('a.os-menu-item-link')
|
||||
.should('contain', 'Report Post') // sanity check
|
||||
.should('not.contain', 'Pin post')
|
||||
})
|
||||
|
||||
@ -5,7 +5,7 @@ defineStep('I click on "Report Post" from the content menu of the post', () => {
|
||||
.find('[data-test="content-menu-button"]')
|
||||
.click()
|
||||
|
||||
cy.get('.popover .ds-menu-item-link')
|
||||
cy.get('.popover .os-menu-item-link')
|
||||
.contains('Report Post')
|
||||
.click()
|
||||
})
|
||||
|
||||
@ -2,6 +2,6 @@ import { defineStep } from '@badeball/cypress-cucumber-preprocessor'
|
||||
|
||||
defineStep('I {string} see {string} from the content menu in the user info box', (condition, link) => {
|
||||
cy.get('.user-content-menu [data-test="content-menu-button"]').click()
|
||||
cy.get('.popover .ds-menu-item-link')
|
||||
cy.get('.popover .os-menu-item-link')
|
||||
.should(condition === 'should' ? 'contain' : 'not.contain', link)
|
||||
})
|
||||
|
||||
@ -10,7 +10,7 @@ defineStep('I click on {string}', element => {
|
||||
'comment button': 'button[type=submit]',
|
||||
'reply button': '.reply-button',
|
||||
'security menu': 'a[href="/settings/security"]',
|
||||
'pin post': '.ds-menu-item:first-child',
|
||||
'pin post': '.os-menu-item:first-child',
|
||||
'Moderation': 'a[href="/moderation"]',
|
||||
}
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ import { defineStep } from '@badeball/cypress-cucumber-preprocessor'
|
||||
defineStep('I click on {string} from the content menu in the user info box',
|
||||
button => {
|
||||
cy.get('.user-content-menu [data-test="content-menu-button"]').click()
|
||||
cy.get('.popover .ds-menu-item-link')
|
||||
cy.get('.popover .os-menu-item-link')
|
||||
.contains(button)
|
||||
.click({
|
||||
force: true
|
||||
|
||||
@ -6,5 +6,5 @@ defineStep('I navigate to my {string} settings page', settingsPage => {
|
||||
.find('a[href]')
|
||||
.contains('Settings')
|
||||
.click()
|
||||
cy.contains('.ds-menu-item-link', settingsPage).click()
|
||||
cy.contains('.os-menu-item-link', settingsPage).click()
|
||||
})
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
// eslint-disable-next-line import-x/no-unassigned-import
|
||||
import '@fontsource-variable/inter'
|
||||
|
||||
// eslint-disable-next-line import-x/no-relative-parent-imports
|
||||
import '../src/styles/index.css'
|
||||
// eslint-disable-next-line import-x/no-unassigned-import
|
||||
import './storybook.css'
|
||||
|
||||
|
||||
@ -15,7 +15,7 @@ Phase 4: Tier 1 ██████████ 100% (OsButton, OsIcon, Os
|
||||
Phase 4: Tier A → HTML ██████████ 100% (10 ds-* Wrapper → Plain HTML) ✅
|
||||
Phase 4: Tier B ██████████ 100% (ds-chip→OsBadge✅, ds-tag→OsBadge✅, ds-grid✅, ds-number→OsNumber✅, ds-radio→HTML✅)
|
||||
Phase 4: Tier B ██████████ 100% (Chip→OsBadge, Tag→OsBadge, Grid→HTML, Number→OsNumber, Radio→HTML, Table→HTML) ✅
|
||||
Phase 4: Tier 2+ ████████░░ 70% (OsModal✅, ds-form entkoppelt✅, ds-input→OcelotInput✅, ds-select→OcelotSelect✅) | Rest ausstehend (OsMenu, OsDropdown, OsAvatar)
|
||||
Phase 4: Tier 2+ ██████████ 100% (OsModal✅, ds-form✅, OcelotInput✅, OcelotSelect✅, OsMenu/OsMenuItem✅) | 0 ds-* Tags verbleibend ✅
|
||||
```
|
||||
|
||||
### Statistiken
|
||||
@ -33,7 +33,7 @@ Phase 4: Tier 2+ ████████░░ 70% (OsModal✅, ds-form
|
||||
| ✅ ds-input → OcelotInput | Input (23 Dateien → OcelotInput Webapp-Komponente, lokale Imports, formValidation-kompatibel) |
|
||||
| ✅ ds-form entkoppelt | Form-Validierung → formValidation Mixin (async-validator), vuelidate entfernt |
|
||||
| ✅ ds-select → OcelotSelect | Select (3 Dateien → OcelotSelect Webapp-Komponente, lokale Imports, click-outside inline) |
|
||||
| ⬜ → UI-Library | Menu, MenuItem (2) — Tier 3 |
|
||||
| ✅ → OsMenu/OsMenuItem | Menu, MenuItem (17 Nutzungen → packages/ui, dropdown Prop, eigene CSS) |
|
||||
| ⬜ Nicht in Webapp | Code, CopyField, FormItem, InputError, InputLabel, Page, PageTitle, Logo, Avatar, TableCol, TableHeadCol (11) |
|
||||
|
||||
### OsButton Migration (Phase 3) ✅
|
||||
@ -103,8 +103,8 @@ Phase 4: Tier 2+ ████████░░ 70% (OsModal✅, ds-form
|
||||
| 32 | List | ✅ → HTML | Tier A: `<ul class="ds-list">` |
|
||||
| 33 | ListItem | ✅ → HTML | Tier A: `<li class="ds-list-item">` |
|
||||
| 34 | Logo | ⬜ Nicht genutzt | Webapp nutzt eigenes Logo |
|
||||
| 35 | Menu | ⬜ Tier 3 | 11 Dateien → OsMenu |
|
||||
| 36 | MenuItem | ⬜ Tier 3 | 6 Dateien → OsMenuItem |
|
||||
| 35 | Menu | ✅ UI-Library | 11 Dateien → OsMenu (packages/ui, dropdown Prop) |
|
||||
| 36 | MenuItem | ✅ UI-Library | 6 Dateien → OsMenuItem (packages/ui) |
|
||||
|
||||
### Typography
|
||||
| # | Komponente | Status | Notizen |
|
||||
@ -372,6 +372,8 @@ Phase 4: Tier 2+ ████████░░ 70% (OsModal✅, ds-form
|
||||
- Modal → OsModal ✅
|
||||
- Input → OcelotInput (Webapp-Komponente) ✅ — langfristig → OsInput in packages/ui
|
||||
- Select → OcelotSelect (Webapp-Komponente) ✅ — langfristig → OsSelect in packages/ui
|
||||
- Menu → OsMenu (packages/ui) ✅
|
||||
- MenuItem → OsMenuItem (packages/ui) ✅
|
||||
- Avatar → OsAvatar (falls benötigt)
|
||||
|
||||
### Layout & Typography — → Plain HTML ✅ (Tier A)
|
||||
@ -414,6 +416,7 @@ Phase 4: Tier 2+ ████████░░ 70% (OsModal✅, ds-form
|
||||
| 2026-02-19 | Claude | **Katalog konsolidiert** | Styleguide- und Webapp-Tabellen aktualisiert, veraltete Status korrigiert |
|
||||
| 2026-03-23 | Claude | **ds-input → OcelotInput** | 23 Dateien migriert, Webapp-Komponente mit lokalen Imports (tree-shakeable), FormItem/Label/Error vereint |
|
||||
| 2026-03-23 | Claude | **ds-select → OcelotSelect** | 3 Dateien migriert, Webapp-Komponente, DsSelect+inputMixin+multiinputMixin vereint, Form-Kopplung entfernt, DsChip→OsBadge, DsSpinner→OsSpinner, click-outside inline |
|
||||
| 2026-03-23 | Claude | **ds-menu → OsMenu/OsMenuItem** | packages/ui Komponenten mit h() Render, vue-demi, provide/inject, dropdown Prop, eigene CSS in index.css. 17 Nutzungen in 11 Dateien migriert. Vite-Build: ui.css in style.css integriert. Action-Menüs nutzen `<a>` statt router-link. 0 ds-* Tags verbleibend in Webapp. |
|
||||
|
||||
---
|
||||
|
||||
@ -451,12 +454,12 @@ Phase 4: Tier 2+ ████████░░ 70% (OsModal✅, ds-form
|
||||
19. [x] OsModal (h() Render, Focus-Trap, Scroll-Lock, A11y; ConfirmModal + ReportModal nutzen OsModal; DeleteUserModal/DisableModal/ReleaseModal gelöscht) ✅
|
||||
20. [x] ds-form → formValidation Mixin (async-validator), 18 Dateien migriert, vuelidate entfernt ✅
|
||||
21. [x] ds-input → OcelotInput (23 Dateien, Webapp-Komponente mit lokalen Imports, FormItem/Label/Error vereint, formValidation-kompatibel) ✅
|
||||
22. [ ] OsMenu / OsMenuItem (17 Dateien)
|
||||
22. [x] OsMenu / OsMenuItem (packages/ui, 17 Nutzungen in 11 Dateien, dropdown Prop, eigene CSS) ✅
|
||||
23. [x] ds-select → OcelotSelect (3 Dateien, Webapp-Komponente, click-outside inline, DsChip→OsBadge) ✅
|
||||
|
||||
---
|
||||
|
||||
**✅ Phase 0-3 abgeschlossen. Phase 4: Tier 1 + Tier A ✅, Tier B ✅ (Chip→OsBadge, Tag→OsBadge, Grid→HTML, Number→OsNumber, Radio→HTML, Table→HTML), Tier 2: OsModal ✅, ds-form entkoppelt ✅, ds-input → OcelotInput ✅, ds-select → OcelotSelect ✅, Rest ausstehend (OsMenu).**
|
||||
**✅ Phase 0-3 abgeschlossen. Phase 4: Alle ds-* Komponenten migriert! Tier 1 ✅, Tier A ✅, Tier B ✅, Tier 2: OsModal ✅, OcelotInput ✅, OcelotSelect ✅, Tier 3: OsMenu/OsMenuItem ✅. 0 ds-* Tags in Webapp.**
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -81,10 +81,10 @@ Phase 0: ██████████ 100% (6/6 Aufgaben) ✅
|
||||
Phase 1: ██████████ 100% (6/6 Aufgaben) ✅
|
||||
Phase 2: ██████████ 100% (26/26 Aufgaben) ✅
|
||||
Phase 3: ██████████ 100% (24/24 Aufgaben) ✅ - Webapp-Integration komplett
|
||||
Phase 4: ████████░░ 78% (21/27 Aufgaben) - Tier 1 ✅, Tier A ✅, Infra ✅, OsBadge ✅, ds-grid ✅, ds-table→HTML ✅, OsNumber ✅, OsModal ✅, ds-radio→HTML ✅ | Tier B ✅, OcelotInput ✅, OcelotSelect ✅, Tier 2-3 Rest ausstehend
|
||||
Phase 4: █████████░ 85% (23/27 Aufgaben) - Tier 1 ✅, Tier A ✅, Infra ✅, OsBadge ✅, ds-grid ✅, ds-table→HTML ✅, OsNumber ✅, OsModal ✅, ds-radio→HTML ✅ | Tier B ✅, OcelotInput ✅, OcelotSelect ✅, OsMenu ✅ | 0 ds-* Tags verbleibend
|
||||
Phase 5: ░░░░░░░░░░ 0% (0/7 Aufgaben)
|
||||
───────────────────────────────────────
|
||||
Gesamt: ████████░░ 86% (83/96 Aufgaben)
|
||||
Gesamt: █████████░ 89% (85/96 Aufgaben)
|
||||
```
|
||||
|
||||
### Katalogisierung (Details in KATALOG.md)
|
||||
@ -288,8 +288,8 @@ ds-chip + ds-tag → OsBadge (UI-Library): ✅
|
||||
- [x] Test-Fix: Empty.spec.js `attributes().margin` → `classes().toContain('ds-my-xxx-small')`
|
||||
- [x] 0 Tier-A `ds-*` Komponenten-Tags verbleibend
|
||||
|
||||
**Verbleibende ds-* Komponenten (6 Typen):**
|
||||
- Tier C (→ UI-Library): ds-modal (7→✅ OsModal), ds-input (23→✅ OcelotInput), ds-select (3→✅ OcelotSelect), ds-menu/ds-menu-item (17)
|
||||
**Verbleibende ds-* Komponenten: ✅ ALLE MIGRIERT (0 ds-* Tags in Webapp)**
|
||||
- ✅ ds-modal (7→OsModal), ds-input (23→OcelotInput), ds-select (3→OcelotSelect), ds-menu/ds-menu-item (17→OsMenu/OsMenuItem)
|
||||
- ✅ ds-form (18 Dateien) → formValidation Mixin (async-validator), vuelidate entfernt
|
||||
|
||||
**Zuvor abgeschlossen (Session 26 - CodeRabbit Review Fixes):**
|
||||
@ -418,7 +418,7 @@ ds-chip + ds-tag → OsBadge (UI-Library): ✅
|
||||
- [ ] Weitere Tier 2 Komponenten (OsDropdown, OsAvatar)
|
||||
- [x] ds-form → formValidation Mixin (async-validator), 18 Dateien migriert, vuelidate entfernt ✅
|
||||
- [x] ds-input → OcelotInput (23 Dateien, Webapp-Komponente mit lokalen Imports, formValidation-kompatibel) ✅
|
||||
- [ ] ds-menu / ds-menu-item → OsMenu / OsMenuItem
|
||||
- [x] ds-menu / ds-menu-item → OsMenu / OsMenuItem (packages/ui, 17 Nutzungen in 11 Dateien, dropdown Prop, eigene CSS in index.css) ✅
|
||||
- [x] ds-select → OcelotSelect (3 Dateien, Webapp-Komponente mit lokalen Imports, click-outside inline) ✅
|
||||
- [ ] Browser-Fehler untersuchen: `TypeError: Cannot read properties of undefined (reading 'heartO')` (ocelotIcons undefined im Browser trotz korrekter Webpack-Aliase)
|
||||
|
||||
@ -697,8 +697,7 @@ Jeder migrierte Button muss manuell geprüft werden: Normal, Hover, Focus, Activ
|
||||
- [x] ds-input → OcelotInput (23 Dateien, Webapp-Komponente mit lokalen Imports, formValidation-kompatibel) ✅
|
||||
|
||||
**Tier 3: Navigation (UI-Library)**
|
||||
- [ ] OsMenu (Basis: DsMenu, 11 Dateien)
|
||||
- [ ] OsMenuItem (Basis: DsMenuItem, 6 Dateien)
|
||||
- [x] OsMenu + OsMenuItem (packages/ui, h() Render, vue-demi, provide/inject, dropdown Prop, eigene CSS in index.css, 17 Nutzungen in 11 Dateien) ✅
|
||||
|
||||
**Tier 4: Spezial-Komponenten**
|
||||
- [x] ds-select → OcelotSelect (3 Dateien, Webapp-Komponente, click-outside inline, DsChip→OsBadge, DsSpinner→OsSpinner) ✅
|
||||
@ -1855,6 +1854,7 @@ Bei der Migration werden:
|
||||
| 2026-03-23 | **ds-input → OcelotInput** | Neue Webapp-Komponente `OcelotInput.vue`: vereint DsInput + FormItem + InputLabel + InputError in einer Datei. 23 Vue-Dateien migriert mit lokalen Imports (tree-shakeable). formValidation Mixin voll kompatibel. dot-prop Abhängigkeit durch inline `getNestedValue()` ersetzt. 28 Test-Suites, 210 Tests ✅, 7 Snapshots aktualisiert. |
|
||||
| 2026-03-23 | **OcelotInput: ds-icon → os-icon** | DsIcon durch OsIcon + resolveIcon() ersetzt. at.svg, envelope.svg, paperclip.svg zu Ocelot-Icons hinzugefügt. Ocelot-Icons Visual Snapshot aktualisiert. |
|
||||
| 2026-03-23 | **ds-select → OcelotSelect** | Neue Webapp-Komponente `OcelotSelect.vue`: vereint DsSelect + inputMixin + multiinputMixin (~420 Zeilen). Form-Validation entfernt (von keinem Consumer genutzt). DsChip→OsBadge, DsSpinner→OsSpinner, DsIcon→OsIcon. vue-click-outside durch inline document.addEventListener ersetzt. 3 Dateien migriert, 16 Tests ✅. |
|
||||
| 2026-03-23 | **ds-menu → OsMenu/OsMenuItem** | Neue packages/ui Komponenten: h() Render, vue-demi, provide/inject, dropdown Prop für Popup-Variante. CSS in src/styles/index.css (integriert in style.css Build). 17 Nutzungen in 11 Dateien migriert. Action-Menüs nutzen link-tag default 'a' statt router-link. router-link Stub global in testSetup.js. Vite closeBundle Hook: ui.css in style.css gemergt. 273 UI-Tests, 108 Webapp-Tests ✅. **0 ds-* Komponenten-Tags verbleibend in Webapp.** |
|
||||
|
||||
---
|
||||
|
||||
@ -1878,7 +1878,7 @@ Bei der Migration werden:
|
||||
| ✅ ds-form entkoppelt | Form-Validierung → formValidation Mixin (async-validator), vuelidate entfernt |
|
||||
| ✅ ds-input → OcelotInput | Webapp-Komponente (23 Dateien), lokale Imports, FormItem/InputLabel/InputError vereint |
|
||||
| ✅ ds-select → OcelotSelect | Webapp-Komponente (3 Dateien), lokale Imports, click-outside inline, DsChip→OsBadge |
|
||||
| ⬜ → UI-Library | Menu, MenuItem (2) — Tier 3 |
|
||||
| ✅ → OsMenu/OsMenuItem | Menu, MenuItem (17 Nutzungen → packages/ui, dropdown Prop, eigene CSS) |
|
||||
| ⬜ Nicht genutzt | Code, CopyField, FormItem, InputError, InputLabel, Page, PageTitle, Logo, Avatar, TableCol, TableHeadCol (11) |
|
||||
|
||||
---
|
||||
|
||||
244
packages/ui/src/components/OsMenu/OsMenu.spec.ts
Normal file
244
packages/ui/src/components/OsMenu/OsMenu.spec.ts
Normal file
@ -0,0 +1,244 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import OsMenu from './OsMenu.vue'
|
||||
import OsMenuItem from './OsMenuItem.vue'
|
||||
|
||||
const routes = [
|
||||
{ name: 'Home', path: '/' },
|
||||
{ name: 'Settings', path: '/settings' },
|
||||
{ name: 'Profile', path: '/profile' },
|
||||
]
|
||||
|
||||
const nestedRoutes = [
|
||||
{ name: 'Home', path: '/' },
|
||||
{
|
||||
name: 'Posts',
|
||||
path: '/posts',
|
||||
children: [
|
||||
{ name: 'All', path: '/posts/all' },
|
||||
{ name: 'Drafts', path: '/posts/drafts' },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
describe('osMenu', () => {
|
||||
it('renders a nav element with os-menu class', () => {
|
||||
const wrapper = mount(OsMenu, { props: { routes } })
|
||||
|
||||
expect(wrapper.find('nav.os-menu').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('renders a ul.os-menu-list', () => {
|
||||
const wrapper = mount(OsMenu, { props: { routes } })
|
||||
|
||||
expect(wrapper.find('ul.os-menu-list').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('renders menu items for each route', () => {
|
||||
const wrapper = mount(OsMenu, { props: { routes } })
|
||||
const items = wrapper.findAll('.os-menu-item')
|
||||
|
||||
expect(items).toHaveLength(3)
|
||||
})
|
||||
|
||||
it('uses default linkTag "a"', () => {
|
||||
const wrapper = mount(OsMenu, { props: { routes } })
|
||||
const links = wrapper.findAll('.os-menu-item-link')
|
||||
|
||||
expect(links[0].element.tagName).toBe('A')
|
||||
})
|
||||
|
||||
it('passes custom linkTag to items', () => {
|
||||
const wrapper = mount(OsMenu, {
|
||||
props: { routes, linkTag: 'button' },
|
||||
})
|
||||
const links = wrapper.findAll('.os-menu-item-link')
|
||||
|
||||
expect(links[0].element.tagName).toBe('BUTTON')
|
||||
})
|
||||
|
||||
it('applies custom nameParser', () => {
|
||||
const wrapper = mount(OsMenu, {
|
||||
props: {
|
||||
routes: [{ name: 'test', path: '/test' }],
|
||||
nameParser: () => 'Custom Name',
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.find('.os-menu-item-link').text()).toBe('Custom Name')
|
||||
})
|
||||
|
||||
it('default urlParser falls back to "/" when route has no path', () => {
|
||||
const wrapper = mount(OsMenu, {
|
||||
props: { routes: [{ name: 'No Path' }] },
|
||||
})
|
||||
|
||||
expect(wrapper.find('.os-menu-item-link').attributes('href')).toBe('/')
|
||||
})
|
||||
|
||||
it('default nameParser falls back to "" when route has no name', () => {
|
||||
const wrapper = mount(OsMenu, {
|
||||
props: { routes: [{ path: '/test' }] },
|
||||
})
|
||||
|
||||
expect(wrapper.find('.os-menu-item-link').text()).toBe('')
|
||||
})
|
||||
|
||||
it('applies matcher for active state', () => {
|
||||
const wrapper = mount(OsMenu, {
|
||||
props: {
|
||||
routes,
|
||||
matcher: (_url: string, route: Record<string, unknown>) => route.path === '/settings',
|
||||
},
|
||||
})
|
||||
const items = wrapper.findAll('.os-menu-item-link')
|
||||
const settingsLink = items[1]
|
||||
|
||||
expect(settingsLink.classes()).toContain('os-menu-item--active')
|
||||
})
|
||||
|
||||
it('renders menuitem scoped slot', () => {
|
||||
const wrapper = mount(OsMenu, {
|
||||
props: { routes },
|
||||
slots: {
|
||||
menuitem: `<template #menuitem="{ route, name }">
|
||||
<li class="custom-item">{{ name }}</li>
|
||||
</template>`,
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.findAll('.custom-item')).toHaveLength(3)
|
||||
})
|
||||
|
||||
it('emits navigate on item click', async () => {
|
||||
const wrapper = mount(OsMenu, { props: { routes } })
|
||||
|
||||
await wrapper.find('.os-menu-item-link').trigger('click')
|
||||
|
||||
expect(wrapper.emitted('navigate')?.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
describe('keyboard accessibility', () => {
|
||||
it('menu items are rendered as focusable links', () => {
|
||||
const wrapper = mount(OsMenu, { props: { routes } })
|
||||
const links = wrapper.findAll('.os-menu-item-link')
|
||||
|
||||
expect(links.length).toBeGreaterThan(0)
|
||||
|
||||
links.forEach((link) => {
|
||||
expect(link.element.tagName).toBe('A')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('osMenuItem', () => {
|
||||
const parentMenu = {
|
||||
linkTag: 'a',
|
||||
urlParser: (route: Record<string, unknown>) => (route.path as string) || '/',
|
||||
nameParser: (route: Record<string, unknown>) => (route.name as string) || '',
|
||||
matcher: () => false,
|
||||
isExact: (url: string) => url === '/',
|
||||
handleNavigate: () => {},
|
||||
}
|
||||
|
||||
it('renders an li with os-menu-item class', () => {
|
||||
const wrapper = mount(OsMenuItem, {
|
||||
props: { route: routes[0] },
|
||||
global: { provide: { $parentMenu: parentMenu } },
|
||||
})
|
||||
|
||||
expect(wrapper.find('li.os-menu-item').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('renders link with correct href', () => {
|
||||
const wrapper = mount(OsMenuItem, {
|
||||
props: { route: { name: 'Test', path: '/test' } },
|
||||
global: { provide: { $parentMenu: parentMenu } },
|
||||
})
|
||||
|
||||
expect(wrapper.find('.os-menu-item-link').attributes('href')).toBe('/test')
|
||||
})
|
||||
|
||||
it('displays route name', () => {
|
||||
const wrapper = mount(OsMenuItem, {
|
||||
props: { route: { name: 'My Item', path: '/item' } },
|
||||
global: { provide: { $parentMenu: parentMenu } },
|
||||
})
|
||||
|
||||
expect(wrapper.find('.os-menu-item-link').text()).toBe('My Item')
|
||||
})
|
||||
|
||||
it('applies level class based on parents', () => {
|
||||
const wrapper = mount(OsMenuItem, {
|
||||
props: {
|
||||
route: { name: 'Child', path: '/child' },
|
||||
parents: [{ name: 'Parent', path: '/parent' }],
|
||||
},
|
||||
global: { provide: { $parentMenu: parentMenu } },
|
||||
})
|
||||
|
||||
expect(wrapper.find('.os-menu-item').classes()).toContain('os-menu-item-level-1')
|
||||
})
|
||||
|
||||
it('emits click with route on click', async () => {
|
||||
const route = { name: 'Test', path: '/test' }
|
||||
const wrapper = mount(OsMenuItem, {
|
||||
props: { route },
|
||||
global: { provide: { $parentMenu: parentMenu } },
|
||||
})
|
||||
|
||||
await wrapper.find('.os-menu-item-link').trigger('click')
|
||||
|
||||
const clicks = wrapper.emitted('click')
|
||||
|
||||
expect(clicks?.length).toBeGreaterThan(0)
|
||||
expect(clicks?.[0]?.[1]).toStrictEqual(route)
|
||||
})
|
||||
|
||||
it('renders submenu for routes with children', () => {
|
||||
const wrapper = mount(OsMenuItem, {
|
||||
props: { route: nestedRoutes[1] },
|
||||
global: { provide: { $parentMenu: parentMenu } },
|
||||
})
|
||||
|
||||
expect(wrapper.find('.os-menu-item-submenu').exists()).toBe(true)
|
||||
expect(wrapper.findAll('.os-menu-item-submenu .os-menu-item')).toHaveLength(2)
|
||||
})
|
||||
|
||||
it('renders default slot content', () => {
|
||||
const wrapper = mount(OsMenuItem, {
|
||||
props: { route: { name: 'Test', path: '/test' } },
|
||||
global: { provide: { $parentMenu: parentMenu } },
|
||||
slots: { default: '<strong>Custom</strong>' },
|
||||
})
|
||||
|
||||
expect(wrapper.find('.os-menu-item-link strong').text()).toBe('Custom')
|
||||
})
|
||||
|
||||
it('adds active class when matcher returns true', () => {
|
||||
const activeMenu = {
|
||||
...parentMenu,
|
||||
matcher: () => true,
|
||||
}
|
||||
const wrapper = mount(OsMenuItem, {
|
||||
props: { route: { name: 'Test', path: '/test' } },
|
||||
global: { provide: { $parentMenu: activeMenu } },
|
||||
})
|
||||
|
||||
expect(wrapper.find('.os-menu-item-link').classes()).toContain('os-menu-item--active')
|
||||
})
|
||||
|
||||
describe('keyboard accessibility', () => {
|
||||
it('menu item links are focusable', () => {
|
||||
const wrapper = mount(OsMenuItem, {
|
||||
props: { route: { name: 'Test', path: '/test' } },
|
||||
global: { provide: { $parentMenu: parentMenu } },
|
||||
})
|
||||
const link = wrapper.find('.os-menu-item-link')
|
||||
|
||||
expect(link.element.tagName).toBe('A')
|
||||
})
|
||||
})
|
||||
})
|
||||
134
packages/ui/src/components/OsMenu/OsMenu.stories.ts
Normal file
134
packages/ui/src/components/OsMenu/OsMenu.stories.ts
Normal file
@ -0,0 +1,134 @@
|
||||
import { computed } from 'vue'
|
||||
|
||||
import OsMenu from './OsMenu.vue'
|
||||
import OsMenuItem from './OsMenuItem.vue'
|
||||
|
||||
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
||||
|
||||
const sidebarRoutes = [
|
||||
{ name: 'Dashboard', path: '/dashboard' },
|
||||
{ name: 'Settings', path: '/settings' },
|
||||
{ name: 'Profile', path: '/profile' },
|
||||
{ name: 'Notifications', path: '/notifications' },
|
||||
]
|
||||
|
||||
const nestedRoutes = [
|
||||
{ name: 'Home', path: '/' },
|
||||
{
|
||||
name: 'Posts',
|
||||
path: '/posts',
|
||||
children: [
|
||||
{ name: 'All Posts', path: '/posts/all' },
|
||||
{ name: 'Drafts', path: '/posts/drafts' },
|
||||
],
|
||||
},
|
||||
{ name: 'Users', path: '/users' },
|
||||
]
|
||||
|
||||
const meta: Meta<typeof OsMenu> = {
|
||||
title: 'Components/OsMenu',
|
||||
component: OsMenu,
|
||||
tags: ['autodocs'],
|
||||
}
|
||||
|
||||
export default meta
|
||||
|
||||
export const Sidebar: StoryObj = {
|
||||
render: () => ({
|
||||
components: { OsMenu },
|
||||
setup() {
|
||||
const routes = computed(() => sidebarRoutes)
|
||||
return { routes }
|
||||
},
|
||||
template: `
|
||||
<div style="width: 200px">
|
||||
<OsMenu :routes="routes" />
|
||||
</div>
|
||||
`,
|
||||
}),
|
||||
}
|
||||
|
||||
export const SidebarExactMatch: StoryObj = {
|
||||
render: () => ({
|
||||
components: { OsMenu },
|
||||
setup() {
|
||||
const routes = computed(() => sidebarRoutes)
|
||||
const matcher = (_url: string, route: Record<string, unknown>) => route.path === '/settings'
|
||||
return { routes, matcher }
|
||||
},
|
||||
template: `
|
||||
<div style="width: 200px">
|
||||
<OsMenu :routes="routes" :matcher="matcher" />
|
||||
</div>
|
||||
`,
|
||||
}),
|
||||
}
|
||||
|
||||
export const NestedRoutes: StoryObj = {
|
||||
render: () => ({
|
||||
components: { OsMenu },
|
||||
setup() {
|
||||
const routes = computed(() => nestedRoutes)
|
||||
return { routes }
|
||||
},
|
||||
template: `
|
||||
<div style="width: 200px">
|
||||
<OsMenu :routes="routes" />
|
||||
</div>
|
||||
`,
|
||||
}),
|
||||
}
|
||||
|
||||
export const CustomMenuItem: StoryObj = {
|
||||
render: () => ({
|
||||
components: { OsMenu, OsMenuItem },
|
||||
setup() {
|
||||
const routes = computed(() => [
|
||||
{ name: 'Edit', path: '/edit', label: 'Edit Item' },
|
||||
{ name: 'Delete', path: '/delete', label: 'Delete Item' },
|
||||
{ name: 'Share', path: '/share', label: 'Share Item' },
|
||||
])
|
||||
return { routes }
|
||||
},
|
||||
template: `
|
||||
<div style="width: 200px">
|
||||
<OsMenu dropdown :routes="routes">
|
||||
<template #menuitem="{ route, parents, name }">
|
||||
<OsMenuItem :route="route" :parents="parents">
|
||||
<strong>{{ route.label }}</strong>
|
||||
</OsMenuItem>
|
||||
</template>
|
||||
</OsMenu>
|
||||
</div>
|
||||
`,
|
||||
}),
|
||||
}
|
||||
|
||||
export const DropdownMenu: StoryObj = {
|
||||
render: () => ({
|
||||
components: { OsMenu, OsMenuItem },
|
||||
setup() {
|
||||
const routes = computed(() => [
|
||||
{ name: 'Option A', label: 'Option A' },
|
||||
{ name: 'Option B', label: 'Option B' },
|
||||
{ name: 'Option C', label: 'Option C' },
|
||||
])
|
||||
const handleClick = (_e: Event, route: Record<string, unknown>) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Selected:', route.label)
|
||||
}
|
||||
return { routes, handleClick }
|
||||
},
|
||||
template: `
|
||||
<div style="width: 200px; background: white; border: 1px solid #ddd; border-radius: 4px; padding: 4px 0;">
|
||||
<OsMenu dropdown :routes="routes">
|
||||
<template #menuitem="{ route, parents }">
|
||||
<OsMenuItem :route="route" :parents="parents" @click="handleClick">
|
||||
{{ route.label }}
|
||||
</OsMenuItem>
|
||||
</template>
|
||||
</OsMenu>
|
||||
</div>
|
||||
`,
|
||||
}),
|
||||
}
|
||||
85
packages/ui/src/components/OsMenu/OsMenu.visual.spec.ts
Normal file
85
packages/ui/src/components/OsMenu/OsMenu.visual.spec.ts
Normal file
@ -0,0 +1,85 @@
|
||||
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-osmenu'
|
||||
const STORY_ROOT = '#storybook-root'
|
||||
|
||||
async function waitForFonts(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('OsMenu keyboard accessibility', () => {
|
||||
test('menu items are focusable via links', async ({ page }) => {
|
||||
await page.goto(`${STORY_URL}--sidebar&viewMode=story`)
|
||||
const root = page.locator(STORY_ROOT)
|
||||
await root.waitFor()
|
||||
|
||||
const links = root.locator('.os-menu-item-link')
|
||||
const count = await links.count()
|
||||
|
||||
expect(count).toBeGreaterThan(0)
|
||||
|
||||
// Tab through links
|
||||
for (let i = 0; i < count; i++) {
|
||||
await page.keyboard.press('Tab')
|
||||
const focused = page.locator(':focus')
|
||||
const focusedClass = await focused.getAttribute('class')
|
||||
|
||||
expect(focusedClass).toContain('os-menu-item-link')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('OsMenu visual regression', () => {
|
||||
test('sidebar', async ({ page }) => {
|
||||
await page.goto(`${STORY_URL}--sidebar&viewMode=story`)
|
||||
const root = page.locator(STORY_ROOT)
|
||||
await root.waitFor()
|
||||
await waitForFonts(page)
|
||||
|
||||
await expect(root).toHaveScreenshot('sidebar.png')
|
||||
|
||||
await checkA11y(page)
|
||||
})
|
||||
|
||||
test('sidebar with active item', async ({ page }) => {
|
||||
await page.goto(`${STORY_URL}--sidebar-exact-match&viewMode=story`)
|
||||
const root = page.locator(STORY_ROOT)
|
||||
await root.waitFor()
|
||||
await waitForFonts(page)
|
||||
|
||||
await expect(root).toHaveScreenshot('sidebar-active.png')
|
||||
|
||||
await checkA11y(page)
|
||||
})
|
||||
|
||||
test('nested routes', async ({ page }) => {
|
||||
await page.goto(`${STORY_URL}--nested-routes&viewMode=story`)
|
||||
const root = page.locator(STORY_ROOT)
|
||||
await root.waitFor()
|
||||
await waitForFonts(page)
|
||||
|
||||
await expect(root).toHaveScreenshot('nested.png')
|
||||
|
||||
await checkA11y(page)
|
||||
})
|
||||
|
||||
test('dropdown menu', async ({ page }) => {
|
||||
await page.goto(`${STORY_URL}--dropdown-menu&viewMode=story`)
|
||||
const root = page.locator(STORY_ROOT)
|
||||
await root.waitFor()
|
||||
await waitForFonts(page)
|
||||
|
||||
await expect(root).toHaveScreenshot('dropdown.png')
|
||||
|
||||
await checkA11y(page)
|
||||
})
|
||||
})
|
||||
151
packages/ui/src/components/OsMenu/OsMenu.vue
Normal file
151
packages/ui/src/components/OsMenu/OsMenu.vue
Normal file
@ -0,0 +1,151 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, getCurrentInstance, h, isVue2 } from 'vue-demi'
|
||||
|
||||
import { cn } from '#src/utils'
|
||||
|
||||
import OsMenuItem from './OsMenuItem.vue'
|
||||
|
||||
import type { Component, PropType } from 'vue-demi'
|
||||
|
||||
/**
|
||||
* Navigation menu component that renders a list of route items.
|
||||
*
|
||||
* Use the `menuitem` scoped slot to customize how each item is rendered.
|
||||
* Pass `link-tag` to control which element renders links (e.g. `'nuxt-link'`).
|
||||
*
|
||||
* Styles are defined in src/styles/index.css and included in the style.css build.
|
||||
*
|
||||
* @slot default - Override entire menu content
|
||||
* @slot menuitem - Custom menu item template (scoped: { route, parents, name })
|
||||
*/
|
||||
export default defineComponent({
|
||||
name: 'OsMenu',
|
||||
components: { OsMenuItem },
|
||||
provide() {
|
||||
return {
|
||||
$parentMenu: this,
|
||||
}
|
||||
},
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
/** Array of route objects to display */
|
||||
routes: {
|
||||
type: Array as PropType<Record<string, unknown>[]>,
|
||||
default: null,
|
||||
},
|
||||
/** Display as compact dropdown menu (smaller padding, hover border accent) */
|
||||
dropdown: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
/** Component or tag used for links */
|
||||
linkTag: {
|
||||
type: [String, Object, Function] as PropType<string | Component>,
|
||||
default: 'a',
|
||||
},
|
||||
/** Function to extract URL from a route */
|
||||
urlParser: {
|
||||
type: Function as PropType<
|
||||
(route: Record<string, unknown>, parents: Record<string, unknown>[]) => string
|
||||
>,
|
||||
default: (route: Record<string, unknown>) => {
|
||||
return (route.path as string) || '/'
|
||||
},
|
||||
},
|
||||
/** Function to extract display name from a route */
|
||||
nameParser: {
|
||||
type: Function as PropType<
|
||||
(route: Record<string, unknown>, parents: Record<string, unknown>[]) => string
|
||||
>,
|
||||
default: (route: Record<string, unknown>) => {
|
||||
return (route.name as string) || ''
|
||||
},
|
||||
},
|
||||
/** Custom matcher function for active state */
|
||||
matcher: {
|
||||
type: Function as PropType<(url: string, route: Record<string, unknown>) => boolean>,
|
||||
default: () => false,
|
||||
},
|
||||
/** Function to check if URL must match exactly */
|
||||
isExact: {
|
||||
type: Function as PropType<(url: string) => boolean>,
|
||||
default: (url: string) => url === '/',
|
||||
},
|
||||
},
|
||||
emits: ['navigate'],
|
||||
/* v8 ignore start -- render function tested via unit + visual tests */
|
||||
setup(props, { slots, attrs }) {
|
||||
const instance = isVue2 ? getCurrentInstance() : null
|
||||
|
||||
return () => {
|
||||
// Build menu items from routes
|
||||
let menuItems: unknown[] = []
|
||||
|
||||
if (slots.default) {
|
||||
const defaultContent = slots.default()
|
||||
menuItems = Array.isArray(defaultContent) ? defaultContent : [defaultContent]
|
||||
} else if (props.routes) {
|
||||
menuItems = props.routes.map((route, index) => {
|
||||
if (slots.menuitem) {
|
||||
const name = props.nameParser(route, [])
|
||||
return slots.menuitem({ route, parents: [], name })
|
||||
}
|
||||
const key = (route.path as string) || index
|
||||
|
||||
if (isVue2) {
|
||||
return h(OsMenuItem, { key, props: { route } })
|
||||
}
|
||||
return h(OsMenuItem, { key, route })
|
||||
})
|
||||
}
|
||||
|
||||
const listChildren = menuItems
|
||||
.flat(Infinity)
|
||||
.filter((item): item is ReturnType<typeof h> => item != null)
|
||||
const list = h('ul', { class: 'os-menu-list' }, listChildren)
|
||||
|
||||
if (isVue2) {
|
||||
const proxy = instance?.proxy as Record<string, unknown> | undefined
|
||||
const parentClass =
|
||||
(proxy?.$vnode as Record<string, Record<string, unknown>> | undefined)?.data
|
||||
?.staticClass || ''
|
||||
const parentDynClass = (
|
||||
proxy?.$vnode as Record<string, Record<string, unknown>> | undefined
|
||||
)?.data?.class
|
||||
|
||||
return h(
|
||||
'nav',
|
||||
{
|
||||
class: [
|
||||
cn('os-menu', props.dropdown && 'os-menu--dropdown', parentClass as string),
|
||||
parentDynClass,
|
||||
].filter(Boolean),
|
||||
attrs,
|
||||
},
|
||||
[list],
|
||||
)
|
||||
}
|
||||
|
||||
const { class: attrClass, ...restAttrs } = attrs as Record<string, unknown>
|
||||
return h(
|
||||
'nav',
|
||||
{
|
||||
class: cn(
|
||||
'os-menu',
|
||||
props.dropdown && 'os-menu--dropdown',
|
||||
(attrClass as string) || '',
|
||||
),
|
||||
...restAttrs,
|
||||
},
|
||||
[list],
|
||||
)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleNavigate() {
|
||||
this.$emit('navigate')
|
||||
},
|
||||
},
|
||||
/* v8 ignore stop */
|
||||
})
|
||||
</script>
|
||||
30
packages/ui/src/components/OsMenu/OsMenuItem.spec.ts
Normal file
30
packages/ui/src/components/OsMenu/OsMenuItem.spec.ts
Normal file
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* OsMenuItem unit tests are colocated in OsMenu.spec.ts
|
||||
* since OsMenuItem requires OsMenu as a parent provider.
|
||||
*/
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import OsMenuItem from './OsMenuItem.vue'
|
||||
|
||||
const parentMenu = {
|
||||
linkTag: 'a',
|
||||
urlParser: (route: Record<string, unknown>) => (route.path as string) || '/',
|
||||
nameParser: (route: Record<string, unknown>) => (route.name as string) || '',
|
||||
matcher: () => false,
|
||||
isExact: (url: string) => url === '/',
|
||||
handleNavigate: () => {},
|
||||
}
|
||||
|
||||
describe('keyboard accessibility', () => {
|
||||
it('renders as a focusable link element', () => {
|
||||
const wrapper = mount(OsMenuItem, {
|
||||
props: { route: { name: 'Test', path: '/test' } },
|
||||
global: { provide: { $parentMenu: parentMenu } },
|
||||
})
|
||||
const link = wrapper.find('.os-menu-item-link')
|
||||
|
||||
expect(link.element.tagName).toBe('A')
|
||||
expect(link.attributes('href')).toBe('/test')
|
||||
})
|
||||
})
|
||||
38
packages/ui/src/components/OsMenu/OsMenuItem.stories.ts
Normal file
38
packages/ui/src/components/OsMenu/OsMenuItem.stories.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import OsMenu from './OsMenu.vue'
|
||||
import OsMenuItem from './OsMenuItem.vue'
|
||||
|
||||
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
||||
|
||||
const meta: Meta<typeof OsMenuItem> = {
|
||||
title: 'Components/OsMenuItem',
|
||||
component: OsMenuItem,
|
||||
tags: ['!autodocs', '!dev'],
|
||||
}
|
||||
|
||||
export default meta
|
||||
|
||||
const routes = [
|
||||
{ name: 'Edit', path: '/edit', label: 'Edit Item' },
|
||||
{ name: 'Delete', path: '/delete', label: 'Delete Item' },
|
||||
{ name: 'Share', path: '/share', label: 'Share Item' },
|
||||
]
|
||||
|
||||
export const InDropdown: StoryObj = {
|
||||
render: () => ({
|
||||
components: { OsMenu, OsMenuItem },
|
||||
setup() {
|
||||
return { routes }
|
||||
},
|
||||
template: `
|
||||
<div style="width: 200px; background: white; border: 1px solid #ddd; border-radius: 4px; padding: 4px 0;">
|
||||
<OsMenu dropdown :routes="routes">
|
||||
<template #menuitem="{ route, parents }">
|
||||
<OsMenuItem :route="route" :parents="parents">
|
||||
<strong>{{ route.label }}</strong>
|
||||
</OsMenuItem>
|
||||
</template>
|
||||
</OsMenu>
|
||||
</div>
|
||||
`,
|
||||
}),
|
||||
}
|
||||
48
packages/ui/src/components/OsMenu/OsMenuItem.visual.spec.ts
Normal file
48
packages/ui/src/components/OsMenu/OsMenuItem.visual.spec.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { AxeBuilder } from '@axe-core/playwright'
|
||||
import { expect, test } from '@playwright/test'
|
||||
|
||||
import type { Page } from '@playwright/test'
|
||||
|
||||
/**
|
||||
* OsMenuItem visual tests — uses OsMenu stories since
|
||||
* OsMenuItem is always rendered within an OsMenu context.
|
||||
*/
|
||||
|
||||
const STORY_URL = '/iframe.html?id=components-osmenuitem'
|
||||
const STORY_ROOT = '#storybook-root'
|
||||
|
||||
async function waitForFonts(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('OsMenuItem keyboard accessibility', () => {
|
||||
test('menu items are focusable via links', async ({ page }) => {
|
||||
await page.goto(`${STORY_URL}--in-dropdown&viewMode=story`)
|
||||
const root = page.locator(STORY_ROOT)
|
||||
await root.waitFor()
|
||||
|
||||
const links = root.locator('.os-menu-item-link')
|
||||
const count = await links.count()
|
||||
|
||||
expect(count).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('OsMenuItem visual regression', () => {
|
||||
test('custom menu item', async ({ page }) => {
|
||||
await page.goto(`${STORY_URL}--in-dropdown&viewMode=story`)
|
||||
const root = page.locator(STORY_ROOT)
|
||||
await root.waitFor()
|
||||
await waitForFonts(page)
|
||||
|
||||
await expect(root).toHaveScreenshot('menuitem-custom.png')
|
||||
|
||||
await checkA11y(page)
|
||||
})
|
||||
})
|
||||
251
packages/ui/src/components/OsMenu/OsMenuItem.vue
Normal file
251
packages/ui/src/components/OsMenu/OsMenuItem.vue
Normal file
@ -0,0 +1,251 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, getCurrentInstance, h, isVue2 } from 'vue-demi'
|
||||
|
||||
import { cn } from '#src/utils'
|
||||
|
||||
import type { Component, PropType } from 'vue-demi'
|
||||
|
||||
/**
|
||||
* Individual menu item used inside OsMenu.
|
||||
* Injects `$parentMenu` to access menu-level configuration.
|
||||
*
|
||||
* @slot default - Custom item label content
|
||||
*/
|
||||
export default defineComponent({
|
||||
name: 'OsMenuItem',
|
||||
inject: {
|
||||
$parentMenu: {
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
/** Route object with name, path, children, etc. */
|
||||
route: {
|
||||
type: Object as PropType<Record<string, unknown>>,
|
||||
default: null,
|
||||
},
|
||||
/** Parent routes for nesting level calculation */
|
||||
parents: {
|
||||
type: Array as PropType<Record<string, unknown>[]>,
|
||||
default: () => [],
|
||||
},
|
||||
/** Override link component for this item */
|
||||
linkTag: {
|
||||
type: [String, Object, Function] as PropType<string | Component>,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
emits: ['click'],
|
||||
/* v8 ignore start -- render function tested via unit + visual tests */
|
||||
setup(props, { slots, attrs }) {
|
||||
const instance = isVue2 ? getCurrentInstance() : null
|
||||
|
||||
// We need access to component instance for inject, so use render in setup
|
||||
return () => {
|
||||
// Access component instance for inject values
|
||||
const proxy = instance?.proxy as Record<string, unknown> | undefined
|
||||
const vm = (isVue2 ? proxy : getCurrentInstance()?.proxy) as Record<string, unknown>
|
||||
|
||||
if (!vm || !props.route) return null
|
||||
|
||||
const resolvedLinkTag = vm.resolvedLinkTag as string | Component
|
||||
const matcherResult = vm.matcherResult as boolean
|
||||
const isExact = vm.isExact as boolean
|
||||
const name = vm.name as string
|
||||
const level = vm.level as number
|
||||
const bindings = vm.bindings as Record<string, unknown>
|
||||
const hasSubmenu = vm.hasSubmenu as boolean
|
||||
const showSubmenu = vm.showSubmenu as boolean
|
||||
const handleClick = vm.handleClick as (e: Event) => void
|
||||
|
||||
// Build link element
|
||||
const linkClass = cn('os-menu-item-link', matcherResult && 'os-menu-item--active')
|
||||
|
||||
const defaultSlotContent = slots.default?.()
|
||||
const linkChildren = defaultSlotContent || [name]
|
||||
|
||||
let linkNode: ReturnType<typeof h>
|
||||
if (isVue2) {
|
||||
linkNode = h(
|
||||
resolvedLinkTag,
|
||||
{
|
||||
class: linkClass,
|
||||
props: { ...bindings, exact: isExact },
|
||||
attrs: bindings,
|
||||
on: { click: handleClick },
|
||||
ref: 'link',
|
||||
},
|
||||
linkChildren,
|
||||
)
|
||||
} else {
|
||||
linkNode = h(
|
||||
resolvedLinkTag,
|
||||
{
|
||||
class: linkClass,
|
||||
...bindings,
|
||||
exact: isExact,
|
||||
onClick: handleClick,
|
||||
ref: 'link',
|
||||
},
|
||||
linkChildren,
|
||||
)
|
||||
}
|
||||
|
||||
// Build submenu if children exist
|
||||
const children: ReturnType<typeof h>[] = [linkNode]
|
||||
if (hasSubmenu) {
|
||||
const OsMenuItemSelf = vm.$options as Component
|
||||
const submenuItems = (props.route.children as Record<string, unknown>[]).map((child) => {
|
||||
const childProps = {
|
||||
route: child,
|
||||
parents: [...props.parents, props.route],
|
||||
}
|
||||
if (isVue2) {
|
||||
return h(OsMenuItemSelf, { key: child.name as string, props: childProps })
|
||||
}
|
||||
return h(OsMenuItemSelf, { key: child.name as string, ...childProps })
|
||||
})
|
||||
children.push(h('ul', { class: 'os-menu-item-submenu' }, submenuItems))
|
||||
}
|
||||
|
||||
// Build li wrapper
|
||||
const liClass = cn(
|
||||
'os-menu-item',
|
||||
`os-menu-item-level-${String(level)}`,
|
||||
showSubmenu && 'os-menu-item-show-submenu',
|
||||
)
|
||||
|
||||
if (isVue2) {
|
||||
const parentClass =
|
||||
(proxy?.$vnode as Record<string, Record<string, unknown>> | undefined)?.data
|
||||
?.staticClass || ''
|
||||
const parentDynClass = (
|
||||
proxy?.$vnode as Record<string, Record<string, unknown>> | undefined
|
||||
)?.data?.class
|
||||
const listeners = (proxy as Record<string, unknown>)?.$listeners as
|
||||
| Record<string, unknown>
|
||||
| undefined
|
||||
|
||||
return h(
|
||||
'li',
|
||||
{
|
||||
class: [cn(liClass, parentClass as string), parentDynClass].filter(Boolean),
|
||||
attrs,
|
||||
on: {
|
||||
...(listeners || {}),
|
||||
click: handleClick,
|
||||
},
|
||||
},
|
||||
children,
|
||||
)
|
||||
}
|
||||
|
||||
const { class: attrClass, ...restAttrs } = attrs as Record<string, unknown>
|
||||
return h(
|
||||
'li',
|
||||
{
|
||||
class: cn(liClass, (attrClass as string) || ''),
|
||||
onClick: handleClick,
|
||||
...restAttrs,
|
||||
},
|
||||
children,
|
||||
)
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showSubmenu: false,
|
||||
clickOutsideHandler: null as ((e: Event) => void) | null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
resolvedLinkTag(): string | Component {
|
||||
if (this.linkTag) return this.linkTag
|
||||
const menu = this.$parentMenu as Record<string, unknown> | null
|
||||
return (menu?.linkTag as string | Component) || 'a'
|
||||
},
|
||||
hasSubmenu(): boolean {
|
||||
const children = this.route?.children as unknown[] | undefined
|
||||
return Boolean(children && children.length)
|
||||
},
|
||||
url(): string {
|
||||
const menu = this.$parentMenu as Record<string, unknown> | null
|
||||
const parser = menu?.urlParser as (
|
||||
r: Record<string, unknown>,
|
||||
p: Record<string, unknown>[],
|
||||
) => string
|
||||
return parser ? parser(this.route, this.parents) : (this.route?.path as string) || '/'
|
||||
},
|
||||
name(): string {
|
||||
const menu = this.$parentMenu as Record<string, unknown> | null
|
||||
const parser = menu?.nameParser as (
|
||||
r: Record<string, unknown>,
|
||||
p: Record<string, unknown>[],
|
||||
) => string
|
||||
return parser ? parser(this.route, this.parents) : (this.route?.name as string) || ''
|
||||
},
|
||||
isExact(): boolean {
|
||||
const menu = this.$parentMenu as Record<string, unknown> | null
|
||||
const fn = menu?.isExact as ((url: string) => boolean) | undefined
|
||||
return fn ? fn(this.url) : this.url === '/'
|
||||
},
|
||||
matcherResult(): boolean {
|
||||
const menu = this.$parentMenu as Record<string, unknown> | null
|
||||
const fn = menu?.matcher as
|
||||
| ((url: string, route: Record<string, unknown>) => boolean)
|
||||
| undefined
|
||||
return fn ? fn(this.url, this.route) : false
|
||||
},
|
||||
level(): number {
|
||||
return this.parents.length
|
||||
},
|
||||
bindings(): Record<string, unknown> {
|
||||
const tag = this.resolvedLinkTag
|
||||
if (tag === 'router-link' || tag === 'nuxt-link') {
|
||||
// Support { name, params } objects for vue-router
|
||||
if (this.route?.name && this.route?.params && !this.route?.path) {
|
||||
return { to: { name: this.route.name, params: this.route.params } }
|
||||
}
|
||||
return { to: this.url }
|
||||
}
|
||||
if (tag === 'a') {
|
||||
return { href: this.url }
|
||||
}
|
||||
// Custom component — pass to
|
||||
return { to: this.url }
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.clickOutsideHandler = (e: Event) => {
|
||||
if (this.showSubmenu && !this.$el.contains(e.target as Node)) {
|
||||
this.showSubmenu = false
|
||||
}
|
||||
}
|
||||
document.addEventListener('click', this.clickOutsideHandler, true)
|
||||
},
|
||||
/* v8 ignore next 4 -- Vue 2 lifecycle hook */
|
||||
// eslint-disable-next-line vue/no-deprecated-destroyed-lifecycle
|
||||
beforeDestroy() {
|
||||
this.cleanup()
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.cleanup()
|
||||
},
|
||||
methods: {
|
||||
cleanup() {
|
||||
if (this.clickOutsideHandler) {
|
||||
document.removeEventListener('click', this.clickOutsideHandler, true)
|
||||
}
|
||||
},
|
||||
handleClick(event: Event) {
|
||||
this.$emit('click', event, this.route)
|
||||
const menu = this.$parentMenu as Record<string, unknown> | null
|
||||
if (menu && typeof menu.handleNavigate === 'function') {
|
||||
;(menu.handleNavigate as () => void)()
|
||||
}
|
||||
},
|
||||
},
|
||||
/* v8 ignore stop */
|
||||
})
|
||||
</script>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 5.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.4 KiB |
4
packages/ui/src/components/OsMenu/index.ts
Normal file
4
packages/ui/src/components/OsMenu/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export { default as OsMenu } from './OsMenu.vue'
|
||||
export { default as OsMenuItem } from './OsMenuItem.vue'
|
||||
export { menuItemVariants } from './menu.variants'
|
||||
export type { MenuItemVariants } from './menu.variants'
|
||||
26
packages/ui/src/components/OsMenu/menu.variants.ts
Normal file
26
packages/ui/src/components/OsMenu/menu.variants.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { cva } from 'class-variance-authority'
|
||||
|
||||
import type { VariantProps } from 'class-variance-authority'
|
||||
|
||||
export const menuItemVariants = cva(
|
||||
// Base classes for menu item link
|
||||
[
|
||||
'os-menu-item-link',
|
||||
'block no-underline',
|
||||
'transition-colors duration-[80ms] ease-[cubic-bezier(0.25,0.46,0.45,0.94)]',
|
||||
],
|
||||
{
|
||||
variants: {
|
||||
level: {
|
||||
0: '',
|
||||
1: 'text-sm',
|
||||
2: 'text-sm',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
level: 0,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
export type MenuItemVariants = VariantProps<typeof menuItemVariants>
|
||||
@ -29,3 +29,4 @@ export {
|
||||
} from './OsBadge'
|
||||
export { OsNumber, numberVariants, type NumberVariants } from './OsNumber'
|
||||
export { OsModal, modalPanelVariants } from './OsModal'
|
||||
export { OsMenu, OsMenuItem, menuItemVariants, type MenuItemVariants } from './OsMenu'
|
||||
|
||||
@ -16,3 +16,89 @@
|
||||
@source "../ocelot/**/*.vue";
|
||||
@source "../**/*.ts";
|
||||
|
||||
/* OsMenu — component styles (not expressible as pure utility classes) */
|
||||
.os-menu {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: 1.3;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
ul.os-menu-list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.os-menu-item {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.os-menu-item-link {
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
color: var(--color-text-base, #4b4554);
|
||||
line-height: 1.3;
|
||||
text-decoration: none;
|
||||
padding: 8px 16px;
|
||||
border-left: 2px solid transparent;
|
||||
transition: color 80ms ease-out,
|
||||
background-color 80ms ease-out,
|
||||
border-left-color 80ms ease-out;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.os-menu-item-link:hover {
|
||||
color: var(--color-primary, #17b53f);
|
||||
}
|
||||
|
||||
.os-menu-item-link:focus-visible {
|
||||
color: var(--color-primary, #17b53f);
|
||||
outline: 1px dashed var(--color-primary, #17b53f);
|
||||
outline-offset: -1px;
|
||||
}
|
||||
|
||||
/* Active state via vue-router */
|
||||
.os-menu-item-link.router-link-active {
|
||||
color: var(--color-primary, #17b53f);
|
||||
}
|
||||
|
||||
.os-menu-item-link.router-link-exact-active {
|
||||
color: var(--color-primary, #17b53f);
|
||||
background-color: var(--color-background-soft, #faf9fa);
|
||||
border-left-color: var(--color-primary, #17b53f);
|
||||
}
|
||||
|
||||
/* Active state via matcher prop */
|
||||
.os-menu-item-link.os-menu-item--active {
|
||||
color: var(--color-primary, #17b53f);
|
||||
background-color: var(--color-background-soft, #faf9fa);
|
||||
border-left-color: var(--color-primary, #17b53f);
|
||||
}
|
||||
|
||||
/* Dropdown variant — compact padding, hover with border accent */
|
||||
.os-menu--dropdown .os-menu-item-link {
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.os-menu--dropdown .os-menu-item-link:hover {
|
||||
border-left-color: var(--color-primary, #17b53f);
|
||||
}
|
||||
|
||||
/* Nesting levels */
|
||||
.os-menu-item-level-1 .os-menu-item-link {
|
||||
font-size: 0.8rem;
|
||||
padding-left: 24px;
|
||||
}
|
||||
|
||||
.os-menu-item-level-2 .os-menu-item-link {
|
||||
font-size: 0.8rem;
|
||||
padding-left: 32px;
|
||||
}
|
||||
|
||||
ul.os-menu-item-submenu {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
|
||||
@ -97,14 +97,6 @@
|
||||
padding: $space-x-small $space-small;
|
||||
box-shadow: $box-shadow-x-large;
|
||||
|
||||
nav {
|
||||
margin-left: -$space-small;
|
||||
margin-right: -$space-small;
|
||||
|
||||
a {
|
||||
padding-left: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.popover-arrow {
|
||||
|
||||
@ -87,34 +87,34 @@ describe('AvatarMenu.vue', () => {
|
||||
describe('role user', () => {
|
||||
it('displays a link to user profile', () => {
|
||||
const profileLink = wrapper
|
||||
.findAll('.ds-menu-item span')
|
||||
.findAll('.os-menu-item span')
|
||||
.at(wrapper.vm.routes.findIndex((route) => route.path === '/profile/u343/matt'))
|
||||
expect(profileLink.exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('displays a link to "Groups"', () => {
|
||||
const profileLink = wrapper
|
||||
.findAll('.ds-menu-item span')
|
||||
.findAll('.os-menu-item span')
|
||||
.at(wrapper.vm.routes.findIndex((route) => route.path === '/groups'))
|
||||
expect(profileLink.exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('displays a link to the notifications page', () => {
|
||||
const notificationsLink = wrapper
|
||||
.findAll('.ds-menu-item span')
|
||||
.findAll('.os-menu-item span')
|
||||
.at(wrapper.vm.routes.findIndex((route) => route.path === '/notifications'))
|
||||
expect(notificationsLink.exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('displays a link to the settings page', () => {
|
||||
const settingsLink = wrapper
|
||||
.findAll('.ds-menu-item span')
|
||||
.findAll('.os-menu-item span')
|
||||
.at(wrapper.vm.routes.findIndex((route) => route.path === '/settings'))
|
||||
expect(settingsLink.exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('displays a total of 6 links', () => {
|
||||
const allLinks = wrapper.findAll('.ds-menu-item')
|
||||
const allLinks = wrapper.findAll('.os-menu-item')
|
||||
expect(allLinks).toHaveLength(6)
|
||||
})
|
||||
})
|
||||
@ -134,13 +134,13 @@ describe('AvatarMenu.vue', () => {
|
||||
|
||||
it('displays a link to moderation page', () => {
|
||||
const moderationLink = wrapper
|
||||
.findAll('.ds-menu-item span')
|
||||
.findAll('.os-menu-item span')
|
||||
.at(wrapper.vm.routes.findIndex((route) => route.path === '/moderation'))
|
||||
expect(moderationLink.exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('displays a total of 7 links', () => {
|
||||
const allLinks = wrapper.findAll('.ds-menu-item')
|
||||
const allLinks = wrapper.findAll('.os-menu-item')
|
||||
expect(allLinks).toHaveLength(7)
|
||||
})
|
||||
})
|
||||
@ -160,13 +160,13 @@ describe('AvatarMenu.vue', () => {
|
||||
|
||||
it('displays a link to admin page', () => {
|
||||
const adminLink = wrapper
|
||||
.findAll('.ds-menu-item span')
|
||||
.findAll('.os-menu-item span')
|
||||
.at(wrapper.vm.routes.findIndex((route) => route.path === '/admin'))
|
||||
expect(adminLink.exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('displays a total of 8 links', () => {
|
||||
const allLinks = wrapper.findAll('.ds-menu-item')
|
||||
const allLinks = wrapper.findAll('.os-menu-item')
|
||||
expect(allLinks).toHaveLength(8)
|
||||
})
|
||||
})
|
||||
|
||||
@ -33,21 +33,21 @@
|
||||
</p>
|
||||
</template>
|
||||
<hr />
|
||||
<ds-menu :routes="routes" :matcher="matcher">
|
||||
<ds-menu-item
|
||||
slot="menuitem"
|
||||
slot-scope="item"
|
||||
:route="item.route"
|
||||
:parents="item.parents"
|
||||
@click.native="
|
||||
closeMenu(false)
|
||||
$emit('toggle-Mobile-Menu-view')
|
||||
"
|
||||
>
|
||||
<os-icon :icon="item.route.icon" />
|
||||
{{ item.route.name }}
|
||||
</ds-menu-item>
|
||||
</ds-menu>
|
||||
<os-menu dropdown :routes="routes" :matcher="matcher" link-tag="router-link">
|
||||
<template #menuitem="item">
|
||||
<os-menu-item
|
||||
:route="item.route"
|
||||
:parents="item.parents"
|
||||
@click.native="
|
||||
closeMenu(false)
|
||||
$emit('toggle-Mobile-Menu-view')
|
||||
"
|
||||
>
|
||||
<os-icon :icon="item.route.icon" />
|
||||
{{ item.route.name }}
|
||||
</os-menu-item>
|
||||
</template>
|
||||
</os-menu>
|
||||
<hr />
|
||||
<nuxt-link class="logout-link" :to="{ name: 'logout' }">
|
||||
<os-icon :icon="icons.signOut" />
|
||||
@ -59,7 +59,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { OsIcon } from '@ocelot-social/ui'
|
||||
import { OsIcon, OsMenu, OsMenuItem } from '@ocelot-social/ui'
|
||||
import { iconRegistry } from '~/utils/iconRegistry'
|
||||
import { mapGetters } from 'vuex'
|
||||
import Dropdown from '~/components/Dropdown'
|
||||
@ -69,6 +69,8 @@ export default {
|
||||
components: {
|
||||
Dropdown,
|
||||
OsIcon,
|
||||
OsMenu,
|
||||
OsMenuItem,
|
||||
ProfileAvatar,
|
||||
},
|
||||
props: {
|
||||
|
||||
@ -83,7 +83,7 @@ describe('ContentMenu.vue - Group', () => {
|
||||
},
|
||||
})
|
||||
expect(
|
||||
wrapper.findAll('.ds-menu-item').filter((item) => item.text() === 'post.menu.groupPin'),
|
||||
wrapper.findAll('.os-menu-item').filter((item) => item.text() === 'post.menu.groupPin'),
|
||||
).toHaveLength(0)
|
||||
})
|
||||
|
||||
@ -100,7 +100,7 @@ describe('ContentMenu.vue - Group', () => {
|
||||
},
|
||||
})
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'post.menu.groupUnpin')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -142,7 +142,7 @@ describe('ContentMenu.vue - Group', () => {
|
||||
},
|
||||
})
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'post.menu.groupPin')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -174,7 +174,7 @@ describe('ContentMenu.vue - Group', () => {
|
||||
},
|
||||
})
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'post.menu.groupUnpin')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -210,7 +210,7 @@ describe('ContentMenu.vue - Group', () => {
|
||||
},
|
||||
})
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'post.menu.groupPin')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -242,7 +242,7 @@ describe('ContentMenu.vue - Group', () => {
|
||||
},
|
||||
})
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'post.menu.groupUnpin')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -286,7 +286,7 @@ describe('ContentMenu.vue - Group', () => {
|
||||
},
|
||||
})
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'post.menu.groupPin')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -318,7 +318,7 @@ describe('ContentMenu.vue - Group', () => {
|
||||
},
|
||||
})
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'post.menu.groupUnpin')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -354,7 +354,7 @@ describe('ContentMenu.vue - Group', () => {
|
||||
},
|
||||
})
|
||||
expect(
|
||||
wrapper.findAll('.ds-menu-item').filter((item) => item.text() === 'post.menu.groupPin')
|
||||
wrapper.findAll('.os-menu-item').filter((item) => item.text() === 'post.menu.groupPin')
|
||||
.length,
|
||||
).toEqual(0)
|
||||
})
|
||||
@ -373,7 +373,7 @@ describe('ContentMenu.vue - Group', () => {
|
||||
},
|
||||
})
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'post.menu.groupUnpin')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -417,7 +417,7 @@ describe('ContentMenu.vue - Group', () => {
|
||||
},
|
||||
})
|
||||
expect(
|
||||
wrapper.findAll('.ds-menu-item').filter((item) => item.text() === 'post.menu.groupPin'),
|
||||
wrapper.findAll('.os-menu-item').filter((item) => item.text() === 'post.menu.groupPin'),
|
||||
).toHaveLength(0)
|
||||
})
|
||||
|
||||
@ -434,7 +434,7 @@ describe('ContentMenu.vue - Group', () => {
|
||||
},
|
||||
})
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'post.menu.groupUnpin')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -476,7 +476,7 @@ describe('ContentMenu.vue - Group', () => {
|
||||
},
|
||||
})
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'post.menu.groupPin')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -508,7 +508,7 @@ describe('ContentMenu.vue - Group', () => {
|
||||
},
|
||||
})
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'post.menu.groupUnpin')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -544,7 +544,7 @@ describe('ContentMenu.vue - Group', () => {
|
||||
},
|
||||
})
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'post.menu.groupPin')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -576,7 +576,7 @@ describe('ContentMenu.vue - Group', () => {
|
||||
},
|
||||
})
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'post.menu.groupUnpin')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -620,7 +620,7 @@ describe('ContentMenu.vue - Group', () => {
|
||||
},
|
||||
})
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'post.menu.groupPin')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -652,7 +652,7 @@ describe('ContentMenu.vue - Group', () => {
|
||||
},
|
||||
})
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'post.menu.groupUnpin')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -688,7 +688,7 @@ describe('ContentMenu.vue - Group', () => {
|
||||
},
|
||||
})
|
||||
expect(
|
||||
wrapper.findAll('.ds-menu-item').filter((item) => item.text() === 'post.menu.groupPin')
|
||||
wrapper.findAll('.os-menu-item').filter((item) => item.text() === 'post.menu.groupPin')
|
||||
.length,
|
||||
).toEqual(0)
|
||||
})
|
||||
@ -707,7 +707,7 @@ describe('ContentMenu.vue - Group', () => {
|
||||
},
|
||||
})
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'post.menu.groupUnpin')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -751,7 +751,7 @@ describe('ContentMenu.vue - Group', () => {
|
||||
},
|
||||
})
|
||||
expect(
|
||||
wrapper.findAll('.ds-menu-item').filter((item) => item.text() === 'post.menu.groupPin'),
|
||||
wrapper.findAll('.os-menu-item').filter((item) => item.text() === 'post.menu.groupPin'),
|
||||
).toHaveLength(0)
|
||||
})
|
||||
|
||||
@ -768,7 +768,7 @@ describe('ContentMenu.vue - Group', () => {
|
||||
},
|
||||
})
|
||||
expect(
|
||||
wrapper.findAll('.ds-menu-item').filter((item) => item.text() === 'post.menu.groupUnpin'),
|
||||
wrapper.findAll('.os-menu-item').filter((item) => item.text() === 'post.menu.groupUnpin'),
|
||||
).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
@ -794,7 +794,7 @@ describe('ContentMenu.vue - Group', () => {
|
||||
},
|
||||
})
|
||||
expect(
|
||||
wrapper.findAll('.ds-menu-item').filter((item) => item.text() === 'post.menu.groupPin'),
|
||||
wrapper.findAll('.os-menu-item').filter((item) => item.text() === 'post.menu.groupPin'),
|
||||
).toHaveLength(0)
|
||||
})
|
||||
|
||||
@ -812,7 +812,7 @@ describe('ContentMenu.vue - Group', () => {
|
||||
},
|
||||
})
|
||||
expect(
|
||||
wrapper.findAll('.ds-menu-item').filter((item) => item.text() === 'post.menu.groupUnpin'),
|
||||
wrapper.findAll('.os-menu-item').filter((item) => item.text() === 'post.menu.groupUnpin'),
|
||||
).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
||||
@ -11,9 +11,6 @@ localVue.use(VTooltip)
|
||||
localVue.use(Vuex)
|
||||
|
||||
const stubs = {
|
||||
'router-link': {
|
||||
template: '<span><slot /></span>',
|
||||
},
|
||||
'confirm-modal': { template: '<div class="confirm-modal-stub" />' },
|
||||
'report-modal': { template: '<div class="report-modal-stub" />' },
|
||||
}
|
||||
@ -77,19 +74,15 @@ describe('ContentMenu.vue', () => {
|
||||
})
|
||||
|
||||
it('can edit the contribution', () => {
|
||||
expect(
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.filter((item) => item.text() === 'post.menu.edit')
|
||||
.at(0)
|
||||
.find('span.ds-menu-item-link')
|
||||
.attributes('to'),
|
||||
).toBe('/post-edit-id')
|
||||
const editItem = wrapper
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'post.menu.edit')
|
||||
expect(editItem).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('can delete the contribution', () => {
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'post.menu.delete')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -110,10 +103,10 @@ describe('ContentMenu.vue', () => {
|
||||
},
|
||||
})
|
||||
expect(
|
||||
wrapper.findAll('.ds-menu-item').filter((item) => item.text() === 'post.menu.push'),
|
||||
wrapper.findAll('.os-menu-item').filter((item) => item.text() === 'post.menu.push'),
|
||||
).toHaveLength(1)
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'post.menu.push')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -140,7 +133,7 @@ describe('ContentMenu.vue', () => {
|
||||
},
|
||||
})
|
||||
expect(
|
||||
wrapper.findAll('.ds-menu-item').filter((item) => item.text() === 'post.menu.unpush'),
|
||||
wrapper.findAll('.os-menu-item').filter((item) => item.text() === 'post.menu.unpush'),
|
||||
).toHaveLength(0)
|
||||
})
|
||||
|
||||
@ -156,10 +149,10 @@ describe('ContentMenu.vue', () => {
|
||||
},
|
||||
})
|
||||
expect(
|
||||
wrapper.findAll('.ds-menu-item').filter((item) => item.text() === 'post.menu.unpush'),
|
||||
wrapper.findAll('.os-menu-item').filter((item) => item.text() === 'post.menu.unpush'),
|
||||
).toHaveLength(1)
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'post.menu.unpush')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -190,7 +183,7 @@ describe('ContentMenu.vue', () => {
|
||||
},
|
||||
})
|
||||
expect(
|
||||
wrapper.findAll('.ds-menu-item').filter((item) => item.text() === 'post.menu.pin'),
|
||||
wrapper.findAll('.os-menu-item').filter((item) => item.text() === 'post.menu.pin'),
|
||||
).toHaveLength(0)
|
||||
})
|
||||
|
||||
@ -204,7 +197,7 @@ describe('ContentMenu.vue', () => {
|
||||
},
|
||||
})
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'post.menu.unpin')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -235,7 +228,7 @@ describe('ContentMenu.vue', () => {
|
||||
},
|
||||
})
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'post.menu.pin')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -259,7 +252,7 @@ describe('ContentMenu.vue', () => {
|
||||
},
|
||||
})
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'post.menu.unpin')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -288,7 +281,7 @@ describe('ContentMenu.vue', () => {
|
||||
},
|
||||
})
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'post.menu.pin')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -321,7 +314,7 @@ describe('ContentMenu.vue', () => {
|
||||
},
|
||||
})
|
||||
expect(
|
||||
wrapper.findAll('.ds-menu-item').filter((item) => item.text() === 'post.menu.pin'),
|
||||
wrapper.findAll('.os-menu-item').filter((item) => item.text() === 'post.menu.pin'),
|
||||
).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
@ -341,7 +334,7 @@ describe('ContentMenu.vue', () => {
|
||||
},
|
||||
})
|
||||
expect(
|
||||
wrapper.findAll('.ds-menu-item').filter((item) => item.text() === 'post.menu.pin'),
|
||||
wrapper.findAll('.os-menu-item').filter((item) => item.text() === 'post.menu.pin'),
|
||||
).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
@ -365,7 +358,7 @@ describe('ContentMenu.vue', () => {
|
||||
},
|
||||
})
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'post.menu.pin')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -389,7 +382,7 @@ describe('ContentMenu.vue', () => {
|
||||
},
|
||||
})
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'post.menu.unpin')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -421,7 +414,7 @@ describe('ContentMenu.vue', () => {
|
||||
},
|
||||
})
|
||||
expect(
|
||||
wrapper.findAll('.ds-menu-item').filter((item) => item.text() === 'post.menu.pin'),
|
||||
wrapper.findAll('.os-menu-item').filter((item) => item.text() === 'post.menu.pin'),
|
||||
).toHaveLength(0)
|
||||
})
|
||||
|
||||
@ -435,7 +428,7 @@ describe('ContentMenu.vue', () => {
|
||||
},
|
||||
})
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'post.menu.unpin')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -463,7 +456,7 @@ describe('ContentMenu.vue', () => {
|
||||
},
|
||||
})
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'settings.deleteUserAccount.name')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -487,7 +480,7 @@ describe('ContentMenu.vue', () => {
|
||||
})
|
||||
expect(
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'settings.deleteUserAccount.name'),
|
||||
).toEqual({})
|
||||
})
|
||||
@ -507,7 +500,7 @@ describe('ContentMenu.vue', () => {
|
||||
})
|
||||
it('edit the comment', () => {
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'comment.menu.edit')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -515,7 +508,7 @@ describe('ContentMenu.vue', () => {
|
||||
})
|
||||
it('delete the comment', () => {
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'comment.menu.delete')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -536,7 +529,7 @@ describe('ContentMenu.vue', () => {
|
||||
})
|
||||
openModalSpy = jest.spyOn(wrapper.vm, 'openModal')
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'report.contribution.title')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -553,7 +546,7 @@ describe('ContentMenu.vue', () => {
|
||||
})
|
||||
openModalSpy = jest.spyOn(wrapper.vm, 'openModal')
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'report.comment.title')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -570,7 +563,7 @@ describe('ContentMenu.vue', () => {
|
||||
})
|
||||
openModalSpy = jest.spyOn(wrapper.vm, 'openModal')
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'report.user.title')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -587,7 +580,7 @@ describe('ContentMenu.vue', () => {
|
||||
})
|
||||
openModalSpy = jest.spyOn(wrapper.vm, 'openModal')
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'report.organization.title')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -609,7 +602,7 @@ describe('ContentMenu.vue', () => {
|
||||
})
|
||||
openModalSpy = jest.spyOn(wrapper.vm, 'openModal')
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'disable.contribution.title')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -627,7 +620,7 @@ describe('ContentMenu.vue', () => {
|
||||
})
|
||||
openModalSpy = jest.spyOn(wrapper.vm, 'openModal')
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'disable.comment.title')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -645,7 +638,7 @@ describe('ContentMenu.vue', () => {
|
||||
})
|
||||
openModalSpy = jest.spyOn(wrapper.vm, 'openModal')
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'disable.user.title')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -663,7 +656,7 @@ describe('ContentMenu.vue', () => {
|
||||
})
|
||||
openModalSpy = jest.spyOn(wrapper.vm, 'openModal')
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'disable.organization.title')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -681,7 +674,7 @@ describe('ContentMenu.vue', () => {
|
||||
})
|
||||
openModalSpy = jest.spyOn(wrapper.vm, 'openModal')
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'release.contribution.title')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -699,7 +692,7 @@ describe('ContentMenu.vue', () => {
|
||||
})
|
||||
openModalSpy = jest.spyOn(wrapper.vm, 'openModal')
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'release.comment.title')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -717,7 +710,7 @@ describe('ContentMenu.vue', () => {
|
||||
})
|
||||
openModalSpy = jest.spyOn(wrapper.vm, 'openModal')
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'release.user.title')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -735,7 +728,7 @@ describe('ContentMenu.vue', () => {
|
||||
})
|
||||
openModalSpy = jest.spyOn(wrapper.vm, 'openModal')
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'release.organization.title')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -754,14 +747,11 @@ describe('ContentMenu.vue', () => {
|
||||
id: 'd23a4265-f5f7-4e17-9f86-85f714b4b9f8',
|
||||
},
|
||||
})
|
||||
expect(
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.filter((item) => item.text() === 'settings.name')
|
||||
.at(0)
|
||||
.find('span.ds-menu-item-link')
|
||||
.attributes('to'),
|
||||
).toBe('/settings')
|
||||
const settingsItem = wrapper
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'settings.name')
|
||||
expect(settingsItem).toHaveLength(1)
|
||||
expect(settingsItem.at(0).find('.os-menu-item-link').attributes('href')).toBe('/settings')
|
||||
})
|
||||
|
||||
it('can mute other users', async () => {
|
||||
@ -774,7 +764,7 @@ describe('ContentMenu.vue', () => {
|
||||
},
|
||||
})
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'settings.muted-users.mute')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -798,7 +788,7 @@ describe('ContentMenu.vue', () => {
|
||||
},
|
||||
})
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'settings.muted-users.unmute')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -822,7 +812,7 @@ describe('ContentMenu.vue', () => {
|
||||
},
|
||||
})
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'post.menu.observe')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
@ -841,7 +831,7 @@ describe('ContentMenu.vue', () => {
|
||||
},
|
||||
})
|
||||
wrapper
|
||||
.findAll('.ds-menu-item')
|
||||
.findAll('.os-menu-item')
|
||||
.filter((item) => item.text() === 'post.menu.unobserve')
|
||||
.at(0)
|
||||
.trigger('click')
|
||||
|
||||
@ -20,18 +20,18 @@
|
||||
</template>
|
||||
<template #popover="{ toggleMenu }">
|
||||
<div class="content-menu-popover">
|
||||
<ds-menu :routes="routes">
|
||||
<os-menu dropdown :routes="routes">
|
||||
<template #menuitem="item">
|
||||
<ds-menu-item
|
||||
<os-menu-item
|
||||
:route="item.route"
|
||||
:parents="item.parents"
|
||||
@click.stop.prevent="openItem(item.route, toggleMenu)"
|
||||
>
|
||||
<os-icon :icon="item.route.icon" />
|
||||
{{ item.route.label }}
|
||||
</ds-menu-item>
|
||||
</os-menu-item>
|
||||
</template>
|
||||
</ds-menu>
|
||||
</os-menu>
|
||||
</div>
|
||||
</template>
|
||||
</dropdown>
|
||||
@ -51,7 +51,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { OsButton, OsIcon } from '@ocelot-social/ui'
|
||||
import { OsButton, OsIcon, OsMenu, OsMenuItem } from '@ocelot-social/ui'
|
||||
import { iconRegistry } from '~/utils/iconRegistry'
|
||||
import Dropdown from '~/components/Dropdown'
|
||||
import ConfirmModal from '~/components/Modal/ConfirmModal'
|
||||
@ -66,6 +66,8 @@ export default {
|
||||
Dropdown,
|
||||
OsButton,
|
||||
OsIcon,
|
||||
OsMenu,
|
||||
OsMenuItem,
|
||||
ReportModal,
|
||||
},
|
||||
mixins: [PinnedPostsMixin],
|
||||
@ -401,12 +403,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.content-menu-popover {
|
||||
nav {
|
||||
margin-top: -$space-xx-small;
|
||||
margin-bottom: -$space-xx-small;
|
||||
margin-left: -$space-x-small;
|
||||
margin-right: -$space-x-small;
|
||||
}
|
||||
.tooltip-inner.popover-inner .os-menu {
|
||||
margin: (-$space-xx-small) (-$space-small) !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -20,19 +20,18 @@
|
||||
</template>
|
||||
<template #popover="{ toggleMenu }">
|
||||
<div class="group-menu-popover">
|
||||
<ds-menu :routes="routes">
|
||||
<os-menu dropdown :routes="routes">
|
||||
<template #menuitem="item">
|
||||
{{ item.parents }}
|
||||
<ds-menu-item
|
||||
<os-menu-item
|
||||
:route="item.route"
|
||||
:parents="item.parents"
|
||||
@click.stop.prevent="openItem(item.route, toggleMenu)"
|
||||
>
|
||||
<os-icon :icon="item.route.icon" />
|
||||
{{ item.route.label }}
|
||||
</ds-menu-item>
|
||||
</os-menu-item>
|
||||
</template>
|
||||
</ds-menu>
|
||||
</os-menu>
|
||||
</div>
|
||||
</template>
|
||||
</dropdown>
|
||||
@ -40,7 +39,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { OsButton, OsIcon } from '@ocelot-social/ui'
|
||||
import { OsButton, OsIcon, OsMenu, OsMenuItem } from '@ocelot-social/ui'
|
||||
import { iconRegistry } from '~/utils/iconRegistry'
|
||||
import Dropdown from '~/components/Dropdown'
|
||||
|
||||
@ -50,6 +49,8 @@ export default {
|
||||
Dropdown,
|
||||
OsButton,
|
||||
OsIcon,
|
||||
OsMenu,
|
||||
OsMenuItem,
|
||||
},
|
||||
props: {
|
||||
usage: {
|
||||
@ -128,12 +129,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.group-menu-popover {
|
||||
nav {
|
||||
margin-top: -$space-xx-small;
|
||||
margin-bottom: -$space-xx-small;
|
||||
margin-left: -$space-x-small;
|
||||
margin-right: -$space-x-small;
|
||||
}
|
||||
.tooltip-inner.popover-inner .os-menu {
|
||||
margin: (-$space-xx-small) (-$space-small) !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -60,20 +60,16 @@ exports[`GroupContentMenu renders as groupProfile when I am the owner 1`] = `
|
||||
class="group-menu-popover"
|
||||
>
|
||||
<nav
|
||||
class="ds-menu"
|
||||
class="os-menu os-menu--dropdown"
|
||||
>
|
||||
<ul
|
||||
class="ds-menu-list"
|
||||
class="os-menu-list"
|
||||
>
|
||||
|
||||
|
||||
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<a
|
||||
class="ds-menu-item-link"
|
||||
exact="true"
|
||||
class="os-menu-item-link"
|
||||
href="/"
|
||||
>
|
||||
<span
|
||||
@ -94,16 +90,12 @@ exports[`GroupContentMenu renders as groupProfile when I am the owner 1`] = `
|
||||
group.contentMenu.muteGroup
|
||||
|
||||
</a>
|
||||
<!---->
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<a
|
||||
class="ds-menu-item-link"
|
||||
class="os-menu-item-link"
|
||||
href="/groups/edit/groupid"
|
||||
>
|
||||
<span
|
||||
@ -124,16 +116,12 @@ exports[`GroupContentMenu renders as groupProfile when I am the owner 1`] = `
|
||||
admin.settings.name
|
||||
|
||||
</a>
|
||||
<!---->
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<a
|
||||
class="ds-menu-item-link"
|
||||
class="os-menu-item-link"
|
||||
href="/groups/edit/groupid/invites"
|
||||
>
|
||||
<span
|
||||
@ -154,7 +142,6 @@ exports[`GroupContentMenu renders as groupProfile when I am the owner 1`] = `
|
||||
group.contentMenu.inviteLinks
|
||||
|
||||
</a>
|
||||
<!---->
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
@ -225,20 +212,16 @@ exports[`GroupContentMenu renders as groupProfile, muted 1`] = `
|
||||
class="group-menu-popover"
|
||||
>
|
||||
<nav
|
||||
class="ds-menu"
|
||||
class="os-menu os-menu--dropdown"
|
||||
>
|
||||
<ul
|
||||
class="ds-menu-list"
|
||||
class="os-menu-list"
|
||||
>
|
||||
|
||||
|
||||
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<a
|
||||
class="ds-menu-item-link"
|
||||
exact="true"
|
||||
class="os-menu-item-link"
|
||||
href="/"
|
||||
>
|
||||
<span
|
||||
@ -259,7 +242,6 @@ exports[`GroupContentMenu renders as groupProfile, muted 1`] = `
|
||||
group.contentMenu.unmuteGroup
|
||||
|
||||
</a>
|
||||
<!---->
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
@ -330,20 +312,16 @@ exports[`GroupContentMenu renders as groupProfile, not muted 1`] = `
|
||||
class="group-menu-popover"
|
||||
>
|
||||
<nav
|
||||
class="ds-menu"
|
||||
class="os-menu os-menu--dropdown"
|
||||
>
|
||||
<ul
|
||||
class="ds-menu-list"
|
||||
class="os-menu-list"
|
||||
>
|
||||
|
||||
|
||||
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<a
|
||||
class="ds-menu-item-link"
|
||||
exact="true"
|
||||
class="os-menu-item-link"
|
||||
href="/"
|
||||
>
|
||||
<span
|
||||
@ -364,7 +342,6 @@ exports[`GroupContentMenu renders as groupProfile, not muted 1`] = `
|
||||
group.contentMenu.muteGroup
|
||||
|
||||
</a>
|
||||
<!---->
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
@ -435,19 +412,16 @@ exports[`GroupContentMenu renders as groupTeaser 1`] = `
|
||||
class="group-menu-popover"
|
||||
>
|
||||
<nav
|
||||
class="ds-menu"
|
||||
class="os-menu os-menu--dropdown"
|
||||
>
|
||||
<ul
|
||||
class="ds-menu-list"
|
||||
class="os-menu-list"
|
||||
>
|
||||
|
||||
|
||||
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<a
|
||||
class="ds-menu-item-link"
|
||||
class="os-menu-item-link"
|
||||
href="/groups/groupid"
|
||||
>
|
||||
<span
|
||||
@ -468,7 +442,6 @@ exports[`GroupContentMenu renders as groupTeaser 1`] = `
|
||||
group.contentMenu.visitGroupPage
|
||||
|
||||
</a>
|
||||
<!---->
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
@ -14,23 +14,23 @@
|
||||
</a>
|
||||
</template>
|
||||
<template #popover="{ toggleMenu }">
|
||||
<ds-menu class="dropdown-menu-popover" :routes="filterOptions">
|
||||
<os-menu dropdown class="dropdown-menu-popover" :routes="filterOptions">
|
||||
<template #menuitem="item">
|
||||
<ds-menu-item
|
||||
<os-menu-item
|
||||
class="dropdown-menu-item"
|
||||
:route="item.route"
|
||||
:parents="item.parents"
|
||||
@click.stop.prevent="filter(item.route, toggleMenu)"
|
||||
>
|
||||
{{ item.route.label }}
|
||||
</ds-menu-item>
|
||||
</os-menu-item>
|
||||
</template>
|
||||
</ds-menu>
|
||||
</os-menu>
|
||||
</template>
|
||||
</dropdown>
|
||||
</template>
|
||||
<script>
|
||||
import { OsIcon } from '@ocelot-social/ui'
|
||||
import { OsIcon, OsMenu, OsMenuItem } from '@ocelot-social/ui'
|
||||
import { iconRegistry } from '~/utils/iconRegistry'
|
||||
import Dropdown from '~/components/Dropdown'
|
||||
|
||||
@ -38,6 +38,8 @@ export default {
|
||||
components: {
|
||||
Dropdown,
|
||||
OsIcon,
|
||||
OsMenu,
|
||||
OsMenuItem,
|
||||
},
|
||||
setup() {
|
||||
return { icons: iconRegistry }
|
||||
|
||||
@ -20,25 +20,25 @@
|
||||
</os-button>
|
||||
</template>
|
||||
<template #popover="{ toggleMenu }">
|
||||
<ds-menu class="locale-menu-popover" :matcher="matcher" :routes="routes">
|
||||
<os-menu dropdown class="locale-menu-popover" :matcher="matcher" :routes="routes">
|
||||
<template #menuitem="item">
|
||||
<ds-menu-item
|
||||
<os-menu-item
|
||||
class="locale-menu-item"
|
||||
:route="item.route"
|
||||
:parents="item.parents"
|
||||
@click.stop.prevent="changeLanguage(item.route.path, toggleMenu)"
|
||||
>
|
||||
{{ item.route.name }}
|
||||
</ds-menu-item>
|
||||
</os-menu-item>
|
||||
</template>
|
||||
</ds-menu>
|
||||
</os-menu>
|
||||
</template>
|
||||
</dropdown>
|
||||
</client-only>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { OsButton, OsIcon } from '@ocelot-social/ui'
|
||||
import { OsButton, OsIcon, OsMenu, OsMenuItem } from '@ocelot-social/ui'
|
||||
import { iconRegistry } from '~/utils/iconRegistry'
|
||||
import Dropdown from '~/components/Dropdown'
|
||||
import find from 'lodash/find'
|
||||
@ -52,6 +52,8 @@ export default {
|
||||
Dropdown,
|
||||
OsButton,
|
||||
OsIcon,
|
||||
OsMenu,
|
||||
OsMenuItem,
|
||||
},
|
||||
props: {
|
||||
placement: { type: String, default: 'bottom-start' },
|
||||
|
||||
@ -24,164 +24,109 @@ exports[`settings.vue given badges are disabled renders 1`] = `
|
||||
class="menu-container"
|
||||
>
|
||||
<nav
|
||||
class="ds-menu"
|
||||
class="os-menu"
|
||||
>
|
||||
<ul
|
||||
class="ds-menu-list"
|
||||
class="os-menu-list"
|
||||
>
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<a
|
||||
class="ds-menu-item-link"
|
||||
exact="true"
|
||||
class="os-menu-item-link"
|
||||
href="/settings"
|
||||
>
|
||||
|
||||
|
||||
|
||||
</a>
|
||||
<!---->
|
||||
to="/settings"
|
||||
/>
|
||||
</li>
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<a
|
||||
class="ds-menu-item-link"
|
||||
exact="true"
|
||||
class="os-menu-item-link"
|
||||
href="/settings/my-email-address"
|
||||
>
|
||||
|
||||
|
||||
|
||||
</a>
|
||||
<!---->
|
||||
to="/settings/my-email-address"
|
||||
/>
|
||||
</li>
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<a
|
||||
class="ds-menu-item-link"
|
||||
exact="true"
|
||||
class="os-menu-item-link"
|
||||
href="/settings/security"
|
||||
>
|
||||
|
||||
|
||||
|
||||
</a>
|
||||
<!---->
|
||||
to="/settings/security"
|
||||
/>
|
||||
</li>
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<a
|
||||
class="ds-menu-item-link"
|
||||
exact="true"
|
||||
class="os-menu-item-link"
|
||||
href="/settings/privacy"
|
||||
>
|
||||
|
||||
|
||||
|
||||
</a>
|
||||
<!---->
|
||||
to="/settings/privacy"
|
||||
/>
|
||||
</li>
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<a
|
||||
class="ds-menu-item-link"
|
||||
exact="true"
|
||||
class="os-menu-item-link"
|
||||
href="/settings/my-social-media"
|
||||
>
|
||||
|
||||
|
||||
|
||||
</a>
|
||||
<!---->
|
||||
to="/settings/my-social-media"
|
||||
/>
|
||||
</li>
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<a
|
||||
class="ds-menu-item-link"
|
||||
exact="true"
|
||||
class="os-menu-item-link"
|
||||
href="/settings/muted-users"
|
||||
>
|
||||
|
||||
|
||||
|
||||
</a>
|
||||
<!---->
|
||||
to="/settings/muted-users"
|
||||
/>
|
||||
</li>
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<a
|
||||
class="ds-menu-item-link"
|
||||
exact="true"
|
||||
class="os-menu-item-link"
|
||||
href="/settings/blocked-users"
|
||||
>
|
||||
|
||||
|
||||
|
||||
</a>
|
||||
<!---->
|
||||
to="/settings/blocked-users"
|
||||
/>
|
||||
</li>
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<a
|
||||
class="ds-menu-item-link"
|
||||
exact="true"
|
||||
class="os-menu-item-link"
|
||||
href="/settings/embeds"
|
||||
>
|
||||
|
||||
|
||||
|
||||
</a>
|
||||
<!---->
|
||||
to="/settings/embeds"
|
||||
/>
|
||||
</li>
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<a
|
||||
class="ds-menu-item-link"
|
||||
exact="true"
|
||||
class="os-menu-item-link"
|
||||
href="/settings/notifications"
|
||||
>
|
||||
|
||||
|
||||
|
||||
</a>
|
||||
<!---->
|
||||
to="/settings/notifications"
|
||||
/>
|
||||
</li>
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<a
|
||||
class="ds-menu-item-link"
|
||||
exact="true"
|
||||
class="os-menu-item-link"
|
||||
href="/settings/data-download"
|
||||
>
|
||||
|
||||
|
||||
|
||||
</a>
|
||||
<!---->
|
||||
to="/settings/data-download"
|
||||
/>
|
||||
</li>
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<a
|
||||
class="ds-menu-item-link"
|
||||
exact="true"
|
||||
class="os-menu-item-link"
|
||||
href="/settings/delete-account"
|
||||
>
|
||||
|
||||
|
||||
|
||||
</a>
|
||||
<!---->
|
||||
to="/settings/delete-account"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
@ -227,178 +172,118 @@ exports[`settings.vue given badges are enabled renders 1`] = `
|
||||
class="menu-container"
|
||||
>
|
||||
<nav
|
||||
class="ds-menu"
|
||||
class="os-menu"
|
||||
>
|
||||
<ul
|
||||
class="ds-menu-list"
|
||||
class="os-menu-list"
|
||||
>
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<a
|
||||
class="ds-menu-item-link"
|
||||
exact="true"
|
||||
class="os-menu-item-link"
|
||||
href="/settings"
|
||||
>
|
||||
|
||||
|
||||
|
||||
</a>
|
||||
<!---->
|
||||
to="/settings"
|
||||
/>
|
||||
</li>
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<a
|
||||
class="ds-menu-item-link"
|
||||
exact="true"
|
||||
class="os-menu-item-link"
|
||||
href="/settings/my-email-address"
|
||||
>
|
||||
|
||||
|
||||
|
||||
</a>
|
||||
<!---->
|
||||
to="/settings/my-email-address"
|
||||
/>
|
||||
</li>
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<a
|
||||
class="ds-menu-item-link"
|
||||
exact="true"
|
||||
class="os-menu-item-link"
|
||||
href="/settings/badges"
|
||||
>
|
||||
|
||||
|
||||
|
||||
</a>
|
||||
<!---->
|
||||
to="/settings/badges"
|
||||
/>
|
||||
</li>
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<a
|
||||
class="ds-menu-item-link"
|
||||
exact="true"
|
||||
class="os-menu-item-link"
|
||||
href="/settings/security"
|
||||
>
|
||||
|
||||
|
||||
|
||||
</a>
|
||||
<!---->
|
||||
to="/settings/security"
|
||||
/>
|
||||
</li>
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<a
|
||||
class="ds-menu-item-link"
|
||||
exact="true"
|
||||
class="os-menu-item-link"
|
||||
href="/settings/privacy"
|
||||
>
|
||||
|
||||
|
||||
|
||||
</a>
|
||||
<!---->
|
||||
to="/settings/privacy"
|
||||
/>
|
||||
</li>
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<a
|
||||
class="ds-menu-item-link"
|
||||
exact="true"
|
||||
class="os-menu-item-link"
|
||||
href="/settings/my-social-media"
|
||||
>
|
||||
|
||||
|
||||
|
||||
</a>
|
||||
<!---->
|
||||
to="/settings/my-social-media"
|
||||
/>
|
||||
</li>
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<a
|
||||
class="ds-menu-item-link"
|
||||
exact="true"
|
||||
class="os-menu-item-link"
|
||||
href="/settings/muted-users"
|
||||
>
|
||||
|
||||
|
||||
|
||||
</a>
|
||||
<!---->
|
||||
to="/settings/muted-users"
|
||||
/>
|
||||
</li>
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<a
|
||||
class="ds-menu-item-link"
|
||||
exact="true"
|
||||
class="os-menu-item-link"
|
||||
href="/settings/blocked-users"
|
||||
>
|
||||
|
||||
|
||||
|
||||
</a>
|
||||
<!---->
|
||||
to="/settings/blocked-users"
|
||||
/>
|
||||
</li>
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<a
|
||||
class="ds-menu-item-link"
|
||||
exact="true"
|
||||
class="os-menu-item-link"
|
||||
href="/settings/embeds"
|
||||
>
|
||||
|
||||
|
||||
|
||||
</a>
|
||||
<!---->
|
||||
to="/settings/embeds"
|
||||
/>
|
||||
</li>
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<a
|
||||
class="ds-menu-item-link"
|
||||
exact="true"
|
||||
class="os-menu-item-link"
|
||||
href="/settings/notifications"
|
||||
>
|
||||
|
||||
|
||||
|
||||
</a>
|
||||
<!---->
|
||||
to="/settings/notifications"
|
||||
/>
|
||||
</li>
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<a
|
||||
class="ds-menu-item-link"
|
||||
exact="true"
|
||||
class="os-menu-item-link"
|
||||
href="/settings/data-download"
|
||||
>
|
||||
|
||||
|
||||
|
||||
</a>
|
||||
<!---->
|
||||
to="/settings/data-download"
|
||||
/>
|
||||
</li>
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<a
|
||||
class="ds-menu-item-link"
|
||||
exact="true"
|
||||
class="os-menu-item-link"
|
||||
href="/settings/delete-account"
|
||||
>
|
||||
|
||||
|
||||
|
||||
</a>
|
||||
<!---->
|
||||
to="/settings/delete-account"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
<h1 class="ds-heading ds-heading-h1">{{ $t('admin.name') }}</h1>
|
||||
<div class="ds-flex ds-flex-gap-small admin-layout">
|
||||
<div class="admin-layout__sidebar">
|
||||
<ds-menu :routes="routes" :is-exact="() => true" />
|
||||
<os-menu :routes="routes" :is-exact="() => true" link-tag="router-link" />
|
||||
</div>
|
||||
<div class="admin-layout__main">
|
||||
<transition name="slide-up" appear>
|
||||
@ -15,7 +15,12 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { OsMenu } from '@ocelot-social/ui'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
OsMenu,
|
||||
},
|
||||
middleware: ['isAdmin'],
|
||||
computed: {
|
||||
routes() {
|
||||
|
||||
@ -120,21 +120,17 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a close
|
||||
class="group-menu-popover"
|
||||
>
|
||||
<nav
|
||||
class="ds-menu"
|
||||
class="os-menu os-menu--dropdown"
|
||||
>
|
||||
<ul
|
||||
class="ds-menu-list"
|
||||
class="os-menu-list"
|
||||
>
|
||||
|
||||
|
||||
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<router-link-stub
|
||||
class="ds-menu-item-link"
|
||||
exact="true"
|
||||
to="/"
|
||||
<a
|
||||
class="os-menu-item-link"
|
||||
href="/"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@ -153,18 +149,14 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a close
|
||||
|
||||
group.contentMenu.unmuteGroup
|
||||
|
||||
</router-link-stub>
|
||||
<!---->
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<router-link-stub
|
||||
class="ds-menu-item-link"
|
||||
to="/groups/edit/g1"
|
||||
<a
|
||||
class="os-menu-item-link"
|
||||
href="/groups/edit/g1"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@ -183,18 +175,14 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a close
|
||||
|
||||
admin.settings.name
|
||||
|
||||
</router-link-stub>
|
||||
<!---->
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<router-link-stub
|
||||
class="ds-menu-item-link"
|
||||
to="/groups/edit/g1/invites"
|
||||
<a
|
||||
class="os-menu-item-link"
|
||||
href="/groups/edit/g1/invites"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@ -213,8 +201,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a close
|
||||
|
||||
group.contentMenu.inviteLinks
|
||||
|
||||
</router-link-stub>
|
||||
<!---->
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
@ -2241,21 +2228,17 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a close
|
||||
class="group-menu-popover"
|
||||
>
|
||||
<nav
|
||||
class="ds-menu"
|
||||
class="os-menu os-menu--dropdown"
|
||||
>
|
||||
<ul
|
||||
class="ds-menu-list"
|
||||
class="os-menu-list"
|
||||
>
|
||||
|
||||
|
||||
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<router-link-stub
|
||||
class="ds-menu-item-link"
|
||||
exact="true"
|
||||
to="/"
|
||||
<a
|
||||
class="os-menu-item-link"
|
||||
href="/"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@ -2274,8 +2257,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a close
|
||||
|
||||
group.contentMenu.unmuteGroup
|
||||
|
||||
</router-link-stub>
|
||||
<!---->
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
@ -3292,21 +3274,17 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
class="group-menu-popover"
|
||||
>
|
||||
<nav
|
||||
class="ds-menu"
|
||||
class="os-menu os-menu--dropdown"
|
||||
>
|
||||
<ul
|
||||
class="ds-menu-list"
|
||||
class="os-menu-list"
|
||||
>
|
||||
|
||||
|
||||
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<router-link-stub
|
||||
class="ds-menu-item-link"
|
||||
exact="true"
|
||||
to="/"
|
||||
<a
|
||||
class="os-menu-item-link"
|
||||
href="/"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@ -3325,18 +3303,14 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
|
||||
group.contentMenu.muteGroup
|
||||
|
||||
</router-link-stub>
|
||||
<!---->
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<router-link-stub
|
||||
class="ds-menu-item-link"
|
||||
to="/groups/edit/g2"
|
||||
<a
|
||||
class="os-menu-item-link"
|
||||
href="/groups/edit/g2"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@ -3355,18 +3329,14 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
|
||||
admin.settings.name
|
||||
|
||||
</router-link-stub>
|
||||
<!---->
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<router-link-stub
|
||||
class="ds-menu-item-link"
|
||||
to="/groups/edit/g2/invites"
|
||||
<a
|
||||
class="os-menu-item-link"
|
||||
href="/groups/edit/g2/invites"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@ -3385,8 +3355,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
|
||||
group.contentMenu.inviteLinks
|
||||
|
||||
</router-link-stub>
|
||||
<!---->
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
@ -5933,21 +5902,17 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
class="group-menu-popover"
|
||||
>
|
||||
<nav
|
||||
class="ds-menu"
|
||||
class="os-menu os-menu--dropdown"
|
||||
>
|
||||
<ul
|
||||
class="ds-menu-list"
|
||||
class="os-menu-list"
|
||||
>
|
||||
|
||||
|
||||
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<router-link-stub
|
||||
class="ds-menu-item-link"
|
||||
exact="true"
|
||||
to="/"
|
||||
<a
|
||||
class="os-menu-item-link"
|
||||
href="/"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@ -5966,8 +5931,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
|
||||
group.contentMenu.muteGroup
|
||||
|
||||
</router-link-stub>
|
||||
<!---->
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
@ -6916,21 +6880,17 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a hidde
|
||||
class="group-menu-popover"
|
||||
>
|
||||
<nav
|
||||
class="ds-menu"
|
||||
class="os-menu os-menu--dropdown"
|
||||
>
|
||||
<ul
|
||||
class="ds-menu-list"
|
||||
class="os-menu-list"
|
||||
>
|
||||
|
||||
|
||||
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<router-link-stub
|
||||
class="ds-menu-item-link"
|
||||
exact="true"
|
||||
to="/"
|
||||
<a
|
||||
class="os-menu-item-link"
|
||||
href="/"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@ -6949,18 +6909,14 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a hidde
|
||||
|
||||
group.contentMenu.muteGroup
|
||||
|
||||
</router-link-stub>
|
||||
<!---->
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<router-link-stub
|
||||
class="ds-menu-item-link"
|
||||
to="/groups/edit/g0"
|
||||
<a
|
||||
class="os-menu-item-link"
|
||||
href="/groups/edit/g0"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@ -6979,18 +6935,14 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a hidde
|
||||
|
||||
admin.settings.name
|
||||
|
||||
</router-link-stub>
|
||||
<!---->
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<router-link-stub
|
||||
class="ds-menu-item-link"
|
||||
to="/groups/edit/g0/invites"
|
||||
<a
|
||||
class="os-menu-item-link"
|
||||
href="/groups/edit/g0/invites"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@ -7009,8 +6961,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a hidde
|
||||
|
||||
group.contentMenu.inviteLinks
|
||||
|
||||
</router-link-stub>
|
||||
<!---->
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
@ -8028,21 +7979,17 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a hidde
|
||||
class="group-menu-popover"
|
||||
>
|
||||
<nav
|
||||
class="ds-menu"
|
||||
class="os-menu os-menu--dropdown"
|
||||
>
|
||||
<ul
|
||||
class="ds-menu-list"
|
||||
class="os-menu-list"
|
||||
>
|
||||
|
||||
|
||||
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<router-link-stub
|
||||
class="ds-menu-item-link"
|
||||
exact="true"
|
||||
to="/"
|
||||
<a
|
||||
class="os-menu-item-link"
|
||||
href="/"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@ -8061,8 +8008,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a hidde
|
||||
|
||||
group.contentMenu.muteGroup
|
||||
|
||||
</router-link-stub>
|
||||
<!---->
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
<div class="ds-my-large"></div>
|
||||
<div class="ds-flex ds-flex-gap-small group-edit-layout">
|
||||
<div class="group-edit-layout__sidebar">
|
||||
<ds-menu :routes="routes" :is-exact="() => true" />
|
||||
<os-menu :routes="routes" :is-exact="() => true" link-tag="router-link" />
|
||||
</div>
|
||||
<div class="group-edit-layout__main">
|
||||
<transition name="slide-up" appear>
|
||||
@ -24,10 +24,14 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { OsMenu } from '@ocelot-social/ui'
|
||||
import { groupQuery } from '~/graphql/groups.js'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
OsMenu,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
group: {},
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
</h1>
|
||||
<div class="ds-flex ds-flex-gap-small moderation-layout">
|
||||
<div class="moderation-layout__sidebar">
|
||||
<ds-menu :routes="routes" />
|
||||
<os-menu :routes="routes" link-tag="router-link" />
|
||||
</div>
|
||||
<div class="moderation-layout__main">
|
||||
<transition name="slide-up" appear>
|
||||
@ -17,7 +17,12 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { OsMenu } from '@ocelot-social/ui'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
OsMenu,
|
||||
},
|
||||
middleware: ['isModerator'],
|
||||
computed: {
|
||||
routes() {
|
||||
|
||||
@ -176,7 +176,7 @@
|
||||
</os-card>
|
||||
</div>
|
||||
<div class="post-detail-layout__sidebar" style="flex: 0 0 200px; width: 200px">
|
||||
<ds-menu :routes="routes" class="post-side-navigation" />
|
||||
<os-menu :routes="routes" class="post-side-navigation" link-tag="router-link" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -184,7 +184,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { OsButton, OsCard, OsIcon } from '@ocelot-social/ui'
|
||||
import { OsButton, OsCard, OsIcon, OsMenu } from '@ocelot-social/ui'
|
||||
import { iconRegistry } from '~/utils/iconRegistry'
|
||||
import ContentViewer from '~/components/Editor/ContentViewer'
|
||||
import CommentForm from '~/components/CommentForm/CommentForm'
|
||||
@ -224,6 +224,7 @@ export default {
|
||||
OsCard,
|
||||
OsButton,
|
||||
OsIcon,
|
||||
OsMenu,
|
||||
CommentForm,
|
||||
CommentList,
|
||||
ContentMenu,
|
||||
|
||||
@ -14,16 +14,16 @@
|
||||
<div class="ds-my-large"></div>
|
||||
<div class="ds-flex ds-flex-gap-small post-create-layout">
|
||||
<div class="post-create-layout__sidebar">
|
||||
<ds-menu :routes="routes">
|
||||
<ds-menu-item
|
||||
<os-menu :routes="routes" link-tag="router-link">
|
||||
<os-menu-item
|
||||
@click.prevent="switchPostType($event, item)"
|
||||
slot="menuitem"
|
||||
slot-scope="item"
|
||||
:route="item.route"
|
||||
>
|
||||
{{ item.route.name }}
|
||||
</ds-menu-item>
|
||||
</ds-menu>
|
||||
</os-menu-item>
|
||||
</os-menu>
|
||||
</div>
|
||||
<div class="post-create-layout__main">
|
||||
<transition name="slide-up" appear>
|
||||
@ -35,12 +35,15 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { OsMenu, OsMenuItem } from '@ocelot-social/ui'
|
||||
import { groupQuery } from '~/graphql/groups'
|
||||
import ContributionForm from '~/components/ContributionForm/ContributionForm'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ContributionForm,
|
||||
OsMenu,
|
||||
OsMenuItem,
|
||||
},
|
||||
data() {
|
||||
const { groupId = null } = this.$route.query
|
||||
|
||||
@ -89,18 +89,17 @@ exports[`ProfileSlug given an authenticated user given another profile user and
|
||||
class="content-menu-popover"
|
||||
>
|
||||
<nav
|
||||
class="ds-menu"
|
||||
class="os-menu os-menu--dropdown"
|
||||
>
|
||||
<ul
|
||||
class="ds-menu-list"
|
||||
class="os-menu-list"
|
||||
>
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<router-link-stub
|
||||
class="ds-menu-item-link"
|
||||
exact="true"
|
||||
to="/"
|
||||
<a
|
||||
class="os-menu-item-link"
|
||||
href="/"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@ -119,16 +118,14 @@ exports[`ProfileSlug given an authenticated user given another profile user and
|
||||
|
||||
report.user.title
|
||||
|
||||
</router-link-stub>
|
||||
<!---->
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<router-link-stub
|
||||
class="ds-menu-item-link"
|
||||
exact="true"
|
||||
to="/"
|
||||
<a
|
||||
class="os-menu-item-link"
|
||||
href="/"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@ -147,16 +144,14 @@ exports[`ProfileSlug given an authenticated user given another profile user and
|
||||
|
||||
disable.user.title
|
||||
|
||||
</router-link-stub>
|
||||
<!---->
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<router-link-stub
|
||||
class="ds-menu-item-link"
|
||||
exact="true"
|
||||
to="/"
|
||||
<a
|
||||
class="os-menu-item-link"
|
||||
href="/"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@ -175,16 +170,14 @@ exports[`ProfileSlug given an authenticated user given another profile user and
|
||||
|
||||
settings.muted-users.mute
|
||||
|
||||
</router-link-stub>
|
||||
<!---->
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<router-link-stub
|
||||
class="ds-menu-item-link"
|
||||
exact="true"
|
||||
to="/"
|
||||
<a
|
||||
class="os-menu-item-link"
|
||||
href="/"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@ -203,8 +196,7 @@ exports[`ProfileSlug given an authenticated user given another profile user and
|
||||
|
||||
settings.blocked-users.block
|
||||
|
||||
</router-link-stub>
|
||||
<!---->
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
@ -784,18 +776,17 @@ exports[`ProfileSlug given an authenticated user given another profile user and
|
||||
class="content-menu-popover"
|
||||
>
|
||||
<nav
|
||||
class="ds-menu"
|
||||
class="os-menu os-menu--dropdown"
|
||||
>
|
||||
<ul
|
||||
class="ds-menu-list"
|
||||
class="os-menu-list"
|
||||
>
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<router-link-stub
|
||||
class="ds-menu-item-link"
|
||||
exact="true"
|
||||
to="/"
|
||||
<a
|
||||
class="os-menu-item-link"
|
||||
href="/"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@ -814,16 +805,14 @@ exports[`ProfileSlug given an authenticated user given another profile user and
|
||||
|
||||
report.user.title
|
||||
|
||||
</router-link-stub>
|
||||
<!---->
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<router-link-stub
|
||||
class="ds-menu-item-link"
|
||||
exact="true"
|
||||
to="/"
|
||||
<a
|
||||
class="os-menu-item-link"
|
||||
href="/"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@ -842,16 +831,14 @@ exports[`ProfileSlug given an authenticated user given another profile user and
|
||||
|
||||
disable.user.title
|
||||
|
||||
</router-link-stub>
|
||||
<!---->
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<router-link-stub
|
||||
class="ds-menu-item-link"
|
||||
exact="true"
|
||||
to="/"
|
||||
<a
|
||||
class="os-menu-item-link"
|
||||
href="/"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@ -870,16 +857,14 @@ exports[`ProfileSlug given an authenticated user given another profile user and
|
||||
|
||||
settings.muted-users.mute
|
||||
|
||||
</router-link-stub>
|
||||
<!---->
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<router-link-stub
|
||||
class="ds-menu-item-link"
|
||||
exact="true"
|
||||
to="/"
|
||||
<a
|
||||
class="os-menu-item-link"
|
||||
href="/"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@ -898,8 +883,7 @@ exports[`ProfileSlug given an authenticated user given another profile user and
|
||||
|
||||
settings.blocked-users.block
|
||||
|
||||
</router-link-stub>
|
||||
<!---->
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
@ -1546,17 +1530,17 @@ exports[`ProfileSlug given an authenticated user given the logged in user as pro
|
||||
class="content-menu-popover"
|
||||
>
|
||||
<nav
|
||||
class="ds-menu"
|
||||
class="os-menu os-menu--dropdown"
|
||||
>
|
||||
<ul
|
||||
class="ds-menu-list"
|
||||
class="os-menu-list"
|
||||
>
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<router-link-stub
|
||||
class="ds-menu-item-link"
|
||||
to="/settings"
|
||||
<a
|
||||
class="os-menu-item-link"
|
||||
href="/settings"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@ -1575,8 +1559,7 @@ exports[`ProfileSlug given an authenticated user given the logged in user as pro
|
||||
|
||||
settings.name
|
||||
|
||||
</router-link-stub>
|
||||
<!---->
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
@ -2135,17 +2118,17 @@ exports[`ProfileSlug given an authenticated user given the logged in user as pro
|
||||
class="content-menu-popover"
|
||||
>
|
||||
<nav
|
||||
class="ds-menu"
|
||||
class="os-menu os-menu--dropdown"
|
||||
>
|
||||
<ul
|
||||
class="ds-menu-list"
|
||||
class="os-menu-list"
|
||||
>
|
||||
<li
|
||||
class="ds-menu-item ds-menu-item-level-0"
|
||||
class="os-menu-item os-menu-item-level-0"
|
||||
>
|
||||
<router-link-stub
|
||||
class="ds-menu-item-link"
|
||||
to="/settings"
|
||||
<a
|
||||
class="os-menu-item-link"
|
||||
href="/settings"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
@ -2164,8 +2147,7 @@ exports[`ProfileSlug given an authenticated user given the logged in user as pro
|
||||
|
||||
settings.name
|
||||
|
||||
</router-link-stub>
|
||||
<!---->
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
<div class="ds-my-large"></div>
|
||||
<div class="ds-flex ds-flex-gap-small">
|
||||
<div class="menu-container">
|
||||
<ds-menu :routes="routes" :is-exact="() => true" />
|
||||
<os-menu :routes="routes" :is-exact="() => true" link-tag="router-link" />
|
||||
</div>
|
||||
<div class="settings-content" id="settings-content">
|
||||
<transition name="slide-up" appear>
|
||||
@ -18,7 +18,12 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { OsMenu } from '@ocelot-social/ui'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
OsMenu,
|
||||
},
|
||||
computed: {
|
||||
routes() {
|
||||
const routes = [
|
||||
|
||||
@ -32,3 +32,20 @@ global.localVue.use(Filters)
|
||||
global.localVue.use(Directives)
|
||||
global.localVue.use(InfiniteLoading)
|
||||
global.localVue.use(VueObserveVisibility)
|
||||
|
||||
// Register router-link stub globally (OsMenu/OsMenuItem render it via h())
|
||||
Vue.component('router-link', {
|
||||
name: 'RouterLink',
|
||||
props: { to: { type: [String, Object], default: '' }, exact: { type: Boolean, default: false } },
|
||||
render(h) {
|
||||
// Resolve href: string path or { name, params } object
|
||||
let href = ''
|
||||
const to = this.to
|
||||
if (typeof to === 'string') {
|
||||
href = to
|
||||
} else if (to) {
|
||||
href = to.path || `/${to.name || ''}`
|
||||
}
|
||||
return h('a', { attrs: { href, to: href }, class: this.$attrs.class }, this.$slots.default)
|
||||
},
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user