Component structure

Make-up, location, behavior, and interactions for each component.

SpacesSearch

Search input with icon. Substring search over spaces title, description, address, hierarchy path.

src/lib/components/spaces/SpacesSearch.svelte

Live example

Tailwind (summary)

  • Input: rounded-lg border border-emerald-200 bg-white py-2 pl-9 pr-3 text-sm, focus:ring-2 focus:ring-emerald-500
  • Icon: absolute left-3 top-1/2 -translate-y-1/2 text-emerald-500

Props

searchText
string
default: ''

Two-way bound search query

Children

None

Used by

SpacesFiltersPanel

Behavior

  • Binds searchText to input. Parent filters spaces by substring match.
  • No internal state; purely controlled by parent.

Structure

  • label > relative div > Search icon (lucide) + input[type=search]
  • Emits no events; uses bind:value for sync.

SpacesToolbar

View toggle (List/Map), Sort menu, Filters toggle. Sticky above content.

src/lib/components/spaces/SpacesToolbar.svelte

Live example

Sort list by

Tailwind (summary)

  • Wrapper: sticky top-0 z-10, bg-gradient-to-b from-slate-50/95 via-white/95, backdrop-blur-sm
  • View group: rounded-xl bg-emerald-100/60 p-1 shadow-inner ring-1 ring-emerald-200/60
  • Buttons: rounded-lg px-4 py-2.5, selected bg-emerald-500 text-white

Props

currentView
'list' | 'map'
default: 'map'
onSwitchToList
() => void
onSwitchToMap
() => void
sortBy
string
default: 'score'

Two-way bound: recent | score | proximity

sortOptions
{ value, label }[]
filtersOpen
boolean
onToggleFilters
() => void
filtersActive
boolean
filteredCount
number
totalCount
number
spacesByScoreCount
number
allSpacesCount
number
spacesLoading
boolean
spacesError
string | null

Children

None

Used by

/spaces +page.svelte

Behavior

  • Local state: sortMenuOpen. Escape key closes sort menu.
  • View buttons call onSwitchToList/onSwitchToMap. Sort menu assigns sortBy and closes.
  • Filters button calls onToggleFilters, aria-controls filters-panel.
  • Click-outside overlay closes sort menu.

Structure

  • Sticky wrapper: View group (List/Map toggle) | Sort menu + Filters button
  • View: two buttons, aria-pressed by currentView. Subtext shows count.
  • Sort: button opens dropdown; listbox with Recent, Score, Proximity. Overlay for close.
  • Filters: button with badge (filteredCount of totalCount) when filtersActive.

SpacesFiltersPanel

Collapsible filter panel. Search, Distance, Score tier, Rank, Domain/Object/Condition.

src/lib/components/spaces/SpacesFiltersPanel.svelte

Live example

Filters

3 problems

Tailwind (summary)

  • Panel: rounded-xl bg-emerald-50/80 p-4 shadow-sm ring-1 ring-emerald-200/60
  • Distance pills: rounded-full, active=bg-emerald-600 text-white, inactive=bg-white border-emerald-200
  • Selects: rounded-lg border border-emerald-200 bg-white, focus:ring-emerald-500

Props

searchText
string

Two-way bound

filterDomain
string
filterObject
string
filterCondition
string
filterOptions
{ domains, objects, conditions }
filterRadiusKm
number
default: 0

0 = no limit

userLocationAvailable
boolean
filterScoreTier
'70+' | '50-70' | '20-50' | '<20' | ''
filterRankTopN
0 | 10 | 25 | 50 | 100
spacesByScoreCount
number
onClearFilters
() => void

Children

SpacesSearch

Used by

/spaces +page.svelte

Behavior

  • filtersActive derived from any filter set. Clear all resets via onClearFilters.
  • Distance: discrete options (50m, 200m, 500m, 1km, 2.5km, 5km, No limit). Maps to filterRadiusKm.

Structure

  • id="filters-panel", aria-labelledby="filters-toggle"
  • Header: "Filters" + Clear all button or count
  • SpacesSearch (bind:searchText)
  • Distance: clickable pill buttons with 7 preset increments (when userLocationAvailable)
  • Score tier: select. Rank: select (top N)
  • Domain / Object / Condition: 3 selects from filterOptions

SpacesPageHeader

Page header with Add a problem and View here actions.

src/lib/components/spaces/SpacesPageHeader.svelte

Live example

See it. Share it. Sort it.

View problems reported in this area. Vote them up/down. Add new problems.

Props

onAddHereClick
() => void
onViewHereClick
(() => void) | undefined

Children

None

Used by

/spaces +page.svelte

Behavior

  • Add a problem: opens QuickAddSpaceModal (parent).
  • View here: zooms map to user location. If no handler, uses link /spaces?view=map&zoom=close.

Structure

  • header > h1 "See it. Share it. Sort it." + button group
  • Add a problem: rose button, MapPin icon
  • View here: emerald button/link, Map icon

SpaceVotingButtons

Up / Down / Resolve vote buttons. Used in list cards and detail page.

src/lib/components/spaces/SpaceVotingButtons.svelte

Live example

variant="card"

variant="detail"

Props

votes
{ up, down, resolve }
default: { up:0, down:0, resolve:0 }
userVoteType
'up' | 'down' | 'resolve' | null
onVote
((voteType) => void) | null
votePending
boolean
variant
'card' | 'detail'
default: 'card'

Children

None

Used by

SpaceListItem, /spaces/[id] detail page

Behavior

  • card: compact inline layout for list. detail: larger with ring/shadow.
  • Calls onVote(voteType). Prevents default/stopPropagation on click/keydown.
  • Disabled when votePending or !onVote.

Structure

  • div[role=group] > 3 buttons: Up (ThumbsUp), Down (ThumbsDown), Resolve (CheckCircle)
  • Each shows count. Selected state: bg-emerald-500 / bg-rose-500 / bg-emerald-700.

SpaceHierarchyPath

Succinct visual guide: one line [icon] Domain · [icon] Object · Condition. Sized for popup, cards, detail.

src/lib/components/spaces/SpaceHierarchyPath.svelte

Live example

variant="popup" (map/tooltips)

Leisure
Sports
Football

variant="compact" (cards)

Leisure
Sports
Football

variant="default" (detail)

Leisure
Sports
Football

Tailwind (summary)

  • popup: text-[10px], no border; compact: max-w-[180px] rounded-md border py-1 px-2; default: max-w-[220px] rounded-lg

Props

path
string | undefined

e.g. "Domain > Object > Condition"

variant
'popup' | 'compact' | 'default'
default: 'compact'

Children

None

Used by

SpaceListItem, /spaces/[id] detail page

Behavior

  • Parses path by > » |. Single line: [icon] Domain · [icon] Object · Condition. Icons from getSegmentIconStyle.
  • popup = smallest (map/tooltips); compact = cards; default = detail. Renders only when at least one segment exists.

Structure

  • Inline-flex row: domain icon+value · object icon+value · condition (text). Map popup uses HTML snippet (same path text).

SpaceListItem

Card for one space. Title, hierarchy, score, voting. Links to /spaces/[id].

src/lib/components/SpaceListItem.svelte

Live example

Example space (dev preview)

1.2 km away

Short description for the component preview.

Leisure

Sports

Football

Details

Tailwind (summary)

  • Card: rounded-2xl border border-slate-200/90 border-l-4 shadow, hover:scale-[1.02]
  • Score: lead = rounded-xl px-4 py-2.5 text-xl; primary/default = rounded-lg px-2.5 py-1 text-xs

Props

space
Space | SpaceWithScore
votes
SpaceVoteCounts

Deprecated; use space.vote_*

priority
'lead' | 'primary' | 'default'
default: 'default'
distanceKm
number | undefined
userVoteType
'up' | 'down' | 'resolve' | null'
onVote
callback(spaceId, voteType) | null
votePending
boolean
isCreator
boolean
onDelete
callback(spaceId) | null
deletePending
boolean

Children

SpaceVotingButtons, SpaceHierarchyPath, ScoreBreakdownVisual

Used by

/spaces +page.svelte (list view)

Behavior

  • lead/primary/default: icon size, score badge size, title level.
  • Calls onVote(space.id, voteType). Delete: onDelete(space.id) when isCreator.

Structure

  • a[href=/spaces/:id]. Image (optional) | Content column.
  • Top: title + icon (getSegmentIconStyle) + "View details" chevron
  • Middle: description + SpaceHierarchyPath(compact) + distance
  • Bottom: score badge + ScoreBreakdownVisual + SpaceVotingButtons(card) + Delete (if creator)