@fluid-app/portal-sdk 0.1.342 → 0.1.344
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{AddressAutocompleteInput-D-kCqSV0.cjs → AddressAutocompleteInput-CY68YXfm.cjs} +3 -2
- package/dist/{AddressAutocompleteInput-D-kCqSV0.cjs.map → AddressAutocompleteInput-CY68YXfm.cjs.map} +1 -1
- package/dist/{AddressAutocompleteInput-B9qlTAJB.mjs → AddressAutocompleteInput-CnqEe4wQ.mjs} +3 -2
- package/dist/{AddressAutocompleteInput-B9qlTAJB.mjs.map → AddressAutocompleteInput-CnqEe4wQ.mjs.map} +1 -1
- package/dist/{FluidProvider-BFU7ermL.cjs → FluidProvider-BAo0N4vR.cjs} +4 -3
- package/dist/{FluidProvider-BFU7ermL.cjs.map → FluidProvider-BAo0N4vR.cjs.map} +1 -1
- package/dist/{FluidProvider-BqJ-t2bW.mjs → FluidProvider-CqwDLIAd.mjs} +4 -3
- package/dist/{FluidProvider-BqJ-t2bW.mjs.map → FluidProvider-CqwDLIAd.mjs.map} +1 -1
- package/dist/{MessagingScreen-BIWRCS3T.mjs → MessagingScreen-BWphOSEd.mjs} +3 -3
- package/dist/{MessagingScreen-D_wVko-i.cjs → MessagingScreen-BnkvuLwD.cjs} +2 -2
- package/dist/{MessagingScreen-D_wVko-i.cjs.map → MessagingScreen-BnkvuLwD.cjs.map} +1 -1
- package/dist/{MessagingScreen-7BBNidBx.cjs → MessagingScreen-OKptFYb8.cjs} +3 -3
- package/dist/{MessagingScreen-ZwSPp6zu.mjs → MessagingScreen-nFIeSiWi.mjs} +2 -2
- package/dist/{MessagingScreen-ZwSPp6zu.mjs.map → MessagingScreen-nFIeSiWi.mjs.map} +1 -1
- package/dist/{OrdersScreen-BkUvISL0.mjs → OrdersScreen-ANJVxGZ0.mjs} +3 -3
- package/dist/{OrdersScreen-t1EwXPZx.cjs → OrdersScreen-Bf5s54Wa.cjs} +3 -3
- package/dist/{OrdersScreen-TGNvFpjj.cjs → OrdersScreen-CJEdwxJl.cjs} +2 -2
- package/dist/{OrdersScreen-TGNvFpjj.cjs.map → OrdersScreen-CJEdwxJl.cjs.map} +1 -1
- package/dist/{OrdersScreen-DgriwQBo.mjs → OrdersScreen-DNqRO4EE.mjs} +2 -2
- package/dist/{OrdersScreen-DgriwQBo.mjs.map → OrdersScreen-DNqRO4EE.mjs.map} +1 -1
- package/dist/{ProfileScreen-D1g_H9b7.mjs → ProfileScreen-7bkBCO-g.mjs} +4 -4
- package/dist/ProfileScreen-7bkBCO-g.mjs.map +1 -0
- package/dist/{ProfileScreen-ChfAPeR8.mjs → ProfileScreen-Byt2EAq9.mjs} +4 -4
- package/dist/{ProfileScreen-D4mDCPKi.cjs → ProfileScreen-CzmvKYzU.cjs} +4 -4
- package/dist/ProfileScreen-CzmvKYzU.cjs.map +1 -0
- package/dist/{ProfileScreen-DgcUeLkp.cjs → ProfileScreen-DIGIotyV.cjs} +4 -4
- package/dist/{QuickLinksWidget-Cik7DoRr.cjs → QuickLinksWidget-BRcUEh_P.cjs} +16 -9
- package/dist/{QuickLinksWidget-Cik7DoRr.cjs.map → QuickLinksWidget-BRcUEh_P.cjs.map} +1 -1
- package/dist/{QuickLinksWidget-Dwk93xrT.mjs → QuickLinksWidget-D8LqZkUS.mjs} +16 -9
- package/dist/{QuickLinksWidget-Dwk93xrT.mjs.map → QuickLinksWidget-D8LqZkUS.mjs.map} +1 -1
- package/dist/{ShopScreen-BjuLPVrk.cjs → ShopScreen-BPIlTezi.cjs} +2 -2
- package/dist/{ShopScreen-BjuLPVrk.cjs.map → ShopScreen-BPIlTezi.cjs.map} +1 -1
- package/dist/{ShopScreen-CtqHSaUW.mjs → ShopScreen-Bio45-Kw.mjs} +3 -3
- package/dist/{ShopScreen-CwGh-q7J.cjs → ShopScreen-D5fnm7kz.cjs} +3 -3
- package/dist/{ShopScreen-DNJ4Geoq.mjs → ShopScreen-DUjyj_xY.mjs} +2 -2
- package/dist/{ShopScreen-DNJ4Geoq.mjs.map → ShopScreen-DUjyj_xY.mjs.map} +1 -1
- package/dist/{SubscriptionsScreen-C4pGAW9l.mjs → SubscriptionsScreen-2mmW2fkf.mjs} +4 -4
- package/dist/{SubscriptionsScreen-ClCmeCmY.cjs → SubscriptionsScreen-Bp8o3--E.cjs} +3 -3
- package/dist/{SubscriptionsScreen-ClCmeCmY.cjs.map → SubscriptionsScreen-Bp8o3--E.cjs.map} +1 -1
- package/dist/{SubscriptionsScreen-CVoJNU7w.cjs → SubscriptionsScreen-CjE4rZKc.cjs} +4 -4
- package/dist/{SubscriptionsScreen-Bw2mBSSR.mjs → SubscriptionsScreen-i1jetQsP.mjs} +3 -3
- package/dist/{SubscriptionsScreen-Bw2mBSSR.mjs.map → SubscriptionsScreen-i1jetQsP.mjs.map} +1 -1
- package/dist/index.cjs +18 -18
- package/dist/index.mjs +18 -18
- package/package.json +20 -20
- package/dist/ProfileScreen-D1g_H9b7.mjs.map +0 -1
- package/dist/ProfileScreen-D4mDCPKi.cjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"QuickLinksWidget-Cik7DoRr.cjs","names":["BookmarkIcon","LinkIcon","ChevronRight","borderWidthClasses","borderColorClasses","getFontSizeField","getColorField","getBorderRadiusField","getPaddingField","getBorderWidthField","getBorderColorField"],"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 {\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\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 return (\n <>\n <a\n href={href}\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. The Portal Tenant BFF\n// resolves these tokens server-side via `UrlResolver`; in admin builder\n// preview, `substituteUrlVariables` against `PREVIEW_URL_VARIABLES` renders\n// sample values so admins see what users will see.\n//\n// `{{external_id}}` is intentionally omitted from the chip set — the\n// underlying `user_company.external_id` field is being deprecated as Connect\n// brings multi-integration support. The resolver still substitutes it for\n// backward compatibility with existing URLs.\nconst URL_TOKEN_SUGGESTIONS = [\n { label: \"username\", value: \"username || /signup\" },\n { label: \"replicated_url\", value: \"replicated_url\" },\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}}`); 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: \"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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"}
|
|
1
|
+
{"version":3,"file":"QuickLinksWidget-BRcUEh_P.cjs","names":["BookmarkIcon","LinkIcon","ChevronRight","borderWidthClasses","borderColorClasses","getFontSizeField","getColorField","getBorderRadiusField","getPaddingField","getBorderWidthField","getBorderColorField"],"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 {\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\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 return (\n <>\n <a\n href={href}\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: \"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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"}
|
|
@@ -389,13 +389,20 @@ function QuickLinksWidget({ titleEnabled = true, title = "Quick Links", titleFon
|
|
|
389
389
|
})
|
|
390
390
|
});
|
|
391
391
|
}
|
|
392
|
-
const URL_TOKEN_SUGGESTIONS = [
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
392
|
+
const URL_TOKEN_SUGGESTIONS = [
|
|
393
|
+
{
|
|
394
|
+
label: "username",
|
|
395
|
+
value: "username || /signup"
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
label: "replicated_url",
|
|
399
|
+
value: "replicated_url"
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
label: "external_id",
|
|
403
|
+
value: "external_id || /signup"
|
|
404
|
+
}
|
|
405
|
+
];
|
|
399
406
|
const SLOT_DEFAULTS = {
|
|
400
407
|
1: {
|
|
401
408
|
label: "Profile",
|
|
@@ -446,7 +453,7 @@ function buildLinkSlotFields(slot) {
|
|
|
446
453
|
key: `link${slot}Url`,
|
|
447
454
|
label: "URL",
|
|
448
455
|
type: "text",
|
|
449
|
-
description: "Where the link points (use '#' for a placeholder). Click a chip below to insert a user variable (e.g. `{{username}}`, `{{replicated_url}}`); append `|| /fallback` to set a fallback path.",
|
|
456
|
+
description: "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.",
|
|
450
457
|
defaultValue: "#",
|
|
451
458
|
tokenSuggestions: URL_TOKEN_SUGGESTIONS,
|
|
452
459
|
group,
|
|
@@ -604,4 +611,4 @@ const quickLinksWidgetPropertySchema = {
|
|
|
604
611
|
//#endregion
|
|
605
612
|
export { QuickLinksWidget_exports as n, quickLinksWidgetPropertySchema as r, QuickLinksWidget as t };
|
|
606
613
|
|
|
607
|
-
//# sourceMappingURL=QuickLinksWidget-
|
|
614
|
+
//# sourceMappingURL=QuickLinksWidget-D8LqZkUS.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"QuickLinksWidget-Dwk93xrT.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 {\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\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 return (\n <>\n <a\n href={href}\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. The Portal Tenant BFF\n// resolves these tokens server-side via `UrlResolver`; in admin builder\n// preview, `substituteUrlVariables` against `PREVIEW_URL_VARIABLES` renders\n// sample values so admins see what users will see.\n//\n// `{{external_id}}` is intentionally omitted from the chip set — the\n// underlying `user_company.external_id` field is being deprecated as Connect\n// brings multi-integration support. The resolver still substitutes it for\n// backward compatibility with existing URLs.\nconst URL_TOKEN_SUGGESTIONS = [\n { label: \"username\", value: \"username || /signup\" },\n { label: \"replicated_url\", value: \"replicated_url\" },\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}}`); 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: \"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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,EACH,UACA,EAAA,CAAA;AAIP,QACE,qBAAA,YAAA,EAAA,UAAA,CACE,oBAAC,OAAD;EAAK,WAAW;YAAiB;EAAc,CAAA,EAC9C,UACA,EAAA,CAAA;;AAwEP,SAAgB,iBAAiB,EAC/B,eAAe,MACf,QAAQ,eACR,gBAAgB,MAChB,aAAa,cAEb,YACA,UACA,WACA,YACA,eAAe,MACf,YACA,UACA,WACA,YACA,eAAe,MACf,YACA,UACA,WACA,YACA,eAAe,MACf,YACA,UACA,WACA,YACA,eAAe,OACf,YACA,UACA,WACA,YACA,eAAe,OACf,YACA,UACA,WACA,YACA,eAAe,OACf,YACA,UACA,WACA,YACA,eAAe,OACf,YACA,UACA,WACA,YAEA,OAEA,SAAS,SACT,aAAa,MACb,cAAc,MACd,eAAe,OAEf,aAAa;CAAE,MAAM;CAAS,OAAO;CAAc,EACnD,YAAY,cACZ,UAAU,GACV,eAAe,MACf,cAAc,QACd,cAAc,SAEd,WACA,GAAG,SACwC;CAC3C,MAAM,kBAAkB,WAAW,SAAS;CAC5C,MAAM,mBACH,WAAW,UAAU,aAAa,WAAW,UAAU,aACxD,WAAW,SAAS,UAChB,OAAO,WAAW,SAAS,aAAa,WAAW,SAAS,SAAS,KACrE;CAiEN,MAAM,SAAuB,aA5DF;EACzB;GACE,SAAS;GACT,OAAO;GACP,KAAK;GACL,MAAM;GACN,OAAO;GACR;EACD;GACE,SAAS;GACT,OAAO;GACP,KAAK;GACL,MAAM;GACN,OAAO;GACR;EACD;GACE,SAAS;GACT,OAAO;GACP,KAAK;GACL,MAAM;GACN,OAAO;GACR;EACD;GACE,SAAS;GACT,OAAO;GACP,KAAK;GACL,MAAM;GACN,OAAO;GACR;EACD;GACE,SAAS;GACT,OAAO;GACP,KAAK;GACL,MAAM;GACN,OAAO;GACR;EACD;GACE,SAAS;GACT,OAAO;GACP,KAAK;GACL,MAAM;GACN,OAAO;GACR;EACD;GACE,SAAS;GACT,OAAO;GACP,KAAK;GACL,MAAM;GACN,OAAO;GACR;EACD;GACE,SAAS;GACT,OAAO;GACP,KAAK;GACL,MAAM;GACN,OAAO;GACR;EA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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;GACd,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"}
|
|
1
|
+
{"version":3,"file":"QuickLinksWidget-D8LqZkUS.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 {\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\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 return (\n <>\n <a\n href={href}\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: \"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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,EACH,UACA,EAAA,CAAA;AAIP,QACE,qBAAA,YAAA,EAAA,UAAA,CACE,oBAAC,OAAD;EAAK,WAAW;YAAiB;EAAc,CAAA,EAC9C,UACA,EAAA,CAAA;;AAwEP,SAAgB,iBAAiB,EAC/B,eAAe,MACf,QAAQ,eACR,gBAAgB,MAChB,aAAa,cAEb,YACA,UACA,WACA,YACA,eAAe,MACf,YACA,UACA,WACA,YACA,eAAe,MACf,YACA,UACA,WACA,YACA,eAAe,MACf,YACA,UACA,WACA,YACA,eAAe,OACf,YACA,UACA,WACA,YACA,eAAe,OACf,YACA,UACA,WACA,YACA,eAAe,OACf,YACA,UACA,WACA,YACA,eAAe,OACf,YACA,UACA,WACA,YAEA,OAEA,SAAS,SACT,aAAa,MACb,cAAc,MACd,eAAe,OAEf,aAAa;CAAE,MAAM;CAAS,OAAO;CAAc,EACnD,YAAY,cACZ,UAAU,GACV,eAAe,MACf,cAAc,QACd,cAAc,SAEd,WACA,GAAG,SACwC;CAC3C,MAAM,kBAAkB,WAAW,SAAS;CAC5C,MAAM,mBACH,WAAW,UAAU,aAAa,WAAW,UAAU,aACxD,WAAW,SAAS,UAChB,OAAO,WAAW,SAAS,aAAa,WAAW,SAAS,SAAS,KACrE;CAiEN,MAAM,SAAuB,aA5DF;EACzB;GACE,SAAS;GACT,OAAO;GACP,KAAK;GACL,MAAM;GACN,OAAO;GACR;EACD;GACE,SAAS;GACT,OAAO;GACP,KAAK;GACL,MAAM;GACN,OAAO;GACR;EACD;GACE,SAAS;GACT,OAAO;GACP,KAAK;GACL,MAAM;GACN,OAAO;GACR;EACD;GACE,SAAS;GACT,OAAO;GACP,KAAK;GACL,MAAM;GACN,OAAO;GACR;EACD;GACE,SAAS;GACT,OAAO;GACP,KAAK;GACL,MAAM;GACN,OAAO;GACR;EACD;GACE,SAAS;GACT,OAAO;GACP,KAAK;GACL,MAAM;GACN,OAAO;GACR;EACD;GACE,SAAS;GACT,OAAO;GACP,KAAK;GACL,MAAM;GACN,OAAO;GACR;EACD;GACE,SAAS;GACT,OAAO;GACP,KAAK;GACL,MAAM;GACN,OAAO;GACR;EA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AAO;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;GACd,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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const require_chunk = require("./chunk-9hOWP6kD.cjs");
|
|
2
|
-
const require_FluidProvider = require("./FluidProvider-
|
|
2
|
+
const require_FluidProvider = require("./FluidProvider-BAo0N4vR.cjs");
|
|
3
3
|
const require_src = require("./src-Cx7UyT_c.cjs");
|
|
4
4
|
const require_SearchSort = require("./SearchSort-CDuQPacI.cjs");
|
|
5
5
|
const require_ShopWidget = require("./ShopWidget-BAi2p8DP.cjs");
|
|
@@ -1008,4 +1008,4 @@ Object.defineProperty(exports, "shopScreenPropertySchema", {
|
|
|
1008
1008
|
}
|
|
1009
1009
|
});
|
|
1010
1010
|
|
|
1011
|
-
//# sourceMappingURL=ShopScreen-
|
|
1011
|
+
//# sourceMappingURL=ShopScreen-BPIlTezi.cjs.map
|