Refactor Lint/Format Setup (#499)

This commit is contained in:
Pascal Jufer 2024-12-17 00:25:26 +01:00 committed by GitHub
commit 2bf8087c26
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
96 changed files with 3860 additions and 6768 deletions

View file

@ -1,3 +0,0 @@
dist/
/examples/react-native/**/*.js
/iconoir.com/out/

View file

@ -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'],
},
};

View file

View file

@ -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

View file

@ -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
View 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 }}

View file

@ -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 }}

View file

@ -1 +0,0 @@
dist/

View file

@ -1,4 +0,0 @@
{
"singleQuote": true,
"quoteProps": "consistent"
}

50
.vscode/settings.json vendored Normal file
View 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"
]
}

View file

@ -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

View file

@ -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:

View file

@ -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)) {

View file

@ -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}";`;
}

View file

@ -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',

View file

@ -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}');}`;

View file

@ -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;

View file

@ -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,

View file

@ -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;

View file

@ -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,

View file

@ -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(

View file

@ -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;

View file

@ -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;

View file

@ -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
View 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',
},
});

View file

@ -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"]

View file

@ -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",

View file

@ -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__/*"]
}

View file

@ -1,5 +1,4 @@
{
"files": [],
"references": [
{
"path": "./tsconfig.node.json"
@ -7,5 +6,6 @@
{
"path": "./tsconfig.app.json"
}
]
],
"files": []
}

View file

@ -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"]
}
]
}

View file

@ -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)),
},
},
});

View file

@ -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"
}
}
]
}

View file

@ -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} />;
}

View file

@ -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',

View file

@ -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>
</>
);
}

View file

@ -50,7 +50,7 @@ export const LargeButton = styled(ResetButton)`
}
`;
export const Button = styled(LargeButton)`
const Button = styled(LargeButton)`
&&&& {
height: 40px;
font-size: 13px;

View file

@ -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>
);
}

View file

@ -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>
);
}

View file

@ -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);
}
}
`;

View file

@ -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;
}
}
`;

View file

@ -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);
`;

View file

@ -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}>&times;</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}>&times;</CloseButton>
</PopupContent>
)}
</>
);
}

View file

@ -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;
}
`;

View file

@ -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) {

View file

@ -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 &copy;2020-{year} by individual Iconoir
contributors. Content available under a{' '}
<a href={LICENSE_LINK} target={'_blank'} rel={'nofollow noreferrer'}>
Parts of this content are &copy;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);
}
`;

View file

@ -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);}

View file

@ -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 &mdash; 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 &mdash; 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>
);
}

View file

@ -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} {
}
`;

View file

@ -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>;
}

View file

@ -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>
);
}

View file

@ -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>
);
}

View file

@ -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>;
}

View file

@ -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 &apos;{searchTerm}&apos;
</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 &apos;
{searchTerm}
&apos;
</Title>
<Text18 style={{ color: 'var(--black-60)' }}>
If you can&apos;t find the icon, you can make a
<br />
<a href={SUGGEST_ICON_LINK} target="_blank" rel="noreferrer">
suggestion on GitHub.
</a>
</Text18>
</Container>
);
}

View file

@ -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;
}
`;

View file

@ -19,7 +19,7 @@ const ResetInput = styled.input`
}
`;
export const Input = styled(ResetInput)`
const Input = styled(ResetInput)`
&&& {
min-height: 35px;
background: var(--white);

View file

@ -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>
);
}

View file

@ -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 (

View file

@ -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>
);
}

View file

@ -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&apos;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&apos;s the tiny details that determine the degree of delight your
customers experience from your product. Adopting Iconoir icons
will easily boost your app&apos;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>
</>
);
}

View file

@ -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;
`;

View file

@ -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);

View file

@ -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>
);
}

View file

@ -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 : ''
}`;

View file

@ -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;
}
`;

View file

@ -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>
);
}

View file

@ -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>
);
}

View file

@ -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>
);
}

View file

@ -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>

View file

@ -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>
);
}

View file

@ -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(

View file

@ -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',

View file

@ -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());

View file

@ -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,
});
}

View file

@ -1,3 +1,4 @@
import process from 'node:process';
import { Octokit } from '@octokit/rest';
export const octokit = new Octokit({

View file

@ -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(() => {

View file

@ -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.

View file

@ -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",

View file

@ -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"

View file

@ -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],
},
});

View file

@ -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],
},
}),
}),

View file

@ -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 theyre growing fast!'
}
/>
<Stat
value={'100%'}
description={
'free icons. Iconoir is open source and were 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 theyre growing fast!"
/>
<Stat
value="100%"
description="free icons. Iconoir is open source and were 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: {

View file

@ -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&apos;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&apos;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&apos;s dynamic capabilities. Whether you&apos;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&apos;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() {

View file

@ -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>
Were 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>
Were 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() {

View file

@ -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);
}

View file

@ -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"]

View file

@ -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"
}
}

View file

@ -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:

View file

@ -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

View file

@ -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"
}
}
}

View file

@ -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

View file

@ -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"
}
}
}

View file

@ -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>

View file

@ -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"
}
}
}

View file

@ -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;

View file

@ -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

File diff suppressed because it is too large Load diff