diff --git a/package.json b/package.json index 3dcf799..c58aec3 100644 --- a/package.json +++ b/package.json @@ -21,9 +21,7 @@ "repository": "github:phosphor-icons/phosphor-home", "private": true, "dependencies": { - "@types/braintree-web": "^3.75.3", "@types/braintree-web-drop-in": "^1.22.3", - "braintree-web": "^3.78.2", "braintree-web-drop-in": "^1.30.1", "file-saver": "^2.0.2", "framer-motion": "^3.10.0", diff --git a/src/components/App/App.css b/src/components/App/App.css index d076926..3db5216 100644 --- a/src/components/App/App.css +++ b/src/components/App/App.css @@ -144,7 +144,7 @@ button.text-button svg { display: flex; flex-flow: row wrap; justify-content: space-around; - row-gap: 16px; + row-gap: 8px; } .radio-button input[type="radio"] { diff --git a/src/components/App/App.tsx b/src/components/App/App.tsx index d426e3e..e144c9b 100644 --- a/src/components/App/App.tsx +++ b/src/components/App/App.tsx @@ -1,14 +1,16 @@ import React, { Suspense } from "react"; +import Header from "components/Header"; +import Modal from "components/Modal"; +import Toolbar from "components/Toolbar"; +import IconGrid from "components/IconGrid"; +import Footer from "components/Footer"; +import ErrorBoundary from "components/ErrorBoundary"; +import Notice from "components/Notice"; + +import useIconParameters from "hooks/useIconParameters"; + import "./App.css"; -import Header from "../Header/Header"; -import Modal from "../Modal/Modal"; -import Toolbar from "../Toolbar/Toolbar"; -import IconGrid from "../IconGrid/IconGrid"; -import Footer from "../Footer/Footer"; -import ErrorBoundary from "../ErrorBoundary/ErrorBoundary"; -import Notice from "../Notice/Notice"; -import useIconParameters from "../../hooks/useIconParameters"; const errorFallback = ; const paymentFallback = ; diff --git a/src/components/App/index.ts b/src/components/App/index.ts new file mode 100644 index 0000000..9ba5deb --- /dev/null +++ b/src/components/App/index.ts @@ -0,0 +1,2 @@ +import App from "./App"; +export default App; diff --git a/src/components/ColorInput/ColorInput.tsx b/src/components/ColorInput/ColorInput.tsx index b98df2a..71061db 100644 --- a/src/components/ColorInput/ColorInput.tsx +++ b/src/components/ColorInput/ColorInput.tsx @@ -1,9 +1,10 @@ import React, { useCallback } from "react"; import { useRecoilState, useRecoilValue } from "recoil"; -import { iconColorAtom } from "../../state/atoms"; -import { isDarkThemeSelector } from "../../state/selectors"; -import useThrottled from "../../hooks/useThrottled"; +import { iconColorAtom } from "state/atoms"; +import { isDarkThemeSelector } from "state/selectors"; +import useThrottled from "hooks/useThrottled"; + import "./ColorInput.css"; type ColorInputProps = {}; diff --git a/src/components/ColorInput/index.ts b/src/components/ColorInput/index.ts new file mode 100644 index 0000000..9c761d8 --- /dev/null +++ b/src/components/ColorInput/index.ts @@ -0,0 +1,2 @@ +import ColorInput from "./ColorInput"; +export default ColorInput; diff --git a/src/components/ErrorBoundary/index.ts b/src/components/ErrorBoundary/index.ts new file mode 100644 index 0000000..bbeca7b --- /dev/null +++ b/src/components/ErrorBoundary/index.ts @@ -0,0 +1,2 @@ +import ErrorBoundary from "./ErrorBoundary"; +export default ErrorBoundary; diff --git a/src/components/Footer/Footer.tsx b/src/components/Footer/Footer.tsx index ff15879..6d32a47 100644 --- a/src/components/Footer/Footer.tsx +++ b/src/components/Footer/Footer.tsx @@ -1,10 +1,12 @@ import React from "react"; import { Coffee, Heart } from "phosphor-react"; -import uArrowUpLeft from "../../assets/u-arrow-up-left.svg"; -import markerGreen from "../../assets/marker-green.svg"; -import postIt from "../../assets/footer-mobile.svg"; -import Links from "../Links/Links"; +import uArrowUpLeft from "assets/u-arrow-up-left.svg"; +import markerGreen from "assets/marker-green.svg"; +import postIt from "assets/footer-mobile.svg"; + +import Links from "components/Links"; + import "./Footer.css"; type FooterProps = {}; @@ -34,8 +36,8 @@ const Footer: React.FC = () => { a little quirky, too.

- We're thankful for the tools we've benefited from and - this is our contribution towards a collaborative community. + We're thankful for the tools we've benefited from and this is our + contribution towards a collaborative community.

Phosphor is free and open-source, licensed under{" "} diff --git a/src/components/Footer/index.ts b/src/components/Footer/index.ts new file mode 100644 index 0000000..ccb5e78 --- /dev/null +++ b/src/components/Footer/index.ts @@ -0,0 +1,2 @@ +import Footer from "./Footer"; +export default Footer; diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx index fd2cce6..28fa7dd 100644 --- a/src/components/Header/Header.tsx +++ b/src/components/Header/Header.tsx @@ -1,22 +1,24 @@ import React from "react"; import { ArrowCircleUpRight, ArrowCircleDown } from "phosphor-react"; -import markerPurple from "../../assets/marker-purple.svg"; -import paperclips from "../../assets/paperclips-header-mobile.svg"; -import paperclipsThree from "../../assets/paperclips-header.svg"; -import tablet from "../../assets/tablet.svg"; -import tabletSpec from "../../assets/tablet-spec.svg"; -import billiardBall from "../../assets/billiard-ball.svg"; -import billiardBallSpec from "../../assets/billiard-ball-spec.svg"; -import warning from "../../assets/warning.svg"; -import warningSpec from "../../assets/warning-spec.svg"; -import cuttingMat from "../../assets/cutting-mat.svg"; -import cuttingMatSpec from "../../assets/cutting-mat-spec.svg"; -import receipt from "../../assets/receipt.svg"; -import receiptSpec from "../../assets/receipt-spec.svg"; -import calculator from "../../assets/calculator.svg"; -import calculatorSpec from "../../assets/calculator-spec.svg"; -import Links from "../Links/Links"; +import markerPurple from "assets/marker-purple.svg"; +import paperclips from "assets/paperclips-header-mobile.svg"; +import paperclipsThree from "assets/paperclips-header.svg"; +import tablet from "assets/tablet.svg"; +import tabletSpec from "assets/tablet-spec.svg"; +import billiardBall from "assets/billiard-ball.svg"; +import billiardBallSpec from "assets/billiard-ball-spec.svg"; +import warning from "assets/warning.svg"; +import warningSpec from "assets/warning-spec.svg"; +import cuttingMat from "assets/cutting-mat.svg"; +import cuttingMatSpec from "assets/cutting-mat-spec.svg"; +import receipt from "assets/receipt.svg"; +import receiptSpec from "assets/receipt-spec.svg"; +import calculator from "assets/calculator.svg"; +import calculatorSpec from "assets/calculator-spec.svg"; + +import Links from "components/Links"; + import "./Header.css"; type HeaderProps = {}; diff --git a/src/components/Header/index.ts b/src/components/Header/index.ts new file mode 100644 index 0000000..aa3e4e8 --- /dev/null +++ b/src/components/Header/index.ts @@ -0,0 +1,2 @@ +import Header from "./Header"; +export default Header; diff --git a/src/components/IconGrid/DetailsPanel.tsx b/src/components/IconGrid/DetailsPanel.tsx index 766fc0c..cc94217 100644 --- a/src/components/IconGrid/DetailsPanel.tsx +++ b/src/components/IconGrid/DetailsPanel.tsx @@ -3,18 +3,19 @@ import { useRecoilValue, useSetRecoilState } from "recoil"; import { motion } from "framer-motion"; import { Svg2Png } from "svg2png-converter"; import { saveAs } from "file-saver"; -import { Copy, X, CheckCircle, Download } from "phosphor-react"; import ReactGA from "react-ga"; +import { Copy, X, CheckCircle, Download } from "phosphor-react"; import { iconWeightAtom, iconSizeAtom, iconColorAtom, iconPreviewOpenAtom, -} from "../../state/atoms"; -import useTransientState from "../../hooks/useTransientState"; +} from "state/atoms"; +import { IconEntry } from "lib"; +import useTransientState from "hooks/useTransientState"; + import TagCloud from "./TagCloud"; -import { IconEntry } from "../../lib"; const panelVariants = { open: { diff --git a/src/components/IconGrid/IconGrid.tsx b/src/components/IconGrid/IconGrid.tsx index efa94fd..75bb599 100644 --- a/src/components/IconGrid/IconGrid.tsx +++ b/src/components/IconGrid/IconGrid.tsx @@ -3,18 +3,27 @@ import { useRecoilValue } from "recoil"; import { motion, useAnimation } from "framer-motion"; import { IconContext } from "phosphor-react"; -import { iconWeightAtom, iconSizeAtom, iconColorAtom } from "../../state/atoms"; +import { iconWeightAtom, iconSizeAtom, iconColorAtom } from "state/atoms"; import { filteredQueryResultsSelector, isDarkThemeSelector, -} from "../../state/selectors"; -import useGridSpans from "../../hooks/useGridSpans"; +} from "state/selectors"; +import useGridSpans from "hooks/useGridSpans"; + import IconGridItem from "./IconGridItem"; import TagCloud from "./TagCloud"; -import Notice from "../Notice/Notice"; +import Notice from "components/Notice"; + import "./IconGrid.css"; -const defaultSearchTags = ["*new*", "communication", "editor", "emoji", "maps", "weather"]; +const defaultSearchTags = [ + "*new*", + "communication", + "editor", + "emoji", + "maps", + "weather", +]; type IconGridProps = {}; diff --git a/src/components/IconGrid/IconGridItem.tsx b/src/components/IconGrid/IconGridItem.tsx index ecd2ee4..7c78777 100644 --- a/src/components/IconGrid/IconGridItem.tsx +++ b/src/components/IconGrid/IconGridItem.tsx @@ -7,9 +7,9 @@ import React, { import { useRecoilState } from "recoil"; import { motion, AnimatePresence } from "framer-motion"; -import { iconPreviewOpenAtom } from "../../state/atoms"; +import { IconEntry } from "lib"; +import { iconPreviewOpenAtom } from "state/atoms"; import DetailsPanel from "./DetailsPanel"; -import { IconEntry } from "../../lib"; interface IconGridItemProps { index: number; @@ -86,9 +86,9 @@ const IconGridItem: React.FC = (props) => {

{name}

- - {isOpen && } - + + {isOpen && } + ); }; diff --git a/src/components/IconGrid/TagCloud.tsx b/src/components/IconGrid/TagCloud.tsx index 5d376b4..3e32a83 100644 --- a/src/components/IconGrid/TagCloud.tsx +++ b/src/components/IconGrid/TagCloud.tsx @@ -1,7 +1,8 @@ import React, { useCallback } from "react"; import { useSetRecoilState } from "recoil"; -import { searchQueryAtom } from "../../state/atoms"; +import { searchQueryAtom } from "state/atoms"; + import "./TagCloud.css"; interface TagCloudProps { diff --git a/src/components/IconGrid/index.ts b/src/components/IconGrid/index.ts new file mode 100644 index 0000000..272fd4a --- /dev/null +++ b/src/components/IconGrid/index.ts @@ -0,0 +1,2 @@ +import IconGrid from "./IconGrid"; +export default IconGrid; diff --git a/src/components/Links/Links.css b/src/components/Links/Links.css index fd241ef..b6893a7 100644 --- a/src/components/Links/Links.css +++ b/src/components/Links/Links.css @@ -20,13 +20,14 @@ margin-right: 12px; } -a.nav-link { +.nav-link { text-decoration: none; position: relative; color: black; + cursor: pointer; } -a.nav-link:after { +.nav-link:after { content: ""; position: absolute; bottom: -2px; @@ -36,6 +37,6 @@ a.nav-link:after { transition: 0.2s; } -a.nav-link:hover:after { +.nav-link:hover:after { width: 100%; -} \ No newline at end of file +} diff --git a/src/components/Links/Links.tsx b/src/components/Links/Links.tsx index 0dc800f..e91171c 100644 --- a/src/components/Links/Links.tsx +++ b/src/components/Links/Links.tsx @@ -3,17 +3,19 @@ import { OutboundLink } from "react-ga"; import { useSetRecoilState } from "recoil"; import { ArrowElbowDownRight } from "phosphor-react"; -import { iconCount } from "../../lib/icons"; -import { modalOpenAtom } from "../../state/atoms"; +import { iconCount } from "lib/icons"; +import { modalSelector } from "state/selectors"; +import DonationModal from "components/Modal/DonationModal"; import "./Links.css"; interface LinksProps {} const Links: React.FC = () => { - const setModalOpen = useSetRecoilState(modalOpenAtom); + const openModal = useSetRecoilState(modalSelector); + const openDonationModal = () => openModal({ type: DonationModal }); - const openDonationModal = () => setTimeout(() => setModalOpen(true), 1000); + const delayedOpenDonationModal = () => setTimeout(openDonationModal, 2000); return (
@@ -25,7 +27,7 @@ const Links: React.FC = () => { eventLabel="Download all" download type="application/zip" - onClick={openDonationModal} + onClick={delayedOpenDonationModal} > Download all ({iconCount}) @@ -59,27 +61,11 @@ const Links: React.FC = () => { Request an icon
- {/*
- - - - Donate on PayPal - - {" / "} - - Patreon - - -
*/}
- - Donate on PayPal - + + Donate +
diff --git a/src/components/Links/index.ts b/src/components/Links/index.ts new file mode 100644 index 0000000..a49c54e --- /dev/null +++ b/src/components/Links/index.ts @@ -0,0 +1,2 @@ +import Links from "./Links"; +export default Links; diff --git a/src/components/Modal/DonationModal.tsx b/src/components/Modal/DonationModal/DonationModal.tsx similarity index 51% rename from src/components/Modal/DonationModal.tsx rename to src/components/Modal/DonationModal/DonationModal.tsx index f5eb029..1043483 100644 --- a/src/components/Modal/DonationModal.tsx +++ b/src/components/Modal/DonationModal/DonationModal.tsx @@ -1,23 +1,40 @@ import React, { useState } from "react"; -import { useSetRecoilState } from "recoil"; -import { X } from "phosphor-react"; -import { modalOpenAtom } from "../../state/atoms"; +import { ModalInstance } from "../Modal"; import DonationStepMethod from "./DonationStepMethod"; import DonationStepDropin from "./DonationStepDropin"; import DonationStepThanks from "./DonationStepThanks"; const routes = [DonationStepMethod, DonationStepDropin, DonationStepThanks]; +export enum DonationType { + FIVE_DOLLARS = 5, + TEN_DOLLARS = 10, + TWENTY_DOLLARS = 20, + FIFTY_DOLLARS = 50, + ONE_HUNDRED_DOLLARS = 100, + CUSTOM = -1, +} + +interface RouteProps { + donationType: DonationType | undefined; + setDonationType: React.Dispatch< + React.SetStateAction + >; + donationAmount: number; + setDonationAmount: React.Dispatch>; +} + export interface StepProps { previousStep: () => void; nextStep: () => void; close: () => void; + routeProps: RouteProps; } interface StepperProps { routes: Array>; - routeProps: any; + routeProps: RouteProps; close: () => void; } @@ -42,26 +59,32 @@ const Stepper: React.FC = ({ routes, routeProps, close }) => { previousStep={previousStep} nextStep={nextStep} close={close} - {...routeProps} + routeProps={routeProps} /> ); }; -const DonationModal: React.FC<{}> = () => { - const setModalOpen = useSetRecoilState(modalOpenAtom); - const close = () => setModalOpen(false); +const DonationModal = ({ close }: ModalInstance): JSX.Element => { + const [donationType, setDonationType] = useState(); + const [donationAmount, setDonationAmount] = useState(0); return ( -
- - -
+ <> +
+

Donate

+
+ + ); }; diff --git a/src/components/Modal/DonationStepDropin.tsx b/src/components/Modal/DonationModal/DonationStepDropin.tsx similarity index 88% rename from src/components/Modal/DonationStepDropin.tsx rename to src/components/Modal/DonationModal/DonationStepDropin.tsx index ca13cd4..4c5b361 100644 --- a/src/components/Modal/DonationStepDropin.tsx +++ b/src/components/Modal/DonationModal/DonationStepDropin.tsx @@ -17,10 +17,12 @@ const BT_PAYMENT_FIELDS = { }, }; -const PaymentModal: React.FC = ({ previousStep }) => { +const PaymentModal: React.FC = ({ previousStep, routeProps }) => { const instance = useRef(); const [isValid, setIsValid] = useState(false); + const { donationAmount } = routeProps; + const submit = async () => { if (!instance.current) return; const payload = await instance.current.requestPaymentMethod(); @@ -43,13 +45,13 @@ const PaymentModal: React.FC = ({ previousStep }) => { }, paypal: { flow: "checkout", - amount: "10.00", + amount: donationAmount.toFixed(2).toString(), currency: "USD", commit: true, }, paypalCredit: { flow: "checkout", - amount: "10.00", + amount: donationAmount.toFixed(2).toString(), currency: "USD", commit: true, }, @@ -65,7 +67,7 @@ const PaymentModal: React.FC = ({ previousStep }) => { }; initializePayments(); - }, []); + }, [donationAmount]); return ( <> diff --git a/src/components/Modal/DonationStepMethod.tsx b/src/components/Modal/DonationModal/DonationStepMethod.tsx similarity index 68% rename from src/components/Modal/DonationStepMethod.tsx rename to src/components/Modal/DonationModal/DonationStepMethod.tsx index 236119e..a17b9ed 100644 --- a/src/components/Modal/DonationStepMethod.tsx +++ b/src/components/Modal/DonationModal/DonationStepMethod.tsx @@ -1,27 +1,19 @@ -import React, { useState } from "react"; +import React from "react"; -import { StepProps } from "./DonationModal"; - -enum DonationAmount { - FIVE_DOLLARS = 5, - TEN_DOLLARS = 10, - TWENTY_DOLLARS = 20, - FIFTY_DOLLARS = 50, - ONE_HUNDRED_DOLLARS = 100, - CUSTOM = -1, -} +import { StepProps, DonationType } from "./DonationModal"; const DonationStepMethod: React.FC = ({ previousStep, nextStep, close, + routeProps, }) => { - const [donationType, setDonationType] = useState(); - const [donationAmount, setDonationAmount] = useState(0); + const { donationType, donationAmount, setDonationType, setDonationAmount } = + routeProps; const onDonationChange = (e: React.ChangeEvent) => { - setDonationType(+e.target.value as DonationAmount); - if (!(+e.target.value === DonationAmount.CUSTOM)) + setDonationType(+e.target.value as DonationType); + if (!(+e.target.value === DonationType.CUSTOM)) setDonationAmount(+e.target.value); }; @@ -39,8 +31,8 @@ const DonationStepMethod: React.FC = ({ type="radio" id="donate-5" name="donation-type" - value={DonationAmount.FIVE_DOLLARS} - checked={donationType === DonationAmount.FIVE_DOLLARS} + value={DonationType.FIVE_DOLLARS} + checked={donationType === DonationType.FIVE_DOLLARS} onChange={onDonationChange} /> @@ -50,8 +42,8 @@ const DonationStepMethod: React.FC = ({ type="radio" id="donate-10" name="donation-type" - value={DonationAmount.TEN_DOLLARS} - checked={donationType === DonationAmount.TEN_DOLLARS} + value={DonationType.TEN_DOLLARS} + checked={donationType === DonationType.TEN_DOLLARS} onChange={onDonationChange} /> @@ -61,8 +53,8 @@ const DonationStepMethod: React.FC = ({ type="radio" id="donate-20" name="donation-type" - value={DonationAmount.TWENTY_DOLLARS} - checked={donationType === DonationAmount.TWENTY_DOLLARS} + value={DonationType.TWENTY_DOLLARS} + checked={donationType === DonationType.TWENTY_DOLLARS} onChange={onDonationChange} /> @@ -72,8 +64,8 @@ const DonationStepMethod: React.FC = ({ type="radio" id="donate-50" name="donation-type" - value={DonationAmount.FIFTY_DOLLARS} - checked={donationType === DonationAmount.FIFTY_DOLLARS} + value={DonationType.FIFTY_DOLLARS} + checked={donationType === DonationType.FIFTY_DOLLARS} onChange={onDonationChange} /> @@ -83,8 +75,8 @@ const DonationStepMethod: React.FC = ({ type="radio" id="donate-100" name="donation-type" - value={DonationAmount.ONE_HUNDRED_DOLLARS} - checked={donationType === DonationAmount.ONE_HUNDRED_DOLLARS} + value={DonationType.ONE_HUNDRED_DOLLARS} + checked={donationType === DonationType.ONE_HUNDRED_DOLLARS} onChange={onDonationChange} /> @@ -94,8 +86,8 @@ const DonationStepMethod: React.FC = ({ type="radio" id="donate-custom" name="donation-type" - value={DonationAmount.CUSTOM} - checked={donationType === DonationAmount.CUSTOM} + value={DonationType.CUSTOM} + checked={donationType === DonationType.CUSTOM} onChange={onDonationChange} /> diff --git a/src/components/Modal/DonationStepThanks.tsx b/src/components/Modal/DonationModal/DonationStepThanks.tsx similarity index 100% rename from src/components/Modal/DonationStepThanks.tsx rename to src/components/Modal/DonationModal/DonationStepThanks.tsx diff --git a/src/components/Modal/DonationModal/index.ts b/src/components/Modal/DonationModal/index.ts new file mode 100644 index 0000000..36af021 --- /dev/null +++ b/src/components/Modal/DonationModal/index.ts @@ -0,0 +1,2 @@ +import DonationModal from "./DonationModal"; +export default DonationModal; diff --git a/src/components/Modal/DonationStepCC.tsx b/src/components/Modal/DonationStepCC.tsx deleted file mode 100644 index d250dc6..0000000 --- a/src/components/Modal/DonationStepCC.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import React, { useRef, useEffect } from "react"; -import { client, hostedFields, Client, HostedFields } from "braintree-web"; - -const BT_STYLES = { - ":focus": { - color: "blue", - }, - ".valid": { - color: "green", - }, - ".invalid": { - color: "red", - }, -}; -const BT_PAYMENT_FIELDS = { - number: { - container: "#card-number", - placeholder: "4111 1111 1111 1111", - }, - cvv: { - container: "#cvv", - placeholder: "123", - }, - expirationDate: { - container: "#expiration-date", - placeholder: "10/2022", - }, -}; - -const PaymentModal: React.FC<{}> = () => { - const bt = useRef(); - const form = useRef(); - - const handleSubmit = async (event: React.FormEvent) => { - event.preventDefault(); - if (!form.current || !bt.current) return; - - const response = await form.current.tokenize(); - console.log({ response }); - }; - - useEffect(() => { - const initializeBt = async () => { - try { - bt.current = await client.create({ - authorization: "sandbox_246jdjxq_8h7hm5rvngkykjds", - }); - - form.current = await hostedFields.create({ - client: bt.current!!, - fields: BT_PAYMENT_FIELDS, - styles: BT_STYLES, - }); - } catch (err) { - console.error(err); - } - }; - - initializeBt(); - }, []); - - return ( -
- -
- - -
- - -
- -
- -
-
- ); -}; - -export default PaymentModal; diff --git a/src/components/Modal/Modal.css b/src/components/Modal/Modal.css index a52a4f4..678e2ff 100644 --- a/src/components/Modal/Modal.css +++ b/src/components/Modal/Modal.css @@ -13,7 +13,6 @@ flex-direction: column; width: 400px; min-height: 600px; - /* background-color: #ffe8dc; */ background-color: white; border-radius: 8px; border: 2px solid black; @@ -22,8 +21,10 @@ @media screen and (max-width: 480px) { .modal-content { - min-width: 90%; - min-height: 100%; + position: fixed; + inset: 0; + width: unset; + min-height: unset; border: none; border-radius: 0; } @@ -48,6 +49,17 @@ button.modal-close-button { z-index: 1; } +.modal-titlebar { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; +} + +.modal-titlebar > * { + margin: 0; +} + .payment-methods { display: grid; gap: 16px; @@ -64,4 +76,4 @@ button.modal-close-button { .payment-methods button > svg { margin: 0; -} \ No newline at end of file +} diff --git a/src/components/Modal/Modal.tsx b/src/components/Modal/Modal.tsx index 9c0746b..d044a27 100644 --- a/src/components/Modal/Modal.tsx +++ b/src/components/Modal/Modal.tsx @@ -1,23 +1,36 @@ import React from "react"; -import { useRecoilState } from "recoil"; +import { useRecoilState, useRecoilValue } from "recoil"; import { useLockBodyScroll } from "react-use"; import { useHotkeys } from "react-hotkeys-hook"; +import { X } from "phosphor-react"; -import { modalOpenAtom } from "../../state/atoms"; -import DonationModal from "./DonationModal"; +import { modalAtom, modalOpenAtom } from "state/atoms"; import "./Modal.css"; -const Modal: React.FC<{}> = () => { +export interface ModalInstance { + close: () => void; + children?: JSX.Element; +} + +const Modal = (): JSX.Element | null => { + const Instance = useRecoilValue(modalAtom); const [isModalOpen, setModalOpen] = useRecoilState(modalOpenAtom); useHotkeys("esc", () => isModalOpen && setModalOpen(false), [isModalOpen]); useLockBodyScroll(isModalOpen); - if (!isModalOpen) return null; + const close = () => setModalOpen(false); + + if (!Instance || !isModalOpen) return null; return (
- +
+ + +
); }; diff --git a/src/components/Modal/index.ts b/src/components/Modal/index.ts new file mode 100644 index 0000000..920453e --- /dev/null +++ b/src/components/Modal/index.ts @@ -0,0 +1,3 @@ +import Modal from "./Modal"; +export default Modal; + diff --git a/src/components/Notice/Notice.tsx b/src/components/Notice/Notice.tsx index a89d20f..fcc348e 100644 --- a/src/components/Notice/Notice.tsx +++ b/src/components/Notice/Notice.tsx @@ -1,17 +1,21 @@ import React from "react"; import { motion } from "framer-motion"; import { useRecoilValue } from "recoil"; - -import { isDarkThemeSelector } from "../../state/selectors"; -import { searchQueryAtom } from "../../state/atoms"; import { HourglassMedium, Question, SmileyXEyes } from "phosphor-react"; +import { isDarkThemeSelector } from "state/selectors"; +import { searchQueryAtom } from "state/atoms"; + interface NoticeProps { message?: string; type?: "wait" | "help" | "warn" | "none"; } -const Notice: React.FC = ({ message, type = "warn", children }) => { +const Notice: React.FC = ({ + message, + type = "warn", + children, +}) => { const isDark = useRecoilValue(isDarkThemeSelector); const query = useRecoilValue(searchQueryAtom); diff --git a/src/components/Notice/index.ts b/src/components/Notice/index.ts new file mode 100644 index 0000000..5fdedb9 --- /dev/null +++ b/src/components/Notice/index.ts @@ -0,0 +1,2 @@ +import Notice from "./Notice"; +export default Notice; diff --git a/src/components/SearchInput/SearchInput.tsx b/src/components/SearchInput/SearchInput.tsx index d308cc3..c76463c 100644 --- a/src/components/SearchInput/SearchInput.tsx +++ b/src/components/SearchInput/SearchInput.tsx @@ -5,7 +5,8 @@ import { useHotkeys } from "react-hotkeys-hook"; import { Command, MagnifyingGlass, X, HourglassHigh } from "phosphor-react"; import ReactGA from "react-ga"; -import { searchQueryAtom } from "../../state/atoms"; +import { searchQueryAtom } from "state/atoms"; + import "./SearchInput.css"; const apple = /iPhone|iPod|iPad|Macintosh|MacIntel|MacPPC/i; @@ -19,7 +20,8 @@ type SearchInputProps = {}; const SearchInput: React.FC = () => { const [value, setValue] = useState(""); const [query, setQuery] = useRecoilState(searchQueryAtom); - const inputRef = useRef() as MutableRefObject; + const inputRef = + useRef() as MutableRefObject; useHotkeys("ctrl+k,cmd+k", (e) => { e.preventDefault(); diff --git a/src/components/SearchInput/index.ts b/src/components/SearchInput/index.ts new file mode 100644 index 0000000..90c2283 --- /dev/null +++ b/src/components/SearchInput/index.ts @@ -0,0 +1,2 @@ +import SearchInput from "./SearchInput"; +export default SearchInput; diff --git a/src/components/SizeInput/SizeInput.tsx b/src/components/SizeInput/SizeInput.tsx index d8e37be..c09ea54 100644 --- a/src/components/SizeInput/SizeInput.tsx +++ b/src/components/SizeInput/SizeInput.tsx @@ -1,7 +1,8 @@ import React, { useCallback } from "react"; import { useRecoilState } from "recoil"; -import { iconSizeAtom } from "../../state/atoms"; +import { iconSizeAtom } from "state/atoms"; + import "./SizeInput.css"; type SizeInputProps = {}; diff --git a/src/components/SizeInput/index.ts b/src/components/SizeInput/index.ts new file mode 100644 index 0000000..357c511 --- /dev/null +++ b/src/components/SizeInput/index.ts @@ -0,0 +1,2 @@ +import SizeInput from "./SizeInput"; +export default SizeInput; diff --git a/src/components/StyleInput/StyleInput.tsx b/src/components/StyleInput/StyleInput.tsx index 644573d..778c921 100644 --- a/src/components/StyleInput/StyleInput.tsx +++ b/src/components/StyleInput/StyleInput.tsx @@ -3,8 +3,9 @@ import { useRecoilState } from "recoil"; import Select from "react-dropdown-select"; import { PencilLine } from "phosphor-react"; -import { iconWeightAtom } from "../../state/atoms"; -import { IconStyle } from "../../lib"; +import { iconWeightAtom } from "state/atoms"; +import { IconStyle } from "lib"; + import "./StyleInput.css"; type WeightOption = { key: string; value: IconStyle; icon: JSX.Element }; diff --git a/src/components/StyleInput/index.ts b/src/components/StyleInput/index.ts new file mode 100644 index 0000000..fe7e478 --- /dev/null +++ b/src/components/StyleInput/index.ts @@ -0,0 +1,2 @@ +import StyleInput from "./StyleInput"; +export default StyleInput; diff --git a/src/components/Toolbar/Toolbar.tsx b/src/components/Toolbar/Toolbar.tsx index e5a939c..93b5577 100644 --- a/src/components/Toolbar/Toolbar.tsx +++ b/src/components/Toolbar/Toolbar.tsx @@ -1,10 +1,11 @@ import React from "react"; +import StyleInput from "components/StyleInput"; +import SearchInput from "components/SearchInput"; +import SizeInput from "components/SizeInput"; +import ColorInput from "components/ColorInput"; + import "./Toolbar.css"; -import StyleInput from "../StyleInput/StyleInput"; -import SearchInput from "../SearchInput/SearchInput"; -import SizeInput from "../SizeInput/SizeInput"; -import ColorInput from "../ColorInput/ColorInput"; type ToolbarProps = {}; diff --git a/src/components/Toolbar/index.ts b/src/components/Toolbar/index.ts new file mode 100644 index 0000000..df607a5 --- /dev/null +++ b/src/components/Toolbar/index.ts @@ -0,0 +1,2 @@ +import Toolbar from "./Toolbar"; +export default Toolbar; diff --git a/src/hooks/index.ts b/src/hooks/index.ts new file mode 100644 index 0000000..e883a9f --- /dev/null +++ b/src/hooks/index.ts @@ -0,0 +1,8 @@ +export { default as useDebounce } from "./useDebounce"; +export { default as useGridSpans } from "./useGridSpans"; +export { default as useIconParameters } from "./useIconParameters"; +export { default as useThrottle } from "./useThrottle"; +export { default as useThrottled } from "./useThrottled"; +export { default as useTimeoutFn } from "./useTimeoutFn"; +export { default as useTransientState } from "./useTransientState"; +export { default as useUnmount } from "./useUnmount"; diff --git a/src/index.tsx b/src/index.tsx index 9a0e59d..3407269 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -2,7 +2,7 @@ import React from "react"; import ReactDOM from "react-dom"; import { RecoilRoot } from "recoil"; import * as serviceWorker from "./serviceWorker"; -import App from "./components/App/App"; +import App from "./components/App"; import ReactGA from "react-ga"; ReactGA.initialize("UA-179205759-1", { titleCase: false }); @@ -23,7 +23,6 @@ ReactDOM.render( serviceWorker.unregister(); console.log(` - %c sphorphosphor %co%cspho %c s%cphorphosphor %co%csphorpho%cs %c o %cp%chorphosphor %co%csphorphosph%co diff --git a/src/state/atoms.ts b/src/state/atoms.ts index 7a2a5bb..97c9149 100644 --- a/src/state/atoms.ts +++ b/src/state/atoms.ts @@ -1,4 +1,5 @@ import { atom } from "recoil"; +import { ModalInstance } from "../components/Modal/Modal"; import { IconStyle } from "../lib"; export const searchQueryAtom = atom({ @@ -26,7 +27,12 @@ export const iconPreviewOpenAtom = atom({ default: false, }); +export const modalAtom = atom<((props: ModalInstance) => JSX.Element) | null>({ + key: "modalAtom", + default: null, +}); + export const modalOpenAtom = atom({ key: "modalOpenAtom", - default: true, + default: false, }); diff --git a/src/state/selectors.ts b/src/state/selectors.ts index c76205a..518a0d0 100644 --- a/src/state/selectors.ts +++ b/src/state/selectors.ts @@ -1,10 +1,16 @@ -import { selector, selectorFamily } from "recoil"; +import { DefaultValue, selector, selectorFamily } from "recoil"; import TinyColor from "tinycolor2"; import Fuse from "fuse.js"; -import { searchQueryAtom, iconColorAtom } from "./atoms"; +import { + searchQueryAtom, + iconColorAtom, + modalAtom, + modalOpenAtom, +} from "./atoms"; import { IconEntry, IconCategory } from "../lib"; import { icons } from "../lib/icons"; +import { ModalInstance } from "../components/Modal/Modal"; const fuse = new Fuse(icons, { keys: [{ name: "name", weight: 4 }, "tags", "categories"], @@ -52,17 +58,40 @@ export const singleCategoryQueryResultsSelector = selectorFamily< IconCategory >({ key: "singleCategoryQueryResultsSelector", - get: (category: IconCategory) => ({ get }) => { - const filteredResults = get(filteredQueryResultsSelector); - return new Promise((resolve) => - resolve( - filteredResults.filter((icon) => icon.categories.includes(category)) - ) - ); - }, + get: + (category: IconCategory) => + ({ get }) => { + const filteredResults = get(filteredQueryResultsSelector); + return new Promise((resolve) => + resolve( + filteredResults.filter((icon) => icon.categories.includes(category)) + ) + ); + }, }); export const isDarkThemeSelector = selector({ key: "isDarkThemeSelector", get: ({ get }) => TinyColor(get(iconColorAtom)).isLight(), }); + +export const modalSelector = selector<{ + type: (props: ModalInstance) => JSX.Element; +} | null>({ + key: "openModalSelector", + set: ({ set }, instance) => { + if (instance instanceof DefaultValue || instance === null) { + set(modalAtom, null); + set(modalOpenAtom, false); + return; + } + + set(modalAtom, () => instance.type!!); + set(modalOpenAtom, true); + }, + get: ({ get }) => { + const currentModal = get(modalAtom); + if (!currentModal) return null; + return { type: currentModal }; + }, +}); diff --git a/tsconfig.json b/tsconfig.json index cf0f8ac..331d55e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,36 +1,27 @@ { "compilerOptions": { - "target": "es5", - "lib": [ - "es6", - "dom", - "dom.iterable", - "esnext" - ], "allowJs": true, - "skipLibCheck": true, - "esModuleInterop": true, "allowSyntheticDefaultImports": true, - "strict": true, + "baseUrl": "./src/", + "declaration": true, + "esModuleInterop": true, + "experimentalDecorators": true, "forceConsistentCasingInFileNames": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, "isolatedModules": true, "jsx": "react", - "sourceMap": true, - "declaration": true, + "lib": ["es6", "dom", "dom.iterable", "esnext"], + "module": "esnext", + "moduleResolution": "node", + "noEmit": true, + "noFallthroughCasesInSwitch": true, "noUnusedLocals": true, "noUnusedParameters": true, - "experimentalDecorators": true, - "noFallthroughCasesInSwitch": true, - "noEmit": true + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "target": "es5" }, - "include": [ - "src" - ], - "exclude": [ - "node_modules", - "build" - ] + "include": ["src"], + "exclude": ["node_modules", "build"] }