feat(package/ui): os-badge (#9250)

This commit is contained in:
Ulf Gebhardt 2026-02-20 05:49:02 +01:00 committed by GitHub
parent 0e23f4ec4e
commit 951a24f100
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 865 additions and 253 deletions

View File

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

View File

@ -81,10 +81,10 @@ Phase 0: ██████████ 100% (6/6 Aufgaben) ✅
Phase 1: ██████████ 100% (6/6 Aufgaben) ✅
Phase 2: ██████████ 100% (26/26 Aufgaben) ✅
Phase 3: ██████████ 100% (24/24 Aufgaben) ✅ - Webapp-Integration komplett
Phase 4: █████░░░░░ 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: `&nbsp;` aus Aside-Div entfernt (Screenreader-Problem) + zwei Style-Blöcke zu einem scoped Block gemergt
- [x] post/create/_type.vue: Zwei Style-Blöcke (scoped + unscoped) zu einem scoped Block gemergt
- [x] _ds-compat.scss: `display: flex` zu `.ds-space-centered` hinzugefügt (justify-content/align-items wirkten ohne display:flex nicht)
- [x] blocked-users.vue: Unnötiges Wrapper-Div `ds-mb-large` am Kartenende entfernt
- [x] Cypress SocialMedia: `cy.get('.ds-list-item a')``cy.get('.ds-list-item a[href="${link}"]')` (nutzt link-Parameter)
- [x] Als "nicht in diesem PR" bewertet: pre-existing Code-Style Nitpicks (required:false, Ternary-Syntax, Prop-Validierung, Spacer-Divs, flex:1 0 0 Einheit, v-if="user", CSS-Nesting, Dark-Mode SVG-Farbe)
**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?) |

View 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()
})
})
})

View 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>
`,
}),
}

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

View 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

View 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']>

View 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'

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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