Skip to content

Commit

Permalink
new features and improvements, antlers tag
Browse files Browse the repository at this point in the history
  • Loading branch information
eminos committed Sep 18, 2023
1 parent 6b5717a commit 63eca01
Show file tree
Hide file tree
Showing 12 changed files with 258 additions and 65 deletions.
40 changes: 37 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ A Statamic plugin for the fantastic [Iconify](https://iconify.design/) framework

This plugin gives you an Iconify fieldtype in Statamic where you can search and pick an icon from the huge icon library that Iconify provides.

![Screenshot of the search and select icon GUI](docs/screenshot1.png)

## Installation

Install this plugin using composer.
Expand All @@ -12,8 +14,18 @@ Install this plugin using composer.
composer require eminos/statamic-iconify
```

## Features

- Search the library of Iconify icons.
- Over 150 000 open source icons that are loaded on demand.
- Uses Iconify's API. (Always updated icon sets)
- You can choose to only store the selected icon's name, and load it on demand, or store the icon as "SVG data" and use the provided `{{ iconify }}` antlers tag to generate an SVG.

## Usage

Depending on how you chose to store the icon, you have a few options for rendering it on the frontend.

### Storing only the **icon name**.
You can use any of Iconifys methods/components to display the icon in the frontend.
Here is an example with their web component that fetches the icon on demand through their API.

Expand All @@ -23,12 +35,34 @@ Here is an example with their web component that fetches the icon on demand thro

Read more about how you can use the icons in the [Iconify usage documentation](https://iconify.design/docs/usage/).

### Storing the icon as **SVG Data**.

If you store the icon as "SVG Data" you can render the SVG using the provided Antlers Tag.
The obvious advantage to store the icon as SVG Data (instead of only the icon name) is that there are no more API calls to Iconify after you have picked the icon. The necessary data to render the icon is stored in your field.

```html
{{ iconify:icon_field }}
```
would render:
```html
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
<path fill="currentColor" d="M12 3L1 9l4 2.18v6L12 21l7-3.82v-6l2-1.09V17h2V9L12 3zm6.82 6L12 12.72L5.18 9L12 5.28L18.82 9zM17 15.99l-5 2.73l-5-2.73v-3.72L12 15l5-2.73v3.72z"/>
</svg>
```

You can also add other attributes and/or override the icon's default attributes **width**, **height** and **viewBox**.

```html
{{ iconify:icon_field class="text-xl" }}
```


## TODO

- Save the SVG when you pick an icon.
- Statamic Tag to render the icon (on demand or saved SVG).
- ~~Make the search modal nicer.~~
- ~~Save the SVG when you pick an icon.~~
- ~~Statamic Tag to render the icon (on demand or saved SVG).~~
- More filtering options. For example limit to specific icon vendors or licences.
- Make the search modal nicer.

## License

Expand Down
4 changes: 4 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
{
"name": "eminos/statamic-iconify",
"type": "statamic-addon",
"description": "A Statamic addon to use Iconify icons in your Statamic site.",
"license": "MIT",
"require": {
"statamic/cms": "^4.0"
},
"require-dev": {
"php": "^8.1",
"laravel/framework": "^10.8",
Expand Down
1 change: 0 additions & 1 deletion dist/build/assets/iconify-fieldtype-21371cb9.css

This file was deleted.

11 changes: 0 additions & 11 deletions dist/build/assets/iconify-fieldtype-b436dd86.js

This file was deleted.

11 changes: 11 additions & 0 deletions dist/build/assets/iconify-fieldtype-bb29bde3.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dist/build/assets/iconify-fieldtype-c7815dee.css

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

4 changes: 2 additions & 2 deletions dist/build/manifest.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"resources/css/iconify-fieldtype.css": {
"file": "assets/iconify-fieldtype-21371cb9.css",
"file": "assets/iconify-fieldtype-c7815dee.css",
"isEntry": true,
"src": "resources/css/iconify-fieldtype.css"
},
"resources/js/iconify-fieldtype.js": {
"file": "assets/iconify-fieldtype-b436dd86.js",
"file": "assets/iconify-fieldtype-bb29bde3.js",
"isEntry": true,
"src": "resources/js/iconify-fieldtype.js"
}
Expand Down
Binary file added docs/screenshot1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
171 changes: 129 additions & 42 deletions resources/js/compontents/IconifyFieldtype.vue
Original file line number Diff line number Diff line change
@@ -1,92 +1,179 @@
<template>

<div>
<div v-if="value" class="iconify-flex iconify-items-center iconify-gap-4 iconify-group">
<iconify-icon :icon="value" class="iconify-text-4xl iconify-text-gray-800" v-tooltip="{content: value, delay: 500, autoHide: false}"></iconify-icon>
<div v-if="value" class="iconify-flex iconify-items-center iconify-gap-4">
<dropdown-list placement="bottom-center">
<template #trigger>
<iconify-icon v-if="typeof value === 'string'" :icon="value" class="iconify-cursor-pointer iconify-text-4xl iconify-text-gray-800" v-tooltip="{content: value, delay: 500, autoHide: false}"></iconify-icon>

<dropdown-list class="iconify-invisible group-hover:iconify-visible">
<dropdown-item text="Change" @click="searchModalIsOpen = true" />
<svg v-else v-bind="value.attributes" class="iconify-cursor-pointer iconify-text-4xl iconify-text-gray-800" v-html="value.body" v-tooltip="{content: value.name, delay: 500, autoHide: false}"></svg>
</template>
<dropdown-item text="Change" @click="openSearchModal" />
<dropdown-item text="Remove" @click="value = ''" />
</dropdown-list>
</div>

<button v-else class="btn btn-xs" @click="searchModalIsOpen = true">Find an icon</button>
<button v-else class="btn btn-xs" @click="openSearchModal">Find an icon</button>

<modal
<stack
v-if="searchModalIsOpen"
name="search-modal"
:scrollable="true"
@closed="searchModalIsOpen = false"
@opened="modalOpened"
>
<div slot-scope="{ close }" class="iconify-p-4 iconify-flex iconify-flex-col iconify-h-[inherit]">
<div class="iconify-flex iconify-justify-between iconify-items-center iconify-mb-4">
<h2>Pick an icon</h2>
<button class="btn" @click="close">x</button>
</div>

<div class="iconify-w-full iconify-flex iconify-gap-4 iconify-mb-4">
<text-input ref="query" v-model="query" class="iconify-flex-1" @keydown.enter="search" />
<button class="btn-primary" @click="search">Search</button>
</div>
<div slot-scope="{ close }" class="iconify-flex iconify-flex-col iconify-h-full iconify-bg-white">

<header class="bg-white pl-6 pr-3 py-2 mb-4 border-b shadow-md text-lg font-medium flex items-center justify-between">
Search and select an icon
<button type="button" class="btn-close" @click="searchModalIsOpen = false">×</button>
</header>

<div class="iconify-px-6 iconify-py-3 iconify-pr-0 iconify-flex-1 iconify-flex iconify-flex-col iconify-overflow-hidden">
<div class="iconify-w-full iconify-flex iconify-gap-4 iconify-mb-4 iconify-pr-6">
<text-input ref="query" v-model="query" class="iconify-flex-1" placeholder="Search for an icon..." @keydown.enter="search" />
<button class="btn-primary" @click="search" :disabled="loading">{{ loading ? 'Searching...' : 'Search' }}</button>
<div class="btn-group">
<button class="btn px-4" :class="{ active: listType === 'grid' }" @click="listType = 'grid'">
<iconify-icon icon="ph:grid-nine-light" class="iconify-text-xl"></iconify-icon>
</button>
<button class="btn px-4" :class="{ active: listType === 'table' }" @click="listType = 'table'">
<iconify-icon icon="ph:table-light" class="iconify-text-xl"></iconify-icon>
</button>
</div>
</div>

<div v-if="result" class="iconify-overflow-y-scroll iconify-flex-1 iconify-pr-6">

<table v-if="listType === 'table'" class="data-table iconify-w-full">
<thead>
<tr>
<th>Icon</th>
<th>Name</th>
<th>Collection</th>
<th>License</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="icon in icons">
<td>
<iconify-icon :icon="icon.name" class="iconify-text-2xl iconify-text-gray-800"></iconify-icon>
</td>
<td>
<span v-text="icon.name" class="iconify-text-sm"></span>
</td>
<td>
<span v-text="icon.collection.name" class="iconify-text-sm"></span>
</td>
<td>
<span v-text="icon.collection.license.title" class="iconify-text-sm"></span>
</td>
<td>
<button class="btn btn-sm" @click="select(icon)">Select</button>
</td>
</tr>
</tbody>
</table>

<div v-if="listType === 'grid'" class="iconify-grid iconify-grid-cols-8 iconify-gap-4">

<button v-for="icon in icons" class="iconify-relative iconify-aspect-square iconify-bg-gray-100 iconify-rounded-lg iconify-flex iconify-items-center iconify-justify-center iconify-group" @click="select(icon)">

<iconify-icon :icon="icon.name" class="iconify-text-4xl iconify-text-gray-800 group-hover:iconify-scale-125 iconify-transition-all"></iconify-icon>

<div class="iconify-absolute iconify-bottom-0 iconify-left-0 iconify-right-0 iconify-p-2 iconify-bg-gray-50 iconify-text-xs iconify-text-gray-500 iconify-text-center iconify-rounded-b-lg iconify-opacity-0 group-hover:iconify-opacity-100 iconify-transition-opacity">
<span v-text="icon.name"></span>
</div>

<div class="iconify-absolute iconify-top-2 iconify-left-2 iconify-p-1 iconify-bg-gray-200 iconify-text-xs iconify-text-gray-600 iconify-rounded-sm iconify-opacity-0 group-hover:iconify-opacity-100 iconify-transition-opacity">
<span v-text="icon.collection.license.title"></span>
</div>

</button>

</div>
</div>

<div v-if="result" class="iconify-overflow-y-scroll iconify-flex-1">


<table class="data-table iconify-w-full">
<tbody>
<tr v-for="icon in result.icons">
<td>
<iconify-icon :icon="icon" class="iconify-text-2xl iconify-text-gray-800"></iconify-icon>
</td>
<td>
<span v-text="icon" class="iconify-text-sm"></span>
</td>
<td>
<button class="btn" @click="select(icon)">Pick</button>
</td>
</tr>
</tbody>
</table>
</div>



</div>
</modal>
</stack>

</div>

</template>

<script>
import { getIcon, buildIcon } from 'iconify-icon';
export default {
mixins: [Fieldtype],
data() {
return {
searchModalIsOpen: false,
listType: 'grid',
query: '',
result: null
result: null,
loading: false,
};
},
computed: {
icons() {
if (!this.result) return []
return this.result.icons.map(icon => {
return {
name: icon,
collection: this.result.collections[icon.split(':')[0]],
}
})
}
},
methods: {
openSearchModal() {
this.searchModalIsOpen = true
this.$wait(300).then(() => { this.$refs.query.$el.querySelector('input').focus() })
},
search() {
this.loading = true
ky.get('https://api.iconify.design/search?limit=999&query=' + this.query)
.json()
.then(data => {
this.result = data
console.log(data)
})
.finally(() => {
this.loading = false
})
},
getIconBuildData(icon) {
const iconBuildData = buildIcon(getIcon(icon.name))
const iconData = {
name: icon.name,
}
Object.keys(iconBuildData).forEach(key => {
iconData[key] = iconBuildData[key]
})
return iconData
},
select(icon) {
this.$emit('input', icon)
if (this.config.store_as === 'name') {
this.$emit('input', icon.name)
} else if (this.config.store_as === 'svg_data') {
this.$emit('input', this.getIconBuildData(icon))
}
this.searchModalIsOpen = false
},
modalOpened() {
this.$refs.query.$el.querySelector('input').focus()
}
}
};
</script>
17 changes: 17 additions & 0 deletions src/Fieldtypes/IconifyFieldtype.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,21 @@ public function process($data)
{
return $data;
}

protected function configFieldItems(): array
{
return [
'store_as' => [
'display' => 'Store icon as',
'instructions' => 'Choose how the selected icon should be stored.',
'type' => 'button_group',
'default' => 'name',
'options' => [
'name' => 'Icon name',
'svg_data' => 'SVG data',
],
'width' => 50
],
];
}
}
12 changes: 6 additions & 6 deletions src/ServiceProvider.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?php

namespace StatamicIconify;
use Statamic\Statamic;

use StatamicIconify\Tags\IconifyTag;
use Statamic\Providers\AddonServiceProvider;
use StatamicIconify\Fieldtypes\IconifyFieldtype;

Expand All @@ -20,9 +20,9 @@ public function __construct()
IconifyFieldtype::class,
];

// protected $publishables = [
// __DIR__.'/../dist/build' => 'build',
// ];
protected $tags = [
IconifyTag::class,
];

protected $vite = [
'hotFile' => null, // set in the constructor for reasons
Expand Down
Loading

0 comments on commit 63eca01

Please sign in to comment.