Add components

This commit is contained in:
Ben McCann
2021-06-11 14:31:09 -07:00
parent ec7dcb3efd
commit f6eef8252e
7 changed files with 4440 additions and 0 deletions

View File

@@ -0,0 +1,68 @@
<script>
export const primary = false;
export let active = false;
</script>
<style>
div {
border: 2px solid #7e7e93;
border-radius: 5px;
background-color: white;
color: #7e7e93;
display: flex;
align-items: center;
padding: 5px 15px;
position: relative;
}
div:hover {
cursor: pointer;
border-color: var(--color) !important;
color: var(--color) !important;
}
.arrow {
margin-left: 25px;
height: 16px;
-webkit-mask: url(/right-arrow.svg) no-repeat center;
mask: url(/right-arrow.svg) no-repeat center;
background-color: #7e7e93;
}
div:hover .arrow {
background-color: var(--color);
}
.arrow.active {
background-color: var(--color-secondary);
}
.popin {
display: none;
position: absolute;
left: calc(100% - 1rem);
top: 1rem;
z-index: 100;
margin: 0;
padding: 0;
border: 2px solid var(--color);
border-radius: 5px;
background: white;
}
div:hover .popin:not(:empty) {
display: block;
}
@media screen and (max-width: 700px) {
.popin {
left: 1rem;
right: 1rem;
top: 100%
}
}
</style>
<div on:click>
<span>
<slot />
</span>
<div class="arrow" class:active />
<span class="popin"><slot name="menu"></slot></span>
</div>

View File

@@ -0,0 +1,97 @@
<script>
import Tag from "../Tag.svelte";
export let active = false;
export let title = "";
export let description = "";
export let image = "";
export let tags = [];
export let stars = 0;
export let addedOn = new Date();
export let url = "";
export let npm = "";
export let repo = "";
const copyToClipboard = (text) => {
navigator.permissions.query({name: "clipboard-write"}).then(result => {
if (result.state == "granted" || result.state == "prompt") {
navigator.clipboard.writeText(text)
}
});
}
</script>
<style>
.card {
display: flex;
flex-direction: column;
max-width: var(--width-card);
padding: 14px;
background: #f3f6f9;
border-radius: 5px;
}
.card h1 {
word-break: break-word;
}
.active,
.card:hover {
background: #e8f3fe;
}
.card__tags {
display: flex;
flex-direction: row;
flex-wrap: wrap;
margin-bottom: 1rem;
}
.card__bottom {
display: flex;
justify-content: space-between;
}
.card__bottom > * {
white-space: nowrap;
}
.flex-grow {
flex-grow: 1;
}
@media screen and (max-width: 400px) {
.card {
font-size: 0.9rem;
}
.card h1 {
font-size: 24px;
}
}
</style>
<div class="card" class:active id="component-{escape(title)}">
{#if image}
<img src={image} alt={title} />
{/if}
<h1>
<a href={url}>{title}</a> <a href="#component-{escape(title)}">#</a>
{#if npm}<Tag click={() => copyToClipboard(`npm install ${npm}`)} variant="copy" title="npm install {npm}"/>{/if}
</h1>
<p class="flex-grow">{description}</p>
<div class="card__tags">
{#each tags as tag}
<Tag title={tag} variant='blue' />
{/each}
</div>
<div class="card__bottom">
<div>
{#if stars > 0}
{#if (repo || url).includes('github')}
<img src="/github_logo.svg" alt="github logo" />
{:else if (repo || url).includes('gitlab')}
<img src="/gitlab_logo.svg" alt="gitlab logo" />
{:else}
&#9733;
{/if}
{stars}
{/if}
</div>
<div>{new Intl.DateTimeFormat('en-Us').format(Date.parse(addedOn))}</div>
</div>
</div>

View File

@@ -0,0 +1,45 @@
<script>
export let title;
</script>
<style>
h1 {
font-family: Overpass;
font-style: normal;
font-weight: 600;
font-size: 27px;
line-height: 150%;
margin-bottom: 1rem;
}
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-gap: 25px;
}
.list {
margin-bottom: 5rem;
}
@media screen and (max-width: 1024px) {
.grid {
max-width: calc(var(--width-card) * 2 + 25px);
grid-template-columns: repeat(2, 1fr);
margin: 0 auto;
}
}
@media screen and (max-width: 700px) {
.grid {
max-width: var(--width-card);
grid-template-columns: 1fr;
margin: 0 auto;
}
}
</style>
<div class="list">
<h1 id="category-{escape(title)}">{title} <a href="#{escape(title)}">#</a></h1>
<div class="grid">
<slot />
</div>
</div>

View File

@@ -0,0 +1,38 @@
<script>
export let title = "";
export let variant;
export let click = undefined
</script>
<style>
div {
/* max-width: 33%; */
padding: 2px 12px;
border-radius: 5px;
font-family: Overpass;
font-style: normal;
font-weight: normal;
font-size: 12px;
line-height: 150%;
text-align: center;
margin-right: 0.5rem;
margin-bottom: 0.5rem;
}
.red {
color: #ff3e01;
background: #ffdbcf;
}
.blue {
color: #158fff;
background: #ddefff;
}
.copy {
color: #ff6f01;
background: #ffe9cf;
cursor: copy;
}
</style>
<div on:click={click} class={variant}>{title}</div>

View File

@@ -0,0 +1,24 @@
const components = new Array(6)
.fill({
image: "/preview-image.png",
title: "svelte-tooltip",
description:
"A tooltip action that you can attach to any element and pass in a Svelte component...",
tags: [],
stars: 511,
addedOn: new Date(),
category: "forms"
})
.concat(
new Array(3).fill({
image: "/preview-image.png",
title: "svelte-ui",
description:
"A tooltip action that you can attach to any element and pass in a Svelte component...",
tags: [],
stars: 511,
addedOn: new Date(),
category: "ui"
})
);
export default components;

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,212 @@
<script>
import ComponentCard from "$lib/components/ComponentIndex/Card.svelte";
import List from "$lib/components/ComponentIndex/CardList.svelte";
import Button from "$lib/components/ComponentIndex/ArrowButton.svelte";
import components from "./components.json";
let searchValue;
let searchTag;
const tags = Array.from(new Set(components.map(item => item.tags).flat()))
const allCategories = Array.from(new Set(components.map(item => item.category).flat()))
let filterTag = []
let filterCategory = null
let sorting = 'added_desc';
const intersection = (array1, array2) => {
return array1.filter(item => array2.includes(item))
}
$: dataToDisplay = components.filter(component => {
if (!searchValue && filterTag.length === 0 && filterCategory === null) return true
if (
(searchValue && !(component.title.toLowerCase().includes(searchValue.toLowerCase()) || component.description.toLowerCase().includes(searchValue.toLowerCase())))
|| (filterTag.length > 0 && intersection(filterTag, component.tags).length === 0)
|| (filterCategory !== null && component.category !== filterCategory)
) {
return false
}
return true
}).sort((componentA, componentB) => {
switch (sorting) {
case "added_desc": return new Date(componentB.addedOn || '').getTime() - new Date(componentA.addedOn || '').getTime()
case "added_asc": return new Date(componentA.addedOn || '').getTime() - new Date(componentB.addedOn || '').getTime()
case "name_asc": return componentA.title.toLowerCase().localeCompare(componentB.title.toLowerCase())
case "name_desc": return componentB.title.toLowerCase().localeCompare(componentA.title.toLowerCase())
case "stars_desc": return (componentB.stars || 0) - (componentA.stars || 0)
case "stars_asc": return (componentA.stars || 0) - (componentB.stars || 0)
}
});
$: tagSearchResult = searchTag ? tags.filter(item => item.includes(searchTag)) : tags
$: categories = Array.from(new Set(dataToDisplay.map(item => item.category)))
</script>
<style>
.controls {
display: flex;
justify-content: space-between;
align-items: center;
font-family: Overpass;
position: relative;
}
.inputs {
display: grid;
grid-template-columns: repeat(3, auto);
grid-gap: 0.5rem;
margin-right: 2rem;
}
.searchbar {
height: 100%;
width: 35%;
font-family: Overpass;
border-width: 0;
background: #f3f6f9 url(/search-icon.svg) 98% no-repeat;
margin: 0;
padding: 10px 15px;
}
.searchbar-count {
position: absolute;
top: 100%;
right: 0;
}
ul.popin {
padding: 0.5ex;
margin: 0;
font-size: 0.7em;
max-height: 50vh;
overflow-y: auto;
}
ul.popin li {
list-style: none;
padding: 0;
margin: 0;
text-transform: capitalize;
}
ul.popin li:hover {
background: #eee;
border-radius: 3px;
}
ul.popin li.tag-search {
position: sticky;
top: -0.5ex;
margin: 0 -0.5ex;
padding: 0.5ex;
border-radius: 4px;
background: white;
}
ul.popin li.tag-search input {
margin: 0;
background: #f3f6f9;
width: 100%;
min-width: 15ch;
}
ul.popin li.tag-search:hover {
background: white;
}
ul.popin.no-wrap li {
white-space: nowrap;
}
ul.popin li label {
display: flex;
align-items: center;
line-height: 0.7rem;
padding: 0.8ex;
}
ul.popin li input {
flex: 0;
margin: 0 1ex 0 0;
}
@media screen and (max-width: 1024px) {
.controls {
flex-flow: column-reverse;
}
.inputs {
align-self: flex-start;
width: 100%;
grid-template-columns: repeat(3, auto);
}
.searchbar {
align-self: flex-end;
margin-bottom: 1ex;
}
}
@media screen and (max-width: 700px) {
.controls {
align-items: stretch;
}
.inputs {
grid-template-columns: auto;
}
.searchbar {
width: auto;
align-self: stretch;
}
}
</style>
<main>
<h1>Components</h1>
<div class="controls">
<div class="inputs">
<Button active={filterTag.length > 0}>
Tags {#if filterTag.length > 0}<small>({filterTag.length})</small>{/if}
<ul slot="menu" role="menu" class="popin">
<li class="tag-search"><input placeholder="Search for tags..." bind:value={searchTag} /></li>
{#each tagSearchResult as tag}
<li><label><input type="checkbox" bind:group={filterTag} value={tag}> {tag}</label></li>
{/each}
</ul>
</Button>
<Button active={filterCategory !== null}>
Categories
<ul slot="menu" role="menu" class="popin">
<li><label><input type="radio" bind:group={filterCategory} value={null}> All</label></li>
{#each allCategories as category}
<li><label><input type="radio" bind:group={filterCategory} value={category}> {category||"Unclassified"}</label></li>
{/each}
</ul>
</Button>
<Button on:click={() => location.href = "/help/components"}>Submit a component</Button>
<Button active={sorting !== ''}>
Sort
<ul slot="menu" role="menu" class="popin no-wrap">
<li><label><input type="radio" bind:group={sorting} value="added_desc"> Last Added first</label></li>
<li><label><input type="radio" bind:group={sorting} value="added_asc"> Oldest first</label></li>
<li><label><input type="radio" bind:group={sorting} value="stars_desc"> Stars Desc</label></li>
<li><label><input type="radio" bind:group={sorting} value="stars_asc"> Stars Asc</label></li>
<li><label><input type="radio" bind:group={sorting} value="name_asc"> Name Asc</label></li>
<li><label><input type="radio" bind:group={sorting} value="name_desc"> Name Desc</label></li>
</ul>
</Button>
</div>
<input
class="searchbar"
type="text"
placeholder="Search for components..."
bind:value={searchValue} />
<span class="searchbar-count">{dataToDisplay.length} result{#if dataToDisplay.length !== 1}s{/if}</span>
</div>
<hr />
{#each categories as category}
<List title={category||"Unclassified"}>
{#each dataToDisplay.filter(d => d.category === category) as data}
<ComponentCard {...data} />
{/each}
</List>
{/each}
</main>