diff --git a/src/components/App/App.css b/src/components/App/App.css index 9ede2c8..2ec16d4 100644 --- a/src/components/App/App.css +++ b/src/components/App/App.css @@ -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; diff --git a/src/components/ColorInput/ColorInput.tsx b/src/components/ColorInput/ColorInput.tsx index f091dbb..4f9201c 100644 --- a/src/components/ColorInput/ColorInput.tsx +++ b/src/components/ColorInput/ColorInput.tsx @@ -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"; diff --git a/src/components/Footer/Footer.css b/src/components/Footer/Footer.css index 5168379..31b40cd 100644 --- a/src/components/Footer/Footer.css +++ b/src/components/Footer/Footer.css @@ -67,7 +67,7 @@ footer .links { padding: 0; } - #back-to-top-button img { + #back-to-top-button svg { width: 28px; height: 28px; } diff --git a/src/components/IconGrid/DetailFooter.tsx b/src/components/IconGrid/DetailFooter.tsx index 33b2fc7..60a1705 100644 --- a/src/components/IconGrid/DetailFooter.tsx +++ b/src/components/IconGrid/DetailFooter.tsx @@ -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: (
-
+              
                 
                   {isWeightSupported
                     ? snippets[type]
@@ -213,31 +210,44 @@ const DetailFooter = () => {
           
-
{entry.name}
+
+

{entry.name}

+ + available in v{entry.published_in.toFixed(1)}.0+ + +
- - in ≥ {entry.published_in.toFixed(1)}.0 - +
+ + + +
+ -
- - - -
)} diff --git a/src/components/IconGrid/DetailsPanel.tsx b/src/components/IconGrid/DetailsPanel.tsx index f02a056..8c6609e 100644 --- a/src/components/IconGrid/DetailsPanel.tsx +++ b/src/components/IconGrid/DetailsPanel.tsx @@ -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"; diff --git a/src/components/IconGrid/IconGrid.css b/src/components/IconGrid/IconGrid.css index 2a58caa..6a25fe8 100644 --- a/src/components/IconGrid/IconGrid.css +++ b/src/components/IconGrid/IconGrid.css @@ -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; } diff --git a/src/components/IconGrid/IconGrid.tsx b/src/components/IconGrid/IconGrid.tsx index b2f06af..4679ae9 100644 --- a/src/components/IconGrid/IconGrid.tsx +++ b/src/components/IconGrid/IconGrid.tsx @@ -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 = { + light: {}, + dark: { + backgroundColor: "#35313D", + }, +} as const; + type IconGridProps = {}; const IconGrid = (_: IconGridProps) => { @@ -56,15 +65,10 @@ const IconGrid = (_: IconGridProps) => {
- + {filteredQueryResults.map((iconEntry, index) => ( { 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 && }

- {/* - {isOpen && } - */} ); }; diff --git a/src/components/IconGrid/TagCloud.tsx b/src/components/IconGrid/TagCloud.tsx index e2ab621..817163b 100644 --- a/src/components/IconGrid/TagCloud.tsx +++ b/src/components/IconGrid/TagCloud.tsx @@ -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 { diff --git a/src/components/Notice/Notice.tsx b/src/components/Notice/Notice.tsx index 43f5082..cacc302 100644 --- a/src/components/Notice/Notice.tsx +++ b/src/components/Notice/Notice.tsx @@ -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; diff --git a/src/components/SearchInput/SearchInput.tsx b/src/components/SearchInput/SearchInput.tsx index 688f6e3..de10ec7 100644 --- a/src/components/SearchInput/SearchInput.tsx +++ b/src/components/SearchInput/SearchInput.tsx @@ -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; diff --git a/src/components/SettingsActions/SettingsActions.tsx b/src/components/SettingsActions/SettingsActions.tsx index ea9b34e..9de39af 100644 --- a/src/components/SettingsActions/SettingsActions.tsx +++ b/src/components/SettingsActions/SettingsActions.tsx @@ -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"; diff --git a/src/components/SizeInput/SizeInput.tsx b/src/components/SizeInput/SizeInput.tsx index 071077f..7d8aaf9 100644 --- a/src/components/SizeInput/SizeInput.tsx +++ b/src/components/SizeInput/SizeInput.tsx @@ -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 = {}; diff --git a/src/components/StyleInput/StyleInput.tsx b/src/components/StyleInput/StyleInput.tsx index f2843ff..0028094 100644 --- a/src/components/StyleInput/StyleInput.tsx +++ b/src/components/StyleInput/StyleInput.tsx @@ -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"; diff --git a/src/components/Tabs/Tabs.css b/src/components/Tabs/Tabs.css index 36428ae..37a7a52 100644 --- a/src/components/Tabs/Tabs.css +++ b/src/components/Tabs/Tabs.css @@ -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; } diff --git a/src/components/Tabs/Tabs.tsx b/src/components/Tabs/Tabs.tsx index 5de51a2..341f6f2 100644 --- a/src/components/Tabs/Tabs.tsx +++ b/src/components/Tabs/Tabs.tsx @@ -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 = { + light: { "--tabs-background": "white" }, + dark: { "--tabs-background": "rgba(194, 186, 196, 0.25)" }, +} as const; + +const contentStyles: Record = { + activeLeft: { borderTopLeftRadius: 0 }, + activeRight: { borderTopRightRadius: 0 }, +} as const; + const Tabs = ({ tabs }: TabsProps) => { const [activeIndex, setActiveIndex] = useState(0); + const isDark = useRecoilValue(isDarkThemeSelector); return ( -
+
{tabs.map((tab, i) => ( ))}
-
{tabs[activeIndex]?.content}
+
+ {tabs[activeIndex]?.content} +
); }; diff --git a/src/hooks/index.ts b/src/hooks/index.ts new file mode 100644 index 0000000..17d8bf1 --- /dev/null +++ b/src/hooks/index.ts @@ -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"; diff --git a/src/hooks/useEvent.ts b/src/hooks/useEvent.ts new file mode 100644 index 0000000..27929a9 --- /dev/null +++ b/src/hooks/useEvent.ts @@ -0,0 +1,45 @@ +import { useEffect } from "react"; + +export type UseEventTarget = HTMLElement | SVGElement | Document | Window; + +export type UseEventMap = E extends HTMLElement + ? HTMLElementEventMap + : E extends SVGElement + ? SVGElementEventMap + : E extends Document + ? DocumentEventMap + : WindowEventMap; + +export type UseEventType = keyof UseEventMap; + +/** + * 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, + M extends UseEventMap, + 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]); +} diff --git a/src/state/index.ts b/src/state/index.ts new file mode 100644 index 0000000..bd7725b --- /dev/null +++ b/src/state/index.ts @@ -0,0 +1,2 @@ +export * from "./atoms"; +export * from "./selectors";