@flamingo-stack/openframe-frontend-core 0.0.348 → 0.0.349
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-3JIUN64F.cjs → chunk-26DV5GSP.cjs} +25 -25
- package/dist/{chunk-3JIUN64F.cjs.map → chunk-26DV5GSP.cjs.map} +1 -1
- package/dist/{chunk-YWEWRAMA.js → chunk-2CKA3CCD.js} +4 -4
- package/dist/{chunk-4UUFNLXQ.cjs → chunk-3DMOLXLC.cjs} +7 -7
- package/dist/{chunk-4UUFNLXQ.cjs.map → chunk-3DMOLXLC.cjs.map} +1 -1
- package/dist/{chunk-RDJNK5AV.js → chunk-63EJ7OVZ.js} +2 -2
- package/dist/{chunk-O5D3YWMW.cjs → chunk-6GQWIOOD.cjs} +936 -861
- package/dist/chunk-6GQWIOOD.cjs.map +1 -0
- package/dist/{chunk-ZS7YHQXO.js → chunk-CVFEPMED.js} +2 -2
- package/dist/{chunk-7CBXPBSB.js → chunk-CWNVR32U.js} +3536 -3461
- package/dist/chunk-CWNVR32U.js.map +1 -0
- package/dist/{chunk-RT6GEODM.js → chunk-ETCT2IBA.js} +2 -2
- package/dist/{chunk-6BW37KWJ.js → chunk-G2PUXREK.js} +4 -4
- package/dist/{chunk-4RMUTJTZ.js → chunk-GPLQR3AR.js} +2 -2
- package/dist/{chunk-WADLOXCY.js → chunk-H4AQ3DSB.js} +4 -4
- package/dist/{chunk-CH2F32CW.cjs → chunk-JENZN2RS.cjs} +56 -56
- package/dist/{chunk-CH2F32CW.cjs.map → chunk-JENZN2RS.cjs.map} +1 -1
- package/dist/{chunk-YFOLFU7F.cjs → chunk-NBZSABAO.cjs} +9 -9
- package/dist/{chunk-YFOLFU7F.cjs.map → chunk-NBZSABAO.cjs.map} +1 -1
- package/dist/{chunk-K5G3JKIU.cjs → chunk-NFUMSUOV.cjs} +5 -5
- package/dist/{chunk-K5G3JKIU.cjs.map → chunk-NFUMSUOV.cjs.map} +1 -1
- package/dist/{chunk-DU5LLHRI.js → chunk-O36624LY.js} +4 -4
- package/dist/{chunk-PQG3L4MK.cjs → chunk-O36BKXM2.cjs} +37 -37
- package/dist/{chunk-PQG3L4MK.cjs.map → chunk-O36BKXM2.cjs.map} +1 -1
- package/dist/{chunk-YTSX3CIK.cjs → chunk-PGBKYPM7.cjs} +12 -12
- package/dist/{chunk-YTSX3CIK.cjs.map → chunk-PGBKYPM7.cjs.map} +1 -1
- package/dist/{chunk-IROBEIGB.cjs → chunk-T4GH47TR.cjs} +31 -31
- package/dist/{chunk-IROBEIGB.cjs.map → chunk-T4GH47TR.cjs.map} +1 -1
- package/dist/{chunk-LBBRR56M.cjs → chunk-TAMJE3SX.cjs} +26 -26
- package/dist/{chunk-LBBRR56M.cjs.map → chunk-TAMJE3SX.cjs.map} +1 -1
- package/dist/{chunk-QZVKSUPR.cjs → chunk-UE4B7KNL.cjs} +62 -62
- package/dist/{chunk-QZVKSUPR.cjs.map → chunk-UE4B7KNL.cjs.map} +1 -1
- package/dist/{chunk-NUO4JXTS.js → chunk-UKEJUXQY.js} +2 -2
- package/dist/{chunk-XBJJIMIG.js → chunk-WY35YHCG.js} +2 -2
- package/dist/{chunk-YOVM7KGM.js → chunk-XKXJB4D4.js} +2 -2
- package/dist/{chunk-MZGJBQ72.cjs → chunk-Y2VXGKNP.cjs} +15 -15
- package/dist/{chunk-MZGJBQ72.cjs.map → chunk-Y2VXGKNP.cjs.map} +1 -1
- 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 +201 -241
- 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/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/package.json +1 -1
- package/src/components/announcement-bar.tsx +19 -40
- 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/icon-display.tsx +67 -0
- package/src/components/index.ts +2 -0
- package/src/stories/MingoOnboardingCard.stories.tsx +3 -3
- package/src/stories/SlashCommandSuggestions.stories.tsx +1 -1
- package/dist/chunk-7CBXPBSB.js.map +0 -1
- package/dist/chunk-O5D3YWMW.cjs.map +0 -1
- package/dist/components/chat/utils/onboarding-icons.d.ts.map +0 -1
- /package/dist/{chunk-YWEWRAMA.js.map → chunk-2CKA3CCD.js.map} +0 -0
- /package/dist/{chunk-RDJNK5AV.js.map → chunk-63EJ7OVZ.js.map} +0 -0
- /package/dist/{chunk-ZS7YHQXO.js.map → chunk-CVFEPMED.js.map} +0 -0
- /package/dist/{chunk-RT6GEODM.js.map → chunk-ETCT2IBA.js.map} +0 -0
- /package/dist/{chunk-6BW37KWJ.js.map → chunk-G2PUXREK.js.map} +0 -0
- /package/dist/{chunk-4RMUTJTZ.js.map → chunk-GPLQR3AR.js.map} +0 -0
- /package/dist/{chunk-WADLOXCY.js.map → chunk-H4AQ3DSB.js.map} +0 -0
- /package/dist/{chunk-DU5LLHRI.js.map → chunk-O36624LY.js.map} +0 -0
- /package/dist/{chunk-NUO4JXTS.js.map → chunk-UKEJUXQY.js.map} +0 -0
- /package/dist/{chunk-XBJJIMIG.js.map → chunk-WY35YHCG.js.map} +0 -0
- /package/dist/{chunk-YOVM7KGM.js.map → chunk-XKXJB4D4.js.map} +0 -0
- /package/src/components/chat/utils/{.icon-registry.md → .icon-library.md} +0 -0
|
@@ -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,
|
|
@@ -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'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
|
2
2
|
import { MingoOnboardingCard } from '../components/chat/mingo-onboarding-card'
|
|
3
|
-
import {
|
|
3
|
+
import { resolveIcon } from '../components/chat/utils/icon-library'
|
|
4
4
|
import { ClickupLogoGreyIcon } from '../components/icons-v2-generated/brand-logos/clickup-logo-grey-icon'
|
|
5
5
|
import { CompassIcon } from '../components/icons-v2-generated/map-and-travel/compass-icon'
|
|
6
6
|
import { Rocket02Icon } from '../components/icons-v2-generated/vehicles-and-delivery/rocket-02-icon'
|
|
@@ -126,7 +126,7 @@ export const WithActions: Story = {
|
|
|
126
126
|
|
|
127
127
|
// =============================================================================
|
|
128
128
|
// Stack of `chipCommands` — mirrors Figma node 7363:205938 (full empty-state).
|
|
129
|
-
// Icons are resolved via the shared `
|
|
129
|
+
// Icons are resolved via the shared `resolveIcon` registry so the
|
|
130
130
|
// story stays in sync with the chat's runtime mapping.
|
|
131
131
|
// =============================================================================
|
|
132
132
|
|
|
@@ -247,7 +247,7 @@ export const ChipCommands: Story = {
|
|
|
247
247
|
<>
|
|
248
248
|
{SAMPLE_CHIP_COMMANDS.map(
|
|
249
249
|
({ iconName, label, slashCommand, description, actions }) => {
|
|
250
|
-
const Icon =
|
|
250
|
+
const Icon = resolveIcon(iconName)
|
|
251
251
|
return (
|
|
252
252
|
<MingoOnboardingCard
|
|
253
253
|
key={slashCommand + label}
|
|
@@ -14,7 +14,7 @@ const meta: Meta<typeof SlashCommandSuggestions> = {
|
|
|
14
14
|
docs: {
|
|
15
15
|
description: {
|
|
16
16
|
component:
|
|
17
|
-
'Slash-command autocomplete dropdown — opens above the chat input when the user types `/`. Each row is a `MingoOnboardingCard` so the dropdown and the empty-state list share one card design. Icons resolve through the shared `
|
|
17
|
+
'Slash-command autocomplete dropdown — opens above the chat input when the user types `/`. Each row is a `MingoOnboardingCard` so the dropdown and the empty-state list share one card design. Icons resolve through the shared `resolveIcon` registry; the production `/api/docs/commands` short keys (`clickup`, `github`, `slack`, `openframe`, `rocket`, `briefcase`, `graduation-cap`, `video`, `headphones`, `calendar`, …) are wired up alongside the legacy Figma-style aliases.',
|
|
18
18
|
},
|
|
19
19
|
},
|
|
20
20
|
},
|