{"version":3,"file":"QuickLinksWidget-BRW9ahLK.mjs","names":["LinkIcon"],"sources":["../../widgets/src/contexts/PortalUrlVariablesContext.tsx","../../core/src/url-templating.ts","../../widgets/src/widgets/QuickLinksWidget.tsx"],"sourcesContent":["import { createContext, useContext, type Provider } from \"react\";\nimport type { UrlVariableValues } from \"@fluid-app/portal-core/url-templating\";\n\n/**\n * Values used to substitute `{{token}}` placeholders in widget-configured URLs.\n *\n * **Production runtime substitution happens server-side** in the Portal Tenant\n * BFF via `UrlResolver` (mirroring `MobileTabWidget#resolve_link_urls`).\n * The portal client receives URLs already resolved, so the default `{}` value\n * is the right one for the live `AppShell` — substitution becomes a no-op\n * because there are no `{{...}}` tokens left to match.\n *\n * **Admin builder preview** mounts the provider with `PREVIEW_URL_VARIABLES`\n * so admins see realistic sample values for any `{{username}}` /\n * `{{replicated_url}}` tokens they've configured, without the builder needing\n * to fetch real user data or hit the BFF resolver.\n */\nconst PortalUrlVariablesContext = createContext<UrlVariableValues>({});\n\nexport const PortalUrlVariablesProvider: Provider<UrlVariableValues> =\n  PortalUrlVariablesContext.Provider;\n\nexport function usePortalUrlVariables(): UrlVariableValues {\n  return useContext(PortalUrlVariablesContext);\n}\n\n/**\n * Deterministic preview values mounted by admin-builder render sites that\n * show widget previews outside the live portal AppShell, so URLs referencing\n * template tokens render to recognizable placeholders rather than their\n * `|| fallback_path` clauses.\n */\nexport const PREVIEW_URL_VARIABLES: UrlVariableValues = {\n  username: \"rep\",\n  replicated_url: \"https://example.com\",\n};\n","/**\n * URL variable substitution for portal widgets.\n *\n * Mirrors the mobile app's Quick Links template-tag syntax so URLs configured\n * by admins in the portal builder can include placeholders like\n * `{{username || /profile}}` that resolve to user-specific values at render\n * time.\n *\n * Supported syntax:\n *   - `{{token}}` — replace with the value at `values[token]`. If no value is\n *     provided, the literal placeholder is left in place.\n *   - `{{token || fallback_path}}` — replace with the value if present and\n *     non-empty, otherwise replace with `fallback_path` (everything after `||`).\n *\n * Leading/trailing whitespace inside the braces is permitted:\n *   `{{ username || /profile }}` is equivalent to `{{username || /profile}}`.\n */\n\nconst TOKEN_PATTERN = /\\{\\{\\s*([^}|\\s]+)\\s*(?:\\|\\|\\s*([^}]*?))?\\s*\\}\\}/g;\n\nexport type UrlVariableValues = Record<string, string | null | undefined>;\n\nexport function substituteUrlVariables(\n  url: string,\n  values: UrlVariableValues,\n): string {\n  return url.replace(\n    TOKEN_PATTERN,\n    (match, rawToken: string, rawFallback?: string) => {\n      const token = rawToken.trim();\n      const value = values[token];\n      if (value != null && value !== \"\") return value;\n      if (rawFallback !== undefined) return rawFallback.trim();\n      return match;\n    },\n  );\n}\n","import { type ComponentProps } from \"react\";\nimport type React from \"react\";\nimport type {\n  BackgroundValue,\n  BorderRadiusOptions,\n  BorderWidthOptions,\n  ColorOptions,\n  FontSizeOptions,\n  PaddingOptions,\n} from \"@fluid-app/portal-core/types\";\nimport type {\n  PropertyField,\n  WidgetPropertySchema,\n} from \"@fluid-app/portal-core/registries\";\nimport {\n  substituteUrlVariables,\n  type UrlVariableValues,\n} from \"@fluid-app/portal-core/url-templating\";\nimport { usePortalUrlVariables } from \"../contexts/PortalUrlVariablesContext\";\nimport { useInternalLinks } from \"../hooks/use-internal-link\";\nimport {\n  getBorderRadiusField,\n  getBorderWidthField,\n  getBorderColorField,\n  getColorField,\n  getFontSizeField,\n  getPaddingField,\n  borderWidthClasses,\n  borderColorClasses,\n} from \"../core/fields\";\nimport {\n  Bell,\n  BookmarkIcon,\n  Calendar,\n  ChevronRight,\n  ExternalLink,\n  Gift,\n  Globe,\n  Heart,\n  Link as LinkIcon,\n  Mail,\n  MapPin,\n  MessageCircle,\n  Package,\n  Phone,\n  Play,\n  Share2,\n  ShoppingBag,\n  Sparkles,\n  Star,\n  Trophy,\n  User,\n  Video,\n  Zap,\n  type LucideIcon,\n} from \"lucide-react\";\n\n// ---------- icon registry ----------\n\nconst ICON_MAP: Record<string, LucideIcon> = {\n  User,\n  Share2,\n  ShoppingBag,\n  Video,\n  Mail,\n  MessageCircle,\n  Globe,\n  Calendar,\n  Bell,\n  Heart,\n  Star,\n  Zap,\n  Gift,\n  Phone,\n  MapPin,\n  Play,\n  Package,\n  ExternalLink,\n  Bookmark: BookmarkIcon,\n  Trophy,\n  Sparkles,\n  Link: LinkIcon,\n};\n\nconst ICON_OPTIONS: Array<{ label: string; value: string }> = [\n  { label: \"Link (default)\", value: \"Link\" },\n  { label: \"User / Profile\", value: \"User\" },\n  { label: \"Share\", value: \"Share2\" },\n  { label: \"Shopping Bag\", value: \"ShoppingBag\" },\n  { label: \"Video\", value: \"Video\" },\n  { label: \"Mail\", value: \"Mail\" },\n  { label: \"Message\", value: \"MessageCircle\" },\n  { label: \"Globe\", value: \"Globe\" },\n  { label: \"Calendar\", value: \"Calendar\" },\n  { label: \"Bell / Notifications\", value: \"Bell\" },\n  { label: \"Heart\", value: \"Heart\" },\n  { label: \"Star\", value: \"Star\" },\n  { label: \"Zap / Lightning\", value: \"Zap\" },\n  { label: \"Gift\", value: \"Gift\" },\n  { label: \"Phone\", value: \"Phone\" },\n  { label: \"Map Pin\", value: \"MapPin\" },\n  { label: \"Play\", value: \"Play\" },\n  { label: \"Package\", value: \"Package\" },\n  { label: \"External Link\", value: \"ExternalLink\" },\n  { label: \"Bookmark\", value: \"Bookmark\" },\n  { label: \"Trophy\", value: \"Trophy\" },\n  { label: \"Sparkles\", value: \"Sparkles\" },\n];\n\nconst capitalize = (s: string): string =>\n  s ? s.charAt(0).toUpperCase() + s.slice(1) : s;\n\nconst getIcon = (name: string | undefined): LucideIcon => {\n  if (!name) return LinkIcon;\n  return ICON_MAP[name] ?? ICON_MAP[capitalize(name)] ?? LinkIcon;\n};\n\n// ---------- link parsing / collection ----------\n\ntype ParsedLink = {\n  id: string;\n  label: string;\n  url: string;\n  iconName: string;\n  iconColor: ColorOptions;\n};\n\nconst isColorOption = (value: string): value is ColorOptions =>\n  [\n    \"background\",\n    \"foreground\",\n    \"primary\",\n    \"secondary\",\n    \"accent\",\n    \"muted\",\n    \"destructive\",\n  ].includes(value);\n\nconst FALLBACK_COLOR_CYCLE: ColorOptions[] = [\n  \"primary\",\n  \"accent\",\n  \"secondary\",\n  \"destructive\",\n];\n\n// Legacy format per line: \"Label | URL | IconName | ColorOption\"\n// Kept for backwards compatibility with existing saved configs.\nconst parseLegacyLinks = (raw: string[] | string): ParsedLink[] => {\n  const lines = Array.isArray(raw) ? raw : raw.split(/\\r?\\n/).filter(Boolean);\n  return lines\n    .map((line, index) => {\n      const parts = line.split(\"|\").map((s) => s.trim());\n      const label = parts[0] ?? \"\";\n      if (!label) return null;\n      const url = parts[1] ?? \"#\";\n      const iconName = parts[2] && parts[2].length > 0 ? parts[2] : \"Link\";\n      const colorCandidate = parts[3];\n      const iconColor: ColorOptions =\n        colorCandidate && isColorOption(colorCandidate)\n          ? colorCandidate\n          : (FALLBACK_COLOR_CYCLE[index % FALLBACK_COLOR_CYCLE.length] ??\n            \"primary\");\n      return {\n        id: `link-${index}`,\n        label,\n        url,\n        iconName,\n        iconColor,\n      };\n    })\n    .filter((x): x is ParsedLink => x !== null);\n};\n\n// New structured format — gather links from individual link{n}* props\nconst MAX_LINKS = 8;\n\ntype SlotProps = {\n  enabled: boolean;\n  label: string | undefined;\n  url: string | undefined;\n  icon: string | undefined;\n  color: ColorOptions | undefined;\n};\n\n// Per-slot merge of structured props with the legacy `links` array. A slot\n// is \"structurally configured\" when at least one of its content props\n// (label / url / icon / color) is explicitly set; in that case structured\n// props win and the slot's `enabled` flag is honored. Slots that haven't\n// been structurally touched fall back to the legacy entry at the same\n// index — so editing one slot in a legacy-format widget doesn't silently\n// drop URLs/icons/colors for the other slots.\nconst resolveLinks = (\n  slots: SlotProps[],\n  legacy: ParsedLink[],\n): ParsedLink[] => {\n  return slots\n    .map((slot, index): ParsedLink | null => {\n      const legacyEntry = legacy[index];\n\n      const hasStructured =\n        slot.label !== undefined ||\n        slot.url !== undefined ||\n        slot.icon !== undefined ||\n        slot.color !== undefined;\n\n      if (hasStructured) {\n        // Slot 1 is always enabled; slots 2+ need enabled=true.\n        const isSlotActive = index === 0 || slot.enabled;\n        if (!isSlotActive) return null;\n\n        const label = (slot.label ?? legacyEntry?.label ?? \"\").trim();\n        if (!label) return null;\n\n        const url =\n          slot.url && slot.url.length > 0\n            ? slot.url\n            : (legacyEntry?.url ?? \"#\");\n        const iconName =\n          slot.icon && slot.icon.length > 0\n            ? slot.icon\n            : (legacyEntry?.iconName ?? \"Link\");\n        const iconColor: ColorOptions =\n          slot.color ??\n          legacyEntry?.iconColor ??\n          FALLBACK_COLOR_CYCLE[index % FALLBACK_COLOR_CYCLE.length] ??\n          \"primary\";\n\n        return {\n          id: `link-${index}`,\n          label,\n          url,\n          iconName,\n          iconColor,\n        };\n      }\n\n      // No structured props for this slot — use the legacy entry as-is.\n      // Legacy widgets predate the per-slot `enabled` toggle, so we don't\n      // gate legacy entries on it (otherwise legacy slots 5+ would vanish\n      // because their `linkNEnabled` defaults to `false`).\n      return legacyEntry ?? null;\n    })\n    .filter((x): x is ParsedLink => x !== null);\n};\n\n// ---------- link row ----------\n\ntype LinkRowProps = {\n  link: ParsedLink;\n  textColor: ColorOptions;\n  borderRadius: BorderRadiusOptions;\n  openInNewTab: boolean;\n  showChevron: boolean;\n  iconRadius: BorderRadiusOptions;\n  layout: \"cards\" | \"list\";\n  isLast: boolean;\n  urlVariables: UrlVariableValues;\n};\n\nfunction LinkRow({\n  link,\n  textColor,\n  borderRadius,\n  openInNewTab,\n  showChevron,\n  iconRadius,\n  layout,\n  isLast,\n  urlVariables,\n}: LinkRowProps) {\n  const Icon = getIcon(link.iconName);\n  const resolveInternalLink = useInternalLinks();\n\n  const wrapperBase = `group relative flex items-center gap-3 py-3 transition-all duration-200`;\n  const wrapperCardClasses = `rounded-${borderRadius} px-3 bg-${textColor}/5 hover:bg-${textColor}/10 hover:-translate-y-0.5`;\n  const wrapperListClasses = `px-0 hover:bg-${textColor}/5 rounded-md`;\n\n  const wrapperClasses =\n    layout === \"cards\"\n      ? `${wrapperBase} ${wrapperCardClasses}`\n      : `${wrapperBase} ${wrapperListClasses}`;\n\n  const content = (\n    <>\n      {/* Icon badge — colored gradient square with white icon centered */}\n      <span\n        aria-hidden=\"true\"\n        className={`relative flex size-10 shrink-0 items-center justify-center rounded-${iconRadius} text-${link.iconColor}-foreground`}\n        style={{\n          background: `linear-gradient(135deg, color-mix(in oklch, var(--color-${link.iconColor}) 75%, white 25%) 0%, var(--color-${link.iconColor}) 100%)`,\n          boxShadow: `\n            inset 0 1px 0 rgba(255,255,255,0.25),\n            inset 0 -1px 0 rgba(0,0,0,0.1),\n            0 1px 2px color-mix(in oklch, var(--color-${link.iconColor}) 30%, transparent)\n          `,\n        }}\n      >\n        <Icon className=\"size-5\" strokeWidth={2.25} />\n      </span>\n\n      {/* Label */}\n      <span\n        className={`min-w-0 flex-1 truncate text-[15px] font-bold tracking-[-0.01em] text-${textColor}`}\n      >\n        {link.label}\n      </span>\n\n      {/* Chevron */}\n      {showChevron && (\n        <ChevronRight\n          aria-hidden=\"true\"\n          className={`size-4 shrink-0 text-${textColor}/35 transition-transform duration-200 group-hover:translate-x-0.5 group-hover:text-${textColor}/60`}\n        />\n      )}\n    </>\n  );\n\n  const separator =\n    layout === \"list\" && !isLast ? (\n      <div\n        aria-hidden=\"true\"\n        className=\"ml-[52px]\"\n        style={{\n          borderBottom: `1px solid color-mix(in oklch, var(--color-${textColor}) 8%, transparent)`,\n        }}\n      />\n    ) : null;\n\n  const resolvedUrl =\n    link.url && link.url !== \"#\"\n      ? substituteUrlVariables(link.url, urlVariables)\n      : link.url;\n  const href = resolvedUrl && resolvedUrl !== \"#\" ? resolvedUrl : undefined;\n\n  if (href) {\n    // Same-tab links into this portal navigate in-app so the destination keeps\n    // a back button; links the author set to open in a new tab stay as they are.\n    const internalLink = resolveInternalLink(openInNewTab ? undefined : href);\n\n    return (\n      <>\n        <a\n          href={href}\n          onClick={internalLink.followInternally}\n          target={openInNewTab ? \"_blank\" : undefined}\n          rel={openInNewTab ? \"noopener noreferrer\" : undefined}\n          className={wrapperClasses}\n        >\n          {content}\n        </a>\n        {separator}\n      </>\n    );\n  }\n\n  return (\n    <>\n      <div className={wrapperClasses}>{content}</div>\n      {separator}\n    </>\n  );\n}\n\n// ---------- widget ----------\n\ntype QuickLinksWidgetProps = ComponentProps<\"div\"> & {\n  // Title\n  titleEnabled?: boolean;\n  title?: string;\n  titleFontSize?: FontSizeOptions;\n  titleColor?: ColorOptions;\n\n  // Links — new structured per-slot props\n  link1Label?: string;\n  link1Url?: string;\n  link1Icon?: string;\n  link1Color?: ColorOptions;\n  link2Enabled?: boolean;\n  link2Label?: string;\n  link2Url?: string;\n  link2Icon?: string;\n  link2Color?: ColorOptions;\n  link3Enabled?: boolean;\n  link3Label?: string;\n  link3Url?: string;\n  link3Icon?: string;\n  link3Color?: ColorOptions;\n  link4Enabled?: boolean;\n  link4Label?: string;\n  link4Url?: string;\n  link4Icon?: string;\n  link4Color?: ColorOptions;\n  link5Enabled?: boolean;\n  link5Label?: string;\n  link5Url?: string;\n  link5Icon?: string;\n  link5Color?: ColorOptions;\n  link6Enabled?: boolean;\n  link6Label?: string;\n  link6Url?: string;\n  link6Icon?: string;\n  link6Color?: ColorOptions;\n  link7Enabled?: boolean;\n  link7Label?: string;\n  link7Url?: string;\n  link7Icon?: string;\n  link7Color?: ColorOptions;\n  link8Enabled?: boolean;\n  link8Label?: string;\n  link8Url?: string;\n  link8Icon?: string;\n  link8Color?: ColorOptions;\n\n  // Legacy — still accepted for backwards compatibility\n  links?: string[] | string;\n\n  // Layout\n  layout?: \"cards\" | \"list\";\n  iconRadius?: BorderRadiusOptions;\n  showChevron?: boolean;\n  openInNewTab?: boolean;\n\n  // Design\n  background?: BackgroundValue;\n  textColor?: ColorOptions;\n  padding?: PaddingOptions;\n  borderRadius?: BorderRadiusOptions;\n  borderWidth?: BorderWidthOptions;\n  borderColor?: ColorOptions;\n};\n\nexport function QuickLinksWidget({\n  titleEnabled = true,\n  title = \"Quick Links\",\n  titleFontSize = \"md\",\n  titleColor = \"foreground\",\n\n  link1Label,\n  link1Url,\n  link1Icon,\n  link1Color,\n  link2Enabled = true,\n  link2Label,\n  link2Url,\n  link2Icon,\n  link2Color,\n  link3Enabled = true,\n  link3Label,\n  link3Url,\n  link3Icon,\n  link3Color,\n  link4Enabled = true,\n  link4Label,\n  link4Url,\n  link4Icon,\n  link4Color,\n  link5Enabled = false,\n  link5Label,\n  link5Url,\n  link5Icon,\n  link5Color,\n  link6Enabled = false,\n  link6Label,\n  link6Url,\n  link6Icon,\n  link6Color,\n  link7Enabled = false,\n  link7Label,\n  link7Url,\n  link7Icon,\n  link7Color,\n  link8Enabled = false,\n  link8Label,\n  link8Url,\n  link8Icon,\n  link8Color,\n\n  links,\n\n  layout = \"cards\",\n  iconRadius = \"lg\",\n  showChevron = true,\n  openInNewTab = false,\n\n  background = { type: \"solid\", color: \"background\" },\n  textColor = \"foreground\",\n  padding = 4,\n  borderRadius = \"md\",\n  borderWidth = \"none\",\n  borderColor = \"muted\",\n\n  className,\n  ...props\n}: QuickLinksWidgetProps): React.JSX.Element {\n  const backgroundColor = background.color || \"background\";\n  const backgroundImage =\n    (background.resource?.image_url || background.resource?.imageUrl) &&\n    background.type === \"image\"\n      ? `url(${background.resource.image_url || background.resource.imageUrl})`\n      : \"none\";\n\n  // Pass props through verbatim — schema `defaultValue`s pre-populate the\n  // builder, so the component shouldn't second-guess with hardcoded\n  // fallbacks (which would mask a user's explicit blank label).\n  const slots: SlotProps[] = [\n    {\n      enabled: true,\n      label: link1Label,\n      url: link1Url,\n      icon: link1Icon,\n      color: link1Color,\n    },\n    {\n      enabled: link2Enabled,\n      label: link2Label,\n      url: link2Url,\n      icon: link2Icon,\n      color: link2Color,\n    },\n    {\n      enabled: link3Enabled,\n      label: link3Label,\n      url: link3Url,\n      icon: link3Icon,\n      color: link3Color,\n    },\n    {\n      enabled: link4Enabled,\n      label: link4Label,\n      url: link4Url,\n      icon: link4Icon,\n      color: link4Color,\n    },\n    {\n      enabled: link5Enabled,\n      label: link5Label,\n      url: link5Url,\n      icon: link5Icon,\n      color: link5Color,\n    },\n    {\n      enabled: link6Enabled,\n      label: link6Label,\n      url: link6Url,\n      icon: link6Icon,\n      color: link6Color,\n    },\n    {\n      enabled: link7Enabled,\n      label: link7Label,\n      url: link7Url,\n      icon: link7Icon,\n      color: link7Color,\n    },\n    {\n      enabled: link8Enabled,\n      label: link8Label,\n      url: link8Url,\n      icon: link8Icon,\n      color: link8Color,\n    },\n  ];\n\n  const legacyLinks: ParsedLink[] = links ? parseLegacyLinks(links) : [];\n  const parsed: ParsedLink[] = resolveLinks(slots, legacyLinks);\n  const urlVariables = usePortalUrlVariables();\n\n  return (\n    <div\n      className={`@container overflow-hidden rounded-${borderRadius} bg-${backgroundColor} ${borderWidthClasses[borderWidth]} ${borderWidth !== \"none\" ? borderColorClasses[borderColor] : \"\"} ${className ?? \"\"}`}\n      style={{ backgroundImage }}\n      {...props}\n    >\n      <div className={`p-${padding} flex flex-col gap-3`}>\n        {titleEnabled && title && (\n          <h2\n            className={`text-${titleFontSize} leading-tight font-bold tracking-[-0.01em] text-${titleColor}`}\n          >\n            {title}\n          </h2>\n        )}\n\n        {parsed.length === 0 ? (\n          <p className={`text-[13px] text-${textColor}/55`}>\n            No links configured.\n          </p>\n        ) : layout === \"cards\" ? (\n          <div className=\"flex flex-col gap-2\">\n            {parsed.map((link, idx) => (\n              <LinkRow\n                key={link.id}\n                link={link}\n                textColor={textColor}\n                borderRadius={borderRadius}\n                openInNewTab={openInNewTab}\n                showChevron={showChevron}\n                iconRadius={iconRadius}\n                layout=\"cards\"\n                isLast={idx === parsed.length - 1}\n                urlVariables={urlVariables}\n              />\n            ))}\n          </div>\n        ) : (\n          <div\n            className={`flex flex-col overflow-hidden rounded-${borderRadius} bg-${textColor}/5`}\n          >\n            {parsed.map((link, idx) => (\n              <LinkRow\n                key={link.id}\n                link={link}\n                textColor={textColor}\n                borderRadius={borderRadius}\n                openInNewTab={openInNewTab}\n                showChevron={showChevron}\n                iconRadius={iconRadius}\n                layout=\"list\"\n                isLast={idx === parsed.length - 1}\n                urlVariables={urlVariables}\n              />\n            ))}\n          </div>\n        )}\n      </div>\n    </div>\n  );\n}\n\n// ---------- schema helpers ----------\n\n// Quick-insert chips for the URL field property panel. Aligns with the\n// mobile builder's `TEMPLATE_TAGS` token set (same three tokens) — fallback\n// values differ on purpose: portal uses a concrete `/signup` path so the\n// inserted template renders cleanly, whereas mobile uses the literal\n// `fallback_path` placeholder for the admin to edit. The Portal Tenant BFF\n// resolves these server-side via `UrlResolver`; in admin builder preview,\n// `substituteUrlVariables` against `PREVIEW_URL_VARIABLES` renders sample\n// values so admins see what users will see.\nconst URL_TOKEN_SUGGESTIONS = [\n  { label: \"username\", value: \"username || /signup\" },\n  { label: \"replicated_url\", value: \"replicated_url\" },\n  { label: \"external_id\", value: \"external_id || /signup\" },\n] as const;\n\ntype SlotDefaults = {\n  label: string;\n  icon: string;\n  color: ColorOptions;\n};\n\nconst SLOT_DEFAULTS: Record<number, SlotDefaults | undefined> = {\n  1: { label: \"Profile\", icon: \"User\", color: \"primary\" },\n  2: { label: \"Social\", icon: \"Share2\", color: \"accent\" },\n  3: { label: \"Product Links\", icon: \"ShoppingBag\", color: \"secondary\" },\n  4: { label: \"Video\", icon: \"Video\", color: \"destructive\" },\n};\n\nfunction buildLinkSlotFields(slot: number): PropertyField[] {\n  const group = `Link ${slot}`;\n  const isFirst = slot === 1;\n  const defaults = SLOT_DEFAULTS[slot];\n  // Enable slots 2-4 by default to match the previous widget behavior.\n  const enabledDefault = slot >= 2 && slot <= 4;\n  const requires = isFirst ? undefined : `link${slot}Enabled`;\n\n  const fields: PropertyField[] = [];\n\n  if (!isFirst) {\n    fields.push({\n      key: `link${slot}Enabled`,\n      label: `Show Link ${slot}`,\n      type: \"boolean\",\n      description: `Display link slot ${slot}`,\n      defaultValue: enabledDefault,\n      group,\n    });\n  }\n\n  fields.push({\n    key: `link${slot}Label`,\n    label: \"Label\",\n    type: \"text\",\n    description: \"Text shown on the link\",\n    defaultValue: defaults?.label ?? \"\",\n    group,\n    ...(requires ? { requiresKeyToBeTrue: requires } : {}),\n  });\n\n  fields.push({\n    key: `link${slot}Url`,\n    label: \"URL\",\n    type: \"text\",\n    description:\n      \"Where the link points (use '#' for a placeholder). Click a chip below to insert a user variable (e.g. `{{username}}`, `{{replicated_url}}`, `{{external_id}}`); append `|| /fallback` to set a fallback path.\",\n    defaultValue: \"#\",\n    tokenSuggestions: URL_TOKEN_SUGGESTIONS,\n    group,\n    ...(requires ? { requiresKeyToBeTrue: requires } : {}),\n  });\n\n  fields.push({\n    key: `link${slot}Icon`,\n    label: \"Icon\",\n    type: \"select\",\n    description: \"Icon displayed in the colored badge\",\n    options: ICON_OPTIONS,\n    defaultValue: defaults?.icon ?? \"Link\",\n    group,\n    ...(requires ? { requiresKeyToBeTrue: requires } : {}),\n  });\n\n  fields.push({\n    key: `link${slot}Color`,\n    label: \"Badge Color\",\n    type: \"colorSelect\",\n    description: \"Color of the icon badge\",\n    defaultValue:\n      defaults?.color ??\n      FALLBACK_COLOR_CYCLE[(slot - 1) % FALLBACK_COLOR_CYCLE.length] ??\n      \"primary\",\n    group,\n    ...(requires ? { requiresKeyToBeTrue: requires } : {}),\n  });\n\n  return fields;\n}\n\nconst linkSlotFields: PropertyField[] = Array.from(\n  { length: MAX_LINKS },\n  (_, i) => buildLinkSlotFields(i + 1),\n).flat();\n\n// ---------- schema ----------\n\nexport const quickLinksWidgetPropertySchema: WidgetPropertySchema = {\n  widgetType: \"QuickLinksWidget\",\n  displayName: \"Quick Links\",\n  fields: [\n    // Title group\n    {\n      key: \"titleEnabled\",\n      label: \"Show Title\",\n      type: \"boolean\",\n      description: \"Small heading shown above the link list\",\n      defaultValue: true,\n      group: \"Title\",\n    },\n    {\n      key: \"title\",\n      label: \"Title\",\n      type: \"text\",\n      description: \"Heading shown above the links\",\n      defaultValue: \"Quick Links\",\n      group: \"Title\",\n      requiresKeyToBeTrue: \"titleEnabled\",\n    },\n    getFontSizeField({\n      key: \"titleFontSize\",\n      label: \"Title Font Size\",\n      description: \"Font size for the widget title\",\n      defaultValue: \"md\",\n      group: \"Title\",\n      requiresKeyToBeTrue: \"titleEnabled\",\n    }),\n    getColorField({\n      key: \"titleColor\",\n      label: \"Title Color\",\n      description: \"Color for the widget title\",\n      defaultValue: \"foreground\",\n      group: \"Title\",\n      requiresKeyToBeTrue: \"titleEnabled\",\n    }),\n\n    // Links — 8 structured slots\n    ...linkSlotFields,\n\n    // Layout group\n    {\n      key: \"layout\",\n      label: \"Layout\",\n      type: \"buttonGroup\",\n      description: \"Cards: separated tiles. List: grouped rows with dividers.\",\n      defaultValue: \"cards\",\n      options: [\n        { label: \"Cards\", value: \"cards\" },\n        { label: \"List\", value: \"list\" },\n      ],\n      group: \"Layout\",\n    },\n    getBorderRadiusField({\n      key: \"iconRadius\",\n      label: \"Icon Corner Radius\",\n      description: \"Corner radius for the colored icon badge\",\n      defaultValue: \"lg\",\n      group: \"Layout\",\n    }),\n    {\n      key: \"showChevron\",\n      label: \"Show Chevron\",\n      type: \"boolean\",\n      description: \"Right-aligned chevron indicating each row is a link\",\n      defaultValue: true,\n      group: \"Layout\",\n    },\n    {\n      key: \"openInNewTab\",\n      label: \"Open in New Tab\",\n      type: \"boolean\",\n      description: \"Open links in a new tab instead of the same window\",\n      defaultValue: false,\n      group: \"Layout\",\n    },\n\n    // Design group\n    {\n      type: \"background\",\n      key: \"background\",\n      label: \"Background\",\n      description: \"Background for the widget container\",\n      defaultValue: { type: \"solid\", color: \"background\" },\n      group: \"Design\",\n    },\n    getColorField({\n      key: \"textColor\",\n      label: \"Text Color\",\n      description:\n        \"Label + chevron color, and the base for row backgrounds (via opacity)\",\n      defaultValue: \"foreground\",\n      group: \"Design\",\n    }),\n    {\n      key: \"separator\",\n      type: \"separator\",\n      label: \"Separator\",\n      group: \"Design\",\n    },\n    getPaddingField({\n      key: \"padding\",\n      label: \"Padding\",\n      description: \"Padding around the widget\",\n      defaultValue: 4,\n      group: \"Design\",\n    }),\n    getBorderRadiusField({\n      key: \"borderRadius\",\n      label: \"Border Radius\",\n      description:\n        \"Widget border radius (also drives row corners in cards layout)\",\n      defaultValue: \"md\",\n      group: \"Design\",\n    }),\n    getBorderWidthField({\n      key: \"borderWidth\",\n      label: \"Border Width\",\n      description: \"Widget border width\",\n      defaultValue: \"none\",\n      group: \"Design\",\n    }),\n    getBorderColorField({\n      key: \"borderColor\",\n      label: \"Border Color\",\n      description: \"Widget border color\",\n      defaultValue: \"muted\",\n      group: \"Design\",\n    }),\n  ],\n} as const satisfies WidgetPropertySchema;\n"],"mappings":"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OAAO;GACP,qBAAqB;GACtB;EACD,iBAAiB;GACf,KAAK;GACL,OAAO;GACP,aAAa;GACb,cAAc;GACd,OAAO;GACP,qBAAqB;GACtB,CAAC;EACF,cAAc;GACZ,KAAK;GACL,OAAO;GACP,aAAa;GACb,cAAc;GACd,OAAO;GACP,qBAAqB;GACtB,CAAC;EAGF,GAAG;EAGH;GACE,KAAK;GACL,OAAO;GACP,MAAM;GACN,aAAa;GACb,cAAc;GACd,SAAS,CACP;IAAE,OAAO;IAAS,OAAO;IAAS,EAClC;IAAE,OAAO;IAAQ,OAAO;IAAQ,CACjC;GACD,OAAO;GACR;EACD,qBAAqB;GACnB,KAAK;GACL,OAAO;GACP,aAAa;GACb,cAAc;GACd,OAAO;GACR,CAAC;EACF;GACE,KAAK;GACL,OAAO;GACP,MAAM;GACN,aAAa;GACb,cAAc;GACd,OAAO;GACR;EACD;GACE,KAAK;GACL,OAAO;GACP,MAAM;GACN,aAAa;GACb,cAAc;GACd,OAAO;GACR;EAGD;GACE,MAAM;GACN,KAAK;GACL,OAAO;GACP,aAAa;GACb,cAAc;IAAE,MAAM;IAAS,OAAO;IAAc;GACpD,OAAO;GACR;EACD,cAAc;GACZ,KAAK;GACL,OAAO;GACP,aACE;GACF,cAAc;GACd,OAAO;GACR,CAAC;EACF;GACE,KAAK;GACL,MAAM;GACN,OAAO;GACP,OAAO;GACR;EACD,gBAAgB;GACd,KAAK;GACL,OAAO;GACP,aAAa;GACb,cAAc;GACd,OAAO;GACR,CAAC;EACF,qBAAqB;GACnB,KAAK;GACL,OAAO;GACP,aACE;GACF,cAAc;GACd,OAAO;GACR,CAAC;EACF,oBAAoB;GAClB,KAAK;GACL,OAAO;GACP,aAAa;GACb,cAAc;GACd,OAAO;GACR,CAAC;EACF,oBAAoB;GAClB,KAAK;GACL,OAAO;GACP,aAAa;GACb,cAAc;GACd,OAAO;GACR,CAAC;EACH;CACF"}