Prevent users from renaming a KVEditor entry to an existing key and show inline validation errors.
263 lines
8.0 KiB
TypeScript
263 lines
8.0 KiB
TypeScript
/**
|
|
* Shared makeStyles definitions for the config page and its components.
|
|
*
|
|
* All config tab components import `useConfigStyles` from this module
|
|
* so that visual changes need updating in only one place.
|
|
*/
|
|
|
|
import { makeStyles, tokens } from "@fluentui/react-components";
|
|
|
|
export const useConfigStyles = makeStyles({
|
|
page: {
|
|
padding: tokens.spacingVerticalXXL,
|
|
maxWidth: "1100px",
|
|
},
|
|
// -------------------------------------------------------------------------
|
|
// List/Detail layout (ConfigListDetail component)
|
|
// -------------------------------------------------------------------------
|
|
/** Root flex-row container for the two-pane list/detail layout. */
|
|
listDetailRoot: {
|
|
display: "flex",
|
|
flexDirection: "row",
|
|
gap: tokens.spacingHorizontalM,
|
|
minHeight: "400px",
|
|
"@media (max-width: 900px)": {
|
|
flexDirection: "column",
|
|
},
|
|
},
|
|
/** Fixed-width left pane with scrollable item list. */
|
|
listPane: {
|
|
width: "280px",
|
|
flexShrink: "0",
|
|
overflowY: "auto",
|
|
borderRight: `1px solid ${tokens.colorNeutralStroke2}`,
|
|
paddingRight: tokens.spacingHorizontalS,
|
|
"@media (max-width: 900px)": {
|
|
width: "100%",
|
|
borderRight: "none",
|
|
borderBottom: `1px solid ${tokens.colorNeutralStroke2}`,
|
|
paddingRight: "0",
|
|
paddingBottom: tokens.spacingVerticalS,
|
|
overflowY: "visible",
|
|
},
|
|
},
|
|
/** A single clickable item in the left pane. */
|
|
listItem: {
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "space-between",
|
|
padding: `${tokens.spacingVerticalS} ${tokens.spacingHorizontalM}`,
|
|
borderRadius: tokens.borderRadiusMedium,
|
|
cursor: "pointer",
|
|
userSelect: "none",
|
|
overflow: "hidden",
|
|
":hover": {
|
|
backgroundColor: tokens.colorNeutralBackground2,
|
|
},
|
|
":focus-visible": {
|
|
outline: `2px solid ${tokens.colorBrandBackground}`,
|
|
outlineOffset: "2px",
|
|
},
|
|
},
|
|
/** Additional styles applied to the currently selected list item. */
|
|
listItemSelected: {
|
|
backgroundColor: tokens.colorNeutralBackground1Selected,
|
|
borderLeft: `3px solid ${tokens.colorBrandBackground}`,
|
|
paddingLeft: `calc(${tokens.spacingHorizontalM} - 3px)`,
|
|
":hover": {
|
|
backgroundColor: tokens.colorNeutralBackground1Selected,
|
|
},
|
|
},
|
|
/** Right pane that shows detail content for the selected item. */
|
|
detailPane: {
|
|
flex: "1",
|
|
overflowY: "auto",
|
|
paddingLeft: tokens.spacingHorizontalM,
|
|
"@media (max-width: 900px)": {
|
|
paddingLeft: "0",
|
|
},
|
|
},
|
|
header: {
|
|
marginBottom: tokens.spacingVerticalL,
|
|
},
|
|
tabContent: {
|
|
marginTop: tokens.spacingVerticalL,
|
|
animationName: "fadeInUp",
|
|
animationDuration: tokens.durationNormal,
|
|
animationTimingFunction: tokens.curveDecelerateMid,
|
|
animationFillMode: "both",
|
|
},
|
|
section: {
|
|
marginBottom: tokens.spacingVerticalXL,
|
|
},
|
|
/** Card container for form sections — adds visual separation and depth. */
|
|
sectionCard: {
|
|
backgroundColor: tokens.colorNeutralBackground2,
|
|
borderRadius: tokens.borderRadiusMedium,
|
|
padding: `${tokens.spacingVerticalM} ${tokens.spacingHorizontalL}`,
|
|
boxShadow: tokens.shadow4,
|
|
marginBottom: tokens.spacingVerticalS,
|
|
},
|
|
/** Label row at the top of a sectionCard. */
|
|
sectionCardHeader: {
|
|
color: tokens.colorNeutralForeground2,
|
|
borderBottom: `1px solid ${tokens.colorNeutralStroke2}`,
|
|
paddingBottom: tokens.spacingVerticalS,
|
|
marginBottom: tokens.spacingVerticalM,
|
|
textTransform: "uppercase",
|
|
letterSpacing: "0.05em",
|
|
fontSize: tokens.fontSizeBase200,
|
|
fontWeight: tokens.fontWeightSemibold,
|
|
},
|
|
/** Monospace input with left brand-colour accent bar. */
|
|
codeInput: {
|
|
fontFamily: "monospace",
|
|
borderLeft: `3px solid ${tokens.colorBrandStroke1}`,
|
|
},
|
|
/** Applied to AccordionItem wrappers to get a hover background on headers. */
|
|
accordionItem: {
|
|
"& button:hover": {
|
|
backgroundColor: tokens.colorNeutralBackground1Hover,
|
|
},
|
|
},
|
|
/** Applied to AccordionItem wrappers that are currently expanded. */
|
|
accordionItemOpen: {
|
|
borderLeft: `3px solid ${tokens.colorBrandBackground}`,
|
|
},
|
|
fieldRow: {
|
|
display: "grid",
|
|
gridTemplateColumns: "1fr 1fr",
|
|
gap: tokens.spacingHorizontalM,
|
|
marginBottom: tokens.spacingVerticalS,
|
|
alignItems: "end",
|
|
"@media (max-width: 900px)": {
|
|
gridTemplateColumns: "1fr",
|
|
},
|
|
},
|
|
fieldRowThree: {
|
|
display: "grid",
|
|
gridTemplateColumns: "1fr 1fr 1fr",
|
|
gap: tokens.spacingHorizontalM,
|
|
marginBottom: tokens.spacingVerticalS,
|
|
"@media (max-width: 900px)": {
|
|
gridTemplateColumns: "1fr 1fr",
|
|
},
|
|
"@media (max-width: 700px)": {
|
|
gridTemplateColumns: "1fr",
|
|
},
|
|
},
|
|
buttonRow: {
|
|
display: "flex",
|
|
gap: tokens.spacingHorizontalS,
|
|
marginTop: tokens.spacingVerticalM,
|
|
flexWrap: "wrap",
|
|
},
|
|
codeFont: {
|
|
fontFamily: "monospace",
|
|
fontSize: "0.85rem",
|
|
},
|
|
regexItem: {
|
|
display: "flex",
|
|
alignItems: "center",
|
|
gap: tokens.spacingHorizontalS,
|
|
marginBottom: tokens.spacingVerticalXS,
|
|
},
|
|
regexInput: {
|
|
flexGrow: "1",
|
|
fontFamily: "monospace",
|
|
borderLeft: `3px solid ${tokens.colorBrandStroke1}`,
|
|
},
|
|
fieldError: {
|
|
gridColumn: "1 / -1",
|
|
marginTop: tokens.spacingVerticalXXS,
|
|
color: tokens.colorPaletteRedForeground1,
|
|
},
|
|
logLine: {
|
|
padding: `${tokens.spacingVerticalXS} ${tokens.spacingHorizontalS}`,
|
|
borderRadius: tokens.borderRadiusSmall,
|
|
fontFamily: "monospace",
|
|
fontSize: "0.8rem",
|
|
marginBottom: tokens.spacingVerticalXXS,
|
|
wordBreak: "break-all",
|
|
},
|
|
matched: {
|
|
backgroundColor: tokens.colorPaletteGreenBackground2,
|
|
},
|
|
notMatched: {
|
|
backgroundColor: tokens.colorNeutralBackground3,
|
|
},
|
|
previewArea: {
|
|
maxHeight: "400px",
|
|
overflowY: "auto",
|
|
padding: tokens.spacingHorizontalS,
|
|
border: `1px solid ${tokens.colorNeutralStroke1}`,
|
|
borderRadius: tokens.borderRadiusMedium,
|
|
marginTop: tokens.spacingVerticalS,
|
|
},
|
|
infoText: {
|
|
color: tokens.colorNeutralForeground3,
|
|
fontStyle: "italic",
|
|
},
|
|
/** Empty-state container: centred icon + message. */
|
|
emptyState: {
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
gap: tokens.spacingVerticalM,
|
|
padding: `${tokens.spacingVerticalXXL} ${tokens.spacingHorizontalL}`,
|
|
color: tokens.colorNeutralForeground3,
|
|
textAlign: "center",
|
|
},
|
|
/** Auto-save status chip — for AutoSaveIndicator. */
|
|
autoSaveWrapper: {
|
|
display: "flex",
|
|
alignItems: "center",
|
|
gap: tokens.spacingHorizontalXS,
|
|
fontSize: tokens.fontSizeBase200,
|
|
color: tokens.colorNeutralForeground2,
|
|
},
|
|
autoSaveSaved: {
|
|
opacity: "1",
|
|
transform: "scale(1)",
|
|
transition: `opacity ${tokens.durationNormal} ${tokens.curveDecelerateMid}, transform ${tokens.durationNormal} ${tokens.curveDecelerateMid}`,
|
|
},
|
|
autoSaveFadingOut: {
|
|
opacity: "0",
|
|
transition: `opacity ${tokens.durationNormal} ${tokens.curveDecelerateMid}`,
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Global CSS keyframes injected once.
|
|
*
|
|
* ``makeStyles`` does not support top-level ``@keyframes``, so we inject them
|
|
* via a ``<style>`` element on first import. The function is idempotent.
|
|
*/
|
|
export function injectGlobalStyles(): void {
|
|
if (document.getElementById("bangui-global-styles")) return;
|
|
const style = document.createElement("style");
|
|
style.id = "bangui-global-styles";
|
|
style.textContent = `
|
|
@keyframes fadeInUp {
|
|
from { opacity: 0; transform: translateY(4px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
@keyframes fadeInScale {
|
|
from { opacity: 0; transform: scale(0.95); }
|
|
to { opacity: 1; transform: scale(1); }
|
|
}
|
|
/* ConfigListDetail responsive: show dropdown on narrow screens */
|
|
@media (max-width: 900px) {
|
|
.bangui-list-dropdown { display: block !important; }
|
|
.bangui-list-items { display: none !important; }
|
|
}
|
|
`;
|
|
document.head.appendChild(style);
|
|
}
|
|
|
|
// Inject keyframes on first module load (browser environment only).
|
|
if (typeof window !== "undefined") {
|
|
injectGlobalStyles();
|
|
}
|