@flamingo-stack/openframe-frontend-core 0.0.256 → 0.0.257
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-JK25REDL.js → chunk-2VZQJIVA.js} +3 -3
- package/dist/{chunk-IG4QRNYZ.cjs → chunk-4MXBBBHB.cjs} +108 -11
- package/dist/chunk-4MXBBBHB.cjs.map +1 -0
- package/dist/{chunk-CMWRZECW.cjs → chunk-AIQ4DGO7.cjs} +34 -34
- package/dist/{chunk-CMWRZECW.cjs.map → chunk-AIQ4DGO7.cjs.map} +1 -1
- package/dist/{chunk-S7IX7YU4.js → chunk-CBIQDPHK.js} +4 -49
- package/dist/chunk-CBIQDPHK.js.map +1 -0
- package/dist/{chunk-TSZBJVYK.cjs → chunk-EB76VWM4.cjs} +100 -49
- package/dist/chunk-EB76VWM4.cjs.map +1 -0
- package/dist/{chunk-6F5CGBVT.cjs → chunk-FBH73RRM.cjs} +1053 -1098
- package/dist/chunk-FBH73RRM.cjs.map +1 -0
- package/dist/{chunk-7RLV3U5C.js → chunk-IJHORDN5.js} +100 -3
- package/dist/chunk-IJHORDN5.js.map +1 -0
- package/dist/{chunk-ISALYOFX.cjs → chunk-LCYDTEEK.cjs} +19 -19
- package/dist/{chunk-ISALYOFX.cjs.map → chunk-LCYDTEEK.cjs.map} +1 -1
- package/dist/{chunk-QXSRQVRO.js → chunk-LDEXTWMK.js} +8 -2
- package/dist/chunk-LDEXTWMK.js.map +1 -0
- package/dist/{chunk-U6UNAC32.cjs → chunk-N5UOJC3V.cjs} +8 -2
- package/dist/chunk-N5UOJC3V.cjs.map +1 -0
- package/dist/{chunk-V5L4D454.js → chunk-NAOTZAKM.js} +54 -3
- package/dist/chunk-NAOTZAKM.js.map +1 -0
- package/dist/{chunk-V4Q5PIBV.cjs → chunk-NNDLCSGR.cjs} +27 -27
- package/dist/{chunk-V4Q5PIBV.cjs.map → chunk-NNDLCSGR.cjs.map} +1 -1
- package/dist/{chunk-HCMVLEC3.js → chunk-RUMSS73U.js} +4 -4
- package/dist/{chunk-46DHPQY6.js → chunk-YMZZ5THD.js} +3 -3
- package/dist/components/chat/index.cjs +4 -4
- package/dist/components/chat/index.js +3 -3
- package/dist/components/contact/index.cjs +5 -5
- package/dist/components/contact/index.js +4 -4
- package/dist/components/features/index.cjs +4 -4
- package/dist/components/features/index.js +3 -3
- package/dist/components/index.cjs +101 -98
- 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 +16 -13
- package/dist/components/index.js.map +1 -1
- package/dist/components/navigation/index.cjs +4 -4
- package/dist/components/navigation/index.js +3 -3
- package/dist/components/onboarding-guides/index.cjs +40 -26
- package/dist/components/onboarding-guides/index.cjs.map +1 -1
- package/dist/components/onboarding-guides/index.js +17 -3
- package/dist/components/onboarding-guides/index.js.map +1 -1
- package/dist/components/onboarding-guides/onboarding-guide-detail-view.d.ts +8 -1
- package/dist/components/onboarding-guides/onboarding-guide-detail-view.d.ts.map +1 -1
- package/dist/components/related-content/index.cjs +5 -5
- package/dist/components/related-content/index.js +4 -4
- package/dist/components/shared/article-author-byline.d.ts +58 -0
- package/dist/components/shared/article-author-byline.d.ts.map +1 -0
- package/dist/components/social-icon-row.d.ts +4 -2
- package/dist/components/social-icon-row.d.ts.map +1 -1
- package/dist/components/tickets/index.cjs +75 -75
- package/dist/components/tickets/index.js +5 -5
- package/dist/components/ui/button/button.d.ts +1 -1
- package/dist/components/ui/button/button.d.ts.map +1 -1
- package/dist/components/ui/file-manager/index.cjs +20 -20
- package/dist/components/ui/file-manager/index.js +1 -1
- package/dist/components/ui/index.cjs +4 -4
- package/dist/components/ui/index.js +3 -3
- package/dist/index.cjs +8 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +7 -7
- package/dist/types/entity-author.d.ts +3 -0
- package/dist/types/entity-author.d.ts.map +1 -1
- package/dist/utils/content-href.d.ts.map +1 -1
- package/dist/utils/index.cjs +6 -1
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.js +6 -1
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/index.ts +5 -0
- package/src/components/onboarding-guides/onboarding-guide-detail-view.tsx +22 -0
- package/src/components/shared/article-author-byline.tsx +187 -0
- package/src/components/social-icon-row.tsx +25 -8
- package/src/components/ui/button/button.tsx +6 -0
- package/src/types/entity-author.ts +3 -0
- package/src/utils/__tests__/content-href.test.ts +3 -0
- package/src/utils/content-href.ts +5 -0
- package/dist/chunk-6F5CGBVT.cjs.map +0 -1
- package/dist/chunk-7RLV3U5C.js.map +0 -1
- package/dist/chunk-IG4QRNYZ.cjs.map +0 -1
- package/dist/chunk-QXSRQVRO.js.map +0 -1
- package/dist/chunk-S7IX7YU4.js.map +0 -1
- package/dist/chunk-TSZBJVYK.cjs.map +0 -1
- package/dist/chunk-U6UNAC32.cjs.map +0 -1
- package/dist/chunk-V5L4D454.js.map +0 -1
- /package/dist/{chunk-JK25REDL.js.map → chunk-2VZQJIVA.js.map} +0 -0
- /package/dist/{chunk-HCMVLEC3.js.map → chunk-RUMSS73U.js.map} +0 -0
- /package/dist/{chunk-46DHPQY6.js.map → chunk-YMZZ5THD.js.map} +0 -0
package/package.json
CHANGED
package/src/components/index.ts
CHANGED
|
@@ -90,5 +90,10 @@ export * from './shared/legal-document'
|
|
|
90
90
|
// Detail Page Skeleton
|
|
91
91
|
export { DetailPageSkeleton, type DetailPageSkeletonProps } from './shared/detail-page-skeleton'
|
|
92
92
|
|
|
93
|
+
// Author byline card (end-of-article author description) — embeddable via
|
|
94
|
+
// embed-shims + optional-runtime avatar proxying; the hub wraps it to inject
|
|
95
|
+
// its platform-aware fallbackBio default.
|
|
96
|
+
export { ArticleAuthorByline, type ArticleAuthorBylineProps } from './shared/article-author-byline'
|
|
97
|
+
|
|
93
98
|
// Priority UI components that exist in main components directory
|
|
94
99
|
// Note: These are re-exported from ./ui already, no need to duplicate
|
|
@@ -32,6 +32,7 @@ import { getCaptionsUrl } from '../features/captions-url'
|
|
|
32
32
|
import { SimpleMarkdownRenderer } from '../ui/simple-markdown-renderer'
|
|
33
33
|
import { EntityTagBadges } from '../features/entity-tag-badges'
|
|
34
34
|
import { LoadError } from '../ui/error-state'
|
|
35
|
+
import { ArticleAuthorByline } from '../shared/article-author-byline'
|
|
35
36
|
import { EntityAuthorCard } from '../chat/entity-cards/entity-author-card'
|
|
36
37
|
import { OnboardingGuideCard } from '../chat/entity-cards/onboarding-guide-card'
|
|
37
38
|
import { useChatRuntime } from '../../contexts/chat-runtime-context'
|
|
@@ -53,6 +54,13 @@ export interface OnboardingGuideDetailViewProps {
|
|
|
53
54
|
/** Link target for the author name in the metadata grid — the host
|
|
54
55
|
* computes it (public author page; absent ⇒ plain text). */
|
|
55
56
|
authorHref?: string
|
|
57
|
+
/** Bio paragraph for the end-of-article author byline. Defaults to the
|
|
58
|
+
* guide payload's `author.about` (hubs that hydrate it can omit this). */
|
|
59
|
+
authorBio?: string | null
|
|
60
|
+
/** Byline fallback paragraph when the bio is empty — the host passes its
|
|
61
|
+
* platform-aware copy (the lib has no config awareness). Absent ⇒ the
|
|
62
|
+
* byline renders nothing below the name when the bio is empty. */
|
|
63
|
+
fallbackBio?: string | null
|
|
56
64
|
/** Optional markdown renderer override. Defaults to lib
|
|
57
65
|
* `<SimpleMarkdownRenderer>`. */
|
|
58
66
|
MarkdownRenderer?: ComponentType<{ content: string }>
|
|
@@ -69,6 +77,8 @@ export interface OnboardingGuideDetailViewProps {
|
|
|
69
77
|
|
|
70
78
|
export function OnboardingGuideDetailView({
|
|
71
79
|
authorHref,
|
|
80
|
+
authorBio,
|
|
81
|
+
fallbackBio,
|
|
72
82
|
initialData,
|
|
73
83
|
slug,
|
|
74
84
|
guideEndpoint,
|
|
@@ -190,6 +200,18 @@ export function OnboardingGuideDetailView({
|
|
|
190
200
|
/>
|
|
191
201
|
)}
|
|
192
202
|
|
|
203
|
+
{/* End-of-article author byline (avatar + linked name + bio) — the
|
|
204
|
+
author DESCRIPTION block every article-shaped detail page renders.
|
|
205
|
+
Hidden when the guide has no author (the byline returns null). */}
|
|
206
|
+
<ArticleAuthorByline
|
|
207
|
+
author={guide.author?.full_name ?? null}
|
|
208
|
+
avatar={guide.author?.avatar_url}
|
|
209
|
+
jobTitle={guide.author?.job_title}
|
|
210
|
+
bio={authorBio ?? guide.author?.about}
|
|
211
|
+
href={authorHref}
|
|
212
|
+
fallbackBio={fallbackBio}
|
|
213
|
+
/>
|
|
214
|
+
|
|
193
215
|
{/* Related — same-section, ordered by step. */}
|
|
194
216
|
{related.length > 0 && (
|
|
195
217
|
<div className="space-y-4 pt-8 border-t border-ods-border">
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared "author byline" card used by article-shaped detail pages
|
|
5
|
+
* (blog post, product release, onboarding guide, investor update).
|
|
6
|
+
*
|
|
7
|
+
* MOVED from the hub (`components/shared/article-author-byline.tsx`) so any
|
|
8
|
+
* consuming app can embed it; the hub keeps a thin wrapper that pre-binds its
|
|
9
|
+
* host-specific injections (the platform-aware `fallbackBio` default derived
|
|
10
|
+
* from `getAppConfig()`).
|
|
11
|
+
*
|
|
12
|
+
* Embed-readiness contract:
|
|
13
|
+
* - `Link` / `Image` render through the embed-shims (plain `<a>` / `<img>`
|
|
14
|
+
* in non-Next hosts; the real Next primitives once the host registers
|
|
15
|
+
* them at app init).
|
|
16
|
+
* - The avatar is proxied through the OPTIONAL ambient `ChatRuntime`
|
|
17
|
+
* (`endpoints.imageProxyUrlPrefix`, same config `useProxiedImageUrl`
|
|
18
|
+
* reads) — when no runtime is mounted the raw URL renders as-is, so the
|
|
19
|
+
* component never throws outside a provider. Hosts can also inject
|
|
20
|
+
* `proxyImageUrl` to bypass the runtime entirely.
|
|
21
|
+
* - `fallbackBio` is a PLAIN prop (no app-config import): the lib has no
|
|
22
|
+
* platform awareness; pass copy or leave it absent to render nothing.
|
|
23
|
+
*
|
|
24
|
+
* Render order: avatar → name + job title + date → bio (when present).
|
|
25
|
+
* Returns null when `author` is empty (no card rendered).
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import React from 'react'
|
|
29
|
+
import { Calendar, User } from 'lucide-react'
|
|
30
|
+
import Image from '../../embed-shims/next-image'
|
|
31
|
+
import Link from '../../embed-shims/next-link'
|
|
32
|
+
import { cn } from '../../utils/cn'
|
|
33
|
+
import { formatBioText } from '../../utils/format'
|
|
34
|
+
import { getProxiedImageUrl } from '../../utils/image-proxy'
|
|
35
|
+
import { useChatRuntime } from '../../contexts/chat-runtime-context'
|
|
36
|
+
|
|
37
|
+
export interface ArticleAuthorBylineProps {
|
|
38
|
+
/** Author display name. Required — block is hidden when null/empty. */
|
|
39
|
+
author: string | null
|
|
40
|
+
/** Avatar URL. Falls back to a placeholder when null. */
|
|
41
|
+
avatar?: string | null
|
|
42
|
+
/** Optional bio paragraph rendered below the name. */
|
|
43
|
+
bio?: string | null
|
|
44
|
+
/** Optional job title rendered immediately under the name. */
|
|
45
|
+
jobTitle?: string | null
|
|
46
|
+
/** Optional published date (ISO string or Date). Rendered next to the name. */
|
|
47
|
+
publishedAt?: string | Date | null
|
|
48
|
+
/** Optional link target for the author name (e.g. the author page). */
|
|
49
|
+
href?: string | null
|
|
50
|
+
/**
|
|
51
|
+
* Fallback paragraph when `bio` is empty. Plain copy — the lib has no
|
|
52
|
+
* platform/config awareness, so hosts that want a branded default
|
|
53
|
+
* ("Contributing author on the {platform} platform") pass it explicitly
|
|
54
|
+
* (the hub wrapper derives it from `getAppConfig()`). Absent/null ⇒
|
|
55
|
+
* nothing renders below the name when `bio` is empty.
|
|
56
|
+
*/
|
|
57
|
+
fallbackBio?: string | null
|
|
58
|
+
/** Avatar size variant. `md` = 56px (default), `lg` = 64px. */
|
|
59
|
+
size?: 'md' | 'lg'
|
|
60
|
+
/**
|
|
61
|
+
* Host-injected avatar-URL mapper. Wins over the ambient-runtime proxy
|
|
62
|
+
* resolution. Use when the host proxies images outside the `ChatRuntime`
|
|
63
|
+
* config (e.g. a bespoke CDN rewrite).
|
|
64
|
+
*/
|
|
65
|
+
proxyImageUrl?: (url: string) => string
|
|
66
|
+
className?: string
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function formatDate(value: string | Date): string {
|
|
70
|
+
const d = typeof value === 'string' ? new Date(value) : value
|
|
71
|
+
if (Number.isNaN(d.getTime())) return ''
|
|
72
|
+
// `timeZone: 'UTC'` keeps the SSR (server = UTC) and client (user's local tz)
|
|
73
|
+
// renders identical — without it a published_at near a midnight boundary
|
|
74
|
+
// formats to a different day on each side, triggering a React #418 hydration
|
|
75
|
+
// text mismatch. Matches the convention in blog-metadata.tsx / investor-update.
|
|
76
|
+
return d.toLocaleDateString('en-US', {
|
|
77
|
+
month: 'long',
|
|
78
|
+
day: 'numeric',
|
|
79
|
+
year: 'numeric',
|
|
80
|
+
timeZone: 'UTC',
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function ArticleAuthorByline({
|
|
85
|
+
author,
|
|
86
|
+
avatar,
|
|
87
|
+
bio,
|
|
88
|
+
jobTitle,
|
|
89
|
+
publishedAt,
|
|
90
|
+
href,
|
|
91
|
+
fallbackBio,
|
|
92
|
+
size = 'md',
|
|
93
|
+
proxyImageUrl,
|
|
94
|
+
className,
|
|
95
|
+
}: ArticleAuthorBylineProps) {
|
|
96
|
+
// Optional runtime — `useChatRuntime` returns null outside a provider, so
|
|
97
|
+
// the byline works in bare embeds (raw avatar URL) and proxies whenever the
|
|
98
|
+
// host mounted a runtime with `imageProxyUrlPrefix` (the hub always does).
|
|
99
|
+
const runtime = useChatRuntime()
|
|
100
|
+
if (!author) return null
|
|
101
|
+
|
|
102
|
+
const proxiedAvatar = avatar
|
|
103
|
+
? proxyImageUrl
|
|
104
|
+
? proxyImageUrl(avatar)
|
|
105
|
+
: (getProxiedImageUrl(avatar, {
|
|
106
|
+
proxyPrefix: runtime?.endpoints.imageProxyUrlPrefix,
|
|
107
|
+
skipDomains: runtime?.endpoints.imageProxySkipDomains,
|
|
108
|
+
directHttps: true,
|
|
109
|
+
}) ?? avatar)
|
|
110
|
+
: ''
|
|
111
|
+
|
|
112
|
+
// Class-driven sizing (md = 56px, lg = 64px). The numeric pair feeds only
|
|
113
|
+
// the Image element's intrinsic width/height attributes (required by the
|
|
114
|
+
// Next image shim) — layout comes from the classes.
|
|
115
|
+
const avatarSizeClass = size === 'lg' ? 'w-16 h-16' : 'w-14 h-14'
|
|
116
|
+
const avatarIconClass = size === 'lg' ? 'w-8 h-8' : 'w-7 h-7'
|
|
117
|
+
const avatarDim = size === 'lg' ? 64 : 56
|
|
118
|
+
const formattedBio = formatBioText(bio ?? null)
|
|
119
|
+
const dateLabel = publishedAt ? formatDate(publishedAt) : ''
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<div
|
|
123
|
+
className={cn(
|
|
124
|
+
'bg-ods-card border border-ods-border rounded-lg p-6',
|
|
125
|
+
'flex flex-col md:flex-row gap-4 items-start',
|
|
126
|
+
className,
|
|
127
|
+
)}
|
|
128
|
+
>
|
|
129
|
+
{/* Avatar */}
|
|
130
|
+
<div className="flex-shrink-0">
|
|
131
|
+
{avatar ? (
|
|
132
|
+
<Image
|
|
133
|
+
src={proxiedAvatar || avatar}
|
|
134
|
+
alt={author}
|
|
135
|
+
width={avatarDim}
|
|
136
|
+
height={avatarDim}
|
|
137
|
+
className={cn('rounded-full border-2 border-ods-border object-cover', avatarSizeClass)}
|
|
138
|
+
/>
|
|
139
|
+
) : (
|
|
140
|
+
<div
|
|
141
|
+
className={cn(
|
|
142
|
+
'rounded-full border-2 border-ods-border bg-ods-bg flex items-center justify-center',
|
|
143
|
+
avatarSizeClass,
|
|
144
|
+
)}
|
|
145
|
+
>
|
|
146
|
+
<User className={cn('text-ods-text-secondary', avatarIconClass)} />
|
|
147
|
+
</div>
|
|
148
|
+
)}
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
{/* Name + meta + bio */}
|
|
152
|
+
<div className="flex-1 min-w-0">
|
|
153
|
+
<div className="flex flex-wrap items-baseline gap-x-3 gap-y-1">
|
|
154
|
+
<h3 className="text-h5 text-ods-text-primary">
|
|
155
|
+
{href ? (
|
|
156
|
+
<Link href={href} className="hover:text-ods-accent transition-colors">
|
|
157
|
+
{author}
|
|
158
|
+
</Link>
|
|
159
|
+
) : (
|
|
160
|
+
author
|
|
161
|
+
)}
|
|
162
|
+
</h3>
|
|
163
|
+
{dateLabel && (
|
|
164
|
+
<span className="inline-flex items-center gap-1 font-body text-body-sm text-ods-text-secondary">
|
|
165
|
+
<Calendar className="h-3 w-3" />
|
|
166
|
+
{dateLabel}
|
|
167
|
+
</span>
|
|
168
|
+
)}
|
|
169
|
+
</div>
|
|
170
|
+
{jobTitle && (
|
|
171
|
+
<p className="font-body text-body-sm text-ods-text-secondary mt-0.5">
|
|
172
|
+
{jobTitle}
|
|
173
|
+
</p>
|
|
174
|
+
)}
|
|
175
|
+
{formattedBio ? (
|
|
176
|
+
<p className="font-body text-body-md text-ods-text-secondary leading-relaxed mt-2">
|
|
177
|
+
{formattedBio}
|
|
178
|
+
</p>
|
|
179
|
+
) : fallbackBio ? (
|
|
180
|
+
<p className="font-body text-body-md text-ods-text-secondary italic mt-2">
|
|
181
|
+
{fallbackBio}
|
|
182
|
+
</p>
|
|
183
|
+
) : null}
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
)
|
|
187
|
+
}
|
|
@@ -19,8 +19,10 @@ interface SocialIconRowProps {
|
|
|
19
19
|
className?: string;
|
|
20
20
|
links?: SocialLink[];
|
|
21
21
|
variant?: "accent" | "outline" | "transparent" | "destructive" | null | undefined;
|
|
22
|
-
/**
|
|
23
|
-
*
|
|
22
|
+
/** Quiet metadata row for page-level identity/share slots: 32px ghost
|
|
23
|
+
* icon buttons (size="icon-sm", 16px glyphs), gap-2, w-fit container,
|
|
24
|
+
* variant defaulting to "transparent" (an explicit `variant` still wins).
|
|
25
|
+
* Default false: 44/48px buttons stretching across the container —
|
|
24
26
|
* the original card-width behavior (TMCG member cards, footers). */
|
|
25
27
|
compact?: boolean;
|
|
26
28
|
}
|
|
@@ -78,17 +80,32 @@ function renderSocialIcon(platform: string) {
|
|
|
78
80
|
}
|
|
79
81
|
}
|
|
80
82
|
|
|
81
|
-
export function SocialIconRow({ className = '', links = defaultLinks, variant
|
|
83
|
+
export function SocialIconRow({ className = '', links = defaultLinks, variant, compact = false }: SocialIconRowProps) {
|
|
84
|
+
// ── Compact design rationale ──────────────────────────────────────────
|
|
85
|
+
// Page-level identity/share rows read as METADATA, not CTAs. The major
|
|
86
|
+
// design systems converge on one recipe for this slot: a ~32px ghost icon
|
|
87
|
+
// button with a ~16px glyph, tight 8px gap, transparent at rest, subtle
|
|
88
|
+
// background state-layer on hover (Carbon "ghost" sm, Primer "invisible"
|
|
89
|
+
// medium, shadcn ghost+icon-sm — all 32px; Material 3 "standard" icon
|
|
90
|
+
// button = state-layer hover). Author headers on content platforms
|
|
91
|
+
// (Medium / dev.to / Substack) use the same quiet treatment. Hence
|
|
92
|
+
// compact: size="icon-sm" + variant defaulting to "transparent" — the
|
|
93
|
+
// hover affordance comes from the bg state layer (hover:bg-ods-bg-hover
|
|
94
|
+
// inside the variant) because the brand icons carry fixed fills, not
|
|
95
|
+
// currentColor. An explicit `variant` prop still wins (e.g. outline
|
|
96
|
+
// chips). Non-compact keeps the legacy outline default + full-width
|
|
97
|
+
// stretch untouched.
|
|
98
|
+
const resolvedVariant = variant !== undefined ? variant : (compact ? 'transparent' : 'outline');
|
|
82
99
|
return (
|
|
83
|
-
<div className={`flex flex-row
|
|
100
|
+
<div className={`flex flex-row ${compact ? 'gap-2 w-fit' : 'gap-3 w-full'} ${className}`}>
|
|
84
101
|
{links.map((link, index) => {
|
|
85
102
|
const ariaLabel = link.label || link.platform;
|
|
86
103
|
return link.onClick ? (
|
|
87
104
|
<Button
|
|
88
105
|
key={index}
|
|
89
106
|
type="button"
|
|
90
|
-
variant={
|
|
91
|
-
size=
|
|
107
|
+
variant={resolvedVariant}
|
|
108
|
+
size={compact ? 'icon-sm' : 'icon'}
|
|
92
109
|
className={compact ? undefined : 'flex-1'}
|
|
93
110
|
aria-label={ariaLabel}
|
|
94
111
|
onClick={link.onClick}
|
|
@@ -101,8 +118,8 @@ export function SocialIconRow({ className = '', links = defaultLinks, variant =
|
|
|
101
118
|
// no asChild/<a> nesting.
|
|
102
119
|
<Button
|
|
103
120
|
key={index}
|
|
104
|
-
variant={
|
|
105
|
-
size=
|
|
121
|
+
variant={resolvedVariant}
|
|
122
|
+
size={compact ? 'icon-sm' : 'icon'}
|
|
106
123
|
className={compact ? undefined : 'flex-1'}
|
|
107
124
|
href={link.href}
|
|
108
125
|
openInNewTab
|
|
@@ -31,6 +31,12 @@ const buttonVariants = cva(
|
|
|
31
31
|
small: "p-[var(--spacing-system-xs)] text-h5 h-6 md:h-8",
|
|
32
32
|
"small-legacy": "py-[var(--spacing-system-xs)] px-[var(--spacing-system-m)] h-10 text-[14px] font-bold", // Temporary alias for "small" to avoid breaking changes in AnnouncementBar's CTA button; will be removed in the future
|
|
33
33
|
icon: "p-[var(--spacing-system-sf)] h-11 w-11 md:h-12 md:w-12 [&_svg]:h-4 [&_svg]:w-4 md:[&_svg]:h-6 md:[&_svg]:w-6",
|
|
34
|
+
// Quiet 32px icon target with a 16px glyph, fixed across breakpoints
|
|
35
|
+
// (Carbon ghost sm / Primer medium / shadcn icon-sm all pin 32px;
|
|
36
|
+
// ≥ the 24px WCAG 2.5.8 target floor). For icon actions that read as
|
|
37
|
+
// metadata rather than CTAs — author-page social rows, share rows.
|
|
38
|
+
// Pair with variant="transparent" for the ghost treatment.
|
|
39
|
+
"icon-sm": "p-[var(--spacing-system-xxs)] h-8 w-8 [&_svg]:h-4 [&_svg]:w-4",
|
|
34
40
|
},
|
|
35
41
|
fullWidth: {
|
|
36
42
|
true: "w-full",
|
|
@@ -9,6 +9,9 @@ export interface EntityAuthor {
|
|
|
9
9
|
full_name: string | null
|
|
10
10
|
avatar_url: string | null
|
|
11
11
|
job_title?: string | null
|
|
12
|
+
/** Author bio paragraph (`profiles.about`) — feeds the end-of-article
|
|
13
|
+
* `<ArticleAuthorByline bio>` on every detail page with an author. */
|
|
14
|
+
about?: string | null
|
|
12
15
|
/** Public author-page slug — present only when the hub's public-author
|
|
13
16
|
* gate passes (`is_real` profiles only). Feeds /authors/[slug] links. */
|
|
14
17
|
slug?: string
|
|
@@ -132,6 +132,9 @@ describe('DEFAULT_CONTENT_SUFFIXES', () => {
|
|
|
132
132
|
webinar: 'webinars',
|
|
133
133
|
podcast: 'podcasts',
|
|
134
134
|
event: 'events',
|
|
135
|
+
// Hub-side home of the segment: AUTHORS_PATH in lib/utils/breadcrumbs.ts
|
|
136
|
+
// (PUBLIC_URL_PATHS.author derives from it).
|
|
137
|
+
author: 'authors',
|
|
135
138
|
})
|
|
136
139
|
})
|
|
137
140
|
})
|
|
@@ -44,6 +44,11 @@ export const DEFAULT_CONTENT_SUFFIXES: Record<string, string> = {
|
|
|
44
44
|
webinar: 'webinars',
|
|
45
45
|
podcast: 'podcasts',
|
|
46
46
|
event: 'events',
|
|
47
|
+
// Author profile pages (`/authors/<slug>`) — identified by the profile
|
|
48
|
+
// slug. Mirrors the hub's `PUBLIC_URL_PATHS.author`, which derives from
|
|
49
|
+
// `AUTHORS_PATH` in the hub's `lib/utils/breadcrumbs.ts` (the hub-side
|
|
50
|
+
// SSOT for the segment).
|
|
51
|
+
author: 'authors',
|
|
47
52
|
}
|
|
48
53
|
|
|
49
54
|
/** Input to the unified content-href seam. ONE object covers both callers:
|