Skip to content

Commit

Permalink
Merge pull request #75 from aversini/feat-adding-Flexgrid-and-Flexgri…
Browse files Browse the repository at this point in the history
…dItem-components

feat: adding Flexgrid and FlexgridItem components
  • Loading branch information
aversini authored Nov 25, 2023
2 parents e7214cc + db15930 commit f222142
Show file tree
Hide file tree
Showing 11 changed files with 592 additions and 0 deletions.
193 changes: 193 additions & 0 deletions packages/documentation/src/stories/Flexgrid.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import type { Meta, StoryObj } from "@storybook/react";
import { Flexgrid, FlexgridItem } from "@versini/ui-components";

const meta: Meta<typeof Flexgrid> = {
component: Flexgrid,
parameters: {
controls: { exclude: ["spacing"], sort: "requiredFirst" },
},
args: {
className: "",
rowGap: 1,
columnGap: 1,
alignHorizontal: "flex-start",
alignVertical: "flex-start",
direction: "row",
},
argTypes: {
className: {
control: "text",
},
rowGap: {
control: "number",
},
columnGap: {
control: "number",
},
direction: {
control: "radio",
options: ["row", "column", "row-reverse", "column-reverse"],
},
alignHorizontal: {
control: "radio",
options: [
"flex-start",
"center",
"flex-end",
"space-between",
"space-around",
"space-evenly",
],
},
alignVertical: {
control: "radio",
options: ["flex-start", "center", "flex-end", "stretch", "baseline"],
},
},
};

export default meta;

type Story = StoryObj<typeof Flexgrid>;

const Container = ({ children }: { children: React.ReactNode }) => (
<div className="bg-slate-500 p-11">{children}</div>
);

export const Basic: Story = {
render: (args) => (
<Flexgrid {...args}>
<FlexgridItem span={4}>
<Container aria-label="item 1">
item 1
<div>
<small>(starts col 1, spans 4 cols)</small>
</div>
</Container>
</FlexgridItem>
<FlexgridItem span={4}>
<Container aria-label="item 2">
item 2
<div>
<small>(starts col 5, spans 4 cols)</small>
</div>
</Container>
</FlexgridItem>
<FlexgridItem span={4}>
<Container aria-label="item 3">
item 3
<div>
<small>(starts col 9, spans 4 cols)</small>
</div>
</Container>
</FlexgridItem>
<FlexgridItem span={4}>
<Container aria-label="item 4">
item 4
<div>
<small>(starts col 1, spans 4 cols)</small>
</div>
</Container>
</FlexgridItem>

<FlexgridItem span={2} />

<FlexgridItem span="auto">
<Container aria-label="item 5">
item 5
<div>
<small>(starts col 7, spans across the remaining space)</small>
</div>
</Container>
</FlexgridItem>

<FlexgridItem span={6}>
<Container aria-label="item 6">
item 6
<div>
<small>(starts col 1, spans 6 cols)</small>
</div>
</Container>
</FlexgridItem>

<FlexgridItem span={6} />

<FlexgridItem span={7}>
<Container aria-label="item 7">
item 7
<div>
<small>(starts col 1, spans 7 cols)</small>
</div>
</Container>
</FlexgridItem>
<FlexgridItem span={2} />
<FlexgridItem span={3}>
<Container aria-label="item 8">
item 8
<div>
<small>(starts col 10, spans 3 cols)</small>
</div>
</Container>
</FlexgridItem>

<FlexgridItem span={12}>
<Container aria-label="item 9">
item 9
<div>
<small>(starts col 1, spans 12 cols)</small>
</div>
</Container>
</FlexgridItem>
</Flexgrid>
),
};

export const Interactive: Story = {
args: {
alignHorizontal: "flex-start",
alignVertical: "flex-start",
height: "auto",
},
render: (args) => (
<>
<Flexgrid {...args}>
<FlexgridItem>
<Container aria-label="cell 1">
<div
style={{
padding: "5px",
backgroundColor: "#e5f2e9",
}}
>
Cell 1
</div>
</Container>
</FlexgridItem>
<FlexgridItem>
<Container aria-label="cell 2">
<div
style={{
padding: "10px",
backgroundColor: "#e5f2e9",
}}
>
Cell 2
</div>
</Container>
</FlexgridItem>
<FlexgridItem>
<Container aria-label="cell 3">
<div
style={{
padding: "15px",
backgroundColor: "#e5f2e9",
}}
>
Cell 3
</div>
</Container>
</FlexgridItem>
</Flexgrid>
</>
),
};
10 changes: 10 additions & 0 deletions packages/ui-components/src/common/__tests__/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,13 @@ export const expectToHaveClasses = (
expect(Array.from(elementClasses)).toContain(expectedClass);
});
};

export const expectToHaveStyles = (
element: HTMLElement,
styles: Record<string, string>,
) => {
const elementStyles = getComputedStyle(element);
Object.entries(styles).forEach(([property, value]) => {
expect(elementStyles.getPropertyValue(property)).toBe(value);
});
};
5 changes: 5 additions & 0 deletions packages/ui-components/src/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,10 @@ export const MAIN_CLASSNAME = "av-main";
export const TEXT_INPUT_CLASSNAME = "av-text-input";
export const TEXT_INPUT_WRAPPER_CLASSNAME = "av-text-input-wrapper";
export const TEXT_INPUT_HELPER_TEXT_CLASSNAME = "av-text-input-helper-text";
export const FLEXGRID_CLASSNAME = "av-flexgrid";
export const FLEXGRID_ITEM_CLASSNAME = "av-flexgrid-item";

export const VISUALLY_HIDDEN_CLASSNAME = "av-visually-hidden";

export const FLEXGRID_MAX_COLUMNS = 12;
export const FLEXGRID_GAP_RATIO = 0.25;
53 changes: 53 additions & 0 deletions packages/ui-components/src/components/Flexgrid/Flexgrid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import clsx from "clsx";

import { FLEXGRID_CLASSNAME, FLEXGRID_GAP_RATIO } from "../../common/constants";
import { FlexgridContext } from "./FlexgridContext";
import type { FlexgridProps } from "./FlexgridTypes";

export const Flexgrid = ({
children,
className,
columnGap = 1,
rowGap = 0,
height = "auto",
width = "auto",

direction = "row",
alignHorizontal = "flex-start",
alignVertical = "flex-start",

...otherProps
}: FlexgridProps) => {
const cssRoot = {
flexDirection: direction,
justifyContent: alignHorizontal,
alignItems: alignVertical,
height,
width,
/**
* Trick to account for the extra space taken
* by the columnGap and rowGap that will be applied
* to all FlexgridItems (see context and paddings).
*/
marginLeft: columnGap * -1 * FLEXGRID_GAP_RATIO + "rem",
marginTop: rowGap * -1 * FLEXGRID_GAP_RATIO + "rem",
};

const flexgridClassName = clsx(
className,
FLEXGRID_CLASSNAME,
"box-border flex flex-wrap",
);

const context = { columnGap, rowGap };

return (
<div className={flexgridClassName} style={cssRoot} {...otherProps}>
<FlexgridContext.Provider value={context}>
{children}
</FlexgridContext.Provider>
</div>
);
};

export default Flexgrid;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import React from "react";
export const FlexgridContext = React.createContext({
columnGap: 0,
rowGap: 0,
});
40 changes: 40 additions & 0 deletions packages/ui-components/src/components/Flexgrid/FlexgridItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import clsx from "clsx";
import { useContext } from "react";

import {
FLEXGRID_GAP_RATIO,
FLEXGRID_ITEM_CLASSNAME,
} from "../../common/constants";
import { FlexgridContext } from "./FlexgridContext";
import type { FlexgridItemProps } from "./FlexgridTypes";
import { getBasis } from "./utilities";

/**
* FlexgridItem is a child of Flexgrid. It is used to define the width of a
* column in a Flexgrid. It can be used to span across multiple columns in a
* Flexgrid by providing a number of columns or "auto" value to the `span` prop.
*/
export const FlexgridItem = ({
children,
className,
span,
...otherProps
}: FlexgridItemProps) => {
const { columnGap, rowGap } = useContext(FlexgridContext);
const cssRoot = {
...getBasis(span),
paddingLeft: columnGap * FLEXGRID_GAP_RATIO + "rem",
paddingTop: rowGap * FLEXGRID_GAP_RATIO + "rem",
};
const flexgridItemClassName = clsx(
className,
FLEXGRID_ITEM_CLASSNAME,
"box-border",
);

return (
<div className={flexgridItemClassName} style={cssRoot} {...otherProps}>
{children}
</div>
);
};
77 changes: 77 additions & 0 deletions packages/ui-components/src/components/Flexgrid/FlexgridTypes.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
export type FlexgridProps = {
/**
* Children of the Flexgrid (FlexgridItem(s) or any other nodes).
*/
children: React.ReactNode;

/**
* Equivalent to "justify-content" in flexbox, this prop defines
* the alignment along the main axis (horizontal).
*/
alignHorizontal?:
| "flex-start"
| "center"
| "flex-end"
| "space-between"
| "space-around"
| "space-evenly";

/**
* Equivalent to "align-items" in flexbox, this prop defines
* the alignment along the cross axis (vertical).
*/
alignVertical?: "flex-start" | "center" | "flex-end" | "stretch" | "baseline";

/**
* The class name of the Flexgrid.
* It follows the [CSS class name property](https://developer.mozilla.org/en-US/docs/Web/CSS/class).
*/
className?: string;

/**
* Width of the gutters between the columns.
* See the “Customization -> Spacing” section for
* more information about this unit.
*/
columnGap?: number;

/**
* Equivalent to "flex-direction" in flexbox, this prop
* establishes the main-axis, thus defining the direction
* flex items are placed in the flex container. Flexbox is
* (aside from optional wrapping) a single-direction layout
* concept. Think of flex items as primarily laying out
* either in horizontal rows or vertical columns.
*/
direction?: "row" | "row-reverse" | "column" | "column-reverse";

/**
* The height of the Flexgrid.
* It follows the [CSS height property](https://developer.mozilla.org/en-US/docs/Web/CSS/height).
*/
height?: string;

/**
* Width of the gutters between the rows.
* See the “Customization -> Spacing” section for
* more information about this unit.
*/
rowGap?: number;

/**
* The width of the Flexgrid.
* It follows the [CSS width property](https://developer.mozilla.org/en-US/docs/Web/CSS/width).
*/
width?: string;
};

export type FlexgridItemProps = {
/** Children of the FlexgridItem. */
children?: React.ReactNode;

/** Classname to apply to the FlexgridItem. */
className?: string;

/** The item will span across the provided number of grid tracks. */
span?: number | "auto";
};
Loading

0 comments on commit f222142

Please sign in to comment.