refactor: replace Fomantic modal with native dialog (#39846)

Replace Fomantic's hard-forked jQuery modal with native `<dialog>`
elements, advancing https://github.com/go-gitea/gitea/issues/29849. The
UI is visually unchanged. Dialogs open with `show()` over an `inert`
page, so tooltips, popups and toasts mounted on `<body>` stay usable
above them, unlike with `showModal()`
(https://github.com/whatwg/html/issues/9936).

- Remove the Fomantic modal and dimmer, add `showModal` and `hideModal`
- Declare modals as `<dialog class="modal-dialog">`, shown inside a
`.modal-backdrop`, instead of `ui modal` with its unused size classes
- Remove two modals that could never be opened
- Block the page while a modal is open, close the topmost one on Escape
and restore focus to its opener
- Allow stacked modals, use a confirm modal instead of `window.confirm`
for deleting content history
- Hide popups and toasts when a modal opens or closes
- Keep the release notes modal open when generating notes fails

Fixes: https://github.com/go-gitea/gitea/issues/39643
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
silverwindandwxiaoguang authored and GitHub committed 2026-10-20 12:32:27 +02:00
1 parent 7610336d74
commit e645afbb3f
95 files changed
+607 -1719

No files matched your search

+7 -10
View File
@@ -1,27 +1,24 @@
/* These are the remnants of the fomantic dimmer module */
.ui.dimmer {
.modal-backdrop {
position: fixed;
display: none;
inset: 0;
background: var(--color-overlay-backdrop);
opacity: 0;
z-index: 1000;
z-index: var(--z-index-modal);
overflow-y: auto;
justify-content: center;
padding: 8px 0;
animation-name: fadein;
animation-duration: .2s;
overscroll-behavior: contain;
user-select: none;
}
.ui.active.dimmer {
.modal-backdrop:has(> [open]) {
display: flex;
opacity: 1;
}
.ui.dimmer > .ui.modal {
.modal-backdrop > .modal-dialog {
position: static;
margin-top: auto !important;
margin-bottom: auto !important;
margin-top: auto;
margin-bottom: auto;
}
+23 -27
View File
@@ -1,17 +1,17 @@
/* to keep the changes minimal and easy to revert or fine tune, the legacy classes
like "small" and "mini" are still kept for the HTML elements, these can be cleaned up in the future */
.ui.modal {
position: absolute;
display: none;
z-index: var(--z-index-modal);
.modal-page-blocked {
overflow: hidden;
}
.modal-dialog {
text-align: left;
background: var(--color-body);
border: none;
margin: 0;
padding: 0;
color: inherit;
box-shadow: 1px 3px 3px 0 var(--color-shadow), 1px 3px 15px 2px var(--color-shadow);
transform-origin: 50% 25%;
border-radius: var(--border-radius);
user-select: text;
will-change: top, left, margin, transform, opacity;
width: fit-content;
/* on small screens, the modal should not exceed 90% of the viewport width;
@@ -22,17 +22,17 @@ like "small" and "mini" are still kept for the HTML elements, these can be clean
min-width: min(800px, max(min(300px, 90vw), 30vw));
}
.ui.modal > :first-child {
.modal-dialog > :first-child {
border-top-left-radius: var(--border-radius);
border-top-right-radius: var(--border-radius);
}
.ui.modal > :last-child {
.modal-dialog > :last-child {
border-bottom-left-radius: var(--border-radius);
border-bottom-right-radius: var(--border-radius);
}
.ui.modal .close-modal {
.modal-dialog .close-modal {
cursor: pointer;
position: absolute;
top: 50%;
@@ -47,7 +47,7 @@ like "small" and "mini" are still kept for the HTML elements, these can be clean
height: 1.5rem;
}
.ui.modal .close-modal::before {
.modal-dialog .close-modal::before {
content: "";
display: block;
width: 100%;
@@ -57,11 +57,11 @@ like "small" and "mini" are still kept for the HTML elements, these can be clean
mask-size: cover;
}
.ui.modal .close-modal:hover {
.modal-dialog .close-modal:hover {
opacity: 1;
}
.ui.modal > .header {
.modal-dialog > .header {
display: block;
position: relative;
font-size: 18px;
@@ -76,8 +76,8 @@ like "small" and "mini" are still kept for the HTML elements, these can be clean
border-top-right-radius: var(--border-radius);
}
.ui.modal > .content,
.ui.modal form > .content {
.modal-dialog > .content,
.modal-dialog form > .content {
display: block;
width: 100%;
font-size: 1em;
@@ -87,9 +87,9 @@ like "small" and "mini" are still kept for the HTML elements, these can be clean
border-radius: 0 0 var(--border-radius) var(--border-radius);
}
.ui.modal > .actions,
.ui.modal .content + .actions,
.ui.modal .content + form > .actions {
.modal-dialog > .actions,
.modal-dialog .content + .actions,
.modal-dialog .content + form > .actions {
background: var(--color-secondary-bg);
padding: 1rem;
border-top: 1px solid var(--color-secondary);
@@ -97,17 +97,13 @@ like "small" and "mini" are still kept for the HTML elements, these can be clean
border-radius: 0 0 var(--border-radius) var(--border-radius);
}
.ui.active.modal {
display: block;
}
.ui.modal .content > form > .actions,
.ui.modal .content > .actions {
.modal-dialog .content > form > .actions,
.modal-dialog .content > .actions {
padding-top: 1em;
text-align: right;
}
.ui.modal .actions > .ui.button {
.modal-dialog .actions > .ui.button {
display: inline-flex;
gap: var(--gap-inline);
align-items: center;
@@ -116,7 +112,7 @@ like "small" and "mini" are still kept for the HTML elements, these can be clean
margin-right: 0;
}
.ui.modal .actions > .ui.button.danger {
.modal-dialog .actions > .ui.button.danger {
display: block;
width: 100%;
margin: 0 auto;
+1 -12
View File
@@ -1,23 +1,12 @@
/* styles for Fomantic transition (toggle): dimmer, modal, dropdown menu */
/* styles for Fomantic transition (toggle): dropdown menu */
/* this is the only place using "hidden" and "visible" classes, it can safely work with tailwind without prefix in the future */
.ui.modal.transition.hidden,
.ui.dropdown > .menu.transition.hidden {
display: none;
visibility: hidden;
}
.ui.modal.transition.visible,
.ui.dropdown > .menu.transition.visible {
display: block;
visibility: visible;
}
/* dimmer uses "active" but not "hidden/visible" classes, no special classes for it here */
/* only modal and dimmer need animation, dropdown menu doesn't */
.ui.modal.transition {
animation-name: fadein;
animation-duration: 100ms;
animation-timing-function: ease-in-out;
}