mirror of
https://github.com/iconoir-icons/iconoir
synced 2026-03-14 14:05:44 +01:00
Refactor Lint/Format Setup (#499)
This commit is contained in:
parent
a1003eb8bd
commit
2bf8087c26
96 changed files with 3860 additions and 6768 deletions
|
|
@ -1,3 +0,0 @@
|
|||
dist/
|
||||
/examples/react-native/**/*.js
|
||||
/iconoir.com/out/
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
es2022: true,
|
||||
node: true,
|
||||
},
|
||||
extends: ['eslint:recommended', 'plugin:prettier/recommended'],
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
},
|
||||
rules: {
|
||||
'padding-line-between-statements': [
|
||||
'error',
|
||||
{
|
||||
blankLine: 'always',
|
||||
prev: [
|
||||
'block',
|
||||
'block-like',
|
||||
'class',
|
||||
'export',
|
||||
'import',
|
||||
'multiline-block-like',
|
||||
'multiline-expression',
|
||||
],
|
||||
next: '*',
|
||||
},
|
||||
{
|
||||
blankLine: 'always',
|
||||
prev: ['const', 'let'],
|
||||
next: ['block', 'block-like', 'class', 'export', 'import'],
|
||||
},
|
||||
{
|
||||
blankLine: 'always',
|
||||
prev: '*',
|
||||
next: ['multiline-block-like', 'multiline-expression', 'return'],
|
||||
},
|
||||
{
|
||||
blankLine: 'any',
|
||||
prev: ['export', 'import'],
|
||||
next: ['export', 'import'],
|
||||
},
|
||||
],
|
||||
'prettier/prettier': ['error'],
|
||||
},
|
||||
};
|
||||
0
FUNDING.yml → .github/FUNDING.yml
vendored
0
FUNDING.yml → .github/FUNDING.yml
vendored
14
.github/ISSUE_TEMPLATE/bug_report.md
vendored
14
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
|
@ -13,21 +13,21 @@ Before reporting an issue, please search to see if someone has filed a similar i
|
|||
|
||||
## Prerequisites
|
||||
|
||||
* Version:
|
||||
* Are you running from source/main:
|
||||
* Are you using a released build:
|
||||
* Operating system:
|
||||
- Version:
|
||||
- Are you running from source/main:
|
||||
- Are you using a released build:
|
||||
- Operating system:
|
||||
|
||||
## Step to reproduce
|
||||
|
||||
*(Type here)*
|
||||
_(Type here)_
|
||||
|
||||
### Actual behavior
|
||||
|
||||
## Any message or error
|
||||
|
||||
*(Type here)*
|
||||
_(Type here)_
|
||||
|
||||
## Additional info or screenshots
|
||||
|
||||
* Screenshots
|
||||
- Screenshots
|
||||
|
|
|
|||
6
.github/ISSUE_TEMPLATE/icon_request.md
vendored
6
.github/ISSUE_TEMPLATE/icon_request.md
vendored
|
|
@ -13,6 +13,6 @@ Before creating an icon request, please search to see if someone has requested t
|
|||
|
||||
## Icon Request
|
||||
|
||||
* Icon name:
|
||||
* Use case:
|
||||
* Screenshots of similar icons:
|
||||
- Icon name:
|
||||
- Use case:
|
||||
- Screenshots of similar icons:
|
||||
|
|
|
|||
28
.github/workflows/ci.yaml
vendored
Normal file
28
.github/workflows/ci.yaml
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v45
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
|
||||
- name: Run Linter
|
||||
run: pnpm exec eslint ${{ steps.changed-files.outputs.all_changed_files }}
|
||||
26
.github/workflows/website.yaml
vendored
26
.github/workflows/website.yaml
vendored
|
|
@ -1,15 +1,5 @@
|
|||
name: Website
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
concurrency:
|
||||
group: pages
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
workflow_run:
|
||||
|
|
@ -18,6 +8,16 @@ on:
|
|||
types:
|
||||
- completed
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
|
|
@ -33,7 +33,7 @@ jobs:
|
|||
run: pnpm run build react
|
||||
|
||||
- name: Build website
|
||||
run: ./node_modules/.bin/next build
|
||||
run: pnpm run build
|
||||
working-directory: iconoir.com
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
@ -44,12 +44,12 @@ jobs:
|
|||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: './iconoir.com/out'
|
||||
path: ./iconoir.com/out
|
||||
|
||||
deploy:
|
||||
name: Deploy
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
dist/
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"singleQuote": true,
|
||||
"quoteProps": "consistent"
|
||||
}
|
||||
50
.vscode/settings.json
vendored
Normal file
50
.vscode/settings.json
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
// Disable the default formatter, use eslint instead
|
||||
"prettier.enable": false,
|
||||
"editor.formatOnSave": false,
|
||||
|
||||
// Auto fix
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit",
|
||||
"source.organizeImports": "never"
|
||||
},
|
||||
|
||||
// Silent the stylistic rules in you IDE, but still auto fix them
|
||||
"eslint.rules.customizations": [
|
||||
{ "rule": "style/*", "severity": "off", "fixable": true },
|
||||
{ "rule": "format/*", "severity": "off", "fixable": true },
|
||||
{ "rule": "*-indent", "severity": "off", "fixable": true },
|
||||
{ "rule": "*-spacing", "severity": "off", "fixable": true },
|
||||
{ "rule": "*-spaces", "severity": "off", "fixable": true },
|
||||
{ "rule": "*-order", "severity": "off", "fixable": true },
|
||||
{ "rule": "*-dangle", "severity": "off", "fixable": true },
|
||||
{ "rule": "*-newline", "severity": "off", "fixable": true },
|
||||
{ "rule": "*quotes", "severity": "off", "fixable": true },
|
||||
{ "rule": "*semi", "severity": "off", "fixable": true }
|
||||
],
|
||||
|
||||
// Enable eslint for all supported languages
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"typescript",
|
||||
"typescriptreact",
|
||||
"vue",
|
||||
"html",
|
||||
"markdown",
|
||||
"json",
|
||||
"json5",
|
||||
"jsonc",
|
||||
"yaml",
|
||||
"toml",
|
||||
"xml",
|
||||
"gql",
|
||||
"graphql",
|
||||
"astro",
|
||||
"css",
|
||||
"less",
|
||||
"scss",
|
||||
"pcss",
|
||||
"postcss"
|
||||
]
|
||||
}
|
||||
|
|
@ -17,23 +17,23 @@ diverse, inclusive, and healthy community.
|
|||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
- Demonstrating empathy and kindness toward other people
|
||||
- Being respectful of differing opinions, viewpoints, and experiences
|
||||
- Giving and gracefully accepting constructive feedback
|
||||
- Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
- Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
- The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
- Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
- Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
|
@ -106,7 +106,7 @@ Violating these terms may lead to a permanent ban.
|
|||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
|
|
@ -118,11 +118,11 @@ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
|||
version 2.0, available at
|
||||
[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0].
|
||||
|
||||
Community Impact Guidelines were inspired by
|
||||
Community Impact Guidelines were inspired by
|
||||
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available
|
||||
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available
|
||||
at [https://www.contributor-covenant.org/translations][translations].
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
|
|
|||
|
|
@ -65,10 +65,7 @@ You can switch between icons from the right sidebar in the editor.
|
|||
Import the CSS file:
|
||||
|
||||
```html
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/gh/iconoir-icons/iconoir@main/css/iconoir.css"
|
||||
/>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/iconoir-icons/iconoir@main/css/iconoir.css" />
|
||||
```
|
||||
|
||||
Here is an example in HTML:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { Listr } from 'listr2';
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import process from 'node:process';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { Listr } from 'listr2';
|
||||
import { pascalCase, snakeCase } from 'scule';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
|
@ -37,6 +38,8 @@ const targets = {
|
|||
},
|
||||
};
|
||||
|
||||
const cliTargets = [];
|
||||
|
||||
const tasks = new Listr(
|
||||
[
|
||||
{
|
||||
|
|
@ -110,8 +113,6 @@ const tasks = new Listr(
|
|||
},
|
||||
);
|
||||
|
||||
const cliTargets = [];
|
||||
|
||||
// Get targets from command line arguments
|
||||
// (build all targets if no arguments given)
|
||||
for (const arg of process.argv.slice(2)) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import path from 'node:path';
|
||||
|
||||
export function generateImport(name, from) {
|
||||
if (Array.isArray(name)) name = `{${name.toString()}}`;
|
||||
if (Array.isArray(name))
|
||||
name = `{${name.toString()}}`;
|
||||
|
||||
return `import ${name} from "${from}";`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ export function getDts(path, content, options) {
|
|||
const _readFile = host.readFile;
|
||||
|
||||
host.readFile = (filename) => {
|
||||
if (filename === path) return content;
|
||||
if (filename === path)
|
||||
return content;
|
||||
|
||||
return _readFile(filename);
|
||||
};
|
||||
|
|
@ -24,7 +25,8 @@ export function getDts(path, content, options) {
|
|||
const dtsFilename = path.replace(/\.(m|c)?(ts|js)x?$/, '.d.$1ts');
|
||||
|
||||
host.writeFile = (filename, contents) => {
|
||||
if (filename === dtsFilename) output = contents;
|
||||
if (filename === dtsFilename)
|
||||
output = contents;
|
||||
};
|
||||
|
||||
const program = ts.createProgram([path], options, host);
|
||||
|
|
@ -40,6 +42,7 @@ export function getDts(path, content, options) {
|
|||
diagnostic.file,
|
||||
diagnostic.start,
|
||||
);
|
||||
|
||||
const message = ts.flattenDiagnosticMessageText(
|
||||
diagnostic.messageText,
|
||||
'\n',
|
||||
|
|
|
|||
|
|
@ -18,10 +18,9 @@ export default async (ctx, target) => {
|
|||
const variantCssContent = [header];
|
||||
|
||||
const cssTarget = (icon, suffixed) => {
|
||||
const iconName =
|
||||
suffixed && variant !== ctx.global.defaultVariant
|
||||
? icon.nameVariant
|
||||
: icon.name;
|
||||
const iconName = suffixed && variant !== ctx.global.defaultVariant
|
||||
? icon.nameVariant
|
||||
: icon.name;
|
||||
|
||||
return `.iconoir-${iconName}::before`;
|
||||
};
|
||||
|
|
@ -31,8 +30,8 @@ export default async (ctx, target) => {
|
|||
|
||||
const transformedContent = fileContent
|
||||
.replace(/\n/g, '')
|
||||
.replace(/(width|height)="[0-9]+px"/g, '')
|
||||
.replace(/[ ]+/g, ' ');
|
||||
.replace(/(width|height)="\d+px"/g, '')
|
||||
.replace(/ +/g, ' ');
|
||||
|
||||
const cssContent = `{mask-image:url('data:image/svg+xml;charset=utf-8,${transformedContent}');-webkit-mask-image:url('data:image/svg+xml;charset=utf-8,${transformedContent}');}`;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
const template = (name, svg) => `
|
||||
function template(name, svg) {
|
||||
return `
|
||||
import 'package:flutter/widgets.dart' as widgets;
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
|
|
@ -21,5 +22,6 @@ ${svg}''',
|
|||
);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
export default template;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import * as svgr from '@svgr/core';
|
||||
import * as esbuild from 'esbuild';
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import * as svgr from '@svgr/core';
|
||||
import * as esbuild from 'esbuild';
|
||||
import {
|
||||
generateExport,
|
||||
generateImport,
|
||||
|
|
@ -79,6 +79,7 @@ export default async (ctx, target) => {
|
|||
jsTarget.path,
|
||||
'IconoirContext.tsx',
|
||||
);
|
||||
|
||||
const iconoirContextDtsPath = path.join(
|
||||
jsTarget.path,
|
||||
`IconoirContext.${jsTarget.dtsExt}`,
|
||||
|
|
@ -106,10 +107,9 @@ export default async (ctx, target) => {
|
|||
const variantIndex = prepareIndex(jsTarget, variant);
|
||||
|
||||
for (const icon of icons) {
|
||||
const mainIndexComponentName =
|
||||
variant === ctx.global.defaultVariant
|
||||
? icon.pascalName
|
||||
: icon.pascalNameVariant;
|
||||
const mainIndexComponentName = variant === ctx.global.defaultVariant
|
||||
? icon.pascalName
|
||||
: icon.pascalNameVariant;
|
||||
|
||||
const jsPath = path.join(
|
||||
jsTarget.path,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
const template = (native) => {
|
||||
function template(native) {
|
||||
const useClientDirective = native ? '' : '"use client";';
|
||||
|
||||
const imports = [
|
||||
|
|
@ -11,8 +11,8 @@ ${useClientDirective}
|
|||
${imports}
|
||||
|
||||
type IconoirContextValue = Partial<${
|
||||
native ? 'SvgProps' : 'React.SVGProps<SVGSVGElement>'
|
||||
}>;
|
||||
native ? 'SvgProps' : 'React.SVGProps<SVGSVGElement>'
|
||||
}>;
|
||||
|
||||
export const IconoirContext = React.createContext<IconoirContextValue>({});
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ export function IconoirProvider({ iconProps, children }: IconoirProviderProps) {
|
|||
);
|
||||
}
|
||||
`;
|
||||
};
|
||||
}
|
||||
|
||||
export default template;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,12 +5,11 @@ export function getTemplate(native, iconoirContextPath) {
|
|||
variables.props[0].name = 'passedProps';
|
||||
|
||||
// Workaround to fix ref type for React Native
|
||||
if (native) {
|
||||
variables.props[1].typeAnnotation.typeAnnotation.typeParameters.params[0].typeName.name =
|
||||
'Svg';
|
||||
}
|
||||
if (native)
|
||||
variables.props[1].typeAnnotation.typeAnnotation.typeParameters.params[0].typeName.name = 'Svg';
|
||||
|
||||
const useClientDirective = native ? '' : '"use client";';
|
||||
|
||||
const iconoirContextImport = generateImport(
|
||||
['IconoirContext'],
|
||||
iconoirContextPath,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import { fromHtml } from 'hast-util-from-html';
|
||||
import { toHtml } from 'hast-util-to-html';
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { build } from 'vite';
|
||||
import dts from 'vite-plugin-dts';
|
||||
import { generateExport } from '../../lib/import-export.js';
|
||||
|
|
@ -44,10 +44,9 @@ export default async (ctx, target) => {
|
|||
|
||||
promises.push(generateIconFile(icon.path, vueFileName));
|
||||
|
||||
const mainIndexComponentName =
|
||||
variant === ctx.global.defaultVariant
|
||||
? icon.pascalName
|
||||
: icon.pascalNameVariant;
|
||||
const mainIndexComponentName = variant === ctx.global.defaultVariant
|
||||
? icon.pascalName
|
||||
: icon.pascalNameVariant;
|
||||
|
||||
mainIndexContent.push(
|
||||
generateExport(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
const template = (svg) => `<script lang="ts">
|
||||
function template(svg) {
|
||||
return `<script lang="ts">
|
||||
import { defineComponent, inject } from "vue-demi";
|
||||
import type { SVGAttributes } from "vue-demi";
|
||||
import providerKey from "../providerKey";
|
||||
|
|
@ -12,5 +13,6 @@ export default defineComponent<SVGAttributes>(() => {
|
|||
<template>
|
||||
${svg}
|
||||
</template>`;
|
||||
}
|
||||
|
||||
export default template;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import { updateYamlKey } from '@atomist/yaml-updater';
|
||||
/* eslint-disable no-console */
|
||||
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import process from 'node:process';
|
||||
import { updateYamlKey } from '@atomist/yaml-updater';
|
||||
import semver from 'semver';
|
||||
|
||||
const PACKAGE_BASE = '';
|
||||
|
|
@ -21,10 +24,10 @@ publishPubPackage('iconoir-flutter');
|
|||
function publishNpmPackage(name) {
|
||||
console.info('Publishing %s', name);
|
||||
|
||||
const packageJsonPath =
|
||||
name === 'iconoir'
|
||||
? 'package.json'
|
||||
: path.join('packages', name, 'package.json');
|
||||
const packageJsonPath = name === 'iconoir'
|
||||
? 'package.json'
|
||||
: path.join('packages', name, 'package.json');
|
||||
|
||||
const contents = JSON.parse(fs.readFileSync(packageJsonPath).toString());
|
||||
contents.version = newVersion;
|
||||
|
||||
|
|
|
|||
|
|
@ -3,10 +3,7 @@
|
|||
Import the CSS file:
|
||||
|
||||
```html
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/gh/iconoir-icons/iconoir@main/css/iconoir.css"
|
||||
/>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/iconoir-icons/iconoir@main/css/iconoir.css" />
|
||||
```
|
||||
|
||||
Here is an example in HTML:
|
||||
|
|
|
|||
116
eslint.config.js
Normal file
116
eslint.config.js
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
// @ts-check
|
||||
import antfu from '@antfu/eslint-config';
|
||||
import nextPlugin from '@next/eslint-plugin-next';
|
||||
import jsxA11yPlugin from 'eslint-plugin-jsx-a11y';
|
||||
import reactPlugin from 'eslint-plugin-react';
|
||||
import hooksPlugin from 'eslint-plugin-react-hooks';
|
||||
|
||||
export default antfu({
|
||||
typescript: true,
|
||||
formatters: true,
|
||||
stylistic: {
|
||||
semi: true,
|
||||
overrides: {
|
||||
'style/arrow-parens': 'error',
|
||||
'style/brace-style': ['error', '1tbs', { allowSingleLine: true }],
|
||||
},
|
||||
},
|
||||
javascript: {
|
||||
overrides: {
|
||||
'antfu/no-top-level-await': 'off',
|
||||
},
|
||||
},
|
||||
ignores: [
|
||||
'css/*.css',
|
||||
'examples/react-native/',
|
||||
'iconoir.com/out/',
|
||||
'packages/iconoir-flutter/.dart_tool/',
|
||||
'packages/iconoir-flutter/build/',
|
||||
'packages/iconoir-flutter/example/',
|
||||
|
||||
],
|
||||
rules: {
|
||||
'style/padding-line-between-statements': [
|
||||
'error',
|
||||
{
|
||||
blankLine: 'always',
|
||||
prev: [
|
||||
'block',
|
||||
'block-like',
|
||||
'cjs-export',
|
||||
'class',
|
||||
'multiline-block-like',
|
||||
'multiline-const',
|
||||
'multiline-expression',
|
||||
'multiline-let',
|
||||
'multiline-var',
|
||||
],
|
||||
next: '*',
|
||||
},
|
||||
{
|
||||
blankLine: 'always',
|
||||
prev: ['const', 'let'],
|
||||
next: [
|
||||
'block',
|
||||
'block-like',
|
||||
'cjs-export',
|
||||
'class',
|
||||
],
|
||||
},
|
||||
{
|
||||
blankLine: 'always',
|
||||
prev: '*',
|
||||
next: [
|
||||
'multiline-block-like',
|
||||
'multiline-const',
|
||||
'multiline-expression',
|
||||
'multiline-let',
|
||||
'multiline-var',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
}, {
|
||||
files: ['iconoir.com/**'],
|
||||
plugins: {
|
||||
'@next/next': nextPlugin,
|
||||
'react': reactPlugin,
|
||||
'react-hooks': hooksPlugin,
|
||||
'jsx-a11y': jsxA11yPlugin,
|
||||
},
|
||||
settings: {
|
||||
next: {
|
||||
rootDir: 'iconoir.com/',
|
||||
},
|
||||
react: {
|
||||
version: 'detect',
|
||||
},
|
||||
|
||||
},
|
||||
// @ts-ignore
|
||||
rules: {
|
||||
...nextPlugin.configs.recommended.rules,
|
||||
...nextPlugin.configs['core-web-vitals'].rules,
|
||||
...reactPlugin.configs.recommended.rules,
|
||||
...hooksPlugin.configs.recommended.rules,
|
||||
|
||||
// rules from "eslint-config-next"
|
||||
'import/no-anonymous-default-export': 'warn',
|
||||
'react/no-unknown-property': 'off',
|
||||
'react/react-in-jsx-scope': 'off',
|
||||
'react/prop-types': 'off',
|
||||
'jsx-a11y/alt-text': [
|
||||
'warn',
|
||||
{
|
||||
elements: ['img'],
|
||||
img: ['Image'],
|
||||
},
|
||||
],
|
||||
'jsx-a11y/aria-props': 'warn',
|
||||
'jsx-a11y/aria-proptypes': 'warn',
|
||||
'jsx-a11y/aria-unsupported-elements': 'warn',
|
||||
'jsx-a11y/role-has-required-aria-props': 'warn',
|
||||
'jsx-a11y/role-supports-aria-props': 'warn',
|
||||
'react/jsx-no-target-blank': 'off',
|
||||
},
|
||||
});
|
||||
|
|
@ -1,26 +1,26 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"incremental": true,
|
||||
"target": "es5",
|
||||
"jsx": "preserve",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
},
|
||||
"resolveJsonModule": true,
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"skipLibCheck": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "example-vue",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "run-p type-check \"build-only {@}\" --",
|
||||
"build-only": "vite build",
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||
"exclude": ["src/**/__tests__/*"],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||
"exclude": ["src/**/__tests__/*"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
|
|
@ -7,5 +6,6 @@
|
|||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
}
|
||||
]
|
||||
],
|
||||
"files": []
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
{
|
||||
"extends": "@tsconfig/node18/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": [
|
||||
"vite.config.*",
|
||||
"vitest.config.*",
|
||||
"cypress.config.*",
|
||||
"nightwatch.conf.*",
|
||||
"playwright.config.*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"types": ["node"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import { fileURLToPath, URL } from 'node:url';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
|
|
@ -10,7 +9,7 @@ export default defineConfig({
|
|||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
}
|
||||
}
|
||||
})
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"extends": ["next/core-web-vitals"],
|
||||
"settings": {
|
||||
"next": {
|
||||
"rootDir": "iconoir.com"
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
"react/no-unescaped-entities": ["off"]
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.js"],
|
||||
"parser": "espree",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "latest"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,30 +1,6 @@
|
|||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export function Ad() {
|
||||
const containerRef = React.useRef<HTMLDivElement>(null);
|
||||
const addedScript = React.useRef(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
const container = containerRef.current;
|
||||
|
||||
if (container && !addedScript.current) {
|
||||
addedScript.current = true;
|
||||
const script = document.createElement('script');
|
||||
script.async = true;
|
||||
script.type = 'text/javascript';
|
||||
|
||||
script.src =
|
||||
'//cdn.carbonads.com/carbon.js?serve=CESDK5QJ&placement=iconoircom';
|
||||
|
||||
script.id = '_carbonads_js';
|
||||
container.appendChild(script);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return <AdContainer ref={containerRef} />;
|
||||
}
|
||||
|
||||
const AdContainer = styled.div`
|
||||
#carbonads {
|
||||
margin: 24px 0 0 0;
|
||||
|
|
@ -54,3 +30,24 @@ const AdContainer = styled.div`
|
|||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export function Ad() {
|
||||
const containerRef = React.useRef<HTMLDivElement>(null);
|
||||
const addedScript = React.useRef(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
const container = containerRef.current;
|
||||
|
||||
if (container && !addedScript.current) {
|
||||
addedScript.current = true;
|
||||
const script = document.createElement('script');
|
||||
script.async = true;
|
||||
script.type = 'text/javascript';
|
||||
script.src = '//cdn.carbonads.com/carbon.js?serve=CESDK5QJ&placement=iconoircom';
|
||||
script.id = '_carbonads_js';
|
||||
container.appendChild(script);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return <AdContainer ref={containerRef} />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import anime from 'animejs';
|
||||
import React from 'react';
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
type SetInstances = (instances: anime.AnimeInstance[]) => void;
|
||||
|
||||
function playWithLines1(setInstances: SetInstances): anime.AnimeInstance[] {
|
||||
|
|
@ -11,7 +10,7 @@ function playWithLines1(setInstances: SetInstances): anime.AnimeInstance[] {
|
|||
strokeDashoffset: [anime.setDashoffset, 0],
|
||||
easing: 'easeInOutSine',
|
||||
duration: 1500,
|
||||
delay: function (el, i) {
|
||||
delay(_el, i) {
|
||||
return i * 250;
|
||||
},
|
||||
direction: 'alternate',
|
||||
|
|
@ -27,7 +26,7 @@ function playWithLines2(setInstances: SetInstances): anime.AnimeInstance[] {
|
|||
strokeDashoffset: [anime.setDashoffset, 0],
|
||||
easing: 'easeInOutSine',
|
||||
duration: 1500,
|
||||
delay: function (el, i) {
|
||||
delay(_el, i) {
|
||||
return i * 250;
|
||||
},
|
||||
direction: 'alternate',
|
||||
|
|
@ -43,7 +42,7 @@ function playWithLines3(setInstances: SetInstances): anime.AnimeInstance[] {
|
|||
strokeDashoffset: [anime.setDashoffset, 0],
|
||||
easing: 'easeInOutSine',
|
||||
duration: 1500,
|
||||
delay: function (el, i) {
|
||||
delay(_el, i) {
|
||||
return i * 250;
|
||||
},
|
||||
direction: 'alternate',
|
||||
|
|
@ -68,7 +67,7 @@ function playWithLines4(setInstances: SetInstances): anime.AnimeInstance[] {
|
|||
strokeDashoffset: [anime.setDashoffset, 0],
|
||||
easing: 'easeInOutSine',
|
||||
duration: 1500,
|
||||
delay: function (el, i) {
|
||||
delay(_el, i) {
|
||||
return i * 250;
|
||||
},
|
||||
direction: 'alternate',
|
||||
|
|
|
|||
|
|
@ -8,115 +8,12 @@ import {
|
|||
import { media } from '../lib/responsive';
|
||||
import { Text14 } from './Typography';
|
||||
|
||||
export function AvailableFor() {
|
||||
const { ref, width } = useResizeObserver();
|
||||
|
||||
return (
|
||||
<>
|
||||
<MobileHeader>Available For</MobileHeader>
|
||||
<AvailableForOuter>
|
||||
<AvailableForContainer $contentWidth={width || 0} ref={ref}>
|
||||
<DesktopHeader>Available for</DesktopHeader>
|
||||
<a href={LIBRARY_LINKS.React} target={'_blank'} rel={'noreferrer'}>
|
||||
<AvailableForImage
|
||||
src={'/logo-react.svg'}
|
||||
alt={'React Logo'}
|
||||
title={'React'}
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href={'https://github.com/iconoir-icons/iconoir#swift-package'}
|
||||
target={'_blank'}
|
||||
rel={'nofollow noreferrer'}
|
||||
>
|
||||
<AvailableForImage
|
||||
src={'/logo-swift.svg'}
|
||||
alt={'Swift Logo'}
|
||||
title={'Swift'}
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href={LIBRARY_LINKS.Flutter}
|
||||
target={'_blank'}
|
||||
rel={'nofollow noreferrer'}
|
||||
>
|
||||
<AvailableForImage
|
||||
src={'/logo-flutter.svg'}
|
||||
alt={'Flutter Logo'}
|
||||
title={'Flutter'}
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href={LIBRARY_LINKS.Figma}
|
||||
target={'_blank'}
|
||||
rel={'nofollow noreferrer'}
|
||||
>
|
||||
<AvailableForImage
|
||||
src={'/logo-figma.svg'}
|
||||
alt={'Figma Logo'}
|
||||
title={'Figma'}
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href={LIBRARY_LINKS.ReactNative}
|
||||
target={'_blank'}
|
||||
rel={'nofollow noreferrer'}
|
||||
>
|
||||
<AvailableForImage
|
||||
src={'/logo-react-native.svg'}
|
||||
alt={'React Native Logo'}
|
||||
title={'React Native'}
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href={LIBRARY_LINKS.Vue}
|
||||
target={'_blank'}
|
||||
rel={'nofollow noreferrer'}
|
||||
>
|
||||
<AvailableForImage
|
||||
src={'/logo-vue.svg'}
|
||||
alt={'Vue Logo'}
|
||||
title={'Vue'}
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href={LIBRARY_LINKS.Framer}
|
||||
target={'_blank'}
|
||||
rel={'nofollow noreferrer'}
|
||||
>
|
||||
<AvailableForImage
|
||||
src={'/logo-framer.svg'}
|
||||
alt={'Framer Logo'}
|
||||
title={'Framer'}
|
||||
/>
|
||||
</a>
|
||||
<AreYouUsing>
|
||||
<a
|
||||
href={SUGGEST_LIBRARY_LINK}
|
||||
target={'_blank'}
|
||||
rel={'nofollow noreferrer'}
|
||||
>
|
||||
<Text14>More?</Text14>
|
||||
</a>
|
||||
<a
|
||||
href={FEEDBACK_LINK}
|
||||
target={'_blank'}
|
||||
rel={'nofollow noreferrer'}
|
||||
>
|
||||
<Text14>Are you using the library?</Text14>
|
||||
</a>
|
||||
</AreYouUsing>
|
||||
</AvailableForContainer>
|
||||
</AvailableForOuter>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const AreYouUsing = styled.div`
|
||||
* {
|
||||
white-space: nowrap;
|
||||
}
|
||||
`;
|
||||
|
||||
const MobileHeader = styled(Text14)`
|
||||
&&& {
|
||||
display: block;
|
||||
|
|
@ -127,6 +24,7 @@ const MobileHeader = styled(Text14)`
|
|||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const DesktopHeader = styled(Text14)`
|
||||
&&& {
|
||||
display: none;
|
||||
|
|
@ -135,6 +33,7 @@ const DesktopHeader = styled(Text14)`
|
|||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const AvailableForAnimation = keyframes`
|
||||
5% {
|
||||
transform: translateX(0);
|
||||
|
|
@ -149,6 +48,7 @@ const AvailableForAnimation = keyframes`
|
|||
transform: translateX(0);
|
||||
}
|
||||
`;
|
||||
|
||||
const AvailableForOuter = styled.div`
|
||||
max-width: 100vw;
|
||||
margin: 16px -30px 70px -30px;
|
||||
|
|
@ -159,6 +59,7 @@ const AvailableForOuter = styled.div`
|
|||
padding: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
const AvailableForContainer = styled.div<{ $contentWidth: number }>`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -166,8 +67,8 @@ const AvailableForContainer = styled.div<{ $contentWidth: number }>`
|
|||
width: max-content;
|
||||
--content-width: ${(props) => props.$contentWidth}px;
|
||||
${(props) =>
|
||||
props.$contentWidth &&
|
||||
css`
|
||||
props.$contentWidth
|
||||
&& css`
|
||||
animation: ${AvailableForAnimation} 40s cubic-bezier(0.37, 0, 0.63, 1)
|
||||
infinite;
|
||||
`}
|
||||
|
|
@ -185,6 +86,7 @@ const AvailableForContainer = styled.div<{ $contentWidth: number }>`
|
|||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const AvailableForImage = styled.img`
|
||||
height: 40px;
|
||||
display: block;
|
||||
|
|
@ -197,3 +99,107 @@ const AvailableForImage = styled.img`
|
|||
height: 50px;
|
||||
}
|
||||
`;
|
||||
|
||||
export function AvailableFor() {
|
||||
const { ref, width } = useResizeObserver();
|
||||
|
||||
return (
|
||||
<>
|
||||
<MobileHeader>Available For</MobileHeader>
|
||||
<AvailableForOuter>
|
||||
<AvailableForContainer $contentWidth={width || 0} ref={ref}>
|
||||
<DesktopHeader>Available for</DesktopHeader>
|
||||
<a href={LIBRARY_LINKS.React} target="_blank" rel="noreferrer">
|
||||
<AvailableForImage
|
||||
src="/logo-react.svg"
|
||||
alt="React Logo"
|
||||
title="React"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/iconoir-icons/iconoir#swift-package"
|
||||
target="_blank"
|
||||
rel="nofollow noreferrer"
|
||||
>
|
||||
<AvailableForImage
|
||||
src="/logo-swift.svg"
|
||||
alt="Swift Logo"
|
||||
title="Swift"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href={LIBRARY_LINKS.Flutter}
|
||||
target="_blank"
|
||||
rel="nofollow noreferrer"
|
||||
>
|
||||
<AvailableForImage
|
||||
src="/logo-flutter.svg"
|
||||
alt="Flutter Logo"
|
||||
title="Flutter"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href={LIBRARY_LINKS.Figma}
|
||||
target="_blank"
|
||||
rel="nofollow noreferrer"
|
||||
>
|
||||
<AvailableForImage
|
||||
src="/logo-figma.svg"
|
||||
alt="Figma Logo"
|
||||
title="Figma"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href={LIBRARY_LINKS.ReactNative}
|
||||
target="_blank"
|
||||
rel="nofollow noreferrer"
|
||||
>
|
||||
<AvailableForImage
|
||||
src="/logo-react-native.svg"
|
||||
alt="React Native Logo"
|
||||
title="React Native"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href={LIBRARY_LINKS.Vue}
|
||||
target="_blank"
|
||||
rel="nofollow noreferrer"
|
||||
>
|
||||
<AvailableForImage
|
||||
src="/logo-vue.svg"
|
||||
alt="Vue Logo"
|
||||
title="Vue"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href={LIBRARY_LINKS.Framer}
|
||||
target="_blank"
|
||||
rel="nofollow noreferrer"
|
||||
>
|
||||
<AvailableForImage
|
||||
src="/logo-framer.svg"
|
||||
alt="Framer Logo"
|
||||
title="Framer"
|
||||
/>
|
||||
</a>
|
||||
<AreYouUsing>
|
||||
<a
|
||||
href={SUGGEST_LIBRARY_LINK}
|
||||
target="_blank"
|
||||
rel="nofollow noreferrer"
|
||||
>
|
||||
<Text14>More?</Text14>
|
||||
</a>
|
||||
<a
|
||||
href={FEEDBACK_LINK}
|
||||
target="_blank"
|
||||
rel="nofollow noreferrer"
|
||||
>
|
||||
<Text14>Are you using the library?</Text14>
|
||||
</a>
|
||||
</AreYouUsing>
|
||||
</AvailableForContainer>
|
||||
</AvailableForOuter>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ export const LargeButton = styled(ResetButton)`
|
|||
}
|
||||
`;
|
||||
|
||||
export const Button = styled(LargeButton)`
|
||||
const Button = styled(LargeButton)`
|
||||
&&&& {
|
||||
height: 40px;
|
||||
font-size: 13px;
|
||||
|
|
|
|||
|
|
@ -2,25 +2,6 @@ import styled from 'styled-components';
|
|||
import { media } from '../lib/responsive';
|
||||
import { Text15 } from './Typography';
|
||||
|
||||
export interface CategoryRowProps {
|
||||
category: string;
|
||||
numIcons: number;
|
||||
style?: any;
|
||||
}
|
||||
export function CategoryRow({ category, numIcons, style }: CategoryRowProps) {
|
||||
return (
|
||||
<Container style={style}>
|
||||
<InnerContainer>
|
||||
<Title>{category}</Title>
|
||||
<Text15>
|
||||
{numIcons} Icon{numIcons === 1 ? '' : 's'}
|
||||
</Text15>
|
||||
<Separator />
|
||||
</InnerContainer>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
const InnerContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -32,6 +13,7 @@ const InnerContainer = styled.div`
|
|||
margin-right: 10px;
|
||||
}
|
||||
`;
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
|
|
@ -41,6 +23,7 @@ const Container = styled.div`
|
|||
padding-bottom: 40px;
|
||||
}
|
||||
`;
|
||||
|
||||
const Title = styled(Text15)`
|
||||
&&& {
|
||||
font-weight: 700;
|
||||
|
|
@ -50,8 +33,32 @@ const Title = styled(Text15)`
|
|||
padding: 6px 10px;
|
||||
}
|
||||
`;
|
||||
|
||||
const Separator = styled.div`
|
||||
height: 1px;
|
||||
flex: 1;
|
||||
background: var(--g6);
|
||||
`;
|
||||
|
||||
export interface CategoryRowProps {
|
||||
category: string;
|
||||
numIcons: number;
|
||||
style?: any;
|
||||
}
|
||||
|
||||
export function CategoryRow({ category, numIcons, style }: CategoryRowProps) {
|
||||
return (
|
||||
<Container style={style}>
|
||||
<InnerContainer>
|
||||
<Title>{category}</Title>
|
||||
<Text15>
|
||||
{numIcons}
|
||||
{' '}
|
||||
Icon
|
||||
{numIcons === 1 ? '' : 's'}
|
||||
</Text15>
|
||||
<Separator />
|
||||
</InnerContainer>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,72 +1,15 @@
|
|||
import type { MDXRemoteSerializeResult } from 'next-mdx-remote';
|
||||
import { BoxIso } from 'iconoir-react';
|
||||
import moment from 'moment';
|
||||
import { MDXRemoteSerializeResult } from 'next-mdx-remote';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { MDXRemote } from './MDXRemote';
|
||||
import { media } from '../lib/responsive';
|
||||
import { Code, Text15, Text18 } from './Typography';
|
||||
import { CopyButton } from './Button';
|
||||
import { MDXRemote } from './MDXRemote';
|
||||
import { Code, Text15, Text18 } from './Typography';
|
||||
|
||||
const EXPAND_HEIGHT = 400;
|
||||
|
||||
export interface ChangelogEntryProps {
|
||||
name: string;
|
||||
url: string;
|
||||
created_at: string;
|
||||
body?: MDXRemoteSerializeResult;
|
||||
}
|
||||
export function ChangelogEntry({
|
||||
name,
|
||||
url,
|
||||
body,
|
||||
created_at,
|
||||
}: ChangelogEntryProps) {
|
||||
const [expanded, setExpanded] = React.useState(false);
|
||||
const [shouldExpand, setShouldExpand] = React.useState(false);
|
||||
const containerRef = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (
|
||||
containerRef.current &&
|
||||
containerRef.current.clientHeight > EXPAND_HEIGHT
|
||||
) {
|
||||
setShouldExpand(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Container ref={containerRef}>
|
||||
<ContainerLeft>
|
||||
<ContainerIcon>
|
||||
<BoxIso />
|
||||
</ContainerIcon>
|
||||
<TitleContainer>
|
||||
<a
|
||||
href={url}
|
||||
target={'_blank'}
|
||||
rel={'noreferrer'}
|
||||
style={{ textDecoration: 'none' }}
|
||||
>
|
||||
<EntryTitle>{name}</EntryTitle>
|
||||
</a>
|
||||
<Text15>{moment(created_at).format('MMM DD, YYYY')}</Text15>
|
||||
</TitleContainer>
|
||||
</ContainerLeft>
|
||||
<EntryBody $expanded={expanded}>
|
||||
{body ? <MDXRemote {...body} /> : 'No changelog'}
|
||||
{shouldExpand ? (
|
||||
<ExpandContainer>
|
||||
<CopyButton onClick={() => setExpanded((e) => !e)}>
|
||||
{expanded ? 'Collapse' : 'Expand'}
|
||||
</CopyButton>
|
||||
</ExpandContainer>
|
||||
) : null}
|
||||
</EntryBody>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
margin: 40px 0;
|
||||
display: flex;
|
||||
|
|
@ -79,6 +22,7 @@ const Container = styled.div`
|
|||
margin: 24px 0;
|
||||
}
|
||||
`;
|
||||
|
||||
const ContainerLeft = styled.div`
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
|
|
@ -88,25 +32,30 @@ const ContainerLeft = styled.div`
|
|||
margin-right: 30px;
|
||||
}
|
||||
`;
|
||||
|
||||
const ContainerIcon = styled.div`
|
||||
font-size: 18px;
|
||||
color: var(--black);
|
||||
margin-right: 18px;
|
||||
`;
|
||||
|
||||
const TitleContainer = styled.div`
|
||||
width: 100px;
|
||||
`;
|
||||
|
||||
const EntryTitle = styled(Text18)`
|
||||
&&& {
|
||||
color: var(--black);
|
||||
font-weight: 700;
|
||||
}
|
||||
`;
|
||||
|
||||
const ExpandContainer = styled.div`
|
||||
position: absolute;
|
||||
bottom: 16px;
|
||||
right: 23px;
|
||||
`;
|
||||
|
||||
const EntryBody = styled(Code)<{ $expanded?: boolean }>`
|
||||
&&& {
|
||||
flex: 1;
|
||||
|
|
@ -135,3 +84,63 @@ const EntryBody = styled(Code)<{ $expanded?: boolean }>`
|
|||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export interface ChangelogEntryProps {
|
||||
name: string;
|
||||
url: string;
|
||||
created_at: string;
|
||||
body?: MDXRemoteSerializeResult;
|
||||
}
|
||||
|
||||
export function ChangelogEntry({
|
||||
name,
|
||||
url,
|
||||
body,
|
||||
created_at,
|
||||
}: ChangelogEntryProps) {
|
||||
const [expanded, setExpanded] = React.useState(false);
|
||||
const [shouldExpand, setShouldExpand] = React.useState(false);
|
||||
const containerRef = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (
|
||||
containerRef.current
|
||||
&& containerRef.current.clientHeight > EXPAND_HEIGHT
|
||||
) {
|
||||
setShouldExpand(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Container ref={containerRef}>
|
||||
<ContainerLeft>
|
||||
<ContainerIcon>
|
||||
<BoxIso />
|
||||
</ContainerIcon>
|
||||
<TitleContainer>
|
||||
<a
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
style={{ textDecoration: 'none' }}
|
||||
>
|
||||
<EntryTitle>{name}</EntryTitle>
|
||||
</a>
|
||||
<Text15>{moment(created_at).format('MMM DD, YYYY')}</Text15>
|
||||
</TitleContainer>
|
||||
</ContainerLeft>
|
||||
<EntryBody $expanded={expanded}>
|
||||
{body ? <MDXRemote {...body} /> : 'No changelog'}
|
||||
{shouldExpand
|
||||
? (
|
||||
<ExpandContainer>
|
||||
<CopyButton onClick={() => setExpanded((e) => !e)}>
|
||||
{expanded ? 'Collapse' : 'Expand'}
|
||||
</CopyButton>
|
||||
</ExpandContainer>
|
||||
)
|
||||
: null}
|
||||
</EntryBody>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,33 +2,34 @@ import Link from 'next/link';
|
|||
import styled from 'styled-components';
|
||||
import { Text13 } from './Typography';
|
||||
|
||||
const Container = styled(Text13)`
|
||||
&&& {
|
||||
color: var(--g1);
|
||||
font-weight: 700;
|
||||
background: var(--g5);
|
||||
line-height: 1;
|
||||
padding: 7px 16px;
|
||||
border-radius: 200px;
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
transition:
|
||||
color 0.1s linear,
|
||||
background 0.1s linear;
|
||||
&:hover {
|
||||
background: var(--black);
|
||||
color: var(--white);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export interface CurrentVersionProps {
|
||||
version: string;
|
||||
}
|
||||
|
||||
export function CurrentVersion({ version }: CurrentVersionProps) {
|
||||
return (
|
||||
<Link href={'/docs/changelog'} passHref legacyBehavior>
|
||||
<Container as={'a'}>{version}</Container>
|
||||
<Link href="/docs/changelog" passHref legacyBehavior>
|
||||
<Container as="a">{version}</Container>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
const Container = styled(Text13)`
|
||||
&&& {
|
||||
color: var(--g1);
|
||||
font-weight: 700;
|
||||
background: var(--g5);
|
||||
line-height: 1;
|
||||
padding: 7px 16px;
|
||||
border-radius: 200px;
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
transition:
|
||||
color 0.1s linear,
|
||||
background 0.1s linear;
|
||||
&:hover {
|
||||
background: var(--black);
|
||||
color: var(--white);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -1,16 +1,62 @@
|
|||
import type { IconListCustomizations } from './IconList';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { DEFAULT_CUSTOMIZATIONS, IconListCustomizations } from './IconList';
|
||||
import { media } from '../lib/responsive';
|
||||
import { DEFAULT_CUSTOMIZATIONS } from './IconList';
|
||||
import { ColorButton, ColorInput } from './Input';
|
||||
import { Slider } from './Slider';
|
||||
import { Text13, Text15 } from './Typography';
|
||||
import { media } from '../lib/responsive';
|
||||
|
||||
const CustomizationBox = styled.div`
|
||||
background-color: var(--g7);
|
||||
width: 84%;
|
||||
padding: 8%;
|
||||
border-radius: 10px;
|
||||
margin: 24px 0;
|
||||
display: none;
|
||||
${media.md} {
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
|
||||
const Header = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 30px;
|
||||
border-bottom: solid 1px var(--g6);
|
||||
padding-bottom: 10px;
|
||||
`;
|
||||
|
||||
const Field = styled.div`
|
||||
margin-bottom: 18px;
|
||||
`;
|
||||
|
||||
const HorizontalField = styled(Field)`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
`;
|
||||
|
||||
const ResetButton = styled(Field)`
|
||||
&&& {
|
||||
margin: initial;
|
||||
text-decoration: underline;
|
||||
color: var(--dark-gray);
|
||||
font-size: 13px;
|
||||
|
||||
&:hover {
|
||||
color: var(--black);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export interface CustomizationEditorProps {
|
||||
customizations: IconListCustomizations;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
onChange: (customizations: IconListCustomizations) => void;
|
||||
}
|
||||
|
||||
export function CustomizationEditor({
|
||||
customizations,
|
||||
onChange,
|
||||
|
|
@ -18,6 +64,7 @@ export function CustomizationEditor({
|
|||
const [, startTransition] = (React as any).useTransition();
|
||||
const [color, setColor] = React.useState(customizations.hexColor);
|
||||
const [size, setSize] = React.useState(customizations.size);
|
||||
|
||||
const [strokeWidth, setStrokeWidth] = React.useState(
|
||||
customizations.strokeWidth,
|
||||
);
|
||||
|
|
@ -50,7 +97,7 @@ export function CustomizationEditor({
|
|||
</Header>
|
||||
<Field>
|
||||
<Slider
|
||||
label={'Optical Size'}
|
||||
label="Optical Size"
|
||||
minValue={16}
|
||||
maxValue={64}
|
||||
value={[size]}
|
||||
|
|
@ -63,7 +110,7 @@ export function CustomizationEditor({
|
|||
</Field>
|
||||
<Field>
|
||||
<Slider
|
||||
label={'Stroke Weight'}
|
||||
label="Stroke Weight"
|
||||
minValue={0.5}
|
||||
maxValue={3}
|
||||
value={[strokeWidth]}
|
||||
|
|
@ -78,7 +125,7 @@ export function CustomizationEditor({
|
|||
<HorizontalField>
|
||||
<Text13>Color</Text13>
|
||||
<ColorInput
|
||||
type={'color'}
|
||||
type="color"
|
||||
value={color}
|
||||
onChange={(e) => {
|
||||
setColor(e.target.value);
|
||||
|
|
@ -91,44 +138,3 @@ export function CustomizationEditor({
|
|||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const CustomizationBox = styled.div`
|
||||
background-color: var(--g7);
|
||||
width: 84%;
|
||||
padding: 8%;
|
||||
border-radius: 10px;
|
||||
margin: 24px 0;
|
||||
display: none;
|
||||
${media.md} {
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
const Header = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 30px;
|
||||
border-bottom: solid 1px var(--g6);
|
||||
padding-bottom: 10px;
|
||||
`;
|
||||
const Field = styled.div`
|
||||
margin-bottom: 18px;
|
||||
`;
|
||||
const HorizontalField = styled(Field)`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
`;
|
||||
const ResetButton = styled(Field)`
|
||||
&&& {
|
||||
margin: initial;
|
||||
text-decoration: underline;
|
||||
color: var(--dark-gray);
|
||||
font-size: 13px;
|
||||
|
||||
&:hover {
|
||||
color: var(--black);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -1,15 +1,104 @@
|
|||
import type { DocumentationItem } from '../pages/docs/[...slug]';
|
||||
import { NavArrowUp } from 'iconoir-react';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { DocumentationItem } from '../pages/docs/[...slug]';
|
||||
import { media } from '../lib/responsive';
|
||||
|
||||
const HeaderItemIcon = styled.div<{ $active?: boolean }>`
|
||||
font-size: 13px;
|
||||
transition: transform 0.25s linear;
|
||||
transform: rotate(${(props) => (props.$active ? 180 : 0)}deg);
|
||||
margin-right: 7px;
|
||||
position: relative;
|
||||
top: 6px;
|
||||
svg {
|
||||
display: block;
|
||||
}
|
||||
${media.lg} {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
|
||||
const ChildrenContainer = styled.div<{ $expanded?: boolean }>`
|
||||
display: ${(props) => (props.$expanded ? 'block' : 'none')};
|
||||
${media.lg} {
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
|
||||
const HeaderItem = styled.div`
|
||||
padding: 10px 30px;
|
||||
font-size: 15px;
|
||||
line-height: 19px;
|
||||
color: var(--g0);
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
cursor: pointer;
|
||||
${media.lg} {
|
||||
padding: 22px 45px;
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
&:not(:first-child) {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const NavigationItem = styled.div<{ $active?: boolean }>`
|
||||
padding: 12px 12px 12px 75px;
|
||||
transition:
|
||||
background 0.1s linear,
|
||||
color 0.1s linear;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 14.5px;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--g1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
span {
|
||||
font-weight: 500;
|
||||
}
|
||||
> :not(:last-child) {
|
||||
margin-right: 14px;
|
||||
}
|
||||
&:hover,
|
||||
${(props) => (props.$active ? '&' : '&.noop')} {
|
||||
color: var(--g0);
|
||||
text-decoration: underline;
|
||||
}
|
||||
${(props) => (props.$active ? 'span' : '&.noop')} {
|
||||
font-weight: 700;
|
||||
}
|
||||
${media.lg} {
|
||||
padding: 12px 12px 12px 65px;
|
||||
}
|
||||
`;
|
||||
|
||||
const NavigationItemLabel = styled.span`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
line-height: 17.6px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--g1);
|
||||
background: var(--g5);
|
||||
`;
|
||||
|
||||
export interface DocumentationNavigationProps {
|
||||
documentationItems: DocumentationItem[];
|
||||
pathPrefix?: string[];
|
||||
}
|
||||
|
||||
export function DocumentationNavigation({
|
||||
documentationItems,
|
||||
pathPrefix,
|
||||
|
|
@ -23,14 +112,15 @@ export function DocumentationNavigation({
|
|||
const normalized = activePath.replace((pathPrefix || []).join('/'), '');
|
||||
|
||||
return (
|
||||
normalized === item.path ||
|
||||
item.children?.some((child) => {
|
||||
normalized === item.path
|
||||
|| item.children?.some((child) => {
|
||||
return activePath.startsWith(
|
||||
[item.path, child.path].filter(Boolean).join('/'),
|
||||
);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
setExpandedTitles(expandedItems.map((item) => item.title));
|
||||
}, [activePath, pathPrefix, documentationItems]);
|
||||
|
||||
|
|
@ -81,13 +171,15 @@ export function DocumentationNavigation({
|
|||
legacyBehavior
|
||||
key={documentationItem.path}
|
||||
>
|
||||
<NavigationItem as={'a'} $active={activePath === path}>
|
||||
<NavigationItem as="a" $active={activePath === path}>
|
||||
<span>{documentationItem.title}</span>
|
||||
{documentationItem.label ? (
|
||||
<NavigationItemLabel>
|
||||
{documentationItem.label}
|
||||
</NavigationItemLabel>
|
||||
) : null}
|
||||
{documentationItem.label
|
||||
? (
|
||||
<NavigationItemLabel>
|
||||
{documentationItem.label}
|
||||
</NavigationItemLabel>
|
||||
)
|
||||
: null}
|
||||
</NavigationItem>
|
||||
</Link>
|
||||
);
|
||||
|
|
@ -96,87 +188,3 @@ export function DocumentationNavigation({
|
|||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const HeaderItemIcon = styled.div<{ $active?: boolean }>`
|
||||
font-size: 13px;
|
||||
transition: transform 0.25s linear;
|
||||
transform: rotate(${(props) => (props.$active ? 180 : 0)}deg);
|
||||
margin-right: 7px;
|
||||
position: relative;
|
||||
top: 6px;
|
||||
svg {
|
||||
display: block;
|
||||
}
|
||||
${media.lg} {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
const ChildrenContainer = styled.div<{ $expanded?: boolean }>`
|
||||
display: ${(props) => (props.$expanded ? 'block' : 'none')};
|
||||
${media.lg} {
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
const HeaderItem = styled.div`
|
||||
padding: 10px 30px;
|
||||
font-size: 15px;
|
||||
line-height: 19px;
|
||||
color: var(--g0);
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
cursor: pointer;
|
||||
${media.lg} {
|
||||
padding: 22px 45px;
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
&:not(:first-child) {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
const NavigationItem = styled.div<{ $active?: boolean }>`
|
||||
padding: 12px 12px 12px 75px;
|
||||
transition:
|
||||
background 0.1s linear,
|
||||
color 0.1s linear;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 14.5px;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--g1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
span {
|
||||
font-weight: 500;
|
||||
}
|
||||
> :not(:last-child) {
|
||||
margin-right: 14px;
|
||||
}
|
||||
&:hover,
|
||||
${(props) => (props.$active ? '&' : '&.noop')} {
|
||||
color: var(--g0);
|
||||
text-decoration: underline;
|
||||
}
|
||||
${(props) => (props.$active ? 'span' : '&.noop')} {
|
||||
font-weight: 700;
|
||||
}
|
||||
${media.lg} {
|
||||
padding: 12px 12px 12px 65px;
|
||||
}
|
||||
`;
|
||||
const NavigationItemLabel = styled.span`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
line-height: 17.6px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--g1);
|
||||
background: var(--g5);
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -1,65 +1,13 @@
|
|||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { LargeButton } from '../components/Button';
|
||||
import { media } from '../lib/responsive';
|
||||
import { Sparks } from 'iconoir-react';
|
||||
|
||||
export function DonationPopup() {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const isInitialMount = useRef(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (isInitialMount.current) {
|
||||
isInitialMount.current = false;
|
||||
const isReturningUser = localStorage.getItem('returningUser');
|
||||
const isReturningUserAge = localStorage.getItem('returningUserAge');
|
||||
console.log('isReturningUser:', isReturningUser); // Debugging line
|
||||
console.log('isReturningUserAge:', isReturningUserAge); // Debugging line
|
||||
|
||||
if (isReturningUser === 'true' && isReturningUserAge === 'false') {
|
||||
setTimeout(() => setIsVisible(true), 15000);
|
||||
localStorage.setItem('returningUserAge', 'true');
|
||||
} else {
|
||||
localStorage.setItem('returningUser', 'true');
|
||||
localStorage.setItem('returningUserAge', 'false');
|
||||
console.log('Set returningUser to true'); // Debugging line
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleClose = () => {
|
||||
setIsVisible(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{isVisible && (
|
||||
<PopupContent>
|
||||
<Sparks></Sparks>
|
||||
<Text>
|
||||
Your one-time or recurring contribution does a lot to keep Iconoir
|
||||
going.
|
||||
</Text>
|
||||
<a
|
||||
href={
|
||||
'https://opencollective.com/iconoir/donate?interval=month&amount=10'
|
||||
}
|
||||
target={'_blank'}
|
||||
>
|
||||
Support the project!
|
||||
</a>
|
||||
<CloseButton onClick={handleClose}>×</CloseButton>
|
||||
</PopupContent>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { media } from '../lib/responsive';
|
||||
|
||||
const Text = styled.span`
|
||||
color: #ffffffba;
|
||||
`;
|
||||
|
||||
export const PopupContent = styled.div`
|
||||
const PopupContent = styled.div`
|
||||
display: none;
|
||||
${media.lg} {
|
||||
display: flex;
|
||||
|
|
@ -101,6 +49,48 @@ const CloseButton = styled.span`
|
|||
}
|
||||
`;
|
||||
|
||||
export const LargeButtonModal = styled(LargeButton)`
|
||||
margin-top: 30px;
|
||||
`;
|
||||
export function DonationPopup() {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const isInitialMount = useRef(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (isInitialMount.current) {
|
||||
isInitialMount.current = false;
|
||||
const isReturningUser = localStorage.getItem('returningUser');
|
||||
const isReturningUserAge = localStorage.getItem('returningUserAge');
|
||||
|
||||
if (isReturningUser === 'true' && isReturningUserAge === 'false') {
|
||||
setTimeout(() => setIsVisible(true), 15000);
|
||||
localStorage.setItem('returningUserAge', 'true');
|
||||
} else {
|
||||
localStorage.setItem('returningUser', 'true');
|
||||
localStorage.setItem('returningUserAge', 'false');
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleClose = () => {
|
||||
setIsVisible(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{isVisible && (
|
||||
<PopupContent>
|
||||
<Sparks></Sparks>
|
||||
<Text>
|
||||
Your one-time or recurring contribution does a lot to keep Iconoir
|
||||
going.
|
||||
</Text>
|
||||
<a
|
||||
href="https://opencollective.com/iconoir/donate?interval=month&amount=10"
|
||||
target="_blank"
|
||||
>
|
||||
Support the project!
|
||||
</a>
|
||||
<CloseButton onClick={handleClose}>×</CloseButton>
|
||||
</PopupContent>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,64 @@
|
|||
import type { Icon, IconListFilters } from './IconList';
|
||||
import { IconoirProvider } from 'iconoir-react';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Streamline } from './Streamline';
|
||||
import { media } from '../lib/responsive';
|
||||
import { CustomizationEditor } from './CustomizationEditor';
|
||||
import { FiltersEditor } from './FiltersEditor';
|
||||
import { Icon, IconList, IconListFilters } from './IconList';
|
||||
import { media } from '../lib/responsive';
|
||||
import { IconList } from './IconList';
|
||||
import { Streamline } from './Streamline';
|
||||
import { useCustomizationPersistence } from './useCustomizationPersistence';
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
${media.md} {
|
||||
align-items: flex-start;
|
||||
flex-direction: row;
|
||||
}
|
||||
`;
|
||||
|
||||
const Left = styled.div`
|
||||
flex: 1;
|
||||
min-height: calc(100vh - 100px);
|
||||
background: white;
|
||||
${media.md} {
|
||||
background: none;
|
||||
}
|
||||
`;
|
||||
|
||||
const Right = styled.div`
|
||||
position: sticky;
|
||||
top: 20px;
|
||||
width: 275px;
|
||||
display: block;
|
||||
z-index: -1;
|
||||
margin: 110px auto;
|
||||
${media.md} {
|
||||
margin-left: 68px;
|
||||
z-index: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
const FilterContainer = styled.div<{ $isMobile?: boolean }>`
|
||||
display: ${(props) => (props.$isMobile ? 'block' : 'none')};
|
||||
margin-bottom: 40px;
|
||||
position: sticky;
|
||||
top: 20px;
|
||||
z-index: 100;
|
||||
width: 100%;
|
||||
${media.md} {
|
||||
position: relative;
|
||||
top: 0;
|
||||
display: ${(props) => (props.$isMobile ? 'none' : 'block')};
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
`;
|
||||
|
||||
export interface ExploreProps {
|
||||
allIcons: Icon[];
|
||||
}
|
||||
|
||||
export function Explore({ allIcons }: ExploreProps) {
|
||||
const [filters, setFilters] = React.useState<IconListFilters>({});
|
||||
const [customizations, setCustomizations] = useCustomizationPersistence();
|
||||
|
|
@ -47,46 +95,3 @@ export function Explore({ allIcons }: ExploreProps) {
|
|||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
${media.md} {
|
||||
align-items: flex-start;
|
||||
flex-direction: row;
|
||||
}
|
||||
`;
|
||||
const Left = styled.div`
|
||||
flex: 1;
|
||||
min-height: calc(100vh - 100px);
|
||||
background: white;
|
||||
${media.md} {
|
||||
background: none;
|
||||
}
|
||||
`;
|
||||
const Right = styled.div`
|
||||
position: sticky;
|
||||
top: 20px;
|
||||
width: 275px;
|
||||
display: block;
|
||||
z-index: -1;
|
||||
margin: 110px auto;
|
||||
${media.md} {
|
||||
margin-left: 68px;
|
||||
z-index: 1;
|
||||
}
|
||||
`;
|
||||
const FilterContainer = styled.div<{ $isMobile?: boolean }>`
|
||||
display: ${(props) => (props.$isMobile ? 'block' : 'none')};
|
||||
margin-bottom: 40px;
|
||||
position: sticky;
|
||||
top: 20px;
|
||||
z-index: 100;
|
||||
width: 100%;
|
||||
${media.md} {
|
||||
position: relative;
|
||||
top: 0;
|
||||
display: ${(props) => (props.$isMobile ? 'none' : 'block')};
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import type { IconListFilters } from './IconList';
|
||||
import React from 'react';
|
||||
import { IconListFilters } from './IconList';
|
||||
import { LargeInput } from './Input';
|
||||
|
||||
export interface FiltersEditorProps {
|
||||
filters: IconListFilters;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
onChange: (filters: IconListFilters) => void;
|
||||
}
|
||||
|
||||
export function FiltersEditor({ filters, onChange }: FiltersEditorProps) {
|
||||
const [, startTransition] = (React as any).useTransition();
|
||||
const [search, setSearch] = React.useState(filters.search);
|
||||
|
|
@ -43,10 +43,10 @@ export function FiltersEditor({ filters, onChange }: FiltersEditorProps) {
|
|||
|
||||
return (
|
||||
<LargeInput
|
||||
placeholder={'Search...'}
|
||||
placeholder="Search..."
|
||||
value={search}
|
||||
type={'search'}
|
||||
autoCapitalize={'none'}
|
||||
type="search"
|
||||
autoCapitalize="none"
|
||||
tabIndex={1}
|
||||
onFocus={(e) => {
|
||||
if (!didScrollRef.current) {
|
||||
|
|
|
|||
|
|
@ -10,13 +10,68 @@ import {
|
|||
import { Logo, LogoContainer, LogoIcon } from './Header';
|
||||
import { Text13, Text17 } from './Typography';
|
||||
|
||||
export interface FooterCategoryProps {
|
||||
const Container = styled.div`
|
||||
display: block;
|
||||
margin-top: 110px;
|
||||
padding-top: 30px;
|
||||
margin-top: 100px;
|
||||
padding: 84px 12%;
|
||||
background-color: var(--g7);
|
||||
align-items: center;
|
||||
> :not(:last-child) {
|
||||
margin-right: 50px;
|
||||
}
|
||||
`;
|
||||
|
||||
const FooterEnd = styled.div`
|
||||
border-top: 1px solid var(--g5);
|
||||
padding-top: 20px;
|
||||
margin-top: 74px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
`;
|
||||
|
||||
const FooterCategories = styled.div`
|
||||
width: 100%;
|
||||
margin-top: 54px;
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const FooterCategoryContainer = styled.div`
|
||||
width: 28%;
|
||||
margin-right: 20px;
|
||||
`;
|
||||
|
||||
const FooterCategoryTitle = styled(Text17)`
|
||||
&&& {
|
||||
margin-bottom: 24px;
|
||||
font-weight: 600;
|
||||
display: block;
|
||||
color: var(--g0);
|
||||
}
|
||||
`;
|
||||
|
||||
const FooterCategoryLinks = styled.div``;
|
||||
|
||||
const FooterCategoryLink = styled.a`
|
||||
display: block;
|
||||
font-size: 17px;
|
||||
color: var(--g1);
|
||||
width: fit-content;
|
||||
margin-bottom: 12px;
|
||||
text-decoration: none;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
color: var(--g0);
|
||||
}
|
||||
`;
|
||||
|
||||
interface FooterCategoryProps {
|
||||
category: string;
|
||||
links: { name: string; url: string }[];
|
||||
}
|
||||
|
||||
const year = new Date().getFullYear();
|
||||
|
||||
function FooterCategory({ category, links }: FooterCategoryProps) {
|
||||
return (
|
||||
<FooterCategoryContainer>
|
||||
|
|
@ -33,17 +88,19 @@ function FooterCategory({ category, links }: FooterCategoryProps) {
|
|||
}
|
||||
|
||||
export function Footer() {
|
||||
const year = new Date().getFullYear();
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<LogoContainer>
|
||||
<LogoIcon>
|
||||
<PeaceHand />
|
||||
</LogoIcon>
|
||||
<Logo src={'/iconoir-logo.svg'} alt={'Iconoir Logo'} />
|
||||
<Logo src="/iconoir-logo.svg" alt="Iconoir Logo" />
|
||||
</LogoContainer>
|
||||
<FooterCategories>
|
||||
<FooterCategory
|
||||
category={'Project'}
|
||||
category="Project"
|
||||
links={[
|
||||
{ name: 'Our Mission', url: '/support' },
|
||||
{ name: 'Contribute', url: '/docs/contributing' },
|
||||
|
|
@ -54,7 +111,7 @@ export function Footer() {
|
|||
]}
|
||||
/>
|
||||
<FooterCategory
|
||||
category={'Support'}
|
||||
category="Support"
|
||||
links={[
|
||||
{
|
||||
name: 'License',
|
||||
|
|
@ -71,7 +128,7 @@ export function Footer() {
|
|||
]}
|
||||
/>
|
||||
<FooterCategory
|
||||
category={'Developers'}
|
||||
category="Developers"
|
||||
links={[
|
||||
{ name: 'Changelog', url: '/docs/changelog' },
|
||||
{
|
||||
|
|
@ -86,15 +143,19 @@ export function Footer() {
|
|||
</FooterCategories>
|
||||
<FooterEnd>
|
||||
<Text13 style={{ fontWeight: 400 }}>
|
||||
Parts of this content are ©2020-{year} by individual Iconoir
|
||||
contributors. Content available under a{' '}
|
||||
<a href={LICENSE_LINK} target={'_blank'} rel={'nofollow noreferrer'}>
|
||||
Parts of this content are ©2020-
|
||||
{year}
|
||||
{' '}
|
||||
by individual Iconoir
|
||||
contributors. Content available under a
|
||||
{' '}
|
||||
<a href={LICENSE_LINK} target="_blank" rel="nofollow noreferrer">
|
||||
MIT License
|
||||
</a>
|
||||
.
|
||||
</Text13>
|
||||
<Text13 style={{ fontWeight: 400 }}>
|
||||
<a href={PRIVACY_LINK} target={'_blank'} rel={'nofollow noreferrer'}>
|
||||
<a href={PRIVACY_LINK} target="_blank" rel="nofollow noreferrer">
|
||||
Privacy
|
||||
</a>
|
||||
</Text13>
|
||||
|
|
@ -102,54 +163,3 @@ export function Footer() {
|
|||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
display: block;
|
||||
margin-top: 110px;
|
||||
padding-top: 30px;
|
||||
margin-top: 100px;
|
||||
padding: 84px 12%;
|
||||
background-color: var(--g7);
|
||||
align-items: center;
|
||||
> :not(:last-child) {
|
||||
margin-right: 50px;
|
||||
}
|
||||
`;
|
||||
const FooterEnd = styled.div`
|
||||
border-top: 1px solid var(--g5);
|
||||
padding-top: 20px;
|
||||
margin-top: 74px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
`;
|
||||
const FooterCategories = styled.div`
|
||||
width: 100%;
|
||||
margin-top: 54px;
|
||||
display: flex;
|
||||
`;
|
||||
const FooterCategoryContainer = styled.div`
|
||||
width: 28%;
|
||||
margin-right: 20px;
|
||||
`;
|
||||
const FooterCategoryTitle = styled(Text17)`
|
||||
&&& {
|
||||
margin-bottom: 24px;
|
||||
font-weight: 600;
|
||||
display: block;
|
||||
color: var(--g0);
|
||||
}
|
||||
`;
|
||||
const FooterCategoryLinks = styled.div``;
|
||||
const FooterCategoryLink = styled.a`
|
||||
display: block;
|
||||
font-size: 17px;
|
||||
color: var(--g1);
|
||||
width: fit-content;
|
||||
margin-bottom: 12px;
|
||||
text-decoration: none;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
color: var(--g0);
|
||||
}
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ export function GA() {
|
|||
<>
|
||||
<Script
|
||||
src="https://www.googletagmanager.com/gtag/js?id=UA-33344001-9"
|
||||
strategy={'afterInteractive'}
|
||||
strategy="afterInteractive"
|
||||
/>
|
||||
<Script id={'google-analytics'} strategy={'afterInteractive'}>
|
||||
<Script id="google-analytics" strategy="afterInteractive">
|
||||
{`
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Menu, Xmark, Discord, Sparks } from 'iconoir-react';
|
||||
import { Discord, Menu, Sparks, Xmark } from 'iconoir-react';
|
||||
import { Heart } from 'iconoir-react/solid';
|
||||
import Link from 'next/link';
|
||||
import React from 'react';
|
||||
|
|
@ -11,72 +11,6 @@ import { CurrentVersion } from './CurrentVersion';
|
|||
import { NavigationItem } from './NavigationItem';
|
||||
import { Text15 } from './Typography';
|
||||
|
||||
export interface HeaderProps {
|
||||
currentVersion: string;
|
||||
}
|
||||
export function Header({ currentVersion }: HeaderProps) {
|
||||
const [menuVisible, setMenuVisible] = React.useState(false);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Banner>
|
||||
<Sparks></Sparks>
|
||||
<a
|
||||
href={
|
||||
'https://opencollective.com/iconoir/donate?interval=month&amount=10'
|
||||
}
|
||||
target={'_blank'}
|
||||
>
|
||||
Your one-time or recurring contribution does a lot to keep Iconoir
|
||||
going.
|
||||
</a>
|
||||
</Banner>
|
||||
<HeaderLeft>
|
||||
<Link href={'/'}>
|
||||
<LogoContainer>
|
||||
<LogoIcon>
|
||||
<AnimatedSvg />
|
||||
</LogoIcon>
|
||||
<Logo src={'/iconoir-logo.svg'} alt={'Iconoir Logo'} />
|
||||
</LogoContainer>
|
||||
</Link>
|
||||
<CurrentVersion version={currentVersion} />
|
||||
</HeaderLeft>
|
||||
<HeaderCenter>
|
||||
<MobileMenuContainer $visible={menuVisible}>
|
||||
<NavigationItem href={'/'}>Icons</NavigationItem>
|
||||
<NavigationItem href={'/docs/introduction'} activeMatch={'/docs'}>
|
||||
Documentation
|
||||
</NavigationItem>
|
||||
<NavigationItem href={'/support'} style={{ marginRight: 0 }}>
|
||||
Donate — Our Mission
|
||||
</NavigationItem>
|
||||
<Share $isMobile>
|
||||
<a href={SHARE_LINK} target={'_blank'} rel={'noreferrer nofollow'}>
|
||||
Share with <Heart width={'1em'} height={'1em'} /> on{' '}
|
||||
<span>X (Twitter)</span>
|
||||
</a>
|
||||
</Share>
|
||||
</MobileMenuContainer>
|
||||
</HeaderCenter>
|
||||
<HeaderRight>
|
||||
<Share>
|
||||
<a href={SHARE_LINK} target={'_blank'} rel={'noreferrer nofollow'}>
|
||||
Share with <Heart width={'1em'} height={'1em'} /> on{' '}
|
||||
<span>X (Twitter)</span>
|
||||
</a>
|
||||
</Share>
|
||||
<a href={DISCORD_LINK} rel={'nofollow noreferrer'}>
|
||||
<StyledDiscord $isMobile />
|
||||
</a>
|
||||
<MobileMenuButton onClick={() => setMenuVisible((v) => !v)}>
|
||||
{menuVisible ? <Xmark /> : <Menu />}
|
||||
</MobileMenuButton>
|
||||
</HeaderRight>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
const StyledDiscord = styled(Discord)<{ $isMobile?: boolean }>`
|
||||
display: none;
|
||||
${media.lg} {
|
||||
|
|
@ -102,7 +36,7 @@ export const LogoContainer = styled.div`
|
|||
}
|
||||
`;
|
||||
|
||||
export const Banner = styled(Text15)`
|
||||
const Banner = styled(Text15)`
|
||||
display: none;
|
||||
${media.lg} {
|
||||
display: flex;
|
||||
|
|
@ -139,6 +73,7 @@ const MobileMenuButton = styled(ResetButton)`
|
|||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const MobileMenuContainer = styled.div<{ $visible?: boolean }>`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
|
@ -158,8 +93,8 @@ const MobileMenuContainer = styled.div<{ $visible?: boolean }>`
|
|||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
${(props) =>
|
||||
props.$visible &&
|
||||
css`
|
||||
props.$visible
|
||||
&& css`
|
||||
pointer-events: all;
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
|
|
@ -180,6 +115,7 @@ const MobileMenuContainer = styled.div<{ $visible?: boolean }>`
|
|||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -188,6 +124,7 @@ const Container = styled.div`
|
|||
margin-top: 40px;
|
||||
}
|
||||
`;
|
||||
|
||||
const HeaderItem = styled.div`
|
||||
flex: 1;
|
||||
width: 33%;
|
||||
|
|
@ -196,17 +133,20 @@ const HeaderItem = styled.div`
|
|||
justify-content: center;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const HeaderCenter = styled(HeaderItem)`
|
||||
padding: 0 16px;
|
||||
> :not(:last-child) {
|
||||
margin-right: 16px;
|
||||
}
|
||||
`;
|
||||
|
||||
const HeaderLeft = styled(HeaderItem)`
|
||||
&&& {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
`;
|
||||
|
||||
const HeaderRight = styled(HeaderItem)`
|
||||
&&& {
|
||||
justify-content: flex-end;
|
||||
|
|
@ -233,8 +173,8 @@ const Share = styled(Text15)<{ $isMobile?: boolean }>`
|
|||
&&& {
|
||||
display: none;
|
||||
${(props) =>
|
||||
props.$isMobile &&
|
||||
css`
|
||||
props.$isMobile
|
||||
&& css`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 12px 0;
|
||||
|
|
@ -259,3 +199,78 @@ const Share = styled(Text15)<{ $isMobile?: boolean }>`
|
|||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export interface HeaderProps {
|
||||
currentVersion: string;
|
||||
}
|
||||
|
||||
export function Header({ currentVersion }: HeaderProps) {
|
||||
const [menuVisible, setMenuVisible] = React.useState(false);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Banner>
|
||||
<Sparks></Sparks>
|
||||
<a
|
||||
href="https://opencollective.com/iconoir/donate?interval=month&amount=10"
|
||||
target="_blank"
|
||||
>
|
||||
Your one-time or recurring contribution does a lot to keep Iconoir
|
||||
going.
|
||||
</a>
|
||||
</Banner>
|
||||
<HeaderLeft>
|
||||
<Link href="/">
|
||||
<LogoContainer>
|
||||
<LogoIcon>
|
||||
<AnimatedSvg />
|
||||
</LogoIcon>
|
||||
<Logo src="/iconoir-logo.svg" alt="Iconoir Logo" />
|
||||
</LogoContainer>
|
||||
</Link>
|
||||
<CurrentVersion version={currentVersion} />
|
||||
</HeaderLeft>
|
||||
<HeaderCenter>
|
||||
<MobileMenuContainer $visible={menuVisible}>
|
||||
<NavigationItem href="/">Icons</NavigationItem>
|
||||
<NavigationItem href="/docs/introduction" activeMatch="/docs">
|
||||
Documentation
|
||||
</NavigationItem>
|
||||
<NavigationItem href="/support" style={{ marginRight: 0 }}>
|
||||
Donate — Our Mission
|
||||
</NavigationItem>
|
||||
<Share $isMobile>
|
||||
<a href={SHARE_LINK} target="_blank" rel="noreferrer nofollow">
|
||||
Share with
|
||||
{' '}
|
||||
<Heart width="1em" height="1em" />
|
||||
{' '}
|
||||
on
|
||||
{' '}
|
||||
<span>X (Twitter)</span>
|
||||
</a>
|
||||
</Share>
|
||||
</MobileMenuContainer>
|
||||
</HeaderCenter>
|
||||
<HeaderRight>
|
||||
<Share>
|
||||
<a href={SHARE_LINK} target="_blank" rel="noreferrer nofollow">
|
||||
Share with
|
||||
{' '}
|
||||
<Heart width="1em" height="1em" />
|
||||
{' '}
|
||||
on
|
||||
{' '}
|
||||
<span>X (Twitter)</span>
|
||||
</a>
|
||||
</Share>
|
||||
<a href={DISCORD_LINK} rel="nofollow noreferrer">
|
||||
<StyledDiscord $isMobile />
|
||||
</a>
|
||||
<MobileMenuButton onClick={() => setMenuVisible((v) => !v)}>
|
||||
{menuVisible ? <Xmark /> : <Menu />}
|
||||
</MobileMenuButton>
|
||||
</HeaderRight>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,101 @@
|
|||
import React from 'react';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { media } from '../lib/responsive';
|
||||
import { useRef, useEffect } from 'react';
|
||||
|
||||
const HeaderContainer = styled.div`
|
||||
position: relative;
|
||||
width: fit-content;
|
||||
margin: auto;
|
||||
`;
|
||||
|
||||
const FloatingIcon = styled.div`
|
||||
position: absolute;
|
||||
display: none;
|
||||
background-repeat: no-repeat;
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
${media.md} {
|
||||
display: flex;
|
||||
}
|
||||
`;
|
||||
|
||||
const FloatingIconCellar = styled(FloatingIcon)`
|
||||
-webkit-transform: rotate(6deg);
|
||||
-moz-transform: rotate(6deg);
|
||||
top: -120px;
|
||||
right: 0px;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
background-image: url(/cellar.gif);
|
||||
background-size: 70%;
|
||||
${media.lg} {
|
||||
}
|
||||
`;
|
||||
|
||||
const FloatingIconPay = styled(FloatingIcon)`
|
||||
-webkit-transform: rotate(18deg);
|
||||
-moz-transform: rotate(18deg);
|
||||
top: -50px;
|
||||
right: -100px;
|
||||
width: 130px;
|
||||
height: 130px;
|
||||
background-image: url(/pay-bitcoin.gif);
|
||||
background-size: 70%;
|
||||
${media.lg} {
|
||||
}
|
||||
`;
|
||||
|
||||
const FloatingFaceID = styled(FloatingIcon)`
|
||||
-webkit-transform: rotate(6deg);
|
||||
-moz-transform: rotate(6deg);
|
||||
top: -130px;
|
||||
right: 380px;
|
||||
width: 110px;
|
||||
height: 110px;
|
||||
background-image: url(/face-id.gif);
|
||||
background-size: 70%;
|
||||
${media.lg} {
|
||||
}
|
||||
`;
|
||||
|
||||
const FloatingCommand = styled(FloatingIcon)`
|
||||
-webkit-transform: rotate(-7deg);
|
||||
-moz-transform: rotate(-7deg);
|
||||
top: -94px;
|
||||
left: 150px;
|
||||
width: 110px;
|
||||
height: 110px;
|
||||
background-image: url(/command.gif);
|
||||
background-size: 70%;
|
||||
${media.lg} {
|
||||
}
|
||||
`;
|
||||
|
||||
const FloatingFill = styled(FloatingIcon)`
|
||||
-webkit-transform: rotate(-14deg);
|
||||
-moz-transform: rotate(-14deg);
|
||||
top: -64px;
|
||||
left: -75px;
|
||||
width: 110px;
|
||||
height: 110px;
|
||||
background-image: url(/fill.gif);
|
||||
background-size: 70%;
|
||||
${media.lg} {
|
||||
}
|
||||
`;
|
||||
|
||||
export interface HeaderBackgroundProps {
|
||||
children: React.ReactElement;
|
||||
}
|
||||
|
||||
export function HeaderBackground({ children }: HeaderBackgroundProps) {
|
||||
const parallaxRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!parallaxRef.current) return;
|
||||
if (!parallaxRef.current)
|
||||
return;
|
||||
|
||||
const parallaxElements = parallaxRef.current.querySelectorAll(
|
||||
'[data-parallax-factor]',
|
||||
|
|
@ -21,7 +106,7 @@ export function HeaderBackground({ children }: HeaderBackgroundProps) {
|
|||
const y = event.clientY / window.innerHeight;
|
||||
|
||||
parallaxElements.forEach((el) => {
|
||||
const factor = parseFloat(
|
||||
const factor = Number.parseFloat(
|
||||
el.getAttribute('data-parallax-factor') || '1',
|
||||
);
|
||||
|
||||
|
|
@ -49,81 +134,3 @@ export function HeaderBackground({ children }: HeaderBackgroundProps) {
|
|||
</HeaderContainer>
|
||||
);
|
||||
}
|
||||
|
||||
const HeaderContainer = styled.div`
|
||||
position: relative;
|
||||
width: fit-content;
|
||||
margin: auto;
|
||||
`;
|
||||
const FloatingIcon = styled.div`
|
||||
position: absolute;
|
||||
display: none;
|
||||
background-repeat: no-repeat;
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
${media.md} {
|
||||
display: flex;
|
||||
}
|
||||
`;
|
||||
const FloatingIconCellar = styled(FloatingIcon)`
|
||||
-webkit-transform: rotate(6deg);
|
||||
-moz-transform: rotate(6deg);
|
||||
top: -120px;
|
||||
right: 0px;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
background-image: url(/cellar.gif);
|
||||
background-size: 70%;
|
||||
${media.lg} {
|
||||
}
|
||||
`;
|
||||
const FloatingIconPay = styled(FloatingIcon)`
|
||||
-webkit-transform: rotate(18deg);
|
||||
-moz-transform: rotate(18deg);
|
||||
top: -50px;
|
||||
right: -100px;
|
||||
width: 130px;
|
||||
height: 130px;
|
||||
background-image: url(/pay-bitcoin.gif);
|
||||
background-size: 70%;
|
||||
${media.lg} {
|
||||
}
|
||||
`;
|
||||
const FloatingFaceID = styled(FloatingIcon)`
|
||||
-webkit-transform: rotate(6deg);
|
||||
-moz-transform: rotate(6deg);
|
||||
top: -130px;
|
||||
right: 380px;
|
||||
width: 110px;
|
||||
height: 110px;
|
||||
background-image: url(/face-id.gif);
|
||||
background-size: 70%;
|
||||
${media.lg} {
|
||||
}
|
||||
`;
|
||||
const FloatingCommand = styled(FloatingIcon)`
|
||||
-webkit-transform: rotate(-7deg);
|
||||
-moz-transform: rotate(-7deg);
|
||||
top: -94px;
|
||||
left: 150px;
|
||||
width: 110px;
|
||||
height: 110px;
|
||||
background-image: url(/command.gif);
|
||||
background-size: 70%;
|
||||
${media.lg} {
|
||||
}
|
||||
`;
|
||||
const FloatingFill = styled(FloatingIcon)`
|
||||
-webkit-transform: rotate(-14deg);
|
||||
-moz-transform: rotate(-14deg);
|
||||
top: -64px;
|
||||
left: -75px;
|
||||
width: 110px;
|
||||
height: 110px;
|
||||
background-image: url(/fill.gif);
|
||||
background-size: 70%;
|
||||
${media.lg} {
|
||||
}
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export interface HeaderSecondaryProps {
|
||||
children: React.ReactElement;
|
||||
}
|
||||
export function HeaderSecondary({ children }: HeaderSecondaryProps) {
|
||||
return <HeaderContainer>{children}</HeaderContainer>;
|
||||
}
|
||||
|
||||
const HeaderContainer = styled.div`
|
||||
position: relative;
|
||||
width: fit-content;
|
||||
margin: auto;
|
||||
`;
|
||||
|
||||
export interface HeaderSecondaryProps {
|
||||
children: React.ReactElement;
|
||||
}
|
||||
|
||||
export function HeaderSecondary({ children }: HeaderSecondaryProps) {
|
||||
return <HeaderContainer>{children}</HeaderContainer>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,6 @@
|
|||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export function Hono() {
|
||||
return (
|
||||
<a
|
||||
href="https://wearehono.com/?utm_source=iconoir&utm_medium=sidebar"
|
||||
style={{ textDecoration: 'none' }}
|
||||
>
|
||||
<PromoContainer>
|
||||
<PromoContent>
|
||||
<PromoInfo>
|
||||
<PromoTitle>Buy high-quality logos</PromoTitle>
|
||||
<PromoSub>with Hono.</PromoSub>
|
||||
<PromoImage src="./hono-ad.png" />
|
||||
<SponsorLabel>Our sponsor</SponsorLabel>
|
||||
</PromoInfo>
|
||||
</PromoContent>
|
||||
</PromoContainer>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
const PromoContainer = styled.div`
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--g6);
|
||||
|
|
@ -31,6 +11,7 @@ const PromoContainer = styled.div`
|
|||
background-color: var(--g7);
|
||||
}
|
||||
`;
|
||||
|
||||
const SponsorLabel = styled.div`
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
|
|
@ -60,3 +41,23 @@ const PromoSub = styled.h2`
|
|||
font-size: 14px;
|
||||
margin: 0 auto 10px auto;
|
||||
`;
|
||||
|
||||
export function Hono() {
|
||||
return (
|
||||
<a
|
||||
href="https://wearehono.com/?utm_source=iconoir&utm_medium=sidebar"
|
||||
style={{ textDecoration: 'none' }}
|
||||
>
|
||||
<PromoContainer>
|
||||
<PromoContent>
|
||||
<PromoInfo>
|
||||
<PromoTitle>Buy high-quality logos</PromoTitle>
|
||||
<PromoSub>with Hono.</PromoSub>
|
||||
<PromoImage src="./hono-ad.png" />
|
||||
<SponsorLabel>Our sponsor</SponsorLabel>
|
||||
</PromoInfo>
|
||||
</PromoContent>
|
||||
</PromoContainer>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import type { Icon as IconType } from './IconList';
|
||||
import * as AllIcons from 'iconoir-react';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { showNotification } from '../lib/showNotification';
|
||||
import { ResetButton } from './Button';
|
||||
import { DEFAULT_CUSTOMIZATIONS, Icon as IconType } from './IconList';
|
||||
import { DEFAULT_CUSTOMIZATIONS } from './IconList';
|
||||
|
||||
const HEADER = '<?xml version="1.0" encoding="UTF-8"?>';
|
||||
|
||||
|
|
@ -13,8 +14,8 @@ function bakeSvg(
|
|||
strokeWidth: string | number,
|
||||
) {
|
||||
return (
|
||||
HEADER +
|
||||
svgString
|
||||
HEADER
|
||||
+ svgString
|
||||
.replace(
|
||||
/stroke="currentColor"/g,
|
||||
`stroke="currentColor" stroke-width="${strokeWidth}"`,
|
||||
|
|
@ -23,106 +24,6 @@ function bakeSvg(
|
|||
);
|
||||
}
|
||||
|
||||
export interface IconProps {
|
||||
iconWidth: number;
|
||||
icon: IconType;
|
||||
}
|
||||
export function Icon({ iconWidth, icon }: IconProps) {
|
||||
const IconComponent = (AllIcons as any)[icon.iconComponentName];
|
||||
const iconContainerRef = React.useRef<HTMLDivElement>(null);
|
||||
const downloadRef = React.useRef<HTMLAnchorElement>(null);
|
||||
const htmlContentsRef = React.useRef<string>('');
|
||||
const iconContext = React.useContext(AllIcons.IconoirContext);
|
||||
const [supportsClipboard, setSupportsClipboard] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
setSupportsClipboard(
|
||||
typeof window !== 'undefined' &&
|
||||
typeof window?.navigator?.clipboard?.writeText !== 'undefined',
|
||||
);
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (iconContainerRef.current) {
|
||||
htmlContentsRef.current = bakeSvg(
|
||||
(iconContainerRef.current.firstChild as SVGElement).outerHTML,
|
||||
iconContext.color || DEFAULT_CUSTOMIZATIONS.hexColor,
|
||||
iconContext.strokeWidth || DEFAULT_CUSTOMIZATIONS.strokeWidth,
|
||||
);
|
||||
}
|
||||
}, [iconContext, supportsClipboard]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const element =
|
||||
downloadRef.current ||
|
||||
(iconContainerRef.current as unknown as HTMLAnchorElement);
|
||||
|
||||
if (element) {
|
||||
element.href = `data:image/svg+xml;base64,${btoa(
|
||||
htmlContentsRef.current,
|
||||
)}`;
|
||||
}
|
||||
}, [iconContext, supportsClipboard]);
|
||||
|
||||
return (
|
||||
<div className={'icon-container'}>
|
||||
<BorderContainer $iconWidth={iconWidth}>
|
||||
<IconContainer
|
||||
ref={iconContainerRef}
|
||||
{...((supportsClipboard
|
||||
? {}
|
||||
: {
|
||||
as: 'a',
|
||||
href: '#',
|
||||
rel: 'noreferrer',
|
||||
download: `${icon.filename}.svg`,
|
||||
}) as any)}
|
||||
>
|
||||
<IconComponent />
|
||||
|
||||
{icon.filename.includes('-solid') ? <IconTag>SOLID</IconTag> : ''}
|
||||
</IconContainer>
|
||||
{supportsClipboard ? (
|
||||
<HoverContainer>
|
||||
<CornerBR />
|
||||
<CornerTR />
|
||||
<CornerBL />
|
||||
<CornerTL />
|
||||
<HoverButton
|
||||
onClick={() => {
|
||||
if (htmlContentsRef.current) {
|
||||
navigator.clipboard
|
||||
.writeText(htmlContentsRef.current)
|
||||
.then(() => {
|
||||
showNotification('SVG code copied!');
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
Copy SVG
|
||||
</HoverButton>
|
||||
<HoverButton
|
||||
as={'a'}
|
||||
ref={downloadRef}
|
||||
href={'#'}
|
||||
rel={'noreferrer'}
|
||||
download={`${icon.filename}.svg`}
|
||||
>
|
||||
Download
|
||||
</HoverButton>
|
||||
</HoverContainer>
|
||||
) : null}
|
||||
</BorderContainer>
|
||||
<Subtitle $iconWidth={iconWidth} title={icon.filename}>
|
||||
{icon.filename}
|
||||
</Subtitle>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const Overlay = styled.div`
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
|
|
@ -131,23 +32,28 @@ const Overlay = styled.div`
|
|||
width: 8px;
|
||||
height: 8px;
|
||||
`;
|
||||
|
||||
const CornerBR = styled(Overlay)`
|
||||
bottom: -6px;
|
||||
right: -6px;
|
||||
z-index: 999;
|
||||
`;
|
||||
|
||||
const CornerTR = styled(Overlay)`
|
||||
top: -6px;
|
||||
right: -6px;
|
||||
`;
|
||||
|
||||
const CornerBL = styled(Overlay)`
|
||||
bottom: -6px;
|
||||
left: -6px;
|
||||
`;
|
||||
|
||||
const CornerTL = styled(Overlay)`
|
||||
top: -6px;
|
||||
left: -6px;
|
||||
`;
|
||||
|
||||
const HoverContainer = styled.div`
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
|
|
@ -161,6 +67,7 @@ const HoverContainer = styled.div`
|
|||
opacity: 0;
|
||||
pointer-events: none;
|
||||
`;
|
||||
|
||||
const HoverButton = styled(ResetButton)`
|
||||
&&& {
|
||||
display: flex;
|
||||
|
|
@ -184,6 +91,7 @@ const HoverButton = styled(ResetButton)`
|
|||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const BorderContainer = styled.div<{ $iconWidth: number }>`
|
||||
width: ${(props) => props.$iconWidth}px;
|
||||
box-sizing: border-box;
|
||||
|
|
@ -200,6 +108,7 @@ const BorderContainer = styled.div<{ $iconWidth: number }>`
|
|||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const IconContainer = styled.div`
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
|
|
@ -207,6 +116,7 @@ const IconContainer = styled.div`
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const IconTag = styled.div`
|
||||
background-color: var(--g6);
|
||||
position: absolute;
|
||||
|
|
@ -219,6 +129,7 @@ const IconTag = styled.div`
|
|||
font-size: 11px;
|
||||
color: var(--g0);
|
||||
`;
|
||||
|
||||
const Subtitle = styled.div<{ $iconWidth: number }>`
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
|
|
@ -230,3 +141,104 @@ const Subtitle = styled.div<{ $iconWidth: number }>`
|
|||
text-overflow: ellipsis;
|
||||
width: ${(props) => props.$iconWidth}px;
|
||||
`;
|
||||
|
||||
export interface IconProps {
|
||||
iconWidth: number;
|
||||
icon: IconType;
|
||||
}
|
||||
|
||||
export function Icon({ iconWidth, icon }: IconProps) {
|
||||
const IconComponent = (AllIcons as any)[icon.iconComponentName];
|
||||
const iconContainerRef = React.useRef<HTMLDivElement>(null);
|
||||
const downloadRef = React.useRef<HTMLAnchorElement>(null);
|
||||
const htmlContentsRef = React.useRef<string>('');
|
||||
const iconContext = React.useContext(AllIcons.IconoirContext);
|
||||
const [supportsClipboard, setSupportsClipboard] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
setSupportsClipboard(
|
||||
typeof window !== 'undefined'
|
||||
&& typeof window?.navigator?.clipboard?.writeText !== 'undefined',
|
||||
);
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (iconContainerRef.current) {
|
||||
htmlContentsRef.current = bakeSvg(
|
||||
(iconContainerRef.current.firstChild as SVGElement).outerHTML,
|
||||
iconContext.color || DEFAULT_CUSTOMIZATIONS.hexColor,
|
||||
iconContext.strokeWidth || DEFAULT_CUSTOMIZATIONS.strokeWidth,
|
||||
);
|
||||
}
|
||||
}, [iconContext, supportsClipboard]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const element = downloadRef.current || (iconContainerRef.current as unknown as HTMLAnchorElement);
|
||||
|
||||
if (element) {
|
||||
element.href = `data:image/svg+xml;base64,${btoa(
|
||||
htmlContentsRef.current,
|
||||
)}`;
|
||||
}
|
||||
}, [iconContext, supportsClipboard]);
|
||||
|
||||
return (
|
||||
<div className="icon-container">
|
||||
<BorderContainer $iconWidth={iconWidth}>
|
||||
<IconContainer
|
||||
ref={iconContainerRef}
|
||||
{...((supportsClipboard
|
||||
? {}
|
||||
: {
|
||||
as: 'a',
|
||||
href: '#',
|
||||
rel: 'noreferrer',
|
||||
download: `${icon.filename}.svg`,
|
||||
}) as any)}
|
||||
>
|
||||
<IconComponent />
|
||||
|
||||
{icon.filename.includes('-solid') ? <IconTag>SOLID</IconTag> : ''}
|
||||
</IconContainer>
|
||||
{supportsClipboard
|
||||
? (
|
||||
<HoverContainer>
|
||||
<CornerBR />
|
||||
<CornerTR />
|
||||
<CornerBL />
|
||||
<CornerTL />
|
||||
<HoverButton
|
||||
onClick={() => {
|
||||
if (htmlContentsRef.current) {
|
||||
navigator.clipboard
|
||||
.writeText(htmlContentsRef.current)
|
||||
.then(() => {
|
||||
showNotification('SVG code copied!');
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
Copy SVG
|
||||
</HoverButton>
|
||||
<HoverButton
|
||||
as="a"
|
||||
ref={downloadRef}
|
||||
href="#"
|
||||
rel="noreferrer"
|
||||
download={`${icon.filename}.svg`}
|
||||
>
|
||||
Download
|
||||
</HoverButton>
|
||||
</HoverContainer>
|
||||
)
|
||||
: null}
|
||||
</BorderContainer>
|
||||
<Subtitle $iconWidth={iconWidth} title={icon.filename}>
|
||||
{icon.filename}
|
||||
</Subtitle>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
import type {
|
||||
ListChildComponentProps,
|
||||
} from 'react-window';
|
||||
import { chunk } from 'lodash';
|
||||
import React from 'react';
|
||||
import {
|
||||
areEqual,
|
||||
ListChildComponentProps,
|
||||
VariableSizeList as List,
|
||||
} from 'react-window';
|
||||
import styled from 'styled-components';
|
||||
import useResizeObserver from 'use-resize-observer';
|
||||
import { CategoryRow } from './CategoryRow';
|
||||
import { ICON_SPACE, ICON_WIDTH } from '../lib/constants';
|
||||
import { CategoryRow } from './CategoryRow';
|
||||
import { IconListEmpty } from './IconListEmpty';
|
||||
import { IconsRow } from './IconsRow';
|
||||
import { ReactWindowScroller } from './ReactWindowScroller';
|
||||
|
|
@ -46,15 +48,17 @@ function filterIcons(allIcons: Icon[], filters: IconListFilters): Icon[] {
|
|||
for (const term of normalSearch.split(' ')) {
|
||||
result = result.filter((icon) => {
|
||||
return (
|
||||
normalizeString(icon.filename).includes(term) ||
|
||||
normalizeString(icon.category).includes(term) ||
|
||||
icon.tags.some((tag) => normalizeString(tag).includes(term))
|
||||
normalizeString(icon.filename).includes(term)
|
||||
|| normalizeString(icon.category).includes(term)
|
||||
|| icon.tags.some((tag) => normalizeString(tag).includes(term))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
} else return allIcons;
|
||||
} else {
|
||||
return allIcons;
|
||||
}
|
||||
}
|
||||
|
||||
interface IconCategoryRow {
|
||||
|
|
@ -77,7 +81,8 @@ function getRowsFromIcons(
|
|||
const categoryGroups: Record<string, Icon[]> = {};
|
||||
|
||||
for (const icon of filteredIcons) {
|
||||
if (!categoryGroups[icon.category]) categoryGroups[icon.category] = [];
|
||||
if (!categoryGroups[icon.category])
|
||||
categoryGroups[icon.category] = [];
|
||||
categoryGroups[icon.category].push(icon);
|
||||
}
|
||||
|
||||
|
|
@ -117,71 +122,8 @@ interface IconListContextValue {
|
|||
iconWidth: number;
|
||||
iconsPerRow: number;
|
||||
}
|
||||
export const IconListContext = React.createContext<
|
||||
IconListContextValue | undefined
|
||||
>(undefined);
|
||||
|
||||
export interface IconListProps {
|
||||
filters: IconListFilters;
|
||||
allIcons: Icon[];
|
||||
}
|
||||
export function IconList({ filters, allIcons }: IconListProps) {
|
||||
const filteredIcons = filterIcons(allIcons, filters);
|
||||
const { ref, width = 400 } = useResizeObserver();
|
||||
const iconsPerRow = width
|
||||
? Math.floor((width + ICON_SPACE) / (ICON_WIDTH + ICON_SPACE))
|
||||
: null;
|
||||
let children = null;
|
||||
const listRef = React.useRef<List<IconRow[]> | null>();
|
||||
const [height, setHeight] = React.useState(400);
|
||||
const iconWidth = iconsPerRow
|
||||
? Math.floor((width + ICON_SPACE) / iconsPerRow) - ICON_SPACE
|
||||
: null;
|
||||
|
||||
React.useEffect(() => {
|
||||
setHeight(window.innerHeight);
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (listRef.current) {
|
||||
listRef.current.resetAfterIndex(0, true);
|
||||
}
|
||||
}, [iconWidth, height]);
|
||||
|
||||
if (filteredIcons.length && iconsPerRow && width && iconWidth) {
|
||||
const iconRows = getRowsFromIcons(filteredIcons, iconsPerRow);
|
||||
|
||||
children = (
|
||||
<IconListContext.Provider value={{ iconsPerRow, iconWidth }}>
|
||||
<ReactWindowScroller>
|
||||
{({ ref, outerRef, style, onScroll }: any) => (
|
||||
<List<IconRow[]>
|
||||
ref={(c) => {
|
||||
if (typeof ref === 'function') ref(c);
|
||||
else ref.current = c;
|
||||
listRef.current = c;
|
||||
}}
|
||||
itemData={iconRows}
|
||||
width={width}
|
||||
outerRef={outerRef}
|
||||
style={style}
|
||||
height={height}
|
||||
itemCount={iconRows.length}
|
||||
onScroll={onScroll}
|
||||
itemSize={(index) => getItemSize(iconRows[index], iconWidth)}
|
||||
>
|
||||
{Row}
|
||||
</List>
|
||||
)}
|
||||
</ReactWindowScroller>
|
||||
</IconListContext.Provider>
|
||||
);
|
||||
} else if (width && filters.search) {
|
||||
return <IconListEmpty searchTerm={filters.search} />;
|
||||
}
|
||||
|
||||
return <Container ref={ref}>{children}</Container>;
|
||||
}
|
||||
const IconListContext = React.createContext<IconListContextValue | undefined>(undefined);
|
||||
|
||||
const Container = styled.div`
|
||||
width: 100%;
|
||||
|
|
@ -213,4 +155,73 @@ const Row = React.memo(
|
|||
},
|
||||
areEqual,
|
||||
);
|
||||
|
||||
Row.displayName = 'Row';
|
||||
|
||||
export interface IconListProps {
|
||||
filters: IconListFilters;
|
||||
allIcons: Icon[];
|
||||
}
|
||||
|
||||
export function IconList({ filters, allIcons }: IconListProps) {
|
||||
const filteredIcons = filterIcons(allIcons, filters);
|
||||
const { ref, width = 400 } = useResizeObserver();
|
||||
|
||||
const iconsPerRow = width
|
||||
? Math.floor((width + ICON_SPACE) / (ICON_WIDTH + ICON_SPACE))
|
||||
: null;
|
||||
|
||||
let children = null;
|
||||
const listRef = React.useRef<List<IconRow[]> | null>();
|
||||
const [height, setHeight] = React.useState(400);
|
||||
|
||||
const iconWidth = iconsPerRow
|
||||
? Math.floor((width + ICON_SPACE) / iconsPerRow) - ICON_SPACE
|
||||
: null;
|
||||
|
||||
React.useEffect(() => {
|
||||
setHeight(window.innerHeight);
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (listRef.current) {
|
||||
listRef.current.resetAfterIndex(0, true);
|
||||
}
|
||||
}, [iconWidth, height]);
|
||||
|
||||
if (filteredIcons.length && iconsPerRow && width && iconWidth) {
|
||||
const iconRows = getRowsFromIcons(filteredIcons, iconsPerRow);
|
||||
|
||||
children = (
|
||||
<IconListContext.Provider value={{ iconsPerRow, iconWidth }}>
|
||||
<ReactWindowScroller>
|
||||
{({ ref, outerRef, style, onScroll }: any) => (
|
||||
<List<IconRow[]>
|
||||
ref={(c) => {
|
||||
if (typeof ref === 'function')
|
||||
ref(c);
|
||||
else
|
||||
ref.current = c;
|
||||
listRef.current = c;
|
||||
}}
|
||||
itemData={iconRows}
|
||||
width={width}
|
||||
outerRef={outerRef}
|
||||
style={style}
|
||||
height={height}
|
||||
itemCount={iconRows.length}
|
||||
onScroll={onScroll}
|
||||
itemSize={(index) => getItemSize(iconRows[index], iconWidth)}
|
||||
>
|
||||
{Row}
|
||||
</List>
|
||||
)}
|
||||
</ReactWindowScroller>
|
||||
</IconListContext.Provider>
|
||||
);
|
||||
} else if (width && filters.search) {
|
||||
return <IconListEmpty searchTerm={filters.search} />;
|
||||
}
|
||||
|
||||
return <Container ref={ref}>{children}</Container>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,29 +3,6 @@ import styled from 'styled-components';
|
|||
import { SUGGEST_ICON_LINK } from '../lib/constants';
|
||||
import { Text18 } from './Typography';
|
||||
|
||||
export interface IconListEmptyProps {
|
||||
searchTerm: string;
|
||||
}
|
||||
export function IconListEmpty({ searchTerm }: IconListEmptyProps) {
|
||||
return (
|
||||
<Container>
|
||||
<IconContainer>
|
||||
<SpockHandGesture />
|
||||
</IconContainer>
|
||||
<Title>
|
||||
Unfortunately there are no icons for '{searchTerm}'
|
||||
</Title>
|
||||
<Text18 style={{ color: 'var(--black-60)' }}>
|
||||
{"If you can't find the icon, you can make a"}
|
||||
<br />
|
||||
<a href={SUGGEST_ICON_LINK} target={'_blank'} rel={'noreferrer'}>
|
||||
suggestion on GitHub.
|
||||
</a>
|
||||
</Text18>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
margin-top: 90px;
|
||||
display: flex;
|
||||
|
|
@ -33,6 +10,7 @@ const Container = styled.div`
|
|||
flex-direction: column;
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const IconContainer = styled.div`
|
||||
svg {
|
||||
width: 60px;
|
||||
|
|
@ -41,6 +19,7 @@ const IconContainer = styled.div`
|
|||
margin-bottom: 65px;
|
||||
color: var(--black);
|
||||
`;
|
||||
|
||||
const Title = styled(Text18)`
|
||||
&&& {
|
||||
font-weight: 700;
|
||||
|
|
@ -48,3 +27,29 @@ const Title = styled(Text18)`
|
|||
color: var(--black);
|
||||
}
|
||||
`;
|
||||
|
||||
export interface IconListEmptyProps {
|
||||
searchTerm: string;
|
||||
}
|
||||
|
||||
export function IconListEmpty({ searchTerm }: IconListEmptyProps) {
|
||||
return (
|
||||
<Container>
|
||||
<IconContainer>
|
||||
<SpockHandGesture />
|
||||
</IconContainer>
|
||||
<Title>
|
||||
Unfortunately there are no icons for '
|
||||
{searchTerm}
|
||||
'
|
||||
</Title>
|
||||
<Text18 style={{ color: 'var(--black-60)' }}>
|
||||
If you can't find the icon, you can make a
|
||||
<br />
|
||||
<a href={SUGGEST_ICON_LINK} target="_blank" rel="noreferrer">
|
||||
suggestion on GitHub.
|
||||
</a>
|
||||
</Text18>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,22 @@
|
|||
import type { Icon } from './IconList';
|
||||
import styled from 'styled-components';
|
||||
import { ICON_SPACE } from '../lib/constants';
|
||||
import { Icon as IconC } from './Icon';
|
||||
import { Icon } from './IconList';
|
||||
|
||||
const RowContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
> :not(:last-child) {
|
||||
margin-right: ${ICON_SPACE}px;
|
||||
}
|
||||
`;
|
||||
|
||||
export interface IconsRowProps {
|
||||
icons: Icon[];
|
||||
style?: any;
|
||||
iconWidth: number;
|
||||
}
|
||||
|
||||
export function IconsRow({ icons, style, iconWidth }: IconsRowProps) {
|
||||
return (
|
||||
<RowContainer style={style}>
|
||||
|
|
@ -17,11 +26,3 @@ export function IconsRow({ icons, style, iconWidth }: IconsRowProps) {
|
|||
</RowContainer>
|
||||
);
|
||||
}
|
||||
|
||||
const RowContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
> :not(:last-child) {
|
||||
margin-right: ${ICON_SPACE}px;
|
||||
}
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ const ResetInput = styled.input`
|
|||
}
|
||||
`;
|
||||
|
||||
export const Input = styled(ResetInput)`
|
||||
const Input = styled(ResetInput)`
|
||||
&&& {
|
||||
min-height: 35px;
|
||||
background: var(--white);
|
||||
|
|
|
|||
|
|
@ -3,19 +3,18 @@ import styled from 'styled-components';
|
|||
import { media } from '../lib/responsive';
|
||||
import { GA } from './GA';
|
||||
|
||||
export interface LayoutProps {}
|
||||
export function Layout({ children }: React.PropsWithChildren<LayoutProps>) {
|
||||
return (
|
||||
<Container>
|
||||
<GA />
|
||||
{children}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
padding: 50px 30px;
|
||||
${media.lg} {
|
||||
padding: 30px 50px 50px 50px;
|
||||
}
|
||||
`;
|
||||
|
||||
export function Layout({ children }: React.PropsWithChildren) {
|
||||
return (
|
||||
<Container>
|
||||
<GA />
|
||||
{children}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { MDXRemote as CoreMDXRemote, MDXRemoteProps } from 'next-mdx-remote';
|
||||
import type { MDXRemoteProps } from 'next-mdx-remote';
|
||||
import { MDXRemote as CoreMDXRemote } from 'next-mdx-remote';
|
||||
import { Table } from './Table';
|
||||
import { Body, Code, InlineCode, H1, H2, H3, Pre, Li } from './Typography';
|
||||
import { Body, Code, H1, H2, H3, InlineCode, Li, Pre } from './Typography';
|
||||
|
||||
export function MDXRemote(props: MDXRemoteProps) {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -5,41 +5,7 @@ import styled from 'styled-components';
|
|||
import { media } from '../lib/responsive';
|
||||
import { Text15 } from './Typography';
|
||||
|
||||
export interface NavigationItemProps {
|
||||
href: string;
|
||||
activeMatch?: string;
|
||||
children: React.ReactElement | string;
|
||||
style?: any;
|
||||
}
|
||||
export function NavigationItem({
|
||||
href,
|
||||
activeMatch,
|
||||
children,
|
||||
style,
|
||||
}: NavigationItemProps) {
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<Link href={href} passHref legacyBehavior>
|
||||
<NavigationItemContainer
|
||||
as={'a'}
|
||||
style={style}
|
||||
$text={children.toString()}
|
||||
$isActive={
|
||||
activeMatch
|
||||
? router.asPath.startsWith(activeMatch)
|
||||
: href.slice(1)
|
||||
? router.asPath.slice(1).startsWith(href.slice(1))
|
||||
: router.asPath === href
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</NavigationItemContainer>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
export const NavigationItemContainer = styled(Text15)<{
|
||||
const NavigationItemContainer = styled(Text15)<{
|
||||
$text: string;
|
||||
$isActive?: boolean;
|
||||
}>`
|
||||
|
|
@ -109,3 +75,38 @@ export const NavigationItemContainer = styled(Text15)<{
|
|||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export interface NavigationItemProps {
|
||||
href: string;
|
||||
activeMatch?: string;
|
||||
children: React.ReactElement | string;
|
||||
style?: any;
|
||||
}
|
||||
|
||||
export function NavigationItem({
|
||||
href,
|
||||
activeMatch,
|
||||
children,
|
||||
style,
|
||||
}: NavigationItemProps) {
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<Link href={href} passHref legacyBehavior>
|
||||
<NavigationItemContainer
|
||||
as="a"
|
||||
style={style}
|
||||
$text={children.toString()}
|
||||
$isActive={
|
||||
activeMatch
|
||||
? router.asPath.startsWith(activeMatch)
|
||||
: href.slice(1)
|
||||
? router.asPath.slice(1).startsWith(href.slice(1))
|
||||
: router.asPath === href
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</NavigationItemContainer>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,110 +1,10 @@
|
|||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { PraiseItem } from './PraiseItem';
|
||||
import { media } from '../lib/responsive';
|
||||
import { PraiseItem } from './PraiseItem';
|
||||
|
||||
const NUM_PRAISE_ITEMS = 3;
|
||||
|
||||
export function Praise() {
|
||||
const containerRef = React.useRef<HTMLDivElement>(null);
|
||||
const indicatorContainerRef = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (containerRef.current) {
|
||||
const handle = () => {
|
||||
if (indicatorContainerRef.current && containerRef.current) {
|
||||
const currentScrollLeft = containerRef.current.scrollLeft;
|
||||
const totalScroll = containerRef.current.scrollWidth;
|
||||
const interval = totalScroll / NUM_PRAISE_ITEMS;
|
||||
const currentIndex =
|
||||
currentScrollLeft >=
|
||||
containerRef.current.scrollWidth - window.innerWidth - 100
|
||||
? indicatorContainerRef.current.children.length - 1
|
||||
: Math.round(currentScrollLeft / interval);
|
||||
|
||||
for (
|
||||
let i = 0;
|
||||
i < indicatorContainerRef.current.children.length;
|
||||
i++
|
||||
) {
|
||||
const child = indicatorContainerRef.current.children[i];
|
||||
|
||||
if (currentIndex === i) {
|
||||
child.classList.add('active');
|
||||
} else {
|
||||
child.classList.remove('active');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const element = containerRef.current;
|
||||
element.addEventListener('scroll', handle);
|
||||
|
||||
return () => {
|
||||
element.removeEventListener('scroll', handle);
|
||||
};
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Container ref={containerRef}>
|
||||
<PraiseItem
|
||||
name={'Riccardo Suardi'}
|
||||
position={'Nibol CEO'}
|
||||
description={
|
||||
<>
|
||||
In Nibol we decided to use Iconoir to speed up the design process.
|
||||
We want to focus on the product and let Iconoir help us with the
|
||||
design.
|
||||
</>
|
||||
}
|
||||
imageUrl={'./riccardo-suardi.png'}
|
||||
logoUrl={'./nibol-logo.svg'}
|
||||
logoLink={'https://www.nibol.com/'}
|
||||
logoAlt={'Nibol Logo'}
|
||||
/>
|
||||
<PraiseItem
|
||||
name={'Fabrizio Rinaldi'}
|
||||
position={'Mailbrew and Typefully founder'}
|
||||
description={
|
||||
<>
|
||||
There's no shortage of icon packs, and yet I always find myself
|
||||
browsing iconoir. I love the style and attention to detail, and
|
||||
how easy it is to grab the perfect icons for my projects.
|
||||
</>
|
||||
}
|
||||
imageUrl={'./fabrizio-rinaldi.png'}
|
||||
logoUrl={'./typefully-logo.png'}
|
||||
logoLink={'https://typefully.com/'}
|
||||
logoAlt={'Typefully Logo'}
|
||||
/>
|
||||
<PraiseItem
|
||||
name={'Chris Messina'}
|
||||
position={'Entrepreneur and # inventor'}
|
||||
description={
|
||||
<>
|
||||
It's the tiny details that determine the degree of delight your
|
||||
customers experience from your product. Adopting Iconoir icons
|
||||
will easily boost your app's delight by a factor of 10!
|
||||
</>
|
||||
}
|
||||
imageUrl={'./chris-messina.png'}
|
||||
logoUrl={'./twitter-logo.png'}
|
||||
logoLink={'https://twitter.com/chrismessina'}
|
||||
logoAlt={'Twitter Logo'}
|
||||
/>
|
||||
</Container>
|
||||
<IndicatorContainer ref={indicatorContainerRef}>
|
||||
<Indicator className={'active'} />
|
||||
<Indicator />
|
||||
<Indicator />
|
||||
</IndicatorContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
max-width: 100%;
|
||||
margin: 0 -30px;
|
||||
|
|
@ -138,6 +38,7 @@ const Container = styled.div`
|
|||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const Indicator = styled.div`
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
|
|
@ -148,6 +49,7 @@ const Indicator = styled.div`
|
|||
background: var(--black);
|
||||
}
|
||||
`;
|
||||
|
||||
const IndicatorContainer = styled.div`
|
||||
margin: 40px auto 0 auto;
|
||||
display: flex;
|
||||
|
|
@ -160,3 +62,102 @@ const IndicatorContainer = styled.div`
|
|||
display: none;
|
||||
}
|
||||
`;
|
||||
|
||||
export function Praise() {
|
||||
const containerRef = React.useRef<HTMLDivElement>(null);
|
||||
const indicatorContainerRef = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (containerRef.current) {
|
||||
const handle = () => {
|
||||
if (indicatorContainerRef.current && containerRef.current) {
|
||||
const currentScrollLeft = containerRef.current.scrollLeft;
|
||||
const totalScroll = containerRef.current.scrollWidth;
|
||||
const interval = totalScroll / NUM_PRAISE_ITEMS;
|
||||
|
||||
const currentIndex = currentScrollLeft >= containerRef.current.scrollWidth - window.innerWidth - 100
|
||||
? indicatorContainerRef.current.children.length - 1
|
||||
: Math.round(currentScrollLeft / interval);
|
||||
|
||||
for (
|
||||
let i = 0;
|
||||
i < indicatorContainerRef.current.children.length;
|
||||
i++
|
||||
) {
|
||||
const child = indicatorContainerRef.current.children[i];
|
||||
|
||||
if (currentIndex === i) {
|
||||
child.classList.add('active');
|
||||
} else {
|
||||
child.classList.remove('active');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const element = containerRef.current;
|
||||
element.addEventListener('scroll', handle);
|
||||
|
||||
return () => {
|
||||
element.removeEventListener('scroll', handle);
|
||||
};
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Container ref={containerRef}>
|
||||
<PraiseItem
|
||||
name="Riccardo Suardi"
|
||||
position="Nibol CEO"
|
||||
description={(
|
||||
<>
|
||||
In Nibol we decided to use Iconoir to speed up the design process.
|
||||
We want to focus on the product and let Iconoir help us with the
|
||||
design.
|
||||
</>
|
||||
)}
|
||||
imageUrl="./riccardo-suardi.png"
|
||||
logoUrl="./nibol-logo.svg"
|
||||
logoLink="https://www.nibol.com/"
|
||||
logoAlt="Nibol Logo"
|
||||
/>
|
||||
<PraiseItem
|
||||
name="Fabrizio Rinaldi"
|
||||
position="Mailbrew and Typefully founder"
|
||||
description={(
|
||||
<>
|
||||
There's no shortage of icon packs, and yet I always find myself
|
||||
browsing iconoir. I love the style and attention to detail, and
|
||||
how easy it is to grab the perfect icons for my projects.
|
||||
</>
|
||||
)}
|
||||
imageUrl="./fabrizio-rinaldi.png"
|
||||
logoUrl="./typefully-logo.png"
|
||||
logoLink="https://typefully.com/"
|
||||
logoAlt="Typefully Logo"
|
||||
/>
|
||||
<PraiseItem
|
||||
name="Chris Messina"
|
||||
position="Entrepreneur and # inventor"
|
||||
description={(
|
||||
<>
|
||||
It's the tiny details that determine the degree of delight your
|
||||
customers experience from your product. Adopting Iconoir icons
|
||||
will easily boost your app's delight by a factor of 10!
|
||||
</>
|
||||
)}
|
||||
imageUrl="./chris-messina.png"
|
||||
logoUrl="./twitter-logo.png"
|
||||
logoLink="https://twitter.com/chrismessina"
|
||||
logoAlt="Twitter Logo"
|
||||
/>
|
||||
</Container>
|
||||
<IndicatorContainer ref={indicatorContainerRef}>
|
||||
<Indicator className="active" />
|
||||
<Indicator />
|
||||
<Indicator />
|
||||
</IndicatorContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,40 @@ import styled from 'styled-components';
|
|||
import { media } from '../lib/responsive';
|
||||
import { Text14, Text18 } from './Typography';
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
flex-direction: row;
|
||||
flex-shrink: 0;
|
||||
width: calc(100vw - 60px);
|
||||
scroll-snap-align: center;
|
||||
${media.xs} {
|
||||
width: 428px;
|
||||
}
|
||||
`;
|
||||
|
||||
const AuthorImage = styled.img`
|
||||
height: 60px;
|
||||
width: 60px;
|
||||
margin-right: 28px;
|
||||
`;
|
||||
|
||||
const Logo = styled.img`
|
||||
height: 23px;
|
||||
margin-top: 36px;
|
||||
`;
|
||||
|
||||
const Header = styled(Text18)`
|
||||
&&& {
|
||||
font-weight: 700;
|
||||
color: var(--black);
|
||||
}
|
||||
`;
|
||||
|
||||
const Body = styled(Text18)`
|
||||
margin-top: 8px;
|
||||
`;
|
||||
|
||||
export interface PraiseItemProps {
|
||||
name: string;
|
||||
position: string;
|
||||
|
|
@ -12,6 +46,7 @@ export interface PraiseItemProps {
|
|||
logoAlt: string;
|
||||
imageUrl: string;
|
||||
}
|
||||
|
||||
export function PraiseItem({
|
||||
name,
|
||||
position,
|
||||
|
|
@ -28,40 +63,10 @@ export function PraiseItem({
|
|||
<Header>{name}</Header>
|
||||
<Text14>{position}</Text14>
|
||||
<Body>{description}</Body>
|
||||
<a href={logoLink} target={'_blank'} rel={'noreferrer'}>
|
||||
<a href={logoLink} target="_blank" rel="noreferrer">
|
||||
<Logo src={logoUrl} alt={logoAlt} />
|
||||
</a>
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
flex-direction: row;
|
||||
flex-shrink: 0;
|
||||
width: calc(100vw - 60px);
|
||||
scroll-snap-align: center;
|
||||
${media.xs} {
|
||||
width: 428px;
|
||||
}
|
||||
`;
|
||||
const AuthorImage = styled.img`
|
||||
height: 60px;
|
||||
width: 60px;
|
||||
margin-right: 28px;
|
||||
`;
|
||||
const Logo = styled.img`
|
||||
height: 23px;
|
||||
margin-top: 36px;
|
||||
`;
|
||||
const Header = styled(Text18)`
|
||||
&&& {
|
||||
font-weight: 700;
|
||||
color: var(--black);
|
||||
}
|
||||
`;
|
||||
const Body = styled(Text18)`
|
||||
margin-top: 8px;
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
// Modified to remove scrollTo callback to support momentum scroll on iOS. We don't need it
|
||||
// in this implementation anyway.
|
||||
|
||||
import type { GridProps, ListProps } from 'react-window';
|
||||
import { throttle } from 'lodash';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { GridProps, ListProps } from 'react-window';
|
||||
|
||||
function isHtmlElement(
|
||||
element: HTMLElement | typeof window,
|
||||
|
|
@ -16,6 +16,7 @@ interface PositionKey {
|
|||
x: string;
|
||||
y: string;
|
||||
}
|
||||
|
||||
const windowScrollPositionKey: PositionKey = {
|
||||
y: 'pageYOffset',
|
||||
x: 'pageXOffset',
|
||||
|
|
@ -26,19 +27,16 @@ const documentScrollPositionKey: PositionKey = {
|
|||
x: 'scrollLeft',
|
||||
};
|
||||
|
||||
const getScrollPosition = (
|
||||
axis: keyof PositionKey,
|
||||
element?: HTMLElement | null,
|
||||
): number =>
|
||||
// @ts-ignore indexing as string
|
||||
element?.[documentScrollPositionKey[axis] as any] ||
|
||||
// @ts-ignore indexing as string
|
||||
window[windowScrollPositionKey[axis] as any] ||
|
||||
// @ts-ignore indexing as string
|
||||
document.documentElement[documentScrollPositionKey[axis] as any] ||
|
||||
// @ts-ignore indexing as string
|
||||
document.body[documentScrollPositionKey[axis] as any] ||
|
||||
0;
|
||||
function getScrollPosition(axis: keyof PositionKey, element?: HTMLElement | null): number {
|
||||
// @ts-expect-error indexing as string
|
||||
return element?.[documentScrollPositionKey[axis] as any]
|
||||
|| window[windowScrollPositionKey[axis] as any]
|
||||
// @ts-expect-error indexing as string
|
||||
|| document.documentElement[documentScrollPositionKey[axis] as any]
|
||||
// @ts-expect-error indexing as string
|
||||
|| document.body[documentScrollPositionKey[axis] as any]
|
||||
|| 0;
|
||||
}
|
||||
|
||||
interface ChildOpts<Props extends ListProps | GridProps> {
|
||||
ref: React.MutableRefObject<any>;
|
||||
|
|
@ -47,7 +45,7 @@ interface ChildOpts<Props extends ListProps | GridProps> {
|
|||
onScroll: Props['onScroll'];
|
||||
}
|
||||
interface ReactWindowScrollerProps<Props extends ListProps | GridProps> {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
|
||||
children: (opts: ChildOpts<Props>) => React.ReactElement;
|
||||
throttleTime?: number;
|
||||
isGrid?: boolean;
|
||||
|
|
@ -62,27 +60,28 @@ export function ReactWindowScroller<
|
|||
}: ReactWindowScrollerProps<Props>) {
|
||||
const ref = useRef<any>();
|
||||
const outerRef = useRef<HTMLElement>();
|
||||
const targetElement =
|
||||
typeof window === 'undefined' ? (undefined as any) : window;
|
||||
const targetElement = typeof window === 'undefined' ? (undefined as any) : window;
|
||||
|
||||
useEffect(() => {
|
||||
const handleWindowScroll = throttle(() => {
|
||||
const rect = outerRef.current?.parentElement?.getBoundingClientRect();
|
||||
const offsetTop =
|
||||
(rect?.top || 0) +
|
||||
(isHtmlElement(targetElement)
|
||||
|
||||
const offsetTop = (rect?.top || 0)
|
||||
+ (isHtmlElement(targetElement)
|
||||
? targetElement.scrollTop
|
||||
: targetElement.scrollY);
|
||||
const offsetLeft =
|
||||
(rect?.left || 0) +
|
||||
(isHtmlElement(targetElement)
|
||||
|
||||
const offsetLeft = (rect?.left || 0)
|
||||
+ (isHtmlElement(targetElement)
|
||||
? targetElement.scrollLeft
|
||||
: targetElement.scrollX);
|
||||
|
||||
const scrollTop = getScrollPosition('y') - offsetTop;
|
||||
const scrollLeft = getScrollPosition('x') - offsetLeft;
|
||||
if (isGrid)
|
||||
ref.current && ref.current!.scrollTo({ scrollLeft, scrollTop });
|
||||
if (!isGrid) ref.current && ref.current!.scrollTo(scrollTop);
|
||||
if (!isGrid)
|
||||
ref.current && ref.current!.scrollTo(scrollTop);
|
||||
}, throttleTime);
|
||||
|
||||
targetElement.addEventListener('scroll', handleWindowScroll);
|
||||
|
|
|
|||
|
|
@ -1,43 +1,9 @@
|
|||
import { ArrowRight } from 'iconoir-react';
|
||||
import styled from 'styled-components';
|
||||
import { DonateContainer, DonateHeader, DonateRight } from '../pages/support';
|
||||
import { GITHUB_TREE_PREFIX } from '../lib/constants';
|
||||
import { Text18 } from './Typography';
|
||||
import { DonateContainer, DonateHeader, DonateRight } from '../pages/support';
|
||||
import { LargeButton } from './Button';
|
||||
|
||||
export interface ReadOnGitHubProps {
|
||||
path: string;
|
||||
resource?: string;
|
||||
}
|
||||
export function ReadOnGitHub({
|
||||
path,
|
||||
resource = 'our documentation',
|
||||
}: ReadOnGitHubProps) {
|
||||
return (
|
||||
<DonateContainer style={{ marginTop: 88 }}>
|
||||
<div>
|
||||
<DonateHeader>Read it on GitHub</DonateHeader>
|
||||
<Text18>
|
||||
If you prefer, you can take a look at {resource} on our GitHub
|
||||
repository.
|
||||
</Text18>
|
||||
</div>
|
||||
<DonateRight>
|
||||
<a
|
||||
href={`${GITHUB_TREE_PREFIX}/${
|
||||
path.startsWith('/') ? path.slice(1) : path
|
||||
}`}
|
||||
target={'_blank'}
|
||||
rel={'noreferrer'}
|
||||
>
|
||||
<DonateIconButton>
|
||||
<ArrowRight />
|
||||
</DonateIconButton>
|
||||
</a>
|
||||
</DonateRight>
|
||||
</DonateContainer>
|
||||
);
|
||||
}
|
||||
import { Text18 } from './Typography';
|
||||
|
||||
export const DonateIconButton = styled(LargeButton)`
|
||||
&&& {
|
||||
|
|
@ -51,3 +17,42 @@ export const DonateIconButton = styled(LargeButton)`
|
|||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export interface ReadOnGitHubProps {
|
||||
path: string;
|
||||
resource?: string;
|
||||
}
|
||||
|
||||
export function ReadOnGitHub({
|
||||
path,
|
||||
resource = 'our documentation',
|
||||
}: ReadOnGitHubProps) {
|
||||
return (
|
||||
<DonateContainer style={{ marginTop: 88 }}>
|
||||
<div>
|
||||
<DonateHeader>Read it on GitHub</DonateHeader>
|
||||
<Text18>
|
||||
If you prefer, you can take a look at
|
||||
{' '}
|
||||
{resource}
|
||||
{' '}
|
||||
on our GitHub
|
||||
repository.
|
||||
</Text18>
|
||||
</div>
|
||||
<DonateRight>
|
||||
<a
|
||||
href={`${GITHUB_TREE_PREFIX}/${
|
||||
path.startsWith('/') ? path.slice(1) : path
|
||||
}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<DonateIconButton>
|
||||
<ArrowRight />
|
||||
</DonateIconButton>
|
||||
</a>
|
||||
</DonateRight>
|
||||
</DonateContainer>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,12 +7,14 @@ export interface SEOProps {
|
|||
title?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export function SEO({ title, description }: SEOProps) {
|
||||
const { asPath } = useRouter();
|
||||
const pageTitle = title ? `${title} | ${TITLE_SUFFIX}` : TITLE_SUFFIX;
|
||||
const pageDescription = description;
|
||||
|
||||
const pathWithoutQuery = asPath.split(/[?#]/)[0];
|
||||
|
||||
const canonicalUrl = `https://iconoir.com${
|
||||
pathWithoutQuery !== '/' ? pathWithoutQuery : ''
|
||||
}`;
|
||||
|
|
|
|||
|
|
@ -1,22 +1,77 @@
|
|||
import type { SliderState } from '@react-stately/slider';
|
||||
import type { SliderProps as ReactSliderProps } from '@react-types/slider';
|
||||
import { useFocusRing } from '@react-aria/focus';
|
||||
import { useNumberFormatter } from '@react-aria/i18n';
|
||||
import { useSlider, useSliderThumb } from '@react-aria/slider';
|
||||
import { mergeProps } from '@react-aria/utils';
|
||||
import { VisuallyHidden } from '@react-aria/visually-hidden';
|
||||
import { SliderState, useSliderState } from '@react-stately/slider';
|
||||
import { SliderProps as ReactSliderProps } from '@react-types/slider';
|
||||
import { useSliderState } from '@react-stately/slider';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Text13 } from './Typography';
|
||||
|
||||
const SliderContainer = styled.div`
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
touch-action: none;
|
||||
`;
|
||||
|
||||
const SliderHeader = styled.div`
|
||||
display: flex;
|
||||
align-self: stretch;
|
||||
`;
|
||||
|
||||
const Output = styled(Text13)`
|
||||
flex: 1 0 auto;
|
||||
text-align: end;
|
||||
margin-bottom: 6px;
|
||||
`;
|
||||
|
||||
const Track = styled.div`
|
||||
position: relative;
|
||||
height: 30px;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const TrackBackground = styled.div`
|
||||
position: absolute;
|
||||
height: 2px;
|
||||
top: 15px;
|
||||
background: var(--black);
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const ThumbContainer = styled.div`
|
||||
position: absolute;
|
||||
transform: translateX(-50%);
|
||||
`;
|
||||
|
||||
const ThumbInner = styled.div`
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: solid 2px var(--black);
|
||||
box-shadow: 0px 3px 0px 0px var(--g0);
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
transition: 0.2s;
|
||||
scale: 1.2;
|
||||
}
|
||||
`;
|
||||
|
||||
export interface SliderProps extends ReactSliderProps<number[]> {
|
||||
formatOptions?: Parameters<typeof useNumberFormatter>[0];
|
||||
}
|
||||
|
||||
export function Slider(props: SliderProps) {
|
||||
let trackRef = React.useRef(null);
|
||||
let numberFormatter = useNumberFormatter(props.formatOptions);
|
||||
let state = useSliderState({ ...props, numberFormatter });
|
||||
let { groupProps, trackProps, labelProps, outputProps } = useSlider(
|
||||
const trackRef = React.useRef(null);
|
||||
const numberFormatter = useNumberFormatter(props.formatOptions);
|
||||
const state = useSliderState({ ...props, numberFormatter });
|
||||
|
||||
const { groupProps, trackProps, labelProps, outputProps } = useSlider(
|
||||
props,
|
||||
state,
|
||||
trackRef,
|
||||
|
|
@ -26,11 +81,11 @@ export function Slider(props: SliderProps) {
|
|||
<SliderContainer {...groupProps}>
|
||||
<SliderHeader>
|
||||
{props.label && (
|
||||
<Text13 as={'label'} {...labelProps}>
|
||||
<Text13 as="label" {...labelProps}>
|
||||
{props.label}
|
||||
</Text13>
|
||||
)}
|
||||
<Output as={'output'} {...outputProps}>
|
||||
<Output as="output" {...outputProps}>
|
||||
{state.getThumbValueLabel(0)}
|
||||
</Output>
|
||||
</SliderHeader>
|
||||
|
|
@ -49,8 +104,9 @@ interface ThumbProps {
|
|||
}
|
||||
|
||||
function Thumb({ state, trackRef, index }: ThumbProps) {
|
||||
let inputRef = React.useRef(null);
|
||||
let { thumbProps, inputProps } = useSliderThumb(
|
||||
const inputRef = React.useRef(null);
|
||||
|
||||
const { thumbProps, inputProps } = useSliderThumb(
|
||||
{
|
||||
index,
|
||||
trackRef,
|
||||
|
|
@ -59,7 +115,7 @@ function Thumb({ state, trackRef, index }: ThumbProps) {
|
|||
state,
|
||||
);
|
||||
|
||||
let { focusProps, isFocusVisible } = useFocusRing();
|
||||
const { focusProps, isFocusVisible } = useFocusRing();
|
||||
|
||||
return (
|
||||
<ThumbContainer
|
||||
|
|
@ -89,49 +145,3 @@ function Thumb({ state, trackRef, index }: ThumbProps) {
|
|||
</ThumbContainer>
|
||||
);
|
||||
}
|
||||
|
||||
const SliderContainer = styled.div`
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
touch-action: none;
|
||||
`;
|
||||
const SliderHeader = styled.div`
|
||||
display: flex;
|
||||
align-self: stretch;
|
||||
`;
|
||||
const Output = styled(Text13)`
|
||||
flex: 1 0 auto;
|
||||
text-align: end;
|
||||
margin-bottom: 6px;
|
||||
`;
|
||||
const Track = styled.div`
|
||||
position: relative;
|
||||
height: 30px;
|
||||
width: 100%;
|
||||
`;
|
||||
const TrackBackground = styled.div`
|
||||
position: absolute;
|
||||
height: 2px;
|
||||
top: 15px;
|
||||
background: var(--black);
|
||||
width: 100%;
|
||||
`;
|
||||
const ThumbContainer = styled.div`
|
||||
position: absolute;
|
||||
transform: translateX(-50%);
|
||||
`;
|
||||
const ThumbInner = styled.div`
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: solid 2px var(--black);
|
||||
box-shadow: 0px 3px 0px 0px var(--g0);
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
transition: 0.2s;
|
||||
scale: 1.2;
|
||||
}
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -2,27 +2,6 @@ import React from 'react';
|
|||
import styled from 'styled-components';
|
||||
import { media } from '../lib/responsive';
|
||||
|
||||
export function Sponsor() {
|
||||
return (
|
||||
<SponsorContainer>
|
||||
<SponsorText>
|
||||
<SponsorLeft>
|
||||
<SponsorLogo />
|
||||
</SponsorLeft>
|
||||
<SponsorRight>
|
||||
<SponsorTitle>Get 3 months free of Framer with Iconoir.</SponsorTitle>
|
||||
<SponsorDescr>
|
||||
Click the link and use the code “pro-yearly-partner”.
|
||||
</SponsorDescr>
|
||||
</SponsorRight>
|
||||
</SponsorText>
|
||||
<a href="https://www.framer.com?via=iconoir">
|
||||
<SponsorCTA>Get the offer</SponsorCTA>
|
||||
</a>
|
||||
</SponsorContainer>
|
||||
);
|
||||
}
|
||||
|
||||
const SponsorContainer = styled.div`
|
||||
border: 1px solid var(--g7);
|
||||
background-color: var(--g7);
|
||||
|
|
@ -111,3 +90,24 @@ const SponsorCTA = styled.div`
|
|||
color: var(--white);
|
||||
}
|
||||
`;
|
||||
|
||||
export function Sponsor() {
|
||||
return (
|
||||
<SponsorContainer>
|
||||
<SponsorText>
|
||||
<SponsorLeft>
|
||||
<SponsorLogo />
|
||||
</SponsorLeft>
|
||||
<SponsorRight>
|
||||
<SponsorTitle>Get 3 months free of Framer with Iconoir.</SponsorTitle>
|
||||
<SponsorDescr>
|
||||
Click the link and use the code “pro-yearly-partner”.
|
||||
</SponsorDescr>
|
||||
</SponsorRight>
|
||||
</SponsorText>
|
||||
<a href="https://www.framer.com?via=iconoir">
|
||||
<SponsorCTA>Get the offer</SponsorCTA>
|
||||
</a>
|
||||
</SponsorContainer>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,19 +2,6 @@ import styled from 'styled-components';
|
|||
import { media } from '../lib/responsive';
|
||||
import { Text15 } from './Typography';
|
||||
|
||||
export interface StatProps {
|
||||
value: string;
|
||||
description: string;
|
||||
}
|
||||
export function Stat({ value, description }: StatProps) {
|
||||
return (
|
||||
<StatContainer>
|
||||
<StatText>{value}</StatText>
|
||||
<Text15>{description}</Text15>
|
||||
</StatContainer>
|
||||
);
|
||||
}
|
||||
|
||||
const StatText = styled.div`
|
||||
font-size: 38px;
|
||||
font-weight: 700;
|
||||
|
|
@ -27,6 +14,7 @@ const StatText = styled.div`
|
|||
text-stroke: 1.5px;
|
||||
}
|
||||
`;
|
||||
|
||||
const StatContainer = styled.div`
|
||||
text-align: center;
|
||||
width: 45%;
|
||||
|
|
@ -52,3 +40,17 @@ export const StatsContainer = styled.div`
|
|||
margin: 60px auto;
|
||||
}
|
||||
`;
|
||||
|
||||
export interface StatProps {
|
||||
value: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export function Stat({ value, description }: StatProps) {
|
||||
return (
|
||||
<StatContainer>
|
||||
<StatText>{value}</StatText>
|
||||
<Text15>{description}</Text15>
|
||||
</StatContainer>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +1,6 @@
|
|||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export function Streamline() {
|
||||
return (
|
||||
<a
|
||||
rel="sponsored"
|
||||
href="https://bit.ly/3SNgpKo"
|
||||
style={{ textDecoration: 'none' }}
|
||||
>
|
||||
<PromoContainer>
|
||||
<PromoContent>
|
||||
<PromoInfo>
|
||||
<PromoLogo />
|
||||
<PromoTitle>Expand Your Icon Collection</PromoTitle>
|
||||
<PromoSub>with Streamline</PromoSub>
|
||||
<PromoImage src="./streamline-ad.png" />
|
||||
<PromoDescription>170,000 Vector Icons</PromoDescription>
|
||||
<SponsorLabel>Our sponsor</SponsorLabel>
|
||||
</PromoInfo>
|
||||
</PromoContent>
|
||||
</PromoContainer>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
// Styled components go here
|
||||
|
||||
const PromoContainer = styled.div`
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--g6);
|
||||
|
|
@ -36,6 +11,7 @@ const PromoContainer = styled.div`
|
|||
background-color: var(--g7);
|
||||
}
|
||||
`;
|
||||
|
||||
const SponsorLabel = styled.div`
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
|
|
@ -43,9 +19,7 @@ const SponsorLabel = styled.div`
|
|||
margin: 10px 0;
|
||||
`;
|
||||
|
||||
const PromoContent = styled.div`
|
||||
// Your content styles here, similar to SponsorText
|
||||
`;
|
||||
const PromoContent = styled.div``;
|
||||
|
||||
const PromoLogo = styled.div`
|
||||
@keyframes my-animation {
|
||||
|
|
@ -92,9 +66,7 @@ const PromoImage = styled.img`
|
|||
width: 70%;
|
||||
`;
|
||||
|
||||
const PromoInfo = styled.div`
|
||||
// Styles for the text container, similar to SponsorRight
|
||||
`;
|
||||
const PromoInfo = styled.div``;
|
||||
|
||||
const PromoTitle = styled.h2`
|
||||
font-size: 16px;
|
||||
|
|
@ -115,3 +87,26 @@ const PromoDescription = styled.p`
|
|||
padding: 10px 0;
|
||||
font-weight: 600;
|
||||
`;
|
||||
|
||||
export function Streamline() {
|
||||
return (
|
||||
<a
|
||||
rel="sponsored"
|
||||
href="https://bit.ly/3SNgpKo"
|
||||
style={{ textDecoration: 'none' }}
|
||||
>
|
||||
<PromoContainer>
|
||||
<PromoContent>
|
||||
<PromoInfo>
|
||||
<PromoLogo />
|
||||
<PromoTitle>Expand Your Icon Collection</PromoTitle>
|
||||
<PromoSub>with Streamline</PromoSub>
|
||||
<PromoImage src="./streamline-ad.png" />
|
||||
<PromoDescription>170,000 Vector Icons</PromoDescription>
|
||||
<SponsorLabel>Our sponsor</SponsorLabel>
|
||||
</PromoInfo>
|
||||
</PromoContent>
|
||||
</PromoContainer>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { ArrowRight } from 'iconoir-react';
|
||||
import { DonateContainer, DonateHeader, DonateRight } from '../pages/support';
|
||||
import { SUGGEST_LIBRARY_LINK } from '../lib/constants';
|
||||
import { DonateContainer, DonateHeader, DonateRight } from '../pages/support';
|
||||
import { DonateIconButton } from './ReadOnGitHub';
|
||||
import { Text18 } from './Typography';
|
||||
|
||||
|
|
@ -16,7 +16,7 @@ export function SuggestLibrary() {
|
|||
</Text18>
|
||||
</div>
|
||||
<DonateRight>
|
||||
<a href={SUGGEST_LIBRARY_LINK} target={'_blank'} rel={'noreferrer'}>
|
||||
<a href={SUGGEST_LIBRARY_LINK} target="_blank" rel="noreferrer">
|
||||
<DonateIconButton>
|
||||
<ArrowRight />
|
||||
</DonateIconButton>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { media } from '../lib/responsive';
|
||||
import { showNotification } from '../lib/showNotification';
|
||||
import { CopyButton } from './Button';
|
||||
import { media } from '../lib/responsive';
|
||||
|
||||
export const Text15 = styled.div`
|
||||
font-size: 15px;
|
||||
|
|
@ -124,14 +124,6 @@ export const Li = styled.li`
|
|||
margin: 4px 0;
|
||||
`;
|
||||
|
||||
export const CodeElement = styled.code`
|
||||
&&& {
|
||||
display: inline-block;
|
||||
padding: 0 4px;
|
||||
color: var(--g0);
|
||||
}
|
||||
`;
|
||||
|
||||
const PreContainer = styled(Code)`
|
||||
&&& {
|
||||
position: relative;
|
||||
|
|
@ -147,6 +139,7 @@ const PreContainer = styled(Code)`
|
|||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const CopyContainer = styled.div`
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
|
|
@ -159,34 +152,36 @@ export function Pre({ children, ...props }: React.PropsWithChildren<any>) {
|
|||
|
||||
React.useEffect(() => {
|
||||
setSupportsClipboard(
|
||||
typeof window !== 'undefined' &&
|
||||
typeof window?.navigator?.clipboard?.writeText !== 'undefined',
|
||||
typeof window !== 'undefined'
|
||||
&& typeof window?.navigator?.clipboard?.writeText !== 'undefined',
|
||||
);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<PreContainer {...props}>
|
||||
<pre ref={containerRef}>{children}</pre>
|
||||
{supportsClipboard ? (
|
||||
<CopyContainer>
|
||||
<CopyButton
|
||||
onClick={() => {
|
||||
if (containerRef.current) {
|
||||
navigator.clipboard
|
||||
.writeText(containerRef.current.innerText)
|
||||
.then(() => {
|
||||
showNotification('Code copied!');
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
Copy
|
||||
</CopyButton>
|
||||
</CopyContainer>
|
||||
) : null}
|
||||
{supportsClipboard
|
||||
? (
|
||||
<CopyContainer>
|
||||
<CopyButton
|
||||
onClick={() => {
|
||||
if (containerRef.current?.textContent) {
|
||||
navigator.clipboard
|
||||
.writeText(containerRef.current.textContent)
|
||||
.then(() => {
|
||||
showNotification('Code copied!');
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
Copy
|
||||
</CopyButton>
|
||||
</CopyContainer>
|
||||
)
|
||||
: null}
|
||||
</PreContainer>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import type { IconListCustomizations } from './IconList';
|
||||
import React from 'react';
|
||||
import { DEFAULT_CUSTOMIZATIONS, IconListCustomizations } from './IconList';
|
||||
import { DEFAULT_CUSTOMIZATIONS } from './IconList';
|
||||
|
||||
const CUSTOMIZATIONS_KEY = 'iconoir-customize';
|
||||
|
||||
export function useCustomizationPersistence(): [
|
||||
IconListCustomizations,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
(customizations: IconListCustomizations) => void,
|
||||
] {
|
||||
const [customizations, _setCustomizations] = React.useState(
|
||||
|
|
|
|||
|
|
@ -3,14 +3,11 @@ export const REPO = {
|
|||
repo: 'iconoir',
|
||||
} as const;
|
||||
|
||||
export const GITHUB_LINK =
|
||||
`https://github.com/${REPO.owner}/${REPO.repo}` as const;
|
||||
export const GITHUB_LINK = `https://github.com/${REPO.owner}/${REPO.repo}` as const;
|
||||
export const ISSUE_LINK = `${GITHUB_LINK}/issues/new/choose` as const;
|
||||
export const SUGGEST_ICON_LINK =
|
||||
`${GITHUB_LINK}/issues/new?assignees=lucaburgio&labels=icon+request&template=icon_request.md&title=%5BICON%5D` as const;
|
||||
export const SUGGEST_ICON_LINK = `${GITHUB_LINK}/issues/new?assignees=lucaburgio&labels=icon+request&template=icon_request.md&title=%5BICON%5D` as const;
|
||||
export const LICENSE_LINK = `${GITHUB_LINK}/blob/main/LICENSE` as const;
|
||||
export const SUGGEST_LIBRARY_LINK =
|
||||
`${GITHUB_LINK}/issues/new?assignees=&labels=enhancement&template=feature_request.md&title=%5BFEAT%5D` as const;
|
||||
export const SUGGEST_LIBRARY_LINK = `${GITHUB_LINK}/issues/new?assignees=&labels=enhancement&template=feature_request.md&title=%5BFEAT%5D` as const;
|
||||
|
||||
export const GITHUB_TREE_PREFIX = `${GITHUB_LINK}/tree/main` as const;
|
||||
export const LIBRARY_LINKS = {
|
||||
|
|
@ -22,14 +19,11 @@ export const LIBRARY_LINKS = {
|
|||
Figma: 'https://www.figma.com/community/file/983248991460488027/Iconoir-Pack',
|
||||
} as const;
|
||||
|
||||
export const SHARE_LINK =
|
||||
'https://twitter.com/intent/tweet?text=Your%20new%20free%20icons%20library.%20No%20premium%20options%20or%20signups%20by%20%40burgioluca%20&url=https%3A%2F%2Ficonoir.com' as const;
|
||||
export const SUPPORT_LINK =
|
||||
'https://opencollective.com/iconoir/donate?interval=month&amount=10' as const;
|
||||
export const SHARE_LINK = 'https://twitter.com/intent/tweet?text=Your%20new%20free%20icons%20library.%20No%20premium%20options%20or%20signups%20by%20%40burgioluca%20&url=https%3A%2F%2Ficonoir.com' as const;
|
||||
export const SUPPORT_LINK = 'https://opencollective.com/iconoir/donate?interval=month&amount=10' as const;
|
||||
export const DISCORD_LINK = 'https://discord.gg/txXcKCAmKW' as const;
|
||||
export const FEEDBACK_LINK = 'https://forms.gle/3HvwVYow7D6T8zad7' as const;
|
||||
export const PRIVACY_LINK =
|
||||
'https://www.freeprivacypolicy.com/live/ba00d743-a0cd-44f8-8cb5-6f58911db0fb' as const;
|
||||
export const PRIVACY_LINK = 'https://www.freeprivacypolicy.com/live/ba00d743-a0cd-44f8-8cb5-6f58911db0fb' as const;
|
||||
|
||||
export const AUTHOR_LINKS = {
|
||||
Luca: 'https://twitter.com/burgioluca',
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import fs from 'fs';
|
||||
import fs from 'node:fs';
|
||||
|
||||
export function getHeaderProps() {
|
||||
const packageJson = JSON.parse(fs.readFileSync('../package.json').toString());
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { Icon } from '../components/IconList';
|
||||
import csv from 'csvtojson';
|
||||
import * as AllIcons from 'iconoir-react';
|
||||
import { kebabCase, pascalCase } from 'scule';
|
||||
import { Icon } from '../components/IconList';
|
||||
|
||||
const ICONS_PATH = 'icons.csv';
|
||||
const TAG_SEPARATOR = '|';
|
||||
|
|
@ -19,18 +19,19 @@ export async function getAllIcons(): Promise<Icon[]> {
|
|||
(icon) => icon === iconComponentName || icon === iconComponentSolidName,
|
||||
);
|
||||
|
||||
if (iconComponents.length === 0)
|
||||
if (iconComponents.length === 0) {
|
||||
throw new Error(
|
||||
`Couldn't find icons for ${row.filename} (${iconComponentName}) in 'iconoir-react'.`,
|
||||
);
|
||||
}
|
||||
|
||||
for (const iconComponent of iconComponents) {
|
||||
icons.push({
|
||||
filename: kebabCase(iconComponent),
|
||||
category: row.category,
|
||||
tags:
|
||||
row.tags?.split(TAG_SEPARATOR).map((item: string) => item.trim()) ||
|
||||
[],
|
||||
row.tags?.split(TAG_SEPARATOR).map((item: string) => item.trim())
|
||||
|| [],
|
||||
iconComponentName: iconComponent,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import process from 'node:process';
|
||||
import { Octokit } from '@octokit/rest';
|
||||
|
||||
export const octokit = new Octokit({
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
export function showNotification(message: string) {
|
||||
const element = document.createElement('div');
|
||||
element.classList.add('bottom-notification');
|
||||
element.innerText = message;
|
||||
element.textContent = message;
|
||||
document.body.appendChild(element);
|
||||
|
||||
setTimeout(() => {
|
||||
|
|
|
|||
2
iconoir.com/next-env.d.ts
vendored
2
iconoir.com/next-env.d.ts
vendored
|
|
@ -2,4 +2,4 @@
|
|||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||
// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information.
|
||||
|
|
|
|||
|
|
@ -23,9 +23,10 @@
|
|||
"@types/react": "^18.2.45",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"@types/react-window": "^1.8.8",
|
||||
"@types/remark-prism": "1.3.7",
|
||||
"animejs": "^3.2.2",
|
||||
"csvtojson": "^2.0.10",
|
||||
"eslint-config-next": "^14.0.4",
|
||||
"eslint-config-next": "15.1.0",
|
||||
"iconoir-react": "workspace:*",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.29.4",
|
||||
|
|
@ -33,7 +34,6 @@
|
|||
"next-mdx-remote": "^4.4.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-is": "^18.2.0",
|
||||
"react-window": "^1.8.10",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"remark-prism": "^1.3.6",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import Document, {
|
||||
import type {
|
||||
DocumentContext,
|
||||
DocumentInitialProps,
|
||||
} from 'next/document';
|
||||
import Document, {
|
||||
Head,
|
||||
Html,
|
||||
Main,
|
||||
|
|
@ -46,7 +48,7 @@ export default class IconoirDocument extends Document {
|
|||
<link
|
||||
rel="preconnect"
|
||||
href="https://fonts.gstatic.com"
|
||||
crossOrigin={'anonymous'}
|
||||
crossOrigin="anonymous"
|
||||
/>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;700&family=IBM+Plex+Mono:wght@400;700&display=swap"
|
||||
|
|
|
|||
|
|
@ -1,24 +1,64 @@
|
|||
import fs from 'fs';
|
||||
import { GetStaticPathsResult, GetStaticPropsContext } from 'next';
|
||||
import { MDXRemoteSerializeResult } from 'next-mdx-remote';
|
||||
import type { GetStaticPathsResult, GetStaticPropsContext } from 'next';
|
||||
import type { MDXRemoteSerializeResult } from 'next-mdx-remote';
|
||||
import type { ParsedUrlQuery } from 'node:querystring';
|
||||
import type { HeaderProps } from '../../components/Header';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import process from 'node:process';
|
||||
import { SuggestLibrary } from '@/components/SuggestLibrary';
|
||||
import { serialize } from 'next-mdx-remote/serialize';
|
||||
import path from 'path';
|
||||
import { ParsedUrlQuery } from 'querystring';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import remarkPrism from 'remark-prism';
|
||||
import styled from 'styled-components';
|
||||
import {
|
||||
DocumentationNavigation,
|
||||
DocumentationNavigationProps,
|
||||
type DocumentationNavigationProps,
|
||||
} from '../../components/DocumentationNavigation';
|
||||
import { Footer } from '../../components/Footer';
|
||||
import { Header, HeaderProps } from '../../components/Header';
|
||||
import { Header } from '../../components/Header';
|
||||
import { Layout } from '../../components/Layout';
|
||||
import { MDXRemote } from '../../components/MDXRemote';
|
||||
import { ReadOnGitHub } from '../../components/ReadOnGitHub';
|
||||
import { media } from '../../lib/responsive';
|
||||
import { SEO } from '../../components/SEO';
|
||||
import { getHeaderProps } from '../../lib/getHeaderProps';
|
||||
import { SuggestLibrary } from '@/components/SuggestLibrary';
|
||||
import { media } from '../../lib/responsive';
|
||||
|
||||
export const Container = styled.div`
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
margin: 30px -30px 0 -30px;
|
||||
${media.lg} {
|
||||
flex-direction: row;
|
||||
margin: 30px 0 0 -50px;
|
||||
}
|
||||
`;
|
||||
export const NavigationContainer = styled.div`
|
||||
${media.lg} {
|
||||
width: 250px;
|
||||
margin-right: 30px;
|
||||
border-right: solid 1px var(--g6);
|
||||
}
|
||||
`;
|
||||
export const ContentContainer = styled.div`
|
||||
flex: 1;
|
||||
`;
|
||||
export const InnerContentContainer = styled.div`
|
||||
margin: 0 30px;
|
||||
max-width: 800px;
|
||||
${media.lg} {
|
||||
margin: 0 auto;
|
||||
}
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
> div {
|
||||
margin: 24px 0;
|
||||
> a img {
|
||||
margin: 0 5px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface DocumentationPageProps extends HeaderProps {
|
||||
source: MDXRemoteSerializeResult;
|
||||
|
|
@ -62,47 +102,11 @@ export default function DocumentationPage({
|
|||
);
|
||||
}
|
||||
|
||||
export const Container = styled.div`
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
margin: 30px -30px 0 -30px;
|
||||
${media.lg} {
|
||||
flex-direction: row;
|
||||
margin: 30px 0 0 -50px;
|
||||
}
|
||||
`;
|
||||
export const NavigationContainer = styled.div`
|
||||
${media.lg} {
|
||||
width: 250px;
|
||||
margin-right: 30px;
|
||||
border-right: solid 1px var(--g6);
|
||||
}
|
||||
`;
|
||||
export const ContentContainer = styled.div`
|
||||
flex: 1;
|
||||
`;
|
||||
export const InnerContentContainer = styled.div`
|
||||
margin: 0 30px;
|
||||
max-width: 800px;
|
||||
${media.lg} {
|
||||
margin: 0 auto;
|
||||
}
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
> div {
|
||||
margin: 24px 0;
|
||||
> a img {
|
||||
margin: 0 5px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface DocumentationItemProps {
|
||||
suggestLibrary?: boolean;
|
||||
noReadOnGitHub?: boolean;
|
||||
}
|
||||
|
||||
export interface DocumentationItem extends DocumentationItemProps {
|
||||
path: string;
|
||||
filepath?: string;
|
||||
|
|
@ -112,6 +116,7 @@ export interface DocumentationItem extends DocumentationItemProps {
|
|||
label?: string;
|
||||
skip?: boolean;
|
||||
}
|
||||
|
||||
export function getDocumentationStructure(): DocumentationItem[] {
|
||||
return [
|
||||
{
|
||||
|
|
@ -272,10 +277,11 @@ export async function getStaticProps(context: GetStaticPropsContext) {
|
|||
const repositoryRoot = path.join(process.cwd(), '..');
|
||||
const filepath = path.join(repositoryRoot, navigationItem.filepath!);
|
||||
const source = cleanSource(fs.readFileSync(filepath).toString());
|
||||
|
||||
const mdxSource = await serialize(source, {
|
||||
parseFrontmatter: true,
|
||||
mdxOptions: {
|
||||
remarkPlugins: [require('remark-prism'), remarkGfm],
|
||||
remarkPlugins: [remarkPrism, remarkGfm],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,20 +1,21 @@
|
|||
import { serialize } from 'next-mdx-remote/serialize';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import remarkPrism from 'remark-prism';
|
||||
import {
|
||||
ChangelogEntry,
|
||||
ChangelogEntryProps,
|
||||
type ChangelogEntryProps,
|
||||
} from '../../components/ChangelogEntry';
|
||||
import { REPO } from '../../lib/constants';
|
||||
import {
|
||||
DocumentationNavigation,
|
||||
DocumentationNavigationProps,
|
||||
type DocumentationNavigationProps,
|
||||
} from '../../components/DocumentationNavigation';
|
||||
import { Footer } from '../../components/Footer';
|
||||
import { Header, HeaderProps } from '../../components/Header';
|
||||
import { Header, type HeaderProps } from '../../components/Header';
|
||||
import { Layout } from '../../components/Layout';
|
||||
import { ReadOnGitHub } from '../../components/ReadOnGitHub';
|
||||
import { SEO } from '../../components/SEO';
|
||||
import { H1 } from '../../components/Typography';
|
||||
import { REPO } from '../../lib/constants';
|
||||
import { getHeaderProps } from '../../lib/getHeaderProps';
|
||||
import { octokit } from '../../lib/octokit';
|
||||
import {
|
||||
|
|
@ -29,6 +30,7 @@ export interface ChangelogProps extends HeaderProps {
|
|||
documentationProps: DocumentationNavigationProps;
|
||||
entries: ChangelogEntryProps[];
|
||||
}
|
||||
|
||||
export default function Changelog({
|
||||
documentationProps,
|
||||
entries,
|
||||
|
|
@ -37,7 +39,7 @@ export default function Changelog({
|
|||
return (
|
||||
<>
|
||||
<Layout>
|
||||
<SEO title={'Changelog'} />
|
||||
<SEO title="Changelog" />
|
||||
<Header {...headerProps} />
|
||||
<Container>
|
||||
<NavigationContainer>
|
||||
|
|
@ -49,7 +51,7 @@ export default function Changelog({
|
|||
{entries.map((entry) => (
|
||||
<ChangelogEntry key={entry.name} {...entry} />
|
||||
))}
|
||||
<ReadOnGitHub path={'../../releases'} resource="the releases" />
|
||||
<ReadOnGitHub path="../../releases" resource="the releases" />
|
||||
</InnerContentContainer>
|
||||
</ContentContainer>
|
||||
</Container>
|
||||
|
|
@ -63,6 +65,7 @@ export async function getStaticProps() {
|
|||
const { data: releases } = await octokit.rest.repos.listReleases({
|
||||
...REPO,
|
||||
});
|
||||
|
||||
const entries: ChangelogEntryProps[] = [];
|
||||
|
||||
for (const release of releases) {
|
||||
|
|
@ -73,7 +76,7 @@ export async function getStaticProps() {
|
|||
...(release.body && {
|
||||
body: await serialize(release.body, {
|
||||
mdxOptions: {
|
||||
remarkPlugins: [require('remark-prism'), remarkGfm],
|
||||
remarkPlugins: [remarkPrism, remarkGfm],
|
||||
},
|
||||
}),
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -1,158 +1,30 @@
|
|||
import { downloads as npmDownloads } from '@nodesecure/npm-registry-sdk';
|
||||
import type { NextPage } from 'next';
|
||||
import type { Icon } from '../components/IconList';
|
||||
import { downloads as npmDownloads } from '@nodesecure/npm-registry-sdk';
|
||||
import styled from 'styled-components';
|
||||
import { AvailableFor } from '../components/AvailableFor';
|
||||
import { LargeButton } from '../components/Button';
|
||||
import { REPO, SUPPORT_LINK } from '../lib/constants';
|
||||
import { Explore } from '../components/Explore';
|
||||
import { Footer } from '../components/Footer';
|
||||
import { Header } from '../components/Header';
|
||||
import { HeaderBackground } from '../components/HeaderBackground';
|
||||
import { Icon } from '../components/IconList';
|
||||
import { Layout } from '../components/Layout';
|
||||
import { media } from '../lib/responsive';
|
||||
import { SEO } from '../components/SEO';
|
||||
import { Stat, StatsContainer } from '../components/Stats';
|
||||
import { Text18, Text15 } from '../components/Typography';
|
||||
import { Text15, Text18 } from '../components/Typography';
|
||||
import { REPO, SUPPORT_LINK } from '../lib/constants';
|
||||
import { getHeaderProps } from '../lib/getHeaderProps';
|
||||
import { getAllIcons } from '../lib/getIcons';
|
||||
import { octokit } from '../lib/octokit';
|
||||
import { media } from '../lib/responsive';
|
||||
|
||||
interface HomeProps {
|
||||
allIcons: Icon[];
|
||||
currentVersion: string;
|
||||
numStars: number;
|
||||
numDownloads: number;
|
||||
}
|
||||
|
||||
const Home: NextPage<HomeProps> = ({
|
||||
allIcons,
|
||||
currentVersion,
|
||||
numStars,
|
||||
numDownloads,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<Layout>
|
||||
<SEO
|
||||
description={
|
||||
'Iconoir is the biggest open source icon library that provides a massive selection of high-quality icons, available for free download. No premium options or email sign-up required, free for real. Icons available in SVG, Font, React, React Nativ, and Flutter libraries, Figma and Framer.'
|
||||
}
|
||||
/>
|
||||
<Header currentVersion={currentVersion} />
|
||||
<HeaderBackground>
|
||||
<HeroHead>
|
||||
<HeroText>Say hello</HeroText>
|
||||
<HeroTextSecondary>
|
||||
to your new free icon library.
|
||||
</HeroTextSecondary>
|
||||
</HeroHead>
|
||||
</HeaderBackground>
|
||||
<HeroDescription>
|
||||
A high-quality selection of free icons. Your new alternative to Noun
|
||||
Project, Flaticon, and all Figma resources. Available in SVG, Font,
|
||||
React, React Native, Flutter, Figma and Framer.
|
||||
</HeroDescription>
|
||||
<StatsContainer>
|
||||
<Stat
|
||||
value={new Intl.NumberFormat('en-US').format(allIcons.length)}
|
||||
description={
|
||||
'icons available in this very moment, and they’re growing fast!'
|
||||
}
|
||||
/>
|
||||
<Stat
|
||||
value={'100%'}
|
||||
description={
|
||||
'free icons. Iconoir is open source and we’re ready for your help.'
|
||||
}
|
||||
/>
|
||||
<Stat
|
||||
value={new Intl.NumberFormat('en-US', {
|
||||
notation: 'compact',
|
||||
}).format(numDownloads)}
|
||||
description={
|
||||
'downloads/month on React only. Iconoir also supports React Native, Flutter and CSS.'
|
||||
}
|
||||
/>
|
||||
<Stat
|
||||
value={new Intl.NumberFormat('en-US', {
|
||||
notation: 'compact',
|
||||
}).format(numStars)}
|
||||
description={
|
||||
'people who starred the project on GitHub. Show your support and be one of them.'
|
||||
}
|
||||
/>
|
||||
</StatsContainer>
|
||||
<AvailableFor />
|
||||
<SupportContainer>
|
||||
<LargeButton
|
||||
as={'a'}
|
||||
href={SUPPORT_LINK}
|
||||
target={'_blank'}
|
||||
rel={'noreferrer'}
|
||||
>
|
||||
<span>Donate</span>
|
||||
</LargeButton>
|
||||
<Supporters>
|
||||
<Supporter
|
||||
data-tooltip="Pierre Olivier Marec"
|
||||
as={'a'}
|
||||
href={'https://github.com/pomarec'}
|
||||
rel="noopener sponsored"
|
||||
src={'https://avatars.githubusercontent.com/u/802933?v=4'}
|
||||
/>
|
||||
<Supporter
|
||||
data-tooltip="Tuan Hiep"
|
||||
as={'a'}
|
||||
href={'https://opencollective.com/iconoir/contribute'}
|
||||
rel="noopener sponsored"
|
||||
src={
|
||||
'https://images.opencollective.com/tuan-hiep/17b1ef2/avatar.png?height=80'
|
||||
}
|
||||
/>
|
||||
<Supporter
|
||||
data-tooltip="Justin Kendrick"
|
||||
as={'a'}
|
||||
href={'https://opencollective.com/iconoir/contribute'}
|
||||
rel="noopener sponsored"
|
||||
src={
|
||||
'https://images.opencollective.com/guest-39c79745/avatar.png?height=80'
|
||||
}
|
||||
/>
|
||||
<Supporter
|
||||
data-tooltip="Anon"
|
||||
as={'a'}
|
||||
href={'https://opencollective.com/iconoir/contribute'}
|
||||
rel="noopener sponsored"
|
||||
src={
|
||||
'https://opencollective.com/static/images/default-guest-logo.svg'
|
||||
}
|
||||
/>
|
||||
<Supporter
|
||||
data-tooltip="Luca Burgio"
|
||||
as={'a'}
|
||||
href={'https://twitter.com/burgioluca'}
|
||||
rel="noopener sponsored"
|
||||
src={'https://lucaburgio.com/images/profile2.png'}
|
||||
/>
|
||||
</Supporters>
|
||||
<Text15>
|
||||
Join our supporters and help us continue developing Iconoir.
|
||||
</Text15>
|
||||
</SupportContainer>
|
||||
<Explore allIcons={allIcons} />
|
||||
</Layout>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const HeroHead = styled.div`
|
||||
const HeroHead = styled.div`
|
||||
margin: 60px auto 40px auto;
|
||||
${media.md} {
|
||||
margin: 160px auto 80px auto;
|
||||
}
|
||||
`;
|
||||
|
||||
export const HeroText = styled.h1`
|
||||
font-size: 50px;
|
||||
font-weight: 700;
|
||||
|
|
@ -177,7 +49,8 @@ export const HeroTextSecondary = styled(HeroText)`
|
|||
color: var(--g4);
|
||||
max-width: 1140px;
|
||||
`;
|
||||
export const HeroDescription = styled(Text18)<{ topMargin?: number }>`
|
||||
|
||||
const HeroDescription = styled(Text18)<{ topMargin?: number }>`
|
||||
display: block;
|
||||
max-width: 750px;
|
||||
margin: 0 auto;
|
||||
|
|
@ -194,6 +67,7 @@ const Supporters = styled.div`
|
|||
align-items: center;
|
||||
margin-bottom: 8px !important;
|
||||
`;
|
||||
|
||||
const Supporter = styled.div<{ src?: string }>`
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
|
|
@ -241,6 +115,119 @@ const SupportContainer = styled.div`
|
|||
}
|
||||
`;
|
||||
|
||||
interface HomeProps {
|
||||
allIcons: Icon[];
|
||||
currentVersion: string;
|
||||
numStars: number;
|
||||
numDownloads: number;
|
||||
}
|
||||
|
||||
const Home: NextPage<HomeProps> = ({
|
||||
allIcons,
|
||||
currentVersion,
|
||||
numStars,
|
||||
numDownloads,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<Layout>
|
||||
<SEO
|
||||
description="Iconoir is the biggest open source icon library that provides a massive selection of high-quality icons, available for free download. No premium options or email sign-up required, free for real. Icons available in SVG, Font, React, React Nativ, and Flutter libraries, Figma and Framer."
|
||||
/>
|
||||
<Header currentVersion={currentVersion} />
|
||||
<HeaderBackground>
|
||||
<HeroHead>
|
||||
<HeroText>Say hello</HeroText>
|
||||
<HeroTextSecondary>
|
||||
to your new free icon library.
|
||||
</HeroTextSecondary>
|
||||
</HeroHead>
|
||||
</HeaderBackground>
|
||||
<HeroDescription>
|
||||
A high-quality selection of free icons. Your new alternative to Noun
|
||||
Project, Flaticon, and all Figma resources. Available in SVG, Font,
|
||||
React, React Native, Flutter, Figma and Framer.
|
||||
</HeroDescription>
|
||||
<StatsContainer>
|
||||
<Stat
|
||||
value={new Intl.NumberFormat('en-US').format(allIcons.length)}
|
||||
description="icons available in this very moment, and they’re growing fast!"
|
||||
/>
|
||||
<Stat
|
||||
value="100%"
|
||||
description="free icons. Iconoir is open source and we’re ready for your help."
|
||||
/>
|
||||
<Stat
|
||||
value={new Intl.NumberFormat('en-US', {
|
||||
notation: 'compact',
|
||||
}).format(numDownloads)}
|
||||
description="downloads/month on React only. Iconoir also supports React Native, Flutter and CSS."
|
||||
/>
|
||||
<Stat
|
||||
value={new Intl.NumberFormat('en-US', {
|
||||
notation: 'compact',
|
||||
}).format(numStars)}
|
||||
description="people who starred the project on GitHub. Show your support and be one of them."
|
||||
/>
|
||||
</StatsContainer>
|
||||
<AvailableFor />
|
||||
<SupportContainer>
|
||||
<LargeButton
|
||||
as="a"
|
||||
href={SUPPORT_LINK}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<span>Donate</span>
|
||||
</LargeButton>
|
||||
<Supporters>
|
||||
<Supporter
|
||||
data-tooltip="Pierre Olivier Marec"
|
||||
as="a"
|
||||
href="https://github.com/pomarec"
|
||||
rel="noopener sponsored"
|
||||
src="https://avatars.githubusercontent.com/u/802933?v=4"
|
||||
/>
|
||||
<Supporter
|
||||
data-tooltip="Tuan Hiep"
|
||||
as="a"
|
||||
href="https://opencollective.com/iconoir/contribute"
|
||||
rel="noopener sponsored"
|
||||
src="https://images.opencollective.com/tuan-hiep/17b1ef2/avatar.png?height=80"
|
||||
/>
|
||||
<Supporter
|
||||
data-tooltip="Justin Kendrick"
|
||||
as="a"
|
||||
href="https://opencollective.com/iconoir/contribute"
|
||||
rel="noopener sponsored"
|
||||
src="https://images.opencollective.com/guest-39c79745/avatar.png?height=80"
|
||||
/>
|
||||
<Supporter
|
||||
data-tooltip="Anon"
|
||||
as="a"
|
||||
href="https://opencollective.com/iconoir/contribute"
|
||||
rel="noopener sponsored"
|
||||
src="https://opencollective.com/static/images/default-guest-logo.svg"
|
||||
/>
|
||||
<Supporter
|
||||
data-tooltip="Luca Burgio"
|
||||
as="a"
|
||||
href="https://twitter.com/burgioluca"
|
||||
rel="noopener sponsored"
|
||||
src="https://lucaburgio.com/images/profile2.png"
|
||||
/>
|
||||
</Supporters>
|
||||
<Text15>
|
||||
Join our supporters and help us continue developing Iconoir.
|
||||
</Text15>
|
||||
</SupportContainer>
|
||||
<Explore allIcons={allIcons} />
|
||||
</Layout>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
||||
|
||||
export async function getStaticProps() {
|
||||
|
|
@ -251,13 +238,17 @@ export async function getStaticProps() {
|
|||
} = await octokit.rest.repos.get({
|
||||
...REPO,
|
||||
});
|
||||
if (!numStars) throw new Error('Could not find GitHub stars');
|
||||
|
||||
if (!numStars)
|
||||
throw new Error('Could not find GitHub stars');
|
||||
|
||||
const { downloads: numDownloads } = await npmDownloads(
|
||||
'iconoir-react',
|
||||
'last-month',
|
||||
);
|
||||
if (!numDownloads) throw new Error('Could not find NPM downloads');
|
||||
|
||||
if (!numDownloads)
|
||||
throw new Error('Could not find NPM downloads');
|
||||
|
||||
return {
|
||||
props: {
|
||||
|
|
|
|||
|
|
@ -1,108 +1,18 @@
|
|||
import type { NextPage } from 'next';
|
||||
import type { HeaderProps } from '../../components/Header';
|
||||
import { ArrowRight } from 'iconoir-react';
|
||||
import { NextPage } from 'next';
|
||||
import styled from 'styled-components';
|
||||
import { HeroText } from '../.';
|
||||
import { HeroText, HeroTextSecondary } from '../.';
|
||||
import { Ad } from '../../components/Ad';
|
||||
import { HeroTextSecondary } from '../.';
|
||||
import { LargeButton } from '../../components/Button';
|
||||
import { Footer } from '../../components/Footer';
|
||||
import { Header, HeaderProps } from '../../components/Header';
|
||||
import { Header } from '../../components/Header';
|
||||
import { HeaderSecondary } from '../../components/HeaderSecondary';
|
||||
import { Layout } from '../../components/Layout';
|
||||
import { media } from '../../lib/responsive';
|
||||
import { SEO } from '../../components/SEO';
|
||||
import { H2, H3, Text18 } from '../../components/Typography';
|
||||
import { getHeaderProps } from '../../lib/getHeaderProps';
|
||||
|
||||
interface SupportProps extends HeaderProps {}
|
||||
|
||||
const Support: NextPage<SupportProps> = ({ ...headerProps }) => {
|
||||
return (
|
||||
<>
|
||||
<Layout>
|
||||
<SEO
|
||||
title={'React Icons'}
|
||||
description={
|
||||
'Free React Icons - Iconoir is a free open-source icons library.'
|
||||
}
|
||||
/>
|
||||
<Header {...headerProps} />
|
||||
<HeaderSecondary>
|
||||
<SupportHead>
|
||||
<HeroText>Free React Icons</HeroText>
|
||||
<HeroTextSecondary>Iconoir has you covered.</HeroTextSecondary>
|
||||
</SupportHead>
|
||||
</HeaderSecondary>
|
||||
<PageContainer>
|
||||
<Ad />
|
||||
<Text18>
|
||||
Iconoir has a React library, where we blend creativity with
|
||||
functionality to elevate your React projects.
|
||||
<br></br>
|
||||
<br></br>
|
||||
Our library of free, open-source icons is designed to seamlessly
|
||||
integrate with your React applications, providing you with a vast
|
||||
array of options to enhance your UI/UX design.
|
||||
</Text18>
|
||||
<CallToAction as={'a'} href={'/'}>
|
||||
<span>Get the icons</span>
|
||||
<ArrowRight />
|
||||
</CallToAction>
|
||||
<Text18>
|
||||
Integrating Iconoir's React icons into your project is a breeze.
|
||||
<br></br>
|
||||
<br></br>
|
||||
Read the <a href="../docs/packages/iconoir-react">
|
||||
documentation
|
||||
</a>{' '}
|
||||
on how to implement Iconoir React icons, and enhance your
|
||||
application's aesthetics and user experience.
|
||||
</Text18>
|
||||
<H2>
|
||||
<br></br>
|
||||
<br></br>Why Choose Iconoir for Your React Icons?
|
||||
</H2>
|
||||
<Image src="/building-iconoir.gif" alt="Building Iconoir" />
|
||||
<Text18>
|
||||
<H3>Open-Source and Free</H3>Dive into a world where quality meets
|
||||
accessibility. Iconoir offers a comprehensive set of icons, entirely
|
||||
free and open-source, making them a perfect fit for developers and
|
||||
designers alike.
|
||||
<br></br>
|
||||
<br></br>
|
||||
<H3>Designed for React</H3>Our icons are meticulously crafted to
|
||||
complement React's dynamic capabilities. Whether you're building a
|
||||
web app or a mobile application, our React icons are optimized for
|
||||
performance and scalability.
|
||||
<br></br>
|
||||
<br></br>
|
||||
<H3>Easy Integration</H3>With straightforward implementation, you
|
||||
can start using our icons in your React projects with minimal
|
||||
effort. Our documentation guides you through every step, ensuring a
|
||||
smooth integration process.
|
||||
<br></br>
|
||||
<br></br>
|
||||
<H3>Versatile and Customizable</H3>From minimalistic designs to more
|
||||
intricate illustrations, our icons cater to a wide range of styles.
|
||||
Plus, they're fully customizable, allowing you to tweak them to fit
|
||||
your specific design requirements.
|
||||
<br></br>
|
||||
<br></br>
|
||||
<H3>Community-Driven</H3>Being a part of the open-source community,
|
||||
Iconoir thrives on collaboration and feedback. We continuously
|
||||
update and expand our collection based on the needs and suggestions
|
||||
of our users.
|
||||
</Text18>
|
||||
<CallToAction as={'a'} href={'/'}>
|
||||
<span>Get the icons</span>
|
||||
<ArrowRight />
|
||||
</CallToAction>
|
||||
</PageContainer>
|
||||
</Layout>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
};
|
||||
import { media } from '../../lib/responsive';
|
||||
|
||||
export const SupportHead = styled.div`
|
||||
margin: 60px auto 40px auto;
|
||||
|
|
@ -181,6 +91,103 @@ export const DonateHeader = styled.div`
|
|||
}
|
||||
`;
|
||||
|
||||
interface SupportProps extends HeaderProps {}
|
||||
|
||||
const Support: NextPage<SupportProps> = ({ ...headerProps }) => {
|
||||
return (
|
||||
<>
|
||||
<Layout>
|
||||
<SEO
|
||||
title="React Icons"
|
||||
description="Free React Icons - Iconoir is a free open-source icons library."
|
||||
/>
|
||||
<Header {...headerProps} />
|
||||
<HeaderSecondary>
|
||||
<SupportHead>
|
||||
<HeroText>Free React Icons</HeroText>
|
||||
<HeroTextSecondary>Iconoir has you covered.</HeroTextSecondary>
|
||||
</SupportHead>
|
||||
</HeaderSecondary>
|
||||
<PageContainer>
|
||||
<Ad />
|
||||
<Text18>
|
||||
Iconoir has a React library, where we blend creativity with
|
||||
functionality to elevate your React projects.
|
||||
<br></br>
|
||||
<br></br>
|
||||
Our library of free, open-source icons is designed to seamlessly
|
||||
integrate with your React applications, providing you with a vast
|
||||
array of options to enhance your UI/UX design.
|
||||
</Text18>
|
||||
<CallToAction as="a" href="/">
|
||||
<span>Get the icons</span>
|
||||
<ArrowRight />
|
||||
</CallToAction>
|
||||
<Text18>
|
||||
Integrating Iconoir's React icons into your project is a breeze.
|
||||
<br></br>
|
||||
<br></br>
|
||||
Read the
|
||||
{' '}
|
||||
<a href="../docs/packages/iconoir-react">
|
||||
documentation
|
||||
</a>
|
||||
{' '}
|
||||
on how to implement Iconoir React icons, and enhance your
|
||||
application's aesthetics and user experience.
|
||||
</Text18>
|
||||
<H2>
|
||||
<br></br>
|
||||
<br></br>
|
||||
Why Choose Iconoir for Your React Icons?
|
||||
</H2>
|
||||
<Image src="/building-iconoir.gif" alt="Building Iconoir" />
|
||||
<Text18>
|
||||
<H3>Open-Source and Free</H3>
|
||||
Dive into a world where quality meets
|
||||
accessibility. Iconoir offers a comprehensive set of icons, entirely
|
||||
free and open-source, making them a perfect fit for developers and
|
||||
designers alike.
|
||||
<br></br>
|
||||
<br></br>
|
||||
<H3>Designed for React</H3>
|
||||
Our icons are meticulously crafted to
|
||||
complement React's dynamic capabilities. Whether you're building a
|
||||
web app or a mobile application, our React icons are optimized for
|
||||
performance and scalability.
|
||||
<br></br>
|
||||
<br></br>
|
||||
<H3>Easy Integration</H3>
|
||||
With straightforward implementation, you
|
||||
can start using our icons in your React projects with minimal
|
||||
effort. Our documentation guides you through every step, ensuring a
|
||||
smooth integration process.
|
||||
<br></br>
|
||||
<br></br>
|
||||
<H3>Versatile and Customizable</H3>
|
||||
From minimalistic designs to more
|
||||
intricate illustrations, our icons cater to a wide range of styles.
|
||||
Plus, they're fully customizable, allowing you to tweak them to fit
|
||||
your specific design requirements.
|
||||
<br></br>
|
||||
<br></br>
|
||||
<H3>Community-Driven</H3>
|
||||
Being a part of the open-source community,
|
||||
Iconoir thrives on collaboration and feedback. We continuously
|
||||
update and expand our collection based on the needs and suggestions
|
||||
of our users.
|
||||
</Text18>
|
||||
<CallToAction as="a" href="/">
|
||||
<span>Get the icons</span>
|
||||
<ArrowRight />
|
||||
</CallToAction>
|
||||
</PageContainer>
|
||||
</Layout>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Support;
|
||||
|
||||
export async function getStaticProps() {
|
||||
|
|
|
|||
|
|
@ -1,113 +1,19 @@
|
|||
import { ArrowRight, Flash, Discord, Globe, DesignNib } from 'iconoir-react';
|
||||
import { NextPage } from 'next';
|
||||
import type { NextPage } from 'next';
|
||||
import type { HeaderProps } from '../components/Header';
|
||||
import { ArrowRight, DesignNib, Discord, Flash, Globe } from 'iconoir-react';
|
||||
import styled from 'styled-components';
|
||||
import { HeroText } from '.';
|
||||
import { HeroTextSecondary } from '.';
|
||||
import { HeroText, HeroTextSecondary } from '.';
|
||||
import { LargeButton } from '../components/Button';
|
||||
import { DISCORD_LINK, SUPPORT_LINK } from '../lib/constants';
|
||||
import { Footer } from '../components/Footer';
|
||||
import { Header, HeaderProps } from '../components/Header';
|
||||
import { Header } from '../components/Header';
|
||||
import { HeaderSecondary } from '../components/HeaderSecondary';
|
||||
import { Layout } from '../components/Layout';
|
||||
import { media } from '../lib/responsive';
|
||||
import { Praise } from '../components/Praise';
|
||||
import { SEO } from '../components/SEO';
|
||||
import { Body, Heading2, Text18 } from '../components/Typography';
|
||||
import { DISCORD_LINK, SUPPORT_LINK } from '../lib/constants';
|
||||
import { getHeaderProps } from '../lib/getHeaderProps';
|
||||
import { Praise } from '../components/Praise';
|
||||
|
||||
interface SupportProps extends HeaderProps {}
|
||||
|
||||
const Support: NextPage<SupportProps> = ({ ...headerProps }) => {
|
||||
return (
|
||||
<>
|
||||
<Layout>
|
||||
<SEO
|
||||
title={'Donate - Our Mission'}
|
||||
description={
|
||||
'Our mission: A free, complete, and open icon library. Iconoir wants to give to developers and users high-quality free icons.'
|
||||
}
|
||||
/>
|
||||
<Header {...headerProps} />
|
||||
<HeaderSecondary>
|
||||
<SupportHead>
|
||||
<HeroText>Our Goal</HeroText>
|
||||
<HeroTextSecondary>
|
||||
A free, complete, and open icon library.
|
||||
</HeroTextSecondary>
|
||||
</SupportHead>
|
||||
</HeaderSecondary>
|
||||
<PageContainer>
|
||||
<PillarsContainer>
|
||||
<Pillar>
|
||||
<PillarIcon $R={'140'} $G={'26'} $B={'245'}>
|
||||
<Flash />
|
||||
</PillarIcon>
|
||||
<PillarTitle>Free</PillarTitle>
|
||||
<PillarDescription>
|
||||
We want to give to developers and users high-quality free icons.
|
||||
Hassle-free.
|
||||
</PillarDescription>
|
||||
</Pillar>
|
||||
<Pillar>
|
||||
<PillarIcon $R={'72'} $G={'88'} $B={'255'}>
|
||||
<DesignNib />
|
||||
</PillarIcon>
|
||||
<PillarTitle>Complete</PillarTitle>
|
||||
<PillarDescription>
|
||||
We’re aiming at having as much unique icons as possible.
|
||||
</PillarDescription>
|
||||
</Pillar>
|
||||
<Pillar>
|
||||
<PillarIcon $R={'65'} $G={'209'} $B={'255'}>
|
||||
<Globe />
|
||||
</PillarIcon>
|
||||
<PillarTitle>Ambitious</PillarTitle>
|
||||
<PillarDescription>
|
||||
We want to help and be part of as many projects as possible.
|
||||
</PillarDescription>
|
||||
</Pillar>
|
||||
</PillarsContainer>
|
||||
<WideSection>
|
||||
<Heading2>Become part of the project.</Heading2>
|
||||
<Text18>
|
||||
If you consider this library valuable for you and your projects,
|
||||
support Iconoir with a donation to help us sustain costs and
|
||||
development time.
|
||||
</Text18>
|
||||
<CallToAction as={'a'} href={SUPPORT_LINK} target={'_blank'}>
|
||||
<span>Donate</span>
|
||||
<ArrowRight />
|
||||
</CallToAction>
|
||||
</WideSection>
|
||||
<PraiseContainer>
|
||||
<PraiseTitle>What our friends say</PraiseTitle>
|
||||
<Praise />
|
||||
</PraiseContainer>
|
||||
<DonateContainer>
|
||||
<DonateLeft>
|
||||
<DonateHeader>Join us on Discord</DonateHeader>
|
||||
<Text18>
|
||||
Join the community and help us with your suggestions and
|
||||
feedback.
|
||||
</Text18>
|
||||
</DonateLeft>
|
||||
<DonateRight>
|
||||
<DonateButton
|
||||
as={'a'}
|
||||
href={DISCORD_LINK}
|
||||
target={'_blank'}
|
||||
rel={'noreferrer'}
|
||||
>
|
||||
<span>Join</span> <Discord />
|
||||
</DonateButton>
|
||||
</DonateRight>
|
||||
</DonateContainer>
|
||||
</PageContainer>
|
||||
</Layout>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
};
|
||||
import { media } from '../lib/responsive';
|
||||
|
||||
const PillarsContainer = styled.div`
|
||||
display: flex;
|
||||
|
|
@ -167,7 +73,7 @@ const PillarIcon = styled.span<{ $R?: string; $G?: string; $B?: string }>`
|
|||
border-radius: 18px;
|
||||
`;
|
||||
|
||||
export const SupportHead = styled.div`
|
||||
const SupportHead = styled.div`
|
||||
margin: 60px auto 40px auto;
|
||||
${media.md} {
|
||||
margin: 100px auto 80px auto;
|
||||
|
|
@ -181,6 +87,7 @@ const PageContainer = styled.div`
|
|||
margin: 110px auto 0 auto;
|
||||
}
|
||||
`;
|
||||
|
||||
const WideSection = styled.div`
|
||||
width: 60%;
|
||||
background: var(--g7);
|
||||
|
|
@ -192,12 +99,14 @@ const WideSection = styled.div`
|
|||
padding: 80px 20%;
|
||||
}
|
||||
`;
|
||||
|
||||
const PraiseTitle = styled(Heading2)`
|
||||
&&& {
|
||||
margin: 100px 0 80px 0;
|
||||
text-align: center;
|
||||
}
|
||||
`;
|
||||
|
||||
const DonateLeft = styled.div`
|
||||
max-width: 800px;
|
||||
`;
|
||||
|
|
@ -221,6 +130,7 @@ const DonateButton = styled(LargeButton)`
|
|||
height: 50px;
|
||||
}
|
||||
`;
|
||||
|
||||
const CallToAction = styled(LargeButton)`
|
||||
&&& {
|
||||
margin-top: 40px;
|
||||
|
|
@ -265,6 +175,100 @@ const PraiseContainer = styled.div`
|
|||
width: 100%;
|
||||
`;
|
||||
|
||||
interface SupportProps extends HeaderProps {}
|
||||
|
||||
const Support: NextPage<SupportProps> = ({ ...headerProps }) => {
|
||||
return (
|
||||
<>
|
||||
<Layout>
|
||||
<SEO
|
||||
title="Donate - Our Mission"
|
||||
description="Our mission: A free, complete, and open icon library. Iconoir wants to give to developers and users high-quality free icons."
|
||||
/>
|
||||
<Header {...headerProps} />
|
||||
<HeaderSecondary>
|
||||
<SupportHead>
|
||||
<HeroText>Our Goal</HeroText>
|
||||
<HeroTextSecondary>
|
||||
A free, complete, and open icon library.
|
||||
</HeroTextSecondary>
|
||||
</SupportHead>
|
||||
</HeaderSecondary>
|
||||
<PageContainer>
|
||||
<PillarsContainer>
|
||||
<Pillar>
|
||||
<PillarIcon $R="140" $G="26" $B="245">
|
||||
<Flash />
|
||||
</PillarIcon>
|
||||
<PillarTitle>Free</PillarTitle>
|
||||
<PillarDescription>
|
||||
We want to give to developers and users high-quality free icons.
|
||||
Hassle-free.
|
||||
</PillarDescription>
|
||||
</Pillar>
|
||||
<Pillar>
|
||||
<PillarIcon $R="72" $G="88" $B="255">
|
||||
<DesignNib />
|
||||
</PillarIcon>
|
||||
<PillarTitle>Complete</PillarTitle>
|
||||
<PillarDescription>
|
||||
We’re aiming at having as much unique icons as possible.
|
||||
</PillarDescription>
|
||||
</Pillar>
|
||||
<Pillar>
|
||||
<PillarIcon $R="65" $G="209" $B="255">
|
||||
<Globe />
|
||||
</PillarIcon>
|
||||
<PillarTitle>Ambitious</PillarTitle>
|
||||
<PillarDescription>
|
||||
We want to help and be part of as many projects as possible.
|
||||
</PillarDescription>
|
||||
</Pillar>
|
||||
</PillarsContainer>
|
||||
<WideSection>
|
||||
<Heading2>Become part of the project.</Heading2>
|
||||
<Text18>
|
||||
If you consider this library valuable for you and your projects,
|
||||
support Iconoir with a donation to help us sustain costs and
|
||||
development time.
|
||||
</Text18>
|
||||
<CallToAction as="a" href={SUPPORT_LINK} target="_blank">
|
||||
<span>Donate</span>
|
||||
<ArrowRight />
|
||||
</CallToAction>
|
||||
</WideSection>
|
||||
<PraiseContainer>
|
||||
<PraiseTitle>What our friends say</PraiseTitle>
|
||||
<Praise />
|
||||
</PraiseContainer>
|
||||
<DonateContainer>
|
||||
<DonateLeft>
|
||||
<DonateHeader>Join us on Discord</DonateHeader>
|
||||
<Text18>
|
||||
Join the community and help us with your suggestions and
|
||||
feedback.
|
||||
</Text18>
|
||||
</DonateLeft>
|
||||
<DonateRight>
|
||||
<DonateButton
|
||||
as="a"
|
||||
href={DISCORD_LINK}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<span>Join</span>
|
||||
{' '}
|
||||
<Discord />
|
||||
</DonateButton>
|
||||
</DonateRight>
|
||||
</DonateContainer>
|
||||
</PageContainer>
|
||||
</Layout>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Support;
|
||||
|
||||
export async function getStaticProps() {
|
||||
|
|
|
|||
|
|
@ -28,11 +28,11 @@
|
|||
* --syntax-cursor-line: hsla(230, 8%, 24%, 0.05);
|
||||
*/
|
||||
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
code[class*='language-'],
|
||||
pre[class*='language-'] {
|
||||
background: hsl(230, 1%, 98%);
|
||||
color: hsl(230, 8%, 24%);
|
||||
font-family: "Fira Code", "Fira Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace;
|
||||
font-family: 'Fira Code', 'Fira Mono', Menlo, Consolas, 'DejaVu Sans Mono', monospace;
|
||||
direction: ltr;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
|
|
@ -49,22 +49,22 @@ pre[class*="language-"] {
|
|||
}
|
||||
|
||||
/* Selection */
|
||||
code[class*="language-"]::-moz-selection,
|
||||
code[class*="language-"] *::-moz-selection,
|
||||
pre[class*="language-"] *::-moz-selection {
|
||||
code[class*='language-']::-moz-selection,
|
||||
code[class*='language-'] *::-moz-selection,
|
||||
pre[class*='language-'] *::-moz-selection {
|
||||
background: hsl(230, 1%, 90%);
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
code[class*="language-"]::selection,
|
||||
code[class*="language-"] *::selection,
|
||||
pre[class*="language-"] *::selection {
|
||||
code[class*='language-']::selection,
|
||||
code[class*='language-'] *::selection,
|
||||
pre[class*='language-'] *::selection {
|
||||
background: hsl(230, 1%, 90%);
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
pre[class*="language-"] {
|
||||
pre[class*='language-'] {
|
||||
padding: 1em;
|
||||
margin: 0.5em 0;
|
||||
overflow: auto;
|
||||
|
|
@ -72,7 +72,7 @@ pre[class*="language-"] {
|
|||
}
|
||||
|
||||
/* Inline code */
|
||||
:not(pre)>code[class*="language-"] {
|
||||
:not(pre) > code[class*='language-'] {
|
||||
padding: 0.2em 0.3em;
|
||||
border-radius: 0.3em;
|
||||
white-space: normal;
|
||||
|
|
@ -118,7 +118,7 @@ pre[class*="language-"] {
|
|||
.token.inserted,
|
||||
.token.regex,
|
||||
.token.attr-value,
|
||||
.token.attr-value>.token.punctuation {
|
||||
.token.attr-value > .token.punctuation {
|
||||
color: hsl(119, 34%, 47%);
|
||||
}
|
||||
|
||||
|
|
@ -133,8 +133,8 @@ pre[class*="language-"] {
|
|||
}
|
||||
|
||||
/* HTML overrides */
|
||||
.token.attr-value>.token.punctuation.attr-equals,
|
||||
.token.special-attr>.token.attr-value>.token.value.css {
|
||||
.token.attr-value > .token.punctuation.attr-equals,
|
||||
.token.special-attr > .token.attr-value > .token.value.css {
|
||||
color: hsl(230, 8%, 24%);
|
||||
}
|
||||
|
||||
|
|
@ -148,11 +148,11 @@ pre[class*="language-"] {
|
|||
}
|
||||
|
||||
.language-css .token.function,
|
||||
.language-css .token.url>.token.function {
|
||||
.language-css .token.url > .token.function {
|
||||
color: hsl(198, 99%, 37%);
|
||||
}
|
||||
|
||||
.language-css .token.url>.token.string.url {
|
||||
.language-css .token.url > .token.string.url {
|
||||
color: hsl(119, 34%, 47%);
|
||||
}
|
||||
|
||||
|
|
@ -166,7 +166,7 @@ pre[class*="language-"] {
|
|||
color: hsl(301, 63%, 40%);
|
||||
}
|
||||
|
||||
.language-javascript .token.template-string>.token.interpolation>.token.interpolation-punctuation.punctuation {
|
||||
.language-javascript .token.template-string > .token.interpolation > .token.interpolation-punctuation.punctuation {
|
||||
color: hsl(344, 84%, 43%);
|
||||
}
|
||||
|
||||
|
|
@ -181,16 +181,16 @@ pre[class*="language-"] {
|
|||
|
||||
/* MD overrides */
|
||||
.language-markdown .token.url,
|
||||
.language-markdown .token.url>.token.operator,
|
||||
.language-markdown .token.url-reference.url>.token.string {
|
||||
.language-markdown .token.url > .token.operator,
|
||||
.language-markdown .token.url-reference.url > .token.string {
|
||||
color: hsl(230, 8%, 24%);
|
||||
}
|
||||
|
||||
.language-markdown .token.url>.token.content {
|
||||
.language-markdown .token.url > .token.content {
|
||||
color: hsl(221, 87%, 60%);
|
||||
}
|
||||
|
||||
.language-markdown .token.url>.token.url,
|
||||
.language-markdown .token.url > .token.url,
|
||||
.language-markdown .token.url-reference.url {
|
||||
color: hsl(198, 99%, 37%);
|
||||
}
|
||||
|
|
@ -216,7 +216,7 @@ pre[class*="language-"] {
|
|||
.language-markdown .token.strike .token.content,
|
||||
.language-markdown .token.strike .token.punctuation,
|
||||
.language-markdown .token.list.punctuation,
|
||||
.language-markdown .token.title.important>.token.punctuation {
|
||||
.language-markdown .token.title.important > .token.punctuation {
|
||||
color: hsl(5, 74%, 59%);
|
||||
}
|
||||
|
||||
|
|
@ -251,26 +251,26 @@ pre[class*="language-"] {
|
|||
|
||||
/* Toolbar plugin overrides */
|
||||
/* Space out all buttons and move them away from the right edge of the code block */
|
||||
div.code-toolbar>.toolbar.toolbar>.toolbar-item {
|
||||
div.code-toolbar > .toolbar.toolbar > .toolbar-item {
|
||||
margin-right: 0.4em;
|
||||
}
|
||||
|
||||
/* Styling the buttons */
|
||||
div.code-toolbar>.toolbar.toolbar>.toolbar-item>button,
|
||||
div.code-toolbar>.toolbar.toolbar>.toolbar-item>a,
|
||||
div.code-toolbar>.toolbar.toolbar>.toolbar-item>span {
|
||||
div.code-toolbar > .toolbar.toolbar > .toolbar-item > button,
|
||||
div.code-toolbar > .toolbar.toolbar > .toolbar-item > a,
|
||||
div.code-toolbar > .toolbar.toolbar > .toolbar-item > span {
|
||||
background: hsl(230, 1%, 90%);
|
||||
color: hsl(230, 6%, 44%);
|
||||
padding: 0.1em 0.4em;
|
||||
border-radius: 0.3em;
|
||||
}
|
||||
|
||||
div.code-toolbar>.toolbar.toolbar>.toolbar-item>button:hover,
|
||||
div.code-toolbar>.toolbar.toolbar>.toolbar-item>button:focus,
|
||||
div.code-toolbar>.toolbar.toolbar>.toolbar-item>a:hover,
|
||||
div.code-toolbar>.toolbar.toolbar>.toolbar-item>a:focus,
|
||||
div.code-toolbar>.toolbar.toolbar>.toolbar-item>span:hover,
|
||||
div.code-toolbar>.toolbar.toolbar>.toolbar-item>span:focus {
|
||||
div.code-toolbar > .toolbar.toolbar > .toolbar-item > button:hover,
|
||||
div.code-toolbar > .toolbar.toolbar > .toolbar-item > button:focus,
|
||||
div.code-toolbar > .toolbar.toolbar > .toolbar-item > a:hover,
|
||||
div.code-toolbar > .toolbar.toolbar > .toolbar-item > a:focus,
|
||||
div.code-toolbar > .toolbar.toolbar > .toolbar-item > span:hover,
|
||||
div.code-toolbar > .toolbar.toolbar > .toolbar-item > span:focus {
|
||||
background: hsl(230, 1%, 78%);
|
||||
/* custom: darken(--syntax-bg, 20%) */
|
||||
color: hsl(230, 8%, 24%);
|
||||
|
|
@ -295,7 +295,7 @@ div.code-toolbar>.toolbar.toolbar>.toolbar-item>span:focus {
|
|||
|
||||
/* Hovering over a linkable line number (in the gutter area) */
|
||||
/* Requires Line Numbers plugin as well */
|
||||
pre[id].linkable-line-numbers.linkable-line-numbers span.line-numbers-rows>span:hover:before {
|
||||
pre[id].linkable-line-numbers.linkable-line-numbers span.line-numbers-rows > span:hover:before {
|
||||
background-color: hsla(230, 8%, 24%, 0.05);
|
||||
}
|
||||
|
||||
|
|
@ -307,8 +307,8 @@ pre[id].linkable-line-numbers.linkable-line-numbers span.line-numbers-rows>span:
|
|||
}
|
||||
|
||||
/* Stuff in the gutter */
|
||||
.line-numbers .line-numbers-rows>span:before,
|
||||
.command-line .command-line-prompt>span:before {
|
||||
.line-numbers .line-numbers-rows > span:before,
|
||||
.command-line .command-line-prompt > span:before {
|
||||
color: hsl(230, 1%, 62%);
|
||||
}
|
||||
|
||||
|
|
@ -340,41 +340,41 @@ pre[id].linkable-line-numbers.linkable-line-numbers span.line-numbers-rows>span:
|
|||
|
||||
/* Diff Highlight plugin overrides */
|
||||
/* Taken from https://github.com/atom/github/blob/master/styles/variables.less */
|
||||
pre.diff-highlight>code .token.token.deleted:not(.prefix),
|
||||
pre>code.diff-highlight .token.token.deleted:not(.prefix) {
|
||||
pre.diff-highlight > code .token.token.deleted:not(.prefix),
|
||||
pre > code.diff-highlight .token.token.deleted:not(.prefix) {
|
||||
background-color: hsla(353, 100%, 66%, 0.15);
|
||||
}
|
||||
|
||||
pre.diff-highlight>code .token.token.deleted:not(.prefix)::-moz-selection,
|
||||
pre.diff-highlight>code .token.token.deleted:not(.prefix) *::-moz-selection,
|
||||
pre>code.diff-highlight .token.token.deleted:not(.prefix)::-moz-selection,
|
||||
pre>code.diff-highlight .token.token.deleted:not(.prefix) *::-moz-selection {
|
||||
pre.diff-highlight > code .token.token.deleted:not(.prefix)::-moz-selection,
|
||||
pre.diff-highlight > code .token.token.deleted:not(.prefix) *::-moz-selection,
|
||||
pre > code.diff-highlight .token.token.deleted:not(.prefix)::-moz-selection,
|
||||
pre > code.diff-highlight .token.token.deleted:not(.prefix) *::-moz-selection {
|
||||
background-color: hsla(353, 95%, 66%, 0.25);
|
||||
}
|
||||
|
||||
pre.diff-highlight>code .token.token.deleted:not(.prefix)::selection,
|
||||
pre.diff-highlight>code .token.token.deleted:not(.prefix) *::selection,
|
||||
pre>code.diff-highlight .token.token.deleted:not(.prefix)::selection,
|
||||
pre>code.diff-highlight .token.token.deleted:not(.prefix) *::selection {
|
||||
pre.diff-highlight > code .token.token.deleted:not(.prefix)::selection,
|
||||
pre.diff-highlight > code .token.token.deleted:not(.prefix) *::selection,
|
||||
pre > code.diff-highlight .token.token.deleted:not(.prefix)::selection,
|
||||
pre > code.diff-highlight .token.token.deleted:not(.prefix) *::selection {
|
||||
background-color: hsla(353, 95%, 66%, 0.25);
|
||||
}
|
||||
|
||||
pre.diff-highlight>code .token.token.inserted:not(.prefix),
|
||||
pre>code.diff-highlight .token.token.inserted:not(.prefix) {
|
||||
pre.diff-highlight > code .token.token.inserted:not(.prefix),
|
||||
pre > code.diff-highlight .token.token.inserted:not(.prefix) {
|
||||
background-color: hsla(137, 100%, 55%, 0.15);
|
||||
}
|
||||
|
||||
pre.diff-highlight>code .token.token.inserted:not(.prefix)::-moz-selection,
|
||||
pre.diff-highlight>code .token.token.inserted:not(.prefix) *::-moz-selection,
|
||||
pre>code.diff-highlight .token.token.inserted:not(.prefix)::-moz-selection,
|
||||
pre>code.diff-highlight .token.token.inserted:not(.prefix) *::-moz-selection {
|
||||
pre.diff-highlight > code .token.token.inserted:not(.prefix)::-moz-selection,
|
||||
pre.diff-highlight > code .token.token.inserted:not(.prefix) *::-moz-selection,
|
||||
pre > code.diff-highlight .token.token.inserted:not(.prefix)::-moz-selection,
|
||||
pre > code.diff-highlight .token.token.inserted:not(.prefix) *::-moz-selection {
|
||||
background-color: hsla(135, 73%, 55%, 0.25);
|
||||
}
|
||||
|
||||
pre.diff-highlight>code .token.token.inserted:not(.prefix)::selection,
|
||||
pre.diff-highlight>code .token.token.inserted:not(.prefix) *::selection,
|
||||
pre>code.diff-highlight .token.token.inserted:not(.prefix)::selection,
|
||||
pre>code.diff-highlight .token.token.inserted:not(.prefix) *::selection {
|
||||
pre.diff-highlight > code .token.token.inserted:not(.prefix)::selection,
|
||||
pre.diff-highlight > code .token.token.inserted:not(.prefix) *::selection,
|
||||
pre > code.diff-highlight .token.token.inserted:not(.prefix)::selection,
|
||||
pre > code.diff-highlight .token.token.inserted:not(.prefix) *::selection {
|
||||
background-color: hsla(135, 73%, 55%, 0.25);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,26 +1,26 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"incremental": true,
|
||||
"target": "es5",
|
||||
"jsx": "preserve",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
},
|
||||
"resolveJsonModule": true,
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"skipLibCheck": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
|
|
|
|||
30
package.json
30
package.json
|
|
@ -1,7 +1,19 @@
|
|||
{
|
||||
"name": "iconoir",
|
||||
"type": "module",
|
||||
"version": "7.10.1",
|
||||
"packageManager": "pnpm@9.15.0",
|
||||
"description": "Iconoir - The biggest open source icon library with tons of free icons.",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/iconoir"
|
||||
},
|
||||
"homepage": "https://iconoir.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/iconoir-icons/iconoir.git"
|
||||
},
|
||||
"keywords": [
|
||||
"free",
|
||||
"icons",
|
||||
|
|
@ -9,17 +21,6 @@
|
|||
"svg",
|
||||
"css"
|
||||
],
|
||||
"homepage": "https://iconoir.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/iconoir-icons/iconoir.git"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/iconoir"
|
||||
},
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./icons/*.svg": "./icons/regular/*.svg",
|
||||
"./icons/*-solid.svg": "./icons/solid/*.svg",
|
||||
|
|
@ -38,6 +39,7 @@
|
|||
"prepublish-all": "node ./bin/prepublish.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^3.12.0",
|
||||
"@atomist/yaml-updater": "^1.0.2",
|
||||
"@svgr/babel-plugin-remove-jsx-attribute": "^8.0.0",
|
||||
"@svgr/core": "^8.1.0",
|
||||
|
|
@ -45,8 +47,7 @@
|
|||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"esbuild": "^0.24.0",
|
||||
"eslint": "^9.17.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"eslint-plugin-format": "^0.1.3",
|
||||
"hast-util-from-html": "^2.0.3",
|
||||
"hast-util-to-html": "^9.0.4",
|
||||
"listr2": "^8.2.5",
|
||||
|
|
@ -57,6 +58,5 @@
|
|||
"typescript": "^5.7.2",
|
||||
"vite": "^6.0.3",
|
||||
"vite-plugin-dts": "^4.3.0"
|
||||
},
|
||||
"packageManager": "pnpm@9.15.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
name: example
|
||||
description: A new Flutter project.
|
||||
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
publish_to: none # Remove this line if you wish to publish to pub.dev
|
||||
version: 1.0.0+1
|
||||
|
||||
environment:
|
||||
|
|
@ -10,7 +10,7 @@ dependencies:
|
|||
flutter:
|
||||
sdk: flutter
|
||||
iconoir_flutter:
|
||||
path: ../
|
||||
path: ../
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
|
|||
|
|
@ -17,17 +17,17 @@
|
|||
## Usage
|
||||
|
||||
```javascript
|
||||
import { Iconoir } from 'iconoir-react-native';
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { Iconoir } from 'iconoir-react-native';
|
||||
|
||||
const App = () => {
|
||||
function App() {
|
||||
return (
|
||||
<View>
|
||||
<Iconoir />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default App;
|
||||
```
|
||||
|
|
@ -35,7 +35,7 @@ export default App;
|
|||
Icons can take any `react-native-svg` properties as optional props, e.g.
|
||||
|
||||
```javascript
|
||||
<Iconoir color="red" height={36} width={36} />
|
||||
<Iconoir color="red" height={36} width={36} />;
|
||||
```
|
||||
|
||||
Default values for the most common props are given below:
|
||||
|
|
@ -53,7 +53,7 @@ Default values for the most common props are given below:
|
|||
Tired of specifying the same props for every single icon, every time you use them? So were we. Use IconoirProvider to set the default icon props for everything inside IconoirProvider.
|
||||
|
||||
```tsx
|
||||
import { IconoirProvider, Check } from 'iconoir-react-native';
|
||||
import { Check, IconoirProvider } from 'iconoir-react-native';
|
||||
|
||||
return (
|
||||
<IconoirProvider
|
||||
|
|
|
|||
|
|
@ -2,23 +2,23 @@
|
|||
"name": "iconoir-react-native",
|
||||
"version": "7.10.1",
|
||||
"description": "React Native library for Iconoir, the biggest open source icon library with tons of free icons.",
|
||||
"keywords": [
|
||||
"icons",
|
||||
"svg",
|
||||
"library",
|
||||
"react-native"
|
||||
],
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/iconoir"
|
||||
},
|
||||
"homepage": "https://iconoir.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/iconoir-icons/iconoir.git",
|
||||
"directory": "packages/iconoir-react-native"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/iconoir"
|
||||
},
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"icons",
|
||||
"svg",
|
||||
"library",
|
||||
"react-native"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"exports": {
|
||||
".": {
|
||||
|
|
@ -40,12 +40,12 @@
|
|||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.45",
|
||||
"react-native-svg": "^13.14.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.6 || ^17 || ^18",
|
||||
"react-native": ">=0.50.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.45",
|
||||
"react-native-svg": "^13.14.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,12 +17,12 @@
|
|||
## Usage
|
||||
|
||||
```javascript
|
||||
import React from 'react';
|
||||
import { Iconoir } from 'iconoir-react';
|
||||
import React from 'react';
|
||||
|
||||
const App = () => {
|
||||
function App() {
|
||||
return <Iconoir />;
|
||||
};
|
||||
}
|
||||
|
||||
export default App;
|
||||
```
|
||||
|
|
@ -30,7 +30,7 @@ export default App;
|
|||
Icons can take any standard SVG properties as optional props, e.g.
|
||||
|
||||
```javascript
|
||||
<Iconoir color="red" height={36} width={36} />
|
||||
<Iconoir color="red" height={36} width={36} />;
|
||||
```
|
||||
|
||||
Default values for the most common props are given below:
|
||||
|
|
@ -47,7 +47,7 @@ Default values for the most common props are given below:
|
|||
Tired of specifying the same props for every single icon, every time you use them? So were we. Use IconoirProvider to set the default icon props for everything inside IconoirProvider.
|
||||
|
||||
```tsx
|
||||
import { IconoirProvider, Check } from 'iconoir-react';
|
||||
import { Check, IconoirProvider } from 'iconoir-react';
|
||||
|
||||
return (
|
||||
<IconoirProvider
|
||||
|
|
|
|||
|
|
@ -2,23 +2,23 @@
|
|||
"name": "iconoir-react",
|
||||
"version": "7.10.1",
|
||||
"description": "React library for Iconoir, the biggest open source icon library with tons of free icons.",
|
||||
"keywords": [
|
||||
"icons",
|
||||
"svg",
|
||||
"library",
|
||||
"react"
|
||||
],
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/iconoir"
|
||||
},
|
||||
"homepage": "https://iconoir.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/iconoir-icons/iconoir.git",
|
||||
"directory": "packages/iconoir-react"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/iconoir"
|
||||
},
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"icons",
|
||||
"svg",
|
||||
"library",
|
||||
"react"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"exports": {
|
||||
".": {
|
||||
|
|
@ -40,10 +40,10 @@
|
|||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.45"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.6 || ^17 || ^18"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.45"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
### Vue 3
|
||||
|
||||
```js
|
||||
```html
|
||||
<script setup>
|
||||
import { Iconoir } from '@iconoir/vue';
|
||||
</script>
|
||||
|
|
@ -30,7 +30,7 @@
|
|||
|
||||
### Vue 2
|
||||
|
||||
```js
|
||||
```html
|
||||
<script>
|
||||
import { Iconoir } from '@iconoir/vue';
|
||||
|
||||
|
|
@ -50,7 +50,7 @@
|
|||
|
||||
Icons can take any standard SVG properties as optional props, e.g.
|
||||
|
||||
```jsx
|
||||
```html
|
||||
<Iconoir color="red" height="36" width="36" />
|
||||
```
|
||||
|
||||
|
|
@ -67,9 +67,9 @@ Default values for the most common props are given below:
|
|||
|
||||
Tired of specifying the same props for every single icon, every time you use them? So were we. Use IconoirProvider to set the default icon props for everything inside IconoirProvider.
|
||||
|
||||
```js
|
||||
```html
|
||||
<script setup>
|
||||
import { IconoirProvider, Check } from '@iconoir/vue';
|
||||
import { IconoirProvider, Check } from '@iconoir/vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
|||
|
|
@ -2,23 +2,23 @@
|
|||
"name": "@iconoir/vue",
|
||||
"version": "7.10.1",
|
||||
"description": "Vue library for Iconoir, the biggest open source icon library with tons of free icons.",
|
||||
"keywords": [
|
||||
"icons",
|
||||
"svg",
|
||||
"library",
|
||||
"vue"
|
||||
],
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/iconoir"
|
||||
},
|
||||
"homepage": "https://iconoir.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/iconoir-icons/iconoir.git",
|
||||
"directory": "packages/iconoir-vue"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/iconoir"
|
||||
},
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"icons",
|
||||
"svg",
|
||||
"library",
|
||||
"vue"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"exports": {
|
||||
".": {
|
||||
|
|
@ -39,12 +39,6 @@
|
|||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"dependencies": {
|
||||
"vue-demi": "^0.14.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vue": "^3.3.12"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vue/composition-api": ">=1.0.0-rc.1",
|
||||
"vue": "^2.6.11 || >=3.0.0"
|
||||
|
|
@ -53,5 +47,11 @@
|
|||
"@vue/composition-api": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"vue-demi": "^0.14.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vue": "^3.3.12"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { InjectionKey, SVGAttributes } from 'vue-demi';
|
||||
const providerKey = Symbol() as InjectionKey<SVGAttributes>;
|
||||
|
||||
const providerKey = Symbol('IconoirProvider') as InjectionKey<SVGAttributes>;
|
||||
export default providerKey;
|
||||
|
|
|
|||
|
|
@ -1,28 +1,28 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"compilerOptions": {
|
||||
"target": "es2018",
|
||||
"module": "esnext",
|
||||
"strict": true,
|
||||
"jsx": "preserve",
|
||||
"moduleResolution": "node",
|
||||
"rootDir": "src",
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"baseUrl": ".",
|
||||
"declarationMap": false,
|
||||
// "isolatedModules": true,
|
||||
"types": [
|
||||
"vite/client"
|
||||
],
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom",
|
||||
"dom.iterable"
|
||||
],
|
||||
"baseUrl": ".",
|
||||
"rootDir": "src",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
// "isolatedModules": true,
|
||||
"types": [
|
||||
"vite/client"
|
||||
],
|
||||
"strict": true,
|
||||
"declarationMap": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.vue",
|
||||
],
|
||||
"src/**/*.vue"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
6262
pnpm-lock.yaml
generated
6262
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue