Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d7f5ea100 | ||
|
|
3cdcdd4e0d | ||
|
|
c090531800 | ||
|
|
887617e523 | ||
|
|
e242bcc660 | ||
|
|
22b69c3ae6 | ||
|
|
e4b99d2ca9 | ||
|
|
14d8c9d0e7 | ||
|
|
cdcf38466e | ||
|
|
f256109ba4 | ||
|
|
bcff9fecb3 | ||
|
|
a218b632ba | ||
|
|
56dd2ba514 | ||
|
|
f5089e1c60 | ||
|
|
77d93e4038 | ||
|
|
b6e2ae7da5 |
14
README.md
14
README.md
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
Phosphor is a flexible icon family for interfaces, diagrams, presentations — whatever, really.
|
Phosphor is a flexible icon family for interfaces, diagrams, presentations — whatever, really.
|
||||||
|
|
||||||
- 772 icons and counting
|
- 1047 icons and counting
|
||||||
- 6 weights: **Thin**, **Light**, **Regular**, **Bold**, **Fill**, and **Duotone**
|
- 6 weights: **Thin**, **Light**, **Regular**, **Bold**, **Fill**, and **Duotone**
|
||||||
- Designed at 16 x 16px to read well small and scale up big
|
- Designed at 16 x 16px to read well small and scale up big
|
||||||
- Raw stroke information retained to fine-tune the style
|
- Raw stroke information retained to fine-tune the style
|
||||||
@@ -88,7 +88,7 @@ ReactDOM.render(<App />, document.getElementById("root"));
|
|||||||
|
|
||||||
> **Note:** Due to possible namespace collisions with built-in HTML elements, compononent names in the Vue library are prefixed with `Ph`, but otherwise follow the same naming conventions. Both Pascal and kebab-case conventions can be used in templates.
|
> **Note:** Due to possible namespace collisions with built-in HTML elements, compononent names in the Vue library are prefixed with `Ph`, but otherwise follow the same naming conventions. Both Pascal and kebab-case conventions can be used in templates.
|
||||||
|
|
||||||
## Related Projects
|
## Our Related Projects
|
||||||
|
|
||||||
- [phosphor-react](https://github.com/phosphor-icons/phosphor-react) ▲ Phosphor icon component library for React
|
- [phosphor-react](https://github.com/phosphor-icons/phosphor-react) ▲ Phosphor icon component library for React
|
||||||
- [phosphor-vue](https://github.com/phosphor-icons/phosphor-vue) ▲ Phosphor icon component library for Vue
|
- [phosphor-vue](https://github.com/phosphor-icons/phosphor-vue) ▲ Phosphor icon component library for Vue
|
||||||
@@ -96,6 +96,16 @@ ReactDOM.render(<App />, document.getElementById("root"));
|
|||||||
- [phosphor-flutter](https://github.com/phosphor-icons/phosphor-flutter) ▲ Phosphor IconData library for Flutter
|
- [phosphor-flutter](https://github.com/phosphor-icons/phosphor-flutter) ▲ Phosphor IconData library for Flutter
|
||||||
- [phosphor-webcomponents](https://github.com/phosphor-icons/phosphor-webcomponents) ▲ Phosphor icons as Web Components
|
- [phosphor-webcomponents](https://github.com/phosphor-icons/phosphor-webcomponents) ▲ Phosphor icons as Web Components
|
||||||
- [phosphor-figma](https://github.com/phosphor-icons/phosphor-figma) ▲ Phosphor icons Figma plugin
|
- [phosphor-figma](https://github.com/phosphor-icons/phosphor-figma) ▲ Phosphor icons Figma plugin
|
||||||
|
- [phosphor-sketch](https://github.com/phosphor-icons/phosphor-sketch) ▲ Phosphor icons Sketch plugin
|
||||||
|
|
||||||
|
## Community Projects
|
||||||
|
|
||||||
|
- [phosphor-react-native](https://github.com/duongdev/phosphor-react-native) ▲ Phosphor icon component library for React Native
|
||||||
|
- [phosphor-svelte](https://github.com/haruaki07/phosphor-svelte) ▲ Phosphor icons for Svelte apps
|
||||||
|
- [phosphor-r](https://github.com/dreamRs/phosphoricons) ▲ Phosphor icon wrapper for R documents and applications
|
||||||
|
- [blade-phosphor-icons](https://github.com/codeat3/blade-phosphor-icons) ▲ Phosphor icons in your Laravel Blade views
|
||||||
|
|
||||||
|
If you've made a port of Phosphor and you want to see it here, just open a PR [here](https://github.com/phosphor-icons/phosphor-home)!
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
93
bin/fetch.js
Normal file
93
bin/fetch.js
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const fs = require("fs/promises");
|
||||||
|
const path = require("path");
|
||||||
|
const axios = require("axios");
|
||||||
|
const chalk = require("chalk");
|
||||||
|
const { Command } = require("commander");
|
||||||
|
const { version } = require("../package.json");
|
||||||
|
|
||||||
|
const ICON_API_URL = "https://api.phosphoricons.com";
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const program = new Command();
|
||||||
|
program
|
||||||
|
.version(version)
|
||||||
|
.option(
|
||||||
|
"-r --release <version>",
|
||||||
|
"Fetch icons from Phosphor API and compile to internal data structure"
|
||||||
|
)
|
||||||
|
.option("-p --published", "Published status of icons")
|
||||||
|
.option("-P, --no-published", "Published status of icons")
|
||||||
|
.option("-q --query <text>", "Fulltext search term")
|
||||||
|
.option("-n --name <name>", "Exact icon namee match");
|
||||||
|
|
||||||
|
program.parse(process.argv);
|
||||||
|
const params = new URLSearchParams(Object.entries(program.opts())).toString();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await axios.get(`${ICON_API_URL}?${params}`);
|
||||||
|
if (res.data) {
|
||||||
|
let fileString = `\
|
||||||
|
import * as Icons from "phosphor-react";
|
||||||
|
import { IconEntry, IconCategory } from ".";
|
||||||
|
|
||||||
|
export const icons: ReadonlyArray<IconEntry> = [
|
||||||
|
`;
|
||||||
|
|
||||||
|
res.data.icons.forEach((icon) => {
|
||||||
|
let categories = "[";
|
||||||
|
icon.searchCategories?.forEach((c) => {
|
||||||
|
categories += `IconCategory.${c.toUpperCase()},`;
|
||||||
|
});
|
||||||
|
categories += "]";
|
||||||
|
|
||||||
|
fileString += `\
|
||||||
|
{
|
||||||
|
name: "${icon.name}",
|
||||||
|
categories: ${categories},
|
||||||
|
tags: ${JSON.stringify(["*new*", ...icon.tags])},
|
||||||
|
Icon: Icons.${icon.name
|
||||||
|
.split("-")
|
||||||
|
.map((substr) => substr.replace(/^\w/, (c) => c.toUpperCase()))
|
||||||
|
.join("")},
|
||||||
|
},
|
||||||
|
`;
|
||||||
|
console.log(`${chalk.inverse.green(" DONE ")} ${icon.name}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
fileString += `
|
||||||
|
];
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === "development") {
|
||||||
|
console.log(\`\${icons.length} icons\`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const iconCount = (icons.length * 6)
|
||||||
|
.toString()
|
||||||
|
.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
||||||
|
|
||||||
|
`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fs.writeFile(
|
||||||
|
path.join(__dirname, "../src/lib/new.ts"),
|
||||||
|
fileString
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`${chalk.green(" DONE ")} ${res.data.icons.length} icons ingested`
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`${chalk.inverse.red(" FAIL ")} Could not write file`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error(`${chalk.inverse.red(" FAIL ")} No data`);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
process.exit(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
53
package.json
53
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "phosphor-home",
|
"name": "phosphor-home",
|
||||||
"version": "1.2.1",
|
"version": "1.4.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://phosphoricons.com",
|
"homepage": "https://phosphoricons.com",
|
||||||
"author": {
|
"author": {
|
||||||
@@ -20,11 +20,20 @@
|
|||||||
],
|
],
|
||||||
"repository": "github:phosphor-icons/phosphor-home",
|
"repository": "github:phosphor-icons/phosphor-home",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"analyze": "source-map-explorer 'build/static/js/*.js'",
|
||||||
|
"start": "react-scripts start",
|
||||||
|
"build": "react-scripts build",
|
||||||
|
"test": "react-scripts test",
|
||||||
|
"eject": "react-scripts eject",
|
||||||
|
"fetch": "node ./bin/fetch.js",
|
||||||
|
"format": "prettier --write \"./src/**/*.{js,jsx,ts,tsx,json,vue}\""
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"file-saver": "^2.0.2",
|
"file-saver": "^2.0.2",
|
||||||
"framer-motion": "^3.10.0",
|
"framer-motion": "^3.10.0",
|
||||||
"fuse.js": "^6.4.1",
|
"fuse.js": "^6.4.1",
|
||||||
"phosphor-react": "^1.2.1",
|
"phosphor-react": "^1.4.0",
|
||||||
"react": "^17.0.1",
|
"react": "^17.0.1",
|
||||||
"react-dom": "^17.0.1",
|
"react-dom": "^17.0.1",
|
||||||
"react-dropdown-select": "^4.4.2",
|
"react-dropdown-select": "^4.4.2",
|
||||||
@@ -32,19 +41,25 @@
|
|||||||
"react-hotkeys-hook": "^3.2.1",
|
"react-hotkeys-hook": "^3.2.1",
|
||||||
"react-scripts": "3.4.1",
|
"react-scripts": "3.4.1",
|
||||||
"react-use": "^15.3.2",
|
"react-use": "^15.3.2",
|
||||||
"recoil": "^0.1.3",
|
"recoil": "^0.5.2",
|
||||||
"svg2png-converter": "^1.0.0",
|
"svg2png-converter": "^1.0.0",
|
||||||
"tinycolor2": "^1.4.2"
|
"tinycolor2": "^1.4.2"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"devDependencies": {
|
||||||
"analyze": "source-map-explorer 'build/static/js/*.js'",
|
"@testing-library/jest-dom": "^4.2.4",
|
||||||
"start": "react-scripts start",
|
"@testing-library/react": "^9.3.2",
|
||||||
"build": "react-scripts build",
|
"@testing-library/user-event": "^7.1.2",
|
||||||
"test": "react-scripts test",
|
"@types/file-saver": "^2.0.1",
|
||||||
"eject": "react-scripts eject",
|
"@types/jest": "^24.0.0",
|
||||||
"predeploy": "npm run build",
|
"@types/node": "^12.0.0",
|
||||||
"deploy": "gh-pages -d build",
|
"@types/react": "^16.9.46",
|
||||||
"format": "prettier --write \"./src/**/*.{js,jsx,ts,tsx,json,vue}\""
|
"@types/react-dom": "^16.9.8",
|
||||||
|
"@types/react-virtualized": "^9.21.10",
|
||||||
|
"@types/tinycolor2": "^1.4.2",
|
||||||
|
"axios": "^0.24.0",
|
||||||
|
"chalk": "^4",
|
||||||
|
"commander": "^8.3.0",
|
||||||
|
"typescript": "^3.9.6"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": "react-app"
|
"extends": "react-app"
|
||||||
@@ -60,19 +75,5 @@
|
|||||||
"last 1 firefox version",
|
"last 1 firefox version",
|
||||||
"last 1 safari version"
|
"last 1 safari version"
|
||||||
]
|
]
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@testing-library/jest-dom": "^4.2.4",
|
|
||||||
"@testing-library/react": "^9.3.2",
|
|
||||||
"@testing-library/user-event": "^7.1.2",
|
|
||||||
"@types/file-saver": "^2.0.1",
|
|
||||||
"@types/jest": "^24.0.0",
|
|
||||||
"@types/node": "^12.0.0",
|
|
||||||
"@types/react": "^16.9.46",
|
|
||||||
"@types/react-dom": "^16.9.8",
|
|
||||||
"@types/react-list": "^0.8.5",
|
|
||||||
"@types/react-virtualized": "^9.21.10",
|
|
||||||
"@types/tinycolor2": "^1.4.2",
|
|
||||||
"typescript": "^3.9.6"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,13 @@
|
|||||||
"sizes": "512x512"
|
"sizes": "512x512"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"permissions": [
|
||||||
|
"http://*/*",
|
||||||
|
"https://*/*",
|
||||||
|
"clipboardRead",
|
||||||
|
"clipboardWrite",
|
||||||
|
"storage"
|
||||||
|
],
|
||||||
"start_url": ".",
|
"start_url": ".",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"theme_color": "#35313D",
|
"theme_color": "#35313D",
|
||||||
|
|||||||
@@ -109,8 +109,22 @@ a.main-link:after {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
border-bottom: 1px solid black;
|
border-bottom: 1px solid black;
|
||||||
transition: 0.2s;
|
transition: 0.2s;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.main-link:hover:after {
|
a.main-link:hover:after {
|
||||||
width: 0%;
|
width: 0%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.badge.new {
|
||||||
|
color: #ff6e60;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge.updated {
|
||||||
|
color: #397fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 0.5em;
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,12 +8,14 @@ import Footer from "../Footer/Footer";
|
|||||||
import ErrorBoundary from "../ErrorBoundary/ErrorBoundary";
|
import ErrorBoundary from "../ErrorBoundary/ErrorBoundary";
|
||||||
import Notice from "../Notice/Notice";
|
import Notice from "../Notice/Notice";
|
||||||
import useIconParameters from "../../hooks/useIconParameters";
|
import useIconParameters from "../../hooks/useIconParameters";
|
||||||
|
import usePersistSettings from "../../hooks/usePersistSettings";
|
||||||
|
|
||||||
const errorFallback = <Notice message="Search error" />;
|
const errorFallback = <Notice message="Search error" />;
|
||||||
const waitingFallback = <Notice type="none" message="" />;
|
const waitingFallback = <Notice type="none" message="" />;
|
||||||
|
|
||||||
const App: React.FC<any> = () => {
|
const App: React.FC<any> = () => {
|
||||||
useIconParameters();
|
useIconParameters();
|
||||||
|
usePersistSettings();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
|
|||||||
@@ -14,7 +14,15 @@ import TagCloud from "./TagCloud";
|
|||||||
import Notice from "../Notice/Notice";
|
import Notice from "../Notice/Notice";
|
||||||
import "./IconGrid.css";
|
import "./IconGrid.css";
|
||||||
|
|
||||||
const defaultSearchTags = ["*new*", "communication", "editor", "emoji", "maps", "weather"];
|
const defaultSearchTags = [
|
||||||
|
"*new*",
|
||||||
|
"*updated*",
|
||||||
|
"communication",
|
||||||
|
"editor",
|
||||||
|
"emoji",
|
||||||
|
"maps",
|
||||||
|
"weather",
|
||||||
|
];
|
||||||
|
|
||||||
type IconGridProps = {};
|
type IconGridProps = {};
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const delayPerPixel = 0.0004;
|
|||||||
|
|
||||||
const itemVariants = {
|
const itemVariants = {
|
||||||
hidden: { opacity: 0 },
|
hidden: { opacity: 0 },
|
||||||
visible: (delayRef: any) => ({
|
visible: (delayRef: MutableRefObject<number>) => ({
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
transition: { delay: delayRef.current },
|
transition: { delay: delayRef.current },
|
||||||
}),
|
}),
|
||||||
@@ -36,6 +36,8 @@ const IconGridItem: React.FC<IconGridItemProps> = (props) => {
|
|||||||
const { name, Icon } = entry;
|
const { name, Icon } = entry;
|
||||||
const [open, setOpen] = useRecoilState(iconPreviewOpenAtom);
|
const [open, setOpen] = useRecoilState(iconPreviewOpenAtom);
|
||||||
const isOpen = open === name;
|
const isOpen = open === name;
|
||||||
|
const isNew = entry.tags.includes("*new*");
|
||||||
|
const isUpdated = entry.tags.includes("*updated*");
|
||||||
const delayRef = useRef<number>(0);
|
const delayRef = useRef<number>(0);
|
||||||
const offset = useRef({ top: 0, left: 0 });
|
const offset = useRef({ top: 0, left: 0 });
|
||||||
const ref = useRef<any>();
|
const ref = useRef<any>();
|
||||||
@@ -84,11 +86,15 @@ const IconGridItem: React.FC<IconGridItemProps> = (props) => {
|
|||||||
onClick={handleOpen}
|
onClick={handleOpen}
|
||||||
>
|
>
|
||||||
<Icon />
|
<Icon />
|
||||||
<p>{name}</p>
|
<p>
|
||||||
|
{name}
|
||||||
|
{isNew && <span className="badge new">•</span>}
|
||||||
|
{isUpdated && <span className="badge updated">•</span>}
|
||||||
|
</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
<AnimatePresence initial={false}>
|
<AnimatePresence initial={false}>
|
||||||
{isOpen && <DetailsPanel {...props} />}
|
{isOpen && <DetailsPanel {...props} />}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -29,4 +29,4 @@ button.tag-button:focus {
|
|||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ const TagCloud: React.FC<TagCloudProps> = ({ name, tags, isDark }) => {
|
|||||||
onClick={() => void handleTagClick(tag)}
|
onClick={() => void handleTagClick(tag)}
|
||||||
>
|
>
|
||||||
<code className={`${isDark ? "dark" : ""}`}>{tag}</code>
|
<code className={`${isDark ? "dark" : ""}`}>{tag}</code>
|
||||||
|
{tag === "*new*" && <span className="badge new">•</span>}
|
||||||
|
{tag === "*updated*" && <span className="badge updated">•</span>}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -23,36 +23,39 @@ const Links: React.FC<LinksProps> = () => {
|
|||||||
Download all ({iconCount})
|
Download all ({iconCount})
|
||||||
</OutboundLink>
|
</OutboundLink>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<ArrowElbowDownRight size={24} />
|
<ArrowElbowDownRight size={24} />
|
||||||
<span>
|
<span>
|
||||||
<OutboundLink
|
<OutboundLink
|
||||||
className="nav-link"
|
className="nav-link"
|
||||||
to="https://www.figma.com/community/file/903830135544202908/Phosphor-Icons"
|
to="https://www.figma.com/community/file/903830135544202908/Phosphor-Icons"
|
||||||
eventLabel="Figma library"
|
eventLabel="Figma library"
|
||||||
>
|
>
|
||||||
Figma library
|
Figma library
|
||||||
</OutboundLink>
|
</OutboundLink>
|
||||||
{" / "}
|
{" / "}
|
||||||
<OutboundLink
|
<OutboundLink
|
||||||
className="nav-link"
|
className="nav-link"
|
||||||
to="https://www.figma.com/community/plugin/898620911119764089/Phosphor-Icons"
|
to="https://www.figma.com/community/plugin/898620911119764089/Phosphor-Icons"
|
||||||
eventLabel="Figma plugin"
|
eventLabel="Figma plugin"
|
||||||
>
|
>
|
||||||
plugin
|
plugin
|
||||||
</OutboundLink>
|
</OutboundLink>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<ArrowElbowDownRight size={24} />
|
<ArrowElbowDownRight size={24} />
|
||||||
<a
|
<OutboundLink
|
||||||
className="nav-link"
|
className="nav-link"
|
||||||
href="https://github.com/phosphor-icons/phosphor-home/issues"
|
to="https://phosphoricons.com/assets/phosphor-icons.sketchplugin.zip"
|
||||||
|
eventLabel="Download sketch plugin"
|
||||||
|
download
|
||||||
|
type="application/zip"
|
||||||
>
|
>
|
||||||
Request an icon
|
Sketch plugin
|
||||||
</a>
|
</OutboundLink>
|
||||||
</div>
|
</div>
|
||||||
{/* <div>
|
<div>
|
||||||
<ArrowElbowDownRight size={24} />
|
<ArrowElbowDownRight size={24} />
|
||||||
<span>
|
<span>
|
||||||
<a className="nav-link" href="https://paypal.me/minoraxis">
|
<a className="nav-link" href="https://paypal.me/minoraxis">
|
||||||
@@ -63,8 +66,8 @@ const Links: React.FC<LinksProps> = () => {
|
|||||||
Patreon
|
Patreon
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
</div> */}
|
</div>
|
||||||
<div>
|
{/* <div>
|
||||||
<ArrowElbowDownRight size={24} />
|
<ArrowElbowDownRight size={24} />
|
||||||
<a className="nav-link" href="https://paypal.me/minoraxis">
|
<a className="nav-link" href="https://paypal.me/minoraxis">
|
||||||
Donate on PayPal
|
Donate on PayPal
|
||||||
@@ -76,6 +79,7 @@ const Links: React.FC<LinksProps> = () => {
|
|||||||
Support us on Patreon
|
Support us on Patreon
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
*/}
|
||||||
<div>
|
<div>
|
||||||
<ArrowElbowDownRight size={24} />
|
<ArrowElbowDownRight size={24} />
|
||||||
<a
|
<a
|
||||||
@@ -85,6 +89,15 @@ const Links: React.FC<LinksProps> = () => {
|
|||||||
GitHub
|
GitHub
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<ArrowElbowDownRight size={24} />
|
||||||
|
<a
|
||||||
|
className="nav-link"
|
||||||
|
href="https://github.com/phosphor-icons/phosphor-home/issues"
|
||||||
|
>
|
||||||
|
Request an icon
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
21
src/components/SettingsActions/SettingsActions.css
Normal file
21
src/components/SettingsActions/SettingsActions.css
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
button.action-button {
|
||||||
|
background-color: rgba(255, 255, 255, 0.05);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 558px) {
|
||||||
|
.action-button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
57
src/components/SettingsActions/SettingsActions.tsx
Normal file
57
src/components/SettingsActions/SettingsActions.tsx
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { ArrowCounterClockwise, CheckCircle, Link } from "phosphor-react";
|
||||||
|
import { useRecoilValue, useResetRecoilState } from "recoil";
|
||||||
|
import { iconWeightAtom, iconSizeAtom, iconColorAtom } from "../../state/atoms";
|
||||||
|
import "./SettingsActions.css";
|
||||||
|
import useTransientState from "../../hooks/useTransientState";
|
||||||
|
import { resetSettingsSelector } from "../../state/selectors";
|
||||||
|
|
||||||
|
const SettingsActions: React.FC = () => {
|
||||||
|
const weight = useRecoilValue(iconWeightAtom);
|
||||||
|
const size = useRecoilValue(iconSizeAtom);
|
||||||
|
const color = useRecoilValue(iconColorAtom);
|
||||||
|
const reset = useResetRecoilState(resetSettingsSelector);
|
||||||
|
|
||||||
|
const [copied, setCopied] = useTransientState<boolean>(false, 2000);
|
||||||
|
|
||||||
|
const copyDeepLinkToClipboard = () => {
|
||||||
|
const paramString = new URLSearchParams([
|
||||||
|
["weight", weight.toString()],
|
||||||
|
["size", size.toString()],
|
||||||
|
["color", color.replace("#", "")],
|
||||||
|
]).toString();
|
||||||
|
void navigator.clipboard
|
||||||
|
?.writeText(`${window.location.host}?${paramString}`)
|
||||||
|
.then(() => {
|
||||||
|
setCopied(true);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
alert("Clipboard permissions must be enabled to copy links!");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
className="action-button"
|
||||||
|
title="Restore default settings"
|
||||||
|
onClick={reset}
|
||||||
|
>
|
||||||
|
<ArrowCounterClockwise size={24} />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="action-button"
|
||||||
|
title="Copy URL for current settings"
|
||||||
|
onClick={copyDeepLinkToClipboard}
|
||||||
|
>
|
||||||
|
{copied ? (
|
||||||
|
<CheckCircle size={24} color="#1FA647" weight="fill" />
|
||||||
|
) : (
|
||||||
|
<Link size={24} />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SettingsActions;
|
||||||
@@ -5,6 +5,7 @@ import StyleInput from "../StyleInput/StyleInput";
|
|||||||
import SearchInput from "../SearchInput/SearchInput";
|
import SearchInput from "../SearchInput/SearchInput";
|
||||||
import SizeInput from "../SizeInput/SizeInput";
|
import SizeInput from "../SizeInput/SizeInput";
|
||||||
import ColorInput from "../ColorInput/ColorInput";
|
import ColorInput from "../ColorInput/ColorInput";
|
||||||
|
import SettingsActions from "../SettingsActions/SettingsActions";
|
||||||
|
|
||||||
type ToolbarProps = {};
|
type ToolbarProps = {};
|
||||||
|
|
||||||
@@ -16,6 +17,7 @@ const Toolbar: React.FC<ToolbarProps> = () => {
|
|||||||
<SearchInput />
|
<SearchInput />
|
||||||
<SizeInput />
|
<SizeInput />
|
||||||
<ColorInput />
|
<ColorInput />
|
||||||
|
<SettingsActions />
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -34,4 +34,30 @@ export default () => {
|
|||||||
if (normalizedColor.isValid()) setColor(normalizedColor.toHexString());
|
if (normalizedColor.isValid()) setColor(normalizedColor.toHexString());
|
||||||
}
|
}
|
||||||
}, [color, setColor]);
|
}, [color, setColor]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!weight && !size && !color) {
|
||||||
|
const persistedState = JSON.parse(
|
||||||
|
window.localStorage.getItem("__phosphor_settings__") || "null"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!!persistedState) {
|
||||||
|
const { weight, size, color } = persistedState;
|
||||||
|
if (weight) {
|
||||||
|
if (weight.toUpperCase() in IconStyle) setWeight(weight as IconStyle);
|
||||||
|
}
|
||||||
|
if (size) {
|
||||||
|
const normalizedSize = parseInt(size);
|
||||||
|
if (typeof normalizedSize === "number" && isFinite(normalizedSize))
|
||||||
|
setSize(Math.min(Math.max(normalizedSize, 16), 96));
|
||||||
|
}
|
||||||
|
if (color) {
|
||||||
|
const normalizedColor = TinyColor(color);
|
||||||
|
if (normalizedColor.isValid())
|
||||||
|
setColor(normalizedColor.toHexString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
};
|
};
|
||||||
|
|||||||
18
src/hooks/usePersistSettings.ts
Normal file
18
src/hooks/usePersistSettings.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { useRecoilValue } from "recoil";
|
||||||
|
import useDebounce from "./useDebounce";
|
||||||
|
import { iconWeightAtom, iconSizeAtom, iconColorAtom } from "../state/atoms";
|
||||||
|
|
||||||
|
export default function usePersistSettings() {
|
||||||
|
const weight = useRecoilValue(iconWeightAtom);
|
||||||
|
const size = useRecoilValue(iconSizeAtom);
|
||||||
|
const color = useRecoilValue(iconColorAtom);
|
||||||
|
|
||||||
|
useDebounce(
|
||||||
|
() => {
|
||||||
|
const serializedState = JSON.stringify({ weight, size, color });
|
||||||
|
window.localStorage.setItem("__phosphor_settings__", serializedState);
|
||||||
|
},
|
||||||
|
2000,
|
||||||
|
[weight, size, color]
|
||||||
|
);
|
||||||
|
}
|
||||||
3443
src/lib/icons.ts
3443
src/lib/icons.ts
File diff suppressed because it is too large
Load Diff
@@ -13,7 +13,7 @@ export const iconWeightAtom = atom<IconStyle>({
|
|||||||
|
|
||||||
export const iconSizeAtom = atom<number>({
|
export const iconSizeAtom = atom<number>({
|
||||||
key: "iconSizeAtom",
|
key: "iconSizeAtom",
|
||||||
default: 48,
|
default: 32,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const iconColorAtom = atom<string>({
|
export const iconColorAtom = atom<string>({
|
||||||
|
|||||||
@@ -2,7 +2,12 @@ import { selector, selectorFamily } from "recoil";
|
|||||||
import TinyColor from "tinycolor2";
|
import TinyColor from "tinycolor2";
|
||||||
import Fuse from "fuse.js";
|
import Fuse from "fuse.js";
|
||||||
|
|
||||||
import { searchQueryAtom, iconColorAtom } from "./atoms";
|
import {
|
||||||
|
searchQueryAtom,
|
||||||
|
iconWeightAtom,
|
||||||
|
iconSizeAtom,
|
||||||
|
iconColorAtom,
|
||||||
|
} from "./atoms";
|
||||||
import { IconEntry, IconCategory } from "../lib";
|
import { IconEntry, IconCategory } from "../lib";
|
||||||
import { icons } from "../lib/icons";
|
import { icons } from "../lib/icons";
|
||||||
|
|
||||||
@@ -52,17 +57,29 @@ export const singleCategoryQueryResultsSelector = selectorFamily<
|
|||||||
IconCategory
|
IconCategory
|
||||||
>({
|
>({
|
||||||
key: "singleCategoryQueryResultsSelector",
|
key: "singleCategoryQueryResultsSelector",
|
||||||
get: (category: IconCategory) => ({ get }) => {
|
get:
|
||||||
const filteredResults = get(filteredQueryResultsSelector);
|
(category: IconCategory) =>
|
||||||
return new Promise((resolve) =>
|
({ get }) => {
|
||||||
resolve(
|
const filteredResults = get(filteredQueryResultsSelector);
|
||||||
filteredResults.filter((icon) => icon.categories.includes(category))
|
return new Promise((resolve) =>
|
||||||
)
|
resolve(
|
||||||
);
|
filteredResults.filter((icon) => icon.categories.includes(category))
|
||||||
},
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const isDarkThemeSelector = selector<boolean>({
|
export const isDarkThemeSelector = selector<boolean>({
|
||||||
key: "isDarkThemeSelector",
|
key: "isDarkThemeSelector",
|
||||||
get: ({ get }) => TinyColor(get(iconColorAtom)).isLight(),
|
get: ({ get }) => TinyColor(get(iconColorAtom)).isLight(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const resetSettingsSelector = selector<null>({
|
||||||
|
key: "resetSettings",
|
||||||
|
get: () => null,
|
||||||
|
set: ({ reset }) => {
|
||||||
|
reset(iconWeightAtom);
|
||||||
|
reset(iconSizeAtom);
|
||||||
|
reset(iconColorAtom);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user