Last active
December 12, 2024 15:20
-
-
Save eterps/10f04df57a4f766e54eb2610581c91bb to your computer and use it in GitHub Desktop.
Select
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'sinatra' | |
require 'json' | |
TECH_TAGS = %w[javascript python ruby java rust golang react vue angular typescript] | |
get('/') { erb :index } | |
get('/suggest') { TECH_TAGS.grep(/#{params[:q]}/i).to_json } | |
get('/search') { erb :_results, layout: false, locals: { tags: params[:tags].to_s.split(',').reject(&:empty?) } } | |
__END__ | |
@@ layout | |
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Tech Filter</title> | |
<script src="https://unpkg.com/[email protected]/dist/cdn.min.js" defer></script> | |
<script> | |
document.addEventListener('alpine:init', () => { | |
Alpine.data('techFilter', () => ({ | |
search: '', | |
suggestions: [], | |
selected: new URLSearchParams(location.search).get('tags')?.split(',').filter(Boolean) || [], | |
results: '', | |
async fetchSuggestions() { | |
this.suggestions = this.search ? | |
await (await fetch(`/suggest?q=${this.search}`)).json() : []; | |
}, | |
updateUrl() { | |
const url = this.selected.length ? `/?tags=${this.selected.join(',')}` : '/'; | |
history.pushState(null, '', url); | |
}, | |
async addTag(tag) { | |
if (!this.selected.includes(tag)) { | |
this.selected.push(tag); | |
this.updateUrl(); | |
this.results = await (await fetch(`/search?tags=${this.selected}`)).text(); | |
} | |
this.search = ''; | |
this.suggestions = []; | |
}, | |
removeTag(tag) { | |
this.selected = this.selected.filter(t => t !== tag); | |
this.updateUrl(); | |
this.updateResults(); | |
}, | |
async updateResults() { | |
this.results = await (await fetch(`/search?tags=${this.selected}`)).text(); | |
}, | |
init() { | |
if (this.selected.length) this.updateResults(); | |
} | |
})); | |
}); | |
</script> | |
<style> | |
body { display: flex; margin: 0 } | |
aside { | |
width: 250px; | |
padding: 1rem; | |
border-right: 1px solid #ddd; | |
height: 100vh | |
} | |
main { | |
flex: 1; | |
padding: 1rem; | |
display: flex; | |
flex-direction: column | |
} | |
input { | |
width: 100%; | |
margin-bottom: 1rem; | |
padding: .5rem | |
} | |
.tag { | |
display: flex; | |
padding: .5rem 0 | |
} | |
.remove { | |
margin-left: auto; | |
border: 0; | |
background: 0; | |
cursor: pointer | |
} | |
#suggestions { | |
position: absolute; | |
background: #fff; | |
border: 1px solid #ddd; | |
width: 250px | |
} | |
.suggestion { padding: .5rem } | |
.suggestion:hover { background: #f5f5f5 } | |
.results-header { | |
text-align: right; | |
margin-bottom: 1rem | |
} | |
</style> | |
</head> | |
<body> | |
<%= yield %> | |
</body> | |
</html> | |
@@ index | |
<div x-data="techFilter"> | |
<aside> | |
<h3>Tech Stack</h3> | |
<div class="search-container"> | |
<input type="text" | |
x-model="search" | |
@input.debounce.200ms="fetchSuggestions" | |
@click.outside="suggestions = []" | |
placeholder="Search technologies..."> | |
<div id="suggestions" x-show="suggestions.length"> | |
<template x-for="tag in suggestions"> | |
<div class="suggestion" | |
x-text="tag" | |
@click="addTag(tag)"> | |
</div> | |
</template> | |
</div> | |
</div> | |
<template x-for="tag in selected"> | |
<div class="tag"> | |
<span x-text="tag"></span> | |
<button class="remove" @click="removeTag(tag)">×</button> | |
</div> | |
</template> | |
</aside> | |
<main x-html="results"> | |
<h1>Results will appear here</h1> | |
</main> | |
</div> | |
@@ _results | |
<div class="results-header"> | |
<h2>Results for tags: <%= tags.join(', ') %></h2> | |
</div> | |
<div class="results-content"> | |
<!-- Your actual results rendering here --> | |
</div> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment