mirror of
https://github.com/lrnselfreliance/wrolpi.git
synced 2026-10-26 19:01:39 -04:00
The library has been uninstalled for some time, but the codebase still read as though it were there: identifiers named after it, rules written against its class names, and about 180 comments explaining a rule in terms of a thing that is no longer installed. Renamed, keeping the vocabulary the app actually uses: semanticUIColorMap -> navColorHexMap, semanticColorNames -> paletteColorNames, semanticNames -> iconNameAliases, semanticSizes -> sizeAliases, and SemanticUIColors -> NavColors in wrolpi/schema.py, which is what validates nav_color on the way in. ColorToSemanticHexColor had no callers at all and is gone. The baseline ratchet has done its job -- the list reached zero, so the JSON is deleted and the guard keeps only the checks with teeth: no package, no import, no `className="ui "`, no toast library. Its Theme.tsx check went with the wrappers it was watching for. It also asserts its own walk found files, so a clean result cannot mean it scanned nothing. Deleted from App.css: every rule requiring `.ui`, `.inverted`, `.segment` or `.modal`, none of which any component has set since the stylesheet stopped loading, plus the form skeletons, ui-alerts and progress-text rules that nothing renders. Two of them were doing harm rather than nothing: the drag tints had a dark-theme variant under `.inverted` that stopped applying, leaving 15% of a fixed blue over a dark row, and now mix a token instead. Two selectors naming markup that no longer exists were live bugs. The file browser's filter had `.ui.input.file-browser-filter`, so it rendered at the browser default width on a bar laid out to give it 34em -- now qualified by its container, because `.wrolpi-action-input` sets `width: 100%` at the same specificity and is loaded later. And SearchBox takes named props rather than a rest spread, so the `id` DestinationForm hands it was dropped in silence and the label it renders pointed at nothing; the download forms' Destination field has had no associated label since the migration. "Semantic roles" survives in four places, where it means what it says. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
88 lines
3.3 KiB
TypeScript
88 lines
3.3 KiB
TypeScript
import React from 'react';
|
|
import {notifications} from '@mantine/notifications';
|
|
import {RoleName} from '../../themes/mantine';
|
|
|
|
/*
|
|
* Toasts.
|
|
*
|
|
* One call signature for the ~19 places that raise a toast. The container is mounted
|
|
* by ThemeProvider, so a caller only says what happened.
|
|
*/
|
|
|
|
/* `error` is the original spelling and stays; `danger` is the token's name for it. */
|
|
export type ToastType = 'info' | 'success' | 'warning' | 'error' | 'danger';
|
|
|
|
export interface ToastOptions {
|
|
type?: ToastType;
|
|
title?: string;
|
|
description?: string;
|
|
/** Milliseconds before auto-dismiss. 0 keeps it until dismissed. */
|
|
time?: number;
|
|
/**
|
|
* Makes the whole toast activate this. `Events.js` relies on it for the three events
|
|
* that carry a URL: a page shared from the browser extension, a finished archive
|
|
* upload, and a generated screenshot.
|
|
*/
|
|
onClick?: () => void;
|
|
}
|
|
|
|
/*
|
|
* Roles, not hues. With hues, all four kinds were the same pixel in night and amber:
|
|
* `--yellow` there is byte-identical to `--amber`, and `--orange` to `--text`. An
|
|
* error toast was indistinguishable from an informational one, which is the failure
|
|
* mode a toast exists to avoid.
|
|
*/
|
|
const toastRoles: Record<ToastType, RoleName> = {
|
|
info: 'info',
|
|
success: 'success',
|
|
warning: 'warning',
|
|
error: 'danger',
|
|
danger: 'danger',
|
|
};
|
|
|
|
/**
|
|
* The props that make a toast followable.
|
|
*
|
|
* `tabIndex` and the key handler are not decoration: WROLPi runs on devices with a keyboard
|
|
* and no mouse, and a toast only a pointer can follow is a feature half the deployments
|
|
* cannot use. There is deliberately no `role` — a `button` or `link` role may not contain
|
|
* interactive content, and the dismiss button lives inside.
|
|
*/
|
|
const followable = (onClick: () => void) => ({
|
|
style: {cursor: 'pointer'},
|
|
tabIndex: 0,
|
|
onClick: (event: React.MouseEvent) => {
|
|
// The dismiss button is inside the toast, so its click reaches here too. Closing a
|
|
// notification is not following it, and for these three that would be a navigation
|
|
// the user did not ask for.
|
|
if ((event.target as HTMLElement).closest('button')) {
|
|
return;
|
|
}
|
|
onClick();
|
|
},
|
|
onKeyDown: (event: React.KeyboardEvent) => {
|
|
if (event.key === 'Enter' || event.key === ' ') {
|
|
event.preventDefault();
|
|
onClick();
|
|
}
|
|
},
|
|
});
|
|
|
|
export function toast({type = 'info', title, description, time = 5000, onClick}: ToastOptions = {}) {
|
|
notifications.show({
|
|
title,
|
|
message: description,
|
|
color: toastRoles[type],
|
|
// Callers spell "stay up" as `time: 0`; Mantine wants `false`. Its `getAutoClose`
|
|
// returns any number as given and the container then calls `setTimeout(hide, 0)`, so
|
|
// passing the 0 through dismisses the toast on the next tick.
|
|
autoClose: time === 0 ? false : time,
|
|
withBorder: true,
|
|
// Mantine leaves its close button unnamed, which a screen reader reads as "button".
|
|
closeButtonProps: {'aria-label': 'Dismiss notification'},
|
|
...(onClick ? followable(onClick) : {}),
|
|
});
|
|
}
|
|
|
|
/** Dismiss every visible toast. Useful when navigating away from a failed action. */
|
|
export const clearToasts = () => notifications.clean();
|