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:
@@ -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}
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
Reference in New Issue
Block a user