Skip to content
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

Fix: skrive om flex komponent #4258

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
3 changes: 2 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ jest/
tsconfig.json
tsconfig-for-declarations.json
pnpm-lock.yaml
packages/jokul/dev-server.js
packages/jokul/dev-server.js
packages/jokul/src/components/flex/styles/flex.scss
211 changes: 153 additions & 58 deletions packages/jokul/src/components/flex/Flex.tsx
Original file line number Diff line number Diff line change
@@ -1,70 +1,165 @@
import React, { type CSSProperties } from "react";
import tokens from "../../core/tokens.js";
import { AsChildProps } from "../../utilities/polymorphism/as-child.js";
import clsx from "clsx";
import React, { forwardRef } from "react";
import {
PolymorphicPropsWithRef,
Expand,
PolymorphicComponentPropWithRef,
PolymorphicRef,
} from "../../utilities/polymorphism/polymorphism.js";
import { SlotComponent } from "../../utilities/polymorphism/SlotComponent.js";

export type FlexDirection = "row" | "row-reverse" | "column" | "column-reverse";
export type GapValue = Exclude<keyof (typeof tokens)["spacing"], 0>;
type Size = 1 | 2 | 3 | 4 | 6 | 4.8 | 8.4 | 2.1 | 10.2 | 3.9 | 9.3 | 5.7 | 7.5;
type Center = "md" | "lg" | "xl" | "xxl" | boolean;
type Layout = Expand<"auto" | Size | `${Size}`[][number] | "2.10">;
type Gap =
| "none"
| "xs"
| "sm"
| "md"
| "lg"
| "xl"
| "xxl"
| "xxl xl"
| "xxl lg"
| "xxl md"
| "xxl sm"
| "xxl xs"
| "xl none"
| "xl xxl"
| "xl lg"
| "xl md"
| "xl sm"
| "xl xs"
| "xl none"
| "lg xxl"
| "lg xl"
| "lg md"
| "lg sm"
| "lg xs"
| "lg none"
| "md xxl"
| "md xl"
| "md lg"
| "md sm"
| "md xs"
| "md none"
| "sm xxl"
| "sm xl"
| "sm lg"
| "sm md"
| "sm xs"
| "sm none"
| "xs xxl"
| "xs xl"
| "xs lg"
| "xs md"
| "xs sm"
| "xs none"
| "none xxl"
| "none xl"
| "none lg"
| "none md"
| "none sm"
| "none xs";

export type FlexProps<ElementType extends React.ElementType> =
PolymorphicPropsWithRef<
ElementType,
{
direction?: FlexDirection;
wrap?: boolean;
gap?: GapValue;
colGap?: GapValue;
rowGap?: GapValue;
} & Pick<
CSSProperties,
"alignContent" | "alignItems" | "justifyContent" | "justifyItems"
>
>;
type FlexBaseProps = {
align?: "normal" | "start" | "center" | "end" | "baseline" | "stretch";
alignContent?:
| "normal"
| "start"
| "center"
| "end"
| "stretch"
| "baseline"
| "space-between"
| "space-around"
| "space-evenly";
center?: Center;
direction?: "row" | "column" | "row-reverse" | "column-reverse";
fill?: boolean;
gap?: Gap | { xs?: Gap; sm?: Gap; md?: Gap; lg?: Gap; xl?: Gap; xxl?: Gap };
inline?: boolean;
text?: "left" | "right" | "center";
justify?:
| "normal"
| "start"
| "center"
| "end"
| "space-between"
| "space-around"
| "space-evenly";
layout?:
| Layout
| {
xs?: Layout;
sm?: Layout;
md?: Layout;
lg?: Layout;
xl?: Layout;
xxl?: Layout;
};
wrap?: "wrap" | "nowrap" | "reverse";
};

export type FlexComponent = <ElementType extends React.ElementType = "div">(
props: FlexProps<ElementType> & AsChildProps,
) => React.ReactElement | null;
export type FlexProps<As extends React.ElementType = "div"> =
PolymorphicComponentPropWithRef<As, FlexBaseProps>;

export const Flex = React.forwardRef(function Flex<
ElementType extends React.ElementType = "div",
>(props: FlexProps<ElementType>, ref?: PolymorphicRef<ElementType>) {
const {
asChild,
as = "div",
alignContent,
alignItems,
children,
colGap,
direction,
gap,
justifyContent,
justifyItems,
rowGap,
wrap = false,
...rest
} = props;
const Component = asChild ? SlotComponent : as;
type FlexComponent = <As extends React.ElementType = "div">(
props: FlexProps<As>,
) => JSX.Element;

const flexStyle: CSSProperties = {
display: "flex",
flexDirection: direction,
export const Flex: FlexComponent = forwardRef(function Flex<
As extends React.ElementType = "div",
>(
{
align,
alignContent,
alignItems,
justifyContent,
justifyItems,
...(wrap ? { flexWrap: "wrap" } : {}),
...(gap ? { gap: tokens.spacing[gap] } : {}),
...(colGap ? { columnGap: tokens.spacing[colGap] } : {}),
...(rowGap ? { rowGap: tokens.spacing[rowGap] } : {}),
};
as,
center = false,
className,
direction = "row",
fill,
gap = "md",
inline,
justify,
layout = {},
text,
wrap = "wrap",
...rest
}: FlexProps<As>,
ref?: PolymorphicRef<As>,
) {
const Tag = as || "div";
const gaps = toObj(gap).flatMap(([breakpoint, gap]) => {
const [row, col = row] = gap.trim().split(" ");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Her smeller det i eksempelet til Expander (det er derfor visuell regresjonstest feiler) fordi gap er sent inn som et tall og tall har ingen trim() funksjon.

Har også kanskje lyst til å foreslå at toObj får et bedre navn for den ser ikke ut til å lage noe objekt men et array. Det var litt vanskelig å resonere seg fram til hvorfor det krasjer.

return [`${breakpoint}-row-gap-${row}`, `${breakpoint}-col-gap-${col}`];
});
const layouts = toObj(layout).map(
([breakpoint, layout]) =>
`${breakpoint}-${Number(`${layout}`.replace("auto", "0"))}`, // Convert to number to convert 2.10 to 2.1 and false to 0
);

return (
<Component ref={ref} {...rest} style={{ ...flexStyle, ...rest.style }}>
{children}
</Component>
<Tag
{...rest}
className={clsx(
"jkl-flex",
!align || `align-${align}`,
!alignContent || `align-content-${alignContent}`,
!center || `center-${center === true ? "xxl" : center}`,
!fill || "fill",
!inline || "inline",
!justify || `justify-${justify}`,
!text || `text-${text}`,
!wrap || `wrap-${wrap}`,
"flex",
"direction",
...gaps,
...layouts,
className,
)}
ref={ref}
/>
);
}) as FlexComponent;
}) as FlexComponent; // Needed to tell Typescript this does not return ReactNode but acutally JSX.Element

const toObj = (value: string | number | object) =>
Object.entries(typeof value === "object" ? value : { xs: value });
Loading