mirror of
https://github.com/go-gitea/gitea.git
synced 2026-08-10 05:24:18 +09:00
enhance(ui): restyle toasts (#38842)
Restyle toasts, add success toast to match the alerts. <img width="500" alt="Screenshot 2026-08-09 at 11 33 44" src="https://github.com/user-attachments/assets/d36422b8-0030-41dd-8056-b2d10d86280d" /> Alternatively we could also tint to match the alerts, but I do prefer untinted. <img width="500" alt="Screenshot 2026-08-09 at 11 34 44" src="https://github.com/user-attachments/assets/73fcfde4-618b-424f-9aa9-4ada5c2904f6" /> --------- Signed-off-by: silverwind <me@silverwind.io> Co-authored-by: Giteabot <teabot@gitea.io>
This commit is contained in:
@@ -4,6 +4,7 @@
|
|||||||
<div class="ui container">
|
<div class="ui container">
|
||||||
<h1>Toast</h1>
|
<h1>Toast</h1>
|
||||||
<div>
|
<div>
|
||||||
|
<button class="ui button toast-test-button" data-toast-level="success" data-toast-message="test success">Show Success Toast</button>
|
||||||
<button class="ui button toast-test-button" data-toast-level="info" data-toast-message="test info">Show Info Toast</button>
|
<button class="ui button toast-test-button" data-toast-level="info" data-toast-message="test info">Show Info Toast</button>
|
||||||
<button class="ui button toast-test-button" data-toast-level="warning" data-toast-message="test warning">Show Warning Toast</button>
|
<button class="ui button toast-test-button" data-toast-level="warning" data-toast-message="test warning">Show Warning Toast</button>
|
||||||
<button class="ui button toast-test-button" data-toast-level="error" data-toast-message="test error">Show Error Toast</button>
|
<button class="ui button toast-test-button" data-toast-level="error" data-toast-message="test error">Show Error Toast</button>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
.toastify {
|
.toastify {
|
||||||
color: var(--color-white);
|
color: var(--color-text);
|
||||||
|
background: var(--color-menu);
|
||||||
|
border: 1px solid var(--color-secondary);
|
||||||
position: fixed;
|
position: fixed;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: all .2s ease;
|
transition: all .2s ease;
|
||||||
@@ -16,6 +18,11 @@
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.toast-success .toast-icon { color: var(--color-success-text); }
|
||||||
|
.toast-info .toast-icon { color: var(--color-info-text); }
|
||||||
|
.toast-warning .toast-icon { color: var(--color-warning-text); }
|
||||||
|
.toast-error .toast-icon { color: var(--color-error-text); }
|
||||||
|
|
||||||
.toast-body {
|
.toast-body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 5px 0;
|
padding: 5px 0;
|
||||||
|
|||||||
@@ -1,21 +1,19 @@
|
|||||||
import {showInfoToast, showWarningToast, showErrorToast} from './toast.ts';
|
import {showSuccessToast, showInfoToast, showWarningToast, showErrorToast} from './toast.ts';
|
||||||
import type {Toast} from './toast.ts';
|
|
||||||
import {registerGlobalInitFunc} from './observer.ts';
|
import {registerGlobalInitFunc} from './observer.ts';
|
||||||
import {showFomanticModal} from './fomantic/modal.ts';
|
import {showFomanticModal} from './fomantic/modal.ts';
|
||||||
import {createElementFromHTML} from '../utils/dom.ts';
|
import {createElementFromHTML} from '../utils/dom.ts';
|
||||||
import {html} from '../utils/html.ts';
|
import {html} from '../utils/html.ts';
|
||||||
import {showGlobalErrorMessage} from './errors.ts';
|
import {showGlobalErrorMessage} from './errors.ts';
|
||||||
import {AnsiLineRenderer} from '../render/ansi.ts';
|
import {AnsiLineRenderer} from '../render/ansi.ts';
|
||||||
|
import type {Intent} from '../types.ts';
|
||||||
type LevelMap = Record<string, (message: string) => Toast | null>;
|
|
||||||
|
|
||||||
function initDevtestPage() {
|
function initDevtestPage() {
|
||||||
const toastButtons = document.querySelectorAll('.toast-test-button');
|
const toastButtons = document.querySelectorAll('.toast-test-button');
|
||||||
if (toastButtons.length) {
|
if (toastButtons.length) {
|
||||||
const levelMap: LevelMap = {info: showInfoToast, warning: showWarningToast, error: showErrorToast};
|
const levelMap = {success: showSuccessToast, info: showInfoToast, warning: showWarningToast, error: showErrorToast};
|
||||||
for (const el of toastButtons) {
|
for (const el of toastButtons) {
|
||||||
el.addEventListener('click', () => {
|
el.addEventListener('click', () => {
|
||||||
const level = el.getAttribute('data-toast-level')!;
|
const level = el.getAttribute('data-toast-level') as Intent;
|
||||||
const message = el.getAttribute('data-toast-message')!;
|
const message = el.getAttribute('data-toast-message')!;
|
||||||
levelMap[level](message);
|
levelMap[level](message);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,16 +1,21 @@
|
|||||||
import {showInfoToast, showErrorToast, showWarningToast} from './toast.ts';
|
import {showSuccessToast, showInfoToast, showErrorToast, showWarningToast} from './toast.ts';
|
||||||
|
|
||||||
|
test('showSuccessToast', async () => {
|
||||||
|
showSuccessToast('success', {duration: -1});
|
||||||
|
expect(document.querySelector('.toastify')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
test('showInfoToast', async () => {
|
test('showInfoToast', async () => {
|
||||||
showInfoToast('success 😀', {duration: -1});
|
showInfoToast('info', {duration: -1});
|
||||||
expect(document.querySelector('.toastify')).toBeTruthy();
|
expect(document.querySelector('.toastify')).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('showWarningToast', async () => {
|
test('showWarningToast', async () => {
|
||||||
showWarningToast('warning 😐', {duration: -1});
|
showWarningToast('warning', {duration: -1});
|
||||||
expect(document.querySelector('.toastify')).toBeTruthy();
|
expect(document.querySelector('.toastify')).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('showErrorToast', async () => {
|
test('showErrorToast', async () => {
|
||||||
showErrorToast('error 🙁', {duration: -1});
|
showErrorToast('error', {duration: -1});
|
||||||
expect(document.querySelector('.toastify')).toBeTruthy();
|
expect(document.querySelector('.toastify')).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,25 +12,25 @@ export type Toast = ReturnType<typeof StartToastifyInstance>;
|
|||||||
type ToastLevels = {
|
type ToastLevels = {
|
||||||
[intent in Intent]: {
|
[intent in Intent]: {
|
||||||
icon: SvgName,
|
icon: SvgName,
|
||||||
background: string,
|
|
||||||
duration: number,
|
duration: number,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const levels: ToastLevels = {
|
const levels: ToastLevels = {
|
||||||
info: {
|
success: {
|
||||||
icon: 'octicon-check',
|
icon: 'octicon-check',
|
||||||
background: 'var(--color-green)',
|
|
||||||
duration: 2500,
|
duration: 2500,
|
||||||
},
|
},
|
||||||
|
info: {
|
||||||
|
icon: 'octicon-info',
|
||||||
|
duration: 5000,
|
||||||
|
},
|
||||||
warning: {
|
warning: {
|
||||||
icon: 'gitea-exclamation',
|
icon: 'gitea-exclamation',
|
||||||
background: 'var(--color-orange)',
|
|
||||||
duration: -1, // requires dismissal to hide
|
duration: -1, // requires dismissal to hide
|
||||||
},
|
},
|
||||||
error: {
|
error: {
|
||||||
icon: 'gitea-exclamation',
|
icon: 'gitea-exclamation',
|
||||||
background: 'var(--color-red)',
|
|
||||||
duration: -1, // requires dismissal to hide
|
duration: -1, // requires dismissal to hide
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -43,7 +43,7 @@ type ToastOpts = {
|
|||||||
type ToastifyElement = HTMLElement & {_giteaToastifyInstance?: Toast};
|
type ToastifyElement = HTMLElement & {_giteaToastifyInstance?: Toast};
|
||||||
|
|
||||||
/** See https://github.com/apvarun/toastify-js#api for options */
|
/** See https://github.com/apvarun/toastify-js#api for options */
|
||||||
function showToast(message: string, level: Intent, {gravity, position, duration, useHtmlBody, preventDuplicates = true, ...other}: ToastOpts = {}): Toast | null {
|
function showToast(message: string, level: Intent = 'info', {gravity, position, duration, useHtmlBody, preventDuplicates = true, ...other}: ToastOpts = {}): Toast | null {
|
||||||
const parent = document.querySelector('.ui.dimmer.active') ?? document.body;
|
const parent = document.querySelector('.ui.dimmer.active') ?? document.body;
|
||||||
const duplicateKey = preventDuplicates ? (typeof preventDuplicates === 'string' ? preventDuplicates : `${level}-${message}`) : '';
|
const duplicateKey = preventDuplicates ? (typeof preventDuplicates === 'string' ? preventDuplicates : `${level}-${message}`) : '';
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ function showToast(message: string, level: Intent, {gravity, position, duration,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const {icon, background, duration: levelDuration} = levels[level ?? 'info'];
|
const {icon, duration: levelDuration} = levels[level];
|
||||||
const bodyHtml = useHtmlBody ? message : htmlEscape(message);
|
const bodyHtml = useHtmlBody ? message : htmlEscape(message);
|
||||||
const toast = Toastify({
|
const toast = Toastify({
|
||||||
selector: parent,
|
selector: parent,
|
||||||
@@ -69,10 +69,10 @@ function showToast(message: string, level: Intent, {gravity, position, duration,
|
|||||||
<button class='btn toast-close'>${svgRaw('octicon-x')}</button>
|
<button class='btn toast-close'>${svgRaw('octicon-x')}</button>
|
||||||
`,
|
`,
|
||||||
escapeMarkup: false,
|
escapeMarkup: false,
|
||||||
|
className: `toast-${level}`,
|
||||||
gravity: gravity ?? 'top',
|
gravity: gravity ?? 'top',
|
||||||
position: position ?? 'center',
|
position: position ?? 'center',
|
||||||
duration: duration ?? levelDuration,
|
duration: duration ?? levelDuration,
|
||||||
style: {background},
|
|
||||||
...other,
|
...other,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -84,6 +84,10 @@ function showToast(message: string, level: Intent, {gravity, position, duration,
|
|||||||
return toast;
|
return toast;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function showSuccessToast(message: string, opts?: ToastOpts): Toast | null {
|
||||||
|
return showToast(message, 'success', opts);
|
||||||
|
}
|
||||||
|
|
||||||
export function showInfoToast(message: string, opts?: ToastOpts): Toast | null {
|
export function showInfoToast(message: string, opts?: ToastOpts): Toast | null {
|
||||||
return showToast(message, 'info', opts);
|
return showToast(message, 'info', opts);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ import octiconHistory from '../../public/assets/img/svg/octicon-history.svg';
|
|||||||
import octiconHorizontalRule from '../../public/assets/img/svg/octicon-horizontal-rule.svg';
|
import octiconHorizontalRule from '../../public/assets/img/svg/octicon-horizontal-rule.svg';
|
||||||
import octiconHome from '../../public/assets/img/svg/octicon-home.svg';
|
import octiconHome from '../../public/assets/img/svg/octicon-home.svg';
|
||||||
import octiconImage from '../../public/assets/img/svg/octicon-image.svg';
|
import octiconImage from '../../public/assets/img/svg/octicon-image.svg';
|
||||||
|
import octiconInfo from '../../public/assets/img/svg/octicon-info.svg';
|
||||||
import octiconIssueClosed from '../../public/assets/img/svg/octicon-issue-closed.svg';
|
import octiconIssueClosed from '../../public/assets/img/svg/octicon-issue-closed.svg';
|
||||||
import octiconIssueOpened from '../../public/assets/img/svg/octicon-issue-opened.svg';
|
import octiconIssueOpened from '../../public/assets/img/svg/octicon-issue-opened.svg';
|
||||||
import octiconItalic from '../../public/assets/img/svg/octicon-italic.svg';
|
import octiconItalic from '../../public/assets/img/svg/octicon-italic.svg';
|
||||||
@@ -139,6 +140,7 @@ const svgs = {
|
|||||||
'octicon-horizontal-rule': octiconHorizontalRule,
|
'octicon-horizontal-rule': octiconHorizontalRule,
|
||||||
'octicon-home': octiconHome,
|
'octicon-home': octiconHome,
|
||||||
'octicon-image': octiconImage,
|
'octicon-image': octiconImage,
|
||||||
|
'octicon-info': octiconInfo,
|
||||||
'octicon-issue-closed': octiconIssueClosed,
|
'octicon-issue-closed': octiconIssueClosed,
|
||||||
'octicon-issue-opened': octiconIssueOpened,
|
'octicon-issue-opened': octiconIssueOpened,
|
||||||
'octicon-italic': octiconItalic,
|
'octicon-italic': octiconItalic,
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
export type TimeoutId = ReturnType<typeof setTimeout>;
|
export type TimeoutId = ReturnType<typeof setTimeout>;
|
||||||
export type IntervalId = ReturnType<typeof setInterval>;
|
export type IntervalId = ReturnType<typeof setInterval>;
|
||||||
|
|
||||||
export type Intent = 'error' | 'warning' | 'info';
|
export type Intent = 'error' | 'warning' | 'info' | 'success';
|
||||||
|
|
||||||
export type Mention = {
|
export type Mention = {
|
||||||
key: string,
|
key: string,
|
||||||
|
|||||||
Reference in New Issue
Block a user