Compare commits

...

331 commits

Author SHA1 Message Date
Pascal Jufer
2911b580e9
Fix building on Windows (#553) 2025-07-27 13:52:57 +02:00
Pascal Jufer
6e6d2e27ea
Update icon count 2025-07-27 12:42:18 +02:00
Pascal Jufer
ec9e470fda
Adjust discord label casing 2025-07-27 12:41:08 +02:00
Pascal Jufer
320c603674
Update readmes 2025-07-27 12:38:23 +02:00
Pascal Jufer
91f5ff77b3
Fix react-native module file path (#552) 2025-07-26 23:02:27 +02:00
Pascal Jufer
3a0e86816e
Updates & Clean-ups (#551) 2025-07-26 22:56:51 +02:00
Luca Burgio
ea8474ed13 fix: eslint 2025-07-21 09:42:55 +03:00
Luca Burgio
b12a69bf9e Merge branch 'main' of ssh://github.com/iconoir-icons/iconoir 2025-07-21 09:34:06 +03:00
Luca Burgio
569fa8246e chore: styling update on documentation nav 2025-07-21 09:28:15 +03:00
dependabot[bot]
4d9e934c61
build(deps-dev): bump vite from 6.2.6 to 6.2.7 (#533)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.2.6 to 6.2.7.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v6.2.7/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.2.7/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 6.2.7
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-21 09:25:29 +03:00
Pedro Simon
76b89cca9d
feat: add tags to existing icon in icons.csv (#536)
feat: add tags to existing icons in icons.csv
2025-07-21 09:23:44 +03:00
Luca Burgio
fc38183e2b chore: removed avatars 2025-07-21 09:21:25 +03:00
dependabot[bot]
e048feaae0
build(deps-dev): bump esbuild from 0.24.2 to 0.25.0 (#510)
Bumps [esbuild](https://github.com/evanw/esbuild) from 0.24.2 to 0.25.0.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG-2024.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.24.2...v0.25.0)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-13 11:53:11 +02:00
Luca Burgio
48c6486a1d Merge branch 'main' of https://github.com/iconoir-icons/iconoir 2025-04-13 11:46:42 +02:00
Luca Burgio
80608c712a fix: add newline on package.json for eslint update 2025-04-13 11:46:19 +02:00
lucaburgio
99165c8e8e Update build artifacts 2025-04-13 09:30:05 +00:00
dependabot[bot]
a0357830a5
build(deps-dev): bump vite from 6.0.6 to 6.0.15 (#527)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.0.6 to 6.0.15.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v6.0.15/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.0.15/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 6.0.15
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-13 11:29:32 +02:00
dependabot[bot]
36cf751ed3
build(deps): bump next from 15.1.2 to 15.2.4 (#528)
Bumps [next](https://github.com/vercel/next.js) from 15.1.2 to 15.2.4.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v15.1.2...v15.2.4)

---
updated-dependencies:
- dependency-name: next
  dependency-version: 15.2.4
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-13 11:09:07 +02:00
lucaburgio
b4b0e4bc54 Release Version v7.11.0 2025-04-12 16:34:37 +00:00
lucaburgio
5aab3794ad Update build artifacts 2024-12-27 13:51:32 +00:00
Luca Burgio
f581cf0571 add: 43 new icons 2024-12-27 14:50:50 +01:00
Pascal Jufer
02a224f444
Move path config back to main file 2024-12-26 17:07:49 +01:00
Pascal Jufer
1441fc20dc
Commit missing file 2024-12-26 17:02:55 +01:00
Pascal Jufer
c5b1c480a8
Use worker threads for building targets 2024-12-26 17:02:18 +01:00
Pascal Jufer
4b26faf0d2
Drop types-tsconfig again 2024-12-26 14:55:31 +01:00
Pascal Jufer
b386f30b5f
Update pnpm 2024-12-26 14:50:26 +01:00
Pascal Jufer
0805c8fd36
Fix react resolution for @types/react-window 2024-12-26 14:48:11 +01:00
Pascal Jufer
60cd60b0e7
Vue clean-ups / Drop support for Vue 2 (#502)
Vue Clean-ups / Drop support for Vue 2
2024-12-20 02:54:13 +01:00
Pascal Jufer
1ac85cc784
Fix linting & format files 2024-12-18 00:50:02 +01:00
Pascal Jufer
c2179807d1
Drop support for React < 18 (#501) 2024-12-18 00:38:33 +01:00
Pascal Jufer
51a93353db
Update example next 2024-12-17 02:40:33 +01:00
Pascal Jufer
7c5fd6103b
Fix flutter examples 2024-12-17 02:32:53 +01:00
Pascal Jufer
0d359ca1af
Add support for React 19 to iconoir-react (#500) 2024-12-17 02:32:23 +01:00
Pascal Jufer
2eb49e0fd0
Update deps in iconoir.com 2024-12-17 01:23:39 +01:00
Pascal Jufer
2bf8087c26
Refactor Lint/Format Setup (#499) 2024-12-17 00:25:26 +01:00
Pascal Jufer
a1003eb8bd
Update core deps (#497) 2024-12-15 19:23:17 +01:00
Pascal Jufer
e7919b7f5f
Enhance TS options (#496) 2024-12-15 13:32:00 +01:00
Pascal Jufer
401a53c779
Remove leftover logging 2024-12-15 13:23:15 +01:00
lucaburgio
a1d609dc47 Release Version v7.10.1 2024-11-28 22:03:07 +00:00
lucaburgio
8ac885f32e Update build artifacts 2024-11-28 21:55:30 +00:00
Luca Burgio
badca9126c fix: renamed 1 wrong icon
The regular icon droplet-snow-flake has been renamed to droplet-snow-flake-in to match the solid version.
2024-11-28 22:55:02 +01:00
lucaburgio
f7395255f4 Release Version v7.10.0 2024-11-10 23:14:56 +00:00
lucaburgio
a95cf03fb2 Update build artifacts 2024-11-10 23:11:13 +00:00
Luca Burgio
e57a02c201 add: 15 new icons 2024-11-10 23:10:44 +00:00
dependabot[bot]
3a50eb9483
build(deps-dev): bump next from 14.0.4 to 14.2.10 (#485)
Bumps [next](https://github.com/vercel/next.js) from 14.0.4 to 14.2.10.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v14.0.4...v14.2.10)

---
updated-dependencies:
- dependency-name: next
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-10 22:51:57 +00:00
dependabot[bot]
c9ab55448d
build(deps): bump http-proxy-middleware from 2.0.6 to 2.0.7 in /examples/react-native (#486)
build(deps): bump http-proxy-middleware in /examples/react-native

Bumps [http-proxy-middleware](https://github.com/chimurai/http-proxy-middleware) from 2.0.6 to 2.0.7.
- [Release notes](https://github.com/chimurai/http-proxy-middleware/releases)
- [Changelog](https://github.com/chimurai/http-proxy-middleware/blob/v2.0.7/CHANGELOG.md)
- [Commits](https://github.com/chimurai/http-proxy-middleware/compare/v2.0.6...v2.0.7)

---
updated-dependencies:
- dependency-name: http-proxy-middleware
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-10 22:51:27 +00:00
Luca Burgio
06e672671c chore: removed framer sponsor 2024-11-10 19:52:06 +00:00
Luca Burgio
874784995b chore: removed banner and updated header 2024-10-26 13:53:18 +02:00
Luca Burgio
645e5d4200 fix: added missing categories 2024-10-26 13:43:33 +02:00
Pascal Jufer
47272d2a14
Update pnpm to 9.12.0 2024-10-05 21:45:09 +02:00
dependabot[bot]
307cff3b2f
build(deps-dev): bump vite from 5.0.10 to 5.1.8 (#482)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.0.10 to 5.1.8.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.1.8/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.1.8/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-19 22:38:49 +02:00
dependabot[bot]
08adaa2cd1
build(deps): bump path-to-regexp and express in /examples/react-native (#480)
Bumps [path-to-regexp](https://github.com/pillarjs/path-to-regexp) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together.

Updates `path-to-regexp` from 0.1.7 to 0.1.10
- [Release notes](https://github.com/pillarjs/path-to-regexp/releases)
- [Changelog](https://github.com/pillarjs/path-to-regexp/blob/master/History.md)
- [Commits](https://github.com/pillarjs/path-to-regexp/compare/v0.1.7...v0.1.10)

Updates `express` from 4.19.2 to 4.21.0
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.0/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.0)

---
updated-dependencies:
- dependency-name: path-to-regexp
  dependency-type: indirect
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-18 08:37:44 +02:00
dependabot[bot]
f748429080
build(deps): bump body-parser and express in /examples/react-native (#481)
Bumps [body-parser](https://github.com/expressjs/body-parser) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together.

Updates `body-parser` from 1.20.2 to 1.20.3
- [Release notes](https://github.com/expressjs/body-parser/releases)
- [Changelog](https://github.com/expressjs/body-parser/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/body-parser/compare/1.20.2...1.20.3)

Updates `express` from 4.19.2 to 4.21.0
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.0/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.0)

---
updated-dependencies:
- dependency-name: body-parser
  dependency-type: indirect
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-18 08:37:32 +02:00
dependabot[bot]
45cf55d47a
build(deps): bump next from 14.1.1 to 14.2.10 (#478)
Bumps [next](https://github.com/vercel/next.js) from 14.1.1 to 14.2.10.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v14.1.1...v14.2.10)

---
updated-dependencies:
- dependency-name: next
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-18 08:37:11 +02:00
dependabot[bot]
39fff4ccd7
build(deps-dev): bump vite from 5.0.10 to 5.2.14 (#477)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.0.10 to 5.2.14.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.2.14/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.2.14/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-18 08:36:06 +02:00
lucaburgio
65a0a1e2b6 Release Version v7.9.0 2024-09-07 10:38:29 +00:00
lucaburgio
276b734666 Update build artifacts 2024-09-07 10:35:28 +00:00
Luca Burgio
e5f3b49a55 add: 16 new icons and changes on 2 2024-09-07 13:34:53 +03:00
Luca Burgio
db399c987f add: framer sponsor 2024-09-04 09:34:51 +03:00
dependabot[bot]
635fb03f77
build(deps): bump micromatch from 4.0.5 to 4.0.8 in /examples/react-native (#474)
build(deps): bump micromatch in /examples/react-native

Bumps [micromatch](https://github.com/micromatch/micromatch) from 4.0.5 to 4.0.8.
- [Release notes](https://github.com/micromatch/micromatch/releases)
- [Changelog](https://github.com/micromatch/micromatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/micromatch/compare/4.0.5...4.0.8)

---
updated-dependencies:
- dependency-name: micromatch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-31 09:46:33 +02:00
dependabot[bot]
062e4adc50
build(deps): bump webpack from 5.89.0 to 5.94.0 in /examples/react-native (#473)
build(deps): bump webpack in /examples/react-native

Bumps [webpack](https://github.com/webpack/webpack) from 5.89.0 to 5.94.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.89.0...v5.94.0)

---
updated-dependencies:
- dependency-name: webpack
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-31 09:39:15 +02:00
dependabot[bot]
1542558ad8
build(deps): bump fast-xml-parser from 4.3.2 to 4.4.1 in /examples/react-native (#467)
build(deps): bump fast-xml-parser in /examples/react-native

Bumps [fast-xml-parser](https://github.com/NaturalIntelligence/fast-xml-parser) from 4.3.2 to 4.4.1.
- [Release notes](https://github.com/NaturalIntelligence/fast-xml-parser/releases)
- [Changelog](https://github.com/NaturalIntelligence/fast-xml-parser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/NaturalIntelligence/fast-xml-parser/compare/v4.3.2...v4.4.1)

---
updated-dependencies:
- dependency-name: fast-xml-parser
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-13 09:18:07 +02:00
dependabot[bot]
c922ad1412
build(deps): bump braces from 3.0.2 to 3.0.3 in /examples/react-native (#465)
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

---
updated-dependencies:
- dependency-name: braces
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-21 11:28:53 +02:00
lucaburgio
514a3b6f92 Release Version v7.8.0 2024-07-21 07:36:39 +00:00
lucaburgio
e5f1b85231 Update build artifacts 2024-07-21 07:32:27 +00:00
Luca Burgio
7cb5d92018 add: 14 new icons 2024-07-21 10:31:58 +03:00
dependabot[bot]
4498d4c609
build(deps): bump fast-loops from 1.1.3 to 1.1.4 in /examples/react-native (#463)
build(deps): bump fast-loops in /examples/react-native

Bumps [fast-loops](https://github.com/robinweser/fast-loops) from 1.1.3 to 1.1.4.
- [Commits](https://github.com/robinweser/fast-loops/commits)

---
updated-dependencies:
- dependency-name: fast-loops
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-16 11:51:22 +02:00
Dániel Földi
d668898a5a
chore: update actions steps (#459)
Co-authored-by: Dániel Földi <6977583+DaniFoldi@users.noreply.github.com>
2024-07-04 11:30:03 +02:00
Luca Burgio
932b424c54 fix: prettier on DonationPopup 2024-06-23 16:03:15 +03:00
Luca Burgio
eed1425e3d add: donate toast 2024-06-23 15:58:59 +03:00
dependabot[bot]
27a22d57ef
build(deps): bump ws in /examples/react-native (#456)
Bumps  and [ws](https://github.com/websockets/ws). These dependencies needed to be updated together.

Updates `ws` from 8.14.2 to 8.17.1
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.14.2...8.17.1)

Updates `ws` from 7.5.9 to 8.17.1
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.14.2...8.17.1)

Updates `ws` from 6.2.2 to 8.17.1
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.14.2...8.17.1)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: indirect
- dependency-name: ws
  dependency-type: indirect
- dependency-name: ws
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-18 23:42:03 +02:00
dependabot[bot]
186a212831
build(deps): bump next from 14.0.4 to 14.1.1 (#440)
Bumps [next](https://github.com/vercel/next.js) from 14.0.4 to 14.1.1.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v14.0.4...v14.1.1)

---
updated-dependencies:
- dependency-name: next
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-10 06:42:46 +02:00
dependabot[bot]
f372adfafd
build(deps): bump next from 14.0.4 to 14.1.1 in /examples/next (#439)
Bumps [next](https://github.com/vercel/next.js) from 14.0.4 to 14.1.1.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v14.0.4...v14.1.1)

---
updated-dependencies:
- dependency-name: next
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-10 06:37:38 +02:00
lucaburgio
3d3215bda0 Release Version v7.7.0 2024-04-25 12:40:38 +00:00
lucaburgio
e36fae0ae1 Update build artifacts 2024-04-25 12:36:59 +00:00
Luca Burgio
ac19a0d84c add: 10 new icons 2024-04-25 14:36:27 +02:00
dependabot[bot]
433eab8e90
build(deps): bump tar from 6.2.0 to 6.2.1 in /examples/react-native (#427)
Bumps [tar](https://github.com/isaacs/node-tar) from 6.2.0 to 6.2.1.
- [Release notes](https://github.com/isaacs/node-tar/releases)
- [Changelog](https://github.com/isaacs/node-tar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/isaacs/node-tar/compare/v6.2.0...v6.2.1)

---
updated-dependencies:
- dependency-name: tar
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-11 18:32:38 +02:00
lucaburgio
791583941b Release Version v7.6.0 2024-04-06 06:55:16 +00:00
lucaburgio
2124819115 Update build artifacts 2024-04-06 06:52:33 +00:00
Luca Burgio
28288e091c Merge branch 'main' of https://github.com/iconoir-icons/iconoir 2024-04-06 08:52:05 +02:00
Luca Burgio
29255f5a62 fix: remove clip-path 2024-04-06 08:51:50 +02:00
lucaburgio
68ac2c7515 Update build artifacts 2024-04-06 06:47:54 +00:00
Luca Burgio
670825979d add: 8 new icons 2024-04-06 08:47:22 +02:00
dependabot[bot]
0e6b9fccfa
build(deps-dev): bump vite from 5.0.10 to 5.0.13 (#424)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.0.10 to 5.0.13.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.0.13/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.0.13/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-03 19:15:47 +02:00
dependabot[bot]
9f65fa8493
build(deps): bump express from 4.18.2 to 4.19.2 in /examples/react-native (#421)
build(deps): bump express in /examples/react-native

Bumps [express](https://github.com/expressjs/express) from 4.18.2 to 4.19.2.
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.18.2...4.19.2)

---
updated-dependencies:
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-29 14:05:07 +01:00
dependabot[bot]
8cc0960eee
build(deps): bump webpack-dev-middleware from 5.3.3 to 5.3.4 in /examples/react-native (#418)
build(deps): bump webpack-dev-middleware in /examples/react-native

Bumps [webpack-dev-middleware](https://github.com/webpack/webpack-dev-middleware) from 5.3.3 to 5.3.4.
- [Release notes](https://github.com/webpack/webpack-dev-middleware/releases)
- [Changelog](https://github.com/webpack/webpack-dev-middleware/blob/v5.3.4/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack-dev-middleware/compare/v5.3.3...v5.3.4)

---
updated-dependencies:
- dependency-name: webpack-dev-middleware
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-23 23:31:27 +01:00
dependabot[bot]
b9ef950a5e
build(deps): bump follow-redirects from 1.15.3 to 1.15.6 in /examples/react-native (#415)
build(deps): bump follow-redirects in /examples/react-native

Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.3 to 1.15.6.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.3...v1.15.6)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-23 23:31:19 +01:00
lucaburgio
ecda46b0b8 Release Version v7.5.0 2024-03-02 10:24:36 +00:00
lucaburgio
96cb5fc50a Update build artifacts 2024-03-02 10:21:58 +00:00
Luca Burgio
0f3f092228 feat: 15 new icons 2024-03-02 11:21:31 +01:00
rubabredwan
b9d30ab235
fix: remove SOLID div (#395)
Co-authored-by: Pascal Jufer <pascal-jufer@bluewin.ch>
2024-02-27 11:49:57 +01:00
dependabot[bot]
2f8360ec24
build(deps): bump ip from 1.1.8 to 1.1.9 in /examples/react-native (#406)
Bumps [ip](https://github.com/indutny/node-ip) from 1.1.8 to 1.1.9.
- [Commits](https://github.com/indutny/node-ip/compare/v1.1.8...v1.1.9)

---
updated-dependencies:
- dependency-name: ip
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-22 08:17:46 +01:00
Luca Burgio
235f6ed70c fix: changes to title and footer copy 2024-02-20 14:03:37 +01:00
Luca Burgio
04bf4952a7 fix: increased readability 2024-02-20 11:39:50 +01:00
Luca Burgio
f9d372b606 chore: prettier 2024-02-20 11:22:56 +01:00
Luca Burgio
d0a566a495 fix: removed bottom license url from README 2024-02-20 11:13:04 +01:00
Luca Burgio
b0f4479a65 fix: logic for canonical 2024-02-20 11:07:31 +01:00
Luca Burgio
390f392ea5 add: hono sponsor 2024-02-18 15:05:51 +01:00
Luca Burgio
f69b7f5c27 fix: all rel links 2024-02-10 12:48:52 +01:00
Luca Burgio
0f5f811e17 fix: streamline sponsor 2024-02-10 12:20:50 +01:00
lucaburgio
7cf649d870 Release Version v7.4.0 2024-02-10 10:30:38 +00:00
lucaburgio
04595f439d Update build artifacts 2024-02-10 10:27:56 +00:00
Luca Burgio
320c6df106 feat: 7 new solid icons 2024-02-10 11:27:22 +01:00
Luca Burgio
8ad02f8912 chore: prettier 2024-02-10 11:16:31 +01:00
Luca Burgio
bdc17791a5 fix: added sponsored attribute 2024-02-10 11:08:48 +01:00
Luca Burgio
82a1fc0437 revert: restored supporter avatars 2024-02-10 10:41:49 +01:00
lucaburgio
11da0d4b86 Update build artifacts 2024-01-27 08:45:14 +00:00
luxbe
4fb8bf0b9f
SVG code clean up: remove useless clipPaths (#366)
* refactor: remove useless clipPaths

* Delete hexagon-alt.svg

removed hexagon-alt

---------

Co-authored-by: Luca Burgio <burgio.luca@gmail.com>
2024-01-27 09:44:50 +01:00
lucaburgio
e102706245 Update build artifacts 2023-12-30 11:11:54 +00:00
Luca Burgio
2837f863b1 fixed naming with new solid icons 2023-12-30 12:11:28 +01:00
Luca Burgio
ff6dd8bd28 removed solid ref from icons.csv 2023-12-30 12:02:34 +01:00
lucaburgio
57ddd10bcc Release Version v7.3.0 2023-12-30 10:44:55 +00:00
lucaburgio
028db424bd Update build artifacts 2023-12-30 10:42:00 +00:00
Luca Burgio
1c7285fb8d 24 new solid icons and 4 icons fixed 2023-12-30 11:41:34 +01:00
Luca Burgio
0508eb3931 fix: prettier 2023-12-29 18:15:17 +01:00
Luca Burgio
5e510db0c5 fix: changed header url 2023-12-29 18:13:01 +01:00
Luca Burgio
6c358fad4b fix prettier error 2023-12-26 12:07:05 +01:00
Luca Burgio
777af4e27f added banner and removed avatars 2023-12-26 12:01:50 +01:00
Pascal Jufer
cf4d16369c
Update examples 2023-12-17 15:51:02 +01:00
Pascal Jufer
c4b034494e
Update all dependencies (#389) 2023-12-17 15:20:25 +01:00
Luca Burgio
7adc35d582 reverting prettier fixes 2023-12-17 11:45:26 +01:00
Luca Burgio
8585a9df7c Add Discord link on header 2023-12-17 11:26:10 +01:00
Luca Burgio
2bf04c12b0 fixed discord link and small prettier fixes 2023-12-17 10:48:14 +01:00
lucaburgio
55b5db79e2 Release Version v7.2.0 2023-12-16 11:23:56 +00:00
lucaburgio
8a93bdd98f Update build artifacts 2023-12-16 11:19:53 +00:00
Luca Burgio
6548056de5 8 new icons and changes 2023-12-16 12:19:23 +01:00
Luca Burgio
044843a3c3
Update README.md 2023-12-09 15:18:46 +01:00
lucaburgio
585e896a61 Release Version v7.1.0 2023-12-02 15:50:45 +00:00
lucaburgio
9908e62a90 Update build artifacts 2023-12-02 15:47:13 +00:00
Luca Burgio
29ad981ba8 add: 6 icons 2023-12-02 16:46:38 +01:00
Luca Burgio
ea5ec6c435 remove: media import from streamline ad 2023-11-22 15:30:37 +01:00
Luca Burgio
e6c9fe5e97 add: streamline ad 2023-11-22 15:26:27 +01:00
Pascal Jufer
5ff2b0d5cd
chore: fix lint setup 2023-11-21 22:38:48 +01:00
paescuj
282cf0d0a7 Release Version v7.0.3 2023-11-21 21:18:28 +00:00
Pascal Jufer
e983f9b1aa
fix: uningore flutter lib dir (#380) 2023-11-21 22:14:32 +01:00
Pascal Jufer
abe3530887
chore: update dependencies 2023-11-21 22:08:42 +01:00
Luca Burgio
46e146f41e fix: sitemap location 2023-11-20 22:05:56 +01:00
Luca Burgio
d35ee729bc fix: renamed hq folder to kw 2023-11-19 16:19:02 +01:00
Luca Burgio
151e4b53ed add: sitemap 2023-11-19 16:07:55 +01:00
Luca Burgio
bb1d22f970 fix: prettier 2023-11-19 10:40:52 +01:00
Luca Burgio
f2f579af1d fix: prettier 2023-11-19 10:37:39 +01:00
Luca Burgio
a315aacd42 fix: last prettier 2023-11-19 10:29:59 +01:00
Luca Burgio
1dac608fb5 fix: prettier 2023-11-19 10:25:34 +01:00
Luca Burgio
df19c352bd add: description var to SEO tsx + react landing 2023-11-19 10:02:47 +01:00
Pascal Jufer
2684fd353d
Use workflow_run event to run website workflow 2023-11-12 00:15:50 +01:00
lucaburgio
c82cc456e1 Release Version v7.0.2 2023-11-11 19:04:32 +00:00
lucaburgio
7e9115bc4a Update build artifacts 2023-11-11 18:58:38 +00:00
Ali Torbati
0be38ff1d0
Fix: refresh-circle icon stroke fix
The `circle` element in the "Refresh circle" icon included a hard coded `stroke-width`, which caused the circle to not inherit the parent `svg` `stroke-width`. This change fixes that bug.
2023-11-11 19:58:11 +01:00
paescuj
b0346bfed1 Release Version v7.0.1 2023-11-09 19:02:17 +00:00
Pascal Jufer
b4ce435b4b
Fix CSS class names for solid icons (#375) 2023-11-09 19:58:13 +01:00
Luca Burgio
f9e029ca0d fix: clean landing 2023-11-06 23:10:56 +01:00
Luca Burgio
b56dc6bdfd edit: restored classic landing 2023-11-06 23:06:07 +01:00
Luca Burgio
2c1d46e800 fix: prettier 2023-11-05 10:10:42 +01:00
Luca Burgio
c966cc500c add: temp product hunt badge 2023-11-05 10:07:47 +01:00
Pascal Jufer
b7ca56b873
Use same colors for share link as before 2023-10-29 22:24:23 +01:00
Pascal Jufer
4e322c234e
Update share button 2023-10-29 21:02:39 +01:00
Pascal Jufer
1e659eec9b
Clean-up vue example / flutter example lockfile 2023-10-29 15:49:42 +01:00
Pascal Jufer
84670eea99
Update react-native example to iconoir v7 2023-10-29 15:44:16 +01:00
Pascal Jufer
4b493bd49c
Enhance styling of share link 2023-10-29 15:00:40 +01:00
Pascal Jufer
9c4f6eefd9
Make whole share item clickable 2023-10-29 14:50:29 +01:00
Pascal Jufer
d76649b0ae
Twitter -> X 2023-10-29 14:39:22 +01:00
Pascal Jufer
146b5e41c0
Naming convention & few website enhancements 2023-10-29 14:33:17 +01:00
Luca Burgio
6751f840ab website: additional info to naming-conv doc 2023-10-29 14:19:37 +01:00
Pascal Jufer
e1106df996
Make the table scrollable on mobile 2023-10-29 13:09:53 +01:00
Pascal Jufer
246be483fe
Fix footer on docs & support pages 2023-10-29 12:53:50 +01:00
Pascal Jufer
4e970dc149
Delete iconoir.com/generate-icons.js
It seems like this one is no longer required, therefore deleting for now
2023-10-29 12:53:11 +01:00
Pascal Jufer
04bfbf1ff0
Update icons naming example 2023-10-29 10:53:44 +01:00
Pascal Jufer
0cfeb20dd5
Revert "Temporary release workflow for releasing Flutter"
This reverts commit 49dee5fc46.
2023-10-29 10:48:11 +01:00
Pascal Jufer
49dee5fc46
Temporary release workflow for releasing Flutter 2023-10-29 10:41:54 +01:00
Pascal Jufer
5f0007ce9a
Remove flutter assets
Doesn't work with symlink either, so we simply drop that
2023-10-29 10:31:37 +01:00
paescuj
d30af45b0d Release Version v7.0.0 2023-10-29 09:25:55 +00:00
Pascal Jufer
fa9bfd716d
Grant contents write permissions 2023-10-29 10:21:57 +01:00
Pascal Jufer
decf990892
Downgrade git-auto-commit-action to v4 2023-10-29 10:11:36 +01:00
Pascal Jufer
fd80cb43f0
Fix website workflow 2023-10-29 04:15:18 +01:00
Pascal Jufer
90fdaa6303
Fix website export & enhance docs page linking 2023-10-29 04:09:56 +01:00
Pascal Jufer
9789d64baf
Move Vue dist gen to build script 2023-10-29 03:24:18 +01:00
Pascal Jufer
1b58f4b357
Calculate IconoirContext path 2023-10-29 02:41:31 +01:00
Pascal Jufer
4a49bb6e9e
Update vue deps 2023-10-29 02:11:44 +02:00
Pascal Jufer
8a54b9f82e
Update website deps 2023-10-29 02:09:47 +02:00
Pascal Jufer
b3d29d8f4b
Fix vue code highlighting for website 2023-10-29 02:09:27 +02:00
Pascal Jufer
1ec4d9ded7
Ignore js files in react-native example (for now) 2023-10-29 01:46:09 +02:00
Pascal Jufer
ac36cbd737
Update actions 2023-10-29 01:44:52 +02:00
Pascal Jufer
60acd6d8b7
Add react-native example 2023-10-29 01:41:05 +02:00
Pascal Jufer
99f6dac1a6
Cleanup package.json in examples 2023-10-29 01:40:36 +02:00
Pascal Jufer
ff209d4539
Upgrade pnpm version 2023-10-29 01:39:37 +02:00
Pascal Jufer
febdd03940
Use new name for icon 2023-10-29 01:15:46 +02:00
Pascal Jufer
e0437a7cef
Add provider to examples 2023-10-29 01:06:48 +02:00
Pascal Jufer
660f8c51eb
Add Vue example 2023-10-29 01:02:13 +02:00
Pascal Jufer
83f4e1ce49
Enable npm provenance statements 2023-10-29 00:51:03 +02:00
Pascal Jufer
a6596786a1
Bump specificity for derived buttons 2023-10-29 00:48:58 +02:00
Pascal Jufer
d73b7282dd
Split variants (#368) 2023-10-29 00:33:17 +02:00
paescuj
9ea5993377 Update build artifacts 2023-10-28 14:39:26 +00:00
Luca Burgio
1adcd486b8
feat: naming convention (#276)
Co-authored-by: Pascal Jufer <pascal-jufer@bluewin.ch>
2023-10-28 16:38:44 +02:00
Luca Burgio
0e74439edb website: final fixes 2023-10-21 13:07:50 +02:00
Luca Burgio
4c32e86994 website: fix AvailableFor with Prettier 2023-10-21 12:52:56 +02:00
Luca Burgio
784c9b2b12 website: fix using Prettier 2023-10-21 12:50:12 +02:00
Luca Burgio
6340e56995 website: fix supporter div for Prettier 2023-10-21 12:37:52 +02:00
Luca Burgio
65a8d6ebf5 website: fix supporter divs 2023-10-21 12:25:44 +02:00
Luca Burgio
c42febf216 website: add more supported library icons 2023-10-21 12:18:31 +02:00
Luca Burgio
850fafe579 website: add new support CTA 2023-10-21 12:08:03 +02:00
Luca Burgio
b57280ed69 seo: update website cover 2023-10-21 10:00:05 +02:00
Pascal Jufer
a4f099dd72
chore: update package descriptions (#363) 2023-10-11 02:43:10 +02:00
Pascal Jufer
9b6f2b1e07 chore: fix obsolete ignore files & deprecated script 2023-10-11 02:37:06 +02:00
Pascal Jufer
42176717dc chore: clean-up keywords in main package 2023-09-28 00:13:29 +02:00
Pascal Jufer
8397c403c0 chore: clean-up package.json / pubspec.yaml files 2023-09-28 00:10:54 +02:00
Pascal Jufer
d5bd5a075f chore: remove unnecessary next.js readme 2023-09-27 23:54:44 +02:00
Pascal Jufer
42728125fa chore: prettify framer readme 2023-09-27 23:53:17 +02:00
Pascal Jufer
8ab0a3d086 chore: update & align readmes 2023-09-27 23:50:19 +02:00
Pascal Jufer
5ff75ea7bf
chore: remove no longer used asset 2023-09-26 02:39:27 +02:00
Pascal Jufer
6cafd6f2cd
chore: don't track source files anymore (#360) 2023-09-26 02:35:33 +02:00
Pascal Jufer
42c8c37a96 chore: prevent layout shift for active nav items 2023-09-26 01:10:40 +02:00
Pascal Jufer
de8ba37023
chore: active nav item state, fix footer on mobile (#359) 2023-09-26 00:21:30 +02:00
Pascal Jufer
78376407b5 fix: lint issue in website 2023-09-25 05:00:02 +02:00
Pascal Jufer
3f1afb2ecb
chore: fix various website styling issues (#358) 2023-09-25 04:43:10 +02:00
Pascal Jufer
10cb09ab5d
chore: remove package-lock.json 2023-09-25 00:42:41 +02:00
Pascal Jufer
37700c8700
chore: update website dependencies (#357) 2023-09-25 00:31:45 +02:00
Pascal Jufer
413c8d957b
chore: update listr2 (#356) 2023-09-24 21:53:53 +02:00
Pascal Jufer
0a098b96dd
chore: update svgr and adapt iconoir-react-native (#355) 2023-09-24 21:13:56 +02:00
Pascal Jufer
d5a40498ee
chore: update all conflict-free dependencies (#354) 2023-09-24 18:34:07 +02:00
Pascal Jufer
523a25b664
chore: switch to pnpm v8 and consolidate workflow setup (#353) 2023-09-24 17:14:43 +02:00
Pascal Jufer
59fcd073e3
chore: update github actions (#351) 2023-09-24 13:12:17 +02:00
Dániel Földi
e6cbccdadc
Only include svg files in build list (#346) 2023-09-05 12:54:49 +02:00
lucaburgio
03a415b18e Release Version v6.11.0 2023-08-02 12:01:32 +00:00
lucaburgio
2025fb27b4 Update build artifacts 2023-08-02 11:56:13 +00:00
lucaburgio
4af2028fb1 add: 12 new icons 2023-08-02 14:54:55 +03:00
lucaburgio
88ec0a3fac Release Version v6.10.0 2023-07-14 22:55:07 +00:00
lucaburgio
cd7e32d60e Update build artifacts 2023-07-14 22:52:50 +00:00
lucaburgio
834d7184e1 add: 6 new icons 2023-07-15 00:51:36 +02:00
lucaburgio
e2f9f3f94a Release Version v6.9.0 2023-06-21 05:58:18 +00:00
lucaburgio
ed518798e9 Update build artifacts 2023-06-21 05:55:46 +00:00
lucaburgio
696283ff8f add: 3 new icons 2023-06-21 07:54:08 +02:00
Sam Marks
23b582a5bd
fix: css icons had width / height fields removed inside rectangles (#306) 2023-05-10 08:08:57 -04:00
lucaburgio
b74a8eaf34 Release Version v6.8.0 2023-05-06 09:22:23 +00:00
lucaburgio
f0167dbd54 Update build artifacts 2023-05-06 09:18:12 +00:00
lucaburgio
c6a3fa1eca add: 8 new icons 2023-05-06 11:17:09 +02:00
sammarks
e078eb6001 Release Version v6.7.0 2023-05-03 12:31:07 +00:00
Sam Marks
df163b4f7b
chore: update repo paths 2023-05-03 08:27:35 -04:00
Sam Marks
c9eaf33728
chore: typo in readme header 2023-05-03 08:26:10 -04:00
Sam Marks
0650b85d94
chore: add vue to the prepublish list 2023-05-03 08:24:53 -04:00
Raimund
fb280188e1
feat: add vue icons (#294)
Resolves #294
2023-05-03 08:19:37 -04:00
Sam Marks
b7ba606c17
fix: correct footer links (#293) 2023-04-26 10:32:49 -04:00
Sam Marks
bd6de6b5df
chore: readme header fix 2023-04-19 17:04:16 -04:00
Matthew Kaulfers
3b6f7aac37
feat: add swift to readme 2023-04-19 17:03:31 -04:00
lucaburgio
7adad51d3d Release Version v6.6.0 2023-04-12 17:35:11 +00:00
lucaburgio
fc94c6a909 Update build artifacts 2023-04-12 17:29:51 +00:00
lucaburgio
333b8c9f33 add: 7 new icons 2023-04-12 19:28:40 +02:00
lucaburgio
a9f4aba656 fix: package links in the website documentation 2023-04-04 10:30:04 +02:00
Luca Burgio
a4ca0a78b5
Update README.md
Removed `fill` as a suggested prop.
2023-03-30 09:48:52 +02:00
lucaburgio
9599cc9ca2 Release Version v6.5.0 2023-03-26 12:48:38 +00:00
lucaburgio
7c1290f152 Update build artifacts 2023-03-26 12:41:44 +00:00
lucaburgio
2949bf9df7 add: 21 new icons 2023-03-26 14:40:32 +02:00
lucaburgio
08811ba987 fix: changed Ad and Sponsor components 2023-03-24 23:22:19 +01:00
lucaburgio
f0ed1f5a53 fix: added canonical url 2023-03-24 20:11:18 +01:00
lucaburgio
149a9b6c31 feat: added Framer sponsor 2023-03-24 17:37:28 +01:00
lucaburgio
6e6979c91f Release Version v6.4.1 2023-03-24 08:51:06 +00:00
lucaburgio
a68e443f56 Update build artifacts 2023-03-24 08:46:39 +00:00
lucaburgio
22c4815523 fix: optimized the optical size of 7 icons 2023-03-24 09:44:37 +01:00
Pascal Jufer
9b38a1a3d7
feat: support multiple keywords in search (#271) 2023-03-24 00:39:06 +01:00
Luca Oropallo
b9f886b321
feat: upgrade flutter SVG version 2023-03-20 13:33:01 -04:00
Luca Oropallo
555a8f5526
feat: add example app (#275) 2023-03-20 08:10:20 -04:00
Luca Burgio
2720cccb2b
feat: new website (#261)
* add: website redesign and optimization

* fix: additional improvements in home, support, and doc

* fix: typography and spacing

* fix: moved praise to support page

* fix: redesigned support page

* feat: added new pillars and paragraphs

* fix: changed table colors

* fix: shortened home

* fix: small improvements to support page

* fix: optimized mobile version

* feat: added Ad on mobile

* Remove outdated color prop from CurrentVersion

* Small format fixes

* fix: removed prefixes in Slider.tsx

Co-authored-by: Pascal Jufer <pascal-jufer@bluewin.ch>

* fix: removed prefixes in Button.tsx

Co-authored-by: Pascal Jufer <pascal-jufer@bluewin.ch>

* fix: removed unused const

Co-authored-by: Pascal Jufer <pascal-jufer@bluewin.ch>

* fix: correctly renamed const Overlay

Co-authored-by: Pascal Jufer <pascal-jufer@bluewin.ch>

* fix: applied same button style to the Copy button

Co-authored-by: Pascal Jufer <pascal-jufer@bluewin.ch>

* fix: Removed unnecessary prefix properties, fixed transition

Co-authored-by: Pascal Jufer <pascal-jufer@bluewin.ch>

* fix: Added non prefixed property in Stats.tsx

Co-authored-by: Pascal Jufer <pascal-jufer@bluewin.ch>

* fix: Added transition effect on mouse out as well

Co-authored-by: Pascal Jufer <pascal-jufer@bluewin.ch>

* fix: Removed a repeated property in Input.tsx

Co-authored-by: Pascal Jufer <pascal-jufer@bluewin.ch>

* fix: Changed home title

Co-authored-by: Pascal Jufer <pascal-jufer@bluewin.ch>

* fix: Changed icons to icon

Co-authored-by: Pascal Jufer <pascal-jufer@bluewin.ch>

---------

Co-authored-by: Pascal Jufer <pascal-jufer@bluewin.ch>
2023-03-19 21:23:43 +01:00
defectivepixel
c3a359f554
fix: change provider children type to ReactNode (#270) 2023-03-19 09:09:14 -04:00
Luca Burgio
c8662542d6
fix: update README for a cleaner page on website 2023-03-11 12:14:33 +01:00
lucaburgio
619be7a1a9 Update build artifacts 2023-03-07 21:38:51 +00:00
Luca Burgio
f94f631d49
fix: small optimizations to meta. (#258) 2023-03-07 22:37:53 +01:00
Luca Burgio
c9160cb913
Add Discord badge in README.md 2023-03-05 10:35:05 +01:00
lucaburgio
74ea1f3e39 Release Version v6.4.0 2023-03-04 14:57:32 +00:00
lucaburgio
3a29bdbae7 Update build artifacts 2023-03-04 14:55:31 +00:00
lucaburgio
876503b8bf Add: 9 new icons 2023-03-04 15:54:25 +01:00
lucaburgio
75158676ef Release Version v6.3.0 2023-02-22 22:54:19 +00:00
lucaburgio
8d785436aa Update build artifacts 2023-02-22 22:51:18 +00:00
lucaburgio
45bb894698 Add: 46 new icons 2023-02-22 23:50:01 +01:00
lucaburgio
1795808486 Release Version v6.2.1 2023-02-06 19:25:40 +00:00
lucaburgio
4d2583efbb Update build artifacts 2023-02-06 19:20:59 +00:00
lucaburgio
a16d3fd2e3 fix: optimized the optical size of 50 icons, and renewed some of them. 2023-02-06 20:20:00 +01:00
Pascal Jufer
147509d54d Fix footer format & types 2023-01-30 22:48:34 +01:00
Luca Burgio
75effa75c8
Feat website footer (#241)
* add: new footer and logo hover effect

* fix: footer links

* Fix: Footer privacy url, header links and animations, small additional changes

* Update iconoir.com/components/Footer.tsx

Co-authored-by: Sam Marks <sam@sammarks.me>

* fix: fixes on pull-request

---------

Co-authored-by: Sam Marks <sam@sammarks.me>
2023-01-30 22:38:12 +01:00
Luca Burgio
8ec9a33978
fix: optimized customization panel (#239)
* fix: optimized customization panel

* Update iconoir.com/components/CustomizationEditor.tsx

Co-authored-by: Sam Marks <sam@sammarks.me>

---------

Co-authored-by: Sam Marks <sam@sammarks.me>
2023-01-29 21:04:34 +01:00
sammarks
a1e16af3b5 Release Version v6.2.0 2023-01-29 19:54:07 +00:00
Sam Marks
888f634ccb
fix: proper mjs file extension to support astro, and NextJS 13 app directory support (#240) 2023-01-29 14:49:22 -05:00
lucaburgio
82c9d5ab5f Release Version v6.1.1 2023-01-28 18:18:23 +00:00
lucaburgio
bc2cc38a72 Update build artifacts 2023-01-28 18:16:15 +00:00
lucaburgio
2e1bda9e43 Fix 3 icons sizes 2023-01-28 19:15:11 +01:00
Sam Marks
d6d95c40bd
fix: disable file_names lint because it appears to be bugged with the 3d files 2023-01-26 14:29:20 -05:00
lucaburgio
14112242cd Release Version v6.1.0 2023-01-26 18:58:14 +00:00
lucaburgio
50b53d5cbd Release Version v6.1 2023-01-26 18:47:43 +00:00
sammarks
c3ffb2239f Update build artifacts 2023-01-26 18:36:43 +00:00
Sam Marks
601886da2b
chore: update lockfile 2023-01-26 13:35:44 -05:00
lucaburgio
7416b7f1e1 add: 24 new icons 2023-01-26 19:10:30 +01:00
Luca Burgio
dc6210ede7
docs: removed bits info in bug_report 2023-01-23 22:52:40 +01:00
Sam Marks
0b8a2b6112
docs: copy changes to css readme 2023-01-16 10:56:52 -05:00
sammarks
da547085a4 Update build artifacts 2023-01-16 15:55:07 +00:00
Sam Marks
773d84f287
fix: allow icon sizing with css (#232)
Resolves #218
2023-01-16 10:54:05 -05:00
paescuj
0d787b524c Update build artifacts 2023-01-11 18:09:27 +00:00
Pascal Jufer
6c780cc9e9
fix: clean-up website (#227) 2023-01-11 19:08:14 +01:00
Pascal Jufer
450fed6ac8
fix(website): menu bar misalignment (#228) 2023-01-10 19:10:11 -05:00
Pascal Jufer
9950179545 fix: use auth requests to fetch GitHub data in website build
To prevent running into rate limit
2023-01-09 21:07:23 +01:00
Pascal Jufer
ed64b38a02 chore(ci): remove unnecessary checkout in release workflow
This reverts part of commit c39ba626d2.
2023-01-09 20:49:00 +01:00
Sam Marks
c39ba626d2
fix: update icon names inside csv and pull changes before starting flutter release 2023-01-08 11:02:06 -05:00
lucaburgio
9af2ab63f8 Release Version v6.0 2023-01-08 15:11:14 +00:00
lucaburgio
47668b51dd Release Version v6.0 2023-01-08 12:21:41 +00:00
lucaburgio
9671e3d5d4 Update build artifacts 2023-01-08 12:19:52 +00:00
lucaburgio
80c638b599 Release v6.0 2023-01-08 13:18:14 +01:00
Pascal Jufer
20a5bc0847
ci: ignore draft releases for changelog generation (#223)
Ignore draft releases for changelog generation
2022-12-13 00:27:21 +01:00
Sam Marks
e30a858df7
ci: run website deployment after the release is complete 2022-12-08 11:49:38 -05:00
sammarks
42aa05f943 Release Version v5.5.2 2022-12-08 16:41:58 +00:00
Sam Marks
89640dc559
ci: try the new action instead 2022-12-08 11:39:58 -05:00
sammarks
56f2168cf0 Release Version v5.5.1 2022-12-08 16:32:50 +00:00
Sam Marks
0be1e9a35b
chore: attempt to fix flutter publishing 2022-12-08 11:23:52 -05:00
Pascal Jufer
3ea613b934
chore: refactor actions workflows (#222) 2022-12-08 11:06:02 -05:00
Luca Burgio
d3179c5825
Add: icons tags 2022-12-08 14:17:01 +01:00
lucaburgio
5ef52035c7 Release Version v5.5 2022-12-07 18:37:23 +00:00
lucaburgio
217d8e9257 Update build artifacts 2022-12-07 18:32:39 +00:00
lucaburgio
c63b575f92 add: 27 new icons 2022-12-07 19:31:40 +01:00
Sam Marks
8e6297c3d4
fix: hover effect on navigation items 2022-11-20 12:11:38 -05:00
lucaburgio
ef47e9d3c9 Release Version v5.4.1 2022-11-18 19:22:36 +00:00
lucaburgio
b6ab3316e6 Update build artifacts 2022-11-18 19:20:46 +00:00
lucaburgio
09e9a80cba fix: optimized 3 icons 2022-11-18 20:19:40 +01:00
Sam Marks
3a2e4f61b0
fix: oops, needed an important 2022-11-09 19:51:55 -05:00
Sam Marks
2deaff56b2
feat: quick-jump to icon search and hover effects for navigation (#215) 2022-11-09 19:48:15 -05:00
lucaburgio
cc59657579 Update build artifacts 2022-11-01 20:40:33 +00:00
lucaburgio
1e31060d41 fix: fixed naming for fast-arrow- icons 2022-11-01 21:39:31 +01:00
lucaburgio
5a21d2d460 Release Version v5.4 2022-11-01 19:39:03 +00:00
lucaburgio
b05ad740bb feat: updated icon categories 2022-11-01 20:35:45 +01:00
lucaburgio
1fb39edcae Update build artifacts 2022-11-01 19:33:24 +00:00
lucaburgio
a99c6a5ad9 add: 21 new icons 2022-11-01 20:32:24 +01:00
lucaburgio
31078c16d9 Release Version v5.3.2 2022-10-15 07:32:27 +00:00
lucaburgio
50972c0760 Update build artifacts 2022-10-15 07:30:13 +00:00
lucaburgio
908ce72b36 fix: optimized 23 icons 2022-10-15 09:28:57 +02:00
Luca Burgio
0c7cb31485
feat: second update to icon tags 2022-09-24 20:03:00 +02:00
Luca Burgio
b927f54717
feat: updated icon tags 2022-09-24 13:36:23 +02:00
5656 changed files with 28871 additions and 131073 deletions

View file

@ -1,14 +0,0 @@
module.exports = {
env: {
es2021: true,
node: true,
},
extends: ['eslint:recommended', 'plugin:prettier/recommended'],
parserOptions: {
ecmaVersion: 13,
sourceType: 'module',
},
rules: {
'prettier/prettier': ['error'],
},
};

View file

View file

@ -13,22 +13,21 @@ Before reporting an issue, please search to see if someone has filed a similar i
## Prerequisites
* Version:
* Are you running from source/main:
* Are you using a released build:
* Operating system:
* Bits:
- Version:
- Are you running from source/main:
- Are you using a released build:
- Operating system:
## Step to reproduce
*(Type here)*
_(Type here)_
### Actual behavior
## Any message or error
*(Type here)*
_(Type here)_
## Additional info or screenshots
* Screenshots
- Screenshots

View file

@ -13,6 +13,6 @@ Before creating an icon request, please search to see if someone has requested t
## Icon Request
* Icon name:
* Use case:
* Screenshots of similar icons:
- Icon name:
- Use case:
- Screenshots of similar icons:

36
.github/actions/setup/action.yml vendored Normal file
View file

@ -0,0 +1,36 @@
name: Setup
description: Setup the environment for the project
inputs:
node-registry:
description: Node.js package registry to set up for auth
required: false
runs:
using: composite
steps:
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 22
registry-url: ${{ inputs.node-registry }}
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Get pnpm store directory
id: pnpm-cache-dir
shell: bash
run: echo "dir=$(pnpm store path)" >> $GITHUB_OUTPUT
- name: Setup pnpm cache
uses: actions/cache@v4
with:
path: ${{ steps.pnpm-cache-dir.outputs.dir }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
shell: bash
run: pnpm install

View file

@ -9,23 +9,19 @@ jobs:
build:
name: Build
runs-on: ubuntu-latest
# Skip job on forks
if: github.repository_owner == 'iconoir-icons'
steps:
- uses: actions/checkout@v2
- uses: actions/cache@v2
with:
path: ~/.pnpm-store
key: ${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-
- uses: actions/setup-node@v2
with:
node-version: '16'
registry-url: 'https://registry.npmjs.org'
- uses: pnpm/action-setup@v2.1.0
with:
version: 7.8.0
run_install: true
- run: pnpm run build
- uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: Update build artifacts
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup
uses: ./.github/actions/setup
- name: Build CSS
run: pnpm run build css
- name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v6
with:
commit_message: Update build artifacts

28
.github/workflows/ci.yaml vendored Normal file
View file

@ -0,0 +1,28 @@
name: CI
on:
pull_request:
branches:
- main
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v46
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup
uses: ./.github/actions/setup
- name: Run Linter
run: pnpm exec eslint ${{ steps.changed-files.outputs.all_changed_files }}

View file

@ -9,48 +9,56 @@ jobs:
release:
name: Release
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@v2
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: main # We have to checkout main or PNPM fails. Tag should be on main anyway.
- uses: actions/cache@v2
# We have to checkout main or PNPM fails. Tag should be on main anyway.
ref: main
- name: Setup
uses: ./.github/actions/setup
with:
path: ~/.pnpm-store
key: ${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-
- uses: actions/setup-node@v2
node-registry: https://registry.npmjs.org
- name: Generate changelog file
uses: rhysd/changelog-from-release/action@v3
with:
node-version: '16'
registry-url: 'https://registry.npmjs.org'
- uses: pnpm/action-setup@v2.1.0
with:
version: 7.8.0
run_install: true
- uses: rhysd/changelog-from-release/action@v2
with:
file: packages/iconoir-flutter/CHANGELOG.md
github_token: ${{ secrets.GITHUB_TOKEN }}
file: packages/iconoir-flutter/CHANGELOG.md
commit: false
- run: pnpm run build
- run: pnpm run prepublish-all
args: -d=false
- name: Build
run: pnpm run build
- name: Prepare packages
run: pnpm run prepublish-all
env:
TAG_NAME: ${{ github.ref_name }}
- uses: stefanzweifel/git-auto-commit-action@v4
- name: Commit release
uses: stefanzweifel/git-auto-commit-action@v6
with:
commit_message: Release Version ${{ github.ref_name }}
branch: main
- run: git -c user.email="actions@github.com" -c user.name="GitHub Actions" tag -fa ${{ github.ref_name }} -m "${{ github.ref_name }}"
- run: git push -f origin ${{ github.ref_name }}
- run: npm publish --access public
continue-on-error: true
- name: Update tag
run: |
git -c user.email="actions@github.com" -c user.name="GitHub Actions" tag -fa ${{ github.ref_name }} -m "${{ github.ref_name }}"
git push -f origin ${{ github.ref_name }}
- name: Publish packages
run: pnpm -r publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- run: pnpm -r publish --filter './packages/**' --access public
continue-on-error: true
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
# - uses: sakebook/actions-flutter-pub-publisher@v1.4.0
# with:
# credential: ${{ secrets.PUB_CREDENTIAL_JSON }}
# package_directory: ./packages/iconoir-flutter
NPM_CONFIG_PROVENANCE: true
- name: Publish Flutter
uses: k-paxian/dart-package-publisher@v1.6
with:
credentialJson: ${{ secrets.PUB_CREDENTIAL_JSON }}
relativePath: ./packages/iconoir-flutter

View file

@ -1,62 +1,59 @@
name: Website
on:
workflow_dispatch:
workflow_run:
workflows:
- Release
types:
- completed
permissions:
actions: read
contents: read
pages: write
id-token: write
concurrency:
group: pages
group: ${{ github.workflow }}
cancel-in-progress: true
on:
workflow_dispatch: {}
push:
tags:
- 'v*'
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: main # We have to checkout main or PNPM fails. Tag should be on main anyway.
- uses: actions/cache@v2
with:
path: ~/.pnpm-store
key: ${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-
- uses: actions/setup-node@v2
with:
node-version: '16'
registry-url: 'https://registry.npmjs.org'
- uses: pnpm/action-setup@v2.1.0
with:
version: 7.8.0
run_install: true
- name: Build Packages
run: pnpm run dist
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup
uses: ./.github/actions/setup
- name: Build
run: ./node_modules/.bin/next build
run: pnpm run build react
- name: Build website
run: pnpm run build
working-directory: iconoir.com
- name: Export
run: ./node_modules/.bin/next export
working-directory: iconoir.com
- name: Setup Pages
uses: actions/configure-pages@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Setup GitHub Pages
uses: actions/configure-pages@v5
- name: Upload artifact
uses: actions/upload-pages-artifact@v1
uses: actions/upload-pages-artifact@v3
with:
path: './iconoir.com/out'
path: ./iconoir.com/out
deploy:
name: Deploy
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v1
uses: actions/deploy-pages@v4

8
.gitignore vendored
View file

@ -1,2 +1,10 @@
.DS_Store
node_modules/
dist/
packages/iconoir-flutter/lib/
packages/iconoir-vue/src/*
!packages/iconoir-vue/src/IconoirProvider.vue
!packages/iconoir-vue/src/providerKey.ts

1
.node-version Normal file
View file

@ -0,0 +1 @@
22

1
.npmrc
View file

@ -1 +0,0 @@
strict-peer-dependencies=false

View file

@ -1,3 +0,0 @@
{
"singleQuote": true
}

3
.vscode/extensions.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"recommendations": ["dbaeumer.vscode-eslint"]
}

51
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,51 @@
{
// Disable the default formatter, use eslint instead
"prettier.enable": false,
"editor.formatOnSave": false,
// Auto fix
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.organizeImports": "never"
},
// Silent the stylistic rules in you IDE, but still auto fix them
"eslint.rules.customizations": [
{ "rule": "style/*", "severity": "off", "fixable": true },
{ "rule": "format/*", "severity": "off", "fixable": true },
{ "rule": "*-indent", "severity": "off", "fixable": true },
{ "rule": "*-spacing", "severity": "off", "fixable": true },
{ "rule": "*-spaces", "severity": "off", "fixable": true },
{ "rule": "*-order", "severity": "off", "fixable": true },
{ "rule": "*-dangle", "severity": "off", "fixable": true },
{ "rule": "*-newline", "severity": "off", "fixable": true },
{ "rule": "*quotes", "severity": "off", "fixable": true },
{ "rule": "*semi", "severity": "off", "fixable": true }
],
// Enable eslint for all supported languages
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
"vue",
"html",
"markdown",
"json",
"json5",
"jsonc",
"yaml",
"toml",
"xml",
"gql",
"graphql",
"astro",
"svelte",
"css",
"less",
"scss",
"pcss",
"postcss"
]
}

View file

@ -17,23 +17,23 @@ diverse, inclusive, and healthy community.
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
- Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
- The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
@ -106,7 +106,7 @@ Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
@ -118,11 +118,11 @@ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0].
Community Impact Guidelines were inspired by
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available
at [https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org

74
NAMING_CONVENTION.md Normal file
View file

@ -0,0 +1,74 @@
# Naming Convention
## Hierarchy
The foundation of the naming convention in Iconoir is:
```
[Object]-[Modifier]-[Container]
```
### [Object]
Contains one or more objects.
### [Modifier]
It's often an additional and unique element added as a secondary object (`-minus`, `-plus`, `-warning`).
### [Container]
A shape used as a container for the object (`-square`, `-circle`).
If a shape is directly part of an object, it's not considered a container.
\* You could notice a difference between icons such as
`user-minus` and `minus-square`. The second one looks different
because in this case the minus symbol is an _[Object]_, followed
by the _[Container]_.
## Styles
An optional rule is regarding icons style. Iconoir is actually offering Regular and Solid icons. With the latter, icons names end with `-solid`. An example here:
- Regular: `check-circle`
- Solid: `check-circle-solid`
## Object-Oriented Naming
Exceptions apart, icons follow an object-oriented naming and
should not embed actions in their names.
Examples:
- `user-minus` is correct.
- `remove-user` would be wrong.
## Physical Actions
Icons that represent a physical action or movement
can embed that action in their name.
Examples:
- `walking`, `running`, `vehicle-fast`, `crane-lifting` are correct.
## Most-Used Modifiers and Shapes
### Modifiers
`-plus`, `-minus`, `-warning`, `-check`, `-xmark`, `-tag`,
`-ban`, `-slash`
- `-plus-in` and `minus-in`: Differently from `-plus` and `-minus`, these are used when the icon
has a bigger plus or minus icon in the center or inside the main object.
- `-no-access`: It's regularly used when the icon has a restrict
symbol in a corner.
### Shapes
`-square`, `-circle`
## Exceptions
If you spot an icon that is not following any of the rules,
please open a [new issue](https://github.com/iconoir-icons/iconoir/issues/new/choose) on GitHub.

112
README.md
View file

@ -1,69 +1,70 @@
<div align="center">
<img src="assets/cover.png" alt="Iconoir" />
</div>
# Iconoir
<div align="center">
Iconoir is an open-source library with 1000+ unique SVG icons, designed on a 24x24 pixels grid. No premium icons, no email sign-up, no newsletters.
</div>
[![Version](https://img.shields.io/github/v/release/iconoir-icons/iconoir?style=flat-square)](https://github.com/iconoir-icons/iconoir/releases)
[![Project Stars](https://img.shields.io/github/stars/iconoir-icons/iconoir?style=flat-square)](https://github.com/iconoir-icons/iconoir)
[![License](https://img.shields.io/github/license/iconoir-icons/iconoir?style=flat-square)](https://github.com/iconoir-icons/iconoir/blob/main/LICENSE)
[![Discord](https://img.shields.io/discord/998909400234348615?color=5865f2&label=discord&style=flat-square)](https://discord.gg/txXcKCAmKW)
<div align="center">
<a href="https://iconoir.com"><strong>Browse at iconoir.com &rarr;</strong></a>
</div>
## What is Iconoir?
<div align="center">
<a href="https://github.com/lucaburgio/iconoir/releases">
<img src="https://img.shields.io/github/v/release/lucaburgio/iconoir?style=flat-square" alt="Version" />
</a>
<a href="https://github.com/lucaburgio/iconoir">
<img src="https://img.shields.io/github/stars/lucaburgio/iconoir?style=flat-square" alt="Project Stars" />
</a>
<a href="https://www.npmjs.com/package/iconoir-react">
<img src="https://img.shields.io/npm/dm/iconoir-react?color=98E8F3&label=react&style=flat-square" alt="React Library" />
</a>
<a href="https://github.com/lucaburgio/iconoir/blob/main/LICENSE">
<img src="https://img.shields.io/github/license/lucaburgio/iconoir?style=flat-square" alt="License" />
</a>
</div>
Iconoir is an open-source library with 1600+ unique SVG icons, designed on a 24x24 pixels grid.
<a href="https://iconoir.com"><strong>Browse at iconoir.com &rarr;</strong></a>
## Basic Usage
You can download any icon of the pack directly from https://iconoir.com or get them from this repository.
Additionally, the icons are available via the `iconoir` NPM package:
```bash
yarn add iconoir
# or
npm i iconoir
```
The icons are also available via the [`iconoir`](https://www.npmjs.com/package/iconoir) NPM package:
| npm | Yarn | pnpm | Bun |
| --------------- | ------------------ | ------------------ | ----------------- |
| `npm i iconoir` | `yarn add iconoir` | `pnpm add iconoir` | `bun add iconoir` |
Example usage:
```js
import Iconoir from 'iconoir/icons/iconoir.svg'
import Iconoir from 'iconoir/icons/iconoir.svg';
```
## React
A React library is available to install under the name `iconoir-react`. For more details, see the package [README](./packages/iconoir-react).
A React library is available under the name `iconoir-react`.
For more details, see the package [README](./packages/iconoir-react).
## React Native
A React Native library is available to install under the name `iconoir-react-native`. For more details, see the package [README](./packages/iconoir-react-native).
A React Native library is available under the name `iconoir-react-native`.
For more details, see the package [README](./packages/iconoir-react-native).
## Vue
A Vue library is available under the name `@iconoir/vue`.
For more details, see the package [README](./packages/iconoir-vue).
## Flutter
A Flutter library is available to install under the name `iconoir_flutter`. For more details, see the package [README](./packages/iconoir-flutter).
A Flutter library is available under the name `iconoir_flutter`.
For more details, see the package [README](./packages/iconoir-flutter).
## Framer
Iconoir is happily part of [Framer](https://framer.com) now. To start using the icons: On the top menu, `Insert` > `Graphics` > `Iconoir`.
Iconoir is happily part of [Framer](https://framer.com).
To start using the icons: On the top menu, `Insert` > `Graphics` > `Iconoir`.
You can switch between icons from the right sidebar in the editor.
## CSS
Import the CSS File:
Import the CSS file:
```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/lucaburgio/iconoir@main/css/iconoir.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/iconoir-icons/iconoir@main/css/iconoir.css" />
```
Here is an example in HTML:
@ -71,12 +72,49 @@ Here is an example in HTML:
```html
<i class="iconoir-hand-brake"></i>
```
The class must always be "iconoir-" and then the name of the icon. You can find the names of the icons [here](https://iconoir.com).
The icons are `display: inline-block` and default to the current font size. You can control this
by adjusting the `::before` styles of the element (which is where the icons are added as a mask).
## Figma
The library is available in the Figma community [here](https://www.figma.com/community/file/983248991460488027/Iconoir-Pack).
## Swift Package
To add `Iconoir-swift` to your Xcode project, follow these steps:
1. In Xcode, open your project and navigate to _File_ > _Swift Packages_ > _Add Package Dependency..._
2. Enter the repository URL: `https://github.com/iconoir-icons/iconoir-swift.git`
3. Choose the branch or version you want to add, and click _Next_.
4. Select the target where you want to use the package, then click _Finish_.
### UIKit
```swift
import UIKit
import Iconoir
let imageView = UIImageView(image: Iconoir.bell.asUIImage)
```
### SwiftUI
```swift
import SwiftUI
import Iconoir
struct ContentView: View {
var body: some View {
Iconoir.bell.asImage
.foregroundColor(.blue)
.font(.system(size: 24))
}
}
```
## License
MIT License
MIT License.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View file

@ -1,482 +0,0 @@
import execa from 'execa';
import { promises as fs, readFileSync } from 'fs';
import { generateTemplateFilesBatch } from 'generate-template-files';
import { Listr } from 'listr2';
import os from 'os';
import path, { basename, dirname } from 'path';
import { fileURLToPath } from 'url';
import { incompatibleNames } from '../constants.js';
// Paths
const __dirname = dirname(fileURLToPath(import.meta.url));
const rootDir = path.join(__dirname, '..');
const iconoirIconsDir = path.join(rootDir, 'icons');
const ignoreCleanFilenames = ['IconoirContext.tsx'];
// 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',
},
};
// 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) => {
ctx.iconoirIconsFiles = await fs.readdir(iconoirIconsDir);
},
},
{
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, '');
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 promises = files
.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);
}
},
},
],
{ concurrent: false, exitOnError: false }
),
};
});
return task.newListr(tasks, {
concurrent: true,
rendererOptions: { collapse: 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.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`
);
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: 'Cleaning target directory',
task: async (ctx) => {
try {
const files = await fs.readdir(
builtIconsDir
);
const promises = files.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: '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)',
},
async 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: { collapse: false },
});
},
},
],
{ concurrent: false }
),
},
],
{ concurrent: true }
),
},
{
title: 'Removing temporary directory',
skip: (ctx) => !ctx.tmpDir,
task: async (ctx) => {
await fs.rm(ctx.tmpDir, { recursive: true });
},
},
],
{
concurrent: false,
exitOnError: false,
rendererOptions: { collapse: false, collapseErrors: false },
}
);
await tasks.run();

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

@ -0,0 +1,139 @@
import fs from 'node:fs/promises';
import path from 'node:path';
import process from 'node:process';
import { fileURLToPath } from 'node:url';
import { Listr } from 'listr2';
import { pascalCase, snakeCase } from 'scule';
import Tinypool from 'tinypool';
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.tasks = { global: { defaultVariant }, 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.tasks.icons[variant] = icons;
}
},
},
{
title: 'Building targets',
task: (ctx, task) =>
task.newListr(
Object.entries(targets).map(([targetName, targetConfig]) => ({
title: targetConfig.title,
enabled: () => ctx.cliTargets.length === 0 || ctx.cliTargets.includes(targetName),
task: (ctx) => {
targetConfig.path = path.join(
rootDir,
...targetConfig.path.split(path.posix.sep),
);
return ctx.pool.run({ targetName, config: ctx.tasks, 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);
}
}
const pool = new Tinypool({
filename: new URL('./worker.js', import.meta.url).href,
minThreads: 0,
resourceLimits: {
// Vue target (Vite/Rollup) takes up a lot of memory
maxOldGenerationSizeMb: 8192,
},
});
await tasks.run({ cliTargets, pool });
await pool.destroy();

View file

@ -0,0 +1,20 @@
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;
}

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

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

View file

@ -0,0 +1,21 @@
/*!
* Iconoir
* Copyright (c) [YEAR] Luca Burgio - https://iconoir.com
* 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: ' ';
display: block;
background: currentColor;
mask-size: cover;
-webkit-mask-size: cover;
width: 1em;
height: 1em;
}
*[class^='iconoir-'],
*[class*=' iconoir-'] {
display: inline-block;
}

View file

@ -0,0 +1,51 @@
import fs from 'node:fs/promises';
import { EOL } from 'node:os';
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
.replaceAll(EOL, '')
.replace(/(width|height)="\d+px"/g, '')
.replace(/ +/g, ' ');
const cssContent = `{mask-image:url('data:image/svg+xml;charset=utf-8,${transformedContent}');-webkit-mask-image:url('data:image/svg+xml;charset=utf-8,${transformedContent}');}`;
mainCssContent.push(`${cssTarget(icon, true)}${cssContent}`);
variantCssContent.push(`${cssTarget(icon)}${cssContent}`);
}
await fs.writeFile(
path.join(target.path, `iconoir-${variant}.css`),
variantCssContent,
);
}
await fs.writeFile(path.join(target.path, 'iconoir.css'), mainCssContent);
};

View file

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

View file

@ -0,0 +1,26 @@
function template(name, svg) {
return `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}({super.key, this.color, this.width, this.height});
@override
widgets.Widget build(widgets.BuildContext context) => SvgPicture.string(
'''
${svg}''',
colorFilter: color != null
? widgets.ColorFilter.mode(color!, widgets.BlendMode.srcIn)
: null,
width: width,
height: height,
);
}
`;
}
export default template;

View file

@ -0,0 +1,263 @@
import fs from 'node:fs/promises';
import path from 'node:path';
import * as svgr from '@svgr/core';
import * as esbuild from 'esbuild';
import {
generateExport,
generateImport,
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 };
const defaultTsOptions = {
declaration: true,
emitDeclarationOnly: true,
target: 'es6',
strict: true,
esModuleInterop: true,
skipLibCheck: true,
};
export default async (ctx, target) => {
const localJsTargets = jsTargets.map((jsTarget) => ({
...jsTarget,
}));
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,
target.native,
);
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);
if (!jsTarget.iconTemplate) {
jsTarget.iconTemplate = getIconTemplate(
target.native,
toImportPath(
path.relative(
path.join(jsTarget.path, variant),
jsTarget.iconoirContextPath,
),
),
);
}
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,
target.native,
);
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),
template,
};
return svgr.transform(iconContent, options);
}
async function generateDts(inputPath, outputPath, input, module, native) {
const dts = getDts(inputPath, await input, {
...defaultTsOptions,
jsx: native ? 'react-native' : 'react',
module,
...(module === 'esnext' && { moduleResolution: 'bundler' }),
});
return fs.writeFile(outputPath, dts);
}
async function generateJs(outputPath, input, format) {
const { code } = await esbuild.transform(await input, {
...defaultEsbuildOptions,
loader: 'tsx',
format,
});
return fs.writeFile(outputPath, code);
}
function prepareIndex(jsTarget, variant) {
const outputPath = path.join(jsTarget.path, variant ?? '');
const iconoirContextPath = toImportPath(
path.relative(outputPath, jsTarget.iconoirContextPath),
);
const iconoirContext = generateExport(
iconoirContextExports,
iconoirContextPath,
);
const content = [iconoirContext];
const iconJsPath = toImportPath(
path.relative(outputPath, path.join(jsTarget.path, `icon.${jsTarget.ext}`)),
);
const iconDtsImport = generateImport('Icon', iconJsPath);
const dtsContent = [iconoirContext, iconDtsImport, 'type I = typeof Icon;'];
function add(name, iconPath) {
const iconImportPath = toImportPath(path.relative(outputPath, iconPath));
content.push(generateExport(`default as ${name}`, iconImportPath));
dtsContent.push(`export declare const ${name}: I;`);
}
function generate() {
const indexJs = generateIndexJs(
outputPath,
content,
jsTarget.format,
jsTarget.ext,
);
const indexDts = generateIndexDts(outputPath, dtsContent, jsTarget.dtsExt);
return Promise.all([indexJs, indexDts]);
}
return { add, generate };
}
async function generateIndexJs(outputDir, content, format, ext) {
const { code } = await esbuild.transform(content.join(''), {
minify: true,
format,
});
return fs.writeFile(path.join(outputDir, `index.${ext}`), code);
}
async function generateIndexDts(outputDir, content, dtsExt) {
return fs.writeFile(path.join(outputDir, `index.${dtsExt}`), content);
}

View file

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

View file

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

View file

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

View file

@ -0,0 +1,97 @@
import fs from 'node:fs/promises';
import path from 'node:path';
import vue from '@vitejs/plugin-vue';
import { fromHtml } from 'hast-util-from-html';
import { toHtml } from 'hast-util-to-html';
import { build } from 'vite';
import dts from 'vite-plugin-dts';
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));
await Promise.all(promises);
return build({
root: target.path,
logLevel: 'silent',
build: {
outDir: 'dist',
lib: {
entry: path.join('src', 'index.ts'),
fileName: (format, name) =>
format === 'cjs' ? `${name}.js` : `esm/${name}.mjs`,
formats: ['cjs', 'es'],
},
rollupOptions: {
external: ['vue'],
},
},
plugins: [
vue({
isProduction: true,
}),
dts(),
],
});
};

View file

@ -0,0 +1,21 @@
function template(svg) {
return `<script lang="ts">
import type { SVGAttributes } from 'vue';
import { defineComponent, inject } from 'vue';
import providerKey from '../providerKey';
export default defineComponent<SVGAttributes>({
setup() {
const context = inject(providerKey);
return { context };
},
});
</script>
<template>
${svg}
</template>
`;
}
export default template;

7
bin/build/worker.js Normal file
View file

@ -0,0 +1,7 @@
export default async ({ targetName, config, targetConfig }) => {
const { default: task } = await import(
`./targets/${targetConfig.target || targetName}/index.js`
);
return task(config, targetConfig);
};

View file

@ -1,21 +0,0 @@
/*!
* Iconoir
* Copyright (c) [YEAR] Luca Burgio - https://iconoir.com
* License - https://github.com/lucaburgio/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: " ";
display: block;
background: currentColor;
mask-size: cover;
-webkit-mask-size: cover;
width: 1em;
height: 1em;
}
*[class^="iconoir-"],
*[class*=" iconoir-"] {
display: block;
}

View file

@ -1,28 +1,34 @@
import { updateYamlKey } from '@atomist/yaml-updater';
import fs from 'fs';
import path from 'path';
import semver from 'semver';
/* eslint-disable no-console */
const PACKAGE_BASE = '';
import fs from 'node:fs';
import path from 'node:path';
import process from 'node:process';
import { updateYamlKey } from '@atomist/yaml-updater';
import semver from 'semver';
const 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);
const packageJsonPath =
name === 'iconoir'
? 'package.json'
: path.join('packages', name, 'package.json');
const packageJsonPath = name === 'iconoir'
? 'package.json'
: path.join('packages', name, 'package.json');
const contents = JSON.parse(fs.readFileSync(packageJsonPath).toString());
contents.version = newVersion;
if (PACKAGE_BASE) {
contents.name = `${PACKAGE_BASE}/${name}`;
}
fs.writeFileSync(packageJsonPath, JSON.stringify(contents, undefined, 2));
console.info('package.json updated');
}
@ -33,13 +39,8 @@ function publishPubPackage(name) {
fs.writeFileSync(
pubspecFilepath,
updateYamlKey('version', newVersion, pubspecContents)
updateYamlKey('version', newVersion, pubspecContents),
);
console.info('pubspec.yaml updated');
}
publishNpmPackage('iconoir');
publishNpmPackage('iconoir-react');
publishNpmPackage('iconoir-react-native');
publishPubPackage('iconoir-flutter');

View file

@ -1,20 +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__''',
color: color,
width: width,
height: height,
);
}

View file

@ -1,26 +0,0 @@
const template = (
{ template },
opts,
{ imports, interfaces, componentName, props, jsx, exports }
) => {
const plugins = ['jsx'];
if (opts.typescript) {
plugins.push('typescript');
}
const typeScriptTpl = template.smart({ plugins });
props[0].name = 'passedProps';
return typeScriptTpl.ast`${imports}
import { IconoirContext } from './IconoirContext'
${interfaces}
function ${componentName}(${props}) {
const context = React.useContext(IconoirContext);
const props = { ...context, ...passedProps };
return ${jsx};
}
${exports}
`;
};
module.exports = template;

View file

@ -1,15 +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 './${basename}'`;
});
exportEntries.push(
"export { IconoirProvider, IconoirContext, IconoirContextValue } from './IconoirContext'"
);
return exportEntries.join('\n');
}
module.exports = template;

View file

@ -1,12 +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',
};

View file

@ -1,9 +1,9 @@
# Iconoir CSS
# Iconoir - CSS
Import the CSS File:
Import the CSS file:
```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/iconoir-icons/iconoir@main/css/iconoir.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/iconoir-icons/iconoir@main/css/iconoir.css" />
```
Here is an example in HTML:
@ -11,7 +11,8 @@ Here is an example in HTML:
```html
<i class="iconoir-hand-brake"></i>
```
The class must always be "iconoir-" and then the name of the icon. You can find the names of the
icons [here](https://iconoir.com).
<SuggestLibrary />
The class must always be "iconoir-" and then the name of the icon. You can find the names of the icons [here](https://iconoir.com).
The icons are `display: inline-block` and default to the current font size. You can control this
by adjusting the `::before` styles of the element (which is where the icons are added as a mask).

22
css/iconoir-regular.css Normal file

File diff suppressed because one or more lines are too long

22
css/iconoir-solid.css Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,7 +1,7 @@
# Iconoir Framer
# Iconoir - Framer
Iconoir is happily part of [Framer](https://framer.com) now. To start using the icons: On the top
menu, `Insert` > `Graphics` > `Iconoir`. You can switch between icons from the right sidebar in the
editor.
Iconoir is happily part of [Framer](https://framer.com).
<SuggestLibrary />
To start using the icons: On the top menu, `Insert` > `Graphics` > `Iconoir`.
You can switch between icons from the right sidebar in the editor.

115
eslint.config.js Normal file
View file

@ -0,0 +1,115 @@
// @ts-check
import antfu from '@antfu/eslint-config';
import nextPlugin from '@next/eslint-plugin-next';
import jsxA11yPlugin from 'eslint-plugin-jsx-a11y';
import reactPlugin from 'eslint-plugin-react';
import hooksPlugin from 'eslint-plugin-react-hooks';
export default antfu({
typescript: true,
formatters: true,
stylistic: {
semi: true,
overrides: {
'style/arrow-parens': 'error',
'style/brace-style': ['error', '1tbs', { allowSingleLine: true }],
},
},
javascript: {
overrides: {
'antfu/no-top-level-await': 'off',
},
},
ignores: [
'css/*.css',
'iconoir.com/out/',
'**/.expo/',
'packages/iconoir-flutter/.dart_tool/',
'packages/iconoir-flutter/build/',
'packages/iconoir-flutter/example/',
],
rules: {
'style/padding-line-between-statements': [
'error',
{
blankLine: 'always',
prev: [
'block',
'block-like',
'cjs-export',
'class',
'multiline-block-like',
'multiline-const',
'multiline-expression',
'multiline-let',
'multiline-var',
],
next: '*',
},
{
blankLine: 'always',
prev: ['const', 'let'],
next: [
'block',
'block-like',
'cjs-export',
'class',
],
},
{
blankLine: 'always',
prev: '*',
next: [
'multiline-block-like',
'multiline-const',
'multiline-expression',
'multiline-let',
'multiline-var',
],
},
],
},
}, {
files: ['iconoir.com/**'],
plugins: {
'@next/next': nextPlugin,
'react': reactPlugin,
'react-hooks': hooksPlugin,
'jsx-a11y': jsxA11yPlugin,
},
settings: {
next: {
rootDir: 'iconoir.com/',
},
react: {
version: 'detect',
},
},
// @ts-ignore
rules: {
...nextPlugin.configs.recommended.rules,
...nextPlugin.configs['core-web-vitals'].rules,
...reactPlugin.configs.recommended.rules,
...hooksPlugin.configs.recommended.rules,
// rules from "eslint-config-next"
'react/no-unknown-property': 'off',
'react/react-in-jsx-scope': 'off',
'react/prop-types': 'off',
'jsx-a11y/alt-text': [
'warn',
{
elements: ['img'],
img: ['Image'],
},
],
'jsx-a11y/aria-props': 'warn',
'jsx-a11y/aria-proptypes': 'warn',
'jsx-a11y/aria-unsupported-elements': 'warn',
'jsx-a11y/role-has-required-aria-props': 'warn',
'jsx-a11y/role-supports-aria-props': 'warn',
'react/jsx-no-target-blank': 'off',
},
});

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

@ -0,0 +1,41 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
.env*
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

View file

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

View file

@ -0,0 +1,32 @@
import {
Check,
Iconoir,
IconoirProvider,
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 color="red" height={36} width={36} />
<Medal1stSolid />
<AdobeAfterEffectsRegular color="red" />
<AdobeAfterEffectsSolid color="green" />
<IconoirProvider
iconProps={{
color: '#1E441E',
strokeWidth: 1,
width: '2em',
height: '2em',
}}
>
<Check />
</IconoirProvider>
</>
);
}

View file

@ -0,0 +1,9 @@
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
experimental: {
optimizePackageImports: ['iconoir-react'],
},
};
export default nextConfig;

View file

@ -0,0 +1,22 @@
{
"name": "example-next",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"next": "15.4.4",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@types/node": "^22.10.2",
"@types/react": "^19.0.1",
"@types/react-dom": "^19.0.2",
"iconoir-react": "workspace:*",
"typescript": "^5.7.2"
}
}

View file

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

36
examples/react-native/.gitignore vendored Normal file
View file

@ -0,0 +1,36 @@
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
# dependencies
node_modules/
# Expo
.expo/
dist/
web-build/
expo-env.d.ts
# Native
*.orig.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision
# Metro
.metro-health-check*
# debug
npm-debug.*
yarn-debug.*
yarn-error.*
# macOS
.DS_Store
*.pem
# local env files
.env*.local
# typescript
*.tsbuildinfo

View file

@ -0,0 +1,21 @@
import { Check, Iconoir, IconoirProvider } from 'iconoir-react-native';
import { View } from 'react-native';
export default function App() {
return (
<View>
<Iconoir />
<IconoirProvider
iconProps={{
color: '#1E441E',
strokeWidth: 1,
width: '2em',
height: '2em',
}}
>
<Check />
</IconoirProvider>
</View>
);
}

View file

@ -0,0 +1,21 @@
{
"expo": {
"name": "Iconoir",
"slug": "example-react-native",
"version": "1.0.0",
"orientation": "portrait",
"userInterfaceStyle": "light",
"newArchEnabled": true,
"splash": {
"backgroundColor": "#ffffff"
},
"ios": {
"supportsTablet": true
},
"android": {
"adaptiveIcon": {
"backgroundColor": "#ffffff"
}
}
}
}

View file

@ -0,0 +1,8 @@
import { registerRootComponent } from 'expo';
import App from './App';
// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
// It also ensures that whether you load the app in Expo Go or in a native build,
// the environment is set up appropriately
registerRootComponent(App);

View file

@ -0,0 +1,31 @@
/*
* Workaround to be able to import iconoir lib from workspace.
* See also: https://github.com/pnpm/pnpm/issues/4286
*/
const { makeMetroConfig } = require('@rnx-kit/metro-config');
const MetroSymlinksResolver = require('@rnx-kit/metro-resolver-symlinks');
const { getDefaultConfig } = require('expo/metro-config');
const symlinksResolver = MetroSymlinksResolver({
remapModule: (_context, moduleName) => {
if (moduleName === 'iconoir-react-native') {
return require.resolve(moduleName);
}
return moduleName;
},
},
);
/** @type {import('expo/metro-config').MetroConfig} */
const expoConfig = getDefaultConfig(__dirname);
/** @type {import('expo/metro-config').MetroConfig} */
module.exports = makeMetroConfig({
...expoConfig,
resolver: {
...expoConfig.resolver,
resolveRequest: symlinksResolver,
},
});

View file

@ -0,0 +1,30 @@
{
"name": "example-react-native",
"version": "1.0.0",
"private": true,
"main": "index.ts",
"scripts": {
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web"
},
"dependencies": {
"expo": "^53.0.20",
"expo-status-bar": "^2.0.0",
"iconoir-react-native": "workspace:*",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-native": "^0.79.5",
"react-native-svg": "^15.11.2",
"react-native-web": "^0.20.0"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@react-native/metro-config": "^0.79.5",
"@rnx-kit/metro-config": "^2.0.1",
"@rnx-kit/metro-resolver-symlinks": "^0.2.1",
"@types/react": "^19.0.14",
"typescript": "~5.8.3"
}
}

View file

@ -0,0 +1,6 @@
{
"extends": "expo/tsconfig.base",
"compilerOptions": {
"strict": true
}
}

30
examples/vue/.gitignore vendored Normal file
View file

@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo

1
examples/vue/env.d.ts vendored Normal file
View file

@ -0,0 +1 @@
/// <reference types="vite/client" />

12
examples/vue/index.html Normal file
View file

@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Iconoir</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

27
examples/vue/package.json Normal file
View file

@ -0,0 +1,27 @@
{
"name": "example-vue",
"type": "module",
"private": true,
"scripts": {
"dev": "vite",
"build": "run-p type-check \"build-only {@}\" --",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --build"
},
"dependencies": {
"vue": "^3.5.18"
},
"devDependencies": {
"@iconoir/vue": "workspace:*",
"@tsconfig/node22": "^22.0.2",
"@types/node": "^22.16.5",
"@vitejs/plugin-vue": "^6.0.0",
"@vue/tsconfig": "^0.7.0",
"npm-run-all2": "^8.0.4",
"typescript": "~5.8.3",
"vite": "^7.0.6",
"vite-plugin-vue-devtools": "^7.7.7",
"vue-tsc": "^3.0.4"
}
}

26
examples/vue/src/App.vue Normal file
View file

@ -0,0 +1,26 @@
<script setup lang="ts">
import {
Check,
Iconoir,
IconoirProvider,
Medal1st,
Medal1stSolid,
} from '@iconoir/vue';
</script>
<template>
<Iconoir />
<Medal1st color="red" height="36" width="36" />
<Medal1stSolid />
<IconoirProvider
:icon-props="{
'color': '#1E441E',
'stroke-width': 1,
'width': '2em',
'height': '2em',
}"
>
<Check />
</IconoirProvider>
</template>

4
examples/vue/src/main.ts Normal file
View file

@ -0,0 +1,4 @@
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');

View file

@ -0,0 +1,13 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"compilerOptions": {
"composite": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"]
}

View file

@ -0,0 +1,11 @@
{
"references": [
{
"path": "./tsconfig.node.json"
},
{
"path": "./tsconfig.app.json"
}
],
"files": []
}

View file

@ -0,0 +1,19 @@
{
"extends": "@tsconfig/node22/tsconfig.json",
"compilerOptions": {
"composite": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"module": "ESNext",
"moduleResolution": "Bundler",
"types": ["node"],
"noEmit": true
},
"include": [
"vite.config.*",
"vitest.config.*",
"cypress.config.*",
"nightwatch.conf.*",
"playwright.config.*"
]
}

View file

@ -0,0 +1,18 @@
import { fileURLToPath, URL } from 'node:url';
import vue from '@vitejs/plugin-vue';
import { defineConfig } from 'vite';
import vueDevTools from 'vite-plugin-vue-devtools';
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
vueDevTools(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
});

View file

@ -1,6 +0,0 @@
{
"extends": "next/core-web-vitals",
"rules": {
"react/no-unescaped-entities": ["off"]
}
}

0
iconoir.com/.gitignore vendored Executable file → Normal file
View file

View file

@ -1,34 +0,0 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file.
[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.

View file

@ -1,29 +1,11 @@
import React from 'react';
import styled from 'styled-components';
export function Ad() {
const containerRef = React.useRef<HTMLDivElement>(null);
const addedScript = React.useRef(false);
React.useEffect(() => {
const container = containerRef.current;
if (container && !addedScript.current) {
addedScript.current = true;
const script = document.createElement('script');
script.async = true;
script.type = 'text/javascript';
script.src =
'//cdn.carbonads.com/carbon.js?serve=CESDK5QJ&placement=iconoircom';
script.id = '_carbonads_js';
container.appendChild(script);
}
}, []);
return <AdContainer ref={containerRef} />;
}
const AdContainer = styled.div`
#carbonads {
margin: 24px 0 0 0;
a {
text-decoration: none !important;
text-decoration: none;
}
.carbon-wrap {
display: flex;
@ -31,6 +13,11 @@ const AdContainer = styled.div`
> :first-child {
margin-right: 12px;
}
& > a > img {
width: 100px;
height: 74px;
filter: grayscale(100%);
}
}
.carbon-text {
color: var(--black-80);
@ -43,3 +30,24 @@ const AdContainer = styled.div`
}
}
`;
export function Ad() {
const containerRef = React.useRef<HTMLDivElement>(null);
const addedScript = React.useRef(false);
React.useEffect(() => {
const container = containerRef.current;
if (container && !addedScript.current) {
addedScript.current = true;
const script = document.createElement('script');
script.async = true;
script.type = 'text/javascript';
script.src = '//cdn.carbonads.com/carbon.js?serve=CESDK5QJ&placement=iconoircom';
script.id = '_carbonads_js';
container.appendChild(script);
}
}, []);
return <AdContainer ref={containerRef} />;
}

View file

@ -1,7 +1,6 @@
import React from 'react';
import anime from 'animejs';
import React from 'react';
// eslint-disable-next-line no-unused-vars
type SetInstances = (instances: anime.AnimeInstance[]) => void;
function playWithLines1(setInstances: SetInstances): anime.AnimeInstance[] {
@ -11,7 +10,7 @@ function playWithLines1(setInstances: SetInstances): anime.AnimeInstance[] {
strokeDashoffset: [anime.setDashoffset, 0],
easing: 'easeInOutSine',
duration: 1500,
delay: function (el, i) {
delay(_el, i) {
return i * 250;
},
direction: 'alternate',
@ -19,6 +18,7 @@ function playWithLines1(setInstances: SetInstances): anime.AnimeInstance[] {
}),
];
}
function playWithLines2(setInstances: SetInstances): anime.AnimeInstance[] {
return [
anime({
@ -26,7 +26,7 @@ function playWithLines2(setInstances: SetInstances): anime.AnimeInstance[] {
strokeDashoffset: [anime.setDashoffset, 0],
easing: 'easeInOutSine',
duration: 1500,
delay: function (el, i) {
delay(_el, i) {
return i * 250;
},
direction: 'alternate',
@ -34,6 +34,7 @@ function playWithLines2(setInstances: SetInstances): anime.AnimeInstance[] {
}),
];
}
function playWithLines3(setInstances: SetInstances): anime.AnimeInstance[] {
return [
anime({
@ -41,7 +42,7 @@ function playWithLines3(setInstances: SetInstances): anime.AnimeInstance[] {
strokeDashoffset: [anime.setDashoffset, 0],
easing: 'easeInOutSine',
duration: 1500,
delay: function (el, i) {
delay(_el, i) {
return i * 250;
},
direction: 'alternate',
@ -58,6 +59,7 @@ function playWithLines3(setInstances: SetInstances): anime.AnimeInstance[] {
}),
];
}
function playWithLines4(setInstances: SetInstances): anime.AnimeInstance[] {
return [
anime({
@ -65,7 +67,7 @@ function playWithLines4(setInstances: SetInstances): anime.AnimeInstance[] {
strokeDashoffset: [anime.setDashoffset, 0],
easing: 'easeInOutSine',
duration: 1500,
delay: function (el, i) {
delay(_el, i) {
return i * 250;
},
direction: 'alternate',
@ -76,16 +78,19 @@ function playWithLines4(setInstances: SetInstances): anime.AnimeInstance[] {
export function AnimatedSvg() {
const instancesRef = React.useRef<anime.AnimeInstance[] | null>(null);
React.useEffect(() => {
instancesRef.current = playWithLines1((instances) => {
instancesRef.current = instances;
});
return () => {
for (const instance of instancesRef.current || []) {
instance.pause();
}
};
}, []);
return (
<svg
className="playWithLines2"

View file

@ -1,90 +1,39 @@
import React from 'react';
import styled, { keyframes } from 'styled-components';
import styled, { css, keyframes } from 'styled-components';
import useResizeObserver from 'use-resize-observer';
import { FEEDBACK_LINK, LIBRARY_LINKS, SUGGEST_LIBRARY } from './constants';
import { media } from './responsive';
import {
FEEDBACK_LINK,
LIBRARY_LINKS,
SUGGEST_LIBRARY_LINK,
} from '../lib/constants';
import { media } from '../lib/responsive';
import { Text14 } from './Typography';
export function AvailableFor() {
const { ref, width } = useResizeObserver();
return (
<>
<MobileHeader>Available For</MobileHeader>
<AvailableForOuter>
<AvailableForContainer contentWidth={width || 0} ref={ref}>
<DesktopHeader>Available for</DesktopHeader>
<a href={LIBRARY_LINKS.React} target={'_blank'} rel={'noreferrer'}>
<AvailableForImage
src={'/logo-react.svg'}
alt={'React Logo'}
title={'React'}
/>
</a>
<a href={LIBRARY_LINKS.Flutter} target={'_blank'} rel={'noreferrer'}>
<AvailableForImage
src={'/logo-flutter.svg'}
alt={'Flutter Logo'}
title={'Flutter'}
/>
</a>
<a href={LIBRARY_LINKS.Figma} target={'_blank'} rel={'noreferrer'}>
<AvailableForImage
src={'/logo-figma.svg'}
alt={'Figma Logo'}
title={'Figma'}
/>
</a>
<a
href={LIBRARY_LINKS.ReactNative}
target={'_blank'}
rel={'noreferrer'}
>
<AvailableForImage
src={'/logo-react-native.svg'}
alt={'React Native Logo'}
title={'React Native'}
/>
</a>
<a href={LIBRARY_LINKS.Framer} target={'_blank'} rel={'noreferrer'}>
<AvailableForImage
src={'/logo-framer.svg'}
alt={'Framer Logo'}
title={'Framer'}
/>
</a>
<AreYouUsing>
<a href={SUGGEST_LIBRARY} target={'_blank'} rel={'noreferrer'}>
<Text14>More?</Text14>
</a>
<a href={FEEDBACK_LINK} target={'_blank'} rel={'noreferrer'}>
<Text14>Are you using the library?</Text14>
</a>
</AreYouUsing>
</AvailableForContainer>
</AvailableForOuter>
</>
);
}
const AreYouUsing = styled.div`
* {
white-space: nowrap;
}
`;
const MobileHeader = styled(Text14)`
display: block;
margin-top: 5px;
text-align: center;
${media.lg} {
display: none;
}
`;
const DesktopHeader = styled(Text14)`
display: none;
${media.lg} {
&&& {
display: block;
margin-top: 5px;
text-align: center;
${media.lg} {
display: none;
}
}
`;
const DesktopHeader = styled(Text14)`
&&& {
display: none;
${media.lg} {
display: block;
}
}
`;
const AvailableForAnimation = keyframes`
5% {
transform: translateX(0);
@ -99,26 +48,30 @@ const AvailableForAnimation = keyframes`
transform: translateX(0);
}
`;
const AvailableForOuter = styled.div`
max-width: 100vw;
margin: 16px -30px 70px -30px;
padding: 0 30px;
overflow: hidden;
${media.lg} {
margin: 120px auto;
margin: 80px auto;
padding: 0;
}
`;
const AvailableForContainer = styled.div<{ contentWidth: number }>`
const AvailableForContainer = styled.div<{ $contentWidth: number }>`
display: flex;
align-items: center;
justify-content: flex-start;
width: max-content;
--content-width: ${(props) => props.contentWidth}px;
${(props) => (props.contentWidth ? '&' : '&.noop')} {
animation: ${AvailableForAnimation} 40s cubic-bezier(0.37, 0, 0.63, 1)
infinite;
}
--content-width: ${(props) => props.$contentWidth}px;
${(props) =>
props.$contentWidth
&& css`
animation: ${AvailableForAnimation} 40s cubic-bezier(0.37, 0, 0.63, 1)
infinite;
`}
> :not(:last-child) {
margin-right: 30px;
}
@ -129,14 +82,124 @@ const AvailableForContainer = styled.div<{ contentWidth: number }>`
}
${media.md} {
> :not(:last-child) {
margin-right: 60px;
margin-right: 40px;
}
}
`;
const AvailableForImage = styled.img`
height: 40px;
display: block;
transition: 0.2s;
&:hover {
scale: 1.2;
transition: 0.2s;
}
${media.lg} {
height: 50px;
}
`;
export function AvailableFor() {
const { ref, width } = useResizeObserver();
return (
<>
<MobileHeader>Available For</MobileHeader>
<AvailableForOuter>
<AvailableForContainer $contentWidth={width || 0} ref={ref}>
<DesktopHeader>Available for</DesktopHeader>
<a href={LIBRARY_LINKS.React} target="_blank" rel="noreferrer">
<AvailableForImage
src="/logo-react.svg"
alt="React Logo"
title="React"
/>
</a>
<a
href="https://github.com/iconoir-icons/iconoir#swift-package"
target="_blank"
rel="nofollow noreferrer"
>
<AvailableForImage
src="/logo-swift.svg"
alt="Swift Logo"
title="Swift"
/>
</a>
<a
href={LIBRARY_LINKS.Flutter}
target="_blank"
rel="nofollow noreferrer"
>
<AvailableForImage
src="/logo-flutter.svg"
alt="Flutter Logo"
title="Flutter"
/>
</a>
<a
href={LIBRARY_LINKS.Figma}
target="_blank"
rel="nofollow noreferrer"
>
<AvailableForImage
src="/logo-figma.svg"
alt="Figma Logo"
title="Figma"
/>
</a>
<a
href={LIBRARY_LINKS.ReactNative}
target="_blank"
rel="nofollow noreferrer"
>
<AvailableForImage
src="/logo-react-native.svg"
alt="React Native Logo"
title="React Native"
/>
</a>
<a
href={LIBRARY_LINKS.Vue}
target="_blank"
rel="nofollow noreferrer"
>
<AvailableForImage
src="/logo-vue.svg"
alt="Vue Logo"
title="Vue"
/>
</a>
<a
href={LIBRARY_LINKS.Framer}
target="_blank"
rel="nofollow noreferrer"
>
<AvailableForImage
src="/logo-framer.svg"
alt="Framer Logo"
title="Framer"
/>
</a>
<AreYouUsing>
<a
href={SUGGEST_LIBRARY_LINK}
target="_blank"
rel="nofollow noreferrer"
>
<Text14>More?</Text14>
</a>
<a
href={FEEDBACK_LINK}
target="_blank"
rel="nofollow noreferrer"
>
<Text14>Are you using the library?</Text14>
</a>
</AreYouUsing>
</AvailableForContainer>
</AvailableForOuter>
</>
);
}

View file

@ -12,55 +12,74 @@ export const ResetButton = styled.button`
`;
export const LargeButton = styled(ResetButton)`
background: var(--black);
height: 75px;
display: inline-flex;
align-items: center;
text-decoration: none;
color: var(--white);
padding: 0 43px;
font-size: 20px;
line-height: 26px;
font-weight: 700;
position: relative;
cursor: pointer;
z-index: 12;
> :not(:last-child) {
margin-right: 15px;
}
* {
&&& {
background: var(--white);
height: 75px;
border-radius: 10px 50px 50px 50px;
border: solid 2px var(--g0);
box-shadow: 0px 8px 0px 0px var(--g0);
display: inline-flex;
align-items: center;
text-decoration: none;
color: var(--g0);
padding: 0 70px;
font-size: 20px;
line-height: 26px;
font-weight: 700;
}
&::after {
content: ' ';
display: block;
position: absolute;
inset: 0;
border: solid 3px var(--black);
opacity: 0;
transition: inset 0.5s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.15s linear;
z-index: 10;
}
&:focus::after,
&:hover::after {
inset: -7px;
opacity: 1;
position: relative;
cursor: pointer;
z-index: 12;
> :not(:last-child) {
margin-right: 15px;
}
* {
font-weight: 700;
}
transition: 0.2s;
&:hover {
box-shadow: 0px 3px 0px 0px var(--g0);
transform: translateY(5px);
}
&:focus::after,
&:hover::after {
inset: -7px;
opacity: 1;
}
}
`;
export const Button = styled(LargeButton)`
height: 40px;
font-size: 13px;
line-height: 21px;
padding: 0 18px;
&::after {
border-width: 2px;
}
&:focus::after,
&:hover::after {
inset: -4px;
}
&:active {
background: var(--darker-gray);
const Button = styled(LargeButton)`
&&&& {
height: 40px;
font-size: 13px;
line-height: 21px;
padding: 0 18px;
&::after {
border-width: 2px;
}
&:focus::after,
&:hover::after {
inset: -4px;
}
&:active {
background: var(--darker-gray);
}
}
`;
export const CopyButton = styled(Button)`
&&&&& {
text-transform: uppercase;
background: var(--white);
height: 30px;
padding: 0 12px;
font-size: 11px;
letter-spacing: 0.12em;
line-height: 17.6px;
font-weight: 700;
color: var(--black);
font-family: var(--font-family);
}
`;

View file

@ -1,27 +1,7 @@
import React from 'react';
import styled from 'styled-components';
import { media } from './responsive';
import { media } from '../lib/responsive';
import { Text15 } from './Typography';
export interface CategoryRowProps {
category: string;
numIcons: number;
style?: any;
}
export function CategoryRow({ category, numIcons, style }: CategoryRowProps) {
return (
<Container style={style}>
<InnerContainer>
<Title>{category}</Title>
<Text15>
{numIcons} Icon{numIcons === 1 ? '' : 's'}
</Text15>
<Separator />
</InnerContainer>
</Container>
);
}
const InnerContainer = styled.div`
display: flex;
align-items: center;
@ -33,6 +13,7 @@ const InnerContainer = styled.div`
margin-right: 10px;
}
`;
const Container = styled.div`
display: flex;
align-items: flex-end;
@ -42,12 +23,42 @@ const Container = styled.div`
padding-bottom: 40px;
}
`;
const Title = styled(Text15)`
font-weight: 700;
color: var(--black);
&&& {
font-weight: 700;
color: var(--g0);
background-color: var(--g6);
border-radius: 8px;
padding: 6px 10px;
}
`;
const Separator = styled.div`
height: 1px;
flex: 1;
background: var(--light-gray);
background: var(--g6);
`;
export interface CategoryRowProps {
category: string;
numIcons: number;
style?: any;
}
export function CategoryRow({ category, numIcons, style }: CategoryRowProps) {
return (
<Container style={style}>
<InnerContainer>
<Title>{category}</Title>
<Text15>
{numIcons}
{' '}
Icon
{numIcons === 1 ? '' : 's'}
</Text15>
<Separator />
</InnerContainer>
</Container>
);
}

View file

@ -1,70 +1,17 @@
import type { MDXRemoteSerializeResult } from 'next-mdx-remote';
import { BoxIso } from 'iconoir-react';
import moment from 'moment';
import React from 'react';
import styled from 'styled-components';
import { Code, CopyButton, Text15, Text18 } from './Typography';
import moment from 'moment';
import { MDXRemoteSerializeResult } from 'next-mdx-remote';
import { media } from '../lib/responsive';
import { CopyButton } from './Button';
import { MDXRemote } from './MDXRemote';
import { FILE_PREFIX } from './constants';
import { media } from './responsive';
import { Code, Text15, Text18 } from './Typography';
const EXPAND_HEIGHT = 400;
export interface ChangelogEntryProps {
name: string;
body: MDXRemoteSerializeResult;
created_at: string;
}
export function ChangelogEntry({
name,
body,
created_at,
}: ChangelogEntryProps) {
const [expanded, setExpanded] = React.useState(false);
const [shouldExpand, setShouldExpand] = React.useState(false);
const containerRef = React.useRef<HTMLDivElement>(null);
React.useEffect(() => {
if (
containerRef.current &&
containerRef.current.clientHeight > EXPAND_HEIGHT
) {
setShouldExpand(true);
}
}, []);
return (
<Container ref={containerRef}>
<ContainerLeft>
<ContainerIcon>
<BoxIso />
</ContainerIcon>
<TitleContainer>
<a
href={`${FILE_PREFIX}/../../releases/tag/${name}`}
target={'_blank'}
rel={'noreferrer'}
style={{ textDecoration: 'none' }}
>
<EntryTitle>{name}</EntryTitle>
</a>
<Text15>{moment(created_at).format('MMM DD, YYYY')}</Text15>
</TitleContainer>
</ContainerLeft>
<EntryBody expanded={expanded}>
<MDXRemote {...body} />
{shouldExpand ? (
<ExpandContainer>
<CopyButton onClick={() => setExpanded((e) => !e)}>
{expanded ? 'Collapse' : 'Expand'}
</CopyButton>
</ExpandContainer>
) : null}
</EntryBody>
</Container>
);
}
const Container = styled.div`
margin: 40px 0 !important;
margin: 40px 0;
display: flex;
align-items: stretch;
flex-direction: column;
@ -72,9 +19,10 @@ const Container = styled.div`
${media.lg} {
flex-direction: row;
align-items: flex-start;
margin: 24px 0 !important;
margin: 24px 0;
}
`;
const ContainerLeft = styled.div`
display: flex;
align-items: flex-start;
@ -84,46 +32,115 @@ const ContainerLeft = styled.div`
margin-right: 30px;
}
`;
const ContainerIcon = styled.div`
font-size: 18px;
color: var(--black);
margin-right: 18px;
`;
const TitleContainer = styled.div`
width: 100px;
`;
const EntryTitle = styled(Text18)`
color: var(--black);
font-weight: 700;
&&& {
color: var(--black);
font-weight: 700;
}
`;
const ExpandContainer = styled.div`
position: absolute;
bottom: 16px;
right: 23px;
`;
const EntryBody = styled(Code)<{ expanded?: boolean }>`
flex: 1;
margin: 0;
max-height: ${(props) => (props.expanded ? 'none' : `${EXPAND_HEIGHT}px`)};
position: relative;
overflow: hidden;
* {
font-family: var(--code-family);
}
ul {
list-style: none none;
const EntryBody = styled(Code)<{ $expanded?: boolean }>`
&&& {
flex: 1;
margin: 0;
padding: 0;
li {
margin-bottom: 8px;
max-height: ${(props) => (props.$expanded ? 'none' : `${EXPAND_HEIGHT}px`)};
position: relative;
overflow: hidden;
* {
font-family: var(--code-family);
}
ul {
list-style: none none;
margin: 0;
padding: 0;
li {
margin-bottom: 8px;
}
}
code {
display: inline-block;
background: var(--g5);
color: var(--black);
font-family: var(--font-family) !important;
padding: 0 4px;
font-size: 18px;
}
}
code {
display: inline-block;
background: var(--gray) !important;
color: var(--black);
font-family: var(--font-family) !important;
padding: 0 4px;
font-size: 18px !important;
}
`;
export interface ChangelogEntryProps {
name: string;
url: string;
created_at: string;
body?: MDXRemoteSerializeResult;
}
export function ChangelogEntry({
name,
url,
body,
created_at,
}: ChangelogEntryProps) {
const [expanded, setExpanded] = React.useState(false);
const [shouldExpand, setShouldExpand] = React.useState(false);
const containerRef = React.useRef<HTMLDivElement>(null);
React.useEffect(() => {
if (
containerRef.current
&& containerRef.current.clientHeight > EXPAND_HEIGHT
) {
setShouldExpand(true);
}
}, []);
return (
<Container ref={containerRef}>
<ContainerLeft>
<ContainerIcon>
<BoxIso />
</ContainerIcon>
<TitleContainer>
<a
href={url}
target="_blank"
rel="noreferrer"
style={{ textDecoration: 'none' }}
>
<EntryTitle>{name}</EntryTitle>
</a>
<Text15>{moment(created_at).format('MMM DD, YYYY')}</Text15>
</TitleContainer>
</ContainerLeft>
<EntryBody $expanded={expanded}>
{body ? <MDXRemote {...body} /> : 'No changelog'}
{shouldExpand
? (
<ExpandContainer>
<CopyButton onClick={() => setExpanded((e) => !e)}>
{expanded ? 'Collapse' : 'Expand'}
</CopyButton>
</ExpandContainer>
)
: null}
</EntryBody>
</Container>
);
}

View file

@ -1,29 +1,35 @@
import Link from 'next/link';
import React from 'react';
import styled from 'styled-components';
import { Text13 } from './Typography';
export interface CurrentVersionProps {
version: string;
color?: string;
}
export function CurrentVersion({ version, color }: CurrentVersionProps) {
return (
<Link href={'/docs/changelog'} passHref>
<Container as={'a'} style={color ? { background: color } : undefined}>
{version}
</Container>
</Link>
);
}
const Container = styled(Text13)`
color: var(--black);
&&& {
color: var(--g1);
font-weight: 700;
background: var(--pink);
background: var(--g5);
line-height: 1;
padding: 7px 16px;
border-radius: 200px;
display: block;
text-decoration: none !important;
text-decoration: none;
transition:
color 0.1s linear,
background 0.1s linear;
&:hover {
background: var(--black);
color: var(--white);
}
}
`;
export interface CurrentVersionProps {
version: string;
}
export function CurrentVersion({ version }: CurrentVersionProps) {
return (
<Link href="/docs/changelog" passHref legacyBehavior>
<Container as="a">{version}</Container>
</Link>
);
}

View file

@ -1,16 +1,62 @@
import type { IconListCustomizations } from './IconList';
import React from 'react';
import { DEFAULT_CUSTOMIZATIONS, IconListCustomizations } from './IconList';
import styled from 'styled-components';
import { Text13, Text18 } from './Typography';
import { Button } from './Button';
import { ColorInput } from './Input';
import { media } from '../lib/responsive';
import { DEFAULT_CUSTOMIZATIONS } from './IconList';
import { ColorButton, ColorInput } from './Input';
import { Slider } from './Slider';
import { Text13, Text15 } from './Typography';
const CustomizationBox = styled.div`
background-color: var(--g7);
width: 84%;
padding: 8%;
border-radius: 10px;
margin: 24px 0;
display: none;
${media.md} {
display: block;
}
`;
const Header = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 30px;
border-bottom: solid 1px var(--g6);
padding-bottom: 10px;
`;
const Field = styled.div`
margin-bottom: 18px;
`;
const HorizontalField = styled(Field)`
display: flex;
align-items: center;
justify-content: space-between;
`;
const ResetButton = styled(Field)`
&&& {
margin: initial;
text-decoration: underline;
color: var(--dark-gray);
font-size: 13px;
&:hover {
color: var(--black);
cursor: pointer;
}
}
`;
export interface CustomizationEditorProps {
customizations: IconListCustomizations;
// eslint-disable-next-line no-unused-vars
onChange: (customizations: IconListCustomizations) => void;
}
export function CustomizationEditor({
customizations,
onChange,
@ -18,9 +64,11 @@ export function CustomizationEditor({
const [, startTransition] = (React as any).useTransition();
const [color, setColor] = React.useState(customizations.hexColor);
const [size, setSize] = React.useState(customizations.size);
const [strokeWidth, setStrokeWidth] = React.useState(
customizations.strokeWidth
customizations.strokeWidth,
);
React.useEffect(() => {
setColor(customizations.hexColor);
setSize(customizations.size);
@ -38,65 +86,55 @@ export function CustomizationEditor({
return (
<>
<Header>
<Text18 style={{ fontWeight: 700, color: 'var(--black)' }}>
Customize
</Text18>
<Button onClick={() => onChange(DEFAULT_CUSTOMIZATIONS)}>Reset</Button>
</Header>
<Field>
<Slider
label={'Size'}
minValue={12}
maxValue={128}
value={[size]}
formatOptions={{ maximumFractionDigits: 0 }}
onChange={(values) => {
setSize(values[0]);
updateCustomizations({ size: values[0] });
}}
/>
</Field>
<Field>
<Slider
label={'Stroke Width'}
minValue={0.5}
maxValue={3}
value={[strokeWidth]}
step={0.01}
formatOptions={{ maximumFractionDigits: 1 }}
onChange={(values) => {
setStrokeWidth(values[0]);
updateCustomizations({ strokeWidth: values[0] });
}}
/>
</Field>
<HorizontalField>
<Text13>Color</Text13>
<ColorInput
type={'color'}
value={color}
onChange={(e) => {
setColor(e.target.value);
updateCustomizations({ hexColor: e.target.value });
}}
/>
</HorizontalField>
<CustomizationBox>
<Header>
<Text15 style={{ fontWeight: 700, color: 'var(--black)' }}>
Customize
</Text15>
<ResetButton onClick={() => onChange(DEFAULT_CUSTOMIZATIONS)}>
Reset
</ResetButton>
</Header>
<Field>
<Slider
label="Optical Size"
minValue={16}
maxValue={64}
value={[size]}
formatOptions={{ maximumFractionDigits: 0 }}
onChange={(values) => {
setSize(values[0]);
updateCustomizations({ size: values[0] });
}}
/>
</Field>
<Field>
<Slider
label="Stroke Weight"
minValue={0.5}
maxValue={3}
value={[strokeWidth]}
step={0.1}
formatOptions={{ maximumFractionDigits: 1 }}
onChange={(values) => {
setStrokeWidth(values[0]);
updateCustomizations({ strokeWidth: values[0] });
}}
/>
</Field>
<HorizontalField>
<Text13>Color</Text13>
<ColorInput
type="color"
value={color}
onChange={(e) => {
setColor(e.target.value);
updateCustomizations({ hexColor: e.target.value });
}}
/>
<ColorButton />
</HorizontalField>
</CustomizationBox>
</>
);
}
const Header = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 45px;
`;
const Field = styled.div`
margin-bottom: 35px;
`;
const HorizontalField = styled(Field)`
display: flex;
align-items: center;
justify-content: space-between;
`;

View file

@ -1,95 +1,15 @@
import React from 'react';
import { DocumentationItem } from '../pages/docs/[...slug]';
import styled from 'styled-components';
import type { DocumentationItem } from '../pages/docs/[...slug]';
import { NavArrowUp } from 'iconoir-react';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { media } from './responsive';
import { NavArrowUp } from 'iconoir-react';
import React from 'react';
import styled from 'styled-components';
import { media } from '../lib/responsive';
export interface DocumentationNavigationProps {
documentationItems: DocumentationItem[];
pathPrefix?: string[];
}
export function DocumentationNavigation({
documentationItems,
pathPrefix,
}: DocumentationNavigationProps) {
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) => {
return activePath.startsWith(
[item.path, child.path].filter(Boolean).join('/')
);
})
);
});
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];
});
}}
>
<HeaderItemIcon active={active}>
<NavArrowUp />
</HeaderItemIcon>
{documentationItem.title}
</HeaderItem>
<ChildrenContainer expanded={active}>
<DocumentationNavigation
documentationItems={documentationItem.children}
pathPrefix={[
...(pathPrefix || []),
documentationItem.path,
].filter(Boolean)}
/>
</ChildrenContainer>
</React.Fragment>
);
} else {
return (
<Link href={`/docs/${path}`} passHref key={documentationItem.path}>
<NavigationItem as={'a'} active={activePath === path}>
<span>{documentationItem.title}</span>
{documentationItem.label ? (
<NavigationItemLabel>
{documentationItem.label}
</NavigationItemLabel>
) : null}
</NavigationItem>
</Link>
);
}
})}
</>
);
}
const HeaderItemIcon = styled.div<{ active?: boolean }>`
const HeaderItemIcon = styled.div<{ $active?: boolean }>`
font-size: 13px;
transition: transform 0.25s linear;
transform: rotate(${(props) => (props.active ? 180 : 0)}deg);
transform: rotate(${(props) => (props.$active ? 180 : 0)}deg);
margin-right: 7px;
position: relative;
top: 6px;
@ -100,19 +20,19 @@ const HeaderItemIcon = styled.div<{ active?: boolean }>`
display: none;
}
`;
const ChildrenContainer = styled.div<{ expanded?: boolean }>`
display: ${(props) => (props.expanded ? 'block' : 'none')};
const ChildrenContainer = styled.div<{ $expanded?: boolean }>`
display: ${(props) => (props.$expanded ? 'block' : 'none')};
${media.lg} {
display: block;
}
`;
const HeaderItem = styled.div`
padding: 10px 30px;
text-transform: uppercase;
font-size: 12px;
font-size: 15px;
line-height: 19px;
color: var(--black);
letter-spacing: 0.12em;
color: var(--g0);
font-weight: 700;
display: flex;
align-items: baseline;
@ -126,45 +46,144 @@ const HeaderItem = styled.div`
}
}
`;
const NavigationItem = styled.div<{ active?: boolean }>`
padding: 12px 45px 12px 75px;
transition: background 0.1s linear, color 0.1s linear;
font-weight: 500;
const NavigationItem = styled.div<{ $active?: boolean }>`
padding: 12px 12px 12px 75px;
transition:
background 0.1s linear,
color 0.1s linear;
font-size: 16px;
line-height: 14.5px;
letter-spacing: -0.02em;
color: var(--black-60);
color: var(--g1);
display: flex;
align-items: center;
text-decoration: none;
span {
font-weight: 500;
font-weight: 400;
}
> :not(:last-child) {
margin-right: 14px;
}
&:hover,
${(props) => (props.active ? '&' : '&.noop')} {
background: var(--light-gray);
color: var(--black);
${(props) => (props.$active ? '&' : '&.noop')} {
color: var(--g0);
text-decoration: underline;
}
${(props) => (props.active ? 'span' : '&.noop')} {
font-weight: 700;
${(props) => (props.$active ? 'span' : '&.noop')} {
font-weight: 500;
}
${media.lg} {
padding: 12px 45px 12px 65px;
padding: 12px 12px 12px 65px;
}
`;
const NavigationItemLabel = styled.span`
display: flex;
align-items: center;
justify-content: center;
padding: 0 4px;
padding: 2px 6px;
border-radius: 4px;
font-size: 11px;
line-height: 17.6px;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--white);
background: var(--black);
color: var(--g1);
background: var(--g5);
`;
export interface DocumentationNavigationProps {
documentationItems: DocumentationItem[];
pathPrefix?: string[];
}
export function DocumentationNavigation({
documentationItems,
pathPrefix,
}: DocumentationNavigationProps) {
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) => {
return activePath.startsWith(
[item.path, child.path].filter(Boolean).join('/'),
);
})
);
});
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];
});
}}
>
<HeaderItemIcon $active={active}>
<NavArrowUp />
</HeaderItemIcon>
{documentationItem.title}
</HeaderItem>
<ChildrenContainer $expanded={active}>
<DocumentationNavigation
documentationItems={documentationItem.children}
pathPrefix={[
...(pathPrefix || []),
documentationItem.path,
].filter(Boolean)}
/>
</ChildrenContainer>
</React.Fragment>
);
} else {
return (
<Link
href={`/docs/${path}`}
passHref
legacyBehavior
key={documentationItem.path}
>
<NavigationItem as="a" $active={activePath === path}>
<span>{documentationItem.title}</span>
{documentationItem.label
? (
<NavigationItemLabel>
{documentationItem.label}
</NavigationItemLabel>
)
: null}
</NavigationItem>
</Link>
);
}
})}
</>
);
}

View file

@ -0,0 +1,96 @@
import { Sparks } from 'iconoir-react';
import React, { useEffect, useRef, useState } from 'react';
import styled from 'styled-components';
import { media } from '../lib/responsive';
const Text = styled.span`
color: #ffffffba;
`;
const PopupContent = styled.div`
display: none;
${media.lg} {
display: flex;
align-items: center;
justify-content: center;
padding: 14px 0;
background: #8330c6;
color: var(--white);
font-weight: 500;
font-size: 15px;
position: fixed;
top: 16px;
left: 16px;
right: 16px;
border-radius: 8px;
z-index: 9999;
margin: auto;
}
> * {
margin: 0 4px;
}
> a {
color: var(--white);
opacity: 1;
text-decoration: underline;
}
`;
const CloseButton = styled.span`
color: var(--white);
float: right;
font-size: 28px;
font-weight: bold;
&:hover,
&:focus {
color: black;
text-decoration: none;
cursor: pointer;
}
`;
export function DonationPopup() {
const [isVisible, setIsVisible] = useState(false);
const isInitialMount = useRef(true);
useEffect(() => {
if (isInitialMount.current) {
isInitialMount.current = false;
const isReturningUser = localStorage.getItem('returningUser');
const isReturningUserAge = localStorage.getItem('returningUserAge');
if (isReturningUser === 'true' && isReturningUserAge === 'false') {
setTimeout(() => setIsVisible(true), 15000);
localStorage.setItem('returningUserAge', 'true');
} else {
localStorage.setItem('returningUser', 'true');
localStorage.setItem('returningUserAge', 'false');
}
}
}, []);
const handleClose = () => {
setIsVisible(false);
};
return (
<>
{isVisible && (
<PopupContent>
<Sparks></Sparks>
<Text>
Your one-time or recurring contribution does a lot to keep Iconoir
going.
</Text>
<a
href="https://opencollective.com/iconoir/donate?interval=month&amount=10"
target="_blank"
>
Support the project!
</a>
<CloseButton onClick={handleClose}>&times;</CloseButton>
</PopupContent>
)}
</>
);
}

View file

@ -1,23 +1,72 @@
import type { Icon, IconListFilters } from './IconList';
import { IconoirProvider } from 'iconoir-react';
import React from 'react';
import styled from 'styled-components';
import { Ad } from './Ad';
import { media } from '../lib/responsive';
import { CustomizationEditor } from './CustomizationEditor';
import { FiltersEditor } from './FiltersEditor';
import { Icon, IconList, IconListFilters } from './IconList';
import { media } from './responsive';
import { IconList } from './IconList';
import { Streamline } from './Streamline';
import { useCustomizationPersistence } from './useCustomizationPersistence';
const Container = styled.div`
display: flex;
flex-direction: column-reverse;
${media.md} {
align-items: flex-start;
flex-direction: row;
}
`;
const Left = styled.div`
flex: 1;
min-height: calc(100vh - 100px);
background: white;
${media.md} {
background: none;
}
`;
const Right = styled.div`
position: sticky;
top: 20px;
width: 275px;
display: block;
z-index: -1;
margin: 110px auto;
${media.md} {
margin-left: 68px;
z-index: 1;
}
`;
const FilterContainer = styled.div<{ $isMobile?: boolean }>`
display: ${(props) => (props.$isMobile ? 'block' : 'none')};
margin-bottom: 40px;
position: sticky;
top: 20px;
z-index: 100;
width: 100%;
${media.md} {
position: relative;
top: 0;
display: ${(props) => (props.$isMobile ? 'none' : 'block')};
margin-bottom: 10px;
}
`;
export interface ExploreProps {
allIcons: Icon[];
}
export function Explore({ allIcons }: ExploreProps) {
const [filters, setFilters] = React.useState<IconListFilters>({});
const [customizations, setCustomizations] = useCustomizationPersistence();
return (
<Container>
<Left>
<FilterContainer isMobile>
<FilterContainer $isMobile>
<FiltersEditor filters={filters} onChange={setFilters} />
</FilterContainer>
<IconoirProvider
@ -37,45 +86,12 @@ export function Explore({ allIcons }: ExploreProps) {
<FilterContainer>
<FiltersEditor filters={filters} onChange={setFilters} />
</FilterContainer>
<Streamline />
<CustomizationEditor
customizations={customizations}
onChange={setCustomizations}
/>
<Ad />
</Right>
</Container>
);
}
const Container = styled.div`
display: flex;
align-items: flex-start;
flex-direction: row;
`;
const Left = styled.div`
flex: 1;
min-height: calc(100vh - 100px);
`;
const Right = styled.div`
position: sticky;
top: 50px;
width: 275px;
margin-left: 68px;
display: none;
${media.md} {
display: block;
}
`;
const FilterContainer = styled.div<{ isMobile?: boolean }>`
display: ${(props) => (props.isMobile ? 'block' : 'none')};
margin-bottom: 40px;
position: sticky;
top: 20px;
z-index: 100;
${media.md} {
position: relative;
top: 0;
display: ${(props) => (props.isMobile ? 'none' : 'block')};
margin-bottom: 50px;
}
`;

View file

@ -1,15 +1,33 @@
import type { IconListFilters } from './IconList';
import React from 'react';
import { IconListFilters } from './IconList';
import { LargeInput } from './Input';
export interface FiltersEditorProps {
filters: IconListFilters;
// eslint-disable-next-line no-unused-vars
onChange: (filters: IconListFilters) => void;
}
export function FiltersEditor({ filters, onChange }: FiltersEditorProps) {
const [, startTransition] = (React as any).useTransition();
const [search, setSearch] = React.useState(filters.search);
// 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);
};
}, []);
React.useEffect(() => {
setSearch(filters.search);
}, [filters]);
@ -25,10 +43,19 @@ export function FiltersEditor({ filters, onChange }: FiltersEditorProps) {
return (
<LargeInput
placeholder={'Search...'}
placeholder="Search..."
value={search}
type={'search'}
autoCapitalize={'none'}
type="search"
autoCapitalize="none"
tabIndex={1}
onFocus={(e) => {
if (!didScrollRef.current) {
e.target.scrollIntoView({
block: 'start',
behavior: 'auto',
});
}
}}
onChange={(e) => {
const value = e.target.value;
setSearch(value);

View file

@ -1,42 +1,165 @@
import { PeaceHand } from 'iconoir-react';
import React from 'react';
import styled from 'styled-components';
import { LICENSE_LINK } from './constants';
import {
GITHUB_LINK,
ISSUE_LINK,
LICENSE_LINK,
PRIVACY_LINK,
SUPPORT_LINK,
} from '../lib/constants';
import { Logo, LogoContainer, LogoIcon } from './Header';
import { NavigationItemContainer } from './NavigationItem';
export function Footer() {
return (
<Container>
<LogoContainer>
<LogoIcon>
<PeaceHand />
</LogoIcon>
<Logo src={'/iconoir-logo.svg'} alt={'Iconoir Logo'} />
</LogoContainer>
<FooterNavigationItem
as={'a'}
href={LICENSE_LINK}
target={'_blank'}
rel={'noreferrer'}
>
License
</FooterNavigationItem>
</Container>
);
}
import { Text13, Text17 } from './Typography';
const Container = styled.div`
margin-top: 100px;
display: block;
margin-top: 110px;
padding-top: 30px;
border-top: solid 2px var(--light-gray);
display: flex;
margin-top: 100px;
padding: 84px 12%;
background-color: var(--g7);
align-items: center;
> :not(:last-child) {
margin-right: 50px;
}
`;
const FooterNavigationItem = styled(NavigationItemContainer)`
color: var(--black);
const FooterEnd = styled.div`
border-top: 1px solid var(--g5);
padding-top: 20px;
margin-top: 74px;
width: 100%;
display: flex;
justify-content: space-between;
`;
const FooterCategories = styled.div`
width: 100%;
margin-top: 54px;
display: flex;
`;
const FooterCategoryContainer = styled.div`
width: 28%;
margin-right: 20px;
`;
const FooterCategoryTitle = styled(Text17)`
&&& {
margin-bottom: 24px;
font-weight: 600;
display: block;
color: var(--g0);
}
`;
const FooterCategoryLinks = styled.div``;
const FooterCategoryLink = styled.a`
display: block;
font-size: 17px;
color: var(--g1);
width: fit-content;
margin-bottom: 12px;
text-decoration: none;
&:hover {
text-decoration: underline;
color: var(--g0);
}
`;
interface FooterCategoryProps {
category: string;
links: { name: string; url: string }[];
}
function FooterCategory({ category, links }: FooterCategoryProps) {
return (
<FooterCategoryContainer>
<FooterCategoryTitle>{category}</FooterCategoryTitle>
<FooterCategoryLinks>
{links.map((link) => (
<FooterCategoryLink key={link.url} href={link.url}>
{link.name}
</FooterCategoryLink>
))}
</FooterCategoryLinks>
</FooterCategoryContainer>
);
}
export function Footer() {
const year = new Date().getFullYear();
return (
<Container>
<LogoContainer>
<LogoIcon>
<PeaceHand />
</LogoIcon>
<Logo src="/iconoir-logo.svg" alt="Iconoir Logo" />
</LogoContainer>
<FooterCategories>
<FooterCategory
category="Project"
links={[
{ name: 'Our Mission', url: '/support' },
{ name: 'Contribute', url: '/docs/contributing' },
{
name: 'Donate',
url: SUPPORT_LINK,
},
]}
/>
<FooterCategory
category="Support"
links={[
{
name: 'License',
url: LICENSE_LINK,
},
{
name: 'GitHub Repository',
url: GITHUB_LINK,
},
{
name: 'File a Request',
url: ISSUE_LINK,
},
]}
/>
<FooterCategory
category="Developers"
links={[
{ name: 'Changelog', url: '/docs/changelog' },
{
name: 'React and React Native',
url: '/docs/packages/iconoir-react',
},
{ name: 'Flutter', url: '/docs/packages/iconoir-flutter' },
{ name: 'Framer and Figma', url: '/docs/packages/framer' },
{ name: 'CSS', url: '/docs/packages/css' },
]}
/>
</FooterCategories>
<FooterEnd>
<Text13 style={{ fontWeight: 400 }}>
Parts of this content are &copy;2020-
{year}
{' '}
by individual Iconoir
contributors. Content available under a
{' '}
<a href={LICENSE_LINK} target="_blank" rel="nofollow noreferrer">
MIT License
</a>
.
</Text13>
<Text13 style={{ fontWeight: 400 }}>
<a href={PRIVACY_LINK} target="_blank" rel="nofollow noreferrer">
Privacy
</a>
</Text13>
</FooterEnd>
</Container>
);
}

View file

@ -1,4 +1,3 @@
import React from 'react';
import Script from 'next/script';
export function GA() {
@ -6,9 +5,9 @@ export function GA() {
<>
<Script
src="https://www.googletagmanager.com/gtag/js?id=UA-33344001-9"
strategy={'afterInteractive'}
strategy="afterInteractive"
/>
<Script id={'google-analytics'} strategy={'afterInteractive'}>
<Script id="google-analytics" strategy="afterInteractive">
{`
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}

View file

@ -1,95 +1,80 @@
import React from 'react';
import styled from 'styled-components';
import { AUTHOR_LINKS } from './constants';
import { Cancel, Heart, Menu } from 'iconoir-react';
import { CurrentVersion } from './CurrentVersion';
import { media } from './responsive';
import { ResetButton } from './Button';
import { AnimatedSvg } from './AnimatedSvg';
import { NavigationItem, NavigationItemContainer } from './NavigationItem';
import { Discord, Menu, Sparks, Xmark } from 'iconoir-react';
import { Heart } from 'iconoir-react/solid';
import Link from 'next/link';
import React from 'react';
import styled, { css } from 'styled-components';
import { DISCORD_LINK, SHARE_LINK } from '../lib/constants';
import { media } from '../lib/responsive';
import { AnimatedSvg } from './AnimatedSvg';
import { ResetButton } from './Button';
import { CurrentVersion } from './CurrentVersion';
import { NavigationItem } from './NavigationItem';
import { Text15 } from './Typography';
export interface HeaderProps {
currentVersion: string;
currentVersionColor?: string;
}
export function Header({ currentVersion, currentVersionColor }: HeaderProps) {
const [menuVisible, setMenuVisible] = React.useState(false);
return (
<Container>
<HeaderLeft>
<Link href={'/'}>
<a>
<LogoContainer>
<LogoIcon>
<AnimatedSvg />
</LogoIcon>
<Logo src={'/iconoir-logo.svg'} alt={'Iconoir Logo'} />
</LogoContainer>
</a>
</Link>
<CurrentVersion version={currentVersion} color={currentVersionColor} />
</HeaderLeft>
<HeaderCenter>
<MobileMenuContainer visible={menuVisible}>
<NavigationItem href={'/'}>Icons</NavigationItem>
<NavigationItem href={'/docs'}>Documentation</NavigationItem>
<NavigationItem href={'/support'} style={{ marginRight: 0 }}>
Donate &mdash; Our Mission
</NavigationItem>
<BuiltWith isMobile>
Made with <Heart width={'1em'} height={'1em'} /> by{' '}
<a href={AUTHOR_LINKS.Luca} target={'_blank'} rel={'noreferrer'}>
Luca
</a>{' '}
&amp;{' '}
<a href={AUTHOR_LINKS.Sam} target={'_blank'} rel={'noreferrer'}>
Sam
</a>
</BuiltWith>
</MobileMenuContainer>
</HeaderCenter>
<HeaderRight>
<BuiltWith>
Designed and built with <Heart width={'1em'} height={'1em'} /> by{' '}
<a href={AUTHOR_LINKS.Luca} target={'_blank'} rel={'noreferrer'}>
Luca
</a>{' '}
&amp;{' '}
<a href={AUTHOR_LINKS.Sam} target={'_blank'} rel={'noreferrer'}>
Sam
</a>
</BuiltWith>
<MobileMenuButton onClick={() => setMenuVisible((v) => !v)}>
{menuVisible ? <Cancel /> : <Menu />}
</MobileMenuButton>
</HeaderRight>
</Container>
);
}
const StyledDiscord = styled(Discord)<{ $isMobile?: boolean }>`
display: none;
${media.lg} {
display: flex;
margin: 0 0 0 16px;
&:hover {
scale: 1.1;
transition: 0.2s;
color: #7289da;
}
}
`;
export const LogoContainer = styled.div`
position: relative;
z-index: 101;
display: inline-flex;
align-items: center;
`;
const MobileMenuButton = styled(ResetButton)`
z-index: 101;
color: var(--black);
background: transparent;
display: inline-block;
margin-left: auto !important;
cursor: pointer;
svg {
width: 24px;
height: 24px;
transition: 0.1s;
&:hover {
scale: 1.1;
transition: 0.2s;
}
`;
const Banner = styled(Text15)`
display: none;
${media.lg} {
display: none;
display: flex;
align-items: center;
justify-content: center;
padding: 16px 0;
background: var(--g5);
color: var(--g0);
font-weight: 500;
position: absolute;
width: 100%;
text-decoration: underline;
top: 0;
}
> * {
margin: 0 4px;
}
`;
const MobileMenuContainer = styled.div<{ visible?: boolean }>`
const MobileMenuButton = styled(ResetButton)`
&&& {
z-index: 101;
color: var(--black);
background: transparent;
display: inline-block;
margin-left: auto;
cursor: pointer;
svg {
width: 24px;
height: 24px;
}
${media.lg} {
display: none;
}
}
`;
const MobileMenuContainer = styled.div<{ $visible?: boolean }>`
position: absolute;
top: 0;
left: 0;
@ -97,7 +82,9 @@ const MobileMenuContainer = styled.div<{ visible?: boolean }>`
z-index: 100;
background: white;
padding-top: 100px;
transition: transform 0.5s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.25s linear;
transition:
transform 0.5s cubic-bezier(0.16, 1, 0.3, 1),
opacity 0.25s linear;
box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.1);
transform: translateY(-100%);
pointer-events: none;
@ -105,13 +92,14 @@ const MobileMenuContainer = styled.div<{ visible?: boolean }>`
display: flex;
flex-direction: column;
align-items: stretch;
${(props) => (props.visible ? '&' : '&.noop')} {
pointer-events: all;
transform: translateY(0);
opacity: 1;
}
${(props) =>
props.$visible
&& css`
pointer-events: all;
transform: translateY(0);
opacity: 1;
`}
${media.lg} {
margin-left: auto;
background: none;
padding-top: 0;
box-shadow: none;
@ -127,11 +115,16 @@ const MobileMenuContainer = styled.div<{ visible?: boolean }>`
}
}
`;
const Container = styled.div`
display: flex;
align-items: center;
justify-content: center;
${media.lg} {
margin-top: 40px;
}
`;
const HeaderItem = styled.div`
flex: 1;
width: 33%;
@ -140,59 +133,144 @@ const HeaderItem = styled.div`
justify-content: center;
align-items: center;
`;
const HeaderCenter = styled(HeaderItem)`
padding: 0 16px;
> :not(:last-child) {
margin-right: 16px;
}
`;
const HeaderLeft = styled(HeaderItem)`
justify-content: flex-start;
&&& {
justify-content: flex-start;
}
`;
const HeaderRight = styled(HeaderItem)`
justify-content: flex-end;
&&& {
justify-content: flex-end;
}
`;
export const Logo = styled.img`
height: 24px;
margin-top: -4px;
color: var(--black);
margin-right: 16px !important;
margin-right: 16px;
z-index: 101;
`;
export const LogoIcon = styled.div`
color: var(--black);
margin-right: 4px !important;
margin-right: 4px;
svg {
width: 36px;
height: 36px;
}
`;
const BuiltWith = styled(NavigationItemContainer)<{ isMobile?: boolean }>`
display: ${(props) => (props.isMobile ? 'flex' : 'none')};
${media.lg} {
display: ${(props) => (props.isMobile ? 'none' : 'flex')};
}
align-items: center;
justify-content: center;
color: var(--black-60);
border-bottom: none !important;
svg {
fill: var(--black);
margin: 0 0.22em;
}
> * {
margin: 0 0.22em;
}
a {
color: var(--black);
font-weight: 700;
}
> :last-child {
margin-right: 0;
}
${media.lg} {
justify-content: flex-start;
const Share = styled(Text15)<{ $isMobile?: boolean }>`
&&& {
display: none;
${(props) =>
props.$isMobile
&& css`
display: flex;
justify-content: center;
padding: 12px 0;
`}
${media.lg} {
display: ${(props) => (props.$isMobile ? 'none' : 'block')};
}
color: var(--black-60);
white-space: pre-wrap;
a {
font-weight: normal;
display: inline-flex;
align-items: center;
color: unset;
text-decoration: unset;
}
svg,
span {
color: var(--black);
}
a:hover {
text-decoration: underline;
}
}
`;
export interface HeaderProps {
currentVersion: string;
}
export function Header({ currentVersion }: HeaderProps) {
const [menuVisible, setMenuVisible] = React.useState(false);
return (
<Container>
<Banner>
<Sparks></Sparks>
<a
href="https://opencollective.com/iconoir/donate?interval=month&amount=10"
target="_blank"
>
Your one-time or recurring contribution does a lot to keep Iconoir
going.
</a>
</Banner>
<HeaderLeft>
<Link href="/">
<LogoContainer>
<LogoIcon>
<AnimatedSvg />
</LogoIcon>
<Logo src="/iconoir-logo.svg" alt="Iconoir Logo" />
</LogoContainer>
</Link>
<CurrentVersion version={currentVersion} />
</HeaderLeft>
<HeaderCenter>
<MobileMenuContainer $visible={menuVisible}>
<NavigationItem href="/">Icons</NavigationItem>
<NavigationItem href="/docs/introduction" activeMatch="/docs">
Documentation
</NavigationItem>
<NavigationItem href="/support" style={{ marginRight: 0 }}>
Donate &mdash; Our Mission
</NavigationItem>
<Share $isMobile>
<a href={SHARE_LINK} target="_blank" rel="noreferrer nofollow">
Share with
{' '}
<Heart width="1em" height="1em" />
{' '}
on
{' '}
<span>X (Twitter)</span>
</a>
</Share>
</MobileMenuContainer>
</HeaderCenter>
<HeaderRight>
<Share>
<a href={SHARE_LINK} target="_blank" rel="noreferrer nofollow">
Share with
{' '}
<Heart width="1em" height="1em" />
{' '}
on
{' '}
<span>X (Twitter)</span>
</a>
</Share>
<a href={DISCORD_LINK} rel="nofollow noreferrer">
<StyledDiscord $isMobile />
</a>
<MobileMenuButton onClick={() => setMenuVisible((v) => !v)}>
{menuVisible ? <Xmark /> : <Menu />}
</MobileMenuButton>
</HeaderRight>
</Container>
);
}

View file

@ -1,46 +1,136 @@
import React from 'react';
import React, { useEffect, useRef } from 'react';
import styled from 'styled-components';
import { media } from './responsive';
import { media } from '../lib/responsive';
const HeaderContainer = styled.div`
position: relative;
width: fit-content;
margin: auto;
`;
const FloatingIcon = styled.div`
position: absolute;
display: none;
background-repeat: no-repeat;
z-index: -1;
pointer-events: none;
align-items: center;
justify-content: center;
${media.md} {
display: flex;
}
`;
const FloatingIconCellar = styled(FloatingIcon)`
-webkit-transform: rotate(6deg);
-moz-transform: rotate(6deg);
top: -120px;
right: 0px;
width: 200px;
height: 200px;
background-image: url(/cellar.gif);
background-size: 70%;
${media.lg} {
}
`;
const FloatingIconPay = styled(FloatingIcon)`
-webkit-transform: rotate(18deg);
-moz-transform: rotate(18deg);
top: -50px;
right: -100px;
width: 130px;
height: 130px;
background-image: url(/pay-bitcoin.gif);
background-size: 70%;
${media.lg} {
}
`;
const FloatingFaceID = styled(FloatingIcon)`
-webkit-transform: rotate(6deg);
-moz-transform: rotate(6deg);
top: -130px;
right: 380px;
width: 110px;
height: 110px;
background-image: url(/face-id.gif);
background-size: 70%;
${media.lg} {
}
`;
const FloatingCommand = styled(FloatingIcon)`
-webkit-transform: rotate(-7deg);
-moz-transform: rotate(-7deg);
top: -94px;
left: 150px;
width: 110px;
height: 110px;
background-image: url(/command.gif);
background-size: 70%;
${media.lg} {
}
`;
const FloatingFill = styled(FloatingIcon)`
-webkit-transform: rotate(-14deg);
-moz-transform: rotate(-14deg);
top: -64px;
left: -75px;
width: 110px;
height: 110px;
background-image: url(/fill.gif);
background-size: 70%;
${media.lg} {
}
`;
export interface HeaderBackgroundProps {
children: React.ReactElement;
src: string;
}
export function HeaderBackground({ children, src }: HeaderBackgroundProps) {
export function HeaderBackground({ children }: HeaderBackgroundProps) {
const parallaxRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!parallaxRef.current)
return;
const parallaxElements = parallaxRef.current.querySelectorAll(
'[data-parallax-factor]',
);
const handleMouseMove = (event: MouseEvent) => {
const x = event.clientX / window.innerWidth;
const y = event.clientY / window.innerHeight;
parallaxElements.forEach((el) => {
const factor = Number.parseFloat(
el.getAttribute('data-parallax-factor') || '1',
);
(el as HTMLElement).style.transform = `translate3d(${
x * factor * 40
}px, ${y * factor * 80}px, 0)`;
});
};
document.addEventListener('mousemove', handleMouseMove);
return () => {
document.removeEventListener('mousemove', handleMouseMove);
};
}, []);
return (
<HeaderContainer>
<ImageContainer>
<BackgroundImage src={src} />
</ImageContainer>
<HeaderContainer ref={parallaxRef}>
<FloatingIconCellar data-parallax-factor="0.75" />
<FloatingIconPay data-parallax-factor="1.5" />
<FloatingFaceID data-parallax-factor="0.5" />
<FloatingCommand data-parallax-factor="1.25" />
<FloatingFill data-parallax-factor="2" />
{children}
</HeaderContainer>
);
}
const HeaderContainer = styled.div`
position: relative;
`;
const ImageContainer = styled.div`
position: absolute;
top: -100px;
bottom: -100px;
left: -30px;
right: -30px;
${media.lg} {
left: -100px;
right: -100px;
}
z-index: -1;
pointer-events: none;
display: flex;
align-items: center;
justify-content: center;
`;
const BackgroundImage = styled.img`
width: 90%;
max-width: calc(min(1100px, 90vw));
max-height: 60%;
${media.md} {
max-height: 100%;
}
`;

View file

@ -0,0 +1,16 @@
import React from 'react';
import styled from 'styled-components';
const HeaderContainer = styled.div`
position: relative;
width: fit-content;
margin: auto;
`;
export interface HeaderSecondaryProps {
children: React.ReactElement;
}
export function HeaderSecondary({ children }: HeaderSecondaryProps) {
return <HeaderContainer>{children}</HeaderContainer>;
}

View file

@ -0,0 +1,63 @@
import React from 'react';
import styled from 'styled-components';
const PromoContainer = styled.div`
border-radius: 12px;
border: 1px solid var(--g6);
text-align: center;
margin-top: 24px;
text-decoration: none;
&:hover {
background-color: var(--g7);
}
`;
const SponsorLabel = styled.div`
font-size: 12px;
font-weight: 400;
color: var(--g4);
margin: 10px 0;
`;
const PromoContent = styled.div`
// Your content styles here, similar to SponsorText
`;
const PromoImage = styled.img`
width: 70%;
`;
const PromoInfo = styled.div`
// Styles for the text container, similar to SponsorRight
`;
const PromoTitle = styled.h2`
font-size: 16px;
font-weight: 600;
margin: 16px auto 0 auto;
`;
const PromoSub = styled.h2`
font-size: 14px;
margin: 0 auto 10px auto;
`;
export function Hono() {
return (
<a
href="https://wearehono.com/?utm_source=iconoir&utm_medium=sidebar"
style={{ textDecoration: 'none' }}
>
<PromoContainer>
<PromoContent>
<PromoInfo>
<PromoTitle>Buy high-quality logos</PromoTitle>
<PromoSub>with Hono.</PromoSub>
<PromoImage src="./hono-ad.png" />
<SponsorLabel>Our sponsor</SponsorLabel>
</PromoInfo>
</PromoContent>
</PromoContainer>
</a>
);
}

View file

@ -1,31 +1,152 @@
import React from 'react';
import { DEFAULT_CUSTOMIZATIONS, Icon as IconType } from './IconList';
import styled from 'styled-components';
import type { Icon as IconType } from './IconList';
import * as AllIcons from 'iconoir-react';
import React from 'react';
import styled from 'styled-components';
import { showNotification } from '../lib/showNotification';
import { ResetButton } from './Button';
import { showNotification } from '../helpers/showNotification';
import { DEFAULT_CUSTOMIZATIONS } from './IconList';
const HEADER = '<?xml version="1.0" encoding="UTF-8"?>';
function bakeSvg(
svgString: string,
color: string,
strokeWidth: string | number
strokeWidth: string | number,
) {
return (
HEADER +
svgString
HEADER
+ svgString
.replace(
/stroke="currentColor"/g,
`stroke="currentColor" stroke-width="${strokeWidth}"`
`stroke="currentColor" stroke-width="${strokeWidth}"`,
)
.replace(/currentColor/g, color)
);
}
const Overlay = styled.div`
position: absolute;
border-radius: 50%;
border: solid 2px var(--g0);
background: var(--white);
width: 8px;
height: 8px;
`;
const CornerBR = styled(Overlay)`
bottom: -6px;
right: -6px;
z-index: 999;
`;
const CornerTR = styled(Overlay)`
top: -6px;
right: -6px;
`;
const CornerBL = styled(Overlay)`
bottom: -6px;
left: -6px;
`;
const CornerTL = styled(Overlay)`
top: -6px;
left: -6px;
`;
const HoverContainer = styled.div`
position: absolute;
inset: 0;
display: flex;
align-items: stretch;
justify-content: stretch;
flex-direction: column;
transform: translateZ(0px); // Safari Fix
transition: opacity 0.1s linear;
opacity: 0;
pointer-events: none;
`;
const HoverButton = styled(ResetButton)`
&&& {
display: flex;
align-items: center;
justify-content: center;
background: var(--white);
border-radius: 0px;
transition: background 0.1s linear;
color: var(--g0);
font-size: 14px;
line-height: 23px;
font-weight: 700;
text-align: center;
flex: 1;
cursor: pointer;
text-decoration: none;
&:hover,
&:active {
background: var(--g0);
color: var(--white);
}
}
`;
const BorderContainer = styled.div<{ $iconWidth: number }>`
width: ${(props) => props.$iconWidth}px;
box-sizing: border-box;
padding-bottom: 100%;
position: relative;
border: solid 1px var(--g6);
border-radius: 12px;
margin-bottom: 10px;
@media (hover: hover) {
&:hover ${HoverContainer} {
opacity: 1;
pointer-events: all;
border: solid 2px var(--g0);
}
}
`;
const IconContainer = styled.div`
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
`;
const IconTag = styled.div`
background-color: var(--g6);
position: absolute;
top: 0px;
right: 0px;
border-radius: 5px 10px;
padding: 3px 6px;
letter-spacing: 0.3px;
font-weight: 600;
font-size: 11px;
color: var(--g0);
`;
const Subtitle = styled.div<{ $iconWidth: number }>`
font-size: 11px;
font-weight: 500;
line-height: 14.74px;
color: var(--black-60);
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: ${(props) => props.$iconWidth}px;
`;
export interface IconProps {
iconWidth: number;
icon: IconType;
}
export function Icon({ iconWidth, icon }: IconProps) {
const IconComponent = (AllIcons as any)[icon.iconComponentName];
const iconContainerRef = React.useRef<HTMLDivElement>(null);
@ -33,34 +154,37 @@ 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'
typeof window !== 'undefined'
&& typeof window?.navigator?.clipboard?.writeText !== 'undefined',
);
}, []);
React.useEffect(() => {
if (iconContainerRef.current) {
htmlContentsRef.current = bakeSvg(
iconContainerRef.current.innerHTML,
(iconContainerRef.current.firstChild as SVGElement).outerHTML,
iconContext.color || DEFAULT_CUSTOMIZATIONS.hexColor,
iconContext.strokeWidth || DEFAULT_CUSTOMIZATIONS.strokeWidth
iconContext.strokeWidth || DEFAULT_CUSTOMIZATIONS.strokeWidth,
);
}
}, [iconContext, supportsClipboard]);
React.useEffect(() => {
const element =
downloadRef.current ||
(iconContainerRef.current as unknown as HTMLAnchorElement);
const element = downloadRef.current || (iconContainerRef.current as unknown as HTMLAnchorElement);
if (element) {
element.href = `data:image/svg+xml;base64,${btoa(
htmlContentsRef.current
htmlContentsRef.current,
)}`;
}
}, [iconContext, supportsClipboard]);
return (
<div className={'icon-container'}>
<BorderContainer iconWidth={iconWidth}>
<div className="icon-container">
<BorderContainer $iconWidth={iconWidth}>
<IconContainer
ref={iconContainerRef}
{...((supportsClipboard
@ -73,103 +197,48 @@ export function Icon({ iconWidth, icon }: IconProps) {
}) as any)}
>
<IconComponent />
{icon.filename.includes('-solid') ? <IconTag>SOLID</IconTag> : ''}
</IconContainer>
{supportsClipboard ? (
<HoverContainer>
<HoverButton
onClick={() => {
if (htmlContentsRef.current) {
navigator.clipboard
.writeText(htmlContentsRef.current)
.then(() => {
showNotification('SVG code copied!');
})
.catch((err) => {
console.error(err);
});
}
}}
>
Copy SVG
</HoverButton>
<HoverButton
as={'a'}
ref={downloadRef}
href={'#'}
rel={'noreferrer'}
download={`${icon.filename}.svg`}
>
Download
</HoverButton>
</HoverContainer>
) : null}
{supportsClipboard
? (
<HoverContainer>
<CornerBR />
<CornerTR />
<CornerBL />
<CornerTL />
<HoverButton
onClick={() => {
if (htmlContentsRef.current) {
navigator.clipboard
.writeText(htmlContentsRef.current)
.then(() => {
showNotification('SVG code copied!');
})
.catch((err) => {
console.error(err);
});
}
}}
>
Copy SVG
</HoverButton>
<HoverButton
as="a"
ref={downloadRef}
href="#"
rel="noreferrer"
download={`${icon.filename}.svg`}
>
Download
</HoverButton>
</HoverContainer>
)
: null}
</BorderContainer>
<Subtitle>{icon.filename}</Subtitle>
<Subtitle $iconWidth={iconWidth} title={icon.filename}>
{icon.filename}
</Subtitle>
</div>
);
}
const HoverContainer = styled.div<{ supportsCopy?: boolean }>`
position: absolute;
display: ${(props) => (props.supportsCopy ? 'block' : 'none')};
inset: 0;
display: flex;
align-items: stretch;
justify-content: stretch;
flex-direction: column;
border-radius: 12px;
overflow: hidden;
transform: translateZ(0px); // Safari Fix
transition: opacity 0.1s linear;
opacity: 0;
pointer-events: none;
`;
const HoverButton = styled(ResetButton)`
display: flex;
align-items: center;
justify-content: center;
background: var(--light-gray);
border-radius: 0 !important;
transition: background 0.1s linear;
color: var(--black);
font-size: 14px;
line-height: 23px;
font-weight: 700;
text-align: center;
flex: 1;
cursor: pointer;
text-decoration: none;
&:hover,
&:active {
background: var(--gray);
}
`;
const BorderContainer = styled.div<{ iconWidth: number }>`
width: ${(props) => props.iconWidth}px;
box-sizing: border-box;
padding-bottom: 100%;
position: relative;
border: solid 1px var(--light-gray);
border-radius: 12px;
margin-bottom: 10px;
@media (hover: hover) {
&:hover ${HoverContainer} {
opacity: 1;
pointer-events: all;
}
}
`;
const IconContainer = styled.div`
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
`;
const Subtitle = styled.div`
font-size: 11px;
font-weight: 500;
line-height: 14.74px;
color: var(--black-40);
text-align: center;
`;

View file

@ -1,17 +1,19 @@
import React from 'react';
import {
VariableSizeList as List,
import type {
ListChildComponentProps,
areEqual,
} from 'react-window';
import { chunk } from 'lodash';
import React from 'react';
import {
areEqual,
VariableSizeList as List,
} from 'react-window';
import styled from 'styled-components';
import useResizeObserver from 'use-resize-observer';
import { ICON_SPACE, ICON_WIDTH } from './constants';
import { ICON_SPACE, ICON_WIDTH } from '../lib/constants';
import { CategoryRow } from './CategoryRow';
import { IconListEmpty } from './IconListEmpty';
import { IconsRow } from './IconsRow';
import { ReactWindowScroller } from './ReactWindowScroller';
import styled from 'styled-components';
import { IconListEmpty } from './IconListEmpty';
export interface IconListFilters {
search?: string;
@ -40,15 +42,23 @@ function normalizeString(s: string) {
function filterIcons(allIcons: Icon[], filters: IconListFilters): Icon[] {
if (filters.search) {
return allIcons.filter((icon) => {
const normalSearch = normalizeString(filters.search!);
return (
normalizeString(icon.filename).includes(normalSearch) ||
normalizeString(icon.category).includes(normalSearch) ||
icon.tags.some((tag) => normalizeString(tag).includes(normalSearch))
);
});
} else return allIcons;
const normalSearch = normalizeString(filters.search!);
let result = allIcons;
for (const term of normalSearch.split(' ')) {
result = result.filter((icon) => {
return (
normalizeString(icon.filename).includes(term)
|| normalizeString(icon.category).includes(term)
|| icon.tags.some((tag) => normalizeString(tag).includes(term))
);
});
}
return result;
} else {
return allIcons;
}
}
interface IconCategoryRow {
@ -59,28 +69,34 @@ interface IconIconsRow {
icons: Icon[];
}
type IconRow = IconCategoryRow | IconIconsRow;
function isCategoryRow(iconRow: IconRow): iconRow is IconCategoryRow {
return !!(iconRow as IconCategoryRow).category;
}
function getRowsFromIcons(
filteredIcons: Icon[],
iconsPerRow: number
iconsPerRow: number,
): IconRow[] {
const categoryGroups: Record<string, Icon[]> = {};
for (const icon of filteredIcons) {
if (!categoryGroups[icon.category]) categoryGroups[icon.category] = [];
if (!categoryGroups[icon.category])
categoryGroups[icon.category] = [];
categoryGroups[icon.category].push(icon);
}
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 });
}
@ -93,6 +109,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;
@ -105,29 +122,67 @@ interface IconListContextValue {
iconWidth: number;
iconsPerRow: number;
}
export const IconListContext = React.createContext<
IconListContextValue | undefined
>(undefined);
const IconListContext = React.createContext<IconListContextValue | undefined>(undefined);
const Container = styled.div`
width: 100%;
margin-top: -${HEADER_TOP_PADDING}px;
> :first-child {
overflow: visible !important;
> :first-child {
-webkit-overflow-scrolling: touch;
}
}
`;
const Row = React.memo(
({ data, index, style }: ListChildComponentProps<IconRow[]>) => {
const { iconWidth } = React.useContext(IconListContext)!;
const row = data[index];
if (isCategoryRow(row)) {
return (
<CategoryRow
category={row.category}
numIcons={row.numIcons}
style={style}
/>
);
} else {
return <IconsRow icons={row.icons} style={style} iconWidth={iconWidth} />;
}
},
areEqual,
);
Row.displayName = 'Row';
export interface IconListProps {
filters: IconListFilters;
allIcons: Icon[];
}
export function IconList({ filters, allIcons }: IconListProps) {
const filteredIcons = filterIcons(allIcons, filters);
const { ref, width = 400 } = useResizeObserver();
const iconsPerRow = width
? Math.floor((width + ICON_SPACE) / (ICON_WIDTH + ICON_SPACE))
: null;
let children = null;
const listRef = React.useRef<List<IconRow[]> | null>();
const listRef = React.useRef<List<IconRow[]> | null>(null);
const [height, setHeight] = React.useState(400);
const iconWidth = iconsPerRow
? Math.floor((width + ICON_SPACE) / iconsPerRow) - ICON_SPACE
: null;
React.useEffect(() => {
setHeight(window.innerHeight);
}, []);
React.useEffect(() => {
if (listRef.current) {
listRef.current.resetAfterIndex(0, true);
@ -136,14 +191,17 @@ export function IconList({ filters, allIcons }: IconListProps) {
if (filteredIcons.length && iconsPerRow && width && iconWidth) {
const iconRows = getRowsFromIcons(filteredIcons, iconsPerRow);
children = (
<IconListContext.Provider value={{ iconsPerRow, iconWidth }}>
<ReactWindowScroller>
{({ ref, outerRef, style, onScroll }: any) => (
<List<IconRow[]>
ref={(c) => {
if (typeof ref === 'function') ref(c);
else ref.current = c;
if (typeof ref === 'function')
ref(c);
else
ref.current = c;
listRef.current = c;
}}
itemData={iconRows}
@ -167,34 +225,3 @@ export function IconList({ filters, allIcons }: IconListProps) {
return <Container ref={ref}>{children}</Container>;
}
const Container = styled.div`
width: 100%;
margin-top: -${HEADER_TOP_PADDING}px;
> :first-child {
overflow: visible;
> :first-child {
-webkit-overflow-scrolling: touch;
}
}
`;
const Row = React.memo(
({ data, index, style }: ListChildComponentProps<IconRow[]>) => {
const { iconWidth } = React.useContext(IconListContext)!;
const row = data[index];
if (isCategoryRow(row)) {
return (
<CategoryRow
category={row.category}
numIcons={row.numIcons}
style={style}
/>
);
} else {
return <IconsRow icons={row.icons} style={style} iconWidth={iconWidth} />;
}
},
areEqual
);
Row.displayName = 'Row';

View file

@ -1,32 +1,8 @@
import { SpockHandGesture } from 'iconoir-react';
import React from 'react';
import styled from 'styled-components';
import { SUGGEST_ICON } from './constants';
import { SUGGEST_ICON_LINK } from '../lib/constants';
import { Text18 } from './Typography';
export interface IconListEmptyProps {
searchTerm: string;
}
export function IconListEmpty({ searchTerm }: IconListEmptyProps) {
return (
<Container>
<IconContainer>
<SpockHandGesture />
</IconContainer>
<Title>
Unfortunately there are no icons for &apos;{searchTerm}&apos;
</Title>
<Text18 style={{ color: 'var(--black-60)' }}>
{"If you can't find the icon, you can make a"}
<br />
<a href={SUGGEST_ICON} target={'_blank'} rel={'noreferrer'}>
suggestion on GitHub.
</a>
</Text18>
</Container>
);
}
const Container = styled.div`
margin-top: 90px;
display: flex;
@ -34,6 +10,7 @@ const Container = styled.div`
flex-direction: column;
text-align: center;
`;
const IconContainer = styled.div`
svg {
width: 60px;
@ -42,8 +19,37 @@ const IconContainer = styled.div`
margin-bottom: 65px;
color: var(--black);
`;
const Title = styled(Text18)`
font-weight: 700;
margin-bottom: 30px;
color: var(--black);
&&& {
font-weight: 700;
margin-bottom: 30px;
color: var(--black);
}
`;
export interface IconListEmptyProps {
searchTerm: string;
}
export function IconListEmpty({ searchTerm }: IconListEmptyProps) {
return (
<Container>
<IconContainer>
<SpockHandGesture />
</IconContainer>
<Title>
Unfortunately there are no icons for &apos;
{searchTerm}
&apos;
</Title>
<Text18 style={{ color: 'var(--black-60)' }}>
If you can&apos;t find the icon, you can make a
<br />
<a href={SUGGEST_ICON_LINK} target="_blank" rel="noreferrer">
suggestion on GitHub.
</a>
</Text18>
</Container>
);
}

View file

@ -1,14 +1,22 @@
import React from 'react';
import { ICON_SPACE } from './constants';
import { Icon } from './IconList';
import type { Icon } from './IconList';
import styled from 'styled-components';
import { ICON_SPACE } from '../lib/constants';
import { Icon as IconC } from './Icon';
const RowContainer = styled.div`
display: flex;
align-items: center;
> :not(:last-child) {
margin-right: ${ICON_SPACE}px;
}
`;
export interface IconsRowProps {
icons: Icon[];
style?: any;
iconWidth: number;
}
export function IconsRow({ icons, style, iconWidth }: IconsRowProps) {
return (
<RowContainer style={style}>
@ -18,11 +26,3 @@ export function IconsRow({ icons, style, iconWidth }: IconsRowProps) {
</RowContainer>
);
}
const RowContainer = styled.div`
display: flex;
align-items: center;
> :not(:last-child) {
margin-right: ${ICON_SPACE}px;
}
`;

View file

@ -19,38 +19,77 @@ const ResetInput = styled.input`
}
`;
export const Input = styled(ResetInput)`
min-height: 35px;
background: var(--super-light-gray);
border: 1px solid var(--gray);
border-radius: 10px;
overflow: hidden;
padding: 6px;
text-align: center;
font-size: 13px;
line-height: 21px;
font-weight: 500;
color: var(--black);
const Input = styled(ResetInput)`
&&& {
min-height: 35px;
background: var(--white);
border-radius: 10px;
overflow: hidden;
padding: 6px;
text-align: center;
font-size: 13px;
line-height: 21px;
font-weight: 500;
color: var(--black);
border: solid 1px var(--g6);
&:hover {
border: solid 2px var(--g0);
}
&:focus {
border: solid 2px var(--g0);
}
}
`;
export const LargeInput = styled(Input)`
height: 75px;
font-size: 16px;
line-height: 26px;
border-radius: 12px;
padding: 0 23px;
text-align: left;
width: 100%;
box-sizing: border-box;
outline: none;
&&& {
height: 60px;
font-size: 16px;
line-height: 26px;
border-radius: 12px;
padding: 0 23px;
text-align: left;
width: 100%;
box-sizing: border-box;
outline: none;
transition: 0.2s;
&:hover {
transform: scale(1.02);
}
}
`;
export const ColorInput = styled(Input)`
export const ColorButton = styled.div`
width: 24px;
height: 24px;
background-image: url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='51px' height='51px' stroke-width='2.3' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg' color='%23000000'%3E%3Cpath d='M7 13.161l5.464-5.464a1 1 0 011.415 0l2.12 2.12a1 1 0 010 1.415l-1.928 1.929m-7.071 0l-2.172 2.172a.999.999 0 00-.218.327l-1.028 2.496c-.508 1.233.725 2.466 1.958 1.959l2.497-1.028c.122-.05.233-.125.326-.218l5.708-5.708m-7.071 0h7.071M13.878 3.454l2.121 2.121m4.243 4.243l-2.121-2.121m-2.122-2.122l1.414-1.414a1 1 0 011.415 0l.707.707a1 1 0 010 1.414L18.12 7.697m-2.122-2.122l2.122 2.122' stroke='%23000000' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3C/path%3E%3C/svg%3E");
background-repeat: no-repeat;
background-size: 100%;
right: 20px;
position: absolute;
pointer-events: none;
&:hover {
transition: 0.2s;
scale: 1.2;
}
`;
export const ColorInput = styled.input`
padding: 0px;
border: none;
cursor: pointer;
width: 30px;
height: 30px;
&::-webkit-color-swatch,
&::-moz-color-swatch {
border: none;
}
background-color: var(--gray-200);
opacity: 0;
transition: 0.2s;
&:hover + ${ColorButton} {
transition: 0.2s;
scale: 1.3;
}
`;

View file

@ -1,12 +1,20 @@
import React from 'react';
import styled from 'styled-components';
import { media } from '../lib/responsive';
import { GA } from './GA';
export interface LayoutProps {}
export function Layout({ children }: React.PropsWithChildren<LayoutProps>) {
const Container = styled.div`
padding: 50px 30px;
${media.lg} {
padding: 30px 50px 50px 50px;
}
`;
export function Layout({ children }: React.PropsWithChildren) {
return (
<div>
<Container>
<GA />
{children}
</div>
</Container>
);
}

View file

@ -1,8 +1,7 @@
import React from 'react';
import { MDXRemote as CoreMDXRemote, MDXRemoteProps } from 'next-mdx-remote';
import { SuggestLibrary } from './SuggestLibrary';
import { Pre, Body, H1, H2, H3, Code } from './Typography';
import type { MDXRemoteProps } from 'next-mdx-remote';
import { MDXRemote as CoreMDXRemote } from 'next-mdx-remote';
import { Table } from './Table';
import { Body, Code, H1, H2, H3, InlineCode, Li, Pre } from './Typography';
export function MDXRemote(props: MDXRemoteProps) {
return (
@ -17,7 +16,8 @@ export function MDXRemote(props: MDXRemoteProps) {
h2: H2,
h3: H3,
table: Table,
SuggestLibrary,
code: InlineCode,
li: Li,
}}
/>
);

View file

@ -2,70 +2,111 @@ import Link from 'next/link';
import { useRouter } from 'next/router';
import React from 'react';
import styled from 'styled-components';
import { media } from './responsive';
import { media } from '../lib/responsive';
import { Text15 } from './Typography';
const NavigationItemContainer = styled(Text15)<{
$text: string;
$isActive?: boolean;
}>`
&&& {
font-weight: ${(props) => (props.$isActive ? '700' : '500')};
font-size: 18px;
line-height: 28px;
text-decoration: none;
white-space: nowrap;
padding: 24px;
color: var(--black);
text-align: center;
width: 100%;
box-sizing: border-box;
position: relative;
z-index: 2;
transition: background 0.1s linear;
&:not(:last-child) {
border-bottom: solid 1px var(--light-gray);
}
${media.lg} {
font-size: 15px;
line-height: 20px;
font-weight: ${(props) => (props.$isActive ? '600' : '500')};
padding: 0;
color: var(--g0);
width: auto;
border-bottom: none !important;
transition: 0.2s;
/* Prevent layout shift */
display: inline-flex;
flex-direction: column;
&::after {
content: '${(props) => props.$text}';
height: 0;
visibility: hidden;
overflow: hidden;
user-select: none;
pointer-events: none;
font-weight: 600;
}
&::before {
position: absolute;
z-index: -1;
content: '';
display: block;
top: -12px;
bottom: -12px;
left: -16px;
right: -16px;
border-radius: 10px;
transition: background 0.1s linear;
}
&:hover::before {
background: var(--g6);
transition: 0.2s;
top: -16px;
bottom: -16px;
left: -20px;
right: -20px;
}
}
}
`;
export interface NavigationItemProps {
href: string;
activeMatch?: string;
children: React.ReactElement | string;
style?: any;
}
export function NavigationItem({ href, children, style }: NavigationItemProps) {
export function NavigationItem({
href,
activeMatch,
children,
style,
}: NavigationItemProps) {
const router = useRouter();
return (
<Link href={href} passHref>
<Link href={href} passHref legacyBehavior>
<NavigationItemContainer
as={'a'}
isActive={
href.slice(1)
? router.asPath.slice(1).startsWith(href.slice(1))
: router.asPath === href
}
as="a"
style={style}
$text={children.toString()}
$isActive={
activeMatch
? router.asPath.startsWith(activeMatch)
: href.slice(1)
? router.asPath.slice(1).startsWith(href.slice(1))
: router.asPath === href
}
>
{children}
</NavigationItemContainer>
</Link>
);
}
export const NavigationItemContainer = styled(Text15)<{ isActive?: boolean }>`
font-weight: 700;
font-size: 18px;
line-height: 28px;
text-decoration: none;
white-space: nowrap;
padding: 25px;
color: var(--black);
text-align: center;
width: 100%;
box-sizing: border-box;
position: relative;
z-index: 2;
&:not(:last-child) {
border-bottom: solid 1px var(--light-gray);
}
${media.lg} {
font-size: 15px;
line-height: 20px;
font-weight: 500;
padding: 0;
color: var(--black-60);
width: auto;
border-bottom: none !important;
${(props) => (props.isActive ? '&' : '&.noop')} {
color: var(--white);
&::before {
position: absolute;
z-index: -1;
content: '';
display: block;
top: -18px;
bottom: -18px;
left: -24px;
right: -24px;
background: var(--black);
}
}
}
`;

View file

@ -1,102 +1,9 @@
import React from 'react';
import styled from 'styled-components';
import { media } from '../lib/responsive';
import { PraiseItem } from './PraiseItem';
import { media } from './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 = () => {
if (indicatorContainerRef.current && containerRef.current) {
const currentScrollLeft = containerRef.current.scrollLeft;
const totalScroll = containerRef.current.scrollWidth;
const interval = totalScroll / NUM_PRAISE_ITEMS;
const currentIndex =
currentScrollLeft >=
containerRef.current.scrollWidth - window.innerWidth - 100
? indicatorContainerRef.current.children.length - 1
: Math.round(currentScrollLeft / interval);
for (
let i = 0;
i < indicatorContainerRef.current.children.length;
i++
) {
const child = indicatorContainerRef.current.children[i];
if (currentIndex === i) {
child.classList.add('active');
} else {
child.classList.remove('active');
}
}
}
};
const element = containerRef.current;
element.addEventListener('scroll', handle);
return () => {
element.removeEventListener('scroll', handle);
};
}
}, []);
return (
<>
<Container ref={containerRef}>
<PraiseItem
name={'Riccardo Suardi'}
position={'Nibol CEO'}
description={
<>
In Nibol we decided to use Iconoir to speed up the design process.
We want to focus on the product and let Iconoir help us with the
design.
</>
}
imageUrl={'./riccardo-suardi.png'}
logoUrl={'./nibol-logo.svg'}
logoLink={'https://www.nibol.com/'}
logoAlt={'Nibol Logo'}
/>
<PraiseItem
name={'Fabrizio Rinaldi'}
position={'Mailbrew and Typefully founder'}
description={
<>
There's no shortage of icon packs, and yet I always find myself
browsing iconoir. I love the style and attention to detail, and
how easy it is to grab the perfect icons for my projects.
</>
}
imageUrl={'./fabrizio-rinaldi.png'}
logoUrl={'./typefully-logo.png'}
logoLink={'https://typefully.com/'}
logoAlt={'Typefully Logo'}
/>
<PraiseItem
name={'Chris Messina'}
position={'Entrepreneur and # inventor'}
description={
<>
It's the tiny details that determine the degree of delight your
customers experience from your product. Adopting Iconoir icons
will easily boost your app's delight by a factor of 10!
</>
}
imageUrl={'./chris-messina.png'}
logoUrl={'./twitter-logo.png'}
logoLink={'https://twitter.com/chrismessina'}
logoAlt={'Twitter Logo'}
/>
</Container>
<IndicatorContainer ref={indicatorContainerRef}>
<Indicator className={'active'} />
<Indicator />
<Indicator />
</IndicatorContainer>
</>
);
}
const Container = styled.div`
max-width: 100%;
@ -131,6 +38,7 @@ const Container = styled.div`
}
}
`;
const Indicator = styled.div`
width: 6px;
height: 6px;
@ -141,6 +49,7 @@ const Indicator = styled.div`
background: var(--black);
}
`;
const IndicatorContainer = styled.div`
margin: 40px auto 0 auto;
display: flex;
@ -153,3 +62,102 @@ const IndicatorContainer = styled.div`
display: none;
}
`;
export function Praise() {
const containerRef = React.useRef<HTMLDivElement>(null);
const indicatorContainerRef = React.useRef<HTMLDivElement>(null);
React.useEffect(() => {
if (containerRef.current) {
const handle = () => {
if (indicatorContainerRef.current && containerRef.current) {
const currentScrollLeft = containerRef.current.scrollLeft;
const totalScroll = containerRef.current.scrollWidth;
const interval = totalScroll / NUM_PRAISE_ITEMS;
const currentIndex = currentScrollLeft >= containerRef.current.scrollWidth - window.innerWidth - 100
? indicatorContainerRef.current.children.length - 1
: Math.round(currentScrollLeft / interval);
for (
let i = 0;
i < indicatorContainerRef.current.children.length;
i++
) {
const child = indicatorContainerRef.current.children[i];
if (currentIndex === i) {
child.classList.add('active');
} else {
child.classList.remove('active');
}
}
}
};
const element = containerRef.current;
element.addEventListener('scroll', handle);
return () => {
element.removeEventListener('scroll', handle);
};
}
}, []);
return (
<>
<Container ref={containerRef}>
<PraiseItem
name="Riccardo Suardi"
position="Nibol CEO"
description={(
<>
In Nibol we decided to use Iconoir to speed up the design process.
We want to focus on the product and let Iconoir help us with the
design.
</>
)}
imageUrl="./riccardo-suardi.png"
logoUrl="./nibol-logo.svg"
logoLink="https://www.nibol.com/"
logoAlt="Nibol Logo"
/>
<PraiseItem
name="Fabrizio Rinaldi"
position="Mailbrew and Typefully founder"
description={(
<>
There&apos;s no shortage of icon packs, and yet I always find myself
browsing iconoir. I love the style and attention to detail, and
how easy it is to grab the perfect icons for my projects.
</>
)}
imageUrl="./fabrizio-rinaldi.png"
logoUrl="./typefully-logo.png"
logoLink="https://typefully.com/"
logoAlt="Typefully Logo"
/>
<PraiseItem
name="Chris Messina"
position="Entrepreneur and # inventor"
description={(
<>
It&apos;s the tiny details that determine the degree of delight your
customers experience from your product. Adopting Iconoir icons
will easily boost your app&apos;s delight by a factor of 10!
</>
)}
imageUrl="./chris-messina.png"
logoUrl="./twitter-logo.png"
logoLink="https://twitter.com/chrismessina"
logoAlt="Twitter Logo"
/>
</Container>
<IndicatorContainer ref={indicatorContainerRef}>
<Indicator className="active" />
<Indicator />
<Indicator />
</IndicatorContainer>
</>
);
}

View file

@ -1,8 +1,42 @@
import React from 'react';
import styled from 'styled-components';
import { media } from './responsive';
import { media } from '../lib/responsive';
import { Text14, Text18 } from './Typography';
const Container = styled.div`
display: flex;
align-items: flex-start;
flex-direction: row;
flex-shrink: 0;
width: calc(100vw - 60px);
scroll-snap-align: center;
${media.xs} {
width: 428px;
}
`;
const AuthorImage = styled.img`
height: 60px;
width: 60px;
margin-right: 28px;
`;
const Logo = styled.img`
height: 23px;
margin-top: 36px;
`;
const Header = styled(Text18)`
&&& {
font-weight: 700;
color: var(--black);
}
`;
const Body = styled(Text18)`
margin-top: 8px;
`;
export interface PraiseItemProps {
name: string;
position: string;
@ -12,6 +46,7 @@ export interface PraiseItemProps {
logoAlt: string;
imageUrl: string;
}
export function PraiseItem({
name,
position,
@ -28,38 +63,10 @@ export function PraiseItem({
<Header>{name}</Header>
<Text14>{position}</Text14>
<Body>{description}</Body>
<a href={logoLink} target={'_blank'} rel={'noreferrer'}>
<a href={logoLink} target="_blank" rel="noreferrer">
<Logo src={logoUrl} alt={logoAlt} />
</a>
</div>
</Container>
);
}
const Container = styled.div`
display: flex;
align-items: flex-start;
flex-direction: row;
flex-shrink: 0;
width: calc(100vw - 60px);
scroll-snap-align: center;
${media.xs} {
width: 428px;
}
`;
const AuthorImage = styled.img`
height: 60px;
width: 60px;
margin-right: 28px;
`;
const Logo = styled.img`
height: 23px;
margin-top: 36px;
`;
const Header = styled(Text18)`
font-weight: 700;
color: var(--black);
`;
const Body = styled(Text18)`
margin-top: 8px;
`;

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