refactor: use select components for dropdown

This commit is contained in:
Paolo Tiu
2021-09-03 16:33:30 +08:00
parent ea0e9820e9
commit 186304e4fe
8 changed files with 444 additions and 462 deletions

14
package-lock.json generated
View File

@@ -5,7 +5,6 @@
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "sveltesociety.dev",
"version": "0.0.1", "version": "0.0.1",
"dependencies": { "dependencies": {
"svelte-highlight": "^3.2.0" "svelte-highlight": "^3.2.0"
@@ -30,6 +29,7 @@
"prettier-plugin-svelte": "^2.2.0", "prettier-plugin-svelte": "^2.2.0",
"svelte": "^3.38.2", "svelte": "^3.38.2",
"svelte-preprocess": "^4.7.4", "svelte-preprocess": "^4.7.4",
"svelte-select": "^4.4.0",
"tailwindcss": "^2.2.4", "tailwindcss": "^2.2.4",
"ts-jest": "^27.0.5", "ts-jest": "^27.0.5",
"tslib": "^2.0.0", "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": { "node_modules/svgo": {
"version": "2.4.0", "version": "2.4.0",
"resolved": "https://registry.npmjs.org/svgo/-/svgo-2.4.0.tgz", "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.4.0.tgz",
@@ -12033,6 +12039,12 @@
"strip-indent": "^3.0.0" "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": { "svgo": {
"version": "2.4.0", "version": "2.4.0",
"resolved": "https://registry.npmjs.org/svgo/-/svgo-2.4.0.tgz", "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.4.0.tgz",

View File

@@ -30,6 +30,7 @@
"prettier-plugin-svelte": "^2.2.0", "prettier-plugin-svelte": "^2.2.0",
"svelte": "^3.38.2", "svelte": "^3.38.2",
"svelte-preprocess": "^4.7.4", "svelte-preprocess": "^4.7.4",
"svelte-select": "^4.4.0",
"tailwindcss": "^2.2.4", "tailwindcss": "^2.2.4",
"ts-jest": "^27.0.5", "ts-jest": "^27.0.5",
"tslib": "^2.0.0", "tslib": "^2.0.0",

View 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>

View File

@@ -1,24 +1,45 @@
type SortableEntity = { type SortableEntity = {
title: string, title: string;
addedOn?: string, addedOn?: string;
stars?: number, stars?: number;
} };
export const compare = (sorting: string) => { export const compare = (sorting: string) => {
return (sortableEntityA : SortableEntity, sortableEntityB : SortableEntity) : number => { return (sortableEntityA: SortableEntity, sortableEntityB: SortableEntity): number => {
switch (sorting) { switch (sorting) {
case "added_desc": case 'added_desc':
return new Date(sortableEntityB.addedOn || "").getTime() - new Date(sortableEntityA.addedOn || "").getTime(); return (
case "added_asc": new Date(sortableEntityB.addedOn || '').getTime() -
return new Date(sortableEntityA.addedOn || "").getTime() - new Date(sortableEntityB.addedOn || "").getTime(); new Date(sortableEntityA.addedOn || '').getTime()
case "name_asc": );
return sortableEntityA.title.toLowerCase().localeCompare(sortableEntityB.title.toLowerCase()); case 'added_asc':
case "name_desc": return (
return sortableEntityB.title.toLowerCase().localeCompare(sortableEntityA.title.toLowerCase()); new Date(sortableEntityA.addedOn || '').getTime() -
case "stars_desc": new Date(sortableEntityB.addedOn || '').getTime()
return (sortableEntityB.stars || 0) - (sortableEntityA.stars || 0); );
case "stars_asc": case 'name_asc':
return (sortableEntityA.stars || 0) - (sortableEntityB.stars || 0); 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 }));

View File

@@ -1,224 +1,246 @@
<script> <script>
import ComponentCard from "$lib/components/ComponentIndex/Card.svelte"; import ComponentCard from '$lib/components/ComponentIndex/Card.svelte';
import List from "$lib/components/ComponentIndex/CardList.svelte"; import List from '$lib/components/ComponentIndex/CardList.svelte';
import Button from "$lib/components/ComponentIndex/ArrowButton.svelte"; import Button from '$lib/components/ComponentIndex/ArrowButton.svelte';
import components from "./components.json"; import components from './components.json';
import { compare } from '$lib/utils/sort'; import { compare, selectSortItems } from '$lib/utils/sort';
import Select from '$lib/components/Select.svelte';
let searchValue; 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'
const intersection = (array1, array2) => { const tags = Array.from(new Set(components.map((item) => item.tags).flat()));
return array1.filter(item => array2.includes(item)) const tagItems = tags.map((t) => ({ label: t, value: t }));
} let filterTag = [];
let selectedTags = null;
$: dataToDisplay = components.filter(component => { const allCategories = Array.from(new Set(components.map((item) => item.category).flat()));
if (!searchValue && filterTag.length === 0 && filterCategory === null) return true const categoryItems = [
{ label: 'All', value: null },
...allCategories.filter((cat) => cat !== '').map((cat) => ({ label: cat, value: cat }))
];
let selectedCategory = null;
let filterCategory = null;
if ( let sorting = 'stars_desc';
(searchValue && !(component.title.toLowerCase().includes(searchValue.toLowerCase()) || component.description.toLowerCase().includes(searchValue.toLowerCase()))) let selectedSorting = { value: 'stars_desc', label: 'Stars Desc' };
|| (filterTag.length > 0 && intersection(filterTag, component.tags).length === 0) $: sorting = selectedSorting?.value || 'stars_desc';
|| (filterCategory !== null && component.category !== filterCategory)
) {
return false
}
return true let packageManager = 'npm';
}).sort(compare(sorting));
$: tagSearchResult = searchTag ? tags.filter(item => item.includes(searchTag)) : tags const intersection = (array1, array2) => {
$: categories = Array.from(new Set(dataToDisplay.map(item => item.category))) 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> </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> <svelte:head>
<title>Components - Svelte Society</title> <title>Components - Svelte Society</title>
</svelte:head> </svelte:head>
<main class="wrapper"> <main class="wrapper">
<h1>Components</h1> <h1>Components</h1>
<div class="controls"> <div class="controls">
<div class="inputs"> <div class="inputs">
<Button active={filterTag.length > 0}> <Select bind:value={selectedTags} items={tagItems} isMulti label="Tags" />
Tags {#if filterTag.length > 0}<small>({filterTag.length})</small>{/if} <Select
<ul slot="menu" role="menu" class="popin"> label="Category"
<li class="tag-search"><input placeholder="Search for tags..." bind:value={searchTag} /></li> bind:value={selectedCategory}
{#each tagSearchResult as tag} items={categoryItems}
<li><label><input type="checkbox" bind:group={filterTag} value={tag}> {tag}</label></li> placeholder="Category"
{/each} isClearable={false}
</ul> showIndicator
</Button> />
<Button active={filterCategory !== null}> <Select
Categories items={selectSortItems}
<ul slot="menu" role="menu" class="popin"> bind:value={selectedSorting}
<li><label><input type="radio" bind:group={filterCategory} value={null}> All</label></li> label="Sorting"
{#each allCategories as category} showIndicator
<li><label><input type="radio" bind:group={filterCategory} value={category}> {category||"Unclassified"}</label></li> isClearable={false}
{/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>
<input <Button on:click={() => (location.href = '/help/components')}>Submit a component</Button>
class="searchbar"
type="text" <Button small active={packageManager !== ''}>
placeholder="Search for components..." Package Manager
bind:value={searchValue} /> <ul slot="menu" role="menu" class="popin no-wrap">
<span class="searchbar-count">{dataToDisplay.length} result{#if dataToDisplay.length !== 1}s{/if}</span> <li><label><input type="radio" bind:group={packageManager} value="npm" /> NPM</label></li>
</div> <li>
<hr /> <label><input type="radio" bind:group={packageManager} value="pnpm" /> PNPM</label>
{#each categories as category} </li>
<List title={category||"Unclassified"}> <li>
{#each dataToDisplay.filter(d => d.category === category) as data} <label><input type="radio" bind:group={packageManager} value="yarn" /> Yarn</label>
<ComponentCard {...data} manager={packageManager} /> </li>
{/each} </ul>
</List> </Button>
{/each} </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> </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>

View File

@@ -3,20 +3,32 @@
import List from '$lib/components/ComponentIndex/CardList.svelte'; import List from '$lib/components/ComponentIndex/CardList.svelte';
import Button from '$lib/components/ComponentIndex/ArrowButton.svelte'; import Button from '$lib/components/ComponentIndex/ArrowButton.svelte';
import components from './templates.json'; 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 searchValue;
let searchTag;
const tags = Array.from(new Set(components.map((item) => item.tags).flat())); 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 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 filterCategory = null;
let sorting = 'stars_desc';
let selectedSorting = { value: 'stars_desc', label: 'Stars Desc' };
$: sorting = selectedSorting?.value || 'stars_desc';
const intersection = (array1, array2) => { const intersection = (array1, array2) => {
return array1.filter((item) => array2.includes(item)); return array1.filter((item) => array2.includes(item));
}; };
$: filterCategory = selectedCategory?.value || null;
$: dataToDisplay = components $: dataToDisplay = components
.filter((component) => { .filter((component) => {
if (!searchValue && filterTag.length === 0 && filterCategory === null) return true; if (!searchValue && filterTag.length === 0 && filterCategory === null) return true;
@@ -34,9 +46,11 @@
} }
return true; 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))); $: categories = Array.from(new Set(dataToDisplay.map((item) => item.category)));
$: filterTag = selectedTags?.map((obj) => obj.value) || [];
</script> </script>
<svelte:head> <svelte:head>
@@ -47,62 +61,24 @@
<h1>Templates</h1> <h1>Templates</h1>
<div class="controls"> <div class="controls">
<div class="inputs"> <div class="inputs">
<Button active={filterTag.length > 0}> <Select bind:value={selectedTags} items={tagItems} isMulti label="Tags" />
Tags {#if filterTag.length > 0}<small>({filterTag.length})</small>{/if} <Select
<ul slot="menu" role="menu" class="popin"> label="Category"
<li class="tag-search"> bind:value={selectedCategory}
<input placeholder="Search for tags..." bind:value={searchTag} /> items={categoryItems}
</li> placeholder="Category"
{#each tagSearchResult as tag} isClearable={false}
<li> showIndicator
<label><input type="checkbox" bind:group={filterTag} value={tag} /> {tag}</label> />
</li> <Select
{/each} items={selectSortItems}
</ul> bind:value={selectedSorting}
</Button> label="Sorting"
<Button active={filterCategory !== null}> showIndicator
Categories isClearable={false}
<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 template</Button> <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> </div>
<input <input
@@ -147,6 +123,7 @@
grid-template-columns: repeat(4, auto); grid-template-columns: repeat(4, auto);
grid-gap: 0.5rem; grid-gap: 0.5rem;
margin-right: 2rem; margin-right: 2rem;
padding-top: 1rem;
} }
.searchbar { .searchbar {
@@ -165,62 +142,10 @@
right: 0; 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) { @media screen and (max-width: 1024px) {
.controls { .controls {
flex-flow: column-reverse; flex-flow: column-reverse;
} }
.inputs { .inputs {
align-self: flex-start; align-self: flex-start;
width: 100%; width: 100%;
@@ -246,5 +171,9 @@
width: auto; width: auto;
align-self: stretch; align-self: stretch;
} }
:global(.select-container) {
width: 100%;
}
} }
</style> </style>

View File

@@ -3,16 +3,26 @@
import List from '$lib/components/ComponentIndex/CardList.svelte'; import List from '$lib/components/ComponentIndex/CardList.svelte';
import Button from '$lib/components/ComponentIndex/ArrowButton.svelte'; import Button from '$lib/components/ComponentIndex/ArrowButton.svelte';
import tools from './tools.json'; 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 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, const allCategories = Array.from(new Set(tools.map((item) => item.category).flat()));
searchTag, const categoryItems = [
filterTag = [], { label: 'all', value: null },
filterCategory = null, ...allCategories.map((cat) => ({ label: cat, value: cat }))
sorting = 'stars_desc'; ];
let selectedCategory = null;
let filterCategory = null;
let selectedSorting = { value: 'stars_desc', label: 'Stars Desc' };
$: sorting = selectedSorting?.value || 'stars_desc';
const intersection = (array1, array2) => { const intersection = (array1, array2) => {
return array1.filter((item) => array2.includes(item)); return array1.filter((item) => array2.includes(item));
@@ -37,8 +47,9 @@
return true; return true;
}) })
.sort(compare(sorting)); .sort(compare(sorting));
$: tagSearchResult = searchTag ? tags.filter((item) => item.includes(searchTag)) : tags;
$: categories = Array.from(new Set(dataToDisplay.map((item) => item.category))); $: categories = Array.from(new Set(dataToDisplay.map((item) => item.category)));
$: filterTag = selectedTags?.map((obj) => obj.value) || [];
</script> </script>
<svelte:head> <svelte:head>
@@ -49,62 +60,23 @@
<h1>Tooling</h1> <h1>Tooling</h1>
<div class="controls"> <div class="controls">
<div class="inputs"> <div class="inputs">
<Button active={filterTag.length > 0}> <Select bind:value={selectedTags} items={tagItems} isMulti label="Tags" />
Tags {#if filterTag.length > 0}<small>({filterTag.length})</small>{/if} <Select
<ul slot="menu" role="menu" class="popin"> label="Category"
<li class="tag-search"> bind:value={selectedCategory}
<input placeholder="Search for tags..." bind:value={searchTag} /> items={categoryItems}
</li> placeholder="Category"
{#each tagSearchResult as tag} isClearable={false}
<li> showIndicator
<label><input type="checkbox" bind:group={filterTag} value={tag} /> {tag}</label> />
</li> <Select
{/each} items={selectSortItems}
</ul> bind:value={selectedSorting}
</Button> label="Sorting"
<Button active={filterCategory !== null}> showIndicator
Categories isClearable={false}
<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 tool</Button> <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> </div>
<input <input
@@ -167,57 +139,6 @@
right: 0; 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) { @media screen and (max-width: 1024px) {
.controls { .controls {
flex-flow: column-reverse; flex-flow: column-reverse;
@@ -248,5 +169,9 @@
width: auto; width: auto;
align-self: stretch; align-self: stretch;
} }
:global(.select-container) {
width: 100%;
}
} }
</style> </style>

View File

@@ -8,10 +8,11 @@ const extensions = [`.svelte`, '.md', `.mdx`, '.svx'];
const config = { const config = {
preprocess: [ preprocess: [
preprocess({ preprocess({
"postcss": true postcss: true
}), }),
mdsvex({ mdsvex({
extensions: extensions, // Breaks svelte-select when .svelte extension is included
extensions: extensions.filter((ext) => ext !== '.svelte'),
layout: { layout: {
eventPage: './src/lib/layouts/EventPage.svelte', eventPage: './src/lib/layouts/EventPage.svelte',
recipe: './src/lib/layouts/Recipe.svelte', recipe: './src/lib/layouts/Recipe.svelte',
@@ -28,7 +29,7 @@ const config = {
optimizeDeps: { optimizeDeps: {
// workaround Vite issue to fix highlighting on cheatsheet // workaround Vite issue to fix highlighting on cheatsheet
// https://github.com/metonym/svelte-highlight/issues/158 // https://github.com/metonym/svelte-highlight/issues/158
include: ["highlight.js/lib/core"], include: ['highlight.js/lib/core']
} }
} }
} }