feat(app): major refactorings and details footer updates

This commit is contained in:
rektdeckard
2023-02-11 13:58:33 -07:00
parent eba876b3ea
commit 345acafb45
33 changed files with 376 additions and 540 deletions

View File

@@ -1,3 +1,17 @@
:root {
--red: #ff6e60;
--blue: #397fff;
--yellow: #ffd171;
--purple: #925bff;
--eggplant: #35313d;
--neutral: #86838b;
--translucent: rgba(163, 159, 171, 0.1);
--scrim: rgba(255, 255, 255, 0.05);
--sheer: rgba(194, 186, 196, 0.25);
--soft: rgba(194, 186, 196, 0.7);
--shadow: rgba(0, 0, 0, 0.15);
}
body {
margin: 0px;
font-variant-ligatures: common-ligatures;
@@ -24,16 +38,14 @@ img {
pre,
code {
font-family: "IBM Plex Mono", "Courier New", monospace;
font-size: 14px;
font-size: 12px;
}
pre {
box-sizing: border-box;
padding: 20px 16px 20px 24px;
margin: 12px 0px;
/* background-color: white; */
margin: 0;
border-radius: 6px;
/* border: 1px solid #e1d4d7; */
font-size: 12x;
white-space: pre-wrap;
}
@@ -77,14 +89,6 @@ button.main-button:active {
box-shadow: 0 0 0 0 black;
}
button.main-button:focus {
outline: none;
}
/* button.main-button:not(:last-child) {
margin: 0 24px 24px 0;
} */
button.main-button svg {
margin-right: 12px;
}
@@ -117,11 +121,11 @@ a.main-link:hover:after {
}
.badge.new {
color: #ff6e60;
color: var(--red);
}
.badge.updated {
color: #397fff;
color: var(--blue);
}
.badge {
@@ -131,15 +135,15 @@ a.main-link:hover:after {
.card {
border-radius: 8px;
border: 2px solid rgba(163, 159, 171, 0.1);
border: 2px solid var(--translucent);
}
.card.dark {
color: white;
background-color: #413c48;
.primary {
color: var(--foreground);
background-color: var(--background);
}
.card.light {
color: rgb(53, 49, 61);
background-color: #f6f5f6;
.secondary {
color: var(--foreground-card);
background-color: var(--background-card);
}

View File

@@ -1,14 +1,19 @@
import React, { Suspense } from "react";
import { Fragment, Suspense, useMemo } from "react";
import { useRecoilValue } from "recoil";
import "./App.css";
import Header from "../Header/Header";
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";
import usePersistSettings from "../../hooks/usePersistSettings";
import Header from "@/components/Header";
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,
usePersistSettings,
useCSSVariables,
} from "@/hooks";
import { isDarkThemeSelector } from "@/state";
const errorFallback = <Notice message="Search error" />;
const waitingFallback = <Notice type="none" message="" />;
@@ -16,9 +21,23 @@ const waitingFallback = <Notice type="none" message="" />;
const App: React.FC<any> = () => {
useIconParameters();
usePersistSettings();
const isDark = useRecoilValue(isDarkThemeSelector);
const properties = useMemo(
() => ({
"--foreground": isDark ? "white" : "black",
"--foreground-card": isDark ? "white" : "#35313D",
"--background": isDark ? "#35313D" : "white",
"--background-card": isDark ? "#413c48" : "#f6f5f6",
}),
[isDark]
);
useCSSVariables(properties);
return (
<React.StrictMode>
<Fragment>
<Header />
<main>
<Toolbar />
@@ -29,7 +48,7 @@ const App: React.FC<any> = () => {
</ErrorBoundary>
</main>
<Footer />
</React.StrictMode>
</Fragment>
);
};

View File

@@ -4,7 +4,7 @@
padding: 24px;
color: white;
text-align: center;
background-color: #35313d;
background-color: var(--eggplant);
}
.banner .main-button {

View File

@@ -1,5 +1,5 @@
footer {
background-color: #925bff;
background-color: var(--purple);
}
#back-to-top-button {
@@ -8,6 +8,11 @@ footer {
margin: 0;
border-radius: 50%;
z-index: 2;
font-size: 56px;
}
#back-to-top-button svg {
margin-right: unset;
}
.container {
@@ -65,11 +70,7 @@ footer .links {
width: 56px;
height: 56px;
padding: 0;
}
#back-to-top-button svg {
width: 28px;
height: 28px;
font-size: 28px;
}
footer .links {
@@ -133,12 +134,6 @@ footer .links {
top: 276px;
}
/* #command {
position: absolute;
left: 532px;
top: 150px;
} */
.illustrations-footer {
display: initial;
position: absolute;

View File

@@ -1,30 +1,51 @@
import { Coffee, Heart } from "phosphor-react";
import { useRecoilValue } from "recoil";
import { motion, AnimatePresence, Variants } from "framer-motion";
import { Coffee, Heart, ArrowULeftUp } from "phosphor-react";
import Links from "@/components/Links/Links";
import { ReactComponent as UArrowUpLeft } from "@/assets/u-arrow-up-left.svg";
import { ReactComponent as MarkerGreen } from "@/assets/marker-green.svg";
import { ReactComponent as PostIt } from "@/assets/footer-mobile.svg";
import { useMediaQuery } from "@/hooks";
import { selectionEntryAtom } from "@/state";
import "./Footer.css";
type FooterProps = {};
const variants: Variants = {
initial: { y: 188 },
animate: { y: 0 },
exit: { y: 188 },
};
const Footer = (_: FooterProps) => {
const isMobile = useMediaQuery("(max-width: 719px)");
const isViewing = !!useRecoilValue(selectionEntryAtom);
return (
<footer>
<div className="container">
<button
id="back-to-top-button"
aria-label="back-to-top button"
className="main-button"
onClick={() => {
document
.getElementById("root")
?.scrollIntoView({ behavior: "smooth", block: "start" });
}}
>
<UArrowUpLeft />
</button>
<AnimatePresence initial={false}>
{(!isMobile || !isViewing) && (
<motion.button
id="back-to-top-button"
aria-label="back-to-top button"
className="main-button"
variants={variants}
initial="initial"
animate="animate"
exit="exit"
transition={{ duration: 0.1 }}
onClick={() => {
document
.getElementById("root")
?.scrollIntoView({ behavior: "smooth", block: "start" });
}}
>
<ArrowULeftUp size="1em" />
</motion.button>
)}
</AnimatePresence>
<div className="outro">
<Links />
<p>

View File

@@ -1,6 +1,6 @@
header {
width: 100%;
background-color: #ffd171;
background-color: var(--yellow);
overflow: hidden;
}

View File

@@ -4,11 +4,11 @@ import { useHotkeys } from "react-hotkeys-hook";
import { motion, AnimatePresence, Variants } from "framer-motion";
import { Svg2Png } from "svg2png-converter";
import { saveAs } from "file-saver";
import { Copy, CheckCircle, DownloadSimple } from "phosphor-react";
import { Copy, CheckCircle, DownloadSimple, XCircle } from "phosphor-react";
import ReactGA from "react-ga4";
import Tabs, { Tab } from "@/components/Tabs";
import { useTransientState } from "@/hooks";
import { useMediaQuery, useTransientState, useSessionState } from "@/hooks";
import { SnippetType } from "@/lib";
import {
iconWeightAtom,
@@ -21,10 +21,17 @@ import { getCodeSnippets, supportsWeight } from "@/utils";
import TagCloud from "./TagCloud";
const variants: Variants = {
initial: { y: 188 },
animate: { y: 0 },
exit: { y: 188 },
const variants: Record<string, Variants> = {
desktop: {
initial: { y: 188 },
animate: { y: 0 },
exit: { y: 188 },
},
mobile: {
initial: { y: "60vh" },
animate: { y: 0 },
exit: { y: "60vh" },
},
};
const RENDERED_SNIPPETS = [
@@ -32,12 +39,20 @@ const RENDERED_SNIPPETS = [
SnippetType.VUE,
SnippetType.HTML,
SnippetType.FLUTTER,
SnippetType.ELM,
];
const buttonColor = "#35313D";
const successColor = "#1FA647";
const disabledColor = "#B7B7B7";
function cloneWithSize(svg: SVGSVGElement, size: number): SVGSVGElement {
const sized = svg.cloneNode(true) as SVGSVGElement;
sized.setAttribute("width", `${size}`);
sized.setAttribute("height", `${size}`);
return sized;
}
const DetailFooter = () => {
const [entry, setSelectionEntry] = useRecoilState(selectionEntryAtom);
@@ -51,6 +66,10 @@ const DetailFooter = () => {
);
const ref = useRef<SVGSVGElement>(null);
const [{ i }, setInitialTab] = useSessionState("tab", { i: 0 });
const isMobile = useMediaQuery("(max-width: 719px)");
const [snippets, tabs] = useMemo<
[Partial<Record<SnippetType, string>>, Tab[]]
>(() => {
@@ -67,7 +86,7 @@ const DetailFooter = () => {
const snippetButtonStyle: CSSProperties =
weight === "duotone"
? { color: disabledColor, userSelect: "none" }
: { color: buttonColor };
: { color: "currentcolor" };
const tabs = [
{
@@ -104,7 +123,11 @@ const DetailFooter = () => {
title="Copy snippet"
onClick={(e) => handleCopySnippet(e, type)}
disabled={!isWeightSupported}
style={isWeightSupported ? undefined : snippetButtonStyle}
style={
isWeightSupported
? { color: "currentColor" }
: snippetButtonStyle
}
>
{copied === type ? (
<CheckCircle size={24} color={successColor} weight="fill" />
@@ -113,7 +136,7 @@ const DetailFooter = () => {
size={24}
color={
isWeightSupported
? buttonColor
? "currentColor"
: snippetButtonStyle.color
}
weight="fill"
@@ -128,7 +151,7 @@ const DetailFooter = () => {
);
return [snippets, tabs];
}, [entry, weight, copied, isDark]);
}, [entry, weight, size, copied, isDark]);
useHotkeys("esc", () => setSelectionEntry(null));
@@ -163,9 +186,10 @@ const DetailFooter = () => {
) => {
event.currentTarget.blur();
if (!entry) return;
if (!ref.current) return;
navigator.clipboard?.writeText(cloneWithSize(ref.current, size).outerHTML);
setCopied("SVG");
ref.current && void navigator.clipboard?.writeText(ref.current.outerHTML);
};
const handleDownloadSVG = (
@@ -173,9 +197,9 @@ const DetailFooter = () => {
) => {
event.currentTarget.blur();
if (!entry) return;
if (!ref.current?.outerHTML) return;
if (!ref.current) return;
const blob = new Blob([ref.current.outerHTML]);
const blob = new Blob([cloneWithSize(ref.current, size).outerHTML]);
saveAs(
blob,
`${entry?.name}${weight === "regular" ? "" : `-${weight}`}.svg`
@@ -187,12 +211,11 @@ const DetailFooter = () => {
) => {
event.currentTarget.blur();
if (!entry) return;
if (!ref.current?.outerHTML) return;
if (!ref.current) return;
Svg2Png.save(
ref.current,
`${entry?.name}${weight === "regular" ? "" : `-${weight}`}.png`,
{ scaleX: 2.667, scaleY: 2.667 }
cloneWithSize(ref.current, size),
`${entry?.name}${weight === "regular" ? "" : `-${weight}`}.png`
);
};
@@ -203,9 +226,9 @@ const DetailFooter = () => {
initial="initial"
animate="animate"
exit="exit"
variants={variants}
className={`detail-footer card ${isDark ? "dark" : "light"}`}
transition={{ duration: 0.1 }}
variants={isMobile ? variants.mobile : variants.desktop}
className="secondary detail-footer card"
transition={isMobile ? { duration: 0.25 } : { duration: 0.1 }}
>
<div className="detail-preview">
<figure>
@@ -223,14 +246,16 @@ const DetailFooter = () => {
style={buttonBarStyle}
onClick={handleDownloadPNG}
>
<DownloadSimple size={24} color="currentColor" weight="fill" /> PNG
<DownloadSimple size={24} color="currentColor" weight="fill" />{" "}
PNG
</button>
<button
tabIndex={0}
style={buttonBarStyle}
onClick={handleDownloadSVG}
>
<DownloadSimple size={24} color="currentColor" weight="fill" /> SVG
<DownloadSimple size={24} color="currentColor" weight="fill" />{" "}
SVG
</button>
<button
tabIndex={0}
@@ -247,7 +272,22 @@ const DetailFooter = () => {
</div>
</div>
<Tabs tabs={tabs} />
<Tabs
tabs={tabs}
initialIndex={i}
onTabChange={(i) => setInitialTab({ i })}
/>
<button
tabIndex={0}
className="close-button"
onClick={() => setSelectionEntry(null)}
onKeyDown={(e) => {
e.key === "Enter" && setSelectionEntry(null);
}}
>
<XCircle color="currentColor" size={28} weight="fill" />
</button>
</motion.aside>
)}
</AnimatePresence>

View File

@@ -1,256 +0,0 @@
import React, { useRef, useEffect, CSSProperties } from "react";
import { useRecoilValue, useSetRecoilState } from "recoil";
import { useHotkeys } from "react-hotkeys-hook";
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-ga4";
import { useTransientState } from "@/hooks";
import { IconEntry, SnippetType } from "@/lib";
import {
iconWeightAtom,
iconSizeAtom,
iconColorAtom,
iconPreviewOpenAtom,
} from "@/state";
import { getCodeSnippets, supportsWeight } from "@/utils";
import TagCloud from "./TagCloud";
const panelVariants = {
open: {
opacity: 1,
height: "100%",
marginTop: "4px",
marginBottom: "4px",
transition: { type: "tween", duration: 0.1 },
},
collapsed: {
opacity: 0,
height: "0px",
marginTop: "0px",
marginBottom: "0px",
transition: { type: "tween", duration: 0.1 },
},
};
const contentVariants = {
open: { opacity: 1, transition: { duration: 0.2, delay: 0.1 } },
collapsed: { opacity: 0, transition: { duration: 0 } },
};
const buttonColor = "#35313D";
const successColor = "#1FA647";
const disabledColor = "#B7B7B7";
interface InfoPanelProps {
index: number;
spans: number;
isDark: boolean;
entry: IconEntry;
}
const renderedSnippets = [
SnippetType.REACT,
SnippetType.VUE,
SnippetType.HTML,
SnippetType.FLUTTER,
];
const DetailsPanel = (props: InfoPanelProps) => {
const { index, spans, isDark, entry } = props;
const { name, Icon, categories, tags } = entry;
const weight = useRecoilValue(iconWeightAtom);
const size = useRecoilValue(iconSizeAtom);
const color = useRecoilValue(iconColorAtom);
const setOpen = useSetRecoilState(iconPreviewOpenAtom);
const [copied, setCopied] = useTransientState<SnippetType | "SVG" | false>(
false,
2000
);
const ref = useRef<SVGSVGElement>(null);
useHotkeys("esc", () => setOpen(false));
useEffect(
() => ReactGA.event({ category: "Grid", action: "Details", label: name }),
[name]
);
const buttonBarStyle: CSSProperties = {
color: isDark ? "white" : buttonColor,
};
const snippetButtonStyle: CSSProperties =
weight === "duotone"
? { color: disabledColor, userSelect: "none" }
: { color: buttonColor };
const snippets = getCodeSnippets({
displayName: Icon.displayName!,
name,
weight,
size,
color,
});
const handleCopySnippet = (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>,
type: SnippetType
) => {
event.currentTarget.blur();
setCopied(type);
const data = snippets[type];
data && void navigator.clipboard?.writeText(data);
};
const handleCopySVG = (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => {
event.currentTarget.blur();
setCopied("SVG");
ref.current && void navigator.clipboard?.writeText(ref.current.outerHTML);
};
const handleDownloadSVG = (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => {
event.currentTarget.blur();
if (!ref.current?.outerHTML) return;
const blob = new Blob([ref.current.outerHTML]);
saveAs(blob, `${name}${weight === "regular" ? "" : `-${weight}`}.svg`);
};
const handleDownloadPNG = async (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => {
event.currentTarget.blur();
if (!ref.current?.outerHTML) return;
Svg2Png.save(
ref.current,
`${name}${weight === "regular" ? "" : `-${weight}`}.png`,
{ scaleX: 2.667, scaleY: 2.667 }
);
};
return (
<motion.section
className="info-box"
animate="open"
exit="collapsed"
variants={panelVariants}
style={{
order: index + (spans - (index % spans)),
color: isDark ? "white" : "black",
}}
>
<motion.div
initial="collapsed"
animate="open"
exit="collapsed"
variants={contentVariants}
className="icon-preview"
>
<Icon ref={ref} color={color} weight={weight} size={192} />
<p className="name">{name}</p>
<p className="versioning">in &ge; {entry.published_in.toFixed(1)}.0</p>
<TagCloud
name={name}
tags={Array.from(
new Set<string>([...categories, ...name.split("-"), ...tags])
)}
isDark={isDark}
/>
</motion.div>
<motion.div
initial="collapsed"
animate="open"
exit="collapsed"
variants={contentVariants}
className="icon-usage"
>
{renderedSnippets.map((type) => {
const isWeightSupported = supportsWeight({ type, weight });
return (
<div className="snippet" key={type}>
{type}
<pre
tabIndex={0}
style={isWeightSupported ? undefined : snippetButtonStyle}
>
<span>
{isWeightSupported
? snippets[type]
: "This weight is not yet supported"}
</span>
<button
title="Copy snippet"
onClick={(e) => handleCopySnippet(e, type)}
disabled={!isWeightSupported}
style={isWeightSupported ? undefined : snippetButtonStyle}
>
{copied === type ? (
<CheckCircle size={24} color={successColor} weight="fill" />
) : (
<Copy
size={24}
color={
isWeightSupported
? buttonColor
: snippetButtonStyle.color
}
weight="fill"
/>
)}
</button>
</pre>
</div>
);
})}
<div className="button-row">
<button style={buttonBarStyle} onClick={handleDownloadPNG}>
<Download size={32} color="currentColor" weight="fill" /> Download
PNG
</button>
<button style={buttonBarStyle} onClick={handleDownloadSVG}>
<Download size={32} color="currentColor" weight="fill" /> Download
SVG
</button>
<button style={buttonBarStyle} onClick={handleCopySVG}>
{copied === "SVG" ? (
<CheckCircle size={32} color={successColor} weight="fill" />
) : (
<Copy size={32} color="currentColor" weight="fill" />
)}
{copied === "SVG" ? "Copied!" : "Copy SVG"}
</button>
</div>
</motion.div>
<motion.span
initial="collapsed"
animate="open"
exit="collapsed"
variants={contentVariants}
title="Close"
>
<X
className="close-icon"
tabIndex={0}
color={buttonBarStyle.color}
size={32}
weight="fill"
onClick={() => setOpen(false)}
onKeyDown={(e) => {
e.key === "Enter" && setOpen(false);
}}
/>
</motion.span>
</motion.section>
);
};
export default DetailsPanel;

View File

@@ -3,6 +3,8 @@
padding: 32px 16px;
/* min-height: 80vh; */
content-visibility: auto;
color: var(--foreground);
background-color: var(--background);
}
.grid {
@@ -27,22 +29,21 @@
-webkit-user-select: none;
user-select: none;
cursor: pointer;
/* transition: background-color 100ms ease; */
}
.grid-item:hover {
background-color: rgba(163, 159, 171, 0.1);
background-color: var(--translucent);
}
.grid-item:focus {
outline: none;
border: 2px solid rgba(163, 159, 171, 0.1);
border: 2px solid var(--translucent);
}
.grid-item p {
font-size: 12px;
line-height: 16px;
color: #86838b;
color: var(--neutral);
margin-top: 12px;
text-align: center;
}
@@ -65,61 +66,12 @@
}
}
.info-box {
position: relative;
display: flex;
width: 100%;
height: 0px;
margin: 0 4px;
border-radius: 16px;
background-color: rgba(163, 159, 171, 0.1);
}
@media screen and (max-width: 1023px) {
.icon-preview {
display: none !important;
}
.icon-usage {
padding-left: 10% !important;
}
.snippet pre {
padding: 12px 8px 12px 20px;
}
}
.icon-preview {
width: 30%;
display: flex;
text-align: center;
flex-direction: column;
align-items: center;
margin-top: 72px;
}
.icon-preview p {
margin: 0;
font-size: 12px;
line-height: 16px;
}
.icon-preview > p.name {
font-size: 16px;
}
.versioning {
margin-top: 2px;
opacity: 0.6;
}
.icon-usage {
flex: 1;
padding: 56px 10% 56px 0;
}
.snippet {
/* margin-bottom: 24px; */
width: 100%;
}
@@ -127,7 +79,6 @@
display: flex;
align-items: center;
text-overflow: ellipsis;
/* color: black; */
-moz-user-select: all;
-webkit-user-select: all;
user-select: all;
@@ -182,12 +133,30 @@
.close-icon {
position: absolute;
top: 24px;
right: 24px;
top: 12px;
right: 12px;
text-align: end;
cursor: pointer;
}
.close-button {
color: inherit;
background: var(--background);
height: unset !important;
padding: 0 !important;
margin: 0 !important;
border-radius: 48px !important;
position: absolute;
top: -14px;
right: -18px;
text-align: end;
cursor: pointer;
}
.close-button:active {
opacity: 0.7;
}
.empty-list {
display: flex;
flex-direction: column;
@@ -256,3 +225,18 @@ figcaption > p {
align-items: center;
gap: 8px;
}
@media screen and (max-width: 719px) {
.close-button {
top: 4px;
right: 12px;
}
aside.detail-footer {
top: 16px;
bottom: -4px;
display: flex;
flex-direction: column;
height: 60vh;
}
}

View File

@@ -1,4 +1,4 @@
import { useRef, useEffect, CSSProperties } from "react";
import { useRef, useEffect } from "react";
import { useRecoilValue } from "recoil";
import { motion, useAnimation } from "framer-motion";
import { IconContext } from "phosphor-react";
@@ -10,7 +10,6 @@ import {
filteredQueryResultsSelector,
isDarkThemeSelector,
} from "@/state";
import useGridSpans from "@/hooks/useGridSpans";
import Notice from "@/components/Notice";
import DetailFooter from "./DetailFooter";
@@ -28,13 +27,6 @@ const defaultSearchTags = [
"weather",
];
const gridStyle: Record<string, CSSProperties> = {
light: {},
dark: {
backgroundColor: "#35313D",
},
} as const;
type IconGridProps = {};
const IconGrid = (_: IconGridProps) => {
@@ -42,8 +34,6 @@ const IconGrid = (_: IconGridProps) => {
const size = useRecoilValue(iconSizeAtom);
const color = useRecoilValue(iconColorAtom);
const isDark = useRecoilValue(isDarkThemeSelector);
const spans = useGridSpans();
const filteredQueryResults = useRecoilValue(filteredQueryResultsSelector);
const originOffset = useRef({ top: 0, left: 0 });
@@ -63,17 +53,13 @@ const IconGrid = (_: IconGridProps) => {
return (
<IconContext.Provider value={{ weight, size, color, mirrored: false }}>
<div
className="grid-container"
style={isDark ? gridStyle.dark : gridStyle.light}
>
<div className="grid-container">
<i id="beacon" className="beacon" />
<motion.div className="grid" initial="hidden" animate={controls}>
{filteredQueryResults.map((iconEntry, index) => (
<IconGridItem
key={index}
index={index}
spans={spans}
isDark={isDark}
entry={iconEntry}
originOffset={originOffset}

View File

@@ -7,7 +7,6 @@ import { selectionEntryAtom } from "@/state";
interface IconGridItemProps {
index: number;
spans: number;
isDark: boolean;
entry: IconEntry;
originOffset: MutableRefObject<{ top: number; left: number }>;
@@ -68,11 +67,8 @@ const IconGridItem = (props: IconGridItemProps) => {
className="grid-item"
key={name}
ref={ref}
tabIndex={1}
style={{
order: index,
backgroundColor: isOpen ? "rgba(163, 159, 171, 0.1)" : undefined,
}}
tabIndex={0}
style={isOpen ? { backgroundColor: "var(--translucent)" } : undefined}
custom={delayRef}
transition={transition}
variants={itemVariants}

View File

@@ -2,31 +2,27 @@
display: flex;
flex-wrap: wrap;
justify-content: center;
/* padding: 24px; */
}
button.tag-button {
margin: 4px;
border-radius: 4px;
background-color: rgba(194, 186, 196, 0.25);
background-color: var(--sheer);
outline: none;
cursor: pointer;
transition: background-color 200ms ease, box-shadow 200ms ease;
color: var(--foreground);
}
button.tag-button:hover {
background-color: rgba(194, 186, 196, 0.7);
background-color: var(--soft);
}
button.tag-button:focus {
box-shadow: 0 0 0 1px rgba(194, 186, 196, 0.7);
button.tag-button:focus-visible {
box-shadow: 0 0 0 1px var(--soft);
}
.tag-button code {
padding: 4px;
font-size: 12px;
}
.dark {
color: white;
}

View File

@@ -1,6 +1,7 @@
import { useCallback } from "react";
import { useSetRecoilState } from "recoil";
import { useMediaQuery } from "@/hooks";
import { searchQueryAtom } from "@/state";
import "./TagCloud.css";
@@ -11,13 +12,14 @@ interface TagCloudProps {
}
const TagCloud = ({ name, tags, isDark }: TagCloudProps) => {
const isMobile = useMediaQuery("(max-width: 719px)");
const setQuery = useSetRecoilState(searchQueryAtom);
const handleTagClick = useCallback(
(tag: string) => {
setQuery(tag);
document.getElementById("search-input")?.focus();
!isMobile && document.getElementById("search-input")?.focus();
},
[setQuery]
[setQuery, isMobile]
);
return (
@@ -28,7 +30,7 @@ const TagCloud = ({ name, tags, isDark }: TagCloudProps) => {
className="tag-button"
onClick={() => void handleTagClick(tag)}
>
<code className={`${isDark ? "dark" : ""}`}>{tag}</code>
<code>{tag}</code>
{tag === "*new*" && <span className="badge new"></span>}
{tag === "*updated*" && <span className="badge updated"></span>}
</button>

View File

@@ -3,7 +3,7 @@ import { motion } from "framer-motion";
import { useRecoilValue } from "recoil";
import { HourglassMedium, Question, SmileyXEyes } from "phosphor-react";
import { searchQueryAtom, isDarkThemeSelector } from "@/state";
import { searchQueryAtom } from "@/state";
interface NoticeProps {
message?: string;
@@ -12,11 +12,10 @@ interface NoticeProps {
}
const Notice = ({ message, type = "warn", children }: NoticeProps) => {
const isDark = useRecoilValue(isDarkThemeSelector);
const query = useRecoilValue(searchQueryAtom);
return (
<div style={isDark ? { backgroundColor: "#35313D", color: "white" } : {}}>
<div className="primary">
<motion.div
className="empty-list"
initial={{ opacity: 0 }}

View File

@@ -6,7 +6,7 @@
padding: 0 24px;
border-radius: 8px;
color: white;
background-color: rgba(255, 255, 255, 0.05);
background-color: var(--scrim);
}
.search-bar:focus-within {

View File

@@ -84,8 +84,8 @@ const SearchInput = (_: SearchInputProps) => {
value={value}
placeholder="Search"
onChange={({ currentTarget }) => setValue(currentTarget.value)}
onKeyPress={({ currentTarget, key }) =>
key === "Enter" && currentTarget.blur()
onKeyDown={({ currentTarget, key }) =>
(key === "Enter" || key === "Escape") && currentTarget.blur()
}
/>
{!value && !isMobile && <Keys>{isApple ? <Command /> : "Ctrl + "}K</Keys>}

View File

@@ -1,17 +1,13 @@
button.action-button {
background-color: rgba(255, 255, 255, 0.05);
background-color: var(--scrim);
color: white;
padding: 8px;
border-radius: 8px;
cursor: pointer;
}
button.action-button:hover {
background-color: rgba(255, 255, 255, 0.1);
}
button.action-button:active {
background-color: rgba(255, 255, 255, 0.2);
background-color: var(--sheer);
}
@media screen and (max-width: 558px) {

View File

@@ -7,7 +7,7 @@
padding: 0 24px;
color: white;
border-radius: 8px;
background-color: rgba(255, 255, 255, 0.05);
background-color: var(--scrim);
font-family: "Manrope", sans-serif;
font-size: 16px;
}
@@ -68,12 +68,12 @@
outline: none;
width: 24px; /* Set a specific slider handle width */
height: 24px; /* Slider handle height */
box-shadow: 0 0 0 6px rgba(255, 255, 255, 0.2);
box-shadow: 0 0 0 6px var(--sheer);
}
.size-bar input:focus::-webkit-slider-thumb {
outline: none;
width: 24px; /* Set a specific slider handle width */
height: 24px; /* Slider handle height */
box-shadow: 0 0 0 6px rgba(255, 255, 255, 0.2);
box-shadow: 0 0 0 6px var(--sheer);
}

View File

@@ -1,20 +1,3 @@
/* .style-select {
position: relative;
}
.style-select {
background-color: gold;
border-radius: 24px;
box-shadow: 4px 4px #ccc;
display: none;
}
.style-select option {
background-color: gold;
border-radius: 24px;
display: none;
} */
.react-dropdown-select {
width: 176px !important;
height: 48px !important;
@@ -22,7 +5,7 @@
padding: 0 24px !important;
color: white;
border-radius: 8px !important;
background-color: rgba(255, 255, 255, 0.05);
background-color: var(--scrim);
font-size: 16px;
border: none !important;
}
@@ -50,19 +33,6 @@
box-shadow: none !important;
}
/* .react-dropdown-select-type-single {
height: 100% !important;
} */
/* .react-dropdown-select-clear,
.react-dropdown-select-dropdown-handle {
color: #fff;
} */
/* .react-dropdown-select-option {
border: 1px solid #000;
} */
.react-dropdown-select-item {
color: #333;
height: 40px !important;
@@ -89,25 +59,24 @@
max-height: 300px;
overflow: auto;
z-index: 9;
/* background: rgb(29, 20, 20) !important; */
box-shadow: none;
}
.react-dropdown-select-item {
color: black;
}
.react-dropdown-select-item:hover {
background-color: #ffd171 !important;
background-color: var(--yellow) !important;
}
.react-dropdown-select-item.react-dropdown-select-item-selected,
.react-dropdown-select-item.react-dropdown-select-item-active {
color: black !important;
background-color: #ffd171 !important;
background-color: var(--yellow) !important;
}
.react-dropdown-select-item:focus {
color: black !important;
background-color: #ffd171 !important;
background-color: var(--yellow) !important;
}
.react-dropdown-select-item.react-dropdown-select-item-disabled {

View File

@@ -20,21 +20,38 @@ button.tab {
border-top-right-radius: 8px;
}
button.tab:focus-within {
/* background-color: var(--tabs-background); */
button.tab:focus-visible {
outline: 1px solid currentColor;
}
.tab.active {
background-color: var(--tabs-background);
button.tab:hover:not(.active) {
background-color: var(--sheer);
}
button.tab.active {
background-color: var(--background);
border-bottom: none;
}
.tab-content {
flex: 1;
padding: 8px 16px;
height: 80px;
max-height: 80px;
padding: 16px;
display: grid;
place-items: center;
border-radius: 8px;
background-color: var(--tabs-background);
background-color: var(--background);
overflow-y: auto;
}
@media screen and (max-width: 719px) {
.tabs {
flex: 1;
}
.tab-content {
height: unset;
max-height: unset;
}
}

View File

@@ -1,7 +1,4 @@
import { CSSProperties, ReactNode, useState } from "react";
import { useRecoilValue } from "recoil";
import { isDarkThemeSelector } from "@/state";
import "./Tabs.css";
@@ -12,41 +9,32 @@ export type Tab = {
type TabsProps = {
tabs: Tab[];
initialIndex?: number;
onTabChange?: (index: number) => void;
};
type CSSCustomPropertyName = `--${string}`;
type CSSCustomProperties = {
[property: CSSCustomPropertyName]: string;
};
const colorStyles: Record<string, CSSProperties & CSSCustomProperties> = {
light: { "--tabs-background": "white" },
dark: { "--tabs-background": "rgba(194, 186, 196, 0.25)" },
} as const;
const contentStyles: Record<string, CSSProperties> = {
activeLeft: { borderTopLeftRadius: 0 },
activeRight: { borderTopRightRadius: 0 },
} as const;
const Tabs = ({ tabs }: TabsProps) => {
const [activeIndex, setActiveIndex] = useState<number>(0);
const isDark = useRecoilValue(isDarkThemeSelector);
const Tabs = ({ tabs, initialIndex = 0, onTabChange }: TabsProps) => {
const [activeIndex, setActiveIndex] = useState<number>(
!!tabs[initialIndex] ? initialIndex : 0
);
return (
<div
className="tabs"
tabIndex={0}
style={isDark ? colorStyles.dark : colorStyles.light}
>
<div className="secondary tabs" tabIndex={0}>
<div className="tabs-header">
{tabs.map((tab, i) => (
<button
key={i}
tabIndex={0}
className={`tab ${activeIndex === i ? "active" : ""}`}
onClick={() => setActiveIndex(i)}
onClick={() => {
setActiveIndex(i);
onTabChange?.(i);
}}
>
{tab.header}
</button>

View File

@@ -4,12 +4,12 @@ nav.toolbar {
top: -1px;
padding: 0;
margin: 0;
background-color: #35313d;
background-color: var(--eggplant);
z-index: 1;
display: flex;
justify-content: center;
align-items: center;
box-shadow: 0 2px 0 0 rgba(0, 0, 0, 0.15);
box-shadow: 0 2px 0 0 var(--shadow);
}
.toolbar-contents {