refactor(webapp): ds-radio -> html (#9403)

This commit is contained in:
Ulf Gebhardt 2026-03-14 18:35:54 +01:00 committed by GitHub
parent 83df85001d
commit 4727eb6eb4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 100 additions and 25 deletions

View File

@ -4,7 +4,7 @@ defineStep(/^I confirm the reporting dialog .*:$/, message => {
cy.contains(message) // wait for element to become visible
cy.get('.os-modal')
.within(() => {
cy.get('.ds-radio-option-label')
cy.get('.report-radio-option')
.first()
.click({
force: true

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 ████████░░ 80% (ds-chip→OsBadge✅, ds-tag→OsBadge✅, ds-grid✅, ds-number→OsNumber✅, ds-radio⬜)
Phase 4: Tier B ██████████ 100% (ds-chip→OsBadge✅, ds-tag→OsBadge✅, ds-grid✅, ds-number→OsNumber✅, ds-radio→HTML✅)
Phase 4: Tier B+ ████████░░ 75% (ds-table→HTML✅) | Tier 2 begonnen (OsModal✅) | Rest ausstehend (OsInput, OsMenu, OsSelect)
```
@ -27,7 +27,7 @@ Phase 4: Tier B+ ████████░░ 75% (ds-table→HTML✅)
| ✅ → Plain HTML | Section, Placeholder, List, ListItem, Container, Heading, Text, Space, Flex, FlexItem, Grid, GridItem, Table (13) |
| ✅ → OsBadge | Chip (20 Nutzungen → OsBadge), Tag (3 → OsBadge shape="square") |
| ✅ → OsNumber | Number (5 Nutzungen → OsNumber, CountTo.vue gelöscht, vue-count-to entfernt) |
| ⬜ → Plain HTML | Radio (1) — Tier B Rest |
| ✅ → Plain HTML | Radio (1 Datei → native `<input type="radio">` in ReportModal) |
| ⬜ → UI-Library | Modal, Input, Menu, MenuItem, Select (5) — Tier 2-3 |
| ⬜ Offen | Form (18 Dateien — HTML oder OsForm?) |
| ⬜ Nicht in Webapp | Code, CopyField, FormItem, InputError, InputLabel, Page, PageTitle, Logo, Avatar, TableCol, TableHeadCol (11) |
@ -76,7 +76,7 @@ Phase 4: Tier B+ ████████░░ 75% (ds-table→HTML✅)
| 17 | Input | ⬜ Tier 2 | 23 Dateien → OsInput (gekoppelt mit Form) |
| 18 | InputError | ⬜ Nicht genutzt | Intern von Input genutzt |
| 19 | InputLabel | ⬜ Nicht genutzt | Intern von Input genutzt |
| 20 | Radio | ⬜ Tier B | 1 Datei → native `<input type="radio">` |
| 20 | Radio | ✅ → HTML | 1 Datei → native `<input type="radio">` + `<fieldset>` (ReportModal) |
| 21 | Select | ⬜ Tier 4 | 3 Dateien → OsSelect |
### Layout
@ -439,7 +439,7 @@ Phase 4: Tier B+ ████████░░ 75% (ds-table→HTML✅)
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">`
18. [x] ds-radio (1 Datei) → native `<input type="radio">` + `<fieldset>` (ReportModal) ✅
### Phase 4: Tier 2-4 — UI-Library
18. [x] OsModal (h() Render, Focus-Trap, Scroll-Lock, A11y; ConfirmModal + ReportModal nutzen OsModal; DeleteUserModal/DisableModal/ReleaseModal gelöscht) ✅

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: ███████░░░ 67% (18/27 Aufgaben) - Tier 1 ✅, Tier A ✅, Infra ✅, OsBadge ✅, ds-grid ✅, ds-table→HTML ✅, OsNumber ✅, OsModal ✅ | Tier B (rest), Tier 2-3 ausstehend
Phase 4: ███████░░░ 70% (19/27 Aufgaben) - Tier 1 ✅, Tier A ✅, Infra ✅, OsBadge ✅, ds-grid ✅, ds-table→HTML ✅, OsNumber ✅, OsModal ✅, ds-radio→HTML ✅ | Tier B ✅, Tier 2-3 ausstehend
Phase 5: ░░░░░░░░░░ 0% (0/7 Aufgaben)
───────────────────────────────────────
Gesamt: ████████░░ 83% (80/96 Aufgaben)
Gesamt: ████████░░ 84% (81/96 Aufgaben)
```
### Katalogisierung (Details in KATALOG.md)
@ -216,11 +216,20 @@ ds-chip + ds-tag → OsBadge (UI-Library): ✅
## Aktueller Stand
**Letzte Aktualisierung:** 2026-02-20 (Session 32)
**Letzte Aktualisierung:** 2026-03-14 (Session 33)
**Aktuelle Phase:** Phase 4 - Tier 1 ✅, Tier A ✅, OsBadge ✅, ds-grid ✅, ds-table→HTML ✅, OsNumber ✅ | Tier B (rest), Tier 2-3 ausstehend
**Aktuelle Phase:** Phase 4 - Tier 1 ✅, Tier A ✅, Tier B ✅, OsModal ✅ | Tier 2-3 ausstehend
**Zuletzt abgeschlossen (Session 32 - OsNumber: ds-number + CountTo → OsNumber):**
**Zuletzt abgeschlossen (Session 33 - ds-radio → native HTML):**
- [x] `<ds-radio>` in ReportModal.vue → native `<fieldset>` + `<input type="radio">` + `<label>`
- [x] Accessible Radio-Group: `<fieldset>` mit `<legend>` für Screen-Reader
- [x] CSS: `.report-radio-group`, `.report-radio-option`, `.report-radio-option-label` (in ReportModal `<style>`)
- [x] ReportModal.spec.js: `.ds-radio-option-label``input[type="radio"]` Selektor
- [x] Cypress E2E: `.ds-radio-option-label``input[type="radio"]` Selektor
- [x] 14/14 Unit-Tests bestanden
- [x] 0 ds-radio Nutzungen verbleibend
**Zuvor abgeschlossen (Session 32 - OsNumber: ds-number + CountTo → OsNumber):**
- [x] OsNumber Komponente in packages/ui erstellt (h() Render-Function, requestAnimationFrame Animation)
- [x] Props: count (required), label (optional), animated (optional, 1500ms ease-out)
- [x] Animation: requestAnimationFrame-Loop, watch(count) re-animiert von oldVal→newVal
@ -279,8 +288,7 @@ ds-chip + ds-tag → OsBadge (UI-Library): ✅
- [x] Test-Fix: Empty.spec.js `attributes().margin``classes().toContain('ds-my-xxx-small')`
- [x] 0 Tier-A `ds-*` Komponenten-Tags verbleibend
**Verbleibende ds-* Komponenten (7 Typen):**
- Tier B Rest (→ Plain HTML): ds-radio (1)
**Verbleibende ds-* Komponenten (6 Typen):**
- Tier C (→ UI-Library): ds-input (23), ds-form (18), ds-modal (7), ds-menu/ds-menu-item (17), ds-select (3)
**Zuvor abgeschlossen (Session 26 - CodeRabbit Review Fixes):**

View File

@ -162,7 +162,7 @@ describe('ReportModal.vue', () => {
describe('click confirm button', () => {
beforeEach(async () => {
wrapper.find('.ds-radio-option-label').trigger('click')
wrapper.find('.report-radio-option').trigger('click')
await Vue.nextTick()
wrapper.find('button.confirm').trigger('click')
await Vue.nextTick()

View File

@ -9,13 +9,26 @@
<!-- eslint-disable-next-line vue/no-v-html -->
<p v-html="message" />
<div class="ds-mb-small"></div>
<ds-radio
v-model="form.reasonCategory"
:schema="formSchema.reasonCategory"
:label="$t('report.reason.category.label')"
:options="form.reasonCategoryOptions"
labelProp="label"
/>
<fieldset class="report-radio-group" data-test="report-radio-group" role="radiogroup">
<legend>{{ $t('report.reason.category.label') }}</legend>
<div
v-for="(option, index) in form.reasonCategoryOptions"
:key="option.value"
class="report-radio-option"
:class="{ 'report-radio-option-selected': form.reasonCategory === option }"
role="radio"
:aria-checked="String(form.reasonCategory === option)"
:tabindex="form.reasonCategory === option || (!form.reasonCategory && index === 0) ? 0 : -1"
@click="selectReasonCategory(option)"
@keydown.space.prevent="selectReasonCategory(option)"
@keydown.enter.prevent="selectReasonCategory(option)"
@keydown.down.prevent="focusRadioOption(index + 1)"
@keydown.up.prevent="focusRadioOption(index - 1)"
>
<span class="report-radio-option-mark" />
<span class="report-radio-option-label">{{ option.label }}</span>
</div>
</fieldset>
<ds-input
class="reason-description"
v-model="form.reasonDescription"
@ -119,6 +132,18 @@ export default {
this.$emit('close')
}, 1000)
},
selectReasonCategory(option) {
this.form.reasonCategory = option
this.$nextTick(() => {
const selected = this.$el.querySelector('.report-radio-option[aria-checked="true"]')
if (selected) selected.focus()
})
},
focusRadioOption(index) {
const options = this.form.reasonCategoryOptions
const wrappedIndex = (index + options.length) % options.length
this.selectReasonCategory(options[wrappedIndex])
},
async confirm() {
const { reasonCategory, reasonDescription } = this.form
this.loading = true
@ -174,12 +199,54 @@ export default {
width: 700px !important;
max-width: 700px !important;
}
.report-modal .ds-radio-option {
width: 100% !important;
.report-modal .report-radio-group {
border: none;
padding: 0;
margin: 0;
}
.report-modal .ds-radio-option-label {
margin: 5px 20px 5px 5px !important;
width: 100% !important;
.report-modal .report-radio-group legend {
font-weight: bold;
margin-bottom: $space-xx-small;
}
.report-modal .report-radio-option {
display: flex;
align-items: center;
width: 100%;
padding: 4px 0;
cursor: pointer;
user-select: none;
}
.report-modal .report-radio-option-mark {
display: inline-block;
position: relative;
width: 16px;
height: 16px;
min-width: 16px;
border: 2px solid $border-color-base;
background-color: $background-color-base;
border-radius: 50%;
margin-right: 8px;
}
.report-modal .report-radio-option-mark::before {
position: absolute;
content: '';
top: 50%;
left: 50%;
transform: translateY(-50%) translateX(-50%) scale(0);
opacity: 0;
width: 8px;
height: 8px;
border-radius: 50%;
background-color: $text-color-primary;
transition: all 0.1s ease-in;
}
.report-modal .report-radio-option-selected .report-radio-option-mark::before {
opacity: 1;
transform: translateY(-50%) translateX(-50%) scale(1);
}
.report-modal .report-radio-option-label {
cursor: pointer;
flex: 1;
}
.report-modal .reason-description {
margin-top: $space-x-small !important;