@flamingo-stack/openframe-frontend-core 0.0.568-1914.5171.1 → 0.0.568

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/dist/{chunk-LYP43VO3.js → chunk-2LKVYI7H.js} +4 -4
  2. package/dist/{chunk-WZW5TXKM.cjs → chunk-6I76UK2Z.cjs} +3 -3
  3. package/dist/{chunk-WZW5TXKM.cjs.map → chunk-6I76UK2Z.cjs.map} +1 -1
  4. package/dist/{chunk-H6JEWGJN.cjs → chunk-6PWUUWCO.cjs} +39 -39
  5. package/dist/{chunk-H6JEWGJN.cjs.map → chunk-6PWUUWCO.cjs.map} +1 -1
  6. package/dist/{chunk-SWPLMGKK.cjs → chunk-6RSKK4E7.cjs} +479 -526
  7. package/dist/chunk-6RSKK4E7.cjs.map +1 -0
  8. package/dist/{chunk-PA47Q3JE.js → chunk-6TDWCBQM.js} +6 -6
  9. package/dist/{chunk-RDKJCZHY.js → chunk-B44VZF2Z.js} +9 -8
  10. package/dist/chunk-B44VZF2Z.js.map +1 -0
  11. package/dist/{chunk-OW4OF7UP.js → chunk-BZ7B255K.js} +6 -6
  12. package/dist/{chunk-BMHU5V5T.cjs → chunk-DHTT77AG.cjs} +62 -62
  13. package/dist/{chunk-BMHU5V5T.cjs.map → chunk-DHTT77AG.cjs.map} +1 -1
  14. package/dist/{chunk-TN4KUALQ.js → chunk-DZ2Z7HRF.js} +2 -2
  15. package/dist/{chunk-KVCOILLR.js → chunk-EYXZF46V.js} +1032 -1079
  16. package/dist/chunk-EYXZF46V.js.map +1 -0
  17. package/dist/{chunk-HISA2T34.js → chunk-G2PAUMV6.js} +2 -2
  18. package/dist/{chunk-LRFCOGYE.js → chunk-HB3IG2MC.js} +3 -3
  19. package/dist/{chunk-NFVQ77GG.cjs → chunk-HKG7BZON.cjs} +9 -9
  20. package/dist/{chunk-NFVQ77GG.cjs.map → chunk-HKG7BZON.cjs.map} +1 -1
  21. package/dist/{chunk-MTVLA67G.cjs → chunk-KZ3PIUCH.cjs} +14 -14
  22. package/dist/{chunk-MTVLA67G.cjs.map → chunk-KZ3PIUCH.cjs.map} +1 -1
  23. package/dist/{chunk-EWSLKRSP.cjs → chunk-KZS2DFC2.cjs} +31 -31
  24. package/dist/{chunk-EWSLKRSP.cjs.map → chunk-KZS2DFC2.cjs.map} +1 -1
  25. package/dist/{chunk-QZQUXNVF.cjs → chunk-L22CSZZU.cjs} +11 -11
  26. package/dist/{chunk-QZQUXNVF.cjs.map → chunk-L22CSZZU.cjs.map} +1 -1
  27. package/dist/{chunk-ADBY2WCW.cjs → chunk-LNUXUOPN.cjs} +4 -4
  28. package/dist/{chunk-ADBY2WCW.cjs.map → chunk-LNUXUOPN.cjs.map} +1 -1
  29. package/dist/{chunk-NQI5BF7S.cjs → chunk-LXWQLUBR.cjs} +7 -7
  30. package/dist/{chunk-NQI5BF7S.cjs.map → chunk-LXWQLUBR.cjs.map} +1 -1
  31. package/dist/{chunk-NRU3AUUP.js → chunk-MQQM4NCN.js} +2 -2
  32. package/dist/{chunk-I5J7RE5E.js → chunk-O3JCKEVG.js} +2 -2
  33. package/dist/{chunk-XOFFASWS.cjs → chunk-P7QGTNEW.cjs} +97 -97
  34. package/dist/{chunk-XOFFASWS.cjs.map → chunk-P7QGTNEW.cjs.map} +1 -1
  35. package/dist/{chunk-FXEEH6GY.js → chunk-QVQLNKFS.js} +2 -2
  36. package/dist/{chunk-ANDU6MPG.cjs → chunk-RBYMJF2V.cjs} +26 -26
  37. package/dist/{chunk-ANDU6MPG.cjs.map → chunk-RBYMJF2V.cjs.map} +1 -1
  38. package/dist/{chunk-7GFUYIKG.cjs → chunk-T7AE7CKV.cjs} +5 -5
  39. package/dist/{chunk-7GFUYIKG.cjs.map → chunk-T7AE7CKV.cjs.map} +1 -1
  40. package/dist/{chunk-TCVAF576.js → chunk-VEXTUGUA.js} +4 -4
  41. package/dist/{chunk-4LHEQMOV.cjs → chunk-WPPBPRFU.cjs} +9 -8
  42. package/dist/chunk-WPPBPRFU.cjs.map +1 -0
  43. package/dist/{chunk-ED6F54AE.cjs → chunk-Y2BYHTEN.cjs} +29 -29
  44. package/dist/{chunk-ED6F54AE.cjs.map → chunk-Y2BYHTEN.cjs.map} +1 -1
  45. package/dist/{chunk-A3FU7F26.js → chunk-ZARYIFCI.js} +2 -2
  46. package/dist/{chunk-RGEFDRKL.js → chunk-ZIZJDXUI.js} +2 -2
  47. package/dist/{chunk-WC4CFOYB.js → chunk-ZXASQL3E.js} +2 -2
  48. package/dist/components/case-studies/index.cjs +9 -9
  49. package/dist/components/case-studies/index.js +3 -3
  50. package/dist/components/chat/index.cjs +3 -3
  51. package/dist/components/chat/index.js +2 -2
  52. package/dist/components/contact/index.cjs +4 -4
  53. package/dist/components/contact/index.js +3 -3
  54. package/dist/components/docs/index.cjs +6 -6
  55. package/dist/components/docs/index.js +5 -5
  56. package/dist/components/embeds/claude-embed.d.ts +1 -9
  57. package/dist/components/embeds/claude-embed.d.ts.map +1 -1
  58. package/dist/components/embeds/embed-viewer-frame.d.ts +1 -8
  59. package/dist/components/embeds/embed-viewer-frame.d.ts.map +1 -1
  60. package/dist/components/embeds/index.cjs +4 -4
  61. package/dist/components/embeds/index.js +3 -3
  62. package/dist/components/faq/index.cjs +4 -4
  63. package/dist/components/faq/index.js +3 -3
  64. package/dist/components/features/command-box.d.ts +5 -1
  65. package/dist/components/features/command-box.d.ts.map +1 -1
  66. package/dist/components/features/index.cjs +3 -3
  67. package/dist/components/features/index.js +2 -2
  68. package/dist/components/help-center-pages/index.cjs +23 -23
  69. package/dist/components/help-center-pages/index.js +14 -14
  70. package/dist/components/index.cjs +144 -144
  71. package/dist/components/index.js +12 -12
  72. package/dist/components/meeting-scheduler/index.cjs +60 -60
  73. package/dist/components/meeting-scheduler/index.js +4 -4
  74. package/dist/components/navigation/index.cjs +3 -3
  75. package/dist/components/navigation/index.js +2 -2
  76. package/dist/components/onboarding-guides/index.cjs +6 -6
  77. package/dist/components/onboarding-guides/index.js +5 -5
  78. package/dist/components/related-content/index.cjs +4 -4
  79. package/dist/components/related-content/index.js +3 -3
  80. package/dist/components/tickets/index.cjs +7 -7
  81. package/dist/components/tickets/index.js +6 -6
  82. package/dist/components/ui/autocomplete.d.ts +2 -0
  83. package/dist/components/ui/autocomplete.d.ts.map +1 -1
  84. package/dist/components/ui/field-wrapper.d.ts +7 -0
  85. package/dist/components/ui/field-wrapper.d.ts.map +1 -1
  86. package/dist/components/ui/file-manager/index.cjs +36 -36
  87. package/dist/components/ui/file-manager/index.js +1 -1
  88. package/dist/components/ui/index.cjs +3 -3
  89. package/dist/components/ui/index.js +2 -2
  90. package/dist/components/ui/markdown/rich/embed-overrides.d.ts.map +1 -1
  91. package/dist/components/ui/markdown/rich/shortcodes.d.ts.map +1 -1
  92. package/dist/components/ui/markdown/sanitize.d.ts.map +1 -1
  93. package/dist/components/ui/select.d.ts +2 -0
  94. package/dist/components/ui/select.d.ts.map +1 -1
  95. package/dist/components/ui/tab-selector.d.ts.map +1 -1
  96. package/dist/index.cjs +3 -3
  97. package/dist/index.js +2 -2
  98. package/dist/types/design-doc.d.ts +0 -7
  99. package/dist/types/design-doc.d.ts.map +1 -1
  100. package/package.json +1 -1
  101. package/src/components/embeds/claude-embed.tsx +7 -38
  102. package/src/components/embeds/embed-viewer-frame.tsx +4 -78
  103. package/src/components/features/command-box.tsx +36 -10
  104. package/src/components/ui/autocomplete.tsx +4 -1
  105. package/src/components/ui/field-wrapper.tsx +15 -7
  106. package/src/components/ui/markdown/rich/__tests__/shortcodes.test.ts +0 -42
  107. package/src/components/ui/markdown/rich/embed-overrides.tsx +0 -1
  108. package/src/components/ui/markdown/rich/shortcodes.ts +6 -20
  109. package/src/components/ui/markdown/sanitize.ts +0 -5
  110. package/src/components/ui/select.tsx +4 -2
  111. package/src/components/ui/tab-selector.tsx +6 -2
  112. package/src/types/design-doc.ts +0 -7
  113. package/dist/chunk-4LHEQMOV.cjs.map +0 -1
  114. package/dist/chunk-KVCOILLR.js.map +0 -1
  115. package/dist/chunk-RDKJCZHY.js.map +0 -1
  116. package/dist/chunk-SWPLMGKK.cjs.map +0 -1
  117. /package/dist/{chunk-LYP43VO3.js.map → chunk-2LKVYI7H.js.map} +0 -0
  118. /package/dist/{chunk-PA47Q3JE.js.map → chunk-6TDWCBQM.js.map} +0 -0
  119. /package/dist/{chunk-OW4OF7UP.js.map → chunk-BZ7B255K.js.map} +0 -0
  120. /package/dist/{chunk-TN4KUALQ.js.map → chunk-DZ2Z7HRF.js.map} +0 -0
  121. /package/dist/{chunk-HISA2T34.js.map → chunk-G2PAUMV6.js.map} +0 -0
  122. /package/dist/{chunk-LRFCOGYE.js.map → chunk-HB3IG2MC.js.map} +0 -0
  123. /package/dist/{chunk-NRU3AUUP.js.map → chunk-MQQM4NCN.js.map} +0 -0
  124. /package/dist/{chunk-I5J7RE5E.js.map → chunk-O3JCKEVG.js.map} +0 -0
  125. /package/dist/{chunk-FXEEH6GY.js.map → chunk-QVQLNKFS.js.map} +0 -0
  126. /package/dist/{chunk-TCVAF576.js.map → chunk-VEXTUGUA.js.map} +0 -0
  127. /package/dist/{chunk-A3FU7F26.js.map → chunk-ZARYIFCI.js.map} +0 -0
  128. /package/dist/{chunk-RGEFDRKL.js.map → chunk-ZIZJDXUI.js.map} +0 -0
  129. /package/dist/{chunk-WC4CFOYB.js.map → chunk-ZXASQL3E.js.map} +0 -0
@@ -19,14 +19,6 @@ export interface ClaudeEmbedProps {
19
19
  height?: string
20
20
  /** iframe loading strategy. Defaults to `lazy`, as `FigmaEmbed` does. */
21
21
  loading?: 'eager' | 'lazy'
22
- /** Self-hosted mirror of the artifact to FRAME instead of claude.ai.
23
- * claude.ai frame-locks Claude CODE artifact urls entirely and serves
24
- * public artifacts only to author-allow-listed hosts — when the host
25
- * app keeps its own serving copy (e.g. the hub's design-briefs store),
26
- * it passes that path here. The frame shows the mirror; the "Open in
27
- * Claude" action still targets `url`, so commenting stays on
28
- * claude.ai. Wins over `toClaudeEmbedUrl(url)` when set. */
29
- srcOverride?: string | null
30
22
  }
31
23
 
32
24
  const KIND_HEADING: Record<ClaudeEmbedKind, string> = {
@@ -51,16 +43,8 @@ const KIND_HEADING: Record<ClaudeEmbedKind, string> = {
51
43
  * (no embed route), an artifact whose author has not allow-listed this host,
52
44
  * or an http host (mixed content — it will not paint on `http://localhost`).
53
45
  */
54
- export function ClaudeEmbed({ url, kind = 'artifact', title, height, loading = 'lazy', srcOverride }: ClaudeEmbedProps) {
55
- // A mirror is by contract a SAME-ORIGIN absolute path ('/…', never
56
- // '//…' or a full url) — anything else is discarded, because whatever
57
- // this component frames wears Claude-branded chrome, and every other
58
- // frame source here is allow-listed (`toClaudeEmbedUrl` pins
59
- // claude.ai/claude.site). Shape-checked again here even though the
60
- // shortcode parser applies the same rule: the prop is public API and
61
- // callers can pass it directly.
62
- const mirrorSrc = srcOverride && /^\/(?!\/)/.test(srcOverride) ? srcOverride : null
63
- const embedUrl = mirrorSrc ?? toClaudeEmbedUrl(url)
46
+ export function ClaudeEmbed({ url, kind = 'artifact', title, height, loading = 'lazy' }: ClaudeEmbedProps) {
47
+ const embedUrl = toClaudeEmbedUrl(url)
64
48
  return (
65
49
  <EmbedViewerFrame
66
50
  className="my-6 space-y-3"
@@ -81,10 +65,6 @@ export function ClaudeEmbed({ url, kind = 'artifact', title, height, loading = '
81
65
  </Button>
82
66
  }
83
67
  src={embedUrl}
84
- // The framed artifact has no internal fullscreen button of its own
85
- // (unlike Figma's player) — the shell provides the toggle for both
86
- // the claude.ai `/embed` route and a mirror.
87
- fullscreenControl
88
68
  loading={loading}
89
69
  height={height}
90
70
  // Same frame contract Figma gets — an artifact is a real app: it copies
@@ -92,22 +72,11 @@ export function ClaudeEmbed({ url, kind = 'artifact', title, height, loading = '
92
72
  allow="clipboard-write; clipboard-read; fullscreen"
93
73
  // …but UNLIKE Figma, an artifact is USER-AUTHORED HTML and JS, so it is
94
74
  // sandboxed. Omitting `allow-top-navigation` is the point: an artifact
95
- // cannot navigate the page it is embedded in. The `allow-same-origin`
96
- // token is CONDITIONAL on where the frame points:
97
- // - claude.ai `/embed` (no srcOverride): Anthropic's own embed
98
- // snippet uses it, and on a CROSS-ORIGIN frame it grants the
99
- // frame claude.ai's origin — never ours. The artifact runtime
100
- // needs it (storage, postMessage handshake).
101
- // - a `srcOverride` mirror is served from the HOST's OWN origin —
102
- // `allow-same-origin` + `allow-scripts` there would be a no-op
103
- // sandbox handing the artifact first-party cookies and
104
- // `window.parent`. A static mirror renders fine from an opaque
105
- // origin, so the token is dropped.
106
- sandbox={
107
- mirrorSrc
108
- ? 'allow-scripts allow-popups allow-forms'
109
- : 'allow-scripts allow-same-origin allow-popups allow-forms'
110
- }
75
+ // cannot navigate the page it is embedded in. `allow-same-origin` beside
76
+ // `allow-scripts` is what Anthropic's own embed snippet uses and is safe
77
+ // on a CROSS-ORIGIN frame — it grants the frame claude.ai's origin, never
78
+ // ours.
79
+ sandbox="allow-scripts allow-same-origin allow-popups allow-forms"
111
80
  allowFullScreen
112
81
  emptyIcon={<ClaudeIcon className="w-16 h-16 mb-4" />}
113
82
  emptyMessage="Open this one in Claude · it has no embeddable view"
@@ -1,9 +1,7 @@
1
1
  'use client'
2
2
 
3
- import React, { useCallback, useEffect, useRef, useState } from 'react'
4
- import { Maximize2, Minimize2 } from 'lucide-react'
3
+ import React from 'react'
5
4
  import { cn } from '../../utils/cn'
6
- import { Button } from '../ui/button/button'
7
5
  import { EmbedIframe } from './embed-iframe'
8
6
 
9
7
  /**
@@ -49,13 +47,6 @@ export interface EmbedViewerFrameProps {
49
47
  sandbox?: string
50
48
  /** Wrapper classes; defaults to the sheets/pdf `space-y-4`. */
51
49
  className?: string
52
- /** Opt-in fullscreen toggle in the header row (rendered after `actions`,
53
- * only when there is a `src`). Fullscreens the WHOLE frame — header
54
- * included — so the exit control stays reachable (Esc works too).
55
- * Exists because some embedded contents ship their own fullscreen
56
- * button (the Figma player) and some cannot (a self-hosted Claude
57
- * artifact mirror) — the shell provides the affordance uniformly. */
58
- fullscreenControl?: boolean
59
50
  }
60
51
 
61
52
  export function EmbedViewerFrame({
@@ -72,51 +63,9 @@ export function EmbedViewerFrame({
72
63
  loading,
73
64
  sandbox,
74
65
  className,
75
- fullscreenControl,
76
66
  }: EmbedViewerFrameProps) {
77
- const frameRef = useRef<HTMLDivElement>(null)
78
- const [isFullscreen, setIsFullscreen] = useState(false)
79
-
80
- // Track the DOCUMENT's fullscreen element rather than local intent —
81
- // Esc, F11, and programmatic exits all land here, so the icon can
82
- // never desync from reality.
83
- useEffect(() => {
84
- if (!fullscreenControl) return
85
- const onChange = () => setIsFullscreen(document.fullscreenElement === frameRef.current)
86
- document.addEventListener('fullscreenchange', onChange)
87
- return () => document.removeEventListener('fullscreenchange', onChange)
88
- }, [fullscreenControl])
89
-
90
- const toggleFullscreen = useCallback(() => {
91
- const el = frameRef.current
92
- if (!el) return
93
- // Denials arrive BOTH ways: `requestFullscreen` throws synchronously
94
- // on some engines and returns a REJECTED promise on others (e.g. the
95
- // Fullscreen permissions policy when this component is itself inside
96
- // an embedder's iframe without `allow="fullscreen"`). Swallow both —
97
- // the button is a convenience; an unhandled rejection is not.
98
- try {
99
- if (document.fullscreenElement === el) {
100
- document.exitFullscreen().catch(() => {})
101
- } else {
102
- el.requestFullscreen().catch(() => {})
103
- }
104
- } catch {
105
- // Fullscreen API unavailable (old WebKit) — fail silently.
106
- }
107
- }, [])
108
-
109
67
  return (
110
- <div
111
- ref={frameRef}
112
- className={cn(
113
- 'space-y-4',
114
- className,
115
- // Fullscreen paints its own ground (the fullscreened element
116
- // otherwise sits on the UA's black backdrop) and scrolls itself.
117
- isFullscreen && 'overflow-auto bg-ods-bg p-[var(--spacing-system-mf)]',
118
- )}
119
- >
68
+ <div className={cn('space-y-4', className)}>
120
69
  <div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
121
70
  <div className="flex items-center gap-2 min-w-0">
122
71
  {icon}
@@ -126,36 +75,13 @@ export function EmbedViewerFrame({
126
75
  <span className="text-h6 font-semibold text-ods-text-primary truncate">{title}</span>
127
76
  )}
128
77
  </div>
129
- {/* Same responsive action-row idiom as the PDF viewer's button pair
130
- and every viewer's own action: full-width stacked on mobile,
131
- inline row from `sm:` up, SAME Button idiom (outline /
132
- small-legacy / w-4 icon / w-full sm:w-auto). Without the
133
- fullscreen toggle, `actions` renders EXACTLY as before — the
134
- wrapper exists only when the toggle joins it, so pre-existing
135
- viewers (figma/pdf/sheets) are byte-identical. */}
136
- {fullscreenControl && src ? (
137
- <div className="flex flex-col gap-[var(--spacing-system-xsf)] sm:flex-row sm:items-center">
138
- {actions}
139
- <Button
140
- variant="outline"
141
- size="small-legacy"
142
- onClick={toggleFullscreen}
143
- aria-label={isFullscreen ? 'Exit fullscreen' : 'Enter fullscreen'}
144
- leftIcon={isFullscreen ? <Minimize2 className="w-4 h-4" /> : <Maximize2 className="w-4 h-4" />}
145
- className="w-full sm:w-auto"
146
- >
147
- {isFullscreen ? 'Exit fullscreen' : 'Fullscreen'}
148
- </Button>
149
- </div>
150
- ) : (
151
- actions
152
- )}
78
+ {actions}
153
79
  </div>
154
80
  {src ? (
155
81
  <EmbedIframe
156
82
  src={src}
157
83
  title={title}
158
- height={isFullscreen ? 'calc(100vh - 96px)' : height}
84
+ height={height}
159
85
  allow={allow}
160
86
  allowFullScreen={allowFullScreen}
161
87
  loading={loading}
@@ -2,6 +2,7 @@
2
2
 
3
3
  import React from 'react'
4
4
  import { Button } from '../ui/button'
5
+ import { Copy02Icon } from '../icons-v2-generated/documents/copy-02-icon'
5
6
  import { cn } from '../../utils/cn'
6
7
 
7
8
  export interface CommandBoxAction {
@@ -34,6 +35,10 @@ export interface CommandBoxProps {
34
35
  commandClassName?: string
35
36
  /** Maximum lines to show (uses line-clamp, 0 for unlimited) */
36
37
  maxLines?: number
38
+ /** When set, shows a copy icon button in the top-right corner of the box */
39
+ onCopy?: () => void
40
+ /** Accessible label for the corner copy button */
41
+ copyAriaLabel?: string
37
42
  }
38
43
 
39
44
  /**
@@ -86,11 +91,26 @@ export function CommandBox({
86
91
  secondaryAction,
87
92
  className,
88
93
  commandClassName,
89
- maxLines = 0
94
+ maxLines = 0,
95
+ onCopy,
96
+ copyAriaLabel = 'Copy command'
90
97
  }: CommandBoxProps) {
91
98
  // Get static line-clamp class or undefined for unlimited
92
99
  const lineClampClass = maxLines > 0 ? lineClampClasses[maxLines] : undefined
93
100
 
101
+ const commandText = (
102
+ <div
103
+ className={cn(
104
+ 'text-ods-text-primary text-code break-all',
105
+ onCopy && 'flex-1 min-w-0',
106
+ lineClampClass,
107
+ commandClassName
108
+ )}
109
+ >
110
+ {command}
111
+ </div>
112
+ )
113
+
94
114
  return (
95
115
  <div className={cn('flex flex-col gap-2', className)}>
96
116
  {title && (
@@ -99,15 +119,21 @@ export function CommandBox({
99
119
  </div>
100
120
  )}
101
121
  <div className="bg-ods-bg border border-ods-border rounded-[6px] p-4">
102
- <div
103
- className={cn(
104
- 'text-ods-text-primary text-code break-all',
105
- lineClampClass,
106
- commandClassName
107
- )}
108
- >
109
- {command}
110
- </div>
122
+ {onCopy ? (
123
+ <div className="flex items-start gap-4">
124
+ {commandText}
125
+ <button
126
+ type="button"
127
+ onClick={onCopy}
128
+ aria-label={copyAriaLabel}
129
+ className="shrink-0 rounded-md text-ods-text-secondary transition-colors hover:text-ods-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ods-focus"
130
+ >
131
+ <Copy02Icon className="w-5 h-5" />
132
+ </button>
133
+ </div>
134
+ ) : (
135
+ commandText
136
+ )}
111
137
  {(primaryAction || secondaryAction) && (
112
138
  <div className="flex flex-col md:flex-row gap-3 md:justify-end mt-4">
113
139
  {secondaryAction && (
@@ -58,6 +58,8 @@ interface AutocompleteBaseProps<T = string> {
58
58
  freeSolo?: boolean
59
59
  /** Label for the input */
60
60
  label?: string
61
+ /** Label scale forwarded to FieldWrapper ("large" = text-h4 for designs with body-scale field titles) */
62
+ labelVariant?: "default" | "large"
61
63
  /** Error message displayed below the field */
62
64
  error?: string
63
65
  /** Custom filter function */
@@ -143,6 +145,7 @@ function AutocompleteInner<T = string>(
143
145
  dropdownClassName,
144
146
  freeSolo = false,
145
147
  label,
148
+ labelVariant,
146
149
  error,
147
150
  filterOptions,
148
151
  renderOption,
@@ -685,7 +688,7 @@ function AutocompleteInner<T = string>(
685
688
  )
686
689
 
687
690
  return (
688
- <FieldWrapper label={label} error={error} className={className}>
691
+ <FieldWrapper label={label} labelVariant={labelVariant} error={error} className={className}>
689
692
  <div className="relative" ref={containerRef}>
690
693
  {popover}
691
694
 
@@ -27,6 +27,13 @@ export interface FieldWrapperProps {
27
27
  error?: string
28
28
  /** Color variant for the message: "error" (red), "warning" (yellow), "success" (green) or "muted" (grey) */
29
29
  errorVariant?: "error" | "warning" | "success" | "muted"
30
+ /**
31
+ * Label scale. Default is the standard form-label scale (text-h6).
32
+ * "large" (text-h4) is for screens whose design specifies body-scale field
33
+ * titles (e.g. the devices New Device page) — opt in per call site, the
34
+ * default stays the converged small scale.
35
+ */
36
+ labelVariant?: "default" | "large"
30
37
  /** Additional className for the outer wrapper */
31
38
  className?: string
32
39
  children: React.ReactNode
@@ -40,18 +47,19 @@ const errorVariantClasses = {
40
47
  } as const
41
48
 
42
49
  const FieldWrapper = React.forwardRef<HTMLDivElement, FieldWrapperProps>(
43
- ({ label, htmlFor, error, errorVariant = "error", className, children }, ref) => {
50
+ ({ label, htmlFor, error, errorVariant = "error", labelVariant = "default", className, children }, ref) => {
44
51
  const hasChrome = label != null || error != null
45
52
 
46
53
  return (
47
54
  <div ref={ref} className={cn(hasChrome ? "relative flex w-full flex-col" : "contents", className)}>
48
55
  {label && (
49
- // text-h6, NOT text-h4: `Field` (ui/field.tsx) renders its label via
50
- // `Label variant="small"` = text-h6, and every form that mixes
51
- // Field-wrapped controls with `label=`-prop controls showed two label
52
- // sizes side by side — the exact inconsistency the admin editors kept
53
- // reporting. One label scale, owned here and in Field together.
54
- <Label className="mb-1" htmlFor={htmlFor}>
56
+ // Default is text-h6, NOT text-h4: `Field` (ui/field.tsx) renders its
57
+ // label via `Label variant="small"` = text-h6, and every form that
58
+ // mixes Field-wrapped controls with `label=`-prop controls showed two
59
+ // label sizes side by side — the exact inconsistency the admin
60
+ // editors kept reporting. One label scale, owned here and in Field
61
+ // together; `labelVariant` is the per-design opt-out.
62
+ <Label className="mb-1" htmlFor={htmlFor} variant={labelVariant}>
55
63
  {label}
56
64
  </Label>
57
65
  )}
@@ -91,48 +91,6 @@ describe('processShortcodes design-doc embeds', () => {
91
91
  expect(design).toContain('data-kind="design"');
92
92
  });
93
93
 
94
- it('threads the optional title and mirror segments into data attributes', () => {
95
- const titled = processShortcodes('{{claude-artifact:https://claude.ai/a|Brief doc}}');
96
- expect(titled).toContain('data-title="Brief doc"');
97
- expect(titled).not.toContain('data-embed-src');
98
-
99
- const mirrored = processShortcodes(
100
- '{{claude-artifact:https://claude.ai/a|Brief doc|/api/storage/view/design-briefs/x.html}}',
101
- );
102
- expect(mirrored).toContain('data-title="Brief doc"');
103
- expect(mirrored).toContain('data-embed-src="/api/storage/view/design-briefs/x.html"');
104
-
105
- // Untitled mirror: the empty title slot is skipped, the mirror still rides.
106
- const untitled = processShortcodes(
107
- '{{claude-artifact:https://claude.ai/a||/api/storage/view/design-briefs/x.html}}',
108
- );
109
- expect(untitled).not.toContain('data-title');
110
- expect(untitled).toContain('data-embed-src="/api/storage/view/design-briefs/x.html"');
111
- });
112
-
113
- it('recognizes the mirror segment by path SHAPE, never by pipe position', () => {
114
- // A pre-existing title containing `|` stays a title — nothing is framed.
115
- const pipeTitle = processShortcodes('{{claude-artifact:https://claude.ai/a|Design | v2}}');
116
- expect(pipeTitle).toContain('data-title="Design | v2"');
117
- expect(pipeTitle).not.toContain('data-embed-src');
118
-
119
- // Pipe title AND a mirror: the path-shaped tail is the mirror, the rest is the title.
120
- const both = processShortcodes(
121
- '{{claude-artifact:https://claude.ai/a|Design | v2|/api/storage/view/design-briefs/x.html}}',
122
- );
123
- expect(both).toContain('data-title="Design | v2"');
124
- expect(both).toContain('data-embed-src="/api/storage/view/design-briefs/x.html"');
125
-
126
- // Non-path tails (absolute urls, protocol-relative, schemes) are NEVER a
127
- // frame source — they fold into the title as plain text.
128
- const evil = processShortcodes('{{claude-artifact:https://claude.ai/a|T|https://evil.example}}');
129
- expect(evil).not.toContain('data-embed-src');
130
- const protoRelative = processShortcodes('{{claude-artifact:https://claude.ai/a|T|//evil.example}}');
131
- expect(protoRelative).not.toContain('data-embed-src');
132
- const scheme = processShortcodes('{{claude-artifact:https://claude.ai/a|T|javascript:alert(1)}}');
133
- expect(scheme).not.toContain('data-embed-src');
134
- });
135
-
136
94
  it('escapes the url attribute so a crafted link cannot break out of the block', () => {
137
95
  const output = processShortcodes('{{claude-artifact:https://claude.ai/a"><script>alert(1)</script>}}');
138
96
  expect(output).not.toContain('<script>');
@@ -153,7 +153,6 @@ export function buildRichEmbedOverrides({
153
153
  url={props['data-url']}
154
154
  kind={props['data-kind'] === 'design' ? 'design' : 'artifact'}
155
155
  title={props['data-title']}
156
- srcOverride={props['data-embed-src']}
157
156
  height="70vh"
158
157
  />
159
158
  );
@@ -68,26 +68,12 @@ export const processShortcodes = (content: string): string => {
68
68
  // Claude artifact / Claude Design: {{claude-artifact:URL}} / {{claude-design:URL}}
69
69
  // — the same shape as figma, so a Claude link is a markdown BLOCK wherever
70
70
  // markdown renders, never a bespoke card one surface hand-assembles.
71
- // `{{claude-artifact:URL}}` / `{{claude-artifact:URL|Name}}` /
72
- // `{{claude-artifact:URL|Name|EMBED_SRC}}` — the optional name after the
73
- // first pipe is the ONLY source of a real title (claude.ai serves one
74
- // og:title for every artifact); the optional LAST segment is a
75
- // host-provided mirror path framed INSTEAD of claude.ai (`ClaudeEmbed
76
- // srcOverride` — claude.ai frame-locks artifacts, so hosts that keep a
77
- // serving copy thread it through here and there is ONE Claude mount for
78
- // rails and prose alike). The mirror segment is recognized by SHAPE — a
79
- // same-origin absolute path (`/…`, not `//…`) — never by position, so a
80
- // pre-existing title containing `|` keeps rendering as a title instead of
81
- // being silently swallowed as a bogus frame source.
82
- .replace(/\{\{claude-(artifact|design):([^}]+)\}\}/g, (match, kind, rest) => {
83
- const parts = String(rest).split('|');
84
- const url = parts[0];
85
- const last = parts.length > 1 ? parts[parts.length - 1].trim() : '';
86
- const embedSrc = /^\/(?!\/)/.test(last) ? parts.pop()!.trim() : '';
87
- const title = parts.slice(1).join('|').trim();
88
- const titleAttr = title ? ` data-title="${escapeAttr(title)}"` : '';
89
- const embedSrcAttr = embedSrc ? ` data-embed-src="${escapeAttr(embedSrc)}"` : '';
90
- return `\n\n<div class="claude-embed" data-url="${escapeAttr(url.trim())}" data-kind="${kind}"${titleAttr}${embedSrcAttr}></div>\n\n`;
71
+ // `{{claude-artifact:URL}}` or `{{claude-artifact:URL|Name}}` — the optional
72
+ // name after the pipe is the ONLY source of a real title (claude.ai serves
73
+ // one og:title for every artifact), so the block carries it through.
74
+ .replace(/\{\{claude-(artifact|design):([^|}]+)(?:\|([^}]*))?\}\}/g, (match, kind, url, title) => {
75
+ const titleAttr = title && title.trim() ? ` data-title="${escapeAttr(title.trim())}"` : '';
76
+ return `\n\n<div class="claude-embed" data-url="${escapeAttr(url.trim())}" data-kind="${kind}"${titleAttr}></div>\n\n`;
91
77
  })
92
78
  // LinkedIn embeds: {{linkedin:POST_URL}}
93
79
  .replace(/\{\{linkedin:([^}]+)\}\}/g, (match, url) => {
@@ -395,11 +395,6 @@ const URL_ATTRS = new Set([
395
395
  'data',
396
396
  'action',
397
397
  'background',
398
- // The claude shortcode's mirror path — it becomes an <iframe src>
399
- // (`ClaudeEmbed srcOverride`), so it gets the same scheme check as
400
- // every other URL sink even though the component ALSO shape-validates
401
- // it to a same-origin path (defense in depth).
402
- 'data-embed-src',
403
398
  ])
404
399
 
405
400
  /**
@@ -20,9 +20,11 @@ const SelectTrigger = React.forwardRef<
20
20
  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> & {
21
21
  invalid?: boolean
22
22
  label?: string
23
+ /** Label scale forwarded to FieldWrapper ("large" = text-h4 for designs with body-scale field titles) */
24
+ labelVariant?: "default" | "large"
23
25
  error?: string
24
26
  }
25
- >(({ className, children, invalid, label, error, ...props }, ref) => {
27
+ >(({ className, children, invalid, label, labelVariant, error, ...props }, ref) => {
26
28
  const isInvalid = invalid || !!error
27
29
 
28
30
  const trigger = (
@@ -68,7 +70,7 @@ const SelectTrigger = React.forwardRef<
68
70
  )
69
71
 
70
72
  return (
71
- <FieldWrapper label={label} error={error}>
73
+ <FieldWrapper label={label} labelVariant={labelVariant} error={error}>
72
74
  {trigger}
73
75
  </FieldWrapper>
74
76
  )
@@ -1,6 +1,7 @@
1
1
  'use client'
2
2
 
3
3
  import React from 'react'
4
+ import { Label } from './label'
4
5
  import { cn } from '../../utils/cn'
5
6
 
6
7
  export interface TabSelectorItem {
@@ -59,9 +60,12 @@ export function TabSelector({
59
60
  aria-disabled={disabled || undefined}
60
61
  >
61
62
  {label && (
62
- <p className="text-ods-text-primary text-h4">
63
+ // Label for family/color; "large" (text-h4) is this component's
64
+ // long-standing label scale — neighbours opt into the same scale via
65
+ // their `labelVariant="large"` when a design puts them side by side.
66
+ <Label variant="large">
63
67
  {label}
64
- </p>
68
+ </Label>
65
69
  )}
66
70
  <div
67
71
  className={cn(
@@ -141,13 +141,6 @@ export interface DesignDocLink {
141
141
  title: string | null
142
142
  display_order: number | null
143
143
  created_at: string
144
- /** View-proxy path of THIS link's self-hosted artifact mirror
145
- * (`/api/storage/view/design-briefs/<artifact-uuid>.html`) when one
146
- * exists — resolved per artifact id from the link's url, so two
147
- * claude links on one doc each frame their OWN mirror. Transparent
148
- * frame source for `ClaudeEmbed srcOverride` (claude.ai cannot be
149
- * iframed). Null / absent = no mirror ingested for that artifact. */
150
- embed_src?: string | null
151
144
  /**
152
145
  * A `roadmap_task` link, hydrated from the mirror as the ROADMAP item it is —
153
146
  * so it renders with the roadmap card, the same as `/roadmap` and the chat.
@@ -1 +0,0 @@
1
- {"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-4LHEQMOV.cjs","../src/components/ui/label.tsx","../src/components/ui/field-wrapper.tsx","../src/components/ui/input.tsx","../src/components/ui/checkbox.tsx","../src/components/ui/dropdown-menu.tsx","../src/components/ui/portal-container.tsx","../src/components/ui/skeleton.tsx"],"names":["jsx","jsxs"],"mappings":"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","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-4LHEQMOV.cjs","sourcesContent":[null,"\"use client\"\n\nimport * as React from \"react\"\nimport * as LabelPrimitive from \"@radix-ui/react-label\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"../../utils/cn\"\n\n/**\n * THE form label. One treatment, stated once:\n *\n * - Scale + color live in the BASE (`text-h6 text-ods-text-primary`), so call\n * sites never restate them — before this, ~100 call sites carried a\n * `className=\"text-ods-text-primary\"` override and ~115 cargo-culted\n * `variant=\"small\"` because the base under-specified.\n * - NO margin. Layout belongs to the CONTAINER (`Field`'s gap, a `space-y-2`\n * wrapper) — a margin inside a text primitive stacks with container gaps,\n * which is exactly how `Field` rows ended up 12px label→control while the\n * hand-rolled rows were 8px, and how labels sat 2px off their inline badges\n * in `items-center` rows. Opt into `spacing` ONLY when the label truly has\n * no spacing container.\n *\n * Raw `<label>` elements remain correct for CLICK-SURFACES (a field's adorned\n * chrome, checkbox/radio rows, upload dropzones) — those are interaction\n * wrappers, not text labels, and must not inherit this typography.\n */\nconst labelVariants = cva(\n \"block text-h6 text-ods-text-primary\",\n {\n variants: {\n variant: {\n default: \"\",\n /** @deprecated alias of `default` — the three small scales converged on text-h6. */\n small: \"\",\n /** @deprecated alias of `default`. */\n medium: \"\",\n large: \"text-h4\"\n },\n spacing: {\n none: \"\",\n tight: \"mb-0.5\",\n normal: \"mb-2\",\n loose: \"mb-3\"\n }\n },\n defaultVariants: {\n variant: \"default\",\n spacing: \"none\"\n }\n }\n)\n\nconst Label = React.forwardRef<\n React.ElementRef<typeof LabelPrimitive.Root>,\n React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & VariantProps<typeof labelVariants>\n>(({ className, variant, spacing, ...props }, ref) => (\n <LabelPrimitive.Root\n ref={ref}\n className={cn(labelVariants({ variant, spacing }), className)}\n {...props}\n />\n))\nLabel.displayName = LabelPrimitive.Root.displayName\n\nexport { Label }\n","\"use client\"\n\nimport { Label } from './label'\nimport * as React from \"react\"\nimport { cn } from \"../../utils/cn\"\n\nexport interface FieldWrapperProps {\n /** Label text displayed above the field */\n label?: string\n /**\n * `id` of the control the label names. Without it the label is decoration:\n * clicking it focuses nothing and assistive tech reads the field unnamed,\n * which is why every caller that cared was rendering its own <Label> instead\n * of using `label` here.\n */\n htmlFor?: string\n /**\n * Status message displayed below the field, on ONE line, ellipsized on\n * overflow (full text via the native `title`).\n *\n * It is positioned OUT OF FLOW (absolute, hanging below the wrapper), so it\n * never changes the field's height when it appears — the same treatment\n * `CheckboxBlock` uses. The trade is that it overlaps whatever sits directly\n * below: a form stacking fields needs at least ~20px of vertical gap (e.g.\n * `gap-[var(--spacing-system-lf)]`) for the message to land in clear space.\n */\n error?: string\n /** Color variant for the message: \"error\" (red), \"warning\" (yellow), \"success\" (green) or \"muted\" (grey) */\n errorVariant?: \"error\" | \"warning\" | \"success\" | \"muted\"\n /** Additional className for the outer wrapper */\n className?: string\n children: React.ReactNode\n}\n\nconst errorVariantClasses = {\n error: \"text-ods-error\",\n warning: \"text-ods-warning\",\n success: \"text-ods-success\",\n muted: \"text-ods-text-secondary\",\n} as const\n\nconst FieldWrapper = React.forwardRef<HTMLDivElement, FieldWrapperProps>(\n ({ label, htmlFor, error, errorVariant = \"error\", className, children }, ref) => {\n const hasChrome = label != null || error != null\n\n return (\n <div ref={ref} className={cn(hasChrome ? \"relative flex w-full flex-col\" : \"contents\", className)}>\n {label && (\n // text-h6, NOT text-h4: `Field` (ui/field.tsx) renders its label via\n // `Label variant=\"small\"` = text-h6, and every form that mixes\n // Field-wrapped controls with `label=`-prop controls showed two label\n // sizes side by side — the exact inconsistency the admin editors kept\n // reporting. One label scale, owned here and in Field together.\n <Label className=\"mb-1\" htmlFor={htmlFor}>\n {label}\n </Label>\n )}\n {children}\n {error && (\n <p className={cn(\"absolute bottom-0 left-0 right-0 translate-y-full text-h6 truncate\", errorVariantClasses[errorVariant])} title={error}>\n {error}\n </p>\n )}\n </div>\n )\n }\n)\nFieldWrapper.displayName = \"FieldWrapper\"\n\nexport { FieldWrapper }\n","\"use client\"\n\nimport * as React from \"react\";\n\nimport { Loader2 } from \"lucide-react\";\nimport { cn } from \"../../utils/cn\";\nimport { FieldWrapper } from \"./field-wrapper\";\n\nexport interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {\n /** When true, renders error border & ring */\n invalid?: boolean;\n /** Element displayed at the start (left) of the input */\n startAdornment?: React.ReactNode;\n /** Element displayed at the end (right) of the input */\n endAdornment?: React.ReactNode;\n /** Label text displayed above the input */\n label?: string;\n /** Status message displayed below the input */\n error?: string;\n /** Color variant for the message: \"error\" (red), \"warning\" (yellow), \"success\" (green) or \"muted\" (grey).\n Only \"error\" and \"warning\" mark the field invalid (colored border). */\n errorVariant?: \"error\" | \"warning\" | \"success\" | \"muted\";\n /** When true, shows a loading spinner as end adornment */\n loading?: boolean;\n}\n\nconst invalidBorderClasses = {\n error: \"border-ods-error hover:border-ods-error has-[:focus]:border-ods-error\",\n warning: \"!border-ods-warning hover:!border-ods-warning has-[:focus]:!border-ods-warning\",\n} as const;\n\nconst Input = React.forwardRef<HTMLInputElement, InputProps>(\n ({ className, type, invalid = false, startAdornment, endAdornment, label, error, errorVariant = \"error\", loading = false, ...props }, ref) => {\n // success/muted are informational — they never paint the invalid border\n const variantIsInvalid = errorVariant === \"error\" || errorVariant === \"warning\"\n const isInvalid = invalid || (!!error && variantIsInvalid)\n\n // Range inputs get a clean slider rendering — no label wrapper, borders, or adornments\n if (type === 'range') {\n const rangeInput = (\n <input\n type=\"range\"\n className={cn(\n \"w-full cursor-pointer appearance-none rounded-full bg-white/30 h-1\",\n // Webkit (Chrome/Safari) thumb\n \"[&::-webkit-slider-thumb]:appearance-none\",\n \"[&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3\",\n \"[&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-white\",\n \"[&::-webkit-slider-thumb]:cursor-pointer [&::-webkit-slider-thumb]:shadow-sm\",\n // Firefox thumb\n \"[&::-moz-range-thumb]:w-3 [&::-moz-range-thumb]:h-3\",\n \"[&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:bg-white\",\n \"[&::-moz-range-thumb]:border-0 [&::-moz-range-thumb]:cursor-pointer\",\n // Firefox track\n \"[&::-moz-range-track]:bg-transparent\",\n // Disabled\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n className\n )}\n ref={ref}\n {...props}\n />\n )\n return label ? (\n <FieldWrapper label={label} error={error} errorVariant={errorVariant}>\n {rangeInput}\n </FieldWrapper>\n ) : rangeInput\n }\n\n const content = (\n <label\n data-invalid={isInvalid || undefined}\n className={cn(\n // Layout & spacing\n \"flex w-full items-center gap-2 rounded-[6px] border px-3 h-11 md:h-12 cursor-text\",\n // Focus-within states\n \"has-[:focus-visible]:outline-none\",\n \"group\",\n // Animations & touch UX\n \"transition-colors duration-200\",\n // Theme palette\n \"bg-ods-card border-ods-border has-[:focus]:border-ods-accent\",\n // Hover & active (not disabled)\n !props.disabled && \"hover:bg-ods-bg-hover hover:border-ods-border-hover active:bg-ods-bg-active active:border-ods-border-active\",\n // Disabled. The adornments (and any icon inside them) carry their own\n // `text-ods-text-secondary`, so grey them from here — a disabled field\n // must read as one flat colour, not grey text next to a live icon.\n // Scoped to the DIRECT span children (the two adornment wrappers): a\n // descendant rule would also repaint whatever the caller renders INSIDE\n // an adornment, which is passed verbatim precisely so it can own its\n // colour (a `Tag` there went invisible against its own fill).\n props.disabled && \"!cursor-not-allowed bg-ods-bg\",\n \"has-[:disabled]:[&>span]:text-ods-text-disabled has-[:disabled]:[&_svg]:text-ods-text-disabled\",\n // Invalid\n isInvalid && invalidBorderClasses[variantIsInvalid ? (errorVariant as \"error\" | \"warning\") : \"error\"],\n className\n )}\n >\n {startAdornment && (\n <span className=\"text-h6 flex-shrink-0 text-ods-text-secondary transition-colors duration-200 group-has-[:focus]:text-ods-accent group-data-[invalid]:text-ods-error [&_svg]:size-4 md:[&_svg]:size-6\">\n {startAdornment}\n </span>\n )}\n <input\n type={type}\n className={cn(\n // Layout\n \"flex-1 min-w-0 bg-transparent border-none outline-none\",\n // Typography\n \"text-h4\",\n // Colors\n \"text-ods-text-primary placeholder:text-ods-text-secondary\",\n // File input adjustments\n \"file:border-0 file:bg-transparent\",\n // Disabled\n \"disabled:cursor-not-allowed disabled:text-ods-text-disabled disabled:placeholder:text-ods-border\",\n // Touch\n \"touch-manipulation\",\n // Autofill override\n \"[&:-webkit-autofill]:[-webkit-box-shadow:0_0_0_9999px_transparent_inset] [&:-webkit-autofill]:[-webkit-text-fill-color:var(--color-text-primary)] [&:-webkit-autofill]:[caret-color:var(--color-text-primary)] [&:-webkit-autofill]:[transition:background-color_9999s_ease-in-out_0s]\"\n )}\n ref={ref}\n {...props}\n />\n {loading && (\n <Loader2 className=\"animate-spin flex-shrink-0 text-ods-text-secondary size-4 md:size-6\" />\n )}\n {!loading && endAdornment && (\n <span className=\"text-h6 flex-shrink-0 text-ods-text-secondary transition-colors duration-200 group-has-[:focus]:text-ods-accent group-data-[invalid]:text-ods-error [&_svg]:size-4 md:[&_svg]:size-6\">\n {endAdornment}\n </span>\n )}\n </label>\n )\n\n return (\n <FieldWrapper label={label} error={error} errorVariant={errorVariant}>\n {content}\n </FieldWrapper>\n )\n }\n)\nInput.displayName = \"Input\"\n\nexport { Input };\n","\"use client\"\n\nimport * as React from \"react\"\nimport * as CheckboxPrimitive from \"@radix-ui/react-checkbox\"\n\nimport { cn } from \"../../utils/cn\"\nimport { CheckboxCheckmarkIcon } from \"../icons-v2-generated/signs-and-symbols/checkbox-checkmark-icon\"\n\nconst Checkbox = React.forwardRef<\n React.ElementRef<typeof CheckboxPrimitive.Root>,\n React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>\n>(({ className, ...props }, ref) => (\n <CheckboxPrimitive.Root\n ref={ref}\n className={cn(\n \"peer h-4 w-4 shrink-0 rounded-sm border border-ods-border bg-ods-card focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ods-accent focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-ods-accent data-[state=checked]:border-ods-accent\",\n className\n )}\n {...props}\n >\n <CheckboxPrimitive.Indicator\n className={cn(\"flex items-center justify-center text-ods-text-on-accent\")}\n >\n <CheckboxCheckmarkIcon size={10} />\n </CheckboxPrimitive.Indicator>\n </CheckboxPrimitive.Root>\n))\nCheckbox.displayName = CheckboxPrimitive.Root.displayName\n\nexport { Checkbox }\n","\"use client\"\n\nimport * as React from \"react\"\nimport * as DropdownMenuPrimitive from \"@radix-ui/react-dropdown-menu\"\nimport { Check, ChevronRight, Circle } from \"lucide-react\"\n\nimport { cn } from \"../../utils/cn\"\nimport {\n COLLISION_PADDING_PX,\n useCollisionBoundary,\n usePortalContainer,\n} from \"./portal-container\"\n\nconst DropdownMenu = DropdownMenuPrimitive.Root\n\nconst DropdownMenuTrigger = DropdownMenuPrimitive.Trigger\n\nconst DropdownMenuGroup = DropdownMenuPrimitive.Group\n\nconst DropdownMenuPortal = DropdownMenuPrimitive.Portal\n\nconst DropdownMenuSub = DropdownMenuPrimitive.Sub\n\nconst DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup\n\nconst DropdownMenuSubTrigger = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {\n inset?: boolean\n }\n>(({ className, inset, children, ...props }, ref) => (\n <DropdownMenuPrimitive.SubTrigger\n ref={ref}\n className={cn(\n \"flex cursor-pointer gap-2 select-none items-center rounded-sm px-2 py-1.5 text-h6 outline-none hover:bg-ods-bg-hover focus:bg-ods-bg-hover data-[state=open]:bg-ods-bg-hover [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0\",\n inset && \"pl-8\",\n className\n )}\n {...props}\n >\n {children}\n <ChevronRight className=\"ml-auto\" />\n </DropdownMenuPrimitive.SubTrigger>\n))\nDropdownMenuSubTrigger.displayName =\n DropdownMenuPrimitive.SubTrigger.displayName\n\nconst DropdownMenuSubContent = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>\n>(({ className, collisionBoundary, collisionPadding, hideWhenDetached, ...props }, ref) => {\n // Same container as the root content — `DropdownMenuContent` portals, and a\n // submenu left un-portaled inherits whatever clipping/stacking context its\n // parent sits in instead of the one the host published. (The hand-rolled\n // submenu in `actions-menu` already portals; this keeps the shared primitive\n // consistent with both.)\n const container = usePortalContainer()\n // Same boundary as the root content — a submenu must not escape the surface\n // its parent menu is confined to.\n const boundary = useCollisionBoundary()\n const resolvedBoundary = collisionBoundary ?? boundary ?? undefined\n return (\n <DropdownMenuPrimitive.Portal container={container ?? undefined}>\n <DropdownMenuPrimitive.SubContent\n ref={ref}\n collisionBoundary={resolvedBoundary}\n collisionPadding={collisionPadding ?? (resolvedBoundary ? COLLISION_PADDING_PX : undefined)}\n // Only defaulted ON where a boundary was published (i.e. inside a panel\n // that owns its menus) — see `DropdownMenuContent`.\n hideWhenDetached={hideWhenDetached ?? (resolvedBoundary ? true : undefined)}\n className={cn(\n \"z-50 min-w-[8rem] overflow-hidden rounded-md border border-ods-border bg-ods-card p-1 text-ods-text-primary shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2\",\n className\n )}\n {...props}\n />\n </DropdownMenuPrimitive.Portal>\n )\n})\nDropdownMenuSubContent.displayName =\n DropdownMenuPrimitive.SubContent.displayName\n\nconst DropdownMenuContent = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>\n>(({\n className,\n sideOffset = 4,\n collisionBoundary,\n collisionPadding,\n hideWhenDetached,\n ...props\n}, ref) => {\n // Portal into the active container (e.g. a drawer) so the menu inherits its\n // stacking context; falls back to `document.body` when none is provided.\n const container = usePortalContainer()\n // Keep flip/shift inside the surface that owns the menu. Without this Radix\n // collides against the VIEWPORT, so a menu opened near the top of the chat\n // panel grows upward past the panel and lands over the app header. An\n // explicit prop still wins; no provider → viewport, Radix's own default.\n const boundary = useCollisionBoundary()\n const resolvedBoundary = collisionBoundary ?? boundary ?? undefined\n return (\n <DropdownMenuPrimitive.Portal container={container ?? undefined}>\n <DropdownMenuPrimitive.Content\n ref={ref}\n sideOffset={sideOffset}\n collisionBoundary={resolvedBoundary}\n collisionPadding={collisionPadding ?? (resolvedBoundary ? COLLISION_PADDING_PX : undefined)}\n // A menu tracks its trigger for as long as it is open. When the trigger\n // scrolls out of its clipping ancestor — a chat card carried away by the\n // thread's auto-scroll while the menu is open — Radix keeps the menu\n // pinned to that off-screen anchor, so it slides to the surface edge and\n // reads as a detached popup floating over unrelated chrome.\n // `hideWhenDetached` makes it disappear with its trigger instead.\n //\n // Defaulted ON only where a boundary was published. This is a SHARED\n // primitive: flipping it on globally would change dismissal behavior for\n // every dropdown in every consumer, and the detached-popup problem it\n // solves is specific to a menu living inside a scrolling panel.\n hideWhenDetached={hideWhenDetached ?? (resolvedBoundary ? true : undefined)}\n className={cn(\n \"z-50 min-w-[8rem] overflow-hidden rounded-md border border-ods-border bg-ods-card p-1 text-ods-text-primary shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2\",\n className\n )}\n {...props}\n />\n </DropdownMenuPrimitive.Portal>\n )\n})\nDropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName\n\nconst DropdownMenuItem = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Item>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {\n inset?: boolean\n }\n>(({ className, inset, ...props }, ref) => (\n <DropdownMenuPrimitive.Item\n ref={ref}\n className={cn(\n \"relative flex cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 text-h6 outline-none transition-colors text-ods-text-primary hover:bg-ods-bg-hover hover:text-ods-text-primary focus:bg-ods-bg-hover focus:text-ods-text-primary data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0\",\n inset && \"pl-8\",\n className\n )}\n {...props}\n />\n))\nDropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName\n\nconst DropdownMenuCheckboxItem = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>\n>(({ className, children, checked, ...props }, ref) => (\n <DropdownMenuPrimitive.CheckboxItem\n ref={ref}\n className={cn(\n \"relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-h6 outline-none transition-colors text-ods-text-primary hover:bg-ods-bg-hover hover:text-ods-text-primary focus:bg-ods-bg-hover focus:text-ods-text-primary data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n className\n )}\n checked={checked}\n {...props}\n >\n <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n <DropdownMenuPrimitive.ItemIndicator>\n <Check className=\"h-4 w-4\" />\n </DropdownMenuPrimitive.ItemIndicator>\n </span>\n {children}\n </DropdownMenuPrimitive.CheckboxItem>\n))\nDropdownMenuCheckboxItem.displayName =\n DropdownMenuPrimitive.CheckboxItem.displayName\n\nconst DropdownMenuRadioItem = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>\n>(({ className, children, ...props }, ref) => (\n <DropdownMenuPrimitive.RadioItem\n ref={ref}\n className={cn(\n \"relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-h6 outline-none transition-colors text-ods-text-primary hover:bg-ods-bg-hover hover:text-ods-text-primary focus:bg-ods-bg-hover focus:text-ods-text-primary data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n className\n )}\n {...props}\n >\n <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n <DropdownMenuPrimitive.ItemIndicator>\n <Circle className=\"h-2 w-2 fill-ods-accent\" />\n </DropdownMenuPrimitive.ItemIndicator>\n </span>\n {children}\n </DropdownMenuPrimitive.RadioItem>\n))\nDropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName\n\nconst DropdownMenuLabel = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Label>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {\n inset?: boolean\n }\n>(({ className, inset, ...props }, ref) => (\n <DropdownMenuPrimitive.Label\n ref={ref}\n className={cn(\n \"px-2 py-1.5 text-h6 font-semibold text-ods-text-secondary\",\n inset && \"pl-8\",\n className\n )}\n {...props}\n />\n))\nDropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName\n\nconst DropdownMenuSeparator = React.forwardRef<\n React.ElementRef<typeof DropdownMenuPrimitive.Separator>,\n React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n <DropdownMenuPrimitive.Separator\n ref={ref}\n className={cn(\"-mx-1 my-1 h-px bg-ods-border\", className)}\n {...props}\n />\n))\nDropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName\n\nconst DropdownMenuShortcut = ({\n className,\n ...props\n}: React.HTMLAttributes<HTMLSpanElement>) => {\n return (\n <span\n className={cn(\"ml-auto text-h6 text-ods-text-secondary opacity-60\", className)}\n {...props}\n />\n )\n}\nDropdownMenuShortcut.displayName = \"DropdownMenuShortcut\"\n\nexport {\n DropdownMenu,\n DropdownMenuTrigger,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuCheckboxItem,\n DropdownMenuRadioItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuShortcut,\n DropdownMenuGroup,\n DropdownMenuPortal,\n DropdownMenuSub,\n DropdownMenuSubContent,\n DropdownMenuSubTrigger,\n DropdownMenuRadioGroup,\n}\n","'use client'\n\nimport * as React from 'react'\n\n/**\n * The DOM node that Radix overlays (dropdown menus, tooltips) should portal\n * into, instead of the default `document.body`.\n *\n * Portaling to `document.body` lifts content out of every local stacking\n * context, which forces overlays opened *inside* a high-z surface (e.g. the\n * chat drawer) to escalate their own z-index to compete at the document\n * root. Pointing the portal at a node *inside* that surface instead\n * lets the content inherit the surface's stacking context — small, local\n * z-indices then \"just work\" and no escalation is needed. Radix positions\n * content with `strategy: \"fixed\"`, so it still escapes ancestor\n * `overflow: hidden` clipping regardless of where it is portaled.\n *\n * Default `null` → Radix falls back to `document.body` (unchanged behaviour\n * everywhere a provider isn't present).\n */\nexport const PortalContainerContext = React.createContext<HTMLElement | null>(\n null,\n)\n\n/** Read the active portal container (or `null` to use `document.body`). */\nexport function usePortalContainer(): HTMLElement | null {\n return React.useContext(PortalContainerContext)\n}\n\n/**\n * The element Radix overlays should treat as their COLLISION boundary — the\n * surface they must stay inside of.\n *\n * Separate from {@link PortalContainerContext} on purpose: the portal target is\n * a `display: contents` node (no box, so it can't be measured), while a\n * collision boundary must be a real, laid-out element.\n *\n * Why it matters: Radix positions overlays `position: fixed` and, by default,\n * flips/shifts them against the VIEWPORT. A menu opened near the top of a\n * panel therefore happily grows upward past the panel's edge and lands on top\n * of the app header — visually detached from the surface it belongs to. Handing\n * Radix the panel element instead keeps flip/shift (and the\n * `--radix-popper-available-height` a menu sizes itself by) inside that panel.\n *\n * Default `null` → viewport, i.e. Radix's own default, unchanged everywhere a\n * provider isn't present.\n */\nexport const CollisionBoundaryContext = React.createContext<HTMLElement | null>(\n null,\n)\n\n/** Read the active collision boundary (or `null` for the viewport). */\nexport function useCollisionBoundary(): HTMLElement | null {\n return React.useContext(CollisionBoundaryContext)\n}\n\n/** Padding (px) kept between an overlay and its boundary edge. Matches the\n * panel's own gutter so a shifted menu never sits flush against the edge. */\nexport const COLLISION_PADDING_PX = 8\n","\"use client\"\n\nimport * as React from 'react'\nimport { cn } from \"../../utils/cn\"\n\ninterface SkeletonProps extends React.HTMLAttributes<HTMLDivElement> {\n className?: string\n}\n\nconst Skeleton = React.forwardRef<HTMLDivElement, SkeletonProps>(\n ({ className, ...props }, ref) => {\n return (\n <div\n ref={ref}\n className={cn(\n \"animate-pulse rounded-md bg-ods-border\",\n className\n )}\n {...props}\n />\n )\n }\n)\nSkeleton.displayName = 'Skeleton'\n\ninterface SkeletonTextProps extends React.HTMLAttributes<HTMLDivElement> {\n lines?: number\n className?: string\n}\n\nconst SkeletonText = React.forwardRef<HTMLDivElement, SkeletonTextProps>(\n ({ lines = 1, className, ...props }, ref) => {\n return (\n <div ref={ref} className={cn(\"space-y-2\", className)} {...props}>\n {Array.from({ length: lines }).map((_, i) => (\n <Skeleton \n key={i} \n className={cn(\n \"h-4\",\n i === lines - 1 && lines > 1 && \"w-3/4\" // Last line shorter for multi-line\n )} \n />\n ))}\n </div>\n )\n }\n)\nSkeletonText.displayName = 'SkeletonText'\n\ninterface SkeletonCardProps extends React.HTMLAttributes<HTMLDivElement> {\n showImage?: boolean\n className?: string\n}\n\nconst SkeletonCard = React.forwardRef<HTMLDivElement, SkeletonCardProps>(\n ({ showImage = false, className, ...props }, ref) => {\n return (\n <div \n ref={ref}\n className={cn(\n \"rounded-lg border border-ods-border overflow-hidden\",\n className\n )}\n {...props}\n >\n {showImage && (\n <Skeleton className=\"h-48 w-full\" />\n )}\n <div className=\"p-6\">\n <Skeleton className=\"h-6 w-3/4 mb-4\" />\n <SkeletonText lines={3} />\n </div>\n </div>\n )\n }\n)\nSkeletonCard.displayName = 'SkeletonCard'\n\ninterface SkeletonGridProps extends React.HTMLAttributes<HTMLDivElement> {\n columns?: number\n items?: number\n showImages?: boolean\n className?: string\n}\n\nconst SkeletonGrid = React.forwardRef<HTMLDivElement, SkeletonGridProps>(\n ({ columns = 3, items = 6, showImages = false, className, ...props }, ref) => {\n return (\n <div \n ref={ref}\n className={cn(\n `grid grid-cols-1 gap-6`,\n columns === 2 && \"md:grid-cols-2\",\n columns === 3 && \"md:grid-cols-3\",\n columns === 4 && \"md:grid-cols-4\",\n className\n )}\n {...props}\n >\n {Array.from({ length: items }).map((_, i) => (\n <SkeletonCard key={i} showImage={showImages} />\n ))}\n </div>\n )\n }\n)\nSkeletonGrid.displayName = 'SkeletonGrid'\n\ninterface SkeletonButtonProps extends React.HTMLAttributes<HTMLDivElement> {\n size?: 'sm' | 'default' | 'lg'\n className?: string\n}\n\nconst SkeletonButton = React.forwardRef<HTMLDivElement, SkeletonButtonProps>(\n ({ size = 'default', className, ...props }, ref) => {\n const sizeClasses = {\n sm: 'h-8 w-20',\n default: 'h-10 w-32',\n lg: 'h-12 w-40'\n }\n \n return (\n <Skeleton \n ref={ref}\n className={cn(\n \"rounded-lg\",\n sizeClasses[size],\n className\n )}\n {...props}\n />\n )\n }\n)\nSkeletonButton.displayName = 'SkeletonButton'\n\ninterface SkeletonHeadingProps extends React.HTMLAttributes<HTMLDivElement> {\n level?: 1 | 2 | 3 | 4 | 5 | 6\n className?: string\n}\n\nconst SkeletonHeading = React.forwardRef<HTMLDivElement, SkeletonHeadingProps>(\n ({ level = 1, className, ...props }, ref) => {\n const heightClasses = {\n 1: 'h-12',\n 2: 'h-10',\n 3: 'h-8',\n 4: 'h-7',\n 5: 'h-6',\n 6: 'h-5'\n }\n \n return (\n <Skeleton \n ref={ref}\n className={cn(\n heightClasses[level],\n \"w-3/4\",\n className\n )}\n {...props}\n />\n )\n }\n)\nSkeletonHeading.displayName = 'SkeletonHeading'\n\ninterface SkeletonListProps extends React.HTMLAttributes<HTMLDivElement> {\n items?: number\n className?: string\n}\n\nconst SkeletonList = React.forwardRef<HTMLDivElement, SkeletonListProps>(\n ({ items = 5, className, ...props }, ref) => {\n return (\n <div ref={ref} className={cn(\"space-y-3\", className)} {...props}>\n {Array.from({ length: items }).map((_, i) => (\n <div key={i} className=\"flex items-center gap-3\">\n <Skeleton className=\"h-10 w-10 rounded-full flex-shrink-0\" />\n <div className=\"flex-1\">\n <Skeleton className=\"h-4 w-1/3 mb-1\" />\n <Skeleton className=\"h-3 w-1/2\" />\n </div>\n </div>\n ))}\n </div>\n )\n }\n)\nSkeletonList.displayName = 'SkeletonList'\n\ninterface SkeletonNavigationProps extends React.HTMLAttributes<HTMLDivElement> {\n items?: number\n className?: string\n}\n\nconst SkeletonNavigation = React.forwardRef<HTMLDivElement, SkeletonNavigationProps>(\n ({ items = 6, className, ...props }, ref) => {\n return (\n <div ref={ref} className={cn(\"flex items-center gap-6\", className)} {...props}>\n {Array.from({ length: items }).map((_, i) => (\n <Skeleton key={i} className=\"h-5 w-20\" />\n ))}\n </div>\n )\n }\n)\nSkeletonNavigation.displayName = 'SkeletonNavigation'\n\nexport { \n Skeleton,\n SkeletonText,\n SkeletonCard,\n SkeletonGrid,\n SkeletonButton,\n SkeletonHeading,\n SkeletonList,\n SkeletonNavigation\n}"]}