Initial
This commit is contained in:
@@ -1,59 +1,137 @@
|
||||
import { Col, Divider, Row } from "antd";
|
||||
import { Card, Col, Row } from "antd";
|
||||
import { Children, isValidElement } from "react";
|
||||
import "./layout-form-row.styles.scss";
|
||||
|
||||
export default function LayoutFormRow({ header, children, grow = false, noDivider = false, ...restProps }) {
|
||||
const DividerHeader = () =>
|
||||
!noDivider && (
|
||||
<Divider titlePlacement="left" orientation="horizontal" style={{ marginTop: ".8rem" }}>
|
||||
{header}
|
||||
</Divider>
|
||||
);
|
||||
export default function LayoutFormRow({
|
||||
header,
|
||||
children,
|
||||
grow = false,
|
||||
noDivider = false,
|
||||
gutter = [16, 16],
|
||||
rowProps,
|
||||
|
||||
if (!children.length) {
|
||||
//We have only one element. It's going to get the whole thing.
|
||||
// Optional overrides if you ever need per-section customization
|
||||
surface = true,
|
||||
surfaceBg,
|
||||
surfaceHeaderBg,
|
||||
|
||||
...cardProps
|
||||
}) {
|
||||
const items = Children.toArray(children).filter(Boolean);
|
||||
if (items.length === 0) return null;
|
||||
|
||||
const title = !noDivider && header ? header : undefined;
|
||||
|
||||
const bg = surfaceBg ?? (surface ? "var(--imex-form-surface)" : undefined);
|
||||
const headBg = surfaceHeaderBg ?? (surface ? "var(--imex-form-surface-head)" : undefined);
|
||||
|
||||
const mergedStyles = mergeSemanticStyles(
|
||||
{
|
||||
header: {
|
||||
paddingInline: 16,
|
||||
background: headBg
|
||||
},
|
||||
body: {
|
||||
padding: 16,
|
||||
background: bg
|
||||
}
|
||||
},
|
||||
cardProps.styles
|
||||
);
|
||||
|
||||
const baseCardStyle = {
|
||||
marginBottom: ".8rem",
|
||||
...(bg ? { background: bg } : null), // ensures the “circled area” is tinted
|
||||
...cardProps.style
|
||||
};
|
||||
|
||||
// single child => just render it
|
||||
if (items.length === 1) {
|
||||
return (
|
||||
<div className="imex-form-row" {...restProps} style={{ marginBottom: ".8rem", ...restProps.style }}>
|
||||
<DividerHeader />
|
||||
{children}
|
||||
</div>
|
||||
<Card
|
||||
{...cardProps}
|
||||
title={cardProps.title ?? title}
|
||||
size={cardProps.size ?? "small"}
|
||||
variant={cardProps.variant ?? "outlined"}
|
||||
className={["imex-form-row", cardProps.className].filter(Boolean).join(" ")}
|
||||
style={baseCardStyle}
|
||||
styles={mergedStyles}
|
||||
>
|
||||
{items[0]}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
const rowGutter = { gutter: [16, 16] };
|
||||
|
||||
const colSpan = (spanOverride) => {
|
||||
if (spanOverride) return { span: spanOverride };
|
||||
return {
|
||||
xs: {
|
||||
span: !grow ? 24 : Math.max(12, 24 / children.length)
|
||||
},
|
||||
sm: {
|
||||
span: !grow ? 12 : Math.max(12, 24 / children.length)
|
||||
},
|
||||
md: {
|
||||
span: !grow ? 8 : Math.max(8, 24 / children.length)
|
||||
},
|
||||
lg: {
|
||||
span: !grow ? 6 : Math.max(6, 24 / children.length)
|
||||
},
|
||||
xl: {
|
||||
span: !grow ? 4 : Math.max(4, 24 / children.length)
|
||||
}
|
||||
};
|
||||
const count = items.length;
|
||||
const spanFor = (min) => Math.max(min, Math.floor(24 / count));
|
||||
|
||||
// Mobile-first: stack on xs
|
||||
const baseCol = {
|
||||
xs: { span: 24 },
|
||||
sm: { span: grow ? spanFor(12) : 12 },
|
||||
md: { span: grow ? spanFor(8) : 8 },
|
||||
lg: { span: grow ? spanFor(6) : 6 },
|
||||
xl: { span: grow ? spanFor(4) : 4 },
|
||||
xxl: { span: grow ? spanFor(4) : 4 }
|
||||
};
|
||||
//{header ? <Typography.Title level={4}>{header}</Typography.Title> : null}
|
||||
|
||||
const getColPropsForChild = (child) => {
|
||||
if (!isValidElement(child)) return baseCol;
|
||||
|
||||
// Back-compat with old pattern: child.props.span can override Col sizing
|
||||
const spanOverride = child.props?.span;
|
||||
if (typeof spanOverride === "number") return { span: spanOverride };
|
||||
if (spanOverride && typeof spanOverride === "object") return spanOverride;
|
||||
|
||||
// Optional explicit override: <Field col={{ md:{span:12}, ... }} />
|
||||
const colOverride = child.props?.col;
|
||||
if (colOverride && typeof colOverride === "object") {
|
||||
return { ...baseCol, ...colOverride };
|
||||
}
|
||||
|
||||
return baseCol;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="imex-form-row" {...restProps} style={{ marginBottom: ".8rem", ...restProps.style }}>
|
||||
<DividerHeader />
|
||||
<Row {...rowGutter}>
|
||||
{children.map(
|
||||
(c, idx) =>
|
||||
c && (
|
||||
<Col key={idx} {...colSpan(c && c.props && c.props.span)}>
|
||||
{c}
|
||||
</Col>
|
||||
)
|
||||
)}
|
||||
<Card
|
||||
{...cardProps}
|
||||
title={cardProps.title ?? title}
|
||||
size={cardProps.size ?? "small"}
|
||||
variant={cardProps.variant ?? "outlined"}
|
||||
className={["imex-form-row", cardProps.className].filter(Boolean).join(" ")}
|
||||
style={baseCardStyle}
|
||||
styles={mergedStyles}
|
||||
>
|
||||
<Row gutter={gutter} wrap {...rowProps}>
|
||||
{items.map((child, idx) => (
|
||||
<Col key={child?.key ?? idx} {...getColPropsForChild(child)}>
|
||||
{child}
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
function mergeSemanticStyles(defaults, userStyles) {
|
||||
if (!userStyles) return defaults;
|
||||
|
||||
if (typeof userStyles === "function") {
|
||||
return (info) => {
|
||||
const computed = userStyles(info) || {};
|
||||
return {
|
||||
...defaults,
|
||||
...computed,
|
||||
header: { ...defaults.header, ...(computed.header || {}) },
|
||||
body: { ...defaults.body, ...(computed.body || {}) }
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...defaults,
|
||||
...userStyles,
|
||||
header: { ...defaults.header, ...(userStyles.header || {}) },
|
||||
body: { ...defaults.body, ...(userStyles.body || {}) }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,20 +1,64 @@
|
||||
//Override Antd Row margin to save space on forms.
|
||||
/* layout-form-row.styles.scss */
|
||||
|
||||
/**
|
||||
* Surface tokens (light/dark)
|
||||
* - --imex-form-surface: the main section background (circled area)
|
||||
* - --imex-form-surface-head: slightly different strip for the Card header
|
||||
* - --imex-form-surface-border: border color for the card
|
||||
*
|
||||
* Update the dark selector(s) to match whatever you use (body class, data-theme, etc).
|
||||
*/
|
||||
|
||||
:root {
|
||||
--imex-form-surface: #fafafa; /* subtle contrast vs white page */
|
||||
--imex-form-surface-head: #f5f5f5; /* header strip */
|
||||
--imex-form-surface-border: #d9d9d9; /* matches AntD-ish border */
|
||||
}
|
||||
|
||||
/* Pick the selector that matches your app and remove the rest */
|
||||
html[data-theme="dark"] {
|
||||
--imex-form-surface: rgba(255, 255, 255, 0.01); /* subtle lift off page bg */
|
||||
--imex-form-surface-head: rgba(255, 255, 255, 0.06); /* slightly stronger for header strip */
|
||||
--imex-form-surface-border: rgba(5, 5, 5, 0.12);
|
||||
}
|
||||
|
||||
.imex-form-row {
|
||||
.ant-row {
|
||||
margin-bottom: 0rem;
|
||||
width: 100%;
|
||||
|
||||
/* Match old Divider title typography */
|
||||
.ant-card-head-title {
|
||||
font-weight: 500;
|
||||
font-size: var(--ant-font-size-lg);
|
||||
line-height: 1.2; /* optional, makes it feel like a section header */
|
||||
}
|
||||
|
||||
.ant-form-item-label {
|
||||
padding: 0rem;
|
||||
/* Make the whole section read as its own surface */
|
||||
&.ant-card {
|
||||
background: var(--imex-form-surface);
|
||||
border-color: var(--imex-form-surface-border);
|
||||
}
|
||||
|
||||
.ant-card-head {
|
||||
background: var(--imex-form-surface-head);
|
||||
border-bottom-color: var(--imex-form-surface-border);
|
||||
}
|
||||
|
||||
.ant-card-body {
|
||||
background: var(--imex-form-surface);
|
||||
}
|
||||
|
||||
/* Optional: slightly tighter on phones */
|
||||
@media (max-width: 575px) {
|
||||
.ant-card-head {
|
||||
padding-inline: 12px;
|
||||
}
|
||||
|
||||
label {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
padding: 0rem;
|
||||
margin: 0rem;
|
||||
.ant-card-body {
|
||||
padding: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Common form nicety */
|
||||
.ant-col > * {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user