feat: Update API

BREAKING CHANGE: 

Each icon in the `feather.icons` object is now an `Icon` object with a `name`, `contents`, `tags` and `attrs` property.
```js
/* BEFORE */
feather.icons.x
// '<line ... /><line ... />'

/* AFTER */
feather.icons.x
// {
//    name: 'x',
//    contents: '<line ... /><line ... />`,
//    tags: ['cancel', 'close', 'delete', 'remove'],
//    attrs: {
//      class: 'feather feather-x',
//      xmlns: 'http://www.w3.org/2000/svg',
//      width: 24,
//      height: 24,
//      viewBox: '0 0 24 24',
//      fill: 'none',
//      stroke: 'currentColor',
//      'stroke-width': 2,
//      'stroke-linecap': 'round',
//      'stroke-linejoin': 'round',
//    }
// }
```

`feather.toSvg()` has been deprecated in favor of `feather.icons[name].toSvg()`:
```js
/* BEFORE */
feather.toSvg('x')

/* AFTER */
feather.icons.x.toSvg()
```

`feather.replace()` now copies all attributes on the placeholder element (i.e. `<i>`) to the `<svg>` tag instead of just `class` and `id`:

```html
<i data-feather="circle" id="my-circle" class="foo bar" stroke-width="1"></i>
<!--
<i> will be replaced with:
<svg id="my-circle" class="feather feather-circle foo bar" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle></svg>
-->
```
This commit is contained in:
Cole Bemis 2017-11-18 20:00:16 -08:00 committed by GitHub
parent 0dc2bf5c9d
commit f243624fbd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 825 additions and 274 deletions

View File

@ -1,5 +1,4 @@
{ {
"presets": [ "presets": ["es2015"],
"es2015" "plugins": ["transform-object-rest-spread"]
]
} }

View File

@ -1,12 +1,11 @@
{ {
"extends": "airbnb-base", "extends": "airbnb-base",
"plugins": [ "plugins": ["import"],
"import"
],
"rules": { "rules": {
"no-use-before-define": "off",
"arrow-parens": ["error", "as-needed"], "arrow-parens": ["error", "as-needed"],
"no-console": ["error", { "allow": ["warn", "error"] }],
"no-param-reassign": "off",
"no-shadow": "off", "no-shadow": "off",
"no-console": ["error", { "allow": ["warn", "error"] }] "no-use-before-define": "off"
} }
} }

View File

@ -8,6 +8,6 @@ node_js: 6
before_script: before_script:
- npm prune - npm prune
script: script:
- npm run all - npm start
after_success: after_success:
- npm run semantic-release - npm run semantic-release

View File

@ -1,34 +0,0 @@
src_files := src/*.js
src_dir := src
.PHONY: all lint test build
all: lint test build
lint: dist/icons.json
./node_modules/.bin/eslint .
test:
./node_modules/.bin/jest
build: dist/feather.js dist/feather.min.js dist/icons
node_modules:
npm install
dist:
mkdir dist
dist/icons.json: node_modules dist icons icons/*.svg
./node_modules/.bin/babel-node bin/build-json.js
dist/feather.js: dist/icons.json $(src_dir) $(src_files)
./node_modules/.bin/webpack --output-filename feather.js
dist/feather.min.js: dist/icons.json $(src_dir) $(src_files)
./node_modules/.bin/webpack --output-filename feather.min.js -p
dist/icons: dist/icons.json
rm -rf dist/icons
mkdir -p dist/icons
./node_modules/.bin/babel-node bin/build-svgs.js

147
README.md
View File

@ -3,12 +3,12 @@
[![Travis branch](https://img.shields.io/travis/colebemis/feather/master.svg?style=flat-square)](https://travis-ci.org/colebemis/feather) [![Travis branch](https://img.shields.io/travis/colebemis/feather/master.svg?style=flat-square)](https://travis-ci.org/colebemis/feather)
[![npm](https://img.shields.io/npm/v/feather-icons.svg?style=flat-square)](https://www.npmjs.com/package/feather-icons) [![npm](https://img.shields.io/npm/v/feather-icons.svg?style=flat-square)](https://www.npmjs.com/package/feather-icons)
[![npm](https://img.shields.io/npm/dm/feather-icons.svg?style=flat-square)](https://npm-stat.com/charts.html?package=feather-icons&from=2017-06-01) [![npm](https://img.shields.io/npm/dm/feather-icons.svg?style=flat-square)](https://npm-stat.com/charts.html?package=feather-icons&from=2017-06-01)
[![Code Climate](https://img.shields.io/codeclimate/github/colebemis/feather.svg?style=flat-square)](https://codeclimate.com/github/colebemis/feather)
[![CDNJS version](https://img.shields.io/cdnjs/v/feather-icons.svg?style=flat-square)](https://cdnjs.com/libraries/feather-icons) [![CDNJS version](https://img.shields.io/cdnjs/v/feather-icons.svg?style=flat-square)](https://cdnjs.com/libraries/feather-icons)
[![Code Climate](https://img.shields.io/codeclimate/github/colebemis/feather.svg?style=flat-square)](https://codeclimate.com/github/colebemis/feather)
## What is Feather? ## What is Feather?
Feather is a collection of **simply beautiful open source icons**. Each icon is designed on a 24x24 grid with an emphasis on simplicity, consistency and readability. Feather is a collection of simply beautiful open source icons. Each icon is designed on a 24x24 grid with an emphasis on simplicity, consistency and readability.
**[feathericons.com](https://feathericons.com)** **[feathericons.com](https://feathericons.com)**
@ -20,12 +20,13 @@ npm install feather-icons
* [Quick Start](#quick-start) * [Quick Start](#quick-start)
* [Usage](#usage) * [Usage](#usage)
* [Client-side JavaScript](#client-side-javascript) * [Client-side](#client-side)
* [Node](#node) * [Node](#node)
* [API Reference](#api-reference) * [API Reference](#api-reference)
* [`feather.icons`](#feathericons) * [`feather.icons`](#feathericons)
* [`feather.toSvg()`](#feathertosvgkey-options) * [`feather.icons[name].toSvg()`](#feathericonsnametosvgattrs)
* [`feather.replace()`](#featherreplaceoptions) * [`feather.replace()`](#featherreplaceattrs)
* [[DEPRECATED] `feather.toSvg()`](#deprecated-feathertosvgname-attrs)
* [Roadmap](#roadmap) * [Roadmap](#roadmap)
* [Contributing](#contributing) * [Contributing](#contributing)
* [Related Projects](#related-projects) * [Related Projects](#related-projects)
@ -60,7 +61,7 @@ At its core, Feather is a collection of [SVG](https://svgontheweb.com/#svg) file
The following are additional ways you can use Feather. The following are additional ways you can use Feather.
### Client-side JavaScript ### Client-side
#### 1. Install #### 1. Install
@ -79,7 +80,7 @@ Or just copy [`feather.js`](https://unpkg.com/feather-icons/dist/feather.js) or
Include `feather.js` or `feather.min.js` with a `<script>` tag. These files are located in the `dist` directory of the npm package. Include `feather.js` or `feather.min.js` with a `<script>` tag. These files are located in the `dist` directory of the npm package.
```html ```html
<script src="path/to/dist/feather.min.js"></script> <script src="path/to/dist/feather.js"></script>
``` ```
Or load the script from a CDN provider. Or load the script from a CDN provider.
@ -104,7 +105,7 @@ See the complete list of icons at [feathericons.com](https://feathericons.com).
#### 4. Replace #### 4. Replace
Call the `feather.replace` method. Call the `feather.replace()` method.
```html ```html
<script> <script>
@ -126,44 +127,75 @@ npm install feather-icons --save
#### 2. Require #### 2. Require
```javascript ```javascript
var feather = require('feather-icons') const feather = require('feather-icons')
``` ```
#### 3. Use #### 3. Use
```javascript
feather.icons.circle
// <circle cx="12" cy="12" r="10"></circle>
feather.toSvg('circle') ```js
// '<svg class="feather feather-circle" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle></svg>' feather.icons.x
// {
// name: 'x',
// contents: '<line ... /><line ... />`,
// tags: ['cancel', 'close', 'delete', 'remove'],
// attrs: {
// class: 'feather feather-x',
// xmlns: 'http://www.w3.org/2000/svg',
// width: 24,
// height: 24,
// viewBox: '0 0 24 24',
// fill: 'none',
// stroke: 'currentColor',
// 'stroke-width': 2,
// 'stroke-linecap': 'round',
// 'stroke-linejoin': 'round',
// }
// }
feather.toSvg('circle', { class: 'my-class', 'stroke-width': 1 }) feather.icons.x.toSvg()
// '<svg class="feather feather-circle my-class" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle></svg>' // <svg class="feather feather-x" ...><line ... /><line ... /></svg>
feather.icons.x.toSvg({ class: 'foo bar', 'stroke-width': 1, color: 'red' })
// <svg class="feather feather-x foo bar" stroke-width="1" color="red" ...><line ... /><line ... /></svg>
``` ```
See the [API Reference](#api-reference) for more information about the available properties and methods of the `feather` object. See the [API Reference](#api-reference) for more information about the available properties and methods of the `feather` object.
### Sprite
*Coming soon*
## API Reference ## API Reference
### `feather.icons` ### `feather.icons`
An object with SVG path information for every icon. An object with information about every icon.
#### Usage #### Usage
```javascript ```js
feather.icons.circle feather.icons.x
// <circle cx="12" cy="12" r="10"></circle> // {
// name: 'x',
// contents: '<line ... /><line ... />`,
// tags: ['cancel', 'close', 'delete', 'remove'],
// attrs: {
// class: 'feather feather-x',
// xmlns: 'http://www.w3.org/2000/svg',
// width: 24,
// height: 24,
// viewBox: '0 0 24 24',
// fill: 'none',
// stroke: 'currentColor',
// 'stroke-width': 2,
// 'stroke-linecap': 'round',
// 'stroke-linejoin': 'round',
// }
// }
feather.icons.clock feather.icons.x.toString()
// '<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 15 15"/>' // '<line ... /><line ... />`
``` ```
### `feather.toSvg(key, [options])` [View Source](https://github.com/colebemis/feather/blob/master/src/icons.js)
### `feather.icons[name].toSvg([attrs])`
Returns an SVG string. Returns an SVG string.
@ -171,25 +203,24 @@ Returns an SVG string.
| Name | Type | Description | | Name | Type | Description |
| --------- | ------ | ----------- | | --------- | ------ | ----------- |
| `key` | string | Icon name | | `attrs` (optional) | Object | Key-value pairs in the `attrs` object will be mapped to HTML attributes on the `<svg>` tag (e.g. `{ foo: 'bar' }` maps to `foo="bar"`). All default attributes on the `<svg>` tag can be overridden with the `attrs` object. |
| `options` (optional) | Object | Key-value pairs in the `options` object will be mapped to HTML attributes on the `<svg>` tag (e.g. `{ foo: 'bar' }` maps to `foo="bar"`). All default attributes on the `<svg>` tag can be overridden with the `options` object. |
#### Usage #### Usage
```javascript ```javascript
feather.toSvg('circle') feather.icons.circle.toSvg()
// '<svg class="feather feather-circle" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle></svg>' // '<svg class="feather feather-circle" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle></svg>'
feather.toSvg('circle', { 'stroke-width': 1 }) feather.icons.circle.toSvg({ 'stroke-width': 1 })
// '<svg class="feather feather-circle" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle></svg>' // '<svg class="feather feather-circle" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle></svg>'
feather.toSvg('circle', { class: 'foo bar' }) feather.icons.circle.toSvg({ class: 'foo bar' })
// '<svg class="feather feather-circle foo bar" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle></svg>' // '<svg class="feather feather-circle foo bar" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle></svg>'
``` ```
[View Source](https://github.com/colebemis/feather/blob/master/src/to-svg.js) [View Source](https://github.com/colebemis/feather/blob/master/src/icons.js)
### `feather.replace([options])` ### `feather.replace([attrs])`
Replaces all elements that have a `data-feather` attribute with SVG markup corresponding to the element's `data-feather` attribute value. Replaces all elements that have a `data-feather` attribute with SVG markup corresponding to the element's `data-feather` attribute value.
@ -197,7 +228,7 @@ Replaces all elements that have a `data-feather` attribute with SVG markup corre
| Name | Type | Description | | Name | Type | Description |
| ---------- | ------ | ----------- | | ---------- | ------ | ----------- |
| `options` (optional) | Object | Key-value pairs in the `options` object will be mapped to HTML attributes on the `<svg>` tag (e.g. `{ foo: 'bar' }` maps to `foo="bar"`). All default attributes on the `<svg>` tag can be overridden with the `options` object. | | `attrs` (optional) | Object | Key-value pairs in the `attrs` object will be mapped to HTML attributes on the `<svg>` tag (e.g. `{ foo: 'bar' }` maps to `foo="bar"`). All default attributes on the `<svg>` tag can be overridden with the `attrs` object. |
#### Usage #### Usage
@ -216,7 +247,7 @@ Simple usage:
</script> </script>
``` ```
You can pass `feather.replace()` an `options` object: You can pass `feather.replace()` an `attrs` object:
```html ```html
<i data-feather="circle"></i> <i data-feather="circle"></i>
<!-- <!--
@ -229,13 +260,13 @@ You can pass `feather.replace()` an `options` object:
</script> </script>
``` ```
The id and classes on a placeholder element (i.e. `<i>`) will be copied to the `<svg>` tag: All attributes on the placeholder element (i.e. `<i>`) will be copied to the `<svg>` tag:
```html ```html
<i id="my-circle-icon" class="foo bar" data-feather="circle"></i> <i data-feather="circle" id="my-circle" class="foo bar" stroke-width="1"></i>
<!-- <!--
<i> will be replaced with: <i> will be replaced with:
<svg id="my-circle-icon" class="feather feather-circle foo bar" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle></svg> <svg id="my-circle" class="feather feather-circle foo bar" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle></svg>
--> -->
<script> <script>
@ -245,19 +276,43 @@ The id and classes on a placeholder element (i.e. `<i>`) will be copied to the `
[View Source](https://github.com/colebemis/feather/blob/master/src/replace.js) [View Source](https://github.com/colebemis/feather/blob/master/src/replace.js)
### [DEPRECATED] `feather.toSvg(name, [attrs])`
> **Note:** `feather.toSvg()` is deprecated. Please use `feather.icons[name].toSvg()` instead.
Returns an SVG string.
#### Parameters
| Name | Type | Description |
| --------- | ------ | ----------- |
| `name` | string | Icon name |
| `attrs` (optional) | Object | Key-value pairs in the `attrs` object will be mapped to HTML attributes on the `<svg>` tag (e.g. `{ foo: 'bar' }` maps to `foo="bar"`). All default attributes on the `<svg>` tag can be overridden with the `attrs` object. |
#### Usage
```javascript
feather.toSvg('circle')
// '<svg class="feather feather-circle" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle></svg>'
feather.toSvg('circle', { 'stroke-width': 1 })
// '<svg class="feather feather-circle" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle></svg>'
feather.toSvg('circle', { class: 'foo bar' })
// '<svg class="feather feather-circle foo bar" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle></svg>'
```
[View Source](https://github.com/colebemis/feather/blob/master/src/to-svg.js)
## Roadmap ## Roadmap
- [x] Write contributing guidelines
- [ ] Write icon design guidelines - [ ] Write icon design guidelines
- [ ] Add usage examples
- [ ] Add SVG sprite
- [ ] Add tests
- [ ] Track code coverage - [ ] Track code coverage
- [ ] Use Prettier to enforce consistent code style - [ ] Use Prettier to enforce consistent code style
- [ ] Add search/filter functionality to project website
- [ ] Handle icon aliases
- [ ] Handle usage of custom icons
- [ ] Improve SVG accessibility - [ ] Improve SVG accessibility
- [ ] Handle usage of custom icons
- [ ] Add usage examples
- [ ] Make `<feather-icon>` web component
## Contributing ## Contributing

View File

@ -0,0 +1,8 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`builds object correctly 1`] = `
Object {
"icon1": "<line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\"></line><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\"></line>",
"icon2": "<circle cx=\\"12\\" cy=\\"12\\" r=\\"11\\"></circle>",
}
`;

View File

@ -0,0 +1,17 @@
/* eslint-env jest */
import buildIconsObject from '../build-icons-object';
const SVG_FILES = {
'icon1.svg':
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="23" y1="1" x2="1" y2="23" /><line x1="1" y1="1" x2="23" y2="23" /></svg>',
'icon2.svg':
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="11" /></svg>',
};
function getSvg(svgFile) {
return SVG_FILES[svgFile];
}
test('builds object correctly', () => {
expect(buildIconsObject(Object.keys(SVG_FILES), getSvg)).toMatchSnapshot();
});

19
bin/build-icons-json.js Normal file
View File

@ -0,0 +1,19 @@
import fs from 'fs';
import path from 'path';
import buildIconsObject from './build-icons-object';
const IN_DIR = path.resolve(__dirname, '../icons');
const OUT_FILE = path.resolve(__dirname, '../dist/icons.json');
console.log(`Building ${OUT_FILE}`); // eslint-disable-line no-console
const svgFiles = fs
.readdirSync(IN_DIR)
.filter(file => path.extname(file) === '.svg');
const getSvg = svgFile => fs.readFileSync(path.join(IN_DIR, svgFile));
const icons = buildIconsObject(svgFiles, getSvg);
fs.writeFileSync(OUT_FILE, JSON.stringify(icons));

34
bin/build-icons-object.js Normal file
View File

@ -0,0 +1,34 @@
/* eslint-disable import/no-extraneous-dependencies */
import path from 'path';
import cheerio from 'cheerio';
/**
* Build an object in the format: `{ <name>: <contents> }`.
* @param {string[]} svgFiles - A list of file names.
* @param {Function} getSvg - A function that returns the contents of an SVG file.
* @returns {Object}
*/
function buildIconsObject(svgFiles, getSvg) {
return svgFiles
.map(svgFile => {
const name = path.basename(svgFile, '.svg');
const svg = getSvg(svgFile);
const contents = getSvgContents(svg);
return { name, contents };
})
.reduce((icons, icon) => {
icons[icon.name] = icon.contents;
return icons;
}, {});
}
/**
* Get contents between opening and closing `<svg>` tags.
* @param {string} svg
*/
function getSvgContents(svg) {
const $ = cheerio.load(svg);
return $('svg').html();
}
export default buildIconsObject;

View File

@ -1,71 +0,0 @@
/**
* @file Builds `icons.json` from `icons` directory.
*/
/* eslint-disable import/no-extraneous-dependencies */
import fs from 'fs';
import path from 'path';
import RSVP from 'rsvp';
import Svgo from 'svgo';
import parse5 from 'parse5';
const svgFiles = fs.readdirSync(path.resolve(__dirname, '../icons'))
.filter(file => path.extname(file) === '.svg');
buildIconsObject(svgFiles)
.then(icons => {
fs.writeFileSync(
path.resolve(__dirname, '../dist/icons.json'),
JSON.stringify(icons),
);
});
/**
* Build an icons object in the format: `{ <icon name>: <svg content> }`.
* @param {string[]} svgFiles - A list of file names.
* @returns {RSVP.Promise<Object>}
*/
function buildIconsObject(svgFiles) {
const icons = {};
svgFiles.forEach(svgFile => {
const svg = fs.readFileSync(path.resolve(__dirname, '../icons', svgFile), 'utf8');
const key = path.basename(svgFile, '.svg');
icons[key] = optimizeSvg(svg)
.then(optimizedSvg => getSvgContent(optimizedSvg));
});
return RSVP.hash(icons);
}
/**
* Optimize SVG with `svgo`.
* @param {string} svg - An SVG string.
* @returns {RSVP.Promise<string>}
*/
function optimizeSvg(svg) {
// configure svgo
const svgo = new Svgo({
plugins: [
{ convertShapeToPath: false },
{ mergePaths: false },
{ removeAttrs: { attrs: '(fill|stroke.*)' } },
],
});
return new RSVP.Promise(resolve => {
svgo.optimize(svg, ({ data }) => resolve(data));
});
}
/**
* Get content between opening and closing `<svg>` tags.
* @param {string} svg - An SVG string.
* @returns {string}
*/
function getSvgContent(svg) {
const fragment = parse5.parseFragment(svg);
const content = parse5.serialize(fragment.childNodes[0]);
return content;
}

View File

@ -1,13 +1,13 @@
/**
* @file Builds `dist/icons` directory.
*/
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import { icons, toSvg } from '../src'; import icons from '../src/icons';
Object.keys(icons).forEach(icon => { const OUT_DIR = path.resolve(__dirname, '../dist/icons');
const svg = toSvg(icon);
fs.writeFileSync(path.resolve(__dirname, `../dist/icons/${icon}.svg`), svg); console.log(`Building SVGs in ${OUT_DIR}`); // eslint-disable-line no-console
Object.keys(icons).forEach(name => {
const svg = icons[name].toSvg();
fs.writeFileSync(path.join(OUT_DIR, `${name}.svg`), svg);
}); });

14
bin/build.sh Executable file
View File

@ -0,0 +1,14 @@
#!/bin/bash
./node_modules/.bin/babel-node bin/process-svgs.js
./node_modules/.bin/rimraf dist
mkdir dist
./node_modules/.bin/babel-node bin/build-icons-json.js
./node_modules/.bin/rimraf dist/icons
mkdir dist/icons
./node_modules/.bin/babel-node bin/build-svgs.js
./node_modules/.bin/webpack --output-filename feather.js
./node_modules/.bin/webpack --output-filename feather.min.js -p

View File

@ -3,7 +3,7 @@ import Svgo from 'svgo';
import cheerio from 'cheerio'; import cheerio from 'cheerio';
import { format } from 'prettier'; import { format } from 'prettier';
import DEFAULT_ATTRIBUTES from '../src/default-attributes.json'; import DEFAULT_ATTRS from '../src/default-attrs.json';
/** /**
* Process SVG string. * Process SVG string.
@ -13,7 +13,7 @@ import DEFAULT_ATTRIBUTES from '../src/default-attributes.json';
function processSvg(svg) { function processSvg(svg) {
return ( return (
optimize(svg) optimize(svg)
.then(setAttributes) .then(setAttrs)
.then(format) .then(format)
// remove semicolon inserted by prettier // remove semicolon inserted by prettier
// because prettier thinks it's formatting JSX not HTML // because prettier thinks it's formatting JSX not HTML
@ -46,11 +46,11 @@ function optimize(svg) {
* @param {string} svg - An SVG string. * @param {string} svg - An SVG string.
* @returns {string} * @returns {string}
*/ */
function setAttributes(svg) { function setAttrs(svg) {
const $ = cheerio.load(svg); const $ = cheerio.load(svg);
Object.keys(DEFAULT_ATTRIBUTES).forEach(key => Object.keys(DEFAULT_ATTRS).forEach(key =>
$('svg').attr(key, DEFAULT_ATTRIBUTES[key]), $('svg').attr(key, DEFAULT_ATTRS[key]),
); );
return $('body').html(); return $('body').html();

View File

@ -1,15 +1,18 @@
/* eslint-disable import/no-extraneous-dependencies */
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import processSvg from './process-svg'; import processSvg from './process-svg';
const ICONS_DIR = path.resolve(__dirname, '../icons'); const IN_DIR = path.resolve(__dirname, '../icons');
console.log(`Processing SVGs in ${IN_DIR}`); // eslint-disable-line no-console
fs fs
.readdirSync(ICONS_DIR) .readdirSync(IN_DIR)
.filter(file => path.extname(file) === '.svg') .filter(file => path.extname(file) === '.svg')
.forEach(svgFile => { .forEach(svgFile => {
const svg = fs.readFileSync(path.join(ICONS_DIR, svgFile)); const svg = fs.readFileSync(path.join(IN_DIR, svgFile));
processSvg(svg).then(svg => fs.writeFileSync(path.join(ICONS_DIR, svgFile), svg)); processSvg(svg).then(svg =>
fs.writeFileSync(path.join(IN_DIR, svgFile), svg),
);
}); });

240
package-lock.json generated
View File

@ -304,12 +304,30 @@
"integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=",
"dev": true "dev": true
}, },
"array-filter": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/array-filter/-/array-filter-0.0.1.tgz",
"integrity": "sha1-fajPLiZijtcygDWB/SH2fKzS7uw=",
"dev": true
},
"array-find-index": { "array-find-index": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
"integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=",
"dev": true "dev": true
}, },
"array-map": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz",
"integrity": "sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI=",
"dev": true
},
"array-reduce": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/array-reduce/-/array-reduce-0.0.0.tgz",
"integrity": "sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys=",
"dev": true
},
"array-union": { "array-union": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
@ -889,6 +907,16 @@
"regexpu-core": "2.0.0" "regexpu-core": "2.0.0"
} }
}, },
"babel-plugin-transform-object-rest-spread": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz",
"integrity": "sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY=",
"dev": true,
"requires": {
"babel-plugin-syntax-object-rest-spread": "6.13.0",
"babel-runtime": "6.26.0"
}
},
"babel-plugin-transform-regenerator": { "babel-plugin-transform-regenerator": {
"version": "6.26.0", "version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz", "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz",
@ -1417,6 +1445,11 @@
"chalk": "1.1.3" "chalk": "1.1.3"
} }
}, },
"classnames": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.5.tgz",
"integrity": "sha1-+zgB1FNGdknvNgPH1hoCvRKb3m0="
},
"cli-cursor": { "cli-cursor": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz",
@ -1962,6 +1995,16 @@
} }
} }
}, },
"define-properties": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz",
"integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=",
"dev": true,
"requires": {
"foreach": "2.0.5",
"object-keys": "1.0.11"
}
},
"del": { "del": {
"version": "2.2.2", "version": "2.2.2",
"resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz",
@ -2184,6 +2227,30 @@
"is-arrayish": "0.2.1" "is-arrayish": "0.2.1"
} }
}, },
"es-abstract": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.9.0.tgz",
"integrity": "sha512-kk3IJoKo7A3pWJc0OV8yZ/VEX2oSUytfekrJiqoxBlKJMFAJVJVpGdHClCCTdv+Fn2zHfpDHHIelMFhZVfef3Q==",
"dev": true,
"requires": {
"es-to-primitive": "1.1.1",
"function-bind": "1.1.1",
"has": "1.0.1",
"is-callable": "1.1.3",
"is-regex": "1.0.4"
}
},
"es-to-primitive": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz",
"integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=",
"dev": true,
"requires": {
"is-callable": "1.1.3",
"is-date-object": "1.0.1",
"is-symbol": "1.0.1"
}
},
"es5-ext": { "es5-ext": {
"version": "0.10.35", "version": "0.10.35",
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.35.tgz", "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.35.tgz",
@ -2998,6 +3065,12 @@
"for-in": "1.0.2" "for-in": "1.0.2"
} }
}, },
"foreach": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz",
"integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=",
"dev": true
},
"foreachasync": { "foreachasync": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/foreachasync/-/foreachasync-3.0.0.tgz", "resolved": "https://registry.npmjs.org/foreachasync/-/foreachasync-3.0.0.tgz",
@ -4619,6 +4692,12 @@
"builtin-modules": "1.1.1" "builtin-modules": "1.1.1"
} }
}, },
"is-callable": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz",
"integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI=",
"dev": true
},
"is-ci": { "is-ci": {
"version": "1.0.10", "version": "1.0.10",
"resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.0.10.tgz", "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.0.10.tgz",
@ -4628,6 +4707,12 @@
"ci-info": "1.1.1" "ci-info": "1.1.1"
} }
}, },
"is-date-object": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz",
"integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=",
"dev": true
},
"is-dotfile": { "is-dotfile": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz",
@ -4751,6 +4836,15 @@
"integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=",
"dev": true "dev": true
}, },
"is-regex": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz",
"integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=",
"dev": true,
"requires": {
"has": "1.0.1"
}
},
"is-resolvable": { "is-resolvable": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz", "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz",
@ -4766,6 +4860,12 @@
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
"dev": true "dev": true
}, },
"is-symbol": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz",
"integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=",
"dev": true
},
"is-typedarray": { "is-typedarray": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
@ -5827,6 +5927,12 @@
"integrity": "sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w==", "integrity": "sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w==",
"dev": true "dev": true
}, },
"json-parse-better-errors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.1.tgz",
"integrity": "sha512-xyQpxeWWMKyJps9CuGJYeng6ssI5bpqS9ltQpdVQ90t4ql6NdnxFKh95JcRt2cun/DjMVNrdjniLPuMA69xmCw==",
"dev": true
},
"json-schema": { "json-schema": {
"version": "0.2.3", "version": "0.2.3",
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
@ -6207,6 +6313,12 @@
"readable-stream": "2.3.3" "readable-stream": "2.3.3"
} }
}, },
"memorystream": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz",
"integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=",
"dev": true
},
"meow": { "meow": {
"version": "3.7.0", "version": "3.7.0",
"resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
@ -6565,6 +6677,96 @@
"slide": "1.1.6" "slide": "1.1.6"
} }
}, },
"npm-run-all": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.2.tgz",
"integrity": "sha512-Z2aRlajMK4SQ8u19ZA75NZZu7wupfCNQWdYosIi8S6FgBdGf/8Y6Hgyjdc8zU2cYmIRVCx1nM80tJPkdEd+UYg==",
"dev": true,
"requires": {
"ansi-styles": "3.2.0",
"chalk": "2.3.0",
"cross-spawn": "5.1.0",
"memorystream": "0.3.1",
"minimatch": "3.0.4",
"ps-tree": "1.1.0",
"read-pkg": "3.0.0",
"shell-quote": "1.6.1",
"string.prototype.padend": "3.0.0"
},
"dependencies": {
"ansi-styles": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
"integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==",
"dev": true,
"requires": {
"color-convert": "1.9.1"
}
},
"chalk": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz",
"integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==",
"dev": true,
"requires": {
"ansi-styles": "3.2.0",
"escape-string-regexp": "1.0.5",
"supports-color": "4.5.0"
}
},
"load-json-file": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
"integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=",
"dev": true,
"requires": {
"graceful-fs": "4.1.11",
"parse-json": "4.0.0",
"pify": "3.0.0",
"strip-bom": "3.0.0"
}
},
"parse-json": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
"integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
"dev": true,
"requires": {
"error-ex": "1.3.1",
"json-parse-better-errors": "1.0.1"
}
},
"path-type": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz",
"integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==",
"dev": true,
"requires": {
"pify": "3.0.0"
}
},
"read-pkg": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
"integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=",
"dev": true,
"requires": {
"load-json-file": "4.0.0",
"normalize-package-data": "2.4.0",
"path-type": "3.0.0"
}
},
"supports-color": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz",
"integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=",
"dev": true,
"requires": {
"has-flag": "2.0.0"
}
}
}
},
"npm-run-path": { "npm-run-path": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
@ -6662,6 +6864,12 @@
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
"dev": true "dev": true
}, },
"object-keys": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz",
"integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=",
"dev": true
},
"object.omit": { "object.omit": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz",
@ -7080,6 +7288,15 @@
"integrity": "sha1-GoS4WQgyVQFBGFPQCB7j+obikmo=", "integrity": "sha1-GoS4WQgyVQFBGFPQCB7j+obikmo=",
"dev": true "dev": true
}, },
"ps-tree": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.1.0.tgz",
"integrity": "sha1-tCGyQUDWID8e08dplrRCewjowBQ=",
"dev": true,
"requires": {
"event-stream": "3.3.4"
}
},
"pseudomap": { "pseudomap": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
@ -7671,6 +7888,18 @@
"integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
"dev": true "dev": true
}, },
"shell-quote": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.6.1.tgz",
"integrity": "sha1-9HgZSczkAmlxJ0MOo7PFR29IF2c=",
"dev": true,
"requires": {
"array-filter": "0.0.1",
"array-map": "0.0.0",
"array-reduce": "0.0.0",
"jsonify": "0.0.0"
}
},
"shelljs": { "shelljs": {
"version": "0.7.6", "version": "0.7.6",
"resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.6.tgz", "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.6.tgz",
@ -7897,6 +8126,17 @@
"strip-ansi": "3.0.1" "strip-ansi": "3.0.1"
} }
}, },
"string.prototype.padend": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.0.0.tgz",
"integrity": "sha1-86rvfBcZ8XDF6rHDK/eA2W4h8vA=",
"dev": true,
"requires": {
"define-properties": "1.1.2",
"es-abstract": "1.9.0",
"function-bind": "1.1.1"
}
},
"string_decoder": { "string_decoder": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",

View File

@ -7,30 +7,36 @@
"dist" "dist"
], ],
"scripts": { "scripts": {
"all": "make", "start": "npm-run-all --sequential build lint test",
"lint": "make lint", "build": "./bin/build.sh",
"test": "make test", "lint": "eslint .",
"build": "make build", "test": "jest",
"commitmsg": "validate-commit-msg", "commitmsg": "validate-commit-msg",
"cm": "git-cz", "cm": "git-cz",
"semantic-release": "semantic-release pre && npm publish && semantic-release post" "semantic-release": "semantic-release pre && npm publish && semantic-release post"
}, },
"dependencies": {
"classnames": "^2.2.5"
},
"devDependencies": { "devDependencies": {
"babel-cli": "^6.24.1", "babel-cli": "^6.24.1",
"babel-loader": "^7.1.1", "babel-loader": "^7.1.1",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-preset-es2015": "^6.24.1", "babel-preset-es2015": "^6.24.1",
"babel-register": "^6.24.1", "babel-register": "^6.24.1",
"cheerio": "^1.0.0-rc.2", "cheerio": "^1.0.0-rc.2",
"commitizen": "^2.9.6", "commitizen": "^2.9.6",
"core-js": "^2.4.1", "core-js": "^2.4.1",
"cz-conventional-changelog": "^2.0.0", "cz-conventional-changelog": "^2.1.0",
"eslint": "^4.0.0", "eslint": "^4.0.0",
"eslint-config-airbnb-base": "^11.2.0", "eslint-config-airbnb-base": "^11.2.0",
"eslint-plugin-import": "^2.5.0", "eslint-plugin-import": "^2.5.0",
"husky": "^0.13.4", "husky": "^0.13.4",
"jest": "^21.2.1", "jest": "^21.2.1",
"npm-run-all": "^4.1.2",
"parse5": "^3.0.2", "parse5": "^3.0.2",
"prettier": "^1.8.2", "prettier": "^1.8.2",
"rimraf": "^2.6.2",
"rsvp": "^3.6.0", "rsvp": "^3.6.0",
"semantic-release": "^6.3.6", "semantic-release": "^6.3.6",
"svgo": "^0.7.2", "svgo": "^0.7.2",

View File

@ -0,0 +1,54 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`constructs icon object correctly 1`] = `
Icon {
"attrs": Object {
"class": "feather feather-test",
"fill": "none",
"height": 24,
"stroke": "currentColor",
"stroke-linecap": "round",
"stroke-linejoin": "round",
"stroke-width": 2,
"viewBox": "0 0 24 24",
"width": 24,
"xmlns": "http://www.w3.org/2000/svg",
},
"contents": "<line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\" /><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\" />",
"name": "test",
"tags": Array [
"hello",
"world",
"foo",
"bar",
],
}
`;
exports[`constructs icon object correctly 2`] = `
Icon {
"attrs": Object {
"class": "feather feather-test",
"fill": "none",
"height": 24,
"stroke": "currentColor",
"stroke-linecap": "round",
"stroke-linejoin": "round",
"stroke-width": 2,
"viewBox": "0 0 24 24",
"width": 24,
"xmlns": "http://www.w3.org/2000/svg",
},
"contents": "<line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\" /><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\" />",
"name": "test",
"tags": Array [],
}
`;
exports[`toString() returns correct string 1`] = `"<line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\" /><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\" />"`;
exports[`toSvg() returns correct string 1`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-width=\\"2\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" class=\\"feather feather-test\\"><line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\" /><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\" /></svg>"`;
exports[`toSvg() returns correct string 2`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-width=\\"1\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" class=\\"feather feather-test\\" color=\\"red\\"><line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\" /><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\" /></svg>"`;
exports[`toSvg() returns correct string 3`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-width=\\"2\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" class=\\"feather feather-test foo bar\\" color=\\"red\\"><line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\" /><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\" /></svg>"`;

View File

@ -0,0 +1,45 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`exports correct object 1`] = `
Object {
"icon1": Icon {
"attrs": Object {
"class": "feather feather-icon1",
"fill": "none",
"height": 24,
"stroke": "currentColor",
"stroke-linecap": "round",
"stroke-linejoin": "round",
"stroke-width": 2,
"viewBox": "0 0 24 24",
"width": 24,
"xmlns": "http://www.w3.org/2000/svg",
},
"contents": "<line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\" /><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\" />",
"name": "icon1",
"tags": Array [
"foo",
"bar",
"hello",
"world",
],
},
"icon2": Icon {
"attrs": Object {
"class": "feather feather-icon2",
"fill": "none",
"height": 24,
"stroke": "currentColor",
"stroke-linecap": "round",
"stroke-linejoin": "round",
"stroke-width": 2,
"viewBox": "0 0 24 24",
"width": 24,
"xmlns": "http://www.w3.org/2000/svg",
},
"contents": "<circle cx=\\"12\\" cy=\\"12\\" r=\\"11\\" />",
"name": "icon2",
"tags": Array [],
},
}
`;

View File

@ -0,0 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`copies placeholder element attributes to <svg> tag 1`] = `"<i data-feather=\\"icon1\\" id=\\"test\\" class=\\"foo bar\\" stroke-width=\\"1\\"></i>"`;
exports[`copies placeholder element attributes to <svg> tag 2`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-width=\\"1\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" class=\\"feather feather-icon1 foo bar\\" id=\\"test\\"><line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\"></line><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\"></line></svg>"`;
exports[`replaces [data-feather] elements with SVG markup 1`] = `"<i data-feather=\\"icon1\\"></i><span data-feather=\\"icon2\\"></span>"`;
exports[`replaces [data-feather] elements with SVG markup 2`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-width=\\"2\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" class=\\"feather feather-icon1\\"><line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\"></line><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\"></line></svg><svg xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-width=\\"2\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" class=\\"feather feather-icon2\\"><circle cx=\\"12\\" cy=\\"12\\" r=\\"11\\"></circle></svg>"`;
exports[`sets attributes passed as parameters 1`] = `"<i data-feather=\\"icon1\\" id=\\"test\\" class=\\"foo bar\\" stroke-width=\\"1\\"></i>"`;
exports[`sets attributes passed as parameters 2`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-width=\\"1\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" class=\\"feather feather-icon1 foo bar hello\\" color=\\"salmon\\" id=\\"test\\"><line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\"></line><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\"></line></svg>"`;

View File

@ -0,0 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`returns correct string 1`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-width=\\"2\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" class=\\"feather feather-icon1\\"><line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\" /><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\" /></svg>"`;
exports[`returns correct string 2`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-width=\\"1\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" class=\\"feather feather-icon1\\" color=\\"red\\"><line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\" /><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\" /></svg>"`;
exports[`returns correct string 3`] = `"<svg xmlns=\\"http://www.w3.org/2000/svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\" fill=\\"none\\" stroke=\\"currentColor\\" stroke-width=\\"2\\" stroke-linecap=\\"round\\" stroke-linejoin=\\"round\\" class=\\"feather feather-icon1 foo bar\\" color=\\"red\\"><line x1=\\"23\\" y1=\\"1\\" x2=\\"1\\" y2=\\"23\\" /><line x1=\\"1\\" y1=\\"1\\" x2=\\"23\\" y2=\\"23\\" /></svg>"`;

View File

@ -0,0 +1,28 @@
/* eslint-env jest */
import Icon from '../icon';
const icon1 = new Icon(
'test',
'<line x1="23" y1="1" x2="1" y2="23" /><line x1="1" y1="1" x2="23" y2="23" />',
['hello', 'world', 'foo', 'bar'],
);
const icon2 = new Icon(
'test',
'<line x1="23" y1="1" x2="1" y2="23" /><line x1="1" y1="1" x2="23" y2="23" />',
);
test('constructs icon object correctly', () => {
expect(icon1).toMatchSnapshot();
expect(icon2).toMatchSnapshot();
});
test('toSvg() returns correct string', () => {
expect(icon1.toSvg()).toMatchSnapshot();
expect(icon1.toSvg({ 'stroke-width': 1, color: 'red' })).toMatchSnapshot();
expect(icon1.toSvg({ class: 'foo bar', color: 'red' })).toMatchSnapshot();
});
test('toString() returns correct string', () => {
expect(icon1.toString()).toMatchSnapshot();
});

View File

@ -0,0 +1,16 @@
/* eslint-env jest */
import icons from '../icons';
jest.mock('../../dist/icons.json', () => ({
icon1:
'<line x1="23" y1="1" x2="1" y2="23" /><line x1="1" y1="1" x2="23" y2="23" />',
icon2: '<circle cx="12" cy="12" r="11" />',
}));
jest.mock('../tags.json', () => ({
icon1: ['foo', 'bar', 'hello', 'world'],
}));
test('exports correct object', () => {
expect(icons).toMatchSnapshot();
});

View File

@ -0,0 +1,8 @@
/* eslint-env jest */
import feather from '../..';
test('has correct properties', () => {
expect(feather).toHaveProperty('icons');
expect(feather).toHaveProperty('toSvg');
expect(feather).toHaveProperty('replace');
});

View File

@ -0,0 +1,32 @@
/* eslint-env jest, browser */
import replace from '../replace';
jest.mock('../../dist/icons.json', () => ({
icon1:
'<line x1="23" y1="1" x2="1" y2="23" /><line x1="1" y1="1" x2="23" y2="23" />',
icon2: '<circle cx="12" cy="12" r="11" />',
}));
test('replaces [data-feather] elements with SVG markup', () => {
document.body.innerHTML =
'<i data-feather="icon1"></i><span data-feather="icon2"></i>';
expect(document.body.innerHTML).toMatchSnapshot();
replace();
expect(document.body.innerHTML).toMatchSnapshot();
});
test('copies placeholder element attributes to <svg> tag', () => {
document.body.innerHTML =
'<i data-feather="icon1" id="test" class="foo bar" stroke-width="1"></i>';
expect(document.body.innerHTML).toMatchSnapshot();
replace();
expect(document.body.innerHTML).toMatchSnapshot();
});
test('sets attributes passed as parameters', () => {
document.body.innerHTML =
'<i data-feather="icon1" id="test" class="foo bar" stroke-width="1"></i>';
expect(document.body.innerHTML).toMatchSnapshot();
replace({ class: 'foo bar hello', 'stroke-width': 1.5, color: 'salmon' });
expect(document.body.innerHTML).toMatchSnapshot();
});

View File

@ -0,0 +1,21 @@
/* eslint-env jest */
import toSvg from '../to-svg';
jest.mock('../../dist/icons.json', () => ({
icon1:
'<line x1="23" y1="1" x2="1" y2="23" /><line x1="1" y1="1" x2="23" y2="23" />',
}));
test('returns correct string', () => {
expect(toSvg('icon1')).toMatchSnapshot();
expect(toSvg('icon1', { 'stroke-width': 1, color: 'red' })).toMatchSnapshot();
expect(toSvg('icon1', { class: 'foo bar', color: 'red' })).toMatchSnapshot();
});
test('throws error when `name` parameter is undefined', () => {
expect(() => toSvg()).toThrow();
});
test('throws error when passed unknown icon name', () => {
expect(() => toSvg('foo')).toThrow();
});

55
src/icon.js Normal file
View File

@ -0,0 +1,55 @@
import classnames from 'classnames/dedupe';
import DEFAULT_ATTRS from './default-attrs.json';
class Icon {
constructor(name, contents, tags = []) {
this.name = name;
this.contents = contents;
this.tags = tags;
this.attrs = {
...DEFAULT_ATTRS,
...{ class: `feather feather-${name}` },
};
}
/**
* Create an SVG string.
* @param {Object} attrs
* @returns {string}
*/
toSvg(attrs = {}) {
const combinedAttrs = {
...this.attrs,
...attrs,
...{ class: classnames(this.attrs.class, attrs.class) },
};
return `<svg ${attrsToString(combinedAttrs)}>${this.contents}</svg>`;
}
/**
* Return string representation of an `Icon`.
*
* Added for backward compatibility. If old code expects `feather.icons.<name>`
* to be a string, `toString()` will get implicitly called.
*
* @returns {string}
*/
toString() {
return this.contents;
}
}
/**
* Convert attributes object to string of HTML attributes.
* @param {Object} attrs
* @returns {string}
*/
function attrsToString(attrs) {
return Object.keys(attrs)
.map(key => `${key}="${attrs[key]}"`)
.join(' ');
}
export default Icon;

10
src/icons.js Normal file
View File

@ -0,0 +1,10 @@
import Icon from './icon';
import icons from '../dist/icons.json';
import tags from './tags.json';
export default Object.keys(icons)
.map(key => new Icon(key, icons[key], tags[key]))
.reduce((object, icon) => {
object[icon.name] = icon;
return object;
}, {});

View File

@ -1,8 +1,4 @@
/** import icons from './icons';
* @file Exposes `feather` object.
*/
import icons from '../dist/icons.json';
import toSvg from './to-svg'; import toSvg from './to-svg';
import replace from './replace'; import replace from './replace';

View File

@ -1,54 +1,60 @@
/** /* eslint-env browser */
* @file Implements `replace` function. import classnames from 'classnames/dedupe';
*/
/* global document, DOMParser */ import icons from './icons';
import icons from '../dist/icons.json';
import toSvg from './to-svg';
/** /**
* Replace all elements that have a `data-feather` attribute with SVG markup * Replace all HTML elements that have a `data-feather` attribute with SVG markup
* corresponding to the element's `data-feather` attribute value. * corresponding to the element's `data-feather` attribute value.
* @param {Object} options * @param {Object} attrs
*/ */
export default function replace(options = {}) { function replace(attrs = {}) {
if (typeof document === 'undefined') { if (typeof document === 'undefined') {
throw new Error('`feather.replace()` only works in a browser environment.'); throw new Error('`feather.replace()` only works in a browser environment.');
} }
const elementsToReplace = document.querySelectorAll('[data-feather]'); const elementsToReplace = document.querySelectorAll('[data-feather]');
Array.from(elementsToReplace).forEach(element => replaceElement(element, options)); Array.from(elementsToReplace).forEach(element =>
replaceElement(element, attrs),
);
} }
/** /**
* Replace single element with SVG markup * Replace a single HTML element with SVG markup
* corresponding to the element's `data-feather` attribute value. * corresponding to the element's `data-feather` attribute value.
* @param {Element} element * @param {HTMLElement} element
* @param {Object} options * @param {Object} attrs
*/ */
function replaceElement(element, options) { function replaceElement(element, attrs = {}) {
const key = element.getAttribute('data-feather'); const elementAttrs = getAttrs(element);
const name = elementAttrs['data-feather'];
delete elementAttrs['data-feather'];
if (!key) { const svgString = icons[name].toSvg({
throw new Error('The required `data-feather` attribute has no value.'); ...attrs,
} ...elementAttrs,
...{ class: classnames(attrs.class, elementAttrs.class) },
if (!icons[key]) { });
throw new Error(`No icon matching '${key}'. See the complete list of icons at https://feathericons.com`); const svgDocument = new DOMParser().parseFromString(
} svgString,
'image/svg+xml',
const elementClassAttr = element.getAttribute('class') || '';
const elementIdAttr = element.getAttribute('id');
const classNames = (
options.class ? `${options.class} ${elementClassAttr}` : elementClassAttr
); );
const svgOptions = Object.assign({}, options, { class: classNames, id: elementIdAttr });
const svgString = toSvg(key, svgOptions);
const svgDocument = new DOMParser().parseFromString(svgString, 'image/svg+xml');
const svgElement = svgDocument.querySelector('svg'); const svgElement = svgDocument.querySelector('svg');
element.parentNode.replaceChild(svgElement, element); element.parentNode.replaceChild(svgElement, element);
} }
/**
* Get the attributes of an HTML element.
* @param {HTMLElement} element
* @returns {Object}
*/
function getAttrs(element) {
return Array.from(element.attributes).reduce((attrs, attr) => {
attrs[attr.name] = attr.value;
return attrs;
}, {});
}
export default replace;

7
src/tags.json Normal file
View File

@ -0,0 +1,7 @@
{
"airplay": ["stream"],
"bell": ["alarm", "notification"],
"settings": ["cog", "edit", "gear", "preferences"],
"star": ["bookmark"],
"x": ["cancel", "close", "delete", "remove"]
}

View File

@ -1,66 +1,30 @@
/** import icons from './icons';
* @file Implements `toSvg` function.
*/
import icons from '../dist/icons.json';
import DEFAULT_ATTRIBUTES from './default-attributes.json';
/** /**
* Create an SVG string. * Create an SVG string.
* @param {string} key - Icon name. * @deprecated
* @param {Object} options * @param {string} name
* @param {Object} attrs
* @returns {string} * @returns {string}
*/ */
export default function toSvg(key, options = {}) { function toSvg(name, attrs = {}) {
if (!key) { console.warn(
'feather.toSvg() is deprecated. Please use feather.icons[name].toSvg() instead.',
);
if (!name) {
throw new Error('The required `key` (icon name) parameter is missing.'); throw new Error('The required `key` (icon name) parameter is missing.');
} }
if (!icons[key]) { if (!icons[name]) {
throw new Error(`No icon matching '${key}'. See the complete list of icons at https://feathericons.com`); throw new Error(
`No icon matching '${
name
}'. See the complete list of icons at https://feathericons.com`,
);
} }
const combinedOptions = Object.assign({}, DEFAULT_ATTRIBUTES, options); return icons[name].toSvg(attrs);
combinedOptions.class = addDefaultClassNames(combinedOptions.class, key);
const attributes = optionsToAttributes(combinedOptions);
return `<svg ${attributes}>${icons[key]}</svg>`;
} }
/** export default toSvg;
* Add default class names.
* @param {string} classNames - One or more class names seperated by spaces.
* @param {string} key - Icon name.
* @returns {string}
*/
function addDefaultClassNames(classNames, key) {
// convert class names string into an array
const classNamesArray = classNames ? classNames.trim().split(/\s+/) : [];
// use Set to avoid duplicate class names
const classNamesSet = new Set(classNamesArray);
// add default class names
classNamesSet.add('feather').add(`feather-${key}`);
return Array.from(classNamesSet).join(' ');
}
/**
* Convert options object to string of html attributes.
* @param {Object} options
* @returns {string}
*/
function optionsToAttributes(options) {
const attributes = [];
Object.keys(options).forEach(key => {
if (options[key]) {
attributes.push(`${key}="${options[key]}"`);
}
});
return attributes.join(' ');
}