Files
homarr/packages/widget-sdk/examples/checklist.json
T

47 lines
3.5 KiB
JSON

{
"$schema": "homarr-widget-package-v3",
"manifest": {
"id": "homarr.reference.checklist",
"version": "1.0.0",
"sdkVersion": "1",
"name": "Shared maintenance checklist",
"description": "A persistent dashboard checklist with user, placement, or installation storage and host-native commands.",
"entrypoints": {
"tile": "widget.tsx",
"advanced": "widget.tsx"
},
"handlers": {}
},
"files": {
"widget.tsx": "import type { FormEvent } from \"react\";\nimport type { WidgetStorageScope } from \"@homarr/widget-sdk\";\ntype ChecklistItem = {id:string;label:string;done:boolean};\nimport { Alert, Button, Checkbox, Group, Stack, Text, TextInput } from \"@mantine/core\";\nimport { useWidgetCommands, useWidgetHost, useWidgetOptions, useWidgetState, useWidgetStorage } from \"@homarr/widget-sdk\";\nexport default function Checklist() {\n const host = useWidgetHost();\n const options = useWidgetOptions<{title:string;scope:WidgetStorageScope}>();\n const [draft, setDraft] = useWidgetState(\"draft\", \"\");\n const stored = useWidgetStorage<ChecklistItem[]>(options.scope, \"checklist-v1\", []);\n const items = Array.isArray(stored.data) ? stored.data : [];\n const locked = host.isEditMode || stored.isPending || stored.saving;\n const save = (value:ChecklistItem[]) => { void stored.set(value).catch(() => undefined); };\n useWidgetCommands([{ id: \"clear-completed\", label: \"Clear completed items\", disabled: locked || !items.some((item) => item.done), run: () => save(items.filter((item) => !item.done)) }]);\n const add = (event:FormEvent<HTMLFormElement>) => { event.preventDefault(); if (!draft.trim() || locked) return; save([...items, { id: crypto.randomUUID(), label: draft.trim(), done: false }]); setDraft(\"\"); };\n return <Stack p=\"md\" gap=\"sm\" h=\"100%\" style={{ overflow: \"auto\" }}>\n <Group justify=\"space-between\"><Text fw={600}>{options.title}</Text><Text size=\"sm\" c=\"dimmed\">{items.filter((item) => item.done).length}/{items.length}</Text></Group>\n {(stored.error || stored.saveError) && <Alert color=\"red\">{stored.error?.message ?? stored.saveError?.message}</Alert>}\n {items.map((item) => <Checkbox key={item.id} label={item.label} checked={item.done} disabled={locked} onChange={() => save(items.map((row) => row.id === item.id ? { ...row, done: !row.done } : row))} />)}\n {!items.length && !stored.isPending && <Text c=\"dimmed\" size=\"sm\">Add your first maintenance task.</Text>}\n <form onSubmit={add}><Group wrap=\"nowrap\"><TextInput aria-label=\"New task\" placeholder=\"New task\" value={draft} onChange={(event) => setDraft(event.currentTarget.value)} style={{ flex: 1 }} /><Button type=\"submit\" disabled={locked || !draft.trim()}>Add</Button></Group></form>\n {host.displayMode === \"advanced\" && <Text size=\"xs\" c=\"dimmed\">Changes are saved to Homarr. The selected storage scope controls whether this checklist belongs to you, this tile, or every placement of this installation.</Text>}\n </Stack>;\n}"
},
"dependencies": {},
"connections": {},
"options": {
"title": {
"control": "text",
"label": "Title",
"default": "Maintenance"
},
"scope": {
"control": "select",
"label": "Storage scope",
"default": "instance",
"choices": [
{
"label": "This tile",
"value": "instance"
},
{
"label": "My checklist",
"value": "user"
},
{
"label": "All installation placements",
"value": "installation"
}
]
}
}
}