forked from tsai/budibase
committed by
GitHub
15 changed files with 6824 additions and 63 deletions
@ -0,0 +1,293 @@ |
|||
<script> |
|||
import { DropdownMenu, Button, Input } from "@budibase/bbui" |
|||
import { createEventDispatcher, tick } from "svelte" |
|||
|
|||
import icons from "./icons.js" |
|||
|
|||
const dispatch = createEventDispatcher() |
|||
|
|||
export let value = "" |
|||
export let maxIconsPerPage = 30 |
|||
|
|||
let searchTerm = "" |
|||
let selectedLetter = "A" |
|||
|
|||
let currentPage = 1 |
|||
let filteredIcons = findIconByTerm(selectedLetter) |
|||
|
|||
$: dispatch("change", value) |
|||
|
|||
const alphabet = [ |
|||
"A", |
|||
"B", |
|||
"C", |
|||
"D", |
|||
"E", |
|||
"F", |
|||
"G", |
|||
"H", |
|||
"I", |
|||
"J", |
|||
"K", |
|||
"L", |
|||
"M", |
|||
"N", |
|||
"O", |
|||
"P", |
|||
"Q", |
|||
"R", |
|||
"S", |
|||
"T", |
|||
"U", |
|||
"V", |
|||
"W", |
|||
"X", |
|||
"Y", |
|||
"Z", |
|||
] |
|||
let buttonAnchor, dropdown |
|||
let loading = false |
|||
|
|||
function findIconByTerm(term) { |
|||
const r = new RegExp(`\^${term}`, "i") |
|||
return icons.filter(i => r.test(i.label)) |
|||
} |
|||
|
|||
async function switchLetter(letter) { |
|||
currentPage = 1 |
|||
searchTerm = "" |
|||
loading = true |
|||
selectedLetter = letter |
|||
filteredIcons = findIconByTerm(letter) |
|||
await tick() //svg icons do not update without tick |
|||
loading = false |
|||
} |
|||
|
|||
async function findIconOnPage() { |
|||
loading = true |
|||
const iconIdx = filteredIcons.findIndex(i => i.value === value) |
|||
if (iconIdx !== -1) { |
|||
currentPage = Math.ceil(iconIdx / maxIconsPerPage) |
|||
} |
|||
await tick() //svg icons do not update without tick |
|||
loading = false |
|||
} |
|||
|
|||
async function setSelectedUI() { |
|||
if (value) { |
|||
const letter = displayValue.substring(0, 1) |
|||
await switchLetter(letter) |
|||
await findIconOnPage() |
|||
} |
|||
} |
|||
|
|||
async function pageClick(next) { |
|||
loading = true |
|||
if (next && currentPage < totalPages) { |
|||
currentPage++ |
|||
} else if (!next && currentPage > 1) { |
|||
currentPage-- |
|||
} |
|||
await tick() //svg icons do not update without tick |
|||
loading = false |
|||
} |
|||
|
|||
async function searchForIcon(e) { |
|||
currentPage = 1 |
|||
loading = true |
|||
filteredIcons = findIconByTerm(searchTerm) |
|||
await tick() //svg icons do not update without tick |
|||
loading = false |
|||
} |
|||
|
|||
$: displayValue = value ? value.substring(7) : "Pick Icon" |
|||
|
|||
$: totalPages = Math.ceil(filteredIcons.length / maxIconsPerPage) |
|||
$: pageEndIdx = maxIconsPerPage * currentPage |
|||
$: pagedIcons = filteredIcons.slice(pageEndIdx - maxIconsPerPage, pageEndIdx) |
|||
|
|||
$: pagerText = `Page ${currentPage} of ${totalPages}` |
|||
</script> |
|||
|
|||
<div bind:this={buttonAnchor}> |
|||
<Button secondary on:click={dropdown.show}>{displayValue}</Button> |
|||
</div> |
|||
<DropdownMenu |
|||
bind:this={dropdown} |
|||
on:open={setSelectedUI} |
|||
anchor={buttonAnchor}> |
|||
<div class="container"> |
|||
<div class="search-area"> |
|||
<div class="alphabet-area"> |
|||
{#each alphabet as letter, idx} |
|||
<span |
|||
class="letter" |
|||
class:letter-selected={letter === selectedLetter} |
|||
on:click={() => switchLetter(letter)}> |
|||
{letter} |
|||
</span> |
|||
{#if idx !== alphabet.length - 1} |
|||
<span>-</span> |
|||
{/if} |
|||
{/each} |
|||
</div> |
|||
<div class="search-input"> |
|||
<div class="input-wrapper"> |
|||
<Input bind:value={searchTerm} thin placeholder="Search Icon" /> |
|||
</div> |
|||
<Button secondary on:click={searchForIcon}>Search</Button> |
|||
</div> |
|||
<div class="page-area"> |
|||
<div class="pager"> |
|||
<span on:click={() => pageClick(false)}> |
|||
<i class="page-btn fas fa-chevron-left" /> |
|||
</span> |
|||
<span>{pagerText}</span> |
|||
<span on:click={() => pageClick(true)}> |
|||
<i class="page-btn fas fa-chevron-right" /> |
|||
</span> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
{#if pagedIcons.length > 0} |
|||
<div class="icon-area"> |
|||
{#if !loading} |
|||
{#each pagedIcons as icon} |
|||
<div |
|||
class="icon-container" |
|||
class:selected={value === icon.value} |
|||
on:click={() => (value = icon.value)}> |
|||
<div class="icon-preview"> |
|||
<i class={`${icon.value} fa-3x`} /> |
|||
</div> |
|||
<div class="icon-label">{icon.label}</div> |
|||
</div> |
|||
{/each} |
|||
{/if} |
|||
</div> |
|||
{:else} |
|||
<div class="no-icons"> |
|||
<h5> |
|||
{`There is no icons for this ${searchTerm ? 'search' : 'page'}`} |
|||
</h5> |
|||
</div> |
|||
{/if} |
|||
</div> |
|||
</DropdownMenu> |
|||
|
|||
<style> |
|||
.container { |
|||
width: 610px; |
|||
height: 350px; |
|||
display: flex; |
|||
flex-direction: column; |
|||
padding: 10px 0px 10px 15px; |
|||
overflow-x: hidden; |
|||
} |
|||
|
|||
.search-area { |
|||
flex: 0 0 80px; |
|||
display: flex; |
|||
flex-direction: column; |
|||
} |
|||
|
|||
.icon-area { |
|||
flex: 1; |
|||
display: grid; |
|||
grid-template-columns: repeat(5, 1fr); |
|||
grid-gap: 5px; |
|||
justify-content: flex-start; |
|||
overflow-y: auto; |
|||
overflow-x: hidden; |
|||
padding-right: 10px; |
|||
} |
|||
|
|||
.no-icons { |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
|
|||
.alphabet-area { |
|||
display: flex; |
|||
flex-flow: row wrap; |
|||
padding-bottom: 10px; |
|||
padding-right: 15px; |
|||
justify-content: space-around; |
|||
} |
|||
|
|||
.loading-container { |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
|
|||
.search-input { |
|||
display: flex; |
|||
flex-flow: row nowrap; |
|||
width: 100%; |
|||
padding-right: 15px; |
|||
} |
|||
|
|||
.input-wrapper { |
|||
width: 510px; |
|||
margin-right: 5px; |
|||
} |
|||
|
|||
.page-area { |
|||
padding: 10px; |
|||
display: flex; |
|||
justify-content: center; |
|||
} |
|||
|
|||
.letter { |
|||
color: var(--blue); |
|||
} |
|||
|
|||
.letter:hover { |
|||
cursor: pointer; |
|||
text-decoration: underline; |
|||
} |
|||
|
|||
.letter-selected { |
|||
text-decoration: underline; |
|||
} |
|||
|
|||
.icon-container { |
|||
height: 100px; |
|||
display: flex; |
|||
justify-content: center; |
|||
flex-direction: column; |
|||
border: var(--border-dark); |
|||
} |
|||
|
|||
.icon-container:hover { |
|||
cursor: pointer; |
|||
background: var(--grey-2); |
|||
} |
|||
|
|||
.selected { |
|||
background: var(--grey-3); |
|||
} |
|||
|
|||
.icon-preview { |
|||
flex: 1; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
|
|||
.icon-label { |
|||
flex: 0 0 20px; |
|||
text-align: center; |
|||
font-size: 12px; |
|||
} |
|||
|
|||
.page-btn { |
|||
color: var(--blue); |
|||
} |
|||
|
|||
.page-btn:hover { |
|||
cursor: pointer; |
|||
} |
|||
</style> |
|||
File diff suppressed because it is too large
@ -0,0 +1,3 @@ |
|||
import "@fortawesome/fontawesome-free/js/all.js" |
|||
|
|||
export { default as IconSelect } from "./IconSelect.svelte" |
|||
@ -1,9 +1,9 @@ |
|||
<script> |
|||
export let icon = "" |
|||
export let fontSize = "1em" |
|||
export let _bb |
|||
|
|||
$: style = { fontSize } |
|||
</script> |
|||
|
|||
<i class={icon} {style} /> |
|||
<script> |
|||
import "@fortawesome/fontawesome-free/js/all.js" |
|||
|
|||
export let icon = "" |
|||
export let size = "fa-lg" |
|||
export let color = "#000" |
|||
</script> |
|||
|
|||
<i style={`color: ${color};`} class={`${icon} ${size}`} /> |
|||
|
|||
@ -0,0 +1,9 @@ |
|||
<script> |
|||
export let icon = "" |
|||
export let fontSize = "1em" |
|||
export let _bb |
|||
|
|||
$: style = { fontSize } |
|||
</script> |
|||
|
|||
<i class={icon} {style} /> |
|||
Loading…
Reference in new issue