mirror of
https://github.com/LukeHagar/sveltesociety.dev.git
synced 2025-12-06 04:21:38 +00:00
refactor: use select components for dropdown
This commit is contained in:
14
package-lock.json
generated
14
package-lock.json
generated
@@ -5,7 +5,6 @@
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "sveltesociety.dev",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"svelte-highlight": "^3.2.0"
|
||||
@@ -30,6 +29,7 @@
|
||||
"prettier-plugin-svelte": "^2.2.0",
|
||||
"svelte": "^3.38.2",
|
||||
"svelte-preprocess": "^4.7.4",
|
||||
"svelte-select": "^4.4.0",
|
||||
"tailwindcss": "^2.2.4",
|
||||
"ts-jest": "^27.0.5",
|
||||
"tslib": "^2.0.0",
|
||||
@@ -6499,6 +6499,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-select": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte-select/-/svelte-select-4.4.0.tgz",
|
||||
"integrity": "sha512-iLE9dPT/2ITtwC4z4YfuOuKGuHxJKQ1Ezmt7IpsZhYdxGMDJlS2qqRjWzMPHlgpAH4ANp+TxzxIMNJ5TkFdPSA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/svgo": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/svgo/-/svgo-2.4.0.tgz",
|
||||
@@ -12033,6 +12039,12 @@
|
||||
"strip-indent": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"svelte-select": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte-select/-/svelte-select-4.4.0.tgz",
|
||||
"integrity": "sha512-iLE9dPT/2ITtwC4z4YfuOuKGuHxJKQ1Ezmt7IpsZhYdxGMDJlS2qqRjWzMPHlgpAH4ANp+TxzxIMNJ5TkFdPSA==",
|
||||
"dev": true
|
||||
},
|
||||
"svgo": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/svgo/-/svgo-2.4.0.tgz",
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
"prettier-plugin-svelte": "^2.2.0",
|
||||
"svelte": "^3.38.2",
|
||||
"svelte-preprocess": "^4.7.4",
|
||||
"svelte-select": "^4.4.0",
|
||||
"tailwindcss": "^2.2.4",
|
||||
"ts-jest": "^27.0.5",
|
||||
"tslib": "^2.0.0",
|
||||
|
||||
71
src/lib/components/Select.svelte
Normal file
71
src/lib/components/Select.svelte
Normal file
@@ -0,0 +1,71 @@
|
||||
<script>
|
||||
import SvelteSelect from 'svelte-select';
|
||||
|
||||
export let value;
|
||||
export let label = '';
|
||||
</script>
|
||||
|
||||
<div class="themed">
|
||||
{#if label}
|
||||
<span>{label}</span>
|
||||
{/if}
|
||||
<SvelteSelect containerClasses="select-container" bind:value {...$$restProps} />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
span {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
.themed {
|
||||
--inputFontSize: 0.875rem;
|
||||
--multiItemActiveBG: var(--color);
|
||||
--height: 1rem;
|
||||
--borderHoverColor: var(--color);
|
||||
--borderFocusColor: var(--color);
|
||||
--itemHoverBG: var(--color-shadow);
|
||||
--itemIsActiveBG: var(--color);
|
||||
--indicatorTop: calc(50% - 13px);
|
||||
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.themed :global(.select-container) {
|
||||
cursor: pointer;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
gap: 10px;
|
||||
min-width: 150px;
|
||||
min-height: 3rem;
|
||||
}
|
||||
|
||||
.themed :global(.multiSelectItem) {
|
||||
font-size: 0.875rem;
|
||||
align-items: center;
|
||||
--multiItemBorderRadius: var(--radius-100);
|
||||
--multiItemHeight: 1.25rem;
|
||||
--multiItemMargin: 0;
|
||||
--multiItemPadding: 0.2rem 0.3rem;
|
||||
--multiClearBG: transparent;
|
||||
--multiClearFill: var(--color-text-secondary);
|
||||
--multiClearHoverBG: transparent;
|
||||
--multiClearHoverFill: var(--color-bg);
|
||||
--multiLabelMargin: 1px 5px 0 0;
|
||||
}
|
||||
|
||||
.themed :global(input) {
|
||||
height: 1rem !important;
|
||||
cursor: pointer !important;
|
||||
}
|
||||
|
||||
.themed :global(.multiSelectItem_clear) {
|
||||
position: static;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.themed :global(.listItem) {
|
||||
--height: 1.4rem;
|
||||
}
|
||||
</style>
|
||||
@@ -1,24 +1,45 @@
|
||||
type SortableEntity = {
|
||||
title: string,
|
||||
addedOn?: string,
|
||||
stars?: number,
|
||||
}
|
||||
title: string;
|
||||
addedOn?: string;
|
||||
stars?: number;
|
||||
};
|
||||
|
||||
export const compare = (sorting: string) => {
|
||||
return (sortableEntityA : SortableEntity, sortableEntityB : SortableEntity) : number => {
|
||||
switch (sorting) {
|
||||
case "added_desc":
|
||||
return new Date(sortableEntityB.addedOn || "").getTime() - new Date(sortableEntityA.addedOn || "").getTime();
|
||||
case "added_asc":
|
||||
return new Date(sortableEntityA.addedOn || "").getTime() - new Date(sortableEntityB.addedOn || "").getTime();
|
||||
case "name_asc":
|
||||
return sortableEntityA.title.toLowerCase().localeCompare(sortableEntityB.title.toLowerCase());
|
||||
case "name_desc":
|
||||
return sortableEntityB.title.toLowerCase().localeCompare(sortableEntityA.title.toLowerCase());
|
||||
case "stars_desc":
|
||||
return (sortableEntityB.stars || 0) - (sortableEntityA.stars || 0);
|
||||
case "stars_asc":
|
||||
return (sortableEntityA.stars || 0) - (sortableEntityB.stars || 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
return (sortableEntityA: SortableEntity, sortableEntityB: SortableEntity): number => {
|
||||
switch (sorting) {
|
||||
case 'added_desc':
|
||||
return (
|
||||
new Date(sortableEntityB.addedOn || '').getTime() -
|
||||
new Date(sortableEntityA.addedOn || '').getTime()
|
||||
);
|
||||
case 'added_asc':
|
||||
return (
|
||||
new Date(sortableEntityA.addedOn || '').getTime() -
|
||||
new Date(sortableEntityB.addedOn || '').getTime()
|
||||
);
|
||||
case 'name_asc':
|
||||
return sortableEntityA.title
|
||||
.toLowerCase()
|
||||
.localeCompare(sortableEntityB.title.toLowerCase());
|
||||
case 'name_desc':
|
||||
return sortableEntityB.title
|
||||
.toLowerCase()
|
||||
.localeCompare(sortableEntityA.title.toLowerCase());
|
||||
case 'stars_desc':
|
||||
return (sortableEntityB.stars || 0) - (sortableEntityA.stars || 0);
|
||||
case 'stars_asc':
|
||||
return (sortableEntityA.stars || 0) - (sortableEntityB.stars || 0);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const sortMap = {
|
||||
added_desc: 'Added Desc',
|
||||
added_asc: 'Added Asc',
|
||||
name_asc: 'Name Asc',
|
||||
name_desc: 'Name Desc',
|
||||
stars_desc: 'Stars Desc',
|
||||
stars_asc: 'Stars Asc'
|
||||
};
|
||||
|
||||
export const selectSortItems = Object.entries(sortMap).map(([value, label]) => ({ label, value }));
|
||||
|
||||
@@ -1,224 +1,246 @@
|
||||
<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";
|
||||
import { compare } from '$lib/utils/sort';
|
||||
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';
|
||||
import { compare, selectSortItems } from '$lib/utils/sort';
|
||||
import Select from '$lib/components/Select.svelte';
|
||||
|
||||
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 = 'stars_desc';
|
||||
let packageManager = 'npm'
|
||||
let searchValue;
|
||||
|
||||
const intersection = (array1, array2) => {
|
||||
return array1.filter(item => array2.includes(item))
|
||||
}
|
||||
const tags = Array.from(new Set(components.map((item) => item.tags).flat()));
|
||||
const tagItems = tags.map((t) => ({ label: t, value: t }));
|
||||
let filterTag = [];
|
||||
let selectedTags = null;
|
||||
|
||||
$: dataToDisplay = components.filter(component => {
|
||||
if (!searchValue && filterTag.length === 0 && filterCategory === null) return true
|
||||
const allCategories = Array.from(new Set(components.map((item) => item.category).flat()));
|
||||
const categoryItems = [
|
||||
{ label: 'All', value: null },
|
||||
...allCategories.filter((cat) => cat !== '').map((cat) => ({ label: cat, value: cat }))
|
||||
];
|
||||
let selectedCategory = null;
|
||||
let filterCategory = null;
|
||||
|
||||
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
|
||||
}
|
||||
let sorting = 'stars_desc';
|
||||
let selectedSorting = { value: 'stars_desc', label: 'Stars Desc' };
|
||||
$: sorting = selectedSorting?.value || 'stars_desc';
|
||||
|
||||
return true
|
||||
}).sort(compare(sorting));
|
||||
$: tagSearchResult = searchTag ? tags.filter(item => item.includes(searchTag)) : tags
|
||||
$: categories = Array.from(new Set(dataToDisplay.map(item => item.category)))
|
||||
let packageManager = 'npm';
|
||||
|
||||
const intersection = (array1, array2) => {
|
||||
return array1.filter((item) => array2.includes(item));
|
||||
};
|
||||
|
||||
$: filterCategory = selectedCategory?.value || null;
|
||||
$: 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(compare(sorting));
|
||||
|
||||
$: categories = Array.from(new Set(dataToDisplay.map((item) => item.category)));
|
||||
$: filterTag = selectedTags?.map((obj) => obj.value) || [];
|
||||
</script>
|
||||
|
||||
<style>
|
||||
h1 {
|
||||
@apply text-5xl;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin-block: 4rem;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-family: Overpass;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.inputs {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, auto);
|
||||
grid-gap: 0.5rem;
|
||||
margin-right: 2rem;
|
||||
}
|
||||
|
||||
.searchbar {
|
||||
height: 100%;
|
||||
width: 35%;
|
||||
font-family: Overpass;
|
||||
border-width: 0;
|
||||
background: #f3f6f9 url(/images/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>
|
||||
<svelte:head>
|
||||
<title>Components - Svelte Society</title>
|
||||
<title>Components - Svelte Society</title>
|
||||
</svelte:head>
|
||||
|
||||
<main class="wrapper">
|
||||
<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>
|
||||
<Button small active={packageManager !== ''}>
|
||||
Package Manager
|
||||
<ul slot="menu" role="menu" class="popin no-wrap">
|
||||
<li><label><input type="radio" bind:group={packageManager} value="npm"> NPM</label></li>
|
||||
<li><label><input type="radio" bind:group={packageManager} value="pnpm"> PNPM</label></li>
|
||||
<li><label><input type="radio" bind:group={packageManager} value="yarn"> Yarn</label></li>
|
||||
</ul>
|
||||
</Button>
|
||||
</div>
|
||||
<h1>Components</h1>
|
||||
<div class="controls">
|
||||
<div class="inputs">
|
||||
<Select bind:value={selectedTags} items={tagItems} isMulti label="Tags" />
|
||||
<Select
|
||||
label="Category"
|
||||
bind:value={selectedCategory}
|
||||
items={categoryItems}
|
||||
placeholder="Category"
|
||||
isClearable={false}
|
||||
showIndicator
|
||||
/>
|
||||
<Select
|
||||
items={selectSortItems}
|
||||
bind:value={selectedSorting}
|
||||
label="Sorting"
|
||||
showIndicator
|
||||
isClearable={false}
|
||||
/>
|
||||
|
||||
<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} manager={packageManager} />
|
||||
{/each}
|
||||
</List>
|
||||
{/each}
|
||||
<Button on:click={() => (location.href = '/help/components')}>Submit a component</Button>
|
||||
|
||||
<Button small active={packageManager !== ''}>
|
||||
Package Manager
|
||||
<ul slot="menu" role="menu" class="popin no-wrap">
|
||||
<li><label><input type="radio" bind:group={packageManager} value="npm" /> NPM</label></li>
|
||||
<li>
|
||||
<label><input type="radio" bind:group={packageManager} value="pnpm" /> PNPM</label>
|
||||
</li>
|
||||
<li>
|
||||
<label><input type="radio" bind:group={packageManager} value="yarn" /> Yarn</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} manager={packageManager} />
|
||||
{/each}
|
||||
</List>
|
||||
{/each}
|
||||
</main>
|
||||
|
||||
<style>
|
||||
h1 {
|
||||
@apply text-5xl;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin-block: 4rem;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-family: Overpass;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.inputs {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, auto);
|
||||
grid-gap: 0.5rem;
|
||||
margin-right: 2rem;
|
||||
}
|
||||
|
||||
.searchbar {
|
||||
height: 100%;
|
||||
width: 35%;
|
||||
font-family: Overpass;
|
||||
border-width: 0;
|
||||
background: #f3f6f9 url(/images/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;
|
||||
}
|
||||
|
||||
:global(.select-container) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,20 +3,32 @@
|
||||
import List from '$lib/components/ComponentIndex/CardList.svelte';
|
||||
import Button from '$lib/components/ComponentIndex/ArrowButton.svelte';
|
||||
import components from './templates.json';
|
||||
import { compare } from '$lib/utils/sort';
|
||||
import { compare, selectSortItems } from '$lib/utils/sort';
|
||||
import Select from '$lib/components/Select.svelte';
|
||||
|
||||
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()));
|
||||
const tagItems = tags.map((t) => ({ label: t, value: t }));
|
||||
let filterTag = [];
|
||||
let selectedTags = null;
|
||||
|
||||
const allCategories = Array.from(new Set(components.map((item) => item.category).flat()));
|
||||
const categoryItems = [
|
||||
{ label: 'all', value: null },
|
||||
...allCategories.map((cat) => ({ label: cat, value: cat }))
|
||||
];
|
||||
let selectedCategory = null;
|
||||
let filterCategory = null;
|
||||
let sorting = 'stars_desc';
|
||||
|
||||
let selectedSorting = { value: 'stars_desc', label: 'Stars Desc' };
|
||||
$: sorting = selectedSorting?.value || 'stars_desc';
|
||||
|
||||
const intersection = (array1, array2) => {
|
||||
return array1.filter((item) => array2.includes(item));
|
||||
};
|
||||
|
||||
$: filterCategory = selectedCategory?.value || null;
|
||||
$: dataToDisplay = components
|
||||
.filter((component) => {
|
||||
if (!searchValue && filterTag.length === 0 && filterCategory === null) return true;
|
||||
@@ -34,9 +46,11 @@
|
||||
}
|
||||
|
||||
return true;
|
||||
}).sort(compare(sorting));
|
||||
$: tagSearchResult = searchTag ? tags.filter((item) => item.includes(searchTag)) : tags;
|
||||
})
|
||||
.sort(compare(sorting));
|
||||
|
||||
$: categories = Array.from(new Set(dataToDisplay.map((item) => item.category)));
|
||||
$: filterTag = selectedTags?.map((obj) => obj.value) || [];
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -47,62 +61,24 @@
|
||||
<h1>Templates</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>
|
||||
<Select bind:value={selectedTags} items={tagItems} isMulti label="Tags" />
|
||||
<Select
|
||||
label="Category"
|
||||
bind:value={selectedCategory}
|
||||
items={categoryItems}
|
||||
placeholder="Category"
|
||||
isClearable={false}
|
||||
showIndicator
|
||||
/>
|
||||
<Select
|
||||
items={selectSortItems}
|
||||
bind:value={selectedSorting}
|
||||
label="Sorting"
|
||||
showIndicator
|
||||
isClearable={false}
|
||||
/>
|
||||
|
||||
<Button on:click={() => (location.href = '/help/components')}>Submit a template</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
|
||||
@@ -147,6 +123,7 @@
|
||||
grid-template-columns: repeat(4, auto);
|
||||
grid-gap: 0.5rem;
|
||||
margin-right: 2rem;
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.searchbar {
|
||||
@@ -165,62 +142,10 @@
|
||||
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%;
|
||||
@@ -246,5 +171,9 @@
|
||||
width: auto;
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
:global(.select-container) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,16 +3,26 @@
|
||||
import List from '$lib/components/ComponentIndex/CardList.svelte';
|
||||
import Button from '$lib/components/ComponentIndex/ArrowButton.svelte';
|
||||
import tools from './tools.json';
|
||||
import { compare } from '$lib/utils/sort';
|
||||
import Select from '$lib/components/Select.svelte';
|
||||
import { compare, selectSortItems } from '$lib/utils/sort';
|
||||
|
||||
let searchValue;
|
||||
|
||||
const tags = Array.from(new Set(tools.map((item) => item.tags).flat()));
|
||||
const allCategories = Array.from(new Set(tools.map((item) => item.category).flat()));
|
||||
const tagItems = tags.map((t) => ({ label: t, value: t }));
|
||||
let filterTag = [];
|
||||
let selectedTags = null;
|
||||
|
||||
let searchValue,
|
||||
searchTag,
|
||||
filterTag = [],
|
||||
filterCategory = null,
|
||||
sorting = 'stars_desc';
|
||||
const allCategories = Array.from(new Set(tools.map((item) => item.category).flat()));
|
||||
const categoryItems = [
|
||||
{ label: 'all', value: null },
|
||||
...allCategories.map((cat) => ({ label: cat, value: cat }))
|
||||
];
|
||||
let selectedCategory = null;
|
||||
let filterCategory = null;
|
||||
|
||||
let selectedSorting = { value: 'stars_desc', label: 'Stars Desc' };
|
||||
$: sorting = selectedSorting?.value || 'stars_desc';
|
||||
|
||||
const intersection = (array1, array2) => {
|
||||
return array1.filter((item) => array2.includes(item));
|
||||
@@ -37,8 +47,9 @@
|
||||
return true;
|
||||
})
|
||||
.sort(compare(sorting));
|
||||
$: tagSearchResult = searchTag ? tags.filter((item) => item.includes(searchTag)) : tags;
|
||||
|
||||
$: categories = Array.from(new Set(dataToDisplay.map((item) => item.category)));
|
||||
$: filterTag = selectedTags?.map((obj) => obj.value) || [];
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -49,62 +60,23 @@
|
||||
<h1>Tooling</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>
|
||||
<Select bind:value={selectedTags} items={tagItems} isMulti label="Tags" />
|
||||
<Select
|
||||
label="Category"
|
||||
bind:value={selectedCategory}
|
||||
items={categoryItems}
|
||||
placeholder="Category"
|
||||
isClearable={false}
|
||||
showIndicator
|
||||
/>
|
||||
<Select
|
||||
items={selectSortItems}
|
||||
bind:value={selectedSorting}
|
||||
label="Sorting"
|
||||
showIndicator
|
||||
isClearable={false}
|
||||
/>
|
||||
<Button on:click={() => (location.href = '/help/components')}>Submit a tool</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
|
||||
@@ -167,57 +139,6 @@
|
||||
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;
|
||||
@@ -248,5 +169,9 @@
|
||||
width: auto;
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
:global(.select-container) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -8,10 +8,11 @@ const extensions = [`.svelte`, '.md', `.mdx`, '.svx'];
|
||||
const config = {
|
||||
preprocess: [
|
||||
preprocess({
|
||||
"postcss": true
|
||||
postcss: true
|
||||
}),
|
||||
mdsvex({
|
||||
extensions: extensions,
|
||||
// Breaks svelte-select when .svelte extension is included
|
||||
extensions: extensions.filter((ext) => ext !== '.svelte'),
|
||||
layout: {
|
||||
eventPage: './src/lib/layouts/EventPage.svelte',
|
||||
recipe: './src/lib/layouts/Recipe.svelte',
|
||||
@@ -28,7 +29,7 @@ const config = {
|
||||
optimizeDeps: {
|
||||
// workaround Vite issue to fix highlighting on cheatsheet
|
||||
// https://github.com/metonym/svelte-highlight/issues/158
|
||||
include: ["highlight.js/lib/core"],
|
||||
include: ['highlight.js/lib/core']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user