@flamingo-stack/openframe-frontend-core 0.0.347 → 0.0.349-snapshot.20260701004402
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/{chunk-AQWXNUY5.js → chunk-22YDMHQ6.js} +4 -4
- package/dist/{chunk-SN55DABG.cjs → chunk-2UFPHLQW.cjs} +62 -62
- package/dist/{chunk-SN55DABG.cjs.map → chunk-2UFPHLQW.cjs.map} +1 -1
- package/dist/{chunk-RVQCUROB.cjs → chunk-2VJOIQWC.cjs} +15 -15
- package/dist/{chunk-RVQCUROB.cjs.map → chunk-2VJOIQWC.cjs.map} +1 -1
- package/dist/{chunk-TTVJV7AX.js → chunk-4LIII4VU.js} +4 -4
- package/dist/{chunk-BB75OCWM.cjs → chunk-4TWVKDZ3.cjs} +25 -25
- package/dist/{chunk-BB75OCWM.cjs.map → chunk-4TWVKDZ3.cjs.map} +1 -1
- package/dist/{chunk-SNGWHQOT.cjs → chunk-5DLWZXWX.cjs} +12 -12
- package/dist/{chunk-SNGWHQOT.cjs.map → chunk-5DLWZXWX.cjs.map} +1 -1
- package/dist/{chunk-DNIXKKAO.js → chunk-5EVW3XJK.js} +4 -4
- package/dist/{chunk-FCQTT3EN.js → chunk-5O4OTDH3.js} +3538 -3463
- package/dist/chunk-5O4OTDH3.js.map +1 -0
- package/dist/{chunk-2WTA2TGZ.cjs → chunk-6EBR75I2.cjs} +26 -26
- package/dist/{chunk-2WTA2TGZ.cjs.map → chunk-6EBR75I2.cjs.map} +1 -1
- package/dist/{chunk-T3NJFBR4.cjs → chunk-B6MWUSP2.cjs} +9 -9
- package/dist/{chunk-T3NJFBR4.cjs.map → chunk-B6MWUSP2.cjs.map} +1 -1
- package/dist/{chunk-JDWOPC5W.js → chunk-DGUTSVZ2.js} +2 -2
- package/dist/{chunk-WV2O2KJQ.js → chunk-DIWT3MLF.js} +2 -2
- package/dist/{chunk-DBP5XYHP.js → chunk-EET6FJB4.js} +2 -2
- package/dist/{chunk-BAQUBE3J.cjs → chunk-HEBUZLJ2.cjs} +5 -5
- package/dist/{chunk-BAQUBE3J.cjs.map → chunk-HEBUZLJ2.cjs.map} +1 -1
- package/dist/{chunk-LURIO2V6.cjs → chunk-ICKM2U5N.cjs} +7 -7
- package/dist/{chunk-LURIO2V6.cjs.map → chunk-ICKM2U5N.cjs.map} +1 -1
- package/dist/{chunk-EQQS4C66.js → chunk-K5R7QYZJ.js} +2 -2
- package/dist/{chunk-USF72I36.js → chunk-MIYA5JM5.js} +2 -2
- package/dist/{chunk-LLKZVG42.cjs → chunk-NLOK2CZ2.cjs} +31 -31
- package/dist/{chunk-LLKZVG42.cjs.map → chunk-NLOK2CZ2.cjs.map} +1 -1
- package/dist/{chunk-E5GGKGLG.js → chunk-OSILW6QN.js} +4 -4
- package/dist/{chunk-IK76VFPB.cjs → chunk-QM4W5LAZ.cjs} +56 -56
- package/dist/{chunk-IK76VFPB.cjs.map → chunk-QM4W5LAZ.cjs.map} +1 -1
- package/dist/{chunk-Q2MTKMST.js → chunk-SHCBCWUN.js} +2 -2
- package/dist/{chunk-LUZMRZIT.cjs → chunk-T265FMHX.cjs} +37 -37
- package/dist/{chunk-LUZMRZIT.cjs.map → chunk-T265FMHX.cjs.map} +1 -1
- package/dist/{chunk-Q2ACKKFN.cjs → chunk-VLBYQVU6.cjs} +938 -863
- package/dist/chunk-VLBYQVU6.cjs.map +1 -0
- package/dist/{chunk-LYKMMMJM.js → chunk-Y4JKCD7B.js} +2 -2
- package/dist/components/announcement-bar.d.ts.map +1 -1
- package/dist/components/case-studies/index.cjs +8 -8
- package/dist/components/case-studies/index.js +2 -2
- package/dist/components/chat/embeddable-chat.d.ts.map +1 -1
- package/dist/components/chat/guide-welcome.d.ts +6 -0
- package/dist/components/chat/guide-welcome.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +2 -2
- package/dist/components/chat/index.cjs.map +1 -1
- package/dist/components/chat/index.js +7 -7
- package/dist/components/chat/slash-command-suggestions.d.ts +3 -3
- package/dist/components/chat/utils/{onboarding-icons.d.ts → icon-library.d.ts} +25 -15
- package/dist/components/chat/utils/icon-library.d.ts.map +1 -0
- package/dist/components/chat/utils/index.d.ts +1 -1
- package/dist/components/chat/utils/index.d.ts.map +1 -1
- package/dist/components/contact/index.cjs +3 -3
- package/dist/components/contact/index.js +2 -2
- package/dist/components/docs/index.cjs +5 -5
- package/dist/components/docs/index.js +4 -4
- package/dist/components/embeds/index.cjs +3 -3
- package/dist/components/embeds/index.js +2 -2
- package/dist/components/faq/index.cjs +3 -3
- package/dist/components/faq/index.js +2 -2
- package/dist/components/features/index.cjs +2 -2
- package/dist/components/features/index.js +1 -1
- package/dist/components/help-center-pages/index.cjs +20 -20
- package/dist/components/help-center-pages/index.js +11 -11
- package/dist/components/icon-display.d.ts +27 -0
- package/dist/components/icon-display.d.ts.map +1 -0
- package/dist/components/index.cjs +202 -242
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +588 -628
- package/dist/components/index.js.map +1 -1
- package/dist/components/navigation/index.cjs +2 -2
- package/dist/components/navigation/index.js +1 -1
- package/dist/components/navigation/sliding-sidebar.d.ts.map +1 -1
- package/dist/components/onboarding-guides/index.cjs +5 -5
- package/dist/components/onboarding-guides/index.js +4 -4
- package/dist/components/related-content/index.cjs +3 -3
- package/dist/components/related-content/index.js +2 -2
- package/dist/components/tickets/index.cjs +5 -5
- package/dist/components/tickets/index.js +4 -4
- package/dist/components/ui/index.cjs +2 -2
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.js +7 -7
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +7 -7
- package/dist/types/announcement.cjs.map +1 -1
- package/dist/types/announcement.d.ts +17 -22
- package/dist/types/announcement.d.ts.map +1 -1
- package/dist/types/announcement.js.map +1 -1
- package/dist/types/index.cjs.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/supabase.d.ts +6 -9
- package/dist/types/supabase.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/ODS_TOKEN_RULES.md +8 -2
- package/src/components/announcement-bar.tsx +20 -38
- package/src/components/chat/embeddable-chat.tsx +4 -5
- package/src/components/chat/guide-welcome.tsx +15 -0
- package/src/components/chat/slash-command-suggestions.tsx +5 -5
- package/src/components/chat/utils/__tests__/icon-resolver.test.ts +51 -0
- package/src/components/chat/utils/{onboarding-icons.tsx → icon-library.tsx} +68 -22
- package/src/components/chat/utils/index.ts +10 -10
- package/src/components/footer.tsx +1 -1
- package/src/components/icon-display.tsx +67 -0
- package/src/components/index.ts +2 -0
- package/src/components/navigation/sliding-sidebar.tsx +7 -3
- package/src/stories/AnnouncementBar.stories.tsx +9 -11
- package/src/stories/MingoOnboardingCard.stories.tsx +3 -3
- package/src/stories/SlashCommandSuggestions.stories.tsx +1 -1
- package/src/types/announcement.ts +17 -23
- package/src/types/supabase.ts +6 -9
- package/dist/chunk-FCQTT3EN.js.map +0 -1
- package/dist/chunk-Q2ACKKFN.cjs.map +0 -1
- package/dist/components/chat/utils/onboarding-icons.d.ts.map +0 -1
- /package/dist/{chunk-AQWXNUY5.js.map → chunk-22YDMHQ6.js.map} +0 -0
- /package/dist/{chunk-TTVJV7AX.js.map → chunk-4LIII4VU.js.map} +0 -0
- /package/dist/{chunk-DNIXKKAO.js.map → chunk-5EVW3XJK.js.map} +0 -0
- /package/dist/{chunk-JDWOPC5W.js.map → chunk-DGUTSVZ2.js.map} +0 -0
- /package/dist/{chunk-WV2O2KJQ.js.map → chunk-DIWT3MLF.js.map} +0 -0
- /package/dist/{chunk-DBP5XYHP.js.map → chunk-EET6FJB4.js.map} +0 -0
- /package/dist/{chunk-EQQS4C66.js.map → chunk-K5R7QYZJ.js.map} +0 -0
- /package/dist/{chunk-USF72I36.js.map → chunk-MIYA5JM5.js.map} +0 -0
- /package/dist/{chunk-E5GGKGLG.js.map → chunk-OSILW6QN.js.map} +0 -0
- /package/dist/{chunk-Q2MTKMST.js.map → chunk-SHCBCWUN.js.map} +0 -0
- /package/dist/{chunk-LYKMMMJM.js.map → chunk-Y4JKCD7B.js.map} +0 -0
- /package/src/components/chat/utils/{.icon-registry.md → .icon-library.md} +0 -0
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect } from 'react';
|
|
4
|
-
import Image from '../embed-shims/next-image';
|
|
5
4
|
import { X } from 'lucide-react';
|
|
6
5
|
import { Button } from './ui/button';
|
|
7
|
-
import {
|
|
6
|
+
import { EntityIcon } from './icon-display';
|
|
8
7
|
import {
|
|
9
8
|
setStoredAnnouncement,
|
|
10
9
|
getStoredAnnouncement,
|
|
@@ -14,19 +13,6 @@ import { Announcement } from '../types/announcement';
|
|
|
14
13
|
import { getAppType } from '../utils/app-config';
|
|
15
14
|
import { useEndpointsRuntime } from '../contexts/endpoints-runtime-context';
|
|
16
15
|
|
|
17
|
-
// Helper that defers to renderSvgIcon so we don't need local icon imports
|
|
18
|
-
const getSvgIcon = (
|
|
19
|
-
name: string,
|
|
20
|
-
size: 'main' | 'cta' = 'main',
|
|
21
|
-
extra: Record<string, any> = {}
|
|
22
|
-
) => {
|
|
23
|
-
const cls =
|
|
24
|
-
size === 'cta'
|
|
25
|
-
? 'relative shrink-0 w-3 h-3 md:w-4 md:h-4'
|
|
26
|
-
: 'relative shrink-0 w-6 h-6 md:w-8 md:h-8';
|
|
27
|
-
return renderSvgIcon(name, { className: cls, ...extra });
|
|
28
|
-
};
|
|
29
|
-
|
|
30
16
|
export function AnnouncementBar() {
|
|
31
17
|
const [announcement, setAnnouncement] = useState<Announcement | null>(null);
|
|
32
18
|
const [isVisible, setIsVisible] = useState<boolean>(false);
|
|
@@ -138,24 +124,18 @@ export function AnnouncementBar() {
|
|
|
138
124
|
|
|
139
125
|
const renderIcon = () => {
|
|
140
126
|
if (!announcement) return null;
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return getSvgIcon(
|
|
156
|
-
announcement.icon_svg_name || 'openframe-logo',
|
|
157
|
-
'main',
|
|
158
|
-
announcement.icon_svg_props ?? {}
|
|
127
|
+
// ONE unified display path (shared with the chat): uploaded image URL wins,
|
|
128
|
+
// else a library glyph by name (+ props), via <EntityIcon>.
|
|
129
|
+
return (
|
|
130
|
+
<EntityIcon
|
|
131
|
+
icon={{
|
|
132
|
+
name: announcement.icon_name || 'openframe-logo',
|
|
133
|
+
url: announcement.icon_url,
|
|
134
|
+
props: announcement.icon_props,
|
|
135
|
+
}}
|
|
136
|
+
size={32}
|
|
137
|
+
className="relative shrink-0 w-6 h-6 md:w-8 md:h-8"
|
|
138
|
+
/>
|
|
159
139
|
);
|
|
160
140
|
};
|
|
161
141
|
|
|
@@ -201,11 +181,13 @@ export function AnnouncementBar() {
|
|
|
201
181
|
variant="outline"
|
|
202
182
|
size="small-legacy"
|
|
203
183
|
leftIcon={
|
|
204
|
-
announcement.cta_show_icon && announcement.
|
|
205
|
-
?
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
184
|
+
announcement.cta_show_icon && announcement.cta_icon_name
|
|
185
|
+
? (
|
|
186
|
+
<EntityIcon
|
|
187
|
+
icon={{ name: announcement.cta_icon_name, props: announcement.cta_icon_props }}
|
|
188
|
+
size={16}
|
|
189
|
+
className="w-3 h-3 md:w-4 md:h-4"
|
|
190
|
+
/>
|
|
209
191
|
)
|
|
210
192
|
: undefined
|
|
211
193
|
}
|
|
@@ -99,8 +99,7 @@ import { resolveHrefForRuntime } from './utils/chat-nav-resolution'
|
|
|
99
99
|
import { ChatPanelContext, type ChatPanelHandle } from './chat-panel-context'
|
|
100
100
|
import { resolveSourceRowCTA, sourceRowCtxFromRuntime } from './utils/source-row-cta'
|
|
101
101
|
import { chatChipClass } from './utils/chip-styles'
|
|
102
|
-
import {
|
|
103
|
-
import { resolveOnboardingIcon } from './utils/onboarding-icons'
|
|
102
|
+
import { resolveIcon } from './utils/icon-library'
|
|
104
103
|
import { getSourceIconName } from './utils/source-icons'
|
|
105
104
|
import { formatSingularLookupInvocation } from './utils/slash-dispatch-utils'
|
|
106
105
|
|
|
@@ -442,7 +441,7 @@ function dispatchSlashCommandAction(
|
|
|
442
441
|
*/
|
|
443
442
|
function resolveChipIcon(src: ChatSource): React.ReactNode {
|
|
444
443
|
const iconName = getSourceIconName(src.sourceRepo)
|
|
445
|
-
const Icon = iconName ?
|
|
444
|
+
const Icon = iconName ? resolveIcon(iconName, { variant: 'brand' }) : FileText
|
|
446
445
|
return <Icon className="h-3.5 w-3.5" />
|
|
447
446
|
}
|
|
448
447
|
|
|
@@ -874,7 +873,7 @@ function EmbeddableChatInner({
|
|
|
874
873
|
resolveSourceIcon: (sourceId: string) => {
|
|
875
874
|
const iconName = getSourceIconName(sourceId)
|
|
876
875
|
if (!iconName) return undefined
|
|
877
|
-
return { Icon:
|
|
876
|
+
return { Icon: resolveIcon(iconName, { variant: 'brand' }), label: sourceId }
|
|
878
877
|
},
|
|
879
878
|
onAction: (cmd: SlashCommandSummary, actionId: SlashCommandActionId) => {
|
|
880
879
|
dispatchSlashCommandAction(actionId, cmd.id, chatInputRef)
|
|
@@ -1837,7 +1836,7 @@ function EmbeddableChatInner({
|
|
|
1837
1836
|
/>
|
|
1838
1837
|
))}
|
|
1839
1838
|
{chipCommands.map((cmd) => {
|
|
1840
|
-
const Icon =
|
|
1839
|
+
const Icon = resolveIcon(cmd.iconName)
|
|
1841
1840
|
const cmdId = cmd.id
|
|
1842
1841
|
const label = cmd.label ?? `/${cmdId}`
|
|
1843
1842
|
const cardActions = cmd.actions.map((action) => ({
|
|
@@ -5,6 +5,7 @@ import { cn } from '../../utils/cn'
|
|
|
5
5
|
import { MingoIcon } from '../icons'
|
|
6
6
|
import { Skeleton } from '../ui/skeleton'
|
|
7
7
|
import { ChatQuickActionRow } from './chat-quick-action-row'
|
|
8
|
+
import { EntityIcon } from '../icon-display'
|
|
8
9
|
|
|
9
10
|
// =============================================================================
|
|
10
11
|
// Types
|
|
@@ -18,6 +19,19 @@ export interface GuideQuickAction {
|
|
|
18
19
|
label: string
|
|
19
20
|
/** Prompt text seeded into the composer on click. Defaults to `label`. */
|
|
20
21
|
prompt?: string
|
|
22
|
+
/** Optional library-glyph name (resolved via the onboarding-icon registry). */
|
|
23
|
+
iconName?: string | null
|
|
24
|
+
/** Optional uploaded image URL (wins over `iconName`). */
|
|
25
|
+
iconUrl?: string | null
|
|
26
|
+
/** Optional props spread onto the resolved glyph. */
|
|
27
|
+
iconProps?: Record<string, unknown> | null
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** A chip's leading icon via the unified <EntityIcon> (shared with the
|
|
31
|
+
* announcement bar). Undefined when the chip has no icon. */
|
|
32
|
+
function renderQuickActionIcon(a: GuideQuickAction): React.ReactNode {
|
|
33
|
+
if (!a.iconName && !a.iconUrl) return undefined
|
|
34
|
+
return <EntityIcon icon={{ name: a.iconName, url: a.iconUrl, props: a.iconProps }} size={16} />
|
|
21
35
|
}
|
|
22
36
|
|
|
23
37
|
export interface GuideWelcomeProps {
|
|
@@ -113,6 +127,7 @@ export function GuideWelcome({
|
|
|
113
127
|
quickActions.map((action) => ({
|
|
114
128
|
id: action.id,
|
|
115
129
|
label: action.label,
|
|
130
|
+
icon: renderQuickActionIcon(action),
|
|
116
131
|
onSelect: () => onQuickAction?.(action),
|
|
117
132
|
onHoverStart: () => onQuickActionHover?.(action),
|
|
118
133
|
onHoverEnd: () => onQuickActionHoverEnd?.(),
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import type { KeyboardEvent } from 'react'
|
|
4
4
|
import { cn } from '../../utils/cn'
|
|
5
5
|
import { MingoOnboardingCard } from './mingo-onboarding-card'
|
|
6
|
-
import {
|
|
6
|
+
import { resolveIcon } from './utils/icon-library'
|
|
7
7
|
import type {
|
|
8
8
|
SlashCommandActionId,
|
|
9
9
|
SlashCommandSourceMeta,
|
|
@@ -24,7 +24,7 @@ interface SlashCommandSuggestionsProps {
|
|
|
24
24
|
onSelect: (cmd: SlashCommandSummary) => void
|
|
25
25
|
/** Legacy `primarySourceId` → icon resolver. Used as a FALLBACK only
|
|
26
26
|
* when `cmd.iconName` is missing. Newer command summaries carry
|
|
27
|
-
* `iconName` directly and route through `
|
|
27
|
+
* `iconName` directly and route through `resolveIcon` so
|
|
28
28
|
* the dropdown row uses the same icon registry as the empty-state
|
|
29
29
|
* onboarding cards. */
|
|
30
30
|
resolveSourceIcon?: (sourceId: string) => SlashCommandSourceMeta | undefined
|
|
@@ -47,11 +47,11 @@ interface SlashCommandSuggestionsProps {
|
|
|
47
47
|
* `border-b` divider into a clean 1-px frame.
|
|
48
48
|
*
|
|
49
49
|
* Icon resolution priority:
|
|
50
|
-
* 1. `cmd.iconName` → `
|
|
50
|
+
* 1. `cmd.iconName` → `resolveIcon` (production path —
|
|
51
51
|
* matches the keys returned by `/api/docs/commands`).
|
|
52
52
|
* 2. `cmd.primarySourceId` → consumer-provided `resolveSourceIcon`
|
|
53
53
|
* (legacy fallback for command feeds without `iconName`).
|
|
54
|
-
* 3. `FileIcon` (default inside `
|
|
54
|
+
* 3. `FileIcon` (default inside `resolveIcon`).
|
|
55
55
|
*
|
|
56
56
|
* Clicking the row body fires `onSelect` (the host typically maps this
|
|
57
57
|
* to Search-mode pre-fill so the user can refine). Clicking an action
|
|
@@ -85,7 +85,7 @@ export function SlashCommandSuggestions({
|
|
|
85
85
|
)}
|
|
86
86
|
>
|
|
87
87
|
{commands.map((cmd, idx) => {
|
|
88
|
-
const Icon =
|
|
88
|
+
const Icon = resolveIcon(cmd.iconName)
|
|
89
89
|
// Legacy resolver runs ONLY when `iconName` is missing — otherwise
|
|
90
90
|
// the v2 registry above is authoritative.
|
|
91
91
|
const legacyMeta =
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { resolveIcon, ICON_OPTIONS } from '../icon-library'
|
|
3
|
+
import { getIconComponent } from '../icon-registry'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Proof that unifying the two resolvers into one variant-aware `resolveIcon`
|
|
7
|
+
* does NOT change any glyph:
|
|
8
|
+
* - brand variant must be REFERENCE-IDENTICAL to the legacy `getIconComponent`
|
|
9
|
+
* (so every social / source-row consumer renders exactly the same icon).
|
|
10
|
+
* - design variant must resolve every curated picker name to a real component
|
|
11
|
+
* (no new FileIcon regressions), and never throw on null/unknown.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
// Names spanning BOTH consumer families, incl. the overlap set flagged in review
|
|
15
|
+
// (github/slack/clickup/hubspot) and the brand-color social names, plus PascalCase
|
|
16
|
+
// DB values that `getIconComponent` normalises.
|
|
17
|
+
const BRAND_NAMES = [
|
|
18
|
+
'github', 'slack', 'clickup', 'hubspot', 'openframe',
|
|
19
|
+
'linkedin', 'facebook', 'youtube', 'twitter', 'instagram', 'discord', 'rocket',
|
|
20
|
+
// PascalCase social_platforms.icon_name values:
|
|
21
|
+
'LinkedInIcon', 'XLogo', 'YouTubeIcon', 'Github',
|
|
22
|
+
// unknown + nullish:
|
|
23
|
+
'definitely-not-an-icon',
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
describe('unified resolveIcon — brand variant preserves getIconComponent exactly', () => {
|
|
27
|
+
for (const name of BRAND_NAMES) {
|
|
28
|
+
it(`brand('${name}') === getIconComponent('${name}')`, () => {
|
|
29
|
+
expect(resolveIcon(name, { variant: 'brand' })).toBe(getIconComponent(name))
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
it('null/undefined brand → same fallback as getIconComponent, no throw', () => {
|
|
34
|
+
expect(resolveIcon(null, { variant: 'brand' })).toBe(getIconComponent(null))
|
|
35
|
+
expect(resolveIcon(undefined, { variant: 'brand' })).toBe(getIconComponent(undefined))
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
describe('unified resolveIcon — design variant (default) resolves every picker name', () => {
|
|
40
|
+
it('every ICON_OPTIONS key resolves to a real component', () => {
|
|
41
|
+
for (const opt of ICON_OPTIONS) {
|
|
42
|
+
const Comp = resolveIcon(opt.key)
|
|
43
|
+
expect(Comp, `design('${opt.key}') should resolve`).toBeDefined()
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('null/unknown design → defined fallback, never throws', () => {
|
|
48
|
+
expect(resolveIcon(null)).toBeDefined()
|
|
49
|
+
expect(resolveIcon('definitely-not-an-icon')).toBeDefined()
|
|
50
|
+
})
|
|
51
|
+
})
|
|
@@ -28,8 +28,9 @@
|
|
|
28
28
|
* misconfigured row still renders.
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
|
-
import type { ComponentType } from 'react'
|
|
31
|
+
import type { ComponentType, CSSProperties } from 'react'
|
|
32
32
|
|
|
33
|
+
import { getIconComponent } from './icon-registry'
|
|
33
34
|
import * as IconsV2 from '../../icons-v2-generated'
|
|
34
35
|
import { ClickupLogoGreyIcon } from '../../icons-v2-generated/brand-logos/clickup-logo-grey-icon'
|
|
35
36
|
import { GithubIcon } from '../../icons-v2-generated/brand-logos/github-icon'
|
|
@@ -52,14 +53,30 @@ import { VideoRecorderIcon } from '../../icons-v2-generated/audio-and-visual/vid
|
|
|
52
53
|
import { SearchIcon } from '../../icons-v2-generated/interface/search-icon'
|
|
53
54
|
import { TicketIcon } from '../../icons-v2-generated/shopping/ticket-icon'
|
|
54
55
|
import { OpenFrameLogo } from '../../icons/openframe-logo'
|
|
56
|
+
import { OpenmspLogo } from '../../icons/openmsp-logo'
|
|
57
|
+
import { FlamingoLogo } from '../../icons/flamingo-logo'
|
|
58
|
+
import { Megaphone, Bell, Info, Star, Package as PackageGlyph } from 'lucide-react'
|
|
55
59
|
|
|
56
60
|
/** Shape used by every icon registered here (matches `icons-v2-generated`). */
|
|
57
|
-
export type
|
|
61
|
+
export type IconComponent = ComponentType<{
|
|
58
62
|
size?: number
|
|
59
63
|
className?: string
|
|
60
64
|
color?: string
|
|
61
65
|
}>
|
|
62
66
|
|
|
67
|
+
/** Wrap a brand logo (className/SVG-sized) so it satisfies the `size`-prop
|
|
68
|
+
* `IconComponent` contract — drives size via `style` (CSS wins over
|
|
69
|
+
* the logo's hardcoded width/height), keeping the logo's own brand fill. */
|
|
70
|
+
function sizedLogo(
|
|
71
|
+
Logo: ComponentType<{ className?: string; style?: CSSProperties }>,
|
|
72
|
+
): IconComponent {
|
|
73
|
+
return function SizedLogo({ size = 16, className }) {
|
|
74
|
+
return <Logo className={className} style={{ width: size, height: size }} />
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const OpenmspLogoIcon = sizedLogo(OpenmspLogo)
|
|
78
|
+
const FlamingoLogoIcon = sizedLogo(FlamingoLogo)
|
|
79
|
+
|
|
63
80
|
/**
|
|
64
81
|
* `OpenFrameLogo` from `components/icons/` predates the v2 set and:
|
|
65
82
|
* 1. hardcodes `width="32" height="32"` AFTER spreading `{...props}`,
|
|
@@ -74,16 +91,20 @@ export type OnboardingIconComponent = ComponentType<{
|
|
|
74
91
|
function LogoOpenframeIcon({
|
|
75
92
|
size = 16,
|
|
76
93
|
className,
|
|
94
|
+
color,
|
|
77
95
|
}: {
|
|
78
96
|
size?: number
|
|
79
97
|
className?: string
|
|
80
98
|
color?: string
|
|
81
99
|
}) {
|
|
100
|
+
// Forward `color` (from `icon_props.color`) like every other resolver-backed
|
|
101
|
+
// glyph; default to `currentColor` so the logo tints to its slot.
|
|
102
|
+
const tint = color ?? 'currentColor'
|
|
82
103
|
return (
|
|
83
104
|
<OpenFrameLogo
|
|
84
105
|
className={className}
|
|
85
|
-
lowerPathColor=
|
|
86
|
-
upperPathColor=
|
|
106
|
+
lowerPathColor={tint}
|
|
107
|
+
upperPathColor={tint}
|
|
87
108
|
style={{ width: size, height: size }}
|
|
88
109
|
/>
|
|
89
110
|
)
|
|
@@ -95,7 +116,7 @@ function LogoOpenframeIcon({
|
|
|
95
116
|
* monochrome v2 glyphs that inherit `currentColor` from the card slot
|
|
96
117
|
* (`text-ods-text-secondary`).
|
|
97
118
|
*/
|
|
98
|
-
export const
|
|
119
|
+
export const ICON_ALIASES: Record<string, IconComponent> = {
|
|
99
120
|
// Production `/api/docs/commands` keys (multi-platform-hub contract)
|
|
100
121
|
clickup: ClickupLogoGreyIcon,
|
|
101
122
|
slack: SlackLogoGreyIcon,
|
|
@@ -124,6 +145,18 @@ export const ONBOARDING_ICONS: Record<string, OnboardingIconComponent> = {
|
|
|
124
145
|
'rocket-01': Rocket02Icon,
|
|
125
146
|
'rocket-02': Rocket02Icon,
|
|
126
147
|
|
|
148
|
+
// Announcement-bar legacy names — folded in so resolveIcon covers them too
|
|
149
|
+
// (was the separate `renderSvgIcon` map; now ONE resolver everywhere).
|
|
150
|
+
'openframe-logo': LogoOpenframeIcon,
|
|
151
|
+
'openmsp-logo': OpenmspLogoIcon,
|
|
152
|
+
flamingo: FlamingoLogoIcon,
|
|
153
|
+
'flamingo-logo': FlamingoLogoIcon,
|
|
154
|
+
megaphone: Megaphone,
|
|
155
|
+
bell: Bell,
|
|
156
|
+
info: Info,
|
|
157
|
+
star: Star,
|
|
158
|
+
package: PackageGlyph,
|
|
159
|
+
|
|
127
160
|
// Generic v2-native keys — useful fall-throughs for slash commands that
|
|
128
161
|
// don't have a brand-specific glyph in the design.
|
|
129
162
|
search: SearchIcon,
|
|
@@ -145,7 +178,7 @@ export const ONBOARDING_ICONS: Record<string, OnboardingIconComponent> = {
|
|
|
145
178
|
*
|
|
146
179
|
* Every generated icon follows one naming convention: a file named
|
|
147
180
|
* `<kebab>-icon.tsx` exports a component `<PascalCase>Icon` with the
|
|
148
|
-
* `{ size, className, color }` signature `
|
|
181
|
+
* `{ size, className, color }` signature `IconComponent`
|
|
149
182
|
* expects. So any backend `iconName` that names a real icon (e.g.
|
|
150
183
|
* `chart-pie`, `bank`, `money-bill-dollar`, `piggy-bank`) resolves
|
|
151
184
|
* here without a hand-maintained entry — the whole library is
|
|
@@ -161,7 +194,7 @@ export const ONBOARDING_ICONS: Record<string, OnboardingIconComponent> = {
|
|
|
161
194
|
*/
|
|
162
195
|
function resolveFromLibrary(
|
|
163
196
|
iconName: string,
|
|
164
|
-
):
|
|
197
|
+
): IconComponent | undefined {
|
|
165
198
|
const tokens = iconName.trim().toLowerCase().replace(/_/g, '-').split('-')
|
|
166
199
|
if (tokens.some((t) => t.length === 0)) return undefined
|
|
167
200
|
const cap = (t: string) => t.charAt(0).toUpperCase() + t.slice(1)
|
|
@@ -169,7 +202,7 @@ function resolveFromLibrary(
|
|
|
169
202
|
`${tokens.map(cap).join('')}Icon`,
|
|
170
203
|
`${tokens.map((t) => (t === 'ai' ? 'AI' : cap(t))).join('')}Icon`,
|
|
171
204
|
]
|
|
172
|
-
const registry = IconsV2 as unknown as Record<string,
|
|
205
|
+
const registry = IconsV2 as unknown as Record<string, IconComponent | undefined>
|
|
173
206
|
for (const name of candidates) {
|
|
174
207
|
const Icon = registry[name]
|
|
175
208
|
if (Icon) return Icon
|
|
@@ -178,25 +211,38 @@ function resolveFromLibrary(
|
|
|
178
211
|
}
|
|
179
212
|
|
|
180
213
|
/**
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
214
|
+
* THE single icon-name → component resolver for the whole app. Two variants —
|
|
215
|
+
* one entry point, no duplicated resolver machinery:
|
|
216
|
+
*
|
|
217
|
+
* • `'design'` (DEFAULT): the icons-v2 design set — chat chips, slash
|
|
218
|
+
* autocomplete, announcement bar, AI-agent icons, the admin picker.
|
|
219
|
+
* Order: curated `ICON_ALIASES` (brand-grey logos, tinted OpenFrame,
|
|
220
|
+
* semantic aliases) → generic `icons-v2-generated` by naming convention →
|
|
221
|
+
* `FileIcon`.
|
|
222
|
+
* • `'brand'`: the brand/social set (lucide + brand-colored logos) via the
|
|
223
|
+
* registry resolver `getIconComponent` (PascalCase-normalised) — social
|
|
224
|
+
* links, source-row CTAs.
|
|
225
|
+
*
|
|
226
|
+
* The two glyph SETS differ on purpose (a grey design 'slack' vs a brand-color
|
|
227
|
+
* social 'slack'); this resolver is the ONE place that owns both. `getIconComponent`
|
|
228
|
+
* / `getDynamicIcon` remain as the brand-variant + sized-brand helpers.
|
|
188
229
|
*/
|
|
189
|
-
export function
|
|
230
|
+
export function resolveIcon(
|
|
190
231
|
iconName: string | null | undefined,
|
|
191
|
-
|
|
232
|
+
opts?: { variant?: 'design' | 'brand' },
|
|
233
|
+
): IconComponent {
|
|
234
|
+
if (opts?.variant === 'brand') {
|
|
235
|
+
// Brand variant — delegate to the registry resolver (same glyph as before).
|
|
236
|
+
return getIconComponent(iconName) as IconComponent
|
|
237
|
+
}
|
|
192
238
|
if (!iconName) return FileIcon
|
|
193
|
-
return
|
|
239
|
+
return ICON_ALIASES[iconName] ?? resolveFromLibrary(iconName) ?? FileIcon
|
|
194
240
|
}
|
|
195
241
|
|
|
196
242
|
/** One selectable icon in the admin slash-command picker. */
|
|
197
|
-
export interface
|
|
243
|
+
export interface IconOption {
|
|
198
244
|
/** Stored `icon_name` value — a kebab-case `icons-v2-generated` key (or a
|
|
199
|
-
* curated alias) that `
|
|
245
|
+
* curated alias) that `resolveIcon` displays in the chat. */
|
|
200
246
|
key: string
|
|
201
247
|
/** Human-friendly label shown in the picker dropdown. */
|
|
202
248
|
label: string
|
|
@@ -207,14 +253,14 @@ export interface OnboardingIconOption {
|
|
|
207
253
|
* (`/admin/chat-config` → Edit slash command).
|
|
208
254
|
*
|
|
209
255
|
* THE PICKER AND THE CHAT SHARE ONE ICON SOURCE: every `key` here is an
|
|
210
|
-
* `icons-v2-generated` name (or curated alias) that `
|
|
256
|
+
* `icons-v2-generated` name (or curated alias) that `resolveIcon`
|
|
211
257
|
* renders — so whatever the admin selects always displays in the chip /
|
|
212
258
|
* autocomplete, with no fallback-glyph drift. (The full library is far larger
|
|
213
259
|
* and resolvable generically; this is the curated, slash-command-relevant
|
|
214
260
|
* subset for a usable dropdown.) Keep keys kebab-case to match the DB
|
|
215
261
|
* `chat_admin_slash_commands.icon_name` convention.
|
|
216
262
|
*/
|
|
217
|
-
export const
|
|
263
|
+
export const ICON_OPTIONS: ReadonlyArray<IconOption> = [
|
|
218
264
|
// Brands
|
|
219
265
|
{ key: 'openframe', label: 'OpenFrame' },
|
|
220
266
|
{ key: 'slack', label: 'Slack' },
|
|
@@ -173,16 +173,16 @@ export {
|
|
|
173
173
|
getDynamicIcon,
|
|
174
174
|
type DynamicIconSize,
|
|
175
175
|
} from './icon-registry'
|
|
176
|
-
//
|
|
177
|
-
//
|
|
178
|
-
//
|
|
179
|
-
export {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
type
|
|
184
|
-
type
|
|
185
|
-
} from './
|
|
176
|
+
// Library icon resolver — `icons-v2-generated`-backed, shared by the chat
|
|
177
|
+
// empty-state chips, the slash-command autocomplete, AND the admin icon picker
|
|
178
|
+
// (`ICON_OPTIONS`), so picked icons always display.
|
|
179
|
+
export {
|
|
180
|
+
resolveIcon,
|
|
181
|
+
ICON_ALIASES,
|
|
182
|
+
ICON_OPTIONS,
|
|
183
|
+
type IconComponent,
|
|
184
|
+
type IconOption,
|
|
185
|
+
} from './icon-library'
|
|
186
186
|
export {
|
|
187
187
|
SOURCE_ICON_NAMES,
|
|
188
188
|
getSourceIconName,
|
|
@@ -79,7 +79,7 @@ function UniversalFooter({ config, renderLink }: { config: FooterConfig; renderL
|
|
|
79
79
|
const linkRenderer = renderLink || defaultRenderLink
|
|
80
80
|
|
|
81
81
|
return (
|
|
82
|
-
<footer className={`w-full flex flex-col justify-center items-center ${config.backgroundColor || 'bg-ods-bg-card'} px-6 py-10 relative gap-6 md:gap-6 min-h-[auto] md:min-h-[248px] z-
|
|
82
|
+
<footer className={`w-full flex flex-col justify-center items-center ${config.backgroundColor || 'bg-ods-bg-card'} px-6 py-10 relative gap-6 md:gap-6 min-h-[auto] md:min-h-[248px] z-[44] border-t border-ods-border`}>
|
|
83
83
|
<div className="w-full grid grid-cols-2 lg:grid-cols-4 gap-6 md:gap-8 items-start">
|
|
84
84
|
|
|
85
85
|
{/* Column 1: Logo and optionally description */}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { AgentMark, type AgentName } from './agent-mark'
|
|
5
|
+
import { resolveIcon } from './chat/utils/icon-library'
|
|
6
|
+
|
|
7
|
+
/** Unified icon value used across the app (announcement bar, chat quick actions,
|
|
8
|
+
* AI-agent identity). Exactly one of `name`/`url` is typically set. */
|
|
9
|
+
export interface EntityIconValue {
|
|
10
|
+
/** Library glyph name resolved via {@link resolveIcon} (icons-v2 + curated aliases). */
|
|
11
|
+
name?: string | null
|
|
12
|
+
/** Uploaded image URL — wins over `name`. */
|
|
13
|
+
url?: string | null
|
|
14
|
+
/** Props spread onto the resolved glyph (e.g. `{ color }`). */
|
|
15
|
+
props?: Record<string, unknown> | null
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const BRAND_MARK_NAMES = new Set<string>(['fae', 'mingo'])
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* THE single icon-display path for the whole app. Resolution order:
|
|
22
|
+
* 1. `url` → uploaded image
|
|
23
|
+
* 2. `name` ∈ {fae,mingo} → packaged `AgentMark`
|
|
24
|
+
* 3. `name` → library glyph via `resolveIcon` (+ `props`)
|
|
25
|
+
*
|
|
26
|
+
* Replaces the old per-surface logic (the announcement bar's `renderSvgIcon`
|
|
27
|
+
* map and the chat's ad-hoc `resolveIcon` calls) so every surface renders an
|
|
28
|
+
* icon identically.
|
|
29
|
+
*/
|
|
30
|
+
export function EntityIcon({
|
|
31
|
+
icon,
|
|
32
|
+
size = 20,
|
|
33
|
+
className,
|
|
34
|
+
}: {
|
|
35
|
+
icon?: EntityIconValue | null
|
|
36
|
+
size?: number
|
|
37
|
+
className?: string
|
|
38
|
+
}): React.ReactElement {
|
|
39
|
+
if (icon?.url) {
|
|
40
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
41
|
+
return (
|
|
42
|
+
<img
|
|
43
|
+
src={icon.url}
|
|
44
|
+
alt=""
|
|
45
|
+
width={size}
|
|
46
|
+
height={size}
|
|
47
|
+
className={className}
|
|
48
|
+
style={{ objectFit: 'contain' }}
|
|
49
|
+
/>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
if (icon?.name && BRAND_MARK_NAMES.has(icon.name)) {
|
|
53
|
+
// Size via `className` when provided (so responsive Tailwind sizing like
|
|
54
|
+
// `w-6 md:w-8` works); fall back to a fixed `size` px box ONLY when no
|
|
55
|
+
// className is given — an inline `style` would otherwise override the class.
|
|
56
|
+
return (
|
|
57
|
+
<span
|
|
58
|
+
className={`inline-flex${className ? ` ${className}` : ''}`}
|
|
59
|
+
style={className ? undefined : { width: size, height: size }}
|
|
60
|
+
>
|
|
61
|
+
<AgentMark agent={icon.name as AgentName} className="w-full h-full" />
|
|
62
|
+
</span>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
const Glyph = resolveIcon(icon?.name ?? undefined)
|
|
66
|
+
return <Glyph size={size} className={className} {...(icon?.props ?? {})} />
|
|
67
|
+
}
|
package/src/components/index.ts
CHANGED
|
@@ -110,3 +110,5 @@ export { AuthorDetailView, type AuthorDetailViewProps } from './authors/author-d
|
|
|
110
110
|
|
|
111
111
|
// Priority UI components that exist in main components directory
|
|
112
112
|
// Note: These are re-exported from ./ui already, no need to duplicate
|
|
113
|
+
|
|
114
|
+
export { EntityIcon, type EntityIconValue } from './icon-display'
|
|
@@ -155,7 +155,11 @@ export function SlidingSidebar({ config }: SlidingSidebarProps) {
|
|
|
155
155
|
|
|
156
156
|
return (
|
|
157
157
|
<>
|
|
158
|
-
{/* Overlay
|
|
158
|
+
{/* Overlay — admin-drawer tier (overlay z-[45] / panel z-[46]): ABOVE the
|
|
159
|
+
footer (lowered to z-[44]) so the open drawer covers it, but BEHIND the
|
|
160
|
+
header (z-[50]) so the header stays visible on top (the panel has a
|
|
161
|
+
header spacer below). Kept under z-50 so it also stays beneath modals/
|
|
162
|
+
dialogs (z-50+). See the z-index hierarchy in ODS_TOKEN_RULES.md. */}
|
|
159
163
|
<AnimatePresence>
|
|
160
164
|
{config.isOpen && (
|
|
161
165
|
<motion.div
|
|
@@ -166,7 +170,7 @@ export function SlidingSidebar({ config }: SlidingSidebarProps) {
|
|
|
166
170
|
duration: 0.3,
|
|
167
171
|
ease: [0.4, 0, 0.2, 1]
|
|
168
172
|
}}
|
|
169
|
-
className="fixed inset-0 bg-black/50 z-[
|
|
173
|
+
className="fixed inset-0 bg-black/50 z-[45]"
|
|
170
174
|
onClick={() => config.onClose()}
|
|
171
175
|
/>
|
|
172
176
|
)}
|
|
@@ -187,7 +191,7 @@ export function SlidingSidebar({ config }: SlidingSidebarProps) {
|
|
|
187
191
|
velocity: config.isOpen ? 5 : -5
|
|
188
192
|
}}
|
|
189
193
|
className={cn(
|
|
190
|
-
"fixed top-0 bottom-0 z-[
|
|
194
|
+
"fixed top-0 bottom-0 z-[46] w-72 bg-ods-card border-ods-border flex flex-col shadow-xl",
|
|
191
195
|
config.position === 'right' ? "right-0 border-l" : "left-0 border-r",
|
|
192
196
|
config.className
|
|
193
197
|
)}
|
|
@@ -40,8 +40,7 @@ const baseAnnouncement: Announcement = {
|
|
|
40
40
|
title: 'New Feature Released',
|
|
41
41
|
description: 'Check out our latest updates and improvements to the platform.',
|
|
42
42
|
background_color: '#FFD951',
|
|
43
|
-
|
|
44
|
-
icon_svg_name: 'rocket',
|
|
43
|
+
icon_name: 'rocket',
|
|
45
44
|
platform_id: 'openframe',
|
|
46
45
|
is_active: true,
|
|
47
46
|
created_at: now,
|
|
@@ -95,12 +94,12 @@ export const WithCTA: Story = {
|
|
|
95
94
|
title: 'OpenFrame v2.0 is here!',
|
|
96
95
|
description: 'Explore the new dashboard, improved device management, and more.',
|
|
97
96
|
background_color: '#FFD951',
|
|
98
|
-
|
|
97
|
+
icon_name: 'star',
|
|
99
98
|
cta_enabled: true,
|
|
100
99
|
cta_text: 'Learn More',
|
|
101
100
|
cta_url: 'https://example.com',
|
|
102
101
|
cta_target: '_blank',
|
|
103
|
-
|
|
102
|
+
cta_icon_name: 'rocket',
|
|
104
103
|
cta_show_icon: true,
|
|
105
104
|
cta_button_background_color: '#1A1A1A',
|
|
106
105
|
cta_button_text_color: '#FFFFFF',
|
|
@@ -122,7 +121,7 @@ export const WithCTASameTab: Story = {
|
|
|
122
121
|
title: 'Scheduled Maintenance',
|
|
123
122
|
description: 'We will perform maintenance on Saturday from 2:00 AM to 4:00 AM UTC.',
|
|
124
123
|
background_color: '#FFE082',
|
|
125
|
-
|
|
124
|
+
icon_name: 'info',
|
|
126
125
|
cta_enabled: true,
|
|
127
126
|
cta_text: 'View Status',
|
|
128
127
|
cta_url: '/status',
|
|
@@ -145,7 +144,7 @@ export const BlueBackground: Story = {
|
|
|
145
144
|
title: 'Join our Community',
|
|
146
145
|
description: 'Connect with other MSPs and share best practices.',
|
|
147
146
|
background_color: '#60A5FA',
|
|
148
|
-
|
|
147
|
+
icon_name: 'megaphone',
|
|
149
148
|
})
|
|
150
149
|
return <Story />
|
|
151
150
|
},
|
|
@@ -164,7 +163,7 @@ export const GreenBackground: Story = {
|
|
|
164
163
|
title: 'All Systems Operational',
|
|
165
164
|
description: 'Everything is running smoothly. No issues detected.',
|
|
166
165
|
background_color: '#86EFAC',
|
|
167
|
-
|
|
166
|
+
icon_name: 'bell',
|
|
168
167
|
})
|
|
169
168
|
return <Story />
|
|
170
169
|
},
|
|
@@ -183,7 +182,7 @@ export const WithOpenFrameLogo: Story = {
|
|
|
183
182
|
title: 'Welcome to OpenFrame',
|
|
184
183
|
description: 'Your open-source IT infrastructure management platform.',
|
|
185
184
|
background_color: '#FFD951',
|
|
186
|
-
|
|
185
|
+
icon_name: 'openframe-logo',
|
|
187
186
|
cta_enabled: true,
|
|
188
187
|
cta_text: 'Get Started',
|
|
189
188
|
cta_url: '/getting-started',
|
|
@@ -208,8 +207,7 @@ export const WithPNGIcon: Story = {
|
|
|
208
207
|
title: 'Partner Program Launch',
|
|
209
208
|
description: 'Become an OpenFrame certified partner and grow your business.',
|
|
210
209
|
background_color: '#C4B5FD',
|
|
211
|
-
|
|
212
|
-
icon_png_url: 'https://placehold.co/32x32/1a1a1a/white?text=OF',
|
|
210
|
+
icon_url: 'https://placehold.co/32x32/1a1a1a/white?text=OF',
|
|
213
211
|
})
|
|
214
212
|
return <Story />
|
|
215
213
|
},
|
|
@@ -229,7 +227,7 @@ export const LongContent: Story = {
|
|
|
229
227
|
description:
|
|
230
228
|
'This is an extremely long description that goes into great detail about the announcement. It should be truncated on the desktop view and hidden on mobile view to keep the bar compact and readable.',
|
|
231
229
|
background_color: '#FCA5A5',
|
|
232
|
-
|
|
230
|
+
icon_name: 'info',
|
|
233
231
|
cta_enabled: true,
|
|
234
232
|
cta_text: 'Read Full Announcement',
|
|
235
233
|
cta_url: '/announcements/long',
|