@flamingo-stack/openframe-frontend-core 0.0.397 → 0.0.398-snapshot.20260710082611
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-IVXOR2IA.js → chunk-2L6ALD2Q.js} +3 -3
- package/dist/{chunk-CK5UZLIN.js → chunk-2XVOYDRF.js} +3418 -3312
- package/dist/chunk-2XVOYDRF.js.map +1 -0
- package/dist/{chunk-VTR4QDL2.js → chunk-4H2ICJYB.js} +4 -4
- package/dist/{chunk-WCDM226S.js → chunk-4K5XJ5EJ.js} +3 -3
- package/dist/{chunk-WWHPTP4B.js → chunk-4KWIQEZT.js} +2 -2
- package/dist/{chunk-OAENYOKC.cjs → chunk-4PEC5KU6.cjs} +12 -12
- package/dist/{chunk-OAENYOKC.cjs.map → chunk-4PEC5KU6.cjs.map} +1 -1
- package/dist/{chunk-ZUKBC2ZX.cjs → chunk-7OKNKZBW.cjs} +763 -657
- package/dist/chunk-7OKNKZBW.cjs.map +1 -0
- package/dist/{chunk-J6TAK6HE.js → chunk-A6KGHQRF.js} +6 -6
- package/dist/{chunk-JNU3YPLP.js → chunk-BSJO5VFV.js} +6 -6
- package/dist/{chunk-D4F7ZLJG.js → chunk-CRRH7RJX.js} +5 -5
- package/dist/{chunk-M6NPDNVB.cjs → chunk-D5IHYUBY.cjs} +33 -33
- package/dist/{chunk-M6NPDNVB.cjs.map → chunk-D5IHYUBY.cjs.map} +1 -1
- package/dist/{chunk-K5FRWPWF.js → chunk-FXWURWDU.js} +3 -3
- package/dist/{chunk-3E4BDAHJ.js → chunk-G3A7QHCX.js} +3 -3
- package/dist/{chunk-55SNCMFS.cjs → chunk-G5NDKTX6.cjs} +40 -40
- package/dist/{chunk-55SNCMFS.cjs.map → chunk-G5NDKTX6.cjs.map} +1 -1
- package/dist/{chunk-NYIZVI4I.cjs → chunk-GA5AN24T.cjs} +10 -3
- package/dist/chunk-GA5AN24T.cjs.map +1 -0
- package/dist/{chunk-X2FPWS3P.js → chunk-HGKWTKW7.js} +10 -3
- package/dist/chunk-HGKWTKW7.js.map +1 -0
- package/dist/{chunk-DB6CUWTL.cjs → chunk-JBLBFJ4N.cjs} +32 -32
- package/dist/{chunk-DB6CUWTL.cjs.map → chunk-JBLBFJ4N.cjs.map} +1 -1
- package/dist/{chunk-743FCO64.cjs → chunk-JWBRDNYH.cjs} +31 -31
- package/dist/{chunk-743FCO64.cjs.map → chunk-JWBRDNYH.cjs.map} +1 -1
- package/dist/{chunk-LWNPMLIH.cjs → chunk-LPH2JDOW.cjs} +56 -2
- package/dist/chunk-LPH2JDOW.cjs.map +1 -0
- package/dist/{chunk-LQHMXPOJ.js → chunk-N7YBNHVT.js} +563 -509
- package/dist/chunk-N7YBNHVT.js.map +1 -0
- package/dist/{chunk-BB7TZ6KS.cjs → chunk-NCW22SII.cjs} +75 -75
- package/dist/{chunk-BB7TZ6KS.cjs.map → chunk-NCW22SII.cjs.map} +1 -1
- package/dist/{chunk-XNAYZHPK.cjs → chunk-O5NA3ULU.cjs} +93 -93
- package/dist/{chunk-XNAYZHPK.cjs.map → chunk-O5NA3ULU.cjs.map} +1 -1
- package/dist/{chunk-Z6PAD2UN.cjs → chunk-QNYNXSYQ.cjs} +12 -12
- package/dist/{chunk-Z6PAD2UN.cjs.map → chunk-QNYNXSYQ.cjs.map} +1 -1
- package/dist/{chunk-T5F54MN4.cjs → chunk-RIC4TCPD.cjs} +19 -19
- package/dist/{chunk-T5F54MN4.cjs.map → chunk-RIC4TCPD.cjs.map} +1 -1
- package/dist/{chunk-HPQKX5IW.cjs → chunk-VOHOA3WV.cjs} +19 -19
- package/dist/{chunk-HPQKX5IW.cjs.map → chunk-VOHOA3WV.cjs.map} +1 -1
- package/dist/{chunk-TTHTO5H5.js → chunk-VVKWNVKE.js} +7 -7
- package/dist/{chunk-2U3YPWHG.cjs → chunk-WBLRUO3F.cjs} +3 -3
- package/dist/{chunk-2U3YPWHG.cjs.map → chunk-WBLRUO3F.cjs.map} +1 -1
- package/dist/{chunk-2OOZASQE.js → chunk-XBCAY6NE.js} +4 -4
- package/dist/{chunk-SBWTMJPQ.cjs → chunk-YWCCKB4J.cjs} +5 -5
- package/dist/{chunk-SBWTMJPQ.cjs.map → chunk-YWCCKB4J.cjs.map} +1 -1
- package/dist/{chunk-ZFK4HHRX.js → chunk-Z5ERSGP2.js} +2 -2
- package/dist/{chunk-43C5ECNT.cjs → chunk-ZDT2MBKE.cjs} +40 -40
- package/dist/{chunk-43C5ECNT.cjs.map → chunk-ZDT2MBKE.cjs.map} +1 -1
- package/dist/{chunk-KQESCGVG.js → chunk-ZSZRJWY3.js} +2 -2
- package/dist/components/case-studies/index.cjs +15 -15
- package/dist/components/case-studies/index.js +5 -5
- package/dist/components/chat/index.cjs +5 -5
- package/dist/components/chat/index.js +4 -4
- package/dist/components/contact/index.cjs +6 -6
- package/dist/components/contact/index.js +5 -5
- package/dist/components/docs/index.cjs +8 -8
- package/dist/components/docs/index.js +7 -7
- package/dist/components/embeds/index.cjs +6 -6
- package/dist/components/embeds/index.js +5 -5
- package/dist/components/faq/index.cjs +7 -7
- package/dist/components/faq/index.js +6 -6
- package/dist/components/features/auth/complete-account-form.d.ts.map +1 -1
- package/dist/components/features/auth/create-organization-form.d.ts.map +1 -1
- package/dist/components/features/auth/openframe-sso-signup-form.d.ts.map +1 -1
- package/dist/components/features/index.cjs +9 -5
- package/dist/components/features/index.cjs.map +1 -1
- package/dist/components/features/index.d.ts +1 -0
- package/dist/components/features/index.d.ts.map +1 -1
- package/dist/components/features/index.js +8 -4
- package/dist/components/features/video-bites-strip.d.ts.map +1 -1
- package/dist/components/features/video-center-badge.d.ts +40 -0
- package/dist/components/features/video-center-badge.d.ts.map +1 -0
- package/dist/components/features/video.d.ts +7 -4
- package/dist/components/features/video.d.ts.map +1 -1
- package/dist/components/help-center-pages/index.cjs +24 -24
- package/dist/components/help-center-pages/index.js +15 -15
- package/dist/components/icons/index.cjs +6 -2
- package/dist/components/icons/index.cjs.map +1 -1
- package/dist/components/icons/index.d.ts +2 -0
- package/dist/components/icons/index.d.ts.map +1 -1
- package/dist/components/icons/index.js +5 -1
- package/dist/components/icons/play-filled-icon.d.ts +15 -0
- package/dist/components/icons/play-filled-icon.d.ts.map +1 -0
- package/dist/components/icons/volume-off-filled-icon.d.ts +14 -0
- package/dist/components/icons/volume-off-filled-icon.d.ts.map +1 -0
- package/dist/components/index.cjs +176 -166
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +23 -13
- package/dist/components/index.js.map +1 -1
- package/dist/components/media-carousel.d.ts.map +1 -1
- package/dist/components/navigation/index.cjs +5 -5
- package/dist/components/navigation/index.js +4 -4
- package/dist/components/onboarding-guides/index.cjs +9 -9
- package/dist/components/onboarding-guides/index.js +8 -8
- package/dist/components/related-content/index.cjs +7 -7
- package/dist/components/related-content/index.js +6 -6
- package/dist/components/tickets/index.cjs +8 -8
- package/dist/components/tickets/index.js +7 -7
- package/dist/components/ui/crossfade-panels.d.ts +52 -0
- package/dist/components/ui/crossfade-panels.d.ts.map +1 -0
- package/dist/components/ui/field-wrapper.d.ts +1 -1
- package/dist/components/ui/field-wrapper.d.ts.map +1 -1
- package/dist/components/ui/file-manager/index.cjs +50 -50
- package/dist/components/ui/file-manager/index.js +1 -1
- package/dist/components/ui/index.cjs +7 -5
- 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 +6 -4
- package/dist/components/ui/tab-navigation.d.ts +6 -2
- package/dist/components/ui/tab-navigation.d.ts.map +1 -1
- package/dist/hooks/index.cjs +3 -3
- package/dist/hooks/index.js +2 -2
- package/dist/index.cjs +15 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +14 -4
- package/package.json +4 -4
- package/src/components/features/auth/complete-account-form.tsx +37 -51
- package/src/components/features/auth/create-organization-form.tsx +22 -29
- package/src/components/features/auth/openframe-sso-signup-form.tsx +37 -51
- package/src/components/features/index.ts +1 -0
- package/src/components/features/video-bites-strip.tsx +73 -15
- package/src/components/features/video-center-badge.tsx +77 -0
- package/src/components/features/video.tsx +51 -28
- package/src/components/icons/index.ts +2 -0
- package/src/components/icons/play-filled-icon.tsx +36 -0
- package/src/components/icons/volume-off-filled-icon.tsx +38 -0
- package/src/components/media-carousel.tsx +2 -4
- package/src/components/ui/crossfade-panels.tsx +88 -0
- package/src/components/ui/field-wrapper.tsx +6 -3
- package/src/components/ui/index.ts +1 -0
- package/src/components/ui/tab-navigation.tsx +31 -14
- package/src/stories/Input.stories.tsx +44 -0
- package/src/stories/auth/CreateOrganization.stories.tsx +19 -0
- package/src/styles/app-globals.css +23 -0
- package/dist/chunk-CK5UZLIN.js.map +0 -1
- package/dist/chunk-LQHMXPOJ.js.map +0 -1
- package/dist/chunk-LWNPMLIH.cjs.map +0 -1
- package/dist/chunk-NYIZVI4I.cjs.map +0 -1
- package/dist/chunk-X2FPWS3P.js.map +0 -1
- package/dist/chunk-ZUKBC2ZX.cjs.map +0 -1
- /package/dist/{chunk-IVXOR2IA.js.map → chunk-2L6ALD2Q.js.map} +0 -0
- /package/dist/{chunk-VTR4QDL2.js.map → chunk-4H2ICJYB.js.map} +0 -0
- /package/dist/{chunk-WCDM226S.js.map → chunk-4K5XJ5EJ.js.map} +0 -0
- /package/dist/{chunk-WWHPTP4B.js.map → chunk-4KWIQEZT.js.map} +0 -0
- /package/dist/{chunk-J6TAK6HE.js.map → chunk-A6KGHQRF.js.map} +0 -0
- /package/dist/{chunk-JNU3YPLP.js.map → chunk-BSJO5VFV.js.map} +0 -0
- /package/dist/{chunk-D4F7ZLJG.js.map → chunk-CRRH7RJX.js.map} +0 -0
- /package/dist/{chunk-K5FRWPWF.js.map → chunk-FXWURWDU.js.map} +0 -0
- /package/dist/{chunk-3E4BDAHJ.js.map → chunk-G3A7QHCX.js.map} +0 -0
- /package/dist/{chunk-TTHTO5H5.js.map → chunk-VVKWNVKE.js.map} +0 -0
- /package/dist/{chunk-2OOZASQE.js.map → chunk-XBCAY6NE.js.map} +0 -0
- /package/dist/{chunk-ZFK4HHRX.js.map → chunk-Z5ERSGP2.js.map} +0 -0
- /package/dist/{chunk-KQESCGVG.js.map → chunk-ZSZRJWY3.js.map} +0 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { SVGProps } from "react";
|
|
2
|
+
|
|
3
|
+
export interface PlayFilledIconProps
|
|
4
|
+
extends Omit<SVGProps<SVGSVGElement>, "width" | "height"> {
|
|
5
|
+
className?: string;
|
|
6
|
+
size?: number;
|
|
7
|
+
color?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Solid play triangle — the SAME glyph media-chrome/MuxPlayer renders in its
|
|
12
|
+
* native center play button, so custom video affordances (strip cards,
|
|
13
|
+
* facades, carousels) stay pixel-consistent with real player chrome.
|
|
14
|
+
* Hand-maintained (not in the Figma-generated set, which has only the
|
|
15
|
+
* outline `PlayIcon`).
|
|
16
|
+
*/
|
|
17
|
+
export function PlayFilledIcon({
|
|
18
|
+
className = "",
|
|
19
|
+
size = 24,
|
|
20
|
+
color = "currentColor",
|
|
21
|
+
...props
|
|
22
|
+
}: PlayFilledIconProps) {
|
|
23
|
+
return (
|
|
24
|
+
<svg
|
|
25
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
26
|
+
width={size}
|
|
27
|
+
height={size}
|
|
28
|
+
fill="none"
|
|
29
|
+
viewBox="0 0 24 24"
|
|
30
|
+
className={className}
|
|
31
|
+
{...props}
|
|
32
|
+
>
|
|
33
|
+
<path fill={color} d="m6 21 15-9L6 3v18Z" />
|
|
34
|
+
</svg>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { SVGProps } from "react";
|
|
2
|
+
|
|
3
|
+
export interface VolumeOffFilledIconProps
|
|
4
|
+
extends Omit<SVGProps<SVGSVGElement>, "width" | "height"> {
|
|
5
|
+
className?: string;
|
|
6
|
+
size?: number;
|
|
7
|
+
color?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Solid muted-volume glyph (speaker + slash) — the filled companion to
|
|
12
|
+
* `PlayFilledIcon`, matching media-chrome's icon family so video unmute
|
|
13
|
+
* affordances read identically to real player chrome. Hand-maintained (the
|
|
14
|
+
* Figma-generated set has only outline volume icons).
|
|
15
|
+
*/
|
|
16
|
+
export function VolumeOffFilledIcon({
|
|
17
|
+
className = "",
|
|
18
|
+
size = 24,
|
|
19
|
+
color = "currentColor",
|
|
20
|
+
...props
|
|
21
|
+
}: VolumeOffFilledIconProps) {
|
|
22
|
+
return (
|
|
23
|
+
<svg
|
|
24
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
25
|
+
width={size}
|
|
26
|
+
height={size}
|
|
27
|
+
fill="none"
|
|
28
|
+
viewBox="0 0 24 24"
|
|
29
|
+
className={className}
|
|
30
|
+
{...props}
|
|
31
|
+
>
|
|
32
|
+
<path
|
|
33
|
+
fill={color}
|
|
34
|
+
d="M16.5 12A4.5 4.5 0 0 0 14 7.97v2.21l2.45 2.45c.03-.2.05-.41.05-.63Zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51A8.796 8.796 0 0 0 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71ZM4.27 3 3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06a8.99 8.99 0 0 0 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3ZM12 4 9.91 6.09 12 8.18V4Z"
|
|
35
|
+
/>
|
|
36
|
+
</svg>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -4,7 +4,7 @@ import { useState, useRef, useEffect, memo, useCallback } from 'react';
|
|
|
4
4
|
import { cn } from "../utils/cn";
|
|
5
5
|
import { MediaItem } from '../utils/media-carousel-utils-stub';
|
|
6
6
|
import { Video, extractYouTubeId } from './features/video';
|
|
7
|
-
import {
|
|
7
|
+
import { VideoPlayBadge } from './features/video-center-badge';
|
|
8
8
|
|
|
9
9
|
// Navigation icons
|
|
10
10
|
const ChevronLeftIcon = () => (
|
|
@@ -215,9 +215,7 @@ export const MediaCarousel = memo(function MediaCarousel({
|
|
|
215
215
|
{/* Play icon overlay for videos */}
|
|
216
216
|
{(item.type === 'video' || item.type === 'youtube') && (
|
|
217
217
|
<div className="absolute inset-0 flex items-center justify-center bg-black/30">
|
|
218
|
-
<
|
|
219
|
-
<PlayIcon size={12} color="white" />
|
|
220
|
-
</div>
|
|
218
|
+
<VideoPlayBadge size="sm" />
|
|
221
219
|
</div>
|
|
222
220
|
)}
|
|
223
221
|
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* <CrossfadePanels> — THE keep-mounted tab-panel stack: every panel stays in
|
|
5
|
+
* the DOM, stacked in the SAME grid cell, and switching is an opacity
|
|
6
|
+
* crossfade. Use it under any tab control (TabNavigation, TabSelector, …)
|
|
7
|
+
* whenever panels are content surfaces where height stability and a soft
|
|
8
|
+
* switch matter (strips, media rails, dashboards-in-view).
|
|
9
|
+
*
|
|
10
|
+
* Invariants encoded HERE exactly once (so no consumer re-derives them):
|
|
11
|
+
* - HEIGHT LOCK: grid items in one cell size the row to max(panel heights) —
|
|
12
|
+
* switching never shifts the page (no CLS), and surrounding layout treats
|
|
13
|
+
* the stack as one constant-height block.
|
|
14
|
+
* - FADE-THEN-HIDE: `visibility` transitions alongside `opacity`; per the CSS
|
|
15
|
+
* transitions spec's special visibility interpolation the outgoing panel
|
|
16
|
+
* stays paintable until the fade completes, THEN becomes `hidden` — no
|
|
17
|
+
* pop-out, and the settled hidden panel is fully removed from paint.
|
|
18
|
+
* - DEAD WHILE HIDDEN: `inert` + `aria-hidden` + `pointer-events-none` on
|
|
19
|
+
* inactive panels — no tab-stops, no screen-reader content, no hover/click
|
|
20
|
+
* leaks from the invisible layer (WCAG aria-hidden-focus).
|
|
21
|
+
* - MOTION RESPECT: `prefers-reduced-motion` collapses the fade to an
|
|
22
|
+
* instant swap.
|
|
23
|
+
* - The ACTIVE panel takes `z-[1]` so the incoming layer composites on top
|
|
24
|
+
* during the crossfade regardless of DOM order.
|
|
25
|
+
*
|
|
26
|
+
* This deliberately complements (not replaces) <TabContent>, which MOUNTS one
|
|
27
|
+
* panel at a time — right for heavy admin surfaces that should unmount when
|
|
28
|
+
* hidden. CrossfadePanels is for surfaces whose panels are cheap enough to
|
|
29
|
+
* keep alive and whose switch must be seamless.
|
|
30
|
+
*
|
|
31
|
+
* NOTE for marquee/animated panel content: hidden panels stay mounted and
|
|
32
|
+
* their animations keep running unless the content self-pauses. CardsStrip
|
|
33
|
+
* panels are fine (its rAF gates on near-viewport + visibility), but a
|
|
34
|
+
* consumer embedding heavier always-on content should pause it via its own
|
|
35
|
+
* visibility signal.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
import React from 'react'
|
|
39
|
+
import { cn } from '../../utils/cn'
|
|
40
|
+
|
|
41
|
+
export interface CrossfadePanel {
|
|
42
|
+
id: string
|
|
43
|
+
content: React.ReactNode
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface CrossfadePanelsProps {
|
|
47
|
+
/** Which panel is shown. Unknown id → all panels hidden (renders the
|
|
48
|
+
* stack's reserved height, nothing visible). */
|
|
49
|
+
activeId: string | undefined
|
|
50
|
+
panels: ReadonlyArray<CrossfadePanel>
|
|
51
|
+
/** Crossfade duration in ms. Default 300. */
|
|
52
|
+
durationMs?: number
|
|
53
|
+
className?: string
|
|
54
|
+
/** Extra classes for every panel wrapper (e.g. `min-w-0` is built in). */
|
|
55
|
+
panelClassName?: string
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function CrossfadePanels({
|
|
59
|
+
activeId,
|
|
60
|
+
panels,
|
|
61
|
+
durationMs = 300,
|
|
62
|
+
className,
|
|
63
|
+
panelClassName,
|
|
64
|
+
}: CrossfadePanelsProps) {
|
|
65
|
+
if (panels.length === 0) return null
|
|
66
|
+
return (
|
|
67
|
+
<div className={cn('grid w-full', className)}>
|
|
68
|
+
{panels.map(p => {
|
|
69
|
+
const active = p.id === activeId
|
|
70
|
+
return (
|
|
71
|
+
<div
|
|
72
|
+
key={p.id}
|
|
73
|
+
className={cn(
|
|
74
|
+
'col-start-1 row-start-1 min-w-0 transition-[opacity,visibility] ease-out motion-reduce:transition-none',
|
|
75
|
+
active ? 'visible opacity-100 z-[1]' : 'invisible opacity-0 pointer-events-none',
|
|
76
|
+
panelClassName,
|
|
77
|
+
)}
|
|
78
|
+
style={{ transitionDuration: `${durationMs}ms` }}
|
|
79
|
+
aria-hidden={!active || undefined}
|
|
80
|
+
inert={!active || undefined}
|
|
81
|
+
>
|
|
82
|
+
{p.content}
|
|
83
|
+
</div>
|
|
84
|
+
)
|
|
85
|
+
})}
|
|
86
|
+
</div>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
@@ -6,7 +6,7 @@ import { cn } from "../../utils/cn"
|
|
|
6
6
|
export interface FieldWrapperProps {
|
|
7
7
|
/** Label text displayed above the field */
|
|
8
8
|
label?: string
|
|
9
|
-
/** Status message displayed below the field.
|
|
9
|
+
/** Status message displayed below the field. Wraps up to two lines, then ellipsizes. */
|
|
10
10
|
error?: string
|
|
11
11
|
/** Color variant for the message: "error" (red), "warning" (yellow), "success" (green) or "muted" (grey) */
|
|
12
12
|
errorVariant?: "error" | "warning" | "success" | "muted"
|
|
@@ -27,7 +27,7 @@ const FieldWrapper = React.forwardRef<HTMLDivElement, FieldWrapperProps>(
|
|
|
27
27
|
const hasChrome = label != null || error != null
|
|
28
28
|
|
|
29
29
|
return (
|
|
30
|
-
<div ref={ref} className={cn(hasChrome ? "
|
|
30
|
+
<div ref={ref} className={cn(hasChrome ? "flex w-full flex-col" : "contents", className)}>
|
|
31
31
|
{label && (
|
|
32
32
|
<label className="text-h4 text-ods-text-primary mb-1">
|
|
33
33
|
{label}
|
|
@@ -35,7 +35,10 @@ const FieldWrapper = React.forwardRef<HTMLDivElement, FieldWrapperProps>(
|
|
|
35
35
|
)}
|
|
36
36
|
{children}
|
|
37
37
|
{error && (
|
|
38
|
-
<p
|
|
38
|
+
<p
|
|
39
|
+
className={cn("mt-1 text-h6 break-words line-clamp-2", errorVariantClasses[errorVariant])}
|
|
40
|
+
title={error}
|
|
41
|
+
>
|
|
39
42
|
{error}
|
|
40
43
|
</p>
|
|
41
44
|
)}
|
|
@@ -47,6 +47,7 @@ export * from './breadcrumb'
|
|
|
47
47
|
export * from './dropdown-menu'
|
|
48
48
|
export * from './menubar'
|
|
49
49
|
export * from './navigation-menu'
|
|
50
|
+
export * from './crossfade-panels'
|
|
50
51
|
export * from './tab-content'
|
|
51
52
|
export * from './tab-navigation'
|
|
52
53
|
// Feedback components
|
|
@@ -7,7 +7,8 @@ import { cn } from '../../utils/cn'
|
|
|
7
7
|
export interface TabItem {
|
|
8
8
|
id: string
|
|
9
9
|
label: string
|
|
10
|
-
|
|
10
|
+
/** Optional — text-only tabs (e.g. the homepage content-strip switcher) omit it. */
|
|
11
|
+
icon?: React.ComponentType<React.SVGProps<SVGSVGElement>>
|
|
11
12
|
component?: React.ComponentType<any>
|
|
12
13
|
indicator?: 'success' | 'warning' | 'error'
|
|
13
14
|
}
|
|
@@ -29,6 +30,9 @@ interface TabNavigationProps {
|
|
|
29
30
|
showRightGradient?: boolean
|
|
30
31
|
/** Force the left-edge gradient to always render, independent of scroll state. */
|
|
31
32
|
showLeftGradient?: boolean
|
|
33
|
+
/** Tabs grow to share the bar's full width equally (Figma segmented-underline
|
|
34
|
+
* look, e.g. the 480px homepage strip switcher). Default: natural width. */
|
|
35
|
+
stretchTabs?: boolean
|
|
32
36
|
|
|
33
37
|
// URL sync mode
|
|
34
38
|
urlSync?: boolean | TabNavigationUrlSyncOptions
|
|
@@ -46,6 +50,7 @@ export function TabNavigation({
|
|
|
46
50
|
shadowClassName,
|
|
47
51
|
showRightGradient = false,
|
|
48
52
|
showLeftGradient = false,
|
|
53
|
+
stretchTabs = false,
|
|
49
54
|
urlSync = false,
|
|
50
55
|
defaultTab,
|
|
51
56
|
children
|
|
@@ -222,24 +227,36 @@ export function TabNavigation({
|
|
|
222
227
|
className={cn(
|
|
223
228
|
"flex gap-[var(--spacing-system-xxs)] items-center justify-center p-[var(--spacing-system-m)] relative shrink-0 cursor-pointer",
|
|
224
229
|
"transition-all duration-200 bg-transparent border-none outline-none",
|
|
230
|
+
stretchTabs && 'flex-1',
|
|
225
231
|
isActive
|
|
226
232
|
? 'bg-gradient-to-b from-[rgba(255,255,255,0)] to-[rgba(255,255,255,0.1)]'
|
|
227
233
|
: 'hover:bg-gradient-to-b hover:from-[rgba(255,255,255,0)] hover:to-[rgba(255,255,255,0.1)]'
|
|
228
234
|
)}
|
|
229
235
|
>
|
|
230
|
-
|
|
231
|
-
<
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
236
|
+
{tab.icon ? (
|
|
237
|
+
<div className="relative flex items-center justify-center">
|
|
238
|
+
<tab.icon
|
|
239
|
+
className={cn("h-4 w-4 md:h-6 md:w-6 transition-colors", isActive ? 'text-ods-accent' : 'text-ods-text-secondary')}
|
|
240
|
+
/>
|
|
241
|
+
{tab.indicator && (
|
|
242
|
+
<div className={cn(
|
|
243
|
+
"absolute right-0 top-[-3px] w-3 h-3 rounded-full border-2 border-ods-bg",
|
|
244
|
+
tab.indicator === 'error' && 'bg-ods-error',
|
|
245
|
+
tab.indicator === 'warning' && 'bg-ods-accent',
|
|
246
|
+
tab.indicator === 'success' && 'bg-green-500'
|
|
247
|
+
)} />
|
|
248
|
+
)}
|
|
249
|
+
</div>
|
|
250
|
+
) : tab.indicator ? (
|
|
251
|
+
// Text-only tabs keep their status badge — inline dot before
|
|
252
|
+
// the label (there's no icon corner to anchor to).
|
|
253
|
+
<div className={cn(
|
|
254
|
+
"w-3 h-3 shrink-0 rounded-full border-2 border-ods-bg",
|
|
255
|
+
tab.indicator === 'error' && 'bg-ods-error',
|
|
256
|
+
tab.indicator === 'warning' && 'bg-ods-accent',
|
|
257
|
+
tab.indicator === 'success' && 'bg-green-500'
|
|
258
|
+
)} />
|
|
259
|
+
) : null}
|
|
243
260
|
|
|
244
261
|
<span className={cn(
|
|
245
262
|
"text-h4 whitespace-nowrap transition-colors",
|
|
@@ -219,3 +219,47 @@ export const AllVariants: Story = {
|
|
|
219
219
|
</div>
|
|
220
220
|
),
|
|
221
221
|
};
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Error / status states. Long messages wrap up to two lines, then ellipsize.
|
|
225
|
+
*/
|
|
226
|
+
export const ErrorState: Story = {
|
|
227
|
+
args: {
|
|
228
|
+
placeholder: 'Enter text...',
|
|
229
|
+
},
|
|
230
|
+
render: () => (
|
|
231
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '2.5rem', width: '480px' }}>
|
|
232
|
+
<Input label="Short error (no interaction)" placeholder="username@mail.com" error="Required field" />
|
|
233
|
+
<Input
|
|
234
|
+
label="Long error — wraps to two lines"
|
|
235
|
+
defaultValue="ada@example.com"
|
|
236
|
+
error="This email is already registered. Sign in instead or use a different email address to create the organization."
|
|
237
|
+
/>
|
|
238
|
+
{/* Half-width pair, as on the Create Organization screen */}
|
|
239
|
+
<div style={{ display: 'flex', gap: '1.5rem' }}>
|
|
240
|
+
<Input
|
|
241
|
+
label="Email"
|
|
242
|
+
defaultValue="ada@example.com"
|
|
243
|
+
error="This email is already registered. Sign in instead."
|
|
244
|
+
/>
|
|
245
|
+
<Input
|
|
246
|
+
label="Organization Name"
|
|
247
|
+
defaultValue="ϕ"
|
|
248
|
+
error="Organization Name must be 2-100 characters"
|
|
249
|
+
/>
|
|
250
|
+
</div>
|
|
251
|
+
<Input
|
|
252
|
+
label="Success status"
|
|
253
|
+
defaultValue="ada@example.com"
|
|
254
|
+
error="Email is available"
|
|
255
|
+
errorVariant="success"
|
|
256
|
+
/>
|
|
257
|
+
<Input
|
|
258
|
+
label="Muted status (long, truncating)"
|
|
259
|
+
defaultValue="ada-lovelace-computing"
|
|
260
|
+
error="Checking availability against every registered organization domain, this may take a moment…"
|
|
261
|
+
errorVariant="muted"
|
|
262
|
+
/>
|
|
263
|
+
</div>
|
|
264
|
+
),
|
|
265
|
+
};
|
|
@@ -97,6 +97,25 @@ export const Filled: Story = {
|
|
|
97
97
|
),
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
/**
|
|
101
|
+
* Error state: long messages under the fields wrap up to two lines.
|
|
102
|
+
*/
|
|
103
|
+
export const ErrorState: Story = {
|
|
104
|
+
render: () => (
|
|
105
|
+
<CreateOrganizationPage
|
|
106
|
+
email="ada@example.com"
|
|
107
|
+
organizationName="ϕ"
|
|
108
|
+
domainSuffix=".openframe.ai"
|
|
109
|
+
termsUrl="#terms"
|
|
110
|
+
privacyPolicyUrl="#privacy"
|
|
111
|
+
emailStatus={{ message: 'This email is already registered. Sign in instead.', variant: 'error' }}
|
|
112
|
+
errors={{
|
|
113
|
+
organizationName: 'Organization Name must be 2-100 characters',
|
|
114
|
+
}}
|
|
115
|
+
/>
|
|
116
|
+
),
|
|
117
|
+
}
|
|
118
|
+
|
|
100
119
|
const SSO_PROVIDERS: AuthSsoProvider[] = ['openframe', 'google', 'microsoft']
|
|
101
120
|
|
|
102
121
|
/** SSO configured: fields disabled, submit replaced by provider buttons. */
|
|
@@ -182,3 +182,26 @@ body {
|
|
|
182
182
|
--ring: 180 100% 69%;
|
|
183
183
|
--radius: 0.5rem;
|
|
184
184
|
}
|
|
185
|
+
|
|
186
|
+
/* ============================================================================
|
|
187
|
+
Video center-control identity — ONE look across every video surface:
|
|
188
|
+
a bare solid-white glyph, NO background disc (see
|
|
189
|
+
components/features/video-center-badge.tsx for the React consumers).
|
|
190
|
+
This strips media-chrome's default dark circle off MuxPlayer's big
|
|
191
|
+
center pre-play button so full players match the strip/facade glyphs.
|
|
192
|
+
========================================================================== */
|
|
193
|
+
/* REAL players (any mounted MuxPlayer): icons are white at rest and turn
|
|
194
|
+
the platform ACCENT color while the icon's control is hovered — every
|
|
195
|
+
media-chrome control carries the `button` part token, so this covers
|
|
196
|
+
center play, bar play/mute/fullscreen/etc. uniformly. */
|
|
197
|
+
mux-player::part(button):hover {
|
|
198
|
+
--media-icon-color: var(--ods-accent, var(--color-accent-primary));
|
|
199
|
+
}
|
|
200
|
+
mux-player::part(center play button) {
|
|
201
|
+
background: transparent;
|
|
202
|
+
filter: drop-shadow(0 1px 4px rgba(0, 0, 0, 0.5));
|
|
203
|
+
}
|
|
204
|
+
mux-player::part(center play button):hover {
|
|
205
|
+
background: transparent;
|
|
206
|
+
opacity: 0.85;
|
|
207
|
+
}
|