Split variants (#368)
1
.eslintignore
Normal file
|
@ -0,0 +1 @@
|
|||
dist/
|
|
@ -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'],
|
||||
},
|
||||
};
|
||||
|
|
4
.github/workflows/build.yaml
vendored
|
@ -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
|
@ -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
|
@ -0,0 +1 @@
|
|||
dist/
|
|
@ -1,3 +1,4 @@
|
|||
{
|
||||
"singleQuote": true
|
||||
"singleQuote": true,
|
||||
"quoteProps": "consistent"
|
||||
}
|
||||
|
|
663
bin/build.js
|
@ -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
|
@ -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();
|
19
bin/build/lib/import-export.js
Normal 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
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
51
bin/build/targets/css/index.js
Normal 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);
|
||||
};
|
47
bin/build/targets/flutter/index.js
Normal 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);
|
||||
}
|
25
bin/build/targets/flutter/template.js
Normal 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;
|
248
bin/build/targets/react/index.js
Normal 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);
|
||||
}
|
36
bin/build/targets/react/resources/context-template.js
Normal 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'];
|
35
bin/build/targets/react/resources/icon-template.js
Normal 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};
|
||||
`;
|
||||
};
|
||||
}
|
46
bin/build/targets/react/resources/svgr-options.js
Normal 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'],
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
72
bin/build/targets/vue/index.js
Normal 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);
|
||||
};
|
|
@ -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;
|
|
@ -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');
|
||||
}
|
|
@ -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');
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
17
constants.js
|
@ -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
22
css/iconoir-solid.css
Normal file
36
examples/next/.gitignore
vendored
Normal 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
|
17
examples/next/app/layout.tsx
Normal 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>
|
||||
);
|
||||
}
|
15
examples/next/app/page.tsx
Normal 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" />
|
||||
</>
|
||||
);
|
||||
}
|
8
examples/next/next.config.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
experimental: {
|
||||
optimizePackageImports: ['iconoir-react'],
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
22
examples/next/package.json
Normal 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"
|
||||
}
|
||||
}
|
1
examples/next/public/next.svg
Normal 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 |
1
examples/next/public/vercel.svg
Normal 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 |
27
examples/next/tsconfig.json
Normal 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"]
|
||||
}
|
|
@ -1,5 +1,10 @@
|
|||
{
|
||||
"extends": ["next/core-web-vitals"],
|
||||
"settings": {
|
||||
"next": {
|
||||
"rootDir": "iconoir.com"
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
"react/no-unescaped-entities": ["off"]
|
||||
},
|
||||
|
|
|
@ -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} />;
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -10,6 +10,7 @@ import { Text14 } from './Typography';
|
|||
|
||||
export function AvailableFor() {
|
||||
const { ref, width } = useResizeObserver();
|
||||
|
||||
return (
|
||||
<>
|
||||
<MobileHeader>Available For</MobileHeader>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -21,6 +21,7 @@ export function CustomizationEditor({
|
|||
const [strokeWidth, setStrokeWidth] = React.useState(
|
||||
customizations.strokeWidth,
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
setColor(customizations.hexColor);
|
||||
setSize(customizations.size);
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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')};
|
||||
|
|
|
@ -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)`;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -12,6 +12,7 @@ export interface NavigationItemProps {
|
|||
}
|
||||
export function NavigationItem({ href, children, style }: NavigationItemProps) {
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<Link href={href} passHref legacyBehavior>
|
||||
<NavigationItemContainer
|
||||
|
|
|
@ -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}>
|
||||
|
|
|
@ -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]);
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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={{
|
||||
|
|
|
@ -36,6 +36,7 @@ const StatContainer = styled.div`
|
|||
margin-bottom: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
export const StatsContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
];
|
||||
|
|
|
@ -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}",`);
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
|
@ -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}`,
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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: (
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -3,8 +3,10 @@ import React from 'react';
|
|||
|
||||
export default function Index() {
|
||||
const router = useRouter();
|
||||
|
||||
React.useEffect(() => {
|
||||
router.replace('/docs/introduction');
|
||||
}, [router]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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} {
|
||||
|
|
|
@ -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"]
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 902 B After Width: | Height: | Size: 902 B |
Before Width: | Height: | Size: 964 B After Width: | Height: | Size: 964 B |
Before Width: | Height: | Size: 681 B After Width: | Height: | Size: 681 B |
Before Width: | Height: | Size: 230 B After Width: | Height: | Size: 230 B |
Before Width: | Height: | Size: 677 B After Width: | Height: | Size: 677 B |
Before Width: | Height: | Size: 677 B After Width: | Height: | Size: 677 B |
Before Width: | Height: | Size: 634 B After Width: | Height: | Size: 634 B |
Before Width: | Height: | Size: 557 B After Width: | Height: | Size: 557 B |
Before Width: | Height: | Size: 807 B After Width: | Height: | Size: 807 B |
Before Width: | Height: | Size: 623 B After Width: | Height: | Size: 623 B |
Before Width: | Height: | Size: 696 B After Width: | Height: | Size: 696 B |
Before Width: | Height: | Size: 667 B After Width: | Height: | Size: 667 B |
Before Width: | Height: | Size: 996 B After Width: | Height: | Size: 996 B |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 1,016 B After Width: | Height: | Size: 1,016 B |
Before Width: | Height: | Size: 475 B After Width: | Height: | Size: 475 B |
Before Width: | Height: | Size: 596 B After Width: | Height: | Size: 596 B |
Before Width: | Height: | Size: 586 B After Width: | Height: | Size: 586 B |
Before Width: | Height: | Size: 620 B After Width: | Height: | Size: 620 B |
Before Width: | Height: | Size: 756 B After Width: | Height: | Size: 756 B |
Before Width: | Height: | Size: 522 B After Width: | Height: | Size: 522 B |