Skip to content

Json Schema Form rwesponsive layouts #1316

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Work in Progress for Responsive Layout
  • Loading branch information
FalkWolsky committed Nov 17, 2024
commit c7bddbf001e63a8138ea364b4e95f75e75688fc1
Original file line number Diff line number Diff line change
@@ -1,33 +1,14 @@
import React, { useEffect, useRef, useState } from "react";
import { Row, Col } from "antd";
import {
ObjectFieldTemplateProps,
getTemplate,
getUiOptions,
descriptionId,
titleId,
canExpand,
} from "@rjsf/utils";
import { ConfigConsumer } from "antd/es/config-provider/context";
import { Row, Col } from 'antd';
import { ObjectFieldTemplateProps, getTemplate, getUiOptions, descriptionId, titleId, canExpand } from '@rjsf/utils';
import { ConfigConsumer } from 'antd/es/config-provider/context';
import { useContainerWidth } from "./jsonSchemaFormComp";
import styled from "styled-components";

const DESCRIPTION_COL_STYLE = {
paddingBottom: "8px",
paddingBottom: '8px',
};

interface ColSpan {
xs: number;
sm: number;
md: number;
lg: number;
xl: number;
}

interface UiOptions {
colSpan: ColSpan;
rowGutter: number;
// other properties...
}

const ObjectFieldTemplate = (props: ObjectFieldTemplateProps) => {
const {
title,
Expand All @@ -42,59 +23,62 @@ const ObjectFieldTemplate = (props: ObjectFieldTemplateProps) => {
readonly,
registry,
} = props;

const containerRef = useRef<HTMLDivElement>(null);
const [containerWidth, setContainerWidth] = useState(0);

// Monitor the container's width
useEffect(() => {
const updateWidth = () => {
if (containerRef.current) {
setContainerWidth(containerRef.current.offsetWidth);
}
};

// Create a ResizeObserver to watch for width changes
const resizeObserver = new ResizeObserver(() => {
updateWidth();
});

if (containerRef.current) {
resizeObserver.observe(containerRef.current);
}

// Initial update
updateWidth();

// Cleanup observer on unmount
return () => {
resizeObserver.disconnect();
};
}, []);
const containerWidth = useContainerWidth();

const uiOptions = getUiOptions(uiSchema);
const TitleFieldTemplate = getTemplate("TitleFieldTemplate", registry, uiOptions);
const DescriptionFieldTemplate = getTemplate("DescriptionFieldTemplate", registry, uiOptions);
const TitleFieldTemplate = getTemplate('TitleFieldTemplate', registry, uiOptions);
const DescriptionFieldTemplate = getTemplate('DescriptionFieldTemplate', registry, uiOptions);
const {
ButtonTemplates: { AddButton },
} = registry.templates;

const defaultResponsiveColSpan = (width: number) => {
if (width > 1200) return 8; // Wide screens
if (width > 768) return 12; // Tablets
return 24; // Mobile
// Define responsive column spans based on the ui:props or fallback to defaults
const defaultResponsiveColSpan = {
xs: 24, // Extra small devices
sm: 24, // Small devices
md: 12, // Medium devices
lg: 12, // Large devices
xl: 8, // Extra large devices
};

const { rowGutter = 4 } = uiSchema?.["ui:props"] || {};

const calculateResponsiveColSpan = (element: any): { span: number } => {
const { rowGutter = 4 } = uiSchema?.['ui:props'] || {};

const calculateResponsiveColSpan = (element: any, level: number): { span: number } => {

console.log("Calculating span for", element.name, "at level", level);

// root level
if (level === 0) return { span: 24 };

// Check if the element has a layout definition in ui:grid
const gridColSpan = uiSchema?.['ui:grid']
?.find((row: Record<string, any>) => row[element.name])
?. [element.name];

if (gridColSpan) {
if (typeof gridColSpan === "number") {
return { span: gridColSpan };
} else if (typeof gridColSpan === "object") {
if (containerWidth > 1200 && gridColSpan.xl !== undefined) {
return { span: gridColSpan.xl };
} else if (containerWidth > 992 && gridColSpan.lg !== undefined) {
return { span: gridColSpan.lg };
} else if (containerWidth > 768 && gridColSpan.md !== undefined) {
return { span: gridColSpan.md };
} else if (containerWidth > 576 && gridColSpan.sm !== undefined) {
return { span: gridColSpan.sm };
} else if (gridColSpan.xs !== undefined) {
return { span: gridColSpan.xs };
}
}
}

// Fallback to default colSpan or ui:props.colSpan
const uiSchemaProps = getUiOptions(element.content.props.uiSchema)?.["ui:props"] as
| { colSpan?: Record<string, number> | number }
| undefined;

const uiSchemaColSpan = uiSchemaProps?.colSpan;
const defaultSpan = containerWidth > 1200 ? 8 : containerWidth > 768 ? 12 : 24;

if (uiSchemaColSpan) {
if (typeof uiSchemaColSpan === "number") {
Expand All @@ -114,133 +98,94 @@ const ObjectFieldTemplate = (props: ObjectFieldTemplateProps) => {
}
}

// Default responsive behavior
const defaultSpan = containerWidth > 1200 ? 8 : containerWidth > 768 ? 12 : 24;
return { span: defaultSpan };
};

const renderSectionLayout = (properties: any[], uiGrid: any, section: string) => {

if (uiGrid && Array.isArray(uiGrid)) {
return (
<Row gutter={rowGutter} key={section}>
{uiGrid.map((ui_row: Record<string, any>) =>
Object.keys(ui_row).map((row_item) => {
const element = properties.find((p) => p.name === row_item);
if (element) {
const span = calculateResponsiveColSpan(element).span;
return (
<Col key={element.name} span={span}>
{element.content}
</Col>
);
}
return null;
})
)}
</Row>
);
}

// Default layout if no grid is provided
const renderProperties = (properties: any[], level: number) => {
console.log("Rendering level:", level); // Debugging level
return (
<Row gutter={rowGutter} key={section}>
{properties.map((element) => (
<Col key={element.name} {...calculateResponsiveColSpan(element)}>
{element.content}
</Col>
))}
<Row
gutter={rowGutter}
style={level === 0 ? { width: "100%" } : { marginLeft: -8, marginRight: -8 }}
>
{properties.map((element) => {
const span = calculateResponsiveColSpan(element, level);

// Check if the element is an object or array and has nested properties
if (element.content?.props?.schema?.type === "object" && element.content.props.properties) {
// Render nested objects with an incremented level
return (
<Col key={element.name} span={24}>
<fieldset>
<legend>{element.content.props.title || element.name}</legend>
{renderProperties(element.content.props.properties, level + 1)}
</fieldset>
</Col>
);
}

// Render normal elements
return (
<Col key={element.name} span={span.span}>
{element.content}
</Col>
);
})}
</Row>
);
};

const renderCustomLayout = () => {
const schemaType = schema.type as string;
switch (schemaType) {
case "Group":
return (
<div style={{ border: "1px solid #ccc", padding: "15px", marginBottom: "10px" }}>
<h3>{schema.label || "Group"}</h3>
{renderSectionLayout(properties, uiSchema?.["ui:grid"], schema.label)}
</div>
);
case "HorizontalLayout":
return (
<Row gutter={rowGutter} style={{ display: "flex", gap: "10px" }}>
{properties.map((element) => (
<Col key={element.name} {...calculateResponsiveColSpan(element)}>
{element.content}
</Col>
))}
</Row>
);
case "VerticalLayout":
return (
<div style={{ display: "flex", flexDirection: "column", gap: "10px" }}>
{properties.map((element) => (
<div key={element.name}>{element.content}</div>
))}
</div>
);
default:
return null; // Fall back to default rendering if no match
}
};

// Check if the schema is a custom layout type
const schemaType = schema.type as string; // Extract schema type safely
const isCustomLayout = ["Group", "HorizontalLayout", "VerticalLayout"].includes(schemaType);


return (
<div ref={containerRef}>
<ConfigConsumer>
{(configProps) => (
<fieldset id={idSchema.$id} className="form-section">
{!isCustomLayout && (
<>
{schema.type === "object" && title && (
<legend>
<TitleFieldTemplate
id={titleId(idSchema)}
title={title}
required={props.required}
schema={schema}
uiSchema={uiSchema}
registry={registry}
/>
</legend>
)}
{description && (
<Col span={24} style={DESCRIPTION_COL_STYLE}>
<DescriptionFieldTemplate
id={descriptionId(idSchema)}
description={description}
schema={schema}
uiSchema={uiSchema}
registry={registry}
/>
</Col>
)}
{renderSectionLayout(properties, uiSchema?.["ui:grid"], "root")}
</>
)}

{isCustomLayout && renderCustomLayout()}

{canExpand(schema, uiSchema, formData) && (
<Row justify="end" style={{ marginTop: "24px" }}>
<Col>
<AddButton
className="object-property-expand"
onClick={onAddClick(schema)}
disabled={disabled || readonly}
registry={registry}
/>
</Col>
</Row>
)}
</fieldset>
)}
</ConfigConsumer>
</div>
<ConfigConsumer>
{(configProps) => (
<fieldset id={idSchema.$id} className="form-section">
{/* Render Title */}
{schema.type === "object" && title && (
<legend>
<TitleFieldTemplate
id={titleId(idSchema)}
title={title}
required={props.required}
schema={schema}
uiSchema={uiSchema}
registry={registry}
/>
</legend>
)}
{/* Render Description */}
{description && (
<Row>
<Col span={24} style={DESCRIPTION_COL_STYLE}>
<DescriptionFieldTemplate
id={descriptionId(idSchema)}
description={description}
schema={schema}
uiSchema={uiSchema}
registry={registry}
/>
</Col>
</Row>
)}
{/* Render Properties */}
{renderProperties(properties, 0)}
{/* Expand Button */}
{canExpand(schema, uiSchema, formData) && (
<Row justify="end" style={{ width: "100%", marginTop: "24px" }}>
<Col>
<AddButton
className="object-property-expand"
onClick={onAddClick(schema)}
disabled={disabled || readonly}
registry={registry}
/>
</Col>
</Row>
)}
</fieldset>
)}
</ConfigConsumer>
);
};

Expand Down
Loading