Skip to content

Instantly share code, notes, and snippets.

@palpich
Created December 17, 2020 11:47
Show Gist options
  • Save palpich/b13fbd5212464717e854823990b9b3a8 to your computer and use it in GitHub Desktop.
Save palpich/b13fbd5212464717e854823990b9b3a8 to your computer and use it in GitHub Desktop.
<script lang="tsx">
import Vue, { VNode, AsyncComponent } from 'vue'
import { camelCase, upperFirst, mergeProps } from '@tds/c-utils'
interface Icons {
[key: string]: AsyncComponent<any>
}
const icons: Icons = {}
function getAsyncComponent(name = '') {
if (!(name in icons)) {
icons[name] = () =>
import('../icons/Icon' + upperFirst(camelCase(name)) + '.vue')
}
return icons[name]
}
/**
* Компонент работает в двух режимах - проброс иконки через дефолтный слот и асихронная загрузка.
*
* Чтобы добавить новую иконку, создайте функциональный компонент в папке
* `./icons` с именем Icon<SomeName>, где используйте только теги для рисования
* на холсте SVG. Пример компонента-иконки IconAdd.vue:
*
```
<template functional>
<path d="M19 13H13V19H11V13H5V11H11V5H13V11H19V13Z" />
</template>
```
*
* Если компонент состоит из нескольких элементов, можно использовать элемент <g>
* для группировки элементов. Пример компонента-иконки IconHome.vue:
*
```
<template functional>
<g
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
<polyline points="9 22 9 12 15 12 15 22" />
</g>
</template>
```
*
* Все иконки используют viewBox="0 0 24 24" и получить содержимое любой
* иконки, для создания компонента, можно у дизайнеров.
*/
const CIcon = Vue.extend({
functional: true,
props: {
/**
* Имя иконки, в формате camelCase
*/
name: {
type: String,
default: null,
},
color: {
type: String,
default: '',
},
size: {
type: [Number, String],
default: null,
},
},
render(h, context): VNode {
const { props, data, scopedSlots } = context
let Icon = null
/**
* Наличие слота имеет более высокий приоритет, чем асинхронная загрузка иконки
*/
if (scopedSlots.default) {
Icon = scopedSlots.default(props)
} else if (props.name) {
Icon = h(getAsyncComponent(props.name))
}
const sizeIsNumber =
props.size &&
parseInt(props.size as string).toString().length ===
props.size.toString().length
const size = sizeIsNumber ? `${props.size}px` : props.size
return (
<svg
{...mergeProps([
{
class: {
'c-icon': true,
[`color-${props.color}`]: props?.color?.length,
},
style: {
width: size,
height: size,
},
attrs: {
viewBox: '0 0 24 24',
fill: 'currentColor',
xmlns: 'http://www.w3.org/2000/svg',
'data-v-test': `с-icon-${props.name}`,
},
},
data,
])}
>
{Icon}
</svg>
)
},
})
export default CIcon
</script>
<style lang="stylus">
.c-icon
// Во флекс контейнерах иконка должна сохранять пропорции
flex 0 0 auto
width 24px
height 24px
.color-success
theme-property(color, success)
.color-warning
theme-property(color, warning)
.color-danger
theme-property(color, danger)
.color-primary
theme-property(color, primary)
.color-on-surface
theme-property(color, on-surface)
.color-status-grey
theme-property(color, status-grey)
.color-status-connectivity-problem
theme-property(color, status-connectivity-problem)
.color-status-license-problem
theme-property(color, status-license-problem)
.color-status-new
theme-property(color, status-new)
.color-status-success
theme-property(color, status-success)
.color-status-caution
theme-property(color, status-caution)
.color-status-warning
theme-property(color, status-warning)
.color-status-danger
theme-property(color, status-danger)
.color-status-performance-problem
theme-property(color, status-performance-problem)
.color-status-integration-problem
theme-property(color, status-integration-problem)
.color-status-remote
theme-property(color, status-remote)
</style>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment