Skip to content

Commit

Permalink
feat: add icon
Browse files Browse the repository at this point in the history
  • Loading branch information
CyanSalt committed Jul 7, 2023
1 parent 3f25f03 commit 2fe4229
Show file tree
Hide file tree
Showing 6 changed files with 325 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export default defineConfig({
{ text: 'Divider', link: '/components/divider' },
{ text: 'Graphics', link: '/components/graphics' },
{ text: 'Grid Guide', link: '/components/grid-guide' },
{ text: 'Icon', link: '/components/icon' },
{ text: 'Link', link: '/components/link' },
{ text: 'Space', link: '/components/space' },
{ text: 'Text', link: '/components/text' },
Expand Down
129 changes: 129 additions & 0 deletions docs/components/icon.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
<script lang="ts" setup>
import { RAlert, RDetails, RIcon, RSpace, RTable, RText } from 'roughness'
</script>

# RIcon

No Picture, No Truth.

<RAlert type="info">

INFO<br>Roughness uses [Feather Icons](https://feathericons.com/).

</RAlert>

## Example

### Basic

<RDetails>
<template #summary>Show Code</template>

```vue
<script lang="ts" setup>
import { RIcon, RSpace } from 'roughness'
</script>
<template>
<RSpace>
<RIcon name="minus" />
<RIcon name="maximize-2" />
<RIcon name="x" />
</RSpace>
</template>
```

</RDetails>

<RSpace>
<RIcon name="minus" />
<RIcon name="maximize-2" />
<RIcon name="x" />
</RSpace>

### Color and Size

See [Text](/components/text).

## Usage

### Props

<RSpace overflow>
<RTable
:columns="['name', 'type', 'default', 'description']"
:rows="['name', 'reactions', '...']"
>
<template #body:*.name="{ row }">{{ row }}</template>
<template #body:name.type>

`string`

</template>
<template #body:name.default>
<RText type="error">Required</RText>
</template>
<template #body:name.description>

Name of the icon in [Feather Icons](https://feathericons.com/).

</template>

<template #body:reactions.type>

`string[]`

</template>
<template #body:reactions.default>

`[]`

</template>
<template #body:reactions.description>

States that trigger graphics redrawing.

See [Reactions](/guide/theme#reactions).

</template>

<template #body:....description>

See [Text Props](/components/text#props).

</template>
</RTable>
</RSpace>

### Styles

<RSpace overflow>
<RTable
:columns="['name', 'values', 'default', 'description']"
:rows="['color', '...']"
>
<template #body:*.name="{ row }">--r-icon-{{ row }}</template>
<template #body:color.values>

`<color>`

</template>
<template #body:color.default>

`var(--r-common-text-color)` for `default` `type`, other theme colors for other `type`

</template>
<template #body:color.description>
Color of the icon.
</template>

<template #body:....name>...</template>
<template #body:....description>

See [Text Styles](/components/text#styles).

</template>
</RTable>
</RSpace>
50 changes: 50 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"chart.js": "^4.3.0",
"chartjs-plugin-roughness": "^1.0.6",
"fast-glob": "^3.2.12",
"feather-icons": "^4.29.0",
"lodash-es": "^4.17.21",
"parse-unit": "^1.0.1",
"release-it": "^15.11.0",
Expand Down
1 change: 1 addition & 0 deletions src/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export { default as RDivider } from './divider/index.vue'
export { default as RGraphics } from './graphics/index.vue'
export { default as RGraphicsConfig } from './graphics/graphics-config.vue'
export { default as RGridGuide } from './grid-guide/index.vue'
export { default as RIcon } from './icon/index.vue'
export { default as RInput } from './input/index.vue'
export { default as RRate } from './rate/index.vue'
export { default as RSelect } from './select/index.vue'
Expand Down
143 changes: 143 additions & 0 deletions src/icon/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
<script lang="ts" setup>
import icons from 'feather-icons/dist/icons.json'
import { chunk } from 'lodash-es'
import type { Options } from 'roughjs/bin/core'
import type { Point } from 'roughjs/bin/geometry'
import type { RoughSVG } from 'roughjs/bin/svg'
import { toRef } from 'vue'
import type { ReactionProps } from '../common/utils'
import { useReactionState } from '../common/utils'
import RGraphics from '../graphics/index.vue'
import RText from '../text/index.vue'
defineOptions({
name: 'RIcon',
})
const {
name,
reactions = (() => ['']) as never,
} = defineProps<{
name: string,
} & ReactionProps>()
const svgAttrs: Partial<Record<keyof SVGSVGElement, string>> = {
viewBox: '0 0 24 24',
}
const parser = new DOMParser()
const elements = $computed<SVGElement[]>(() => {
const root = parser.parseFromString(`<svg>${icons[name]}</svg>`, 'image/svg+xml')
const doc = root.documentElement as unknown as SVGSVGElement
return Array.from(doc.children as HTMLCollectionOf<SVGElement>)
})
function getSVGElementValue(element: SVGElement, property: string) {
return element.attributes.getNamedItem(property)?.value
}
function getSVGElementValueAsNumber(element: SVGElement, property: string) {
const value = getSVGElementValue(element, property)
return value === undefined ? value : Number(value)
}
const getReactionState = useReactionState(toRef(() => reactions))
function draw(rc: RoughSVG, svg: SVGSVGElement) {
getReactionState()
const options: Options = {
stroke: 'var(--r-icon-color)',
strokeWidth: 2,
}
for (const item of elements) {
switch (item.tagName) {
case 'ellipse': {
const ellipse = rc.ellipse(
getSVGElementValueAsNumber(item, 'cx') ?? 0,
getSVGElementValueAsNumber(item, 'cy') ?? 0,
(getSVGElementValueAsNumber(item, 'rx') ?? 0) * 2,
(getSVGElementValueAsNumber(item, 'ry') ?? 0) * 2,
options,
)
svg.appendChild(ellipse)
break
}
case 'circle': {
const circle = rc.circle(
getSVGElementValueAsNumber(item, 'cx') ?? 0,
getSVGElementValueAsNumber(item, 'cy') ?? 0,
(getSVGElementValueAsNumber(item, 'r') ?? 0) * 2,
options,
)
svg.appendChild(circle)
break
}
case 'line': {
const line = rc.line(
getSVGElementValueAsNumber(item, 'x1') ?? 0,
getSVGElementValueAsNumber(item, 'y1') ?? 0,
getSVGElementValueAsNumber(item, 'x2') ?? 0,
getSVGElementValueAsNumber(item, 'y2') ?? 0,
options,
)
svg.appendChild(line)
break
}
case 'path': {
const path = rc.path(getSVGElementValue(item, 'd') ?? '', options)
svg.appendChild(path)
break
}
case 'polygon': {
const points = getSVGElementValue(item, 'points') ?? ''
const positions = chunk((points.match(/\d+(?:\.\d+)?/g) ?? []).map(Number), 2) as Point[]
const polygon = rc.polygon(positions, options)
svg.appendChild(polygon)
break
}
case 'polyline': {
const points = getSVGElementValue(item, 'points') ?? ''
const positions = chunk((points.match(/\d+(?:\.\d+)?/g) ?? []).map(Number), 2) as Point[]
const linearPath = rc.linearPath(positions, options)
svg.appendChild(linearPath)
break
}
case 'rect': {
const rectangle = rc.rectangle(
getSVGElementValueAsNumber(item, 'x') ?? 0,
getSVGElementValueAsNumber(item, 'y') ?? 0,
getSVGElementValueAsNumber(item, 'width') ?? 0,
getSVGElementValueAsNumber(item, 'height') ?? 0,
options,
)
svg.appendChild(rectangle)
break
}
}
}
}
</script>

<template>
<RText class="r-icon">
<RGraphics
:responsive="false"
v-bind="svgAttrs"
@draw="draw"
/>
</RText>
</template>

<style lang="scss" scoped>
.r-icon {
--r-icon-color: var(--r-common-text-color);
display: inline-block;
width: 1em;
height: 1em;
:deep(.r-graphics) {
width: 100%;
height: 100%;
}
}
</style>

0 comments on commit 2fe4229

Please sign in to comment.