Phase 3 of the "fully MUI" cleanup. - New src/admin/ui/RichText.tsx exports two theme-aware styled() wrappers: RichEditorRoot (the .admin-rich-editor Quill overrides) and RichTextView (the .admin-rich-text-view read-only output). All colors resolve via theme.vars; picker/tooltip shadows use theme.applyStyles for the light/dark difference; the active-button tint uses the primary channel. - Dropped the ~170 dead font/size picker rules from the old stylesheet — the editor toolbar exposes neither picker. Kept the .ql-font-* content classes (shared by editor + view) so legacy stored HTML still renders. - RichEditor uses <RichEditorRoot> (min-height still via --re-min-height); InvoiceDetail + OrderDetail use <RichTextView> for the sanitized output. - offers.css deleted; its import removed from AdminApp. tsc -b --noEmit and npm run build clean. Verified the editor in Chrome (light + dark): toolbar, editor surface, text, icon strokes and min-height all correct and theme-reactive. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
208 lines
7.1 KiB
TypeScript
208 lines
7.1 KiB
TypeScript
import { styled } from "@mui/material/styles";
|
|
|
|
/**
|
|
* Theme-aware styling for the Quill rich-text editor and its read-only
|
|
* rendered output. Replaces the former `offers.css` — the overrides now layer
|
|
* on top of `react-quill-new/dist/quill.snow.css` via scoped `styled()`
|
|
* wrappers, resolving against `theme.vars` for light/dark.
|
|
*
|
|
* The font/size picker rules from the old stylesheet were dropped: the editor
|
|
* toolbar (see RichEditor.tsx) exposes neither picker. The `.ql-font-*`
|
|
* content classes are kept so legacy stored HTML still renders with its font.
|
|
*/
|
|
|
|
// Font classes applied to content spans (legacy HTML). Shared by editor + view.
|
|
const QL_FONT_CLASSES = {
|
|
"& .ql-font-arial": { fontFamily: "Arial, sans-serif" },
|
|
"& .ql-font-tahoma": { fontFamily: "Tahoma, sans-serif" },
|
|
"& .ql-font-verdana": { fontFamily: "Verdana, sans-serif" },
|
|
"& .ql-font-georgia": { fontFamily: "Georgia, serif" },
|
|
"& .ql-font-times-new-roman": { fontFamily: '"Times New Roman", serif' },
|
|
"& .ql-font-courier-new": { fontFamily: '"Courier New", monospace' },
|
|
"& .ql-font-trebuchet-ms": { fontFamily: '"Trebuchet MS", sans-serif' },
|
|
"& .ql-font-impact": { fontFamily: "Impact, sans-serif" },
|
|
"& .ql-font-comic-sans-ms": { fontFamily: '"Comic Sans MS", cursive' },
|
|
"& .ql-font-lucida-console": { fontFamily: '"Lucida Console", monospace' },
|
|
"& .ql-font-palatino-linotype": { fontFamily: '"Palatino Linotype", serif' },
|
|
"& .ql-font-garamond": { fontFamily: "Garamond, serif" },
|
|
} as const;
|
|
|
|
/** Wrapper around <ReactQuill> — was `.admin-rich-editor`. */
|
|
export const RichEditorRoot = styled("div")(({ theme }) => ({
|
|
border: `1px solid ${theme.vars!.palette.divider}`,
|
|
borderRadius: "0.5rem",
|
|
overflow: "visible",
|
|
|
|
"& .quill": { display: "flex", flexDirection: "column" },
|
|
|
|
// Toolbar
|
|
"& .ql-toolbar.ql-snow": {
|
|
background: theme.vars!.palette.background.paper,
|
|
border: "none",
|
|
borderBottom: `1px solid ${theme.vars!.palette.divider}`,
|
|
borderRadius: "0.5rem 0.5rem 0 0",
|
|
padding: "0.5rem",
|
|
flexWrap: "wrap",
|
|
gap: "2px",
|
|
},
|
|
"& .ql-toolbar .ql-formats": { marginRight: "8px" },
|
|
|
|
// Toolbar icons (idle)
|
|
"& .ql-snow .ql-stroke": { stroke: theme.vars!.palette.text.secondary },
|
|
"& .ql-snow .ql-fill": { fill: theme.vars!.palette.text.secondary },
|
|
"& .ql-snow .ql-picker-label": {
|
|
color: theme.vars!.palette.text.secondary,
|
|
borderColor: theme.vars!.palette.divider,
|
|
},
|
|
|
|
// Toolbar icons (hover)
|
|
"& .ql-snow button:hover .ql-stroke, & .ql-snow .ql-picker-label:hover .ql-stroke":
|
|
{ stroke: theme.vars!.palette.text.primary },
|
|
"& .ql-snow button:hover .ql-fill, & .ql-snow .ql-picker-label:hover .ql-fill":
|
|
{ fill: theme.vars!.palette.text.primary },
|
|
"& .ql-snow button:hover, & .ql-snow .ql-picker-label:hover": {
|
|
color: theme.vars!.palette.text.primary,
|
|
},
|
|
|
|
// Active state
|
|
"& .ql-snow button.ql-active": {
|
|
color: theme.vars!.palette.primary.main,
|
|
background: `rgba(${theme.vars!.palette.primary.mainChannel} / 0.15)`,
|
|
borderRadius: "4px",
|
|
},
|
|
"& .ql-snow button.ql-active .ql-stroke": {
|
|
stroke: theme.vars!.palette.primary.main,
|
|
},
|
|
"& .ql-snow button.ql-active .ql-fill, & .ql-snow button.ql-active .ql-stroke.ql-fill":
|
|
{ fill: theme.vars!.palette.primary.main },
|
|
"& .ql-snow .ql-picker-item.ql-selected": {
|
|
color: theme.vars!.palette.primary.main,
|
|
},
|
|
"& .ql-snow .ql-picker-label.ql-active": {
|
|
color: theme.vars!.palette.primary.main,
|
|
},
|
|
"& .ql-snow .ql-picker-label.ql-active .ql-stroke": {
|
|
stroke: theme.vars!.palette.primary.main,
|
|
},
|
|
|
|
// Dropdowns (color / background / align pickers)
|
|
"& .ql-snow .ql-picker-options": {
|
|
background: theme.vars!.palette.background.default,
|
|
border: `1px solid ${theme.vars!.palette.divider}`,
|
|
borderRadius: "0.375rem",
|
|
boxShadow: "0 4px 12px rgba(0, 0, 0, 0.08)",
|
|
zIndex: 1000,
|
|
padding: "0.25rem",
|
|
...theme.applyStyles("dark", {
|
|
boxShadow: "0 4px 16px rgba(0, 0, 0, 0.3)",
|
|
}),
|
|
},
|
|
"& .ql-snow .ql-picker-item": {
|
|
color: theme.vars!.palette.text.secondary,
|
|
padding: "0.25rem 0.5rem",
|
|
borderRadius: "0.25rem",
|
|
},
|
|
"& .ql-snow .ql-picker-item:hover": {
|
|
color: theme.vars!.palette.text.primary,
|
|
background: theme.vars!.palette.background.paper,
|
|
},
|
|
|
|
// Editor area
|
|
"& .ql-container.ql-snow": {
|
|
border: "none",
|
|
borderRadius: "0 0 0.5rem 0.5rem",
|
|
fontFamily: "Tahoma, sans-serif",
|
|
fontSize: "14px",
|
|
},
|
|
"& .ql-editor": {
|
|
minHeight: "var(--re-min-height, 120px)",
|
|
padding: "0.75rem",
|
|
color: theme.vars!.palette.text.primary,
|
|
lineHeight: 1.6,
|
|
fontFamily: "Tahoma, sans-serif",
|
|
fontSize: "14px",
|
|
background: theme.vars!.palette.background.paper,
|
|
},
|
|
"& .ql-editor.ql-blank::before": {
|
|
color: theme.vars!.palette.text.disabled,
|
|
fontStyle: "normal",
|
|
},
|
|
"& .ql-editor ul, & .ql-editor ol": { paddingLeft: "1.5rem" },
|
|
|
|
// Color picker
|
|
'& .ql-snow .ql-color-picker .ql-picker-options[aria-hidden="false"]': {
|
|
width: "176px",
|
|
padding: "0.375rem",
|
|
display: "flex",
|
|
flexWrap: "wrap",
|
|
gap: "2px",
|
|
},
|
|
"& .ql-snow .ql-color-picker .ql-picker-item": {
|
|
width: "18px",
|
|
height: "18px",
|
|
borderRadius: "2px",
|
|
margin: 0,
|
|
padding: 0,
|
|
flexShrink: 0,
|
|
},
|
|
|
|
// Tooltip (link editor)
|
|
"& .ql-snow .ql-tooltip": {
|
|
background: theme.vars!.palette.background.default,
|
|
border: `1px solid ${theme.vars!.palette.divider}`,
|
|
borderRadius: "0.375rem",
|
|
boxShadow: "0 4px 12px rgba(0, 0, 0, 0.08)",
|
|
color: theme.vars!.palette.text.primary,
|
|
...theme.applyStyles("dark", {
|
|
boxShadow: "0 4px 16px rgba(0, 0, 0, 0.3)",
|
|
}),
|
|
},
|
|
'& .ql-snow .ql-tooltip input[type="text"]': {
|
|
background: theme.vars!.palette.background.paper,
|
|
border: `1px solid ${theme.vars!.palette.divider}`,
|
|
borderRadius: "0.25rem",
|
|
color: theme.vars!.palette.text.primary,
|
|
padding: "0.25rem 0.5rem",
|
|
},
|
|
"& .ql-snow .ql-tooltip a": { color: theme.vars!.palette.primary.main },
|
|
|
|
...QL_FONT_CLASSES,
|
|
|
|
// Keep picker dropdowns on-screen on narrow phones (the color picker is a
|
|
// fixed 176px which overflows a ~360px viewport).
|
|
"@media (max-width:480px)": {
|
|
"& .ql-snow .ql-picker-options": {
|
|
minWidth: 0,
|
|
maxWidth: "calc(100vw - 32px)",
|
|
},
|
|
'& .ql-snow .ql-color-picker .ql-picker-options[aria-hidden="false"]': {
|
|
width: "auto",
|
|
maxWidth: "calc(100vw - 32px)",
|
|
},
|
|
},
|
|
}));
|
|
|
|
/** Read-only rendered Quill HTML output — was `.admin-rich-text-view`. */
|
|
export const RichTextView = styled("div")(({ theme }) => ({
|
|
color: theme.vars!.palette.text.secondary,
|
|
lineHeight: 1.6,
|
|
fontSize: "0.875rem",
|
|
overflowWrap: "break-word",
|
|
wordBreak: "break-word",
|
|
minWidth: 0,
|
|
|
|
"& ul, & ol": { paddingLeft: "1.5rem", margin: "0.25rem 0 0.75rem" },
|
|
"& li": { marginBottom: "0.15rem" },
|
|
"& a": { color: theme.vars!.palette.primary.main },
|
|
"& strong, & b": {
|
|
fontWeight: 600,
|
|
color: theme.vars!.palette.text.primary,
|
|
display: "inline-block",
|
|
marginTop: "0.5rem",
|
|
},
|
|
"& br + b, & br + strong": { marginTop: "0.75rem" },
|
|
"& > br:first-child, & ul + br, & ol + br": { display: "none" },
|
|
|
|
...QL_FONT_CLASSES,
|
|
}));
|