mirror of
https://github.com/barthuijgen/factorio-sites.git
synced 2025-03-17 21:17:57 +02:00
[WIP] factorio-style-components (#34)
* Added factorio style button * Added panel inset panel, button css improvements * Fix my blueprints list and edit button * Comments related fixes * Fixing to guidelines and small style adjustments Co-authored-by: Bart <45095973+barthuijgen@users.noreply.github.com>
This commit is contained in:
parent
9ce9c4b4df
commit
7278f23f48
82
apps/blueprints/src/components/Button.tsx
Normal file
82
apps/blueprints/src/components/Button.tsx
Normal file
@ -0,0 +1,82 @@
|
||||
import React from "react";
|
||||
import styled from "@emotion/styled";
|
||||
import clsx from "clsx";
|
||||
|
||||
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
primary?: boolean;
|
||||
}
|
||||
|
||||
export const Button: React.FC<ButtonProps> = ({ primary, className, children, ...props }) => {
|
||||
return (
|
||||
<StyledButton className={clsx({ primary }, className)} {...props}>
|
||||
{children}
|
||||
</StyledButton>
|
||||
);
|
||||
};
|
||||
|
||||
const StyledButton = styled.button`
|
||||
background-color: #8e8e8e;
|
||||
padding: 10px 12px;
|
||||
text-align: left;
|
||||
color: #000;
|
||||
font-weight: 600;
|
||||
display: inline-block;
|
||||
border: none;
|
||||
line-height: 100%;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
box-shadow: inset 8px 0 4px -8px #000, inset -8px 0 4px -8px #000, inset 0 10px 2px -8px #e3e3e3,
|
||||
inset 0 10px 2px -8px #282828, inset 0 -9px 2px -8px #000, 0 0 4px 0 #000;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
height: 36px;
|
||||
outline: none;
|
||||
|
||||
&.primary {
|
||||
background-color: #5eb663;
|
||||
}
|
||||
|
||||
&.danger {
|
||||
background-color: #fe5a5a;
|
||||
box-shadow: inset 8px 0 4px -8px #000, inset -8px 0 4px -8px #000, inset 0 10px 2px -8px #fda1a1,
|
||||
inset 0 10px 2px -8px #8b0101, inset 0 -9px 2px -8px #000, 0 0 4px 0 #000;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #000;
|
||||
text-decoration: none;
|
||||
outline: 0;
|
||||
box-shadow: inset 8px 0 4px -8px #000, inset -8px 0 4px -8px #000, inset 0 9px 2px -8px #fff,
|
||||
inset 0 8px 4px -8px #000, inset 0 -8px 4px -8px #000, inset 0 -9px 2px -8px #432400,
|
||||
0 0 4px 0 #000, inset 0 0 4px 2px #f9b44b;
|
||||
background-color: #e39827;
|
||||
filter: drop-shadow(0 0 2px #f9b44b);
|
||||
}
|
||||
|
||||
&:active {
|
||||
position: relative;
|
||||
padding-top: 12px;
|
||||
padding-bottom: 8px;
|
||||
vertical-align: -2px;
|
||||
box-shadow: inset 0 10px 2px -8px #000, inset 0 9px 2px -8px #000, inset 8px 0 4px -8px #563a10,
|
||||
inset 8px 0 4px -8px #563a10, inset -8px 0 4px -8px #563a10, inset -8px 0 4px -8px #563a10,
|
||||
inset 0 9px 2px -8px #563a10, inset 0 -9px 2px -8px #563a10, inset 0 -8.5px 0 -8px #563a10,
|
||||
0 0 4px 0 #000;
|
||||
background-color: #f1be64;
|
||||
filter: none;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
cursor: default;
|
||||
vertical-align: 0;
|
||||
background-color: #3d3d3d;
|
||||
color: #818181;
|
||||
box-shadow: inset 8px 0 4px -8px #000, inset -8px 0 4px -8px #000, inset 0 8px 4px -8px #000,
|
||||
inset 0 -6px 4px -8px #818181, inset 0 -8px 4px -8px #000, 0 0 4px 0 #000;
|
||||
filter: none;
|
||||
}
|
||||
`;
|
@ -1,6 +1,7 @@
|
||||
import React, { useMemo, useState } from "react";
|
||||
import { Button, ButtonProps } from "@chakra-ui/react";
|
||||
import { MdCheck, MdClose } from "react-icons/md";
|
||||
import { IoMdClipboard } from "react-icons/io";
|
||||
import { Button, ButtonProps } from "../components/Button";
|
||||
|
||||
const SUCCESS_ICON_DURATION = 2000;
|
||||
|
||||
@ -8,44 +9,45 @@ export const CopyButton: React.FC<
|
||||
Omit<ButtonProps, "children"> & { content: string; label?: string }
|
||||
> = ({ content, label, ...props }) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [icon, setIcon] = useState<"red" | "green" | null>(null);
|
||||
const [iconType, setIconType] = useState<"success" | "error" | null>(null);
|
||||
|
||||
const iconProps = useMemo(() => {
|
||||
if (icon === "green") {
|
||||
return {
|
||||
colorScheme: "green",
|
||||
leftIcon: <MdCheck />,
|
||||
};
|
||||
} else if (icon === "red") {
|
||||
return {
|
||||
colorScheme: "red",
|
||||
leftIcon: <MdClose />,
|
||||
};
|
||||
} else {
|
||||
return { colorScheme: "green" };
|
||||
const icon = useMemo(() => {
|
||||
switch (iconType) {
|
||||
case "success":
|
||||
return <MdCheck />;
|
||||
case "error":
|
||||
return <MdClose />;
|
||||
default:
|
||||
return <IoMdClipboard />;
|
||||
}
|
||||
}, [icon]);
|
||||
}, [iconType]);
|
||||
|
||||
const handleClick = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
await navigator.clipboard.writeText(content);
|
||||
setIconType("success");
|
||||
setTimeout(() => setIconType(null), SUCCESS_ICON_DURATION);
|
||||
setLoading(false);
|
||||
} catch (err) {
|
||||
setIconType("error");
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
{...props}
|
||||
isLoading={loading}
|
||||
{...iconProps}
|
||||
onClick={() => {
|
||||
setLoading(true);
|
||||
navigator.clipboard
|
||||
.writeText(content)
|
||||
.then(() => {
|
||||
setLoading(false);
|
||||
setIcon("green");
|
||||
setTimeout(() => setIcon(null), SUCCESS_ICON_DURATION);
|
||||
})
|
||||
.catch(() => {
|
||||
setLoading(false);
|
||||
setIcon("red");
|
||||
});
|
||||
css={{
|
||||
display: "inline-flex",
|
||||
minWidth: "128px",
|
||||
}}
|
||||
disabled={loading}
|
||||
{...props}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<span className="icon" css={{ marginRight: "5px" }}>
|
||||
{icon}
|
||||
</span>
|
||||
{label || "copy"}
|
||||
</Button>
|
||||
);
|
||||
|
@ -23,17 +23,30 @@ const panelStyles = css`
|
||||
align-items: center;
|
||||
}
|
||||
`;
|
||||
const panelInsetStyles = css`
|
||||
padding: 4px;
|
||||
box-shadow: inset 0 0 3px 0 #000, 0 -2px 2px -1px #000, -2px 0 2px -2px #28221f,
|
||||
-2px 0 2px -2px #28221f, 2px 0 2px -2px #28221f, 2px 0 2px -2px #28221f, 0 3px 3px -3px #8f8c8b,
|
||||
0 2px 2px -2px #8f8c8b, 0 1px 1px -1px #8f8c8b;
|
||||
background-color: #242324;
|
||||
margin-top: 12px;
|
||||
`;
|
||||
const boxShadow = `inset 0 0 3px 0 #000, 0 -2px 2px -1px #000, -2px 0 2px -2px #28221f,
|
||||
-2px 0 2px -2px #28221f, 2px 0 2px -2px #28221f, 2px 0 2px -2px #28221f,
|
||||
0 3px 3px -3px #8f8c8b, 0 2px 2px -2px #8f8c8b, 0 1px 1px -1px #8f8c8b`;
|
||||
|
||||
export const Panel: React.FC<
|
||||
Omit<BoxProps, "title"> & { title?: ReactNode; css?: SerializedStyles }
|
||||
> = ({ children, title, css: prop_css, ...props }) => (
|
||||
Omit<BoxProps, "title" | "bottom"> & {
|
||||
title?: ReactNode;
|
||||
css?: SerializedStyles;
|
||||
bottom?: ReactNode;
|
||||
}
|
||||
> = ({ children, title, bottom, css: prop_css, ...props }) => (
|
||||
<Box css={prop_css ? [panelStyles, prop_css] : [panelStyles]} {...props}>
|
||||
{title ? <h2>{title}</h2> : null}
|
||||
{title && <h2>{title}</h2>}
|
||||
<Box color="white" height="100%" padding="12px" bg="#414040" boxShadow={boxShadow}>
|
||||
{children}
|
||||
</Box>
|
||||
{bottom && <Box css={panelInsetStyles}>{bottom}</Box>}
|
||||
</Box>
|
||||
);
|
||||
|
@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
|
||||
import { NextPage } from "next";
|
||||
import Link from "next/link";
|
||||
import BBCode from "bbcode-to-react";
|
||||
import { Button, Grid, Image, Box } from "@chakra-ui/react";
|
||||
import { Grid, Image, Box } from "@chakra-ui/react";
|
||||
import {
|
||||
getBlueprintBookById,
|
||||
getBlueprintById,
|
||||
@ -32,6 +32,7 @@ import { pageHandler } from "../../utils/page-handler";
|
||||
import styled from "@emotion/styled";
|
||||
import { css } from "@emotion/react";
|
||||
import { AiOutlineHeart, AiFillHeart } from "react-icons/ai";
|
||||
import { Button } from "../../components/Button";
|
||||
|
||||
type Selected =
|
||||
| { type: "blueprint"; data: Pick<Blueprint, "id" | "blueprint_hash" | "image_hash" | "label"> }
|
||||
@ -157,11 +158,13 @@ export const Index: NextPage<IndexProps> = ({
|
||||
<span className="text">{blueprint_page.title}</span>
|
||||
{auth && (
|
||||
<Button
|
||||
colorScheme="green"
|
||||
onClick={onClickFavorite}
|
||||
css={{ position: "absolute", right: "10px", top: "-7px", height: "35px" }}
|
||||
css={{ display: "inline-flex", float: "right", fontSize: "initial" }}
|
||||
>
|
||||
Favorite {isFavorite ? <AiFillHeart /> : <AiOutlineHeart />}
|
||||
Favorite
|
||||
<span className="icon" css={{ marginLeft: "5px" }}>
|
||||
{isFavorite ? <AiFillHeart /> : <AiOutlineHeart />}
|
||||
</span>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@ -220,21 +223,16 @@ export const Index: NextPage<IndexProps> = ({
|
||||
</tbody>
|
||||
</StyledTable>
|
||||
</Box>
|
||||
<Box css={{ marginLeft: "1rem" }}>
|
||||
{selected.data.blueprint_hash && typeof window !== "undefined" && (
|
||||
<CopyButton
|
||||
label="copy url"
|
||||
content={`${window.location.origin}/api/string/${selected.data.blueprint_hash}`}
|
||||
marginBottom="0.5rem"
|
||||
/>
|
||||
<Box primary css={{ marginLeft: "1rem" }}>
|
||||
{selectedBlueprintString && (
|
||||
<CopyButton primary label="Copy Blueprint" content={selectedBlueprintString} />
|
||||
)}
|
||||
</Box>
|
||||
<Box css={{ marginLeft: "1rem" }}>
|
||||
{selectedBlueprintString && (
|
||||
{selected.data.blueprint_hash && typeof window !== "undefined" && (
|
||||
<CopyButton
|
||||
label="copy blueprint"
|
||||
content={selectedBlueprintString}
|
||||
marginBottom="0.5rem"
|
||||
label="Copy URL"
|
||||
content={`${window.location.origin}/api/string/${selected.data.blueprint_hash}`}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
@ -319,16 +317,14 @@ export const Index: NextPage<IndexProps> = ({
|
||||
>
|
||||
<Box>
|
||||
<Button
|
||||
colorScheme="green"
|
||||
onClick={() => {
|
||||
setShowDetails(showDetails === "string" ? "none" : "string");
|
||||
}}
|
||||
>
|
||||
{showDetails === "string" ? "hide" : "show"} string
|
||||
{showDetails === "string" ? "Hide" : "Show"} string
|
||||
</Button>
|
||||
<Button
|
||||
css={{ marginLeft: "1rem" }}
|
||||
colorScheme="green"
|
||||
onClick={() => {
|
||||
setShowDetails(showDetails === "json" ? "none" : "json");
|
||||
if (!selectedData) {
|
||||
@ -341,7 +337,7 @@ export const Index: NextPage<IndexProps> = ({
|
||||
}
|
||||
}}
|
||||
>
|
||||
{showDetails === "json" ? "hide" : "show"} json
|
||||
{showDetails === "json" ? "Hide" : "Show"} json
|
||||
</Button>
|
||||
</Box>
|
||||
<Box css={{ marginTop: "1rem" }}>
|
||||
|
@ -72,7 +72,7 @@ export const Index: NextPage<IndexProps> = ({
|
||||
borderRight: "1px solid #b7b7b7",
|
||||
paddingRight: "1rem",
|
||||
marginRight: "1rem",
|
||||
width: "250px",
|
||||
width: "213px",
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
@ -167,11 +167,13 @@ export const Index: NextPage<IndexProps> = ({
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box>
|
||||
<Box css={{ display: "flex", flexWrap: "wrap", minHeight: "400px" }}>
|
||||
{blueprints.map((bp) => (
|
||||
<BlueprintLink key={bp.id} blueprint={bp} type="tile" />
|
||||
))}
|
||||
<Box css={{ display: "flex", flexDirection: "column" }}>
|
||||
<Box css={{ display: "flex", flexWrap: "wrap", minHeight: "400px", flexGrow: 1 }}>
|
||||
{blueprints.length ? (
|
||||
blueprints.map((bp) => <BlueprintLink key={bp.id} blueprint={bp} type="tile" />)
|
||||
) : (
|
||||
<p css={{ marginTop: "10px" }}>No results found</p>
|
||||
)}
|
||||
</Box>
|
||||
<Box css={{ marginTop: "15px" }}>
|
||||
<Pagination page={currentPage} totalPages={totalPages} totalItems={totalItems} />
|
||||
|
@ -7,7 +7,6 @@ import {
|
||||
FormErrorMessage,
|
||||
Input,
|
||||
SimpleGrid,
|
||||
Button,
|
||||
Box,
|
||||
Text,
|
||||
Textarea,
|
||||
@ -20,6 +19,7 @@ import { Panel } from "../../components/Panel";
|
||||
import { validateCreateBlueprintForm } from "../../utils/validate";
|
||||
import { ImageEditor } from "../../components/ImageEditor";
|
||||
import { Select } from "../../components/Select";
|
||||
import { Button } from "../../components/Button";
|
||||
import { pageHandler } from "../../utils/page-handler";
|
||||
|
||||
const FieldStyle = css`
|
||||
@ -131,7 +131,7 @@ export const UserBlueprintCreate: NextPage = () => {
|
||||
</Field>
|
||||
|
||||
<Box css={{ display: "flex", alignItems: "center" }}>
|
||||
<Button type="submit" colorScheme="green" disabled={isSubmitting}>
|
||||
<Button primary type="submit" disabled={isSubmitting}>
|
||||
Submit
|
||||
</Button>
|
||||
{status && <Text css={{ marginLeft: "1rem", color: "red" }}>{status}</Text>}
|
||||
|
@ -9,7 +9,6 @@ import {
|
||||
FormErrorMessage,
|
||||
Input,
|
||||
SimpleGrid,
|
||||
Button,
|
||||
Box,
|
||||
Text,
|
||||
Textarea,
|
||||
@ -22,12 +21,13 @@ import {
|
||||
getBlueprintStringByHash,
|
||||
} from "@factorio-sites/database";
|
||||
import { Blueprint, BlueprintBook, BlueprintPage } from "@factorio-sites/types";
|
||||
import { TAGS } from "@factorio-sites/common-utils";
|
||||
import { pageHandler } from "../../../utils/page-handler";
|
||||
import { Panel } from "../../../components/Panel";
|
||||
import { validateCreateBlueprintForm } from "../../../utils/validate";
|
||||
import { ImageEditor } from "../../../components/ImageEditor";
|
||||
import { Select } from "../../../components/Select";
|
||||
import { TAGS } from "@factorio-sites/common-utils";
|
||||
import { Button } from "../../../components/Button";
|
||||
|
||||
const FieldStyle = css`
|
||||
margin-bottom: 1rem;
|
||||
@ -156,7 +156,7 @@ export const UserBlueprint: NextPage<UserBlueprintProps> = ({ blueprintPage, sel
|
||||
</Field>
|
||||
|
||||
<Box css={{ display: "flex", alignItems: "center" }}>
|
||||
<Button type="submit" colorScheme="green" disabled={isSubmitting}>
|
||||
<Button primary type="submit" disabled={isSubmitting}>
|
||||
Submit
|
||||
</Button>
|
||||
{status && <Text css={{ marginLeft: "1rem", color: "red" }}>{status}</Text>}
|
||||
|
@ -1,12 +1,13 @@
|
||||
import React from "react";
|
||||
import { NextPage } from "next";
|
||||
import Link from "next/link";
|
||||
import { Button, SimpleGrid, Box } from "@chakra-ui/react";
|
||||
import { SimpleGrid, Box } from "@chakra-ui/react";
|
||||
import { getBlueprintPageByUserId } from "@factorio-sites/database";
|
||||
import { BlueprintPage } from "@factorio-sites/types";
|
||||
import { pageHandler } from "../../utils/page-handler";
|
||||
import { BlueprintLink } from "../../components/BlueprintLink";
|
||||
import { Panel } from "../../components/Panel";
|
||||
import { Button } from "../../components/Button";
|
||||
interface UserBlueprintsProps {
|
||||
blueprints: BlueprintPage[];
|
||||
}
|
||||
@ -27,14 +28,18 @@ export const UserBlueprints: NextPage<UserBlueprintsProps> = ({ blueprints }) =>
|
||||
>
|
||||
<Link href="/user/blueprint-create">
|
||||
<a>
|
||||
<Button colorScheme="green">create blueprint</Button>
|
||||
<Button primary>Create Blueprint</Button>
|
||||
</a>
|
||||
</Link>
|
||||
</Box>
|
||||
<Box>
|
||||
{blueprints.map((bp) => (
|
||||
<BlueprintLink key={bp.id} blueprint={bp} editLink type="row" />
|
||||
))}
|
||||
{blueprints.length !== 0 ? (
|
||||
blueprints.map((bp) => (
|
||||
<BlueprintLink key={bp.id} blueprint={bp} editLink type="row" />
|
||||
))
|
||||
) : (
|
||||
<p css={{ marginTop: "10px" }}>You don't have any blueprints yet</p>
|
||||
)}
|
||||
</Box>
|
||||
</Panel>
|
||||
</SimpleGrid>
|
||||
|
@ -1,17 +1,11 @@
|
||||
import React from "react";
|
||||
import { NextPage } from "next";
|
||||
import { useRouter } from "next/router";
|
||||
import {
|
||||
FormControl,
|
||||
FormLabel,
|
||||
FormErrorMessage,
|
||||
Input,
|
||||
SimpleGrid,
|
||||
Button,
|
||||
} from "@chakra-ui/react";
|
||||
import { FormControl, FormLabel, FormErrorMessage, Input, SimpleGrid } from "@chakra-ui/react";
|
||||
import { Formik, Field, FieldProps } from "formik";
|
||||
import { css } from "@emotion/react";
|
||||
import { Panel } from "../../components/Panel";
|
||||
import { Button } from "../../components/Button";
|
||||
import { validateUserForm } from "../../utils/validate";
|
||||
import { useAuth } from "../../providers/auth";
|
||||
import { pageHandler } from "../../utils/page-handler";
|
||||
@ -79,8 +73,13 @@ export const UserEdit: NextPage = () => {
|
||||
)}
|
||||
</Field>
|
||||
|
||||
<Button type="submit" colorScheme="green" disabled={isSubmitting}>
|
||||
Save
|
||||
<Button
|
||||
primary
|
||||
css={{ width: 80, textAlign: "center" }}
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
{isSubmitting ? "Saving..." : "Save"}
|
||||
</Button>
|
||||
</form>
|
||||
)}
|
||||
|
@ -40,6 +40,7 @@
|
||||
"@hookstate/core": "3.0.6",
|
||||
"@prisma/client": "2.18.0",
|
||||
"bbcode-to-react": "0.2.9",
|
||||
"clsx": "1.1.1",
|
||||
"bcrypt": "5.0.1",
|
||||
"cookie": "0.4.1",
|
||||
"document-register-element": "1.14.10",
|
||||
|
@ -6207,6 +6207,11 @@ clone@^2.1.1:
|
||||
resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
|
||||
integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=
|
||||
|
||||
clsx@1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188"
|
||||
integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==
|
||||
|
||||
co@^4.6.0:
|
||||
version "4.6.0"
|
||||
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
|
||||
|
Loading…
x
Reference in New Issue
Block a user