feat(app): banner, style tweaks

This commit is contained in:
rektdeckard
2023-03-03 22:39:42 -07:00
parent baeec27267
commit d379cea5bc
15 changed files with 215 additions and 40 deletions

View File

@@ -41,7 +41,6 @@
"react-use": "^17.4.0", "react-use": "^17.4.0",
"recoil": "^0.7.6", "recoil": "^0.7.6",
"svg2png-converter": "^1.0.2", "svg2png-converter": "^1.0.2",
"tinycolor": "^0.0.1",
"tinycolor2": "^1.4.2" "tinycolor2": "^1.4.2"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -1,7 +1,11 @@
:root { :root {
--red: #ff6e60; --red: #ff6e60;
--blue: #397fff; --orange: #ff8e51;
--yellow: #ffd171; --yellow: #ffd171;
--pale: #ffe8dc;
--peach: #ffd5c0;
--darkgreen: #245633;
--blue: #397fff;
--purple: #925bff; --purple: #925bff;
--eggplant: #35313d; --eggplant: #35313d;
--neutral: #86838b; --neutral: #86838b;
@@ -59,6 +63,7 @@ button {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: flex-start;
cursor: pointer;
} }
button.main-button { button.main-button {

View File

@@ -1,10 +1,20 @@
.banner { .banner {
display: grid; position: fixed;
place-items: center; top: 0;
padding: 24px; left: 0;
right: 0;
border-radius: 0;
/* top: 8px;
left: 8px;
right: 8px;
max-width: 1120px; */
display: flex;
padding: 12px;
color: white; color: white;
text-align: center; text-align: center;
margin: auto;
background-color: var(--eggplant); background-color: var(--eggplant);
z-index: 1;
} }
.banner .main-button { .banner .main-button {
@@ -12,3 +22,31 @@
min-height: 64px; min-height: 64px;
margin: 8px 0 0; 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;
}

View File

@@ -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 ReactGA from "react-ga4";
import { useLocalStorage } from "@/hooks";
import "./Banner.css"; import "./Banner.css";
const Banner = () => { type BannerState = {
const handleClick = () => { seen: Record<string, boolean>;
ReactGA.event({ category: "Outbound", action: "Click", label: "Vote" }); };
window.open(
"https://www.figma.com/community/file/903830135544202908", type BannerProps = {
"_blank", id: string;
"noopener noreferrer" children?: ReactNode;
); onClose?: (dispatch: Dispatch<SetStateAction<BannerState>>) => 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<BannerState>(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 ( return (
<div className="banner"> <AnimatePresence initial={true}>
The 2022 Figma Community Awards are here! {!seen && (
<button className="main-button" onClick={handleClick}> <motion.aside
<Medal size={24} weight="fill" /> className="card banner"
Vote for Phosphor initial="initial"
</button> animate="animate"
</div> exit="exit"
variants={variants}
>
<div className="banner-content">
{children}
<button
tabIndex={0}
className="banner-button"
onClick={handleClose}
onKeyDown={(e) => {
e.key === "Enter" && handleClose();
}}
>
<XCircle color="currentColor" size={28} weight="fill" />
</button>
</div>
</motion.aside>
)}
</AnimatePresence>
); );
}; };

View File

@@ -11,6 +11,11 @@ footer {
font-size: 56px; 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 { #back-to-top-button svg {
margin-right: unset; margin-right: unset;
} }
@@ -138,7 +143,7 @@ footer .links {
display: initial; display: initial;
position: absolute; position: absolute;
left: -240px; left: -240px;
top: 656px; top: 632px;
height: 584px; height: 584px;
overflow: hidden; overflow: hidden;
} }

View File

@@ -1,7 +1,7 @@
header { header {
width: 100%;
background-color: var(--yellow);
overflow: hidden; overflow: hidden;
position: relative;
background-color: var(--yellow);
} }
.header-contents { .header-contents {

View File

@@ -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 MarkerPurple } from "@/assets/marker-purple.svg";
import { ReactComponent as PaperClips } from "@/assets/paperclips-header-mobile.svg"; import { ReactComponent as PaperClips } from "@/assets/paperclips-header-mobile.svg";
@@ -36,6 +38,22 @@ const handleScrollToIcons = () =>
const Header = (_: HeaderProps) => { const Header = (_: HeaderProps) => {
return ( return (
<header> <header>
<Banner
id={Math.random().toString()}
children={
<>
<Megaphone mirrored color="var(--orange)" size={28} weight="fill" />
<small>
Phosphor has some big updates, and some APIs have changed for
users of the Vanilla JS library. Please check our{" "}
<a href="https://github.com/phosphor-icons/homepage#readme">
documentation
</a>{" "}
to see what's new...
</small>
</>
}
/>
<div className="header-contents"> <div className="header-contents">
<div className="illustrations-top"> <div className="illustrations-top">
<MarkerPurple id="marker-purple" /> <MarkerPurple id="marker-purple" />

View File

@@ -8,7 +8,7 @@ import { Copy, CheckCircle, DownloadSimple, XCircle } from "phosphor-react";
import ReactGA from "react-ga4"; import ReactGA from "react-ga4";
import Tabs, { Tab } from "@/components/Tabs"; import Tabs, { Tab } from "@/components/Tabs";
import { useMediaQuery, useTransientState, useSessionState } from "@/hooks"; import { useMediaQuery, useTransientState, useSessionStorage } from "@/hooks";
import { SnippetType } from "@/lib"; import { SnippetType } from "@/lib";
import { import {
iconWeightAtom, iconWeightAtom,
@@ -66,7 +66,7 @@ const DetailFooter = () => {
); );
const ref = useRef<SVGSVGElement>(null); const ref = useRef<SVGSVGElement>(null);
const [{ i }, setInitialTab] = useSessionState("tab", { i: 0 }); const [{ i }, setInitialTab] = useSessionStorage("tab", { i: 0 });
const isMobile = useMediaQuery("(max-width: 719px)"); const isMobile = useMediaQuery("(max-width: 719px)");
@@ -219,7 +219,7 @@ const DetailFooter = () => {
}; };
return ( return (
<AnimatePresence initial={false}> <AnimatePresence initial={true}>
{!!entry && ( {!!entry && (
<motion.aside <motion.aside
initial="initial" initial="initial"

View File

@@ -2,6 +2,7 @@
position: relative; position: relative;
padding: 32px 16px; padding: 32px 16px;
/* min-height: 80vh; */ /* min-height: 80vh; */
z-index: 1;
content-visibility: auto; content-visibility: auto;
color: var(--foreground); color: var(--foreground);
background-color: var(--background); background-color: var(--background);

View File

@@ -1,11 +1,17 @@
import { useRef, useLayoutEffect, useEffect, MutableRefObject } from "react"; import {
useRef,
useLayoutEffect,
useEffect,
MutableRefObject,
HTMLAttributes,
} from "react";
import { useRecoilState } from "recoil"; import { useRecoilState } from "recoil";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { IconEntry } from "@/lib"; import { IconEntry } from "@/lib";
import { selectionEntryAtom } from "@/state"; import { selectionEntryAtom } from "@/state";
interface IconGridItemProps { interface IconGridItemProps extends HTMLAttributes<HTMLDivElement> {
index: number; index: number;
isDark: boolean; isDark: boolean;
entry: IconEntry; entry: IconEntry;
@@ -25,7 +31,7 @@ const itemVariants = {
}; };
const IconGridItem = (props: IconGridItemProps) => { const IconGridItem = (props: IconGridItemProps) => {
const { index, originOffset, entry } = props; const { index, originOffset, entry, style } = props;
const { name, Icon } = entry; const { name, Icon } = entry;
const [selection, setSelectionEntry] = useRecoilState(selectionEntryAtom); const [selection, setSelectionEntry] = useRecoilState(selectionEntryAtom);
const isOpen = selection?.name === name; const isOpen = selection?.name === name;
@@ -68,7 +74,10 @@ const IconGridItem = (props: IconGridItemProps) => {
key={name} key={name}
ref={ref} ref={ref}
tabIndex={0} tabIndex={0}
style={isOpen ? { backgroundColor: "var(--translucent)" } : undefined} style={{
...style,
backgroundColor: isOpen ? "var(--translucent)" : undefined,
}}
custom={delayRef} custom={delayRef}
transition={transition} transition={transition}
variants={itemVariants} variants={itemVariants}

View File

@@ -5,7 +5,7 @@ nav.toolbar {
padding: 0; padding: 0;
margin: 0; margin: 0;
background-color: var(--eggplant); background-color: var(--eggplant);
z-index: 1; z-index: 2;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;

View File

@@ -2,9 +2,10 @@ export { default as useCSSVariables } from "./useCSSVariables";
export { default as useDebounce } from "./useDebounce"; export { default as useDebounce } from "./useDebounce";
export { default as useEvent } from "./useEvent"; export { default as useEvent } from "./useEvent";
export { default as useIconParameters } from "./useIconParameters"; export { default as useIconParameters } from "./useIconParameters";
export { default as useLocalStorage } from "./useLocalStorage";
export { default as useMediaQuery } from "./useMediaQuery"; export { default as useMediaQuery } from "./useMediaQuery";
export { default as usePersistSettings } from "./usePersistSettings"; 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 useThrottle } from "./useThrottle";
export { default as useThrottled } from "./useThrottled"; export { default as useThrottled } from "./useThrottled";
export { default as useTimeoutFn } from "./useTimeoutFn"; export { default as useTimeoutFn } from "./useTimeoutFn";

View File

@@ -0,0 +1,40 @@
import { useCallback, useState, Dispatch, SetStateAction } from "react";
import { STORAGE_KEY } from "@/state";
type Initializer<S> = () => S;
type Setter<S> = (prev: S) => S;
type Action<S> = S | Setter<S> | Initializer<S>;
function expand<S extends object>(action: Action<S>, prev?: S) {
if (typeof action === "function") {
return (action as Setter<S>)(prev!);
} else {
return action;
}
}
export default function useLocalStorage<S extends object>(
key: string,
fallbackState: S | (() => S)
): [S, Dispatch<SetStateAction<S>>, (partial: Partial<S>) => void] {
const [value, setValue] = useState<S>(() => {
let val = localStorage.getItem(STORAGE_KEY + key);
if (val) return JSON.parse(val) as S;
return expand(fallbackState);
});
const set: Dispatch<SetStateAction<S>> = useCallback((val) => {
setValue((prev) => {
const next = expand(val, prev);
localStorage.setItem(STORAGE_KEY + key, JSON.stringify(next));
return next;
});
}, []);
const insert = useCallback(
(partial: Partial<S>) => set((value) => ({ ...value, ...partial })),
[]
);
return [value, set, insert];
}

View File

@@ -13,10 +13,10 @@ function expand<S extends object>(action: Action<S>, prev?: S) {
} }
} }
export default function useSessionState<S extends object>( export default function useSessionStorage<S extends object>(
key: string, key: string,
fallbackState: S | (() => S) fallbackState: S | (() => S)
): [S, Dispatch<SetStateAction<S>>] { ): [S, Dispatch<SetStateAction<S>>, (partial: Partial<S>) => void] {
const [value, setValue] = useState<S>(() => { const [value, setValue] = useState<S>(() => {
let val = sessionStorage.getItem(STORAGE_KEY + key); let val = sessionStorage.getItem(STORAGE_KEY + key);
if (val) return JSON.parse(val) as S; if (val) return JSON.parse(val) as S;
@@ -31,5 +31,10 @@ export default function useSessionState<S extends object>(
}); });
}, []); }, []);
return [value, set]; const insert = useCallback(
(partial: Partial<S>) => set((value) => ({ ...value, ...partial })),
[]
);
return [value, set, insert];
} }

View File

@@ -21,9 +21,9 @@ export function getCodeSnippets({
const elmWeight = weight.replace(/^\w/, (c) => c.toUpperCase()); const elmWeight = weight.replace(/^\w/, (c) => c.toUpperCase());
return { return {
[SnippetType.HTML]: `<i class="ph-${name}${ [SnippetType.HTML]: `<i class="ph${
isDefaultWeight ? "" : `-${weight}` isDefaultWeight ? "" : `-${weight}`
}"></i>`, } ph-${name}"></i>`,
[SnippetType.REACT]: `<${displayName} size={${size}} ${ [SnippetType.REACT]: `<${displayName} size={${size}} ${
!isDefaultColor ? `color="${color}" ` : "" !isDefaultColor ? `color="${color}" ` : ""
}${isDefaultWeight ? "" : `weight="${weight}" `}/>`, }${isDefaultWeight ? "" : `weight="${weight}" `}/>`,
@@ -56,6 +56,6 @@ export function supportsWeight({
type: SnippetType; type: SnippetType;
weight: IconStyle; weight: IconStyle;
}): boolean { }): boolean {
if (type !== SnippetType.HTML && type !== SnippetType.FLUTTER) return true; if (type !== SnippetType.FLUTTER) return true;
return weight !== IconStyle.DUOTONE; return weight !== IconStyle.DUOTONE;
} }