mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2026-03-01 12:44:37 +00:00
feat(package/ui): os-badge (#9250)
This commit is contained in:
parent
0e23f4ec4e
commit
951a24f100
@ -13,7 +13,7 @@ Phase 0: Analyse ██████████ 100% (8/8 Schritte) ✅
|
||||
Phase 3: OsButton ██████████ 100% (133/133 Buttons) ✅
|
||||
Phase 4: Tier 1 ██████████ 100% (OsButton, OsIcon, OsSpinner, OsCard) ✅
|
||||
Phase 4: Tier A → HTML ██████████ 100% (10 ds-* Wrapper → Plain HTML) ✅
|
||||
Phase 4: Tier B → HTML ░░░░░░░░░░ 0% (ds-chip, ds-number, ds-grid, ds-radio)
|
||||
Phase 4: Tier B ██████░░░░ 60% (ds-chip→OsBadge✅, ds-tag→OsBadge✅, ds-grid✅, ds-number⬜, ds-radio⬜)
|
||||
Phase 4: Tier 2-4 ░░░░░░░░░░ 0% (OsModal, OsInput, OsMenu, OsSelect, OsTable)
|
||||
```
|
||||
|
||||
@ -23,9 +23,10 @@ Phase 4: Tier 2-4 ░░░░░░░░░░ 0% (OsModal, OsInput, Os
|
||||
| Webapp Komponenten | 139 |
|
||||
| Styleguide Komponenten | 38 (23 in Webapp genutzt) |
|
||||
| **Gesamt** | **177** |
|
||||
| ✅ UI-Library | OsButton, OsIcon, OsSpinner, OsCard (4) |
|
||||
| ✅ → Plain HTML | Section, Placeholder, Tag, List, ListItem, Container, Heading, Text, Space, Flex, FlexItem (11) |
|
||||
| ⬜ → Plain HTML | Chip, Number, Grid, GridItem, Radio (5) — Tier B |
|
||||
| ✅ UI-Library | OsButton, OsIcon, OsSpinner, OsCard, OsBadge (5) |
|
||||
| ✅ → Plain HTML | Section, Placeholder, List, ListItem, Container, Heading, Text, Space, Flex, FlexItem, Grid, GridItem (12) |
|
||||
| ✅ → OsBadge | Chip (20 Nutzungen → OsBadge), Tag (3 → OsBadge shape="square") |
|
||||
| ⬜ → Plain HTML | Number, Radio (2) — Tier B Rest |
|
||||
| ⬜ → UI-Library | Modal, Input, Menu, MenuItem, Select, Table (6) — Tier 2-4 |
|
||||
| ⬜ Offen | Form (18 Dateien — HTML oder OsForm?) |
|
||||
| ⬜ Nicht in Webapp | Code, CopyField, FormItem, InputError, InputLabel, Page, PageTitle, Logo, Avatar, TableCol, TableHeadCol (11) |
|
||||
@ -53,7 +54,7 @@ Phase 4: Tier 2-4 ░░░░░░░░░░ 0% (OsModal, OsInput, Os
|
||||
|---|------------|--------|---------|
|
||||
| 1 | Avatar | ⬜ Nicht genutzt | Webapp nutzt eigenes ProfileAvatar |
|
||||
| 2 | Card | ✅ UI-Library | → OsCard (BaseCard gelöscht) |
|
||||
| 3 | Chip | ⬜ Tier B | 5 Dateien → Plain HTML `<span class="ds-chip">` |
|
||||
| 3 | Chip | ✅ UI-Library | → OsBadge (20 Nutzungen in 5 Dateien) |
|
||||
| 4 | Code | ⬜ Nicht genutzt | Nicht in Webapp verwendet |
|
||||
| 5 | Icon | ✅ UI-Library | → OsIcon (BaseIcon gelöscht, 82 Ocelot-Icons) |
|
||||
| 6 | Number | ⬜ Tier B | 5 Dateien → Plain HTML `<div class="ds-number">` |
|
||||
@ -62,7 +63,7 @@ Phase 4: Tier 2-4 ░░░░░░░░░░ 0% (OsModal, OsInput, Os
|
||||
| 9 | Table | ⬜ Tier 4 | 7 Dateien → OsTable |
|
||||
| 10 | TableCol | ⬜ Tier 4 | Intern von Table genutzt |
|
||||
| 11 | TableHeadCol | ⬜ Tier 4 | Intern von Table genutzt |
|
||||
| 12 | Tag | ✅ → HTML | Tier A: `<span class="ds-tag">` |
|
||||
| 12 | Tag | ✅ UI-Library | → OsBadge shape="square" (3 Nutzungen in 3 Dateien) |
|
||||
|
||||
### Data Input
|
||||
| # | Komponente | Status | Notizen |
|
||||
@ -83,8 +84,8 @@ Phase 4: Tier 2-4 ░░░░░░░░░░ 0% (OsModal, OsInput, Os
|
||||
| 22 | Container | ✅ → HTML | Tier A: `<div class="ds-container ds-container-{width}">` |
|
||||
| 23 | Flex | ✅ → HTML | Tier A: Plain HTML + CSS @media Queries |
|
||||
| 24 | FlexItem | ✅ → HTML | Tier A: Plain HTML + CSS @media Queries |
|
||||
| 25 | Grid | ⬜ Tier B | 2 Dateien → CSS Grid |
|
||||
| 26 | GridItem | ⬜ Tier B | 8 Dateien → CSS Grid |
|
||||
| 25 | Grid | ✅ → HTML | 2 Dateien → CSS Grid (class="ds-grid") |
|
||||
| 26 | GridItem | ✅ → HTML | 8 Dateien → CSS Grid |
|
||||
| 27 | Modal | ⬜ Tier 2 | 7 Dateien → OsModal |
|
||||
| 28 | Page | ⬜ Nicht genutzt | Nicht direkt in Webapp verwendet |
|
||||
| 29 | PageTitle | ⬜ Nicht genutzt | Nicht direkt in Webapp verwendet |
|
||||
@ -202,7 +203,7 @@ Phase 4: Tier 2-4 ░░░░░░░░░░ 0% (OsModal, OsInput, Os
|
||||
### H-L
|
||||
| # | Komponente | Status | Kategorie | Styleguide-Pendant | Notizen |
|
||||
|---|------------|--------|-----------|-------------------|---------|
|
||||
| 64 | Hashtag | ⬜ Ausstehend | Display | Tag/Chip | |
|
||||
| 64 | Hashtag | ✅ Migriert | Display | Tag/Chip | 🔄 nutzt OsBadge shape="square" |
|
||||
| 65 | HashtagsFilter | ⬜ Ausstehend | Filter | | |
|
||||
| 66 | HeaderButton | ✅ Migriert | Button | Button | 🔄 Button-Familie, nutzt OsButton |
|
||||
| 67 | HeaderMenu | ⬜ Ausstehend | Navigation | Menu | |
|
||||
@ -370,10 +371,12 @@ Phase 4: Tier 2-4 ░░░░░░░░░░ 0% (OsModal, OsInput, Os
|
||||
|
||||
### Layout & Typography — → Plain HTML ✅ (Tier A)
|
||||
- ~~Container, Flex, FlexItem, Section, Space~~ ✅ → HTML + CSS
|
||||
- ~~Heading, Text, List, ListItem, Tag, Placeholder~~ ✅ → HTML + CSS
|
||||
- ~~Heading, Text, List, ListItem, Placeholder~~ ✅ → HTML + CSS
|
||||
- ~~Chip, Tag~~ ✅ → OsBadge (UI-Library)
|
||||
|
||||
### Noch zu migrieren (Tier B → Plain HTML)
|
||||
- Chip, Number, Grid, GridItem, Radio
|
||||
### Noch zu migrieren (Tier B Rest)
|
||||
- Number (5 Dateien), Radio (1 Datei)
|
||||
- ~~Grid, GridItem~~ ✅ → CSS Grid (Plain HTML)
|
||||
|
||||
### Feature-Komponenten (bleiben in Webapp)
|
||||
- Chat, Group, Registration, Search, etc.
|
||||
@ -430,11 +433,12 @@ Phase 4: Tier 2-4 ░░░░░░░░░░ 0% (OsModal, OsInput, Os
|
||||
12. [x] _ds-compat.scss Utility-Klassen
|
||||
13. [x] 10 ds-* Wrapper → HTML + CSS (~450 Nutzungen, ~90 Dateien)
|
||||
|
||||
### Phase 4: Tier B — ds-* → Plain HTML (ausstehend)
|
||||
14. [ ] ds-chip (5 Dateien) → `<span class="ds-chip">`
|
||||
15. [ ] ds-number (5 Dateien) → `<div class="ds-number">`
|
||||
16. [ ] ds-grid / ds-grid-item (10 Dateien) → CSS Grid
|
||||
17. [ ] ds-radio (1 Datei) → native `<input type="radio">`
|
||||
### Phase 4: Tier B — ds-* Migration (60%)
|
||||
14. [x] ds-chip (5 Dateien, 20 Nutzungen) → OsBadge (UI-Library)
|
||||
15. [x] ds-tag (3 Dateien) → OsBadge shape="square" (UI-Library)
|
||||
16. [x] ds-grid / ds-grid-item (10 Dateien) → CSS Grid (Plain HTML)
|
||||
17. [ ] ds-number (5 Dateien) → `<div class="ds-number">`
|
||||
18. [ ] ds-radio (1 Datei) → native `<input type="radio">`
|
||||
|
||||
### Phase 4: Tier 2-4 — UI-Library (ausstehend)
|
||||
18. [ ] OsModal (7 Dateien)
|
||||
@ -445,7 +449,7 @@ Phase 4: Tier 2-4 ░░░░░░░░░░ 0% (OsModal, OsInput, Os
|
||||
|
||||
---
|
||||
|
||||
**✅ Phase 0-3 abgeschlossen. Phase 4: Tier 1 + Tier A ✅, Tier B + Tier 2-4 ausstehend.**
|
||||
**✅ Phase 0-3 abgeschlossen. Phase 4: Tier 1 + Tier A ✅, Tier B 60% (Chip→OsBadge, Tag→OsBadge, Grid→HTML), Tier 2-4 ausstehend.**
|
||||
|
||||
---
|
||||
|
||||
@ -945,19 +949,19 @@ interface OsDropdownProps {
|
||||
|
||||
| # | Komponente | Status |
|
||||
|---|------------|--------|
|
||||
| — | ds-section, ds-placeholder, ds-tag, ds-list, ds-list-item | ✅ → HTML-Elemente + CSS-Klassen |
|
||||
| — | ds-section, ds-placeholder, ds-list, ds-list-item | ✅ → HTML-Elemente + CSS-Klassen |
|
||||
| — | ds-container, ds-heading, ds-text | ✅ → HTML-Elemente + CSS-Klassen |
|
||||
| — | ds-space | ✅ → div + Margin-Utility-Klassen |
|
||||
| — | ds-flex, ds-flex-item | ✅ → HTML + CSS @media Queries |
|
||||
|
||||
### Tier B: Einfache ds-* → Plain HTML (ausstehend)
|
||||
### Tier B: Einfache ds-* Migration (60%)
|
||||
|
||||
| # | Komponente | Dateien | Ziel |
|
||||
|---|------------|---------|------|
|
||||
| — | ds-chip | 5 | `<span class="ds-chip">` |
|
||||
| — | ds-number | 5 | `<div class="ds-number">` |
|
||||
| — | ds-grid / ds-grid-item | 10 | CSS Grid |
|
||||
| — | ds-radio | 1 | native `<input type="radio">` |
|
||||
| # | Komponente | Dateien | Ziel | Status |
|
||||
|---|------------|---------|------|--------|
|
||||
| 5 | **OsBadge** | — | ds-chip (20 Nutzungen, 5 Dateien) + ds-tag (3 Dateien) | ✅ |
|
||||
| — | ds-grid / ds-grid-item | 10 | CSS Grid (Plain HTML) | ✅ |
|
||||
| — | ds-number | 5 | `<div class="ds-number">` | ⬜ |
|
||||
| — | ds-radio | 1 | native `<input type="radio">` | ⬜ |
|
||||
|
||||
### Tier 2: Layout & Feedback (ausstehend)
|
||||
|
||||
@ -998,21 +1002,26 @@ interface OsDropdownProps {
|
||||
2. OsSpinner ✅ Vereint: DsSpinner + LoadingSpinner
|
||||
3. OsButton ✅ Vereint: DsButton + BaseButton → 133 Buttons in 79 Dateien
|
||||
4. OsCard ✅ Vereint: DsCard + BaseCard → ~30 Dateien
|
||||
5. OsBadge ✅ Vereint: ds-chip (20 Nutzungen) + ds-tag (3 Dateien)
|
||||
```
|
||||
|
||||
### Tier A: Triviale ds-* Wrapper → Plain HTML ✅
|
||||
|
||||
```
|
||||
ds-section, ds-placeholder, ds-tag, ds-list, ds-list-item ✅ → HTML + CSS-Klassen
|
||||
ds-container, ds-heading, ds-text ✅ → HTML + CSS-Klassen
|
||||
ds-space ✅ → div + Margin-Utilities
|
||||
ds-flex, ds-flex-item ✅ → HTML + CSS @media Queries
|
||||
ds-section, ds-placeholder, ds-list, ds-list-item ✅ → HTML + CSS-Klassen
|
||||
ds-container, ds-heading, ds-text ✅ → HTML + CSS-Klassen
|
||||
ds-space ✅ → div + Margin-Utilities
|
||||
ds-flex, ds-flex-item ✅ → HTML + CSS @media Queries
|
||||
```
|
||||
|
||||
### Tier B: Einfache ds-* → Plain HTML (ausstehend)
|
||||
### Tier B: Einfache ds-* Migration (60%)
|
||||
|
||||
```
|
||||
ds-chip, ds-number, ds-grid/ds-grid-item, ds-radio → Plain HTML + CSS
|
||||
ds-chip → OsBadge (UI-Library) ✅
|
||||
ds-tag → OsBadge shape="square" (UI-Library) ✅
|
||||
ds-grid / ds-grid-item → CSS Grid (HTML) ✅
|
||||
ds-number → Plain HTML ⬜ (5 Dateien)
|
||||
ds-radio → native <input type="radio"> ⬜ (1 Datei)
|
||||
```
|
||||
|
||||
### Tier 2-4: UI-Library (ausstehend)
|
||||
@ -1162,4 +1171,4 @@ $box-shadow-small-inset: inset 0 0 0 1px rgba(0,0,0,.05)
|
||||
**OsButton Migration: ✅ Vollständig abgeschlossen.**
|
||||
- BaseButton.vue gelöscht, base-components.js Plugin entfernt
|
||||
- Alle Tests, Snapshots, Cypress E2E-Selektoren aktualisiert
|
||||
- Nächster Schritt: Tier B (ds-chip, ds-number, ds-grid, ds-radio) oder Tier 2 (OsModal)
|
||||
- Nächster Schritt: Tier B Rest (ds-number, ds-radio) oder Tier 2 (OsModal)
|
||||
|
||||
@ -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: █████░░░░░ 46% (12/26 Aufgaben) - Tier 1 ✅, Tier A ✅, Infra ✅ | Tier B, Tier 2-4 ausstehend
|
||||
Phase 4: ██████░░░░ 54% (14/26 Aufgaben) - Tier 1 ✅, Tier A ✅, Infra ✅, OsBadge ✅ | Tier B (rest), Tier 2-4 ausstehend
|
||||
Phase 5: ░░░░░░░░░░ 0% (0/7 Aufgaben)
|
||||
───────────────────────────────────────
|
||||
Gesamt: ████████░░ 78% (74/95 Aufgaben)
|
||||
Gesamt: ████████░░ 80% (76/95 Aufgaben)
|
||||
```
|
||||
|
||||
### Katalogisierung (Details in KATALOG.md)
|
||||
@ -186,38 +186,50 @@ Tier A ds-* → Plain HTML + CSS: ✅
|
||||
├─ ds-flex/ds-flex-item: JavaScript window.innerWidth → CSS @media Queries
|
||||
│ (kein Layout-Shift bei SSR, bessere Performance)
|
||||
├─ system.css bleibt geladen — bestehende CSS-Klassen funktionieren weiter
|
||||
├─ Verbleibend: 13 ds-* Komponenten (Tier B: 4 einfache, Tier C: 6 komplexe → UI-Library)
|
||||
├─ Verbleibend: 9 ds-* Komponenten (Tier B Rest: 2 einfache, Tier C: 6 komplexe → UI-Library)
|
||||
└─ 0 Tier-A ds-* Komponenten-Tags verbleibend
|
||||
|
||||
ds-chip + ds-tag → OsBadge (UI-Library): ✅
|
||||
├─ OsBadge Komponente: CVA-Varianten, h() Render-Function, inheritAttrs: false
|
||||
├─ Props: variant (default/primary/danger), size (sm/md/lg), shape (pill/square)
|
||||
├─ Types: BadgeVariant, BadgeSize, BadgeShape (+ BadgeVariants)
|
||||
├─ ds-chip: 20 Nutzungen in 5 Dateien → <os-badge> (Formzähler + Gruppen-Metadaten)
|
||||
├─ ds-tag: 3 Nutzungen in 3 Dateien → <os-badge shape="square"> (Category, Hashtag)
|
||||
├─ ARIA: role="status" aria-live="polite" auf Form-Zähler (11 Stellen in 2 Dateien)
|
||||
├─ CSS-Variable: --color-default + --color-default-contrast (beide in requiredCssVariables)
|
||||
├─ 18 Unit-Tests, 6 Stories, 5 Visual Tests + 1 Keyboard Test
|
||||
└─ 0 ds-chip/ds-tag Nutzungen verbleibend
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Aktueller Stand
|
||||
|
||||
**Letzte Aktualisierung:** 2026-02-19 (Session 28)
|
||||
**Letzte Aktualisierung:** 2026-02-20 (Session 30)
|
||||
|
||||
**Aktuelle Phase:** Phase 4 - OsIcon ✅, BaseIcon → OsIcon Migration ✅, OsSpinner ✅, Spinner Webapp-Migration ✅, OsCard ✅, BaseCard → OsCard Migration ✅, Tier A ds-* → Plain HTML ✅
|
||||
**Aktuelle Phase:** Phase 4 - Tier 1 ✅, Tier A ✅, OsBadge ✅ (ds-chip + ds-tag → UI-Library) | Tier B, Tier 2-4 ausstehend
|
||||
|
||||
**Zuletzt abgeschlossen (Session 28 - CodeRabbit Review Fixes für Tier A PR):**
|
||||
- [x] GroupForm.vue: Fehlende `buttons`-Klasse auf Button-Container ergänzt (CSS-Regel `.group-form > .buttons` griff nicht)
|
||||
- [x] ComponentSlider.vue: `<h1>` → `<h3>` (Original war `<ds-heading size="h3">`, falsch zu h1 migriert)
|
||||
- [x] SearchHeading.vue: `<h1>` → `<h5>` (Original war `<ds-heading size="h5">`, falsch zu h1 migriert)
|
||||
- [x] MySomethingList.vue: Redundantes verschachteltes `<div class="ds-mt-base ds-mb-large">` entfernt (Überbleibsel von verschachtelten ds-space)
|
||||
- [x] Empty.vue: Prop-Type `[String, Object]` → `String` (Object-Support war ds-space-Relikt, nicht implementiert)
|
||||
- [x] ChangePassword.vue: Inline-Spans → separate `<p class="ds-text">` Elemente (visuelle Regression: fehlende Block-Level-Trennung)
|
||||
- [x] RegistrationSlideEmail.vue: Label `for="checkbox0"` → `for="sendEmailAgain"` passend zu `id="sendEmailAgain"` (A11y WCAG 1.3.1/4.1.2)
|
||||
- [x] Signup.vue: `margin="large"` (totes ds-space-Attribut auf div) → `class="ds-my-large"` + `<style>` → `<style scoped>`
|
||||
- [x] SocialMedia.vue: `:key="link.id"` → `:key="link.url"` (link.id war immer undefined, socialMediaLinks() gibt kein id zurück)
|
||||
- [x] logout.vue: Hardcodierter "Logging out..." → `$t('login.loggingOut')` mit Übersetzungen in allen 9 Sprachen
|
||||
- [x] maintenance/index.vue: `alt="Under maintenance"` → `:alt="$t('maintenance.title', metadata)"` (i18n-Konsistenz) + `<style scoped>`
|
||||
- [x] admin/index.vue: `33%` → `33.333%` (Rundungsfehler bei 3-Spalten-Layout)
|
||||
- [x] profile/_id/_slug.vue: Inline-Style `margin-bottom: 10px; text-align: center` → `ds-text-center ds-mb-x-small` Utility-Klassen
|
||||
- [x] post/edit/_id.vue: ` ` aus Aside-Div entfernt (Screenreader-Problem) + zwei Style-Blöcke zu einem scoped Block gemergt
|
||||
- [x] post/create/_type.vue: Zwei Style-Blöcke (scoped + unscoped) zu einem scoped Block gemergt
|
||||
- [x] _ds-compat.scss: `display: flex` zu `.ds-space-centered` hinzugefügt (justify-content/align-items wirkten ohne display:flex nicht)
|
||||
- [x] blocked-users.vue: Unnötiges Wrapper-Div `ds-mb-large` am Kartenende entfernt
|
||||
- [x] Cypress SocialMedia: `cy.get('.ds-list-item a')` → `cy.get('.ds-list-item a[href="${link}"]')` (nutzt link-Parameter)
|
||||
- [x] Als "nicht in diesem PR" bewertet: pre-existing Code-Style Nitpicks (required:false, Ternary-Syntax, Prop-Validierung, Spacer-Divs, flex:1 0 0 Einheit, v-if="user", CSS-Nesting, Dark-Mode SVG-Farbe)
|
||||
**Zuletzt abgeschlossen (Session 30 - OsBadge Code-Review Fixes):**
|
||||
- [x] `--color-default-contrast` zu `requiredCssVariables` in tailwind.preset.ts hinzugefügt
|
||||
- [x] Doppelte `--color-default`-Deklaration in ocelot-ui-variables.scss entfernt (softest → softer konsolidiert)
|
||||
- [x] Redundante Ternär-Ausdrücke entfernt: GroupTeaser.vue + groups/_slug.vue (`group ? group.about : ''` → `group.about`)
|
||||
- [x] `BadgeVariant` Typ hinzugefügt und in OsBadge.vue verwendet (statt `NonNullable<BadgeVariants['variant']>`)
|
||||
- [x] Exports erweitert: BadgeVariant in index.ts + components/index.ts
|
||||
- [x] GroupForm.vue: `float: right` → `display: flex; flex-direction: column; align-self: flex-end` (konsistentes Flexbox-Layout)
|
||||
- [x] PlaygroundArgs in Stories: `string` → `BadgeVariant | BadgeSize | BadgeShape` (typsichere Story-Args)
|
||||
- [x] WithIcon-Story: Inline-Style `style="margin-right: 4px"` → Tailwind `class="mr-1"`
|
||||
- [x] ARIA Live Regions: `role="status" aria-live="polite"` auf 10 Form-Zähler + `role="alert" aria-live="assertive"` auf 1 Fehler-Badge
|
||||
- [x] `live` Prop entfernt — Standard-ARIA-Attribute werden direkt durchgereicht (attrs)
|
||||
- [x] KATALOG.md: Übersichtstabelle, Tier B Status, Komponenten-Tabelle, Zusammenfassungen aktualisiert
|
||||
|
||||
**Zuvor abgeschlossen (Session 29 - OsBadge: ds-chip + ds-tag Migration):**
|
||||
- [x] OsBadge Komponente in packages/ui erstellt (CVA-Varianten, h() Render-Function)
|
||||
- [x] Props: variant (default/primary/danger), size (sm/md/lg), shape (pill/square)
|
||||
- [x] ds-chip → OsBadge: 20 Nutzungen in 5 Webapp-Dateien (GroupTeaser, GroupMember, GroupForm, ContributionForm, groups/_slug)
|
||||
- [x] ds-tag → OsBadge: 3 Nutzungen in 3 Webapp-Dateien (Category, Hashtag, PostTeaser CSS)
|
||||
- [x] CSS-Variable --color-default für neutralen Badge-Hintergrund
|
||||
- [x] Unit-Tests (16), Stories (6), Visual Tests (5+1 Keyboard), 100% Coverage
|
||||
- [x] Exports: OsBadge, badgeVariants, BadgeShape, BadgeSize, BadgeVariants
|
||||
|
||||
**Zuvor abgeschlossen (Session 27 - Tier A: ds-* Komponenten → Plain HTML):**
|
||||
- [x] `_ds-compat.scss` erstellt (Utility-Klassen für Margins, Flex, Centered)
|
||||
@ -236,8 +248,8 @@ Tier A ds-* → Plain HTML + CSS: ✅
|
||||
- [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 (13 Typen):**
|
||||
- Tier B (→ Plain HTML): ds-chip (5), ds-number (5), ds-grid/ds-grid-item (10), ds-radio (1)
|
||||
**Verbleibende ds-* Komponenten (9 Typen):**
|
||||
- Tier B Rest (→ Plain HTML): ds-number (5), ds-radio (1)
|
||||
- Tier C (→ UI-Library): ds-input (23), ds-form (18), ds-modal (7), ds-menu/ds-menu-item (17), ds-table (7), ds-select (3)
|
||||
|
||||
**Zuvor abgeschlossen (Session 26 - CodeRabbit Review Fixes):**
|
||||
@ -359,7 +371,8 @@ Tier A ds-* → Plain HTML + CSS: ✅
|
||||
- [x] OsSpinner Webapp-Migration (DsSpinner + LoadingSpinner → OsSpinner) ✅
|
||||
- [x] OsCard Komponente + BaseCard → OsCard Webapp-Migration ✅
|
||||
- [x] Tier A: 10 triviale ds-* Wrapper → Plain HTML + CSS ✅
|
||||
- [ ] Tier B: ds-chip, ds-number, ds-grid/ds-grid-item, ds-radio → Plain HTML
|
||||
- [x] OsBadge Komponente + ds-chip/ds-tag → OsBadge Webapp-Migration ✅
|
||||
- [ ] Tier B (Rest): ds-number, ds-grid/ds-grid-item, ds-radio → Plain HTML
|
||||
- [ ] Weitere Tier 2 Komponenten (OsModal, OsDropdown, OsAvatar, OsInput)
|
||||
- [ ] ds-form + ds-input → OsForm + OsInput (stark gekoppelt, 18+23 Dateien)
|
||||
- [ ] ds-menu / ds-menu-item → OsMenu / OsMenuItem
|
||||
@ -627,8 +640,9 @@ Jeder migrierte Button muss manuell geprüft werden: Normal, Hover, Focus, Activ
|
||||
- [x] ds-space → div + Margin-Utility-Klassen (139 Nutzungen)
|
||||
- [x] ds-flex / ds-flex-item → HTML + CSS @media Queries (103 Nutzungen)
|
||||
|
||||
**Tier B: Einfache ds-* → Plain HTML (ausstehend)**
|
||||
- [ ] ds-chip (5 Dateien) → `<span class="ds-chip">`
|
||||
**Tier B: Einfache ds-* → Plain HTML / UI-Library**
|
||||
- [x] ds-chip (5 Dateien) → OsBadge (UI-Library) ✅
|
||||
- [x] ds-tag (3 Dateien) → OsBadge shape="square" (UI-Library) ✅
|
||||
- [ ] ds-number (5 Dateien) → `<div class="ds-number">`
|
||||
- [ ] ds-grid / ds-grid-item (10 Dateien) → CSS Grid
|
||||
- [ ] ds-radio (1 Datei) → native `<input type="radio">`
|
||||
@ -1765,6 +1779,15 @@ Bei der Migration werden:
|
||||
| 2026-02-19 | **CSS Fixes** | .buttons Klasse ergänzt (GroupForm), display:flex auf .ds-space-centered, 33%→33.333%, inline-style→Utilities |
|
||||
| 2026-02-19 | **Scoping** | Signup.vue, maintenance/index.vue, post/create/_type.vue, post/edit/_id.vue: unscoped → scoped Style-Blöcke |
|
||||
| 2026-02-19 | **Migration-Artefakte** | Redundante Wrapper-Divs, tote Attribute (margin="large"), Span→P Block-Level Regression |
|
||||
| 2026-02-20 | **OsBadge Komponente (Session 29)** | Neue Komponente: CVA-Varianten (variant, size, shape), h() Render-Function, 16 Tests, 6 Stories |
|
||||
| 2026-02-20 | **ds-chip → OsBadge** | 20 Nutzungen in 5 Dateien: GroupTeaser, GroupMember, GroupForm, ContributionForm, groups/_slug |
|
||||
| 2026-02-20 | **ds-tag → OsBadge** | 3 Nutzungen in 3 Dateien: Category (shape="square"), Hashtag (shape="square"), PostTeaser (CSS) |
|
||||
| 2026-02-20 | **OsBadge Features** | shape-Prop (pill/square), w-fit (Flex-Container-Fix), inline-flex items-center (Icon-Zentrierung) |
|
||||
| 2026-02-20 | **CSS-Variable** | --color-default + --color-default-contrast für neutralen Badge-Hintergrund |
|
||||
| 2026-02-20 | **Code-Review Fixes (Session 30)** | --color-default-contrast in requiredCssVariables, doppelte --color-default Deklaration entfernt |
|
||||
| 2026-02-20 | **Type Safety** | BadgeVariant Typ exportiert, PlaygroundArgs typisiert, redundante Ternäre entfernt |
|
||||
| 2026-02-20 | **Layout-Konsistenz** | GroupForm float:right → Flexbox align-self:flex-end, Inline-Style → Tailwind in Stories |
|
||||
| 2026-02-20 | **ARIA Live Regions** | role="status"/aria-live="polite" auf 11 Form-Badges (WCAG 4.1.3), live Prop → Standard-Attribute |
|
||||
|
||||
---
|
||||
|
||||
@ -1782,9 +1805,10 @@ Bei der Migration werden:
|
||||
**Styleguide-Migration:**
|
||||
| Status | Komponenten |
|
||||
|--------|------------|
|
||||
| ✅ UI-Library | OsButton, OsIcon, OsSpinner, OsCard (4) |
|
||||
| ✅ → Plain HTML | Section, Placeholder, Tag, List, ListItem, Container, Heading, Text, Space, Flex, FlexItem (11) — Tier A |
|
||||
| ⬜ → Plain HTML | Chip, Number, Grid, GridItem, Radio (5) — Tier B |
|
||||
| ✅ UI-Library | OsButton, OsIcon, OsSpinner, OsCard, OsBadge (5) |
|
||||
| ✅ → Plain HTML | Section, Placeholder, List, ListItem, Container, Heading, Text, Space, Flex, FlexItem (10) — Tier A |
|
||||
| ✅ → UI-Library | Chip, Tag → OsBadge (2) — Tier B |
|
||||
| ⬜ → Plain HTML | Number, Grid, GridItem, Radio (4) — Tier B |
|
||||
| ⬜ → UI-Library | Modal, Input, Menu, MenuItem, Select, Table (6) — Tier 2-4 |
|
||||
| ⬜ Nicht genutzt | Code, CopyField, FormItem, InputError, InputLabel, Page, PageTitle, Logo, Avatar, TableCol, TableHeadCol (11) |
|
||||
| ⬜ Offen | Form (18 Dateien — HTML `<form>` oder OsForm?) |
|
||||
|
||||
157
packages/ui/src/components/OsBadge/OsBadge.spec.ts
Normal file
157
packages/ui/src/components/OsBadge/OsBadge.spec.ts
Normal file
@ -0,0 +1,157 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import OsBadge from './OsBadge.vue'
|
||||
|
||||
describe('osBadge', () => {
|
||||
describe('rendering', () => {
|
||||
it('renders as span element', () => {
|
||||
const wrapper = mount(OsBadge)
|
||||
|
||||
expect((wrapper.element as HTMLElement).tagName).toBe('SPAN')
|
||||
})
|
||||
|
||||
it('renders default slot content', () => {
|
||||
const wrapper = mount(OsBadge, {
|
||||
slots: { default: 'Badge text' },
|
||||
})
|
||||
|
||||
expect(wrapper.text()).toBe('Badge text')
|
||||
})
|
||||
|
||||
it('renders without content', () => {
|
||||
const wrapper = mount(OsBadge)
|
||||
|
||||
expect(wrapper.exists()).toBe(true)
|
||||
expect(wrapper.text()).toBe('')
|
||||
})
|
||||
})
|
||||
|
||||
describe('css', () => {
|
||||
it('has os-badge class', () => {
|
||||
const wrapper = mount(OsBadge)
|
||||
|
||||
expect(wrapper.classes()).toContain('os-badge')
|
||||
})
|
||||
|
||||
it('merges custom classes', () => {
|
||||
const wrapper = mount(OsBadge, {
|
||||
attrs: { class: 'my-custom-class' },
|
||||
})
|
||||
|
||||
expect(wrapper.classes()).toContain('os-badge')
|
||||
expect(wrapper.classes()).toContain('my-custom-class')
|
||||
})
|
||||
|
||||
it('passes through attributes', () => {
|
||||
const wrapper = mount(OsBadge, {
|
||||
attrs: { 'data-testid': 'my-badge' },
|
||||
})
|
||||
|
||||
expect(wrapper.attributes('data-testid')).toBe('my-badge')
|
||||
})
|
||||
})
|
||||
|
||||
describe('variant prop', () => {
|
||||
it('applies default variant classes when no variant specified', () => {
|
||||
const wrapper = mount(OsBadge)
|
||||
|
||||
expect(wrapper.classes()).toContain('bg-[var(--color-default)]')
|
||||
expect(wrapper.classes()).toContain('text-[var(--color-default-contrast)]')
|
||||
})
|
||||
|
||||
it('applies primary variant classes', () => {
|
||||
const wrapper = mount(OsBadge, {
|
||||
props: { variant: 'primary' },
|
||||
})
|
||||
|
||||
expect(wrapper.classes()).toContain('bg-[var(--color-primary)]')
|
||||
expect(wrapper.classes()).toContain('text-[var(--color-primary-contrast)]')
|
||||
})
|
||||
|
||||
it('applies danger variant classes', () => {
|
||||
const wrapper = mount(OsBadge, {
|
||||
props: { variant: 'danger' },
|
||||
})
|
||||
|
||||
expect(wrapper.classes()).toContain('bg-[var(--color-danger)]')
|
||||
expect(wrapper.classes()).toContain('text-[var(--color-danger-contrast)]')
|
||||
})
|
||||
})
|
||||
|
||||
describe('size prop', () => {
|
||||
it('applies sm size classes by default', () => {
|
||||
const wrapper = mount(OsBadge)
|
||||
|
||||
expect(wrapper.classes()).toContain('text-[0.75rem]')
|
||||
expect(wrapper.classes()).toContain('px-[0.8em]')
|
||||
})
|
||||
|
||||
it('applies md size classes', () => {
|
||||
const wrapper = mount(OsBadge, {
|
||||
props: { size: 'md' },
|
||||
})
|
||||
|
||||
expect(wrapper.classes()).toContain('text-[0.875rem]')
|
||||
expect(wrapper.classes()).toContain('px-[1em]')
|
||||
})
|
||||
|
||||
it('applies lg size classes', () => {
|
||||
const wrapper = mount(OsBadge, {
|
||||
props: { size: 'lg' },
|
||||
})
|
||||
|
||||
expect(wrapper.classes()).toContain('text-[1rem]')
|
||||
expect(wrapper.classes()).toContain('px-[1.2em]')
|
||||
})
|
||||
})
|
||||
|
||||
describe('shape prop', () => {
|
||||
it('applies pill shape by default', () => {
|
||||
const wrapper = mount(OsBadge)
|
||||
|
||||
expect(wrapper.classes()).toContain('rounded-[2em]')
|
||||
})
|
||||
|
||||
it('applies square shape', () => {
|
||||
const wrapper = mount(OsBadge, {
|
||||
props: { shape: 'square' },
|
||||
})
|
||||
|
||||
expect(wrapper.classes()).toContain('rounded-[0.25em]')
|
||||
})
|
||||
})
|
||||
|
||||
describe('aria attributes', () => {
|
||||
it('has no role or aria-live by default', () => {
|
||||
const wrapper = mount(OsBadge)
|
||||
|
||||
expect(wrapper.attributes('role')).toBeUndefined()
|
||||
expect(wrapper.attributes('aria-live')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('passes through role attribute', () => {
|
||||
const wrapper = mount(OsBadge, {
|
||||
attrs: { role: 'status' },
|
||||
})
|
||||
|
||||
expect(wrapper.attributes('role')).toBe('status')
|
||||
})
|
||||
|
||||
it('passes through aria-live attribute', () => {
|
||||
const wrapper = mount(OsBadge, {
|
||||
attrs: { 'aria-live': 'polite' },
|
||||
})
|
||||
|
||||
expect(wrapper.attributes('aria-live')).toBe('polite')
|
||||
})
|
||||
})
|
||||
|
||||
describe('keyboard accessibility', () => {
|
||||
it('is not focusable (non-interactive element)', () => {
|
||||
const wrapper = mount(OsBadge)
|
||||
|
||||
expect(wrapper.attributes('tabindex')).toBeUndefined()
|
||||
})
|
||||
})
|
||||
})
|
||||
140
packages/ui/src/components/OsBadge/OsBadge.stories.ts
Normal file
140
packages/ui/src/components/OsBadge/OsBadge.stories.ts
Normal file
@ -0,0 +1,140 @@
|
||||
import { computed } from 'vue'
|
||||
|
||||
import OsBadge from './OsBadge.vue'
|
||||
|
||||
import type { BadgeShape, BadgeSize, BadgeVariant } from './badge.variants'
|
||||
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
||||
|
||||
const meta: Meta<typeof OsBadge> = {
|
||||
title: 'Components/OsBadge',
|
||||
component: OsBadge,
|
||||
tags: ['autodocs'],
|
||||
}
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof OsBadge>
|
||||
|
||||
interface PlaygroundArgs {
|
||||
variant: BadgeVariant
|
||||
size: BadgeSize
|
||||
shape: BadgeShape
|
||||
content: string
|
||||
}
|
||||
|
||||
export const Playground: StoryObj<PlaygroundArgs> = {
|
||||
argTypes: {
|
||||
variant: {
|
||||
control: 'select',
|
||||
options: ['default', 'primary', 'danger'],
|
||||
},
|
||||
size: {
|
||||
control: 'select',
|
||||
options: ['sm', 'md', 'lg'],
|
||||
},
|
||||
shape: {
|
||||
control: 'select',
|
||||
options: ['pill', 'square'],
|
||||
},
|
||||
content: {
|
||||
control: 'text',
|
||||
},
|
||||
},
|
||||
args: {
|
||||
variant: 'default',
|
||||
size: 'sm',
|
||||
shape: 'pill',
|
||||
content: 'Badge',
|
||||
},
|
||||
render: (args) => ({
|
||||
components: { OsBadge },
|
||||
setup() {
|
||||
const badgeProps = computed(() => ({
|
||||
variant: args.variant,
|
||||
size: args.size,
|
||||
shape: args.shape,
|
||||
}))
|
||||
const content = computed(() => args.content)
|
||||
return { badgeProps, content }
|
||||
},
|
||||
template: `<OsBadge v-bind="badgeProps">{{ content }}</OsBadge>`,
|
||||
}),
|
||||
}
|
||||
|
||||
export const AllVariants: Story = {
|
||||
render: () => ({
|
||||
components: { OsBadge },
|
||||
template: `
|
||||
<div data-testid="all-variants" class="flex items-center gap-3">
|
||||
<OsBadge variant="default">Default</OsBadge>
|
||||
<OsBadge variant="primary">Primary</OsBadge>
|
||||
<OsBadge variant="danger">Danger</OsBadge>
|
||||
</div>
|
||||
`,
|
||||
}),
|
||||
}
|
||||
|
||||
export const AllSizes: Story = {
|
||||
render: () => ({
|
||||
components: { OsBadge },
|
||||
template: `
|
||||
<div data-testid="all-sizes" class="flex items-end gap-3">
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<OsBadge size="sm">Small</OsBadge>
|
||||
<span class="text-xs text-gray-500">sm</span>
|
||||
</div>
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<OsBadge size="md">Medium</OsBadge>
|
||||
<span class="text-xs text-gray-500">md</span>
|
||||
</div>
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<OsBadge size="lg">Large</OsBadge>
|
||||
<span class="text-xs text-gray-500">lg</span>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
}),
|
||||
}
|
||||
|
||||
export const AllShapes: Story = {
|
||||
render: () => ({
|
||||
components: { OsBadge },
|
||||
template: `
|
||||
<div data-testid="all-shapes" class="flex items-center gap-3">
|
||||
<OsBadge shape="pill">Pill</OsBadge>
|
||||
<OsBadge shape="square">Square</OsBadge>
|
||||
</div>
|
||||
`,
|
||||
}),
|
||||
}
|
||||
|
||||
export const FormCounter: Story = {
|
||||
render: () => ({
|
||||
components: { OsBadge },
|
||||
template: `
|
||||
<div data-testid="form-counter" class="flex flex-col gap-3">
|
||||
<p class="text-sm text-gray-500 m-0">Valid counter:</p>
|
||||
<OsBadge size="md">42/100</OsBadge>
|
||||
<p class="text-sm text-gray-500 m-0">Error counter (danger):</p>
|
||||
<OsBadge size="md" variant="danger">105/100 ⚠</OsBadge>
|
||||
<p class="text-sm text-gray-500 m-0">Default with counter (form idle):</p>
|
||||
<OsBadge size="md" variant="default">0/100</OsBadge>
|
||||
</div>
|
||||
`,
|
||||
}),
|
||||
}
|
||||
|
||||
export const WithIcon: Story = {
|
||||
render: () => ({
|
||||
components: { OsBadge },
|
||||
template: `
|
||||
<div data-testid="with-icon" class="flex items-center gap-3">
|
||||
<OsBadge variant="primary">
|
||||
<span class="mr-1">✓</span> Verified
|
||||
</OsBadge>
|
||||
<OsBadge variant="danger">
|
||||
<span class="mr-1">⚠</span> Error
|
||||
</OsBadge>
|
||||
</div>
|
||||
`,
|
||||
}),
|
||||
}
|
||||
97
packages/ui/src/components/OsBadge/OsBadge.visual.spec.ts
Normal file
97
packages/ui/src/components/OsBadge/OsBadge.visual.spec.ts
Normal file
@ -0,0 +1,97 @@
|
||||
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-osbadge'
|
||||
const STORY_ROOT = '#storybook-root'
|
||||
|
||||
async function waitForReady(page: Page) {
|
||||
await page.evaluate(async () => document.fonts.ready)
|
||||
}
|
||||
|
||||
async function checkA11y(page: Page) {
|
||||
const results = await new AxeBuilder({ page }).include(STORY_ROOT).analyze()
|
||||
|
||||
expect(results.violations).toEqual([])
|
||||
}
|
||||
|
||||
test.describe('OsBadge keyboard accessibility', () => {
|
||||
test('badge is not focusable (non-interactive element)', async ({ page }) => {
|
||||
await page.goto(`${STORY_URL}--all-variants&viewMode=story`)
|
||||
const root = page.locator(STORY_ROOT)
|
||||
await root.waitFor()
|
||||
|
||||
const badges = root.locator('.os-badge')
|
||||
const count = await badges.count()
|
||||
|
||||
expect(count).toBeGreaterThan(0)
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
await expect(badges.nth(i)).not.toHaveAttribute('tabindex')
|
||||
await expect(badges.nth(i)).not.toHaveAttribute('role')
|
||||
}
|
||||
|
||||
await page.keyboard.press('Tab')
|
||||
for (let i = 0; i < count; i++) {
|
||||
await expect(badges.nth(i)).not.toBeFocused()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('OsBadge visual regression', () => {
|
||||
test('all variants', async ({ page }) => {
|
||||
await page.goto(`${STORY_URL}--all-variants&viewMode=story`)
|
||||
const root = page.locator(STORY_ROOT)
|
||||
await root.waitFor()
|
||||
await waitForReady(page)
|
||||
|
||||
await expect(root.locator('[data-testid="all-variants"]')).toHaveScreenshot('all-variants.png')
|
||||
|
||||
await checkA11y(page)
|
||||
})
|
||||
|
||||
test('all sizes', async ({ page }) => {
|
||||
await page.goto(`${STORY_URL}--all-sizes&viewMode=story`)
|
||||
const root = page.locator(STORY_ROOT)
|
||||
await root.waitFor()
|
||||
await waitForReady(page)
|
||||
|
||||
await expect(root.locator('[data-testid="all-sizes"]')).toHaveScreenshot('all-sizes.png')
|
||||
|
||||
await checkA11y(page)
|
||||
})
|
||||
|
||||
test('all shapes', async ({ page }) => {
|
||||
await page.goto(`${STORY_URL}--all-shapes&viewMode=story`)
|
||||
const root = page.locator(STORY_ROOT)
|
||||
await root.waitFor()
|
||||
await waitForReady(page)
|
||||
|
||||
await expect(root.locator('[data-testid="all-shapes"]')).toHaveScreenshot('all-shapes.png')
|
||||
|
||||
await checkA11y(page)
|
||||
})
|
||||
|
||||
test('form counter', async ({ page }) => {
|
||||
await page.goto(`${STORY_URL}--form-counter&viewMode=story`)
|
||||
const root = page.locator(STORY_ROOT)
|
||||
await root.waitFor()
|
||||
await waitForReady(page)
|
||||
|
||||
await expect(root.locator('[data-testid="form-counter"]')).toHaveScreenshot('form-counter.png')
|
||||
|
||||
await checkA11y(page)
|
||||
})
|
||||
|
||||
test('with icon', async ({ page }) => {
|
||||
await page.goto(`${STORY_URL}--with-icon&viewMode=story`)
|
||||
const root = page.locator(STORY_ROOT)
|
||||
await root.waitFor()
|
||||
await waitForReady(page)
|
||||
|
||||
await expect(root.locator('[data-testid="with-icon"]')).toHaveScreenshot('with-icon.png')
|
||||
|
||||
await checkA11y(page)
|
||||
})
|
||||
})
|
||||
96
packages/ui/src/components/OsBadge/OsBadge.vue
Normal file
96
packages/ui/src/components/OsBadge/OsBadge.vue
Normal file
@ -0,0 +1,96 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, getCurrentInstance, h, isVue2 } from 'vue-demi'
|
||||
|
||||
import { cn } from '#src/utils'
|
||||
|
||||
import { badgeVariants } from './badge.variants'
|
||||
|
||||
import type { BadgeShape, BadgeSize, BadgeVariant } from './badge.variants'
|
||||
import type { ClassValue } from 'clsx'
|
||||
import type { PropType } from 'vue-demi'
|
||||
|
||||
/**
|
||||
* Non-interactive label for metadata display (e.g. group info) and
|
||||
* form counters (e.g. character count with validation state).
|
||||
*
|
||||
* @slot default - Badge content (text, icons)
|
||||
*/
|
||||
export default defineComponent({
|
||||
name: 'OsBadge',
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
/**
|
||||
* Visual style of the badge.
|
||||
* - `default` — neutral gray background
|
||||
* - `primary` — brand color (green)
|
||||
* - `danger` — error/warning color (red)
|
||||
*/
|
||||
variant: {
|
||||
type: String as PropType<BadgeVariant>,
|
||||
default: undefined,
|
||||
},
|
||||
/**
|
||||
* Size of the badge.
|
||||
* - `sm` — compact (default)
|
||||
* - `md` — medium
|
||||
* - `lg` — large
|
||||
*/
|
||||
size: {
|
||||
type: String as PropType<BadgeSize>,
|
||||
default: undefined,
|
||||
},
|
||||
/**
|
||||
* Shape of the badge.
|
||||
* - `pill` — fully rounded (default)
|
||||
* - `square` — slightly rounded corners
|
||||
*/
|
||||
shape: {
|
||||
type: String as PropType<BadgeShape>,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
setup(props, { slots, attrs }) {
|
||||
/* v8 ignore start -- Vue 2 only */
|
||||
const instance = isVue2 ? getCurrentInstance() : null
|
||||
/* v8 ignore stop */
|
||||
|
||||
return () => {
|
||||
const children = slots.default?.()
|
||||
const badgeClass = cn(
|
||||
'os-badge',
|
||||
badgeVariants({ variant: props.variant, size: props.size, shape: props.shape }),
|
||||
)
|
||||
|
||||
/* v8 ignore start -- Vue 2 branch tested in webapp Jest tests */
|
||||
if (isVue2) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const proxy = instance?.proxy as any
|
||||
const parentClass = proxy?.$vnode?.data?.staticClass || ''
|
||||
const parentDynClass = proxy?.$vnode?.data?.class
|
||||
const parentAttrs = proxy?.$vnode?.data?.attrs || {}
|
||||
|
||||
return h(
|
||||
'span',
|
||||
{
|
||||
class: cn(badgeClass, parentClass, parentDynClass),
|
||||
attrs: { ...parentAttrs, ...attrs },
|
||||
},
|
||||
children,
|
||||
)
|
||||
}
|
||||
/* v8 ignore stop */
|
||||
|
||||
const { class: attrClass, ...restAttrs } = attrs as Record<string, unknown>
|
||||
|
||||
return h(
|
||||
'span',
|
||||
{
|
||||
class: cn(badgeClass, attrClass as ClassValue),
|
||||
...restAttrs,
|
||||
},
|
||||
children,
|
||||
)
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
46
packages/ui/src/components/OsBadge/badge.variants.ts
Normal file
46
packages/ui/src/components/OsBadge/badge.variants.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { cva } from 'class-variance-authority'
|
||||
|
||||
import type { VariantProps } from 'class-variance-authority'
|
||||
|
||||
/**
|
||||
* Badge variants using CVA (Class Variance Authority)
|
||||
*
|
||||
* Non-interactive label for metadata display and form counters.
|
||||
*/
|
||||
export const badgeVariants = cva(
|
||||
['inline-flex w-fit items-center gap-[0.25em] font-semibold leading-[1.3]'],
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'text-[var(--color-default-contrast)] bg-[var(--color-default)]',
|
||||
primary: 'text-[var(--color-primary-contrast)] bg-[var(--color-primary)]',
|
||||
danger: 'text-[var(--color-danger-contrast)] bg-[var(--color-danger)]',
|
||||
},
|
||||
size: {
|
||||
sm: 'text-[0.75rem] py-[0.2em] px-[0.8em]',
|
||||
md: 'text-[0.875rem] py-[0.15em] px-[1em]',
|
||||
lg: 'text-[1rem] py-[0.15em] px-[1.2em]',
|
||||
},
|
||||
shape: {
|
||||
pill: 'rounded-[2em]',
|
||||
square: 'rounded-[0.25em]',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
size: 'sm',
|
||||
shape: 'pill',
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
export type BadgeVariants = VariantProps<typeof badgeVariants>
|
||||
|
||||
/** Badge color variant: default | primary | danger */
|
||||
export type BadgeVariant = NonNullable<BadgeVariants['variant']>
|
||||
|
||||
/** Badge-specific size subset: sm | md | lg */
|
||||
export type BadgeSize = NonNullable<BadgeVariants['size']>
|
||||
|
||||
/** Badge shape: pill | square */
|
||||
export type BadgeShape = NonNullable<BadgeVariants['shape']>
|
||||
8
packages/ui/src/components/OsBadge/index.ts
Normal file
8
packages/ui/src/components/OsBadge/index.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export { default as OsBadge } from './OsBadge.vue'
|
||||
export {
|
||||
badgeVariants,
|
||||
type BadgeShape,
|
||||
type BadgeSize,
|
||||
type BadgeVariant,
|
||||
type BadgeVariants,
|
||||
} from './badge.variants'
|
||||
@ -19,3 +19,11 @@ export {
|
||||
} from './OsIcon'
|
||||
export { OsSpinner, SPINNER_SIZES } from './OsSpinner'
|
||||
export { OsCard } from './OsCard'
|
||||
export {
|
||||
OsBadge,
|
||||
badgeVariants,
|
||||
type BadgeShape,
|
||||
type BadgeSize,
|
||||
type BadgeVariant,
|
||||
type BadgeVariants,
|
||||
} from './OsBadge'
|
||||
|
||||
@ -1,13 +1,15 @@
|
||||
/* eslint-disable import-x/no-deprecated */
|
||||
import { describe, it, expect, vi, afterEach, expectTypeOf } from 'vitest'
|
||||
|
||||
import { ocelotPreset, requiredCssVariables, validateCssVariables } from './tailwind.preset'
|
||||
|
||||
describe('tailwind.preset', () => {
|
||||
describe('ocelotPreset', () => {
|
||||
it('exports a valid Tailwind preset with theme.extend structure', () => {
|
||||
describe('ocelotPreset (deprecated)', () => {
|
||||
it('exports empty preset for backward compatibility', () => {
|
||||
expect(ocelotPreset).toBeDefined()
|
||||
expect(ocelotPreset).toHaveProperty('theme')
|
||||
expect(ocelotPreset.theme).toHaveProperty('extend')
|
||||
expect(ocelotPreset.theme.extend).toStrictEqual({})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -1,29 +1,23 @@
|
||||
/**
|
||||
* Tailwind CSS Preset for @ocelot-social/ui
|
||||
* CSS Variable contract for @ocelot-social/ui
|
||||
*
|
||||
* This preset defines CSS Custom Properties used by components.
|
||||
* The library does NOT provide default values - the consuming app must define all variables.
|
||||
* Components use CSS Custom Properties via arbitrary values (e.g. `bg-[var(--color-primary)]`)
|
||||
* rather than Tailwind theme tokens. No Tailwind preset or @config/@theme is needed.
|
||||
*
|
||||
* Branding hierarchy:
|
||||
* 1. Webapp defines default branding (base colors)
|
||||
* 2. Specialized brandings override the defaults
|
||||
*
|
||||
* Usage in your tailwind.config.js:
|
||||
* ```js
|
||||
* import { ocelotPreset } from '@ocelot-social/ui/tailwind.preset'
|
||||
*
|
||||
* export default {
|
||||
* presets: [ocelotPreset],
|
||||
* // your config...
|
||||
* The consuming app must define all required CSS variables on :root:
|
||||
* ```css
|
||||
* :root {
|
||||
* --color-primary: #6dba4a;
|
||||
* --color-primary-hover: #7ecf5a;
|
||||
* --color-primary-active: #4a8a30;
|
||||
* --color-primary-contrast: #ffffff;
|
||||
* // ... see requiredCssVariables for the full list
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Required CSS Variables (defined by webapp):
|
||||
* - See `requiredCssVariables` export for the full list
|
||||
* Use `validateCssVariables()` in development to catch missing variables early.
|
||||
*/
|
||||
|
||||
import type { Config } from 'tailwindcss'
|
||||
|
||||
/**
|
||||
* List of CSS Custom Properties that must be defined by the consuming app.
|
||||
* This list grows as components are added to the library.
|
||||
@ -32,27 +26,42 @@ export const requiredCssVariables: string[] = [
|
||||
// Primary
|
||||
'--color-primary',
|
||||
'--color-primary-hover',
|
||||
'--color-primary-active',
|
||||
'--color-primary-contrast',
|
||||
// Secondary
|
||||
'--color-secondary',
|
||||
'--color-secondary-hover',
|
||||
'--color-secondary-active',
|
||||
'--color-secondary-contrast',
|
||||
// Danger
|
||||
'--color-danger',
|
||||
'--color-danger-hover',
|
||||
'--color-danger-active',
|
||||
'--color-danger-contrast',
|
||||
// Warning
|
||||
'--color-warning',
|
||||
'--color-warning-hover',
|
||||
'--color-warning-active',
|
||||
'--color-warning-contrast',
|
||||
// Success
|
||||
'--color-success',
|
||||
'--color-success-hover',
|
||||
'--color-success-active',
|
||||
'--color-success-contrast',
|
||||
// Info
|
||||
'--color-info',
|
||||
'--color-info-hover',
|
||||
'--color-info-active',
|
||||
'--color-info-contrast',
|
||||
// Default (neutral)
|
||||
'--color-default',
|
||||
'--color-default-hover',
|
||||
'--color-default-active',
|
||||
'--color-default-contrast',
|
||||
'--color-default-contrast-inverse',
|
||||
// Disabled
|
||||
'--color-disabled',
|
||||
'--color-disabled-contrast',
|
||||
]
|
||||
|
||||
/**
|
||||
@ -84,12 +93,13 @@ export function validateCssVariables(): void {
|
||||
}
|
||||
}
|
||||
|
||||
export const ocelotPreset: Partial<Config> = {
|
||||
/**
|
||||
* @deprecated This preset is currently empty. Components use CSS Custom Properties
|
||||
* via arbitrary values (e.g. `bg-[var(--color-primary)]`) and do not require
|
||||
* Tailwind theme tokens. Kept for backward compatibility.
|
||||
*/
|
||||
export const ocelotPreset = {
|
||||
theme: {
|
||||
extend: {
|
||||
// Colors and other theme extensions will be added here as components are developed.
|
||||
// All values use CSS Custom Properties WITHOUT defaults.
|
||||
// Example: primary: { DEFAULT: 'var(--color-primary)' }
|
||||
},
|
||||
extend: {},
|
||||
},
|
||||
}
|
||||
} as const
|
||||
|
||||
@ -1,20 +1,17 @@
|
||||
<template>
|
||||
<span
|
||||
class="ds-tag ds-tag-size-base ds-tag-medium category-tag"
|
||||
:class="filterActive ? 'filterActive' : ''"
|
||||
>
|
||||
<os-badge shape="square" class="category-tag" :class="filterActive ? 'filterActive' : ''">
|
||||
<os-icon :icon="resolvedIcon" />
|
||||
{{ name }}
|
||||
</span>
|
||||
</os-badge>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { OsIcon } from '@ocelot-social/ui'
|
||||
import { OsBadge, OsIcon } from '@ocelot-social/ui'
|
||||
import { resolveIcon } from '~/utils/iconRegistry'
|
||||
|
||||
export default {
|
||||
name: 'HcCategory',
|
||||
components: { OsIcon },
|
||||
components: { OsBadge, OsIcon },
|
||||
props: {
|
||||
icon: { type: String, required: true },
|
||||
name: { type: String, default: '' },
|
||||
@ -30,14 +27,11 @@ export default {
|
||||
|
||||
<style lang="scss">
|
||||
.category-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
&.language {
|
||||
float: right;
|
||||
}
|
||||
|
||||
> .os-icon {
|
||||
margin-right: $space-xx-small;
|
||||
font-size: $font-size-base;
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,20 +39,28 @@
|
||||
autofocus
|
||||
size="large"
|
||||
/>
|
||||
<ds-chip size="base" :color="errors && errors.title && 'danger'">
|
||||
<os-badge
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
:variant="errors && errors.title ? 'danger' : undefined"
|
||||
>
|
||||
{{ formData.title.length }}/{{ formSchema.title.max }}
|
||||
<os-icon v-if="errors && errors.title" :icon="icons.warning" />
|
||||
</ds-chip>
|
||||
</os-badge>
|
||||
<editor
|
||||
:users="users"
|
||||
:value="formData.content"
|
||||
:hashtags="hashtags"
|
||||
@input="updateEditorContent"
|
||||
/>
|
||||
<ds-chip size="base" :color="errors && errors.content && 'danger'">
|
||||
<os-badge
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
:variant="errors && errors.content ? 'danger' : undefined"
|
||||
>
|
||||
{{ contentLength }}
|
||||
<os-icon v-if="errors && errors.content" :icon="icons.warning" />
|
||||
</ds-chip>
|
||||
</os-badge>
|
||||
|
||||
<!-- Eventdata -->
|
||||
<div v-if="createEvent" class="eventDatas">
|
||||
@ -81,9 +89,13 @@
|
||||
v-if="errors && errors.eventStart"
|
||||
class="chipbox event-grid-item-margin-helper"
|
||||
>
|
||||
<ds-chip size="base" :color="errors && errors.eventStart && 'danger'">
|
||||
<os-badge
|
||||
role="alert"
|
||||
aria-live="assertive"
|
||||
:variant="errors && errors.eventStart ? 'danger' : undefined"
|
||||
>
|
||||
<os-icon :icon="icons.warning" />
|
||||
</ds-chip>
|
||||
</os-badge>
|
||||
</div>
|
||||
</div>
|
||||
<div class="event-grid-item">
|
||||
@ -114,10 +126,14 @@
|
||||
:placeholder="$t('post.viewEvent.eventVenue')"
|
||||
/>
|
||||
<div class="chipbox">
|
||||
<ds-chip size="base" :color="errors && errors.eventVenue && 'danger'">
|
||||
<os-badge
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
:variant="errors && errors.eventVenue ? 'danger' : undefined"
|
||||
>
|
||||
{{ formData.eventVenue.length }}/{{ formSchema.eventVenue.max }}
|
||||
<os-icon v-if="errors && errors.eventVenue" :icon="icons.warning" />
|
||||
</ds-chip>
|
||||
</os-badge>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="showEventLocationName" class="event-grid-item">
|
||||
@ -127,10 +143,14 @@
|
||||
:placeholder="$t('post.viewEvent.eventLocationName')"
|
||||
/>
|
||||
<div class="chipbox">
|
||||
<ds-chip size="base" :color="errors && errors.eventLocationName && 'danger'">
|
||||
<os-badge
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
:variant="errors && errors.eventLocationName ? 'danger' : undefined"
|
||||
>
|
||||
{{ formData.eventLocationName.length }}/{{ formSchema.eventLocationName.max }}
|
||||
<os-icon v-if="errors && errors.eventLocationName" :icon="icons.warning" />
|
||||
</ds-chip>
|
||||
</os-badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -153,14 +173,15 @@
|
||||
model="categoryIds"
|
||||
:existingCategoryIds="formData.categoryIds"
|
||||
/>
|
||||
<ds-chip
|
||||
<os-badge
|
||||
v-if="categoriesActive"
|
||||
size="base"
|
||||
:color="errors && errors.categoryIds && 'danger'"
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
:variant="errors && errors.categoryIds ? 'danger' : undefined"
|
||||
>
|
||||
{{ formData.categoryIds.length }} / 3
|
||||
<os-icon v-if="errors && errors.categoryIds" :icon="icons.warning" />
|
||||
</ds-chip>
|
||||
</os-badge>
|
||||
<div class="ds-flex ds-flex-gap-xxx-small buttons-footer">
|
||||
<div style="flex: 3.5 0 0" class="buttons-footer-helper">
|
||||
<!-- TODO => remove v-html! only text ! no html! security first! -->
|
||||
@ -201,7 +222,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { OsButton, OsCard, OsIcon } from '@ocelot-social/ui'
|
||||
import { OsBadge, OsButton, OsCard, OsIcon } from '@ocelot-social/ui'
|
||||
import { iconRegistry } from '~/utils/iconRegistry'
|
||||
import gql from 'graphql-tag'
|
||||
import { mapGetters } from 'vuex'
|
||||
@ -222,6 +243,7 @@ export default {
|
||||
DatePicker,
|
||||
Editor,
|
||||
ImageUploader,
|
||||
OsBadge,
|
||||
OsButton,
|
||||
OsCard,
|
||||
OsIcon,
|
||||
@ -516,7 +538,7 @@ export default {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
> .ds-chip {
|
||||
> .os-badge {
|
||||
margin-top: -10px;
|
||||
}
|
||||
}
|
||||
@ -568,7 +590,7 @@ export default {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
> .ds-chip {
|
||||
> .os-badge {
|
||||
align-self: flex-end;
|
||||
margin: $space-xx-small 0 $space-base;
|
||||
cursor: default;
|
||||
|
||||
@ -17,10 +17,14 @@
|
||||
autofocus
|
||||
:placeholder="`${$t('group.name')} …`"
|
||||
/>
|
||||
<ds-chip size="base" :color="errors && errors.name ? 'danger' : 'medium'">
|
||||
<os-badge
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
:variant="errors && errors.name ? 'danger' : undefined"
|
||||
>
|
||||
{{ `${formData.name.length} / ${formSchema.name.min}–${formSchema.name.max}` }}
|
||||
<os-icon v-if="errors && errors.name" :icon="icons.warning" />
|
||||
</ds-chip>
|
||||
</os-badge>
|
||||
|
||||
<!-- group Slug -->
|
||||
<ds-input
|
||||
@ -49,16 +53,17 @@
|
||||
{{ $t(`group.typesOptions.${groupType}`) }}
|
||||
</option>
|
||||
</select>
|
||||
<ds-chip
|
||||
size="base"
|
||||
:color="errors && errors.groupType && formData.groupType === '' ? 'danger' : 'medium'"
|
||||
<os-badge
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
:variant="errors && errors.groupType && formData.groupType === '' ? 'danger' : undefined"
|
||||
>
|
||||
{{ `${formData.groupType === '' ? 0 : 1} / 1` }}
|
||||
<os-icon
|
||||
v-if="errors && errors.groupType && formData.groupType === ''"
|
||||
:icon="icons.warning"
|
||||
/>
|
||||
</ds-chip>
|
||||
</os-badge>
|
||||
|
||||
<!-- goal -->
|
||||
<ds-input
|
||||
@ -83,10 +88,14 @@
|
||||
:hashtags="null"
|
||||
@input="updateEditorDescription"
|
||||
/>
|
||||
<ds-chip size="base" :color="errors && errors.description ? 'danger' : 'medium'">
|
||||
<os-badge
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
:variant="errors && errors.description ? 'danger' : undefined"
|
||||
>
|
||||
{{ `${descriptionLength} / ${formSchema.description.min}` }}
|
||||
<os-icon v-if="errors && errors.description" :icon="icons.warning" />
|
||||
</ds-chip>
|
||||
</os-badge>
|
||||
|
||||
<!-- actionRadius -->
|
||||
<p class="ds-text select-label">
|
||||
@ -96,10 +105,11 @@
|
||||
v-model="formData.actionRadius"
|
||||
@change.native="changeActionRadius($event)"
|
||||
/>
|
||||
<ds-chip
|
||||
size="base"
|
||||
:color="
|
||||
errors && errors.actionRadius && formData.actionRadius === '' ? 'danger' : 'medium'
|
||||
<os-badge
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
:variant="
|
||||
errors && errors.actionRadius && formData.actionRadius === '' ? 'danger' : undefined
|
||||
"
|
||||
>
|
||||
{{ `${formData.actionRadius === '' ? 0 : 1} / 1` }}
|
||||
@ -107,7 +117,7 @@
|
||||
v-if="errors && errors.actionRadius && formData.actionRadius === ''"
|
||||
:icon="icons.warning"
|
||||
/>
|
||||
</ds-chip>
|
||||
</os-badge>
|
||||
|
||||
<!-- location -->
|
||||
<location-select v-model="formData.locationName" />
|
||||
@ -125,10 +135,14 @@
|
||||
name="categoryIds"
|
||||
:existingCategoryIds="formData.categoryIds"
|
||||
/>
|
||||
<ds-chip size="base" :color="errors && errors.categoryIds ? 'danger' : 'medium'">
|
||||
<os-badge
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
:variant="errors && errors.categoryIds ? 'danger' : undefined"
|
||||
>
|
||||
{{ formData.categoryIds.length }} / 3
|
||||
<os-icon v-if="errors && errors.categoryIds" :icon="icons.warning" />
|
||||
</ds-chip>
|
||||
</os-badge>
|
||||
</div>
|
||||
<!-- submit -->
|
||||
<div class="buttons ds-mt-large ds-mb-large">
|
||||
@ -151,7 +165,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { OsButton, OsIcon } from '@ocelot-social/ui'
|
||||
import { OsBadge, OsButton, OsIcon } from '@ocelot-social/ui'
|
||||
import { iconRegistry } from '~/utils/iconRegistry'
|
||||
import CategoriesSelect from '~/components/CategoriesSelect/CategoriesSelect'
|
||||
import { CATEGORIES_MIN, CATEGORIES_MAX } from '~/constants/categories.js'
|
||||
@ -173,6 +187,7 @@ export default {
|
||||
Editor,
|
||||
ActionRadiusSelect,
|
||||
LocationSelect,
|
||||
OsBadge,
|
||||
OsButton,
|
||||
OsIcon,
|
||||
},
|
||||
@ -353,12 +368,23 @@ export default {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
> .ds-chip {
|
||||
> .os-badge {
|
||||
align-self: flex-end;
|
||||
margin: $space-xx-small 0 $space-base;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
> div:not(.buttons) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
> .os-badge {
|
||||
align-self: flex-end;
|
||||
margin: $space-xx-small 0 $space-base;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
> .select-field {
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
@ -48,9 +48,9 @@
|
||||
{{ $t(`group.roles.${role}`) }}
|
||||
</option>
|
||||
</select>
|
||||
<ds-chip v-else color="primary">
|
||||
<os-badge v-else variant="primary">
|
||||
{{ $t(`group.roles.${scope.row.membership.role}`) }}
|
||||
</ds-chip>
|
||||
</os-badge>
|
||||
</template>
|
||||
<template #edit="scope">
|
||||
<os-button
|
||||
@ -83,7 +83,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { OsButton, OsIcon } from '@ocelot-social/ui'
|
||||
import { OsBadge, OsButton, OsIcon } from '@ocelot-social/ui'
|
||||
import { iconRegistry } from '~/utils/iconRegistry'
|
||||
import { changeGroupMemberRoleMutation, removeUserFromGroupMutation } from '~/graphql/groups.js'
|
||||
import ProfileAvatar from '~/components/_new/generic/ProfileAvatar/ProfileAvatar'
|
||||
@ -91,6 +91,7 @@ import ProfileAvatar from '~/components/_new/generic/ProfileAvatar/ProfileAvatar
|
||||
export default {
|
||||
name: 'GroupMember',
|
||||
components: {
|
||||
OsBadge,
|
||||
OsButton,
|
||||
OsIcon,
|
||||
ProfileAvatar,
|
||||
|
||||
@ -30,17 +30,17 @@
|
||||
<footer class="footer">
|
||||
<div>
|
||||
<!-- group my role in group -->
|
||||
<ds-chip v-if="group && group.myRole" color="primary">
|
||||
<os-badge v-if="group && group.myRole" variant="primary">
|
||||
{{ group && group.myRole ? $t('group.roles.' + group.myRole) : '' }}
|
||||
</ds-chip>
|
||||
</os-badge>
|
||||
<!-- group type -->
|
||||
<ds-chip color="primary">
|
||||
<os-badge variant="primary">
|
||||
{{ group && group.groupType ? $t('group.types.' + group.groupType) : '' }}
|
||||
</ds-chip>
|
||||
</os-badge>
|
||||
<!-- group action radius -->
|
||||
<ds-chip color="primary">
|
||||
<os-badge variant="primary">
|
||||
{{ group && group.actionRadius ? $t('group.actionRadii.' + group.actionRadius) : '' }}
|
||||
</ds-chip>
|
||||
</os-badge>
|
||||
</div>
|
||||
<!-- group categories -->
|
||||
<div
|
||||
@ -70,7 +70,7 @@
|
||||
{{ $t('group.goal') }}
|
||||
</p>
|
||||
<div class="chip">
|
||||
<ds-chip v-if="group && group.about">{{ group ? group.about : '' }}</ds-chip>
|
||||
<os-badge v-if="group && group.about">{{ group.about }}</os-badge>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
@ -79,7 +79,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { OsCard, OsIcon } from '@ocelot-social/ui'
|
||||
import { OsBadge, OsCard, OsIcon } from '@ocelot-social/ui'
|
||||
import { iconRegistry } from '~/utils/iconRegistry'
|
||||
import Category from '~/components/Category'
|
||||
import GroupContentMenu from '~/components/ContentMenu/GroupContentMenu'
|
||||
@ -91,6 +91,7 @@ export default {
|
||||
components: {
|
||||
Category,
|
||||
GroupContentMenu,
|
||||
OsBadge,
|
||||
OsCard,
|
||||
OsIcon,
|
||||
},
|
||||
|
||||
@ -1,12 +1,15 @@
|
||||
<template>
|
||||
<span class="ds-tag ds-tag-size-base ds-tag-medium hc-hashtag">
|
||||
<os-badge shape="square" class="hc-hashtag">
|
||||
<nuxt-link :to="hashtagUrl">#{{ id }}</nuxt-link>
|
||||
</span>
|
||||
</os-badge>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { OsBadge } from '@ocelot-social/ui'
|
||||
|
||||
export default {
|
||||
name: 'HcHashtag',
|
||||
components: { OsBadge },
|
||||
props: {
|
||||
id: { type: String, required: true },
|
||||
},
|
||||
|
||||
@ -330,6 +330,7 @@ export default {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
padding-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.content {
|
||||
@ -357,7 +358,7 @@ export default {
|
||||
z-index: $z-index-post-teaser-link;
|
||||
}
|
||||
|
||||
.ds-tag {
|
||||
.os-badge {
|
||||
margin: 0;
|
||||
margin-right: $space-xx-small;
|
||||
}
|
||||
|
||||
@ -412,12 +412,11 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a close
|
||||
class="chip"
|
||||
>
|
||||
<span
|
||||
class="ds-chip ds-chip-size-base ds-chip-primary ds-chip-round"
|
||||
class="os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-primary-contrast)] bg-[var(--color-primary)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em]"
|
||||
>
|
||||
|
||||
group.roles.owner
|
||||
|
||||
<!---->
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -434,12 +433,11 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a close
|
||||
class="chip"
|
||||
>
|
||||
<span
|
||||
class="ds-chip ds-chip-size-base ds-chip-primary ds-chip-round"
|
||||
class="os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-primary-contrast)] bg-[var(--color-primary)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em]"
|
||||
>
|
||||
|
||||
group.types.closed
|
||||
|
||||
<!---->
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -456,12 +454,11 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a close
|
||||
class="chip"
|
||||
>
|
||||
<span
|
||||
class="ds-chip ds-chip-size-base ds-chip-primary ds-chip-round"
|
||||
class="os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-primary-contrast)] bg-[var(--color-primary)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em]"
|
||||
>
|
||||
|
||||
group.actionRadii.national
|
||||
|
||||
<!---->
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -494,7 +491,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a close
|
||||
align="center"
|
||||
>
|
||||
<span
|
||||
class="ds-tag ds-tag-size-base ds-tag-medium category-tag has-tooltip"
|
||||
class="category-tag os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[0.25em] category-tag has-tooltip"
|
||||
data-original-title="null"
|
||||
>
|
||||
<!---->
|
||||
@ -511,7 +508,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a close
|
||||
align="center"
|
||||
>
|
||||
<span
|
||||
class="ds-tag ds-tag-size-base ds-tag-medium category-tag has-tooltip"
|
||||
class="category-tag os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[0.25em] category-tag has-tooltip"
|
||||
data-original-title="null"
|
||||
>
|
||||
<!---->
|
||||
@ -547,10 +544,9 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a close
|
||||
class="chip"
|
||||
>
|
||||
<span
|
||||
class="ds-chip ds-chip-size-base ds-chip-medium ds-chip-round"
|
||||
class="os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em]"
|
||||
>
|
||||
Our children shall receive education for life.
|
||||
<!---->
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -1311,12 +1307,11 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a close
|
||||
class="chip"
|
||||
>
|
||||
<span
|
||||
class="ds-chip ds-chip-size-base ds-chip-primary ds-chip-round"
|
||||
class="os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-primary-contrast)] bg-[var(--color-primary)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em]"
|
||||
>
|
||||
|
||||
group.types.closed
|
||||
|
||||
<!---->
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -1333,12 +1328,11 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a close
|
||||
class="chip"
|
||||
>
|
||||
<span
|
||||
class="ds-chip ds-chip-size-base ds-chip-primary ds-chip-round"
|
||||
class="os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-primary-contrast)] bg-[var(--color-primary)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em]"
|
||||
>
|
||||
|
||||
group.actionRadii.national
|
||||
|
||||
<!---->
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -1371,7 +1365,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a close
|
||||
align="center"
|
||||
>
|
||||
<span
|
||||
class="ds-tag ds-tag-size-base ds-tag-medium category-tag has-tooltip"
|
||||
class="category-tag os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[0.25em] category-tag has-tooltip"
|
||||
data-original-title="null"
|
||||
>
|
||||
<!---->
|
||||
@ -1388,7 +1382,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a close
|
||||
align="center"
|
||||
>
|
||||
<span
|
||||
class="ds-tag ds-tag-size-base ds-tag-medium category-tag has-tooltip"
|
||||
class="category-tag os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[0.25em] category-tag has-tooltip"
|
||||
data-original-title="null"
|
||||
>
|
||||
<!---->
|
||||
@ -1424,10 +1418,9 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a close
|
||||
class="chip"
|
||||
>
|
||||
<span
|
||||
class="ds-chip ds-chip-size-base ds-chip-medium ds-chip-round"
|
||||
class="os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em]"
|
||||
>
|
||||
Our children shall receive education for life.
|
||||
<!---->
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -1817,12 +1810,11 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a close
|
||||
class="chip"
|
||||
>
|
||||
<span
|
||||
class="ds-chip ds-chip-size-base ds-chip-primary ds-chip-round"
|
||||
class="os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-primary-contrast)] bg-[var(--color-primary)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em]"
|
||||
>
|
||||
|
||||
group.roles.pending
|
||||
|
||||
<!---->
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -1839,12 +1831,11 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a close
|
||||
class="chip"
|
||||
>
|
||||
<span
|
||||
class="ds-chip ds-chip-size-base ds-chip-primary ds-chip-round"
|
||||
class="os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-primary-contrast)] bg-[var(--color-primary)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em]"
|
||||
>
|
||||
|
||||
group.types.closed
|
||||
|
||||
<!---->
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -1861,12 +1852,11 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a close
|
||||
class="chip"
|
||||
>
|
||||
<span
|
||||
class="ds-chip ds-chip-size-base ds-chip-primary ds-chip-round"
|
||||
class="os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-primary-contrast)] bg-[var(--color-primary)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em]"
|
||||
>
|
||||
|
||||
group.actionRadii.national
|
||||
|
||||
<!---->
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -1899,7 +1889,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a close
|
||||
align="center"
|
||||
>
|
||||
<span
|
||||
class="ds-tag ds-tag-size-base ds-tag-medium category-tag has-tooltip"
|
||||
class="category-tag os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[0.25em] category-tag has-tooltip"
|
||||
data-original-title="null"
|
||||
>
|
||||
<!---->
|
||||
@ -1916,7 +1906,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a close
|
||||
align="center"
|
||||
>
|
||||
<span
|
||||
class="ds-tag ds-tag-size-base ds-tag-medium category-tag has-tooltip"
|
||||
class="category-tag os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[0.25em] category-tag has-tooltip"
|
||||
data-original-title="null"
|
||||
>
|
||||
<!---->
|
||||
@ -1952,10 +1942,9 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a close
|
||||
class="chip"
|
||||
>
|
||||
<span
|
||||
class="ds-chip ds-chip-size-base ds-chip-medium ds-chip-round"
|
||||
class="os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em]"
|
||||
>
|
||||
Our children shall receive education for life.
|
||||
<!---->
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -2477,12 +2466,11 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a close
|
||||
class="chip"
|
||||
>
|
||||
<span
|
||||
class="ds-chip ds-chip-size-base ds-chip-primary ds-chip-round"
|
||||
class="os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-primary-contrast)] bg-[var(--color-primary)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em]"
|
||||
>
|
||||
|
||||
group.roles.usual
|
||||
|
||||
<!---->
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -2499,12 +2487,11 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a close
|
||||
class="chip"
|
||||
>
|
||||
<span
|
||||
class="ds-chip ds-chip-size-base ds-chip-primary ds-chip-round"
|
||||
class="os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-primary-contrast)] bg-[var(--color-primary)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em]"
|
||||
>
|
||||
|
||||
group.types.closed
|
||||
|
||||
<!---->
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -2521,12 +2508,11 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a close
|
||||
class="chip"
|
||||
>
|
||||
<span
|
||||
class="ds-chip ds-chip-size-base ds-chip-primary ds-chip-round"
|
||||
class="os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-primary-contrast)] bg-[var(--color-primary)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em]"
|
||||
>
|
||||
|
||||
group.actionRadii.national
|
||||
|
||||
<!---->
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -2559,7 +2545,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a close
|
||||
align="center"
|
||||
>
|
||||
<span
|
||||
class="ds-tag ds-tag-size-base ds-tag-medium category-tag has-tooltip"
|
||||
class="category-tag os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[0.25em] category-tag has-tooltip"
|
||||
data-original-title="null"
|
||||
>
|
||||
<!---->
|
||||
@ -2576,7 +2562,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a close
|
||||
align="center"
|
||||
>
|
||||
<span
|
||||
class="ds-tag ds-tag-size-base ds-tag-medium category-tag has-tooltip"
|
||||
class="category-tag os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[0.25em] category-tag has-tooltip"
|
||||
data-original-title="null"
|
||||
>
|
||||
<!---->
|
||||
@ -2612,10 +2598,9 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a close
|
||||
class="chip"
|
||||
>
|
||||
<span
|
||||
class="ds-chip ds-chip-size-base ds-chip-medium ds-chip-round"
|
||||
class="os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em]"
|
||||
>
|
||||
Our children shall receive education for life.
|
||||
<!---->
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -3532,12 +3517,11 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
class="chip"
|
||||
>
|
||||
<span
|
||||
class="ds-chip ds-chip-size-base ds-chip-primary ds-chip-round"
|
||||
class="os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-primary-contrast)] bg-[var(--color-primary)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em]"
|
||||
>
|
||||
|
||||
group.roles.owner
|
||||
|
||||
<!---->
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -3554,12 +3538,11 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
class="chip"
|
||||
>
|
||||
<span
|
||||
class="ds-chip ds-chip-size-base ds-chip-primary ds-chip-round"
|
||||
class="os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-primary-contrast)] bg-[var(--color-primary)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em]"
|
||||
>
|
||||
|
||||
group.types.public
|
||||
|
||||
<!---->
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -3576,12 +3559,11 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
class="chip"
|
||||
>
|
||||
<span
|
||||
class="ds-chip ds-chip-size-base ds-chip-primary ds-chip-round"
|
||||
class="os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-primary-contrast)] bg-[var(--color-primary)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em]"
|
||||
>
|
||||
|
||||
group.actionRadii.interplanetary
|
||||
|
||||
<!---->
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -3614,7 +3596,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
align="center"
|
||||
>
|
||||
<span
|
||||
class="ds-tag ds-tag-size-base ds-tag-medium category-tag has-tooltip"
|
||||
class="category-tag os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[0.25em] category-tag has-tooltip"
|
||||
data-original-title="null"
|
||||
>
|
||||
<!---->
|
||||
@ -3631,7 +3613,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
align="center"
|
||||
>
|
||||
<span
|
||||
class="ds-tag ds-tag-size-base ds-tag-medium category-tag has-tooltip"
|
||||
class="category-tag os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[0.25em] category-tag has-tooltip"
|
||||
data-original-title="null"
|
||||
>
|
||||
<!---->
|
||||
@ -3648,7 +3630,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
align="center"
|
||||
>
|
||||
<span
|
||||
class="ds-tag ds-tag-size-base ds-tag-medium category-tag has-tooltip"
|
||||
class="category-tag os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[0.25em] category-tag has-tooltip"
|
||||
data-original-title="null"
|
||||
>
|
||||
<!---->
|
||||
@ -4399,12 +4381,11 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
class="chip"
|
||||
>
|
||||
<span
|
||||
class="ds-chip ds-chip-size-base ds-chip-primary ds-chip-round"
|
||||
class="os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-primary-contrast)] bg-[var(--color-primary)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em]"
|
||||
>
|
||||
|
||||
group.types.public
|
||||
|
||||
<!---->
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -4421,12 +4402,11 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
class="chip"
|
||||
>
|
||||
<span
|
||||
class="ds-chip ds-chip-size-base ds-chip-primary ds-chip-round"
|
||||
class="os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-primary-contrast)] bg-[var(--color-primary)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em]"
|
||||
>
|
||||
|
||||
group.actionRadii.interplanetary
|
||||
|
||||
<!---->
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -4459,7 +4439,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
align="center"
|
||||
>
|
||||
<span
|
||||
class="ds-tag ds-tag-size-base ds-tag-medium category-tag has-tooltip"
|
||||
class="category-tag os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[0.25em] category-tag has-tooltip"
|
||||
data-original-title="null"
|
||||
>
|
||||
<!---->
|
||||
@ -4476,7 +4456,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
align="center"
|
||||
>
|
||||
<span
|
||||
class="ds-tag ds-tag-size-base ds-tag-medium category-tag has-tooltip"
|
||||
class="category-tag os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[0.25em] category-tag has-tooltip"
|
||||
data-original-title="null"
|
||||
>
|
||||
<!---->
|
||||
@ -4493,7 +4473,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
align="center"
|
||||
>
|
||||
<span
|
||||
class="ds-tag ds-tag-size-base ds-tag-medium category-tag has-tooltip"
|
||||
class="category-tag os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[0.25em] category-tag has-tooltip"
|
||||
data-original-title="null"
|
||||
>
|
||||
<!---->
|
||||
@ -5208,12 +5188,11 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
class="chip"
|
||||
>
|
||||
<span
|
||||
class="ds-chip ds-chip-size-base ds-chip-primary ds-chip-round"
|
||||
class="os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-primary-contrast)] bg-[var(--color-primary)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em]"
|
||||
>
|
||||
|
||||
group.roles.pending
|
||||
|
||||
<!---->
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -5230,12 +5209,11 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
class="chip"
|
||||
>
|
||||
<span
|
||||
class="ds-chip ds-chip-size-base ds-chip-primary ds-chip-round"
|
||||
class="os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-primary-contrast)] bg-[var(--color-primary)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em]"
|
||||
>
|
||||
|
||||
group.types.public
|
||||
|
||||
<!---->
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -5252,12 +5230,11 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
class="chip"
|
||||
>
|
||||
<span
|
||||
class="ds-chip ds-chip-size-base ds-chip-primary ds-chip-round"
|
||||
class="os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-primary-contrast)] bg-[var(--color-primary)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em]"
|
||||
>
|
||||
|
||||
group.actionRadii.interplanetary
|
||||
|
||||
<!---->
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -5290,7 +5267,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
align="center"
|
||||
>
|
||||
<span
|
||||
class="ds-tag ds-tag-size-base ds-tag-medium category-tag has-tooltip"
|
||||
class="category-tag os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[0.25em] category-tag has-tooltip"
|
||||
data-original-title="null"
|
||||
>
|
||||
<!---->
|
||||
@ -5307,7 +5284,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
align="center"
|
||||
>
|
||||
<span
|
||||
class="ds-tag ds-tag-size-base ds-tag-medium category-tag has-tooltip"
|
||||
class="category-tag os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[0.25em] category-tag has-tooltip"
|
||||
data-original-title="null"
|
||||
>
|
||||
<!---->
|
||||
@ -5324,7 +5301,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
align="center"
|
||||
>
|
||||
<span
|
||||
class="ds-tag ds-tag-size-base ds-tag-medium category-tag has-tooltip"
|
||||
class="category-tag os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[0.25em] category-tag has-tooltip"
|
||||
data-original-title="null"
|
||||
>
|
||||
<!---->
|
||||
@ -6134,12 +6111,11 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
class="chip"
|
||||
>
|
||||
<span
|
||||
class="ds-chip ds-chip-size-base ds-chip-primary ds-chip-round"
|
||||
class="os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-primary-contrast)] bg-[var(--color-primary)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em]"
|
||||
>
|
||||
|
||||
group.roles.usual
|
||||
|
||||
<!---->
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -6156,12 +6132,11 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
class="chip"
|
||||
>
|
||||
<span
|
||||
class="ds-chip ds-chip-size-base ds-chip-primary ds-chip-round"
|
||||
class="os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-primary-contrast)] bg-[var(--color-primary)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em]"
|
||||
>
|
||||
|
||||
group.types.public
|
||||
|
||||
<!---->
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -6178,12 +6153,11 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
class="chip"
|
||||
>
|
||||
<span
|
||||
class="ds-chip ds-chip-size-base ds-chip-primary ds-chip-round"
|
||||
class="os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-primary-contrast)] bg-[var(--color-primary)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em]"
|
||||
>
|
||||
|
||||
group.actionRadii.interplanetary
|
||||
|
||||
<!---->
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -6216,7 +6190,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
align="center"
|
||||
>
|
||||
<span
|
||||
class="ds-tag ds-tag-size-base ds-tag-medium category-tag has-tooltip"
|
||||
class="category-tag os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[0.25em] category-tag has-tooltip"
|
||||
data-original-title="null"
|
||||
>
|
||||
<!---->
|
||||
@ -6233,7 +6207,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
align="center"
|
||||
>
|
||||
<span
|
||||
class="ds-tag ds-tag-size-base ds-tag-medium category-tag has-tooltip"
|
||||
class="category-tag os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[0.25em] category-tag has-tooltip"
|
||||
data-original-title="null"
|
||||
>
|
||||
<!---->
|
||||
@ -6250,7 +6224,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a curre
|
||||
align="center"
|
||||
>
|
||||
<span
|
||||
class="ds-tag ds-tag-size-base ds-tag-medium category-tag has-tooltip"
|
||||
class="category-tag os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[0.25em] category-tag has-tooltip"
|
||||
data-original-title="null"
|
||||
>
|
||||
<!---->
|
||||
@ -7215,12 +7189,11 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a hidde
|
||||
class="chip"
|
||||
>
|
||||
<span
|
||||
class="ds-chip ds-chip-size-base ds-chip-primary ds-chip-round"
|
||||
class="os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-primary-contrast)] bg-[var(--color-primary)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em]"
|
||||
>
|
||||
|
||||
group.roles.owner
|
||||
|
||||
<!---->
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -7237,12 +7210,11 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a hidde
|
||||
class="chip"
|
||||
>
|
||||
<span
|
||||
class="ds-chip ds-chip-size-base ds-chip-primary ds-chip-round"
|
||||
class="os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-primary-contrast)] bg-[var(--color-primary)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em]"
|
||||
>
|
||||
|
||||
group.types.hidden
|
||||
|
||||
<!---->
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -7259,12 +7231,11 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a hidde
|
||||
class="chip"
|
||||
>
|
||||
<span
|
||||
class="ds-chip ds-chip-size-base ds-chip-primary ds-chip-round"
|
||||
class="os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-primary-contrast)] bg-[var(--color-primary)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em]"
|
||||
>
|
||||
|
||||
group.actionRadii.global
|
||||
|
||||
<!---->
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -7297,7 +7268,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a hidde
|
||||
align="center"
|
||||
>
|
||||
<span
|
||||
class="ds-tag ds-tag-size-base ds-tag-medium category-tag has-tooltip"
|
||||
class="category-tag os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[0.25em] category-tag has-tooltip"
|
||||
data-original-title="null"
|
||||
>
|
||||
<!---->
|
||||
@ -7314,7 +7285,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a hidde
|
||||
align="center"
|
||||
>
|
||||
<span
|
||||
class="ds-tag ds-tag-size-base ds-tag-medium category-tag has-tooltip"
|
||||
class="category-tag os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[0.25em] category-tag has-tooltip"
|
||||
data-original-title="null"
|
||||
>
|
||||
<span
|
||||
@ -7344,7 +7315,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a hidde
|
||||
align="center"
|
||||
>
|
||||
<span
|
||||
class="ds-tag ds-tag-size-base ds-tag-medium category-tag has-tooltip"
|
||||
class="category-tag os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[0.25em] category-tag has-tooltip"
|
||||
data-original-title="null"
|
||||
>
|
||||
<!---->
|
||||
@ -7380,10 +7351,9 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a hidde
|
||||
class="chip"
|
||||
>
|
||||
<span
|
||||
class="ds-chip ds-chip-size-base ds-chip-medium ds-chip-round"
|
||||
class="os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em]"
|
||||
>
|
||||
Investigative journalists share ideas and insights and can collaborate.
|
||||
<!---->
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -8272,12 +8242,11 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a hidde
|
||||
class="chip"
|
||||
>
|
||||
<span
|
||||
class="ds-chip ds-chip-size-base ds-chip-primary ds-chip-round"
|
||||
class="os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-primary-contrast)] bg-[var(--color-primary)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em]"
|
||||
>
|
||||
|
||||
group.roles.usual
|
||||
|
||||
<!---->
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -8294,12 +8263,11 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a hidde
|
||||
class="chip"
|
||||
>
|
||||
<span
|
||||
class="ds-chip ds-chip-size-base ds-chip-primary ds-chip-round"
|
||||
class="os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-primary-contrast)] bg-[var(--color-primary)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em]"
|
||||
>
|
||||
|
||||
group.types.hidden
|
||||
|
||||
<!---->
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -8316,12 +8284,11 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a hidde
|
||||
class="chip"
|
||||
>
|
||||
<span
|
||||
class="ds-chip ds-chip-size-base ds-chip-primary ds-chip-round"
|
||||
class="os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-primary-contrast)] bg-[var(--color-primary)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em]"
|
||||
>
|
||||
|
||||
group.actionRadii.global
|
||||
|
||||
<!---->
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -8354,7 +8321,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a hidde
|
||||
align="center"
|
||||
>
|
||||
<span
|
||||
class="ds-tag ds-tag-size-base ds-tag-medium category-tag has-tooltip"
|
||||
class="category-tag os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[0.25em] category-tag has-tooltip"
|
||||
data-original-title="null"
|
||||
>
|
||||
<!---->
|
||||
@ -8371,7 +8338,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a hidde
|
||||
align="center"
|
||||
>
|
||||
<span
|
||||
class="ds-tag ds-tag-size-base ds-tag-medium category-tag has-tooltip"
|
||||
class="category-tag os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[0.25em] category-tag has-tooltip"
|
||||
data-original-title="null"
|
||||
>
|
||||
<span
|
||||
@ -8401,7 +8368,7 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a hidde
|
||||
align="center"
|
||||
>
|
||||
<span
|
||||
class="ds-tag ds-tag-size-base ds-tag-medium category-tag has-tooltip"
|
||||
class="category-tag os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[0.25em] category-tag has-tooltip"
|
||||
data-original-title="null"
|
||||
>
|
||||
<!---->
|
||||
@ -8437,10 +8404,9 @@ exports[`GroupProfileSlug given a puplic group – "yoga-practice" given a hidde
|
||||
class="chip"
|
||||
>
|
||||
<span
|
||||
class="ds-chip ds-chip-size-base ds-chip-medium ds-chip-round"
|
||||
class="os-badge inline-flex w-fit items-center gap-[0.25em] font-semibold text-[var(--color-default-contrast)] bg-[var(--color-default)] text-[0.75rem] py-[0.2em] px-[0.8em] rounded-[2em]"
|
||||
>
|
||||
Investigative journalists share ideas and insights and can collaborate.
|
||||
<!---->
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -92,9 +92,9 @@
|
||||
{{ $t('group.role') }}
|
||||
</p>
|
||||
<div class="chip" align="center">
|
||||
<ds-chip color="primary">
|
||||
<os-badge variant="primary">
|
||||
{{ group && group.myRole ? $t('group.roles.' + group.myRole) : '' }}
|
||||
</ds-chip>
|
||||
</os-badge>
|
||||
</div>
|
||||
</template>
|
||||
<!-- group type -->
|
||||
@ -102,20 +102,20 @@
|
||||
{{ $t('group.type') }}
|
||||
</p>
|
||||
<div class="chip" align="center">
|
||||
<ds-chip color="primary">
|
||||
<os-badge variant="primary">
|
||||
{{ group && group.groupType ? $t('group.types.' + group.groupType) : '' }}
|
||||
</ds-chip>
|
||||
</os-badge>
|
||||
</div>
|
||||
<!-- group action radius -->
|
||||
<p class="ds-text ds-text-soft ds-text-size-small centered-text hyphenate-text">
|
||||
{{ $t('group.actionRadius') }}
|
||||
</p>
|
||||
<div class="chip" align="center">
|
||||
<ds-chip color="primary">
|
||||
<os-badge variant="primary">
|
||||
{{
|
||||
group && group.actionRadius ? $t('group.actionRadii.' + group.actionRadius) : ''
|
||||
}}
|
||||
</ds-chip>
|
||||
</os-badge>
|
||||
</div>
|
||||
<div class="ds-my-x-small"></div>
|
||||
</div>
|
||||
@ -165,7 +165,7 @@
|
||||
</p>
|
||||
<div class="ds-my-xx-small"></div>
|
||||
<div class="chip" align="center">
|
||||
<ds-chip>{{ group ? group.about : '' }}</ds-chip>
|
||||
<os-badge>{{ group.about }}</os-badge>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -293,7 +293,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { OsButton, OsCard, OsIcon, OsSpinner } from '@ocelot-social/ui'
|
||||
import { OsBadge, OsButton, OsCard, OsIcon, OsSpinner } from '@ocelot-social/ui'
|
||||
import { iconRegistry } from '~/utils/iconRegistry'
|
||||
import uniqBy from 'lodash/uniqBy'
|
||||
import { profilePagePosts } from '~/graphql/PostQuery'
|
||||
@ -330,6 +330,7 @@ import GetCategories from '~/mixins/getCategoriesMixin.js'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
OsBadge,
|
||||
OsCard,
|
||||
OsButton,
|
||||
OsIcon,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user