mirror of
https://github.com/fluxscape/fluxscape.git
synced 2026-01-12 15:22:54 +01:00
Initial commit
Co-Authored-By: Eric Tuvesson <eric.tuvesson@gmail.com> Co-Authored-By: mikaeltellhed <2311083+mikaeltellhed@users.noreply.github.com> Co-Authored-By: kotte <14197736+mrtamagotchi@users.noreply.github.com> Co-Authored-By: Anders Larsson <64838990+anders-topp@users.noreply.github.com> Co-Authored-By: Johan <4934465+joolsus@users.noreply.github.com> Co-Authored-By: Tore Knudsen <18231882+torekndsn@users.noreply.github.com> Co-Authored-By: victoratndl <99176179+victoratndl@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
import React, { useRef, useState, useEffect } from 'react';
|
||||
|
||||
import { ForEachComponent } from '../../../nodes/std-library/data/foreach';
|
||||
import { Noodl, Slot } from '../../../types';
|
||||
|
||||
export interface ColumnsProps extends Noodl.ReactProps {
|
||||
marginX: string;
|
||||
marginY: string;
|
||||
|
||||
justifyContent: 'flex-start' | 'flex-end' | 'center';
|
||||
direction: 'row' | 'column';
|
||||
minWidth: string;
|
||||
layoutString: string;
|
||||
|
||||
children: Slot;
|
||||
}
|
||||
|
||||
function calcAutofold(layout, minWidths, containerWidth, marginX) {
|
||||
const first = _calcAutofold(layout, minWidths, containerWidth, marginX);
|
||||
const second = _calcAutofold(first.layout, minWidths, containerWidth, marginX);
|
||||
|
||||
if (first.totalFractions === second.totalFractions) {
|
||||
return first;
|
||||
} else {
|
||||
return calcAutofold(second.layout, minWidths, containerWidth, marginX);
|
||||
}
|
||||
}
|
||||
|
||||
function _calcAutofold(layout, minWidth, containerWidth, marginX) {
|
||||
const totalFractions = layout.reduce((a, b) => a + b, 0);
|
||||
const fractionSize = 100 / totalFractions;
|
||||
|
||||
const rowWidth = layout.reduce(
|
||||
(acc, curr, i) => {
|
||||
return {
|
||||
expected: acc.expected + (fractionSize / 100) * containerWidth * curr,
|
||||
min: acc.min + parseFloat(minWidth) + marginX
|
||||
};
|
||||
},
|
||||
{ expected: 0, min: 0, max: null }
|
||||
);
|
||||
|
||||
const newLayout = layout;
|
||||
|
||||
if (rowWidth.expected < rowWidth.min) {
|
||||
newLayout.pop();
|
||||
}
|
||||
|
||||
const newTotalFractions = newLayout.reduce((a, b) => a + b, 0);
|
||||
const newFractionSize = 100 / newTotalFractions;
|
||||
const newColumnAmount = newLayout.length;
|
||||
|
||||
return {
|
||||
layout: newLayout,
|
||||
totalFractions: newTotalFractions,
|
||||
fractionSize: newFractionSize,
|
||||
columnAmount: newColumnAmount
|
||||
};
|
||||
}
|
||||
|
||||
export function Columns(props: ColumnsProps) {
|
||||
if (!props.children) return null;
|
||||
let columnLayout = null;
|
||||
|
||||
const containerRef = useRef(null);
|
||||
const [containerWidth, setContainerWidth] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const container = containerRef?.current;
|
||||
if (!container) return;
|
||||
|
||||
const observer = new ResizeObserver(() => {
|
||||
const container = containerRef.current;
|
||||
if (!container) return;
|
||||
setContainerWidth(container.offsetWidth);
|
||||
});
|
||||
|
||||
observer.observe(container);
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
}, []);
|
||||
|
||||
switch (typeof props.layoutString) {
|
||||
case 'string':
|
||||
columnLayout = props.layoutString.trim();
|
||||
break;
|
||||
|
||||
case 'number':
|
||||
columnLayout = String(props.layoutString).trim();
|
||||
break;
|
||||
|
||||
default:
|
||||
columnLayout = null;
|
||||
}
|
||||
|
||||
if (!columnLayout) {
|
||||
return <>{props.children}</>;
|
||||
}
|
||||
|
||||
// all data for childrens width calculation
|
||||
const targetLayout = columnLayout.split(' ').map((number) => parseInt(number));
|
||||
|
||||
// constraints
|
||||
const { layout, columnAmount, fractionSize } = calcAutofold(
|
||||
targetLayout,
|
||||
props.minWidth,
|
||||
containerWidth,
|
||||
props.marginX
|
||||
);
|
||||
|
||||
let children = [];
|
||||
let forEachComponent = null;
|
||||
|
||||
// ForEachCompoent breaks the layout but is needed to send onMount/onUnmount
|
||||
if (!Array.isArray(props.children)) {
|
||||
children = [props.children];
|
||||
} else {
|
||||
children = props.children.filter((child) => child.type !== ForEachComponent);
|
||||
forEachComponent = props.children.find((child) => child.type === ForEachComponent);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={['columns-container', props.className].join(' ')}
|
||||
ref={containerRef}
|
||||
style={{
|
||||
marginTop: parseFloat(props.marginY) * -1,
|
||||
marginLeft: parseFloat(props.marginX) * -1,
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
alignItems: 'stretch',
|
||||
justifyContent: props.justifyContent,
|
||||
flexDirection: props.direction,
|
||||
width: `calc(100% + (${parseFloat(props.marginX)}px)`,
|
||||
boxSizing: 'border-box',
|
||||
...props.style
|
||||
}}
|
||||
>
|
||||
{forEachComponent && forEachComponent}
|
||||
|
||||
{children.map((child, i) => {
|
||||
return (
|
||||
<div
|
||||
className="column-item"
|
||||
key={i}
|
||||
style={{
|
||||
boxSizing: 'border-box',
|
||||
paddingTop: props.marginY,
|
||||
paddingLeft: props.marginX,
|
||||
width: layout[i % columnAmount] * fractionSize + '%',
|
||||
flexShrink: 0,
|
||||
flexGrow: 0,
|
||||
minWidth: props.minWidth
|
||||
// maxWidths needs some more thought
|
||||
//maxWidth: getMinMaxInputValues(maxWidths, columnAmount, props.marginX, i)
|
||||
}}
|
||||
>
|
||||
{React.cloneElement(child)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user