diff --git a/src/components/AccordionFilter.svelte b/src/components/AccordionFilter.svelte new file mode 100644 index 0000000..48658c7 --- /dev/null +++ b/src/components/AccordionFilter.svelte @@ -0,0 +1,22 @@ + + +
+ + + + + e.key === "Escape" && (value = "")} /> + {#if value} + + {/if} +
diff --git a/src/components/destinations/BookmarksDestination.svelte b/src/components/destinations/BookmarksDestination.svelte index 147af35..32813d2 100644 --- a/src/components/destinations/BookmarksDestination.svelte +++ b/src/components/destinations/BookmarksDestination.svelte @@ -2,6 +2,8 @@ import kipclipLogo from "../../assets/kipclip-logo.png?url"; import sillLogo from "../../assets/sill-logo.png?url"; import marginLogo from "../../assets/margin-logo.png?url"; + import AccordionFilter from "../AccordionFilter.svelte"; + import { ACCORDION_FILTER_THRESHOLD } from "../../lib/constants"; let { tags, @@ -13,12 +15,19 @@ checkedTags: string[]; } = $props(); - function onTagChange() { - if (checkedTags.length > 0) checked = true; - } + let filter = $state(""); + + const filteredTags = $derived( + filter + ? tags.filter((t) => t.toLowerCase().includes(filter.toLowerCase())) + : tags + ); function onCheckedChange() { - if (!checked) checkedTags = []; + if (!checked) { + checkedTags = []; + filter = ""; + } } @@ -34,12 +43,32 @@ {#if tags.length === 0}
No tags found.
{:else} - {#each tags as tag (tag)} - - {/each} + {#if tags.length >= ACCORDION_FILTER_THRESHOLD} + + {/if} +
+ {#if filteredTags.length === 0} +
No matches.
+ {:else} + {#each filteredTags as tag (tag)} + + {/each} + {/if} +
{/if} diff --git a/src/components/destinations/CollectionsDestination.svelte b/src/components/destinations/CollectionsDestination.svelte index fd0c8ad..f26cdab 100644 --- a/src/components/destinations/CollectionsDestination.svelte +++ b/src/components/destinations/CollectionsDestination.svelte @@ -2,6 +2,8 @@ import sembleLogo from "../../assets/semble-logo.png?url"; import marginLogo from "../../assets/margin-logo.png?url"; import rabbitholeLogo from "../../assets/rabbithole-logo.png?url"; + import AccordionFilter from "../AccordionFilter.svelte"; + import { ACCORDION_FILTER_THRESHOLD } from "../../lib/constants"; interface Collection { uri: string; @@ -19,10 +21,18 @@ let open = $state(false); let loaded = $state(false); + let filter = $state(""); + + const filtered = $derived( + filter + ? collections.filter((c) => c.name.toLowerCase().includes(filter.toLowerCase())) + : collections + ); function toggle() { open = !open; if (open) loaded = true; + else filter = ""; } @@ -44,12 +54,31 @@ {#if collections.length === 0}
No collections found.
{:else} - {#each collections as collection (collection.uri)} - - {/each} + {#if collections.length >= ACCORDION_FILTER_THRESHOLD} + + {/if} +
+ {#if filtered.length === 0} +
No matches.
+ {:else} + {#each filtered as collection (collection.uri)} + + {/each} + {/if} +
{/if} {/if} diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 86154ff..46f7b13 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -1,2 +1,4 @@ +export const ACCORDION_FILTER_THRESHOLD = 5; + export const OAUTH_SCOPE = "atproto include:app.bsky.authCreatePosts include:at.margin.authFull include:network.cosmik.authFull blob:image/* repo:com.kipclip.annotation repo:com.kipclip.tag repo:com.kipclip.preferences repo:at.margin.bookmark repo:community.lexicon.bookmarks.bookmark"; diff --git a/src/styles/global.css b/src/styles/global.css index 14d9d30..d174856 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -286,3 +286,53 @@ button[type="submit"]:disabled { opacity: 0.35; cursor: not-allowed; } color: var(--muted); border-top: 1px solid var(--border); } + +.accordion-filter { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.6rem 1rem; + border-top: 1px solid var(--border); + color: var(--muted); +} + +.accordion-filter input[type="text"] { + flex: 1; + width: auto; + border: none; + background: transparent; + padding: 0; + font-size: 1rem; + min-width: 0; +} + +.accordion-filter input[type="text"]:focus { + border-color: transparent; + background: transparent; +} + +.accordion-filter-clear { + display: flex; + align-items: center; + justify-content: center; + background: none; + border: none; + padding: 2px; + cursor: pointer; + color: var(--muted); + border-radius: 50%; + flex-shrink: 0; + transition: color 0.15s; +} +.accordion-filter-clear:hover { color: var(--text); } + +.accordion-scroll { + max-height: calc(4.5 * 2.5rem); + overflow-y: auto; + scrollbar-width: thin; + scrollbar-color: var(--border) transparent; +} + +.accordion-scroll::-webkit-scrollbar { width: 4px; } +.accordion-scroll::-webkit-scrollbar-track { background: transparent; } +.accordion-scroll::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }