feat: support a multi-link list in the bookmark widget

A bookmark widget can now carry an optional links array instead of
a single href, rendering as a compact card with one row per link -
for grouping several quick links (e.g. "all services", "all Coolify
apps") into one widget instead of one card per link.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WpxhtQY3CExQdMs4j7MmJe
This commit is contained in:
2026-08-18 10:14:47 +02:00
co-authored by Claude Sonnet 5
parent 39e0eaf256
commit 5a33218235
2 changed files with 39 additions and 1 deletions
+31
View File
@@ -6,6 +6,37 @@ import { BrandIcon } from "@/components/widgets/BrandIcon";
type BookmarkWidget = Extract<Widget, { type: "bookmark" }>; type BookmarkWidget = Extract<Widget, { type: "bookmark" }>;
export function BookmarkWidget({ widget }: { widget: BookmarkWidget }) { export function BookmarkWidget({ widget }: { widget: BookmarkWidget }) {
if (widget.links && widget.links.length > 0) {
return (
<div className={PN_CARD_CLASS} style={{ gridColumn: "span 3" }}>
<div className="flex items-center gap-2">
<BrandIcon slug={widget.icon} />
<span className="min-w-0 flex-1 truncate text-[15px] font-medium text-fg">{widget.name}</span>
</div>
{widget.description && <span className="text-xs text-fg-muted">{widget.description}</span>}
<div className="-mx-1 flex flex-col divide-y divide-border">
{widget.links.map((link) => (
<a
key={link.href}
href={link.href}
target="_blank"
rel="noreferrer"
className="group flex items-center gap-2 px-1 py-1.5 text-sm text-fg-muted no-underline hover:text-accent"
>
<BrandIcon slug={link.icon} size={14} />
<span className="min-w-0 flex-1 truncate">{link.name}</span>
<ArrowUpRight
size={13}
className="shrink-0 text-accent opacity-0 group-hover:opacity-100"
aria-hidden
/>
</a>
))}
</div>
</div>
);
}
return ( return (
<a <a
href={widget.href} href={widget.href}
+8 -1
View File
@@ -6,12 +6,19 @@ const durationSchema = z
.string() .string()
.regex(/^\d+(ms|s|m|h)$/, 'Expected a duration like "500ms", "5s", "1m", or "1h"'); .regex(/^\d+(ms|s|m|h)$/, 'Expected a duration like "500ms", "5s", "1m", or "1h"');
export const bookmarkLinkSchema = z.object({
name: z.string(),
href: z.string().url(),
icon: z.string().optional(),
});
export const bookmarkWidgetSchema = z.object({ export const bookmarkWidgetSchema = z.object({
type: z.literal("bookmark"), type: z.literal("bookmark"),
name: z.string(), name: z.string(),
href: z.string().url(), href: z.string().url().optional(),
description: z.string().optional(), description: z.string().optional(),
icon: z.string().optional(), icon: z.string().optional(),
links: z.array(bookmarkLinkSchema).optional(),
}); });
export const searchWidgetSchema = z.object({ export const searchWidgetSchema = z.object({