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",
"recoil": "^0.7.6",
"svg2png-converter": "^1.0.2",
"tinycolor": "^0.0.1",
"tinycolor2": "^1.4.2"
},
"devDependencies": {

View File

@@ -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 {

View File

@@ -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;
}

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 { 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<string, boolean>;
};
type BannerProps = {
id: string;
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 (
<div className="banner">
The 2022 Figma Community Awards are here!
<button className="main-button" onClick={handleClick}>
<Medal size={24} weight="fill" />
Vote for Phosphor
</button>
</div>
<AnimatePresence initial={true}>
{!seen && (
<motion.aside
className="card banner"
initial="initial"
animate="animate"
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;
}
#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;
}

View File

@@ -1,7 +1,7 @@
header {
width: 100%;
background-color: var(--yellow);
overflow: hidden;
position: relative;
background-color: var(--yellow);
}
.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 PaperClips } from "@/assets/paperclips-header-mobile.svg";
@@ -36,6 +38,22 @@ const handleScrollToIcons = () =>
const Header = (_: HeaderProps) => {
return (
<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="illustrations-top">
<MarkerPurple id="marker-purple" />

View File

@@ -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<SVGSVGElement>(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 (
<AnimatePresence initial={false}>
<AnimatePresence initial={true}>
{!!entry && (
<motion.aside
initial="initial"

View File

@@ -2,6 +2,7 @@
position: relative;
padding: 32px 16px;
/* min-height: 80vh; */
z-index: 1;
content-visibility: auto;
color: var(--foreground);
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 { motion } from "framer-motion";
import { IconEntry } from "@/lib";
import { selectionEntryAtom } from "@/state";
interface IconGridItemProps {
interface IconGridItemProps extends HTMLAttributes<HTMLDivElement> {
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}

View File

@@ -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;

View File

@@ -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";

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,
fallbackState: S | (() => S)
): [S, Dispatch<SetStateAction<S>>] {
): [S, Dispatch<SetStateAction<S>>, (partial: Partial<S>) => void] {
const [value, setValue] = useState<S>(() => {
let val = sessionStorage.getItem(STORAGE_KEY + key);
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());
return {
[SnippetType.HTML]: `<i class="ph-${name}${
[SnippetType.HTML]: `<i class="ph${
isDefaultWeight ? "" : `-${weight}`
}"></i>`,
} ph-${name}"></i>`,
[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;
}