feat(app): new details footer appearance

This commit is contained in:
rektdeckard
2023-02-05 23:09:20 -07:00
parent 3756374140
commit eba876b3ea
19 changed files with 220 additions and 95 deletions

View File

@@ -31,9 +31,9 @@ pre {
box-sizing: border-box;
padding: 20px 16px 20px 24px;
margin: 12px 0px;
background-color: white;
/* background-color: white; */
border-radius: 6px;
border: 1px solid #e1d4d7;
/* border: 1px solid #e1d4d7; */
white-space: pre-wrap;
}
@@ -95,17 +95,6 @@ button.main-button svg {
/* gap: 24px; */
}
figure {
margin: 0;
display: grid;
place-items: center;
}
figcaption {
font-size: 14px;
text-align: center;
}
a.main-link {
text-decoration: none;
position: relative;

View File

@@ -1,9 +1,8 @@
import { useCallback } from "react";
import { useRecoilState, useRecoilValue } from "recoil";
import { iconColorAtom } from "@/state/atoms";
import { isDarkThemeSelector } from "@/state/selectors";
import useThrottled from "@/hooks/useThrottled";
import { useThrottled } from "@/hooks";
import { iconColorAtom, isDarkThemeSelector } from "@/state";
import "./ColorInput.css";

View File

@@ -67,7 +67,7 @@ footer .links {
padding: 0;
}
#back-to-top-button img {
#back-to-top-button svg {
width: 28px;
height: 28px;
}

View File

@@ -4,27 +4,27 @@ 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, Download } from "phosphor-react";
import { Copy, CheckCircle, DownloadSimple } from "phosphor-react";
import ReactGA from "react-ga4";
import Tabs, { Tab } from "@/components/Tabs";
import { useTransientState } from "@/hooks";
import { SnippetType } from "@/lib";
import {
iconWeightAtom,
iconSizeAtom,
iconColorAtom,
selectionEntryAtom,
} from "@/state/atoms";
import { isDarkThemeSelector } from "@/state/selectors";
import Tabs, { Tab } from "@/components/Tabs";
import useTransientState from "@/hooks/useTransientState";
import { SnippetType } from "@/lib";
isDarkThemeSelector,
} from "@/state";
import { getCodeSnippets, supportsWeight } from "@/utils";
import TagCloud from "./TagCloud";
const variants: Variants = {
initial: { opacity: 0 },
animate: { opacity: 1 },
exit: { opacity: 0 },
initial: { y: 188 },
animate: { y: 0 },
exit: { y: 188 },
};
const RENDERED_SNIPPETS = [
@@ -94,10 +94,7 @@ const DetailFooter = () => {
header: type,
content: (
<div className="snippet" key={type}>
<pre
tabIndex={0}
style={isWeightSupported ? undefined : snippetButtonStyle}
>
<pre style={isWeightSupported ? undefined : snippetButtonStyle}>
<span>
{isWeightSupported
? snippets[type]
@@ -213,31 +210,44 @@ const DetailFooter = () => {
<div className="detail-preview">
<figure>
<entry.Icon ref={ref} size={64}></entry.Icon>
<figcaption>{entry.name}</figcaption>
<figcaption>
<p>{entry.name}</p>
<small className="versioning">
available in v{entry.published_in.toFixed(1)}.0+
</small>
</figcaption>
</figure>
<small className="versioning">
in &ge; {entry.published_in.toFixed(1)}.0
</small>
<div className="detail-actions">
<button
tabIndex={0}
style={buttonBarStyle}
onClick={handleDownloadPNG}
>
<DownloadSimple size={24} color="currentColor" weight="fill" /> PNG
</button>
<button
tabIndex={0}
style={buttonBarStyle}
onClick={handleDownloadSVG}
>
<DownloadSimple size={24} color="currentColor" weight="fill" /> SVG
</button>
<button
tabIndex={0}
style={buttonBarStyle}
onClick={handleCopySVG}
>
{copied === "SVG" ? (
<CheckCircle size={24} color={successColor} weight="fill" />
) : (
<Copy size={24} color="currentColor" weight="fill" />
)}
{copied === "SVG" ? "Copied!" : " SVG"}
</button>
</div>
</div>
<Tabs tabs={tabs} />
<div className="detail-actions">
<button style={buttonBarStyle} onClick={handleDownloadPNG}>
<Download size={24} color="currentColor" weight="fill" /> Download
PNG
</button>
<button style={buttonBarStyle} onClick={handleDownloadSVG}>
<Download size={24} color="currentColor" weight="fill" /> Download
SVG
</button>
<button style={buttonBarStyle} onClick={handleCopySVG}>
{copied === "SVG" ? (
<CheckCircle size={24} color={successColor} weight="fill" />
) : (
<Copy size={24} color="currentColor" weight="fill" />
)}
{copied === "SVG" ? "Copied!" : "Copy SVG"}
</button>
</div>
</motion.aside>
)}
</AnimatePresence>

View File

@@ -7,14 +7,14 @@ 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/atoms";
import useTransientState from "@/hooks/useTransientState";
import { IconEntry, SnippetType } from "@/lib";
} from "@/state";
import { getCodeSnippets, supportsWeight } from "@/utils";
import TagCloud from "./TagCloud";

View File

@@ -119,14 +119,15 @@
}
.snippet {
margin-bottom: 24px;
/* margin-bottom: 24px; */
width: 100%;
}
.snippet pre {
display: flex;
align-items: center;
text-overflow: ellipsis;
color: black;
/* color: black; */
-moz-user-select: all;
-webkit-user-select: all;
user-select: all;
@@ -218,16 +219,40 @@ aside.detail-footer {
margin: auto;
max-width: 1120px;
display: grid;
grid-template-columns: 144px 1fr 160px;
grid-template-columns: 232px 1fr;
gap: 24px;
padding: 12px 24px;
height: 136px;
}
figure {
margin: 0;
display: grid;
grid-template-columns: 64px 1fr;
gap: 24px;
align-items: center;
}
figcaption {
display: flex;
flex-direction: column;
font-size: 14px;
}
figcaption > p {
margin: 0;
}
.detail-preview {
display: flex;
flex-direction: column;
align-items: center;
padding: 16px;
justify-content: center;
gap: 24px;
}
.detail-actions {
padding: 16px;
display: flex;
flex-direction: row;
align-items: center;
gap: 8px;
}

View File

@@ -1,13 +1,15 @@
import { useRef, useEffect } from "react";
import { useRef, useEffect, CSSProperties } from "react";
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,
filteredQueryResultsSelector,
isDarkThemeSelector,
} from "@/state/selectors";
} from "@/state";
import useGridSpans from "@/hooks/useGridSpans";
import Notice from "@/components/Notice";
@@ -26,6 +28,13 @@ const defaultSearchTags = [
"weather",
];
const gridStyle: Record<string, CSSProperties> = {
light: {},
dark: {
backgroundColor: "#35313D",
},
} as const;
type IconGridProps = {};
const IconGrid = (_: IconGridProps) => {
@@ -56,15 +65,10 @@ const IconGrid = (_: IconGridProps) => {
<IconContext.Provider value={{ weight, size, color, mirrored: false }}>
<div
className="grid-container"
style={{ backgroundColor: isDark ? "#35313D" : "" }}
style={isDark ? gridStyle.dark : gridStyle.light}
>
<i id="beacon" className="beacon" />
<motion.div
className="grid"
initial="hidden"
animate={controls}
variants={{}}
>
<motion.div className="grid" initial="hidden" animate={controls}>
{filteredQueryResults.map((iconEntry, index) => (
<IconGridItem
key={index}

View File

@@ -1,11 +1,9 @@
import { useRef, useLayoutEffect, useEffect, MutableRefObject } from "react";
import { useRecoilState } from "recoil";
import { motion, AnimatePresence } from "framer-motion";
import { motion } from "framer-motion";
import { IconEntry } from "@/lib";
import { iconPreviewOpenAtom, selectionEntryAtom } from "@/state/atoms";
import DetailsPanel from "./DetailsPanel";
import { selectionEntryAtom } from "@/state";
interface IconGridItemProps {
index: number;
@@ -70,7 +68,7 @@ const IconGridItem = (props: IconGridItemProps) => {
className="grid-item"
key={name}
ref={ref}
tabIndex={0}
tabIndex={1}
style={{
order: index,
backgroundColor: isOpen ? "rgba(163, 159, 171, 0.1)" : undefined,
@@ -88,9 +86,6 @@ const IconGridItem = (props: IconGridItemProps) => {
{isUpdated && <span className="badge updated"></span>}
</p>
</motion.div>
{/* <AnimatePresence initial={false}>
{isOpen && <DetailsPanel {...props} />}
</AnimatePresence> */}
</>
);
};

View File

@@ -1,7 +1,7 @@
import { useCallback } from "react";
import { useSetRecoilState } from "recoil";
import { searchQueryAtom } from "@/state/atoms";
import { searchQueryAtom } from "@/state";
import "./TagCloud.css";
interface TagCloudProps {

View File

@@ -3,8 +3,7 @@ import { motion } from "framer-motion";
import { useRecoilValue } from "recoil";
import { HourglassMedium, Question, SmileyXEyes } from "phosphor-react";
import { isDarkThemeSelector } from "@/state/selectors";
import { searchQueryAtom } from "@/state/atoms";
import { searchQueryAtom, isDarkThemeSelector } from "@/state";
interface NoticeProps {
message?: string;

View File

@@ -11,7 +11,7 @@ import { useHotkeys } from "react-hotkeys-hook";
import { Command, MagnifyingGlass, X, HourglassHigh } from "phosphor-react";
import ReactGA from "react-ga4";
import { searchQueryAtom } from "@/state/atoms";
import { searchQueryAtom } from "@/state";
import "./SearchInput.css";
const apple = /iPhone|iPod|iPad|Macintosh|MacIntel|MacPPC/i;

View File

@@ -1,9 +1,13 @@
import { useRecoilValue, useResetRecoilState } from "recoil";
import { ArrowCounterClockwise, CheckCircle, Link } from "phosphor-react";
import { iconWeightAtom, iconSizeAtom, iconColorAtom } from "@/state/atoms";
import useTransientState from "@/hooks/useTransientState";
import { resetSettingsSelector } from "@/state/selectors";
import { useTransientState } from "@/hooks";
import {
iconWeightAtom,
iconSizeAtom,
iconColorAtom,
resetSettingsSelector,
} from "@/state";
import "./SettingsActions.css";

View File

@@ -1,7 +1,7 @@
import React, { useCallback } from "react";
import { useRecoilState } from "recoil";
import { iconSizeAtom } from "@/state/atoms";
import { iconSizeAtom } from "@/state";
import "./SizeInput.css";
type SizeInputProps = {};

View File

@@ -4,7 +4,7 @@ import Select from "react-dropdown-select";
import { PencilLine } from "phosphor-react";
import { IconStyle } from "@phosphor-icons/core";
import { iconWeightAtom } from "@/state/atoms";
import { iconWeightAtom } from "@/state";
import "./StyleInput.css";

View File

@@ -1,15 +1,12 @@
.tabs {
display: flex;
flex-direction: column;
border-left: 2px solid rgba(163, 159, 171, 0.1);
border-right: 2px solid rgba(163, 159, 171, 0.1);
}
.tabs-header {
display: flex;
align-items: center;
gap: 8px;
border-bottom: 2px solid rgba(163, 159, 171, 0.1);
}
button.tab {
@@ -19,14 +16,25 @@ button.tab {
text-align: center;
cursor: pointer;
flex: 1;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
}
button.tab:focus-within {
/* background-color: var(--tabs-background); */
}
.tab.active {
background-color: rgba(194, 186, 196, 0.25);
background-color: var(--tabs-background);
border-bottom: none;
}
.tab-content {
flex: 1;
padding: 8px 16px;
display: grid;
place-items: center;
border-radius: 8px;
background-color: var(--tabs-background);
overflow-y: auto;
}

View File

@@ -1,4 +1,7 @@
import { ReactNode, useState } from "react";
import { CSSProperties, ReactNode, useState } from "react";
import { useRecoilValue } from "recoil";
import { isDarkThemeSelector } from "@/state";
import "./Tabs.css";
@@ -11,15 +14,37 @@ type TabsProps = {
tabs: Tab[];
};
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);
return (
<div className="tabs">
<div
className="tabs"
tabIndex={0}
style={isDark ? colorStyles.dark : colorStyles.light}
>
<div className="tabs-header">
{tabs.map((tab, i) => (
<button
key={i}
tabIndex={0}
className={`tab ${activeIndex === i ? "active" : ""}`}
onClick={() => setActiveIndex(i)}
>
@@ -27,7 +52,18 @@ const Tabs = ({ tabs }: TabsProps) => {
</button>
))}
</div>
<div className="tab-content">{tabs[activeIndex]?.content}</div>
<div
className="tab-content"
style={
activeIndex === 0
? contentStyles.activeLeft
: activeIndex === tabs.length - 1
? contentStyles.activeRight
: undefined
}
>
{tabs[activeIndex]?.content}
</div>
</div>
);
};

9
src/hooks/index.ts Normal file
View File

@@ -0,0 +1,9 @@
export { default as useDebounce } from "./useDebounce";
export { default as useEvent } from "./useEvent";
export { default as useIconParameters } from "./useIconParameters";
export { default as usePersistSettings } from "./usePersistSettings";
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";

45
src/hooks/useEvent.ts Normal file
View File

@@ -0,0 +1,45 @@
import { useEffect } from "react";
export type UseEventTarget = HTMLElement | SVGElement | Document | Window;
export type UseEventMap<E extends UseEventTarget> = E extends HTMLElement
? HTMLElementEventMap
: E extends SVGElement
? SVGElementEventMap
: E extends Document
? DocumentEventMap
: WindowEventMap;
export type UseEventType<E extends UseEventTarget> = keyof UseEventMap<E>;
/**
* Attach event listeners to arbitrary targets, and perform necessary cleanup
* when unmounting. Provides type inference for the listener based on the
* provided event name (currently supports {@link Window}, {@link Document},
* and subclasses of {@link HTMLElement} and {@link SVGElement}).
*
* @param type an {@link https://developer.mozilla.org/en-US/docs/Web/Events#event_listing event type}
* @param listener a callback to be fired on the event
* @param options {@link AddEventListenerOptions}
* @param el the target element to attack the listener. Defaults to
* {@link Document} when omitted.
*/
export default function useEvent<
K extends UseEventType<T>,
M extends UseEventMap<T>,
T extends UseEventTarget = Document
>(
type: K,
listener: (this: T, ev: M[K]) => any,
options?: boolean | AddEventListenerOptions,
el?: T
) {
useEffect(() => {
const target = el ?? document;
// @ts-ignore
target.addEventListener(type, listener, options);
// @ts-ignore
return () => target.removeEventListener(type, listener);
}, [el, type]);
}

2
src/state/index.ts Normal file
View File

@@ -0,0 +1,2 @@
export * from "./atoms";
export * from "./selectors";