diff --git a/frontend/src/styles.css b/frontend/src/styles.css
index d67874a..d12131d 100644
--- a/frontend/src/styles.css
+++ b/frontend/src/styles.css
@@ -1223,6 +1223,7 @@ button.danger:hover:not([disabled]) {
font-size: 1rem;
font-weight: 600;
color: var(--fg);
+ padding-left: 0.25rem;
}
.sidebar__tenant {
@@ -2198,11 +2199,17 @@ button.danger:hover:not([disabled]) {
.panel-header {
display: flex;
align-items: center;
- justify-content: space-between;
gap: 0.75rem;
padding: 0.5rem;
}
+.panel-header__leading,
+.panel-header__actions {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
.panel-header__title {
margin: 0;
font-size: 1rem;
@@ -2215,26 +2222,18 @@ button.danger:hover:not([disabled]) {
min-width: 0;
}
+.panel-header > .panel-header__title:first-child {
+ padding-left: 0.5rem;
+}
+
+.panel-header__actions {
+ margin-left: auto;
+}
+
.panel-body {
padding: 0.5rem 1rem;
}
-.panel-actions {
- display: flex;
- align-items: center;
- gap: 0.5rem;
- flex: 1 1 auto;
-}
-
-.panel-actions__spacer {
- flex-grow: 1;
-}
-
-.panel-actions > h1:first-child,
-.panel-actions > h2:first-child {
- padding-left: 0.5rem;
-}
-
.detail-panel .panel-body {
flex: 1;
display: flex;
diff --git a/frontend/src/ui/PanelHeader.jsx b/frontend/src/ui/PanelHeader.jsx
new file mode 100644
index 0000000..e7bad5f
--- /dev/null
+++ b/frontend/src/ui/PanelHeader.jsx
@@ -0,0 +1,53 @@
+import React from 'react';
+
+const composeClassName = (base, extra) => (extra ? `${base} ${extra}` : base);
+
+const PanelHeader = ({
+ className = '',
+ leading = null,
+ title = null,
+ titleTag = 'h2',
+ titleProps = {},
+ actions = null,
+}) => {
+ const headerClassName = composeClassName('panel-header', className);
+
+ const renderTitle = () => {
+ if (title == null) {
+ return null;
+ }
+
+ if (React.isValidElement(title)) {
+ if (title.type === React.Fragment) {
+ return (
+
+ {title}
+
+ );
+ }
+
+ const mergedClassName = composeClassName('panel-header__title', title.props.className || '');
+ return React.cloneElement(title, {
+ ...titleProps,
+ className: mergedClassName,
+ });
+ }
+
+ const HeadingTag = titleTag;
+ return (
+
+ {title}
+
+ );
+ };
+
+ return (
+
+ {leading ?
{leading}
: null}
+ {renderTitle()}
+ {actions ?
{actions}
: null}
+
+ );
+};
+
+export default PanelHeader;