@flamingo-stack/openframe-frontend-core 0.0.202 → 0.0.203-snapshot.20260522034243
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-OII2IERE.cjs → chunk-25LVV26X.cjs} +4 -4
- package/dist/chunk-25LVV26X.cjs.map +1 -0
- package/dist/{chunk-55HF462A.js → chunk-CPXLQ57U.js} +6 -7
- package/dist/chunk-CPXLQ57U.js.map +1 -0
- package/dist/{chunk-JIKTMXTZ.cjs → chunk-QQONFWAN.cjs} +945 -784
- package/dist/chunk-QQONFWAN.cjs.map +1 -0
- package/dist/{chunk-3B43AHYE.cjs → chunk-RMB5DVED.cjs} +6 -7
- package/dist/chunk-RMB5DVED.cjs.map +1 -0
- package/dist/{chunk-4ML3NA2L.js → chunk-XGL5FKIK.js} +4 -4
- package/dist/chunk-XGL5FKIK.js.map +1 -0
- package/dist/{chunk-IDULPYOU.js → chunk-ZXDILOFR.js} +1947 -1786
- package/dist/chunk-ZXDILOFR.js.map +1 -0
- package/dist/components/chat/chat-ticket-item.d.ts.map +1 -1
- package/dist/components/features/index.cjs +4 -4
- package/dist/components/features/index.js +3 -3
- package/dist/components/features/select-button.d.ts.map +1 -1
- package/dist/components/index.cjs +6 -4
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +5 -3
- package/dist/components/navigation/index.cjs +4 -4
- package/dist/components/navigation/index.js +3 -3
- package/dist/components/navigation/navigation-sidebar.d.ts.map +1 -1
- package/dist/components/resizable.d.ts +1 -1
- package/dist/components/shared/product-release/product-release-card-skeleton.d.ts.map +1 -1
- package/dist/components/shared/product-release/product-release-card.d.ts.map +1 -1
- package/dist/components/ui/button/split-button.d.ts.map +1 -1
- package/dist/components/ui/data-table/data-table-row.d.ts +16 -4
- package/dist/components/ui/data-table/data-table-row.d.ts.map +1 -1
- package/dist/components/ui/file-manager/index.cjs +52 -52
- package/dist/components/ui/file-manager/index.cjs.map +1 -1
- package/dist/components/ui/file-manager/index.js +3 -3
- package/dist/components/ui/file-manager/index.js.map +1 -1
- package/dist/components/ui/floating-tooltip.d.ts +3 -1
- package/dist/components/ui/floating-tooltip.d.ts.map +1 -1
- package/dist/components/ui/index.cjs +6 -4
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.d.ts +1 -0
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/index.js +5 -3
- package/dist/components/ui/input-trigger.d.ts.map +1 -1
- package/dist/components/ui/radio-group.d.ts.map +1 -1
- package/dist/components/ui/ticket-info-section.d.ts.map +1 -1
- package/dist/components/ui/ticket-note-card.d.ts.map +1 -1
- package/dist/components/ui/truncate-text.d.ts +33 -0
- package/dist/components/ui/truncate-text.d.ts.map +1 -0
- package/dist/components/user-summary-stub.d.ts.map +1 -1
- package/dist/hooks/index.cjs +2 -2
- package/dist/hooks/index.js +1 -1
- package/dist/index.cjs +6 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +5 -3
- package/package.json +1 -1
- package/src/components/chat/chat-container.tsx +2 -2
- package/src/components/chat/chat-ticket-item.tsx +2 -3
- package/src/components/features/board/ticket-card.tsx +2 -2
- package/src/components/features/filters-dropdown.tsx +1 -1
- package/src/components/features/notifications/notification-tile.tsx +2 -2
- package/src/components/features/policy-configuration-panel.tsx +1 -1
- package/src/components/features/push-button-selector.tsx +1 -1
- package/src/components/features/select-button.tsx +2 -3
- package/src/components/features/video-bites-display.tsx +1 -1
- package/src/components/features/waitlist-form.tsx +1 -1
- package/src/components/filter-chip.tsx +1 -1
- package/src/components/layout/title-block.tsx +2 -2
- package/src/components/navigation/header-organization-filter.tsx +1 -1
- package/src/components/navigation/navigation-sidebar.tsx +107 -54
- package/src/components/platform/ScriptInfoSection.tsx +1 -1
- package/src/components/shared/onboarding/onboarding-step-card.tsx +2 -2
- package/src/components/shared/product-release/product-release-card-skeleton.tsx +58 -26
- package/src/components/shared/product-release/product-release-card.tsx +170 -133
- package/src/components/shared/product-release/release-detail-page.tsx +1 -1
- package/src/components/ui/assignee-dropdown.tsx +3 -3
- package/src/components/ui/autocomplete.tsx +2 -2
- package/src/components/ui/button/split-button.tsx +3 -5
- package/src/components/ui/checkbox-block.tsx +1 -1
- package/src/components/ui/data-table/data-table-row.tsx +82 -48
- package/src/components/ui/device-card-compact.tsx +2 -2
- package/src/components/ui/device-card.tsx +2 -2
- package/src/components/ui/entity-image.tsx +1 -1
- package/src/components/ui/field-wrapper.tsx +1 -1
- package/src/components/ui/file-manager/file-manager-table-row.tsx +2 -2
- package/src/components/ui/file-upload.tsx +2 -2
- package/src/components/ui/filter-list.tsx +1 -1
- package/src/components/ui/floating-tooltip.tsx +9 -5
- package/src/components/ui/hidden-tags-popup.tsx +1 -1
- package/src/components/ui/index.ts +1 -0
- package/src/components/ui/info-card.tsx +2 -2
- package/src/components/ui/input-trigger.tsx +1 -2
- package/src/components/ui/organization-card.tsx +3 -3
- package/src/components/ui/radio-group.tsx +2 -3
- package/src/components/ui/search-input.tsx +2 -2
- package/src/components/ui/service-card.tsx +3 -3
- package/src/components/ui/tag.tsx +1 -1
- package/src/components/ui/tags-manager.tsx +2 -2
- package/src/components/ui/ticket-attachments-list.tsx +1 -1
- package/src/components/ui/ticket-info-section.tsx +2 -3
- package/src/components/ui/ticket-note-card.tsx +4 -1
- package/src/components/ui/toaster.tsx +3 -3
- package/src/components/ui/truncate-text.tsx +116 -0
- package/src/components/user-summary-stub.tsx +32 -26
- package/src/components/vendor-display-button.tsx +1 -1
- package/src/stories/SplitButton.stories.tsx +7 -1
- package/dist/chunk-3B43AHYE.cjs.map +0 -1
- package/dist/chunk-4ML3NA2L.js.map +0 -1
- package/dist/chunk-55HF462A.js.map +0 -1
- package/dist/chunk-IDULPYOU.js.map +0 -1
- package/dist/chunk-JIKTMXTZ.cjs.map +0 -1
- package/dist/chunk-OII2IERE.cjs.map +0 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
|
-
import { useCallback, useLayoutEffect, useMemo, useState } from 'react'
|
|
3
|
+
import { useCallback, useEffect, useLayoutEffect, useMemo, useState } from 'react'
|
|
4
4
|
import { useLocalStorage } from '../../hooks/ui/use-local-storage'
|
|
5
|
-
import { useLgUp } from '../../hooks/ui/use-media-query'
|
|
5
|
+
import { useLgUp, useMdUp } from '../../hooks/ui/use-media-query'
|
|
6
6
|
import { NavigationSidebarConfig, NavigationSidebarItem } from '../../types/navigation'
|
|
7
7
|
import { cn } from '../../utils'
|
|
8
8
|
import { NavigationSidebarHeader } from './navigation-sidebar-header'
|
|
@@ -23,25 +23,57 @@ export interface NavigationSidebarProps {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export function NavigationSidebar({ config, disabled = false }: NavigationSidebarProps) {
|
|
26
|
+
const isMdUp = useMdUp() ?? false
|
|
26
27
|
const isLgUp = useLgUp() ?? false
|
|
27
28
|
|
|
28
|
-
//
|
|
29
|
-
|
|
29
|
+
// Tablet = md viewport but not lg. On tablet the sidebar floats over the
|
|
30
|
+
// content area (overlay) instead of pushing it like on desktop.
|
|
31
|
+
const isTablet = isMdUp && !isLgUp
|
|
32
|
+
|
|
33
|
+
// Desktop preference persists across sessions. Tablet state is in-memory
|
|
34
|
+
// only so entering tablet always starts minimized without clobbering the
|
|
35
|
+
// user's desktop choice.
|
|
36
|
+
const [desktopMinimized, setDesktopMinimized] = useLocalStorage<boolean>(
|
|
30
37
|
STORAGE_KEY,
|
|
31
|
-
|
|
38
|
+
config.minimized ?? false,
|
|
32
39
|
)
|
|
40
|
+
const [tabletMinimized, setTabletMinimized] = useState(true)
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (isTablet) setTabletMinimized(true)
|
|
44
|
+
}, [isTablet])
|
|
45
|
+
|
|
46
|
+
const minimized = isTablet ? tabletMinimized : desktopMinimized
|
|
33
47
|
|
|
34
48
|
// Enable transitions only after the correct width is painted
|
|
35
49
|
const [transitionsEnabled, setTransitionsEnabled] = useState(false)
|
|
36
50
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const showLabel =
|
|
51
|
+
const isOverlayOpen = isTablet && !minimized
|
|
52
|
+
|
|
53
|
+
const showLabel = !minimized
|
|
40
54
|
|
|
41
55
|
const handleToggle = useCallback(() => {
|
|
42
|
-
|
|
56
|
+
if (isTablet) {
|
|
57
|
+
setTabletMinimized(prev => !prev)
|
|
58
|
+
} else {
|
|
59
|
+
setDesktopMinimized(prev => !prev)
|
|
60
|
+
}
|
|
43
61
|
config.onToggleMinimized?.()
|
|
44
|
-
}, [
|
|
62
|
+
}, [isTablet, setDesktopMinimized, config])
|
|
63
|
+
|
|
64
|
+
const closeOverlay = useCallback(() => {
|
|
65
|
+
setTabletMinimized(true)
|
|
66
|
+
}, [])
|
|
67
|
+
|
|
68
|
+
// Dismiss the tablet overlay with Escape so it behaves like a transient panel
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (!isOverlayOpen) return
|
|
71
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
72
|
+
if (e.key === 'Escape') closeOverlay()
|
|
73
|
+
}
|
|
74
|
+
document.addEventListener('keydown', handleKeyDown)
|
|
75
|
+
return () => document.removeEventListener('keydown', handleKeyDown)
|
|
76
|
+
}, [isOverlayOpen, closeOverlay])
|
|
45
77
|
|
|
46
78
|
const handleItemClick = useCallback((item: NavigationSidebarItem, event?: React.MouseEvent) => {
|
|
47
79
|
event?.stopPropagation()
|
|
@@ -58,14 +90,12 @@ export function NavigationSidebar({ config, disabled = false }: NavigationSideba
|
|
|
58
90
|
secondaryItems: config.items.filter(item => item.section === 'secondary'),
|
|
59
91
|
}), [config.items])
|
|
60
92
|
|
|
61
|
-
const sidebarWidth = useMemo(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
return isMinimized ? `${MINIMIZED_WIDTH}px` : `${EXPANDED_WIDTH}px`
|
|
66
|
-
}, [isLgUp, isMinimized])
|
|
93
|
+
const sidebarWidth = useMemo(
|
|
94
|
+
() => (minimized ? `${MINIMIZED_WIDTH}px` : `${EXPANDED_WIDTH}px`),
|
|
95
|
+
[minimized],
|
|
96
|
+
)
|
|
67
97
|
|
|
68
|
-
const isHydrated = isLgUp !== undefined
|
|
98
|
+
const isHydrated = isMdUp !== undefined && isLgUp !== undefined
|
|
69
99
|
|
|
70
100
|
useLayoutEffect(() => {
|
|
71
101
|
if (isHydrated && !transitionsEnabled) {
|
|
@@ -77,36 +107,47 @@ export function NavigationSidebar({ config, disabled = false }: NavigationSideba
|
|
|
77
107
|
}, [isHydrated, transitionsEnabled])
|
|
78
108
|
|
|
79
109
|
return (
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
110
|
+
<>
|
|
111
|
+
{/* Backdrop scrim — only visible on tablet while the overlay is open */}
|
|
112
|
+
<div
|
|
113
|
+
className={cn(
|
|
114
|
+
"fixed inset-0 z-[40] bg-black/50",
|
|
115
|
+
"hidden md:block lg:hidden",
|
|
116
|
+
"transition-opacity duration-300",
|
|
117
|
+
isOverlayOpen ? "opacity-100" : "opacity-0 pointer-events-none",
|
|
118
|
+
)}
|
|
119
|
+
onClick={closeOverlay}
|
|
120
|
+
aria-hidden="true"
|
|
121
|
+
/>
|
|
122
|
+
|
|
123
|
+
{/* Flex-flow placeholder — reserves the collapsed 56px slot on tablet so
|
|
124
|
+
the main content keeps its position while the sidebar floats above it */}
|
|
125
|
+
{isTablet && (
|
|
126
|
+
<div
|
|
127
|
+
className="h-full hidden md:block flex-shrink-0"
|
|
128
|
+
style={{ width: `${MINIMIZED_WIDTH}px` }}
|
|
129
|
+
aria-hidden="true"
|
|
130
|
+
/>
|
|
86
131
|
)}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
{secondaryItems.length > 0 && (
|
|
108
|
-
<nav className="flex flex-col" aria-label="Secondary navigation">
|
|
109
|
-
{secondaryItems.map(item => (
|
|
132
|
+
|
|
133
|
+
<aside
|
|
134
|
+
className={cn(
|
|
135
|
+
"flex-col hidden md:flex flex-shrink-0",
|
|
136
|
+
"bg-ods-card border-r border-ods-border",
|
|
137
|
+
isTablet ? "fixed top-0 left-0 h-screen z-[45]" : "relative h-full",
|
|
138
|
+
transitionsEnabled && "transition-[width] duration-300",
|
|
139
|
+
config.className,
|
|
140
|
+
)}
|
|
141
|
+
style={{ width: sidebarWidth }}
|
|
142
|
+
aria-label="Main navigation sidebar"
|
|
143
|
+
>
|
|
144
|
+
{isHydrated && (
|
|
145
|
+
<>
|
|
146
|
+
<NavigationSidebarHeader minimized={minimized} />
|
|
147
|
+
|
|
148
|
+
<div className="flex-1 flex flex-col justify-between py-4 overflow-y-auto">
|
|
149
|
+
<nav className="flex flex-col" aria-label="Primary navigation">
|
|
150
|
+
{primaryItems.map(item => (
|
|
110
151
|
<NavigationSidebarItemButton
|
|
111
152
|
key={item.id}
|
|
112
153
|
item={item}
|
|
@@ -116,18 +157,30 @@ export function NavigationSidebar({ config, disabled = false }: NavigationSideba
|
|
|
116
157
|
/>
|
|
117
158
|
))}
|
|
118
159
|
</nav>
|
|
119
|
-
)}
|
|
120
|
-
</div>
|
|
121
160
|
|
|
122
|
-
|
|
161
|
+
{secondaryItems.length > 0 && (
|
|
162
|
+
<nav className="flex flex-col" aria-label="Secondary navigation">
|
|
163
|
+
{secondaryItems.map(item => (
|
|
164
|
+
<NavigationSidebarItemButton
|
|
165
|
+
key={item.id}
|
|
166
|
+
item={item}
|
|
167
|
+
showLabel={showLabel}
|
|
168
|
+
disabled={disabled}
|
|
169
|
+
onClick={handleItemClick}
|
|
170
|
+
/>
|
|
171
|
+
))}
|
|
172
|
+
</nav>
|
|
173
|
+
)}
|
|
174
|
+
</div>
|
|
175
|
+
|
|
123
176
|
<NavigationSidebarToggle
|
|
124
|
-
minimized={
|
|
177
|
+
minimized={minimized}
|
|
125
178
|
showLabel={showLabel}
|
|
126
179
|
onToggle={handleToggle}
|
|
127
180
|
/>
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
181
|
+
</>
|
|
182
|
+
)}
|
|
183
|
+
</aside>
|
|
184
|
+
</>
|
|
132
185
|
)
|
|
133
186
|
}
|
|
@@ -100,7 +100,7 @@ function InfoCell({ label, value, avatar, className }: InfoCellProps) {
|
|
|
100
100
|
|
|
101
101
|
{/* Text content */}
|
|
102
102
|
<div className="flex flex-col min-w-0">
|
|
103
|
-
<span className="text-ods-text-primary font-['DM_Sans'] text-[14px] leading-[20px] md:text-[18px] md:leading-[24px] font-medium truncate">
|
|
103
|
+
<span className="text-ods-text-primary font-['DM_Sans'] text-[14px] leading-[20px] md:text-[18px] md:leading-[24px] font-medium truncate" title={value}>
|
|
104
104
|
{value}
|
|
105
105
|
</span>
|
|
106
106
|
<span className="text-ods-text-secondary font-['DM_Sans'] text-[12px] leading-[16px] md:text-[14px] md:leading-[20px] font-medium truncate">
|
|
@@ -66,10 +66,10 @@ export function OnboardingStepCard({
|
|
|
66
66
|
>
|
|
67
67
|
{/* Left column - content */}
|
|
68
68
|
<div className="flex-1 w-full md:w-auto min-w-0 flex flex-col justify-center gap-1">
|
|
69
|
-
<h3 className="text-h4 text-ods-text-primary truncate">
|
|
69
|
+
<h3 className="text-h4 text-ods-text-primary truncate" title={step.title}>
|
|
70
70
|
{step.title}
|
|
71
71
|
</h3>
|
|
72
|
-
<p className="font-['DM_Sans'] font-medium text-[14px] leading-[20px] text-ods-text-secondary truncate h-[20px]">
|
|
72
|
+
<p className="font-['DM_Sans'] font-medium text-[14px] leading-[20px] text-ods-text-secondary truncate h-[20px]" title={step.description}>
|
|
73
73
|
{step.description}
|
|
74
74
|
</p>
|
|
75
75
|
</div>
|
|
@@ -15,15 +15,12 @@ export function ProductReleaseCardSkeleton({ className, size = 'default' }: Prod
|
|
|
15
15
|
// ----- CATALOG branch — must match ProductReleaseCard size='catalog'.
|
|
16
16
|
// Same outer frame (`bg-ods-system-greys-black border border-ods-border …
|
|
17
17
|
// p-6 gap-4`). Inner: hero (16:9 cover + version pill + title + summary),
|
|
18
|
-
// changelog strip placeholder
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
// (rare — most releases have at least one feature/fix/improvement).
|
|
25
|
-
// The skeleton always renders the placeholder; net effect is a ~28px
|
|
26
|
-
// shrink on empty-changelog releases. Documented tradeoff.
|
|
18
|
+
// changelog strip placeholder, metadata-grid footer (4 cells via grid).
|
|
19
|
+
// Heights chosen to match the loaded card's rendered metrics so the
|
|
20
|
+
// 5-card slot grid in `ReleasesList` doesn't jump on resolve. The
|
|
21
|
+
// loaded card ALSO always renders the changelog strip + a fixed 4-cell
|
|
22
|
+
// grid (with em-dash placeholders for missing values), so this
|
|
23
|
+
// skeleton's shape matches exactly with zero load-to-resolve reflow.
|
|
27
24
|
if (size === 'catalog') {
|
|
28
25
|
return (
|
|
29
26
|
<div
|
|
@@ -34,27 +31,62 @@ export function ProductReleaseCardSkeleton({ className, size = 'default' }: Prod
|
|
|
34
31
|
className,
|
|
35
32
|
)}
|
|
36
33
|
>
|
|
37
|
-
{/* HERO
|
|
34
|
+
{/* HERO — placeholders use `bg-ods-border` (#3a3a3a) so they
|
|
35
|
+
contrast against the card's `bg-ods-system-greys-black`
|
|
36
|
+
(#212121) container. The metadata grid cells below use
|
|
37
|
+
`bg-ods-card` containers so `bg-ods-bg` placeholders work
|
|
38
|
+
there, but in the hero the card IS `bg-ods-card`-equivalent —
|
|
39
|
+
`bg-ods-bg` (#161616) is only 6 hex points darker than the
|
|
40
|
+
card and renders nearly invisible.
|
|
41
|
+
|
|
42
|
+
CRITICAL: title + summary use the SAME min-h containers as
|
|
43
|
+
the loaded card so total card height is byte-identical
|
|
44
|
+
between skeleton state and loaded state. Without this,
|
|
45
|
+
individual placeholder heights underrun the loaded card's
|
|
46
|
+
min-h reservations and the page jumps on resolve. */}
|
|
38
47
|
<div className="flex flex-col md:flex-row gap-4 md:gap-6">
|
|
39
|
-
<div className="w-full md:w-[256px] aspect-[16/9] bg-ods-
|
|
48
|
+
<div className="w-full md:w-[256px] aspect-[16/9] bg-ods-border rounded-lg flex-shrink-0" />
|
|
40
49
|
<div className="flex-1 min-w-0 flex flex-col">
|
|
41
|
-
{/* Version pill
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
<div className="
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
50
|
+
{/* Version pill — mirrors `flex items-center gap-3 mb-3` in
|
|
51
|
+
the loaded card. The loaded `<span text-lg>` renders at
|
|
52
|
+
line-height 28 px (Tailwind text-lg = 18 px / 28 px LH);
|
|
53
|
+
placeholder uses `h-7` (28 px) to match exactly. */}
|
|
54
|
+
<div className="flex items-center gap-3 mb-3">
|
|
55
|
+
<div className="h-7 w-20 bg-ods-border rounded" />
|
|
56
|
+
</div>
|
|
57
|
+
{/* Title container — SAME min-h as the loaded card so the
|
|
58
|
+
card height contributed by this region matches exactly. */}
|
|
59
|
+
<div className="min-h-[60px] md:min-h-[72px] flex flex-col gap-1.5 justify-start mb-3">
|
|
60
|
+
<div className="h-[25px] md:h-[30px] w-3/4 bg-ods-border rounded" />
|
|
61
|
+
<div className="h-[25px] md:h-[30px] w-1/2 bg-ods-border rounded" />
|
|
62
|
+
</div>
|
|
63
|
+
{/* Summary container — SAME min-h as the loaded card. The
|
|
64
|
+
3 placeholder lines mirror the rendered 3-line clamp;
|
|
65
|
+
`bg-ods-border/70` keeps summary placeholders slightly
|
|
66
|
+
dimmer than title placeholders (primary vs secondary
|
|
67
|
+
text hierarchy). */}
|
|
68
|
+
<div className="min-h-[68px] md:min-h-[78px] flex flex-col gap-2 justify-start">
|
|
69
|
+
<div className="h-3 w-full bg-ods-border/70 rounded" />
|
|
70
|
+
<div className="h-3 w-11/12 bg-ods-border/70 rounded" />
|
|
71
|
+
<div className="h-3 w-5/6 bg-ods-border/70 rounded" />
|
|
72
|
+
</div>
|
|
49
73
|
</div>
|
|
50
74
|
</div>
|
|
51
75
|
|
|
52
|
-
{/* CHANGELOG strip placeholder — always rendered
|
|
76
|
+
{/* CHANGELOG strip placeholder — always rendered. Inner
|
|
77
|
+
placeholder `h-5` mirrors the loaded strip's `text-sm`
|
|
78
|
+
line-height (20 px) so total height is consistent with the
|
|
79
|
+
loaded `border-t pt-3 + text content` (~32-33 px). */}
|
|
53
80
|
<div className="border-t border-ods-border pt-3">
|
|
54
|
-
<div className="h-
|
|
81
|
+
<div className="h-5 w-2/3 bg-ods-border/70 rounded" />
|
|
55
82
|
</div>
|
|
56
83
|
|
|
57
|
-
{/* METADATA GRID — 4-cell placeholder
|
|
84
|
+
{/* METADATA GRID — 4-cell placeholder. The grid cells use
|
|
85
|
+
`bg-ods-card` containers and `bg-ods-bg` placeholders, which
|
|
86
|
+
DO contrast correctly because the cells are brighter than
|
|
87
|
+
the placeholders. Inner content heights mirror the loaded
|
|
88
|
+
cells (`text-h4` ≈ 28 px + `DM_Sans 14px leading-20`) so
|
|
89
|
+
total grid height matches the loaded ~86 px. */}
|
|
58
90
|
<div className="grid grid-cols-1 md:grid-cols-4 border border-ods-border rounded-md overflow-hidden w-full">
|
|
59
91
|
{[0, 1, 2].map((i) => (
|
|
60
92
|
<div
|
|
@@ -62,8 +94,8 @@ export function ProductReleaseCardSkeleton({ className, size = 'default' }: Prod
|
|
|
62
94
|
className="bg-ods-card p-4 flex flex-col gap-3 border-b md:border-b-0 md:border-r border-ods-border"
|
|
63
95
|
>
|
|
64
96
|
<div className="flex flex-col gap-2">
|
|
65
|
-
<div className="h-
|
|
66
|
-
<div className="h-
|
|
97
|
+
<div className="h-7 w-24 bg-ods-bg rounded" />
|
|
98
|
+
<div className="h-4 w-16 bg-ods-bg/60 rounded" />
|
|
67
99
|
</div>
|
|
68
100
|
</div>
|
|
69
101
|
))}
|
|
@@ -71,8 +103,8 @@ export function ProductReleaseCardSkeleton({ className, size = 'default' }: Prod
|
|
|
71
103
|
<div className="bg-ods-card p-4 flex items-center gap-3">
|
|
72
104
|
<div className="h-10 w-10 rounded-full bg-ods-bg shrink-0" />
|
|
73
105
|
<div className="flex flex-col gap-2 flex-1 min-w-0">
|
|
74
|
-
<div className="h-
|
|
75
|
-
<div className="h-
|
|
106
|
+
<div className="h-5 w-3/4 bg-ods-bg rounded" />
|
|
107
|
+
<div className="h-4 w-1/2 bg-ods-bg/60 rounded" />
|
|
76
108
|
</div>
|
|
77
109
|
</div>
|
|
78
110
|
</div>
|