@flamingo-stack/openframe-frontend-core 0.0.466 → 0.0.467
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-XJH5ZT5Y.js → chunk-25YYEVSE.js} +2 -2
- package/dist/{chunk-CC73CSLI.js → chunk-3L44PPMF.js} +2 -2
- package/dist/{chunk-GRB7LPJV.js → chunk-4DFPQKKJ.js} +2 -2
- package/dist/{chunk-3WF7INYU.cjs → chunk-6DZ5QOUM.cjs} +11 -11
- package/dist/{chunk-3WF7INYU.cjs.map → chunk-6DZ5QOUM.cjs.map} +1 -1
- package/dist/{chunk-BVNAYWLM.cjs → chunk-72NEN4G4.cjs} +15 -15
- package/dist/{chunk-BVNAYWLM.cjs.map → chunk-72NEN4G4.cjs.map} +1 -1
- package/dist/{chunk-FHLJQI4F.js → chunk-ABU37QSG.js} +4 -4
- package/dist/{chunk-KX2TBXKN.js → chunk-BIDOIPIT.js} +2 -2
- package/dist/{chunk-IZCOYFCN.js → chunk-CZFPPNYV.js} +3921 -3876
- package/dist/chunk-CZFPPNYV.js.map +1 -0
- package/dist/{chunk-FGL4ZNR7.cjs → chunk-G2H4AKL3.cjs} +62 -62
- package/dist/{chunk-FGL4ZNR7.cjs.map → chunk-G2H4AKL3.cjs.map} +1 -1
- package/dist/{chunk-YN77BNI7.cjs → chunk-G5D7NRH5.cjs} +26 -26
- package/dist/{chunk-YN77BNI7.cjs.map → chunk-G5D7NRH5.cjs.map} +1 -1
- package/dist/{chunk-72HPXAMZ.js → chunk-GXXLJHQX.js} +2 -2
- package/dist/{chunk-RQZOCLTI.js → chunk-ILZ66CS6.js} +4 -4
- package/dist/{chunk-GHZMNDPN.js → chunk-LOWJWKM5.js} +4 -4
- package/dist/{chunk-7DULN25L.js → chunk-NYOQNYSI.js} +2 -2
- package/dist/{chunk-KLBBOLDK.cjs → chunk-O7LQYYUA.cjs} +5 -5
- package/dist/{chunk-KLBBOLDK.cjs.map → chunk-O7LQYYUA.cjs.map} +1 -1
- package/dist/{chunk-HDP5VRPL.cjs → chunk-QITN43HN.cjs} +7 -7
- package/dist/{chunk-HDP5VRPL.cjs.map → chunk-QITN43HN.cjs.map} +1 -1
- package/dist/{chunk-OO7DO4DI.js → chunk-QJ7NIL6A.js} +4 -4
- package/dist/{chunk-NHNKMR22.cjs → chunk-SGDXS7VT.cjs} +59 -59
- package/dist/{chunk-NHNKMR22.cjs.map → chunk-SGDXS7VT.cjs.map} +1 -1
- package/dist/{chunk-WAO34PFO.js → chunk-TZSPLSDU.js} +2 -2
- package/dist/{chunk-MQQ7VIG6.cjs → chunk-UWW4PQQS.cjs} +26 -26
- package/dist/{chunk-MQQ7VIG6.cjs.map → chunk-UWW4PQQS.cjs.map} +1 -1
- package/dist/{chunk-5H3HACZZ.cjs → chunk-VERK32MO.cjs} +37 -37
- package/dist/{chunk-5H3HACZZ.cjs.map → chunk-VERK32MO.cjs.map} +1 -1
- package/dist/{chunk-VLLFRPNP.cjs → chunk-WWC75D3J.cjs} +57 -12
- package/dist/chunk-WWC75D3J.cjs.map +1 -0
- package/dist/{chunk-2VCSDMPV.cjs → chunk-YGW7T57C.cjs} +31 -31
- package/dist/{chunk-2VCSDMPV.cjs.map → chunk-YGW7T57C.cjs.map} +1 -1
- package/dist/{chunk-YHCLDRMU.cjs → chunk-ZYPHZ3R3.cjs} +9 -9
- package/dist/{chunk-YHCLDRMU.cjs.map → chunk-ZYPHZ3R3.cjs.map} +1 -1
- package/dist/components/announcement-bar.d.ts +1 -1
- package/dist/components/announcement-bar.d.ts.map +1 -1
- package/dist/components/case-studies/index.cjs +8 -8
- package/dist/components/case-studies/index.js +2 -2
- package/dist/components/chat/index.cjs +2 -2
- package/dist/components/chat/index.js +1 -1
- package/dist/components/contact/index.cjs +3 -3
- package/dist/components/contact/index.js +2 -2
- package/dist/components/docs/index.cjs +5 -5
- package/dist/components/docs/index.js +4 -4
- package/dist/components/embeds/index.cjs +3 -3
- package/dist/components/embeds/index.js +2 -2
- package/dist/components/faq/index.cjs +3 -3
- package/dist/components/faq/index.js +2 -2
- package/dist/components/features/index.cjs +2 -2
- package/dist/components/features/index.js +1 -1
- package/dist/components/form.d.ts +1 -0
- package/dist/components/form.d.ts.map +1 -1
- package/dist/components/help-center-pages/index.cjs +20 -20
- package/dist/components/help-center-pages/index.js +11 -11
- package/dist/components/index.cjs +232 -206
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +117 -91
- package/dist/components/index.js.map +1 -1
- package/dist/components/navigation/index.cjs +2 -2
- package/dist/components/navigation/index.js +1 -1
- package/dist/components/onboarding-guides/index.cjs +5 -5
- package/dist/components/onboarding-guides/index.js +4 -4
- package/dist/components/related-content/index.cjs +3 -3
- package/dist/components/related-content/index.js +2 -2
- package/dist/components/tickets/index.cjs +5 -5
- package/dist/components/tickets/index.js +4 -4
- package/dist/components/ui/announcement-bar-view.d.ts +49 -0
- package/dist/components/ui/announcement-bar-view.d.ts.map +1 -0
- package/dist/components/ui/index.cjs +4 -2
- 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 +3 -1
- package/dist/index.cjs +4 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3 -1
- package/package.json +1 -1
- package/src/components/announcement-bar.tsx +102 -110
- package/src/components/ui/announcement-bar-view.tsx +94 -0
- package/src/components/ui/index.ts +1 -0
- package/src/styles/ods-colors.css +16 -0
- package/dist/chunk-IZCOYFCN.js.map +0 -1
- package/dist/chunk-VLLFRPNP.cjs.map +0 -1
- /package/dist/{chunk-XJH5ZT5Y.js.map → chunk-25YYEVSE.js.map} +0 -0
- /package/dist/{chunk-CC73CSLI.js.map → chunk-3L44PPMF.js.map} +0 -0
- /package/dist/{chunk-GRB7LPJV.js.map → chunk-4DFPQKKJ.js.map} +0 -0
- /package/dist/{chunk-FHLJQI4F.js.map → chunk-ABU37QSG.js.map} +0 -0
- /package/dist/{chunk-KX2TBXKN.js.map → chunk-BIDOIPIT.js.map} +0 -0
- /package/dist/{chunk-72HPXAMZ.js.map → chunk-GXXLJHQX.js.map} +0 -0
- /package/dist/{chunk-RQZOCLTI.js.map → chunk-ILZ66CS6.js.map} +0 -0
- /package/dist/{chunk-GHZMNDPN.js.map → chunk-LOWJWKM5.js.map} +0 -0
- /package/dist/{chunk-7DULN25L.js.map → chunk-NYOQNYSI.js.map} +0 -0
- /package/dist/{chunk-OO7DO4DI.js.map → chunk-QJ7NIL6A.js.map} +0 -0
- /package/dist/{chunk-WAO34PFO.js.map → chunk-TZSPLSDU.js.map} +0 -0
|
@@ -1,20 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
'use client';
|
|
2
2
|
|
|
3
|
-
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
4
3
|
import { X } from 'lucide-react';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
5
|
+
import { useEndpointsRuntime } from '../contexts/endpoints-runtime-context';
|
|
6
|
+
import { useSelfFetch } from '../hooks/use-self-fetch';
|
|
7
|
+
import type { Announcement, AnnouncementBarProps, AnnouncementResponse } from '../types/announcement';
|
|
8
|
+
import { ANNOUNCEMENT_CTA_DEFAULTS } from '../types/announcement';
|
|
7
9
|
import {
|
|
10
|
+
clearLegacyAnnouncementCache,
|
|
8
11
|
dismissAnnouncement,
|
|
9
12
|
isAnnouncementDismissed,
|
|
10
|
-
clearLegacyAnnouncementCache,
|
|
11
13
|
} from '../utils/announcement-storage';
|
|
12
|
-
import { ANNOUNCEMENT_CTA_DEFAULTS } from '../types/announcement';
|
|
13
|
-
import type { Announcement, AnnouncementBarProps, AnnouncementResponse } from '../types/announcement';
|
|
14
14
|
import { getAppType } from '../utils/app-config';
|
|
15
|
-
import { useEndpointsRuntime } from '../contexts/endpoints-runtime-context';
|
|
16
|
-
import { useSelfFetch } from '../hooks/use-self-fetch';
|
|
17
15
|
import { pickReadableTextColor } from '../utils/color-analysis';
|
|
16
|
+
import { EntityIcon } from './icon-display';
|
|
17
|
+
import { AnnouncementBarView } from './ui/announcement-bar-view';
|
|
18
|
+
import { Button } from './ui/button';
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Platform announcement bar — DUAL MODE, works with or without SSR.
|
|
@@ -41,11 +42,7 @@ import { pickReadableTextColor } from '../utils/color-analysis';
|
|
|
41
42
|
* storage reads happen ONLY in effects (a render-time read would desync
|
|
42
43
|
* hydration in SSR mode).
|
|
43
44
|
*/
|
|
44
|
-
export function AnnouncementBar({
|
|
45
|
-
initialAnnouncement,
|
|
46
|
-
previewMode = false,
|
|
47
|
-
className,
|
|
48
|
-
}: AnnouncementBarProps = {}) {
|
|
45
|
+
export function AnnouncementBar({ initialAnnouncement, previewMode = false, className }: AnnouncementBarProps = {}) {
|
|
49
46
|
// Namespace for the dismissal cookie/legacy keys. Next hosts inline
|
|
50
47
|
// NEXT_PUBLIC_APP_TYPE (matching the server's currentPlatform(), which the
|
|
51
48
|
// hub layout uses for the SSR cookie read); platform-agnostic embeds get
|
|
@@ -55,7 +52,7 @@ export function AnnouncementBar({
|
|
|
55
52
|
|
|
56
53
|
// Optional endpoint runtime: no provider → no URL → fetching disabled.
|
|
57
54
|
const endpoints = useEndpointsRuntime();
|
|
58
|
-
const url = previewMode ? null : endpoints?.announcementsUrl ?? null;
|
|
55
|
+
const url = previewMode ? null : (endpoints?.announcementsUrl ?? null);
|
|
59
56
|
|
|
60
57
|
// MUST be memoized (the hook re-syncs on [initialData] identity — an inline
|
|
61
58
|
// literal per render would setData-loop) and strict-undefined-mapped:
|
|
@@ -157,18 +154,20 @@ export function AnnouncementBar({
|
|
|
157
154
|
// dark-toned needs the dark theme's). `.theme-light` / `.theme-dark` are
|
|
158
155
|
// the design system's own scoping classes (ods-colors.css) and flip every
|
|
159
156
|
// Tier-1 `--ods-*` primitive for descendants.
|
|
160
|
-
const themeScope =
|
|
157
|
+
const themeScope =
|
|
158
|
+
pickReadableTextColor(displayAnnouncement.background_color) === 'dark' ? 'theme-light' : 'theme-dark';
|
|
161
159
|
|
|
162
|
-
// Inside the scope, colors reference Tier-
|
|
163
|
-
//
|
|
164
|
-
//
|
|
165
|
-
// theme
|
|
166
|
-
//
|
|
167
|
-
// ODS theme; nothing is hardcoded.
|
|
160
|
+
// Inside the scope, colors reference Tier-2 semantic aliases. Aliases
|
|
161
|
+
// declared only at :root would NOT re-resolve in a nested theme scope
|
|
162
|
+
// (they compute at :root), so ods-colors.css re-anchors these three inside
|
|
163
|
+
// `.theme-light` / `.theme-dark` specifically for theme islands like this
|
|
164
|
+
// bar. Every value comes from the active ODS theme; nothing is hardcoded.
|
|
168
165
|
const barButtonClasses =
|
|
169
|
-
'text-[color:var(--
|
|
166
|
+
'text-[color:var(--color-text-primary)] hover:bg-[var(--color-bg-hover)] active:bg-[var(--color-bg-active)]';
|
|
170
167
|
|
|
171
|
-
const hasCta = Boolean(
|
|
168
|
+
const hasCta = Boolean(
|
|
169
|
+
displayAnnouncement.cta_enabled && displayAnnouncement.cta_url && displayAnnouncement.cta_text,
|
|
170
|
+
);
|
|
172
171
|
|
|
173
172
|
return (
|
|
174
173
|
<div
|
|
@@ -184,32 +183,23 @@ export function AnnouncementBar({
|
|
|
184
183
|
style={{ gridTemplateRows: expanded ? '1fr' : '0fr' }}
|
|
185
184
|
>
|
|
186
185
|
{/*
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
186
|
+
Markup is the shared pure view (AnnouncementBarView, Figma
|
|
187
|
+
9364-40603 / 9418-43969 / 9418-44006): one row from `md` (800px) up,
|
|
188
|
+
stacked below it with the CTA stretched full-width next to the
|
|
189
|
+
content-width dismiss. The CTA Button is now rendered on EVERY
|
|
190
|
+
breakpoint (it replaced the old whole-bar mobile tap target, which
|
|
191
|
+
also retires the 768px window.innerWidth check that disagreed with
|
|
192
|
+
the md:800px breakpoint).
|
|
193
193
|
*/}
|
|
194
|
-
<div
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
onClick={(e) => {
|
|
204
|
-
// Only handle click on mobile (< 768px) and if CTA is enabled
|
|
205
|
-
if (window.innerWidth < 768 && hasCta) {
|
|
206
|
-
e.preventDefault();
|
|
207
|
-
handleCtaClick();
|
|
208
|
-
}
|
|
209
|
-
}}
|
|
210
|
-
>
|
|
211
|
-
{/* ONE unified icon path (shared with the chat): uploaded image URL
|
|
212
|
-
wins, else a library glyph by name (+ props), via <EntityIcon>. */}
|
|
194
|
+
<div
|
|
195
|
+
className={`min-h-0 overflow-hidden ${themeScope}`}
|
|
196
|
+
style={{ backgroundColor: displayAnnouncement.background_color }}
|
|
197
|
+
>
|
|
198
|
+
<AnnouncementBarView
|
|
199
|
+
className="text-[color:var(--color-text-primary)]"
|
|
200
|
+
startAdornment={
|
|
201
|
+
/* ONE unified icon path (shared with the chat): uploaded image URL
|
|
202
|
+
wins, else a library glyph by name (+ props), via <EntityIcon>. */
|
|
213
203
|
<EntityIcon
|
|
214
204
|
icon={{
|
|
215
205
|
name: displayAnnouncement.icon_name || 'openframe-logo',
|
|
@@ -217,72 +207,74 @@ export function AnnouncementBar({
|
|
|
217
207
|
props: displayAnnouncement.icon_props,
|
|
218
208
|
}}
|
|
219
209
|
size={24}
|
|
220
|
-
|
|
210
|
+
// `!` is required: logo glyphs (LogoOpenframeIcon / sizedLogo in
|
|
211
|
+
// icon-library) drive their size via an inline style, which beats
|
|
212
|
+
// a plain class — author !important beats the inline style, so
|
|
213
|
+
// the responsive token (16px < md, 24px from md) wins everywhere.
|
|
214
|
+
className="relative !size-[var(--icon-size-icon-size)] shrink-0"
|
|
221
215
|
/>
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
216
|
+
}
|
|
217
|
+
title={
|
|
218
|
+
/* Single-line message: title + description inline, truncating as
|
|
219
|
+
one unit. Type per the ODS mockup (2862-8391): the "h3 body @
|
|
220
|
+
500" Figma style maps to the code's `.text-h4` utility (the
|
|
221
|
+
`.text-h3` utility is 700; h3/h4 body share the same size
|
|
222
|
+
tokens) — the same treatment string titles get from the view.
|
|
223
|
+
Description keeps opacity-80 for hierarchy. Separator is a
|
|
224
|
+
middot (house rule: no en/em dashes in copy). */
|
|
225
|
+
<p className="min-w-0 max-w-full text-h4 truncate mb-0">
|
|
226
|
+
<span>{displayAnnouncement.title}</span>
|
|
228
227
|
{displayAnnouncement.description && (
|
|
229
228
|
<span className="hidden sm:inline opacity-80"> · {displayAnnouncement.description}</span>
|
|
230
229
|
)}
|
|
231
230
|
</p>
|
|
231
|
+
}
|
|
232
|
+
actionBlock={
|
|
233
|
+
/* CTA - the common Button carrying the ADMIN-CONFIGURED colors
|
|
234
|
+
(cta_button_background_color / cta_button_text_color are an
|
|
235
|
+
admin FEATURE, defaults from ANNOUNCEMENT_CTA_DEFAULTS —
|
|
236
|
+
announcement colors are data, not token surfaces). Inline
|
|
237
|
+
styles win over the variant's hover classes on every state,
|
|
238
|
+
so hover feedback is opacity (the bar's original treatment);
|
|
239
|
+
nothing can render dark-on-dark.
|
|
232
240
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
}
|
|
269
|
-
>
|
|
270
|
-
{displayAnnouncement.cta_text}
|
|
271
|
-
</Button>
|
|
272
|
-
</div>
|
|
273
|
-
)}
|
|
274
|
-
</div>
|
|
275
|
-
|
|
276
|
-
{/* Dismiss - the common Button in its ghost-icon treatment
|
|
277
|
-
(size="icon-sm": 32px target, >= the 24px WCAG 2.5.8 AA floor,
|
|
278
|
-
16px glyph) with the bar's quiet tint hover. Inert in
|
|
279
|
-
previewMode. */}
|
|
280
|
-
<div className="flex-shrink-0 ml-1 md:ml-2 mr-2 md:mr-4">
|
|
241
|
+
Geometry + type come from the design system's size="compact"
|
|
242
|
+
(24px caption-scale pill for slim strips — rationale documented
|
|
243
|
+
on the variant in button.tsx). */
|
|
244
|
+
hasCta && displayAnnouncement.cta_text ? (
|
|
245
|
+
<Button
|
|
246
|
+
onClick={handleCtaClick}
|
|
247
|
+
variant="outline"
|
|
248
|
+
size="compact"
|
|
249
|
+
className="transition-opacity hover:opacity-90"
|
|
250
|
+
style={{
|
|
251
|
+
backgroundColor:
|
|
252
|
+
displayAnnouncement.cta_button_background_color || ANNOUNCEMENT_CTA_DEFAULTS.background,
|
|
253
|
+
color: displayAnnouncement.cta_button_text_color || ANNOUNCEMENT_CTA_DEFAULTS.text,
|
|
254
|
+
borderColor: displayAnnouncement.cta_button_background_color || ANNOUNCEMENT_CTA_DEFAULTS.background,
|
|
255
|
+
}}
|
|
256
|
+
tabIndex={expanded ? 0 : -1}
|
|
257
|
+
leftIcon={
|
|
258
|
+
displayAnnouncement.cta_show_icon && displayAnnouncement.cta_icon_name ? (
|
|
259
|
+
<EntityIcon
|
|
260
|
+
icon={{ name: displayAnnouncement.cta_icon_name, props: displayAnnouncement.cta_icon_props }}
|
|
261
|
+
size={14}
|
|
262
|
+
className="w-3.5 h-3.5"
|
|
263
|
+
/>
|
|
264
|
+
) : undefined
|
|
265
|
+
}
|
|
266
|
+
>
|
|
267
|
+
{displayAnnouncement.cta_text}
|
|
268
|
+
</Button>
|
|
269
|
+
) : undefined
|
|
270
|
+
}
|
|
271
|
+
endAdornment={
|
|
272
|
+
/* Dismiss - the common Button in its ghost-icon treatment
|
|
273
|
+
(size="icon-sm": 32px target, >= the 24px WCAG 2.5.8 AA floor,
|
|
274
|
+
16px glyph) with the bar's quiet tint hover. Inert in
|
|
275
|
+
previewMode. */
|
|
281
276
|
<Button
|
|
282
|
-
onClick={
|
|
283
|
-
e.stopPropagation(); // Prevent triggering the mobile CTA click
|
|
284
|
-
handleDismiss();
|
|
285
|
-
}}
|
|
277
|
+
onClick={handleDismiss}
|
|
286
278
|
variant="transparent"
|
|
287
279
|
size="icon-sm"
|
|
288
280
|
className={barButtonClasses}
|
|
@@ -292,8 +284,8 @@ export function AnnouncementBar({
|
|
|
292
284
|
>
|
|
293
285
|
<X strokeWidth={2} />
|
|
294
286
|
</Button>
|
|
295
|
-
|
|
296
|
-
|
|
287
|
+
}
|
|
288
|
+
/>
|
|
297
289
|
</div>
|
|
298
290
|
</div>
|
|
299
291
|
);
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { CSSProperties, ReactElement, ReactNode } from 'react';
|
|
4
|
+
import { cn } from '../../utils/cn';
|
|
5
|
+
|
|
6
|
+
export interface AnnouncementBarViewProps {
|
|
7
|
+
/**
|
|
8
|
+
* Leading slot — typically an icon. Rendered before the title. Per the
|
|
9
|
+
* mockup icons are 16px below `md` and 24px from `md` up — size them with
|
|
10
|
+
* the responsive token: `size-[var(--icon-size-icon-size)]`.
|
|
11
|
+
*/
|
|
12
|
+
startAdornment?: ReactNode;
|
|
13
|
+
/**
|
|
14
|
+
* Bar message. A plain string gets the mockup's text treatment (`text-h4`,
|
|
15
|
+
* single line from `md` up, wrapping below); pass a ReactElement to fully
|
|
16
|
+
* own the markup/typography (e.g. bold title + inline description).
|
|
17
|
+
*/
|
|
18
|
+
title?: string | ReactElement;
|
|
19
|
+
/**
|
|
20
|
+
* Primary CTA. From `md` up it sits at content width in one trailing
|
|
21
|
+
* container with `endAdornment`; below `md` it drops to its own
|
|
22
|
+
* full-width second row (Figma ODS 2862-8391). Omit it entirely to
|
|
23
|
+
* collapse that row — the bar height then follows the remaining content.
|
|
24
|
+
*/
|
|
25
|
+
actionBlock?: ReactElement;
|
|
26
|
+
/**
|
|
27
|
+
* Trailing slot (e.g. a dismiss button). Always keeps its content width:
|
|
28
|
+
* inline after the title below `md` (and on every breakpoint when there is
|
|
29
|
+
* no `actionBlock`), after the action from `md` up. NOTE: with an
|
|
30
|
+
* `actionBlock` present the element is mounted twice (one copy per
|
|
31
|
+
* breakpoint, the inactive one `display:none`) — keep it stateless.
|
|
32
|
+
*/
|
|
33
|
+
endAdornment?: ReactElement;
|
|
34
|
+
/** Surface styling (background, text color, theme scope) is the consumer's — pass it here. */
|
|
35
|
+
className?: string;
|
|
36
|
+
style?: CSSProperties;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Pure presentational announcement/notification bar (Figma 9364-40603 desktop,
|
|
41
|
+
* 9418-43969 tablet, ODS 2862-8391 mobile). No data fetching, storage, or
|
|
42
|
+
* navigation — consumers own state and pass content through slots.
|
|
43
|
+
*
|
|
44
|
+
* Layout: `md` (800px) and up is a single row — `startAdornment` + title
|
|
45
|
+
* (flex-1, truncating) + trailing container (`actionBlock` + `endAdornment`)
|
|
46
|
+
* at content width. Below `md`: `startAdornment` + wrapping title +
|
|
47
|
+
* `endAdornment` share the first row (top-aligned so a two-line title keeps
|
|
48
|
+
* the adornments on line one, each centered in a line-height box), and the
|
|
49
|
+
* action alone stretches across its own second row. No action → no second
|
|
50
|
+
* row; the bar height follows the content. Spacing/typography use responsive
|
|
51
|
+
* ODS tokens, so paddings and font sizes track the breakpoint automatically.
|
|
52
|
+
*/
|
|
53
|
+
export function AnnouncementBarView({
|
|
54
|
+
startAdornment,
|
|
55
|
+
title,
|
|
56
|
+
actionBlock,
|
|
57
|
+
endAdornment,
|
|
58
|
+
className,
|
|
59
|
+
style,
|
|
60
|
+
}: AnnouncementBarViewProps) {
|
|
61
|
+
// Below `md` adornments center inside a box matching the title's first
|
|
62
|
+
// text line (20px), so `items-start` keeps them on line one of a wrapped
|
|
63
|
+
// title without hugging the row's top edge. From `md` up the row is
|
|
64
|
+
// single-line and center-aligned, so the boxes dissolve (`h-auto`).
|
|
65
|
+
const adornmentBox = 'flex h-5 shrink-0 items-center md:h-auto';
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<div
|
|
69
|
+
className={cn(
|
|
70
|
+
'flex w-full flex-col gap-[var(--spacing-system-s)] px-[var(--spacing-system-l)] py-[var(--spacing-system-sf)] md:flex-row md:items-center',
|
|
71
|
+
className,
|
|
72
|
+
)}
|
|
73
|
+
style={style}
|
|
74
|
+
>
|
|
75
|
+
<div className="flex min-w-0 flex-1 items-start gap-[var(--spacing-system-s)] md:items-center">
|
|
76
|
+
{startAdornment && <div className={adornmentBox}>{startAdornment}</div>}
|
|
77
|
+
{typeof title === 'string' ? (
|
|
78
|
+
<p className="min-w-0 flex-1 break-words text-h4 md:truncate">{title}</p>
|
|
79
|
+
) : (
|
|
80
|
+
title != null && <div className="min-w-0 flex-1">{title}</div>
|
|
81
|
+
)}
|
|
82
|
+
{/* Mobile home of the trailing adornment — and its only home when
|
|
83
|
+
there is no action row to share from `md` up. */}
|
|
84
|
+
{endAdornment && <div className={cn(adornmentBox, actionBlock && 'md:hidden')}>{endAdornment}</div>}
|
|
85
|
+
</div>
|
|
86
|
+
{actionBlock && (
|
|
87
|
+
<div className="flex w-full shrink-0 items-center gap-[var(--spacing-system-s)] md:w-auto">
|
|
88
|
+
<div className="flex min-w-0 flex-1 md:flex-none [&>*]:w-full md:[&>*]:w-auto">{actionBlock}</div>
|
|
89
|
+
{endAdornment && <div className="hidden shrink-0 md:flex">{endAdornment}</div>}
|
|
90
|
+
</div>
|
|
91
|
+
)}
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
@@ -336,6 +336,22 @@
|
|
|
336
336
|
--ods-accent: var(--ods-open-yellow-base);
|
|
337
337
|
}
|
|
338
338
|
|
|
339
|
+
/* Tier-2 re-anchor for NESTED theme scopes. Tier-2 aliases declared at
|
|
340
|
+
* `:root` compute there (custom properties substitute var() where the
|
|
341
|
+
* property is declared), so descendants of a nested `.theme-light` /
|
|
342
|
+
* `.theme-dark` island inherit the ROOT-theme value — the island's Tier-1
|
|
343
|
+
* flips never reach them. Re-declaring an alias inside the scope classes
|
|
344
|
+
* recomputes it against the scoped Tier-1 primitives, letting components
|
|
345
|
+
* inside theme islands (e.g. AnnouncementBar) use semantic tokens instead
|
|
346
|
+
* of reaching for Tier-1 directly. Values are identical to the `:root`
|
|
347
|
+
* declarations above — keep them in sync. */
|
|
348
|
+
.theme-dark,
|
|
349
|
+
.theme-light {
|
|
350
|
+
--color-text-primary: var(--ods-system-greys-white);
|
|
351
|
+
--color-bg-hover: var(--ods-system-greys-black-hover);
|
|
352
|
+
--color-bg-active: var(--ods-system-greys-black-action);
|
|
353
|
+
}
|
|
354
|
+
|
|
339
355
|
/* Alpha-based semantic tokens that need a light-theme tweak (the only
|
|
340
356
|
* Tier-2 values not derived from a flipping primitive). */
|
|
341
357
|
:root[data-theme="light"],
|