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,
"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",

View File

@@ -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",

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 = {
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":
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":
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,40 +1,122 @@
<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
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;
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;
let sorting = 'stars_desc';
let packageManager = 'npm'
let selectedSorting = { value: 'stars_desc', label: 'Stars Desc' };
$: sorting = selectedSorting?.value || 'stars_desc';
let packageManager = 'npm';
const intersection = (array1, array2) => {
return array1.filter(item => array2.includes(item))
}
return array1.filter((item) => array2.includes(item));
};
$: dataToDisplay = components.filter(component => {
if (!searchValue && filterTag.length === 0 && filterCategory === null) return true
$: 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)
(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 false;
}
return true
}).sort(compare(sorting));
$: tagSearchResult = searchTag ? tags.filter(item => item.includes(searchTag)) : tags
$: categories = Array.from(new Set(dataToDisplay.map(item => item.category)))
return true;
})
.sort(compare(sorting));
$: categories = Array.from(new Set(dataToDisplay.map((item) => item.category)));
$: filterTag = selectedTags?.map((obj) => obj.value) || [];
</script>
<svelte:head>
<title>Components - Svelte Society</title>
</svelte:head>
<main class="wrapper">
<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}
/>
<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;
@@ -156,69 +238,9 @@
width: auto;
align-self: stretch;
}
:global(.select-container) {
width: 100%;
}
}
</style>
<svelte:head>
<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>
<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>

View File

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

View File

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

View File

@@ -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']
}
}
}