diff options
-rw-r--r-- | content/frontend/shared/components/gl_icon.vue | 60 | ||||
-rw-r--r-- | content/frontend/shared/constants.js | 3 | ||||
-rw-r--r-- | lib/filters/icons.rb | 2 | ||||
-rw-r--r-- | lib/helpers/icons_helper.rb | 2 | ||||
-rw-r--r-- | package.json | 1 | ||||
-rw-r--r-- | rollup.config.js | 2 | ||||
-rw-r--r-- | spec/frontend/shared/components/__snapshots__/gl_icon_spec.js.snap | 7 | ||||
-rw-r--r-- | spec/frontend/shared/components/gl_icon_spec.js | 77 | ||||
-rw-r--r-- | yarn.lock | 19 |
9 files changed, 171 insertions, 2 deletions
diff --git a/content/frontend/shared/components/gl_icon.vue b/content/frontend/shared/components/gl_icon.vue new file mode 100644 index 00000000..d40aded1 --- /dev/null +++ b/content/frontend/shared/components/gl_icon.vue @@ -0,0 +1,60 @@ +<script> +import data from '@gitlab/svgs/dist/icons.json'; +import iconSizeOptions from '../constants'; + +let iconValidator = () => true; + +const { icons } = data; +iconValidator = value => { + if (icons.includes(value)) { + return true; + } + // eslint-disable-next-line no-console + console.warn(`Icon '${value}' is not a known icon of @gitlab/svgs`); + return false; +}; + +/** This is a re-usable vue component for rendering a svg sprite icon + * @example + * <icon + * name="retry" + * :size="32" + * class="top" + * /> + */ +export default { + props: { + name: { + type: String, + required: true, + validator: iconValidator, + }, + size: { + type: Number, + required: false, + default: 16, + validator: value => iconSizeOptions.includes(value), + }, + className: { + type: String, + required: false, + default: '', + }, + }, + + computed: { + iconSizeClass() { + return this.size ? `s${this.size}` : ''; + }, + iconHref() { + return `#${this.name}`; + }, + }, +}; +</script> + +<template> + <svg :class="['gl-icon', iconSizeClass, className]"> + <use :href="iconHref" /> + </svg> +</template> diff --git a/content/frontend/shared/constants.js b/content/frontend/shared/constants.js new file mode 100644 index 00000000..ad764b74 --- /dev/null +++ b/content/frontend/shared/constants.js @@ -0,0 +1,3 @@ +const iconSizeOptions = [8, 10, 12, 14, 16, 18, 24, 32, 48, 72]; + +export default iconSizeOptions; diff --git a/lib/filters/icons.rb b/lib/filters/icons.rb index 9b2292db..d695b7a2 100644 --- a/lib/filters/icons.rb +++ b/lib/filters/icons.rb @@ -28,6 +28,6 @@ class IconsFilter < Nanoc::Filter end def generate(icon, size, css_class) - sprite_icon(icon, size, css_class) + icon(icon, size, css_class) end end diff --git a/lib/helpers/icons_helper.rb b/lib/helpers/icons_helper.rb index 9920e4f5..5c8410cc 100644 --- a/lib/helpers/icons_helper.rb +++ b/lib/helpers/icons_helper.rb @@ -12,7 +12,7 @@ module Nanoc::Helpers %[<div class="d-none">#{read_file(sprite_path)}</div>] end - def sprite_icon(icon_name, size, css_class) + def icon(icon_name, size = nil, css_class = nil) unless known_sprites.include?(icon_name) exception = ArgumentError.new("#{icon_name} is not a known icon in @gitlab-org/gitlab-svg") raise exception diff --git a/package.json b/package.json index 5b9f571a..961678e6 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@babel/core": "^7.6.0", "@babel/preset-env": "^7.6.0", "@gitlab/eslint-config": "^1.6.0", + "@rollup/plugin-json": "^4.0.2", "@vue/test-utils": "^1.0.0-beta.29", "babel-core": "^7.0.0-bridge.0", "babel-jest": "^24.9.0", diff --git a/rollup.config.js b/rollup.config.js index 8ce95b44..a53496c1 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -2,6 +2,7 @@ const importResolver = require('rollup-plugin-import-resolver'); const commonjs = require('rollup-plugin-commonjs'); const vue = require('rollup-plugin-vue'); const babel = require('rollup-plugin-babel'); +const json = require('@rollup/plugin-json'); const glob = require('glob'); function mapDirectory(file) { @@ -18,6 +19,7 @@ module.exports = glob.sync('content/frontend/**/*.js').map(file => ({ plugins: [ commonjs(), babel(), + json(), vue(), importResolver({ alias: { diff --git a/spec/frontend/shared/components/__snapshots__/gl_icon_spec.js.snap b/spec/frontend/shared/components/__snapshots__/gl_icon_spec.js.snap new file mode 100644 index 00000000..be478d99 --- /dev/null +++ b/spec/frontend/shared/components/__snapshots__/gl_icon_spec.js.snap @@ -0,0 +1,7 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`GlIcon component when created shows svg class "s8" and path "/path/to/icons.svg#check-circle" 1`] = ` +"<svg class=\\"gl-icon s8\\"> + <use href=\\"#check-circle\\"></use> +</svg>" +`; diff --git a/spec/frontend/shared/components/gl_icon_spec.js b/spec/frontend/shared/components/gl_icon_spec.js new file mode 100644 index 00000000..81525e87 --- /dev/null +++ b/spec/frontend/shared/components/gl_icon_spec.js @@ -0,0 +1,77 @@ +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import GlIcon from '../../../../content/frontend/shared/components/gl_icon.vue'; +import iconSizeOptions from '../../../../content/frontend/shared/constants'; + +const ICONS_PATH = '/path/to/icons.svg'; +const TEST_SIZE = 8; +const TEST_NAME = 'check-circle'; + +const localVue = createLocalVue(); + +jest.mock('@gitlab/svgs/dist/icons.svg', () => '/path/to/icons.svg'); + +describe('GlIcon component', () => { + let wrapper; + let consoleSpy; + + const createComponent = props => { + wrapper = shallowMount(GlIcon, { + propsData: { + size: TEST_SIZE, + name: TEST_NAME, + ...props, + }, + localVue, + }); + }; + + const validateSize = size => GlIcon.props.size.validator(size); + const validateName = name => GlIcon.props.name.validator(name); + + afterEach(() => { + wrapper.destroy(); + + if (consoleSpy) { + consoleSpy.mockRestore(); + } + }); + + describe('when created', () => { + beforeEach(() => { + createComponent(); + }); + + it(`shows svg class "s${TEST_SIZE}" and path "${ICONS_PATH}#${TEST_NAME}"`, () => { + expect(wrapper.html()).toMatchSnapshot(); + }); + }); + + describe('size validator', () => { + const maxSize = Math.max(...iconSizeOptions); + + it('fails with size outside options', () => { + expect(validateSize(maxSize + 10)).toBe(false); + }); + + it('passes with size in options', () => { + expect(validateSize(maxSize)).toBe(true); + }); + }); + + describe('name validator', () => { + it('fails with name that does not exist', () => { + const badName = `${TEST_NAME}-bogus-zebra`; + consoleSpy = jest.spyOn(console, 'warn').mockImplementation(); + + expect(validateName(badName)).toBe(false); + + expect(consoleSpy).toHaveBeenCalledWith( + `Icon '${badName}' is not a known icon of @gitlab/svgs`, + ); + }); + + it('passes with name that exists', () => { + expect(validateName(TEST_NAME)).toBe(true); + }); + }); +}); @@ -882,6 +882,20 @@ "@types/istanbul-reports" "^1.1.1" "@types/yargs" "^13.0.0" +"@rollup/plugin-json@^4.0.2": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@rollup/plugin-json/-/plugin-json-4.0.2.tgz#482185ee36ac7dd21c346e2dbcc22ffed0c6f2d6" + integrity sha512-t4zJMc98BdH42mBuzjhQA7dKh0t4vMJlUka6Fz0c+iO5IVnWaEMiYBy1uBj9ruHZzXBW23IPDGL9oCzBkQ9Udg== + dependencies: + "@rollup/pluginutils" "^3.0.4" + +"@rollup/pluginutils@^3.0.4": + version "3.0.8" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.0.8.tgz#4e94d128d94b90699e517ef045422960d18c8fde" + integrity sha512-rYGeAc4sxcZ+kPG/Tw4/fwJODC3IXHYDH4qusdN/b6aLw5LPUbzpecYbEJh4sVQGPFJxd2dBU4kc1H3oy9/bnw== + dependencies: + estree-walker "^1.0.1" + "@types/babel-types@*", "@types/babel-types@^7.0.0": version "7.0.7" resolved "https://registry.yarnpkg.com/@types/babel-types/-/babel-types-7.0.7.tgz#667eb1640e8039436028055737d2b9986ee336e3" @@ -2289,6 +2303,11 @@ estree-walker@^0.6.1: resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362" integrity sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w== +estree-walker@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700" + integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== + esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" |