Skip to content

Commit

Permalink
fix(module): fix full height (root prop); remove atts; add safari patch
Browse files Browse the repository at this point in the history
  • Loading branch information
rob-gordon committed Mar 15, 2021
1 parent a90f3d9 commit 8d1ae4b
Show file tree
Hide file tree
Showing 9 changed files with 155 additions and 40 deletions.
31 changes: 16 additions & 15 deletions slang/src/Box/Box.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,41 @@ import {
ResponsifyComponentProps,
PropsWithAs,
produceComponentClassesPropsGetter,
separateComponentProps,
} from "../utils";
import "./Box.css";
import { boxConfig, ResponsiveProps } from "./props";
import { boxConfig, propKeys, ResponsiveProps } from "./props";

export interface BaseBoxProps<
export type BaseBoxProps<
Breakpoint extends string = "tablet" | "desktop",
Colors extends string = "foreground" | "background"
> extends ResponsifyComponentProps<ResponsiveProps<Colors>, Breakpoint> {
// Stretch
stretch?: boolean;
}
> = ResponsifyComponentProps<ResponsiveProps<Colors>, Breakpoint>;

const componentKeys = [...propKeys, "at"];

const getCssClasses = produceComponentClassesPropsGetter<
BaseBoxProps,
ResponsiveProps
>(boxConfig);

export default function BoxComponent<Breakpoint extends string>(
{ as, ...props }: PropsWithAs<BaseBoxProps<Breakpoint>>,
props: PropsWithAs<BaseBoxProps<Breakpoint>>,
ref: Ref<HTMLDivElement>,
) {
const { as, ...otherProps } = props;
const Component = as || "div";
const [css, classes] = getCssClasses<Breakpoint>(props);

const [componentProps, elementProps] = separateComponentProps(
otherProps,
componentKeys,
);
const [css, classes] = getCssClasses<Breakpoint>(componentProps);

return (
<Component
{...props}
{...elementProps}
ref={ref}
className={[
props.className,
"slang-box",
...classes,
props.stretch ? "stretch" : "",
]
className={[props.className, "slang-box", ...classes]
.filter(Boolean)
.join(" ")
.trim()}
Expand Down
46 changes: 43 additions & 3 deletions slang/src/Box/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,12 @@ export interface ResponsiveProps<

// Size
h?: boolean | string;
hmin?: boolean;

/**
* Ensures the app is 100% of the screen height
* Best applied to root element
*/
root?: boolean;

// Shape
/**
Expand All @@ -63,6 +68,30 @@ export interface ResponsiveProps<
background?: Colors;
}

// Must include all Keys!!
export const propKeys = [
"p",
"py",
"px",
"pt",
"pr",
"pb",
"pl",
"template",
"content",
"items",
"self",
"gap",
"flow",
"display",
"contain",
"overflow",
"h",
"root",
"rad",
"background",
];

export const boxConfig: ComponentConfig<ResponsiveProps>[] = [
{
key: "flow",
Expand Down Expand Up @@ -126,7 +155,7 @@ export const boxConfig: ComponentConfig<ResponsiveProps>[] = [
key: "display",
defaultValue: "grid",
cssFromVariable: (v) => `display: ${v};`,
propValueToCssValue: (n: string | boolean) => {
propValueToCssValue: (n) => {
if (typeof n === "boolean") {
return n ? "grid" : "none";
}
Expand All @@ -143,14 +172,15 @@ export const boxConfig: ComponentConfig<ResponsiveProps>[] = [
defaultValue: "100%",
cssFromVariable: (v) => `height: ${v};`,
propValueToCssValue: (height) => {
if (typeof height === "undefined") return height;
if (typeof height === "boolean") {
return height ? "100%" : "auto";
}
return height ?? "auto";
},
},
{
key: "hmin",
key: "root",
defaultValue: "100%",
cssFromVariable: (v) => `min-height: ${v};`,
propValueToCssValue: (min) => {
Expand All @@ -160,6 +190,16 @@ export const boxConfig: ComponentConfig<ResponsiveProps>[] = [
}
return min ?? "none";
},
iosSafariPatch: {
cssFromVariable: (v) => `min-height: ${v};`,
propValueToCssValue: (min) => {
if (typeof min === "undefined") return min;
if (typeof min === "boolean") {
return min ? "-webkit-fill-available" : "none";
}
return min ?? "none";
},
},
},
{
key: "background",
Expand Down
19 changes: 13 additions & 6 deletions slang/src/Type/Type.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,42 @@
import React, { ReactNode, Ref } from "react";
import React, { Ref } from "react";
import "./Type.scss";
import {
produceComponentClassesPropsGetter,
PropsWithAs,
ResponsifyComponentProps,
separateComponentProps,
} from "../utils";
import { typeConfig, ResponsiveProps } from "./props";
import { typeConfig, ResponsiveProps, propKeys } from "./props";

export interface BaseTypeProps<
Breakpoint extends string = "tablet" | "desktop",
Colors extends string = "foreground" | "background"
> extends ResponsifyComponentProps<ResponsiveProps<Colors>, Breakpoint> {
size?: number;
children?: ReactNode;
}

const componentKeys = [...propKeys, "at", "size"];

const getCssClasses = produceComponentClassesPropsGetter<
BaseTypeProps,
ResponsiveProps
>(typeConfig);

export default function TypeComponent<Breakpoint extends string>(
{ as, ...props }: PropsWithAs<BaseTypeProps<Breakpoint>, "p">,
props: PropsWithAs<BaseTypeProps<Breakpoint>, "p">,
ref: Ref<HTMLParagraphElement>,
) {
const { as, ...otherProps } = props;
const Component = as || "p";
const [css, classes] = getCssClasses<Breakpoint>(props);
const [componentProps, elementProps] = separateComponentProps(
otherProps,
componentKeys,
);
const [css, classes] = getCssClasses<Breakpoint>(componentProps);

return (
<Component
{...props}
{...elementProps}
ref={ref}
className={[
props.className,
Expand Down
2 changes: 2 additions & 0 deletions slang/src/Type/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ export interface ResponsiveProps<
color?: Colors;
}

export const propKeys = ["weight", "color"];

export const typeConfig: ComponentConfig<ResponsiveProps>[] = [
{
key: "weight",
Expand Down
1 change: 1 addition & 0 deletions slang/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const SAFARI_PATCH_KEY = "safari";
25 changes: 21 additions & 4 deletions slang/src/createResponsiveCss.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,39 @@
import { SlangConfig, defaultConfig, componentClassesConfig } from "./config";
import { SAFARI_PATCH_KEY } from "./constants";

export function createResponsiveCss(userConfig?: Partial<SlangConfig>): string {
let breakpoints: string[] = [""];
let cssLines: string[] = [""];
const config = { ...defaultConfig, ...userConfig };

// Loop over each component's responsive property list
for (const [className, configList] of Object.entries(
componentClassesConfig,
)) {
// Loop over each property
for (const prop of configList) {
breakpoints = breakpoints.concat(
cssLines = cssLines.concat(
`.${className}.${prop.key.toString()} {`,
prop.cssFromVariable(`var(--${prop.key.toString()})`),
`}`,
);

// Check for safari patch and do the same
if ("iosSafariPatch" in prop) {
cssLines = cssLines.concat(
"@supports (-webkit-touch-callout: none) {",
`.${className}.${prop.key.toString()}-${SAFARI_PATCH_KEY} {`,
prop.cssFromVariable(
`var(--${prop.key.toString()}-${SAFARI_PATCH_KEY})`,
),
"}",
"}",
);
}
}

// Loop over each breakpoint
for (const [breakpoint, size] of Object.entries(config.breakpoints)) {
breakpoints = breakpoints.concat([
cssLines = cssLines.concat([
`@media(min-width: ${size}px) {`,
...configList.reduce<string[]>(
(acc, p) =>
Expand All @@ -32,5 +49,5 @@ export function createResponsiveCss(userConfig?: Partial<SlangConfig>): string {
}
}

return breakpoints.join("\n");
return cssLines.join("\n");
}
5 changes: 3 additions & 2 deletions slang/src/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ if (watch === "-w") {

function getComponentCode(userConfig: Partial<SlangConfig>) {
const config = mergeDefault(userConfig);
return `import {
return `/* Do not edit this file directly */
import {
BoxComponent,
TypeComponent,
forwardRefWithAs,
Expand All @@ -83,7 +84,7 @@ type Colors =
export type BoxProps = PropsWithAs<BaseBoxProps<Breakpoints, Colors>>;
export type TypeProps = PropsWithAs<BaseTypeProps<Breakpoints, Colors>>;
const Box = forwardRefWithAs<BoxProps, "div">(BoxComponent);
const Type = forwardRefWithAs<TypeProps, "div">(TypeComponent);
const Type = forwardRefWithAs<TypeProps, "p">(TypeComponent);
export { Box, Type };
`;
}
12 changes: 11 additions & 1 deletion slang/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,22 @@
padding: 0;
}

/* Cascade heights for Next.js and Create-React-App */
html,
body,
#__next,
#root {
height: 100%;
height: -webkit-fill-available;
/* height: -webkit-fill-available; */
}

@supports (-webkit-touch-callout: none) {
html,
body,
#__next,
#root {
height: -webkit-fill-available;
}
}

html {
Expand Down
54 changes: 45 additions & 9 deletions slang/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable @typescript-eslint/ban-types */
/* eslint-disable @typescript-eslint/no-explicit-any */
import React, { CSSProperties } from "react";
import { SAFARI_PATCH_KEY } from "./constants";

export type As<Props = any> = React.ElementType<Props>;

Expand Down Expand Up @@ -50,6 +51,19 @@ type ResponsiveComponentProperty<X> = {
* into the component, into the value that will be assed to the custom property
*/
propValueToCssValue?: (x: keyof X) => string | undefined;

/**
* Declares that a patch needs to be made to make ios safari behavior consistent
*
* Adds custom property in the form {key}-{breakpoint}-safari: value
*
* The presence of this key causes the classes and custom prop to be rendered
* This variable takes an object expecting {cssFromVariable, propValueToCssValue}
*/
iosSafariPatch?: {
cssFromVariable: (customProperty: string) => string;
propValueToCssValue?: (x: keyof X) => string | undefined;
};
};

export type ComponentConfig<X> = {
Expand Down Expand Up @@ -83,14 +97,16 @@ export function produceComponentClassesPropsGetter<ComponentProps, X>(
): [CSSProperties, string[]] {
const [props, classes] = toReduce.reduce<[CSSProperties, string[]]>(
(acc, prop) => {
const [p, c] = getIndividualChildCssProp<ComponentProps, Breakpoint, X>(
{
...prop,
node,
},
);
acc[0] = { ...acc[0], ...p };
acc[1] = acc[1].concat(c);
const [properties, classes] = getIndividualChildCssProp<
ComponentProps,
Breakpoint,
X
>({
...prop,
node,
});
acc[0] = { ...acc[0], ...properties };
acc[1] = acc[1].concat(classes);
return acc;
},
[{}, []],
Expand All @@ -111,6 +127,7 @@ function getIndividualChildCssProp<
propValueToCssValue = (z: any) => z,

node,
iosSafariPatch,
}: ComponentConfig<X> & {
node: ResponsifyComponentProps<ComponentProps, Breakpoint>;
}): [Record<string, string>, string[]] {
Expand All @@ -129,7 +146,16 @@ function getIndividualChildCssProp<
properties[`--${key}`] = last;
}

// ~~need all breakpoints~~
if (iosSafariPatch && iosSafariPatch.propValueToCssValue) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const safariValue = iosSafariPatch.propValueToCssValue(node?.[key] as any);
if (safariValue) {
classes.push(`${key.toString()}-${SAFARI_PATCH_KEY}`);
properties[`--${key.toString()}-${SAFARI_PATCH_KEY}`] = safariValue;
}
}

// no longer should need all breakpoints, because
// going to use compiled classes, to make the breakpoints
// take effect
Expand All @@ -152,3 +178,13 @@ function getIndividualChildCssProp<
}
return [properties, classes];
}

export function separateComponentProps<T>(obj: T, componentKeys: string[]) {
return Object.keys(obj).reduce(
(acc, key) =>
componentKeys.includes(key)
? [{ ...acc[0], [key]: obj[key] }, acc[1]]
: [acc[0], { ...acc[1], [key]: obj[key] }],
[{}, {}],
);
}

0 comments on commit 8d1ae4b

Please sign in to comment.