From d379cea5bcf45d19cc9317dc3f689373c09d4152 Mon Sep 17 00:00:00 2001 From: rektdeckard Date: Fri, 3 Mar 2023 22:39:42 -0700 Subject: [PATCH] feat(app): banner, style tweaks --- package.json | 1 - src/components/App/App.css | 7 +- src/components/Banner/Banner.css | 44 +++++++++- src/components/Banner/Banner.tsx | 86 +++++++++++++++---- src/components/Footer/Footer.css | 7 +- src/components/Header/Header.css | 4 +- src/components/Header/Header.tsx | 20 ++++- src/components/IconGrid/DetailFooter.tsx | 6 +- src/components/IconGrid/IconGrid.css | 1 + src/components/IconGrid/IconGridItem.tsx | 17 +++- src/components/Toolbar/Toolbar.css | 2 +- src/hooks/index.ts | 3 +- src/hooks/useLocalStorage.ts | 40 +++++++++ ...seSessionState.ts => useSessionStorage.ts} | 11 ++- src/utils/index.ts | 6 +- 15 files changed, 215 insertions(+), 40 deletions(-) create mode 100644 src/hooks/useLocalStorage.ts rename src/hooks/{useSessionState.ts => useSessionStorage.ts} (76%) diff --git a/package.json b/package.json index 84a1576..b8600b3 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,6 @@ "react-use": "^17.4.0", "recoil": "^0.7.6", "svg2png-converter": "^1.0.2", - "tinycolor": "^0.0.1", "tinycolor2": "^1.4.2" }, "devDependencies": { diff --git a/src/components/App/App.css b/src/components/App/App.css index 874d6bb..effc40e 100644 --- a/src/components/App/App.css +++ b/src/components/App/App.css @@ -1,7 +1,11 @@ :root { --red: #ff6e60; - --blue: #397fff; + --orange: #ff8e51; --yellow: #ffd171; + --pale: #ffe8dc; + --peach: #ffd5c0; + --darkgreen: #245633; + --blue: #397fff; --purple: #925bff; --eggplant: #35313d; --neutral: #86838b; @@ -59,6 +63,7 @@ button { display: flex; align-items: center; justify-content: flex-start; + cursor: pointer; } button.main-button { diff --git a/src/components/Banner/Banner.css b/src/components/Banner/Banner.css index 8c6ce88..d3d3236 100644 --- a/src/components/Banner/Banner.css +++ b/src/components/Banner/Banner.css @@ -1,10 +1,20 @@ .banner { - display: grid; - place-items: center; - padding: 24px; + position: fixed; + top: 0; + left: 0; + right: 0; + border-radius: 0; + /* top: 8px; + left: 8px; + right: 8px; + max-width: 1120px; */ + display: flex; + padding: 12px; color: white; text-align: center; + margin: auto; background-color: var(--eggplant); + z-index: 1; } .banner .main-button { @@ -12,3 +22,31 @@ min-height: 64px; margin: 8px 0 0; } + +.banner a { + color: white; +} + +.banner-content { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + flex: 1; + max-width: 1120px; + margin: auto; +} + +.banner-button { + color: inherit; + background: var(--eggplant); + height: unset !important; + padding: 0 !important; + margin: 0 !important; + border-radius: 48px !important; + cursor: pointer; +} + +.banner-button:active { + opacity: 0.7; +} diff --git a/src/components/Banner/Banner.tsx b/src/components/Banner/Banner.tsx index 8d5c6eb..d460b89 100644 --- a/src/components/Banner/Banner.tsx +++ b/src/components/Banner/Banner.tsx @@ -1,26 +1,80 @@ -import { Medal } from "phosphor-react"; +import { ReactNode, Dispatch, SetStateAction } from "react"; +import { motion, AnimatePresence, Variants } from "framer-motion"; +import { XCircle } from "phosphor-react"; import ReactGA from "react-ga4"; +import { useLocalStorage } from "@/hooks"; + import "./Banner.css"; -const Banner = () => { - const handleClick = () => { - ReactGA.event({ category: "Outbound", action: "Click", label: "Vote" }); - window.open( - "https://www.figma.com/community/file/903830135544202908", - "_blank", - "noopener noreferrer" - ); +type BannerState = { + seen: Record; +}; + +type BannerProps = { + id: string; + children?: ReactNode; + onClose?: (dispatch: Dispatch>) => void; +}; + +const variants: Variants = { + initial: { y: -120 }, + animate: { y: 0 }, + exit: { y: -120 }, +}; + +const BANNER_STATE_KEY = "banner_state"; + +const Banner = ({ id, children, onClose }: BannerProps) => { + const [ + { + seen: { [id]: seen }, + }, + setBannerState, + ] = useLocalStorage(BANNER_STATE_KEY, { + seen: { [id]: false }, + }); + + const handleClose = () => { + ReactGA.event({ + category: "Banner", + action: "Dismiss", + label: id, + }); + onClose + ? onClose(setBannerState) + : setBannerState((state) => ({ + ...state, + seen: { ...state.seen, [id]: true }, + })); }; return ( -
- The 2022 Figma Community Awards are here! - -
+ + {!seen && ( + +
+ {children} + +
+
+ )} +
); }; diff --git a/src/components/Footer/Footer.css b/src/components/Footer/Footer.css index 7d1fc89..60a3601 100644 --- a/src/components/Footer/Footer.css +++ b/src/components/Footer/Footer.css @@ -11,6 +11,11 @@ footer { font-size: 56px; } +#back-to-top-button:active { + transform: translate(4px, 4px) !important; + box-shadow: 0 0 0 0 black; +} + #back-to-top-button svg { margin-right: unset; } @@ -138,7 +143,7 @@ footer .links { display: initial; position: absolute; left: -240px; - top: 656px; + top: 632px; height: 584px; overflow: hidden; } diff --git a/src/components/Header/Header.css b/src/components/Header/Header.css index 9cbae24..f54fdad 100644 --- a/src/components/Header/Header.css +++ b/src/components/Header/Header.css @@ -1,7 +1,7 @@ header { - width: 100%; - background-color: var(--yellow); overflow: hidden; + position: relative; + background-color: var(--yellow); } .header-contents { diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx index 9e2ab07..12f13f8 100644 --- a/src/components/Header/Header.tsx +++ b/src/components/Header/Header.tsx @@ -1,4 +1,6 @@ -import { ArrowCircleUpRight, ArrowCircleDown } from "phosphor-react"; +import { ArrowCircleUpRight, ArrowCircleDown, Megaphone } from "phosphor-react"; + +import Banner from "@/components/Banner"; import { ReactComponent as MarkerPurple } from "@/assets/marker-purple.svg"; import { ReactComponent as PaperClips } from "@/assets/paperclips-header-mobile.svg"; @@ -36,6 +38,22 @@ const handleScrollToIcons = () => const Header = (_: HeaderProps) => { return (
+ + + + Phosphor has some big updates, and some APIs have changed for + users of the Vanilla JS library. Please check our{" "} + + documentation + {" "} + to see what's new... + + + } + />
diff --git a/src/components/IconGrid/DetailFooter.tsx b/src/components/IconGrid/DetailFooter.tsx index 2e069ec..b94cd4d 100644 --- a/src/components/IconGrid/DetailFooter.tsx +++ b/src/components/IconGrid/DetailFooter.tsx @@ -8,7 +8,7 @@ import { Copy, CheckCircle, DownloadSimple, XCircle } from "phosphor-react"; import ReactGA from "react-ga4"; import Tabs, { Tab } from "@/components/Tabs"; -import { useMediaQuery, useTransientState, useSessionState } from "@/hooks"; +import { useMediaQuery, useTransientState, useSessionStorage } from "@/hooks"; import { SnippetType } from "@/lib"; import { iconWeightAtom, @@ -66,7 +66,7 @@ const DetailFooter = () => { ); const ref = useRef(null); - const [{ i }, setInitialTab] = useSessionState("tab", { i: 0 }); + const [{ i }, setInitialTab] = useSessionStorage("tab", { i: 0 }); const isMobile = useMediaQuery("(max-width: 719px)"); @@ -219,7 +219,7 @@ const DetailFooter = () => { }; return ( - + {!!entry && ( { index: number; isDark: boolean; entry: IconEntry; @@ -25,7 +31,7 @@ const itemVariants = { }; const IconGridItem = (props: IconGridItemProps) => { - const { index, originOffset, entry } = props; + const { index, originOffset, entry, style } = props; const { name, Icon } = entry; const [selection, setSelectionEntry] = useRecoilState(selectionEntryAtom); const isOpen = selection?.name === name; @@ -68,7 +74,10 @@ const IconGridItem = (props: IconGridItemProps) => { key={name} ref={ref} tabIndex={0} - style={isOpen ? { backgroundColor: "var(--translucent)" } : undefined} + style={{ + ...style, + backgroundColor: isOpen ? "var(--translucent)" : undefined, + }} custom={delayRef} transition={transition} variants={itemVariants} diff --git a/src/components/Toolbar/Toolbar.css b/src/components/Toolbar/Toolbar.css index c620223..c9f5576 100644 --- a/src/components/Toolbar/Toolbar.css +++ b/src/components/Toolbar/Toolbar.css @@ -5,7 +5,7 @@ nav.toolbar { padding: 0; margin: 0; background-color: var(--eggplant); - z-index: 1; + z-index: 2; display: flex; justify-content: center; align-items: center; diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 4a06ae8..eb8c161 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -2,9 +2,10 @@ export { default as useCSSVariables } from "./useCSSVariables"; export { default as useDebounce } from "./useDebounce"; export { default as useEvent } from "./useEvent"; export { default as useIconParameters } from "./useIconParameters"; +export { default as useLocalStorage } from "./useLocalStorage"; export { default as useMediaQuery } from "./useMediaQuery"; export { default as usePersistSettings } from "./usePersistSettings"; -export { default as useSessionState } from "./useSessionState"; +export { default as useSessionStorage } from "./useSessionStorage"; export { default as useThrottle } from "./useThrottle"; export { default as useThrottled } from "./useThrottled"; export { default as useTimeoutFn } from "./useTimeoutFn"; diff --git a/src/hooks/useLocalStorage.ts b/src/hooks/useLocalStorage.ts new file mode 100644 index 0000000..4d49cbb --- /dev/null +++ b/src/hooks/useLocalStorage.ts @@ -0,0 +1,40 @@ +import { useCallback, useState, Dispatch, SetStateAction } from "react"; +import { STORAGE_KEY } from "@/state"; + +type Initializer = () => S; +type Setter = (prev: S) => S; +type Action = S | Setter | Initializer; + +function expand(action: Action, prev?: S) { + if (typeof action === "function") { + return (action as Setter)(prev!); + } else { + return action; + } +} + +export default function useLocalStorage( + key: string, + fallbackState: S | (() => S) +): [S, Dispatch>, (partial: Partial) => void] { + const [value, setValue] = useState(() => { + let val = localStorage.getItem(STORAGE_KEY + key); + if (val) return JSON.parse(val) as S; + return expand(fallbackState); + }); + + const set: Dispatch> = useCallback((val) => { + setValue((prev) => { + const next = expand(val, prev); + localStorage.setItem(STORAGE_KEY + key, JSON.stringify(next)); + return next; + }); + }, []); + + const insert = useCallback( + (partial: Partial) => set((value) => ({ ...value, ...partial })), + [] + ); + + return [value, set, insert]; +} diff --git a/src/hooks/useSessionState.ts b/src/hooks/useSessionStorage.ts similarity index 76% rename from src/hooks/useSessionState.ts rename to src/hooks/useSessionStorage.ts index 8547015..9ce2453 100644 --- a/src/hooks/useSessionState.ts +++ b/src/hooks/useSessionStorage.ts @@ -13,10 +13,10 @@ function expand(action: Action, prev?: S) { } } -export default function useSessionState( +export default function useSessionStorage( key: string, fallbackState: S | (() => S) -): [S, Dispatch>] { +): [S, Dispatch>, (partial: Partial) => void] { const [value, setValue] = useState(() => { let val = sessionStorage.getItem(STORAGE_KEY + key); if (val) return JSON.parse(val) as S; @@ -31,5 +31,10 @@ export default function useSessionState( }); }, []); - return [value, set]; + const insert = useCallback( + (partial: Partial) => set((value) => ({ ...value, ...partial })), + [] + ); + + return [value, set, insert]; } diff --git a/src/utils/index.ts b/src/utils/index.ts index 748b849..bb28914 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -21,9 +21,9 @@ export function getCodeSnippets({ const elmWeight = weight.replace(/^\w/, (c) => c.toUpperCase()); return { - [SnippetType.HTML]: ``, + } ph-${name}">`, [SnippetType.REACT]: `<${displayName} size={${size}} ${ !isDefaultColor ? `color="${color}" ` : "" }${isDefaultWeight ? "" : `weight="${weight}" `}/>`, @@ -56,6 +56,6 @@ export function supportsWeight({ type: SnippetType; weight: IconStyle; }): boolean { - if (type !== SnippetType.HTML && type !== SnippetType.FLUTTER) return true; + if (type !== SnippetType.FLUTTER) return true; return weight !== IconStyle.DUOTONE; }