Skip to content

Commit

Permalink
dynamic layouts
Browse files Browse the repository at this point in the history
  • Loading branch information
coffee-cup committed May 12, 2021
1 parent 8d82406 commit 20cf769
Show file tree
Hide file tree
Showing 13 changed files with 293 additions and 27 deletions.
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@
"puppeteer-core": "9.1.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-feather": "^2.0.9",
"styled-components": "^5.3.0",
"twemoji": "13.0.2"
"twemoji": "13.0.2",
"use-local-storage-state": "^9.0.2",
"zustand": "^3.5.1"
},
"devDependencies": {
"@types/marked": "2.0.2",
Expand Down
9 changes: 9 additions & 0 deletions src/components/Field.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import tw from "twin.macro";

export const Field = tw.div`
flex items-center space-x-4
`;

export const Label = tw.label`
w-24
`;
8 changes: 8 additions & 0 deletions src/components/Input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import React from "react";
import tw from "twin.macro";

export const Input = tw.input`
appearance-none w-full border rounded
px-3 py-1 h-9
focus:outline-none focus:ring-2 focus:ring-accent
`;
28 changes: 28 additions & 0 deletions src/components/Layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from "react";
import { ILayout } from "../types";
import { Field, Label } from "./Field";
import { Input } from "./Input";
import { Select } from "./Select";
import "twin.macro";

export interface Props {
layout: ILayout;
}

export const Layout: React.FC<Props> = ({ layout, ...props }) => {
return (
<div className={`layout-${layout.name}`} tw="space-y-4">
{layout.properties.map(p => (
<Field key={p.name}>
<Label>{p.name} </Label>

{p.type === "text" ? (
<Input placeholder={`Value for ${p.name}`} />
) : p.type === "select" ? (
<Select options={p.options.map(value => ({ value }))} />
) : null}
</Field>
))}
</div>
);
};
74 changes: 74 additions & 0 deletions src/components/Select.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import classnames from "classnames";
import React from "react";
import tw from "twin.macro";
import { ChevronDown } from "react-feather";

export interface Option {
value: string;
text?: string;
disabled?: boolean;
}

export interface Props {
options: Option[];
value?: string;
width?: string;
name?: string;
error?: boolean;
disabled?: boolean;
autoFocus?: boolean;
onChange?: (value: string) => void;
}

export const Select = React.forwardRef<HTMLSelectElement, Props>(
(
{ value, options, name, error, disabled, autoFocus, onChange, ...rest },
forwardedRef,
) => {
return (
<div
css={[
tw`relative flex items-center w-full`,
disabled && tw`opacity-60`,
]}
>
<select
ref={forwardedRef}
value={value}
disabled={disabled}
autoFocus={autoFocus}
name={name}
className={classnames("select", { [`select-${name}`]: name != null })}
css={[
tw`bg-transparent w-full h-9 appearance-none border rounded`,
tw`py-1 pl-3 pr-8`,
!disabled && tw`cursor-pointer hover:border-gray-400`,
error
? tw`border-red-500 hover:border-red-500`
: tw`border-gray-200`,
tw`focus:outline-none focus-visible:ring-2 focus-visible:ring-accent`,
]}
{...rest}
onChange={e => {
if (onChange != null) {
onChange(e.target.value);
}
}}
>
{options.map(opt => (
<option
key={opt.value}
value={opt.value}
disabled={opt.disabled}
tw="text-fg"
>
{opt.text ?? opt.value}
</option>
))}
</select>

<ChevronDown tw="h-full absolute top-0 right-2 pointer-events-none" />
</div>
);
},
);
7 changes: 7 additions & 0 deletions src/hooks/useIsMounted.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { useEffect, useState } from "react";

export const useIsMounted = (): boolean => {
const [isMounted, setIsMounted] = useState(false);
useEffect(() => setIsMounted(true), []);
return isMounted;
};
25 changes: 25 additions & 0 deletions src/layouts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { ILayout } from "./types";

export const simpleLayout: ILayout = {
name: "Simple",
properties: [
{ name: "Test", type: "text" },
{
name: "Boop",
description: "This is a test",
type: "select",
options: ["one", "two", "three"],
},
],
};

export const starterLayout: ILayout = {
name: "Starter",
properties: [
{ name: "Name", type: "text" },
{ name: "Icon", type: "text" },
{ name: "URL", type: "text" },
],
};

export const layouts: ILayout[] = [simpleLayout, starterLayout];
14 changes: 0 additions & 14 deletions src/pages/api/_lib/template.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { readFileSync } from "fs";
import marked from "marked";
import { sanitizeHtml } from "./sanitizer";
import { ParsedRequest } from "./types";
Expand All @@ -7,19 +6,6 @@ const twOptions = { folder: "svg", ext: ".svg" };
const emojify = (text: string) => twemoji.parse(text, twOptions);

function getCss(theme: string, fontSize: string) {
console.log("DIRNAME", __dirname);

// const rglr = readFileSync(`../_fonts/Inter-Regular.woff2`).toString("base64");

// console.log("REGULAR", rglr);

// const bold = readFileSync(`${__dirname}/../_fonts/Inter-Bold.woff2`).toString(
// "base64",
// );
// const mono = readFileSync(`${__dirname}/../_fonts/Vera-Mono.woff2`).toString(
// "base64",
// );

let background = "white";
let foreground = "black";
let radial = "lightgray";
Expand Down
77 changes: 66 additions & 11 deletions src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,91 @@
import { NextPage } from "next";
import React from "react";
import React, { useMemo, useState } from "react";
import "twin.macro";
import { Field, Label } from "../components/Field";
import { Input } from "../components/Input";
import { Layout } from "../components/Layout";
import { Select } from "../components/Select";
import { useIsMounted } from "../hooks/useIsMounted";
import { layouts } from "../layouts";
import { useStore } from "../store";
import { FileType, Theme } from "../types";

const Home: NextPage = () => {
const isMounted = useIsMounted();

return (
<main tw="px-6 max-w-6xl w-full mx-auto">
<header tw="text-center mt-20 mb-12 space-y-4">
<h1 tw="text-4xl font-bold">Railway OG Image Generator</h1>
</header>

<section tw="grid gap-4 grid-cols-1 md:grid-cols-3">
<Config />
<OGImage />
</section>
{/* We pull the state from local storage so need the app to be loaded in the browser */}
{isMounted && (
<section tw="grid gap-8 grid-cols-1 md:grid-cols-3">
<Config />
<OGImage />
</section>
)}
</main>
);
};

export default Home;

export const Config: React.FC = () => {
return <div tw="bg-pink-100">CONFIG</div>;
const [{ theme, fileType, layout: layoutName }, setStore] = useStore();

const layout = useMemo(
() => layouts.find(l => l.name === layoutName),
[layoutName],
);

return (
<div tw="space-y-4">
<Field>
<Label>Theme</Label>
<Select
value={theme}
options={[{ value: "light" }, { value: "dark" }]}
onChange={theme => setStore(s => ({ ...s, theme: theme as Theme }))}
/>
</Field>

<Field>
<Label>File type</Label>
<Select
value={fileType}
options={[{ value: "png" }, { value: "jpeg" }]}
onChange={fileType =>
setStore(s => ({ ...s, fileType: fileType as FileType }))
}
/>
</Field>

<Field>
<Label>Layout</Label>
<Select
value={layoutName}
options={layouts.map(l => ({ value: l.name }))}
onChange={layout => setStore(s => ({ ...s, layout }))}
/>
</Field>

<hr />

{layout == null ? (
<p>Layout {layoutName} does not exist</p>
) : (
<Layout layout={layout} key={layout.name} />
)}
</div>
);
};

export const OGImage: React.FC = () => {
return (
<div className="image-wrapper" tw="col-span-2">
<img
tw="shadow-lg w-full"
src="https://og-image.vercel.app/**Hello**%20World.png?theme=light&md=1&fontSize=100px&images=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Ffront%2Fassets%2Fdesign%2Fvercel-triangle-black.svg"
alt=""
/>
<img tw="shadow-lg w-full" src="/api/boooop" alt="" />
</div>
);
};
20 changes: 20 additions & 0 deletions src/store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { createLocalStorageStateHook } from "use-local-storage-state";
import { simpleLayout } from "./layouts";
import { FileType, Theme } from "./types";

export interface StoreState {
theme: Theme;
fileType: FileType;
layout: string;
}

const defaultStoreState: StoreState = {
theme: "light",
fileType: "png",
layout: simpleLayout.name,
};

export const useStore = createLocalStorageStateHook<StoreState>(
"state",
defaultStoreState,
);
32 changes: 32 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
export type Theme = "light" | "dark";
export type FileType = "png" | "jpeg";

export interface IConfig {
theme: Theme;
fileType: FileType;
}

export interface ILayout {
name: string;
properties: LayoutProperty[];
}

export type LayoutProperty = BaseLayoutProperty &
(
| {
type: "text";
}
| {
type: "array";
subProperty: LayoutProperty;
}
| {
type: "select";
options: string[];
}
);

export interface BaseLayoutProperty {
name: string;
description?: string;
}
2 changes: 2 additions & 0 deletions tailwind.config.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
const colors = require("tailwindcss/colors");

const customColors = {
transparent: "transparent",
bg: "#f8f9fa",
fg: "#212529",
accent: "#C049FF",
};

module.exports = {
Expand Down
Loading

0 comments on commit 20cf769

Please sign in to comment.