@refraction-ui/astro 0.12.0 → 0.14.0

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 (162) hide show
  1. package/dist/astro-audience-feature-card/AudienceFeatureCard.astro +53 -0
  2. package/dist/astro-audience-feature-card/index.ts +1 -0
  3. package/dist/astro-audio-room/AudioRoom.astro +81 -0
  4. package/dist/astro-audio-room/index.ts +1 -0
  5. package/dist/astro-brand-network-cell/BrandNetworkCell.astro +77 -0
  6. package/dist/astro-brand-network-cell/index.ts +1 -0
  7. package/dist/astro-browser-chrome-mock/BrowserChromeMock.astro +62 -0
  8. package/dist/astro-browser-chrome-mock/index.ts +1 -0
  9. package/dist/astro-call-controls/CallControls.astro +57 -0
  10. package/dist/astro-call-controls/index.ts +1 -0
  11. package/dist/astro-checklist/Checklist.astro +76 -0
  12. package/dist/astro-checklist/index.ts +1 -0
  13. package/dist/astro-editor-status-bar/EditorStatusBar.astro +92 -0
  14. package/dist/astro-editor-status-bar/index.ts +1 -0
  15. package/dist/astro-editor-tabs/EditorTabs.astro +74 -0
  16. package/dist/astro-editor-tabs/index.ts +1 -0
  17. package/dist/astro-floating-reactions/FloatingReactions.astro +56 -0
  18. package/dist/astro-floating-reactions/index.ts +1 -0
  19. package/dist/astro-flow-editor/FlowEditor.astro +99 -0
  20. package/dist/astro-flow-editor/index.ts +3 -0
  21. package/dist/astro-graph-view/GraphView.astro +109 -0
  22. package/dist/astro-graph-view/index.ts +1 -0
  23. package/dist/astro-infinite-canvas/InfiniteCanvas.astro +43 -0
  24. package/dist/astro-infinite-canvas/index.ts +1 -0
  25. package/dist/astro-live-captions/LiveCaptions.astro +54 -0
  26. package/dist/astro-live-captions/index.ts +1 -0
  27. package/dist/astro-live-cursors/LiveCursors.astro +55 -0
  28. package/dist/astro-live-cursors/index.ts +1 -0
  29. package/dist/astro-live-transcript/LiveTranscript.astro +55 -0
  30. package/dist/astro-live-transcript/index.ts +1 -0
  31. package/dist/astro-marquee-strip/MarqueeStrip.astro +63 -0
  32. package/dist/astro-marquee-strip/index.ts +1 -0
  33. package/dist/astro-mastery-bar/MasteryBar.astro +56 -0
  34. package/dist/astro-mastery-bar/index.ts +1 -0
  35. package/dist/astro-mini-map/MiniMap.astro +74 -0
  36. package/dist/astro-mini-map/index.ts +1 -0
  37. package/dist/astro-numbered-steps/NumberedSteps.astro +47 -0
  38. package/dist/astro-numbered-steps/index.ts +1 -0
  39. package/dist/astro-pre-call-lobby/PreCallLobby.astro +202 -0
  40. package/dist/astro-pre-call-lobby/index.ts +1 -0
  41. package/dist/astro-pricing-card/PricingCard.astro +114 -0
  42. package/dist/astro-pricing-card/index.ts +1 -0
  43. package/dist/astro-radial-gauge/RadialGauge.astro +144 -0
  44. package/dist/astro-radial-gauge/index.ts +1 -0
  45. package/dist/astro-rating-scale/RatingScale.astro +73 -0
  46. package/dist/astro-rating-scale/index.ts +1 -0
  47. package/dist/astro-section-head/SectionHead.astro +44 -0
  48. package/dist/astro-section-head/index.ts +1 -0
  49. package/dist/astro-stat-grid/StatGrid.astro +46 -0
  50. package/dist/astro-stat-grid/index.ts +1 -0
  51. package/dist/astro-sticky-note/StickyNote.astro +55 -0
  52. package/dist/astro-sticky-note/index.ts +1 -0
  53. package/dist/astro-terminal/Terminal.astro +67 -0
  54. package/dist/astro-terminal/index.ts +1 -0
  55. package/dist/astro-test-results/TestResults.astro +76 -0
  56. package/dist/astro-test-results/index.ts +1 -0
  57. package/dist/astro-timeline/Timeline.astro +59 -0
  58. package/dist/astro-timeline/index.ts +1 -0
  59. package/dist/astro-video-grid/VideoGrid.astro +182 -0
  60. package/dist/astro-video-grid/index.ts +1 -0
  61. package/dist/astro-video-tile/VideoTile.astro +104 -0
  62. package/dist/astro-video-tile/index.ts +1 -0
  63. package/dist/astro-wizard/Wizard.astro +137 -0
  64. package/dist/astro-wizard/index.ts +1 -0
  65. package/dist/audience-feature-card/audience-feature-card.styles.ts +23 -0
  66. package/dist/audience-feature-card/audience-feature-card.ts +39 -0
  67. package/dist/audience-feature-card/index.ts +13 -0
  68. package/dist/audio-room/audio-room.styles.ts +88 -0
  69. package/dist/audio-room/audio-room.ts +70 -0
  70. package/dist/audio-room/index.ts +16 -0
  71. package/dist/brand-network-cell/brand-network-cell.styles.ts +46 -0
  72. package/dist/brand-network-cell/brand-network-cell.ts +35 -0
  73. package/dist/brand-network-cell/index.ts +14 -0
  74. package/dist/browser-chrome-mock/browser-chrome-mock.styles.ts +43 -0
  75. package/dist/browser-chrome-mock/browser-chrome-mock.ts +52 -0
  76. package/dist/browser-chrome-mock/index.ts +17 -0
  77. package/dist/call-controls/call-controls.styles.ts +63 -0
  78. package/dist/call-controls/call-controls.ts +79 -0
  79. package/dist/call-controls/index.ts +15 -0
  80. package/dist/checklist/checklist.styles.ts +76 -0
  81. package/dist/checklist/checklist.ts +62 -0
  82. package/dist/checklist/index.ts +18 -0
  83. package/dist/editor-status-bar/editor-status-bar.styles.ts +40 -0
  84. package/dist/editor-status-bar/editor-status-bar.ts +78 -0
  85. package/dist/editor-status-bar/index.ts +15 -0
  86. package/dist/editor-tabs/editor-tabs.styles.ts +52 -0
  87. package/dist/editor-tabs/editor-tabs.ts +82 -0
  88. package/dist/editor-tabs/index.ts +15 -0
  89. package/dist/floating-reactions/floating-reactions.styles.ts +54 -0
  90. package/dist/floating-reactions/floating-reactions.ts +85 -0
  91. package/dist/floating-reactions/index.ts +14 -0
  92. package/dist/flow-editor/flow-editor.styles.ts +56 -0
  93. package/dist/flow-editor/flow-editor.ts +126 -0
  94. package/dist/flow-editor/index.ts +18 -0
  95. package/dist/graph-view/graph-view.styles.ts +66 -0
  96. package/dist/graph-view/graph-view.ts +130 -0
  97. package/dist/graph-view/index.ts +21 -0
  98. package/dist/index.ts +32 -0
  99. package/dist/infinite-canvas/index.ts +17 -0
  100. package/dist/infinite-canvas/infinite-canvas.styles.ts +49 -0
  101. package/dist/infinite-canvas/infinite-canvas.ts +97 -0
  102. package/dist/live-captions/index.ts +13 -0
  103. package/dist/live-captions/live-captions.styles.ts +45 -0
  104. package/dist/live-captions/live-captions.ts +57 -0
  105. package/dist/live-cursors/index.ts +14 -0
  106. package/dist/live-cursors/live-cursors.styles.ts +41 -0
  107. package/dist/live-cursors/live-cursors.ts +73 -0
  108. package/dist/live-transcript/index.ts +16 -0
  109. package/dist/live-transcript/live-transcript.styles.ts +48 -0
  110. package/dist/live-transcript/live-transcript.ts +82 -0
  111. package/dist/marquee-strip/index.ts +14 -0
  112. package/dist/marquee-strip/marquee-strip.styles.ts +61 -0
  113. package/dist/marquee-strip/marquee-strip.ts +35 -0
  114. package/dist/mastery-bar/index.ts +15 -0
  115. package/dist/mastery-bar/mastery-bar.styles.ts +45 -0
  116. package/dist/mastery-bar/mastery-bar.ts +55 -0
  117. package/dist/mini-map/index.ts +16 -0
  118. package/dist/mini-map/mini-map.styles.ts +32 -0
  119. package/dist/mini-map/mini-map.ts +122 -0
  120. package/dist/numbered-steps/index.ts +16 -0
  121. package/dist/numbered-steps/numbered-steps.styles.ts +36 -0
  122. package/dist/numbered-steps/numbered-steps.ts +64 -0
  123. package/dist/pre-call-lobby/index.ts +22 -0
  124. package/dist/pre-call-lobby/pre-call-lobby.styles.ts +108 -0
  125. package/dist/pre-call-lobby/pre-call-lobby.ts +61 -0
  126. package/dist/pricing-card/index.ts +19 -0
  127. package/dist/pricing-card/pricing-card.styles.ts +71 -0
  128. package/dist/pricing-card/pricing-card.ts +37 -0
  129. package/dist/radial-gauge/index.ts +26 -0
  130. package/dist/radial-gauge/radial-gauge.styles.ts +75 -0
  131. package/dist/radial-gauge/radial-gauge.ts +183 -0
  132. package/dist/rating-scale/index.ts +16 -0
  133. package/dist/rating-scale/rating-scale.styles.ts +48 -0
  134. package/dist/rating-scale/rating-scale.ts +101 -0
  135. package/dist/section-head/index.ts +13 -0
  136. package/dist/section-head/section-head.styles.ts +30 -0
  137. package/dist/section-head/section-head.ts +28 -0
  138. package/dist/stat-grid/index.ts +13 -0
  139. package/dist/stat-grid/stat-grid.styles.ts +25 -0
  140. package/dist/stat-grid/stat-grid.ts +41 -0
  141. package/dist/sticky-note/index.ts +14 -0
  142. package/dist/sticky-note/sticky-note.styles.ts +37 -0
  143. package/dist/sticky-note/sticky-note.ts +47 -0
  144. package/dist/terminal/index.ts +13 -0
  145. package/dist/terminal/terminal.styles.ts +56 -0
  146. package/dist/terminal/terminal.ts +60 -0
  147. package/dist/test-results/index.ts +19 -0
  148. package/dist/test-results/test-results.styles.ts +93 -0
  149. package/dist/test-results/test-results.ts +83 -0
  150. package/dist/timeline/index.ts +22 -0
  151. package/dist/timeline/timeline.styles.ts +98 -0
  152. package/dist/timeline/timeline.ts +86 -0
  153. package/dist/video-grid/index.ts +16 -0
  154. package/dist/video-grid/video-grid.styles.ts +38 -0
  155. package/dist/video-grid/video-grid.ts +93 -0
  156. package/dist/video-tile/index.ts +17 -0
  157. package/dist/video-tile/video-tile.styles.ts +59 -0
  158. package/dist/video-tile/video-tile.ts +74 -0
  159. package/dist/wizard/index.ts +28 -0
  160. package/dist/wizard/wizard.styles.ts +147 -0
  161. package/dist/wizard/wizard.ts +128 -0
  162. package/package.json +33 -1
@@ -0,0 +1,67 @@
1
+ ---
2
+ import {
3
+ createTerminal,
4
+ formatPrompt,
5
+ terminalVariants,
6
+ terminalLineVariants,
7
+ terminalPromptClass,
8
+ type TerminalLine,
9
+ } from '../terminal/index.ts'
10
+ import { cn } from '../shared/index.ts'
11
+
12
+ type TerminalMaxHeight = 'sm' | 'md' | 'lg' | 'none'
13
+
14
+ interface Props extends astroHTML.JSX.HTMLAttributes {
15
+ /** Lines to render in order. Each line's `kind` determines its styling. */
16
+ lines: TerminalLine[]
17
+ /**
18
+ * Prompt symbol prepended to `command` lines.
19
+ * @default '$'
20
+ */
21
+ promptSymbol?: string
22
+ /**
23
+ * Maximum height of the scroll region before it becomes scrollable.
24
+ * @default 'md'
25
+ */
26
+ maxHeight?: TerminalMaxHeight
27
+ /** Accessible label for the live region. */
28
+ 'aria-label'?: string
29
+ }
30
+
31
+ const {
32
+ lines,
33
+ promptSymbol = '$',
34
+ maxHeight = 'md',
35
+ 'aria-label': ariaLabel,
36
+ class: className,
37
+ ...rest
38
+ } = Astro.props
39
+
40
+ const { ariaProps, dataAttributes } = createTerminal({ label: ariaLabel })
41
+ ---
42
+
43
+ <div
44
+ class={cn(terminalVariants({ maxHeight }), className)}
45
+ {...ariaProps}
46
+ {...dataAttributes}
47
+ {...rest}
48
+ >
49
+ {
50
+ lines.map((line, index) => {
51
+ const lineClass = terminalLineVariants({ kind: line.kind })
52
+ if (line.kind === 'command') {
53
+ return (
54
+ <span key={line.id ?? index} class={lineClass}>
55
+ <span class={terminalPromptClass}>{promptSymbol}</span>
56
+ {line.text}
57
+ </span>
58
+ )
59
+ }
60
+ return (
61
+ <span key={line.id ?? index} class={lineClass}>
62
+ {line.text}
63
+ </span>
64
+ )
65
+ })
66
+ }
67
+ </div>
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,76 @@
1
+ ---
2
+ import {
3
+ summarizeTests,
4
+ createTestResults,
5
+ testResultsContainerVariants,
6
+ testResultsSummaryVariants,
7
+ testRowVariants,
8
+ testStatusBadgeVariants,
9
+ testNameClass,
10
+ testDurationClass,
11
+ testDiffBlockVariants,
12
+ testMessageClass,
13
+ type TestResultData,
14
+ } from '../test-results/index.ts'
15
+ import { cn } from '../shared/index.ts'
16
+
17
+ interface Props extends astroHTML.JSX.HTMLAttributes {
18
+ /** Array of test case results to display. */
19
+ results: TestResultData[]
20
+ /** When true, renders a summary bar showing "{passed}/{total} passed". */
21
+ showSummary?: boolean
22
+ }
23
+
24
+ const { results, showSummary = true, class: className, ...rest } = Astro.props
25
+
26
+ const summary = summarizeTests(results)
27
+ const { ariaProps, dataAttributes } = createTestResults()
28
+ const summaryOutcome = summary.failed > 0 ? 'fail' : summary.skipped > 0 ? 'skip' : 'pass'
29
+ ---
30
+
31
+ <div class={cn(testResultsContainerVariants(), className)} {...dataAttributes} {...rest}>
32
+ {showSummary && results.length > 0 && (
33
+ <div class={testResultsSummaryVariants({ outcome: summaryOutcome })} aria-live="polite">
34
+ <span>{summary.passed}/{summary.total} passed</span>
35
+ {summary.failed > 0 && <span>· {summary.failed} failed</span>}
36
+ {summary.skipped > 0 && <span>· {summary.skipped} skipped</span>}
37
+ </div>
38
+ )}
39
+
40
+ <ul {...ariaProps} class="flex flex-col gap-2">
41
+ {
42
+ results.map((result) => (
43
+ <li role="listitem" class={testRowVariants({ status: result.status })}>
44
+ <div class="flex items-center justify-between gap-2">
45
+ <div class="flex items-center gap-2 min-w-0">
46
+ <span class={testStatusBadgeVariants({ status: result.status })}>
47
+ {result.status}
48
+ </span>
49
+ <span class={cn(testNameClass, 'truncate')}>{result.name}</span>
50
+ </div>
51
+ {result.durationMs !== undefined && (
52
+ <span class={testDurationClass}>{result.durationMs} ms</span>
53
+ )}
54
+ </div>
55
+
56
+ {result.status === 'fail' && result.expected !== undefined && (
57
+ <div class="flex flex-col gap-1 mt-1">
58
+ <div class={testDiffBlockVariants({ side: 'expected' })}>
59
+ <span class="opacity-60 mr-1 select-none">expected</span>
60
+ {result.expected}
61
+ </div>
62
+ {result.actual !== undefined && (
63
+ <div class={testDiffBlockVariants({ side: 'actual' })}>
64
+ <span class="opacity-60 mr-1 select-none">actual</span>
65
+ {result.actual}
66
+ </div>
67
+ )}
68
+ </div>
69
+ )}
70
+
71
+ {result.message && <p class={testMessageClass}>{result.message}</p>}
72
+ </li>
73
+ ))
74
+ }
75
+ </ul>
76
+ </div>
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,59 @@
1
+ ---
2
+ import {
3
+ createTimeline,
4
+ resolveTimelineItems,
5
+ timelineVariants,
6
+ timelineItemVariants,
7
+ timelineMarkerVariants,
8
+ timelineConnectorVariants,
9
+ timelineGutterClass,
10
+ timelineTimeClass,
11
+ timelineTitleClass,
12
+ timelineDescriptionClass,
13
+ timelineContentClass,
14
+ type TimelineOrientation,
15
+ type TimelineItemData,
16
+ } from '../timeline/index.ts'
17
+ import { cn } from '../shared/index.ts'
18
+
19
+ interface Props extends astroHTML.JSX.HTMLAttributes {
20
+ /** Ordered list of events to render. */
21
+ items: TimelineItemData[]
22
+ /** Layout direction. Defaults to `'vertical'`. */
23
+ orientation?: TimelineOrientation
24
+ }
25
+
26
+ const {
27
+ items,
28
+ orientation = 'vertical',
29
+ class: className,
30
+ ...rest
31
+ } = Astro.props
32
+
33
+ const resolved = resolveTimelineItems(items)
34
+ const { ariaProps, dataAttributes } = createTimeline({ orientation })
35
+ ---
36
+
37
+ <ul class={cn(timelineVariants({ orientation }), className)} {...ariaProps} {...dataAttributes} {...rest}>
38
+ {
39
+ resolved.map((item, index) => {
40
+ const isLast = index === resolved.length - 1
41
+ return (
42
+ <li role="listitem" class={timelineItemVariants({ orientation })} data-status={item.status}>
43
+ {/* Gutter: marker + connector */}
44
+ <div class={timelineGutterClass}>
45
+ <div class={timelineMarkerVariants({ status: item.status })} />
46
+ {!isLast && <div class={timelineConnectorVariants({ orientation })} />}
47
+ </div>
48
+
49
+ {/* Content */}
50
+ <div class={timelineContentClass}>
51
+ {item.time && <span class={timelineTimeClass}>{item.time}</span>}
52
+ <span class={timelineTitleClass}>{item.title}</span>
53
+ {item.description && <p class={timelineDescriptionClass}>{item.description}</p>}
54
+ </div>
55
+ </li>
56
+ )
57
+ })
58
+ }
59
+ </ul>
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,182 @@
1
+ ---
2
+ import {
3
+ createVideoGrid,
4
+ computeGridColumns,
5
+ videoGridVariants,
6
+ videoGridSpotlightClass,
7
+ videoGridFilmstripClass,
8
+ videoGridFilmstripTileClass,
9
+ type VideoGridLayout,
10
+ } from '../video-grid/index.ts'
11
+ import { getInitials, type VideoTileData } from '../video-tile/index.ts'
12
+ import { cn } from '../shared/index.ts'
13
+
14
+ interface Props extends astroHTML.JSX.HTMLAttributes {
15
+ /** List of participant data to render as tiles. */
16
+ participants: VideoTileData[]
17
+ /**
18
+ * Grid layout mode.
19
+ * - 'auto' — grid unless ≤1 participant (falls back to speaker)
20
+ * - 'grid' — always uniform CSS grid
21
+ * - 'speaker' — spotlight for `spotlightId` (or first participant) + filmstrip
22
+ */
23
+ layout?: VideoGridLayout
24
+ /**
25
+ * Participant `id` to spotlight in speaker view.
26
+ * Falls back to the first participant when omitted.
27
+ */
28
+ spotlightId?: string
29
+ }
30
+
31
+ const {
32
+ participants,
33
+ layout = 'auto',
34
+ spotlightId,
35
+ class: className,
36
+ style,
37
+ ...rest
38
+ } = Astro.props
39
+
40
+ const count = participants.length
41
+
42
+ // Resolve effective layout: 'auto' with ≤1 participant shows speaker view.
43
+ const effectiveLayout: VideoGridLayout =
44
+ layout === 'auto' ? (count <= 1 ? 'speaker' : 'grid') : layout
45
+
46
+ const { ariaProps, dataAttributes } = createVideoGrid({ layout: effectiveLayout })
47
+ const columns = computeGridColumns(count)
48
+
49
+ const gridStyle =
50
+ effectiveLayout !== 'speaker'
51
+ ? `grid-template-columns: repeat(${columns}, 1fr);${style ? ` ${style}` : ''}`
52
+ : (style as string | undefined)
53
+
54
+ const spotlightParticipant =
55
+ effectiveLayout === 'speaker'
56
+ ? (participants.find((p) => p.id === spotlightId) ?? participants[0])
57
+ : undefined
58
+
59
+ const filmstrip =
60
+ effectiveLayout === 'speaker'
61
+ ? participants.filter((p) => p.id !== spotlightParticipant?.id)
62
+ : []
63
+
64
+ // Inline tile helper — renders VideoTile markup without importing the .astro
65
+ // file (which may not be built yet in a parallel-build context). When
66
+ // astro-video-tile ships its own VideoTile.astro, replace this with:
67
+ // import VideoTile from '../astro-video-tile/src/VideoTile.astro.ts'
68
+ function renderTileAttrs(p: VideoTileData): Record<string, string> {
69
+ const speaking = p.speaking ?? false
70
+ const pinned = p.pinned ?? false
71
+ return {
72
+ role: 'group',
73
+ 'data-speaking': speaking ? 'true' : 'false',
74
+ 'data-pinned': pinned ? 'true' : 'false',
75
+ }
76
+ }
77
+ ---
78
+
79
+ {effectiveLayout === 'speaker' ? (
80
+ <div
81
+ class={cn(videoGridVariants({ layout: 'speaker' }), className)}
82
+ style={style as string | undefined}
83
+ {...ariaProps}
84
+ {...dataAttributes}
85
+ {...rest}
86
+ >
87
+ {spotlightParticipant && (
88
+ <div class={videoGridSpotlightClass}>
89
+ {/* Inline VideoTile markup for the spotlight participant */}
90
+ <div
91
+ {...renderTileAttrs(spotlightParticipant)}
92
+ class="relative flex h-full w-full flex-col items-center justify-center bg-muted rounded-xl overflow-hidden"
93
+ aria-label={spotlightParticipant.name}
94
+ >
95
+ {spotlightParticipant.avatarUrl ? (
96
+ <img
97
+ src={spotlightParticipant.avatarUrl}
98
+ alt={spotlightParticipant.name}
99
+ class="h-full w-full object-cover"
100
+ />
101
+ ) : (
102
+ <span class="flex h-20 w-20 items-center justify-center rounded-full bg-primary text-3xl font-semibold text-primary-foreground">
103
+ {getInitials(spotlightParticipant.name)}
104
+ </span>
105
+ )}
106
+ <div class="absolute bottom-2 left-2 flex items-center gap-1 rounded-md bg-black/60 px-2 py-0.5 text-xs text-white">
107
+ {(spotlightParticipant.micState ?? 'on') === 'muted' && (
108
+ <span aria-label="Muted" role="img">🔇</span>
109
+ )}
110
+ <span>{spotlightParticipant.name}</span>
111
+ </div>
112
+ </div>
113
+ </div>
114
+ )}
115
+
116
+ {filmstrip.length > 0 && (
117
+ <div class={videoGridFilmstripClass}>
118
+ {filmstrip.map((p) => (
119
+ <div class={videoGridFilmstripTileClass}>
120
+ <div
121
+ {...renderTileAttrs(p)}
122
+ class="relative flex h-24 flex-col items-center justify-center bg-muted rounded-lg overflow-hidden"
123
+ aria-label={p.name}
124
+ >
125
+ {p.avatarUrl ? (
126
+ <img
127
+ src={p.avatarUrl}
128
+ alt={p.name}
129
+ class="h-full w-full object-cover"
130
+ />
131
+ ) : (
132
+ <span class="flex h-10 w-10 items-center justify-center rounded-full bg-primary text-sm font-semibold text-primary-foreground">
133
+ {getInitials(p.name)}
134
+ </span>
135
+ )}
136
+ <div class="absolute bottom-1 left-1 flex items-center gap-0.5 rounded bg-black/60 px-1 py-0.5 text-[10px] text-white">
137
+ {(p.micState ?? 'on') === 'muted' && (
138
+ <span aria-label="Muted" role="img">🔇</span>
139
+ )}
140
+ <span>{p.name}</span>
141
+ </div>
142
+ </div>
143
+ </div>
144
+ ))}
145
+ </div>
146
+ )}
147
+ </div>
148
+ ) : (
149
+ <div
150
+ class={cn(videoGridVariants({ layout: effectiveLayout }), className)}
151
+ style={gridStyle}
152
+ {...ariaProps}
153
+ {...dataAttributes}
154
+ {...rest}
155
+ >
156
+ {participants.map((p) => (
157
+ <div
158
+ {...renderTileAttrs(p)}
159
+ class="relative flex flex-col items-center justify-center bg-muted rounded-xl overflow-hidden aspect-video"
160
+ aria-label={p.name}
161
+ >
162
+ {p.avatarUrl ? (
163
+ <img
164
+ src={p.avatarUrl}
165
+ alt={p.name}
166
+ class="h-full w-full object-cover"
167
+ />
168
+ ) : (
169
+ <span class="flex h-14 w-14 items-center justify-center rounded-full bg-primary text-xl font-semibold text-primary-foreground">
170
+ {getInitials(p.name)}
171
+ </span>
172
+ )}
173
+ <div class="absolute bottom-2 left-2 flex items-center gap-1 rounded-md bg-black/60 px-2 py-0.5 text-xs text-white">
174
+ {(p.micState ?? 'on') === 'muted' && (
175
+ <span aria-label="Muted" role="img">🔇</span>
176
+ )}
177
+ <span>{p.name}</span>
178
+ </div>
179
+ </div>
180
+ ))}
181
+ </div>
182
+ )}
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,104 @@
1
+ ---
2
+ import {
3
+ createVideoTile,
4
+ getInitials,
5
+ videoTileVariants,
6
+ videoTileNameChipClass,
7
+ videoTileMicIconClass,
8
+ videoTileAvatarFallbackClass,
9
+ videoTileAvatarCircleClass,
10
+ videoTileReactionBadgeClass,
11
+ type VideoTileMicState,
12
+ } from '../video-tile/index.ts'
13
+ import { cn } from '../shared/index.ts'
14
+
15
+ interface Props extends astroHTML.JSX.HTMLAttributes {
16
+ /** Participant display name — shown in the name chip and used for initials. */
17
+ name: string
18
+ /** Current microphone state. Defaults to 'on'. */
19
+ micState?: VideoTileMicState
20
+ /** Whether the participant is actively speaking. Adds an emerald ring. */
21
+ speaking?: boolean
22
+ /** Whether the tile is pinned in the grid. Adds a primary ring. */
23
+ pinned?: boolean
24
+ /** URL of the participant's avatar image, used in the fallback circle. */
25
+ avatarUrl?: string
26
+ }
27
+
28
+ const {
29
+ name,
30
+ micState = 'on',
31
+ speaking = false,
32
+ pinned = false,
33
+ avatarUrl,
34
+ class: className,
35
+ ...rest
36
+ } = Astro.props
37
+
38
+ const { ariaProps, dataAttributes } = createVideoTile({ speaking, pinned })
39
+ const initials = getInitials(name)
40
+ const isMuted = micState === 'muted'
41
+ const hasDefaultSlot = Astro.slots.has('default')
42
+ ---
43
+
44
+ <div
45
+ class={cn(
46
+ videoTileVariants({
47
+ speaking: speaking ? 'true' : 'false',
48
+ pinned: pinned ? 'true' : 'false',
49
+ }),
50
+ className,
51
+ )}
52
+ aria-label={name}
53
+ {...ariaProps}
54
+ {...dataAttributes}
55
+ {...rest}
56
+ >
57
+ {hasDefaultSlot ? (
58
+ <!-- Media slot fills the tile -->
59
+ <div class="absolute inset-0 h-full w-full">
60
+ <slot />
61
+ </div>
62
+ ) : avatarUrl ? (
63
+ <!-- Avatar image fallback -->
64
+ <div class={videoTileAvatarFallbackClass}>
65
+ <img src={avatarUrl} alt={name} class={videoTileAvatarCircleClass} />
66
+ </div>
67
+ ) : (
68
+ <!-- Initials fallback -->
69
+ <div class={videoTileAvatarFallbackClass}>
70
+ <div class={videoTileAvatarCircleClass} aria-hidden="true">{initials}</div>
71
+ </div>
72
+ )}
73
+
74
+ {/* Reaction badge (top-right) — via named slot */}
75
+ <slot name="reaction" />
76
+
77
+ <!-- Name chip (bottom-left) -->
78
+ <div class={videoTileNameChipClass}>
79
+ <span>{name}</span>
80
+ {isMuted && (
81
+ <span class={videoTileMicIconClass} aria-label="Microphone muted">
82
+ <svg
83
+ xmlns="http://www.w3.org/2000/svg"
84
+ width="12"
85
+ height="12"
86
+ viewBox="0 0 24 24"
87
+ fill="none"
88
+ stroke="currentColor"
89
+ stroke-width="2"
90
+ stroke-linecap="round"
91
+ stroke-linejoin="round"
92
+ aria-hidden="true"
93
+ >
94
+ <line x1="2" y1="2" x2="22" y2="22" />
95
+ <path d="M18.89 13.23A7.12 7.12 0 0 0 19 12v-2" />
96
+ <path d="M5 10v2a7 7 0 0 0 12 5" />
97
+ <path d="M15 9.34V5a3 3 0 0 0-5.68-1.33" />
98
+ <path d="M9 9v3a3 3 0 0 0 5.12 2.12" />
99
+ <line x1="12" y1="19" x2="12" y2="22" />
100
+ </svg>
101
+ </span>
102
+ )}
103
+ </div>
104
+ </div>
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,137 @@
1
+ ---
2
+ import {
3
+ createWizard,
4
+ getStepState,
5
+ wizardVariants,
6
+ wizardRailVariants,
7
+ wizardStepItemVariants,
8
+ wizardStepIndicatorVariants,
9
+ wizardStepLabelVariants,
10
+ wizardStepOptionalClass,
11
+ wizardConnectorVariants,
12
+ wizardContentClass,
13
+ wizardFooterClass,
14
+ wizardPrimaryButtonVariants,
15
+ wizardSecondaryButtonVariants,
16
+ type WizardStep,
17
+ type WizardOrientation,
18
+ } from '../wizard/index.ts'
19
+ import { cn } from '../shared/index.ts'
20
+
21
+ interface Props extends astroHTML.JSX.HTMLAttributes {
22
+ /** Step definitions for the rail. */
23
+ steps: WizardStep[]
24
+ /** Current step index (0-based). */
25
+ currentStep?: number
26
+ /** Orientation of the step rail. */
27
+ orientation?: WizardOrientation
28
+ /** Label for the Next button. */
29
+ nextLabel?: string
30
+ /** Label for the Back button. */
31
+ backLabel?: string
32
+ /** Label for the button on the final step. */
33
+ completeLabel?: string
34
+ /** Label for the Skip button (shown on optional steps only). */
35
+ skipLabel?: string
36
+ }
37
+
38
+ const {
39
+ steps,
40
+ currentStep = 0,
41
+ orientation = 'vertical',
42
+ nextLabel = 'Next',
43
+ backLabel = 'Back',
44
+ completeLabel = 'Complete',
45
+ skipLabel = 'Skip',
46
+ class: className,
47
+ ...rest
48
+ } = Astro.props
49
+
50
+ const stepStates = getStepState(steps, currentStep)
51
+ const isLastStep = currentStep === steps.length - 1
52
+ const isOptional = steps[currentStep]?.optional ?? false
53
+ const { ariaProps, dataAttributes } = createWizard({
54
+ currentIndex: currentStep,
55
+ count: steps.length,
56
+ orientation,
57
+ })
58
+ ---
59
+
60
+ <div class={cn(wizardVariants({ orientation }), className)} {...ariaProps} {...dataAttributes} {...rest}>
61
+ <!-- Step Rail -->
62
+ <nav aria-label="Progress" class={wizardRailVariants({ orientation })}>
63
+ {
64
+ stepStates.map((state, i) => {
65
+ const isLast = i === stepStates.length - 1
66
+ return (
67
+ <>
68
+ <div class={wizardStepItemVariants({ orientation, status: state.status })}>
69
+ <span
70
+ aria-current={state.status === 'current' ? 'step' : undefined}
71
+ class={wizardStepIndicatorVariants({ status: state.status })}
72
+ >
73
+ {state.status === 'complete' ? (
74
+ <svg
75
+ aria-hidden="true"
76
+ xmlns="http://www.w3.org/2000/svg"
77
+ viewBox="0 0 24 24"
78
+ fill="none"
79
+ stroke="currentColor"
80
+ stroke-width="2.5"
81
+ stroke-linecap="round"
82
+ stroke-linejoin="round"
83
+ class="size-4"
84
+ >
85
+ <polyline points="20 6 9 17 4 12" />
86
+ </svg>
87
+ ) : (
88
+ <span>{state.index + 1}</span>
89
+ )}
90
+ </span>
91
+ <div class="flex flex-col">
92
+ <span class={wizardStepLabelVariants({ status: state.status })}>
93
+ {state.step.label}
94
+ </span>
95
+ {state.step.optional && (
96
+ <span class={wizardStepOptionalClass}>(Optional)</span>
97
+ )}
98
+ </div>
99
+ </div>
100
+ {!isLast && (
101
+ <div
102
+ aria-hidden="true"
103
+ class={wizardConnectorVariants({
104
+ orientation,
105
+ complete: state.status === 'complete' ? 'true' : 'false',
106
+ })}
107
+ />
108
+ )}
109
+ </>
110
+ )
111
+ })
112
+ }
113
+ </nav>
114
+
115
+ <!-- Content + Footer -->
116
+ <div class={wizardContentClass}>
117
+ <div class="flex-1">
118
+ <slot />
119
+ </div>
120
+
121
+ <div class={wizardFooterClass}>
122
+ <button type="button" disabled={currentStep === 0} class={wizardSecondaryButtonVariants()}>
123
+ {backLabel}
124
+ </button>
125
+
126
+ {isOptional && (
127
+ <button type="button" class={wizardSecondaryButtonVariants()}>
128
+ {skipLabel}
129
+ </button>
130
+ )}
131
+
132
+ <button type="button" class={wizardPrimaryButtonVariants()}>
133
+ {isLastStep ? completeLabel : nextLabel}
134
+ </button>
135
+ </div>
136
+ </div>
137
+ </div>
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,23 @@
1
+ import { cva } from '../shared/index.ts'
2
+
3
+ /** Container — full-height flex column so footers align across a row. */
4
+ export const audienceFeatureCardVariants = cva({
5
+ base: 'flex h-full flex-col rounded-xl border border-border bg-card p-5',
6
+ variants: {},
7
+ defaultVariants: {},
8
+ })
9
+
10
+ /** Kicker text above the title. */
11
+ export const audienceFeatureCardKickerClass =
12
+ 'text-[10px] uppercase tracking-widest text-primary font-semibold'
13
+
14
+ /** Main title. */
15
+ export const audienceFeatureCardTitleClass =
16
+ 'mt-2 text-lg font-bold text-foreground'
17
+
18
+ /** Body copy. */
19
+ export const audienceFeatureCardBodyClass =
20
+ 'mt-1 text-sm text-muted-foreground leading-relaxed'
21
+
22
+ /** Footer area — pushed to bottom via mt-auto. */
23
+ export const audienceFeatureCardFooterClass = 'mt-auto pt-4 text-xs'