Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57016742a5 | ||
|
|
7cd27c509b | ||
|
|
8a7921d082 |
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.
|
||||||
|
|
||||||
- 1047 icons and counting
|
- 772 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.
|
||||||
|
|
||||||
## Our Related Projects
|
## 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,16 +96,6 @@ 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
93
bin/fetch.js
@@ -1,93 +0,0 @@
|
|||||||
#!/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();
|
|
||||||
56
package.json
56
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "phosphor-home",
|
"name": "phosphor-home",
|
||||||
"version": "1.4.0",
|
"version": "1.2.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://phosphoricons.com",
|
"homepage": "https://phosphoricons.com",
|
||||||
"author": {
|
"author": {
|
||||||
@@ -20,20 +20,14 @@
|
|||||||
],
|
],
|
||||||
"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": {
|
||||||
|
"@types/braintree-web-drop-in": "^1.22.3",
|
||||||
|
"axios": "^0.21.1",
|
||||||
|
"braintree-web-drop-in": "^1.30.1",
|
||||||
"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.4.0",
|
"phosphor-react": "^1.2.1",
|
||||||
"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",
|
||||||
@@ -41,25 +35,19 @@
|
|||||||
"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.5.2",
|
"recoil": "^0.1.3",
|
||||||
"svg2png-converter": "^1.0.0",
|
"svg2png-converter": "^1.0.0",
|
||||||
"tinycolor2": "^1.4.2"
|
"tinycolor2": "^1.4.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"scripts": {
|
||||||
"@testing-library/jest-dom": "^4.2.4",
|
"analyze": "source-map-explorer 'build/static/js/*.js'",
|
||||||
"@testing-library/react": "^9.3.2",
|
"start": "react-scripts start",
|
||||||
"@testing-library/user-event": "^7.1.2",
|
"build": "react-scripts build",
|
||||||
"@types/file-saver": "^2.0.1",
|
"test": "react-scripts test",
|
||||||
"@types/jest": "^24.0.0",
|
"eject": "react-scripts eject",
|
||||||
"@types/node": "^12.0.0",
|
"predeploy": "npm run build",
|
||||||
"@types/react": "^16.9.46",
|
"deploy": "gh-pages -d build",
|
||||||
"@types/react-dom": "^16.9.8",
|
"format": "prettier --write \"./src/**/*.{js,jsx,ts,tsx,json,vue}\""
|
||||||
"@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"
|
||||||
@@ -75,5 +63,19 @@
|
|||||||
"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,13 +18,6 @@
|
|||||||
"sizes": "512x512"
|
"sizes": "512x512"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"permissions": [
|
|
||||||
"http://*/*",
|
|
||||||
"https://*/*",
|
|
||||||
"clipboardRead",
|
|
||||||
"clipboardWrite",
|
|
||||||
"storage"
|
|
||||||
],
|
|
||||||
"start_url": ".",
|
"start_url": ".",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"theme_color": "#35313D",
|
"theme_color": "#35313D",
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ button {
|
|||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input.main-button,
|
||||||
button.main-button {
|
button.main-button {
|
||||||
height: 64px;
|
height: 64px;
|
||||||
padding: 0 48px 0 40px;
|
padding: 0 48px 0 40px;
|
||||||
@@ -72,19 +73,29 @@ button.main-button {
|
|||||||
margin: 0 24px 24px 0;
|
margin: 0 24px 24px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input.main-button:active,
|
||||||
button.main-button:active {
|
button.main-button:active {
|
||||||
transform: translate(4px, 4px);
|
transform: translate(4px, 4px);
|
||||||
box-shadow: 0 0 0 0 black;
|
box-shadow: 0 0 0 0 black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input.main-button:focus,
|
||||||
button.main-button:focus {
|
button.main-button:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input.main-button:disabled,
|
||||||
|
button.main-button:disabled {
|
||||||
|
border: 2px solid gray;
|
||||||
|
box-shadow: 4px 4px 0 0 gray;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
/* button.main-button:not(:last-child) {
|
/* button.main-button:not(:last-child) {
|
||||||
margin: 0 24px 24px 0;
|
margin: 0 24px 24px 0;
|
||||||
} */
|
} */
|
||||||
|
|
||||||
|
input.main-button,
|
||||||
button.main-button svg {
|
button.main-button svg {
|
||||||
margin-right: 12px;
|
margin-right: 12px;
|
||||||
}
|
}
|
||||||
@@ -109,22 +120,60 @@ 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 {
|
button.text-button {
|
||||||
color: #ff6e60;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px;
|
||||||
|
background-color: transparent;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.badge.updated {
|
button.text-button svg {
|
||||||
color: #397fff;
|
margin: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.badge {
|
.radio-group {
|
||||||
font-size: 24px;
|
display: flex;
|
||||||
line-height: 0.5em;
|
flex-flow: row wrap;
|
||||||
|
justify-content: space-around;
|
||||||
|
row-gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.radio-button input[type="radio"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-button label {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 8px;
|
||||||
|
min-width: 50px;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 2px solid black;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: center;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-button label:hover {
|
||||||
|
background-color: #E0E0E0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-button input[type="radio"]:checked + label {
|
||||||
|
color: white;
|
||||||
|
background-color: black;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-button label input {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
@@ -1,21 +1,23 @@
|
|||||||
import React, { Suspense } from "react";
|
import React, { Suspense } from "react";
|
||||||
|
|
||||||
|
import Header from "components/Header";
|
||||||
|
import Modal from "components/Modal";
|
||||||
|
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 from "hooks/useIconParameters";
|
||||||
|
|
||||||
import "./App.css";
|
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";
|
|
||||||
|
|
||||||
const errorFallback = <Notice message="Search error" />;
|
const errorFallback = <Notice message="Search error" />;
|
||||||
|
const paymentFallback = <Notice message="Could not connect to payments" />;
|
||||||
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>
|
||||||
@@ -28,6 +30,9 @@ const App: React.FC<any> = () => {
|
|||||||
</Suspense>
|
</Suspense>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</main>
|
</main>
|
||||||
|
<Suspense fallback={paymentFallback}>
|
||||||
|
<Modal />
|
||||||
|
</Suspense>
|
||||||
<Footer />
|
<Footer />
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
);
|
);
|
||||||
|
|||||||
2
src/components/App/index.ts
Normal file
2
src/components/App/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
import App from "./App";
|
||||||
|
export default App;
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
import React, { useCallback } from "react";
|
import React, { useCallback } from "react";
|
||||||
import { useRecoilState, useRecoilValue } from "recoil";
|
import { useRecoilState, useRecoilValue } from "recoil";
|
||||||
|
|
||||||
import { iconColorAtom } from "../../state/atoms";
|
import { iconColorAtom } from "state/atoms";
|
||||||
import { isDarkThemeSelector } from "../../state/selectors";
|
import { isDarkThemeSelector } from "state/selectors";
|
||||||
import useThrottled from "../../hooks/useThrottled";
|
import useThrottled from "hooks/useThrottled";
|
||||||
|
|
||||||
import "./ColorInput.css";
|
import "./ColorInput.css";
|
||||||
|
|
||||||
type ColorInputProps = {};
|
type ColorInputProps = {};
|
||||||
|
|||||||
2
src/components/ColorInput/index.ts
Normal file
2
src/components/ColorInput/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
import ColorInput from "./ColorInput";
|
||||||
|
export default ColorInput;
|
||||||
2
src/components/ErrorBoundary/index.ts
Normal file
2
src/components/ErrorBoundary/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
import ErrorBoundary from "./ErrorBoundary";
|
||||||
|
export default ErrorBoundary;
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Coffee, Heart } from "phosphor-react";
|
import { Coffee, Heart } from "phosphor-react";
|
||||||
|
|
||||||
import uArrowUpLeft from "../../assets/u-arrow-up-left.svg";
|
import uArrowUpLeft from "assets/u-arrow-up-left.svg";
|
||||||
import markerGreen from "../../assets/marker-green.svg";
|
import markerGreen from "assets/marker-green.svg";
|
||||||
import postIt from "../../assets/footer-mobile.svg";
|
import postIt from "assets/footer-mobile.svg";
|
||||||
import Links from "../Links/Links";
|
|
||||||
|
import Links from "components/Links";
|
||||||
|
|
||||||
import "./Footer.css";
|
import "./Footer.css";
|
||||||
|
|
||||||
type FooterProps = {};
|
type FooterProps = {};
|
||||||
@@ -34,8 +36,8 @@ const Footer: React.FC<FooterProps> = () => {
|
|||||||
a little quirky, too.
|
a little quirky, too.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
We're thankful for the tools we've benefited from and
|
We're thankful for the tools we've benefited from and this is our
|
||||||
this is our contribution towards a collaborative community.
|
contribution towards a collaborative community.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Phosphor is free and open-source, licensed under{" "}
|
Phosphor is free and open-source, licensed under{" "}
|
||||||
|
|||||||
2
src/components/Footer/index.ts
Normal file
2
src/components/Footer/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
import Footer from "./Footer";
|
||||||
|
export default Footer;
|
||||||
@@ -1,22 +1,24 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { ArrowCircleUpRight, ArrowCircleDown } from "phosphor-react";
|
import { ArrowCircleUpRight, ArrowCircleDown } from "phosphor-react";
|
||||||
|
|
||||||
import markerPurple from "../../assets/marker-purple.svg";
|
import markerPurple from "assets/marker-purple.svg";
|
||||||
import paperclips from "../../assets/paperclips-header-mobile.svg";
|
import paperclips from "assets/paperclips-header-mobile.svg";
|
||||||
import paperclipsThree from "../../assets/paperclips-header.svg";
|
import paperclipsThree from "assets/paperclips-header.svg";
|
||||||
import tablet from "../../assets/tablet.svg";
|
import tablet from "assets/tablet.svg";
|
||||||
import tabletSpec from "../../assets/tablet-spec.svg";
|
import tabletSpec from "assets/tablet-spec.svg";
|
||||||
import billiardBall from "../../assets/billiard-ball.svg";
|
import billiardBall from "assets/billiard-ball.svg";
|
||||||
import billiardBallSpec from "../../assets/billiard-ball-spec.svg";
|
import billiardBallSpec from "assets/billiard-ball-spec.svg";
|
||||||
import warning from "../../assets/warning.svg";
|
import warning from "assets/warning.svg";
|
||||||
import warningSpec from "../../assets/warning-spec.svg";
|
import warningSpec from "assets/warning-spec.svg";
|
||||||
import cuttingMat from "../../assets/cutting-mat.svg";
|
import cuttingMat from "assets/cutting-mat.svg";
|
||||||
import cuttingMatSpec from "../../assets/cutting-mat-spec.svg";
|
import cuttingMatSpec from "assets/cutting-mat-spec.svg";
|
||||||
import receipt from "../../assets/receipt.svg";
|
import receipt from "assets/receipt.svg";
|
||||||
import receiptSpec from "../../assets/receipt-spec.svg";
|
import receiptSpec from "assets/receipt-spec.svg";
|
||||||
import calculator from "../../assets/calculator.svg";
|
import calculator from "assets/calculator.svg";
|
||||||
import calculatorSpec from "../../assets/calculator-spec.svg";
|
import calculatorSpec from "assets/calculator-spec.svg";
|
||||||
import Links from "../Links/Links";
|
|
||||||
|
import Links from "components/Links";
|
||||||
|
|
||||||
import "./Header.css";
|
import "./Header.css";
|
||||||
|
|
||||||
type HeaderProps = {};
|
type HeaderProps = {};
|
||||||
|
|||||||
2
src/components/Header/index.ts
Normal file
2
src/components/Header/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
import Header from "./Header";
|
||||||
|
export default Header;
|
||||||
@@ -3,18 +3,19 @@ import { useRecoilValue, useSetRecoilState } from "recoil";
|
|||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { Svg2Png } from "svg2png-converter";
|
import { Svg2Png } from "svg2png-converter";
|
||||||
import { saveAs } from "file-saver";
|
import { saveAs } from "file-saver";
|
||||||
import { Copy, X, CheckCircle, Download } from "phosphor-react";
|
|
||||||
import ReactGA from "react-ga";
|
import ReactGA from "react-ga";
|
||||||
|
import { Copy, X, CheckCircle, Download } from "phosphor-react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
iconWeightAtom,
|
iconWeightAtom,
|
||||||
iconSizeAtom,
|
iconSizeAtom,
|
||||||
iconColorAtom,
|
iconColorAtom,
|
||||||
iconPreviewOpenAtom,
|
iconPreviewOpenAtom,
|
||||||
} from "../../state/atoms";
|
} from "state/atoms";
|
||||||
import useTransientState from "../../hooks/useTransientState";
|
import { IconEntry } from "lib";
|
||||||
|
import useTransientState from "hooks/useTransientState";
|
||||||
|
|
||||||
import TagCloud from "./TagCloud";
|
import TagCloud from "./TagCloud";
|
||||||
import { IconEntry } from "../../lib";
|
|
||||||
|
|
||||||
const panelVariants = {
|
const panelVariants = {
|
||||||
open: {
|
open: {
|
||||||
|
|||||||
@@ -3,20 +3,21 @@ import { useRecoilValue } from "recoil";
|
|||||||
import { motion, useAnimation } from "framer-motion";
|
import { motion, useAnimation } from "framer-motion";
|
||||||
import { IconContext } from "phosphor-react";
|
import { IconContext } from "phosphor-react";
|
||||||
|
|
||||||
import { iconWeightAtom, iconSizeAtom, iconColorAtom } from "../../state/atoms";
|
import { iconWeightAtom, iconSizeAtom, iconColorAtom } from "state/atoms";
|
||||||
import {
|
import {
|
||||||
filteredQueryResultsSelector,
|
filteredQueryResultsSelector,
|
||||||
isDarkThemeSelector,
|
isDarkThemeSelector,
|
||||||
} from "../../state/selectors";
|
} from "state/selectors";
|
||||||
import useGridSpans from "../../hooks/useGridSpans";
|
import useGridSpans from "hooks/useGridSpans";
|
||||||
|
|
||||||
import IconGridItem from "./IconGridItem";
|
import IconGridItem from "./IconGridItem";
|
||||||
import TagCloud from "./TagCloud";
|
import TagCloud from "./TagCloud";
|
||||||
import Notice from "../Notice/Notice";
|
import Notice from "components/Notice";
|
||||||
|
|
||||||
import "./IconGrid.css";
|
import "./IconGrid.css";
|
||||||
|
|
||||||
const defaultSearchTags = [
|
const defaultSearchTags = [
|
||||||
"*new*",
|
"*new*",
|
||||||
"*updated*",
|
|
||||||
"communication",
|
"communication",
|
||||||
"editor",
|
"editor",
|
||||||
"emoji",
|
"emoji",
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import React, {
|
|||||||
import { useRecoilState } from "recoil";
|
import { useRecoilState } from "recoil";
|
||||||
import { motion, AnimatePresence } from "framer-motion";
|
import { motion, AnimatePresence } from "framer-motion";
|
||||||
|
|
||||||
import { iconPreviewOpenAtom } from "../../state/atoms";
|
import { IconEntry } from "lib";
|
||||||
|
import { iconPreviewOpenAtom } from "state/atoms";
|
||||||
import DetailsPanel from "./DetailsPanel";
|
import DetailsPanel from "./DetailsPanel";
|
||||||
import { IconEntry } from "../../lib";
|
|
||||||
|
|
||||||
interface IconGridItemProps {
|
interface IconGridItemProps {
|
||||||
index: number;
|
index: number;
|
||||||
@@ -25,7 +25,7 @@ const delayPerPixel = 0.0004;
|
|||||||
|
|
||||||
const itemVariants = {
|
const itemVariants = {
|
||||||
hidden: { opacity: 0 },
|
hidden: { opacity: 0 },
|
||||||
visible: (delayRef: MutableRefObject<number>) => ({
|
visible: (delayRef: any) => ({
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
transition: { delay: delayRef.current },
|
transition: { delay: delayRef.current },
|
||||||
}),
|
}),
|
||||||
@@ -36,8 +36,6 @@ 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>();
|
||||||
@@ -86,11 +84,7 @@ const IconGridItem: React.FC<IconGridItemProps> = (props) => {
|
|||||||
onClick={handleOpen}
|
onClick={handleOpen}
|
||||||
>
|
>
|
||||||
<Icon />
|
<Icon />
|
||||||
<p>
|
<p>{name}</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} />}
|
||||||
|
|||||||
@@ -29,4 +29,4 @@ button.tag-button:focus {
|
|||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import React, { useCallback } from "react";
|
import React, { useCallback } from "react";
|
||||||
import { useSetRecoilState } from "recoil";
|
import { useSetRecoilState } from "recoil";
|
||||||
|
|
||||||
import { searchQueryAtom } from "../../state/atoms";
|
import { searchQueryAtom } from "state/atoms";
|
||||||
|
|
||||||
import "./TagCloud.css";
|
import "./TagCloud.css";
|
||||||
|
|
||||||
interface TagCloudProps {
|
interface TagCloudProps {
|
||||||
@@ -29,8 +30,6 @@ 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>
|
||||||
|
|||||||
2
src/components/IconGrid/index.ts
Normal file
2
src/components/IconGrid/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
import IconGrid from "./IconGrid";
|
||||||
|
export default IconGrid;
|
||||||
@@ -20,13 +20,14 @@
|
|||||||
margin-right: 12px;
|
margin-right: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.nav-link {
|
.nav-link {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
position: relative;
|
position: relative;
|
||||||
color: black;
|
color: black;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.nav-link:after {
|
.nav-link:after {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: -2px;
|
bottom: -2px;
|
||||||
@@ -36,6 +37,6 @@ a.nav-link:after {
|
|||||||
transition: 0.2s;
|
transition: 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.nav-link:hover:after {
|
.nav-link:hover:after {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,22 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { OutboundLink } from "react-ga";
|
import { OutboundLink } from "react-ga";
|
||||||
|
import { useSetRecoilState } from "recoil";
|
||||||
import { ArrowElbowDownRight } from "phosphor-react";
|
import { ArrowElbowDownRight } from "phosphor-react";
|
||||||
|
|
||||||
import { iconCount } from "../../lib/icons";
|
import { iconCount } from "lib/icons";
|
||||||
|
import { modalSelector } from "state/selectors";
|
||||||
|
import DonationModal from "components/Modal/DonationModal";
|
||||||
|
|
||||||
import "./Links.css";
|
import "./Links.css";
|
||||||
|
|
||||||
interface LinksProps {}
|
interface LinksProps {}
|
||||||
|
|
||||||
const Links: React.FC<LinksProps> = () => {
|
const Links: React.FC<LinksProps> = () => {
|
||||||
|
const openModal = useSetRecoilState(modalSelector);
|
||||||
|
const openDonationModal = () => openModal({ type: DonationModal });
|
||||||
|
|
||||||
|
const delayedOpenDonationModal = () => setTimeout(openDonationModal, 2000);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="links">
|
<div className="links">
|
||||||
<div>
|
<div>
|
||||||
@@ -19,6 +27,7 @@ const Links: React.FC<LinksProps> = () => {
|
|||||||
eventLabel="Download all"
|
eventLabel="Download all"
|
||||||
download
|
download
|
||||||
type="application/zip"
|
type="application/zip"
|
||||||
|
onClick={delayedOpenDonationModal}
|
||||||
>
|
>
|
||||||
Download all ({iconCount})
|
Download all ({iconCount})
|
||||||
</OutboundLink>
|
</OutboundLink>
|
||||||
@@ -45,41 +54,25 @@ const Links: React.FC<LinksProps> = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<ArrowElbowDownRight size={24} />
|
<ArrowElbowDownRight size={24} />
|
||||||
<OutboundLink
|
<a
|
||||||
className="nav-link"
|
className="nav-link"
|
||||||
to="https://phosphoricons.com/assets/phosphor-icons.sketchplugin.zip"
|
href="https://github.com/phosphor-icons/phosphor-home/issues"
|
||||||
eventLabel="Download sketch plugin"
|
|
||||||
download
|
|
||||||
type="application/zip"
|
|
||||||
>
|
>
|
||||||
Sketch plugin
|
Request an icon
|
||||||
</OutboundLink>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<ArrowElbowDownRight size={24} />
|
<ArrowElbowDownRight size={24} />
|
||||||
<span>
|
<span className="nav-link" onClick={openDonationModal}>
|
||||||
<a className="nav-link" href="https://paypal.me/minoraxis">
|
Donate
|
||||||
Donate on PayPal
|
|
||||||
</a>
|
|
||||||
{" / "}
|
|
||||||
<a className="nav-link" href="https://patreon.com/phosphoricons">
|
|
||||||
Patreon
|
|
||||||
</a>
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{/* <div>
|
|
||||||
<ArrowElbowDownRight size={24} />
|
|
||||||
<a className="nav-link" href="https://paypal.me/minoraxis">
|
|
||||||
Donate on PayPal
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<ArrowElbowDownRight size={24} />
|
<ArrowElbowDownRight size={24} />
|
||||||
<a className="nav-link" href="https://patreon.com/phosphoricons">
|
<a className="nav-link" href="https://patreon.com/phosphoricons">
|
||||||
Support us on Patreon
|
Support us on Patreon
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
*/}
|
|
||||||
<div>
|
<div>
|
||||||
<ArrowElbowDownRight size={24} />
|
<ArrowElbowDownRight size={24} />
|
||||||
<a
|
<a
|
||||||
@@ -89,15 +82,6 @@ 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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
2
src/components/Links/index.ts
Normal file
2
src/components/Links/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
import Links from "./Links";
|
||||||
|
export default Links;
|
||||||
91
src/components/Modal/DonationModal/DonationModal.tsx
Normal file
91
src/components/Modal/DonationModal/DonationModal.tsx
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
|
||||||
|
import { ModalInstance } from "../Modal";
|
||||||
|
import DonationStepMethod from "./DonationStepMethod";
|
||||||
|
import DonationStepDropin from "./DonationStepDropin";
|
||||||
|
import DonationStepThanks from "./DonationStepThanks";
|
||||||
|
|
||||||
|
const routes = [DonationStepMethod, DonationStepDropin, DonationStepThanks];
|
||||||
|
|
||||||
|
export enum DonationType {
|
||||||
|
FIVE_DOLLARS = 5,
|
||||||
|
TEN_DOLLARS = 10,
|
||||||
|
TWENTY_DOLLARS = 20,
|
||||||
|
FIFTY_DOLLARS = 50,
|
||||||
|
ONE_HUNDRED_DOLLARS = 100,
|
||||||
|
CUSTOM = -1,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RouteProps {
|
||||||
|
donationType: DonationType | undefined;
|
||||||
|
setDonationType: React.Dispatch<
|
||||||
|
React.SetStateAction<DonationType | undefined>
|
||||||
|
>;
|
||||||
|
donationAmount: number;
|
||||||
|
setDonationAmount: React.Dispatch<React.SetStateAction<number>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StepProps {
|
||||||
|
previousStep: () => void;
|
||||||
|
nextStep: () => void;
|
||||||
|
close: () => void;
|
||||||
|
routeProps: RouteProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StepperProps {
|
||||||
|
routes: Array<React.FC<StepProps>>;
|
||||||
|
routeProps: RouteProps;
|
||||||
|
close: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Stepper: React.FC<StepperProps> = ({ routes, routeProps, close }) => {
|
||||||
|
const [currentStep, setCurrentStep] = useState<number>(0);
|
||||||
|
|
||||||
|
const previousStep = () => {
|
||||||
|
if (currentStep <= 0) return;
|
||||||
|
setTimeout(() => setCurrentStep((c) => c - 1), 500);
|
||||||
|
};
|
||||||
|
|
||||||
|
const nextStep = () => {
|
||||||
|
if (currentStep >= routes.length - 1) return;
|
||||||
|
setTimeout(() => setCurrentStep((c) => c + 1), 500);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Component = routes[currentStep];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Component
|
||||||
|
previousStep={previousStep}
|
||||||
|
nextStep={nextStep}
|
||||||
|
close={close}
|
||||||
|
routeProps={routeProps}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const DonationModal = ({ close }: ModalInstance): JSX.Element => {
|
||||||
|
const [donationType, setDonationType] = useState<DonationType | undefined>();
|
||||||
|
const [donationAmount, setDonationAmount] = useState<number>(0);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="modal-titlebar">
|
||||||
|
<h2>Donate</h2>
|
||||||
|
</div>
|
||||||
|
<Stepper
|
||||||
|
routes={routes}
|
||||||
|
routeProps={{
|
||||||
|
donationType,
|
||||||
|
setDonationType,
|
||||||
|
donationAmount,
|
||||||
|
setDonationAmount,
|
||||||
|
}}
|
||||||
|
close={close}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DonationModal;
|
||||||
128
src/components/Modal/DonationModal/DonationStepDropin.tsx
Normal file
128
src/components/Modal/DonationModal/DonationStepDropin.tsx
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
import React, { useRef, useState, useEffect } from "react";
|
||||||
|
import dropin, { Dropin } from "braintree-web-drop-in";
|
||||||
|
import axios from "axios";
|
||||||
|
import { CurrencyCircleDollar } from "phosphor-react";
|
||||||
|
|
||||||
|
import { StepProps } from "./DonationModal";
|
||||||
|
|
||||||
|
const PaymentServer = axios.create({
|
||||||
|
baseURL: "https://us-central1-phosphor-14c61.cloudfunctions.net/paymentsApi",
|
||||||
|
});
|
||||||
|
|
||||||
|
const BT_PAYMENT_FIELDS = {
|
||||||
|
number: {
|
||||||
|
placeholder: "4111 1111 1111 1111",
|
||||||
|
},
|
||||||
|
cvv: {
|
||||||
|
placeholder: "123",
|
||||||
|
},
|
||||||
|
expirationDate: {
|
||||||
|
placeholder: "10/22",
|
||||||
|
},
|
||||||
|
cardholderName: {
|
||||||
|
placeholder: "Person McFace",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const PaymentModal: React.FC<StepProps> = ({
|
||||||
|
nextStep,
|
||||||
|
previousStep,
|
||||||
|
routeProps,
|
||||||
|
}) => {
|
||||||
|
const instance = useRef<Dropin>();
|
||||||
|
const [isValid, setIsValid] = useState<boolean>(false);
|
||||||
|
const [isLoading, setLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const { donationAmount } = routeProps;
|
||||||
|
|
||||||
|
const submit = async () => {
|
||||||
|
if (!instance.current) return;
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const payload = await instance.current.requestPaymentMethod();
|
||||||
|
console.log({ ...payload, donationAmount });
|
||||||
|
const response = await PaymentServer.post("/", {
|
||||||
|
...payload,
|
||||||
|
donationAmount,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log({ response });
|
||||||
|
if (!!response.data?.success) {
|
||||||
|
nextStep();
|
||||||
|
} else {
|
||||||
|
setIsValid(false);
|
||||||
|
}
|
||||||
|
} catch (_e) {
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const initializePayments = async () => {
|
||||||
|
try {
|
||||||
|
instance.current = await dropin.create({
|
||||||
|
authorization: "sandbox_246jdjxq_8h7hm5rvngkykjds",
|
||||||
|
container: "#braintree-dropin",
|
||||||
|
card: {
|
||||||
|
cardholderName: {
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
overrides: {
|
||||||
|
fields: BT_PAYMENT_FIELDS,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
paypal: {
|
||||||
|
flow: "checkout",
|
||||||
|
amount: donationAmount.toFixed(2).toString(),
|
||||||
|
currency: "USD",
|
||||||
|
commit: false,
|
||||||
|
},
|
||||||
|
paypalCredit: {
|
||||||
|
flow: "checkout",
|
||||||
|
amount: donationAmount.toFixed(2).toString(),
|
||||||
|
currency: "USD",
|
||||||
|
commit: false,
|
||||||
|
},
|
||||||
|
venmo: { allowNewBrowserTab: false },
|
||||||
|
});
|
||||||
|
instance.current.on("paymentMethodRequestable", () => setIsValid(true));
|
||||||
|
instance.current.on("noPaymentMethodRequestable", () =>
|
||||||
|
setIsValid(false)
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
initializePayments();
|
||||||
|
}, [donationAmount]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div id="braintree-dropin"></div>
|
||||||
|
<div className="donation-details">
|
||||||
|
<CurrencyCircleDollar size={64} weight="duotone" />
|
||||||
|
<span>
|
||||||
|
Preparing your ${routeProps.donationAmount.toFixed(2)} donation...
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="step-button-container">
|
||||||
|
<button className="main-button" onClick={previousStep}>
|
||||||
|
Back
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="main-button"
|
||||||
|
onClick={submit}
|
||||||
|
disabled={isLoading || !isValid}
|
||||||
|
>
|
||||||
|
{isLoading ? "Processing..." : "Donate"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PaymentModal;
|
||||||
123
src/components/Modal/DonationModal/DonationStepMethod.tsx
Normal file
123
src/components/Modal/DonationModal/DonationStepMethod.tsx
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import { StepProps, DonationType } from "./DonationModal";
|
||||||
|
|
||||||
|
const DonationStepMethod: React.FC<StepProps> = ({
|
||||||
|
previousStep,
|
||||||
|
nextStep,
|
||||||
|
close,
|
||||||
|
routeProps,
|
||||||
|
}) => {
|
||||||
|
const { donationType, donationAmount, setDonationType, setDonationAmount } =
|
||||||
|
routeProps;
|
||||||
|
|
||||||
|
const onDonationChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setDonationType(+e.target.value as DonationType);
|
||||||
|
if (!(+e.target.value === DonationType.CUSTOM))
|
||||||
|
setDonationAmount(+e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
void previousStep;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h3>
|
||||||
|
Thanks for using Phosphor! Would you like to make a donation to help
|
||||||
|
support our work?
|
||||||
|
</h3>
|
||||||
|
<div className="radio-group">
|
||||||
|
<div className="radio-button">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="donate-5"
|
||||||
|
name="donation-type"
|
||||||
|
value={DonationType.FIVE_DOLLARS}
|
||||||
|
checked={donationType === DonationType.FIVE_DOLLARS}
|
||||||
|
onChange={onDonationChange}
|
||||||
|
/>
|
||||||
|
<label htmlFor="donate-5">$5</label>
|
||||||
|
</div>
|
||||||
|
<div className="radio-button">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="donate-10"
|
||||||
|
name="donation-type"
|
||||||
|
value={DonationType.TEN_DOLLARS}
|
||||||
|
checked={donationType === DonationType.TEN_DOLLARS}
|
||||||
|
onChange={onDonationChange}
|
||||||
|
/>
|
||||||
|
<label htmlFor="donate-10">$10</label>
|
||||||
|
</div>
|
||||||
|
<div className="radio-button">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="donate-20"
|
||||||
|
name="donation-type"
|
||||||
|
value={DonationType.TWENTY_DOLLARS}
|
||||||
|
checked={donationType === DonationType.TWENTY_DOLLARS}
|
||||||
|
onChange={onDonationChange}
|
||||||
|
/>
|
||||||
|
<label htmlFor="donate-20">$20</label>
|
||||||
|
</div>
|
||||||
|
<div className="radio-button">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="donate-50"
|
||||||
|
name="donation-type"
|
||||||
|
value={DonationType.FIFTY_DOLLARS}
|
||||||
|
checked={donationType === DonationType.FIFTY_DOLLARS}
|
||||||
|
onChange={onDonationChange}
|
||||||
|
/>
|
||||||
|
<label htmlFor="donate-50">$50</label>
|
||||||
|
</div>
|
||||||
|
<div className="radio-button">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="donate-100"
|
||||||
|
name="donation-type"
|
||||||
|
value={DonationType.ONE_HUNDRED_DOLLARS}
|
||||||
|
checked={donationType === DonationType.ONE_HUNDRED_DOLLARS}
|
||||||
|
onChange={onDonationChange}
|
||||||
|
/>
|
||||||
|
<label htmlFor="donate-100">$100</label>
|
||||||
|
</div>
|
||||||
|
<div className="radio-button">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id="donate-custom"
|
||||||
|
name="donation-type"
|
||||||
|
value={DonationType.CUSTOM}
|
||||||
|
checked={donationType === DonationType.CUSTOM}
|
||||||
|
onChange={onDonationChange}
|
||||||
|
/>
|
||||||
|
<label htmlFor="donate-custom">
|
||||||
|
Other: $
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min={2}
|
||||||
|
max={1000000}
|
||||||
|
id="donate-custom"
|
||||||
|
value={donationAmount}
|
||||||
|
disabled={donationType !== DonationType.CUSTOM}
|
||||||
|
onChange={(e) => setDonationAmount(+e.target.value)}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="step-button-container">
|
||||||
|
<button className="main-button" onClick={close}>
|
||||||
|
No thanks
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="main-button"
|
||||||
|
onClick={nextStep}
|
||||||
|
disabled={!(donationType && donationAmount)}
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DonationStepMethod;
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const DonationStepThanks: React.FC<{}> = () => {
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DonationStepThanks;
|
||||||
2
src/components/Modal/DonationModal/index.ts
Normal file
2
src/components/Modal/DonationModal/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
import DonationModal from "./DonationModal";
|
||||||
|
export default DonationModal;
|
||||||
85
src/components/Modal/Modal.css
Normal file
85
src/components/Modal/Modal.css
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
.modal-container {
|
||||||
|
position: fixed;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
inset: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.4);
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 400px;
|
||||||
|
min-height: 600px;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 2px solid black;
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 480px) {
|
||||||
|
.modal-content {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
width: unset;
|
||||||
|
min-height: unset;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-button-container {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.donation-details {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
padding: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.modal-close-button {
|
||||||
|
position: absolute;
|
||||||
|
top: 24px;
|
||||||
|
right: 24px;
|
||||||
|
background: transparent;
|
||||||
|
padding: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
box-sizing: content-box;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-titlebar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-titlebar > * {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-methods {
|
||||||
|
display: grid;
|
||||||
|
gap: 16px;
|
||||||
|
grid-template-rows: repeat(2, 1fr);
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-methods button {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
justify-content: center;
|
||||||
|
height: 96px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-methods button > svg {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
38
src/components/Modal/Modal.tsx
Normal file
38
src/components/Modal/Modal.tsx
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useRecoilState, useRecoilValue } from "recoil";
|
||||||
|
import { useLockBodyScroll } from "react-use";
|
||||||
|
import { useHotkeys } from "react-hotkeys-hook";
|
||||||
|
import { X } from "phosphor-react";
|
||||||
|
|
||||||
|
import { modalAtom, modalOpenAtom } from "state/atoms";
|
||||||
|
|
||||||
|
import "./Modal.css";
|
||||||
|
|
||||||
|
export interface ModalInstance {
|
||||||
|
close: () => void;
|
||||||
|
children?: JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Modal = (): JSX.Element | null => {
|
||||||
|
const Instance = useRecoilValue(modalAtom);
|
||||||
|
const [isModalOpen, setModalOpen] = useRecoilState(modalOpenAtom);
|
||||||
|
useHotkeys("esc", () => isModalOpen && setModalOpen(false), [isModalOpen]);
|
||||||
|
useLockBodyScroll(isModalOpen);
|
||||||
|
|
||||||
|
const close = () => setModalOpen(false);
|
||||||
|
|
||||||
|
if (!Instance || !isModalOpen) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="modal-container">
|
||||||
|
<div className="modal-content">
|
||||||
|
<button className="modal-close-button" onClick={close}>
|
||||||
|
<X size={32} />
|
||||||
|
</button>
|
||||||
|
<Instance close={close} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Modal;
|
||||||
3
src/components/Modal/index.ts
Normal file
3
src/components/Modal/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import Modal from "./Modal";
|
||||||
|
export default Modal;
|
||||||
|
|
||||||
@@ -1,17 +1,21 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { useRecoilValue } from "recoil";
|
import { useRecoilValue } from "recoil";
|
||||||
|
|
||||||
import { isDarkThemeSelector } from "../../state/selectors";
|
|
||||||
import { searchQueryAtom } from "../../state/atoms";
|
|
||||||
import { HourglassMedium, Question, SmileyXEyes } from "phosphor-react";
|
import { HourglassMedium, Question, SmileyXEyes } from "phosphor-react";
|
||||||
|
|
||||||
|
import { isDarkThemeSelector } from "state/selectors";
|
||||||
|
import { searchQueryAtom } from "state/atoms";
|
||||||
|
|
||||||
interface NoticeProps {
|
interface NoticeProps {
|
||||||
message?: string;
|
message?: string;
|
||||||
type?: "wait" | "help" | "warn" | "none";
|
type?: "wait" | "help" | "warn" | "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
const Notice: React.FC<NoticeProps> = ({ message, type = "warn", children }) => {
|
const Notice: React.FC<NoticeProps> = ({
|
||||||
|
message,
|
||||||
|
type = "warn",
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
const isDark = useRecoilValue(isDarkThemeSelector);
|
const isDark = useRecoilValue(isDarkThemeSelector);
|
||||||
const query = useRecoilValue(searchQueryAtom);
|
const query = useRecoilValue(searchQueryAtom);
|
||||||
|
|
||||||
|
|||||||
2
src/components/Notice/index.ts
Normal file
2
src/components/Notice/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
import Notice from "./Notice";
|
||||||
|
export default Notice;
|
||||||
@@ -5,7 +5,8 @@ import { useHotkeys } from "react-hotkeys-hook";
|
|||||||
import { Command, MagnifyingGlass, X, HourglassHigh } from "phosphor-react";
|
import { Command, MagnifyingGlass, X, HourglassHigh } from "phosphor-react";
|
||||||
import ReactGA from "react-ga";
|
import ReactGA from "react-ga";
|
||||||
|
|
||||||
import { searchQueryAtom } from "../../state/atoms";
|
import { searchQueryAtom } from "state/atoms";
|
||||||
|
|
||||||
import "./SearchInput.css";
|
import "./SearchInput.css";
|
||||||
|
|
||||||
const apple = /iPhone|iPod|iPad|Macintosh|MacIntel|MacPPC/i;
|
const apple = /iPhone|iPod|iPad|Macintosh|MacIntel|MacPPC/i;
|
||||||
@@ -19,7 +20,8 @@ type SearchInputProps = {};
|
|||||||
const SearchInput: React.FC<SearchInputProps> = () => {
|
const SearchInput: React.FC<SearchInputProps> = () => {
|
||||||
const [value, setValue] = useState<string>("");
|
const [value, setValue] = useState<string>("");
|
||||||
const [query, setQuery] = useRecoilState(searchQueryAtom);
|
const [query, setQuery] = useRecoilState(searchQueryAtom);
|
||||||
const inputRef = useRef<HTMLInputElement>() as MutableRefObject<HTMLInputElement>;
|
const inputRef =
|
||||||
|
useRef<HTMLInputElement>() as MutableRefObject<HTMLInputElement>;
|
||||||
|
|
||||||
useHotkeys("ctrl+k,cmd+k", (e) => {
|
useHotkeys("ctrl+k,cmd+k", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|||||||
2
src/components/SearchInput/index.ts
Normal file
2
src/components/SearchInput/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
import SearchInput from "./SearchInput";
|
||||||
|
export default SearchInput;
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
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;
|
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import React, { useCallback } from "react";
|
import React, { useCallback } from "react";
|
||||||
import { useRecoilState } from "recoil";
|
import { useRecoilState } from "recoil";
|
||||||
|
|
||||||
import { iconSizeAtom } from "../../state/atoms";
|
import { iconSizeAtom } from "state/atoms";
|
||||||
|
|
||||||
import "./SizeInput.css";
|
import "./SizeInput.css";
|
||||||
|
|
||||||
type SizeInputProps = {};
|
type SizeInputProps = {};
|
||||||
|
|||||||
2
src/components/SizeInput/index.ts
Normal file
2
src/components/SizeInput/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
import SizeInput from "./SizeInput";
|
||||||
|
export default SizeInput;
|
||||||
@@ -3,8 +3,9 @@ import { useRecoilState } from "recoil";
|
|||||||
import Select from "react-dropdown-select";
|
import Select from "react-dropdown-select";
|
||||||
import { PencilLine } from "phosphor-react";
|
import { PencilLine } from "phosphor-react";
|
||||||
|
|
||||||
import { iconWeightAtom } from "../../state/atoms";
|
import { iconWeightAtom } from "state/atoms";
|
||||||
import { IconStyle } from "../../lib";
|
import { IconStyle } from "lib";
|
||||||
|
|
||||||
import "./StyleInput.css";
|
import "./StyleInput.css";
|
||||||
|
|
||||||
type WeightOption = { key: string; value: IconStyle; icon: JSX.Element };
|
type WeightOption = { key: string; value: IconStyle; icon: JSX.Element };
|
||||||
|
|||||||
2
src/components/StyleInput/index.ts
Normal file
2
src/components/StyleInput/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
import StyleInput from "./StyleInput";
|
||||||
|
export default StyleInput;
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
|
import StyleInput from "components/StyleInput";
|
||||||
|
import SearchInput from "components/SearchInput";
|
||||||
|
import SizeInput from "components/SizeInput";
|
||||||
|
import ColorInput from "components/ColorInput";
|
||||||
|
|
||||||
import "./Toolbar.css";
|
import "./Toolbar.css";
|
||||||
import StyleInput from "../StyleInput/StyleInput";
|
|
||||||
import SearchInput from "../SearchInput/SearchInput";
|
|
||||||
import SizeInput from "../SizeInput/SizeInput";
|
|
||||||
import ColorInput from "../ColorInput/ColorInput";
|
|
||||||
import SettingsActions from "../SettingsActions/SettingsActions";
|
|
||||||
|
|
||||||
type ToolbarProps = {};
|
type ToolbarProps = {};
|
||||||
|
|
||||||
@@ -17,7 +17,6 @@ const Toolbar: React.FC<ToolbarProps> = () => {
|
|||||||
<SearchInput />
|
<SearchInput />
|
||||||
<SizeInput />
|
<SizeInput />
|
||||||
<ColorInput />
|
<ColorInput />
|
||||||
<SettingsActions />
|
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
);
|
);
|
||||||
|
|||||||
2
src/components/Toolbar/index.ts
Normal file
2
src/components/Toolbar/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
import Toolbar from "./Toolbar";
|
||||||
|
export default Toolbar;
|
||||||
8
src/hooks/index.ts
Normal file
8
src/hooks/index.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export { default as useDebounce } from "./useDebounce";
|
||||||
|
export { default as useGridSpans } from "./useGridSpans";
|
||||||
|
export { default as useIconParameters } from "./useIconParameters";
|
||||||
|
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";
|
||||||
@@ -34,30 +34,4 @@ 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
|
|
||||||
}, []);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
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]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,7 @@ import React from "react";
|
|||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
import { RecoilRoot } from "recoil";
|
import { RecoilRoot } from "recoil";
|
||||||
import * as serviceWorker from "./serviceWorker";
|
import * as serviceWorker from "./serviceWorker";
|
||||||
import App from "./components/App/App";
|
import App from "./components/App";
|
||||||
import ReactGA from "react-ga";
|
import ReactGA from "react-ga";
|
||||||
|
|
||||||
ReactGA.initialize("UA-179205759-1", { titleCase: false });
|
ReactGA.initialize("UA-179205759-1", { titleCase: false });
|
||||||
@@ -22,8 +22,8 @@ ReactDOM.render(
|
|||||||
// Learn more about service workers: https://bit.ly/CRA-PWA
|
// Learn more about service workers: https://bit.ly/CRA-PWA
|
||||||
serviceWorker.unregister();
|
serviceWorker.unregister();
|
||||||
|
|
||||||
|
// prettier-ignore
|
||||||
console.log(`
|
console.log(`
|
||||||
|
|
||||||
%c sphorphosphor %co%cspho
|
%c sphorphosphor %co%cspho
|
||||||
%c s%cphorphosphor %co%csphorpho%cs
|
%c s%cphorphosphor %co%csphorpho%cs
|
||||||
%c o %cp%chorphosphor %co%csphorphosph%co
|
%c o %cp%chorphosphor %co%csphorphosph%co
|
||||||
|
|||||||
3443
src/lib/icons.ts
3443
src/lib/icons.ts
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
|||||||
import { atom } from "recoil";
|
import { atom } from "recoil";
|
||||||
|
import { ModalInstance } from "../components/Modal/Modal";
|
||||||
import { IconStyle } from "../lib";
|
import { IconStyle } from "../lib";
|
||||||
|
|
||||||
export const searchQueryAtom = atom<string>({
|
export const searchQueryAtom = atom<string>({
|
||||||
@@ -13,7 +14,7 @@ export const iconWeightAtom = atom<IconStyle>({
|
|||||||
|
|
||||||
export const iconSizeAtom = atom<number>({
|
export const iconSizeAtom = atom<number>({
|
||||||
key: "iconSizeAtom",
|
key: "iconSizeAtom",
|
||||||
default: 32,
|
default: 48,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const iconColorAtom = atom<string>({
|
export const iconColorAtom = atom<string>({
|
||||||
@@ -25,3 +26,13 @@ export const iconPreviewOpenAtom = atom<string | false>({
|
|||||||
key: "iconPreviewOpenAtom",
|
key: "iconPreviewOpenAtom",
|
||||||
default: false,
|
default: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const modalAtom = atom<((props: ModalInstance) => JSX.Element) | null>({
|
||||||
|
key: "modalAtom",
|
||||||
|
default: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const modalOpenAtom = atom<boolean>({
|
||||||
|
key: "modalOpenAtom",
|
||||||
|
default: false,
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
import { selector, selectorFamily } from "recoil";
|
import { DefaultValue, selector, selectorFamily } from "recoil";
|
||||||
import TinyColor from "tinycolor2";
|
import TinyColor from "tinycolor2";
|
||||||
import Fuse from "fuse.js";
|
import Fuse from "fuse.js";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
searchQueryAtom,
|
searchQueryAtom,
|
||||||
iconWeightAtom,
|
|
||||||
iconSizeAtom,
|
|
||||||
iconColorAtom,
|
iconColorAtom,
|
||||||
|
modalAtom,
|
||||||
|
modalOpenAtom,
|
||||||
} from "./atoms";
|
} from "./atoms";
|
||||||
import { IconEntry, IconCategory } from "../lib";
|
import { IconEntry, IconCategory } from "../lib";
|
||||||
import { icons } from "../lib/icons";
|
import { icons } from "../lib/icons";
|
||||||
|
import { ModalInstance } from "../components/Modal/Modal";
|
||||||
|
|
||||||
const fuse = new Fuse(icons, {
|
const fuse = new Fuse(icons, {
|
||||||
keys: [{ name: "name", weight: 4 }, "tags", "categories"],
|
keys: [{ name: "name", weight: 4 }, "tags", "categories"],
|
||||||
@@ -74,12 +75,23 @@ export const isDarkThemeSelector = selector<boolean>({
|
|||||||
get: ({ get }) => TinyColor(get(iconColorAtom)).isLight(),
|
get: ({ get }) => TinyColor(get(iconColorAtom)).isLight(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const resetSettingsSelector = selector<null>({
|
export const modalSelector = selector<{
|
||||||
key: "resetSettings",
|
type: (props: ModalInstance) => JSX.Element;
|
||||||
get: () => null,
|
} | null>({
|
||||||
set: ({ reset }) => {
|
key: "openModalSelector",
|
||||||
reset(iconWeightAtom);
|
set: ({ set }, instance) => {
|
||||||
reset(iconSizeAtom);
|
if (instance instanceof DefaultValue || instance === null) {
|
||||||
reset(iconColorAtom);
|
set(modalAtom, null);
|
||||||
|
set(modalOpenAtom, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
set(modalAtom, () => instance.type!!);
|
||||||
|
set(modalOpenAtom, true);
|
||||||
|
},
|
||||||
|
get: ({ get }) => {
|
||||||
|
const currentModal = get(modalAtom);
|
||||||
|
if (!currentModal) return null;
|
||||||
|
return { type: currentModal };
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,36 +1,27 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es5",
|
|
||||||
"lib": [
|
|
||||||
"es6",
|
|
||||||
"dom",
|
|
||||||
"dom.iterable",
|
|
||||||
"esnext"
|
|
||||||
],
|
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"strict": true,
|
"baseUrl": "./src/",
|
||||||
|
"declaration": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"module": "esnext",
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"jsx": "react",
|
"jsx": "react",
|
||||||
"sourceMap": true,
|
"lib": ["es6", "dom", "dom.iterable", "esnext"],
|
||||||
"declaration": true,
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"noEmit": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"experimentalDecorators": true,
|
"resolveJsonModule": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"skipLibCheck": true,
|
||||||
"noEmit": true
|
"sourceMap": true,
|
||||||
|
"strict": true,
|
||||||
|
"target": "es5"
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["src"],
|
||||||
"src"
|
"exclude": ["node_modules", "build"]
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"node_modules",
|
|
||||||
"build"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user