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

chore: scaffold react package #1

Merged
merged 1 commit into from
Dec 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions packages/react/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react-hooks/recommended",
],
ignorePatterns: ["dist", ".eslintrc.cjs"],
parser: "@typescript-eslint/parser",
plugins: ["react-refresh"],
rules: {
"react-refresh/only-export-components": [
"warn",
{ allowConstantExport: true },
],
},
};
21 changes: 21 additions & 0 deletions packages/react/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Logs
logs
*.log
yarn-debug.log*
yarn-error.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
1 change: 1 addition & 0 deletions packages/react/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Farcaster Connect React
12 changes: 12 additions & 0 deletions packages/react/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Farcaster Connect</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/demo.tsx"></script>
</body>
</html>
42 changes: 42 additions & 0 deletions packages/react/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"name": "connect-react",
"version": "0.0.0",
"type": "module",
"main": "./dist/connect-react.umd.cjs",
"module": "./dist/connect-react.js",
"exports": {
".": {
"import": "./dist/my-lib.js",
"require": "./dist/my-lib.umd.cjs"
}
},
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
},
"dependencies": {
"@vanilla-extract/css": "^1.14.0",
"qrcode": "^1.5.3",
"react-remove-scroll": "^2.5.7"
},
"devDependencies": {
"@types/react": "^18.2.43",
"@types/react-dom": "^18.2.17",
"@typescript-eslint/eslint-plugin": "^6.14.0",
"@typescript-eslint/parser": "^6.14.0",
"@vanilla-extract/vite-plugin": "^3.9.3",
"@vitejs/plugin-react": "^4.2.1",
"eslint": "^8.55.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.5",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"typescript": "^5.2.2",
"vite": "^4.4.11"
},
"peerDependencies": {
"react": ">= 17",
"react-dom": "^18.2.0"
}
}
37 changes: 37 additions & 0 deletions packages/react/src/components/Dialog/Dialog.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { keyframes, style } from "@vanilla-extract/css";

const slideUp = keyframes({
"0%": { transform: "translateY(100%)" },
"100%": { transform: "translateY(0)" },
});

const fadeIn = keyframes({
"0%": { opacity: 0 },
"100%": { opacity: 1 },
});

const bleed = 200;

export const overlay = style({
backdropFilter: "modalOverlay",
background: "rgba(0, 0, 0, 0.3)",
display: "flex",
justifyContent: "center",
position: "fixed",
animation: `${fadeIn} 150ms ease`,
bottom: -bleed,
left: -bleed,
padding: bleed,
right: -bleed,
top: -bleed,
transform: "translateZ(0)", // This is required for content to render under the URL bar on iOS
zIndex: 999999999,
});

export const content = style({
display: "flex",
flexDirection: "column",
position: "relative",
animation: `${slideUp} 350ms cubic-bezier(.15,1.15,0.6,1.00), ${fadeIn} 150ms ease`,
maxWidth: "100vw",
});
74 changes: 74 additions & 0 deletions packages/react/src/components/Dialog/Dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import {
MouseEventHandler,
ReactNode,
useCallback,
useEffect,
useState,
} from "react";
import { createPortal } from "react-dom";
import { RemoveScroll } from "react-remove-scroll";
import * as styles from "./Dialog.css";
import { FocusTrap } from "./FocusTrap";
import { isMobile } from "../../utils";

const stopPropagation: MouseEventHandler<unknown> = (event) =>
event.stopPropagation();

interface DialogProps {
open: boolean;
onClose: () => void;
titleId: string;
onMountAutoFocus?: (event: Event) => void;
children: ReactNode;
}

export function Dialog({ children, onClose, open, titleId }: DialogProps) {
useEffect(() => {
const handleEscape = (event: KeyboardEvent) =>
open && event.key === "Escape" && onClose();

document.addEventListener("keydown", handleEscape);

return () => document.removeEventListener("keydown", handleEscape);
}, [open, onClose]);

const [bodyScrollable, setBodyScrollable] = useState(true);
useEffect(() => {
setBodyScrollable(
getComputedStyle(window.document.body).overflow !== "hidden"
);
}, []);

const handleBackdropClick = useCallback(() => onClose(), [onClose]);

return (
<>
{open
? createPortal(
<RemoveScroll enabled={bodyScrollable}>
<div
style={{
alignItems: isMobile() ? "flex-end" : "center",
position: "fixed",
}}
aria-labelledby={titleId}
aria-modal
className={styles.overlay}
onClick={handleBackdropClick}
role="dialog"
>
<FocusTrap
className={styles.content}
onClick={stopPropagation}
role="document"
>
{children}
</FocusTrap>
</div>
</RemoveScroll>,
document.body
)
: null}
</>
);
}
66 changes: 66 additions & 0 deletions packages/react/src/components/Dialog/FocusTrap.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { useCallback, useEffect, useRef } from "react";

const moveFocusWithin = (element: HTMLElement, position: "start" | "end") => {
const focusableElements = element.querySelectorAll(
"button:not(:disabled), a[href]"
) as NodeListOf<HTMLButtonElement | HTMLAnchorElement>;

if (focusableElements.length === 0) return;

focusableElements[
position === "end" ? focusableElements.length - 1 : 0
].focus();
};

export function FocusTrap(props: JSX.IntrinsicElements["div"]) {
const contentRef = useRef<HTMLDivElement>(null);

useEffect(() => {
const previouslyActiveElement = document.activeElement;

return () => {
(previouslyActiveElement as HTMLElement).focus?.();
};
}, []);

useEffect(() => {
if (contentRef.current) {
const elementToFocus =
contentRef.current.querySelector("[data-auto-focus]");
if (elementToFocus) {
(elementToFocus as HTMLElement).focus();
} else {
contentRef.current.focus();
}
}
}, []);

return (
<>
<div
onFocus={useCallback(
() =>
contentRef.current && moveFocusWithin(contentRef.current, "end"),
[]
)}
// biome-ignore lint/a11y/noNoninteractiveTabindex: incorrect
tabIndex={0}
/>
<div
ref={contentRef}
style={{ outline: "none" }}
tabIndex={-1}
{...props}
/>
<div
onFocus={useCallback(
() =>
contentRef.current && moveFocusWithin(contentRef.current, "start"),
[]
)}
// biome-ignore lint/a11y/noNoninteractiveTabindex: incorrect
tabIndex={0}
/>
</>
);
}
1 change: 1 addition & 0 deletions packages/react/src/components/Dialog/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./Dialog";
27 changes: 27 additions & 0 deletions packages/react/src/components/SignInButton/SignInButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { button } from "../styles.css.ts";

export function SignInButton() {
const handleClick = () => {
console.log("clicked");
};

return (
<button className={button} onClick={handleClick}>
<svg width="20" height="20" viewBox="0 0 1000 1000" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M257.778 155.556H742.222V844.445H671.111V528.889H670.414C662.554 441.677 589.258 373.333 500 373.333C410.742 373.333 337.446 441.677 329.586 528.889H328.889V844.445H257.778V155.556Z"
fill="white"
/>
<path
d="M128.889 253.333L157.778 351.111H182.222V746.667C169.949 746.667 160 756.616 160 768.889V795.556H155.556C143.283 795.556 133.333 805.505 133.333 817.778V844.445H382.222V817.778C382.222 805.505 372.273 795.556 360 795.556H355.556V768.889C355.556 756.616 345.606 746.667 333.333 746.667H306.667V253.333H128.889Z"
fill="white"
/>
<path
d="M675.556 746.667C663.282 746.667 653.333 756.616 653.333 768.889V795.556H648.889C636.616 795.556 626.667 805.505 626.667 817.778V844.445H875.556V817.778C875.556 805.505 865.606 795.556 853.333 795.556H848.889V768.889C848.889 756.616 838.94 746.667 826.667 746.667V351.111H851.111L880 253.333H702.222V746.667H675.556Z"
fill="white"
/>
</svg>
<span style={{ marginLeft: 9 }}>Sign in with Farcaster</span>
</button>
);
}
1 change: 1 addition & 0 deletions packages/react/src/components/SignInButton/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./SignInButton";
1 change: 1 addition & 0 deletions packages/react/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./SignInButton";
38 changes: 38 additions & 0 deletions packages/react/src/components/styles.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { style } from "@vanilla-extract/css";

const resetBase = {
border: 0,
borderColor: "#f1f1f1",
borderStyle: "solid",
borderWidth: 0,
boxSizing: "border-box",
fontSize: "100%",
margin: 0,
padding: 0,
verticalAlign: "baseline",
WebkitFontSmoothing: "antialiased",
WebkitTapHighlightColor: "transparent",
} as const;

const reset = {
button: {
...resetBase,
appearance: "none",
background: "none",
border: "none",
cursor: "pointer",
textAlign: "left",
},
} as const;

export const button = style({
...reset.button,
paddingLeft: 12,
paddingRight: 12,
paddingTop: 9,
paddingBottom: 9,
backgroundColor: "#855DCD",
color: "white",
display: "flex",
alignItems: "center",
});
6 changes: 6 additions & 0 deletions packages/react/src/demo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { createRoot } from "react-dom/client";
import { SignInButton } from "./index";

const domNode = document.getElementById("root");
const root = createRoot(domNode!);
root.render(<SignInButton />);
1 change: 1 addition & 0 deletions packages/react/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const placeholder = "hello world";
2 changes: 2 additions & 0 deletions packages/react/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./components";
export * from "./hooks";
22 changes: 22 additions & 0 deletions packages/react/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export function isAndroid(): boolean {
return typeof navigator !== "undefined" && /android/i.test(navigator.userAgent);
}

export function isSmallIOS(): boolean {
return typeof navigator !== "undefined" && /iPhone|iPod/.test(navigator.userAgent);
}

export function isLargeIOS(): boolean {
return (
typeof navigator !== "undefined" &&
(/iPad/.test(navigator.userAgent) || (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1))
);
}

export function isIOS(): boolean {
return isSmallIOS() || isLargeIOS();
}

export function isMobile(): boolean {
return isAndroid() || isIOS();
}
1 change: 1 addition & 0 deletions packages/react/src/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="vite/client" />
Loading