Exosphere is a set of small, modular, self-hostable community tools built on the AT Protocol. app.exosphere.site
7
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add label reorder buttons in settings

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Hugo 8a092256 505ec453

+78 -8
+31
packages/app/src/pages/color-picker.css.ts
··· 29 29 border: "none", 30 30 cursor: "pointer", 31 31 }); 32 + 33 + export const reorderArrows = style({ 34 + display: "flex", 35 + flexDirection: "column", 36 + gap: "2px", 37 + }); 38 + 39 + export const arrowBtn = style({ 40 + display: "inline-flex", 41 + alignItems: "center", 42 + justifyContent: "center", 43 + inlineSize: "24px", 44 + blockSize: "24px", 45 + border: `1px solid ${vars.color.border}`, 46 + borderRadius: vars.radius.sm, 47 + backgroundColor: vars.color.surface, 48 + color: vars.color.text, 49 + cursor: "pointer", 50 + padding: 0, 51 + fontSize: "12px", 52 + lineHeight: 1, 53 + selectors: { 54 + "&:disabled": { 55 + opacity: 0.3, 56 + cursor: "default", 57 + }, 58 + "&:hover:not(:disabled)": { 59 + backgroundColor: vars.color.surfaceHover, 60 + }, 61 + }, 62 + });
+47 -8
packages/app/src/pages/sphere-labels.tsx
··· 11 11 createSphereLabel, 12 12 updateSphereLabel, 13 13 deleteSphereLabel, 14 + reorderSphereLabels, 14 15 type LabelData, 15 16 } from "../api/spheres.ts"; 16 17 ··· 108 109 } 109 110 }; 110 111 112 + const handleMove = async (index: number, direction: -1 | 1) => { 113 + if (!data) return; 114 + const newIndex = index + direction; 115 + if (newIndex < 0 || newIndex >= data.labels.length) return; 116 + const reordered = [...data.labels]; 117 + const [item] = reordered.splice(index, 1); 118 + reordered.splice(newIndex, 0, item); 119 + try { 120 + await reorderSphereLabels( 121 + handle, 122 + reordered.map((l) => l.id), 123 + ); 124 + refetch(); 125 + } catch (err) { 126 + listError.value = err instanceof Error ? err.message : "Failed to reorder labels."; 127 + } 128 + }; 129 + 111 130 const confirmingDeleteId = useSignal<string | null>(null); 112 131 113 132 const handleDelete = async (id: string) => { ··· 189 208 <p class={ui.emptyState}>No labels yet.</p> 190 209 ) : ( 191 210 <div class={ui.stackSm}> 192 - {data.labels.map((label) => ( 211 + {data.labels.map((label, index) => ( 193 212 <div key={label.id} class={ui.card}> 194 213 {editingId.value === label.id ? ( 195 214 <div class={ui.formStack}> ··· 233 252 </div> 234 253 ) : ( 235 254 <div class={ui.row}> 236 - <div> 237 - <LabelBadge label={label} /> 238 - {label.description && ( 239 - <span class={ui.muted} style={{ marginInlineStart: "8px" }}> 240 - {label.description} 241 - </span> 242 - )} 255 + <div class={ui.row}> 256 + <div class={cpUi.reorderArrows}> 257 + <button 258 + class={cpUi.arrowBtn} 259 + disabled={index === 0} 260 + onClick={() => handleMove(index, -1)} 261 + title="Move up" 262 + > 263 + &#8593; 264 + </button> 265 + <button 266 + class={cpUi.arrowBtn} 267 + disabled={index === data!.labels.length - 1} 268 + onClick={() => handleMove(index, 1)} 269 + title="Move down" 270 + > 271 + &#8595; 272 + </button> 273 + </div> 274 + <div> 275 + <LabelBadge label={label} /> 276 + {label.description && ( 277 + <span class={ui.muted} style={{ marginInlineStart: "8px" }}> 278 + {label.description} 279 + </span> 280 + )} 281 + </div> 243 282 </div> 244 283 <div class={ui.row}> 245 284 <button class={ui.buttonInline} onClick={() => startEdit(label)}>