class DesktopPeopleToolbar extends Shadow { constructor(people, searchText, filterRole, filterTier, onSearch, onFilterRole, onFilterTier) { super() this.people = people this.searchText = searchText this.filterRole = filterRole this.filterTier = filterTier this.onSearch = onSearch this.onFilterRole = onFilterRole this.onFilterTier = onFilterTier } get allRoles() { const roles = new Set(); this.people.forEach(p => (p.roles || []).forEach(r => roles.add(r))); return [...roles].sort(); } render() { const online = this.people.filter(p => p._online).length; const total = this.people.length; HStack(() => { // Counts HStack(() => { p(`${total} member${total !== 1 ? "s" : ""}`) .margin(0) .fontSize(0.88, em) .fontWeight("600") .color("var(--headertext)") if (online > 0) { HStack(() => { VStack(() => {}) .width(0.42, em).height(0.42, em).borderRadius(50, pct) .background("#22c55e").flexShrink(0) p(`${online} online`) .margin(0).fontSize(0.78, em).color("#22c55e").fontWeight("500") }) .gap(0.3, em).alignItems("center") .paddingHorizontal(0.6, em).paddingVertical(0.18, em) .background("rgba(34,197,94,0.08)") .border("1px solid rgba(34,197,94,0.2)") .borderRadius(100, px) } }) .marginTop(20, px) .gap(0.75, em).alignItems("center").flex(1) // Search HStack(() => { p("🔍") .margin(0).fontSize(0.8, em).opacity(0.38).flexShrink(0) input("", "200px") .attr({ type: "text", placeholder: "Search members…", value: this.searchText }) .border("none").outline("none").background("transparent") .color("var(--headertext)").fontSize(0.85, em) .onInput((e) => this.onSearch(e.target.value)) }) .gap(0.5, em).paddingHorizontal(0.8, em) .background("var(--darkaccent)").border("1px solid var(--divider)") .borderRadius(0.5, em).alignItems("center") // Role filter this.filterSelect( [{ label: "All Roles", value: "" }, ...this.allRoles.map(r => ({ label: this.capitalize(r), value: r }))], this.filterRole, this.onFilterRole ) // Tier filter this.filterSelect( [{ label: "All Tiers", value: "" }, { label: "Regular", value: "1" }, { label: "Patron", value: "2" }], this.filterTier, this.onFilterTier ) }) .gap(0.65, em).paddingHorizontal(1.5, em).paddingVertical(0.85, em) .borderBottom("1px solid var(--divider)") .alignItems("center").width(100, pct).boxSizing("border-box").flexShrink(0) } filterSelect(options, value, onChange) { select(() => { options.forEach(opt => { option(opt.label) .attr({ value: opt.value, ...(opt.value === value ? { selected: "" } : {}) }) }) }) .paddingVertical(0.52, em).paddingHorizontal(0.75, em) .background("var(--darkaccent)").border("1px solid var(--divider)") .borderRadius(0.5, em).color("var(--headertext)").fontSize(0.85, em) .outline("none").cursor("pointer").flexShrink(0) .onChange((e) => onChange(e.target.value)) } capitalize(s) { return s ? s[0].toUpperCase() + s.slice(1) : s; } } register(DesktopPeopleToolbar)