Skip to content

Commit

Permalink
feat(NcIcon): add a generic component for icons
Browse files Browse the repository at this point in the history
Signed-off-by: Grigorii K. Shartsev <[email protected]>
  • Loading branch information
ShGKme committed Jan 4, 2025
1 parent 74bfe86 commit f64cd7b
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 0 deletions.
87 changes: 87 additions & 0 deletions src/components/NcIcon/NcIcon.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<docs>
## General

A component to render an icons. It can render:
- A Vue component icon like `vue-material-design-icons/Account.vue`
- An SVG icon like `@mdi/svg/svg/account.svg`
- A path icon like `@mdi/js/account.js`
- A class icon like `icon-account` (not recommended)
- Any icon from the slot

```vue
<script>
import IconAccountComp from 'vue-material-design-icons/Account.vue'
import IconAccountSvg from '@mdi/svg/svg/account.svg?raw'
import { mdiAccount } from '@mdi/js'

export default {
setup() {
return { IconAccountComp, IconAccountSvg, mdiAccount }
},
}
</script>

<template>
<div>
<NcIcon :icon="IconAccountComp" />
<NcIcon :icon="IconAccountSvg" />
<NcIcon :icon="mdiAccount" />
<NcIcon icon="icon-user-white" />
</div>
</template>

<style scoped>
</style>
```
</docs>

<script setup>
import { computed, useSlots } from 'vue'
import NcIconSvgWrapper from '../NcIconSvgWrapper/NcIconSvgWrapper.vue'
import NcVNodes from '../NcVNodes/NcVNodes.vue'
import { useIcon } from './useIcon.ts'
const props = defineProps({
icon: { type: [Object, String], default: undefined },
size: { type: [Number, String], default: 20 },
inline: { type: Boolean, default: false },
})
const normalizedIcon = useIcon(() => props.icon)
const normalizedSize = computed(() => typeof props.size === 'number' ? `${props.size}px` : props.size)
const slots = useSlots()
</script>

<template>
<NcVNodes v-if="slots.icon">
<slot name="icon" />
</NcVNodes>
<component :is="icon" v-else-if="normalizedIcon.type === 'component'" :size="normalizedSize" />
<NcIconSvgWrapper v-else-if="normalizedIcon.type === 'svg'" :svg="normalizedIcon.icon" :inline="inline" />
<NcIconSvgWrapper v-else-if="normalizedIcon.type === 'path'" :path="normalizedIcon.icon" :inline="inline" />
<span v-else-if="normalizedIcon.type === 'class'"
class="icon"
:class="[normalizedIcon.icon, { inline }]"
:style="'--icon-size: ' + normalizedSize"
aria-hidden="true" />
</template>

<style scoped>
.icon {
display: flex;
justify-content: center;
align-items: center;
min-width: var(--default-clickable-area);
min-height: var(--default-clickable-area);
height: var(--icon-size);
width: var(--icon-size);
&.inline {
display: inline-flex;
min-width: fit-content;
min-height: fit-content;
vertical-align: text-bottom;
}
}
</style>
38 changes: 38 additions & 0 deletions src/components/NcIcon/normalizeIcon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type { Component } from 'vue'

export type IconComponent = Component
export type IconPath = `M${number}${string}`
export type IconSvg = `<svg${string}>${string}</svg>`
export type IconUrl = `http://${string}` | `https://${string}` | `data:${string}`
export type IconClass = `icon-${string}`
export type IconGeneral = IconComponent | IconPath | IconClass | IconSvg | IconUrl

export type IconNormalized = { type: 'component', icon: IconComponent }
| { type: 'path', icon: IconPath }
| { type: 'svg', icon: IconSvg }
| { type: 'url', icon: IconUrl }
| { type: 'class', icon: IconClass }
| { type: 'unknown', icon: IconGeneral }

/**
*
* @param icon - Icon in any supported format
*/
export function normalizeIcon(icon): IconNormalized {
if (typeof icon === 'object' || typeof icon === 'function') {
return { type: 'component', icon }
}
if (icon.startsWith('<svg')) {
return { type: 'svg', icon }
}
if (icon.startsWith('M')) {
return { type: 'path', icon }
}
if (icon.startsWith('http://') || icon.startsWith('https://') || icon.startsWith('data:')) {
return { type: 'url', icon }
}
if (icon.startsWith('icon-')) {
return { type: 'class', icon }
}
return { type: 'unknown', icon }
}
11 changes: 11 additions & 0 deletions src/components/NcIcon/useIcon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { MaybeRefOrGetter } from '@vueuse/core'
import { toValue } from '@vueuse/core'
import { normalizeIcon } from './normalizeIcon'

/**
*
* @param icon - Icon in any supported format
*/
export function useIcon(icon: MaybeRefOrGetter<IconGeneral>): IconNormalized {
return normalizeIcon(toValue(icon))
}

0 comments on commit f64cd7b

Please sign in to comment.