Split variants (#368)

This commit is contained in:
Pascal Jufer 2023-10-29 00:33:17 +02:00 committed by GitHub
parent 9ea5993377
commit d73b7282dd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
1606 changed files with 3324 additions and 2070 deletions

1
.eslintignore Normal file
View file

@ -0,0 +1 @@
dist/

View file

@ -10,6 +10,38 @@ module.exports = {
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

@ -18,8 +18,8 @@ jobs:
- name: Setup
uses: ./.github/actions/setup
- name: Build meta-data and css
run: pnpm run build meta-data css
- name: Build CSS
run: pnpm run build css
- name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v4

11
.gitignore vendored
View file

@ -5,11 +5,6 @@ dist/
packages/iconoir-flutter/lib/
packages/iconoir-react/src/
!packages/iconoir-react/src/IconoirContext.tsx
!packages/iconoir-react/src/server/IconoirContext.tsx
packages/iconoir-react-native/src/
!packages/iconoir-react-native/src/IconoirContext.tsx
packages/iconoir-vue/src/
packages/iconoir-vue/src/*
!packages/iconoir-vue/src/IconoirProvider.vue
!packages/iconoir-vue/src/providerKey.ts

1
.prettierignore Normal file
View file

@ -0,0 +1 @@
dist/

View file

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

View file

@ -1,663 +0,0 @@
import { execa } from 'execa';
import { generateTemplateFilesBatch } from 'generate-template-files';
import { Listr } from 'listr2';
import { existsSync, promises as fs, readFileSync } from 'node:fs';
import os from 'node:os';
import path, { basename, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { flutterIncompatibleNames, incompatibleNames } from '../constants.js';
import { buildVueIcons } from './buildVue.js';
// Paths
const __dirname = dirname(fileURLToPath(import.meta.url));
const rootDir = path.join(__dirname, '..');
const iconoirIconsDir = path.join(rootDir, 'icons');
const ignoreCleanFilenames = ['IconoirContext.tsx', 'server'];
// Targets for building icons
const targets = {
'meta-data': { path: 'meta-data.json' },
css: { path: 'css/iconoir.css' },
'iconoir-flutter': { flutter: true, path: 'packages/iconoir-flutter' },
'iconoir-react': { react: true, path: 'packages/iconoir-react' },
'iconoir-react-native': {
react: true,
path: 'packages/iconoir-react-native',
},
'iconoir-vue': {
vue: true,
path: 'packages/iconoir-vue',
},
};
// Get targets from command line arguments
// (build all targets if no arguments)
const args = process.argv.slice(2);
const cliTargets = [];
args.forEach((target) => {
if (target in targets) {
cliTargets.push(target);
} else {
console.error(`Target '${target}' doesn't exist!\n\nPossible targets are:`);
for (const [targetName] of Object.entries(targets)) {
console.log(`- ${targetName}`);
}
process.exit(1);
}
});
// Build tasks
const tasks = new Listr(
[
{
title: 'Fetching icons',
task: async (ctx) => {
const iconFiles = await fs.readdir(iconoirIconsDir);
ctx.iconoirIconsFiles = iconFiles.filter((file) =>
file.endsWith('.svg'),
);
},
},
{
title: 'Building targets',
skip: (ctx) => !ctx.iconoirIconsFiles,
task: (_, task) =>
task.newListr(
[
{
title: 'Building meta-data file',
enabled: () =>
cliTargets.length === 0 || cliTargets.includes('meta-data'),
task: async (ctx) => {
await fs.writeFile(
path.join(rootDir, targets['meta-data'].path),
JSON.stringify({ icons: ctx.iconoirIconsFiles }),
);
},
},
{
title: 'Building CSS file',
enabled: () =>
cliTargets.length === 0 || cliTargets.includes('css'),
task: async (ctx) => {
const content = [
(
await fs.readFile(
path.join(__dirname, 'header.css'),
'utf8',
)
).replace('[YEAR]', new Date().getFullYear()),
];
ctx.iconoirIconsFiles.forEach((file) => {
const fileContents = readFileSync(
path.join(__dirname, '../icons/', file),
)
.toString()
.replace(/\n/g, '')
.replace(/(width|height)="[0-9]+px"/g, '')
.replace(/[ ]+/g, ' ');
content.push(
`.iconoir-${
path.parse(file).name
}::before{mask-image:url('data:image/svg+xml;charset=utf-8,${fileContents}');-webkit-mask-image:url('data:image/svg+xml;charset=utf-8,${fileContents}');}`,
);
});
await fs.writeFile(
path.join(rootDir, targets.css.path),
content,
);
},
},
{
title: 'Building React libraries',
enabled: () =>
cliTargets.length === 0 ||
cliTargets.filter((cliTarget) => targets[cliTarget]?.react)
.length > 0,
task: (_, task) =>
task.newListr(
[
{
title: 'Creating temporary directory',
task: async (ctx) => {
try {
ctx.tmpDir = await fs.mkdtemp(
path.join(os.tmpdir(), 'iconoir-'),
);
} catch (err) {
ctx.skip = true;
throw new Error(err.message);
}
},
},
{
title:
'Copying icon files to temporary directory, while renaming icons with incompatible names',
skip: (ctx) => ctx.skip,
task: async (ctx) => {
try {
const promises = ctx.iconoirIconsFiles.map((file) => {
const srcFilePath = path.join(
iconoirIconsDir,
file,
);
const iconName = file.split('.')[0];
const dstFileName =
iconName in incompatibleNames
? incompatibleNames[iconName]
: iconName;
const dstFilePath = path.join(
ctx.tmpDir,
`${dstFileName}.svg`,
);
return fs.copyFile(srcFilePath, dstFilePath);
});
return Promise.all(promises).catch((err) => {
ctx.skip = true;
throw new Error(err.message);
});
} catch (err) {
ctx.skip = true;
throw new Error(err.message);
}
},
},
{
skip: (ctx) => ctx.skip,
task: (_, task) => {
const targetsToBuild =
cliTargets.length > 0
? cliTargets.filter(
(cliTarget) => targets[cliTarget]?.react,
)
: Object.keys(targets).filter(
(target) => targets[target].react,
);
const tasks = targetsToBuild.map((target) => {
const builtIconsDir = path.join(
rootDir,
targets[target].path,
'src',
);
return {
title: `Building ${target}`,
task: (_, task) =>
task.newListr(
[
{
title: 'Cleaning target directory',
task: async (ctx) => {
try {
const files =
await fs.readdir(builtIconsDir);
const serverFiles = existsSync(
path.join(builtIconsDir, 'server'),
)
? (
await fs.readdir(
path.join(
builtIconsDir,
'server',
),
)
).map((file) => `server/${file}`)
: [];
const promises = [
...files,
...serverFiles,
]
.filter(
(file) =>
!ignoreCleanFilenames.includes(
path.basename(file),
),
)
.map((file) => {
return fs.unlink(
path.join(builtIconsDir, file),
);
});
return Promise.all(promises).catch(
(err) => {
ctx[target] = { skip: true };
throw new Error(err.message);
},
);
} catch (err) {
ctx[target] = { skip: true };
throw new Error(err.message);
}
},
},
{
title: 'Building icon files',
skip: (ctx) => ctx[target]?.skip,
task: async (ctx) => {
try {
await execa(
'svgr',
[
'--config-file',
path.join(
targets[target].path,
'.svgrrc.json',
),
'--out-dir',
builtIconsDir,
'--template',
'bin/templates/icon-template.cjs',
'--index-template',
'bin/templates/index-template.cjs',
'--',
ctx.tmpDir,
],
{ preferLocal: true },
);
} catch (err) {
throw new Error(err.message);
}
},
},
...(target === 'iconoir-react'
? [
{
title:
'Building icon files (server components)',
skip: (ctx) => ctx[target]?.skip,
task: async (ctx) => {
try {
await execa(
'svgr',
[
'--config-file',
path.join(
targets[target].path,
'.svgrrc.json',
),
'--out-dir',
path.join(
builtIconsDir,
'server',
),
'--template',
'bin/templates/icon-template-server-component.cjs',
'--index-template',
'bin/templates/index-template.cjs',
'--',
ctx.tmpDir,
],
{ preferLocal: true },
);
} catch (err) {
throw new Error(err.message);
}
},
},
]
: []),
],
{ concurrent: false, exitOnError: false },
),
};
});
return task.newListr(tasks, {
concurrent: true,
rendererOptions: { collapseSubtasks: false },
});
},
},
],
{ concurrent: false },
),
},
{
title: 'Building Vue library',
enabled: () =>
cliTargets.length === 0 ||
cliTargets.filter((cliTarget) => targets[cliTarget]?.vue)
.length > 0,
task: (_, task) =>
task.newListr(
[
{
title: 'Creating temporary directory',
task: async (ctx) => {
try {
ctx.tmpDir = await fs.mkdtemp(
path.join(os.tmpdir(), 'iconoir-'),
);
} catch (err) {
ctx.skip = true;
throw new Error(err.message);
}
},
},
{
title:
'Copying icon files to temporary directory, while renaming icons with incompatible names',
skip: (ctx) => ctx.skip,
task: async (ctx) => {
try {
const promises = ctx.iconoirIconsFiles.map((file) => {
const srcFilePath = path.join(
iconoirIconsDir,
file,
);
const iconName = file.split('.')[0];
const dstFileName =
iconName in incompatibleNames
? incompatibleNames[iconName]
: iconName;
const dstFilePath = path.join(
ctx.tmpDir,
`${dstFileName}.svg`,
);
return fs.copyFile(srcFilePath, dstFilePath);
});
return Promise.all(promises).catch((err) => {
ctx.skip = true;
throw new Error(err.message);
});
} catch (err) {
ctx.skip = true;
throw new Error(err.message);
}
},
},
{
skip: (ctx) => ctx.skip,
task: (_, task) => {
const targetsToBuild =
cliTargets.length > 0
? cliTargets.filter(
(cliTarget) => targets[cliTarget]?.vue,
)
: Object.keys(targets).filter(
(target) => targets[target].vue,
);
const tasks = targetsToBuild.map((target) => {
const builtIconsDir = path.join(
rootDir,
targets[target].path,
'src',
);
return {
title: `Building ${target}`,
task: (_, task) =>
task.newListr(
[
{
title: 'Create target directory',
task: async (ctx) => {
try {
await fs.mkdir(builtIconsDir, {
recursive: true,
});
} catch (err) {
ctx[target] = { skip: true };
throw new Error(err.message);
}
},
},
{
title: 'Building icon files',
skip: (ctx) => ctx[target]?.skip,
task: async (ctx) => {
try {
await buildVueIcons(ctx.tmpDir, {
outDir: builtIconsDir,
});
} catch (err) {
throw new Error(err.message);
}
},
},
],
{ concurrent: false, exitOnError: false },
),
};
});
return task.newListr(tasks, {
concurrent: true,
rendererOptions: { collapseSubtasks: false },
});
},
},
],
{ concurrent: false },
),
},
{
title: 'Building Flutter libraries',
enabled: () =>
cliTargets.length === 0 ||
cliTargets.filter((cliTarget) => targets[cliTarget]?.flutter)
.length > 0,
task: (_, task) =>
task.newListr(
[
{
title: 'Creating temporary directory',
task: async (ctx) => {
try {
ctx.flutterTmpDir = await fs.mkdtemp(
path.join(os.tmpdir(), 'iconoir-'),
);
} catch (err) {
ctx.skip = true;
throw new Error(err.message);
}
},
},
{
title:
'Copying icon files to temporary directory, while renaming icons with incompatible names',
skip: (ctx) => ctx.skip,
task: async (ctx) => {
try {
const promises = ctx.iconoirIconsFiles.map((file) => {
const srcFilePath = path.join(
iconoirIconsDir,
file,
);
const iconName = file.split('.')[0];
const dstFileName =
iconName in flutterIncompatibleNames
? flutterIncompatibleNames[iconName]
: iconName;
const dstFilePath = path.join(
ctx.flutterTmpDir,
`${dstFileName}.svg`,
);
ctx.dstFilePaths = [
...(ctx.dstFilePaths ?? []),
dstFilePath,
];
return fs.copyFile(srcFilePath, dstFilePath);
});
return Promise.all(promises).catch((err) => {
ctx.skip = true;
throw new Error(err.message);
});
} catch (err) {
ctx.skip = true;
throw new Error(err.message);
}
},
},
{
skip: (ctx) => ctx.skip,
task: (_, task) => {
const targetsToBuild =
cliTargets.length > 0
? cliTargets.filter(
(cliTarget) => targets[cliTarget]?.flutter,
)
: Object.keys(targets).filter(
(target) => targets[target].flutter,
);
const tasks = targetsToBuild.map((target) => {
const builtIconsDir = path.join(
rootDir,
targets[target].path,
'lib',
);
return {
title: `Building ${target}`,
task: (_, task) =>
task.newListr(
[
{
title: 'Create target directory',
task: async (ctx) => {
try {
await fs.mkdir(builtIconsDir, {
recursive: true,
});
} catch (err) {
ctx[target] = { skip: true };
throw new Error(err.message);
}
},
},
{
title: 'Create entry file',
task: async () => {
await fs.writeFile(
path.join(
builtIconsDir,
'iconoir_flutter.dart',
),
'library iconoir_flutter;\n\n',
);
},
},
{
title: 'Building icon files',
skip: (ctx) => ctx[target]?.skip,
task: async (ctx) => {
const finalFileNames = [];
try {
await Promise.all(
ctx.dstFilePaths.map(async (file) => {
const svgfilename =
path.parse(file).name;
// Prefix with Svg if icon name starts with a number
const iconname = `${
/^\d/.test(svgfilename)
? 'Svg'
: ''
}${svgfilename}`;
const svgfilecontent = (
await fs.readFile(file)
).toString();
await generateTemplateFilesBatch([
{
option:
'Create Icon Flutter Widget',
entry: {
folderPath:
'./bin/templates/__svgfilename__.dart',
},
dynamicReplacers: [
{
slot: '__icon__',
slotValue: iconname,
},
{
slot: '__svgfilecontent__',
slotValue: svgfilecontent,
},
{
slot: '__svgfilename__',
slotValue: svgfilename,
},
],
output: {
path: './packages/iconoir-flutter/lib/__svgfilename__(snakeCase).dart',
pathAndFileNameDefaultCase:
'(snakeCase)',
},
onComplete(results) {
finalFileNames.push(
results.output.path,
);
},
},
]);
}),
);
finalFileNames.sort();
await fs.appendFile(
path.join(
builtIconsDir,
'iconoir_flutter.dart',
),
finalFileNames
.map(
(fileName) =>
`export './${basename(
fileName,
)}';`,
)
.join('\n'),
);
} catch (err) {
throw new Error(err.message);
}
},
},
],
{ concurrent: false, exitOnError: false },
),
};
});
return task.newListr(tasks, {
concurrent: true,
rendererOptions: {
collapseSubtasks: false,
},
});
},
},
],
{ concurrent: false },
),
},
],
{ concurrent: true },
),
},
{
title: 'Removing React temporary directory',
skip: (ctx) => !ctx.tmpDir,
task: async (ctx) => {
await fs.rm(ctx.tmpDir, { recursive: true });
},
},
{
title: 'Removing Flutter temporary directory',
skip: (ctx) => !ctx.flutterTmpDir,
task: async (ctx) => {
await fs.rm(ctx.flutterTmpDir, { recursive: true });
},
},
],
{
concurrent: false,
exitOnError: false,
rendererOptions: {
collapseSubtasks: false,
collapseErrors: false,
},
},
);
await tasks.run();

133
bin/build/index.js Normal file
View file

@ -0,0 +1,133 @@
import { Listr } from 'listr2';
import fs from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { pascalCase, snakeCase } from 'scule';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const rootDir = path.join(__dirname, '..', '..');
const iconsDir = path.join(rootDir, 'icons');
const iconsVariants = ['regular', 'solid'];
const defaultVariant = iconsVariants[0];
const targets = {
'css': {
title: 'CSS files',
path: 'css',
},
'flutter': {
title: 'Flutter library',
path: 'packages/iconoir-flutter',
},
'react': {
title: 'React library',
path: 'packages/iconoir-react',
},
'react-native': {
title: 'React Native library',
target: 'react',
native: true,
path: 'packages/iconoir-react-native',
},
'vue': {
title: 'Vue library',
path: 'packages/iconoir-vue',
},
};
const tasks = new Listr(
[
{
title: 'Fetching icons',
task: async (ctx) => {
ctx.icons = {};
const iconsVariantsDirs = Object.fromEntries(
iconsVariants.map((variant) => [
variant,
path.join(iconsDir, variant),
]),
);
for (const [variant, dir] of Object.entries(iconsVariantsDirs)) {
const files = await fs.readdir(dir);
const icons = files
.filter((file) => file.endsWith('.svg'))
.map((file) => {
const name = path.parse(file).name;
const nameVariant = `${name}-${variant}`;
return {
name,
nameVariant,
pascalName: pascalCase(name),
pascalNameVariant: pascalCase(nameVariant),
snakeName: snakeCase(name),
snakeNameVariant: snakeCase(nameVariant),
path: path.join(dir, file),
};
});
ctx.icons[variant] = icons;
}
ctx.global = { defaultVariant };
},
},
{
title: 'Building targets',
task: (_, task) =>
task.newListr(
Object.entries(targets).map(([targetName, targetConfig]) => ({
title: targetConfig.title,
enabled: () =>
cliTargets.length === 0 || cliTargets.includes(targetName),
task: async (ctx) => {
const { default: task } = await import(
`./targets/${targetConfig.target || targetName}/index.js`
);
targetConfig.path = path.join(
rootDir,
...targetConfig.path.split(path.posix.sep),
);
return task(ctx, targetConfig);
},
})),
{ concurrent: true, exitOnError: false },
),
},
],
{
rendererOptions: {
collapseSubtasks: false,
collapseErrors: false,
},
},
);
const cliTargets = [];
// Get targets from command line arguments
// (build all targets if no arguments given)
for (const arg of process.argv.slice(2)) {
if (arg in targets) {
cliTargets.push(arg);
} else {
console.error(
`Target '${arg}' doesn't exist!\n\nPossible targets are:\n${Object.keys(
targets,
)
.map((name) => `- ${name}`)
.join('\n')}`,
);
process.exit(1);
}
}
await tasks.run();

View file

@ -0,0 +1,19 @@
import path from 'node:path';
export function generateImport(name, from) {
if (Array.isArray(name)) name = `{${name.toString()}}`;
return `import ${name} from "${from}";`;
}
export function generateExport(name, from) {
const base = `export {${name.toString()}}`;
return from ? `${base} from "${from}";` : `${base};`;
}
export function toImportPath(input) {
input = input.split(path.sep).join(path.posix.sep);
return input.charAt(0) !== '.' ? `./${input}` : input;
}

53
bin/build/lib/ts.js Normal file
View file

@ -0,0 +1,53 @@
import ts from 'typescript';
export function getDts(path, content, options) {
let output;
const host = ts.createCompilerHost(options);
const _readFile = host.readFile;
host.readFile = (filename) => {
if (filename === path) return content;
return _readFile(filename);
};
const dtsFilename = path.replace(/\.(m|c)?(ts|js)x?$/, '.d.$1ts');
host.writeFile = (filename, contents) => {
if (filename === dtsFilename) output = contents;
};
const program = ts.createProgram([path], options, host);
const emitResult = program.emit();
const allDiagnostics = ts
.getPreEmitDiagnostics(program)
.concat(emitResult.diagnostics);
const results = allDiagnostics.map((diagnostic) => {
if (diagnostic.file) {
const { line, character } = ts.getLineAndCharacterOfPosition(
diagnostic.file,
diagnostic.start,
);
const message = ts.flattenDiagnosticMessageText(
diagnostic.messageText,
'\n',
);
return `${diagnostic.file.fileName} (${line + 1},${
character + 1
}): ${message}`;
} else {
return ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
}
});
if (results.length > 0) {
throw new Error(results);
}
return output;
}

View file

@ -1,13 +1,13 @@
/*!
* Iconoir
* Copyright (c) [YEAR] Luca Burgio - https://iconoir.com
* License - https://github.com/lucaburgio/iconoir/blob/main/LICENSE (Code: MIT License)
* License - https://github.com/iconoir-icons/iconoir/blob/main/LICENSE (Code: MIT License)
* CSS file created by Till Esser (@Wiwaltill) and automated by Pascal Jufer (@paescuj)
*/
*[class^="iconoir-"]::before,
*[class*=" iconoir-"]::before {
content: " ";
*[class^='iconoir-']::before,
*[class*=' iconoir-']::before {
content: ' ';
display: block;
background: currentColor;
mask-size: cover;
@ -15,7 +15,7 @@
width: 1em;
height: 1em;
}
*[class^="iconoir-"],
*[class*=" iconoir-"] {
*[class^='iconoir-'],
*[class*=' iconoir-'] {
display: inline-block;
}

View file

@ -0,0 +1,51 @@
import fs from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
export default async (ctx, target) => {
const headerFile = await fs.readFile(
path.join(__dirname, 'header.css'),
'utf8',
);
const header = headerFile.replace('[YEAR]', new Date().getFullYear());
const mainCssContent = [header];
for (const [variant, icons] of Object.entries(ctx.icons)) {
const variantCssContent = [header];
const cssTarget = (icon, suffixed) => {
const iconName =
suffixed && variant !== ctx.global.defaultVariant
? icon.nameVariant
: icon.name;
return `.iconoir-${iconName}::before`;
};
for (const icon of icons) {
const fileContent = await fs.readFile(icon.path, 'utf8');
const transformedContent = fileContent
.replace(/\n/g, '')
.replace(/(width|height)="[0-9]+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}');}`;
mainCssContent.push(`${cssTarget(icon)}${cssContent}`);
variantCssContent.push(`${cssTarget(icon, true)}${cssContent}`);
}
await fs.writeFile(
path.join(target.path, `iconoir-${variant}.css`),
variantCssContent,
);
}
await fs.writeFile(path.join(target.path, 'iconoir.css'), mainCssContent);
};

View file

@ -0,0 +1,47 @@
import fs from 'node:fs/promises';
import path from 'node:path';
import iconTemplate from './template.js';
export default async (ctx, target) => {
const promises = [];
const outDir = path.join(target.path, 'lib');
const entryContent = ['library iconoir_flutter;'];
for (const [variant, icons] of Object.entries(ctx.icons)) {
const variantOutDir = path.join(outDir, variant);
await fs.mkdir(variantOutDir, { recursive: true });
for (const icon of icons) {
const dartFileName = `${icon.snakeName}.dart`;
const dartPath = path.join(variant, dartFileName);
promises.push(
generateIconFile(
icon.path,
path.join(outDir, dartPath),
variant !== ctx.global.defaultVariant
? icon.pascalNameVariant
: icon.pascalName,
),
);
entryContent.push(`export './${dartPath}';`);
}
}
promises.push(
fs.writeFile(path.join(outDir, 'iconoir_flutter.dart'), entryContent),
);
return Promise.all(promises);
};
async function generateIconFile(src, dest, iconName) {
const iconContent = await fs.readFile(src, 'utf8');
const dartContent = iconTemplate(iconName, iconContent);
return fs.writeFile(dest, dartContent);
}

View file

@ -0,0 +1,25 @@
const template = (name, svg) => `
import 'package:flutter/widgets.dart' as widgets;
import 'package:flutter_svg/flutter_svg.dart';
class ${name} extends widgets.StatelessWidget {
final widgets.Color? color;
final double? width;
final double? height;
const ${name}({widgets.Key? key, this.color, this.width, this.height})
: super(key: key);
@override
widgets.Widget build(widgets.BuildContext context) => SvgPicture.string(
'''
${svg}''',
colorFilter:
color != null ? widgets.ColorFilter.mode(color!, widgets.BlendMode.srcIn) : null,
width: width,
height: height,
);
}
`;
export default template;

View file

@ -0,0 +1,248 @@
import * as svgr from '@svgr/core';
import * as esbuild from 'esbuild';
import fs from 'node:fs/promises';
import path from 'node:path';
import {
generateExport,
generateImport,
toImportPath,
} from '../../lib/import-export.js';
import { getDts } from '../../lib/ts.js';
import iconoirContextTemplate, {
exports as iconoirContextExports,
} from './resources/context-template.js';
import { getTemplate as getIconTemplate } from './resources/icon-template.js';
import { nativeSvgrOptions, svgrOptions } from './resources/svgr-options.js';
const outDir = 'dist';
const jsTargets = [
{
format: 'cjs',
module: 'commonjs',
dir: '.',
ext: 'js',
dtsExt: 'd.ts',
},
{
format: 'esm',
module: 'esnext',
dir: 'esm',
ext: 'mjs',
dtsExt: 'd.mts',
},
];
/** @type {import('esbuild').TransformOptions} */
const defaultEsbuildOptions = { target: 'es6', minify: true };
/** @type {import('typescript').CompilerOptions} */
const defaultTsOptions = {
jsx: 'react',
declaration: true,
emitDeclarationOnly: true,
target: 'es6',
strict: true,
esModuleInterop: true,
skipLibCheck: true,
};
export default async (ctx, target) => {
const localJsTargets = jsTargets.map((jsTarget) => ({
...jsTarget,
iconTemplate: getIconTemplate(target.native, jsTarget.ext),
}));
const promises = [];
const outPath = path.join(target.path, outDir);
// Preparation
// (needs to run in a separate loop, otherwise leads to uncaught exceptions in case of errors in main loop)
for (const jsTarget of localJsTargets) {
jsTarget.path = path.join(outPath, jsTarget.dir);
await fs.mkdir(jsTarget.path, { recursive: true });
const iconoirContext = iconoirContextTemplate(target.native);
jsTarget.iconoirContextPath = path.join(
jsTarget.path,
`IconoirContext.${jsTarget.ext}`,
);
await generateJs(
jsTarget.iconoirContextPath,
iconoirContext,
jsTarget.format,
);
const iconoirContextTsxPath = path.join(
jsTarget.path,
'IconoirContext.tsx',
);
const iconoirContextDtsPath = path.join(
jsTarget.path,
`IconoirContext.${jsTarget.dtsExt}`,
);
await generateDts(
iconoirContextTsxPath,
iconoirContextDtsPath,
iconoirContext,
jsTarget.module,
);
for (const variant of Object.keys(ctx.icons)) {
jsTarget.path = path.join(outPath, jsTarget.dir);
await fs.mkdir(path.join(jsTarget.path, variant), { recursive: true });
}
}
for (const jsTarget of localJsTargets) {
const mainIndex = prepareIndex(jsTarget);
for (const [variant, icons] of Object.entries(ctx.icons)) {
const variantIndex = prepareIndex(jsTarget, variant);
for (const icon of icons) {
const mainIndexComponentName =
variant === ctx.global.defaultVariant
? icon.pascalName
: icon.pascalNameVariant;
const jsPath = path.join(
jsTarget.path,
variant,
`${icon.pascalName}.${jsTarget.ext}`,
);
mainIndex.add(mainIndexComponentName, jsPath);
variantIndex.add(icon.pascalName, jsPath);
const reactComponent = getReactComponent(
icon.path,
target.native,
jsTarget.iconTemplate,
);
// Only run for first icon, type is same and can be reused for all the others
if (!jsTarget.iconDts) {
jsTarget.iconDts = true;
// Virtual input path
const tsxPath = path.join(jsTarget.path, variant, 'icon.tsx');
const dtsPath = path.join(jsTarget.path, `icon.${jsTarget.dtsExt}`);
const iconDts = generateDts(
tsxPath,
dtsPath,
reactComponent,
jsTarget.module,
);
promises.push(iconDts);
}
const iconJs = generateJs(jsPath, reactComponent, jsTarget.format);
promises.push(iconJs);
}
promises.push(variantIndex.generate());
}
promises.push(mainIndex.generate());
}
return Promise.all(promises);
};
async function getReactComponent(iconPath, native, template) {
const iconContent = await fs.readFile(iconPath, 'utf8');
const options = native ? nativeSvgrOptions : svgrOptions;
options.template = template;
return svgr.transform(iconContent, options);
}
async function generateDts(inputPath, outputPath, input, module) {
const dts = getDts(inputPath, await input, {
...defaultTsOptions,
module,
});
return fs.writeFile(outputPath, dts);
}
async function generateJs(outputPath, input, format) {
const { code } = await esbuild.transform(await input, {
...defaultEsbuildOptions,
loader: 'tsx',
format,
});
return fs.writeFile(outputPath, code);
}
function prepareIndex(jsTarget, variant) {
const outputPath = path.join(jsTarget.path, variant ?? '');
const iconoirContextPath = toImportPath(
path.relative(outputPath, jsTarget.iconoirContextPath),
);
const iconoirContext = generateExport(
iconoirContextExports,
iconoirContextPath,
);
const content = [iconoirContext];
const iconJsPath = toImportPath(
path.relative(outputPath, path.join(jsTarget.path, `icon.${jsTarget.ext}`)),
);
const iconDtsImport = generateImport('Icon', iconJsPath);
const dtsContent = [iconoirContext, iconDtsImport, 'type I = typeof Icon;'];
function add(name, iconPath) {
const iconImportPath = toImportPath(path.relative(outputPath, iconPath));
content.push(generateExport(`default as ${name}`, iconImportPath));
dtsContent.push(`export declare const ${name}: I;`);
}
function generate() {
const indexJs = generateIndexJs(
outputPath,
content,
jsTarget.format,
jsTarget.ext,
);
const indexDts = generateIndexDts(outputPath, dtsContent, jsTarget.dtsExt);
return Promise.all([indexJs, indexDts]);
}
return { add, generate };
}
async function generateIndexJs(outputDir, content, format, ext) {
const { code } = await esbuild.transform(content.join(''), {
minify: true,
format,
});
return fs.writeFile(path.join(outputDir, `index.${ext}`), code);
}
async function generateIndexDts(outputDir, content, dtsExt) {
return fs.writeFile(path.join(outputDir, `index.${dtsExt}`), content);
}

View file

@ -0,0 +1,36 @@
const template = (native) => {
const useClientDirective = native ? '' : '"use client";';
const imports = [
'import React from "react";',
...(native ? ['import type { SvgProps } from "react-native-svg";'] : []),
].join('\n');
return `
${useClientDirective}
${imports}
type IconoirContextValue = Partial<${
native ? 'SvgProps' : 'React.SVGProps<SVGSVGElement>'
}>;
export const IconoirContext = React.createContext<IconoirContextValue>({});
export interface IconoirProviderProps {
iconProps?: Partial<${
native ? `Omit<SvgProps, 'children'>` : 'React.SVGProps<SVGSVGElement>'
}>;
children: React.ReactNode;
}
export function IconoirProvider({ iconProps, children }: IconoirProviderProps) {
return (
<IconoirContext.Provider value={iconProps || {}} children={children} />
);
}
`;
};
export default template;
export const exports = ['IconoirContext', 'IconoirProvider'];

View file

@ -0,0 +1,35 @@
import { generateImport } from '../../../lib/import-export.js';
export function getTemplate(native, ext) {
return (variables, { tpl }) => {
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';
}
const useClientDirective = native ? '' : '"use client";';
const iconoirContextImport = generateImport(
['IconoirContext'],
`../IconoirContext.${ext}`,
);
return tpl`
${useClientDirective}
${variables.imports};
${iconoirContextImport};
${variables.interfaces};
const ${variables.componentName} = (${variables.props}) => {
const context = React.useContext(IconoirContext);
const props = { ...context, ...passedProps };
return ${variables.jsx};
};
${variables.exports};
`;
};
}

View file

@ -0,0 +1,46 @@
/** @type {import('@svgr/core').Config} */
export const svgrOptions = {
plugins: ['@svgr/plugin-jsx'],
icon: true,
ref: true,
typescript: true,
svgProps: {
width: '1.5em',
height: '1.5em',
color: 'currentColor',
},
jsx: {
babelConfig: {
plugins: [
[
'@svgr/babel-plugin-remove-jsx-attribute',
{
elements: ['path'],
attributes: ['strokeWidth'],
},
'remove-stroke-width',
],
],
},
},
};
/** @type {import('@svgr/core').Config} */
export const nativeSvgrOptions = {
...svgrOptions,
native: true,
jsx: {
babelConfig: {
plugins: [
...svgrOptions.jsx.babelConfig.plugins,
[
'@svgr/babel-plugin-remove-jsx-attribute',
{
elements: ['Svg'],
attributes: ['xmlns'],
},
],
],
},
},
};

View file

@ -0,0 +1,72 @@
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 { generateExport } from '../../lib/import-export.js';
import iconTemplate from './template.js';
export default async (ctx, target) => {
const promises = [];
const outDir = path.join(target.path, 'src');
const mainIndexContent = [
generateExport(`default as IconoirProvider`, `./IconoirProvider.vue`),
];
for (const [variant, icons] of Object.entries(ctx.icons)) {
const variantOutDir = path.join(outDir, variant);
await fs.mkdir(variantOutDir, { recursive: true });
const variantIndexContent = [
generateExport(`default as IconoirProvider`, `../IconoirProvider.vue`),
];
const generateIconFile = async (src, vueFileName) => {
const iconContent = await fs.readFile(src, 'utf8');
const iconAst = fromHtml(iconContent, { fragment: true });
// Bind iconProps of the provider to the svg root
iconAst.children[0].properties['v-bind'] = 'context';
const transformedIcon = toHtml(iconAst);
const componentContent = iconTemplate(transformedIcon);
const vuePath = path.join(variantOutDir, vueFileName);
return fs.writeFile(vuePath, componentContent);
};
for (const icon of icons) {
const vueFileName = `${icon.pascalName}.vue`;
promises.push(generateIconFile(icon.path, vueFileName));
const mainIndexComponentName =
variant === ctx.global.defaultVariant
? icon.pascalName
: icon.pascalNameVariant;
mainIndexContent.push(
generateExport(
`default as ${mainIndexComponentName}`,
`./${variant}/${vueFileName}`,
),
);
variantIndexContent.push(
generateExport(
`default as ${mainIndexComponentName}`,
`./${vueFileName}`,
),
);
}
promises.push(
fs.writeFile(path.join(variantOutDir, 'index.ts'), variantIndexContent),
);
}
promises.push(fs.writeFile(path.join(outDir, 'index.ts'), mainIndexContent));
return Promise.all(promises);
};

View file

@ -1,14 +1,16 @@
const template = (svg, keyFileName) => `<script lang="ts">
const template = (svg) => `<script lang="ts">
import { defineComponent, inject } from "vue-demi";
import type { SVGAttributes } from "vue-demi";
import providerKey from "./${keyFileName}";
import providerKey from "../providerKey";
export default defineComponent<SVGAttributes>(() => {
const context = inject(providerKey);
return { context };
});
</script>
<template>
${svg}
${svg}
</template>`;
module.exports = template;
export default template;

View file

@ -1,52 +0,0 @@
import { toHtml } from 'hast-util-to-html';
import fs from 'node:fs/promises';
import path from 'node:path';
import { parse } from 'svg-parser';
import componentTemplate from './templates/vue/icon-template.cjs';
import indexTemplate from './templates/vue/index-template.cjs';
import providerKeyTemplate from './templates/vue/provider-key-template.cjs';
import providerTemplate from './templates/vue/provider-template.cjs';
export async function buildVueIcons(srcDir, { outDir = './out/' }) {
const files = await fs.readdir(srcDir, 'utf8');
const providerKeyFileName = 'providerKey';
const providerKey = providerKeyTemplate();
await fs.writeFile(
path.join(outDir, providerKeyFileName + '.ts'),
providerKey,
'utf8',
);
const fileNames = [];
for (const file of files) {
const svgRaw = await fs.readFile(path.join(srcDir, file), 'utf8');
const svgAst = parse(svgRaw);
// Bind iconProps of the provider to the svg root
svgAst.children[0].properties['v-bind'] = 'context';
const svgString = toHtml(svgAst);
const component = componentTemplate(svgString, providerKeyFileName);
const pascalCaseFileName = file
.replaceAll(/[\s-]([\w\d])/g, (_, cg1) => cg1.toUpperCase())
.replace(/^\w/, (m) => m.toUpperCase())
.replace('.svg', '.vue');
await fs.writeFile(
path.join(outDir, pascalCaseFileName),
component,
'utf8',
);
fileNames.push(pascalCaseFileName);
}
const providerFileName = 'IconoirProvider.vue';
const provider = providerTemplate(providerKeyFileName);
await fs.writeFile(path.join(outDir, providerFileName), provider, 'utf8');
fileNames.push(providerFileName);
const index = indexTemplate(fileNames);
await fs.writeFile(path.join(outDir, 'index.ts'), index, 'utf8');
}

View file

@ -7,10 +7,17 @@ const PACKAGE_BASE = '';
const newVersion = semver.valid(semver.coerce(process.env.TAG_NAME));
console.info('New version is %s', newVersion);
if (!newVersion) {
throw new Error(`Tag name ${process.env.TAG_NAME} is not valid.`);
}
publishNpmPackage('iconoir');
publishNpmPackage('iconoir-react');
publishNpmPackage('iconoir-react-native');
publishNpmPackage('iconoir-vue');
publishPubPackage('iconoir-flutter');
function publishNpmPackage(name) {
console.info('Publishing %s', name);
@ -20,9 +27,11 @@ function publishNpmPackage(name) {
: path.join('packages', name, 'package.json');
const contents = JSON.parse(fs.readFileSync(packageJsonPath).toString());
contents.version = newVersion;
if (PACKAGE_BASE) {
contents.name = `${PACKAGE_BASE}/${name}`;
}
fs.writeFileSync(packageJsonPath, JSON.stringify(contents, undefined, 2));
console.info('package.json updated');
}
@ -38,9 +47,3 @@ function publishPubPackage(name) {
console.info('pubspec.yaml updated');
}
publishNpmPackage('iconoir');
publishNpmPackage('iconoir-react');
publishNpmPackage('iconoir-react-native');
publishNpmPackage('iconoir-vue');
publishPubPackage('iconoir-flutter');

View file

@ -1,21 +0,0 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_svg/flutter_svg.dart';
class __icon__(pascalCase) extends StatelessWidget {
final Color? color;
final double? width;
final double? height;
const __icon__(pascalCase)({Key? key, this.color, this.width, this.height})
: super(key: key);
@override
Widget build(BuildContext context) => SvgPicture.string(
'''
__svgfilecontent__''',
colorFilter:
color != null ? ColorFilter.mode(color!, BlendMode.srcIn) : null,
width: width,
height: height,
);
}

View file

@ -1,15 +0,0 @@
const template = (variables, { tpl }) => {
return tpl`
${variables.imports};
${variables.interfaces};
const ${variables.componentName} = (${variables.props}) => (
${variables.jsx}
);
${variables.exports};
`;
};
module.exports = template;

View file

@ -1,30 +0,0 @@
const template = (variables, { tpl }) => {
variables.props[0].name = 'passedProps';
// Workaround to fix ref type for React Native
const isNative = variables.imports.some(
(i) => i.source?.value === 'react-native-svg',
);
if (isNative) {
variables.props[1].typeAnnotation.typeAnnotation.typeParameters.params[0].typeName.name =
'Svg';
}
return tpl`
${variables.imports};
import { IconoirContext } from './IconoirContext';
${variables.interfaces};
const ${variables.componentName} = (${variables.props}) => {
const context = React.useContext(IconoirContext);
const props = { ...context, ...passedProps };
return ${variables.jsx};
};
${variables.exports};
`;
};
module.exports = template;

View file

@ -1,15 +0,0 @@
const path = require('path');
function template(filePaths) {
const exportEntries = filePaths.map(({ path: filePath }) => {
const basename = path.basename(filePath, path.extname(filePath));
const exportName = /^\d/.test(basename) ? `Svg${basename}` : basename;
return `export { default as ${exportName} } from './${basename}'`;
});
exportEntries.push(
"export { IconoirProvider, IconoirContext, IconoirContextValue } from './IconoirContext'",
);
return exportEntries.join('\n');
}
module.exports = template;

View file

@ -1,13 +0,0 @@
const path = require('path');
function template(filePaths) {
const exportEntries = filePaths.map((filePath) => {
const basename = path.basename(filePath, path.extname(filePath));
const exportName = /^\d/.test(basename) ? `Svg${basename}` : basename;
return `export { default as ${exportName} } from './${filePath}'`;
});
return exportEntries.join('\n');
}
module.exports = template;

View file

@ -1,7 +0,0 @@
const template =
() => `import type { InjectionKey, SVGAttributes } from "vue-demi";
const providerKey = Symbol() as InjectionKey<SVGAttributes>;
export default providerKey;
`;
module.exports = template;

View file

@ -1,15 +0,0 @@
const template = (keyFileName) => `<script setup lang="ts">
import { provide } from "vue-demi";
import type { SVGAttributes } from "vue-demi";
import providerKey from "./${keyFileName}";
interface Props {
iconProps: SVGAttributes
}
const props = defineProps<Props>();
provide(providerKey, props.iconProps);
</script>
<template>
<slot />
</template>`;
module.exports = template;

View file

@ -1,17 +0,0 @@
export const incompatibleNames = {
'1st-medal': 'medal-1st',
'4k-display': 'display-4k',
'2x2-cell': 'cell-2x2',
'360-view': 'view360',
github: 'gitHub',
'github-outline': 'gitHubOutline',
'gitlab-full': 'gitLabFull',
linkedin: 'linkedIn',
tiktok: 'tikTok',
youtube: 'youTube',
};
export const flutterIncompatibleNames = {
...incompatibleNames,
'color-filter': 'color-filter-icon',
};

22
css/iconoir-regular.css Normal file

File diff suppressed because one or more lines are too long

22
css/iconoir-solid.css Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

36
examples/next/.gitignore vendored Normal file
View file

@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

View file

@ -0,0 +1,17 @@
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Iconoir',
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}

View file

@ -0,0 +1,15 @@
import { Iconoir, Medal1st, Medal1stSolid } from 'iconoir-react';
import { AdobeAfterEffects as AdobeAfterEffectsRegular } from 'iconoir-react/regular';
import { AdobeAfterEffects as AdobeAfterEffectsSolid } from 'iconoir-react/solid';
export default function Home() {
return (
<>
<Iconoir />
<Medal1st />
<Medal1stSolid />
<AdobeAfterEffectsRegular color="red" />
<AdobeAfterEffectsSolid color="green" />
</>
);
}

View file

@ -0,0 +1,8 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
optimizePackageImports: ['iconoir-react'],
},
};
module.exports = nextConfig;

View file

@ -0,0 +1,22 @@
{
"name": "next",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"next": "14.0.0",
"react": "^18",
"react-dom": "^18"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"iconoir-react": "workspace:*",
"typescript": "^5"
}
}

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>

After

Width:  |  Height:  |  Size: 629 B

View file

@ -0,0 +1,27 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}

View file

@ -1,5 +1,10 @@
{
"extends": ["next/core-web-vitals"],
"settings": {
"next": {
"rootDir": "iconoir.com"
}
},
"rules": {
"react/no-unescaped-entities": ["off"]
},

View file

@ -4,19 +4,24 @@ 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} />;
}

View file

@ -19,6 +19,7 @@ function playWithLines1(setInstances: SetInstances): anime.AnimeInstance[] {
}),
];
}
function playWithLines2(setInstances: SetInstances): anime.AnimeInstance[] {
return [
anime({
@ -34,6 +35,7 @@ function playWithLines2(setInstances: SetInstances): anime.AnimeInstance[] {
}),
];
}
function playWithLines3(setInstances: SetInstances): anime.AnimeInstance[] {
return [
anime({
@ -58,6 +60,7 @@ function playWithLines3(setInstances: SetInstances): anime.AnimeInstance[] {
}),
];
}
function playWithLines4(setInstances: SetInstances): anime.AnimeInstance[] {
return [
anime({
@ -76,16 +79,19 @@ function playWithLines4(setInstances: SetInstances): anime.AnimeInstance[] {
export function AnimatedSvg() {
const instancesRef = React.useRef<anime.AnimeInstance[] | null>(null);
React.useEffect(() => {
instancesRef.current = playWithLines1((instances) => {
instancesRef.current = instances;
});
return () => {
for (const instance of instancesRef.current || []) {
instance.pause();
}
};
}, []);
return (
<svg
className="playWithLines2"

View file

@ -10,6 +10,7 @@ import { Text14 } from './Typography';
export function AvailableFor() {
const { ref, width } = useResizeObserver();
return (
<>
<MobileHeader>Available For</MobileHeader>

View file

@ -25,6 +25,7 @@ export function ChangelogEntry({
const [expanded, setExpanded] = React.useState(false);
const [shouldExpand, setShouldExpand] = React.useState(false);
const containerRef = React.useRef<HTMLDivElement>(null);
React.useEffect(() => {
if (
containerRef.current &&
@ -33,6 +34,7 @@ export function ChangelogEntry({
setShouldExpand(true);
}
}, []);
return (
<Container ref={containerRef}>
<ContainerLeft>

View file

@ -21,6 +21,7 @@ export function CustomizationEditor({
const [strokeWidth, setStrokeWidth] = React.useState(
customizations.strokeWidth,
);
React.useEffect(() => {
setColor(customizations.hexColor);
setSize(customizations.size);

View file

@ -17,9 +17,11 @@ export function DocumentationNavigation({
const router = useRouter();
const activePath = router.asPath.replace('/docs/', '');
const [expandedTitles, setExpandedTitles] = React.useState<string[]>([]);
React.useEffect(() => {
const expandedItems = documentationItems.filter((item) => {
const normalized = activePath.replace((pathPrefix || []).join('/'), '');
return (
normalized === item.path ||
item.children?.some((child) => {
@ -31,20 +33,24 @@ export function DocumentationNavigation({
});
setExpandedTitles(expandedItems.map((item) => item.title));
}, [activePath, pathPrefix, documentationItems]);
return (
<>
{documentationItems.map((documentationItem) => {
const path = [...(pathPrefix || []), documentationItem.path]
.filter(Boolean)
.join('/');
if (documentationItem.children?.length) {
const active = expandedTitles.includes(documentationItem.title);
return (
<React.Fragment key={documentationItem.title}>
<HeaderItem
onClick={() => {
setExpandedTitles((et) => {
const includes = et.includes(documentationItem.title);
return includes
? et.filter((i) => i !== documentationItem.title)
: [...et, documentationItem.title];

View file

@ -15,6 +15,7 @@ export interface ExploreProps {
export function Explore({ allIcons }: ExploreProps) {
const [filters, setFilters] = React.useState<IconListFilters>({});
const [customizations, setCustomizations] = useCustomizationPersistence();
return (
<Container>
<Left>

View file

@ -14,12 +14,15 @@ export function FiltersEditor({ filters, onChange }: FiltersEditorProps) {
// Keep track if the user hits tab before scrolling, so we can scroll the search
// field to the top of the page automatically.
const didScrollRef = React.useRef(false);
React.useEffect(() => {
const scrollEvent = () => {
didScrollRef.current = true;
window.removeEventListener('scroll', scrollEvent);
};
window.addEventListener('scroll', scrollEvent);
return () => {
window.removeEventListener('scroll', scrollEvent);
};

View file

@ -14,6 +14,7 @@ export interface FooterCategoryProps {
category: string;
links: { name: string; url: string }[];
}
function FooterCategory({ category, links }: FooterCategoryProps) {
return (
<FooterCategoryContainer>
@ -28,6 +29,7 @@ function FooterCategory({ category, links }: FooterCategoryProps) {
</FooterCategoryContainer>
);
}
export function Footer() {
return (
<Container>

View file

@ -15,6 +15,7 @@ export interface HeaderProps {
}
export function Header({ currentVersion }: HeaderProps) {
const [menuVisible, setMenuVisible] = React.useState(false);
return (
<Container>
<HeaderLeft>
@ -69,6 +70,7 @@ export const LogoContainer = styled.div`
transition: 0.2s;
}
`;
const MobileMenuButton = styled(ResetButton)`
&&& {
z-index: 101;
@ -156,6 +158,7 @@ const HeaderRight = styled(HeaderItem)`
justify-content: flex-end;
}
`;
export const Logo = styled.img`
height: 24px;
margin-top: -4px;
@ -171,6 +174,7 @@ export const LogoIcon = styled.div`
height: 36px;
}
`;
const BuiltWith = styled(Text15)<{ $isMobile?: boolean }>`
&&& {
display: ${(props) => (props.$isMobile ? 'flex' : 'none')};

View file

@ -19,10 +19,12 @@ export function HeaderBackground({ children }: HeaderBackgroundProps) {
const handleMouseMove = (event: MouseEvent) => {
const x = event.clientX / window.innerWidth;
const y = event.clientY / window.innerHeight;
parallaxElements.forEach((el) => {
const factor = parseFloat(
el.getAttribute('data-parallax-factor') || '1',
);
(el as HTMLElement).style.transform = `translate3d(${
x * factor * 40
}px, ${y * factor * 80}px, 0)`;

View file

@ -6,6 +6,7 @@ import { ResetButton } from './Button';
import { DEFAULT_CUSTOMIZATIONS, Icon as IconType } from './IconList';
const HEADER = '<?xml version="1.0" encoding="UTF-8"?>';
function bakeSvg(
svgString: string,
color: string,
@ -33,12 +34,14 @@ export function Icon({ iconWidth, icon }: IconProps) {
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(
@ -48,16 +51,19 @@ export function Icon({ iconWidth, icon }: IconProps) {
);
}
}, [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}>
@ -75,8 +81,6 @@ export function Icon({ iconWidth, icon }: IconProps) {
<IconComponent />
{icon.filename.includes('-solid') ? <IconTag>SOLID</IconTag> : ''}
</IconContainer>
{supportsClipboard ? (
<HoverContainer>

View file

@ -42,6 +42,7 @@ function filterIcons(allIcons: Icon[], filters: IconListFilters): Icon[] {
if (filters.search) {
const normalSearch = normalizeString(filters.search!);
let result = allIcons;
for (const term of normalSearch.split(' ')) {
result = result.filter((icon) => {
return (
@ -51,6 +52,7 @@ function filterIcons(allIcons: Icon[], filters: IconListFilters): Icon[] {
);
});
}
return result;
} else return allIcons;
}
@ -63,6 +65,7 @@ interface IconIconsRow {
icons: Icon[];
}
type IconRow = IconCategoryRow | IconIconsRow;
function isCategoryRow(iconRow: IconRow): iconRow is IconCategoryRow {
return !!(iconRow as IconCategoryRow).category;
}
@ -72,6 +75,7 @@ function getRowsFromIcons(
iconsPerRow: number,
): IconRow[] {
const categoryGroups: Record<string, Icon[]> = {};
for (const icon of filteredIcons) {
if (!categoryGroups[icon.category]) categoryGroups[icon.category] = [];
categoryGroups[icon.category].push(icon);
@ -79,12 +83,15 @@ function getRowsFromIcons(
const result: IconRow[] = [];
const sortedCategories = Object.keys(categoryGroups).sort();
for (const sortedCategory of sortedCategories) {
result.push({
category: sortedCategory,
numIcons: categoryGroups[sortedCategory].length,
});
const iconRows = chunk(categoryGroups[sortedCategory], iconsPerRow);
for (const iconRow of iconRows) {
result.push({ icons: iconRow });
}
@ -97,6 +104,7 @@ const ICON_BOTTOM_PADDING = 65;
const HEADER_HEIGHT = 150;
const HEADER_INNER_HEIGHT = 15 + 40;
const HEADER_TOP_PADDING = HEADER_HEIGHT - HEADER_INNER_HEIGHT;
function getItemSize(row: IconRow, iconWidth: number): number {
if (isCategoryRow(row)) {
return HEADER_HEIGHT;
@ -129,9 +137,11 @@ export function IconList({ filters, allIcons }: IconListProps) {
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);
@ -140,6 +150,7 @@ export function IconList({ filters, allIcons }: IconListProps) {
if (filteredIcons.length && iconsPerRow && width && iconWidth) {
const iconRows = getRowsFromIcons(filteredIcons, iconsPerRow);
children = (
<IconListContext.Provider value={{ iconsPerRow, iconWidth }}>
<ReactWindowScroller>
@ -187,6 +198,7 @@ const Row = React.memo(
({ data, index, style }: ListChildComponentProps<IconRow[]>) => {
const { iconWidth } = React.useContext(IconListContext)!;
const row = data[index];
if (isCategoryRow(row)) {
return (
<CategoryRow

View file

@ -12,6 +12,7 @@ export interface NavigationItemProps {
}
export function NavigationItem({ href, children, style }: NavigationItemProps) {
const router = useRouter();
return (
<Link href={href} passHref legacyBehavior>
<NavigationItemContainer

View file

@ -4,9 +4,11 @@ import { PraiseItem } from './PraiseItem';
import { media } from '../lib/responsive';
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 = () => {
@ -19,12 +21,14 @@ export function Praise() {
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 {
@ -33,13 +37,16 @@ export function Praise() {
}
}
};
const element = containerRef.current;
element.addEventListener('scroll', handle);
return () => {
element.removeEventListener('scroll', handle);
};
}
}, []);
return (
<>
<Container ref={containerRef}>

View file

@ -52,6 +52,7 @@ interface ReactWindowScrollerProps<Props extends ListProps | GridProps> {
throttleTime?: number;
isGrid?: boolean;
}
export function ReactWindowScroller<
Props extends ListProps | GridProps = ListProps,
>({
@ -85,6 +86,7 @@ export function ReactWindowScroller<
}, throttleTime);
targetElement.addEventListener('scroll', handleWindowScroll);
return () => {
handleWindowScroll.cancel();
targetElement.removeEventListener('scroll', handleWindowScroll);
@ -98,6 +100,7 @@ export function ReactWindowScroller<
// We have to get rid of the scroll handlers here, because they will cause the list
// to go blank whenever adjusting the number of items.
ref.current._onScrollVertical = () => {};
ref.current._onScrollHorizontal = () => {};
}, [outerRef]);

View file

@ -7,6 +7,7 @@ export interface SEOProps {
}
export function SEO({ title }: SEOProps) {
const pageTitle = title ? `${title} | ${TITLE_SUFFIX}` : TITLE_SUFFIX;
return (
<Head>
<title>{pageTitle}</title>

View file

@ -47,6 +47,7 @@ interface ThumbProps {
trackRef: React.RefObject<HTMLElement>;
index: number;
}
function Thumb({ state, trackRef, index }: ThumbProps) {
let inputRef = React.useRef(null);
let { thumbProps, inputProps } = useSliderThumb(
@ -59,6 +60,7 @@ function Thumb({ state, trackRef, index }: ThumbProps) {
);
let { focusProps, isFocusVisible } = useFocusRing();
return (
<ThumbContainer
style={{

View file

@ -36,6 +36,7 @@ const StatContainer = styled.div`
margin-bottom: 0;
}
`;
export const StatsContainer = styled.div`
display: flex;
align-items: flex-start;

View file

@ -142,12 +142,14 @@ const CopyContainer = styled.div`
export function Pre({ children, ...props }: React.PropsWithChildren<any>) {
const containerRef = React.useRef<HTMLPreElement>(null);
const [supportsClipboard, setSupportsClipboard] = React.useState(false);
React.useEffect(() => {
setSupportsClipboard(
typeof window !== 'undefined' &&
typeof window?.navigator?.clipboard?.writeText !== 'undefined',
);
}, []);
return (
<PreContainer {...props}>
<pre ref={containerRef}>{children}</pre>

View file

@ -11,8 +11,10 @@ export function useCustomizationPersistence(): [
const [customizations, _setCustomizations] = React.useState(
DEFAULT_CUSTOMIZATIONS,
);
React.useEffect(() => {
const localStorageValue = localStorage.getItem(CUSTOMIZATIONS_KEY);
if (localStorageValue) {
try {
const parsedValue = JSON.parse(localStorageValue);
@ -30,6 +32,7 @@ export function useCustomizationPersistence(): [
CUSTOMIZATIONS_KEY,
JSON.stringify(newCustomizations),
);
_setCustomizations(newCustomizations);
},
];

View file

@ -4,68 +4,73 @@ const path = require('path');
const previewPath = process.argv[2];
const resultPath = 'icons.csv';
const categoryMap = {
actions: 'Actions',
activities: 'Activities',
analytics: 'Analytics',
animations: 'Animations',
audio: 'Audio',
buildings: 'Buildings',
clothing: 'Clothing',
cloud: 'Cloud',
communication: 'Communication',
'actions': 'Actions',
'activities': 'Activities',
'analytics': 'Analytics',
'animations': 'Animations',
'audio': 'Audio',
'buildings': 'Buildings',
'clothing': 'Clothing',
'cloud': 'Cloud',
'communication': 'Communication',
'communication-1': 'Communication',
connectivity: 'Connectivity',
database: 'Database',
designtools: 'Design Tools',
development: 'Development',
devices: 'Devices',
docs: 'Docs',
editor: 'Editor',
'connectivity': 'Connectivity',
'database': 'Database',
'designtools': 'Design Tools',
'development': 'Development',
'devices': 'Devices',
'docs': 'Docs',
'editor': 'Editor',
'3deditor': '3D Editor',
emojis: 'Emojis',
finance: 'Finance',
food: 'Food',
gaming: 'Gaming',
gestures: 'Gestures',
git: 'Git',
health: 'Health',
home: 'Home',
identity: 'Identity',
layout: 'Layout',
maps: 'Maps',
music: 'Music',
nature: 'Nature',
navigation: 'Navigation',
organization: 'Organization',
other: 'Other',
photosvideos: 'Photos and Videos',
security: 'Security',
shapes: 'Shapes',
shopping: 'Shopping',
science: 'Science',
social: 'Social',
system: 'System',
transport: 'Transport',
users: 'Users',
weather: 'Weather',
'emojis': 'Emojis',
'finance': 'Finance',
'food': 'Food',
'gaming': 'Gaming',
'gestures': 'Gestures',
'git': 'Git',
'health': 'Health',
'home': 'Home',
'identity': 'Identity',
'layout': 'Layout',
'maps': 'Maps',
'music': 'Music',
'nature': 'Nature',
'navigation': 'Navigation',
'organization': 'Organization',
'other': 'Other',
'photosvideos': 'Photos and Videos',
'security': 'Security',
'shapes': 'Shapes',
'shopping': 'Shopping',
'science': 'Science',
'social': 'Social',
'system': 'System',
'transport': 'Transport',
'users': 'Users',
'weather': 'Weather',
};
const allIcons = fs.readdirSync(previewPath);
const resultLines = ['filename,category,tags'];
for (const filename of allIcons) {
const [icon, category] = path
.basename(filename)
.replace(path.extname(filename), '')
.split('@');
if (!icon || !category) {
console.error('invalid filename %s', filename);
process.exit(1);
}
const mappedCategory = categoryMap[category];
if (!mappedCategory) {
console.error('category %s does not exist', category);
process.exit(1);
}
resultLines.push(`"${icon}","${mappedCategory}",`);
}

View file

@ -4,17 +4,11 @@ filename,category,tags
"accessibility-tech","System","assistance,assistant,impaired",
"activity","Other",
"adobe-after-effects","Design Tools",
"adobe-after-effects-solid","Design Tools","",
"adobe-illustrator","Design Tools",
"adobe-illustrator-solid","Design Tools","",
"adobe-indesign","Design Tools",
"adobe-indesign-solid","Design Tools","",
"adobe-lightroom","Design Tools",
"adobe-lightroom-solid","Design Tools","",
"adobe-photoshop","Design Tools",
"adobe-photoshop-solid","Design Tools","",
"adobe-xd","Design Tools",
"adobe-xd-solid","Design Tools","",
"african-tree","Nature",
"agile","Business",
"air-conditioner","Home",
@ -24,33 +18,23 @@ filename,category,tags
"airplane-off","Transport","fly,jet",
"airplane-rotation","Transport","fly,jet",
"airplay","Connectivity",
"airplay-solid","Connectivity","",
"alarm","Other",
"alarm-solid","Other","",
"album","Music",
"album-carousel","Music",
"album-list","Music",
"album-open","Music",
"align-bottom-box","Design Tools",
"align-bottom-box-solid","Design Tools","",
"align-center","Editor",
"align-horizontal-centers","Design Tools","figma, design",
"align-horizontal-centers-solid","Design Tools","figma, design",
"align-horizontal-spacing","Design Tools","figma, design",
"align-horizontal-spacing-solid","Design Tools","figma, design",
"align-justify","Editor",
"align-left","Editor",
"align-left-box","Design Tools",
"align-left-box-solid","Design Tools","",
"align-right","Editor",
"align-right-box","Design Tools",
"align-right-box-solid","Design Tools","",
"align-top-box","Design Tools",
"align-top-box-solid","Design Tools","",
"align-vertical-centers","Design Tools","figma, design",
"align-vertical-centers-solid","Design Tools","",
"align-vertical-spacing","Design Tools","figma, design",
"align-vertical-spacing-solid","Design Tools","",
"angle-tool","Tools",
"antenna","Connectivity",
"antenna-off","Connectivity",
@ -58,7 +42,6 @@ filename,category,tags
"antenna-signal-tag","Connectivity",
"app-notification","Communication",
"app-store","Social","apple, iphone, ios",
"app-store-solid","Social","",
"app-window","System",
"apple","Food",
"apple-half","Food",
@ -66,7 +49,6 @@ filename,category,tags
"apple-imac-2021-side","Devices",
"apple-mac","System",
"apple-shortcuts","System",
"apple-shortcuts-solid","System","",
"apple-swift","Development",
"apple-wallet","Finance",
"ar-tag","Devices",
@ -80,27 +62,21 @@ filename,category,tags
"arrow-archery","Activities","sport,activity,arrows",
"arrow-down","Navigation",
"arrow-down-circle","Navigation",
"arrow-down-circle-solid","Navigation","",
"arrow-down-left","Navigation",
"arrow-down-left-circle","Navigation",
"arrow-down-left-circle-solid","Navigation","",
"arrow-down-left-square","Navigation",
"arrow-down-right","Navigation",
"arrow-down-right-circle","Navigation",
"arrow-down-right-circle-solid","Navigation","",
"arrow-down-right-square","Navigation",
"arrow-down-right-square-solid","Navigation","",
"arrow-down-tag","Navigation",
"arrow-email-forward","Communication",
"arrow-enlarge-tag","Navigation",
"arrow-left","Navigation",
"arrow-left-circle","Navigation",
"arrow-left-circle-solid","Navigation","",
"arrow-left-tag","Navigation",
"arrow-reduce-tag","Navigation",
"arrow-right","Navigation",
"arrow-right-circle","Navigation",
"arrow-right-circle-solid","Navigation","",
"arrow-right-tag","Navigation",
"arrow-separate","Navigation",
"arrow-separate-vertical","Navigation",
@ -108,17 +84,12 @@ filename,category,tags
"arrow-union-vertical","Navigation",
"arrow-up","Navigation",
"arrow-up-circle","Navigation",
"arrow-up-circle-solid","Navigation","",
"arrow-up-left","Navigation",
"arrow-up-left-circle","Navigation",
"arrow-up-left-circle-solid","Navigation","",
"arrow-up-left-square","Navigation",
"arrow-up-left-square-solid","Navigation","",
"arrow-up-right","Navigation",
"arrow-up-right-circle","Navigation",
"arrow-up-right-circle-solid","Navigation","",
"arrow-up-right-square","Navigation",
"arrow-up-right-square-solid","Navigation","",
"arrow-up-tag","Navigation",
"arrows-up-from-line","Shopping",
"asana","Development",
@ -139,7 +110,6 @@ filename,category,tags
"basketball","Activities",
"basketball-field","Activities",
"bathroom","Buildings","",
"bathroom-solid","Buildings","",
"battery-25","System","energy,source,charge",
"battery-50","System","energy,source,charge",
"battery-75","System","energy,source,charge",
@ -169,24 +139,18 @@ filename,category,tags
"bishop","Gaming","game,chess",
"bitbucket","Git","git, repository, github",
"bitcoin-circle","Finance",
"bitcoin-circle-solid","Finance","",
"bitcoin-rotate-out","Finance",
"bluetooth","Connectivity",
"bluetooth-tag","Connectivity",
"bluetooth-tag-solid","Connectivity","",
"bold","Editor",
"bold-square","Editor",
"bold-square-solid","Editor","",
"bonfire","Activities",
"book","Activities",
"book-lock","Activities",
"book-solid","Activities","",
"book-stack","Activities",
"bookmark","Organization",
"bookmark-book","Activities",
"bookmark-circle","Organization",
"bookmark-circle-solid","Organization","",
"bookmark-solid","Organization","",
"border-bl","Design Tools",
"border-bottom","Design Tools",
"border-br","Design Tools",
@ -221,7 +185,6 @@ filename,category,tags
"bubble-income","Communication",
"bubble-outcome","Communication",
"bubble-search","Communication",
"bubble-search-solid","Communication","",
"bubble-star","Communication",
"bubble-upload","Communication",
"bubble-warning","Communication",
@ -232,7 +195,6 @@ filename,category,tags
"bus-stop","Transport","transport,station,track,public,shuttle",
"c-square","Typography","carbon,element",
"cable-tag","Connectivity",
"cable-tag-solid","Connectivity","",
"calculator","System","math,operation",
"calendar","System","organization,organisation,plan,planning,date,day,month,year",
"calendar-minus","System",
@ -253,7 +215,6 @@ filename,category,tags
"cell-2x2","Layout",
"cellar","Buildings",
"center-align","Design Tools",
"center-align-solid","Design Tools","",
"chat-bubble","Communication",
"chat-bubble-check","Communication",
"chat-bubble-empty","Communication",
@ -266,7 +227,6 @@ filename,category,tags
"chat-plus-in","Communication",
"check","Actions",
"check-circle","Actions",
"check-circle-solid","Actions","",
"chocolate","Food",
"chromecast","Devices",
"chromecast-active","Devices",
@ -280,7 +240,6 @@ filename,category,tags
"clipboard-check","Actions",
"clock","Other",
"clock-rotate-right","Shopping",
"clock-solid","Other","",
"closed-captions-tag","Photos and Videos",
"closet","Home",
"cloud","Weather",
@ -289,7 +248,6 @@ filename,category,tags
"cloud-desync","Cloud",
"cloud-download","Cloud",
"cloud-square","Cloud",
"cloud-square-solid","Cloud","",
"cloud-sunny","Weather","weather,clouds",
"cloud-sync","Cloud",
"cloud-upload","Cloud",
@ -311,17 +269,12 @@ filename,category,tags
"commodity","Finance","gold, ingot, bar",
"community","Users",
"comp-align-bottom","Design Tools","figma, design",
"comp-align-bottom-solid","Design Tools","",
"comp-align-left","Design Tools","figma, design",
"comp-align-left-solid","Design Tools","",
"comp-align-right","Design Tools","figma, design",
"comp-align-right-solid","Design Tools","",
"comp-align-top","Design Tools","figma, design",
"comp-align-top-solid","Design Tools","",
"compact-disc","Music",
"compass","Navigation",
"component","Design Tools","figma, design",
"component-solid","Design Tools","",
"compress","Other",
"compress-lines","Editor",
"computer","Devices",
@ -376,7 +329,6 @@ filename,category,tags
"database","Database","db, storage, repo",
"database-backup","Database","db, storage, repo",
"database-check","Database","db, storage, repo",
"database-check-solid","Database","",
"database-export","Database","db, storage, repo",
"database-monitor","Database","db, storage, repo",
"database-restore","Database","db, storage, repo",
@ -385,20 +337,16 @@ filename,category,tags
"database-script-plus","Database",
"database-search","Database","db, storage, repo",
"database-settings","Database","db, storage, repo",
"database-solid","Database","",
"database-star","Database","db, storage, repo",
"database-stats","Database","db, storage, repo",
"database-tag","Database","db, storage, repo",
"database-tag-solid","Database","",
"database-warning","Database","db, storage, repo",
"database-xmark","Database","db, storage, repo, error",
"database-xmark-solid","Database","",
"de-compress","Other",
"delivery","Transport","ship,shipment,amazon",
"delivery-truck","Transport","ship,semitruck,shipment,amazon",
"depth","Science",
"design-nib","Design Tools",
"design-nib-solid","Design Tools","",
"design-pencil","Design Tools",
"desk","Home",
"developer","Development",
@ -424,7 +372,6 @@ filename,category,tags
"doc-star","Docs",
"doc-star-in","Docs",
"dogecoin-circle","Finance",
"dogecoin-circle-solid","Finance","",
"dogecoin-rotate-out","Finance",
"dollar","Finance",
"dollar-circle","Finance",
@ -437,10 +384,8 @@ filename,category,tags
"double-check","Actions",
"download","Actions",
"download-circle","Actions",
"download-circle-solid","Actions","",
"download-data-window","System","browser,os",
"download-square","Actions",
"download-square-solid","Actions","",
"drag","Navigation",
"drag-hand-gesture","Gestures",
"drawer","Home",
@ -457,7 +402,6 @@ filename,category,tags
"droplet","Design Tools",
"droplet-check","Nature","check, safe, water, clean, pure",
"droplet-half","Design Tools",
"droplet-solid","Design Tools","",
"ease-curve-control-points","Animations",
"ease-in","Animations",
"ease-in-control-point","Animations",
@ -501,9 +445,7 @@ filename,category,tags
"energy-usage-window","System","browser,os,consuption,economy",
"enlarge","Photos and Videos",
"erase","Actions",
"erase-solid","Actions","",
"ethereum-circle","Finance",
"ethereum-circle-solid","Finance","",
"ethereum-rotate-out","Finance",
"euro","Finance",
"euro-square","Finance",
@ -520,7 +462,6 @@ filename,category,tags
"extrude","3D Editor",
"eye","Actions",
"eye-closed","Actions",
"eye-solid","Actions","",
"f-square","Typography","fluorine,element",
"face-3d-draft","3D Editor",
"face-id","Identity",
@ -546,13 +487,11 @@ filename,category,tags
"figma","Design Tools",
"file-not-found","Other",
"fill-color","Design Tools",
"fill-color-solid","Design Tools","",
"fillet-3d","3D Editor",
"filter","Organization",
"filter-alt","Organization",
"filter-list","Navigation",
"filter-list-circle","Navigation",
"filter-solid","Organization","",
"finder","System","mac,apple,folder,file,os,operative,system",
"fingerprint","Identity",
"fingerprint-check-circle","Identity",
@ -587,7 +526,6 @@ filename,category,tags
"forward","Music",
"forward-15-seconds","Music",
"forward-message","Communication",
"forward-solid","Music","",
"frame","Design Tools",
"frame-alt","Design Tools",
"frame-alt-empty","Design Tools",
@ -596,7 +534,6 @@ filename,category,tags
"frame-select","Design Tools",
"frame-simple","Design Tools",
"frame-tool","Design Tools",
"frame-tool-solid","Design Tools","",
"fridge","Home",
"fx","Photos and Videos",
"fx-tag","Photos and Videos",
@ -659,22 +596,16 @@ filename,category,tags
"hdr","Photos and Videos",
"headset","Music","earphones,earbuds,listenings,audio",
"headset-bolt","Music","charge,energy,electricity,audio,listenings,earphones,earbuds",
"headset-bolt-solid","Music","",
"headset-help","Communication","earphones,earbuds,listenings,audio",
"headset-solid","Music","",
"headset-warning","Music","earphones,earbuds,listenings,audio,issue,error",
"headset-warning-solid","Music","",
"health-shield","Health",
"healthcare","Health",
"heart","Health",
"heart-arrow-down","System",
"heart-solid","Health","",
"heating-square","Science",
"heavy-rain","Weather","weather,storm",
"help-circle","Actions",
"help-circle-solid","Actions","",
"help-square","Actions",
"help-square-solid","Actions","",
"heptagon","Shapes",
"hexagon","Shapes",
"hexagon-dice","Gaming",
@ -696,14 +627,11 @@ filename,category,tags
"home-temperature-out","Home",
"home-user","Home",
"horiz-distribution-left","Design Tools",
"horiz-distribution-left-solid","Design Tools","",
"horiz-distribution-right","Design Tools",
"horiz-distribution-right-solid","Design Tools","",
"horizontal-merge","Navigation",
"horizontal-split","Navigation",
"hospital","Buildings",
"hospital-circle","Health",
"hospital-circle-solid","Health","",
"hot-air-balloon","Transport","fly, airship",
"hourglass","Activities",
"house-rooms","Buildings",
@ -714,7 +642,6 @@ filename,category,tags
"industry","Buildings",
"infinite","Science","limitless, endless, boundless, unbounded, unlimited",
"info-circle","Actions",
"info-circle-solid","Actions","",
"input-field","System","element,ui,textbox",
"input-output","System",
"input-search","Organization",
@ -727,7 +654,6 @@ filename,category,tags
"iris-scan","Identity",
"italic","Editor",
"italic-square","Editor",
"italic-square-solid","Editor","",
"jellyfish","Animals",
"journal","Docs",
"journal-page","Docs",
@ -754,7 +680,6 @@ filename,category,tags
"keyframes-minus","Animations",
"keyframes-plus","Animations","add",
"label","Organization",
"label-solid","Organization","",
"lamp","Home","light, home, house",
"language","Other",
"laptop","Devices",
@ -784,7 +709,6 @@ filename,category,tags
"list","Editor",
"list-select","Editor",
"litecoin-circle","Finance",
"litecoin-circle-solid","Finance","",
"litecoin-rotate-out","Finance",
"lock","Security",
"lock-slash","Security",
@ -834,8 +758,6 @@ filename,category,tags
"maximize","Other",
"medal","Activities",
"medal-1st","Activities","prize,place,first,winner,champion",
"medal-1st-solid","Activities","prize,place,first,winner,champion",
"medal-solid","Activities","",
"media-image","Photos and Videos",
"media-image-folder","Photos and Videos",
"media-image-list","Photos and Videos",
@ -856,25 +778,17 @@ filename,category,tags
"metro","Transport","transport,train,station,track,public",
"microphone","Audio",
"microphone-check","Audio",
"microphone-check-solid","Audio","",
"microphone-minus","Audio",
"microphone-minus-solid","Audio","",
"microphone-mute","Audio",
"microphone-plus","Audio",
"microphone-plus-solid","Audio","",
"microphone-solid","Audio","",
"microphone-speaking","Audio",
"microphone-speaking-solid","Audio","",
"microphone-warning","Audio",
"microphone-warning-solid","Audio","",
"microscope","Science",
"minus","Actions",
"minus-circle","Actions",
"minus-circle-solid","Actions","",
"minus-hexagon","Shapes",
"minus-square","Actions",
"minus-square-dashed","Other","remove,selection,select",
"minus-square-solid","Actions","",
"mirror","Home",
"mobile-dev-mode","Development",
"mobile-fingerprint","Identity",
@ -905,8 +819,6 @@ filename,category,tags
"music-double-note-plus","Music",
"music-note","Music",
"music-note-plus","Music",
"music-note-plus-solid","Music","",
"music-note-solid","Music","",
"n-square","Typography","nitrogen,element",
"nav-arrow-down","Navigation",
"nav-arrow-left","Navigation",
@ -917,12 +829,8 @@ filename,category,tags
"neighbourhood","Buildings",
"network","Connectivity","connection, computer, network, information",
"network-left","Connectivity","connection, computer, network, information",
"network-left-solid","Connectivity","",
"network-reverse","Connectivity","connection, computer, network, information",
"network-reverse-solid","Connectivity","",
"network-right","Connectivity","connection, computer, network, information",
"network-right-solid","Connectivity","",
"network-solid","Connectivity","",
"new-tab","System",
"nintendo-switch","Gaming","game,console,portable",
"no-smoking-circle","Activities",
@ -931,25 +839,15 @@ filename,category,tags
"npm","Development",
"npm-square","Development",
"number-0-square","Other",
"number-0-square-solid","Other","",
"number-1-square","Other",
"number-1-square-solid","Other","",
"number-2-square","Other",
"number-2-square-solid","Other","",
"number-3-square","Other",
"number-3-square-solid","Other","",
"number-4-square","Other",
"number-4-square-solid","Other","",
"number-5-square","Other",
"number-5-square-solid","Other","",
"number-6-square","Other",
"number-6-square-solid","Other","",
"number-7-square","Other",
"number-7-square-solid","Other","",
"number-8-square","Other",
"number-8-square-solid","Other","",
"number-9-square","Other",
"number-9-square-solid","Other","",
"numbered-list-left","Editor",
"numbered-list-right","Editor",
"o-square","Typography","oxygen,element",
@ -1001,7 +899,6 @@ filename,category,tags
"paste-clipboard","Actions",
"path-arrow","Navigation",
"pause","Music",
"pause-solid","Music","",
"pause-window","System","browser,os,hold,wait,freeze",
"paypal","Finance",
"pc-check","Security",
@ -1020,9 +917,7 @@ filename,category,tags
"percent-rotate-out","Finance",
"percentage","Activities",
"percentage-circle","Activities",
"percentage-circle-solid","Activities","",
"percentage-square","Activities",
"percentage-square-solid","Activities","",
"perspective-view","3D Editor",
"pharmacy-cross-circle","Health","hospital,health,emergency",
"pharmacy-cross-tag","Health","hospital,health,emergency",
@ -1038,8 +933,6 @@ filename,category,tags
"pillow","Home",
"pin","Organization",
"pin-slash","Organization",
"pin-slash-solid","Organization","",
"pin-solid","Organization","",
"pine-tree","Nature",
"pinterest","Social",
"pipe-3d","3D Editor",
@ -1049,7 +942,6 @@ filename,category,tags
"planet-sat","Science",
"planimetry","Buildings",
"play","Music",
"play-solid","Music","",
"playlist","Music",
"playlist-play","Music",
"playlist-plus","Music",
@ -1060,10 +952,8 @@ filename,category,tags
"plug-type-l","Connectivity","port, access, connection, cable",
"plus","Actions",
"plus-circle","Actions","add",
"plus-circle-solid","Actions","",
"plus-square","Actions","add",
"plus-square-dashed","Other",
"plus-square-solid","Actions","",
"png-format","Photos and Videos",
"pocket","Social","save,tool,app",
"podcast","Communication",
@ -1071,22 +961,16 @@ filename,category,tags
"position","Maps",
"position-align","Design Tools",
"post","Social",
"post-solid","Social","",
"potion","Gaming","power,superpower,energy",
"pound","Finance",
"precision-tool","Design Tools",
"presentation","Business",
"presentation-solid","Business","",
"printer","Devices",
"printing-page","Devices",
"priority-down","Business",
"priority-down-solid","Business","",
"priority-high","Business",
"priority-high-solid","Business","",
"priority-medium","Business",
"priority-medium-solid","Business","",
"priority-up","Business",
"priority-up-solid","Business","",
"privacy-policy","Docs",
"private-wifi","Connectivity",
"profile-circle","Users","user,round,person",
@ -1108,11 +992,9 @@ filename,category,tags
"redo","Actions",
"redo-action","Actions",
"redo-circle","Actions",
"redo-circle-solid","Actions","",
"reduce","Photos and Videos",
"refresh","Actions",
"refresh-circle","Actions",
"refresh-circle-solid","Actions","",
"refresh-double","Actions",
"reload-window","System","refresh,browser,os",
"reminder-hand-gesture","Gestures",
@ -1122,11 +1004,9 @@ filename,category,tags
"reply-to-message","Communication",
"report-columns","Activities",
"reports","Activities",
"reports-solid","Activities","",
"repository","Git","git, github",
"restart","Actions",
"rewind","Music",
"rewind-solid","Music","",
"rhombus","Shapes",
"rings","Activities",
"rocket","Science","space, nasa, esa, spacex, missile",
@ -1180,12 +1060,9 @@ filename,category,tags
"send-yens","Finance",
"server","Connectivity","connection, computer, network",
"server-connection","Connectivity","connection, computer, network",
"server-connection-solid","Connectivity","",
"server-solid","Connectivity","",
"settings","System",
"settings-profiles","System",
"share-android","Actions",
"share-android-solid","Actions","",
"share-ios","Actions",
"shield","Security","security,protection,firewall",
"shield-alert","Security","security,protection,firewall",
@ -1231,9 +1108,7 @@ filename,category,tags
"skateboard","Activities",
"skateboarding","Activities",
"skip-next","Music",
"skip-next-solid","Music","",
"skip-prev","Music",
"skip-prev-solid","Music","",
"slash","Development",
"slash-square","Git","git, github, command",
"sleeper-chair","Home",
@ -1259,9 +1134,7 @@ filename,category,tags
"sound-off","Audio",
"spades","Gaming","poker,game,cards,gambling,vegas",
"spark","Other","ai, artificial, intelligence",
"spark-solid","Other","",
"sparks","Other","ai, artificial, intelligence",
"sparks-solid","Other","",
"sphere","3D Editor","modeling,blender,4d,geometry",
"spiral","3D Editor","modeling,blender,4d,geometry",
"split-area","Other",
@ -1273,27 +1146,22 @@ filename,category,tags
"square-3d-from-center","3D Editor","modeling,blender,4d",
"square-3d-three-points","3D Editor","modeling,blender,4d",
"square-cursor","System",
"square-cursor-solid","System","",
"square-dashed","Other","selection",
"square-wave","Science",
"stackoverflow","Social",
"star","Organization",
"star-dashed","Organization",
"star-half-dashed","Organization",
"star-solid","Organization","",
"stat-down","Activities",
"stat-up","Activities",
"stats-down-square","Business",
"stats-down-square-solid","Business","",
"stats-report","Business",
"stats-up-square","Business",
"stats-up-square-solid","Business","",
"strategy","Business",
"stretching","Activities",
"strikethrough","Editor",
"stroller","Health",
"style-border","Design Tools",
"style-border-solid","Design Tools","",
"submit-document","Docs",
"substract","Design Tools",
"suggestion","Maps",
@ -1335,11 +1203,9 @@ filename,category,tags
"text-magnifying-glass","Editor","search",
"text-size","Editor",
"text-square","Editor",
"text-square-solid","Editor","",
"threads","Social",
"three-points-circle","3D Editor",
"three-stars","Organization",
"three-stars-solid","Organization","",
"thumbs-down","Social","disapprove,dislike,facebook,fb,meta,no",
"thumbs-up","Social","approve,like,facebook,fb,meta,yes",
"thunderstorm","Weather",
@ -1349,7 +1215,6 @@ filename,category,tags
"time-zone","Communication",
"timer","Other",
"timer-off","Other",
"timer-solid","Other","",
"tools","Tools",
"tournament","Gaming",
"tower","Security",
@ -1365,7 +1230,6 @@ filename,category,tags
"transition-up","Animations",
"translate","Other",
"trash","Actions",
"trash-solid","Actions","",
"treadmill","Activities",
"tree","Nature",
"trekking","Activities",
@ -1389,11 +1253,9 @@ filename,category,tags
"umbrella","Clothing",
"underline","Editor",
"underline-square","Editor",
"underline-square-solid","Editor","",
"undo","Actions",
"undo-action","Actions",
"undo-circle","Actions",
"undo-circle-solid","Actions","",
"union","Design Tools",
"union-alt","Design Tools",
"union-horiz-alt","Design Tools",
@ -1403,9 +1265,7 @@ filename,category,tags
"upload","Actions","upload,arrow,data,cloud,save",
"upload-data-window","System","os,browser",
"upload-square","Actions",
"upload-square-solid","Actions","",
"usb","Connectivity","port, access, connection, cable",
"usb-solid","Connectivity","",
"user","Users","person,people,ux",
"user-badge-check","Users","profile,person",
"user-bag","Shopping",
@ -1447,12 +1307,9 @@ filename,category,tags
"walking","Activities",
"wallet","Finance",
"warning-circle","Actions",
"warning-circle-solid","Actions","",
"warning-hexagon","Devices",
"warning-square","Actions",
"warning-square-solid","Actions","",
"warning-triangle","Actions",
"warning-triangle-solid","Actions","",
"warning-window","System","alert,danger",
"wash","Home",
"washing-machine","Home",
@ -1464,15 +1321,11 @@ filename,category,tags
"weight","Health",
"weight-alt","Health",
"white-flag","Other",
"white-flag-solid","Other","",
"wifi","Connectivity","signal, connection, network",
"wifi-off","Connectivity","signal, connection, network",
"wifi-signal-none","Connectivity","signal, connection, network",
"wifi-signal-none-solid","Connectivity","",
"wifi-tag","Connectivity","signal, connection, network",
"wifi-tag-solid","Connectivity","",
"wifi-warning","Connectivity","signal, connection, network",
"wifi-warning-solid","Connectivity","",
"wifi-xmark","Connectivity","signal,connection,network,error",
"wind","Weather","weather,air,fresh",
"window-check","System","browser,os",
@ -1492,9 +1345,7 @@ filename,category,tags
"xbox-y","Gaming",
"xmark","Actions","cancel,delete,remove",
"xmark-circle","Actions","delete",
"xmark-circle-solid","Actions","",
"xmark-square","Actions","remove",
"xmark-square-solid","Actions","",
"xray-view","3D Editor",
"y-square","Typography","coordinate,axis",
"yelp","Social",

Can't render this file because it has a wrong number of fields in line 2.

View file

@ -2,6 +2,7 @@ import fs from 'fs';
export function getHeaderProps() {
const packageJson = JSON.parse(fs.readFileSync('../package.json').toString());
return {
currentVersion: `v${packageJson.version}`,
};

View file

@ -1,42 +1,40 @@
import Case from 'case';
import csv from 'csvtojson';
import * as AllIcons from 'iconoir-react';
import { incompatibleNames } from '../../constants';
import { kebabCase, pascalCase } from 'scule';
import { Icon } from '../components/IconList';
const ICONS_PATH = 'icons.csv';
const TAG_SEPARATOR = '|';
const typedIncompatibleNames = incompatibleNames as Record<string, string>;
function getIconComponentName(filename: string) {
const dstFileName =
filename in typedIncompatibleNames
? typedIncompatibleNames[filename]
: filename;
return Case.pascal(dstFileName);
}
export async function getAllIcons(): Promise<Icon[]> {
const rows = await csv().fromFile(ICONS_PATH);
return rows.map<Icon>((row) => {
const iconComponentName = getIconComponentName(row.filename);
// Convert to lowercase to solve for differences in how the names are calculated.
const matchingKey = Object.keys(AllIcons).find(
(k) =>
k.toLowerCase() === iconComponentName.toLowerCase() ||
k.toLowerCase() === `svg${iconComponentName.toLowerCase()}`,
const icons: Icon[] = [];
for (const row of rows) {
const iconComponentName = pascalCase(row.filename);
const iconComponentSolidName = pascalCase(`${row.filename}-solid`);
const iconComponents = Object.keys(AllIcons).filter(
(icon) => icon === iconComponentName || icon === iconComponentSolidName,
);
if (!matchingKey)
if (iconComponents.length === 0)
throw new Error(
`Cannot find icon '${iconComponentName}' in iconoir-react.`,
`Couldn't find icons for ${row.filename} (${iconComponentName}) in 'iconoir-react'.`,
);
return {
filename: row.filename,
category: row.category,
tags:
row.tags?.split(TAG_SEPARATOR).map((item: string) => item.trim()) || [],
iconComponentName: matchingKey,
};
});
for (const iconComponent of iconComponents) {
icons.push({
filename: kebabCase(iconComponent),
category: row.category,
tags:
row.tags?.split(TAG_SEPARATOR).map((item: string) => item.trim()) ||
[],
iconComponentName: iconComponent,
});
}
}
return icons;
}

View file

@ -3,6 +3,7 @@ export function showNotification(message: string) {
element.classList.add('bottom-notification');
element.innerText = message;
document.body.appendChild(element);
setTimeout(() => {
element.remove();
}, 3000);

View file

@ -20,14 +20,13 @@
"@react-types/slider": "^3.6.1",
"@types/animejs": "^3.1.8",
"@types/lodash": "^4.14.199",
"@types/node": "^20.7.0",
"@types/react": "^18.2.22",
"@types/react-dom": "^18.2.7",
"@types/node": "^20.8.9",
"@types/react": "^18.2.33",
"@types/react-dom": "^18.2.14",
"@types/react-window": "^1.8.5",
"animejs": "^3.2.1",
"case": "^1.6.3",
"csvtojson": "^2.0.10",
"eslint": "^8.50.0",
"eslint": "^8.52.0",
"eslint-config-next": "^13.5.3",
"iconoir-react": "workspace:*",
"lodash": "^4.17.21",
@ -40,6 +39,7 @@
"react-window": "^1.8.9",
"remark-gfm": "^3.0.1",
"remark-prism": "^1.3.6",
"scule": "^1.0.0",
"styled-components": "^6.0.8",
"typescript": "^5.1.6",
"use-resize-observer": "^9.1.0"

View file

@ -21,7 +21,9 @@ export default class IconoirDocument extends Document {
enhanceApp: (App) => (props) =>
sheet.collectStyles(<App {...props} />),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
styles: (

View file

@ -25,6 +25,7 @@ interface DocumentationPageProps extends HeaderProps {
navigationItem: DocumentationItem;
navigationProps: DocumentationNavigationProps;
}
export default function DocumentationPage({
source,
title,
@ -183,10 +184,12 @@ function structureItemsToPaths(
): ParsedUrlQuery[] {
const result: ParsedUrlQuery[] = [];
const filteredItems = items.filter((item) => !item.skip);
for (const item of filteredItems) {
if (item.filepath && item.path) {
result.push({ slug: [...(slugPrefix || []), item.path].filter(Boolean) });
}
if (item.children?.length) {
result.push(
...structureItemsToPaths(
@ -234,11 +237,13 @@ function itemFromSlug(
const item = flatItems.find((flatItem) => flatItem.path === joinedSlug);
if (!item)
throw new Error(`Cannot find item matching slug '${slug.join('/')}'`);
return item;
}
export async function getStaticPaths(): Promise<GetStaticPathsResult> {
const structure = getDocumentationStructure();
return {
paths: structureItemsToPaths(structure).map((p) => ({ params: p })),
fallback: false,
@ -263,6 +268,7 @@ export async function getStaticProps(context: GetStaticPropsContext) {
remarkPlugins: [require('remark-prism'), remarkGfm],
},
});
return {
props: {
source: mdxSource,

View file

@ -62,6 +62,7 @@ export async function getStaticProps() {
...REPO,
});
const entries: ChangelogEntryProps[] = [];
for (const release of releases) {
entries.push({
name: release.name || release.tag_name,
@ -76,7 +77,9 @@ export async function getStaticProps() {
}),
});
}
const items = getDocumentationStructure();
return {
props: {
entries,

View file

@ -3,8 +3,10 @@ import React from 'react';
export default function Index() {
const router = useRouter();
React.useEffect(() => {
router.replace('/docs/introduction');
}, [router]);
return null;
}

View file

@ -24,6 +24,7 @@ interface HomeProps {
numStars: number;
numDownloads: number;
}
const Home: NextPage<HomeProps> = ({
allIcons,
currentVersion,
@ -176,6 +177,7 @@ export const HeroDescription = styled(Text18)<{ topMargin?: number }>`
margin-top: ${(props) => props.topMargin || 0}px;
}
`;
const Supporters = styled.div`
display: flex;
flex-direction: row;

View file

@ -16,6 +16,7 @@ import { getHeaderProps } from '../lib/getHeaderProps';
import { Praise } from '../components/Praise';
interface SupportProps extends HeaderProps {}
const Support: NextPage<SupportProps> = ({ ...headerProps }) => {
return (
<Layout>
@ -192,6 +193,7 @@ const PraiseTitle = styled(Heading2)`
const DonateLeft = styled.div`
max-width: 800px;
`;
export const DonateRight = styled.div`
display: flex;
align-items: center;
@ -204,6 +206,7 @@ export const DonateRight = styled.div`
margin-left: 30px;
}
`;
const DonateButton = styled(LargeButton)`
&&& {
padding: 0 24px;
@ -219,6 +222,7 @@ const CallToAction = styled(LargeButton)`
}
}
`;
export const DonateContainer = styled.div`
display: flex;
flex-direction: column;
@ -244,6 +248,7 @@ export const DonateHeader = styled.div`
font-size: 22px;
}
`;
const PraiseContainer = styled.div`
margin: 50px 0 64px 0;
${media.md} {

View file

@ -5,16 +5,23 @@
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}

View file

Before

Width:  |  Height:  |  Size: 902 B

After

Width:  |  Height:  |  Size: 902 B

View file

Before

Width:  |  Height:  |  Size: 964 B

After

Width:  |  Height:  |  Size: 964 B

View file

Before

Width:  |  Height:  |  Size: 681 B

After

Width:  |  Height:  |  Size: 681 B

View file

Before

Width:  |  Height:  |  Size: 230 B

After

Width:  |  Height:  |  Size: 230 B

View file

Before

Width:  |  Height:  |  Size: 677 B

After

Width:  |  Height:  |  Size: 677 B

View file

Before

Width:  |  Height:  |  Size: 677 B

After

Width:  |  Height:  |  Size: 677 B

View file

Before

Width:  |  Height:  |  Size: 634 B

After

Width:  |  Height:  |  Size: 634 B

View file

Before

Width:  |  Height:  |  Size: 557 B

After

Width:  |  Height:  |  Size: 557 B

View file

Before

Width:  |  Height:  |  Size: 807 B

After

Width:  |  Height:  |  Size: 807 B

View file

Before

Width:  |  Height:  |  Size: 623 B

After

Width:  |  Height:  |  Size: 623 B

View file

Before

Width:  |  Height:  |  Size: 696 B

After

Width:  |  Height:  |  Size: 696 B

View file

Before

Width:  |  Height:  |  Size: 667 B

After

Width:  |  Height:  |  Size: 667 B

View file

Before

Width:  |  Height:  |  Size: 996 B

After

Width:  |  Height:  |  Size: 996 B

View file

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

Before

Width:  |  Height:  |  Size: 1,016 B

After

Width:  |  Height:  |  Size: 1,016 B

View file

Before

Width:  |  Height:  |  Size: 475 B

After

Width:  |  Height:  |  Size: 475 B

View file

Before

Width:  |  Height:  |  Size: 596 B

After

Width:  |  Height:  |  Size: 596 B

View file

Before

Width:  |  Height:  |  Size: 586 B

After

Width:  |  Height:  |  Size: 586 B

View file

Before

Width:  |  Height:  |  Size: 620 B

After

Width:  |  Height:  |  Size: 620 B

View file

Before

Width:  |  Height:  |  Size: 756 B

After

Width:  |  Height:  |  Size: 756 B

View file

Before

Width:  |  Height:  |  Size: 522 B

After

Width:  |  Height:  |  Size: 522 B

Some files were not shown because too many files have changed in this diff Show more