@flamingo-stack/openframe-frontend-core 0.0.222 → 0.0.223
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-AIKPR6BE.js → chunk-4VHWSAQR.js} +6069 -5235
- package/dist/chunk-4VHWSAQR.js.map +1 -0
- package/dist/{chunk-GMNE7P7V.cjs → chunk-4WBVMIKQ.cjs} +26 -26
- package/dist/{chunk-GMNE7P7V.cjs.map → chunk-4WBVMIKQ.cjs.map} +1 -1
- package/dist/{chunk-QYRV6MKX.js → chunk-53EA4QPZ.js} +13 -7
- package/dist/chunk-53EA4QPZ.js.map +1 -0
- package/dist/{chunk-4JOWZLEL.cjs → chunk-7TKYFZ3L.cjs} +7 -7
- package/dist/{chunk-4JOWZLEL.cjs.map → chunk-7TKYFZ3L.cjs.map} +1 -1
- package/dist/{chunk-IZA5WF44.cjs → chunk-ABBU7QOM.cjs} +12 -12
- package/dist/{chunk-IZA5WF44.cjs.map → chunk-ABBU7QOM.cjs.map} +1 -1
- package/dist/{chunk-KM4KK2A6.cjs → chunk-ENDUMC7Z.cjs} +2558 -1724
- package/dist/chunk-ENDUMC7Z.cjs.map +1 -0
- package/dist/{chunk-CCSEXVI4.js → chunk-GYYGA24C.js} +2 -2
- package/dist/{chunk-RMOHKAHA.js → chunk-ISWWP2BH.js} +3 -3
- package/dist/{chunk-QNYH3WUU.cjs → chunk-JUWPBMQM.cjs} +13 -7
- package/dist/chunk-JUWPBMQM.cjs.map +1 -0
- package/dist/{chunk-A2SFHYVF.cjs → chunk-KXLTWANQ.cjs} +10 -10
- package/dist/{chunk-A2SFHYVF.cjs.map → chunk-KXLTWANQ.cjs.map} +1 -1
- package/dist/{chunk-ND24XSVT.js → chunk-TA6Z2AKD.js} +3 -3
- package/dist/chunk-TA6Z2AKD.js.map +1 -0
- package/dist/{chunk-HRIAZQ5D.js → chunk-WOKB6RJ7.js} +3 -3
- package/dist/components/chat/chat-archive-page.d.ts.map +1 -1
- package/dist/components/chat/chat-composer.d.ts.map +1 -1
- package/dist/components/chat/chat-message-enhanced.d.ts.map +1 -1
- package/dist/components/chat/chat-message-skeleton.d.ts +19 -4
- package/dist/components/chat/chat-message-skeleton.d.ts.map +1 -1
- package/dist/components/chat/chat-panel-header-mobile.d.ts +24 -0
- package/dist/components/chat/chat-panel-header-mobile.d.ts.map +1 -0
- package/dist/components/chat/chat-panel-header.d.ts +6 -0
- package/dist/components/chat/chat-panel-header.d.ts.map +1 -1
- package/dist/components/chat/chat-quick-action-row.d.ts +31 -0
- package/dist/components/chat/chat-quick-action-row.d.ts.map +1 -0
- package/dist/components/chat/embeddable-chat.d.ts +27 -0
- package/dist/components/chat/embeddable-chat.d.ts.map +1 -1
- package/dist/components/chat/entity-cards/dispatch.d.ts +14 -0
- package/dist/components/chat/entity-cards/dispatch.d.ts.map +1 -1
- package/dist/components/chat/entity-cards/github-activity-card.d.ts +10 -1
- package/dist/components/chat/entity-cards/github-activity-card.d.ts.map +1 -1
- package/dist/components/chat/guide-welcome.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts +9 -0
- package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-slash-commands.d.ts +26 -0
- package/dist/components/chat/hooks/use-slash-commands.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts +12 -1
- package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-unified-chat.d.ts +12 -0
- package/dist/components/chat/hooks/use-unified-chat.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +9 -3
- package/dist/components/chat/index.cjs.map +1 -1
- package/dist/components/chat/index.js +8 -2
- package/dist/components/chat/mingo-chat-history.d.ts +10 -0
- package/dist/components/chat/mingo-chat-history.d.ts.map +1 -1
- package/dist/components/chat/mingo-info-card.d.ts +63 -0
- package/dist/components/chat/mingo-info-card.d.ts.map +1 -0
- package/dist/components/chat/mingo-welcome.d.ts +14 -1
- package/dist/components/chat/mingo-welcome.d.ts.map +1 -1
- package/dist/components/chat/types/chat.types.d.ts +6 -0
- package/dist/components/chat/types/chat.types.d.ts.map +1 -1
- package/dist/components/chat/types/unified-chat-state.types.d.ts +30 -0
- package/dist/components/chat/types/unified-chat-state.types.d.ts.map +1 -1
- package/dist/components/chat/utils/process-historical-messages.d.ts.map +1 -1
- package/dist/components/contact/index.cjs +4 -4
- package/dist/components/contact/index.js +3 -3
- package/dist/components/features/index.cjs +3 -3
- package/dist/components/features/index.js +2 -2
- package/dist/components/index.cjs +110 -104
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +36 -30
- package/dist/components/index.js.map +1 -1
- package/dist/components/navigation/app-layout-drawer.d.ts +7 -0
- package/dist/components/navigation/app-layout-drawer.d.ts.map +1 -1
- package/dist/components/navigation/index.cjs +3 -3
- package/dist/components/navigation/index.js +2 -2
- package/dist/components/onboarding-guides/index.cjs +26 -26
- package/dist/components/onboarding-guides/index.js +4 -4
- package/dist/components/tickets/index.cjs +67 -67
- package/dist/components/tickets/index.js +5 -5
- package/dist/components/ui/actions-menu.d.ts +9 -0
- package/dist/components/ui/actions-menu.d.ts.map +1 -1
- package/dist/components/ui/dropdown-button.d.ts +2 -2
- package/dist/components/ui/dropdown-button.d.ts.map +1 -1
- package/dist/components/ui/index.cjs +9 -3
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.js +8 -2
- package/dist/components/ui/modal-v2.d.ts.map +1 -1
- package/dist/components/ui/more-actions-menu.d.ts +13 -0
- package/dist/components/ui/more-actions-menu.d.ts.map +1 -1
- package/dist/components/ui/square-avatar.d.ts +4 -0
- package/dist/components/ui/square-avatar.d.ts.map +1 -1
- package/dist/components/ui/textarea.d.ts.map +1 -1
- package/dist/hooks/index.cjs +2 -2
- package/dist/hooks/index.js +1 -1
- package/dist/hooks/ui/use-auto-limit-tags.d.ts +7 -1
- package/dist/hooks/ui/use-auto-limit-tags.d.ts.map +1 -1
- package/dist/index.cjs +9 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +8 -2
- package/package.json +1 -1
- package/src/components/chat/chat-archive-page.tsx +32 -7
- package/src/components/chat/chat-composer.tsx +6 -2
- package/src/components/chat/chat-message-enhanced.tsx +24 -7
- package/src/components/chat/chat-message-list.tsx +1 -1
- package/src/components/chat/chat-message-skeleton.tsx +60 -60
- package/src/components/chat/chat-panel-header-mobile.tsx +94 -0
- package/src/components/chat/chat-panel-header.tsx +78 -50
- package/src/components/chat/chat-quick-action-row.tsx +146 -0
- package/src/components/chat/embeddable-chat.tsx +140 -45
- package/src/components/chat/entity-cards/dispatch.tsx +770 -232
- package/src/components/chat/entity-cards/github-activity-card.tsx +9 -3
- package/src/components/chat/guide-welcome.tsx +11 -8
- package/src/components/chat/hooks/use-nats-chat-adapter.ts +80 -7
- package/src/components/chat/hooks/use-slash-commands.ts +39 -0
- package/src/components/chat/hooks/use-sse-chat-adapter.ts +49 -45
- package/src/components/chat/hooks/use-unified-chat.ts +31 -8
- package/src/components/chat/mingo-chat-history.tsx +118 -36
- package/src/components/chat/mingo-info-card.tsx +214 -0
- package/src/components/chat/mingo-welcome.tsx +89 -36
- package/src/components/chat/types/chat.types.ts +4 -0
- package/src/components/chat/types/unified-chat-state.types.ts +35 -0
- package/src/components/chat/utils/process-historical-messages.ts +8 -0
- package/src/components/content-loading-container.tsx +1 -1
- package/src/components/empty-state.tsx +3 -3
- package/src/components/faq-accordion.tsx +2 -2
- package/src/components/features/changelog-manager.tsx +2 -2
- package/src/components/features/highlight-config-section.tsx +2 -2
- package/src/components/features/og-editor-preview.tsx +3 -3
- package/src/components/features/release-media-manager.tsx +2 -2
- package/src/components/loading/card-skeleton.tsx +3 -3
- package/src/components/loading/device-card-skeleton.tsx +1 -1
- package/src/components/loading/margin-report-skeleton.tsx +2 -2
- package/src/components/loading/organization-card-skeleton.tsx +1 -1
- package/src/components/loading/organization-icon-skeleton.tsx +1 -1
- package/src/components/loading/page-layout-skeleton.tsx +9 -9
- package/src/components/loading/users-grid-skeleton.tsx +2 -2
- package/src/components/logs-list.tsx +1 -1
- package/src/components/media-carousel.tsx +1 -1
- package/src/components/navigation/app-layout-drawer.tsx +70 -6
- package/src/components/open-source-features.tsx +2 -2
- package/src/components/ui/actions-menu.tsx +36 -13
- package/src/components/ui/allowed-domains-input.tsx +3 -3
- package/src/components/ui/cursor-pagination.tsx +1 -1
- package/src/components/ui/dropdown-button.tsx +6 -6
- package/src/components/ui/info-row.tsx +2 -2
- package/src/components/ui/modal-v2.tsx +8 -1
- package/src/components/ui/more-actions-menu.tsx +13 -0
- package/src/components/ui/square-avatar.tsx +6 -1
- package/src/components/ui/tags-input.tsx +1 -1
- package/src/components/ui/textarea.tsx +18 -4
- package/src/components/vendor-icon.tsx +1 -1
- package/src/components/vendor-tag.tsx +1 -1
- package/src/components/why-it-matters.tsx +1 -1
- package/src/hooks/ui/use-auto-limit-tags.ts +22 -8
- package/src/stories/ActionsMenu.stories.tsx +256 -0
- package/src/stories/AppLayoutSidebar.stories.tsx +6 -0
- package/src/stories/Board.stories.tsx +1 -1
- package/src/stories/ChatMingoInfoCards.stories.tsx +748 -0
- package/src/stories/ChatTicketList.stories.tsx +1 -1
- package/src/stories/DataTable.stories.tsx +17 -13
- package/src/stories/EmbeddableChat.stories.tsx +233 -1
- package/src/stories/MingoInfoCard.stories.tsx +193 -0
- package/src/stories/MingoOnboardingCard.stories.tsx +1 -1
- package/src/stories/Modal.stories.tsx +1 -0
- package/src/stories/NotificationDrawer.stories.tsx +14 -3
- package/src/stories/PageActions.stories.tsx +4 -4
- package/src/stories/PageContainer.stories.tsx +5 -5
- package/src/stories/PageLayout.stories.tsx +1 -1
- package/src/stories/TabSelector.stories.tsx +2 -2
- package/src/stories/Tag.stories.tsx +10 -5
- package/src/stories/TicketInfoSection.stories.tsx +23 -11
- package/src/stories/Toaster.stories.tsx +5 -5
- package/src/stories/__fixtures__/chat-card-decorator.tsx +44 -0
- package/dist/chunk-AIKPR6BE.js.map +0 -1
- package/dist/chunk-KM4KK2A6.cjs.map +0 -1
- package/dist/chunk-ND24XSVT.js.map +0 -1
- package/dist/chunk-QNYH3WUU.cjs.map +0 -1
- package/dist/chunk-QYRV6MKX.js.map +0 -1
- package/src/stories/.TicketDescriptionViewer.stories.md +0 -39
- package/src/stories/.TicketKnowledgeBaseList.stories.md +0 -36
- package/src/stories/TicketDescriptionViewer.stories.tsx +0 -70
- package/src/stories/TicketKnowledgeBaseList.stories.tsx +0 -51
- /package/dist/{chunk-CCSEXVI4.js.map → chunk-GYYGA24C.js.map} +0 -0
- /package/dist/{chunk-RMOHKAHA.js.map → chunk-ISWWP2BH.js.map} +0 -0
- /package/dist/{chunk-HRIAZQ5D.js.map → chunk-WOKB6RJ7.js.map} +0 -0
|
@@ -27,6 +27,12 @@ function getOwnerDisplayName(owner?: MessageOwner): string {
|
|
|
27
27
|
return owner?.type === OWNER_TYPE.ADMIN ? 'Admin' : 'You'
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/** Per-message author avatar (e.g. an admin's profile photo) when the owner
|
|
31
|
+
* carries one. `imageUrl` may be relative; the host resolves it downstream. */
|
|
32
|
+
function getOwnerAvatar(owner?: MessageOwner): string | undefined {
|
|
33
|
+
return owner?.user?.image?.imageUrl ?? undefined
|
|
34
|
+
}
|
|
35
|
+
|
|
30
36
|
function pushStandaloneMessages(
|
|
31
37
|
processedMessages: ProcessedMessage[],
|
|
32
38
|
msg: HistoricalMessage,
|
|
@@ -143,6 +149,7 @@ export function processHistoricalMessages(
|
|
|
143
149
|
role: 'user',
|
|
144
150
|
content: data.text,
|
|
145
151
|
name: getOwnerDisplayName(msg.owner),
|
|
152
|
+
avatar: getOwnerAvatar(msg.owner),
|
|
146
153
|
authorType: userAuthorType,
|
|
147
154
|
timestamp: new Date(msg.createdAt),
|
|
148
155
|
})
|
|
@@ -517,6 +524,7 @@ export function processHistoricalMessagesWithErrors(
|
|
|
517
524
|
role: 'user',
|
|
518
525
|
content: data.text,
|
|
519
526
|
name: getOwnerDisplayName(msg.owner),
|
|
527
|
+
avatar: getOwnerAvatar(msg.owner),
|
|
520
528
|
authorType: userAuthorType,
|
|
521
529
|
timestamp: new Date(msg.createdAt),
|
|
522
530
|
})
|
|
@@ -83,7 +83,7 @@ export function ContentLoadingContainer({
|
|
|
83
83
|
{/* Loading Skeleton Overlay */}
|
|
84
84
|
{isLoading && (
|
|
85
85
|
<div
|
|
86
|
-
className="absolute inset-0 z-10 bg-
|
|
86
|
+
className="absolute inset-0 z-10 bg-ods-bg"
|
|
87
87
|
style={{
|
|
88
88
|
opacity: loadingOpacity,
|
|
89
89
|
transition: `opacity ${transitionDuration}ms ease-in-out`
|
|
@@ -177,9 +177,9 @@ export function EmptyState({
|
|
|
177
177
|
<div className="w-full max-w-xs mb-3">
|
|
178
178
|
<Button
|
|
179
179
|
onClick={smartCTA.action}
|
|
180
|
-
className={ctaVariant === 'primary'
|
|
181
|
-
? "w-full bg-
|
|
182
|
-
: "w-full bg-transparent border border-ods-border text-ods-text-primary hover:border-
|
|
180
|
+
className={ctaVariant === 'primary'
|
|
181
|
+
? "w-full bg-ods-accent text-ods-text-on-accent hover:bg-ods-accent-hover transition-all duration-150 font-['DM_Sans'] font-medium"
|
|
182
|
+
: "w-full bg-transparent border border-ods-border text-ods-text-primary hover:border-ods-accent hover:text-ods-accent transition-all duration-150 font-['DM_Sans'] font-medium"
|
|
183
183
|
}
|
|
184
184
|
>
|
|
185
185
|
{smartCTA.text}
|
|
@@ -52,7 +52,7 @@ export function FaqAccordion({ items, defaultOpenIds = [] }: FaqAccordionProps)
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
return (
|
|
55
|
-
<div className="rounded-3xl border border-ods-border divide-y divide-
|
|
55
|
+
<div className="rounded-3xl border border-ods-border divide-y divide-ods-border bg-ods-card overflow-hidden">
|
|
56
56
|
{items.map(item => {
|
|
57
57
|
const isOpen = openSet.has(item.id)
|
|
58
58
|
const { ref, maxHeight } = useMeasuredHeight(isOpen)
|
|
@@ -60,7 +60,7 @@ export function FaqAccordion({ items, defaultOpenIds = [] }: FaqAccordionProps)
|
|
|
60
60
|
return (
|
|
61
61
|
<div
|
|
62
62
|
key={item.id}
|
|
63
|
-
className={cn('group transition-colors hover:bg-[#1E1E1E]', isOpen ? 'bg-
|
|
63
|
+
className={cn('group transition-colors hover:bg-[#1E1E1E]', isOpen ? 'bg-ods-bg' : 'bg-transparent')}
|
|
64
64
|
>
|
|
65
65
|
{/* Header */}
|
|
66
66
|
<div
|
|
@@ -177,7 +177,7 @@ export function ChangelogManager({
|
|
|
177
177
|
value={entry.title}
|
|
178
178
|
onChange={(e) => updateEntry(index, 'title', e.target.value)}
|
|
179
179
|
onKeyDown={(e) => e.key === 'Enter' && e.preventDefault()}
|
|
180
|
-
className="bg-
|
|
180
|
+
className="bg-ods-bg"
|
|
181
181
|
/>
|
|
182
182
|
</div>
|
|
183
183
|
|
|
@@ -189,7 +189,7 @@ export function ChangelogManager({
|
|
|
189
189
|
value={entry.description || ''}
|
|
190
190
|
onChange={(e) => updateEntry(index, 'description', e.target.value)}
|
|
191
191
|
rows={2}
|
|
192
|
-
className="bg-
|
|
192
|
+
className="bg-ods-bg"
|
|
193
193
|
/>
|
|
194
194
|
</div>
|
|
195
195
|
</div>
|
|
@@ -43,7 +43,7 @@ export function HighlightConfigSection({
|
|
|
43
43
|
onValueChange={(value) => onTargetDurationChange(parseInt(value))}
|
|
44
44
|
disabled={disabled}
|
|
45
45
|
>
|
|
46
|
-
<SelectTrigger className="bg-
|
|
46
|
+
<SelectTrigger className="bg-ods-bg mt-1">
|
|
47
47
|
<SelectValue />
|
|
48
48
|
</SelectTrigger>
|
|
49
49
|
<SelectContent className="bg-ods-card">
|
|
@@ -62,7 +62,7 @@ export function HighlightConfigSection({
|
|
|
62
62
|
checked={skipSubtitleBurning}
|
|
63
63
|
onChange={(e) => onSkipSubtitleBurningChange(e.target.checked)}
|
|
64
64
|
disabled={disabled}
|
|
65
|
-
className="h-4 w-4 rounded border-ods-border bg-
|
|
65
|
+
className="h-4 w-4 rounded border-ods-border bg-ods-bg text-ods-accent focus:ring-ods-accent"
|
|
66
66
|
/>
|
|
67
67
|
<Label htmlFor="skipSubtitleBurning" className="text-sm cursor-pointer">
|
|
68
68
|
Skip subtitle burning
|
|
@@ -65,7 +65,7 @@ export function OGEditorPreview({
|
|
|
65
65
|
onChange={(e) => onSeoTitleChange(e.target.value)}
|
|
66
66
|
disabled={disabled}
|
|
67
67
|
placeholder="Enter SEO meta title..."
|
|
68
|
-
className="bg-
|
|
68
|
+
className="bg-ods-bg border-ods-border text-ods-text-primary"
|
|
69
69
|
/>
|
|
70
70
|
</div>
|
|
71
71
|
|
|
@@ -78,7 +78,7 @@ export function OGEditorPreview({
|
|
|
78
78
|
onChange={(e) => onSeoKeywordsChange(e.target.value)}
|
|
79
79
|
disabled={disabled}
|
|
80
80
|
placeholder="Enter SEO keywords..."
|
|
81
|
-
className="bg-
|
|
81
|
+
className="bg-ods-bg border-ods-border text-ods-text-primary"
|
|
82
82
|
/>
|
|
83
83
|
</div>
|
|
84
84
|
</div>
|
|
@@ -94,7 +94,7 @@ export function OGEditorPreview({
|
|
|
94
94
|
onChange={(e) => onSeoDescriptionChange(e.target.value)}
|
|
95
95
|
disabled={disabled}
|
|
96
96
|
placeholder="Enter SEO meta description..."
|
|
97
|
-
className="bg-
|
|
97
|
+
className="bg-ods-bg border-ods-border text-ods-text-primary flex-1 resize-none"
|
|
98
98
|
rows={6}
|
|
99
99
|
/>
|
|
100
100
|
</div>
|
|
@@ -241,7 +241,7 @@ export function ReleaseMediaManager({
|
|
|
241
241
|
onValueChange={(value: any) => updateMedia(index, 'media_type', value)}
|
|
242
242
|
disabled={item._uploading}
|
|
243
243
|
>
|
|
244
|
-
<SelectTrigger className="bg-
|
|
244
|
+
<SelectTrigger className="bg-ods-bg h-8 text-xs">
|
|
245
245
|
<SelectValue />
|
|
246
246
|
</SelectTrigger>
|
|
247
247
|
<SelectContent className="bg-ods-card">
|
|
@@ -258,7 +258,7 @@ export function ReleaseMediaManager({
|
|
|
258
258
|
value={item.title}
|
|
259
259
|
onChange={(e) => updateMedia(index, 'title', e.target.value)}
|
|
260
260
|
onKeyDown={(e) => e.key === 'Enter' && e.preventDefault()}
|
|
261
|
-
className="bg-
|
|
261
|
+
className="bg-ods-bg h-8 text-xs"
|
|
262
262
|
disabled={item._uploading}
|
|
263
263
|
/>
|
|
264
264
|
</div>
|
|
@@ -72,7 +72,7 @@ function VendorCardContent({ showActions, showMetadata }: { showActions: boolean
|
|
|
72
72
|
{/* Header Section - Row layout matching actual VendorCard */}
|
|
73
73
|
<div className="flex items-start gap-3 w-full">
|
|
74
74
|
{/* Logo Frame - 60px width fixed, matching actual structure */}
|
|
75
|
-
<div className="w-[60px] h-[60px] bg-
|
|
75
|
+
<div className="w-[60px] h-[60px] bg-ods-bg border border-ods-border rounded-lg p-2 flex items-center justify-center flex-shrink-0">
|
|
76
76
|
<MediaSkeleton.Avatar size="sm" className="w-11 h-11" />
|
|
77
77
|
</div>
|
|
78
78
|
|
|
@@ -112,7 +112,7 @@ function VendorCardContent({ showActions, showMetadata }: { showActions: boolean
|
|
|
112
112
|
|
|
113
113
|
{/* Tag Section - Contained within card boundaries */}
|
|
114
114
|
<div className="flex-shrink-0">
|
|
115
|
-
<div className="flex items-center gap-1.5 bg-
|
|
115
|
+
<div className="flex items-center gap-1.5 bg-ods-bg border border-ods-border rounded px-2.5 py-1.5">
|
|
116
116
|
<div className="w-4 h-4 bg-ods-border rounded-sm flex items-center justify-center flex-shrink-0">
|
|
117
117
|
<MediaSkeleton.Icon size="sm" className="w-2.5 h-2.5" />
|
|
118
118
|
</div>
|
|
@@ -131,7 +131,7 @@ function BlogCardContent({ showActions, showMetadata }: { showActions: boolean;
|
|
|
131
131
|
return (
|
|
132
132
|
<>
|
|
133
133
|
{/* Image Section — OG 1200×630 aspect (matches blog-card.tsx loaded state) */}
|
|
134
|
-
<div className="blog-card-image-container relative w-full aspect-[1200/630] overflow-hidden bg-
|
|
134
|
+
<div className="blog-card-image-container relative w-full aspect-[1200/630] overflow-hidden bg-ods-bg">
|
|
135
135
|
<MediaSkeleton.CardImage />
|
|
136
136
|
</div>
|
|
137
137
|
|
|
@@ -32,7 +32,7 @@ export function DeviceCardSkeleton({ className }: DeviceCardSkeletonProps) {
|
|
|
32
32
|
{/* Row 1: Device icon + Device name + More button */}
|
|
33
33
|
<div className="flex gap-4 items-center px-4 py-3">
|
|
34
34
|
{/* Device type icon (8x8 container) */}
|
|
35
|
-
<div className="w-8 h-8 bg-
|
|
35
|
+
<div className="w-8 h-8 bg-ods-bg border border-ods-border rounded-[6px] flex items-center justify-center flex-shrink-0">
|
|
36
36
|
<div className="w-4 h-4 bg-ods-border rounded animate-pulse" />
|
|
37
37
|
</div>
|
|
38
38
|
|
|
@@ -11,7 +11,7 @@ interface MarginReportSkeletonProps {
|
|
|
11
11
|
|
|
12
12
|
export function MarginReportSkeleton({ animate = true, description, overlayContent }: MarginReportSkeletonProps) {
|
|
13
13
|
return (
|
|
14
|
-
<main className={`bg-
|
|
14
|
+
<main className={`bg-ods-bg ${animate ? 'animate-pulse' : ''} relative min-h-screen`}>
|
|
15
15
|
<div className="max-w-[1920px] px-6 md:px-20 py-6 md:py-10 mx-auto space-y-10">
|
|
16
16
|
{/* Header */}
|
|
17
17
|
<div className="space-y-3">
|
|
@@ -69,7 +69,7 @@ export function MarginReportSkeleton({ animate = true, description, overlayConte
|
|
|
69
69
|
{/* vendor rows */}
|
|
70
70
|
<div className="flex-1 flex flex-col gap-3 p-3">
|
|
71
71
|
{Array.from({ length: 5 }).map((_, j) => (
|
|
72
|
-
<div key={j} className="bg-
|
|
72
|
+
<div key={j} className="bg-ods-bg border border-ods-border rounded-lg px-4 py-3 flex items-center justify-between">
|
|
73
73
|
{/* left section: icon + text */}
|
|
74
74
|
<div className="flex items-center gap-3 min-w-0">
|
|
75
75
|
<div className="w-12 h-12 bg-[#2A2A2A] rounded-lg flex-shrink-0" />
|
|
@@ -101,7 +101,7 @@ export function OrganizationCardSkeleton({
|
|
|
101
101
|
|
|
102
102
|
{/* Tag/Badge Section */}
|
|
103
103
|
<div className="flex-shrink-0">
|
|
104
|
-
<div className="flex items-center gap-1.5 bg-
|
|
104
|
+
<div className="flex items-center gap-1.5 bg-ods-bg border border-ods-border rounded px-2.5 py-1.5">
|
|
105
105
|
<div className="w-4 h-4 bg-ods-border rounded-sm flex items-center justify-center flex-shrink-0">
|
|
106
106
|
<MediaSkeleton.Icon size="sm" className="w-2.5 h-2.5" />
|
|
107
107
|
</div>
|
|
@@ -45,7 +45,7 @@ const sizeClasses = {
|
|
|
45
45
|
* Background style classes matching VendorIcon/OrganizationIcon exactly
|
|
46
46
|
*/
|
|
47
47
|
const backgroundClasses = {
|
|
48
|
-
dark: 'bg-
|
|
48
|
+
dark: 'bg-ods-bg border border-ods-border',
|
|
49
49
|
light: 'bg-ods-card border border-ods-border',
|
|
50
50
|
white: 'bg-white border border-[#E5E5E5]'
|
|
51
51
|
}
|
|
@@ -308,7 +308,7 @@ export function ArticleLayoutSkeleton({ className }: PageLayoutSkeletonProps) {
|
|
|
308
308
|
*/
|
|
309
309
|
export function VendorDetailLayoutSkeleton({ className }: PageLayoutSkeletonProps) {
|
|
310
310
|
return (
|
|
311
|
-
<main className={cn("bg-
|
|
311
|
+
<main className={cn("bg-ods-bg min-h-screen", className)}>
|
|
312
312
|
<div className="max-w-[1920px] mx-auto px-6 md:px-20 py-6 md:py-10">
|
|
313
313
|
{/* Breadcrumb */}
|
|
314
314
|
<BreadcrumbSkeleton className="mb-6" />
|
|
@@ -457,7 +457,7 @@ export function VendorDetailLayoutSkeleton({ className }: PageLayoutSkeletonProp
|
|
|
457
457
|
<div className="h-5 bg-[#2A2A2A] rounded animate-pulse w-16"></div>
|
|
458
458
|
{Array.from({ length: 3 }).map((_, i) => (
|
|
459
459
|
<div key={i} className="bg-ods-card border border-ods-border rounded-lg p-4 flex gap-3">
|
|
460
|
-
<div className="w-12 h-12 bg-
|
|
460
|
+
<div className="w-12 h-12 bg-ods-bg border border-ods-border rounded flex-shrink-0 animate-pulse"></div>
|
|
461
461
|
<div className="flex-1 space-y-2">
|
|
462
462
|
<div className="h-5 bg-[#2A2A2A] rounded animate-pulse w-2/3"></div>
|
|
463
463
|
<div className="h-4 bg-[#2A2A2A] rounded animate-pulse"></div>
|
|
@@ -470,7 +470,7 @@ export function VendorDetailLayoutSkeleton({ className }: PageLayoutSkeletonProp
|
|
|
470
470
|
<div className="h-5 bg-[#2A2A2A] rounded animate-pulse w-16"></div>
|
|
471
471
|
{Array.from({ length: 3 }).map((_, i) => (
|
|
472
472
|
<div key={i} className="bg-ods-card border border-ods-border rounded-lg p-4 flex gap-3">
|
|
473
|
-
<div className="w-12 h-12 bg-
|
|
473
|
+
<div className="w-12 h-12 bg-ods-bg border border-ods-border rounded flex-shrink-0 animate-pulse"></div>
|
|
474
474
|
<div className="flex-1 space-y-2">
|
|
475
475
|
<div className="h-5 bg-[#2A2A2A] rounded animate-pulse w-2/3"></div>
|
|
476
476
|
<div className="h-4 bg-[#2A2A2A] rounded animate-pulse"></div>
|
|
@@ -489,7 +489,7 @@ export function VendorDetailLayoutSkeleton({ className }: PageLayoutSkeletonProp
|
|
|
489
489
|
<div key={i} className="bg-ods-card border border-ods-border rounded-lg p-6 space-y-4">
|
|
490
490
|
{/* Header */}
|
|
491
491
|
<div className="flex items-start gap-4">
|
|
492
|
-
<div className="w-16 h-16 bg-
|
|
492
|
+
<div className="w-16 h-16 bg-ods-bg border border-ods-border rounded-lg animate-pulse flex-shrink-0"></div>
|
|
493
493
|
<div className="flex-1 space-y-2">
|
|
494
494
|
<div className="h-6 bg-[#2A2A2A] rounded animate-pulse w-3/4"></div>
|
|
495
495
|
<div className="h-4 bg-[#2A2A2A] rounded animate-pulse w-1/2"></div>
|
|
@@ -523,7 +523,7 @@ export function VendorDetailLayoutSkeleton({ className }: PageLayoutSkeletonProp
|
|
|
523
523
|
{/* Comment Form Skeleton */}
|
|
524
524
|
<div className="space-y-3">
|
|
525
525
|
<div className="flex gap-3">
|
|
526
|
-
<div className="flex-1 bg-
|
|
526
|
+
<div className="flex-1 bg-ods-bg border border-ods-border rounded-lg overflow-hidden">
|
|
527
527
|
{/* Title Section */}
|
|
528
528
|
<div className="border-b border-ods-border p-3">
|
|
529
529
|
<div className="h-3 bg-[#2A2A2A] rounded animate-pulse w-8 mb-2"></div>
|
|
@@ -702,7 +702,7 @@ export function VendorGridSkeleton({ className }: PageLayoutSkeletonProps) {
|
|
|
702
702
|
export function SlackCommunitySkeleton() {
|
|
703
703
|
return (
|
|
704
704
|
<section
|
|
705
|
-
className="w-full bg-
|
|
705
|
+
className="w-full bg-ods-bg px-4 md:px-20 lg:px-20 py-12 md:py-20"
|
|
706
706
|
aria-label="Slack Community Loading"
|
|
707
707
|
>
|
|
708
708
|
{/* Frame 651 Container */}
|
|
@@ -717,7 +717,7 @@ export function SlackCommunitySkeleton() {
|
|
|
717
717
|
<div className="w-full flex flex-col lg:flex-row lg:justify-end lg:items-start gap-4 md:gap-6 min-w-0">
|
|
718
718
|
|
|
719
719
|
{/* Channel List Skeleton */}
|
|
720
|
-
<div className="w-full lg:w-[290px] lg:max-w-[290px] bg-
|
|
720
|
+
<div className="w-full lg:w-[290px] lg:max-w-[290px] bg-ods-bg border border-ods-border rounded flex flex-col flex-shrink-0 overflow-hidden animate-pulse">
|
|
721
721
|
<div className="p-4 space-y-4">
|
|
722
722
|
{/* Header */}
|
|
723
723
|
<div className="flex flex-col gap-2">
|
|
@@ -762,7 +762,7 @@ export function SlackCommunitySkeleton() {
|
|
|
762
762
|
<div className="flex-1 min-h-[450px] h-[450px] md:min-h-[500px] md:h-[500px] lg:min-h-[600px] lg:h-[600px] bg-ods-card border border-ods-border rounded-lg flex flex-col min-w-0 animate-pulse">
|
|
763
763
|
|
|
764
764
|
{/* Header */}
|
|
765
|
-
<div className="flex items-center justify-between p-4 md:p-6 border-b border-ods-border bg-
|
|
765
|
+
<div className="flex items-center justify-between p-4 md:p-6 border-b border-ods-border bg-ods-bg h-[56px] md:h-[60px] flex-shrink-0">
|
|
766
766
|
<div className="flex items-center gap-2">
|
|
767
767
|
<div className="h-3 md:h-4 bg-[#2A2A2A] rounded w-3 md:w-4"></div>
|
|
768
768
|
<div className="h-3 md:h-4 bg-[#2A2A2A] rounded w-24 md:w-32"></div>
|
|
@@ -771,7 +771,7 @@ export function SlackCommunitySkeleton() {
|
|
|
771
771
|
</div>
|
|
772
772
|
|
|
773
773
|
{/* Messages */}
|
|
774
|
-
<div className="flex-1 bg-
|
|
774
|
+
<div className="flex-1 bg-ods-bg p-4 md:p-6 space-y-3 md:space-y-4 overflow-hidden min-h-[280px] md:min-h-[320px] lg:min-h-[420px]">
|
|
775
775
|
{Array.from({ length: 6 }).map((_, i) => (
|
|
776
776
|
<div key={i} className="flex gap-2 md:gap-3">
|
|
777
777
|
<div className="w-10 h-10 md:w-12 md:h-12 bg-[#2A2A2A] rounded-lg flex-shrink-0"></div>
|
|
@@ -9,7 +9,7 @@ export function UsersGridSkeleton({ rows = 10, className }: Props) {
|
|
|
9
9
|
const skeletonRows = Array.from({ length: rows })
|
|
10
10
|
return (
|
|
11
11
|
<div className={cn('overflow-x-auto rounded-lg border border-ods-border bg-ods-card animate-pulse', className)}>
|
|
12
|
-
<table className="min-w-full divide-y divide-
|
|
12
|
+
<table className="min-w-full divide-y divide-ods-border">
|
|
13
13
|
<thead className="bg-[#2A2A2A]">
|
|
14
14
|
<tr>
|
|
15
15
|
{['Name', 'Email', 'Role', 'Created', 'Last Sign-In'].map((h) => (
|
|
@@ -17,7 +17,7 @@ export function UsersGridSkeleton({ rows = 10, className }: Props) {
|
|
|
17
17
|
))}
|
|
18
18
|
</tr>
|
|
19
19
|
</thead>
|
|
20
|
-
<tbody className="divide-y divide-
|
|
20
|
+
<tbody className="divide-y divide-ods-border">
|
|
21
21
|
{skeletonRows.map((_, idx) => (
|
|
22
22
|
<tr key={idx}>
|
|
23
23
|
{Array.from({ length: 5 }).map((__, cell) => (
|
|
@@ -46,7 +46,7 @@ const LogCard: React.FC<{
|
|
|
46
46
|
{log.title}
|
|
47
47
|
</p>
|
|
48
48
|
<div className="flex items-center gap-2">
|
|
49
|
-
<p className="font-['Azeret_Mono'] font-normal text-[13px] leading-4 text-
|
|
49
|
+
<p className="font-['Azeret_Mono'] font-normal text-[13px] leading-4 text-ods-text-secondary uppercase tracking-wider">
|
|
50
50
|
{formatTimestamp(log.timestamp)}
|
|
51
51
|
</p>
|
|
52
52
|
{log.toolType && (
|
|
@@ -250,7 +250,7 @@ export const MediaCarousel = memo(function MediaCarousel({
|
|
|
250
250
|
<div
|
|
251
251
|
ref={carouselRef}
|
|
252
252
|
className={cn(
|
|
253
|
-
"relative bg-
|
|
253
|
+
"relative bg-ods-bg border border-ods-border rounded-2xl overflow-hidden group w-full",
|
|
254
254
|
getAspectRatioClass()
|
|
255
255
|
)}
|
|
256
256
|
onTouchStart={onTouchStart}
|
|
@@ -151,6 +151,24 @@ const appLayoutDrawerPanelVariants = cva(
|
|
|
151
151
|
|
|
152
152
|
const HORIZONTAL_SIDES: ReadonlySet<DrawerSide> = new Set(["left", "right"])
|
|
153
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Persist mode (`forceMount`) keeps the panel mounted across close, so the
|
|
156
|
+
* `slide-out-*` / `fade-out` exit animations run and then — because
|
|
157
|
+
* `tailwindcss-animate`'s `.animate-out` does NOT set `animation-fill-mode:
|
|
158
|
+
* forwards` — the panel/overlay snap back to their resting frame (on-screen,
|
|
159
|
+
* opacity 1). The element ends up fully visible again, which reads as "the
|
|
160
|
+
* drawer won't close" (and dims the whole content area via the backdrop).
|
|
161
|
+
*
|
|
162
|
+
* Pinning `fill-mode: forwards` for the closed state holds the exit animation's
|
|
163
|
+
* final frame (off-screen / transparent) after it ends. It can't be replaced by
|
|
164
|
+
* a static resting transform/opacity: that would override the animation's
|
|
165
|
+
* `from` value (= current computed style) and collapse the animation to a no-op.
|
|
166
|
+
*
|
|
167
|
+
* Not applied without persist — Radix unmounts `Content` after the animation,
|
|
168
|
+
* so there's nothing to snap back.
|
|
169
|
+
*/
|
|
170
|
+
const PERSIST_CLOSED_HOLD = "data-[state=closed]:fill-mode-forwards"
|
|
171
|
+
|
|
154
172
|
function clamp(value: number, min: number, max: number): number {
|
|
155
173
|
if (max < min) return min
|
|
156
174
|
return Math.min(max, Math.max(min, value))
|
|
@@ -321,12 +339,12 @@ function AppLayoutDrawerResizeHandle({
|
|
|
321
339
|
|
|
322
340
|
const trackPosition =
|
|
323
341
|
side === "right"
|
|
324
|
-
? "right-full top-4 bottom-4 w-
|
|
342
|
+
? "right-full top-4 bottom-4 w-12 items-center justify-end pr-1"
|
|
325
343
|
: side === "left"
|
|
326
|
-
? "left-full top-4 bottom-4 w-
|
|
344
|
+
? "left-full top-4 bottom-4 w-12 items-center justify-start pl-1"
|
|
327
345
|
: side === "bottom"
|
|
328
|
-
? "bottom-full left-4 right-4 h-
|
|
329
|
-
: "top-full left-4 right-4 h-
|
|
346
|
+
? "bottom-full left-4 right-4 h-12 justify-center items-end pb-1"
|
|
347
|
+
: "top-full left-4 right-4 h-12 justify-center items-start pt-1"
|
|
330
348
|
|
|
331
349
|
const cursorClass = isHorizontal ? "cursor-col-resize" : "cursor-row-resize"
|
|
332
350
|
const gripClass = isHorizontal ? "h-10 w-1" : "w-10 h-1"
|
|
@@ -388,6 +406,13 @@ export interface AppLayoutDrawerContentProps
|
|
|
388
406
|
/** Override the portal container. Defaults to AppLayout's main-area container
|
|
389
407
|
* (from context). Pass `null` to opt out of portaling. */
|
|
390
408
|
container?: HTMLElement | null
|
|
409
|
+
/** Keep the panel mounted across close so its subtree state survives an
|
|
410
|
+
* open → close → open cycle (no remount, no refetch, no flicker). Lazy:
|
|
411
|
+
* nothing renders until the first open; after that the panel stays mounted
|
|
412
|
+
* (Radix `forceMount`) and the slide animation runs off `data-state`.
|
|
413
|
+
* Default `false` — preserves the standard unmount-on-close behaviour for
|
|
414
|
+
* every other consumer. */
|
|
415
|
+
keepMounted?: boolean
|
|
391
416
|
/** When `true` (default), clicks on the dim overlay over the main content
|
|
392
417
|
* area close the drawer. Clicks on AppLayout chrome (header, sidebar) NEVER
|
|
393
418
|
* close the drawer regardless of this flag — chrome interactions shouldn't
|
|
@@ -430,6 +455,7 @@ const AppLayoutDrawerContent = React.forwardRef<
|
|
|
430
455
|
dismissOnInteractOutside = true,
|
|
431
456
|
onInteractOutside,
|
|
432
457
|
debugLayoutShift = false,
|
|
458
|
+
keepMounted = false,
|
|
433
459
|
children,
|
|
434
460
|
...props
|
|
435
461
|
},
|
|
@@ -439,6 +465,20 @@ const AppLayoutDrawerContent = React.forwardRef<
|
|
|
439
465
|
const portalContainer = container !== undefined ? container : contextContainer
|
|
440
466
|
const open = React.useContext(DrawerOpenContext)
|
|
441
467
|
|
|
468
|
+
// Persist mode: lazily mount on the first open, then never unmount. Radix
|
|
469
|
+
// unmounts `Dialog.Content` on close by default, which would wipe the
|
|
470
|
+
// panel's subtree state (and trigger a full refetch) on every reopen.
|
|
471
|
+
// `forceMount` keeps it mounted; `hasOpened` defers that until the user
|
|
472
|
+
// opens the panel at least once (no work, no exit-animation flash before
|
|
473
|
+
// first open).
|
|
474
|
+
// Seed from `open` so an initially-open (defaultOpen / controlled open=true)
|
|
475
|
+
// drawer mounts on the first paint instead of flashing `null` for one frame.
|
|
476
|
+
const [hasOpened, setHasOpened] = React.useState<boolean>(Boolean(open))
|
|
477
|
+
React.useEffect(() => {
|
|
478
|
+
if (open) setHasOpened(true)
|
|
479
|
+
}, [open])
|
|
480
|
+
const persist = keepMounted && hasOpened
|
|
481
|
+
|
|
442
482
|
// Diagnostic: walk ancestors and snapshot horizontal-scroll metrics
|
|
443
483
|
// around the open transition. See `debugLayoutShift` prop doc.
|
|
444
484
|
React.useEffect(() => {
|
|
@@ -533,8 +573,12 @@ const AppLayoutDrawerContent = React.forwardRef<
|
|
|
533
573
|
: { height: size }
|
|
534
574
|
: {}
|
|
535
575
|
|
|
576
|
+
// Persist mode, pre-first-open: render nothing so the panel mounts lazily.
|
|
577
|
+
// (All hooks above run unconditionally — this early return is hooks-safe.)
|
|
578
|
+
if (keepMounted && !hasOpened) return null
|
|
579
|
+
|
|
536
580
|
return (
|
|
537
|
-
<DialogPrimitive.Portal container={portalContainer}>
|
|
581
|
+
<DialogPrimitive.Portal container={portalContainer} forceMount={persist || undefined}>
|
|
538
582
|
{/* Overlay rendered manually: Radix's DialogPrimitive.Overlay returns
|
|
539
583
|
null in non-modal mode, so we render a plain div here. Setting
|
|
540
584
|
`pointer-events-auto` ensures it catches clicks on the main area
|
|
@@ -546,17 +590,32 @@ const AppLayoutDrawerContent = React.forwardRef<
|
|
|
546
590
|
data-state={open ? "open" : "closed"}
|
|
547
591
|
className={cn(
|
|
548
592
|
"absolute inset-0 z-[40] bg-[color-mix(in_srgb,var(--ods-system-greys-background)_50%,transparent)] outline-none data-[state=open]:pointer-events-auto data-[state=closed]:pointer-events-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
593
|
+
// Persist mode only: hold the fade-out's final (transparent) frame
|
|
594
|
+
// so the backdrop doesn't snap back to opacity 1 and dim the whole
|
|
595
|
+
// content area while the drawer is "closed" (see PERSIST_CLOSED_HOLD).
|
|
596
|
+
persist && PERSIST_CLOSED_HOLD,
|
|
549
597
|
overlayClassName,
|
|
550
598
|
)}
|
|
551
599
|
/>
|
|
552
600
|
<DialogPrimitive.Content
|
|
553
601
|
ref={ref}
|
|
602
|
+
forceMount={persist || undefined}
|
|
603
|
+
// Persist mode: the panel stays mounted while closed (slid off-screen
|
|
604
|
+
// by the exit animation). Make sure that off-screen content can't
|
|
605
|
+
// catch clicks or be reached by tab/screen-reader.
|
|
606
|
+
{...(persist && !open
|
|
607
|
+
? { inert: true as const, "aria-hidden": true }
|
|
608
|
+
: {})}
|
|
554
609
|
className={cn(
|
|
555
610
|
appLayoutDrawerVariants({ side, flush }),
|
|
556
611
|
// Mobile: span the whole container and drop the outer gap so the
|
|
557
612
|
// panel renders edge-to-edge full-screen. (`p-0` overrides the
|
|
558
613
|
// variant's wrapper padding via tailwind-merge.)
|
|
559
614
|
isMobile && "inset-0 p-0",
|
|
615
|
+
"data-[state=closed]:pointer-events-none",
|
|
616
|
+
// Persist mode only: hold the slide-out's final (off-screen) frame
|
|
617
|
+
// so the panel doesn't snap back into view (see PERSIST_CLOSED_HOLD).
|
|
618
|
+
persist && PERSIST_CLOSED_HOLD,
|
|
560
619
|
)}
|
|
561
620
|
style={style}
|
|
562
621
|
onInteractOutside={(event) => {
|
|
@@ -576,7 +635,12 @@ const AppLayoutDrawerContent = React.forwardRef<
|
|
|
576
635
|
}}
|
|
577
636
|
{...props}
|
|
578
637
|
>
|
|
579
|
-
{
|
|
638
|
+
{/* The resize grip sits just OUTSIDE the panel's inner edge
|
|
639
|
+
(`right-full` etc.), so the wrapper's `translateX(100%)` close
|
|
640
|
+
transform doesn't carry it off-screen — it stays as a ~12px sliver
|
|
641
|
+
at the container edge. In persist mode it's also non-interactive
|
|
642
|
+
while closed, so drop it entirely until the panel reopens. */}
|
|
643
|
+
{applyInlineSize && (!persist || open) ? (
|
|
580
644
|
<AppLayoutDrawerResizeHandle
|
|
581
645
|
side={resolvedSide}
|
|
582
646
|
size={size}
|
|
@@ -13,7 +13,7 @@ const FeatureCard: React.FC<FeatureCardProps> = ({ icon, title, description }) =
|
|
|
13
13
|
return (
|
|
14
14
|
<div className="bg-ods-card border border-ods-border rounded-3xl p-6 flex flex-col gap-6 h-full hover:bg-[#252525] transition-colors duration-200">
|
|
15
15
|
{/* Icon Container */}
|
|
16
|
-
<div className="w-12 h-12 bg-
|
|
16
|
+
<div className="w-12 h-12 bg-ods-bg border border-ods-border rounded flex items-center justify-center">
|
|
17
17
|
<div className="w-6 h-6 text-ods-text-secondary">
|
|
18
18
|
{icon}
|
|
19
19
|
</div>
|
|
@@ -57,7 +57,7 @@ const OpenSourceFeatures: React.FC = () => {
|
|
|
57
57
|
];
|
|
58
58
|
|
|
59
59
|
return (
|
|
60
|
-
<section className="w-full bg-
|
|
60
|
+
<section className="w-full bg-ods-bg py-12 md:py-16 lg:py-20">
|
|
61
61
|
<div className="w-full max-w-[1920px] mx-auto px-6 md:px-20">
|
|
62
62
|
{/* Section Title */}
|
|
63
63
|
<div className="flex flex-col items-center gap-10">
|