@flamingo-stack/openframe-frontend-core 0.0.230 → 0.0.231

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.
Files changed (76) hide show
  1. package/dist/{chunk-2BMV2LVL.js → chunk-3C4GHSLI.js} +1693 -1678
  2. package/dist/chunk-3C4GHSLI.js.map +1 -0
  3. package/dist/{chunk-FLUZ2QMJ.js → chunk-BAQ6SS32.js} +2 -2
  4. package/dist/{chunk-Z2KOH5S4.cjs → chunk-BVLU6HLN.cjs} +9 -9
  5. package/dist/{chunk-Z2KOH5S4.cjs.map → chunk-BVLU6HLN.cjs.map} +1 -1
  6. package/dist/{chunk-4VXGQIMC.cjs → chunk-DNRO2BXS.cjs} +10 -10
  7. package/dist/{chunk-4VXGQIMC.cjs.map → chunk-DNRO2BXS.cjs.map} +1 -1
  8. package/dist/{chunk-Y4N4XWUK.cjs → chunk-J3F3LZZI.cjs} +23 -8
  9. package/dist/chunk-J3F3LZZI.cjs.map +1 -0
  10. package/dist/{chunk-AAX7PMU7.cjs → chunk-NFZLQOD7.cjs} +25 -25
  11. package/dist/{chunk-AAX7PMU7.cjs.map → chunk-NFZLQOD7.cjs.map} +1 -1
  12. package/dist/{chunk-G5JQMCE7.js → chunk-R46QCZLS.js} +2 -2
  13. package/dist/{chunk-GROUDMXE.cjs → chunk-RL6VQEFT.cjs} +7 -7
  14. package/dist/{chunk-GROUDMXE.cjs.map → chunk-RL6VQEFT.cjs.map} +1 -1
  15. package/dist/{chunk-6ME63EW3.js → chunk-U2OLWICZ.js} +2 -2
  16. package/dist/{chunk-XVJQ7SKB.js → chunk-USACX2O4.js} +2 -2
  17. package/dist/components/chat/index.cjs +2 -2
  18. package/dist/components/chat/index.js +1 -1
  19. package/dist/components/contact/index.cjs +3 -3
  20. package/dist/components/contact/index.js +2 -2
  21. package/dist/components/features/entity-tag-badges.d.ts +17 -0
  22. package/dist/components/features/entity-tag-badges.d.ts.map +1 -0
  23. package/dist/components/features/index.cjs +4 -2
  24. package/dist/components/features/index.cjs.map +1 -1
  25. package/dist/components/features/index.d.ts +1 -0
  26. package/dist/components/features/index.d.ts.map +1 -1
  27. package/dist/components/features/index.js +3 -1
  28. package/dist/components/index.cjs +97 -104
  29. package/dist/components/index.cjs.map +1 -1
  30. package/dist/components/index.js +9 -16
  31. package/dist/components/index.js.map +1 -1
  32. package/dist/components/navigation/index.cjs +2 -2
  33. package/dist/components/navigation/index.js +1 -1
  34. package/dist/components/onboarding-guides/index.cjs +27 -25
  35. package/dist/components/onboarding-guides/index.cjs.map +1 -1
  36. package/dist/components/onboarding-guides/index.js +5 -3
  37. package/dist/components/onboarding-guides/index.js.map +1 -1
  38. package/dist/components/onboarding-guides/onboarding-guide-detail-view.d.ts.map +1 -1
  39. package/dist/components/shared/product-release/release-detail-page.d.ts.map +1 -1
  40. package/dist/components/tickets/index.cjs +64 -64
  41. package/dist/components/tickets/index.js +4 -4
  42. package/dist/components/ui/index.cjs +2 -2
  43. package/dist/components/ui/index.js +1 -1
  44. package/dist/components/unified-filter-logic.d.ts.map +1 -1
  45. package/dist/index.cjs +4 -2
  46. package/dist/index.cjs.map +1 -1
  47. package/dist/index.js +3 -1
  48. package/dist/types/blog.d.ts +13 -1
  49. package/dist/types/blog.d.ts.map +1 -1
  50. package/dist/types/case-study.d.ts +2 -5
  51. package/dist/types/case-study.d.ts.map +1 -1
  52. package/dist/types/index.cjs.map +1 -1
  53. package/dist/types/index.js.map +1 -1
  54. package/dist/types/product-release.d.ts +2 -5
  55. package/dist/types/product-release.d.ts.map +1 -1
  56. package/dist/types/supabase.d.ts +0 -40
  57. package/dist/types/supabase.d.ts.map +1 -1
  58. package/dist/utils/index.cjs.map +1 -1
  59. package/dist/utils/index.js.map +1 -1
  60. package/package.json +1 -1
  61. package/src/components/features/entity-tag-badges.tsx +32 -0
  62. package/src/components/features/index.ts +1 -0
  63. package/src/components/features/tags-selector.tsx +6 -6
  64. package/src/components/onboarding-guides/onboarding-guide-detail-view.tsx +4 -0
  65. package/src/components/shared/product-release/release-detail-page.tsx +4 -14
  66. package/src/components/unified-filter-logic.tsx +4 -3
  67. package/src/types/blog.ts +15 -1
  68. package/src/types/case-study.ts +3 -5
  69. package/src/types/product-release.ts +3 -5
  70. package/src/types/supabase.ts +0 -40
  71. package/dist/chunk-2BMV2LVL.js.map +0 -1
  72. package/dist/chunk-Y4N4XWUK.cjs.map +0 -1
  73. /package/dist/{chunk-FLUZ2QMJ.js.map → chunk-BAQ6SS32.js.map} +0 -0
  74. /package/dist/{chunk-G5JQMCE7.js.map → chunk-R46QCZLS.js.map} +0 -0
  75. /package/dist/{chunk-6ME63EW3.js.map → chunk-U2OLWICZ.js.map} +0 -0
  76. /package/dist/{chunk-XVJQ7SKB.js.map → chunk-USACX2O4.js.map} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flamingo-stack/openframe-frontend-core",
3
- "version": "0.0.230",
3
+ "version": "0.0.231",
4
4
  "description": "Shared design system and components for all Flamingo platforms",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -0,0 +1,32 @@
1
+ import { StatusBadge } from '../ui/status-badge'
2
+ import type { TagAssoc } from '../../types/blog'
3
+
4
+ interface EntityTagBadgesProps {
5
+ /** Flat `<entity>_tags[]` association array (from entity_tags hydration). */
6
+ tags?: TagAssoc[] | null
7
+ /** Extra classes on the wrapper row. */
8
+ className?: string
9
+ }
10
+
11
+ /**
12
+ * The single lib-side tag-badge renderer for detail views (release, onboarding
13
+ * guide, …). Lib-package analogue of the hub's `EntityTagList` — the hub can't
14
+ * be imported here, so this lives in the lib and is reused across lib detail
15
+ * pages instead of each one re-implementing the StatusBadge chip row. Renders
16
+ * nothing when there are no tags.
17
+ */
18
+ export function EntityTagBadges({ tags, className }: EntityTagBadgesProps) {
19
+ if (!tags || tags.length === 0) return null
20
+ return (
21
+ <div className={`flex flex-wrap gap-2 w-full ${className || ''}`}>
22
+ {tags.map((tag) => (
23
+ <StatusBadge
24
+ key={tag.tag_id ?? tag.id}
25
+ text={(tag.name || '').toUpperCase()}
26
+ variant="card"
27
+ className="bg-ods-card border border-ods-border"
28
+ />
29
+ ))}
30
+ </div>
31
+ )
32
+ }
@@ -23,6 +23,7 @@ export * from './changelog-manager'
23
23
  export * from './changelog-sections-manager'
24
24
  export * from './clickup-tasks-manager'
25
25
  export * from './command-box'
26
+ export * from './entity-tag-badges'
26
27
  export * from './error-boundary'
27
28
  export * from './figma-prototype-viewer'
28
29
  export * from './filters-dropdown'
@@ -117,22 +117,22 @@ export function TagsSelector({
117
117
  {getSelectedTags().map((tag) => (
118
118
  <span
119
119
  key={tag.id}
120
- className="inline-flex items-center gap-1 px-2 py-0.5 rounded bg-ods-accent text-ods-bg text-xs font-medium font-['DM_Sans']"
120
+ className="inline-flex items-center gap-1 pl-2.5 pr-1 py-1 rounded-md bg-ods-accent/15 border border-ods-accent/30 text-ods-text-primary text-[13px] leading-none font-medium font-['DM_Sans']"
121
121
  >
122
122
  {tag.name}
123
123
  {!disabled && (
124
- <Button
125
- variant="transparent"
124
+ <button
126
125
  type="button"
127
126
  onClick={(e) => {
128
127
  e.preventDefault();
129
128
  e.stopPropagation();
130
129
  handleTagRemove(tag.id);
131
130
  }}
132
- leftIcon={<X className="w-2.5 h-2.5" />}
133
- className="w-3 h-3 p-0 min-h-0 min-w-0 ml-0.5 hover:opacity-70 text-ods-bg hover:bg-transparent"
131
+ className="inline-flex items-center justify-center w-4 h-4 rounded-full text-ods-text-secondary hover:text-ods-text-primary hover:bg-ods-accent/20 transition-colors"
134
132
  aria-label={`Remove ${tag.name}`}
135
- />
133
+ >
134
+ <X className="w-3 h-3" />
135
+ </button>
136
136
  )}
137
137
  </span>
138
138
  ))}
@@ -26,6 +26,7 @@ import { VideoBitesDisplay } from '../features/video-bites-display'
26
26
  import { useVideoWarmup } from '../features/use-video-warmup'
27
27
  import { getCaptionsUrl } from '../features/captions-url'
28
28
  import { SimpleMarkdownRenderer } from '../ui/simple-markdown-renderer'
29
+ import { EntityTagBadges } from '../features/entity-tag-badges'
29
30
  import { LoadError } from '../ui/error-state'
30
31
  import { EntityAuthorCard } from '../chat/entity-cards/entity-author-card'
31
32
  import { OnboardingGuideCard } from '../chat/entity-cards/onboarding-guide-card'
@@ -128,6 +129,9 @@ export function OnboardingGuideDetailView({
128
129
 
129
130
  <h1 className="text-h1 tracking-[-1.12px] text-ods-text-primary">{guide.title}</h1>
130
131
 
132
+ {/* Tags — flat onboarding_guide_tags[] from entity_tags. */}
133
+ <EntityTagBadges tags={(guide as any).onboarding_guide_tags} />
134
+
131
135
  {/* Metadata grid — Section · Step | Published | Author. */}
132
136
  <EntityAuthorCard
133
137
  author={guide.author}
@@ -7,7 +7,7 @@ import { Card, CardContent } from '../../ui/card';
7
7
  import { ArticleDetailLayout } from '../../layout/article-detail-layout';
8
8
  import { BackButton } from '../../layout/back-button';
9
9
  import { ReleaseChangelogSection } from '../../ui/release-changelog-section';
10
- import { StatusBadge } from '../../ui/status-badge';
10
+ import { EntityTagBadges } from '../../features/entity-tag-badges';
11
11
  import { SquareAvatar } from '../../ui/square-avatar';
12
12
  import { ImageGalleryModal } from '../../ui/image-gallery-modal';
13
13
  import { GitHubIcon } from '../../icons/github-icon';
@@ -17,6 +17,7 @@ import { nameInitials } from '../../../utils/format';
17
17
  import { Video } from '../../features/video';
18
18
  import { DetailPageSkeleton } from '../detail-page-skeleton';
19
19
  import type { ChangelogEntry } from '../../../types/product-release';
20
+ import type { TagAssoc } from '../../../types/blog';
20
21
  import type { VideoTeaser } from '../../../types/video-processing';
21
22
 
22
23
  // Types for injectable components
@@ -202,7 +203,6 @@ export function ReleaseDetailPage({
202
203
  const releaseDate = release.release_date as string;
203
204
  const releaseType = release.release_type as string;
204
205
  const releaseStatus = release.release_status as string;
205
- const blogTags = release.blog_tags as Array<{ id?: string; name?: string; blog_tags?: { id: string; name: string } }> | undefined;
206
206
  const releaseMedia = release.release_media as Array<{ id?: string; media_type: string; media_url: string; title?: string }> | undefined;
207
207
  const author = release.author as { avatar_url?: string; full_name?: string } | undefined;
208
208
  const githubReleases = release.github_releases as Array<{ id: string; github_release_url: string }> | undefined;
@@ -246,18 +246,8 @@ export function ReleaseDetailPage({
246
246
  </div>
247
247
  </div>
248
248
 
249
- {/* Category Tags */}
250
- <div className="flex flex-wrap gap-2 w-full">
251
- {/* Blog Tags */}
252
- {blogTags?.map((tag) => (
253
- <StatusBadge
254
- key={tag.id || tag.blog_tags?.id}
255
- text={(tag.name || tag.blog_tags?.name || '').toUpperCase()}
256
- variant="card"
257
- className="bg-ods-card border border-ods-border"
258
- />
259
- ))}
260
- </div>
249
+ {/* Tags — flat product_release_tags[] from entity_tags */}
250
+ <EntityTagBadges tags={release.product_release_tags as TagAssoc[] | undefined} />
261
251
 
262
252
  {/* Metadata Grid */}
263
253
  <div className="grid grid-cols-1 md:grid-cols-4 border border-ods-border rounded-md overflow-hidden w-full">
@@ -45,7 +45,7 @@ export function useUnifiedFiltering(config: FilterConfig) {
45
45
  const search = searchParams?.get('search') || undefined
46
46
  const categories = searchParams?.get('category')?.split(',').filter(Boolean) || []
47
47
  const subcategories = searchParams?.get('subcategory')?.split(',').filter(Boolean) || []
48
- const tags = searchParams?.get('tag')?.split(',').filter(Boolean) || []
48
+ const tags = searchParams?.get('tags')?.split(',').filter(Boolean) || []
49
49
  const filters = searchParams?.getAll('filter') || []
50
50
  const pricing = searchParams?.get('pricing') || undefined
51
51
  const page = parseInt(searchParams?.get('page') || "1")
@@ -88,9 +88,10 @@ export function useUnifiedFiltering(config: FilterConfig) {
88
88
  params.set('subcategory', updatedState.subcategories.join(','))
89
89
  }
90
90
 
91
- // Add tag parameters (AND logic - all must match)
91
+ // Add tag parameters (AND logic - all must match). Unified `?tags=` param
92
+ // (matches blog/case-study/media/publication).
92
93
  if (updatedState.tags && updatedState.tags.length > 0 && config.supportedFilters.tags) {
93
- params.set('tag', updatedState.tags.join(','))
94
+ params.set('tags', updatedState.tags.join(','))
94
95
  }
95
96
 
96
97
  // Add filter parameters (AND logic - all must match)
package/src/types/blog.ts CHANGED
@@ -45,7 +45,8 @@ export interface BlogPost {
45
45
  };
46
46
  blog_post_platforms?: BlogPostPlatform[];
47
47
  blog_post_categories?: BlogPostCategory[];
48
- blog_post_tags?: BlogPostTag[];
48
+ // Flat unified tag-association shape (hydrated by entity-tag-utils).
49
+ blog_post_tags?: TagAssoc[];
49
50
  blog_media_assets?: BlogMediaAsset[];
50
51
  }
51
52
 
@@ -70,6 +71,19 @@ export interface BlogPostTag {
70
71
  blog_tags?: BlogTag;
71
72
  }
72
73
 
74
+ /**
75
+ * Flat per-entity tag association — the unified shape hydrated onto every
76
+ * taggable entity's `<entity>_tags[]` array (mirrors the platform `*_platforms`
77
+ * association shape). `tag_id` is always present; `id`/`name`/`slug` come from
78
+ * the embedded vocabulary row. Replaces the legacy nested `{ blog_tags: {...} }`.
79
+ */
80
+ export interface TagAssoc {
81
+ tag_id: number;
82
+ id?: number;
83
+ name?: string;
84
+ slug?: string;
85
+ }
86
+
73
87
  export interface BlogCategory {
74
88
  id: number;
75
89
  name: string;
@@ -2,7 +2,7 @@
2
2
  // Following the blog.ts pattern for consistency
3
3
 
4
4
  import type { PlatformRecord } from './platform'
5
- import type { BlogTag } from './blog'
5
+ import type { BlogTag, TagAssoc } from './blog'
6
6
  import type { MSP } from './stack'
7
7
  import type { UserProfile } from './user'
8
8
  import type { VideoTeaser, CustomerInterview } from './customer-interview'
@@ -64,10 +64,8 @@ export interface CaseStudy {
64
64
  is_featured: boolean
65
65
  featured_order: number | null
66
66
  }>
67
- case_study_tags?: Array<{
68
- tag_id: number
69
- }>
70
- blog_tags?: BlogTag[]
67
+ // Flat unified tag-association shape (hydrated by entity-tag-utils).
68
+ case_study_tags?: TagAssoc[]
71
69
  }
72
70
 
73
71
  export interface CreateCaseStudyData {
@@ -2,7 +2,7 @@
2
2
  // Following the blog.ts pattern for consistency
3
3
 
4
4
  import type { PlatformRecord } from './platform'
5
- import type { BlogTag } from './blog'
5
+ import type { BlogTag, TagAssoc } from './blog'
6
6
  import type { VideoTeaser, TranscriptWord, SpeakerMapping } from './video-processing'
7
7
 
8
8
  export interface ChangelogEntry {
@@ -162,10 +162,8 @@ export interface ProductRelease {
162
162
  name?: string
163
163
  display_name?: string
164
164
  }>
165
- product_release_tags?: Array<{
166
- tag_id: number
167
- }>
168
- blog_tags?: BlogTag[]
165
+ // Flat unified tag-association shape (hydrated by entity-tag-utils).
166
+ product_release_tags?: TagAssoc[]
169
167
  }
170
168
 
171
169
  export interface CreateProductReleaseData {
@@ -61,7 +61,6 @@ export type Database = {
61
61
  icon_type: string | null
62
62
  id: string
63
63
  is_active: boolean | null
64
- platform_id: string | null
65
64
  title: string
66
65
  updated_at: string | null
67
66
  }
@@ -75,7 +74,6 @@ export type Database = {
75
74
  icon_type?: string | null
76
75
  id?: string
77
76
  is_active?: boolean | null
78
- platform_id?: string | null
79
77
  title: string
80
78
  updated_at?: string | null
81
79
  }
@@ -89,18 +87,10 @@ export type Database = {
89
87
  icon_type?: string | null
90
88
  id?: string
91
89
  is_active?: boolean | null
92
- platform_id?: string | null
93
90
  title?: string
94
91
  updated_at?: string | null
95
92
  }
96
93
  Relationships: [
97
- {
98
- foreignKeyName: "announcements_platform_id_fkey"
99
- columns: ["platform_id"]
100
- isOneToOne: false
101
- referencedRelation: "platforms"
102
- referencedColumns: ["id"]
103
- },
104
94
  ]
105
95
  }
106
96
  slack_channels: {
@@ -299,7 +289,6 @@ export type Database = {
299
289
  published_at: string | null
300
290
  external_url: string | null
301
291
  hosts: Json | null
302
- platform_id: string
303
292
  is_deleted: boolean | null
304
293
  last_synced_at: string | null
305
294
  created_at: string | null
@@ -320,7 +309,6 @@ export type Database = {
320
309
  published_at?: string | null
321
310
  external_url?: string | null
322
311
  hosts?: Json | null
323
- platform_id: string
324
312
  is_deleted?: boolean | null
325
313
  last_synced_at?: string | null
326
314
  created_at?: string | null
@@ -341,20 +329,12 @@ export type Database = {
341
329
  published_at?: string | null
342
330
  external_url?: string | null
343
331
  hosts?: Json | null
344
- platform_id?: string
345
332
  is_deleted?: boolean | null
346
333
  last_synced_at?: string | null
347
334
  created_at?: string | null
348
335
  updated_at?: string | null
349
336
  }
350
337
  Relationships: [
351
- {
352
- foreignKeyName: "podcast_episodes_platform_id_fkey"
353
- columns: ["platform_id"]
354
- isOneToOne: false
355
- referencedRelation: "platforms"
356
- referencedColumns: ["id"]
357
- },
358
338
  ]
359
339
  }
360
340
  webinars: {
@@ -370,7 +350,6 @@ export type Database = {
370
350
  registration_url: string | null
371
351
  main_video_url: string | null
372
352
  hosts: Json | null
373
- platform_id: string
374
353
  is_deleted: boolean | null
375
354
  last_synced_at: string | null
376
355
  created_at: string | null
@@ -388,7 +367,6 @@ export type Database = {
388
367
  registration_url?: string | null
389
368
  main_video_url?: string | null
390
369
  hosts?: Json | null
391
- platform_id: string
392
370
  is_deleted?: boolean | null
393
371
  last_synced_at?: string | null
394
372
  created_at?: string | null
@@ -406,20 +384,12 @@ export type Database = {
406
384
  registration_url?: string | null
407
385
  main_video_url?: string | null
408
386
  hosts?: Json | null
409
- platform_id?: string
410
387
  is_deleted?: boolean | null
411
388
  last_synced_at?: string | null
412
389
  created_at?: string | null
413
390
  updated_at?: string | null
414
391
  }
415
392
  Relationships: [
416
- {
417
- foreignKeyName: "webinars_platform_id_fkey"
418
- columns: ["platform_id"]
419
- isOneToOne: false
420
- referencedRelation: "platforms"
421
- referencedColumns: ["id"]
422
- },
423
393
  ]
424
394
  }
425
395
  luma_events: {
@@ -445,7 +415,6 @@ export type Database = {
445
415
  registration_questions: Json | null
446
416
  tags: string[] | null
447
417
  guest_count: number | null
448
- platform_id: string
449
418
  is_deleted: boolean | null
450
419
  last_synced_at: string | null
451
420
  created_at: string | null
@@ -473,7 +442,6 @@ export type Database = {
473
442
  registration_questions?: Json | null
474
443
  tags?: string[] | null
475
444
  guest_count?: number | null
476
- platform_id: string
477
445
  is_deleted?: boolean | null
478
446
  last_synced_at?: string | null
479
447
  created_at?: string | null
@@ -501,20 +469,12 @@ export type Database = {
501
469
  registration_questions?: Json | null
502
470
  tags?: string[] | null
503
471
  guest_count?: number | null
504
- platform_id?: string
505
472
  is_deleted?: boolean | null
506
473
  last_synced_at?: string | null
507
474
  created_at?: string | null
508
475
  updated_at?: string | null
509
476
  }
510
477
  Relationships: [
511
- {
512
- foreignKeyName: "luma_events_platform_id_fkey"
513
- columns: ["platform_id"]
514
- isOneToOne: false
515
- referencedRelation: "platforms"
516
- referencedColumns: ["id"]
517
- },
518
478
  ]
519
479
  }
520
480
  vendor_github_stats: {