Files
BanGUI/frontend/src/components/config/configStyles.ts
Lukas 3e4f688484 Fix vertical alignment of DNS Mode dropdown in jail config
Add alignItems: "end" to the fieldRow grid style so that all grid
cells align their content to the bottom edge of the row. This ensures
the DNS Mode <Select> and the Date Pattern <Combobox> sit on the same
horizontal baseline even though Date Pattern carries a hint line that
makes it taller.

All other fieldRow usages have consistent hint presence across their
fields, so no visual regressions are introduced.
2026-03-14 09:51:00 +01:00

258 lines
7.8 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}`,
},
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();
}