Skip to content

Commit

Permalink
Merge pull request #208 from eccenca/feature/flexibleLayoutHelper
Browse files Browse the repository at this point in the history
Provide helper component to create flexible layout boxes
  • Loading branch information
haschek authored Oct 7, 2024
2 parents 2fe9927 + 4466325 commit 8a4ce72
Show file tree
Hide file tree
Showing 9 changed files with 240 additions and 0 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p

- `<MultiSuggestField />`
- An optional custom search function property has been added, it defines how to filter elements.
- `<FlexibleLayoutContainer />` and `<FlexibleLayoutItem />`
- helper components to create flex layouts for positioning sub elements
- stop misusing `Toolbar*` components to do that (anti pattern)
- `<PropertyValueList />` and `<PropertyValuePair />`
- `singleColumn` property to display label and value below each other
- `<Label />`
Expand Down
67 changes: 67 additions & 0 deletions src/components/FlexibleLayout/FlexibleLayoutContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React, { forwardRef } from "react"; // @see https://github.com/storybookjs/storybook/issues/8881#issuecomment-831937051

import { CLASSPREFIX as eccgui } from "../../configuration/constants";

import { DividerProps } from "./../Separation/Divider";

export interface FlexibleLayoutContainerProps extends React.HTMLAttributes<HTMLDivElement> {
/**
* Use the exact space defined by the parent element.
* This parent element must be displayed using a fixed, relative or absolute position.
*/
useAbsoluteSpace?: boolean;
/**
* If set then the container behaves similar to a column and displays its items on a vertical axis.
* Children could used as rows in this situation.
*/
vertical?: boolean;
/**
* If true the used amount of space for each item is related to the amout of its content compared to each other.
* Otherwise the items use equal amounts as long this is possible.
*/
noEqualItemSpace?: boolean;
/**
* Quick way to add whitespace between container children.
* For more complex usecases like dividers you need to use extra `<FlexibleLayoutItem/>` components in combination with `<Divider/>` components.
*/
gapSize?: DividerProps["addSpacing"];
}

/**
* Simple layout helper to organize items into rows and columns that are not necessarily need to be aligned.
* A `FlexibleLayoutContainer` can contain `FlexibleLayoutItem`s.
* Do not misuse it as grid because it comes without any predefined ratios for widths and heights.
*/
export const FlexibleLayoutContainer = forwardRef<HTMLDivElement, FlexibleLayoutContainerProps>(
(
{
children,
className = "",
useAbsoluteSpace,
vertical,
noEqualItemSpace,
gapSize = "none",
...otherDivProps
}: FlexibleLayoutContainerProps,
ref
) => {
return (
<div
className={
`${eccgui}-flexible__container` +
(useAbsoluteSpace ? ` ${eccgui}-flexible__container--absolutespace` : "") +
(vertical ? ` ${eccgui}-flexible__container--vertical` : "") +
(noEqualItemSpace ? ` ${eccgui}-flexible__container--notequalitemspace` : "") +
(gapSize !== "none" ? ` ${eccgui}-flexible__container--gap-${gapSize}` : "") +
(className ? " " + className : "")
}
ref={ref}
{...otherDivProps}
>
{children}
</div>
);
}
);

export default FlexibleLayoutContainer;
59 changes: 59 additions & 0 deletions src/components/FlexibleLayout/FlexibleLayoutItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import React, { forwardRef } from "react";

import { CLASSPREFIX as eccgui } from "../../configuration/constants";

export interface FlexibleLayoutItemProps extends React.HTMLAttributes<HTMLDivElement> {
/**
* Defines the ability for the item to grow.
* The factor defines how much space the item would take up compared to the other items with a grow factor greater than zero.
* Must be equal or greater than zero.
* With a factor of `0` the item cannot grow.
*/
growFactor?: number;
/**
* Defines the ability for the item to shrink.
* The factor defines how strong the shrink effect has impact on the item compared to the other items with a shrink factor greater than zero.
* Must be equal or greater than zero.
* With a factor of `0` the item cannot shrink.
*/
shrinkFactor?: number;
}

/**
* Simple layout helper to organize items into rows and columns that are not necessarily need to be aligned.
* `FlexibleLayoutItem`s can contain `FlexibleLayoutContainer` for more partitions.
* `FlexibleLayoutItem` siblings will share all available space from the `FlexibleLayoutContainer` container.
*/
export const FlexibleLayoutItem = forwardRef<HTMLDivElement, FlexibleLayoutItemProps>(
(
{
children,
className = "",
growFactor = 1,
shrinkFactor = 1,
style,
...otherDivProps
}: FlexibleLayoutItemProps,
ref
) => {
const sizing = {} as any;
if (typeof growFactor !== "undefined" && growFactor >= 0 && growFactor !== 1) {
sizing[`--${eccgui}-flexible-item-grow`] = growFactor.toString(10);
}
if (typeof shrinkFactor !== "undefined" && shrinkFactor >= 0 && shrinkFactor !== 1) {
sizing[`--${eccgui}-flexible-item-shrink`] = shrinkFactor.toString(10);
}
return (
<div
className={`${eccgui}-flexible__item` + (className ? " " + className : "")}
style={{ ...(style ?? {}), ...sizing }}
ref={ref}
{...otherDivProps}
>
{children}
</div>
);
}
);

export default FlexibleLayoutItem;
48 changes: 48 additions & 0 deletions src/components/FlexibleLayout/flexiblelayout.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
.#{$eccgui}-flexible__container {
--#{$eccgui}-flexible-item-shrink: 1;
--#{$eccgui}-flexible-item-grow: 1;
--#{$eccgui}-flexible-container-gapsize: 0;

position: relative;
display: flex;
flex-flow: row nowrap;
gap: var(--#{$eccgui}-flexible-container-gapsize);
place-content: stretch center;
align-items: stretch;
width: 100%;
}

.#{$eccgui}-flexible__container--absolutespace {
position: absolute;
inset: 0;
}

.#{$eccgui}-flexible__container--vertical {
flex-direction: column;
}

.#{$eccgui}-flexible__container--gap-tiny {
--#{$eccgui}-flexible-container-gapsize: #{$eccgui-size-block-whitespace * 0.25};
}
.#{$eccgui}-flexible__container--gap-small {
--#{$eccgui}-flexible-container-gapsize: #{$eccgui-size-block-whitespace * 0.5};
}
.#{$eccgui}-flexible__container--gap-medium {
--#{$eccgui}-flexible-container-gapsize: #{$eccgui-size-block-whitespace};
}
.#{$eccgui}-flexible__container--gap-large {
--#{$eccgui}-flexible-container-gapsize: #{$eccgui-size-block-whitespace * 1.5};
}
.#{$eccgui}-flexible__container--gap-xlarge {
--#{$eccgui}-flexible-container-gapsize: #{$eccgui-size-block-whitespace * 2};
}

.#{$eccgui}-flexible__item {
position: relative;
flex: var(--#{$eccgui}-flexible-item-grow) var(--#{$eccgui}-flexible-item-shrink) 100%;
min-width: 0;

.#{$eccgui}-flexible__container--notequalitemspace > & {
flex-basis: auto;
}
}
2 changes: 2 additions & 0 deletions src/components/FlexibleLayout/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./FlexibleLayoutContainer";
export * from "./FlexibleLayoutItem";
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from "react";
import { LoremIpsum } from "react-lorem-ipsum";
import { Meta, StoryFn } from "@storybook/react";

import { Divider, FlexibleLayoutContainer, FlexibleLayoutItem, HtmlContentBlock } from "../../../../index";

export default {
title: "Components/FlexibleLayout/Container",
component: FlexibleLayoutContainer,
} as Meta<typeof FlexibleLayoutContainer>;

const Template: StoryFn<typeof FlexibleLayoutContainer> = (args) => (
<div style={{ position: "relative", height: "400px" }}>
<FlexibleLayoutContainer {...args}>
<FlexibleLayoutItem>
<HtmlContentBlock>
<LoremIpsum p={1} avgSentencesPerParagraph={3} random={false} />
</HtmlContentBlock>
</FlexibleLayoutItem>
<FlexibleLayoutItem>
<Divider />
<HtmlContentBlock>
<LoremIpsum p={3} avgSentencesPerParagraph={2} random={false} />
</HtmlContentBlock>
</FlexibleLayoutItem>
</FlexibleLayoutContainer>
</div>
);

export const Default = Template.bind({});
Default.args = {};
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from "react";
import { LoremIpsum } from "react-lorem-ipsum";
import { Meta, StoryFn } from "@storybook/react";

import { FlexibleLayoutContainer, FlexibleLayoutItem, HtmlContentBlock } from "../../../../index";

export default {
title: "Components/FlexibleLayout/Item",
component: FlexibleLayoutItem,
} as Meta<typeof FlexibleLayoutItem>;

const Template: StoryFn<typeof FlexibleLayoutItem> = (args) => (
<FlexibleLayoutContainer horizontal>
<FlexibleLayoutItem {...args}>
<HtmlContentBlock>
<LoremIpsum p={1} avgSentencesPerParagraph={1} avgWordsPerSentence={3} random={false} />
</HtmlContentBlock>
</FlexibleLayoutItem>
<FlexibleLayoutItem>
<HtmlContentBlock>
<LoremIpsum p={2} avgSentencesPerParagraph={4} random={false} />
</HtmlContentBlock>
</FlexibleLayoutItem>
</FlexibleLayoutContainer>
);

export const Default = Template.bind({});
Default.args = {};
1 change: 1 addition & 0 deletions src/components/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
@import "./Checkbox/checkbox";
@import "./Depiction/depiction";
@import "./Dialog/dialog";
@import "./FlexibleLayout/flexiblelayout";
@import "./Form/form";
@import "./Grid/grid";
@import "./HoverToggler/hovertoggler";
Expand Down
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export * from "./Checkbox/Checkbox";
export * from "./ContextOverlay";
export * from "./Depiction/Depiction";
export * from "./Dialog";
export * from "./FlexibleLayout";
export * from "./Form";
export * from "./Grid";
export * from "./HoverToggler/HoverToggler";
Expand Down

0 comments on commit 8a4ce72

Please sign in to comment.