feat(app): add copy unicode button

This commit is contained in:
rektdeckard
2024-01-10 01:06:34 -07:00
committed by Tobias Fried
parent 7502b8b3ce
commit 697c6c836c
8 changed files with 73 additions and 41 deletions

View File

@@ -27,13 +27,12 @@
"format": "prettier --write \"./src/**/*.{js,jsx,ts,tsx,json,vue}\""
},
"dependencies": {
"@phosphor-icons/core": "^2.0.2",
"@phosphor-icons/core": "^2.0.4",
"@phosphor-icons/react": "^2.0.15",
"@recoiljs/refine": "^0.1.1",
"file-saver": "^2.0.2",
"framer-motion": "^9.0.1",
"framer-motion": "^10.17.12",
"fuse.js": "^6.4.1",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-dropdown-select": "^4.4.2",

18
pnpm-lock.yaml generated
View File

@@ -6,7 +6,7 @@ settings:
dependencies:
'@phosphor-icons/core':
specifier: ^2.0.2
specifier: ^2.0.4
version: 2.0.4
'@phosphor-icons/react':
specifier: ^2.0.15
@@ -18,14 +18,11 @@ dependencies:
specifier: ^2.0.2
version: 2.0.5
framer-motion:
specifier: ^9.0.1
version: 9.1.7(react-dom@18.2.0)(react@18.2.0)
specifier: ^10.17.12
version: 10.17.12(react-dom@18.2.0)(react@18.2.0)
fuse.js:
specifier: ^6.4.1
version: 6.6.2
prop-types:
specifier: ^15.8.1
version: 15.8.1
react:
specifier: ^18.2.0
version: 18.2.0
@@ -1043,11 +1040,16 @@ packages:
resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==}
dev: false
/framer-motion@9.1.7(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-nKxBkIO4IPkMEqcBbbATxsVjwPYShKl051yhBv9628iAH6JLeHD0siBHxkL62oQzMC1+GNX73XtPjgP753ufuw==}
/framer-motion@10.17.12(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-6aaBLN2EgH/GilXwOzEalTfw5Rx9DTQJJjTrxq5bfDbGtPCzXz2GCN6ePGRpTi1ZGugLHxdU273h38ENbcdFKQ==}
peerDependencies:
react: ^18.0.0
react-dom: ^18.0.0
peerDependenciesMeta:
react:
optional: true
react-dom:
optional: true
dependencies:
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)

View File

@@ -8,7 +8,7 @@ import IconGrid from "@/components/IconGrid";
import Footer from "@/components/Footer";
import ErrorBoundary from "@/components/ErrorBoundary";
import Notice from "@/components/Notice";
import Recipes from "@/components/Recipes";
// import Recipes from "@/components/Recipes";
import { useCSSVariables } from "@/hooks";
import { isDarkThemeSelector } from "@/state";

View File

@@ -10,7 +10,7 @@
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
max-width: 1120px;
max-width: 1152px;
margin: auto;
}

View File

@@ -64,10 +64,15 @@ const IconGrid = (_: IconGridProps) => {
<IconContext.Provider value={{ weight, size, color, mirrored: false }}>
<div className="grid-container">
<i id="beacon" className="beacon" />
<motion.div className="grid" initial="hidden" animate={controls}>
<motion.div
key={query}
className="grid"
initial="hidden"
animate={controls}
>
{filteredQueryResults.map((iconEntry, index) => (
<IconGridItem
key={index}
key={iconEntry.name}
index={index}
isDark={isDark}
entry={iconEntry}

View File

@@ -20,7 +20,7 @@ interface IconGridItemProps extends HTMLAttributes<HTMLDivElement> {
const transition = { duration: 0.2 };
const originIndex = 0;
const delayPerPixel = 0.0004;
const delayPerPixel = 0.0003;
const itemVariants = {
hidden: { opacity: 0 },
@@ -68,30 +68,28 @@ const IconGridItem = (props: IconGridItemProps) => {
}, [originOffset]);
return (
<>
<motion.div
className="grid-item"
key={name}
ref={ref}
tabIndex={0}
style={{
...style,
backgroundColor: isOpen ? "var(--background-layer)" : undefined,
}}
custom={delayRef}
transition={transition}
variants={itemVariants}
onKeyPress={(e) => e.key === "Enter" && handleOpen()}
onClick={handleOpen}
>
<Icon />
<p>
<span className="name">{name}</span>
{isNew && <span className="badge new"></span>}
{isUpdated && <span className="badge updated"></span>}
</p>
</motion.div>
</>
<motion.div
className="grid-item"
key={name}
ref={ref}
tabIndex={0}
style={{
...style,
backgroundColor: isOpen ? "var(--background-layer)" : undefined,
}}
custom={delayRef}
transition={transition}
variants={itemVariants}
onKeyPress={(e) => e.key === "Enter" && handleOpen()}
onClick={handleOpen}
>
<Icon />
<p>
<span className="name">{name}</span>
{isNew && <span className="badge new"></span>}
{isUpdated && <span className="badge updated"></span>}
</p>
</motion.div>
);
};

View File

@@ -18,6 +18,7 @@ import {
CaretDoubleLeft,
CaretDoubleRight,
} from "@phosphor-icons/react";
import { IconStyle } from "@phosphor-icons/core";
import ReactGA from "react-ga4";
import Tabs, { Tab } from "@/components/Tabs";
@@ -61,6 +62,7 @@ enum CopyType {
SVG_DATA,
PNG,
PNG_DATA,
UNICODE,
}
function cloneWithSize(svg: SVGSVGElement, size: number): SVGSVGElement {
@@ -75,12 +77,18 @@ const ActionButton = (
active?: boolean;
label: string;
download?: boolean;
disabled?: boolean;
} & HTMLAttributes<HTMLButtonElement>
) => {
const { active, download, label, ...rest } = props;
const Icon = download ? ArrowFatLinesDown : Copy;
return (
<button {...rest} className="action-button text" tabIndex={0}>
<button
{...rest}
className={`action-button text ${props.disabled ? "disabled" : ""}`}
aria-disabled={props.disabled}
tabIndex={0}
>
{active ? (
<CheckCircle size={20} color="var(--olive)" weight="fill" />
) : (
@@ -246,6 +254,14 @@ const Panel = () => {
setCopied(CopyType.SVG_RAW);
};
const handleCopyUnicode = async () => {
if (!entry) return;
const content = String.fromCharCode(entry.codepoint);
navigator.clipboard?.writeText(content);
setCopied(CopyType.UNICODE);
};
const handleDownloadSVG = (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => {
@@ -335,6 +351,9 @@ const Panel = () => {
<entry.Icon ref={ref} size={64}></entry.Icon>
<figcaption>
<p>{entry.name}</p>
<small className="versioning">
U+{entry.codepoint.toString(16).toUpperCase()}
</small>
<small className="versioning">
available in v{entry.published_in.toFixed(1)}.0+
</small>
@@ -395,6 +414,14 @@ const Panel = () => {
active={copied === CopyType.SVG_DATA}
onClick={handleCopyDataSVG}
/>
<ActionButton
label="Unicode"
title="Copy Unicode character (v2.1.0 or newer)"
active={copied === CopyType.UNICODE}
disabled={weight === IconStyle.DUOTONE}
onClick={handleCopyUnicode}
/>
</>
)}
</div>

View File

@@ -42,6 +42,7 @@ export default ({ children }: { children: ReactNode }) => {
const listen: ListenToItems = useCallback(
({ updateItem, updateAllKnownItems }) => {
void updateAllKnownItems;
const onStorage = (event: StorageEvent) => {
// ignore clear() calls
if (event.storageArea === localStorage && event.key !== null) {